ToolsWaves
Free GSAP Landing Page Template · ToolsWaves · Events

Free Event Landing Page Template with Countdown and Ticket Loader

Free event landing page template from ToolsWaves with a split-screen music/event layout, live countdown, progress bar, and concert-ticket loader that stamps and tears apart. Custom cursor, magnetic nav, and countdown that scrambles on hover. Free for any event launch.

EventTicketCountdownCustom CursorSplit Layout

About this ToolsWaves template

This free event landing page template from ToolsWaves is purpose-built for time-sensitive event pages — music festivals, conferences, product launches, or any countdown-driven announcement. The loader features a concert ticket that stamps with a quality mark and then tears along its perforation — a tactile animation that immediately signals the event-ticketing context. Once loaded, the page shows a live countdown timer with a progress bar, a custom cursor with magnetic navigation, and countdown digits that scramble on hover for added interactivity.

Use this template for festival announcements, conference landing pages, product launch countdown sites, or any time-sensitive event page where the date itself is the headline. ToolsWaves provides this template free with no sign-up required — copy the HTML, CSS, and JS files into your project, replace the demo event branding with your actual event details, and configure the countdown target date in the JavaScript file. The split-screen layout balances date prominence with supporting content like venue details or speaker lineups.

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 Event Landing Page Template with Countdown and Ticket Loader</title>
  <meta name="description" content="Free GSAP event landing page hero from ToolsWaves" />
  <meta name="generator" content="ToolsWaves - https://toolswaves.in/landing-pages" />

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

  <!-- Twitter -->
  <meta name="twitter:card" content="summary_large_image" />
  <meta name="twitter:title" content="Free Event Landing Page Template with Countdown and Ticket Loader" />
  <meta name="twitter:description" content="Free GSAP event landing page hero from ToolsWaves" />
  <meta name="twitter:image" content="https://toolswaves.in/og?title=Free%20Event%20Landing%20Page%20Template%20with%20Countdown%20and%20Ticket%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:    #0a0810;
    --bg-2:  #110c18;
    --ink:   #ffffff;
    --muted: rgba(255, 255, 255, 0.55);
    --line:  rgba(255, 255, 255, 0.15);
    --accent:#d14a7a;
    --violet:#5a2c8a;
    --cream: #f5ecd8;
}

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.08);
    border: 1px solid rgba(255, 255, 255, 0.18);
    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: 6px; height: 6px;
    background: var(--cream);
    border-radius: 50%;
}
.cursor-ring {
    width: 36px; height: 36px;
    border: 1px solid rgba(245, 236, 216, 0.6);
    border-radius: 50%;
    transition: width 0.3s ease, height 0.3s ease, border-color 0.3s ease;
}
.cursor-ring.is-hover { width: 60px; height: 60px; border-color: var(--cream); }
@media (hover: none), (pointer: coarse) { .cursor-dot, .cursor-ring { display: none; } }

/* =========================================================
   LOADER — Concert ticket
   ========================================================= */
.loader {
    position: fixed;
    inset: 0;
    z-index: 999;
    background: radial-gradient(ellipse at center, var(--bg-2) 0%, var(--bg) 80%);
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 1.75rem;
}

.ticket {
    position: relative;
    width: min(520px, 82vw);
    height: 180px;
    display: flex;
    background: var(--cream);
    color: #1a1a1a;
    border-radius: 6px;
    box-shadow: 0 30px 60px rgba(0, 0, 0, 0.55),
                0 10px 20px rgba(0, 0, 0, 0.35);
    overflow: hidden;
    will-change: transform;
}

.ticket__half {
    position: relative;
    padding: 1rem 1.25rem;
    display: flex;
    flex-direction: column;
    justify-content: space-between;
    will-change: transform;
}
.ticket__half--left { flex: 0 0 72%; }
.ticket__half--right {
    flex: 1;
    background: #1a1a1a;
    color: var(--cream);
    align-items: center;
    justify-content: center;
    text-align: center;
}

.ticket__perf {
    width: 1px;
    border-left: 2px dashed rgba(26, 26, 26, 0.35);
    margin: 12px 0;
    background: transparent;
}

