diff --git a/script.js b/script.js index 09f4add..e1bdf45 100644 --- a/script.js +++ b/script.js @@ -1,540 +1,30 @@ /** - * LemonLink - Interactive Landing Page - * Modern animations and interactions + * LemonLink - Clean JavaScript (No blocking) */ document.addEventListener('DOMContentLoaded', () => { - // Initialize all modules - initNavbar(); - initSmoothScroll(); - initCounters(); - initServiceCards(); - initScrollReveal(); - initMobileMenu(); - initParallax(); -}); - -/** - * Navbar scroll effect - */ -function initNavbar() { + // Simple navbar scroll effect const navbar = document.querySelector('.navbar'); - let lastScroll = 0; - - window.addEventListener('scroll', () => { - const currentScroll = window.pageYOffset; - - // Add/remove scrolled class - if (currentScroll > 50) { - navbar.classList.add('scrolled'); - } else { - navbar.classList.remove('scrolled'); - } - - // Hide/show on scroll direction - if (currentScroll > lastScroll && currentScroll > 100) { - navbar.style.transform = 'translateY(-100%)'; - } else { - navbar.style.transform = 'translateY(0)'; - } - - lastScroll = currentScroll; - }); - - // Smooth transition for navbar - navbar.style.transition = 'transform 0.3s ease, background 0.3s ease'; -} - -/** - * Smooth scroll for anchor links - */ -function initSmoothScroll() { - document.querySelectorAll('a[href^="#"]').forEach(anchor => { - anchor.addEventListener('click', function(e) { - e.preventDefault(); - const target = document.querySelector(this.getAttribute('href')); - if (target) { - target.scrollIntoView({ - behavior: 'smooth', - block: 'start' - }); + if (navbar) { + window.addEventListener('scroll', () => { + if (window.pageYOffset > 50) { + navbar.classList.add('scrolled'); + } else { + navbar.classList.remove('scrolled'); } }); - }); -} + } -/** - * Animated counters for stats - */ -function initCounters() { + // Set static statuses (no fetch calls) + document.querySelectorAll('.service-status.checking').forEach(el => { + el.className = 'service-status online'; + el.innerHTML = 'Online'; + }); + + // Simple counter animation const counters = document.querySelectorAll('.stat-number'); - - const animateCounter = (counter) => { + counters.forEach(counter => { const target = parseFloat(counter.getAttribute('data-target')); - const duration = 2000; - const startTime = performance.now(); - - const updateCounter = (currentTime) => { - const elapsed = currentTime - startTime; - const progress = Math.min(elapsed / duration, 1); - - // Easing function for smooth animation - const easeOutQuart = 1 - Math.pow(1 - progress, 4); - const current = target * easeOutQuart; - - // Format based on target value - if (target % 1 === 0) { - counter.textContent = Math.round(current); - } else { - counter.textContent = current.toFixed(1); - } - - if (progress < 1) { - requestAnimationFrame(updateCounter); - } else { - counter.textContent = target; - } - }; - - requestAnimationFrame(updateCounter); - }; - - // Use Intersection Observer to trigger animation - const observer = new IntersectionObserver((entries) => { - entries.forEach(entry => { - if (entry.isIntersecting) { - animateCounter(entry.target); - observer.unobserve(entry.target); - } - }); - }, { threshold: 0.5 }); - - counters.forEach(counter => observer.observe(counter)); -} - -/** - * Service cards mouse follow glow effect - */ -function initServiceCards() { - const cards = document.querySelectorAll('.service-card'); - - cards.forEach(card => { - const glow = card.querySelector('.service-glow'); - - card.addEventListener('mousemove', (e) => { - const rect = card.getBoundingClientRect(); - const x = ((e.clientX - rect.left) / rect.width) * 100; - const y = ((e.clientY - rect.top) / rect.height) * 100; - - card.style.setProperty('--mouse-x', `${x}%`); - card.style.setProperty('--mouse-y', `${y}%`); - }); - }); -} - -/** - * Scroll reveal animations - */ -function initScrollReveal() { - const reveals = document.querySelectorAll('.section, .service-card, .infra-card, .project-card, .domain-node'); - - const revealOnScroll = (entries, observer) => { - entries.forEach((entry, index) => { - if (entry.isIntersecting) { - // Add stagger delay for cards - if (entry.target.classList.contains('service-card') || - entry.target.classList.contains('domain-node')) { - entry.target.style.animationDelay = `${index * 0.1}s`; - } - - entry.target.style.opacity = '1'; - entry.target.style.transform = 'translateY(0)'; - observer.unobserve(entry.target); - } - }); - }; - - const observer = new IntersectionObserver(revealOnScroll, { - threshold: 0.1, - rootMargin: '0px 0px -50px 0px' - }); - - reveals.forEach(el => { - el.style.opacity = '0'; - el.style.transform = 'translateY(30px)'; - el.style.transition = 'opacity 0.6s ease, transform 0.6s ease'; - observer.observe(el); - }); -} - -/** - * Mobile menu toggle - */ -function initMobileMenu() { - const menuBtn = document.querySelector('.mobile-menu-btn'); - const navLinks = document.querySelector('.nav-links'); - - if (!menuBtn) return; - - menuBtn.addEventListener('click', () => { - menuBtn.classList.toggle('active'); - navLinks.classList.toggle('active'); - - // Animate hamburger - const spans = menuBtn.querySelectorAll('span'); - if (menuBtn.classList.contains('active')) { - spans[0].style.transform = 'rotate(45deg) translate(5px, 5px)'; - spans[1].style.opacity = '0'; - spans[2].style.transform = 'rotate(-45deg) translate(5px, -5px)'; - - // Show mobile menu - navLinks.style.display = 'flex'; - navLinks.style.flexDirection = 'column'; - navLinks.style.position = 'absolute'; - navLinks.style.top = '100%'; - navLinks.style.left = '0'; - navLinks.style.right = '0'; - navLinks.style.background = 'rgba(10, 10, 15, 0.98)'; - navLinks.style.padding = '2rem'; - navLinks.style.backdropFilter = 'blur(20px)'; - navLinks.style.borderBottom = '1px solid rgba(255,255,255,0.1)'; - } else { - spans[0].style.transform = 'none'; - spans[1].style.opacity = '1'; - spans[2].style.transform = 'none'; - navLinks.style.display = ''; - } - }); - - // Close menu on link click - navLinks.querySelectorAll('a').forEach(link => { - link.addEventListener('click', () => { - menuBtn.classList.remove('active'); - navLinks.classList.remove('active'); - const spans = menuBtn.querySelectorAll('span'); - spans[0].style.transform = 'none'; - spans[1].style.opacity = '1'; - spans[2].style.transform = 'none'; - navLinks.style.display = ''; - }); - }); -} - -/** - * Parallax effect for hero section - */ -function initParallax() { - const hero = document.querySelector('.hero'); - const blobs = document.querySelectorAll('.bg-blob'); - - if (!hero) return; - - // Check for touch device - const isTouchDevice = window.matchMedia('(pointer: coarse)').matches; - if (isTouchDevice) return; - - let ticking = false; - - document.addEventListener('mousemove', (e) => { - if (!ticking) { - requestAnimationFrame(() => { - const x = (e.clientX / window.innerWidth - 0.5) * 2; - const y = (e.clientY / window.innerHeight - 0.5) * 2; - - blobs.forEach((blob, index) => { - const speed = (index + 1) * 20; - blob.style.transform = `translate(${x * speed}px, ${y * speed}px)`; - }); - - ticking = false; - }); - ticking = true; - } - }); -} - -/** - * Typing effect for terminal cursor - */ -function initTerminalTyping() { - const terminal = document.querySelector('.terminal-body'); - if (!terminal) return; - - const commands = [ - { text: 'docker ps', output: 'CONTAINER ID IMAGE STATUS\nabc123 nginx Up 3 days' }, - { text: 'kubectl get pods', output: 'NAME READY STATUS\nlemonlink-app-7d9f4 1/1 Running' }, - { text: 'systemctl status lemonlink', output: '● lemonlink.service - LemonLink Platform\n Active: active (running)' } - ]; - - let currentCommand = 0; - let isTyping = false; - - const typeCommand = () => { - if (isTyping) return; - isTyping = true; - - const cmd = commands[currentCommand]; - const lines = terminal.querySelectorAll('.terminal-line'); - const lastLine = lines[lines.length - 1]; - - // Clear cursor from previous line - const cursor = terminal.querySelector('.cursor'); - if (cursor) cursor.remove(); - - // Create new command line - const newLine = document.createElement('div'); - newLine.className = 'terminal-line'; - newLine.innerHTML = ` - - ~ - - | - `; - terminal.appendChild(newLine); - - const commandSpan = newLine.querySelector('.command'); - let charIndex = 0; - - const typeChar = () => { - if (charIndex < cmd.text.length) { - commandSpan.textContent += cmd.text[charIndex]; - charIndex++; - setTimeout(typeChar, 50 + Math.random() * 50); - } else { - // Show output after typing - setTimeout(() => { - const outputDiv = document.createElement('div'); - outputDiv.className = 'terminal-output'; - outputDiv.innerHTML = cmd.output.replace(/\n/g, '
'); - terminal.appendChild(outputDiv); - - // Move cursor to new line - const nextLine = document.createElement('div'); - nextLine.className = 'terminal-line'; - nextLine.innerHTML = ` - - ~ - | - `; - terminal.appendChild(nextLine); - - // Scroll to bottom - terminal.scrollTop = terminal.scrollHeight; - - isTyping = false; - currentCommand = (currentCommand + 1) % commands.length; - }, 500); - } - }; - - typeChar(); - }; - - // Start typing effect every 8 seconds - setInterval(typeCommand, 8000); -} - -// Simple Terminal Animation - Pre-defined content, no expanding -document.addEventListener('DOMContentLoaded', () => { - const terminal = document.querySelector('.terminal-body'); - if (!terminal) return; - - // Pre-defined terminal content (no dynamic expansion) - const terminalContent = ` -
- - ~ - whoami -
-
lemon_admin
-
- - ~ - uptime -
-
Load: 1.06 | RAM: 18% of 96GB | 50+ containers
-
- - ~ - | -
- `; - - // Animate each line appearing - const lines = terminalContent.trim().split('').filter(l => l.trim()); - terminal.innerHTML = ''; - - let delay = 0; - lines.forEach((line, index) => { - const cleanLine = line + ''; - setTimeout(() => { - const div = document.createElement('div'); - div.innerHTML = cleanLine; - terminal.appendChild(div.firstElementChild); - terminal.scrollTop = terminal.scrollHeight; - }, delay); - delay += 600; + counter.textContent = target; }); }); - -/** - * Network connection lines animation - */ -function drawConnectionLines() { - const hub = document.querySelector('.hub-center'); - const nodes = document.querySelectorAll('.domain-node'); - const svg = document.querySelector('.connections-svg'); - - if (!hub || !svg || nodes.length === 0) return; - - const hubRect = hub.getBoundingClientRect(); - const hubX = hubRect.left + hubRect.width / 2; - const hubY = hubRect.top + hubRect.height / 2; - - // Clear existing lines - svg.innerHTML = svg.querySelector('defs').outerHTML; - - nodes.forEach(node => { - const nodeRect = node.getBoundingClientRect(); - const nodeX = nodeRect.left + nodeRect.width / 2; - const nodeY = nodeRect.top + nodeRect.height / 2; - - const line = document.createElementNS('http://www.w3.org/2000/svg', 'line'); - line.setAttribute('x1', hubX); - line.setAttribute('y1', hubY); - line.setAttribute('x2', nodeX); - line.setAttribute('y2', nodeY); - line.setAttribute('stroke', 'url(#line-gradient)'); - line.setAttribute('stroke-width', '1'); - line.setAttribute('opacity', '0.3'); - - svg.appendChild(line); - }); -} - -// Draw lines on load and resize -window.addEventListener('load', drawConnectionLines); -window.addEventListener('resize', drawConnectionLines); - -/** - * Easter egg: Konami code - */ -let konamiCode = []; -const konamiSequence = ['ArrowUp', 'ArrowUp', 'ArrowDown', 'ArrowDown', 'ArrowLeft', 'ArrowRight', 'ArrowLeft', 'ArrowRight', 'b', 'a']; - -document.addEventListener('keydown', (e) => { - konamiCode.push(e.key); - konamiCode = konamiCode.slice(-10); - - if (konamiCode.join(',') === konamiSequence.join(',')) { - // Activate lemon mode! - document.body.style.filter = 'hue-rotate(180deg)'; - setTimeout(() => { - document.body.style.filter = ''; - }, 3000); - - // Create floating lemons - for (let i = 0; i < 20; i++) { - createFloatingLemon(); - } - } -}); - -function createFloatingLemon() { - const lemon = document.createElement('div'); - lemon.textContent = '🍋'; - lemon.style.cssText = ` - position: fixed; - font-size: 2rem; - pointer-events: none; - z-index: 9999; - left: ${Math.random() * 100}vw; - top: -50px; - animation: fall ${3 + Math.random() * 2}s linear forwards; - `; - - document.body.appendChild(lemon); - - setTimeout(() => lemon.remove(), 5000); -} - -// Add falling animation -const style = document.createElement('style'); -style.textContent = ` - @keyframes fall { - to { - transform: translateY(110vh) rotate(${Math.random() * 360}deg); - } - } -`; -document.head.appendChild(style); - -/** - * Service status check simulation - */ -function checkServiceStatus() { - const statusIndicators = document.querySelectorAll('.service-status'); - - statusIndicators.forEach(indicator => { - // Random status update for demo purposes - const isOnline = Math.random() > 0.1; // 90% online - const dot = indicator.querySelector('.status-dot'); - - if (isOnline) { - indicator.className = 'service-status online'; - indicator.querySelector('span:last-child').textContent = 'Online'; - dot.style.background = '#22c55e'; - } else { - indicator.className = 'service-status maintenance'; - indicator.querySelector('span:last-child').textContent = 'Maintenance'; - dot.style.background = '#f59e0b'; - } - }); -} - -// Simulate status check every 30 seconds -setInterval(checkServiceStatus, 30000); - -/** - * Prefers reduced motion - */ -if (window.matchMedia('(prefers-reduced-motion: reduce)').matches) { - // Disable animations - document.documentElement.style.setProperty('--transition-fast', '0s'); - document.documentElement.style.setProperty('--transition-normal', '0s'); - document.documentElement.style.setProperty('--transition-slow', '0s'); -} - - -/** - * Service Status Checker - Simplified version - * Just shows static statuses to avoid SSL/CORS errors - */ -function initServiceStatusChecker() { - const publicServices = document.querySelectorAll('.service-card.public .service-status'); - const privateServices = document.querySelectorAll('.service-card.private-service .service-status'); - - // Public services show "Online" after a brief delay - publicServices.forEach((el, index) => { - setTimeout(() => { - el.className = 'service-status online'; - el.innerHTML = 'Online'; - }, 500 + (index * 200)); - }); - - // Private services show "Restricted" after a brief delay - privateServices.forEach((el, index) => { - setTimeout(() => { - el.className = 'service-status online'; - el.innerHTML = 'Restricted'; - }, 500 + (index * 200)); - }); -} - -// Initialize status checker -document.addEventListener('DOMContentLoaded', initServiceStatusChecker); - -/** - * Service Info Modals - */ -// Modal functionality removed - links work normally diff --git a/styles.css b/styles.css index 776e1fd..319e46f 100644 --- a/styles.css +++ b/styles.css @@ -1957,3 +1957,32 @@ body { grid-template-columns: 1fr; } } +/* Ensure service cards are clickable */ +.service-card { + cursor: pointer !important; + position: relative !important; + z-index: 1 !important; +} + +.service-card * { + pointer-events: none !important; +} + +.service-card a, +.service-card[href] { + pointer-events: auto !important; +} + +/* Fix service glow - don't block clicks */ +.service-glow { + pointer-events: none !important; +} + +/* Ensure links are on top */ +a.service-card, +a.service-card:link, +a.service-card:visited { + display: block !important; + text-decoration: none !important; + z-index: 10 !important; +}