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 Food Restaurant Hero Template with Steaming Bowl Loader</title>
<meta name="description" content="Free GSAP food and restaurant landing page hero from ToolsWaves" />
<meta name="generator" content="ToolsWaves - https://toolswaves.in/landing-pages" />
<!-- Open Graph -->
<meta property="og:title" content="Free Food Restaurant Hero Template with Steaming Bowl Loader" />
<meta property="og:description" content="Free GSAP food and restaurant landing page hero from ToolsWaves" />
<meta property="og:type" content="website" />
<meta property="og:image" content="https://toolswaves.in/og?title=Free%20Food%20Restaurant%20Hero%20Template%20with%20Steaming%20Bowl%20Loader&category=Landing%20Page&icon=%F0%9F%93%84" />
<!-- Twitter -->
<meta name="twitter:card" content="summary_large_image" />
<meta name="twitter:title" content="Free Food Restaurant Hero Template with Steaming Bowl Loader" />
<meta name="twitter:description" content="Free GSAP food and restaurant landing page hero from ToolsWaves" />
<meta name="twitter:image" content="https://toolswaves.in/og?title=Free%20Food%20Restaurant%20Hero%20Template%20with%20Steaming%20Bowl%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 {
--teal: #2d6d7d;
--teal-deep: #1e4f5e;
--yellow: #f5b900;
--yellow-deep: #e0a800;
--cream: #fff3e0;
--ink: #1a1a1a;
--white: #ffffff;
--muted: rgba(255, 255, 255, 0.75);
--red: #e63932;
}
html, body {
font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
background: var(--teal);
color: var(--white);
overflow: hidden;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
.back-link {
position: fixed;
bottom: 0.75rem;
left: 0.75rem;
z-index: 1000;
color: var(--ink);
text-decoration: none;
font-size: 0.65rem;
font-weight: 600;
letter-spacing: 0.12em;
padding: 0.45rem 0.9rem;
background: var(--white);
border-radius: 999px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
opacity: 0.85;
transition: opacity 0.3s, transform 0.3s;
}
.back-link:hover { opacity: 1; transform: translateY(-2px); }
/* =========================================================
LOADER — Bowl filling with noodles + steam
========================================================= */
.loader {
position: fixed;
inset: 0;
z-index: 999;
background: linear-gradient(135deg, var(--teal) 0%, var(--teal-deep) 100%);
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 2rem;
}
.loader__scene {
position: relative;
width: 240px;
height: 240px;
}
.loader__bowl {
position: absolute;
bottom: 0;
left: 0;
width: 100%;
height: auto;
filter: drop-shadow(0 20px 30px rgba(0, 0, 0, 0.3));
}
#noodles {
transform-origin: center;
will-change: transform;
}
.loader__steam {
position: absolute;
top: -10px;
left: 50%;
transform: translateX(-50%);
width: 80px;
height: 80px;
pointer-events: none;
}
.loader__steam span {
position: absolute;
bottom: 0;
width: 10px;
height: 10px;
background: rgba(255, 255, 255, 0.6);
border-radius: 50%;
filter: blur(3px);
will-change: transform, opacity;
}
.loader__meta {
display: flex;
gap: 1.25rem;
align-items: center;
font-family: 'JetBrains Mono', monospace;
font-size: 0.75rem;
letter-spacing: 0.25em;
color: var(--muted);
text-transform: uppercase;
}
.loader__counter {
color: var(--yellow);
font-weight: 600;
font-variant-numeric: tabular-nums;
min-width: 3ch;
}
/* =========================================================
RASA SECTION
========================================================= */
.rasa {
position: relative;
width: 100%;
height: 100vh;
min-height: 720px;
overflow: hidden;
background: var(--teal);
}
.split {
position: absolute;
inset: 0;
will-change: transform;
}
.split--teal { background: var(--teal); z-index: 1; }
.split--yellow {
background: var(--yellow);
clip-path: polygon(70% 0, 100% 0, 100% 100%, 45% 100%);
z-index: 2;
}
.split__texture {
position: absolute;
inset: 0;
z-index: 3;
background-image:
repeating-linear-gradient(115deg, transparent 0, transparent 8px, rgba(0, 0, 0, 0.02) 8px, rgba(0, 0, 0, 0.02) 9px),
repeating-linear-gradient(25deg, transparent 0, transparent 12px, rgba(255, 255, 255, 0.02) 12px, rgba(255, 255, 255, 0.02) 13px);
pointer-events: none;
mix-blend-mode: overlay;
}
/* ---------- Top nav ---------- */
.topnav {
position: relative;
z-index: 30;
display: flex;
align-items: center;
gap: 2rem;
padding: 1.25rem 2.5rem;
}
.topnav__logo {
display: inline-flex;
align-items: center;
gap: 0.45rem;
text-decoration: none;
color: var(--white);
font-weight: 700;
font-size: 1.25rem;
letter-spacing: 0.02em;
will-change: transform;
}
.topnav__logo-mark { width: 30px; height: 30px; }
.topnav__logo-mark svg { width: 100%; height: 100%; display: block; }
.topnav__logo-text em { color: var(--yellow); font-style: normal; }
.topnav__links {
margin: 0 auto;
display: flex;
gap: 2rem;
}
.topnav__links a {
position: relative;
color: var(--white);
text-decoration: none;
font-weight: 500;
font-size: 0.95rem;
padding: 0.3rem 0;
will-change: transform;
}
.topnav__links a.is-active { color: var(--white); }
.topnav__links a::after {
content: '';
position: absolute;
left: 0; bottom: -4px;
width: 100%; height: 2px;
background: var(--yellow);
transform: scaleX(0);
transform-origin: right;
transition: transform 0.4s cubic-bezier(0.65, 0, 0.35, 1);
}
.topnav__links a.is-active::after,
.topnav__links a:hover::after { transform: scaleX(1); transform-origin: left; }
.topnav__actions {
display: flex;
align-items: center;
gap: 0.5rem;
}
.topnav__icon {
position: relative;
width: 36px; height: 36px;
background: transparent;
border: none;
color: var(--white);
display: inline-flex;
align-items: center;
justify-content: center;
text-decoration: none;
cursor: pointer;
will-change: transform;
transition: color 0.3s;
}
.topnav__icon:hover { color: var(--yellow); }
.topnav__icon svg { width: 20px; height: 20px; }
.topnav__badge {
position: absolute;
top: -2px; right: -2px;
width: 16px; height: 16px;
background: var(--red);
color: #fff;
font-size: 0.6rem;
font-weight: 700;
border-radius: 50%;
display: inline-flex;
align-items: center;
justify-content: center;
line-height: 1;
}
/* ---------- Left pagination ---------- */
.pagination {
position: absolute;
left: 0; top: 0; bottom: 0;
width: 54px;
background: var(--yellow);
z-index: 15;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 1rem;
padding: 3rem 0;
will-change: transform;
}
.pagination__bar {
position: absolute;
bottom: 3rem;
width: 1.5px;
height: 120px;
background: var(--white);
}
.pagination__nums {
display: flex;
flex-direction: column;
gap: 0.6rem;
margin-top: 5rem;
font-family: 'Inter', sans-serif;
font-weight: 500;
color: var(--white);
font-size: 0.85rem;
font-variant-numeric: tabular-nums;
}
.pagination__nums span {
cursor: pointer;
padding: 0.1rem 0;
opacity: 0.5;
transition: opacity 0.3s, transform 0.3s;
display: inline-block;
}
.pagination__nums span.is-active { opacity: 1; font-weight: 700; transform: scale(1.3); }
.pagination__nums span:hover { opacity: 1; }
/* ---------- Right social ---------- */
.social {
position: absolute;
right: 0; top: 0; bottom: 0;
width: 54px;
z-index: 15;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 1.5rem;
}
.social__icon {
position: relative;
width: 40px; height: 40px;
color: var(--white);
display: inline-flex;
align-items: center;
justify-content: center;
text-decoration: none;
border-radius: 50%;
background: transparent;
will-change: transform;
transition: color 0.35s ease, background 0.35s ease, box-shadow 0.35s ease;
}
.social__icon svg {
width: 20px; height: 20px;
position: relative;
z-index: 2;
transition: transform 0.35s cubic-bezier(0.65, 0, 0.35, 1);
}
.social__icon:hover {
color: var(--white);
background: var(--teal-deep);
box-shadow: 0 6px 16px rgba(30, 79, 94, 0.5),
0 0 0 2px rgba(255, 255, 255, 0.35);
}
.social__icon:hover svg { transform: scale(1.1); }
/* ---------- Hero ---------- */
.hero {
position: absolute;
inset: 0;
z-index: 10;
display: grid;
grid-template-columns: 1fr 1.2fr;
gap: 2rem;
padding: 5rem 6rem;
align-items: center;
pointer-events: none;
}
.hero > * { pointer-events: auto; }
.hero__content {
max-width: 520px;
color: var(--white);
}
.hero__script {
display: inline-block;
font-family: 'Dancing Script', cursive;
font-size: 2rem;
color: var(--yellow);
font-weight: 700;
margin-bottom: 0.5rem;
will-change: transform;
}
.hero__title {
font-family: 'Playfair Display', serif;
font-weight: 700;
font-size: clamp(2rem, 4vw, 3.25rem);
line-height: 1.15;
color: var(--white);
margin-bottom: 1.5rem;
}
.hero__line {
display: block;
overflow: hidden;
padding-bottom: 0.04em;
}
.hero__char {
display: inline-block;
will-change: transform, color;
transition: color 0.3s ease;
}
.hero__char.is-space { width: 0.25em; }
.hero__desc {
font-size: 0.85rem;
line-height: 1.65;
color: rgba(255, 255, 255, 0.82);
margin-bottom: 2rem;
max-width: 420px;
}
.hero__links {
display: flex;
gap: 2rem;
}
.hero__link {
position: relative;
color: var(--white);
text-decoration: none;
font-weight: 500;
font-size: 0.9rem;
padding: 0.3rem 0;
will-change: transform;
}
.hero__link::after {
content: '';
position: absolute;
left: 0; bottom: -2px;
width: 100%; height: 2px;
background: var(--yellow);
transform-origin: left;
}
.hero__link:hover::after {
animation: underlineSlide 0.8s ease;
}
@keyframes underlineSlide {
0% { transform: scaleX(1); transform-origin: right; }
50% { transform: scaleX(0); transform-origin: right; }
51% { transform: scaleX(0); transform-origin: left; }
100% { transform: scaleX(1); transform-origin: left; }
}
.hero__link--alt::after { background: var(--white); }
/* ---------- Plate ---------- */
.plate-stage {
position: relative;
justify-self: center;
width: clamp(320px, 42vw, 560px);
aspect-ratio: 1;
max-width: 100%;
}
.plate {
position: relative;
width: 100%; height: 100%;
border-radius: 50%;
overflow: hidden;
background: #f5e7d4;
box-shadow: 0 30px 60px rgba(0, 0, 0, 0.35),
inset 0 0 0 8px rgba(255, 255, 255, 0.5),
inset 0 0 0 9px rgba(0, 0, 0, 0.1);
will-change: transform;
}
.plate.is-fallback {
background: radial-gradient(circle at 30% 30%, #f5e7d4 40%, #e0c9a6 100%);
}
.plate.is-fallback::after {
content: '';
position: absolute;
inset: 20%;
border-radius: 50%;
background:
radial-gradient(circle at 40% 40%, #ffd788 0%, #f9a03f 50%, #a55f2a 100%);
box-shadow: inset 0 10px 30px rgba(0, 0, 0, 0.3);
}
.plate img {
width: 100%; height: 100%;
object-fit: cover;
user-select: none;
transition: transform 0.6s ease;
}
.plate:hover img { transform: scale(1.05) rotate(-3deg); }
.thumb-badge {
position: absolute;
top: 15%;
right: 5%;
width: 64px; height: 64px;
background: var(--red);
color: #fff;
border-radius: 50%;
display: inline-flex;
align-items: center;
justify-content: center;
text-decoration: none;
box-shadow: 0 10px 25px rgba(230, 57, 50, 0.4);
will-change: transform;
z-index: 4;
cursor: pointer;
}
.thumb-badge svg { width: 28px; height: 28px; }
/* ---------- Price block ---------- */
.price-block {
position: absolute;
bottom: 3rem;
right: 6rem;
z-index: 12;
display: flex;
flex-direction: column;
align-items: center;
gap: 1rem;
}
.price-block__price {
font-family: 'Playfair Display', serif;
font-weight: 800;
font-size: clamp(3rem, 5vw, 4.5rem);
line-height: 1;
color: var(--white);
display: inline-flex;
align-items: flex-start;
letter-spacing: -0.02em;
}
.price-block__price em {
font-size: 0.6em;
font-style: normal;
font-weight: 600;
color: rgba(255, 255, 255, 0.85);
margin-right: 0.05em;
margin-top: 0.1em;
}
.price-block__btn {
display: inline-flex;
align-items: center;
gap: 0.5rem;
color: var(--white);
text-decoration: none;
font-size: 0.9rem;
font-weight: 500;
padding: 0.5rem 0;
position: relative;
will-change: transform;
}
.price-block__btn::before {
content: '';
position: absolute;
left: 28px; right: 0; bottom: 0;
height: 2px;
background: var(--white);
transform-origin: left;
transition: transform 0.4s cubic-bezier(0.65, 0, 0.35, 1);
}
.price-block__btn:hover::before { transform: scaleX(1.1); }
.price-block__btn svg { width: 18px; height: 18px; }
/* ---------- Initial states ---------- */
[data-magnetic], [data-link], [data-fade], [data-title-line] .hero__char,
[data-script], [data-plate], [data-thumb], [data-price], [data-pagination],
[data-social], [data-social-ico] {
opacity: 0;
}
/* ---------- Responsive ---------- */
@media (max-width: 1100px) {
.hero { padding: 4rem 5rem 3rem; grid-template-columns: 1fr; gap: 1rem; }
.topnav { padding: 1rem 4rem; }
.hero__content { max-width: 100%; }
.plate-stage { width: 300px; margin: 0 auto; }
.price-block { right: 5rem; bottom: 2rem; }
.topnav__links { display: none; }
}
@media (max-width: 760px) {
.pagination { width: 40px; padding: 2rem 0; gap: 0.5rem; }
.pagination__nums { font-size: 0.75rem; margin-top: 3rem; }
.pagination__bar { height: 80px; bottom: 2rem; }
.social { width: 40px; gap: 1rem; }
.social__icon svg { width: 18px; height: 18px; }
.topnav { padding: 0.75rem 2.75rem; }
.hero { padding: 4rem 3rem; }
.hero__script { font-size: 1.5rem; }
.hero__title { font-size: clamp(1.5rem, 6vw, 2.5rem); }
.hero__desc { font-size: 0.75rem; }
.hero__links { gap: 1.25rem; flex-wrap: wrap; }
.plate-stage { width: 250px; }
.thumb-badge { width: 46px; height: 46px; top: 8%; right: 2%; }
.price-block { right: 3rem; bottom: 1.5rem; }
.price-block__price { font-size: 2.5rem; }
}
@media (max-width: 480px) {
.split--yellow { clip-path: polygon(50% 20%, 100% 0, 100% 100%, 0 100%); }
.pagination { display: none; }
.social { background: rgba(0, 0, 0, 0.15); }
.topnav { padding: 0.75rem 1rem; }
.topnav__logo-text { font-size: 1rem; }
.hero { padding: 3.5rem 2rem 5rem; }
.hero__desc { max-width: 100%; }
.plate-stage { width: 200px; }
.price-block { right: 1rem; left: auto; bottom: 1rem; flex-direction: row; gap: 0.75rem; }
.price-block__price { font-size: 2rem; }
}
</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 — Rasa</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&family=Playfair+Display:wght@500;700;800&family=Dancing+Script:wght@500;700&family=JetBrains+Mono:wght@500;600&display=swap" rel="stylesheet">
<link rel="stylesheet" href="banner-rasa.css">
</head>
<body>
<!-- ========== LOADER — Bowl + steam + noodle swirl ========== -->
<div class="loader" id="loader">
<div class="loader__scene">
<!-- Steam particles above bowl -->
<div class="loader__steam" id="loader-steam"></div>
<!-- Bowl -->
<svg class="loader__bowl" viewBox="0 0 220 180" aria-hidden="true">
<!-- Bowl outline -->
<ellipse cx="110" cy="78" rx="95" ry="18" fill="none" stroke="#f7b500" stroke-width="3"/>
<path d="M 15 78 Q 20 160 110 165 Q 200 160 205 78" fill="none" stroke="#f7b500" stroke-width="3" stroke-linecap="round"/>
<!-- Noodle swirl (animated) -->
<g id="noodles" transform="translate(110 90)">
<path d="M 0 -40 Q 40 -30 35 0 Q 30 35 -10 30 Q -45 20 -35 -15 Q -25 -45 10 -40"
fill="none" stroke="#ffde5c" stroke-width="4" stroke-linecap="round"/>
<path d="M -25 -25 Q 5 -30 15 -5 Q 20 20 -10 15"
fill="none" stroke="#ffc93d" stroke-width="3" stroke-linecap="round"/>
<path d="M 20 -20 Q 5 5 -20 -5"
fill="none" stroke="#ffb800" stroke-width="3" stroke-linecap="round"/>
</g>
<!-- Egg garnish -->
<circle cx="130" cy="75" r="14" fill="#fff3e0"/>
<circle cx="132" cy="72" r="6" fill="#f9b12f"/>
</svg>
</div>
<div class="loader__meta">
<span class="loader__label" id="loader-label">PREPARING YOUR DISH</span>
<span class="loader__counter" id="loader-counter">0%</span>
</div>
</div>
<section class="rasa" id="rasa">
<!-- Background split -->
<div class="split split--teal"></div>
<div class="split split--yellow"></div>
<!-- Paint texture overlay -->
<div class="split__texture"></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 40 40" aria-hidden="true">
<circle cx="20" cy="20" r="18" fill="none" stroke="#f7b500" stroke-width="2"/>
<path d="M11 20 Q 20 28 29 20" stroke="#fff" stroke-width="2" fill="none" stroke-linecap="round"/>
<circle cx="20" cy="15" r="1.5" fill="#f7b500"/>
</svg>
</span>
<span class="topnav__logo-text">Rasa<em>.</em></span>
</a>
<nav class="topnav__links">
<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>Menu</a>
<a href="#" data-magnetic data-link>Location</a>
<a href="#" data-magnetic data-link>Contact</a>
</nav>
<div class="topnav__actions">
<a href="#" class="topnav__icon" data-magnetic data-cart>
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8">
<path d="M6 6h15l-1.5 9h-12z"/>
<circle cx="10" cy="20" r="1.5"/>
<circle cx="18" cy="20" r="1.5"/>
<path d="M3 3h3l.5 3"/>
</svg>
<span class="topnav__badge" id="cart-count">1</span>
</a>
<button class="topnav__icon" data-magnetic id="search-btn" aria-label="Search">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8">
<circle cx="11" cy="11" r="7"/>
<path d="M20 20l-4-4" stroke-linecap="round"/>
</svg>
</button>
</div>
</header>
<!-- ========== LEFT PAGINATION ========== -->
<aside class="pagination" data-pagination>
<div class="pagination__bar"></div>
<div class="pagination__nums">
<span class="is-active" data-page="1">1</span>
<span data-page="2">2</span>
<span data-page="3">3</span>
<span data-page="4">4</span>
<span data-page="5">5</span>
</div>
</aside>
<!-- ========== RIGHT SOCIAL ========== -->
<aside class="social" data-social>
<a href="#" class="social__icon" data-social-ico aria-label="Facebook">
<svg viewBox="0 0 24 24" fill="currentColor"><path d="M13.5 21v-8h2.7l.4-3h-3.1V8c0-.9.3-1.5 1.5-1.5h1.6V3.8c-.3 0-1.2-.1-2.3-.1-2.3 0-3.9 1.4-3.9 4V10H7.8v3h2.5v8h3.2z"/></svg>
</a>
<a href="#" class="social__icon" data-social-ico aria-label="Instagram">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8">
<rect x="3" y="3" width="18" height="18" rx="5"/>
<circle cx="12" cy="12" r="4"/>
<circle cx="17" cy="7" r="0.8" fill="currentColor"/>
</svg>
</a>
<a href="#" class="social__icon" data-social-ico aria-label="Twitter">
<svg viewBox="0 0 24 24" fill="currentColor"><path d="M18.9 7.3c0 .2 0 .4 0 .5 0 5.4-4.1 11.6-11.6 11.6-2.3 0-4.5-.7-6.3-1.9.3 0 .6.1 1 .1 1.9 0 3.7-.7 5.1-1.8-1.8 0-3.3-1.2-3.9-2.9.3 0 .5.1.8.1.4 0 .7 0 1.1-.1-1.9-.4-3.4-2.1-3.4-4.2v-.1c.6.3 1.2.5 1.9.5C2.4 8.4 1.6 7 1.6 5.5c0-.8.2-1.5.6-2.1 2.1 2.5 5.1 4.1 8.6 4.3-.1-.3-.1-.6-.1-.9 0-2.3 1.8-4.1 4.1-4.1 1.2 0 2.3.5 3 1.3.9-.2 1.8-.5 2.6-1-.3 1-1 1.8-1.8 2.3.8-.1 1.6-.3 2.4-.6-.6.8-1.3 1.5-2.1 2.1z"/></svg>
</a>
<a href="#" class="social__icon" data-social-ico aria-label="WhatsApp">
<svg viewBox="0 0 24 24" fill="currentColor"><path d="M17.5 14.4c-.3-.1-1.8-.9-2-1s-.5-.1-.7.1c-.2.3-.8 1-.9 1.2-.2.2-.3.2-.6.1-.3-.1-1.3-.5-2.4-1.5-.9-.8-1.5-1.8-1.7-2.1-.2-.3 0-.5.1-.6l.4-.5c.1-.2.2-.3.3-.5.1-.2 0-.4 0-.5l-.9-2.2c-.2-.5-.5-.5-.7-.5H7.8c-.2 0-.6.1-.9.4s-1.1 1.1-1.1 2.7 1.2 3.1 1.3 3.3c.1.2 2.2 3.3 5.3 4.7 2.6 1.1 3.1 1 3.7.9.5-.1 1.7-.7 2-1.3.2-.6.2-1.1.2-1.2-.1-.2-.2-.2-.5-.3zM12 2C6.5 2 2 6.5 2 12c0 1.8.5 3.5 1.3 4.9L2 22l5.2-1.4c1.4.8 3 1.2 4.8 1.2 5.5 0 10-4.5 10-10S17.5 2 12 2z"/></svg>
</a>
</aside>
<!-- ========== MAIN CONTENT ========== -->
<div class="hero" id="hero">
<div class="hero__content">
<span class="hero__script" data-script>Made with Love</span>
<h1 class="hero__title" aria-label="Mie Goreng Jawa Telur Ceplok">
<span class="hero__line" data-title-line>Mie Goreng Jawa</span>
<span class="hero__line" data-title-line>Telur Ceplok</span>
</h1>
<p class="hero__desc" data-fade>
A hand-crafted savory noodle dish from the kitchens of Java,
served with a fried egg on top and fresh vegetable garnish.
Balanced sweet-savory flavors, simmered with house-made sauce.
</p>
<div class="hero__links" data-fade>
<a href="#" class="hero__link" data-magnetic>
<span>Get a Discount</span>
</a>
<a href="#" class="hero__link hero__link--alt" data-magnetic>
<span>See Other Menus</span>
</a>
</div>
</div>
<!-- Plate area -->
<div class="plate-stage" id="plate-stage">
<div class="plate" id="plate" data-plate>
<img src="https://images.unsplash.com/photo-1569718212165-3a8278d5f624?auto=format&fit=crop&w=900&q=80"
alt="Dish"
draggable="false"
onerror="this.parentElement.classList.add('is-fallback')">
</div>
<a href="#" class="thumb-badge" data-thumb>
<svg viewBox="0 0 24 24" fill="#fff">
<path d="M7 11v9H3v-9zm0-2V7l4-5 2 2-1 5h6a2 2 0 012 2l-2 7a2 2 0 01-2 2H7"/>
</svg>
</a>
</div>
<!-- Price + Add to cart -->
<div class="price-block" data-price>
<div class="price-block__price" id="price"><em>$</em><span id="price-num">19</span></div>
<a href="#" class="price-block__btn" data-magnetic data-add>
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8">
<path d="M6 6h15l-1.5 9h-12z"/>
<circle cx="10" cy="20" r="1.5"/>
<circle cx="18" cy="20" r="1.5"/>
</svg>
<span>Add to cart</span>
</a>
</div>
</div>
</section>
<script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.12.5/gsap.min.js"></script>
<script src="banner-rasa.js"></script>
</body>
</html>
</body>
</html>JS banner-rasa.js — GSAP animation timeline
(() => {
const root = document.getElementById('rasa');
if (!root) return;
// -------------------------------------------------------------------
// SPLIT TITLE
// -------------------------------------------------------------------
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 = ' '; }
else span.textContent = ch;
line.appendChild(span);
});
});
// -------------------------------------------------------------------
// BUILD LOADER STEAM PARTICLES
// -------------------------------------------------------------------
const steam = document.getElementById('loader-steam');
const STEAM_COUNT = 5;
for (let i = 0; i < STEAM_COUNT; i++) {
const s = document.createElement('span');
s.style.left = (10 + i * 14) + 'px';
steam.appendChild(s);
}
const steamEls = steam.querySelectorAll('span');
// -------------------------------------------------------------------
// REFERENCES
// -------------------------------------------------------------------
const loader = document.getElementById('loader');
const loaderCounter = document.getElementById('loader-counter');
const loaderLabel = document.getElementById('loader-label');
const noodles = document.getElementById('noodles');
const magnetics = root.querySelectorAll('[data-magnetic]');
const fades = root.querySelectorAll('[data-fade]');
const titleChars = root.querySelectorAll('.hero__char');
const scriptEl = root.querySelector('[data-script]');
const plate = root.querySelector('[data-plate]');
const plateStage = document.getElementById('plate-stage');
const thumb = root.querySelector('[data-thumb]');
const price = root.querySelector('[data-price]');
const priceNum = document.getElementById('price-num');
const pagination = root.querySelector('[data-pagination]');
const paginationNums = root.querySelectorAll('.pagination__nums span');
const social = root.querySelector('[data-social]');
const socialIcons = root.querySelectorAll('[data-social-ico]');
const logoMark = document.getElementById('logo-mark');
const cartEl = root.querySelector('[data-cart]');
const cartCount = document.getElementById('cart-count');
const addBtn = root.querySelector('[data-add]');
const splitTeal = root.querySelector('.split--teal');
const splitYellow = root.querySelector('.split--yellow');
// -------------------------------------------------------------------
// INITIAL STATES
// -------------------------------------------------------------------
gsap.set(splitTeal, { opacity: 0 });
gsap.set(splitYellow, { xPercent: 100, opacity: 1 });
gsap.set(magnetics, { y: -15, opacity: 0 });
gsap.set(fades, { y: 20, opacity: 0 });
gsap.set(scriptEl, { x: -30, opacity: 0 });
gsap.set(titleChars, { yPercent: 110, opacity: 0 });
gsap.set(plate, { scale: 0.4, opacity: 0, rotation: -30 });
gsap.set(thumb, { scale: 0, opacity: 0 });
gsap.set(price, { y: 30, opacity: 0 });
gsap.set(pagination, { x: -80, opacity: 0 });
gsap.set(social, { x: 60, opacity: 0 });
gsap.set(socialIcons, { x: 30, opacity: 0 });
// -------------------------------------------------------------------
// LOADER TIMELINE
// -------------------------------------------------------------------
const loaderTl = gsap.timeline({ onComplete: playScene });
// Steam rises continuously
steamEls.forEach((s, i) => {
gsap.fromTo(s,
{ y: 0, opacity: 0, scale: 0.8 },
{
y: -60,
opacity: 0.7,
scale: 1.8,
duration: 1.5,
repeat: -1,
delay: i * 0.25,
ease: 'power1.out',
keyframes: [
{ opacity: 0, y: 0, scale: 0.5, duration: 0 },
{ opacity: 0.7, y: -30, scale: 1.2, duration: 0.4 },
{ opacity: 0, y: -70, scale: 2, duration: 0.6 },
],
}
);
});
// Noodles rotate
gsap.to(noodles, {
rotation: 360,
duration: 6,
repeat: -1,
ease: 'none',
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 > 30 && loaderLabel.textContent === 'PREPARING YOUR DISH') loaderLabel.textContent = 'ADDING SPICES';
if (p.v > 65 && loaderLabel.textContent === 'ADDING SPICES') loaderLabel.textContent = 'PLATING UP';
if (p.v > 95 && loaderLabel.textContent === 'PLATING UP') loaderLabel.textContent = 'BON APPÉTIT';
},
});
// Exit: bowl scales up and fades
loaderTl.to([loaderCounter, loaderLabel], {
y: -10, opacity: 0, duration: 0.3, stagger: 0.05,
}, '+=0.3');
loaderTl.to('.loader__scene', {
scale: 1.3, opacity: 0, y: -20, duration: 0.6, ease: 'power3.in',
}, '-=0.3');
loaderTl.to(loader, {
opacity: 0, duration: 0.5, ease: 'power2.inOut',
}, '-=0.3');
loaderTl.set(loader, { display: 'none' });
// -------------------------------------------------------------------
// MAIN SCENE ENTRANCE
// -------------------------------------------------------------------
function playScene() {
const tl = gsap.timeline({ defaults: { ease: 'power3.out' } });
// Splits reveal
tl.to(splitTeal, { opacity: 1, duration: 0.5 }, 0);
tl.to(splitYellow, {
xPercent: 0,
duration: 1.2,
ease: 'power4.out',
}, 0.1);
// Nav
tl.to(magnetics, {
y: 0, opacity: 1,
duration: 0.6,
stagger: 0.05,
}, 0.5);
// Pagination slides in
tl.to(pagination, {
x: 0, opacity: 1,
duration: 0.8,
ease: 'power4.out',
}, 0.6);
// Social
tl.to(social, { x: 0, opacity: 1, duration: 0.5 }, 0.7);
tl.to(socialIcons, {
x: 0, opacity: 1,
duration: 0.5,
stagger: 0.08,
}, 0.85);
// Plate scales in with rotation
tl.to(plate, {
scale: 1, opacity: 1, rotation: 0,
duration: 1.3,
ease: 'back.out(1.4)',
}, 0.9);
// Thumb badge pops
tl.to(thumb, {
scale: 1, opacity: 1,
duration: 0.7,
ease: 'back.out(2)',
}, 1.4);
// Script text
tl.to(scriptEl, { x: 0, opacity: 1, duration: 0.7 }, 1);
// Title
tl.to(titleChars, {
yPercent: 0, opacity: 1,
duration: 1,
stagger: 0.02,
ease: 'expo.out',
}, 1.15);
// Fades
tl.to(fades, {
y: 0, opacity: 1,
duration: 0.6,
stagger: 0.12,
}, 1.5);
// Price
tl.to(price, { y: 0, opacity: 1, duration: 0.7 }, 1.7);
const priceObj = { v: 0 };
tl.to(priceObj, {
v: 19,
duration: 1,
snap: 'v',
ease: 'power2.out',
onUpdate: () => { priceNum.textContent = Math.round(priceObj.v); },
}, 1.7);
tl.call(startContinuous, null, 1.9);
tl.call(enableInteractions, null, 1.9);
}
// -------------------------------------------------------------------
// CONTINUOUS
// -------------------------------------------------------------------
function startContinuous() {
// Plate idle rotate
gsap.to(plate, {
rotation: 360,
duration: 80,
repeat: -1,
ease: 'none',
transformOrigin: 'center',
});
// Thumb idle bob
gsap.to(thumb, {
y: -6,
rotation: 5,
duration: 1.6,
yoyo: true,
repeat: -1,
ease: 'sine.inOut',
});
// Cart badge pulse
const badge = root.querySelector('.topnav__badge');
if (badge) gsap.to(badge, {
scale: 1.15,
duration: 1,
yoyo: true,
repeat: -1,
ease: 'sine.inOut',
});
}
// -------------------------------------------------------------------
// INTERACTIONS
// -------------------------------------------------------------------
function enableInteractions() {
// ---- Magnetic ----
magnetics.forEach((el) => {
const strength = el.classList.contains('price-block__btn') ? 0.3
: 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) * strength,
y: (e.clientY - cy) * strength,
duration: 0.4, ease: 'power3.out',
});
});
el.addEventListener('mouseleave', () => {
gsap.to(el, { x: 0, y: 0, duration: 0.6, ease: 'elastic.out(1, 0.4)' });
});
});
// ---- Logo: icon spins on hover ----
const logoEl = root.querySelector('.topnav__logo');
logoEl.addEventListener('mouseenter', () => {
gsap.to(logoMark.querySelector('svg'), { rotation: 360, duration: 0.8, ease: 'power3.inOut' });
});
logoEl.addEventListener('mouseleave', () => {
gsap.set(logoMark.querySelector('svg'), { rotation: 0 });
});
// ---- Title char proximity + 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 < 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: '#f5b900', duration: 0.2 }));
c.addEventListener('mouseleave', () => gsap.to(c, { color: '#ffffff', duration: 0.4 }));
});
// ---- Plate: hover tilt + spin faster ----
plate.addEventListener('mouseenter', () => {
gsap.to(plate, {
scale: 1.04,
duration: 0.4,
ease: 'back.out(2)',
});
});
plate.addEventListener('mouseleave', () => {
gsap.to(plate, { scale: 1, duration: 0.5, ease: 'elastic.out(1, 0.4)' });
});
// ---- Thumb badge: click = bounce ----
thumb.addEventListener('click', (e) => {
e.preventDefault();
gsap.fromTo(thumb,
{ scale: 1 },
{ scale: 1.3, duration: 0.2, yoyo: true, repeat: 1, ease: 'back.out(2)' }
);
// Heart burst
const rect = thumb.getBoundingClientRect();
const cx = rect.left + rect.width / 2;
const cy = rect.top + rect.height / 2;
for (let i = 0; i < 6; i++) {
const h = document.createElement('span');
h.textContent = '\u2764\uFE0F';
h.style.cssText = `position:fixed;left:${cx}px;top:${cy}px;font-size:18px;pointer-events:none;z-index:200;transform:translate(-50%,-50%);`;
document.body.appendChild(h);
gsap.fromTo(h,
{ x: 0, y: 0, opacity: 1, scale: 0.5 },
{
x: (Math.random() - 0.5) * 120,
y: -80 - Math.random() * 60,
opacity: 0,
scale: 1.2,
duration: 0.9,
ease: 'power2.out',
onComplete: () => h.remove(),
}
);
}
});
// ---- Pagination ----
paginationNums.forEach((num) => {
num.addEventListener('click', () => {
paginationNums.forEach(n => n.classList.remove('is-active'));
num.classList.add('is-active');
// Plate flip
gsap.fromTo(plate,
{ rotationY: 0 },
{ rotationY: 360, duration: 0.9, ease: 'power3.inOut', transformPerspective: 1000 }
);
});
});
// ---- Social icons: spring + glow ----
socialIcons.forEach((s) => {
s.addEventListener('mouseenter', () => {
gsap.to(s, { scale: 1.2, rotation: -8, duration: 0.3, ease: 'back.out(2)' });
});
s.addEventListener('mouseleave', () => {
gsap.to(s, { scale: 1, rotation: 0, duration: 0.5, ease: 'elastic.out(1, 0.4)' });
});
});
// ---- Cart click = bump ----
cartEl.addEventListener('click', (e) => {
e.preventDefault();
const badge = cartEl.querySelector('.topnav__badge');
gsap.fromTo(badge, { scale: 1 }, { scale: 1.6, duration: 0.15, yoyo: true, repeat: 1, ease: 'back.out(2.5)' });
});
// ---- Add to cart: food emoji burst + count up ----
addBtn.addEventListener('click', (e) => {
e.preventDefault();
const rect = addBtn.getBoundingClientRect();
const cx = rect.left + rect.width / 2;
const cy = rect.top + rect.height / 2;
const emojis = ['\u{1F35C}', '\u{1F35B}', '\u{1F373}', '\u{1F961}', '\u2728'];
for (let i = 0; i < 8; i++) {
const e2 = document.createElement('span');
e2.textContent = emojis[i % emojis.length];
e2.style.cssText = `position:fixed;left:${cx}px;top:${cy}px;font-size:22px;pointer-events:none;z-index:200;transform:translate(-50%,-50%);`;
document.body.appendChild(e2);
const cartRect = cartEl.getBoundingClientRect();
const tx = cartRect.left + cartRect.width / 2 - cx;
const ty = cartRect.top + cartRect.height / 2 - cy;
gsap.fromTo(e2,
{ x: 0, y: 0, opacity: 1, scale: 1 },
{
x: tx + (Math.random() - 0.5) * 60,
y: ty + (Math.random() - 0.5) * 40,
opacity: 0,
scale: 0.4,
duration: 0.9 + Math.random() * 0.3,
ease: 'power2.in',
delay: i * 0.05,
onComplete: () => e2.remove(),
}
);
}
// Increment cart
const current = parseInt(cartCount.textContent, 10) || 0;
const target = current + 1;
const obj = { v: current };
gsap.to(obj, {
v: target,
duration: 0.5,
delay: 0.7,
snap: 'v',
ease: 'back.out(2)',
onUpdate: () => cartCount.textContent = Math.round(obj.v),
});
// Cart bump
gsap.fromTo(cartEl.querySelector('.topnav__badge'),
{ scale: 1 },
{ scale: 1.6, duration: 0.2, delay: 0.9, yoyo: true, repeat: 1, ease: 'back.out(2.5)' }
);
});
// ---- Plate stage parallax ----
let px = 0, py = 0, pcx = 0, pcy = 0;
plateStage.addEventListener('mousemove', (e) => {
const r = plateStage.getBoundingClientRect();
px = ((e.clientX - r.left) / r.width - 0.5) * 2;
py = ((e.clientY - r.top) / r.height - 0.5) * 2;
});
plateStage.addEventListener('mouseleave', () => { px = 0; py = 0; });
gsap.ticker.add(() => {
pcx += (px - pcx) * 0.08;
pcy += (py - pcy) * 0.08;
gsap.set(thumb, { x: pcx * 20, y: pcy * 14 });
});
}
})();