From 57eea69527c04ad14c86405ae8091ac5466fdfad Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Mon, 12 Oct 2026 14:30:39 +0000 Subject: [PATCH 1/3] Fix public-page contrast, tap targets, skip link, and header wrap Add a shared public-a11y stylesheet so WCAG AA hero contrast, 24px tap targets, a skip-to-content link, and a nowrap 390px header land without editing the soundtrack/journal hero rules in PR 263. Co-authored-by: Patrick --- server/tests/public-a11y.test.ts | 40 ++++++++++++++++++++++++++++++++ web/404.html | 4 +++- web/apple-music-soundtrack.html | 4 +++- web/beta.html | 1 + web/privacy.html | 4 +++- web/private-driving-journal.html | 4 +++- web/public-a11y.css | 31 +++++++++++++++++++++++++ web/support.html | 4 +++- web/terms.html | 4 +++- 9 files changed, 90 insertions(+), 6 deletions(-) create mode 100644 server/tests/public-a11y.test.ts create mode 100644 web/public-a11y.css diff --git a/server/tests/public-a11y.test.ts b/server/tests/public-a11y.test.ts new file mode 100644 index 00000000..6f07dd02 --- /dev/null +++ b/server/tests/public-a11y.test.ts @@ -0,0 +1,40 @@ +import assert from "node:assert/strict"; +import test from "node:test"; +import { createApp } from "../src/app.js"; +import { fixtureDatabase, root } from "./helpers.js"; + +const publicPages = ["/", "/privacy", "/support", "/terms", "/apple-music-soundtrack", "/private-driving-journal"]; + +test("public pages expose a skip link, 24px tap targets, and contrast overlay CSS", async () => { + const fixture = fixtureDatabase(); + const runtime = await createApp({ databasePath: fixture.filename, root, allowTestAuth: true, legacyUpstream: "", mode: "web" }); + try { + const css = await runtime.app.inject({ method: "GET", url: "/public-a11y.css?v=1" }); + assert.equal(css.statusCode, 200, css.body); + assert.match(String(css.headers["content-type"]), /text\/css/); + assert.equal(css.headers["cache-control"], "public, max-age=31536000, immutable"); + assert.match(css.body, /\.skip-link/); + assert.match(css.body, /min-height: 24px/); + assert.match(css.body, /min-width: 24px/); + assert.match(css.body, /flex-wrap: nowrap/); + assert.match(css.body, /#081832f8/); + assert.doesNotMatch(css.body, /[\u2013\u2014]/); + + for (const url of publicPages) { + const page = await runtime.app.inject({ method: "GET", url }); + assert.equal(page.statusCode, 200, url); + assert.match(page.body, /class="skip-link" href="#main">Skip to content<\/a>/, url); + assert.match(page.body, /id="main"/, url); + assert.match(page.body, /href="\/public-a11y\.css\?v=1"/, url); + assert.match(String(page.headers["content-security-policy"]), /default-src 'self'/, url); + } + + const missing = await runtime.app.inject({ method: "GET", url: "/missing-public-page" }); + assert.equal(missing.statusCode, 404); + assert.match(missing.body, /class="skip-link" href="#main">Skip to content<\/a>/); + assert.match(missing.body, /href="\/public-a11y\.css\?v=1"/); + } finally { + await runtime.app.close(); + fixture.cleanup(); + } +}); diff --git a/web/404.html b/web/404.html index 755ec3a9..70d8feda 100644 --- a/web/404.html +++ b/web/404.html @@ -10,15 +10,17 @@ + Page Not Found — JourneyDeck + -
+

Route not found · 404

That road isn’t on the map.

diff --git a/web/apple-music-soundtrack.html b/web/apple-music-soundtrack.html index a91d97d2..98d829f4 100644 --- a/web/apple-music-soundtrack.html +++ b/web/apple-music-soundtrack.html @@ -24,6 +24,7 @@ + Record iPhone drives with optional Apple Music | JourneyDeck + -
+

Your drive, your music

Record drives on iPhone with an optional Apple Music soundtrack

diff --git a/web/beta.html b/web/beta.html index 7714b1ec..8501cd0c 100644 --- a/web/beta.html +++ b/web/beta.html @@ -27,6 +27,7 @@ + diff --git a/web/privacy.html b/web/privacy.html index ae4bd4f1..d65d519d 100644 --- a/web/privacy.html +++ b/web/privacy.html @@ -24,17 +24,19 @@ + JourneyDeck Privacy Policy + -
+

Your data, your journey

Privacy Policy

diff --git a/web/private-driving-journal.html b/web/private-driving-journal.html index 05b647c7..14996554 100644 --- a/web/private-driving-journal.html +++ b/web/private-driving-journal.html @@ -24,6 +24,7 @@ + Private driving journal for iPhone | JourneyDeck + -
+

YOUR ROADS, YOUR ARCHIVE

Private driving journal for iPhone

diff --git a/web/public-a11y.css b/web/public-a11y.css new file mode 100644 index 00000000..0091b0e1 --- /dev/null +++ b/web/public-a11y.css @@ -0,0 +1,31 @@ +.skip-link { position: fixed; z-index: 100; top: 8px; left: 8px; transform: translateY(-150%); min-height: 44px; padding: 12px 16px; border-radius: 7px; background: var(--ivory, #f6f0e2); color: var(--navy, #081832); font-size: 14px; font-weight: 650; text-decoration: none; } +.skip-link:focus { transform: none; } +.site-header, .header-actions { flex-wrap: nowrap; } +.site-header .brand { white-space: nowrap; } +.header-actions .button { white-space: nowrap; flex-shrink: 0; } +.desktop-nav a { display: inline-flex; align-items: center; min-height: 24px; padding: 6px 2px; } +.footer a, .footer-bottom a { display: inline-flex; align-items: center; min-height: 24px; padding: 4px 2px; } +.page-actions { gap: 16px; } +.contact, .secondary-action { min-height: 52px; } +.tour-dots button { min-width: 24px; width: 24px; } +.text-link, .hero-soundtrack-link, .feature-cta, .hero-baseline a { min-height: 24px; } +section.hero::before, +section.hero.has-photo::before { + content: ""; + position: absolute; + z-index: 1; + inset: 0; + background: linear-gradient(90deg, #081832f8 0%, #081832f4 58%, #081832e8 100%); + pointer-events: none; +} +.hero > :not(.hero-photo) { position: relative; z-index: 2; } +@media (max-width: 620px) { + section.hero::before, + section.hero.has-photo::before { background: linear-gradient(90deg, #081832f8, #081832e8); } +} +@media (max-width: 390px) { + .site-header { gap: 8px; padding-inline: 12px; flex-wrap: nowrap; } + .header-actions { gap: 6px; flex-wrap: nowrap; } + .header-actions .button-small { gap: 6px; padding-inline: 10px; white-space: nowrap; } + .brand { font-size: 17px; } +} diff --git a/web/support.html b/web/support.html index 14882e4b..2b065517 100644 --- a/web/support.html +++ b/web/support.html @@ -24,6 +24,7 @@ + JourneyDeck Support + -
+

Here to help

JourneyDeck Support

diff --git a/web/terms.html b/web/terms.html index 880698a0..b396a442 100644 --- a/web/terms.html +++ b/web/terms.html @@ -24,17 +24,19 @@ + JourneyDeck Terms of Use + -
+

The rules of the road

Terms of Use

From 29c3b4ca89efcb65382b606973ebbcd398ad9ebd Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Mon, 12 Oct 2026 14:50:37 +0000 Subject: [PATCH 2/3] Lighten public hero overlay so the photo stays visible Keep skip-link and 390px header rules. Drop the 95% navy overlay to #081832cc behind the text column, fading to #081832b3 on the photo side. Co-authored-by: Patrick --- server/tests/public-a11y.test.ts | 10 ++++++---- web/404.html | 2 +- web/apple-music-soundtrack.html | 2 +- web/beta.html | 2 +- web/privacy.html | 2 +- web/private-driving-journal.html | 2 +- web/public-a11y.css | 13 +++++++++---- web/support.html | 2 +- web/terms.html | 2 +- 9 files changed, 22 insertions(+), 15 deletions(-) diff --git a/server/tests/public-a11y.test.ts b/server/tests/public-a11y.test.ts index 6f07dd02..0b7671ec 100644 --- a/server/tests/public-a11y.test.ts +++ b/server/tests/public-a11y.test.ts @@ -9,7 +9,7 @@ test("public pages expose a skip link, 24px tap targets, and contrast overlay CS const fixture = fixtureDatabase(); const runtime = await createApp({ databasePath: fixture.filename, root, allowTestAuth: true, legacyUpstream: "", mode: "web" }); try { - const css = await runtime.app.inject({ method: "GET", url: "/public-a11y.css?v=1" }); + const css = await runtime.app.inject({ method: "GET", url: "/public-a11y.css?v=2" }); assert.equal(css.statusCode, 200, css.body); assert.match(String(css.headers["content-type"]), /text\/css/); assert.equal(css.headers["cache-control"], "public, max-age=31536000, immutable"); @@ -17,7 +17,9 @@ test("public pages expose a skip link, 24px tap targets, and contrast overlay CS assert.match(css.body, /min-height: 24px/); assert.match(css.body, /min-width: 24px/); assert.match(css.body, /flex-wrap: nowrap/); - assert.match(css.body, /#081832f8/); + assert.match(css.body, /#081832cc/); + assert.match(css.body, /#081832b3/); + assert.doesNotMatch(css.body, /#081832f8|#081832f4|#081832e8/); assert.doesNotMatch(css.body, /[\u2013\u2014]/); for (const url of publicPages) { @@ -25,14 +27,14 @@ test("public pages expose a skip link, 24px tap targets, and contrast overlay CS assert.equal(page.statusCode, 200, url); assert.match(page.body, /class="skip-link" href="#main">Skip to content<\/a>/, url); assert.match(page.body, /id="main"/, url); - assert.match(page.body, /href="\/public-a11y\.css\?v=1"/, url); + assert.match(page.body, /href="\/public-a11y\.css\?v=2"/, url); assert.match(String(page.headers["content-security-policy"]), /default-src 'self'/, url); } const missing = await runtime.app.inject({ method: "GET", url: "/missing-public-page" }); assert.equal(missing.statusCode, 404); assert.match(missing.body, /class="skip-link" href="#main">Skip to content<\/a>/); - assert.match(missing.body, /href="\/public-a11y\.css\?v=1"/); + assert.match(missing.body, /href="\/public-a11y\.css\?v=2"/); } finally { await runtime.app.close(); fixture.cleanup(); diff --git a/web/404.html b/web/404.html index 70d8feda..3b8dd991 100644 --- a/web/404.html +++ b/web/404.html @@ -10,7 +10,7 @@ - + Page Not Found — JourneyDeck diff --git a/web/apple-music-soundtrack.html b/web/apple-music-soundtrack.html index 98d829f4..c1cda98c 100644 --- a/web/apple-music-soundtrack.html +++ b/web/apple-music-soundtrack.html @@ -24,7 +24,7 @@ - + Record iPhone drives with optional Apple Music | JourneyDeck diff --git a/web/privacy.html b/web/privacy.html index d65d519d..c08f55e4 100644 --- a/web/privacy.html +++ b/web/privacy.html @@ -24,7 +24,7 @@ - + JourneyDeck Privacy Policy diff --git a/web/private-driving-journal.html b/web/private-driving-journal.html index 14996554..3dab65a3 100644 --- a/web/private-driving-journal.html +++ b/web/private-driving-journal.html @@ -24,7 +24,7 @@ - + Private driving journal for iPhone | JourneyDeck From 91f24db04144a69450d909bac6f7c36576fdda11 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Mon, 12 Oct 2026 14:56:17 +0000 Subject: [PATCH 3/3] Retrigger JourneyDeck CI for the overlay follow-up Co-authored-by: Patrick