.ticket__top, .ticket__bottom {
    display: flex;
    justify-content: space-between;
    align-items: center;
    font-family: 'JetBrains Mono', monospace;
    font-size: 0.62rem;
    letter-spacing: 0.2em;
    color: rgba(26, 26, 26, 0.6);
}
.ticket__sm {
    font-family: 'JetBrains Mono', monospace;
    font-size: 0.6rem;
    letter-spacing: 0.2em;
    font-weight: 600;
}
.ticket__main {
    display: flex;
    align-items: flex-end;
    gap: 1rem;
}
.ticket__date {
    font-family: 'Bebas Neue', sans-serif;
    font-size: 1.4rem;
    line-height: 1;
    color: var(--accent);
    letter-spacing: 0.05em;
}
.ticket__event {
    font-family: 'Bebas Neue', sans-serif;
    font-size: 2.3rem;
    line-height: 0.85;
    letter-spacing: 0.02em;
    color: #1a1a1a;
}
.ticket__barcode {
    display: flex;
    gap: 1px;
    height: 28px;
    align-items: stretch;
    flex: 1;
    margin-right: 0.75rem;
}
.ticket__barcode span {
    display: block;
    background: #1a1a1a;
    flex: 1;
}
.ticket__stub {
    display: flex;
    flex-direction: column;
    gap: 0.25rem;
    align-items: center;
}
.ticket__stub-no {
    font-family: 'Bebas Neue', sans-serif;
    font-size: 2rem;
    line-height: 1;
    letter-spacing: 0.05em;
    color: var(--accent);
}

.ticket__stamp {
    position: absolute;
    top: 18%;
    right: 18%;
    font-family: 'Bebas Neue', sans-serif;
    font-size: 1.6rem;
    color: var(--accent);
    border: 3px solid var(--accent);
    padding: 0.2rem 0.8rem;
    letter-spacing: 0.15em;
    transform: rotate(-14deg) scale(3);
    opacity: 0;
    z-index: 5;
    text-shadow: 1px 1px 0 rgba(209, 74, 122, 0.4);
}

.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(--cream);
    font-weight: 600;
    font-variant-numeric: tabular-nums;
    min-width: 3ch;
}

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

/* ---------- Split background ---------- */
.bg { position: absolute; inset: 0; z-index: 1; }
.bg__half {
    position: absolute;
    top: 0; bottom: 0;
    width: 50%;
    overflow: hidden;
    will-change: transform;
}
.bg__half--left  { left: 0; }
.bg__half--right { right: 0; }
.bg__half img,
.bg__half video {
    width: 100%; height: 100%;
    object-fit: cover;
    filter: saturate(1.1) contrast(1.05);
}

.bg__overlay {
    position: absolute;
    inset: 0;
    background:
        radial-gradient(ellipse at center, rgba(10, 8, 16, 0.45) 0%, rgba(10, 8, 16, 0.85) 100%),
        linear-gradient(180deg, rgba(10, 8, 16, 0.4) 0%, rgba(10, 8, 16, 0.2) 30%, rgba(10, 8, 16, 0.8) 100%),
        linear-gradient(90deg, rgba(90, 44, 138, 0.35) 0%, rgba(10, 8, 16, 0) 25%, rgba(10, 8, 16, 0) 75%, rgba(209, 74, 122, 0.2) 100%);
    pointer-events: none;
}

.bg__divider {
    position: absolute;
    top: 0; bottom: 0;
    left: 50%;
    width: 1px;
    background: linear-gradient(180deg, transparent, rgba(255,255,255,0.25) 15%, rgba(255,255,255,0.25) 85%, transparent);
    transform-origin: center;
    z-index: 2;
    pointer-events: none;
}
.bg__divider::before {
    content: '';
    position: absolute;
    top: 50%; left: 50%;
    transform: translate(-50%, -50%);
    width: 6px; height: 40px;
    background: rgba(255, 255, 255, 0.5);
    border-radius: 4px;
}

