# Fixed Underlay Navigation

> Page content slides aside to reveal a fixed underlay menu — layered navigation with dim overlay and staggered link reveals.

**Definition:** Fixed Underlay Navigation is a navigation UI animation from collections.html. It is built with HTML, CSS, JavaScript, and GSAP, ships with a live preview, and is free to copy into personal and commercial projects.

## Facts
- Category: Navigation
- Stack: HTML, CSS, JavaScript, and GSAP
- Added: 2026-08-02
- Canonical page: https://html.bosconvertpro.com/collection/fixed-underlay-navigation
- Full-page preview: https://html.bosconvertpro.com/collection/fixed-underlay-navigation/full
- Live demo: https://html.bosconvertpro.com/demos/fixed-underlay-navigation/index.html
- Markdown mirror: https://html.bosconvertpro.com/collection/fixed-underlay-navigation/index.md
- License: https://html.bosconvertpro.com/terms

## How to use
1. Open https://html.bosconvertpro.com/collection/fixed-underlay-navigation/full to preview the animation in full screen.
2. Copy index.html, styles.css, and script.js from the code tabs on https://html.bosconvertpro.com/collection/fixed-underlay-navigation.
3. Paste the files into your project and adjust timing, easing, and content as needed.

## FAQ
### What is Fixed Underlay Navigation?

Fixed Underlay Navigation is a navigation UI animation from collections.html. Page content slides aside to reveal a fixed underlay menu — layered navigation with dim overlay and staggered link reveals.

### What is Fixed Underlay Navigation built with?

Fixed Underlay Navigation is built with HTML, CSS, JavaScript, and GSAP. The source for every file is shown on the animation page and can be copied straight into your project.

### How do I use Fixed Underlay Navigation in my project?

