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

Free Corporate Landing Page Template with Compass-Dial Loader

Free corporate landing page template from ToolsWaves featuring a compass-dial loader with rotating needle, a floating white card with word reveal, business photo parallax, branded social buttons, and slider pagination. Free for any commercial use, no sign-up required.

CorporateCompassSliderMagnetic NavSocial

About this ToolsWaves template

This free corporate landing page template from ToolsWaves uses navigation-themed metaphors throughout โ€” a compass-dial loader with a rotating needle, drawn axes that suggest structure and direction, and slide navigation with both arrows and dot indicators. It is built for B2B consultancies, financial advisory firms, and corporate service providers that want polish without feeling staid. The lavender and blue palette is a softer alternative to traditional corporate navy.

Use this hero for B2B consulting firms, professional services, advisory practices, or corporate service businesses. ToolsWaves offers this template completely free with no sign-up โ€” copy the three template files into your build, replace the demo branding with your firm's identity, and swap the placeholder business imagery for your team and case studies. The CSS variables at the top of the stylesheet let you retheme the entire banner without hunting through individual rules.

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 Corporate Landing Page Template with Compass-Dial Loader</title>
  <meta name="description" content="Free GSAP corporate landing page hero from ToolsWaves" />
  <meta name="generator" content="ToolsWaves - https://toolswaves.in/landing-pages" />

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

  <!-- Twitter -->
  <meta name="twitter:card" content="summary_large_image" />
  <meta name="twitter:title" content="Free Corporate Landing Page Template with Compass-Dial Loader" />
  <meta name="twitter:description" content="Free GSAP corporate landing page hero from ToolsWaves" />
  <meta name="twitter:image" content="https://toolswaves.in/og?title=Free%20Corporate%20Landing%20Page%20Template%20with%20Compass-Dial%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:    #e6e6fa;
    --bg-2:  #dedef8;
    --ink:   #1a1a2e;
    --muted: rgba(26, 26, 46, 0.6);
    --line:  rgba(26, 26, 46, 0.12);
    --blue:  #3452e8;
    --blue-deep: #2439c4;
    --blue-soft: #6478ee;
    --white: #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: var(--white);
    border-radius: 999px;
    box-shadow: 0 4px 12px rgba(26, 26, 46, 0.1);
    opacity: 0.85;
    transition: opacity 0.3s, transform 0.3s;
}
.back-link:hover { opacity: 1; transform: translateY(-2px); }

/* =========================================================
   LOADER โ€” Compass dial
   ========================================================= */
.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__dial {
    width: min(220px, 55vw);
    filter: drop-shadow(0 20px 30px rgba(52, 82, 232, 0.2));
}
.loader__svg { width: 100%; height: auto; display: block; }

#loader-outer {
    stroke-dasharray: 535;
    stroke-dashoffset: 535;
}
#axis-v, #axis-h {
    stroke-dasharray: 140;
    stroke-dashoffset: 140;
}
#loader-needle {
    transform-origin: 100px 100px;
    will-change: transform;
}

.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);
    text-transform: uppercase;
}
.loader__counter {
    color: var(--blue);
    font-weight: 600;
    font-variant-numeric: tabular-nums;
    min-width: 3ch;
}

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

