MediaWiki:Gadget-bhc-accessibility.css: Difference between revisions

No edit summary
No edit summary
Line 3: Line 3:
*/
*/


(() => {
  "use strict";


  // -------------------------------------------------------------------
/* =========================================================
  // Cookie helpers
  BHC Accessibility Gadget – MediaWiki / Timeless
  // -------------------------------------------------------------------
  ========================================================= */
  const COOKIE_PREFIX = "a11y_";


  function setCookie(name, value, days = 365) {
/* ---------------------------------------------------------
    const exp = new Date();
  Variables
    exp.setTime(exp.getTime() + days * 24 * 60 * 60 * 1000);
  --------------------------------------------------------- */
    document.cookie =
:root{
      `${encodeURIComponent(COOKIE_PREFIX + name)}=${encodeURIComponent(String(value))}; ` +
  --a11y-bg: #111827;
      `expires=${exp.toUTCString()}; path=/; SameSite=Lax`;
  --a11y-border: #374151;
  }
  --a11y-fg: #ffffff;
  --a11y-focus: #FFBF47;
  --a11y-link-underline: #36C; /* MediaWiki link blue */
  --a11y-menu-width: 340px;
}


   function getCookie(name, fallback = "") {
/* Timeless: stop skin CSS shrinking the accessibility icon */
    const key = encodeURIComponent(COOKIE_PREFIX + name) + "=";
body.skin-timeless .a11y-toggle{
    const parts = document.cookie.split(";").map(s => s.trim());
   font-size: 16px !important;
    for (const p of parts) {
  line-height: 1 !important;
      if (p.startsWith(key)) return decodeURIComponent(p.substring(key.length));
}
    }
body.skin-timeless .a11y-toggle svg{
    return fallback;
  display: block !important;
   }
  width: 36px !important;
  height: 36px !important;
  min-width: 36px !important;
  min-height: 36px !important;
  max-width: none !important;
   max-height: none !important;


   function delCookie(name) {
   /* If the glyph still looks small, keep this: */
    document.cookie =
  transform: scale(1.15) !important;
      `${encodeURIComponent(COOKIE_PREFIX + name)}=; ` +
  transform-origin: 50% 50% !important;
      `expires=Thu, 01 Jan 1970 00:00:00 GMT; path=/; SameSite=Lax`;
}
  }
/* Make sure fill isn’t being altered by skin rules */
body.skin-timeless .a11y-toggle svg path{
  fill: currentColor !important;
}


  // -------------------------------------------------------------------
/* ---------------------------------------------------------
  // State + announcements
  Skip links (appear when focused)
  // -------------------------------------------------------------------
  --------------------------------------------------------- */
   const STATE = {
.skip-link{
    contrast: false,
   position: absolute;
    underline: false,
  left: 12px;
    spacing: "off",          // off | plus | minus
  top: 12px;
    cursor: "off",          // off | white | yellow
  z-index: 11000;
    ruler: false,
    fontPct: 100            // 90..140 in steps of 10
  };


   let liveEl = null;
   padding: 10px 14px;
   let rulerEl = null;
   border-radius: 8px;
  let rulerY = 0;


   function announce(msg) {
   background: #111827;
    if (!liveEl) return;
  color: #fff;
    liveEl.textContent = "";
  text-decoration: none;
    setTimeout(() => { liveEl.textContent = msg; }, 10);
  }


   function clamp(n, min, max) { return Math.max(min, Math.min(max, n)); }
   transform: translateY(-150%);
  transition: transform 0.15s ease;
  border: 2px solid var(--a11y-focus);
}
.skip-link:focus,
.skip-link:focus-visible{
  transform: translateY(0);
  outline: none;
}
body.a11y-contrast .skip-link{
  background: #000 !important;
  color: #FFD400 !important;
  border-color: #FFD400 !important;
}


  // -------------------------------------------------------------------
/* ---------------------------------------------------------
  // Apply state to DOM
  Floating accessibility toggle
  // -------------------------------------------------------------------
  --------------------------------------------------------- */
   function applyState() {
.a11y-toggle{
    document.body.classList.toggle("a11y-contrast", !!STATE.contrast);
  position: fixed;
    document.body.classList.toggle("a11y-underline-links", !!STATE.underline);
   right: 16px;
  bottom: 16px;
  z-index: 10001;


    document.body.classList.toggle("a11y-spacing-plus", STATE.spacing === "plus");
  width: 56px;
    document.body.classList.toggle("a11y-spacing-minus", STATE.spacing === "minus");
  height: 56px;
  border-radius: 50%;


    document.body.classList.toggle("a11y-cursor-white", STATE.cursor === "white");
  background: #005FCC;
    document.body.classList.toggle("a11y-cursor-yellow", STATE.cursor === "yellow");
  border: 2px solid #ffffff;
  color: #ffffff;


    document.documentElement.style.fontSize = `${STATE.fontPct}%`;
  display: inline-flex;
  align-items: center;
  justify-content: center;


    ensureRuler();
  box-shadow: 0 10px 24px rgba(0,0,0,0.25);
    if (STATE.ruler) {
  cursor: pointer;
      rulerEl.hidden = false;
}
      setRulerY(rulerY || Math.floor(window.innerHeight / 2));
.a11y-toggle:hover{ background:#0047A6; }
      enableRulerListeners();
.a11y-toggle:focus-visible{
    } else {
  outline: 3px solid var(--a11y-focus);
      if (rulerEl) rulerEl.hidden = true;
  outline-offset: 3px;
      disableRulerListeners();
}
    }
.a11y-toggle svg{
  width: 44px;
  height: 44px;
  fill: currentColor;
}


    updateUiButtons();
/* ---------------------------------------------------------
   }
  Panel
  --------------------------------------------------------- */
.a11y-panel{
  position: fixed;
  right: 14px;
  bottom: 76px;
   z-index: 10000;


   // -------------------------------------------------------------------
   width: var(--a11y-menu-width);
  // Ruler support
  max-width: calc(100vw - 28px);
  // -------------------------------------------------------------------
  function ensureRuler() {
    if (rulerEl) return;
    rulerEl = document.getElementById("a11y-ruler");
    if (!rulerEl) {
      rulerEl = document.createElement("div");
      rulerEl.id = "a11y-ruler";
      rulerEl.hidden = true;
      document.body.appendChild(rulerEl);
    }
  }


   function setRulerY(y) {
   background: var(--a11y-bg);
    rulerY = clamp(y, 0, Math.max(0, window.innerHeight - 1));
  border: 1px solid var(--a11y-border);
    if (rulerEl) rulerEl.style.transform = `translateY(${rulerY}px)`;
  color: var(--a11y-fg);
   }
   border-radius: 16px;
  padding: 12px;


   function onMouseMove(e) {
   box-shadow: 0 14px 40px rgba(0,0,0,0.28);
    if (!STATE.ruler) return;
}
    setRulerY(e.clientY - 11);
.a11y-panel[hidden]{ display:none !important; }
  }


   function onKeyDown(e) {
/* Header */
    if (!STATE.ruler) return;
.a11y-head{
  display:flex;
  align-items:center;
   justify-content: space-between;
  margin-bottom: 10px;
}
.a11y-head h2{
  font-size: 16px;
  margin: 0;
}
.a11y-help{
  width: 28px;
  height: 28px;
  border-radius: 50%;
  background: #fff;
  color: #111827;
  font-weight: 900;
  text-decoration: none;
  display:flex;
  align-items:center;
  justify-content:center;
}
.a11y-help:focus-visible{
  outline: 3px solid var(--a11y-focus);
  outline-offset: 2px;
}


    if (e.key === "ArrowDown") {
/* Buttons */
      e.preventDefault();
.a11y-row{
      setRulerY(rulerY + 10);
  display:flex;
      announce("Reading ruler moved down");
  gap: 8px;
    } else if (e.key === "ArrowUp") {
  flex-wrap: wrap;
      e.preventDefault();
  margin-bottom: 10px;
      setRulerY(rulerY - 10);
}
      announce("Reading ruler moved up");
.a11y-btn{
    } else if (e.key === "Escape") {
  border: 1px solid #D1D5DB;
      e.preventDefault();
  background:#fff;
      STATE.ruler = false;
  color:#111827;
      setCookie("ruler", "0");
  padding: 8px 10px;
      applyState();
  border-radius: 999px;
      announce("Reading ruler off");
  font-size: 14px;
    }
  font-weight: 800;
  }
  cursor: pointer;
}
.a11y-btn[aria-pressed="true"]{
  background:#111827;
  color:#fff;
}
.a11y-btn:focus-visible{
  outline: 3px solid var(--a11y-focus);
  outline-offset: 2px;
}


  let rulerListenersOn = false;
.a11y-hint{
  function enableRulerListeners() {
  font-size: 13px;
    if (rulerListenersOn) return;
   color: var(--a11y-fg);
    rulerListenersOn = true;
  line-height: 1.4;
    window.addEventListener("mousemove", onMouseMove, { passive: true });
}
    window.addEventListener("keydown", onKeyDown);
    window.addEventListener("resize", () => setRulerY(rulerY), { passive: true });
   }
  function disableRulerListeners() {
    if (!rulerListenersOn) return;
    rulerListenersOn = false;
    window.removeEventListener("mousemove", onMouseMove);
    window.removeEventListener("keydown", onKeyDown);
  }


  // -------------------------------------------------------------------
/* Screen reader only */
   // UI creation
.a11y-sr{
   // -------------------------------------------------------------------
  position:absolute;
   function ensureUi() {
   width:1px;
    if (document.getElementById("a11y-toggle")) return;
   height:1px;
   overflow:hidden;
  clip:rect(0,0,0,0);
}


    // Help link goes to the wiki page (you said: index.php/Accessibility)
/* =========================================================
    const helpHref = (window.mw && mw.util && mw.util.getUrl)
  Feature classes
      ? mw.util.getUrl("Accessibility")
  ========================================================= */
      : "/index.php/Accessibility";


    const toggle = document.createElement("button");
/* ------------------------------
    toggle.type = "button";
  High contrast (black/yellow/blue)
    toggle.id = "a11y-toggle";
  ------------------------------ */
    toggle.className = "a11y-toggle";
body.a11y-contrast{
    toggle.setAttribute("aria-label", "Accessibility options");
  background:#000 !important;
    toggle.setAttribute("aria-haspopup", "dialog");
  color:#FFD400 !important;
    toggle.setAttribute("aria-expanded", "false");


    toggle.innerHTML = `
  --a11y-bg: #000;
      <svg viewBox="0 0 24 24" aria-hidden="true" focusable="false">
  --a11y-border: rgba(255,212,0,.7);
        <path d="M12 2a2 2 0 1 0 .001 4.001A2 2 0 0 0 12 2zm9 7h-6.5c-.7 0-1.3.4-1.6 1L12 12l-.9-2c-.3-.6-.9-1-1.6-1H3a1 1 0 0 0 0 2h5.3l1.3 2.9-1.7 6.2a1 1 0 0 0 1.9.6l1.2-4.3 1.2 4.3a1 1 0 0 0 1.9-.6l-1.7-6.2 1.3-2.9H21a1 1 0 0 0 0-2z"/>
  --a11y-fg: #FFD400;
      </svg>
  --a11y-focus: #FFD400;
    `;
  --a11y-link-underline: #36C;
    document.body.appendChild(toggle);
}


    const panel = document.createElement("div");
body.a11y-contrast a,
    panel.id = "a11y-panel";
body.a11y-contrast a:visited{
    panel.className = "a11y-panel";
  color:#FFD400 !important;
    panel.setAttribute("role", "dialog");
}
    panel.setAttribute("aria-label", "Accessibility options");
    panel.hidden = true;


    panel.innerHTML = `
/* Headings */
      <div class="a11y-head">
body.a11y-contrast h1,
        <h2>Accessibility</h2>
body.a11y-contrast h2,
        <a class="a11y-help" href="${helpHref}" aria-label="Accessibility help">?</a>
body.a11y-contrast h3,
      </div>
body.a11y-contrast h4,
body.a11y-contrast h5,
body.a11y-contrast h6{
  color:#FFD400 !important;
  border-color:#FFD400 !important;
}


      <div class="a11y-row" role="group" aria-label="Text size">
/* Main containers (Timeless needs explicit coverage) */
        <button type="button" class="a11y-btn" data-action="font-minus" aria-pressed="false" aria-label="Decrease text size">A−</button>
body.a11y-contrast #mw-wrapper,
        <button type="button" class="a11y-btn" data-action="font-plus" aria-pressed="false" aria-label="Increase text size">A+</button>
body.a11y-contrast #mw-content-container,
        <button type="button" class="a11y-btn" data-action="font-reset" aria-pressed="false" aria-label="Reset text size">Reset</button>
body.a11y-contrast #mw-content,
      </div>
body.a11y-contrast .mw-body,
body.a11y-contrast .mw-parser-output{
  background:#000 !important;
  color:#FFD400 !important;
}


      <div class="a11y-row" role="group" aria-label="Contrast and links">
/* Sidebars / tools */
        <button type="button" class="a11y-btn" data-action="contrast" aria-pressed="false">High contrast</button>
body.a11y-contrast #site-navigation,
        <button type="button" class="a11y-btn" data-action="underline" aria-pressed="false">Underline links</button>
body.a11y-contrast #site-tools,
      </div>
body.a11y-contrast #page-tools,
body.a11y-contrast .mw-portlet,
body.a11y-contrast .mw-portlet-body{
  background:#000 !important;
  color:#FFD400 !important;
  border-color: rgba(255,212,0,.7) !important;
}


      <div class="a11y-row" role="group" aria-label="Text spacing">
/* Ensure links inside toolboxes */
        <button type="button" class="a11y-btn" data-action="spacing-minus" aria-pressed="false">Less spacing</button>
body.a11y-contrast #site-navigation a,
        <button type="button" class="a11y-btn" data-action="spacing-plus" aria-pressed="false">More spacing</button>
body.a11y-contrast #site-tools a,
        <button type="button" class="a11y-btn" data-action="spacing-reset" aria-pressed="false">Spacing reset</button>
body.a11y-contrast #page-tools a{
      </div>
  color:#FFD400 !important;
}


      <div class="a11y-row" role="group" aria-label="Cursor and reading aid">
/* A11y UI in contrast */
        <button type="button" class="a11y-btn" data-action="cursor" aria-pressed="false" aria-label="Large cursor setting">Large cursor: Off</button>
body.a11y-contrast .a11y-panel,
        <button type="button" class="a11y-btn" data-action="ruler" aria-pressed="false" aria-label="Toggle reading ruler">Reading ruler</button>
body.a11y-contrast .a11y-toggle{
      </div>
  background:#000 !important;
}
body.a11y-contrast .a11y-btn{
  background:#000 !important;
  color:#FFD400 !important;
  border:2px solid rgba(255,212,0,.85) !important;
}


      <div class="a11y-row" role="group" aria-label="Reset all">
/* ------------------------------
        <button type="button" class="a11y-btn" data-action="reset" aria-pressed="false">Reset all</button>
  Underline links
      </div>
  ------------------------------ */
/* Underline links – MediaWiki-style */
body.a11y-underline-links a{
  text-decoration: none !important;
  border-bottom: 2px solid var(--a11y-link-underline);
  padding-bottom: 0.05em;
}
/* In contrast mode you currently want blue (#36c) */
body.a11y-underline-links.a11y-contrast a{
  border-bottom-color: #36C !important;
}


      <div class="a11y-hint">
/* ------------------------------
        Settings are saved on this device. Large cursor cycles <strong>Off → White → Yellow → Off</strong>.
  Text spacing
        When Reading ruler is on, use <strong>↑/↓</strong> to move it and <strong>Esc</strong> to turn it off.
  ------------------------------ */
      </div>
body.a11y-spacing-plus{
  line-height: 1.8 !important;
  letter-spacing: 0.06em !important;
  word-spacing: 0.16em !important;
}
body.a11y-spacing-minus{
  line-height: 1.45 !important;
  letter-spacing: 0.01em !important;
  word-spacing: 0.02em !important;
}


      <div id="a11y-live" class="a11y-sr" aria-live="polite" aria-atomic="true"></div>
/* ------------------------------
    `;
  Large cursors (wiki-hosted)
    document.body.appendChild(panel);
  ------------------------------ */
body.a11y-cursor-white,
body.a11y-cursor-white *{
  cursor: url("/index.php/Special:FilePath/large-arrow-white-64.png") 8 8, auto !important;
}
body.a11y-cursor-yellow,
body.a11y-cursor-yellow *{
  cursor: url("/index.php/Special:FilePath/large-arrow-yellow-64.png") 8 8, auto !important;
}


    liveEl = panel.querySelector("#a11y-live");
/* ------------------------------
  Reading ruler
  ------------------------------ */
#a11y-ruler{
  position: fixed;
  left: 0;
  top: 0;
  height: 22px;
  width: 100vw;
  z-index: 9999;
  pointer-events: none;


    function openPanel() {
  background: linear-gradient(
      panel.hidden = false;
    to bottom,
      toggle.setAttribute("aria-expanded", "true");
    #FFD400 0%,
      const firstBtn = panel.querySelector("button.a11y-btn");
    #FFD400 40%,
      if (firstBtn) firstBtn.focus();
    #000000 40%,
     }
     #000000 60%,
     function closePanel() {
     #FFD400 60%,
      panel.hidden = true;
    #FFD400 100%
      toggle.setAttribute("aria-expanded", "false");
  );
      toggle.focus();
}
    }
#a11y-ruler[hidden]{ display:none !important; }


    toggle.addEventListener("click", () => {
/* =========================================================
      if (panel.hidden) openPanel(); else closePanel();
  MediaWiki: extra HIGH CONTRAST coverage (Timeless + Vector)
    });
  Add at END of Gadget CSS so it wins.
  ========================================================= */


    document.addEventListener("click", (e) => {
/* TOC (Timeless/Vector both use variants) */
      if (panel.hidden) return;
body.a11y-contrast #toc,
      if (panel.contains(e.target) || toggle.contains(e.target)) return;
body.a11y-contrast .toc,
      closePanel();
body.a11y-contrast #toc * ,
    });
body.a11y-contrast .toc *{
  background: #000 !important;
  color: #FFD400 !important;
  border-color: rgba(255, 212, 0, 0.75) !important;
}


    document.addEventListener("keydown", (e) => {
/* Category links area */
      if (panel.hidden) return;
body.a11y-contrast .mw-normal-catlinks,
      if (e.key === "Escape") {
body.a11y-contrast .mw-normal-catlinks *,
        e.preventDefault();
body.a11y-contrast .catlinks,
        closePanel();
body.a11y-contrast .catlinks *{
      }
  background: #000 !important;
    });
  color: #FFD400 !important;
  border-color: rgba(255, 212, 0, 0.75) !important;
}


    panel.addEventListener("click", (e) => {
/* Wikitables (and common table elements that retain dark text) */
      const btn = e.target.closest("button[data-action]");
body.a11y-contrast table,
      if (!btn) return;
body.a11y-contrast table *,
      const action = btn.getAttribute("data-action") || "";
body.a11y-contrast .wikitable,
      handleAction(action);
body.a11y-contrast .wikitable *,
    });
body.a11y-contrast .mw-datatable,
  }
body.a11y-contrast .mw-datatable *{
  background: #000 !important;
  color: #FFD400 !important;
  border-color: rgba(255, 212, 0, 0.75) !important;
}


  function updateUiButtons() {
/* Table headers sometimes have their own background */
    const panel = document.getElementById("a11y-panel");
body.a11y-contrast .wikitable th,
    if (!panel) return;
body.a11y-contrast table th{
  background: #000 !important;
  color: #FFD400 !important;
}


    const setPressed = (action, pressed) => {
/* Figures/captions */
      const b = panel.querySelector(`button[data-action="${action}"]`);
body.a11y-contrast figure,
      if (b) b.setAttribute("aria-pressed", pressed ? "true" : "false");
body.a11y-contrast figure *,
    };
body.a11y-contrast .mw-halign-center,
body.a11y-contrast .mw-halign-centre,
body.a11y-contrast .mw-halign-center *,
body.a11y-contrast .mw-halign-centre *{
  background: #000 !important;
  color: #FFD400 !important;
}


    setPressed("contrast", STATE.contrast);
/* Thumb frames / image captions */
    setPressed("underline", STATE.underline);
body.a11y-contrast .thumb,
    setPressed("ruler", STATE.ruler);
body.a11y-contrast .thumb *,
    setPressed("cursor", STATE.cursor !== "off");
body.a11y-contrast .thumbinner,
    setPressed("spacing-plus", STATE.spacing === "plus");
body.a11y-contrast .thumbinner *,
    setPressed("spacing-minus", STATE.spacing === "minus");
body.a11y-contrast .thumbcaption,
    setPressed("spacing-reset", false);
body.a11y-contrast .thumbcaption *{
    setPressed("font-plus", false);
  background: #000 !important;
    setPressed("font-minus", false);
  color: #FFD400 !important;
    setPressed("font-reset", STATE.fontPct !== 100);
  border-color: rgba(255, 212, 0, 0.75) !important;
    setPressed("reset", false);
}


    const cursorBtn = panel.querySelector('button[data-action="cursor"]');
/* Code-like blocks */
    if (cursorBtn) {
body.a11y-contrast pre,
      const label = (STATE.cursor === "off") ? "Off" : (STATE.cursor === "white") ? "White" : "Yellow";
body.a11y-contrast pre *,
      cursorBtn.textContent = `Large cursor: ${label}`;
body.a11y-contrast code,
      cursorBtn.setAttribute(
body.a11y-contrast code *{
        "aria-label",
  background: #000 !important;
        STATE.cursor === "off"
  color: #FFD400 !important;
          ? "Large cursor off. Press to turn on"
  border-color: rgba(255, 212, 0, 0.75) !important;
          : `Large cursor ${label}. Press to change colour or turn off`
}
      );
    }
  }


  // -------------------------------------------------------------------
/* Safety: ensure visited links don’t revert */
  // Actions
body.a11y-contrast a:visited,
  // -------------------------------------------------------------------
body.a11y-contrast .mw-parser-output a:visited{
  function handleAction(action) {
  color: #FFD400 !important;
    switch (action) {
}
      case "contrast":
        STATE.contrast = !STATE.contrast;
        setCookie("contrast", STATE.contrast ? "1" : "0");
        applyState();
        announce(`High contrast ${STATE.contrast ? "on" : "off"}`);
        return;


      case "underline":
/* =========================================================
        STATE.underline = !STATE.underline;
  HIGH CONTRAST: Footer (Vector + Timeless)
        setCookie("underline", STATE.underline ? "1" : "0");
  ========================================================= */
        applyState();
body.a11y-contrast #mw-footer,
        announce(`Underline links ${STATE.underline ? "on" : "off"}`);
body.a11y-contrast #mw-footer-container{
        return;
  background: #000 !important;
  color: #FFD400 !important;
  background-image: none !important;
  border-top: 10px solid #FFD400 !important;
}
body.a11y-contrast #footer-list,
body.a11y-contrast #footer-list *,
body.a11y-contrast #footer-icons,
body.a11y-contrast #footer-icons *{
  background: #000 !important;
  color: #FFD400 !important;
}
body.a11y-contrast #mw-footer a,
body.a11y-contrast #mw-footer a:visited{
  color: #FFD400 !important;
  text-decoration-color: #FFD400 !important;
}
body.a11y-contrast #mw-footer .mw-portlet,
body.a11y-contrast #mw-footer .mw-portlet-body{
  background: #000 !important;
  color: #FFD400 !important;
  border-color: rgba(255, 212, 0, 0.75) !important;
}
body.a11y-contrast #mw-footer hr{
  border-color: #FFD400 !important;
}


      case "spacing-plus":
/* =========================================================
        STATE.spacing = (STATE.spacing === "plus") ? "off" : "plus";
  Underline links: ensure TOC wins over table border-color rules
        setCookie("spacing", STATE.spacing);
  ========================================================= */
        applyState();
body.a11y-underline-links #toc a,
        announce(STATE.spacing === "plus" ? "Text spacing increased" : "Text spacing normal");
body.a11y-underline-links .toc a{
        return;
  border-bottom-color: var(--a11y-link-underline) !important;
}
body.a11y-underline-links.a11y-contrast #toc a,
body.a11y-underline-links.a11y-contrast .toc a{
  border-bottom-color: #36C !important;
}


      case "spacing-minus":
/* =========================================================
        STATE.spacing = (STATE.spacing === "minus") ? "off" : "minus";
  HIGH CONTRAST: Header + search + user tools
        setCookie("spacing", STATE.spacing);
  ========================================================= */
        applyState();
        announce(STATE.spacing === "minus" ? "Text spacing decreased" : "Text spacing normal");
        return;


      case "spacing-reset":
/* Make header + content containers black (your enhancement #3) */
        STATE.spacing = "off";
body.a11y-contrast #mw-header-container,
        setCookie("spacing", "off");
body.a11y-contrast #mw-content-container{
        applyState();
  background: #000 !important;
        announce("Text spacing reset");
  background-image: none !important;
        return;
}


      case "cursor":
/* Timeless header containers */
        STATE.cursor = (STATE.cursor === "off") ? "white" : (STATE.cursor === "white") ? "yellow" : "off";
body.a11y-contrast #mw-header,
        setCookie("cursor", STATE.cursor);
body.a11y-contrast #mw-header-container .ts-inner,
        applyState();
body.a11y-contrast #mw-header-container .mw-body,
        announce(
body.a11y-contrast #mw-header-container .mw-portlet,
          STATE.cursor === "off" ? "Large cursor off" :
body.a11y-contrast #mw-header-container .mw-portlet-body{
          STATE.cursor === "white" ? "Large cursor white" :
  background: #000 !important;
          "Large cursor yellow"
  color: #FFD400 !important;
        );
  background-image: none !important;
        return;
  border-color: #FFD400 !important;
}


      case "ruler":
