1/* ========================================== 2 VELOO TELECOM - MAIN JAVASCRIPT 3 Carousel, Animations & Interactions 4 ========================================== */ 5 6document.addEventListener('DOMContentLoaded', function () { 7 // Initialize all components 8 initCarousel(); 9 initNavigation(); 10 initScrollEffects(); 11 initAnimations(); 12 // Plan toggle is now handled by plan-toggle.js 13}); 14 15/* ========================================== 16 CAROUSEL 17 ========================================== */ 18function initCarousel() { 19 const carousel = document.getElementById('carousel'); 20 const slides = carousel.querySelectorAll('.carousel-slide'); 21 const dots = carousel.querySelectorAll('.carousel-dot'); 22 const prevBtn = document.getElementById('carousel-prev'); 23 const nextBtn = document.getElementById('carousel-next'); 24 25 let currentSlide = 0; 26 let autoplayInterval; 27 const AUTOPLAY_DELAY = 6000; // 6 seconds 28 29 // Set initial state 30 function showSlide(index) { 31 // Handle bounds 32 if (index >= slides.length) index = 0; 33 if (index < 0) index = slides.length - 1; 34 35 // Update slides 36 slides.forEach((slide, i) => { 37 slide.classList.toggle('active', i === index); 38 }); 39 40 // Update dots 41 dots.forEach((dot, i) => { 42 dot.classList.toggle('active', i === index); 43 }); 44 45 currentSlide = index; 46 } 47 48 // Navigation functions 49 function nextSlide() { 50 showSlide(currentSlide + 1); 51 } 52 53 function prevSlide() { 54 showSlide(currentSlide - 1); 55 } 56 57 // Autoplay 58 function startAutoplay() { 59 autoplayInterval = setInterval(nextSlide, AUTOPLAY_DELAY); 60 } 61 62 function stopAutoplay() { 63 clearInterval(autoplayInterval); 64 } 65 66 function resetAutoplay() { 67 stopAutoplay(); 68 startAutoplay(); 69 } 70 71 // Event listeners 72 if (prevBtn && nextBtn) { 73 prevBtn.addEventListener('click', () => { 74 prevSlide(); 75 resetAutoplay(); 76 }); 77 78 nextBtn.addEventListener('click', () => { 79 nextSlide(); 80 resetAutoplay(); 81 }); 82 } 83 84 // Dot navigation 85 dots.forEach((dot, index) => { 86 dot.addEventListener('click', () => { 87 showSlide(index); 88 resetAutoplay(); 89 }); 90 }); 91 92 // Pause on hover 93 carousel.addEventListener('mouseenter', stopAutoplay); 94 carousel.addEventListener('mouseleave', startAutoplay); 95 96 // Touch/Swipe support 97 let touchStartX = 0; 98 let touchEndX = 0; 99 100 carousel.addEventListener('touchstart', (e) => { 101 touchStartX = e.changedTouches[0].screenX; 102 }, { passive: true }); 103 104 carousel.addEventListener('touchend', (e) => { 105 touchEndX = e.changedTouches[0].screenX; 106 handleSwipe(); 107 }, { passive: true }); 108 109 function handleSwipe() { 110 const swipeThreshold = 50; 111 const diff = touchStartX - touchEndX; 112 113 if (Math.abs(diff) > swipeThreshold) { 114 if (diff > 0) { 115 nextSlide(); 116 } else { 117 prevSlide(); 118 } 119 resetAutoplay(); 120 } 121 } 122 123 // Keyboard navigation 124 document.addEventListener('keydown', (e) => { 125 if (e.key === 'ArrowLeft') { 126 prevSlide(); 127 resetAutoplay(); 128 } else if (e.key === 'ArrowRight') { 129 nextSlide(); 130 resetAutoplay(); 131 } 132 }); 133 134 // Start autoplay 135 startAutoplay(); 136} 137 138/* ========================================== 139 NAVIGATION 140 ========================================== */ 141function initNavigation() { 142 const header = document.getElementById('header'); 143 const navToggle = document.getElementById('nav-toggle'); 144 const navMenu = document.getElementById('nav-menu'); 145 const navLinks = document.querySelectorAll('.nav-link'); 146 147 // Mobile menu toggle 148 if (navToggle && navMenu) { 149 navToggle.addEventListener('click', () => { 150 navMenu.classList.toggle('active'); 151 const icon = navToggle.querySelector('i'); 152 if (navMenu.classList.contains('active')) { 153 icon.classList.remove('fa-bars'); 154 icon.classList.add('fa-times'); 155 } else { 156 icon.classList.remove('fa-times'); 157 icon.classList.add('fa-bars'); 158 } 159 }); 160 } 161 162 // Close menu on link click 163 navLinks.forEach(link => { 164 link.addEventListener('click', () => { 165 if (navMenu) { 166 navMenu.classList.remove('active'); 167 const icon = navToggle.querySelector('i'); 168 icon.classList.remove('fa-times'); 169 icon.classList.add('fa-bars'); 170 } 171 }); 172 });
173 174 // Active link on scroll 175 function updateActiveLink() { 176 const sections = document.querySelectorAll('section[id]'); 177 const scrollY = window.scrollY + 100; 178 const windowHeight = window.innerHeight; 179 const documentHeight = document.documentElement.scrollHeight; 180 181 // Se está no final da página (footer), ativar Contato 182 const footer = document.getElementById('footer'); 183 const footerTop = footer ? footer.offsetTop : documentHeight; 184 if (scrollY >= footerTop - 100 || scrollY + windowHeight >= documentHeight - 50) { 185 navLinks.forEach(link => { 186 link.classList.remove('active'); 187 if (link.getAttribute('href') === '#footer') { 188 link.classList.add('active'); 189 } 190 }); 191 return; 192 } 193 194 let currentSection = null; 195 196 sections.forEach(section => { 197 const sectionTop = section.offsetTop; 198 const sectionHeight = section.offsetHeight; 199 200 if (scrollY > sectionTop && scrollY <= sectionTop + sectionHeight) { 201 currentSection = section.getAttribute('id'); 202 } 203 }); 204 205 navLinks.forEach(link => { 206 link.classList.remove('active'); 207 const href = link.getAttribute('href'); 208 if (currentSection && (href === `#${currentSection}` || href === `/#${currentSection}`)) { 209 link.classList.add('active'); 210 } 211 }); 212 } 213 214 window.addEventListener('scroll', updateActiveLink); 215 updateActiveLink(); // Initial call to set active link on load 216 217 // Smooth scroll for anchor links 218 document.querySelectorAll('a[href^="#"], a[href^="/#"]').forEach(anchor => { 219 anchor.addEventListener('click', function (e) { 220 let targetId = this.getAttribute('href'); 221 if (targetId.startsWith('/#')) targetId = targetId.substring(1); 222 if (targetId === '#') return; 223 224 const target = document.querySelector(targetId); 225 if (target) { 226 e.preventDefault(); 227 const headerHeight = header ? header.offsetHeight : 0; 228 const targetPosition = target.offsetTop - headerHeight; 229 230 window.scrollTo({ 231 top: targetPosition, 232 behavior: 'smooth' 233 }); 234 } 235 }); 236 }); 237} 238 239/* ========================================== 240 SCROLL EFFECTS 241 ========================================== */ 242function initScrollEffects() { 243 const header = document.getElementById('header'); 244 const backToTop = document.getElementById('back-to-top'); 245 246 // Header scroll effect 247 function handleHeaderScroll() { 248 if (window.scrollY > 50) { 249 header.classList.add('scrolled'); 250 } else { 251 header.classList.remove('scrolled'); 252 } 253 } 254 255 // Back to top visibility 256 function handleBackToTop() { 257 if (window.scrollY > 500) { 258 backToTop.classList.add('visible'); 259 } else { 260 backToTop.classList.remove('visible'); 261 } 262 } 263 264 // Scroll event 265 window.addEventListener('scroll', () => { 266 handleHeaderScroll(); 267 handleBackToTop(); 268 }); 269 270 // Back to top click 271 if (backToTop) { 272 backToTop.addEventListener('click', () => { 273 window.scrollTo({ 274 top: 0, 275 behavior: 'smooth' 276 }); 277 }); 278 } 279} 280 281/* ========================================== 282 SCROLL ANIMATIONS (AOS-like) 283 ========================================== */ 284function initAnimations() { 285 const animatedElements = document.querySelectorAll('[data-aos]'); 286 287 // Intersection Observer for animations 288 const observerOptions = { 289 root: null, 290 rootMargin: '0px', 291 threshold: 0.1 292 }; 293 294 const observer = new IntersectionObserver((entries) => { 295 entries.forEach(entry => { 296 if (entry.isIntersecting) { 297 // Add delay if specified
298 const delay = entry.target.getAttribute('data-aos-delay'); 299 if (delay) { 300 setTimeout(() => { 301 entry.target.classList.add('aos-animate'); 302 }, parseInt(delay)); 303 } else { 304 entry.target.classList.add('aos-animate'); 305 } 306 } 307 }); 308 }, observerOptions); 309 310 // Observe all animated elements 311 animatedElements.forEach(el => { 312 observer.observe(el); 313 }); 314 315 // Counter animation for stats 316 const statValues = document.querySelectorAll('.stat-value'); 317 const counterObserver = new IntersectionObserver((entries) => { 318 entries.forEach(entry => { 319 if (entry.isIntersecting) { 320 animateCounter(entry.target); 321 counterObserver.unobserve(entry.target); 322 } 323 }); 324 }, { threshold: 0.5 }); 325 326 statValues.forEach(stat => { 327 counterObserver.observe(stat); 328 }); 329} 330 331function animateCounter(element) { 332 const text = element.textContent; 333 const match = text.match(/[\d,]+/); 334 335 if (!match) return; 336 337 const target = parseFloat(match[0].replace(',', '.')); 338 const duration = 2000; 339 const steps = 60; 340 const increment = target / steps; 341 let current = 0; 342 343 const timer = setInterval(() => { 344 current += increment; 345 if (current >= target) { 346 element.textContent = text; 347 clearInterval(timer); 348 } else { 349 element.textContent = text.replace(match[0], 350 match[0].includes(',') 351 ? current.toFixed(1).replace('.', ',') 352 : Math.floor(current).toString() 353 ); 354 } 355 }, duration / steps); 356} 357
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.