Build a Developer Portfolio Site

Build and deploy a dark-themed portfolio site to GitHub Pages.

Introduction

30 Second Summary

Recruiters spend about 6 seconds on a portfolio page before deciding whether to keep reading or close the tab. If your online presence does not immediately communicate who you are, what you do, and why you are worth talking to, you lose the opportunity.

In this project, you will build and deploy a polished, dark-themed portfolio website from scratch using HTML, CSS, and JavaScript. You will push it live to GitHub Pages so anyone with your link can see it instantly.

What You'll Build

You will share a single link with a recruiter and watch them land on a fast, premium-looking page that puts your name, target role, and best project right in their face within seconds.

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

  • A live portfolio website at your-username.github.io that you can send to recruiters, share on LinkedIn, or drop into any job application.
  • A glassmorphism design system with smooth scroll navigation and scroll-triggered animations that make the page feel alive as visitors explore your projects, skills, certifications, and contact info.
  • A fully responsive layout that looks just as good on a phone as it does on a desktop monitor, with zero external dependencies beyond fonts and icons.
  • Hands-on experience with Git version control (clone, add, commit, push) and deploying a site to the public internet through GitHub Pages.
  • Secret Mission: Add a dark/light theme toggle that remembers the visitor's preference across page visits.

Are there any prerequisites?

You need a GitHub account (free) and VS Code installed on your computer. This guide includes full setup instructions for Git if you do not have it yet.

Before We Start

Before we write any code, let's lock in what your portfolio will say about you. You need two decisions ready: the role you are targeting (your hero headline) and the one project you are most proud of (your featured project card).

Set Up Git, GitHub, and Project Files

Your portfolio needs a home on the internet. GitHub Pages can host a website directly from a GitHub repository for free.

For GitHub Pages to work, the repository must follow a specific naming convention. In this step, you'll install Git, create the correctly named repository, and set up your project files.

In this step, get ready to:
  • Install and configure Git on your machine.
  • Create a GitHub Pages repository.
  • Set up your project files and open them in VS Code.
Check if Git is installed

Git is a version control tool that tracks changes to your files. It also lets you push your code to GitHub, which is where your site will be hosted.

  • Press Cmd+Space (macOS) or the Windows key (Windows) to open your search bar.
  • Type Terminal (macOS) or Command Prompt (Windows) and press Enter to open it.
  • Check if Git is already installed by running this command:
git --version

Before you hit Enter, what do you expect to see? Either a version number or a prompt to install.

✔️ I see a version number

If you see output like git version 2.39.5 (any version works), Git is ready. Skip ahead to the next substep.

ⓧ macOS prompted me to install or I see command not found

No problem. Follow the instructions for your operating system.

macOS

macOS shows a dialog asking if you want to install Command Line Tools. This package includes Git.

  • Click Install in the dialog that appears.
  • Wait for the installation to finish. This can take a few minutes.
  • Once complete, confirm Git is installed by running:
git --version

You should now see a version number printed in the terminal.

Windows

  • Download the Git installer from git-scm.com/download/win.
  • Run the downloaded installer and accept all the default options.
  • Once complete, close any open terminal windows.
  • Press the Windows key, type Git Bash, and press Enter to open it.
  • Confirm Git is installed by running:
git --version

You should now see a version number printed in the terminal.

Use Git Bash for the rest of this project

Git Bash provides Unix commands like touch and ls that the rest of this project uses. Keep Git Bash open for all terminal commands going forward.

Still not seeing a version number?

Make sure you restarted your terminal after installation. On macOS, you can also run xcode-select --install to manually trigger the Command Line Tools installation.

help me install Git on my machine

Configure Git and create your repository

Git needs to know your name and email so it can label every change you make. These details appear in your commit history on GitHub.

  • Set your Git username by running this command:
git config --global user.name "[[YOUR_NAME="Your Name"]]"
  • Set your Git email by running this command:
git config --global user.email "[[YOUR_EMAIL="you@example.com"]]"
  • Confirm both values saved by running:
git config --global user.name

You should see the name you just entered printed back to you.

What do these commands do?

The --global flag means this setting applies to every Git project on your machine. You only need to run these once.

Now you need a repository on GitHub to store your portfolio code. GitHub Pages requires the repository to follow a specific naming pattern.

  • Open your browser and go to github.com.
  • Click the + icon in the top-right corner.
  • Click New repository.
  • In the Repository name field, type your GitHub username followed by .github.io (for example, janedoe.github.io).
  • Select Public.
  • Check the box next to Add a README file.
  • Click Create repository.

Why this exact name?

GitHub Pages looks for a repository named username.github.io to automatically serve as your personal website. Any other name requires extra configuration.

You should now see your new repository page on GitHub with a single README.md file listed.

  • Move to your Desktop by running this command:
cd ~/Desktop
  • Clone the repository to your computer by running this command (replace the username with your own):
git clone https://github.com/[[GITHUB_USERNAME="your-github-username"]]/[[GITHUB_USERNAME="your-github-username"]].github.io.git
  • Move into the cloned folder by running:
cd [[GITHUB_USERNAME="your-github-username"]].github.io
  • Confirm the clone worked by running:
ls

You should see README.md listed in the output.

Seeing a repository not found error?

Double-check that the repository name in the clone URL matches your GitHub username exactly. GitHub usernames are case-sensitive in URLs.

If prompted to sign in, enter your GitHub username and a personal access token (not your GitHub password).

help me fix my git clone error

Create your project files

Your portfolio will use three files: one for structure, one for styling, and one for interactivity. You also need a special file that tells GitHub Pages how to serve your site.

  • Create all four project files by running this command:
touch index.html style.css script.js .nojekyll
  • Confirm the files were created by running:
ls -a

You should see index.html, style.css, script.js, and .nojekyll in the output. The -a flag shows hidden files like .nojekyll.

What is .nojekyll?

GitHub Pages uses Jekyll by default for branch deploys. The .nojekyll file disables this processing, telling GitHub to serve your files exactly as they are.

  • Open the project in VS Code by running:
code .

VS Code opens with your project folder in the sidebar. You should see all four files listed in the Explorer panel.

code command not found?

On macOS, you need to install the code command manually. Open VS Code, press Cmd+Shift+P to open the Command Palette, type shell command, and select Shell Command: Install 'code' command in PATH. Restart your terminal and try again.

On Windows, if you installed VS Code with the default options, code should already be available. Try closing and reopening Git Bash.

help me get the code command working in my terminal

Your environment is ready. You have Git configured, a correctly named repository on GitHub, and your project files waiting in VS Code. Next up, you'll build the hero section that tells recruiters who you are in 6 seconds.

Build the Hero and Navigation

