# Landing Page Reveal — Preloader-to-Hero Intro

## Goal
Build a full-screen fashion/editorial landing hero with a cinematic **preloader-to-hero reveal** that plays automatically once on page load (~7 seconds). A dark overlay counts `0 → 100` while a small masked label steps through three words; five portrait images rise from below, collapse their gap and scale up; the four side images wipe away upward with a clip-path while the center image scales to fill the screen; finally the dark overlay wipes upward to reveal a giant hero name that slides in word by word. Everything is driven by three parallel GSAP timelines sharing one custom ease called `hop`.

## Tech
Vanilla HTML/CSS/JS with ES module imports. Use `gsap` (npm) plus the GSAP plugins **`CustomEase`** and **`SplitText`**. No smooth-scroll library (the page does not scroll during the intro). Register the plugins with `gsap.registerPlugin(CustomEase, SplitText)`. Fire the whole sequence on `DOMContentLoaded`.

## Layout / HTML
Semantic structure (class names are load-bearing — the JS/CSS query them):

```
<nav>
  <div class="nav-logo"><a href="#">Elara Vandenberg</a></div>
  <div class="nav-items">
    <a>Runway</a><a>Lookbook</a><a>Campaigns</a><a>Biography</a>
  </div>
</nav>

<section class="hero">
  <div class="hero-overlay">
    <div class="counter"><h1>0</h1></div>
    <div class="overlay-text-container">
      <div class="overlay-text">
        <p>Structure</p>
        <p>Designed Identity</p>
        <p>Welcome</p>
      </div>
    </div>
  </div>

  <div class="hero-images">
    <div class="img"><img src="..." alt="" /></div>
    <div class="img"><img src="..." alt="" /></div>
    <div class="img hero-img"><img src="..." alt="" /></div>  <!-- center, index 2 -->
    <div class="img"><img src="..." alt="" /></div>
    <div class="img"><img src="..." alt="" /></div>
  </div>

  <div class="hero-header"><h1>Elara Vandenberg</h1></div>
</section>
```

Notes:
- `.hero-images` holds exactly **5** `.img` wrappers; the **third one (index 2)** also gets class `hero-img` — it is the center image that survives and scales to fill.
- Use "Elara Vandenberg" as the neutral placeholder brand/name.

## Styling
Fonts (Google Fonts): **DM Sans** (body, all big headings) and **DM Mono** (nav links + overlay label). Import both.

Palette (CSS vars): `--light: #fff`, `--dark: #0f0f0f`. Default text color `#000`.

Global:
- `* { margin:0; padding:0; box-sizing:border-box }`; `body { font-family:"DM Sans" }`.
- `img { width:100%; height:100%; object-fit:cover }`.
- `a, p`: `text-decoration:none; text-transform:uppercase; font-family:"DM Mono"; font-size:0.9rem; font-weight:500; line-height:1.25; color:#000`.

`nav`: `position:fixed; width:100%; padding:2rem; display:flex; justify-content:space-between; align-items:flex-start`. `.nav-items` is a `flex-direction:column; align-items:flex-end` stack.

`.hero`: `position:relative; width:100%; height:100svh; overflow:hidden`.

`.hero-overlay` (the dark preloader panel):
- `position:absolute; width:100%; height:100svh; background:var(--dark); z-index:0`.
- **Initial** `clip-path: polygon(0% 0%, 100% 0%, 100% 100%, 0% 100%)` (full rectangle covering everything). `will-change: clip-path`.
- `.counter` inside it: `position:absolute; right:2rem; bottom:2rem; color:var(--light)`. Its `h1`: `font-size:4rem; font-weight:500`.
- `.overlay-text-container`: `position:absolute; top:2rem; left:2rem; height:2rem; overflow:hidden` (a 2rem-tall clipping window — only one line shows at a time).
- `.overlay-text`: `display:flex; flex-direction:column; transform:translateY(2rem); will-change:transform` (starts pushed one line DOWN, so the window is initially empty).
- `.overlay-text p`: `color:var(--light); height:2rem; display:flex; align-items:center` (each line is exactly the window height).

`.hero-images`:
- `position:absolute; top:50%; transform:translateY(-50%); width:100%; padding:0 2rem; display:flex; justify-content:center; gap:10vw; z-index:2; will-change:gap`.
- `.hero-images .img`: `width:10vw; aspect-ratio:5/7`. **Initial** `transform:translateY(50%) scale(0.5); opacity:0; clip-path: polygon(0% 0%, 100% 0%, 100% 100%, 0% 100%)` (full rect). `will-change:opacity, transform, clip-path`.

`.hero-header`:
- `position:absolute; bottom:2rem; width:100%; z-index:-1` (sits BEHIND the overlay, revealed only when the overlay wipes away).
- `h1`: `text-transform:uppercase; text-align:center; font-size:15vw; font-weight:500; line-height:0.85`.
- `.hero-header h1 .word` (the SplitText word wrappers): `transform:translateY(100%); will-change:transform` (each word starts hidden below its masked line).

Z-index stack (important for the reveal to read right): header `-1` (behind) < overlay `0` < images `2` (front).

## GSAP effect (be exact)

### Setup
```js
CustomEase.create("hop", "0.85, 0, 0.15, 1");

const counterEl  = document.querySelector(".hero-overlay .counter h1");
const heroHeader = document.querySelector(".hero-header h1");
const overlayText= document.querySelector(".overlay-text");
const heroImages = document.querySelector(".hero-images");
const images     = gsap.utils.toArray(".hero-images .img"); // length 5
const heroOverlay= document.querySelector(".hero-overlay");
const counter    = { value: 0 };

const split = new SplitText(heroHeader, {
  type: "words",
  mask: "words",        // each word gets an overflow-hidden mask wrapper
  wordsClass: "word",   // matches the CSS translateY(100%) init above
});
```

