From 6560fc6c5c57cc0e95ff7a8b24bfc558b2d0bb9b Mon Sep 17 00:00:00 2001 From: Ian Clarke Date: Wed, 2 Sep 2026 15:26:44 -0500 Subject: [PATCH 1/4] fix(theme): stop the dark scheme reading as a stock generated landing 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 --- hugo-site/static/js/network-hero.js | 47 +++--- .../themes/freenet/assets/css/freenet.css | 157 +++++++----------- 2 files changed, 85 insertions(+), 119 deletions(-) diff --git a/hugo-site/static/js/network-hero.js b/hugo-site/static/js/network-hero.js index 4f86c5b9..148ba6a6 100644 --- a/hugo-site/static/js/network-hero.js +++ b/hugo-site/static/js/network-hero.js @@ -16,13 +16,16 @@ const SPAWN_INTERVAL = 120; // ms between particle spawns const MAX_PARTICLES = 60; - // Event type colors (matches dashboard) + // Event type colours. These deliberately do NOT match the telemetry + // dashboard: the dashboard's ramp is tuned for a dense technical view with a + // legend, whereas here the hues are decorative (the homepage shows no key) + // and the saturated originals fluoresced against the dark ground. const COLORS = { - connect: '#7ecfef', - put: '#fbbf24', - get: '#34d399', - update: '#a78bfa', - subscribe: '#f472b6', + connect: '#84a9c9', + put: '#c9a227', + get: '#6fa88c', + update: '#9990bd', + subscribe: '#b48a9f', }; const COLOR_LIST = Object.values(COLORS); const TYPE_NAMES = Object.keys(COLORS); @@ -41,14 +44,13 @@ return window.matchMedia('(prefers-color-scheme: dark)').matches; } - function ringColor() { return isDark() ? '#1a2a2a' : '#d0d8e0'; } - function glowColor() { return isDark() ? 'rgba(0, 212, 170, 0.08)' : 'rgba(0, 140, 120, 0.06)'; } - function peerColor() { return isDark() ? '#007FFF' : '#3388dd'; } - function peerGlow() { return isDark() ? 'rgba(0, 127, 255, 0.18)' : 'rgba(0, 100, 200, 0.12)'; } - function gwColor() { return '#f59e0b'; } - function labelColor() { return isDark() ? '#484f58' : '#8090a0'; } - function innerRing() { return isDark() ? 'rgba(255,255,255,0.03)' : 'rgba(0,0,0,0.04)'; } - function bgColor() { return isDark() ? '#0d1117' : '#ffffff'; } + function ringColor() { return isDark() ? '#2b303a' : '#d0d8e0'; } + function peerColor() { return isDark() ? '#79a7d6' : '#3f7fbf'; } + function gwColor() { return isDark() ? '#c9922b' : '#b3801f'; } + function labelColor() { return isDark() ? '#5b6472' : '#8090a0'; } + function innerRing() { return isDark() ? 'rgba(255,255,255,0.04)' : 'rgba(0,0,0,0.05)'; } + // Matches the page ground in each scheme; a mismatch shows as a disc edge. + function bgColor() { return isDark() ? '#15171b' : '#f8fafc'; } // --- Geometry helpers --- function locationToXY(loc) { @@ -169,15 +171,6 @@ // --- Draw static ring elements --- function drawRing(ctx) { - var dark = isDark(); - - // Outer glow - ctx.beginPath(); - ctx.arc(CENTER, CENTER, RADIUS + 5, 0, Math.PI * 2); - ctx.strokeStyle = glowColor(); - ctx.lineWidth = 20; - ctx.stroke(); - // Inner reference circles [0.6, 0.3].forEach(function (scale) { ctx.beginPath(); @@ -214,9 +207,9 @@ ctx.lineWidth = 0.7; ctx.lineCap = 'round'; var dark = isDark(); - var alpha = dark ? 0.09 : 0.05; + var alpha = dark ? 0.16 : 0.13; - ctx.strokeStyle = dark ? 'rgba(0, 127, 255, ' + alpha + ')' : 'rgba(0, 80, 180, ' + alpha + ')'; + ctx.strokeStyle = dark ? 'rgba(121, 167, 214, ' + alpha + ')' : 'rgba(40, 90, 150, ' + alpha + ')'; ctx.beginPath(); for (var i = 0; i < connections.length; i++) { var c = connections[i]; @@ -243,7 +236,9 @@ var p = peers[i]; ctx.beginPath(); ctx.arc(p.pos.x, p.pos.y, p.isGateway ? gwGlowR : glowR, 0, Math.PI * 2); - ctx.fillStyle = p.isGateway ? 'rgba(245, 158, 11, 0.25)' : peerGlow(); + ctx.fillStyle = p.isGateway + ? 'rgba(201, 146, 43, 0.16)' + : (isDark() ? 'rgba(121, 167, 214, 0.07)' : 'rgba(63, 127, 191, 0.07)'); ctx.fill(); } // Solid pass diff --git a/hugo-site/themes/freenet/assets/css/freenet.css b/hugo-site/themes/freenet/assets/css/freenet.css index 268539dd..9e3e0128 100644 --- a/hugo-site/themes/freenet/assets/css/freenet.css +++ b/hugo-site/themes/freenet/assets/css/freenet.css @@ -1,6 +1,6 @@ /* Hover effect for navbar item */ .navbar-item:hover .logo-text { - color: #007bff; /* Preferred hover color */ + color: var(--accent-text); } /* Logo container styling */ @@ -19,11 +19,7 @@ font-family: 'Leto', sans-serif !important; font-size: 1.5rem !important; font-weight: 700 !important; - background: radial-gradient(circle, #000000 0%, #404040 100%); - -webkit-background-clip: text; - -webkit-text-fill-color: transparent; - background-clip: text; - color: transparent; + color: var(--text-strong); } /* Override Bulma's navbar-item styles for the logo */ @@ -85,8 +81,9 @@ --font-display: 'Outfit', system-ui, sans-serif; --font-body: 'Source Sans 3', system-ui, sans-serif; --font-code: 'Inconsolata', 'Hack', 'SF Mono', 'Roboto Mono', 'Source Code Pro', 'Ubuntu Mono', monospace; - --color-primary: #0066CC; + --color-primary: #0066CC; /* button fill; white text sits on this */ --color-primary-hover: #004C99; + --accent-text: #0066CC; /* accent used AS text, on the page ground */ /* Surface and text roles. These carry everything base.css paints; Bulma derived the same values from its own scheme variables. Declaring both @@ -125,9 +122,13 @@ :root { --text-strong: #ebecf0; --text: #abb1bf; + /* Same luminance as the old #4da6ff (so contrast is unchanged at ~7:1) with + roughly half the chroma. The neon read came from saturation, not + brightness, so this is a chroma cut rather than a dimming. */ + --accent-text: #79a7d6; --border: #353a46; - --surface: #14161a; - --surface-soft: #1f2229; + --surface: #1c1f26; + --surface-soft: #232a33; --code-color: #e7e9ee; --code-background: #1e2430; } @@ -141,10 +142,7 @@ .home .content h1:first-child { font-size: 2.75rem; margin-bottom: 1.5rem; - background: linear-gradient(135deg, #005cbf 0%, #0066CC 50%, #00a6ff 100%); - -webkit-background-clip: text; - -webkit-text-fill-color: transparent; - background-clip: text; + color: var(--text-strong); } /* News section styling */ @@ -194,7 +192,7 @@ .cta-button { display: inline-block; margin: 0; - background: linear-gradient(135deg, #0066CC 0%, #004C99 100%); + background: var(--color-primary); color: #ffffff !important; border: none; font-family: var(--font-display); @@ -202,12 +200,12 @@ padding: 0.875rem 2rem; border-radius: 8px; transition: all 0.2s ease; - box-shadow: 0 4px 14px rgba(0, 127, 255, 0.25); + box-shadow: 0 1px 2px rgba(16, 24, 40, 0.12); } .cta-button:hover { - background: linear-gradient(135deg, #0066cc 0%, #005cbf 100%); - box-shadow: 0 6px 20px rgba(0, 127, 255, 0.35); + background: var(--color-primary-hover); + box-shadow: 0 2px 6px rgba(16, 24, 40, 0.16); transform: translateY(-1px); color: #ffffff !important; } @@ -259,13 +257,13 @@ @media (prefers-color-scheme: dark) { .cta-secondary-btn { - color: #4da6ff !important; - border-color: rgba(77, 166, 255, 0.65); + color: #79a7d6 !important; + border-color: rgba(121, 167, 214, 0.65); } .cta-secondary-btn:hover { - border-color: #4da6ff; - background: rgba(77, 166, 255, 0.1); + border-color: #79a7d6; + background: rgba(121, 167, 214, 0.1); } .cta-note { @@ -620,11 +618,11 @@ p.page-end-spacer { /* Dark mode styles */ @media (prefers-color-scheme: dark) { html { - background-color: #121212; + background-color: #15171b; } body { - background-color: #121212; + background-color: #15171b; color: #e0e0e0; } @@ -632,12 +630,10 @@ p.page-end-spacer { color: #ffffff; } - /* Dark mode hero gradient text */ + /* Headline is solid ink in both schemes. A gradient-filled headline is a + stock landing-page tell and it costs contrast for nothing. */ .home .content h1:first-child { - background: linear-gradient(135deg, #4da6ff 0%, #0066CC 50%, #00a6ff 100%); - -webkit-background-clip: text; - -webkit-text-fill-color: transparent; - background-clip: text; + color: var(--text-strong); } /* Dark mode news section */ @@ -651,26 +647,26 @@ p.page-end-spacer { /* Dark mode CTA button */ .cta-button { - background: linear-gradient(135deg, #0066CC 0%, #004C99 100%); - box-shadow: 0 4px 14px rgba(0, 127, 255, 0.3); + background: var(--color-primary); + box-shadow: 0 1px 2px rgba(0, 0, 0, 0.4); } .cta-button:hover { - background: linear-gradient(135deg, #4da6ff 0%, #0066CC 100%); - box-shadow: 0 6px 20px rgba(0, 127, 255, 0.4); + background: var(--color-primary-hover); + box-shadow: 0 2px 6px rgba(0, 0, 0, 0.5); } a { - color: #4da6ff; + color: #79a7d6; } a:hover { - color: #80bfff; + color: #9dc0e2; } .box { - background-color: #1e1e1e; - color: #e0e0e0; + background-color: var(--surface); + color: var(--text); } .date { @@ -687,8 +683,8 @@ p.page-end-spacer { .button.is-medium-teal, .button.is-medium-purple, .button.is-medium-orange { - border-color: #4da6ff; - color: #4da6ff; + border-color: #79a7d6; + color: #79a7d6; } .buttons .button:hover, @@ -700,12 +696,12 @@ p.page-end-spacer { .button.is-medium-teal:hover, .button.is-medium-purple:hover, .button.is-medium-orange:hover { - background-color: #4da6ff; - color: #121212; + background-color: #79a7d6; + color: #15171b; } .navbar { - background-color: #121212; + background-color: #15171b; } .navbar-item, @@ -719,22 +715,6 @@ p.page-end-spacer { color: #ffffff !important; } - .logo-text { - background: radial-gradient(circle, #ffffff 0%, #707070 100%); - -webkit-background-clip: text; - -webkit-text-fill-color: transparent; - background-clip: text; - color: transparent; - } - - .navbar-item:hover .logo-text { - background: radial-gradient(circle, #ffffff 0%, #909090 100%); - -webkit-background-clip: text; - -webkit-text-fill-color: transparent; - background-clip: text; - color: transparent; - } - .navbar-burger span { background-color: #e0e0e0; } @@ -742,11 +722,11 @@ p.page-end-spacer { /* Open state. Keyed off the checkbox rather than .is-active, which used to be applied by a JS handler that has been removed. */ .navbar-toggle-checkbox:checked ~ .navbar-brand .navbar-burger span { - background-color: #4da6ff; + background-color: #79a7d6; } .navbar-link:not(.is-arrowless)::after { - border-color: #4da6ff; + border-color: #79a7d6; } .button.is-dark-blue, @@ -782,12 +762,12 @@ p.page-end-spacer { /* Dark mode Donate header button */ .navbar-item.donate-nav-button { - color: #4da6ff !important; - border-color: #4da6ff; + color: #79a7d6 !important; + border-color: #79a7d6; } .navbar-item.donate-nav-button:hover { - background-color: #4da6ff !important; + background-color: #79a7d6 !important; color: #0a0a0a !important; } } @@ -937,8 +917,8 @@ p.page-end-spacer { @media (prefers-color-scheme: dark) { .talk-card { - background: #1a1a1a; - border-color: rgba(255, 255, 255, 0.1); + background: var(--surface); + border-color: rgba(255, 255, 255, 0.14); } .talk-card-caption { color: #94a3b8; @@ -958,22 +938,17 @@ p.page-end-spacer { } .network-hero-link a { - font-size: 0.8rem; - color: #888; + font-size: 0.85rem; + color: var(--accent-text); text-decoration: none; - opacity: 0.6; - transition: opacity 0.2s; + transition: text-decoration-color 0.2s; + text-decoration: underline; + text-decoration-color: transparent; + text-underline-offset: 2px; } .network-hero-link a:hover { - opacity: 1; - color: #007bff; -} - -@media (prefers-color-scheme: dark) { - .network-hero-link a:hover { - color: #7ecfef; - } + text-decoration-color: currentColor; } /* App screenshot styling with click-to-expand */ @@ -1052,7 +1027,7 @@ picture.app-screenshot img { .home-section h2 { margin-top: 0; font-size: 1.25rem; - color: var(--color-primary); + color: var(--text-strong); } .home-section p { @@ -1067,12 +1042,8 @@ picture.app-screenshot img { @media (prefers-color-scheme: dark) { .home-section { - background: #1a1a1a; - border-color: rgba(255, 255, 255, 0.08); - } - - .home-section h2 { - color: #4da6ff; + background: var(--surface); + border-color: rgba(255, 255, 255, 0.14); } .home-section p { @@ -1104,7 +1075,7 @@ picture.app-screenshot img { } .secondary-links a:hover { - color: #4da6ff; + color: #79a7d6; } } @@ -1165,8 +1136,8 @@ picture.app-screenshot img { .footer-institutional a:hover, .footer-report a:hover { - color: #4da6ff; - text-decoration-color: #4da6ff; + color: #79a7d6; + text-decoration-color: #79a7d6; } } @@ -1400,7 +1371,7 @@ picture.app-screenshot img { } .funding-enables li::before { - background: #4da6ff; + background: #79a7d6; } .funding-major-supporters { @@ -1408,7 +1379,7 @@ picture.app-screenshot img { } .funding-contact a { - color: #4da6ff; + color: #79a7d6; } .funding-donate-button { @@ -1416,7 +1387,7 @@ picture.app-screenshot img { } .funding-donate-button:hover { - background: #4da6ff; + background: #79a7d6; } .funding-ghost-keys { @@ -1451,11 +1422,11 @@ picture.app-screenshot img { @media (prefers-color-scheme: dark) { .funding-learn-more { - color: #4da6ff; + color: #79a7d6; } .funding-learn-more:hover { - color: #80bfff; + color: #9dc0e2; } } @@ -1524,8 +1495,8 @@ picture.app-screenshot img { --gk-bg: #1e1e1e; --gk-fg: #e7e9ee; --gk-muted: #9aa3b2; - --gk-accent: #4da6ff; - --gk-accent-soft: rgba(77, 166, 255, 0.16); + --gk-accent: #79a7d6; + --gk-accent-soft: rgba(121, 167, 214, 0.16); --gk-border: #2a2a2a; --gk-shadow: 0 1px 2px rgba(0,0,0,.4), 0 6px 20px rgba(0,0,0,.4); } From 1ee370121fc56fee924c3fd03fd2056227c65e0b Mon Sep 17 00:00:00 2001 From: Ian Clarke Date: Wed, 2 Sep 2026 15:31:44 -0500 Subject: [PATCH 2/4] refactor(network-hero): drop two dead bindings 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 --- hugo-site/static/js/network-hero.js | 3 --- 1 file changed, 3 deletions(-) diff --git a/hugo-site/static/js/network-hero.js b/hugo-site/static/js/network-hero.js index 148ba6a6..303d64e8 100644 --- a/hugo-site/static/js/network-hero.js +++ b/hugo-site/static/js/network-hero.js @@ -49,8 +49,6 @@ function gwColor() { return isDark() ? '#c9922b' : '#b3801f'; } function labelColor() { return isDark() ? '#5b6472' : '#8090a0'; } function innerRing() { return isDark() ? 'rgba(255,255,255,0.04)' : 'rgba(0,0,0,0.05)'; } - // Matches the page ground in each scheme; a mismatch shows as a disc edge. - function bgColor() { return isDark() ? '#15171b' : '#f8fafc'; } // --- Geometry helpers --- function locationToXY(loc) { @@ -518,7 +516,6 @@ } // Mark static dirty on resize - var origResize = window.onresize; window.addEventListener('resize', function () { dims = sizeCanvas(); staticDirty = true; }); requestAnimationFrame(frame); From f4e0fed524d235814c212138611cd434689ae4ed Mon Sep 17 00:00:00 2001 From: Ian Clarke Date: Wed, 2 Sep 2026 15:48:05 -0500 Subject: [PATCH 3/4] fix(theme): finish the palette migration outside freenet.css 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 --- hugo-site/layouts/shortcodes/os-install.html | 16 +++++----- hugo-site/static/js/network-hero.js | 29 ++++++++++++++----- .../themes/freenet/assets/css/freenet.css | 18 +++++------- .../themes/freenet/layouts/river/baseof.html | 2 +- .../layouts/shortcodes/donation-success.html | 4 +-- .../shortcodes/river-invite-button.html | 4 +-- 6 files changed, 42 insertions(+), 31 deletions(-) diff --git a/hugo-site/layouts/shortcodes/os-install.html b/hugo-site/layouts/shortcodes/os-install.html index 611569ea..452cf8ad 100644 --- a/hugo-site/layouts/shortcodes/os-install.html +++ b/hugo-site/layouts/shortcodes/os-install.html @@ -153,38 +153,38 @@ .os-install-tabs .tab-btn:hover, .os-install-tabs .tab-btn.active { - color: #4da6ff; + color: #79a7d6; } .os-install-tabs .tab-btn.active { - border-bottom-color: #4da6ff; + border-bottom-color: #79a7d6; } .os-install-tabs .install-note { - border-left-color: #4da6ff; + border-left-color: #79a7d6; background: rgba(77, 166, 255, 0.08); } .os-install-tabs .install-note strong { - color: #4da6ff; + color: #79a7d6; } .os-mobile-notice { border-color: #374151; - border-left-color: #4da6ff; + border-left-color: #79a7d6; background: rgba(77, 166, 255, 0.08); } .os-mobile-notice strong { - color: #4da6ff; + color: #79a7d6; } .os-mobile-show { - color: #4da6ff; + color: #79a7d6; } .os-mobile-show:hover { - color: #7cc0ff; + color: #9dc0e2; } .os-install-tabs .download-btn { diff --git a/hugo-site/static/js/network-hero.js b/hugo-site/static/js/network-hero.js index 303d64e8..10b22c16 100644 --- a/hugo-site/static/js/network-hero.js +++ b/hugo-site/static/js/network-hero.js @@ -446,15 +446,28 @@ loopStart = performance.now(); - // Static frame for reduced-motion + // Static frame for reduced-motion. There is no animation loop on this + // path, so the scheme poll in frame() never runs; redraw on the media + // query instead or the ring keeps the old scheme's colours for the life + // of the page. if (prefersReduced) { - var ctx = canvas.getContext('2d'); - var scale = dims.displayW / SIZE; - ctx.setTransform(dims.dpr * scale, 0, 0, dims.dpr * scale, 0, 0); - drawRing(ctx); - drawConnections(ctx, peers, connections); - drawPeers(ctx, peers); - drawCenterLabel(ctx); + var drawStatic = function () { + dims = sizeCanvas(); + var ctx = canvas.getContext('2d'); + var scale = dims.displayW / SIZE; + ctx.setTransform(1, 0, 0, 1, 0, 0); + ctx.clearRect(0, 0, canvas.width, canvas.height); + ctx.setTransform(dims.dpr * scale, 0, 0, dims.dpr * scale, 0, 0); + drawRing(ctx); + drawConnections(ctx, peers, connections); + drawPeers(ctx, peers); + drawCenterLabel(ctx); + }; + drawStatic(); + var schemeQuery = window.matchMedia('(prefers-color-scheme: dark)'); + if (schemeQuery.addEventListener) schemeQuery.addEventListener('change', drawStatic); + else if (schemeQuery.addListener) schemeQuery.addListener(drawStatic); + window.addEventListener('resize', drawStatic); return; } diff --git a/hugo-site/themes/freenet/assets/css/freenet.css b/hugo-site/themes/freenet/assets/css/freenet.css index 9e3e0128..95edf290 100644 --- a/hugo-site/themes/freenet/assets/css/freenet.css +++ b/hugo-site/themes/freenet/assets/css/freenet.css @@ -940,17 +940,23 @@ p.page-end-spacer { .network-hero-link a { font-size: 0.85rem; color: var(--accent-text); - text-decoration: none; - transition: text-decoration-color 0.2s; + transition: color 0.2s, text-decoration-color 0.2s; text-decoration: underline; text-decoration-color: transparent; text-underline-offset: 2px; } .network-hero-link a:hover { + color: var(--color-primary-hover); text-decoration-color: currentColor; } +@media (prefers-color-scheme: dark) { + .network-hero-link a:hover { + color: #9dc0e2; + } +} + /* App screenshot styling with click-to-expand */ .screenshot-container { position: relative; @@ -1382,14 +1388,6 @@ picture.app-screenshot img { color: #79a7d6; } - .funding-donate-button { - background: #0066CC; - } - - .funding-donate-button:hover { - background: #79a7d6; - } - .funding-ghost-keys { border-top-color: rgba(255, 255, 255, 0.08); } diff --git a/hugo-site/themes/freenet/layouts/river/baseof.html b/hugo-site/themes/freenet/layouts/river/baseof.html index 0c3f92d4..d65c5696 100644 --- a/hugo-site/themes/freenet/layouts/river/baseof.html +++ b/hugo-site/themes/freenet/layouts/river/baseof.html @@ -24,7 +24,7 @@ .river-page{ --rv-band:#171b22; --rv-card:#1e222b; --rv-ink:#f2f5f8; --rv-ink-soft:#aebccb; --rv-ink-faint:#93a2b3; - --rv-line:rgba(255,255,255,.10); --rv-brand:#4da6ff; --rv-brand-bright:#5cc0ff; + --rv-line:rgba(255,255,255,.10); --rv-brand:#79a7d6; --rv-brand-bright:#9dc0e2; --rv-brand-soft:rgba(77,166,255,.14); --rv-dot:#3a424e; --rv-shadow:0 1px 2px rgba(0,0,0,.4), 0 6px 18px rgba(0,0,0,.4); --rv-shadow-l:0 14px 40px rgba(0,0,0,.55); diff --git a/hugo-site/themes/freenet/layouts/shortcodes/donation-success.html b/hugo-site/themes/freenet/layouts/shortcodes/donation-success.html index 32ea02b9..c3be857b 100644 --- a/hugo-site/themes/freenet/layouts/shortcodes/donation-success.html +++ b/hugo-site/themes/freenet/layouts/shortcodes/donation-success.html @@ -141,11 +141,11 @@