/* Title: force yellow (Timeless has burgundy rule) */
        STATE.ruler = !STATE.ruler;
body.a11y-contrast.skin-timeless #p-logo-text #p-banner,
        setCookie("ruler", STATE.ruler ? "1" : "0");
body.a11y-contrast.skin-timeless #p-banner.mw-wiki-title,
        if (STATE.ruler && !rulerY) rulerY = Math.floor(window.innerHeight / 2);
body.a11y-contrast.skin-timeless #p-logo-text .mw-wiki-title{
        applyState();
  color: #FFD400 !important;
        announce(`Reading ruler ${STATE.ruler ? "on" : "off"}`);
}
        return;


      case "font-plus":
/* Strapline */
        STATE.fontPct = clamp(STATE.fontPct + 10, 90, 140);
body.a11y-contrast #bhc-strapline,
        setCookie("fontPct", String(STATE.fontPct));
body.a11y-contrast .bhc-strapline{
        applyState();
  color: #FFD400 !important;
        announce("Text size increased");
}
        return;


      case "font-minus":
/* Search box (input + button) — force yellow on black */
        STATE.fontPct = clamp(STATE.fontPct - 10, 90, 140);
body.a11y-contrast #p-search,
        setCookie("fontPct", String(STATE.fontPct));
body.a11y-contrast #simpleSearch{
        applyState();
  background: #000 !important;
        announce("Text size decreased");
  color: #FFD400 !important;
        return;
}


      case "font-reset":