Create **three independent timelines** that run in parallel (each with its own `delay`):

### Timeline 1 — `counterTl` (delay `0.5`)
One tween on the plain object `counter`:
- `counter.value: 0 → 100`, `duration: 5`, `ease: "power2.out"`.
- `onUpdate`: write `Math.floor(counter.value)` into `counterEl.textContent` (so the bottom-right number ticks up from 0 to 100 over 5s, decelerating).

### Timeline 2 — `overlayTextTl` (delay `0.75`)
Four sequential tweens on `overlayText`, each `duration: 0.75, ease: "hop"`, stepping the flex column up one line per beat through the 2rem window (label text: Structure → Designed Identity → Welcome → empty):
1. `y: "0"` — reveals line 1 ("Structure").
2. `y: "-2rem"`, `delay: 0.75` — reveals line 2 ("Designed Identity").
3. `y: "-4rem"`, `delay: 0.75` — reveals line 3 ("Welcome").
4. `y: "-6rem"`, `delay: 1` — scrolls everything out (empty window again).

### Timeline 3 — `revealTl` (delay `0.5`) — the main reveal
Sequential unless a position param says otherwise. All tweens `ease: "hop"` unless noted.
1. **Images rise + fade in:** `images` → `{ y: 0, opacity: 1, stagger: 0.05, duration: 1 }`. (From `translateY(50%) scale(0.5) opacity:0` to `y:0 opacity:1`, still at scale 0.5, left-to-right stagger.)
2. **Gap collapses:** `heroImages` → `{ gap: "0.75vw", duration: 1, delay: 0.5 }` (from `10vw` down to `0.75vw` — images pull together into a tight filmstrip).
3. **Images scale up — SIMULTANEOUS with step 2** (position param `"<"`): `images` → `{ scale: 1, duration: 1 }` (0.5 → 1).
4. **Side images wipe away upward:** `images.filter((_, i) => i !== 2)` (the 4 non-center images) → `{ clipPath: "polygon(0% 0%, 100% 0%, 100% 0%, 0% 0%)", duration: 1, stagger: 0.1 }`. The clip-path collapses each image's bottom edge up to the top edge, so they vanish upward one after another, leaving only the center image.
5. **Center image scales to fill:** `images[2]` → `{ scale: 2, duration: 1 }` (1 → 2, grows to dominate the frame).
6. **Overlay wipes upward:** `heroOverlay` → `{ clipPath: "polygon(0% 0%, 100% 0%, 100% 0%, 0% 0%)", duration: 1 }`. Same collapse-to-top-edge clip-path, so the whole dark preloader panel wipes up and off, uncovering the hero header behind it.
7. **Hero name slides in word by word** (position param `"-=0.5"`, i.e. starts 0.5s before step 6 ends, overlapping the overlay wipe): `".hero-header h1 .word"` → `{ y: "0", duration: 0.75, stagger: 0.1, ease: "power3.out" }`. Each word slides up from `translateY(100%)` into its mask (`ease: "power3.out"`, NOT hop, for this one).

### Ease reference
- `hop` = `CustomEase.create("hop", "0.85, 0, 0.15, 1")` — a steep symmetric in-out used for nearly everything.
- `counterTl` uses `power2.out`; the final word slide uses `power3.out`.

## Assets / images
Five **portrait editorial fashion photos**, aspect-ratio roughly **5/7**, each a single subject against a flat studio or atmospheric backdrop. They are NOT a matched set — the color stories differ per frame, which is fine here since only one survives to full-bleed. What's actually shown, in row order (positions 1–5 = img_1–img_5):
- **img_1** (position 1): a woman with shoulder-length brown hair looking off to the side, wearing a **deep burgundy/wine** wool coat over a matching draped red gown, against a saturated **cobalt-blue** background.
- **img_2** (position 2): a young man with curly dark-brown hair in an **olive/moss-green** tailored suit and tie, facing camera, against a soft **painterly abstract backdrop** of warm tan, peach and turquoise brushstrokes.
- **img_3** — the **center image (index 2, the third)** that scales up to fill the screen: a side-profile of a woman with dark hair in a low bun and gold hoop earring, wearing a **multicolor brushstroke-print** sleeveless top, against a bright **orange** background. Being the hero frame, its off-center profile still reads well full-bleed.
- **img_4** (position 4): a model with a short brown fringe/bob, bare chest under an open **bright-red** blazer with layered gold necklaces, against a deep **teal/petrol-blue** background.
- **img_5** (position 5): a pale, white-haired woman in a long **white** Victorian lace dress standing on a grassy islet flanked by two **black swans**, in a misty lake scene with pine trees and lily pads — muted **sage-green, grey and cream** tones.

Use `object-fit: cover`. The four non-center frames are only seen briefly as a tight filmstrip before they wipe away upward, so their differing palettes and moods don't need to match. If you have fewer than five images, repeat one to fill the row.

## Behavior notes
- **Autoplay once** on load; no scroll, hover or click triggers. Total runtime ≈ 7s.
- **Responsive** (`max-width: 1000px`): `nav` padding → `1rem`; `.counter` → `right:1rem; bottom:1rem`; `.hero-images` → `padding:0 0.5rem; gap:2.5vw`; `.hero-images .img` → `width:20vw`. The `hero-header` font at `15vw` and image scale-up naturally adapt.
- Uses `100svh` (small viewport height) so mobile browser chrome doesn't clip the hero.
- `will-change` hints are set on the overlay clip-path, image transforms/clip-path, the overlay-text transform, the images `gap`, and the header words — keep them, they matter for smoothness of the clip-path and gap animations.
