Add a favicon, home-screen icons and a web manifest

The favicon is a bowl of soup: this household's catalog is half keitto, so it
is at least honest. favicon.svg follows the system theme in the tab strip.

Home-screen icons cannot do the same. They must be opaque and must not change
with the theme, so assets/icon.svg is a separate fixed-colour source, with the
artwork inside the central 80% for Android to mask to any launcher shape. The
same 512 is declared maskable in the manifest.

`make icons` rasterises with rsvg-convert and compresses with
`oxipng -o max --zopfli`, which beat `optipng -o7` at every size — 5860 bytes
against 6109 for the three files. Plain `oxipng -o max` was not an upgrade: it
won the two small icons and lost the 512, ending up larger overall. All output
verified pixel-identical to the rasterizer. The PNGs are committed so the
build needs no rasterizer.

Two things that only fail on a real device, so both are covered by smoke
checks: Go has no mime entry for .webmanifest and served it as octet-stream,
which browsers ignore; and a manifest fetch carries no credentials by default,
so behind Basic auth it needs crossorigin="use-credentials" or it 401s.
This commit is contained in:
Esa Kataja
2026-09-05 18:58:42 +03:00
parent d15f741ab1
commit 0538d61ba6
16 changed files with 283 additions and 4 deletions
+118
View File
@@ -0,0 +1,118 @@
<!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 — favicon options</title>
<link rel="icon" href="a-kulho.svg" type="image/svg+xml">
<style>
:root{
color-scheme: light dark;
--paper: light-dark(#ECEDE8, #121316);
--card: light-dark(#FFFFFF, #1C1E22);
--ink: light-dark(#14161A, #E9EAE5);
--muted: light-dark(#6B6F6A, #8B8F89);
--line: light-dark(#D5D6D0, #2C2F34);
}
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(--paper);color:var(--ink);
font-family:system-ui,sans-serif;font-size:16px;line-height:1.45;}
.wrap{max-width:900px;margin:0 auto;padding:24px 20px 60px;}
h1{font-size:20px;letter-spacing:-.02em;margin:0 0 4px;}
.lede{margin:0 0 22px;color:var(--muted);font-size:14px;}
.themebtn{font:inherit;font-size:13px;background:var(--card);border:1px solid var(--line);
color:var(--muted);padding:7px 12px;border-radius:7px;cursor:pointer;margin-bottom:22px;}
.opt{background:var(--card);border:1px solid var(--line);border-radius:12px;
padding:18px;margin-bottom:14px;}
.opt h2{font-size:16px;margin:0 0 2px;letter-spacing:-.02em;}
.opt p{margin:0 0 14px;font-size:13.5px;color:var(--muted);}
.sizes{display:flex;align-items:flex-end;gap:22px;flex-wrap:wrap;}
.size{text-align:center;}
.size img{display:block;}
.size span{display:block;margin-top:6px;font-size:10px;letter-spacing:.06em;
text-transform:uppercase;color:var(--muted);}
/* A browser tab is the real test: 16px against the tab strip. */
.tabsim{margin-top:16px;display:flex;gap:10px;flex-wrap:wrap;}
.tab{display:flex;align-items:center;gap:7px;border-radius:8px 8px 0 0;
padding:7px 14px 7px 10px;font-size:12.5px;}
.tab img{width:16px;height:16px;}
.onwhite{background:#fff;color:#333;border:1px solid #ddd;}
.ondark{background:#1b1d22;color:#bbb;border:1px solid #33363c;}
</style>
</head>
<body>
<div class="wrap">
<h1>Foodster — favicon, round two</h1>
<p class="lede">Objects from the kitchen rather than geometry. The 16&nbsp;px row and the tab strips are the only test that counts.</p>
<button class="themebtn" id="t">Theme: auto</button>
<div class="opt">
<h2>A · Kulho</h2>
<p>A bowl with steam. This household's catalog is half <em>keitto</em>, so soup is honest branding.</p>
<div class="sizes">
<div class="size"><img src="a-kulho.svg" width="16" height="16"><span>16</span></div>
<div class="size"><img src="a-kulho.svg" width="32" height="32"><span>32</span></div>
<div class="size"><img src="a-kulho.svg" width="64" height="64"><span>64</span></div>
<div class="size"><img src="a-kulho.svg" width="180" height="180"><span>180</span></div>
</div>
<div class="tabsim">
<span class="tab onwhite"><img src="a-kulho.svg" alt="">Foodster</span>
<span class="tab ondark"><img src="a-kulho.svg" alt="">Foodster</span>
</div>
</div>
<div class="opt">
<h2>B · Kattila</h2>
<p>A pot with the lid on and handles out. About cooking rather than eating.</p>
<div class="sizes">
<div class="size"><img src="b-kattila.svg" width="16" height="16"><span>16</span></div>
<div class="size"><img src="b-kattila.svg" width="32" height="32"><span>32</span></div>
<div class="size"><img src="b-kattila.svg" width="64" height="64"><span>64</span></div>
<div class="size"><img src="b-kattila.svg" width="180" height="180"><span>180</span></div>
</div>
<div class="tabsim">
<span class="tab onwhite"><img src="b-kattila.svg" alt="">Foodster</span>
<span class="tab ondark"><img src="b-kattila.svg" alt="">Foodster</span>
</div>
</div>
<div class="opt">
<h2>C · Lusikka</h2>
<p>One spoon on the diagonal. A fork is the cliché; a lone spoon is not, and it stays one solid shape at any size.</p>
<div class="sizes">
<div class="size"><img src="c-lusikka.svg" width="16" height="16"><span>16</span></div>
<div class="size"><img src="c-lusikka.svg" width="32" height="32"><span>32</span></div>
<div class="size"><img src="c-lusikka.svg" width="64" height="64"><span>64</span></div>
<div class="size"><img src="c-lusikka.svg" width="180" height="180"><span>180</span></div>
</div>
<div class="tabsim">
<span class="tab onwhite"><img src="c-lusikka.svg" alt="">Foodster</span>
<span class="tab ondark"><img src="c-lusikka.svg" alt="">Foodster</span>
</div>
</div>
<div class="opt">
<h2>D · Ruisleipä</h2>
<p>The rye loaf with its hole. Nothing else in a tab strip looks like this — which is the appeal and also the risk of reading as a ring.</p>
<div class="sizes">
<div class="size"><img src="d-ruisleipa.svg" width="16" height="16"><span>16</span></div>
<div class="size"><img src="d-ruisleipa.svg" width="32" height="32"><span>32</span></div>
<div class="size"><img src="d-ruisleipa.svg" width="64" height="64"><span>64</span></div>
<div class="size"><img src="d-ruisleipa.svg" width="180" height="180"><span>180</span></div>
</div>
<div class="tabsim">
<span class="tab onwhite"><img src="d-ruisleipa.svg" alt="">Foodster</span>
<span class="tab ondark"><img src="d-ruisleipa.svg" alt="">Foodster</span>
</div>
</div>
</div>
<script>
const h=document.documentElement,b=document.getElementById('t'),m=['auto','light','dark'];
b.onclick=()=>{const n=m[(m.indexOf(h.dataset.theme)+1)%3];h.dataset.theme=n;b.textContent='Theme: '+n;};
</script>
</body>
</html>