body.a11y-contrast #searchInput,
        STATE.fontPct = 100;
body.a11y-contrast #p-search #searchInput,
        setCookie("fontPct", "100");
body.a11y-contrast #p-search input[type="search"]{
        applyState();
  background: #000 !important;
        announce("Text size reset");
  color: #FFD400 !important;
        return;
  caret-color: #FFD400 !important;
  border: 2px solid #FFD400 !important;
  box-shadow: none !important;
}
body.a11y-contrast #searchInput::placeholder{
  color: rgba(255,212,0,0.75) !important;
}


      case "reset":
/* Search submit button */
        ["contrast","underline","spacing","cursor","ruler","fontPct"].forEach(delCookie);
body.a11y-contrast input#mw-searchButton,
        STATE.contrast = false;
body.a11y-contrast input#searchButton,
        STATE.underline = false;
body.a11y-contrast #mw-searchButton{
        STATE.spacing = "off";
  background-color: #000 !important;
        STATE.cursor = "off";
  border: 2px solid rgba(255,212,0,.85) !important;
        STATE.ruler = false;
  filter: none !important;
        STATE.fontPct = 100;
}
        rulerY = 0;
        applyState();
        announce("Accessibility settings reset");
        return;
    }
  }


  // -------------------------------------------------------------------
