Landing Page Reveal — Giant Word-Mark + Clip-Path Product Grid Unmask
Goal
Build a full-screen editorial furniture landing hero with an on-load reveal driven by a single paused GSAP timeline (~4.8s total, plays once). Two giant word-marks — "NORK" and "WOOD" — rise letter-by-letter out of masks with a stagger, spread apart and scale up from tiny to full size, while the large center product image un-clips from its collapsed center point and scales up; the six surrounding side-product thumbnails then wipe open top-to-bottom via a staggered clip-path, the whole headline block drops from mid-screen down to the bottom edge, the product captions slide up out of their masks, and the top nav drops in. Everything is one timeline, no scroll — a pure load-triggered intro.
Tech
Vanilla HTML/CSS/JS with ES module imports. Use gsap (npm) only — no plugins (no ScrollTrigger, no SplitText, no smooth-scroll). All splitting is done manually in the HTML (each letter is its own element). Import with import gsap from "gsap"; and build the whole sequence inside a DOMContentLoaded handler.
Layout / HTML
Semantic structure (class names are load-bearing — the JS/CSS query them):
<nav>
<div class="logo"><a href="#">Nork Wood</a></div>
<div class="nav-items">
<a href="#">Home</a>
<a href="#">Info</a>
<a href="#">Products</a>
<a href="#">Shop</a>
</div>
<div class="contact"><a href="#">Contact</a></div>
</nav>
<div class="container">
<div class="items">
<!-- LEFT column: 3 side items -->
<div class="items-col">
<div class="item item-side">
<div class="item-copy">
<div class="item-copy-wrapper"><p>Scandinavian Chair</p></div>
<div class="item-copy-wrapper"><p>(Seating)</p></div>
</div>
<div class="item-img"><img src="/c/lp-reveal-anim/img1.jpg" alt="" /></div>
</div>
<!-- item2 "Minimal Lamp / (Lighting)" img2, item3 "Modern Desk / (Office)" img3 -->
</div>
<!-- CENTER column: the single large hero product -->
<div class="items-col">
<div class="item-main">
<div class="item-copy">
<div class="item-copy-wrapper"><p>Elegant Sofa</p></div>
<div class="item-copy-wrapper"><p>(Living Room)</p></div>
</div>
<div class="item-img"><img src="/c/lp-reveal-anim/img4.jpg" alt="" /></div>
</div>
</div>
<!-- RIGHT column: 3 side items -->
<div class="items-col">
<!-- "Compact Shelf / (Storage)" img5, "Ornate Mirror / (Decor)" img6, "Sleek Table / (Dining)" img7 -->
</div>
</div>
<div class="header">
<div class="header-item header-item-1">
<div class="letter"><div class="letter-wrapper">N</div></div>
<div class="letter"><div class="letter-wrapper">O</div></div>
<div class="letter"><div class="letter-wrapper">R</div></div>
<div class="letter"><div class="letter-wrapper">K</div></div>
</div>
<div class="header-item header-item-2">
<div class="letter"><div class="letter-wrapper">W</div></div>
<div class="letter"><div class="letter-wrapper">O</div></div>
<div class="letter"><div class="letter-wrapper">O</div></div>
<div class="letter"><div class="letter-wrapper">D</div></div>
</div>
</div>
</div>
Details:
- 7 products total. Left column = 3
.item.item-side, center column = 1.item-main, right column = 3.item.item-side. - Product name + parenthetical category per item, in order: Scandinavian Chair (Seating), Minimal Lamp (Lighting), Modern Desk (Office) [left]; Elegant Sofa (Living Room) [center hero, img4]; Compact Shelf (Storage), Ornate Mirror (Decor), Sleek Table (Dining) [right].
- Each product caption is TWO stacked
.item-copy-wrapperblocks (name, then category), each wrapping a single<p>. Both wrappers are masks. - The headline is split manually: each of the 8 letters is a
.letter(the mask) containing a.letter-wrapper(the moving glyph). "NORK" =.header-item-1, "WOOD" =.header-item-2. - Use "Nork Wood" as the neutral placeholder furniture brand (nav logo). No real brand names.
Styling
Font: the design uses "PP Neue Montreal" (a clean neo-grotesque sans). It is not a free web font — declare font-family: "PP Neue Montreal" on html, body and let it fall back to a system sans-serif (Helvetica, Arial, sans-serif) if unavailable; the layout does not depend on the exact face. Weights used: 300 (the giant letters), 500 (nav links, captions), bold (logo).
Palette:
- Page background:
rgb(196, 196, 176)— a muted sage / warm-beige olive. - All text:
#000.
Global:
* { margin:0; padding:0; box-sizing:border-box }.html, body { width:100%; height:100%; overflow:hidden }(no scrolling — the whole thing fits one viewport).img { width:100%; height:100%; object-fit:cover }.a { text-decoration:none; color:#000; font-size:13.5px; font-weight:500 }.
Nav:
nav { position:fixed; top:0; width:100%; padding:1em; display:flex };nav > div { flex:1 }(three equal thirds)..logo a { font-weight:bold }..nav-items { display:flex; gap:1em; justify-content:center }..contact { display:flex; justify-content:flex-end }.
Container + grid:
.container { display:flex; width:100vw; height:100vh; overflow:hidden }..items { width:100%; display:flex };.items-col { flex:1; display:flex }.- Center column override:
.items-col:nth-child(2) { flex:0 0 300px; display:flex; justify-content:center }(fixed 300px-wide middle column, the two side columns share the rest).
Side items:
.item { position:relative; top:15vh; height:300px; flex:1; display:flex; flex-direction:column }(pushed down 15vh from the top)..item-copy { padding:0.5em; height:50px; display:flex; flex-direction:column; justify-content:center }..item-copy-wrapper { clip-path: polygon(0 0, 100% 0, 100% 100%, 0% 100%) }— a full-rect clip that acts as a mask for the caption<p>..item-copy-wrapper p { position:relative; margin-bottom:0.25em; font-weight:500; font-size:13.5px; line-height:100% }..item-side .item-img { flex:1; overflow:hidden; clip-path: polygon(0 0, 100% 0, 100% 0, 0 0) }— initial clip collapses the image to a zero-height sliver along its top edge (all four points at the top → invisible).
Center hero:
.item-main { position:relative; top:15vh; width:300px; height:450px; overflow:hidden }..item-main .item-img { overflow:hidden }..item-main .item-img img { transform: scale(0.5); clip-path: polygon(50% 50%, 50% 50%, 50% 50%, 50% 50%) }— initial state: shrunk to half size AND clipped to a single collapsed center point (fully hidden).
Headline:
.header { position:absolute; bottom:35%; transform-origin:center center; width:100%; display:flex }— initialbottom:35%(floating in the mid/lower screen)..header-item { position:relative; flex:1; display:flex; justify-content:center; transform: scale(0.25) }— initial each word-mark is scaled to a quarter size..header-item-1 { left: 18vw }and.header-item-2 { right: 18vw }— initial the two words sit pulled inward toward center..letter { flex:1; font-size:17vw; font-weight:300; display:flex; justify-content:center; align-items:center; clip-path: polygon(0 0, 100% 0, 100% 100%, 0% 100%) }— huge glyphs; the full-rect clip masks the wrapper inside..letter-wrapper { position:relative }(this is what moves; JS parks it below the mask).
GSAP effect (be exact)
Pre-set (initial JS states, before the timeline)
gsap.set("nav", { y: -100 }); // nav parked 100px above the top edge
gsap.set(".letter-wrapper", { y: 400 }); // every glyph shoved 400px down, hidden below its .letter mask
gsap.set(".item-copy-wrapper p", { y: 50 }); // every caption line shoved 50px down, hidden below its mask
Defaults + timeline
gsap.defaults({ duration: 1, ease: "power3.out" }); // EVERY tween below is 1s, power3.out (none overrides it)
const tl = gsap.timeline({ paused: true, delay: 0.5 }); // 0.5s delay, then...
// ...build the chain below...
tl.play();
Position params: a bare .to() appends at the end of the current timeline; "<" aligns to the start of the previous tween. There are three functional phases.
Phase A — headline letters rise (starts at t=0)
tl.to(".letter-wrapper", { y: 0, stagger: 0.1 });
All 8 .letter-wrapper glyphs slide from y:400 back to y:0 — each rises up out of its .letter mask. Stagger 0.1 left-to-right, so "N-O-R-K-W-O-O-D" pop up one after another. Duration 1 each → last glyph starts at 0.7s, this phase ends ~t=1.7.
Phase B — words nudge apart + center image un-clips (appended, starts at t≈1.7)
tl.to(".header-item-1", { left: "12vw" }) // NORK: left 18vw → 12vw (drifts left)
.to(".header-item-2", { right: "8vw" }, "<") // WOOD: right 18vw → 8vw (drifts right) — same start
.to(".item-main .item-img img", // center hero image...
{ clipPath: "polygon(0% 100%, 100% 100%, 100% 0%, 0% 0%)" }, "<"); // ...clip opens center-point → full rect
The two word-marks begin spreading outward while the center product image blooms open from its collapsed center point to a full rectangle (still at scale(0.5)). All three tweens share the same start ("<"), duration 1 → ends ~t=2.7.
Phase C — the big finish (all appended at the SAME start, t≈2.7)
All seven tweens below use "<" to align to the start of the first Phase-C tween, so they run in parallel from ~t=2.7:
tl.to(".header-item-1", { left: 0, scale: 1 }) // NORK slams to left edge + scales 0.25 → 1
.to(".header-item-2", { right: 0, scale: 1 }, "<") // WOOD slams to right edge + scales 0.25 → 1
.to(".item-main .item-img img", { scale: 1 }, "<") // center hero image scales 0.5 → 1
.to(".item-side .item-img", // the 6 side thumbnails...
{ clipPath: "polygon(0% 100%, 100% 100%, 100% 0%, 0% 0%)", stagger: 0.1 }, "<") // ...wipe open top→bottom, staggered
.to(".header", { bottom: 0 }, "<") // whole headline block drops 35% → 0 (to bottom edge)
.to(".item-copy-wrapper p", { y: 0, stagger: 0.05 }, "<") // all 14 caption lines rise out of their masks
.to("nav", { y: 0 }, "<"); // nav drops in from -100 → 0
Clip-path notes:
- The side images start clipped to their top edge (
polygon(0 0, 100% 0, 100% 0, 0 0)) and animate to the full rectpolygon(0% 100%, 100% 100%, 100% 0%, 0% 0%)— the top two points stay put, the bottom two travel down from the top, so each thumbnail wipes open downward from the top. Stagger0.1across the 6 → they cascade. - The caption lines: 14
<p>(2 per product × 7) slidey:50 → 0, stagger0.05. - All clip-path polygons have matching 4-point counts so GSAP interpolates them smoothly.
Timeline summary (seconds, relative to timeline start; add the 0.5s delay for wall-clock)
| t (s) | what happens | |------|--------------| | 0 – 1.7 | 8 headline letters rise y:400→0 out of masks, stagger 0.1 | | 1.7 – 2.7 | NORK left 18→12vw, WOOD right 18→8vw, center image clip center-point → full rect | | 2.7 – 3.7 | NORK left→0 scale→1, WOOD right→0 scale→1, center image scale 0.5→1, headline block bottom 35%→0, nav y:-100→0 | | 2.7 – 4.2 | 6 side thumbnails clip open top→bottom, stagger 0.1 | | 2.7 – 4.35 | 14 caption lines y:50→0, stagger 0.05 |
Total runtime ≈ 4.85s including the 0.5s delay. Every tween is duration:1, ease:"power3.out" (the global default — nothing overrides it).
Assets / images
7 product photographs of minimalist furniture / home objects, each shot on a plain neutral studio background (soft warm/grey seamless), matching the muted sage page. object-fit: cover.
- 1 hero image (center,
.item-main, box ~300×450 → portrait ~2:3): an elegant upholstered sofa as the flagship product. - 6 thumbnails (side columns, cover-cropped roughly square): a Scandinavian wooden chair, a minimal table/floor lamp, a modern wooden desk, a compact wooden shelving unit, an ornate decorative mirror, a sleek minimal dining table.
All should read as a matched catalog set — clean, editorial, evenly lit, neutral backdrops. If exact subjects are unavailable, any set of well-lit minimalist furniture on neutral backgrounds works.
Behavior notes
- Autoplay once on
DOMContentLoadedafter a 0.5s delay. No scroll, hover, or click triggers; the page never scrolls (overflow:hiddenonhtml,body). - The animation mixes CSS-property tweens (
left/right/bottominvw/%,yin px,scale, andclipPathpolygons) — GSAP handles the unit interpolation; keep the initial CSS values exactly as specified since the tweens read from them. - Responsive (
@media max-width: 900px):.item-side { display:none }(side thumbnails hidden, only the center hero shows);.header { bottom:45% };.header-item { transform: scale(0.5) };.header-item-1 { left:14vw };.header-item-2 { right:11vw }. The timeline itself is unchanged — it still animates the (now hidden) side images harmlessly. - No reduced-motion handling in the original; the whole reveal is the point of the page.