# Fullscreen Portfolio Menu — Clip-Path Overlay with Staggered Giant Links

## Goal

Build a fullscreen overlay navigation for an editorial portfolio site. A burger button in the top-right toggles the menu: a single GSAP timeline sweeps a dark `clip-path` overlay down over the entire viewport, then staggers three oversized menu words up from behind a mask, grows a colored underline bar across the active item (animated via `CSSRulePlugin` on a `::after` pseudo-element), and fades a social sub-nav up into place. Clicking the burger again plays the exact same timeline in reverse. The burger morphs into an X. The top nav uses `mix-blend-mode: difference` so its text/lines invert against whatever is behind them.

## Tech

Vanilla HTML/CSS/JS with ES module imports. Use `gsap` (npm) plus the GSAP plugin **`CSSRulePlugin`** (this is required — the active-item underline is a CSS `::after` pseudo-element and can only be tweened through `CSSRulePlugin.getRule(...)`).

```js
import gsap from "gsap";
import { CSSRulePlugin } from "gsap/CSSRulePlugin";
gsap.registerPlugin(CSSRulePlugin);
```

Wrap all JS in a `DOMContentLoaded` listener. No smooth-scroll library, no ScrollTrigger, no canvas/WebGL.

## Layout / HTML

```
<div class="website-content">
  <div class="hero-img"><img src="./hero.jpg" alt="" /></div>
</div>

<nav>
  <div class="info"><p>Folio Vol. 1</p></div>
  <div class="logo"><p><a href="#">The Elite Portfolio</a></p></div>
  <div class="toggle-btn">
    <button class="burger" onclick="this.classList.toggle('active');"></button>
  </div>
</nav>

<div class="overlay">
  <div class="overlay-menu">
    <div class="menu-item"><div class="menu-item-name"><p id="active"><a href="#">Index</a></p></div></div>
    <div class="menu-item"><div class="menu-item-name"><p><a href="#">Work</a></p></div></div>
    <div class="menu-item"><div class="menu-item-name"><p><a href="#">About</a></p></div></div>
  </div>
  <div class="sub-nav">
    <p><a href="#">Twitter</a></p><p>·</p>
    <p><a href="#">Instagram</a></p><p>·</p>
    <p><a href="#">Dribble</a></p><p>·</p>
    <p><a href="#">Behance</a></p>
  </div>
</div>

<script type="module" src="./script.js"></script>
```

Notes:
- The burger `<button>` has an inline `onclick="this.classList.toggle('active');"` that toggles the X shape via CSS — keep it. The JS adds a *separate* click listener on `.burger` that drives the GSAP timeline.
- The first menu item's `<p>` carries `id="active"` — its `::after` is the animated underline.
- Stacking order (via z-index): `.website-content` = 0 (base page), `.overlay` sits above it, `nav` = 2 (always on top).

## Styling

Reset: `* { margin: 0; padding: 0; box-sizing: border-box; }`. `html, body { width: 100%; height: 100%; }`.

**Palette (exact):**
- `#cdc6be` — warm greige / off-white (page background, most text, links)
- `#141412` — near-black (overlay background, hero border)
- `#fff` — white (menu text, burger lines)
- `#c03f13` — burnt orange-red (the active-item underline bar)

**Fonts (by role):** the small UI text (`info`, links) uses a clean neutral grotesque — name it `"Neue Montreal"` with a sans-serif fallback (substitute Inter/Helvetica Neue). The logo, the giant menu words and the sub-nav use an elegant high-contrast display serif — name it `"Canopee"` with a serif fallback (substitute any refined display serif such as Cormorant/Playfair if unavailable). The named families can stay declared even without `@font-face`; the layout is what matters.

Key rules:
- `a { text-decoration: none; color: #cdc6be; }`
- `.website-content` — `position: fixed; inset: 0; width/height 100%; z-index: 0; background: #cdc6be; padding: 2em;`
- `.hero-img` — `margin-top: 4em; width: 100%; height: 100%; border: 2px solid #141412;` and its `img { width:100%; height:100%; object-fit: cover; }`
- `nav` — `position: fixed; width: 100%; display: flex; justify-content: space-between; align-items: center; padding: 1.5em 2em; color: #cdc6be; mix-blend-mode: difference; z-index: 2;`. `nav > div { flex: 1; }`. **The `mix-blend-mode: difference` is essential** — it makes the nav text/burger invert against the light page and the dark overlay.
- `.logo` — `text-align: center; font-family: "Canopee";`. `.logo p { position: relative; top: 0.225em; }`. `.logo a { font-size: 30px; color: #cdc6be; }` (drops to `20px` under `@media (max-width: 900px)`).
- `.toggle-btn { display: flex; justify-content: flex-end; }`
- `.burger` — `display: flex; justify-content: center; align-items: center; padding: 1.75em 2em 1.5em 2em; background: rgba(255,255,255,0); border-radius: 0.25em; outline: none; height: 20px; width: 28px; border: none; cursor: pointer; transition: all 250ms ease-out;`
- `.burger:before, .burger:after` — `content: ""; width: 40px; height: 2px; position: absolute; background: #fff; transition: all 250ms ease-out; will-change: transform;`. `:before { transform: translateY(-3px); }`, `:after { transform: translateY(3px); }`. When `.burger.active`: `:before { transform: translateY(0) rotate(45deg); }`, `:after { transform: translateY(0) rotate(-45deg); }` — the two bars cross into an **X**.
- `.overlay` — `position: fixed; inset: 0; width: 100vw; height: 100vh; display: flex; background: #141412; clip-path: polygon(0 0, 100% 0, 100% 0, 0 0); will-change: transform;`. **The initial clip-path is collapsed flat against the top edge (zero height)** so the overlay is invisible until animated.
- `.overlay-menu` — `position: fixed; inset: 0; width: 100vw; height: 100vh; display: flex; flex-direction: column; justify-content: center; align-items: center; gap: 1em; color: #fff;`
- `.menu-item` — `display: flex; cursor: pointer; clip-path: polygon(0 0, 100% 0, 100% 100%, 0% 100%);`. **This rectangular clip-path is the mask** that hides each word while it sits pushed down (y: 225) so it can rise into view.
- `.menu-item p` — `position: relative; text-align: center; font-family: "Canopee"; font-size: 15vw; line-height: 80%; will-change: transform; transition: letter-spacing 0.3s;`. On `:hover`, `letter-spacing: 0.075em;`.
- `.menu-item p#active::after` — `content: ""; position: absolute; top: 45%; left: 0; transform: translateY(-50%); background: #c03f13; width: 0%; height: 12px;`. **Starts at `width: 0%`** — GSAP grows it to 100%.
- `.menu-item a { color: #cdc6be; }`
- `.sub-nav` — `position: absolute; bottom: 5%; left: 50%; transform: translateX(-50%); display: flex; gap: 0.5em; opacity: 0;`. `.sub-nav p { font-family: "Canopee"; font-size: 20px; color: #cdc6be; }`.