Your repository is cloned, your files are created, and VS Code is ready to go. Now it is time to put content on the page.

Recruiters spend about 6 seconds scanning a portfolio before deciding to stay or close the tab. If your hero section does not immediately say who you are, what you do, and what stack you use, they bounce. This step puts your positioning front and center with a dark, polished layout and smooth glass-effect navigation.

In this step, get ready to:
  • Write the HTML skeleton with a sticky navigation and hero section.
  • Style the dark theme foundation, glass navbar, and full-viewport hero.
  • Add a responsive hamburger menu for mobile devices.
Write the HTML skeleton with your hero section

Your portfolio needs a semantic structure that search engines and screen readers can parse. You will link external fonts and icons in the <head>, then build a header with navigation and a hero section that screams your value proposition.

  • Open index.html in VS Code and paste the following document head and navigation structure:
<!DOCTYPE html>
<html lang="en" data-theme="dark">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Your Name - Portfolio</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;500;600;700&family=Space+Grotesk:wght@400;500;700&display=swap" rel="stylesheet">
    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.7.2/css/all.min.css">
    <link rel="stylesheet" href="style.css">
</head>
<body>
    <header>
        <nav class="navbar">
            <a href="#hero" class="nav-logo">YN</a>
            <button class="nav-toggle" aria-label="Toggle navigation menu">
                <i class="fa-solid fa-bars"></i>
            </button>
            <ul class="nav-links">
                <li><a href="#projects">Projects</a></li>
                <li><a href="#skills">Skills</a></li>
                <li><a href="#certifications">Certifications</a></li>
                <li><a href="#contact">Contact</a></li>
            </ul>
        </nav>
    </header>

What does this code do?

  • The <link> tags preconnect to Google Fonts and load Inter (body text) and Space Grotesk (headings) without blocking the page render.
  • Font Awesome 6.7.2 gives you 2,000+ free icons for skills, contact links, and the hamburger menu.
  • The <nav> contains anchor links that will smooth-scroll to each section once they exist.
  • The nav-toggle button is hidden on desktop and will become the hamburger icon on mobile.
  • Directly below the closing </header> tag, add the hero section and closing tags:

    <section id="hero">
        <div class="hero-content">
            <h1>Your Name</h1>
            <p class="hero-subtitle">Full Stack Developer</p>
            <p class="hero-description">I build performant web applications with modern JavaScript, cloud infrastructure, and a focus on user experience.</p>
            <a href="#projects" class="btn-primary">View My Work</a>
        </div>
    </section>

    <script src="script.js"></script>
</body>
</html>

What does this code do?

  • The #hero section holds your name, target role, a one-line positioning statement, and a call-to-action button.
  • The script.js file is loaded at the bottom of <body> so it runs after the DOM elements exist.
  • Save index.html.
  • Open the file in your browser by double-clicking index.html in Finder (macOS) or File Explorer (Windows).
  • You should see unstyled black text on a white background showing your nav links, name, role, and a plain "View My Work" link.

Seeing a completely blank page?

  • Check that the <!DOCTYPE html> line is the very first line of the file with no spaces above it.
  • Make sure the closing </html> tag is present at the bottom of the file.
  • Confirm you saved the file (look for a dot or circle on the tab in VS Code indicating unsaved changes).

Why is my HTML page showing blank or broken?

✔️ Awesome, I've got everything!

Great. Double check you have saved index.html and move on to styling.

ⓧ I'd like to double check the full code

Scroll up and confirm your index.html contains both code blocks above in order:

  • The document head and navigation block (starting with <!DOCTYPE html> and ending with </header>).
  • The hero section block (starting with <section id="hero"> and ending with </html>).
Style the dark theme foundation and hero layout

Right now the page looks like a plain document from 1995. You will define a color palette with CSS custom properties, then use those variables to style the navbar with a glass blur effect and the hero as a full-viewport centerpiece.

  • Open style.css and add your design system variables and base styles:
:root {
    --bg-primary: #0a0a0f;
    --bg-secondary: #12121a;
    --text-primary: #f0f0f0;
    --text-secondary: #a0a0b0;
    --accent: #6366f1;
    --accent-hover: #818cf8;
    --glass-bg: rgba(255, 255, 255, 0.05);
    --glass-border: rgba(255, 255, 255, 0.1);
    --font-display: 'Space Grotesk', sans-serif;
    --font-body: 'Inter', sans-serif;
}

* {
    margin: 0;
    padding: 0;
    box-sizing: border-box;
}

html {
    scroll-behavior: smooth;
}

body {
    font-family: var(--font-body);
    background-color: var(--bg-primary);
    color: var(--text-primary);
    line-height: 1.6;
}

What does this code do?

  • The :root block defines CSS variables for colors, fonts, and glass effects that every element will reference.
  • The universal reset (*) removes default browser margins so your layout starts from a clean slate.
  • Setting scroll-behavior: smooth on html means clicking any anchor link will glide to that section instead of jumping.
  • Save style.css and refresh your browser.
  • The page background should now be a very dark navy-black with light gray text.

Still seeing a white background?

  • Make sure the <link rel="stylesheet" href="style.css"> line in your HTML points to the exact filename (case-sensitive).
  • Confirm style.css is in the same folder as index.html (not inside a subfolder).

Why aren't my CSS styles loading?

  • Below the body rule you just added, paste the heading font and navbar styles:
h1, h2, h3 {
    font-family: var(--font-display);
}

.navbar {
    position: sticky;
    top: 0;
    display: flex;
    justify-content: space-between;
    align-items: center;
    padding: 1rem 2rem;
    background: rgba(10, 10, 15, 0.8);
    backdrop-filter: blur(10px);
    border-bottom: 1px solid var(--glass-border);
    z-index: 1000;
}

.nav-logo {
    font-family: var(--font-display);
    font-size: 1.5rem;
    font-weight: 700;
    color: var(--accent);
    text-decoration: none;
}

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

What does this code do?

  • The .navbar uses position: sticky so it stays pinned at the top as you scroll.
  • The backdrop-filter: blur(10px) creates the glassmorphism effect where content behind the nav appears frosted.
  • The logo uses the accent color (#6366f1) to stand out as a purple focal point.
  • Save style.css and refresh your browser.
  • You should see a purple "YN" logo on the left with section links spaced to the right, sitting on a frosted glass-effect bar at the top.
  • Continue in style.css by adding the nav link colors and toggle button styles below the .nav-links block:
.nav-links a {
    color: var(--text-secondary);
    text-decoration: none;
    font-weight: 500;
    transition: color 0.3s ease;
}

.nav-links a:hover {
    color: var(--accent);
}

.nav-toggle {
    display: none;
    background: none;
    border: none;
    color: var(--text-primary);
    font-size: 1.5rem;
    cursor: pointer;
}

What does this code do?

  • Nav links start as muted gray (--text-secondary) and transition to the accent purple on hover for a clean interactive feel.
  • The .nav-toggle is display: none by default. It only appears on mobile screens via a media query you will add shortly.
  • Save style.css and refresh.
  • Hover over the nav links. They should transition from gray to purple smoothly.
  • Now add the hero section layout below the .nav-toggle block:
#hero {
    min-height: 100vh;
    display: flex;
    align-items: center;
    justify-content: center;
    text-align: center;
    padding: 2rem;
}

