All components

AlaDesign Landing Page Reveal

GSAP animation component · Published 2026-07-21 · by vanguardia.dev

Open live demo ↗ Raw prompt (.md)

What it does

Preloader-to-hero landing page reveal driven by three parallel GSAP timelines with a custom 'hop' ease: a dark overlay counts 0 to 100 while masked overlay text steps through three lines, five portrait images rise and collapse their flex gap, the side images wipe away with clip-path as the center image scales up, then the overlay wipes upward and the giant hero name slides in word by word (SplitText). Plays automatically once on page load, about 7 seconds total.

How it's built

Categorypreloader
Techgsap
GSAP pluginsCustomEase, SplitText
Complexitypage
Performance costmedium
Mobile-safeyes

preloader reveal counter clip-path splittext customease landing hero stagger intro

Rebuild it with AI

To reproduce this animation in your own project, copy the prompt below into Claude Code, Cursor or any AI coding agent. The prompt is validated — it describes the exact structure, timing and easing, so the agent rebuilds the effect faithfully and you can then adapt colors, copy and layout to your design.

The full prompt

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
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; .counterright:1rem; bottom:1rem; .hero-imagespadding:0 0.5rem; gap:2.5vw; .hero-images .imgwidth: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.