Files
2026-09-21 17:59:29 +02:00

152 lines
4.4 KiB
JavaScript

/*
* CTAO portal mockup: header behaviour
*
* 1. The sticky header turns solid Galaxy Blue after 40px of scroll.
* 2. Submenus: every button with data-submenu-toggle (Example, language)
* toggles the element named in its aria-controls. One open at a time.
* 3. The search button toggles the search panel.
* 4. The burger button toggles the main nav on small screens.
* 5. Escape and clicks outside the header close open menus.
*
* Review helper: index.html#open=<id> opens a panel on load,
* for example #open=submenu-example, #open=submenu-lang,
* #open=search-panel or #open=site-nav.
*/
(function () {
"use strict";
// Keep in sync with the small-screen media query in css/styles.css
const MOBILE_QUERY = window.matchMedia("(max-width: 1079px)");
const header = document.getElementById("site-header");
const nav = document.getElementById("site-nav");
const searchPanel = document.getElementById("search-panel");
const searchButton = header.querySelector('[aria-controls="search-panel"]');
const menuButton = header.querySelector('[aria-controls="site-nav"]');
const submenuButtons = Array.from(
header.querySelectorAll("[data-submenu-toggle]"),
);
/* 1. Scrolled state */
function updateScrolled() {
header.classList.toggle("is-scrolled", window.scrollY > 40);
}
window.addEventListener("scroll", updateScrolled, { passive: true });
updateScrolled();
/* Keeps the header solid while a full-width panel is open */
function updateHeaderState() {
const panelOpen =
!searchPanel.hidden || header.classList.contains("is-nav-open");
header.classList.toggle("is-open", panelOpen);
}
/* 2. Submenus */
function setSubmenu(button, open) {
const submenu = document.getElementById(
button.getAttribute("aria-controls"),
);
button.setAttribute("aria-expanded", String(open));
submenu.hidden = !open;
}
function closeSubmenus(except) {
submenuButtons.forEach(function (button) {
if (button !== except) setSubmenu(button, false);
});
}
submenuButtons.forEach(function (button) {
button.addEventListener("click", function () {
const open = button.getAttribute("aria-expanded") !== "true";
closeSubmenus(button);
setSearch(false);
setSubmenu(button, open);
});
});
/* 3. Search */
function setSearch(open) {
searchButton.setAttribute("aria-expanded", String(open));
searchPanel.hidden = !open;
updateHeaderState();
if (open) searchPanel.querySelector("input").focus();
}
searchButton.addEventListener("click", function () {
const open = searchPanel.hidden;
closeSubmenus();
setMobileNav(false);
setSearch(open);
});
/* 4. Mobile nav */
function setMobileNav(open) {
menuButton.setAttribute("aria-expanded", String(open));
header.classList.toggle("is-nav-open", open);
document.body.classList.toggle("no-scroll", open);
updateHeaderState();
}
menuButton.addEventListener("click", function () {
const open = !header.classList.contains("is-nav-open");
closeSubmenus();
setSearch(false);
setMobileNav(open);
});
/* 5. Dismiss */
function closeAll() {
closeSubmenus();
setSearch(false);
setMobileNav(false);
}
document.addEventListener("click", function (event) {
if (!header.contains(event.target)) closeAll();
});
document.addEventListener("keydown", function (event) {
if (event.key !== "Escape") return;
const openSubmenuButton = submenuButtons.find(function (button) {
return button.getAttribute("aria-expanded") === "true";
});
if (openSubmenuButton) {
setSubmenu(openSubmenuButton, false);
openSubmenuButton.focus();
} else if (!searchPanel.hidden) {
setSearch(false);
searchButton.focus();
} else if (header.classList.contains("is-nav-open")) {
setMobileNav(false);
menuButton.focus();
}
});
/* Switching between desktop and small-screen layouts starts clean */
MOBILE_QUERY.addEventListener("change", closeAll);
/* Review helpers */
const openMatch = location.hash.match(/^#open=([\w-]+)$/);
if (openMatch) {
const target = header.querySelector(
'[aria-controls="' + openMatch[1] + '"]',
);
if (target) {
if (MOBILE_QUERY.matches && nav.contains(target)) menuButton.click();
target.click();
}
}
if (location.hash === "#scrolled") window.scrollTo(0, 420);
})();