Web Pages Hard
Nova Portfolio
A dark-themed portfolio website with smooth scroll animations, Three.js particle background, expandable project cards, and GSAP-powered transitions.
Open in Lab
MCP
html css javascript threejs gsap lenis
Targets: JS HTML
Code
@import url("https://fonts.googleapis.com/css2?family=Black+Ops+One&family=Orbitron:wght@400;700;900&family=Rajdhani:wght@300;400;500;600;700&display=swap");
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
:root {
--pink: #ffb6c1;
--pink-dark: #ff69b4;
--pink-light: #ffc0cb;
--dark-bg: #0a0a0a;
--dark-bg-2: #1a0a0a;
--text-white: #ffffff;
--text-gray: #cccccc;
}
body {
font-family: "Rajdhani", sans-serif;
background: var(--dark-bg);
color: var(--text-white);
overflow-x: hidden;
position: relative;
}
#canvas {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: 0;
pointer-events: none;
}
.container {
position: relative;
z-index: 1;
}
/* Navbar */
.navbar {
position: fixed;
top: 0;
left: 0;
width: 100%;
padding: 1.5rem 6rem;
background: rgba(10, 10, 10, 0.8);
backdrop-filter: blur(10px);
border-bottom: 1px solid rgba(255, 182, 193, 0.2);
z-index: 1000;
transform: translateY(-100%);
opacity: 0;
transition:
transform 0.6s cubic-bezier(0.4, 0, 0.2, 1),
opacity 0.6s ease,
padding 0.3s ease,
background 0.3s ease,
box-shadow 0.3s ease;
}
.navbar.visible {
transform: translateY(0);
opacity: 1;
}
.navbar.hidden {
transform: translateY(-100%);
opacity: 0;
}
.navbar.scrolled {
padding: 1rem 6rem;
background: rgba(10, 10, 10, 0.95);
box-shadow: 0 5px 20px rgba(0, 0, 0, 0.5);
border-bottom: 1px solid rgba(255, 182, 193, 0.3);
}
.nav-container {
display: flex;
justify-content: space-between;
align-items: center;
max-width: 1400px;
margin: 0 auto;
}
.nav-logo {
font-family: "Black Ops One", cursive;
font-size: 1.5rem;
font-weight: 400;
opacity: 0;
transform: translateX(-30px);
transition: opacity 0.6s ease 0.2s, transform 0.6s cubic-bezier(0.4, 0, 0.2, 1) 0.2s;
}
.navbar.visible .nav-logo {
opacity: 1;
transform: translateX(0);
}
.logo-link {
color: var(--pink);
text-decoration: none;
text-transform: uppercase;
letter-spacing: 0.1em;
text-shadow: 0 0 10px rgba(255, 182, 193, 0.5), 2px 2px 0px rgba(0, 0, 0, 0.8);
transition: all 0.3s ease;
}
.logo-link:hover {
color: var(--pink-light);
text-shadow: 0 0 20px rgba(255, 182, 193, 0.8), 2px 2px 0px rgba(0, 0, 0, 0.8);
}
.nav-menu {
display: flex;
list-style: none;
gap: 3rem;
align-items: center;
opacity: 0;
transform: translateX(30px);
transition: opacity 0.6s ease 0.4s, transform 0.6s cubic-bezier(0.4, 0, 0.2, 1) 0.4s;
}
.navbar.visible .nav-menu {
opacity: 1;
transform: translateX(0);
}
.nav-item {
position: relative;
opacity: 0;
transform: translateY(-20px);
transition: opacity 0.4s ease, transform 0.4s ease;
}
.navbar.visible .nav-item {
opacity: 1;
transform: translateY(0);
}
.navbar.visible .nav-item:nth-child(1) {
transition-delay: 0.5s;
}
.navbar.visible .nav-item:nth-child(2) {
transition-delay: 0.6s;
}
.navbar.visible .nav-item:nth-child(3) {
transition-delay: 0.7s;
}
.navbar.visible .nav-item:nth-child(4) {
transition-delay: 0.8s;
}
.navbar.visible .nav-item:nth-child(5) {
transition-delay: 0.9s;
}
.nav-link {
font-family: "Orbitron", sans-serif;
font-size: 0.875rem;
font-weight: 700;
text-transform: uppercase;
letter-spacing: 0.15em;
color: var(--text-white);
text-decoration: none;
padding: 0.5rem 0;
position: relative;
transition: color 0.3s ease, transform 0.3s ease;
}
.nav-link::after {
content: "";
position: absolute;
bottom: 0;
left: 0;
width: 0;
height: 2px;
background: var(--pink);
transition: width 0.3s ease;
box-shadow: 0 0 10px rgba(255, 182, 193, 0.5);
}
.nav-link:hover {
color: var(--pink);
transform: translateY(-2px);
}
.nav-link:hover::after {
width: 100%;
}
.nav-link.active {
color: var(--pink);
}
.nav-link.active::after {
width: 100%;
}
.nav-toggle {
display: none;
flex-direction: column;
cursor: pointer;
gap: 5px;
opacity: 0;
transform: scale(0.8);
transition: opacity 0.6s ease 0.6s, transform 0.6s cubic-bezier(0.4, 0, 0.2, 1) 0.6s;
}
.navbar.visible .nav-toggle {
opacity: 1;
transform: scale(1);
}
.bar {
width: 25px;
height: 3px;
background: var(--pink);
transition: all 0.3s ease;
box-shadow: 0 0 5px rgba(255, 182, 193, 0.5);
}
.nav-toggle.active .bar:nth-child(1) {
transform: rotate(45deg) translate(8px, 8px);
}
.nav-toggle.active .bar:nth-child(2) {
opacity: 0;
}
.nav-toggle.active .bar:nth-child(3) {
transform: rotate(-45deg) translate(7px, -7px);
}
/* Hero Section */
.hero {
min-height: 100vh;
display: flex;
flex-direction: column;
justify-content: space-between;
padding: 4rem 6rem;
padding-top: 8rem;
position: relative;
}
.main-title {
font-family: "Black Ops One", cursive;
font-size: clamp(8rem, 20vw, 16rem);
font-weight: 400;
color: var(--pink);
text-transform: uppercase;
letter-spacing: 0.1em;
line-height: 0.9;
text-shadow: 0 0 20px rgba(255, 182, 193, 0.5), 0 0 40px rgba(255, 182, 193, 0.3), 4px 4px 0px
rgba(0, 0, 0, 0.8), 8px 8px 0px rgba(0, 0, 0, 0.6);
margin-top: 2rem;
position: relative;
z-index: 2;
}
.subtitle-section {
display: flex;
align-items: baseline;
gap: 2rem;
margin-top: -4rem;
position: relative;
z-index: 2;
}
.subtitle {
font-family: "Orbitron", sans-serif;
font-size: clamp(1.5rem, 3vw, 2.5rem);
font-weight: 700;
text-transform: uppercase;
letter-spacing: 0.2em;
color: var(--text-white);
}
.copyright {
font-family: "Rajdhani", sans-serif;
font-size: clamp(0.875rem, 1.5vw, 1rem);
font-weight: 400;
color: var(--text-white);
letter-spacing: 0.1em;
}
.hero-bottom {
display: flex;
justify-content: space-between;
align-items: flex-end;
margin-top: auto;
padding-top: 4rem;
}
.hero-left {
display: flex;
flex-direction: column;
gap: 1rem;
}
.scroll-cta {
font-family: "Orbitron", sans-serif;
font-size: clamp(1rem, 2vw, 1.5rem);
font-weight: 700;
text-transform: uppercase;
letter-spacing: 0.15em;
color: var(--text-white);
}
.hero-description {
font-family: "Rajdhani", sans-serif;
font-size: clamp(0.875rem, 1.5vw, 1rem);
font-weight: 400;
text-transform: uppercase;
letter-spacing: 0.1em;
color: var(--text-white);
max-width: 500px;
}
.hero-buttons {
display: flex;
gap: 1.5rem;
}
.btn {
display: flex;
align-items: center;
gap: 1rem;
padding: 1rem 2rem;
background: var(--pink);
border: none;
color: var(--text-white);
font-family: "Orbitron", sans-serif;
font-size: clamp(0.875rem, 1.2vw, 1rem);
font-weight: 700;
text-transform: uppercase;
letter-spacing: 0.1em;
cursor: pointer;
transition: all 0.3s ease;
position: relative;
overflow: hidden;
}
.btn::before {
content: "";
position: absolute;
left: 0;
top: 0;
width: 40px;
height: 100%;
background: rgba(0, 0, 0, 0.3);
display: flex;
align-items: center;
justify-content: center;
}
.btn-icon {
width: 40px;
height: 40px;
display: flex;
align-items: center;
justify-content: center;
background: rgba(0, 0, 0, 0.3);
font-size: 1.2rem;
position: relative;
z-index: 1;
}
.btn:hover {
background: var(--pink-dark);
transform: translateY(-2px);
box-shadow: 0 10px 30px rgba(255, 182, 193, 0.4);
}
/* Sections */
.section {
min-height: 100vh;
padding: 8rem 6rem;
position: relative;
}
.section-title {
font-family: "Black Ops One", cursive;
font-size: clamp(4rem, 10vw, 8rem);
color: var(--pink);
text-transform: uppercase;
letter-spacing: 0.1em;
margin-bottom: 4rem;
text-shadow: 0 0 20px rgba(255, 182, 193, 0.5), 2px 2px 0px rgba(0, 0, 0, 0.8);
}
/* About Section */
.content-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
gap: 3rem;
margin-top: 4rem;
}
.content-item {
padding: 2rem;
border: 1px solid rgba(255, 182, 193, 0.2);
background: rgba(255, 182, 193, 0.05);
transition: all 0.3s ease;
}
.content-item:hover {
border-color: var(--pink);
background: rgba(255, 182, 193, 0.1);
transform: translateY(-5px);
}
.content-item h3 {
font-family: "Orbitron", sans-serif;
font-size: 1.5rem;
font-weight: 700;
text-transform: uppercase;
letter-spacing: 0.1em;
color: var(--pink);
margin-bottom: 1rem;
}
.content-item p {
font-family: "Rajdhani", sans-serif;
font-size: 1rem;
line-height: 1.6;
color: var(--text-gray);
}
/* Projects Section */
.projects-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
gap: 3rem;
margin-top: 4rem;
}
.project-card {
padding: 2.5rem;
border: 1px solid rgba(255, 182, 193, 0.2);
background: rgba(255, 182, 193, 0.05);
position: relative;
transition: all 0.3s ease;
cursor: pointer;
overflow: hidden;
}
.project-card::after {
content: "CLICK TO EXPAND →";
position: absolute;
bottom: 1rem;
left: 2.5rem;
font-family: "Orbitron", sans-serif;
font-size: 0.75rem;
font-weight: 700;
text-transform: uppercase;
letter-spacing: 0.1em;
color: var(--pink);
opacity: 0;
transition: opacity 0.3s ease;
pointer-events: none;
}
.project-card:hover {
border-color: var(--pink);
background: rgba(255, 182, 193, 0.1);
transform: translateY(-10px);
box-shadow: 0 20px 40px rgba(255, 182, 193, 0.2);
}
.project-card:hover::after {
opacity: 0.7;
}
.project-card.active {
z-index: 100;
}
/* Card Expanded Overlay */
.card-expanded {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(10, 10, 10, 0.95);
backdrop-filter: blur(20px);
z-index: 2000;
display: flex;
align-items: center;
justify-content: center;
opacity: 0;
pointer-events: none;
overflow-y: auto;
padding: 2rem;
will-change: transform, opacity, filter;
}
.card-expanded.active {
opacity: 1;
pointer-events: all;
}
.close-card {
position: fixed;
top: 2rem;
right: 2rem;
width: 60px;
height: 60px;
background: rgba(255, 182, 193, 0.1);
border: 2px solid var(--pink);
color: var(--pink);
font-size: 2.5rem;
font-family: "Orbitron", sans-serif;
font-weight: 700;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
border-radius: 50%;
transition: all 0.3s ease;
z-index: 2001;
line-height: 1;
padding: 0;
}
.close-card::after {
content: "CLOSE";
position: absolute;
right: 70px;
font-family: "Orbitron", sans-serif;
font-size: 0.75rem;
font-weight: 700;
text-transform: uppercase;
letter-spacing: 0.1em;
color: var(--pink);
opacity: 0;
transition: opacity 0.3s ease;
white-space: nowrap;
}
.close-card:hover::after {
opacity: 1;
}
.close-card:hover {
background: var(--pink);
color: var(--dark-bg);
transform: rotate(90deg) scale(1.1);
box-shadow: 0 0 30px rgba(255, 182, 193, 0.6);
}
.expanded-content {
max-width: 95%;
width: 100%;
min-height: 85vh;
position: relative;
padding: 5rem;
background: rgba(255, 182, 193, 0.05);
border: 2px solid rgba(255, 182, 193, 0.3);
border-radius: 20px;
box-shadow: 0 30px 60px rgba(0, 0, 0, 0.5);
will-change: transform;
overflow-y: auto;
margin: 2rem;
}
.expanded-content::-webkit-scrollbar {
width: 8px;
}
.expanded-content::-webkit-scrollbar-track {
background: rgba(255, 182, 193, 0.1);
border-radius: 10px;
}
.expanded-content::-webkit-scrollbar-thumb {
background: var(--pink);
border-radius: 10px;
}
.expanded-content::-webkit-scrollbar-thumb:hover {
background: var(--pink-dark);
}
.expanded-number {
font-family: "Black Ops One", cursive;
font-size: clamp(8rem, 15vw, 12rem);
color: var(--pink);
opacity: 0.2;
position: absolute;
top: -1rem;
right: 2rem;
line-height: 1;
}
.expanded-title {
font-family: "Black Ops One", cursive;
font-size: clamp(3rem, 6vw, 5rem);
color: var(--pink);
text-transform: uppercase;
letter-spacing: 0.1em;
margin-bottom: 3rem;
text-shadow: 0 0 20px rgba(255, 182, 193, 0.5), 2px 2px 0px rgba(0, 0, 0, 0.8);
position: relative;
z-index: 1;
}
.expanded-details {
position: relative;
z-index: 1;
}
.expanded-description {
font-family: "Rajdhani", sans-serif;
font-size: clamp(1.1rem, 2vw, 1.5rem);
line-height: 1.8;
color: var(--text-gray);
margin-bottom: 3rem;
opacity: 0;
max-width: 90%;
}
.expanded-features {
margin-bottom: 3rem;
opacity: 0;
max-width: 90%;
}
.expanded-features h4 {
font-family: "Orbitron", sans-serif;
font-size: clamp(1.25rem, 2.5vw, 2rem);
font-weight: 700;
text-transform: uppercase;
letter-spacing: 0.1em;
color: var(--pink);
margin-bottom: 2rem;
}
.expanded-features ul {
list-style: none;
padding: 0;
}
.expanded-features li {
font-family: "Rajdhani", sans-serif;
font-size: clamp(1rem, 1.5vw, 1.3rem);
color: var(--text-white);
padding: 1rem 0;
padding-left: 2.5rem;
position: relative;
border-bottom: 1px solid rgba(255, 182, 193, 0.1);
}
.expanded-features li::before {
content: "→";
position: absolute;
left: 0;
color: var(--pink);
font-weight: 700;
}
.expanded-links {
display: flex;
gap: 2rem;
flex-wrap: wrap;
opacity: 0;
margin-top: 2rem;
}
.expanded-close-hint {
font-family: "Rajdhani", sans-serif;
font-size: 0.875rem;
color: rgba(255, 182, 193, 0.6);
text-transform: uppercase;
letter-spacing: 0.1em;
margin-top: 2rem;
text-align: center;
opacity: 0;
}
.expanded-close-hint span {
color: var(--pink);
font-weight: 600;
}
.expanded-link {
font-family: "Orbitron", sans-serif;
font-size: 1rem;
font-weight: 700;
text-transform: uppercase;
letter-spacing: 0.1em;
color: var(--pink);
text-decoration: none;
padding: 1rem 2rem;
border: 2px solid var(--pink);
background: rgba(255, 182, 193, 0.1);
transition: all 0.3s ease;
display: inline-block;
}
.expanded-link:hover {
background: var(--pink);
color: var(--dark-bg);
transform: translateY(-3px);
box-shadow: 0 10px 30px rgba(255, 182, 193, 0.4);
}
/* Blur effect for background when card is open */
body.card-open {
overflow: hidden;
}
body.card-open .container {
filter: blur(10px);
pointer-events: none;
transition: filter 0.3s ease;
}
/* Responsive for expanded cards */
@media (max-width: 768px) {
.expanded-content {
padding: 2.5rem;
margin: 1rem;
min-height: 80vh;
max-width: 98%;
}
.expanded-number {
font-size: 5rem;
top: -0.5rem;
right: 1rem;
}
.expanded-title {
font-size: 2rem;
margin-bottom: 2rem;
}
.expanded-description {
font-size: 1rem;
max-width: 100%;
}
.expanded-features {
max-width: 100%;
}
.expanded-features h4 {
font-size: 1.25rem;
}
.expanded-features li {
font-size: 1rem;
padding-left: 2rem;
}
.close-card {
top: 1rem;
right: 1rem;
width: 50px;
height: 50px;
font-size: 2rem;
}
.expanded-links {
flex-direction: column;
gap: 1rem;
}
.expanded-link {
width: 100%;
text-align: center;
}
}
.project-number {
font-family: "Black Ops One", cursive;
font-size: 4rem;
color: var(--pink);
opacity: 0.3;
position: absolute;
top: 1rem;
right: 1.5rem;
}
.project-title {
font-family: "Orbitron", sans-serif;
font-size: 1.75rem;
font-weight: 700;
text-transform: uppercase;
letter-spacing: 0.1em;
color: var(--text-white);
margin-bottom: 1rem;
margin-top: 1rem;
}
.project-description {
font-family: "Rajdhani", sans-serif;
font-size: 1rem;
line-height: 1.6;
color: var(--text-gray);
margin-bottom: 1.5rem;
}
.project-tags {
display: flex;
flex-wrap: wrap;
gap: 0.5rem;
}
.tag {
padding: 0.5rem 1rem;
background: rgba(255, 182, 193, 0.2);
border: 1px solid var(--pink);
color: var(--pink);
font-family: "Rajdhani", sans-serif;
font-size: 0.875rem;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.05em;
}
/* Skills Section */
.skills-list {
max-width: 800px;
margin-top: 4rem;
}
.skill-item {
margin-bottom: 2rem;
}
.skill-name {
font-family: "Orbitron", sans-serif;
font-size: 1.25rem;
font-weight: 700;
text-transform: uppercase;
letter-spacing: 0.1em;
color: var(--text-white);
display: block;
margin-bottom: 0.5rem;
}
.skill-bar {
width: 100%;
height: 8px;
background: rgba(255, 182, 193, 0.1);
border: 1px solid rgba(255, 182, 193, 0.3);
position: relative;
overflow: hidden;
}
.skill-progress {
height: 100%;
background: var(--pink);
width: 0%;
transition: width 1s ease;
box-shadow: 0 0 10px rgba(255, 182, 193, 0.5);
}
/* Contact Section */
.contact-content {
max-width: 600px;
margin-top: 4rem;
}
.contact-text {
font-family: "Rajdhani", sans-serif;
font-size: 1.5rem;
font-weight: 400;
text-transform: uppercase;
letter-spacing: 0.1em;
color: var(--text-white);
margin-bottom: 2rem;
}
.contact-info {
display: flex;
flex-direction: column;
gap: 2rem;
}
.contact-link {
font-family: "Orbitron", sans-serif;
font-size: 1.25rem;
font-weight: 700;
color: var(--pink);
text-decoration: none;
transition: all 0.3s ease;
display: inline-block;
}
.contact-link:hover {
color: var(--pink-light);
text-shadow: 0 0 10px rgba(255, 182, 193, 0.5);
}
.social-links {
display: flex;
gap: 2rem;
}
.social-link {
font-family: "Rajdhani", sans-serif;
font-size: 1rem;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.1em;
color: var(--text-white);
text-decoration: none;
padding: 0.5rem 1rem;
border: 1px solid rgba(255, 182, 193, 0.3);
transition: all 0.3s ease;
}
.social-link:hover {
border-color: var(--pink);
background: rgba(255, 182, 193, 0.1);
color: var(--pink);
}
/* Responsive */
@media (max-width: 768px) {
.navbar {
padding: 1rem 2rem;
}
.navbar.scrolled {
padding: 0.75rem 2rem;
}
.nav-menu {
position: fixed;
left: -100%;
top: 70px;
flex-direction: column;
background: rgba(10, 10, 10, 0.98);
width: 100%;
text-align: center;
transition: left 0.3s ease;
padding: 2rem 0;
border-bottom: 1px solid rgba(255, 182, 193, 0.2);
gap: 2rem;
}
.nav-menu.active {
left: 0;
}
.nav-toggle {
display: flex;
}
.hero {
padding: 2rem;
padding-top: 6rem;
}
.hero-bottom {
flex-direction: column;
gap: 2rem;
}
.hero-buttons {
flex-direction: column;
width: 100%;
}
.btn {
width: 100%;
justify-content: center;
}
.section {
padding: 4rem 2rem;
padding-top: 6rem;
}
.subtitle-section {
flex-direction: column;
gap: 0.5rem;
}
}// Initialize Lenis smooth scroll
const lenis = new Lenis({
duration: 1.2,
easing: (t) => Math.min(1, 1.001 - Math.pow(2, -10 * t)),
direction: "vertical",
gestureDirection: "vertical",
smooth: true,
smoothTouch: false,
touchMultiplier: 2,
});
// Animation loop
function raf(time) {
lenis.raf(time);
requestAnimationFrame(raf);
}
requestAnimationFrame(raf);
// Register GSAP ScrollTrigger
gsap.registerPlugin(ScrollTrigger);
// Update ScrollTrigger on scroll
lenis.on("scroll", ScrollTrigger.update);
// Proxy scroll events to GSAP
gsap.ticker.add((time) => {
lenis.raf(time * 1000);
});
gsap.ticker.lagSmoothing(0);
// Three.js Background Setup
let scene, camera, renderer, particles;
function initThree() {
// Scene
scene = new THREE.Scene();
// Camera
camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
camera.position.z = 5;
// Renderer
const canvas = document.getElementById("canvas");
renderer = new THREE.WebGLRenderer({
canvas: canvas,
alpha: true,
antialias: true,
});
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.setPixelRatio(window.devicePixelRatio);
// Create particles (stars)
const particleCount = 2000;
const geometry = new THREE.BufferGeometry();
const positions = new Float32Array(particleCount * 3);
const colors = new Float32Array(particleCount * 3);
const pinkColor = new THREE.Color(0xffb6c1);
const whiteColor = new THREE.Color(0xffffff);
for (let i = 0; i < particleCount * 3; i += 3) {
positions[i] = (Math.random() - 0.5) * 20;
positions[i + 1] = (Math.random() - 0.5) * 20;
positions[i + 2] = (Math.random() - 0.5) * 20;
const color = Math.random() > 0.7 ? pinkColor : whiteColor;
colors[i] = color.r;
colors[i + 1] = color.g;
colors[i + 2] = color.b;
}
geometry.setAttribute("position", new THREE.BufferAttribute(positions, 3));
geometry.setAttribute("color", new THREE.BufferAttribute(colors, 3));
const material = new THREE.PointsMaterial({
size: 0.05,
vertexColors: true,
transparent: true,
opacity: 0.8,
blending: THREE.AdditiveBlending,
});
particles = new THREE.Points(geometry, material);
scene.add(particles);
// Create smoke/glow effect
const smokeGeometry = new THREE.PlaneGeometry(10, 10);
const smokeMaterial = new THREE.MeshBasicMaterial({
color: 0xff69b4,
transparent: true,
opacity: 0.1,
side: THREE.DoubleSide,
});
const smokeCount = 5;
for (let i = 0; i < smokeCount; i++) {
const smoke = new THREE.Mesh(smokeGeometry, smokeMaterial.clone());
smoke.position.set((Math.random() - 0.5) * 8, (Math.random() - 0.5) * 8 - 2, -2);
smoke.rotation.z = Math.random() * Math.PI;
smoke.scale.set(Math.random() * 2 + 1, Math.random() * 3 + 2, 1);
scene.add(smoke);
}
animate();
}
function animate() {
requestAnimationFrame(animate);
// Rotate particles
if (particles) {
particles.rotation.y += 0.0005;
particles.rotation.x += 0.0002;
}
renderer.render(scene, camera);
}
// Handle window resize
function onWindowResize() {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
}
window.addEventListener("resize", onWindowResize);
// Initialize Three.js
initThree();
// GSAP Animations
gsap.utils.toArray(".section").forEach((section, index) => {
if (index > 0) {
gsap.from(section, {
opacity: 0,
y: 100,
duration: 1,
scrollTrigger: {
trigger: section,
start: "top 80%",
end: "top 20%",
scrub: 1,
},
});
}
});
// Animate main title
gsap.from(".main-title", {
opacity: 0,
scale: 0.8,
duration: 1.5,
ease: "power3.out",
});
// Animate subtitle
gsap.from(".subtitle", {
opacity: 0,
x: -50,
duration: 1,
delay: 0.5,
ease: "power3.out",
});
// Animate buttons
gsap.from(".btn", {
opacity: 0,
y: 30,
duration: 0.8,
delay: 1,
stagger: 0.2,
ease: "power3.out",
});
// Animate content items
gsap.utils.toArray(".content-item, .project-card").forEach((item, index) => {
gsap.from(item, {
opacity: 0,
y: 50,
duration: 0.8,
scrollTrigger: {
trigger: item,
start: "top 85%",
toggleActions: "play none none none",
},
delay: index * 0.1,
});
});
// Animate skill bars
gsap.utils.toArray(".skill-progress").forEach((bar) => {
const progress = bar.getAttribute("data-progress");
gsap.to(bar, {
width: `${progress}%`,
duration: 1.5,
ease: "power2.out",
scrollTrigger: {
trigger: bar,
start: "top 85%",
toggleActions: "play none none none",
},
});
});
// Button interactions
document.querySelectorAll(".btn").forEach((btn) => {
btn.addEventListener("click", (e) => {
if (btn.classList.contains("btn-docs")) {
// Scroll to about section or external link
lenis.scrollTo("#about", { duration: 1.5 });
} else if (btn.classList.contains("btn-showcase")) {
// Scroll to projects section
lenis.scrollTo("#projects", { duration: 1.5 });
}
});
});
// Card Expansion Animation
const projectCards = document.querySelectorAll(".project-card");
const body = document.body;
projectCards.forEach((card) => {
const expandedContent = card.querySelector(".card-expanded");
const closeBtn = card.querySelector(".close-card");
const cardContent = card.querySelector(
".project-title, .project-description, .project-tags, .project-number"
);
// Open card animation
card.addEventListener("click", (e) => {
// Don't trigger if clicking on expanded content or close button
if (e.target.closest(".card-expanded") || e.target.closest(".close-card")) return;
// Don't open if already open
if (expandedContent.classList.contains("active")) return;
// Prevent scroll
lenis.stop();
body.classList.add("card-open");
card.classList.add("active");
expandedContent.classList.add("active");
// Get card position
const rect = card.getBoundingClientRect();
const cardCenterX = rect.left + rect.width / 2;
const cardCenterY = rect.top + rect.height / 2;
// Get the expanded content element (not the overlay)
const expandedContentBox = expandedContent.querySelector(".expanded-content");
// Set initial transform origin
if (expandedContentBox) {
expandedContentBox.style.transformOrigin = `${cardCenterX}px ${cardCenterY}px`;
}
// Animate overlay background
gsap.fromTo(
expandedContent,
{
opacity: 0,
},
{
opacity: 1,
duration: 0.5,
ease: "power2.out",
}
);
// Animate expanded content box
if (expandedContentBox) {
gsap.fromTo(
expandedContentBox,
{
opacity: 0,
scale: 0.3,
rotation: -5,
y: 50,
},
{
opacity: 1,
scale: 1,
rotation: 0,
y: 0,
duration: 1.2,
ease: "power3.out",
}
);
}
// Animate expanded content
const expandedNumber = expandedContent.querySelector(".expanded-number");
const expandedTitle = expandedContent.querySelector(".expanded-title");
const expandedDescription = expandedContent.querySelector(".expanded-description");
const expandedFeatures = expandedContent.querySelector(".expanded-features");
const expandedLinks = expandedContent.querySelector(".expanded-links");
gsap.fromTo(
expandedNumber,
{
scale: 0,
rotation: -180,
opacity: 0,
},
{
scale: 1,
rotation: 0,
opacity: 0.2,
duration: 1,
ease: "back.out(1.7)",
delay: 0.2,
}
);
gsap.fromTo(
expandedTitle,
{
y: 50,
opacity: 0,
scale: 0.8,
},
{
y: 0,
opacity: 1,
scale: 1,
duration: 0.8,
ease: "power3.out",
delay: 0.4,
}
);
gsap.fromTo(
expandedDescription,
{
y: 30,
opacity: 0,
},
{
y: 0,
opacity: 1,
duration: 0.6,
ease: "power2.out",
delay: 0.6,
}
);
gsap.fromTo(
expandedFeatures,
{
x: -50,
opacity: 0,
},
{
x: 0,
opacity: 1,
duration: 0.8,
ease: "power3.out",
delay: 0.8,
}
);
gsap.fromTo(
expandedLinks,
{
y: 30,
opacity: 0,
},
{
y: 0,
opacity: 1,
duration: 0.6,
ease: "power2.out",
delay: 1,
}
);
// Animate close hint
const expandedCloseHint = expandedContent.querySelector(".expanded-close-hint");
if (expandedCloseHint) {
gsap.fromTo(
expandedCloseHint,
{
y: 20,
opacity: 0,
},
{
y: 0,
opacity: 1,
duration: 0.5,
ease: "power2.out",
delay: 1.2,
}
);
}
// Animate close button
gsap.fromTo(
closeBtn,
{
scale: 0,
rotation: -180,
opacity: 0,
},
{
scale: 1,
rotation: 0,
opacity: 1,
duration: 0.6,
ease: "back.out(1.7)",
delay: 0.5,
}
);
});
// Close card animation
function closeCard() {
const expandedNumber = expandedContent.querySelector(".expanded-number");
const expandedTitle = expandedContent.querySelector(".expanded-title");
const expandedDescription = expandedContent.querySelector(".expanded-description");
const expandedFeatures = expandedContent.querySelector(".expanded-features");
const expandedLinks = expandedContent.querySelector(".expanded-links");
// Animate close button
gsap.to(closeBtn, {
scale: 0,
rotation: 180,
opacity: 0,
duration: 0.3,
ease: "power2.in",
});
// Animate content out
const expandedCloseHint = expandedContent.querySelector(".expanded-close-hint");
gsap.to(
[
expandedNumber,
expandedTitle,
expandedDescription,
expandedFeatures,
expandedLinks,
expandedCloseHint,
],
{
y: -30,
opacity: 0,
duration: 0.4,
ease: "power2.in",
stagger: 0.05,
}
);
// Animate expanded content box out
const expandedContentBox = expandedContent.querySelector(".expanded-content");
if (expandedContentBox) {
gsap.to(expandedContentBox, {
opacity: 0,
scale: 0.3,
rotation: 5,
y: -50,
duration: 0.8,
ease: "power3.in",
});
}
// Animate overlay out
gsap.to(expandedContent, {
opacity: 0,
duration: 0.5,
ease: "power2.in",
delay: 0.3,
onComplete: () => {
expandedContent.classList.remove("active");
card.classList.remove("active");
body.classList.remove("card-open");
lenis.start();
// Reset transforms
const expandedCloseHint = expandedContent.querySelector(".expanded-close-hint");
gsap.set(
[
expandedNumber,
expandedTitle,
expandedDescription,
expandedFeatures,
expandedLinks,
expandedCloseHint,
closeBtn,
expandedContentBox,
],
{
clearProps: "all",
}
);
},
});
}
closeBtn.addEventListener("click", (e) => {
e.stopPropagation();
closeCard();
});
// Close on escape key
document.addEventListener("keydown", (e) => {
if (e.key === "Escape" && expandedContent.classList.contains("active")) {
closeCard();
}
});
// Close on background click (only if clicking the overlay itself, not the content)
expandedContent.addEventListener("click", (e) => {
if (e.target === expandedContent || e.target.classList.contains("card-expanded")) {
closeCard();
}
});
});
// Smooth scroll on page load
window.addEventListener("load", () => {
gsap.to(window, { duration: 0, scrollTo: 0 });
});
// Navbar functionality
const navbar = document.getElementById("navbar");
const navToggle = document.getElementById("nav-toggle");
const navMenu = document.getElementById("nav-menu");
const navLinks = document.querySelectorAll(".nav-link");
let lastScrollY = 0;
let scrollTimeout;
// Animate navbar on page load
window.addEventListener("load", () => {
setTimeout(() => {
navbar.classList.add("visible");
}, 300);
});
// Mobile menu toggle
navToggle.addEventListener("click", () => {
navMenu.classList.toggle("active");
navToggle.classList.toggle("active");
});
// Close mobile menu when clicking on a link
navLinks.forEach((link) => {
link.addEventListener("click", () => {
navMenu.classList.remove("active");
navToggle.classList.remove("active");
});
});
// Smooth scroll for nav links
navLinks.forEach((link) => {
link.addEventListener("click", (e) => {
e.preventDefault();
const targetId = link.getAttribute("href");
if (targetId.startsWith("#")) {
lenis.scrollTo(targetId, { duration: 1.5, offset: -80 });
}
});
});
// Update active nav link on scroll
function updateActiveNavLink() {
const sections = document.querySelectorAll(".section, .hero");
const scrollPos = window.scrollY + 150;
sections.forEach((section) => {
const sectionTop = section.offsetTop;
const sectionHeight = section.offsetHeight;
const sectionId = section.getAttribute("id");
if (scrollPos >= sectionTop && scrollPos < sectionTop + sectionHeight) {
navLinks.forEach((link) => {
link.classList.remove("active");
if (link.getAttribute("href") === `#${sectionId}`) {
link.classList.add("active");
}
});
}
});
}
// Navbar scroll effect with hide/show on scroll direction
function handleNavbarScroll() {
const currentScrollY = window.scrollY;
// Add scrolled class for styling
if (currentScrollY > 50) {
navbar.classList.add("scrolled");
} else {
navbar.classList.remove("scrolled");
}
// Hide/show navbar based on scroll direction
if (currentScrollY > 100) {
if (currentScrollY > lastScrollY && currentScrollY > 200) {
// Scrolling down - hide navbar
navbar.classList.remove("visible");
navbar.classList.add("hidden");
} else if (currentScrollY < lastScrollY) {
// Scrolling up - show navbar
navbar.classList.remove("hidden");
navbar.classList.add("visible");
}
} else {
// Always show at top
navbar.classList.remove("hidden");
navbar.classList.add("visible");
}
lastScrollY = currentScrollY;
updateActiveNavLink();
// Clear timeout and set new one
clearTimeout(scrollTimeout);
scrollTimeout = setTimeout(() => {
// Show navbar after scrolling stops
if (currentScrollY > 100) {
navbar.classList.remove("hidden");
navbar.classList.add("visible");
}
}, 150);
}
// Listen to scroll events
lenis.on("scroll", () => {
handleNavbarScroll();
});
// Initial check
handleNavbarScroll();<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Nova Portfolio - Smooth Scroll</title>
<link rel="stylesheet" href="style.css" />
<!-- Three.js -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script>
<!-- GSAP -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.12.2/gsap.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.12.2/ScrollTrigger.min.js"></script>
<!-- Lenis -->
<script src="https://cdn.jsdelivr.net/gh/studio-freight/lenis@1.0.29/bundled/lenis.min.js"></script>
</head>
<body>
<canvas id="canvas"></canvas>
<!-- Navbar -->
<nav class="navbar" id="navbar">
<div class="nav-container">
<div class="nav-logo">
<a href="#hero" class="logo-link">Chuangyu</a>
</div>
<ul class="nav-menu" id="nav-menu">
<li class="nav-item">
<a href="#hero" class="nav-link">HOME</a>
</li>
<li class="nav-item">
<a href="#about" class="nav-link">ABOUT</a>
</li>
<li class="nav-item">
<a href="#projects" class="nav-link">PROJECTS</a>
</li>
<li class="nav-item">
<a href="#skills" class="nav-link">SKILLS</a>
</li>
<li class="nav-item">
<a href="#contact" class="nav-link">CONTACT</a>
</li>
</ul>
<div class="nav-toggle" id="nav-toggle">
<span class="bar"></span>
<span class="bar"></span>
<span class="bar"></span>
</div>
</div>
</nav>
<div class="container">
<!-- Hero Section -->
<section class="hero" id="hero">
<h1 class="main-title">Chuangyu</h1>
<div class="subtitle-section">
<h2 class="subtitle">SMOOTH SCROLL</h2>
</div>
<div class="hero-bottom">
<div class="hero-left">
<p class="scroll-cta">SCROLL TO EXPLORE</p>
<p class="hero-description">A SMOOTH SCROLL LIBRARY FRESH OUT OF DARKROOM.ENGINEERING.</p>
</div>
<div class="hero-buttons">
<button class="btn btn-docs">
<span class="btn-icon">📚</span>
<span>DOCUMENTATION</span>
</button>
<button class="btn btn-showcase">
<span class="btn-icon">→</span>
<span>VIEW SHOWCASE</span>
</button>
</div>
</div>
</section>
<!-- About Section -->
<section class="section about" id="about">
<h2 class="section-title">ABOUT</h2>
<div class="content-grid">
<div class="content-item">
<h3>WEB DEVELOPER</h3>
<p>Passionate about creating smooth, interactive web experiences. Specialized in modern JavaScript
frameworks and creative animations.</p>
</div>
<div class="content-item">
<h3>DESIGNER</h3>
<p>Focused on dark, gothic aesthetics with modern UI/UX principles. Creating visually striking
interfaces that engage users.</p>
</div>
<div class="content-item">
<h3>CREATIVE CODER</h3>
<p>Combining technical expertise with artistic vision to build unique digital experiences that push
boundaries.</p>
</div>
</div>
</section>
<!-- Projects Section -->
<section class="section projects" id="projects">
<h2 class="section-title">PROJECTS</h2>
<div class="projects-grid">
<div class="project-card" data-card="1">
<div class="project-number">01</div>
<h3 class="project-title">DARK INTERFACE</h3>
<p class="project-description">A modern dark-themed dashboard with smooth animations and interactive
elements.</p>
<div class="project-tags">
<span class="tag">React</span>
<span class="tag">GSAP</span>
<span class="tag">Three.js</span>
</div>
<div class="card-expanded">
<button class="close-card">×</button>
<div class="expanded-content">
<div class="expanded-number">01</div>
<h2 class="expanded-title">DARK INTERFACE</h2>
<div class="expanded-details">
<p class="expanded-description">
A cutting-edge dark-themed dashboard that combines modern design principles with
smooth animations and interactive elements. Built with React for component-based
architecture, enhanced with GSAP for fluid animations, and powered by Three.js for
immersive 3D experiences.
</p>
<div class="expanded-features">
<h4>FEATURES</h4>
<ul>
<li>Real-time data visualization</li>
<li>Interactive 3D components</li>
<li>Smooth page transitions</li>
<li>Responsive design</li>
<li>Dark mode optimized</li>
</ul>
</div>
<div class="expanded-links">
<a href="#" class="expanded-link">VIEW PROJECT →</a>
<a href="#" class="expanded-link">GITHUB →</a>
</div>
<p class="expanded-close-hint">Press <span>ESC</span> or click outside to close</p>
</div>
</div>
</div>
</div>
<div class="project-card" data-card="2">
<div class="project-number">02</div>
<h3 class="project-title">SCROLL EXPERIENCE</h3>
<p class="project-description">An immersive scrolling website with parallax effects and smooth
transitions.</p>
<div class="project-tags">
<span class="tag">Chuangyu</span>
<span class="tag">GSAP</span>
<span class="tag">CSS3</span>
</div>
<div class="card-expanded">
<button class="close-card">×</button>
<div class="expanded-content">
<div class="expanded-number">02</div>
<h2 class="expanded-title">SCROLL EXPERIENCE</h2>
<div class="expanded-details">
<p class="expanded-description">
An immersive scrolling website that takes users on a journey through parallax
effects
and smooth transitions. Utilizing Chuangyu for buttery-smooth scrolling, GSAP for
complex animations, and advanced CSS3 techniques for stunning visual effects.
</p>
<div class="expanded-features">
<h4>FEATURES</h4>
<ul>
<li>Parallax scrolling effects</li>
<li>Magnetic cursor interactions</li>
<li>Scroll-triggered animations</li>
<li>Performance optimized</li>
<li>Cross-browser compatible</li>
</ul>
</div>
<div class="expanded-links">
<a href="#" class="expanded-link">VIEW PROJECT →</a>
<a href="#" class="expanded-link">GITHUB →</a>
</div>
<p class="expanded-close-hint">Press <span>ESC</span> or click outside to close</p>
</div>
</div>
</div>
</div>
<div class="project-card" data-card="3">
<div class="project-number">03</div>
<h3 class="project-title">3D PORTFOLIO</h3>
<p class="project-description">A three-dimensional portfolio showcase with interactive 3D elements.
</p>
<div class="project-tags">
<span class="tag">Three.js</span>
<span class="tag">WebGL</span>
<span class="tag">JavaScript</span>
</div>
<div class="card-expanded">
<button class="close-card">×</button>
<div class="expanded-content">
<div class="expanded-number">03</div>
<h2 class="expanded-title">3D PORTFOLIO</h2>
<div class="expanded-details">
<p class="expanded-description">
A revolutionary three-dimensional portfolio showcase featuring interactive 3D
elements
that respond to user interactions. Built with Three.js for WebGL rendering, creating
an immersive experience that pushes the boundaries of web design.
</p>
<div class="expanded-features">
<h4>FEATURES</h4>
<ul>
<li>Interactive 3D models</li>
<li>Real-time lighting effects</li>
<li>Mouse-controlled camera</li>
<li>WebGL optimization</li>
<li>VR-ready architecture</li>
</ul>
</div>
<div class="expanded-links">
<a href="#" class="expanded-link">VIEW PROJECT →</a>
<a href="#" class="expanded-link">GITHUB →</a>
</div>
<p class="expanded-close-hint">Press <span>ESC</span> or click outside to close</p>
</div>
</div>
</div>
</div>
<div class="project-card" data-card="4">
<div class="project-number">04</div>
<h3 class="project-title">ANIMATION STUDIO</h3>
<p class="project-description">A collection of creative animations and micro-interactions for web
projects.</p>
<div class="project-tags">
<span class="tag">GSAP</span>
<span class="tag">CSS</span>
<span class="tag">JavaScript</span>
</div>
<div class="card-expanded">
<button class="close-card">×</button>
<div class="expanded-content">
<div class="expanded-number">04</div>
<h2 class="expanded-title">ANIMATION STUDIO</h2>
<div class="expanded-details">
<p class="expanded-description">
A comprehensive collection of creative animations and micro-interactions designed
for
modern web projects. Showcasing the power of GSAP, advanced CSS techniques, and
vanilla JavaScript to create stunning visual experiences.
</p>
<div class="expanded-features">
<h4>FEATURES</h4>
<ul>
<li>50+ animation presets</li>
<li>Custom easing functions</li>
<li>Micro-interaction library</li>
<li>Performance optimized</li>
<li>Easy to integrate</li>
</ul>
</div>
<div class="expanded-links">
<a href="#" class="expanded-link">VIEW PROJECT →</a>
<a href="#" class="expanded-link">GITHUB →</a>
</div>
<p class="expanded-close-hint">Press <span>ESC</span> or click outside to close</p>
</div>
</div>
</div>
</div>
</div>
</section>
<!-- Skills Section -->
<section class="section skills" id="skills">
<h2 class="section-title">SKILLS</h2>
<div class="skills-list">
<div class="skill-item">
<span class="skill-name">JavaScript</span>
<div class="skill-bar">
<div class="skill-progress" data-progress="90"></div>
</div>
</div>
<div class="skill-item">
<span class="skill-name">React</span>
<div class="skill-bar">
<div class="skill-progress" data-progress="85"></div>
</div>
</div>
<div class="skill-item">
<span class="skill-name">Three.js</span>
<div class="skill-bar">
<div class="skill-progress" data-progress="80"></div>
</div>
</div>
<div class="skill-item">
<span class="skill-name">GSAP</span>
<div class="skill-bar">
<div class="skill-progress" data-progress="88"></div>
</div>
</div>
<div class="skill-item">
<span class="skill-name">CSS3</span>
<div class="skill-bar">
<div class="skill-progress" data-progress="92"></div>
</div>
</div>
</div>
</section>
<!-- Contact Section -->
<section class="section contact" id="contact">
<h2 class="section-title">CONTACT</h2>
<div class="contact-content">
<p class="contact-text">Ready to create something amazing together?</p>
<div class="contact-info">
<a href="mailto:contact@darkroom.engineering" class="contact-link">contact@darkroom.engineering</a>
<div class="social-links">
<a href="#" class="social-link">GitHub</a>
<a href="#" class="social-link">LinkedIn</a>
<a href="#" class="social-link">Twitter</a>
</div>
</div>
</div>
</section>
</div>
<script src="script.js"></script>
</body>
</html>Nova Portfolio
A sophisticated dark-themed portfolio website featuring smooth scroll animations powered by Lenis, interactive Three.js particle backgrounds, expandable project cards with GSAP animations, and a responsive navigation system.
How it works
The portfolio combines multiple advanced web technologies:
- Lenis Smooth Scroll — Provides buttery-smooth scrolling experience
- Three.js Particles — Animated starfield background with pink and white particles
- GSAP Animations — Scroll-triggered animations and card expansion effects
- Expandable Cards — Click-to-expand project cards with detailed views
- Responsive Navigation — Auto-hiding navbar that responds to scroll direction
Key features
- Smooth scroll with Lenis integration
- Three.js particle background with animated stars
- GSAP-powered scroll-triggered animations
- Expandable project cards with detailed views
- Skill progress bars with animated fills
- Responsive mobile navigation
- Dark gothic aesthetic with pink accents
When to use it
- Personal portfolio websites
- Creative agency showcases
- Developer portfolios
- Design studio presentations
- Interactive portfolio galleries