/* ---------- Top nav ---------- */
.topnav {
    position: absolute;
    top: 0; left: 0; right: 0;
    z-index: 20;
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 2rem;
    padding: 1.25rem 2rem;
    background: rgba(10, 8, 16, 0.6);
    backdrop-filter: blur(10px);
}
.topnav__left, .topnav__right {
    display: flex;
    gap: 2rem;
    flex: 1;
}
.topnav__left  { justify-content: flex-end; }
.topnav__right { justify-content: flex-start; }
.topnav a {
    position: relative;
    color: var(--ink);
    text-decoration: none;
    font-weight: 600;
    font-size: 0.85rem;
    letter-spacing: 0.12em;
    padding: 0.3rem 0;
    cursor: none;
    will-change: transform;
    display: inline-flex;
    align-items: center;
    gap: 0.3rem;
}
.topnav a.is-active { color: var(--cream); }
.topnav a::after {
    content: '';
    position: absolute;
    left: 0; bottom: -2px;
    width: 100%; height: 1px;
    background: var(--cream);
    transform: scaleX(0);
    transform-origin: right;
    transition: transform 0.4s cubic-bezier(0.65, 0, 0.35, 1);
}
.topnav a.is-active::after,
.topnav a:hover::after { transform: scaleX(1); transform-origin: left; }
.caret { font-size: 0.65em; opacity: 0.7; transition: transform 0.3s; }
.topnav a:hover .caret { transform: translateY(2px); }

.topnav__logo {
    display: inline-flex;
    align-items: center;
    gap: 0.4rem;
    font-family: 'Inter', sans-serif;
    font-weight: 700;
    font-size: 0.95rem;
    letter-spacing: 0.18em;
    padding: 0 0.75rem;
    cursor: none;
    will-change: transform;
    text-decoration: none;
    color: var(--ink);
    white-space: nowrap;
}
.topnav__logo::after { display: none; }
.topnav__logo-divider {
    color: var(--accent);
    font-size: 1.2em;
    font-weight: 400;
}

/* ---------- Hero ---------- */
.hero {
    position: absolute;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%);
    z-index: 15;
    text-align: center;
    width: min(700px, 90vw);
    padding: 0 1rem;
}

.hero__title {
    font-family: 'Bebas Neue', sans-serif;
    font-size: clamp(2.4rem, 5.5vw, 4.5rem);
    line-height: 1;
    letter-spacing: 0.02em;
    color: var(--ink);
    margin-bottom: 1rem;
    text-shadow: 0 8px 40px rgba(0, 0, 0, 0.6);
}
.hero__line {
    display: block;
    overflow: hidden;
    padding-bottom: 0.02em;
}
.hero__char {
    display: inline-block;
    will-change: transform, color;
    transition: color 0.3s ease;
}
.hero__char.is-space { width: 0.2em; }

.hero__date {
    font-family: 'JetBrains Mono', monospace;
    font-size: 0.8rem;
    letter-spacing: 0.35em;
    color: var(--muted);
    margin-bottom: 1.75rem;
}

/* Progress bar */
.progress {
    position: relative;
    width: 100%;
    max-width: 460px;
    margin: 0 auto 1.5rem;
    height: 20px;
    display: flex;
    align-items: center;
}
.progress__ticks {
    position: absolute;
    inset: 0;
    display: flex;
    justify-content: space-between;
    align-items: center;
    pointer-events: none;
}
.progress__ticks span {
    flex: 1;
    height: 100%;
    border-right: 1px solid rgba(255, 255, 255, 0.06);
}
.progress__fill {
    position: absolute;
    left: 0; top: 50%;
    height: 2px;
    width: 0%;
    background: var(--cream);
    transform: translateY(-50%);
    box-shadow: 0 0 10px rgba(245, 236, 216, 0.6);
    transform-origin: left;
}
.progress__handle {
    position: absolute;
    top: 50%;
    left: 0;
    width: 10px; height: 10px;
    background: var(--cream);
    border-radius: 50%;
    transform: translate(-50%, -50%);
    box-shadow: 0 0 0 4px rgba(245, 236, 216, 0.2);
}

