What You'll Build
A complete one-page portfolio website with a fixed navigation bar, hero section, about section, project grid, and contact section. It's fully responsive — the nav collapses to a mobile menu on small screens.
- Fixed navigation with smooth scroll to sections
- Hero section with your name and tagline
- About section with skills list
- Projects grid — responsive, cards with hover effects
- Contact section with email link and social icons
- Mobile hamburger menu that opens/closes
Hi, I'm Sarah.
I build things for the web.
Frontend developer focused on clean UI, smooth interactions, and honest code. Currently learning React and TypeScript.
A little background
I've been coding for 3 years, starting with Python scripts and moving into frontend development. I love building tools that solve small, real problems.
Outside of code, I write tutorials, contribute to open source, and drink too much coffee.
- HTML
- CSS
- JavaScript
- React
- Python
- Git
- Figma
Currently: Building a collection of free browser tools.
Learning: TypeScript, Testing, Node.js
Open to: Junior frontend roles, freelance projects
Recent projects
A few things I've built recently. All open source, all free to use.
BMI Calculator
A live BMI calculator with healthy weight range and category display.
JavaScriptTimestamp Converter
Convert between Unix timestamps and human-readable dates.
JavaScriptBase64 Encoder
Encode and decode Base64 text and files in the browser.
JavaScriptWord Counter
Live word stats with reading time and keyword density.
JavaScriptLet's work together
Got a project, an idea, or just want to say hi? Drop me a line.
hello@example.comWhat You'll Learn
- Multi-section layout: hero, about, projects, contact — all on one page
- Fixed navigation: stays visible while scrolling
- Smooth scroll: pure CSS with
scroll-behavior: smooth - CSS Grid with auto-fit: the responsive projects grid reflows without media queries
- Mobile navigation: hamburger toggle with CSS transform
- CSS custom properties: consistent colours and spacing
Why this project matters: A portfolio is the first real project every developer should build. It's your business card, your proof of work, and your playground for testing new design ideas. This is also the project recruiters ask to see.
Build the HTML Structure
Create index.html. Notice the pattern — five sections, each with a <div class="container"> inside for consistent width.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Sarah - Frontend Developer</title>
</head>
<body>
<nav>
<div class="nav-inner">
<div class="logo">SARAH.DEV</div>
<button class="hamburger" id="hamburger">
<span></span><span></span><span></span>
</button>
<ul class="nav-links" id="navLinks">
<li><a href="#about">About</a></li>
<li><a href="#projects">Projects</a></li>
<li><a href="#contact">Contact</a></li>
</ul>
</div>
</nav>
<section class="hero">
<div class="container">
<h1>Hi, I'm Sarah.<br>I build <span>things for the web.</span></h1>
<p>Frontend developer focused on clean UI, smooth interactions, and honest code.</p>
<div class="hero-btns">
<a href="#projects" class="btn primary">See my work</a>
<a href="#contact" class="btn ghost">Get in touch</a>
</div>
</div>
</section>
<section id="about">
<div class="container">
<div class="section-tag">About me</div>
<h2 class="section-title">A little background</h2>
<div class="about-grid">
<div>
<p>I've been coding for 3 years...</p>
<ul class="skill-list">
<li>HTML</li><li>CSS</li><li>JavaScript</li>
</ul>
</div>
<div>
<p><strong>Currently:</strong> Building free tools.</p>
</div>
</div>
</div>
</section>
<section id="projects">
<div class="container">
<div class="section-tag">Selected work</div>
<h2 class="section-title">Recent projects</h2>
<div class="projects-grid">
<a href="#" class="project-card">
<div class="icon">⚖️</div>
<h3>BMI Calculator</h3>
<p>A live BMI calculator...</p>
<span class="tag">JavaScript</span>
</a>
<!-- More cards -->
</div>
</div>
</section>
<section id="contact" class="contact">
<div class="container">
<div class="section-tag">Contact</div>
<h2 class="section-title">Let's work together</h2>
<a href="mailto:hello@example.com" class="email">hello@example.com</a>
<div class="socials">
<a href="#">GitHub</a>
<a href="#">LinkedIn</a>
</div>
</div>
</section>
<footer>© 2026 Sarah</footer>
<script src="script.js"></script>
</body>
</html>
Why is the hamburger before the nav links? The button is always visible on mobile (top-right corner). The nav links become a hidden panel below. This is the classic mobile-menu pattern used by almost every modern site.
Style It with CSS
This is the largest CSS file in the series. The key sections: reset, nav, hero, sections, projects grid, contact, mobile.
* { box-sizing: border-box; margin: 0; padding: 0; }
/* Smooth scroll for all in-page links */
html { scroll-behavior: smooth; }
body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
color: #0f172a;
line-height: 1.6;
}
/* ============================================================
NAVIGATION
============================================================ */
nav {
position: fixed;
top: 0; left: 0; right: 0;
background: rgba(255,255,255,0.95);
backdrop-filter: blur(10px);
border-bottom: 1px solid #e2e8f0;
z-index: 100;
padding: 14px 0;
}
.nav-inner {
max-width: 1000px;
margin: 0 auto;
display: flex;
justify-content: space-between;
align-items: center;
padding: 0 20px;
}
.logo {
font-weight: 900;
font-size: 1.1rem;
letter-spacing: -0.02em;
}
.nav-links {
display: flex;
gap: 26px;
list-style: none;
}
.nav-links a {
color: #475569;
text-decoration: none;
font-weight: 600;
font-size: 0.88rem;
transition: 0.2s;
}
.nav-links a:hover { color: #3b82f6; }
.hamburger {
display: none;
background: transparent;
border: none;
cursor: pointer;
padding: 6px;
}
.hamburger span {
display: block;
width: 22px;
height: 2px;
background: #0f172a;
margin: 4px 0;
transition: 0.3s;
}
/* ============================================================
SECTIONS
============================================================ */
section { padding: 90px 20px; }
.container { max-width: 1000px; margin: 0 auto; }
.section-tag {
color: #3b82f6;
font-weight: 700;
font-size: 0.72rem;
text-transform: uppercase;
letter-spacing: 0.12em;
margin-bottom: 8px;
}
h2.section-title {
font-size: 1.8rem;
font-weight: 900;
letter-spacing: -0.03em;
margin-bottom: 14px;
}
.section-sub {
color: #64748b;
font-size: 0.95rem;
margin-bottom: 30px;
max-width: 600px;
}
/* ============================================================
HERO
============================================================ */
.hero {
min-height: 100vh;
display: flex;
align-items: center;
padding-top: 70px;
background: linear-gradient(135deg, #f8fafc 0%, #eff6ff 100%);
}
.hero h1 {
font-size: clamp(2.2rem, 6vw, 3.6rem);
font-weight: 900;
letter-spacing: -0.04em;
line-height: 1.05;
margin-bottom: 16px;
}
.hero h1 span { color: #3b82f6; }
.hero p {
font-size: 1.1rem;
color: #475569;
max-width: 560px;
margin-bottom: 24px;
}
.hero-btns { display: flex; gap: 10px; flex-wrap: wrap; }
.btn {
display: inline-block;
padding: 12px 24px;
border-radius: 10px;
text-decoration: none;
font-weight: 700;
font-size: 0.9rem;
transition: 0.2s;
}
.btn.primary { background: #3b82f6; color: #fff; }
.btn.primary:hover {
background: #2563eb;
transform: translateY(-2px);
}
.btn.ghost {
background: #fff;
color: #0f172a;
border: 2px solid #e2e8f0;
}
.btn.ghost:hover {
border-color: #3b82f6;
color: #3b82f6;
}
/* ============================================================
ABOUT
============================================================ */
.about-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 40px;
align-items: center;
}
.skill-list {
display: flex;
flex-wrap: wrap;
gap: 8px;
margin-top: 16px;
list-style: none;
}
.skill-list li {
background: #eff6ff;
color: #1d4ed8;
padding: 5px 12px;
border-radius: 999px;
font-size: 0.78rem;
font-weight: 600;
}
/* ============================================================
PROJECTS
============================================================ */
.projects-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(260px, 1fr));
gap: 18px;
}
.project-card {
background: #fff;
border: 1px solid #e2e8f0;
border-radius: 14px;
padding: 22px;
text-decoration: none;
color: inherit;
transition: 0.25s;
display: block;
}
.project-card:hover {
transform: translateY(-4px);
box-shadow: 0 16px 40px rgba(59,130,246,0.15);
border-color: #3b82f6;
}
.project-card .icon { font-size: 1.8rem; margin-bottom: 12px; }
.project-card h3 { font-size: 1.05rem; margin-bottom: 6px; }
.project-card p { font-size: 0.85rem; color: #64748b; }
.project-card .tag {
display: inline-block;
background: #f1f5f9;
color: #475569;
font-size: 0.7rem;
font-weight: 600;
padding: 3px 8px;
border-radius: 6px;
margin-top: 12px;
}
/* ============================================================
CONTACT
============================================================ */
.contact {
background: #0f172a;
color: #cbd5e1;
text-align: center;
}
.contact h2 { color: #fff; }
.contact .section-sub {
color: #94a3b8;
margin-left: auto;
margin-right: auto;
}
.contact .email {
display: inline-block;
color: #60a5fa;
font-weight: 700;
font-size: 1.15rem;
text-decoration: none;
margin: 8px 0 20px;
}
.contact .email:hover { text-decoration: underline; }
.socials {
display: flex;
justify-content: center;
gap: 14px;
margin-top: 8px;
}
.socials a {
color: #94a3b8;
text-decoration: none;
font-size: 0.85rem;
transition: 0.2s;
padding: 8px 14px;
border: 1px solid #334155;
border-radius: 999px;
}
.socials a:hover {
color: #fff;
border-color: #3b82f6;
background: #1e293b;
}
footer {
background: #0f172a;
color: #64748b;
text-align: center;
font-size: 0.78rem;
padding: 20px;
border-top: 1px solid #1e293b;
}
/* ============================================================
MOBILE
============================================================ */
@media (max-width: 640px) {
.hamburger { display: block; }
.nav-links {
position: fixed;
top: 58px; left: 0; right: 0;
background: #fff;
flex-direction: column;
padding: 20px;
gap: 16px;
border-bottom: 1px solid #e2e8f0;
transform: translateY(-150%);
transition: 0.3s;
}
.nav-links.open { transform: translateY(0); }
.about-grid { grid-template-columns: 1fr; gap: 20px; }
section { padding: 70px 20px; }
}
Write the JavaScript — Mobile Menu
The only JavaScript this project needs — the mobile menu toggle. This is deliberately small, because most of the work is done in CSS.
// ============================================================
// MOBILE MENU TOGGLE
// ============================================================
var hamburger = document.getElementById("hamburger");
var navLinks = document.getElementById("navLinks");
hamburger.addEventListener("click", function() {
navLinks.classList.toggle("open");
});
// Close the menu when any nav link is clicked.
// This is important for UX — otherwise the menu stays open
// after jumping to a section.
navLinks.querySelectorAll("a").forEach(function(link) {
link.addEventListener("click", function() {
navLinks.classList.remove("open");
});
});
Save and open index.html. Resize the window — the nav collapses to a hamburger below 640px. Click the hamburger — the menu slides down. Click a link — it smooth-scrolls and closes the menu.
Understand the Big Ideas
1. Fixed navigation with scroll-behavior
The nav is position: fixed — it stays at the top of the viewport even when scrolling. The backdrop-filter: blur(10px) creates that modern frosted-glass look. scroll-behavior: smooth on html makes all in-page anchor links animate instead of jumping.
2. The auto-fit grid — no media queries needed
This single line handles all responsive layout for the projects grid:
grid-template-columns: repeat(auto-fill, minmax(260px, 1fr));
Translation: "Fit as many columns as you can, each at least 260px wide, filling the remaining space evenly." On desktop, you get 3 columns. On tablet, 2. On mobile, 1. No media queries — the browser figures it out.
3. The mobile menu transform trick
Instead of display: none / display: block, we slide the menu with CSS:
/* Hidden above the viewport */
.nav-links { transform: translateY(-150%); }
/* Slide into view */
.nav-links.open { transform: translateY(0); }
This is smoother than displaying/hiding. The transition: 0.3s animates the slide. Users see a smooth motion instead of a jarring appearance.
4. clamp() for fluid typography
font-size: clamp(2.2rem, 6vw, 3.6rem) — this scales the font size based on viewport width, keeping it between 2.2rem and 3.6rem. No media query needed. On mobile it's small; on desktop it's large; the transition is smooth.
Why this project is important: Every other project in this series produces a small self-contained tool. This one produces a full website. It's your business card. Recruiters and clients will look at this first.
Practice Challenges
🟢 Beginner
- Change the colours — pick your own accent colour, update all instances of
#3b82f6 - Add a dark mode toggle using the
prefers-color-schememedia query - Replace the placeholder with your real name, bio, and projects
🟡 Intermediate
- Add a typing animation to the hero heading
- Add scroll-triggered fade-ins using
IntersectionObserver - Add a contact form that opens the user's email client with a pre-filled message
🔴 Advanced
- Add light/dark theme toggle with localStorage persistence
- Add a blog section — grid of post previews
- Add CSS animations for the project cards on load
Full Source Code
Common Mistakes & Fixes
❌ "Hero section is cut off at the top"
The fixed nav overlaps the hero. Add padding-top: 70px to the hero (or use scroll-padding-top on html) — we already did this.
❌ "Smooth scroll doesn't work"
You're missing html { scroll-behavior: smooth; }. Add it to the top of your CSS. Some browsers don't respect it on body — it must be on html.
❌ "Hamburger menu doesn't appear on mobile"
The .hamburger { display: block; } rule must be inside the @media block. If it's outside, it shows on desktop too. Check the media query is max-width: 640px.
❌ "Menu stays open after clicking a link"
You need the close-on-link-click code. It's in step 3 — the navLinks.querySelectorAll("a") loop.
❌ "Sections overlap or have weird gaps"
Check that each section has padding: 90px 20px and each .container has max-width: 1000px; margin: 0 auto;. The container width is what keeps text readable on wide screens.