Necesito dise帽ar una ladpage para un producto Saas llamado potbi, corresponde a una aplicaci贸n de gesti贸n de restuarantes, donde tiene modulo de operaciones, pos, y modulo de gestion financiera
071bafa verified | // Potbi Landing Page JavaScript | |
| document.addEventListener('DOMContentLoaded', function() { | |
| // Initialize Feather icons | |
| if (typeof feather !== 'undefined') { | |
| feather.replace(); | |
| } | |
| // Demo tabs functionality | |
| initDemoTabs(); | |
| // Counter animation | |
| initCounters(); | |
| // Smooth scroll for anchor links | |
| initSmoothScroll(); | |
| // Intersection Observer for animations | |
| initScrollAnimations(); | |
| // Navbar scroll effect | |
| initNavbarScroll(); | |
| }); | |
| // Demo Tabs | |
| function initDemoTabs() { | |
| const tabs = document.querySelectorAll('.demo-tab'); | |
| const contents = document.querySelectorAll('.demo-content'); | |
| tabs.forEach(tab => { | |
| tab.addEventListener('click', () => { | |
| const targetId = tab.dataset.tab; | |
| // Update tabs | |
| tabs.forEach(t => { | |
| t.classList.remove('active'); | |
| t.classList.add('bg-slate-800/30', 'border-slate-800'); | |
| t.classList.remove('bg-slate-800/50', 'border-slate-700'); | |
| }); | |
| tab.classList.add('active'); | |
| tab.classList.remove('bg-slate-800/30', 'border-slate-800'); | |
| tab.classList.add('bg-slate-800/50', 'border-slate-700'); | |
| // Update contents | |
| contents.forEach(content => { | |
| content.classList.add('hidden'); | |
| content.classList.remove('active'); | |
| }); | |
| const targetContent = document.getElementById(`demo-${targetId}`); | |
| if (targetContent) { | |
| targetContent.classList.remove('hidden'); | |
| // Small delay to allow display:block to apply before opacity transition | |
| setTimeout(() => { | |
| targetContent.classList.add('active'); | |
| }, 10); | |
| } | |
| // Re-render icons | |
| if (typeof feather !== 'undefined') { | |
| feather.replace(); | |
| } | |
| }); | |
| }); | |
| } | |
| // Counter Animation | |
| function initCounters() { | |
| const counters = document.querySelectorAll('[data-count]'); | |
| const observerOptions = { | |
| threshold: 0.5, | |
| rootMargin: '0px' | |
| }; | |
| const observer = new IntersectionObserver((entries) => { | |
| entries.forEach(entry => { | |
| if (entry.isIntersecting) { | |
| const counter = entry.target; | |
| const target = parseFloat(counter.dataset.count); | |
| const isDecimal = target % 1 !== 0; | |
| const duration = 2000; | |
| const startTime = performance.now(); | |
| function updateCounter(currentTime) { | |
| const elapsed = currentTime - startTime; | |
| const progress = Math.min(elapsed / duration, 1); | |
| // Easing function | |
| const easeOutQuart = 1 - Math.pow(1 - progress, 4); | |
| const current = target * easeOutQuart; | |
| if (isDecimal) { | |
| counter.textContent = current.toFixed(1); | |
| } else { | |
| counter.textContent = Math.floor(current).toLocaleString(); | |
| } | |
| if (progress < 1) { | |
| requestAnimationFrame(updateCounter); | |
| } else { | |
| if (isDecimal) { | |
| counter.textContent = target.toFixed(1); | |
| } else { | |
| counter.textContent = target.toLocaleString(); | |
| } | |
| } | |
| } | |
| requestAnimationFrame(updateCounter); | |
| observer.unobserve(counter); | |
| } | |
| }); | |
| }, observerOptions); | |
| counters.forEach(counter => observer.observe(counter)); | |
| } | |
| // Smooth Scroll | |
| function initSmoothScroll() { | |
| document.querySelectorAll('a[href^="#"]').forEach(anchor => { | |
| anchor.addEventListener('click', function(e) { | |
| const href = this.getAttribute('href'); | |
| if (href === '#') return; | |
| const target = document.querySelector(href); | |
| if (target) { | |
| e.preventDefault(); | |
| const offsetTop = target.offsetTop - 80; // Account for fixed header | |
| window.scrollTo({ | |
| top: offsetTop, | |
| behavior: 'smooth' | |
| }); | |
| } | |
| }); | |
| }); | |
| } | |
| // Scroll Animations | |
| function initScrollAnimations() { | |
| const animatedElements = document.querySelectorAll('.animate-on-scroll'); | |
| const observerOptions = { | |
| threshold: 0.1, | |
| rootMargin: '0px 0px -50px 0px' | |
| }; | |
| const observer = new IntersectionObserver((entries) => { | |
| entries.forEach(entry => { | |
| if (entry.isIntersecting) { | |
| entry.target.classList.add('animate-in'); | |
| observer.unobserve(entry.target); | |
| } | |
| }); | |
| }, observerOptions); | |
| animatedElements.forEach(el => observer.observe(el)); | |
| } | |
| // Navbar Scroll Effect | |
| function initNavbarScroll() { | |
| const navbar = document.querySelector('potbi-navbar'); | |
| if (!navbar) return; | |
| let lastScroll = 0; | |
| let ticking = false; | |
| function updateNavbar() { | |
| const currentScroll = window.pageYOffset; | |
| if (currentScroll > 100) { | |
| navbar.classList.add('scrolled'); | |
| } else { | |
| navbar.classList.remove('scrolled'); | |
| } | |
| lastScroll = currentScroll; | |
| ticking = false; | |
| } | |
| window.addEventListener('scroll', () => { | |
| if (!ticking) { | |
| requestAnimationFrame(updateNavbar); | |
| ticking = true; | |
| } | |
| }, { passive: true }); | |
| } | |
| // Utility: Debounce function | |
| function debounce(func, wait) { | |
| let timeout; | |
| return function executedFunction(...args) { | |
| const later = () => { | |
| clearTimeout(timeout); | |
| func(...args); | |
| }; | |
| clearTimeout(timeout); | |
| timeout = setTimeout(later, wait); | |
| }; | |
| } | |
| // Utility: Throttle function | |
| function throttle(func, limit) { | |
| let inThrottle; | |
| return function(...args) { | |
| if (!inThrottle) { | |
| func.apply(this, args); | |
| inThrottle = true; | |
| setTimeout(() => inThrottle = false, limit); | |
| } | |
| }; | |
| } | |
| // Form validation helper | |
| function validateEmail(email) { | |
| const re = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; | |
| return re.test(String(email).toLowerCase()); | |
| } | |
| // Mobile menu toggle (for components) | |
| function toggleMobileMenu() { | |
| const menu = document.getElementById('mobile-menu'); | |
| if (menu) { | |
| menu.classList.toggle('hidden'); | |
| menu.classList.toggle('mobile-menu-enter'); | |
| } | |
| } | |
| // Export functions for global access | |
| window.Potbi = { | |
| toggleMobileMenu, | |
| validateEmail, | |
| debounce, | |
| throttle | |
| }; |