Files
interblue.pl/themes/InterBlue/assets/js/accessibility.js
Jacek Pyziak 602e6a92fe Add accessibility features: high contrast, blue filter, grayscale, image toggle, and cursor size adjustments
- Implemented high contrast mode toggle with state persistence.
- Added blue filter toggle with visual effect and state persistence.
- Introduced grayscale mode toggle with state persistence.
- Included option to hide/show images with state persistence.
- Added functionality to increase/decrease cursor size with visual feedback.
- Enhanced font size controls with increment, decrement, and reset options.
- Updated accessibility panel UI and button labels for clarity.
- Ensured all new features are accessible and maintain user preferences across sessions.
2025-09-03 22:27:22 +02:00

275 lines
21 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
document.addEventListener('DOMContentLoaded', function () {
// ─────────────────────────────────────────────────────────────────────────────
// 1) Przycisk otwierający panel
// ─────────────────────────────────────────────────────────────────────────────
const accessBtn = document.createElement('div');
accessBtn.id = 'accessibility-button';
accessBtn.setAttribute('aria-label', 'Opcje dostępności');
accessBtn.innerHTML = `
<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" width="24" height="24">
<circle cx="12" cy="12" r="10" stroke="#ffffff" stroke-width="1.5"></circle>
<path d="M14 7C14 8.10457 13.1046 9 12 9C10.8954 9 10 8.10457 10 7C10 5.89543 10.8954 5 12 5C13.1046 5 14 5.89543 14 7Z" stroke="#ffffff" stroke-width="1.5"></path>
<path d="M18 10C18 10 14.4627 11.5 12 11.5C9.53727 11.5 6 10 6 10" stroke="#ffffff" stroke-width="1.5" stroke-linecap="round"></path>
<path d="M12 12V13.4522M12 13.4522C12 14.0275 12.1654 14.5906 12.4765 15.0745L15 19M12 13.4522C12 14.0275 11.8346 14.5906 11.5235 15.0745L9 19" stroke="#ffffff" stroke-width="1.5" stroke-linecap="round"></path>
</svg>
`;
document.body.appendChild(accessBtn);
// ─────────────────────────────────────────────────────────────────────────────
// 2) Panel opcji dostępności
// ─────────────────────────────────────────────────────────────────────────────
const accessPanel = document.createElement('div');
accessPanel.id = 'accessibility-panel';
accessPanel.innerHTML = `
<div class="panel-header">
<span>Opcje dostępności</span>
<button id="close-accessibility-panel" aria-label="Zamknij">×</button>
</div>
<div class="panel-body">
<button id="toggle-contrast">Wysoki kontrast: OFF</button>
<button id="toggle-blue-filter">Filtr niebieski: OFF</button>
<button id="toggle-grayscale">Tryb szarości: OFF</button>
<button id="toggle-images">Ukryj obrazy</button>
<button id="toggle-cursor">Zwiększ kursor</button>
<div class="font-size-controls" role="group" aria-label="Rozmiar czcionki">
<button id="font-dec" aria-label="Zmniejsz czcionkę" title="Zmniejsz czcionkę">A</button>
<button id="font-reset" aria-label="Resetuj rozmiar czcionki" title="Resetuj rozmiar czcionki">A</button>
<button id="font-inc" aria-label="Zwiększ czcionkę" title="Zwiększ czcionkę">A+</button>
</div>
</div>
`;
document.body.appendChild(accessPanel);
// ─────────────────────────────────────────────────────────────────────────────
// 3) Referencje
// ─────────────────────────────────────────────────────────────────────────────
const panel = document.getElementById('accessibility-panel');
const contrastBtn = document.getElementById('toggle-contrast');
const blueFilterBtn = document.getElementById('toggle-blue-filter');
const grayscaleBtn = document.getElementById('toggle-grayscale');
const imagesBtn = document.getElementById('toggle-images');
const cursorBtn = document.getElementById('toggle-cursor');
const fontIncBtn = document.getElementById('font-inc');
const fontDecBtn = document.getElementById('font-dec');
const fontResetBtn = document.getElementById('font-reset');
// ─────────────────────────────────────────────────────────────────────────────
// 4) Panel open/close
// ─────────────────────────────────────────────────────────────────────────────
accessBtn.addEventListener('click', () => panel.classList.toggle('open'));
document.getElementById('close-accessibility-panel').addEventListener('click', () => panel.classList.remove('open'));
// ─────────────────────────────────────────────────────────────────────────────
// 5) Helpery etykiet
// ─────────────────────────────────────────────────────────────────────────────
function updateContrastButton() {
const isOn = document.body.classList.contains('high-contrast');
contrastBtn.textContent = isOn ? 'Wyłącz wysoki kontrast' : 'Włącz wysoki kontrast';
}
function updateBlueFilterButton() {
const isOn = !!document.getElementById('eyeAble-Bluefilter');
blueFilterBtn.textContent = isOn ? 'Wyłącz filtr niebieski' : 'Włącz filtr niebieski';
}
function updateGrayscaleButton() {
const isOn = document.documentElement.classList.contains('grayscale');
grayscaleBtn.textContent = isOn ? 'Wyłącz tryb szarości' : 'Włącz tryb szarości';
}
function updateImagesButton() {
const isOn = document.body.classList.contains('hide-images');
imagesBtn.textContent = isOn ? 'Pokaż obrazy' : 'Ukryj obrazy';
}
function updateCursorButton() {
const isOn = document.body.classList.contains('big-cursor');
cursorBtn.textContent = isOn ? 'Przywróć kursor' : 'Zwiększ kursor';
}
// ─────────────────────────────────────────────────────────────────────────────
// 6) Akcje istniejące
// ─────────────────────────────────────────────────────────────────────────────
contrastBtn.addEventListener('click', () => {
document.body.classList.toggle('high-contrast');
localStorage.setItem('highContrast', document.body.classList.contains('high-contrast') ? '1' : '0');
updateContrastButton();
});
blueFilterBtn.addEventListener('click', () => {
const shader = document.getElementById('eyeAble-Bluefilter');
if (shader) {
shader.remove();
localStorage.setItem('blueFilter', '0');
} else {
const s = document.createElement('eye-able-shader');
s.id = 'eyeAble-Bluefilter';
s.setAttribute('aria-hidden', 'true');
s.style.cssText = `
background: rgba(255, 147, 41, 0.43);
z-index: 2147483646;
margin: 0; border-radius: 0; padding: 0; pointer-events: none;
position: fixed; top: -10%; right: -10%; bottom: -10%; left: -10%;
width: auto; height: auto; mix-blend-mode: multiply; display: block !important;`;
document.body.appendChild(s);
localStorage.setItem('blueFilter', '1');
}
updateBlueFilterButton();
});
grayscaleBtn.addEventListener('click', () => {
document.documentElement.classList.toggle('grayscale');
localStorage.setItem('grayscaleMode', document.documentElement.classList.contains('grayscale') ? '1' : '0');
updateGrayscaleButton();
});
imagesBtn.addEventListener('click', () => {
document.body.classList.toggle('hide-images');
localStorage.setItem('hideImages', document.body.classList.contains('hide-images') ? '1' : '0');
updateImagesButton();
});
// ─────────────────────────────────────────────────────────────────────────────
// 7) DUŻY KURSOR overlay z INLINE SVG (CSP-safe), z aktywacją po pierwszym ruchu
// ─────────────────────────────────────────────────────────────────────────────
let overlay = null; // <div id="a11y-cursor">
let mmHandler = null;
let leaveHandler = null;
let enteredOnce = false;
const ARROW_SVG = `
<svg xmlns="http://www.w3.org/2000/svg" x="0px" y="0px" width="100" height="100" viewBox="0 0 80 80">
<path d="M 22 8.734375 L 22 64.328125 L 36.34375 54.273438 L 45.605469 72.628906 L 54.722656 67.921875 L 45.140625 50.1875 L 63.054688 46.230469 L 61.632813 44.933594 Z M 24 13.265625 L 58.859375 45.109375 L 42.125 48.800781 L 51.996094 67.078125 L 46.480469 69.925781 L 37.089844 51.308594 L 24 60.484375 Z M 28 24 C 27.449219 24 27 24.449219 27 25 C 27 25.550781 27.449219 26 28 26 C 28.550781 26 29 25.550781 29 25 C 29 24.449219 28.550781 24 28 24 Z M 28 28 C 27.449219 28 27 28.449219 27 29 C 27 29.550781 27.449219 30 28 30 C 28.550781 30 29 29.550781 29 29 C 29 28.449219 28.550781 28 28 28 Z M 28 32 C 27.449219 32 27 32.449219 27 33 C 27 33.550781 27.449219 34 28 34 C 28.550781 34 29 33.550781 29 33 C 29 32.449219 28.550781 32 28 32 Z M 28 36 C 27.449219 36 27 36.449219 27 37 C 27 37.550781 27.449219 38 28 38 C 28.550781 38 29 37.550781 29 37 C 29 36.449219 28.550781 36 28 36 Z M 28 40 C 27.449219 40 27 40.449219 27 41 C 27 41.550781 27.449219 42 28 42 C 28.550781 42 29 41.550781 29 41 C 29 40.449219 28.550781 40 28 40 Z M 28 44 C 27.449219 44 27 44.449219 27 45 C 27 45.550781 27.449219 46 28 46 C 28.550781 46 29 45.550781 29 45 C 29 44.449219 28.550781 44 28 44 Z M 28 48 C 27.449219 48 27 48.449219 27 49 C 27 49.550781 27.449219 50 28 50 C 28.550781 50 29 49.550781 29 49 C 29 48.449219 28.550781 48 28 48 Z M 28 52 C 27.449219 52 27 52.449219 27 53 C 27 53.550781 27.449219 54 28 54 C 28.550781 54 29 53.550781 29 53 C 29 52.449219 28.550781 52 28 52 Z"/>
</svg>`;
const HAND_SVG = `
<svg xmlns="http://www.w3.org/2000/svg" x="0px" y="0px" width="100" height="100" viewBox="0 0 80 80">
<path d="M 29.5 5 C 26.472656 5 24 7.472656 24 10.5 L 24 42.160156 L 19.976563 45.453125 C 16.941406 47.941406 16.125 52.261719 18.050781 55.683594 L 24.75 67.585938 L 24.699219 67.484375 C 26.0625 70.726563 29.273438 73 33 73 L 53 73 C 57.957031 73 62 68.957031 62 64 L 62 36.5 C 62 33.472656 59.527344 31 56.5 31 C 55.066406 31 53.777344 31.585938 52.796875 32.496094 C 52.304688 29.960938 50.167969 28 47.5 28 C 45.820313 28 44.371094 28.804688 43.359375 30 L 43.324219 30 C 42.402344 28.242188 40.613281 27 38.5 27 C 37.152344 27 35.957031 27.546875 35 28.355469 L 35 10.5 C 35 7.472656 32.527344 5 29.5 5 Z M 29.5 7 C 31.445313 7 33 8.554688 33 10.5 L 33 36 L 35 36 L 35 32.5 C 35 30.554688 36.554688 29 38.5 29 C 40.445313 29 42 30.554688 42 32.5 L 42 36 L 44 36 L 44 33.5 C 44 31.554688 45.554688 30 47.5 30 C 49.445313 30 51 31.554688 51 33.5 L 51 36.5 L 53 36.5 C 53 34.554688 54.554688 33 56.5 33 C 58.445313 33 60 34.554688 60 36.5 L 60 64 C 60 67.878906 56.878906 71 53 71 L 33 71 C 30.085938 71 27.601563 69.226563 26.546875 66.707031 L 26.519531 66.65625 L 19.792969 54.703125 C 18.339844 52.117188 18.949219 48.878906 21.246094 47 L 26 43.109375 L 26 10.5 C 26 8.554688 27.554688 7 29.5 7 Z M 34 41 C 33.449219 41 33 41.449219 33 42 C 33 42.550781 33.449219 43 34 43 C 34.550781 43 35 42.550781 35 42 C 35 41.449219 34.550781 41 34 41 Z M 43 41 C 42.449219 41 42 41.449219 42 42 C 42 42.550781 42.449219 43 43 43 C 43.550781 43 44 42.550781 44 42 C 44 41.449219 43.550781 41 43 41 Z M 52 41 C 51.449219 41 51 41.449219 51 42 C 51 42.550781 51.449219 43 52 43 C 52.550781 43 53 42.550781 53 42 C 53 41.449219 52.550781 41 52 41 Z M 34 45 C 33.449219 45 33 45.449219 33 46 C 33 46.550781 33.449219 47 34 47 C 34.550781 47 35 46.550781 35 46 C 35 45.449219 34.550781 45 34 45 Z M 43 45 C 42.449219 45 42 45.449219 42 46 C 42 46.550781 42.449219 47 43 47 C 43.550781 47 44 46.550781 44 46 C 44 45.449219 43.550781 45 43 45 Z M 52 45 C 51.449219 45 51 45.449219 51 46 C 51 46.550781 51.449219 47 52 47 C 52.550781 47 53 46.550781 53 46 C 53 45.449219 52.550781 45 52 45 Z M 34 49 C 33.449219 49 33 49.449219 33 50 C 33 50.550781 33.449219 51 34 51 C 34.550781 51 35 50.550781 35 50 C 35 49.449219 34.550781 49 34 49 Z M 43 49 C 42.449219 49 42 49.449219 42 50 C 42 50.550781 42.449219 51 43 51 C 43.550781 51 44 50.550781 44 50 C 44 49.449219 43.550781 49 43 49 Z M 52 49 C 51.449219 49 51 49.449219 51 50 C 51 50.550781 51.449219 51 52 51 C 52.550781 51 53 50.550781 53 50 C 53 49.449219 52.550781 49 52 49 Z M 34 53 C 33.449219 53 33 53.449219 33 54 C 33 54.550781 33.449219 55 34 55 C 34.550781 55 35 54.550781 35 54 C 35 53.449219 34.550781 53 34 53 Z M 43 53 C 42.449219 53 42 53.449219 42 54 C 42 54.550781 42.449219 55 43 55 C 43.550781 55 44 54.550781 44 54 C 44 53.449219 43.550781 53 43 53 Z M 52 53 C 51.449219 53 51 53.449219 51 54 C 51 54.550781 51.449219 55 52 55 C 52.550781 55 53 54.550781 53 54 C 53 53.449219 52.550781 53 52 53 Z"/>
</svg>`;
const CURSORS = {
arrow: { svg: ARROW_SVG, hotspotX: 6, hotspotY: 6 },
hand: { svg: HAND_SVG, hotspotX: 26, hotspotY: 12 },
};
let current = 'arrow';
function ensureOverlay() {
if (overlay) return;
overlay = document.createElement('div');
overlay.id = 'a11y-cursor';
overlay.setAttribute('aria-hidden', 'true');
overlay.style.cssText = [
'position:fixed','left:0','top:0','width:48px','height:48px',
'pointer-events:none','z-index:2147483647','transform:translate3d(-9999px,-9999px,0)',
'opacity:1','display:block'
].join(';');
document.body.appendChild(overlay);
}
function setOverlay(name){ current = name; if (!overlay) return; overlay.innerHTML = CURSORS[name].svg; }
function moveOverlay(x,y){ if(!overlay) return; const c = CURSORS[current]; overlay.style.transform = `translate3d(${x-c.hotspotX}px, ${y-c.hotspotY}px, 0)`; }
function isInteractive(el){ return el && el.closest && el.closest('a[href],button,[role="button"],input[type="button"],input[type="submit"],input[type="checkbox"],input[type="radio"],select,.clickable,[onclick]'); }
function isTextInput(el){ return el && el.closest && el.closest('input[type="text"],input[type="search"],input[type="email"],input[type="url"],input[type="tel"],input[type="password"],textarea,[contenteditable=""],[contenteditable="true"]'); }
function enableBigCursor(){
ensureOverlay();
setOverlay('arrow');
document.body.classList.add('big-cursor');
document.body.classList.remove('big-cursor-active');
enteredOnce = false;
mmHandler = (e)=>{
const t = e.target;
if (!enteredOnce) { enteredOnce = true; document.body.classList.add('big-cursor-active'); }
if (isTextInput(t)) { overlay.style.opacity = '0'; return; } else { overlay.style.opacity = '1'; }
setOverlay(isInteractive(t) ? 'hand' : 'arrow');
moveOverlay(e.clientX, e.clientY);
};
leaveHandler = ()=>{ if(overlay) overlay.style.opacity='0'; };
document.addEventListener('mousemove', mmHandler);
document.addEventListener('mouseleave', leaveHandler);
}
function disableBigCursor(){
document.body.classList.remove('big-cursor','big-cursor-active');
if (mmHandler) document.removeEventListener('mousemove', mmHandler);
if (leaveHandler) document.removeEventListener('mouseleave', leaveHandler);
mmHandler = leaveHandler = null;
if (overlay) { overlay.remove(); overlay = null; }
}
cursorBtn.addEventListener('click', () => {
const willEnable = !document.body.classList.contains('big-cursor');
localStorage.setItem('bigCursor', willEnable ? '1' : '0');
if (willEnable) enableBigCursor(); else disableBigCursor();
updateCursorButton();
});
// ─────────────────────────────────────────────────────────────────────────────
// 8) KONTROLKI ROZMIARU CZCIONKI działają także dla px w CSS
// ─────────────────────────────────────────────────────────────────────────────
const FONT_STEP = 0.1; // 10%
const FONT_MIN = 0.7; // 70%
const FONT_MAX = 2.0; // 200%
const TEXT_SELECTOR = 'h1,h2,h3,h4,h5,h6,p,span,li,dt,dd,blockquote,figcaption,small,strong,em,mark,code,pre,a,button,input,select,textarea,label,td,th,caption,legend';
function clamp(n, min, max){ return Math.min(Math.max(n, min), max); }
function getScale(){ const v = parseFloat(localStorage.getItem('a11yFontScale') || '1'); return isNaN(v) ? 1 : clamp(v, FONT_MIN, FONT_MAX); }
function updateFontButtons(){
const s = getScale();
fontDecBtn.disabled = s <= FONT_MIN;
fontIncBtn.disabled = s >= FONT_MAX;
fontResetBtn.textContent = s === 1 ? 'A' : `A (${Math.round(s*100)}%)`;
}
function applyFontScale(scale){
const nodes = document.querySelectorAll(TEXT_SELECTOR);
nodes.forEach(el => {
if (el.closest('#accessibility-panel, #accessibility-button')) return; // nie skaluj UI panelu
if (el.hasAttribute('data-no-scale')) return; // wykluczenia opcjonalne
let base = el.getAttribute('data-font-base');
if (!base) {
const comp = getComputedStyle(el).fontSize;
const px = parseFloat(comp);
if (!isNaN(px) && px > 0) {
el.setAttribute('data-font-base', String(px));
base = String(px);
}
}
if (base) {
const b = parseFloat(base);
if (scale === 1) {
el.style.removeProperty('font-size');
} else {
const newPx = Math.round(b * scale * 100) / 100;
el.style.setProperty('font-size', newPx + 'px', 'important');
}
}
});
}
function setScale(s){ s = clamp(s, FONT_MIN, FONT_MAX); localStorage.setItem('a11yFontScale', String(s)); applyFontScale(s); updateFontButtons(); }
fontIncBtn.addEventListener('click', () => setScale(getScale() + FONT_STEP));
fontDecBtn.addEventListener('click', () => setScale(getScale() - FONT_STEP));
fontResetBtn.addEventListener('click', () => setScale(1));
// ─────────────────────────────────────────────────────────────────────────────
// 9) Przywracanie stanu
// ─────────────────────────────────────────────────────────────────────────────
if (localStorage.getItem('highContrast') === '1') document.body.classList.add('high-contrast');
if (localStorage.getItem('blueFilter') === '1') {
const s = document.createElement('eye-able-shader');
s.id = 'eyeAble-Bluefilter'; s.setAttribute('aria-hidden','true');
s.style.cssText = 'background:rgba(255,147,41,0.43);z-index:2147483646;margin:0;border-radius:0;padding:0;pointer-events:none;position:fixed;top:-10%;right:-10%;bottom:-10%;left:-10%;width:auto;height:auto;mix-blend-mode:multiply;display:block!important;';
document.body.appendChild(s);
}
if (localStorage.getItem('grayscaleMode') === '1') document.documentElement.classList.add('grayscale');
if (localStorage.getItem('hideImages') === '1') document.body.classList.add('hide-images');
if (localStorage.getItem('bigCursor') === '1') { enableBigCursor(); }
// Zastosuj i zainicjuj przyciski rozmiaru czcionki
setScale(getScale());
// ─────────────────────────────────────────────────────────────────────────────
// 10) Inicjalizacja etykiet
// ─────────────────────────────────────────────────────────────────────────────
updateContrastButton();
updateBlueFilterButton();
updateGrayscaleButton();
updateImagesButton();
updateCursorButton();
updateFontButtons();
});