/* ========================================================================= age-gate.js — non-dismissible age verification interstitial. The 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(); } })();