Ghost Key (contains both certificate and private key)< @media (prefers-color-scheme: dark) { .ghost-key-container { - background-color: #1a1a1a; + background-color: #1c1f26; border-color: rgba(255, 255, 255, 0.08); } #combinedKey { - background-color: #121212; + background-color: #15171b; color: #e0e0e0; border-color: rgba(255, 255, 255, 0.12); } diff --git a/hugo-site/themes/freenet/layouts/shortcodes/river-invite-button.html b/hugo-site/themes/freenet/layouts/shortcodes/river-invite-button.html index 82684bf3..5c30ea33 100644 --- a/hugo-site/themes/freenet/layouts/shortcodes/river-invite-button.html +++ b/hugo-site/themes/freenet/layouts/shortcodes/river-invite-button.html @@ -92,7 +92,7 @@ @media (prefers-color-scheme: dark) { .invite-container { - background-color: #1a1a1a; + background-color: #1c1f26; border-color: rgba(77, 166, 255, 0.2); color: #f5f5f5; } @@ -134,7 +134,7 @@
Using riverctl? Copy invite code
- +

Run: riverctl invite accept <code>

From 2d4568ff14f45a7be6804e1ca07677d39e122e96 Mon Sep 17 00:00:00 2001 From: Ian Clarke Date: Wed, 2 Sep 2026 16:02:03 -0500 Subject: [PATCH 4/4] fix(theme): give the focus ring its own token; finish the glow removal Accessibility review at f4e0fed5 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 --- hugo-site/layouts/shortcodes/os-install.html | 12 +++++++----- hugo-site/themes/freenet/assets/css/base.css | 2 +- .../themes/freenet/assets/css/freenet.css | 19 +++++++++++++++---- .../themes/freenet/layouts/river/baseof.html | 2 +- .../shortcodes/river-invite-button.html | 2 +- 5 files changed, 25 insertions(+), 12 deletions(-) diff --git a/hugo-site/layouts/shortcodes/os-install.html b/hugo-site/layouts/shortcodes/os-install.html index 452cf8ad..246976e0 100644 --- a/hugo-site/layouts/shortcodes/os-install.html +++ b/hugo-site/layouts/shortcodes/os-install.html @@ -162,7 +162,7 @@ .os-install-tabs .install-note { border-left-color: #79a7d6; - background: rgba(77, 166, 255, 0.08); + background: rgba(121, 167, 214, 0.08); } .os-install-tabs .install-note strong { @@ -172,7 +172,7 @@ .os-mobile-notice { border-color: #374151; border-left-color: #79a7d6; - background: rgba(77, 166, 255, 0.08); + background: rgba(121, 167, 214, 0.08); } .os-mobile-notice strong { @@ -188,16 +188,18 @@ } .os-install-tabs .download-btn { - box-shadow: 0 4px 14px rgba(77, 166, 255, 0.25); + box-shadow: 0 1px 2px rgba(0, 0, 0, 0.4); } .os-install-tabs .download-btn:hover, .os-install-tabs .download-btn:focus { - box-shadow: 0 6px 20px rgba(77, 166, 255, 0.4); + box-shadow: 0 2px 6px rgba(0, 0, 0, 0.5); } + /* The focus ring stays coloured on purpose: it is an indicator, not + decoration. Uses the new accent so it matches the site-wide ring. */ .os-install-tabs .download-btn:focus-visible { - box-shadow: 0 0 0 4px rgba(77, 166, 255, 0.6), 0 6px 20px rgba(77, 166, 255, 0.4); + box-shadow: 0 0 0 4px rgba(121, 167, 214, 0.7), 0 2px 6px rgba(0, 0, 0, 0.5); } } diff --git a/hugo-site/themes/freenet/assets/css/base.css b/hugo-site/themes/freenet/assets/css/base.css index 5bb31f6d..7adc12f3 100644 --- a/hugo-site/themes/freenet/assets/css/base.css +++ b/hugo-site/themes/freenet/assets/css/base.css @@ -172,7 +172,7 @@ a:focus-visible, button:focus-visible, label:focus-visible, summary:focus-visible { - outline: 2px solid var(--color-primary); + outline: 2px solid var(--focus-ring, var(--color-primary)); outline-offset: 1px; } diff --git a/hugo-site/themes/freenet/assets/css/freenet.css b/hugo-site/themes/freenet/assets/css/freenet.css index 95edf290..9b7e9f9e 100644 --- a/hugo-site/themes/freenet/assets/css/freenet.css +++ b/hugo-site/themes/freenet/assets/css/freenet.css @@ -78,12 +78,14 @@ /* Custom font and color variables */ :root { + color-scheme: light dark; --font-display: 'Outfit', system-ui, sans-serif; --font-body: 'Source Sans 3', system-ui, sans-serif; --font-code: 'Inconsolata', 'Hack', 'SF Mono', 'Roboto Mono', 'Source Code Pro', 'Ubuntu Mono', monospace; --color-primary: #0066CC; /* button fill; white text sits on this */ --color-primary-hover: #004C99; --accent-text: #0066CC; /* accent used AS text, on the page ground */ + --focus-ring: #0066CC; /* needs 3:1 against every surface it lands on */ /* Surface and text roles. These carry everything base.css paints; Bulma derived the same values from its own scheme variables. Declaring both @@ -122,10 +124,19 @@ :root { --text-strong: #ebecf0; --text: #abb1bf; - /* Same luminance as the old #4da6ff (so contrast is unchanged at ~7:1) with - roughly half the chroma. The neon read came from saturation, not - brightness, so this is a chroma cut rather than a dimming. */ + /* Essentially the same relative luminance as the old #4da6ff (0.3656 vs + 0.3607, i.e. a shade lighter) with roughly half the chroma. The neon read + came from saturation, not brightness, so this is a chroma cut rather than + a dimming. Accent-on-ground lands at 7.10:1 here; it was 7.33:1 before, + the small drop coming from the ground being lifted, not from this value. */ --accent-text: #79a7d6; + /* #0066CC measures only 2.96:1 against --surface, failing SC 1.4.11 for a + focus indicator. The accent gives 7.10 on the ground and 6.53 on cards. */ + --focus-ring: #79a7d6; + /* 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: 1.05:1, + which made the wordmark vanish on Tab. */ + --navbar-item-hover: #232a33; --border: #353a46; --surface: #1c1f26; --surface-soft: #232a33; @@ -369,7 +380,7 @@ @media (prefers-color-scheme: dark) { .home-browser { - background: #1a1a1a; + background: var(--surface); border-color: rgba(255, 255, 255, 0.1); box-shadow: 0 18px 50px rgba(0, 0, 0, 0.5); } diff --git a/hugo-site/themes/freenet/layouts/river/baseof.html b/hugo-site/themes/freenet/layouts/river/baseof.html index d65c5696..d5cec6b8 100644 --- a/hugo-site/themes/freenet/layouts/river/baseof.html +++ b/hugo-site/themes/freenet/layouts/river/baseof.html @@ -25,7 +25,7 @@ --rv-band:#171b22; --rv-card:#1e222b; --rv-ink:#f2f5f8; --rv-ink-soft:#aebccb; --rv-ink-faint:#93a2b3; --rv-line:rgba(255,255,255,.10); --rv-brand:#79a7d6; --rv-brand-bright:#9dc0e2; - --rv-brand-soft:rgba(77,166,255,.14); --rv-dot:#3a424e; + --rv-brand-soft:rgba(121,167,214,.14); --rv-dot:#3a424e; --rv-shadow:0 1px 2px rgba(0,0,0,.4), 0 6px 18px rgba(0,0,0,.4); --rv-shadow-l:0 14px 40px rgba(0,0,0,.55); } diff --git a/hugo-site/themes/freenet/layouts/shortcodes/river-invite-button.html b/hugo-site/themes/freenet/layouts/shortcodes/river-invite-button.html index 5c30ea33..5faf2c4a 100644 --- a/hugo-site/themes/freenet/layouts/shortcodes/river-invite-button.html +++ b/hugo-site/themes/freenet/layouts/shortcodes/river-invite-button.html @@ -93,7 +93,7 @@ @media (prefers-color-scheme: dark) { .invite-container { background-color: #1c1f26; - border-color: rgba(77, 166, 255, 0.2); + border-color: rgba(121, 167, 214, 0.2); color: #f5f5f5; }