/* Search suggestions dropdown (DOM-created) */
   // Load saved settings
body.a11y-contrast .suggestions,
   // -------------------------------------------------------------------
body.a11y-contrast .suggestions-results,
   function loadState() {
body.a11y-contrast .suggestions-special,
    STATE.contrast = getCookie("contrast", "0") === "1";
body.a11y-contrast .suggestions a,
    STATE.underline = getCookie("underline", "0") === "1";
body.a11y-contrast .suggestions-result,
body.a11y-contrast .mw-searchSuggest,
body.a11y-contrast .mw-searchSuggest *{
   background: #000 !important;
   color: #FFD400 !important;
   border-color: rgba(255,212,0,.85) !important;
}
body.a11y-contrast .suggestions a:visited{
  color: #FFD400 !important;
}
body.a11y-contrast .suggestions-result-current,
body.a11y-contrast .suggestions a:hover{
  background: #111 !important;
}


    const spacing = getCookie("spacing", "off");
/* Vector search tabs area */
    STATE.spacing = (spacing === "plus" || spacing === "minus") ? spacing : "off";
body.a11y-contrast #mw-search-profile-tabs,
body.a11y-contrast #mw-search-profile-tabs *{
  background: #000 !important;
  color: #FFD400 !important;
  border-color: rgba(255,212,0,.85) !important;
}


    const cursor = getCookie("cursor", "off");
