Turn Your GitHub Profile 3D
Build a scroll-triggered 3D portfolio site and deploy it to GitHub Pages.
Introduction
30 Second Summary
Think about scrolling through a website where content rotates into view, cards flip in 3D, and sections fly toward you as you move down the page. That kind of motion design is what separates memorable portfolios from flat, forgettable ones.
In this project, you will build a 3D portfolio webpage that recreates your GitHub profile content with scroll-triggered animations. You will deploy it live for free using GitHub Pages.
What You'll Build
A single-page portfolio website with CSS 3D transforms and GSAP scroll animations, styled with a dark GitHub-inspired theme and hosted for free on GitHub Pages.
By the end of this project, you'll have:
- A live portfolio webpage with semantic sections for your hero, about, skills, projects, and contact info.
- Working CSS 3D flip cards and hover effects using perspective, transform-style, and backface-visibility.
- Scroll-triggered GSAP ScrollTrigger animations that rotate, fly in, and scale sections into view as visitors scroll down the page.
- Secret Mission: A parallax starfield background that drifts at different speeds as you scroll, giving the page a deep space effect.
Are there any prerequisites?
None. This project includes setup instructions for every tool you need. You will install VS Code, set up a live preview server, and learn Git basics along the way.
Before We Start
Before We Start...
Set Up Your Project
Every 3D scroll experience starts as a set of plain files. Before adding any animations, you need a workspace where your HTML, CSS, and JavaScript can live together.
In this step, you'll create your project folder, install a tool that auto-refreshes your browser as you code, and wire up the animation library that powers the 3D effects.
In this step, get ready to:
- Create a project folder with three starter files.
- Install the Live Server extension for live browser previews.
- Set up HTML boilerplate with GSAP animation library links.
Create your project folder and files
You need a dedicated folder to hold the three files that make up your portfolio: the HTML structure, the CSS styles, and the JavaScript animations.
- Press Cmd+Space (macOS) or the Windows key (Windows) to open your search bar.
- Type Visual Studio Code and press Enter to open it.
- Open VS Code's integrated terminal by clicking Terminal in the top menu bar, then selecting New Terminal.
- Move to your Desktop by running this command:
cd ~/Desktop
- Create your project folder and move into it by running these commands:
mkdir 3d-portfolio
cd 3d-portfolio
- Create the three project files by running this command:
macOS
touch index.html style.css script.js
Windows
New-Item index.html, style.css, script.js
- Confirm the files exist by running:
ls
You should see index.html, style.css, and script.js in the output.
- In VS Code, click File in the top menu bar.
- Click Open Folder.
- Navigate to your Desktop, select the 3d-portfolio folder, and click Open.
VS Code's Explorer sidebar now shows your three empty files.
Don't see your files in the sidebar?
Make sure you opened the 3d-portfolio folder itself, not the Desktop folder. If you see other Desktop files in the sidebar, go to File > Open Folder again and select only the 3d-portfolio folder.
help me find my project files in VS Code
Install the Live Server extension
Live Server is a VS Code extension that launches a local development server. Every time you save a file, it automatically refreshes your browser so you can see changes instantly.
- Click the Extensions icon in the left sidebar (it looks like four small squares).
- Type Live Server in the search bar at the top of the Extensions panel.
- Find the result by Ritwick Dey and click Install.
After a few seconds, the extension finishes installing and you'll see a Go Live button appear in VS Code's bottom status bar.
Don't see the Go Live button?
Try closing and reopening VS Code. Some extensions require a window reload to activate. You can also press Cmd+Shift+P (macOS) or Ctrl+Shift+P (Windows), type Reload Window, and press Enter.
help me get Live Server working in VS Code
Set up the HTML boilerplate with GSAP
Your index.html file needs a basic HTML structure, a link to your stylesheet, and script tags that load the GSAP animation library from a CDN. GSAP's ScrollTrigger plugin is what makes animations respond to scrolling.
- In VS Code's Explorer sidebar, click index.html to open it.
- Paste the following code into the file:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>keylocker006 | 3D Portfolio</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<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>
</body>
</html>
What does this code do?
- The <head> section sets the page title and links to your style.css file so the browser knows where to find your styles.
- The two GSAP <script> tags load the core animation engine and the ScrollTrigger plugin from a CDN. Loading them from a CDN means no installation is needed.
- Your script.js file is linked last so it can use everything that GSAP loaded before it.
- Save index.html by pressing Cmd+S (macOS) or Ctrl+S (Windows).
- Right-click anywhere inside the index.html editor and select Open with Live Server.
Your default browser opens with a blank white page. That blank page means everything is wired up correctly.
Seeing a file list instead of a blank page?
If your browser shows a list of files instead of a blank page, you may have right-clicked the wrong file or opened Live Server from the folder level. Close the browser tab, right-click inside the index.html editor content (not the filename in the sidebar), and select Open with Live Server again.
help me get Live Server to show my page
✔️ Awesome, I've got everything!
Great. Double check you've saved index.html and that your browser shows a blank page with Live Server running.
ⓧ I'd like to double check the full code
Here is the complete index.html file at this point:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>keylocker006 | 3D Portfolio</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<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>
</body>
</html>
Your style.css and script.js files should both be empty at this point.
Your workspace is ready. Next up, you'll build the HTML structure that gives your portfolio its five main sections.
Build the HTML Structure
Your project files are ready and Live Server is showing a blank page. Now it's time to fill that page with content.
Every 3D animation needs something to animate. In this step, you'll build the five sections of your portfolio page as plain HTML. Once the structure is in place, you'll layer on 3D styles and scroll animations in the next steps.
In this step, get ready to:
- Create the Hero and About sections with your username and bio.
- Create the Skills section with a 2x2 grid and the Projects section with 3D flip cards.
- Create the Contact section with a link to your GitHub profile.
Create the Hero and About sections
The hero is the first thing visitors see. It displays your GitHub username prominently with a tagline underneath. The about section follows with a short bio card.
- In index.html, find the empty space between the opening <body> tag and the first <script> tag.
- Add the Hero and About sections by pasting this code into that empty space:
<section class="hero" id="hero">
<div class="hero-content">
<h1>keylocker006</h1>
<p class="tagline">Developer & Creator</p>
</div>
</section>
<section class="about" id="about">
<h2>About Me</h2>
<div class="about-card">
<p>Welcome to my 3D portfolio. I'm a developer passionate about building cool things on the web.</p>
</div>
</section>
What does this code do?
Each <section> has a class (for styling later) and an id (for scroll targeting). The hero wraps your name in an <h1> and your tagline in a paragraph.
The & is the HTML entity for the ampersand symbol (&). The about-card div will become a styled card with 3D depth in the CSS step.
- Save index.html.
- Check your browser. You should see "keylocker006" as a large heading, "Developer & Creator" below it, then "About Me" and the bio paragraph.
Don't see anything in the browser?
Make sure the code sits between <body> and the first <script> tag. If you paste it after the scripts, it will still render, but keeping the order correct now prevents issues in later steps.
Also confirm Live Server is still running (look for "Port: 5500" in the VS Code status bar). If it stopped, right-click index.html and select Open with Live Server again.
Why isn't my HTML showing up in the browser?
Create the Skills and Projects sections
The skills section uses a grid of cards. The projects section introduces a more complex structure. Each project card has three layers: an outer container, an inner wrapper that rotates, and two faces (front and back) that swap on hover.
- In index.html, place your cursor right after the closing </section> tag of the About section.
- Add the Skills section by pasting this code below it:
<section class="skills" id="skills">
<h2>Skills</h2>
<div class="skills-grid">
<div class="skill-card">
<h3>HTML</h3>
</div>
<div class="skill-card">
<h3>CSS</h3>
</div>
<div class="skill-card">
<h3>JavaScript</h3>
</div>
<div class="skill-card">
<h3>Git</h3>
</div>
</div>
</section>
What does this code do?
The skills-grid div wraps four skill-card divs. In the CSS step, you'll turn this into a 2x2 grid where each card tilts in 3D on hover.
- Now add the Projects section directly after the Skills section's closing </section> tag by pasting this code:
<section class="projects" id="projects">
<h2>Projects</h2>
<div class="projects-grid">
<div class="project-card">
<div class="project-card-inner">
<div class="project-card-front">
<h3>Project 1</h3>
</div>
<div class="project-card-back">
<p>A cool project I built.</p>
<a href="https://github.com/keylocker006">View on GitHub</a>
</div>
</div>
</div>
<div class="project-card">
<div class="project-card-inner">
<div class="project-card-front">
<h3>Project 2</h3>
</div>
<div class="project-card-back">
<p>Another awesome project.</p>
<a href="https://github.com/keylocker006">View on GitHub</a>
</div>
</div>
</div>
<div class="project-card">
<div class="project-card-inner">
<div class="project-card-front">
<h3>Project 3</h3>
</div>
<div class="project-card-back">
<p>Something experimental.</p>
<a href="https://github.com/keylocker006">View on GitHub</a>
</div>
</div>
</div>
</div>
</section>
Why three layers per project card?
Each project card uses a three-layer structure. The outer .project-card sets the card's size and perspective. The .project-card-inner is the piece that rotates on hover.
The two faces (.project-card-front and .project-card-back) are stacked on top of each other. CSS will hide whichever face is turned away from the viewer using backface-visibility.
- Save index.html.
- Check your browser. You should now see "Skills" followed by HTML, CSS, JavaScript, and Git listed out. Below that, "Projects" with all three project names and their descriptions visible (the flip effect comes later with CSS).
Seeing the back-face text jumbled with the front?
That's expected right now. Without CSS, both the front and back face content are visible at the same time. You'll fix this in the next step when you add backface-visibility: hidden to the card faces.
If you're missing sections entirely, check that every opening <div> tag has a matching </div>. The project cards have three nested div levels, so it's easy to miss one.
My project card structure looks wrong in the browser. Can you help me check it?
Create the Contact section
- Add the Contact section after the Projects section's closing </section> tag by pasting this code:
<section class="contact" id="contact">
<h2>Get In Touch</h2>
<div class="contact-content">
<a href="https://github.com/keylocker006" class="contact-link">GitHub</a>
</div>
</section>
What does this code do?
The contact section wraps a link to your GitHub profile inside a contact-content div. In the animation step, this entire section will scale up from the distance as the user scrolls to it.
- Save index.html.
- Check your browser. You should see "Get In Touch" with a clickable "GitHub" link at the bottom of the page. All five sections (hero, about, skills, projects, contact) should now appear as plain unstyled text.
GitHub link not clickable?
Make sure the href attribute has quotes around the URL and starts with https://. A missing quote or typo will break the link.
My contact link isn't working. Help me troubleshoot.
✔️ Awesome, I've got everything!
Great. Double check you've saved index.html and all five sections are visible in your browser.
ⓧ I'd like to double check the full code
Here's the complete index.html file. Compare your code against this:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>keylocker006 | 3D Portfolio</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<section class="hero" id="hero">
<div class="hero-content">
<h1>keylocker006</h1>
<p class="tagline">Developer & Creator</p>
</div>
</section>
<section class="about" id="about">
<h2>About Me</h2>
<div class="about-card">
<p>Welcome to my 3D portfolio. I'm a developer passionate about building cool things on the web.</p>
</div>
</section>
<section class="skills" id="skills">
<h2>Skills</h2>
<div class="skills-grid">
<div class="skill-card">
<h3>HTML</h3>
</div>
<div class="skill-card">
<h3>CSS</h3>
</div>
<div class="skill-card">
<h3>JavaScript</h3>
</div>
<div class="skill-card">
<h3>Git</h3>
</div>
</div>
</section>
<section class="projects" id="projects">
<h2>Projects</h2>
<div class="projects-grid">
<div class="project-card">
<div class="project-card-inner">
<div class="project-card-front">
<h3>Project 1</h3>
</div>
<div class="project-card-back">
<p>A cool project I built.</p>
<a href="https://github.com/keylocker006">View on GitHub</a>
</div>
</div>
</div>
<div class="project-card">
<div class="project-card-inner">
<div class="project-card-front">
<h3>Project 2</h3>
</div>
<div class="project-card-back">
<p>Another awesome project.</p>
<a href="https://github.com/keylocker006">View on GitHub</a>
</div>
</div>
</div>
<div class="project-card">
<div class="project-card-inner">
<div class="project-card-front">
<h3>Project 3</h3>
</div>
<div class="project-card-back">
<p>Something experimental.</p>
<a href="https://github.com/keylocker006">View on GitHub</a>
</div>
</div>
</div>
</div>
</section>
<section class="contact" id="contact">
<h2>Get In Touch</h2>
<div class="contact-content">
<a href="https://github.com/keylocker006" class="contact-link">GitHub</a>
</div>
</section>
<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>
</body>
</html>
Your HTML skeleton is complete. All five sections are on the page, and you've got the three-layer flip card structure ready for 3D effects. Next up, you'll bring this flat text to life with CSS 3D perspective and transforms.
Style with CSS 3D Perspective
Your HTML structure is in place, but everything looks like unstyled white text on a white background. Time to transform it into a dark, immersive 3D experience.
In this step, you will write all the CSS that gives your portfolio its GitHub-inspired dark theme and 3D depth. The key property is perspective, which tells the browser to render child elements in a 3D space instead of flat on the screen.
In this step, get ready to:
- Set up the dark theme and 3D-enabled section layout.
- Style skill cards and project flip cards with 3D hover effects.
- Style the about card and contact link with depth transforms.
Set up the base styles and hero section
Every 3D effect on this page depends on two CSS foundations: a dark background that makes elements pop, and perspective on each section so child elements can rotate and translate in 3D space.
- Switch back to VS Code and open style.css (currently empty).
- Add the base reset, body, and section styles by pasting this code:
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
background: #0d1117;
color: #e6edf3;
overflow-x: hidden;
}
section {
min-height: 100vh;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 4rem 2rem;
perspective: 1000px;
}
What does this code do?
- The * selector resets all margins and padding so spacing is consistent across browsers.
- background: #0d1117 sets the dark GitHub-style background, and overflow-x: hidden prevents horizontal scrollbars from 3D elements that extend off-screen.
- perspective: 1000px on every section creates a 3D viewing context. Lower values make rotations more dramatic. 1000px is a balanced starting point.
- Save style.css and check your browser.
- The page background should now be dark (#0d1117) with light text, and each section takes up the full viewport height.
Still seeing a white background?
- Confirm that style.css is saved and that your index.html contains <link rel="stylesheet" href="style.css"> in the <head>.
- Make sure Live Server is still running. If the browser tab closed, right-click index.html and select Open with Live Server again.
Why isn't my dark background showing up?
- Now add the hero section styles below the section rule by pasting this code:
.hero {
background: linear-gradient(135deg, #0d1117 0%, #161b22 100%);
}
.hero-content {
text-align: center;
transform-style: preserve-3d;
}
.hero-content h1 {
font-size: 4rem;
background: linear-gradient(90deg, #58a6ff, #bc8cff);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
}
.hero-content .tagline {
font-size: 1.5rem;
color: #8b949e;
margin-top: 1rem;
}
What does this code do?
- transform-style: preserve-3d tells the browser that children of .hero-content should be rendered in 3D space, not flattened.
- background-clip: text clips the blue-to-purple gradient so it only shows through the text characters, creating a colorful heading with a transparent fill.
- Save style.css and check your browser.
- The keylocker006 heading should now be large with a blue-to-purple gradient color, centered on the page.
Heading text is invisible?
- If the heading disappears entirely, check that you have both -webkit-background-clip: text and -webkit-text-fill-color: transparent in your .hero-content h1 rule. Missing either one can cause the text to vanish.
- Help me debug my gradient text heading.
Style the skill cards and project flip cards
Now for the interactive 3D elements. Skill cards will tilt in 3D space when you hover over them. Project cards will perform a full 180-degree flip to reveal their back face.
- Add the skills grid and skill card styles below the hero code by pasting this:
.skills-grid {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 1.5rem;
max-width: 500px;
perspective: 1000px;
}
.skill-card {
background: #161b22;
border: 1px solid #30363d;
border-radius: 12px;
padding: 2rem;
text-align: center;
transform-style: preserve-3d;
transition: transform 0.3s ease;
}
.skill-card:hover {
transform: rotateY(10deg) rotateX(5deg) scale(1.05);
}
How does the 3D tilt work?
transform-style: preserve-3d keeps the card in 3D space. On hover, rotateY(10deg) tilts the card sideways, rotateX(5deg) tilts it forward, and scale(1.05) makes it slightly larger.
The transition: transform 0.3s ease makes the tilt animate smoothly instead of snapping.
- Save style.css and scroll down to the Skills section in your browser.
- You should see four dark cards in a 2x2 grid. Hover over any card and it tilts in 3D space.
Cards not tilting on hover?
- Make sure transform-style: preserve-3d is on .skill-card, not on .skills-grid. The element being transformed needs to preserve its own 3D context.
- Check that transition: transform 0.3s ease is present. Without it the tilt happens instantly and can be hard to notice.
- Why aren't my skill cards tilting in 3D on hover?
- Now add the project flip card styles below the skill card code by pasting this:
.projects-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
gap: 2rem;
max-width: 900px;
width: 100%;
}
.project-card {
height: 200px;
perspective: 1000px;
}
.project-card-inner {
position: relative;
width: 100%;
height: 100%;
transform-style: preserve-3d;
transition: transform 0.6s ease;
}
.project-card:hover .project-card-inner {
transform: rotateY(180deg);
}
.project-card-front,
.project-card-back {
position: absolute;
width: 100%;
height: 100%;
backface-visibility: hidden;
border-radius: 12px;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 1.5rem;
}
.project-card-front {
background: linear-gradient(135deg, #161b22, #1f2937);
border: 1px solid #30363d;
}
.project-card-front h3 {
font-size: 1.3rem;
}
.project-card-back {
background: linear-gradient(135deg, #1f2937, #161b22);
border: 1px solid #58a6ff;
transform: rotateY(180deg);
}
.project-card-back a {
color: #58a6ff;
text-decoration: none;
margin-top: 1rem;
border: 1px solid #58a6ff;
padding: 0.5rem 1rem;
border-radius: 6px;
transition: background 0.3s ease;
}
.project-card-back a:hover {
background: #58a6ff;
color: #0d1117;
}
How does the card flip work?
The flip card uses three layers. The outer .project-card sets up the 3D viewing distance with perspective: 1000px. The .project-card-inner wrapper holds both faces and rotates 180 degrees on hover.
backface-visibility: hidden hides whichever face is rotated away from you. The back face starts pre-rotated 180 degrees with transform: rotateY(180deg), so when the inner container flips, the back face ends up facing forward.
- Save style.css and scroll down to the Projects section.
- You should see three dark project cards. Hover over any card and it flips to reveal a blue-bordered back face with a GitHub link.
Cards not flipping?
- Confirm your HTML has the three-layer structure: .project-card > .project-card-inner > .project-card-front + .project-card-back. Missing a layer breaks the flip.
- If both faces show at once, check that backface-visibility: hidden is on both .project-card-front and .project-card-back.
- My project cards aren't flipping on hover. Help me debug.
Style the about card and contact links
The about card and contact link complete the page styling. The contact link uses translateZ to float toward the viewer on hover, adding subtle depth.
- Scroll up in style.css to find the .hero-content .tagline rule. Add the about card and heading styles directly below it (above .skills-grid):
.about-card {
background: #161b22;
border: 1px solid #30363d;
border-radius: 12px;
padding: 2rem;
max-width: 600px;
transform-style: preserve-3d;
}
h2 {
font-size: 2.5rem;
margin-bottom: 2rem;
color: #58a6ff;
}
What does this code do?
- .about-card gives the bio a contained card appearance with a dark background and subtle border, matching the GitHub panel style.
- The h2 rule styles all section headings with a blue color (#58a6ff) that matches the GitHub link color palette.
- Now scroll to the very bottom of style.css (below the .project-card-back a:hover rule) and add the contact styles:
.contact-content {
display: flex;
gap: 1.5rem;
}
.contact-link {
color: #e6edf3;
text-decoration: none;
font-size: 1.2rem;
padding: 1rem 2rem;
border: 1px solid #30363d;
border-radius: 8px;
transition: all 0.3s ease;
transform-style: preserve-3d;
}
.contact-link:hover {
border-color: #58a6ff;
transform: translateZ(20px);
box-shadow: 0 10px 30px rgba(88, 166, 255, 0.2);
}
What does translateZ do?
translateZ(20px) moves the element 20 pixels toward the viewer along the z-axis. Combined with the section's perspective: 1000px, this creates a subtle "lifting off the page" effect. The blue box-shadow reinforces the floating illusion.
- Save style.css and scroll through the entire page in your browser.
- The About section should show a dark bordered card. All section headings (About Me, Skills, Projects, Get In Touch) should be blue. The GitHub contact link at the bottom should glow and float forward on hover.
Contact link not floating on hover?
- Make sure transform-style: preserve-3d is on .contact-link itself. Without a 3D context, translateZ has no visible effect.
- Check that the parent section still has perspective: 1000px from the base styles. The parent perspective gives translateZ its visual depth.
- Why doesn't my contact link hover effect look 3D?
✔️ Awesome, I've got everything!
Great. Double check you have saved style.css and that skill cards tilt on hover and project cards flip on hover.
ⓧ I'd like to double check the full code
Here is the complete style.css file. Make sure your code matches this:
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
background: #0d1117;
color: #e6edf3;
overflow-x: hidden;
}
section {
min-height: 100vh;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 4rem 2rem;
perspective: 1000px;
}
.hero {
background: linear-gradient(135deg, #0d1117 0%, #161b22 100%);
}
.hero-content {
text-align: center;
transform-style: preserve-3d;
}
.hero-content h1 {
font-size: 4rem;
background: linear-gradient(90deg, #58a6ff, #bc8cff);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
}
.hero-content .tagline {
font-size: 1.5rem;
color: #8b949e;
margin-top: 1rem;
}
.about-card {
background: #161b22;
border: 1px solid #30363d;
border-radius: 12px;
padding: 2rem;
max-width: 600px;
transform-style: preserve-3d;
}
h2 {
font-size: 2.5rem;
margin-bottom: 2rem;
color: #58a6ff;
}
.skills-grid {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 1.5rem;
max-width: 500px;
perspective: 1000px;
}
.skill-card {
background: #161b22;
border: 1px solid #30363d;
border-radius: 12px;
padding: 2rem;
text-align: center;
transform-style: preserve-3d;
transition: transform 0.3s ease;
}
.skill-card:hover {
transform: rotateY(10deg) rotateX(5deg) scale(1.05);
}
.projects-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
gap: 2rem;
max-width: 900px;
width: 100%;
}
.project-card {
height: 200px;
perspective: 1000px;
}
.project-card-inner {
position: relative;
width: 100%;
height: 100%;
transform-style: preserve-3d;
transition: transform 0.6s ease;
}
.project-card:hover .project-card-inner {
transform: rotateY(180deg);
}
.project-card-front,
.project-card-back {
position: absolute;
width: 100%;
height: 100%;
backface-visibility: hidden;
border-radius: 12px;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 1.5rem;
}
.project-card-front {
background: linear-gradient(135deg, #161b22, #1f2937);
border: 1px solid #30363d;
}
.project-card-front h3 {
font-size: 1.3rem;
}
.project-card-back {
background: linear-gradient(135deg, #1f2937, #161b22);
border: 1px solid #58a6ff;
transform: rotateY(180deg);
}
.project-card-back a {
color: #58a6ff;
text-decoration: none;
margin-top: 1rem;
border: 1px solid #58a6ff;
padding: 0.5rem 1rem;
border-radius: 6px;
transition: background 0.3s ease;
}
.project-card-back a:hover {
background: #58a6ff;
color: #0d1117;
}
.contact-content {
display: flex;
gap: 1.5rem;
}
.contact-link {
color: #e6edf3;
text-decoration: none;
font-size: 1.2rem;
padding: 1rem 2rem;
border: 1px solid #30363d;
border-radius: 8px;
transition: all 0.3s ease;
transform-style: preserve-3d;
}
.contact-link:hover {
border-color: #58a6ff;
transform: translateZ(20px);
box-shadow: 0 10px 30px rgba(88, 166, 255, 0.2);
}
Your portfolio now looks like a polished dark-themed site with interactive 3D hover effects. Next up, you will bring the page to life with scroll-triggered animations using GSAP.
Animate with GSAP ScrollTrigger
Your page looks great with its dark theme and 3D hover effects. But right now, everything is static when you scroll. The sections just sit there, waiting.
In this step, you will use GSAP and its ScrollTrigger plugin to make each section animate into view as the user scrolls. Sections will rotate, fly in from depth, and scale up in 3D space.
In this step, get ready to:
- Register the ScrollTrigger plugin and animate the hero entrance.
- Add scroll-triggered 3D animations to the About and Skills sections.
- Add scroll-triggered 3D animations to the Projects and Contact sections.
Register ScrollTrigger and animate the hero
GSAP's ScrollTrigger plugin lets you link animations to scroll position. Before using it, you need to register the plugin. Then you can create your first animation: a dramatic entrance for the hero section that plays immediately on page load.
- Switch back to script.js in VS Code.
- Add the following code to register the plugin and create the hero entrance animations:
gsap.registerPlugin(ScrollTrigger);
gsap.from(".hero-content h1", {
duration: 1.5,
y: -100,
opacity: 0,
rotateX: 90,
ease: "power3.out"
});
gsap.from(".hero-content .tagline", {
duration: 1.5,
y: 50,
opacity: 0,
delay: 0.5,
ease: "power3.out"
});
What does this code do?
gsap.registerPlugin(ScrollTrigger) activates the ScrollTrigger plugin so you can use scroll-based triggers in later animations.
gsap.from() animates an element FROM a starting state back to its natural CSS position. The heading starts rotated 90 degrees on the X axis (flipped backward) and invisible, then rotates and fades into place over 1.5 seconds. The tagline starts below and fades in with a 0.5 second delay so it appears after the heading.
What is gsap.from()?
Think of gsap.from() as describing where an element was hiding before it appeared. You define the hidden state (rotated, invisible, off-screen), and GSAP animates from that state to the element's current CSS position.
- Save script.js.
- Check your browser. The hero heading should rotate in from above and the tagline should fade up with a short delay.
Hero text not animating?
- Make sure your GSAP script tags in index.html load before script.js. The order matters because your code depends on the gsap object being available.
- Open DevTools (press F12) and check the Console tab for errors. A typo in the selector string like .hero-content h1 will silently fail.
- Try a hard refresh (Cmd+Shift+R on macOS or Ctrl+Shift+R on Windows) to clear cached scripts.
Help me debug why my GSAP hero animation isn't playing on page load.
Animate the About and Skills sections on scroll
The hero animation plays once on page load. For the remaining sections, you want animations that are tied to scroll position. As the user scrolls down, each section should smoothly rotate or fly into view.
- In script.js, add the following code below the hero animations:
gsap.from(".about-card", {
scrollTrigger: {
trigger: ".about",
start: "top 80%",
end: "top 30%",
scrub: 1
},
rotateY: -90,
opacity: 0,
x: -200
});
gsap.from(".skill-card", {
scrollTrigger: {
trigger: ".skills",
start: "top 80%",
end: "top 30%",
scrub: 1
},
rotateX: -90,
opacity: 0,
y: 100,
stagger: 0.2
});
What does scrollTrigger do here?
The scrollTrigger object tells GSAP when to play the animation based on scroll position. trigger sets which element GSAP watches. start: "top 80%" means the animation begins when the top of the trigger hits 80% down the viewport.
scrub: 1 is the key property. It links animation progress directly to scroll position instead of playing it all at once. As you scroll through the trigger zone, the animation smoothly progresses. Scroll back up and it reverses.
stagger: 0.2 on the skill cards means each card animates slightly after the previous one, creating a cascading effect instead of all four appearing at the same time.
- Save script.js and scroll down in your browser.
- The about card should rotate in from the left side. The four skill cards should flip up one after another as you scroll past the Skills section.
Sections not animating on scroll?
- Make sure gsap.registerPlugin(ScrollTrigger) is at the very top of your file. Without it, the scrollTrigger property is ignored.
- Check that the trigger selector matches your HTML. The trigger is .about (the section), not .about-card (the animated element).
- If the page is too short to scroll, the trigger zone is never reached. Each section needs min-height: 100vh in your CSS to ensure enough scroll distance.
Help me figure out why my scroll-triggered animations are not firing.
Animate the Projects and Contact sections on scroll
The last two sections use different 3D effects to keep things visually interesting. Project cards will fly in from deep behind the screen. The contact section will scale up from small, as if approaching from a distance.
- In script.js, add the following code below the skill card animation:
gsap.from(".project-card", {
scrollTrigger: {
trigger: ".projects",
start: "top 80%",
end: "top 30%",
scrub: 1
},
z: -500,
opacity: 0,
rotateY: 45,
stagger: 0.3
});
gsap.from(".contact-content", {
scrollTrigger: {
trigger: ".contact",
start: "top 80%",
end: "top 50%",
scrub: 1
},
scale: 0.5,
opacity: 0,
rotateX: -45
});
What does z: -500 do?
The z property moves elements along the depth axis (toward or away from the viewer). Setting z: -500 places the project cards 500 pixels behind the screen. Combined with rotateY: 45, they appear to fly toward you from deep in 3D space as you scroll.
The contact animation uses scale: 0.5 so the content starts at half-size and grows to full size. This creates the illusion of something approaching from far away.
- Save script.js.
- Scroll through the entire page in your browser. Each section should smoothly animate in 3D as you scroll past it. Scroll back up and the animations reverse.
Project cards not flying in?
- The z transform only works when a parent has perspective set. Confirm your section rule in style.css includes perspective: 1000px.
- Check that the trigger selector is .projects (the section), not .project-card (the animated element).
- If animations feel choppy, try scrolling more slowly. The scrub: 1 value adds 1 second of smoothing to the scroll-linked animation.
Help me debug why my project cards are not animating with the z-depth effect.
✔️ Awesome, I've got everything!
Great. Double check you have saved script.js before moving on.
ⓧ I'd like to double check the full code
Here is the complete script.js file. Compare yours against it:
gsap.registerPlugin(ScrollTrigger);
gsap.from(".hero-content h1", {
duration: 1.5,
y: -100,
opacity: 0,
rotateX: 90,
ease: "power3.out"
});
gsap.from(".hero-content .tagline", {
duration: 1.5,
y: 50,
opacity: 0,
delay: 0.5,
ease: "power3.out"
});
gsap.from(".about-card", {
scrollTrigger: {
trigger: ".about",
start: "top 80%",
end: "top 30%",
scrub: 1
},
rotateY: -90,
opacity: 0,
x: -200
});
gsap.from(".skill-card", {
scrollTrigger: {
trigger: ".skills",
start: "top 80%",
end: "top 30%",
scrub: 1
},
rotateX: -90,
opacity: 0,
y: 100,
stagger: 0.2
});
gsap.from(".project-card", {
scrollTrigger: {
trigger: ".projects",
start: "top 80%",
end: "top 30%",
scrub: 1
},
z: -500,
opacity: 0,
rotateY: 45,
stagger: 0.3
});
gsap.from(".contact-content", {
scrollTrigger: {
trigger: ".contact",
start: "top 80%",
end: "top 50%",
scrub: 1
},
scale: 0.5,
opacity: 0,
rotateX: -45
});
Your page is now fully animated. Every section responds to scrolling with smooth 3D rotations, depth transitions, and staggered entrances. Next up, you will deploy this live to the internet with GitHub Pages.
Deploy to GitHub Pages
Your 3D portfolio is looking incredible locally. Skill cards tilt, project cards flip, and every section animates smoothly as you scroll.
Now it's time to put it on the internet so anyone can visit it. GitHub Pages lets you host static websites directly from a GitHub repository for free. You'll push your project code to GitHub, flip a switch in the settings, and your 3D portfolio will be live at a public URL.
In this step, get ready to:
- Initialize a Git repository and commit your files.
- Push your code to a new GitHub repository.
- Enable GitHub Pages and visit your live site.
Initialize a Git repository
Git tracks changes to your files and lets you upload them to GitHub. You need to initialize a Git repository in your project folder, then create a snapshot (called a commit) of your current code.
- Open the terminal in VS Code by pressing Ctrl+` (macOS and Windows).
- Initialize a new Git repository by running this command:
git init
- Stage all your project files for commit by running:
git add .
- Create your first commit by running:
git commit -m "Initial commit: 3D portfolio"
What do these commands do?
- git init creates a new Git repository in your current folder.
- git add . stages all files (the dot means "everything") so Git knows to include them in the next commit.
- git commit -m "..." saves a snapshot of your staged files with a descriptive message.
You should see a message that includes 3 files changed confirming your three files were committed.
Seeing 'git' is not recognized?
This means Git is not installed on your machine. Visit git-scm.com/downloads to download and install it, then restart VS Code and try again.
help me install Git on my computer
Create a GitHub repository and push your code
Your code is committed locally, but GitHub doesn't know about it yet. You need to create a repository on GitHub and push your local code up to it.
- Open your browser and go to github.com.
- Click the + icon in the top-right corner of the page.
- Select New repository.
- Enter 3d-portfolio as the repository name.
- Leave the repository set to Public.
- Do NOT check the "Add a README file" box. Leave it empty.
- Click Create repository.
Why no README?
If you add a README, GitHub creates an initial commit on the remote repository. This conflicts with the commit you already made locally, making the push harder. Starting with an empty repository avoids that problem entirely.
GitHub will show you a setup page with commands. You don't need to copy from there. Switch back to your VS Code terminal instead.
- Connect your local repository to GitHub by running:
git remote add origin https://github.com/keylocker006/3d-portfolio.git
- Rename your default branch to main and push your code by running these commands:
git branch -M main
git push -u origin main
What do these commands do?
- git remote add origin tells your local Git where to send code on GitHub.
- git branch -M main renames your current branch to main (the standard name GitHub expects).
- git push -u origin main uploads your committed code to GitHub.
- Refresh your GitHub repository page in the browser.
You should see your three files (index.html, style.css, script.js) listed in the repository.
Push failed or asking for authentication?
GitHub may prompt you to sign in. If a browser window opens asking you to authorize Git, click Authorize and return to VS Code.
If you see a "remote already exists" error, it means you've already added the remote. Run git push -u origin main again to retry the push.
help me push my code to GitHub
Enable GitHub Pages
Your code is on GitHub, but it's not a live website yet. GitHub Pages turns a repository's files into a hosted site with one settings change.
- On your repository page, click Settings in the top navigation bar.
- In the left sidebar, click Pages.
- Under Source, select Deploy from a branch.
- In the Branch dropdown, select main.
- In the folder dropdown, select / (root).
- Click Save.
What is GitHub Pages doing?
GitHub Pages takes the files on your main branch and serves them as a website. Since your index.html is in the root folder, it becomes the homepage automatically.
- Wait 1-2 minutes for GitHub to build and deploy your site.
- Visit your live site at https://keylocker006.github.io/3d-portfolio/.
- Scroll through the page to verify your 3D animations are working.
You should see the hero heading rotate in, skill cards animate on scroll, project cards fly in from depth, and the contact section scale up. Everything you built locally is now live on the internet.
Page showing a 404 or not loading?
GitHub Pages can take a couple of minutes to deploy after you save. Try refreshing after 2 minutes.
Make sure the branch is set to main and the folder is / (root) in your Pages settings. If you selected a different branch or folder, the site won't find your files.
help me get my GitHub Pages site working
Secret mission
Add a Parallax Starfield Background
Your 3D portfolio already looks impressive with scroll-triggered animations. But what if the background itself had depth? In this secret mission, you'll create a starfield that drifts at different speeds as users scroll, giving the entire page a deep space atmosphere.
Clean Up Your Resources
Clean Up Your Resources
This project runs entirely on GitHub Pages, which is free for public repositories. There are no ongoing costs to worry about.
Decide whether to keep your site live, take it offline temporarily, or delete everything entirely.
Resources you used:
- GitHub repository keylocker006/3d-portfolio.
- GitHub Pages site at https://keylocker006.github.io/3d-portfolio/.
- Local 3d-portfolio project folder on your Desktop.
Keep everything running
No action needed. Choose this if you want your 3D portfolio to stay live and accessible to anyone with the link.
- Your site stays live at https://keylocker006.github.io/3d-portfolio/ indefinitely for free.
- Push new commits to the main branch anytime and the site auto-updates.
- Your local 3d-portfolio folder stays on your Desktop for future edits.
Pause - I'll come back to this later
Take your site offline while keeping your code saved on GitHub. You can re-enable it anytime.
- Go to your keylocker006/3d-portfolio repository on GitHub.
- Click Settings, then select Pages in the sidebar.
- Under Source, change the selection to None and click Save.
- Your site goes offline, but the repository and all code remain intact.
- To bring it back, repeat the steps above and select main as the branch again.
Delete - I don't want to use this again
Remove all project resources completely. This deletes your repository, takes the site offline, and removes local files.
- Go to your keylocker006/3d-portfolio repository on GitHub.
- Click Settings.
- Scroll to the bottom of the page to the danger zone and select the option to delete the repository.
- Confirm by typing the repository name when prompted.
This permanently removes both the repository and the GitHub Pages site.
Delete your local project folder by running this command in your terminal:
cd ~/Desktop
rm -rf 3d-portfolio
On Windows, run this instead:
cd ~\Desktop
Remove-Item -Recurse -Force 3d-portfolio
Confirm the folder is gone by running:
ls
You should no longer see 3d-portfolio in the output.
Nice Work!
Nice Work!
You just built and deployed a live 3D portfolio website that transforms your GitHub profile into an interactive scroll experience. Every section animates into view with depth, rotation, and smooth transitions.
Here's what you learned:
- Applied CSS 3D transforms including perspective, rotateX, rotateY, transform-style: preserve-3d, and backface-visibility to create depth effects and interactive flip cards.
- Used GSAP ScrollTrigger with scrub to link 3D animations directly to scroll progress, making sections fly and rotate into view as users scroll down the page.
- Deployed a live website to GitHub Pages for free using Git, making your portfolio accessible to anyone on the internet.
- Secret Mission: Created a parallax starfield background using the Canvas API and requestAnimationFrame, with stars drifting at different speeds on scroll to create a deep space effect.
Ready to quiz yourself?