Build a Developer Portfolio Website

Build and deploy a responsive portfolio site to GitHub Pages.

Introduction

30 Second Summary

Hiring managers spend about 60 seconds scanning a developer portfolio before deciding whether to keep reading. A polished, live website that loads fast and looks professional can be the difference between landing an interview and getting passed over.

In this project, you will build a responsive portfolio website from scratch using HTML, CSS, and JavaScript, then deploy it for free so anyone can visit it from a simple URL.

What You'll Build

A single-page portfolio website with a dark hero section, smooth scroll navigation, animated project cards, a visual experience timeline, and a fully responsive layout that works on every screen size.

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

  • A fully responsive single-page portfolio with six key sections: Hero, About, Skills, Projects, Experience, and Contact.
  • Scroll-triggered reveal animations powered by the IntersectionObserver API, a mobile hamburger menu, and active nav link highlighting.
  • A live site deployed for free to GitHub Pages with a shareable URL you can put on your resume.
  • Secret Mission: Add a dark/light theme toggle with localStorage persistence so visitors' preferences are remembered across visits.

Are there any prerequisites?

This project assumes basic knowledge of HTML, CSS, and JavaScript. You should also have VS Code, Git, and a GitHub account ready to go.

Set Up Your Project Files

Every website is built from three core files: an HTML file for structure, a CSS file for styling, and a JavaScript file for interactivity. Your portfolio is no different.

In this step, you'll create your project folder, add these three files, and write the HTML boilerplate that connects your fonts and icon libraries.

In this step, get ready to:
  • Create a portfolio project folder with three files: index.html, style.css, and script.js.
  • Add the HTML5 boilerplate with links to Google Fonts and Font Awesome.
  • Preview your page in the browser to confirm everything loads.
Create the project folder and files
  • Press Cmd+Space (macOS) or the Windows key (Windows) to open your search bar.
  • Type Visual Studio Code and press Enter to open it.

VS Code has a built-in terminal you can use without leaving the editor.

  • Open the integrated terminal by pressing Ctrl+` (Windows) or Cmd+` (macOS).
  • Move to your Desktop by running this command:
cd ~/Desktop
  • Create your project folder and move into it by running these commands:
mkdir portfolio
cd portfolio

Now create the three files your portfolio needs. The command differs between macOS and Windows.

  • Create the three project files by running the command for your operating system:

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 listed in the output.

Now open this folder in VS Code so you can start editing.

  • In VS Code, click File in the top menu bar.
  • Click Open Folder.
  • Navigate to your Desktop and select the portfolio folder, then click Open.

You should see your three files in the Explorer sidebar on the left.

Don't see your files in the Explorer?

Make sure you opened the portfolio folder itself, not the Desktop or a parent folder. The Explorer sidebar should show the folder name PORTFOLIO at the top with your three files underneath.

If the Explorer sidebar is hidden, toggle it by pressing Cmd+B (macOS) or Ctrl+B (Windows).

Help me find my project files in VS Code.

Add the HTML boilerplate

Your index.html file is the entry point for your portfolio. The boilerplate connects your page to external resources like fonts and icons before you write any visible content.

  • Click on index.html in the Explorer sidebar to open it in the editor.
  • Copy and paste the following HTML boilerplate into the file:
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <!-- Page title shown in the browser tab -->
    <title>Your Name | Web Developer Portfolio</title>
    <!-- SEO description for search engines -->
    <meta name="description" content="Portfolio of Your Name - Web Developer specializing in building modern, responsive websites and applications.">
    <!-- Google Fonts - loads the Inter font family -->
    <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
    <!-- Font Awesome - icon library for social and UI icons -->
    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.6.0/css/all.min.css">
    <!-- Your custom stylesheet -->
    <link rel="stylesheet" href="style.css">
</head>
<body>
    <!-- Your custom JavaScript -->
    <script src="script.js"></script>
</body>
</html>

What does this code do?

  • The viewport meta tag tells mobile browsers to scale the page to the device width. Without it, your site looks zoomed out on phones.
  • The description meta tag provides a summary for search engines to display in results. This is a basic but important SEO step.
  • The Google Fonts <link> loads the Inter font family from Google's CDN in four weights (400, 500, 600, 700). Inter is a clean, highly readable font popular in professional portfolios.
  • The Font Awesome <link> loads version 6.6.0 of the icon library. You'll use these icons for navigation, social links, and project cards later.
  • The last two links connect your local style.css and script.js files so the browser loads them alongside your HTML.
  • Save index.html by pressing Cmd+S (macOS) or Ctrl+S (Windows).

Seeing red squiggly lines in the file?

Make sure you pasted the code starting from line 1. There should be no extra blank lines above <!DOCTYPE html>. Also check that the file is named exactly index.html (no extra spaces or characters).

Help me fix errors in my HTML boilerplate.

✔️ Awesome, I've got everything!

Great. Double check you've saved your file before moving on.

ⓧ I'd like to double check the full code

Here is the full index.html file. Compare it against yours to make sure everything matches.

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Your Name | Web Developer Portfolio</title>
    <meta name="description" content="Portfolio of Your Name - Web Developer specializing in building modern, responsive websites and applications.">
    <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.6.0/css/all.min.css">
    <link rel="stylesheet" href="style.css">
</head>
<body>
    <script src="script.js"></script>
</body>
</html>
Preview your page in the browser

Opening your HTML file in a browser confirms that all external resources (fonts, icons) load without errors. If something is misconfigured, you'll see errors in the browser's developer console.

  • Open Finder (macOS) or File Explorer (Windows) and navigate to the portfolio folder on your Desktop.
  • Double-click index.html to open it in your default browser.

You should see a blank white page. This is expected because you have not added any visible content yet.

  • Open the browser's developer console by pressing Cmd+Option+J (macOS Chrome) or Ctrl+Shift+J (Windows Chrome).
  • Check that there are no red error messages in the console.

What am I checking for?

A clean console (no red errors) means the browser successfully loaded the Google Fonts stylesheet, the Font Awesome icon library, and your local style.css and script.js files.

If you see a 404 error for style.css or script.js, it means the browser cannot find those files. Make sure they exist in the same folder as index.html.

Use Live Server for auto-refresh

Instead of manually refreshing the browser every time you save, install the Live Server extension in VS Code. Click the Extensions icon in the left sidebar, search for Live Server, and click Install.

Once installed, right-click index.html in the Explorer sidebar and select Open with Live Server. Your page will now reload automatically whenever you save a file.

Seeing errors in the console?

If you see a red error referencing the Google Fonts or Font Awesome URL, check that you copied the entire <link> tag including the full URL. A missing character in the URL will break the connection.

If the error mentions style.css or script.js not found, confirm all three files are in the same portfolio folder and that the filenames match exactly (no typos, no extra extensions like .txt).

Help me fix console errors when opening my HTML file.

Your project is set up and ready to go. Next, you'll fill in the HTML structure with all six sections of your portfolio.

Build the HTML Structure

Your project files are set up and your blank page loads without errors. Now it is time to build the actual content that hiring managers will see.

A portfolio needs to answer three questions within 60 seconds: who are you, what can you build, and how do I reach you. In this step, you will add all six sections of your portfolio using semantic HTML elements that clearly describe your content's purpose.

In this step, get ready to:
  • Build a navigation bar and hero section that grabs attention above the fold.
  • Add About, Skills, and Projects sections that showcase your work.
  • Add Experience, Contact, and Footer sections so hiring managers can learn more and reach out.
Build the navigation and hero section

The navigation bar and hero section are the first things a visitor sees. The nav gives them a way to jump to any section, and the hero section delivers your value proposition immediately.

  • In index.html, find the opening <body> tag.
  • Add the navigation bar directly below <body> by copying this code:
    <nav class="navbar" id="navbar">
        <div class="container nav-container">
            <a href="#hero" class="nav-logo">YN</a>
            <button class="nav-toggle" id="nav-toggle" aria-label="Toggle navigation">
                <i class="fa-solid fa-bars"></i>
            </button>
            <ul class="nav-links" id="nav-links">
                <li><a href="#about">About</a></li>
                <li><a href="#skills">Skills</a></li>
                <li><a href="#projects">Projects</a></li>
                <li><a href="#experience">Experience</a></li>
                <li><a href="#contact">Contact</a></li>
            </ul>
        </div>
    </nav>

What does this code do?

  • The <nav> element wraps the entire navigation bar. The id="navbar" lets JavaScript target it later for scroll effects.
  • The nav-logo link uses your initials ("YN") as a clickable logo that scrolls back to the top.
  • The nav-toggle button is a hamburger menu icon for mobile visitors. It is hidden on desktop and revealed with CSS later.
  • Each <a> link uses an href that points to a section's id (like #about), enabling smooth scroll navigation.
  • Save index.html and refresh your browser.
  • You should see the text "YN" followed by a small bars icon and the links About, Skills, Projects, Experience, Contact as unstyled text at the top of the page.

Don't see any text on the page?

  • Make sure you pasted the code between the <body> tag and the <script> tag. Code placed outside <body> will not render.
  • Check that your <nav> element has a matching </nav> closing tag. A missing closing tag can break the rest of the page.

Help me debug my nav element in index.html.

Now add the hero section. This is the large, attention-grabbing area directly below the nav that tells visitors who you are and what you do.

  • In index.html, add the hero section directly below the closing </nav> tag:
    <section class="hero" id="hero">
        <div class="container hero-content">
            <p class="hero-greeting">Hello, I'm</p>
            <h1 class="hero-name">Your Name</h1>
            <p class="hero-tagline">I build fast, accessible web applications that solve real problems.</p>
            <div class="hero-buttons">
                <a href="#projects" class="btn btn-primary">View My Work</a>
                <a href="#contact" class="btn btn-outline">Get In Touch</a>
            </div>
        </div>
    </section>

What does this code do?

  • The <h1> tag holds your name. There should only be one <h1> per page for good SEO.
  • The tagline paragraph tells visitors your value in one sentence.
  • The two buttons link to #projects and #contact. They give the visitor a clear next action.
  • Save index.html and refresh your browser.
  • You should see "Hello, I'm" followed by "Your Name" as a large heading, a tagline, and two links that say "View My Work" and "Get In Touch".

Seeing the nav text but not the hero?

  • Make sure the hero <section> is outside the </nav> closing tag. Nesting a section inside nav will cause display issues.
  • Look for any unclosed tags in the nav code above. A missing </ul> or </div> can swallow everything that follows.

Help me figure out why my hero section is not showing up.

Build the About, Skills, and Projects sections

These three sections form the core of your portfolio. They tell visitors about you, show what technologies you know, and display the projects you have built.

  • In index.html, add the About section directly below the closing </section> tag of the hero:
    <section class="section" id="about">
        <div class="container">
            <h2 class="section-title">About Me</h2>
            <div class="about-content">
                <p>I'm a web developer passionate about creating clean, efficient, and user-friendly digital experiences. With a strong foundation in HTML, CSS, and JavaScript, I enjoy turning ideas into functional, beautiful websites.</p>
                <p>When I'm not coding, you can find me exploring new technologies, contributing to open-source projects, or learning about UI/UX design. I'm currently looking for opportunities where I can contribute to meaningful projects and grow as a developer.</p>
            </div>
        </div>
    </section>

