Build a stunning portfolio website template using HTML, CSS, and JavaScript. Simple guide for beginners to design a responsive personal website.
Table of Contents
Having a personal portfolio website is very important if you want to showcase your skills, projects, or resume online. Instead of using ready-made templates, you can create your own portfolio website template using HTML, CSS, and JavaScript. This not only improves your coding skills but also gives your website a unique and professional look.
In this guide, we will walk you through the step-by-step process of building a simple yet modern portfolio website template. Even if you are a beginner, you can follow along and create your first portfolio.
Prerequisites
Before we start, make sure you have the following:
- Basic knowledge of HTML, CSS, and JavaScript
- A code editor like VS Code
- A web browser (Google Chrome, Firefox, Edge)
- Basic understanding of how to create and save files
Source Code
Step 1 (HTML Code):
To get started, we first need to create a basic HTML file. In this file, we will include the main structure for our portfolio website template. Let’s break down the HTML code section by section:
Head Section
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
- Defines UTF-8 encoding and makes the site responsive.
<title>Ethan Cole - Developer Portfolio</title>
<link rel="icon" href="..." type="image/png">
- Title of the website + Favicon (browser tab icon).
<script src="https://cdn.tailwindcss.com"></script>
<link href="https://fonts.googleapis.com/css2?family=Fira+Code&display=swap" rel="stylesheet">
- Tailwind CSS (utility-first CSS framework).
- Fira Code font for coding/dev style vibe.
<link rel="stylesheet" href="...highlight.js...github-dark.min.css">
<script src="...highlight.min.js"></script>
- Adds highlight.js (for code syntax highlighting in modals).
<link rel="stylesheet" href="styles.css">
- Custom styles for extra design.
Body Section
1. Custom Cursor
<div id="cursor-dot"></div>
- A small div that will act as a custom cursor (styled & animated with CSS/JS).
2. Header / Navigation
<header class="fixed top-0 ...">
<nav>...</nav>
</header>
- Fixed navigation bar at top.
- Shows site logo "
<EC />". - Links: /about, /portfolio, /experience, /contact.
- Responsive:
- Desktop → inline menu.
- Mobile → hamburger menu (☰) with dropdown.
3. Hero Section
<section id="hero" class="h-screen relative">
<canvas id="hero-canvas"></canvas>
- Full-screen hero section with an animated canvas background.
Inside it:
- Fake Terminal Card → Looks like a coding terminal with colored buttons.
<p>$ Hello, I am <span class="text-accent">Ethan Cole</span></p>
<p>$ I am a <span id="typewriter"></span><span class="typewriter-cursor">|</span></p>
- Displays a typing effect ("I am a ...") using JavaScript.
4. About Section
<section id="about">
- Shows profile photo (circular with glowing effect).
- Fake terminal window design with cat
./bio.txt→ prints developer bio. - Skills List → progress bars (with animation using data-width).
5. Portfolio Section
<section id="portfolio">
- Displays the developer’s projects.
- Filter buttons: All, Frontend, Backend, Full Stack.
- Grid layout (#portfolio-grid) where JS dynamically injects project cards.
6. Experience Section
<section id="experience">
- Timeline or list of experience items (injected via JS).
7. Contact Section
<section id="contact">
<form>
<input type="text" name="name">
<input type="email" name="email">
<textarea name="message"></textarea>
<button>SEND MESSAGE</button>
</form>
- Terminal-styled contact form where users can send a message.
8. Project Modal
<div id="project-modal-overlay">
<div id="project-modal">
<button id="modal-close-btn">×</button>
<div class="modal-body"></div>
</div>
</div>
- A pop-up window for project details (opens when clicking a portfolio item).
- Tabs for switching between Description.md, Features.js, and Snippet.html.
9. Footer
<footer>
<p>© 2025 Ethan Cole. Designed & Built with <3 by CodewithFaraz</p>
</footer>
- Simple footer with credits.
10. Scripts
<script src="script.js"></script>
Custom JavaScript for:
- Cursor effect
- Canvas animation
- Typewriter effect
- Portfolio filtering
- Modal functionality
- Skill bar animations
Step 2 (CSS Code):
Once the basic HTML structure of the portfolio website template is in place, the next step is to add styling to the website using CSS. Let’s break down the CSS code section by section:
1. Theme Variables
:root {
--background: #0d1117;
--primary: #c9d1d9;
--secondary: #8b949e;
--accent: #58a6ff;
--green: #39d353;
--border: #30363d;
}
- Defines CSS custom properties (variables).
- Easy to reuse colors throughout the site.
- Dark GitHub-like theme (#0d1117 background, blue #58a6ff accent, green #39d353).
2. Global Styles
body {
font-family: 'Fira Code', monospace;
background-color: var(--background);
color: var(--primary);
cursor: none; /* hides default cursor */
}
- Monospace coding font (Fira Code).
- Dark background + light text.
- Removes default cursor (replaced by custom one).
3. Utility Classes
.bg-accent { background-color: var(--accent); }
.bg-green { background-color: var(--green); }
- Quick reusable background color helpers.
4. Custom Cursor
#cursor-dot {
width: 8px; height: 8px;
background-color: var(--accent);
border-radius: 50%;
position: fixed;
transform: translate(-50%, -50%);
pointer-events: none;
transition: width 0.2s, height 0.2s, background-color 0.2s;
}
#cursor-dot.hovered {
width: 32px; height: 32px;
background-color: rgba(88,166,255,0.2);
}
- Small blue dot cursor.
- Expands into a soft circle when hovering over links/buttons.
5. Glow Effect
.glow {
box-shadow: 0 0 5px var(--accent), 0 0 10px var(--accent), 0 0 15px var(--accent);
}
- Makes elements glow with accent-colored shadows.
6. Hero Section
#hero-canvas {
position: absolute; top: 0; left: 0;
width: 100%; height: 100%;
z-index: 0;
}
.hero-content {
z-index: 1;
background-color: rgba(13,17,23,0.7);
backdrop-filter: blur(5px);
border: 1px solid var(--border);
}
- Canvas used as an animated background.
- Hero content sits above with a blurred glass effect (like frosted glass).
7. Typewriter Cursor
.typewriter-cursor {
display: inline-block;
width: 10px; height: 1.5em;
background-color: var(--accent);
animation: blink 0.7s infinite;
}
@keyframes blink {
0%,100% { opacity: 1; }
50% { opacity: 0; }
}
- Blue blinking cursor bar for typing animation effect.
8. Skill Bars
.skill-bar-inner {
transition: width 1.5s cubic-bezier(0.25,1,0.5,1);
}
- Smoothly animates progress bars (e.g., "JavaScript 90%").
9. Project Cards
.project-card {
background-color: #161b22;
border: 1px solid var(--border);
transition: transform 0.3s, box-shadow 0.3s;
cursor: none;
}
.project-card:hover {
transform: translateY(-10px);
box-shadow: 0 0 15px var(--accent);
}
- Dark project cards.
- Lift up + glowing shadow when hovered.
10. Portfolio Filter Buttons
.filter-btn {
border: 1px solid var(--border);
color: var(--secondary);
transition: background-color 0.3s, color 0.3s;
}
.filter-btn:hover { background-color: var(--border); color: var(--primary); }
.filter-btn.active { background-color: var(--accent); color: var(--background); }
- Buttons to filter projects by category.
- Highlighted when active.
11. Project Modal
#project-modal-overlay {
position: fixed; top:0; left:0; width:100%; height:100%;
background-color: rgba(0,0,0,0.8);
display: flex; justify-content:center; align-items:center;
opacity: 0; visibility: hidden;
transition: opacity 0.3s, visibility 0.3s;
}
#project-modal-overlay.active {
opacity: 1; visibility: visible;
}
#project-modal {
background-color: #161b22;
border: 1px solid var(--border);
border-radius: 8px;
max-width: 800px;
transform: scale(0.9);
transition: transform 0.3s;
}
#project-modal-overlay.active #project-modal {
transform: scale(1);
}
- Fullscreen dark overlay with pop-up modal.
- Opens with fade-in + zoom animation.
12. Modal Tabs
.modal-tab {
cursor: none;
border-bottom: 2px solid transparent;
transition: border-color 0.2s, color 0.2s;
}
.modal-tab.active {
color: var(--accent);
border-bottom-color: var(--accent);
}
- Tabs (Description.md, Features.js, Snippet.html).
- Highlight active tab with blue underline.
13. Timeline (Experience Section)
.timeline-item {
position: relative;
padding-left: 2rem;
}
.timeline-item:before {
content: ''; position: absolute; left:0; width:1px; height:100%;
background-color: var(--border);
}
.timeline-dot {
width: 15px; height: 15px;
border-radius: 50%; border: 2px solid var(--accent);
}
.timeline-item:hover .timeline-dot {
box-shadow: 0 0 10px var(--accent);
}
- Vertical timeline with dots and connecting line.
- Dot glows on hover.
14. Forms
.form-input {
background-color: #010409;
border: 1px solid var(--border);
caret-color: var(--accent);
}
.form-input:focus {
border-color: var(--accent);
box-shadow: 0 0 5px var(--accent);
}
.submit-btn {
background-color: var(--accent);
transition: background-color 0.3s, box-shadow 0.3s;
}
.submit-btn:hover {
background-color: #79c0ff;
box-shadow: 0 0 10px #79c0ff;
}
- Inputs styled like terminal.
- Blue caret + glowing border on focus.
- Submit button glows on hover.
15. Reveal Animation
.reveal {
opacity: 0;
transform: translateY(30px);
transition: opacity 0.8s, transform 0.8s;
}
.reveal.visible {
opacity: 1;
transform: translateY(0);
}
- Scroll reveal effect: sections slide up + fade in when they become visible.
:root {
--background: #0d1117;
--primary: #c9d1d9;
--secondary: #8b949e;
--accent: #58a6ff;
--green: #39d353;
--border: #30363d;
}
body {
font-family: 'Fira Code', monospace;
background-color: var(--background);
color: var(--primary);
cursor: none;
/* Hide default cursor */
}
.bg-accent {
background-color: var(--accent);
}
.bg-green {
background-color: var(--green);
}
/* Custom Cursor */
#cursor-dot {
width: 8px;
height: 8px;
background-color: var(--accent);
border-radius: 50%;
position: fixed;
top: 0;
left: 0;
transform: translate(-50%, -50%);
pointer-events: none;
z-index: 9999;
transition: width 0.2s, height 0.2s, background-color 0.2s;
}
#cursor-dot.hovered {
width: 32px;
height: 32px;
background-color: rgba(88, 166, 255, 0.2);
}
.glow {
box-shadow: 0 0 5px var(--accent), 0 0 10px var(--accent),
0 0 15px var(--accent);
}
#hero-canvas {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: 0;
}
.hero-content {
z-index: 1;
position: relative;
background-color: rgba(13, 17, 23, 0.7);
backdrop-filter: blur(5px);
-webkit-backdrop-filter: blur(5px);
padding: 2rem 3rem;
border-radius: 0.5rem;
border: 1px solid var(--border);
}
.glitch {
position: relative;
}
.typewriter-cursor {
display: inline-block;
width: 10px;
height: 1.5em;
background-color: var(--accent);
animation: blink 0.7s infinite;
margin-left: 5px;
}
@keyframes blink {
0%,
100% {
opacity: 1;
}
50% {
opacity: 0;
}
}
.skill-bar-inner {
transition: width 1.5s cubic-bezier(0.25, 1, 0.5, 1);
}
.project-card {
background-color: #161b22;
border: 1px solid var(--border);
transition: transform 0.3s ease, box-shadow 0.3s ease;
cursor: none;
}
.project-card:hover {
transform: translateY(-10px);
box-shadow: 0 0 15px var(--accent);
}
.filter-btn {
background-color: transparent;
border: 1px solid var(--border);
color: var(--secondary);
padding: 8px 16px;
border-radius: 6px;
transition: background-color 0.3s, color 0.3s, border-color 0.3s;
}
.filter-btn:hover {
background-color: var(--border);
color: var(--primary);
}
.filter-btn.active {
background-color: var(--accent);
color: var(--background);
border-color: var(--accent);
}
#project-modal-overlay {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.8);
z-index: 1000;
display: flex;
justify-content: center;
align-items: center;
opacity: 0;
visibility: hidden;
transition: opacity 0.3s, visibility 0.3s;
}
#project-modal-overlay.active {
opacity: 1;
visibility: visible;
}
#project-modal {
background-color: #161b22;
border: 1px solid var(--border);
border-radius: 8px;
width: 90%;
max-width: 800px;
max-height: 90vh;
display: flex;
flex-direction: column;
transform: scale(0.9);
transition: transform 0.3s;
}
#project-modal-overlay.active #project-modal {
transform: scale(1);
}
.modal-header {
padding: 0.75rem 1rem;
border-bottom: 1px solid var(--border);
display: flex;
justify-content: space-between;
align-items: center;
}
.modal-body {
padding: 1.5rem;
overflow-y: auto;
}
.modal-tab {
padding: 0.5rem 1rem;
cursor: none;
border-bottom: 2px solid transparent;
transition: border-color 0.2s, color 0.2s;
}
.modal-tab.active {
color: var(--accent);
border-bottom-color: var(--accent);
}
.timeline-item {
position: relative;
padding-bottom: 2rem;
padding-left: 2rem;
}
.timeline-item:before {
content: '';
position: absolute;
left: 0;
top: 5px;
width: 1px;
height: 100%;
background-color: var(--border);
}
.timeline-dot {
position: absolute;
left: -7px;
top: 5px;
width: 15px;
height: 15px;
border-radius: 50%;
background-color: var(--background);
border: 2px solid var(--accent);
transition: box-shadow 0.3s ease;
}
.timeline-item:hover .timeline-dot {
box-shadow: 0 0 10px var(--accent);
}
.form-input {
background-color: #010409;
border: 1px solid var(--border);
caret-color: var(--accent);
}
.form-input:focus {
outline: none;
border-color: var(--accent);
box-shadow: 0 0 5px var(--accent);
}
.submit-btn {
background-color: var(--accent);
transition: background-color 0.3s ease, box-shadow 0.3s ease;
}
.submit-btn:hover {
background-color: #79c0ff;
box-shadow: 0 0 10px #79c0ff;
}
.blinking-cursor {
animation: blink 1s step-end infinite;
}
.reveal {
opacity: 0;
transform: translateY(30px);
transition: opacity 0.8s, transform 0.8s;
}
.reveal.visible {
opacity: 1;
transform: translateY(0);
} Step 3 (JavaScript Code):
Finally, we need to create a function in JavaScript. Let me break it down section by section:
1. Custom Cursor
const cursorDot = document.getElementById('cursor-dot');
window.addEventListener('mousemove', (e) => {
cursorDot.style.left = e.clientX + 'px';
cursorDot.style.top = e.clientY + 'px';
});
- Replaces the default cursor with a custom dot (
#cursor-dot). - Tracks the mouse position and moves the dot accordingly.
- Adds/removes a
.hoveredclass when hovering over interactive elements (a, button, .project-card, input, textarea).
2. Typewriter Effect
const roles = ['Frontend Developer', 'Backend Developer', 'Full Stack Engineer', 'UI/UX Enthusiast'];
- Displays a typing and deleting animation inside
#typewriter. - Cycles through job titles from the roles array.
- Uses recursion (setTimeout(type, typeSpeed)) to simulate typing and deleting characters.
3. Pixel Assembler Hero Effect (Canvas Animation)
- Uses
<canvas id="hero-canvas">to create a particle-based image effect. - Breaks down an image (
#about-img) into particles made of random characters ({}, [], +, -, etc.). - Particles move away from the mouse cursor within a certain radius and then return to their original position.
- Continuously animated with requestAnimationFrame.
4. Scroll Reveal Animation
const revealObserver = new IntersectionObserver(...)
- Watch the elements with
.reveal. - When scrolled into view (10% visible), adds the .visible class for animations.
- Stops observing once revealed.
5. Skill Bar Animation
const skillBars = document.querySelectorAll('.skill-bar-inner');
- Animates skill bars in
#about. - Expands width of each bar one after another (staggered effect).
- Triggered when the section is 50% visible.
6. Portfolio Filtering & Modals
- Holds project data in an array (projects).
- Allows filtering by category (frontend, backend, fullstack, or all).
- Renders project cards dynamically in
#portfolio-grid. - Clicking a card opens a modal with:
- Description
- Features
- Code snippet (with syntax highlighting via hljs)
- Modal has tab navigation (description/features/snippet).
7. Experience Timeline
- Defines an experience array with job history.
- Dynamically inserts timeline items into
#experience.
8. Mobile Menu Toggle
mobileMenuBtn.addEventListener('click', () =>
mobileMenu.classList.toggle('hidden')
);
- Handles hamburger menu (
#mobile-menu-btn) toggle. - Smooth scrolls to sections on click, auto-closing menu if open.
document.addEventListener('DOMContentLoaded', function () {
// --- Custom Cursor ---
const cursorDot = document.getElementById('cursor-dot');
window.addEventListener('mousemove', (e) => {
cursorDot.style.left = e.clientX + 'px';
cursorDot.style.top = e.clientY + 'px';
});
function updateInteractiveElements() {
const interactiveElements = document.querySelectorAll(
'a, button, .project-card, input, textarea'
);
interactiveElements.forEach((el) => {
el.addEventListener('mouseenter', () =>
cursorDot.classList.add('hovered')
);
el.addEventListener('mouseleave', () =>
cursorDot.classList.remove('hovered')
);
});
}
updateInteractiveElements();
// --- Typewriter Effect ---
const typewriterElement = document.getElementById('typewriter');
const roles = [
'Frontend Developer',
'Backend Developer',
'Full Stack Engineer',
'UI/UX Enthusiast',
];
let roleIndex = 0;
let charIndex = 0;
let isDeleting = false;
function type() {
const currentRole = roles[roleIndex];
if (isDeleting) {
typewriterElement.textContent = currentRole.substring(0, charIndex - 1);
charIndex--;
} else {
typewriterElement.textContent = currentRole.substring(0, charIndex + 1);
charIndex++;
}
if (!isDeleting && charIndex === currentRole.length) {
setTimeout(() => (isDeleting = true), 2000);
} else if (isDeleting && charIndex === 0) {
isDeleting = false;
roleIndex = (roleIndex + 1) % roles.length;
}
const typeSpeed = isDeleting ? 100 : 200;
setTimeout(type, typeSpeed);
}
type();
// --- Pixel Assembler Hero Effect ---
const canvas = document.getElementById('hero-canvas');
const heroSection = document.getElementById('hero');
const ctx = canvas.getContext('2d', { willReadFrequently: true });
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
let particleArray = [];
const characters = [
'{',
'}',
';',
':',
'=',
'+',
'-',
'*',
'/',
'>',
'<',
'(',
')',
'[',
']',
];
const mouse = {
x: null,
y: null,
radius: 100,
};
heroSection.addEventListener('mousemove', function (event) {
const rect = heroSection.getBoundingClientRect();
mouse.x = event.clientX - rect.left;
mouse.y = event.clientY - rect.top;
});
heroSection.addEventListener('mouseleave', function () {
mouse.x = null;
mouse.y = null;
});
class Particle {
constructor(x, y, character, color) {
this.x = x;
this.y = y;
this.character = character;
this.color = color;
this.baseX = this.x;
this.baseY = this.y;
this.density = Math.random() * 40 + 5;
this.size = 3;
}
draw() {
ctx.fillStyle = this.color;
ctx.font = '10px Fira Code';
ctx.fillText(this.character, this.x, this.y);
}
update() {
let dx = mouse.x - this.x;
let dy = mouse.y - this.y;
let distance = Math.sqrt(dx * dx + dy * dy);
let forceDirectionX = dx / distance;
let forceDirectionY = dy / distance;
let maxDistance = mouse.radius;
let force = (maxDistance - distance) / maxDistance;
let directionX = forceDirectionX * force * this.density;
let directionY = forceDirectionY * force * this.density;
if (distance < mouse.radius) {
this.x -= directionX;
this.y -= directionY;
} else {
if (this.x !== this.baseX) {
let dx = this.x - this.baseX;
this.x -= dx / 10;
}
if (this.y !== this.baseY) {
let dy = this.y - this.baseY;
this.y -= dy / 10;
}
}
}
}
function initParticles(image) {
particleArray = [];
const heroWidth = canvas.width;
const heroHeight = canvas.height;
const imgAspect = image.width / image.height;
const canvasAspect = heroWidth / heroHeight;
let imgWidth, imgHeight;
if (imgAspect > canvasAspect) {
imgHeight = heroHeight;
imgWidth = imgHeight * imgAspect;
} else {
imgWidth = heroWidth;
imgHeight = imgWidth / imgAspect;
}
const startX = heroWidth - imgWidth;
let startY;
if (window.innerWidth > 1024) {
startY = (heroHeight - imgHeight) / 2 + 100;
} else {
startY = (heroHeight - imgHeight) / 2;
}
// Draw image to a temporary canvas to grab pixel data
const tempCanvas = document.createElement('canvas');
const tempCtx = tempCanvas.getContext('2d');
tempCanvas.width = imgWidth;
tempCanvas.height = imgHeight;
tempCtx.drawImage(image, 0, 0, imgWidth, imgHeight);
const imageData = tempCtx.getImageData(0, 0, imgWidth, imgHeight);
// step controls particle density → higher = smoother
const step = 9;
for (let y = 0; y < imageData.height; y += step) {
for (let x = 0; x < imageData.width; x += step) {
const i = (y * imageData.width + x) * 4;
const alpha = imageData.data[i + 3];
if (alpha > 128) {
const r = imageData.data[i];
const g = imageData.data[i + 1];
const b = imageData.data[i + 2];
const color = `rgb(${r},${g},${b})`;
const randomChar =
characters[Math.floor(Math.random() * characters.length)];
particleArray.push(
new Particle(startX + x, startY + y, randomChar, color)
);
}
}
}
}
function animate() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
for (let i = 0; i < particleArray.length; i++) {
particleArray[i].draw();
particleArray[i].update();
}
requestAnimationFrame(animate);
}
const profileImage = new Image();
profileImage.crossOrigin = 'Anonymous';
profileImage.src = document.getElementById('about-img').src;
profileImage.onload = () => {
initParticles(profileImage);
animate();
};
window.addEventListener('resize', () => {
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
initParticles(profileImage);
});
// --- Scroll Reveal Animation ---
const revealElements = document.querySelectorAll('.reveal');
const revealObserver = new IntersectionObserver(
(entries) => {
entries.forEach((entry) => {
if (entry.isIntersecting) {
entry.target.classList.add('visible');
revealObserver.unobserve(entry.target);
}
});
},
{ threshold: 0.1 }
);
revealElements.forEach((el) => revealObserver.observe(el));
// --- Staggered Skill Bar Animation ---
const skillsSection = document.querySelector('#about');
const skillBars = document.querySelectorAll('.skill-bar-inner');
const skillObserver = new IntersectionObserver(
(entries) => {
if (entries[0].isIntersecting) {
skillBars.forEach((bar, index) => {
setTimeout(() => {
bar.style.width = bar.getAttribute('data-width');
}, index * 150);
});
skillObserver.unobserve(skillsSection);
}
},
{ threshold: 0.5 }
);
skillObserver.observe(skillsSection);
// --- Portfolio Filtering & Modals ---
const projects = [
{
id: 0,
title: 'E-commerce Platform',
category: 'fullstack',
description:
'A full-featured e-commerce site with React and Node.js, featuring product catalogs, user authentication, and a Stripe-integrated checkout process.',
tech: ['React', 'Node.js', 'MongoDB', 'Stripe'],
features: [
'User authentication with JWT',
'Product search and filtering',
'Shopping cart functionality',
'Secure payment processing',
],
snippet: `<div class="product-card">\n <img src={product.image} alt={product.name} />\n <h3>{product.name}</h3>\n <p>{product.price}</p>\n <button>Add to Cart</button>\n</div>`,
link: '#',
},
{
id: 1,
title: 'Data Visualization Dashboard',
category: 'frontend',
description:
'Interactive dashboard using D3.js for visualizing complex datasets with dynamic charts and graphs.',
tech: ['D3.js', 'React', 'Tailwind'],
features: [
'Multiple chart types (bar, line, pie)',
'Real-time data updates',
'Export charts as SVG or PNG',
'Responsive design for all devices',
],
snippet: `const svg = d3.select(ref.current)\n .attr("width", width)\n .attr("height", height);\n\nsvg.selectAll("rect")\n .data(data)\n .enter()\n .append("rect");`,
link: '#',
},
{
id: 2,
title: 'RESTful API for Social App',
category: 'backend',
description:
'A scalable backend API for a social media application, handling user profiles, posts, comments, and likes.',
tech: ['Node.js', 'Express', 'PostgreSQL'],
features: [
'CRUD operations for users, posts, comments',
'Secure authentication endpoints',
'Pagination for handling large datasets',
'Comprehensive API documentation',
],
snippet: `app.get('/api/posts', async (req, res) => {\n try {\n const posts = await Post.findAll();\n res.json(posts);\n } catch (err) {\n res.status(500).send('Server Error');\n }\n});`,
link: '#',
},
{
id: 3,
title: 'Real-time Chat Application',
category: 'fullstack',
description:
'A chat app built with WebSockets for instant communication.',
tech: ['Socket.IO', 'React', 'Node.js'],
features: [
'Instant messaging between users',
'User online status indicators',
'Room-based chat functionality',
'Message history',
],
snippet: `io.on('connection', (socket) => {\n console.log('a user connected');\n socket.on('chat message', (msg) => {\n io.emit('chat message', msg);\n });\n});`,
link: '#',
},
];
const portfolioGrid = document.getElementById('portfolio-grid');
const filterBtns = document.querySelectorAll('.filter-btn');
const modalOverlay = document.getElementById('project-modal-overlay');
const modalBody = document.querySelector('.modal-body');
const modalTabs = document.querySelectorAll('.modal-tab');
function displayProjects(filter) {
portfolioGrid.innerHTML = '';
const filteredProjects =
filter === 'all'
? projects
: projects.filter((p) => p.category === filter);
filteredProjects.forEach((project) => {
const projectCard = `
<div class="project-card rounded-lg overflow-hidden" data-id="${
project.id
}">
<div class="p-6">
<h3 class="text-xl font-bold mb-2 text-accent">${
project.title
}</h3>
<p class="text-secondary mb-4">${project.description.substring(
0,
80
)}...</p>
<div class="flex flex-wrap gap-2 mb-4">
${project.tech
.map(
(t) =>
`<span class="bg-gray-700 text-xs font-semibold mr-2 px-2.5 py-0.5 rounded">${t}</span>`
)
.join('')}
</div>
<span class="text-green hover:underline">View Details →</span>
</div>
</div>
`;
portfolioGrid.innerHTML += projectCard;
});
document.querySelectorAll('.project-card').forEach((card) => {
card.addEventListener('click', (e) =>
openModal(e.currentTarget.dataset.id)
);
});
updateInteractiveElements();
}
function openModal(projectId) {
const project = projects.find((p) => p.id == projectId);
if (!project) return;
updateModalContent(project, 'description');
modalOverlay.classList.add('active');
}
function updateModalContent(project, activeTab) {
let content = '';
modalTabs.forEach((tab) => {
tab.classList.toggle('active', tab.dataset.tab === activeTab);
tab.onclick = () => updateModalContent(project, tab.dataset.tab);
});
switch (activeTab) {
case 'features':
content = `<h3 class="text-xl font-bold mb-4 text-accent">Key Features</h3><ul class="list-disc list-inside space-y-2">${project.features
.map((f) => `<li>${f}</li>`)
.join('')}</ul>`;
break;
case 'snippet':
content = `<h3 class="text-xl font-bold mb-4 text-accent">Code Snippet</h3><pre><code class="language-javascript rounded-lg">${project.snippet}</code></pre>`;
break;
default:
content = `<h3 class="text-xl font-bold mb-4 text-accent">${project.title}</h3><p class="text-primary mb-4">${project.description}</p><a href="${project.link}" target="_blank" class="text-green hover:underline font-bold">Visit Website →</a>`;
}
modalBody.innerHTML = content;
if (activeTab === 'snippet') {
hljs.highlightAll();
}
}
filterBtns.forEach((btn) => {
btn.addEventListener('click', () => {
filterBtns.forEach((b) => b.classList.remove('active'));
btn.classList.add('active');
displayProjects(btn.dataset.filter);
});
});
document
.getElementById('modal-close-btn')
.addEventListener('click', () => modalOverlay.classList.remove('active'));
modalOverlay.addEventListener('click', (e) => {
if (e.target === modalOverlay) modalOverlay.classList.remove('active');
});
displayProjects('all');
document
.querySelector('.filter-btn[data-filter="all"]')
.classList.add('active');
const experiences = [
{
date: '2021 - Present',
role: 'Senior Software Engineer',
company: 'Tech Solutions Inc.',
description:
'Led the development of a new microservices architecture, improving system scalability by 40%. Mentored junior developers and conducted code reviews.',
},
{
date: '2019 - 2021',
role: 'Software Engineer',
company: 'Innovate Co.',
description:
'Developed and maintained features for a high-traffic web application using React and Node.js. Collaborated with cross-functional teams to deliver high-quality software.',
},
{
date: '2017 - 2019',
role: 'Junior Developer',
company: 'CodeCrafters LLC',
description:
'Assisted in the development of client websites using HTML, CSS, and JavaScript. Gained experience with version control systems like Git.',
},
];
const experienceContainer = document.querySelector('#experience .relative');
experiences.forEach((exp) => {
const item = `<div class="timeline-item"><div class="timeline-dot"></div><p class="text-sm text-secondary mb-1">${exp.date}</p><h3 class="text-xl font-bold text-accent">${exp.role}</h3><p class="font-semibold mb-2">${exp.company}</p><p class="text-primary">${exp.description}</p></div>`;
experienceContainer.innerHTML += item;
});
const mobileMenuBtn = document.getElementById('mobile-menu-btn');
const mobileMenu = document.getElementById('mobile-menu');
mobileMenuBtn.addEventListener('click', () =>
mobileMenu.classList.toggle('hidden')
);
document.querySelectorAll('a[href^="#"]').forEach((anchor) => {
anchor.addEventListener('click', function (e) {
e.preventDefault();
document
.querySelector(this.getAttribute('href'))
.scrollIntoView({ behavior: 'smooth' });
if (!mobileMenu.classList.contains('hidden'))
mobileMenu.classList.add('hidden');
});
});
});Final Output:
Conclusion:
Creating a portfolio website template using HTML, CSS, and JavaScript is not as difficult as it looks. With just a few steps, you can build a clean and modern portfolio to showcase your work and skills. This website can also be customized and improved with more features like animations, image galleries, or contact forms.
So, start today and build your own portfolio website. It’s the best way to stand out in the digital world!
That’s a wrap!
I hope you enjoyed this post. Now, with these examples, you can create your own amazing page.
Did you like it? Let me know in the comments below 🔥 and you can support me by buying me a coffee
And don’t forget to sign up to our email newsletter so you can get useful content like this sent right to your inbox!
Thanks!
Faraz 😊


