fix(theme): stop the dark scheme reading as a stock generated landing page - #164
Conversation
… page The dark palette was a mechanical lightness inversion of the light one: hue and saturation were kept and only lightness flipped, which landed on a near-black ground (#121212) carrying a fully saturated #4da6ff on every heading. That combination, plus a gradient-filled headline and a coloured glow under the primary button, is the recognisable generated-template look. Changes, all of which keep or improve measured contrast: - Accent #4da6ff -> #79a7d6. Chosen for the SAME relative luminance as the old value, so every contrast pair involving it is unchanged (~7.1:1 on the ground) while the chroma is roughly halved. The neon read came from saturation, not brightness. - Ground #121212 -> #15171b, off pure near-black with a slight cool cast. - Card headings use --text-strong instead of the accent, in both schemes. Blue is now reserved for things that are clickable. - Cards get --surface (#1c1f26) and a stronger border so they are visible against the ground; previously #1a1a1a on #121212 essentially vanished. - Headline is solid ink in both schemes; the gradient text fill is removed. - Primary CTA is a flat fill with a neutral shadow rather than a gradient with a blue glow. - Ring visualisation: the teal halo band is removed, peers and event hues are desaturated (the five originals were Tailwind's default 400 ramp), and the canvas ground now matches the page so there is no disc edge. Two accessibility defects fixed on the way, both confirmed against production as pre-existing: - The "Freenet" wordmark was gradient-clipped text with color: transparent, measuring 1:1 on every page in both schemes. It is now solid. - "Live network dashboard" was #888 at 0.6 opacity, compositing to 1.95:1 light / 2.67:1 dark. It is now a real link at 5.32:1 / 7.1:1. Verified with Playwright across home, river, faq, news, donate and ghostkey in both schemes plus mobile: no page errors, no horizontal overflow, and an automated contrast sweep run against production as a control shows 15 failures fixed and none introduced. The 7 remaining are pre-existing on /river/ and live outside these files. Claude-Session: https://claude.ai/code/session_01Cwq3ZRYFqSW9rMtGJYqfo2
bgColor() had no call site: the canvas is cleared with clearRect and composites over the page, so nothing ever painted a ground. The comment added in the previous commit claimed it prevented a disc edge, which it cannot do. Removing it rather than leaving a comment that misdescribes it. origResize captured window.onresize and was never read. Verified in a browser: resize still repaints (non-blank pixel fraction 0.121 -> 0.120 -> 0.120 across shrink and restore), and a live OS theme flip still recolours the ring (mean canvas colour 106,148,190 dark -> 59,104,157 light -> 106,147,190 back), which is handled by the isDark() !== lastDark check in frame(). Claude-Session: https://claude.ai/code/session_01Cwq3ZRYFqSW9rMtGJYqfo2
Adversarial review found the first commit's find-and-replace stopped at the stylesheet boundary, leaving the old neon accent live in four other files. The result was worse than either state alone: /quickstart/ rendered BOTH blues in one viewport, the muted #79a7d6 on "Try River in your browser" a few hundred px above the old #4da6ff on the active OS tab. - os-install.html: 7x #4da6ff, 1x #7cc0ff (drives /quickstart/) - river/baseof.html: --rv-brand, --rv-brand-bright - donation-success.html, river-invite-button.html: #1a1a1a card surfaces, which the ground lift had made LESS visible (1.10 -> 1.06), the exact defect the first commit set out to fix. Now on the same #1c1f26 surface as every other card, at 1.09. Also from review: - .funding-donate-button:hover had a dark override setting a bare accent fill under white text: 2.53:1, and it LIGHTENED on hover while every other primary CTA darkens. The base rule already uses the variables and resolves correctly in dark, so the override was both redundant and wrong. Deleting it gives 8.42:1 in both schemes (verified by hovering). - Reduced-motion drew one static frame and returned, so the scheme poll in frame() never ran and a reduced-motion visitor whose OS switched theme kept the old colours for the life of the page. Now redraws on the media query. Verified: mean canvas colour 105,148,190 -> 58,104,157 on flip, where before it did not move at all. - .network-hero-link lost its hover colour change in the first commit (only the underline moved). Restored, plus a dead text-decoration: none that was overridden two lines later. Correcting an overstatement in the first commit message: it said the wordmark measured "1:1 on every page in both schemes". That is what an automated checker reports for color: transparent, but background-clip: text was painting the gradient into the glyphs, so the wordmark was legible in production. The real problem was that transparent-fill text is fragile, not that it was invisible. The PR body has been corrected too. Claude-Session: https://claude.ai/code/session_01Cwq3ZRYFqSW9rMtGJYqfo2
Review round 1 — findings addressedAn adversarial review pass found a regression I introduced plus several real defects. All fixed in f4e0fed, each verified in a browser rather than by inspection. Correcting two claims I made in the PR descriptionThe wordmark was not invisible. I wrote that it "measured 1:1 on every page in both schemes". That is what an automated checker reports for "15 contrast failures fixed" was inflated. 12 of those were the wordmark, and 1 more ( The "0 introduced" half of the claim was also wrong; see below. Regression I introduced
Other fixes
Verified after the fixes
Knowingly not fixed here
[AI-assisted - Claude] |
Accessibility review at f4e0fed found the surface lift had broken the focus indicator, which is a defect this branch caused. base.css drew the ring in --color-primary (#0066CC). That is a FILL colour, chosen to carry white text, and it was never checked against the surfaces it lands on. Against the old #1a1a1a card it scraped 3.13:1; raising --surface to #1c1f26 dropped it to 2.96:1, under the 3:1 that WCAG SC 1.4.11 requires of a focus indicator. Six homepage card links were affected. Rather than move --color-primary (which would change every button fill), the ring now has its own --focus-ring token, defaulting to --color-primary so nothing outside this theme changes. Dark resolves to the accent: 2.96 -> 6.53:1 on cards, verified by focusing a real link. Also from that review, all pre-existing but all in scope here: - --navbar-item-hover was declared only in the light scheme, so base.css painted a near-white #f1f2f4 under near-white text on a keyboard-focused navbar item. The wordmark disappeared on Tab: 1.05:1, measured. Declared for dark -> 12.26:1. - os-install.html kept the coloured glow this branch strips from .cta-button, on a visually identical button, so Windows and macOS visitors still got it. Now the same flat fill and neutral shadow. Its :focus-visible ring stays coloured on purpose (indicator, not decoration) and uses the new accent. - .home-browser was the last dark card left on #1a1a1a; its separation from the ground had fallen to 1.031:1. Now --surface, 1.09:1 like the others. - Four remaining rgba(77,166,255) tints migrated. - color-scheme: light dark declared. base.css forces overflow-y: scroll, so a scrollbar is always present and was rendering light against a dark page. Corrected the --accent-text comment: it claimed contrast was "unchanged", but the ground moved too, so accent-on-ground is 7.33 -> 7.10:1. The drop comes from the lifted ground, not from the new accent. Verified: full sweep over home, quickstart, river, faq, news, donate, ghostkey in both schemes plus mobile, with production as control. Still 7 pre-existing /river/ failures and nothing new; every earlier fix re-checked and still holding. Claude-Session: https://claude.ai/code/session_01Cwq3ZRYFqSW9rMtGJYqfo2
Review round 2 — accessibility / cross-browser findings addressedAll fixed in 2d4568f, each verified by measuring the real state in a browser. Blocking regression this branch causedThe focus ring stopped meeting SC 1.4.11. Fixed by giving the ring its own Pre-existing, but in scope and fixed here
Independent confirmation of the core claimThe reviewer recomputed the luminance argument: It also independently reproduced the honest accounting: of the "15 failures fixed", 13 were checker artifacts and 2 were real. Pixel-sampling showed the wordmark rendered at 7.3–20:1 in Chromium, Firefox and WebKit, and was fine under Verified clean
Still knowingly not fixedThe pre-existing [AI-assisted - Claude] |
Ian flagged that freenet.org looked like cliché AI-generated design ("the glowing colors"). In light mode that turned out to be mostly wrong; in dark mode it was right, and this fixes it.
Root cause
The dark palette was a mechanical lightness inversion of the light one. Hue and saturation were preserved and only lightness flipped:
--text#404654#abb1bf--surface#ffffff#14161a#0066CC#4da6ffThat lands on a near-black ground (
#121212) carrying a fully saturated#4da6ffon every card heading. Add a gradient-filled headline and a blue glow under the primary button and you have the recognisable generated-template look. A designed dark theme desaturates its accents and lifts the ground off pure black; a flipped one does neither.Changes
#4da6ff→#79a7d6, chosen for the same relative luminance as the old value, so every contrast pair involving it is unchanged (~7.1:1 on the ground) while chroma is roughly halved. The neon read came from saturation, not brightness, so this is a chroma cut rather than a dimming.#121212→#15171b— off pure near-black, slight cool cast.--text-stronginstead of the accent, in both schemes. Blue is now reserved for things you can click.--surface(#1c1f26) and a stronger border.#1a1a1aon#121212was a 1.03:1 difference and essentially invisible.0.05 → 0.13light, it was washing out entirely against the page), and the canvas ground now matches the page so there is no disc edge.Two accessibility defects fixed on the way
color: transparent. It rendered fine in production, becausebackground-clip: textpainted the gradient into the glyphs — an automated checker reports 1:1 for it, but a human sees the word. The real defect is that transparent-fill text is fragile: it breaks under forced-colors, and it broke in light mode mid-way through this branch when the gradient was removed but thecolor: transparentwas left behind. Now a plain solid colour.#888at0.6opacity, compositing to 1.95:1 light / 2.67:1 dark. Now a real link at 5.32:1 / 7.1:1. This is the one unambiguous contrast fix, and it appears twice.Verification
Playwright across home, quickstart, river, faq, news, donate, ghostkey in both schemes plus mobile: no page errors, no transparent-text, no horizontal overflow.
A contrast sweep was run against production as a control, so pre-existing failures could be separated from new ones. After the review round: 7 failures remain, all pre-existing on
/river/, and none are introduced by this PR.The first commit did introduce a regression (
/quickstart/showing two different accent blues, because the find-and-replace only coveredfreenet.css) and a contrast bug on the donate CTA hover at 2.53:1. Both are fixed in f4e0fed; details in the review-response comment.Known gap
The light-mode hero screenshot (
river-hero-light.webp) is not addressed. Its legible content shows users reporting that the tutorial is outdated and that there is no search engine. It needs a fresh capture from a real River session, which cannot be produced authentically here. See the review comment for detail.[AI-assisted - Claude]
https://claude.ai/code/session_01Cwq3ZRYFqSW9rMtGJYqfo2