What does this code do?

The About section uses a <section> element with the class section and an id of about. The id matches the #about link in the nav so clicking "About" scrolls here.

The two paragraphs inside about-content are placeholder text. You will personalize these with your own story later.

  • Save index.html and refresh your browser.
  • You should see an "About Me" heading followed by two paragraphs of text below the hero content.

Next, add the Skills section. This groups your technical skills into three categories so hiring managers can quickly scan your toolbox.

  • Add the Skills section directly below the closing </section> tag of the About section:
    <section class="section section-alt" id="skills">
        <div class="container">
            <h2 class="section-title">Skills</h2>
            <div class="skills-grid">
                <div class="skill-category">
                    <h3>Frontend</h3>
                    <ul class="skill-list">
                        <li>HTML5</li>
                        <li>CSS3</li>
                        <li>JavaScript</li>
                        <li>React</li>
                        <li>Responsive Design</li>
                    </ul>
                </div>
                <div class="skill-category">
                    <h3>Tools</h3>
                    <ul class="skill-list">
                        <li>Git &amp; GitHub</li>
                        <li>VS Code</li>
                        <li>Chrome DevTools</li>
                        <li>Figma</li>
                        <li>npm</li>
                    </ul>
                </div>
                <div class="skill-category">
                    <h3>Other</h3>
                    <ul class="skill-list">
                        <li>REST APIs</li>
                        <li>Accessibility</li>
                        <li>SEO Basics</li>
                        <li>Agile / Scrum</li>
                        <li>Problem Solving</li>
                    </ul>
                </div>
            </div>
        </div>
    </section>

What does this code do?

  • The section-alt class marks this section for an alternating background color. This creates visual separation between sections when CSS is applied.
  • The skills-grid div wraps three skill-category cards. Each card has an <h3> title and a list of skills.
  • The &amp; in "Git & GitHub" is the HTML entity for the ampersand character. Browsers need this encoding to render it correctly.
  • Save index.html and refresh your browser.
  • You should see a "Skills" heading followed by three groups of bullet points: Frontend, Tools, and Other.

Now add the Projects section. This is the most important part of your portfolio. Hiring managers look for proof that you can build things.

  • Add the Projects section directly below the closing </section> tag of the Skills section:
    <section class="section" id="projects">
        <div class="container">
            <h2 class="section-title">Projects</h2>
            <div class="projects-grid">
                <div class="project-card">
                    <div class="project-image">
                        <div class="project-placeholder">Project 1</div>
                    </div>
                    <div class="project-info">
                        <h3>Project Title One</h3>
                        <p>A brief description of what this project does and the problem it solves.</p>
                        <div class="project-tech">
                            <span>HTML</span>
                            <span>CSS</span>
                            <span>JavaScript</span>
                        </div>
                        <div class="project-links">
                            <a href="#" target="_blank"><i class="fa-solid fa-arrow-up-right-from-square"></i> Live Demo</a>
                            <a href="#" target="_blank"><i class="fa-brands fa-github"></i> Code</a>
                        </div>
                    </div>
                </div>
                <div class="project-card">
                    <div class="project-image">
                        <div class="project-placeholder">Project 2</div>
                    </div>
                    <div class="project-info">
                        <h3>Project Title Two</h3>
                        <p>A brief description of what this project does and the problem it solves.</p>
                        <div class="project-tech">
                            <span>React</span>
                            <span>CSS</span>
                            <span>API</span>
                        </div>
                        <div class="project-links">
                            <a href="#" target="_blank"><i class="fa-solid fa-arrow-up-right-from-square"></i> Live Demo</a>
                            <a href="#" target="_blank"><i class="fa-brands fa-github"></i> Code</a>
                        </div>
                    </div>
                </div>
                <div class="project-card">
                    <div class="project-image">
                        <div class="project-placeholder">Project 3</div>
                    </div>
                    <div class="project-info">
                        <h3>Project Title Three</h3>
                        <p>A brief description of what this project does and the problem it solves.</p>
                        <div class="project-tech">
                            <span>JavaScript</span>
                            <span>Node.js</span>
                            <span>MongoDB</span>
                        </div>
                        <div class="project-links">
                            <a href="#" target="_blank"><i class="fa-solid fa-arrow-up-right-from-square"></i> Live Demo</a>
                            <a href="#" target="_blank"><i class="fa-brands fa-github"></i> Code</a>
                        </div>
                    </div>
                </div>
            </div>
        </div>
    </section>

What does this code do?

  • The projects-grid div wraps three project-card elements. CSS Grid will arrange these into a responsive layout in a later step.
  • Each card has a placeholder image area, a title, a description, tech stack badges inside <span> elements, and two links for a live demo and GitHub code.
  • The fa-solid fa-arrow-up-right-from-square and fa-brands fa-github classes render Font Awesome icons next to each link.
  • Save index.html and refresh your browser.
  • You should see a "Projects" heading followed by three groups of text, each with a project title, description, tech names, and "Live Demo" and "Code" links with small icons.

Not seeing the Font Awesome icons?

  • Check that your <head> still contains the Font Awesome CDN link. If it was accidentally deleted, add it back:
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.6.0/css/all.min.css">
  • Make sure the icon class names are exactly fa-solid or fa-brands followed by the icon name. A typo like fa-solid fa-github will not work because GitHub is a brand icon (fa-brands).

Help me fix my Font Awesome icons.

Build the Experience, Contact, and Footer

The final three pieces complete your portfolio. The Experience section shows your professional journey, the Contact section makes it easy to reach you, and the footer wraps it all up.

  • Add the Experience section directly below the closing </section> tag of the Projects section:
    <section class="section section-alt" id="experience">
        <div class="container">
            <h2 class="section-title">Experience &amp; Courses</h2>
            <div class="timeline">
                <div class="timeline-item">
                    <div class="timeline-marker"></div>
                    <div class="timeline-content">
                        <span class="timeline-date">2024 - Present</span>
                        <h3>Your Role or Course Name</h3>
                        <p class="timeline-org">Company or Platform Name</p>
                        <p>Describe what you did, what you learned, or what you achieved. Use specific outcomes where possible.</p>
                    </div>
                </div>
                <div class="timeline-item">
                    <div class="timeline-marker"></div>
                    <div class="timeline-content">
                        <span class="timeline-date">2023 - 2024</span>
                        <h3>Another Role or Course</h3>
                        <p class="timeline-org">Company or Platform Name</p>
                        <p>Describe what you did, what you learned, or what you achieved. Use specific outcomes where possible.</p>
                    </div>
                </div>
                <div class="timeline-item">
                    <div class="timeline-marker"></div>
                    <div class="timeline-content">
                        <span class="timeline-date">2022 - 2023</span>
                        <h3>Earlier Role or Course</h3>
                        <p class="timeline-org">Company or Platform Name</p>
                        <p>Describe what you did, what you learned, or what you achieved. Use specific outcomes where possible.</p>
                    </div>
                </div>
            </div>
        </div>
    </section>

What does this code do?

  • The timeline div wraps three timeline-item entries. CSS will later turn these into a vertical timeline with markers and a connecting line.
  • Each item has an empty timeline-marker div. This empty div becomes a styled circle positioned on the timeline line with CSS.
  • The dates, titles, and descriptions are placeholder text. You will replace these with your own roles, courses, or certifications.
  • Save index.html and refresh your browser.
  • You should see an "Experience & Courses" heading followed by three entries, each with a date, role name, organization, and description.

Finish the page with the Contact section and footer. These give visitors a clear way to reach out and close the page professionally.

  • Add the Contact section and footer directly below the closing </section> tag of the Experience section:
    <section class="section" id="contact">
        <div class="container">
            <h2 class="section-title">Get In Touch</h2>
            <p class="contact-text">I'm currently looking for new opportunities. Whether you have a question or just want to say hello, feel free to reach out!</p>
            <div class="contact-links">
                <a href="mailto:your.email@example.com" class="contact-item">
                    <i class="fa-solid fa-envelope"></i>
                    <span>your.email@example.com</span>
                </a>
                <a href="https://github.com/yourusername" target="_blank" class="contact-item">
                    <i class="fa-brands fa-github"></i>
                    <span>GitHub</span>
                </a>
                <a href="https://linkedin.com/in/yourusername" target="_blank" class="contact-item">
                    <i class="fa-brands fa-linkedin"></i>
                    <span>LinkedIn</span>
                </a>
            </div>
        </div>
    </section>

    <footer class="footer">
        <div class="container">
            <p>&copy; 2026 Your Name. Built from scratch with HTML, CSS &amp; JavaScript.</p>
        </div>
    </footer>

What does this code do?

  • The contact-links div contains three clickable items: an email link using mailto:, and external links to GitHub and LinkedIn.
  • The target="_blank" attribute on the GitHub and LinkedIn links opens them in a new tab so visitors do not leave your portfolio.
  • The <footer> element closes the page with a copyright notice. The &copy; entity renders the © symbol.
  • Save index.html and refresh your browser.
  • Scroll to the bottom of the page. You should see a "Get In Touch" heading with your email, GitHub, and LinkedIn links, followed by a copyright line.

Page looks jumbled or sections are overlapping?

  • Check that every opening tag has a matching closing tag. In VS Code, click on any tag and its matching pair will highlight.
  • Make sure each <section> is at the same nesting level, directly inside <body>. Sections should not be nested inside each other.
  • Confirm the footer is placed after the Contact section and before the <script> tag.

Help me fix my HTML structure.

Why does the page look plain right now?

Your page is showing all the content as unstyled text. That is completely expected. Right now you have built the skeleton of your portfolio using semantic HTML.

In the next step, you will add CSS to transform this raw text into a polished, professional-looking site.

✔️ Awesome, I've got everything!

Great. Double check you have saved index.html.

ⓧ I'd like to double check the full code

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Your Name | Web Developer Portfolio</title>
    <meta name="description" content="Portfolio of Your Name - Web Developer specializing in building modern, responsive websites and applications.">
    <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.6.0/css/all.min.css">
    <link rel="stylesheet" href="style.css">
