Files
igaming-template/assets/js/age-gate.js
T

176 lines
5.0 KiB
JavaScript

/* =========================================================================
age-gate.js — non-dismissible age verification interstitial.
The <html> class "age-gate-active" is set as early as possible by an inline
script in baseof.html to avoid a flash for returning (confirmed) visitors.
This module wires the confirm/exit buttons, syncs the displayed age with the
active locale (set by the locale switcher) and — because the dialog is
role="dialog" aria-modal="true" — manages focus: it moves focus into the
dialog, traps Tab/Shift+Tab, marks the rest of the page inert while active,
and restores focus on confirm.
Deliberately dependency-free (no Alpine, no eval/new Function): the gate is
driven purely by the CSS "age-gate-active" class, so it still fails safe
(gate shows, confirm works) even if Alpine never loads.
========================================================================= */
(function () {
"use strict";
var STORAGE_KEY = "ageConfirmed";
var INERT_SELECTORS = ["header", "main", "footer"];
var FOCUSABLE =
'a[href], button:not([disabled]), textarea, input, select, [tabindex]:not([tabindex="-1"])';
var gate = null;
var lastFocused = null;
var trapActive = false;
function hasConfirmed() {
try {
return localStorage.getItem(STORAGE_KEY) === "1";
} catch (e) {
return false;
}
}
function focusableIn(el) {
return Array.prototype.filter.call(
el.querySelectorAll(FOCUSABLE),
function (node) {
return (
node.offsetWidth > 0 ||
node.offsetHeight > 0 ||
node === document.activeElement
);
}
);
}
function onKeydown(e) {
if (!trapActive || e.key !== "Tab" || !gate) return;
var f = focusableIn(gate);
if (!f.length) {
e.preventDefault();
return;
}
var first = f[0];
var last = f[f.length - 1];
if (e.shiftKey && document.activeElement === first) {
e.preventDefault();
last.focus();
} else if (!e.shiftKey && document.activeElement === last) {
e.preventDefault();
first.focus();
}
}
function onFocusIn(e) {
if (trapActive && gate && !gate.contains(e.target)) {
var f = focusableIn(gate);
if (f.length) f[0].focus();
}
}
function setBackgroundInert(on) {
INERT_SELECTORS.forEach(function (sel) {
var el = document.querySelector(sel);
if (!el) return;
if (on) {
el.setAttribute("inert", "");
el.setAttribute("aria-hidden", "true");
} else {
el.removeAttribute("inert");
el.removeAttribute("aria-hidden");
}
});
}
function activateTrap() {
if (trapActive || !gate) return;
trapActive = true;
lastFocused = document.activeElement;
setBackgroundInert(true);
document.addEventListener("keydown", onKeydown, true);
document.addEventListener("focusin", onFocusIn);
var confirmBtn = gate.querySelector("[data-age-confirm]");
if (confirmBtn) {
// Defer to ensure the element is focusable after the first paint.
window.setTimeout(function () {
confirmBtn.focus();
}, 0);
}
}
function releaseTrap() {
trapActive = false;
setBackgroundInert(false);
document.removeEventListener("keydown", onKeydown, true);
document.removeEventListener("focusin", onFocusIn);
}
function restoreFocus() {
var target =
(lastFocused &&
document.contains(lastFocused) &&
lastFocused !== document.body &&
lastFocused) ||
document.querySelector("header a, header button") ||
document.getElementById("main");
if (!target) return;
if (target.id === "main" && !target.hasAttribute("tabindex")) {
target.setAttribute("tabindex", "-1");
}
try {
target.focus();
} catch (e) {
/* focus can fail on detached nodes — ignore */
}
}
function confirmAge() {
try {
localStorage.setItem(STORAGE_KEY, "1");
} catch (e) {
/* storage unavailable — still allow through for this session */
}
document.documentElement.classList.remove("age-gate-active");
releaseTrap();
restoreFocus();
}
function exitSite() {
window.location.href = "https://www.google.com/";
}
function init() {
gate = document.getElementById("age-gate");
if (!gate) {
document.documentElement.classList.remove("age-gate-active");
return;
}
var confirmBtn = gate.querySelector("[data-age-confirm]");
if (confirmBtn) {
confirmBtn.addEventListener("click", confirmAge);
}
var exitBtn = gate.querySelector("[data-age-exit]");
if (exitBtn) {
exitBtn.addEventListener("click", exitSite);
}
if (hasConfirmed()) {
document.documentElement.classList.remove("age-gate-active");
setBackgroundInert(false);
} else {
document.documentElement.classList.add("age-gate-active");
activateTrap();
}
}
if (document.readyState === "loading") {
document.addEventListener("DOMContentLoaded", init);
} else {
init();
}
})();