Copy Code<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Free Creative Agency Hero Template with Scramble-Text Loader</title>
<meta name="description" content="Free GSAP creative agency landing page hero from ToolsWaves" />
<meta name="generator" content="ToolsWaves - https://toolswaves.in/landing-pages" />
<!-- Open Graph -->
<meta property="og:title" content="Free Creative Agency Hero Template with Scramble-Text Loader" />
<meta property="og:description" content="Free GSAP creative agency landing page hero from ToolsWaves" />
<meta property="og:type" content="website" />
<meta property="og:image" content="https://toolswaves.in/og?title=Free%20Creative%20Agency%20Hero%20Template%20with%20Scramble-Text%20Loader&category=Landing%20Page&icon=%F0%9F%93%84" />
<!-- Twitter -->
<meta name="twitter:card" content="summary_large_image" />
<meta name="twitter:title" content="Free Creative Agency Hero Template with Scramble-Text Loader" />
<meta name="twitter:description" content="Free GSAP creative agency landing page hero from ToolsWaves" />
<meta name="twitter:image" content="https://toolswaves.in/og?title=Free%20Creative%20Agency%20Hero%20Template%20with%20Scramble-Text%20Loader&category=Landing%20Page&icon=%F0%9F%93%84" />
<!-- Bootstrap (used by template โ replace with your own framework if you prefer) -->
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" />
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
:root {
--bg: #06080b;
--ink: #ffffff;
--muted: rgba(255, 255, 255, 0.55);
--dim: rgba(255, 255, 255, 0.12);
--line: rgba(255, 255, 255, 0.08);
}
html, body {
font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
background: var(--bg);
color: var(--ink);
overflow: hidden;
cursor: none;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
@media (hover: none), (pointer: coarse) { html, body { cursor: auto; } }
.back-link {
position: fixed;
bottom: 0.75rem;
left: 0.75rem;
z-index: 1000;
color: var(--ink);
text-decoration: none;
font-size: 0.65rem;
font-weight: 600;
letter-spacing: 0.12em;
padding: 0.45rem 0.9rem;
background: rgba(255, 255, 255, 0.06);
border: 1px solid rgba(255, 255, 255, 0.15);
border-radius: 999px;
backdrop-filter: blur(8px);
cursor: none;
opacity: 0.85;
transition: opacity 0.3s, transform 0.3s;
}
.back-link:hover { opacity: 1; transform: translateY(-2px); }
/* =========================================================
CUSTOM CURSOR
========================================================= */
.cursor-dot, .cursor-ring {
position: fixed;
top: 0; left: 0;
pointer-events: none;
z-index: 10000;
transform: translate(-50%, -50%);
mix-blend-mode: difference;
will-change: transform;
}
.cursor-dot {
width: 5px; height: 5px;
background: #ffffff;
border-radius: 50%;
}
.cursor-ring {
width: 32px; height: 32px;
border: 1px solid rgba(255, 255, 255, 0.5);
border-radius: 50%;
transition: width 0.3s, height 0.3s, border-color 0.3s;
}
.cursor-ring.is-hover {
width: 60px; height: 60px;
border-color: #fff;
}
@media (hover: none), (pointer: coarse) { .cursor-dot, .cursor-ring { display: none; } }
/* =========================================================
LOADER โ Scramble text + grid draw
========================================================= */
.loader {
position: fixed;
inset: 0;
z-index: 999;
background: var(--bg);
display: flex;
align-items: center;
justify-content: center;
}
.loader__grid {
position: absolute;
inset: 0;
background-image: repeating-linear-gradient(
90deg,
transparent 0,
transparent calc(100% / 16 - 1px),
rgba(255, 255, 255, 0.08) calc(100% / 16 - 1px),
rgba(255, 255, 255, 0.08) calc(100% / 16)
);
opacity: 0;
}
.loader__content {
position: relative;
z-index: 2;
text-align: center;
display: flex;
flex-direction: column;
align-items: center;
gap: 1.5rem;
}
.loader__diamond {
width: 48px;
height: 48px;
will-change: transform;
}
.loader__diamond svg { width: 100%; height: 100%; }
.loader__title {
font-family: 'Inter', sans-serif;
font-weight: 800;
font-size: clamp(3rem, 10vw, 8rem);
line-height: 1;
letter-spacing: -0.04em;
color: var(--ink);
display: inline-flex;
gap: 2px;
}
.loader__title span {
display: inline-block;
font-variant-numeric: tabular-nums;
will-change: transform;
}
.loader__meta {
display: flex;
gap: 1.25rem;
align-items: center;
font-family: 'JetBrains Mono', monospace;
font-size: 0.7rem;
letter-spacing: 0.3em;
color: var(--muted);
text-transform: uppercase;
}
.loader__counter {
color: var(--ink);
font-weight: 600;
font-variant-numeric: tabular-nums;
min-width: 3ch;
}
/* =========================================================
EVOLVE SECTION
========================================================= */
.evolve {
position: relative;
width: 100%;
height: 100vh;
min-height: 720px;
overflow: hidden;
background: var(--bg);
}
.evolve__video {
position: absolute;
inset: 0;
width: 100%; height: 100%;
object-fit: cover;
z-index: 1;
filter: brightness(0.4) contrast(1.1) saturate(0.9);
will-change: transform;
}
.evolve__overlay {
position: absolute;
inset: 0;
z-index: 2;
background:
radial-gradient(ellipse at center, rgba(6, 8, 11, 0.3) 0%, rgba(6, 8, 11, 0.85) 100%),
linear-gradient(180deg, rgba(6, 8, 11, 0.7) 0%, rgba(6, 8, 11, 0.1) 30%, rgba(6, 8, 11, 0.5) 70%, rgba(6, 8, 11, 0.9) 100%);
pointer-events: none;
}
.evolve__grid {
position: absolute;
inset: 0;
z-index: 3;
background-image: repeating-linear-gradient(
90deg,
transparent 0,
transparent calc(100% / 16 - 1px),
rgba(255, 255, 255, 0.05) calc(100% / 16 - 1px),
rgba(255, 255, 255, 0.05) calc(100% / 16)
);
pointer-events: none;
}
/* ---------- Top nav ---------- */
.topnav {
position: relative;
z-index: 20;
display: grid;
grid-template-columns: 1fr 2fr 2fr auto;
gap: 2rem;
padding: 1.25rem 2rem;
align-items: flex-start;
}
.topnav__logo {
display: inline-flex;
align-items: center;
gap: 0.4rem;
text-decoration: none;
color: var(--ink);
font-weight: 500;
font-size: 1.15rem;
letter-spacing: -0.01em;
cursor: none;
will-change: transform;
}
.topnav__logo-mark { width: 18px; height: 18px; }
.topnav__logo-mark svg { width: 100%; height: 100%; display: block; }
.topnav__desc {
font-size: 0.75rem;
font-weight: 500;
color: var(--ink);
line-height: 1.45;
}
.topnav__contact {
display: flex;
flex-direction: column;
align-items: flex-start;
gap: 0.1rem;
font-size: 0.75rem;
font-weight: 500;
line-height: 1.45;
}
.topnav__email {
color: var(--ink);
text-decoration: none;
position: relative;
cursor: none;
will-change: transform;
}
.topnav__email::after {
content: '';
position: absolute;
left: 0; bottom: -2px;
width: 100%; height: 1px;
background: currentColor;
transform: scaleX(0);
transform-origin: right;
transition: transform 0.4s cubic-bezier(0.65, 0, 0.35, 1);
}
.topnav__email:hover::after { transform: scaleX(1); transform-origin: left; }
.topnav__loc { color: var(--ink); }
.topnav__actions {
display: flex;
gap: 0.6rem;
align-items: center;
}
.topnav__lang {
background: transparent;
border: none;
color: var(--ink);
font-weight: 600;
font-size: 0.85rem;
letter-spacing: 0.1em;
cursor: none;
padding: 0.3rem 0.6rem;
will-change: transform;
font-family: inherit;
}
.topnav__menu {
display: inline-flex;
align-items: center;
gap: 0.4rem;
background: var(--ink);
color: #000;
border: none;
padding: 0.5rem 1.1rem;
font-size: 0.85rem;
font-weight: 600;
font-family: inherit;
letter-spacing: 0.05em;
cursor: none;
will-change: transform;
transition: background 0.3s ease, color 0.3s ease;
}
.topnav__menu svg { width: 11px; height: 11px; transition: transform 0.3s; }
.topnav__menu:hover { background: #dcdcdc; }
.topnav__menu:hover svg { transform: rotate(180deg); }
/* ---------- Side labels ---------- */
.side-label {
position: absolute;
top: 50%;
transform: translateY(-50%);
z-index: 15;
display: flex;
align-items: center;
gap: 0.75rem;
font-size: 0.8rem;
font-weight: 500;
color: var(--ink);
pointer-events: auto;
will-change: transform;
}
.side-label--left { left: 2rem; }
.side-label--right { right: 2rem; }
.side-label__bar {
display: inline-block;
width: 40px;
height: 1px;
background: var(--ink);
transition: width 0.5s cubic-bezier(0.65, 0, 0.35, 1);
}
.side-label:hover .side-label__bar { width: 80px; }
/* ---------- Center caption ---------- */
.center-caption {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
z-index: 9;
display: inline-flex;
align-items: center;
gap: 0.5rem;
font-size: 0.75rem;
font-weight: 600;
letter-spacing: 0.05em;
color: var(--ink);
padding: 0.4rem 1rem;
background: rgba(255, 255, 255, 0.06);
backdrop-filter: blur(8px);
border: 1px solid rgba(255, 255, 255, 0.12);
border-radius: 999px;
will-change: transform;
cursor: none;
}
.center-caption__dot {
width: 5px; height: 5px;
border-radius: 50%;
background: #ffffff;
}
/* ---------- Huge title ---------- */
.title {
position: absolute;
left: 0; right: 0;
bottom: 0;
z-index: 10;
font-family: 'Inter', sans-serif;
font-weight: 800;
font-size: clamp(3.5rem, 13vw, 16rem);
line-height: 0.9;
letter-spacing: -0.04em;
color: #ffffff;
text-align: center;
padding: 0 1rem;
pointer-events: none;
}
.title__line {
display: block;
overflow: hidden;
padding-bottom: 0.04em;
white-space: nowrap;
pointer-events: auto;
}
.title__char {
display: inline-block;
will-change: transform, color;
transition: color 0.3s ease;
}
.title__char.is-space { width: 0.2em; }
/* ---------- Footer meta ---------- */
.footer-meta {
position: absolute;
bottom: 1rem;
left: 0; right: 0;
z-index: 15;
display: flex;
justify-content: space-between;
padding: 0 2rem;
font-family: 'JetBrains Mono', monospace;
font-size: 0.7rem;
letter-spacing: 0.15em;
color: var(--muted);
text-transform: uppercase;
pointer-events: none;
}
.footer-meta__cell { display: inline-flex; align-items: center; gap: 0.4rem; }
.footer-meta__cell b { color: var(--ink); font-weight: 500; }
/* ---------- Initial hidden states ---------- */
[data-magnetic], [data-link], [data-fade], [data-title-line] .title__char,
[data-side], [data-caption], [data-footer] { opacity: 0; }
/* ---------- Responsive ---------- */
@media (max-width: 1024px) {
.topnav { grid-template-columns: 1fr auto; gap: 1rem; }
.topnav__desc, .topnav__contact { display: none; }
.side-label { display: none; }
}
@media (max-width: 768px) {
.topnav { padding: 1rem 1.25rem; }
.topnav__logo-text { font-size: 1rem; }
.center-caption { font-size: 0.65rem; padding: 0.3rem 0.75rem; }
.footer-meta { font-size: 0.6rem; padding: 0 1rem; gap: 0.5rem; flex-wrap: wrap; justify-content: center; }
.title { bottom: 3rem; }
.loader__title { font-size: clamp(2.5rem, 14vw, 6rem); }
}
@media (max-width: 480px) {
.topnav__lang { padding: 0.3rem 0.4rem; font-size: 0.75rem; }
.topnav__menu { padding: 0.4rem 0.8rem; font-size: 0.75rem; }
.center-caption { display: none; }
.footer-meta__cell:last-child { display: none; }
}
</style>
</head>
<body>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Banner โ Evolve</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800;900&family=JetBrains+Mono:wght@400;500;600&display=swap" rel="stylesheet">
<link rel="stylesheet" href="banner-evolve.css">
</head>
<body>
<!-- Custom cursor -->
<div class="cursor-dot" id="cursor-dot"></div>
<div class="cursor-ring" id="cursor-ring"></div>
<!-- ========== LOADER โ Scramble text + grid ========== -->
<div class="loader" id="loader">
<div class="loader__grid" id="loader-grid"></div>
<div class="loader__content">
<div class="loader__diamond" id="loader-diamond">
<svg viewBox="0 0 40 40" aria-hidden="true">
<path d="M20 4 L36 20 L20 36 L4 20 Z" fill="none" stroke="#ffffff" stroke-width="1.5"/>
<path d="M20 12 L28 20 L20 28 L12 20 Z" fill="#ffffff"/>
</svg>
</div>
<div class="loader__title" id="loader-title" aria-label="evolve">
<span data-char="e">e</span><span data-char="v">v</span><span data-char="o">o</span><span data-char="l">l</span><span data-char="v">v</span><span data-char="e">e</span>
</div>
<div class="loader__meta">
<span class="loader__label" id="loader-label">LOADING</span>
<span class="loader__counter" id="loader-counter">000</span>
</div>
</div>
</div>
<section class="evolve" id="evolve">
<!-- Background media -->
<video class="evolve__video" id="bg-video" autoplay loop muted playsinline
poster="https://images.unsplash.com/photo-1496346701500-f1adcc34e09e?auto=format&fit=crop&w=1600&q=80">
<source src="https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/TearsOfSteel.mp4" type="video/mp4">
</video>
<div class="evolve__overlay"></div>
<!-- Grid lines overlay -->
<div class="evolve__grid" id="grid"></div>
<!-- ========== TOP NAV ========== -->
<header class="topnav">
<a href="#" class="topnav__logo" data-magnetic>
<span class="topnav__logo-mark" id="nav-diamond">
<svg viewBox="0 0 24 24" aria-hidden="true">
<path d="M12 2 L22 12 L12 22 L2 12 Z" fill="none" stroke="#ffffff" stroke-width="1.5"/>
<path d="M12 6 L18 12 L12 18 L6 12 Z" fill="#ffffff"/>
</svg>
</span>
<span class="topnav__logo-text">evolve</span>
</a>
<div class="topnav__desc" data-fade>
Ecommerce and brand systems.<br>
Driven by visions. Built with design<br>
and technology.
</div>
<div class="topnav__contact" data-fade>
<a href="mailto:hello@evolvebrand.com" data-magnetic class="topnav__email">hello@evolvebrand.com</a>
<span class="topnav__loc">Berlin, EST 2008©</span>
</div>
<div class="topnav__actions">
<button class="topnav__lang" data-magnetic id="lang-btn">
<span id="lang-current">EN</span>
</button>
<button class="topnav__menu" data-magnetic id="menu-btn">
<span>Menu</span>
<svg viewBox="0 0 12 12" fill="none" stroke="currentColor" stroke-width="1.5">
<path d="M3 4l3 3 3-3" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
</button>
</div>
</header>
<!-- ========== SIDE LABELS ========== -->
<aside class="side-label side-label--left" data-side>
<span class="side-label__bar"></span>
<span>Brand Direction</span>
</aside>
<aside class="side-label side-label--right" data-side>
<span>Advanced Tech</span>
<span class="side-label__bar"></span>
</aside>
<!-- Center caption over image -->
<span class="center-caption" data-caption>
<span class="center-caption__dot"></span>
Performance Marketing
</span>
<!-- ========== HUGE TITLE ========== -->
<h1 class="title" id="title" aria-label="Ecommerce Innovation Agency">
<span class="title__line" data-title-line>ECOMMERCE</span>
<span class="title__line" data-title-line>INNOVATION</span>
<span class="title__line" data-title-line>AGENCY</span>
</h1>
<!-- Bottom meta info -->
<div class="footer-meta" data-footer>
<span class="footer-meta__cell"><b>©</b> 2026 — all rights</span>
<span class="footer-meta__cell"><b>●</b> available for work</span>
<span class="footer-meta__cell">scroll <b>↓</b></span>
</div>
</section>
<script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.12.5/gsap.min.js"></script>
<script src="banner-evolve.js"></script>
</body>
</html>
</body>
</html>JS banner-evolve.js โ GSAP animation timeline
(() => {
const root = document.getElementById('evolve');
if (!root) return;
// -------------------------------------------------------------------
// SPLIT TITLE LINES INTO CHARS
// -------------------------------------------------------------------
const titleLines = root.querySelectorAll('[data-title-line]');
titleLines.forEach((line) => {
const text = line.textContent;
line.textContent = '';
[...text].forEach((ch) => {
const span = document.createElement('span');
span.className = 'title__char';
if (ch === ' ') { span.classList.add('is-space'); span.innerHTML = ' '; }
else span.textContent = ch;
line.appendChild(span);
});
});
// -------------------------------------------------------------------
// REFERENCES
// -------------------------------------------------------------------
const loader = document.getElementById('loader');
const loaderGrid = document.getElementById('loader-grid');
const loaderCounter = document.getElementById('loader-counter');
const loaderLabel = document.getElementById('loader-label');
const loaderDiamond = document.getElementById('loader-diamond');
const loaderTitleChars = document.querySelectorAll('#loader-title span');
const cursorDot = document.getElementById('cursor-dot');
const cursorRing = document.getElementById('cursor-ring');
const video = document.getElementById('bg-video');
const magnetics = root.querySelectorAll('[data-magnetic]');
const fades = root.querySelectorAll('[data-fade]');
const titleChars = root.querySelectorAll('.title__char');
const sides = root.querySelectorAll('[data-side]');
const caption = root.querySelector('[data-caption]');
const footer = root.querySelector('[data-footer]');
const navDiamond = document.getElementById('nav-diamond');
const langBtn = document.getElementById('lang-btn');
const langCurrent = document.getElementById('lang-current');
// -------------------------------------------------------------------
// INITIAL STATES
// -------------------------------------------------------------------
gsap.set(video, { scale: 1.15, opacity: 0 });
gsap.set(magnetics, { y: -15, opacity: 0 });
gsap.set(fades, { y: 10, opacity: 0 });
gsap.set(sides, { opacity: 0 });
gsap.set(caption, { scale: 0, opacity: 0 });
gsap.set(titleChars, { yPercent: 110, opacity: 0 });
gsap.set(footer, { y: 10, opacity: 0 });
// -------------------------------------------------------------------
// LOADER TIMELINE
// -------------------------------------------------------------------
const loaderTl = gsap.timeline({ onComplete: playScene });
// Grid fades in
loaderTl.to(loaderGrid, {
opacity: 1,
duration: 0.6,
ease: 'power2.out',
}, 0);
// Diamond rotates in
loaderTl.from(loaderDiamond, {
rotation: -180,
scale: 0,
opacity: 0,
duration: 0.8,
ease: 'back.out(2)',
}, 0.1);
// Diamond continuous rotation
gsap.to(loaderDiamond, {
rotation: 360,
duration: 4,
repeat: -1,
ease: 'none',
});
// Scramble text: each letter cycles through random chars before settling
const finalChars = ['e', 'v', 'o', 'l', 'v', 'e'];
const scrambleChars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ$#@&%=+*';
loaderTitleChars.forEach((el, i) => {
const tl = gsap.timeline({ delay: 0.3 + i * 0.08 });
const scrambleCount = 8;
for (let s = 0; s < scrambleCount; s++) {
tl.call(() => {
el.textContent = scrambleChars[Math.floor(Math.random() * scrambleChars.length)];
}, null, s * 0.05);
}
tl.call(() => {
el.textContent = finalChars[i];
gsap.fromTo(el, { scale: 1.2 }, { scale: 1, duration: 0.3, ease: 'back.out(2.5)' });
}, null, scrambleCount * 0.05);
});
// Counter + label rotation
const p = { v: 0 };
loaderTl.to(p, {
v: 100,
duration: 2.2,
ease: 'power1.inOut',
onUpdate: () => {
loaderCounter.textContent = String(Math.floor(p.v)).padStart(3, '0');
if (p.v > 30 && loaderLabel.textContent === 'LOADING') loaderLabel.textContent = 'COMPILING';
if (p.v > 60 && loaderLabel.textContent === 'COMPILING') loaderLabel.textContent = 'RENDERING';
if (p.v > 95 && loaderLabel.textContent === 'RENDERING') loaderLabel.textContent = 'READY';
},
}, 0.2);
// Exit: scramble text glitches + fades
loaderTl.to(loaderTitleChars, {
y: -40,
opacity: 0,
duration: 0.5,
stagger: 0.03,
ease: 'power3.in',
}, '+=0.3');
loaderTl.to(loaderDiamond, {
scale: 0.5, opacity: 0, duration: 0.4, ease: 'power3.in',
}, '-=0.4');
loaderTl.to([loaderCounter, loaderLabel], {
y: -10, opacity: 0, duration: 0.3, stagger: 0.05,
}, '-=0.4');
loaderTl.to(loaderGrid, {
opacity: 0, duration: 0.4,
}, '-=0.3');
loaderTl.to(loader, {
yPercent: -100, duration: 0.9, ease: 'power4.inOut',
}, '-=0.1');
loaderTl.set(loader, { display: 'none' });
// -------------------------------------------------------------------
// MAIN SCENE ENTRANCE
// -------------------------------------------------------------------
function playScene() {
const tl = gsap.timeline({ defaults: { ease: 'power3.out' } });
tl.to(video, {
scale: 1, opacity: 1,
duration: 2,
ease: 'power3.out',
}, 0);
// Nav elements
tl.to(magnetics, {
y: 0, opacity: 1,
duration: 0.6,
stagger: 0.05,
}, 0.3);
tl.to(fades, {
y: 0, opacity: 1,
duration: 0.7,
stagger: 0.1,
}, 0.45);
// Side labels
tl.to(sides, {
opacity: 1,
duration: 0.6,
stagger: 0.1,
}, 0.7);
// Center caption
tl.to(caption, {
scale: 1, opacity: 1,
duration: 0.6,
ease: 'back.out(1.6)',
}, 0.9);
// Huge title chars
tl.to(titleChars, {
yPercent: 0, opacity: 1,
duration: 1.2,
stagger: { each: 0.02, from: 'start' },
ease: 'expo.out',
}, 1);
// Footer meta
tl.to(footer, {
y: 0, opacity: 1,
duration: 0.5,
}, 1.3);
tl.call(enableInteractions, null, 1.5);
}
// -------------------------------------------------------------------
// INTERACTIONS
// -------------------------------------------------------------------
function enableInteractions() {
// ---- Custom cursor ----
let mx = window.innerWidth / 2, my = window.innerHeight / 2;
let rx = mx, ry = my;
window.addEventListener('mousemove', (e) => { mx = e.clientX; my = e.clientY; });
gsap.ticker.add(() => {
rx += (mx - rx) * 0.18;
ry += (my - ry) * 0.18;
gsap.set(cursorDot, { x: mx, y: my });
gsap.set(cursorRing, { x: rx, y: ry });
});
const hovers = root.querySelectorAll('a, button, [data-magnetic], .side-label, .title__char');
hovers.forEach((el) => {
el.addEventListener('mouseenter', () => cursorRing.classList.add('is-hover'));
el.addEventListener('mouseleave', () => cursorRing.classList.remove('is-hover'));
});
// ---- Magnetic ----
magnetics.forEach((el) => {
const strength = el.classList.contains('topnav__menu') ? 0.35
: el.classList.contains('topnav__lang') ? 0.45
: el.classList.contains('topnav__logo') ? 0.2
: 0.22;
el.addEventListener('mousemove', (e) => {
const r = el.getBoundingClientRect();
const cx = r.left + r.width / 2;
const cy = r.top + r.height / 2;
gsap.to(el, {
x: (e.clientX - cx) * strength,
y: (e.clientY - cy) * strength,
duration: 0.4, ease: 'power3.out',
});
});
el.addEventListener('mouseleave', () => {
gsap.to(el, { x: 0, y: 0, duration: 0.6, ease: 'elastic.out(1, 0.4)' });
});
});
// ---- Logo: diamond rotates on hover ----
const logo = root.querySelector('.topnav__logo');
logo.addEventListener('mouseenter', () => {
gsap.to(navDiamond, { rotation: 180, duration: 0.6, ease: 'back.out(2)' });
});
logo.addEventListener('mouseleave', () => {
gsap.to(navDiamond, { rotation: 0, duration: 0.6, ease: 'power3.out' });
});
// ---- Language toggle ----
langBtn.addEventListener('click', (e) => {
e.preventDefault();
const current = langCurrent.textContent;
const next = current === 'EN' ? 'IT' : 'EN';
gsap.to(langCurrent, {
y: -20, opacity: 0, duration: 0.2, ease: 'power2.in',
onComplete: () => {
langCurrent.textContent = next;
gsap.fromTo(langCurrent,
{ y: 20, opacity: 0 },
{ y: 0, opacity: 1, duration: 0.3, ease: 'back.out(2)' }
);
}
});
});
// ---- Title char proximity + hover invert ----
const title = document.getElementById('title');
let tmx = -9999, tmy = -9999;
root.addEventListener('mousemove', (e) => {
tmx = e.clientX; tmy = e.clientY;
});
root.addEventListener('mouseleave', () => {
tmx = -9999; tmy = -9999;
titleChars.forEach((c) => gsap.to(c, { y: 0, duration: 0.5, ease: 'power3.out' }));
});
gsap.ticker.add(() => {
if (tmx < 0) return;
titleChars.forEach((c) => {
const r = c.getBoundingClientRect();
if (r.width === 0) return;
const cx = r.left + r.width / 2;
const cy = r.top + r.height / 2;
const dx = tmx - cx, dy = tmy - cy;
const dist = Math.sqrt(dx * dx + dy * dy);
if (dist < 140) gsap.set(c, { y: -(1 - dist / 140) * 18 });
else gsap.set(c, { y: 0 });
});
});
titleChars.forEach((c) => {
c.addEventListener('mouseenter', () => {
gsap.to(c, { color: '#06080b', duration: 0.2 });
c.style.textShadow = '0 0 30px rgba(255,255,255,0.6)';
c.style.background = '#ffffff';
c.style.padding = '0 4px';
});
c.addEventListener('mouseleave', () => {
gsap.to(c, { color: '#ffffff', duration: 0.4 });
c.style.textShadow = '';
c.style.background = '';
c.style.padding = '';
});
});
// ---- Side labels: slide on hover ----
sides.forEach((s) => {
const dir = s.classList.contains('side-label--left') ? -1 : 1;
s.addEventListener('mouseenter', () => {
gsap.to(s, { x: dir * 10, duration: 0.4, ease: 'power3.out' });
});
s.addEventListener('mouseleave', () => {
gsap.to(s, { x: 0, duration: 0.5, ease: 'elastic.out(1, 0.4)' });
});
});
// ---- Caption: dot pulses continuously ----
gsap.to(caption.querySelector('.center-caption__dot'), {
scale: 2.2, opacity: 0.3,
duration: 1.2,
yoyo: true,
repeat: -1,
ease: 'sine.inOut',
});
// ---- Video parallax ----
let vX = 0, vY = 0, vcX = 0, vcY = 0;
root.addEventListener('mousemove', (e) => {
const r = root.getBoundingClientRect();
vX = ((e.clientX - r.left) / r.width - 0.5) * 2;
vY = ((e.clientY - r.top) / r.height - 0.5) * 2;
});
root.addEventListener('mouseleave', () => { vX = 0; vY = 0; });
gsap.ticker.add(() => {
vcX += (vX - vcX) * 0.04;
vcY += (vY - vcY) * 0.04;
gsap.set(video, { x: -vcX * 20, y: -vcY * 12, scale: 1.05 });
gsap.set(caption, { x: vcX * 10, y: vcY * 6 });
});
// Ensure video plays
video.play().catch(() => {
const tryPlay = () => { video.play().catch(() => {}); window.removeEventListener('click', tryPlay); };
window.addEventListener('click', tryPlay);
});
}
})();