/* Large blue arc behind photo */
.bg-arc {
    position: absolute;
    top: 0; bottom: 0;
    right: 0;
    width: 55%;
    background: linear-gradient(135deg, #8497ff 0%, #3452e8 100%);
    border-radius: 0 0 0 50% / 0 0 0 40%;
    z-index: 1;
    will-change: transform;
}

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

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

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

.topnav__social {
    display: flex;
    gap: 0.5rem;
}
.social-btn {
    width: 34px; height: 34px;
    border-radius: 50%;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    color: var(--white);
    text-decoration: none;
    will-change: transform;
    transition: transform 0.3s, box-shadow 0.3s, filter 0.3s;
}
.social-btn svg { width: 15px; height: 15px; }
.social-btn--fb { background: #3452e8; }
.social-btn--tw { background: #1a1a2e; }
.social-btn--ig { background: linear-gradient(135deg, #f58529, #dd2a7b 50%, #8134af 100%); }
.social-btn--yt { background: #e63946; }
.social-btn:hover {
    transform: translateY(-3px);
    box-shadow: 0 10px 20px rgba(26, 26, 46, 0.2);
    filter: brightness(1.1);
}

/* ---------- Nav arrows ---------- */
.nav-arrow {
    position: absolute;
    top: 50%;
    transform: translateY(-50%);
    z-index: 25;
    width: 54px; height: 54px;
    background: var(--white);
    border: none;
    color: var(--ink);
    border-radius: 50%;
    cursor: pointer;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    box-shadow: 0 10px 24px rgba(26, 26, 46, 0.12);
    will-change: transform;
    transition: background 0.3s, color 0.3s;
}
.nav-arrow svg { width: 20px; height: 20px; transition: transform 0.3s; }
.nav-arrow--left  { left: 1.25rem; }
.nav-arrow--right { right: 1.25rem; }
.nav-arrow:hover {
    background: var(--blue);
    color: #fff;
}
.nav-arrow--left:hover svg  { transform: translateX(-3px); }
.nav-arrow--right:hover svg { transform: translateX(3px); }

/* ---------- Right photo ---------- */
.photo {
    position: absolute;
    top: 0; bottom: 0;
    right: 0;
    width: 55%;
    z-index: 3;
    overflow: hidden;
    border-radius: 0 0 0 50% / 0 0 0 40%;
    will-change: transform;
}
.photo img {
    width: 100%; height: 100%;
    object-fit: cover;
    object-position: center 30%;
    user-select: none;
    filter: saturate(1.05);
    transition: transform 0.8s ease;
}

/* ---------- White content card ---------- */
.card {
    position: absolute;
    top: 50%;
    left: 10%;
    transform: translateY(-50%);
    z-index: 10;
    width: min(560px, 55%);
    background: var(--white);
    padding: 3rem 3rem 2.5rem;
    border-radius: 14px;
    box-shadow: 0 25px 50px rgba(26, 26, 46, 0.08);
    will-change: transform;
}

.card__title {
    font-family: 'Inter', sans-serif;
    font-weight: 800;
    font-size: clamp(2.2rem, 4vw, 3.5rem);
    line-height: 1.05;
    letter-spacing: -0.02em;
    color: var(--ink);
    margin-bottom: 1.25rem;
    display: flex;
    flex-wrap: wrap;
    gap: 0 0.3rem;
}
.card__word {
    display: inline-block;
    overflow: hidden;
    padding-bottom: 0.05em;
    will-change: transform;
}
.card__word-inner {
    display: inline-block;
    will-change: transform, color;
    transition: color 0.3s ease;
}
.card__word--accent .card__word-inner { color: var(--blue); }

.card__desc {
    font-size: 0.9rem;
    line-height: 1.65;
    color: var(--muted);
    margin-bottom: 2rem;
    max-width: 460px;
}

.card__cta {
    display: flex;
    align-items: center;
    gap: 1.25rem;
}

.btn-dark {
    position: relative;
    display: inline-flex;
    align-items: center;
    gap: 0.5rem;
    background: var(--ink);
    color: #fff;
    text-decoration: none;
    padding: 0.95rem 2rem;
    border-radius: 999px;
    font-weight: 500;
    font-size: 0.9rem;
    overflow: hidden;
    will-change: transform;
}
.btn-dark__label { position: relative; z-index: 2; }
.btn-dark::before {
    content: '';
    position: absolute;
    inset: 0;
    background: var(--blue);
    transform: scaleX(0);
    transform-origin: right;
    transition: transform 0.5s cubic-bezier(0.65, 0, 0.35, 1);
    z-index: 1;
}
.btn-dark:hover::before { transform: scaleX(1); transform-origin: left; }

.btn-play {
    position: relative;
    width: 58px; height: 58px;
    background: var(--blue);
    color: #fff;
    border-radius: 50%;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    text-decoration: none;
    box-shadow: 0 10px 22px rgba(52, 82, 232, 0.4);
    will-change: transform;
}
.btn-play svg { width: 18px; height: 18px; margin-left: 2px; }
.btn-play-ring {
    position: absolute;
    inset: -6px;
    border: 2px solid var(--blue);
    border-radius: 50%;
    opacity: 0.4;
}

/* ---------- Dots ---------- */
.dots {
    position: absolute;
    bottom: 2rem;
    left: 13%;
    z-index: 15;
    display: flex;
    gap: 0.65rem;
    align-items: center;
}
.dots span {
    width: 10px; height: 10px;
    border: 2px solid var(--blue);
    border-radius: 50%;
    background: transparent;
    cursor: pointer;
    transition: background 0.3s, transform 0.3s;
}
.dots span.is-active { background: var(--blue); transform: scale(1.2); }
.dots span:hover:not(.is-active) { background: rgba(52, 82, 232, 0.4); }

/* ---------- Initial states ---------- */
[data-magnetic], [data-link], [data-fade], [data-title-word] .card__word-inner,
[data-card], [data-photo], [data-arc], [data-social], [data-cta], [data-play], [data-dots] {
    opacity: 0;
}

/* ---------- Responsive ---------- */
@media (max-width: 1100px) {
    .card { left: 5%; width: 60%; padding: 2rem 2rem 1.5rem; }
    .photo, .bg-arc { width: 45%; }
    .topnav__links { display: none; }
    .topnav { padding: 1rem 1.5rem; gap: 1rem; }
}
@media (max-width: 760px) {
    .photo, .bg-arc { width: 100%; opacity: 0.5; border-radius: 0; }
    .card {
        position: static;
        transform: none;
        margin: 2rem auto 0;
        width: calc(100% - 2rem);
        max-width: 500px;
    }
    .axis { height: auto; min-height: 100vh; padding-bottom: 5rem; }
    .nav-arrow { top: auto; bottom: 4.5rem; transform: none; }
    .dots { bottom: 2rem; left: 50%; transform: translateX(-50%); }
    .card__title { font-size: clamp(1.6rem, 7vw, 2.5rem); }
    .card__cta { flex-wrap: wrap; }
    .btn-dark { padding: 0.75rem 1.5rem; }
}
@media (max-width: 480px) {
    .topnav { flex-wrap: wrap; padding: 0.75rem 1rem; }
    .topnav__social { order: 2; margin-left: auto; }
    .nav-arrow { width: 40px; height: 40px; }
    .card { padding: 1.5rem 1.25rem; }
}

  </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 โ€” Axis</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-axis.css">
</head>
<body>
    

    <!-- ========== LOADER โ€” Compass dial with rotating needle ========== -->
    <div class="loader" id="loader">
        <div class="loader__dial">
            <svg class="loader__svg" viewBox="0 0 200 200" aria-hidden="true">
                <!-- Outer ring -->
                <circle cx="100" cy="100" r="85" fill="none" stroke="#3452e8" stroke-width="2" id="loader-outer"/>
                <!-- Tick marks -->
                <g id="loader-ticks" stroke="#3452e8" stroke-width="1.5"></g>
                <!-- Cross axes -->
                <line x1="100" y1="30" x2="100" y2="170" stroke="#3452e8" stroke-width="0.8" stroke-dasharray="2 3" opacity="0.4" id="axis-v"/>
                <line x1="30" y1="100" x2="170" y2="100" stroke="#3452e8" stroke-width="0.8" stroke-dasharray="2 3" opacity="0.4" id="axis-h"/>
                <!-- Rotating needle -->
                <g id="loader-needle" transform="translate(100 100)">
                    <path d="M 0 -65 L -6 0 L 0 -55 L 6 0 Z" fill="#3452e8"/>
                    <path d="M 0 65 L -4 0 L 0 55 L 4 0 Z" fill="#b8c3d6"/>
                </g>
                <!-- Center dot -->
                <circle cx="100" cy="100" r="6" fill="#3452e8"/>
                <circle cx="100" cy="100" r="3" fill="#fff"/>
            </svg>
        </div>
        <div class="loader__meta">
            <span class="loader__label" id="loader-label">CALIBRATING</span>
            <span class="loader__counter" id="loader-counter">0%</span>
        </div>
    </div>

    <section class="axis" id="axis">
        <!-- Background arc -->
        <div class="bg-arc" data-arc></div>

        <!-- ========== 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 36" aria-hidden="true">
                        <circle cx="18" cy="18" r="15" fill="none" stroke="#3452e8" stroke-width="2.2"/>
                        <path d="M 8 18 L 28 18 M 18 8 L 18 28" stroke="#3452e8" stroke-width="2.2" stroke-linecap="round"/>
                        <circle cx="18" cy="18" r="3" fill="#3452e8"/>
                    </svg>
                </span>
                <span class="topnav__logo-text">Axis</span>
            </a>

            <nav class="topnav__links">
                <a href="#" class="is-active" data-magnetic data-link>Home</a>
                <a href="#" data-magnetic data-link>Pages</a>
                <a href="#" data-magnetic data-link>Gallery</a>
                <a href="#" data-magnetic data-link>Blog</a>
                <a href="#" data-magnetic data-link>Contacts</a>
            </nav>

            <div class="topnav__social">
                <a href="#" class="social-btn social-btn--fb" data-magnetic data-social aria-label="Facebook">
                    <svg viewBox="0 0 24 24" fill="currentColor"><path d="M13.5 21v-8h2.7l.4-3h-3.1V8c0-.9.3-1.5 1.5-1.5h1.6V3.8c-.3 0-1.2-.1-2.3-.1-2.3 0-3.9 1.4-3.9 4V10H7.8v3h2.5v8h3.2z"/></svg>
                </a>
                <a href="#" class="social-btn social-btn--tw" data-magnetic data-social aria-label="Twitter">
                    <svg viewBox="0 0 24 24" fill="currentColor"><path d="M18.9 7.3c0 .2 0 .4 0 .5 0 5.4-4.1 11.6-11.6 11.6-2.3 0-4.5-.7-6.3-1.9.3 0 .6.1 1 .1 1.9 0 3.7-.7 5.1-1.8-1.8 0-3.3-1.2-3.9-2.9.3 0 .5.1.8.1.4 0 .7 0 1.1-.1-1.9-.4-3.4-2.1-3.4-4.2v-.1c.6.3 1.2.5 1.9.5C2.4 8.4 1.6 7 1.6 5.5c0-.8.2-1.5.6-2.1 2.1 2.5 5.1 4.1 8.6 4.3-.1-.3-.1-.6-.1-.9 0-2.3 1.8-4.1 4.1-4.1 1.2 0 2.3.5 3 1.3.9-.2 1.8-.5 2.6-1-.3 1-1 1.8-1.8 2.3.8-.1 1.6-.3 2.4-.6-.6.8-1.3 1.5-2.1 2.1z"/></svg>
                </a>
                <a href="#" class="social-btn social-btn--ig" data-magnetic data-social aria-label="Instagram">
                    <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8">
                        <rect x="3" y="3" width="18" height="18" rx="5"/>
                        <circle cx="12" cy="12" r="4"/>
                        <circle cx="17" cy="7" r="0.8" fill="currentColor"/>
                    </svg>
                </a>
                <a href="#" class="social-btn social-btn--yt" data-magnetic data-social aria-label="YouTube">
                    <svg viewBox="0 0 24 24" fill="currentColor"><path d="M23 9.7s-.2-1.6-.9-2.3c-.8-.9-1.8-.9-2.2-1C16.6 6.2 12 6.2 12 6.2s-4.6 0-7.9.2c-.4 0-1.4.1-2.2 1-.7.7-.9 2.3-.9 2.3S.8 11.5.8 13.4v1.7c0 1.9.2 3.7.2 3.7s.2 1.6.9 2.3c.8.9 2 .9 2.5 1 1.9.2 7.6.2 7.6.2s4.6 0 7.9-.2c.4 0 1.4-.1 2.2-1 .7-.7.9-2.3.9-2.3s.2-1.9.2-3.7v-1.7c0-1.9-.2-3.7-.2-3.7zM9.8 17V10l5.9 3.5L9.8 17z"/></svg>
                </a>
            </div>
        </header>

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

        <!-- Right photo -->
        <div class="photo" id="photo" data-photo>
            <img src="https://images.unsplash.com/photo-1521737604893-d14cc237f11d?auto=format&fit=crop&w=1400&q=80"
                 alt="Business team"
                 draggable="false"
                 onerror="this.style.display='none'">
        </div>

        <!-- White content card -->
        <div class="card" id="card" data-card>
            <h1 class="card__title" aria-label="Axis Business Template">
                <span class="card__word card__word--accent" data-title-word>Axis</span>
                <span class="card__word" data-title-word>Business</span>
                <span class="card__word" data-title-word>Template</span>
            </h1>
            <p class="card__desc" data-fade>
                This is our dedicated team who work day-in and day-out
                together to bring our clients the most amazing projects
                for a digitally connected world.
            </p>
            <div class="card__cta" data-fade>
                <a href="#" class="btn-dark" data-magnetic data-cta>
                    <span class="btn-dark__label">Learn More</span>
                </a>
                <a href="#" class="btn-play" data-magnetic data-play>
                    <svg viewBox="0 0 24 24" fill="currentColor"><path d="M8 5v14l11-7z"/></svg>
                    <span class="btn-play-ring"></span>
                </a>
            </div>
        </div>

        <!-- Bottom pagination dots -->
        <div class="dots" data-dots>
            <span class="is-active" data-dot="1"></span>
            <span data-dot="2"></span>
            <span data-dot="3"></span>
            <span data-dot="4"></span>
        </div>
    </section>

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

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

    // -------------------------------------------------------------------
    //  WRAP TITLE WORDS IN INNER SPAN (for reveal + hover color)
    // -------------------------------------------------------------------
    const titleWords = root.querySelectorAll('[data-title-word]');
    titleWords.forEach((w) => {
        const text = w.textContent;
        w.textContent = '';
        const inner = document.createElement('span');
        inner.className = 'card__word-inner';
        inner.textContent = text;
        w.appendChild(inner);
    });
    const wordInners = root.querySelectorAll('.card__word-inner');

    // -------------------------------------------------------------------
    //  BUILD LOADER TICK MARKS
    // -------------------------------------------------------------------
    const ticksG = document.getElementById('loader-ticks');
    const TICK_COUNT = 24;
    for (let i = 0; i < TICK_COUNT; i++) {
        const angle = (i / TICK_COUNT) * 360;
        const tick = document.createElementNS('http://www.w3.org/2000/svg', 'line');
        const isLarge = i % 6 === 0;
        const rad = (angle - 90) * Math.PI / 180;
        const r1 = isLarge ? 73 : 77;
        const r2 = 85;
        tick.setAttribute('x1', 100 + Math.cos(rad) * r1);
        tick.setAttribute('y1', 100 + Math.sin(rad) * r1);
        tick.setAttribute('x2', 100 + Math.cos(rad) * r2);
        tick.setAttribute('y2', 100 + Math.sin(rad) * r2);
        tick.setAttribute('stroke-width', isLarge ? 2 : 1);
        tick.setAttribute('opacity', 0);
        ticksG.appendChild(tick);
    }
    const tickEls = ticksG.querySelectorAll('line');

    // -------------------------------------------------------------------
    //  REFERENCES
    // -------------------------------------------------------------------
    const loader = document.getElementById('loader');
    const loaderCounter = document.getElementById('loader-counter');
    const loaderLabel = document.getElementById('loader-label');
    const loaderOuter = document.getElementById('loader-outer');
    const loaderNeedle = document.getElementById('loader-needle');
    const axisV = document.getElementById('axis-v');
    const axisH = document.getElementById('axis-h');

    const magnetics = root.querySelectorAll('[data-magnetic]');
    const fades = root.querySelectorAll('[data-fade]');
    const card = root.querySelector('[data-card]');
    const photo = document.getElementById('photo');
    const photoImg = photo.querySelector('img');
    const arc = root.querySelector('[data-arc]');
    const logoMark = document.getElementById('logo-mark');
    const dots = root.querySelectorAll('.dots span');
    const dotsContainer = root.querySelector('[data-dots]');
    const prevBtn = document.getElementById('prev');
    const nextBtn = document.getElementById('next');
    const playRing = root.querySelector('.btn-play-ring');
    const socialBtns = root.querySelectorAll('[data-social]');

    // -------------------------------------------------------------------
    //  INITIAL STATES
    // -------------------------------------------------------------------
    gsap.set(magnetics, { y: -15, opacity: 0 });
    gsap.set(fades, { y: 20, opacity: 0 });
    gsap.set(wordInners, { yPercent: 110, opacity: 0 });
    gsap.set(card, { x: -60, opacity: 0, scale: 0.96 });
    gsap.set(photo, { opacity: 0 });
    gsap.set(photoImg, { scale: 1.15 });
    gsap.set(arc, { xPercent: 50, opacity: 0 });
    gsap.set(socialBtns, { scale: 0, opacity: 0 });
    gsap.set(dotsContainer, { y: 20, opacity: 0 });

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

    // Outer ring draws
    loaderTl.to(loaderOuter, {
        strokeDashoffset: 0,
        duration: 1.2,
        ease: 'power2.inOut',
    }, 0);

    // Axes draw
    loaderTl.to([axisV, axisH], {
        strokeDashoffset: 0,
        duration: 0.8,
        stagger: 0.1,
        ease: 'power2.out',
    }, 0.4);

    // Ticks appear sequentially
    loaderTl.to(tickEls, {
        opacity: 1,
        duration: 0.03,
        stagger: 0.025,
    }, 0.6);

    // Needle rotates continuously (like compass seeking)
    gsap.to(loaderNeedle, {
        rotation: 360,
        duration: 2.2,
        repeat: -1,
        ease: 'none',
    });

    // Counter + labels
    const p = { v: 0 };
    loaderTl.to(p, {
        v: 100,
        duration: 2,
        ease: 'power1.inOut',
        onUpdate: () => {
            loaderCounter.textContent = Math.floor(p.v) + '%';
            if (p.v > 30 && loaderLabel.textContent === 'CALIBRATING') loaderLabel.textContent = 'ALIGNING AXES';
            if (p.v > 60 && loaderLabel.textContent === 'ALIGNING AXES') loaderLabel.textContent = 'FINDING TRUE NORTH';
            if (p.v > 95 && loaderLabel.textContent === 'FINDING TRUE NORTH') loaderLabel.textContent = 'READY';
        },
    }, 0.2);

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

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

        // Background arc slides in
        tl.to(arc, {
            xPercent: 0, opacity: 1,
            duration: 1.2,
            ease: 'power4.out',
        }, 0);

        // Photo fades + zoom settle
        tl.to(photo, { opacity: 1, duration: 1 }, 0.3);
        tl.to(photoImg, {
            scale: 1,
            duration: 1.6,
            ease: 'power3.out',
        }, 0.3);

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

        // Social buttons pop
        tl.to(socialBtns, {
            scale: 1, opacity: 1,
            duration: 0.6,
            stagger: 0.08,
            ease: 'back.out(1.8)',
        }, 0.7);

        // Card slides in from left
        tl.to(card, {
            x: 0, opacity: 1, scale: 1,
            duration: 1.1,
            ease: 'power4.out',
        }, 0.6);

        // Title words reveal
        tl.to(wordInners, {
            yPercent: 0, opacity: 1,
            duration: 0.9,
            stagger: 0.1,
            ease: 'expo.out',
        }, 0.95);

        // Description + CTA
        tl.to(fades, {
            y: 0, opacity: 1,
            duration: 0.7,
            stagger: 0.15,
        }, 1.3);

        // Dots
        tl.to(dotsContainer, { y: 0, opacity: 1, duration: 0.6 }, 1.6);

        tl.call(startContinuous, null, 1.8);
        tl.call(enableInteractions, null, 1.8);
    }

    // -------------------------------------------------------------------
    //  CONTINUOUS
    // -------------------------------------------------------------------
    function startContinuous() {
        // Play ring pulse
        gsap.to(playRing, {
            scale: 1.25,
            opacity: 0,
            duration: 1.6,
            repeat: -1,
            ease: 'power1.out',
            transformOrigin: 'center',
        });

        // Active dot breath
        const activeDot = root.querySelector('.dots span.is-active');
        if (activeDot) gsap.to(activeDot, {
            scale: 1.35,
            duration: 1,
            yoyo: true,
            repeat: -1,
            ease: 'sine.inOut',
        });
    }

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

        // ---- Magnetic ----
        magnetics.forEach((el) => {
            const strength = el.classList.contains('btn-dark') ? 0.3
                : el.classList.contains('btn-play') ? 0.4
                : el.classList.contains('nav-arrow') ? 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: icon rotates on hover ----
        const logoEl = root.querySelector('.topnav__logo');
        logoEl.addEventListener('mouseenter', () => {
            gsap.to(logoMark.querySelector('svg'), { rotation: 360, duration: 0.7, ease: 'power3.inOut' });
        });
        logoEl.addEventListener('mouseleave', () => {
            gsap.set(logoMark.querySelector('svg'), { rotation: 0 });
        });

        // ---- Title words hover: accent color ----
        wordInners.forEach((inner) => {
            const isAccent = inner.closest('.card__word--accent');
            inner.addEventListener('mouseenter', () => {
                gsap.to(inner, { color: isAccent ? '#1a1a2e' : '#3452e8', duration: 0.2 });
            });
            inner.addEventListener('mouseleave', () => {
                gsap.to(inner, { color: isAccent ? '#3452e8' : '#1a1a2e', duration: 0.4 });
            });
        });

        // ---- Play button: click = pulse wave ----
        const playBtn = root.querySelector('[data-play]');
        playBtn.addEventListener('click', (e) => {
            e.preventDefault();
            gsap.fromTo(playBtn,
                { scale: 1 },
                { scale: 0.9, duration: 0.15, yoyo: true, repeat: 1, ease: 'back.out(2)' }
            );
            // Burst rings
            for (let i = 0; i < 3; i++) {
                const r = document.createElement('span');
                r.style.cssText = `position:absolute;inset:0;border:2px solid #3452e8;border-radius:50%;pointer-events:none;`;
                playBtn.appendChild(r);
                gsap.fromTo(r,
                    { scale: 1, opacity: 0.7 },
                    {
                        scale: 2.2, opacity: 0,
                        duration: 0.8,
                        delay: i * 0.1,
                        ease: 'power2.out',
                        onComplete: () => r.remove(),
                    }
                );
            }
        });

        // ---- Slide content (4 unique slides) ----
        const slides = [
            {
                accent: 'Axis',
                words: ['Business', 'Template'],
                desc: 'This is our dedicated team who work day-in and day-out together to bring our clients the most amazing projects for a digitally connected world.',
                image: 'https://images.unsplash.com/photo-1521737604893-d14cc237f11d?auto=format&fit=crop&w=1400&q=80',
            },
            {
                accent: 'Creative',
                words: ['Marketing', 'Agency'],
                desc: 'We craft data-driven campaigns that tell meaningful stories, reach the right audience, and turn casual browsers into lifelong customers.',
                image: 'https://images.unsplash.com/photo-1522071820081-009f0129c71c?auto=format&fit=crop&w=1400&q=80',
            },
            {
                accent: 'Strategic',
                words: ['Partners', 'Worldwide'],
                desc: 'A network of senior consultants, designers, and engineers ready to plug into your team across time zones and scale your next big move.',
                image: 'https://images.unsplash.com/photo-1600880292203-757bb62b4baf?auto=format&fit=crop&w=1400&q=80',
            },
            {
                accent: 'Innovative',
                words: ['Digital', 'Solutions'],
                desc: 'From AI workflows to real-time dashboards, we build pragmatic software that ships fast, scales cleanly, and actually moves the needle.',
                image: 'https://images.unsplash.com/photo-1556761175-5973dc0f32e7?auto=format&fit=crop&w=1400&q=80',
            },
        ];

        const titleEl = root.querySelector('.card__title');
        const descEl = root.querySelector('.card__desc');
        let currentSlide = 0; // initial (1st slide is-active per HTML)

        function renderSlide(idx, direction = 1) {
            const slide = slides[idx];
            const oldInners = titleEl.querySelectorAll('.card__word-inner');
            const outTl = gsap.timeline();

            // Slide out old content
            outTl.to(oldInners, {
                yPercent: -110,
                opacity: 0,
                duration: 0.35,
                stagger: 0.04,
                ease: 'power2.in',
            }, 0);
            outTl.to(descEl, {
                y: -15,
                opacity: 0,
                duration: 0.3,
                ease: 'power2.in',
            }, 0);
            outTl.to(photoImg, {
                scale: 1.15,
                opacity: 0.4,
                duration: 0.45,
                ease: 'power2.in',
            }, 0);

            outTl.call(() => {
                // Rebuild title HTML with new content
                titleEl.innerHTML = `
                    <span class="card__word card__word--accent"><span class="card__word-inner">${slide.accent}</span></span>
                    <span class="card__word"><span class="card__word-inner">${slide.words[0]}</span></span>
                    <span class="card__word"><span class="card__word-inner">${slide.words[1]}</span></span>
                `;
                descEl.textContent = slide.desc;
                photoImg.src = slide.image;

                // Attach hover handlers on the new words
                const newInners = titleEl.querySelectorAll('.card__word-inner');
                newInners.forEach((inner) => {
                    const isAccent = inner.closest('.card__word--accent');
                    inner.addEventListener('mouseenter', () => {
                        gsap.to(inner, { color: isAccent ? '#1a1a2e' : '#3452e8', duration: 0.2 });
                    });
                    inner.addEventListener('mouseleave', () => {
                        gsap.to(inner, { color: isAccent ? '#3452e8' : '#1a1a2e', duration: 0.4 });
                    });
                });

                // Animate in new content
                gsap.set(newInners, { yPercent: 110, opacity: 0 });
                gsap.set(descEl, { y: 15, opacity: 0 });

                gsap.to(newInners, {
                    yPercent: 0, opacity: 1,
                    duration: 0.7,
                    stagger: 0.08,
                    ease: 'power3.out',
                });
                gsap.to(descEl, {
                    y: 0, opacity: 1,
                    duration: 0.6,
                    delay: 0.15,
                    ease: 'power3.out',
                });
                gsap.to(photoImg, {
                    scale: 1.03, opacity: 1,
                    duration: 0.9,
                    ease: 'power3.out',
                });
            });

            // Arc + card flash on transition
            gsap.fromTo(arc,
                { x: 30 * direction },
                { x: 0, duration: 0.9, ease: 'power4.out' }
            );
            gsap.fromTo(card,
                { x: -20 * direction },
                { x: 0, duration: 0.9, ease: 'power4.out' }
            );
        }

        // ---- Dots ----
        dots.forEach((dot, i) => {
            dot.addEventListener('click', () => {
                if (i === currentSlide) return;
                const direction = i > currentSlide ? 1 : -1;
                // Handle wrap (far end โ†’ first slide reads as forward visually)
                dots.forEach(d => d.classList.remove('is-active'));
                dot.classList.add('is-active');
                // Re-apply active-dot breathing to the new active
                const newActive = root.querySelector('.dots span.is-active');
                gsap.killTweensOf(dots);
                gsap.set(dots, { scale: 1 });
                if (newActive) gsap.to(newActive, {
                    scale: 1.35,
                    duration: 1,
                    yoyo: true,
                    repeat: -1,
                    ease: 'sine.inOut',
                });
                currentSlide = i;
                renderSlide(i, direction);
            });
        });

        // ---- Nav arrows: advance dots ----
        const advance = (dir) => {
            const next = (currentSlide + dir + dots.length) % dots.length;
            dots[next].click();
        };
        nextBtn.addEventListener('click', () => advance(1));
        prevBtn.addEventListener('click', () => advance(-1));

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

        // ---- Photo parallax + tilt ----
        let mx = 0, my = 0, cmx = 0, cmy = 0;
        root.addEventListener('mousemove', (e) => {
            const r = root.getBoundingClientRect();
            mx = ((e.clientX - r.left) / r.width - 0.5) * 2;
            my = ((e.clientY - r.top) / r.height - 0.5) * 2;
        });
        root.addEventListener('mouseleave', () => { mx = 0; my = 0; });

        gsap.ticker.add(() => {
            cmx += (mx - cmx) * 0.05;
            cmy += (my - cmy) * 0.05;
            gsap.set(photoImg, {
                scale: 1.03,
                x: cmx * 18,
                y: cmy * 12,
            });
            gsap.set(arc, {
                x: cmx * 14,
                y: cmy * 10,
            });
            gsap.set(card, {
                x: cmx * 6,
                y: cmy * 4,
            });
        });
    }
})();

More GSAP landing pages