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 Design Studio Hero Template with Orbiting Sparkles</title>
<meta name="description" content="Free GSAP design studio landing page hero from ToolsWaves" />
<meta name="generator" content="ToolsWaves - https://toolswaves.in/landing-pages" />
<!-- Open Graph -->
<meta property="og:title" content="Free Design Studio Hero Template with Orbiting Sparkles" />
<meta property="og:description" content="Free GSAP design studio landing page hero from ToolsWaves" />
<meta property="og:type" content="website" />
<meta property="og:image" content="https://toolswaves.in/og?title=Free%20Design%20Studio%20Hero%20Template%20with%20Orbiting%20Sparkles&category=Landing%20Page&icon=%F0%9F%93%84" />
<!-- Twitter -->
<meta name="twitter:card" content="summary_large_image" />
<meta name="twitter:title" content="Free Design Studio Hero Template with Orbiting Sparkles" />
<meta name="twitter:description" content="Free GSAP design studio landing page hero from ToolsWaves" />
<meta name="twitter:image" content="https://toolswaves.in/og?title=Free%20Design%20Studio%20Hero%20Template%20with%20Orbiting%20Sparkles&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: #080808;
--bg-2: #0a0a0a;
--ink: #e8dfd0;
--muted: rgba(232, 223, 208, 0.55);
--lime: #c4f52d;
--lime-deep: #9fcf1f;
--line: rgba(255, 255, 255, 0.08);
--cream: #e8dfd0;
}
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.04);
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%);
will-change: transform;
}
.cursor-dot {
width: 5px; height: 5px;
background: var(--lime);
border-radius: 50%;
box-shadow: 0 0 10px var(--lime);
}
.cursor-ring {
width: 32px; height: 32px;
border: 1px solid rgba(196, 245, 45, 0.5);
border-radius: 50%;
transition: width 0.3s, height 0.3s, border-color 0.3s, background 0.3s;
}
.cursor-ring.is-hover {
width: 56px; height: 56px;
border-color: var(--lime);
background: rgba(196, 245, 45, 0.08);
}
@media (hover: none), (pointer: coarse) { .cursor-dot, .cursor-ring { display: none; } }
/* =========================================================
LOADER — Sparkle + stroke-draw "N"
========================================================= */
.loader {
position: fixed;
inset: 0;
z-index: 999;
background: var(--bg);
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 2.5rem;
overflow: hidden;
}
.loader__grid {
position: absolute;
inset: 0;
background-image:
repeating-linear-gradient(0deg, transparent 0, transparent 80px, rgba(196, 245, 45, 0.04) 80px, rgba(196, 245, 45, 0.04) 81px),
repeating-linear-gradient(90deg, transparent 0, transparent 80px, rgba(196, 245, 45, 0.04) 80px, rgba(196, 245, 45, 0.04) 81px);
opacity: 0;
}
.loader__stage {
position: relative;
width: 200px;
height: 200px;
display: flex;
align-items: center;
justify-content: center;
z-index: 2;
}
.loader__mark {
width: 130px;
height: 130px;
position: relative;
z-index: 2;
filter: drop-shadow(0 0 12px rgba(196, 245, 45, 0.5));
}
#loader-N {
stroke-dasharray: 250;
stroke-dashoffset: 250;
}
.loader__sparkles {
position: absolute;
inset: -30px;
pointer-events: none;
}
.loader__sparkles span {
position: absolute;
font-size: 24px;
color: var(--lime);
will-change: transform, opacity;
text-shadow: 0 0 10px rgba(196, 245, 45, 0.6);
}
.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;
z-index: 2;
}
.loader__counter {
color: var(--lime);
font-weight: 600;
font-variant-numeric: tabular-nums;
min-width: 3ch;
}
/* =========================================================
NEXA SECTION
========================================================= */
.nexa {
position: relative;
width: 100%;
height: 100vh;
min-height: 780px;
overflow: hidden;
background: var(--bg);
}
.nexa__grid {
position: absolute;
inset: 0;
background-image:
repeating-linear-gradient(0deg, transparent 0, transparent 80px, rgba(196, 245, 45, 0.025) 80px, rgba(196, 245, 45, 0.025) 81px),
repeating-linear-gradient(90deg, transparent 0, transparent 80px, rgba(196, 245, 45, 0.025) 80px, rgba(196, 245, 45, 0.025) 81px);
z-index: 1;
pointer-events: none;
}
/* ---------- Top nav ---------- */
.topnav {
position: relative;
z-index: 30;
display: grid;
grid-template-columns: 1fr auto 1fr;
gap: 2rem;
align-items: center;
padding: 1.5rem 3rem;
}
.topnav__logo {
display: inline-flex;
align-items: center;
gap: 0.5rem;
text-decoration: none;
color: var(--ink);
font-weight: 700;
font-size: 1.5rem;
letter-spacing: -0.01em;
cursor: none;
will-change: transform;
justify-self: flex-start;
}
.topnav__logo-mark {
width: 36px; height: 36px;
background: rgba(196, 245, 45, 0.06);
border: 1px solid rgba(196, 245, 45, 0.3);
border-radius: 10px;
display: inline-flex;
align-items: center;
justify-content: center;
filter: drop-shadow(0 0 6px rgba(196, 245, 45, 0.3));
}
.topnav__logo-mark svg { width: 70%; height: 70%; }
.topnav__pill {
display: inline-flex;
align-items: center;
gap: 0;
background: rgba(255, 255, 255, 0.04);
border: 1px solid var(--line);
border-radius: 999px;
padding: 0.35rem;
backdrop-filter: blur(10px);
justify-self: center;
}
.topnav__pill a {
position: relative;
color: var(--muted);
text-decoration: none;
font-weight: 500;
font-size: 0.95rem;
padding: 0.65rem 1.4rem;
cursor: none;
will-change: transform;
border-radius: 999px;
transition: color 0.3s, background 0.3s;
}
.topnav__pill a.is-active { color: var(--ink); background: rgba(255, 255, 255, 0.08); }
.topnav__pill a:hover { color: var(--ink); }
.topnav__actions {
display: flex;
align-items: center;
gap: 0.5rem;
justify-self: flex-end;
}
.topnav__signup {
color: var(--muted);
text-decoration: none;
padding: 0.6rem 1.2rem;
font-weight: 500;
font-size: 0.95rem;
cursor: none;
will-change: transform;
transition: color 0.3s;
}
.topnav__signup:hover { color: var(--ink); }
.topnav__login {
background: transparent;
color: var(--ink);
text-decoration: none;
padding: 0.65rem 1.5rem;
border-radius: 999px;
font-weight: 500;
font-size: 0.95rem;
cursor: none;
will-change: transform;
border: 1px solid rgba(255, 255, 255, 0.2);
transition: background 0.3s, border-color 0.3s;
}
.topnav__login:hover {
background: var(--lime);
color: var(--bg);
border-color: var(--lime);
}
/* ---------- Hero ---------- */
.hero {
position: relative;
z-index: 10;
display: grid;
grid-template-columns: 1.4fr 1fr;
gap: 3rem;
padding: 2rem 3rem 2rem;
align-items: center;
}
.hero__left {
position: relative;
padding-top: 1rem;
}
.hero__title {
font-family: 'Fraunces', serif;
font-weight: 500;
font-size: clamp(2.4rem, 5.8vw, 5.25rem);
line-height: 1.1;
letter-spacing: -0.02em;
color: var(--cream);
margin-bottom: 2.25rem;
font-style: italic;
}
.hero__line {
display: block;
overflow: hidden;
padding-bottom: 0.04em;
white-space: nowrap;
}
.hero__star {
color: var(--lime);
font-style: normal;
margin: 0 0.1em;
display: inline-block;
will-change: transform;
filter: drop-shadow(0 0 10px rgba(196, 245, 45, 0.6));
}
.hero__char {
display: inline-block;
will-change: transform, color;
transition: color 0.3s ease;
}
.hero__char.is-space { width: 0.25em; }
.hero__card {
position: relative;
background: rgba(196, 245, 45, 0.025);
border: 1px solid rgba(196, 245, 45, 0.1);
border-radius: 24px;
padding: 1.5rem 2rem;
max-width: 560px;
overflow: hidden;
will-change: transform;
}
.hero__card-glow {
position: absolute;
top: -40%;
left: -20%;
width: 70%;
height: 180%;
background: radial-gradient(ellipse at center, rgba(196, 245, 45, 0.15) 0%, transparent 60%);
filter: blur(20px);
pointer-events: none;
}
.hero__desc {
position: relative;
z-index: 2;
font-size: 0.95rem;
line-height: 1.55;
color: var(--muted);
}
.hero__sparkle {
position: absolute;
top: -5px;
right: 15%;
color: var(--lime);
font-size: 36px;
filter: drop-shadow(0 0 12px rgba(196, 245, 45, 0.7));
will-change: transform;
cursor: none;
}
/* Portrait frame */
.hero__portrait {
position: relative;
width: 100%;
max-width: 340px;
justify-self: center;
will-change: transform;
}
.hero__portrait-frame {
position: relative;
border-radius: 30px;
overflow: hidden;
background: linear-gradient(135deg, rgba(196, 245, 45, 0.1) 0%, rgba(0, 0, 0, 0.9) 100%);
border: 1px solid rgba(196, 245, 45, 0.2);
aspect-ratio: 3 / 3.8;
box-shadow: 0 30px 60px rgba(0, 0, 0, 0.6),
inset 0 0 50px rgba(196, 245, 45, 0.1);
}
.hero__portrait-img {
position: absolute;
inset: 0;
}
.hero__portrait-img img {
width: 100%; height: 100%;
object-fit: cover;
object-position: center 28%;
user-select: none;
filter: contrast(1.15) saturate(1.2) brightness(0.75) hue-rotate(-5deg) sepia(0.2);
mix-blend-mode: multiply;
}
.hero__portrait-img::after {
content: '';
position: absolute;
inset: 0;
background:
radial-gradient(ellipse at 30% 40%, rgba(255, 130, 60, 0.4) 0%, transparent 55%),
radial-gradient(ellipse at 70% 50%, rgba(196, 245, 45, 0.2) 0%, transparent 60%),
linear-gradient(180deg, transparent 40%, rgba(0, 0, 0, 0.6) 100%);
mix-blend-mode: screen;
pointer-events: none;
}
.hero__portrait-grid {
position: absolute;
inset: 0;
width: 100%; height: 100%;
pointer-events: none;
}
.corner {
position: absolute;
width: 14px; height: 14px;
border: 2px solid var(--lime);
pointer-events: none;
filter: drop-shadow(0 0 6px rgba(196, 245, 45, 0.6));
}
.corner--tl { top: 12px; left: 12px; border-right: none; border-bottom: none; }
.corner--tr { top: 12px; right: 12px; border-left: none; border-bottom: none; }
.corner--bl { bottom: 12px; left: 12px; border-right: none; border-top: none; }
.corner--br { bottom: 12px; right: 12px; border-left: none; border-top: none; }
/* ---------- Terrain + Tags ---------- */
.terrain {
position: absolute;
left: 0; right: 0;
bottom: 0;
height: 260px;
z-index: 8;
}
.terrain__mountains {
position: absolute;
inset: 0;
width: 100%; height: 100%;
pointer-events: none;
}
.tag {
position: absolute;
display: flex;
flex-direction: column;
align-items: center;
gap: 0;
will-change: transform;
cursor: none;
pointer-events: auto;
}
.tag__line {
display: block;
width: 1px;
height: 40px;
background: linear-gradient(180deg, rgba(196, 245, 45, 0.1), rgba(196, 245, 45, 0.8));
order: 2;
transform-origin: top;
}
.tag__pill {
display: inline-flex;
align-items: center;
gap: 0.3rem;
background: rgba(196, 245, 45, 0.06);
border: 1px solid rgba(196, 245, 45, 0.25);
color: var(--lime);
padding: 0.35rem 0.75rem;
border-radius: 6px;
font-size: 0.65rem;
font-weight: 700;
letter-spacing: 0.15em;
backdrop-filter: blur(8px);
order: 1;
margin-bottom: 4px;
transition: background 0.3s, border-color 0.3s;
}
.tag__pill b { color: var(--lime); font-weight: 400; }
.tag:hover .tag__pill { background: rgba(196, 245, 45, 0.15); border-color: var(--lime); }
.tag--1 { bottom: 110px; left: 11%; }
.tag--2 { bottom: 70px; left: 26%; }
.tag--3 { bottom: 45px; left: 62%; }
.tag--4 { bottom: 65px; left: 80%; }
.cta-big {
position: absolute;
bottom: 55px;
left: 50%;
transform: translateX(-50%);
display: inline-flex;
align-items: center;
gap: 0.5rem;
background: var(--lime);
color: var(--bg);
text-decoration: none;
padding: 1rem 2rem;
border-radius: 999px;
font-weight: 600;
font-size: 1.05rem;
cursor: none;
will-change: transform;
box-shadow: 0 0 40px rgba(196, 245, 45, 0.5),
0 10px 24px rgba(0, 0, 0, 0.4);
z-index: 3;
overflow: hidden;
}
.cta-big::before {
content: '';
position: absolute;
top: 0; left: -100%;
width: 60%; height: 100%;
background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.5), transparent);
transform: skewX(-20deg);
transition: left 0.7s;
}
.cta-big:hover::before { left: 200%; }
.cta-big__arrow {
display: inline-flex;
align-items: center;
justify-content: center;
transition: transform 0.3s;
}
.cta-big__arrow svg { width: 18px; height: 18px; }
.cta-big:hover .cta-big__arrow { transform: translate(3px, -3px); }
/* ---------- Initial hidden states ---------- */
[data-magnetic], [data-link], [data-fade], [data-title-line] .hero__char,
[data-title-line] .hero__star, [data-card], [data-portrait], [data-tag],
[data-cta], [data-sparkle] {
opacity: 0;
}
/* ---------- Responsive ---------- */
@media (max-width: 1100px) {
.topnav { grid-template-columns: auto 1fr auto; padding: 1rem 1.5rem; }
.topnav__pill { display: none; }
.hero { grid-template-columns: 1fr; gap: 2rem; padding: 1.5rem; }
.hero__portrait { max-width: 280px; }
.tag__pill { font-size: 0.55rem; padding: 0.25rem 0.5rem; }
}
@media (max-width: 768px) {
.topnav__signup { display: none; }
.hero__title { font-size: clamp(1.8rem, 10vw, 3.2rem); white-space: normal; }
.hero__line { white-space: normal; }
.hero__card { padding: 1rem 1.25rem; }
.hero__desc { font-size: 0.85rem; }
.hero__portrait { max-width: 220px; }
.terrain { height: 200px; }
.tag--2, .tag--4 { display: none; }
.tag--1 { bottom: 90px; left: 3%; }
.tag--3 { bottom: 50px; left: 75%; }
.cta-big { padding: 0.75rem 1.5rem; font-size: 0.9rem; }
}
@media (max-width: 480px) {
.tag--1, .tag--3 { display: none; }
.hero__sparkle { display: none; }
.topnav__logo-text { 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 — Nexa</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=Fraunces:ital,wght@0,500;0,700;1,500;1,700&family=Inter:wght@400;500;600;700&family=JetBrains+Mono:wght@500;600&display=swap" rel="stylesheet">
<link rel="stylesheet" href="banner-nexa.css">
</head>
<body>
<!-- Custom cursor -->
<div class="cursor-dot" id="cursor-dot"></div>
<div class="cursor-ring" id="cursor-ring"></div>
<!-- ========== LOADER — Star sparkle + stroke-draw N ========== -->
<div class="loader" id="loader">
<div class="loader__grid" id="loader-grid-bg"></div>
<div class="loader__stage">
<!-- Orbiting sparkles -->
<div class="loader__sparkles" id="loader-sparkles"></div>
<!-- Stroke-drawn "N" logo mark -->
<svg class="loader__mark" viewBox="0 0 120 120" aria-hidden="true">
<path id="loader-N" d="M 25 90 L 25 30 L 95 90 L 95 30"
fill="none" stroke="#c4f52d" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
</div>
<div class="loader__meta">
<span class="loader__label" id="loader-label">INITIALIZING</span>
<span class="loader__counter" id="loader-counter">0%</span>
</div>
</div>
<section class="nexa" id="nexa">
<!-- Background subtle grid -->
<div class="nexa__grid" id="grid"></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">
<path d="M 8 28 L 8 8 L 28 28 L 28 8" stroke="#c4f52d" stroke-width="3" stroke-linecap="round" stroke-linejoin="round" fill="none"/>
</svg>
</span>
<span class="topnav__logo-text">Nexa</span>
</a>
<nav class="topnav__pill">
<a href="#" class="is-active" data-magnetic data-link>Home</a>
<a href="#" data-magnetic data-link>About</a>
<a href="#" data-magnetic data-link>Feature</a>
</nav>
<div class="topnav__actions">
<a href="#" class="topnav__signup" data-magnetic>Signup</a>
<a href="#" class="topnav__login" data-magnetic>Login</a>
</div>
</header>
<!-- ========== HERO ========== -->
<div class="hero" id="hero">
<div class="hero__left">
<h1 class="hero__title" aria-label="Unveil Tomorrow's Design Today">
<span class="hero__line" data-title-line>Unveil Tomorrow's</span>
<span class="hero__line" data-title-line>Design <span class="hero__star">✶</span> Today!</span>
</h1>
<div class="hero__card" data-card>
<div class="hero__card-glow"></div>
<p class="hero__desc" data-fade>
Experience the future in our cutting-edge designs — where
innovation meets style, crafting a vision for the smart generation.
</p>
</div>
<span class="hero__sparkle hero__sparkle--1" data-sparkle>✶</span>
</div>
<!-- Right: Portrait frame -->
<div class="hero__portrait" id="portrait" data-portrait>
<div class="hero__portrait-frame">
<div class="hero__portrait-img">
<img src="https://images.unsplash.com/photo-1599566150163-29194dcaad36?auto=format&fit=crop&w=700&q=80"
alt="Character"
draggable="false"
onerror="this.style.display='none'">
</div>
<!-- Grid overlay -->
<svg class="hero__portrait-grid" viewBox="0 0 300 380" preserveAspectRatio="none" aria-hidden="true">
<g stroke="rgba(196, 245, 45, 0.25)" stroke-width="0.6" fill="none">
<line x1="0" y1="95" x2="300" y2="95"/>
<line x1="0" y1="190" x2="300" y2="190"/>
<line x1="0" y1="285" x2="300" y2="285"/>
<line x1="75" y1="0" x2="75" y2="380"/>
<line x1="150" y1="0" x2="150" y2="380"/>
<line x1="225" y1="0" x2="225" y2="380"/>
</g>
</svg>
<!-- Corner accents -->
<span class="corner corner--tl"></span>
<span class="corner corner--tr"></span>
<span class="corner corner--bl"></span>
<span class="corner corner--br"></span>
</div>
</div>
</div>
<!-- ========== BOTTOM TERRAIN + TAGS ========== -->
<div class="terrain" id="terrain">
<svg class="terrain__mountains" viewBox="0 0 1400 260" preserveAspectRatio="none" aria-hidden="true">
<path d="M0 260 L0 140 Q 100 80 200 110 T 400 90 T 600 120 T 800 85 T 1000 100 T 1200 75 T 1400 115 L 1400 260 Z"
fill="rgba(196, 245, 45, 0.05)"/>
<path d="M0 260 L0 180 Q 120 130 250 160 T 500 140 T 750 175 T 1000 145 T 1250 165 T 1400 155 L 1400 260 Z"
fill="rgba(196, 245, 45, 0.08)"/>
<path d="M0 260 L0 210 Q 150 180 300 200 T 600 190 T 900 210 T 1200 200 T 1400 205 L 1400 260 Z"
fill="rgba(196, 245, 45, 0.12)"/>
</svg>
<div class="tag tag--1" data-tag>
<span class="tag__line"></span>
<span class="tag__pill"><b>✶</b> FUTURISTIC</span>
</div>
<div class="tag tag--2" data-tag>
<span class="tag__line"></span>
<span class="tag__pill"><b>✶</b> SLEEK</span>
</div>
<div class="tag tag--3" data-tag>
<span class="tag__line"></span>
<span class="tag__pill"><b>✶</b> TRENDSETTING</span>
</div>
<div class="tag tag--4" data-tag>
<span class="tag__line"></span>
<span class="tag__pill"><b>✶</b> INTERACTIVE</span>
</div>
<a href="#" class="cta-big" data-magnetic data-cta>
<span class="cta-big__label">Connect Now</span>
<span class="cta-big__arrow">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2">
<path d="M7 17L17 7M17 7H8M17 7v9" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
</span>
</a>
</div>
</section>
<script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.12.5/gsap.min.js"></script>
<script src="banner-nexa.js"></script>
</body>
</html>
</body>
</html>JS banner-nexa.js — GSAP animation timeline
(() => {
const root = document.getElementById('nexa');
if (!root) return;
// -------------------------------------------------------------------
// SPLIT TITLE (preserve star element)
// -------------------------------------------------------------------
const titleLines = root.querySelectorAll('[data-title-line]');
titleLines.forEach((line) => {
const frag = document.createDocumentFragment();
line.childNodes.forEach((node) => {
if (node.nodeType === Node.TEXT_NODE) {
[...node.textContent].forEach((ch) => {
const span = document.createElement('span');
span.className = 'hero__char';
if (ch === ' ') { span.classList.add('is-space'); span.innerHTML = ' '; }
else span.textContent = ch;
frag.appendChild(span);
});
} else {
// Keep star span intact
frag.appendChild(node.cloneNode(true));
}
});
line.textContent = '';
line.appendChild(frag);
});
// -------------------------------------------------------------------
// BUILD LOADER SPARKLES
// -------------------------------------------------------------------
const sparkleHost = document.getElementById('loader-sparkles');
const STAR_COUNT = 8;
for (let i = 0; i < STAR_COUNT; i++) {
const s = document.createElement('span');
s.textContent = i % 2 === 0 ? '\u2736' : '\u2737';
const angle = (i / STAR_COUNT) * Math.PI * 2;
const r = 100 + (i % 2) * 20;
s.style.left = (100 + Math.cos(angle) * r) + 'px';
s.style.top = (100 + Math.sin(angle) * r) + 'px';
s.style.transform = 'translate(-50%, -50%)';
sparkleHost.appendChild(s);
}
const sparkles = sparkleHost.querySelectorAll('span');
// -------------------------------------------------------------------
// REFERENCES
// -------------------------------------------------------------------
const loader = document.getElementById('loader');
const loaderGridBg = document.getElementById('loader-grid-bg');
const loaderN = document.getElementById('loader-N');
const loaderCounter = document.getElementById('loader-counter');
const loaderLabel = document.getElementById('loader-label');
const cursorDot = document.getElementById('cursor-dot');
const cursorRing = document.getElementById('cursor-ring');
const magnetics = root.querySelectorAll('[data-magnetic]');
const titleChars = root.querySelectorAll('.hero__char');
const titleStar = root.querySelector('.hero__star');
const card = root.querySelector('[data-card]');
const desc = root.querySelector('[data-fade]');
const portrait = document.getElementById('portrait');
const tags = root.querySelectorAll('[data-tag]');
const cta = root.querySelector('[data-cta]');
const sparkle = root.querySelector('[data-sparkle]');
const logoMark = document.getElementById('logo-mark');
// -------------------------------------------------------------------
// INITIAL STATES
// -------------------------------------------------------------------
gsap.set(magnetics, { y: -15, opacity: 0 });
gsap.set(titleChars, { yPercent: 110, opacity: 0 });
gsap.set(titleStar, { scale: 0, opacity: 0, rotation: -90 });
gsap.set(card, { y: 30, opacity: 0 });
gsap.set(portrait, { x: 80, opacity: 0, scale: 0.9 });
gsap.set(tags, { y: 20, opacity: 0 });
gsap.set(cta, { scale: 0.8, opacity: 0 });
gsap.set(sparkle, { scale: 0, opacity: 0, rotation: -90 });
gsap.set(sparkles, { scale: 0, opacity: 0 });
// -------------------------------------------------------------------
// LOADER TIMELINE
// -------------------------------------------------------------------
const loaderTl = gsap.timeline({ onComplete: playScene });
// Grid fades in
loaderTl.to(loaderGridBg, {
opacity: 1,
duration: 0.5,
ease: 'power2.out',
}, 0);
// N stroke draws in
loaderTl.to(loaderN, {
strokeDashoffset: 0,
duration: 1.4,
ease: 'power2.inOut',
}, 0.2);
// Sparkles pop in around N with orbit
loaderTl.to(sparkles, {
scale: 1, opacity: 1,
duration: 0.5,
stagger: 0.05,
ease: 'back.out(2)',
}, 0.5);
// Sparkles twinkle continuously
sparkles.forEach((s) => {
gsap.to(s, {
scale: 1.4,
rotation: 45,
duration: 0.6 + Math.random() * 0.8,
yoyo: true,
repeat: -1,
ease: 'sine.inOut',
delay: Math.random() * 1,
transformOrigin: 'center',
});
});
// Counter + labels
const p = { v: 0 };
loaderTl.to(p, {
v: 100,
duration: 2.3,
ease: 'power1.inOut',
onUpdate: () => {
loaderCounter.textContent = Math.floor(p.v) + '%';
if (p.v > 25 && loaderLabel.textContent === 'INITIALIZING') loaderLabel.textContent = 'CRAFTING DESIGN';
if (p.v > 60 && loaderLabel.textContent === 'CRAFTING DESIGN') loaderLabel.textContent = 'POLISHING PIXELS';
if (p.v > 95 && loaderLabel.textContent === 'POLISHING PIXELS') loaderLabel.textContent = 'READY';
},
}, 0.3);
// Exit: sparkles burst outward, N scales up and fades
loaderTl.to(sparkles, {
x: (i) => Math.cos(i / STAR_COUNT * Math.PI * 2) * 250,
y: (i) => Math.sin(i / STAR_COUNT * Math.PI * 2) * 250,
opacity: 0,
scale: 0.2,
rotation: 180,
duration: 0.9,
ease: 'power3.in',
stagger: 0.02,
}, '+=0.2');
loaderTl.to('.loader__mark', {
scale: 1.3, opacity: 0, duration: 0.6, ease: 'power3.in',
}, '-=0.5');
loaderTl.to([loaderCounter, loaderLabel], {
y: -10, opacity: 0, duration: 0.3, stagger: 0.05,
}, '-=0.4');
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: 'power3.out' } });
// Nav
tl.to(magnetics, {
y: 0, opacity: 1,
duration: 0.6,
stagger: 0.05,
}, 0);
// Title chars + star
tl.to(titleChars, {
yPercent: 0, opacity: 1,
duration: 1,
stagger: 0.018,
ease: 'expo.out',
}, 0.3);
tl.to(titleStar, {
scale: 1, opacity: 1, rotation: 0,
duration: 0.7,
ease: 'back.out(2)',
}, 0.6);
tl.to(sparkle, {
scale: 1, opacity: 1, rotation: 0,
duration: 0.7,
ease: 'back.out(2)',
}, 0.55);
// Description card
tl.to(card, {
y: 0, opacity: 1,
duration: 0.8,
ease: 'power4.out',
}, 0.9);
// Portrait slides in
tl.to(portrait, {
x: 0, opacity: 1, scale: 1,
duration: 1.2,
ease: 'power4.out',
}, 0.5);
// Tags pop up from mountain
tl.to(tags, {
y: 0, opacity: 1,
duration: 0.7,
stagger: 0.1,
ease: 'back.out(1.6)',
}, 1.1);
// CTA pops
tl.to(cta, {
scale: 1, opacity: 1,
duration: 0.9,
ease: 'back.out(1.6)',
}, 1.4);
tl.call(startContinuous, null, 1.7);
tl.call(enableInteractions, null, 1.7);
}
// -------------------------------------------------------------------
// CONTINUOUS
// -------------------------------------------------------------------
function startContinuous() {
// Star rotates
gsap.to(titleStar, {
rotation: 360,
duration: 10,
repeat: -1,
ease: 'none',
transformOrigin: 'center center',
});
// Decorative sparkle pulses
gsap.to(sparkle, {
scale: 1.2, rotation: 30,
duration: 1.5,
yoyo: true,
repeat: -1,
ease: 'sine.inOut',
transformOrigin: 'center',
});
// Portrait subtle breath
gsap.to(portrait, {
y: -10,
duration: 3,
yoyo: true,
repeat: -1,
ease: 'sine.inOut',
});
// Tags gentle float
tags.forEach((tag, i) => {
gsap.to(tag, {
y: `-=${6 + i}`,
duration: 2 + i * 0.3,
yoyo: true,
repeat: -1,
ease: 'sine.inOut',
delay: i * 0.15,
});
});
// CTA subtle glow pulse
gsap.to(cta, {
boxShadow: '0 0 60px rgba(196, 245, 45, 0.7), 0 10px 24px rgba(0, 0, 0, 0.4)',
duration: 1.4,
yoyo: true,
repeat: -1,
ease: 'sine.inOut',
});
}
// -------------------------------------------------------------------
// 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-tag], .hero__char, .hero__sparkle');
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('cta-big') ? 0.3
: el.classList.contains('topnav__login') ? 0.35
: 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 N redraws on hover ----
const logo = root.querySelector('.topnav__logo');
logo.addEventListener('mouseenter', () => {
gsap.fromTo(logoMark.querySelector('path'),
{ strokeDasharray: 80, strokeDashoffset: 80 },
{ strokeDashoffset: 0, duration: 0.7, ease: 'power2.out' }
);
});
// ---- Title char proximity + hover color ----
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 < 140) gsap.set(c, { y: -(1 - dist / 140) * 14 });
else gsap.set(c, { y: 0 });
});
});
titleChars.forEach((c) => {
c.addEventListener('mouseenter', () => gsap.to(c, { color: '#c4f52d', duration: 0.2 }));
c.addEventListener('mouseleave', () => gsap.to(c, { color: '#e8dfd0', duration: 0.4 }));
});
// ---- Portrait 3D tilt ----
const hPortraitImg = root.querySelector('.hero__portrait-img');
let px = 0, py = 0, pcx = 0, pcy = 0;
portrait.addEventListener('mousemove', (e) => {
const r = portrait.getBoundingClientRect();
px = ((e.clientX - r.left) / r.width - 0.5) * 2;
py = ((e.clientY - r.top) / r.height - 0.5) * 2;
});
portrait.addEventListener('mouseleave', () => { px = 0; py = 0; });
gsap.ticker.add(() => {
pcx += (px - pcx) * 0.1;
pcy += (py - pcy) * 0.1;
gsap.set(portrait, {
rotationY: pcx * 10,
rotationX: -pcy * 8,
transformPerspective: 1000,
transformOrigin: 'center',
});
if (hPortraitImg) gsap.set(hPortraitImg, {
x: -pcx * 10,
y: -pcy * 6,
});
});
// ---- Tags: hover lifts pill + line extends ----
tags.forEach((tag) => {
const line = tag.querySelector('.tag__line');
const pill = tag.querySelector('.tag__pill');
tag.addEventListener('mouseenter', () => {
gsap.to(line, { scaleY: 1.5, duration: 0.4, ease: 'power3.out' });
gsap.to(pill, { y: -8, scale: 1.05, duration: 0.4, ease: 'back.out(2)' });
});
tag.addEventListener('mouseleave', () => {
gsap.to(line, { scaleY: 1, duration: 0.5 });
gsap.to(pill, { y: 0, scale: 1, duration: 0.5, ease: 'elastic.out(1, 0.4)' });
});
});
// ---- CTA click = shoot sparkle particles ----
cta.addEventListener('click', (e) => {
e.preventDefault();
gsap.fromTo(cta,
{ scale: 1 },
{ scale: 0.95, duration: 0.1, yoyo: true, repeat: 1, ease: 'sine.inOut' }
);
const rect = cta.getBoundingClientRect();
const cx = rect.left + rect.width / 2;
const cy = rect.top + rect.height / 2;
for (let i = 0; i < 10; i++) {
const s = document.createElement('span');
s.textContent = '\u2736';
s.style.position = 'fixed';
s.style.left = cx + 'px';
s.style.top = cy + 'px';
s.style.color = '#c4f52d';
s.style.fontSize = '20px';
s.style.textShadow = '0 0 8px rgba(196, 245, 45, 0.6)';
s.style.pointerEvents = 'none';
s.style.zIndex = '200';
s.style.transform = 'translate(-50%, -50%)';
document.body.appendChild(s);
const a = (i / 10) * Math.PI * 2;
const dist = 80 + Math.random() * 60;
gsap.fromTo(s,
{ x: 0, y: 0, opacity: 1, scale: 0.6 },
{
x: Math.cos(a) * dist,
y: Math.sin(a) * dist,
opacity: 0,
scale: 1.4,
rotation: 180,
duration: 0.9,
ease: 'power3.out',
onComplete: () => s.remove(),
}
);
}
});
// ---- Overall scene parallax ----
let sx = 0, sy = 0, scx = 0, scy = 0;
root.addEventListener('mousemove', (e) => {
const r = root.getBoundingClientRect();
sx = ((e.clientX - r.left) / r.width - 0.5) * 2;
sy = ((e.clientY - r.top) / r.height - 0.5) * 2;
});
root.addEventListener('mouseleave', () => { sx = 0; sy = 0; });
gsap.ticker.add(() => {
scx += (sx - scx) * 0.04;
scy += (sy - scy) * 0.04;
tags.forEach((tag, i) => {
if (tag.matches(':hover')) return;
gsap.set(tag, { x: scx * (10 + i * 4) });
});
});
}
})();