From b5c2cc831da7e121e211ee1ccf6d760600a1f8e7 Mon Sep 17 00:00:00 2001 From: Jayavel2005 Date: Fri, 22 May 2026 22:55:39 +0530 Subject: [PATCH 1/3] feat(navbar): add responsive animated sidebar navigation --- revati-kadam-portfolio/css/style.css | 388 ++++++++++++++++++++---- revati-kadam-portfolio/index.html | 57 +++- revati-kadam-portfolio/js/navigation.js | 272 ++++++++++++++--- 3 files changed, 598 insertions(+), 119 deletions(-) diff --git a/revati-kadam-portfolio/css/style.css b/revati-kadam-portfolio/css/style.css index 2b76e2b..a661a02 100644 --- a/revati-kadam-portfolio/css/style.css +++ b/revati-kadam-portfolio/css/style.css @@ -39,63 +39,313 @@ body { /* ── NAVBAR ── */ .navbar { position: fixed; - top: 0; left: 0; + top: 0; left: 0; right: 0; + margin: 0 auto; width: 100%; - height: 64px; - background: rgba(10, 17, 32, 0.9); - backdrop-filter: blur(14px); - border-bottom: 1px solid var(--border); + height: 80px; + background: transparent; + z-index: 1000; + display: flex; + justify-content: center; + transition: all 0.4s cubic-bezier(0.16, 1, 0.3, 1); + will-change: transform, background-color, backdrop-filter, box-shadow, height, width, top, border-radius; +} + +.navbar.scrolled { + top: 20px; + width: calc(100% - 40px); + max-width: 1200px; + height: 70px; + background: rgba(15, 23, 42, 0.65); + backdrop-filter: blur(24px); + -webkit-backdrop-filter: blur(24px); + box-shadow: 0 15px 40px -10px rgba(0,0,0,0.5), inset 0 1px 1px rgba(255,255,255,0.15); + border-radius: 40px; + border: 1px solid rgba(255, 255, 255, 0.08); +} + +.nav-bg-gradient { + position: absolute; + inset: 0; + background: linear-gradient(90deg, rgba(59, 130, 246, 0.08) 0%, transparent 50%, rgba(59, 130, 246, 0.08) 100%); + opacity: 0; + transition: opacity 0.4s ease; + pointer-events: none; + border-radius: inherit; +} + +.navbar.scrolled .nav-bg-gradient { + opacity: 1; +} + +.nav-container { display: flex; justify-content: space-between; align-items: center; + width: 100%; + max-width: 1440px; + height: 100%; padding: 0 40px; - z-index: 1000; + position: relative; + transition: padding 0.4s ease; +} + +.navbar.scrolled .nav-container { + padding: 0 30px; } .logo { - font-size: 20px; + font-size: 26px; font-weight: 700; + color: var(--text); + letter-spacing: 1px; + z-index: 1001; + cursor: pointer; +} + +.logo-accent { color: var(--primary); } -.hamburger { - font-size: 26px; - cursor: pointer; - color: var(--text); - display: none; - transition: color 0.2s; +.desktop-nav { + display: flex; + align-items: center; } -.hamburger:hover { color: var(--primary); } .nav-links { list-style: none; display: flex; flex-direction: row; - gap: 4px; - padding: 0; - margin: 0; + gap: 8px; + position: relative; +} + +.nav-indicator { + position: absolute; + top: 50%; + transform: translateY(-50%); + height: 40px; + background: rgba(59, 130, 246, 0.15); + border: 1px solid rgba(59, 130, 246, 0.3); + border-radius: 20px; + z-index: 0; + pointer-events: none; + opacity: 0; /* Updated by GSAP */ + box-shadow: 0 0 15px rgba(59, 130, 246, 0.2); + will-change: transform, width, opacity; } .nav-links li { + position: relative; + z-index: 1; +} + +.nav-link { + color: var(--text-muted-1); + text-decoration: none; + font-size: 15px; + font-weight: 600; + letter-spacing: 0.5px; + padding: 10px 20px; + display: block; + transition: color 0.3s ease, text-shadow 0.3s ease; + position: relative; + border-radius: 20px; +} + +.nav-link::after { + display: none; +} + +.nav-link:hover, .nav-link.active { + color: #fff; + text-shadow: 0 0 10px rgba(255, 255, 255, 0.3); +} + +.hamburger { + display: none; + background: transparent; + border: none; cursor: pointer; + padding: 10px; + z-index: 1001; + width: 44px; + height: 44px; + position: relative; +} + +.hamburger .line { + display: block; + width: 24px; + height: 2px; + background-color: var(--text); + margin-bottom: 5px; + border-radius: 2px; + transition: transform 0.3s ease, opacity 0.3s ease, background-color 0.3s ease; + transform-origin: center; + position: absolute; + left: 10px; +} + +.hamburger .line-1 { top: 14px; } +.hamburger .line-2 { top: 21px; } +.hamburger .line-3 { top: 28px; } + +.hamburger.is-active .line-1 { transform: translateY(7px) rotate(45deg); } +.hamburger.is-active .line-2 { opacity: 0; } +.hamburger.is-active .line-3 { transform: translateY(-7px) rotate(-45deg); } + +.nav-overlay { + position: fixed; + inset: 0; + background: rgba(0, 0, 0, 0.4); + backdrop-filter: blur(6px); + -webkit-backdrop-filter: blur(6px); + z-index: 9998; + opacity: 0; + visibility: hidden; + pointer-events: none; +} + +.sidebar-drawer { + position: fixed; + top: 0; right: 0; + width: 80vw; + max-width: 340px; + height: 100vh; + background: rgba(10, 15, 30, 0.95); + backdrop-filter: blur(20px); + -webkit-backdrop-filter: blur(20px); + z-index: 9999; + transform: translateX(100%); + display: flex; + flex-direction: column; + padding: 40px 30px; + border-left: 1px solid rgba(255, 255, 255, 0.05); + will-change: transform; +} + +.sidebar-header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 60px; +} + +.sidebar-logo { + font-size: 22px; + font-weight: 700; color: var(--text); - font-size: 15px; - padding: 7px 16px; - border-radius: 8px; - transition: background 0.2s, color 0.2s; - white-space: nowrap; } -.nav-links li a { + +.close-sidebar { + background: transparent; + border: none; color: var(--text); + font-size: 36px; + cursor: pointer; + line-height: 1; + transition: color 0.3s ease; +} + +.close-sidebar:hover { + color: var(--primary); +} + +.sidebar-links { + list-style: none; + display: flex; + flex-direction: column; + gap: 2rem; + margin: 0; + padding: 0; + flex: 1; +} + +.sidebar-link { + color: var(--text-muted-1); text-decoration: none; - display: block; - width: 100%; - height: 100%; + font-size: clamp(1.3rem, 3vw, 1.8rem); + font-weight: 500; + min-height: 56px; + display: flex; + align-items: center; + position: relative; + padding: 0 20px; + margin-bottom: 8px; + border-radius: 16px; + transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); + overflow: hidden; + will-change: transform, background, color; } -.nav-links li:hover, -.nav-links li:hover a { + +.sidebar-link::after { + content: ''; + position: absolute; + inset: 0; + background: linear-gradient(90deg, rgba(59, 130, 246, 0.15), transparent); + opacity: 0; + transition: opacity 0.3s ease; + border-radius: 16px; + z-index: -1; +} + +.sidebar-link::before { + content: ''; + position: absolute; + left: 0; + top: 50%; + transform: translateY(-50%); + height: 0; + width: 4px; background: var(--primary); + border-radius: 0 4px 4px 0; + opacity: 0; + transition: height 0.4s cubic-bezier(0.4, 0, 0.2, 1), opacity 0.3s ease; + box-shadow: 0 0 10px rgba(59, 130, 246, 0); +} + +.sidebar-link:hover, .sidebar-link.active { color: #fff; + transform: translateX(8px); + text-shadow: 0 0 15px rgba(255, 255, 255, 0.3); +} + +.sidebar-link:hover::after, .sidebar-link.active::after { + opacity: 1; +} + +.sidebar-link:hover::before, .sidebar-link.active::before { + height: 60%; + opacity: 1; + box-shadow: 0 0 10px rgba(59, 130, 246, 0.6); +} + +.sidebar-social { + display: flex; + gap: 20px; + margin-top: auto; +} + +.sidebar-social a { + color: var(--text-muted-2); + text-decoration: none; + font-weight: 600; + font-size: 15px; + transition: color 0.3s ease; +} + +.sidebar-social a:hover { + color: var(--primary); +} + +.nav-link:focus-visible, +.sidebar-link:focus-visible, +.hamburger:focus-visible, +.close-sidebar:focus-visible { + outline: 2px solid var(--primary); + outline-offset: 4px; + border-radius: 4px; } /* ── SECTION BASE ── */ @@ -1100,26 +1350,24 @@ form button:disabled { .section { opacity: 1; transform: none; transition: none; } } +/* ── RESPONSIVE STYLES ── */ +@media (max-width: 1024px) { + .nav-container { padding: 0 40px; } + .nav-links { gap: 10px; } + .nav-link { padding: 8px 16px; font-size: 15px; } + .nav-indicator { height: 38px; border-radius: 20px; } + .logo { font-size: 24px; } +} + /* ── MOBILE ── */ -@media (max-width: 768px) { - .navbar { padding: 0 20px; } +@media (max-width: 767px) { + .nav-container { padding: 0 24px; } .hamburger { display: block; } - .nav-links { - position: absolute; - top: 64px; right: 20px; - background: var(--card); - backdrop-filter: blur(12px); - border: 1px solid var(--border); - border-radius: 12px; - flex-direction: column; - padding: 16px; - width: 180px; - box-shadow: 0 10px 30px var(--shadow); - display: none; - } + .desktop-nav { display: none; } - .nav-links.show { display: flex; } + /* Adjust Logo for Mobile */ + .logo { font-size: 22px; } #home { padding: 100px 24px 60px; } .section { padding: 70px 24px; } @@ -1140,9 +1388,16 @@ form button:disabled { .settings { bottom: 16px; right: 16px; } } -@media (min-width: 769px) { +/* ── VERY SMALL MOBILE ── */ +@media (max-width: 380px) { + .nav-container { padding: 0 16px; } + .logo { font-size: 20px; } + .hamburger { transform: scale(0.9); } + .nav-link { font-size: 22px; } +} + +@media (min-width: 768px) { .hamburger { display: none; } - .nav-links { display: flex !important; } } /* ══════════════════════════════════════════ LIGHT THEME OVERRIDES @@ -1162,28 +1417,39 @@ body.light-theme { } /* Navbar stays light-background-aware */ -body.light-theme .navbar { - background: rgba(248,250,252,0.95); - border-bottom: 1px solid rgba(0,0,0,0.1); +body.light-theme .navbar.scrolled { + background: rgba(248, 250, 252, 0.85); + border: 1px solid rgba(0, 0, 0, 0.08); + box-shadow: 0 15px 40px -10px rgba(0,0,0,0.1), inset 0 1px 1px rgba(255,255,255,0.8); } -/* Nav links: dark text on light bg, white on hover/active */ -body.light-theme .nav-links li { - color: #0f172a; +body.light-theme .nav-overlay { + background: rgba(255, 255, 255, 0.3); } -body.light-theme .nav-links li:hover, -body.light-theme .nav-links li.nav-active { - background: var(--primary); - color: #fff; + +body.light-theme .sidebar-drawer { + background: rgba(248, 250, 252, 0.98); + border-left: 1px solid rgba(0, 0, 0, 0.05); } -body.light-theme .hamburger { + +/* Nav links: dark text on light bg, white on hover/active */ +body.light-theme .nav-link, +body.light-theme .sidebar-link { + color: var(--text-muted-1); +} +body.light-theme .nav-link:hover, +body.light-theme .nav-link.active { color: #0f172a; + text-shadow: 0 0 10px rgba(0, 0, 0, 0.1); } -/* Mobile dropdown bg */ -body.light-theme .nav-links { - background: #f1f5f9; - border-color: rgba(0,0,0,0.1); +body.light-theme .sidebar-link:hover, +body.light-theme .sidebar-link.active { + color: #0f172a; + text-shadow: 0 0 15px rgba(0, 0, 0, 0.1); +} +body.light-theme .hamburger .line { + background-color: #0f172a; } /* Section headings (About, Projects, etc.) stay dark, not pure black */ diff --git a/revati-kadam-portfolio/index.html b/revati-kadam-portfolio/index.html index 1bf2a0c..bf615d6 100644 --- a/revati-kadam-portfolio/index.html +++ b/revati-kadam-portfolio/index.html @@ -7,23 +7,60 @@ +
- + @@ -63,39 +69,163 @@
-
-
-
-

HELLO!

-

I am Revati Kadam

-

I am a

-

- A passionate Computer Science student who loves building clean, - responsive web apps and solving real-world problems through code. -

-
- View Projects - Download Resume +
+ + +
+
+
+
+
+ + +
+ +
+ +
+
+ + + WELCOME TO MY PORTFOLIO + +
+ +

+ I'm Revati
Kadam. +

+ +
+ I build digital| +
+ +

+ A passionate Computer Science student who loves building clean, + responsive web apps and solving real-world problems through code. +

+ + + +
+
CONNECT WITH ME
+ +
-
- - GitHub - - - LinkedIn - - - LeetCode - + + +
+ +
+ + +
+
+
+
+
+
+
+
+
+
+ + +
+
+
+ Revati Kadam — Portfolio +
+
+ + +
+ +
+ + +
+
+ +
+
+ STATUS + Available for Opportunities +
+
-
-
-
- Rkimg + + +
+
+
+ +
+
+ 15+ + Projects Completed +
+
+
+
+
+ +
+
+ 2+ + Years Coding +
+
+
+
+
+
-
+
+ 10+ + Technologies +
+
+
+
+
+ +
+
+ Web / DSA + Current Focus +
+
+ + +
+ Scroll Down +
+
+
+
+
@@ -252,5 +382,6 @@

Contact Me

+ \ No newline at end of file diff --git a/revati-kadam-portfolio/js/hero.js b/revati-kadam-portfolio/js/hero.js new file mode 100644 index 0000000..9f60dea --- /dev/null +++ b/revati-kadam-portfolio/js/hero.js @@ -0,0 +1,294 @@ +/* ═══════════════════════════════════════════ + HERO V2 — Immersive Motion Engine (GSAP & Canvas) + Staggered Load, Orbital Physics, Magnetic Hooks, Canvas Repulsion + ═══════════════════════════════════════════ */ + +const prefersReduced = window.matchMedia('(prefers-reduced-motion: reduce)').matches; +const isMobile = window.matchMedia('(max-width: 767px)').matches; + +document.addEventListener('DOMContentLoaded', () => { + setTimeout(initHeroV2, 80); +}); + +function initHeroV2() { + if (prefersReduced) { + showAllInstant(); + return; + } + + // 1. Entrance Staggered Orchestration + const tl = gsap.timeline({ defaults: { ease: 'power3.out' } }); + + // Activate glows and meshes + tl.to('.hv2-radial--a', { opacity: 1, duration: 2.0 }, 0.0) + .to('.hv2-radial--b', { opacity: 1, duration: 2.0 }, 0.2) + .to('.hv2-radial--c', { opacity: 1, duration: 1.5 }, 0.4); + + // Stagger Header Navigation items + tl.fromTo('.navbar .logo', + { y: -20, opacity: 0 }, + { y: 0, opacity: 1, duration: 0.8 }, 0.2); + + tl.fromTo('.desktop-nav .nav-link', + { y: -20, opacity: 0 }, + { y: 0, opacity: 1, duration: 0.6, stagger: 0.08 }, 0.3); + + tl.fromTo('.nav-actions', + { y: -20, opacity: 0 }, + { y: 0, opacity: 1, duration: 0.8 }, 0.5); + + // Left Content Text Elements + tl.fromTo('.hv2-eyebrow-wrap', + { y: 24, opacity: 0 }, + { y: 0, opacity: 1, duration: 0.8 }, 0.4); + + tl.fromTo('.hv2-heading', + { y: 35, opacity: 0 }, + { y: 0, opacity: 1, duration: 1.0 }, 0.6); + + tl.fromTo('.hv2-typing-wrap', + { y: 20, opacity: 0 }, + { y: 0, opacity: 1, duration: 0.8 }, 0.95); + + tl.fromTo('.hv2-desc', + { y: 20, opacity: 0 }, + { y: 0, opacity: 1, duration: 0.8 }, 1.1); + + tl.fromTo('#hv2CTA', + { y: 20, opacity: 0 }, + { y: 0, opacity: 1, duration: 0.8 }, 1.25); + + tl.fromTo('#hv2Social', + { y: 15, opacity: 0 }, + { y: 0, opacity: 1, duration: 0.8 }, 1.35); + + // Right Centerpiece Portrait Scene + tl.fromTo('#hv2Portrait', + { scale: 0.88, opacity: 0, rotationY: -15, y: 30 }, + { scale: 1, opacity: 1, rotationY: 0, y: 0, duration: 1.5, ease: 'power4.out' }, + 0.85); + + tl.fromTo('#statusBadge', + { y: 20, opacity: 0, scale: 0.85 }, + { y: 0, opacity: 1, scale: 1, duration: 0.8, ease: 'back.out(1.4)' }, + 1.4); + + // Bottom Stats Panel + tl.fromTo('#heroStatsPanel', + { y: 30, opacity: 0 }, + { y: 0, opacity: 1, duration: 1.0, ease: 'power3.out' }, + 1.4); + + // Scroll Down Indicator + tl.fromTo('#scrollIndicator', + { y: 15, opacity: 0 }, + { y: 0, opacity: 1, duration: 0.8 }, + 1.6); + + // Loop & Ambient Handlers Initialization + tl.add(() => { + initOrbitRotation(); + if (!isMobile) { + initParallax(); + initMagneticInteractivity(); + } + initCanvasMesh(); + }, 1.8); +} + +function showAllInstant() { + gsap.set([ + '.hv2-radial', '.navbar .logo', '.desktop-nav .nav-link', '.nav-actions', + '.hv2-eyebrow-wrap', '.hv2-heading', '.hv2-typing-wrap', '.hv2-desc', + '#hv2CTA', '#hv2Social', '#hv2Portrait', '#statusBadge', + '#heroStatsPanel', '#scrollIndicator' + ], { opacity: 1, y: 0, scale: 1, rotationY: 0, skewX: 0 }); +} + +/* ── GSAP CONTINUOUS ORBIT ROTATION ── */ +function initOrbitRotation() { + gsap.to('#portraitOrbital', { + rotation: 360, + duration: 20, + repeat: -1, + ease: 'none' + }); + gsap.to('#portraitOrbital2', { + rotation: -360, + duration: 30, + repeat: -1, + ease: 'none' + }); +} + +/* ── MULTI-DEPTH MOUSE PARALLAX ── */ +function initParallax() { + const scene = document.querySelector('.hero-v2'); + const chassis = document.getElementById('portraitChassis'); + const glow = document.getElementById('portraitGlow'); + const img = document.getElementById('portraitImg'); + const badge = document.getElementById('statusBadge'); + + if (!scene || !chassis) return; + + let mx = 0, my = 0, raf = null; + + function update() { + gsap.to(chassis, { + rotationY: mx * 12, rotationX: -my * 10, + x: mx * 12, y: my * 12, + duration: 1.2, ease: 'power2.out', overwrite: 'auto' + }); + gsap.to(img, { + x: -mx * 10, y: -my * 8, + duration: 1.4, ease: 'power2.out', overwrite: 'auto' + }); + gsap.to(glow, { + x: mx * 30, y: my * 24, + duration: 1.8, ease: 'power2.out', overwrite: 'auto' + }); + gsap.to(badge, { + x: mx * 20, y: my * 15, + duration: 1.0, ease: 'power2.out', overwrite: 'auto' + }); + raf = null; + } + + scene.addEventListener('mousemove', e => { + const r = scene.getBoundingClientRect(); + mx = ((e.clientX - r.left) / r.width - 0.5) * 2; + my = ((e.clientY - r.top) / r.height - 0.5) * 2; + if (!raf) raf = requestAnimationFrame(update); + }); + + scene.addEventListener('mouseleave', () => { + mx = 0; my = 0; + gsap.to([chassis, img, glow, badge], { + x: 0, y: 0, rotationX: 0, rotationY: 0, + duration: 1.6, ease: 'elastic.out(1, 0.35)', overwrite: 'auto' + }); + }); +} + +/* ── MAGNETIC HOOKS INTERACTIVITY ── */ +function initMagneticInteractivity() { + const magneticElements = document.querySelectorAll('.magnetic-btn, .hv2-circular-frame, .hv2-status-card'); + + magneticElements.forEach(elem => { + elem.addEventListener('mousemove', e => { + const r = elem.getBoundingClientRect(); + const pullStrength = elem.classList.contains('hv2-circular-frame') ? 0.08 : 0.22; + const dx = (e.clientX - r.left - r.width / 2) * pullStrength; + const dy = (e.clientY - r.top - r.height / 2) * pullStrength; + + gsap.to(elem, { + x: dx, + y: dy, + duration: 0.4, + ease: 'power2.out', + overwrite: 'auto' + }); + }); + + elem.addEventListener('mouseleave', () => { + gsap.to(elem, { + x: 0, + y: 0, + duration: 0.9, + ease: 'elastic.out(1, 0.3)', + overwrite: 'auto' + }); + }); + }); +} + +/* ── KINETIC CANVAS PARTICLE MESH (Repulsion Physics) ── */ +function initCanvasMesh() { + const canvas = document.getElementById('heroCanvas'); + if (!canvas) return; + const ctx = canvas.getContext('2d'); + let particles = []; + const spacing = 40; + let mouse = { x: -1000, y: -1000 }; + let animationFrameId; + + function resizeCanvas() { + canvas.width = canvas.parentElement.offsetWidth; + canvas.height = canvas.parentElement.offsetHeight; + generateGrid(); + } + + function generateGrid() { + particles = []; + const cols = Math.floor(canvas.width / spacing) + 2; + const rows = Math.floor(canvas.height / spacing) + 2; + + // We only generate grid points for the bottom half to create a floor grid wave + const startRow = Math.floor(rows * 0.55); + + for (let r = startRow; r < rows; r++) { + for (let c = 0; c < cols; c++) { + particles.push({ + baseX: c * spacing, + baseY: r * spacing, + x: c * spacing, + y: r * spacing, + vx: 0, + vy: 0 + }); + } + } + } + + window.addEventListener('resize', resizeCanvas); + resizeCanvas(); + + const scene = document.querySelector('.hero-v2'); + scene.addEventListener('mousemove', e => { + const r = canvas.getBoundingClientRect(); + mouse.x = e.clientX - r.left; + mouse.y = e.clientY - r.top; + }); + + scene.addEventListener('mouseleave', () => { + mouse.x = -1000; + mouse.y = -1000; + }); + + function animate() { + ctx.clearRect(0, 0, canvas.width, canvas.height); + ctx.fillStyle = 'rgba(99, 102, 241, 0.25)'; + + particles.forEach(p => { + const dx = mouse.x - p.x; + const dy = mouse.y - p.y; + const dist = Math.sqrt(dx * dx + dy * dy); + const maxDist = 180; + + if (dist < maxDist) { + const force = (maxDist - dist) / maxDist; + p.vx -= (dx / dist) * force * 1.2; + p.vy -= (dy / dist) * force * 1.2; + } + + // Spring physics back to grid coordinate anchor + p.vx += (p.baseX - p.x) * 0.05; + p.vy += (p.baseY - p.y) * 0.05; + + // Friction damping + p.vx *= 0.83; + p.vy *= 0.83; + + p.x += p.vx; + p.y += p.vy; + + ctx.beginPath(); + ctx.arc(p.x, p.y, 1.5, 0, Math.PI * 2); + ctx.fill(); + }); + + animationFrameId = requestAnimationFrame(animate); + } + + animate(); +} diff --git a/revati-kadam-portfolio/js/navigation.js b/revati-kadam-portfolio/js/navigation.js index a6b64ff..20e16bd 100644 --- a/revati-kadam-portfolio/js/navigation.js +++ b/revati-kadam-portfolio/js/navigation.js @@ -60,10 +60,12 @@ function updateActiveIndicator(activeId) { if (window.innerWidth >= 768 && link.classList.contains("nav-link")) { const linkRect = link.getBoundingClientRect(); const navRect = document.getElementById("desktopNavLinks").getBoundingClientRect(); + const dotWidth = 6; + const centerX = (linkRect.left - navRect.left) + (linkRect.width / 2) - (dotWidth / 2); gsap.to(navIndicator, { - x: linkRect.left - navRect.left, - width: linkRect.width, + x: centerX, + width: dotWidth, opacity: 1, duration: 0.4, ease: "power3.out", @@ -99,10 +101,12 @@ desktopNavLinks.forEach(link => { if (window.innerWidth >= 768) { const linkRect = link.getBoundingClientRect(); const navRect = document.getElementById("desktopNavLinks").getBoundingClientRect(); + const dotWidth = 6; + const centerX = (linkRect.left - navRect.left) + (linkRect.width / 2) - (dotWidth / 2); gsap.to(navIndicator, { - x: linkRect.left - navRect.left, - width: linkRect.width, + x: centerX, + width: dotWidth, opacity: 1, duration: 0.3, ease: "power2.out", @@ -125,9 +129,12 @@ if (desktopNavContainer) { if (activeLink) { const linkRect = activeLink.getBoundingClientRect(); const navRect = desktopNavContainer.getBoundingClientRect(); + const dotWidth = 6; + const centerX = (linkRect.left - navRect.left) + (linkRect.width / 2) - (dotWidth / 2); + gsap.to(navIndicator, { - x: linkRect.left - navRect.left, - width: linkRect.width, + x: centerX, + width: dotWidth, opacity: 1, duration: 0.4, ease: "power3.out", diff --git a/revati-kadam-portfolio/js/script.js b/revati-kadam-portfolio/js/script.js index 660aad5..96a708a 100644 --- a/revati-kadam-portfolio/js/script.js +++ b/revati-kadam-portfolio/js/script.js @@ -2,13 +2,15 @@ function showSection(id) { document.getElementById(id).scrollIntoView({ behavior: "smooth" }); } -const roles = ["Web Developer", "Programmer", "Problem Solver", "Website Designer", "Tech Enthusiast"]; +const roles = ["digital products", "web applications", "modern websites", "interactive UIs", "clean code"]; let i = 0, j = 0, del = false; function type() { + const el = document.getElementById("typing"); + if (!el) return; // Hero V2 uses role drum instead let text = roles[i]; j = del ? j - 1 : j + 1; - document.getElementById("typing").innerText = text.substring(0, j); + el.innerText = text.substring(0, j); if (!del && j === text.length) { del = true; return setTimeout(type, 1500); @@ -21,6 +23,7 @@ function type() { } type(); + (function() { const saved = localStorage.getItem("theme"); if (saved === "light") document.body.classList.add("light-theme"); From 1a08d4c5c3a1f4b0f5208a73926e1e2b1fb41b4e Mon Sep 17 00:00:00 2001 From: Jayavel2005 Date: Sat, 23 May 2026 00:10:51 +0530 Subject: [PATCH 3/3] feat(preloader): implement smooth loading animation and transitions --- revati-kadam-portfolio/css/style.css | 269 ++++++++++++++++++++++++- revati-kadam-portfolio/index.html | 68 ++++++- revati-kadam-portfolio/js/hero.js | 5 +- revati-kadam-portfolio/js/preloader.js | 210 +++++++++++++++++++ 4 files changed, 547 insertions(+), 5 deletions(-) create mode 100644 revati-kadam-portfolio/js/preloader.js diff --git a/revati-kadam-portfolio/css/style.css b/revati-kadam-portfolio/css/style.css index 1c74105..964ef79 100644 --- a/revati-kadam-portfolio/css/style.css +++ b/revati-kadam-portfolio/css/style.css @@ -2064,4 +2064,271 @@ body.light-theme .hv2-btn--ghost { body.light-theme .hv2-btn--ghost:hover { background: rgba(0, 0, 0, 0.05); border-color: #0f172a; -} \ No newline at end of file +} + +/* ================= PRELOADER ANIMATION ================= */ +body.loading { + overflow: hidden; + height: 100vh; +} + +#preloader { + position: fixed; + inset: 0; + z-index: 9999; + background: transparent; + display: flex; + align-items: center; + justify-content: center; + overflow: hidden; +} + +.preloader-curtain { + position: absolute; + top: 0; + height: 100%; + width: 56vw; /* Overlap slightly for skews */ + will-change: transform; +} + +.preloader-curtain--left { + left: 0; + transform-origin: top left; +} + +.preloader-curtain--right { + right: 0; + transform-origin: bottom right; +} + +/* Three separate layers of panels */ +.preloader-curtain--layer1 { + background: #030712; + z-index: 3; +} + +.preloader-curtain--layer2 { + background: linear-gradient(135deg, var(--primary), #6366f1, #a855f7); + z-index: 2; +} + +.preloader-curtain--layer3 { + background: rgba(15, 23, 42, 0.45); + backdrop-filter: blur(15px); + -webkit-backdrop-filter: blur(15px); + z-index: 1; +} + +.preloader-curtain--left.preloader-curtain--layer3 { + border-right: 1px solid rgba(255, 255, 255, 0.05); +} + +.preloader-curtain--right.preloader-curtain--layer3 { + border-left: 1px solid rgba(255, 255, 255, 0.05); +} + +/* Background glows for the loader */ +.preloader-radial { + position: absolute; + border-radius: 50%; + filter: blur(100px); + pointer-events: none; + opacity: 0.7; + z-index: 4; /* Render on top of curtains but under text content */ +} +.preloader-radial--1 { + width: 400px; + height: 400px; + top: -10%; + left: -10%; + background: rgba(99, 102, 241, 0.15); +} +.preloader-radial--2 { + width: 500px; + height: 500px; + bottom: -15%; + right: -10%; + background: rgba(168, 85, 247, 0.12); +} + +.preloader-content { + display: flex; + flex-direction: column; + align-items: center; + position: relative; + z-index: 10; + width: 90%; + max-width: 450px; +} + +.preloader-logo { + font-size: clamp(24px, 4vw, 36px); + font-weight: 700; + letter-spacing: 2px; + color: #fff; + margin-bottom: 28px; + display: flex; + overflow: hidden; +} + +/* Character animation defaults */ +.pl-char, .pl-char-accent, .pl-space { + display: inline-block; + opacity: 0; + transform: translateY(30px); + will-change: transform, opacity; +} + +.pl-char-accent { + color: var(--primary); + text-shadow: 0 0 10px rgba(59, 130, 246, 0.6); +} + +/* Dashboard & Circle Progress Ring */ +.preloader-dashboard { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; +} + +.preloader-circle-wrapper { + position: relative; + width: 140px; + height: 140px; + display: flex; + align-items: center; + justify-content: center; + margin-bottom: 16px; +} + +.preloader-svg-circle { + position: absolute; + inset: 0; + width: 100%; + height: 100%; + transform: rotate(-90deg); /* Start trace at 12 o'clock */ +} + +.circle-bg { + fill: none; + stroke: rgba(255, 255, 255, 0.03); + stroke-width: 3; +} + +.circle-progress { + fill: none; + stroke: var(--primary); + stroke-width: 4; + stroke-linecap: round; + stroke-dasharray: 282.7; + stroke-dashoffset: 282.7; + filter: drop-shadow(0 0 6px var(--primary)); + will-change: stroke-dashoffset; +} + +/* Odometer mechanical rolling percentage ticker */ +.preloader-percentage { + position: relative; + z-index: 2; +} + +.ticker-wrapper { + display: flex; + align-items: flex-start; + height: 36px; + overflow: hidden; + line-height: 36px; + font-size: 32px; + font-weight: 700; + color: #fff; + font-family: monospace; +} + +.ticker-column { + display: flex; + flex-direction: column; + will-change: transform; +} + +.ticker-column--hundreds { + opacity: 0; + width: 0; + transition: opacity 0.3s cubic-bezier(0.25, 1, 0.5, 1), width 0.3s cubic-bezier(0.25, 1, 0.5, 1); +} + +.ticker-column--hundreds.visible { + opacity: 1; + width: 20px; +} + +.ticker-column span { + display: block; + height: 36px; + text-align: center; +} + +.ticker-percent { + color: var(--primary); + font-size: 18px; + margin-left: 2px; + align-self: flex-start; + line-height: 28px; + font-weight: 600; + text-shadow: 0 0 6px rgba(59, 130, 246, 0.4); +} + +/* Dynamic status telemetry log */ +.preloader-status { + font-family: monospace; + font-size: 10px; + color: var(--text-muted-2); + letter-spacing: 2px; + text-transform: uppercase; + height: 16px; + min-width: 280px; + text-align: center; + opacity: 0.8; + text-shadow: 0 0 5px rgba(255, 255, 255, 0.05); + transition: opacity 0.15s ease; +} + +/* ── Light Theme Preloader Overrides ── */ +body.light-theme .preloader-curtain--layer1 { + background: #f8fafc; +} + +body.light-theme .preloader-curtain--layer3 { + background: rgba(241, 245, 249, 0.45); +} + +body.light-theme .preloader-curtain--left.preloader-curtain--layer3 { + border-right: 1px solid rgba(0, 0, 0, 0.05); +} + +body.light-theme .preloader-curtain--right.preloader-curtain--layer3 { + border-left: 1px solid rgba(0, 0, 0, 0.05); +} + +body.light-theme .preloader-radial--1 { + background: rgba(59, 130, 246, 0.08); +} +body.light-theme .preloader-radial--2 { + background: rgba(165, 180, 252, 0.06); +} +body.light-theme .preloader-logo { + color: #0f172a; +} +body.light-theme .pl-char-accent { + color: var(--primary); + text-shadow: 0 0 8px rgba(59, 130, 246, 0.2); +} +body.light-theme .circle-bg { + stroke: rgba(0, 0, 0, 0.04); +} +body.light-theme .ticker-wrapper { + color: #0f172a; +} +body.light-theme .preloader-status { + color: var(--text-muted-1); +} \ No newline at end of file diff --git a/revati-kadam-portfolio/index.html b/revati-kadam-portfolio/index.html index c5f65bb..1a442c2 100644 --- a/revati-kadam-portfolio/index.html +++ b/revati-kadam-portfolio/index.html @@ -9,7 +9,72 @@ - + + + +
+ +
+
+
+ + +
+
+
+ +
+
+ +
+ + +
+
+ + + + +
+
+
+ 0 + 1 +
+
+ 0 + 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 +
+
+ 0 + 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 +
+ % +
+
+
+
CONNECTING TO HOST...
+
+
+
@@ -383,5 +448,6 @@

Contact Me

+ \ No newline at end of file diff --git a/revati-kadam-portfolio/js/hero.js b/revati-kadam-portfolio/js/hero.js index 9f60dea..6f06731 100644 --- a/revati-kadam-portfolio/js/hero.js +++ b/revati-kadam-portfolio/js/hero.js @@ -6,9 +6,8 @@ const prefersReduced = window.matchMedia('(prefers-reduced-motion: reduce)').matches; const isMobile = window.matchMedia('(max-width: 767px)').matches; -document.addEventListener('DOMContentLoaded', () => { - setTimeout(initHeroV2, 80); -}); +// Decoupled trigger: initHeroV2() is now called by the preloader script on loading completion. + function initHeroV2() { if (prefersReduced) { diff --git a/revati-kadam-portfolio/js/preloader.js b/revati-kadam-portfolio/js/preloader.js new file mode 100644 index 0000000..15da046 --- /dev/null +++ b/revati-kadam-portfolio/js/preloader.js @@ -0,0 +1,210 @@ +const prefersReducedMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches; + +document.addEventListener('DOMContentLoaded', () => { + if (prefersReducedMotion) { + const loader = document.getElementById("preloader"); + if (loader) loader.style.display = "none"; + document.body.classList.remove("loading"); + if (typeof initHeroV2 === "function") { + initHeroV2(); + } + return; + } + + startPreloaderAnimation(); +}); + +function startPreloaderAnimation() { + const circleEl = document.getElementById("preloaderCircle"); + + // Set up GSAP timeline + const tl = gsap.timeline(); + + // 1. Premium Logo Character Reveal (Staggered scale, translation, and rotational flip) + tl.fromTo('.preloader-logo span', + { y: 40, rotateX: -60, opacity: 0 }, + { y: 0, rotateX: 0, opacity: 1, duration: 0.8, stagger: 0.04, ease: "back.out(1.5)" } + ); + + // Status messages and transition tracking variables + const statusMessages = [ + { min: 0, max: 20, text: "CONNECTING TO PORTFOLIO NODE..." }, + { min: 20, max: 45, text: "INITIALIZING DIGITAL GRIDS..." }, + { min: 45, max: 70, text: "RESOLVING IMMERSIVE PHYSICS..." }, + { min: 70, max: 90, text: "STABILIZING GRAPHICS MESH..." }, + { min: 90, max: 100, text: "PREPARING INTERFACE..." } + ]; + + let lastHundreds = -1; + let lastTens = -1; + let lastOnes = -1; + let lastStatusText = ""; + + // Progress tracker object + const progressObj = { value: 0 }; + + // 2. Count progress from 0 to 100 + tl.to(progressObj, { + value: 100, + duration: 2.2, + ease: "power2.out", + onUpdate: () => { + const roundedVal = Math.floor(progressObj.value); + + // A. Update Circular Progress Stroke + if (circleEl) { + // stroke-dasharray is 282.7 (2 * PI * r) + const offset = 282.7 - (roundedVal / 100) * 282.7; + circleEl.style.strokeDashoffset = offset; + } + + // B. Calculate digits for mechanical rolling ticker + const hundreds = Math.floor(roundedVal / 100); + const tens = Math.floor((roundedVal % 100) / 10); + const ones = roundedVal % 10; + + // C. Animate Hundreds Digit Column (only visible at >= 100%) + if (hundreds !== lastHundreds) { + lastHundreds = hundreds; + const col = document.getElementById("tickerHundreds"); + if (col) { + if (hundreds > 0) { + col.classList.add("visible"); + } + gsap.to(col, { y: -hundreds * 36, duration: 0.4, ease: "back.out(1.2)" }); + } + } + + // D. Animate Tens Digit Column + if (tens !== lastTens) { + lastTens = tens; + const col = document.getElementById("tickerTens"); + if (col) { + gsap.to(col, { y: -tens * 36, duration: 0.35, ease: "back.out(1.2)" }); + } + } + + // E. Animate Ones Digit Column + if (ones !== lastOnes) { + lastOnes = ones; + const col = document.getElementById("tickerOnes"); + if (col) { + gsap.to(col, { y: -ones * 36, duration: 0.15, ease: "power1.out" }); + } + } + + // F. Smooth change transition for status messages + const activeMsg = statusMessages.find(m => roundedVal >= m.min && roundedVal <= m.max)?.text || ""; + if (activeMsg !== lastStatusText) { + lastStatusText = activeMsg; + const statusEl = document.getElementById("preloaderStatus"); + if (statusEl) { + gsap.to(statusEl, { + opacity: 0, + y: -5, + duration: 0.1, + onComplete: () => { + statusEl.innerText = activeMsg; + gsap.to(statusEl, { opacity: 0.8, y: 0, duration: 0.15 }); + } + }); + } + } + }, + onComplete: dismissPreloader + }, "-=0.2"); +} + +function dismissPreloader() { + const exitTimeline = gsap.timeline({ + onComplete: () => { + const loader = document.getElementById("preloader"); + if (loader) loader.style.display = "none"; + document.body.classList.remove("loading"); + } + }); + + // 1. Stagger letters out upwards with tilt rotation + exitTimeline.to('.preloader-logo span', { + y: -40, + rotateX: 45, + opacity: 0, + duration: 0.45, + stagger: 0.02, + ease: "power2.in" + }, 0); + + // 2. Fade out other loading dashboard components + exitTimeline.to(['.preloader-dashboard', '.preloader-radial'], { + opacity: 0, + scale: 0.9, + duration: 0.4, + ease: "power2.in" + }, 0); + + // 3. Staggered, skewed diagonal curtain split reveals + + // LAYER 1: Frontmost solid curtains + exitTimeline.to('.preloader-curtain--left.preloader-curtain--layer1', { + xPercent: -105, + skewX: -10, + duration: 1.1, + ease: "power3.inOut" + }, 0.2); + + exitTimeline.to('.preloader-curtain--right.preloader-curtain--layer1', { + xPercent: 105, + skewX: 10, + duration: 1.1, + ease: "power3.inOut" + }, "<"); + + // LAYER 2: Middle colored gradient curtains + exitTimeline.to('.preloader-curtain--left.preloader-curtain--layer2', { + xPercent: -105, + skewX: -7, + duration: 1.25, + ease: "power3.inOut" + }, 0.28); + + exitTimeline.to('.preloader-curtain--right.preloader-curtain--layer2', { + xPercent: 105, + skewX: 7, + duration: 1.25, + ease: "power3.inOut" + }, "<"); + + // LAYER 3: Backmost glassmorphic blur curtains + exitTimeline.to('.preloader-curtain--left.preloader-curtain--layer3', { + xPercent: -105, + skewX: -4, + duration: 1.4, + ease: "power3.inOut" + }, 0.36); + + exitTimeline.to('.preloader-curtain--right.preloader-curtain--layer3', { + xPercent: 105, + skewX: 4, + duration: 1.4, + ease: "power3.inOut" + }, "<"); + + // 4. Hero Cinematic Handoff + const heroEl = document.querySelector('.hero-v2'); + if (heroEl) { + gsap.set(heroEl, { scale: 0.96, filter: "blur(8px)" }); + exitTimeline.to(heroEl, { + scale: 1, + filter: "blur(0px)", + duration: 1.5, + ease: "power2.out" + }, 0.36); // Starts exactly when the glassmorphic layer begins to pull back + } + + // Trigger standard hero element staggers mid-reveal + exitTimeline.call(() => { + if (typeof initHeroV2 === "function") { + initHeroV2(); + } + }, null, 0.44); +}