Interactive Experience with Paper + GSAP
Build an animated portfolio microsite with scroll effects and cursor tracking.
Introduction
30 Second Summary
You know those web pages that feel alive? The ones where a glowing dot follows your cursor, content slides into view as you scroll, and the whole thing seems to respond to your presence. Those pages look magical, but they are built with surprisingly approachable code.
In this project, you will build a single-page interactive experience on a topic you care about using Paper Desktop for visual design and GSAP for animation. You will connect Paper to Claude Code via MCP to bridge the gap between design and code.
What You'll Build
Move your cursor across your finished page and a white dot chases it, swelling over anything clickable. Scroll down and each section fades up with cinematic timing. Show it to a friend and watch them ask how you made a web page feel this alive.
By the end of this project, you'll have:
- A custom cursor follower that tracks your mouse and scales up over interactive elements, plus hover-responsive cards that subtly grow when you point at them.
- Scroll-triggered animations that reveal each section as visitors reach it, paired with a parallax depth effect where background layers drift at different speeds than the foreground.
- A working design-to-code workflow connecting Paper Desktop to Claude Code via MCP, so you can design visually and generate real HTML/CSS without starting from a blank file.
- Secret Mission: Add a scroll progress bar and a dynamic gradient that shifts color as the viewer scrolls through your page.
Are there any prerequisites?
You should have basic familiarity with HTML and CSS. Knowing what tags and properties are is enough. No prior JavaScript experience is required.
Before We Start
Before we dive in, let's lock in what you're about to build and why it matters.
Set Up Paper MCP and Project Files
Before building anything visible, you need Paper Desktop talking to Claude Code. This connection is what lets you move between visual design and code without friction.
You also need a project folder with your three core files. By the end of this step, your tools will be connected and your workspace ready to go.
In this step, get ready to:
- Open Paper Desktop and start the MCP server.
- Connect Claude Code to Paper's MCP server.
- Create the project folder and files.
Open Paper Desktop and create a new file
Paper's MCP server starts automatically whenever a file is open in Paper Desktop. Creating a new file is all it takes to make Paper available to Claude Code.
- Press Cmd+Space (macOS) or the Windows key (Windows) to open your search bar.
- Type Paper and press Enter to open Paper Desktop.
- Create a new file from the dashboard.
- Name the file interactive-demo.
You should see the blank canvas open with your new file name visible at the top.
What is MCP?
MCP (Model Context Protocol) is a standard that lets AI tools read from and write to other applications. Paper Desktop runs an MCP server on your machine that Claude Code can connect to.
Once connected, Claude Code can inspect your designs, read styles, and generate code that matches what you see on the canvas.
Connect Claude Code to Paper's MCP server
With Paper's MCP server now running in the background, you can connect Claude Code to it using a plugin. This gives Claude Code direct access to your design file.
- Press Cmd+Space to open Spotlight search.
- Type Terminal and press Enter to open it.
- Start Claude Code by running this command:
claude
You should see Claude Code's interactive prompt appear in your terminal.
- Install the Paper plugin by running these two commands inside Claude Code:
/plugin marketplace add paper-design/agent-plugins
/plugin install paper-desktop@paper
What do these commands do?
The first command adds Paper's plugin repository to Claude Code's marketplace. The second installs the specific Paper Desktop plugin, which connects to the MCP server running on your machine at http://127.0.0.1:29979/mcp.
- Verify the connection by running:
/mcp
You should see paper listed as a connected MCP server in the output.
Don't see paper in the list?
Make sure Paper Desktop is open with your interactive-demo file loaded. The MCP server only runs when a file is open.
If you closed Paper Desktop or the file, reopen it and run /mcp again in Claude Code.
Help me troubleshoot my Paper MCP connection in Claude Code.
Create your project folder and files
With the MCP connection verified, you need a project folder to hold your HTML, CSS, and JavaScript files.
- Open a new terminal tab by pressing Cmd+T in Terminal.
- Move to your Desktop by running this command:
cd ~/Desktop
- Create your project folder and move into it by running these commands:
mkdir interactive-demo && cd interactive-demo
- Create the three project files by running:
touch index.html style.css script.js
- Confirm the files exist by running:
ls
You should see index.html, style.css, and script.js listed in the output.
Before you open the HTML file, what do you expect to see in the browser given that the file is completely empty?
- Open index.html in your browser by running:
open index.html
You should see a blank white page in your browser. That confirms the file loaded successfully.
Seeing a file-not-found error?
Make sure you ran cd interactive-demo before running open index.html. The file only exists inside the interactive-demo folder on your Desktop.
Help me find and open my index.html file.
Your tools are connected and your workspace is ready. Next up, you'll design your page layout in Paper and translate it into real HTML and CSS.
Build Your Static Page
Your Paper Desktop is connected to Claude Code via MCP. Now you can move between visual design and code without friction.
Before any interaction can exist, you need a visually designed page structure. In this step, you will pick a topic you care about, design a themed layout in Paper, and have Claude Code translate it into real HTML and CSS via the MCP connection.
In this step, get ready to:
- Choose a topic and design a dark-themed page layout in Paper Desktop.
- Generate HTML and CSS from your design using Claude Code's MCP bridge.
- Add interaction-ready elements and animation library scripts.
Design your layout in Paper Desktop
Pick a topic that interests you. It could be a hobby, a cause, a favourite game, a music genre, a place you love. The page will have a bold headline, a couple of content sections, and interactive cards. Your topic gives everything a personal flavour.
- Switch back to Paper Desktop with your interactive-demo file open.
- Design a full-height hero area in the centre of the canvas with a large heading for your topic name and a smaller subtitle below it (e.g. "The Art of Bouldering" with subtitle "Strength, Flow, Problem-Solving").
- Below the hero, add a Story section with a short paragraph of text about your topic.
- Add a Highlights section with three cards arranged in a row. Each card has a title and a one-line description showcasing an aspect of your topic.
- Add a Connect section at the bottom with a heading, a line of text, and a link.
- Keep the overall colour scheme dark (near-black background, light text). Think minimal and clean.
What does Paper need to look like?
The design does not need to be pixel-perfect. Paper's MCP tools extract structure and computed styles, so Claude Code can interpret a rough layout and produce clean code from it.
Focus on getting the sections, headings, and cards placed in the right order. Claude Code handles the polish.
You should now see your four-section layout on the Paper canvas: hero, story, highlights cards, and connect.
Generate HTML and CSS with Claude Code
Now you will bring the design into code. Claude Code can read your Paper file through MCP and produce matching HTML and CSS automatically.
- Switch back to Claude Code in your terminal.
- Ask Claude Code to read your Paper design and generate the corresponding HTML and CSS. For example, type something like: "Read my Paper design using get_jsx and get_computed_styles. Generate a dark-themed page into index.html with a hero section, story section, highlights section with three cards, and connect section. Put all styles in style.css."
- Claude Code will call the Paper MCP tools (get_jsx and get_computed_styles) and write both files.
What are get_jsx and get_computed_styles?
These are MCP tools that Paper exposes to Claude Code. get_jsx returns the structural layout of your design as component-like markup. get_computed_styles returns the resolved CSS values (colours, sizes, spacing) for each element.
Together they give Claude Code everything it needs to produce real HTML and CSS from your visual canvas.
- Once Claude Code finishes writing the files, refresh index.html in your browser.
- You should see a styled dark-themed page with your hero heading at the top, followed by the story paragraph, three highlight cards, and a connect section at the bottom.
Page looks blank or unstyled?
Make sure the <link rel="stylesheet" href="style.css"> tag is present in your index.html <head>. If Claude Code did not include it, ask it to add the stylesheet link.
Also confirm that style.css is in the same folder as index.html. If the file path is wrong, the styles will not load.
help me debug why my page looks unstyled
Now you need to add the GSAP animation library so it is available for the interactive code you will write in later steps.
- Ask Claude Code to add three script tags at the bottom of index.html, just before the closing </body> tag. Tell it to add these exact scripts:
<script src="https://cdn.jsdelivr.net/npm/gsap@3/dist/gsap.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/gsap@3/dist/ScrollTrigger.min.js"></script>
<script src="script.js"></script>
Why load scripts at the bottom of the body?
Placing script tags just before </body> ensures the HTML above has fully loaded before any JavaScript runs. This guarantees your code can find and animate the page elements.
The first two scripts load GSAP and its ScrollTrigger plugin from a CDN. The third loads your own script.js where you will write all your animation code.
- Refresh the browser to confirm the page still loads correctly with no console errors.
Add the cursor follower and parallax elements
Two invisible elements need to exist in the HTML before you can animate them with JavaScript. One will become a custom cursor. The other will create a parallax depth effect behind the hero.
- Ask Claude Code to add a <div class="cursor-follower"></div> as the very first child inside <body>, before any section.
- Ask it to also add a <div class="parallax-bg"></div> inside the hero section, before the heading elements.
The top of your index.html body should now look like this:
<body>
<div class="cursor-follower"></div>
<section class="hero">
<div class="parallax-bg"></div>
<h1 class="hero-title">Your Topic</h1>
<p class="hero-subtitle">Your Subtitle</p>
</section>
Why add invisible elements now?
The cursor follower starts with opacity: 0 and only appears when JavaScript detects mouse movement. The parallax background sits behind the hero content and only shifts when scroll-based animation kicks in.
Adding them now means the HTML is ready for the JavaScript you will write in the next steps.
Now you need the CSS that styles these elements and hides the default cursor.
- Ask Claude Code to add cursor: none to the body rule in style.css. This hides the default cursor so your custom follower can replace it.
- Ask Claude Code to add styles for .cursor-follower and .parallax-bg matching these rules:
.cursor-follower {
width: 20px;
height: 20px;
background-color: #ffffff;
border-radius: 50%;
position: fixed;
top: 0;
left: 0;
pointer-events: none;
z-index: 9999;
mix-blend-mode: difference;
opacity: 0;
}
.parallax-bg {
position: absolute;
top: -50px;
left: -50px;
right: -50px;
bottom: -50px;
background: radial-gradient(circle at 30% 40%, #1a1a2e 0%, transparent 50%),
radial-gradient(circle at 70% 60%, #16213e 0%, transparent 50%);
z-index: -1;
}
What do these styles do?
The .cursor-follower is a 20px white circle fixed to the viewport. pointer-events: none ensures it never blocks clicks. mix-blend-mode: difference inverts colours beneath it so it stays visible against any background.
The .parallax-bg stretches a subtle gradient behind the hero content using absolute positioning. The negative offsets prevent gaps from appearing when the element shifts during parallax animation.
- Save both files and refresh index.html in your browser.
- Your default cursor should now be invisible (the custom one will not appear until you add JavaScript). You should still see the full dark-themed page with all four sections.
Cursor still showing?
Check that cursor: none is inside the body rule in style.css. If it was added to a different selector it will not affect the whole page.
Also check that your browser tab is actively focused. Some browsers restore the cursor when the window loses focus.
help me figure out why my cursor is still visible
Before you move on, what do you expect will happen if you scroll down the page right now? Will the sections animate in, or will they just sit there?
- Scroll down through the full page to see what happens.
Everything appears all at once. The sections are simply there, with no entrance animation, no movement, and no response to scrolling. The page looks good but feels static.
Why does the page feel dead?
You have structure and style, but no behaviour. Nothing responds to your mouse. Nothing reacts to scrolling. This is the gap that JavaScript and GSAP will fill in the next steps.
✔️ Awesome, I've got everything!
Great. Make sure both index.html and style.css are saved.
ⓧ I'd like to double check the full code
Your files are over 30 lines each, so confirm these sections are present in order:
In index.html:
- A <div class="cursor-follower"></div> as the first child inside <body>.
- A hero section containing .parallax-bg, .hero-title, and .hero-subtitle.
- An about section with class section containing .section-title and .section-text.
- A work section with a .card-grid containing three .card divs.
- A contact section with .contact-link anchor.
- Three script tags before </body>: GSAP core, ScrollTrigger plugin, and script.js.
In style.css:
- A universal reset (* selector with margin, padding, box-sizing).
- Body with background-color: #0a0a0a, color: #f0f0f0, and cursor: none.
- The .cursor-follower rule (20px fixed white circle with mix-blend-mode: difference and opacity: 0).
- Hero styles (height: 100vh, flexbox centering, overflow: hidden).
- The .parallax-bg rule (absolute positioning with negative offsets and radial gradients).
- Section, card-grid, card, and contact-link styles.
Your page structure is solid and styled. Next up, you will make the cursor come alive by writing JavaScript that tracks your mouse and animates a glowing dot across the screen.
Make the Cursor Come Alive
Your page has structure, style, and content. But right now it feels like a poster pinned to a wall. Nothing responds when you interact with it.
A custom cursor follower is the simplest interaction that immediately makes a page feel alive. In this step, you will write JavaScript that listens for mouse events and uses GSAP to animate a glowing dot that tracks your every move.
In this step, get ready to:
- Register the GSAP ScrollTrigger plugin and make the cursor follow your mouse.
- Add a mouseleave fade and a cinematic hero entrance animation.
- Verify the full cursor interaction and notice the gap that remains for the next step.
Register GSAP and make the cursor track your mouse
Every time your mouse moves, the browser fires a mousemove event containing the cursor's x and y coordinates. You will listen for that event and use GSAP to smoothly animate the .cursor-follower element to the new position.
- Open script.js in Claude Code (it is currently empty).
- Paste the following code to register the plugin, grab the cursor element, and add the mousemove listener:
gsap.registerPlugin(ScrollTrigger);
// --- Custom Cursor Follower ---
const cursor = document.querySelector(".cursor-follower");
window.addEventListener("mousemove", (e) => {
const isLink = e.target.closest("a") || e.target.closest(".card");
gsap.to(cursor, {
x: e.clientX - 10,
y: e.clientY - 10,
duration: 0.4,
ease: "power2.out",
opacity: 1,
scale: isLink ? 3 : 1,
});
});
What does this code do?
This code makes the invisible cursor-follower div spring to life whenever your mouse moves.
- gsap.registerPlugin(ScrollTrigger) activates the ScrollTrigger plugin so it is ready for later steps.
- e.clientX - 10 offsets the position by half the cursor's 20px width, centering it on your mouse.
- e.target.closest("a") || e.target.closest(".card") checks whether the mouse is hovering over a link or card element.
- scale: isLink ? 3 : 1 makes the dot expand to 3x its size over interactive elements, giving clear hover feedback.
- Save script.js.
- Switch to your browser and refresh the page.
- Move your mouse across the page. You should see a white dot smoothly following your cursor.
- Hover over one of the work cards. The dot should scale up to 3x its normal size.
Don't see the white dot?
- Open DevTools (right-click the page, then click Inspect) and check the Console tab for errors. A typo in the selector .cursor-follower will silently fail.
- Confirm your index.html has <div class="cursor-follower"></div> as the first child inside <body>.
- Make sure the GSAP script tags in index.html load before script.js. If GSAP loads after your code, gsap will be undefined.
Why isn't my cursor follower appearing when I move my mouse?
Add mouseleave fade and hero entrance animation
The cursor tracks nicely, but it stays visible even when your mouse leaves the browser window. You also want the hero headline to animate in with cinematic timing when the page loads, rather than just appearing.
- In script.js, add the following code below the closing }); of the mousemove listener:
document.addEventListener("mouseleave", () => {
gsap.to(cursor, { opacity: 0, duration: 0.3 });
});
// --- Hero Animation on Load ---
const heroTl = gsap.timeline();
heroTl
.from(".hero-title", {
y: 100,
opacity: 0,
duration: 1.2,
ease: "power4.out",
})
.from(
".hero-subtitle",
{
y: 50,
opacity: 0,
duration: 1,
ease: "power4.out",
},
"-=0.5"
);
What does this code do?
- The mouseleave listener on document detects when the mouse exits the browser window and fades the dot to invisible.
- gsap.timeline() creates a sequence of animations that play one after another.
- .from(".hero-title", { y: 100, opacity: 0 }) means the title starts 100px below its final position and invisible, then animates up into place.
- "-=0.5" is a position parameter that tells GSAP to start the subtitle animation 0.5 seconds before the previous one finishes. This overlap creates a staggered feel.
- Save script.js.
- Refresh the page in your browser.
- Watch the hero section. Your name should slide up from below and fade in, followed closely by the subtitle.
- Move your mouse off the browser window entirely. The white dot should fade out.
Hero text not animating on load?
- Make sure the hero elements in your HTML have the exact classes hero-title and hero-subtitle. GSAP's selector won't find them if the class names differ.
- Check that heroTl is declared after the event listeners, not inside one. A timeline inside a listener only runs when that event fires.
- If the text appears already in place with no animation, you may have a cached page. Do a hard refresh with Cmd+Shift+R (macOS) or Ctrl+Shift+R (Windows).
Help me debug my GSAP hero timeline animation.
Verify the full cursor interaction
Before you move on, what do you expect to see when you scroll down past the hero section?
- Refresh the page and watch the hero animate in.
- Move your mouse across the page. The white dot follows smoothly.
- Hover over a card. The dot scales up to 3x.
- Move your mouse off the browser window. The dot fades out.
- Now scroll down through the Story, Highlights, and Connect sections.
All the content below the hero appears instantly with no animation. It just sits there, fully visible, with no ceremony. That flat reveal is the gap you will close in the next step.
✔️ Awesome, I've got everything!
Great. Double check you have saved script.js and your cursor interaction is working.
ⓧ I'd like to double check the full code
Your script.js should contain two blocks of code in this order. Scroll up and confirm each one is present:
- The Custom Cursor Follower block: gsap.registerPlugin(ScrollTrigger), the cursor variable, and the mousemove event listener.
- The mouseleave and Hero Animation block: the mouseleave listener, heroTl timeline with .hero-title and .hero-subtitle animations.
Your index.html and style.css should be unchanged from the previous step.
Your page responds to every mouse movement now. The cursor follower and hero entrance prove that JavaScript can listen for events and GSAP can animate anything smoothly. Next up, you will tackle that flat scroll experience and make each section reveal itself as visitors scroll down.
Animate on Scroll
Your cursor now tracks every movement and reacts to hoverable elements. But scroll down the page and the Story, Highlights, and Connect sections simply sit there, fully visible with no entrance.
GSAP's ScrollTrigger plugin links animations to scroll position. Instead of everything appearing at once, content reveals itself as visitors reach it.
In this step, get ready to:
- Add scroll-triggered animations that reveal each section as it enters the viewport.
- Visualize trigger points with debug markers to confirm timing.
Add scroll-triggered section reveals
ScrollTrigger watches the scroll position and fires animations when elements enter the viewport. You define where in the viewport the trigger activates, and what happens when the learner scrolls past it or back above it.
- In script.js, find the closing "-=0.5" line at the end of your hero timeline.
- Add the following code below the hero timeline's closing ); to create scroll-triggered reveals for every section:
// --- Scroll-Triggered Section Reveals ---
gsap.utils.toArray(".section").forEach((section) => {
gsap.from(
section.querySelectorAll(
".section-title, .section-text, .card-grid, .contact-link"
),
{
y: 80,
opacity: 0,
duration: 1,
stagger: 0.2,
ease: "power3.out",
scrollTrigger: {
trigger: section,
start: "top 80%",
toggleActions: "play none none reverse",
},
}
);
});
What does this code do?
This block selects every element with the class .section (About, Work, and Contact) and loops over them.
- gsap.from() animates elements FROM the values you specify (invisible and shifted down) TO their natural CSS position.
- stagger: 0.2 spaces each child element's entrance by 0.2 seconds, creating a cascading reveal.
- trigger: section tells ScrollTrigger to watch this specific section's position in the viewport.
- start: "top 80%" means the animation fires when the section's top edge reaches 80% down the viewport.
- toggleActions: "play none none reverse" plays the animation on enter and reverses it when you scroll back above the trigger point.
- Save script.js.
- Refresh your browser and scroll down slowly. Each section's title, text, and cards should fade up smoothly as they enter the viewport.
- Scroll back up past a section. The elements should reverse and disappear again.
Sections still appearing all at once?
- Make sure the code is placed AFTER the hero timeline block, not inside it.
- Check that your HTML sections have the class section (e.g. <section class="about section">). The .section selector only matches elements with that class.
- Open the browser console and look for errors. A typo in the selector string will silently fail.
Why aren't my scroll animations working?
Visualize your trigger points with markers
ScrollTrigger has a built-in debug mode that draws colored lines on the page showing exactly where each trigger starts and ends. This helps you confirm the animations fire at the right scroll positions.
- In script.js, find this line inside your scrollTrigger config:
toggleActions: "play none none reverse",
- Add markers: true, on a new line directly below it, so your scrollTrigger config now looks like this:
scrollTrigger: {
trigger: section,
start: "top 80%",
toggleActions: "play none none reverse",
markers: true,
},
- Save script.js and refresh your browser.
- You should see pairs of colored lines (green for start, red for end) on the right side of the viewport showing where each trigger fires.
- Scroll down and confirm each section's animation begins right as its green marker crosses the corresponding viewport marker.
Why use markers for debugging?
If an animation fires too early or too late, markers reveal the problem instantly. You can adjust the start value (e.g. change "top 80%" to "top 60%") and see the marker shift in real time.
- Once you are satisfied with the trigger timing, remove the markers: true, line from your scrollTrigger config.
- Save script.js and refresh to confirm the colored lines are gone.
Before you scroll through the full page one more time, predict: what will happen when you scroll all the way down and then back up to the top?
Each section's children fade up as you reach them. Scrolling back reverses the animation, so they slide back down and disappear. The page feels like it is revealing itself in response to you.
✔️ Awesome, I've got everything!
Great. Double check you have saved script.js and that markers: true has been removed.
ⓧ I'd like to double check the full code
Your script.js has grown across two steps. Scroll up and confirm these sections are present, in order:
- gsap.registerPlugin(ScrollTrigger); at the very top.
- The Custom Cursor Follower block (const cursor, mousemove listener, mouseleave listener).
- The Hero Animation on Load block (heroTl timeline with .hero-title and .hero-subtitle).
- The Scroll-Triggered Section Reveals block (gsap.utils.toArray(".section").forEach() with scrollTrigger config, NO markers: true).
Your index.html and style.css are unchanged from the previous step.
Your page now responds to scrolling with cinematic section reveals. But the page still feels flat. Next, you will add depth with a parallax background that drifts at a different speed and hover effects that make cards feel tactile.
Add Depth with Parallax and Hover
Your page animates on scroll, but it still feels flat. Every element moves at the same speed, like a slideshow rather than a living space.
Parallax fixes this by moving background layers at a different speed than foreground content, creating the illusion of depth. Hover effects add another layer of responsiveness, making cards feel tactile under your cursor.
In this step, get ready to:
- Add a parallax drift to the hero background gradient.
- Add hover animations that scale cards up on interaction.
- Test the full interactive experience end to end.
Add a parallax effect to the hero background
The .parallax-bg element is already in your HTML and styled as a gradient layer behind the hero content. Right now it scrolls at the same speed as everything else. You need it to move slower, creating the feeling of depth.
- In script.js, scroll to the bottom of your file (after the section reveals forEach loop).
- Add the parallax animation by pasting this code at the bottom:
// --- Parallax Background ---
gsap.to(".parallax-bg", {
y: "-20%",
ease: "none",
scrollTrigger: {
trigger: ".hero",
start: "top top",
end: "bottom top",
scrub: true,
},
});
What does this code do?
This animates the .parallax-bg element upward by 20% of its height as you scroll through the hero section.
- scrub: true links animation progress directly to the scrollbar position. Instead of playing once, the animation scrubs forward and backward as you scroll.
- start: "top top" means the animation begins when the hero's top reaches the viewport's top.
- end: "bottom top" means the animation completes when the hero's bottom reaches the viewport's top (i.e. when the hero scrolls fully out of view).
- ease: "none" ensures a linear relationship between scroll distance and movement. No acceleration, no deceleration.
- Save script.js.
- Refresh your browser and scroll slowly through the hero section. You should see the gradient background drift upward at a slower rate than the rest of the page content.
Background not drifting?
- Make sure the code is placed after the section reveals loop, not inside it.
- Check that your .parallax-bg element in index.html is inside the .hero section. The trigger depends on it being a child of that section.
- The effect is subtle. Scroll slowly and watch the gradient move relative to the text.
Why isn't my parallax background moving when I scroll?
Add hover animations to the work cards
Your cards already change border color on hover via CSS. Adding a scale animation with GSAP makes them feel physical, like they lift toward you when you reach for them.
- Add the card hover animation code below the parallax code you just wrote:
// --- Card Hover Animations ---
document.querySelectorAll(".card").forEach((card) => {
card.addEventListener("mouseenter", () => {
gsap.to(card, { scale: 1.03, duration: 0.3, ease: "power2.out" });
});
card.addEventListener("mouseleave", () => {
gsap.to(card, { scale: 1, duration: 0.3, ease: "power2.out" });
});
});
What does this code do?
- document.querySelectorAll(".card") grabs all three cards at once, then .forEach() attaches listeners to each one individually.
- On mouseenter, the card scales up to 1.03x (a 3% increase). This is subtle enough to feel natural.
- On mouseleave, it animates back to scale 1. GSAP handles the smooth transition between states automatically.
- Save script.js.
- Refresh your browser, scroll down to the Work section, and hover over any card. You should see it subtly scale up to 1.03x, then shrink back when your cursor leaves.
Cards not scaling on hover?
- Open your browser's DevTools console and check for errors. A typo in querySelectorAll or a missing closing bracket will prevent the code from running.
- Make sure the class name .card in your JavaScript matches class="card" in your HTML. Class names are case-sensitive.
Why aren't my card hover animations working?
Test the full interactive experience
You now have four layers of interaction working together. Before you call this step done, test them all in sequence to confirm nothing conflicts.
Before you run through the page, predict: when you scroll through the hero section, will the background gradient move faster or slower than the foreground text?
- Refresh your browser and start at the top of the page.
- Move your mouse around. Confirm the white dot follows your cursor smoothly.
- Scroll slowly through the hero section. Watch the gradient background drift upward slower than the rest of the page.
- Continue scrolling past the hero. Confirm each section fades up as it enters the viewport.
- Hover over a work card. Confirm it scales up slightly, and the cursor dot expands to 3x over it.
- Scroll back up. Confirm the section reveals reverse and the parallax background drifts back down.
The background moves slower than the foreground. That speed difference is what creates the illusion of depth. Your page now feels layered and responsive to every interaction.
Why does parallax create depth?
In the real world, distant objects appear to move slower than nearby ones when you shift your viewpoint. By moving the background gradient at a different rate than the foreground text, you trick the viewer's brain into perceiving layers of distance on a flat screen.
✔️ Awesome, I've got everything!
Great. Make sure you have saved script.js with all your changes.
ⓧ I'd like to double check the full code
Your script.js should contain these sections in this order. Scroll up and confirm each one is present:
- gsap.registerPlugin(ScrollTrigger) at the very top.
- Custom Cursor Follower section with mousemove and mouseleave listeners.
- Hero Animation on Load section with the heroTl timeline.
- Scroll-Triggered Section Reveals with the .section forEach loop.
- Parallax Background section with gsap.to(".parallax-bg", {...}) and scrub: true.
- Card Hover Animations section with mouseenter and mouseleave listeners on each card.
Your interactive experience now responds to every interaction: cursor tracking, scroll reveals, parallax depth, and tactile hover effects. Next up, you will take on a Secret Mission to add a scroll progress bar and a dynamic gradient that shifts color as visitors scroll.
Secret mission
Add a Scroll Progress Bar and Dynamic Gradient
Your page responds to the cursor, scrolls with cinematic reveals, and has layered depth. But visitors still have no sense of how far down the page they are. Take it further by adding a scroll progress indicator and a background gradient that shifts color as the viewer travels through your content.
Clean Up Your Resources
Clean Up Your Resources
This project runs entirely locally, so there are no ongoing costs. Decide whether to keep your resources as they are, deploy them to share with others, or delete them entirely.
Resources you used:
- A local portfolio-microsite folder on your Desktop containing index.html, style.css, and script.js.
- A Paper Desktop design file (free tier, stays in your account).
Keep everything running
No action needed. Choose this if you want to keep iterating on your portfolio or use the code as a reference for future projects.
- Your files are local. Open index.html in your browser anytime to see your interactive portfolio.
- Your Paper Desktop design file remains in the app for future reference.
- The Paper MCP plugin stays installed in Claude Code for future design-to-code workflows.
Pause - I'll come back to this later
Since everything is local, there is nothing to pause. Your files sit on your Desktop until you decide to return or delete them.
- Your project folder stays on your Desktop with no ongoing cost or resource usage.
- To pick back up, open index.html in your browser and script.js in Claude Code.
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 your terminal:
rm -rf ~/Desktop/portfolio-microsite
- On Windows, run this instead:
rmdir /s /q %USERPROFILE%\Desktop\portfolio-microsite
- In Paper Desktop, delete the "portfolio-microsite" design file from your files list.
That's a Wrap!
That's a Wrap!
Nice work. You just built an interactive experience that responds to every gesture with smooth, intentional animation.
You've learned how to:
- Connected Paper Desktop to Claude Code via MCP, establishing a design-to-code workflow that bridges visual design with real HTML and CSS.
- Wrote a custom cursor follower and scroll-triggered section reveals using JavaScript event listeners and GSAP animation.
- Created parallax depth and hover effects that make the page feel layered and responsive to every interaction.
- Secret Mission: Added a scroll progress indicator and a dynamic gradient that shifts color as the viewer scrolls through the page.
Ready to quiz yourself?