.hero-content h1 {
    font-size: clamp(2.5rem, 6vw, 4.5rem);
    margin-bottom: 0.5rem;
}

.hero-subtitle {
    font-size: clamp(1.2rem, 3vw, 1.8rem);
    color: var(--accent);
    margin-bottom: 1rem;
    font-family: var(--font-display);
}

.hero-description {
    font-size: 1.1rem;
    color: var(--text-secondary);
    max-width: 600px;
    margin: 0 auto 2rem;
}

What does this code do?

  • Setting min-height: 100vh on #hero makes the hero fill the entire browser viewport so your name dominates the screen.
  • The clamp() function makes the heading responsive. It scales fluidly between a minimum and maximum font size based on the viewport width.
  • Your subtitle renders in the accent purple, immediately drawing the eye to your target role.
  • Save style.css and refresh.
  • Your name should now be large and centered vertically on the page. The role subtitle appears in purple below it.
  • Finally, add the call-to-action button styles below the .hero-description block:
.btn-primary {
    display: inline-block;
    padding: 0.8rem 2rem;
    background: var(--accent);
    color: white;
    text-decoration: none;
    border-radius: 8px;
    font-weight: 600;
    transition: background 0.3s ease, transform 0.3s ease;
}

.btn-primary:hover {
    background: var(--accent-hover);
    transform: translateY(-2px);
}

What does this code do?

  • The button uses the accent purple background and lifts upward (translateY(-2px)) on hover, giving a tactile click-me feel.
  • The border-radius: 8px rounds the corners for a modern look that pairs with the glass cards you will build later.
  • Save style.css and refresh.
  • A solid purple "View My Work" button now sits below your positioning statement. Hover over it and it lifts slightly.
Add the responsive nav with a mobile hamburger menu

On desktop, the nav links sit in a horizontal row. On phones (screens below 768px), you need to collapse those links behind a hamburger icon that toggles open and closed.

  • At the bottom of style.css, add the responsive media query:
@media (max-width: 768px) {
    .nav-toggle {
        display: block;
    }

    .nav-links {
        display: none;
        position: absolute;
        top: 100%;
        left: 0;
        right: 0;
        flex-direction: column;
        background: rgba(10, 10, 15, 0.95);
        backdrop-filter: blur(10px);
        padding: 1rem 2rem;
        gap: 1rem;
        border-bottom: 1px solid var(--glass-border);
    }

    .nav-links.active {
        display: flex;
    }
}

What does this code do?

  • On screens 768px or narrower, the hamburger button (.nav-toggle) becomes visible and the link list hides.
  • When JavaScript adds the .active class, the links reappear as a vertical dropdown with the same frosted-glass backdrop.
  • Save style.css and resize your browser window to a narrow width (below 768px).
  • The nav links should disappear and a hamburger icon (three bars) should appear on the right side of the nav bar.

✔️ Awesome, I've got everything!

Great. Double check you have saved style.css and move on to the JavaScript toggle below.

ⓧ I'd like to double check the full code

