ToolsWaves
Free GSAP Landing Page Template ยท ToolsWaves ยท E-commerce

Free E-commerce Hero Template with Animated Discount Slider

Free e-commerce landing page hero template from ToolsWaves featuring a rotating discount-badge loader, four animated slides, counter-up stats, and a confetti burst on the CTA. Copy the HTML, CSS, and JavaScript and use it in any project โ€” no sign-up, no licensing fee.

E-commerceSliderBadgeConfettiStats Counter

About this ToolsWaves template

This is one of the free e-commerce landing page templates available on ToolsWaves. It uses GSAP animations to drive a rotating discount-badge loader, four sequential slides that highlight different value propositions (shopping experience, delivery, quality, community), animated stats counters that climb on view, and a celebratory confetti burst on the primary CTA. Everything is wrapped in a vibrant purple-coral palette designed for direct-to-consumer brands and marketplaces.

Drop this hero into the top of any e-commerce landing page โ€” Shopify-alternative storefronts, flash-sale pages, marketplace landing pages, or DTC product launches. Like every template on ToolsWaves, it is completely free, runs entirely in the browser with no backend required, and can be copied as three plain files (HTML, CSS, JS) into your project. Replace the placeholder branding with your own logo and copy, swap the demo images, and you have a production-ready hero in under an hour.

Copy the code

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8" />
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
  <title>Free E-commerce Hero Template with Animated Discount Slider</title>
  <meta name="description" content="Free GSAP e-commerce landing page hero from ToolsWaves" />
  <meta name="generator" content="ToolsWaves - https://toolswaves.in/landing-pages" />

  <!-- Open Graph -->
  <meta property="og:title" content="Free E-commerce Hero Template with Animated Discount Slider" />
  <meta property="og:description" content="Free GSAP e-commerce landing page hero from ToolsWaves" />
  <meta property="og:type" content="website" />
  <meta property="og:image" content="https://toolswaves.in/og?title=Free%20E-commerce%20Hero%20Template%20with%20Animated%20Discount%20Slider&category=Landing%20Page&icon=%F0%9F%93%84" />

  <!-- Twitter -->
  <meta name="twitter:card" content="summary_large_image" />
  <meta name="twitter:title" content="Free E-commerce Hero Template with Animated Discount Slider" />
  <meta name="twitter:description" content="Free GSAP e-commerce landing page hero from ToolsWaves" />
  <meta name="twitter:image" content="https://toolswaves.in/og?title=Free%20E-commerce%20Hero%20Template%20with%20Animated%20Discount%20Slider&category=Landing%20Page&icon=%F0%9F%93%84" />

  <!-- Bootstrap (used by template โ€” replace with your own framework if you prefer) -->
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" />

  <style>
* { margin: 0; padding: 0; box-sizing: border-box; }

:root {
    --bg:     #ffffff;
    --bg-2:   #f7f7fb;
    --ink:    #1a1b4e;
    --muted:  rgba(26, 27, 78, 0.55);
    --line:   rgba(26, 27, 78, 0.12);
    --purple: #6c5ce7;
    --purple-deep: #5444c7;
    --coral:  #f47a76;
    --coral-deep: #e0625e;
    --yellow: #ffcd40;
    --navy:   #1a1b4e;
}

html, body {
    font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
    background: var(--bg);
    color: var(--ink);
    overflow-x: hidden;
    -webkit-font-smoothing: antialiased;
    -moz-osx-font-smoothing: grayscale;
}

.back-link {
    position: fixed;
    bottom: 0.75rem;
    left: 0.75rem;
    z-index: 1000;
    color: var(--ink);
    text-decoration: none;
    font-size: 0.65rem;
    font-weight: 600;
    letter-spacing: 0.12em;
    padding: 0.45rem 0.9rem;
    background: var(--bg);
    border: 1px solid var(--line);
    border-radius: 999px;
    box-shadow: 0 4px 12px rgba(26, 27, 78, 0.1);
    opacity: 0.85;
    transition: opacity 0.3s, transform 0.3s;
}
.back-link:hover { opacity: 1; transform: translateY(-2px); }

/* =========================================================
   LOADER โ€” Rotating discount badge
   ========================================================= */
.loader {
    position: fixed;
    inset: 0;
    z-index: 999;
    background: var(--bg);
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 2rem;
}

.loader__stage {
    position: relative;
    width: 220px;
    height: 220px;
    display: flex;
    align-items: center;
    justify-content: center;
}

.loader__dashed {
    position: absolute;
    inset: 0;
    width: 100%; height: 100%;
    will-change: transform;
}
#loader-dashed {
    stroke-dasharray: 8 8;
    transform-origin: center;
}

