ToolsWaves
Free GSAP Landing Page Template ยท ToolsWaves ยท Agency

Free Creative Agency Hero Template with Scramble-Text Loader

Free creative agency landing page template from ToolsWaves featuring a scramble-text loader where letters cycle through random characters, a looping background video, custom cursor, grid overlay, massive typography that inverts on hover, and magnetic navigation. Free for commercial use.

EditorialScramble TextCinematicBackground VideoGrid Overlay

About this ToolsWaves template

This free creative agency landing page template on ToolsWaves uses a scramble-text effect โ€” letters cycling through random characters before snapping into the brand name โ€” to immediately signal that this is a digitally-native brand. Behind everything plays a looping background video, overlaid with a faint grid that anchors the composition. The massive typography inverts on hover, the custom cursor follows mouse movement, and magnetic nav links pull toward the cursor as you approach them.

Use this template for premium creative agencies, video production houses, motion design studios, or any brand whose work is the product they sell. ToolsWaves provides this template free for any project โ€” copy the HTML, CSS, and JavaScript bundle and replace the placeholder text and demo video with your own brand assets. The structure is intentionally cinematic and works best when paired with high-quality video footage representing your studio's work.

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 Creative Agency Hero Template with Scramble-Text Loader</title>
  <meta name="description" content="Free GSAP creative agency landing page hero from ToolsWaves" />
  <meta name="generator" content="ToolsWaves - https://toolswaves.in/landing-pages" />

  <!-- Open Graph -->
  <meta property="og:title" content="Free Creative Agency Hero Template with Scramble-Text Loader" />
  <meta property="og:description" content="Free GSAP creative agency landing page hero from ToolsWaves" />
  <meta property="og:type" content="website" />
  <meta property="og:image" content="https://toolswaves.in/og?title=Free%20Creative%20Agency%20Hero%20Template%20with%20Scramble-Text%20Loader&category=Landing%20Page&icon=%F0%9F%93%84" />

  <!-- Twitter -->
  <meta name="twitter:card" content="summary_large_image" />
  <meta name="twitter:title" content="Free Creative Agency Hero Template with Scramble-Text Loader" />
  <meta name="twitter:description" content="Free GSAP creative agency landing page hero from ToolsWaves" />
  <meta name="twitter:image" content="https://toolswaves.in/og?title=Free%20Creative%20Agency%20Hero%20Template%20with%20Scramble-Text%20Loader&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:    #06080b;
    --ink:   #ffffff;
    --muted: rgba(255, 255, 255, 0.55);
    --dim:   rgba(255, 255, 255, 0.12);
    --line:  rgba(255, 255, 255, 0.08);
}

html, body {
    font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
    background: var(--bg);
    color: var(--ink);
    overflow: hidden;
    cursor: none;
    -webkit-font-smoothing: antialiased;
    -moz-osx-font-smoothing: grayscale;
}
@media (hover: none), (pointer: coarse) { html, body { cursor: auto; } }

.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: rgba(255, 255, 255, 0.06);
    border: 1px solid rgba(255, 255, 255, 0.15);
    border-radius: 999px;
    backdrop-filter: blur(8px);
    cursor: none;
    opacity: 0.85;
    transition: opacity 0.3s, transform 0.3s;
}
.back-link:hover { opacity: 1; transform: translateY(-2px); }

/* =========================================================
   CUSTOM CURSOR
   ========================================================= */
.cursor-dot, .cursor-ring {
    position: fixed;
    top: 0; left: 0;
    pointer-events: none;
    z-index: 10000;
    transform: translate(-50%, -50%);
    mix-blend-mode: difference;
    will-change: transform;
}
.cursor-dot {
    width: 5px; height: 5px;
    background: #ffffff;
    border-radius: 50%;
}
.cursor-ring {
    width: 32px; height: 32px;
    border: 1px solid rgba(255, 255, 255, 0.5);
    border-radius: 50%;
    transition: width 0.3s, height 0.3s, border-color 0.3s;
}
.cursor-ring.is-hover {
    width: 60px; height: 60px;
    border-color: #fff;
}
@media (hover: none), (pointer: coarse) { .cursor-dot, .cursor-ring { display: none; } }

/* =========================================================
   LOADER โ€” Scramble text + grid draw
   ========================================================= */