Scroll up and confirm your style.css contains all six code blocks in order:

  • CSS variables and base styles (:root through body).
  • Heading font and navbar styles (h1, h2, h3 through .nav-links).
  • Nav link colors and toggle (.nav-links a through .nav-toggle).
  • Hero layout (#hero through .hero-description).
  • Button styles (.btn-primary and .btn-primary:hover).
  • Responsive media query (@media (max-width: 768px)).

The hamburger icon appears, but clicking it does nothing yet. You need JavaScript to toggle the .active class on the nav links.

  • Open script.js and add the mobile navigation toggle logic:
// Mobile navigation toggle
const navToggle = document.querySelector('.nav-toggle');
const navLinks = document.querySelector('.nav-links');

navToggle.addEventListener('click', () => {
    navLinks.classList.toggle('active');
});

// Close mobile nav when a link is clicked
navLinks.querySelectorAll('a').forEach(link => {
    link.addEventListener('click', () => {
        navLinks.classList.remove('active');
    });
});

What does this code do?

  • Clicking the hamburger button toggles the .active class on .nav-links, which your CSS uses to show or hide the mobile menu.
  • The second block automatically closes the menu when the user taps a link, so they are not stuck with an open menu after navigating.
  • Save script.js.
  • Refresh the page with your browser still at a narrow width (below 768px).
  • Click the hamburger icon. The nav links should slide open as a vertical list. Click a link and the menu closes.

Hamburger click does nothing?

  • Make sure <script src="script.js"></script> is at the bottom of your index.html body, not in the <head>.
  • Open the browser console (Cmd+Option+J on macOS or Ctrl+Shift+J on Windows) and check for red error messages.
  • Verify the class name in your JavaScript (.nav-toggle) matches the class on the <button> in your HTML exactly.

Why is my hamburger menu not toggling open?

✔️ Awesome, I've got everything!

Great. Double check you have saved script.js and keep reading.

ⓧ I'd like to double check the full code

// Mobile navigation toggle
const navToggle = document.querySelector('.nav-toggle');
const navLinks = document.querySelector('.nav-links');

navToggle.addEventListener('click', () => {
    navLinks.classList.toggle('active');
});

// Close mobile nav when a link is clicked
navLinks.querySelectorAll('a').forEach(link => {
    link.addEventListener('click', () => {
        navLinks.classList.remove('active');
    });
});

Before you widen your browser back to full size, what do you expect the page to look like at full width?

Resize your browser back to full desktop width. You should see a dark page with your name in large text, your role in purple, a positioning statement, and a sticky glass-effect navigation bar at the top with section links.

Your hero passes the 6-second test. Next up, you will fill the page with project cards and skill icons that prove you can back up that headline.

Add Projects and Skills Sections

Your hero passes the 6-second scan. The recruiter now scrolls down looking for proof.

A flat wall of text or a massive skill dump signals "junior." Curated projects with measurable outcomes and skills grouped by category signal "hire-worthy." In this step, you will build both sections with glassmorphism cards and responsive grid layouts.

In this step, get ready to:
  • Build a projects section with glass-effect cards that show outcomes, tech tags, and links.
  • Build a skills section with icons grouped into Languages, Frameworks, and Tools.
  • Make both sections collapse to single-column on mobile.
Add the projects section HTML

Each project card needs a title, a one-line outcome (not a description of features), tech tags, and links. Recruiters care about impact, not implementation details.

  • In index.html, find the closing </section> tag of the hero section.
  • Below it, add the projects section with your first project card:
    <section id="projects">
        <h2 class="section-title">Featured Projects</h2>
        <div class="projects-grid">
            <div class="glass-card project-card">
                <h3>Project One</h3>
                <p>Built a real-time dashboard that reduced monitoring response time by 40%.</p>
                <div class="tech-tags">
                    <span>React</span>
                    <span>Node.js</span>
                    <span>WebSocket</span>
                </div>
                <div class="project-links">
                    <a href="#" aria-label="Live demo"><i class="fa-solid fa-arrow-up-right-from-square"></i> Live</a>
                    <a href="#" aria-label="GitHub repository"><i class="fa-brands fa-github"></i> Code</a>
                </div>
            </div>
  • Still inside the <div class="projects-grid">, add the second and third project cards and close the section:
            <div class="glass-card project-card">
                <h3>Project Two</h3>
                <p>Designed and deployed a serverless API handling 10k+ requests daily.</p>
                <div class="tech-tags">
                    <span>AWS Lambda</span>
                    <span>Python</span>
                    <span>DynamoDB</span>
                </div>
                <div class="project-links">
                    <a href="#" aria-label="Live demo"><i class="fa-solid fa-arrow-up-right-from-square"></i> Live</a>
                    <a href="#" aria-label="GitHub repository"><i class="fa-brands fa-github"></i> Code</a>
                </div>
            </div>
            <div class="glass-card project-card">
                <h3>Project Three</h3>
                <p>Created a CLI tool that automates deployment pipelines, saving 2 hours per release.</p>
                <div class="tech-tags">
                    <span>Go</span>
                    <span>Docker</span>
                    <span>GitHub Actions</span>
                </div>
                <div class="project-links">
                    <a href="#" aria-label="Live demo"><i class="fa-solid fa-arrow-up-right-from-square"></i> Live</a>
                    <a href="#" aria-label="GitHub repository"><i class="fa-brands fa-github"></i> Code</a>
                </div>
            </div>
        </div>
    </section>

What does this HTML do?

  • Each card uses two classes: glass-card for the frosted-glass look and project-card for project-specific spacing.
  • The tech-tags div holds small pill-shaped labels showing the stack used.
  • The aria-label attributes make the icon links accessible to screen readers.
  • Save index.html and refresh your browser.
  • You should see unstyled text below the hero: "Featured Projects", project titles, descriptions, and tech names.

Don't see any new text below the hero?

  • Make sure the new <section id="projects"> is placed after the hero's closing </section> tag, not inside it.
  • Check that all tags are properly closed. Each <div> needs a matching </div>.

Help me find a missing closing tag in my index.html projects section.

Style the glass cards and section layout

Glassmorphism uses a translucent background with a blur filter, creating a frosted-glass effect against the dark background. You will define this once and reuse it across multiple sections.

  • In style.css, find the .btn-primary:hover rule at the bottom (above the @media query).
  • Below it, add the section and glass-card base styles:
/* Sections */
section {
    padding: 5rem 2rem;
    max-width: 1200px;
    margin: 0 auto;
}

.section-title {
    font-size: 2rem;
    text-align: center;
    margin-bottom: 3rem;
}

/* Glass Card */
.glass-card {
    background: var(--glass-bg);
    backdrop-filter: blur(10px);
    border: 1px solid var(--glass-border);
    border-radius: 16px;
    padding: 1.5rem;
    transition: transform 0.3s ease, box-shadow 0.3s ease;
}

.glass-card:hover {
    transform: translateY(-5px);
    box-shadow: 0 10px 40px rgba(99, 102, 241, 0.1);
}

What does this CSS do?

  • The section rule constrains all sections to 1200px and centers them with even padding.
  • The backdrop-filter: blur(10px) creates the frosted-glass effect by blurring whatever sits behind the card.
  • On hover, cards lift up 5px and gain a subtle purple glow from the box-shadow.
  • Save style.css and refresh your browser.
  • Scroll below the hero. You should see your project text inside translucent cards with rounded corners. Hover over a card and it lifts up slightly.

Cards don't look translucent?

  • Make sure your HTML uses both classes together: class="glass-card project-card".
  • Check that the new CSS sits before the @media block, not inside it.

Help me debug my glass-card CSS styling.

Style the projects grid and card details

The cards still stack in a single column and the tech tags are just plain text. CSS Grid will arrange them in responsive columns, and you will style the tags as colored pills.

  • Below the .glass-card:hover rule you just added, paste the projects grid and card content styles:
/* Projects */
.projects-grid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
    gap: 1.5rem;
}

.project-card h3 {
    margin-bottom: 0.5rem;
    font-size: 1.3rem;
}

.project-card p {
    color: var(--text-secondary);
    margin-bottom: 1rem;
    font-size: 0.95rem;
}

.tech-tags {
    display: flex;
    flex-wrap: wrap;
    gap: 0.5rem;
    margin-bottom: 1rem;
}

.tech-tags span {
    padding: 0.25rem 0.75rem;
    background: rgba(99, 102, 241, 0.15);
    border-radius: 20px;
    font-size: 0.8rem;
    color: var(--accent);
}
  • Continue below that code with the project link styles:
.project-links {
    display: flex;
    gap: 1rem;
}

.project-links a {
    color: var(--text-secondary);
    text-decoration: none;
    font-size: 0.9rem;
    transition: color 0.3s ease;
}

.project-links a:hover {
    color: var(--accent);
}

What does this CSS do?

  • The repeat(auto-fit, minmax(300px, 1fr)) creates columns that are at least 300px wide and automatically wrap to fewer columns on smaller screens.
  • Tech tags get a subtle purple background with pill-shaped corners so they look like badges.
  • Project links change to accent color on hover, giving a clear interactive signal.
  • Save style.css and refresh your browser.
  • Your projects now sit in a multi-column grid. Each card shows purple pill-shaped tech tags and "Live" / "Code" links at the bottom.

Cards still in a single column?

  • If your browser window is narrower than 900px, the auto-fit grid collapses to one column. Try making the window wider.
  • Confirm the parent div in your HTML has class="projects-grid" (not just projects-grid without the class attribute).

Help me debug my CSS Grid projects layout.

Build the skills section with grouped categories

