Add accessibility toolbar with 6 toggleable options

Adds a floating accessibility toolbar to the template with individually
enable/disable options: text resize, color inversion, high contrast,
highlight links, readable font, and pause animations. Each option has
an admin toggle in the Theme tab and persists visitor preferences in
localStorage. Also fixes count() on null in mod_login override.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-04 11:21:22 -05:00
parent 005644b07f
commit 28bf07a443
7 changed files with 543 additions and 2 deletions

View File

@@ -71,7 +71,7 @@ $headerClass = htmlspecialchars($params->get('header_class', ''), ENT_COMPAT, 'U
</div> </div>
</div> </div>
<?php if (count($twofactormethods) > 1) : ?> <?php if (!empty($twofactormethods) && count($twofactormethods) > 1) : ?>
<div class="mod-login__field mb-3"> <div class="mod-login__field mb-3">
<label for="modlgn-secretkey-<?php echo $module->id; ?>" class="form-label visually-hidden"><?php echo Text::_('JGLOBAL_SECRETKEY'); ?></label> <label for="modlgn-secretkey-<?php echo $module->id; ?>" class="form-label visually-hidden"><?php echo Text::_('JGLOBAL_SECRETKEY'); ?></label>
<div class="input-group"> <div class="input-group">

View File

@@ -43,6 +43,16 @@ $params_theme_control_type = (string) $this->params->get('theme_control_type', '
$params_theme_fab_enabled = $this->params->get('theme_fab_enabled', 1); $params_theme_fab_enabled = $this->params->get('theme_fab_enabled', 1);
$params_theme_fab_pos = $this->params->get('theme_fab_pos', 'br'); $params_theme_fab_pos = $this->params->get('theme_fab_pos', 'br');
// Accessibility params
$params_a11y_toolbar = $this->params->get('a11y_toolbar_enabled', 1);
$params_a11y_resize = $this->params->get('a11y_text_resize', 1);
$params_a11y_invert = $this->params->get('a11y_color_inversion', 1);
$params_a11y_contrast = $this->params->get('a11y_high_contrast', 1);
$params_a11y_links = $this->params->get('a11y_highlight_links', 1);
$params_a11y_font = $this->params->get('a11y_readable_font', 1);
$params_a11y_animations = $this->params->get('a11y_pause_animations', 1);
$params_a11y_pos = (string) $this->params->get('a11y_toolbar_pos', 'tl');
// Detecting Active Variables // Detecting Active Variables
$option = $input->getCmd('option', ''); $option = $input->getCmd('option', '');
$view = $input->getCmd('view', ''); $view = $input->getCmd('view', '');
@@ -277,8 +287,16 @@ $wa->useScript('user.js'); // js/user.js
<?php if (trim($params_custom_head_end)) : ?><?php echo $params_custom_head_end; ?><?php endif; ?> <?php if (trim($params_custom_head_end)) : ?><?php echo $params_custom_head_end; ?><?php endif; ?>
</head> </head>
<body data-bs-spy="scroll" data-bs-target="#toc" <body data-bs-spy="scroll" data-bs-target="#toc"
data-theme-fab-enabled="<?php echo $params_theme_fab_enabled ? '1' : '0'; ?>" data-theme-fab-enabled="<?php echo $params_theme_fab_enabled ? '1' : '0'; ?>"
data-theme-fab-pos="<?php echo htmlspecialchars($params_theme_fab_pos, ENT_QUOTES, 'UTF-8'); ?>" data-theme-fab-pos="<?php echo htmlspecialchars($params_theme_fab_pos, ENT_QUOTES, 'UTF-8'); ?>"
data-a11y-toolbar="<?php echo $params_a11y_toolbar ? '1' : '0'; ?>"
data-a11y-resize="<?php echo $params_a11y_resize ? '1' : '0'; ?>"
data-a11y-invert="<?php echo $params_a11y_invert ? '1' : '0'; ?>"
data-a11y-contrast="<?php echo $params_a11y_contrast ? '1' : '0'; ?>"
data-a11y-links="<?php echo $params_a11y_links ? '1' : '0'; ?>"
data-a11y-font="<?php echo $params_a11y_font ? '1' : '0'; ?>"
data-a11y-animations="<?php echo $params_a11y_animations ? '1' : '0'; ?>"
data-a11y-pos="<?php echo htmlspecialchars($params_a11y_pos, ENT_QUOTES, 'UTF-8'); ?>"
class="site <?php class="site <?php
echo $option . ' ' . $wrapper echo $option . ' ' . $wrapper
. ' view-' . $view . ' view-' . $view

View File

@@ -108,6 +108,29 @@ TPL_MOKO_THEME_FAB_ENABLED_DESC="Display a persistent, accessible theme toggle."
TPL_MOKO_THEME_FAB_POS="Floating switch position" TPL_MOKO_THEME_FAB_POS="Floating switch position"
TPL_MOKO_THEME_FAB_POS_DESC="Screen corner for the toggle." TPL_MOKO_THEME_FAB_POS_DESC="Screen corner for the toggle."
; ===== Accessibility toolbar =====
TPL_MOKO_A11Y_TOOLBAR_ENABLED="Accessibility toolbar"
TPL_MOKO_A11Y_TOOLBAR_ENABLED_DESC="Show a floating accessibility toolbar with text resize and colour inversion controls."
TPL_MOKO_A11Y_TEXT_RESIZE="Text resize"
TPL_MOKO_A11Y_TEXT_RESIZE_DESC="Allow visitors to increase or decrease text size."
TPL_MOKO_A11Y_COLOR_INVERSION="Colour inversion"
TPL_MOKO_A11Y_COLOR_INVERSION_DESC="Allow visitors to invert page colours for improved readability."
TPL_MOKO_A11Y_TOOLBAR_POS="Toolbar position"
TPL_MOKO_A11Y_TOOLBAR_POS_DESC="Screen corner for the accessibility toolbar."
TPL_MOKO_A11Y_BTN_LABEL="Accessibility options"
TPL_MOKO_A11Y_TEXT_DECREASE="Decrease text size"
TPL_MOKO_A11Y_TEXT_RESET="Reset text size"
TPL_MOKO_A11Y_TEXT_INCREASE="Increase text size"
TPL_MOKO_A11Y_INVERT_COLORS="Invert colours"
TPL_MOKO_A11Y_HIGH_CONTRAST="High contrast"
TPL_MOKO_A11Y_HIGH_CONTRAST_DESC="Allow visitors to boost page contrast for improved readability."
TPL_MOKO_A11Y_HIGHLIGHT_LINKS="Highlight links"
TPL_MOKO_A11Y_HIGHLIGHT_LINKS_DESC="Allow visitors to outline all links so they stand out from surrounding text."
TPL_MOKO_A11Y_READABLE_FONT="Readable font"
TPL_MOKO_A11Y_READABLE_FONT_DESC="Allow visitors to switch to a clean system font optimised for readability."
TPL_MOKO_A11Y_PAUSE_ANIMATIONS="Pause animations"
TPL_MOKO_A11Y_PAUSE_ANIMATIONS_DESC="Allow visitors to stop all CSS animations and transitions."
; ===== CSS Variables tab ===== ; ===== CSS Variables tab =====
TPL_MOKOCASSIOPEIA_CSS_VARS_FIELDSET_LABEL="CSS Variables" TPL_MOKOCASSIOPEIA_CSS_VARS_FIELDSET_LABEL="CSS Variables"
TPL_MOKOCASSIOPEIA_CSS_VARS_INTRO="<p>All colours, spacing and layout values are driven by CSS custom properties. To override any variable without editing the template, add your overrides to <code>media/templates/site/mokocassiopeia/css/user.css</code>, or create a custom palette file (see the Theme tab). Variables are scoped to <code>:root[data-bs-theme=&quot;light&quot;]</code> or <code>:root[data-bs-theme=&quot;dark&quot;]</code> so light and dark values are independent.</p>" TPL_MOKOCASSIOPEIA_CSS_VARS_INTRO="<p>All colours, spacing and layout values are driven by CSS custom properties. To override any variable without editing the template, add your overrides to <code>media/templates/site/mokocassiopeia/css/user.css</code>, or create a custom palette file (see the Theme tab). Variables are scoped to <code>:root[data-bs-theme=&quot;light&quot;]</code> or <code>:root[data-bs-theme=&quot;dark&quot;]</code> so light and dark values are independent.</p>"

View File

@@ -108,6 +108,29 @@ TPL_MOKO_THEME_FAB_ENABLED_DESC="Display a persistent, accessible theme toggle."
TPL_MOKO_THEME_FAB_POS="Floating switch position" TPL_MOKO_THEME_FAB_POS="Floating switch position"
TPL_MOKO_THEME_FAB_POS_DESC="Screen corner for the toggle." TPL_MOKO_THEME_FAB_POS_DESC="Screen corner for the toggle."
; ===== Accessibility toolbar =====
TPL_MOKO_A11Y_TOOLBAR_ENABLED="Accessibility toolbar"
TPL_MOKO_A11Y_TOOLBAR_ENABLED_DESC="Show a floating accessibility toolbar with text resize and color inversion controls."
TPL_MOKO_A11Y_TEXT_RESIZE="Text resize"
TPL_MOKO_A11Y_TEXT_RESIZE_DESC="Allow visitors to increase or decrease text size."
TPL_MOKO_A11Y_COLOR_INVERSION="Color inversion"
TPL_MOKO_A11Y_COLOR_INVERSION_DESC="Allow visitors to invert page colors for improved readability."
TPL_MOKO_A11Y_TOOLBAR_POS="Toolbar position"
TPL_MOKO_A11Y_TOOLBAR_POS_DESC="Screen corner for the accessibility toolbar."
TPL_MOKO_A11Y_BTN_LABEL="Accessibility options"
TPL_MOKO_A11Y_TEXT_DECREASE="Decrease text size"
TPL_MOKO_A11Y_TEXT_RESET="Reset text size"
TPL_MOKO_A11Y_TEXT_INCREASE="Increase text size"
TPL_MOKO_A11Y_INVERT_COLORS="Invert colors"
TPL_MOKO_A11Y_HIGH_CONTRAST="High contrast"
TPL_MOKO_A11Y_HIGH_CONTRAST_DESC="Allow visitors to boost page contrast for improved readability."
TPL_MOKO_A11Y_HIGHLIGHT_LINKS="Highlight links"
TPL_MOKO_A11Y_HIGHLIGHT_LINKS_DESC="Allow visitors to outline all links so they stand out from surrounding text."
TPL_MOKO_A11Y_READABLE_FONT="Readable font"
TPL_MOKO_A11Y_READABLE_FONT_DESC="Allow visitors to switch to a clean system font optimized for readability."
TPL_MOKO_A11Y_PAUSE_ANIMATIONS="Pause animations"
TPL_MOKO_A11Y_PAUSE_ANIMATIONS_DESC="Allow visitors to stop all CSS animations and transitions."
; ===== CSS Variables tab ===== ; ===== CSS Variables tab =====
TPL_MOKOCASSIOPEIA_CSS_VARS_FIELDSET_LABEL="CSS Variables" TPL_MOKOCASSIOPEIA_CSS_VARS_FIELDSET_LABEL="CSS Variables"
TPL_MOKOCASSIOPEIA_CSS_VARS_INTRO="<p>All colors, spacing and layout values are driven by CSS custom properties. To override any variable without editing the template, add your overrides to <code>media/templates/site/mokocassiopeia/css/user.css</code>, or create a custom palette file (see the Theme tab). Variables are scoped to <code>:root[data-bs-theme=&quot;light&quot;]</code> or <code>:root[data-bs-theme=&quot;dark&quot;]</code> so light and dark values are independent.</p>" TPL_MOKOCASSIOPEIA_CSS_VARS_INTRO="<p>All colors, spacing and layout values are driven by CSS custom properties. To override any variable without editing the template, add your overrides to <code>media/templates/site/mokocassiopeia/css/user.css</code>, or create a custom palette file (see the Theme tab). Variables are scoped to <code>:root[data-bs-theme=&quot;light&quot;]</code> or <code>:root[data-bs-theme=&quot;dark&quot;]</code> so light and dark values are independent.</p>"

View File

@@ -17180,6 +17180,190 @@ button#mokoThemeSwitch {
outline-offset: 2px; outline-offset: 2px;
} }
/* ================================================================
ACCESSIBILITY TOOLBAR
================================================================ */
/* Color inversion */
html.a11y-inverted {
filter: invert(1) hue-rotate(180deg);
}
html.a11y-inverted img,
html.a11y-inverted video,
html.a11y-inverted picture,
html.a11y-inverted svg,
html.a11y-inverted [style*="background-image"],
html.a11y-inverted .brand-logo img {
filter: invert(1) hue-rotate(180deg);
}
/* High contrast */
html.a11y-high-contrast {
filter: contrast(1.4);
}
html.a11y-high-contrast img,
html.a11y-high-contrast video,
html.a11y-high-contrast picture {
filter: contrast(0.714);
}
/* Highlight links */
html.a11y-highlight-links a {
outline: 2px solid currentColor !important;
outline-offset: 2px !important;
text-decoration: underline !important;
}
/* Readable font — system sans-serif stack for maximum clarity */
html.a11y-readable-font,
html.a11y-readable-font * {
font-family: system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif !important;
letter-spacing: 0.02em !important;
word-spacing: 0.05em !important;
}
/* Pause animations */
html.a11y-pause-animations *,
html.a11y-pause-animations *::before,
html.a11y-pause-animations *::after {
animation-duration: 0s !important;
animation-delay: 0s !important;
transition-duration: 0s !important;
transition-delay: 0s !important;
scroll-behavior: auto !important;
}
/* Toolbar container */
#mokoA11yToolbar {
position: fixed;
z-index: 1201;
display: flex;
flex-direction: column;
align-items: flex-start;
gap: .25rem;
font-family: inherit;
}
#mokoA11yToolbar.a11y-pos-tl { top: 1rem; left: 1rem; }
#mokoA11yToolbar.a11y-pos-tr { top: 1rem; right: 1rem; align-items: flex-end; }
#mokoA11yToolbar.a11y-pos-bl { bottom: 1rem; left: 1rem; }
#mokoA11yToolbar.a11y-pos-br { bottom: 1rem; right: 1rem; align-items: flex-end; }
/* Toggle button */
.a11y-toggle {
display: flex;
align-items: center;
justify-content: center;
width: 42px;
height: 42px;
border-radius: 50%;
border: 2px solid var(--link-color, #3565e5);
background: var(--bs-body-bg, #fff);
color: var(--link-color, #3565e5);
font-size: 1.25rem;
cursor: pointer;
transition: background .2s, color .2s, box-shadow .2s;
box-shadow: var(--box-shadow, 0 .25rem .5rem rgba(0,0,0,.15));
}
.a11y-toggle:hover,
.a11y-toggle:focus-visible {
background: var(--link-color, #3565e5);
color: #fff;
}
.a11y-toggle.active {
background: var(--link-color, #3565e5);
color: #fff;
}
/* Panel */
.a11y-panel {
background: var(--bs-body-bg, #fff);
border: 1px solid var(--border-color, #dee2e6);
border-radius: var(--border-radius, .375rem);
padding: .75rem;
min-width: 200px;
box-shadow: var(--box-shadow-lg, 0 1rem 3rem rgba(0,0,0,.175));
}
.a11y-group {
margin-bottom: .5rem;
}
.a11y-group:last-child {
margin-bottom: 0;
}
.a11y-group-label {
display: block;
font-size: .75rem;
font-weight: 600;
text-transform: uppercase;
letter-spacing: .05em;
margin-bottom: .35rem;
color: var(--body-font-color, #444);
opacity: .7;
}
.a11y-btn-row {
display: flex;
align-items: center;
gap: .35rem;
}
.a11y-btn {
display: inline-flex;
align-items: center;
justify-content: center;
min-width: 34px;
height: 34px;
border: 1px solid var(--border-color, #dee2e6);
border-radius: var(--border-radius, .375rem);
background: var(--bs-body-bg, #fff);
color: var(--body-font-color, #444);
font-size: .875rem;
cursor: pointer;
transition: background .15s, border-color .15s;
}
.a11y-btn:hover,
.a11y-btn:focus-visible {
border-color: var(--link-color, #3565e5);
background: var(--link-color, #3565e5);
color: #fff;
}
.a11y-size-display {
font-size: .8rem;
font-weight: 600;
min-width: 3ch;
text-align: center;
color: var(--body-font-color, #444);
}
.a11y-btn-wide {
width: 100%;
gap: .5rem;
padding: .35rem .75rem;
justify-content: flex-start;
}
.a11y-btn-wide.active {
background: var(--link-color, #3565e5);
border-color: var(--link-color, #3565e5);
color: #fff;
}
@media (prefers-reduced-motion: reduce) {
.a11y-toggle,
.a11y-btn {
transition: none;
}
}
html.component body{ html.component body{
padding-top: 3.125rem; padding-top: 3.125rem;
} }

View File

@@ -160,6 +160,235 @@
}, 50); }, 50);
} }
// ========================================================================
// ACCESSIBILITY TOOLBAR
// ========================================================================
var a11yStorageKey = "moko-a11y";
var fontSizeSteps = [85, 90, 100, 110, 120, 130];
var defaultStep = 2; // index of 100
function getA11yPrefs() {
try {
var raw = localStorage.getItem(a11yStorageKey);
if (raw) return JSON.parse(raw);
} catch (e) {}
return { fontStep: defaultStep, inverted: false, contrast: false, links: false, font: false, paused: false };
}
function saveA11yPrefs(prefs) {
try { localStorage.setItem(a11yStorageKey, JSON.stringify(prefs)); } catch (e) {}
}
function applyFontSize(step) {
root.style.fontSize = fontSizeSteps[step] + "%";
}
function applyInversion(on) {
if (on) {
root.classList.add("a11y-inverted");
} else {
root.classList.remove("a11y-inverted");
}
}
function applyContrast(on) {
root.classList.toggle("a11y-high-contrast", on);
}
function applyLinks(on) {
root.classList.toggle("a11y-highlight-links", on);
}
function applyFont(on) {
root.classList.toggle("a11y-readable-font", on);
}
function applyPaused(on) {
root.classList.toggle("a11y-pause-animations", on);
}
/** 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;
}
function buildA11yToolbar() {
if (doc.getElementById("mokoA11yToolbar")) return;
var body = doc.body;
var showResize = body.getAttribute("data-a11y-resize") === "1";
var showInvert = body.getAttribute("data-a11y-invert") === "1";
var showContrast = body.getAttribute("data-a11y-contrast") === "1";
var showLinks = body.getAttribute("data-a11y-links") === "1";
var showFont = body.getAttribute("data-a11y-font") === "1";
var showAnimations = body.getAttribute("data-a11y-animations") === "1";
var pos = (body.getAttribute("data-a11y-pos") || "tl").toLowerCase();
if (!/^(br|bl|tr|tl)$/.test(pos)) pos = "tl";
var prefs = getA11yPrefs();
// Container
var toolbar = doc.createElement("div");
toolbar.id = "mokoA11yToolbar";
toolbar.className = "a11y-pos-" + pos;
toolbar.setAttribute("role", "toolbar");
toolbar.setAttribute("aria-label", "Accessibility options");
// Toggle button (accessibility icon)
var toggle = doc.createElement("button");
toggle.type = "button";
toggle.id = "mokoA11yToggle";
toggle.className = "a11y-toggle";
toggle.setAttribute("aria-label", "Accessibility options");
toggle.setAttribute("aria-expanded", "false");
toggle.appendChild(faIcon("fa-solid fa-universal-access"));
// Panel
var panel = doc.createElement("div");
panel.id = "mokoA11yPanel";
panel.className = "a11y-panel";
panel.hidden = true;
// --- Text resize controls ---
if (showResize) {
var resizeGroup = doc.createElement("div");
resizeGroup.className = "a11y-group";
resizeGroup.setAttribute("role", "group");
resizeGroup.setAttribute("aria-label", "Text size");
var sizeLabel = doc.createElement("span");
sizeLabel.className = "a11y-group-label";
sizeLabel.textContent = "Text size";
resizeGroup.appendChild(sizeLabel);
var btnRow = doc.createElement("div");
btnRow.className = "a11y-btn-row";
var btnMinus = doc.createElement("button");
btnMinus.type = "button";
btnMinus.className = "a11y-btn";
btnMinus.setAttribute("aria-label", "Decrease text size");
btnMinus.appendChild(faIcon("fa-solid fa-minus"));
var sizeDisplay = doc.createElement("span");
sizeDisplay.className = "a11y-size-display";
sizeDisplay.setAttribute("aria-live", "polite");
sizeDisplay.textContent = fontSizeSteps[prefs.fontStep] + "%";
var btnPlus = doc.createElement("button");
btnPlus.type = "button";
btnPlus.className = "a11y-btn";
btnPlus.setAttribute("aria-label", "Increase text size");
btnPlus.appendChild(faIcon("fa-solid fa-plus"));
var btnReset = doc.createElement("button");
btnReset.type = "button";
btnReset.className = "a11y-btn a11y-btn-reset";
btnReset.setAttribute("aria-label", "Reset text size");
btnReset.appendChild(faIcon("fa-solid fa-rotate-left"));
btnMinus.addEventListener("click", function () {
if (prefs.fontStep > 0) {
prefs.fontStep--;
applyFontSize(prefs.fontStep);
sizeDisplay.textContent = fontSizeSteps[prefs.fontStep] + "%";
saveA11yPrefs(prefs);
}
});
btnPlus.addEventListener("click", function () {
if (prefs.fontStep < fontSizeSteps.length - 1) {
prefs.fontStep++;
applyFontSize(prefs.fontStep);
sizeDisplay.textContent = fontSizeSteps[prefs.fontStep] + "%";
saveA11yPrefs(prefs);
}
});
btnReset.addEventListener("click", function () {
prefs.fontStep = defaultStep;
applyFontSize(prefs.fontStep);
sizeDisplay.textContent = fontSizeSteps[prefs.fontStep] + "%";
saveA11yPrefs(prefs);
});
btnRow.appendChild(btnMinus);
btnRow.appendChild(sizeDisplay);
btnRow.appendChild(btnPlus);
btnRow.appendChild(btnReset);
resizeGroup.appendChild(btnRow);
panel.appendChild(resizeGroup);
}
// --- Helper: build a switch-style toggle button ---
function addSwitchOption(show, prefKey, icon, label, applyFn) {
if (!show) return;
var group = doc.createElement("div");
group.className = "a11y-group";
var btn = doc.createElement("button");
btn.type = "button";
btn.className = "a11y-btn a11y-btn-wide";
btn.setAttribute("role", "switch");
btn.setAttribute("aria-checked", prefs[prefKey] ? "true" : "false");
btn.setAttribute("aria-label", label);
btn.appendChild(faIcon(icon));
btn.appendChild(doc.createTextNode(" " + label));
if (prefs[prefKey]) btn.classList.add("active");
btn.addEventListener("click", function () {
prefs[prefKey] = !prefs[prefKey];
applyFn(prefs[prefKey]);
btn.setAttribute("aria-checked", prefs[prefKey] ? "true" : "false");
btn.classList.toggle("active", prefs[prefKey]);
saveA11yPrefs(prefs);
});
group.appendChild(btn);
panel.appendChild(group);
}
// --- Toggle options ---
addSwitchOption(showInvert, "inverted", "fa-solid fa-circle-half-stroke", "Invert colors", applyInversion);
addSwitchOption(showContrast, "contrast", "fa-solid fa-adjust", "High contrast", applyContrast);
addSwitchOption(showLinks, "links", "fa-solid fa-link", "Highlight links", applyLinks);
addSwitchOption(showFont, "font", "fa-solid fa-font", "Readable font", applyFont);
addSwitchOption(showAnimations, "paused", "fa-solid fa-pause", "Pause animations", applyPaused);
// Toggle panel open/close
toggle.addEventListener("click", function () {
var isOpen = !panel.hidden;
panel.hidden = isOpen;
toggle.setAttribute("aria-expanded", isOpen ? "false" : "true");
toggle.classList.toggle("active", !isOpen);
});
// Close on outside click
doc.addEventListener("click", function (e) {
if (!toolbar.contains(e.target) && !panel.hidden) {
panel.hidden = true;
toggle.setAttribute("aria-expanded", "false");
toggle.classList.remove("active");
}
});
// Apply saved preferences on load
if (prefs.fontStep !== defaultStep) applyFontSize(prefs.fontStep);
if (prefs.inverted) applyInversion(true);
if (prefs.contrast) applyContrast(true);
if (prefs.links) applyLinks(true);
if (prefs.font) applyFont(true);
if (prefs.paused) applyPaused(true);
toolbar.appendChild(toggle);
toolbar.appendChild(panel);
body.appendChild(toolbar);
}
// ======================================================================== // ========================================================================
// TEMPLATE UTILITIES // TEMPLATE UTILITIES
// ======================================================================== // ========================================================================
@@ -271,6 +500,11 @@
buildThemeToggle(); buildThemeToggle();
} }
// Build accessibility toolbar if enabled
if (doc.body.getAttribute("data-a11y-toolbar") === "1") {
buildA11yToolbar();
}
// Sticky header behavior // Sticky header behavior
handleScroll(); handleScroll();
win.addEventListener("scroll", handleScroll); win.addEventListener("scroll", handleScroll);

View File

@@ -241,6 +241,65 @@
<option value="1">JYES</option> <option value="1">JYES</option>
</field> </field>
<!-- Accessibility -->
<field name="theme_sep_a11y" type="spacer" label="Accessibility" hr="false" class="text fw-bold" />
<field name="a11y_toolbar_enabled" type="radio" default="1"
label="TPL_MOKO_A11Y_TOOLBAR_ENABLED" description="TPL_MOKO_A11Y_TOOLBAR_ENABLED_DESC"
layout="joomla.form.field.radio.switcher" filter="boolean">
<option value="0">JNO</option>
<option value="1">JYES</option>
</field>
<field name="a11y_text_resize" type="radio" default="1"
label="TPL_MOKO_A11Y_TEXT_RESIZE" description="TPL_MOKO_A11Y_TEXT_RESIZE_DESC"
layout="joomla.form.field.radio.switcher" filter="boolean"
showon="a11y_toolbar_enabled:1">
<option value="0">JNO</option>
<option value="1">JYES</option>
</field>
<field name="a11y_color_inversion" type="radio" default="1"
label="TPL_MOKO_A11Y_COLOR_INVERSION" description="TPL_MOKO_A11Y_COLOR_INVERSION_DESC"
layout="joomla.form.field.radio.switcher" filter="boolean"
showon="a11y_toolbar_enabled:1">
<option value="0">JNO</option>
<option value="1">JYES</option>
</field>
<field name="a11y_high_contrast" type="radio" default="1"
label="TPL_MOKO_A11Y_HIGH_CONTRAST" description="TPL_MOKO_A11Y_HIGH_CONTRAST_DESC"
layout="joomla.form.field.radio.switcher" filter="boolean"
showon="a11y_toolbar_enabled:1">
<option value="0">JNO</option>
<option value="1">JYES</option>
</field>
<field name="a11y_highlight_links" type="radio" default="1"
label="TPL_MOKO_A11Y_HIGHLIGHT_LINKS" description="TPL_MOKO_A11Y_HIGHLIGHT_LINKS_DESC"
layout="joomla.form.field.radio.switcher" filter="boolean"
showon="a11y_toolbar_enabled:1">
<option value="0">JNO</option>
<option value="1">JYES</option>
</field>
<field name="a11y_readable_font" type="radio" default="1"
label="TPL_MOKO_A11Y_READABLE_FONT" description="TPL_MOKO_A11Y_READABLE_FONT_DESC"
layout="joomla.form.field.radio.switcher" filter="boolean"
showon="a11y_toolbar_enabled:1">
<option value="0">JNO</option>
<option value="1">JYES</option>
</field>
<field name="a11y_pause_animations" type="radio" default="1"
label="TPL_MOKO_A11Y_PAUSE_ANIMATIONS" description="TPL_MOKO_A11Y_PAUSE_ANIMATIONS_DESC"
layout="joomla.form.field.radio.switcher" filter="boolean"
showon="a11y_toolbar_enabled:1">
<option value="0">JNO</option>
<option value="1">JYES</option>
</field>
<field name="a11y_toolbar_pos" type="list" default="tl"
label="TPL_MOKO_A11Y_TOOLBAR_POS" description="TPL_MOKO_A11Y_TOOLBAR_POS_DESC"
showon="a11y_toolbar_enabled:1">
<option value="br">Bottom-right</option>
<option value="bl">Bottom-left</option>
<option value="tr">Top-right</option>
<option value="tl">Top-left</option>
</field>
<!-- Toggle UI --> <!-- Toggle UI -->
<field name="theme_sep_toggle" type="spacer" label="Theme Toggle UI" hr="false" class="text fw-bold" /> <field name="theme_sep_toggle" type="spacer" label="Theme Toggle UI" hr="false" class="text fw-bold" />
<field name="theme_fab_enabled" type="radio" default="1" <field name="theme_fab_enabled" type="radio" default="1"