.loader {
    position: fixed;
    inset: 0;
    z-index: 999;
    background: var(--bg);
    display: flex;
    align-items: center;
    justify-content: center;
}

.loader__grid {
    position: absolute;
    inset: 0;
    background-image: repeating-linear-gradient(
        90deg,
        transparent 0,
        transparent calc(100% / 16 - 1px),
        rgba(255, 255, 255, 0.08) calc(100% / 16 - 1px),
        rgba(255, 255, 255, 0.08) calc(100% / 16)
    );
    opacity: 0;
}

.loader__content {
    position: relative;
    z-index: 2;
    text-align: center;
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 1.5rem;
}

.loader__diamond {
    width: 48px;
    height: 48px;
    will-change: transform;
}
.loader__diamond svg { width: 100%; height: 100%; }

.loader__title {
    font-family: 'Inter', sans-serif;
    font-weight: 800;
    font-size: clamp(3rem, 10vw, 8rem);
    line-height: 1;
    letter-spacing: -0.04em;
    color: var(--ink);
    display: inline-flex;
    gap: 2px;
}
.loader__title span {
    display: inline-block;
    font-variant-numeric: tabular-nums;
    will-change: transform;
}

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

/* =========================================================
   EVOLVE SECTION
   ========================================================= */
.evolve {
    position: relative;
    width: 100%;
    height: 100vh;
    min-height: 720px;
    overflow: hidden;
    background: var(--bg);
}

.evolve__video {
    position: absolute;
    inset: 0;
    width: 100%; height: 100%;
    object-fit: cover;
    z-index: 1;
    filter: brightness(0.4) contrast(1.1) saturate(0.9);
    will-change: transform;
}
.evolve__overlay {
    position: absolute;
    inset: 0;
    z-index: 2;
    background:
        radial-gradient(ellipse at center, rgba(6, 8, 11, 0.3) 0%, rgba(6, 8, 11, 0.85) 100%),
        linear-gradient(180deg, rgba(6, 8, 11, 0.7) 0%, rgba(6, 8, 11, 0.1) 30%, rgba(6, 8, 11, 0.5) 70%, rgba(6, 8, 11, 0.9) 100%);
    pointer-events: none;
}

.evolve__grid {
    position: absolute;
    inset: 0;
    z-index: 3;
    background-image: repeating-linear-gradient(
        90deg,
        transparent 0,
        transparent calc(100% / 16 - 1px),
        rgba(255, 255, 255, 0.05) calc(100% / 16 - 1px),
        rgba(255, 255, 255, 0.05) calc(100% / 16)
    );
    pointer-events: none;
}

/* ---------- Top nav ---------- */
.topnav {
    position: relative;
    z-index: 20;
    display: grid;
    grid-template-columns: 1fr 2fr 2fr auto;
    gap: 2rem;
    padding: 1.25rem 2rem;
    align-items: flex-start;
}

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

.topnav__desc {
    font-size: 0.75rem;
    font-weight: 500;
    color: var(--ink);
    line-height: 1.45;
}

.topnav__contact {
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    gap: 0.1rem;
    font-size: 0.75rem;
    font-weight: 500;
    line-height: 1.45;
}
.topnav__email {
    color: var(--ink);
    text-decoration: none;
    position: relative;
    cursor: none;
    will-change: transform;
}
.topnav__email::after {
    content: '';
    position: absolute;
    left: 0; bottom: -2px;
    width: 100%; height: 1px;
    background: currentColor;
    transform: scaleX(0);
    transform-origin: right;
    transition: transform 0.4s cubic-bezier(0.65, 0, 0.35, 1);
}
.topnav__email:hover::after { transform: scaleX(1); transform-origin: left; }
.topnav__loc { color: var(--ink); }