/* User tools dropdown text — ensure yellow everywhere (your #2.2) */
    STATE.cursor = (cursor === "white" || cursor === "yellow") ? cursor : "off";
body.a11y-contrast #user-tools,
body.a11y-contrast #user-tools *,
body.a11y-contrast #personal,
body.a11y-contrast #personal *{
  background: #000 !important;
  color: #FFD400 !important;
  border-color: rgba(255,212,0,.85) !important;
}


    STATE.ruler = getCookie("ruler", "0") === "1";
body.a11y-contrast #personal .dropdown,
body.a11y-contrast #personal-inner.dropdown,
body.a11y-contrast #personal .mw-portlet-body,
body.a11y-contrast #personal .mw-portlet-body *,
body.a11y-contrast #personal-inner.dropdown *{
  background: #000 !important;
  color: #FFD400 !important;
  border: 2px solid rgba(255,212,0,.85) !important;
}


    const fp = parseInt(getCookie("fontPct", "100"), 10);
/* Keep any header links yellow (incl visited) */
    STATE.fontPct = clamp(isNaN(fp) ? 100 : fp, 90, 140);
body.a11y-contrast #mw-header a,
  }
body.a11y-contrast #mw-header a:visited,
body.a11y-contrast #mw-head a,
body.a11y-contrast #mw-head a:visited{
  color: #FFD400 !important;
}


  // -------------------------------------------------------------------
