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.
215 lines
9.2 KiB
HTML
215 lines
9.2 KiB
HTML
<!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>
|