ToolsWaves
Free GSAP Landing Page Template · ToolsWaves · Media & Collaboration

Free Media Platform Hero Template with Waveform Loader

Free media platform landing page template from ToolsWaves featuring an animated waveform loader, a scattered image grid with depth parallax, magnetic navigation, proximity-reactive title, tile hover lift with shadow, and animated logo bars. Free for commercial use.

GalleryWaveformDepthCollaborationMagnetic Nav

About this ToolsWaves template

This free media platform landing page template from ToolsWaves is designed for media platforms, video collaboration tools, and creative review software where visual content is the primary asset. The loader uses an animated waveform that builds across the screen — referencing both audio and video editing contexts depending on your brand positioning. Behind the headline, a scattered grid of images sits at varying depths and shifts subtly with mouse parallax. Tiles lift with shadow on hover, magnetic navigation responds to proximity, and the title reacts to cursor movement character by character.

Use this template for media SaaS, video collaboration platforms, creative review tools, or any creative tool that handles a high volume of media assets. ToolsWaves provides this template free for any project — copy the HTML, CSS, and JavaScript files and replace the demo brand and image grid with your own product screenshots, customer logos, or sample content from your platform. The depth parallax effect uses CSS 3D transforms and can be adjusted to be more or less pronounced.

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 Media Platform Hero Template with Waveform Loader</title>
  <meta name="description" content="Free GSAP media platform landing page hero from ToolsWaves" />
  <meta name="generator" content="ToolsWaves - https://toolswaves.in/landing-pages" />

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

  <!-- Twitter -->
  <meta name="twitter:card" content="summary_large_image" />
  <meta name="twitter:title" content="Free Media Platform Hero Template with Waveform Loader" />
  <meta name="twitter:description" content="Free GSAP media platform landing page hero from ToolsWaves" />
  <meta name="twitter:image" content="https://toolswaves.in/og?title=Free%20Media%20Platform%20Hero%20Template%20with%20Waveform%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: #000000;
    --ink: #ffffff;
    --muted: rgba(255, 255, 255, 0.55);
    --line: rgba(255, 255, 255, 0.12);
    --cream: #f2e8d5;
    --accent: #ffffff;
}

html, body {
    font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
    background: var(--bg);
    color: var(--ink);
    overflow: 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: rgba(255, 255, 255, 0.08);
    border: 1px solid rgba(255, 255, 255, 0.18);
    border-radius: 999px;
    backdrop-filter: blur(8px);
    opacity: 0.85;
    transition: opacity 0.3s, transform 0.3s;
}
.back-link:hover { opacity: 1; transform: translateY(-2px); }

/* =========================================================
   LOADER — audio waveform bars
   ========================================================= */
.loader {
    position: fixed;
    inset: 0;
    z-index: 999;
    background: var(--bg);
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 2.5rem;
}

.loader__wave {
    display: flex;
    align-items: center;
    gap: 4px;
    height: 120px;
}
.loader__wave span {
    display: block;
    width: 5px;
    min-height: 5px;
    background: var(--ink);
    border-radius: 3px;
    transform-origin: center;
    will-change: transform, height;
}

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

/* =========================================================
   MEDIA GRID SECTION
   ========================================================= */
.mediagrid {
    position: relative;
    width: 100%;
    height: 100vh;
    min-height: 780px;
    overflow: hidden;
    background: var(--bg);
}

