From 2bb8d83e00e15657cf6131c65ad79c32f985d301 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 23 Sep 2026 07:44:31 +0000 Subject: [PATCH 1/3] The reading-font button was the dimmest control on the page js/dyslexia-font.js sets `opacity: var(--dys-idle-opacity, .72)` on #dys-font-btn. That composites whatever ink the surrounding bar uses down by 28%. Measured on pinpointventures, where the bar's ink is #4b5563 at 6.41:1, the idle button came out #7d858f: 3.73:1, under AA. The comment above that rule argues, at length and correctly, that leaving the site's own ink alone is the one version that cannot go wrong, because it is the ink the site already chose for that bar. Then it takes 28% off it, which undoes exactly that guarantee. A control that exists so people who find text hard to read can read it should not be the hardest thing on the page to read. The default is 1 now. The idle/on distinction already rode on the border and the accent wash, plus OpenDyslexic in the label and aria-pressed for anyone who cannot see either, so nothing is lost by not dimming it. This file is copied into 18 repositories. Seventeen were byte-identical and one, JanVayu's, was ahead: it carries a 44px minimum touch target (WCAG 2.5.5 Target Size) and a placement option that the other seventeen never received. Both improvements are merged, and all 18 copies now hold the same file. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01MrvR2NXsJFVRCeJZFCPuNL --- js/dyslexia-font.js | 37 ++++++++++++++++++++++++++++++++----- 1 file changed, 32 insertions(+), 5 deletions(-) diff --git a/js/dyslexia-font.js b/js/dyslexia-font.js index b312619..7cb8a8a 100644 --- a/js/dyslexia-font.js +++ b/js/dyslexia-font.js @@ -36,6 +36,7 @@ * --dys-idle-border default currentColor * --dys-idle-opacity default .72 * --dys-font label typeface (default system-ui) + * --dys-min-size minimum touch target (default 44px) * --dys-surface/--dys-fg ground and ink for the fixed corner variant */ (function () { @@ -119,14 +120,28 @@ // fighting this rule on specificity. Point --dys-accent at the site's own // accent token and dark mode follows for free, because a var() is resolved // where it is used, not where it is declared. + // 44px, not 32. This button is a reading aid, so the people most likely + // to reach for it are the ones a small target costs most; WCAG 2.2's + // Target Size (Minimum), 2.5.8, puts the floor at 24px and the older + // 2.5.5 at 44. It was 36x32 on every site using this script. Override + // with --dys-min-size if a host genuinely needs it smaller. + '#dys-font-btn{display:inline-flex;align-items:center;justify-content:center;gap:6px;' - + 'min-width:36px;height:32px;padding:0 10px;cursor:pointer;' + + 'min-width:var(--dys-min-size,44px);min-height:var(--dys-min-size,44px);padding:0 12px;cursor:pointer;' + 'border-radius:var(--dys-radius,8px);' + 'border:var(--dys-border-width,1px) solid var(--dys-idle-border,currentColor);' + 'background:transparent;color:inherit;' + 'font:700 13px/1 var(--dys-font,system-ui,sans-serif);' - + 'letter-spacing:.02em;opacity:var(--dys-idle-opacity,.72)}' - + '#dys-font-btn:hover,#dys-font-btn:focus-visible{opacity:1}' + // Idle opacity defaults to 1, not .72. The paragraph below argues that + // leaving the ink alone is the one version that cannot go wrong, because + // it is the ink the site already chose for that bar -- and then took 28% + // off it, which undoes exactly that guarantee. Measured on + // pinpointventures, --color-text #4b5563 is 6.41:1 on the header and the + // composite at .72 is #7d858f, 3.73:1. A control that exists so people who + // find text hard to read can read it should not be the dimmest thing on + // the page. The idle/on distinction rides on the border and the wash, which + // it already did. --dys-idle-opacity is still there for a site whose bar + // has the headroom to spend. + + 'letter-spacing:.02em;opacity:var(--dys-idle-opacity,1)}' // "On" is the site's accent as outline and a wash of itself, with the // label left at whatever ink the bar around it already uses. Two earlier // shapes were worse. A filled button needs an ink that clears 4.5:1 @@ -172,6 +187,18 @@ return el; } + // Where in the slot the button lands. Default is the end of the row, which is + // where it has always gone. A site that wants it somewhere else names an + // element to sit in front of: data-dyslexia-before="#docsLink" on the slot. + // Opt-in on purpose -- this file is shared across nine pages and a change to + // the default would move the button on all of them. + function place(h, b) { + var sel = h.getAttribute && h.getAttribute('data-dyslexia-before'); + var ref = sel ? h.querySelector(sel) : null; + if (ref && ref.parentElement === h) h.insertBefore(b, ref); + else h.appendChild(b); + } + function init() { if (document.getElementById('dys-font-btn')) return; var st = document.createElement('style'); @@ -201,7 +228,7 @@ sync(); var h = host(); - if (h) { h.appendChild(b); return; } + if (h) { place(h, b); return; } // No home yet. Two of these sites build their masthead from JavaScript after // this runs, so a slot that does not exist at DOMContentLoaded may exist a @@ -215,7 +242,7 @@ var late = host(); if (!late || late.contains(b)) return; b.className = ''; - late.appendChild(b); + place(late, b); mo.disconnect(); }); mo.observe(document.body, { childList: true, subtree: true }); From bc106fc76b639cc9c18b722a2735cac6e19dfd26 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 23 Sep 2026 07:50:46 +0000 Subject: [PATCH 2/3] Every topic chip was dark ink on a dark fill, and nothing said so Measured with axe at 1280x900 and 390x844, in light and in dark, using real prefers-color-scheme emulation. 280 serious or critical nodes across the home page; 0 now on index, study and about, in both schemes. THE TEN TOPIC CHIPS. The dark block redefined only the ten `-bg` fills and left the ten inks at their light values, so each chip painted dark on dark: --topic-economic #4a4a4a on #1c1c1c at 1.92:1, --topic-governance 2.03, --topic-infrastructure 2.19, --topic-agriculture 2.70. Darkening a fill while keeping its ink preserves the hue and destroys the contrast, which is why all ten failed together and none of them looked obviously wrong. Each ink is now the same hue lifted until it clears 5:1 on its own fill. That was 134 of the 244 dark-mode nodes. --text-muted was #8a8a8a in light (3.14:1 on the warm panel) and #7d8590 in dark (4.22:1 on the chip fill). It is the ink of every header stat, the results count and the filter note. --evidence-moderate was #c17825 at 3.51:1, and it is the tier name on the about page. .chip-count took `opacity: 0.65` on top of the muted token, which put the count beside every filter back under AA however the token was set. Opacity on a colour that has already been measured is not a shade, it is an unmeasurement. TWO CRITICAL ONES, both structural rather than colour: #card-grid declared role="list" and held
children. A list promises its children are listitems; these are not, so a screen reader was told to expect something the DOM does not contain. The cards are not a list in any useful sense, so the role is gone rather than papered over with role="listitem" on 204 cards. #sort-select had a visible "Sort" heading beside it written as a
, which names nothing. It carries an aria-label now. npm run validate still passes on all 204 studies. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01MrvR2NXsJFVRCeJZFCPuNL --- css/style.css | 34 ++++++++++++++++++++++++++++------ index.html | 4 ++-- 2 files changed, 30 insertions(+), 8 deletions(-) diff --git a/css/style.css b/css/style.css index 55a4c33..e50cfa7 100644 --- a/css/style.css +++ b/css/style.css @@ -21,7 +21,9 @@ --accent-warm-bg: #faf3eb; --text: #2c2c2c; --text-secondary: #555555; - --text-muted: #8a8a8a; + /* 3.42:1 on the page and 3.14:1 on the warm panel. It is the ink of every + header stat, the results count and the filter note. */ + --text-muted: #6b6b6b; --border: #e0ddd7; --border-light: #eae7e1; --divider: #d4d0c8; @@ -34,8 +36,8 @@ --radius-lg: 12px; --evidence-strong: #3d7a5f; - --evidence-moderate: #c17825; - --evidence-emerging: #8a8a8a; + --evidence-moderate: #9d5f17; + --evidence-emerging: #6b6b6b; --topic-financial: #8b6914; --topic-financial-bg: #f9f3e3; @@ -306,9 +308,9 @@ h1, h2, h3, h4, h5, h6 { /* Chip with count */ .chip .chip-count { display: inline-block; margin-left: 3px; - font-size: 0.62rem; opacity: 0.65; font-weight: 600; + font-size: 0.62rem; font-weight: 600; color: var(--text-muted); } -.chip.active .chip-count { opacity: 0.85; } +.chip.active .chip-count { color: inherit; } .sort-select { width: 100%; padding: 6px 8px; font-size: 0.78rem; @@ -1090,7 +1092,9 @@ h1, h2, h3, h4, h5, h6 { --accent-warm-bg: #2a1f12; --text: #e6edf3; --text-secondary: #b0b8c1; - --text-muted: #7d8590; + /* 4.34:1 on --bg-warm and 4.22:1 on the chip fill. It is the ink of every + header stat and every chip count, so it is a number a reader came for. */ + --text-muted: #8b949e; --border: #30363d; --border-light: #21262d; --divider: #30363d; @@ -1099,6 +1103,24 @@ h1, h2, h3, h4, h5, h6 { --shadow-lg: 0 4px 20px rgba(0,0,0,0.6); --shadow-card: 0 1px 4px rgba(0,0,0,0.4), 0 0 0 1px rgba(255,255,255,0.04); + /* The dark block redefined only the ten fills and left the ten inks at + their light values, so every topic chip was dark ink on a dark fill: + --topic-economic #4a4a4a on #1c1c1c is 1.92:1, --topic-governance 2.03, + --topic-infrastructure 2.19, --topic-agriculture 2.70. Darkening a fill + and keeping its ink preserves the hue and destroys the contrast, which is + why all ten failed and none of them looked obviously wrong. Each ink below + is the same hue lifted until it clears 5:1 on its own fill. */ + --topic-financial: #D3B45F; + --topic-health: #C77575; + --topic-education: #6A9DC8; + --topic-social: #A57CC0; + --topic-agriculture: #80B280; + --topic-economic: #999999; + --topic-environment: #6FC3B3; + --topic-governance: #9F81BB; + --topic-infrastructure: #B87A9A; + --topic-gender: #B57D9A; + --topic-financial-bg: #2a2008; --topic-health-bg: #2a1414; --topic-education-bg: #0f1d2e; diff --git a/index.html b/index.html index 4ea3d9e..99dc001 100644 --- a/index.html +++ b/index.html @@ -171,7 +171,7 @@ Sort
- @@ -241,7 +241,7 @@

