/* * Settings > Accessibility, the CSS half (lib/accessibility-settings.ts writes * the data attributes on ). * * 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; } }