Build an Animated Landing Page

Use Claude Code to build a premium dark-themed animated agency website.

Introduction

30 Second Summary

You have probably scrolled through a beautifully animated website and assumed it cost tens of thousands of dollars to build. The smooth scroll effects, frosted-glass cards, and bold typography all feel out of reach without a professional design team.

In this project, you will use Claude Code to build a premium animated landing page from a single detailed prompt. The page features scroll-triggered animations, glassmorphism effects, and oversized typography that looks like a $10,000 agency website.

What You'll Build

A single-page animated landing page for a fictional creative agency called NOIR Studio, featuring a dark theme with electric blue accents, GSAP-powered scroll animations, glassmorphism cards, and a parallax background effect.

By the end of this project, you'll have:

  • A complete index.html file with embedded CSS and JavaScript that opens directly in your browser with no build tools required.
  • Scroll-triggered animations powered by GSAP ScrollTrigger including letter-by-letter text reveals, staggered card entrances, and counting number stats.
  • Premium design patterns including dark-theme UI, glassmorphism cards with glowing hover effects, gradient accents, parallax motion, and bold typography.
  • Secret Mission: Add a dark/light theme toggle with animated transition and localStorage persistence.

Are there any prerequisites?

You need Claude Code installed with a Pro subscription and basic familiarity with opening Terminal on macOS. No HTML, CSS, or JavaScript experience is required.

Before We Start

Before We Start...

Set Up Your Project Folder

Before we can build a premium animated landing page, we need a dedicated folder for our project and a working connection to Claude Code.

Since Claude Code is already installed on your Mac, setup is quick. You will create a project folder, confirm Claude Code responds, and understand the workflow for the rest of the project.

In this step, get ready to:
  • Create a project folder on your Desktop.
  • Verify Claude Code launches and responds.
  • Understand the prompt-to-website workflow.
Create the project directory
  • Press Cmd+Space (macOS) or the Windows key (Windows) to open your search bar.
  • Type Terminal and press Enter to open it.
  • Move to your Desktop by running this command:
cd ~/Desktop
  • Create your project folder and move into it by running this command:
mkdir noir-studio && cd noir-studio
  • Confirm you are inside the new folder by running:
pwd

You should see a path ending in noir-studio, such as /Users/yourname/Desktop/noir-studio.

Seeing 'No such file or directory'?

Make sure you ran cd ~/Desktop first. If your Desktop path is different, try running ls ~/Desktop to confirm it exists.

help me figure out why my mkdir command failed in Terminal

Verify Claude Code is ready

Claude Code is the AI tool that will generate your entire website from a single prompt. Let's confirm it launches correctly.

  • Start a Claude Code interactive session by running:
claude

You should see the Claude Code prompt appear, ready for input. Since you already have a Pro subscription, you should be logged in automatically.

  • Leave the session for now by typing:
/exit

You should be back at your regular Terminal prompt. You will re-enter Claude Code with the full mega-prompt in the next step.

Claude Code not responding?

Make sure you have an active internet connection. Claude Code requires a network connection to communicate with Anthropic's servers.

If you see an authentication error, confirm your Pro subscription is active by visiting your Anthropic account settings in a browser.

help me troubleshoot why Claude Code is not launching

Understand the workflow

The rest of this project follows a simple three-part workflow. You give Claude Code one detailed prompt, it generates the entire website as a single file, and you open that file in your browser.

How does the workflow look in practice?

  • You run claude to start an interactive session inside the noir-studio folder.
  • You paste a detailed prompt describing the website. Claude Code creates an index.html file with all the HTML, CSS, and JavaScript embedded.
  • You run open index.html in Terminal to view the finished site in your default browser. No server needed.

Why a single HTML file?

Keeping everything in one file means zero build tools, zero dependencies, and zero framework setup. You get a complete website that opens in any browser instantly. This is how many premium agency prototypes start.

Your project folder is ready and Claude Code is confirmed working. Next up, you will craft the mega-prompt that builds your entire premium landing page in one shot.

Craft and Run the Mega-Prompt

Your project folder is ready. Now comes the main event.

You'll give Claude Code a single, highly detailed prompt that specifies exact colors, fonts, animations, and layout. This level of specificity is what separates a generic AI-generated page from one that looks like a $10,000 professional agency build.

In this step, get ready to:
  • Launch Claude Code and paste the mega-prompt.
  • Wait for the complete index.html file to generate.
  • Open the finished website in your browser and see all the animations in action.
Start Claude Code and paste the mega-prompt

The workflow is simple: give Claude Code one detailed prompt, and it generates the entire website as an index.html file.

  • In your Terminal (still in the noir-studio directory), start a Claude Code session by running:
claude

You should see the Claude Code prompt appear, ready for your input.

  • Paste the following mega-prompt into your Claude Code session:
Build a single-file index.html for a premium creative agency landing page called "NOIR Studio". This should look like a $10,000 website with complicated UI/UX design, animations, and motions. Here are the exact specifications:

THEME AND COLORS:
- Dark background: #0a0a0f
- Card/surface background: rgba(255, 255, 255, 0.03)
- Primary accent: #4f46e5 (electric indigo)
- Secondary accent: #06b6d4 (cyan)
- Text: #ffffff for headings, #94a3b8 for body text
- Gradient accents: linear-gradient from #4f46e5 to #06b6d4

TYPOGRAPHY (use Google Fonts):
- Headings: 'Syne', sans-serif (weights 700 and 800)
- Body: 'Inter', sans-serif (weights 300 and 400)
- Hero headline should be 6rem on desktop, 3rem on mobile

STRUCTURE (sections in order):
1. NAVIGATION - Fixed/sticky transparent nav that becomes solid on scroll. Logo "NOIR" on left, menu links (Work, Services, About, Contact) on right. Add a subtle backdrop-filter blur.

2. HERO SECTION - Full viewport height. Oversized headline "We Create Digital Experiences" with a subtitle "Award-winning studio crafting premium digital products for ambitious brands." Add a scroll-down indicator arrow that bounces.

3. SERVICES SECTION - Heading "What We Do" with 3 glassmorphism cards in a row (stacks on mobile):
   - Card 1: "Brand Strategy" - "We build brands that resonate and endure."
   - Card 2: "Digital Design" - "Pixel-perfect interfaces that convert."
   - Card 3: "Development" - "Performant code that scales."
   Each card should have a glowing border effect on hover (the accent gradient as a border).

4. FEATURED WORK SECTION - Heading "Selected Work" with 4 project cards in a 2x2 grid. Each card is a colored gradient rectangle (use different gradient combos) with a project name and category overlaid. Cards: "Lumina App" (Mobile Design), "Vertex AI" (Brand Identity), "Onyx Bank" (Web Platform), "Prism Studio" (Creative Direction).

5. STATS SECTION - A horizontal row of 4 stats with large numbers that count up when scrolled into view: "150+" (Projects), "12" (Awards), "8" (Years), "50+" (Clients).

6. CONTACT/FOOTER - Heading "Let's Work Together" with a subtitle, a large CTA button with hover animation, and footer with copyright text.

