# Unfolding Words Menu — Frosted Veil & Letter-by-Letter Link Reveal

## Goal
Build a fullscreen overlay navigation where every link **unfolds letter by letter**. Pressing "Menu": a **frosted glass veil** fades over the hero photo (which also zooms and blurs slightly); then, link by link, the small index number and the **first letter rise out of a mask**, a thin **light divider** grows from its centre with a 20° lean, and the **remaining letters slide in from the right** while the box that holds them **widens from 0 to its natural width**. Serif and grotesk links alternate. Hovering a link nudges it to the right and dims the others; the toggle text flickers between "Menu" and "Close". Reverse on close. GSAP + SplitText.

## Tech
Vanilla HTML/CSS/JS with ES module imports. Use `gsap` (npm) and the plugin **`SplitText`** (`gsap/SplitText`, included with GSAP 3.13+). Fonts: **Instrument Serif** (400) and **Space Grotesk** (400–600).

## Layout / HTML
```html
<div class="uw">
  <nav class="uw-nav">
    <a class="uw-brand" href="#">Obscura</a>
    <button class="uw-toggle" aria-expanded="false" aria-controls="uw-menu">Menu</button>
  </nav>

  <nav class="uw-menu" id="uw-menu" aria-hidden="true">
    <div class="uw-menu__veil"></div>
    <!-- six links; add class uw-item--sans on 02, 03 and 06 -->
    <a class="uw-item" href="#">
      <span class="uw-item__index">01</span>
      <span class="uw-item__label">Work</span>
      <span class="uw-item__divider"></span>
    </a>
    …Portfolio (sans), Retrospective (sans), Lens, Selected, Enquire (sans)…
    <p class="uw-menu__foot"><span>Studio for photography and film</span><span>Lisbon · Oslo</span></p>
  </nav>

  <section class="uw-hero">
    <img class="uw-hero__bg" src="…" alt="" />
    <div class="uw-hero__copy">
      <p class="uw-hero__eyebrow">Obscura — 2026 reel</p>
      <h1>Light, held still</h1>
    </div>
  </section>
</div>
```

## Styling
Prefixed variables on `:root`: `--uw-ink:#ece9e2`, `--uw-tint:rgba(10,12,16,.42)`, `--uw-blur:26px`, `--uw-line:rgba(255,255,255,.18)`, `--uw-glass:rgba(255,255,255,.08)`, `--uw-size:5.4vw`.

- `.uw` — `min-height:100svh; background:#0b0d10; color:var(--uw-ink); font-family:"Space Grotesk"`.
- `.uw-nav` — fixed top bar, `z-index:100`, `padding:1.25rem 1.5rem`, brand in Instrument Serif 1.5rem; `.uw-toggle` is a glass pill (`border:1px solid var(--uw-line); background:var(--uw-glass); backdrop-filter:blur(14px) saturate(1.3); border-radius:999px; padding:.75rem 1.1rem; font-size:.72rem; font-weight:600; letter-spacing:.12em; uppercase; inset 0 1px 0 rgba(255,255,255,.18)`).
- `.uw-menu` — `position:fixed; inset:0; z-index:10; display:flex; flex-direction:column; align-items:flex-start; justify-content:center; gap:.2em; padding:8rem 2rem; font-size:var(--uw-size); pointer-events:none` → `auto` with `.is-open`.
- `.uw-menu__veil` — `position:absolute; inset:0; z-index:-1; background:linear-gradient(180deg, var(--uw-tint), rgba(10,12,16,.62)); backdrop-filter:blur(var(--uw-blur)) saturate(1.25); opacity:0`.
- `.uw-item` — `display:inline-flex; align-items:center; white-space:nowrap; font-family:"Instrument Serif"; font-size:1em; line-height:1.2; letter-spacing:-.02em`; `.uw-item--sans` uses Space Grotesk 600 at `.9em`, `letter-spacing:-.03em`.
- `.uw-item__index` — `align-self:flex-start; margin:.35em .45em 0 0; font:600 .16em "Space Grotesk"; letter-spacing:.1em; opacity:.75`.
- `.uw-item__divider` — `width:2px; height:1em; margin:0 .3em; background:linear-gradient(180deg,#fff, rgba(178,196,255,.95) 45%, rgba(255,184,226,.9)); box-shadow:0 0 12px rgba(200,210,255,.35); transform:rotate(20deg) scaleY(0); transform-origin:center`.
- `.uw-item__body` (created by JS) — `width:0; display:inline-block; white-space:nowrap; overflow:hidden`.
- `.uw-menu__foot` — bottom row, `font-size:.7rem; letter-spacing:.08em; uppercase; opacity:0`.
- `.uw-hero` — `height:100svh; overflow:hidden; display:flex; align-items:flex-end; padding:2rem 1.5rem`; the `img` is absolute, `object-fit:cover`; eyebrow `.72rem` uppercase, `h1` Instrument Serif `clamp(2.6rem,8vw,8rem)`, `line-height:.95`.
- `@media (max-width:1000px)`: `--uw-size:2.4rem`, menu padding `6rem 1.25rem`, gap `.35em`, foot stacks.