Open the animation page (https://html.bosconvertpro.com/collection/fixed-underlay-navigation), preview it live, then copy index.html, styles.css, and script.js from the code tabs. Paste the files into your project and adjust timing, easing, and content as needed. A standalone demo runs at https://html.bosconvertpro.com/demos/fixed-underlay-navigation/index.html.

### Is Fixed Underlay Navigation free to use?

Yes. The Fixed Underlay Navigation demo code may be used in personal and commercial projects. You may not resell or redistribute the collection itself as a competing template pack. Full terms: https://html.bosconvertpro.com/terms.

## Source — index.html
````html
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Fixed Underlay Navigation</title>
    <link rel="stylesheet" href="styles.css" />
  </head>
  <body>
    <!-- Fixed underlay navigation (sits behind page content) -->
    <div class="fun" id="fun">
      <header class="fun__header">
        <div class="fun__bar">
          <div class="fun__container">
            <a href="#" class="fun__logo" aria-label="ABBOS home">
              <span class="fun__logo-mark" aria-hidden="true">✳</span>
              <span class="fun__logo-text">ABBOS</span>
            </a>

            <button
              type="button"
              class="fun__toggle"
              data-fun-toggle
              aria-expanded="false"
              aria-controls="fun-menu"
              aria-label="Open menu"
            >
              <span class="fun__toggle-text">
                <span class="fun__toggle-track" data-fun-toggle-track>
                  <span class="fun__toggle-label">Menu</span>
                  <span class="fun__toggle-label">Close</span>
                </span>
              </span>
              <span class="fun__toggle-icon" aria-hidden="true">
                <span class="fun__toggle-bar"></span>
                <span class="fun__toggle-bar"></span>
              </span>
            </button>
          </div>
        </div>
      </header>

      <nav
        class="fun__menu"
        id="fun-menu"
        data-fun-menu
        aria-label="Primary"
      >
        <div class="fun__inner">
          <ul class="fun__list" role="list">
            <li data-reveal-l>
              <a href="#studio" class="fun__link-large is-current" aria-current="page">
                <span class="fun__link-label">Studio</span>
              </a>
            </li>
            <li data-reveal-l>
              <a href="#work" class="fun__link-large">
                <span class="fun__link-label">Work</span>
              </a>
            </li>
            <li data-reveal-l>
              <a href="#process" class="fun__link-large">
                <span class="fun__link-label">Process</span>
              </a>
            </li>
            <li data-reveal-l>
              <a href="#journal" class="fun__link-large">
                <span class="fun__link-label">Journal</span>
              </a>
            </li>
            <li data-reveal-l>
              <a href="#careers" class="fun__link-large">
                <span class="fun__link-label">Careers</span>
              </a>
            </li>
            <li data-reveal-l>
              <a href="#contact" class="fun__link-large">
                <span class="fun__link-label">Contact</span>
              </a>
            </li>
          </ul>

          <div class="fun__bottom">
            <div class="fun__bottom-col">
              <div data-reveal-s>
                <span class="fun__link-small is-faded">Socials</span>
              </div>
              <ul class="fun__list is-small" role="list">
                <li data-reveal-s>
                  <a href="https://instagram.com" class="fun__link-small" target="_blank" rel="noopener">Instagram</a>
                </li>
                <li data-reveal-s>
                  <a href="https://linkedin.com" class="fun__link-small" target="_blank" rel="noopener">LinkedIn</a>
                </li>
                <li data-reveal-s>
                  <a href="https://x.com" class="fun__link-small" target="_blank" rel="noopener">X / Twitter</a>
                </li>
              </ul>
            </div>

            <div class="fun__bottom-col">
              <div data-reveal-s>
                <span class="fun__link-small is-faded">Quick Links</span>
              </div>
              <ul class="fun__list is-small" role="list">
                <li data-reveal-s>
                  <a href="#privacy" class="fun__link-small">Privacy Policy ↗</a>
                </li>
                <li data-reveal-s>
                  <a href="#terms" class="fun__link-small">Terms & Conditions ↗</a>
                </li>
              </ul>
            </div>

            <div class="fun__bottom-border" data-fun-border></div>
          </div>
        </div>
      </nav>

      <!-- Overlay rides with main content: dim + decorative edge -->
      <div class="fun__overlay" data-fun-overlay>
        <div class="fun__dark" data-fun-dark></div>
        <div class="fun__borders">
          <div class="fun__border-row" data-fun-border-row>
            <div class="fun__border"></div>
            <div class="fun__corner" data-fun-corner></div>
          </div>
          <div class="fun__border-row" data-fun-border-row>
            <div class="fun__corner is-bottom" data-fun-corner></div>
            <div class="fun__border"></div>
          </div>
        </div>
      </div>
    </div>

    <!-- Page content — slides left to reveal the underlay menu -->
    <main class="main" data-main>
      <section class="hero" id="studio" aria-label="Hero">
        <div class="hero__media" aria-hidden="true">
          <img
            class="hero__img"
            src="https://images.unsplash.com/photo-1618005182384-a83a8bd57fbe?auto=format&fit=crop&w=2400&q=80"
            alt=""
            width="2400"
            height="1600"
            decoding="async"
            fetchpriority="high"
          />
          <div class="hero__veil"></div>
        </div>

        <div class="hero__content">
          <p class="hero__eyebrow">Creative Direction · Digital Craft</p>
          <h1 class="hero__title">
            Fixed Underlay<br />
            Navigation
          </h1>
          <p class="hero__lede">
            Open the menu — page content slides away and the navigation
            is revealed underneath.
          </p>
        </div>

        <div class="hero__mark" aria-hidden="true">✳</div>
      </section>
    </main>

    <script src="https://cdn.jsdelivr.net/npm/gsap@3.13.0/dist/gsap.min.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/gsap@3.13.0/dist/CustomEase.min.js"></script>
    <script src="script.js"></script>
  </body>
</html>
````

## Source — styles.css
````css
/* ==========================================================================
   Fixed Underlay Navigation
   Menu lives fixed under the page. Opening slides [data-main] left to reveal it.
   ========================================================================== */

@font-face {
  font-family: "Haffer VF";
  src: url("https://cdn.prod.website-files.com/68a5787bba0829184628bd51/68a5df2de5b7c113032b4047_HafferStandardUprights-VF.ttf")
    format("truetype");
  font-weight: 100 1000;
  font-style: normal;
  font-display: swap;
}

:root {
  --font: "Haffer VF", "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI",
    system-ui, sans-serif;

  --menu-width: 30em;

  --ink: #141414;
  --ink-soft: #2a2a2a;
  --muted: #8a8a8a;
  --paper: #f7f6f4;
  --menu-bg: #ffffff;
  --accent: #e8462f;
  --accent-text: #f5f0ee;
  --header-on-dark: #f4f4f4;
  --header-on-menu: #1a1a1a;
  --dim: rgba(0, 0, 0, 0.38);
  --edge: #ffffff;

  --ease-out: cubic-bezier(0.32, 0.72, 0, 1);
  --pad: clamp(1.25rem, 3.5vw, 2.5rem);
}

*,
*::before,
*::after {
  box-sizing: border-box;
  margin: 0;
  padding: 0;
}

html {
  height: 100%;
  -webkit-text-size-adjust: 100%;
}

body {
  min-height: 100%;
  font-family: var(--font);
  font-weight: 450;
  color: var(--ink);
  background: var(--paper);
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  overflow-x: hidden;
}

body[data-menu-status="open"] {
  overflow: hidden;
}

a {
  color: inherit;
  text-decoration: none;
}

button {
  font: inherit;
  color: inherit;
  border: 0;
  background: none;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
}

ul {
  list-style: none;
}

img {
  display: block;
  max-width: 100%;
  height: auto;
}

/* --------------------------------------------------------------------------
   Page content layer (covers the underlay menu)
   -------------------------------------------------------------------------- */

.main {
  position: relative;
  z-index: 2;
  min-height: 100vh;
  min-height: 100dvh;
  background: #0c0c0c;
  will-change: transform;
}

/* --------------------------------------------------------------------------
   Hero — full viewport
   -------------------------------------------------------------------------- */

.hero {
  position: relative;
  display: grid;
  place-items: center;
  min-height: 100vh;
  min-height: 100dvh;
  overflow: hidden;
  color: var(--header-on-dark);
}

.hero__media {
  position: absolute;
  inset: 0;
  z-index: 0;
}

.hero__img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: center;
  transform: scale(1.02);
  user-select: none;
  -webkit-user-drag: none;
}

