mirror of
https://github.com/bulwarkmail/webmail.git
synced 2026-10-31 08:13:50 +00:00
268 lines
12 KiB
CSS
268 lines
12 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).
|
|
*
|
|
* Containers that only take focus from script (tabindex="-1" on a list, a
|
|
* pane, a dialog, <main>, a heading focused after navigation) skip it: the
|
|
* focus there is a starting point, not a control. Controls with tabindex="-1"
|
|
* keep it, because roving-focus widgets (menu items, options, grid cells,
|
|
* tabs, rows) are focused while their tabindex is -1.
|
|
*/
|
|
:root {
|
|
--focus-ring-width: 2px;
|
|
--focus-ring-offset: 2px;
|
|
--focus-ring-color: var(--color-ring);
|
|
}
|
|
: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"]:not(a[href], button, input, select, textarea, summary, [contenteditable="true"], [role="button"], [role="link"], [role="menuitem"], [role="menuitemradio"], [role="menuitemcheckbox"], [role="option"], [role="gridcell"], [role="row"], [role="treeitem"], [role="tab"], [role="radio"], [role="checkbox"], [role="switch"], [role="slider"], [role="spinbutton"], [role="columnheader"], [role="rowheader"])),
|
|
:root[data-focus-always="on"] :focus:not([tabindex="-1"]:not(a[href], button, input, select, textarea, summary, [contenteditable="true"], [role="button"], [role="link"], [role="menuitem"], [role="menuitemradio"], [role="menuitemcheckbox"], [role="option"], [role="gridcell"], [role="row"], [role="treeitem"], [role="tab"], [role="radio"], [role="checkbox"], [role="switch"], [role="slider"], [role="spinbutton"], [role="columnheader"], [role="rowheader"])) {
|
|
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.
|
|
`focus-inset` does the same for any control that sits flush against the
|
|
edge of a clipping container. */
|
|
:is([role="option"], [role="row"], [role="treeitem"], [role="gridcell"], [role="menuitem"], [role="menuitemradio"], [role="menuitemcheckbox"], [role="tab"], .focus-inset):focus-visible {
|
|
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"]:not(a[href], button, input, select, textarea, summary, [contenteditable="true"], [role="button"], [role="link"], [role="menuitem"], [role="menuitemradio"], [role="menuitemcheckbox"], [role="option"], [role="gridcell"], [role="row"], [role="treeitem"], [role="tab"], [role="radio"], [role="checkbox"], [role="switch"], [role="slider"], [role="spinbutton"], [role="columnheader"], [role="rowheader"])) {
|
|
outline-color: Highlight !important;
|
|
}
|
|
}
|
|
|
|
/* ---------- Simple mode: no side panels ---------- */
|
|
|
|
:root[data-simple-mode="on"] [data-simple-hide] {
|
|
display: none !important;
|
|
}
|