feat(ui): Enhance landing page interactions and animations

This commit is contained in:
cogwheel0
2025-11-26 20:01:48 +05:30
parent 1cd82c1916
commit 7fdb342b86
3 changed files with 1892 additions and 403 deletions

View File

@@ -1,21 +1,79 @@
/**
* Conduit Landing Page
* Smooth interactions and animations
*/
document.addEventListener('DOMContentLoaded', () => {
// Smooth scrolling for anchor links
// ============================================
// SMOOTH SCROLL
// ============================================
document.querySelectorAll('a[href^="#"]').forEach(anchor => {
anchor.addEventListener('click', function (e) {
e.preventDefault();
const target = document.querySelector(this.getAttribute('href'));
const targetId = this.getAttribute('href');
if (targetId === '#') return;
const target = document.querySelector(targetId);
if (target) {
target.scrollIntoView({
const navHeight = document.querySelector('.navbar').offsetHeight;
const targetPosition = target.offsetTop - navHeight - 20;
window.scrollTo({
top: targetPosition,
behavior: 'smooth'
});
// Close mobile menu if open
mobileMenu.classList.remove('active');
}
});
});
// Intersection Observer for fade-in animations
// ============================================
// MOBILE MENU
// ============================================
const mobileMenuBtn = document.querySelector('.mobile-menu-btn');
const mobileMenu = document.querySelector('.mobile-menu');
if (mobileMenuBtn && mobileMenu) {
mobileMenuBtn.addEventListener('click', () => {
mobileMenu.classList.toggle('active');
mobileMenuBtn.classList.toggle('active');
});
// Close menu when clicking a link
mobileMenu.querySelectorAll('a').forEach(link => {
link.addEventListener('click', () => {
mobileMenu.classList.remove('active');
mobileMenuBtn.classList.remove('active');
});
});
}
// ============================================
// NAVBAR SCROLL EFFECT
// ============================================
const navbar = document.querySelector('.navbar');
let lastScroll = 0;
window.addEventListener('scroll', () => {
const currentScroll = window.pageYOffset;
if (currentScroll > 100) {
navbar.style.background = 'rgba(10, 10, 12, 0.9)';
} else {
navbar.style.background = 'rgba(10, 10, 12, 0.6)';
}
lastScroll = currentScroll;
}, { passive: true });
// ============================================
// INTERSECTION OBSERVER - FADE IN
// ============================================
const observerOptions = {
threshold: 0.1,
rootMargin: '0px 0px -50px 0px'
rootMargin: '0px 0px -60px 0px'
};
const observer = new IntersectionObserver((entries) => {
@@ -27,32 +85,249 @@ document.addEventListener('DOMContentLoaded', () => {
});
}, observerOptions);
// Add fade-in class to elements we want to animate
const animatedElements = document.querySelectorAll('.feature-card, .screenshot-item, .hero-content, .hero-image');
animatedElements.forEach(el => {
// Observe feature cards
document.querySelectorAll('.feature-card').forEach(el => {
el.classList.add('fade-in');
observer.observe(el);
});
// Observe stat cards
document.querySelectorAll('.stat-card').forEach((el, i) => {
el.classList.add('fade-in');
el.style.transitionDelay = `${i * 0.1}s`;
observer.observe(el);
});
// Observe gallery items
document.querySelectorAll('.gallery-item').forEach((el, i) => {
el.classList.add('fade-in');
el.style.transitionDelay = `${i * 0.1}s`;
observer.observe(el);
});
// Observe section headers
document.querySelectorAll('.section-header').forEach(el => {
el.classList.add('fade-in');
observer.observe(el);
});
});
// Add this CSS to styles.css dynamically or ensure it's in the CSS file
const style = document.createElement('style');
style.textContent = `
.fade-in {
opacity: 0;
transform: translateY(20px);
transition: opacity 0.6s ease-out, transform 0.6s ease-out;
}
// ============================================
// PARALLAX ORBS (subtle effect)
// ============================================
const orbs = document.querySelectorAll('.orb');
.fade-in.visible {
opacity: 1;
transform: translateY(0);
if (window.matchMedia('(prefers-reduced-motion: no-preference)').matches) {
window.addEventListener('scroll', () => {
const scrollY = window.pageYOffset;
orbs.forEach((orb, i) => {
const speed = (i + 1) * 0.03;
orb.style.transform = `translateY(${scrollY * speed}px)`;
});
}, { passive: true });
}
// ============================================
// DEVICE FRAME TILT ON MOUSE MOVE
// ============================================
const deviceFrame = document.querySelector('.device-frame');
const heroSection = document.querySelector('.hero');
.feature-card:nth-child(2) { transition-delay: 0.1s; }
.feature-card:nth-child(3) { transition-delay: 0.2s; }
.feature-card:nth-child(4) { transition-delay: 0.3s; }
.feature-card:nth-child(5) { transition-delay: 0.4s; }
.feature-card:nth-child(6) { transition-delay: 0.5s; }
`;
document.head.appendChild(style);
if (deviceFrame && heroSection && window.matchMedia('(prefers-reduced-motion: no-preference)').matches) {
heroSection.addEventListener('mousemove', (e) => {
const rect = heroSection.getBoundingClientRect();
const x = (e.clientX - rect.left) / rect.width - 0.5;
const y = (e.clientY - rect.top) / rect.height - 0.5;
const tiltX = y * 10;
const tiltY = -x * 10;
deviceFrame.style.transform = `perspective(1000px) rotateX(${tiltX}deg) rotateY(${tiltY}deg) scale(1.02)`;
});
heroSection.addEventListener('mouseleave', () => {
deviceFrame.style.transform = 'perspective(1000px) rotateX(0) rotateY(0) scale(1)';
});
}
// ============================================
// GALLERY DRAG SCROLL
// ============================================
const galleryTrack = document.querySelector('.gallery-track');
if (galleryTrack) {
let isDown = false;
let startX;
let scrollLeft;
galleryTrack.addEventListener('mousedown', (e) => {
isDown = true;
galleryTrack.style.cursor = 'grabbing';
startX = e.pageX - galleryTrack.offsetLeft;
scrollLeft = galleryTrack.scrollLeft;
});
galleryTrack.addEventListener('mouseleave', () => {
isDown = false;
galleryTrack.style.cursor = 'grab';
});
galleryTrack.addEventListener('mouseup', () => {
isDown = false;
galleryTrack.style.cursor = 'grab';
});
galleryTrack.addEventListener('mousemove', (e) => {
if (!isDown) return;
e.preventDefault();
const x = e.pageX - galleryTrack.offsetLeft;
const walk = (x - startX) * 1.5;
galleryTrack.scrollLeft = scrollLeft - walk;
});
// Set initial cursor
galleryTrack.style.cursor = 'grab';
}
// ============================================
// CTA RING ANIMATION RESET
// ============================================
const ctaSection = document.querySelector('.cta-section');
const ctaRings = document.querySelectorAll('.cta-ring');
if (ctaSection && ctaRings.length) {
const ctaObserver = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
// Reset animation when section comes into view
ctaRings.forEach(ring => {
ring.style.animation = 'none';
ring.offsetHeight; // Trigger reflow
ring.style.animation = null;
});
}
});
}, { threshold: 0.3 });
ctaObserver.observe(ctaSection);
}
// ============================================
// BUTTON RIPPLE EFFECT
// ============================================
document.querySelectorAll('.btn').forEach(btn => {
btn.addEventListener('click', function(e) {
const ripple = document.createElement('span');
const rect = this.getBoundingClientRect();
ripple.style.cssText = `
position: absolute;
background: rgba(255, 255, 255, 0.3);
border-radius: 50%;
pointer-events: none;
transform: scale(0);
animation: ripple 0.6s ease-out;
left: ${e.clientX - rect.left}px;
top: ${e.clientY - rect.top}px;
width: 0;
height: 0;
`;
this.style.position = 'relative';
this.style.overflow = 'hidden';
this.appendChild(ripple);
setTimeout(() => ripple.remove(), 600);
});
});
// Add ripple keyframes
const style = document.createElement('style');
style.textContent = `
@keyframes ripple {
to {
width: 200px;
height: 200px;
margin-left: -100px;
margin-top: -100px;
opacity: 0;
transform: scale(1);
}
}
`;
document.head.appendChild(style);
// ============================================
// PRELOAD IMAGES
// ============================================
const preloadImages = [
'screenshots/1.png',
'screenshots/2.png',
'screenshots/3.png',
'screenshots/4.png'
];
preloadImages.forEach(src => {
const img = new Image();
img.src = src;
});
// ============================================
// FETCH GITHUB STATS
// ============================================
const formatNumber = (num) => {
if (num >= 1000000) {
return (num / 1000000).toFixed(1).replace(/\.0$/, '') + 'M';
}
if (num >= 1000) {
return (num / 1000).toFixed(1).replace(/\.0$/, '') + 'k';
}
return num.toString();
};
const starsElement = document.getElementById('github-stars');
const downloadsElement = document.getElementById('github-downloads');
// Fetch repo stats (stars)
if (starsElement) {
fetch('https://api.github.com/repos/cogwheel0/conduit')
.then(response => response.json())
.then(data => {
if (data.stargazers_count !== undefined) {
starsElement.textContent = formatNumber(data.stargazers_count);
starsElement.classList.add('loaded');
}
})
.catch(() => {
starsElement.textContent = '★';
});
}
// Fetch releases (downloads)
if (downloadsElement) {
fetch('https://api.github.com/repos/cogwheel0/conduit/releases')
.then(response => response.json())
.then(releases => {
if (Array.isArray(releases)) {
const totalDownloads = releases.reduce((total, release) => {
return total + release.assets.reduce((assetTotal, asset) => {
return assetTotal + (asset.download_count || 0);
}, 0);
}, 0);
if (totalDownloads > 0) {
downloadsElement.textContent = formatNumber(totalDownloads);
} else {
// If no GitHub downloads, show a generic indicator
downloadsElement.textContent = 'New';
}
downloadsElement.classList.add('loaded');
}
})
.catch(() => {
downloadsElement.textContent = '↓';
});
}
console.log('✨ Conduit landing page initialized');
});