From 7796566dfc53818daa9c8402a9373311a659ac89 Mon Sep 17 00:00:00 2001 From: rays Date: Mon, 28 Sep 2026 13:31:16 +0000 Subject: [PATCH] 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 , 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 --- CHANGELOG.md | 3 +++ src/web.rs | 22 +++++++++++++++------- tests/ui/app.spec.js | 8 ++++---- web/admin.html | 2 +- web/app.css | 4 ++-- web/apple-touch-icon.png | Bin 20206 -> 5652 bytes web/favicon.png | Bin 17373 -> 3229 bytes web/index.html | 2 +- web/login.html | 6 +++--- web/logo-dark.svg | 19 +++++++++++++++++++ web/logo.svg | 22 ++++++++++++++++++++++ 11 files changed, 70 insertions(+), 18 deletions(-) create mode 100644 web/logo-dark.svg create mode 100644 web/logo.svg diff --git a/CHANGELOG.md b/CHANGELOG.md index 8ad805e..a1d7545 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added +- A new logo, on every page, in the browser tab and on the home screen: the old radio's screen, + its tuning scale and orange needle, drawn flat to Apple's app icon guidelines, with a dark + version. The 2004 icon is still at /icon.png. - Words that hide items. Settings has a list for every feed you read, and each feed's settings a list of its own; an item with one of those words or phrases in its title or text is hidden from you and is not downloaded on your account. Other people's lists never hide anything from diff --git a/src/web.rs b/src/web.rs index 64a30d9..762f755 100644 --- a/src/web.rs +++ b/src/web.rs @@ -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")], diff --git a/tests/ui/app.spec.js b/tests/ui/app.spec.js index f8dc334..707b55e 100644 --- a/tests/ui/app.spec.js +++ b/tests/ui/app.spec.js @@ -558,10 +558,10 @@ test('a second person has their own feeds and their own read state', async ({ br const ctx = await browser.newContext(); const page = await ctx.newPage(); await page.goto('/login'); - // The sign-in page shows the icon, so it has to load before anyone has signed in. - const icon = await page.request.get('/icon.png'); - expect(icon.status()).toBe(200); - expect(icon.headers()['content-type']).toBe('image/png'); + // The sign-in page shows the logo, so it has to load before anyone has signed in. + const logo = await page.request.get('/logo.svg'); + expect(logo.status()).toBe(200); + expect(logo.headers()['content-type']).toBe('image/svg+xml'); await page.locator('#name').fill('sam'); await page.locator('#pw').fill('sampassword'); await page.locator('button[type=submit]').click(); diff --git a/web/admin.html b/web/admin.html index 3b03640..2128f77 100644 --- a/web/admin.html +++ b/web/admin.html @@ -13,7 +13,7 @@
- +

Admin