A logo drawn from the radio's screen, to app icon guidelines (#55)

The 2004 icon is a whole radio on a transparent background and not
square, so the tab icon was padded and iOS painted the home-screen icon
on white. web/logo.svg is the radio's screen alone, its tuning scale and
orange needle, laid out as Apple's app icon guidelines ask: opaque and
full-bleed (the system cuts its own corners), a gradient background and
flat foreground layers with hard edges, no highlights or shadows of its
own, nothing thin enough to vanish at 32px. Each layer is a <g>, ready to
split out for Icon Composer. web/logo-dark.svg is the same layers
recoloured.

favicon.png (128) and apple-touch-icon.png (180) are renders of it. The
pages show it from /logo.svg, served outside the auth layer for the
sign-in page, with an app icon's rounded corners. The 2004 icon stays at
/icon.png for anything outside that links to it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-09-28 13:31:16 +00:00
parent 162665b010
commit 7796566dfc
11 changed files with 70 additions and 18 deletions

View File

@@ -67,6 +67,7 @@ pub fn router(state: WebState) -> Router {
.route("/login", get(login_page))
.route("/api/login", post(login))
.route("/icon.png", get(icon))
.route("/logo.svg", get(logo))
.route("/favicon.ico", get(favicon))
.route("/favicon.png", get(favicon))
.route("/apple-touch-icon.png", get(touch_icon))
@@ -552,8 +553,16 @@ fn script(js: &'static str) -> impl IntoResponse {
([(header::CONTENT_TYPE, "text/javascript; charset=utf-8"), (header::CACHE_CONTROL, SCRIPT_CACHE)], js)
}
/// The 2004 icon, served once for both pages rather than inlined as base64 into each. The
/// sign-in page shows it, so it sits outside the auth layer with /login.
/// The logo the pages show, served once rather than inlined into each. The sign-in page shows
/// it, so it sits outside the auth layer with /login.
async fn logo() -> impl IntoResponse {
(
[(header::CONTENT_TYPE, "image/svg+xml"), (header::CACHE_CONTROL, "max-age=86400")],
include_str!("../web/logo.svg"),
)
}
/// The 2004 icon. Nothing here shows it any more; it stays for whatever outside ipx links to it.
async fn icon() -> impl IntoResponse {
(
[(header::CONTENT_TYPE, "image/png"), (header::CACHE_CONTROL, "max-age=86400")],
@@ -561,9 +570,8 @@ async fn icon() -> impl IntoResponse {
)
}
/// The logo, squared up with transparent padding: it is 128x121, and a tab icon that is not
/// square can be passed over. /favicon.ico is the same PNG, for a browser that asks for that
/// on its own; behind the auth layer it answered 401, and the tab stayed blank.
/// web/logo.svg at 128px. /favicon.ico is the same PNG, for a browser that asks for that on its
/// own; behind the auth layer it answered 401, and the tab stayed blank.
async fn favicon() -> impl IntoResponse {
(
[(header::CONTENT_TYPE, "image/png"), (header::CACHE_CONTROL, "max-age=86400")],
@@ -571,8 +579,8 @@ async fn favicon() -> impl IntoResponse {
)
}
/// For an iPhone's home screen, which paints a transparent icon's background black, so this
/// one is on white.
/// web/logo.svg at 180px, for an iPhone's home screen. It is opaque edge to edge, as iOS paints
/// a transparent icon's background black and cuts its own corners.
async fn touch_icon() -> impl IntoResponse {
(
[(header::CONTENT_TYPE, "image/png"), (header::CACHE_CONTROL, "max-age=86400")],