mirror of
https://github.com/Psychotoxical/psysonic.git
synced 2026-07-22 15:25:46 +00:00
feat(settings): OpenDyslexic font option for dyslexic readers (#507)
* feat(settings): OpenDyslexic font option for dyslexic readers Next step on the accessibility track. The first pass was on the colour side — WCAG contrast audits across every theme and dedicated colour- vision-deficiency variants for the protanopia / deuteranopia / tritan- opia palettes. Typography is the other axis: some users with dyslexia find a font with a heavier weighted baseline and asymmetric glyph shapes (b/d, p/q never mirror, italic forms differentiated rather than slanted-regular) easier to track than a typical sans. Adds OpenDyslexic to the existing Fontsource font picker. SIL OFL licensed, freely redistributable, and the de-facto open-source standard for this use case. Non-variable axis, ships as four discrete weight/style files (regular, bold, italic, bold-italic) — the Settings picker grew an optional `hint` field on font entries so this one row can carry a "dyslexia-friendly · no RU/ZH support" subtitle without bloating the other 14 entries. Latin + Latin-extended only. Cyrillic and CJK locales (RU, ZH) fall back to the system font when this is selected; the subtitle calls out that limitation upfront. i18n: hint string in all 8 locales (settings.fontHintOpenDyslexic). Accessibility is intentional product positioning here — it's an underserved corner of the Subsonic-client ecosystem. * chore(nix): sync npmDepsHash with package-lock.json * docs: changelog entry for PR #507 Logs the OpenDyslexic font option in v1.46.0 "## Added". * docs(settings): contributor entry for PR #507 Adds the OpenDyslexic accessibility bullet to Psychotoxical's contributions list. --------- Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
This commit is contained in:
committed by
GitHub
parent
bd56177e2c
commit
f520f7951a
@@ -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)**
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
{
|
||||
"npmDepsHash": "sha256-3zwfk7gxSYXoiOwNyPHP+K9P7yAksGKXQvwtvic/IdQ="
|
||||
"npmDepsHash": "sha256-5u9O1O7q936/Ik7/X++pK2Bg65+GYh0RdL4YVvasNSQ="
|
||||
}
|
||||
|
||||
Generated
+10
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -611,6 +611,7 @@ export const ruTranslation = {
|
||||
languageRu: 'Русский',
|
||||
languageRu2: 'Русский 2',
|
||||
font: 'Шрифт',
|
||||
fontHintOpenDyslexic: 'Подходит для дислексии · без поддержки RU/ZH',
|
||||
theme: 'Тема',
|
||||
appearance: 'Оформление',
|
||||
servers: 'Серверы',
|
||||
|
||||
@@ -571,6 +571,7 @@ export const zhTranslation = {
|
||||
languageNb: '挪威',
|
||||
languageRu: '俄语',
|
||||
font: '字体',
|
||||
fontHintOpenDyslexic: '阅读障碍友好 · 不支持俄文/中文',
|
||||
theme: '主题',
|
||||
appearance: '外观',
|
||||
servers: '服务器',
|
||||
|
||||
+15
-3
@@ -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
|
||||
}</span>
|
||||
<ChevronDown size={14} style={{ color: 'var(--text-muted)', transform: fontPickerOpen ? 'rotate(180deg)' : 'none', transition: 'transform 0.2s' }} />
|
||||
@@ -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 => (
|
||||
<button
|
||||
key={f.id}
|
||||
className={`btn ${fontStore.font === f.id ? 'btn-primary' : 'btn-ghost'}`}
|
||||
style={{ justifyContent: 'flex-start', fontFamily: f.stack }}
|
||||
style={{
|
||||
justifyContent: 'flex-start',
|
||||
fontFamily: f.stack,
|
||||
...(f.hint ? { flexDirection: 'column', alignItems: 'flex-start', gap: '2px', paddingTop: '8px', paddingBottom: '8px' } : null),
|
||||
}}
|
||||
onClick={() => { fontStore.setFont(f.id); setFontPickerOpen(false); }}
|
||||
>
|
||||
{f.label}
|
||||
<span>{f.label}</span>
|
||||
{f.hint && (
|
||||
<span style={{ fontSize: 11, color: 'var(--text-muted)', fontFamily: 'var(--font-sans)' }}>
|
||||
{f.hint}
|
||||
</span>
|
||||
)}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
───────────────────────────────────────────────────────────── */
|
||||
|
||||
Reference in New Issue
Block a user