A long flat list of skills is noise. Grouping them into categories (Languages, Frameworks, Tools) makes it scannable. Each skill gets a Font Awesome icon so recruiters can identify technologies at a glance.

  • In index.html, find the closing </section> tag of the projects section you just added.
  • Below it, add the skills section:
    <section id="skills">
        <h2 class="section-title">Tech Stack</h2>
        <div class="skills-container">
            <div class="skill-group">
                <h3>Languages</h3>
                <div class="skill-items">
                    <div class="skill-item"><i class="fa-brands fa-js"></i><span>JavaScript</span></div>
                    <div class="skill-item"><i class="fa-brands fa-python"></i><span>Python</span></div>
                    <div class="skill-item"><i class="fa-brands fa-html5"></i><span>HTML5</span></div>
                    <div class="skill-item"><i class="fa-brands fa-css3-alt"></i><span>CSS3</span></div>
                </div>
            </div>
            <div class="skill-group">
                <h3>Frameworks</h3>
                <div class="skill-items">
                    <div class="skill-item"><i class="fa-brands fa-react"></i><span>React</span></div>
                    <div class="skill-item"><i class="fa-brands fa-node-js"></i><span>Node.js</span></div>
                    <div class="skill-item"><i class="fa-solid fa-server"></i><span>Express</span></div>
                </div>
            </div>
            <div class="skill-group">
                <h3>Tools</h3>
                <div class="skill-items">
                    <div class="skill-item"><i class="fa-brands fa-aws"></i><span>AWS</span></div>
                    <div class="skill-item"><i class="fa-brands fa-docker"></i><span>Docker</span></div>
                    <div class="skill-item"><i class="fa-brands fa-git-alt"></i><span>Git</span></div>
                </div>
            </div>
        </div>
    </section>

What does this HTML do?

  • Each skill-group wraps a category heading and a grid of individual skill items.
  • The Font Awesome class names (like fa-brands fa-js) pull in brand icons from the stylesheet you linked in the <head>.
  • The fa-solid fa-server icon is used for Express because Font Awesome does not have a dedicated Express icon.
  • Save index.html and refresh your browser.
  • Scroll past the projects. You should see "Tech Stack" followed by colored icons (JavaScript, Python, etc.) and their labels in plain text.

Icons not showing?

  • Check that your <head> still has the Font Awesome link: https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.7.2/css/all.min.css.
  • Font Awesome class names are case-sensitive. Make sure you have fa-brands (not fa-brand).

Help me figure out why my Font Awesome icons aren't rendering.

Style the skills section
  • In style.css, below the .project-links a:hover rule, add the skills container and grid styles:
/* Skills */
.skills-container {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
    gap: 2rem;
}

.skill-group h3 {
    margin-bottom: 1rem;
    color: var(--accent);
    font-size: 1.1rem;
}

.skill-items {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(100px, 1fr));
    gap: 1rem;
}

.skill-item {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 0.5rem;
    padding: 1rem;
    border-radius: 12px;
    background: var(--glass-bg);
    border: 1px solid var(--glass-border);
    transition: transform 0.3s ease;
}
  • Continue below with the skill item interaction and icon styles:
.skill-item:hover {
    transform: translateY(-3px);
}

.skill-item i {
    font-size: 1.8rem;
    color: var(--accent);
}

.skill-item span {
    font-size: 0.8rem;
    color: var(--text-secondary);
}

What does this CSS do?

  • The skills-container grid uses minmax(250px, 1fr) so each skill group column is at least 250px before wrapping.
  • Each skill-item is a mini card with the glass background and border, centering the icon above the label.
  • The hover lift is smaller (3px instead of 5px) to feel lighter than the project cards.
  • Save style.css and refresh your browser.
  • Scroll to the Tech Stack section. You should see three columns (Languages, Frameworks, Tools) with purple icons inside glass-bordered tiles. Hover over a tile and it lifts slightly.

Skills items not in a grid?

  • Confirm the HTML structure matches: .skills-container wraps .skill-group divs, which each contain a .skill-items div.
  • If the icons appear but everything is in one column, widen your browser window past 800px.

Help me debug my skills grid layout.

Make both sections responsive

The auto-fit grid already helps on medium screens, but on phones you want a clean single column with no cramped cards.

  • In style.css, find the existing @media (max-width: 768px) block.
  • Inside it, after the .nav-links.active rule, add these overrides:
    .projects-grid {
        grid-template-columns: 1fr;
    }

    .skills-container {
        grid-template-columns: 1fr;
    }

What does this CSS do?

Setting grid-template-columns: 1fr forces a single column on screens 768px or narrower. This overrides the auto-fit behavior to guarantee cards never squish side-by-side on a phone.

  • Save style.css.
  • Before you check the result, predict what happens if you shrink the browser to phone width.
  • Resize your browser to about 375px wide (or use DevTools device toolbar). Both project cards and skill groups should stack into a single column.

Still seeing multiple columns on mobile?

  • Make sure the new rules are inside the @media (max-width: 768px) { } curly braces, not after the closing brace.
  • Check for typos in the selector names. .projects-grid and .skills-container must match your HTML exactly.

Help me fix my responsive media query.

✔️ 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

Scroll up and confirm each chunk is present in order. In index.html:

  • The projects section (<section id="projects">) with 3 glass-card project-card divs, placed after the hero section.
  • The skills section (<section id="skills">) with 3 skill-group divs (Languages, Frameworks, Tools), placed after the projects section.

In style.css, confirm these groups exist in order (all before the @media block):

  • Section + section-title base styles.
  • Glass-card + glass-card:hover styles.
  • Projects-grid, project-card, tech-tags, and project-links styles.
  • Skills-container, skill-group, skill-items, and skill-item styles.
  • Inside the @media block: .projects-grid and .skills-container set to 1fr.

Your projects and skills now look polished and scannable. Next up, you will add the certifications timeline and contact section so recruiters have the full picture and a clear way to reach you.

Add Certifications, Education, and Contact

Your projects and skills get recruiters interested. Your certifications and education validate those skills, and your contact section removes friction so interested recruiters can actually reach you.

Without a clear contact section, interested recruiters bounce. In this step, you will add a timeline of credentials, a contact section with tappable icon links, and a footer to tie the page together.

In this step, get ready to:
  • Build a vertical timeline for certifications and education.
  • Add a contact section with email, LinkedIn, and GitHub icon buttons.
  • Add a footer with a copyright line and back-to-top link.
Build the certifications and education timeline

