1// ââ Mobile menu ââ 2const menuBtn = document.getElementById('menu-btn'); 3const mobileMenu = document.getElementById('mobile-menu'); 4menuBtn.addEventListener('click', () => mobileMenu.classList.toggle('hidden')); 5 6// ââ HERO SLIDER ââ 7const slides = Array.from(document.querySelectorAll('.hero-slide')); 8const dots = Array.from(document.querySelectorAll('#slider-dots .dot')); 9let current = 0; 10let timer; 11 12function goTo(n) { 13 // Remove active do slide atual 14 slides[current].classList.remove('active'); 15 dots[current].classList.remove('active'); 16 // Novo Ãndice com loop 17 current = (n + slides.length) % slides.length; 18 // Activa novo slide e dot 19 slides[current].classList.add('active'); 20 dots[current].classList.add('active'); 21} 22 23function startTimer() { 24 clearInterval(timer); 25 timer = setInterval(() => { 26 if (!document.hidden) goTo(current + 1); 27 }, 6000); 28} 29 30// Dots navegam pelo slider 31dots.forEach((dot, i) => { 32 dot.addEventListener('click', () => { 33 goTo(i); 34 startTimer(); // reinicia o timer ao clicar 35 }); 36}); 37 38// Inicia autoplay 39startTimer(); 40 41// ââ SEGMENT TABS ââ 42const segTabs = document.querySelectorAll('.seg-tab'); 43const tabPanels = document.querySelectorAll('.tab-panel'); 44 45// ââ Segment Dropdown Toggle & Hover ââ 46document.querySelectorAll('.relative.group').forEach(container => { 47 const btn = container.querySelector('.seg-dropdown-btn'); 48 const menu = container.querySelector('.seg-dropdown-menu'); 49 if (!btn || !menu) return; 50 51 // Click toggle (mainly for mobile) 52 btn.addEventListener('click', (e) => { 53 e.stopPropagation(); 54 const isExpanded = btn.getAttribute('aria-expanded') === 'true'; 55 56 // Close other menus 57 document.querySelectorAll('.seg-dropdown-menu.show').forEach(m => { 58 if (m !== menu) m.classList.remove('show'); 59 }); 60 document.querySelectorAll('.seg-dropdown-btn').forEach(b => { 61 if (b !== btn) b.setAttribute('aria-expanded', 'false'); 62 }); 63 64 btn.setAttribute('aria-expanded', !isExpanded); 65 menu.classList.toggle('show'); 66 }); 67 68 // Hover support (matches megamenu behavior on desktop) 69 container.addEventListener('mouseenter', () => { 70 if (window.innerWidth >= 1024) { 71 btn.setAttribute('aria-expanded', 'true'); 72 // No need to add .show here as CSS group-hover handles visibility 73 } 74 }); 75 76 container.addEventListener('mouseleave', () => { 77 if (window.innerWidth >= 1024) { 78 btn.setAttribute('aria-expanded', 'false'); 79 } 80 }); 81}); 82 83// ââ Segment Dropdown Item Click ââ 84document.querySelectorAll('.seg-dropdown-item').forEach(item => { 85 item.addEventListener('click', () => { 86 const seg = item.dataset.seg; 87 const labelText = item.textContent.trim(); 88 const iconHtml = item.querySelector('svg').outerHTML; 89 90 // Update all labels and icons in triggers 91 document.querySelectorAll('.seg-current-label').forEach(el => { 92 el.textContent = labelText; 93 }); 94 document.querySelectorAll('.seg-current-icon').forEach(el => { 95 el.innerHTML = iconHtml; 96 }); 97 98 // Close all menus 99 document.querySelectorAll('.seg-dropdown-menu').forEach(m => m.classList.remove('show')); 100 document.querySelectorAll('.seg-dropdown-btn').forEach(b => b.setAttribute('aria-expanded', 'false')); 101 102 // Trigger existing tab logic by finding the correct .seg-tab 103 // We target the wide-screen tabs for the click trigger 104 const correspondingTab = document.querySelector(`.seg-tab[data-seg="${seg}"]`); 105 if (correspondingTab) { 106 correspondingTab.click(); // This will trigger panel switch AND scroll 107 } 108 }); 109}); 110 111// ââ Close on click outside ââ 112document.addEventListener('click', () => { 113 document.querySelectorAll('.seg-dropdown-menu').forEach(m => m.classList.remove('show')); 114 document.querySelectorAll('.seg-dropdown-btn').forEach(b => b.setAttribute('aria-expanded', 'false')); 115}); 116 117segTabs.forEach(tab => { 118 tab.addEventListener('click', () => { 119 // Skip scroll/active logic if this is a dropdown toggle button 120 if (tab.classList.contains('seg-dropdown-btn')) return; 121 122 // Remove active from ALL seg-tabs (handles dual desktop/mobile tab set
122s) 123 document.querySelectorAll('.seg-tab.active').forEach(activeTab => { 124 activeTab.classList.remove('active'); 125 activeTab.setAttribute('aria-selected', 'false'); 126 }); 127 const activePanel = document.querySelector('.tab-panel.active'); 128 129 if (activePanel) activePanel.classList.remove('active'); 130 131 tab.classList.add('active'); 132 tab.setAttribute('aria-selected', 'true'); 133 134 // Auto-scroll to content 135 const targetSection = document.getElementById('section-tabs'); 136 if (targetSection) { 137 targetSection.scrollIntoView({ behavior: 'smooth' }); 138 } 139 140 const seg = tab.dataset.seg; 141 const panel = document.getElementById('panel-' + seg); 142 if (panel) { 143 panel.classList.add('active'); 144 145 // Quando o segmento (Empresa/CondomÃnio) fica visÃvel, agendamos o recálculo via requestAnimationFrame 146 // para garantir que o CSS display:block já foi processado pelo browser 147 requestAnimationFrame(() => { 148 const activePlanTab = panel.querySelector('.plan-tab.active'); 149 if (activePlanTab && window.carouselRefreshFns) { 150 const plan = activePlanTab.dataset.plan; 151 if (window.carouselRefreshFns[plan]) window.carouselRefreshFns[plan](); 152 } 153 154 // Garante que o carrossel de serviços (onde existe) seja "acordado" da tela oculta 155 panel.querySelectorAll('.svc-track').forEach(t => { 156 if (typeof t._refresh === 'function') t._refresh(); 157 }); 158 }); 159 } 160 }); 161}); 162 163// ââ SERVICE CARD CAROUSEL (Empresa) â loop infinito ââ 164(function () { 165 const track = document.getElementById('svc-track-empresa'); 166 const prevBtn = document.getElementById('svc-prev-empresa'); 167 const nextBtn = document.getElementById('svc-next-empresa'); 168 if (!track || !prevBtn || !nextBtn) return; 169 170 // 1. Clonar todos os cards originais e appendar (cria "anel" duplo) 171 const originals = Array.from(track.querySelectorAll('.svc-card')); 172 const N = originals.length; 173 originals.forEach(c => track.appendChild(c.cloneNode(true))); 174 // track agora tem 2*N cards 175 176 let current = 0; // Ãndice lógico, pode ir além de N 177 let isResetting = false; 178 179 function perView() { return window.innerWidth >= 1024 ? 3 : 1; } 180 181 function cardWidth() { 182 const c = track.querySelectorAll('.svc-card')[0]; 183 return c ? c.offsetWidth + 18 : 240; 184 } 185 186 // Move o track para o Ãndice dado, com ou sem transição 187 function moveTo(index, animated) { 188 track.style.transition = animated 189 ? 'transform 0.42s cubic-bezier(0.4, 0, 0.2, 1)' 190 : 'none'; 191 track.style.transform = `translateX(-${index * cardWidth()}px)`; 192 } 193 194 // Avança/recua e gerencia o reset silencioso ao cruzar os limites 195 function goTo(index) { 196 current = index; 197 moveTo(current, true); 198 199 // Atualiza estado visual dos botões (sempre habilitados no loop infinito) 200 prevBtn.disabled = false; 201 nextBtn.disabled = false; 202 prevBtn.style.opacity = '1'; 203 nextBtn.style.opacity = '1'; 204 } 205 206 // Após a transição terminar, verifica se precisa de reset silencioso 207 track.addEventListener('transitionend', () => { 208 if (isResetting) return; 209 if (current >= N) { 210 isResetting = true; 211 current -= N; 212 moveTo(current, false); 213 requestAnimationFrame(() => isResetting = false); 214 } else if (current < 0) { 215 isResetting = true; 216 current += N; 217 moveTo(current, false); 218 requestAnimationFrame(() => isResetting = false); 219 } 220 }); 221 222 // ââ Autoplay ââ 223 const INTERVAL = 3500; 224 let autoTimer; 225 226 function startAuto() { 227 clearInterval(autoTimer); 228 autoTimer = setInterval(() => { 229 if (track && track.offsetWidth > 0 && !document.hidden) { 230 goTo(current + 1); 231 } 232 }, INTERVAL); 233 } 234 235 prevBtn.addEventListener('click', () => { goTo(current - 1); startAuto(); }); 236 nextBtn.addEventListener('click', () => { goTo(current + 1); startAuto(); }); 237 238 // Recalculate on resize 239 window.addEventListener('resize', () => moveTo(current, false)); 240 241 // Init 242 goTo(0); 243 startAuto(); 244})(); 245 246// ââ SERVICE CARD CAROUSEL â fábrica reutilizável ââ 247function makeServiceCarousel(trackId, prevId, nextId) { 248 const track = document.getElementById(trackId); 249 const prevBtn = document.getElementById(prevId); 250 const nextBtn = document.getElementById(nextId); 251 if (!track) return; 252 253 const originals = Array.from(track.querySelectorAll('.svc-card')); 254 const N = originals.length;
255 originals.forEach(c => track.appendChild(c.cloneNode(true))); 256 257 let current = 0; 258 let isResetting = false; 259 260 function cardWidth() { 261 const c = track.querySelectorAll('.svc-card')[0]; 262 return c ? c.offsetWidth + 18 : 240; 263 } 264 265 function moveTo(index, animated) { 266 track.style.transition = animated 267 ? 'transform 0.42s cubic-bezier(0.4, 0, 0.2, 1)' 268 : 'none'; 269 track.style.transform = `translateX(-${index * cardWidth()}px)`; 270 } 271 272 function goTo(index) { 273 current = index; 274 moveTo(current, true); 275 if (prevBtn) { 276 prevBtn.disabled = false; 277 prevBtn.style.opacity = '1'; 278 } 279 if (nextBtn) { 280 nextBtn.disabled = false; 281 nextBtn.style.opacity = '1'; 282 } 283 } 284 285 track.addEventListener('transitionend', () => { 286 if (isResetting) return; 287 if (current >= N) { 288 isResetting = true; 289 current -= N; 290 moveTo(current, false); 291 requestAnimationFrame(() => isResetting = false); 292 } else if (current < 0) { 293 isResetting = true; 294 current += N; 295 moveTo(current, false); 296 requestAnimationFrame(() => isResetting = false); 297 } 298 }); 299 300 const INTERVAL = 3500; 301 let autoTimer; 302 function startAuto() { 303 clearInterval(autoTimer); 304 autoTimer = setInterval(() => { 305 if (track && track.offsetWidth > 0 && !document.hidden) { 306 goTo(current + 1); 307 } 308 }, INTERVAL); 309 } 310 311 if (prevBtn) prevBtn.addEventListener('click', () => { goTo(current - 1); startAuto(); }); 312 if (nextBtn) nextBtn.addEventListener('click', () => { goTo(current + 1); startAuto(); }); 313 314 window.addEventListener('resize', () => moveTo(current, false)); 315 316 track._refresh = () => { 317 moveTo(current, false); 318 }; 319 320 goTo(0); 321 startAuto(); 322} 323 324makeServiceCarousel('svc-track-condominio', 'svc-prev-condominio', 'svc-next-condominio'); 325makeServiceCarousel('svc-track-evento', 'svc-prev-evento', 'svc-next-evento'); 326 327// ââ GALLERY CAROUSEL (Evento) ââ 328(function initGalleryCarousel() { 329 const track = document.getElementById('gallery-track-evento'); 330 const prevBtn = document.getElementById('gallery-prev-evento'); 331 const nextBtn = document.getElementById('gallery-next-evento'); 332 const dotsContainer = document.getElementById('gallery-dots-evento'); 333 if (!track) return; 334 335 const slides = Array.from(track.querySelectorAll('.gallery-slide')); 336 const total = slides.length; 337 let current = 0; 338
339 function getItemsPerView() { 340 return window.innerWidth >= 1024 ? 3 : window.innerWidth >= 768 ? 2 : 1; 341 } 342 343 function updateCarousel() { 344 const perView = getItemsPerView(); 345 const maxIndex = Math.max(0, total - perView); 346 if (current > maxIndex) current = maxIndex; 347 if (current < 0) current = 0; 348 349 const gap = 16; // gap-4 is 1rem = 16px 350 const slideW = slides[0].offsetWidth; 351 track.style.transform = `translateX(-${current * (slideW + gap)}px)`; 352 353 if (dotsContainer) { 354 Array.from(dotsContainer.children).forEach((dot, i) => { 355 if (i === current) { 356 dot.className = 'h-2 w-6 bg-azul-tradicao rounded-full transition-all duration-300'; 357 } else { 358 dot.className = 'h-2 w-2 bg-gray-300 rounded-full transition-all duration-300'; 359 } 360 }); 361 } 362 } 363 364 function renderDots() { 365 if (!dotsContainer) return; 366 const perView = getItemsPerView(); 367 const maxIndex = Math.max(0, total - perView); 368 dotsContainer.innerHTML = ''; 369 for (let i = 0; i <= maxIndex; i++) { 370 const dot = document.createElement('button'); 371 dot.className = i === current ? 'h-2 w-6 bg-azul-tradicao rounded-full transition-all duration-300' : 'h-2 w-2 bg-gray-300 rounded-full transition-all duration-300'; 372 dot.setAttribute('aria-label', 'Slide ' + (i + 1)); 373 dot.addEventListener('click', () => { current = i; updateCarousel(); startAuto(); }); 374 dotsContainer.appendChild(dot); 375 } 376 } 377 378 let autoTimer; 379 function startAuto() { 380 clearInterval(autoTimer); 381 autoTimer = setInterval(() => { 382 if (track && track.offsetWidth > 0 && !document.hidden) {
383 const maxIndex = Math.max(0, total - getItemsPerView()); 384 current = current >= maxIndex ? 0 : current + 1; 385 updateCarousel(); 386 } 387 }, 4000); 388 } 389 390 if (prevBtn) prevBtn.addEventListener('click', () => { 391 const maxIndex = Math.max(0, total - getItemsPerView()); 392 current = current <= 0 ? maxIndex : current - 1; 393 updateCarousel(); startAuto(); 394 }); 395 if (nextBtn) nextBtn.addEventListener('click', () => { 396 const maxIndex = Math.max(0, total - getItemsPerView()); 397 current = current >= maxIndex ? 0 : current + 1; 398 updateCarousel(); startAuto(); 399 }); 400 401 window.addEventListener('resize', () => { 402 current = 0; 403 renderDots(); 404 updateCarousel(); 405 }); 406 407 renderDots(); 408 updateCarousel(); 409 startAuto(); 410})(); 411// ââ PLAN TABS ââ 412const carouselRefreshFns = {}; 413window.carouselRefreshFns = carouselRefreshFns; 414 415document.querySelectorAll('.plan-tab').forEach(tab => { 416 tab.addEventListener('click', () => { 417 const parentSegment = tab.closest('.tab-panel'); 418 if (!parentSegment) return; 419 420 parentSegment.querySelectorAll('.plan-tab').forEach(t => { t.classList.remove('active'); t.setAttribute('aria-selected', 'false'); }); 421 parentSegment.querySelectorAll('.plan-panel').forEach(p => p.classList.remove('active')); 422 423 tab.classList.add('active'); 424 tab.setAttribute('aria-selected', 'true'); 425 426 const plan = tab.dataset.plan; 427 const targetPanel = document.getElementById('plan-panel-' + plan); 428 if (targetPanel) targetPanel.classList.add('active'); 429 430 if (window.carouselRefreshFns[plan]) window.carouselRefreshFns[plan](); 431 }); 432}); 433 434// ââ PLAN CARDS CAROUSEL (mobile/tablet) ââ 435(function initPlanCarousels() { 436 const configs = [ 437 { trackId: 'plan-track-internet', dotsId: 'plan-dots-internet', planKey: 'internet' }, 438 { trackId: 'plan-track-adicionais', dotsId: 'plan-dots-adicionais', planKey: 'adicionais' }, 439 { trackId: 'plan-track-internet-condominio', dotsId: 'plan-dots-internet-condominio', planKey: 'internet-condominio' }, 440 { trackId: 'plan-track-banda-larga-condominio', dotsId: 'plan-dots-banda-larga-condominio', planKey: 'banda-larga-condominio' }, 441 { trackId: 'plan-track-adicionais-condominio', dotsId: 'plan-dots-adicionais-condominio', planKey: 'adicionais-condominio' }, 442 { trackId: 'plan-track-tv-condominio', dotsId: 'plan-dots-tv-condominio', planKey: 'tv-condominio' }, 443 { trackId: 'plan-track-internet-evento', dotsId: 'plan-dots-internet-evento', planKey: 'internet-evento' }, 444 { trackId: 'plan-track-adicionais-evento', dotsId: 'plan-dots-adicionais-evento', planKey: 'adicionais-evento' }, 445 { trackId: 'plan-track-tv-evento', dotsId: 'plan-dots-tv-evento', planKey: 'tv-evento' }, 446 ]; 447 448 configs.forEach(({ trackId, dotsId, planKey }) => { 449 const track = document.getElementById(trackId); 450 if (!track) return; 451 452 const outer = track.closest('.plan-carousel-outer'); 453 const dotsContainer = dotsId ? document.getElementById(dotsId) : null; 454 const cards = Array.from(track.querySelectorAll('.plan-card')); 455 const total = cards.length; 456 let current = 0; 457 let startX = 0; 458 let isDragging = false; 459 460 function getSlideWidth() { 461 const s = getComputedStyle(outer); 462 let w = outer.clientWidth 463 - parseFloat(s.paddingLeft || 0) 464 - parseFloat(s.paddingRight || 0); 465 if (w <= 0) { 466 let el = outer.parentElement; 467 while (el && el.clientWidth === 0) el = el.parentElement; 468 if (el) w = el.clientWidth - parseFloat(s.paddingLeft || 0) - parseFloat(s.paddingRight || 0); 469 } 470 return Math.max(w, 1); 471 } 472 473 function setCardWidths() { 474 const w = getSlideWidth(); 475 cards.forEach(card => { 476 card.style.width = w + 'px'; 477 card.style.minWidth = w + 'px'; 478 card.style.flexShrink = '0'; 479 }); 480 } 481 482 function updateDots() { 483 if (!dotsContainer) return; 484 Array.from(dotsContainer.children).forEach((dot, i) => { 485 dot.className = i === current ? 'plan-dot plan-dot-active' : 'plan-dot'; 486 }); 487 } 488 489 function renderDots() { 490 if (!dotsContainer) return; 491 dotsContainer.innerHTML = ''; 492 for (let i = 0; i < total; i++) { 493 const dot = document.createElement('button'); 494 dot.className = i === 0 ? 'plan-dot plan-dot-active' : 'plan-dot'; 495 dot.setAttribute('aria-label', 'Slide ' + (i + 1)); 496 dot.addEventListener('click', () => { goTo(i); }); 497 dotsContainer.appendChild(dot); 498 } 499 } 500 501 function goTo(idx) { 502 if (idx < 0) current = total - 1; 503 else if (idx >= total) current = 0; 504 else current = idx; 505 506 const w = getSlideWidth(); 507 track.style.transform = 'translateX(-' + (current * w) + 'px)'; 508 updateDots(); 509 } 510 511 function refresh() { 512 setCardWidths(); 513 goTo(current); 514 } 515 516 carouselRefreshFns[planKey] = refresh; 517 518 document.querySelectorAll('.plan-carousel-btn[data-carousel="' + trackId + '"]').forEach(btn => { 519 btn.addEventListener('click', () => { 520 if (btn.classList.contains('prev')) goTo(current - 1); 521 else goTo(current + 1); 522 }); 523 }); 524 525 track.addEventListener('touchstart', e => { startX = e.touches[0].clientX; isDragging = true; }, { passive: true }); 526 track.addEventListener('touchend', e => { 527 if (!isDragging) return; 528 const diff = startX - e.changedTouches[0].clientX; 529 if (Math.abs(diff) > 40) goTo(current + (diff > 0 ? 1 : -1)); 530 isDragging = false;
531 }); 532 533 window.addEventListener('resize', () => { 534 setCardWidths(); 535 goTo(current); 536 }); 537 538 setCardWidths(); 539 renderDots(); 540 goTo(0); 541 }); 542})(); 543 544// ââ BACK TO TOP ââ 545const backToTop = document.getElementById('back-to-top'); 546window.addEventListener('scroll', () => { 547 backToTop.classList.toggle('visible', window.scrollY > 300); 548}); 549backToTop.addEventListener('click', () => { 550 window.scrollTo({ top: 0, behavior: 'smooth' }); 551}); 552 553// ââ COOKIE NOTICE ââ 554(function initCookieNotice() { 555 if (localStorage.getItem('cookieAccepted')) return; 556 557 const cookieBar = document.createElement('div'); 558 cookieBar.className = 'cookie-notice-bar'; 559 cookieBar.innerHTML = ` 560 <div class="cookie-notice-content"> 561 <p>Utilizamos cookies para melhorar sua experiência. Ao continuar, você aceita nossa <a href="/public/politica-privacidade.html">PolÃtica de Privacidade</a>.</p> 562 <button id="accept-cookies-btn" class="cookie-notice-btn">Entendi e concordo</button> 563 </div> 564 `; 565 566 document.body.appendChild(cookieBar); 567 568 document.getElementById('accept-cookies-btn').addEventListener('click', () => { 569 localStorage.setItem('cookieAccepted', 'true'); 570 cookieBar.classList.add('hide'); 571 setTimeout(() => cookieBar.remove(), 300); 572 }); 573})(); 574 575// ââ HASH NAVIGATION (CondomÃnios) ââ 576function checkHashAndNavigate() { 577 if (window.location.hash === '#condominios') { 578 const tabCondominio = document.querySelector('.seg-tab[data-seg="condominio"]'); 579 if (tabCondominio) { 580 setTimeout(() => { 581 // Ignora a animação e força o scroll da tab 582 tabCondominio.click(); 583 }, 150); 584 } 585 } 586} 587 588window.addEventListener('load', checkHashAndNavigate); 589window.addEventListener('hashchange', checkHashAndNavigate);
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.