ToolsWaves
Free GSAP Landing Page Template · ToolsWaves · Food & Restaurant

Free Food Restaurant Hero Template with Steaming Bowl Loader

Free food and restaurant landing page template from ToolsWaves with a noodle-bowl loader emitting steam, a rotating plate with thumb-up badge, vertical pagination, social rails, and a heart burst on thumb interactions. Free for any project.

FoodRestaurantPlateSplit LayoutHover Effects

About this ToolsWaves template

This free food and restaurant landing page template from ToolsWaves opens with a noodle-bowl loader emitting animated steam — a fitting tone-setter for any food-focused site. The split layout combines a teal and yellow palette with a rotating plate of food and a thumb-up badge that animates with playful warmth. Interactive touches include a heart burst when you tap the thumb-up, an emoji that animates from the dish to the cart on the Add button, and vertical pagination with social rails along the side.

Use this template for restaurant chains, food delivery apps, cooking platforms, or any culinary-adjacent business. The free template suits both fast-casual brands and upscale culinary platforms because the structure is solid even when the playful elements are toned down for a different brand voice. As with all ToolsWaves templates, copy the HTML, CSS, and JS files into your project and rebrand at your own pace. No fees, no sign-up, no time-limited license.

Copy the code

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8" />
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
  <title>Free 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>
JSbanner-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 = '&nbsp;'; }
            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 });
        });
    }
})();

More GSAP landing pages