## GSAP effect (exhaustive)

State: a single boolean `isOpen = false` and one **paused** timeline built once on load.

### Setup (runs immediately on `DOMContentLoaded`)

1. Grab the underline pseudo-element rule:
   `const activeItemIndicator = CSSRulePlugin.getRule(".menu-item p#active::after");`
2. `const toggleButton = document.querySelector(".burger");`
3. Set the closed/initial state of the giant words: `gsap.set(".menu-item p", { y: 225 });` — all three words pushed 225px down, hidden behind each `.menu-item`'s clip-path mask.
4. `const timeline = gsap.timeline({ paused: true });`

### Timeline (4 tweens, in this exact order and position)

**Tween 1 — overlay wipes down.** Target `.overlay`.
- `clipPath: "polygon(0% 0%, 100% 0%, 100% 100%, 0% 100%)"` (from the CSS initial collapsed-top polygon → full rectangle, i.e. the dark panel unrolls downward covering the viewport).
- `duration: 1.5`, `ease: "power4.inOut"`. Inserted at the timeline start (t = 0).

**Tween 2 — menu words rise, staggered.** Target `.menu-item p`.
- `y: 0` (from the preset `y: 225` → 0, so each word slides up into its mask).
- `duration: 1.5`, `stagger: 0.2` (Index, then Work, then About, 0.2s apart), `ease: "power4.out"`.
- **Position parameter `"-=1"`** — starts 1s before the previous tween ended, i.e. at t ≈ 0.5s, overlapping the overlay wipe.

**Tween 3 — active underline grows.** Target `activeItemIndicator` (the `CSSRulePlugin` rule).
- `width: "100%"` (from `0%` → `100%`, the burnt-orange bar draws left→right across the "Index" word).
- `duration: 1`, `ease: "power4.out"`, `delay: 0.5`.
- **Position parameter `"<"`** — inserted at the *start* of Tween 2 (t ≈ 0.5s); with its own `delay: 0.5` it effectively begins around t ≈ 1.0s and finishes at ≈ 2.0s.

**Tween 4 — social sub-nav fades up.** Target `.sub-nav`.
- `bottom: "10%"` (from `5%` → `10%`, rises slightly) and `opacity: 1` (from `0` → `1`).
- `duration: 0.5`, `delay: 0.5`.
- **Position parameter `"<"`** — inserted at the start of Tween 3 (t ≈ 0.5s); with `delay: 0.5` it effectively begins around t ≈ 1.0s.

### Trigger

Add a click listener on `.burger`:
```js
toggleButton.addEventListener("click", () => {
  if (isOpen) timeline.reverse();
  else timeline.play();
  isOpen = !isOpen;
});
```
So the first click plays the timeline forward (menu opens), the next click reverses it (everything retracts along the same eases), and so on. The inline `onclick` on the button independently toggles the `.active` class that morphs the burger into an X (250ms CSS transition), in sync with the toggle.

## Assets / images

One image: `hero.jpg` — a large, moody, editorial photograph (portrait mood works well) filling the bordered hero frame full-bleed via `object-fit: cover`. It sits on the base page behind the overlay and is only visible when the menu is closed. Any single high-quality editorial/fashion-style photo works; aspect ratio is flexible since it is cropped to fill the frame.

## Behavior notes

- Desktop-oriented; the `15vw` menu type and centered flex layout scale down gracefully. `@media (max-width: 900px)` only shrinks the logo to `20px`.
- The whole effect is a single reusable paused timeline — no re-creation per click, just `play()` / `reverse()`.
- No infinite loops, no scroll hijacking; lightweight and mobile-safe.
- `will-change: transform` hints are set on the overlay, menu words and burger bars for smoothness.