A timeline layout gives recruiters a quick visual scan of your credentials without forcing them to read a wall of bullet points.

  • In index.html, find the closing </section> tag of the skills section.
  • Add the certifications section below it by pasting this code:
    <section id="certifications">
        <h2 class="section-title">Certifications &amp; Education</h2>
        <div class="timeline">
            <div class="timeline-item">
                <div class="timeline-date">2024</div>
                <div class="timeline-content glass-card">
                    <h3>AWS Solutions Architect Associate</h3>
                    <p>Amazon Web Services</p>
                </div>
            </div>
            <div class="timeline-item">
                <div class="timeline-date">2023</div>
                <div class="timeline-content glass-card">
                    <h3>B.S. Computer Science</h3>
                    <p>Your University</p>
                </div>
            </div>
            <div class="timeline-item">
                <div class="timeline-date">2023</div>
                <div class="timeline-content glass-card">
                    <h3>Meta Front-End Developer Certificate</h3>
                    <p>Coursera / Meta</p>
                </div>
            </div>
        </div>
    </section>

What does this code do?

Each .timeline-item holds a date and a .glass-card with the credential title and issuer. The &amp; is the HTML entity for the ampersand character.

Replace the placeholder certifications, degrees, and dates with your own. Add or remove .timeline-item blocks as needed.

  • Save index.html and refresh your browser.
  • You should see your certification titles and dates appearing as unstyled text below the skills section.

Now you will style this into a vertical timeline with a line on the left and accent-colored dots marking each entry.

  • In style.css, find the @media (max-width: 768px) block at the bottom of your file.
  • Add the following styles directly above that media query:
/* Timeline */
.timeline {
    position: relative;
    max-width: 600px;
    margin: 0 auto;
    padding-left: 2rem;
}

.timeline::before {
    content: '';
    position: absolute;
    left: 0;
    top: 0;
    bottom: 0;
    width: 2px;
    background: var(--glass-border);
}

.timeline-item {
    position: relative;
    margin-bottom: 2rem;
}

.timeline-item::before {
    content: '';
    position: absolute;
    left: -2rem;
    top: 1.5rem;
    width: 12px;
    height: 12px;
    border-radius: 50%;
    background: var(--accent);
    border: 2px solid var(--bg-primary);
    transform: translateX(-5px);
}

What does this code do?

  • .timeline::before creates the vertical line running down the left side using a CSS pseudo-element.
  • .timeline-item::before creates the accent-colored circle at each timeline node.
  • The padding-left: 2rem on .timeline creates space for the line and dots to sit beside the content.
  • Save style.css and refresh your browser.
  • You should see a thin vertical line on the left with purple dots marking each certification entry.
  • Add the timeline content styles directly below the code you just pasted (still above the media query):
.timeline-date {
    font-size: 0.85rem;
    color: var(--accent);
    margin-bottom: 0.5rem;
    font-weight: 600;
}

.timeline-content {
    padding: 1rem 1.5rem;
}

.timeline-content h3 {
    font-size: 1.1rem;
    margin-bottom: 0.25rem;
}

.timeline-content p {
    color: var(--text-secondary);
    font-size: 0.9rem;
}

What does this code do?

These rules style the date labels in your accent color and give the card content proper spacing. The .timeline-content class already inherits the glassmorphism look from .glass-card that you defined earlier.

  • Save style.css and refresh your browser.
  • Your timeline now shows polished glass cards with accent-colored dates and clean typography for each credential.

Timeline dots not showing?

  • Check that .timeline-item::before has content: '' (two single quotes, not missing). Without content, pseudo-elements do not render.
  • Make sure the styles are placed above the @media block. Styles inside a media query only apply at that screen width.
  • Still stuck? Why aren't my timeline dots and vertical line appearing in the certifications section?
Build the contact section

Recruiters who scroll past your credentials are interested. The contact section removes the last bit of friction by giving them large, tappable icons to reach you immediately.

  • In index.html, find the closing </section> tag of the certifications section you just added.
  • Add the contact section below it by pasting this code:
    <section id="contact">
        <h2 class="section-title">Let's Connect</h2>
        <p class="contact-description">I'm open to new opportunities. Reach out and let's talk.</p>
        <div class="contact-links">
            <a href="mailto:you@example.com" class="contact-btn" aria-label="Email"><i class="fa-solid fa-envelope"></i></a>
            <a href="https://linkedin.com/in/yourprofile" target="_blank" class="contact-btn" aria-label="LinkedIn"><i class="fa-brands fa-linkedin"></i></a>
            <a href="https://github.com/yourusername" target="_blank" class="contact-btn" aria-label="GitHub"><i class="fa-brands fa-github"></i></a>
        </div>
    </section>

What does this code do?

  • Each .contact-btn is an anchor tag wrapping a Font Awesome icon. The aria-label attribute makes each link accessible to screen readers.
  • The mailto: link opens the visitor's email client. Replace the placeholder URLs with your real email, LinkedIn, and GitHub links.
  • Save index.html and refresh your browser.
  • You should see "Let's Connect" with three small icons below it. They are unstyled text links for now.
  • Style the contact layout by adding this code in style.css directly above the @media block (below the timeline styles you just added):
/* Contact */
#contact {
    text-align: center;
}

.contact-description {
    color: var(--text-secondary);
    margin-bottom: 2rem;
    font-size: 1.1rem;
}

.contact-links {
    display: flex;
    justify-content: center;
    gap: 1.5rem;
}

What does this code do?

The #contact rule centers all text in the section. .contact-links uses Flexbox to arrange the icon buttons in a horizontal row with even spacing.

  • Now add the button styles directly below the code you just pasted:
.contact-btn {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 60px;
    height: 60px;
    border-radius: 50%;
    background: var(--glass-bg);
    border: 1px solid var(--glass-border);
    color: var(--text-primary);
    font-size: 1.5rem;
    text-decoration: none;
    transition: transform 0.3s ease, color 0.3s ease, border-color 0.3s ease;
}

.contact-btn:hover {
    transform: translateY(-3px);
    color: var(--accent);
    border-color: var(--accent);
}

What does this code do?

  • Each button is a 60px circle (border-radius: 50%) with the same glass background used elsewhere on the site.
  • On hover, the button lifts up slightly and the icon color changes to your accent purple. This provides visual feedback that the links are clickable.
  • Save style.css and refresh your browser.
  • You should see three circular icon buttons (envelope, LinkedIn, GitHub) centered below the heading. Hover over one to see it lift and turn purple.

Icons not showing as circles?

  • Verify each link has class="contact-btn" in the HTML. A missing class means the CSS will not apply.
  • Check that Font Awesome is still linked in your <head>. If the CDN link was accidentally deleted, the icons will not render.
  • Still stuck? Why aren't my contact buttons rendering as circles with icons?
Add a footer with a back-to-top link
  • In index.html, find the closing </section> tag of the contact section.
  • Add the footer below it (but above the <script> tag) by pasting this code:
    <footer>
        <p>&copy; 2025 Your Name. All rights reserved.</p>
        <a href="#hero" class="back-to-top" aria-label="Back to top"><i class="fa-solid fa-arrow-up"></i></a>
    </footer>

