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,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>
|
||||
Reference in New Issue
Block a user