.topnav__actions {
    display: flex;
    gap: 0.6rem;
    align-items: center;
}
.topnav__lang {
    background: transparent;
    border: none;
    color: var(--ink);
    font-weight: 600;
    font-size: 0.85rem;
    letter-spacing: 0.1em;
    cursor: none;
    padding: 0.3rem 0.6rem;
    will-change: transform;
    font-family: inherit;
}
.topnav__menu {
    display: inline-flex;
    align-items: center;
    gap: 0.4rem;
    background: var(--ink);
    color: #000;
    border: none;
    padding: 0.5rem 1.1rem;
    font-size: 0.85rem;
    font-weight: 600;
    font-family: inherit;
    letter-spacing: 0.05em;
    cursor: none;
    will-change: transform;
    transition: background 0.3s ease, color 0.3s ease;
}
.topnav__menu svg { width: 11px; height: 11px; transition: transform 0.3s; }
.topnav__menu:hover { background: #dcdcdc; }
.topnav__menu:hover svg { transform: rotate(180deg); }

/* ---------- Side labels ---------- */
.side-label {
    position: absolute;
    top: 50%;
    transform: translateY(-50%);
    z-index: 15;
    display: flex;
    align-items: center;
    gap: 0.75rem;
    font-size: 0.8rem;
    font-weight: 500;
    color: var(--ink);
    pointer-events: auto;
    will-change: transform;
}
.side-label--left  { left: 2rem; }
.side-label--right { right: 2rem; }
.side-label__bar {
    display: inline-block;
    width: 40px;
    height: 1px;
    background: var(--ink);
    transition: width 0.5s cubic-bezier(0.65, 0, 0.35, 1);
}
.side-label:hover .side-label__bar { width: 80px; }

/* ---------- Center caption ---------- */
.center-caption {
    position: absolute;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%);
    z-index: 9;
    display: inline-flex;
    align-items: center;
    gap: 0.5rem;
    font-size: 0.75rem;
    font-weight: 600;
    letter-spacing: 0.05em;
    color: var(--ink);
    padding: 0.4rem 1rem;
    background: rgba(255, 255, 255, 0.06);
    backdrop-filter: blur(8px);
    border: 1px solid rgba(255, 255, 255, 0.12);
    border-radius: 999px;
    will-change: transform;
    cursor: none;
}
.center-caption__dot {
    width: 5px; height: 5px;
    border-radius: 50%;
    background: #ffffff;
}

/* ---------- Huge title ---------- */
.title {
    position: absolute;
    left: 0; right: 0;
    bottom: 0;
    z-index: 10;
    font-family: 'Inter', sans-serif;
    font-weight: 800;
    font-size: clamp(3.5rem, 13vw, 16rem);
    line-height: 0.9;
    letter-spacing: -0.04em;
    color: #ffffff;
    text-align: center;
    padding: 0 1rem;
    pointer-events: none;
}
.title__line {
    display: block;
    overflow: hidden;
    padding-bottom: 0.04em;
    white-space: nowrap;
    pointer-events: auto;
}
.title__char {
    display: inline-block;
    will-change: transform, color;
    transition: color 0.3s ease;
}
.title__char.is-space { width: 0.2em; }

/* ---------- Footer meta ---------- */
.footer-meta {
    position: absolute;
    bottom: 1rem;
    left: 0; right: 0;
    z-index: 15;
    display: flex;
    justify-content: space-between;
    padding: 0 2rem;
    font-family: 'JetBrains Mono', monospace;
    font-size: 0.7rem;
    letter-spacing: 0.15em;
    color: var(--muted);
    text-transform: uppercase;
    pointer-events: none;
}
.footer-meta__cell { display: inline-flex; align-items: center; gap: 0.4rem; }
.footer-meta__cell b { color: var(--ink); font-weight: 500; }

/* ---------- Initial hidden states ---------- */
[data-magnetic], [data-link], [data-fade], [data-title-line] .title__char,
[data-side], [data-caption], [data-footer] { opacity: 0; }

/* ---------- Responsive ---------- */
@media (max-width: 1024px) {
    .topnav { grid-template-columns: 1fr auto; gap: 1rem; }
    .topnav__desc, .topnav__contact { display: none; }
    .side-label { display: none; }
}
@media (max-width: 768px) {
    .topnav { padding: 1rem 1.25rem; }
    .topnav__logo-text { font-size: 1rem; }
    .center-caption { font-size: 0.65rem; padding: 0.3rem 0.75rem; }
    .footer-meta { font-size: 0.6rem; padding: 0 1rem; gap: 0.5rem; flex-wrap: wrap; justify-content: center; }
    .title { bottom: 3rem; }
    .loader__title { font-size: clamp(2.5rem, 14vw, 6rem); }
}
@media (max-width: 480px) {
    .topnav__lang { padding: 0.3rem 0.4rem; font-size: 0.75rem; }
    .topnav__menu { padding: 0.4rem 0.8rem; font-size: 0.75rem; }
    .center-caption { display: none; }
    .footer-meta__cell:last-child { display: none; }
}

  </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 โ€” Evolve</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;900&family=JetBrains+Mono:wght@400;500;600&display=swap" rel="stylesheet">
    <link rel="stylesheet" href="banner-evolve.css">