What does this code do?

The &copy; entity renders as the copyright symbol. The back-to-top link uses href="#hero" to smooth-scroll the visitor back to the top of the page (because you set scroll-behavior: smooth in your CSS earlier).

  • Save index.html.
  • Now style the footer by adding this code in style.css directly above the @media block:
/* Footer */
footer {
    text-align: center;
    padding: 2rem;
    border-top: 1px solid var(--glass-border);
    color: var(--text-secondary);
    font-size: 0.9rem;
    display: flex;
    justify-content: space-between;
    align-items: center;
    max-width: 1200px;
    margin: 0 auto;
}

.back-to-top {
    color: var(--text-secondary);
    font-size: 1.2rem;
    transition: color 0.3s ease;
}

.back-to-top:hover {
    color: var(--accent);
}

What does this code do?

The footer uses flexbox to push the copyright text to the left and the back-to-top arrow to the right. The border-top creates a subtle divider separating the footer from the contact section above.

  • Finally, add a responsive rule for the footer. Find the inside of your @media (max-width: 768px) block and add this at the end, after the existing .skills-container rule:
    footer {
        flex-direction: column;
        gap: 1rem;
    }

What does this code do?

On mobile screens, this stacks the copyright text and back-to-top arrow vertically instead of side by side.

  • Save style.css and refresh your browser.

Before you check the result, what do you expect to happen when you click one of the navigation links in the top bar?

  • Scroll to the bottom of the page. You should see a footer with copyright text on the left and an up-arrow icon on the right.
  • Click the **Contact** link in the navigation bar. The page smooth-scrolls down to the contact section.
  • Click the up-arrow in the footer. The page smooth-scrolls back to the top.

Smooth scroll not working?

  • Verify that html { scroll-behavior: smooth; } is still present near the top of your style.css. This single rule enables smooth scrolling for all anchor links.
  • Check that each section's id matches the nav link's href exactly. For example, href="#contact" requires id="contact" on the target section.
  • Still stuck? Why isn't smooth scroll working when I click my navigation links?

✔️ Awesome, I've got everything!

Great. Double check you have saved both index.html and style.css.

ⓧ I'd like to double check the full code

Your index.html and style.css are now too long to show in a single block. Scroll up through this step and confirm you have each of these chunks in order:

  • index.html: The <section id="certifications"> with three .timeline-item divs, placed after the skills section.
  • index.html: The <section id="contact"> with three .contact-btn links, placed after certifications.
  • index.html: The <footer> with copyright and .back-to-top link, placed after the contact section but before <script>.
  • style.css: Timeline styles (.timeline, .timeline-item, .timeline-date, .timeline-content) above the media query.
  • style.css: Contact styles (#contact, .contact-btn) above the media query.
  • style.css: Footer styles (footer, .back-to-top) above the media query.
  • style.css: The footer { flex-direction: column; gap: 1rem; } rule inside the @media block.

Your portfolio now has every section a recruiter expects to see. Next up, you will add scroll animations to make the page feel polished and deploy it live so recruiters can actually visit it.

Add Scroll Animations and Deploy to GitHub Pages

Your portfolio has all its content in place. Projects, skills, certifications, and contact sections are styled and ready to impress.

Right now, everything appears at once when the page loads. That makes the page feel static and lifeless. Scroll-triggered animations add a layer of polish that separates professional sites from amateur ones. Once the animations are in, you'll deploy to GitHub Pages so recruiters can actually visit your portfolio at a live URL.

In this step, get ready to:
  • Add scroll-triggered fade-in animations using IntersectionObserver.
  • Push your code to GitHub with Git.
  • Enable GitHub Pages and visit your live portfolio site.
Add IntersectionObserver scroll animations

An IntersectionObserver watches elements on the page and fires a callback when they scroll into view. You'll use it to add a .visible class to each section as the user scrolls down.

  • In style.css, find the existing section rule (it currently has padding, max-width, and margin).
  • Replace the entire section rule with the following code, which adds animation defaults and two new rules below it:
section {
    padding: 5rem 2rem;
    max-width: 1200px;
    margin: 0 auto;
    opacity: 0;
    transform: translateY(30px);
    transition: opacity 0.6s ease, transform 0.6s ease;
}

section.visible {
    opacity: 1;
    transform: translateY(0);
}

#hero {
    opacity: 1;
    transform: none;
}

What does this code do?

  • The section rule now starts every section at invisible (opacity: 0) and shifted 30 pixels down (translateY(30px)). The transition property tells the browser to animate smoothly when these values change.
  • The section.visible rule is the "revealed" state. When JavaScript adds the .visible class, the section fades in and slides up to its natural position.
  • The #hero override keeps the hero section always visible, since it is the first thing visitors see and should never be hidden.
  • Save style.css and refresh your browser.
  • Your hero section remains visible, but every other section (Projects, Skills, Certifications, Contact) has disappeared. That is expected. They are now hidden by default, waiting for JavaScript to reveal them.

Hero section also disappeared?

  • Make sure you added the #hero override rule AFTER the section.visible rule. The #hero selector overrides the generic section rule because of specificity, but it needs to appear after the section block in the file.
  • Check that your #hero override has opacity: 1 and transform: none with no typos.

Why is my hero section hidden even though I added the override?

  • Now switch to script.js in VS Code. Add the following code at the bottom of the file, below the existing mobile nav code:
// Scroll-triggered animations with IntersectionObserver
const observerOptions = {
    threshold: 0.1,
    rootMargin: '0px 0px -50px 0px'
};

const observer = new IntersectionObserver((entries) => {
    entries.forEach(entry => {
        if (entry.isIntersecting) {
            entry.target.classList.add('visible');
        }
    });
}, observerOptions);

document.querySelectorAll('section:not(#hero)').forEach(section => {
    observer.observe(section);
});

What does this code do?

  • The observerOptions object sets threshold: 0.1 (trigger when 10% of the element is visible) and rootMargin: '0px 0px -50px 0px' (activate 50 pixels before the element fully enters the viewport from the bottom).
  • The IntersectionObserver callback fires whenever a watched element enters the viewport. It adds the visible class, which triggers the CSS transition you just wrote.
  • The querySelectorAll('section:not(#hero)') line selects every section except the hero and tells the observer to watch each one.

Before you save and refresh, what do you think will happen when you scroll down the page now?

  • Save script.js and refresh your browser.
  • Scroll down past the hero section. Each section fades in and slides up smoothly as it enters your viewport.