/* Countdown */
.countdown {
    display: inline-flex;
    align-items: flex-start;
    gap: 2rem;
    font-family: 'Inter', sans-serif;
}
.countdown__dash {
    font-size: 2rem;
    line-height: 1;
    color: var(--muted);
    margin-top: 0.3rem;
}
.countdown__cell {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 0.4rem;
    min-width: 60px;
}
.countdown__value {
    font-size: clamp(1.75rem, 3vw, 2.5rem);
    font-weight: 700;
    line-height: 1;
    color: var(--ink);
    font-variant-numeric: tabular-nums;
    letter-spacing: -0.02em;
    position: relative;
    will-change: transform;
}
.countdown__label {
    font-size: 0.7rem;
    letter-spacing: 0.2em;
    color: var(--muted);
    text-transform: uppercase;
}

/* ---------- Right-side quicklinks ---------- */
.quicklinks {
    position: absolute;
    top: 50%;
    right: 3rem;
    transform: translateY(-50%);
    z-index: 18;
    display: flex;
    flex-direction: column;
    gap: 0.75rem;
}
.quicklinks a {
    position: relative;
    display: inline-flex;
    align-items: center;
    gap: 1rem;
    color: var(--ink);
    text-decoration: none;
    font-weight: 700;
    font-size: 1rem;
    letter-spacing: 0.15em;
    cursor: none;
    will-change: transform;
}
.quicklinks__bar {
    display: inline-block;
    width: 40px;
    height: 1px;
    background: var(--cream);
    transition: width 0.4s cubic-bezier(0.65, 0, 0.35, 1);
}
.quicklinks a:hover .quicklinks__bar { width: 70px; }
.quicklinks__text {
    position: relative;
    transition: transform 0.4s cubic-bezier(0.65, 0, 0.35, 1);
}
.quicklinks a:hover .quicklinks__text {
    transform: translateX(6px);
    color: var(--cream);
}

/* ---------- Bottom meta ---------- */
.meta {
    position: absolute;
    bottom: 1.5rem;
    left: 0; right: 0;
    z-index: 15;
    display: flex;
    justify-content: center;
    gap: 3rem;
    font-family: 'JetBrains Mono', monospace;
    font-size: 0.7rem;
    letter-spacing: 0.25em;
    color: var(--muted);
}

/* ---------- Initial hidden states ---------- */
[data-magnetic], [data-link], [data-quick], [data-fade], [data-title-line] .hero__char, [data-bg] { opacity: 0; }
[data-title-line] .hero__char { opacity: 0; }