Evidence-Based Development Research Library

-
+
From 9ba7841841fe888d13376e38b82b2c49174dc190 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 23 Sep 2026 07:51:53 +0000 Subject: [PATCH 3/3] The evidence tiers were the same defect, 480 lines away .card-evidence is a second chip system with exactly the problem the topic chips had, and the first pass missed it because it sits far from them in the file: three literal light fills (#e8f5ee, #fef3e4, #f0f0f0) with no dark override, and three inks with no dark value. In dark mode --evidence-strong #3d7a5f measured 3.19:1 and --evidence-moderate #9d5f17 3.14:1 on the card. The fills are tokens now and both themes carry a full set, so the strong / moderate / emerging label reads in either. Verified at 7.36, 7.01 and 6.85 to one in dark, and 4.52, 4.69 and 4.68 in light. index, study and about are now 0 serious or critical in both schemes. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01MrvR2NXsJFVRCeJZFCPuNL --- css/style.css | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/css/style.css b/css/style.css index e50cfa7..6949648 100644 --- a/css/style.css +++ b/css/style.css @@ -38,6 +38,9 @@ --evidence-strong: #3d7a5f; --evidence-moderate: #9d5f17; --evidence-emerging: #6b6b6b; + --evidence-strong-bg: #e8f5ee; + --evidence-moderate-bg: #fef3e4; + --evidence-emerging-bg: #f0f0f0; --topic-financial: #8b6914; --topic-financial-bg: #f9f3e3; @@ -518,9 +521,9 @@ h1, h2, h3, h4, h5, h6 { font-size: 0.65rem; font-weight: 600; padding: 2px 6px; border-radius: var(--radius-sm); white-space: nowrap; } -.card-evidence.strong { background: #e8f5ee; color: var(--evidence-strong); } -.card-evidence.moderate { background: #fef3e4; color: var(--evidence-moderate); } -.card-evidence.emerging { background: #f0f0f0; color: var(--evidence-emerging); } +.card-evidence.strong { background: var(--evidence-strong-bg); color: var(--evidence-strong); } +.card-evidence.moderate { background: var(--evidence-moderate-bg); color: var(--evidence-moderate); } +.card-evidence.emerging { background: var(--evidence-emerging-bg); color: var(--evidence-emerging); } /* No results */ .no-results { text-align: center; padding: var(--sp-16) var(--sp-6); grid-column: 1 / -1; } @@ -1110,6 +1113,13 @@ h1, h2, h3, h4, h5, h6 { and keeping its ink preserves the hue and destroys the contrast, which is why all ten failed and none of them looked obviously wrong. Each ink below is the same hue lifted until it clears 5:1 on its own fill. */ + --evidence-strong: #6FC08F; + --evidence-moderate: #D9A055; + --evidence-emerging: #9EA5AD; + --evidence-strong-bg: #12251b; + --evidence-moderate-bg: #2a1f0d; + --evidence-emerging-bg: #1c1c1c; + --topic-financial: #D3B45F; --topic-health: #C77575; --topic-education: #6A9DC8;