Portfolio Website Development

A deep dive into the code behind this portfolio website

Project Overview

This project involved designing and developing a responsive portfolio website from scratch. The goal was to create a clean, modern, and performance-optimized website that effectively showcases my projects and skills while incorporating interesting interactive elements.

The website features custom animations, dark mode support, responsive design for all device sizes, and React integration for dynamic components. The development process focused on maintaining clean, modular code while optimizing for performance and accessibility.

HTML5
CSS3
JavaScript
React
Dark Mode
Responsive Design
Performance Optimization

Key Features

Dark Mode Implementation

A seamless dark/light mode toggle with localStorage persistence to remember user preferences. The implementation uses CSS variables to create a consistent theming system throughout the site.

JavaScript
const themeToggle = document.getElementById('theme-toggle'); const themeIcon = themeToggle.querySelector('i'); const savedTheme = localStorage.getItem('theme'); if (savedTheme === 'light') { // Light mode } else { document.documentElement.classList.add('dark'); themeIcon.classList.remove('fa-moon'); themeIcon.classList.add('fa-sun'); if (!savedTheme) { localStorage.setItem('theme', 'dark'); } }

The CSS variables make it easy to define color schemes for both light and dark modes:

CSS
:root { /* Light mode colors */ --bg-color: #f8f9fa; --text-primary: #212529; --accent-primary: #5b46ff; /* ...more variables */ } .dark { /* Dark mode colors */ --bg-color: #0a0d14; --text-primary: #eef1f8; --accent-primary: #6c63ff; /* ...more variables */ }

React Components Integration

The site uses React for interactive elements like the animated text headers. This integration allows for reusable components without the need for a full React build pipeline.

JSX
import React, { useEffect, useRef } from 'react'; const HeaderPressure = ({ text, className = '' }) => { const containerRef = useRef(null); const titleRef = useRef(null); const spansRef = useRef([]); const mouseRef = useRef({ x: 0, y: 0 }); const cursorRef = useRef({ x: 0, y: 0 }); const chars = text.split(''); useEffect(() => { const handleMouseMove = (e) => { cursorRef.current.x = e.clientX; cursorRef.current.y = e.clientY; }; window.addEventListener('mousemove', handleMouseMove); // More implementation... }, []); // Return JSX };

The React components are mounted into the DOM using the createRoot API:

JavaScript
import React from 'react'; import { createRoot } from 'react-dom/client'; import HeaderPressure from './HeaderPressure'; // Define the headers to be rendered const headerElements = [ { id: 'nameHeader', text: 'Sia Khorsand', className: 'text-4xl font-title text-primary-950' }, // More headers... ]; // Render each header headerElements.forEach(({ id, text, className }) => { const container = document.getElementById(id); if (container) { const root = createRoot(container); root.render( <React.StrictMode> <HeaderPressure text={text} className={className} /> </React.StrictMode> ); } });

Interactive UI Elements

The portfolio includes various interactive elements to create an engaging experience, from subtle hover animations to 3D tilt effects on project cards.

JavaScript
function applyProjectCardTiltEffects() { document.querySelectorAll('.project-card').forEach(card => { card.addEventListener('mousemove', e => { const rect = card.getBoundingClientRect(); const centerX = rect.width / 2; const centerY = rect.height / 2; const x = e.clientX - rect.left - centerX; const y = e.clientY - rect.top - centerY; const rotateX = -(y / centerY) * 20; const rotateY = (x / centerX) * 20; card.style.transform = ` perspective(800px) rotateX(${rotateX}deg) rotateY(${rotateY}deg) translateZ(15px) scale3d(1.05, 1.05, 1.05) `; const shadowX = -rotateY / 1.5; const shadowY = rotateX / 1.5; card.style.boxShadow = ` ${shadowX}px ${shadowY}px 25px rgba(0, 0, 0, 0.15), 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -4px rgba(0, 0, 0, 0.1) `; }); card.addEventListener('mouseleave', () => { card.style.transform = 'perspective(800px) rotateX(0deg) rotateY(0deg) translateZ(0) scale3d(1, 1, 1)'; card.style.boxShadow = 'var(--card-shadow)'; }); }); }

Responsive Design & Mobile Optimization (still in progress)

The website is fully responsive, adapting to all screen sizes from mobile phones to large desktop monitors. Special attention was paid to optimizing performance on mobile devices.

CSS
/* Mobile optimizations */ @media (max-width: 767px) { /* Simplify animations for better performance */ * { transition-duration: 0.2s !important; } /* Disable complex animations on mobile */ .profile-photo-wrapper, .profile-photo, .text-pressure-title span { animation: none !important; transform: none !important; transition: none !important; will-change: auto !important; } /* Optimize images for mobile */ .project-image { height: 140px; } }
JavaScript
// Mobile optimizations document.addEventListener('DOMContentLoaded', function() { // Add a touch class to the body if device supports touch if ('ontouchstart' in window || navigator.maxTouchPoints > 0) { document.body.classList.add('touch-device'); } const isMobile = window.innerWidth <= 767; if (isMobile) { // Optimize images with lazy loading document.querySelectorAll('img').forEach(img => { if (!img.hasAttribute('loading')) { img.setAttribute('loading', 'lazy'); } }); } // Fix for mobile viewport units function setVHVariable() { let vh = window.innerHeight * 0.01; document.documentElement.style.setProperty('--vh', `${vh}px`); } setVHVariable(); window.addEventListener('resize', setVHVariable); window.addEventListener('orientationchange', setVHVariable); });

Architecture and Design Decisions

Several key architectural decisions were made to ensure the website's performance, maintainability, and user experience:

No Framework Approach: The site was built without a heavy framework like Next.js or Gatsby, focusing instead on vanilla HTML/CSS/JS with targeted React integration where needed. This approach provides faster initial load times and simpler maintenance.
CSS Variables for Theming: Using CSS variables for colors and other theme elements allows for easier maintenance and the seamless implementation of dark mode.
Progressive Enhancement: Core content is accessible without JavaScript, with interactive elements layered on top for enhanced experience when JS is available.
Mobile-First Optimizations: Performance considerations for mobile devices were prioritized, including simplified animations, lazy loading, and touch-friendly interfaces.
Modular JavaScript: Code is organized into discrete modules with clear responsibilities for better maintainability.

Animation Techniques

The website uses several animation techniques to create an engaging experience without sacrificing performance:

Text Animation

The "About Me" and project overview sections feature a subtle text transformation effect that reveals the text character by character with a randomized animation.

JavaScript
document.addEventListener('DOMContentLoaded', () => { const projectOverviewEl = document.getElementById('project-overview'); const originalHTML = projectOverviewEl.innerHTML; const lines = originalHTML.split(//); const characters = 'abcdefghijklmnopqrstuvwxyz_'; let interval; let iteration = 0; const totalIterations = 10; function transformLine(line, iteration, totalIterations) { const progress = iteration / totalIterations; const charsToReveal = Math.floor(line.length * progress); let transformed = ""; for (let j = 0; j < line.length; j++) { if (j < charsToReveal) { transformed += line[j]; } else { if (line[j].match(/[<>\/]/) || line[j] === ' ' || line[j].match(/[.,!?]/)) { transformed += line[j]; } else { transformed += characters[Math.floor(Math.random() * characters.length)]; } } } return transformed; } function startTransformationAnimation() { iteration = 0; clearInterval(interval); interval = setInterval(() => { iteration++; const newLines = lines.map(line => transformLine(line, iteration, totalIterations)); projectOverviewEl.innerHTML = newLines.join('
'
);
if (iteration >= totalIterations) { clearInterval(interval); projectOverviewEl.innerHTML = originalHTML; } }, 60); } const observer = new IntersectionObserver((entries) => { entries.forEach(entry => { if (entry.isIntersecting) { startTransformationAnimation(); observer.unobserve(entry.target); } }); }, { threshold: 0.5 }); observer.observe(projectOverviewEl); });

Scroll Progress

A subtle scroll progress indicator at the top of the page shows readers how far they've scrolled through the content.

JavaScript
const scrollProgress = document.querySelector('.scroll-progress'); window.addEventListener('scroll', () => { const scrollPosition = window.scrollY; const totalHeight = document.body.scrollHeight - window.innerHeight; const scrollPercentage = (scrollPosition / totalHeight) * 100; scrollProgress.style.width = `${scrollPercentage}%`; });

The CSS for the progress indicator:

CSS
/* Scroll progress indicator */ .scroll-progress { position: fixed; top: 0; left: 0; height: 6px; background-color: var(--accent-primary); width: 0%; z-index: 100; transition: width 0.1s ease-out; }

Performance Optimizations

Several optimizations were implemented to ensure the website loads quickly and runs smoothly on all devices:

Lazy Loading: Images are lazy-loaded to improve initial page load time and reduce data usage.
Animation Throttling: Complex animations are disabled on mobile devices to improve performance.
Efficient Event Handling: Event listeners use passive options and requestAnimationFrame where appropriate to prevent jank.
Minimal Dependencies: The site has very few external dependencies, reducing download and parsing time.
CSS Variables: Using CSS variables for theming reduces stylesheet size and complexity.
JavaScript
// Performance optimization using requestAnimationFrame function setupSidebar() { // Only apply for desktop screens if (window.innerWidth >= 768) { // ... setup code ... let ticking = false; function updateSidebarPosition() { // Position calculation logic ticking = false; } function onScroll() { if (!ticking) { window.requestAnimationFrame(updateSidebarPosition); ticking = true; } } // Passive event listener for better scrolling performance window.addEventListener('scroll', onScroll, { passive: true }); } }

Explore the Code

Check out the complete source code for this portfolio website on GitHub:

View on GitHub