.hero__veil {
  position: absolute;
  inset: 0;
  background:
    linear-gradient(
      180deg,
      rgba(0, 0, 0, 0.42) 0%,
      rgba(0, 0, 0, 0.18) 38%,
      rgba(0, 0, 0, 0.45) 100%
    ),
    radial-gradient(
      ellipse 70% 55% at 50% 55%,
      transparent 20%,
      rgba(0, 0, 0, 0.35) 100%
    );
  pointer-events: none;
}

.hero__content {
  position: relative;
  z-index: 1;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 1.1rem;
  padding: calc(var(--pad) + 4rem) var(--pad) var(--pad);
  text-align: center;
  max-width: 48rem;
}

.hero__eyebrow {
  font-size: clamp(0.75rem, 1.1vw, 0.875rem);
  font-weight: 500;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: rgba(255, 255, 255, 0.62);
}

.hero__title {
  font-size: clamp(2.75rem, 8.5vw, 6.5rem);
  font-weight: 520;
  letter-spacing: -0.045em;
  line-height: 0.92;
  color: rgba(255, 255, 255, 0.88);
  text-wrap: balance;
}

.hero__lede {
  max-width: 28rem;
  margin-top: 0.35rem;
  font-size: clamp(0.95rem, 1.4vw, 1.125rem);
  font-weight: 420;
  line-height: 1.5;
  color: rgba(255, 255, 255, 0.55);
  text-wrap: pretty;
}