.loader__badge {
    position: relative;
    width: 120px; height: 120px;
    border-radius: 50%;
    background: linear-gradient(135deg, #f47a76 0%, #e0625e 100%);
    color: #fff;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 0.1rem;
    box-shadow: 0 20px 40px rgba(244, 122, 118, 0.4);
    z-index: 2;
    will-change: transform;
    line-height: 1;
}
.loader__badge-pct {
    font-weight: 800;
    font-size: 2rem;
    font-variant-numeric: tabular-nums;
    letter-spacing: -0.02em;
}
.loader__badge-symbol {
    font-weight: 700;
    font-size: 1.4rem;
    margin-top: -0.5rem;
}
.loader__badge-off {
    font-weight: 800;
    font-size: 0.85rem;
    letter-spacing: 0.15em;
    margin-top: 0.2rem;
}

.loader__sparkles {
    position: absolute;
    inset: 0;
    pointer-events: none;
}
.loader__sparkles span {
    position: absolute;
    color: var(--yellow);
    font-size: 18px;
    will-change: transform, opacity;
    filter: drop-shadow(0 0 6px rgba(255, 205, 64, 0.6));
}

.loader__meta {
    display: flex;
    gap: 1.25rem;
    align-items: center;
    font-family: 'JetBrains Mono', monospace;
    font-size: 0.75rem;
    letter-spacing: 0.3em;
    color: var(--muted);
    text-transform: uppercase;
}
.loader__counter {
    color: var(--coral);
    font-weight: 700;
    font-variant-numeric: tabular-nums;
    min-width: 3ch;
}

/* =========================================================
   CARTLY SECTION
   ========================================================= */
.cartly {
    position: relative;
    width: 100%;
    min-height: 100vh;
    overflow: hidden;
    background: var(--bg);
    padding-bottom: 3rem;
}

/* ---------- Top nav ---------- */
.topnav {
    position: relative;
    z-index: 30;
    display: flex;
    align-items: center;
    gap: 2rem;
    padding: 1.5rem 3rem;
}

.topnav__logo {
    display: inline-flex;
    align-items: center;
    gap: 0.5rem;
    text-decoration: none;
    color: var(--ink);
    font-weight: 800;
    font-size: 1.3rem;
    letter-spacing: -0.01em;
    will-change: transform;
}
.topnav__logo-mark { width: 32px; height: 28px; }
.topnav__logo-mark svg { width: 100%; height: 100%; display: block; }

.topnav__links {
    display: flex;
    gap: 2rem;
    margin-left: 1.5rem;
}
.topnav__links a {
    position: relative;
    color: var(--ink);
    text-decoration: none;
    font-weight: 600;
    font-size: 0.95rem;
    padding: 0.3rem 0;
    will-change: transform;
}
.topnav__links a::after {
    content: '';
    position: absolute;
    left: 0; bottom: 0;
    width: 100%; height: 2px;
    background: var(--purple);
    transform: scaleX(0);
    transform-origin: right;
    transition: transform 0.4s cubic-bezier(0.65, 0, 0.35, 1);
    border-radius: 2px;
}
.topnav__links a:hover::after { transform: scaleX(1); transform-origin: left; }

.topnav__actions {
    margin-left: auto;
    display: flex;
    align-items: center;
    gap: 0.75rem;
}

.topnav__search {
    display: flex;
    align-items: center;
    background: var(--bg-2);
    border-radius: 8px;
    padding: 0.25rem;
    transition: box-shadow 0.3s;
    width: 280px;
}
.topnav__search:focus-within {
    box-shadow: 0 0 0 2px rgba(108, 92, 231, 0.3);
}
.topnav__search-input {
    flex: 1;
    border: none;
    background: transparent;
    padding: 0.5rem 0.75rem;
    font-size: 0.9rem;
    color: var(--ink);
    font-family: inherit;
    outline: none;
    min-width: 0;
}
.topnav__search-input::placeholder { color: var(--muted); }
.topnav__search-btn {
    width: 38px; height: 38px;
    background: var(--purple);
    color: #fff;
    border: none;
    border-radius: 6px;
    cursor: pointer;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    will-change: transform;
    transition: background 0.3s;
}
.topnav__search-btn svg { width: 16px; height: 16px; }
.topnav__search-btn:hover { background: var(--purple-deep); }

.topnav__login {
    color: var(--ink);
    text-decoration: none;
    padding: 0.65rem 1.5rem;
    border: 1.5px solid var(--line);
    border-radius: 8px;
    font-weight: 500;
    font-size: 0.9rem;
    will-change: transform;
    transition: background 0.3s, color 0.3s, border-color 0.3s;
}
.topnav__login:hover {
    background: var(--ink);
    color: var(--bg);
    border-color: var(--ink);
}

/* ---------- Side arrows ---------- */
.side-arrow {
    position: fixed;
    top: 50%;
    transform: translateY(-50%);
    z-index: 40;
    width: 48px; height: 48px;
    background: var(--bg);
    border: 1.5px solid var(--line);
    color: var(--ink);
    border-radius: 50%;
    cursor: pointer;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    box-shadow: 0 6px 16px rgba(26, 27, 78, 0.08);
    will-change: transform;
    transition: background 0.3s, color 0.3s, border-color 0.3s, box-shadow 0.3s;
}
.side-arrow svg { width: 18px; height: 18px; transition: transform 0.3s; }
.side-arrow--left  { left: 1.25rem; }
.side-arrow--right { right: 1.25rem; }
.side-arrow:hover {
    background: var(--purple);
    color: #fff;
    border-color: var(--purple);
    box-shadow: 0 10px 22px rgba(108, 92, 231, 0.35);
}
.side-arrow--left:hover svg  { transform: translateX(-3px); }
.side-arrow--right:hover svg { transform: translateX(3px); }

/* ---------- Hero ---------- */
.hero {
    position: relative;
    z-index: 10;
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 3rem;
    padding: 3rem 5rem 2rem;
    align-items: center;
}

.hero__left { max-width: 560px; padding-left: 2rem; }

.hero__title {
    font-family: 'Inter', sans-serif;
    font-weight: 800;
    font-size: clamp(2rem, 4vw, 3.25rem);
    line-height: 1.15;
    letter-spacing: -0.015em;
    color: var(--navy);
    margin-bottom: 1.75rem;
}
.hero__title .hl-coral {
    color: var(--coral);
    display: inline;
    will-change: transform;
}
.hero__word {
    display: inline-block;
    white-space: nowrap;
    margin-right: 0.25em;
}
.hero__char {
    display: inline-block;
    will-change: transform, color;
    transition: color 0.3s ease;
}

.hero__desc {
    font-size: 0.95rem;
    line-height: 1.6;
    color: var(--muted);
    margin-bottom: 2rem;
    max-width: 420px;
}

.cta-pill {
    display: inline-block;
    background: var(--purple);
    color: #fff;
    text-decoration: none;
    padding: 1rem 2.5rem;
    border-radius: 8px;
    font-weight: 600;
    font-size: 1rem;
    box-shadow: 0 10px 22px rgba(108, 92, 231, 0.35);
    will-change: transform;
    position: relative;
    overflow: hidden;
    transition: box-shadow 0.3s;
}
.cta-pill span { position: relative; z-index: 2; }
.cta-pill::before {
    content: '';
    position: absolute;
    inset: 0;
    background: var(--purple-deep);
    transform: scaleX(0);
    transform-origin: right;
    transition: transform 0.5s cubic-bezier(0.65, 0, 0.35, 1);
    z-index: 1;
}
.cta-pill:hover::before { transform: scaleX(1); transform-origin: left; }
.cta-pill:hover { box-shadow: 0 15px 30px rgba(108, 92, 231, 0.45); }

/* ---------- Right stage ---------- */
.hero__right {
    position: relative;
    min-height: 520px;
    display: flex;
    align-items: center;
    justify-content: center;
}

.deco-ring {
    position: absolute;
    top: 0; left: 10%;
    right: 0;
    width: 90%;
    height: 90%;
    will-change: transform;
    z-index: 1;
}
#deco-ring {
    transform-origin: 250px 250px;
}