/* =========================================================
  // Init
  EMERGENCY: ensure the accessibility toggle is visible
  // -------------------------------------------------------------------
  (keep at VERY END)
   function init() {
  ========================================================= */
    ensureUi();
#a11y-toggle.a11y-toggle{
    loadState();
   position: fixed !important;
    applyState();
  right: 16px !important;
  }
  bottom: 16px !important;


   if (document.readyState === "loading") {
   display: inline-flex !important;
    document.addEventListener("DOMContentLoaded", init);
  visibility: visible !important;
   } else {
  opacity: 1 !important;
    init();
 
   }
  z-index: 2147483647 !important;
})();
  pointer-events: auto !important;
 
   transform: none !important;
}
#a11y-toggle.a11y-toggle svg{
  display: block !important;
   visibility: visible !important;
  opacity: 1 !important;
}

Revision as of 18:18, 27 January 2026

/*
⚠️ Do not reorder rules below this line — contrast overrides depend on cascade order.
*/


/* =========================================================
   BHC Accessibility Gadget – MediaWiki / Timeless
   ========================================================= */

/* ---------------------------------------------------------
   Variables
   --------------------------------------------------------- */
:root{
  --a11y-bg: #111827;
  --a11y-border: #374151;
  --a11y-fg: #ffffff;
  --a11y-focus: #FFBF47;
  --a11y-link-underline: #36C; /* MediaWiki link blue */
  --a11y-menu-width: 340px;
}

/* Timeless: stop skin CSS shrinking the accessibility icon */
body.skin-timeless .a11y-toggle{
  font-size: 16px !important;
  line-height: 1 !important;
}
body.skin-timeless .a11y-toggle svg{
  display: block !important;
  width: 36px !important;
  height: 36px !important;
  min-width: 36px !important;
  min-height: 36px !important;
  max-width: none !important;
  max-height: none !important;

  /* If the glyph still looks small, keep this: */
  transform: scale(1.15) !important;
  transform-origin: 50% 50% !important;
}
/* Make sure fill isn’t being altered by skin rules */
body.skin-timeless .a11y-toggle svg path{
  fill: currentColor !important;
}

/* ---------------------------------------------------------
   Skip links (appear when focused)
   --------------------------------------------------------- */
.skip-link{
  position: absolute;
  left: 12px;
  top: 12px;
  z-index: 11000;

  padding: 10px 14px;
  border-radius: 8px;

  background: #111827;
  color: #fff;
  text-decoration: none;

  transform: translateY(-150%);
  transition: transform 0.15s ease;
  border: 2px solid var(--a11y-focus);
}
.skip-link:focus,
.skip-link:focus-visible{
  transform: translateY(0);
  outline: none;
}
body.a11y-contrast .skip-link{
  background: #000 !important;
  color: #FFD400 !important;
  border-color: #FFD400 !important;
}

/* ---------------------------------------------------------
   Floating accessibility toggle
   --------------------------------------------------------- */
.a11y-toggle{
  position: fixed;
  right: 16px;
  bottom: 16px;
  z-index: 10001;

  width: 56px;
  height: 56px;
  border-radius: 50%;

  background: #005FCC;
  border: 2px solid #ffffff;
  color: #ffffff;

  display: inline-flex;
  align-items: center;
  justify-content: center;

  box-shadow: 0 10px 24px rgba(0,0,0,0.25);
  cursor: pointer;
}
.a11y-toggle:hover{ background:#0047A6; }
.a11y-toggle:focus-visible{
  outline: 3px solid var(--a11y-focus);
  outline-offset: 3px;
}
.a11y-toggle svg{
  width: 44px;
  height: 44px;
  fill: currentColor;
}

/* ---------------------------------------------------------
   Panel
   --------------------------------------------------------- */
.a11y-panel{
  position: fixed;
  right: 14px;
  bottom: 76px;
  z-index: 10000;

  width: var(--a11y-menu-width);
  max-width: calc(100vw - 28px);

  background: var(--a11y-bg);
  border: 1px solid var(--a11y-border);
  color: var(--a11y-fg);
  border-radius: 16px;
  padding: 12px;

  box-shadow: 0 14px 40px rgba(0,0,0,0.28);
}
.a11y-panel[hidden]{ display:none !important; }

/* Header */
.a11y-head{
  display:flex;
  align-items:center;
  justify-content: space-between;
  margin-bottom: 10px;
}
.a11y-head h2{
  font-size: 16px;
  margin: 0;
}
.a11y-help{
  width: 28px;
  height: 28px;
  border-radius: 50%;
  background: #fff;
  color: #111827;
  font-weight: 900;
  text-decoration: none;
  display:flex;
  align-items:center;
  justify-content:center;
}
.a11y-help:focus-visible{
  outline: 3px solid var(--a11y-focus);
  outline-offset: 2px;
}

/* Buttons */
.a11y-row{
  display:flex;
  gap: 8px;
  flex-wrap: wrap;
  margin-bottom: 10px;
}
.a11y-btn{
  border: 1px solid #D1D5DB;
  background:#fff;
  color:#111827;
  padding: 8px 10px;
  border-radius: 999px;
  font-size: 14px;
  font-weight: 800;
  cursor: pointer;
}
.a11y-btn[aria-pressed="true"]{
  background:#111827;
  color:#fff;
}
.a11y-btn:focus-visible{
  outline: 3px solid var(--a11y-focus);
  outline-offset: 2px;
}

.a11y-hint{
  font-size: 13px;
  color: var(--a11y-fg);
  line-height: 1.4;
}

/* Screen reader only */
.a11y-sr{
  position:absolute;
  width:1px;
  height:1px;
  overflow:hidden;
  clip:rect(0,0,0,0);
}

/* =========================================================
   Feature classes
   ========================================================= */

/* ------------------------------
   High contrast (black/yellow/blue)
   ------------------------------ */
body.a11y-contrast{
  background:#000 !important;
  color:#FFD400 !important;

  --a11y-bg: #000;
  --a11y-border: rgba(255,212,0,.7);
  --a11y-fg: #FFD400;
  --a11y-focus: #FFD400;
  --a11y-link-underline: #36C;
}

body.a11y-contrast a,
body.a11y-contrast a:visited{
  color:#FFD400 !important;
}

/* Headings */
body.a11y-contrast h1,
body.a11y-contrast h2,
body.a11y-contrast h3,
body.a11y-contrast h4,
body.a11y-contrast h5,
body.a11y-contrast h6{
  color:#FFD400 !important;
  border-color:#FFD400 !important;
}

/* Main containers (Timeless needs explicit coverage) */
body.a11y-contrast #mw-wrapper,
body.a11y-contrast #mw-content-container,
body.a11y-contrast #mw-content,
body.a11y-contrast .mw-body,
body.a11y-contrast .mw-parser-output{
  background:#000 !important;
  color:#FFD400 !important;
}

/* Sidebars / tools */
body.a11y-contrast #site-navigation,
body.a11y-contrast #site-tools,
body.a11y-contrast #page-tools,
body.a11y-contrast .mw-portlet,
body.a11y-contrast .mw-portlet-body{
  background:#000 !important;
  color:#FFD400 !important;
  border-color: rgba(255,212,0,.7) !important;
}

/* Ensure links inside toolboxes */
body.a11y-contrast #site-navigation a,
body.a11y-contrast #site-tools a,
body.a11y-contrast #page-tools a{
  color:#FFD400 !important;
}

