Fix a11y icon, FAB label colors, add high-contrast stylesheet, sync theme vars
Some checks failed
Repo Health / Access control (push) Failing after 2s
Repo Health / Release configuration (push) Has been skipped
Repo Health / Scripts governance (push) Has been skipped
Repo Health / Repository health (push) Has been skipped

- Fix faIcon() to use <i> elements (FA7 compatibility)
- Fix #mokoThemeFab .label and button colors to white (visible on
  both light and dark themes)
- Fix font-weight typo (600px → 600)
- Add --footer-padding-* variables to all 6 theme files
- Create a11y-high-contrast.css with WCAG AAA contrast ratios for
  both light and dark modes
- Register high-contrast stylesheet in joomla.asset.json
- Lazy-load high-contrast CSS when a11y contrast toggle is activated

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Jonathan Miller
2026-04-14 20:25:15 -05:00
parent cc58b75b0d
commit ddf3655501
10 changed files with 289 additions and 7 deletions

View File

@@ -124,6 +124,12 @@
"uri": "media/templates/site/mokocassiopeia/css/theme/dark.custom.min.css",
"attributes": {"media": "all"}
},
{
"name": "template.a11y-high-contrast",
"type": "style",
"uri": "media/templates/site/mokocassiopeia/css/a11y-high-contrast.css",
"attributes": {"media": "all"}
},
{
"name": "template.js",
"type": "script",

View File

@@ -0,0 +1,227 @@
@charset "UTF-8";
/* Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
*
* This file is part of a Moko Consulting project.
*
* SPDX-License-Identifier: GPL-3.0-or-later
*
* FILE INFORMATION
* DEFGROUP: Joomla.Template.Site
* INGROUP: MokoCassiopeia.Accessibility
* REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoCassiopeia
* PATH: ./media/css/a11y-high-contrast.css
* VERSION: 03.09.14
* BRIEF: High-contrast stylesheet for accessibility toolbar
*/
/* ===================================================================
* HIGH CONTRAST MODE
* Applied when .a11y-high-contrast is on <html>.
* Overrides theme variables to maximise contrast ratios (WCAG AAA).
* =================================================================== */
/* ── Light mode high contrast ─────────────────────────────────────── */
:root[data-bs-theme="light"].a11y-high-contrast {
--body-color: #000;
--body-color-rgb: 0, 0, 0;
--body-bg: #fff;
--body-bg-rgb: 255, 255, 255;
--heading-color: #000;
--emphasis-color: #000;
--secondary-color: #000000bf;
--tertiary-color: #00000080;
--muted-color: #333;
/* Links — strong blue on white */
--color-link: #0000ee;
--link-color: #0000ee;
--link-color-rgb: 0, 0, 238;
--color-hover: #cc0000;
--link-hover-color: #cc0000;
--link-hover-color-rgb: 204, 0, 0;
/* Borders — visible on all backgrounds */
--border-color: #000;
--border-color-soft: #333;
/* Backgrounds */
--secondary-bg: #e0e0e0;
--secondary-bg-rgb: 224, 224, 224;
--tertiary-bg: #f0f0f0;
--tertiary-bg-rgb: 240, 240, 240;
/* Navigation */
--nav-bg-color: #000;
--nav-text-color: #fff;
--mainmenu-nav-link-color: #fff;
/* Buttons */
--btn-color: #fff;
--btn-bg: #000;
--btn-border-color: #000;
--btn-hover-color: #000;
--btn-hover-bg: #ffff00;
--btn-hover-border-color: #000;
--btn-active-color: #000;
--btn-active-bg: #ffff00;
--btn-active-border-color: #000;
/* Forms */
--input-color: #000;
--input-bg: #fff;
--input-border-color: #000;
--input-focus-color: #000;
--input-focus-bg: #ffffcc;
--input-focus-border-color: #0000ee;
--input-placeholder-color: #555;
/* Cards */
--card-border-color: #000;
--card-bg: #fff;
--card-cap-bg: #e0e0e0;
/* Tables */
--table-color: #000;
--table-bg: #fff;
--table-border-color: #000;
--table-striped-bg: #f0f0f0;
--table-hover-bg: #ffff99;
/* Alerts */
--alert-border-width: 2px;
/* Code */
--code-color: #000;
--code-bg-color: #ffffcc;
/* Selection */
--selection-bg: #0000ee;
--selection-ink: #fff;
/* Focus indicator — always visible */
--focus-ring-color: #0000ee;
--focus-ring-width: 3px;
}
/* ── Dark mode high contrast ──────────────────────────────────────── */
:root[data-bs-theme="dark"].a11y-high-contrast {
--body-color: #fff;
--body-color-rgb: 255, 255, 255;
--body-bg: #000;
--body-bg-rgb: 0, 0, 0;
--heading-color: #fff;
--emphasis-color: #fff;
--secondary-color: #ffffffbf;
--tertiary-color: #ffffff80;
--muted-color: #ccc;
/* Links — bright yellow on black */
--color-link: #ffff00;
--link-color: #ffff00;
--link-color-rgb: 255, 255, 0;
--color-hover: #00ffff;
--link-hover-color: #00ffff;
--link-hover-color-rgb: 0, 255, 255;
/* Borders */
--border-color: #fff;
--border-color-soft: #ccc;
/* Backgrounds */
--secondary-bg: #1a1a1a;
--secondary-bg-rgb: 26, 26, 26;
--tertiary-bg: #111;
--tertiary-bg-rgb: 17, 17, 17;
/* Navigation */
--nav-bg-color: #000;
--nav-text-color: #fff;
--mainmenu-nav-link-color: #ffff00;
/* Buttons */
--btn-color: #000;
--btn-bg: #ffff00;
--btn-border-color: #ffff00;
--btn-hover-color: #000;
--btn-hover-bg: #00ffff;
--btn-hover-border-color: #00ffff;
--btn-active-color: #000;
--btn-active-bg: #00ffff;
--btn-active-border-color: #00ffff;
/* Forms */
--input-color: #fff;
--input-bg: #000;
--input-border-color: #fff;
--input-focus-color: #fff;
--input-focus-bg: #1a1a1a;
--input-focus-border-color: #ffff00;
--input-placeholder-color: #aaa;
/* Cards */
--card-border-color: #fff;
--card-bg: #000;
--card-cap-bg: #1a1a1a;
/* Tables */
--table-color: #fff;
--table-bg: #000;
--table-border-color: #fff;
--table-striped-bg: #111;
--table-hover-bg: #333;
/* Alerts */
--alert-border-width: 2px;
/* Code */
--code-color: #00ff00;
--code-bg-color: #1a1a1a;
/* Selection */
--selection-bg: #ffff00;
--selection-ink: #000;
/* Focus indicator */
--focus-ring-color: #ffff00;
--focus-ring-width: 3px;
}
/* ── Shared high-contrast overrides (both modes) ──────────────────── */
.a11y-high-contrast * {
border-color: var(--border-color) !important;
}
.a11y-high-contrast *:focus-visible {
outline: var(--focus-ring-width, 3px) solid var(--focus-ring-color, #0000ee) !important;
outline-offset: 2px !important;
}
.a11y-high-contrast img {
outline: 2px solid var(--border-color);
}
.a11y-high-contrast a {
text-decoration: underline !important;
text-decoration-thickness: 2px !important;
}
.a11y-high-contrast button,
.a11y-high-contrast .btn,
.a11y-high-contrast input,
.a11y-high-contrast select,
.a11y-high-contrast textarea {
border-width: 2px !important;
border-style: solid !important;
}
.a11y-high-contrast .badge,
.a11y-high-contrast .alert {
border: 2px solid var(--border-color) !important;
}
/* Ensure disabled states are still distinguishable */
.a11y-high-contrast [disabled],
.a11y-high-contrast .disabled {
opacity: .5 !important;
text-decoration: line-through !important;
}

View File

@@ -17070,8 +17070,8 @@ form .form-select {
background: var(--muted-color, #6d757e);
box-shadow: var(--box-shadow, 0 .5rem 1rem #00000066);
font: inherit;
color: var(--body-bg, #0e1318);
font-weight: 600px;
color: #fff;
font-weight: 600;
}
#mokoThemeFab.pos-br {
@@ -17133,10 +17133,11 @@ button#mokoThemeSwitch {
#mokoThemeFab .label {
user-select: none;
font-size: .875rem;
color: #fff;
}
#mokoThemeFab button {
color: var(--body-bg, #0e1318);
color: #fff;
}
/* Auto toggle switch (on/off style) */

View File

@@ -817,6 +817,12 @@ color-scheme: dark;
--table-active-color: var(--body-color);
--table-active-bg: rgba(var(--white-rgb), 0.1);
/* ===== FOOTER ===== */
--footer-padding-top: 1rem;
--footer-padding-bottom: 80px;
--footer-grid-padding-y: 2.5rem;
--footer-grid-padding-x: 0.5em;
/* ===== BACKDROP ===== */
--backdrop-zindex: 1040;
--backdrop-bg: hsl(0, 0%, 0%);

View File

@@ -817,6 +817,12 @@ color-scheme: dark;
--table-active-color: var(--body-color);
--table-active-bg: rgba(var(--white-rgb), 0.1);
/* ===== FOOTER ===== */
--footer-padding-top: 1rem;
--footer-padding-bottom: 80px;
--footer-grid-padding-y: 2.5rem;
--footer-grid-padding-x: 0.5em;
/* ===== BACKDROP ===== */
--backdrop-zindex: 1040;
--backdrop-bg: hsl(0, 0%, 0%);

View File

@@ -816,6 +816,12 @@ color-scheme: light;
--table-active-color: var(--body-color);
--table-active-bg: rgba(var(--black-rgb), 0.075);
/* ===== FOOTER ===== */
--footer-padding-top: 1rem;
--footer-padding-bottom: 80px;
--footer-grid-padding-y: 2.5rem;
--footer-grid-padding-x: 0.5em;
/* ===== BACKDROP ===== */
--backdrop-zindex: 1040;
--backdrop-bg: hsl(0, 0%, 0%);

View File

@@ -816,6 +816,12 @@ color-scheme: light;
--table-active-color: var(--body-color);
--table-active-bg: rgba(var(--black-rgb), 0.075);
/* ===== FOOTER ===== */
--footer-padding-top: 1rem;
--footer-padding-bottom: 80px;
--footer-grid-padding-y: 2.5rem;
--footer-grid-padding-x: 0.5em;
/* ===== BACKDROP ===== */
--backdrop-zindex: 1040;
--backdrop-bg: hsl(0, 0%, 0%);

View File

@@ -229,6 +229,18 @@
function applyContrast(on) {
root.classList.toggle("a11y-high-contrast", on);
// Lazy-load the high-contrast stylesheet
var hcId = "mokoA11yHcSheet";
var existing = doc.getElementById(hcId);
if (on && !existing) {
var link = doc.createElement("link");
link.id = hcId;
link.rel = "stylesheet";
link.href = (doc.querySelector('link[href*="mokocassiopeia/css/template"]') || {}).href
? (doc.querySelector('link[href*="mokocassiopeia/css/template"]').href.replace(/\/css\/template[^/]*$/, "/css/a11y-high-contrast.css"))
: "media/templates/site/mokocassiopeia/css/a11y-high-contrast.css";
doc.head.appendChild(link);
}
}
function applyLinks(on) {
@@ -245,10 +257,10 @@
/** Create a Font Awesome icon element (safe DOM, no innerHTML). */
function faIcon(classes) {
var span = doc.createElement("span");
span.className = classes;
span.setAttribute("aria-hidden", "true");
return span;
var i = doc.createElement("i");
i.className = classes;
i.setAttribute("aria-hidden", "true");
return i;
}
function buildA11yToolbar() {

View File

@@ -813,6 +813,12 @@ color-scheme: dark;
--table-active-color: var(--body-color);
--table-active-bg: rgba(var(--white-rgb), 0.1);
/* ===== FOOTER ===== */
--footer-padding-top: 1rem;
--footer-padding-bottom: 80px;
--footer-grid-padding-y: 2.5rem;
--footer-grid-padding-x: 0.5em;
/* ===== BACKDROP ===== */
--backdrop-zindex: 1040;
--backdrop-bg: hsl(0, 0%, 0%);

View File

@@ -812,6 +812,12 @@ color-scheme: light;
--table-active-color: var(--body-color);
--table-active-bg: rgba(var(--black-rgb), 0.075);
/* ===== FOOTER ===== */
--footer-padding-top: 1rem;
--footer-padding-bottom: 80px;
--footer-grid-padding-y: 2.5rem;
--footer-grid-padding-x: 0.5em;
/* ===== BACKDROP ===== */
--backdrop-zindex: 1040;
--backdrop-bg: hsl(0, 0%, 0%);