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

Free Cinematic Landscape Hero Template with Text Reveal

Free minimal cinematic landing page template from ToolsWaves with a staggered character text reveal animation over a wide landscape photograph and mouse-driven image parallax. Free for any project.

Text RevealParallaxCinematicMinimal

About this ToolsWaves template

This free cinematic landing page template from ToolsWaves is minimal but impactful. It uses a staggered character text reveal animation over a wide landscape photograph, and as the mouse moves, the image shifts subtly with parallax depth โ€” creating a sense of immersion and space without using video. The simplicity makes it adaptable across very different verticals.

Use this template for travel platforms, outdoor brands, photography portfolios, luxury real estate listings, hospitality sites, or any brand whose value proposition is location, scenery, or expansive experience. ToolsWaves provides this template completely free โ€” copy the three template files into your project, swap the landscape photograph for your own imagery, and update the headline copy. The intentional restraint of this template means it works across nearly any brand voice with minimal customization required.

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 Cinematic Landscape Hero Template with Text Reveal</title>
  <meta name="description" content="Free GSAP cinematic landing page hero from ToolsWaves" />
  <meta name="generator" content="ToolsWaves - https://toolswaves.in/landing-pages" />

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

  <!-- Twitter -->
  <meta name="twitter:card" content="summary_large_image" />
  <meta name="twitter:title" content="Free Cinematic Landscape Hero Template with Text Reveal" />
  <meta name="twitter:description" content="Free GSAP cinematic landing page hero from ToolsWaves" />
  <meta name="twitter:image" content="https://toolswaves.in/og?title=Free%20Cinematic%20Landscape%20Hero%20Template%20with%20Text%20Reveal&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; }

html, body {
    font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
    background: #0a0a0a;
    color: #fff;
    overflow-x: hidden;
}

.back-link {
    position: fixed;
    top: 1.5rem;
    left: 1.5rem;
    z-index: 10;
    color: #fff;
    text-decoration: none;
    font-size: 0.875rem;
    padding: 0.5rem 1rem;
    background: rgba(0,0,0,0.4);
    border: 1px solid rgba(255,255,255,0.15);
    border-radius: 999px;
    backdrop-filter: blur(8px);
    transition: background 0.3s ease;
}
.back-link:hover { background: rgba(0,0,0,0.7); }

.banner {
    position: relative;
    width: 100%;
    height: 100vh;
    overflow: hidden;
    display: flex;
    align-items: center;
    justify-content: center;
}

.banner__bg {
    position: absolute;
    inset: -8% -8% -8% -8%;
    will-change: transform;
}
.banner__bg img {
    width: 100%;
    height: 100%;
    object-fit: cover;
    user-select: none;
    transform: scale(1.05);
}

.banner__overlay {
    position: absolute;
    inset: 0;
    background:
        linear-gradient(180deg, rgba(10,10,10,0.4) 0%, rgba(10,10,10,0.1) 40%, rgba(10,10,10,0.85) 100%),
        radial-gradient(ellipse at center, transparent 40%, rgba(10,10,10,0.6) 100%);
}

.banner__content {
    position: relative;
    z-index: 2;
    text-align: center;
    max-width: 900px;
    padding: 0 2rem;
}

.banner__eyebrow {
    display: inline-block;
    font-size: 0.75rem;
    letter-spacing: 0.4em;
    text-transform: uppercase;
    color: rgba(255,255,255,0.7);
    margin-bottom: 1.5rem;
    padding: 0.4rem 1rem;
    border: 1px solid rgba(255,255,255,0.2);
    border-radius: 999px;
}

.banner__title {
    font-size: clamp(2.5rem, 8vw, 6rem);
    font-weight: 800;
    line-height: 1;
    letter-spacing: -0.03em;
    margin-bottom: 1.5rem;
}
.banner__title .line {
    display: block;
    overflow: hidden;
}
.banner__title .char {
    display: inline-block;
    transform: translateY(110%);
    will-change: transform;
}

.banner__subtitle {
    font-size: clamp(1rem, 1.5vw, 1.25rem);
    color: rgba(255,255,255,0.75);
    max-width: 560px;
    margin: 0 auto 2.5rem;
    line-height: 1.5;
}

.banner__cta {
    display: flex;
    gap: 1rem;
    justify-content: center;
    flex-wrap: wrap;
}

.btn {
    display: inline-block;
    padding: 0.9rem 1.8rem;
    border-radius: 999px;
    text-decoration: none;
    font-size: 0.95rem;
    font-weight: 500;
    letter-spacing: 0.02em;
    transition: transform 0.3s ease, background 0.3s ease, color 0.3s ease;
}
.btn:hover { transform: translateY(-2px); }