</head>
<body>
    <nav class="navbar" id="navbar">
        <div class="container nav-container">
            <a href="#hero" class="nav-logo">YN</a>
            <button class="nav-toggle" id="nav-toggle" aria-label="Toggle navigation">
                <i class="fa-solid fa-bars"></i>
            </button>
            <ul class="nav-links" id="nav-links">
                <li><a href="#about">About</a></li>
                <li><a href="#skills">Skills</a></li>
                <li><a href="#projects">Projects</a></li>
                <li><a href="#experience">Experience</a></li>
                <li><a href="#contact">Contact</a></li>
            </ul>
        </div>
    </nav>

    <section class="hero" id="hero">
        <div class="container hero-content">
            <p class="hero-greeting">Hello, I'm</p>
            <h1 class="hero-name">Your Name</h1>
            <p class="hero-tagline">I build fast, accessible web applications that solve real problems.</p>
            <div class="hero-buttons">
                <a href="#projects" class="btn btn-primary">View My Work</a>
                <a href="#contact" class="btn btn-outline">Get In Touch</a>
            </div>
        </div>
    </section>

    <section class="section" id="about">
        <div class="container">
            <h2 class="section-title">About Me</h2>
            <div class="about-content">
                <p>I'm a web developer passionate about creating clean, efficient, and user-friendly digital experiences. With a strong foundation in HTML, CSS, and JavaScript, I enjoy turning ideas into functional, beautiful websites.</p>
                <p>When I'm not coding, you can find me exploring new technologies, contributing to open-source projects, or learning about UI/UX design. I'm currently looking for opportunities where I can contribute to meaningful projects and grow as a developer.</p>
            </div>
        </div>
    </section>

    <section class="section section-alt" id="skills">
        <div class="container">
            <h2 class="section-title">Skills</h2>
            <div class="skills-grid">
                <div class="skill-category">
                    <h3>Frontend</h3>
                    <ul class="skill-list">
                        <li>HTML5</li>
                        <li>CSS3</li>
                        <li>JavaScript</li>
                        <li>React</li>
                        <li>Responsive Design</li>
                    </ul>
                </div>
                <div class="skill-category">
                    <h3>Tools</h3>
                    <ul class="skill-list">
                        <li>Git &amp; GitHub</li>
                        <li>VS Code</li>
                        <li>Chrome DevTools</li>
                        <li>Figma</li>
                        <li>npm</li>
                    </ul>
                </div>
                <div class="skill-category">
                    <h3>Other</h3>
                    <ul class="skill-list">
                        <li>REST APIs</li>
                        <li>Accessibility</li>
                        <li>SEO Basics</li>
                        <li>Agile / Scrum</li>
                        <li>Problem Solving</li>
                    </ul>
                </div>
            </div>
        </div>
    </section>

    <section class="section" id="projects">
        <div class="container">
            <h2 class="section-title">Projects</h2>
            <div class="projects-grid">
                <div class="project-card">
                    <div class="project-image">
                        <div class="project-placeholder">Project 1</div>
                    </div>
                    <div class="project-info">
                        <h3>Project Title One</h3>
                        <p>A brief description of what this project does and the problem it solves.</p>
                        <div class="project-tech">
                            <span>HTML</span>
                            <span>CSS</span>
                            <span>JavaScript</span>
                        </div>
                        <div class="project-links">
                            <a href="#" target="_blank"><i class="fa-solid fa-arrow-up-right-from-square"></i> Live Demo</a>
                            <a href="#" target="_blank"><i class="fa-brands fa-github"></i> Code</a>
                        </div>
                    </div>
                </div>
                <div class="project-card">
                    <div class="project-image">
                        <div class="project-placeholder">Project 2</div>
                    </div>
                    <div class="project-info">
                        <h3>Project Title Two</h3>
                        <p>A brief description of what this project does and the problem it solves.</p>
                        <div class="project-tech">
                            <span>React</span>
                            <span>CSS</span>
                            <span>API</span>
                        </div>
                        <div class="project-links">
                            <a href="#" target="_blank"><i class="fa-solid fa-arrow-up-right-from-square"></i> Live Demo</a>
                            <a href="#" target="_blank"><i class="fa-brands fa-github"></i> Code</a>
                        </div>
                    </div>
                </div>
                <div class="project-card">
                    <div class="project-image">
                        <div class="project-placeholder">Project 3</div>
                    </div>
                    <div class="project-info">
                        <h3>Project Title Three</h3>
                        <p>A brief description of what this project does and the problem it solves.</p>
                        <div class="project-tech">
                            <span>JavaScript</span>
                            <span>Node.js</span>
                            <span>MongoDB</span>
                        </div>
                        <div class="project-links">
                            <a href="#" target="_blank"><i class="fa-solid fa-arrow-up-right-from-square"></i> Live Demo</a>
                            <a href="#" target="_blank"><i class="fa-brands fa-github"></i> Code</a>
                        </div>
                    </div>
                </div>
            </div>
        </div>
    </section>

    <section class="section section-alt" id="experience">
        <div class="container">
            <h2 class="section-title">Experience &amp; Courses</h2>
            <div class="timeline">
                <div class="timeline-item">
                    <div class="timeline-marker"></div>
                    <div class="timeline-content">
                        <span class="timeline-date">2024 - Present</span>
                        <h3>Your Role or Course Name</h3>
                        <p class="timeline-org">Company or Platform Name</p>
                        <p>Describe what you did, what you learned, or what you achieved. Use specific outcomes where possible.</p>
                    </div>
                </div>
                <div class="timeline-item">
                    <div class="timeline-marker"></div>
                    <div class="timeline-content">
                        <span class="timeline-date">2023 - 2024</span>
                        <h3>Another Role or Course</h3>
                        <p class="timeline-org">Company or Platform Name</p>
                        <p>Describe what you did, what you learned, or what you achieved. Use specific outcomes where possible.</p>
                    </div>
                </div>
                <div class="timeline-item">
                    <div class="timeline-marker"></div>
                    <div class="timeline-content">
                        <span class="timeline-date">2022 - 2023</span>
                        <h3>Earlier Role or Course</h3>
                        <p class="timeline-org">Company or Platform Name</p>
                        <p>Describe what you did, what you learned, or what you achieved. Use specific outcomes where possible.</p>
                    </div>
                </div>
            </div>
        </div>
    </section>

    <section class="section" id="contact">
        <div class="container">
            <h2 class="section-title">Get In Touch</h2>
            <p class="contact-text">I'm currently looking for new opportunities. Whether you have a question or just want to say hello, feel free to reach out!</p>
            <div class="contact-links">
                <a href="mailto:your.email@example.com" class="contact-item">
                    <i class="fa-solid fa-envelope"></i>
                    <span>your.email@example.com</span>
                </a>
                <a href="https://github.com/yourusername" target="_blank" class="contact-item">
                    <i class="fa-brands fa-github"></i>
                    <span>GitHub</span>
                </a>
                <a href="https://linkedin.com/in/yourusername" target="_blank" class="contact-item">
                    <i class="fa-brands fa-linkedin"></i>
                    <span>LinkedIn</span>
                </a>
            </div>
        </div>
    </section>

    <footer class="footer">
        <div class="container">
            <p>&copy; 2026 Your Name. Built from scratch with HTML, CSS &amp; JavaScript.</p>
        </div>
    </footer>

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

Your portfolio's skeleton is complete. All six sections are in place and every nav link has a matching section to scroll to. Next up, you will add CSS to transform this plain text into a polished, professional design.

Style the Core Layout

Your HTML is complete, but right now the page looks like a wall of unstyled text. A hiring manager would close that tab in under a second.

In this step, you'll add the CSS foundation that transforms your raw HTML into a polished, professional layout. You'll set up reusable color variables, style a fixed navigation bar, and build a dark hero section that grabs attention immediately.

In this step, get ready to:
  • Define a CSS reset and reusable color variables with custom properties.
  • Style a fixed, semi-transparent navigation bar with a frosted glass effect.
  • Build a full-height dark hero section with styled call-to-action buttons.
Add CSS reset and custom properties

Every browser applies its own default spacing and font styles to HTML elements. Without a reset, your portfolio will look slightly different on Chrome, Firefox, and Safari.

CSS custom properties let you define colors, fonts, and sizes in one place and reuse them everywhere. Change a single variable, and every element that references it updates automatically.

  • Open style.css in VS Code and add the CSS reset, custom properties, and base element styles by pasting this code:
/* ===== CSS Reset & Variables ===== */
*,
*::before,
*::after {
    margin: 0;
    padding: 0;
    box-sizing: border-box;
}

:root {
    --color-primary: #2563eb;
    --color-primary-dark: #1d4ed8;
    --color-dark: #0f172a;
    --color-text: #1e293b;
    --color-text-light: #64748b;
    --color-bg: #ffffff;
    --color-bg-alt: #f8fafc;
    --color-border: #e2e8f0;
    --font-family: 'Inter', sans-serif;
    --container-width: 1100px;
    --nav-height: 70px;
}

html {
    scroll-behavior: smooth;
    scroll-padding-top: var(--nav-height);
}

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

a {
    text-decoration: none;
    color: inherit;
}

ul {
    list-style: none;
}

img {
    max-width: 100%;
    display: block;
}

.container {
    max-width: var(--container-width);
    margin: 0 auto;
    padding: 0 24px;
}

What does this code do?

  • The universal selector (*) strips all default margins, padding, and switches to border-box sizing so padding never breaks your layout widths.
  • The :root block defines all your design tokens as CSS custom properties. Every color, font, and spacing value lives here so you only need to update one line to change a color across the entire site.
  • The scroll-behavior: smooth on html makes clicking a nav link glide to the target section instead of jumping instantly.
  • The scroll-padding-top offsets the scroll destination by the navbar height so sections don't get hidden behind the fixed nav.
  • The .container class centers content at a max width of 1100px with side padding, keeping everything readable on wide screens.
  • Save style.css and refresh your browser.
  • You should see the default browser spacing disappear. Your text is now in the Inter font, bullet points are gone from lists, and all the content is centered within a narrower column.

Page still looks the same?

  • Make sure you saved style.css and did a hard refresh (Cmd+Shift+R on macOS or Ctrl+Shift+R on Windows) to clear cached styles.
  • Check that the <link rel="stylesheet" href="style.css"> tag in index.html matches the filename exactly. CSS file names are case-sensitive.
  • Open DevTools (F12) and check the Console tab for any 404 errors indicating the stylesheet failed to load.

Why aren't my CSS styles showing up in the browser?

Style the navigation bar

The navbar needs to stay visible as visitors scroll through your portfolio. A fixed position with a frosted glass effect keeps it accessible without blocking the content underneath.

  • Add the navigation styles below your .container rule in style.css by pasting this code:
/* ===== Navigation ===== */
.navbar {
    position: fixed;
    top: 0;
    left: 0;
    width: 100%;
    height: var(--nav-height);
    background-color: rgba(255, 255, 255, 0.95);
    backdrop-filter: blur(10px);
    border-bottom: 1px solid var(--color-border);
    z-index: 1000;
    display: flex;
    align-items: center;
}

.nav-container {
    display: flex;
    align-items: center;
    justify-content: space-between;
}

.nav-logo {
    font-size: 1.5rem;
    font-weight: 700;
    color: var(--color-primary);
}

.nav-links {
    display: flex;
    gap: 32px;
}

.nav-links a {
    font-size: 0.95rem;
    font-weight: 500;
    color: var(--color-text-light);
    transition: color 0.3s;
}

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

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

What does this code do?

  • position: fixed locks the navbar to the top of the viewport so it stays visible as the user scrolls.
  • The rgba(255, 255, 255, 0.95) background is 95% opaque white. Combined with backdrop-filter: blur(10px), this creates a frosted glass effect where scrolling content blurs through the navbar.
  • Flexbox with justify-content: space-between pushes the logo to the left and the nav links to the right.
  • The .nav-toggle is hidden with display: none on desktop. You will reveal it later in the responsive breakpoint for mobile screens.
  • Save style.css and refresh your browser.
  • You should see a white navbar pinned to the top of the page with your blue "YN" logo on the left and five navigation links on the right. Hover over any link to see it turn blue.

