1/* UHOST 2027 â interações. Vanilla JS, sem dependências. 2 Movimento sutil e elegante (regra do design system): fade/slide, contadores, 3 texto digitando, accordion. Respeita prefers-reduced-motion. */ 4(function () { 5 'use strict'; 6 var reduz = window.matchMedia && window.matchMedia('(prefers-reduced-motion: reduce)').matches; 7 8 /* ---------- tema claro/escuro ---------- */ 9 document.querySelectorAll('[data-tema-btn]').forEach(function (btn) { 10 btn.addEventListener('click', function () { 11 var root = document.documentElement; 12 var claro = root.getAttribute('data-tema') === 'claro'; 13 if (claro) { root.removeAttribute('data-tema'); } else { root.setAttribute('data-tema', 'claro'); } 14 try { localStorage.setItem('uh-tema', claro ? 'escuro' : 'claro'); } catch (e) {} 15 }); 16 }); 17 18 /* ---------- header: sombra/blur ao rolar ---------- */ 19 var hd = document.querySelector('[data-header]'); 20 if (hd) { 21 var onScroll = function () { hd.style.boxShadow = window.scrollY > 8 ? 'var(--shadow-card)' : 'none'; }; 22 onScroll(); 23 window.addEventListener('scroll', onScroll, { passive: true }); 24 } 25 26 /* ---------- menu mobile ---------- */ 27 var burger = document.querySelector('[data-burger]'); 28 if (burger) { 29 burger.addEventListener('click', function () { 30 var aberto = document.body.classList.toggle('is-menu'); 31 burger.setAttribute('aria-expanded', aberto ? 'true' : 'false'); 32 document.body.style.overflow = aberto ? 'hidden' : ''; 33 }); 34 document.querySelectorAll('[data-mnav] a').forEach(function (a) { 35 a.addEventListener('click', function () { 36 document.body.classList.remove('is-menu'); 37 burger.setAttribute('aria-expanded', 'false'); 38 document.body.style.overflow = ''; 39 }); 40 }); 41 } 42 43 /* ---------- reveal on scroll ---------- */ 44 var revs = document.querySelectorAll('.rv'); 45 if (reduz || !('IntersectionObserver' in window)) { 46 revs.forEach(function (el) { el.classList.add('on'); }); 47 } else { 48 var stagger = new WeakMap(); 49 var io = new IntersectionObserver(function (entries) { 50 entries.forEach(function (en) { 51 if (en.isIntersecting) { 52 en.target.classList.add('on'); 53 io.unobserve(en.target); 54 } 55 }); 56 }, { threshold: 0.12, rootMargin: '0px 0px -8% 0px' }); 57 /* dá um pequeno atraso escalonado a grupos de irmãos (cards em grade) */ 58 revs.forEach(function (el) { 59 var p = el.parentElement, i = 0, n = el; 60 while ((n = n.previousElementSibling)) { if (n.classList && n.classList.contains('rv')) i++; } 61 if (i > 0 && i < 8) el.style.transitionDelay = (i * 0.07).toFixed(2) + 's'; 62 io.observe(el); 63 }); 64 } 65 66 /* ---------- contadores animados ---------- 67 Padrão: 1,6s. Com data-dur, vira ODÃMETRO EM RAJADAS: sobe de perto do 68 alvo até ele em poucas rajadas e, dali em diante, NÃO para de vez: segue 69 subindo um passo visÃvel com pausas cada vez maiores (número vivo). 70 Só começa quando o usuário Và os números (IntersectionObserver). 71 72 ð¨ O disparo parte de 90% do alvo, NÃO de zero (medido em 2026-09-10: 73 partindo do 7% a faixa levava ~40s para chegar em 14,7 mil e o visitante 74 lia "1,0 mil rádios atendidas" â o número que ESTà na tela é a única prova 75 social do site, porque a marca proÃbe depoimento. Contador que subestima a 76 empresa em 14x é pior do que contador nenhum). O movimento continua; o que 77 mudou é de ONDE ele parte e em quanto tempo alcança (~3s). */ 78 function animaContador(el) { 79 var bruto = el.getAttribute('data-count') || el.textContent; 80 var sufixo = el.getAttribute('data-suffix') || ''; 81 var numerico = /^[\d.,]+$/.test(bruto); 82 if (!numerico || reduz) { el.textContent = bruto + sufixo; return; } 83 var alvo = parseFloat(bruto.replace(/\./g, '').replace(',', '.')); 84 var decimais = bruto.indexOf(',') >= 0 ? 1 : 0; 85 var fmt = function (n) { return n.toLocaleString('pt-BR', { minimumFractionDigits: decimais, maximumFractionDigits: decimais }); }; 86 var ultimo = -1; 87 function pinta(v) { 88 var m = decimais ? Math.round(v * 10) : Math.round(v); 89 if (m !== ultimo) { el.textContent = fmt(v) + sufixo; ultimo = m; } 90 } 91 var easeOut = function (p) { return 1 - Math.pow(1 - p, 3); }; 92 var easeIO = function (p) { return p < .5 ? 4 * p * p * p : 1 - Math.pow(-2 * p + 2, 3) / 2; }; 93 var atual = 0; 94 function anima(de, ate, dur, easing, fim) { 95 var t0 = null; 96 function tick(t) { 97 if (t0 === null) t0 = t; 98 var p = Math.min((t - t0) / dur, 1); 99 pinta(de + (ate - de) * easing(p)); 100 if (p < 1) { requestAnimationFrame(tick); } else { atual = ate; if (fim) fim(); } 101 } 102 requestAnimationFrame(tick); 103 } 104 105 if (!el.getAttribute('data-dur')) { 106 /* Contador comum: parte dos mesmos 90% (é o valor que já está pintado na 107 largada). Partindo de zero, o "99,9% de uptime" caÃa para 0,0% na frente 108 do visitante antes de subir â pior do que não animar. */ 109 atual = alvo * 0.9; 110 anima(atual, alvo, 1200, easeOut, null); 111 return; 112 } 113 114 /* modo rajadas */ 115 var passo = decimais ? 0.1 : 1; /* menor incremento visÃvel */ 116 var teto = alvo * 1.1; /* daqui em diante vira conta-gotas */ 117 function deriva(pausa) { 118 /* nunca cessa: sobe um passo visÃvel com pausas que crescem só até 18s; 119 passado o teto (+10% do alvo), o passo continua mas a cada 5min */ 120 setTimeout(function () { 121 anima(atual, atual + passo, 700, easeIO, function () { 122 deriva(atual < teto ? Math.min(pausa * 1.2, 18000) : 300000); 123 }); 124 }, pausa * (0.85 + Math.random() * 0.3)); 125 } 126 function proximaRajada() { 127 var resta = alvo - atual; 128 if (resta <= 0) { pinta(alvo); deriva(2200); return; } 129 var pausa = 320 + Math.random() * 480; /* respiro parado */ 130 setTimeout(function () { 131 var pedaco = resta * (0.34 + Math.random() * 0.30); /* sobe um pedaço */ 132 if (resta - pedaco < alvo * 0.02) pedaco = resta; /* reta final: alcança o número base */ 133 anima(atual, atual + pedaco, 420 + Math.random() * 380, easeIO, proximaRajada); 134 }, pausa); 135 } 136 var largada = alvo * 0.9; /* nunca começa em zero: ver o comentário acima */ 137 atual = largada; pinta(largada); 138 anima(largada, alvo * 0.97, 1200, easeOut, proximaRajada); 139 } 140 var counters = document.querySelectorAll('[data-count]'); 141 if (counters.length) { 142 if (!('IntersectionObserver' in window)) {
143 counters.forEach(animaContador); 144 } else { 145 var ioc = new IntersectionObserver(function (entries) { 146 entries.forEach(function (en) { 147 if (en.isIntersecting) { animaContador(en.target); ioc.unobserve(en.target); } 148 }); 149 }, { threshold: 0.5 }); 150 counters.forEach(function (el) { 151 /* Antes de disparar mostra 90% do alvo, não zero: a faixa fica logo abaixo 152 da dobra e no celular muita gente passa por ela antes do IntersectionObserver 153 acordar â quem via "0,0% de uptime" lia isso como o nosso uptime. */ 154 if (!reduz && /^[\d.,]+$/.test(el.getAttribute('data-count') || '')) { 155 var suf = el.getAttribute('data-suffix') || ''; 156 var cru = el.getAttribute('data-count') || ''; 157 var dec = cru.indexOf(',') >= 0 ? 1 : 0; 158 var ini = parseFloat(cru.replace(/\./g, '').replace(',', '.')) * 0.9; 159 el.textContent = ini.toLocaleString('pt-BR', { minimumFractionDigits: dec, maximumFractionDigits: dec }) + suf; 160 } 161 ioc.observe(el); 162 }); 163 } 164 } 165 166 /* ---------- accordion (FAQ) ---------- */ 167 document.querySelectorAll('[data-faq]').forEach(function (faq) { 168 faq.querySelectorAll('.uh-q').forEach(function (q) { 169 var btn = q.querySelector('.uh-q__b'); 170 var painel = q.querySelector('.uh-q__a'); 171 /* abre o que já vem com is-open */ 172 if (q.classList.contains('is-open')) painel.style.maxHeight = painel.scrollHeight + 'px'; 173 btn.addEventListener('click', function () { 174 var abrindo = !q.classList.contains('is-open'); 175 faq.querySelectorAll('.uh-q').forEach(function (o) { 176 o.classList.remove('is-open'); 177 o.querySelector('.uh-q__b').setAttribute('aria-expanded', 'false'); 178 o.querySelector('.uh-q__a').style.maxHeight = null; 179 }); 180 if (abrindo) { 181 q.classList.add('is-open'); 182 btn.setAttribute('aria-expanded', 'true'); 183 painel.style.maxHeight = painel.scrollHeight + 'px'; 184 } 185 }); 186 }); 187 }); 188 189 /* ---------- celular do app: troca de rádio ---------- */ 190 document.querySelectorAll('.uh-cels').forEach(function (grupo) { 191 var bts = [].slice.call(grupo.querySelectorAll('[data-cel-b]')); 192 var fs = [].slice.call(grupo.querySelectorAll('[data-cel]')); 193 if (!bts.length || bts.length !== fs.length) return; 194 function mostra(i, focar) { 195 bts.forEach(function (b, j) { 196 b.classList.toggle('is-on', j === i); 197 b.setAttribute('aria-selected', j === i ? 'true' : 'false'); 198 b.tabIndex = j === i ? 0 : -1; 199 fs[j].classList.toggle('is-on', j === i); 200 }); 201 if (focar) bts[i].focus(); 202 } 203 bts.forEach(function (b, i) { 204 b.addEventListener('click', function () { mostra(i, false); }); 205 b.addEventListener('keydown', function (e) { 206 var n = bts.length, alvo = -1; 207 if (e.key === 'ArrowRight' || e.key === 'ArrowDown') alvo = (i + 1) % n; 208 else if (e.key === 'ArrowLeft' || e.key === 'ArrowUp') alvo = (i - 1 + n) % n; 209 if (alvo < 0) return; 210 e.preventDefault(); mostra(alvo, true); 211 }); 212 }); 213 }); 214 215 /* ---------- [data-troca]: botão -> painel (uh-perfis, uh-kbps) ---------- 216 Um só bloco serve aos dois componentes de 10/09. Padrão de tablist da 217 WAI-ARIA: seta troca o painel, Home/End vão à s pontas, e o foco segue a 218 seleção (tabindex roving). Sem JS a seção continua legÃvel â o primeiro 219 par já sai marcado do PHP e os outros painéis ficam no HTML para o Google. */ 220 document.querySelectorAll('[data-troca]').forEach(function (grupo) { 221 var bts = [].slice.call(grupo.querySelectorAll('[data-troca-b]')); 222 var pns = [].slice.call(grupo.querySelectorAll('.uh-troca2__p')); 223 if (!bts.length || bts.length !== pns.length) return; 224 225 function mostra(i, focar) { 226 bts.forEach(function (b, j) { 227 var on = j === i; 228 b.classList.toggle('is-on', on); 229 b.setAttribute('aria-selected', on ? 'true' : 'false'); 230 b.tabIndex = on ? 0 : -1; 231 pns[j].hidden = !on; 232 pns[j].classList.toggle('is-on', on); 233 }); 234 if (focar) bts[i].focus(); 235 }
236 bts.forEach(function (b, i) { 237 b.addEventListener('click', function () { mostra(i, false); }); 238 b.addEventListener('keydown', function (e) { 239 var k = e.key, n = bts.length, alvo = -1; 240 if (k === 'ArrowDown' || k === 'ArrowRight') alvo = (i + 1) % n; 241 else if (k === 'ArrowUp' || k === 'ArrowLeft') alvo = (i - 1 + n) % n; 242 else if (k === 'Home') alvo = 0; 243 else if (k === 'End') alvo = n - 1; 244 if (alvo < 0) return; 245 e.preventDefault(); mostra(alvo, true); 246 }); 247 }); 248 }); 249 250 /* ---------- cards de plano: lista recolhida no celular ---------- 251 O HTML sai com <details open> (sem JS a lista fica inteira, e é assim que o 252 Google a lê). Aqui, e só abaixo de 720px, os três fecham: empilhados, os 253 cards somavam 3.337px â quatro telas de iPhone antes de o visitante ver as 254 três opções. Quem abriu um mantém aberto na volta do resize. */ 255 var incs = document.querySelectorAll('.uh-plan__inc'); 256 if (incs.length) { 257 var estreito = window.matchMedia('(max-width:720px)'); 258 var aplicaInc = function () { 259 incs.forEach(function (d) { 260 if (d.dataset.tocado === '1') return; 261 d.open = !estreito.matches; 262 }); 263 }; 264 incs.forEach(function (d) { 265 d.addEventListener('toggle', function () { 266 if (estreito.matches) d.dataset.tocado = '1'; 267 }); 268 }); 269 aplicaInc(); 270 if (estreito.addEventListener) { estreito.addEventListener('change', aplicaInc); } 271 else if (estreito.addListener) { estreito.addListener(aplicaInc); } 272 } 273 274 /* ---------- hero: texto digitando ---------- 275 ð¨ O <h1> depende desta palavra para fazer sentido ("...emissora grande: site"). 276 Enquanto ela apaga, o tÃtulo fica truncado nos dois-pontos â então o ciclo 277 dá UMA volta pelas palavras, volta para a primeira e PARA de vez (medido em 278 2026-09-10: em loop infinito o tÃtulo passava ~48% do tempo incompleto, e 279 era assim que ele aparecia em print e para quem voltava à aba). */ 280 var type = document.querySelector('[data-type]'); 281 if (type) { 282 var palavras; 283 try { palavras = JSON.parse(type.getAttribute('data-type')); } catch (e) { palavras = []; } 284 if (reduz || !palavras.length) { 285 type.textContent = palavras[0] || ''; 286 } else { 287 var wi = 0, txt = '', apagando = false, voltas = 0; 288 (function loop() { 289 var palavra = palavras[wi]; 290 if (!apagando) { 291 txt = palavra.slice(0, txt.length + 1); 292 type.textContent = txt; 293 if (txt === palavra) { 294 /* deu a volta e voltou à primeira palavra: assenta e não mexe mais */ 295 if (voltas >= 1 && wi === 0) return; 296 apagando = true; setTimeout(loop, 2200); return; 297 } 298 } else { 299 txt = palavra.slice(0, txt.length - 1); 300 type.textContent = txt; 301 if (txt === '') { 302 apagando = false; 303 wi = (wi + 1) % palavras.length; 304 if (wi === 0) voltas++; 305 } 306 } 307 setTimeout(loop, apagando ? 45 : 95); 308 })(); 309 } 310 } 311 312 /* ---------- lightbox: ampliar prints ao clicar ---------- */ 313 var lb = document.querySelector('[data-lb]'); 314 if (lb) { 315 var lbImg = lb.querySelector('.uh-lb__img'); 316 var lbCap = lb.querySelector('.uh-lb__cap'); 317 var alvos = '.uh-shot, .uh-show__media, .uh-strip, .uh-phone, .uh-wide, .uh-frame'; 318 function abrirLb(src, cap) { 319 lbImg.src = src; lbImg.alt = cap || ''; 320 lbCap.textContent = cap || ''; 321 lb.classList.add('is-open'); lb.setAttribute('aria-hidden', 'false'); 322 document.body.style.overflow = 'hidden'; 323 } 324 function fecharLb() { 325 lb.classList.remove('is-open'); lb.setAttribute('aria-hidden', 'true'); 326 document.body.style.overflow = ''; 327 setTimeout(function () { if (!lb.classList.contains('is-open')) lbImg.src = ''; }, 320); 328 } 329 document.addEventListener('click', function (ev) { 330 var el = ev.target.closest(alvos); 331 if (!el || lb.contains(el)) return; 332 if (el.closest('a')) return; /* imagem dentro de link navega, não amplia */ 333 // fonte em alta: data-zoom (showcase/strip) ou o <img> interno (moldura/phone) 334 var src = el.getAttribute('data-zoom'); 335 var cap = el.getAttribute('data-cap'); 336 if (!src) { var im = el.querySelector('img'); if (im) { src = im.currentSrc || im.src; cap = cap || im.alt; } } 337 if (src) { ev.preventDefault(); abrirLb(src, cap); } 338 }); 339 lb.addEventListener('click', function (ev) { if (ev.target === lb || ev.target.closest('[data-lb-close]')) fecharLb(); }); 340 document.addEventListener('keydown', function (ev) { if (ev.key === 'Escape' && lb.classList.contains('is-open')) fecharLb(); }); 341 } 342 343 /* ---------- página Alexa: conversa "você diz â Alexa responde" ---------- */ 344 var say = document.querySelector('[data-uhx-say]'); 345 var play = document.querySelector('[data-uhx-play]'); 346 if (say && play) { 347 var radios = ['a minha rádio', 'Nativa FM', 'Band FM Campinas', 'a sua rádio', 'NotÃcia FM']; 348 if (reduz) { 349 say.textContent = radios[0]; play.textContent = radios[0]; 350 } else { 351 var ri = 0; 352 function digita(el, txt, vel, fim) { 353 var i = 0; 354 (function passo() { 355 el.textContent = txt.slice(0, i++); 356 if (i <= txt.length) { setTimeout(passo, vel); } else if (fim) { setTimeout(fim, 0); } 357 })(); 358 } 359 (function cicloAlexa() { 360 var nome = radios[ri]; 361 play.textContent = ''; 362 digita(say, nome, 85, function () { 363 setTimeout(function () { 364 digita(play, nome, 55, function () { 365 setTimeout(function () { 366 say.textContent = ''; play.textContent = ''; 367 ri = (ri + 1) % radios.length; 368 setTimeout(cicloAlexa, 500); 369 }, 2600); 370 }); 371 }, 900); 372 }); 373 })(); 374 } 375 } 376 377 /* ---------- leque de celulares: o scroll abre o leque ---------- */ 378 /* o leque só é dirigido pelo scroll em tela larga; no celular o CSS o deixa 379 aberto e parado (ver o comentário em .uh-fan no uhost.css). */ 380 var fans = document.querySelectorAll('[data-fan]'); 381 if (fans.length && !reduz && window.matchMedia('(min-width:721px)').matches) { 382 var fanTick = false;
383 function atualizaFan() { 384 fanTick = false; 385 var vh = window.innerHeight; 386 fans.forEach(function (f) { 387 var r = f.getBoundingClientRect(); 388 /* 0 quando o leque entra embaixo; 1 quando chega ao meio da tela */ 389 var p = (vh - r.top - vh * 0.18) / (vh * 0.55); 390 p = Math.max(0, Math.min(1, p)); 391 f.style.setProperty('--fanp', p.toFixed(3)); 392 }); 393 } 394 window.addEventListener('scroll', function () { 395 if (!fanTick) { fanTick = true; requestAnimationFrame(atualizaFan); } 396 }, { passive: true }); 397 atualizaFan(); 398 } 399 400 /* ---------- esferas do hero: parallax sutil no mouse ---------- */ 401 if (!reduz && window.matchMedia('(pointer:fine)').matches) { 402 var orbs = document.querySelectorAll('.uh-orb'); 403 if (orbs.length) { 404 window.addEventListener('mousemove', function (ev) { 405 var x = (ev.clientX / window.innerWidth - 0.5); 406 var y = (ev.clientY / window.innerHeight - 0.5); 407 orbs.forEach(function (o, i) { 408 var f = (i + 1) * 14; 409 o.style.transform = 'translate(' + (x * f).toFixed(1) + 'px,' + (y * f).toFixed(1) + 'px)'; 410 }); 411 }, { passive: true }); 412 } 413 } 414})(); 415 416 417/* ===== live demo do hero (#uhld) ===== */ 418(function(){ 419 const root=document.querySelector('[data-demo]'); if(!root) return; 420 const prev=root.querySelector('[data-prev]'), scr=root.querySelector('[data-scroll]'); 421 const cursor=root.querySelector('[data-cursor]'), sceneEl=root.querySelector('[data-scene]'), actEl=root.querySelector('[data-act]'); 422 const progress=root.querySelector('[data-progress]'); 423 const reduce=matchMedia('(prefers-reduced-motion:reduce)').matches; 424 const swBtns=[...root.querySelectorAll('[data-sw] button')]; 425 426 function tab(name){root.querySelectorAll('.tabs button').forEach(b=>b.classList.toggle('on',b.dataset.tab===name)); 427 root.querySelectorAll('.pane').forEach(p=>p.classList.toggle('on',p.dataset.pane===name));} 428 function hot(sel){root.querySelectorAll('.ctl').forEach(c=>c.classList.toggle('hot',c.dataset.c===sel));} 429 function moveTo(el,ox=.5,oy=.5){if(!el)return;const pr=prev.getBoundingClientRect(),r=el.getBoundingClientRect(); 430 cursor.style.transform=`translate(${r.left-pr.left+r.width*ox}px,${r.top-pr.top+r.height*oy}px)`;} 431 function click(){cursor.classList.remove('click');void cursor.offsetWidth;cursor.classList.add('click');} 432 function scrollTo(px){scr.style.transform=`translateY(${-px}px)`;} 433 const ICO={cor:'#F',fonte:'#F',tema:'#F'}; 434 function act(n,label){actEl.querySelector('i').textContent=n;actEl.querySelector('span').textContent=label;actEl.classList.add('show');} 435 function scene(strong,em){sceneEl.querySelector('b').textContent=strong;sceneEl.querySelector('em').textContent=em||'';sceneEl.classList.add('show');} 436 437 function color(hex,btn){prev.style.setProperty('--brand',hex);swBtns.forEach(b=>b.classList.toggle('sel',b===btn));} 438 function font(p){prev.style.setProperty('--fs',(0.82+p*0.85).toFixed(2));root.querySelector('[data-f]').style.width=(p*100)+'%';root.querySelector('[data-k]').style.left=(p*100)+'%';} 439 function tema(t,btn){prev.style.setProperty('--rad',t==='wave'?'2px':t==='neon'?'20px':'14px'); 440 prev.style.setProperty('--heroBg',t==='neon'?'linear-gradient(160deg,#171a2e,#101117)':t==='wave'?'#0c0d10':'#101117'); 441 root.querySelectorAll('[data-tema] button').forEach(b=>b.classList.toggle('sel',b===btn));} 442 function player(p,btn){prev.classList.toggle('player-b',p==='b');root.querySelectorAll('[data-plr] button').forEach(b=>b.classList.toggle('sel',b===btn));} 443 function mod(name,on){const m=root.querySelector('[data-mod="'+name+'"]');if(m)m.classList.toggle('show',on!==false); 444 const r=root.querySelector('[data-r="'+name+'"]');if(r)r.classList.add('on');} 445 function toggle(name){root.querySelector('[data-'+name+']').classList.add('show');const r=root.querySelector('[data-r="'+name+'"]');if(r)r.classList.add('on');} 446 const ORD={slider:10,top:30,news:40,prog:50,pod:60,banner:70}; 447 function setOrder(){root.querySelectorAll('.mod').forEach(m=>{m.style.transform='';m.style.transition=''; 448 m.style.order=m.classList.contains('mod--hero')?20:ORD[m.dataset.mod];});} 449 function flip(mut){const mods=[...root.querySelectorAll('.mod')];const b=new Map(mods.map(m=>[m,m.getBoundingClientRect().top]));mut(); 450 mods.forEach(m=>{const dy=b.get(m)-m.getBoundingClientRect().top;if(Math.abs(dy)>1){m.style.transition='none';m.style.transform='translateY('+dy+'px)'; 451 requestAnimationFrame(()=>{m.style.transition='transform .55s var(--ease)';m.style.transform='';});}});} 452 function lift(name,on){const m=root.querySelector('[data-mod="'+name+'"]');
452if(m)m.classList.toggle('lift',on!==false);} 453 function moveMod(name,ord){flip(()=>{const m=root.querySelector('[data-mod="'+name+'"]');if(m)m.style.order=ord;});} 454 // grips (aparecem no modo edição) 455 root.querySelectorAll('.mod[data-mod]').forEach(m=>{const g=document.createElement('span');g.className='grip'; 456 g.innerHTML='<svg width="13" height="13" viewBox="0 0 24 24" fill="currentColor"><circle cx="9" cy="6" r="1.4"/><circle cx="15" cy="6" r="1.4"/><circle cx="9" cy="12" r="1.4"/><circle cx="15" cy="12" r="1.4"/><circle cx="9" cy="18" r="1.4"/><circle cx="15" cy="18" r="1.4"/></svg>'; 457 m.appendChild(g);}); 458 459 // ---------- timeline (segundos) ---------- 460 const q=s=>root.querySelector(s); 461 const L=[ 462 // ATO 1 â Aparência 463 [0.2, ()=>{act('1','A cara da sua rádio');tab('aparencia');scene('Este é o site da sua rádio','tudo por um painel');}], 464 [1.8, ()=>{hot('cor');moveTo(swBtns[1]);}], 465 [2.6, ()=>{click();color('#7C5CFF',swBtns[1]);scene('Cor da marca','muda no site na hora');}], 466 [3.4, ()=>{moveTo(swBtns[2]);}],[3.9,()=>{click();color('#22C55E',swBtns[2]);}], 467 [4.6, ()=>{moveTo(swBtns[0]);}],[5.1,()=>{click();color('#FAC643',swBtns[0]);}], 468 [5.8, ()=>{hot('fonte');moveTo(q('[data-k]'));scene('Tipografia','arraste e o texto cresce');}], 469 [6.6, ()=>{font(0.9);}],[7.5,()=>{font(0.2);}],[8.3,()=>{font(0.5);}], 470 [9.1, ()=>{hot('tema');moveTo(q('[data-tema] [data-t=neon]'));scene('Troque o tema','4 estilos, um clique');}], 471 [9.9, ()=>{click();tema('neon',q('[data-tema] [data-t=neon]'));}], 472 [10.9,()=>{moveTo(q('[data-tema] [data-t=pulse]'));}],[11.4,()=>{click();tema('pulse',q('[data-tema] [data-t=pulse]'));}], 473 [12.1,()=>{hot('player');moveTo(q('[data-plr] [data-p=b]'));scene('Estilo do player','sem tocar em código');}], 474 [12.8,()=>{click();player('b',q('[data-plr] [data-p=b]'));}], 475 [13.7,()=>{moveTo(q('[data-plr] [data-p=a]'));}],[14.2,()=>{click();player('a',q('[data-plr] [data-p=a]'));}], 476 // ATO 2 â Conteúdo 477 [15.0,()=>{act('2','O site se enche de conteúdo');tab('conteudo');hot('');moveTo(q('[data-r=top]'));scene('Top Músicas','o ouvinte vota, o ranking se monta');}], 478 [15.8,()=>{click();mod('top');scrollTo(150);}], 479 [17.4,()=>{moveTo(q('[data-r=news]'));scene('NotÃcias','entram sozinhas, por RSS');}], 480 [18.2,()=>{click();mod('news');scrollTo(330);}], 481 [19.8,()=>{moveTo(q('[data-r=prog]'));scene('Programação','quem está no ar agora');}], 482 [20.6,()=>{click();mod('prog');scrollTo(490);}], 483 [22.0,()=>{moveTo(q('[data-r=pod]'));scene('Podcasts','feed pronto pro Spotify e Apple');}], 484 [22.8,()=>{click();mod('pod');scrollTo(620);}], 485 // ATO 3 â Monte a home (arrastar módulos) 486 [24.6,()=>{act('3','Monte a home do seu jeito');prev.classList.add('editing');scrollTo(300);scene('Arraste pra reordenar','os módulos, na ordem que quiser');}], 487 [25.8,()=>{moveTo(q('[data-mod=prog]'),.5,.2);}], 488 [26.7,()=>{click();lift('prog');}], 489 [27.5,()=>{moveMod('prog',35);moveTo(q('[data-mod=news]'),.5,.2);}], 490 [28.8,()=>{lift('prog',false);scene('O site já reflete','a nova ordem, na hora');}], 491 [30.2,()=>{prev.classList.remove('editing');}], 492 // ATO 4 â Envolver 493 [31.0,()=>{act('4','Engajamento e receita');tab('envolver');scrollTo(0);moveTo(q('[data-r=slider]'));scene('Slider de destaque','promoções em primeiro plano');}], 494 [31.8,()=>{click();mod('slider');q('[data-r=promo]').classList.add('on');}], 495 [33.4,()=>{scrollTo(770);moveTo(q('[data-r=banner]'));scene('Publicidade','espaço que vira receita');}], 496 [34.2,()=>{click();mod('banner');}], 497 [35.6,()=>{scrollTo(0);moveTo(q('[data-r=wa]'));scene('WhatsApp','o ouvinte fala em um toque');}], 498 [36.4,()=>{click();toggle('wa');}], 499 // ATO 5 â Ao vivo 500 [37.6,()=>{act('5','Ao vivo e no bolso');tab('aovivo');moveTo(q('[data-r=video]'));scene('VÃdeo ao vivo','a câmera do estúdio no ar');}], 501 [38.4,()=>{click();toggle('video');}], 502 [40.6,()=>{q('[data-video]').classList.remove('show');moveTo(q('[data-r=pwa]'));scene('App instalável','o site vira app no celular');}], 503 [41.4,()=>{click();toggle('pwa');}], 504 // FINAL 505 [43.6,()=>{q('[data-pwa]').classList.remove('show');actEl.classList.remove('show');sceneEl.classList.remove('show'); 506 q('[data-more]').classList.add('show');buildMore();}], 507 ]; 508 const TOTAL=49; 509 510 const moreItems=[['shield','Segurança do stream'],['lock','LGPD e cookies'],['zap','SEO nativo'],['save','Backup automático'], 511 ['map','Mapa de ouvintes'],['users','Vários usuários'],['net','Modo Rede'],['img','Direitos de imagem'],['music','ID da música']]; 512 const svg={shield:'<path d="M20 13c0 5-3.5 7.5-7.7 9a1 1 0 0 1-.6 0C7.5 20.5 4 18 4 13V6a1 1 0 0 1 1-1c2 0 4.5-1.2 6.2-2.7a1.2 1.2 0 0 1 1.6 0C14.5 3.8 17 5 19 5a1 1 0 0 1 1 1z"/>', 513 lock:'<rect width="18" height="11" x="3" y="11" rx="2"/><path d="M7 11V7a5 5 0 0 1 10 0v4"/>
513',zap:'<path d="M13 2 3 14h7l-1 8 10-12h-7z"/>', 514 save:'<path d="M19 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11l5 5v11a2 2 0 0 1-2 2z"/><path d="M17 21v-8H7v8M7 3v5h8"/>', 515 map:'<path d="M20 10c0 5-8 12-8 12s-8-7-8-12a8 8 0 0 1 16 0"/><circle cx="12" cy="10" r="3"/>',users:'<path d="M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2"/><circle cx="9" cy="7" r="4"/><path d="M22 21v-2a4 4 0 0 0-3-3.9"/>', 516 net:'<rect x="16" y="16" width="6" height="6" rx="1"/><rect x="2" y="16" width="6" height="6" rx="1"/><rect x="9" y="2" width="6" height="6" rx="1"/><path d="M5 16v-3a1 1 0 0 1 1-1h12a1 1 0 0 1 1 1v3M12 12V8"/>', 517 img:'<rect width="18" height="18" x="3" y="3" rx="2"/><circle cx="9" cy="9" r="2"/><path d="m21 15-3.1-3.1a2 2 0 0 0-2.8 0L6 21"/>',music:'<path d="M9 18V5l12-2v13"/><circle cx="6" cy="18" r="3"/><circle cx="18" cy="16" r="3"/>'}; 518 function buildMore(){const g=root.querySelector('[data-moregrid]');if(g.childElementCount)return; 519 moreItems.forEach((it,i)=>{const el=document.createElement('span');el.className='g';el.style.animationDelay=(i*.07)+'s'; 520 el.innerHTML='<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">'+svg[it[0]]+'</svg>'+it[1];g.appendChild(el);});} 521 522 function reset(){ 523 color('#FAC643',swBtns[0]);font(0.5);tema('pulse',root.querySelector('[data-tema] [data-t=pulse]'));player('a',root.querySelector('[data-plr] [data-p=a]')); 524 tab('aparencia');hot('');scrollTo(0);prev.classList.remove('editing');setOrder(); 525 root.querySelectorAll('.mod[data-mod]').forEach(m=>m.classList.remove('show','lift')); 526 root.querySelectorAll('.row').forEach(r=>r.classList.remove('on')); 527 ['wa','pwa','video','more'].forEach(k=>root.querySelector('[data-'+k+']').classList.remove('show')); 528 moveTo(root.querySelector('.mod--hero'),.5,.4); 529 } 530 function play(){reset();L.forEach(([t,fn])=>setTimeout(fn,t*1000)); 531 if(!reduce){progress.style.transition='none';progress.style.width='0';requestAnimationFrame(()=>{progress.style.transition=`width ${TOTAL}s linear`;progress.style.width='100%';});} 532 setTimeout(play,TOTAL*1000);} 533 if(reduce){tab('aparencia');mod('top');mod('news');mod('slider');toggle('wa');scene('Você mexe no painel','o site muda na hora');buildMore();} 534 else{const io=new IntersectionObserver(es=>es.forEach(e=>{if(e.isIntersecting){io.disconnect();play();}}),{threshold:.3});io.observe(root);} 535})(); 536/* ===================================================================== 537 VITRINE AO VIVO â clicar no logo do cliente e ouvir a rádio no ar. 538 Regras que valem ouro aqui: 539 · o <audio> NUNCA recebe src antes do clique (preload="none"): stream 540 carregado à toa entra como OUVINTE na estatÃstica da rádio cliente; 541 · uma rádio por vez; 542 · a API é consultada pelo nosso /api/no-ar.php (lista-branca + cache), 543 nunca direto do navegador; 544 · o polling da música só roda com o player aberto e a aba visÃvel. 545 ===================================================================== */ 546(function () { 547 var faixa = document.querySelector('[data-cli]'); 548 var dock = document.querySelector('[data-dock]'); 549 if (!faixa || !dock) { return; } 550 551 var audio = dock.querySelector('[data-dock-audio]'); 552 var elNome = dock.querySelector('[data-dock-nome]'); 553 var elMus = dock.querySelector('[data-dock-mus]'); 554 var elLogo = dock.querySelector('[data-dock-logo]'); 555 var elLive = dock.querySelector('.uh-dock__live'); 556 var bMain = dock.querySelector('[data-dock-toggle]'); 557 var bClose = dock.querySelector('[data-dock-close]'); 558 var vol = dock.querySelector('[data-dock-vol]'); 559 var dica = document.querySelector('[data-cli-dica]'); 560 var dicaTx = document.querySelector('[data-cli-txt]'); 561 562 var atual = null; // porta em reprodução 563 var timer = null; // polling do "tocando agora" 564 var pedido = 0; // serializa respostas fora de ordem 565 566 function api(qs) { 567 return fetch('api/no-ar.php?' + qs, { credentials: 'omit' }) 568 .then(function (r) { return r.ok ? r.json() : null; }) 569 .catch(function () { return null; }); 570 } 571 function botoes(porta) { // as 2 cópias do letreiro têm o mesmo data-radio 572 return faixa.querySelectorAll('[data-radio="' + porta + '"]'); 573 } 574 function marcar(porta, cls, on) { 575 var l = botoes(porta), i; 576 for (i = 0; i < l.length; i++) { l[i].classList[on ? 'add' : 'remove'](cls); } 577 } 578 function limpar() { 579 var l = faixa.querySelectorAll('.is-playing,.is-loading'), i; 580 for (i = 0; i < l.length; i++) { l[i].classList.remove('is-playing', 'is-loading'); } 581 faixa.classList.remove('is-playing'); 582 } 583 584 /* ââ 1) quem está no ar: acende o pontinho verde nos logos âââââââââââââ */ 585 function acender() { 586 api('all=1').then(function (d) { 587 if (!d || !d.radios) { return; } 588 var p, n = 0; 589 for (p in d.radios) { 590 if (d.radios[p] && d.radios[p].no_ar) { marcar(p, 'is-live', true); n++; } 591 } 592 if (n && dica) { 593 dica.classList.add('on'); 594 if (dicaTx) { 595 dicaTx.textContent = n === 1 596 ? 'Uma dessas rádios está no ar agora. Clique no logo e ouça.' 597 : n + ' dessas rádios estão no ar agora. Clique em um logo e ouça.'; 598 } 599 } 600 }); 601 }
602 // só quando a faixa aparece: nada de disparar 18 consultas em quem nem rolou até lá 603 if ('IntersectionObserver' in window) { 604 var io = new IntersectionObserver(function (es) { 605 if (es[0].isIntersecting) { io.disconnect(); acender(); } 606 }, { rootMargin: '200px' }); 607 io.observe(faixa); 608 } else { acender(); } 609 610 /* ââ 2) tocar âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ */ 611 function parar() { 612 audio.pause(); 613 audio.removeAttribute('src'); 614 audio.load(); // encerra a conexão com o streaming 615 clearInterval(timer); timer = null; 616 limpar(); 617 dock.classList.remove('playing', 'loading'); 618 atual = null; 619 } 620 function fechar() { 621 parar(); 622 dock.classList.remove('show'); 623 setTimeout(function () { if (!atual) { dock.hidden = true; } }, 500); 624 } 625 626 function tocar(btn) { 627 var porta = btn.getAttribute('data-radio'); 628 if (porta === atual) { // clicou de novo = pausa/retoma 629 if (audio.paused) { audio.play().catch(function () {}); } else { audio.pause(); } 630 return; 631 } 632 parar(); 633 atual = porta; 634 var meu = ++pedido; 635 636 elNome.textContent = btn.getAttribute('data-nome'); 637 elLogo.src = btn.getAttribute('data-logo'); 638 elLogo.alt = btn.getAttribute('data-nome'); 639 elMus.textContent = ''; 640 elLive.lastChild.nodeValue = 'CONECTANDO'; 641 dock.classList.remove('off'); 642 dock.hidden = false; 643 void dock.offsetWidth; // reflow p/ a transição de entrada rodar 644 dock.classList.add('show', 'loading'); 645 faixa.classList.add('is-playing'); 646 marcar(porta, 'is-loading', true); 647 648 api('p=' + encodeURIComponent(porta)).then(function (d) { 649 if (meu !== pedido) { return; } // o visitante já clicou em outra 650 if (!d || !d.no_ar || !d.stream) { 651 dock.classList.remove('loading'); 652 dock.classList.add('off'); 653 elLive.lastChild.nodeValue = 'FORA DO AR'; 654 elMus.textContent = 'Esta rádio não está transmitindo neste momento.'; 655 marcar(porta, 'is-loading', false); 656 return; 657 } 658 elLive.lastChild.nodeValue = 'AO VIVO'; 659 pintarMusica(d.musica); 660 audio.src = d.stream; // só agora a conexão é aberta 661 audio.volume = (vol ? vol.value : 80) / 100; 662 audio.play().catch(function () { 663 // autoplay barrado só acontece sem gesto; aqui houve clique. Se cair 664 // aqui é stream inacessÃvel: some com o "carregando" em vez de girar eterno. 665 dock.classList.remove('loading'); 666 marcar(porta, 'is-loading', false); 667 }); 668 clearInterval(timer); 669 timer = setInterval(atualizarMusica, 20000); 670 }); 671 } 672 function pintarMusica(m) { elMus.textContent = m ? '· ' + m : ''; } 673 function atualizarMusica() { 674 if (!atual || document.hidden) { return; } 675 var meu = pedido; 676 api('p=' + encodeURIComponent(atual)).then(function (d) { 677 if (d && meu === pedido) { pintarMusica(d.musica); } 678 }); 679 } 680 681 /* ââ 3) eventos âââââââââââââââââââââââââââââââââââââââââââââââââââââââââ */ 682 faixa.addEventListener('click', function (e) { 683 var b = e.target.closest('.uh-cli__logo--play'); 684 if (b) { tocar(b); } 685 }); 686 bMain.addEventListener('click', function () { 687 if (!atual) { return; } 688 if (audio.paused) { audio.play().catch(function () {}); } else { audio.pause(); } 689 }); 690 bClose.addEventListener('click', fechar); 691 if (vol) { 692 vol.addEventListener('input', function () { audio.volume = vol.value / 100; }); 693 } 694 audio.addEventListener('playing', function () { 695 dock.classList.remove('loading'); dock.classList.add('playing'); 696 bMain.setAttribute('aria-label', 'Pausar'); 697 if (atual) { marcar(atual, 'is-loading', false); marcar(atual, 'is-playing', true); } 698 }); 699 audio.addEventListener('pause', function () { 700 dock.classList.remove('playing'); 701 bMain.setAttribute('aria-label', 'Tocar'); 702 if (atual) { marcar(atual, 'is-playing', false); } 703 faixa.classList.remove('is-playing'); 704 }); 705 audio.addEventListener('play', function () { 706 dock.classList.add('loading'); 707 faixa.classList.add('is-playing'); 708 }); 709 audio.addEventListener('error', function () { 710 if (!audio.getAttribute('src')) { return; } // src limpo pelo parar(): não é erro 711 dock.classList.remove('loading', 'playing'); 712 dock.classList.add('off'); 713 elLive.lastChild.nodeValue = 'SEM SINAL'; 714 elMus.textContent = 'Não foi possÃvel conectar agora.'; 715 limpar(); 716 }); 717 document.addEventListener('keydown', function (e) { 718 if (e.key === 'Escape' && atual) { fechar(); } 719 }); 720 721 // a tela de bloqueio/central de mÃdia do celular mostra a rádio, não "uhost.com.br" 722 if ('mediaSession' in navigator) { 723 audio.addEventListener('playing', function () {
724 try { 725 navigator.mediaSession.metadata = new MediaMetadata({ 726 title: elMus.textContent.replace(/^·\s+/, '') || 'Ao vivo', 727 artist: elNome.textContent, 728 album: 'UHOST' 729 }); 730 } catch (err) {} 731 }); 732 try { 733 navigator.mediaSession.setActionHandler('play', function () { audio.play().catch(function () {}); }); 734 navigator.mediaSession.setActionHandler('pause', function () { audio.pause(); }); 735 navigator.mediaSession.setActionHandler('stop', fechar); 736 } catch (err) {} 737 } 738})(); 739 740/* ===================================================================== 741 DIAGNÃSTICO DA OPERAÃÃO DIGITAL (diagnostico.php) 742 Uma pergunta por vez, sem recarregar. O resultado aparece na hora â 743 sem cadastro, por decisão de marca (a /planos promete que a UHOST não 744 esconde coisa atrás de formulário; travar o diagnóstico desmentiria). 745 ===================================================================== */ 746(function () { 747 var caixa = document.querySelector('[data-diag]'); 748 if (!caixa || !window.UH_DIAG_REC) { return; } 749 750 var res = document.querySelector('[data-diag-res]'); 751 var barra = caixa.querySelector('[data-diag-barra]'); 752 var passo = caixa.querySelector('[data-diag-passo]'); 753 var voltar = caixa.querySelector('[data-diag-voltar]'); 754 var perg = caixa.querySelectorAll('[data-diag-p]'); 755 var total = perg.length; 756 var atual = 0; 757 var notas = []; 758 759 function mostrar(i) { 760 for (var k = 0; k < total; k++) { perg[k].hidden = (k !== i); } 761 passo.textContent = 'Pergunta ' + (i + 1) + ' de ' + total; 762 barra.style.width = ((i / total) * 100) + '%'; 763 voltar.hidden = (i === 0); 764 var b = perg[i].querySelector('.uh-diag__o'); 765 if (b) { b.focus({ preventScroll: true }); } 766 } 767 768 caixa.addEventListener('click', function (e) { 769 var b = e.target.closest('.uh-diag__o'); 770 if (b) { 771 notas[atual] = parseInt(b.getAttribute('data-diag-v'), 10); 772 atual++; 773 if (atual < total) { mostrar(atual); } else { concluir(); } 774 return; 775 } 776 if (e.target.closest('[data-diag-voltar]') && atual > 0) { atual--; mostrar(atual); } 777 }); 778 779 function concluir() { 780 var soma = 0, i; 781 for (i = 0; i < notas.length; i++) { soma += notas[i]; } 782 var nota = Math.round((soma / (total * 2)) * 100); 783 784 // faixa 785 var f = window.UH_DIAG_F[window.UH_DIAG_F.length - 1]; 786 for (i = 0; i < window.UH_DIAG_F.length; i++) { 787 if (nota >= window.UH_DIAG_F[i][0] && nota <= window.UH_DIAG_F[i][1]) { f = window.UH_DIAG_F[i]; break; } 788 } 789 res.querySelector('[data-diag-faixa]').textContent = f[2]; 790 res.querySelector('[data-diag-texto]').innerHTML = f[3]; 791 792 // as 3 recomendações mais urgentes: as respostas mais fracas primeiro, 793 // desempatando pela ordem das perguntas (as primeiras doem mais) 794 var ordem = []; 795 for (i = 0; i < notas.length; i++) { ordem.push([notas[i], i]); } 796 ordem.sort(function (a, b) { return a[0] - b[0] || a[1] - b[1]; }); 797 var ul = res.querySelector('[data-diag-recs]'); 798 ul.innerHTML = ''; 799 var postas = 0; 800 for (i = 0; i < ordem.length && postas < 3; i++) { 801 if (ordem[i][0] >= 2) { continue; } // já está bem: não vira tarefa 802 var li = document.createElement('li'); 803 li.innerHTML = '<svg viewBox="0 0 24 24" fill="none" stroke="var(--uh-gold)" stroke-width="2.5" aria-hidden="true"><path d="M20 6 9 17l-5-5"/></svg><span>' 804 + window.UH_DIAG_REC[ordem[i][1]] + '</span>'; 805 ul.appendChild(li); 806 postas++; 807 } 808 if (!postas) { 809 var li2 = document.createElement('li'); 810 li2.innerHTML = '<svg viewBox="0 0 24 24" fill="none" stroke="var(--uh-gold)" stroke-width="2.5" aria-hidden="true"><path d="M20 6 9 17l-5-5"/></svg><span>Nada urgente apareceu. Vale conversar sobre custo: operação nesse nÃvel costuma estar espalhada em vários contratos.</span>'; 811 ul.appendChild(li2); 812 } 813 814 // leva as respostas junto no envio, senão o lead chega sem contexto 815 var ini = res.querySelector('[data-diag-inota]'); 816 if (ini) { ini.value = nota; } 817 var iresp = res.querySelector('[data-diag-iresp]'); 818 if (iresp) { 819 var linhas = []; 820 for (i = 0; i < notas.length; i++) { 821 linhas.push((i + 1) + '. ' + window.UH_DIAG_Q[i] + '\n -> ' + window.UH_DIAG_O[i][notas[i]]); 822 } 823 iresp.value = linhas.join('\n'); 824 } 825 826 caixa.hidden = true; 827 res.hidden = false;
828 res.id = 'resultado'; 829 res.scrollIntoView({ behavior: 'smooth', block: 'start' }); 830 831 // conta a nota e desenha o arco 832 var reduz = window.matchMedia && window.matchMedia('(prefers-reduced-motion: reduce)').matches; 833 var num = res.querySelector('[data-diag-num]'); 834 var arco = res.querySelector('[data-diag-arco]'); 835 var fim = 327 - (327 * nota / 100); 836 if (reduz) { num.textContent = nota; arco.style.strokeDashoffset = fim; return; } 837 arco.style.transition = 'stroke-dashoffset 1.2s cubic-bezier(.22,1,.36,1)'; 838 requestAnimationFrame(function () { arco.style.strokeDashoffset = fim; }); 839 var t0 = null; 840 requestAnimationFrame(function step(ts) { 841 if (!t0) { t0 = ts; } 842 var p = Math.min((ts - t0) / 1200, 1); 843 num.textContent = Math.round(nota * (1 - Math.pow(1 - p, 3))); 844 if (p < 1) { requestAnimationFrame(step); } 845 }); 846 } 847 848 var rf = document.querySelector('[data-diag-refazer]'); 849 if (rf) { 850 rf.addEventListener('click', function () { 851 atual = 0; notas = []; 852 res.hidden = true; caixa.hidden = false; 853 mostrar(0); 854 caixa.scrollIntoView({ behavior: 'smooth', block: 'center' }); 855 }); 856 } 857 mostrar(0); 858})(); 859 860/* ===================================================================== 861 BARRA FIXA DE CONVERSAO (uh_sticky) 862 Aparece depois que o visitante passa do hero, e SOME quando o CTA final 863 entra na tela (dois botões iguais na mesma dobra viram ruÃdo) ou quando 864 o player da vitrine abre (quem está ouvindo não quer ser interrompido). 865 ===================================================================== */ 866(function () { 867 var barra = document.querySelector('[data-sticky]'); 868 if (!barra) { return; } 869 870 var ctaFinal = document.querySelector('.uh-cta'); 871 var dock = document.querySelector('[data-dock]'); 872 var gatilho = 620; // ~ o fim do hero 873 var tocando = false; 874 875 barra.hidden = false; 876 877 /* ð¨ A visibilidade do CTA final é medida por GEOMETRIA, não por 878 IntersectionObserver. Nas duas páginas mais longas (/planos com 14.205 px 879 e /site-para-radio com 15.736) o observer disparava ANTES do layout 880 assentar â as imagens da vitrine ainda estavam carregando e empurravam a 881 página em ~190 px depois â e a barra ficava presa no estado errado, sem 882 nada que a reavaliasse. Medir no mesmo rAF do scroll não tem esse furo. */ 883 function ctaVisivel() { 884 if (!ctaFinal) { return false; } 885 var r = ctaFinal.getBoundingClientRect(); 886 var alturaJanela = window.innerHeight || document.documentElement.clientHeight; 887 return r.top < alturaJanela * 0.85 && r.bottom > 0; 888 } 889 890 function avaliar() { 891 var mostrar = (window.scrollY > gatilho) && !ctaVisivel() && !tocando; 892 barra.classList.toggle('show', mostrar); 893 document.body.classList.toggle('tem-sticky', mostrar); 894 } 895 896 var tick = false; 897 function agendar() { 898 if (!tick) { tick = true; requestAnimationFrame(function () { tick = false; avaliar(); }); } 899 } 900 window.addEventListener('scroll', agendar, { passive: true }); 901 window.addEventListener('resize', agendar, { passive: true }); 902 window.addEventListener('load', agendar); // imagens tardias mudam a altura 903 904 if (dock) { 905 // o dock não emite evento próprio: observa a classe que o player liga/desliga 906 new MutationObserver(function () { 907 tocando = dock.classList.contains('show'); 908 avaliar(); 909 }).observe(dock, { attributes: true, attributeFilter: ['class'] }); 910 } 911 912 avaliar(); 913})();
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.