## GSAP effect (be exact)
`gsap.registerPlugin(SplitText)`. Build in `mount(config)` → returns `destroy()`.

```js
const DEFAULTS = { itemStagger: .14, revealDuration: .85, unfoldDuration: 1, charStagger: .045, hoverShift: 12, dimOthers: .4, veilDuration: .8, veilBlur: 26 };
```
Set `--uw-blur` to `${veilBlur}px` on the root at mount.

**Preparation, per link:**
1. `chars = SplitText.create(label, { type: "chars", mask: "chars" })` → `[firstChar, ...trailing]`.
2. Create `<span class="uw-item__body">`; move each trailing char's **mask wrapper** (`char.parentElement`) into it; insert the body right after the label (`label.after(body)`). Now the label holds only the first letter.
3. `idx = SplitText.create(index, { type: "words", mask: "words" })`.
4. `gsap.set([idx.words, firstChar], { yPercent: 110 })`, `gsap.set(trailing, { xPercent: 130 })`, `gsap.set(body, { width: 0 })`.

Also `gsap.set(veil, { opacity: 0 })`, `gsap.set(foot, { opacity: 0 })`, `gsap.set(heroBg, { scale: 1, filter: "blur(0px)" })`.

**Timeline** (`paused`, defaults `ease: "power3.out"`):
- `0` — veil `opacity: 1`, `veilDuration`.
- `0` — hero image `{ scale: 1.08, filter: "blur(6px)" }`, `veilDuration * 1.6`, `power2.out`.
- For link `i`, `t = 0.45 + i * itemStagger`:
  - `t` — `[idxWords, firstChar]` → `yPercent: 0`, `revealDuration`.
  - `t + .05` — divider `scaleY: 1`, 1s.
  - `t + .25` — body `width: () => body.scrollWidth` (function-based, so it re-measures on `invalidate()`), `unfoldDuration`, `ease: "power4.inOut"`.
  - `t + .5` — trailing chars `xPercent: 0`, .75s, `stagger: charStagger`.
- `">-0.4"` — foot `opacity: 1`, .6s.

**Toggle** — `isOpen ? tl.invalidate().play() : tl.reverse()`; toggle `.is-open` on the menu, aria attributes; `Escape` closes.

**Toggle label flicker** — revert any previous split, set `textContent` to "Close"/"Menu", `SplitText.create(toggle, { type: "chars" })`, then `gsap.fromTo(chars, { opacity: 0 }, { opacity: 1, duration: .05, ease: "power2.inOut", overwrite: true, stagger: { amount: .3, from: "random" } })`.

**Hover** — `pointerenter` on a link: `gsap.to(link, { x: hoverShift, duration: .6, ease: "power3.out", overwrite: "auto" })` and the other links `{ opacity: dimOthers, duration: .5, ease: "power2.out" }`; `pointerleave`: `x: 0` and all links `opacity: 1`.

**destroy()** — remove listeners, kill the timeline and tweens, remove the created `.uw-item__body` spans, `revert()` every SplitText (including the toggle's), `clearProps:"all"`, reset the toggle text and aria state.

## Assets / images
One atmospheric landscape photo for the hero (≈16:9, ≥1600px wide): a misty forest, dusk street or textured landscape. It is blurred behind the menu, so texture matters more than detail.

## Behavior notes
- The menu is `position:fixed`; the nav stays above it. Links get `pointer-events` only when open.
- Because the trailing letters live in a `width:0; overflow:hidden` box, the reveal never reflows the line: the first letter and index are stable anchors.
- Mobile: font size drops to `2.4rem`; everything else is identical (touch toggles the menu).
- Keep the prefixed variables on `:root`; nothing else is global.

## Images

This component ships with 1 reference asset, served publicly.
Use them as-is to reproduce the demo faithfully, then swap in your own — the layout expects the
same aspect ratios.

```
https://motionprompts.dev/c/unfold-word-menu/hero.jpg
```

They are hotlinkable for prototyping. For anything you ship, replace them: they are licensed for
demonstration of this component, not for redistribution.

## Using this outside its demo page

This component is written as a complete page — that is how the demo is meant to look. If you are dropping it into an existing project, or combining it with other components, these are the things it declares at document level and that you need to move or reconcile first.

- **Palette on `:root`** — `--uw-ink`, `--uw-tint`, `--uw-blur`, `--uw-line`, `--uw-glass`, `--uw-size`. These names are not namespaced and they collide: `--ink` is defined by 164 of the 219 components in this catalogue, `--paper` by 94, `--muted` by 80, each with different values — and they will also collide with whatever your own project defines. Move them onto the component's wrapper (`.my-section { --ink: … }`) or rename them with a prefix.
- **Rules on `html, body`** — the demo owns the whole document, so these set the page background, typography and resets. Dropped into an existing project they restyle the entire page, not just this section. Re-target them at the component's wrapper before using it.
- **Full-screen overlay** — a fixed element covers the viewport (a loader or transition). Only one may exist per page and it must remove itself when done. If your page already has one, keep that and drop this; otherwise the second silently hides the first.
