diff --git a/_includes/plain.njk b/_includes/plain.njk
index 1a374af36..3f67b729a 100644
--- a/_includes/plain.njk
+++ b/_includes/plain.njk
@@ -13,6 +13,7 @@
+
{% if has_mavo %}
diff --git a/assets/css/style.css b/assets/css/style.css
index a88c33e85..37c1da2cd 100644
--- a/assets/css/style.css
+++ b/assets/css/style.css
@@ -197,7 +197,7 @@ body > footer {
animation: var(--rainbow-scroll);
& nav {
- & > .menu {
+ & > :where(.menu, .hamburger-menu) {
flex: 1;
display: flex;
position: relative;
@@ -224,13 +224,14 @@ body > footer {
}
}
- & a {
+ & a:where(:not([hidden])) {
display: block;
text-align: center;
}
& > .menu > a,
- & a:not(.logo) {
+ & a:not(.logo),
+ & .hamburger-button {
flex: 1;
padding: .6em;
font-weight: 800;
@@ -243,6 +244,33 @@ body > footer {
text-decoration: none;
}
}
+
+ .hamburger-menu {
+ &:has(li:last-of-type[hidden]) {
+ /* All menu items are hidden; hide the menu */
+ display: none;
+ }
+
+ & .hamburger-button {
+ display: grid;
+ place-items: center;
+ border: none;
+ cursor: default;
+ text-align: center;
+ color: hsl(var(--gray) 40%);
+
+ > svg {
+ width: 100%;
+ }
+ }
+
+ & .hamburger-list {
+ min-inline-size: 15ch;
+ right: 0;
+ top: 100%;
+ transform-origin: top right;
+ }
+ }
}
}
@@ -295,6 +323,7 @@ body > header {
& img {
height: 2.15em;
+ min-width: 1.3em;
margin-bottom: -1.5em;
margin-left: -.3em;
}
@@ -426,7 +455,7 @@ pre[class*="language-"] {
}
@supports (-webkit-background-clip: text) and (not (-moz-margin-start: 0)) {
- body > header nav a,
+ body > header nav :where(a, .hamburger-button),
body > footer nav a,
main h2,
main h2 > a {
diff --git a/assets/js/nav.js b/assets/js/nav.js
new file mode 100644
index 000000000..b38df04ec
--- /dev/null
+++ b/assets/js/nav.js
@@ -0,0 +1,77 @@
+/**
+ * Responsive Navigation Script
+ * Dynamically hides navigation items that don't fit on screen in the main nav
+ * and shows them in the hamburger menu instead. Uses ResizeObserver to monitor
+ * the nav element and checks items from right to left to determine which ones
+ * overflow. Items are matched between nav and hamburger menu using data-nav-item
+ * attributes.
+ */
+
+const nav = document.querySelector("header nav");
+const menu = nav.querySelector(".hamburger-menu");
+const [menuButton, menuList] = menu.children;
+
+// Map to track nav items and their hamburger menu counterparts
+let itemMap = new Map();
+
+// Get all nav items (excluding the hamburger menu and the ones that are supposed to be shown in the footer)
+let navItems = [...nav.children].filter(
+ child => !child.classList.contains("hamburger-menu") && !child.classList.contains("footer"),
+);
+
+let hamburgerMenuItems = [...menuList.children];
+for (let navItem of navItems) {
+ let index = navItem.dataset.navItem;
+ let hamburgerItem = hamburgerMenuItems.find(item => item.dataset.navItem === index);
+ if (hamburgerItem) {
+ itemMap.set(navItem, hamburgerItem);
+ }
+}
+
+const resizeObserver = new ResizeObserver(checkFit);
+resizeObserver.observe(nav);
+
+function checkFit () {
+ // Reset: show all items in nav, hide all in hamburger
+ itemMap.forEach((hamburgerItem, navItem) => {
+ navItem.hidden = false;
+ hamburgerItem.hidden = true;
+ });
+
+ // Temporarily show hamburger menu to measure its width
+ menu.style.setProperty("display", "block");
+
+ let hamburgerButtonWidth = menuButton.offsetWidth ?? 50;
+ let navRect = nav.getBoundingClientRect();
+ let navRight = navRect.right;
+ let availableRight = navRight - hamburgerButtonWidth;
+
+ let items = [...itemMap.keys()];
+ let toHide = [];
+
+ // Check each item from right to left to see which ones overflow
+ // by comparing their right edge to available space
+ for (let i = items.length - 1; i >= 0; i--) {
+ const item = items[i];
+ const itemRect = item.getBoundingClientRect();
+ const itemRight = itemRect.right;
+ // If this item's right edge exceeds available space, hide it
+ if (itemRight > availableRight) {
+ toHide.push(item);
+ }
+ else {
+ // Once we find an item that fits, we can stop
+ break;
+ }
+ }
+
+ menu.style.removeProperty("display");
+
+ // Hide items in nav and show corresponding items in hamburger menu
+ for (let navItem of toHide) {
+ let hamburgerItem = itemMap.get(navItem);
+
+ navItem.hidden = true;
+ hamburgerItem.hidden = false;
+ }
+}