.hero__mark {
  position: absolute;
  z-index: 1;
  bottom: var(--pad);
  left: 50%;
  transform: translateX(-50%);
  font-size: 1.35rem;
  color: var(--accent);
  line-height: 1;
  pointer-events: none;
  user-select: none;
}

/* --------------------------------------------------------------------------
   Underlay navigation shell
   -------------------------------------------------------------------------- */

.fun__header {
  position: fixed;
  inset: 0 0 auto;
  z-index: 100;
  color: var(--header-on-dark);
  pointer-events: none;
}

.fun__bar {
  width: 100%;
}

.fun__container {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: var(--pad);
  pointer-events: none;
}

.fun__container > * {
  pointer-events: auto;
}

/* Logo */

.fun__logo {
  display: inline-flex;
  align-items: center;
  gap: 0.45em;
  color: inherit;
  mix-blend-mode: difference;
}

.fun__logo-mark {
  font-size: 1.15em;
  line-height: 1;
  color: var(--accent);
  mix-blend-mode: normal;
}

.fun__logo-text {
  font-size: 1.05rem;
  font-weight: 620;
  letter-spacing: 0.16em;
  line-height: 1;
}

/* Toggle */

.fun__toggle {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 0.75em;
  margin: -1em;
  padding: 1em;
  color: inherit;
  outline: none;
  transition: color 0.2s ease;
}

.fun__toggle:focus-visible {
  outline: 2px solid currentColor;
  outline-offset: 4px;
  border-radius: 4px;
}

.fun__toggle-text {
  position: relative;
  display: block;
  flex: none;
  height: 1.2em;
  font-size: 1.25em;
  overflow: hidden;
}

.fun__toggle-track {
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  will-change: transform;
  backface-visibility: hidden;
  transform: translate3d(0, 0, 0);
}

.fun__toggle-label {
  display: block;
  flex: none;
  height: 1.2em;
  font-size: 1em;
  font-weight: 500;
  line-height: 1.2em;
  letter-spacing: -0.01em;
  white-space: nowrap;
  backface-visibility: hidden;
}

.fun__toggle-icon {
  display: flex;
  flex-direction: column;
  flex: none;
  align-items: center;
  justify-content: center;
  gap: 0.375em;
  width: 1.5em;
  margin-bottom: -0.3em;
}

.fun__toggle-bar {
  flex: none;
  width: 100%;
  height: 0.125em;
  background: currentColor;
  transform-origin: center;
  will-change: transform;
}

/* Menu panel — fixed under the main layer */

.fun__menu {
  position: fixed;
  top: 0;
  right: 0;
  bottom: 0;
  z-index: 1;
  width: var(--menu-width);
  background: var(--menu-bg);
  color: var(--ink);
  overflow: hidden;
}

.fun__inner {
  display: flex;
  flex-direction: column;
  justify-content: space-between;
  align-items: stretch;
  gap: 2em;
  width: 100%;
  height: 100%;
  padding: 7.5em 2em 2em;
  overflow-x: hidden;
  overflow-y: auto;
  overscroll-behavior: contain;
  scrollbar-width: none;
  -ms-overflow-style: none;
}

.fun__inner::-webkit-scrollbar {
  width: 0;
  height: 0;
  display: none;
}

.fun__list {
  display: flex;
  flex-direction: column;
  width: 100%;
  overflow: hidden;
}

.fun__list.is-small {
  gap: 0.75em;
  overflow: hidden;
}

/* Clip large-link slide so x-shift never creates scroll */
.fun__list > [data-reveal-l] {
  overflow: hidden;
}

.fun__link-large {
  display: block;
  width: 100%;
  padding: 0.7em 0.9em;
  border-radius: 0.3em;
  transition:
    background-color 0.3s var(--ease-out),
    color 0.3s ease,
    padding-left 0.4s var(--ease-out);
}