Navbar not sticking to the top?

  • Check that your .navbar rule includes position: fixed and top: 0. Without both, the navbar won't anchor to the viewport.
  • Make sure the z-index: 1000 is present. Without it, other content can stack on top of the navbar as you scroll.

Why is my fixed navbar not visible or overlapping content?

Style the hero section

The hero is the first thing a hiring manager sees. A dark, full-viewport section with a clear name and tagline creates a strong first impression in under three seconds.

  • Add the hero layout styles below the navigation rules in style.css by pasting this code:
/* ===== Hero ===== */
.hero {
    min-height: 100vh;
    display: flex;
    align-items: center;
    background-color: var(--color-dark);
    color: #ffffff;
    padding-top: var(--nav-height);
}

.hero-content {
    text-align: center;
    padding: 60px 0;
}

.hero-greeting {
    font-size: 1.1rem;
    color: var(--color-primary);
    font-weight: 500;
    margin-bottom: 12px;
}

.hero-name {
    font-size: 3.5rem;
    font-weight: 700;
    margin-bottom: 16px;
    line-height: 1.2;
}

.hero-tagline {
    font-size: 1.2rem;
    color: #94a3b8;
    max-width: 550px;
    margin: 0 auto 32px;
}

What does this code do?

  • min-height: 100vh makes the hero fill the entire browser window. display: flex with align-items: center vertically centers everything inside it.
  • padding-top: var(--nav-height) pushes the hero content down by exactly the navbar height so nothing gets hidden behind the fixed nav.
  • The greeting uses your primary blue color while the tagline uses a muted gray (#94a3b8). This creates a visual hierarchy that draws the eye to your name first.
  • Save style.css and refresh your browser.
  • You should see a dark, full-height hero section with "Hello, I'm" in blue, your name in large white text, and a muted tagline centered below it.

Hero text hidden behind the navbar?

  • Make sure padding-top: var(--nav-height) is on the .hero rule. Without it, the fixed navbar overlaps the top of the hero content.
  • Check that your :root block includes --nav-height: 70px. If the variable is missing, the padding won't apply.

Why is my hero section content overlapping with the navbar?

The hero layout is in place, but the two call-to-action buttons still look like plain links. Let's add the button styles next.

  • Add the button styles directly below the .hero-tagline rule you just pasted by adding this code:
.hero-buttons {
    display: flex;
    gap: 16px;
    justify-content: center;
}

.btn {
    display: inline-block;
    padding: 12px 28px;
    border-radius: 8px;
    font-size: 1rem;
    font-weight: 600;
    transition: all 0.3s;
    cursor: pointer;
}

.btn-primary {
    background-color: var(--color-primary);
    color: #ffffff;
}

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

.btn-outline {
    border: 2px solid #475569;
    color: #e2e8f0;
}

.btn-outline:hover {
    border-color: var(--color-primary);
    color: var(--color-primary);
    transform: translateY(-2px);
}

What does this code do?

  • The .btn class defines shared button properties like padding, rounded corners, and a smooth transition. Both button variants inherit these base styles.
  • .btn-primary gets a solid blue fill that darkens on hover. .btn-outline has a subtle gray border that turns blue on hover.
  • transform: translateY(-2px) on hover creates a subtle lift effect. Combined with the transition: all 0.3s, the button floats up smoothly when the cursor enters.
  • Save style.css and refresh your browser.
  • You should see two buttons side by side below your tagline. "View My Work" is a solid blue button and "Get In Touch" has a gray outline. Hover over each one to see it lift slightly.

Buttons not lifting on hover?

  • Confirm that your .btn rule includes transition: all 0.3s. Without the transition, the lift happens instantly and is almost invisible.
  • Check that the :hover selectors are separate rules (.btn-primary:hover), not placed inside the .btn-primary block. CSS does not support nesting in plain stylesheets.

Why isn't my CSS hover effect working on the buttons?

✔️ Awesome, I've got everything!

Great. Double check you've saved style.css before moving on.

ⓧ I'd like to double check the full code

Here is the complete style.css so far. Compare it with yours and fix any differences.

/* ===== CSS Reset & Variables ===== */
*,
*::before,
*::after {
    margin: 0;
    padding: 0;
    box-sizing: border-box;
}

:root {
    --color-primary: #2563eb;
    --color-primary-dark: #1d4ed8;
    --color-dark: #0f172a;
    --color-text: #1e293b;
    --color-text-light: #64748b;
    --color-bg: #ffffff;
    --color-bg-alt: #f8fafc;
    --color-border: #e2e8f0;
    --font-family: 'Inter', sans-serif;
    --container-width: 1100px;
    --nav-height: 70px;
}

html {
    scroll-behavior: smooth;
    scroll-padding-top: var(--nav-height);
}

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

a {
    text-decoration: none;
    color: inherit;
}

ul {
    list-style: none;
}

img {
    max-width: 100%;
    display: block;
}

.container {
    max-width: var(--container-width);
    margin: 0 auto;
    padding: 0 24px;
}

/* ===== Navigation ===== */
.navbar {
    position: fixed;
    top: 0;
    left: 0;
    width: 100%;
    height: var(--nav-height);
    background-color: rgba(255, 255, 255, 0.95);
    backdrop-filter: blur(10px);
    border-bottom: 1px solid var(--color-border);
    z-index: 1000;
    display: flex;
    align-items: center;
}

.nav-container {
    display: flex;
    align-items: center;
    justify-content: space-between;
}

.nav-logo {
    font-size: 1.5rem;
    font-weight: 700;
    color: var(--color-primary);
}

.nav-links {
    display: flex;
    gap: 32px;
}

.nav-links a {
    font-size: 0.95rem;
    font-weight: 500;
    color: var(--color-text-light);
    transition: color 0.3s;
}

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

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

/* ===== Hero ===== */
.hero {
    min-height: 100vh;
    display: flex;
    align-items: center;
    background-color: var(--color-dark);
    color: #ffffff;
    padding-top: var(--nav-height);
}

.hero-content {
    text-align: center;
    padding: 60px 0;
}

.hero-greeting {
    font-size: 1.1rem;
    color: var(--color-primary);
    font-weight: 500;
    margin-bottom: 12px;
}

.hero-name {
    font-size: 3.5rem;
    font-weight: 700;
    margin-bottom: 16px;
    line-height: 1.2;
}

.hero-tagline {
    font-size: 1.2rem;
    color: #94a3b8;
    max-width: 550px;
    margin: 0 auto 32px;
}

.hero-buttons {
    display: flex;
    gap: 16px;
    justify-content: center;
}

.btn {
    display: inline-block;
    padding: 12px 28px;
    border-radius: 8px;
    font-size: 1rem;
    font-weight: 600;
    transition: all 0.3s;
    cursor: pointer;
}

.btn-primary {
    background-color: var(--color-primary);
    color: #ffffff;
}

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

.btn-outline {
    border: 2px solid #475569;
    color: #e2e8f0;
}

.btn-outline:hover {
    border-color: var(--color-primary);
    color: var(--color-primary);
    transform: translateY(-2px);
}

Your portfolio already looks professional with a polished navbar and a bold hero section. Next up, you'll style the remaining sections including project cards, a skills grid, and an experience timeline.

Style Projects, Skills, and Remaining Sections

Your hero and navigation look polished, but the rest of your portfolio is still raw, unstyled HTML. A hiring manager scrolling past the hero will immediately see plain text and stacked elements.

In this step, you will style every remaining section of the page. You will use CSS Grid for multi-column layouts, build interactive hover effects on project cards, and create a timeline component for your experience section.

In this step, get ready to:
  • Style the section layout, About section, and Skills grid.
  • Style the project cards with hover effects and tech stack badges.
  • Style the experience timeline, contact section, and footer.
Style the section layout, About, and Skills

Every content section on your page needs consistent vertical spacing and a styled heading. The ::after pseudo-element lets you add a decorative accent bar beneath each section title without touching your HTML.

  • In style.css, add the following styles below the .btn-outline:hover block to set up your section foundations and About section:
/* ===== Sections ===== */
.section {
    padding: 80px 0;
}

.section-alt {
    background-color: var(--color-bg-alt);
}

/* Centered heading with a blue underline accent bar */
.section-title {
    font-size: 2rem;
    font-weight: 700;
    text-align: center;
    margin-bottom: 48px;
    position: relative;
}

.section-title::after {
    content: '';
    display: block;
    width: 60px;
    height: 4px;
    background-color: var(--color-primary);
    margin: 12px auto 0;
    border-radius: 2px;
}

/* ===== About ===== */
.about-content {
    max-width: 700px;
    margin: 0 auto;
    text-align: center;
}

.about-content p {
    color: var(--color-text-light);
    margin-bottom: 16px;
    font-size: 1.05rem;
}

What does this code do?

.section adds 80px of vertical padding to every content section, giving each one breathing room.

.section-alt applies a subtle alternate background color so adjacent sections are visually distinct.

.section-title::after creates a 60px-wide blue bar beneath every section heading. The position: relative on the title lets this pseudo-element sit in the right place.

  • Save style.css and refresh your browser.
  • Scroll down past the hero. You should see each section heading centered with a small blue underline bar beneath it, and the About text centered in a narrower column.

Don't see the blue underline bar?

Make sure your HTML section headings use class="section-title". The ::after pseudo-element only appears on elements with that class.

Help me debug why my section title underline bar isn't showing up.

Now the Skills section needs a three-column grid layout so each skill category appears as its own card.

  • Add the following CSS below the .about-content p block to style the Skills section:
/* ===== Skills ===== */
.skills-grid {
    display: grid;
    grid-template-columns: repeat(3, 1fr);
    gap: 32px;
}

/* Each skill group styled as a bordered card */
.skill-category {
    background-color: var(--color-bg);
    padding: 32px;
    border-radius: 12px;
    border: 1px solid var(--color-border);
}

.skill-category h3 {
    font-size: 1.1rem;
    font-weight: 600;
    color: var(--color-primary);
    margin-bottom: 16px;
}

.skill-list li {
    padding: 6px 0;
    color: var(--color-text-light);
    font-size: 0.95rem;
    border-bottom: 1px solid var(--color-border);
}

.skill-list li:last-child {
    border-bottom: none;
}

What does this code do?

grid-template-columns: repeat(3, 1fr) creates three equal-width columns. Each .skill-category automatically fills the next available column.

The border-bottom on each list item creates subtle separators between skills. The :last-child selector removes the border from the final item so there is no dangling line.

  • Save style.css and refresh your browser.
  • Scroll to the Skills section. You should see three bordered cards sitting side by side, each with a blue category title and a list of skills separated by faint lines.

Skills cards stacking vertically instead of side by side?

Check that your HTML uses class="skills-grid" on the wrapper div. The grid layout only applies to direct children of that class.

Help me figure out why my CSS Grid columns are not working.

Style the project cards

The Projects section is the most important part of your portfolio. Hiring managers look for proof of work first. Each project card needs to feel interactive and polished.

  • Add the following CSS below the .skill-list li:last-child block to create the project grid and card shell:
/* ===== Projects ===== */
.projects-grid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(320px, 1fr));
    gap: 28px;
}