/* A11y UI in contrast */
body.a11y-contrast .a11y-panel,
body.a11y-contrast .a11y-toggle{
  background:#000 !important;
}
body.a11y-contrast .a11y-btn{
  background:#000 !important;
  color:#FFD400 !important;
  border:2px solid rgba(255,212,0,.85) !important;
}

/* ------------------------------
   Underline links
   ------------------------------ */
/* Underline links – MediaWiki-style */
body.a11y-underline-links a{
  text-decoration: none !important;
  border-bottom: 2px solid var(--a11y-link-underline);
  padding-bottom: 0.05em;
}
/* In contrast mode you currently want blue (#36c) */
body.a11y-underline-links.a11y-contrast a{
  border-bottom-color: #36C !important;
}

/* ------------------------------
   Text spacing
   ------------------------------ */
body.a11y-spacing-plus{
  line-height: 1.8 !important;
  letter-spacing: 0.06em !important;
  word-spacing: 0.16em !important;
}
body.a11y-spacing-minus{
  line-height: 1.45 !important;
  letter-spacing: 0.01em !important;
  word-spacing: 0.02em !important;
}

/* ------------------------------
   Large cursors (wiki-hosted)
   ------------------------------ */
body.a11y-cursor-white,
body.a11y-cursor-white *{
  cursor: url("/index.php/Special:FilePath/large-arrow-white-64.png") 8 8, auto !important;
}
body.a11y-cursor-yellow,
body.a11y-cursor-yellow *{
  cursor: url("/index.php/Special:FilePath/large-arrow-yellow-64.png") 8 8, auto !important;
}

/* ------------------------------
   Reading ruler
   ------------------------------ */
#a11y-ruler{
  position: fixed;
  left: 0;
  top: 0;
  height: 22px;
  width: 100vw;
  z-index: 9999;
  pointer-events: none;

  background: linear-gradient(
    to bottom,
    #FFD400 0%,
    #FFD400 40%,
    #000000 40%,
    #000000 60%,
    #FFD400 60%,
    #FFD400 100%
  );
}
#a11y-ruler[hidden]{ display:none !important; }

/* =========================================================
   MediaWiki: extra HIGH CONTRAST coverage (Timeless + Vector)
   Add at END of Gadget CSS so it wins.
   ========================================================= */

/* TOC (Timeless/Vector both use variants) */
body.a11y-contrast #toc,
body.a11y-contrast .toc,
body.a11y-contrast #toc * ,
body.a11y-contrast .toc *{
  background: #000 !important;
  color: #FFD400 !important;
  border-color: rgba(255, 212, 0, 0.75) !important;
}

/* Category links area */
body.a11y-contrast .mw-normal-catlinks,
body.a11y-contrast .mw-normal-catlinks *,
body.a11y-contrast .catlinks,
body.a11y-contrast .catlinks *{
  background: #000 !important;
  color: #FFD400 !important;
  border-color: rgba(255, 212, 0, 0.75) !important;
}

/* Wikitables (and common table elements that retain dark text) */
body.a11y-contrast table,
body.a11y-contrast table *,
body.a11y-contrast .wikitable,
body.a11y-contrast .wikitable *,
body.a11y-contrast .mw-datatable,
body.a11y-contrast .mw-datatable *{
  background: #000 !important;
  color: #FFD400 !important;
  border-color: rgba(255, 212, 0, 0.75) !important;
}

/* Table headers sometimes have their own background */
body.a11y-contrast .wikitable th,
body.a11y-contrast table th{
  background: #000 !important;
  color: #FFD400 !important;
}

/* Figures/captions */
body.a11y-contrast figure,
body.a11y-contrast figure *,
body.a11y-contrast .mw-halign-center,
body.a11y-contrast .mw-halign-centre,
body.a11y-contrast .mw-halign-center *,
body.a11y-contrast .mw-halign-centre *{
  background: #000 !important;
  color: #FFD400 !important;
}

/* Thumb frames / image captions */
body.a11y-contrast .thumb,
body.a11y-contrast .thumb *,
body.a11y-contrast .thumbinner,
body.a11y-contrast .thumbinner *,
body.a11y-contrast .thumbcaption,
body.a11y-contrast .thumbcaption *{
  background: #000 !important;
  color: #FFD400 !important;
  border-color: rgba(255, 212, 0, 0.75) !important;
}

/* Code-like blocks */
body.a11y-contrast pre,
body.a11y-contrast pre *,
body.a11y-contrast code,
body.a11y-contrast code *{
  background: #000 !important;
  color: #FFD400 !important;
  border-color: rgba(255, 212, 0, 0.75) !important;
}

