Files
foodster/mockups/b-enamel-tile.html
Esa Kataja 6ed047aafd 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.
2026-09-05 17:35:37 +03:00

215 lines
9.2 KiB
HTML
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!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>