/* Copyright (C) 2025 Moko Consulting This file is part of a Moko Consulting project. SPDX-License-Identifier: GPL-3.0-or-later */ (function (win, doc) { "use strict"; // ======================================================================== // THEME INITIALIZATION (Early theme application) // ======================================================================== var storageKey = "theme"; var mql = win.matchMedia("(prefers-color-scheme: dark)"); var root = doc.documentElement; /** * Apply theme to , syncing both data-bs-theme and data-aria-theme. * @param {"light"|"dark"} theme */ function applyTheme(theme) { root.setAttribute("data-bs-theme", theme); root.setAttribute("data-aria-theme", theme); try { localStorage.setItem(storageKey, theme); } catch (e) {} } /** * Clear stored preference so system preference is followed. */ function clearStored() { try { localStorage.removeItem(storageKey); } catch (e) {} } /** * Determine system theme. */ function systemTheme() { return mql.matches ? "dark" : "light"; } /** * Get stored theme preference. */ function getStored() { try { return localStorage.getItem(storageKey); } catch (e) { return null; } } // ======================================================================== // FLOATING THEME TOGGLE (FAB) // ======================================================================== function posClassFromBody() { var pos = (doc.body.getAttribute('data-theme-fab-pos') || 'br').toLowerCase(); if (!/^(br|bl|tr|tl)$/.test(pos)) pos = 'br'; return 'pos-' + pos; } function buildThemeToggle() { if (doc.getElementById('mokoThemeFab')) return; var wrap = doc.createElement('div'); wrap.id = 'mokoThemeFab'; wrap.className = posClassFromBody(); // Sun/Moon toggle button var switchWrap = doc.createElement('button'); switchWrap.id = 'mokoThemeSwitch'; switchWrap.type = 'button'; switchWrap.className = 'theme-icon-btn'; switchWrap.setAttribute('aria-label', 'Toggle dark mode'); var sunIcon = doc.createElement('i'); sunIcon.className = 'fa-solid fa-sun'; sunIcon.setAttribute('aria-hidden', 'true'); var moonIcon = doc.createElement('i'); moonIcon.className = 'fa-solid fa-moon'; moonIcon.setAttribute('aria-hidden', 'true'); switchWrap.appendChild(sunIcon); switchWrap.appendChild(moonIcon); function updateThemeIcon(theme) { if (theme === 'dark') { switchWrap.classList.add('is-dark'); switchWrap.classList.remove('is-light'); } else { switchWrap.classList.add('is-light'); switchWrap.classList.remove('is-dark'); } } // Auto toggle (on/off switch style) var autoWrap = doc.createElement('div'); autoWrap.className = 'auto-toggle-wrap'; var autoLabel = doc.createElement('span'); autoLabel.className = 'auto-label'; autoLabel.textContent = 'Auto'; var auto = doc.createElement('button'); auto.id = 'mokoThemeAuto'; auto.type = 'button'; auto.className = 'auto-switch'; auto.setAttribute('role', 'switch'); auto.setAttribute('aria-label', 'Automatic theme (follow system)'); auto.setAttribute('aria-checked', getStored() ? 'false' : 'true'); var autoTrack = doc.createElement('span'); autoTrack.className = 'auto-track'; var autoKnob = doc.createElement('span'); autoKnob.className = 'auto-knob'; autoTrack.appendChild(autoKnob); auto.appendChild(autoTrack); if (!getStored()) auto.classList.add('on'); autoWrap.appendChild(autoLabel); autoWrap.appendChild(auto); // Divider before a11y slot var divider = doc.createElement('span'); divider.className = 'fab-divider'; // A11y slot — buildA11yToolbar will inject its toggle here var a11ySlot = doc.createElement('span'); a11ySlot.id = 'mokoA11ySlot'; // Behavior switchWrap.addEventListener('click', function () { var current = (root.getAttribute('data-bs-theme') || 'light').toLowerCase(); var next = current === 'dark' ? 'light' : 'dark'; applyTheme(next); updateThemeIcon(next); // Turn off auto when manually switching auto.classList.remove('on'); auto.setAttribute('aria-checked', 'false'); // Update meta theme color var meta = doc.querySelector('meta[name="theme-color"]'); if (meta) { meta.setAttribute('content', next === 'dark' ? '#0f1115' : '#ffffff'); } }); auto.addEventListener('click', function () { var isAuto = auto.classList.toggle('on'); auto.setAttribute('aria-checked', isAuto ? 'true' : 'false'); if (isAuto) { clearStored(); var sys = systemTheme(); applyTheme(sys); updateThemeIcon(sys); } }); // Respond to OS changes only when not user-forced var onMql = function () { if (!getStored()) { var sys = systemTheme(); applyTheme(sys); updateThemeIcon(sys); } }; if (typeof mql.addEventListener === 'function') mql.addEventListener('change', onMql); else if (typeof mql.addListener === 'function') mql.addListener(onMql); // Initial state var initial = getStored() || systemTheme(); updateThemeIcon(initial); // Mount wrap.appendChild(switchWrap); wrap.appendChild(autoWrap); wrap.appendChild(divider); wrap.appendChild(a11ySlot); doc.body.appendChild(wrap); // Debug helper win.mokoThemeFabStatus = function () { var el = doc.getElementById('mokoThemeFab'); if (!el) return { mounted: false }; var r = el.getBoundingClientRect(); return { mounted: true, rect: { top: r.top, left: r.left, width: r.width, height: r.height }, zIndex: win.getComputedStyle(el).zIndex, posClass: el.className }; }; // Outline if invisible setTimeout(function () { var r = wrap.getBoundingClientRect(); if (r.width < 10 || r.height < 10) { wrap.classList.add('debug-outline'); console.warn('[moko] Theme FAB mounted but appears too small — check CSS collisions.'); } }, 50); } // ======================================================================== // ACCESSIBILITY TOOLBAR // ======================================================================== var a11yStorageKey = "moko-a11y"; var fontSizeSteps = [85, 90, 100, 110, 120, 130]; var defaultStep = 2; // index of 100 function getA11yPrefs() { try { var raw = localStorage.getItem(a11yStorageKey); if (raw) return JSON.parse(raw); } catch (e) {} return { fontStep: defaultStep, inverted: false, contrast: false, links: false, font: false, paused: false }; } function saveA11yPrefs(prefs) { try { localStorage.setItem(a11yStorageKey, JSON.stringify(prefs)); } catch (e) {} } function applyFontSize(step) { root.style.fontSize = fontSizeSteps[step] + "%"; } function applyInversion(on) { if (on) { root.classList.add("a11y-inverted"); } else { root.classList.remove("a11y-inverted"); } } function applyContrast(on) { root.classList.toggle("a11y-high-contrast", on); // Lazy-load the high-contrast stylesheet var hcId = "mokoA11yHcSheet"; var existing = doc.getElementById(hcId); if (on && !existing) { var link = doc.createElement("link"); link.id = hcId; link.rel = "stylesheet"; link.href = (doc.querySelector('link[href*="mokocassiopeia/css/template"]') || {}).href ? (doc.querySelector('link[href*="mokocassiopeia/css/template"]').href.replace(/\/css\/template[^/]*$/, "/css/a11y-high-contrast.css")) : "media/templates/site/mokocassiopeia/css/a11y-high-contrast.css"; doc.head.appendChild(link); } } function applyLinks(on) { root.classList.toggle("a11y-highlight-links", on); } function applyFont(on) { root.classList.toggle("a11y-readable-font", on); } function applyPaused(on) { root.classList.toggle("a11y-pause-animations", on); } /** Create a Font Awesome icon element (safe DOM, no innerHTML). */ function faIcon(classes) { var i = doc.createElement("i"); i.className = classes; i.setAttribute("aria-hidden", "true"); return i; } function buildA11yToolbar() { if (doc.getElementById("mokoA11yToolbar")) return; var body = doc.body; var showResize = body.getAttribute("data-a11y-resize") === "1"; var showInvert = body.getAttribute("data-a11y-invert") === "1"; var showContrast = body.getAttribute("data-a11y-contrast") === "1"; var showLinks = body.getAttribute("data-a11y-links") === "1"; var showFont = body.getAttribute("data-a11y-font") === "1"; var showAnimations = body.getAttribute("data-a11y-animations") === "1"; var pos = (body.getAttribute("data-a11y-pos") || "tl").toLowerCase(); if (!/^(br|bl|tr|tl)$/.test(pos)) pos = "tl"; var prefs = getA11yPrefs(); // Container var toolbar = doc.createElement("div"); toolbar.id = "mokoA11yToolbar"; toolbar.className = "a11y-pos-" + pos; toolbar.setAttribute("role", "toolbar"); toolbar.setAttribute("aria-label", "Accessibility options"); // Toggle button (accessibility icon) var toggle = doc.createElement("button"); toggle.type = "button"; toggle.id = "mokoA11yToggle"; toggle.className = "a11y-toggle"; toggle.setAttribute("aria-label", "Accessibility options"); toggle.setAttribute("aria-expanded", "false"); var a11yIcon = faIcon("fa-solid fa-universal-access"); // Unicode fallback if FA7 glyph doesn't render (e.g. FA6/FA7 conflict) setTimeout(function () { var cs = win.getComputedStyle(a11yIcon, "::before"); if (!cs.content || cs.content === "none" || cs.content === '""' || cs.content === '"" / ""') { a11yIcon.className = ""; a11yIcon.textContent = "\u267F"; a11yIcon.style.fontSize = "1.1rem"; } }, 500); toggle.appendChild(a11yIcon); // Panel var panel = doc.createElement("div"); panel.id = "mokoA11yPanel"; panel.className = "a11y-panel"; panel.hidden = true; // --- Text resize controls --- if (showResize) { var resizeGroup = doc.createElement("div"); resizeGroup.className = "a11y-group"; resizeGroup.setAttribute("role", "group"); resizeGroup.setAttribute("aria-label", "Text size"); var sizeLabel = doc.createElement("span"); sizeLabel.className = "a11y-group-label"; sizeLabel.textContent = "Text size"; resizeGroup.appendChild(sizeLabel); var btnRow = doc.createElement("div"); btnRow.className = "a11y-btn-row"; var btnMinus = doc.createElement("button"); btnMinus.type = "button"; btnMinus.className = "a11y-btn"; btnMinus.setAttribute("aria-label", "Decrease text size"); btnMinus.appendChild(faIcon("fa-solid fa-minus")); var sizeDisplay = doc.createElement("span"); sizeDisplay.className = "a11y-size-display"; sizeDisplay.setAttribute("aria-live", "polite"); sizeDisplay.textContent = fontSizeSteps[prefs.fontStep] + "%"; var btnPlus = doc.createElement("button"); btnPlus.type = "button"; btnPlus.className = "a11y-btn"; btnPlus.setAttribute("aria-label", "Increase text size"); btnPlus.appendChild(faIcon("fa-solid fa-plus")); var btnReset = doc.createElement("button"); btnReset.type = "button"; btnReset.className = "a11y-btn a11y-btn-reset"; btnReset.setAttribute("aria-label", "Reset text size"); btnReset.appendChild(faIcon("fa-solid fa-rotate-left")); btnMinus.addEventListener("click", function () { if (prefs.fontStep > 0) { prefs.fontStep--; applyFontSize(prefs.fontStep); sizeDisplay.textContent = fontSizeSteps[prefs.fontStep] + "%"; saveA11yPrefs(prefs); } }); btnPlus.addEventListener("click", function () { if (prefs.fontStep < fontSizeSteps.length - 1) { prefs.fontStep++; applyFontSize(prefs.fontStep); sizeDisplay.textContent = fontSizeSteps[prefs.fontStep] + "%"; saveA11yPrefs(prefs); } }); btnReset.addEventListener("click", function () { prefs.fontStep = defaultStep; applyFontSize(prefs.fontStep); sizeDisplay.textContent = fontSizeSteps[prefs.fontStep] + "%"; saveA11yPrefs(prefs); }); btnRow.appendChild(btnMinus); btnRow.appendChild(sizeDisplay); btnRow.appendChild(btnPlus); btnRow.appendChild(btnReset); resizeGroup.appendChild(btnRow); panel.appendChild(resizeGroup); } // --- Helper: build a switch-style toggle button --- function addSwitchOption(show, prefKey, icon, label, applyFn) { if (!show) return; var group = doc.createElement("div"); group.className = "a11y-group"; var btn = doc.createElement("button"); btn.type = "button"; btn.className = "a11y-btn a11y-btn-wide"; btn.setAttribute("role", "switch"); btn.setAttribute("aria-checked", prefs[prefKey] ? "true" : "false"); btn.setAttribute("aria-label", label); btn.appendChild(faIcon(icon)); btn.appendChild(doc.createTextNode(" " + label)); if (prefs[prefKey]) btn.classList.add("active"); btn.addEventListener("click", function () { prefs[prefKey] = !prefs[prefKey]; applyFn(prefs[prefKey]); btn.setAttribute("aria-checked", prefs[prefKey] ? "true" : "false"); btn.classList.toggle("active", prefs[prefKey]); saveA11yPrefs(prefs); }); group.appendChild(btn); panel.appendChild(group); } // --- Toggle options --- addSwitchOption(showInvert, "inverted", "fa-solid fa-circle-half-stroke", "Invert colors", applyInversion); addSwitchOption(showContrast, "contrast", "fa-solid fa-adjust", "High contrast", applyContrast); addSwitchOption(showLinks, "links", "fa-solid fa-link", "Highlight links", applyLinks); addSwitchOption(showFont, "font", "fa-solid fa-font", "Readable font", applyFont); addSwitchOption(showAnimations, "paused", "fa-solid fa-pause", "Pause animations", applyPaused); // Apply saved preferences on load if (prefs.fontStep !== defaultStep) applyFontSize(prefs.fontStep); if (prefs.inverted) applyInversion(true); if (prefs.contrast) applyContrast(true); if (prefs.links) applyLinks(true); if (prefs.font) applyFont(true); if (prefs.paused) applyPaused(true); // If theme FAB is present, mount a11y toggle inside it; otherwise standalone var fabSlot = doc.getElementById("mokoA11ySlot"); if (fabSlot) { toggle.className = "a11y-toggle a11y-toggle-inline"; fabSlot.appendChild(toggle); toolbar.className = "a11y-toolbar-floating"; toolbar.appendChild(panel); body.appendChild(toolbar); // Position panel near the FAB toggle.addEventListener("click", function () { var isOpen = !panel.hidden; panel.hidden = isOpen; toggle.setAttribute("aria-expanded", isOpen ? "false" : "true"); toggle.classList.toggle("active", !isOpen); if (!isOpen) { var rect = toggle.getBoundingClientRect(); toolbar.style.position = "fixed"; toolbar.style.bottom = (win.innerHeight - rect.top + 8) + "px"; toolbar.style.right = (win.innerWidth - rect.right) + "px"; toolbar.style.zIndex = "1202"; } }); // Close on outside click for inline mode doc.addEventListener("click", function (e) { if (!toggle.contains(e.target) && !toolbar.contains(e.target) && !panel.hidden) { panel.hidden = true; toggle.setAttribute("aria-expanded", "false"); toggle.classList.remove("active"); } }); } else { // Standalone mode — toggle and close handlers toggle.addEventListener("click", function () { var isOpen = !panel.hidden; panel.hidden = isOpen; toggle.setAttribute("aria-expanded", isOpen ? "false" : "true"); toggle.classList.toggle("active", !isOpen); }); doc.addEventListener("click", function (e) { if (!toolbar.contains(e.target) && !panel.hidden) { panel.hidden = true; toggle.setAttribute("aria-expanded", "false"); toggle.classList.remove("active"); } }); toolbar.appendChild(toggle); toolbar.appendChild(panel); body.appendChild(toolbar); } } // ======================================================================== // TEMPLATE UTILITIES // ======================================================================== /** * Utility: smooth scroll to top */ function backToTop() { win.scrollTo({ top: 0, behavior: "smooth" }); } /** * Utility: toggle body class on scroll for sticky header styling */ function handleScroll() { if (win.scrollY > 50) { doc.body.classList.add("scrolled"); } else { doc.body.classList.remove("scrolled"); } } /** * Initialize offcanvas drawer buttons for left/right drawers. * Bootstrap handles drawers automatically via data-bs-toggle="offcanvas" * This function is kept for backwards compatibility but only runs if drawers exist. */ function initDrawers() { // Check if any drawer buttons exist before initializing var hasDrawers = doc.querySelector(".drawer-toggle-left") || doc.querySelector(".drawer-toggle-right"); if (!hasDrawers) { return; // No drawers, skip initialization } // Bootstrap 5 handles offcanvas automatically via data-bs-toggle attribute // No manual initialization needed if Bootstrap is loaded correctly // The buttons already have data-bs-toggle="offcanvas" and data-bs-target="#drawer-*" } /** * Initialize back-to-top link if present */ function initBackTop() { var backTop = doc.getElementById("back-top"); if (backTop) { backTop.addEventListener("click", function (e) { e.preventDefault(); backToTop(); }); } } /** * Initialize theme based on stored preference or system setting */ function initTheme() { var stored = getStored(); var theme = stored ? stored : systemTheme(); applyTheme(theme); // Listen for system changes only if Auto mode (no stored) var onChange = function () { if (!getStored()) { applyTheme(systemTheme()); } }; if (typeof mql.addEventListener === "function") { mql.addEventListener("change", onChange); } else if (typeof mql.addListener === "function") { mql.addListener(onChange); } // Hook toggle UI if present (for inline switch, not FAB) var switchEl = doc.getElementById("themeSwitch"); var autoBtn = doc.getElementById("themeAuto"); if (switchEl) { switchEl.checked = (theme === "dark"); switchEl.addEventListener("change", function () { var choice = switchEl.checked ? "dark" : "light"; applyTheme(choice); }); } if (autoBtn) { autoBtn.addEventListener("click", function () { clearStored(); applyTheme(systemTheme()); }); } } /** * Check if theme FAB should be enabled based on body data attribute */ function shouldEnableThemeFab() { return doc.body.getAttribute('data-theme-fab-enabled') === '1'; } /** * Convert sidebar card modules into accordion on mobile. * On screens <= 991px each card collapses; on desktop they revert. */ function initSidebarAccordion() { var BREAKPOINT = 992; var sidebars = doc.querySelectorAll(".container-sidebar-left, .container-sidebar-right"); if (!sidebars.length) return; // Build accordion structure once — works at all breakpoints sidebars.forEach(function (sidebar, si) { var accId = "sidebarAcc-" + si; sidebar.setAttribute("id", accId); sidebar.classList.add("accordion"); var isMobile = win.innerWidth < BREAKPOINT; var cards = sidebar.querySelectorAll(":scope > .card"); cards.forEach(function (card, ci) { var collapseId = accId + "-c" + ci; card.classList.add("accordion-item"); var header = card.querySelector(".card-header"); var body = card.querySelector(".card-body"); if (!header || !body) return; // Turn header into accordion button header.classList.add("accordion-header"); var btn = doc.createElement("button"); btn.className = isMobile ? "accordion-button collapsed" : "accordion-button"; btn.type = "button"; btn.setAttribute("data-bs-toggle", "collapse"); btn.setAttribute("data-bs-target", "#" + collapseId); btn.setAttribute("aria-expanded", isMobile ? "false" : "true"); btn.setAttribute("aria-controls", collapseId); btn.textContent = header.textContent; header.textContent = ""; header.appendChild(btn); // Wrap body in collapse var wrapper = doc.createElement("div"); wrapper.id = collapseId; wrapper.className = isMobile ? "accordion-collapse collapse" : "accordion-collapse collapse show"; wrapper.setAttribute("data-bs-parent", "#" + accId); card.insertBefore(wrapper, body); wrapper.appendChild(body); body.classList.add("accordion-body"); }); }); } /** * Toggle search on mobile via .show class */ function initSearchToggle() { var btn = doc.querySelector(".search-toggler"); var target = doc.getElementById("headerSearchCollapse"); if (!btn || !target) return; btn.addEventListener("click", function () { var isOpen = target.classList.toggle("show"); btn.setAttribute("aria-expanded", isOpen ? "true" : "false"); }); } // ======================================================================== // CSS VARIABLE CLICK-TO-COPY // ======================================================================== /** * Inject toast + variable-chip styles once. */ function injectVarCopyStyles() { if (doc.getElementById("moko-var-copy-styles")) return; var style = doc.createElement("style"); style.id = "moko-var-copy-styles"; style.textContent = ".moko-var-chip{cursor:pointer;font-family:var(--font-monospace,monospace);font-size:.875em;" + "background:var(--secondary-bg,#151b22);color:var(--link-color,#8ab4f8);" + "border:1px solid var(--border-color,#2b323b);border-radius:.25rem;padding:.1em .4em;" + "transition:background .15s,border-color .15s;white-space:nowrap;display:inline}" + ".moko-var-chip:hover{background:var(--color-primary,#112855);color:#fff;border-color:var(--color-primary,#112855)}" + ".moko-toast{position:fixed;bottom:1.5rem;left:50%;transform:translateX(-50%);z-index:10000;" + "background:var(--color-primary,#112855);color:#fff;padding:.6rem 1.25rem;" + "border-radius:.375rem;font-size:.875rem;box-shadow:0 4px 12px rgba(0,0,0,.25);" + "opacity:0;transition:opacity .2s;pointer-events:none}" + ".moko-toast--show{opacity:1}"; doc.head.appendChild(style); } /** * Show a brief "Copied to clipboard" toast. * @param {string} text - The variable name that was copied */ function showCopyToast(text) { var existing = doc.querySelector(".moko-toast"); if (existing) existing.remove(); var toast = doc.createElement("div"); toast.className = "moko-toast"; toast.textContent = "Copied to clipboard: " + text; doc.body.appendChild(toast); // Trigger reflow then show void toast.offsetWidth; toast.classList.add("moko-toast--show"); setTimeout(function () { toast.classList.remove("moko-toast--show"); setTimeout(function () { toast.remove(); }, 200); }, 2000); } /** * Copy text to clipboard and show toast. * @param {string} text */ function copyVariable(text) { if (navigator.clipboard && navigator.clipboard.writeText) { navigator.clipboard.writeText(text).then(function () { showCopyToast(text); }); } else { // Fallback for older browsers using deprecated API var ta = doc.createElement("textarea"); ta.value = text; ta.style.cssText = "position:fixed;left:-9999px"; doc.body.appendChild(ta); ta.select(); try { doc.execCommand("copy"); } catch (e) { /* noop */ } ta.remove(); showCopyToast(text); } } /** * Scan text nodes for CSS variable patterns (--variable-name) and wrap * each match in a clickable chip that copies the variable to clipboard. */ function initVarCopy() { injectVarCopyStyles(); // Pattern: --[a-zA-Z] followed by word/hyphen chars var varPattern = /--[a-zA-Z][\w-]*/g; // Elements to skip (inputs, scripts, styles, already-processed, code editors) var SKIP_TAGS = { SCRIPT: 1, STYLE: 1, TEXTAREA: 1, INPUT: 1, SELECT: 1, NOSCRIPT: 1 }; var walker = doc.createTreeWalker( doc.body, NodeFilter.SHOW_TEXT, { acceptNode: function (node) { if (SKIP_TAGS[node.parentNode.tagName]) return NodeFilter.FILTER_REJECT; if (node.parentNode.classList && node.parentNode.classList.contains("moko-var-chip")) return NodeFilter.FILTER_REJECT; if (!varPattern.test(node.nodeValue)) return NodeFilter.FILTER_REJECT; varPattern.lastIndex = 0; return NodeFilter.FILTER_ACCEPT; } } ); var textNodes = []; while (walker.nextNode()) textNodes.push(walker.currentNode); textNodes.forEach(function (node) { var text = node.nodeValue; var frag = doc.createDocumentFragment(); var lastIndex = 0; var match; varPattern.lastIndex = 0; while ((match = varPattern.exec(text)) !== null) { // Text before the match if (match.index > lastIndex) { frag.appendChild(doc.createTextNode(text.slice(lastIndex, match.index))); } // Clickable chip var chip = doc.createElement("span"); chip.className = "moko-var-chip"; chip.textContent = match[0]; chip.setAttribute("role", "button"); chip.setAttribute("tabindex", "0"); chip.setAttribute("title", "Click to copy " + match[0]); chip.addEventListener("click", (function (varName) { return function (e) { e.preventDefault(); copyVariable(varName); }; })(match[0])); chip.addEventListener("keydown", (function (varName) { return function (e) { if (e.key === "Enter" || e.key === " ") { e.preventDefault(); copyVariable(varName); } }; })(match[0])); frag.appendChild(chip); lastIndex = match.index + match[0].length; } // Remaining text after last match if (lastIndex < text.length) { frag.appendChild(doc.createTextNode(text.slice(lastIndex))); } node.parentNode.replaceChild(frag, node); }); } /** * Run all template JS initializations */ function init() { // Initialize theme first initTheme(); // Build floating theme toggle if enabled if (shouldEnableThemeFab()) { buildThemeToggle(); } // Build accessibility toolbar if enabled if (doc.body.getAttribute("data-a11y-toolbar") === "1") { buildA11yToolbar(); } // Sticky header behavior handleScroll(); win.addEventListener("scroll", handleScroll); // Init features initDrawers(); initBackTop(); initSearchToggle(); initSidebarAccordion(); initVarCopy(); } if (doc.readyState === "loading") { doc.addEventListener("DOMContentLoaded", init); } else { init(); } })(window, document);