PageSourceSearch

https://www.brasil247.com/wp-content/themes/tiebreak/assets/js/b247-header-button.js?m=1790818383g

js brasil247.com collected 2026-10-01 08:40:43 UTC 7,687 bytes, 195 lines download raw bytes

1/**
2 * b247-header-button.js
3 *
4 * Os botões do header (inc/header-button.php, até quatro): manda cada clique
5 * para o GA4, abre e fecha as janelas da ação "janela", acende a bolinha do
6 * "Ao vivo" enquanto a TV 247 está no ar e refaz o ajuste dos textos quando
7 * a largura muda. A ação "newsletter" continua com o b247-newsletter.js, que
8 * abre o formulário.
9 */
10(function (w, d) {
11	'use strict';
12
13	// ── Cliques: para o GA4 do site ───────────────────────────────────────
14	// Um evento header_button_click pela window.b247Ga4Event do b247-ga4.js
15	// (inc/ga4.php), procurada na hora do clique: sem ela (GA4 desligado),
16	// nada sai. content_type e content_id são dimensões prontas do GA4
17	// ("Content type", "Content ID"), então cada botão aparece nos relatórios
18	// sem criar dimensão; button_position só aparece se for registrada.
19	// "Apoie o 247" entra para comparar.
20	var envia = function (acao, pos, url) {
21		if (typeof w.b247Ga4Event !== 'function') { return; }
22		w.b247Ga4Event('header_button_click', { content_type: 'header_button', content_id: acao, button_position: pos, link_url: url });
23	};
24	d.addEventListener('click', function (e) {
25		var t = e.target;
26		if (!t || typeof t.closest !== 'function') { return; }
27		var b = t.closest('[data-hbtn]');
28		if (b) {
29			envia(b.getAttribute('data-hbtn'), b.getAttribute('data-hbtn-pos') || '', b.getAttribute('href') || '');
30			return;
31		}
32		var apoie = t.closest('.b247-header-right .b247-apoie-cta a');
33		if (apoie) { envia('apoie', '', apoie.getAttribute('href') || ''); }
34	});
35
36	// ── As janelas: uma por botão "janela", ligada a ele pelo aria-controls ─
37	var aberta = null;
38	var lastFocus = null;
39
40	// Mesma moldura do formulário da newsletter: só abre se o CSS dela
41	// carregou (position: fixed); sem ele, vai direto para o endereço do
42	// botão da janela em vez de travar a página.
43	var abrir = function (modal, trigger) {
44		var cta = modal.querySelector('.b247-hmodal__cta');
45		if (w.getComputedStyle(modal).position !== 'fixed') {
46			if (cta) { w.location.assign(cta.href); }
47			return;
48		}
49		lastFocus = trigger || d.activeElement;
50		modal.hidden = false;
51		aberta = modal;
52		d.body.classList.add('b247-modal-open');
53		if (cta) { cta.focus(); }
54	};
55	var fechar = function () {
56		if (!aberta) { return; }
57		aberta.hidden = true;
58		aberta = null;
59		// A trava de rolagem é a mesma do formulário da newsletter: só sai se
60		// ele não estiver aberto também.
61		var nl = d.getElementById('b247-newsletter-modal');
62		if (!nl || nl.hidden) { d.body.classList.remove('b247-modal-open'); }
63		if (lastFocus && typeof lastFocus.focus === 'function') { lastFocus.focus(); }
64		lastFocus = null;
65	};
66
67	d.addEventListener('click', function (e) {
68		var t = e.target;
69		if (!t || typeof t.closest !== 'function') { return; }
70		var abre = t.closest('[data-action="open-header-modal"]');
71		if (abre) {
72			var modal = d.getElementById(abre.getAttribute('aria-controls') || '');
73			if (modal) {
74				e.preventDefault();
75				abrir(modal, abre);
76			}
77			return;
78		}
79		if (t.closest('[data-action="close-header-modal"]')) { fechar(); }
80	});
81	d.addEventListener('keydown', function (e) {
82		if (!aberta) { return; }
83		if ('Escape' === e.key) {
84			fechar();
85			return;
86		}
87		// O foco não sai da janela: Tab e Shift+Tab giram entre o X e os
88		// botões, em vez de voltar ao header atrás do fundo escuro.
89		if ('Tab' !== e.key) { return; }
90		var itens = aberta.querySelectorAll('a[href], button:not([disabled])');
91		if (!itens.length) { return; }
92		var primeiro = itens[0];
93		var ultimo = itens[itens.length - 1];
94		if (!aberta.contains(d.activeElement)) {
95			e.preventDefault();
96			primeiro.focus();
97		} else if (e.shiftKey && d.activeElement === primeiro) {
98			e.preventDefault();
99			ultimo.focus();
100		} else if (!e.shiftKey && d.activeElement === ultimo) {
101			e.preventDefault();
102			primeiro.focus();
103		}
104	});
105
106	// ── "Ao vivo": bolinha enquanto a TV está no ar ───────────────────────
107	// Mesma regra da grade na sidebar (b247-sidebar.js): um programa vai até
108	// o próximo e o último até a meia-noite, então a TV está no ar do primeiro
109	// programa até o fim do dia. Calculado aqui, no horário de Brasília,
110	// porque o HTML sai do cache de borda.
111	//
112	// O link também: o botão abre o vídeo do programa no ar (cada programa da
113	// grade com o seu vídeo, em data-live-programs) e, quando o programa no ar
114	// não tem vídeo próprio ou a TV não está no ar, o canal (data-live-
115	// fallback). Sem data-live-programs o link está fixo no Gestor e fica como
116	// veio.
117	var fmt = null;
118	var agora = function () {
119		try {
120			fmt = fmt || new Intl.DateTimeFormat('en-CA', {
121				timeZone: 'America/Sao_Paulo',
122				year: 'numeric', month: '2-digit', day: '2-digit',
123				hour: '2-digit', minute: '2-digit', hour12: false
124			});
125			var p = {};
126			fmt.formatToParts(new Date()).forEach(function (x) { p[x.type] = x.value; });
127			return { dia: p.year + '-' + p.month + '-' + p.day, min: (parseInt(p.hour, 10) % 24) * 60 + parseInt(p.minute, 10) };
128		} catch (e) {
129			var n = new Date();
130			return { dia: '', min: n.getHours() * 60 + n.getMinutes() };
131		}
132	};
133	var vivos = [];
134	Array.prototype.forEach.call(d.querySelectorAll('.b247-nl-open--aovivo[data-live-start]'), function (live) {
135		var dot = live.querySelector('.b247-nl-open__dot');
136		var inicio = parseInt(live.getAttribute('data-live-start'), 10);
137		var data = live.getAttribute('data-live-date') || '';
138		var programas = [];
139		try { programas = JSON.parse(live.getAttribute('data-live-programs') || '[]') || []; } catch (e) { programas = []; }
140		var reserva = live.getAttribute('data-live-fallback') || '';
141		var rotulo = live.getAttribute('aria-label') || '';
142		vivos.push(function (a) {
143			// Uma grade de outro dia não acende nada: a página pode ter saído
144			// do cache ontem.
145			var hoje = !data || !a.dia || a.dia === data;
146			var noAr = hoje && !isNaN(inicio) && a.min >= inicio;
147			if (dot) { dot.hidden = !noAr; }
148			live.classList.toggle('is-live', noAr);
149			// A bolinha é só visual; o nome do botão diz o mesmo para quem
150			// não a vê.
151			if (rotulo) {
152				live.setAttribute('aria-label', noAr ? rotulo + ' (no ar agora)' : rotulo);
153				live.setAttribute('title', noAr ? rotulo + ' (no ar agora)' : rotulo);
154			}
155			if (programas.length && reserva) {
156				var video = '';
157				if (noAr) {
158					for (var i = 0; i < programas.length; i++) {
159						var ini = programas[i][0];
160						var prox = i + 1 < programas.length ? programas[i + 1][0] : 24 * 60;
161						if (a.min >= ini && a.min < prox) { video = programas[i][1] || ''; }
162					}
163				}
164				live.setAttribute('href', video || reserva);
165			}
166		});
167	});
168	if (vivos.length) {
169		var atualiza = function () {
170			var a = agora();
171			vivos.forEach(function (f) { f(a); });
172		};
173		atualiza();
174		w.setInterval(atualiza, 60000);
175	}
176
177	// ── O que cabe no header: os textos no computador, o 2º ícone no celular
178	// O ajuste (b247HbtnFit) já rodou no fim do header, antes de a página
179	// aparecer, e só existe se algum botão mostra texto ou se há dois no
180	// celular. Roda de novo quando a largura muda e quando as fontes terminam
181	// de carregar, porque as duas coisas mudam o que cabe.
182	if (typeof w.b247HbtnFit === 'function') {
183		var quadro = 0;
184		w.addEventListener('resize', function () {
185			if (quadro) { return; }
186			quadro = w.requestAnimationFrame(function () {
187				quadro = 0;
188				w.b247HbtnFit();
189			});
190		});
191		if (d.fonts && d.fonts.ready) {
192			d.fonts.ready.then(function () { w.b247HbtnFit(); });
193		}
194	}
195}(window, document));

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.