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
+5
View File
@@ -15,6 +15,7 @@ import (
"flag"
"fmt"
"log"
"mime"
"net/http"
"os"
"os/signal"
@@ -140,6 +141,10 @@ func openDB(path string) (*sql.DB, error) {
}
func routes(db *sql.DB, loc *time.Location, password string) http.Handler {
// Go's mime table has no entry for .webmanifest, and a manifest served as
// octet-stream is ignored by the browser.
_ = mime.AddExtensionType(".webmanifest", "application/manifest+json")
a := &app{db: db, loc: loc}
mux := http.NewServeMux()
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

+16
View File
@@ -0,0 +1,16 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32" role="img" aria-label="Foodster">
<!-- Kulho: a bowl with steam. Finnish dinner is soup more often than not,
and this household's own catalog is half keitto. -->
<style>
svg { color: #15616D; }
@media (prefers-color-scheme: dark) { svg { color: #58C6D2; } }
</style>
<g fill="currentColor">
<rect x="1.5" y="12" width="29" height="3.2" rx="1.6"/>
<path d="M3.5 15.2 h25 a12.5 12.5 0 0 1 -25 0 z"/>
</g>
<g fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="round">
<path d="M11.5 9 c0 -2.1 2.1 -2.6 2.1 -4.6"/>
<path d="M19 9 c0 -2.1 2.1 -2.6 2.1 -4.6"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 688 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

+17
View File
@@ -0,0 +1,17 @@
{
"id": "/",
"name": "Foodster",
"short_name": "Foodster",
"lang": "fi",
"dir": "ltr",
"start_url": "/",
"scope": "/",
"display": "standalone",
"background_color": "#ECEDE8",
"theme_color": "#15616D",
"icons": [
{ "src": "/static/icon-192.png", "sizes": "192x192", "type": "image/png" },
{ "src": "/static/icon-512.png", "sizes": "512x512", "type": "image/png" },
{ "src": "/static/icon-512.png", "sizes": "512x512", "type": "image/png", "purpose": "maskable" }
]
}
+7
View File
@@ -76,7 +76,14 @@ templ page(title, current string) {
<meta charset="utf-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover"/>
<meta name="color-scheme" content="light dark"/>
<meta name="theme-color" media="(prefers-color-scheme: light)" content="#ECEDE8"/>
<meta name="theme-color" media="(prefers-color-scheme: dark)" content="#121316"/>
<title>{ title }</title>
<link rel="icon" href="/static/favicon.svg" type="image/svg+xml"/>
<link rel="apple-touch-icon" href="/static/apple-touch-icon.png"/>
<!-- use-credentials: the manifest is fetched behind Basic auth and
would otherwise come back 401 and be ignored. -->
<link rel="manifest" href="/static/manifest.webmanifest" crossorigin="use-credentials"/>
<link rel="stylesheet" href="/static/app.css"/>
<script type="module" src="/static/datastar.js"></script>
</head>