/* Card container with hover lift effect */
.project-card {
    background-color: var(--color-bg);
    border-radius: 12px;
    border: 1px solid var(--color-border);
    overflow: hidden;
    transition: transform 0.3s, box-shadow 0.3s;
}

.project-card:hover {
    transform: translateY(-6px);
    box-shadow: 0 12px 24px rgba(0, 0, 0, 0.1);
}

/* Placeholder area for project screenshots */
.project-image {
    height: 200px;
    background-color: var(--color-bg-alt);
    display: flex;
    align-items: center;
    justify-content: center;
}

.project-placeholder {
    font-size: 1.2rem;
    color: var(--color-text-light);
    font-weight: 500;
}

What does this code do?

auto-fit with minmax(320px, 1fr) makes the grid responsive without a media query. Cards are at least 320px wide and expand to fill available space. On narrow screens, they stack into a single column automatically.

The transform: translateY(-6px) on hover lifts the card upward. Combined with the box-shadow, this creates a subtle 3D floating effect that makes cards feel interactive.

  • Save style.css and refresh your browser.
  • Scroll to the Projects section. You should see three rounded cards with a grey placeholder area at the top. Hover over a card and watch it lift up with a shadow appearing beneath it.

Cards not lifting on hover?

Make sure the transition property is on .project-card (the base state), not inside the :hover rule. Transitions need to be declared on the element before the state change.

Help me debug my CSS hover transition on project cards.

Now you need to style the content inside each card. This includes the project description, tech stack badges, and action links.

  • Add the following CSS below the .project-placeholder block to style the card content area:
.project-info {
    padding: 24px;
}

.project-info h3 {
    font-size: 1.2rem;
    font-weight: 600;
    margin-bottom: 8px;
}

.project-info p {
    color: var(--color-text-light);
    font-size: 0.95rem;
    margin-bottom: 16px;
}

/* Tech stack badges styled as small rounded pills */
.project-tech {
    display: flex;
    flex-wrap: wrap;
    gap: 8px;
    margin-bottom: 16px;
}

.project-tech span {
    background-color: var(--color-bg-alt);
    color: var(--color-primary);
    padding: 4px 12px;
    border-radius: 20px;
    font-size: 0.85rem;
    font-weight: 500;
}

/* Action links with icon spacing */
.project-links {
    display: flex;
    gap: 16px;
}

.project-links a {
    color: var(--color-text-light);
    font-size: 0.9rem;
    font-weight: 500;
    transition: color 0.3s;
}

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

.project-links a i {
    margin-right: 4px;
}

What does this code do?

.project-tech span turns each tech label (like "HTML" or "React") into a small rounded pill badge. The border-radius: 20px combined with tight padding creates the pill shape.

flex-wrap: wrap on .project-tech ensures badges flow onto a new line if the card is too narrow to fit them all on one row.

  • Save style.css and refresh your browser.
  • Your project cards should now show a title, description, blue pill-shaped tech badges, and "Live Demo" and "Code" links at the bottom of each card.

Tech badges not showing the pill shape?

Confirm that each tech label in your HTML is wrapped in a <span> tag inside a class="project-tech" div. The CSS targets .project-tech span specifically.

Help me fix my tech stack badge styling.

Style the timeline, contact, and footer

The experience timeline uses a vertical line and circular markers to create a visual progression of your career or learning history. This is built entirely with CSS positioning and pseudo-elements.

  • Add the following CSS below the .project-links a i block to build the experience timeline:
/* ===== Experience Timeline ===== */
.timeline {
    max-width: 700px;
    margin: 0 auto;
    position: relative;
    padding-left: 40px;
}

/* Vertical line running down the left side */
.timeline::before {
    content: '';
    position: absolute;
    left: 7px;
    top: 0;
    bottom: 0;
    width: 2px;
    background-color: var(--color-border);
}

.timeline-item {
    position: relative;
    margin-bottom: 40px;
}

.timeline-item:last-child {
    margin-bottom: 0;
}

/* Blue circle positioned on the vertical line */
.timeline-marker {
    position: absolute;
    left: -40px;
    top: 4px;
    width: 16px;
    height: 16px;
    background-color: var(--color-primary);
    border-radius: 50%;
    border: 3px solid var(--color-bg-alt);
}

.timeline-date {
    display: inline-block;
    font-size: 0.85rem;
    font-weight: 600;
    color: var(--color-primary);
    margin-bottom: 8px;
}

.timeline-content h3 {
    font-size: 1.15rem;
    font-weight: 600;
    margin-bottom: 4px;
}

.timeline-org {
    font-size: 0.95rem;
    color: var(--color-text-light);
    font-weight: 500;
    margin-bottom: 8px;
}

.timeline-content > p:last-child {
    color: var(--color-text-light);
    font-size: 0.95rem;
}

How does the timeline work?

The .timeline::before pseudo-element draws a 2px vertical line down the left side of the container. It stretches from top to bottom using top: 0 and bottom: 0.

Each .timeline-marker is a blue circle that sits on top of that line, positioned with position: absolute and left: -40px. The 3px border matches the background color, creating a gap between the marker and the line.

  • Save style.css and refresh your browser.
  • Scroll to the Experience section. You should see a thin vertical line on the left with blue circle markers at each entry, and your role titles, dates, and descriptions offset to the right.

Timeline markers not aligned with the line?

The vertical line is positioned at left: 7px and the marker at left: -40px. These values depend on the padding-left: 40px on .timeline. Check that all three values are present and match the code above.

Help me fix the alignment of my CSS timeline markers.

The last two pieces are the Contact section and the footer. These close out the page with a clean, centered layout.

  • Add the following CSS below the .timeline-content > p:last-child block to style the Contact section and footer:
/* ===== Contact ===== */
#contact .container {
    text-align: center;
}

.contact-text {
    color: var(--color-text-light);
    font-size: 1.1rem;
    max-width: 550px;
    margin: 0 auto 32px;
}

.contact-links {
    display: flex;
    justify-content: center;
    gap: 24px;
    flex-wrap: wrap;
}

/* Bordered pill-shaped links with hover lift */
.contact-item {
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 14px 24px;
    border: 1px solid var(--color-border);
    border-radius: 10px;
    transition: all 0.3s;
    font-weight: 500;
}

.contact-item:hover {
    border-color: var(--color-primary);
    color: var(--color-primary);
    transform: translateY(-2px);
}

.contact-item i {
    font-size: 1.2rem;
}

/* ===== Footer ===== */
.footer {
    background-color: var(--color-dark);
    color: #94a3b8;
    text-align: center;
    padding: 24px 0;
    font-size: 0.9rem;
}

What does this code do?

Each .contact-item is a bordered box that uses Flexbox to align the icon and text side by side. On hover, the border turns blue and the item lifts slightly, matching the interactive feel of your project cards.

The footer uses the same dark background as the hero section (--color-dark), creating a visual bookend that frames the entire page.

  • Save style.css and refresh your browser.
  • Scroll to the Contact section. You should see your email, GitHub, and LinkedIn as bordered pill-shaped links centered on the page. Hover over one and watch it turn blue and lift.
  • Scroll to the very bottom. The footer should appear as a dark bar with muted grey text, matching the hero.

Contact links not centered?

The centering depends on #contact .container having text-align: center and .contact-links having justify-content: center. Make sure your HTML contact section has id="contact" (not a class) because the CSS uses the # ID selector.

Help me debug why my contact section is not centered.

✔️ Awesome, I've got everything!

Great. Double check you have saved style.css before moving on.

ⓧ I'd like to double check the full code

Here is the complete style.css file as it should look at the end of this step. Compare it with yours and fix any differences.

/* ===== CSS Reset & Variables ===== */
*,
*::before,
*::after {
    margin: 0;
    padding: 0;
    box-sizing: border-box;
}

:root {
    --color-primary: #2563eb;
    --color-primary-dark: #1d4ed8;
    --color-dark: #0f172a;
    --color-text: #1e293b;
    --color-text-light: #64748b;
    --color-bg: #ffffff;
    --color-bg-alt: #f8fafc;
    --color-border: #e2e8f0;
    --font-family: 'Inter', sans-serif;
    --container-width: 1100px;
    --nav-height: 70px;
}

html {
    scroll-behavior: smooth;
    scroll-padding-top: var(--nav-height);
}

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

a {
    text-decoration: none;
    color: inherit;
}

ul {
    list-style: none;
}

img {
    max-width: 100%;
    display: block;
}

.container {
    max-width: var(--container-width);
    margin: 0 auto;
    padding: 0 24px;
}

/* ===== Navigation ===== */
.navbar {
    position: fixed;
    top: 0;
    left: 0;
    width: 100%;
    height: var(--nav-height);
    background-color: rgba(255, 255, 255, 0.95);
    backdrop-filter: blur(10px);
    border-bottom: 1px solid var(--color-border);
    z-index: 1000;
    display: flex;
    align-items: center;
}

.nav-container {
    display: flex;
    align-items: center;
    justify-content: space-between;
}

.nav-logo {
    font-size: 1.5rem;
    font-weight: 700;
    color: var(--color-primary);
}

.nav-links {
    display: flex;
    gap: 32px;
}

.nav-links a {
    font-size: 0.95rem;
    font-weight: 500;
    color: var(--color-text-light);
    transition: color 0.3s;
}

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

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

/* ===== Hero ===== */
.hero {
    min-height: 100vh;
    display: flex;
    align-items: center;
    background-color: var(--color-dark);
    color: #ffffff;
    padding-top: var(--nav-height);
}

.hero-content {
    text-align: center;
    padding: 60px 0;
}

.hero-greeting {
    font-size: 1.1rem;
    color: var(--color-primary);
    font-weight: 500;
    margin-bottom: 12px;
}

.hero-name {
    font-size: 3.5rem;
    font-weight: 700;
    margin-bottom: 16px;
    line-height: 1.2;
}

.hero-tagline {
    font-size: 1.2rem;
    color: #94a3b8;
    max-width: 550px;
    margin: 0 auto 32px;
}

.hero-buttons {
    display: flex;
    gap: 16px;
    justify-content: center;
}

.btn {
    display: inline-block;
    padding: 12px 28px;
    border-radius: 8px;
    font-size: 1rem;
    font-weight: 600;
    transition: all 0.3s;
    cursor: pointer;
}

.btn-primary {
    background-color: var(--color-primary);
    color: #ffffff;
}

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

.btn-outline {
    border: 2px solid #475569;
    color: #e2e8f0;
}

.btn-outline:hover {
    border-color: var(--color-primary);
    color: var(--color-primary);
    transform: translateY(-2px);
}

/* ===== Sections ===== */
.section {
    padding: 80px 0;
}

.section-alt {
    background-color: var(--color-bg-alt);
}

.section-title {
    font-size: 2rem;
    font-weight: 700;
    text-align: center;
    margin-bottom: 48px;
    position: relative;
}

.section-title::after {
    content: '';
    display: block;
    width: 60px;
    height: 4px;
    background-color: var(--color-primary);
    margin: 12px auto 0;
    border-radius: 2px;
}

/* ===== About ===== */
.about-content {
    max-width: 700px;
    margin: 0 auto;
    text-align: center;
}

.about-content p {
    color: var(--color-text-light);
    margin-bottom: 16px;
    font-size: 1.05rem;
}