</head>
<body>
    

    <!-- Custom cursor -->
    <div class="cursor-dot" id="cursor-dot"></div>
    <div class="cursor-ring" id="cursor-ring"></div>

    <!-- ========== LOADER โ€” Scramble text + grid ========== -->
    <div class="loader" id="loader">
        <div class="loader__grid" id="loader-grid"></div>
        <div class="loader__content">
            <div class="loader__diamond" id="loader-diamond">
                <svg viewBox="0 0 40 40" aria-hidden="true">
                    <path d="M20 4 L36 20 L20 36 L4 20 Z" fill="none" stroke="#ffffff" stroke-width="1.5"/>
                    <path d="M20 12 L28 20 L20 28 L12 20 Z" fill="#ffffff"/>
                </svg>
            </div>
            <div class="loader__title" id="loader-title" aria-label="evolve">
                <span data-char="e">e</span><span data-char="v">v</span><span data-char="o">o</span><span data-char="l">l</span><span data-char="v">v</span><span data-char="e">e</span>
            </div>
            <div class="loader__meta">
                <span class="loader__label" id="loader-label">LOADING</span>
                <span class="loader__counter" id="loader-counter">000</span>
            </div>
        </div>
    </div>

    <section class="evolve" id="evolve">

        <!-- Background media -->
        <video class="evolve__video" id="bg-video" autoplay loop muted playsinline
               poster="https://images.unsplash.com/photo-1496346701500-f1adcc34e09e?auto=format&fit=crop&w=1600&q=80">
            <source src="https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/TearsOfSteel.mp4" type="video/mp4">
        </video>
        <div class="evolve__overlay"></div>

        <!-- Grid lines overlay -->
        <div class="evolve__grid" id="grid"></div>

        <!-- ========== TOP NAV ========== -->
        <header class="topnav">
            <a href="#" class="topnav__logo" data-magnetic>
                <span class="topnav__logo-mark" id="nav-diamond">
                    <svg viewBox="0 0 24 24" aria-hidden="true">
                        <path d="M12 2 L22 12 L12 22 L2 12 Z" fill="none" stroke="#ffffff" stroke-width="1.5"/>
                        <path d="M12 6 L18 12 L12 18 L6 12 Z" fill="#ffffff"/>
                    </svg>
                </span>
                <span class="topnav__logo-text">evolve</span>
            </a>

            <div class="topnav__desc" data-fade>
                Ecommerce and brand systems.<br>
                Driven by visions. Built with design<br>
                and technology.
            </div>

            <div class="topnav__contact" data-fade>
                <a href="mailto:hello@evolvebrand.com" data-magnetic class="topnav__email">hello@evolvebrand.com</a>
                <span class="topnav__loc">Berlin, EST 2008&copy;</span>
            </div>

            <div class="topnav__actions">
                <button class="topnav__lang" data-magnetic id="lang-btn">
                    <span id="lang-current">EN</span>
                </button>
                <button class="topnav__menu" data-magnetic id="menu-btn">
                    <span>Menu</span>
                    <svg viewBox="0 0 12 12" fill="none" stroke="currentColor" stroke-width="1.5">
                        <path d="M3 4l3 3 3-3" stroke-linecap="round" stroke-linejoin="round"/>
                    </svg>
                </button>
            </div>
        </header>

        <!-- ========== SIDE LABELS ========== -->
        <aside class="side-label side-label--left" data-side>
            <span class="side-label__bar"></span>
            <span>Brand Direction</span>
        </aside>
        <aside class="side-label side-label--right" data-side>
            <span>Advanced Tech</span>
            <span class="side-label__bar"></span>
        </aside>

        <!-- Center caption over image -->
        <span class="center-caption" data-caption>
            <span class="center-caption__dot"></span>
            Performance Marketing
        </span>

        <!-- ========== HUGE TITLE ========== -->
        <h1 class="title" id="title" aria-label="Ecommerce Innovation Agency">
            <span class="title__line" data-title-line>ECOMMERCE</span>
            <span class="title__line" data-title-line>INNOVATION</span>
            <span class="title__line" data-title-line>AGENCY</span>
        </h1>

        <!-- Bottom meta info -->
        <div class="footer-meta" data-footer>
            <span class="footer-meta__cell"><b>&copy;</b> 2026 &mdash; all rights</span>
            <span class="footer-meta__cell"><b>&#9679;</b> available for work</span>
            <span class="footer-meta__cell">scroll <b>&darr;</b></span>
        </div>
    </section>

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

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

    // -------------------------------------------------------------------
    //  SPLIT TITLE LINES INTO CHARS
    // -------------------------------------------------------------------
    const titleLines = root.querySelectorAll('[data-title-line]');
    titleLines.forEach((line) => {
        const text = line.textContent;
        line.textContent = '';
        [...text].forEach((ch) => {
            const span = document.createElement('span');
            span.className = 'title__char';
            if (ch === ' ') { span.classList.add('is-space'); span.innerHTML = '&nbsp;'; }
            else span.textContent = ch;
            line.appendChild(span);
        });
    });

    // -------------------------------------------------------------------
    //  REFERENCES
    // -------------------------------------------------------------------
    const loader = document.getElementById('loader');
    const loaderGrid = document.getElementById('loader-grid');
    const loaderCounter = document.getElementById('loader-counter');
    const loaderLabel = document.getElementById('loader-label');
    const loaderDiamond = document.getElementById('loader-diamond');
    const loaderTitleChars = document.querySelectorAll('#loader-title span');

    const cursorDot = document.getElementById('cursor-dot');
    const cursorRing = document.getElementById('cursor-ring');

    const video = document.getElementById('bg-video');
    const magnetics = root.querySelectorAll('[data-magnetic]');
    const fades = root.querySelectorAll('[data-fade]');
    const titleChars = root.querySelectorAll('.title__char');
    const sides = root.querySelectorAll('[data-side]');
    const caption = root.querySelector('[data-caption]');
    const footer = root.querySelector('[data-footer]');
    const navDiamond = document.getElementById('nav-diamond');
    const langBtn = document.getElementById('lang-btn');
    const langCurrent = document.getElementById('lang-current');

    // -------------------------------------------------------------------
    //  INITIAL STATES
    // -------------------------------------------------------------------
    gsap.set(video, { scale: 1.15, opacity: 0 });
    gsap.set(magnetics, { y: -15, opacity: 0 });
    gsap.set(fades, { y: 10, opacity: 0 });
    gsap.set(sides, { opacity: 0 });
    gsap.set(caption, { scale: 0, opacity: 0 });
    gsap.set(titleChars, { yPercent: 110, opacity: 0 });
    gsap.set(footer, { y: 10, opacity: 0 });

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

    // Grid fades in
    loaderTl.to(loaderGrid, {
        opacity: 1,
        duration: 0.6,
        ease: 'power2.out',
    }, 0);

    // Diamond rotates in
    loaderTl.from(loaderDiamond, {
        rotation: -180,
        scale: 0,
        opacity: 0,
        duration: 0.8,
        ease: 'back.out(2)',
    }, 0.1);

    // Diamond continuous rotation
    gsap.to(loaderDiamond, {
        rotation: 360,
        duration: 4,
        repeat: -1,
        ease: 'none',
    });

    // Scramble text: each letter cycles through random chars before settling
    const finalChars = ['e', 'v', 'o', 'l', 'v', 'e'];
    const scrambleChars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ$#@&%=+*';
    loaderTitleChars.forEach((el, i) => {
        const tl = gsap.timeline({ delay: 0.3 + i * 0.08 });
        const scrambleCount = 8;
        for (let s = 0; s < scrambleCount; s++) {
            tl.call(() => {
                el.textContent = scrambleChars[Math.floor(Math.random() * scrambleChars.length)];
            }, null, s * 0.05);
        }
        tl.call(() => {
            el.textContent = finalChars[i];
            gsap.fromTo(el, { scale: 1.2 }, { scale: 1, duration: 0.3, ease: 'back.out(2.5)' });
        }, null, scrambleCount * 0.05);
    });

    // Counter + label rotation
    const p = { v: 0 };
    loaderTl.to(p, {
        v: 100,
        duration: 2.2,
        ease: 'power1.inOut',
        onUpdate: () => {
            loaderCounter.textContent = String(Math.floor(p.v)).padStart(3, '0');
            if (p.v > 30 && loaderLabel.textContent === 'LOADING') loaderLabel.textContent = 'COMPILING';
            if (p.v > 60 && loaderLabel.textContent === 'COMPILING') loaderLabel.textContent = 'RENDERING';
            if (p.v > 95 && loaderLabel.textContent === 'RENDERING') loaderLabel.textContent = 'READY';
        },
    }, 0.2);

    // Exit: scramble text glitches + fades
    loaderTl.to(loaderTitleChars, {
        y: -40,
        opacity: 0,
        duration: 0.5,
        stagger: 0.03,
        ease: 'power3.in',
    }, '+=0.3');
    loaderTl.to(loaderDiamond, {
        scale: 0.5, opacity: 0, duration: 0.4, ease: 'power3.in',
    }, '-=0.4');
    loaderTl.to([loaderCounter, loaderLabel], {
        y: -10, opacity: 0, duration: 0.3, stagger: 0.05,
    }, '-=0.4');
    loaderTl.to(loaderGrid, {
        opacity: 0, duration: 0.4,
    }, '-=0.3');
    loaderTl.to(loader, {
        yPercent: -100, duration: 0.9, ease: 'power4.inOut',
    }, '-=0.1');
    loaderTl.set(loader, { display: 'none' });

    // -------------------------------------------------------------------
    //  MAIN SCENE ENTRANCE
    // -------------------------------------------------------------------
    function playScene() {
        const tl = gsap.timeline({ defaults: { ease: 'power3.out' } });

        tl.to(video, {
            scale: 1, opacity: 1,
            duration: 2,
            ease: 'power3.out',
        }, 0);

        // Nav elements
        tl.to(magnetics, {
            y: 0, opacity: 1,
            duration: 0.6,
            stagger: 0.05,
        }, 0.3);
        tl.to(fades, {
            y: 0, opacity: 1,
            duration: 0.7,
            stagger: 0.1,
        }, 0.45);

        // Side labels
        tl.to(sides, {
            opacity: 1,
            duration: 0.6,
            stagger: 0.1,
        }, 0.7);

        // Center caption
        tl.to(caption, {
            scale: 1, opacity: 1,
            duration: 0.6,
            ease: 'back.out(1.6)',
        }, 0.9);

        // Huge title chars
        tl.to(titleChars, {
            yPercent: 0, opacity: 1,
            duration: 1.2,
            stagger: { each: 0.02, from: 'start' },
            ease: 'expo.out',
        }, 1);

        // Footer meta
        tl.to(footer, {
            y: 0, opacity: 1,
            duration: 0.5,
        }, 1.3);

        tl.call(enableInteractions, null, 1.5);
    }

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

        // ---- Custom cursor ----
        let mx = window.innerWidth / 2, my = window.innerHeight / 2;
        let rx = mx, ry = my;
        window.addEventListener('mousemove', (e) => { mx = e.clientX; my = e.clientY; });
        gsap.ticker.add(() => {
            rx += (mx - rx) * 0.18;
            ry += (my - ry) * 0.18;
            gsap.set(cursorDot, { x: mx, y: my });
            gsap.set(cursorRing, { x: rx, y: ry });
        });
        const hovers = root.querySelectorAll('a, button, [data-magnetic], .side-label, .title__char');
        hovers.forEach((el) => {
            el.addEventListener('mouseenter', () => cursorRing.classList.add('is-hover'));
            el.addEventListener('mouseleave', () => cursorRing.classList.remove('is-hover'));
        });

        // ---- Magnetic ----
        magnetics.forEach((el) => {
            const strength = el.classList.contains('topnav__menu') ? 0.35
                : el.classList.contains('topnav__lang') ? 0.45
                : 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: diamond rotates on hover ----
        const logo = root.querySelector('.topnav__logo');
        logo.addEventListener('mouseenter', () => {
            gsap.to(navDiamond, { rotation: 180, duration: 0.6, ease: 'back.out(2)' });
        });
        logo.addEventListener('mouseleave', () => {
            gsap.to(navDiamond, { rotation: 0, duration: 0.6, ease: 'power3.out' });
        });

        // ---- Language toggle ----
        langBtn.addEventListener('click', (e) => {
            e.preventDefault();
            const current = langCurrent.textContent;
            const next = current === 'EN' ? 'IT' : 'EN';
            gsap.to(langCurrent, {
                y: -20, opacity: 0, duration: 0.2, ease: 'power2.in',
                onComplete: () => {
                    langCurrent.textContent = next;
                    gsap.fromTo(langCurrent,
                        { y: 20, opacity: 0 },
                        { y: 0, opacity: 1, duration: 0.3, ease: 'back.out(2)' }
                    );
                }
            });
        });

        // ---- Title char proximity + hover invert ----
        const title = document.getElementById('title');
        let tmx = -9999, tmy = -9999;
        root.addEventListener('mousemove', (e) => {
            tmx = e.clientX; tmy = e.clientY;
        });
        root.addEventListener('mouseleave', () => {
            tmx = -9999; tmy = -9999;
            titleChars.forEach((c) => gsap.to(c, { y: 0, duration: 0.5, ease: 'power3.out' }));
        });

        gsap.ticker.add(() => {
            if (tmx < 0) return;
            titleChars.forEach((c) => {
                const r = c.getBoundingClientRect();
                if (r.width === 0) return;
                const cx = r.left + r.width / 2;
                const cy = r.top + r.height / 2;
                const dx = tmx - cx, dy = tmy - cy;
                const dist = Math.sqrt(dx * dx + dy * dy);
                if (dist < 140) gsap.set(c, { y: -(1 - dist / 140) * 18 });
                else gsap.set(c, { y: 0 });
            });
        });

        titleChars.forEach((c) => {
            c.addEventListener('mouseenter', () => {
                gsap.to(c, { color: '#06080b', duration: 0.2 });
                c.style.textShadow = '0 0 30px rgba(255,255,255,0.6)';
                c.style.background = '#ffffff';
                c.style.padding = '0 4px';
            });
            c.addEventListener('mouseleave', () => {
                gsap.to(c, { color: '#ffffff', duration: 0.4 });
                c.style.textShadow = '';
                c.style.background = '';
                c.style.padding = '';
            });
        });

        // ---- Side labels: slide on hover ----
        sides.forEach((s) => {
            const dir = s.classList.contains('side-label--left') ? -1 : 1;
            s.addEventListener('mouseenter', () => {
                gsap.to(s, { x: dir * 10, duration: 0.4, ease: 'power3.out' });
            });
            s.addEventListener('mouseleave', () => {
                gsap.to(s, { x: 0, duration: 0.5, ease: 'elastic.out(1, 0.4)' });
            });
        });

        // ---- Caption: dot pulses continuously ----
        gsap.to(caption.querySelector('.center-caption__dot'), {
            scale: 2.2, opacity: 0.3,
            duration: 1.2,
            yoyo: true,
            repeat: -1,
            ease: 'sine.inOut',
        });

        // ---- Video parallax ----
        let vX = 0, vY = 0, vcX = 0, vcY = 0;
        root.addEventListener('mousemove', (e) => {
            const r = root.getBoundingClientRect();
            vX = ((e.clientX - r.left) / r.width - 0.5) * 2;
            vY = ((e.clientY - r.top) / r.height - 0.5) * 2;
        });
        root.addEventListener('mouseleave', () => { vX = 0; vY = 0; });
        gsap.ticker.add(() => {
            vcX += (vX - vcX) * 0.04;
            vcY += (vY - vcY) * 0.04;
            gsap.set(video, { x: -vcX * 20, y: -vcY * 12, scale: 1.05 });
            gsap.set(caption, { x: vcX * 10, y: vcY * 6 });
        });

        // Ensure video plays
        video.play().catch(() => {
            const tryPlay = () => { video.play().catch(() => {}); window.removeEventListener('click', tryPlay); };
            window.addEventListener('click', tryPlay);
        });
    }
})();

More GSAP landing pages