/* ---------- Responsive ---------- */
@media (max-width: 900px) {
    .topnav__left, .topnav__right { gap: 1.25rem; }
    .quicklinks { right: 1.25rem; gap: 0.5rem; }
    .quicklinks a { font-size: 0.85rem; }
    .quicklinks__bar { width: 24px; }
    .countdown { gap: 1rem; }
    .countdown__cell { min-width: 50px; }
    .ticket { height: 150px; }
    .ticket__event { font-size: 1.8rem; }
}
@media (max-width: 640px) {
    .topnav {
        display: grid;
        grid-template-columns: 1fr;
        padding: 0.6rem 1rem;
    }
    .topnav__left, .topnav__right { display: none; }
    .topnav__logo { justify-self: center; }
    .quicklinks {
        position: absolute;
        top: auto;
        bottom: 4rem;
        right: 50%;
        transform: translateX(50%);
        flex-direction: row;
        gap: 1rem;
    }
    .quicklinks__bar { display: none; }
    .quicklinks a { font-size: 0.7rem; }
    .meta { display: none; }
    .hero__date { font-size: 0.65rem; margin-bottom: 1rem; }
    .progress { max-width: 280px; }
    .countdown { gap: 0.7rem; }
    .countdown__dash { display: none; }
    .countdown__cell { min-width: 42px; }
    .countdown__value { font-size: 1.5rem; }
    .countdown__label { font-size: 0.6rem; }
    .bg__divider { 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 — Event Countdown</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=Bebas+Neue&family=Inter:wght@400;500;600;700;800&family=JetBrains+Mono:wght@500;600&display=swap" rel="stylesheet">
    <link rel="stylesheet" href="banner-event-countdown.css">
</head>
<body>
    

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

    <!-- ========== LOADER — CONCERT TICKET ========== -->
    <div class="loader" id="loader">
        <div class="ticket" id="ticket">
            <div class="ticket__half ticket__half--left" id="ticket-left">
                <div class="ticket__top">
                    <span class="ticket__sm">ADMIT ONE</span>
                    <span class="ticket__sm">#00427</span>
                </div>
                <div class="ticket__main">
                    <div class="ticket__date">12·12·26</div>
                    <div class="ticket__event">NIGHT<br>RHYTHM</div>
                </div>
                <div class="ticket__bottom">
                    <div class="ticket__barcode" id="barcode"></div>
                    <span class="ticket__sm">GATE A</span>
                </div>
            </div>
            <div class="ticket__perf"></div>
            <div class="ticket__half ticket__half--right" id="ticket-right">
                <div class="ticket__stub">
                    <span class="ticket__sm">STUB</span>
                    <div class="ticket__stub-no">427</div>
                    <span class="ticket__sm">10 PM</span>
                </div>
            </div>
            <div class="ticket__stamp" id="ticket-stamp">ADMITTED</div>
        </div>
        <div class="loader__meta">
            <span class="loader__label" id="loader-label">SOUND CHECK</span>
            <span class="loader__counter" id="loader-counter">00%</span>
        </div>
    </div>

    <section class="event" id="event">

        <!-- ========== SPLIT BACKGROUND (image + video) ========== -->
        <div class="bg">
            <div class="bg__half bg__half--left" data-bg>
                <img src="https://images.unsplash.com/photo-1514320291840-2e0a9bf2a9ae?auto=format&fit=crop&w=1200&q=75"
                     alt="" draggable="false" onerror="this.style.display='none'">
            </div>
            <div class="bg__half bg__half--right" data-bg>
                <video autoplay loop muted playsinline
                       poster="https://images.unsplash.com/photo-1501281668745-f7f57925c3b4?auto=format&fit=crop&w=1200&q=75">
                    <source src="https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/ForBiggerJoyrides.mp4" type="video/mp4">
                </video>
            </div>
            <div class="bg__overlay"></div>
            <div class="bg__divider" id="divider"></div>
        </div>

        <!-- ========== TOP NAV ========== -->
        <header class="topnav">
            <nav class="topnav__left">
                <a href="#" class="is-active" data-magnetic data-link>HOME</a>
                <a href="#" data-magnetic data-link>EVENTS <span class="caret">&#9662;</span></a>
                <a href="#" data-magnetic data-link>GALLERY</a>
            </nav>
            <a href="#" class="topnav__logo" data-magnetic>
                <span>BRAND</span>
                <span class="topnav__logo-divider">/</span>
                <span>CLUB</span>
            </a>
            <nav class="topnav__right">
                <a href="#" data-magnetic data-link>PRODUCTS</a>
                <a href="#" data-magnetic data-link>BLOG</a>
                <a href="#" data-magnetic data-link>CONTACTS</a>
            </nav>
        </header>

        <!-- ========== MAIN CONTENT ========== -->
        <div class="hero" id="hero">

            <h1 class="hero__title" aria-label="Night Rhythm Live Party">
                <span class="hero__line" data-title-line>NIGHT RHYTHM</span>
                <span class="hero__line" data-title-line>LIVE PARTY</span>
            </h1>

            <div class="hero__date" data-fade>12 DECEMBER 2026 FROM 10PM</div>

            <!-- Progress bar -->
            <div class="progress" data-fade>
                <div class="progress__ticks" id="progress-ticks"></div>
                <div class="progress__fill" id="progress-fill"></div>
                <div class="progress__handle" id="progress-handle"></div>
            </div>

            <!-- Countdown -->
            <div class="countdown" data-fade>
                <span class="countdown__dash">&mdash;</span>
                <div class="countdown__cell">
                    <span class="countdown__value" id="days">000</span>
                    <span class="countdown__label">Days</span>
                </div>
                <div class="countdown__cell">
                    <span class="countdown__value" id="hours">00</span>
                    <span class="countdown__label">Hours</span>
                </div>
                <div class="countdown__cell">
                    <span class="countdown__value" id="mins">00</span>
                    <span class="countdown__label">Mins</span>
                </div>
                <div class="countdown__cell">
                    <span class="countdown__value" id="secs">00</span>
                    <span class="countdown__label">Secs</span>
                </div>
            </div>
        </div>

        <!-- ========== RIGHT-SIDE QUICK LINKS ========== -->
        <nav class="quicklinks" aria-label="Event quick links">
            <a href="#" data-quick>
                <span class="quicklinks__bar"></span>
                <span class="quicklinks__text">DETAILS</span>
            </a>
            <a href="#" data-quick>
                <span class="quicklinks__bar"></span>
                <span class="quicklinks__text">GALLERY</span>
            </a>
            <a href="#" data-quick>
                <span class="quicklinks__bar"></span>
                <span class="quicklinks__text">LOCATION</span>
            </a>
        </nav>

        <!-- ========== BOTTOM META ========== -->
        <div class="meta" data-fade>
            <span class="meta__cell">EST. 2015</span>
            <span class="meta__cell">SEASON 08</span>
            <span class="meta__cell">+40 MINS SET</span>
        </div>
    </section>

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

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

    // -------------------------------------------------------------------
    //  EVENT TARGET DATE
    // -------------------------------------------------------------------
    const TARGET_DATE = new Date('2026-12-12T22:00:00').getTime();
    const BASE_DATE = new Date('2026-04-20T00:00:00').getTime(); // used for progress %

    // -------------------------------------------------------------------
    //  BUILD BARCODE + PROGRESS TICKS
    // -------------------------------------------------------------------
    const barcode = document.getElementById('barcode');
    for (let i = 0; i < 45; i++) {
        const b = document.createElement('span');
        b.style.flexGrow = String(0.4 + Math.random() * 1.4);
        barcode.appendChild(b);
    }

    const progressTicks = document.getElementById('progress-ticks');
    for (let i = 0; i < 40; i++) progressTicks.appendChild(document.createElement('span'));

    // -------------------------------------------------------------------
    //  SPLIT TITLE 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 = 'hero__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 ticket = document.getElementById('ticket');
    const ticketLeft = document.getElementById('ticket-left');
    const ticketRight = document.getElementById('ticket-right');
    const stamp = document.getElementById('ticket-stamp');
    const loaderCounter = document.getElementById('loader-counter');
    const loaderLabel = document.getElementById('loader-label');
    const barcodeBars = barcode.querySelectorAll('span');

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

    const bgHalves = root.querySelectorAll('[data-bg]');
    const divider = document.getElementById('divider');
    const magnetics = root.querySelectorAll('[data-magnetic]');
    const titleChars = root.querySelectorAll('.hero__char');
    const fades = root.querySelectorAll('[data-fade]');
    const quickLinks = root.querySelectorAll('[data-quick]');

    const daysEl = document.getElementById('days');
    const hoursEl = document.getElementById('hours');
    const minsEl = document.getElementById('mins');
    const secsEl = document.getElementById('secs');

    const progressFill = document.getElementById('progress-fill');
    const progressHandle = document.getElementById('progress-handle');

    // -------------------------------------------------------------------
    //  INITIAL STATES
    // -------------------------------------------------------------------
    gsap.set(bgHalves[0], { xPercent: -100 });
    gsap.set(bgHalves[1], { xPercent: 100 });
    gsap.set(divider, { scaleY: 0, opacity: 0, transformOrigin: 'center' });
    gsap.set(magnetics, { y: -20, opacity: 0 });
    gsap.set(titleChars, { yPercent: 110, opacity: 0 });
    gsap.set(fades, { y: 20, opacity: 0 });
    gsap.set(quickLinks, { x: 40, opacity: 0 });
    gsap.set(progressFill, { width: '0%' });
    gsap.set(progressHandle, { left: '0%' });

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

    // Ticket entrance (slides in + slight rotate)
    loaderTl.from(ticket, {
        y: 40, rotation: -8, opacity: 0, scale: 0.9,
        duration: 0.7, ease: 'power3.out',
    }, 0);

    // Barcode bars scale in sequentially
    loaderTl.from(barcodeBars, {
        scaleY: 0, duration: 0.5,
        stagger: 0.015, ease: 'power2.out',
        transformOrigin: 'bottom',
    }, 0.3);

    // Counter + progress
    const p = { v: 0 };
    loaderTl.to(p, {
        v: 100,
        duration: 2.2,
        ease: 'power1.inOut',
        onUpdate: () => {
            loaderCounter.textContent = Math.floor(p.v) + '%';
            if (p.v > 25 && loaderLabel.textContent === 'SOUND CHECK') loaderLabel.textContent = 'LIGHTS UP';
            if (p.v > 60 && loaderLabel.textContent === 'LIGHTS UP')    loaderLabel.textContent = 'DROP THE BEAT';
            if (p.v > 90 && loaderLabel.textContent === 'DROP THE BEAT')loaderLabel.textContent = 'READY';
        },
    }, 0.1);

    // Stamp lands (scale from big + rotate)
    loaderTl.to(stamp, {
        scale: 1,
        opacity: 1,
        duration: 0.4,
        ease: 'back.out(2.5)',
    }, '-=0.3');
    loaderTl.to(ticket, {
        scale: 1.03, duration: 0.08, yoyo: true, repeat: 1, ease: 'sine.inOut',
    }, '<');

    // Tear: left slides left, right slides right
    loaderTl.to(ticketLeft, {
        x: '-=60', rotation: -5, duration: 0.7, ease: 'power3.in',
    }, '+=0.3');
    loaderTl.to(ticketRight, {
        x: '+=60', rotation: 5, duration: 0.7, ease: 'power3.in',
    }, '<');

    // Everything fades + scales down
    loaderTl.to(ticket, {
        y: -40, scale: 0.92, opacity: 0, duration: 0.6, ease: 'power3.in',
    }, '-=0.2');
    loaderTl.to([loaderCounter, loaderLabel], {
        y: 10, opacity: 0, duration: 0.3, stagger: 0.05,
    }, '<');
    loaderTl.to(loader, {
        opacity: 0, duration: 0.4, ease: 'power2.inOut',
    }, '-=0.2');
    loaderTl.set(loader, { display: 'none' });

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

        // Background halves slide in from sides (like a ticket tear reverse)
        tl.to(bgHalves[0], { xPercent: 0, opacity: 1, duration: 1.2 }, 0);
        tl.to(bgHalves[1], { xPercent: 0, opacity: 1, duration: 1.2 }, 0);

        // Divider grows
        tl.to(divider, {
            scaleY: 1, opacity: 1,
            duration: 1,
            ease: 'power4.inOut',
        }, 0.4);

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

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

        // Fades
        tl.to(fades, {
            y: 0, opacity: 1,
            duration: 0.7,
            stagger: 0.12,
            ease: 'power3.out',
        }, 1);

        // Quick links
        tl.to(quickLinks, {
            x: 0, opacity: 1,
            duration: 0.7,
            stagger: 0.1,
            ease: 'power3.out',
        }, 1);

        // Progress bar fills to computed %
        const pct = computeProgressPercent();
        tl.to(progressFill, {
            width: pct + '%',
            duration: 1.4,
            ease: 'power2.out',
        }, 1.2);
        tl.to(progressHandle, {
            left: pct + '%',
            duration: 1.4,
            ease: 'power2.out',
        }, 1.2);

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

    // -------------------------------------------------------------------
    //  COUNTDOWN
    // -------------------------------------------------------------------
    function updateCountdown() {
        const now = Date.now();
        const diff = Math.max(0, TARGET_DATE - now);
        const days = Math.floor(diff / 86400000);
        const hours = Math.floor((diff / 3600000) % 24);
        const mins = Math.floor((diff / 60000) % 60);
        const secs = Math.floor((diff / 1000) % 60);
        daysEl.textContent = String(days).padStart(3, '0');
        hoursEl.textContent = String(hours).padStart(2, '0');
        minsEl.textContent = String(mins).padStart(2, '0');
        secsEl.textContent = String(secs).padStart(2, '0');
    }

    function computeProgressPercent() {
        // Rough: progress from base date to target
        const total = TARGET_DATE - BASE_DATE;
        const elapsed = Date.now() - BASE_DATE;
        if (total <= 0) return 100;
        const pct = Math.max(0, Math.min(100, (elapsed / total) * 100));
        return pct;
    }

    function startLoops() {
        updateCountdown();
        setInterval(updateCountdown, 1000);
    }

    // -------------------------------------------------------------------
    //  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], [data-quick], .countdown__cell');
        hovers.forEach((el) => {
            el.addEventListener('mouseenter', () => cursorRing.classList.add('is-hover'));
            el.addEventListener('mouseleave', () => cursorRing.classList.remove('is-hover'));
        });

        // ---- Magnetic ----
        magnetics.forEach((el) => {
            const s = el.classList.contains('topnav__logo') ? 0.18 : 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) * s,
                    y: (e.clientY - cy) * s,
                    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)' });
            });
        });

        // ---- Title char proximity + individual hover ----
        const hero = document.getElementById('hero');
        let tmx = -9999, tmy = -9999;
        hero.addEventListener('mousemove', (e) => { tmx = e.clientX; tmy = e.clientY; });
        hero.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 < 150) gsap.set(c, { y: -(1 - dist / 150) * 18 });
                else gsap.set(c, { y: 0 });
            });
        });
        titleChars.forEach((c) => {
            c.addEventListener('mouseenter', () => gsap.to(c, { color: '#d14a7a', duration: 0.2 }));
            c.addEventListener('mouseleave', () => gsap.to(c, { color: '#ffffff', duration: 0.4 }));
        });

        // ---- Countdown cells: scramble on hover ----
        const cells = [
            { el: daysEl,  valueFn: () => daysEl.textContent, len: 3 },
            { el: hoursEl, valueFn: () => hoursEl.textContent, len: 2 },
            { el: minsEl,  valueFn: () => minsEl.textContent, len: 2 },
            { el: secsEl,  valueFn: () => secsEl.textContent, len: 2 },
        ];
        cells.forEach(({ el, valueFn, len }) => {
            const cellParent = el.closest('.countdown__cell');
            cellParent.addEventListener('mouseenter', () => {
                const final = valueFn();
                let t = 0;
                const scramble = () => {
                    t++;
                    if (t >= 8) {
                        el.textContent = final;
                        gsap.to(el, { scale: 1.18, duration: 0.15, yoyo: true, repeat: 1, ease: 'sine.inOut' });
                        return;
                    }
                    el.textContent = Array.from({ length: len }, () => Math.floor(Math.random() * 10)).join('');
                    setTimeout(scramble, 35);
                };
                scramble();
            });
        });

        // ---- Quicklinks on hover: reveal sweeping bar ----
        quickLinks.forEach((q) => {
            const bar = q.querySelector('.quicklinks__bar');
            q.addEventListener('mouseenter', () => {
                gsap.fromTo(bar,
                    { width: 40, background: '#ffffff' },
                    { width: 70, background: '#d14a7a', duration: 0.4, ease: 'power3.out' }
                );
            });
            q.addEventListener('mouseleave', () => {
                gsap.to(bar, { width: 40, background: '#f5ecd8', duration: 0.5, ease: 'power3.out' });
            });
        });

        // ---- Background image/video parallax (opposite directions) ----
        let bx = 0, by = 0, bxC = 0, byC = 0;
        root.addEventListener('mousemove', (e) => {
            const r = root.getBoundingClientRect();
            bx = ((e.clientX - r.left) / r.width - 0.5) * 2;
            by = ((e.clientY - r.top) / r.height - 0.5) * 2;
        });
        root.addEventListener('mouseleave', () => { bx = 0; by = 0; });

        gsap.ticker.add(() => {
            bxC += (bx - bxC) * 0.05;
            byC += (by - byC) * 0.05;
            gsap.set(bgHalves[0], { x: -bxC * 20, y: -byC * 10 });
            gsap.set(bgHalves[1], { x: bxC * 20, y: -byC * 10 });
            gsap.set(divider, { x: bxC * 6 });
        });

        // ---- Center handle pulses continuously ----
        gsap.to(progressHandle, {
            scale: 1.25,
            duration: 0.8,
            yoyo: true,
            repeat: -1,
            ease: 'sine.inOut',
            transformOrigin: 'center center',
        });

        // ---- Divider subtle shimmer ----
        const divHighlight = divider;
        gsap.to(divHighlight, {
            opacity: 0.55,
            duration: 2.4,
            yoyo: true,
            repeat: -1,
            ease: 'sine.inOut',
        });
    }
})();

More GSAP landing pages