/* ===== Skills ===== */
.skills-grid {
    display: grid;
    grid-template-columns: repeat(3, 1fr);
    gap: 32px;
}

.skill-category {
    background-color: var(--color-bg);
    padding: 32px;
    border-radius: 12px;
    border: 1px solid var(--color-border);
}

.skill-category h3 {
    font-size: 1.1rem;
    font-weight: 600;
    color: var(--color-primary);
    margin-bottom: 16px;
}

.skill-list li {
    padding: 6px 0;
    color: var(--color-text-light);
    font-size: 0.95rem;
    border-bottom: 1px solid var(--color-border);
}

.skill-list li:last-child {
    border-bottom: none;
}

/* ===== Projects ===== */
.projects-grid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(320px, 1fr));
    gap: 28px;
}

.project-card {
    background-color: var(--color-bg);
    border-radius: 12px;
    border: 1px solid var(--color-border);
    overflow: hidden;
    transition: transform 0.3s, box-shadow 0.3s;
}

.project-card:hover {
    transform: translateY(-6px);
    box-shadow: 0 12px 24px rgba(0, 0, 0, 0.1);
}

.project-image {
    height: 200px;
    background-color: var(--color-bg-alt);
    display: flex;
    align-items: center;
    justify-content: center;
}

.project-placeholder {
    font-size: 1.2rem;
    color: var(--color-text-light);
    font-weight: 500;
}

.project-info {
    padding: 24px;
}

.project-info h3 {
    font-size: 1.2rem;
    font-weight: 600;
    margin-bottom: 8px;
}

.project-info p {
    color: var(--color-text-light);
    font-size: 0.95rem;
    margin-bottom: 16px;
}

.project-tech {
    display: flex;
    flex-wrap: wrap;
    gap: 8px;
    margin-bottom: 16px;
}

.project-tech span {
    background-color: var(--color-bg-alt);
    color: var(--color-primary);
    padding: 4px 12px;
    border-radius: 20px;
    font-size: 0.85rem;
    font-weight: 500;
}

.project-links {
    display: flex;
    gap: 16px;
}

.project-links a {
    color: var(--color-text-light);
    font-size: 0.9rem;
    font-weight: 500;
    transition: color 0.3s;
}

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

.project-links a i {
    margin-right: 4px;
}

/* ===== Experience Timeline ===== */
.timeline {
    max-width: 700px;
    margin: 0 auto;
    position: relative;
    padding-left: 40px;
}

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

.timeline-item {
    position: relative;
    margin-bottom: 40px;
}

.timeline-item:last-child {
    margin-bottom: 0;
}

.timeline-marker {
    position: absolute;
    left: -40px;
    top: 4px;
    width: 16px;
    height: 16px;
    background-color: var(--color-primary);
    border-radius: 50%;
    border: 3px solid var(--color-bg-alt);
}

.timeline-date {
    display: inline-block;
    font-size: 0.85rem;
    font-weight: 600;
    color: var(--color-primary);
    margin-bottom: 8px;
}

.timeline-content h3 {
    font-size: 1.15rem;
    font-weight: 600;
    margin-bottom: 4px;
}

.timeline-org {
    font-size: 0.95rem;
    color: var(--color-text-light);
    font-weight: 500;
    margin-bottom: 8px;
}

.timeline-content > p:last-child {
    color: var(--color-text-light);
    font-size: 0.95rem;
}

/* ===== Contact ===== */
#contact .container {
    text-align: center;
}

.contact-text {
    color: var(--color-text-light);
    font-size: 1.1rem;
    max-width: 550px;
    margin: 0 auto 32px;
}

.contact-links {
    display: flex;
    justify-content: center;
    gap: 24px;
    flex-wrap: wrap;
}

.contact-item {
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 14px 24px;
    border: 1px solid var(--color-border);
    border-radius: 10px;
    transition: all 0.3s;
    font-weight: 500;
}

.contact-item:hover {
    border-color: var(--color-primary);
    color: var(--color-primary);
    transform: translateY(-2px);
}

.contact-item i {
    font-size: 1.2rem;
}

/* ===== Footer ===== */
.footer {
    background-color: var(--color-dark);
    color: #94a3b8;
    text-align: center;
    padding: 24px 0;
    font-size: 0.9rem;
}

Every section of your portfolio is now fully styled. Next up, you will make the layout responsive for mobile devices and add JavaScript interactivity like a hamburger menu and scroll animations.

Make It Responsive and Add JavaScript

Your portfolio looks polished on a desktop screen. Every section is styled, the timeline is in place, and project cards lift on hover.

A hiring manager might pull up your site on their phone during a commute. If the layout breaks on a smaller screen, that's a missed opportunity. In this step, you'll add responsive design breakpoints and vanilla JavaScript interactivity so your portfolio works on every device.

In this step, get ready to:
  • Add a CSS media query that adapts the layout for mobile screens.
  • Wire up the mobile hamburger navigation menu with JavaScript.
  • Add scroll-triggered reveal animations and active nav link highlighting.
Add responsive breakpoints

Your three-column grids and large headings look great on a wide monitor. On a phone screen, they'd be cramped and unreadable. CSS media queries let you apply different styles based on screen width.

  • In style.css, scroll to the very bottom of the file (after the .footer styles).
  • Add the following responsive media query:
/* ===== Responsive ===== */
@media (max-width: 768px) {
    /* Shrink the hero heading and tagline for small screens */
    .hero-name {
        font-size: 2.5rem;
    }

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

    /* Stack buttons vertically instead of side by side */
    .hero-buttons {
        flex-direction: column;
        align-items: center;
    }

    /* Show the hamburger icon on mobile */
    .nav-toggle {
        display: block;
    }

    /* Convert nav links into a dropdown panel */
    .nav-links {
        position: fixed;
        top: var(--nav-height);
        left: 0;
        width: 100%;
        background-color: var(--color-bg);
        flex-direction: column;
        align-items: center;
        padding: 24px 0;
        gap: 20px;
        border-bottom: 1px solid var(--color-border);
        transform: translateY(-100%);
        opacity: 0;
        pointer-events: none;
        transition: all 0.3s;
    }

    /* Slide the nav panel in when open */
    .nav-links.open {
        transform: translateY(0);
        opacity: 1;
        pointer-events: all;
    }

    /* Collapse grids to a single column */
    .skills-grid {
        grid-template-columns: 1fr;
    }

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

    /* Tighten section spacing and title size */
    .section {
        padding: 60px 0;
    }

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

What does this code do?

Everything inside @media (max-width: 768px) only applies when the browser window is 768 pixels wide or narrower. That covers most phones and small tablets.

  • .nav-toggle { display: block; } reveals the hamburger menu button that was hidden on desktop.
  • .nav-links becomes a full-width dropdown panel that starts hidden (translateY(-100%)). The .open class slides it into view.
  • The skills and projects grids collapse from multi-column to single-column so cards stack vertically.
  • Save style.css.
  • Refresh your browser and resize the window to about half your screen width (or press F12 to open DevTools and click the device toggle icon).
  • You should see the hero heading shrink, the nav links disappear, and a hamburger icon appear in the top right. The skills and projects sections now stack into a single column.

Layout not changing when you resize?

  • Make sure you saved style.css before refreshing.
  • Check that the @media block is outside and below all other CSS rules. It should not be nested inside another selector.
  • Confirm the closing brace of the media query matches its opening brace. A missing } can silently break everything below it.

Help me debug why my responsive media query isn't working.

Add mobile navigation and navbar scroll effect

The hamburger icon is visible on mobile now, but clicking it does nothing. You need JavaScript to toggle the menu open and closed.

  • Open script.js in VS Code. The file is currently empty.
  • Add the mobile navigation toggle by pasting this code:
// ===== Mobile Navigation Toggle =====
const navToggle = document.getElementById('nav-toggle');
const navLinks = document.getElementById('nav-links');

// Open or close the mobile menu when the hamburger is clicked
navToggle.addEventListener('click', function () {
    navLinks.classList.toggle('open');
});

// Close mobile nav when a link is clicked
const navLinkItems = document.querySelectorAll('.nav-links a');
navLinkItems.forEach(function (link) {
    link.addEventListener('click', function () {
        navLinks.classList.remove('open');
    });
});

What does this code do?

  • getElementById('nav-toggle') grabs the hamburger button from the HTML. getElementById('nav-links') grabs the navigation list.
  • When the hamburger is clicked, classList.toggle('open') adds or removes the open class. Your CSS makes .nav-links.open slide the panel into view.
  • The second block closes the menu automatically when a visitor clicks any nav link. Without this, the menu would stay open after navigating.
  • Save script.js and refresh your browser.
  • Resize your browser to mobile width (or use DevTools device mode). Click the hamburger icon. The nav links should slide down from the top. Click any link and the menu closes.

Hamburger icon not responding to clicks?

  • Check that your HTML has id="nav-toggle" on the <button> element and id="nav-links" on the <ul> element. JavaScript can't find elements with mismatched IDs.
  • Open the browser console (F12, then the Console tab) and look for red error messages. A typo in script.js will show up here.

Help me debug my mobile navigation toggle.

Next, you'll add a subtle shadow to the navbar when visitors scroll down. This gives a visual cue that the page content is scrolling beneath the fixed navigation.

  • In script.js, add the navbar scroll effect below your existing code:
// ===== Navbar Shadow on Scroll =====
const navbar = document.getElementById('navbar');

window.addEventListener('scroll', function () {
    // Add a shadow after scrolling 50 pixels
    if (window.scrollY > 50) {
        navbar.style.boxShadow = '0 2px 10px rgba(0, 0, 0, 0.1)';
    } else {
        navbar.style.boxShadow = 'none';
    }
});

What does this code do?

A scroll event listener fires every time the page scrolls. When window.scrollY passes 50 pixels, a soft box shadow appears under the navbar. When the user scrolls back to the top, the shadow disappears.

  • Save script.js and refresh your browser.
  • Scroll down the page slowly. You should see a faint shadow appear beneath the white navbar once you scroll past the hero section.

No shadow appearing on scroll?

  • Make sure your HTML has id="navbar" on the <nav> element.
  • The shadow is subtle. Try scrolling in a well-lit area or inspect the navbar in DevTools to see the box-shadow property change.

Help me debug why my navbar scroll shadow isn't appearing.

Add scroll reveal animations and active nav highlighting

Right now, all six content sections appear at once when the page loads. Scroll-triggered animations make each section fade in as the visitor reaches it, creating a polished feel.

The animation needs two parts: CSS classes that define the hidden and visible states, and JavaScript that switches between them as the user scrolls.

  • Switch back to style.css.
  • Find the /* ===== Responsive ===== */ comment you just added.
  • Add the scroll reveal animation classes directly above that comment:
/* ===== Scroll Reveal Animation ===== */
.reveal {
    opacity: 0;
    transform: translateY(30px);
    transition: opacity 0.6s ease, transform 0.6s ease;
}

.reveal.active {
    opacity: 1;
    transform: translateY(0);
}

What does this code do?

  • .reveal is the "hidden" state. Elements start invisible (opacity: 0) and shifted 30 pixels down.
  • .reveal.active is the "visible" state. The element fades in and slides up to its natural position.
  • The transition property makes the change animate smoothly over 0.6 seconds instead of snapping instantly.
  • Save style.css.