/* Safety: ensure visited links don’t revert */
body.a11y-contrast a:visited,
body.a11y-contrast .mw-parser-output a:visited{
  color: #FFD400 !important;
}

/* =========================================================
   HIGH CONTRAST: Footer (Vector + Timeless)
   ========================================================= */
body.a11y-contrast #mw-footer,
body.a11y-contrast #mw-footer-container{
  background: #000 !important;
  color: #FFD400 !important;
  background-image: none !important;
  border-top: 10px solid #FFD400 !important;
}
body.a11y-contrast #footer-list,
body.a11y-contrast #footer-list *,
body.a11y-contrast #footer-icons,
body.a11y-contrast #footer-icons *{
  background: #000 !important;
  color: #FFD400 !important;
}
body.a11y-contrast #mw-footer a,
body.a11y-contrast #mw-footer a:visited{
  color: #FFD400 !important;
  text-decoration-color: #FFD400 !important;
}
body.a11y-contrast #mw-footer .mw-portlet,
body.a11y-contrast #mw-footer .mw-portlet-body{
  background: #000 !important;
  color: #FFD400 !important;
  border-color: rgba(255, 212, 0, 0.75) !important;
}
body.a11y-contrast #mw-footer hr{
  border-color: #FFD400 !important;
}

/* =========================================================
   Underline links: ensure TOC wins over table border-color rules
   ========================================================= */
body.a11y-underline-links #toc a,
body.a11y-underline-links .toc a{
  border-bottom-color: var(--a11y-link-underline) !important;
}
body.a11y-underline-links.a11y-contrast #toc a,
body.a11y-underline-links.a11y-contrast .toc a{
  border-bottom-color: #36C !important;
}

/* =========================================================
   HIGH CONTRAST: Header + search + user tools
   ========================================================= */

/* Make header + content containers black (your enhancement #3) */
body.a11y-contrast #mw-header-container,
body.a11y-contrast #mw-content-container{
  background: #000 !important;
  background-image: none !important;
}

/* Timeless header containers */
body.a11y-contrast #mw-header,
body.a11y-contrast #mw-header-container .ts-inner,
body.a11y-contrast #mw-header-container .mw-body,
body.a11y-contrast #mw-header-container .mw-portlet,
body.a11y-contrast #mw-header-container .mw-portlet-body{
  background: #000 !important;
  color: #FFD400 !important;
  background-image: none !important;
  border-color: #FFD400 !important;
}

/* Title: force yellow (Timeless has burgundy rule) */
body.a11y-contrast.skin-timeless #p-logo-text #p-banner,
body.a11y-contrast.skin-timeless #p-banner.mw-wiki-title,
body.a11y-contrast.skin-timeless #p-logo-text .mw-wiki-title{
  color: #FFD400 !important;
}

/* Strapline */
body.a11y-contrast #bhc-strapline,
body.a11y-contrast .bhc-strapline{
  color: #FFD400 !important;
}

/* Search box (input + button) — force yellow on black */
body.a11y-contrast #p-search,
body.a11y-contrast #simpleSearch{
  background: #000 !important;
  color: #FFD400 !important;
}

body.a11y-contrast #searchInput,
body.a11y-contrast #p-search #searchInput,
body.a11y-contrast #p-search input[type="search"]{
  background: #000 !important;
  color: #FFD400 !important;
  caret-color: #FFD400 !important;
  border: 2px solid #FFD400 !important;
  box-shadow: none !important;
}
body.a11y-contrast #searchInput::placeholder{
  color: rgba(255,212,0,0.75) !important;
}

/* Search submit button */
body.a11y-contrast input#mw-searchButton,
body.a11y-contrast input#searchButton,
body.a11y-contrast #mw-searchButton{
  background-color: #000 !important;
  border: 2px solid rgba(255,212,0,.85) !important;
  filter: none !important;
}

/* Search suggestions dropdown (DOM-created) */
body.a11y-contrast .suggestions,
body.a11y-contrast .suggestions-results,
body.a11y-contrast .suggestions-special,
body.a11y-contrast .suggestions a,
body.a11y-contrast .suggestions-result,
body.a11y-contrast .mw-searchSuggest,
body.a11y-contrast .mw-searchSuggest *{
  background: #000 !important;
  color: #FFD400 !important;
  border-color: rgba(255,212,0,.85) !important;
}
body.a11y-contrast .suggestions a:visited{
  color: #FFD400 !important;
}
body.a11y-contrast .suggestions-result-current,
body.a11y-contrast .suggestions a:hover{
  background: #111 !important;
}

/* Vector search tabs area */
body.a11y-contrast #mw-search-profile-tabs,
body.a11y-contrast #mw-search-profile-tabs *{
  background: #000 !important;
  color: #FFD400 !important;
  border-color: rgba(255,212,0,.85) !important;
}

/* User tools dropdown text — ensure yellow everywhere (your #2.2) */
body.a11y-contrast #user-tools,
body.a11y-contrast #user-tools *,
body.a11y-contrast #personal,
body.a11y-contrast #personal *{
  background: #000 !important;
  color: #FFD400 !important;
  border-color: rgba(255,212,0,.85) !important;
}

body.a11y-contrast #personal .dropdown,
body.a11y-contrast #personal-inner.dropdown,
body.a11y-contrast #personal .mw-portlet-body,
body.a11y-contrast #personal .mw-portlet-body *,
body.a11y-contrast #personal-inner.dropdown *{
  background: #000 !important;
  color: #FFD400 !important;
  border: 2px solid rgba(255,212,0,.85) !important;
}

/* Keep any header links yellow (incl visited) */
body.a11y-contrast #mw-header a,
body.a11y-contrast #mw-header a:visited,
body.a11y-contrast #mw-head a,
body.a11y-contrast #mw-head a:visited{
  color: #FFD400 !important;
}

/* =========================================================
   EMERGENCY: ensure the accessibility toggle is visible
   (keep at VERY END)
   ========================================================= */
#a11y-toggle.a11y-toggle{
  position: fixed !important;
  right: 16px !important;
  bottom: 16px !important;

  display: inline-flex !important;
  visibility: visible !important;
  opacity: 1 !important;

  z-index: 2147483647 !important;
  pointer-events: auto !important;

  transform: none !important;
}
#a11y-toggle.a11y-toggle svg{
  display: block !important;
  visibility: visible !important;
  opacity: 1 !important;
}