.deco-curve {
    position: absolute;
    left: -4%;
    top: 25%;
    width: 120px;
    height: 280px;
    z-index: 2;
    will-change: transform;
}
#deco-curve { stroke-dasharray: 8 8; }

.portrait {
    position: relative;
    z-index: 3;
    width: 100%;
    max-width: 440px;
    height: 500px;
    overflow: hidden;
    will-change: transform;
    mask-image: radial-gradient(ellipse at center top, #000 55%, transparent 100%);
    -webkit-mask-image: radial-gradient(ellipse at center top, #000 55%, transparent 100%);
}
.portrait img {
    width: 100%; height: 100%;
    object-fit: cover;
    object-position: center 25%;
    user-select: none;
    transition: transform 0.8s ease;
}

.discount-badge {
    position: absolute;
    top: 12%;
    right: 5%;
    z-index: 5;
    width: 110px; height: 110px;
    border-radius: 50%;
    background: linear-gradient(135deg, var(--coral) 0%, var(--coral-deep) 100%);
    color: #fff;
    text-decoration: none;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    font-weight: 800;
    font-size: 1.5rem;
    line-height: 1;
    letter-spacing: -0.01em;
    box-shadow: 0 15px 30px rgba(244, 122, 118, 0.45);
    will-change: transform;
}
.discount-badge em {
    font-style: normal;
    font-size: 0.65rem;
    font-weight: 800;
    letter-spacing: 0.15em;
    margin-top: 0.2rem;
}

/* ---------- Stats card ---------- */
.stats-card {
    position: absolute;
    bottom: 5rem;
    left: 50%;
    transform: translateX(-50%);
    z-index: 20;
    display: flex;
    align-items: center;
    background: #fff;
    border-radius: 12px;
    padding: 1.25rem 2rem;
    box-shadow: 0 20px 50px rgba(26, 27, 78, 0.12);
    gap: 2rem;
    min-width: 460px;
    will-change: transform;
}
.stats-card__cell {
    display: flex;
    flex-direction: column;
    gap: 0.3rem;
    min-width: 90px;
}
.stats-card__label {
    font-size: 0.75rem;
    color: var(--muted);
    font-weight: 500;
}
.stats-card__num {
    font-size: 1.35rem;
    font-weight: 800;
    color: var(--ink);
    font-variant-numeric: tabular-nums;
    letter-spacing: -0.01em;
}
.stats-card__num--coral { color: var(--coral); }
.stats-card__divider {
    width: 1px;
    height: 34px;
    background: var(--line);
}

/* ---------- Dots ---------- */
.dots {
    position: absolute;
    bottom: 1rem;
    left: 50%;
    transform: translateX(-50%);
    z-index: 20;
    display: flex;
    gap: 0.4rem;
}
.dots span {
    width: 8px; height: 8px;
    border-radius: 50%;
    background: rgba(26, 27, 78, 0.2);
    cursor: pointer;
    transition: background 0.3s, width 0.3s;
}
.dots span.is-active {
    background: var(--purple);
    width: 30px;
    border-radius: 4px;
}

/* ---------- Initial states ---------- */
[data-magnetic], [data-link], [data-desc], [data-cta], [data-portrait], [data-badge],
[data-stats], [data-dots], [data-stage] > svg {
    opacity: 0;
}

/* ---------- Responsive ---------- */
@media (max-width: 1100px) {
    .topnav { padding: 1.25rem 1.5rem; gap: 1rem; flex-wrap: wrap; }
    .topnav__links { display: none; }
    .topnav__search { width: 200px; }
    .hero { grid-template-columns: 1fr; padding: 2rem 1.5rem; gap: 2rem; }
    .hero__left { padding-left: 0; max-width: 100%; }
    .hero__right { min-height: 400px; }
    .portrait { max-width: 320px; height: 380px; }
    .stats-card { bottom: 4rem; min-width: 360px; padding: 1rem 1.5rem; gap: 1rem; }
}
@media (max-width: 760px) {
    .topnav__search { width: 100%; order: 3; }
    .topnav__login { order: 4; }
    .hero__title { font-size: clamp(1.6rem, 7vw, 2.5rem); }
    .portrait { max-width: 240px; height: 300px; }
    .discount-badge { width: 80px; height: 80px; font-size: 1.1rem; top: 5%; right: 0; }
    .stats-card { min-width: calc(100% - 3rem); padding: 0.85rem 1rem; gap: 0.75rem; }
    .stats-card__num { font-size: 1rem; }
    .stats-card__label { font-size: 0.65rem; }
    .stats-card__cell { min-width: 70px; }
    .deco-curve { display: none; }
}
@media (max-width: 480px) {
    .side-arrow { width: 40px; height: 40px; }
    .stats-card { gap: 0.4rem; padding: 0.65rem 0.75rem; }
    .stats-card__divider { height: 24px; }
    .stats-card__num { font-size: 0.85rem; }
    .stats-card__cell { min-width: 56px; }
}

  </style>
</head>
<body>
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Banner โ€” Cartly</title>
    <link rel="preconnect" href="https://fonts.googleapis.com">
    <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
    <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&family=JetBrains+Mono:wght@500;600&display=swap" rel="stylesheet">
    <link rel="stylesheet" href="banner-cartly.css">
</head>
<body>
    

    <!-- ========== LOADER โ€” Rotating discount badge ========== -->
    <div class="loader" id="loader">
        <div class="loader__stage">
            <svg class="loader__dashed" viewBox="0 0 220 220" aria-hidden="true">
                <circle cx="110" cy="110" r="95" fill="none" stroke="#ffcd40" stroke-width="3"
                        stroke-dasharray="8 8" id="loader-dashed"/>
            </svg>
            <div class="loader__badge" id="loader-badge">
                <span class="loader__badge-pct" id="loader-pct">0</span>
                <span class="loader__badge-symbol">%</span>
                <span class="loader__badge-off">OFF</span>
            </div>
            <div class="loader__sparkles" id="loader-sparkles"></div>
        </div>
        <div class="loader__meta">
            <span class="loader__label" id="loader-label">UNLOCKING DEALS</span>
            <span class="loader__counter" id="loader-counter">0%</span>
        </div>
    </div>

    <section class="cartly" id="cartly">

        <!-- ========== TOP NAV ========== -->
        <header class="topnav">
            <a href="#" class="topnav__logo" data-magnetic>
                <span class="topnav__logo-mark" id="logo-mark">
                    <svg viewBox="0 0 36 32" aria-hidden="true">
                        <path d="M6 6h4l2 14h16l2-10H14" fill="none" stroke="#6c5ce7" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round"/>
                        <circle cx="14" cy="26" r="2.5" fill="#6c5ce7"/>
                        <circle cx="26" cy="26" r="2.5" fill="#6c5ce7"/>
                    </svg>
                </span>
                <span class="topnav__logo-text">Cartly</span>
            </a>

            <nav class="topnav__links">
                <a href="#" data-magnetic data-link>Home</a>
                <a href="#" data-magnetic data-link>Featured</a>
                <a href="#" data-magnetic data-link>Help</a>
            </nav>

            <div class="topnav__actions">
                <form class="topnav__search" data-form onsubmit="event.preventDefault()">
                    <input type="text" class="topnav__search-input" placeholder="Search products...">
                    <button type="submit" class="topnav__search-btn" data-magnetic aria-label="Search">
                        <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
                            <circle cx="11" cy="11" r="7"/>
                            <path d="M20 20l-4-4" stroke-linecap="round"/>
                        </svg>
                    </button>
                </form>
                <a href="#" class="topnav__login" data-magnetic>Login</a>
            </div>
        </header>

        <!-- Side arrows -->
        <button class="side-arrow side-arrow--left" id="prev" data-magnetic aria-label="Previous">
            <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2"><path d="M15 6l-6 6 6 6" stroke-linecap="round" stroke-linejoin="round"/></svg>
        </button>
        <button class="side-arrow side-arrow--right" id="next" data-magnetic aria-label="Next">
            <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2"><path d="M9 6l6 6-6 6" stroke-linecap="round" stroke-linejoin="round"/></svg>
        </button>

        <!-- ========== HERO ========== -->
        <div class="hero" id="hero">
            <div class="hero__left">
                <h1 class="hero__title" id="title"></h1>
                <p class="hero__desc" id="desc" data-desc></p>
                <a href="#" class="cta-pill" data-magnetic data-cta>
                    <span id="cta-label">Join Now</span>
                </a>
            </div>

            <div class="hero__right" id="stage" data-stage>
                <!-- Decorative dashed yellow circle -->
                <svg class="deco-ring" viewBox="0 0 500 500" aria-hidden="true">
                    <circle cx="250" cy="250" r="220" fill="none" stroke="#ffcd40" stroke-width="3" stroke-dasharray="10 10" id="deco-ring"/>
                </svg>
                <!-- Gray dashed left curve -->
                <svg class="deco-curve" viewBox="0 0 200 400" aria-hidden="true">
                    <path d="M 180 20 Q 20 100 20 200 Q 20 300 180 380"
                          fill="none" stroke="#d1d5db" stroke-width="2" stroke-dasharray="8 8" id="deco-curve"/>
                </svg>
                <!-- Portrait image -->
                <div class="portrait" id="portrait" data-portrait>
                    <img id="portrait-img"
                         src="https://images.unsplash.com/photo-1506794778202-cad84cf45f1d?auto=format&fit=crop&w=700&q=80"
                         alt="" draggable="false" onerror="this.style.display='none'">
                </div>
                <!-- Coral discount badge -->
                <a href="#" class="discount-badge" id="discount-badge" data-magnetic data-badge>
                    <span id="badge-pct">50</span>%
                    <em id="badge-label">OFF</em>
                </a>
            </div>
        </div>

        <!-- Stats card -->
        <div class="stats-card" id="stats-card" data-stats>
            <div class="stats-card__cell">
                <span class="stats-card__label">Customer</span>
                <span class="stats-card__num" data-stat="customer">0</span>
            </div>
            <div class="stats-card__divider"></div>
            <div class="stats-card__cell">
                <span class="stats-card__label">Traffic</span>
                <span class="stats-card__num stats-card__num--coral" data-stat="traffic">0</span>
            </div>
            <div class="stats-card__divider"></div>
            <div class="stats-card__cell">
                <span class="stats-card__label">Last Sale</span>
                <span class="stats-card__num" data-stat="sale">0</span>
            </div>
        </div>

        <!-- Dots -->
        <div class="dots" data-dots>
            <span class="is-active" data-dot="0"></span>
            <span data-dot="1"></span>
            <span data-dot="2"></span>
            <span data-dot="3"></span>
        </div>
    </section>

    <script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.12.5/gsap.min.js"></script>
    <script src="banner-cartly.js"></script>
</body>
</html>

</body>
</html>
JSbanner-cartly.jsโ€” GSAP animation timeline
(() => {
    const root = document.getElementById('cartly');
    if (!root) return;

    // -------------------------------------------------------------------
    //  SLIDES
    // -------------------------------------------------------------------
    const slides = [
        {
            titleHtml: 'Get an Easier and More Enjoyable Online <span class="hl-coral">Shopping Experience</span>',
            desc: 'Get inspiration and creative ideas to beautify your design with our products, get attractive prizes by purchasing certain products.',
            cta: 'Join Now',
            badgePct: '50',
            badgeLabel: 'OFF',
            portrait: 'https://images.unsplash.com/photo-1506794778202-cad84cf45f1d?auto=format&fit=crop&w=700&q=80',
            stats: { customer: '467K', traffic: '35.0M', sale: '150K' },
        },
        {
            titleHtml: 'Fast and Reliable Doorstep <span class="hl-coral">Delivery Service</span> Nationwide',
            desc: 'Track every order in real time with live updates, photo proof of delivery, and instant refunds if anything goes sideways along the route.',
            cta: 'Track Order',
            badgePct: 'FREE',
            badgeLabel: 'SHIP',
            portrait: 'https://images.unsplash.com/photo-1519085360753-af0119f7cbe7?auto=format&fit=crop&w=700&q=80',
            stats: { customer: '982K', traffic: '52.4M', sale: '230K' },
        },
        {
            titleHtml: 'Handpicked Products for <span class="hl-coral">Premium Quality</span> Every Time',
            desc: 'Every item is tested by our team of product specialists before it reaches your cart, so what you see is exactly what shows up on your doorstep.',
            cta: 'Explore Collection',
            badgePct: 'NEW',
            badgeLabel: 'ARRIVALS',
            portrait: 'https://images.unsplash.com/photo-1500648767791-00dcc994a43e?auto=format&fit=crop&w=700&q=80',
            stats: { customer: '1.2M', traffic: '68.7M', sale: '340K' },
        },
        {
            titleHtml: 'Join Our Growing Global <span class="hl-coral">Shopping Community</span> Today',
            desc: 'Members-only drops, early access to sales, and a loyalty program that pays you back every single time you complete a purchase. No catches.',
            cta: 'Sign Up Free',
            badgePct: 'VIP',
            badgeLabel: 'PERKS',
            portrait: 'https://images.unsplash.com/photo-1539571696357-5a69c17a67c6?auto=format&fit=crop&w=700&q=80',
            stats: { customer: '2.4M', traffic: '98.1M', sale: '512K' },
        },
    ];
    let currentSlide = 0;

    // -------------------------------------------------------------------
    //  BUILD LOADER SPARKLES
    // -------------------------------------------------------------------
    const sparklesHost = document.getElementById('loader-sparkles');
    const SPARK_COUNT = 8;
    for (let i = 0; i < SPARK_COUNT; i++) {
        const s = document.createElement('span');
        s.textContent = '\u2728';
        const angle = (i / SPARK_COUNT) * Math.PI * 2;
        s.style.left = (110 + Math.cos(angle) * 90) + 'px';
        s.style.top  = (110 + Math.sin(angle) * 90) + 'px';
        s.style.transform = 'translate(-50%, -50%)';
        sparklesHost.appendChild(s);
    }
    const sparkles = sparklesHost.querySelectorAll('span');

    // -------------------------------------------------------------------
    //  REFERENCES
    // -------------------------------------------------------------------
    const loader = document.getElementById('loader');
    const loaderCounter = document.getElementById('loader-counter');
    const loaderLabel = document.getElementById('loader-label');
    const loaderDashed = document.getElementById('loader-dashed');
    const loaderBadge = document.getElementById('loader-badge');
    const loaderPct = document.getElementById('loader-pct');

    const magnetics = root.querySelectorAll('[data-magnetic]');
    const titleEl = document.getElementById('title');
    const descEl = document.getElementById('desc');
    const ctaLabel = document.getElementById('cta-label');
    const ctaPill = root.querySelector('[data-cta]');
    const portrait = document.getElementById('portrait');
    const portraitImg = document.getElementById('portrait-img');
    const discountBadge = document.getElementById('discount-badge');
    const badgePct = document.getElementById('badge-pct');
    const badgeLabel = document.getElementById('badge-label');
    const decoRing = document.getElementById('deco-ring');
    const decoCurve = document.getElementById('deco-curve');
    const stage = document.getElementById('stage');
    const statsCard = document.getElementById('stats-card');
    const statEls = root.querySelectorAll('[data-stat]');
    const dots = root.querySelectorAll('.dots span');
    const logoMark = document.getElementById('logo-mark');
    const prevBtn = document.getElementById('prev');
    const nextBtn = document.getElementById('next');

    // -------------------------------------------------------------------
    //  INITIAL STATES
    // -------------------------------------------------------------------
    gsap.set(magnetics, { y: -15, opacity: 0 });
    gsap.set(descEl, { y: 20, opacity: 0 });
    gsap.set(ctaPill, { scale: 0.9, opacity: 0 });
    gsap.set(portrait, { scale: 0.85, opacity: 0 });
    gsap.set(discountBadge, { scale: 0, opacity: 0, rotation: -90 });
    gsap.set(decoRing, { opacity: 0, rotation: -45, transformOrigin: '250px 250px' });
    gsap.set(decoCurve, { opacity: 0 });
    gsap.set(statsCard, { y: 30, opacity: 0 });
    gsap.set(dots, { opacity: 0 });

    // -------------------------------------------------------------------
    //  LOADER TIMELINE
    // -------------------------------------------------------------------
    const loaderTl = gsap.timeline({ onComplete: playScene });

    // Dashed ring rotates
    gsap.to(loaderDashed, {
        rotation: 360,
        duration: 6,
        repeat: -1,
        ease: 'none',
        transformOrigin: 'center',
    });

    // Badge pulses
    gsap.to(loaderBadge, {
        scale: 1.06,
        duration: 1.2,
        yoyo: true,
        repeat: -1,
        ease: 'sine.inOut',
    });

    // Sparkles twinkle
    sparkles.forEach((s, i) => {
        gsap.fromTo(s,
            { scale: 0, opacity: 0 },
            {
                scale: 1.2, opacity: 1,
                duration: 0.6,
                yoyo: true,
                repeat: -1,
                ease: 'sine.inOut',
                delay: i * 0.15,
            }
        );
    });

    // Percentage counter in badge
    const bp = { v: 0 };
    loaderTl.to(bp, {
        v: 50,
        duration: 2.2,
        ease: 'power2.out',
        onUpdate: () => { loaderPct.textContent = Math.floor(bp.v); },
    }, 0);

    // Progress counter + labels
    const p = { v: 0 };
    loaderTl.to(p, {
        v: 100,
        duration: 2.2,
        ease: 'power1.inOut',
        onUpdate: () => {
            loaderCounter.textContent = Math.floor(p.v) + '%';
            if (p.v > 30 && loaderLabel.textContent === 'UNLOCKING DEALS') loaderLabel.textContent = 'STOCKING SHELVES';
            if (p.v > 65 && loaderLabel.textContent === 'STOCKING SHELVES') loaderLabel.textContent = 'ALMOST OPEN';
            if (p.v > 95 && loaderLabel.textContent === 'ALMOST OPEN') loaderLabel.textContent = 'READY TO SHOP';
        },
    }, 0);

    // Exit
    loaderTl.to([loaderCounter, loaderLabel], {
        y: -10, opacity: 0, duration: 0.3, stagger: 0.05,
    }, '+=0.3');
    loaderTl.to('.loader__stage', {
        scale: 1.3, opacity: 0, duration: 0.7, ease: 'power3.in',
    }, '-=0.3');
    loaderTl.to(loader, {
        opacity: 0, duration: 0.4, ease: 'power2.inOut',
    }, '-=0.3');
    loaderTl.set(loader, { display: 'none' });

    // -------------------------------------------------------------------
    //  RENDER SLIDE
    // -------------------------------------------------------------------
    let titleChars = [];

    function wrapWordChars(word, parent) {
        const wordSpan = document.createElement('span');
        wordSpan.className = 'hero__word';
        [...word].forEach((ch) => {
            const charSpan = document.createElement('span');
            charSpan.className = 'hero__char';
            charSpan.textContent = ch;
            wordSpan.appendChild(charSpan);
        });
        parent.appendChild(wordSpan);
    }

    function splitTitle(html) {
        // Render html first so coral span is available
        titleEl.innerHTML = html;
        // Clone children, split text nodes into words, wrap each word
        const children = [...titleEl.childNodes];
        titleEl.innerHTML = '';

        children.forEach((node) => {
            if (node.nodeType === Node.TEXT_NODE) {
                const words = node.textContent.split(/\s+/).filter(Boolean);
                words.forEach((w) => wrapWordChars(w, titleEl));
            } else if (node.nodeType === Node.ELEMENT_NODE) {
                // Preserve element (e.g., .hl-coral) and wrap its words inside it
                const wrapper = document.createElement(node.tagName);
                wrapper.className = node.className;
                const words = node.textContent.split(/\s+/).filter(Boolean);
                words.forEach((w) => wrapWordChars(w, wrapper));
                titleEl.appendChild(wrapper);
            }
        });

        return titleEl.querySelectorAll('.hero__char');
    }

    function parseStatNum(str) {
        // "467K" -> 467, "35.0M" -> 35, "1.2M" -> 1.2
        const m = str.match(/([\d.]+)/);
        return m ? parseFloat(m[1]) : 0;
    }

    function animateStat(el, targetStr) {
        const target = parseStatNum(targetStr);
        const suffix = targetStr.replace(/[\d.]+/, '');
        const decimal = targetStr.includes('.');
        const obj = { v: 0 };
        gsap.to(obj, {
            v: target,
            duration: 1.2,
            ease: 'power2.out',
            onUpdate: () => {
                const val = decimal ? obj.v.toFixed(1) : Math.floor(obj.v);
                el.textContent = val + suffix;
            },
        });
    }

    function renderSlide(idx, direction = 1) {
        const slide = slides[idx];
        const outTl = gsap.timeline();

        outTl.to(titleChars, {
            yPercent: -110, opacity: 0,
            duration: 0.35,
            stagger: 0.012,
            ease: 'power2.in',
        }, 0);
        outTl.to([descEl, ctaLabel], {
            y: -15, opacity: 0, duration: 0.3, ease: 'power2.in',
        }, 0);
        outTl.to(portrait, {
            scale: 0.9, opacity: 0.3, duration: 0.4, ease: 'power2.in',
        }, 0);
        outTl.to(discountBadge, {
            scale: 0.6, opacity: 0, rotation: 180 * direction,
            duration: 0.4, ease: 'power2.in',
        }, 0);
        outTl.to(statsCard, {
            y: 15, opacity: 0, duration: 0.3, ease: 'power2.in',
        }, 0);

        outTl.call(() => {
            // Rebuild title
            titleChars = splitTitle(slide.titleHtml);
            descEl.textContent = slide.desc;
            ctaLabel.textContent = slide.cta;
            portraitImg.src = slide.portrait;
            badgePct.textContent = slide.badgePct;
            badgeLabel.textContent = slide.badgeLabel;

            // Update dots
            dots.forEach((d, i) => d.classList.toggle('is-active', i === idx));

            // Animate in
            gsap.set(titleChars, { yPercent: 110, opacity: 0 });
            gsap.set([descEl, ctaLabel], { y: 15, opacity: 0 });

            gsap.to(titleChars, {
                yPercent: 0, opacity: 1,
                duration: 0.7,
                stagger: 0.012,
                ease: 'power3.out',
            });
            gsap.to(descEl, {
                y: 0, opacity: 1,
                duration: 0.6,
                delay: 0.25,
                ease: 'power3.out',
            });
            gsap.to(ctaLabel, {
                y: 0, opacity: 1,
                duration: 0.5,
                delay: 0.4,
                ease: 'power3.out',
            });
            gsap.to(portrait, {
                scale: 1, opacity: 1,
                duration: 0.9,
                ease: 'power3.out',
            });
            gsap.to(discountBadge, {
                scale: 1, opacity: 1, rotation: 0,
                duration: 0.7,
                delay: 0.2,
                ease: 'back.out(1.8)',
            });
            gsap.to(statsCard, {
                y: 0, opacity: 1,
                duration: 0.6,
                delay: 0.35,
                ease: 'power3.out',
                onStart: () => {
                    animateStat(statEls[0], slide.stats.customer);
                    animateStat(statEls[1], slide.stats.traffic);
                    animateStat(statEls[2], slide.stats.sale);
                },
            });

            attachTitleHover();
        });
    }

    function attachTitleHover() {
        titleChars.forEach((c) => {
            c.addEventListener('mouseenter', () => gsap.to(c, { color: '#6c5ce7', duration: 0.2 }));
            c.addEventListener('mouseleave', () => {
                const isCoral = c.parentElement && c.parentElement.classList.contains('hl-coral');
                gsap.to(c, { color: isCoral ? '#f47a76' : '#1a1b4e', duration: 0.4 });
            });
        });
    }

    // -------------------------------------------------------------------
    //  MAIN SCENE ENTRANCE
    // -------------------------------------------------------------------
    function playScene() {
        // Initial content
        const slide = slides[0];
        titleChars = splitTitle(slide.titleHtml);
        descEl.textContent = slide.desc;
        ctaLabel.textContent = slide.cta;
        badgePct.textContent = slide.badgePct;
        badgeLabel.textContent = slide.badgeLabel;

        gsap.set(titleChars, { yPercent: 110, opacity: 0 });

        const tl = gsap.timeline({ defaults: { ease: 'power3.out' } });

        // Nav
        tl.to(magnetics, {
            y: 0, opacity: 1,
            duration: 0.6,
            stagger: 0.04,
        }, 0);

        // Decorative ring + curve
        tl.to(decoRing, {
            opacity: 1, rotation: 0,
            duration: 1.4,
            ease: 'power4.out',
        }, 0.3);
        tl.to(decoCurve, { opacity: 1, duration: 0.8 }, 0.6);

        // Portrait
        tl.to(portrait, {
            scale: 1, opacity: 1,
            duration: 1.2,
            ease: 'power4.out',
        }, 0.5);

        // Title
        tl.to(titleChars, {
            yPercent: 0, opacity: 1,
            duration: 1,
            stagger: 0.018,
            ease: 'expo.out',
        }, 0.7);

        // Desc + CTA
        tl.to(descEl, { y: 0, opacity: 1, duration: 0.7 }, 1.1);
        tl.to(ctaPill, {
            scale: 1, opacity: 1,
            duration: 0.7,
            ease: 'back.out(1.6)',
        }, 1.3);

        // Discount badge
        tl.to(discountBadge, {
            scale: 1, opacity: 1, rotation: 0,
            duration: 0.9,
            ease: 'back.out(1.8)',
        }, 1.1);

        // Stats
        tl.to(statsCard, {
            y: 0, opacity: 1,
            duration: 0.8,
            onStart: () => {
                animateStat(statEls[0], slide.stats.customer);
                animateStat(statEls[1], slide.stats.traffic);
                animateStat(statEls[2], slide.stats.sale);
            },
        }, 1.5);

        // Dots
        tl.to(dots, { opacity: 1, duration: 0.3, stagger: 0.05 }, 1.7);

        tl.call(() => {
            attachTitleHover();
            startContinuous();
            enableInteractions();
        }, null, 1.9);
    }

    // -------------------------------------------------------------------
    //  CONTINUOUS
    // -------------------------------------------------------------------
    function startContinuous() {
        // Yellow dashed ring rotates continuously
        gsap.to(decoRing, {
            rotation: 360,
            duration: 40,
            repeat: -1,
            ease: 'none',
            transformOrigin: '250px 250px',
        });

        // Discount badge gentle bob
        gsap.to(discountBadge, {
            y: -8,
            duration: 1.8,
            yoyo: true,
            repeat: -1,
            ease: 'sine.inOut',
        });
    }

    // -------------------------------------------------------------------
    //  INTERACTIONS
    // -------------------------------------------------------------------
    function enableInteractions() {

        // ---- Magnetic ----
        magnetics.forEach((el) => {
            const strength = el.classList.contains('cta-pill') ? 0.3
                : el.classList.contains('discount-badge') ? 0.4
                : el.classList.contains('side-arrow') ? 0.4
                : el.classList.contains('topnav__logo') ? 0.2
                : 0.22;
            el.addEventListener('mousemove', (e) => {
                const r = el.getBoundingClientRect();
                const cx = r.left + r.width / 2;
                const cy = r.top + r.height / 2;
                gsap.to(el, {
                    x: (e.clientX - cx) * strength,
                    y: (e.clientY - cy) * strength,
                    duration: 0.4, ease: 'power3.out',
                });
            });
            el.addEventListener('mouseleave', () => {
                gsap.to(el, { x: 0, y: 0, duration: 0.6, ease: 'elastic.out(1, 0.4)' });
            });
        });

        // ---- Logo cart wheels spin ----
        const logoEl = root.querySelector('.topnav__logo');
        logoEl.addEventListener('mouseenter', () => {
            gsap.to(logoMark.querySelectorAll('circle'), {
                rotation: 720,
                duration: 0.8,
                ease: 'power3.inOut',
                transformOrigin: 'center',
                stagger: 0.05,
            });
        });

        // ---- Discount badge rotates on hover ----
        discountBadge.addEventListener('mouseenter', () => {
            gsap.to(discountBadge, {
                rotation: 15,
                scale: 1.08,
                duration: 0.4,
                ease: 'back.out(2)',
            });
        });
        discountBadge.addEventListener('mouseleave', () => {
            gsap.to(discountBadge, {
                rotation: 0, scale: 1,
                duration: 0.5,
                ease: 'elastic.out(1, 0.4)',
            });
        });

        // ---- CTA click: confetti burst ----
        ctaPill.addEventListener('click', (e) => {
            e.preventDefault();
            const rect = ctaPill.getBoundingClientRect();
            const cx = rect.left + rect.width / 2;
            const cy = rect.top + rect.height / 2;
            const colors = ['#6c5ce7', '#f47a76', '#ffcd40', '#1a1b4e'];
            for (let i = 0; i < 12; i++) {
                const s = document.createElement('span');
                s.style.cssText = `position:fixed;left:${cx}px;top:${cy}px;width:8px;height:8px;background:${colors[i % colors.length]};pointer-events:none;z-index:200;transform:translate(-50%,-50%);border-radius:2px;`;
                document.body.appendChild(s);
                const a = (i / 12) * Math.PI * 2;
                const dist = 80 + Math.random() * 40;
                gsap.fromTo(s,
                    { x: 0, y: 0, opacity: 1, rotation: 0 },
                    {
                        x: Math.cos(a) * dist,
                        y: Math.sin(a) * dist,
                        opacity: 0,
                        scale: 0.3,
                        rotation: 360,
                        duration: 0.9,
                        ease: 'power2.out',
                        onComplete: () => s.remove(),
                    }
                );
            }
        });

        // ---- Stats card hover: lift ----
        statsCard.addEventListener('mouseenter', () => {
            gsap.to(statsCard, { y: -6, scale: 1.02, duration: 0.4, ease: 'back.out(2)' });
        });
        statsCard.addEventListener('mouseleave', () => {
            gsap.to(statsCard, { y: 0, scale: 1, duration: 0.5, ease: 'elastic.out(1, 0.4)' });
        });

        // ---- Slider arrows ----
        const goSlide = (dir) => {
            const next = (currentSlide + dir + slides.length) % slides.length;
            currentSlide = next;
            renderSlide(next, dir);
        };
        prevBtn.addEventListener('click', () => goSlide(-1));
        nextBtn.addEventListener('click', () => goSlide(1));

        // ---- Dots ----
        dots.forEach((dot, i) => {
            dot.addEventListener('click', () => {
                if (i === currentSlide) return;
                const direction = i > currentSlide ? 1 : -1;
                currentSlide = i;
                renderSlide(i, direction);
            });
        });

        // ---- Keyboard ----
        window.addEventListener('keydown', (e) => {
            if (e.key === 'ArrowLeft') goSlide(-1);
            if (e.key === 'ArrowRight') goSlide(1);
        });

        // ---- Auto-advance ----
        let autoTimer = setInterval(() => goSlide(1), 8000);
        root.addEventListener('mouseenter', () => clearInterval(autoTimer));
        root.addEventListener('mouseleave', () => {
            clearInterval(autoTimer);
            autoTimer = setInterval(() => goSlide(1), 8000);
        });

        // ---- Portrait 3D tilt ----
        let mx = 0, my = 0, cmx = 0, cmy = 0;
        stage.addEventListener('mousemove', (e) => {
            const r = stage.getBoundingClientRect();
            mx = ((e.clientX - r.left) / r.width - 0.5) * 2;
            my = ((e.clientY - r.top) / r.height - 0.5) * 2;
        });
        stage.addEventListener('mouseleave', () => { mx = 0; my = 0; });
        gsap.ticker.add(() => {
            cmx += (mx - cmx) * 0.06;
            cmy += (my - cmy) * 0.06;
            gsap.set(portrait, {
                rotationY: cmx * 8,
                rotationX: -cmy * 5,
                transformPerspective: 1000,
                transformOrigin: 'center',
            });
            gsap.set(decoCurve, { x: -cmx * 12, y: -cmy * 8 });
        });
    }
})();

More GSAP landing pages