Design system foundation — Tailwind 4 theme, CSS tokens, fonts #31
394
specs/frontend/j1-add-recipe.html
Normal file
394
specs/frontend/j1-add-recipe.html
Normal file
@@ -0,0 +1,394 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8"/>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
|
||||
<title>J1 — Add a Recipe</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"/>
|
||||
<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:#F2C12E;--yellow-dark:#C49610;--yellow-text:#8A6800;--blue-tint:#E6F1FB;--blue-light:#A4CFF4;--blue:#2D7DD2;--blue-dark:#185FA5;--purple-tint:#EEEDFE;--purple:#534AB7;--purple-dark:#3C3489;--orange-tint:#FEF0E6;--orange:#E8862A;--orange-dark:#B46820;--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-xl:16px;--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,.08),0 2px 4px rgba(28,28,24,.04);--shadow-overlay:0 8px 32px rgba(28,28,24,.12),0 2px 8px rgba(28,28,24,.06);}
|
||||
*,*::before,*::after{box-sizing:border-box;margin:0;padding:0;}
|
||||
body{font-family:var(--font-sans);background:#E8E7E2;color:var(--color-text);font-size:14px;line-height:1.6;}
|
||||
.doc{max-width:1200px;margin:0 auto;padding:48px 40px 120px;}
|
||||
|
||||
/* Header */
|
||||
.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;background:var(--color-page);margin:-48px -40px 48px;padding:48px 40px 28px;border-radius:var(--radius-xl) var(--radius-xl) 0 0;}
|
||||
.doc-header h1{font-family:var(--font-display);font-size:28px;font-weight:500;letter-spacing:-.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;}
|
||||
.pill{display:inline-block;padding:2px 8px;border-radius:var(--radius-sm);font-size:10px;font-weight:500;letter-spacing:.05em;background:var(--green-tint);color:var(--green-dark);}
|
||||
|
||||
/* Sections */
|
||||
.section{margin-bottom:64px;}
|
||||
.section-title{font-size:10px;font-weight:500;letter-spacing:.12em;text-transform:uppercase;color:var(--color-text-muted);padding-bottom:10px;border-bottom:1px solid var(--color-border);margin-bottom:24px;}
|
||||
.prose{font-size:13px;color:var(--color-text-muted);line-height:1.65;max-width:720px;margin-bottom:20px;}
|
||||
|
||||
/* Journey headers */
|
||||
.jh{padding:20px 24px;border-radius:var(--radius-xl);margin-bottom:40px;display:flex;align-items:center;gap:16px;}
|
||||
.jh .jn{font-family:var(--font-display);font-size:48px;font-weight:300;line-height:1;opacity:.5;}
|
||||
.jh h2{font-family:var(--font-display);font-size:22px;font-weight:500;letter-spacing:-.02em;margin-bottom:4px;}
|
||||
.jh p{font-size:13px;line-height:1.5;}.jh .fl{font-family:var(--font-mono);font-size:11px;margin-top:6px;opacity:.7;}
|
||||
.jh-p{background:var(--purple-tint);border:1px solid #CECBF6;}.jh-p .jn{color:var(--purple);}.jh-p p,.jh-p .fl{color:var(--purple-dark);}
|
||||
.jh-g{background:var(--green-tint);border:1px solid var(--green-light);}.jh-g .jn{color:var(--green);}.jh-g p,.jh-g .fl{color:var(--green-dark);}
|
||||
.jh-y{background:var(--yellow-tint);border:1px solid var(--yellow-light);}.jh-y .jn{color:var(--yellow-dark);}.jh-y p,.jh-y .fl{color:var(--yellow-text);}
|
||||
.jh-o{background:var(--orange-tint);border:1px solid #FBCDA4;}.jh-o .jn{color:var(--orange);}.jh-o p,.jh-o .fl{color:var(--orange-dark);}
|
||||
.jh-b{background:var(--blue-tint);border:1px solid var(--blue-light);}.jh-b .jn{color:var(--blue);}.jh-b p,.jh-b .fl{color:var(--blue-dark);}
|
||||
|
||||
/* Screen block */
|
||||
.scr{margin-bottom:56px;}
|
||||
.scr-head{display:flex;justify-content:space-between;align-items:center;margin-bottom:6px;}
|
||||
.scr-head h3{font-family:var(--font-display);font-size:20px;font-weight:500;letter-spacing:-.02em;}
|
||||
.scr-id{font-family:var(--font-mono);font-size:11px;color:var(--color-text-muted);padding:2px 8px;border:1px solid var(--color-border);border-radius:var(--radius-sm);background:var(--color-page);}
|
||||
.scr-desc{font-size:12px;color:var(--color-text-muted);line-height:1.6;max-width:720px;margin-bottom:6px;}
|
||||
.scr-var{font-size:11px;color:var(--color-text-muted);margin-bottom:20px;}.scr-var strong{color:var(--color-text);}
|
||||
|
||||
/* Preview container */
|
||||
.previews{display:flex;gap:32px;flex-wrap:wrap;justify-content:center;align-items:flex-start;margin-bottom:20px;}
|
||||
.prev-col{display:flex;flex-direction:column;align-items:center;gap:10px;}
|
||||
.bp-lbl{font-family:var(--font-mono);font-size:10px;color:var(--color-text-muted);}
|
||||
|
||||
/* Phone frame - 320px */
|
||||
.phone{width:320px;flex-shrink:0;background:var(--color-page);border-radius:36px;overflow:hidden;box-shadow:var(--shadow-overlay),0 0 0 1px rgba(0,0,0,.07);display:flex;flex-direction:column;border:6px solid #1C1C18;}
|
||||
.pst{padding:10px 20px 0;display:flex;justify-content:space-between;align-items:center;font-size:12px;background:var(--color-page);}.pst b{font-weight:600;}.pst span{font-size:10px;}
|
||||
.pb{flex:1;overflow-y:auto;display:flex;flex-direction:column;}
|
||||
|
||||
/* Desktop frame - 1040px */
|
||||
.desk{width:100%;max-width:1040px;background:var(--color-page);border-radius:var(--radius-xl);overflow:hidden;box-shadow:var(--shadow-overlay),0 0 0 1px rgba(0,0,0,.06);display:flex;min-height:520px;}
|
||||
|
||||
/* Shared nav components */
|
||||
.mtb{padding:10px 16px;background:var(--color-page);border-bottom:1px solid var(--color-border);display:flex;justify-content:space-between;align-items:center;}
|
||||
.mtb-t{font-family:var(--font-display);font-size:20px;font-weight:500;letter-spacing:-.02em;}
|
||||
.mi{width:32px;height:32px;border-radius:var(--radius-md);border:1px solid var(--color-border);background:var(--color-surface);display:flex;align-items:center;justify-content:center;font-size:13px;color:var(--color-text-muted);flex-shrink:0;}.mi.gn{background:var(--green);border-color:var(--green);color:#fff;}
|
||||
.mbt{border-top:1px solid var(--color-border);background:var(--color-surface);padding:8px 16px 28px;display:flex;justify-content:space-around;}
|
||||
.mt-i{display:flex;flex-direction:column;align-items:center;gap:2px;}.mt-ic{width:20px;height:20px;border-radius:4px;background:var(--color-subtle);display:flex;align-items:center;justify-content:center;font-size:11px;}.mt-i.a .mt-ic{background:var(--green-tint);}.mt-l{font-size:9px;font-weight:500;color:var(--color-text-muted);}.mt-i.a .mt-l{color:var(--green-dark);}
|
||||
|
||||
/* Desktop sidebar - 224px per nav-spec */
|
||||
.dsb{width:224px;flex-shrink:0;background:var(--color-surface);border-right:1px solid var(--color-border);display:flex;flex-direction:column;}
|
||||
.dsb-logo{padding:20px 16px 16px;border-bottom:1px solid var(--color-border);}
|
||||
.dsb-lm{display:flex;align-items:center;gap:8px;margin-bottom:2px;}.dsb-ic{width:24px;height:24px;border-radius:5px;background:var(--green);display:flex;align-items:center;justify-content:center;font-size:12px;}.dsb-nm{font-family:var(--font-display);font-size:16px;font-weight:500;letter-spacing:-.02em;}.dsb-sub{font-size:10px;color:var(--color-text-muted);padding-left:32px;}
|
||||
.dsb-nav{padding:12px 10px;flex:1;}.dsb-nl{font-size:8px;font-weight:500;letter-spacing:.1em;text-transform:uppercase;color:var(--color-text-muted);padding:0 8px;margin-bottom:4px;}.dsb-ni{display:flex;align-items:center;gap:8px;padding:7px 8px;border-radius:var(--radius-md);font-size:13px;color:var(--color-text-muted);margin-bottom:2px;cursor:default;}.dsb-ni.a{background:var(--green-tint);color:var(--green-dark);font-weight:500;}.dsb-nc{font-size:13px;width:18px;text-align:center;}
|
||||
.dm{flex:1;display:flex;flex-direction:column;min-width:0;}
|
||||
.dtb{padding:12px 24px;border-bottom:1px solid var(--color-border);display:flex;justify-content:space-between;align-items:center;flex-shrink:0;}
|
||||
.dtb-t{font-family:var(--font-display);font-size:18px;font-weight:500;letter-spacing:-.02em;}
|
||||
.dtb-r{display:flex;align-items:center;gap:8px;}
|
||||
.dab{font-family:var(--font-sans);font-size:13px;font-weight:500;padding:7px 16px;border-radius:var(--radius-md);background:var(--green);color:#fff;border:none;}
|
||||
.dab-b{background:var(--blue);}
|
||||
|
||||
/* Shared form */
|
||||
.fi{width:100%;font-family:var(--font-sans);font-size:14px;padding:10px 12px;border-radius:var(--radius-md);border:1px solid var(--color-border);background:var(--color-page);color:var(--color-text);outline:none;}
|
||||
.fl{font-size:12px;font-weight:500;color:var(--color-text);margin-bottom:6px;display:block;}
|
||||
.fg{margin-bottom:16px;}
|
||||
.bp{font-family:var(--font-sans);font-size:14px;font-weight:500;padding:12px 24px;border-radius:var(--radius-md);background:var(--green);color:#fff;border:none;cursor:pointer;width:100%;}
|
||||
.bg{font-family:var(--font-sans);font-size:13px;font-weight:500;padding:10px 20px;border-radius:var(--radius-md);background:var(--color-subtle);color:var(--color-text-muted);border:1px solid var(--color-border);cursor:pointer;}
|
||||
|
||||
/* Tags */
|
||||
.tc{display:inline-flex;font-size:12px;font-weight:500;padding:6px 12px;border-radius:20px;border:1px solid var(--color-border);background:var(--color-surface);color:var(--color-text-muted);cursor:pointer;margin:0 4px 4px 0;user-select:none;}.tc.s{background:var(--green-tint);color:var(--green-dark);border-color:var(--green-light);}
|
||||
.badge{font-size:9px;font-weight:500;padding:2px 6px;border-radius:3px;display:inline-block;}.badge-g{background:var(--green-tint);color:var(--green-dark);}.badge-y{background:var(--yellow-tint);color:var(--yellow-text);}.badge-m{background:var(--color-subtle);color:var(--color-text-muted);}
|
||||
|
||||
/* Ingredient rows */
|
||||
.ir{display:flex;align-items:center;gap:8px;padding:8px 0;border-bottom:1px solid var(--color-subtle);}.ir:last-child{border-bottom:none;}.ir-q{font-family:var(--font-mono);font-size:12px;color:var(--color-text-muted);width:55px;flex-shrink:0;text-align:right;}.ir-n{font-size:13px;color:var(--color-text);flex:1;}.ir-x{font-size:14px;color:var(--color-border);cursor:pointer;width:20px;text-align:center;}
|
||||
|
||||
/* Checklist */
|
||||
.ck{display:flex;align-items:center;gap:10px;padding:10px 0;border-bottom:1px solid var(--color-subtle);cursor:pointer;}.ck:last-child{border-bottom:none;}.ck-b{width:22px;height:22px;border-radius:4px;border:2px solid var(--color-border);flex-shrink:0;display:flex;align-items:center;justify-content:center;font-size:11px;}.ck.d .ck-b{background:var(--green);border-color:var(--green);color:#fff;}.ck-c{flex:1;}.ck-n{font-size:14px;color:var(--color-text);}.ck.d .ck-n{text-decoration:line-through;color:var(--color-text-muted);}.ck-s{font-size:10px;color:var(--color-text-muted);}.ck-q{font-family:var(--font-mono);font-size:12px;color:var(--color-text-muted);flex-shrink:0;}
|
||||
|
||||
/* Suggestion cards */
|
||||
.sg{background:var(--color-surface);border:1px solid var(--color-border);border-radius:var(--radius-lg);padding:12px;margin-bottom:8px;display:flex;align-items:center;gap:10px;}.sg-r{font-family:var(--font-display);font-size:16px;font-weight:300;color:var(--color-text-muted);width:20px;text-align:center;flex-shrink:0;}.sg-b{flex:1;}.sg-n{font-family:var(--font-display);font-size:13px;font-weight:400;color:var(--color-text);margin-bottom:2px;}.sg-i{font-size:10px;color:var(--color-text-muted);}.sg-w{font-size:9px;color:var(--green-dark);background:var(--green-tint);padding:2px 6px;border-radius:3px;display:inline-block;margin-top:3px;}.sg-p{font-size:11px;font-weight:500;color:var(--green);flex-shrink:0;}
|
||||
|
||||
/* Eyebrow labels */
|
||||
.eye{font-size:10px;font-weight:500;letter-spacing:.08em;text-transform:uppercase;color:var(--color-text-muted);}
|
||||
|
||||
/* Agent table */
|
||||
.agent{background:var(--color-text);color:#E8E8E2;padding:24px;border-radius:var(--radius-lg);margin-top:20px;}
|
||||
.agent h4{font-size:9px;font-weight:500;letter-spacing:.1em;text-transform:uppercase;color:#5A5A55;margin-bottom:12px;}
|
||||
.agent pre{font-family:var(--font-mono);font-size:10px;color:#444440;margin-bottom:16px;line-height:1.8;white-space:pre-wrap;}
|
||||
.at{width:100%;border-collapse:collapse;font-family:var(--font-mono);font-size:10px;}
|
||||
.at thead tr{border-bottom:1px solid #2A2A26;}.at th{text-align:left;padding:6px 10px;font-size:8px;font-weight:500;letter-spacing:.08em;text-transform:uppercase;color:#5A5A55;font-family:var(--font-sans);}.at td{padding:5px 10px;border-bottom:1px solid #1E1E1A;vertical-align:top;line-height:1.5;}.at tr:last-child td{border-bottom:none;}.at td:first-child{color:#7A7A72;}.at td:nth-child(2){color:#E8E8E2;font-weight:500;}.at td:nth-child(3){color:#5A5A55;}.at .grp td{padding-top:14px;font-family:var(--font-sans);font-size:8px;font-weight:500;letter-spacing:.08em;text-transform:uppercase;color:#3A3A36;}
|
||||
|
||||
/* LLM instruction box */
|
||||
.llm{background:var(--color-page);border:2px solid var(--green);border-radius:var(--radius-xl);padding:32px 40px;margin-top:64px;}
|
||||
.llm h2{font-family:var(--font-display);font-size:22px;font-weight:500;letter-spacing:-.02em;margin-bottom:8px;color:var(--green-dark);}
|
||||
.llm h3{font-size:14px;font-weight:600;margin:20px 0 8px;color:var(--color-text);}
|
||||
.llm p,.llm li{font-size:13px;color:var(--color-text-muted);line-height:1.65;}
|
||||
.llm ul,.llm ol{padding-left:20px;margin-bottom:12px;}
|
||||
.llm li{margin-bottom:4px;}
|
||||
.llm code{font-family:var(--font-mono);font-size:11px;background:var(--color-surface);padding:1px 5px;border-radius:3px;}
|
||||
.llm table{width:100%;border-collapse:collapse;margin:12px 0;font-size:12px;}
|
||||
.llm th,.llm td{text-align:left;padding:6px 10px;border-bottom:1px solid var(--color-border);}
|
||||
.llm th{font-weight:500;color:var(--color-text);font-size:11px;text-transform:uppercase;letter-spacing:.05em;}
|
||||
.llm td{color:var(--color-text-muted);}
|
||||
|
||||
@media(max-width:900px){.doc{padding:24px 16px 80px;}}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="doc">
|
||||
|
||||
<div class="jh jh-g">
|
||||
<div class="jn">J1</div>
|
||||
<div><h2>Add a recipe</h2><p>Save recipe with ingredients, steps, tags, and hero image.</p><div class="fl">B1 → B3 → B1 · Planner · Min: effort + 1 category tag</div></div>
|
||||
</div>
|
||||
|
||||
<!-- ═══ B1 RECIPE LIBRARY ═══ -->
|
||||
<div class="scr" id="b1">
|
||||
<div class="scr-head"><h3>Recipe library</h3><span class="scr-id">B1</span></div>
|
||||
<div class="scr-desc">Browse all recipes. Desktop: app sidebar + topbar with search bar and filter chips + 4-column card grid. The grid fills the content area naturally — not wrapped in an additional card or panel.</div>
|
||||
<div class="scr-var"><strong>V2 · Card grid</strong> — mobile 2-col, desktop sidebar + 4-col with filters</div>
|
||||
|
||||
<div class="previews">
|
||||
<div class="prev-col">
|
||||
<div class="bp-lbl">Mobile · 320px</div>
|
||||
<div class="phone">
|
||||
<div class="pst"><b>9:41</b><span>●●● WiFi 🔋</span></div>
|
||||
<div class="pb">
|
||||
<div class="mtb"><div class="mtb-t">Recipes</div><div style="display:flex;gap:6px;"><div class="mi">🔍</div><div class="mi gn">+</div></div></div>
|
||||
<div style="padding:10px 12px;">
|
||||
<div style="display:grid;grid-template-columns:1fr 1fr;gap:8px;">
|
||||
<div style="background:var(--color-surface);border:1px solid var(--color-border);border-radius:var(--radius-lg);overflow:hidden;"><div style="height:64px;background:var(--green-tint);display:flex;align-items:center;justify-content:center;font-size:28px;">🍝</div><div style="padding:8px 10px;"><div style="font-family:var(--font-display);font-size:12px;margin-bottom:3px;">Tomato pasta</div><div style="font-size:9px;color:var(--color-text-muted);">45 min · Easy</div></div></div>
|
||||
<div style="background:var(--color-surface);border:1px solid var(--color-border);border-radius:var(--radius-lg);overflow:hidden;"><div style="height:64px;background:var(--yellow-tint);display:flex;align-items:center;justify-content:center;font-size:28px;">🍗</div><div style="padding:8px 10px;"><div style="font-family:var(--font-display);font-size:12px;margin-bottom:3px;">Chicken stir-fry</div><div style="font-size:9px;color:var(--color-text-muted);">25 min · Easy</div></div></div>
|
||||
<div style="background:var(--color-surface);border:1px solid var(--color-border);border-radius:var(--radius-lg);overflow:hidden;"><div style="height:64px;background:var(--blue-tint);display:flex;align-items:center;justify-content:center;font-size:28px;">🐟</div><div style="padding:8px 10px;"><div style="font-family:var(--font-display);font-size:12px;margin-bottom:3px;">Salmon teriyaki</div><div style="font-size:9px;color:var(--color-text-muted);">35 min · Medium</div></div></div>
|
||||
<div style="background:var(--color-surface);border:1px solid var(--color-border);border-radius:var(--radius-lg);overflow:hidden;"><div style="height:64px;background:var(--green-tint);display:flex;align-items:center;justify-content:center;font-size:28px;">🍄</div><div style="padding:8px 10px;"><div style="font-family:var(--font-display);font-size:12px;margin-bottom:3px;">Mushroom risotto</div><div style="font-size:9px;color:var(--color-text-muted);">50 min · Medium</div></div></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mbt"><div class="mt-i"><div class="mt-ic">📅</div><div class="mt-l">Planner</div></div><div class="mt-i a"><div class="mt-ic">📖</div><div class="mt-l">Recipes</div></div><div class="mt-i"><div class="mt-ic">🛒</div><div class="mt-l">Shopping</div></div><div class="mt-i"><div class="mt-ic">⚙️</div><div class="mt-l">Settings</div></div></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="prev-col">
|
||||
<div class="bp-lbl">Desktop · 1040px</div>
|
||||
<div class="desk">
|
||||
<div class="dsb">
|
||||
<div class="dsb-logo"><div class="dsb-lm"><div class="dsb-ic">🥗</div><div class="dsb-nm">Mealplan</div></div><div class="dsb-sub">Smith household</div></div>
|
||||
<div class="dsb-nav"><div style="margin-bottom:16px;"><div class="dsb-nl">Plan</div><div class="dsb-ni"><span class="dsb-nc">📅</span>Planner</div><div class="dsb-ni a"><span class="dsb-nc">📖</span>Recipes</div><div class="dsb-ni"><span class="dsb-nc">🛒</span>Shopping</div></div><div><div class="dsb-nl">Account</div><div class="dsb-ni"><span class="dsb-nc">👨👩👧</span>Household</div><div class="dsb-ni"><span class="dsb-nc">⚙️</span>Settings</div></div></div>
|
||||
</div>
|
||||
<div class="dm">
|
||||
<div class="dtb"><div class="dtb-t">Recipe library</div><div class="dtb-r"><input class="fi" style="width:220px;font-size:12px;padding:7px 12px;" placeholder="🔍 Search recipes…"/><button class="dab">+ Add recipe</button></div></div>
|
||||
<div style="flex:1;padding:20px 24px;overflow-y:auto;">
|
||||
<!-- Filter chips -->
|
||||
<div style="display:flex;gap:5px;margin-bottom:16px;">
|
||||
<div style="font-size:11px;font-weight:500;padding:5px 14px;border-radius:12px;background:var(--green-tint);color:var(--green-dark);">All (24)</div>
|
||||
<div style="font-size:11px;font-weight:500;padding:5px 14px;border-radius:12px;border:1px solid var(--color-border);color:var(--color-text-muted);">Easy</div>
|
||||
<div style="font-size:11px;font-weight:500;padding:5px 14px;border-radius:12px;border:1px solid var(--color-border);color:var(--color-text-muted);">Medium</div>
|
||||
<div style="font-size:11px;font-weight:500;padding:5px 14px;border-radius:12px;border:1px solid var(--color-border);color:var(--color-text-muted);">Chicken</div>
|
||||
<div style="font-size:11px;font-weight:500;padding:5px 14px;border-radius:12px;border:1px solid var(--color-border);color:var(--color-text-muted);">Fish</div>
|
||||
<div style="font-size:11px;font-weight:500;padding:5px 14px;border-radius:12px;border:1px solid var(--color-border);color:var(--color-text-muted);">Veggie</div>
|
||||
</div>
|
||||
<!-- 4-col grid -->
|
||||
<div style="display:grid;grid-template-columns:repeat(4,1fr);gap:12px;">
|
||||
<div style="background:var(--color-surface);border:1px solid var(--color-border);border-radius:var(--radius-lg);overflow:hidden;cursor:pointer;"><div style="height:100px;background:var(--green-tint);display:flex;align-items:center;justify-content:center;font-size:32px;">🍝</div><div style="padding:10px 12px;"><div style="font-family:var(--font-display);font-size:14px;font-weight:400;margin-bottom:4px;">Slow-roasted tomato pasta</div><div style="font-size:10px;color:var(--color-text-muted);margin-bottom:6px;">45 min · Easy · Last cooked 3 days ago</div><div style="display:flex;gap:3px;"><span class="badge badge-g">Vegetarian</span><span class="badge badge-y">Child-friendly</span></div></div></div>
|
||||
<div style="background:var(--color-surface);border:1px solid var(--color-border);border-radius:var(--radius-lg);overflow:hidden;"><div style="height:100px;background:var(--yellow-tint);display:flex;align-items:center;justify-content:center;font-size:32px;">🍗</div><div style="padding:10px 12px;"><div style="font-family:var(--font-display);font-size:14px;font-weight:400;margin-bottom:4px;">Chicken stir-fry</div><div style="font-size:10px;color:var(--color-text-muted);margin-bottom:6px;">25 min · Easy · 5 days ago</div><div style="display:flex;gap:3px;"><span class="badge badge-m">Chicken</span></div></div></div>
|
||||
<div style="background:var(--color-surface);border:1px solid var(--color-border);border-radius:var(--radius-lg);overflow:hidden;"><div style="height:100px;background:var(--blue-tint);display:flex;align-items:center;justify-content:center;font-size:32px;">🐟</div><div style="padding:10px 12px;"><div style="font-family:var(--font-display);font-size:14px;font-weight:400;margin-bottom:4px;">Salmon teriyaki with rice</div><div style="font-size:10px;color:var(--color-text-muted);margin-bottom:6px;">35 min · Medium · 2 weeks ago</div><div style="display:flex;gap:3px;"><span class="badge badge-m">Fish</span></div></div></div>
|
||||
<div style="background:var(--color-surface);border:1px solid var(--color-border);border-radius:var(--radius-lg);overflow:hidden;"><div style="height:100px;background:var(--green-tint);display:flex;align-items:center;justify-content:center;font-size:32px;">🍄</div><div style="padding:10px 12px;"><div style="font-family:var(--font-display);font-size:14px;font-weight:400;margin-bottom:4px;">Mushroom risotto</div><div style="font-size:10px;color:var(--color-text-muted);margin-bottom:6px;">50 min · Medium · 2 weeks ago</div><div style="display:flex;gap:3px;"><span class="badge badge-g">Vegetarian</span></div></div></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="agent">
|
||||
<h4>B1 · Recipe library</h4>
|
||||
<pre>/* Desktop: 224px sidebar + topbar (search 220px + Add button) + content: filter chips row + 4-col grid.
|
||||
* Desktop cards are richer: 100px image area + name (Fraunces 14px) + meta + tags row.
|
||||
* Mobile: 2-col, 64px image, no tags on cards (too small).
|
||||
* Filter chips: from tag table. Active: green-tint. 12px border-radius.
|
||||
* Grid fills the content area directly — no wrapping card.
|
||||
* Card click → B2 (recipe detail). Add button → B3 (empty form). */</pre>
|
||||
<table class="at"><thead><tr><th>Element</th><th>Value</th><th>Notes</th></tr></thead><tbody>
|
||||
<tr class="grp"><td colspan="3">Desktop</td></tr>
|
||||
<tr><td>Grid</td><td>4 columns, 12px gap, content padding 20px 24px</td><td>Cards: radius-lg, border-default, overflow hidden</td></tr>
|
||||
<tr><td>Card image</td><td>100px height. hero_image_url → object-fit:cover. NULL → tint + emoji.</td><td>Tint color based on first protein tag</td></tr>
|
||||
<tr><td>Card content</td><td>10px 12px padding. Name: Fraunces 14px. Meta: 10px muted. Tags: badge row.</td><td>meta shows cook time + effort + "last cooked X ago"</td></tr>
|
||||
<tr><td>Filter chips</td><td>11px/500, 5px 14px pad, 12px radius</td><td>Active: green-tint bg + green-dark text. Others: border-default.</td></tr>
|
||||
<tr class="grp"><td colspan="3">Mobile</td></tr>
|
||||
<tr><td>Grid</td><td>2 columns, 8px gap</td><td>Cards: 64px image, 12px name, 9px meta. No tags.</td></tr>
|
||||
</tbody></table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<!-- ═══ B3 ADD/EDIT RECIPE ═══ -->
|
||||
<div class="scr" id="b3">
|
||||
<div class="scr-head"><h3>Add / edit recipe</h3><span class="scr-id">B3</span></div>
|
||||
<div class="scr-desc">Single form for add + edit. Mobile: V1 single scroll. Desktop: V5 sidebar + topbar + split content (form left, tags + live preview panel right). The tags panel is a natural sidebar within the content area, not a floating card.</div>
|
||||
<div class="scr-var"><strong>V1 mobile / V5 desktop</strong> — form left, tags + preview right on desktop</div>
|
||||
|
||||
<div class="previews">
|
||||
<div class="prev-col">
|
||||
<div class="bp-lbl">Mobile · 320px</div>
|
||||
<div class="phone">
|
||||
<div class="pst"><b>9:45</b><span>●●● WiFi 🔋</span></div>
|
||||
<div class="pb">
|
||||
<div style="padding:10px 16px;border-bottom:1px solid var(--color-border);display:flex;justify-content:space-between;align-items:center;">
|
||||
<div style="font-size:13px;color:var(--green);font-weight:500;">← Recipes</div>
|
||||
<div style="font-family:var(--font-display);font-size:16px;font-weight:500;">New recipe</div>
|
||||
<div style="font-size:13px;color:var(--green);font-weight:500;">Save</div>
|
||||
</div>
|
||||
<div style="padding:16px;">
|
||||
<div style="height:56px;background:var(--color-subtle);border:1px dashed var(--color-border);border-radius:var(--radius-lg);display:flex;align-items:center;justify-content:center;margin-bottom:16px;"><div style="text-align:center;"><div style="font-size:14px;color:var(--color-border);">📷</div><div style="font-size:9px;color:var(--color-text-muted);">Add photo</div></div></div>
|
||||
<div class="fg"><label class="fl">Recipe name</label><input class="fi" value="Slow-roasted tomato pasta"/></div>
|
||||
<div style="display:flex;gap:8px;">
|
||||
<div class="fg" style="flex:1;"><label class="fl">Serves</label><input class="fi" type="number" value="4"/></div>
|
||||
<div class="fg" style="flex:1;"><label class="fl">Cook time</label><input class="fi" value="45 min"/></div>
|
||||
</div>
|
||||
<div class="eye" style="margin-bottom:6px;">Ingredients</div>
|
||||
<div style="border:1px solid var(--color-border);border-radius:var(--radius-lg);padding:4px 12px;margin-bottom:6px;">
|
||||
<div class="ir"><div class="ir-q">500g</div><div class="ir-n">Cherry tomatoes</div><div class="ir-x">×</div></div>
|
||||
<div class="ir"><div class="ir-q">300g</div><div class="ir-n">Penne pasta</div><div class="ir-x">×</div></div>
|
||||
<div class="ir"><div class="ir-q">3 cloves</div><div class="ir-n">Garlic</div><div class="ir-x">×</div></div>
|
||||
</div>
|
||||
<button class="bg" style="width:100%;font-size:11px;padding:7px;margin-bottom:12px;">+ Add ingredient</button>
|
||||
<div class="eye" style="margin-bottom:6px;">Tags (required)</div>
|
||||
<div style="font-size:11px;color:var(--color-text-muted);margin-bottom:4px;">Effort</div>
|
||||
<div style="display:flex;gap:4px;margin-bottom:10px;"><span class="tc s">Easy</span><span class="tc">Medium</span><span class="tc">Hard</span></div>
|
||||
<div style="font-size:11px;color:var(--color-text-muted);margin-bottom:4px;">Category (pick ≥ 1)</div>
|
||||
<div style="margin-bottom:14px;"><span class="tc">Chicken</span><span class="tc">Fish</span><span class="tc s">Vegetarian</span><span class="tc s">Child-friendly</span><span class="tc">Pasta</span></div>
|
||||
<button class="bp">Save recipe</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="prev-col">
|
||||
<div class="bp-lbl">Desktop · 1040px</div>
|
||||
<div class="desk" style="min-height:540px;">
|
||||
<div class="dsb">
|
||||
<div class="dsb-logo"><div class="dsb-lm"><div class="dsb-ic">🥗</div><div class="dsb-nm">Mealplan</div></div><div class="dsb-sub">Smith household</div></div>
|
||||
<div class="dsb-nav"><div><div class="dsb-nl">Plan</div><div class="dsb-ni"><span class="dsb-nc">📅</span>Planner</div><div class="dsb-ni a"><span class="dsb-nc">📖</span>Recipes</div><div class="dsb-ni"><span class="dsb-nc">🛒</span>Shopping</div></div></div>
|
||||
</div>
|
||||
<div class="dm">
|
||||
<div class="dtb">
|
||||
<div style="display:flex;align-items:center;gap:8px;"><span style="font-size:13px;color:var(--color-text-muted);">← Recipes</span><span style="color:var(--color-border);">/</span><span class="dtb-t">New recipe</span></div>
|
||||
<div class="dtb-r"><button class="bg" style="padding:7px 14px;font-size:12px;">Cancel</button><button class="dab">Save recipe</button></div>
|
||||
</div>
|
||||
<div style="flex:1;display:flex;overflow:hidden;">
|
||||
<!-- Left: form -->
|
||||
<div style="flex:1;padding:24px;overflow-y:auto;border-right:1px solid var(--color-border);">
|
||||
<div style="height:80px;background:var(--color-subtle);border:1px dashed var(--color-border);border-radius:var(--radius-lg);display:flex;align-items:center;justify-content:center;margin-bottom:20px;cursor:pointer;"><span style="font-size:13px;color:var(--color-text-muted);">📷 Add hero image</span></div>
|
||||
<div class="fg"><label class="fl">Recipe name</label><input class="fi" value="Slow-roasted tomato pasta" style="font-size:16px;padding:12px 14px;"/></div>
|
||||
<div style="display:flex;gap:12px;">
|
||||
<div class="fg" style="flex:1;"><label class="fl">Serves</label><input class="fi" type="number" value="4"/></div>
|
||||
<div class="fg" style="flex:1;"><label class="fl">Cook time</label><input class="fi" value="45 min"/></div>
|
||||
<div class="fg" style="flex:1;"><label class="fl">Prep time</label><input class="fi" placeholder="Optional"/></div>
|
||||
</div>
|
||||
<div class="eye" style="margin:8px 0;">Ingredients</div>
|
||||
<div style="border:1px solid var(--color-border);border-radius:var(--radius-lg);padding:4px 14px;margin-bottom:8px;">
|
||||
<div class="ir"><div class="ir-q">500g</div><div class="ir-n">Cherry tomatoes</div><div class="ir-x">×</div></div>
|
||||
<div class="ir"><div class="ir-q">300g</div><div class="ir-n">Penne pasta</div><div class="ir-x">×</div></div>
|
||||
<div class="ir"><div class="ir-q">3 cloves</div><div class="ir-n">Garlic</div><div class="ir-x">×</div></div>
|
||||
<div class="ir"><div class="ir-q">2 tbsp</div><div class="ir-n">Olive oil</div><div class="ir-x">×</div></div>
|
||||
</div>
|
||||
<button class="bg" style="width:100%;font-size:12px;padding:8px;">+ Add ingredient</button>
|
||||
<div class="eye" style="margin:16px 0 8px;">Steps</div>
|
||||
<div style="display:flex;gap:10px;padding:8px 0;border-bottom:1px solid var(--color-subtle);"><div style="width:24px;height:24px;border-radius:50%;background:var(--color-subtle);display:flex;align-items:center;justify-content:center;font-size:11px;font-weight:500;flex-shrink:0;">1</div><div style="font-size:13px;line-height:1.5;">Preheat oven to 180°C. Halve the tomatoes and place on a baking tray.</div></div>
|
||||
<div style="display:flex;gap:10px;padding:8px 0;border-bottom:1px solid var(--color-subtle);"><div style="width:24px;height:24px;border-radius:50%;background:var(--color-subtle);display:flex;align-items:center;justify-content:center;font-size:11px;font-weight:500;flex-shrink:0;">2</div><div style="font-size:13px;line-height:1.5;">Drizzle with olive oil, season. Roast for 40 minutes.</div></div>
|
||||
<div style="display:flex;gap:10px;padding:8px 0;"><div style="width:24px;height:24px;border-radius:50%;background:var(--color-subtle);display:flex;align-items:center;justify-content:center;font-size:11px;font-weight:500;flex-shrink:0;">3</div><div style="font-size:13px;line-height:1.5;">Cook pasta. Toss with roasted tomatoes and garlic.</div></div>
|
||||
<button class="bg" style="width:100%;font-size:12px;padding:8px;margin-top:8px;">+ Add step</button>
|
||||
</div>
|
||||
<!-- Right: tags + preview -->
|
||||
<div style="width:280px;flex-shrink:0;padding:24px;background:var(--color-surface);overflow-y:auto;">
|
||||
<div class="eye" style="margin-bottom:8px;">Tags (required)</div>
|
||||
<div style="font-size:11px;font-weight:500;margin-bottom:4px;">Effort level</div>
|
||||
<div style="display:flex;gap:4px;margin-bottom:14px;"><span class="tc s">Easy</span><span class="tc">Medium</span><span class="tc">Hard</span></div>
|
||||
<div style="font-size:11px;font-weight:500;margin-bottom:4px;">Category</div>
|
||||
<div style="margin-bottom:20px;"><span class="tc">Chicken</span><span class="tc">Fish</span><span class="tc">Beef</span><span class="tc s">Vegetarian</span><span class="tc s">Child-friendly</span><span class="tc">Pasta</span></div>
|
||||
<div style="border-top:1px solid var(--color-border);padding-top:16px;">
|
||||
<div class="eye" style="margin-bottom:8px;">Live preview</div>
|
||||
<div style="background:var(--color-page);border:1px solid var(--color-border);border-radius:var(--radius-lg);overflow:hidden;">
|
||||
<div style="height:48px;background:var(--green-tint);display:flex;align-items:center;justify-content:center;font-size:18px;">🍝</div>
|
||||
<div style="padding:10px;"><div style="font-family:var(--font-display);font-size:13px;margin-bottom:3px;">Slow-roasted tomato pasta</div><div style="font-size:9px;color:var(--color-text-muted);margin-bottom:4px;">45 min · 4 servings · 4 ingredients</div><div style="display:flex;gap:3px;"><span class="badge badge-g">Easy</span><span class="badge badge-g">Vegetarian</span><span class="badge badge-y">Child-friendly</span></div></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="agent">
|
||||
<h4>B3 · Add/edit recipe</h4>
|
||||
<pre>/* Desktop: 224px sidebar + topbar (breadcrumb + Save) + split content:
|
||||
* Left (flex:1, page bg): hero upload + name + serves/time/prep (3-col) + ingredients + steps
|
||||
* Right (280px, surface bg): effort chips + category chips + live preview card
|
||||
* Form content is NOT in a card — it's directly on the page bg.
|
||||
* Tags panel (right) uses surface bg as a section differentiator, not as a card.
|
||||
* Mobile: single scroll, full width. Back + title + Save in topbar.
|
||||
* Ingredient autocomplete: citext ILIKE from ingredient table. */</pre>
|
||||
<table class="at"><thead><tr><th>Element</th><th>Value</th><th>Notes</th></tr></thead><tbody>
|
||||
<tr class="grp"><td colspan="3">Desktop</td></tr>
|
||||
<tr><td>Form area</td><td>flex:1, page bg, 24px padding, border-right</td><td>Contains: hero + name (16px input) + 3-col row + ingredients + steps</td></tr>
|
||||
<tr><td>Tags panel</td><td>280px, surface bg, 24px padding</td><td>Effort chips + category chips + divider + live preview card</td></tr>
|
||||
<tr><td>Live preview</td><td>Mini B1 card inside the panel</td><td>Updates as user types. Shows name, time, servings, ingredient count, tags.</td></tr>
|
||||
</tbody></table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ═══ LLM IMPLEMENTATION GUIDE ═══ -->
|
||||
<div class="llm">
|
||||
<h2>Implementation Guide — J1 Add a Recipe</h2>
|
||||
|
||||
<h3>1. Journey Flow</h3>
|
||||
<p>B1 (Recipe library) → B3 (Add/edit form) → B1 (Recipe library). Actor: <strong>Planner only</strong>.</p>
|
||||
|
||||
<h3>2. Screen B1 — Recipe Library</h3>
|
||||
<ul>
|
||||
<li><strong>Mobile:</strong> topbar + 2-col card grid (8px gap, 64px image, no tags on cards).</li>
|
||||
<li><strong>Desktop:</strong> 224px sidebar + topbar (search input 220px + Add button) + filter chips row + 4-col grid (12px gap, 100px image, tags visible as badge row).</li>
|
||||
<li><strong>Filter chips:</strong> sourced from the <code>tag</code> table. Active chip = <code>green-tint</code> bg / <code>green-dark</code> text. Inactive chip = <code>border-default</code>. Style: <code>font-size:11px; font-weight:500; padding:5px 14px; border-radius:12px</code>.</li>
|
||||
<li>Card click → B2 (recipe detail). Add button → B3 (empty form).</li>
|
||||
</ul>
|
||||
|
||||
<h3>3. Screen B3 — Add/Edit Recipe</h3>
|
||||
<ul>
|
||||
<li>Single form component with two states: empty (new recipe) vs prefilled (edit existing recipe).</li>
|
||||
<li><strong>Design rule:</strong> B3 add = B3 edit — build once with two initial states.</li>
|
||||
<li><strong>Mobile:</strong> single scroll column, full width.</li>
|
||||
<li><strong>Desktop:</strong> 224px sidebar + topbar (breadcrumb + Save/Cancel buttons) + split content: form left (<code>flex:1</code>, page bg, 24px padding) + tags panel right (280px, surface bg).</li>
|
||||
</ul>
|
||||
<p><strong>Form sections:</strong></p>
|
||||
<ol>
|
||||
<li>Hero image upload (optional) — dashed border placeholder, click to upload.</li>
|
||||
<li>Recipe name (required) — 16px font on desktop.</li>
|
||||
<li>Serves / Cook time / Prep time — 3-col row on desktop, 2-col on mobile (prep time hidden or below).</li>
|
||||
<li>Ingredients — editable list with autocomplete. Each row: quantity + name + remove button.</li>
|
||||
<li>Steps — numbered list, optional at save. Each step: circle number + text.</li>
|
||||
</ol>
|
||||
<p><strong>Tags (required):</strong></p>
|
||||
<ul>
|
||||
<li>Effort level: Easy / Medium / Hard — single-select chip group.</li>
|
||||
<li>Category: at least 1 required — Chicken, Fish, Beef, Vegetarian, Pasta, etc. Multi-select chips.</li>
|
||||
</ul>
|
||||
<p><strong>Desktop right panel:</strong> live preview card (mini B1 card) updates as user types — shows name, time, servings, ingredient count, and selected tags.</p>
|
||||
<p><strong>Ingredient autocomplete:</strong> <code>citext ILIKE</code> query against the <code>ingredient</code> table.</p>
|
||||
|
||||
<h3>4. Data Operations</h3>
|
||||
<table>
|
||||
<thead><tr><th>Screen</th><th>Operation</th><th>Tables</th></tr></thead>
|
||||
<tbody>
|
||||
<tr><td>B1</td><td><code>SELECT</code> recipes with tags</td><td><code>recipe</code> JOIN <code>recipe_tag</code> + <code>tag</code></td></tr>
|
||||
<tr><td>B3</td><td><code>INSERT</code> / <code>UPDATE</code> recipe</td><td><code>recipe</code></td></tr>
|
||||
<tr><td>B3</td><td><code>INSERT</code> / <code>DELETE</code> ingredients</td><td><code>recipe_ingredient</code></td></tr>
|
||||
<tr><td>B3</td><td><code>INSERT</code> / <code>DELETE</code> steps</td><td><code>recipe_step</code></td></tr>
|
||||
<tr><td>B3</td><td><code>INSERT</code> / <code>DELETE</code> tags</td><td><code>recipe_tag</code></td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<p><strong>Minimum to save:</strong> name + effort tag + at least 1 category tag.</p>
|
||||
|
||||
<h3>5. Design Constraints</h3>
|
||||
<ul>
|
||||
<li>Tags power the variety algorithm — they are not cosmetic. Ensure tags are always saved correctly.</li>
|
||||
<li>If entered from a planner day slot, offer to assign the recipe to that day after save.</li>
|
||||
<li>Steps are optional — recipes without steps can still be planned and cooked from the ingredient list only.</li>
|
||||
<li>Form content is NOT in a card on desktop — it sits directly on the page background.</li>
|
||||
<li>The tags panel uses <code>surface</code> bg as a section differentiator, not as a card wrapper.</li>
|
||||
</ul>
|
||||
|
||||
<h3>6. Accessibility</h3>
|
||||
<ul>
|
||||
<li>All form inputs must have associated <code><label></code> elements.</li>
|
||||
<li>Ingredient and step lists must support keyboard navigation (add, remove, reorder).</li>
|
||||
<li>Chip selections (effort level, category tags) must use proper ARIA attributes: <code>role="radiogroup"</code> for single-select effort, <code>role="group"</code> with <code>aria-pressed</code> for multi-select categories.</li>
|
||||
<li>Live preview region should use <code>aria-live="polite"</code> to announce updates.</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
1085
specs/frontend/j2-plan-the-week.html
Normal file
1085
specs/frontend/j2-plan-the-week.html
Normal file
File diff suppressed because it is too large
Load Diff
395
specs/frontend/j3-cook-tonight.html
Normal file
395
specs/frontend/j3-cook-tonight.html
Normal file
@@ -0,0 +1,395 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8"/>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
|
||||
<title>J3 — Cook Tonight</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"/>
|
||||
<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:#F2C12E;--yellow-dark:#C49610;--yellow-text:#8A6800;--blue-tint:#E6F1FB;--blue-light:#A4CFF4;--blue:#2D7DD2;--blue-dark:#185FA5;--purple-tint:#EEEDFE;--purple:#534AB7;--purple-dark:#3C3489;--orange-tint:#FEF0E6;--orange:#E8862A;--orange-dark:#B46820;--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-xl:16px;--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,.08),0 2px 4px rgba(28,28,24,.04);--shadow-overlay:0 8px 32px rgba(28,28,24,.12),0 2px 8px rgba(28,28,24,.06);}
|
||||
*,*::before,*::after{box-sizing:border-box;margin:0;padding:0;}
|
||||
body{font-family:var(--font-sans);background:#E8E7E2;color:var(--color-text);font-size:14px;line-height:1.6;}
|
||||
.doc{max-width:1200px;margin:0 auto;padding:48px 40px 120px;}
|
||||
|
||||
/* Header */
|
||||
.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;background:var(--color-page);margin:-48px -40px 48px;padding:48px 40px 28px;border-radius:var(--radius-xl) var(--radius-xl) 0 0;}
|
||||
.doc-header h1{font-family:var(--font-display);font-size:28px;font-weight:500;letter-spacing:-.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;}
|
||||
.pill{display:inline-block;padding:2px 8px;border-radius:var(--radius-sm);font-size:10px;font-weight:500;letter-spacing:.05em;background:var(--green-tint);color:var(--green-dark);}
|
||||
|
||||
/* Sections */
|
||||
.section{margin-bottom:64px;}
|
||||
.section-title{font-size:10px;font-weight:500;letter-spacing:.12em;text-transform:uppercase;color:var(--color-text-muted);padding-bottom:10px;border-bottom:1px solid var(--color-border);margin-bottom:24px;}
|
||||
.prose{font-size:13px;color:var(--color-text-muted);line-height:1.65;max-width:720px;margin-bottom:20px;}
|
||||
|
||||
/* Journey headers */
|
||||
.jh{padding:20px 24px;border-radius:var(--radius-xl);margin-bottom:40px;display:flex;align-items:center;gap:16px;}
|
||||
.jh .jn{font-family:var(--font-display);font-size:48px;font-weight:300;line-height:1;opacity:.5;}
|
||||
.jh h2{font-family:var(--font-display);font-size:22px;font-weight:500;letter-spacing:-.02em;margin-bottom:4px;}
|
||||
.jh p{font-size:13px;line-height:1.5;}.jh .fl{font-family:var(--font-mono);font-size:11px;margin-top:6px;opacity:.7;}
|
||||
.jh-p{background:var(--purple-tint);border:1px solid #CECBF6;}.jh-p .jn{color:var(--purple);}.jh-p p,.jh-p .fl{color:var(--purple-dark);}
|
||||
.jh-g{background:var(--green-tint);border:1px solid var(--green-light);}.jh-g .jn{color:var(--green);}.jh-g p,.jh-g .fl{color:var(--green-dark);}
|
||||
.jh-y{background:var(--yellow-tint);border:1px solid var(--yellow-light);}.jh-y .jn{color:var(--yellow-dark);}.jh-y p,.jh-y .fl{color:var(--yellow-text);}
|
||||
.jh-o{background:var(--orange-tint);border:1px solid #FBCDA4;}.jh-o .jn{color:var(--orange);}.jh-o p,.jh-o .fl{color:var(--orange-dark);}
|
||||
.jh-b{background:var(--blue-tint);border:1px solid var(--blue-light);}.jh-b .jn{color:var(--blue);}.jh-b p,.jh-b .fl{color:var(--blue-dark);}
|
||||
|
||||
/* Screen block */
|
||||
.scr{margin-bottom:56px;}
|
||||
.scr-head{display:flex;justify-content:space-between;align-items:center;margin-bottom:6px;}
|
||||
.scr-head h3{font-family:var(--font-display);font-size:20px;font-weight:500;letter-spacing:-.02em;}
|
||||
.scr-id{font-family:var(--font-mono);font-size:11px;color:var(--color-text-muted);padding:2px 8px;border:1px solid var(--color-border);border-radius:var(--radius-sm);background:var(--color-page);}
|
||||
.scr-desc{font-size:12px;color:var(--color-text-muted);line-height:1.6;max-width:720px;margin-bottom:6px;}
|
||||
.scr-var{font-size:11px;color:var(--color-text-muted);margin-bottom:20px;}.scr-var strong{color:var(--color-text);}
|
||||
|
||||
/* Preview container */
|
||||
.previews{display:flex;gap:32px;flex-wrap:wrap;justify-content:center;align-items:flex-start;margin-bottom:20px;}
|
||||
.prev-col{display:flex;flex-direction:column;align-items:center;gap:10px;}
|
||||
.bp-lbl{font-family:var(--font-mono);font-size:10px;color:var(--color-text-muted);}
|
||||
|
||||
/* Phone frame - 320px */
|
||||
.phone{width:320px;flex-shrink:0;background:var(--color-page);border-radius:36px;overflow:hidden;box-shadow:var(--shadow-overlay),0 0 0 1px rgba(0,0,0,.07);display:flex;flex-direction:column;border:6px solid #1C1C18;}
|
||||
.pst{padding:10px 20px 0;display:flex;justify-content:space-between;align-items:center;font-size:12px;background:var(--color-page);}.pst b{font-weight:600;}.pst span{font-size:10px;}
|
||||
.pb{flex:1;overflow-y:auto;display:flex;flex-direction:column;}
|
||||
|
||||
/* Desktop frame - 1040px */
|
||||
.desk{width:100%;max-width:1040px;background:var(--color-page);border-radius:var(--radius-xl);overflow:hidden;box-shadow:var(--shadow-overlay),0 0 0 1px rgba(0,0,0,.06);display:flex;min-height:520px;}
|
||||
|
||||
/* Shared nav components */
|
||||
.mtb{padding:10px 16px;background:var(--color-page);border-bottom:1px solid var(--color-border);display:flex;justify-content:space-between;align-items:center;}
|
||||
.mtb-t{font-family:var(--font-display);font-size:20px;font-weight:500;letter-spacing:-.02em;}
|
||||
.mi{width:32px;height:32px;border-radius:var(--radius-md);border:1px solid var(--color-border);background:var(--color-surface);display:flex;align-items:center;justify-content:center;font-size:13px;color:var(--color-text-muted);flex-shrink:0;}.mi.gn{background:var(--green);border-color:var(--green);color:#fff;}
|
||||
.mbt{border-top:1px solid var(--color-border);background:var(--color-surface);padding:8px 16px 28px;display:flex;justify-content:space-around;}
|
||||
.mt-i{display:flex;flex-direction:column;align-items:center;gap:2px;}.mt-ic{width:20px;height:20px;border-radius:4px;background:var(--color-subtle);display:flex;align-items:center;justify-content:center;font-size:11px;}.mt-i.a .mt-ic{background:var(--green-tint);}.mt-l{font-size:9px;font-weight:500;color:var(--color-text-muted);}.mt-i.a .mt-l{color:var(--green-dark);}
|
||||
|
||||
/* Desktop sidebar - 224px per nav-spec */
|
||||
.dsb{width:224px;flex-shrink:0;background:var(--color-surface);border-right:1px solid var(--color-border);display:flex;flex-direction:column;}
|
||||
.dsb-logo{padding:20px 16px 16px;border-bottom:1px solid var(--color-border);}
|
||||
.dsb-lm{display:flex;align-items:center;gap:8px;margin-bottom:2px;}.dsb-ic{width:24px;height:24px;border-radius:5px;background:var(--green);display:flex;align-items:center;justify-content:center;font-size:12px;}.dsb-nm{font-family:var(--font-display);font-size:16px;font-weight:500;letter-spacing:-.02em;}.dsb-sub{font-size:10px;color:var(--color-text-muted);padding-left:32px;}
|
||||
.dsb-nav{padding:12px 10px;flex:1;}.dsb-nl{font-size:8px;font-weight:500;letter-spacing:.1em;text-transform:uppercase;color:var(--color-text-muted);padding:0 8px;margin-bottom:4px;}.dsb-ni{display:flex;align-items:center;gap:8px;padding:7px 8px;border-radius:var(--radius-md);font-size:13px;color:var(--color-text-muted);margin-bottom:2px;cursor:default;}.dsb-ni.a{background:var(--green-tint);color:var(--green-dark);font-weight:500;}.dsb-nc{font-size:13px;width:18px;text-align:center;}
|
||||
.dm{flex:1;display:flex;flex-direction:column;min-width:0;}
|
||||
.dtb{padding:12px 24px;border-bottom:1px solid var(--color-border);display:flex;justify-content:space-between;align-items:center;flex-shrink:0;}
|
||||
.dtb-t{font-family:var(--font-display);font-size:18px;font-weight:500;letter-spacing:-.02em;}
|
||||
.dtb-r{display:flex;align-items:center;gap:8px;}
|
||||
.dab{font-family:var(--font-sans);font-size:13px;font-weight:500;padding:7px 16px;border-radius:var(--radius-md);background:var(--green);color:#fff;border:none;}
|
||||
.dab-b{background:var(--blue);}
|
||||
|
||||
/* Shared form */
|
||||
.fi{width:100%;font-family:var(--font-sans);font-size:14px;padding:10px 12px;border-radius:var(--radius-md);border:1px solid var(--color-border);background:var(--color-page);color:var(--color-text);outline:none;}
|
||||
.fl{font-size:12px;font-weight:500;color:var(--color-text);margin-bottom:6px;display:block;}
|
||||
.fg{margin-bottom:16px;}
|
||||
.bp{font-family:var(--font-sans);font-size:14px;font-weight:500;padding:12px 24px;border-radius:var(--radius-md);background:var(--green);color:#fff;border:none;cursor:pointer;width:100%;}
|
||||
.bg{font-family:var(--font-sans);font-size:13px;font-weight:500;padding:10px 20px;border-radius:var(--radius-md);background:var(--color-subtle);color:var(--color-text-muted);border:1px solid var(--color-border);cursor:pointer;}
|
||||
|
||||
/* Tags */
|
||||
.tc{display:inline-flex;font-size:12px;font-weight:500;padding:6px 12px;border-radius:20px;border:1px solid var(--color-border);background:var(--color-surface);color:var(--color-text-muted);cursor:pointer;margin:0 4px 4px 0;user-select:none;}.tc.s{background:var(--green-tint);color:var(--green-dark);border-color:var(--green-light);}
|
||||
.badge{font-size:9px;font-weight:500;padding:2px 6px;border-radius:3px;display:inline-block;}.badge-g{background:var(--green-tint);color:var(--green-dark);}.badge-y{background:var(--yellow-tint);color:var(--yellow-text);}.badge-m{background:var(--color-subtle);color:var(--color-text-muted);}
|
||||
|
||||
/* Ingredient rows */
|
||||
.ir{display:flex;align-items:center;gap:8px;padding:8px 0;border-bottom:1px solid var(--color-subtle);}.ir:last-child{border-bottom:none;}.ir-q{font-family:var(--font-mono);font-size:12px;color:var(--color-text-muted);width:55px;flex-shrink:0;text-align:right;}.ir-n{font-size:13px;color:var(--color-text);flex:1;}.ir-x{font-size:14px;color:var(--color-border);cursor:pointer;width:20px;text-align:center;}
|
||||
|
||||
/* Checklist */
|
||||
.ck{display:flex;align-items:center;gap:10px;padding:10px 0;border-bottom:1px solid var(--color-subtle);cursor:pointer;}.ck:last-child{border-bottom:none;}.ck-b{width:22px;height:22px;border-radius:4px;border:2px solid var(--color-border);flex-shrink:0;display:flex;align-items:center;justify-content:center;font-size:11px;}.ck.d .ck-b{background:var(--green);border-color:var(--green);color:#fff;}.ck-c{flex:1;}.ck-n{font-size:14px;color:var(--color-text);}.ck.d .ck-n{text-decoration:line-through;color:var(--color-text-muted);}.ck-s{font-size:10px;color:var(--color-text-muted);}.ck-q{font-family:var(--font-mono);font-size:12px;color:var(--color-text-muted);flex-shrink:0;}
|
||||
|
||||
/* Suggestion cards */
|
||||
.sg{background:var(--color-surface);border:1px solid var(--color-border);border-radius:var(--radius-lg);padding:12px;margin-bottom:8px;display:flex;align-items:center;gap:10px;}.sg-r{font-family:var(--font-display);font-size:16px;font-weight:300;color:var(--color-text-muted);width:20px;text-align:center;flex-shrink:0;}.sg-b{flex:1;}.sg-n{font-family:var(--font-display);font-size:13px;font-weight:400;color:var(--color-text);margin-bottom:2px;}.sg-i{font-size:10px;color:var(--color-text-muted);}.sg-w{font-size:9px;color:var(--green-dark);background:var(--green-tint);padding:2px 6px;border-radius:3px;display:inline-block;margin-top:3px;}.sg-p{font-size:11px;font-weight:500;color:var(--green);flex-shrink:0;}
|
||||
|
||||
/* Eyebrow labels */
|
||||
.eye{font-size:10px;font-weight:500;letter-spacing:.08em;text-transform:uppercase;color:var(--color-text-muted);}
|
||||
|
||||
/* Agent table */
|
||||
.agent{background:var(--color-text);color:#E8E8E2;padding:24px;border-radius:var(--radius-lg);margin-top:20px;}
|
||||
.agent h4{font-size:9px;font-weight:500;letter-spacing:.1em;text-transform:uppercase;color:#5A5A55;margin-bottom:12px;}
|
||||
.agent pre{font-family:var(--font-mono);font-size:10px;color:#444440;margin-bottom:16px;line-height:1.8;white-space:pre-wrap;}
|
||||
.at{width:100%;border-collapse:collapse;font-family:var(--font-mono);font-size:10px;}
|
||||
.at thead tr{border-bottom:1px solid #2A2A26;}.at th{text-align:left;padding:6px 10px;font-size:8px;font-weight:500;letter-spacing:.08em;text-transform:uppercase;color:#5A5A55;font-family:var(--font-sans);}.at td{padding:5px 10px;border-bottom:1px solid #1E1E1A;vertical-align:top;line-height:1.5;}.at tr:last-child td{border-bottom:none;}.at td:first-child{color:#7A7A72;}.at td:nth-child(2){color:#E8E8E2;font-weight:500;}.at td:nth-child(3){color:#5A5A55;}.at .grp td{padding-top:14px;font-family:var(--font-sans);font-size:8px;font-weight:500;letter-spacing:.08em;text-transform:uppercase;color:#3A3A36;}
|
||||
|
||||
/* LLM instruction section */
|
||||
.llm{background:var(--color-page);border:2px solid var(--green);border-radius:var(--radius-xl);padding:32px 40px;margin-top:64px;}
|
||||
.llm h2{font-family:var(--font-display);font-size:22px;font-weight:500;letter-spacing:-.02em;margin-bottom:8px;color:var(--green-dark);}
|
||||
.llm h3{font-size:14px;font-weight:600;margin:20px 0 8px;color:var(--color-text);}
|
||||
.llm p,.llm li{font-size:13px;color:var(--color-text-muted);line-height:1.65;}
|
||||
.llm ul,.llm ol{padding-left:20px;margin-bottom:12px;}
|
||||
.llm li{margin-bottom:4px;}
|
||||
.llm code{font-family:var(--font-mono);font-size:11px;background:var(--color-surface);padding:1px 5px;border-radius:3px;}
|
||||
.llm table{width:100%;border-collapse:collapse;margin:12px 0;font-size:12px;}
|
||||
.llm th,.llm td{text-align:left;padding:6px 10px;border-bottom:1px solid var(--color-border);}
|
||||
.llm th{font-weight:500;color:var(--color-text);font-size:11px;text-transform:uppercase;letter-spacing:.05em;}
|
||||
.llm td{color:var(--color-text-muted);}
|
||||
|
||||
@media(max-width:900px){.doc{padding:24px 16px 80px;}}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="doc">
|
||||
|
||||
<!-- ═══ J3 COOK TONIGHT ═══ -->
|
||||
<div class="jh jh-g" style="background:var(--green-tint);">
|
||||
<div class="jn" style="color:var(--green-dark);">J3</div>
|
||||
<div><h2>Cook tonight</h2><p>Recipe detail → cook mode → mark as cooked. Kitchen context.</p><div class="fl">C1 → B2 → B4 → C1 · 16px body, 1.75 line-height, wake lock</div></div>
|
||||
</div>
|
||||
|
||||
<!-- ═══ B2 RECIPE DETAIL ═══ -->
|
||||
<div class="scr" id="b2">
|
||||
<div class="scr-head"><h3>Recipe detail</h3><span class="scr-id">B2</span></div>
|
||||
<div class="scr-desc">V2 Hero header. Desktop: sidebar + full-width hero banner spanning the content area + two-column content below (ingredients left, steps right). The hero fills the horizontal space — it's not a card, it's a page section with a distinct background.</div>
|
||||
<div class="scr-var"><strong>V2 · Hero header</strong> — banner CTA, 2-col content on desktop</div>
|
||||
|
||||
<div class="previews">
|
||||
<div class="prev-col">
|
||||
<div class="bp-lbl">Mobile · 320px</div>
|
||||
<div class="phone">
|
||||
<div class="pst"><b>18:32</b><span>●●● WiFi 🔋</span></div>
|
||||
<div class="pb">
|
||||
<div style="background:var(--green-tint);padding:24px 20px 20px;border-bottom:1px solid var(--green-light);">
|
||||
<div style="font-size:13px;color:var(--green-dark);font-weight:500;margin-bottom:12px;">← Planner</div>
|
||||
<div style="font-family:var(--font-display);font-size:24px;font-weight:500;letter-spacing:-.02em;color:var(--green-deeper);margin-bottom:6px;">Slow-roasted tomato pasta</div>
|
||||
<div style="display:flex;flex-wrap:wrap;gap:4px;margin-bottom:8px;">
|
||||
<span style="font-size:10px;font-weight:500;padding:3px 8px;border-radius:12px;background:rgba(255,255,255,.6);color:var(--green-dark);">45 min</span>
|
||||
<span style="font-size:10px;font-weight:500;padding:3px 8px;border-radius:12px;background:rgba(255,255,255,.6);color:var(--green-dark);">Easy</span>
|
||||
<span style="font-size:10px;font-weight:500;padding:3px 8px;border-radius:12px;background:rgba(255,255,255,.6);color:var(--green-dark);">Serves 4</span>
|
||||
</div>
|
||||
<button class="bp" style="background:var(--green-dark);font-size:15px;padding:12px;">🍳 Start cooking</button>
|
||||
</div>
|
||||
<div style="padding:16px;">
|
||||
<div class="eye" style="margin-bottom:8px;">Ingredients</div>
|
||||
<div style="border:1px solid var(--color-border);border-radius:var(--radius-lg);padding:4px 12px;margin-bottom:16px;">
|
||||
<div class="ir"><div class="ir-q">500g</div><div class="ir-n">Cherry tomatoes</div></div>
|
||||
<div class="ir"><div class="ir-q">300g</div><div class="ir-n">Penne pasta</div></div>
|
||||
<div class="ir"><div class="ir-q">3 cloves</div><div class="ir-n">Garlic</div></div>
|
||||
<div class="ir"><div class="ir-q">2 tbsp</div><div class="ir-n">Olive oil</div></div>
|
||||
</div>
|
||||
<div class="eye" style="margin-bottom:8px;">Steps</div>
|
||||
<div style="display:flex;gap:10px;padding:8px 0;border-bottom:1px solid var(--color-subtle);"><div style="width:22px;height:22px;border-radius:50%;background:var(--color-subtle);display:flex;align-items:center;justify-content:center;font-size:10px;font-weight:500;flex-shrink:0;">1</div><div style="font-size:13px;line-height:1.5;">Preheat oven to 180°C. Halve tomatoes.</div></div>
|
||||
<div style="display:flex;gap:10px;padding:8px 0;border-bottom:1px solid var(--color-subtle);"><div style="width:22px;height:22px;border-radius:50%;background:var(--color-subtle);display:flex;align-items:center;justify-content:center;font-size:10px;font-weight:500;flex-shrink:0;">2</div><div style="font-size:13px;line-height:1.5;">Drizzle with oil, season, roast 40 min.</div></div>
|
||||
<div style="display:flex;gap:10px;padding:8px 0;"><div style="width:22px;height:22px;border-radius:50%;background:var(--color-subtle);display:flex;align-items:center;justify-content:center;font-size:10px;font-weight:500;flex-shrink:0;">3</div><div style="font-size:13px;line-height:1.5;">Cook pasta. Toss with roasted tomatoes and garlic.</div></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="prev-col">
|
||||
<div class="bp-lbl">Desktop · 1040px</div>
|
||||
<div class="desk" style="min-height:560px;">
|
||||
<div class="dsb">
|
||||
<div class="dsb-logo"><div class="dsb-lm"><div class="dsb-ic">🥗</div><div class="dsb-nm">Mealplan</div></div><div class="dsb-sub">Smith household</div></div>
|
||||
<div class="dsb-nav"><div><div class="dsb-nl">Plan</div><div class="dsb-ni"><span class="dsb-nc">📅</span>Planner</div><div class="dsb-ni a"><span class="dsb-nc">📖</span>Recipes</div><div class="dsb-ni"><span class="dsb-nc">🛒</span>Shopping</div></div></div>
|
||||
</div>
|
||||
<div class="dm">
|
||||
<div class="dtb"><div style="display:flex;align-items:center;gap:8px;"><span style="font-size:13px;color:var(--color-text-muted);">← Recipes</span><span style="color:var(--color-border);">/</span><span class="dtb-t">Slow-roasted tomato pasta</span></div><div class="dtb-r"><button class="bg" style="padding:7px 14px;font-size:12px;">✏️ Edit</button></div></div>
|
||||
<!-- Hero banner: full content width -->
|
||||
<div style="background:var(--green-tint);padding:32px;border-bottom:1px solid var(--green-light);display:flex;align-items:center;gap:32px;">
|
||||
<div style="flex:1;">
|
||||
<div style="font-family:var(--font-display);font-size:28px;font-weight:500;letter-spacing:-.02em;color:var(--green-deeper);margin-bottom:8px;">Slow-roasted tomato pasta</div>
|
||||
<div style="display:flex;gap:6px;margin-bottom:12px;">
|
||||
<span style="font-size:11px;font-weight:500;padding:4px 10px;border-radius:12px;background:rgba(255,255,255,.6);color:var(--green-dark);">45 min</span>
|
||||
<span style="font-size:11px;font-weight:500;padding:4px 10px;border-radius:12px;background:rgba(255,255,255,.6);color:var(--green-dark);">Easy</span>
|
||||
<span style="font-size:11px;font-weight:500;padding:4px 10px;border-radius:12px;background:rgba(255,255,255,.6);color:var(--green-dark);">Serves 4</span>
|
||||
<span style="font-size:11px;font-weight:500;padding:4px 10px;border-radius:12px;background:rgba(255,255,255,.6);color:var(--green-dark);">Vegetarian</span>
|
||||
<span style="font-size:11px;font-weight:500;padding:4px 10px;border-radius:12px;background:rgba(255,255,255,.6);color:var(--green-dark);">Child-friendly</span>
|
||||
</div>
|
||||
<div style="font-size:13px;color:var(--green-dark);line-height:1.5;max-width:400px;">Roasting the tomatoes concentrates their sweetness — great for the kids and easy to batch cook.</div>
|
||||
</div>
|
||||
<button class="bp" style="width:auto;padding:14px 28px;font-size:15px;background:var(--green-dark);flex-shrink:0;">🍳 Start cooking</button>
|
||||
</div>
|
||||
<!-- 2-col: ingredients left, steps right -->
|
||||
<div style="flex:1;display:flex;overflow:hidden;">
|
||||
<div style="flex:1;padding:24px;border-right:1px solid var(--color-border);overflow-y:auto;">
|
||||
<div class="eye" style="margin-bottom:10px;">Ingredients · 4 items</div>
|
||||
<div class="ir"><div class="ir-q">500g</div><div class="ir-n">Cherry tomatoes</div></div>
|
||||
<div class="ir"><div class="ir-q">300g</div><div class="ir-n">Penne pasta</div></div>
|
||||
<div class="ir"><div class="ir-q">3 cloves</div><div class="ir-n">Garlic</div></div>
|
||||
<div class="ir"><div class="ir-q">2 tbsp</div><div class="ir-n">Olive oil</div></div>
|
||||
</div>
|
||||
<div style="flex:1;padding:24px;overflow-y:auto;">
|
||||
<div class="eye" style="margin-bottom:10px;">Steps · 3</div>
|
||||
<div style="display:flex;gap:12px;padding:10px 0;border-bottom:1px solid var(--color-subtle);"><div style="width:28px;height:28px;border-radius:50%;background:var(--color-subtle);display:flex;align-items:center;justify-content:center;font-size:12px;font-weight:500;flex-shrink:0;">1</div><div style="font-size:14px;line-height:1.6;">Preheat oven to 180°C. Halve the cherry tomatoes and place cut-side up on a large baking tray.</div></div>
|
||||
<div style="display:flex;gap:12px;padding:10px 0;border-bottom:1px solid var(--color-subtle);"><div style="width:28px;height:28px;border-radius:50%;background:var(--color-subtle);display:flex;align-items:center;justify-content:center;font-size:12px;font-weight:500;flex-shrink:0;">2</div><div style="font-size:14px;line-height:1.6;">Drizzle generously with olive oil, season with salt and pepper. Roast for 40 minutes until caramelized.</div></div>
|
||||
<div style="display:flex;gap:12px;padding:10px 0;"><div style="width:28px;height:28px;border-radius:50%;background:var(--color-subtle);display:flex;align-items:center;justify-content:center;font-size:12px;font-weight:500;flex-shrink:0;">3</div><div style="font-size:14px;line-height:1.6;">Cook pasta according to package. Toss with roasted tomatoes and minced garlic. Serve with fresh basil.</div></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="agent">
|
||||
<h4>B2 · Recipe detail</h4>
|
||||
<pre>/* Desktop: sidebar + topbar (breadcrumb + Edit button) + hero banner (full width, green-tint) + 2-col below.
|
||||
* Hero: name Fraunces 28px + tag pills + description + Cook button on the right.
|
||||
* Below hero: ingredients (left, border-right) + steps (right). Both panels scroll independently.
|
||||
* Hero with image: hero_image_url as bg, 40% dark overlay, text in white.
|
||||
* Mobile: hero banner → stacked ingredients → steps.
|
||||
* "Start cooking" → B4. "Edit" → B3 in edit mode. */</pre>
|
||||
<table class="at"><thead><tr><th>Element</th><th>Value</th><th>Notes</th></tr></thead><tbody>
|
||||
<tr class="grp"><td colspan="3">Desktop hero</td></tr>
|
||||
<tr><td>Layout</td><td>Full content width, green-tint bg, 32px padding</td><td>Flex: info left (flex:1) + Cook button right (flex-shrink:0)</td></tr>
|
||||
<tr><td>Name</td><td>Fraunces 28px/500, green-deeper</td><td>With image: #fff on dark overlay</td></tr>
|
||||
<tr class="grp"><td colspan="3">Desktop content below hero</td></tr>
|
||||
<tr><td>Ingredients</td><td>flex:1, 24px padding, border-right</td><td>ir-row component. Scrolls independently.</td></tr>
|
||||
<tr><td>Steps</td><td>flex:1, 24px padding</td><td>Numbered circles (28px) + 14px body text, 1.6 line-height.</td></tr>
|
||||
</tbody></table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<!-- ═══ B4 COOK MODE ═══ -->
|
||||
<div class="scr" id="b4">
|
||||
<div class="scr-head"><h3>Cook mode</h3><span class="scr-id">B4</span></div>
|
||||
<div class="scr-desc">V1 Centered step. Full-screen on ALL breakpoints — intentionally no sidebar, no tabs, no nav chrome. Kitchen context doesn't change with screen size. Only the text max-width scales (260→400px). The entire screen body is the tap target.</div>
|
||||
<div class="scr-var"><strong>V1 · Centered step</strong> — same layout everywhere. No desktop sidebar. Tap anywhere.</div>
|
||||
|
||||
<div class="previews">
|
||||
<div class="prev-col">
|
||||
<div class="bp-lbl">Mobile · Step 2 of 3</div>
|
||||
<div class="phone">
|
||||
<div class="pst"><b>18:45</b><span>●●● WiFi 🔋</span></div>
|
||||
<div class="pb" style="cursor:pointer;">
|
||||
<div style="padding:10px 16px;display:flex;justify-content:space-between;align-items:center;">
|
||||
<div style="font-size:12px;color:var(--color-error);font-weight:500;">✕ Exit</div>
|
||||
<div style="font-size:11px;color:var(--color-text-muted);">Step 2 of 3</div>
|
||||
<div style="width:40px;"></div>
|
||||
</div>
|
||||
<div style="padding:0 16px;"><div style="height:4px;background:var(--color-subtle);border-radius:2px;overflow:hidden;"><div style="width:66%;height:100%;background:var(--green);border-radius:2px;"></div></div></div>
|
||||
<div style="flex:1;display:flex;flex-direction:column;align-items:center;justify-content:center;padding:32px 24px;text-align:center;">
|
||||
<div style="font-family:var(--font-display);font-size:56px;font-weight:300;color:var(--green-light);margin-bottom:16px;line-height:1;">2</div>
|
||||
<div style="font-size:16px;line-height:1.75;color:var(--color-text);max-width:260px;">Drizzle with olive oil, season generously with salt and pepper. Roast for 40 minutes until caramelized.</div>
|
||||
</div>
|
||||
<div style="padding:16px;text-align:center;font-size:12px;color:var(--color-text-muted);">Tap anywhere for next step →</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="prev-col">
|
||||
<div class="bp-lbl">Desktop · SAME layout, wider text</div>
|
||||
<div class="desk" style="min-height:480px;flex-direction:column;">
|
||||
<div style="padding:14px 24px;display:flex;justify-content:space-between;align-items:center;border-bottom:1px solid var(--color-border);">
|
||||
<div style="font-size:13px;color:var(--color-error);font-weight:500;cursor:pointer;">✕ Exit</div>
|
||||
<div style="font-size:12px;color:var(--color-text-muted);">Step 2 of 3</div>
|
||||
<div style="width:60px;"></div>
|
||||
</div>
|
||||
<div style="padding:0 24px;margin-top:4px;"><div style="height:4px;background:var(--color-subtle);border-radius:2px;overflow:hidden;"><div style="width:66%;height:100%;background:var(--green);border-radius:2px;"></div></div></div>
|
||||
<div style="flex:1;display:flex;flex-direction:column;align-items:center;justify-content:center;padding:48px;text-align:center;cursor:pointer;">
|
||||
<div style="font-family:var(--font-display);font-size:72px;font-weight:300;color:var(--green-light);margin-bottom:24px;line-height:1;">2</div>
|
||||
<div style="font-size:16px;line-height:1.75;color:var(--color-text);max-width:400px;">Drizzle with olive oil, season generously with salt and pepper. Roast for 40 minutes until caramelized.</div>
|
||||
</div>
|
||||
<div style="padding:20px;text-align:center;font-size:13px;color:var(--color-text-muted);">Tap anywhere for next step →</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="agent">
|
||||
<h4>B4 · Cook mode · KITCHEN CRITICAL</h4>
|
||||
<pre>/* FULL SCREEN on all breakpoints. NO sidebar. NO tabs. NO nav chrome.
|
||||
* Body text: EXACTLY 16px, line-height 1.75. NON-NEGOTIABLE.
|
||||
* Max text width: 260px mobile, 320px tablet, 400px desktop.
|
||||
* Step number: Fraunces 56px mobile, 72px desktop. Green-light.
|
||||
* TAP ANYWHERE to advance. Wake lock on enter. Exit = top-left error red.
|
||||
* Final step: "Done — mark as cooked" → cooking_log INSERT → C1.
|
||||
* This is an EXCEPTION to nav-spec: no sidebar, no breadcrumbs, no tabs. */</pre>
|
||||
<table class="at"><thead><tr><th>Element</th><th>Value</th><th>Notes</th></tr></thead><tbody>
|
||||
<tr class="grp"><td colspan="3">Layout — IDENTICAL all breakpoints</td></tr>
|
||||
<tr><td>Body text</td><td>16px, line-height 1.75</td><td>NON-NEGOTIABLE</td></tr>
|
||||
<tr><td>Max text width</td><td>260px mobile / 320px tablet / 400px desktop</td><td>Only difference between breakpoints</td></tr>
|
||||
<tr><td>Step number</td><td>Fraunces 56px mobile / 72px desktop, weight 300, green-light</td><td>Visible from arm's length</td></tr>
|
||||
<tr><td>Tap target</td><td>Entire body area</td><td>onclick → next step</td></tr>
|
||||
<tr><td>Wake lock</td><td>navigator.wakeLock.request('screen')</td><td>On enter, release on exit/done</td></tr>
|
||||
</tbody></table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<!-- ═══ LLM INSTRUCTIONS ═══ -->
|
||||
<div class="llm">
|
||||
<h2>LLM Implementation Instructions — J3 Cook Tonight</h2>
|
||||
<p>This section provides structured guidance for code-generating LLMs implementing the J3 journey. Follow these rules exactly.</p>
|
||||
|
||||
<h3>1. Journey Flow</h3>
|
||||
<p><strong>C1</strong> (today highlight) → <strong>B2</strong> (recipe detail) → <strong>B4</strong> (cook mode) → <strong>C1</strong></p>
|
||||
<ul>
|
||||
<li><strong>Actor:</strong> Planner</li>
|
||||
<li><strong>Context:</strong> Mobile, hands busy, kitchen environment</li>
|
||||
<li>The user taps today's meal on the planner (C1), views the recipe (B2), enters cook mode (B4), steps through instructions, marks as cooked, and returns to the planner (C1).</li>
|
||||
</ul>
|
||||
|
||||
<h3>2. Screen B2 — Recipe Detail</h3>
|
||||
<h3>Mobile layout</h3>
|
||||
<ul>
|
||||
<li>Green-tint hero banner at top: back link ("← Planner"), title in Fraunces 24px, pill tags (time, difficulty, servings), and a full-width "Start cooking" button.</li>
|
||||
<li>Below the hero: stacked ingredients section, then steps section.</li>
|
||||
</ul>
|
||||
<h3>Desktop layout</h3>
|
||||
<ul>
|
||||
<li>Sidebar (224px) + topbar with breadcrumb ("← Recipes / Recipe Name") and Edit button.</li>
|
||||
<li>Full-width hero banner: green-tint background, 32px padding. Flex layout with recipe info left (<code>flex:1</code>) and Cook button right (<code>flex-shrink:0</code>).</li>
|
||||
<li>Below hero: 2-column layout. Ingredients left (<code>flex:1</code>, <code>border-right</code>) + steps right (<code>flex:1</code>). Both panels scroll independently.</li>
|
||||
</ul>
|
||||
<h3>Hero variants</h3>
|
||||
<ul>
|
||||
<li><strong>With image:</strong> <code>hero_image_url</code> as background, 40% dark overlay, all text rendered in white.</li>
|
||||
<li><strong>Without image:</strong> green-tint background, dark text (green-deeper for title).</li>
|
||||
</ul>
|
||||
<h3>Component details</h3>
|
||||
<ul>
|
||||
<li><strong>Ingredients:</strong> Read-only <code>.ir</code> rows (quantity + name). Quantities are scaled to the saved serving count for the planned meal.</li>
|
||||
<li><strong>Steps:</strong> Numbered circles (28px diameter on desktop, 22px mobile) + step text (14px, line-height 1.6 on desktop; 13px, line-height 1.5 on mobile).</li>
|
||||
</ul>
|
||||
<h3>Navigation</h3>
|
||||
<ul>
|
||||
<li>"Start cooking" button → navigates to <strong>B4</strong> (cook mode).</li>
|
||||
<li>"Edit" button → navigates to <strong>B3</strong> (recipe form, prefilled with current recipe data).</li>
|
||||
</ul>
|
||||
|
||||
<h3>3. Screen B4 — Cook Mode (KITCHEN CRITICAL)</h3>
|
||||
<p>This screen is the single exception to all responsive and navigation patterns in the app.</p>
|
||||
<h3>Layout rules</h3>
|
||||
<ul>
|
||||
<li><strong>IDENTICAL LAYOUT on ALL breakpoints</strong> — this is the ONLY screen that ignores responsive patterns.</li>
|
||||
<li><strong>NO sidebar, NO tabs, NO navigation chrome on ANY breakpoint.</strong> This is an explicit exception to the nav-spec.</li>
|
||||
</ul>
|
||||
<h3>Topbar</h3>
|
||||
<ul>
|
||||
<li>Three-column flex: Exit button (left, error red <code>var(--color-error)</code>) + progress indicator (center, "Step N of M") + empty spacer (right).</li>
|
||||
</ul>
|
||||
<h3>Progress bar</h3>
|
||||
<ul>
|
||||
<li>Height: 4px. Track: <code>var(--color-subtle)</code>. Fill: <code>var(--green)</code>. Border-radius: 2px.</li>
|
||||
<li>Width percentage: <code>(currentStep / totalSteps) * 100%</code>.</li>
|
||||
</ul>
|
||||
<h3>Body</h3>
|
||||
<ul>
|
||||
<li>Centered step number: Fraunces, weight 300, <code>var(--green-light)</code>. Size: 56px (mobile) / 72px (desktop).</li>
|
||||
<li>Step text: <strong>EXACTLY 16px, line-height 1.75 — NON-NEGOTIABLE.</strong> This is a readability requirement for kitchen use with dirty hands.</li>
|
||||
<li>Max text width: 260px (mobile) / 320px (tablet) / 400px (desktop).</li>
|
||||
</ul>
|
||||
<h3>Interaction</h3>
|
||||
<ul>
|
||||
<li><strong>TAP ANYWHERE to advance:</strong> The entire body area is the tap target. No fine motor precision required.</li>
|
||||
<li>On the final step, the tap target label changes to "Done — mark as cooked".</li>
|
||||
<li>Tapping on the final step triggers: <code>cooking_log INSERT</code> with <code>recipe_id</code> and <code>cooked_date</code> set to today's date, then navigates back to <strong>C1</strong>.</li>
|
||||
</ul>
|
||||
<h3>Wake lock</h3>
|
||||
<ul>
|
||||
<li>On entering B4: call <code>navigator.wakeLock.request('screen')</code> to prevent the screen from sleeping.</li>
|
||||
<li>On exiting B4 (via Exit button or "Done"): release the wake lock.</li>
|
||||
</ul>
|
||||
|
||||
<h3>4. Critical Feedback Loop</h3>
|
||||
<p>The <code>cooking_log</code> table is the data source for the variety/repetition algorithm used in J2 (meal suggestions). Meals cooked more recently are weighted more heavily in the repetition filter, causing the algorithm to deprioritize them in future suggestions. This means J3's "mark as cooked" action directly makes J2's suggestions smarter over time.</p>
|
||||
|
||||
<h3>5. Data Operations</h3>
|
||||
<table>
|
||||
<thead><tr><th>Screen</th><th>Operation</th><th>Tables</th></tr></thead>
|
||||
<tbody>
|
||||
<tr><td>B2</td><td>READ</td><td><code>recipe</code>, <code>recipe_ingredient</code>, <code>ingredient</code>, <code>recipe_step</code></td></tr>
|
||||
<tr><td>B4</td><td>WRITE</td><td><code>cooking_log</code> INSERT (<code>recipe_id</code>, <code>cooked_date</code>)</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<h3>6. Accessibility</h3>
|
||||
<ul>
|
||||
<li>B4's tap target is the entire screen body — accessible with one finger, no fine motor precision needed. This is intentional for kitchen use where hands may be wet or dirty.</li>
|
||||
<li>The wake lock prevents the screen from sleeping mid-recipe, eliminating the need to unlock the device with messy hands.</li>
|
||||
<li>Step text at 16px with 1.75 line-height ensures readability at arm's length.</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
295
specs/frontend/j4-adapt-on-the-fly.html
Normal file
295
specs/frontend/j4-adapt-on-the-fly.html
Normal file
@@ -0,0 +1,295 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8"/>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
|
||||
<title>J4 — Adapt on the Fly</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"/>
|
||||
<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:#F2C12E;--yellow-dark:#C49610;--yellow-text:#8A6800;--blue-tint:#E6F1FB;--blue-light:#A4CFF4;--blue:#2D7DD2;--blue-dark:#185FA5;--purple-tint:#EEEDFE;--purple:#534AB7;--purple-dark:#3C3489;--orange-tint:#FEF0E6;--orange:#E8862A;--orange-dark:#B46820;--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-xl:16px;--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,.08),0 2px 4px rgba(28,28,24,.04);--shadow-overlay:0 8px 32px rgba(28,28,24,.12),0 2px 8px rgba(28,28,24,.06);}
|
||||
*,*::before,*::after{box-sizing:border-box;margin:0;padding:0;}
|
||||
body{font-family:var(--font-sans);background:#E8E7E2;color:var(--color-text);font-size:14px;line-height:1.6;}
|
||||
.doc{max-width:1200px;margin:0 auto;padding:48px 40px 120px;}
|
||||
|
||||
/* Header */
|
||||
.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;background:var(--color-page);margin:-48px -40px 48px;padding:48px 40px 28px;border-radius:var(--radius-xl) var(--radius-xl) 0 0;}
|
||||
.doc-header h1{font-family:var(--font-display);font-size:28px;font-weight:500;letter-spacing:-.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;}
|
||||
.pill{display:inline-block;padding:2px 8px;border-radius:var(--radius-sm);font-size:10px;font-weight:500;letter-spacing:.05em;background:var(--green-tint);color:var(--green-dark);}
|
||||
|
||||
/* Sections */
|
||||
.section{margin-bottom:64px;}
|
||||
.section-title{font-size:10px;font-weight:500;letter-spacing:.12em;text-transform:uppercase;color:var(--color-text-muted);padding-bottom:10px;border-bottom:1px solid var(--color-border);margin-bottom:24px;}
|
||||
.prose{font-size:13px;color:var(--color-text-muted);line-height:1.65;max-width:720px;margin-bottom:20px;}
|
||||
|
||||
/* Journey headers */
|
||||
.jh{padding:20px 24px;border-radius:var(--radius-xl);margin-bottom:40px;display:flex;align-items:center;gap:16px;}
|
||||
.jh .jn{font-family:var(--font-display);font-size:48px;font-weight:300;line-height:1;opacity:.5;}
|
||||
.jh h2{font-family:var(--font-display);font-size:22px;font-weight:500;letter-spacing:-.02em;margin-bottom:4px;}
|
||||
.jh p{font-size:13px;line-height:1.5;}.jh .fl{font-family:var(--font-mono);font-size:11px;margin-top:6px;opacity:.7;}
|
||||
.jh-p{background:var(--purple-tint);border:1px solid #CECBF6;}.jh-p .jn{color:var(--purple);}.jh-p p,.jh-p .fl{color:var(--purple-dark);}
|
||||
.jh-g{background:var(--green-tint);border:1px solid var(--green-light);}.jh-g .jn{color:var(--green);}.jh-g p,.jh-g .fl{color:var(--green-dark);}
|
||||
.jh-y{background:var(--yellow-tint);border:1px solid var(--yellow-light);}.jh-y .jn{color:var(--yellow-dark);}.jh-y p,.jh-y .fl{color:var(--yellow-text);}
|
||||
.jh-o{background:var(--orange-tint);border:1px solid #FBCDA4;}.jh-o .jn{color:var(--orange);}.jh-o p,.jh-o .fl{color:var(--orange-dark);}
|
||||
.jh-b{background:var(--blue-tint);border:1px solid var(--blue-light);}.jh-b .jn{color:var(--blue);}.jh-b p,.jh-b .fl{color:var(--blue-dark);}
|
||||
|
||||
/* Screen block */
|
||||
.scr{margin-bottom:56px;}
|
||||
.scr-head{display:flex;justify-content:space-between;align-items:center;margin-bottom:6px;}
|
||||
.scr-head h3{font-family:var(--font-display);font-size:20px;font-weight:500;letter-spacing:-.02em;}
|
||||
.scr-id{font-family:var(--font-mono);font-size:11px;color:var(--color-text-muted);padding:2px 8px;border:1px solid var(--color-border);border-radius:var(--radius-sm);background:var(--color-page);}
|
||||
.scr-desc{font-size:12px;color:var(--color-text-muted);line-height:1.6;max-width:720px;margin-bottom:6px;}
|
||||
.scr-var{font-size:11px;color:var(--color-text-muted);margin-bottom:20px;}.scr-var strong{color:var(--color-text);}
|
||||
|
||||
/* Preview container */
|
||||
.previews{display:flex;gap:32px;flex-wrap:wrap;justify-content:center;align-items:flex-start;margin-bottom:20px;}
|
||||
.prev-col{display:flex;flex-direction:column;align-items:center;gap:10px;}
|
||||
.bp-lbl{font-family:var(--font-mono);font-size:10px;color:var(--color-text-muted);}
|
||||
|
||||
/* Phone frame - 320px */
|
||||
.phone{width:320px;flex-shrink:0;background:var(--color-page);border-radius:36px;overflow:hidden;box-shadow:var(--shadow-overlay),0 0 0 1px rgba(0,0,0,.07);display:flex;flex-direction:column;border:6px solid #1C1C18;}
|
||||
.pst{padding:10px 20px 0;display:flex;justify-content:space-between;align-items:center;font-size:12px;background:var(--color-page);}.pst b{font-weight:600;}.pst span{font-size:10px;}
|
||||
.pb{flex:1;overflow-y:auto;display:flex;flex-direction:column;}
|
||||
|
||||
/* Desktop frame - 1040px */
|
||||
.desk{width:100%;max-width:1040px;background:var(--color-page);border-radius:var(--radius-xl);overflow:hidden;box-shadow:var(--shadow-overlay),0 0 0 1px rgba(0,0,0,.06);display:flex;min-height:520px;}
|
||||
|
||||
/* Shared nav components */
|
||||
.mtb{padding:10px 16px;background:var(--color-page);border-bottom:1px solid var(--color-border);display:flex;justify-content:space-between;align-items:center;}
|
||||
.mtb-t{font-family:var(--font-display);font-size:20px;font-weight:500;letter-spacing:-.02em;}
|
||||
.mi{width:32px;height:32px;border-radius:var(--radius-md);border:1px solid var(--color-border);background:var(--color-surface);display:flex;align-items:center;justify-content:center;font-size:13px;color:var(--color-text-muted);flex-shrink:0;}.mi.gn{background:var(--green);border-color:var(--green);color:#fff;}
|
||||
.mbt{border-top:1px solid var(--color-border);background:var(--color-surface);padding:8px 16px 28px;display:flex;justify-content:space-around;}
|
||||
.mt-i{display:flex;flex-direction:column;align-items:center;gap:2px;}.mt-ic{width:20px;height:20px;border-radius:4px;background:var(--color-subtle);display:flex;align-items:center;justify-content:center;font-size:11px;}.mt-i.a .mt-ic{background:var(--green-tint);}.mt-l{font-size:9px;font-weight:500;color:var(--color-text-muted);}.mt-i.a .mt-l{color:var(--green-dark);}
|
||||
|
||||
/* Desktop sidebar - 224px per nav-spec */
|
||||
.dsb{width:224px;flex-shrink:0;background:var(--color-surface);border-right:1px solid var(--color-border);display:flex;flex-direction:column;}
|
||||
.dsb-logo{padding:20px 16px 16px;border-bottom:1px solid var(--color-border);}
|
||||
.dsb-lm{display:flex;align-items:center;gap:8px;margin-bottom:2px;}.dsb-ic{width:24px;height:24px;border-radius:5px;background:var(--green);display:flex;align-items:center;justify-content:center;font-size:12px;}.dsb-nm{font-family:var(--font-display);font-size:16px;font-weight:500;letter-spacing:-.02em;}.dsb-sub{font-size:10px;color:var(--color-text-muted);padding-left:32px;}
|
||||
.dsb-nav{padding:12px 10px;flex:1;}.dsb-nl{font-size:8px;font-weight:500;letter-spacing:.1em;text-transform:uppercase;color:var(--color-text-muted);padding:0 8px;margin-bottom:4px;}.dsb-ni{display:flex;align-items:center;gap:8px;padding:7px 8px;border-radius:var(--radius-md);font-size:13px;color:var(--color-text-muted);margin-bottom:2px;cursor:default;}.dsb-ni.a{background:var(--green-tint);color:var(--green-dark);font-weight:500;}.dsb-nc{font-size:13px;width:18px;text-align:center;}
|
||||
.dm{flex:1;display:flex;flex-direction:column;min-width:0;}
|
||||
.dtb{padding:12px 24px;border-bottom:1px solid var(--color-border);display:flex;justify-content:space-between;align-items:center;flex-shrink:0;}
|
||||
.dtb-t{font-family:var(--font-display);font-size:18px;font-weight:500;letter-spacing:-.02em;}
|
||||
.dtb-r{display:flex;align-items:center;gap:8px;}
|
||||
.dab{font-family:var(--font-sans);font-size:13px;font-weight:500;padding:7px 16px;border-radius:var(--radius-md);background:var(--green);color:#fff;border:none;}
|
||||
.dab-b{background:var(--blue);}
|
||||
|
||||
/* Shared form */
|
||||
.fi{width:100%;font-family:var(--font-sans);font-size:14px;padding:10px 12px;border-radius:var(--radius-md);border:1px solid var(--color-border);background:var(--color-page);color:var(--color-text);outline:none;}
|
||||
.fl{font-size:12px;font-weight:500;color:var(--color-text);margin-bottom:6px;display:block;}
|
||||
.fg{margin-bottom:16px;}
|
||||
.bp{font-family:var(--font-sans);font-size:14px;font-weight:500;padding:12px 24px;border-radius:var(--radius-md);background:var(--green);color:#fff;border:none;cursor:pointer;width:100%;}
|
||||
.bg{font-family:var(--font-sans);font-size:13px;font-weight:500;padding:10px 20px;border-radius:var(--radius-md);background:var(--color-subtle);color:var(--color-text-muted);border:1px solid var(--color-border);cursor:pointer;}
|
||||
|
||||
/* Tags */
|
||||
.tc{display:inline-flex;font-size:12px;font-weight:500;padding:6px 12px;border-radius:20px;border:1px solid var(--color-border);background:var(--color-surface);color:var(--color-text-muted);cursor:pointer;margin:0 4px 4px 0;user-select:none;}.tc.s{background:var(--green-tint);color:var(--green-dark);border-color:var(--green-light);}
|
||||
.badge{font-size:9px;font-weight:500;padding:2px 6px;border-radius:3px;display:inline-block;}.badge-g{background:var(--green-tint);color:var(--green-dark);}.badge-y{background:var(--yellow-tint);color:var(--yellow-text);}.badge-m{background:var(--color-subtle);color:var(--color-text-muted);}
|
||||
|
||||
/* Ingredient rows */
|
||||
.ir{display:flex;align-items:center;gap:8px;padding:8px 0;border-bottom:1px solid var(--color-subtle);}.ir:last-child{border-bottom:none;}.ir-q{font-family:var(--font-mono);font-size:12px;color:var(--color-text-muted);width:55px;flex-shrink:0;text-align:right;}.ir-n{font-size:13px;color:var(--color-text);flex:1;}.ir-x{font-size:14px;color:var(--color-border);cursor:pointer;width:20px;text-align:center;}
|
||||
|
||||
/* Checklist */
|
||||
.ck{display:flex;align-items:center;gap:10px;padding:10px 0;border-bottom:1px solid var(--color-subtle);cursor:pointer;}.ck:last-child{border-bottom:none;}.ck-b{width:22px;height:22px;border-radius:4px;border:2px solid var(--color-border);flex-shrink:0;display:flex;align-items:center;justify-content:center;font-size:11px;}.ck.d .ck-b{background:var(--green);border-color:var(--green);color:#fff;}.ck-c{flex:1;}.ck-n{font-size:14px;color:var(--color-text);}.ck.d .ck-n{text-decoration:line-through;color:var(--color-text-muted);}.ck-s{font-size:10px;color:var(--color-text-muted);}.ck-q{font-family:var(--font-mono);font-size:12px;color:var(--color-text-muted);flex-shrink:0;}
|
||||
|
||||
/* Suggestion cards */
|
||||
.sg{background:var(--color-surface);border:1px solid var(--color-border);border-radius:var(--radius-lg);padding:12px;margin-bottom:8px;display:flex;align-items:center;gap:10px;}.sg-r{font-family:var(--font-display);font-size:16px;font-weight:300;color:var(--color-text-muted);width:20px;text-align:center;flex-shrink:0;}.sg-b{flex:1;}.sg-n{font-family:var(--font-display);font-size:13px;font-weight:400;color:var(--color-text);margin-bottom:2px;}.sg-i{font-size:10px;color:var(--color-text-muted);}.sg-w{font-size:9px;color:var(--green-dark);background:var(--green-tint);padding:2px 6px;border-radius:3px;display:inline-block;margin-top:3px;}.sg-p{font-size:11px;font-weight:500;color:var(--green);flex-shrink:0;}
|
||||
|
||||
/* Eyebrow labels */
|
||||
.eye{font-size:10px;font-weight:500;letter-spacing:.08em;text-transform:uppercase;color:var(--color-text-muted);}
|
||||
|
||||
/* Agent table */
|
||||
.agent{background:var(--color-text);color:#E8E8E2;padding:24px;border-radius:var(--radius-lg);margin-top:20px;}
|
||||
.agent h4{font-size:9px;font-weight:500;letter-spacing:.1em;text-transform:uppercase;color:#5A5A55;margin-bottom:12px;}
|
||||
.agent pre{font-family:var(--font-mono);font-size:10px;color:#444440;margin-bottom:16px;line-height:1.8;white-space:pre-wrap;}
|
||||
.at{width:100%;border-collapse:collapse;font-family:var(--font-mono);font-size:10px;}
|
||||
.at thead tr{border-bottom:1px solid #2A2A26;}.at th{text-align:left;padding:6px 10px;font-size:8px;font-weight:500;letter-spacing:.08em;text-transform:uppercase;color:#5A5A55;font-family:var(--font-sans);}.at td{padding:5px 10px;border-bottom:1px solid #1E1E1A;vertical-align:top;line-height:1.5;}.at tr:last-child td{border-bottom:none;}.at td:first-child{color:#7A7A72;}.at td:nth-child(2){color:#E8E8E2;font-weight:500;}.at td:nth-child(3){color:#5A5A55;}.at .grp td{padding-top:14px;font-family:var(--font-sans);font-size:8px;font-weight:500;letter-spacing:.08em;text-transform:uppercase;color:#3A3A36;}
|
||||
|
||||
/* LLM instruction box */
|
||||
.llm{background:var(--color-page);border:2px solid var(--green-light);border-radius:var(--radius-lg);padding:28px 24px;margin-top:64px;}
|
||||
.llm h3{font-family:var(--font-display);font-size:18px;font-weight:500;letter-spacing:-.02em;color:var(--green-dark);margin-bottom:16px;}
|
||||
.llm h4{font-size:11px;font-weight:600;letter-spacing:.04em;text-transform:uppercase;color:var(--green-dark);margin-top:20px;margin-bottom:8px;}
|
||||
.llm p,.llm li{font-size:12px;color:var(--color-text-muted);line-height:1.7;}
|
||||
.llm ul,.llm ol{padding-left:20px;margin-bottom:12px;}
|
||||
.llm li{margin-bottom:4px;}
|
||||
.llm code{font-family:var(--font-mono);font-size:11px;background:var(--green-tint);color:var(--green-dark);padding:1px 5px;border-radius:3px;}
|
||||
|
||||
@media(max-width:900px){.doc{padding:24px 16px 80px;}}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="doc">
|
||||
|
||||
<!-- ═══ J4 SWAP ═══ -->
|
||||
<div class="jh jh-o">
|
||||
<div class="jn">J4</div>
|
||||
<div><h2>Adapt on the fly</h2><p>Mid-week swap in ≤ 3 taps. Action sheet → pick replacement → done.</p><div class="fl">C1 → action sheet → C2 swap → C1 · Easiest first</div></div>
|
||||
</div>
|
||||
|
||||
<!-- ═══ SWAP TRIGGER ═══ -->
|
||||
<div class="scr" id="swap-trigger">
|
||||
<div class="scr-head"><h3>Swap trigger</h3><span class="scr-id">C1 overlay</span></div>
|
||||
<div class="scr-desc">Mobile: tap meal → bottom action sheet (Swap / Cook / View / Cancel). Desktop: no action sheet needed — the C1 detail panel already has a "Swap meal" button. Clicking it transitions the detail panel content to show swap suggestions inline.</div>
|
||||
<div class="scr-var"><strong>V4 · Action sheet</strong> (mobile) · Detail panel button (desktop)</div>
|
||||
|
||||
<div class="previews">
|
||||
<div class="prev-col">
|
||||
<div class="bp-lbl">Mobile · Action sheet</div>
|
||||
<div class="phone">
|
||||
<div class="pst"><b>17:15</b><span>●●● WiFi 🔋</span></div>
|
||||
<div class="pb">
|
||||
<div class="mtb" style="opacity:.4;"><div class="mtb-t" style="font-size:16px;">This week</div></div>
|
||||
<div style="padding:8px 12px;opacity:.4;">
|
||||
<div style="background:var(--color-surface);border:1px solid var(--color-border);border-radius:var(--radius-lg);padding:10px;margin-bottom:6px;font-family:var(--font-display);font-size:12px;">Mon · Chicken stir-fry</div>
|
||||
<div style="background:var(--color-surface);border:1px solid var(--color-border);border-radius:var(--radius-lg);padding:10px;font-family:var(--font-display);font-size:12px;">Tue · Tomato pasta</div>
|
||||
</div>
|
||||
<div style="flex:1;display:flex;flex-direction:column;justify-content:flex-end;">
|
||||
<div style="background:var(--color-page);border-top:1px solid var(--color-border);border-radius:var(--radius-xl) var(--radius-xl) 0 0;padding:16px;box-shadow:0 -4px 20px rgba(0,0,0,.12);">
|
||||
<div style="width:32px;height:4px;border-radius:2px;background:var(--color-border);margin:0 auto 12px;"></div>
|
||||
<div style="font-family:var(--font-display);font-size:15px;font-weight:500;margin-bottom:4px;">Tuesday — Tomato pasta</div>
|
||||
<div style="font-size:11px;color:var(--color-text-muted);margin-bottom:12px;">45 min · Easy · Vegetarian</div>
|
||||
<div style="display:flex;flex-direction:column;gap:6px;">
|
||||
<div style="padding:12px;border-radius:var(--radius-lg);background:var(--orange-tint);border:1px solid #FBCDA4;font-size:13px;font-weight:500;color:var(--orange-dark);text-align:center;">↻ Swap this meal</div>
|
||||
<div style="padding:12px;border-radius:var(--radius-lg);background:var(--green-tint);border:1px solid var(--green-light);font-size:13px;font-weight:500;color:var(--green-dark);text-align:center;">🍳 Cook now</div>
|
||||
<div style="padding:12px;border-radius:var(--radius-lg);background:var(--color-subtle);border:1px solid var(--color-border);font-size:13px;font-weight:500;color:var(--color-text-muted);text-align:center;">👁 View recipe</div>
|
||||
<div style="padding:12px;font-size:13px;color:var(--color-text-muted);text-align:center;">Cancel</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="agent">
|
||||
<h4>Swap trigger</h4>
|
||||
<pre>/* Mobile: tap meal card → bottom action sheet. Planner dims to 40%.
|
||||
* Sheet: drag handle + meal name + meta + 4 action buttons stacked.
|
||||
* Swap = orange-tint. Cook = green-tint. View = subtle. Cancel = no bg.
|
||||
* Desktop: no action sheet. C1 detail panel has "Swap meal" ghost button (per planner-spec).
|
||||
* Clicking "Swap meal" transitions the detail panel to show swap suggestions inline.
|
||||
* Tap count: Mobile 3 (card → Swap → Pick). Desktop 2 (Swap → Pick). */</pre>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<!-- ═══ C2 SWAP CONTEXT ═══ -->
|
||||
<div class="scr" id="swap-context">
|
||||
<div class="scr-head"><h3>C2 in swap context</h3><span class="scr-id">C2 swap</span></div>
|
||||
<div class="scr-desc">Mobile: bottom sheet over C1 with "Replacing" banner + easiest-first suggestions. Desktop: swap suggestions render inline in the C1 detail panel (replacing the meal detail) — the calendar grid stays visible alongside. No page navigation needed.</div>
|
||||
<div class="scr-var"><strong>V1 · Quick swap sheet</strong> (mobile) · Inline detail panel (desktop)</div>
|
||||
|
||||
<div class="previews">
|
||||
<div class="prev-col">
|
||||
<div class="bp-lbl">Mobile · Swap sheet</div>
|
||||
<div class="phone">
|
||||
<div class="pst"><b>17:16</b><span>●●● WiFi 🔋</span></div>
|
||||
<div class="pb">
|
||||
<div style="opacity:.3;padding:10px 16px;border-bottom:1px solid var(--color-border);font-family:var(--font-display);font-size:16px;font-weight:500;">This week</div>
|
||||
<div style="flex:1;display:flex;flex-direction:column;justify-content:flex-end;">
|
||||
<div style="background:var(--color-page);border-top:1px solid var(--color-border);border-radius:var(--radius-xl) var(--radius-xl) 0 0;padding:16px;box-shadow:0 -4px 20px rgba(0,0,0,.12);">
|
||||
<div style="width:32px;height:4px;border-radius:2px;background:var(--color-border);margin:0 auto 10px;"></div>
|
||||
<div style="background:var(--orange-tint);border:1px solid #FBCDA4;border-radius:var(--radius-lg);padding:10px 12px;margin-bottom:14px;">
|
||||
<div class="eye" style="color:var(--orange-dark);margin-bottom:4px;">Replacing Tuesday's meal</div>
|
||||
<div style="font-family:var(--font-display);font-size:14px;text-decoration:line-through;opacity:.6;">Tomato pasta · 45 min · Easy</div>
|
||||
</div>
|
||||
<div class="eye" style="margin-bottom:6px;">Swap to (easiest first)</div>
|
||||
<div style="background:var(--color-surface);border:1px solid var(--color-border);border-radius:var(--radius-lg);padding:10px 12px;margin-bottom:6px;display:flex;align-items:center;gap:8px;">
|
||||
<div style="flex:1;"><div style="font-family:var(--font-display);font-size:13px;">Quick carbonara</div><div style="font-size:9px;color:var(--color-text-muted);">20 min · Easy · Pasta</div></div>
|
||||
<div style="font-size:11px;color:var(--green);font-weight:500;">Pick</div>
|
||||
</div>
|
||||
<div style="background:var(--color-surface);border:1px solid var(--color-border);border-radius:var(--radius-lg);padding:10px 12px;margin-bottom:6px;display:flex;align-items:center;gap:8px;">
|
||||
<div style="flex:1;"><div style="font-family:var(--font-display);font-size:13px;">Chicken stir-fry</div><div style="font-size:9px;color:var(--color-text-muted);">25 min · Easy</div><div style="font-size:9px;color:var(--yellow-text);">⚠ Already on Mon</div></div>
|
||||
<div style="font-size:11px;color:var(--green);font-weight:500;">Pick</div>
|
||||
</div>
|
||||
<div style="background:var(--color-surface);border:1px solid var(--color-border);border-radius:var(--radius-lg);padding:10px 12px;margin-bottom:6px;display:flex;align-items:center;gap:8px;">
|
||||
<div style="flex:1;"><div style="font-family:var(--font-display);font-size:13px;">Mushroom risotto</div><div style="font-size:9px;color:var(--color-text-muted);">50 min · Medium · Veggie</div></div>
|
||||
<div style="font-size:11px;color:var(--green);font-weight:500;">Pick</div>
|
||||
</div>
|
||||
<div style="text-align:center;padding:8px 0;font-size:11px;color:var(--color-text-muted);">Cancel</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="agent">
|
||||
<h4>C2 swap context</h4>
|
||||
<pre>/* Mobile: bottom sheet over dimmed C1. "Replacing" banner + suggestion list.
|
||||
* Sorted EASIEST FIRST (effort ASC, cook_time ASC) — different from J2.
|
||||
* "Pick" → UPDATE week_plan_slot. Dismiss sheet. No confirmation dialog (undo toast instead).
|
||||
* Desktop: detail panel (280px) transitions in-place. Calendar grid stays visible.
|
||||
* Replacing header: orange-tint, old meal struck through.
|
||||
* Suggestion cards: compact, fitting panel width. Name + meta + "Pick" link.
|
||||
* Tap count: Mobile 3. Desktop 2 (faster — no action sheet intermediary). */</pre>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<!-- ═══ LLM INSTRUCTIONS ═══ -->
|
||||
<div class="llm">
|
||||
<h3>LLM Implementation Instructions — J4 Adapt on the Fly</h3>
|
||||
|
||||
<h4>1. Journey Flow</h4>
|
||||
<p>C1 → action sheet (mobile) or detail panel button (desktop) → swap suggestions → pick → C1.
|
||||
Actor: <strong>Planner</strong>. Frequency: 1-2x/week. Urgency: <strong>HIGH</strong>.</p>
|
||||
|
||||
<h4>2. Constraint: 3 taps maximum</h4>
|
||||
<p>From "Swap" to updated plan in no more than 3 taps.</p>
|
||||
<ul>
|
||||
<li><strong>Mobile: 3 taps</strong> — card → Swap → Pick</li>
|
||||
<li><strong>Desktop: 2 taps</strong> — Swap → Pick (no action sheet intermediary)</li>
|
||||
</ul>
|
||||
|
||||
<h4>3. Mobile: Action Sheet</h4>
|
||||
<ul>
|
||||
<li>Bottom sheet pulls up on meal tap; background dims to <strong>40% opacity</strong>.</li>
|
||||
<li>Drag handle: 32px wide, 4px height, <code>var(--color-border)</code> background.</li>
|
||||
<li>Meal title in 15px display font + metadata in 11px muted text.</li>
|
||||
<li>4 stacked buttons:
|
||||
<ol>
|
||||
<li><strong>"Swap this meal"</strong> — <code>orange-tint</code> bg / <code>orange-dark</code> text</li>
|
||||
<li><strong>"Cook now"</strong> — <code>green-tint</code> bg / <code>green-dark</code> text</li>
|
||||
<li><strong>"View recipe"</strong> — <code>subtle</code> bg / <code>muted</code> text</li>
|
||||
<li><strong>"Cancel"</strong> — no background, muted text</li>
|
||||
</ol>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<h4>4. Mobile: Swap Suggestions (Bottom Sheet)</h4>
|
||||
<ul>
|
||||
<li>"Replacing" banner: <code>orange-tint</code> background, old meal name struck through.</li>
|
||||
<li>"Swap to (easiest first)" eyebrow label above suggestion list.</li>
|
||||
<li>Compact suggestion cards: recipe name + time/effort/tag + "Pick" link on the right.</li>
|
||||
<li><strong>Sorted EASIEST FIRST</strong> — <code>effort ASC, cook_time ASC</code>. This is DIFFERENT from C2 in J2, which sorts by variety score.</li>
|
||||
<li>"Pick" action: <code>UPDATE week_plan_slot</code> with new <code>recipe_id</code> → dismiss sheet → show <strong>undo toast</strong> (NOT a confirmation dialog).</li>
|
||||
</ul>
|
||||
|
||||
<h4>5. Desktop: Inline Panel</h4>
|
||||
<ul>
|
||||
<li>No action sheet needed — the C1 detail panel (280px wide) already has a "Swap meal" ghost button.</li>
|
||||
<li>Clicking the button transitions the detail panel content <strong>in-place</strong> to show swap suggestions.</li>
|
||||
<li>Calendar grid stays visible alongside the panel at all times.</li>
|
||||
<li>Same sorting (<code>effort ASC, cook_time ASC</code>) and "Replacing" header as mobile.</li>
|
||||
</ul>
|
||||
|
||||
<h4>6. Why Easiest First</h4>
|
||||
<p>Mid-week swaps typically happen because the original plan was too ambitious. Sorting by effort makes the fastest, lowest-effort options most visible, matching the user's intent to simplify.</p>
|
||||
|
||||
<h4>7. Data Operations</h4>
|
||||
<ul>
|
||||
<li><strong>Writes:</strong> <code>week_plan_slot UPDATE</code> — sets new <code>recipe_id</code> on the slot.</li>
|
||||
<li>Swap is logged: both the original meal (marked as not cooked) and the replacement are recorded.</li>
|
||||
<li>Original uncooked meal remains in the recipe library for future weeks.</li>
|
||||
<li>Variety score recalculates immediately after swap.</li>
|
||||
</ul>
|
||||
|
||||
<h4>8. Design Constraints</h4>
|
||||
<ul>
|
||||
<li><strong>Speed over deliberation</strong> — undo toast instead of confirmation dialog. The user is in a hurry mid-week.</li>
|
||||
<li>Orange accent color for swap context: <code>orange-tint</code> background, <code>orange-dark</code> text on banners and primary action.</li>
|
||||
<li>Variety filter still applies to suggestions (duplicates get a warning), just sorted differently than J2.</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
389
specs/frontend/j5-shopping-list.html
Normal file
389
specs/frontend/j5-shopping-list.html
Normal file
@@ -0,0 +1,389 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8"/>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
|
||||
<title>Recipe App — J5 Shopping List Journey 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"/>
|
||||
<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:#F2C12E;--yellow-dark:#C49610;--yellow-text:#8A6800;--blue-tint:#E6F1FB;--blue-light:#A4CFF4;--blue:#2D7DD2;--blue-dark:#185FA5;--purple-tint:#EEEDFE;--purple:#534AB7;--purple-dark:#3C3489;--orange-tint:#FEF0E6;--orange:#E8862A;--orange-dark:#B46820;--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-xl:16px;--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,.08),0 2px 4px rgba(28,28,24,.04);--shadow-overlay:0 8px 32px rgba(28,28,24,.12),0 2px 8px rgba(28,28,24,.06);}
|
||||
*,*::before,*::after{box-sizing:border-box;margin:0;padding:0;}
|
||||
body{font-family:var(--font-sans);background:#E8E7E2;color:var(--color-text);font-size:14px;line-height:1.6;}
|
||||
.doc{max-width:1200px;margin:0 auto;padding:48px 40px 120px;}
|
||||
|
||||
/* Header */
|
||||
.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;background:var(--color-page);margin:-48px -40px 48px;padding:48px 40px 28px;border-radius:var(--radius-xl) var(--radius-xl) 0 0;}
|
||||
.doc-header h1{font-family:var(--font-display);font-size:28px;font-weight:500;letter-spacing:-.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;}
|
||||
.pill{display:inline-block;padding:2px 8px;border-radius:var(--radius-sm);font-size:10px;font-weight:500;letter-spacing:.05em;background:var(--green-tint);color:var(--green-dark);}
|
||||
|
||||
/* Sections */
|
||||
.section{margin-bottom:64px;}
|
||||
.section-title{font-size:10px;font-weight:500;letter-spacing:.12em;text-transform:uppercase;color:var(--color-text-muted);padding-bottom:10px;border-bottom:1px solid var(--color-border);margin-bottom:24px;}
|
||||
.prose{font-size:13px;color:var(--color-text-muted);line-height:1.65;max-width:720px;margin-bottom:20px;}
|
||||
|
||||
/* Journey headers */
|
||||
.jh{padding:20px 24px;border-radius:var(--radius-xl);margin-bottom:40px;display:flex;align-items:center;gap:16px;}
|
||||
.jh .jn{font-family:var(--font-display);font-size:48px;font-weight:300;line-height:1;opacity:.5;}
|
||||
.jh h2{font-family:var(--font-display);font-size:22px;font-weight:500;letter-spacing:-.02em;margin-bottom:4px;}
|
||||
.jh p{font-size:13px;line-height:1.5;}.jh .fl{font-family:var(--font-mono);font-size:11px;margin-top:6px;opacity:.7;}
|
||||
.jh-p{background:var(--purple-tint);border:1px solid #CECBF6;}.jh-p .jn{color:var(--purple);}.jh-p p,.jh-p .fl{color:var(--purple-dark);}
|
||||
.jh-g{background:var(--green-tint);border:1px solid var(--green-light);}.jh-g .jn{color:var(--green);}.jh-g p,.jh-g .fl{color:var(--green-dark);}
|
||||
.jh-y{background:var(--yellow-tint);border:1px solid var(--yellow-light);}.jh-y .jn{color:var(--yellow-dark);}.jh-y p,.jh-y .fl{color:var(--yellow-text);}
|
||||
.jh-o{background:var(--orange-tint);border:1px solid #FBCDA4;}.jh-o .jn{color:var(--orange);}.jh-o p,.jh-o .fl{color:var(--orange-dark);}
|
||||
.jh-b{background:var(--blue-tint);border:1px solid var(--blue-light);}.jh-b .jn{color:var(--blue);}.jh-b p,.jh-b .fl{color:var(--blue-dark);}
|
||||
|
||||
/* Screen block */
|
||||
.scr{margin-bottom:56px;}
|
||||
.scr-head{display:flex;justify-content:space-between;align-items:center;margin-bottom:6px;}
|
||||
.scr-head h3{font-family:var(--font-display);font-size:20px;font-weight:500;letter-spacing:-.02em;}
|
||||
.scr-id{font-family:var(--font-mono);font-size:11px;color:var(--color-text-muted);padding:2px 8px;border:1px solid var(--color-border);border-radius:var(--radius-sm);background:var(--color-page);}
|
||||
.scr-desc{font-size:12px;color:var(--color-text-muted);line-height:1.6;max-width:720px;margin-bottom:6px;}
|
||||
.scr-var{font-size:11px;color:var(--color-text-muted);margin-bottom:20px;}.scr-var strong{color:var(--color-text);}
|
||||
|
||||
/* Preview container */
|
||||
.previews{display:flex;gap:32px;flex-wrap:wrap;justify-content:center;align-items:flex-start;margin-bottom:20px;}
|
||||
.prev-col{display:flex;flex-direction:column;align-items:center;gap:10px;}
|
||||
.bp-lbl{font-family:var(--font-mono);font-size:10px;color:var(--color-text-muted);}
|
||||
|
||||
/* Phone frame - 320px */
|
||||
.phone{width:320px;flex-shrink:0;background:var(--color-page);border-radius:36px;overflow:hidden;box-shadow:var(--shadow-overlay),0 0 0 1px rgba(0,0,0,.07);display:flex;flex-direction:column;border:6px solid #1C1C18;}
|
||||
.pst{padding:10px 20px 0;display:flex;justify-content:space-between;align-items:center;font-size:12px;background:var(--color-page);}.pst b{font-weight:600;}.pst span{font-size:10px;}
|
||||
.pb{flex:1;overflow-y:auto;display:flex;flex-direction:column;}
|
||||
|
||||
/* Desktop frame - 1040px */
|
||||
.desk{width:100%;max-width:1040px;background:var(--color-page);border-radius:var(--radius-xl);overflow:hidden;box-shadow:var(--shadow-overlay),0 0 0 1px rgba(0,0,0,.06);display:flex;min-height:520px;}
|
||||
|
||||
/* Shared nav components */
|
||||
.mtb{padding:10px 16px;background:var(--color-page);border-bottom:1px solid var(--color-border);display:flex;justify-content:space-between;align-items:center;}
|
||||
.mtb-t{font-family:var(--font-display);font-size:20px;font-weight:500;letter-spacing:-.02em;}
|
||||
.mi{width:32px;height:32px;border-radius:var(--radius-md);border:1px solid var(--color-border);background:var(--color-surface);display:flex;align-items:center;justify-content:center;font-size:13px;color:var(--color-text-muted);flex-shrink:0;}.mi.gn{background:var(--green);border-color:var(--green);color:#fff;}
|
||||
.mbt{border-top:1px solid var(--color-border);background:var(--color-surface);padding:8px 16px 28px;display:flex;justify-content:space-around;}
|
||||
.mt-i{display:flex;flex-direction:column;align-items:center;gap:2px;}.mt-ic{width:20px;height:20px;border-radius:4px;background:var(--color-subtle);display:flex;align-items:center;justify-content:center;font-size:11px;}.mt-i.a .mt-ic{background:var(--green-tint);}.mt-l{font-size:9px;font-weight:500;color:var(--color-text-muted);}.mt-i.a .mt-l{color:var(--green-dark);}
|
||||
|
||||
/* Desktop sidebar - 224px per nav-spec */
|
||||
.dsb{width:224px;flex-shrink:0;background:var(--color-surface);border-right:1px solid var(--color-border);display:flex;flex-direction:column;}
|
||||
.dsb-logo{padding:20px 16px 16px;border-bottom:1px solid var(--color-border);}
|
||||
.dsb-lm{display:flex;align-items:center;gap:8px;margin-bottom:2px;}.dsb-ic{width:24px;height:24px;border-radius:5px;background:var(--green);display:flex;align-items:center;justify-content:center;font-size:12px;}.dsb-nm{font-family:var(--font-display);font-size:16px;font-weight:500;letter-spacing:-.02em;}.dsb-sub{font-size:10px;color:var(--color-text-muted);padding-left:32px;}
|
||||
.dsb-nav{padding:12px 10px;flex:1;}.dsb-nl{font-size:8px;font-weight:500;letter-spacing:.1em;text-transform:uppercase;color:var(--color-text-muted);padding:0 8px;margin-bottom:4px;}.dsb-ni{display:flex;align-items:center;gap:8px;padding:7px 8px;border-radius:var(--radius-md);font-size:13px;color:var(--color-text-muted);margin-bottom:2px;cursor:default;}.dsb-ni.a{background:var(--green-tint);color:var(--green-dark);font-weight:500;}.dsb-nc{font-size:13px;width:18px;text-align:center;}
|
||||
.dm{flex:1;display:flex;flex-direction:column;min-width:0;}
|
||||
.dtb{padding:12px 24px;border-bottom:1px solid var(--color-border);display:flex;justify-content:space-between;align-items:center;flex-shrink:0;}
|
||||
.dtb-t{font-family:var(--font-display);font-size:18px;font-weight:500;letter-spacing:-.02em;}
|
||||
.dtb-r{display:flex;align-items:center;gap:8px;}
|
||||
.dab{font-family:var(--font-sans);font-size:13px;font-weight:500;padding:7px 16px;border-radius:var(--radius-md);background:var(--green);color:#fff;border:none;}
|
||||
.dab-b{background:var(--blue);}
|
||||
|
||||
/* Shared form */
|
||||
.fi{width:100%;font-family:var(--font-sans);font-size:14px;padding:10px 12px;border-radius:var(--radius-md);border:1px solid var(--color-border);background:var(--color-page);color:var(--color-text);outline:none;}
|
||||
.fl{font-size:12px;font-weight:500;color:var(--color-text);margin-bottom:6px;display:block;}
|
||||
.fg{margin-bottom:16px;}
|
||||
.bp{font-family:var(--font-sans);font-size:14px;font-weight:500;padding:12px 24px;border-radius:var(--radius-md);background:var(--green);color:#fff;border:none;cursor:pointer;width:100%;}
|
||||
.bg{font-family:var(--font-sans);font-size:13px;font-weight:500;padding:10px 20px;border-radius:var(--radius-md);background:var(--color-subtle);color:var(--color-text-muted);border:1px solid var(--color-border);cursor:pointer;}
|
||||
|
||||
/* Tags */
|
||||
.tc{display:inline-flex;font-size:12px;font-weight:500;padding:6px 12px;border-radius:20px;border:1px solid var(--color-border);background:var(--color-surface);color:var(--color-text-muted);cursor:pointer;margin:0 4px 4px 0;user-select:none;}.tc.s{background:var(--green-tint);color:var(--green-dark);border-color:var(--green-light);}
|
||||
.badge{font-size:9px;font-weight:500;padding:2px 6px;border-radius:3px;display:inline-block;}.badge-g{background:var(--green-tint);color:var(--green-dark);}.badge-y{background:var(--yellow-tint);color:var(--yellow-text);}.badge-m{background:var(--color-subtle);color:var(--color-text-muted);}
|
||||
|
||||
/* Ingredient rows */
|
||||
.ir{display:flex;align-items:center;gap:8px;padding:8px 0;border-bottom:1px solid var(--color-subtle);}.ir:last-child{border-bottom:none;}.ir-q{font-family:var(--font-mono);font-size:12px;color:var(--color-text-muted);width:55px;flex-shrink:0;text-align:right;}.ir-n{font-size:13px;color:var(--color-text);flex:1;}.ir-x{font-size:14px;color:var(--color-border);cursor:pointer;width:20px;text-align:center;}
|
||||
|
||||
/* Checklist */
|
||||
.ck{display:flex;align-items:center;gap:10px;padding:10px 0;border-bottom:1px solid var(--color-subtle);cursor:pointer;}.ck:last-child{border-bottom:none;}.ck-b{width:22px;height:22px;border-radius:4px;border:2px solid var(--color-border);flex-shrink:0;display:flex;align-items:center;justify-content:center;font-size:11px;}.ck.d .ck-b{background:var(--green);border-color:var(--green);color:#fff;}.ck-c{flex:1;}.ck-n{font-size:14px;color:var(--color-text);}.ck.d .ck-n{text-decoration:line-through;color:var(--color-text-muted);}.ck-s{font-size:10px;color:var(--color-text-muted);}.ck-q{font-family:var(--font-mono);font-size:12px;color:var(--color-text-muted);flex-shrink:0;}
|
||||
|
||||
/* Suggestion cards */
|
||||
.sg{background:var(--color-surface);border:1px solid var(--color-border);border-radius:var(--radius-lg);padding:12px;margin-bottom:8px;display:flex;align-items:center;gap:10px;}.sg-r{font-family:var(--font-display);font-size:16px;font-weight:300;color:var(--color-text-muted);width:20px;text-align:center;flex-shrink:0;}.sg-b{flex:1;}.sg-n{font-family:var(--font-display);font-size:13px;font-weight:400;color:var(--color-text);margin-bottom:2px;}.sg-i{font-size:10px;color:var(--color-text-muted);}.sg-w{font-size:9px;color:var(--green-dark);background:var(--green-tint);padding:2px 6px;border-radius:3px;display:inline-block;margin-top:3px;}.sg-p{font-size:11px;font-weight:500;color:var(--green);flex-shrink:0;}
|
||||
|
||||
/* Eyebrow labels */
|
||||
.eye{font-size:10px;font-weight:500;letter-spacing:.08em;text-transform:uppercase;color:var(--color-text-muted);}
|
||||
|
||||
/* Agent table (inline) */
|
||||
.agent{background:var(--color-text);color:#E8E8E2;padding:24px;border-radius:var(--radius-lg);margin-top:20px;}
|
||||
.agent h4{font-size:9px;font-weight:500;letter-spacing:.1em;text-transform:uppercase;color:#5A5A55;margin-bottom:12px;}
|
||||
.agent pre{font-family:var(--font-mono);font-size:10px;color:#444440;margin-bottom:16px;line-height:1.8;white-space:pre-wrap;}
|
||||
.at{width:100%;border-collapse:collapse;font-family:var(--font-mono);font-size:10px;}
|
||||
.at thead tr{border-bottom:1px solid #2A2A26;}.at th{text-align:left;padding:6px 10px;font-size:8px;font-weight:500;letter-spacing:.08em;text-transform:uppercase;color:#5A5A55;font-family:var(--font-sans);}.at td{padding:5px 10px;border-bottom:1px solid #1E1E1A;vertical-align:top;line-height:1.5;}.at tr:last-child td{border-bottom:none;}.at td:first-child{color:#7A7A72;}.at td:nth-child(2){color:#E8E8E2;font-weight:500;}.at td:nth-child(3){color:#5A5A55;}.at .grp td{padding-top:14px;font-family:var(--font-sans);font-size:8px;font-weight:500;letter-spacing:.08em;text-transform:uppercase;color:#3A3A36;}
|
||||
|
||||
/* LLM instruction section */
|
||||
.llm{background:var(--color-text);color:#E8E8E2;border-radius:var(--radius-lg);padding:36px 44px;margin-top:80px;}
|
||||
.llm h2{font-size:10px;font-weight:500;letter-spacing:.1em;text-transform:uppercase;color:#6B6A63;margin-bottom:6px;}
|
||||
.llm > p{font-size:13px;color:#9A9990;margin-bottom:28px;line-height:1.6;max-width:640px;}
|
||||
.llm h3{font-size:9px;font-weight:500;letter-spacing:.09em;text-transform:uppercase;color:#6B6A63;margin-top:32px;margin-bottom:10px;padding-top:20px;border-top:1px solid #2A2A26;}
|
||||
.llm h3:first-of-type{border-top:none;padding-top:0;margin-top:0;}
|
||||
.llm pre{font-family:var(--font-mono);font-size:11px;color:#444440;margin-bottom:20px;line-height:1.8;white-space:pre-wrap;}
|
||||
.llm ul{list-style:none;padding:0;margin:0 0 16px;}
|
||||
.llm li{font-family:var(--font-mono);font-size:11px;color:#9A9990;line-height:1.8;padding-left:14px;position:relative;}
|
||||
.llm li::before{content:"-";position:absolute;left:0;color:#5A5A55;}
|
||||
.llm code{font-family:var(--font-mono);font-size:11px;color:#E8E8E2;background:#2A2A26;padding:1px 5px;border-radius:3px;}
|
||||
|
||||
@media(max-width:900px){.doc{padding:24px 16px 80px;}}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<!--
|
||||
spec:agent
|
||||
id: J5-shopping-list
|
||||
type: journey-spec
|
||||
version: 1.0
|
||||
-->
|
||||
<div class="doc">
|
||||
|
||||
<!-- ── Header ── -->
|
||||
<div class="doc-header">
|
||||
<div>
|
||||
<h1>J5 — Shopping list</h1>
|
||||
<p>Journey spec — Generate shopping list, real-time shared checklist</p>
|
||||
</div>
|
||||
<div class="doc-meta">
|
||||
v1.0<br/>
|
||||
<span class="pill">Journey</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ═══ J5 SHOPPING ═══ -->
|
||||
<div class="jh jh-b">
|
||||
<div class="jn">J5</div>
|
||||
<div><h2>Generate shopping list</h2><p>Merge ingredients, filter staples. Always live and shared with household.</p><div class="fl">C1 → D1 (always live) · Planner generates · All members add/remove/check off</div></div>
|
||||
</div>
|
||||
|
||||
<!-- ═══ D1 SHOPPING LIST ═══ -->
|
||||
<div class="scr" id="d1">
|
||||
<div class="scr-head"><h3>Shopping list (live)</h3><span class="scr-id">D1</span></div>
|
||||
<div class="scr-desc">V1 Checklist with sources. Desktop: sidebar + topbar + two-column content — checklist on the left, a "This week's recipes" reference panel on the right that shows which recipes contributed which items. The panel is a page section (surface bg), not a card.</div>
|
||||
<div class="scr-var"><strong>V1 · Checklist with sources</strong> — desktop: list left, recipe reference right</div>
|
||||
|
||||
<div class="previews">
|
||||
<div class="prev-col">
|
||||
<div class="bp-lbl">Mobile · 320px</div>
|
||||
<div class="phone">
|
||||
<div class="pst"><b>10:24</b><span>●●● WiFi 🔋</span></div>
|
||||
<div class="pb">
|
||||
<div class="mtb"><div class="mtb-t">Shopping list</div><div class="mi">⚙️</div></div>
|
||||
<div style="padding:8px 12px;">
|
||||
<div style="background:var(--blue-tint);border:1px solid var(--blue-light);border-radius:var(--radius-lg);padding:8px 10px;display:flex;align-items:center;gap:8px;margin-bottom:10px;">
|
||||
<div style="width:8px;height:8px;border-radius:50%;background:var(--blue);"></div>
|
||||
<div style="font-size:11px;color:var(--blue-dark);">Shared with household · 2 members online</div>
|
||||
</div>
|
||||
<div class="eye" style="margin-bottom:4px;">5 items remaining</div>
|
||||
<div style="padding:0 4px;">
|
||||
<div class="ck" onclick="this.classList.toggle('d')"><div class="ck-b">✓</div><div class="ck-c"><div class="ck-n">Cherry tomatoes</div><div class="ck-s">For: Tomato pasta</div></div><div class="ck-q">500g</div></div>
|
||||
<div class="ck" onclick="this.classList.toggle('d')"><div class="ck-b">✓</div><div class="ck-c"><div class="ck-n">Penne pasta</div><div class="ck-s">For: Tomato pasta</div></div><div class="ck-q">300g</div></div>
|
||||
<div class="ck" onclick="this.classList.toggle('d')"><div class="ck-b">✓</div><div class="ck-c"><div class="ck-n">Salmon fillet</div><div class="ck-s">For: Salmon teriyaki</div></div><div class="ck-q">400g</div></div>
|
||||
<div class="ck" onclick="this.classList.toggle('d')"><div class="ck-b">✓</div><div class="ck-c"><div class="ck-n">Jasmine rice</div><div class="ck-s">For: Teriyaki, Curry</div></div><div class="ck-q">500g</div></div>
|
||||
<div class="ck" onclick="this.classList.toggle('d')"><div class="ck-b">✓</div><div class="ck-c"><div class="ck-n">Soy sauce</div><div class="ck-s">For: Teriyaki, Stir-fry</div></div><div class="ck-q">4 tbsp</div></div>
|
||||
<div class="ck d" onclick="this.classList.toggle('d')"><div class="ck-b">✓</div><div class="ck-c"><div class="ck-n">Coconut milk</div><div class="ck-s">For: Thai curry</div></div><div class="ck-q">400ml</div></div>
|
||||
<div class="ck d" onclick="this.classList.toggle('d')"><div class="ck-b">✓</div><div class="ck-c"><div class="ck-n">Curry paste</div><div class="ck-s">For: Thai curry</div></div><div class="ck-q">2 tbsp</div></div>
|
||||
</div>
|
||||
<div style="margin-top:12px;text-align:center;font-size:11px;color:var(--blue-dark);font-weight:500;">+ Add custom item</div>
|
||||
</div>
|
||||
<div class="mbt"><div class="mt-i"><div class="mt-ic">📅</div><div class="mt-l">Planner</div></div><div class="mt-i"><div class="mt-ic">📖</div><div class="mt-l">Recipes</div></div><div class="mt-i a"><div class="mt-ic">🛒</div><div class="mt-l">Shopping</div></div><div class="mt-i"><div class="mt-ic">⚙️</div><div class="mt-l">Settings</div></div></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="prev-col">
|
||||
<div class="bp-lbl">Desktop · 1040px</div>
|
||||
<div class="desk">
|
||||
<div class="dsb">
|
||||
<div class="dsb-logo"><div class="dsb-lm"><div class="dsb-ic">🥗</div><div class="dsb-nm">Mealplan</div></div><div class="dsb-sub">Smith household</div></div>
|
||||
<div class="dsb-nav"><div><div class="dsb-nl">Plan</div><div class="dsb-ni"><span class="dsb-nc">📅</span>Planner</div><div class="dsb-ni"><span class="dsb-nc">📖</span>Recipes</div><div class="dsb-ni a"><span class="dsb-nc">🛒</span>Shopping</div></div></div>
|
||||
</div>
|
||||
<div class="dm">
|
||||
<div class="dtb"><div class="dtb-t">Shopping list</div><div class="dtb-r"><div style="background:var(--blue-tint);border:1px solid var(--blue-light);border-radius:var(--radius-md);padding:5px 12px;font-size:11px;color:var(--blue-dark);display:flex;align-items:center;gap:6px;"><div style="width:6px;height:6px;border-radius:50%;background:var(--blue);"></div>2 members online</div></div></div>
|
||||
<div style="flex:1;display:flex;overflow:hidden;">
|
||||
<!-- Left: checklist -->
|
||||
<div style="flex:1;padding:20px 24px;overflow-y:auto;">
|
||||
<div class="eye" style="margin-bottom:10px;">5 items remaining · 2 checked off</div>
|
||||
<div class="ck" onclick="this.classList.toggle('d')"><div class="ck-b">✓</div><div class="ck-c"><div class="ck-n">Cherry tomatoes</div><div class="ck-s">For: Tomato pasta</div></div><div class="ck-q">500g</div></div>
|
||||
<div class="ck" onclick="this.classList.toggle('d')"><div class="ck-b">✓</div><div class="ck-c"><div class="ck-n">Penne pasta</div><div class="ck-s">For: Tomato pasta</div></div><div class="ck-q">300g</div></div>
|
||||
<div class="ck" onclick="this.classList.toggle('d')"><div class="ck-b">✓</div><div class="ck-c"><div class="ck-n">Salmon fillet</div><div class="ck-s">For: Salmon teriyaki</div></div><div class="ck-q">400g</div></div>
|
||||
<div class="ck" onclick="this.classList.toggle('d')"><div class="ck-b">✓</div><div class="ck-c"><div class="ck-n">Jasmine rice</div><div class="ck-s">For: Salmon teriyaki, Thai curry</div></div><div class="ck-q">500g</div></div>
|
||||
<div class="ck" onclick="this.classList.toggle('d')"><div class="ck-b">✓</div><div class="ck-c"><div class="ck-n">Soy sauce</div><div class="ck-s">For: Salmon teriyaki, Stir-fry</div></div><div class="ck-q">4 tbsp</div></div>
|
||||
<div style="border-top:1px solid var(--color-border);margin-top:12px;padding-top:10px;">
|
||||
<div class="eye" style="margin-bottom:6px;opacity:.6;">Checked off</div>
|
||||
<div class="ck d" onclick="this.classList.toggle('d')"><div class="ck-b">✓</div><div class="ck-c"><div class="ck-n">Coconut milk</div><div class="ck-s">For: Thai curry</div></div><div class="ck-q">400ml</div></div>
|
||||
<div class="ck d" onclick="this.classList.toggle('d')"><div class="ck-b">✓</div><div class="ck-c"><div class="ck-n">Curry paste</div><div class="ck-s">For: Thai curry</div></div><div class="ck-q">2 tbsp</div></div>
|
||||
</div>
|
||||
<div style="margin-top:16px;"><span style="font-size:12px;color:var(--blue-dark);font-weight:500;cursor:pointer;">+ Add custom item</span></div>
|
||||
</div>
|
||||
<!-- Right: recipe reference -->
|
||||
<div style="width:280px;flex-shrink:0;border-left:1px solid var(--color-border);background:var(--color-surface);padding:20px;overflow-y:auto;">
|
||||
<div class="eye" style="margin-bottom:12px;">This week's recipes</div>
|
||||
<div style="margin-bottom:10px;padding:10px;background:var(--color-page);border:1px solid var(--color-border);border-radius:var(--radius-md);">
|
||||
<div style="font-size:13px;font-weight:500;margin-bottom:2px;">Tomato pasta</div>
|
||||
<div style="font-size:10px;color:var(--color-text-muted);">Tue · 2 ingredients on list</div>
|
||||
</div>
|
||||
<div style="margin-bottom:10px;padding:10px;background:var(--color-page);border:1px solid var(--color-border);border-radius:var(--radius-md);">
|
||||
<div style="font-size:13px;font-weight:500;margin-bottom:2px;">Salmon teriyaki</div>
|
||||
<div style="font-size:10px;color:var(--color-text-muted);">Wed · 3 ingredients on list</div>
|
||||
</div>
|
||||
<div style="margin-bottom:10px;padding:10px;background:var(--color-page);border:1px solid var(--color-border);border-radius:var(--radius-md);">
|
||||
<div style="font-size:13px;font-weight:500;margin-bottom:2px;">Thai green curry</div>
|
||||
<div style="font-size:10px;color:var(--color-text-muted);">Thu · 2 ingredients on list</div>
|
||||
</div>
|
||||
<div style="margin-bottom:10px;padding:10px;background:var(--color-page);border:1px solid var(--color-border);border-radius:var(--radius-md);">
|
||||
<div style="font-size:13px;font-weight:500;margin-bottom:2px;">Chicken stir-fry</div>
|
||||
<div style="font-size:10px;color:var(--color-text-muted);">Mon · shared soy sauce</div>
|
||||
</div>
|
||||
<div style="border-top:1px solid var(--color-border);margin-top:12px;padding-top:12px;">
|
||||
<div class="eye" style="margin-bottom:6px;">Filtered staples</div>
|
||||
<div style="font-size:11px;color:var(--color-text-muted);line-height:1.5;">Olive oil · Garlic · Salt · Pepper · Rice · Pasta</div>
|
||||
<div style="margin-top:8px;font-size:11px;color:var(--blue);font-weight:500;cursor:pointer;">Edit staples →</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="agent">
|
||||
<h4>D1 · Shopping list</h4>
|
||||
<pre>/* Desktop: 224px sidebar + topbar (title + shared status badge) + 2-col content:
|
||||
* Left (flex:1, page bg): remaining count + checklist rows + "checked off" section + add custom
|
||||
* Right (280px, surface bg): recipe reference cards + filtered staples list + edit staples link
|
||||
* Recipe reference panel: page-section with surface bg, not a floating card.
|
||||
* Mobile: full-width checklist + shared banner + bottom tabs.
|
||||
* Real-time sync: is_checked updates broadcast to all connected clients.
|
||||
* Both roles: planner + member can view and check off. Only planner can regenerate. */</pre>
|
||||
<table class="at"><thead><tr><th>Element</th><th>Value</th><th>Notes</th></tr></thead><tbody>
|
||||
<tr class="grp"><td colspan="3">Desktop</td></tr>
|
||||
<tr><td>Checklist area</td><td>flex:1, page bg, 20px 24px padding</td><td>Remaining items + divider + checked items</td></tr>
|
||||
<tr><td>Recipe reference</td><td>280px, surface bg, border-left</td><td>Recipe name + day + ingredient count. Filtered staples below.</td></tr>
|
||||
<tr class="grp"><td colspan="3">Shared state</td></tr>
|
||||
<tr><td>Banner (mobile)</td><td>blue-tint, blue dot, radius-lg</td><td>"Shared · N online"</td></tr>
|
||||
<tr><td>Badge (desktop)</td><td>blue-tint pill in topbar</td><td>Compact: dot + "N members online"</td></tr>
|
||||
</tbody></table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ═══════════════════════════════════════════════════════
|
||||
LLM INSTRUCTION SECTION
|
||||
════════════════════════════════════════════════════════ -->
|
||||
<!--
|
||||
spec:agent:start
|
||||
document: J5 Shopping List Journey
|
||||
version: 1.0
|
||||
-->
|
||||
<div class="llm">
|
||||
<h2>LLM instructions — J5 Shopping list journey</h2>
|
||||
<p>Authoritative implementation reference for the shopping list journey. Covers screen D1 and references A3/D3 (staples). Use this when building or modifying the shopping list feature.</p>
|
||||
|
||||
<h3>1. Journey flow</h3>
|
||||
<pre>/* J5 flow
|
||||
* C1 (week confirmed) → D1 (shopping list, always live).
|
||||
* Actor: Planner generates the list. All household members shop (view, check off, add items).
|
||||
* Preconditions: J1 (recipes exist) + J2 (week is planned) for generating a list.
|
||||
* J6 (household setup) for shared access.
|
||||
* There is NO draft/publish workflow — the list is always live. */</pre>
|
||||
|
||||
<h3>2. Screen D1 — Shopping list (live shared)</h3>
|
||||
<pre>/* Mobile layout:
|
||||
* topbar (title + settings icon)
|
||||
* + blue-tint shared banner ("Shared with household · N members online", blue dot)
|
||||
* + checklist (unchecked items, then checked items below divider)
|
||||
* + "+ Add custom item" link (blue-dark, centred)
|
||||
* + bottom tabs (Planner | Recipes | Shopping [active] | Settings)
|
||||
*
|
||||
* Desktop layout:
|
||||
* sidebar (224px, dsb) + topbar (dtb: title + blue-tint "N members online" badge)
|
||||
* + split content area:
|
||||
* Left: checklist (flex:1, page bg, 20px 24px padding)
|
||||
* - eyebrow "N items remaining · N checked off"
|
||||
* - unchecked rows
|
||||
* - border-top divider → "Checked off" eyebrow (opacity .6) → checked rows
|
||||
* - "+ Add custom item" link (12px, blue-dark, font-weight 500)
|
||||
* Right: recipe reference panel (280px, surface bg, border-left, 20px padding)
|
||||
* - eyebrow "This week's recipes"
|
||||
* - recipe cards: page bg, border, radius-md, 10px padding
|
||||
* - recipe name (13px/500) + day + ingredient count (10px muted)
|
||||
* - border-top divider → "Filtered staples" eyebrow
|
||||
* - staple names inline (11px muted, dot-separated)
|
||||
* - "Edit staples →" link (11px, blue, font-weight 500) — links to D3
|
||||
*
|
||||
* Checklist row (.ck):
|
||||
* checkbox (.ck-b, 22px, radius 4px, 2px border)
|
||||
* + content (.ck-c): name (.ck-n, 14px) + source (.ck-s, 10px muted, "For: [recipe names]")
|
||||
* + quantity (.ck-q, mono 12px muted, flex-shrink 0)
|
||||
*
|
||||
* Checked state (.ck.d):
|
||||
* checkbox fills green with white checkmark
|
||||
* name gets line-through + muted colour
|
||||
* row moves below divider into "Checked off" section */</pre>
|
||||
|
||||
<h3>3. Shopping list generation</h3>
|
||||
<ul>
|
||||
<li>Only the planner role can generate or regenerate the shopping list</li>
|
||||
<li>Ingredients from ALL planned meals for the week are collected</li>
|
||||
<li>Shared ingredients are merged and quantities summed (e.g. "Jasmine rice: 500g" from "Salmon teriyaki" + "Thai curry")</li>
|
||||
<li>Pantry staples (defined in A3/D3) are automatically filtered out</li>
|
||||
<li>The "For:" source line shows ALL recipes that use that ingredient</li>
|
||||
<li>Filtered staples are listed in the recipe reference panel (desktop) for transparency</li>
|
||||
</ul>
|
||||
|
||||
<h3>4. Real-time sync</h3>
|
||||
<pre>/* Real-time rules:
|
||||
* - is_checked updates broadcast to ALL connected clients instantly
|
||||
* - "N members online" indicator shows who is currently viewing the shopping list
|
||||
* - Prevents double-buying when multiple family members shop simultaneously or at different times
|
||||
* - Blue accent colour for all shared-state UI:
|
||||
* Mobile: blue-tint banner with blue dot
|
||||
* Desktop: blue-tint badge in topbar with blue dot
|
||||
* - WebSocket or SSE for real-time — implementation choice, but must be instant */</pre>
|
||||
|
||||
<h3>5. Custom items</h3>
|
||||
<ul>
|
||||
<li>Any household member (planner or member) can add items not on the generated list</li>
|
||||
<li>Use case: household supplies, snacks, items forgotten by the planner</li>
|
||||
<li>Custom items appear at the bottom of the unchecked list</li>
|
||||
<li>"+ Add custom item" link — blue-dark colour, centred (mobile) or left-aligned (desktop)</li>
|
||||
<li>Custom items can be checked off and removed just like generated items</li>
|
||||
</ul>
|
||||
|
||||
<h3>6. Data operations</h3>
|
||||
<pre>/* Generate list:
|
||||
* SELECT ri.ingredient_id, i.name, SUM(ri.quantity), ri.unit
|
||||
* FROM recipe_ingredient ri
|
||||
* JOIN ingredient i ON ri.ingredient_id = i.id
|
||||
* JOIN week_plan_slot wps ON wps.recipe_id = ri.recipe_id
|
||||
* WHERE wps.week = :current_week
|
||||
* AND i.is_staple = false
|
||||
* GROUP BY ri.ingredient_id, i.name, ri.unit
|
||||
*
|
||||
* Check off:
|
||||
* UPDATE shopping_list_item
|
||||
* SET is_checked = true/false
|
||||
* WHERE id = :item_id
|
||||
* → broadcast change to all connected clients via real-time channel
|
||||
*
|
||||
* Add custom:
|
||||
* INSERT INTO shopping_list_item (name, quantity, is_custom, is_checked, shopping_list_id)
|
||||
* VALUES (:name, :quantity, true, false, :list_id)
|
||||
* → broadcast new item to all connected clients */</pre>
|
||||
|
||||
<h3>7. Design constraints</h3>
|
||||
<ul>
|
||||
<li>List is ALWAYS live — no draft/publish workflow, no approval step</li>
|
||||
<li>Both planner and member can: view, check off, add custom items, remove items</li>
|
||||
<li>Only planner can: generate list, regenerate list</li>
|
||||
<li>"Edit staples" link navigates to D3 (same component as A3 — build once, reference from two entry points)</li>
|
||||
<li>CalDAV export is future scope (E3) — do not build in v1</li>
|
||||
<li>Recipe reference panel is a page section with surface bg — NOT a floating card</li>
|
||||
<li>Blue accent colour is reserved for shared/collaborative state indicators</li>
|
||||
<li>Checked items must visually separate from unchecked via a divider and "Checked off" label</li>
|
||||
</ul>
|
||||
|
||||
<h3>8. Preconditions</h3>
|
||||
<pre>/* Precondition chain:
|
||||
* J1 (recipes exist) — cannot generate a shopping list without recipes
|
||||
* J2 (week is planned) — cannot generate a shopping list without planned meals
|
||||
* J6 (household setup) — required for shared access (multiple members online)
|
||||
*
|
||||
* If no meals are planned: show empty state on D1 with prompt to plan the week first
|
||||
* If no household members: list works for solo planner, shared banner is hidden */</pre>
|
||||
</div>
|
||||
<!--
|
||||
spec:agent:end
|
||||
-->
|
||||
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
472
specs/frontend/j6-household-setup.html
Normal file
472
specs/frontend/j6-household-setup.html
Normal file
@@ -0,0 +1,472 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8"/>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
|
||||
<title>J6 — Household Setup · Screen Specs</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"/>
|
||||
<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:#F2C12E;--yellow-dark:#C49610;--yellow-text:#8A6800;--blue-tint:#E6F1FB;--blue-light:#A4CFF4;--blue:#2D7DD2;--blue-dark:#185FA5;--purple-tint:#EEEDFE;--purple:#534AB7;--purple-dark:#3C3489;--orange-tint:#FEF0E6;--orange:#E8862A;--orange-dark:#B46820;--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-xl:16px;--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,.08),0 2px 4px rgba(28,28,24,.04);--shadow-overlay:0 8px 32px rgba(28,28,24,.12),0 2px 8px rgba(28,28,24,.06);}
|
||||
*,*::before,*::after{box-sizing:border-box;margin:0;padding:0;}
|
||||
body{font-family:var(--font-sans);background:#E8E7E2;color:var(--color-text);font-size:14px;line-height:1.6;}
|
||||
.doc{max-width:1200px;margin:0 auto;padding:48px 40px 120px;}
|
||||
.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;background:var(--color-page);margin:-48px -40px 48px;padding:48px 40px 28px;border-radius:var(--radius-xl) var(--radius-xl) 0 0;}
|
||||
.doc-header h1{font-family:var(--font-display);font-size:28px;font-weight:500;letter-spacing:-.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;}
|
||||
.pill{display:inline-block;padding:2px 8px;border-radius:var(--radius-sm);font-size:10px;font-weight:500;letter-spacing:.05em;background:var(--green-tint);color:var(--green-dark);}
|
||||
.section{margin-bottom:64px;}
|
||||
.section-title{font-size:10px;font-weight:500;letter-spacing:.12em;text-transform:uppercase;color:var(--color-text-muted);padding-bottom:10px;border-bottom:1px solid var(--color-border);margin-bottom:24px;}
|
||||
.prose{font-size:13px;color:var(--color-text-muted);line-height:1.65;max-width:720px;margin-bottom:20px;}
|
||||
.jh{padding:20px 24px;border-radius:var(--radius-xl);margin-bottom:40px;display:flex;align-items:center;gap:16px;}
|
||||
.jh .jn{font-family:var(--font-display);font-size:48px;font-weight:300;line-height:1;opacity:.5;}
|
||||
.jh h2{font-family:var(--font-display);font-size:22px;font-weight:500;letter-spacing:-.02em;margin-bottom:4px;}
|
||||
.jh p{font-size:13px;line-height:1.5;}.jh .fl{font-family:var(--font-mono);font-size:11px;margin-top:6px;opacity:.7;}
|
||||
.jh-p{background:var(--purple-tint);border:1px solid #CECBF6;}.jh-p .jn{color:var(--purple);}.jh-p p,.jh-p .fl{color:var(--purple-dark);}
|
||||
.scr{margin-bottom:56px;}
|
||||
.scr-head{display:flex;justify-content:space-between;align-items:center;margin-bottom:6px;}
|
||||
.scr-head h3{font-family:var(--font-display);font-size:20px;font-weight:500;letter-spacing:-.02em;}
|
||||
.scr-id{font-family:var(--font-mono);font-size:11px;color:var(--color-text-muted);padding:2px 8px;border:1px solid var(--color-border);border-radius:var(--radius-sm);background:var(--color-page);}
|
||||
.scr-desc{font-size:12px;color:var(--color-text-muted);line-height:1.6;max-width:720px;margin-bottom:6px;}
|
||||
.scr-var{font-size:11px;color:var(--color-text-muted);margin-bottom:20px;}.scr-var strong{color:var(--color-text);}
|
||||
.previews{display:flex;gap:32px;flex-wrap:wrap;justify-content:center;align-items:flex-start;margin-bottom:20px;}
|
||||
.prev-col{display:flex;flex-direction:column;align-items:center;gap:10px;}
|
||||
.bp-lbl{font-family:var(--font-mono);font-size:10px;color:var(--color-text-muted);}
|
||||
.phone{width:320px;flex-shrink:0;background:var(--color-page);border-radius:36px;overflow:hidden;box-shadow:var(--shadow-overlay),0 0 0 1px rgba(0,0,0,.07);display:flex;flex-direction:column;border:6px solid #1C1C18;}
|
||||
.pst{padding:10px 20px 0;display:flex;justify-content:space-between;align-items:center;font-size:12px;background:var(--color-page);}.pst b{font-weight:600;}.pst span{font-size:10px;}
|
||||
.pb{flex:1;overflow-y:auto;display:flex;flex-direction:column;}
|
||||
.desk{width:100%;max-width:1040px;background:var(--color-page);border-radius:var(--radius-xl);overflow:hidden;box-shadow:var(--shadow-overlay),0 0 0 1px rgba(0,0,0,.06);display:flex;min-height:520px;}
|
||||
.fi{width:100%;font-family:var(--font-sans);font-size:14px;padding:10px 12px;border-radius:var(--radius-md);border:1px solid var(--color-border);background:var(--color-page);color:var(--color-text);outline:none;}
|
||||
.fl{font-size:12px;font-weight:500;color:var(--color-text);margin-bottom:6px;display:block;}
|
||||
.fg{margin-bottom:16px;}
|
||||
.bp{font-family:var(--font-sans);font-size:14px;font-weight:500;padding:12px 24px;border-radius:var(--radius-md);background:var(--green);color:#fff;border:none;cursor:pointer;width:100%;}
|
||||
.bg{font-family:var(--font-sans);font-size:13px;font-weight:500;padding:10px 20px;border-radius:var(--radius-md);background:var(--color-subtle);color:var(--color-text-muted);border:1px solid var(--color-border);cursor:pointer;}
|
||||
.tc{display:inline-flex;font-size:12px;font-weight:500;padding:6px 12px;border-radius:20px;border:1px solid var(--color-border);background:var(--color-surface);color:var(--color-text-muted);cursor:pointer;margin:0 4px 4px 0;user-select:none;}.tc.s{background:var(--green-tint);color:var(--green-dark);border-color:var(--green-light);}
|
||||
.eye{font-size:10px;font-weight:500;letter-spacing:.08em;text-transform:uppercase;color:var(--color-text-muted);}
|
||||
.agent{background:var(--color-text);color:#E8E8E2;padding:24px;border-radius:var(--radius-lg);margin-top:20px;}
|
||||
.agent h4{font-size:9px;font-weight:500;letter-spacing:.1em;text-transform:uppercase;color:#5A5A55;margin-bottom:12px;}
|
||||
.agent pre{font-family:var(--font-mono);font-size:10px;color:#444440;margin-bottom:16px;line-height:1.8;white-space:pre-wrap;}
|
||||
.at{width:100%;border-collapse:collapse;font-family:var(--font-mono);font-size:10px;}
|
||||
.at thead tr{border-bottom:1px solid #2A2A26;}.at th{text-align:left;padding:6px 10px;font-size:8px;font-weight:500;letter-spacing:.08em;text-transform:uppercase;color:#5A5A55;font-family:var(--font-sans);}.at td{padding:5px 10px;border-bottom:1px solid #1E1E1A;vertical-align:top;line-height:1.5;}.at tr:last-child td{border-bottom:none;}.at td:first-child{color:#7A7A72;}.at td:nth-child(2){color:#E8E8E2;font-weight:500;}.at td:nth-child(3){color:#5A5A55;}.at .grp td{padding-top:14px;font-family:var(--font-sans);font-size:8px;font-weight:500;letter-spacing:.08em;text-transform:uppercase;color:#3A3A36;}
|
||||
/* LLM section */
|
||||
.llm{background:var(--color-page);border:2px solid var(--green);border-radius:var(--radius-xl);padding:32px 40px;margin-top:64px;}
|
||||
.llm h2{font-family:var(--font-display);font-size:22px;font-weight:500;letter-spacing:-.02em;margin-bottom:8px;color:var(--green-dark);}
|
||||
.llm h3{font-size:14px;font-weight:600;margin:20px 0 8px;color:var(--color-text);}
|
||||
.llm p,.llm li{font-size:13px;color:var(--color-text-muted);line-height:1.65;}
|
||||
.llm ul,.llm ol{padding-left:20px;margin-bottom:12px;}
|
||||
.llm li{margin-bottom:4px;}
|
||||
.llm code{font-family:var(--font-mono);font-size:11px;background:var(--color-surface);padding:1px 5px;border-radius:3px;}
|
||||
.llm table{width:100%;border-collapse:collapse;margin:12px 0;font-size:12px;}
|
||||
.llm th,.llm td{text-align:left;padding:6px 10px;border-bottom:1px solid var(--color-border);}
|
||||
.llm th{font-weight:500;color:var(--color-text);font-size:11px;text-transform:uppercase;letter-spacing:.05em;}
|
||||
.llm td{color:var(--color-text-muted);}
|
||||
@media(max-width:900px){.doc{padding:24px 16px 80px;}}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="doc">
|
||||
|
||||
<div class="doc-header">
|
||||
<div>
|
||||
<h1>J6 — Household setup</h1>
|
||||
<p>One-time onboarding journey · Screens A1, A2, A3/D3, A4</p>
|
||||
</div>
|
||||
<div class="doc-meta">
|
||||
<span class="pill">v1.0</span><br>
|
||||
Screens: 4<br>
|
||||
Flow: A1 → A2 → A3 → A2 → [invite] → A4
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="jh jh-p">
|
||||
<div class="jn">J6</div>
|
||||
<div><h2>Household setup</h2><p>One-time onboarding. Account creation → household naming → staples → invite.</p><div class="fl">A1 → A2 → A3 → A2 → [invite] → A4</div></div>
|
||||
</div>
|
||||
|
||||
<!-- ═══ A1 SIGN UP ═══ -->
|
||||
<div class="scr" id="a1">
|
||||
<div class="scr-head"><h3>Sign up</h3><span class="scr-id">A1</span></div>
|
||||
<div class="scr-desc">First screen. Creates user_account. No navigation chrome (pre-auth). Desktop: full-viewport split — brand identity left, signup form right. Not a centered card — the brand section fills the entire left half.</div>
|
||||
<div class="scr-var"><strong>V2 · Split layout</strong> — brand left fills viewport height, form right</div>
|
||||
|
||||
<div class="previews">
|
||||
<div class="prev-col">
|
||||
<div class="bp-lbl">Mobile · 320px</div>
|
||||
<div class="phone">
|
||||
<div class="pst"><b>9:41</b><span>●●● WiFi 🔋</span></div>
|
||||
<div class="pb">
|
||||
<div style="background:var(--green-dark);padding:28px 24px;text-align:center;">
|
||||
<div style="font-size:28px;margin-bottom:8px;">🥗</div>
|
||||
<div style="font-family:var(--font-display);font-size:22px;font-weight:500;color:#fff;">Mealplan</div>
|
||||
<div style="font-size:12px;color:var(--green-light);margin-top:4px;">Plan meals, eat well, waste less</div>
|
||||
</div>
|
||||
<div style="padding:24px 20px;">
|
||||
<div style="font-family:var(--font-display);font-size:18px;font-weight:500;margin-bottom:4px;">Create your account</div>
|
||||
<div style="font-size:12px;color:var(--color-text-muted);margin-bottom:20px;">You'll set up your household next.</div>
|
||||
<div class="fg"><label class="fl">Your name</label><input class="fi" placeholder="e.g. Sarah"/></div>
|
||||
<div class="fg"><label class="fl">Email</label><input class="fi" placeholder="you@example.com"/></div>
|
||||
<div class="fg"><label class="fl">Password</label><input class="fi" type="password" placeholder="At least 8 characters"/></div>
|
||||
<button class="bp">Create account →</button>
|
||||
<div style="text-align:center;margin-top:16px;font-size:12px;color:var(--color-text-muted);">Already have an account? <span style="color:var(--green);font-weight:500;">Log in</span></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="prev-col">
|
||||
<div class="bp-lbl">Desktop · 1040px</div>
|
||||
<div class="desk" style="min-height:500px;">
|
||||
<div style="width:440px;flex-shrink:0;background:var(--green-dark);display:flex;flex-direction:column;align-items:center;justify-content:center;padding:48px 40px;">
|
||||
<div style="font-size:64px;margin-bottom:20px;">🥗</div>
|
||||
<div style="font-family:var(--font-display);font-size:36px;font-weight:500;color:#fff;letter-spacing:-.02em;margin-bottom:10px;">Mealplan</div>
|
||||
<div style="font-size:15px;color:var(--green-light);text-align:center;line-height:1.6;max-width:280px;">Plan your week's dinners. Get variety-aware suggestions. Shop together as a household.</div>
|
||||
<div style="margin-top:32px;display:flex;gap:12px;">
|
||||
<div style="background:rgba(255,255,255,.1);border-radius:8px;padding:12px 16px;text-align:center;"><div style="font-size:18px;margin-bottom:4px;">📅</div><div style="font-size:10px;color:var(--green-light);">Plan</div></div>
|
||||
<div style="background:rgba(255,255,255,.1);border-radius:8px;padding:12px 16px;text-align:center;"><div style="font-size:18px;margin-bottom:4px;">🍳</div><div style="font-size:10px;color:var(--green-light);">Cook</div></div>
|
||||
<div style="background:rgba(255,255,255,.1);border-radius:8px;padding:12px 16px;text-align:center;"><div style="font-size:18px;margin-bottom:4px;">🛒</div><div style="font-size:10px;color:var(--green-light);">Shop</div></div>
|
||||
</div>
|
||||
</div>
|
||||
<div style="flex:1;display:flex;flex-direction:column;justify-content:center;padding:48px 56px;">
|
||||
<div style="max-width:380px;">
|
||||
<div style="font-family:var(--font-display);font-size:28px;font-weight:500;letter-spacing:-.02em;margin-bottom:6px;">Create your account</div>
|
||||
<div style="font-size:14px;color:var(--color-text-muted);margin-bottom:32px;">You'll set up your household next.</div>
|
||||
<div class="fg"><label class="fl">Your name</label><input class="fi" placeholder="e.g. Sarah"/></div>
|
||||
<div class="fg"><label class="fl">Email</label><input class="fi" placeholder="you@example.com"/></div>
|
||||
<div class="fg"><label class="fl">Password</label><input class="fi" type="password" placeholder="At least 8 characters"/></div>
|
||||
<button class="bp" style="margin-top:8px;">Create account →</button>
|
||||
<div style="text-align:center;margin-top:20px;font-size:13px;color:var(--color-text-muted);">Already have an account? <span style="color:var(--green);font-weight:500;cursor:pointer;">Log in</span></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="agent">
|
||||
<h4>A1 · Sign up</h4>
|
||||
<pre>/* Pre-auth. No nav chrome. Desktop: full-viewport 2-col split. Brand ~42% / Form ~58%.
|
||||
* Mobile: brand as ~120px banner, form below.
|
||||
* Brand section: green-dark bg. Contains logo, tagline, 3 feature icons.
|
||||
* Form section: page bg. Form max-width 380px. Not a card — just content on the page.
|
||||
* Writes: user_account INSERT → redirect A2 */</pre>
|
||||
<table class="at"><thead><tr><th>Element</th><th>Value</th><th>Notes</th></tr></thead><tbody>
|
||||
<tr class="grp"><td colspan="3">Desktop layout</td></tr>
|
||||
<tr><td>Brand panel</td><td>440px fixed, green-dark bg, content centered vertically</td><td>Logo 64px + name Fraunces 36px + tagline 15px + 3 feature icons</td></tr>
|
||||
<tr><td>Form panel</td><td>flex:1, page bg, content centered vertically, 48px 56px padding</td><td>Form max-width 380px. Not wrapped in a card.</td></tr>
|
||||
<tr class="grp"><td colspan="3">Mobile layout</td></tr>
|
||||
<tr><td>Brand banner</td><td>~120px, green-dark bg, centered</td><td>Logo 28px + name 22px + tagline 12px</td></tr>
|
||||
<tr><td>Form</td><td>Full width, 24px 20px padding</td><td>Same fields, smaller type sizes</td></tr>
|
||||
<tr class="grp"><td colspan="3">Data</td></tr>
|
||||
<tr><td>Writes</td><td>user_account INSERT</td><td>system_role='user', is_active=true → A2</td></tr>
|
||||
</tbody></table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ═══ A2 HOUSEHOLD SETUP ═══ -->
|
||||
<div class="scr" id="a2">
|
||||
<div class="scr-head"><h3>Household setup</h3><span class="scr-id">A2</span></div>
|
||||
<div class="scr-desc">Name household + optional member invite. Desktop: full-page layout with a left illustration/progress column and the form on the right. Not a floating card — a structured page with clear sections.</div>
|
||||
<div class="scr-var"><strong>V1 · Simple form</strong> — desktop: progress sidebar left, form right</div>
|
||||
|
||||
<div class="previews">
|
||||
<div class="prev-col">
|
||||
<div class="bp-lbl">Mobile · 320px</div>
|
||||
<div class="phone">
|
||||
<div class="pst"><b>9:42</b><span>●●● WiFi 🔋</span></div>
|
||||
<div class="pb">
|
||||
<div style="padding:24px 20px;">
|
||||
<div class="eye" style="margin-bottom:4px;">Step 1 of 3</div>
|
||||
<div style="font-family:var(--font-display);font-size:20px;font-weight:500;margin-bottom:6px;">Name your household</div>
|
||||
<div style="font-size:12px;color:var(--color-text-muted);margin-bottom:20px;">This appears in the sidebar and is shared with all members.</div>
|
||||
<div class="fg"><label class="fl">Household name</label><input class="fi" value="Smith family"/></div>
|
||||
<div style="border-top:1px solid var(--color-border);padding-top:20px;margin-top:8px;">
|
||||
<div class="eye" style="margin-bottom:8px;">Invite members (optional)</div>
|
||||
<div style="font-size:12px;color:var(--color-text-muted);margin-bottom:12px;">You can skip this and invite people later from settings.</div>
|
||||
<button class="bg" style="width:100%;font-size:12px;">+ Generate invite link</button>
|
||||
</div>
|
||||
<div style="margin-top:24px;"><button class="bp">Continue → Set up staples</button></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="prev-col">
|
||||
<div class="bp-lbl">Desktop · 1040px</div>
|
||||
<div class="desk" style="min-height:480px;">
|
||||
<!-- Left: progress / branding strip -->
|
||||
<div style="width:300px;flex-shrink:0;background:var(--color-surface);border-right:1px solid var(--color-border);padding:40px 28px;display:flex;flex-direction:column;">
|
||||
<div style="display:flex;align-items:center;gap:8px;margin-bottom:40px;">
|
||||
<div style="width:28px;height:28px;border-radius:6px;background:var(--green);display:flex;align-items:center;justify-content:center;font-size:14px;">🥗</div>
|
||||
<div style="font-family:var(--font-display);font-size:16px;font-weight:500;">Mealplan</div>
|
||||
</div>
|
||||
<!-- Progress steps -->
|
||||
<div style="display:flex;flex-direction:column;gap:24px;flex:1;">
|
||||
<div style="display:flex;gap:12px;align-items:flex-start;">
|
||||
<div style="width:28px;height:28px;border-radius:50%;background:var(--green);color:#fff;display:flex;align-items:center;justify-content:center;font-size:12px;font-weight:500;flex-shrink:0;">1</div>
|
||||
<div><div style="font-size:13px;font-weight:500;color:var(--color-text);">Name your household</div><div style="font-size:11px;color:var(--color-text-muted);margin-top:2px;">Give your family a name</div></div>
|
||||
</div>
|
||||
<div style="display:flex;gap:12px;align-items:flex-start;">
|
||||
<div style="width:28px;height:28px;border-radius:50%;background:var(--color-subtle);color:var(--color-text-muted);display:flex;align-items:center;justify-content:center;font-size:12px;font-weight:500;flex-shrink:0;">2</div>
|
||||
<div><div style="font-size:13px;color:var(--color-text-muted);">Set up pantry staples</div><div style="font-size:11px;color:var(--color-text-muted);margin-top:2px;">What you always have at home</div></div>
|
||||
</div>
|
||||
<div style="display:flex;gap:12px;align-items:flex-start;">
|
||||
<div style="width:28px;height:28px;border-radius:50%;background:var(--color-subtle);color:var(--color-text-muted);display:flex;align-items:center;justify-content:center;font-size:12px;font-weight:500;flex-shrink:0;">3</div>
|
||||
<div><div style="font-size:13px;color:var(--color-text-muted);">Invite members</div><div style="font-size:11px;color:var(--color-text-muted);margin-top:2px;">Share with your household</div></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Right: form content -->
|
||||
<div style="flex:1;padding:48px 56px;display:flex;flex-direction:column;justify-content:center;">
|
||||
<div style="max-width:420px;">
|
||||
<div style="font-family:var(--font-display);font-size:28px;font-weight:500;letter-spacing:-.02em;margin-bottom:6px;">Name your household</div>
|
||||
<div style="font-size:14px;color:var(--color-text-muted);margin-bottom:28px;">This name appears in the sidebar and is shared with all members.</div>
|
||||
<div class="fg"><label class="fl">Household name</label><input class="fi" value="Smith family" style="font-size:16px;padding:12px 14px;"/></div>
|
||||
<div style="border-top:1px solid var(--color-border);padding-top:24px;margin-top:8px;">
|
||||
<div class="eye" style="margin-bottom:10px;">Invite members (optional)</div>
|
||||
<div style="font-size:13px;color:var(--color-text-muted);margin-bottom:14px;">You can skip this and invite people later from settings.</div>
|
||||
<button class="bg" style="width:100%;font-size:13px;padding:12px;">+ Generate invite link</button>
|
||||
</div>
|
||||
<div style="margin-top:32px;"><button class="bp" style="font-size:15px;padding:14px;">Continue → Set up staples</button></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="agent">
|
||||
<h4>A2 · Household setup</h4>
|
||||
<pre>/* Desktop: progress sidebar (300px, surface bg) + form area (flex:1, page bg).
|
||||
* Progress sidebar shows: app logo + 3 numbered steps (current = green circle, future = subtle).
|
||||
* Form area: content centered vertically, max-width 420px. Not a card — content on page bg.
|
||||
* Mobile: full-width form with step indicator text at top. No progress sidebar.
|
||||
* Writes: household INSERT + household_member INSERT (role=planner) → A3 */</pre>
|
||||
<table class="at"><thead><tr><th>Element</th><th>Value</th><th>Notes</th></tr></thead><tbody>
|
||||
<tr class="grp"><td colspan="3">Desktop layout</td></tr>
|
||||
<tr><td>Progress sidebar</td><td>300px, surface bg, border-right</td><td>App logo top + numbered steps. Current step: green circle.</td></tr>
|
||||
<tr><td>Form area</td><td>flex:1, page bg, centered content</td><td>max-width 420px. Larger input (16px, 12px padding).</td></tr>
|
||||
<tr><td>Step circles</td><td>28px diameter. Active: green bg #fff text. Future: subtle bg muted text.</td><td>Labels: 13px name + 11px description below</td></tr>
|
||||
<tr class="grp"><td colspan="3">Mobile</td></tr>
|
||||
<tr><td>Step indicator</td><td>"Step 1 of 3" eyebrow text</td><td>No sidebar, text-only progress</td></tr>
|
||||
<tr><td>Form</td><td>Full width, 24px 20px padding</td><td>Same fields</td></tr>
|
||||
</tbody></table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<!-- ═══ A3 PANTRY STAPLES ═══ -->
|
||||
<div class="scr" id="a3">
|
||||
<div class="scr-head"><h3>Pantry staples</h3><span class="scr-id">A3 / D3</span></div>
|
||||
<div class="scr-desc">Toggle staple ingredients by category. Desktop: same progress sidebar as A2 (step 2 active) + content area with a multi-column chip grid. The chips flow naturally across the full content width — no card wrapper needed.</div>
|
||||
<div class="scr-var"><strong>V2 · Categorized list</strong> — desktop: progress sidebar + 2-col category grid</div>
|
||||
|
||||
<div class="previews">
|
||||
<div class="prev-col">
|
||||
<div class="bp-lbl">Mobile · 320px</div>
|
||||
<div class="phone">
|
||||
<div class="pst"><b>9:43</b><span>●●● WiFi 🔋</span></div>
|
||||
<div class="pb">
|
||||
<div style="padding:20px;">
|
||||
<div class="eye" style="margin-bottom:4px;">Step 2 of 3</div>
|
||||
<div style="font-family:var(--font-display);font-size:20px;font-weight:500;margin-bottom:6px;">Your pantry staples</div>
|
||||
<div style="font-size:12px;color:var(--color-text-muted);margin-bottom:16px;">These won't appear on your shopping list. Tap to toggle.</div>
|
||||
<div class="eye" style="margin:12px 0 6px;">Oils & fats</div>
|
||||
<div><span class="tc s" onclick="this.classList.toggle('s')">Olive oil</span><span class="tc s" onclick="this.classList.toggle('s')">Butter</span><span class="tc" onclick="this.classList.toggle('s')">Coconut oil</span></div>
|
||||
<div class="eye" style="margin:16px 0 6px;">Spices</div>
|
||||
<div><span class="tc s" onclick="this.classList.toggle('s')">Salt</span><span class="tc s" onclick="this.classList.toggle('s')">Pepper</span><span class="tc s" onclick="this.classList.toggle('s')">Garlic</span><span class="tc" onclick="this.classList.toggle('s')">Cumin</span><span class="tc" onclick="this.classList.toggle('s')">Paprika</span></div>
|
||||
<div class="eye" style="margin:16px 0 6px;">Grains & pasta</div>
|
||||
<div><span class="tc s" onclick="this.classList.toggle('s')">Rice</span><span class="tc s" onclick="this.classList.toggle('s')">Pasta</span><span class="tc" onclick="this.classList.toggle('s')">Flour</span></div>
|
||||
<div class="eye" style="margin:16px 0 6px;">Sauces</div>
|
||||
<div><span class="tc s" onclick="this.classList.toggle('s')">Soy sauce</span><span class="tc" onclick="this.classList.toggle('s')">Fish sauce</span><span class="tc s" onclick="this.classList.toggle('s')">Vinegar</span></div>
|
||||
<div style="margin-top:20px;"><button class="bp">Continue → Invite members</button></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="prev-col">
|
||||
<div class="bp-lbl">Desktop · 1040px</div>
|
||||
<div class="desk" style="min-height:500px;">
|
||||
<!-- Progress sidebar (same as A2 but step 2 active) -->
|
||||
<div style="width:300px;flex-shrink:0;background:var(--color-surface);border-right:1px solid var(--color-border);padding:40px 28px;display:flex;flex-direction:column;">
|
||||
<div style="display:flex;align-items:center;gap:8px;margin-bottom:40px;"><div style="width:28px;height:28px;border-radius:6px;background:var(--green);display:flex;align-items:center;justify-content:center;font-size:14px;">🥗</div><div style="font-family:var(--font-display);font-size:16px;font-weight:500;">Mealplan</div></div>
|
||||
<div style="display:flex;flex-direction:column;gap:24px;">
|
||||
<div style="display:flex;gap:12px;align-items:flex-start;"><div style="width:28px;height:28px;border-radius:50%;background:var(--green-tint);color:var(--green-dark);display:flex;align-items:center;justify-content:center;font-size:12px;font-weight:500;flex-shrink:0;">✓</div><div><div style="font-size:13px;color:var(--green-dark);">Name your household</div><div style="font-size:11px;color:var(--green-dark);margin-top:2px;">Smith family</div></div></div>
|
||||
<div style="display:flex;gap:12px;align-items:flex-start;"><div style="width:28px;height:28px;border-radius:50%;background:var(--green);color:#fff;display:flex;align-items:center;justify-content:center;font-size:12px;font-weight:500;flex-shrink:0;">2</div><div><div style="font-size:13px;font-weight:500;color:var(--color-text);">Set up pantry staples</div><div style="font-size:11px;color:var(--color-text-muted);margin-top:2px;">What you always have at home</div></div></div>
|
||||
<div style="display:flex;gap:12px;align-items:flex-start;"><div style="width:28px;height:28px;border-radius:50%;background:var(--color-subtle);color:var(--color-text-muted);display:flex;align-items:center;justify-content:center;font-size:12px;font-weight:500;flex-shrink:0;">3</div><div><div style="font-size:13px;color:var(--color-text-muted);">Invite members</div></div></div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Content: chips in natural 2-column flow -->
|
||||
<div style="flex:1;padding:48px 56px;overflow-y:auto;">
|
||||
<div style="font-family:var(--font-display);font-size:28px;font-weight:500;letter-spacing:-.02em;margin-bottom:6px;">Your pantry staples</div>
|
||||
<div style="font-size:14px;color:var(--color-text-muted);margin-bottom:28px;max-width:500px;">These items won't appear on your shopping list. Click to toggle. You can always change these later in settings.</div>
|
||||
<div style="display:grid;grid-template-columns:1fr 1fr;gap:24px 32px;margin-bottom:32px;">
|
||||
<div><div class="eye" style="margin-bottom:8px;">Oils & fats</div><div><span class="tc s">Olive oil</span><span class="tc s">Butter</span><span class="tc">Coconut oil</span><span class="tc s">Vegetable oil</span></div></div>
|
||||
<div><div class="eye" style="margin-bottom:8px;">Spices & seasonings</div><div><span class="tc s">Salt</span><span class="tc s">Pepper</span><span class="tc s">Garlic</span><span class="tc">Cumin</span><span class="tc">Paprika</span><span class="tc s">Chili flakes</span><span class="tc">Oregano</span><span class="tc">Cinnamon</span></div></div>
|
||||
<div><div class="eye" style="margin-bottom:8px;">Grains & pasta</div><div><span class="tc s">Rice</span><span class="tc s">Pasta</span><span class="tc">Flour</span><span class="tc">Breadcrumbs</span><span class="tc">Couscous</span></div></div>
|
||||
<div><div class="eye" style="margin-bottom:8px;">Sauces & condiments</div><div><span class="tc s">Soy sauce</span><span class="tc">Fish sauce</span><span class="tc s">Vinegar</span><span class="tc">Mustard</span><span class="tc">Ketchup</span></div></div>
|
||||
<div><div class="eye" style="margin-bottom:8px;">Baking</div><div><span class="tc s">Sugar</span><span class="tc">Baking powder</span><span class="tc">Vanilla extract</span></div></div>
|
||||
<div><div class="eye" style="margin-bottom:8px;">Dairy & basics</div><div><span class="tc s">Eggs</span><span class="tc s">Milk</span><span class="tc">Cream</span><span class="tc">Cheese</span></div></div>
|
||||
</div>
|
||||
<button class="bp" style="max-width:320px;font-size:15px;padding:14px;">Continue → Invite members</button>
|
||||
<div style="margin-top:10px;font-size:12px;color:var(--color-text-muted);cursor:pointer;">Skip for now</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="agent">
|
||||
<h4>A3/D3 · Pantry staples</h4>
|
||||
<pre>/* Desktop onboarding: progress sidebar (300px, step 2 active) + content area with 2-col category grid.
|
||||
* Categories flow in a CSS grid (2 columns, 24px row gap, 32px col gap).
|
||||
* Chips sit directly on the page bg — no card wrappers.
|
||||
* Desktop settings (D3): same chip grid but inside sidebar+topbar layout (no progress sidebar).
|
||||
* Mobile: single-column chip list, full width.
|
||||
* Chip toggle: UPDATE ingredient SET is_staple. Debounced 300ms. */</pre>
|
||||
<table class="at"><thead><tr><th>Element</th><th>Value</th><th>Notes</th></tr></thead><tbody>
|
||||
<tr class="grp"><td colspan="3">Desktop onboarding</td></tr>
|
||||
<tr><td>Progress sidebar</td><td>300px, surface bg. Step 1 = ✓ completed. Step 2 = green active.</td><td>Same sidebar as A2, reused component.</td></tr>
|
||||
<tr><td>Category grid</td><td>grid-template-columns: 1fr 1fr. gap: 24px 32px.</td><td>Categories from ingredient_category, sorted by sort_order.</td></tr>
|
||||
<tr><td>Chips</td><td>12px/500, 6px 12px pad, 20px radius. Selected: green-tint/green-light/green-dark.</td><td>Direct on page bg. No card wrapper.</td></tr>
|
||||
<tr class="grp"><td colspan="3">Desktop settings (D3)</td></tr>
|
||||
<tr><td>Layout</td><td>App sidebar (224px) + topbar ("Staples") + content with 3-col grid</td><td>Wider content area → 3 columns for categories.</td></tr>
|
||||
</tbody></table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<!-- ═══ A4 JOIN HOUSEHOLD ═══ -->
|
||||
<div class="scr" id="a4">
|
||||
<div class="scr-head"><h3>Join household</h3><span class="scr-id">A4</span></div>
|
||||
<div class="scr-desc">Member accepts invite. Desktop: split layout like A1 — left side shows the household identity (name, who invited), right side has the signup form. The left panel uses green-tint (not green-dark) since this is a welcoming join screen, not a brand-first screen.</div>
|
||||
<div class="scr-var"><strong>V2 · Welcome card</strong> — desktop: household identity left, join form right</div>
|
||||
|
||||
<div class="previews">
|
||||
<div class="prev-col">
|
||||
<div class="bp-lbl">Mobile · 320px</div>
|
||||
<div class="phone">
|
||||
<div class="pst"><b>10:15</b><span>●●● WiFi 🔋</span></div>
|
||||
<div class="pb">
|
||||
<div style="background:var(--green-tint);padding:28px 24px;text-align:center;border-bottom:1px solid var(--green-light);">
|
||||
<div style="width:48px;height:48px;border-radius:12px;background:var(--green);display:flex;align-items:center;justify-content:center;font-size:22px;margin:0 auto 12px;">🥗</div>
|
||||
<div style="font-family:var(--font-display);font-size:22px;font-weight:500;color:var(--green-deeper);margin-bottom:4px;">Smith family</div>
|
||||
<div style="font-size:12px;color:var(--green-dark);">Sarah invited you to join their meal planner</div>
|
||||
</div>
|
||||
<div style="padding:24px 20px;">
|
||||
<div style="background:var(--green-tint);border:1px solid var(--green-light);border-radius:var(--radius-lg);padding:10px 12px;margin-bottom:20px;font-size:11px;color:var(--green-dark);line-height:1.5;">You'll be able to view the weekly meal plan and check off items on the shared shopping list.</div>
|
||||
<div class="fg"><label class="fl">Your name</label><input class="fi" placeholder="e.g. Tom"/></div>
|
||||
<div class="fg"><label class="fl">Email</label><input class="fi" placeholder="you@example.com"/></div>
|
||||
<div class="fg"><label class="fl">Password</label><input class="fi" type="password" placeholder="At least 8 characters"/></div>
|
||||
<button class="bp">Join household</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="prev-col">
|
||||
<div class="bp-lbl">Desktop · 1040px</div>
|
||||
<div class="desk" style="min-height:480px;">
|
||||
<!-- Left: household identity -->
|
||||
<div style="width:400px;flex-shrink:0;background:var(--green-tint);border-right:1px solid var(--green-light);display:flex;flex-direction:column;align-items:center;justify-content:center;padding:48px 40px;text-align:center;">
|
||||
<div style="width:64px;height:64px;border-radius:16px;background:var(--green);display:flex;align-items:center;justify-content:center;font-size:32px;margin-bottom:20px;">🥗</div>
|
||||
<div style="font-family:var(--font-display);font-size:32px;font-weight:500;color:var(--green-deeper);letter-spacing:-.02em;margin-bottom:8px;">Smith family</div>
|
||||
<div style="font-size:14px;color:var(--green-dark);margin-bottom:24px;">Sarah invited you to join</div>
|
||||
<div style="background:rgba(255,255,255,.5);border-radius:var(--radius-lg);padding:16px 20px;max-width:280px;">
|
||||
<div style="font-size:12px;font-weight:500;color:var(--green-dark);margin-bottom:8px;">What you'll be able to do:</div>
|
||||
<div style="font-size:12px;color:var(--green-dark);line-height:1.6;text-align:left;">
|
||||
<div style="padding:3px 0;">📅 View the weekly meal plan</div>
|
||||
<div style="padding:3px 0;">🛒 Check off shopping list items</div>
|
||||
<div style="padding:3px 0;">➕ Add items to the list</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Right: form -->
|
||||
<div style="flex:1;display:flex;flex-direction:column;justify-content:center;padding:48px 56px;">
|
||||
<div style="max-width:380px;">
|
||||
<div style="font-family:var(--font-display);font-size:28px;font-weight:500;letter-spacing:-.02em;margin-bottom:6px;">Join the household</div>
|
||||
<div style="font-size:14px;color:var(--color-text-muted);margin-bottom:28px;">Create your account to get started.</div>
|
||||
<div class="fg"><label class="fl">Your name</label><input class="fi" placeholder="e.g. Tom"/></div>
|
||||
<div class="fg"><label class="fl">Email</label><input class="fi" placeholder="you@example.com"/></div>
|
||||
<div class="fg"><label class="fl">Password</label><input class="fi" type="password" placeholder="At least 8 characters"/></div>
|
||||
<button class="bp">Join household</button>
|
||||
<div style="margin-top:16px;font-size:12px;color:var(--color-text-muted);">Already have an account? <span style="color:var(--green);font-weight:500;">Log in instead</span></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="agent">
|
||||
<h4>A4 · Join household</h4>
|
||||
<pre>/* Desktop: split layout. Left (400px, green-tint bg): household identity + permissions list.
|
||||
* Right (flex:1, page bg): signup form max-width 380px.
|
||||
* Left panel uses green-tint (welcoming) not green-dark (brand-first).
|
||||
* Mobile: green-tint banner at top + form below.
|
||||
* Transaction: user_account INSERT + household_member INSERT (role=member) + household_invite UPDATE → C1 */</pre>
|
||||
<table class="at"><thead><tr><th>Element</th><th>Value</th><th>Notes</th></tr></thead><tbody>
|
||||
<tr class="grp"><td colspan="3">Desktop</td></tr>
|
||||
<tr><td>Identity panel</td><td>400px, green-tint bg, centered content</td><td>Logo 64px + name Fraunces 32px + inviter + permissions list</td></tr>
|
||||
<tr><td>Form panel</td><td>flex:1, page bg, max-width 380px</td><td>Same signup fields as A1</td></tr>
|
||||
</tbody></table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ═══════════════════════════════════════════════════════════════
|
||||
LLM IMPLEMENTATION GUIDE
|
||||
═══════════════════════════════════════════════════════════════ -->
|
||||
<div class="llm">
|
||||
<h2>Implementation Guide — J6 Household Setup</h2>
|
||||
<p>This journey is completed once when the app is first used. The planner creates an account, names the household, defines pantry staples, and invites household members.</p>
|
||||
|
||||
<h3>Journey Flow</h3>
|
||||
<ol>
|
||||
<li><strong>A1 — Sign up:</strong> New user creates account with name, email, password. Writes <code>user_account</code> → redirects to A2.</li>
|
||||
<li><strong>A2 — Household setup:</strong> Planner names household (e.g. "Smith family"), optionally generates invite link. Writes <code>household</code> + <code>household_member</code> (role=planner) → redirects to A3.</li>
|
||||
<li><strong>A3 — Pantry staples:</strong> Toggle staple ingredients by category. Debounced 300ms save. Updates <code>ingredient.is_staple</code>. Same component as D3 in settings.</li>
|
||||
<li><strong>A4 — Join household:</strong> Invited member opens link, sees household identity, creates account. Writes <code>user_account</code> + <code>household_member</code> (role=member) + updates <code>household_invite</code> → redirects to C1.</li>
|
||||
</ol>
|
||||
|
||||
<h3>Roles</h3>
|
||||
<table>
|
||||
<tr><th>Role</th><th>Created at</th><th>Access</th></tr>
|
||||
<tr><td><code>planner</code></td><td>A2 (automatic)</td><td>Full access to all 15 screens</td></tr>
|
||||
<tr><td><code>member</code></td><td>A4 (on invite accept)</td><td>C1 read-only + D1 view/check/add</td></tr>
|
||||
</table>
|
||||
|
||||
<h3>Layout Rules</h3>
|
||||
<ul>
|
||||
<li><strong>All screens are pre-auth</strong> — no sidebar, no tab bar, no navigation chrome.</li>
|
||||
<li><strong>A1, A4 desktop:</strong> Full-viewport 2-column split. Brand/identity left (fixed width), form right (flex:1).</li>
|
||||
<li><strong>A2, A3 desktop:</strong> Progress sidebar (300px) left + form/content right (flex:1).</li>
|
||||
<li><strong>Mobile:</strong> All screens are stacked single-column. Brand/identity as a banner at top, form below.</li>
|
||||
</ul>
|
||||
|
||||
<h3>Design Constraints</h3>
|
||||
<ul>
|
||||
<li>A1 brand panel: <code>--green-dark</code> bg (brand-first). A4 identity panel: <code>--green-tint</code> bg (welcoming).</li>
|
||||
<li>A3 = D3 — single component, two contexts. Onboarding (progress sidebar + 2-col grid) vs settings (app sidebar + 3-col grid).</li>
|
||||
<li>Chip toggle: selected = <code>--green-tint</code> bg + <code>--green-light</code> border + <code>--green-dark</code> text.</li>
|
||||
<li>Progress steps: current = green circle, completed = green-tint + checkmark, future = subtle circle.</li>
|
||||
<li>Invite mechanism: link or short code. No in-app email system. Planner shares via messaging app.</li>
|
||||
<li>Form max-width: 380px (A1, A4) or 420px (A2). Never wider.</li>
|
||||
</ul>
|
||||
|
||||
<h3>Data Operations</h3>
|
||||
<table>
|
||||
<tr><th>Screen</th><th>Writes</th><th>Reads</th></tr>
|
||||
<tr><td>A1</td><td><code>user_account INSERT</code></td><td>—</td></tr>
|
||||
<tr><td>A2</td><td><code>household INSERT</code>, <code>household_member INSERT (role=planner)</code>, <code>household_invite INSERT</code> (optional)</td><td>—</td></tr>
|
||||
<tr><td>A3/D3</td><td><code>ingredient UPDATE (is_staple)</code> — debounced 300ms</td><td><code>ingredient SELECT</code> grouped by <code>ingredient_category</code></td></tr>
|
||||
<tr><td>A4</td><td><code>user_account INSERT</code>, <code>household_member INSERT (role=member)</code>, <code>household_invite UPDATE</code></td><td><code>household_invite SELECT</code> (validates code), <code>household SELECT</code> (name, inviter)</td></tr>
|
||||
</table>
|
||||
|
||||
<h3>Accessibility</h3>
|
||||
<ul>
|
||||
<li>All forms: semantic <code><form></code>, <code><label></code> with <code>for</code>, visible focus indicators.</li>
|
||||
<li>Chip toggles (A3): use <code>role="checkbox"</code> or <code><input type="checkbox"></code> with visual styling.</li>
|
||||
<li>Progress steps (A2, A3): use <code>aria-current="step"</code> on the active step.</li>
|
||||
<li>Contrast: all text meets WCAG 2.2 AA (4.5:1 normal, 3:1 large).</li>
|
||||
</ul>
|
||||
|
||||
<h3>Preconditions & Dependencies</h3>
|
||||
<ul>
|
||||
<li>J6 is a <strong>precondition for all other journeys</strong> — user must have an account and household.</li>
|
||||
<li>J6 is a precondition for J5 shared list — household members must be invited before the list is shared.</li>
|
||||
<li>A3 configures staples that J5 (shopping list) uses for smart filtering.</li>
|
||||
<li>No other screens are accessible to household members in v1 besides C1 (read-only) and D1.</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user