Sections still invisible when you scroll?

  • Open your browser's DevTools console (Cmd+Option+J on macOS, Ctrl+Shift+J on Windows) and look for red error messages. A typo in script.js will prevent the observer from running.
  • Confirm that your CSS has section.visible (with a dot, no space) setting opacity: 1. A missing dot turns it into a descendant selector that will not match.
  • Make sure the <script src="script.js"></script> tag is at the bottom of your index.html (just before </body>). If it loads in the <head>, the DOM elements do not exist yet when the script runs.

My scroll animations are not working even after saving both files. Help me debug.

✔️ Awesome, I've got everything!

Great. Make sure both style.css and script.js are saved before moving on to deployment.

ⓧ I'd like to double check the full code

Your script.js should have two sections. First, the mobile navigation toggle (navToggle and navLinks click handlers). Second, the scroll-triggered animations (observerOptions, the IntersectionObserver instance, and the querySelectorAll loop).

Your style.css should have the section rule with opacity: 0, transform: translateY(30px), and the transition property. Directly below it should be section.visible with opacity: 1 and transform: translateY(0). Then the #hero override with opacity: 1 and transform: none.

Push your code to GitHub

Your animations are working locally. Now you need to get all your files onto GitHub so GitHub Pages can serve them as a live website.

  • Switch back to your Terminal in VS Code (or open a new one with Ctrl+`).
  • Stage, commit, and push all your files by running these commands:
git add .
git commit -m "Initial portfolio site"
git push origin main

What do these commands do?

  • git add . stages all files in the current folder (including index.html, style.css, script.js, and .nojekyll) for the next commit.
  • git commit -m "Initial portfolio site" saves a snapshot of those files with a descriptive message.
  • git push origin main uploads the commit to your GitHub repository.

GitHub will ask you to sign in

When you run git push for the first time, GitHub will prompt you to authenticate. A browser window may open asking you to sign in to GitHub and authorize access. Follow the prompts to complete authentication.

If you see a username/password prompt in the terminal instead, GitHub no longer accepts plain passwords. You will need to create a Personal Access Token in your GitHub Settings under Developer settings and use that as your password.

  • After the push completes, visit your repository at github.com and confirm you see index.html, style.css, script.js, and .nojekyll listed alongside the README.

Push failed or rejected?

  • If you see a "rejected" error, your local branch may be behind the remote (the README created on GitHub counts as a commit). Run git pull origin main --rebase first, then try git push origin main again.
  • If you see "Authentication failed", make sure you completed the sign-in prompt or created a Personal Access Token with the repo scope.

My git push to GitHub failed. Help me fix the authentication or rejection error.

Enable GitHub Pages

Your code is on GitHub. Now you need to tell GitHub to serve those files as a live website.

  • Go to your repository page on github.com.
  • Click Settings (the gear icon tab near the top of the repository page).
  • In the left sidebar under Code and automation, click Pages.
  • Under Build and deployment, set Source to Deploy from a branch.
  • Under Branch, select main and folder / (root).
  • Click Save.

Why the .nojekyll file matters

GitHub Pages processes files through Jekyll by default. The .nojekyll file you created earlier tells GitHub to skip that processing and serve your files exactly as they are. Without it, certain files or naming patterns might not load correctly.

GitHub Pages can take up to 10 minutes to publish your site for the first time. What do you think you will see when you visit your URL?

  • Open a new browser tab and visit https://your-username.github.io.
  • You should see your full portfolio site live on the internet. The dark theme, glassmorphism cards, and scroll animations all work exactly as they did locally.

Seeing a 404 or just the README?

  • If you see a 404 page, wait a few more minutes. First deploys can take up to 10 minutes.
  • If you see only the README content, check that your repository has an index.html file at the root level (not inside a subfolder). GitHub Pages looks for index.html as the entry file.
  • Double-check that the repository name is exactly your-username.github.io (with your actual GitHub username). A misspelled name will not activate GitHub Pages for a user site.

My GitHub Pages site is not loading. Help me troubleshoot.

Secret mission

Add a Dark/Light Theme Toggle

Your portfolio looks great in dark mode. But what happens when a recruiter opens it in a sunlit office? A theme toggle that remembers their preference proves you understand CSS custom properties, localStorage, and progressive enhancement.

Clean Up Your Resources

Clean Up Your Resources

This project runs entirely locally and deploys to GitHub Pages for free. There are no ongoing costs, so your site stays live at no charge for as long as the repository exists.

Decide whether to keep your resources running, pause them to come back later, or delete them entirely.

Resources you used:

  • GitHub repository (your-username.github.io) with GitHub Pages hosting.
  • Local project folder (your-username.github.io) on your Desktop.
  • Git global configuration (user.name and user.email).

Keep everything running

No action needed. Choose this if you want your portfolio to stay live and you plan to keep updating it.

  • Your site stays live at your-username.github.io indefinitely at no cost.
  • Push updates anytime with git add ., git commit -m "message", and git push.
  • You can optionally configure a custom domain in your repository's Pages settings for free (you only pay for the domain name itself).

Pause - I'll come back to this later

Your site stays live with no ongoing cost, so there is nothing to pause. If you want to free up local disk space while keeping the remote repository intact:

  • Delete your local project folder. You can re-clone it from GitHub whenever you are ready to continue.
  • On macOS or Linux, run:
rm -rf ~/Desktop/your-username.github.io
  • On Windows, run:
rmdir /s /q "$HOME\Desktop\your-username.github.io"
  • When you are ready to resume, clone the repository again with git clone.

Delete - I don't want to use this again

Remove all project resources and take your site offline permanently.

  • Delete the GitHub repository to take your site offline:
  • Go to your repository on GitHub.
  • Click Settings (under the repository name).
  • Scroll down to the Danger Zone section.
  • Click Delete this repository.
  • Click I want to delete this repository, then click I have read and understand these effects.
  • Type the repository name to confirm, then click Delete this repository.
  • Delete your local project folder. On macOS or Linux, run:
rm -rf ~/Desktop/your-username.github.io
  • On Windows, run:
rmdir /s /q "$HOME\Desktop\your-username.github.io"
  • Remove your Git global configuration by running:
git config --global --unset user.name
git config --global --unset user.email

Nice Work!

Nice Work!

You just built a recruiter-ready portfolio website from scratch and deployed it live to the internet. Anyone with your link can now see who you are, what you build, and how to reach you.

You've learned how to:

  • Build a complete single-page portfolio with HTML, CSS, and JavaScript using a dark glassmorphism design system powered by CSS custom properties.
  • Create responsive layouts with CSS Grid and Flexbox that work on all devices, with smooth scroll navigation and IntersectionObserver-based scroll animations.
  • Use Git basics (clone, add, commit, push) to version control your code and deploy a live site with GitHub Pages.
  • Secret Mission: Added a persistent dark/light theme toggle with localStorage that remembers the visitor's preference across page visits.

Ready to quiz yourself?