mirror of
https://github.com/pewdiepie-archdaemon/odysseus.git
synced 2026-06-24 05:35:31 -04:00
feat(a11y): add a Text size control and an OpenDyslexic font option (#4210)
* feat(a11y): add a Text size control and an OpenDyslexic font option Text size: a Theme > Font & Layout control (Default / Larger) that scales the whole UI via CSS zoom, so the many hard-coded px sizes scale too (density only moves the root font-size). Stored globally so it persists across theme switches; applied early in the boot script to avoid a flash. OpenDyslexic: a dyslexia-friendly self-hosted font (SIL OFL 1.1), bundled as woff2 alongside Fira Code/Inter and wired into the Font select. Reuses the existing density/font pattern end to end; no new colours, spacing, or component styles. * fix(a11y): keep modals on-screen at Larger text size Inline vh heights on .modal-content overrode the ui-scale-125 max-height compensation, so Cookbook (and the email/doc/skills/PDF modals) overflowed the viewport at 125% — pushing the header and close button off-screen. Let the compensation own those heights. * fix(a11y): keep PDF export modal at its original 86vh on Default size
This commit is contained in:
@@ -666,7 +666,7 @@ import * as Modals from './modalManager.js';
|
||||
overlay.className = 'modal pdf-export-overlay';
|
||||
overlay.style.cssText = 'pointer-events:auto;background:rgba(0,0,0,0.5);backdrop-filter:blur(4px);';
|
||||
overlay.innerHTML = `
|
||||
<div class="modal-content" style="width:min(780px,94vw);max-height:86vh;">
|
||||
<div class="modal-content" style="width:min(780px,94vw);">
|
||||
<div class="modal-header">
|
||||
<h4>Export filled PDF</h4>
|
||||
<button id="pdf-export-close" class="modal-close" title="Close">×</button>
|
||||
|
||||
@@ -1595,7 +1595,7 @@ let _libraryArchivedView = false; // Documents tab showing archived docs?
|
||||
modal.className = 'modal';
|
||||
modal.id = 'doclib-modal';
|
||||
modal.innerHTML = `
|
||||
<div class="modal-content doclib-modal-content" style="width:min(640px, 92vw);max-height:85vh;background:var(--bg);">
|
||||
<div class="modal-content doclib-modal-content" style="width:min(640px, 92vw);background:var(--bg);">
|
||||
<div class="modal-header">
|
||||
<!-- Header title + icon mirror the currently-active sub-tab (Chats /
|
||||
Documents / Research / Archive) so the user sees ONE icon at
|
||||
|
||||
@@ -858,7 +858,7 @@ export function openEmailLibrary(opts = {}) {
|
||||
modal.className = 'modal';
|
||||
modal.id = 'email-lib-modal';
|
||||
modal.innerHTML = `
|
||||
<div class="modal-content doclib-modal-content" style="width:min(720px, 92vw);max-height:85vh;background:var(--bg);">
|
||||
<div class="modal-content doclib-modal-content" style="width:min(720px, 92vw);background:var(--bg);">
|
||||
<div class="modal-header">
|
||||
<h4>
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="vertical-align:-2px;margin-right:4px;">
|
||||
@@ -4866,7 +4866,7 @@ async function _openEmailAsTab(em, folder) {
|
||||
modal.className = 'modal email-reader-tab-modal';
|
||||
modal.id = modalId;
|
||||
modal.innerHTML = `
|
||||
<div class="modal-content doclib-modal-content email-reader-tab-content" style="background:var(--bg);width:min(720px, 92vw);max-height:85vh;display:flex;flex-direction:column;">
|
||||
<div class="modal-content doclib-modal-content email-reader-tab-content" style="background:var(--bg);width:min(720px, 92vw);display:flex;flex-direction:column;">
|
||||
<div class="modal-header">
|
||||
<h4 style="display:flex;align-items:center;gap:6px;min-width:0;flex:1;">
|
||||
<span style="overflow:hidden;text-overflow:ellipsis;white-space:nowrap;margin-left:8px;">${_esc(em.subject || '(no subject)')}</span>
|
||||
@@ -5101,7 +5101,7 @@ async function _openEmailWindow(em, folder) {
|
||||
modal.id = winId;
|
||||
modal.style.cssText = 'pointer-events:none;background:transparent;';
|
||||
modal.innerHTML = `
|
||||
<div class="modal-content email-window-content" style="width:min(640px, 92vw);max-height:80vh;display:flex;flex-direction:column;background:var(--bg);">
|
||||
<div class="modal-content email-window-content" style="width:min(640px, 92vw);display:flex;flex-direction:column;background:var(--bg);">
|
||||
<div class="modal-header">
|
||||
<h4 style="display:flex;align-items:center;gap:6px;min-width:0;flex:1;">
|
||||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="flex-shrink:0;"><rect x="2" y="4" width="20" height="16" rx="2"/><path d="m22 7-8.97 5.7a1.94 1.94 0 0 1-2.06 0L2 7"/></svg>
|
||||
|
||||
+1
-1
@@ -1802,7 +1802,7 @@ async function _showSkillSource(name) {
|
||||
wrap.className = 'modal';
|
||||
wrap.style.display = 'block';
|
||||
wrap.innerHTML = `
|
||||
<div class="modal-content" style="max-width:760px;max-height:85vh;display:flex;flex-direction:column">
|
||||
<div class="modal-content" style="max-width:760px;display:flex;flex-direction:column">
|
||||
<div class="modal-header">
|
||||
<h4>SKILL.md — <code>${esc(name)}</code></h4>
|
||||
<span style="flex:1"></span>
|
||||
|
||||
@@ -24,6 +24,7 @@ export const KEYS = {
|
||||
SECTION_ORDER: 'sidebar-section-order',
|
||||
ADMIN_LAST_TAB: 'admin-last-tab',
|
||||
DENSITY: 'odysseus-density',
|
||||
UI_SCALE: 'odysseus-ui-scale',
|
||||
WORKSPACE: 'odysseus-workspace'
|
||||
};
|
||||
|
||||
|
||||
@@ -39,6 +39,7 @@ const FONT_MAP = {
|
||||
mono: "'Fira Code', monospace",
|
||||
sans: "system-ui, -apple-system, 'Segoe UI', sans-serif",
|
||||
serif: "Georgia, 'Times New Roman', serif",
|
||||
opendyslexic: "'OpenDyslexic', sans-serif",
|
||||
};
|
||||
const DEFAULT_FONT = 'mono';
|
||||
const DEFAULT_DENSITY = 'comfortable';
|
||||
@@ -387,6 +388,20 @@ export function applyFontDensity(font, density) {
|
||||
if (d !== 'comfortable') document.documentElement.classList.add('density-' + d);
|
||||
}
|
||||
|
||||
// UI text-size scale (accessibility). Global and independent of the active
|
||||
// theme, so the chosen size persists across theme switches. Stored as a plain
|
||||
// percentage string ('100' | '110' | '125' | '150').
|
||||
const UI_SCALE_KEY = 'odysseus-ui-scale';
|
||||
const DEFAULT_UI_SCALE = '100';
|
||||
|
||||
export function applyUiScale(scale) {
|
||||
const s = scale || DEFAULT_UI_SCALE;
|
||||
// Only one non-default scale ('125'). Remove any legacy classes too so an
|
||||
// older stored value can't leave a stale zoom applied.
|
||||
document.documentElement.classList.remove('ui-scale-110', 'ui-scale-125', 'ui-scale-140');
|
||||
if (s === '125') document.documentElement.classList.add('ui-scale-125');
|
||||
}
|
||||
|
||||
const _BG_CLASSES = ['bg-pattern-dots',
|
||||
'bg-pattern-synapse', 'bg-pattern-rain', 'bg-pattern-constellations',
|
||||
'bg-pattern-perlin-flow',
|
||||
@@ -1133,6 +1148,18 @@ export function initThemeUI() {
|
||||
const s = getSaved(); if (s) _saveFull(s.name, s.colors);
|
||||
});
|
||||
}
|
||||
const textSizeSelect = document.getElementById('theme-text-size-select');
|
||||
if (textSizeSelect) {
|
||||
const nts = textSizeSelect.cloneNode(true); textSizeSelect.parentNode.replaceChild(nts, textSizeSelect);
|
||||
let initScale = DEFAULT_UI_SCALE;
|
||||
try { initScale = localStorage.getItem(UI_SCALE_KEY) || DEFAULT_UI_SCALE; } catch (e) {}
|
||||
nts.value = initScale;
|
||||
applyUiScale(initScale);
|
||||
nts.addEventListener('change', () => {
|
||||
applyUiScale(nts.value);
|
||||
try { localStorage.setItem(UI_SCALE_KEY, nts.value); } catch (e) {}
|
||||
});
|
||||
}
|
||||
if (patternSelect) {
|
||||
const np = patternSelect.cloneNode(true); patternSelect.parentNode.replaceChild(np, patternSelect);
|
||||
np.value = _initPattern;
|
||||
|
||||
Reference in New Issue
Block a user