.btn--primary {
    background: #fff;
    color: #0a0a0a;
}
.btn--primary:hover { background: #f0f0f0; }

.btn--ghost {
    color: #fff;
    border: 1px solid rgba(255,255,255,0.3);
}
.btn--ghost:hover { background: rgba(255,255,255,0.1); }

.banner__scroll {
    position: absolute;
    bottom: 2rem;
    left: 50%;
    transform: translateX(-50%);
    z-index: 2;
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 0.75rem;
    color: rgba(255,255,255,0.6);
    font-size: 0.7rem;
    letter-spacing: 0.3em;
    text-transform: uppercase;
}
.banner__scroll-line {
    width: 1px;
    height: 40px;
    background: linear-gradient(180deg, rgba(255,255,255,0.6), transparent);
    animation: scrollLine 2s ease-in-out infinite;
}
@keyframes scrollLine {
    0%, 100% { transform: scaleY(1); opacity: 0.6; }
    50% { transform: scaleY(0.5); opacity: 1; transform-origin: top; }
}

[data-reveal] {
    opacity: 0;
    transform: translateY(20px);
}

  </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 โ€” GSAP Animations</title>
    <link rel="stylesheet" href="banner.css">
</head>
<body>
    

    <section class="banner" id="banner">
        <div class="banner__bg" data-parallax="0.08">
            <img src="https://images.unsplash.com/photo-1470770841072-f978cf4d019e?w=1600&q=80"
                 alt="Mountain landscape"
                 draggable="false">
        </div>
        <div class="banner__overlay"></div>

        <div class="banner__content">
            <span class="banner__eyebrow" data-reveal>Discover</span>

            <h1 class="banner__title">
                <span class="line" data-split>Escape the ordinary</span>
                <span class="line" data-split>find your horizon</span>
            </h1>

            <p class="banner__subtitle" data-reveal>
                Crafted journeys through landscapes that shift perspective and reshape memory.
            </p>

            <div class="banner__cta" data-reveal>
                <a href="#" class="btn btn--primary">Start exploring</a>
                <a href="#" class="btn btn--ghost">Watch film</a>
            </div>
        </div>

        <div class="banner__scroll" data-reveal>
            <span>Scroll</span>
            <div class="banner__scroll-line"></div>
        </div>
    </section>

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

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

    // Split text into characters wrapped in spans for staggered reveal.
    const splitTargets = banner.querySelectorAll('[data-split]');
    splitTargets.forEach((el) => {
        const text = el.textContent;
        el.textContent = '';
        [...text].forEach((ch) => {
            const span = document.createElement('span');
            span.className = 'char';
            span.textContent = ch === ' ' ? '\u00A0' : ch;
            el.appendChild(span);
        });
    });

    const chars = banner.querySelectorAll('.char');
    const revealEls = banner.querySelectorAll('[data-reveal]');
    const bg = banner.querySelector('.banner__bg');

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

    tl.from(bg, {
        scale: 1.15,
        duration: 1.8,
        ease: 'power2.out',
    }, 0);

    tl.to(chars, {
        y: '0%',
        duration: 1,
        stagger: 0.025,
        ease: 'power4.out',
    }, 0.2);

    tl.to(revealEls, {
        opacity: 1,
        y: 0,
        duration: 0.9,
        stagger: 0.12,
        ease: 'power2.out',
    }, 0.6);

    // Mouse-driven parallax on background image.
    const parallaxStrength = parseFloat(bg.dataset.parallax || '0.08');
    let mouseX = 0, mouseY = 0;
    let currentX = 0, currentY = 0;

    const onMove = (e) => {
        const rect = banner.getBoundingClientRect();
        // Normalise to -1..1 around the center.
        mouseX = ((e.clientX - rect.left) / rect.width - 0.5) * 2;
        mouseY = ((e.clientY - rect.top) / rect.height - 0.5) * 2;
    };

    banner.addEventListener('mousemove', onMove);
    banner.addEventListener('mouseleave', () => { mouseX = 0; mouseY = 0; });

    // Smooth interpolation loop for parallax โ€” runs with GSAP's ticker.
    gsap.ticker.add(() => {
        currentX += (mouseX - currentX) * 0.08;
        currentY += (mouseY - currentY) * 0.08;
        gsap.set(bg, {
            x: -currentX * parallaxStrength * 60,
            y: -currentY * parallaxStrength * 60,
        });
    });

    // Subtle parallax on content layer (opposite direction, smaller).
    const content = banner.querySelector('.banner__content');
    gsap.ticker.add(() => {
        gsap.set(content, {
            x: currentX * 10,
            y: currentY * 10,
        });
    });
})();

More GSAP landing pages