diff --git a/static/css/00-tokens.css b/static/css/00-tokens.css index e502f7670..734303b2f 100644 --- a/static/css/00-tokens.css +++ b/static/css/00-tokens.css @@ -130,11 +130,30 @@ body { @font-face { font-family: 'Fira Code'; font-weight: 400; font-style: normal; font-display: swap; src: url('/static/fonts/FiraCode-Regular.woff2') format('woff2'); } @font-face { font-family: 'Fira Code'; font-weight: 600; font-style: normal; font-display: swap; src: url('/static/fonts/FiraCode-SemiBold.woff2') format('woff2'); } +/* GohuFont is the default UI face across themes; keep the shipped font local + so the default is available before custom-font discovery completes. */ +@font-face { font-family: 'GohuFont'; font-weight: 400; font-style: normal; font-display: swap; src: url('/static/fonts/custom/GohuFont.ttf') format('truetype'); } /* Self-hosted OpenDyslexic — dyslexia-friendly accessibility font option (SIL OFL 1.1) */ @font-face { font-family: 'OpenDyslexic'; font-weight: 400; font-style: normal; font-display: swap; src: url('/static/fonts/OpenDyslexic-Regular.woff2') format('woff2'); } @font-face { font-family: 'OpenDyslexic'; font-weight: 700; font-style: normal; font-display: swap; src: url('/static/fonts/OpenDyslexic-Bold.woff2') format('woff2'); } +/* Self-hosted editor text fonts (SIL OFL 1.1). These are bundled so the text + tool renders distinct, expected faces on every device — phones (Android/iOS) + do not ship Arial/Verdana/Georgia/Times/Courier/Impact, which previously + collapsed every font choice onto the same system fallback. */ +@font-face { font-family: 'Inter'; font-weight: 400; font-style: normal; font-display: swap; src: url('/static/fonts/Inter-Regular.woff2') format('woff2'); } +@font-face { font-family: 'Inter'; font-weight: 500; font-style: normal; font-display: swap; src: url('/static/fonts/Inter-Medium.woff2') format('woff2'); } +@font-face { font-family: 'Inter'; font-weight: 600; font-style: normal; font-display: swap; src: url('/static/fonts/Inter-SemiBold.woff2') format('woff2'); } +@font-face { font-family: 'Roboto'; font-weight: 400; font-style: normal; font-display: swap; src: url('/static/fonts/Roboto-Regular.woff2') format('woff2'); } +@font-face { font-family: 'Roboto'; font-weight: 700; font-style: normal; font-display: swap; src: url('/static/fonts/Roboto-Bold.woff2') format('woff2'); } +@font-face { font-family: 'Playfair Display'; font-weight: 400; font-style: normal; font-display: swap; src: url('/static/fonts/PlayfairDisplay-Regular.woff2') format('woff2'); } +@font-face { font-family: 'Playfair Display'; font-weight: 700; font-style: normal; font-display: swap; src: url('/static/fonts/PlayfairDisplay-Bold.woff2') format('woff2'); } +@font-face { font-family: 'Lobster'; font-weight: 400; font-style: normal; font-display: swap; src: url('/static/fonts/Lobster-Regular.woff2') format('woff2'); } +@font-face { font-family: 'Bebas Neue'; font-weight: 400; font-style: normal; font-display: swap; src: url('/static/fonts/BebasNeue-Regular.woff2') format('woff2'); } +@font-face { font-family: 'Roboto Mono'; font-weight: 400; font-style: normal; font-display: swap; src: url('/static/fonts/RobotoMono-Regular.woff2') format('woff2'); } +@font-face { font-family: 'Roboto Mono'; font-weight: 700; font-style: normal; font-display: swap; src: url('/static/fonts/RobotoMono-Bold.woff2') format('woff2'); } + /* Code block baseline */ pre, code, .hljs { font-size: 11px; diff --git a/static/css/04-memory.css b/static/css/04-memory.css index 71b2537bd..67cb7ca16 100644 --- a/static/css/04-memory.css +++ b/static/css/04-memory.css @@ -704,6 +704,7 @@ textarea.memory-add-input { #serve-bulk-bar, #gallery-bulk-bar, #gallery-editor-drafts-bulk, +#notes-bulk-bar, #memory-modal .memory-bulk-bar { padding-right: 18px; } diff --git a/static/css/12-gallery.css b/static/css/12-gallery.css index 16f47e863..dad05ec52 100644 --- a/static/css/12-gallery.css +++ b/static/css/12-gallery.css @@ -90,6 +90,12 @@ .lib-tabs, .gallery-tabs, .preset-tabs { + /* A flex-item tab strip defaults to min-width:auto, so it grows to its + content width inside a narrow modal and the end tabs get clipped by the + modal's overflow instead of scrolling. min-width:0 lets the strip shrink + to the container so the overflow-x:auto below actually engages. */ + min-width: 0; + max-width: 100%; flex-wrap: nowrap !important; overflow-x: auto !important; overflow-y: hidden; diff --git a/static/js/a11y.js b/static/js/a11y.js index 814472d94..f44ada39d 100644 --- a/static/js/a11y.js +++ b/static/js/a11y.js @@ -18,7 +18,13 @@ 'use strict'; // Click-as-button rows we want reachable by keyboard. - var ROW_SELECTOR = ['#sidebar .list-item', '#user-bar-profile'].join(','); + var ROW_SELECTOR = [ + '#sidebar .list-item', + '#user-bar-profile', + '.sidebar-brand', + '.export-dropdown-item', + '.dropdown-item' + ].join(','); // Native interactive descendants. If a row contains one of these we must // NOT give the row role="button" — a button inside a button is invalid @@ -55,6 +61,31 @@ (root || document).querySelectorAll(ROW_SELECTOR).forEach(enhanceRow); } + // Settings form labels are written as + // with the control as a sibling/descendant, but are not associated via for/id, + // so screen readers cannot name the inputs. Associate each label with its + // control when the mapping is unambiguous (a single form control per row). + var settingsLabelSeq = 0; + function associateSettingsLabels(root) { + (root || document).querySelectorAll('.settings-label').forEach(function (label) { + if (label.hasAttribute('for')) return; + var control = null; + var sib = label.nextElementSibling; + if (sib && /^(INPUT|SELECT|TEXTAREA)$/.test(sib.tagName)) { + control = sib; + } else { + var row = label.closest('.settings-row') || label.parentElement; + if (row) { + var controls = row.querySelectorAll('input,select,textarea'); + if (controls.length === 1) control = controls[0]; + } + } + if (!control || control.closest('label')) return; + if (!control.id) control.id = 'a11y-settings-' + (++settingsLabelSeq); + if (!control.getAttribute('aria-label')) label.setAttribute('for', control.id); + }); + } + // ---- Modal dialogs ----------------------------------------------------- // Odysseus modals are plain