diff --git a/CHANGELOG.md b/CHANGELOG.md
index c6bbbded..573ca3f3 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -18,6 +18,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## Added
+### Settings — OpenDyslexic font option for dyslexic readers
+
+**By [@Psychotoxical](https://github.com/Psychotoxical), PR [#507](https://github.com/Psychotoxical/psysonic/pull/507)**
+
+* Next step on the accessibility track after the colour-side work (WCAG contrast audits across every theme, dedicated colour-vision-deficiency theme variants for protanopia / deuteranopia / tritanopia): a dyslexia-friendly **OpenDyslexic** font option in the existing font picker.
+* OpenDyslexic uses a heavier weighted baseline and asymmetric glyph shapes — `b`/`d`, `p`/`q` never mirror, italics are differentiated forms rather than slanted regulars — which many dyslexic readers find easier to track than a typical sans.
+* Bundled locally via `@fontsource/opendyslexic` (SIL OFL, freely redistributable). No CDN dependency. The Settings picker grew an optional **subtitle** field on font entries so the OpenDyslexic row carries a "dyslexia-friendly · no RU/ZH support" hint without cluttering the other 14 fonts.
+* **Latin + Latin-extended only.** Cyrillic and CJK locales (RU, ZH) fall back to the system font when OpenDyslexic is selected; the subtitle calls that out upfront. Subtitle text is translated in all 8 locales.
+
### Lossless Albums — rail on Home + dedicated page + sidebar entry
**By [@Psychotoxical](https://github.com/Psychotoxical), PR [#506](https://github.com/Psychotoxical/psysonic/pull/506)**
diff --git a/nix/upstream-sources.json b/nix/upstream-sources.json
index 3eff282e..9c19a1f5 100644
--- a/nix/upstream-sources.json
+++ b/nix/upstream-sources.json
@@ -1,3 +1,3 @@
{
- "npmDepsHash": "sha256-3zwfk7gxSYXoiOwNyPHP+K9P7yAksGKXQvwtvic/IdQ="
+ "npmDepsHash": "sha256-5u9O1O7q936/Ik7/X++pK2Bg65+GYh0RdL4YVvasNSQ="
}
diff --git a/package-lock.json b/package-lock.json
index 7b26491d..36221c48 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -22,6 +22,7 @@
"@fontsource-variable/rubik": "^5.2.8",
"@fontsource-variable/space-grotesk": "^5.2.10",
"@fontsource-variable/unbounded": "^5.2.8",
+ "@fontsource/opendyslexic": "^5.2.5",
"@tanstack/react-virtual": "^3.13.24",
"@tauri-apps/api": "^2",
"@tauri-apps/plugin-dialog": "^2.7.1",
@@ -668,6 +669,15 @@
"url": "https://github.com/sponsors/ayuhito"
}
},
+ "node_modules/@fontsource/opendyslexic": {
+ "version": "5.2.5",
+ "resolved": "https://registry.npmjs.org/@fontsource/opendyslexic/-/opendyslexic-5.2.5.tgz",
+ "integrity": "sha512-NNS9aaPQx2TlaTvb3vTEjw3xz8lKj23mBc+6rM00mSNFDygdoll0/nLMHFtDKKrBT6sMfY6TFFPOR0D9ktdspg==",
+ "license": "OFL-1.1",
+ "funding": {
+ "url": "https://github.com/sponsors/ayuhito"
+ }
+ },
"node_modules/@jridgewell/sourcemap-codec": {
"version": "1.5.5",
"resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz",
diff --git a/package.json b/package.json
index 52dd16d6..acf8a184 100644
--- a/package.json
+++ b/package.json
@@ -26,6 +26,7 @@
"@fontsource-variable/rubik": "^5.2.8",
"@fontsource-variable/space-grotesk": "^5.2.10",
"@fontsource-variable/unbounded": "^5.2.8",
+ "@fontsource/opendyslexic": "^5.2.5",
"@tanstack/react-virtual": "^3.13.24",
"@tauri-apps/api": "^2",
"@tauri-apps/plugin-dialog": "^2.7.1",
diff --git a/src/locales/de.ts b/src/locales/de.ts
index 38a2def7..73af0b74 100644
--- a/src/locales/de.ts
+++ b/src/locales/de.ts
@@ -587,6 +587,7 @@ export const deTranslation = {
languageNb: 'Norwegisch',
languageRu: 'Russisch',
font: 'Schriftart',
+ fontHintOpenDyslexic: 'Legasthenie-freundlich · keine RU/ZH-Unterstützung',
theme: 'Design',
appearance: 'Darstellung',
servers: 'Server',
diff --git a/src/locales/en.ts b/src/locales/en.ts
index 85539d50..5d27075a 100644
--- a/src/locales/en.ts
+++ b/src/locales/en.ts
@@ -590,6 +590,7 @@ export const enTranslation = {
languageRu: 'Russian',
languageEs: 'Spanish',
font: 'Font',
+ fontHintOpenDyslexic: 'Dyslexia-friendly · no RU/ZH support',
theme: 'Theme',
appearance: 'Appearance',
servers: 'Servers',
diff --git a/src/locales/es.ts b/src/locales/es.ts
index 6b70426c..2dd6447c 100644
--- a/src/locales/es.ts
+++ b/src/locales/es.ts
@@ -580,6 +580,7 @@ export const esTranslation = {
languageRu: 'Ruso',
languageEs: 'Español',
font: 'Fuente',
+ fontHintOpenDyslexic: 'Compatible con dislexia · sin soporte RU/ZH',
theme: 'Tema',
appearance: 'Apariencia',
servers: 'Servidores',
diff --git a/src/locales/fr.ts b/src/locales/fr.ts
index c22fca92..34d8e6b0 100644
--- a/src/locales/fr.ts
+++ b/src/locales/fr.ts
@@ -577,6 +577,7 @@ export const frTranslation = {
languageNb: 'Norvégien',
languageRu: 'Russe',
font: 'Police',
+ fontHintOpenDyslexic: 'Adapté à la dyslexie · sans prise en charge RU/ZH',
theme: 'Thème',
appearance: 'Apparence',
servers: 'Serveurs',
diff --git a/src/locales/nb.ts b/src/locales/nb.ts
index 4e247261..79690cdc 100644
--- a/src/locales/nb.ts
+++ b/src/locales/nb.ts
@@ -577,6 +577,7 @@ export const nbTranslation = {
languageNb: 'Norsk',
languageRu: 'Russisk',
font: 'Skrifttype',
+ fontHintOpenDyslexic: 'Dyslexivennlig · ingen RU/ZH-støtte',
theme: 'Tema',
appearance: 'Utseende',
servers: 'Tjenere',
diff --git a/src/locales/nl.ts b/src/locales/nl.ts
index cd25392a..745c057a 100644
--- a/src/locales/nl.ts
+++ b/src/locales/nl.ts
@@ -576,6 +576,7 @@ export const nlTranslation = {
languageNb: 'Noors',
languageRu: 'Russisch',
font: 'Lettertype',
+ fontHintOpenDyslexic: 'Dyslexie-vriendelijk · geen RU/ZH-ondersteuning',
theme: 'Thema',
appearance: 'Weergave',
servers: 'Servers',
diff --git a/src/locales/ru.ts b/src/locales/ru.ts
index 1f2507f9..96acb8f4 100644
--- a/src/locales/ru.ts
+++ b/src/locales/ru.ts
@@ -611,6 +611,7 @@ export const ruTranslation = {
languageRu: 'Русский',
languageRu2: 'Русский 2',
font: 'Шрифт',
+ fontHintOpenDyslexic: 'Подходит для дислексии · без поддержки RU/ZH',
theme: 'Тема',
appearance: 'Оформление',
servers: 'Серверы',
diff --git a/src/locales/zh.ts b/src/locales/zh.ts
index ffcdab2e..84065055 100644
--- a/src/locales/zh.ts
+++ b/src/locales/zh.ts
@@ -571,6 +571,7 @@ export const zhTranslation = {
languageNb: '挪威',
languageRu: '俄语',
font: '字体',
+ fontHintOpenDyslexic: '阅读障碍友好 · 不支持俄文/中文',
theme: '主题',
appearance: '外观',
servers: '服务器',
diff --git a/src/pages/Settings.tsx b/src/pages/Settings.tsx
index bb2078c6..953cd5ac 100644
--- a/src/pages/Settings.tsx
+++ b/src/pages/Settings.tsx
@@ -373,6 +373,7 @@ const CONTRIBUTORS = [
'Home: "Because you listened" recommendation rail — Last.fm-anchored similar-artist surfacing with round-robin anchor rotation per server (PR #489)',
'Song Info: absolute file path on Navidrome via native /api/song/{id} — Subsonic only ever returned a relative path (or none on Navidrome), the native endpoint surfaces the full server-side location (PR #504)',
'Home: Lossless Albums rail + dedicated /lossless-albums page with infinite scroll and header parity (selection mode, enqueue, offline, download ZIPs), streaming load via per-fetch onProgress, sidebar entry default visible, detection via Navidrome native bit_depth-sorted song cursor with always-lossless suffix allowlist (PR #506)',
+ 'Accessibility: OpenDyslexic font option in the Settings picker — bundled locally via @fontsource/opendyslexic, asymmetric glyph shapes for easier b/d, p/q tracking, Latin-only with translated subtitle in all 8 locales calling out the dyslexia-friendly intent and the Cyrillic/CJK fallback (PR #507)',
],
},
] as const;
@@ -3832,6 +3833,7 @@ export default function Settings() {
{ id: 'jetbrains-mono', label: 'JetBrains Mono' },
{ id: 'golos-text', label: 'Golos Text' },
{ id: 'unbounded', label: 'Unbounded' },
+ { id: 'opendyslexic', label: 'OpenDyslexic' },
] as { id: FontId; label: string }[]).find(f => f.id === fontStore.font)?.label ?? fontStore.font
}
@@ -3854,15 +3856,25 @@ export default function Settings() {
{ id: 'jetbrains-mono', label: 'JetBrains Mono', stack: "'JetBrains Mono Variable', monospace" },
{ id: 'golos-text', label: 'Golos Text', stack: "'Golos Text Variable', sans-serif" },
{ id: 'unbounded', label: 'Unbounded', stack: "'Unbounded Variable', sans-serif" },
- ] as { id: FontId; label: string; stack: string }[]
+ { id: 'opendyslexic', label: 'OpenDyslexic', stack: "'OpenDyslexic', sans-serif", hint: t('settings.fontHintOpenDyslexic') },
+ ] as { id: FontId; label: string; stack: string; hint?: string }[]
).map(f => (
))}
diff --git a/src/store/fontStore.ts b/src/store/fontStore.ts
index 00042820..18718bcc 100644
--- a/src/store/fontStore.ts
+++ b/src/store/fontStore.ts
@@ -1,7 +1,7 @@
import { create } from 'zustand';
import { persist } from 'zustand/middleware';
-export type FontId = 'inter' | 'outfit' | 'dm-sans' | 'nunito' | 'rubik' | 'space-grotesk' | 'figtree' | 'manrope' | 'plus-jakarta-sans' | 'lexend' | 'geist' | 'jetbrains-mono' | 'golos-text' | 'unbounded';
+export type FontId = 'inter' | 'outfit' | 'dm-sans' | 'nunito' | 'rubik' | 'space-grotesk' | 'figtree' | 'manrope' | 'plus-jakarta-sans' | 'lexend' | 'geist' | 'jetbrains-mono' | 'golos-text' | 'unbounded' | 'opendyslexic';
interface FontState {
font: FontId;
diff --git a/src/styles/theme.css b/src/styles/theme.css
index 2396273d..e6ddb11b 100644
--- a/src/styles/theme.css
+++ b/src/styles/theme.css
@@ -24,6 +24,12 @@
@import '@fontsource-variable/jetbrains-mono/wght-italic.css';
@import '@fontsource-variable/golos-text';
@import '@fontsource-variable/unbounded';
+/* OpenDyslexic — non-variable font with discrete weights/styles. Latin only,
+ no Cyrillic / no CJK. Selected via Settings as a dyslexia-friendly option. */
+@import '@fontsource/opendyslexic/400.css';
+@import '@fontsource/opendyslexic/700.css';
+@import '@fontsource/opendyslexic/400-italic.css';
+@import '@fontsource/opendyslexic/700-italic.css';
/* ─── Catppuccin Mocha Variables ─── */
:root,
@@ -6516,6 +6522,11 @@ input[type="range"]:hover::-webkit-slider-thumb {
--font-display: 'Unbounded Variable', sans-serif;
}
+[data-font='opendyslexic'] {
+ --font-sans: 'OpenDyslexic', system-ui, sans-serif;
+ --font-display: 'OpenDyslexic', sans-serif;
+}
+
/* ─────────────────────────────────────────────────────────────
Middle Earth — Epic Fantasy / Cinematic
───────────────────────────────────────────────────────────── */