Files
Haven/public/css/style-accessibility.css
T

248 lines
15 KiB
CSS

/* ═══════════════════════════════════════════════════════════
ACCESSIBILITY (Settings > Accessibility)
theme-init.js puts each option on <html> before first paint:
data-text-size + --msg-text-scale message text size (80 to 200%)
data-line-spacing normal | relaxed | loose
data-letter-spacing normal | wide
data-readable-font theme | system | hyperlegible | dyslexic
data-contrast normal | high
data-underline-links on | off
data-large-targets on | off
html.reduce-motion the reduce-motion result
An option at its default changes nothing, so every theme looks exactly as
it did. Once someone picks one it outranks the theme (!important where a
theme might pin the same property): themes are free to choose sizes and
spacing, but a reader's accessibility choice has the last word.
This file loads last, after every other core stylesheet.
═══════════════════════════════════════════════════════════ */
/* ── Readable fonts (both SIL Open Font License 1.1, see /fonts) ── */
@font-face {
font-family: 'Atkinson Hyperlegible';
font-style: normal;
font-weight: 400;
font-display: swap;
src: url('/fonts/atkinson-hyperlegible-latin-400.woff2') format('woff2');
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
font-family: 'Atkinson Hyperlegible';
font-style: normal;
font-weight: 400;
font-display: swap;
src: url('/fonts/atkinson-hyperlegible-latin-ext-400.woff2') format('woff2');
unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C4, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
font-family: 'Atkinson Hyperlegible';
font-style: normal;
font-weight: 700;
font-display: swap;
src: url('/fonts/atkinson-hyperlegible-latin-700.woff2') format('woff2');
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
font-family: 'Atkinson Hyperlegible';
font-style: normal;
font-weight: 700;
font-display: swap;
src: url('/fonts/atkinson-hyperlegible-latin-ext-700.woff2') format('woff2');
unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C4, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
/* OpenDyslexic ships unmodified (its license reserves the name for the
original files), so there is no subset: about 100 KB a weight, fetched
only once someone picks it. */
@font-face {
font-family: 'OpenDyslexic';
font-style: normal;
font-weight: 400;
font-display: swap;
src: url('/fonts/OpenDyslexic-Regular.woff2') format('woff2');
}
@font-face {
font-family: 'OpenDyslexic';
font-style: normal;
font-weight: 700;
font-display: swap;
src: url('/fonts/OpenDyslexic-Bold.woff2') format('woff2');
}
/* The theme's font variables are swapped, so everything drawn in the main or
heading font follows. Monospace (code) is left alone. !important because a
custom theme may set these inline on <html>. */
:root[data-readable-font="system"] {
--font-main: system-ui, -apple-system, 'Segoe UI', Roboto, 'Noto Sans', sans-serif !important;
--font-heading: system-ui, -apple-system, 'Segoe UI', Roboto, 'Noto Sans', sans-serif !important;
}
:root[data-readable-font="hyperlegible"] {
--font-main: 'Atkinson Hyperlegible', system-ui, sans-serif !important;
--font-heading: 'Atkinson Hyperlegible', system-ui, sans-serif !important;
}
:root[data-readable-font="dyslexic"] {
--font-main: 'OpenDyslexic', system-ui, sans-serif !important;
--font-heading: 'OpenDyslexic', system-ui, sans-serif !important;
}
/* Form controls do not inherit a font by default. */
:root:is([data-readable-font="system"], [data-readable-font="hyperlegible"], [data-readable-font="dyslexic"]) :is(button, input, textarea, select) {
font-family: inherit;
}
/* ── Message text size ───────────────────────────────────
Separate from Zoom, which scales the whole interface: this is only where
people read and write messages. The base sizes are the ones the core
stylesheets give these elements, times --msg-text-scale (0.8 to 2). Later
rules here win ties, so the thread and forum ones come after
.message-content, which those elements also carry. Inline code is sized
in em and follows on its own. */
:root[data-text-size]:not([data-text-size="100"]) .message-content { font-size: calc(0.875rem * var(--msg-text-scale, 1)) !important; }
:root[data-text-size]:not([data-text-size="100"]) .forum-topic-snippet { font-size: calc(0.82rem * var(--msg-text-scale, 1)) !important; }
:root[data-text-size]:not([data-text-size="100"]) .thread-topic-body { font-size: calc(0.95rem * var(--msg-text-scale, 1)) !important; }
:root[data-text-size]:not([data-text-size="100"]) .thread-panel:not(.thread-panel-forum) .thread-topic-body { font-size: calc(0.85rem * var(--msg-text-scale, 1)) !important; }
:root[data-text-size]:not([data-text-size="100"]) .thread-msg-content { font-size: calc(0.8125rem * var(--msg-text-scale, 1)) !important; }
:root[data-text-size]:not([data-text-size="100"]) .code-block pre { font-size: calc(0.8125rem * var(--msg-text-scale, 1)) !important; }
:root[data-text-size]:not([data-text-size="100"]) :is(.message-input-area textarea, .edit-textarea) { font-size: calc(0.875rem * var(--msg-text-scale, 1)) !important; }
:root[data-text-size]:not([data-text-size="100"]) .thread-input { font-size: calc(0.8125rem * var(--msg-text-scale, 1)) !important; }
/* Compact and Spacious density, and the pop-out DM, start from their own
message size, so a picked size keeps them as much smaller or bigger as
they were. The pop-out DM comes last: it is small in every density. */
:root[data-text-size]:not([data-text-size="100"])[data-density="compact"] .message-content { font-size: calc(0.8125rem * var(--msg-text-scale, 1)) !important; }
:root[data-text-size]:not([data-text-size="100"])[data-density="spacious"] .message-content { font-size: calc(0.9375rem * var(--msg-text-scale, 1)) !important; }
:root[data-text-size]:not([data-text-size="100"]) .dm-pip-messages .message-content { font-size: calc(0.8125rem * var(--msg-text-scale, 1)) !important; }
:root[data-text-size]:not([data-text-size="100"]) .dm-pip-input { font-size: calc(0.8125rem * var(--msg-text-scale, 1)) !important; }
@media (max-width: 480px) {
/* Never under 1rem on phones, or iOS zooms the page in on focus. */
:root[data-text-size]:not([data-text-size="100"]) .message-input-area textarea { font-size: max(1rem, calc(0.875rem * var(--msg-text-scale, 1))) !important; }
}
@media (max-width: 360px) {
:root[data-text-size]:not([data-text-size="100"]) .message-content { font-size: calc(0.8125rem * var(--msg-text-scale, 1)) !important; }
}
/* ── Line and letter spacing ─────────────────────────────
The same places as the text size. The sidebar and toolbars keep their
spacing so labels still fit. */
:root[data-line-spacing="relaxed"] { --a11y-line-height: 1.75; }
:root[data-line-spacing="loose"] { --a11y-line-height: 2.05; }
:root:is([data-line-spacing="relaxed"], [data-line-spacing="loose"]) :is(.message-content, .thread-msg-content, .thread-topic-body, .forum-topic-snippet, .code-block pre, .message-input-area textarea, .thread-input, .edit-textarea) {
line-height: var(--a11y-line-height) !important;
}
:root[data-letter-spacing="wide"] :is(.message-content, .thread-msg-content, .thread-topic-body, .forum-topic-snippet, .forum-topic-title, .message-input-area textarea, .thread-input, .edit-textarea) {
letter-spacing: 0.08em !important;
word-spacing: 0.16em !important;
}
/* ── Keyboard focus ──────────────────────────────────────
A visible ring on whatever the keyboard lands on. :focus-visible keeps it
off mouse clicks, so it only shows up for people who need it. Text boxes
already mark focus with their border, so they only get the ring in high
contrast. */
:root :is(button, a[href], summary, select, [role="button"], [role="tab"], [role="menuitem"], [role="option"], [tabindex]:not([tabindex="-1"]), input[type="checkbox"], input[type="radio"], input[type="range"]):focus-visible {
outline: 2px solid var(--a11y-focus, var(--accent));
outline-offset: 2px;
}
/* Rows that sit flush inside a scrolling list draw the ring inside
themselves, or the list would clip it. */
:root :is(.settings-nav-item, .channel-item, .user-item, .density-btn):focus-visible {
outline-offset: -2px;
}
/* ── High contrast ───────────────────────────────────────
Secondary and muted text, and borders, are pulled toward the theme's own
main text color, so the result keeps the theme's character on any palette.
The theme sets these on <html>; :root plus the attribute outranks it and
!important outranks a custom theme's inline values. */
:root[data-contrast="high"] {
--text-secondary: color-mix(in srgb, var(--text-primary) 88%, var(--bg-primary)) !important;
--text-muted: color-mix(in srgb, var(--text-primary) 76%, var(--bg-primary)) !important;
--border: color-mix(in srgb, var(--text-primary) 50%, var(--bg-primary)) !important;
--border-light: color-mix(in srgb, var(--text-primary) 58%, var(--bg-primary)) !important;
--border-color: color-mix(in srgb, var(--text-primary) 50%, var(--bg-primary)) !important;
--msg-glow: none !important;
--scanline: none !important;
--a11y-focus: var(--text-primary);
}
:root[data-contrast="high"] *:focus-visible {
outline: 3px solid var(--text-primary) !important;
outline-offset: 2px;
}
:root[data-contrast="high"] :is(.settings-nav-item, .channel-item, .user-item, .density-btn):focus-visible {
outline-offset: -3px;
}
:root[data-contrast="high"] :is(input:not([type="checkbox"]):not([type="radio"]):not([type="range"]), textarea, select, .settings-select) {
border-color: var(--border-light);
}
/* Text that is only dimmed with opacity stays readable. Offline members
stay a little dimmer than online ones, so the two still read apart. */
:root[data-contrast="high"] :is(.message-time, .msg-toolbar button, .thread-msg-time, .forum-topic-edit, .settings-hint) {
opacity: 1;
}
:root[data-contrast="high"] .user-item.offline { opacity: 0.8; }
/* ── Underline links ─────────────────────────────────────
Message links are underlined already; this does the rest of the app.
Links dressed as buttons are left alone. */
:root[data-underline-links="on"] a[href]:not([role="button"]):not([class*="btn"]):not(.server-icon) {
text-decoration: underline !important;
text-decoration-thickness: max(1px, 0.06em) !important;
text-underline-offset: 0.15em;
}
/* ── Reduce motion ───────────────────────────────────────
One switch for every animation and transition. Durations go to nearly
zero rather than "none" so code waiting on animationend / transitionend
still hears it, and an animation lands on its final frame. theme.js stops
the theme effects, and animated GIFs, emoji and avatars wait for hover or
tap (app-appearance.js). */
html.reduce-motion,
html.reduce-motion *,
html.reduce-motion *::before,
html.reduce-motion *::after {
animation-duration: 0.001ms !important;
animation-delay: 0s !important;
animation-iteration-count: 1 !important;
transition-duration: 0.001ms !important;
transition-delay: 0s !important;
scroll-behavior: auto !important;
}
/* ── Larger click targets ────────────────────────────────
Taller rows in the sidebars and settings, and bigger buttons in the
message box, the channel header and the message action bars. */
:root[data-large-targets="on"] .channel-item { padding-top: 0.75rem !important; padding-bottom: 0.75rem !important; }
:root[data-large-targets="on"] .user-item { padding-top: 0.5rem !important; padding-bottom: 0.5rem !important; min-height: 2.75rem !important; }
:root[data-large-targets="on"] .settings-nav-item { padding: 0.5625rem 0.75rem !important; }
:root[data-large-targets="on"] .sidebar-bottom-btn { min-height: 2.5rem !important; }
:root[data-large-targets="on"] .icon-btn { min-width: 2.5rem !important; min-height: 2.5rem !important; }
:root[data-large-targets="on"] .msg-toolbar button { width: 2.5rem !important; height: 2.5rem !important; }
:root[data-large-targets="on"] .msg-toolbar button svg { width: 1.125rem !important; height: 1.125rem !important; }
:root[data-large-targets="on"] .thread-msg-toolbar button { width: 2.25rem !important; height: 2.25rem !important; font-size: 0.9375rem !important; }
/* Phones already size the message box buttons for touch, and a row of
bigger ones there would push the text box off the screen. */
@media (min-width: 481px) {
:root[data-large-targets="on"] :is(.input-actions-box > button, .btn-composer-plus) { min-width: 2.75rem !important; min-height: 2.75rem !important; }
:root[data-large-targets="on"] .btn-send { width: 3rem !important; height: 3rem !important; }
}
:root[data-large-targets="on"] .reaction-badge { padding: 0.3125rem 0.75rem !important; }
:root[data-large-targets="on"] .density-btn { min-height: 2.75rem !important; }
:root[data-large-targets="on"] .zoom-step { width: 2.5rem !important; height: 2.5rem !important; }
/* ── The settings section itself ───────────────────────── */
.a11y-text-preview {
margin: 0.25rem 0 0.5rem;
padding: 0.5rem 0.75rem;
border: 1px solid var(--border);
border-radius: var(--radius);
background: var(--bg-primary);
}
.a11y-reset-btn {
flex-shrink: 0;
padding: 0.25rem 0.625rem;
font-size: 0.75rem;
border-radius: var(--radius-sm);
background: var(--bg-primary);
border: 1px solid var(--border);
color: var(--text-secondary);
cursor: pointer;
}
.a11y-reset-btn:hover { color: var(--text-primary); border-color: var(--accent); }
.a11y-reset-btn:disabled { opacity: 0.5; cursor: default; }