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.
@@ -3,6 +3,7 @@
|
|||||||
COMPOSE ?= podman compose
|
COMPOSE ?= podman compose
|
||||||
BIN := foodster
|
BIN := foodster
|
||||||
PKG := ./cmd/foodster
|
PKG := ./cmd/foodster
|
||||||
|
STATIC := cmd/foodster/static
|
||||||
|
|
||||||
# Vendored Datastar client. Bump, run `make vendor`, commit the result.
|
# Vendored Datastar client. Bump, run `make vendor`, commit the result.
|
||||||
DATASTAR_VERSION ?= v1.0.3
|
DATASTAR_VERSION ?= v1.0.3
|
||||||
@@ -18,7 +19,7 @@ endif
|
|||||||
GOFILES = $(shell find . -name '*.go' -not -name '*_templ.go' 2>/dev/null)
|
GOFILES = $(shell find . -name '*.go' -not -name '*_templ.go' 2>/dev/null)
|
||||||
|
|
||||||
.DEFAULT_GOAL := help
|
.DEFAULT_GOAL := help
|
||||||
.PHONY: help generate build run seed test smoke check lint fix vendor image push release up down logs clean
|
.PHONY: help generate build run seed test smoke check lint fix icons vendor image push release up down logs clean
|
||||||
|
|
||||||
help: ## Show this help
|
help: ## Show this help
|
||||||
@grep -hE '^[a-zA-Z_-]+:.*## ' $(MAKEFILE_LIST) \
|
@grep -hE '^[a-zA-Z_-]+:.*## ' $(MAKEFILE_LIST) \
|
||||||
@@ -51,10 +52,20 @@ check: ## Everything that must pass before a commit
|
|||||||
@$(MAKE) --no-print-directory smoke
|
@$(MAKE) --no-print-directory smoke
|
||||||
@echo "check: all passed"
|
@echo "check: all passed"
|
||||||
|
|
||||||
|
icons: ## Rasterise home-screen PNGs from assets/icon.svg and optimise them
|
||||||
|
rsvg-convert -w 180 -h 180 assets/icon.svg -o $(STATIC)/apple-touch-icon.png
|
||||||
|
rsvg-convert -w 192 -h 192 assets/icon.svg -o $(STATIC)/icon-192.png
|
||||||
|
rsvg-convert -w 512 -h 512 assets/icon.svg -o $(STATIC)/icon-512.png
|
||||||
|
# oxipng -o max alone loses to optipng on the 512; --zopfli wins at every
|
||||||
|
# size. Slow, but these are three tiny files built by hand.
|
||||||
|
oxipng -o max --zopfli --quiet \
|
||||||
|
$(STATIC)/apple-touch-icon.png $(STATIC)/icon-192.png $(STATIC)/icon-512.png
|
||||||
|
@ls -l $(STATIC)/*.png
|
||||||
|
|
||||||
vendor: ## Re-download the Datastar client (DATASTAR_VERSION=v1.0.3)
|
vendor: ## Re-download the Datastar client (DATASTAR_VERSION=v1.0.3)
|
||||||
curl -sSfL -o cmd/foodster/static/datastar.js \
|
curl -sSfL -o $(STATIC)/datastar.js \
|
||||||
"https://cdn.jsdelivr.net/gh/starfederation/datastar@$(DATASTAR_VERSION)/bundles/datastar.js"
|
"https://cdn.jsdelivr.net/gh/starfederation/datastar@$(DATASTAR_VERSION)/bundles/datastar.js"
|
||||||
@head -1 cmd/foodster/static/datastar.js
|
@head -1 $(STATIC)/datastar.js
|
||||||
|
|
||||||
lint: generate ## go vet, gofmt check, golangci-lint when installed
|
lint: generate ## go vet, gofmt check, golangci-lint when installed
|
||||||
go vet ./...
|
go vet ./...
|
||||||
|
|||||||
@@ -27,8 +27,12 @@ Working:
|
|||||||
|
|
||||||
Still to build:
|
Still to build:
|
||||||
|
|
||||||
|
- **UI polish.** Checkbox chips hide the native control and signal state only
|
||||||
|
through background colour, so "Tarjoillaan lisukkeiden kanssa" gives no
|
||||||
|
clear read of on versus off. Show a real checkbox. Same applies to the
|
||||||
|
category and side chips.
|
||||||
- Light / dark theme switch, saved per device (PRD §7.4).
|
- Light / dark theme switch, saved per device (PRD §7.4).
|
||||||
- A proper app header and a `favicon.svg`.
|
- A proper app header.
|
||||||
- Stage 2: the seven-meal suggester, which starts once there is history to
|
- Stage 2: the seven-meal suggester, which starts once there is history to
|
||||||
weight against.
|
weight against.
|
||||||
|
|
||||||
@@ -102,6 +106,21 @@ a scratch database:
|
|||||||
make seed # or: SEED=seeds/other.json make seed
|
make seed # or: SEED=seeds/other.json make seed
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Icons
|
||||||
|
|
||||||
|
`cmd/foodster/static/favicon.svg` is the browser-tab icon and follows the
|
||||||
|
system theme. The home-screen icons are PNGs, because a home-screen icon
|
||||||
|
cannot be transparent and must not change with the theme; they are rasterised
|
||||||
|
from `assets/icon.svg`, which is opaque and keeps the artwork inside the
|
||||||
|
central 80% so Android can mask it to any shape.
|
||||||
|
|
||||||
|
```sh
|
||||||
|
make icons # rsvg-convert, then optipng -o7
|
||||||
|
```
|
||||||
|
|
||||||
|
The PNGs are committed so the build needs no rasterizer. Re-run `make icons`
|
||||||
|
after editing `assets/icon.svg`.
|
||||||
|
|
||||||
## Migrations
|
## Migrations
|
||||||
|
|
||||||
Numbered SQL files in `cmd/foodster/migrations`, embedded in the binary and
|
Numbered SQL files in `cmd/foodster/migrations`, embedded in the binary and
|
||||||
|
|||||||
@@ -0,0 +1,19 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" width="512" height="512"
|
||||||
|
role="img" aria-label="Foodster">
|
||||||
|
<!-- Source for the home-screen PNGs; `make icons` rasterises it.
|
||||||
|
Unlike favicon.svg this one is opaque and fixed-colour: a home screen
|
||||||
|
icon cannot be transparent and must not change with the system theme.
|
||||||
|
The bowl sits inside the central 80% so Android can mask it to any
|
||||||
|
shape without clipping the artwork. -->
|
||||||
|
<rect width="512" height="512" fill="#15616D"/>
|
||||||
|
<g transform="translate(112 112) scale(9)">
|
||||||
|
<g fill="#ECEDE8">
|
||||||
|
<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="#ECEDE8" 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>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 918 B |
@@ -15,6 +15,7 @@ import (
|
|||||||
"flag"
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
|
"mime"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"os/signal"
|
"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 {
|
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}
|
a := &app{db: db, loc: loc}
|
||||||
|
|
||||||
mux := http.NewServeMux()
|
mux := http.NewServeMux()
|
||||||
|
|||||||
|
After Width: | Height: | Size: 1.3 KiB |
@@ -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 |
|
After Width: | Height: | Size: 1.4 KiB |
|
After Width: | Height: | Size: 3.0 KiB |
@@ -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" }
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -76,7 +76,14 @@ templ page(title, current string) {
|
|||||||
<meta charset="utf-8"/>
|
<meta charset="utf-8"/>
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover"/>
|
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover"/>
|
||||||
<meta name="color-scheme" content="light dark"/>
|
<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>
|
<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"/>
|
<link rel="stylesheet" href="/static/app.css"/>
|
||||||
<script type="module" src="/static/datastar.js"></script>
|
<script type="module" src="/static/datastar.js"></script>
|
||||||
</head>
|
</head>
|
||||||
|
|||||||
@@ -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 |
@@ -0,0 +1,14 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32" role="img" aria-label="Foodster">
|
||||||
|
<!-- Kattila: a cooking pot, lid on, handles out. Cooking rather than eating. -->
|
||||||
|
<style>
|
||||||
|
svg { color: #15616D; }
|
||||||
|
@media (prefers-color-scheme: dark) { svg { color: #58C6D2; } }
|
||||||
|
</style>
|
||||||
|
<g fill="currentColor">
|
||||||
|
<circle cx="16" cy="6" r="2.5"/>
|
||||||
|
<rect x="2" y="9" width="28" height="3.4" rx="1.7"/>
|
||||||
|
<rect x="0" y="14" width="5.5" height="3.2" rx="1.6"/>
|
||||||
|
<rect x="26.5" y="14" width="5.5" height="3.2" rx="1.6"/>
|
||||||
|
<path d="M5 13.6 h22 v6.9 a5.5 5.5 0 0 1 -5.5 5.5 h-11 a5.5 5.5 0 0 1 -5.5 -5.5 z"/>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 639 B |
@@ -0,0 +1,12 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32" role="img" aria-label="Foodster">
|
||||||
|
<!-- Lusikka: one spoon, set on the diagonal. A fork is the cliché; a spoon
|
||||||
|
on its own is not, and it stays a single solid shape at any size. -->
|
||||||
|
<style>
|
||||||
|
svg { color: #15616D; }
|
||||||
|
@media (prefers-color-scheme: dark) { svg { color: #58C6D2; } }
|
||||||
|
</style>
|
||||||
|
<g fill="currentColor" transform="rotate(34 16 16)">
|
||||||
|
<ellipse cx="16" cy="9.5" rx="5.4" ry="7"/>
|
||||||
|
<rect x="13.9" y="15" width="4.2" height="14" rx="2.1"/>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 545 B |
@@ -0,0 +1,14 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32" role="img" aria-label="Foodster">
|
||||||
|
<!-- Ruisleipä: the Finnish rye loaf, hole in the middle. Nothing else in a
|
||||||
|
tab strip looks like this — which is the point, and also the risk. -->
|
||||||
|
<style>
|
||||||
|
.hole { fill: #ECEDE8; }
|
||||||
|
.loaf { fill: #6B4636; }
|
||||||
|
@media (prefers-color-scheme: dark) {
|
||||||
|
.hole { fill: #121316; }
|
||||||
|
.loaf { fill: #9A6E52; }
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<circle class="loaf" cx="16" cy="16" r="14"/>
|
||||||
|
<circle class="hole" cx="16" cy="16" r="3.8"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 546 B |
@@ -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 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>
|
||||||
@@ -54,6 +54,17 @@ check "healthz needs no password" \
|
|||||||
check "datastar client is served" \
|
check "datastar client is served" \
|
||||||
"$(curl -s -o /dev/null -w '%{http_code}' -u ":$pass" "http://$addr/static/datastar.js")" "200"
|
"$(curl -s -o /dev/null -w '%{http_code}' -u ":$pass" "http://$addr/static/datastar.js")" "200"
|
||||||
|
|
||||||
|
check "favicon is served" \
|
||||||
|
"$(curl -s -o /dev/null -w '%{http_code}' -u ":$pass" "http://$addr/static/favicon.svg")" "200"
|
||||||
|
|
||||||
|
check "apple touch icon is served" \
|
||||||
|
"$(curl -s -o /dev/null -w '%{http_code}' -u ":$pass" "http://$addr/static/apple-touch-icon.png")" "200"
|
||||||
|
|
||||||
|
# A manifest served as octet-stream is silently ignored by the browser.
|
||||||
|
check "manifest has the right content type" \
|
||||||
|
"$(curl -s -o /dev/null -w '%{content_type}' -u ":$pass" "http://$addr/static/manifest.webmanifest")" \
|
||||||
|
"application/manifest+json"
|
||||||
|
|
||||||
check "catalog starts empty" \
|
check "catalog starts empty" \
|
||||||
"$(curl -s -u ":$pass" "http://$addr/ruoat")" "0 pääruokaa"
|
"$(curl -s -u ":$pass" "http://$addr/ruoat")" "0 pääruokaa"
|
||||||
|
|
||||||
|
|||||||