- 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.
275 lines
21 KiB
JavaScript
275 lines
21 KiB
JavaScript
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();
|
||
}); |