.fun__link-large:hover {
  background: rgba(0, 0, 0, 0.04);
  padding-left: 1.45em;
}

.fun__link-large.is-current,
.fun__link-large[aria-current="page"] {
  color: #ffffff;
  background: var(--ink);
}

.fun__link-large.is-current:hover,
.fun__link-large[aria-current="page"]:hover {
  background: var(--ink);
  filter: brightness(1.08);
  padding-left: 1.45em;
}

.fun__link-label {
  font-family: var(--font);
  font-size: clamp(2rem, 3.4vw, 3.25em);
  font-weight: 520;
  letter-spacing: -0.04em;
  line-height: 0.95;
}

.fun__bottom {
  position: relative;
  display: flex;
  align-items: flex-start;
  justify-content: flex-start;
  width: 100%;
  padding-top: 1.5em;
  overflow: hidden;
  /* Prevent transformed children from expanding scroll overflow */
  contain: layout paint;
}

.fun__bottom-col {
  display: flex;
  flex: 1;
  flex-direction: column;
  align-items: flex-start;
  gap: 1em;
  min-width: 0;
  overflow: hidden;
}

/* Mask for each social / quick-link line */
.fun__bottom-col > [data-reveal-s],
.fun__list.is-small > [data-reveal-s] {
  display: block;
  overflow: hidden;
  line-height: 1.2;
}

.fun__link-small {
  position: relative;
  display: inline-block;
  font-size: 1em;
  font-weight: 450;
  line-height: 1.2;
  padding-bottom: 0.12em;
  transition: opacity 0.25s ease;
  will-change: transform, opacity;
  backface-visibility: hidden;
}

/* Underline: grows from left on hover, shrinks to right on unhover */
.fun__link-small:not(.is-faded)::after {
  content: "";
  position: absolute;
  left: 0;
  bottom: 0;
  width: 100%;
  height: 1px;
  background: currentColor;
  transform: scaleX(0);
  transform-origin: 100% 50%; /* idle / leave → collapse toward right */
  transition: transform 0.4s var(--ease-out);
  pointer-events: none;
}

.fun__link-small:not(.is-faded):hover::after,
.fun__link-small:not(.is-faded):focus-visible::after {
  transform: scaleX(1);
  transform-origin: 0% 50%; /* enter → expand from left */
}

.fun__link-small:not(.is-faded):hover {
  opacity: 0.72;
}

.fun__link-small.is-faded {
  opacity: 0.45;
  pointer-events: none;
}

.fun__link-small.is-faded::after {
  display: none;
}

.fun__bottom-border {
  position: absolute;
  inset: 0 0 auto;
  width: 100%;
  height: 1px;
  background: currentColor;
  opacity: 0.15;
  transform-origin: left center;
}

/* Overlay — fixed over main, translates with it */

.fun__overlay {
  position: fixed;
  inset: 0 -1px 0 0;
  z-index: 100;
  overflow: clip;
  visibility: hidden;
  pointer-events: none;
  cursor: pointer;
  will-change: transform;
}

.fun__dark {
  position: absolute;
  inset: 0;
  background: var(--dim);
  opacity: 0;
}

.fun__borders {
  position: absolute;
  inset: 0;
  display: flex;
  flex-direction: column;
  justify-content: space-between;
  align-items: stretch;
  pointer-events: none;
}

.fun__border-row {
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  justify-content: flex-start;
}

.fun__border {
  width: 100%;
  height: 1em;
  background: var(--edge);
}

.fun__corner {
  width: 2em;
  height: 2em;
  color: var(--edge);
  background-image: radial-gradient(
    circle farthest-side at 0 100%,
    transparent 99%,
    var(--edge)
  );
  transform-origin: 100% 0;
  will-change: transform;
}

.fun__corner.is-bottom {
  transform-origin: 100% 100%;
  background-image: radial-gradient(
    circle farthest-side at 0 0,
    transparent 99%,
    var(--edge)
  );
}

