Fix a11y icon, FAB label colors, add high-contrast stylesheet, sync theme vars
- 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:
@@ -124,6 +124,12 @@
|
|||||||
"uri": "media/templates/site/mokocassiopeia/css/theme/dark.custom.min.css",
|
"uri": "media/templates/site/mokocassiopeia/css/theme/dark.custom.min.css",
|
||||||
"attributes": {"media": "all"}
|
"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",
|
"name": "template.js",
|
||||||
"type": "script",
|
"type": "script",
|
||||||
|
|||||||
227
src/media/css/a11y-high-contrast.css
Normal file
227
src/media/css/a11y-high-contrast.css
Normal 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;
|
||||||
|
}
|
||||||
@@ -17070,8 +17070,8 @@ form .form-select {
|
|||||||
background: var(--muted-color, #6d757e);
|
background: var(--muted-color, #6d757e);
|
||||||
box-shadow: var(--box-shadow, 0 .5rem 1rem #00000066);
|
box-shadow: var(--box-shadow, 0 .5rem 1rem #00000066);
|
||||||
font: inherit;
|
font: inherit;
|
||||||
color: var(--body-bg, #0e1318);
|
color: #fff;
|
||||||
font-weight: 600px;
|
font-weight: 600;
|
||||||
}
|
}
|
||||||
|
|
||||||
#mokoThemeFab.pos-br {
|
#mokoThemeFab.pos-br {
|
||||||
@@ -17133,10 +17133,11 @@ button#mokoThemeSwitch {
|
|||||||
#mokoThemeFab .label {
|
#mokoThemeFab .label {
|
||||||
user-select: none;
|
user-select: none;
|
||||||
font-size: .875rem;
|
font-size: .875rem;
|
||||||
|
color: #fff;
|
||||||
}
|
}
|
||||||
|
|
||||||
#mokoThemeFab button {
|
#mokoThemeFab button {
|
||||||
color: var(--body-bg, #0e1318);
|
color: #fff;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Auto toggle switch (on/off style) */
|
/* Auto toggle switch (on/off style) */
|
||||||
|
|||||||
@@ -817,6 +817,12 @@ color-scheme: dark;
|
|||||||
--table-active-color: var(--body-color);
|
--table-active-color: var(--body-color);
|
||||||
--table-active-bg: rgba(var(--white-rgb), 0.1);
|
--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 ===== */
|
||||||
--backdrop-zindex: 1040;
|
--backdrop-zindex: 1040;
|
||||||
--backdrop-bg: hsl(0, 0%, 0%);
|
--backdrop-bg: hsl(0, 0%, 0%);
|
||||||
|
|||||||
@@ -817,6 +817,12 @@ color-scheme: dark;
|
|||||||
--table-active-color: var(--body-color);
|
--table-active-color: var(--body-color);
|
||||||
--table-active-bg: rgba(var(--white-rgb), 0.1);
|
--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 ===== */
|
||||||
--backdrop-zindex: 1040;
|
--backdrop-zindex: 1040;
|
||||||
--backdrop-bg: hsl(0, 0%, 0%);
|
--backdrop-bg: hsl(0, 0%, 0%);
|
||||||
|
|||||||
@@ -816,6 +816,12 @@ color-scheme: light;
|
|||||||
--table-active-color: var(--body-color);
|
--table-active-color: var(--body-color);
|
||||||
--table-active-bg: rgba(var(--black-rgb), 0.075);
|
--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 ===== */
|
||||||
--backdrop-zindex: 1040;
|
--backdrop-zindex: 1040;
|
||||||
--backdrop-bg: hsl(0, 0%, 0%);
|
--backdrop-bg: hsl(0, 0%, 0%);
|
||||||
|
|||||||
@@ -816,6 +816,12 @@ color-scheme: light;
|
|||||||
--table-active-color: var(--body-color);
|
--table-active-color: var(--body-color);
|
||||||
--table-active-bg: rgba(var(--black-rgb), 0.075);
|
--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 ===== */
|
||||||
--backdrop-zindex: 1040;
|
--backdrop-zindex: 1040;
|
||||||
--backdrop-bg: hsl(0, 0%, 0%);
|
--backdrop-bg: hsl(0, 0%, 0%);
|
||||||
|
|||||||
@@ -229,6 +229,18 @@
|
|||||||
|
|
||||||
function applyContrast(on) {
|
function applyContrast(on) {
|
||||||
root.classList.toggle("a11y-high-contrast", 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) {
|
function applyLinks(on) {
|
||||||
@@ -245,10 +257,10 @@
|
|||||||
|
|
||||||
/** Create a Font Awesome icon element (safe DOM, no innerHTML). */
|
/** Create a Font Awesome icon element (safe DOM, no innerHTML). */
|
||||||
function faIcon(classes) {
|
function faIcon(classes) {
|
||||||
var span = doc.createElement("span");
|
var i = doc.createElement("i");
|
||||||
span.className = classes;
|
i.className = classes;
|
||||||
span.setAttribute("aria-hidden", "true");
|
i.setAttribute("aria-hidden", "true");
|
||||||
return span;
|
return i;
|
||||||
}
|
}
|
||||||
|
|
||||||
function buildA11yToolbar() {
|
function buildA11yToolbar() {
|
||||||
|
|||||||
@@ -813,6 +813,12 @@ color-scheme: dark;
|
|||||||
--table-active-color: var(--body-color);
|
--table-active-color: var(--body-color);
|
||||||
--table-active-bg: rgba(var(--white-rgb), 0.1);
|
--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 ===== */
|
||||||
--backdrop-zindex: 1040;
|
--backdrop-zindex: 1040;
|
||||||
--backdrop-bg: hsl(0, 0%, 0%);
|
--backdrop-bg: hsl(0, 0%, 0%);
|
||||||
|
|||||||
@@ -812,6 +812,12 @@ color-scheme: light;
|
|||||||
--table-active-color: var(--body-color);
|
--table-active-color: var(--body-color);
|
||||||
--table-active-bg: rgba(var(--black-rgb), 0.075);
|
--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 ===== */
|
||||||
--backdrop-zindex: 1040;
|
--backdrop-zindex: 1040;
|
||||||
--backdrop-bg: hsl(0, 0%, 0%);
|
--backdrop-bg: hsl(0, 0%, 0%);
|
||||||
|
|||||||
Reference in New Issue
Block a user