Initial commit: PRD, build tooling, and design mockups

Foodster is a self-hosted dinner log and meal suggester for one household.
Stage 1 (eating history) is in development; stage 2 (the suggester) follows
once there is enough history to weight against.

Replace the PRD's original stack (Nuxt, Postgres, Drizzle, Pico CSS) with
Go 1.27, net/http, templ, Datastar and SQLite — one static binary, no
Node.js in the build. Sections 3, 4, 5, 9, 10 and 11 are rewritten to match.

Record the decisions made while reviewing mockups:

- the UI is written in Finnish; PRD, code and comments stay English
- access is one shared password over HTTP Basic rather than open on the LAN
- images are CalVer vYYYYMMDD-N, built with Podman, run under Docker Compose
- registry coordinates live in .env, so no infrastructure detail is
  committed and the MIT publication option stays open

mockups/ holds standalone HTML design studies. log-fi.html is the current
one; the others are superseded exploration kept for reference.
This commit is contained in:
Esa Kataja
2026-09-05 17:35:37 +03:00
commit 6ed047aafd
13 changed files with 2067 additions and 0 deletions
+229
View File
@@ -0,0 +1,229 @@
<!doctype html>
<html lang="en" data-theme="auto">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Foodster — A · Chit rail</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Martian+Mono:wght@300;500;700&family=Public+Sans:wght@400;600&family=Saira+Condensed:wght@600;700&display=swap" rel="stylesheet">
<style>
:root{
color-scheme: light dark;
--ground: light-dark(#E7E8E3, #131417);
--chit: light-dark(#FBFBF8, #1F2126);
--ink: light-dark(#15171B, #E7E8E2);
--muted: light-dark(#6E7269, #8C9088);
--rail: light-dark(#A8ACA2, #4A4E54);
--line: light-dark(#D3D5CD, #32353B);
--stamp: light-dark(#1F3FBF, #7D97FF);
--meat: light-dark(#B23A2E, #E2705F);
--chicken: light-dark(#A97511, #E0A93A);
--fish: light-dark(#1F6FA8, #64ACDC);
--vegetarian:light-dark(#3C7A45, #7CBB86);
}
html[data-theme="light"]{ color-scheme: only light; }
html[data-theme="dark"] { color-scheme: only dark; }
*{box-sizing:border-box;}
body{
margin:0; background:var(--ground); color:var(--ink);
font-family:"Public Sans",system-ui,sans-serif; font-size:16px; line-height:1.5;
-webkit-font-smoothing:antialiased;
}
.wrap{max-width:1180px; margin:0 auto; padding:0 20px 80px;}
/* ---- masthead ---- */
.top{display:flex; align-items:baseline; gap:20px; flex-wrap:wrap;
padding:26px 0 20px; border-bottom:1px solid var(--line);}
.logo{font-family:"Martian Mono",monospace; font-weight:700; font-size:15px;
letter-spacing:.06em; text-transform:uppercase;}
.logo b{color:var(--stamp); font-weight:700;}
nav{display:flex; gap:2px; margin-left:auto;}
nav a{font-family:"Martian Mono",monospace; font-size:11px; letter-spacing:.04em;
text-transform:uppercase; color:var(--muted); text-decoration:none;
padding:7px 12px; border:1px solid transparent; border-radius:2px;}
nav a[aria-current]{color:var(--ink); border-color:var(--line); background:var(--chit);}
nav a:hover{color:var(--ink);}
.themebtn{font-family:"Martian Mono",monospace; font-size:11px; letter-spacing:.04em;
text-transform:uppercase; background:none; border:1px solid var(--line);
color:var(--muted); padding:7px 10px; border-radius:2px; cursor:pointer;}
.themebtn:hover{color:var(--ink);}
/* ---- header ---- */
header.lede{padding:44px 0 30px; display:flex; align-items:flex-end; gap:28px; flex-wrap:wrap;}
h1{font-family:"Saira Condensed",sans-serif; font-weight:700; font-size:clamp(46px,8vw,86px);
line-height:.9; letter-spacing:-.015em; margin:0; text-transform:uppercase;}
.lede p{margin:0 0 6px; max-width:34ch; color:var(--muted); font-size:15px;}
.regen{margin-left:auto; font-family:"Martian Mono",monospace; font-size:11px;
letter-spacing:.05em; text-transform:uppercase; background:var(--stamp); color:#fff;
border:0; padding:13px 20px; border-radius:2px; cursor:pointer;}
.regen:hover{filter:brightness(1.12);}
/* ---- warning ---- */
.notice{display:flex; gap:12px; align-items:flex-start; margin:0 0 34px;
padding:13px 16px; background:var(--chit); border-left:3px solid var(--meat);
border-radius:0 2px 2px 0; font-size:14px;}
.notice b{font-family:"Martian Mono",monospace; font-size:11px; letter-spacing:.05em;
text-transform:uppercase; color:var(--meat); white-space:nowrap; padding-top:2px;}
/* ---- the rail ---- */
.rail{position:relative; padding-top:22px;}
.rail::before{content:""; position:absolute; top:14px; left:-12px; right:-12px; height:3px;
background:var(--rail); border-radius:2px;}
.chits{display:grid; gap:18px; grid-template-columns:repeat(auto-fill,minmax(248px,1fr));}
.chit{position:relative; background:var(--chit); border:1px solid var(--line);
border-top:0; border-radius:0 0 3px 3px; padding:26px 16px 14px;}
.chit::before{content:""; position:absolute; top:0; left:0; right:0; height:7px;
background:radial-gradient(circle at 5px 0, var(--ground) 4px, transparent 4.5px) repeat-x;
background-size:12px 7px;}
.chit::after{content:""; position:absolute; top:-14px; left:50%; margin-left:-5px;
width:10px; height:18px; background:var(--rail); border-radius:2px;}
.cat{display:flex; gap:5px; margin:0 0 12px;}
.cat span{font-family:"Martian Mono",monospace; font-size:9px; font-weight:500;
letter-spacing:.08em; text-transform:uppercase; color:#fff; padding:3px 6px; border-radius:2px;}
.c-meat{background:var(--meat);} .c-chicken{background:var(--chicken);}
.c-fish{background:var(--fish);} .c-vegetarian{background:var(--vegetarian);}
.dish{font-family:"Saira Condensed",sans-serif; font-weight:600; font-size:29px;
line-height:1.02; margin:0 0 10px; text-transform:uppercase; letter-spacing:-.005em;}
.sides{list-style:none; margin:0 0 14px; padding:12px 0 0; border-top:1px dashed var(--line);
font-size:14px; color:var(--ink);}
.sides li{display:flex; gap:8px;}
.sides li::before{content:"+"; color:var(--muted);}
.nosides{margin:0 0 14px; padding:12px 0 0; border-top:1px dashed var(--line);
font-family:"Martian Mono",monospace; font-size:10px; letter-spacing:.04em;
text-transform:uppercase; color:var(--muted);}
.last{font-family:"Martian Mono",monospace; font-size:10px; letter-spacing:.02em;
color:var(--muted); margin:0 0 14px;}
.swaps{display:flex; gap:6px;}
.swaps button{flex:1; font-family:"Martian Mono",monospace; font-size:10px; letter-spacing:.03em;
text-transform:uppercase; background:none; color:var(--muted);
border:1px solid var(--line); border-radius:2px; padding:8px 4px; cursor:pointer;}
.swaps button:hover:not(:disabled){color:var(--stamp); border-color:var(--stamp);}
.swaps button:disabled{opacity:.35; cursor:not-allowed;}
/* ---- log strip ---- */
.logstrip{margin-top:56px; border-top:1px solid var(--line); padding-top:20px;}
.logstrip h2{font-family:"Martian Mono",monospace; font-size:11px; letter-spacing:.06em;
text-transform:uppercase; color:var(--muted); margin:0 0 14px; font-weight:500;}
.logrow{display:flex; gap:16px; align-items:baseline; padding:9px 0;
border-bottom:1px solid var(--line); font-size:15px;}
.logrow time{font-family:"Martian Mono",monospace; font-size:11px; color:var(--muted);
min-width:74px;}
.logrow .w{width:8px; height:8px; border-radius:1px; align-self:center;}
.logrow em{color:var(--muted); font-style:normal; font-size:13px;}
:focus-visible{outline:2px solid var(--stamp); outline-offset:2px;}
@media (max-width:560px){ .rail::before,.chit::after{display:none;} }
@media (prefers-reduced-motion:reduce){ *{transition:none!important;} }
</style>
</head>
<body>
<div class="wrap">
<div class="top">
<div class="logo">Food<b>ster</b></div>
<nav>
<a href="#" aria-current="page">Plan</a>
<a href="#">Log</a>
<a href="#">Catalog</a>
</nav>
<button class="themebtn" id="t">Theme: auto</button>
</div>
<header class="lede">
<h1>Seven<br>dinners</h1>
<p>Drawn from what this house actually eats. Swap anything that doesn't appeal — nothing here is a schedule.</p>
<button class="regen">New seven</button>
</header>
<div class="notice">
<b>No fish</b>
<span>Every fish dish was eaten in the last 14 days. Fish sits this week out — shorten the cooldown in Catalog if that's too strict.</span>
</div>
<div class="rail">
<div class="chits">
<article class="chit">
<div class="cat"><span class="c-meat">meat</span></div>
<h3 class="dish">Jauheliha&shy;kastike</h3>
<ul class="sides"><li>Perunamuusi</li><li>Vihersalaatti</li></ul>
<p class="last">last eaten 23 days ago</p>
<div class="swaps"><button>Swap similar</button><button>Swap anything</button></div>
</article>
<article class="chit">
<div class="cat"><span class="c-chicken">chicken</span></div>
<h3 class="dish">Broileri&shy;kastike</h3>
<ul class="sides"><li>Riisi</li></ul>
<p class="last">last eaten 31 days ago</p>
<div class="swaps"><button>Swap similar</button><button>Swap anything</button></div>
</article>
<article class="chit">
<div class="cat">
<span class="c-meat">meat</span><span class="c-chicken">chicken</span>
<span class="c-fish">fish</span><span class="c-vegetarian">veg</span>
</div>
<h3 class="dish">Tortillat</h3>
<p class="nosides">served on its own</p>
<p class="last">last eaten 40 days ago</p>
<div class="swaps">
<button disabled title="No other dish covers all four categories">Swap similar</button>
<button>Swap anything</button>
</div>
</article>
<article class="chit">
<div class="cat"><span class="c-vegetarian">vegetarian</span></div>
<h3 class="dish">Kasvis&shy;pyörykät</h3>
<ul class="sides"><li>Perunamuusi</li><li>Höyrytetyt Porkkanat</li></ul>
<p class="last">last eaten 52 days ago</p>
<div class="swaps"><button>Swap similar</button><button>Swap anything</button></div>
</article>
<article class="chit">
<div class="cat"><span class="c-meat">meat</span></div>
<h3 class="dish">Makaroni&shy;laatikko</h3>
<p class="nosides">served on its own</p>
<p class="last">last eaten 19 days ago</p>
<div class="swaps"><button>Swap similar</button><button>Swap anything</button></div>
</article>
<article class="chit">
<div class="cat"><span class="c-vegetarian">vegetarian</span></div>
<h3 class="dish">Hernekeitto</h3>
<p class="nosides">served on its own</p>
<p class="last">last eaten 27 days ago</p>
<div class="swaps"><button>Swap similar</button><button>Swap anything</button></div>
</article>
<article class="chit">
<div class="cat"><span class="c-meat">meat</span></div>
<h3 class="dish">Lihapullat</h3>
<ul class="sides"><li>Perunamuusi</li><li>Vihersalaatti</li></ul>
<p class="last">last eaten 16 days ago</p>
<div class="swaps"><button>Swap similar</button><button>Swap anything</button></div>
</article>
</div>
</div>
<section class="logstrip">
<h2>Recently eaten</h2>
<div class="logrow"><time>Thu 3 Sep</time><i class="w c-fish"></i><span>Lohikeitto</span><em>Ruisleipä</em></div>
<div class="logrow"><time>Wed 2 Sep</time><i class="w c-chicken"></i><span>Kanacurry</span><em>Riisi</em></div>
<div class="logrow"><time>Tue 1 Sep</time><i class="w c-meat"></i><span>Uunimakkara</span><em>Perunamuusi · Vihersalaatti</em></div>
</section>
</div>
<script>
const t=document.getElementById('t'), h=document.documentElement, m=['auto','light','dark'];
t.onclick=()=>{const n=m[(m.indexOf(h.dataset.theme)+1)%3]; h.dataset.theme=n; t.textContent='Theme: '+n;};
</script>
</body>
</html>
+214
View File
@@ -0,0 +1,214 @@
<!doctype html>
<html lang="en" data-theme="auto">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Foodster — B · Enamel tile</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Bricolage+Grotesque:opsz,[email protected],500;12..96,700;12..96,800&family=Karla:wght@400;500;700&display=swap" rel="stylesheet">
<style>
:root{
color-scheme: light dark;
--grout: light-dark(#D9D6CE, #191A1C);
--card: light-dark(#FFFFFF, #232529);
--ink: light-dark(#1B1C18, #EDEDE8);
--muted: light-dark(#71736B, #94968D);
--line: light-dark(#C7C4BB, #32343A);
--glaze: light-dark(rgba(255,255,255,.30), rgba(255,255,255,.10));
--meat: light-dark(#A8452F, #8E3826);
--chicken: light-dark(#C68A16, #A87211);
--fish: light-dark(#2C6B92, #23566F);
--vegetarian:light-dark(#4E7A38, #3F6430);
--on-tile:#FFFCF4;
}
html[data-theme="light"]{ color-scheme: only light; }
html[data-theme="dark"] { color-scheme: only dark; }
*{box-sizing:border-box;}
body{margin:0; background:var(--grout); color:var(--ink);
font-family:Karla,system-ui,sans-serif; font-size:16px; line-height:1.5;
-webkit-font-smoothing:antialiased;}
.wrap{max-width:1120px; margin:0 auto; padding:0 20px 80px;}
.top{display:flex; align-items:center; gap:18px; flex-wrap:wrap; padding:22px 0;}
.logo{font-family:"Bricolage Grotesque",sans-serif; font-weight:800; font-size:22px;
letter-spacing:-.03em;}
nav{display:flex; gap:4px; margin-left:auto;}
nav a{font-size:14px; font-weight:500; color:var(--muted); text-decoration:none;
padding:7px 14px; border-radius:999px;}
nav a[aria-current]{background:var(--card); color:var(--ink);}
nav a:hover{color:var(--ink);}
.themebtn{font-family:Karla,sans-serif; font-size:13px; background:var(--card);
border:1px solid var(--line); color:var(--muted); padding:7px 13px;
border-radius:999px; cursor:pointer;}
.themebtn:hover{color:var(--ink);}
header.lede{padding:34px 0 22px; max-width:44ch;}
h1{font-family:"Bricolage Grotesque",sans-serif; font-weight:700; font-size:clamp(38px,6vw,60px);
line-height:1.0; letter-spacing:-.035em; margin:0 0 12px;}
.lede p{margin:0; color:var(--muted); font-size:16px;}
/* ---- signature: coverage bar ---- */
.coverage{margin:26px 0 8px;}
.covbar{display:flex; height:10px; border-radius:3px; overflow:hidden; gap:2px;}
.covbar i{display:block;}
.covkey{display:flex; gap:16px; flex-wrap:wrap; margin-top:10px;
font-size:13px; color:var(--muted);}
.covkey span{display:flex; align-items:center; gap:7px;}
.covkey b{width:9px; height:9px; border-radius:2px;}
.covkey em{font-style:normal; color:var(--ink); font-weight:500;}
.actions{display:flex; gap:10px; margin:26px 0 22px; flex-wrap:wrap;}
.regen{font-family:Karla,sans-serif; font-weight:700; font-size:15px; background:var(--ink);
color:var(--grout); border:0; padding:12px 22px; border-radius:8px; cursor:pointer;}
.regen:hover{opacity:.86;}
/* ---- tiles ---- */
.tiles{display:grid; gap:6px; grid-template-columns:repeat(auto-fill,minmax(232px,1fr));}
.tile{position:relative; border-radius:5px; padding:20px 18px 16px; color:var(--on-tile);
min-height:196px; display:flex; flex-direction:column; overflow:hidden;}
.tile::before{content:""; position:absolute; inset:0;
background:linear-gradient(158deg, var(--glaze) 0%, transparent 46%); pointer-events:none;}
.t-meat{background:var(--meat);} .t-chicken{background:var(--chicken);}
.t-fish{background:var(--fish);} .t-vegetarian{background:var(--vegetarian);}
.tile.wild{background:linear-gradient(112deg,
var(--meat) 0 25%, var(--chicken) 25% 50%, var(--fish) 50% 75%, var(--vegetarian) 75% 100%);}
.tag{font-size:11px; font-weight:700; letter-spacing:.09em; text-transform:uppercase;
opacity:.82; margin:0 0 auto;}
.dish{font-family:"Bricolage Grotesque",sans-serif; font-weight:700; font-size:27px;
line-height:1.05; letter-spacing:-.03em; margin:16px 0 8px;}
.sides{margin:0 0 12px; font-size:14px; opacity:.88;}
.last{font-size:12px; opacity:.7; margin:0 0 14px;}
.swaps{display:flex; gap:6px; position:relative;}
.swaps button{flex:1; font-family:Karla,sans-serif; font-size:12px; font-weight:700;
background:rgba(255,255,255,.16); color:var(--on-tile); border:0;
padding:9px 6px; border-radius:6px; cursor:pointer; backdrop-filter:blur(2px);}
.swaps button:hover:not(:disabled){background:rgba(255,255,255,.30);}
.swaps button:disabled{opacity:.4; cursor:not-allowed;}
/* ---- log ---- */
.logstrip{margin-top:44px;}
.logstrip h2{font-family:"Bricolage Grotesque",sans-serif; font-weight:700; font-size:19px;
letter-spacing:-.02em; margin:0 0 12px;}
.logrow{display:flex; gap:14px; align-items:center; background:var(--card);
border-radius:7px; padding:11px 15px; margin-bottom:5px; font-size:15px;}
.logrow time{color:var(--muted); font-size:13px; min-width:78px;}
.logrow i{width:10px; height:10px; border-radius:2px;}
.logrow em{color:var(--muted); font-style:normal; font-size:13px; margin-left:auto;}
.k-meat{background:var(--meat);} .k-chicken{background:var(--chicken);}
.k-fish{background:var(--fish);} .k-vegetarian{background:var(--vegetarian);}
:focus-visible{outline:2.5px solid var(--ink); outline-offset:2px;}
@media (prefers-reduced-motion:reduce){ *{transition:none!important;} }
</style>
</head>
<body>
<div class="wrap">
<div class="top">
<div class="logo">Foodster</div>
<nav><a href="#" aria-current="page">Plan</a><a href="#">Log</a><a href="#">Catalog</a></nav>
<button class="themebtn" id="t">Theme: auto</button>
</div>
<header class="lede">
<h1>Seven dinners, no schedule.</h1>
<p>Pick any of these on any night. Swap what doesn't appeal.</p>
</header>
<div class="coverage">
<div class="covbar" role="img" aria-label="This week covers meat, chicken, fish and vegetarian">
<i class="t-meat" style="flex:3"></i>
<i class="t-chicken" style="flex:1"></i>
<i class="t-fish" style="flex:1"></i>
<i class="t-vegetarian"style="flex:2"></i>
</div>
<div class="covkey">
<span><b class="t-meat"></b><em>Meat</em> ×3</span>
<span><b class="t-chicken"></b><em>Chicken</em> ×1</span>
<span><b class="t-fish"></b><em>Fish</em> ×1</span>
<span><b class="t-vegetarian"></b><em>Vegetarian</em> ×2</span>
</div>
</div>
<div class="actions"><button class="regen">New seven</button></div>
<div class="tiles">
<article class="tile t-meat">
<p class="tag">Meat</p>
<h3 class="dish">Jauhelihakastike</h3>
<p class="sides">Perunamuusi · Vihersalaatti</p>
<p class="last">Last eaten 23 days ago</p>
<div class="swaps"><button>Similar</button><button>Anything</button></div>
</article>
<article class="tile t-chicken">
<p class="tag">Chicken</p>
<h3 class="dish">Broilerikastike</h3>
<p class="sides">Riisi</p>
<p class="last">Last eaten 31 days ago</p>
<div class="swaps"><button>Similar</button><button>Anything</button></div>
</article>
<article class="tile wild">
<p class="tag">Meat · Chicken · Fish · Veg</p>
<h3 class="dish">Tortillat</h3>
<p class="sides">Served on its own</p>
<p class="last">Last eaten 40 days ago</p>
<div class="swaps">
<button disabled title="Nothing else covers all four">Similar</button>
<button>Anything</button>
</div>
</article>
<article class="tile t-fish">
<p class="tag">Fish</p>
<h3 class="dish">Uunilohi</h3>
<p class="sides">Riisi · Vihersalaatti</p>
<p class="last">Last eaten 34 days ago</p>
<div class="swaps"><button>Similar</button><button>Anything</button></div>
</article>
<article class="tile t-vegetarian">
<p class="tag">Vegetarian</p>
<h3 class="dish">Kasvispyörykät</h3>
<p class="sides">Perunamuusi · Höyrytetyt Porkkanat</p>
<p class="last">Last eaten 52 days ago</p>
<div class="swaps"><button>Similar</button><button>Anything</button></div>
</article>
<article class="tile t-meat">
<p class="tag">Meat</p>
<h3 class="dish">Makaronilaatikko</h3>
<p class="sides">Served on its own</p>
<p class="last">Last eaten 19 days ago</p>
<div class="swaps"><button>Similar</button><button>Anything</button></div>
</article>
<article class="tile t-vegetarian">
<p class="tag">Vegetarian</p>
<h3 class="dish">Hernekeitto</h3>
<p class="sides">Served on its own</p>
<p class="last">Last eaten 27 days ago</p>
<div class="swaps"><button>Similar</button><button>Anything</button></div>
</article>
</div>
<section class="logstrip">
<h2>Recently eaten</h2>
<div class="logrow"><time>Thu 3 Sep</time><i class="k-fish"></i><span>Lohikeitto</span><em>Ruisleipä</em></div>
<div class="logrow"><time>Wed 2 Sep</time><i class="k-chicken"></i><span>Kanacurry</span><em>Riisi</em></div>
<div class="logrow"><time>Tue 1 Sep</time><i class="k-meat"></i><span>Uunimakkara</span><em>Perunamuusi · Vihersalaatti</em></div>
</section>
</div>
<script>
const t=document.getElementById('t'), h=document.documentElement, m=['auto','light','dark'];
t.onclick=()=>{const n=m[(m.indexOf(h.dataset.theme)+1)%3]; h.dataset.theme=n; t.textContent='Theme: '+n;};
</script>
</body>
</html>
+199
View File
@@ -0,0 +1,199 @@
<!doctype html>
<html lang="en" data-theme="auto">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Foodster — C · Fridge board</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Kalam:wght@400;700&family=Figtree:wght@400;500;700;800&display=swap" rel="stylesheet">
<style>
:root{
color-scheme: light dark;
--board: light-dark(#E3E7E3, #1A1D1B);
--card: light-dark(#FCFCFA, #24272A);
--ink: light-dark(#1D2724, #E6E9E5);
--muted: light-dark(#6F7A75, #8E9994);
--line: light-dark(#D2D8D3, #33383A);
--marker:light-dark(#16403C, #6FD3C4);
--tape: light-dark(#E8D48A, #6E6134);
--meat: light-dark(#AF4230, #E07561);
--chicken: light-dark(#BE8410, #DFA83B);
--fish: light-dark(#25688F, #63AAD8);
--vegetarian:light-dark(#457A3C, #82BE7A);
}
html[data-theme="light"]{ color-scheme: only light; }
html[data-theme="dark"] { color-scheme: only dark; }
*{box-sizing:border-box;}
body{margin:0; background:var(--board); color:var(--ink);
font-family:Figtree,system-ui,sans-serif; font-size:16px; line-height:1.5;
-webkit-font-smoothing:antialiased;}
.wrap{max-width:1100px; margin:0 auto; padding:0 20px 80px;}
.top{display:flex; align-items:center; gap:18px; flex-wrap:wrap; padding:22px 0 0;}
.logo{font-family:Kalam,cursive; font-weight:700; font-size:26px; color:var(--marker);}
nav{display:flex; gap:6px; margin-left:auto;}
nav a{font-size:14px; font-weight:500; color:var(--muted); text-decoration:none; padding:6px 2px;
border-bottom:2px solid transparent;}
nav a[aria-current]{color:var(--ink); border-color:var(--marker);}
nav a:hover{color:var(--ink);}
.themebtn{font-family:Figtree,sans-serif; font-size:13px; background:none;
border:1px solid var(--line); color:var(--muted); padding:6px 12px;
border-radius:6px; cursor:pointer;}
.themebtn:hover{color:var(--ink);}
header.lede{padding:38px 0 8px; max-width:46ch;}
h1{font-family:Figtree,sans-serif; font-weight:800; font-size:clamp(34px,5.5vw,52px);
line-height:1.05; letter-spacing:-.03em; margin:0 0 10px;}
.lede p{margin:0; color:var(--muted);}
.handnote{font-family:Kalam,cursive; font-size:19px; color:var(--marker);
transform:rotate(-1.1deg); margin:18px 0 0;}
.actions{display:flex; gap:10px; margin:24px 0 26px;}
.regen{font-family:Figtree,sans-serif; font-weight:700; font-size:15px;
background:var(--marker); color:var(--board); border:0; padding:12px 22px;
border-radius:7px; cursor:pointer;}
.regen:hover{filter:brightness(1.12);}
/* ---- cards on the board ---- */
.cards{display:grid; gap:22px; grid-template-columns:repeat(auto-fill,minmax(238px,1fr));}
.card{position:relative; background:var(--card); border-radius:3px; padding:26px 18px 16px;
box-shadow:0 1px 2px rgba(0,0,0,.10), 0 6px 14px -8px rgba(0,0,0,.22);}
.card:nth-child(3n+1){transform:rotate(-.55deg);}
.card:nth-child(3n+2){transform:rotate(.4deg);}
.card:nth-child(3n+3){transform:rotate(-.2deg);}
.card::before{content:""; position:absolute; top:-7px; left:50%; margin-left:-7px;
width:14px; height:14px; border-radius:50%; background:var(--mag,var(--muted));
box-shadow:inset 0 -2px 3px rgba(0,0,0,.28);}
.m-meat{--mag:var(--meat);} .m-chicken{--mag:var(--chicken);}
.m-fish{--mag:var(--fish);} .m-vegetarian{--mag:var(--vegetarian);}
.card.wild::before{background:conic-gradient(var(--meat) 0 25%, var(--chicken) 25% 50%,
var(--fish) 50% 75%, var(--vegetarian) 75% 100%);}
.tag{font-size:11px; font-weight:700; letter-spacing:.08em; text-transform:uppercase;
color:var(--muted); margin:0;}
.dish{font-family:Figtree,sans-serif; font-weight:700; font-size:25px; line-height:1.08;
letter-spacing:-.025em; margin:8px 0 8px;}
.sides{margin:0 0 10px; font-size:14px; color:var(--ink);}
.sides.none{color:var(--muted); font-style:italic;}
.last{font-size:12.5px; color:var(--muted); margin:0 0 14px;}
.swaps{display:flex; gap:6px; border-top:1px solid var(--line); padding-top:12px;}
.swaps button{flex:1; font-family:Figtree,sans-serif; font-size:12.5px; font-weight:600;
background:none; color:var(--muted); border:1px solid var(--line);
border-radius:6px; padding:8px 5px; cursor:pointer;}
.swaps button:hover:not(:disabled){color:var(--marker); border-color:var(--marker);}
.swaps button:disabled{opacity:.4; cursor:not-allowed;}
/* ---- the split: handwriting = what we actually ate ---- */
.logstrip{margin-top:56px; border-top:2px dashed var(--line); padding-top:24px;}
.logstrip h2{font-family:Figtree,sans-serif; font-weight:800; font-size:18px;
letter-spacing:-.02em; margin:0 0 4px;}
.logstrip .sub{color:var(--muted); font-size:14px; margin:0 0 18px;}
.logrow{display:flex; gap:14px; align-items:baseline; padding:10px 0;
border-bottom:1px solid var(--line);}
.logrow time{font-size:13px; color:var(--muted); min-width:80px;}
.logrow .hand{font-family:Kalam,cursive; font-size:22px; color:var(--marker); line-height:1.2;}
.logrow em{font-family:Kalam,cursive; font-style:normal; font-size:17px;
color:var(--muted); margin-left:auto;}
:focus-visible{outline:2.5px solid var(--marker); outline-offset:3px;}
@media (max-width:560px){ .card{transform:none!important;} }
@media (prefers-reduced-motion:reduce){ *{transition:none!important;} }
</style>
</head>
<body>
<div class="wrap">
<div class="top">
<div class="logo">Foodster</div>
<nav><a href="#" aria-current="page">Plan</a><a href="#">Log</a><a href="#">Catalog</a></nav>
<button class="themebtn" id="t">Theme: auto</button>
</div>
<header class="lede">
<h1>Seven dinners on the door.</h1>
<p>Take them in any order. Swap what nobody's in the mood for.</p>
<p class="handnote">— last regenerated Sunday</p>
</header>
<div class="actions"><button class="regen">New seven</button></div>
<div class="cards">
<article class="card m-meat">
<p class="tag">Meat</p>
<h3 class="dish">Jauhelihakastike</h3>
<p class="sides">Perunamuusi · Vihersalaatti</p>
<p class="last">Last eaten 23 days ago</p>
<div class="swaps"><button>Swap similar</button><button>Swap anything</button></div>
</article>
<article class="card m-chicken">
<p class="tag">Chicken</p>
<h3 class="dish">Broilerikastike</h3>
<p class="sides">Riisi</p>
<p class="last">Last eaten 31 days ago</p>
<div class="swaps"><button>Swap similar</button><button>Swap anything</button></div>
</article>
<article class="card wild">
<p class="tag">Meat · Chicken · Fish · Veg</p>
<h3 class="dish">Tortillat</h3>
<p class="sides none">Served on its own</p>
<p class="last">Last eaten 40 days ago</p>
<div class="swaps">
<button disabled title="Nothing else covers all four">Swap similar</button>
<button>Swap anything</button>
</div>
</article>
<article class="card m-fish">
<p class="tag">Fish</p>
<h3 class="dish">Uunilohi</h3>
<p class="sides">Riisi · Vihersalaatti</p>
<p class="last">Last eaten 34 days ago</p>
<div class="swaps"><button>Swap similar</button><button>Swap anything</button></div>
</article>
<article class="card m-vegetarian">
<p class="tag">Vegetarian</p>
<h3 class="dish">Kasvispyörykät</h3>
<p class="sides">Perunamuusi · Höyrytetyt Porkkanat</p>
<p class="last">Last eaten 52 days ago</p>
<div class="swaps"><button>Swap similar</button><button>Swap anything</button></div>
</article>
<article class="card m-meat">
<p class="tag">Meat</p>
<h3 class="dish">Makaronilaatikko</h3>
<p class="sides none">Served on its own</p>
<p class="last">Last eaten 19 days ago</p>
<div class="swaps"><button>Swap similar</button><button>Swap anything</button></div>
</article>
<article class="card m-vegetarian">
<p class="tag">Vegetarian</p>
<h3 class="dish">Hernekeitto</h3>
<p class="sides none">Served on its own</p>
<p class="last">Last eaten 27 days ago</p>
<div class="swaps"><button>Swap similar</button><button>Swap anything</button></div>
</article>
</div>
<section class="logstrip">
<h2>What we actually ate</h2>
<p class="sub">Printed above is the app guessing. Below is the house, on the record.</p>
<div class="logrow"><time>Thu 3 Sep</time><span class="hand">Lohikeitto</span><em>ruisleipä</em></div>
<div class="logrow"><time>Wed 2 Sep</time><span class="hand">Kanacurry</span><em>riisi</em></div>
<div class="logrow"><time>Tue 1 Sep</time><span class="hand">Uunimakkara</span><em>perunamuusi, vihersalaatti</em></div>
</section>
</div>
<script>
const t=document.getElementById('t'), h=document.documentElement, m=['auto','light','dark'];
t.onclick=()=>{const n=m[(m.indexOf(h.dataset.theme)+1)%3]; h.dataset.theme=n; t.textContent='Theme: '+n;};
</script>
</body>
</html>
+345
View File
@@ -0,0 +1,345 @@
<!doctype html>
<html lang="fi" data-theme="auto">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover">
<title>Foodster — kirjaus ja historia</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Archivo:wght@400;500;600;700&family=Martian+Mono:wght@300;500&display=swap" rel="stylesheet">
<style>
:root{
color-scheme: light dark;
--paper: light-dark(#ECEDE8, #121316);
--card: light-dark(#FFFFFF, #1C1E22);
--sunk: light-dark(#E3E4DE, #17181B);
--ink: light-dark(#14161A, #E9EAE5);
--muted: light-dark(#6B6F6A, #8B8F89);
--line: light-dark(#D5D6D0, #2C2F34);
--accent:light-dark(#15616D, #58C6D2);
--onacc: light-dark(#FFFFFF, #0C1417);
--warn: light-dark(#AF4230, #DE7561);
--liha: light-dark(#AF4230, #DE7561);
--kana: light-dark(#B57E10, #DFA83B);
--kala: light-dark(#25688F, #63AAD8);
--kasvis:light-dark(#457A3C, #82BE7A);
--tap: 48px;
}
html[data-theme="light"]{color-scheme:only light;}
html[data-theme="dark"] {color-scheme:only dark;}
*{box-sizing:border-box; -webkit-tap-highlight-color:transparent;}
body{margin:0; background:var(--sunk); color:var(--ink);
font-family:Archivo,system-ui,sans-serif; font-size:16px; line-height:1.45;
-webkit-font-smoothing:antialiased;}
button,input{font-family:inherit;}
:focus-visible{outline:2.5px solid var(--accent); outline-offset:2px;}
@media (prefers-reduced-motion:reduce){*{transition:none!important;}}
.dot{width:9px; height:9px; border-radius:2px; flex:none; display:inline-block;}
.d-liha{background:var(--liha);} .d-kana{background:var(--kana);}
.d-kala{background:var(--kala);} .d-kasvis{background:var(--kasvis);}
.d-sek{background:conic-gradient(var(--liha) 0 25%,var(--kana) 25% 50%,
var(--kala) 50% 75%,var(--kasvis) 75% 100%);}
/* ---- comparison rig (desktop only) ---- */
.rig{padding:20px; max-width:1400px; margin:0 auto;}
.rigtop{display:flex; align-items:center; gap:14px; flex-wrap:wrap; margin-bottom:18px;}
.rigtop h1{font-size:17px; font-weight:700; letter-spacing:-.02em; margin:0;}
.rigtop p{margin:0; font-size:13px; color:var(--muted);}
.themebtn{font-size:12px; background:var(--card); border:1px solid var(--line);
color:var(--muted); padding:7px 12px; border-radius:7px; cursor:pointer; margin-left:auto;}
.switch{display:none; gap:6px; margin-bottom:14px;}
.switch button{font-size:14px; font-weight:600; background:var(--card);
border:1px solid var(--line); color:var(--muted); padding:10px 12px;
border-radius:8px; cursor:pointer; flex:1;}
.switch button[aria-pressed="true"]{background:var(--accent); border-color:var(--accent); color:var(--onacc);}
.frames{display:flex; gap:22px; align-items:flex-start; overflow-x:auto; padding-bottom:14px;}
.framewrap{flex:none;}
.framelbl{font-size:13px; font-weight:600; margin:0 0 9px; letter-spacing:-.01em;}
.framelbl span{font-weight:400; color:var(--muted);}
.phone{width:390px; height:788px; background:var(--paper); border:1px solid var(--line);
border-radius:26px; overflow:hidden; display:flex; flex-direction:column;}
/* ---- app chrome ---- */
.screen{flex:1; overflow-y:auto; overscroll-behavior:contain; position:relative;}
.appbar{position:sticky; top:0; z-index:5; background:var(--paper);
border-bottom:1px solid var(--line); padding:14px 16px 12px;}
.appbar h2{margin:0; font-size:22px; font-weight:700; letter-spacing:-.03em;}
.appbar .meta{font-size:12.5px; color:var(--muted); margin:2px 0 0;}
.pad{padding:16px;}
.tabbar{flex:none; display:flex; background:var(--card); border-top:1px solid var(--line);
padding-bottom:env(safe-area-inset-bottom);}
.tabbar a{flex:1; text-align:center; text-decoration:none; color:var(--muted);
font-size:11.5px; font-weight:600; padding:11px 0 13px;}
.tabbar a[aria-current]{color:var(--accent);}
.tabbar i{display:block; font-style:normal; font-size:18px; line-height:1.3;}
.primary{display:block; width:100%; min-height:var(--tap); background:var(--accent);
color:var(--onacc); border:0; border-radius:11px; font-size:16.5px; font-weight:700;
cursor:pointer; letter-spacing:-.01em;}
.ghost{display:block; width:100%; min-height:var(--tap); background:none; color:var(--muted);
border:0; font-size:15px; cursor:pointer;}
/* ---- tap board ---- */
.dayseg{display:flex; gap:6px; margin-top:11px;}
.dayseg button{flex:1; min-height:42px; font-size:14px; font-weight:600; background:var(--card);
border:1px solid var(--line); color:var(--ink); border-radius:9px; cursor:pointer;}
.dayseg button[aria-pressed="true"]{background:var(--ink); border-color:var(--ink); color:var(--paper);}
.dayseg input{flex:none; width:52px; font-size:16px; background:var(--card); color:var(--ink);
border:1px solid var(--line); border-radius:9px; padding:0 8px;}
.filter{width:100%; min-height:var(--tap); font-size:16px; background:var(--card);
color:var(--ink); border:1px solid var(--line); border-radius:10px;
padding:0 14px; margin-bottom:14px;}
.board{display:flex; flex-wrap:wrap; gap:8px;}
.pill{display:flex; align-items:center; gap:8px; background:var(--card);
border:1px solid var(--line); border-radius:11px; cursor:pointer; color:var(--ink);
font-weight:600; letter-spacing:-.022em; min-height:var(--tap);}
.pill .n{font-family:"Martian Mono",monospace; font-weight:300; color:var(--muted); font-size:10px;}
.pill.xl{font-size:22px; padding:14px 18px; flex:1 1 100%;}
.pill.lg{font-size:18px; padding:12px 16px;}
.pill.md{font-size:15.5px; padding:11px 14px;}
.pill.sm{font-size:14px; padding:10px 13px; color:var(--muted);}
.pill.picked{background:var(--accent); border-color:var(--accent); color:var(--onacc);}
.pill.picked .n{color:var(--onacc); opacity:.65;}
.sheet{position:absolute; left:0; right:0; bottom:0; z-index:9; background:var(--card);
border-top:1px solid var(--line); border-radius:18px 18px 0 0; padding:8px 16px 16px;
box-shadow:0 -10px 30px -14px rgba(0,0,0,.4);}
.grab{width:38px; height:4px; border-radius:3px; background:var(--line); margin:0 auto 13px;}
.sheet h3{margin:0 0 2px; font-size:19px; letter-spacing:-.025em;}
.sheet .q{margin:0 0 12px; font-size:13px; color:var(--muted);}
.chips{display:flex; gap:7px; flex-wrap:wrap; margin-bottom:14px;}
.chips button{font-size:14.5px; min-height:42px; background:var(--sunk);
border:1px solid var(--line); color:var(--ink); padding:0 14px;
border-radius:999px; cursor:pointer;}
.chips button[aria-pressed="true"]{background:var(--accent); border-color:var(--accent); color:var(--onacc);}
/* ---- already-logged state ---- */
.logged{background:var(--card); border:1px solid var(--line); border-radius:14px; padding:18px;}
.logged .k{font-family:"Martian Mono",monospace; font-size:10px; letter-spacing:.09em;
text-transform:uppercase; color:var(--accent); margin:0 0 10px;}
.logged .nm{font-size:26px; font-weight:700; letter-spacing:-.035em; margin:0 0 3px;
display:flex; align-items:center; gap:10px;}
.logged .sd{font-size:14.5px; color:var(--muted); margin:0 0 16px;}
.logged .pair{display:flex; gap:8px;}
.logged .pair button{flex:1; min-height:var(--tap); font-size:15px; font-weight:600;
background:var(--sunk); border:1px solid var(--line); color:var(--ink);
border-radius:10px; cursor:pointer;}
.logged .pair button.del{color:var(--warn);}
.orlog{margin:22px 0 0; font-size:14px; color:var(--muted); text-align:center;}
.orlog button{background:none; border:0; color:var(--accent); font:inherit;
font-weight:600; text-decoration:underline; cursor:pointer; min-height:38px;}
/* ---- journal ---- */
.entry{display:flex; gap:12px; align-items:center; padding:14px 0;
border-bottom:1px solid var(--line); min-height:var(--tap);}
.entry time{font-family:"Martian Mono",monospace; font-size:11px; color:var(--muted);
min-width:54px; flex:none;}
.entry .nm{font-size:17px; font-weight:600; letter-spacing:-.025em;
display:flex; align-items:center; gap:8px;}
.entry .sd{font-size:12.5px; color:var(--muted); margin-top:1px;}
.entry .chev{margin-left:auto; color:var(--muted); font-size:19px; flex:none;}
.gapline{display:flex; gap:12px; align-items:center; padding:12px 0;
border-bottom:1px dashed var(--line); font-size:13.5px; color:var(--muted);
min-height:var(--tap);}
.gapline time{font-family:"Martian Mono",monospace; font-size:11px; min-width:54px; flex:none;}
.gapline button{margin-left:auto; font-size:13.5px; background:none; border:0;
color:var(--accent); font-weight:600; cursor:pointer; min-height:38px; padding:0 4px;}
.monthrule{font-family:"Martian Mono",monospace; font-size:10px; letter-spacing:.12em;
text-transform:uppercase; color:var(--muted); padding:22px 0 5px;}
@media (max-width:900px){
body{background:var(--paper);}
.rig{padding:0;}
.rigtop{padding:12px 16px 0; margin-bottom:10px;}
.switch{display:flex; padding:0 16px;}
.frames{display:block; overflow:visible; padding:0;}
.framewrap{display:none;} .framewrap.on{display:block;}
.framelbl{display:none;}
.phone{width:auto; height:calc(100svh - 118px); border:0; border-radius:0;}
}
</style>
</head>
<body>
<div class="rig">
<div class="rigtop">
<h1>Foodster · vaihe 1</h1>
<p>390&nbsp;px · Kirjaa, Historia, ja jo kirjattu tila</p>
<button class="themebtn" id="t">Teema: auto</button>
</div>
<div class="switch">
<button aria-pressed="true" data-v="f1">Kirjaa</button>
<button aria-pressed="false" data-v="f2">Kirjattu</button>
<button aria-pressed="false" data-v="f3">Historia</button>
</div>
<div class="frames">
<!-- ============ 1 · KIRJAA ============ -->
<div class="framewrap on" id="f1">
<p class="framelbl">1 · Kirjaa <span>— napauta ruoka, valitse lisukkeet</span></p>
<div class="phone">
<div class="screen">
<div class="appbar">
<h2>Mitä syötiin?</h2>
<div class="dayseg">
<button aria-pressed="true">Tänään</button>
<button aria-pressed="false">Eilen</button>
<input type="date" value="2026-09-05" aria-label="Muu päivä">
</div>
</div>
<div class="pad" style="padding-bottom:280px">
<input class="filter" type="search" placeholder="Etsi" aria-label="Etsi">
<div class="board">
<button class="pill xl"><i class="dot d-liha"></i>Lihapullat <span class="n">12</span></button>
<button class="pill xl"><i class="dot d-liha"></i>Jauhelihakastike <span class="n">11</span></button>
<button class="pill lg picked"><i class="dot d-kala"></i>Lohikeitto <span class="n">9</span></button>
<button class="pill lg"><i class="dot d-kana"></i>Kanacurry <span class="n">8</span></button>
<button class="pill lg"><i class="dot d-liha"></i>Makaronilaatikko <span class="n">7</span></button>
<button class="pill lg"><i class="dot d-kana"></i>Broilerikastike <span class="n">6</span></button>
<button class="pill md"><i class="dot d-kala"></i>Uunilohi <span class="n">5</span></button>
<button class="pill md"><i class="dot d-kasvis"></i>Kasvispyörykät <span class="n">5</span></button>
<button class="pill md"><i class="dot d-sek"></i>Tortillat <span class="n">4</span></button>
<button class="pill md"><i class="dot d-kasvis"></i>Hernekeitto <span class="n">4</span></button>
<button class="pill md"><i class="dot d-liha"></i>Uunimakkara <span class="n">3</span></button>
<button class="pill sm"><i class="dot d-liha"></i>Kaalilaatikko <span class="n">2</span></button>
<button class="pill sm"><i class="dot d-sek"></i>Kotipizza <span class="n">2</span></button>
<button class="pill sm"><i class="dot d-kala"></i>Kalapuikot <span class="n">1</span></button>
</div>
</div>
<div class="sheet">
<div class="grab"></div>
<h3>Lohikeitto</h3>
<p class="q">Lisukkeita?</p>
<div class="chips">
<button aria-pressed="true">Ruisleipä</button>
<button aria-pressed="false">Perunamuusi</button>
<button aria-pressed="false">Riisi</button>
<button aria-pressed="false">Vihersalaatti</button>
</div>
<button class="primary">Tallenna</button>
<button class="ghost">Peruuta</button>
</div>
</div>
<nav class="tabbar">
<a href="#" aria-current="page"><i></i>Kirjaa</a>
<a href="#"><i></i>Historia</a>
<a href="#"><i></i>Ruoat</a>
</nav>
</div>
</div>
<!-- ============ 2 · JO KIRJATTU ============ -->
<div class="framewrap" id="f2">
<p class="framelbl">2 · Päivä jo kirjattu <span>— yksi merkintä per päivä</span></p>
<div class="phone">
<div class="screen">
<div class="appbar">
<h2>Mitä syötiin?</h2>
<div class="dayseg">
<button aria-pressed="true">Tänään</button>
<button aria-pressed="false">Eilen</button>
<input type="date" value="2026-09-05" aria-label="Muu päivä">
</div>
</div>
<div class="pad">
<div class="logged">
<p class="k">Tänään kirjattu</p>
<p class="nm"><i class="dot d-kala"></i>Lohikeitto</p>
<p class="sd">Ruisleipä</p>
<div class="pair">
<button>Muokkaa</button>
<button class="del">Poista</button>
</div>
</div>
<p class="orlog">Väärä päivä? <button>Kirjaa toinen päivä</button></p>
</div>
</div>
<nav class="tabbar">
<a href="#" aria-current="page"><i></i>Kirjaa</a>
<a href="#"><i></i>Historia</a>
<a href="#"><i></i>Ruoat</a>
</nav>
</div>
</div>
<!-- ============ 3 · HISTORIA ============ -->
<div class="framewrap" id="f3">
<p class="framelbl">3 · Historia <span>— lista, ei kalenteria</span></p>
<div class="phone">
<div class="screen">
<div class="appbar">
<h2>Historia</h2>
</div>
<div class="pad">
<div class="monthrule">Syyskuu</div>
<div class="gapline"><time>pe 4.9.</time><span>Ei merkintää</span><button>Merkitse</button></div>
<div class="entry">
<time>to 3.9.</time>
<div><div class="nm"><i class="dot d-liha"></i>Lihapullat</div><div class="sd">Vihersalaatti</div></div>
<span class="chev"></span>
</div>
<div class="entry">
<time>ke 2.9.</time>
<div><div class="nm"><i class="dot d-kala"></i>Lohikeitto</div><div class="sd">Ruisleipä</div></div>
<span class="chev"></span>
</div>
<div class="entry">
<time>ti 1.9.</time>
<div><div class="nm"><i class="dot d-kana"></i>Kanacurry</div><div class="sd">Riisi</div></div>
<span class="chev"></span>
</div>
<div class="monthrule">Elokuu</div>
<div class="entry">
<time>ma 31.8.</time>
<div><div class="nm"><i class="dot d-liha"></i>Uunimakkara</div><div class="sd">Perunamuusi</div></div>
<span class="chev"></span>
</div>
<div class="entry">
<time>su 30.8.</time>
<div><div class="nm"><i class="dot d-kasvis"></i>Kasvispyörykät</div><div class="sd">Ei lisukkeita</div></div>
<span class="chev"></span>
</div>
<div class="entry">
<time>la 29.8.</time>
<div><div class="nm"><i class="dot d-liha"></i>Lihapullat</div><div class="sd">Perunamuusi</div></div>
<span class="chev"></span>
</div>
<div class="gapline"><time>pe 28.8.</time><span>Ei merkintää</span><button>Merkitse</button></div>
<div class="entry">
<time>to 27.8.</time>
<div><div class="nm"><i class="dot d-kana"></i>Kanacurry</div><div class="sd">Riisi</div></div>
<span class="chev"></span>
</div>
<div class="entry">
<time>ke 26.8.</time>
<div><div class="nm"><i class="dot d-liha"></i>Jauhelihakastike</div><div class="sd">Perunamuusi, vihersalaatti</div></div>
<span class="chev"></span>
</div>
</div>
</div>
<nav class="tabbar">
<a href="#"><i></i>Kirjaa</a>
<a href="#" aria-current="page"><i></i>Historia</a>
<a href="#"><i></i>Ruoat</a>
</nav>
</div>
</div>
</div>
</div>
<script>
const h=document.documentElement, tb=document.getElementById('t'), m=['auto','light','dark'];
tb.onclick=()=>{const n=m[(m.indexOf(h.dataset.theme)+1)%3];h.dataset.theme=n;tb.textContent='Teema: '+n;};
document.querySelectorAll('.switch button').forEach(b=>b.onclick=()=>{
document.querySelectorAll('.switch button').forEach(x=>x.setAttribute('aria-pressed',x===b));
document.querySelectorAll('.framewrap').forEach(f=>f.classList.toggle('on',f.id===b.dataset.v));
});
document.querySelectorAll('.chips button,.dayseg button').forEach(b=>b.onclick=()=>
b.setAttribute('aria-pressed', b.getAttribute('aria-pressed')!=='true'));
</script>
</body>
</html>
+440
View File
@@ -0,0 +1,440 @@
<!doctype html>
<html lang="en" data-theme="auto">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover">
<title>Foodster — stage 1 log · mobile layouts</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Archivo:wght@400;500;600;700&family=Martian+Mono:wght@300;500&display=swap" rel="stylesheet">
<style>
/* ============ shared skin ============ */
:root{
color-scheme: light dark;
--paper: light-dark(#ECEDE8, #121316);
--card: light-dark(#FFFFFF, #1C1E22);
--sunk: light-dark(#E3E4DE, #17181B);
--ink: light-dark(#14161A, #E9EAE5);
--muted: light-dark(#6B6F6A, #8B8F89);
--line: light-dark(#D5D6D0, #2C2F34);
--accent:light-dark(#15616D, #58C6D2);
--onacc: light-dark(#FFFFFF, #0C1417);
--meat: light-dark(#AF4230, #DE7561);
--chicken: light-dark(#B57E10, #DFA83B);
--fish: light-dark(#25688F, #63AAD8);
--vegetarian:light-dark(#457A3C, #82BE7A);
--tap: 48px; /* minimum touch target */
}
html[data-theme="light"]{color-scheme:only light;}
html[data-theme="dark"] {color-scheme:only dark;}
*{box-sizing:border-box; -webkit-tap-highlight-color:transparent;}
body{margin:0; background:var(--sunk); color:var(--ink);
font-family:Archivo,system-ui,sans-serif; font-size:16px; line-height:1.45;
-webkit-font-smoothing:antialiased;}
button,input{font-family:inherit;}
:focus-visible{outline:2.5px solid var(--accent); outline-offset:2px;}
@media (prefers-reduced-motion:reduce){*{transition:none!important;}}
.dot{width:9px; height:9px; border-radius:2px; flex:none; display:inline-block;}
.d-meat{background:var(--meat);} .d-chicken{background:var(--chicken);}
.d-fish{background:var(--fish);} .d-vegetarian{background:var(--vegetarian);}
.d-wild{background:conic-gradient(var(--meat) 0 25%,var(--chicken) 25% 50%,
var(--fish) 50% 75%,var(--vegetarian) 75% 100%);}
/* ============ comparison rig (desktop only) ============ */
.rig{padding:20px; max-width:1400px; margin:0 auto;}
.rigtop{display:flex; align-items:center; gap:14px; flex-wrap:wrap; margin-bottom:18px;}
.rigtop h1{font-size:17px; font-weight:700; letter-spacing:-.02em; margin:0;}
.rigtop p{margin:0; font-size:13px; color:var(--muted);}
.themebtn{font-size:12px; background:var(--card); border:1px solid var(--line);
color:var(--muted); padding:7px 12px; border-radius:7px; cursor:pointer; margin-left:auto;}
.switch{display:none; gap:6px; margin-bottom:14px; flex-wrap:wrap;}
.switch button{font-size:14px; font-weight:600; background:var(--card);
border:1px solid var(--line); color:var(--muted); padding:10px 14px;
border-radius:8px; cursor:pointer; flex:1;}
.switch button[aria-pressed="true"]{background:var(--accent); border-color:var(--accent); color:var(--onacc);}
.frames{display:flex; gap:22px; align-items:flex-start; overflow-x:auto; padding-bottom:14px;}
.framewrap{flex:none;}
.framelbl{font-size:13px; font-weight:600; margin:0 0 9px; letter-spacing:-.01em;}
.framelbl span{font-weight:400; color:var(--muted);}
.phone{width:390px; height:788px; background:var(--paper); border:1px solid var(--line);
border-radius:26px; overflow:hidden; display:flex; flex-direction:column;}
/* ============ in-app chrome ============ */
.screen{flex:1; overflow-y:auto; overscroll-behavior:contain;}
.appbar{position:sticky; top:0; z-index:5; background:var(--paper);
border-bottom:1px solid var(--line); padding:14px 16px 12px;}
.appbar h2{margin:0; font-size:22px; font-weight:700; letter-spacing:-.03em;}
.appbar .meta{font-size:12.5px; color:var(--muted); margin:2px 0 0;}
.pad{padding:16px;}
.tabbar{flex:none; display:flex; background:var(--card); border-top:1px solid var(--line);
padding-bottom:env(safe-area-inset-bottom);}
.tabbar a{flex:1; text-align:center; text-decoration:none; color:var(--muted);
font-size:11.5px; font-weight:600; padding:11px 0 13px; letter-spacing:.01em;}
.tabbar a[aria-current]{color:var(--accent);}
.tabbar i{display:block; font-style:normal; font-size:18px; line-height:1.3;}
.primary{display:block; width:100%; min-height:var(--tap); background:var(--accent);
color:var(--onacc); border:0; border-radius:11px; font-size:16.5px; font-weight:700;
cursor:pointer; letter-spacing:-.01em;}
.ghost{display:block; width:100%; min-height:var(--tap); background:none; color:var(--muted);
border:0; font-size:15px; cursor:pointer;}
/* ============ 1 · CALENDAR-LED ============ */
.mini{display:grid; grid-template-columns:repeat(7,1fr); gap:4px;}
.mini .wn{font-size:9.5px; letter-spacing:.06em; text-transform:uppercase;
color:var(--muted); text-align:center; padding-bottom:3px;}
.mini button{aspect-ratio:1; background:var(--card); border:1px solid var(--line);
border-radius:8px; cursor:pointer; display:flex; flex-direction:column;
align-items:center; justify-content:center; gap:4px; padding:0; color:var(--muted);
font-size:11px;}
.mini button .dot{width:11px; height:11px; border-radius:3px;}
.mini button.empty{background:none; border-style:dashed;}
.mini button.empty::after{content:"+"; font-size:14px; opacity:.45;}
.mini button.today{border:2px solid var(--accent); color:var(--accent); font-weight:700;}
.mini button.future{opacity:.3; border-color:transparent; background:none;}
.tally{display:flex; align-items:baseline; gap:8px; margin:14px 0 0; font-size:12.5px;
color:var(--muted);}
.tally b{color:var(--ink); font-size:14px;}
.bar{flex:1; height:5px; border-radius:3px; background:var(--line); overflow:hidden;}
.bar i{display:block; height:100%; background:var(--accent);}
.tonight{background:var(--card); border:1px solid var(--line); border-radius:13px;
padding:16px; margin:18px 0 0;}
.tonight .k{font-family:"Martian Mono",monospace; font-size:10px; letter-spacing:.08em;
text-transform:uppercase; color:var(--muted); margin:0 0 9px;}
.dayrow{display:flex; gap:12px; align-items:center; padding:13px 0;
border-bottom:1px solid var(--line); min-height:var(--tap);}
.dayrow time{font-family:"Martian Mono",monospace; font-size:11px; color:var(--muted);
min-width:56px; flex:none;}
.dayrow .nm{font-size:16px; font-weight:600; letter-spacing:-.02em;}
.dayrow .sd{font-size:12.5px; color:var(--muted);}
.dayrow .chev{margin-left:auto; color:var(--muted); font-size:18px;}
.sechead{font-family:"Martian Mono",monospace; font-size:10px; letter-spacing:.09em;
text-transform:uppercase; color:var(--muted); margin:24px 0 4px;}
/* ============ 2 · TAP BOARD ============ */
.dayseg{display:flex; gap:6px; margin-top:11px;}
.dayseg button{flex:1; min-height:42px; font-size:14px; font-weight:600; background:var(--card);
border:1px solid var(--line); color:var(--ink); border-radius:9px; cursor:pointer;}
.dayseg button[aria-pressed="true"]{background:var(--ink); border-color:var(--ink); color:var(--paper);}
.dayseg input{flex:none; width:52px; font-size:16px; background:var(--card); color:var(--ink);
border:1px solid var(--line); border-radius:9px; padding:0 8px;}
.filter{width:100%; min-height:var(--tap); font-size:16px; background:var(--card);
color:var(--ink); border:1px solid var(--line); border-radius:10px;
padding:0 14px; margin-bottom:14px;}
.board{display:flex; flex-wrap:wrap; gap:8px;}
.pill{display:flex; align-items:center; gap:8px; background:var(--card);
border:1px solid var(--line); border-radius:11px; cursor:pointer; color:var(--ink);
font-weight:600; letter-spacing:-.022em; min-height:var(--tap);}
.pill .n{font-family:"Martian Mono",monospace; font-weight:300; color:var(--muted); font-size:10px;}
.pill.xl{font-size:22px; padding:14px 18px; flex:1 1 100%;}
.pill.lg{font-size:18px; padding:12px 16px;}
.pill.md{font-size:15.5px; padding:11px 14px;}
.pill.sm{font-size:14px; padding:10px 13px; color:var(--muted);}
.pill.picked{background:var(--accent); border-color:var(--accent); color:var(--onacc);}
.pill.picked .n{color:var(--onacc); opacity:.65;}
.sheetwrap{position:relative;}
.sheet{position:absolute; left:0; right:0; bottom:0; z-index:9; background:var(--card);
border-top:1px solid var(--line); border-radius:18px 18px 0 0; padding:8px 16px 16px;
box-shadow:0 -10px 30px -14px rgba(0,0,0,.4);}
.grab{width:38px; height:4px; border-radius:3px; background:var(--line); margin:0 auto 13px;}
.sheet h3{margin:0 0 2px; font-size:19px; letter-spacing:-.025em;}
.sheet .q{margin:0 0 12px; font-size:13px; color:var(--muted);}
.chips{display:flex; gap:7px; flex-wrap:wrap; margin-bottom:14px;}
.chips button{font-size:14.5px; min-height:42px; background:var(--sunk);
border:1px solid var(--line); color:var(--ink); padding:0 14px;
border-radius:999px; cursor:pointer;}
.chips button[aria-pressed="true"]{background:var(--accent); border-color:var(--accent); color:var(--onacc);}
/* ============ 3 · JOURNAL ============ */
.jtonight{background:var(--accent); color:var(--onacc); border-radius:14px;
padding:16px; margin-bottom:20px;}
.jtonight .k{font-family:"Martian Mono",monospace; font-size:10px; letter-spacing:.09em;
text-transform:uppercase; opacity:.75; margin:0 0 5px;}
.jtonight p{margin:0 0 13px; font-size:19px; font-weight:600; letter-spacing:-.025em;}
.jtonight button{width:100%; min-height:var(--tap); background:var(--onacc); color:var(--accent);
border:0; border-radius:10px; font-size:16px; font-weight:700; cursor:pointer;}
.entry{display:flex; gap:12px; align-items:center; padding:14px 0;
border-bottom:1px solid var(--line); min-height:var(--tap);}
.entry time{font-family:"Martian Mono",monospace; font-size:11px; color:var(--muted);
min-width:52px; flex:none;}
.entry .nm{font-size:17px; font-weight:600; letter-spacing:-.025em;
display:flex; align-items:center; gap:8px;}
.entry .sd{font-size:12.5px; color:var(--muted); margin-top:1px;}
.entry .chev{margin-left:auto; color:var(--muted); font-size:19px; flex:none;}
.gapline{display:flex; gap:12px; align-items:center; padding:12px 0;
border-bottom:1px dashed var(--line); font-size:13.5px; color:var(--muted);
min-height:var(--tap);}
.gapline time{font-family:"Martian Mono",monospace; font-size:11px; min-width:52px; flex:none;}
.gapline button{margin-left:auto; font-size:13.5px; background:none; border:0;
color:var(--accent); font-weight:600; cursor:pointer; min-height:38px; padding:0 4px;}
.monthrule{font-family:"Martian Mono",monospace; font-size:10px; letter-spacing:.12em;
text-transform:uppercase; color:var(--muted); padding:22px 0 5px;}
/* ============ real phone: drop the rig, go full bleed ============ */
@media (max-width:900px){
body{background:var(--paper);}
.rig{padding:0;}
.rigtop{padding:12px 16px 0; margin-bottom:10px;}
.switch{display:flex; padding:0 16px;}
.frames{display:block; overflow:visible; padding:0;}
.framewrap{display:none;}
.framewrap.on{display:block;}
.framelbl{display:none;}
.phone{width:auto; height:calc(100svh - 118px); border:0; border-radius:0;}
}
</style>
</head>
<body>
<div class="rig">
<div class="rigtop">
<h1>Foodster · stage 1 log</h1>
<p>390&nbsp;px. Three structures, one skin.</p>
<button class="themebtn" id="t">Theme: auto</button>
</div>
<div class="switch">
<button aria-pressed="true" data-v="f1">Calendar</button>
<button aria-pressed="false" data-v="f2">Tap board</button>
<button aria-pressed="false" data-v="f3">Journal</button>
</div>
<div class="frames">
<!-- ================= 1 · CALENDAR-LED ================= -->
<div class="framewrap on" id="f1">
<p class="framelbl">1 · Calendar-led <span>— history first, calendar navigates</span></p>
<div class="phone">
<div class="screen">
<div class="appbar">
<h2>What we ate</h2>
<p class="meta">Last five weeks · 3 Aug 6 Sep</p>
</div>
<div class="pad">
<div class="mini" aria-hidden="true">
<div class="wn">M</div><div class="wn">T</div><div class="wn">W</div><div class="wn">T</div>
<div class="wn">F</div><div class="wn">S</div><div class="wn">S</div>
</div>
<div class="mini">
<button>3<i class="dot d-meat"></i></button>
<button>4<i class="dot d-fish"></i></button>
<button class="empty">5</button>
<button>6<i class="dot d-meat"></i></button>
<button>7<i class="dot d-wild"></i></button>
<button>8<i class="dot d-vegetarian"></i></button>
<button>9<i class="dot d-fish"></i></button>
<button>10<i class="dot d-chicken"></i></button>
<button>11<i class="dot d-vegetarian"></i></button>
<button>12<i class="dot d-meat"></i></button>
<button class="empty">13</button>
<button>14<i class="dot d-chicken"></i></button>
<button>15<i class="dot d-meat"></i></button>
<button>16<i class="dot d-fish"></i></button>
<button>17<i class="dot d-meat"></i></button>
<button>18<i class="dot d-vegetarian"></i></button>
<button>19<i class="dot d-meat"></i></button>
<button class="empty">20</button>
<button>21<i class="dot d-wild"></i></button>
<button>22<i class="dot d-chicken"></i></button>
<button>23<i class="dot d-meat"></i></button>
<button>24<i class="dot d-fish"></i></button>
<button>25<i class="dot d-vegetarian"></i></button>
<button>26<i class="dot d-meat"></i></button>
<button>27<i class="dot d-chicken"></i></button>
<button class="empty">28</button>
<button>29<i class="dot d-meat"></i></button>
<button>30<i class="dot d-vegetarian"></i></button>
<button>31<i class="dot d-meat"></i></button>
<button>1<i class="dot d-chicken"></i></button>
<button>2<i class="dot d-fish"></i></button>
<button>3<i class="dot d-meat"></i></button>
<button class="empty">4</button>
<button class="today">5</button>
<button class="future">6</button>
</div>
<div class="tally">
<b>30</b><span>of 35 logged</span>
<span class="bar"><i style="width:86%"></i></span>
</div>
<div class="tonight">
<p class="k">Tonight · Sat 5 Sep</p>
<button class="primary">Log tonight's dinner</button>
</div>
<p class="sechead">Recent</p>
<div class="dayrow">
<time>Thu 3</time>
<div><div class="nm">Lihapullat</div><div class="sd">Vihersalaatti</div></div>
<span class="chev"></span>
</div>
<div class="dayrow">
<time>Wed 2</time>
<div><div class="nm">Lohikeitto</div><div class="sd">Ruisleipä</div></div>
<span class="chev"></span>
</div>
<div class="dayrow">
<time>Tue 1</time>
<div><div class="nm">Kanacurry</div><div class="sd">Riisi</div></div>
<span class="chev"></span>
</div>
</div>
</div>
<nav class="tabbar">
<a href="#" aria-current="page"><i></i>Log</a>
<a href="#"><i></i>History</a>
<a href="#"><i></i>Catalog</a>
</nav>
</div>
</div>
<!-- ================= 2 · TAP BOARD ================= -->
<div class="framewrap" id="f2">
<p class="framelbl">2 · Tap board <span>— fastest entry, sheet in the thumb zone</span></p>
<div class="phone">
<div class="screen sheetwrap">
<div class="appbar">
<h2>Tap what you ate</h2>
<div class="dayseg">
<button aria-pressed="true">Today</button>
<button aria-pressed="false">Yesterday</button>
<input type="date" value="2026-09-05" aria-label="Another date">
</div>
</div>
<div class="pad" style="padding-bottom:270px">
<input class="filter" type="search" placeholder="Filter dishes…" aria-label="Filter dishes">
<div class="board">
<button class="pill xl"><i class="dot d-meat"></i>Lihapullat <span class="n">12</span></button>
<button class="pill xl"><i class="dot d-meat"></i>Jauhelihakastike <span class="n">11</span></button>
<button class="pill lg picked"><i class="dot d-fish"></i>Lohikeitto <span class="n">9</span></button>
<button class="pill lg"><i class="dot d-chicken"></i>Kanacurry <span class="n">8</span></button>
<button class="pill lg"><i class="dot d-meat"></i>Makaronilaatikko <span class="n">7</span></button>
<button class="pill lg"><i class="dot d-chicken"></i>Broilerikastike <span class="n">6</span></button>
<button class="pill md"><i class="dot d-fish"></i>Uunilohi <span class="n">5</span></button>
<button class="pill md"><i class="dot d-vegetarian"></i>Kasvispyörykät <span class="n">5</span></button>
<button class="pill md"><i class="dot d-wild"></i>Tortillat <span class="n">4</span></button>
<button class="pill md"><i class="dot d-vegetarian"></i>Hernekeitto <span class="n">4</span></button>
<button class="pill md"><i class="dot d-meat"></i>Uunimakkara <span class="n">3</span></button>
<button class="pill sm"><i class="dot d-meat"></i>Kaalilaatikko <span class="n">2</span></button>
<button class="pill sm"><i class="dot d-wild"></i>Kotipizza <span class="n">2</span></button>
<button class="pill sm"><i class="dot d-fish"></i>Kalapuikot <span class="n">1</span></button>
</div>
</div>
<div class="sheet">
<div class="grab"></div>
<h3>Lohikeitto</h3>
<p class="q">Anything on the side?</p>
<div class="chips">
<button aria-pressed="true">Ruisleipä</button>
<button aria-pressed="false">Perunamuusi</button>
<button aria-pressed="false">Riisi</button>
<button aria-pressed="false">Vihersalaatti</button>
</div>
<button class="primary">Save Saturday</button>
<button class="ghost">Cancel</button>
</div>
</div>
<nav class="tabbar">
<a href="#" aria-current="page"><i></i>Log</a>
<a href="#"><i></i>History</a>
<a href="#"><i></i>Catalog</a>
</nav>
</div>
</div>
<!-- ================= 3 · JOURNAL ================= -->
<div class="framewrap" id="f3">
<p class="framelbl">3 · Journal <span>— one column, gaps called out inline</span></p>
<div class="phone">
<div class="screen">
<div class="appbar">
<h2>Dinner journal</h2>
<p class="meta">One line per night</p>
</div>
<div class="pad">
<div class="jtonight">
<p class="k">Tonight · Sat 5 Sep</p>
<p>Nothing written down yet.</p>
<button>What did we eat?</button>
</div>
<div class="monthrule">September</div>
<div class="gapline"><time>Fri 4</time><span>Not logged</span><button>Fill in</button></div>
<div class="entry">
<time>Thu 3</time>
<div><div class="nm"><i class="dot d-meat"></i>Lihapullat</div><div class="sd">Vihersalaatti</div></div>
<span class="chev"></span>
</div>
<div class="entry">
<time>Wed 2</time>
<div><div class="nm"><i class="dot d-fish"></i>Lohikeitto</div><div class="sd">Ruisleipä</div></div>
<span class="chev"></span>
</div>
<div class="entry">
<time>Tue 1</time>
<div><div class="nm"><i class="dot d-chicken"></i>Kanacurry</div><div class="sd">Riisi</div></div>
<span class="chev"></span>
</div>
<div class="monthrule">August</div>
<div class="entry">
<time>Mon 31</time>
<div><div class="nm"><i class="dot d-meat"></i>Uunimakkara</div><div class="sd">Perunamuusi</div></div>
<span class="chev"></span>
</div>
<div class="entry">
<time>Sun 30</time>
<div><div class="nm"><i class="dot d-vegetarian"></i>Kasvispyörykät</div><div class="sd">Served on its own</div></div>
<span class="chev"></span>
</div>
<div class="entry">
<time>Sat 29</time>
<div><div class="nm"><i class="dot d-meat"></i>Lihapullat</div><div class="sd">Perunamuusi</div></div>
<span class="chev"></span>
</div>
<div class="gapline"><time>Fri 28</time><span>Not logged</span><button>Fill in</button></div>
<div class="entry">
<time>Thu 27</time>
<div><div class="nm"><i class="dot d-chicken"></i>Kanacurry</div><div class="sd">Riisi</div></div>
<span class="chev"></span>
</div>
</div>
</div>
<nav class="tabbar">
<a href="#" aria-current="page"><i></i>Log</a>
<a href="#"><i></i>History</a>
<a href="#"><i></i>Catalog</a>
</nav>
</div>
</div>
</div>
</div>
<script>
const h=document.documentElement, tb=document.getElementById('t'), m=['auto','light','dark'];
tb.onclick=()=>{const n=m[(m.indexOf(h.dataset.theme)+1)%3];h.dataset.theme=n;tb.textContent='Theme: '+n;};
document.querySelectorAll('.switch button').forEach(b=>b.onclick=()=>{
document.querySelectorAll('.switch button').forEach(x=>x.setAttribute('aria-pressed',x===b));
document.querySelectorAll('.framewrap').forEach(f=>f.classList.toggle('on',f.id===b.dataset.v));
});
document.querySelectorAll('.chips button,.dayseg button').forEach(b=>b.onclick=()=>
b.setAttribute('aria-pressed', b.getAttribute('aria-pressed')!=='true'));
</script>
</body>
</html>