/* --------------------------------------------------------------------------
   Reduced motion
   -------------------------------------------------------------------------- */

@media (prefers-reduced-motion: reduce) {
  .main,
  .fun__overlay,
  .fun__toggle-bar,
  .fun__corner {
    will-change: auto;
  }
}

/* --------------------------------------------------------------------------
   Responsive
   -------------------------------------------------------------------------- */

@media screen and (max-width: 767px) {
  :root {
    --menu-width: min(80vw, 22rem);
  }

  .fun__inner {
    padding: 5em 1.25em 1.25em;
  }

  .fun__link-label {
    font-size: 2em;
  }

  .fun__bottom {
    flex-direction: column;
    gap: 1.75em;
    padding-left: 0.9em;
    padding-right: 0.9em;
  }

  .fun__toggle-text {
    height: 1.15em;
    font-size: 1em;
  }

  .fun__toggle-label {
    height: 1.15em;
    line-height: 1.15em;
  }

  .hero__title {
    font-size: clamp(2.4rem, 11vw, 3.5rem);
  }
}

@media screen and (min-width: 1600px) {
  :root {
    --menu-width: 32em;
  }
}
````

## Source — script.js
````javascript
(() => {
  "use strict";

  if (typeof gsap === "undefined") {
    console.error("GSAP is required for Fixed Underlay Navigation");
    return;
  }

  
  if (typeof CustomEase !== "undefined") {
    CustomEase.create("energy", "M0,0 C0.32,0.72 0,1 1,1");
  }

  const EASE = typeof CustomEase !== "undefined" ? "energy" : "power3.out";
  const EASE_REV = "power2.inOut";
  const reduceMotion = window.matchMedia(
    "(prefers-reduced-motion: reduce)"
  ).matches;

  function initFixedUnderlayNavigation() {
    const toggleBtn = document.querySelector("[data-fun-toggle]");
    const toggleTrack = document.querySelector("[data-fun-toggle-track]");
    const toggleBars = gsap.utils.toArray(".fun__toggle-bar");
    const menuEl = document.querySelector("[data-fun-menu]");
    const largeItems = gsap.utils.toArray("[data-reveal-l]");
    const smallItems = gsap.utils.toArray("[data-reveal-s]");
    const menuBorder = document.querySelector("[data-fun-border]");
    const mainEl = document.querySelector("[data-main]");
    const overlayEl = document.querySelector("[data-fun-overlay]");
    const darkEl = document.querySelector("[data-fun-dark]");
    const corners = gsap.utils.toArray("[data-fun-corner]");
    const overlayBorders = gsap.utils.toArray("[data-fun-border-row]");
    const menuLinks = gsap.utils.toArray(".fun__link-large, .fun__link-small:not(.is-faded)");

    if (!toggleBtn || !menuEl || !mainEl || !overlayEl) return;

    const closedColor =
      getComputedStyle(document.documentElement)
        .getPropertyValue("--header-on-dark")
        .trim() || "#f4f4f4";
    const openColor =
      getComputedStyle(document.documentElement)
        .getPropertyValue("--header-on-menu")
        .trim() || "#1a1a1a";

    let isOpen = false;
    let tl;
    let enterEndTime = 0;

    const getMenuOffset = () => -menuEl.offsetWidth;

    /* ---------- Initial state ---------- */
    gsap.set(overlayEl, { visibility: "hidden", pointerEvents: "none" });
    gsap.set(darkEl, { autoAlpha: 0 });
    gsap.set(mainEl, { x: 0 });
    if (toggleTrack) gsap.set(toggleTrack, { yPercent: 0, force3D: true });
    gsap.set(toggleBars, { y: 0, rotation: 0 });
    if (menuBorder) gsap.set(menuBorder, { scaleX: 0 });
    if (overlayBorders[0]) gsap.set(overlayBorders[0], { yPercent: -100 });
    if (overlayBorders[1]) gsap.set(overlayBorders[1], { yPercent: 100 });
    gsap.set(corners, { scale: 0 });
    gsap.set(largeItems, { autoAlpha: 0, xPercent: 18, force3D: true });
    /* Pixel y keeps motion inside overflow:hidden masks — no scrollbar flash */
    gsap.set(smallItems, { autoAlpha: 0, y: 18, force3D: true });

    setA11y(false);

    function setA11y(open) {
      toggleBtn.setAttribute("aria-expanded", String(open));
      toggleBtn.setAttribute("aria-label", open ? "Close menu" : "Open menu");
      menuEl.setAttribute("aria-hidden", String(!open));
      document.body.setAttribute("data-menu-status", open ? "open" : "");

      menuLinks.forEach((el) => {
        if (open) el.removeAttribute("tabindex");
        else el.setAttribute("tabindex", "-1");
      });
    }

    /* ---------- Timeline ----------
       Open sequence → pause → close sequence.
       Reverse while opening uses reverse() before the pause;
       after the pause, play() continues into the close tweens.
    */
    function buildTimeline() {
      const dur = reduceMotion ? 0.01 : 0.7;
      const durShort = reduceMotion ? 0.01 : 0.5;
      const durTiny = reduceMotion ? 0.01 : 0.35;

      tl = gsap.timeline({
        paused: true,
        defaults: {
          ease: EASE,
          // GSAP 3.15+; ignored safely on older builds
          easeReverse: EASE_REV,
        },
      });

      /* —— OPEN —— */
      tl.set(overlayEl, { visibility: "visible", pointerEvents: "auto" }, 0);

      tl.to(
        [mainEl, overlayEl],
        {
          x: getMenuOffset,
          duration: dur,
          force3D: true,
        },
        0
      )
        .to(
          darkEl,
          {
            autoAlpha: 1,
            duration: durShort,
          },
          0
        )
        .to(
          corners,
          {
            scale: 1,
            duration: durShort,
          },
          0
        )
        .to(
          overlayBorders,
          {
            yPercent: 0,
            duration: durShort,
          },
          0
        )
        .to(
          toggleTrack,
          {
            yPercent: -50,
            duration: reduceMotion ? 0.01 : 0.55,
            ease: reduceMotion ? "none" : "power3.inOut",
            force3D: true,
          },
          0
        )
        .to(
          toggleBtn,
          {
            color: openColor,
            duration: reduceMotion ? 0.01 : 0.45,
            ease: "power2.inOut",
          },
          0
        )
        .to(
          toggleBars[0],
          {
            y: "0.25em",
            rotation: 45,
            duration: durTiny,
            ease: reduceMotion ? "none" : "back.out(1.4)",
            easeReverse: "power3.out",
          },
          reduceMotion ? 0 : 0.05
        )
        .to(
          toggleBars[1],
          {
            y: "-0.25em",
            rotation: -45,
            duration: durTiny,
            ease: reduceMotion ? "none" : "back.out(1.4)",
            easeReverse: "power3.out",
          },
          reduceMotion ? 0 : 0.05
        )
        .fromTo(
          largeItems,
          { autoAlpha: 0, xPercent: 18 },
          {
            autoAlpha: 1,
            xPercent: 0,
            duration: dur,
            stagger: reduceMotion ? 0 : 0.05,
            force3D: true,
          },
          0
        )
        .fromTo(
          smallItems,
          { autoAlpha: 0, y: 18 },
          {
            autoAlpha: 1,
            y: 0,
            duration: reduceMotion ? 0.01 : 0.55,
            stagger: reduceMotion ? 0 : 0.04,
            ease: reduceMotion ? "none" : "power3.out",
            force3D: true,
          },
          reduceMotion ? 0 : 0.28
        );

      if (menuBorder) {
        tl.to(
          menuBorder,
          {
            scaleX: 1,
            duration: durShort,
          },
          "<"
        );
      }

      enterEndTime = tl.duration();
      tl.addPause();

      /* —— CLOSE (plays after pause when leaving open state) —— */
      tl.to(
        largeItems,
        {
          autoAlpha: 0,
          xPercent: 10,
          duration: reduceMotion ? 0.01 : 0.28,
          ease: "power2.in",
          stagger: reduceMotion ? 0 : 0.02,
        },
        "<"
      )
        .to(
          smallItems,
          {
            autoAlpha: 0,
            y: 12,
            duration: reduceMotion ? 0.01 : 0.28,
            ease: "power2.in",
            stagger: reduceMotion ? 0 : 0.02,
          },
          "<"
        )
        .to(
          [mainEl, overlayEl],
          {
            x: 0,
            duration: reduceMotion ? 0.01 : 0.6,
            force3D: true,
          },
          "<"
        )
        .to(
          darkEl,
          {
            autoAlpha: 0,
            duration: reduceMotion ? 0.01 : 0.35,
            ease: "power2.inOut",
          },
          "<"
        )
        .to(
          corners,
          {
            scale: 0,
            duration: durShort,
          },
          "<"
        )
        .to(
          overlayBorders[0],
          {
            yPercent: -100,
            duration: durShort,
          },
          "<"
        )
        .to(
          overlayBorders[1],
          {
            yPercent: 100,
            duration: durShort,
          },
          "<"
        )
        .to(
          toggleBtn,
          {
            color: closedColor,
            duration: reduceMotion ? 0.01 : 0.4,
            ease: "power2.inOut",
          },
          reduceMotion ? "<" : "<+=0.05"
        )
        .to(
          toggleTrack,
          {
            yPercent: 0,
            duration: reduceMotion ? 0.01 : 0.5,
            ease: reduceMotion ? "none" : "power3.inOut",
            force3D: true,
          },
          "<"
        )
        .to(
          toggleBars,
          {
            y: 0,
            rotation: 0,
            duration: reduceMotion ? 0.01 : 0.35,
            ease: "power2.inOut",
          },
          "<"
        );

      if (menuBorder) {
        tl.to(
          menuBorder,
          {
            scaleX: 0,
            duration: reduceMotion ? 0.01 : 0.3,
          },
          "<"
        );
      }

      tl.set(overlayEl, {
        visibility: "hidden",
        pointerEvents: "none",
      });
    }

    /* ---------- Open / close ---------- */
    function toggle() {
      isOpen = !isOpen;
      setA11y(isOpen);

      if (isOpen) {
        tl.invalidate();
        if (tl.time() >= enterEndTime) {
          tl.timeScale(1).restart();
        } else {
          tl.timeScale(1).play();
        }
      } else if (tl.time() < enterEndTime) {
        tl.timeScale(1).reverse();
      } else {
        tl.timeScale(1).play();
      }
    }

    function close() {
      if (!isOpen) return;
      toggle();
    }

    /* ---------- Events ---------- */
    buildTimeline();

    toggleBtn.addEventListener("click", toggle);

    overlayEl.addEventListener("click", () => {
      if (isOpen) close();
    });

    document.addEventListener("keydown", (e) => {
      if (e.key === "Escape" && isOpen) {
        close();
        toggleBtn.focus();
      }
    });

    menuLinks.forEach((link) => {
      link.addEventListener("click", () => {
        if (isOpen) close();
      });
    });

    let resizeTimer;
    window.addEventListener("resize", () => {
      clearTimeout(resizeTimer);
      resizeTimer = setTimeout(() => {
        if (isOpen) {
          gsap.set([mainEl, overlayEl], { x: getMenuOffset() });
        } else {
          tl.invalidate();
        }
      }, 150);
    });
  }

  if (document.readyState === "loading") {
    document.addEventListener("DOMContentLoaded", initFixedUnderlayNavigation);
  } else {
    initFixedUnderlayNavigation();
  }
})();
````