You won't see a change yet. The JavaScript in the next chunk will apply the reveal class to each section and add active when it scrolls into view.

✔️ Awesome, I've got everything!

Make sure you saved style.css before moving on.

ⓧ I'd like to double check the full code

Here is the complete style.css file up to this point. Compare it with yours:

/* ===== CSS Reset & Variables ===== */
*,
*::before,
*::after {
    margin: 0;
    padding: 0;
    box-sizing: border-box;
}

:root {
    --color-primary: #2563eb;
    --color-primary-dark: #1d4ed8;
    --color-dark: #0f172a;
    --color-text: #1e293b;
    --color-text-light: #64748b;
    --color-bg: #ffffff;
    --color-bg-alt: #f8fafc;
    --color-border: #e2e8f0;
    --font-family: 'Inter', sans-serif;
    --container-width: 1100px;
    --nav-height: 70px;
}

html {
    scroll-behavior: smooth;
    scroll-padding-top: var(--nav-height);
}

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

a {
    text-decoration: none;
    color: inherit;
}

ul {
    list-style: none;
}

img {
    max-width: 100%;
    display: block;
}

.container {
    max-width: var(--container-width);
    margin: 0 auto;
    padding: 0 24px;
}

/* ===== Navigation ===== */
.navbar {
    position: fixed;
    top: 0;
    left: 0;
    width: 100%;
    height: var(--nav-height);
    background-color: rgba(255, 255, 255, 0.95);
    backdrop-filter: blur(10px);
    border-bottom: 1px solid var(--color-border);
    z-index: 1000;
    display: flex;
    align-items: center;
}

.nav-container {
    display: flex;
    align-items: center;
    justify-content: space-between;
}

.nav-logo {
    font-size: 1.5rem;
    font-weight: 700;
    color: var(--color-primary);
}

.nav-links {
    display: flex;
    gap: 32px;
}

.nav-links a {
    font-size: 0.95rem;
    font-weight: 500;
    color: var(--color-text-light);
    transition: color 0.3s;
}

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

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

/* ===== Hero ===== */
.hero {
    min-height: 100vh;
    display: flex;
    align-items: center;
    background-color: var(--color-dark);
    color: #ffffff;
    padding-top: var(--nav-height);
}

.hero-content {
    text-align: center;
    padding: 60px 0;
}

.hero-greeting {
    font-size: 1.1rem;
    color: var(--color-primary);
    font-weight: 500;
    margin-bottom: 12px;
}

.hero-name {
    font-size: 3.5rem;
    font-weight: 700;
    margin-bottom: 16px;
    line-height: 1.2;
}

.hero-tagline {
    font-size: 1.2rem;
    color: #94a3b8;
    max-width: 550px;
    margin: 0 auto 32px;
}

.hero-buttons {
    display: flex;
    gap: 16px;
    justify-content: center;
}

.btn {
    display: inline-block;
    padding: 12px 28px;
    border-radius: 8px;
    font-size: 1rem;
    font-weight: 600;
    transition: all 0.3s;
    cursor: pointer;
}

.btn-primary {
    background-color: var(--color-primary);
    color: #ffffff;
}

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

.btn-outline {
    border: 2px solid #475569;
    color: #e2e8f0;
}

.btn-outline:hover {
    border-color: var(--color-primary);
    color: var(--color-primary);
    transform: translateY(-2px);
}

/* ===== Sections ===== */
.section {
    padding: 80px 0;
}

.section-alt {
    background-color: var(--color-bg-alt);
}

.section-title {
    font-size: 2rem;
    font-weight: 700;
    text-align: center;
    margin-bottom: 48px;
    position: relative;
}

.section-title::after {
    content: '';
    display: block;
    width: 60px;
    height: 4px;
    background-color: var(--color-primary);
    margin: 12px auto 0;
    border-radius: 2px;
}

/* ===== About ===== */
.about-content {
    max-width: 700px;
    margin: 0 auto;
    text-align: center;
}

.about-content p {
    color: var(--color-text-light);
    margin-bottom: 16px;
    font-size: 1.05rem;
}

/* ===== Skills ===== */
.skills-grid {
    display: grid;
    grid-template-columns: repeat(3, 1fr);
    gap: 32px;
}

.skill-category {
    background-color: var(--color-bg);
    padding: 32px;
    border-radius: 12px;
    border: 1px solid var(--color-border);
}

.skill-category h3 {
    font-size: 1.1rem;
    font-weight: 600;
    color: var(--color-primary);
    margin-bottom: 16px;
}

.skill-list li {
    padding: 6px 0;
    color: var(--color-text-light);
    font-size: 0.95rem;
    border-bottom: 1px solid var(--color-border);
}

.skill-list li:last-child {
    border-bottom: none;
}

/* ===== Projects ===== */
.projects-grid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(320px, 1fr));
    gap: 28px;
}

.project-card {
    background-color: var(--color-bg);
    border-radius: 12px;
    border: 1px solid var(--color-border);
    overflow: hidden;
    transition: transform 0.3s, box-shadow 0.3s;
}

.project-card:hover {
    transform: translateY(-6px);
    box-shadow: 0 12px 24px rgba(0, 0, 0, 0.1);
}

.project-image {
    height: 200px;
    background-color: var(--color-bg-alt);
    display: flex;
    align-items: center;
    justify-content: center;
}

.project-placeholder {
    font-size: 1.2rem;
    color: var(--color-text-light);
    font-weight: 500;
}

.project-info {
    padding: 24px;
}

.project-info h3 {
    font-size: 1.2rem;
    font-weight: 600;
    margin-bottom: 8px;
}

.project-info p {
    color: var(--color-text-light);
    font-size: 0.95rem;
    margin-bottom: 16px;
}

.project-tech {
    display: flex;
    flex-wrap: wrap;
    gap: 8px;
    margin-bottom: 16px;
}

.project-tech span {
    background-color: var(--color-bg-alt);
    color: var(--color-primary);
    padding: 4px 12px;
    border-radius: 20px;
    font-size: 0.85rem;
    font-weight: 500;
}

.project-links {
    display: flex;
    gap: 16px;
}

.project-links a {
    color: var(--color-text-light);
    font-size: 0.9rem;
    font-weight: 500;
    transition: color 0.3s;
}

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

.project-links a i {
    margin-right: 4px;
}

/* ===== Experience Timeline ===== */
.timeline {
    max-width: 700px;
    margin: 0 auto;
    position: relative;
    padding-left: 40px;
}

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

.timeline-item {
    position: relative;
    margin-bottom: 40px;
}

.timeline-item:last-child {
    margin-bottom: 0;
}

.timeline-marker {
    position: absolute;
    left: -40px;
    top: 4px;
    width: 16px;
    height: 16px;
    background-color: var(--color-primary);
    border-radius: 50%;
    border: 3px solid var(--color-bg-alt);
}

.timeline-date {
    display: inline-block;
    font-size: 0.85rem;
    font-weight: 600;
    color: var(--color-primary);
    margin-bottom: 8px;
}

.timeline-content h3 {
    font-size: 1.15rem;
    font-weight: 600;
    margin-bottom: 4px;
}

.timeline-org {
    font-size: 0.95rem;
    color: var(--color-text-light);
    font-weight: 500;
    margin-bottom: 8px;
}

.timeline-content > p:last-child {
    color: var(--color-text-light);
    font-size: 0.95rem;
}

/* ===== Contact ===== */
#contact .container {
    text-align: center;
}

.contact-text {
    color: var(--color-text-light);
    font-size: 1.1rem;
    max-width: 550px;
    margin: 0 auto 32px;
}

.contact-links {
    display: flex;
    justify-content: center;
    gap: 24px;
    flex-wrap: wrap;
}

.contact-item {
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 14px 24px;
    border: 1px solid var(--color-border);
    border-radius: 10px;
    transition: all 0.3s;
    font-weight: 500;
}

.contact-item:hover {
    border-color: var(--color-primary);
    color: var(--color-primary);
    transform: translateY(-2px);
}

.contact-item i {
    font-size: 1.2rem;
}

/* ===== Footer ===== */
.footer {
    background-color: var(--color-dark);
    color: #94a3b8;
    text-align: center;
    padding: 24px 0;
    font-size: 0.9rem;
}

/* ===== Scroll Reveal Animation ===== */
.reveal {
    opacity: 0;
    transform: translateY(30px);
    transition: opacity 0.6s ease, transform 0.6s ease;
}

.reveal.active {
    opacity: 1;
    transform: translateY(0);
}

