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:
@@ -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­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­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­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­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>
|
||||
Reference in New Issue
Block a user