Build and Deploy a Portfolio Site
Build a responsive portfolio site with HTML, CSS, and JS and deploy it live.
Introduction
30 Second Summary
Every developer needs a home on the internet. Whether you're applying for jobs or showcasing side projects, a personal portfolio site is your digital handshake.
In this project, you will build a responsive portfolio website from scratch using HTML, CSS, and JavaScript, then deploy it live to the web using GitHub Pages.
What You'll Build
A modern single-page portfolio site with a hero section, about section, project showcase grid, and contact section, styled with modern CSS and deployed live on GitHub Pages.
By the end of this project, you'll have:
- A complete single-page portfolio with semantic HTML structure, navigation, hero section, project cards, and a contact section.
- Responsive styling using CSS Flexbox, CSS Grid, and media queries that adapts to any screen size.
- A live deployed site on GitHub Pages with a shareable public URL anyone can visit.
- Secret Mission: A filterable project gallery with animated category buttons that show and hide cards.
Are there any prerequisites?
None. This project is designed for beginners and includes setup instructions for all required tools. You just need a computer with a web browser.
Set Up Your Project Repository
Every website needs a home for its files. Before you write any code, you need a place to store your HTML, CSS, and JavaScript files that also tracks your changes over time.
In this step, you will create a GitHub repository for your portfolio project, clone it to your computer using Git, and set up the three files that will make up your site.
In this step, get ready to:
- Create a new repository on GitHub with a README file.
- Clone the repository to your local machine.
- Create the project file structure with three empty files.
Create a new repository on GitHub
A repository is a project folder that lives on GitHub. It stores your files and remembers every change you make to them.
- Open your browser and go to github.com.
- Sign in to your GitHub account.
- In the upper-right corner of any page, click the + icon.
- Click New repository.
- Type portfolio in the Repository name field.
- Make sure Public is selected for the repository visibility.
- Check the box next to Add a README file.
- Click Create repository.
What is a repository?
A repository (often called a "repo") is like a project folder with superpowers. It stores all your project files and keeps a complete history of every change you make.
Because it lives on GitHub, you can access it from any computer. The README file you just added is a description page that appears on the repository's homepage.
Why does the repository need to be public?
GitHub Pages requires a public repository if you are using a free GitHub account. You will use GitHub Pages later in this project to deploy your site to a live URL.
You should now see your new repository page on GitHub, with a single README.md file listed.
Clone the repository to your local machine
Your repository currently lives on GitHub's servers. Cloning creates a copy of it on your computer so you can add files and make changes locally.
- On your repository page, click the green Code button above the list of files.
- Make sure HTTPS is selected.
- Click the copy icon to copy the URL.
What is the clone URL?
The HTTPS URL looks like https://github.com/your-username/portfolio.git. This tells Git where to find your repository on GitHub so it can download a copy.
- Press Cmd+Space (macOS) or the Windows key (Windows) to open your search bar.
- Type Terminal (macOS) or Git Bash (Windows) and press Enter to open it.
- Move to your Desktop by running this command:
cd ~/Desktop
- Clone your repository by running this command, pasting the URL you copied from GitHub:
git clone [[REPO_URL="https://github.com/your-username/portfolio.git"]]
What does this command do?
The git clone command downloads a full copy of your GitHub repository to your computer. It creates a new folder called portfolio on your Desktop containing the README.md file you created earlier.
- Confirm the folder was created by running:
ls
You should see a folder called portfolio in the output.
Don't see the portfolio folder?
- Make sure you ran cd ~/Desktop before cloning. The folder appears in whichever directory you were in when you ran the clone command.
- Double-check that you pasted the correct URL from GitHub. It should end with .git.
- If Git asks for a username and password, use your GitHub username and a personal access token (not your GitHub password).
help me troubleshoot why my git clone command is not working
Create the project files
Your portfolio site will be made up of three files. Each one handles a different layer of the website.
- Move into your project folder by running:
cd portfolio
- Create the three project files by running:
touch index.html style.css script.js
What do these three files do?
- index.html holds the content and structure of your page (headings, paragraphs, links).
- style.css controls how everything looks (colors, spacing, layout).
- script.js adds interactive behavior (smooth scrolling when you click a link).
- Confirm the files were created by running:
ls
You should see index.html, style.css, script.js, and README.md in the output.
Seeing a 'command not found' error for touch?
- On Windows, make sure you are using Git Bash and not the default Command Prompt or PowerShell. The touch command is available in Git Bash.
help me create my project files on Windows
- Press Cmd+Space (macOS) or the Windows key (Windows) to open your search bar.
- Type Visual Studio Code and press Enter to open it.
- In VS Code, click File in the top menu bar.
- Click Open Folder.
- Navigate to your Desktop, select the portfolio folder, then click Open.
You should see your four files listed in the Explorer sidebar on the left.
Don't see your files in VS Code?
- Make sure you opened the portfolio folder itself, not its parent folder (Desktop).
- If the Explorer sidebar is hidden, click View in the top menu and select Explorer to toggle it on.
help me troubleshoot why I cannot see my files in VS Code
Your project repository is ready with all three files in place. Next up, you will build the HTML structure for your portfolio page.
Build the HTML Structure
Your project folder has three empty files ready to fill. In this step, you will write the HTML that gives your portfolio its structure and content.
HTML is the skeleton of every web page. By using semantic elements like header, main, section, and footer, you tell browsers (and search engines) what each part of the page represents.
In this step, get ready to:
- Write the full HTML page with a header, navigation, and multiple content sections.
- Add a navigation bar with anchor links to each section.
- Structure a projects section with a grid of four project cards.
Set up the page head and navigation
Every HTML page starts with a document declaration, a head section for metadata, and a body for visible content. You will also create the navigation bar that lets visitors jump between sections.
- Open index.html in VS Code (it should be empty) and paste the following code:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>My Portfolio</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<header class="header">
<nav class="nav">
<div class="nav-logo">Portfolio</div>
<ul class="nav-links">
<li><a href="#home">Home</a></li>
<li><a href="#about">About</a></li>
<li><a href="#projects">Projects</a></li>
<li><a href="#contact">Contact</a></li>
</ul>
</nav>
</header>
<main>
</main>
<footer class="footer">
<p>© 2026 Alex. Built with HTML, CSS & JS.</p>
</footer>
<script src="script.js"></script>
</body>
</html>
What does this code do?
- The <!DOCTYPE html> line tells the browser this is a modern HTML5 document.
- The head section sets character encoding, viewport scaling for mobile devices, the page title, and links your stylesheet.
- The nav contains four anchor links (#home, #about, #projects, #contact) that will scroll to their matching sections once you add them.
- The <main> element is empty for now. You will fill it with content sections next.
- The <script> tag at the bottom links your JavaScript file so it loads after the HTML is ready.
- Save index.html.
- Open the file in your browser by right-clicking index.html in the VS Code Explorer sidebar and selecting Reveal in Finder (macOS) or Reveal in File Explorer (Windows), then double-clicking the file.
- You should see the word "Portfolio" and four navigation links (Home, About, Projects, Contact) as unstyled text near the top of the page.
Don't see anything in the browser?
- Make sure you saved the file (Cmd+S on macOS or Ctrl+S on Windows).
- Confirm you are opening index.html (not style.css or script.js).
- If the page is blank, check that you pasted the full code starting from <!DOCTYPE html>.
Why isn't my HTML page showing anything in the browser?
Add the hero and about sections
The <main> element is where your page content lives. You will start with a hero section (the big intro visitors see first) and an about section.
- In index.html, find the empty <main> and </main> tags.
- Place your cursor between them and paste the following code:
<section id="home" class="hero">
<div class="hero-content">
<h1>Hi, I'm Alex</h1>
<p class="hero-subtitle">I build things for the web.</p>
<a href="#projects" class="btn">View My Work</a>
</div>
</section>
<section id="about" class="about">
<div class="container">
<h2>About Me</h2>
<p>I'm a web developer passionate about creating clean, responsive, and user-friendly websites. I love turning ideas into reality through code.</p>
</div>
</section>
What does this code do?
- The hero section (id="home") is the first thing visitors see. It contains a greeting, a subtitle, and a call-to-action button that links to the projects section.
- The about section uses a container div to limit the content width for readability once you add CSS later.
- Each section has a unique id that matches the anchor links in the navigation. That is how clicking "About" in the nav will scroll to this section.
- Save index.html and refresh your browser.
- You should now see "Hi, I'm Alex", "I build things for the web.", a "View My Work" link, and the "About Me" heading with its paragraph below the navigation.
Sections not appearing below the nav?
- Make sure you pasted the code between <main> and </main>, not outside the main element.
- Check for missing closing tags. Every <section> must have a matching </section>.
Help me figure out why my hero and about sections are not showing up.
Build the project cards grid and finish the page
The projects section is the most complex part of this page. It uses a grid container with four individual cards, each representing a project.
- In index.html, place your cursor directly after the closing </section> tag of the about section (the one right after About Me).
- Paste the following code:
<section id="projects" class="projects">
<div class="container">
<h2>My Projects</h2>
<div class="projects-grid">
<article class="project-card">
<h3>Weather App</h3>
<p>A responsive weather application that fetches real-time data from a public API.</p>
<a href="#" class="project-link">View Project</a>
</article>
<article class="project-card">
<h3>Task Manager</h3>
<p>A clean task management tool built with vanilla JavaScript and local storage.</p>
<a href="#" class="project-link">View Project</a>
</article>
<article class="project-card">
<h3>Recipe Finder</h3>
<p>Search and save your favorite recipes with this responsive web application.</p>
<a href="#" class="project-link">View Project</a>
</article>
<article class="project-card">
<h3>Portfolio Site</h3>
<p>This very site! Built from scratch with HTML, CSS, and JavaScript.</p>
<a href="#" class="project-link">View Project</a>
</article>
</div>
</div>
</section>
What does this code do?
- The projects-grid div will become a CSS Grid container once you add styling. For now it just wraps the cards.
- Each article.project-card is a self-contained project showcase with a title, description, and link.
- Using <article> instead of plain <div> tells browsers this content is independently meaningful. This helps with accessibility and SEO.
- Save index.html and refresh your browser.
- You should see four project titles (Weather App, Task Manager, Recipe Finder, Portfolio Site) with descriptions listed below the about section.
Only seeing some project cards?
- Count your closing </article> tags. You need exactly four (one per card).
- Make sure the </div> for projects-grid comes after the last card, not before it.
Why are my project cards not displaying correctly?
Now add the final section. The contact section gives visitors a way to reach out.
- Directly after the closing </section> tag of the projects section, paste this code:
<section id="contact" class="contact">
<div class="container">
<h2>Get In Touch</h2>
<p>Interested in working together? Feel free to reach out.</p>
<a href="mailto:hello@example.com" class="btn">Say Hello</a>
</div>
</section>
What does the mailto link do?
The mailto:hello@example.com link opens the visitor's default email app with a new message addressed to that email. You can swap hello@example.com for your own email address later.
- Save index.html and refresh your browser.
- Scroll to the bottom of the page. You should see "Get In Touch" with a "Say Hello" link, followed by the footer text.
Contact section not visible?
- Make sure the contact section is inside <main> (above </main>), not below it.
- Check that the projects section's closing </section> tag appears before the contact section's opening <section> tag.
Help me find where to place my contact section in the HTML.
✔️ Awesome, I've got everything!
Great. Double-check you have saved index.html and all four sections are visible when you scroll the page in your browser.
ⓧ I'd like to double check the full code
Here is the complete index.html file. Make sure yours matches this exactly:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>My Portfolio</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<header class="header">
<nav class="nav">
<div class="nav-logo">Portfolio</div>
<ul class="nav-links">
<li><a href="#home">Home</a></li>
<li><a href="#about">About</a></li>
<li><a href="#projects">Projects</a></li>
<li><a href="#contact">Contact</a></li>
</ul>
</nav>
</header>
<main>
<section id="home" class="hero">
<div class="hero-content">
<h1>Hi, I'm Alex</h1>
<p class="hero-subtitle">I build things for the web.</p>
<a href="#projects" class="btn">View My Work</a>
</div>
</section>
<section id="about" class="about">
<div class="container">
<h2>About Me</h2>
<p>I'm a web developer passionate about creating clean, responsive, and user-friendly websites. I love turning ideas into reality through code.</p>
</div>
</section>
<section id="projects" class="projects">
<div class="container">
<h2>My Projects</h2>
<div class="projects-grid">
<article class="project-card">
<h3>Weather App</h3>
<p>A responsive weather application that fetches real-time data from a public API.</p>
<a href="#" class="project-link">View Project</a>
</article>
<article class="project-card">
<h3>Task Manager</h3>
<p>A clean task management tool built with vanilla JavaScript and local storage.</p>
<a href="#" class="project-link">View Project</a>
</article>
<article class="project-card">
<h3>Recipe Finder</h3>
<p>Search and save your favorite recipes with this responsive web application.</p>
<a href="#" class="project-link">View Project</a>
</article>
<article class="project-card">
<h3>Portfolio Site</h3>
<p>This very site! Built from scratch with HTML, CSS, and JavaScript.</p>
<a href="#" class="project-link">View Project</a>
</article>
</div>
</div>
</section>
<section id="contact" class="contact">
<div class="container">
<h2>Get In Touch</h2>
<p>Interested in working together? Feel free to reach out.</p>
<a href="mailto:hello@example.com" class="btn">Say Hello</a>
</div>
</section>
</main>
<footer class="footer">
<p>© 2026 Alex. Built with HTML, CSS & JS.</p>
</footer>
<script src="script.js"></script>
</body>
</html>
Your HTML structure is complete. Every section of your portfolio has semantic markup and meaningful content. Next up, you will add CSS to transform this plain text into a polished, visually appealing layout.
Style the Layout with CSS
Your HTML structure is solid, but right now the page is just unstyled text and links. CSS transforms that raw structure into a polished, professional design.
In this step, you will set up a design system using CSS custom properties, lay out the navigation with Flexbox, and build a responsive project card grid with CSS Grid.
In this step, get ready to:
- Set up CSS custom properties and style the navigation bar with Flexbox.
- Style the hero section and page layout with proper spacing.
- Build a responsive project card grid with hover effects.
Set up CSS custom properties and navigation styles
CSS custom properties let you define colors, fonts, and sizes in one place. When you want to change your site's color scheme later, you only update the variable instead of hunting through every selector.
- Open style.css in VS Code and add the CSS reset, custom properties, and base styles:
/* CSS Reset */
*,
*::before,
*::after {
margin: 0;
padding: 0;
box-sizing: border-box;
}
/* Custom Properties */
:root {
--color-primary: #2563eb;
--color-primary-dark: #1d4ed8;
--color-text: #1f2937;
--color-text-light: #6b7280;
--color-bg: #ffffff;
--color-bg-alt: #f9fafb;
--color-card-bg: #ffffff;
--color-shadow: rgba(0, 0, 0, 0.1);
--font-main: system-ui, -apple-system, sans-serif;
--max-width: 1100px;
}
/* Base Styles */
html {
scroll-behavior: smooth;
}
body {
font-family: var(--font-main);
color: var(--color-text);
line-height: 1.6;
background-color: var(--color-bg);
}
a {
text-decoration: none;
color: var(--color-primary);
}
What does this code do?
The CSS reset removes default browser margins and padding so every element starts from the same baseline.
The :root block defines custom properties (variables) that you reference throughout the stylesheet with var(--name). This keeps your colors and sizing consistent everywhere.
The base styles apply your font, text color, and background to the entire page. The scroll-behavior: smooth on html enables smooth scrolling when clicking anchor links.
- Save style.css and refresh your browser.
- You should see the default browser spacing disappear. The text now uses a clean system font with dark gray color, and links appear blue without underlines.
Page looks the same as before?
- Make sure the <link rel="stylesheet" href="style.css"> tag in your index.html points to the correct filename. The file must be named exactly style.css in the same folder as index.html.
- Check that you saved the file. Unsaved changes in VS Code show a dot on the file tab.
Why isn't my CSS loading in the browser?
Now you have a design foundation. Next, add the navigation styles that pin the nav bar to the top of the page using Flexbox.
- Below the base styles you just wrote, add the navigation CSS:
/* Navigation */
.header {
position: fixed;
top: 0;
left: 0;
width: 100%;
background-color: var(--color-bg);
box-shadow: 0 2px 4px var(--color-shadow);
z-index: 1000;
}
.nav {
display: flex;
justify-content: space-between;
align-items: center;
max-width: var(--max-width);
margin: 0 auto;
padding: 1rem 2rem;
}
.nav-logo {
font-size: 1.5rem;
font-weight: 700;
color: var(--color-text);
}
.nav-links {
display: flex;
list-style: none;
gap: 2rem;
}
.nav-links a {
color: var(--color-text);
font-weight: 500;
transition: color 0.3s ease;
}
.nav-links a:hover {
color: var(--color-primary);
}
What does this code do?
The .header uses position: fixed so the navigation stays visible at the top of the viewport as you scroll. The z-index: 1000 ensures it sits above all other content.
The .nav uses display: flex with justify-content: space-between to push the logo to the left and the links to the right. Flexbox handles spacing and alignment without any float hacks.
- Save style.css and refresh your browser.
- You should see a white navigation bar fixed to the top of the page with "Portfolio" on the left and the four links (Home, About, Projects, Contact) evenly spaced on the right.
Navigation links stacking vertically?
- Check that .nav-links has display: flex. Without this, the list items stack as block elements.
- Confirm the selector is .nav-links (with a dot for class) and matches class="nav-links" in your HTML.
Why are my flexbox items not lining up horizontally?
Style the hero section and page layout
The hero is the first thing visitors see. It needs to fill the screen and center the greeting text both vertically and horizontally.
- Below your navigation styles, add the hero section and button CSS:
/* Hero Section */
.hero {
display: flex;
align-items: center;
justify-content: center;
min-height: 100vh;
padding: 2rem;
text-align: center;
background-color: var(--color-bg-alt);
}
.hero-content h1 {
font-size: clamp(2.5rem, 5vw + 1rem, 4rem);
margin-bottom: 1rem;
color: var(--color-text);
}
.hero-subtitle {
font-size: clamp(1.1rem, 2vw + 0.5rem, 1.5rem);
color: var(--color-text-light);
margin-bottom: 2rem;
}
/* Button */
.btn {
display: inline-block;
padding: 0.75rem 2rem;
background-color: var(--color-primary);
color: #ffffff;
border-radius: 6px;
font-weight: 600;
transition: background-color 0.3s ease, transform 0.2s ease;
}
.btn:hover {
background-color: var(--color-primary-dark);
transform: translateY(-2px);
}
What does this code do?
The .hero uses Flexbox with align-items: center and justify-content: center to place content in the dead center of the viewport. The min-height: 100vh makes the hero fill the entire screen height.
The clamp() function creates fluid typography that scales smoothly between a minimum and maximum size based on the viewport width. No breakpoints needed for text sizing.
The .btn:hover uses transform: translateY(-2px) to give a subtle lift effect when hovered. The transition property makes the color and movement changes animate smoothly.
- Save style.css and refresh your browser.
- You should see the hero section filling the full screen height with "Hi, I'm Alex" centered in the middle, a lighter gray background, and a blue "View My Work" button below the subtitle.
Hero text hidden behind the navigation?
- The fixed navigation overlaps the page content by design. The hero uses min-height: 100vh and centers with Flexbox, so the text should appear in the middle of the viewport regardless.
- If your heading is cut off at the very top, make sure .hero has padding: 2rem which provides breathing room.
Why is my fixed navbar overlapping my page content?
Now add the shared section styles that give the About, Projects, and Contact sections consistent spacing and a readable content width.
- Below the button styles, add the section layout CSS:
/* Sections */
.about,
.projects,
.contact {
padding: 5rem 2rem;
}
.about {
background-color: var(--color-bg);
}
.projects {
background-color: var(--color-bg-alt);
}
.contact {
background-color: var(--color-bg);
text-align: center;
}
.container {
max-width: var(--max-width);
margin: 0 auto;
}
h2 {
font-size: clamp(1.8rem, 3vw + 0.5rem, 2.5rem);
margin-bottom: 1.5rem;
}
What does this code do?
Each section gets generous vertical padding (5rem) so they feel like distinct pages as you scroll. The alternating background colors (white and light gray) visually separate them.
The .container class caps content width at 1100px and centers it with margin: 0 auto. This keeps text readable on wide screens instead of stretching edge to edge.
- Save style.css and refresh your browser.
- Scroll down past the hero. You should see the About, Projects, and Contact sections stacked with alternating white and light gray backgrounds, each with comfortable padding.
Sections not showing alternating backgrounds?
- The color difference between --color-bg (white) and --color-bg-alt (very light gray) is subtle. Look closely at the Projects section background compared to About.
- Make sure your section class names in CSS (.about, .projects, .contact) match the classes in your HTML exactly.
Help me debug why my CSS section backgrounds aren't showing up.
Build the project card grid with hover effects
CSS Grid handles the project cards layout. The grid automatically adjusts how many columns fit on screen without needing media queries for the cards themselves.
- Below your section styles, add the projects grid and card CSS:
/* Projects Grid */
.projects-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
gap: 2rem;
margin-top: 2rem;
}
.project-card {
background-color: var(--color-card-bg);
padding: 2rem;
border-radius: 12px;
box-shadow: 0 4px 6px var(--color-shadow);
transition: transform 0.3s ease, box-shadow 0.3s ease;
}
.project-card:hover {
transform: translateY(-5px);
box-shadow: 0 10px 25px rgba(0, 0, 0, 0.15);
}
.project-card h3 {
margin-bottom: 0.75rem;
font-size: 1.25rem;
}
.project-card p {
color: var(--color-text-light);
margin-bottom: 1rem;
font-size: 0.95rem;
}
.project-link {
font-weight: 600;
font-size: 0.9rem;
}
What does this code do?
The key line is grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)). This tells the browser to create as many columns as will fit, with each column at least 280px wide. On a wide screen you get 3-4 columns. On a narrow screen, the cards automatically stack into fewer columns.
The .project-card:hover lifts the card upward (translateY(-5px)) and deepens the shadow, creating a "picked up" effect. The transition property animates this smoothly over 0.3 seconds.
- Save style.css and refresh your browser.
- Scroll to the Projects section. You should see four white cards arranged in a grid, each with rounded corners and a subtle shadow. Hover over a card to see it lift upward with an enhanced shadow.
Cards not appearing in a grid?
- Check that your selector is .projects-grid (with the hyphen) matching class="projects-grid" in your HTML.
- If cards stack in a single column, your browser window might be narrower than 280px per card. Try making your browser window wider.
Why is my CSS Grid not creating multiple columns?
Finally, add the contact section paragraph styling and the footer to complete the page.
- Below the project card styles, add the contact and footer CSS:
/* Contact Section */
.contact p {
color: var(--color-text-light);
margin-bottom: 2rem;
font-size: 1.1rem;
}
/* Footer */
.footer {
text-align: center;
padding: 2rem;
background-color: var(--color-text);
color: #ffffff;
font-size: 0.9rem;
}
What does this code do?
The contact paragraph gets lighter text and spacing to separate it from the "Say Hello" button below.
The footer uses --color-text (dark gray) as its background, flipping the color scheme to white text on a dark surface. This visually anchors the bottom of the page.
- Save style.css and refresh your browser.
- Scroll to the very bottom of the page. You should see the contact section centered with a blue "Say Hello" button, followed by a dark footer with white copyright text.
Footer not showing a dark background?
- Confirm your selector is .footer (with the dot) and your HTML has class="footer" on the <footer> element.
- Make sure background-color uses var(--color-text) which resolves to a dark gray. A typo in the variable name produces no color.
Help me figure out why my footer styles aren't applying.
✔️ 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 at this point. Compare it with yours:
/* CSS Reset */
*,
*::before,
*::after {
margin: 0;
padding: 0;
box-sizing: border-box;
}
/* Custom Properties */
:root {
--color-primary: #2563eb;
--color-primary-dark: #1d4ed8;
--color-text: #1f2937;
--color-text-light: #6b7280;
--color-bg: #ffffff;
--color-bg-alt: #f9fafb;
--color-card-bg: #ffffff;
--color-shadow: rgba(0, 0, 0, 0.1);
--font-main: system-ui, -apple-system, sans-serif;
--max-width: 1100px;
}
/* Base Styles */
html {
scroll-behavior: smooth;
}
body {
font-family: var(--font-main);
color: var(--color-text);
line-height: 1.6;
background-color: var(--color-bg);
}
a {
text-decoration: none;
color: var(--color-primary);
}
/* Navigation */
.header {
position: fixed;
top: 0;
left: 0;
width: 100%;
background-color: var(--color-bg);
box-shadow: 0 2px 4px var(--color-shadow);
z-index: 1000;
}
.nav {
display: flex;
justify-content: space-between;
align-items: center;
max-width: var(--max-width);
margin: 0 auto;
padding: 1rem 2rem;
}
.nav-logo {
font-size: 1.5rem;
font-weight: 700;
color: var(--color-text);
}
.nav-links {
display: flex;
list-style: none;
gap: 2rem;
}
.nav-links a {
color: var(--color-text);
font-weight: 500;
transition: color 0.3s ease;
}
.nav-links a:hover {
color: var(--color-primary);
}
/* Hero Section */
.hero {
display: flex;
align-items: center;
justify-content: center;
min-height: 100vh;
padding: 2rem;
text-align: center;
background-color: var(--color-bg-alt);
}
.hero-content h1 {
font-size: clamp(2.5rem, 5vw + 1rem, 4rem);
margin-bottom: 1rem;
color: var(--color-text);
}
.hero-subtitle {
font-size: clamp(1.1rem, 2vw + 0.5rem, 1.5rem);
color: var(--color-text-light);
margin-bottom: 2rem;
}
/* Button */
.btn {
display: inline-block;
padding: 0.75rem 2rem;
background-color: var(--color-primary);
color: #ffffff;
border-radius: 6px;
font-weight: 600;
transition: background-color 0.3s ease, transform 0.2s ease;
}
.btn:hover {
background-color: var(--color-primary-dark);
transform: translateY(-2px);
}
/* Sections */
.about,
.projects,
.contact {
padding: 5rem 2rem;
}
.about {
background-color: var(--color-bg);
}
.projects {
background-color: var(--color-bg-alt);
}
.contact {
background-color: var(--color-bg);
text-align: center;
}
.container {
max-width: var(--max-width);
margin: 0 auto;
}
h2 {
font-size: clamp(1.8rem, 3vw + 0.5rem, 2.5rem);
margin-bottom: 1.5rem;
}
/* Projects Grid */
.projects-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
gap: 2rem;
margin-top: 2rem;
}
.project-card {
background-color: var(--color-card-bg);
padding: 2rem;
border-radius: 12px;
box-shadow: 0 4px 6px var(--color-shadow);
transition: transform 0.3s ease, box-shadow 0.3s ease;
}
.project-card:hover {
transform: translateY(-5px);
box-shadow: 0 10px 25px rgba(0, 0, 0, 0.15);
}
.project-card h3 {
margin-bottom: 0.75rem;
font-size: 1.25rem;
}
.project-card p {
color: var(--color-text-light);
margin-bottom: 1rem;
font-size: 0.95rem;
}
.project-link {
font-weight: 600;
font-size: 0.9rem;
}
/* Contact Section */
.contact p {
color: var(--color-text-light);
margin-bottom: 2rem;
font-size: 1.1rem;
}
/* Footer */
.footer {
text-align: center;
padding: 2rem;
background-color: var(--color-text);
color: #ffffff;
font-size: 0.9rem;
}
Your portfolio now has a complete visual design with a fixed navigation, a full-screen hero, and a responsive project grid. Next up, you will add media queries to fine-tune the layout for mobile screens.
Make It Responsive
Your portfolio looks great on a desktop screen. But most people browse the web on their phones.
In this step, you'll add a media query that adapts your layout for smaller screens. You'll also see how the clamp() values you already wrote handle fluid typography automatically.
In this step, get ready to:
- Add a media query that stacks the navigation and tightens spacing on mobile.
- Confirm your fluid typography already scales with clamp().
- Test the responsive layout using your browser's developer tools.
Add a mobile media query
A media query lets you apply styles only when the browser window is below a certain width. You'll target 768px as the breakpoint where the layout switches to a mobile-friendly stack.
- In VS Code, open style.css.
- Scroll to the very bottom of the file, after the .footer styles.
- Add the responsive media query by pasting this code at the end of the file:
/* Responsive Design */
@media (max-width: 768px) {
.nav {
flex-direction: column;
gap: 1rem;
}
.nav-links {
gap: 1.5rem;
}
.hero {
padding-top: 6rem;
}
.about,
.projects,
.contact {
padding: 3rem 1.5rem;
}
}
What does this code do?
The @media (max-width: 768px) rule says: only apply these styles when the screen is 768 pixels wide or narrower.
- flex-direction: column stacks the logo and navigation links vertically instead of side by side.
- padding-top: 6rem on .hero prevents the hero content from hiding behind the fixed navigation bar on smaller screens.
- The reduced padding on sections keeps content readable without wasting space on narrow devices.
- Save style.css.
- Refresh your browser. If your window is wide, nothing changes yet. You'll test at narrow widths in the final substep.
Styles not applying?
- Make sure the media query is placed after all other styles in style.css. If it appears before the base rules, the base rules override it.
- Check that the closing } braces match. The media query has an outer } and each selector inside also has its own }.
- help me figure out why my responsive media query isn't working
Confirm fluid typography with clamp()
You might notice the heading text already scales smoothly as you resize the window. That is because the clamp() function you added in the previous step handles fluid sizing without needing a media query.
- In style.css, find the .hero-content h1 rule. Notice this line:
font-size: clamp(2.5rem, 5vw + 1rem, 4rem);
What is clamp()?
clamp() takes three values: a minimum size, a preferred size that scales with the viewport, and a maximum size. The browser picks whatever falls within that range.
This means your heading smoothly shrinks on small screens and grows on large screens, all without a breakpoint. Your h2 and .hero-subtitle use the same technique.
No changes needed here. The fluid typography is already working for you.
Test the layout at different viewport widths
Your browser's developer tools let you simulate different screen sizes without needing a phone. This is how professional developers test responsive designs.
- In your browser, open your portfolio page (refresh if it's already open).
- Open developer tools by pressing Cmd+Option+I (macOS) or Ctrl+Shift+I (Windows).
- Click the Toggle device toolbar button (a small phone/tablet icon) in the top-left of the DevTools panel. You can also press Cmd+Shift+M (macOS) or Ctrl+Shift+M (Windows).
- Drag the viewport width handle or type 375 in the width field to simulate a mobile phone screen.
You should see the navigation links stacked vertically below the logo. The hero text is smaller but still readable. The project cards stack into a single column.
- Now widen the viewport past 768 pixels.
The navigation snaps back to a horizontal row. The project cards re-arrange into a multi-column grid.
Why does the grid already adapt?
Your .projects-grid uses repeat(auto-fit, minmax(280px, 1fr)). CSS Grid automatically wraps cards into fewer columns when the container narrows below 280px per card. No media query needed for the grid itself.
Layout looks the same at all sizes?
- Confirm you saved style.css and hard-refreshed the browser (Cmd+Shift+R or Ctrl+Shift+R). Cached styles can hide your changes.
- Check that your HTML has <meta name="viewport" content="width=device-width, initial-scale=1.0"> in the <head>. Without it, mobile browsers ignore media queries.
- help me debug why my responsive layout isn't changing at different screen sizes
✔️ 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 including the new media query at the bottom.
/* CSS Reset */
*,
*::before,
*::after {
margin: 0;
padding: 0;
box-sizing: border-box;
}
/* Custom Properties */
:root {
--color-primary: #2563eb;
--color-primary-dark: #1d4ed8;
--color-text: #1f2937;
--color-text-light: #6b7280;
--color-bg: #ffffff;
--color-bg-alt: #f9fafb;
--color-card-bg: #ffffff;
--color-shadow: rgba(0, 0, 0, 0.1);
--font-main: system-ui, -apple-system, sans-serif;
--max-width: 1100px;
}
/* Base Styles */
html {
scroll-behavior: smooth;
}
body {
font-family: var(--font-main);
color: var(--color-text);
line-height: 1.6;
background-color: var(--color-bg);
}
a {
text-decoration: none;
color: var(--color-primary);
}
/* Navigation */
.header {
position: fixed;
top: 0;
left: 0;
width: 100%;
background-color: var(--color-bg);
box-shadow: 0 2px 4px var(--color-shadow);
z-index: 1000;
}
.nav {
display: flex;
justify-content: space-between;
align-items: center;
max-width: var(--max-width);
margin: 0 auto;
padding: 1rem 2rem;
}
.nav-logo {
font-size: 1.5rem;
font-weight: 700;
color: var(--color-text);
}
.nav-links {
display: flex;
list-style: none;
gap: 2rem;
}
.nav-links a {
color: var(--color-text);
font-weight: 500;
transition: color 0.3s ease;
}
.nav-links a:hover {
color: var(--color-primary);
}
/* Hero Section */
.hero {
display: flex;
align-items: center;
justify-content: center;
min-height: 100vh;
padding: 2rem;
text-align: center;
background-color: var(--color-bg-alt);
}
.hero-content h1 {
font-size: clamp(2.5rem, 5vw + 1rem, 4rem);
margin-bottom: 1rem;
color: var(--color-text);
}
.hero-subtitle {
font-size: clamp(1.1rem, 2vw + 0.5rem, 1.5rem);
color: var(--color-text-light);
margin-bottom: 2rem;
}
/* Button */
.btn {
display: inline-block;
padding: 0.75rem 2rem;
background-color: var(--color-primary);
color: #ffffff;
border-radius: 6px;
font-weight: 600;
transition: background-color 0.3s ease, transform 0.2s ease;
}
.btn:hover {
background-color: var(--color-primary-dark);
transform: translateY(-2px);
}
/* Sections */
.about,
.projects,
.contact {
padding: 5rem 2rem;
}
.about {
background-color: var(--color-bg);
}
.projects {
background-color: var(--color-bg-alt);
}
.contact {
background-color: var(--color-bg);
text-align: center;
}
.container {
max-width: var(--max-width);
margin: 0 auto;
}
h2 {
font-size: clamp(1.8rem, 3vw + 0.5rem, 2.5rem);
margin-bottom: 1.5rem;
}
/* Projects Grid */
.projects-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
gap: 2rem;
margin-top: 2rem;
}
.project-card {
background-color: var(--color-card-bg);
padding: 2rem;
border-radius: 12px;
box-shadow: 0 4px 6px var(--color-shadow);
transition: transform 0.3s ease, box-shadow 0.3s ease;
}
.project-card:hover {
transform: translateY(-5px);
box-shadow: 0 10px 25px rgba(0, 0, 0, 0.15);
}
.project-card h3 {
margin-bottom: 0.75rem;
font-size: 1.25rem;
}
.project-card p {
color: var(--color-text-light);
margin-bottom: 1rem;
font-size: 0.95rem;
}
.project-link {
font-weight: 600;
font-size: 0.9rem;
}
/* Contact Section */
.contact p {
color: var(--color-text-light);
margin-bottom: 2rem;
font-size: 1.1rem;
}
/* Footer */
.footer {
text-align: center;
padding: 2rem;
background-color: var(--color-text);
color: #ffffff;
font-size: 0.9rem;
}
/* Responsive Design */
@media (max-width: 768px) {
.nav {
flex-direction: column;
gap: 1rem;
}
.nav-links {
gap: 1.5rem;
}
.hero {
padding-top: 6rem;
}
.about,
.projects,
.contact {
padding: 3rem 1.5rem;
}
}
Your site now adapts to any screen size. Next up, you'll add smooth scroll behavior with JavaScript and deploy the whole thing live to GitHub Pages.
Add Smooth Scroll and Deploy to GitHub Pages
Your portfolio looks great and adapts to any screen size. Now it's time to make it feel polished with smooth scrolling and put it on the internet for anyone to visit.
In this step, you will add JavaScript that makes your navigation links scroll smoothly instead of jumping. Then you will push your code to GitHub and deploy the site using GitHub Pages.
In this step, get ready to:
- Add smooth scroll behavior to your navigation links.
- Commit and push all files to GitHub.
- Deploy your site live on GitHub Pages.
Add smooth scroll to your navigation
Right now, clicking a navigation link jumps the page instantly to that section. Smooth scrolling makes the page glide to the target section, which feels much more polished.
- In VS Code, open script.js from the Explorer sidebar.
- Add this smooth scroll code by pasting the following into your empty file:
// Smooth scroll for navigation links
document.querySelectorAll('.nav-links a, .btn[href^="#"]').forEach(link => {
link.addEventListener('click', function(e) {
const href = this.getAttribute('href');
if (href.startsWith('#')) {
e.preventDefault();
const target = document.querySelector(href);
if (target) {
target.scrollIntoView({
behavior: 'smooth',
block: 'start'
});
}
}
});
});
What does this code do?
This script selects every navigation link and any button that links to a section (like the "View My Work" button in the hero). It attaches a click listener to each one.
- querySelectorAll('.nav-links a, .btn[href^="#"]') finds all nav links and any .btn element whose href starts with #.
- e.preventDefault() stops the browser from jumping to the section instantly.
- scrollIntoView({ behavior: 'smooth', block: 'start' }) tells the browser to scroll to the target section with a smooth animation.
- Save script.js.
- Refresh your browser and click any navigation link (like About or Projects).
- The page should glide smoothly to that section instead of jumping.
- Also click the View My Work button in the hero section to confirm it scrolls down to the projects grid.
Page still jumps instead of scrolling?
- Make sure you saved script.js and refreshed the browser.
- Check that your index.html has <script src="script.js"></script> just before the closing </body> tag.
- Open your browser's developer console (Cmd+Option+J on macOS or Ctrl+Shift+J on Windows) and look for any red error messages.
Help me debug why my smooth scroll isn't working in script.js
✔️ Awesome, I've got everything!
Great. Make sure you have saved script.js before moving on.
ⓧ I'd like to double check the full code
// Smooth scroll for navigation links
document.querySelectorAll('.nav-links a, .btn[href^="#"]').forEach(link => {
link.addEventListener('click', function(e) {
const href = this.getAttribute('href');
if (href.startsWith('#')) {
e.preventDefault();
const target = document.querySelector(href);
if (target) {
target.scrollIntoView({
behavior: 'smooth',
block: 'start'
});
}
}
});
});
Commit and push your code to GitHub
Your portfolio is complete locally. Now you need to send all your files to GitHub so you can deploy them.
- Open your terminal in VS Code by pressing Ctrl+` (backtick key, usually above Tab).
- Make sure you are inside your portfolio folder. If not, navigate to it by running:
cd ~/Desktop/portfolio
- Stage all your files, create a commit, and push to GitHub by running these commands:
git add .
git commit -m "Add portfolio site files"
git push origin main
What do these commands do?
- git add . stages every file in the folder for the next commit.
- git commit -m "Add portfolio site files" saves a snapshot of your staged files with a descriptive message.
- git push origin main uploads your commit to the main branch on GitHub.
- After pushing, open your portfolio repository on GitHub in your browser.
- You should see index.html, style.css, and script.js listed in the file list.
Push failed or asked for credentials?
- If Git asks for your username and password, you may need to set up a personal access token or SSH key. GitHub no longer accepts passwords for command-line access.
- If you see "rejected" in the output, try running git pull origin main first to sync with the remote, then push again.
Help me fix my git push to GitHub
Enable GitHub Pages and verify your live site
GitHub Pages turns your repository into a live website. You just need to tell GitHub which branch and folder to serve files from.
- In your portfolio repository on GitHub, click Settings in the top navigation bar.
- In the left sidebar, under Code and automation, click Pages.
- Under Build and deployment, find the Source section.
- Select Deploy from a branch.
- In the branch dropdown menu, select main.
- In the folder dropdown, select / (root).
- Click Save.
Why main branch and root folder?
GitHub Pages looks for an index.html file in the location you specify. Since your index.html is at the top level of your repository (not inside a subfolder), the root folder is the correct choice.
It can take up to 10 minutes for your site to go live. Your site will be available at:
https://[[GITHUB_USERNAME="your-github-username"]].github.io/portfolio
- Wait a few minutes, then visit your URL in a new browser tab.
- You should see your full portfolio site with all four sections, responsive layout, and working smooth scroll navigation.
Seeing a 404 page?
- Your site may still be building. Wait a few more minutes and refresh.
- Go back to Settings > Pages and confirm that main branch and root (/) folder are selected.
- Make sure your repository is public. GitHub Pages for free accounts requires a public repository.
Help me get my GitHub Pages site working
Secret mission
Filterable Project Gallery
Your project grid shows everything at once. What if visitors want to browse only a specific category? Add interactive filter buttons that show and hide project cards with smooth animations.
Clean Up Your Resources
Clean Up Your Resources
This project runs entirely locally and deploys to GitHub Pages for free, so there are no ongoing costs. Decide whether to keep your resources running, pause them to come back later, or delete them entirely.
Resources you used:
- Local portfolio folder with index.html, style.css, and script.js.
- GitHub repository portfolio (includes your GitHub Pages deployment).
Keep everything running
No action needed. Choose this if you're still actively building or want to keep your portfolio live at its public URL.
- Your GitHub Pages site stays live at https://your-username.github.io/portfolio.
- Your local files remain in the portfolio folder on your Desktop.
Pause - I'll come back to this later
Your GitHub Pages site costs nothing to keep live, so there is nothing to pause on the remote side. Locally, you can close your code editor and terminal.
- Close any open terminal windows.
- Close your code editor.
- Your site stays live on GitHub Pages until you delete the repository.
Delete - I don't want to use this again
Remove all project resources from your machine and GitHub.
- Delete the local project folder by running this command in your terminal:
macOS
rm -rf ~/Desktop/portfolio
Windows
Remove-Item -Recurse -Force ~\Desktop\portfolio
- Delete the GitHub repository to take your site offline:
- Go to your portfolio repository on GitHub.
- Click Settings (the tab near the top of the repository page).
- Scroll down to the Danger Zone section at the bottom.
- Click Delete this repository.
- Type the repository name to confirm, then click I understand the consequences, delete this repository.
Nice Work!
Nice Work!
You did it. Your personal portfolio site is live on the internet for anyone to visit.
You've learned how to:
- Built a complete single-page portfolio with semantic HTML structure, navigation, and project cards.
- Styled responsive layouts using CSS custom properties, Flexbox, and CSS Grid with fluid typography via clamp() and a mobile-friendly media query.
- Deployed a static site to GitHub Pages with a shareable public URL that anyone can visit.
- Added a filterable project gallery with category buttons and smooth CSS animations.
Ready to quiz yourself?