ANIMATIONS (use GSAP loaded from CDN: https://cdn.jsdelivr.net/npm/gsap@3.15.0/dist/gsap.min.js and https://cdn.jsdelivr.net/npm/gsap@3.15.0/dist/ScrollTrigger.min.js):

- Hero text: Split the headline into individual characters using spans, then animate them in with stagger (opacity 0 to 1, y: 50 to 0, stagger: 0.03, ease: "power3.out")
- Hero subtitle: Fade in and slide up after headline finishes
- Navigation: On scroll past 100px, add a solid background with transition
- Services cards: Animate in from below (y: 80, opacity: 0) with ScrollTrigger, stagger: 0.2
- Featured work cards: Animate in with stagger from left (x: -60, opacity: 0), triggered on scroll
- Stats numbers: Count up from 0 to their value when scrolled into view using GSAP
- Scroll indicator: Infinite bounce animation using GSAP yoyo

GLASSMORPHISM CARDS:
- background: rgba(255, 255, 255, 0.03)
- backdrop-filter: blur(10px)
- border: 1px solid rgba(255, 255, 255, 0.08)
- border-radius: 16px
- On hover: border changes to the gradient accent color with a subtle outer glow (box-shadow with the accent color at low opacity)

RESPONSIVE:
- Mobile-first approach
- Services cards stack vertically on screens under 768px
- Work grid goes to single column on mobile
- Navigation collapses to a simpler layout on mobile
- Hero text scales down appropriately

IMPORTANT:
- Everything in ONE index.html file (CSS in style tag, JS in script tag)
- Load Google Fonts via link tag in head
- Load GSAP and ScrollTrigger via script tags from CDN
- Make sure ScrollTrigger is registered: gsap.registerPlugin(ScrollTrigger)
- Use smooth scrolling on the html element
- Add cursor: pointer on interactive elements
- All animations should be smooth and performant (use transform and opacity only)

What makes this prompt effective?

This prompt succeeds because it specifies exact values instead of vague descriptions. It gives precise hex codes, font weights, animation easing curves, and pixel breakpoints.

The more specific you are with Claude Code, the less guesswork it does. "Electric indigo #4f46e5" beats "use a nice blue" every time.

What is GSAP?

GSAP (GreenSock Animation Platform) is the industry standard animation library used by agencies like Apple and Stripe for premium web experiences. It's 100% free and loaded directly from a CDN, so no installation is needed.

ScrollTrigger is a GSAP plugin that fires animations when elements scroll into view. This is what makes sections "reveal" as you scroll down the page.

Wait for Claude Code to generate the file

After you press Enter, Claude Code reads the entire prompt and starts generating your index.html file. This typically takes 30-60 seconds.

Permissions prompt

Claude Code may ask for permission to create or write files in your directory. If you see a prompt asking to allow file creation, approve it so it can save index.html.

Once done, Claude Code confirms the file has been created. You should see a message indicating it wrote index.html to your noir-studio directory.

Open the website in your browser
  • Exit the Claude Code session by typing:
/exit
  • Open your new landing page in your default browser by running:
open index.html
  • Scroll slowly from top to bottom to watch the animations fire.

You should see a dark landing page with "We Create Digital Experiences" animating in character by character. As you scroll, the service cards fade up, the work cards stagger in from the left, and the stats numbers count up.

What should I see?

  • A full-screen hero with a bouncing scroll indicator at the bottom.
  • Three frosted-glass service cards under "What We Do" that glow on hover.
  • A 2x2 grid of colorful gradient project cards under "Selected Work."
  • Four stats (150+, 12, 8, 50+) that count up from zero when scrolled into view.
  • A "Get In Touch" button with a hover lift effect at the bottom.

Page looks blank or unstyled?

  • Make sure you ran open index.html from inside the noir-studio directory where the file was created.
  • Confirm the file exists by running ls in your Terminal. You should see index.html listed.
  • If the page loads but animations don't play, check that you have an internet connection. GSAP loads from a CDN, so it needs network access.

My NOIR Studio page isn't loading or looks broken. Help me debug what went wrong with the generated index.html file.

✔️ Awesome, my page looks great!

Excellent. Your Claude Code-generated landing page is working perfectly. Make sure you have saved the file (Claude Code does this automatically).

ⓧ I'd like to double check the full code

Since Claude Code generates the file, your output may differ slightly in formatting. The key thing is that all sections, animations, and styles are present. Here is the reference version:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>NOIR Studio | Premium Digital Agency</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@300;400&family=Syne:wght@700;800&display=swap" rel="stylesheet">
    <style>
        * {
            margin: 0;
            padding: 0;
            box-sizing: border-box;
        }

        html {
            scroll-behavior: smooth;
        }

        body {
            background-color: #0a0a0f;
            color: #ffffff;
            font-family: 'Inter', sans-serif;
            font-weight: 300;
            overflow-x: hidden;
        }

        /* Navigation */
        .nav {
            position: fixed;
            top: 0;
            left: 0;
            width: 100%;
            padding: 1.5rem 4rem;
            display: flex;
            justify-content: space-between;
            align-items: center;
            z-index: 1000;
            transition: background-color 0.3s ease, backdrop-filter 0.3s ease;
        }

        .nav.scrolled {
            background-color: rgba(10, 10, 15, 0.9);
            backdrop-filter: blur(20px);
            border-bottom: 1px solid rgba(255, 255, 255, 0.05);
        }

        .nav-logo {
            font-family: 'Syne', sans-serif;
            font-weight: 800;
            font-size: 1.5rem;
            letter-spacing: 0.1em;
        }

        .nav-links {
            display: flex;
            gap: 2.5rem;
            list-style: none;
        }

        .nav-links a {
            color: #94a3b8;
            text-decoration: none;
            font-size: 0.9rem;
            font-weight: 400;
            position: relative;
            transition: color 0.3s ease;
            cursor: pointer;
        }

        .nav-links a::after {
            content: '';
            position: absolute;
            bottom: -4px;
            left: 0;
            width: 100%;
            height: 1px;
            background: linear-gradient(90deg, #4f46e5, #06b6d4);
            transform: scaleX(0);
            transform-origin: left;
            transition: transform 0.3s ease;
        }

        .nav-links a:hover {
            color: #ffffff;
        }

        .nav-links a:hover::after {
            transform: scaleX(1);
        }

        /* Hero Section */
        .hero {
            height: 100vh;
            display: flex;
            flex-direction: column;
            justify-content: center;
            align-items: center;
            text-align: center;
            padding: 0 2rem;
            position: relative;
            overflow: hidden;
        }

        .hero-title {
            font-family: 'Syne', sans-serif;
            font-weight: 800;
            font-size: 6rem;
            line-height: 1.05;
            margin-bottom: 1.5rem;
            max-width: 900px;
        }

        .hero-title .char {
            display: inline-block;
            opacity: 0;
        }

        .hero-subtitle {
            font-size: 1.25rem;
            color: #94a3b8;
            max-width: 600px;
            line-height: 1.7;
            opacity: 0;
        }

        .scroll-indicator {
            position: absolute;
            bottom: 3rem;
            left: 50%;
            transform: translateX(-50%);
            display: flex;
            flex-direction: column;
            align-items: center;
            gap: 0.5rem;
            color: #94a3b8;
            font-size: 0.75rem;
            letter-spacing: 0.15em;
            text-transform: uppercase;
        }

        .scroll-indicator .arrow {
            width: 1px;
            height: 40px;
            background: linear-gradient(to bottom, #4f46e5, transparent);
        }

        /* Sections shared */
        section {
            padding: 8rem 4rem;
        }

        .section-title {
            font-family: 'Syne', sans-serif;
            font-weight: 700;
            font-size: 3rem;
            margin-bottom: 4rem;
            text-align: center;
        }

        /* Services Section */
        .services-grid {
            display: grid;
            grid-template-columns: repeat(3, 1fr);
            gap: 2rem;
            max-width: 1200px;
            margin: 0 auto;
        }

        .service-card {
            background: rgba(255, 255, 255, 0.03);
            backdrop-filter: blur(10px);
            border: 1px solid rgba(255, 255, 255, 0.08);
            border-radius: 16px;
            padding: 3rem 2rem;
            text-align: center;
            transition: border-color 0.3s ease, box-shadow 0.3s ease;
            opacity: 0;
            cursor: pointer;
        }

        .service-card:hover {
            border-color: #4f46e5;
            box-shadow: 0 0 30px rgba(79, 70, 229, 0.15), 0 0 60px rgba(6, 182, 212, 0.05);
        }

        .service-card h3 {
            font-family: 'Syne', sans-serif;
            font-weight: 700;
            font-size: 1.5rem;
            margin-bottom: 1rem;
        }

        .service-card p {
            color: #94a3b8;
            line-height: 1.7;
        }

        /* Work Section */
        .work-grid {
            display: grid;
            grid-template-columns: repeat(2, 1fr);
            gap: 2rem;
            max-width: 1200px;
            margin: 0 auto;
        }

        .work-card {
            border-radius: 16px;
            padding: 3rem;
            display: flex;
            flex-direction: column;
            justify-content: flex-end;
            min-height: 300px;
            position: relative;
            overflow: hidden;
            opacity: 0;
            cursor: pointer;
            transition: transform 0.3s ease;
        }

        .work-card:hover {
            transform: scale(1.02);
        }

        .work-card:nth-child(1) {
            background: linear-gradient(135deg, #1e1b4b, #4f46e5);
        }

        .work-card:nth-child(2) {
            background: linear-gradient(135deg, #042f2e, #06b6d4);
        }

        .work-card:nth-child(3) {
            background: linear-gradient(135deg, #1c1917, #f97316);
        }

        .work-card:nth-child(4) {
            background: linear-gradient(135deg, #2e1065, #a855f7);
        }

        .work-card h3 {
            font-family: 'Syne', sans-serif;
            font-weight: 700;
            font-size: 1.75rem;
            margin-bottom: 0.5rem;
        }

        .work-card span {
            color: rgba(255, 255, 255, 0.7);
            font-size: 0.9rem;
        }

        /* Stats Section */
        .stats-grid {
            display: grid;
            grid-template-columns: repeat(4, 1fr);
            gap: 2rem;
            max-width: 1000px;
            margin: 0 auto;
            text-align: center;
        }

        .stat-item .stat-number {
            font-family: 'Syne', sans-serif;
            font-weight: 800;
            font-size: 3.5rem;
            background: linear-gradient(135deg, #4f46e5, #06b6d4);
            -webkit-background-clip: text;
            -webkit-text-fill-color: transparent;
            background-clip: text;
        }

        .stat-item .stat-label {
            color: #94a3b8;
            font-size: 0.9rem;
            margin-top: 0.5rem;
            text-transform: uppercase;
            letter-spacing: 0.1em;
        }

        /* Contact Section */
        .contact {
            text-align: center;
            padding: 10rem 4rem;
        }

        .contact .section-title {
            margin-bottom: 1.5rem;
        }

        .contact-subtitle {
            color: #94a3b8;
            font-size: 1.1rem;
            margin-bottom: 3rem;
            max-width: 500px;
            margin-left: auto;
            margin-right: auto;
        }

        .cta-button {
            display: inline-block;
            padding: 1.25rem 3rem;
            background: linear-gradient(135deg, #4f46e5, #06b6d4);
            color: #ffffff;
            text-decoration: none;
            font-family: 'Syne', sans-serif;
            font-weight: 700;
            font-size: 1rem;
            border-radius: 60px;
            border: none;
            cursor: pointer;
            transition: transform 0.3s ease, box-shadow 0.3s ease;
        }

        .cta-button:hover {
            transform: translateY(-3px);
            box-shadow: 0 20px 40px rgba(79, 70, 229, 0.3);
        }

        /* Footer */
        .footer {
            text-align: center;
            padding: 2rem 4rem;
            border-top: 1px solid rgba(255, 255, 255, 0.05);
            color: #94a3b8;
            font-size: 0.85rem;
        }

        /* Responsive */
        @media (max-width: 768px) {
            .nav {
                padding: 1rem 2rem;
            }

            .nav-links {
                gap: 1.5rem;
            }

            .hero-title {
                font-size: 3rem;
            }

            .hero-subtitle {
                font-size: 1rem;
            }

            section {
                padding: 5rem 2rem;
            }

            .section-title {
                font-size: 2rem;
            }

            .services-grid {
                grid-template-columns: 1fr;
            }

            .work-grid {
                grid-template-columns: 1fr;
            }

            .stats-grid {
                grid-template-columns: repeat(2, 1fr);
            }
        }
    </style>
</head>
<body>
    <!-- Navigation -->
    <nav class="nav" id="nav">
        <div class="nav-logo">NOIR</div>
        <ul class="nav-links">
            <li><a href="#work">Work</a></li>
            <li><a href="#services">Services</a></li>
            <li><a href="#about">About</a></li>
            <li><a href="#contact">Contact</a></li>
        </ul>
    </nav>

    <!-- Hero Section -->
    <section class="hero" id="hero">
        <h1 class="hero-title"></h1>
        <p class="hero-subtitle">Award-winning studio crafting premium digital products for ambitious brands.</p>
        <div class="scroll-indicator">
            <span>Scroll</span>
            <div class="arrow"></div>
        </div>
    </section>

    <!-- Services Section -->
    <section id="services">
        <h2 class="section-title">What We Do</h2>
        <div class="services-grid">
            <div class="service-card">
                <h3>Brand Strategy</h3>
                <p>We build brands that resonate and endure.</p>
            </div>
            <div class="service-card">
                <h3>Digital Design</h3>
                <p>Pixel-perfect interfaces that convert.</p>
            </div>
            <div class="service-card">
                <h3>Development</h3>
                <p>Performant code that scales.</p>
            </div>
        </div>
    </section>

    <!-- Work Section -->
    <section id="work">
        <h2 class="section-title">Selected Work</h2>
        <div class="work-grid">
            <div class="work-card">
                <h3>Lumina App</h3>
                <span>Mobile Design</span>
            </div>
            <div class="work-card">
                <h3>Vertex AI</h3>
                <span>Brand Identity</span>
            </div>
            <div class="work-card">
                <h3>Onyx Bank</h3>
                <span>Web Platform</span>
            </div>
            <div class="work-card">
                <h3>Prism Studio</h3>
                <span>Creative Direction</span>
            </div>
        </div>
    </section>

    <!-- Stats Section -->
    <section id="about">
        <div class="stats-grid">
            <div class="stat-item">
                <div class="stat-number" data-target="150">0</div>
                <div class="stat-label">Projects</div>
            </div>
            <div class="stat-item">
                <div class="stat-number" data-target="12">0</div>
                <div class="stat-label">Awards</div>
            </div>
            <div class="stat-item">
                <div class="stat-number" data-target="8">0</div>
                <div class="stat-label">Years</div>
            </div>
            <div class="stat-item">
                <div class="stat-number" data-target="50">0</div>
                <div class="stat-label">Clients</div>
            </div>
        </div>
    </section>

    <!-- Contact Section -->
    <section class="contact" id="contact">
        <h2 class="section-title">Let's Work Together</h2>
        <p class="contact-subtitle">Have a project in mind? We'd love to hear about it.</p>
        <a href="mailto:hello@noir.studio" class="cta-button">Get In Touch</a>
    </section>

    <!-- Footer -->
    <footer class="footer">
        <p>2026 NOIR Studio. All rights reserved.</p>
    </footer>

    <!-- GSAP -->
    <script src="https://cdn.jsdelivr.net/npm/gsap@3.15.0/dist/gsap.min.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/gsap@3.15.0/dist/ScrollTrigger.min.js"></script>
    <script>
        gsap.registerPlugin(ScrollTrigger);

        // Split hero title into characters
        const heroTitle = document.querySelector('.hero-title');
        const titleText = "We Create Digital Experiences";
        titleText.split('').forEach(char => {
            const span = document.createElement('span');
            span.classList.add('char');
            span.textContent = char === ' ' ? '\u00A0' : char;
            heroTitle.appendChild(span);
        });

        // Animate hero characters
        gsap.to('.hero-title .char', {
            opacity: 1,
            y: 0,
            duration: 0.8,
            stagger: 0.03,
            ease: "power3.out",
            delay: 0.5
        });

        // Set initial state for chars
        gsap.set('.hero-title .char', { opacity: 0, y: 50 });

        // Animate hero subtitle
        gsap.to('.hero-subtitle', {
            opacity: 1,
            y: 0,
            duration: 1,
            ease: "power2.out",
            delay: 1.5
        });

        gsap.set('.hero-subtitle', { opacity: 0, y: 30 });

        // Scroll indicator bounce
        gsap.to('.scroll-indicator', {
            y: 10,
            duration: 1.5,
            ease: "power1.inOut",
            yoyo: true,
            repeat: -1
        });

        // Navigation scroll effect
        window.addEventListener('scroll', () => {
            const nav = document.getElementById('nav');
            if (window.scrollY > 100) {
                nav.classList.add('scrolled');
            } else {
                nav.classList.remove('scrolled');
            }
        });

        // Service cards animation
        gsap.from('.service-card', {
            scrollTrigger: {
                trigger: '.services-grid',
                start: 'top 80%'
            },
            y: 80,
            opacity: 0,
            duration: 0.8,
            stagger: 0.2,
            ease: "power2.out",
            onComplete: () => {
                gsap.set('.service-card', { opacity: 1 });
            }
        });

        // Work cards animation
        gsap.from('.work-card', {
            scrollTrigger: {
                trigger: '.work-grid',
                start: 'top 80%'
            },
            x: -60,
            opacity: 0,
            duration: 0.8,
            stagger: 0.15,
            ease: "power2.out",
            onComplete: () => {
                gsap.set('.work-card', { opacity: 1 });
            }
        });

        // Stats counter animation
        const statNumbers = document.querySelectorAll('.stat-number');
        statNumbers.forEach(stat => {
            const target = parseInt(stat.getAttribute('data-target'));
            ScrollTrigger.create({
                trigger: stat,
                start: 'top 85%',
                onEnter: () => {
                    gsap.to(stat, {
                        duration: 2,
                        ease: "power1.out",
                        onUpdate: function() {
                            const progress = this.progress();
                            const current = Math.round(target * progress);
                            stat.textContent = current + (target >= 50 ? '+' : '');
                        }
                    });
                },
                once: true
            });
        });

        // Smooth scroll for nav links
        document.querySelectorAll('.nav-links a').forEach(link => {
            link.addEventListener('click', (e) => {
                e.preventDefault();
                const targetId = link.getAttribute('href');
                const targetSection = document.querySelector(targetId);
                if (targetSection) {
                    targetSection.scrollIntoView({ behavior: 'smooth' });
                }
            });
        });
    </script>
</body>
</html>

Your landing page is alive. Next up, you'll dig into the code to understand exactly how the premium design patterns and animations work together.

Review and Understand What Was Built

Your landing page is live in the browser. Claude Code generated hundreds of lines of HTML, CSS, and JavaScript from a single prompt.

In this step, you will explore how each animation fires and then inspect the code to understand the premium design patterns that make this page look like a $10,000 agency site.

In this step, get ready to:
  • Scroll through the page and observe every animation in action.
  • Inspect the code structure and identify the three main sections.
  • Understand the premium design patterns that create a high-end look.
Explore the animations

Premium agency websites use motion to guide the viewer's eye and create a sense of polish. Your page has multiple animation types that fire at different moments.

  • Switch back to your browser where the NOIR Studio page is open.
  • Watch the hero section on page load. The headline "We Create Digital Experiences" animates in character by character, followed by the subtitle fading in.
  • Scroll down slowly past the hero. Notice the navigation bar becomes solid with a blur effect as soon as you scroll past the top.
  • Continue scrolling to the "What We Do" section. The three service cards fade in from below, one after another.
  • Scroll to "Selected Work". The project cards stagger in from the left side.
  • Scroll to the stats section. Watch the numbers count up from 0 to their final values (150+, 12, 8, 50+).
  • Scroll back up to the service cards and hover over each one. You should see a glowing indigo border and subtle outer shadow appear.

What triggers each animation?

The hero text and subtitle animate on page load with a timed delay. Every other animation uses ScrollTrigger, which watches the viewport and fires when each section scrolls into view.

The glow effect on the service cards is pure CSS. The :hover pseudo-class changes the border color and adds a box-shadow with the accent color at low opacity.

Inspect the code structure

The entire website lives in a single index.html file. This is a common pattern for premium landing pages that load fast and require no build tools.

  • Return to your Terminal and view the file contents by running:
cat index.html

The output is long, but you can see the overall shape. The file has three major sections stacked inside the <body> tag.

  • Scroll up in your Terminal output to identify the three parts:
  • The <style> tag near the top contains all the CSS (colors, layout, responsive breakpoints, hover effects).
  • The HTML structure in the middle defines the page content (navigation, hero, services, work, stats, contact, footer).
  • The <script> tag at the bottom contains all the JavaScript animation logic.

How does GSAP get loaded?

Look for the two <script src="..."> tags just before the main <script> block. These load GSAP and its ScrollTrigger plugin from a CDN (content delivery network). The browser downloads them automatically when the page opens.

The first line inside the main script is gsap.registerPlugin(ScrollTrigger). This tells GSAP to activate the ScrollTrigger plugin so scroll-based animations work.

How does the hero text animate letter by letter?

The JavaScript splits the headline string into individual characters. Each character gets wrapped in its own <span> element with the class char. GSAP then animates all these spans with a stagger: 0.03 delay between each, creating the typewriter-like reveal.

Each character starts invisible (opacity: 0) and shifted down (y: 50), then animates to full visibility at its natural position.

Understand the premium design patterns

Several specific design choices combine to create that expensive agency feel. These patterns are reusable across any project.

  • Look at the background color #0a0a0f on the body. This is not pure black. It is a very dark navy that creates depth and avoids the harsh look of #000000.
  • Find the .service-card styles. The combination of backdrop-filter: blur(10px) with a nearly transparent background creates the glassmorphism effect (frosted glass look).
  • Notice how the gradient accent (indigo #4f46e5 to cyan #06b6d4) appears in only a few places: the CTA button, hover borders, stat numbers, and the scroll indicator. Restraint with color is what makes the accents feel premium.
  • Look at the font sizes. The hero headline is 6rem on desktop. Oversized bold typography with generous whitespace is a hallmark of high-end web design.

Why only transform and opacity for animations?

Every animation in this file moves elements using transform (position, scale) and opacity (visibility). These two CSS properties are handled by the browser's compositor thread, which means they run at 60 frames per second without triggering expensive layout recalculations.

Animating other properties like width, height, or margin forces the browser to recalculate the entire page layout on every frame. That causes visible stuttering on complex pages.

You now understand the anatomy of a premium landing page. Next up, you will refine the site with follow-up prompts to add smooth-scroll navigation and hover animations.

Refine with Follow-Up Prompts

Your site looks great, but the navigation links at the top are purely decorative right now. Clicking them does nothing useful.

One of Claude Code's superpowers is iterative refinement. You can re-enter a session, describe what you want changed, and Claude Code updates your file in place. In this step, you will add smooth scroll behavior and a hover animation to the nav links.

In this step, get ready to:
  • Re-enter Claude Code for a follow-up prompt.
  • Add smooth scrolling and a hover underline animation to navigation links.
  • Verify both features work in your browser.
Re-enter Claude Code to make improvements

Claude Code remembers the files in your project directory. When you start a new session, it can read and update index.html directly.

  • Start a new Claude Code session by running this command in your Terminal:
claude

You should see the Claude Code prompt appear, ready for your next instruction.

Add a smooth scroll effect for navigation links

Right now, clicking a nav link like "Work" or "Services" does a plain jump. You want a smooth glide to each section and a subtle underline animation when the user hovers over a link.

  • Paste the following follow-up prompt into Claude Code:
Update index.html: Make the navigation links (Work, Services, About, Contact) smooth-scroll to their respective sections when clicked. Add id attributes to each section if they don't exist already, and use JavaScript scrollIntoView with behavior: 'smooth'. Also add a subtle hover underline animation on the nav links using CSS transform scaleX from 0 to 1.

What does this prompt do?

This prompt asks Claude Code to do two things:

  • Wire up JavaScript scrollIntoView so clicking a nav link smoothly scrolls the page to the matching section.
  • Add a CSS ::after pseudo-element that scales from 0 to full width on hover, creating an animated underline effect.
  • Wait for Claude Code to confirm it has updated index.html. This usually takes 10-20 seconds.
  • Type /exit to leave the Claude Code session.

Follow-up prompts are powerful

You can keep re-entering Claude Code and asking for changes as many times as you like. Each follow-up prompt refines the same file without starting from scratch.

Refresh and test
  • Switch back to your browser where the NOIR Studio page is open.
  • Press Cmd+R to refresh the page.
  • Click the Work link in the navigation bar. The page should glide smoothly down to the Selected Work section.
  • Click Services, About, and Contact to confirm each one scrolls to the correct section.
  • Hover your mouse over each nav link. You should see a gradient underline animate in from left to right.

Links not scrolling smoothly?

  • Make sure you refreshed the browser after Claude Code confirmed the update. Stale cache shows the old version.
  • Check that each section in the HTML has an id attribute matching the nav link's href value (e.g. id="work" matches href="#work").
  • Re-enter Claude Code and ask it to fix the smooth scroll if something looks wrong.

✔️ Awesome, I've got everything!

Great. Double check you see smooth scrolling on every nav link and the underline animating on hover before moving on.

ⓧ I'd like to double check the full code

Here is the complete index.html after adding smooth scroll and the hover underline animation:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>NOIR Studio | Premium Digital Agency</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@300;400&family=Syne:wght@700;800&display=swap" rel="stylesheet">
    <style>
        * { margin: 0; padding: 0; box-sizing: border-box; }
        html { scroll-behavior: smooth; }
        body { background-color: #0a0a0f; color: #ffffff; font-family: 'Inter', sans-serif; font-weight: 300; overflow-x: hidden; }
        .nav { position: fixed; top: 0; left: 0; width: 100%; padding: 1.5rem 4rem; display: flex; justify-content: space-between; align-items: center; z-index: 1000; transition: background-color 0.3s ease, backdrop-filter 0.3s ease; }
        .nav.scrolled { background-color: rgba(10, 10, 15, 0.9); backdrop-filter: blur(20px); border-bottom: 1px solid rgba(255,255,255,0.05); }
        .nav-logo { font-family: 'Syne', sans-serif; font-weight: 800; font-size: 1.5rem; letter-spacing: 0.1em; }
        .nav-links { display: flex; gap: 2.5rem; list-style: none; }
        .nav-links a { color: #94a3b8; text-decoration: none; font-size: 0.9rem; font-weight: 400; position: relative; transition: color 0.3s ease; cursor: pointer; }
        .nav-links a::after { content: ''; position: absolute; bottom: -4px; left: 0; width: 100%; height: 1px; background: linear-gradient(90deg, #4f46e5, #06b6d4); transform: scaleX(0); transform-origin: left; transition: transform 0.3s ease; }
        .nav-links a:hover { color: #ffffff; }
        .nav-links a:hover::after { transform: scaleX(1); }
        .hero { height: 100vh; display: flex; flex-direction: column; justify-content: center; align-items: center; text-align: center; padding: 0 2rem; position: relative; overflow: hidden; }
        .hero-title { font-family: 'Syne', sans-serif; font-weight: 800; font-size: 6rem; line-height: 1.05; margin-bottom: 1.5rem; max-width: 900px; }
        .hero-title .char { display: inline-block; opacity: 0; }
        .hero-subtitle { font-size: 1.25rem; color: #94a3b8; max-width: 600px; line-height: 1.7; opacity: 0; }
        .scroll-indicator { position: absolute; bottom: 3rem; left: 50%; transform: translateX(-50%); display: flex; flex-direction: column; align-items: center; gap: 0.5rem; color: #94a3b8; font-size: 0.75rem; letter-spacing: 0.15em; text-transform: uppercase; }
        .scroll-indicator .arrow { width: 1px; height: 40px; background: linear-gradient(to bottom, #4f46e5, transparent); }
        section { padding: 8rem 4rem; }
        .section-title { font-family: 'Syne', sans-serif; font-weight: 700; font-size: 3rem; margin-bottom: 4rem; text-align: center; }
        .services-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 2rem; max-width: 1200px; margin: 0 auto; }
        .service-card { background: rgba(255,255,255,0.03); backdrop-filter: blur(10px); border: 1px solid rgba(255,255,255,0.08); border-radius: 16px; padding: 3rem 2rem; text-align: center; transition: border-color 0.3s ease, box-shadow 0.3s ease; opacity: 0; cursor: pointer; }
        .service-card:hover { border-color: #4f46e5; box-shadow: 0 0 30px rgba(79,70,229,0.15), 0 0 60px rgba(6,182,212,0.05); }
        .service-card h3 { font-family: 'Syne', sans-serif; font-weight: 700; font-size: 1.5rem; margin-bottom: 1rem; }
        .service-card p { color: #94a3b8; line-height: 1.7; }
        .work-grid { display: grid; grid-template-columns: repeat(2, 1fr); gap: 2rem; max-width: 1200px; margin: 0 auto; }
        .work-card { border-radius: 16px; padding: 3rem; display: flex; flex-direction: column; justify-content: flex-end; min-height: 300px; position: relative; overflow: hidden; opacity: 0; cursor: pointer; transition: transform 0.3s ease; }
        .work-card:hover { transform: scale(1.02); }
        .work-card:nth-child(1) { background: linear-gradient(135deg, #1e1b4b, #4f46e5); }
        .work-card:nth-child(2) { background: linear-gradient(135deg, #042f2e, #06b6d4); }
        .work-card:nth-child(3) { background: linear-gradient(135deg, #1c1917, #f97316); }
        .work-card:nth-child(4) { background: linear-gradient(135deg, #2e1065, #a855f7); }
        .work-card h3 { font-family: 'Syne', sans-serif; font-weight: 700; font-size: 1.75rem; margin-bottom: 0.5rem; }
        .work-card span { color: rgba(255,255,255,0.7); font-size: 0.9rem; }
        .stats-grid { display: grid; grid-template-columns: repeat(4, 1fr); gap: 2rem; max-width: 1000px; margin: 0 auto; text-align: center; }
        .stat-item .stat-number { font-family: 'Syne', sans-serif; font-weight: 800; font-size: 3.5rem; background: linear-gradient(135deg, #4f46e5, #06b6d4); -webkit-background-clip: text; -webkit-text-fill-color: transparent; background-clip: text; }
        .stat-item .stat-label { color: #94a3b8; font-size: 0.9rem; margin-top: 0.5rem; text-transform: uppercase; letter-spacing: 0.1em; }
        .contact { text-align: center; padding: 10rem 4rem; }
        .contact .section-title { margin-bottom: 1.5rem; }
        .contact-subtitle { color: #94a3b8; font-size: 1.1rem; margin-bottom: 3rem; max-width: 500px; margin-left: auto; margin-right: auto; }
        .cta-button { display: inline-block; padding: 1.25rem 3rem; background: linear-gradient(135deg, #4f46e5, #06b6d4); color: #ffffff; text-decoration: none; font-family: 'Syne', sans-serif; font-weight: 700; font-size: 1rem; border-radius: 60px; border: none; cursor: pointer; transition: transform 0.3s ease, box-shadow 0.3s ease; }
        .cta-button:hover { transform: translateY(-3px); box-shadow: 0 20px 40px rgba(79,70,229,0.3); }
        .footer { text-align: center; padding: 2rem 4rem; border-top: 1px solid rgba(255,255,255,0.05); color: #94a3b8; font-size: 0.85rem; }
        @media (max-width: 768px) { .nav { padding: 1rem 2rem; } .nav-links { gap: 1.5rem; } .hero-title { font-size: 3rem; } .hero-subtitle { font-size: 1rem; } section { padding: 5rem 2rem; } .section-title { font-size: 2rem; } .services-grid { grid-template-columns: 1fr; } .work-grid { grid-template-columns: 1fr; } .stats-grid { grid-template-columns: repeat(2, 1fr); } }
    </style>
</head>
<body>
    <nav class="nav" id="nav">
        <div class="nav-logo">NOIR</div>
        <ul class="nav-links">
            <li><a href="#work">Work</a></li>
            <li><a href="#services">Services</a></li>
            <li><a href="#about">About</a></li>
            <li><a href="#contact">Contact</a></li>
        </ul>
    </nav>
    <section class="hero" id="hero">
        <h1 class="hero-title"></h1>
        <p class="hero-subtitle">Award-winning studio crafting premium digital products for ambitious brands.</p>
        <div class="scroll-indicator"><span>Scroll</span><div class="arrow"></div></div>
    </section>
    <section id="services">
        <h2 class="section-title">What We Do</h2>
        <div class="services-grid">
            <div class="service-card"><h3>Brand Strategy</h3><p>We build brands that resonate and endure.</p></div>
            <div class="service-card"><h3>Digital Design</h3><p>Pixel-perfect interfaces that convert.</p></div>
            <div class="service-card"><h3>Development</h3><p>Performant code that scales.</p></div>
        </div>
    </section>
    <section id="work">
        <h2 class="section-title">Selected Work</h2>
        <div class="work-grid">
            <div class="work-card"><h3>Lumina App</h3><span>Mobile Design</span></div>
            <div class="work-card"><h3>Vertex AI</h3><span>Brand Identity</span></div>
            <div class="work-card"><h3>Onyx Bank</h3><span>Web Platform</span></div>
            <div class="work-card"><h3>Prism Studio</h3><span>Creative Direction</span></div>
        </div>
    </section>
    <section id="about">
        <div class="stats-grid">
            <div class="stat-item"><div class="stat-number" data-target="150">0</div><div class="stat-label">Projects</div></div>
            <div class="stat-item"><div class="stat-number" data-target="12">0</div><div class="stat-label">Awards</div></div>
            <div class="stat-item"><div class="stat-number" data-target="8">0</div><div class="stat-label">Years</div></div>
            <div class="stat-item"><div class="stat-number" data-target="50">0</div><div class="stat-label">Clients</div></div>
        </div>
    </section>
    <section class="contact" id="contact">
        <h2 class="section-title">Let's Work Together</h2>
        <p class="contact-subtitle">Have a project in mind? We'd love to hear about it.</p>
        <a href="mailto:hello@noir.studio" class="cta-button">Get In Touch</a>
    </section>
    <footer class="footer"><p>2026 NOIR Studio. All rights reserved.</p></footer>
    <script src="https://cdn.jsdelivr.net/npm/gsap@3.15.0/dist/gsap.min.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/gsap@3.15.0/dist/ScrollTrigger.min.js"></script>
    <script>
        gsap.registerPlugin(ScrollTrigger);
        const heroTitle = document.querySelector('.hero-title');
        const titleText = "We Create Digital Experiences";
        titleText.split('').forEach(char => { const span = document.createElement('span'); span.classList.add('char'); span.textContent = char === ' ' ? '\u00A0' : char; heroTitle.appendChild(span); });
        gsap.to('.hero-title .char', { opacity: 1, y: 0, duration: 0.8, stagger: 0.03, ease: "power3.out", delay: 0.5 });
        gsap.set('.hero-title .char', { opacity: 0, y: 50 });
        gsap.to('.hero-subtitle', { opacity: 1, y: 0, duration: 1, ease: "power2.out", delay: 1.5 });
        gsap.set('.hero-subtitle', { opacity: 0, y: 30 });
        gsap.to('.scroll-indicator', { y: 10, duration: 1.5, ease: "power1.inOut", yoyo: true, repeat: -1 });
        window.addEventListener('scroll', () => { const nav = document.getElementById('nav'); if (window.scrollY > 100) { nav.classList.add('scrolled'); } else { nav.classList.remove('scrolled'); } });
        gsap.from('.service-card', { scrollTrigger: { trigger: '.services-grid', start: 'top 80%' }, y: 80, opacity: 0, duration: 0.8, stagger: 0.2, ease: "power2.out", onComplete: () => { gsap.set('.service-card', { opacity: 1 }); } });
        gsap.from('.work-card', { scrollTrigger: { trigger: '.work-grid', start: 'top 80%' }, x: -60, opacity: 0, duration: 0.8, stagger: 0.15, ease: "power2.out", onComplete: () => { gsap.set('.work-card', { opacity: 1 }); } });
        const statNumbers = document.querySelectorAll('.stat-number');
        statNumbers.forEach(stat => { const target = parseInt(stat.getAttribute('data-target')); ScrollTrigger.create({ trigger: stat, start: 'top 85%', onEnter: () => { gsap.to(stat, { duration: 2, ease: "power1.out", onUpdate: function() { const progress = this.progress(); const current = Math.round(target * progress); stat.textContent = current + (target >= 50 ? '+' : ''); } }); }, once: true }); });
        document.querySelectorAll('.nav-links a').forEach(link => { link.addEventListener('click', (e) => { e.preventDefault(); const targetId = link.getAttribute('href'); const targetSection = document.querySelector(targetId); if (targetSection) { targetSection.scrollIntoView({ behavior: 'smooth' }); } }); });
    </script>
</body>
</html>

Your navigation now feels polished and interactive. Next up, you will add a parallax background effect to give the hero section even more depth.

Add a Parallax Background Effect

Your landing page now has smooth-scrolling navigation links and a polished hover underline effect. The foundations are solid.

The next layer of premium design comes from depth. You'll add parallax-scrolling orbs to the hero section that drift at different speeds, making the page feel three-dimensional and alive.

In this step, get ready to:
  • Add floating glowing orbs with parallax scrolling to the hero section.
  • Preview the depth effect in your browser.
  • Optionally add a custom cursor follower for extra polish.
Add parallax to the hero section

Parallax works by moving background elements at different speeds from the foreground as the user scrolls. Premium agency sites use this to create layers of visual depth without overwhelming the content.

  • In your Claude Code session, paste the following prompt to add parallax orbs and a grain texture to the hero:
Update index.html: Add a subtle parallax effect to the hero section. Create 3 small glowing orbs (circles with radial gradients using the accent colors) positioned absolutely in the hero. As the user scrolls, these orbs should move at different speeds using GSAP ScrollTrigger scrub. One orb should be 200px wide with a soft indigo glow, one 150px with cyan, and one 100px with a mix. Set their opacity to 0.15 so they are subtle background elements. Also add a very subtle grain texture overlay using CSS (a repeating tiny noise pattern using a CSS gradient trick).

What does this prompt ask for?

  • Three absolutely-positioned circles with radial gradient glows, placed behind the hero text.
  • Each orb moves at a different speed using GSAP ScrollTrigger with scrub: true, which ties the animation directly to scroll position instead of time.
  • A CSS grain texture overlay adds subtle visual noise for a film-like quality.

Claude Code will update index.html with the new CSS classes, HTML elements, and JavaScript animations. This typically takes 30-60 seconds.

Preview the parallax effect
  • Once Claude Code confirms the update, refresh your browser by pressing Cmd+R.
  • Scroll slowly from the top of the page and watch for colored orbs drifting at different speeds behind the hero text.

You should see a large indigo glow, a medium cyan glow, and a smaller mixed-color glow all moving at different rates as you scroll. The orbs are subtle at 0.15 opacity, creating depth without competing with the headline.

Why different scroll speeds?

Objects further away appear to move more slowly. By giving each orb a different y travel distance (the largest orb moves less, the smallest moves most), the effect mimics real-world depth perception.

Don't see the orbs moving?

  • Make sure you scrolled from the very top of the page. The parallax only activates within the hero section's scroll range.
  • Check that the orbs aren't hidden behind other elements. They should have a lower z-index than the hero text.
  • If you see nothing at all, re-enter Claude Code and ask it to double-check that the orb divs were added inside the .hero section.

Help me debug why my parallax orbs aren't visible when I scroll.

✔️ Awesome, I've got everything!

Great. Make sure you've saved your file and that the parallax orbs are drifting smoothly as you scroll.

ⓧ I'd like to double check the full code

Here is the full index.html at this point in the project. Compare your file against this to spot any differences.

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>NOIR Studio | Premium Digital Agency</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@300;400&family=Syne:wght@700;800&display=swap" rel="stylesheet">
    <style>
        * { margin: 0; padding: 0; box-sizing: border-box; }
        html { scroll-behavior: smooth; }
        body { background-color: #0a0a0f; color: #ffffff; font-family: 'Inter', sans-serif; font-weight: 300; overflow-x: hidden; }
        .nav { position: fixed; top: 0; left: 0; width: 100%; padding: 1.5rem 4rem; display: flex; justify-content: space-between; align-items: center; z-index: 1000; transition: background-color 0.3s ease, backdrop-filter 0.3s ease; }
        .nav.scrolled { background-color: rgba(10, 10, 15, 0.9); backdrop-filter: blur(20px); border-bottom: 1px solid rgba(255,255,255,0.05); }
        .nav-logo { font-family: 'Syne', sans-serif; font-weight: 800; font-size: 1.5rem; letter-spacing: 0.1em; }
        .nav-links { display: flex; gap: 2.5rem; list-style: none; }
        .nav-links a { color: #94a3b8; text-decoration: none; font-size: 0.9rem; font-weight: 400; position: relative; transition: color 0.3s ease; cursor: pointer; }
        .nav-links a::after { content: ''; position: absolute; bottom: -4px; left: 0; width: 100%; height: 1px; background: linear-gradient(90deg, #4f46e5, #06b6d4); transform: scaleX(0); transform-origin: left; transition: transform 0.3s ease; }
        .nav-links a:hover { color: #ffffff; }
        .nav-links a:hover::after { transform: scaleX(1); }
        .hero { height: 100vh; display: flex; flex-direction: column; justify-content: center; align-items: center; text-align: center; padding: 0 2rem; position: relative; overflow: hidden; }
        .orb { position: absolute; border-radius: 50%; pointer-events: none; }
        .orb-1 { width: 200px; height: 200px; background: radial-gradient(circle, rgba(79,70,229,0.15), transparent 70%); top: 20%; left: 15%; opacity: 0.15; }
        .orb-2 { width: 150px; height: 150px; background: radial-gradient(circle, rgba(6,182,212,0.15), transparent 70%); top: 60%; right: 20%; opacity: 0.15; }
        .orb-3 { width: 100px; height: 100px; background: radial-gradient(circle, rgba(79,70,229,0.1), rgba(6,182,212,0.1), transparent 70%); top: 40%; left: 60%; opacity: 0.15; }
        .hero-title { font-family: 'Syne', sans-serif; font-weight: 800; font-size: 6rem; line-height: 1.05; margin-bottom: 1.5rem; max-width: 900px; position: relative; z-index: 1; }
        .hero-title .char { display: inline-block; opacity: 0; }
        .hero-subtitle { font-size: 1.25rem; color: #94a3b8; max-width: 600px; line-height: 1.7; opacity: 0; position: relative; z-index: 1; }
        .scroll-indicator { position: absolute; bottom: 3rem; left: 50%; transform: translateX(-50%); display: flex; flex-direction: column; align-items: center; gap: 0.5rem; color: #94a3b8; font-size: 0.75rem; letter-spacing: 0.15em; text-transform: uppercase; z-index: 1; }
        .scroll-indicator .arrow { width: 1px; height: 40px; background: linear-gradient(to bottom, #4f46e5, transparent); }
        section { padding: 8rem 4rem; }
        .section-title { font-family: 'Syne', sans-serif; font-weight: 700; font-size: 3rem; margin-bottom: 4rem; text-align: center; }
        .services-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 2rem; max-width: 1200px; margin: 0 auto; }
        .service-card { background: rgba(255,255,255,0.03); backdrop-filter: blur(10px); border: 1px solid rgba(255,255,255,0.08); border-radius: 16px; padding: 3rem 2rem; text-align: center; transition: border-color 0.3s ease, box-shadow 0.3s ease; opacity: 0; cursor: pointer; }
        .service-card:hover { border-color: #4f46e5; box-shadow: 0 0 30px rgba(79,70,229,0.15), 0 0 60px rgba(6,182,212,0.05); }
        .service-card h3 { font-family: 'Syne', sans-serif; font-weight: 700; font-size: 1.5rem; margin-bottom: 1rem; }
        .service-card p { color: #94a3b8; line-height: 1.7; }
        .work-grid { display: grid; grid-template-columns: repeat(2, 1fr); gap: 2rem; max-width: 1200px; margin: 0 auto; }
        .work-card { border-radius: 16px; padding: 3rem; display: flex; flex-direction: column; justify-content: flex-end; min-height: 300px; position: relative; overflow: hidden; opacity: 0; cursor: pointer; transition: transform 0.3s ease; }
        .work-card:hover { transform: scale(1.02); }
        .work-card:nth-child(1) { background: linear-gradient(135deg, #1e1b4b, #4f46e5); }
        .work-card:nth-child(2) { background: linear-gradient(135deg, #042f2e, #06b6d4); }
        .work-card:nth-child(3) { background: linear-gradient(135deg, #1c1917, #f97316); }
        .work-card:nth-child(4) { background: linear-gradient(135deg, #2e1065, #a855f7); }
        .work-card h3 { font-family: 'Syne', sans-serif; font-weight: 700; font-size: 1.75rem; margin-bottom: 0.5rem; }
        .work-card span { color: rgba(255,255,255,0.7); font-size: 0.9rem; }
        .stats-grid { display: grid; grid-template-columns: repeat(4, 1fr); gap: 2rem; max-width: 1000px; margin: 0 auto; text-align: center; }
        .stat-item .stat-number { font-family: 'Syne', sans-serif; font-weight: 800; font-size: 3.5rem; background: linear-gradient(135deg, #4f46e5, #06b6d4); -webkit-background-clip: text; -webkit-text-fill-color: transparent; background-clip: text; }
        .stat-item .stat-label { color: #94a3b8; font-size: 0.9rem; margin-top: 0.5rem; text-transform: uppercase; letter-spacing: 0.1em; }
        .contact { text-align: center; padding: 10rem 4rem; }
        .contact .section-title { margin-bottom: 1.5rem; }
        .contact-subtitle { color: #94a3b8; font-size: 1.1rem; margin-bottom: 3rem; max-width: 500px; margin-left: auto; margin-right: auto; }
        .cta-button { display: inline-block; padding: 1.25rem 3rem; background: linear-gradient(135deg, #4f46e5, #06b6d4); color: #ffffff; text-decoration: none; font-family: 'Syne', sans-serif; font-weight: 700; font-size: 1rem; border-radius: 60px; border: none; cursor: pointer; transition: transform 0.3s ease, box-shadow 0.3s ease; }
        .cta-button:hover { transform: translateY(-3px); box-shadow: 0 20px 40px rgba(79,70,229,0.3); }
        .footer { text-align: center; padding: 2rem 4rem; border-top: 1px solid rgba(255,255,255,0.05); color: #94a3b8; font-size: 0.85rem; }
        @media (max-width: 768px) { .nav { padding: 1rem 2rem; } .nav-links { gap: 1.5rem; } .hero-title { font-size: 3rem; } .hero-subtitle { font-size: 1rem; } section { padding: 5rem 2rem; } .section-title { font-size: 2rem; } .services-grid { grid-template-columns: 1fr; } .work-grid { grid-template-columns: 1fr; } .stats-grid { grid-template-columns: repeat(2, 1fr); } }
    </style>
</head>
<body>
    <nav class="nav" id="nav">
        <div class="nav-logo">NOIR</div>
        <ul class="nav-links">
            <li><a href="#work">Work</a></li>
            <li><a href="#services">Services</a></li>
            <li><a href="#about">About</a></li>
            <li><a href="#contact">Contact</a></li>
        </ul>
    </nav>
    <section class="hero" id="hero">
        <div class="orb orb-1"></div>
        <div class="orb orb-2"></div>
        <div class="orb orb-3"></div>
        <h1 class="hero-title"></h1>
        <p class="hero-subtitle">Award-winning studio crafting premium digital products for ambitious brands.</p>
        <div class="scroll-indicator"><span>Scroll</span><div class="arrow"></div></div>
    </section>
    <section id="services">
        <h2 class="section-title">What We Do</h2>
        <div class="services-grid">
            <div class="service-card"><h3>Brand Strategy</h3><p>We build brands that resonate and endure.</p></div>
            <div class="service-card"><h3>Digital Design</h3><p>Pixel-perfect interfaces that convert.</p></div>
            <div class="service-card"><h3>Development</h3><p>Performant code that scales.</p></div>
        </div>
    </section>
    <section id="work">
        <h2 class="section-title">Selected Work</h2>
        <div class="work-grid">
            <div class="work-card"><h3>Lumina App</h3><span>Mobile Design</span></div>
            <div class="work-card"><h3>Vertex AI</h3><span>Brand Identity</span></div>
            <div class="work-card"><h3>Onyx Bank</h3><span>Web Platform</span></div>
            <div class="work-card"><h3>Prism Studio</h3><span>Creative Direction</span></div>
        </div>
    </section>
    <section id="about">
        <div class="stats-grid">
            <div class="stat-item"><div class="stat-number" data-target="150">0</div><div class="stat-label">Projects</div></div>
            <div class="stat-item"><div class="stat-number" data-target="12">0</div><div class="stat-label">Awards</div></div>
            <div class="stat-item"><div class="stat-number" data-target="8">0</div><div class="stat-label">Years</div></div>
            <div class="stat-item"><div class="stat-number" data-target="50">0</div><div class="stat-label">Clients</div></div>
        </div>
    </section>
    <section class="contact" id="contact">
        <h2 class="section-title">Let's Work Together</h2>
        <p class="contact-subtitle">Have a project in mind? We'd love to hear about it.</p>
        <a href="mailto:hello@noir.studio" class="cta-button">Get In Touch</a>
    </section>
    <footer class="footer"><p>2026 NOIR Studio. All rights reserved.</p></footer>
    <script src="https://cdn.jsdelivr.net/npm/gsap@3.15.0/dist/gsap.min.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/gsap@3.15.0/dist/ScrollTrigger.min.js"></script>
    <script>
        gsap.registerPlugin(ScrollTrigger);
        gsap.to('.orb-1', { scrollTrigger: { trigger: '.hero', start: 'top top', end: 'bottom top', scrub: true }, y: -80 });
        gsap.to('.orb-2', { scrollTrigger: { trigger: '.hero', start: 'top top', end: 'bottom top', scrub: true }, y: -140 });
        gsap.to('.orb-3', { scrollTrigger: { trigger: '.hero', start: 'top top', end: 'bottom top', scrub: true }, y: -60 });
        const heroTitle = document.querySelector('.hero-title');
        const titleText = "We Create Digital Experiences";
        titleText.split('').forEach(char => { const span = document.createElement('span'); span.classList.add('char'); span.textContent = char === ' ' ? '\u00A0' : char; heroTitle.appendChild(span); });
        gsap.to('.hero-title .char', { opacity: 1, y: 0, duration: 0.8, stagger: 0.03, ease: "power3.out", delay: 0.5 });
        gsap.set('.hero-title .char', { opacity: 0, y: 50 });
        gsap.to('.hero-subtitle', { opacity: 1, y: 0, duration: 1, ease: "power2.out", delay: 1.5 });
        gsap.set('.hero-subtitle', { opacity: 0, y: 30 });
        gsap.to('.scroll-indicator', { y: 10, duration: 1.5, ease: "power1.inOut", yoyo: true, repeat: -1 });
        window.addEventListener('scroll', () => { const nav = document.getElementById('nav'); if (window.scrollY > 100) { nav.classList.add('scrolled'); } else { nav.classList.remove('scrolled'); } });
        gsap.from('.service-card', { scrollTrigger: { trigger: '.services-grid', start: 'top 80%' }, y: 80, opacity: 0, duration: 0.8, stagger: 0.2, ease: "power2.out", onComplete: () => { gsap.set('.service-card', { opacity: 1 }); } });
        gsap.from('.work-card', { scrollTrigger: { trigger: '.work-grid', start: 'top 80%' }, x: -60, opacity: 0, duration: 0.8, stagger: 0.15, ease: "power2.out", onComplete: () => { gsap.set('.work-card', { opacity: 1 }); } });
        const statNumbers = document.querySelectorAll('.stat-number');
        statNumbers.forEach(stat => { const target = parseInt(stat.getAttribute('data-target')); ScrollTrigger.create({ trigger: stat, start: 'top 85%', onEnter: () => { gsap.to(stat, { duration: 2, ease: "power1.out", onUpdate: function() { const progress = this.progress(); const current = Math.round(target * progress); stat.textContent = current + (target >= 50 ? '+' : ''); } }); }, once: true }); });
        document.querySelectorAll('.nav-links a').forEach(link => { link.addEventListener('click', (e) => { e.preventDefault(); const targetId = link.getAttribute('href'); const targetSection = document.querySelector(targetId); if (targetSection) { targetSection.scrollIntoView({ behavior: 'smooth' }); } }); });
    </script>
</body>
</html>
Optional micro-interaction polish

If you want to push the premium feel even further, you can add a custom cursor that follows the mouse with a soft gradient glow. This is completely optional.

  • In your Claude Code session, paste this prompt to add a cursor follower:
Update index.html: Add a custom cursor effect - when the mouse moves over the page, show a subtle gradient circle (20px, low opacity) that follows the cursor with a slight delay using GSAP. Only show this on desktop (screens wider than 1024px).
  • Once Claude Code finishes, refresh your browser with Cmd+R.
  • Move your mouse around the page. You should see a small, soft gradient circle trailing behind your cursor with a slight delay.

Cursor effects are desktop-only

The prompt limits this to screens wider than 1024px. Mobile devices don't have a hovering cursor, so the effect would never trigger there. This keeps your site performant on phones.

Secret mission

Add a Dark/Light Theme Toggle

Your landing page looks stunning in dark mode. But what if visitors prefer light mode? Add an animated dark/light theme toggle that smoothly transitions between color schemes and remembers the user's preference across sessions.

Clean Up Your Resources

Clean Up Your Resources

This project runs entirely locally, so there are no ongoing costs. Decide whether to keep your project files, or delete them entirely.

Resources you used:

  • The noir-studio folder on your Desktop (contains index.html).

Keep everything running

No action needed. Choose this if you want to keep experimenting with your landing page or use the prompt template for other projects.

  • Your noir-studio folder stays on your Desktop, ready to open anytime.
  • To view your site again, open Terminal, navigate to the folder, and run open index.html.
  • Your browser's localStorage still holds the theme preference you set with the toggle.

Pause - I'll come back to this later

Since this is a local project with no running processes, pausing and keeping are the same. Your files stay on disk until you decide to delete them.

  • No servers or processes to stop.
  • Your noir-studio folder takes up minimal disk space (a single HTML file).

Delete - I don't want to use this again

Remove all project files from your machine.

  • Delete the project folder by running this command in Terminal:
rm -rf ~/Desktop/noir-studio
  • Confirm the folder is gone by running:
ls ~/Desktop | grep noir-studio

You should see no output, confirming the folder has been removed.

Nice Work!

Nice Work!

Nice work! You just built a premium animated landing page that looks like it came from a $10,000 creative agency. The entire site was generated by Claude Code from a single detailed prompt.

You've learned how to:

  • Use GSAP ScrollTrigger for professional scroll-driven animations including character-by-character text reveals, staggered card entrances, and animated number counters.
  • Apply modern premium design patterns: dark theme, glassmorphism, oversized bold typography, gradient accents, and parallax motion.
  • Direct Claude Code with detailed, specific prompts to generate complete, polished websites from scratch and refine them with follow-up instructions.
  • Secret Mission: Added an animated dark/light theme toggle using CSS custom properties and localStorage persistence.

Ready to quiz yourself?