From f9c9c2b7cc6398685e7d2f3823c208845febc4e3 Mon Sep 17 00:00:00 2001 From: rays Date: Mon, 28 Sep 2026 18:05:44 +0000 Subject: [PATCH] Modern in the logo's colours, and the logo in the page's mode (#63) Modern's palette was sampled from the 2004 iPodderX icon: a neutral navy, its screen blue and amber EQ bars. It now takes the new logo's colours, the dark half from logo-dark.svg (navy ground, #8fc2ea scale, #ff6a1a needle) and the light half from logo.svg (sky ground, #2f6aa0 scale, the needle taken down to #c43e00 so white on it clears AA). The pending amber and the error red moved apart from the needle's orange, and the sign-in page's copy of the palette follows. The pages always showed logo.svg, the light variant, even in a dark theme; logo-dark.svg was never served. It is now, and the app and admin pages show whichever matches data-mode, dark until the script says light, as the palette is. The sign-in page, which has no account's theme, picks by the system's with . Co-Authored-By: Claude Opus 5.5 --- CHANGELOG.md | 3 +++ src/web.rs | 10 +++++++ tests/ui/app.spec.js | 24 +++++++++++++---- web/admin.html | 2 +- web/app.css | 64 +++++++++++++++++++++++--------------------- web/build.mjs | 2 +- web/index.html | 2 +- web/login.html | 59 ++++++++++++++++++++-------------------- 8 files changed, 99 insertions(+), 67 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9c67b29..015fbe3 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 ### Changed +- The Modern theme takes its colours from the new logo: its navy, the blue of its bars and the + orange of its needle. +- The logo follows the page: the dark version in a dark theme, the light one in a light theme. - Between releases, the version on the logo says so: 0.9.1-dev, not 0.9.0. - The logo's needle is a brighter, redder orange that stands apart from the blue bars. - The logo is in the top bar beside the add-feed button, in place of the name at the top of the diff --git a/src/web.rs b/src/web.rs index d97d735..396edc8 100644 --- a/src/web.rs +++ b/src/web.rs @@ -68,6 +68,7 @@ pub fn router(state: WebState) -> Router { .route("/api/login", post(login)) .route("/icon.png", get(icon)) .route("/logo.svg", get(logo)) + .route("/logo-dark.svg", get(logo_dark)) .route("/favicon.ico", get(favicon)) .route("/favicon.png", get(favicon)) .route("/apple-touch-icon.png", get(touch_icon)) @@ -562,6 +563,15 @@ async fn logo() -> impl IntoResponse { ) } +/// logo.svg recoloured for a dark page. The pages pick one or the other by their light or dark +/// mode. +async fn logo_dark() -> impl IntoResponse { + ( + [(header::CONTENT_TYPE, "image/svg+xml"), (header::CACHE_CONTROL, "max-age=86400")], + include_str!("../web/logo-dark.svg"), + ) +} + /// The 2004 icon. Nothing here shows it any more; it stays for whatever outside ipx links to it. async fn icon() -> impl IntoResponse { ( diff --git a/tests/ui/app.spec.js b/tests/ui/app.spec.js index 3129b22..d875439 100644 --- a/tests/ui/app.spec.js +++ b/tests/ui/app.spec.js @@ -47,10 +47,10 @@ test('Settings picks a theme and, where it has both, light, dark or Auto', async // Auto follows the system, live, with no reload. await page.emulateMedia({ colorScheme: 'light' }); await expect.poll(root).toEqual(['modern', 'light']); - await expect.poll(bg).toBe('rgb(242, 244, 247)'); // Modern's light --bg + await expect.poll(bg).toBe('rgb(238, 245, 251)'); // Modern's light --bg await page.emulateMedia({ colorScheme: 'dark' }); await expect.poll(root).toEqual(['modern', 'dark']); - await expect.poll(bg).toBe('rgb(14, 19, 27)'); // Modern's dark --bg + await expect.poll(bg).toBe('rgb(10, 23, 38)'); // Modern's dark --bg // Dracula, then its light half, Alucard. await page.locator('#stheme').selectOption('dracula'); @@ -559,9 +559,11 @@ test('a second person has their own feeds and their own read state', async ({ br const page = await ctx.newPage(); await page.goto('/login'); // 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'); + for (const path of ['/logo.svg', '/logo-dark.svg']) { + const logo = await page.request.get(path); + expect(logo.status(), path).toBe(200); + expect(logo.headers()['content-type'], path).toBe('image/svg+xml'); + } await page.locator('#name').fill('sam'); await page.locator('#pw').fill('sampassword'); await page.locator('button[type=submit]').click(); @@ -1178,6 +1180,18 @@ test('a file not yet downloaded has its icon in line with the rest of its row', for (const o of offsets) expect(Math.abs(o)).toBeLessThanOrEqual(1); }); +test('the logo is the dark one in dark mode and the light one in light mode', async ({ page }) => { + const shown = () => page.locator('#applogo img:visible').getAttribute('src'); + await page.evaluate(() => setTheme('modern', 'dark')); + expect(await shown()).toMatch(/^\/logo-dark\.svg\?v=/); + await page.evaluate(() => setTheme('modern', 'light')); + expect(await shown()).toMatch(/^\/logo\.svg\?v=/); + // Paper has only a light palette, so the light logo whatever the mode asked. + await page.evaluate(() => setTheme('paper', 'dark')); + expect(await shown()).toMatch(/^\/logo\.svg\?v=/); + await page.evaluate(() => setTheme('modern', 'dark')); +}); + test('the favicon is the logo, square, from both pages', async ({ page }) => { await expect(page.locator('link[rel="icon"]')).toHaveAttribute('href', /^\/favicon\.png\?v=[0-9a-f]{12}$/); // A browser asks for /favicon.ico on its own, signed in or not. diff --git a/web/admin.html b/web/admin.html index 3a6b970..8f04894 100644 --- a/web/admin.html +++ b/web/admin.html @@ -13,7 +13,7 @@
- +

Admin