Warning: Accessibilty Features and .jsI on this site is currently deactivated, as JavaScript is disabled.

jShell: WCAG

(function () { "use strict"; const STORAGE_KEY = "a11y-widget-settings"; const FOCUSABLE = 'a[href], button:not([disabled]), textarea, input, select, [tabindex]:not([tabindex="-1"])'; const defaultSettings = { fontScale: 1, lineSpacing: false, highContrast: false, invertColors: false, grayscale: false, underlineLinks: false, dyslexiaFont: false, reduceMotion: false, bigCursor: false, highlightFocus: false, }; let settings = loadSettings(); let lastFocusedBeforeOpen = null; function loadSettings() { try { const raw = localStorage.getItem(STORAGE_KEY); return raw ? { ...defaultSettings, ...JSON.parse(raw) } : { ...defaultSettings }; } catch (e) { return { ...defaultSettings }; } } function saveSettings() { try { localStorage.setItem(STORAGE_KEY, JSON.stringify(settings)); } catch (e) {} } function announce(msg) { const live = document.getElementById("a11y-live-region"); if (live) { live.textContent = ""; requestAnimationFrame(() => { live.textContent = msg; }); } } function injectStyles() { const style = document.createElement("style"); style.id = "a11y-widget-styles"; style.textContent = ` #a11y-skip-link { position: absolute; left: -9999px; top: 0; z-index: 1000000; background: #000; color: #fff; padding: 0.75rem 1rem; font-family: system-ui, sans-serif; text-decoration: none; border-radius: 0 0 6px 0; } #a11y-skip-link:focus { left: 0; } #a11y-live-region { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; } #a11y-header { position: fixed !important; top: 0.5rem !important; right: 0.5rem !important; left: auto !important; width: auto !important; z-index: 999997 !important; display: block !important; margin: 0 !important; padding: 0 !important; background: transparent !important; } #a11y-toggle-btn { display: inline-flex !important; width: auto !important; max-width: max-content !important; align-items: center !important; gap: 0.3rem !important; background: #0b5fcc !important; color: #fff !important; border: 1px solid #66aaff !important; border-radius: 6px !important; padding: 0.25rem 0.55rem !important; margin: 0 !important; cursor: pointer !important; font-family: system-ui, sans-serif !important; font-size: 12px !important; line-height: 1.4 !important; box-shadow: 0 1px 4px rgba(0,0,0,0.4) !important; } #a11y-toggle-btn:hover, #a11y-toggle-btn:focus-visible { background: #0d70eb; outline: 3px solid #ffb400; outline-offset: 2px; } #a11y-overlay { position: fixed; inset: 0; z-index: 999998; background: rgba(0, 0, 0, 0.6); display: flex; align-items: center; justify-content: center; padding: 1rem; overflow-y: auto; } #a11y-overlay[hidden] { display: none; } #a11y-dialog { width: min(96vw, 900px); max-height: 90vh; overflow-y: auto; background: #1a1a1a; color: #fff; border: 1px solid #555; border-radius: 12px; padding: 1.5rem; font-family: system-ui, sans-serif; box-shadow: 0 8px 30px rgba(0,0,0,0.6); } #a11y-dialog-header { display: flex; align-items: center; justify-content: space-between; margin-bottom: 1rem; } #a11y-dialog-header h2 { font-size: 18px; margin: 0; } #a11y-close-btn { background: #2e2e2e; color: #fff; border: 1px solid #555; border-radius: 6px; width: 32px; height: 32px; cursor: pointer; font-size: 16px; } #a11y-close-btn:hover, #a11y-close-btn:focus-visible { background: #4a4a4a; outline: 3px solid #ffb400; outline-offset: 2px; } #a11y-grid { display: grid; grid-template-columns: repeat(2, 1fr); gap: 0.6rem; } #a11y-grid button { width: 100%; background: #2e2e2e; color: #fff; border: 1px solid #555; border-radius: 8px; padding: 0.6rem 0.5rem; cursor: pointer; font-size: 13px; text-align: center; } #a11y-grid button:hover, #a11y-grid button:focus-visible { background: #4a4a4a; outline: 3px solid #ffb400; outline-offset: 2px; } #a11y-grid button[aria-pressed="true"] { background: #0b5fcc; border-color: #66aaff; } #a11y-fontctrl { display: flex; align-items: center; justify-content: space-between; background: #2e2e2e; border: 1px solid #555; border-radius: 8px; padding: 0.4rem 0.6rem; margin-bottom: 0.6rem; } #a11y-fontctrl span { font-size: 13px; } #a11y-fontctrl .a11y-fontbtns { display: flex; gap: 0.4rem; } #a11y-fontctrl button { background: #1a1a1a; color: #fff; border: 1px solid #555; border-radius: 6px; padding: 0.3rem 0.6rem; cursor: pointer; } #a11y-fontctrl button:hover, #a11y-fontctrl button:focus-visible { background: #4a4a4a; outline: 3px solid #ffb400; outline-offset: 2px; } #a11y-reset-btn { width: 100%; margin-top: 1rem; background: #5a1a1a; color: #fff; border: 1px solid #ff5555; border-radius: 8px; padding: 0.6rem; cursor: pointer; font-size: 13px; } #a11y-reset-btn:hover, #a11y-reset-btn:focus-visible { background: #732020; outline: 3px solid #ffb400; outline-offset: 2px; } html.a11y-underline-links a { text-decoration: underline !important; } html.a11y-line-spacing body, html.a11y-line-spacing body * { line-height: 1.8 !important; letter-spacing: 0.02em !important; word-spacing: 0.05em !important; } html.a11y-high-contrast body { background: #000 !important; color: #fff !important; } html.a11y-high-contrast a { color: #ffe86b !important; } html.a11y-invert-colors { filter: invert(1) hue-rotate(180deg); } html.a11y-invert-colors img, html.a11y-invert-colors video { filter: invert(1) hue-rotate(180deg); } html.a11y-grayscale { filter: grayscale(1); } html.a11y-invert-colors.a11y-grayscale { filter: invert(1) hue-rotate(180deg) grayscale(1); } html.a11y-dyslexia-font body, html.a11y-dyslexia-font body * { font-family: Verdana, Tahoma, sans-serif !important; } html.a11y-reduce-motion *, html.a11y-reduce-motion *::before, html.a11y-reduce-motion *::after { animation-duration: 0.001ms !important; animation-iteration-count: 1 !important; transition-duration: 0.001ms !important; scroll-behavior: auto !important; } html.a11y-big-cursor, html.a11y-big-cursor * { cursor: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 24 24"><path d="M3 2l18 9-8 2-3 8-7-19z" fill="black" stroke="white" stroke-width="1"/></svg>') 0 0, auto !important; } html.a11y-highlight-focus *:focus-visible { outline: 4px solid #ffb400 !important; outline-offset: 3px !important; } `; document.head.appendChild(style); } function buildSkipLink() { let target = document.querySelector("main, [role='main'], #main, #content"); if (!target) return; if (!target.id) target.id = "a11y-main-content"; if (!target.hasAttribute("tabindex")) target.setAttribute("tabindex", "-1"); const link = document.createElement("a"); link.id = "a11y-skip-link"; link.href = "#" + target.id; link.textContent = "Skip to main content"; link.addEventListener("click", () => { target.focus(); }); document.body.insertBefore(link, document.body.firstChild); } function buildLiveRegion() { const live = document.createElement("div"); live.id = "a11y-live-region"; live.setAttribute("aria-live", "polite"); live.setAttribute("role", "status"); document.body.appendChild(live); } function buildHeader() { const header = document.createElement("header"); header.id = "a11y-header"; const btn = document.createElement("button"); btn.id = "a11y-toggle-btn"; btn.type = "button"; btn.setAttribute("aria-haspopup", "dialog"); btn.setAttribute("aria-expanded", "false"); btn.setAttribute("aria-controls", "a11y-overlay"); btn.innerHTML = "&#9855; Accessibility"; header.appendChild(btn); document.body.insertBefore(header, document.body.firstChild); return btn; } function buildOverlay() { const overlay = document.createElement("div"); overlay.id = "a11y-overlay"; overlay.hidden = true; overlay.innerHTML = ` <div id="a11y-dialog" role="dialog" aria-modal="true" aria-labelledby="a11y-dialog-title"> <div id="a11y-dialog-header"> <h2 id="a11y-dialog-title">Accessibility Settings</h2> <button type="button" id="a11y-close-btn" aria-label="Close accessibility settings">&times;</button> </div> <div id="a11y-fontctrl"> <span>Font Size</span> <div class="a11y-fontbtns"> <button type="button" data-action="font-dec" aria-label="Decrease font size">A−</button> <button type="button" data-action="font-inc" aria-label="Increase font size">A+</button> </div> </div> <div id="a11y-grid"> <button type="button" data-toggle="lineSpacing" aria-pressed="false">Line Spacing</button> <button type="button" data-toggle="highContrast" aria-pressed="false">High Contrast</button> <button type="button" data-toggle="invertColors" aria-pressed="false">Invert Colors</button> <button type="button" data-toggle="grayscale" aria-pressed="false">Grayscale</button> <button type="button" data-toggle="underlineLinks" aria-pressed="false">Underline Links</button> <button type="button" data-toggle="dyslexiaFont" aria-pressed="false">Readable Font</button> <button type="button" data-toggle="reduceMotion" aria-pressed="false">Reduce Motion</button> <button type="button" data-toggle="highlightFocus" aria-pressed="false">Highlight Focus</button> <button type="button" data-toggle="bigCursor" aria-pressed="false">Big Cursor</button> </div> <button type="button" id="a11y-reset-btn">Reset All Settings</button> <br><br> <p>Im always trying my best to pursue WCAG. If anything misses, contact <a href="https://docs.jbreda.dev/contact.php">me</p> </div> `; document.body.appendChild(overlay); return overlay; } function wireEvents(toggleBtn, overlay) { const dialog = overlay.querySelector("#a11y-dialog"); const closeBtn = overlay.querySelector("#a11y-close-btn"); toggleBtn.addEventListener("click", () => openOverlay(toggleBtn, overlay)); closeBtn.addEventListener("click", () => closeOverlay(toggleBtn, overlay)); overlay.addEventListener("click", (e) => { if (e.target === overlay) closeOverlay(toggleBtn, overlay); }); overlay.addEventListener("click", (e) => { const t = e.target.closest("button"); if (!t) return; const toggleKey = t.getAttribute("data-toggle"); const action = t.getAttribute("data-action"); const id = t.id; if (toggleKey) { settings[toggleKey] = !settings[toggleKey]; applySettings(); announce(t.textContent.trim() + (settings[toggleKey] ? " enabled" : " disabled")); return; } if (action === "font-inc") { settings.fontScale = Math.min(2, +(settings.fontScale + 0.1).toFixed(2)); applySettings(); announce("Font size " + Math.round(settings.fontScale * 100) + " percent"); } else if (action === "font-dec") { settings.fontScale = Math.max(0.7, +(settings.fontScale - 0.1).toFixed(2)); applySettings(); announce("Font size " + Math.round(settings.fontScale * 100) + " percent"); } else if (id === "a11y-reset-btn") { settings = { ...defaultSettings }; applySettings(); announce("All settings reset"); } }); dialog.addEventListener("keydown", (e) => { if (e.key === "Escape") { closeOverlay(toggleBtn, overlay); return; } if (e.key === "Tab") { const focusables = Array.from(dialog.querySelectorAll(FOCUSABLE)); if (focusables.length === 0) return; const first = focusables[0]; const last = focusables[focusables.length - 1]; if (e.shiftKey && document.activeElement === first) { e.preventDefault(); last.focus(); } else if (!e.shiftKey && document.activeElement === last) { e.preventDefault(); first.focus(); } } }); } function openOverlay(toggleBtn, overlay) { lastFocusedBeforeOpen = document.activeElement; overlay.hidden = false; toggleBtn.setAttribute("aria-expanded", "true"); const dialog = overlay.querySelector("#a11y-dialog"); const firstFocusable = dialog.querySelector(FOCUSABLE); if (firstFocusable) firstFocusable.focus(); } function closeOverlay(toggleBtn, overlay) { overlay.hidden = true; toggleBtn.setAttribute("aria-expanded", "false"); if (lastFocusedBeforeOpen) lastFocusedBeforeOpen.focus(); } function applySettings() { const html = document.documentElement; html.style.fontSize = (settings.fontScale * 100) + "%"; html.classList.toggle("a11y-line-spacing", settings.lineSpacing); html.classList.toggle("a11y-high-contrast", settings.highContrast); html.classList.toggle("a11y-invert-colors", settings.invertColors); html.classList.toggle("a11y-grayscale", settings.grayscale); html.classList.toggle("a11y-underline-links", settings.underlineLinks); html.classList.toggle("a11y-dyslexia-font", settings.dyslexiaFont); html.classList.toggle("a11y-reduce-motion", settings.reduceMotion); html.classList.toggle("a11y-big-cursor", settings.bigCursor); html.classList.toggle("a11y-highlight-focus", settings.highlightFocus); saveSettings(); syncButtonStates(); } function syncButtonStates() { document.querySelectorAll("#a11y-overlay [data-toggle]").forEach((btn) => { const key = btn.getAttribute("data-toggle"); btn.setAttribute("aria-pressed", String(!!settings[key])); }); } function init() { injectStyles(); buildSkipLink(); buildLiveRegion(); const toggleBtn = buildHeader(); const overlay = buildOverlay(); wireEvents(toggleBtn, overlay); applySettings(); } if (document.readyState === "loading") { document.addEventListener("DOMContentLoaded", init); } else { init(); } })();