.mediagrid__topbar {
    position: absolute;
    top: 0; left: 0; right: 0;
    height: 84px;
    background: linear-gradient(90deg,
        #c9bf98 0%,
        #6c6450 25%,
        #1a1a18 55%,
        #000 100%);
    opacity: 0.9;
    pointer-events: none;
    transform-origin: left;
}

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

.topnav__logo {
    display: inline-flex;
    align-items: center;
    color: var(--ink);
    text-decoration: none;
    will-change: transform;
}
.topnav__logo-mark {
    display: inline-flex;
    align-items: center;
    gap: 3px;
    height: 24px;
}
.topnav__logo-mark span {
    width: 2px;
    background: var(--ink);
    border-radius: 2px;
    will-change: transform;
}
.topnav__logo-mark span:nth-child(1) { height: 30%; }
.topnav__logo-mark span:nth-child(2) { height: 80%; }
.topnav__logo-mark span:nth-child(3) { height: 45%; }
.topnav__logo-mark span:nth-child(4) { height: 95%; }
.topnav__logo-mark span:nth-child(5) { height: 55%; }
.topnav__logo-mark span:nth-child(6) { height: 25%; }

.topnav__links {
    display: flex;
    gap: 2rem;
    margin-left: 2rem;
}
.topnav__links a {
    position: relative;
    color: var(--ink);
    text-decoration: none;
    font-weight: 500;
    font-size: 0.95rem;
    padding: 0.3rem 0;
    display: inline-flex;
    align-items: center;
    gap: 0.3rem;
    will-change: transform;
}
.caret {
    font-size: 0.65em;
    opacity: 0.7;
    transition: transform 0.3s ease;
}
.topnav__links a:hover .caret { transform: translateY(2px); }
.topnav__links a::after {
    content: '';
    position: absolute;
    left: 0; bottom: 0;
    width: 100%; height: 1px;
    background: var(--ink);
    transform: scaleX(0);
    transform-origin: right;
    transition: transform 0.4s cubic-bezier(0.65, 0, 0.35, 1);
}
.topnav__links a:hover::after { transform: scaleX(1); transform-origin: left; }

.topnav__actions {
    margin-left: auto;
    display: flex;
    align-items: center;
    gap: 1.25rem;
}
.topnav__action {
    color: var(--ink);
    text-decoration: none;
    font-weight: 500;
    font-size: 0.95rem;
    will-change: transform;
    position: relative;
    padding: 0.3rem 0;
}
.topnav__action::after {
    content: '';
    position: absolute;
    left: 0; bottom: 0;
    width: 100%; height: 1px;
    background: var(--ink);
    transform: scaleX(0);
    transform-origin: right;
    transition: transform 0.35s cubic-bezier(0.65, 0, 0.35, 1);
}
.topnav__action:hover::after { transform: scaleX(1); transform-origin: left; }

.topnav__cta {
    background: var(--ink);
    color: var(--bg);
    text-decoration: none;
    padding: 0.7rem 1.3rem;
    border-radius: 999px;
    font-weight: 600;
    font-size: 0.9rem;
    display: inline-block;
    will-change: transform;
    transition: background 0.3s;
}
.topnav__cta:hover { background: #e5e5e5; }

.topnav__mark {
    width: 36px; height: 36px;
    background: linear-gradient(135deg, #ff3b3b, #c22020);
    color: var(--ink);
    text-decoration: none;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    border-radius: 6px;
    will-change: transform;
}
.topnav__mark svg { width: 20px; height: 20px; }

/* ---------- Image grid ---------- */
.grid {
    position: absolute;
    inset: 0;
    z-index: 5;
    pointer-events: none;
}
.tile {
    position: absolute;
    border-radius: 12px;
    overflow: hidden;
    box-shadow: 0 20px 40px rgba(0, 0, 0, 0.55);
    pointer-events: auto;
    cursor: pointer;
    will-change: transform, filter;
    background: #1a1a1a;
}
.tile img {
    width: 100%; height: 100%;
    object-fit: cover;
    user-select: none;
    display: block;
    transition: transform 0.6s cubic-bezier(0.65, 0, 0.35, 1), filter 0.4s ease;
    filter: saturate(1.05);
}
.tile:hover img { transform: scale(1.08); filter: saturate(1.2) brightness(1.05); }

/* Positions (mimic the Frame.io-style scatter) */
.tile--tl-1 { top: 8%;  left: -2%;  width: 260px; height: 190px; transform: rotate(-1deg); }
.tile--tl-2 { top: 38%; left: -3%;  width: 280px; height: 210px; transform: rotate(1deg); }
.tile--tc   { top: 2%;  left: 25%;  width: 260px; height: 150px; transform: rotate(1deg); }
.tile--tr-1 { top: 0%;  left: 60%;  width: 230px; height: 170px; transform: rotate(-1.5deg); }
.tile--tr-2 { top: 10%; right: -2%; width: 280px; height: 190px; transform: rotate(1deg); }

.tile--r-1 { top: 40%; right: -1%; width: 240px; height: 210px; transform: rotate(-1deg); }
.tile--r-2 { top: 68%; right: 4%;  width: 280px; height: 200px; transform: rotate(1deg); }

.tile--l-1 { top: 5%;  left: 2%;   width: 250px; height: 160px; display: none; }
.tile--l-2 { top: 68%; left: 4%;   width: 280px; height: 200px; transform: rotate(-1deg); }

.tile--bl   { bottom: 10%; left: -2%;   width: 280px; height: 200px; transform: rotate(1deg); }
.tile--bc-1 { bottom: 2%;  left: 28%;   width: 240px; height: 140px; transform: rotate(-1deg); }
.tile--bc-2 { bottom: 6%;  left: 46%;   width: 220px; height: 140px; transform: rotate(1deg); }
.tile--bc-3 { bottom: 4%;  left: 62%;   width: 240px; height: 160px; transform: rotate(-1deg); }
.tile--br   { bottom: 14%; right: -2%;  width: 280px; height: 200px; transform: rotate(1deg); display: none; }

/* ---------- Center content ---------- */
.center {
    position: absolute;
    top: 50%; left: 50%;
    transform: translate(-50%, -50%);
    z-index: 10;
    text-align: center;
    max-width: 560px;
    padding: 0 1.5rem;
    pointer-events: none;
}
.center > * { pointer-events: auto; }

.center__title {
    font-family: 'Inter', sans-serif;
    font-weight: 700;
    font-size: clamp(2.2rem, 5vw, 4.2rem);
    line-height: 1.05;
    letter-spacing: -0.025em;
    color: var(--ink);
    margin-bottom: 1.5rem;
}
.center__line {
    display: block;
    overflow: hidden;
    padding-bottom: 0.05em;
}
.center__char {
    display: inline-block;
    will-change: transform, color;
    transition: color 0.3s ease;
}
.center__char.is-space { width: 0.25em; }

.center__desc {
    font-size: 1rem;
    line-height: 1.6;
    color: var(--muted);
    font-weight: 400;
}

/* ---------- Initial hidden states ---------- */
[data-magnetic], [data-link], [data-tile], [data-desc], [data-topbar] { opacity: 0; }
[data-title-line] .center__char { opacity: 0; }

/* ---------- Responsive ---------- */
@media (max-width: 1100px) {
    .topnav__links { display: none; }
    .topnav__action { display: none; }
    .topnav { gap: 1rem; }
    .tile--tc, .tile--tr-1, .tile--r-2 { display: none; }
}
@media (max-width: 768px) {
    .tile {
        transform: none !important;
    }
    .tile--tl-2, .tile--r-2, .tile--bl, .tile--bc-3 { display: none; }
    .tile--tl-1 { width: 40vw; height: 28vw; left: -8%; }
    .tile--tr-2 { width: 42vw; height: 30vw; right: -6%; }
    .tile--r-1 { width: 40vw; height: 30vw; right: -10%; top: 42%; }
    .tile--l-2 { width: 40vw; height: 28vw; left: -8%; top: 62%; }
    .tile--bc-1 { width: 40vw; height: 26vw; left: 8%; bottom: 4%; }
    .tile--bc-2 { width: 40vw; height: 26vw; right: 8%; left: auto; bottom: 4%; }
    .center { max-width: 90%; }
    .center__desc { font-size: 0.9rem; }
}
@media (max-width: 480px) {
    .tile--r-1, .tile--l-2 { display: none; }
    .tile--tl-1 { top: 6rem; left: -8%; width: 45vw; height: 32vw; }
    .tile--tr-2 { top: 6rem; right: -8%; width: 45vw; height: 32vw; }
    .tile--bc-1, .tile--bc-2 { top: auto; bottom: 1rem; width: 45vw; height: 28vw; }
    .tile--bc-1 { left: -5%; }
    .tile--bc-2 { right: -5%; left: auto; }
    .topnav { padding: 1rem; }
    .topnav__cta { padding: 0.5rem 1rem; font-size: 0.8rem; }
    .topnav__mark { width: 30px; height: 30px; }
}

  </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 — Media Gallery</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&display=swap" rel="stylesheet">
    <link rel="stylesheet" href="banner-media-gallery.css">
</head>
<body>
    

    <!-- ========== LOADER ========== -->
    <div class="loader" id="loader">
        <div class="loader__wave" id="loader-wave"></div>
        <div class="loader__meta">
            <span class="loader__label" id="loader-label">INITIALIZING WORKSPACE</span>
            <span class="loader__counter" id="loader-counter">00%</span>
        </div>
    </div>

    <section class="mediagrid" id="mediagrid">
        <!-- Top gradient accent -->
        <div class="mediagrid__topbar" data-topbar></div>

        <!-- ========== TOP NAV ========== -->
        <header class="topnav">
            <a href="#" class="topnav__logo" data-magnetic>
                <span class="topnav__logo-mark" id="logo-mark">
                    <span></span><span></span><span></span><span></span><span></span><span></span>
                </span>
            </a>
            <nav class="topnav__links">
                <a href="#" data-magnetic data-link>Features <span class="caret">&#9662;</span></a>
                <a href="#" data-magnetic data-link>Enterprise <span class="caret">&#9662;</span></a>
                <a href="#" data-magnetic data-link>Resources <span class="caret">&#9662;</span></a>
                <a href="#" data-magnetic data-link>Pricing</a>
            </nav>
            <div class="topnav__actions">
                <a href="#" class="topnav__action" data-magnetic>Contact Us</a>
                <a href="#" class="topnav__action" data-magnetic>Sign In</a>
                <a href="#" class="topnav__cta" data-magnetic>Start Free Trial</a>
                <a href="#" class="topnav__mark" data-magnetic aria-label="Brand mark">
                    <svg viewBox="0 0 24 24" fill="currentColor"><path d="M12 3L2 21h5l1.5-3h7L17 21h5L12 3zm-2.5 13L12 10l2.5 6h-5z"/></svg>
                </a>
            </div>
        </header>

        <!-- ========== IMAGE GRID (scattered around edges) ========== -->
        <div class="grid" id="grid">
            <!-- Top row -->
            <div class="tile tile--tl-1" data-tile data-depth="0.4">
                <img src="https://images.unsplash.com/photo-1517694712202-14dd9538aa97?auto=format&fit=crop&w=500&q=75"
                     alt="" draggable="false" onerror="this.parentElement.style.display='none'">
            </div>
            <div class="tile tile--tl-2" data-tile data-depth="0.3">
                <img src="https://images.unsplash.com/photo-1536440136628-849c177e76a1?auto=format&fit=crop&w=500&q=75"
                     alt="" draggable="false" onerror="this.parentElement.style.display='none'">
            </div>
            <div class="tile tile--tc" data-tile data-depth="0.5">
                <img src="https://images.unsplash.com/photo-1492691527719-9d1e07e534b4?auto=format&fit=crop&w=500&q=75"
                     alt="" draggable="false" onerror="this.parentElement.style.display='none'">
            </div>
            <div class="tile tile--tr-1" data-tile data-depth="0.35">
                <img src="https://images.unsplash.com/photo-1449247709967-d4461a6a6103?auto=format&fit=crop&w=500&q=75"
                     alt="" draggable="false" onerror="this.parentElement.style.display='none'">
            </div>
            <div class="tile tile--tr-2" data-tile data-depth="0.45">
                <img src="https://images.unsplash.com/photo-1506905925346-21bda4d32df4?auto=format&fit=crop&w=500&q=75"
                     alt="" draggable="false" onerror="this.parentElement.style.display='none'">
            </div>

            <!-- Right column -->
            <div class="tile tile--r-1" data-tile data-depth="0.3">
                <img src="https://images.unsplash.com/photo-1517694712202-14dd9538aa97?auto=format&fit=crop&w=500&q=75"
                     alt="" draggable="false" onerror="this.parentElement.style.display='none'">
            </div>
            <div class="tile tile--r-2" data-tile data-depth="0.5">
                <img src="https://images.unsplash.com/photo-1536440136628-849c177e76a1?auto=format&fit=crop&w=500&q=75"
                     alt="" draggable="false" onerror="this.parentElement.style.display='none'">
            </div>

            <!-- Left column -->
            <div class="tile tile--l-1" data-tile data-depth="0.35">
                <img src="https://images.unsplash.com/photo-1517694712202-14dd9538aa97?auto=format&fit=crop&w=500&q=75"
                     alt="" draggable="false" onerror="this.parentElement.style.display='none'">
            </div>
            <div class="tile tile--l-2" data-tile data-depth="0.4">
                <img src="https://images.unsplash.com/photo-1536440136628-849c177e76a1?auto=format&fit=crop&w=500&q=75"
                     alt="" draggable="false" onerror="this.parentElement.style.display='none'">
            </div>

            <!-- Bottom row -->
            <div class="tile tile--bl" data-tile data-depth="0.4">
                <img src="https://images.unsplash.com/photo-1519225421980-715cb0215aed?auto=format&fit=crop&w=500&q=75"
                     alt="" draggable="false" onerror="this.parentElement.style.display='none'">
            </div>
            <div class="tile tile--bc-1" data-tile data-depth="0.3">
                <img src="https://images.unsplash.com/photo-1436491865332-7a61a109cc05?auto=format&fit=crop&w=500&q=75"
                     alt="" draggable="false" onerror="this.parentElement.style.display='none'">
            </div>
            <div class="tile tile--bc-2" data-tile data-depth="0.35">
                <img src="https://images.unsplash.com/photo-1489599192825-a7fc2e3fbfbd?auto=format&fit=crop&w=500&q=75"
                     alt="" draggable="false" onerror="this.parentElement.style.display='none'">
            </div>
            <div class="tile tile--bc-3" data-tile data-depth="0.45">
                <img src="https://images.unsplash.com/photo-1474511320723-9a56873867b5?auto=format&fit=crop&w=500&q=75"
                     alt="" draggable="false" onerror="this.parentElement.style.display='none'">
            </div>
            <div class="tile tile--br" data-tile data-depth="0.5">
                <img src="https://images.unsplash.com/photo-1536440136628-849c177e76a1?auto=format&fit=crop&w=500&q=75"
                     alt="" draggable="false" onerror="this.parentElement.style.display='none'">
            </div>
        </div>

        <!-- ========== CENTER CONTENT ========== -->
        <div class="center">
            <h1 class="center__title" aria-label="Collaborate and conquer.">
                <span class="center__line" data-title-line>Collaborate</span>
                <span class="center__line" data-title-line>and conquer.</span>
            </h1>
            <p class="center__desc" data-desc>
                See how brands, agencies, and production teams of all sizes
                worldwide use our platform to accelerate their workflow and
                empower collaboration.
            </p>
        </div>
    </section>

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

</body>
</html>
JSbanner-media-gallery.js— GSAP animation timeline
(() => {
    const root = document.getElementById('mediagrid');
    if (!root) return;

    // -------------------------------------------------------------------
    //  BUILD WAVEFORM BARS IN LOADER
    // -------------------------------------------------------------------
    const wave = document.getElementById('loader-wave');
    const BAR_COUNT = 28;
    for (let i = 0; i < BAR_COUNT; i++) {
        wave.appendChild(document.createElement('span'));
    }
    const waveBars = wave.querySelectorAll('span');

    // -------------------------------------------------------------------
    //  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 = 'center__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 loaderCounter = document.getElementById('loader-counter');
    const loaderLabel = document.getElementById('loader-label');
    const topbar = root.querySelector('[data-topbar]');
    const magnetics = root.querySelectorAll('[data-magnetic]');
    const tiles = root.querySelectorAll('[data-tile]');
    const titleChars = root.querySelectorAll('.center__char');
    const desc = root.querySelector('[data-desc]');
    const logoMark = document.getElementById('logo-mark');
    const logoBars = logoMark.querySelectorAll('span');

    // -------------------------------------------------------------------
    //  INITIAL STATES
    // -------------------------------------------------------------------
    gsap.set(topbar, { scaleX: 0, opacity: 1, transformOrigin: 'left' });
    gsap.set(magnetics, { y: -20, opacity: 0 });

    tiles.forEach((tile, i) => {
        // Remember the final CSS rotation so parallax can preserve it
        const baseRot = getRotationFromTransform(getComputedStyle(tile).transform);
        tile.dataset.baseRot = baseRot;
        gsap.set(tile, {
            opacity: 0,
            scale: 0.7,
            y: 40,
            rotation: baseRot,
        });
    });

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

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

    // Kick off a continuous waveform animation on bars (independent heights)
    const waveTweens = [];
    waveBars.forEach((bar, i) => {
        const tween = gsap.to(bar, {
            height: () => gsap.utils.random(18, 110),
            duration: () => gsap.utils.random(0.25, 0.55),
            yoyo: true,
            repeat: -1,
            ease: 'sine.inOut',
            delay: i * 0.03,
        });
        waveTweens.push(tween);
    });

    // Counter 0 -> 100
    const p = { v: 0 };
    loaderTl.to(p, {
        v: 100,
        duration: 2.2,
        ease: 'power1.inOut',
        onUpdate: () => {
            loaderCounter.textContent = String(Math.floor(p.v)).padStart(2, '0') + '%';
        },
    });

    loaderTl.call(() => {
        loaderLabel.textContent = 'WORKSPACE READY';
    });

    // Freeze waveform on final beat (stop the yoyo loops)
    loaderTl.call(() => {
        waveTweens.forEach((t) => t.pause());
    }, null, '+=0.1');

    // Bars collapse to thin line then scatter outward
    loaderTl.to(waveBars, {
        height: 4,
        duration: 0.4,
        ease: 'power3.inOut',
    }, '+=0.05');

    loaderTl.to(waveBars, {
        y: (i) => (i % 2 === 0 ? -1 : 1) * gsap.utils.random(100, 300),
        x: (i) => (i - BAR_COUNT / 2) * gsap.utils.random(8, 20),
        opacity: 0,
        rotation: () => gsap.utils.random(-60, 60),
        duration: 0.9,
        ease: 'power3.in',
        stagger: 0.008,
    }, '+=0.1');

    loaderTl.to([loaderLabel, loaderCounter], {
        opacity: 0,
        y: -10,
        duration: 0.35,
        ease: 'power2.in',
        stagger: 0.05,
    }, '-=0.7');

    loaderTl.to(loader, {
        opacity: 0,
        duration: 0.5,
        ease: 'power2.out',
    }, '-=0.2');
    loaderTl.set(loader, { display: 'none' });

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

        // Gradient topbar draws across
        tl.to(topbar, {
            scaleX: 1,
            duration: 1.1,
            ease: 'power4.inOut',
        }, 0);

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

        // Tiles: scale + slide in from random offsets
        tl.to(tiles, {
            opacity: 1,
            scale: 1,
            y: 0,
            rotation: (i, el) => parseFloat(el.dataset.baseRot) || 0,
            duration: 1.2,
            stagger: {
                each: 0.06,
                from: 'random',
            },
            ease: 'power4.out',
        }, 0.35);

        // Title chars reveal
        tl.to(titleChars, {
            yPercent: 0, opacity: 1,
            duration: 1,
            stagger: 0.025,
            ease: 'expo.out',
        }, 0.9);

        // Description fades up
        tl.to(desc, {
            y: 0, opacity: 1,
            duration: 0.8,
        }, 1.4);

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

    // -------------------------------------------------------------------
    //  INTERACTIVE BEHAVIORS
    // -------------------------------------------------------------------
    function enableInteractions() {

        // ---- Magnetic elements ----
        magnetics.forEach((el) => {
            const strength = el.classList.contains('topnav__cta') ? 0.35
                : el.classList.contains('topnav__mark') ? 0.4
                : el.classList.contains('topnav__logo') ? 0.25
                : 0.22;
            el.addEventListener('mousemove', (e) => {
                const r = el.getBoundingClientRect();
                const cx = r.left + r.width / 2;
                const cy = r.top + r.height / 2;
                const dx = (e.clientX - cx) * strength;
                const dy = (e.clientY - cy) * strength;
                gsap.to(el, { x: dx, y: dy, 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 waveform pulse on hover ----
        const logoEl = root.querySelector('.topnav__logo');
        logoEl.addEventListener('mouseenter', () => {
            gsap.to(logoBars, {
                scaleY: () => gsap.utils.random(0.4, 1.8),
                duration: 0.3,
                yoyo: true,
                repeat: 3,
                stagger: { each: 0.05, from: 'random' },
                ease: 'sine.inOut',
                transformOrigin: 'center',
            });
        });

        // ---- Tile hover: bring forward with scale + deeper shadow ----
        tiles.forEach((tile) => {
            const baseRot = parseFloat(tile.dataset.baseRot) || 0;
            tile.addEventListener('mouseenter', () => {
                gsap.to(tile, {
                    scale: 1.08,
                    rotation: baseRot * 0.3,
                    zIndex: 20,
                    boxShadow: '0 40px 80px rgba(0, 0, 0, 0.8), 0 0 0 1px rgba(255, 255, 255, 0.15)',
                    duration: 0.5,
                    ease: 'power3.out',
                });
            });
            tile.addEventListener('mouseleave', () => {
                gsap.to(tile, {
                    scale: 1,
                    rotation: baseRot,
                    zIndex: 5,
                    boxShadow: '0 20px 40px rgba(0, 0, 0, 0.55)',
                    duration: 0.6,
                    ease: 'power3.out',
                });
            });
        });

        // ---- Title char proximity + hover ----
        const center = document.querySelector('.center');
        let mx = -9999, my = -9999;
        center.addEventListener('mousemove', (e) => {
            mx = e.clientX; my = e.clientY;
        });
        center.addEventListener('mouseleave', () => {
            mx = -9999; my = -9999;
            titleChars.forEach((c) => {
                gsap.to(c, { y: 0, color: '#ffffff', duration: 0.5, ease: 'power3.out' });
            });
        });

        gsap.ticker.add(() => {
            if (mx < 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 = mx - cx;
                const dy = my - cy;
                const dist = Math.sqrt(dx * dx + dy * dy);
                const PROX = 150;
                if (dist < PROX) {
                    const s = 1 - dist / PROX;
                    gsap.set(c, { y: -s * 18 });
                } else {
                    gsap.set(c, { y: 0 });
                }
            });
        });

        titleChars.forEach((c) => {
            c.addEventListener('mouseenter', () => {
                gsap.to(c, { color: '#c9bf98', duration: 0.2 });
            });
            c.addEventListener('mouseleave', () => {
                gsap.to(c, { color: '#ffffff', duration: 0.4 });
            });
        });

        // ---- Tile parallax with per-tile depth ----
        let tX = 0, tY = 0, cX = 0, cY = 0;
        root.addEventListener('mousemove', (e) => {
            const r = root.getBoundingClientRect();
            tX = ((e.clientX - r.left) / r.width - 0.5) * 2;
            tY = ((e.clientY - r.top) / r.height - 0.5) * 2;
        });
        root.addEventListener('mouseleave', () => { tX = 0; tY = 0; });

        gsap.ticker.add(() => {
            cX += (tX - cX) * 0.06;
            cY += (tY - cY) * 0.06;
            tiles.forEach((tile) => {
                // Don't override hover transform
                if (tile.matches(':hover')) return;
                const depth = parseFloat(tile.dataset.depth) || 0.3;
                const baseRot = parseFloat(tile.dataset.baseRot) || 0;
                gsap.set(tile, {
                    x: -cX * 40 * depth,
                    y: -cY * 28 * depth,
                    rotation: baseRot + cX * 0.8 * depth,
                });
            });
        });
    }

    // Helper: parse rotation from CSS matrix transform
    function getRotationFromTransform(transform) {
        if (!transform || transform === 'none') return 0;
        const match = transform.match(/matrix\(([^)]+)\)/);
        if (!match) return 0;
        const values = match[1].split(',').map(parseFloat);
        const angle = Math.atan2(values[1], values[0]) * (180 / Math.PI);
        return angle;
    }
})();

More GSAP landing pages