/* ===== Responsive ===== */
@media (max-width: 768px) {
    .hero-name {
        font-size: 2.5rem;
    }

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

    .hero-buttons {
        flex-direction: column;
        align-items: center;
    }

    .nav-toggle {
        display: block;
    }

    .nav-links {
        position: fixed;
        top: var(--nav-height);
        left: 0;
        width: 100%;
        background-color: var(--color-bg);
        flex-direction: column;
        align-items: center;
        padding: 24px 0;
        gap: 20px;
        border-bottom: 1px solid var(--color-border);
        transform: translateY(-100%);
        opacity: 0;
        pointer-events: none;
        transition: all 0.3s;
    }

    .nav-links.open {
        transform: translateY(0);
        opacity: 1;
        pointer-events: all;
    }

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

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

    .section {
        padding: 60px 0;
    }

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

Now for the JavaScript side. The IntersectionObserver API watches elements and tells you when they enter the viewport. It is more performant than calculating scroll positions manually on every scroll event.

  • Switch to script.js and add the scroll reveal observer below your existing code:
// ===== Scroll Reveal Animation =====
const sections = document.querySelectorAll('.section');

// Create an observer that fires when 10% of a section is visible
const revealObserver = new IntersectionObserver(
    function (entries) {
        entries.forEach(function (entry) {
            if (entry.isIntersecting) {
                entry.target.classList.add('active');
            }
        });
    },
    { threshold: 0.1 }
);

// Mark each section as hidden and start observing it
sections.forEach(function (section) {
    section.classList.add('reveal');
    revealObserver.observe(section);
});

What does this code do?

  • querySelectorAll('.section') grabs all six content sections (About, Skills, Projects, Experience, Contact, and the extra section-alt ones).
  • The IntersectionObserver watches each section. When 10% of a section becomes visible (threshold: 0.1), it adds the active class.
  • The forEach loop adds the reveal class to every section (making them start hidden) and then tells the observer to watch each one.
  • Save script.js and refresh your browser.
  • Scroll down from the hero section. Each content section should fade in and slide up smoothly as you reach it.

Sections not animating on scroll?

  • Make sure the .reveal and .reveal.active classes are saved in style.css. The JavaScript adds these class names, but the CSS defines what they look like.
  • If sections are invisible and never appear, check the browser console for errors. A typo in script.js can prevent the observer from running.

Help me debug my scroll reveal animations.

The last piece of interactivity is active nav link highlighting. As visitors scroll through the page, the corresponding nav link should change color to show which section they're currently reading.

  • In script.js, add the active link highlighter below your existing code:
// ===== Active Navigation Link on Scroll =====
const allSections = document.querySelectorAll('.section, .hero');

window.addEventListener('scroll', function () {
    var current = '';

    // Find which section is currently in view
    allSections.forEach(function (section) {
        var sectionTop = section.offsetTop - 100;
        if (window.scrollY >= sectionTop) {
            current = section.getAttribute('id');
        }
    });

    // Highlight the matching nav link
    navLinkItems.forEach(function (link) {
        link.style.color = '';
        if (link.getAttribute('href') === '#' + current) {
            link.style.color = 'var(--color-primary)';
        }
    });
});

What does this code do?

  • On every scroll event, the code loops through all sections and checks which one the user has scrolled past (using offsetTop - 100 to account for the fixed navbar).
  • It resets all nav link colors, then sets the matching link's color to your primary blue using var(--color-primary).
  • This reuses the navLinkItems variable you already declared earlier in the file for the mobile menu.
  • Save script.js and refresh your browser.
  • Scroll through the page slowly. Watch the nav links at the top. The link matching the current section should turn blue as you scroll past each section boundary.

Nav links not highlighting as you scroll?

  • Make sure each <section> in your HTML has an id attribute that matches the href in the corresponding nav link. For example, id="about" matches href="#about".
  • Check the browser console for errors. If a previous script block has a syntax error, code below it won't run.

Help me debug my active navigation highlighting.

✔️ Awesome, I've got everything!

Make sure you saved script.js before moving on.

ⓧ I'd like to double check the full code

Here is the complete script.js file. Compare it with yours:

// ===== Mobile Navigation Toggle =====
const navToggle = document.getElementById('nav-toggle');
const navLinks = document.getElementById('nav-links');

navToggle.addEventListener('click', function () {
    navLinks.classList.toggle('open');
});

// Close mobile nav when a link is clicked
const navLinkItems = document.querySelectorAll('.nav-links a');
navLinkItems.forEach(function (link) {
    link.addEventListener('click', function () {
        navLinks.classList.remove('open');
    });
});

// ===== Navbar Shadow on Scroll =====
const navbar = document.getElementById('navbar');

window.addEventListener('scroll', function () {
    if (window.scrollY > 50) {
        navbar.style.boxShadow = '0 2px 10px rgba(0, 0, 0, 0.1)';
    } else {
        navbar.style.boxShadow = 'none';
    }
});

// ===== Scroll Reveal Animation =====
const sections = document.querySelectorAll('.section');

const revealObserver = new IntersectionObserver(
    function (entries) {
        entries.forEach(function (entry) {
            if (entry.isIntersecting) {
                entry.target.classList.add('active');
            }
        });
    },
    { threshold: 0.1 }
);

sections.forEach(function (section) {
    section.classList.add('reveal');
    revealObserver.observe(section);
});

// ===== Active Navigation Link on Scroll =====
const allSections = document.querySelectorAll('.section, .hero');

window.addEventListener('scroll', function () {
    var current = '';

    allSections.forEach(function (section) {
        var sectionTop = section.offsetTop - 100;
        if (window.scrollY >= sectionTop) {
            current = section.getAttribute('id');
        }
    });

    navLinkItems.forEach(function (link) {
        link.style.color = '';
        if (link.getAttribute('href') === '#' + current) {
            link.style.color = 'var(--color-primary)';
        }
    });
});

Your portfolio is now fully responsive and interactive. The mobile menu slides in, sections fade in on scroll, and the active nav link highlights as visitors browse. Next up, you'll deploy it to GitHub Pages and make it live on the internet.

Deploy to GitHub Pages

Your portfolio looks great locally with responsive layouts, smooth animations, and interactive navigation. The missing piece is getting it on the internet where hiring managers can actually visit it.

GitHub Pages hosts static websites directly from a GitHub repository for free. You'll use it to give your portfolio a real URL you can share on resumes and job applications.

In this step, get ready to:
  • Initialize a Git repository and commit your portfolio files.
  • Push your code to a new GitHub repository.
  • Enable GitHub Pages and get your live URL.
Initialize a Git repository and make your first commit

Git is a version control system that tracks changes to your files. Initializing a repository tells Git to start watching your portfolio folder.

  • Click Terminal in the VS Code menu bar.
  • Click New Terminal.

The terminal opens inside your portfolio folder automatically because VS Code uses the workspace root.

  • Initialize a new Git repository by running this command:
git init

What does this command do?

git init creates a hidden .git folder inside your project. This folder is where Git stores all its tracking data.

You should see a message that says Initialized empty Git repository followed by the path to your folder.

  • Stage all your files and create your first commit by running these commands:
git add .
git commit -m "Initial portfolio site"

What do these commands do?

  • git add . stages every file in the folder, telling Git to include them in the next snapshot.
  • git commit -m "..." saves that snapshot with a descriptive message. Think of a commit as a checkpoint you can always return to.

You should see output listing three files: index.html, style.css, and script.js.

Seeing an error about user identity?

If Git asks you to set your name and email, it means this is your first time using Git on this computer. Run these two commands to configure your identity:

git config --global user.name "Your Name"
git config --global user.email "your.email@example.com"

Replace the name and email with your own, then re-run the git commit command.

Still stuck? Help me fix my git commit error.

Create a GitHub repository and push your code

Your code is committed locally, but it only exists on your computer. You need a GitHub repository to store it online and serve it as a website.

  • Open github.com in your browser and sign in.
  • Click the + button in the top-right corner of the page.
  • Click New repository.
  • Enter portfolio as the Repository name.
  • Select Public for the visibility.
  • Leave all checkboxes unchecked (no README, no .gitignore, no license).
  • Click Create repository.

Why leave the checkboxes unchecked?

You already have files in your local Git repository. If GitHub adds its own README or .gitignore, the two repositories will have conflicting histories and the push will fail.

After clicking Create repository, GitHub shows a quick setup page with commands you can copy.

  • Make sure HTTPS is selected at the top of the quick setup page (not SSH).
  • Copy the URL shown on the page. It looks like https://github.com/yourusername/portfolio.git.
  • Back in your VS Code terminal, connect your local repository to GitHub and push your code by running these commands:
git remote add origin [[YOUR_REMOTE_URL="https://github.com/yourusername/portfolio.git"]]
git branch -M main
git push -u origin main

What do these commands do?

  • git remote add origin tells your local Git repository where the GitHub copy lives.
  • git branch -M main renames your default branch to main, which is the standard branch name on GitHub.
  • git push -u origin main uploads your committed files to GitHub.

Seeing a login prompt?

When you push for the first time, Git needs to verify your GitHub identity. You may see a browser window pop up asking you to authorize Git, or a credential dialog box.

Follow the prompts to sign in with your GitHub account. Once authenticated, the push continues automatically.

  • Refresh your GitHub repository page in the browser.

You should see your three files listed: index.html, style.css, and script.js.

Push failed or permission denied?

  • Double-check that the URL you pasted matches the one on your GitHub quick setup page. A single typo will break the connection.
  • If you see fatal: remote origin already exists, run git remote remove origin and then re-run the three commands above.
  • If authentication failed, make sure you completed the browser sign-in prompt that appeared.

Help me fix my git push error.

Enable GitHub Pages and verify your site

Your code is on GitHub, but it is not a website yet. GitHub Pages reads your repository files and serves them as a live site at a public URL.

  • On your repository page, click Settings in the top tab bar.
  • Click Pages in the left sidebar under Code and automation.
  • Under Build and deployment, set the Source dropdown to Deploy from a branch.
  • In the Branch section, select main from the first dropdown.
  • Leave the folder set to / (root).
  • Click Save.

Why GitHub Pages?

For a static HTML, CSS, and JavaScript site, GitHub Pages is the simplest hosting option. It is free, requires no build step, and needs no additional account beyond GitHub.

Alternatives like Netlify and Vercel add value for framework-based projects with server-side rendering, but they are overkill for a plain static portfolio.

GitHub Pages takes a minute or two to build and deploy your site. You can check the status by refreshing the Pages settings page.

  • Wait about 1-2 minutes, then visit your live site at the URL shown at the top of the Pages settings page.

What is my site URL?

If you named your repository portfolio, your URL is yourusername.github.io/portfolio.

If you named your repository yourusername.github.io (matching your GitHub username), your URL is just yourusername.github.io with no subfolder.

You should see your full portfolio site live in the browser. The dark hero section, your project cards, the timeline, and all your animations should work exactly as they did locally.

Seeing a 404 page?

  • GitHub Pages can take up to 10 minutes to publish on the first deploy. Wait a few more minutes and refresh.
  • Make sure you selected the main branch and / (root) folder in the Pages settings, then clicked Save.
  • Confirm your index.html file is in the root of the repository (not inside a subfolder) by checking the file list on your GitHub repo page.

Help me fix my GitHub Pages deployment.

Your portfolio is live and shareable. Next up, there is an optional Secret Mission where you can add a dark/light theme toggle to give visitors a choice of color schemes.

Secret mission

Add a Dark/Light Theme Toggle

Your portfolio looks great in light mode, but what about visitors who prefer dark mode? In this mission, you will add a sun/moon toggle button to the navigation bar that switches your entire color scheme with one click. The visitor's preference saves to localStorage so it persists across page refreshes and browser sessions.

Before you go

Clean Up Your Resources

Decide whether to keep your resources running, pause them to come back later, or delete them entirely. This project runs entirely on free tools, so there are no ongoing costs.

Resources you used:

  • Local portfolio folder containing index.html, style.css, and script.js.
  • GitHub repository (portfolio or your user site repo).
  • GitHub Pages deployment serving your live site.

Keep everything running

No action needed. Choose this if you want your portfolio to stay live and accessible at your GitHub Pages URL.

  • Your site stays live at your GitHub Pages URL for free.
  • You can push updates to your main branch at any time and your site will redeploy automatically.
  • Your local portfolio folder stays on your Desktop for future edits.

Pause - I'll come back to this later

Disable the live site but keep all your code and files intact so you can pick up later.

  • Go to your repository on GitHub.
  • Click Settings, then Pages.
  • Under Build and deployment, change the Source to None and click Save.
  • Your live site will go offline, but your repository and local files remain untouched.
  • To resume later, set the Source back to the main branch and your site will redeploy.

Delete - I don't want to use this again

Remove all project resources and start fresh. This permanently deletes your repository, live site, and local files.

  • Go to your repository on GitHub.
  • Click Settings.
  • Scroll to the bottom and click Delete this repository.
  • Confirm the deletion by typing the repository name when prompted.
  • Delete the local portfolio folder from your Desktop.

Mission accomplished

That's a Wrap!

Nice work! You just built and deployed a professional portfolio website from scratch using nothing but HTML, CSS, and JavaScript.

You've learned how to:

  • Build a responsive single-page portfolio with semantic HTML, modern CSS (custom properties, Grid, Flexbox, animations), and vanilla JavaScript interactivity.
  • Add scroll-triggered reveal animations using the IntersectionObserver API and a mobile hamburger menu with active navigation highlighting.
  • Deploy your site for free to GitHub Pages with a shareable, professional URL you can put on resumes and job applications.
  • Secret Mission: Added a dark/light theme toggle with localStorage persistence so visitors' preferences survive a page refresh.

Ready to quiz yourself?