mirror of
https://github.com/bulwarkmail/webmail.git
synced 2026-10-31 08:13:50 +00:00
Settings get a page of their own for accessibility, with presets on top (screen reader, low vision, dyslexia, focus, keyboard and motor, motion and seizure safe, deaf and hard of hearing) that change several options at once and can be undone, then the options by topic, and a reset. Text size used to be a fixed 14/16/18px on the root, which threw away the size people set in their browser. It is now a percentage of the browser's size, from 87.5% to 200%. Motion, contrast and transparency follow the system by default and can be overridden either way; the old Animations switch becomes Reduce motion. The visual options are applied before the first paint, so the page no longer flashes at the wrong size. - `lib/accessibility-settings.ts`: the new settings, their allowed values, and the code that writes them onto `<html>` - `app/accessibility.css`: bundled fonts (Atkinson Hyperlegible Next, Lexend, OpenDyslexic), text spacing, a focus outline that survives Windows contrast themes, more-contrast colours on any theme, reduced transparency, a large pointer - admins can preset every new option but lock only shortcuts, send confirmation and the notification duration; a lock on text size or another reading aid works as a default - always-light mail and toolbar labels now sync, and imports drop values the page doesn't know - font size and animations move off Appearance, the plain text font off Reading; everyday settings such as conversation view and undo send show on both their own page and Accessibility
256 lines
10 KiB
CSS
256 lines
10 KiB
CSS
/*
|
|
* Settings > Accessibility, the CSS half (lib/accessibility-settings.ts writes
|
|
* the data attributes on <html>).
|
|
*
|
|
* Each "follow the system" option has two parts: a media query that applies
|
|
* unless the user overrode it (`:root:not([data-x="..."])`), and an attribute
|
|
* rule for an explicit choice. Overrides use `:root[...]`, which outranks the
|
|
* `:root` and `.dark` blocks themes compile to (lib/theme-compiler.ts).
|
|
*/
|
|
|
|
/* ---------- Bundled fonts (SIL Open Font License, see NOTICE) ---------- */
|
|
|
|
@font-face {
|
|
font-family: "Atkinson Hyperlegible Next";
|
|
font-style: normal;
|
|
font-display: swap;
|
|
font-weight: 200 800;
|
|
src: url("./fonts/atkinson-hyperlegible-next-latin-ext-wght-normal.woff2") format("woff2-variations");
|
|
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-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
|
|
}
|
|
@font-face {
|
|
font-family: "Atkinson Hyperlegible Next";
|
|
font-style: normal;
|
|
font-display: swap;
|
|
font-weight: 200 800;
|
|
src: url("./fonts/atkinson-hyperlegible-next-latin-wght-normal.woff2") format("woff2-variations");
|
|
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: "Lexend";
|
|
font-style: normal;
|
|
font-display: swap;
|
|
font-weight: 100 900;
|
|
src: url("./fonts/lexend-latin-ext-wght-normal.woff2") format("woff2-variations");
|
|
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-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
|
|
}
|
|
@font-face {
|
|
font-family: "Lexend";
|
|
font-style: normal;
|
|
font-display: swap;
|
|
font-weight: 100 900;
|
|
src: url("./fonts/lexend-latin-wght-normal.woff2") format("woff2-variations");
|
|
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: "OpenDyslexic";
|
|
font-style: normal;
|
|
font-display: swap;
|
|
font-weight: 400;
|
|
src: url("./fonts/opendyslexic-latin-400-normal.woff2") format("woff2");
|
|
}
|
|
@font-face {
|
|
font-family: "OpenDyslexic";
|
|
font-style: normal;
|
|
font-display: swap;
|
|
font-weight: 700;
|
|
src: url("./fonts/opendyslexic-latin-700-normal.woff2") format("woff2");
|
|
}
|
|
|
|
/* The fonts cover Latin script; other scripts fall through to the stack. */
|
|
:root[data-app-font="atkinson"] {
|
|
--font-sans: "Atkinson Hyperlegible Next", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Arial, sans-serif;
|
|
}
|
|
:root[data-app-font="lexend"] {
|
|
--font-sans: "Lexend", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Arial, sans-serif;
|
|
}
|
|
:root[data-app-font="opendyslexic"] {
|
|
--font-sans: "OpenDyslexic", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Arial, sans-serif;
|
|
}
|
|
:root[data-app-font] body {
|
|
font-family: var(--font-sans);
|
|
}
|
|
|
|
/* ---------- Text spacing (WCAG 1.4.8, 1.4.12) ---------- */
|
|
|
|
:root[data-line-spacing="relaxed"] { --a11y-line-height: 1.6; }
|
|
:root[data-line-spacing="loose"] { --a11y-line-height: 1.9; }
|
|
:root[data-line-spacing] body *:not(svg, svg *) {
|
|
line-height: var(--a11y-line-height) !important;
|
|
}
|
|
:root[data-letter-spacing="wide"] { --a11y-letter-spacing: 0.05em; }
|
|
:root[data-letter-spacing="wider"] { --a11y-letter-spacing: 0.12em; }
|
|
:root[data-letter-spacing] body *:not(svg, svg *) {
|
|
letter-spacing: var(--a11y-letter-spacing) !important;
|
|
}
|
|
:root[data-word-spacing="wide"] body *:not(svg, svg *) {
|
|
word-spacing: 0.16em !important;
|
|
}
|
|
:root[data-paragraph-spacing="wide"] body p {
|
|
margin-block-end: 1.5em !important;
|
|
}
|
|
|
|
/* ---------- Links ---------- */
|
|
|
|
:root[data-underline-links="on"] a[href]:not([role]),
|
|
:root[data-underline-links="on"] .a11y-link {
|
|
text-decoration-line: underline !important;
|
|
text-underline-offset: 0.15em;
|
|
}
|
|
|
|
/* ---------- Focus (WCAG 2.4.7, 2.4.11, 2.4.13) ----------
|
|
*
|
|
* One outline for every focusable element, drawn as a real outline rather
|
|
* than a box-shadow ring: forced-colors mode removes box-shadows, and an
|
|
* outline also keeps its width at any zoom. It outranks the utilities that
|
|
* used to switch the outline off (outline-none plus a ring). Elements that
|
|
* only take focus from script (tabindex="-1": lists, panes, dialog bodies)
|
|
* keep no outline unless "always show focus" is on.
|
|
*/
|
|
:root {
|
|
--focus-ring-width: 2px;
|
|
--focus-ring-offset: 2px;
|
|
--focus-ring-color: var(--color-primary);
|
|
}
|
|
:root[data-focus-ring="thick"] {
|
|
--focus-ring-width: 3px;
|
|
}
|
|
:root[data-focus-ring="extra"] {
|
|
--focus-ring-width: 5px;
|
|
--focus-ring-offset: 3px;
|
|
}
|
|
:focus-visible:not([tabindex="-1"]),
|
|
:root[data-focus-always="on"] :focus:not([tabindex="-1"]) {
|
|
outline: var(--focus-ring-width) solid var(--focus-ring-color) !important;
|
|
outline-offset: var(--focus-ring-offset) !important;
|
|
}
|
|
/* Inside a scroll container a positive offset is clipped; draw it inside. */
|
|
:is([role="option"], [role="row"], [role="treeitem"], [role="gridcell"], [role="menuitem"], [role="menuitemradio"], [role="menuitemcheckbox"], [role="tab"]):focus-visible:not([tabindex="-1"]) {
|
|
outline-offset: calc(var(--focus-ring-width) * -1) !important;
|
|
}
|
|
/* The focused element scrolls clear of sticky headers and the bottom bar. */
|
|
:root {
|
|
scroll-padding-block: 4rem;
|
|
}
|
|
|
|
/* ---------- More contrast (WCAG 1.4.6 as a setting) ---------- */
|
|
|
|
@media (prefers-contrast: more) {
|
|
:root:not([data-contrast="normal"]) {
|
|
--color-foreground: #000000;
|
|
--color-muted-foreground: #1f2937;
|
|
--color-border: #4b5563;
|
|
--color-input: #4b5563;
|
|
--color-ring: #000000;
|
|
--color-sidebar-border: #4b5563;
|
|
--color-primary: #1d4ed8;
|
|
--color-unread: #1d4ed8;
|
|
--color-destructive: #b91c1c;
|
|
--color-accent-foreground: #1e3a8a;
|
|
--color-selection-foreground: #1e3a8a;
|
|
--color-popover-foreground: #000000;
|
|
--color-card-foreground: #000000;
|
|
--color-sidebar-foreground: #000000;
|
|
--focus-ring-color: #000000;
|
|
}
|
|
.dark:not([data-contrast="normal"]) {
|
|
--color-foreground: #ffffff;
|
|
--color-muted-foreground: #e5e7eb;
|
|
--color-border: #9ca3af;
|
|
--color-input: #9ca3af;
|
|
--color-ring: #ffffff;
|
|
--color-sidebar-border: #9ca3af;
|
|
--color-primary: #93c5fd;
|
|
--color-primary-foreground: #000000;
|
|
--color-unread: #93c5fd;
|
|
--color-destructive: #fca5a5;
|
|
--color-accent-foreground: #dbeafe;
|
|
--color-selection-foreground: #dbeafe;
|
|
--color-popover-foreground: #ffffff;
|
|
--color-card-foreground: #ffffff;
|
|
--color-sidebar-foreground: #ffffff;
|
|
--focus-ring-color: #ffffff;
|
|
}
|
|
:root:not([data-contrast="normal"]) a[href]:not([role]) {
|
|
text-decoration-line: underline;
|
|
}
|
|
}
|
|
:root[data-contrast="more"] {
|
|
--color-foreground: #000000;
|
|
--color-muted-foreground: #1f2937;
|
|
--color-border: #4b5563;
|
|
--color-input: #4b5563;
|
|
--color-ring: #000000;
|
|
--color-sidebar-border: #4b5563;
|
|
--color-primary: #1d4ed8;
|
|
--color-unread: #1d4ed8;
|
|
--color-destructive: #b91c1c;
|
|
--color-accent-foreground: #1e3a8a;
|
|
--color-selection-foreground: #1e3a8a;
|
|
--color-popover-foreground: #000000;
|
|
--color-card-foreground: #000000;
|
|
--color-sidebar-foreground: #000000;
|
|
--focus-ring-color: #000000;
|
|
}
|
|
html.dark[data-contrast="more"] {
|
|
--color-foreground: #ffffff;
|
|
--color-muted-foreground: #e5e7eb;
|
|
--color-border: #9ca3af;
|
|
--color-input: #9ca3af;
|
|
--color-ring: #ffffff;
|
|
--color-sidebar-border: #9ca3af;
|
|
--color-primary: #93c5fd;
|
|
--color-primary-foreground: #000000;
|
|
--color-unread: #93c5fd;
|
|
--color-destructive: #fca5a5;
|
|
--color-accent-foreground: #dbeafe;
|
|
--color-selection-foreground: #dbeafe;
|
|
--color-popover-foreground: #ffffff;
|
|
--color-card-foreground: #ffffff;
|
|
--color-sidebar-foreground: #ffffff;
|
|
--focus-ring-color: #ffffff;
|
|
}
|
|
:root[data-contrast="more"] a[href]:not([role]) {
|
|
text-decoration-line: underline;
|
|
}
|
|
|
|
/* ---------- Reduced transparency ---------- */
|
|
|
|
@media (prefers-reduced-transparency: reduce) {
|
|
:root:not([data-transparency="full"]) * {
|
|
backdrop-filter: none !important;
|
|
-webkit-backdrop-filter: none !important;
|
|
}
|
|
:root:not([data-transparency="full"]) :is([class*="bg-background/"], [class*="bg-card/"], [class*="bg-popover/"], [class*="bg-sidebar/"], [class*="bg-muted/"]) {
|
|
background-color: var(--color-background) !important;
|
|
}
|
|
}
|
|
:root[data-transparency="reduce"] * {
|
|
backdrop-filter: none !important;
|
|
-webkit-backdrop-filter: none !important;
|
|
}
|
|
:root[data-transparency="reduce"] :is([class*="bg-background/"], [class*="bg-card/"], [class*="bg-popover/"], [class*="bg-sidebar/"], [class*="bg-muted/"]) {
|
|
background-color: var(--color-background) !important;
|
|
}
|
|
|
|
/* ---------- Large cursor ---------- */
|
|
|
|
:root[data-large-cursor="on"],
|
|
:root[data-large-cursor="on"] body {
|
|
cursor: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='40' height='40' viewBox='0 0 40 40'%3E%3Cpath d='M4 2 L4 32 L12 25 L18 38 L24 35 L18 23 L29 23 Z' fill='black' stroke='white' stroke-width='2.5' stroke-linejoin='round'/%3E%3C/svg%3E") 4 2, auto;
|
|
}
|
|
:root[data-large-cursor="on"] :is(a[href], button:not(:disabled), [role="button"], [role="link"], [role="tab"], [role="menuitem"], [role="option"], [role="switch"], [role="checkbox"], label, summary, select) {
|
|
cursor: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='40' height='40' viewBox='0 0 40 40'%3E%3Cpath d='M13 3 a3 3 0 0 1 6 0 V17 l1-1 a3 3 0 0 1 5 2 l1-1 a3 3 0 0 1 5 2 l1-1 a3 3 0 0 1 5 2 V28 c0 5-4 10-10 10 h-5 c-4 0-6-2-8-5 L6 25 a3 3 0 0 1 5-4 l2 2 Z' fill='white' stroke='black' stroke-width='2.5' stroke-linejoin='round'/%3E%3C/svg%3E") 15 2, pointer;
|
|
}
|
|
:root[data-large-cursor="on"] :is(input:not([type="checkbox"], [type="radio"], [type="range"], [type="button"], [type="submit"]), textarea, [contenteditable="true"]) {
|
|
cursor: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='40' height='40' viewBox='0 0 40 40'%3E%3Cpath d='M14 4 h12 M20 4 V36 M14 36 h12' fill='none' stroke='white' stroke-width='7' stroke-linecap='round'/%3E%3Cpath d='M14 4 h12 M20 4 V36 M14 36 h12' fill='none' stroke='black' stroke-width='3' stroke-linecap='round'/%3E%3C/svg%3E") 20 20, text;
|
|
}
|
|
|
|
/* ---------- Forced colours (Windows contrast themes) ---------- */
|
|
|
|
@media (forced-colors: active) {
|
|
:focus-visible:not([tabindex="-1"]) {
|
|
outline-color: Highlight !important;
|
|
}
|
|
}
|