Cobertura completa das Eleições 2026: dia a dia, do começo da campanha ao resultado final.
Governador – Espírito Santo
Atualizado em 17:29:58h·Dados oficiais: Tribunal Superior Eleitoral
0,00%
0 de 6.104 seções apuradas
Notícias da cobertura
A cobertura das Eleições 2026 feita pela nossa redação, em um só lugar.
Candidatos
Quem disputa cada cargo nas Eleições 2026, reunido em um só lugar.
Governador – São Paulo
Atualizado em 22:56:59h · Dados oficiais: Tribunal Superior Eleitoral
100,00%
103.656 de 103.656 seções apuradas
62,65% 14.491.874 votos
36,42% 8.423.656 votos
0,35% 80.480 votos
0,33% 75.641 votos
0,25% 58.862 votos
Informações da apuração
Total 34.081.699
Válidos 23.130.513 (87,63%)
Brancos 1.348.262 (5,11%)
Nulos 1.915.481 (7,26%)
Abstenções 7.687.443 (22,56%)
Central de Pesquisas
O histórico de intenção de voto das Eleições 2026, por candidato, cargo e estado.
Tarcísio de FreitasRepublicanos53.3%
Pesquisa registrada no TSE sob o número SP-01486/2026, realizada pelo instituto AtlasIntel, 1.800 entrevistados, campo até 27/09/2026, margem de erro de 2 pontos percentuais, intervalo de confiança de 95%.
*Sem candidato inclui: voto branco, nulo, não sabe, não respondeu e não vai votar.
Debates
Quando e onde assistir aos debates das Eleições 2026, para presidente e para o governo do seu estado.
9agodom
Debate de governo do estado
Governo · SP20h00
Debate entre os candidatos ao governo do estado. Data anunciada pela emissora, sujeita a confirmação.
23agodom
Debate presidencial
Presidente20h00
Debate entre os candidatos à Presidência. Data anunciada pela emissora, sujeita a confirmação.
14setseg
Debate presidencial
Próxima data
Presidente22h00
Debate entre os candidatos à Presidência, organizado por um consórcio de veículos. Data anunciada pela organização, sujeita a confirmação.
27setdom
Debate presidencial
Presidente22h00
Debate entre os candidatos à Presidência. Data anunciada pela emissora, sujeita a confirmação.
1outqui
Debate presidencial
Presidente
Debate entre os candidatos à Presidência, transmitido após o Jornal Nacional. Data anunciada pela emissora, sujeita a confirmação.
Calendário eleitoral
A linha do tempo oficial das Eleições 2026, dos prazos do título à diplomação dos eleitos.
06MAIQUA
Prazo final para o título de eleitor
Até 6 de maio, último dia para tirar o primeiro título, transferir o domicílio eleitoral, atualizar dados ou regularizar pendências. A partir de 7 de maio o cadastro fica fechado para a organização do pleito.
20JULSEG
Convenções partidárias
De 20 de julho a 5 de agosto, os partidos realizam convenções para escolher candidatos e definir coligações.
20JULSEG
Habilitação do voto em trânsito
De 20 de julho a 20 de agosto, período para pedir, alterar ou cancelar a habilitação para votar fora do domicílio eleitoral (algumas exceções até 28 de agosto).
15AGOSAB
Prazo final para registro de candidaturas
Até as 19h de 15 de agosto, os partidos registram oficialmente seus candidatos na Justiça Eleitoral. A partir daí a lista de quem disputa a eleição está definida.
16AGODOM
Início da propaganda eleitoral
A partir de 16 de agosto fica liberada a propaganda eleitoral nas ruas e na internet, marcando o início oficial da campanha.
28AGOSEX
Início do horário eleitoral gratuito (1º turno)
De 28 de agosto a 1º de outubro, o rádio e a televisão exibem a propaganda eleitoral gratuita do primeiro turno.
04OUTDOM1º turnoPróxima data
Em 4 de outubro, dia da votação do primeiro turno, com urnas abertas das 8h às 17h pelo horário de Brasília em todo o país.
09OUTSEX
Horário eleitoral gratuito (2º turno)
De 9 a 23 de outubro, propaganda eleitoral gratuita no rádio e na TV para o segundo turno, onde houver.
25OUTDOM2º turno
Em 25 de outubro, dia da votação do segundo turno, onde houver, com urnas abertas das 8h às 17h pelo horário de Brasília.
03NOVTER
Reabertura do cadastro eleitoral
Em 3 de novembro o cadastro eleitoral volta a funcionar para serviços como tirar, transferir e regularizar o título, depois de ficar fechado desde 7 de maio.
03DEZQUI
Prazo para justificar ausência no 1º turno
Até 3 de dezembro, quem não votou no primeiro turno pode apresentar justificativa. A justificativa é feita por turno, de forma separada.
18DEZSEX
Até 18 de dezembro acontece a diplomação dos eleitos, ato que confirma o resultado e habilita os eleitos a tomar posse.
05JAN 2027TER
Posse de presidente e vice
Em 5 de janeiro de 2027, posse do presidente e do vice-presidente eleitos.
06JAN 2027QUA
Em 6 de janeiro de 2027, posse dos governadores e vices eleitos.
06JAN 2027QUA
Prazo para justificar ausência no 2º turno
Até 6 de janeiro de 2027, quem não votou no segundo turno pode apresentar justificativa. A justificativa é feita por turno, de forma separada.
Guia do eleitor
O essencial para votar bem informado nas Eleições 2026, em perguntas e respostas.
O 1º turno será em 4 de outubro e o 2º turno, se houver, em 25 de outubro. Você vai eleger Presidente, Governador, dois Senadores, Deputado Federal e Deputado Estadual (ou Distrital, no DF).
A ordem é: Deputado Federal, Deputado Estadual, Senador (1ª vaga), Senador (2ª vaga), Governador e, por último, Presidente. São seis votos no total. Vale levar uma cola com os seus números.
Porque esta eleição renova dois terços do Senado: cada estado elege dois senadores. Escolha dois candidatos diferentes, pois se o mesmo número for digitado nas duas vezes a urna anula o segundo voto.
Deputados são eleitos pelo sistema proporcional: os votos somam para o partido, que conquista um número de cadeiras, ocupadas pelos seus candidatos mais votados. Por isso, votar em um candidato também ajuda a eleger outros da mesma legenda. Dá para votar só na legenda, digitando apenas o número do partido.
Um documento oficial com foto: RG, CNH, passaporte, carteira de trabalho ou o e-Título com fotografia, válido para quem já fez a biometria. O título físico não é obrigatório e documentos com foto vencidos são aceitos. Celular não entra na cabine de votação.
A votação vai das 8h às 17h, sempre no horário de Brasília. Quem chega à fila até as 17h recebe senha e tem o direito de votar. Para saber onde votar, consulte o aplicativo e-Título ou o serviço Onde Votar, no site do TSE, com seu nome ou número do título. Locais de votação mudam de uma eleição para outra, então confira antes do dia 4 de outubro.
No próprio dia, justifique pelo aplicativo e-Título ou em qualquer local de votação, sem precisar de comprovante. Depois disso, o prazo é de até 60 dias após cada turno. Cada turno conta como uma eleição separada, então justifique os dois.
Qual tema mais pesa na hora do seu voto?
Últimas notícias do portal
O noticiário do dia do portal, atualizado a cada novo acontecimento.
/* ============================================================================
LP Eleições 2026 — Seox · app.css
Fundação visual (CD-0). Servido via CDN versionado: app.css?v=N
Camadas: 1) tokens 2) reset/base 3) tipografia 4) primitivos de marca
5) shell de sessão + estados (loading/empty/erro) 6) utilitários
Grafismos/texturas entram numa etapa posterior — aqui é só cor + tipo + estrutura.
========================================================================== */
/* ===========================================================================
1. TOKENS
======================================================================== */
/* :where(:root) tem especificidade ZERO: estes valores sao apenas DEFAULTS. A
paleta que o construtor edita na folha lp-palette do HTML usa :root (0,1,0) e
vence estes valores esteja ela antes ou depois desta folha. Nunca subir este
seletor para :root.
ATENCAO: nunca escrever nomes de tag entre sinais de maior e menor nos
comentarios deste arquivo. Ele vai INTEIRO para dentro de um template no HTML
de importacao, e ali um texto assim e parseado como markup: abre um elemento
sem fechamento e engole o resto do documento. */
:where(:root) {
/* — Tokens editáveis pelo construtor (FONTE DA VERDADE) ——————————
O construtor altera estes; a LP e os blocos derivam deles. */
–primary: #10586B; /* cor principal */
–primary-hover: var(–primary); /* variante escura: deriva da primaria do portal */
–accent: #FFC800; /* realce (amarelo) */
–accent-hover: color-mix(in oklab, var(–accent) 87%, black); /* realce escurecido */
–bg: #FBFCFD; /* fundo de página */
–surface: #FFFFFF; /* superfície (cards / seções claras) */
–fg: #20242B; /* texto principal */
–muted: #4E545E; /* texto secundário */
–border: #E3E7EC; /* bordas */
–primary-contrast: #FFFFFF; /* texto sobre a primaria (token do construtor) */
–radius: 14px; /* raio base (token do construtor) */
/* Variantes vibrantes (versão alt do logo, p/ fundos escuros) */
–brand-azul-vivo: #36BFF1;
–brand-amarelo-vivo: #FFBF00;
–brand-verde-vivo: #49C36F;
–brand-azul-circulo: var(–primary-hover); /* círculo do losango */
/* Radial do hero e do masthead: a primaria SOBE de luminosidade rumo ao azul
vivo da marca, nao rumo ao branco (misturar com branco tira croma e deixa o
gradiente lavado). Escuro por baixo com preto, que nao afeta croma. */
–hero-radial-in: color-mix(in oklab, var(–primary, #10586B) 62%, var(–brand-azul-vivo, #36BFF1));
–hero-radial-out: color-mix(in oklab, var(–primary-hover, #10586B) 88%, black);
/* — Acentos semânticos (derivados da marca) —————————————————————— */
–accent-link: var(–brand-azul);
–accent-strong: var(–brand-azul-escuro);
–live: #E02424; /* vermelho “ao vivo” (fora da paleta institucional, só p/ selo) */
/* — Cor sobre fundos de marca ————————————————————————————————— */
–on-brand: var(–primary-contrast);
–on-amarelo: #20242B; /* texto sobre amarelo é escuro */
/* — Tipografia ————————————————————————————————————————————————— */
–font-title: ‘REM’, system-ui, sans-serif; /* títulos (token do construtor) */
–font-body: ‘Inter’, system-ui, sans-serif; /* corpo (token do construtor) */
–font-display: var(–font-title); /* alias interno */
–font-text: var(–font-body); /* alias interno */
/* — Layout ————————————————————————————————————————————————————— */
–maxw: 1320px; /* largura útil padrão (padronizada) */
/* Sombra dos cards que envolvem os blocos (Pesquisas, Candidatos, Enquete).
Puxada para o hue do teal da marca em vez de preto neutro. Vive aqui (CDN)
para poder ser ajustada sem reimportar as páginas instaladas. */
–lp-card-shadow: 0 20px 60px rgba(10, 60, 72, 0.24), 0 3px 12px rgba(10, 60, 72, 0.10);
–maxw-prosa: 720px; /* leitura longa */
–gutter: clamp(1.25rem, 0.5rem + 3vw, 3rem);
–container-pad: clamp(20px, 5vw, 48px); /* padding lateral padrão dos containers de sessão (48px no desktop) */
.muted { color: var(–ink-3); }
.prose { max-width: var(–maxw-prosa); text-wrap: pretty; }
.prose p + p { margin-top: var(–sp-4); }
/* ===========================================================================
4. PRIMITIVOS DE MARCA
======================================================================== */
/* ======================================================================== */
/* >>> CONSOLIDADO de sessoes/hero.css */
/* ============================================================================
Sessão 1 — HERO · sessoes/hero.css
Lettering de IMPACTO em loop. Decisões atuais:
– linha = flex centrado (não grid) → palavras fluem, no máx 2 linhas no desktop
– ease-out bem lento e dramático (–ease-hero, cauda longa); parada curta
– ASSINATURA: o bloco inteiro (logos + divisor + Eleições + apoio) EXPANDE junto;
dentro dele o divisor sobe e os logos saem do centro (portal←, Eleições→)
– apoio com espaçamento normal (não herda o tracking do display)
========================================================================== */
/* — Portes por breakpoint (logos da assinatura + countdown) — */
.hero {
–sign-portal: 56px;
–sign-eleicoes: 64px;
/* Teto de LARGURA do logo da cobertura: o portal pode trocar a marca por uma
propria (emissoras de TV), com proporcao desconhecida. A altura acima e o
teto vertical; este e o horizontal, para marca muito deitada nao empurrar o
lockup fora da tela. O logo padrao (632×398) fica bem abaixo destes valores,
entao nada muda para ele. */
–sign-eleicoes-maxw: min(240px, 62vw);
–sign-div: 56px;
–cd-num: 2rem;
–cd-bar-w: 52px;
}
@media (min-width: 768px) { .hero { –sign-portal: 84px; –sign-eleicoes: 96px; –sign-eleicoes-maxw: min(360px, 44vw); –sign-div: 78px; –cd-num: 2.6rem; –cd-bar-w: 60px; } }
@media (min-width: 1366px) { .hero { –sign-portal: 96px; –sign-eleicoes: 108px; –sign-eleicoes-maxw: min(430px, 34vw); –sign-div: 84px; –cd-num: 2.9rem; –cd-bar-w: 60px; } }
@media (min-width: 1920px) { .hero { –sign-portal: 134px; –sign-eleicoes: 150px; –sign-eleicoes-maxw: min(560px, 30vw); –sign-div: 116px; –cd-num: 3.7rem; –cd-bar-w: 72px; } }
.hero__sign-portal {
height: var(–sign-portal); width: auto;
flex: 0 0 auto;
opacity: 0; transform: translateX(72px); /* centro → ESQUERDA */
transition: transform 2.4s var(–ease-hero) 0.2s, opacity 1.6s ease 0.2s;
}
/* Logo da cobertura: pode ser o nosso ou a marca propria do portal. Tres limites,
nesta ordem: teto de altura, teto de largura (limite de composicao) e o encolher
do proprio flex (limite REAL, o unico que sabe quanto o logo do portal e os
intervalos ja consumiram da linha). Dimensoes automaticas em todos os casos,
entao a proporcao original e sempre preservada, sem corte nem distorcao. */
.hero__sign-eleicoes {
height: auto; width: auto;
max-height: var(–sign-eleicoes); max-width: var(–sign-eleicoes-maxw);
flex: 0 1 auto; min-width: 0;
object-fit: contain;
opacity: 0; transform: translateX(-72px); /* centro → DIREITA */
transition: transform 2.4s var(–ease-hero) 0.2s, opacity 1.6s ease 0.2s;
}
.hero__sign.is-in .hero__sign-portal,
.hero__sign.is-in .hero__sign-portal-txt,
.hero__sign.is-in .hero__sign-eleicoes { opacity: 1; transform: translateX(0); }
/* fallback textual quando o logo do portal não carrega (404/URL vazia) */
.hero__sign-portal-txt {
font-family: var(–font-display); font-weight: 800; color: #fff;
font-size: calc(var(–sign-portal) * 0.62); line-height: 1;
letter-spacing: -0.02em; white-space: nowrap;
opacity: 0; transform: translateX(72px);
transition: transform 2.4s var(–ease-hero) 0.2s, opacity 1.6s ease 0.2s;
}
/* ======================================================================== */
/* >>> CONSOLIDADO de sessoes/apuracao.css */
/* ============================================================================
Sessão 1b — APURAÇÃO (simulação) · sessoes/apuracao.css
Vitrine de homologação: substitui o hero durante as janelas de apuração.
Padrão visual da casa (paleta cívica, REM+Inter, cartela clara sobre o azul
radial do hero). O Figma do Publisher serve só de norte de layout.
Dados são SEMPRE fictícios — etiqueta “Simulação. Dados ilustrativos.” visível.
========================================================================== */
/* ======================================================================== */
/* >>> CONSOLIDADO de sessoes/aovivo.css */
/* ============================================================================
Sessão 2 — AO VIVO · sessoes/aovivo.css
Player de transmissão (autoplay mudo) + mini-player flutuante (PiP em CSS).
Fundo broadcast escuro (distinto do hero). Oculta por padrão: só renderiza
com features.aoVivo + fonte. O MESMO iframe é reposicionado (nunca recriado).
========================================================================== */
/* oculta por padrão; o JS revela quando habilitada (feature + fonte) */
.ao-vivo { display: none; }
.ao-vivo.is-on { display: block; }
/* mini-player não existe no mobile (decisão CD-4): nunca flutua */
@media (max-width: 768px) {
.av__pip.is-floating { position: absolute; box-shadow: none; }
.av__pip-bar, .av__pip-live { display: none !important; }
}
/* ======================================================================== */
/* >>> CONSOLIDADO de sessoes/noticias.css */
/* ============================================================================
Sessão 3 — NOTÍCIAS DA COBERTURA · sessoes/noticias.css
A LP entrega só o WRAPPER + personalização visual; os cards vêm do componente
related posts da plataforma (server-rendered): ul.seox-related[data-block=
“related-posts”] > li.seox-related__item. Este CSS estiliza ESSAS classes
reais com a identidade da Eleição (de-para a partir da LP da Copa). Layout:
grade 1/2/4 colunas, 1º item em destaque (imagem ao lado) em >=768px. Data
oculta por padrão (como na Copa). Empty state é do próprio bloco.
========================================================================== */
/* ======================================================================== */
/* >>> CONSOLIDADO de sessoes/debates.css */
/* ============================================================================
Sessão 6 — DEBATES · sessoes/debates.css
Agenda de debates (Presidente nacionais + Governador da UF selecionada).
Calendário único + filtro de UF. Próximo debate em destaque. Logos das redes
junto ao “Onde assistir”. 3 formatos trocáveis por [data-debates-fmt]:
lista · grade · timeline. Dados do calendario.json (eventos type=debate).
========================================================================== */
.cal-item.is-passado .cal-rail::before { background: var(–cal-azul); }
.cal-item.is-passado .cal-dot { border-color: var(–cal-azul); background: var(–cal-azul); }
.cal-item.is-proximo .cal-dot { border-color: var(–cal-amarelo); background: var(–cal-amarelo); box-shadow: 0 0 0 5px rgba(255,200,0,.20); }
/* Em andamento (periodo comecou e nao terminou): a trilha ja chegou nele, mas o
card segue ativo e o selo de proxima data vai para o marco seguinte. */
.cal-item.is-andamento .cal-rail::before { background: var(–cal-azul); }
.cal-item.is-andamento .cal-dot { border-color: var(–cal-azul); background: var(–cal-azul); }
.cal-item.is-destaque .cal-dot { width: 18px; height: 18px; border-color: var(–cal-azul); background: var(–cal-azul); }
/* card do marco: superficie branca com sombra; o proximo em azul escuro e os
turnos identificados pela pilula amarela na linha da data */
.cal-card-i { background: var(–cal-paper); border: 1px solid var(–cal-line); border-radius: 14px; padding: 16px;
margin-top: 12px; min-height: 0; box-shadow: 0 4px 14px rgba(20,30,50,.06); }
.cal-item.is-passado .cal-card-i { opacity: .7; box-shadow: none; background: var(–cal-soft); }
/* Turnos: card BRANCO como os demais (a pilula .cal-turno na linha da data e que
os identifica). Vem antes das regras de is-proximo de proposito: quando o turno
e tambem a proxima data, o tratamento forte de proximo vence o empate 0,3,0. */
.cal-item.is-destaque .cal-card-i { background: var(–cal-paper); border-color: var(–cal-line); box-shadow: 0 4px 14px rgba(20,30,50,.06); }
.cal-item.is-destaque .cal-titulo, .cal-item.is-destaque .cal-desc { color: var(–cal-ink); }
.cal-item.is-destaque .cal-periodo, .cal-item.is-destaque .cal-hora { color: var(–cal-ink2); }
/* icone volta ao padrao dos outros cards brancos (a regra antiga, branco 18% sobre
card teal, sumia no card branco). Antes do is-proximo: turno que tambem e a
proxima data mantem o circulo amarelo. */
.cal-item.is-destaque .cal-ic { background: var(–cal-azul); color: #fff; }
/* Card do proximo marco: fundo teal com texto branco (o tratamento forte da
timeline, antes usado nos turnos). */
.cal-item.is-proximo .cal-card-i { background: var(–cal-azul-d); border-color: var(–cal-azul-d); box-shadow: 0 12px 28px rgba(16,88,107,.28); }
.cal-item.is-proximo .cal-titulo, .cal-item.is-proximo .cal-desc { color: #fff; }
.cal-item.is-proximo .cal-periodo, .cal-item.is-proximo .cal-hora { color: #EAF1F8; }
/* ============================================================================
blocos-identidade.css — camada de identidade da LP sobre os 3 blocos REAIS
do addon Eleicoes (Seox Builder).
ARQUIVO GERADO: concatenacao de entrega/cd9/{cd9-tokens,cd9-pesquisas,
cd9-candidatos,cd9-enquete}.css. Nao editar aqui; editar os fontes do pacote
CD-9 e reconcatenar. Decisoes: entrega/cd9/CD-9-decisoes-finais.md.
Validado contra o build real no staging (CC, 07/08/2026).
Como usar: incluir DEPOIS do app.css (os CSS dos blocos entram no body; os
overrides vencem por ESPECIFICIDADE, nunca por ordem de carga).
============================================================================ */
/* ============================================================================
CD-9 · Camada 1 — tokens de marca no ancestral (:root)
Fonte da verdade: tokens do construtor (na LP, o app.css; no portal, o tema).
:where() zera a especificidade: esta garantia so vale onde NENHUMA definicao
real existir; qualquer :root de app.css/tema vence, independente da ordem de
carregamento. Nao usar :root puro aqui (venceria o app.css e retingiria a LP).
============================================================================ */
:where(:root) {
–primary: #10586B;
/* DERIVADOS, nao literais: esta folha carrega DEPOIS do app.css e empataria em
especificidade zero, anulando as derivacoes dele (a marca do portal deixaria
de propagar para tudo que usa –primary-hover). */
–primary-hover: var(–primary);
–accent: #FFC800;
–accent-hover: color-mix(in oklab, var(–accent) 87%, black);
–font-title: ‘REM’, system-ui, sans-serif;
–font-body: ‘Inter’, system-ui, sans-serif;
}
/* Cards que envolvem os blocos: mesma moldura nos tres (sombra tingida de teal,
token –lp-card-shadow do app.css). 0,3,0 para vencer a folha de secoes do
HTML instalado, que e inline no head e ganharia empate por ordem.
(Sem nomes de tag entre sinais de maior e menor: este CSS e embutido num
template no HTML de importacao, onde texto assim viraria markup.) */
.secao-pesquisas .rp-card.rp-card,
.secao-candidatos .lc-card.lc-card {
box-shadow: var(–lp-card-shadow, 0 20px 60px rgba(10, 60, 72, 0.24));
}
/* ============================================================================
CD-9 · Camada 2 — Central de Pesquisas (.rp / elections-survey-charts)
Overrides sobre as classes estaveis do bloco real (brief cd9-pesquisas, CC-7b).
Os ganchos –rp-teal/–rp-amarelo ja derivam de –primary-hover/–accent; aqui
entra apenas o que o bloco NAO liga aos tokens.
============================================================================ */
/* Tipografia: o bloco hardcoda ‘Inter’; passa a seguir o token do construtor. */
.rp { font-family: var(–font-body, ‘Inter’, system-ui, sans-serif); }
/* Conformidade legal: o rodape sai em 10px no bloco (e o mobile re-especifica
10px com 0,3,0). .rp.rp sobe a especificidade acima do empate: os CSS dos
blocos entram no BODY, depois desta folha, entao empate perde por ordem. */
.rp.rp .rp-legal, .rp.rp[data-device=”mobile”] .rp-legal { font-size: 12px; line-height: 1.5; }
/* Ring de foco dos filtros: ciano hardcoded no bloco (seletor real inclui o
container .rp-filtros, 0,3,0), trocar pelo teal com 0,4,0. */
.rp .rp-filtros .rp-filtro.is-open { box-shadow: 0 0 0 3px rgba(16, 88, 107, 0.14); }
/* Foco por teclado: o bloco nao tem estilo proprio de :focus-visible. */
.rp :is(.rp-tab, .rp-filtro, .rp-menu__opt):focus-visible { outline: 2px solid var(–primary-hover, #10586B); outline-offset: 2px; }
/* Loader e icone “complete a selecao”: cores hardcoded no bloco. Mantem a
paleta de apoio aprovada (verde, ciano) e liga o amarelo ao –accent. */
.rp .rp-loader i:nth-child(1) { background: #56BD3B; }
.rp .rp-loader i:nth-child(2) { background: var(–accent, #FFC800); }
.rp .rp-loader i:nth-child(3) { background: #30B3D3; }
.rp .rp-emptyico .k1 { fill: #56BD3B; }
.rp .rp-emptyico .k2 { fill: var(–accent, #FFC800); }
.rp .rp-emptyico .k3 { fill: #30B3D3; }
/* Loader parado para quem prefere menos movimento. */
@media (prefers-reduced-motion: reduce) { .rp .rp-loader i { animation: none; } }
/* Alvos de toque minimos de 44px em telas estreitas (a11y). */
@media (max-width: 760px) { .rp .rp-filtro, .rp .rp-menu__opt { min-height: 44px; } }
/* Caixa propria do bloco (build QA PUBLISHERS-4885) NEUTRALIZADA dentro do card
da LP: o .rp-card ja e a moldura. Linha pintada de branco (mantem a geometria
de 1px, sem reflow) e respiro interno zerado, para nao empilhar molduras.
0,3,0 vence o CSS do bloco, que e enfileirado no body. */
.rp-card .rp.rp { border-color: #fff; padding: 0; border-radius: 0; }
/* PAGINA INSTALADA: sem .rp-card por fora (o servidor troca o slot pelo bloco),
o proprio .rp e o card da secao. Mesma logica do .secao-candidatos .wrap > .lc. */
.secao-pesquisas .wrap > .rp {
background: var(–paper, #fff);
border: 0;
border-radius: 20px;
padding: clamp(20px, 3vw, 40px);
box-shadow: var(–lp-card-shadow, 0 20px 60px rgba(10, 60, 72, 0.24));
min-height: 520px;
}
/* Anti-CLS: o CSR descarta o SSR e remonta; reservar altura no wrapper da LP. */
.secao-pesquisas .rp-card { min-height: 520px; }
/* Nota: a secao da LP tem fundo branco, entao os brancos hardcoded do bloco
(rp-veil, rp-pill, rp-grip) combinam. Se o fundo mudar, sobrescrever:
.rp .rp-veil { fill: COR-DO-FUNDO; } .rp .rp-pill, .rp .rp-grip { background: COR-DO-FUNDO; }
(Sem tags nem sinais de maior e menor: este CSS e embutido num template no HTML
de importacao, onde texto assim viraria markup.) */
/* ============================================================================
CD-9 · Camada 2 — Candidatos/Apuracao (.lc / elections-results)
Overrides sobre as classes estaveis do bloco real (brief cd9-candidatos, CC-7b).
Os ganchos –lc-teal/–lc-teal-d ja derivam de –primary/–primary-hover.
============================================================================ */
/* Tipografia: o bloco hardcoda ‘Inter’; passa a seguir o token do construtor. */
.lc { font-family: var(–font-body, ‘Inter’, system-ui, sans-serif); }
/* Rings de foco: ciano hardcoded no bloco (seletores reais incluem .lc-filtros,
0,3,0); 0,4,0 vence o empate por ordem (CSS do bloco entra no body). */
.lc .lc-filtros .lc-filtro.is-open, .lc .lc-busca__in:focus { box-shadow: 0 0 0 3px rgba(16, 88, 107, 0.14); }
/* Foco por teclado nos controles (ausente no bloco). */
.lc :is(.lc-tab, .lc-filtro, .lc-menu__opt, .lc-pg):focus-visible, .lc .lc-busca__in:focus-visible { outline: 2px solid var(–primary-hover, #10586B); outline-offset: 2px; }
/* Hover perceptivel na paginacao: –primary e –primary-hover sao a mesma cor
nos tokens atuais, entao o hover nativo do bloco fica invisivel. Seletor do
bloco e .lc-pager .lc-pg–nav:hover (0,3,1): incluir .lc-pager para vencer. */
.lc .lc-pager .lc-pg–nav:hover { background: color-mix(in oklab, var(–primary-hover, #10586B) 82%, black); }
.lc .lc-pg:not(.lc-pg–nav):not(.is-active):hover { background: #F0F5F6; }
/* O loader do addon anima mesmo com prefers-reduced-motion (gap do bloco;
pedido 9 do brief segue de pe). Mitigado por CSS aqui. */
@media (prefers-reduced-motion: reduce) { .lc .lc-loader3 span { animation: none; } }
/* Alvos de toque minimos de 44px em telas estreitas (a11y). */
@media (max-width: 760px) { .lc .lc-filtro, .lc .lc-busca__in, .lc .lc-menu__opt { min-height: 44px; } }
/* Caixa propria do bloco (build QA PUBLISHERS-4885) NEUTRALIZADA so ONDE EXISTE
moldura da LP por fora: dentro do .lc-card (markup do prototipo) e no painel da
Apuracao (o mesmo bloco montado via pre-busca dentro do .apu__bloco, que ja tem
padding proprio). Ver nota em cd9-pesquisas.css. */
.lc-card .lc.lc, .apuracao .lc.lc { border-color: #fff; padding: 0; border-radius: 0; }
/* PAGINA INSTALADA: o .lc-card NAO existe. O construtor substitui o slot pelo
bloco no servidor, entao o .lc nasce direto dentro do .wrap da secao, sem
moldura por fora, e a secao Candidatos tem fundo escuro: sem esta regra a lista
fica nua sobre o teal (texto escuro ilegivel). Aqui o proprio .lc E o card.
0,3,0 com o combinador de filho vence o CSS do bloco, enfileirado no body. */
.secao-candidatos .wrap > .lc {
background: var(–paper, #fff);
border: 0;
border-radius: 20px;
padding: clamp(20px, 3vw, 40px);
box-shadow: var(–lp-card-shadow, 0 20px 60px rgba(10, 60, 72, 0.24));
min-height: 560px;
}
/* Anti-CLS no wrapper da LP (no prototipo o .lc-card e a moldura da secao). */
.secao-candidatos .lc-card { min-height: 560px; }
/* Utilitario OPCIONAL, desligado: ocultar a aba 2o Turno enquanto nao ha dado
real (pendencia 7 do brief pede flag no payload). Se o editorial pedir antes
da flag, ligar por fase da LP:
html[data-fase=”cobertura”] .lc .lc-tab[data-turno=”2″] { display: none; } */
/* ============================================================================
CD-9 · Camada 2 — Enquete (.survey-publishers / .section-survey-form-* / poll)
Overrides sobre as classes estaveis do bloco v2 (brief cd9-enquete, CC-7b).
Escopo .secao-enquete: e o wrapper da LP; nos portais o slot vive dentro dele.
============================================================================ */
/* Ganchos Bootstrap do tema no escopo do bloco + tipografia do construtor. */
.secao-enquete .survey-publishers {
–bs-primary: var(–primary-hover, #10586B);
–bs-border-radius: 12px;
font-family: var(–font-body, ‘Inter’, system-ui, sans-serif);
}
/* Cor de destaque por opcao: –answer-color vem INLINE do admin (precisa
!important). Cobre outline de selecao, hover e barra de resultado. */
.secao-enquete .section-survey-form-question {
–answer-color: var(–primary-hover, #10586B) !important;
}
/* Opcao: o bloco entrega input REAL oculto por acessibilidade + label com borda
e fundo cinza das utilities. Vestir igual ao vocabulario da LP (superficie
branca, borda 1.5px, raio 14px) e DESENHAR o radio, que o bloco nao imprime:
circulo via ::before no label, preenchido no :checked. Sem tocar markup.
Classe dobrada (0,4,0) para vencer base-css e style-index, ambos no body. */
.secao-enquete .survey-publishers .section-survey-form-question-label.section-survey-form-question-label {
display: flex;
align-items: center;
gap: 16px;
background: var(–paper, #fff);
border: 1.5px solid var(–line, #E6EAEF);
border-radius: 14px;
padding: 16px;
transition: border-color .15s ease, background .15s ease, box-shadow .15s ease;
}
.secao-enquete .survey-publishers .section-survey-form-question-label.section-survey-form-question-label::before {
content: “”;
flex: none;
width: 22px;
height: 22px;
border-radius: 50%;
border: 2px solid var(–line, #E6EAEF);
background:
radial-gradient(circle, var(–primary-hover, #10586B) 0 55%, transparent 56%) center / 0 0 no-repeat;
transition: border-color .15s ease, background-size .15s cubic-bezier(.34,1.56,.64,1);
}
.secao-enquete .survey-publishers .section-survey-form-question-label:hover {
border-color: var(–primary-hover, #10586B);
background: color-mix(in oklab, var(–primary-hover, #10586B) 5%, white);
}
.secao-enquete .survey-publishers .section-survey-form-question-input:checked + .section-survey-form-question-label {
border-color: var(–primary-hover, #10586B);
background: color-mix(in oklab, var(–primary-hover, #10586B) 5%, white);
}
.secao-enquete .survey-publishers .section-survey-form-question-input:checked + .section-survey-form-question-label::before {
border-color: var(–primary-hover, #10586B);
background-size: 100% 100%;
}
/* Respiro: o form usa gap-3 das utilities do tema, inerte sem o Bootstrap dele,
e o botao encostava na ultima opcao. */
.secao-enquete .survey-publishers .section-survey-form { gap: 24px; }
.secao-enquete .survey-publishers .section-survey-form-questions { display: flex; flex-direction: column; gap: 12px; }
/* Estado selecionado (modificador BEM real do build; “.–selected” nao existe):
base-css poe 2px no label e o style-index 4px no wrapper; sobrescrever os dois
para um realce unico e limpo. Especificidade 0,4,0 vence o style-index (0,3,0),
que e enfileirado no body, depois desta folha. */
.secao-enquete .survey-publishers .section-survey-form-question.section-survey-form-question–selected,
.secao-enquete .survey-publishers .section-survey-form-question:focus-within {
outline: 2px solid var(–primary-hover, #10586B);
outline-offset: 2px;
border-radius: 12px;
}
.secao-enquete .section-survey-form-question–selected label { outline: none; }
/* Botao Votar: o base-css usa background:currentColor / color:Canvas (cor de
sistema, imprevisivel). Fixar na identidade; hover escurecido por color-mix
(–primary e –primary-hover sao a mesma cor nos tokens atuais). */
.secao-enquete .section-survey-form-submit {
background: var(–primary-hover, #10586B);
color: #fff;
border: none;
border-radius: 12px;
padding: 12px 32px;
font-weight: 700;
}
.secao-enquete .section-survey-form-submit:hover:not(:disabled) {
background: color-mix(in oklab, var(–primary-hover, #10586B) 82%, black);
}
.secao-enquete .section-survey-form-submit:disabled {
background: #E6EAEF;
color: #A3ABB5;
cursor: not-allowed;
}
/* Nota legal + layout do rodape. O markup real traz .section-survey-form-footer
com DUAS celulas: a primeira VAZIA e a segunda com o botao Votar (base-css faz
flex com margin-left:auto no botao). A celula vazia e o lugar natural da nota
(“sem valor cientifico”), que o bloco nao imprime: injetada por content ate
existir campo no admin (pedido ao backend registrado em PENDENCIAS).
A copy fica num token para o portal poder trocar no HTML. */
.secao-enquete .survey-publishers {
/* acentos em escape Unicode: a copy entregavel leva acento, e o escape a torna
imune a charset errado no servidor do portal. */
–lp-enquete-nota: “Enquete de opini000e3o, sem valor cient000edfico. Um voto por pessoa neste dispositivo.”;
}
.secao-enquete .survey-publishers .section-survey-form-footer {
display: flex;
flex-wrap: wrap;
align-items: center;
gap: 16px;
margin-top: 4px;
}
.secao-enquete .survey-publishers .section-survey-form-footer > div:first-child {
flex: 1 1 260px;
min-width: 0;
}
.secao-enquete .survey-publishers .section-survey-form-footer > div:first-child::after {
content: var(–lp-enquete-nota);
display: block;
font-size: 12px;
line-height: 1.5;
color: var(–ink-3, #6B727B);
text-wrap: pretty;
}
.secao-enquete .survey-publishers .section-survey-form-footer > div:last-child { flex: 0 0 auto; }
/* Barra de resultado e porcentagem. */
.secao-enquete .section-survey-form-question-progress-bar-filled {
background: var(–answer-color, var(–primary-hover, #10586B));
}
.secao-enquete .section-survey-form-question-percentage {
font-variant-numeric: tabular-nums;
font-weight: 800;
color: var(–primary-hover, #10586B);
}
/* Cartao do bloco: o base-css desenha BORDA + raio pequeno, o que deixava a
Enquete com contorno enquanto Pesquisas e Candidatos aparecem em card com
sombra. Igualar aos outros dois: sem borda, raio 20px e a sombra tingida de
teal do token. 0,3,0 vence o style-index (0,2,0/0,3,0 no body). */
.secao-enquete .survey-publishers.survey-publishers {
background: #fff;
border: 0;
border-radius: 20px;
padding: clamp(20px, 3vw, 40px);
box-shadow: var(–lp-card-shadow, 0 20px 60px rgba(10, 60, 72, 0.24));
}
/* Margem negativa mobile do style-index (.survey-publishers { margin: 0 -24px })
pode vazar do wrapper da LP: neutralizar. */
@media (max-width: 768px) {
.secao-enquete .survey-publishers { margin-left: 0; margin-right: 0; }
}
/* Anti-CLS: o React descarta o SSR e mostra skeleton antes do form. */
.secao-enquete .survey-block-container { min-height: 440px; }
‘ +
‘
‘ + getContent().global.comum.erro + ‘
‘ +
”;
}
/* =========================================================================
REVEAL por IntersectionObserver (entrada de blocos)
====================================================================== */
function initReveal() {
var els = $all(‘.reveal’);
if (!(‘IntersectionObserver’ in window) || !els.length) {
els.forEach(function (el) { el.classList.add(‘is-in’); });
return;
}
var io = new IntersectionObserver(function (entries) {
entries.forEach(function (e) {
if (e.isIntersecting) { e.target.classList.add(‘is-in’); io.unobserve(e.target); }
});
}, { rootMargin: ‘0px 0px -10% 0px’, threshold: 0.08 });
els.forEach(function (el) { io.observe(el); });
}
// observa também blocos adicionados depois (sessões que montam tarde)
function observeReveal(el) {
$all(‘.reveal’, el).concat(el.classList && el.classList.contains(‘reveal’) ? [el] : [])
.forEach(function (n) { n.classList.add(‘is-in’); });
}
/* =========================================================================
MENU DE ÂNCORAS (bullets flutuantes) — decisão S1
Monta-se sozinho varrendo [data-anchor-label][id]: reflete as sessões
realmente presentes e sua ordem. Bullet some se a sessão não existe ou
está oculta. Rótulo à esquerda, bolinha à direita (ordem via CSS).
– Scrollspy por IntersectionObserver (seção mais central ativa).
– Clique rola sem usar #hash (seguro com ).
– Reavalia no load, após atrasos e em mutações do DOM (conteúdo assíncrono).
– Re-sincronizável em runtime via LP.syncAnchors().
====================================================================== */
var _anchorNav = null;
var _anchorSig = ”;
var _anchorObs = null;
var _reduzMotion = window.matchMedia && window.matchMedia(‘(prefers-reduced-motion: reduce)’).matches;
function ativarAnchor(id) {
if (!_anchorNav) return;
$all(‘a[data-anchor]’, _anchorNav).forEach(function (a) {
a.classList.toggle(‘ativo’, a.getAttribute(‘href’) === ‘#’ + id);
});
}
function onAnchorClick(ev) {
var a = ev.currentTarget;
var el = document.getElementById(a.getAttribute(‘href’).slice(1));
if (!el) return;
ev.preventDefault();
var topo = el.getBoundingClientRect().top + (window.pageYOffset || window.scrollY || 0) – 8;
try { window.scrollTo({ top: topo, behavior: _reduzMotion ? ‘auto’ : ‘smooth’ }); }
catch (e) { window.scrollTo(0, topo); }
ativarAnchor(el.id);
}
function observarScrollspy(secs) {
if (_anchorObs) { _anchorObs.disconnect(); _anchorObs = null; }
if (!(‘IntersectionObserver’ in window)) return;
var vis = {};
_anchorObs = new IntersectionObserver(function (entries) {
entries.forEach(function (e) { vis[e.target.id] = e.isIntersecting ? e.intersectionRatio : 0; });
var melhor = null, melhorR = 0;
secs.forEach(function (el) { var r = vis[el.id] || 0; if (r > melhorR) { melhorR = r; melhor = el.id; } });
if (melhor) ativarAnchor(melhor);
}, { rootMargin: ‘-45% 0px -45% 0px’, threshold: [0, 0.25, 0.5, 1] });
secs.forEach(function (el) { _anchorObs.observe(el); });
}
// (re)monta o menu se o conjunto de âncoras visíveis mudou
function syncAnchors() {
var secs = $all(‘[data-anchor-label][id]’).filter(anchorVisivel);
var sig = secs.map(function (s) { return s.id; }).join(‘|’);
if (sig === _anchorSig) return;
_anchorSig = sig;
if (!secs.length) {
if (_anchorNav) { _anchorNav.remove(); _anchorNav = null; }
if (_anchorObs) { _anchorObs.disconnect(); _anchorObs = null; }
return;
}
if (!_anchorNav) {
_anchorNav = document.createElement(‘nav’);
_anchorNav.className=”anchor-nav”;
_anchorNav.setAttribute(‘aria-label’, ‘Navegação por seções’);
_anchorNav.appendChild(document.createElement(‘ul’));
document.body.appendChild(_anchorNav);
}
var ul = _anchorNav.querySelector(‘ul’);
ul.innerHTML = ”;
secs.forEach(function (sec) {
var li = document.createElement(‘li’);
var a = document.createElement(‘a’);
a.href=”#” + sec.id;
a.setAttribute(‘data-anchor’, ”);
var label = document.createElement(‘span’);
label.className=”an-label”;
label.textContent = sec.getAttribute(‘data-anchor-label’);
var dot = document.createElement(‘span’);
dot.className=”an-dot”;
dot.setAttribute(‘aria-hidden’, ‘true’);
// rótulo acessível (a bolinha é decorativa)
a.setAttribute(‘aria-label’, sec.getAttribute(‘data-anchor-label’));
a.appendChild(label);
a.appendChild(dot);
a.addEventListener(‘click’, onAnchorClick);
li.appendChild(a);
ul.appendChild(li);
});
observarScrollspy(secs);
}
function initAnchorMenu() {
syncAnchors();
window.addEventListener(‘load’, syncAnchors);
setTimeout(syncAnchors, 1500);
setTimeout(syncAnchors, 4000);
// reavalia quando sessões montam/escondem em runtime (conteúdo assíncrono)
if (‘MutationObserver’ in window) {
var deb;
var mo = new MutationObserver(function () {
clearTimeout(deb);
deb = setTimeout(syncAnchors, 200);
});
mo.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: [‘style’, ‘class’, ‘hidden’] });
}
}
/* =========================================================================
BOOT
====================================================================== */
var _booted = false;
function boot() {
if (_booted) return;
_booted = true;
loadContent();
carregarConfigAddon();
// deep-link de fase no demo (?fase=apuracao1) — útil para harness/preview
var faseURL = (location.search.match(/[?&]fase=([a-zA-Z0-9]+)/) || [])[1];
if (faseURL && FASES.indexOf(faseURL) >= 0) { _faseOverride = faseURL; }
// toggle de demo do Ao Vivo (?aovivo=0|1) — controlado pelo cockpit
var aoVivoURL = (location.search.match(/[?&]aovivo=([01])/) || [])[1];
if (aoVivoURL != null) { CONFIG.flags.aoVivoHabilitado = (aoVivoURL === ‘1’); }
aplicarFase(calcularFase());
// Reavaliação periódica (CC-8): sem override, recalcula a fase a cada 60s para
// captar a virada de janela e o embedUrl passando de null->preenchido no dia.
if (!_faseOverride) setInterval(function () { if (!_faseOverride) aplicarFase(calcularFase()); }, 60000);
initReveal();
initAnchorMenu();
// monta as sessões já presentes no DOM
_sections.forEach(tryMount);
// re-render leve quando o content vivo chega (fases dependem de datas)
document.addEventListener(‘lp:content’, function () {
aplicarFase(calcularFase());
});
function elt(tag, cls) { var e = document.createElement(tag); if (cls) e.className = cls; return e; }
function stripDot(s) { return String(s).replace(/.s*$/, ”); }
function sleep(ms) { return new Promise(function (r) { setTimeout(r, ms); }); }
function pad(n) { return (n < 10 ? ‘0’ : ”) + n; }
function buildLine(text, ulFrom) {
var line = elt(‘div’, ‘hero__line’);
var t = elt(‘div’, ‘hero__line__t’);
var parts = String(text).split(‘ ‘);
var hasUl = typeof ulFrom === ‘number’ && ulFrom >= 0 && ulFrom < parts.length;
var ul = hasUl ? elt(‘span’, ‘hero__ul’) : null;
parts.forEach(function (word, idx) {
var w = elt(‘span’, ‘w’);
var i = elt(‘span’, ‘w__i’);
i.style.setProperty(‘–i’, idx);
i.textContent = word;
w.appendChild(i);
if (hasUl && idx === ulFrom) t.appendChild(ul);
var box = (hasUl && idx >= ulFrom) ? ul : t;
box.appendChild(w);
if (idx < parts.length – 1) box.appendChild(document.createTextNode(‘ ‘));
});
line.appendChild(t);
return line;
}
function buildSignature(ctx) {
var sign = elt(‘div’, ‘hero__sign’);
var lockup = elt(‘div’, ‘hero__sign-lockup’);
lockup.setAttribute(‘aria-label’, ctx.portalNome + ‘ e Eleições 2026’);
var portal = elt(‘img’, ‘hero__sign-portal’);
// Caminho relativo do mock resolve no CDN; URL do /config passa intacta.
portal.src = window.LP.assetUrl(ctx.portalLogo);
portal.alt=””; portal.setAttribute(‘aria-hidden’, ‘true’);
// Logo do portal indisponível (404/URL vazia): degrada para o NOME em texto,
// no lugar do ícone quebrado + alt gigante.
portal.addEventListener(‘error’, function () {
var txt = elt(‘span’, ‘hero__sign-portal-txt’);
txt.textContent = ctx.portalNome || ”;
txt.setAttribute(‘aria-hidden’, ‘true’);
if (portal.parentNode) portal.parentNode.replaceChild(txt, portal);
});
var div = elt(‘span’, ‘hero__sign-div’); div.setAttribute(‘aria-hidden’, ‘true’);
var ele = elt(‘img’, ‘hero__sign-eleicoes’);
window.LP.aplicarLogoCobertura(ele); ele.alt=”Eleições 2026″; ele.setAttribute(‘aria-hidden’, ‘true’);
lockup.appendChild(portal); lockup.appendChild(div); lockup.appendChild(ele);
var apoio = elt(‘p’, ‘hero__sign-apoio’);
apoio.textContent = ctx.apoio;
function startCountdown(host, target, acervo) {
if (host.__cdTimer) { clearInterval(host.__cdTimer); host.__cdTimer = null; }
if (acervo) return;
function set(k, v) { var e = host.querySelector(‘[data-cd=”‘ + k + ‘”]’); if (e) e.textContent = v; }
function tick() {
var diff = target – Date.now();
if (diff < 0) diff = 0;
set(‘dias’, Math.floor(diff / 86400000));
set(‘horas’, pad(Math.floor((diff % 86400000) / 3600000)));
set(‘min’, pad(Math.floor((diff % 3600000) / 60000)));
set(‘seg’, pad(Math.floor((diff % 60000) / 1000)));
}
tick();
host.__cdTimer = setInterval(tick, 1000);
}
function mountHero(host) {
if (!window.LP) return;
var content = LP.getContent();
var fase = LP.calcularFase();
var cd = content.global.countdown;
var acervo = (fase === ‘acervo’);
var heroData = acervo ? content.hero.posEleicao : content.hero;
var segments = (heroData.chamadaSegmentos || []).map(stripDot);
var stage = host.querySelector(‘.hero__stage’);
if (!stage) return;
/* Lido A CADA ciclo, nao capturado no boot: o GET /config chega depois, e uma
assinatura remontada com o valor velho fazia a marca piscar entre a do
fallback e a do portal a cada volta da intro. */
function mkCtx() {
return {
portalLogo: LP.config.portal.logo,
portalNome: LP.config.portal.nome,
apoio: LP.resolvePortal(heroData.textoApoio)
};
}
// countdown
var target, suffix;
if (fase === ‘entreTurnos’ || fase === ‘apuracao2’) { target = new Date(cd.turno2).getTime(); suffix = cd.labelTurno2; }
else { target = new Date(cd.turno1).getTime(); suffix = cd.labelTurno1; }
buildCountdown(host, cd, suffix, acervo);
startCountdown(host, target, acervo);
var gen = (host.__gen || 0) + 1; host.__gen = gen;
function alive() { return host.__gen === gen && document.contains(host); }
stage.innerHTML = ”;
if (STATIC || !segments.length) {
host.classList.add(‘hero–static’);
if (FREEZE && /sign/.test(location.search)) {
var sg = buildSignature(mkCtx());
sg.classList.add(‘is-in’);
stage.appendChild(sg);
} else {
var line = elt(‘div’, ‘hero__line is-in’);
var t = elt(‘div’, ‘hero__line__t’);
t.textContent = segments[2] || segments[0] || ”; // linha-tese estática
line.appendChild(t);
stage.appendChild(line);
}
return;
}
host.classList.remove(‘hero–static’);
(async function runLoop() {
while (alive()) {
for (var k = 0; k < segments.length; k++) {
if (!alive()) return;
var last = (k === segments.length – 1);
var ulFrom = last ? Math.max(0, segments[k].split(‘ ‘).length – 3) : -1;
await showLine(segments[k], last, ulFrom);
}
if (!alive()) return;
await showSignature();
}
})();
function nWords(text) { return String(text).split(‘ ‘).length; }
async function showLine(text, isLast, ulFrom) {
var line = buildLine(text, ulFrom);
stage.appendChild(line);
void line.offsetWidth;
line.classList.add(‘is-in’);
var n = nWords(text);
var enterTotal = T.wordDur + (n – 1) * T.wordStagger;
await sleep(enterTotal + T.hold + (isLast ? T.seg4extra : 0));
if (!alive()) { line.remove(); return; }
line.classList.remove(‘is-in’);
line.classList.add(‘is-out’);
await sleep(T.exitBase + (n – 1) * T.exitStagger);
line.remove();
}
async function showSignature() {
var sign = buildSignature(mkCtx());
stage.appendChild(sign);
void sign.offsetWidth;
sign.classList.add(‘is-in’);
await sleep(T.signTotal); // expansão contínua + fade out pelo keyframe
sign.remove();
}
}
function initAll() {
if (!window.LP) return;
LP.$all(‘.hero[data-hero]’).forEach(mountHero);
}
function boot() {
initAll();
document.addEventListener(‘lp:fase’, initAll);
document.addEventListener(‘lp:content’, initAll);
// GET /config chega depois do boot: troca o logo do portal EM CENA (sem
// re-animar a intro). Sem isto, um portal ficaria com a marca do mock.
document.addEventListener(‘lp:config’, function () {
var url = window.LP && window.LP.assetUrl(LP.config.portal.logo);
if (url) LP.$all(‘.hero__sign-portal’).forEach(function (img) { if (img.src !== url) img.src = url; });
// marca da cobertura tambem troca em cena (emissora com marca propria)
LP.$all(‘.hero__sign-eleicoes’).forEach(LP.aplicarLogoCobertura);
});
}
if (window.LP) {
if (document.readyState === ‘loading’) document.addEventListener(‘DOMContentLoaded’, boot);
else boot();
} else {
document.addEventListener(‘DOMContentLoaded’, boot);
}
})();
/* ======================================================================== */
/* >>> CONSOLIDADO de sessoes/apuracao.js */
/* ============================================================================
Sessão 1b — APURAÇÃO (simulação) · sessoes/apuracao.js
Vitrine de homologação que substitui o hero durante a janela de apuração.
Gera dados SEMPRE fictícios localmente (rótulos genéricos, siglas inventadas,
sem nomes/partidos reais), anima % de urnas 0→~target com as barras crescendo,
e permite trocar turno/disputa, buscar e paginar — sem recarregar.
O embed real do TSE entrará no lugar da simulação no CD-10, sem mexer na moldura.
========================================================================== */
(function () {
‘use strict’;
var REDUCED = window.matchMedia && window.matchMedia(‘(prefers-reduced-motion: reduce)’).matches;
var FREEZE = /[?&]freezeb/.test(location.search);
var PER_PAGE = 10;
/* —- utils ————————————————————– */
function elt(tag, cls, html) { var e = document.createElement(tag); if (cls) e.className = cls; if (html != null) e.innerHTML = html; return e; }
function hash(str) { var h = 2166136261; for (var i = 0; i < str.length; i++) { h ^= str.charCodeAt(i); h = Math.imul(h, 16777619); } return h >>> 0; }
function mulberry(seed) { return function () { seed |= 0; seed = seed + 0x6D2B79F5 | 0; var t = Math.imul(seed ^ seed >>> 15, 1 | seed); t = t + Math.imul(t ^ t >>> 7, 61 | t) ^ t; return ((t ^ t >>> 14) >>> 0) / 4294967296; }; }
function fmt(n) { return Math.round(n).toLocaleString(‘pt-BR’); }
function fmtPct(p) { return p.toFixed(2).replace(‘.’, ‘,’) + ‘%’; }
function numeroCargo(dispId, seed) {
var d = DIGITOS[dispId] || 2;
var min = Math.pow(10, d – 1), max = Math.pow(10, d) – 1;
return min + (seed % (max – min + 1));
}
function gerarDisputa(turno, disp) {
var rnd = mulberry(hash(turno + ‘|’ + disp.id));
var n = disp.n;
// pesos decrescentes plausíveis (líder destacado), normalizados p/ votos válidos
var pesos = [];
var ledLead = 0.20 + rnd() * 0.18; // 20–38% p/ o líder
pesos.push(ledLead);
var rest = 1 – ledLead;
for (var i = 1; i < n; i++) {
var w = rest * (0.30 + rnd() * 0.45) / n;
pesos.push(w);
}
var soma = pesos.reduce(function (a, b) { return a + b; }, 0);
pesos = pesos.map(function (w) { return w / soma; }).sort(function (a, b) { return b – a; });
// totais (fictícios, plausíveis)
var eleitores = Math.round(disp.base * (0.96 + rnd() * 0.08));
var abstencao = Math.round(eleitores * (0.18 + rnd() * 0.06));
var compareceram = eleitores – abstencao;
var brancos = Math.round(compareceram * (0.010 + rnd() * 0.02));
var nulos = Math.round(compareceram * (0.025 + rnd() * 0.02));
var validos = compareceram – brancos – nulos;
var wi = 0, mi = 0; // cursores para nomes/fotos por gênero
var majoritario = (disp.id === ‘presidente’ || disp.id === ‘governador’);
var cands = pesos.map(function (p, i) {
var votos = Math.round(p * validos);
var ehMulher = rnd() < 0.35;
var persona = ehMulher ? MULHERES[wi++ % MULHERES.length] : HOMENS[mi++ % HOMENS.length];
var pct = p * 100;
// status (Situação da Totalização): líder >50% Eleito; senão top2 majoritário vão a 2º turno
var status;
if (majoritario) {
if (i === 0 && pct > 50) status=”Eleito”;
else if (i < 2) status=”2º Turno”;
else status=”Não Eleito”;
} else {
status = i === 0 ? ‘Eleito’ : ‘Não Eleito’;
}
return {
nome: persona.nome,
foto: persona.foto,
sigla: SIGLAS[(hash(disp.id + i) % SIGLAS.length)],
numero: numeroCargo(disp.id, hash(disp.id + ‘n’ + i)),
pct: pct,
votos: votos,
lider: i === 0,
status: status,
classificado: (status === ‘Eleito’ || status === ‘2º Turno’),
situacao: ‘Válido’
};
});
var dados = null; // disputa atual (só p/ urnasTarget do cabeçalho ao vivo)
var _blocoMounted = false;
var _mountTries = 0;
var _prefetching = false;
var OFFICE_CODES = { presidente: 1, governador: 3, senador: 5, ‘dep-fed’: 6, ‘dep-est’: 7 };
function mountBloco() {
var el = host.querySelector(‘[data-candidatos]’);
if (!el) return;
// Demo: protótipo local (repo-candidatos.js).
if (window.LCandidatos && window.LCandidatos.mount) { window.LCandidatos.mount(el); _blocoMounted = true; return; }
/* Produção (export sem mock): decisão Q2 (CC, 07/08/2026). O mount do bloco
real em nó sem data-payload é no-op silencioso, então o caminho é
PRÉ-BUSCA + INJEÇÃO: busca o JSON no endpoint do builder, injeta cru em
data-payload e só então monta via window.SeoxCandidatos.mount. */
if (window.SeoxCandidatos && window.SeoxCandidatos.mount) {
// Gate de visibilidade: fora da janela (seção oculta pelo gate de fase),
// NUNCA pré-buscar; senão todo pageview em cobertura dispararia um GET
// mode=apuracao e montaria bloco em seção invisível. O lp:fase re-inicia
// o módulo quando a janela abrir.
if (!sectionVisible()) return;
if (_prefetching || _blocoMounted) return;
_prefetching = true;
var d = dispAtual();
var office = OFFICE_CODES[d.id] || 3;
var uf = (office === 1) ? ” : ((CONFIG.portal.uf || ”)).toUpperCase();
var endpoint=”/wp-json/seox-builder/v1/blocks/elections-results”;
var qs=”?mode=apuracao&office=” + office + ‘&uf=” + encodeURIComponent(uf) +
“&round=’ + state.turno + ‘&page=1&search=&visitor_filters=1’;
fetchJSON(endpoint + qs)
.then(function (json) {
_prefetching = false;
if (_blocoMounted || !el.isConnected) return;
el.setAttribute(‘data-payload’, JSON.stringify(json));
el.setAttribute(‘data-endpoint’, endpoint);
el.setAttribute(‘data-mode-arg’, ‘apuracao’);
el.setAttribute(‘data-visitor-filters’, ‘1’);
window.SeoxCandidatos.mount(el);
// Bloco real traz o próprio resumo: esconde o cabeçalho ao vivo simulado
// (mesmo tratamento do modo iframe) e garante a seção visível.
var apu = host.querySelector(‘.apu’);
if (apu) apu.classList.add(‘apu–embed’);
var sec = host.closest(‘section’); if (sec) sec.style.display = ”;
_blocoMounted = true;
})
.catch(function () {
// Falha/timeout da pré-busca: fallback silencioso da seção (H3/H5),
// nunca bloco vazio. Estilo inline: o gate html[data-apuracao=”on”]
// aplica display:grid por CSS e venceria o atributo hidden.
_prefetching = false;
var sec = host.closest(‘section’);
if (sec && !_blocoMounted) sec.style.display = ‘none’;
setTimeout(mountBloco, 60000); // nova tentativa (janela segue aberta)
});
return;
}
if (_mountTries++ < 100) setTimeout(mountBloco, 60); // espera um dos módulos carregar
}
// embedUrl mudou no /config (60s durante a janela): re-renderiza o bloco/iframe.
document.addEventListener(‘lp:config-embed’, function () {
if (sectionVisible()) { var sec = host.closest(‘section’); if (sec) sec.style.display = ”; renderBloco(); }
});
function sectionVisible() {
return document.documentElement.getAttribute(‘data-apuracao’) === ‘on’;
}
/* embedUrl da janela de apuração (GET /config: apuracao.windows[].embedUrl).
No mock é janelasApuracao[].embed. Preenchido => iframe oficial do TSE no
lugar da simulação; vazio => mantém o bloco de Candidatos puxando da API. */
function embedDoTurno(t) {
var arr = (LP.config && LP.config.janelasApuracao) || CONFIG.janelasApuracao || [];
var j = arr.filter(function (x) { return x.turno === t; })[0];
return (j && (j.embed || j.embedUrl)) || ”;
}
function renderBloco() {
var wrap = host.querySelector(‘[data-apu-bloco]’);
if (!wrap) return;
var apu = host.querySelector(‘.apu’);
var url = embedDoTurno(state.turno);
if (url) {
if (apu) apu.classList.add(‘apu–embed’); // esconde o cabeçalho ao vivo (CSS)
wrap.innerHTML = ”;
_blocoMounted = false;
return;
}
if (apu) apu.classList.remove(‘apu–embed’);
if (!wrap.querySelector(‘[data-candidatos]’)) {
wrap.innerHTML = ”;
_blocoMounted = false;
_prefetching = false;
}
mountBloco();
}
function regenerate() {
var d = dispAtual();
state.dispId = d.id;
dados = gerarDisputa(state.turno, d);
state.page = 1;
renderBloco();
if (embedDoTurno(state.turno)) return; // iframe do TSE: sem progresso simulado
if (sectionVisible() || FREEZE) animarProgresso();
else { state.progress = 0; paint(); } // espera ficar visível p/ a subida aparecer
}
/* tabs de turno */
function renderTurnos() {
refs.turnos.innerHTML = ”;
[1, 2].forEach(function (t) {
var b = elt(‘button’, ‘apu__turno’);
b.type=”button”;
b.setAttribute(‘role’, ‘tab’);
b.setAttribute(‘aria-selected’, state.turno === t ? ‘true’ : ‘false’);
b.textContent = t + ‘º Turno’;
b.addEventListener(‘click’, function () {
if (state.turno === t) return;
state.turno = t;
// ajusta disputa se o turno 2 não tem o cargo atual
if (!disputasDoTurno().some(function (d) { return d.id === state.dispId; })) state.dispId = disputasDoTurno()[0].id;
state.busca=””; if (refs.busca) refs.busca.value=””;
regenerate();
});
refs.turnos.appendChild(b);
});
}
/* seletores de cargo e estado (dropdowns, padrão do bloco de Candidatos) */
var ESTADOS_APU = [‘Espírito Santo, ES’, ‘São Paulo, SP’, ‘Rio de Janeiro, RJ’, ‘Minas Gerais, MG’, ‘Bahia, BA’];
var CHEV = ‘‘;
var CHECK = ‘‘;
function buildSelect(hostEl, value, options, onPick) {
hostEl.innerHTML =
‘‘ +
”;
var btn = hostEl.querySelector(‘.apu-sel__btn’);
var menu = hostEl.querySelector(‘.apu-sel__menu’);
btn.addEventListener(‘click’, function (e) {
e.stopPropagation();
var open = !menu.hidden;
host.querySelectorAll(‘.apu-sel__menu’).forEach(function (m) { m.hidden = true; });
host.querySelectorAll(‘.apu-sel__btn’).forEach(function (b) { b.classList.remove(‘is-open’); });
menu.hidden = open; btn.classList.toggle(‘is-open’, !open);
});
menu.addEventListener(‘click’, function (e) {
var opt = e.target.closest(‘.apu-sel__opt’); if (!opt) return;
menu.hidden = true; btn.classList.remove(‘is-open’);
onPick(opt.dataset.value);
});
}
function renderFiltros() {
var disp = dispAtual();
buildSelect(refs.selCargo, disp.label, disputasDoTurno().map(function (d) { return d.label; }), function (label) {
var alvo = disputasDoTurno().filter(function (d) { return d.label === label; })[0];
if (!alvo || alvo.id === state.dispId) return;
state.dispId = alvo.id; state.busca=””; if (refs.busca) refs.busca.value=””;
regenerate();
});
// estado: cosmético no mock (apuração já é por disputa); presidente esconde estado
if (state.dispId === ‘presidente’) { refs.selEstado.innerHTML = ”; refs.selEstado.hidden = true; }
else {
refs.selEstado.hidden = false;
buildSelect(refs.selEstado, state.estado || ESTADOS_APU[0], ESTADOS_APU, function (uf) {
state.estado = uf; refs.selEstado.querySelector(‘.apu-sel__v’).textContent = uf;
});
}
}
/* lista filtrada + paginada */
function filtrados() {
var q = state.busca.trim().toLowerCase();
if (!q) return dados.candidatos;
return dados.candidatos.filter(function (c) {
return c.nome.toLowerCase().indexOf(q) >= 0 || c.sigla.toLowerCase().indexOf(q) >= 0 || String(c.numero).indexOf(q) >= 0;
});
}
function renderList() {
var arr = filtrados();
var maxPct = dados.candidatos[0] ? dados.candidatos[0].pct : 1;
refs.list.innerHTML = ”;
if (!arr.length) {
refs.list.appendChild(elt(‘div’, ‘apu__empty’, ‘Nenhum candidato encontrado para esta busca.’));
renderPager(0);
return;
}
var totalPages = Math.max(1, Math.ceil(arr.length / PER_PAGE));
if (state.page > totalPages) state.page = totalPages;
var slice = arr.slice((state.page – 1) * PER_PAGE, state.page * PER_PAGE);
/* carimbo de atualização */
function updateCarimbo() {
var agora = new Date();
var hh = String(agora.getHours()).padStart(2, ‘0’);
var mm = String(agora.getMinutes()).padStart(2, ‘0’);
var ss = String(agora.getSeconds()).padStart(2, ‘0’);
var tpl = comum.atualizadoEm || ‘Atualizado em {data}’;
refs.atualizado.textContent = tpl.replace(‘{data}’, hh + ‘:’ + mm + ‘:’ + ss + ‘h’);
}
/* animação: progress 0→1 (urnas, %, votos, barras crescem juntos) */
var rafId = null;
function animarProgresso() {
if (rafId) cancelAnimationFrame(rafId);
var dur = REDUCED || FREEZE ? 0 : 2400;
var t0 = null;
function frame(ts) {
if (t0 == null) t0 = ts;
var k = dur ? Math.min(1, (ts – t0) / dur) : 1;
var eased = 1 – Math.pow(1 – k, 3); // ease-out cubic
state.progress = eased;
paint();
if (k < 1) rafId = requestAnimationFrame(frame);
}
if (dur) rafId = requestAnimationFrame(frame); else { state.progress = 1; paint(); }
}
function paint() {
var p = state.progress;
refs.urnasPct.textContent = fmtPct(dados.urnasTarget * p);
refs.progress.style.width = (dados.urnasTarget * p) + ‘%’;
if (refs.urnasLabel) {
var apuradas = Math.round(dados.totalSecoes * (dados.urnasTarget / 100) * p);
refs.urnasLabel.textContent = fmt(apuradas) + ‘ de ‘ + fmt(dados.totalSecoes) + ‘ seções apuradas’;
}
}
/* busca */
if (refs.busca) {
refs.busca.addEventListener(‘input’, function () {
state.busca = refs.busca.value; state.page = 1; renderList(); paint();
}); }
// carimbo vivo (a cada 30s, como um boletim) — só quando visível
updateCarimbo();
if (!FREEZE) { host.__carimbo = setInterval(updateCarimbo, 30000); }
// dispara a subida (urnas/%/barras) quando a seção fica visível na troca de
// fase; sincroniza o turno default com a fase (apuracao1→1º, apuracao2→2º).
var _wasVisible = false;
function onFase(e) {
var vis = e && e.detail && e.detail.apuracao;
if (!vis) { _wasVisible = false; return; }
var want = (e.detail.fase === ‘apuracao2’) ? 2 : 1;
var turnoMudou = state.turno !== want;
if (turnoMudou) {
state.turno = want;
if (!disputasDoTurno().some(function (d) { return d.id === state.dispId; })) state.dispId = disputasDoTurno()[0].id;
}
if (!_wasVisible || turnoMudou) {
state.busca=””; if (refs.busca) refs.busca.value=””;
regenerate(); // visível agora → anima a subida
} else {
updateCarimbo();
}
_wasVisible = true;
}
document.addEventListener(‘lp:fase’, onFase);
regenerate();
if (sectionVisible()) _wasVisible = true; // entrou direto numa janela
}
/* —- registro ———————————————————– */
function register() {
if (!window.LP) { document.addEventListener(‘DOMContentLoaded’, register); return; }
LP.registerSection(‘apuracao’, mount);
}
register();
})();
/* ======================================================================== */
/* >>> CONSOLIDADO de sessoes/aovivo.js */
/* ============================================================================
Sessão 2 — AO VIVO · sessoes/aovivo.js
Player de transmissão (autoplay mudo ao entrar na viewport) + mini-player
flutuante (PiP em CSS). O MESMO iframe é reposicionado (nunca recriado).
Máquina de estados: ancorado ↔ flutuante ↔ fechado. Mini só no desktop.
Fonte vem do GET /config do addon (CONFIG.aoVivo): youtube → embed mudo.
========================================================================== */
(function () {
‘use strict’;
var FREEZE = /[?&]freezeb/.test(location.search);
var mqDesktop = window.matchMedia(‘(min-width: 769px)’);
function elt(tag, cls, html) { var e = document.createElement(tag); if (cls) e.className = cls; if (html != null) e.innerHTML = html; return e; }
var IC = {
mute: ‘‘,
volume: ‘‘,
fechar: ‘‘,
voltar: ‘‘
};
// extrai o id do YouTube de uma URL completa (watch, youtu.be, embed, shorts)
function ytId(url) {
if (!url) return ”;
var m = String(url).match(/(?:v=|/embed/|youtu.be/|/shorts/)([A-Za-z0-9_-]{11})/);
return m ? m[1] : ”;
}
function buildSrc(av) {
// GET /config real: sourceType ‘youtube’ -> av.url (URL completa; a LP extrai o id).
// Mock/legado: av.youtubeId. Suporta os dois.
if (av.sourceType === ‘youtube’) {
var id = av.youtubeId || ytId(av.url);
if (!id) return ”;
return ‘https://www.youtube.com/embed/’ + id +
‘?autoplay=1&mute=1&playsinline=1&controls=1&rel=0&modestbranding=1&enablejsapi=1&loop=1&playlist=” + id +
“&origin=’ + encodeURIComponent(location.origin);
}
return ”;
}
// markup do embed direto (sourceType ‘embed’): GET /config -> av.embedHtml (iframe
// já reconstruído pelo addon, sem width/height); mock/legado -> av.embed.
function embedHtml(av) { return av.embedHtml || av.embed || ”; }
function mount(host, LP) {
var av = LP.config.aoVivo || {};
var habilitado = LP.config.flags.aoVivoHabilitado;
var src = buildSrc(av);
// GET /config chegou depois do boot (portal): re-monta com as flags reais.
// O switch do addon desligado DERRUBA a seção mesmo com o mock ligado.
if (!host._avCfgBound) {
host._avCfgBound = true;
document.addEventListener(‘lp:config’, function () { mount(host, LP); });
}
// Re-mount: nunca duplicar o PiP no body.
var pipVelho = document.body.querySelector(‘.av__pip’);
if (pipVelho) pipVelho.remove();
// oculta por padrão: sem feature ou sem fonte → não renderiza (some do menu)
if (!habilitado || (!src && !embedHtml(av))) { host.classList.remove(‘is-on’); host.innerHTML = ”; return; }
host.classList.add(‘is-on’);
var comum = LP.getContent().global.comum || {};
host.innerHTML =
‘
‘ +
‘
‘ +
‘TRANSMISSÃO AO VIVO‘ +
‘
‘ + (av.titulo || ”) + ‘
‘ +
‘
‘ + (av.descricao || ”) + ‘
‘ +
‘
‘ +
‘
‘ +
‘
‘ + (comum.loading || ‘Carregando…’) + ‘
‘ +
‘
‘ + (comum.erro || ”) + ‘
‘ +
‘
‘ +
‘AO VIVO‘ +
‘
Você está assistindo no mini player.
‘ +
‘‘ +
‘
‘ +
‘
‘ +
‘
‘ +
// PiP: fixo, fora do fluxo do .av para poder flutuar
‘
‘ +
‘Ao vivo‘ +
‘
‘ +
‘‘ +
‘‘ +
‘‘ +
‘‘ +
‘
‘ +
” +
‘
‘;
var frame = host.querySelector(‘[data-frame]’);
var pip = host.querySelector(‘[data-pip]’);
var mountSlot = host.querySelector(‘[data-mount]’);
var btnSom = host.querySelector(‘[data-som]’);
// PiP precisa flutuar acima de TODAS as seções: tiramos do contexto de
// empilhamento da própria seção (que tem isolation) e montamos no fim da página.
document.body.appendChild(pip);
// o iframe vive dentro do PiP (data-mount); ancorado, o PiP cobre o frame.
var iframe = null;
var srcSet = false;
var muted = true;
var activated = false;
var animating = false;
var state=”ancorado”;
// Em produção a LP é injetada na página do portal (window.top === self): autoplay
// mudo ao entrar na viewport. No preview ela roda dentro de um iframe
// (self !== top), onde o YouTube bloqueia o embed (Erro 153 por referrer):
// aí mostramos a facade (thumbnail + play) e o vídeo carrega no clique.
var autoAllowed = (window.self === window.top);
var thumb = (av.youtubeId || ytId(av.url)) ? ‘https://i.ytimg.com/vi/’ + (av.youtubeId || ytId(av.url)) + ‘/maxresdefault.jpg’ : ”;
function setState(s) { state = s; host.setAttribute(‘data-av-state’, s); }
setState(‘ancorado’);
function showFacade() {
mountSlot.innerHTML =
‘‘;
var b = mountSlot.querySelector(‘[data-play]’);
if (b) b.addEventListener(‘click’, function () { ensureSrc(); });
frame.classList.remove(‘is-loading’);
}
function ensureSrc() {
if (srcSet) return;
srcSet = true;
frame.classList.add(‘is-loading’);
if (av.sourceType !== ‘youtube’ && embedHtml(av)) {
mountSlot.innerHTML = embedHtml(av); // HTML colado já sanitizado/reconstruído pelo addon
iframe = mountSlot.querySelector(‘iframe’);
} else {
mountSlot.innerHTML = ”;
iframe = elt(‘iframe’);
iframe.setAttribute(‘allow’, ‘autoplay; encrypted-media; picture-in-picture’);
iframe.setAttribute(‘allowfullscreen’, ”);
iframe.setAttribute(‘title’, av.titulo || ‘Transmissão ao vivo’);
iframe.src = buildSrc(av) || src; // fonte recomputada (o /config pode ter chegado depois)
mountSlot.appendChild(iframe);
}
// best-effort: limpa o loading no load (ou por timeout). Sem overlay de erro:
// o próprio player de origem mostra eventual indisponibilidade.
if (iframe) iframe.addEventListener(‘load’, function () { frame.classList.remove(‘is-loading’); });
setTimeout(function () { frame.classList.remove(‘is-loading’); }, 5000);
}
/* — geometria do PiP — */
function rectFrame() { return frame.getBoundingClientRect(); }
// Ancorado, o PiP é position:absolute no body: coordenadas do DOCUMENTO.
// Rola nativamente com a página (sem sync por frame = sem “samba”).
function rectFrameDoc() {
var r = rectFrame();
return { top: r.top + (window.pageYOffset || 0), left: r.left + (window.pageXOffset || 0), width: r.width, height: r.height };
}
function setPip(r) {
pip.style.top = r.top + ‘px’; pip.style.left = r.left + ‘px’;
pip.style.width = r.width + ‘px’; pip.style.height = r.height + ‘px’;
}
function cornerRect() {
var w = Math.max(280, Math.min(380, window.innerWidth * 0.28));
var h = w * 9 / 16;
var m = 24;
return { top: window.innerHeight – h – m, left: window.innerWidth – w – m, width: w, height: h };
}
function syncAnchored() {
if (state === ‘ancorado’ && !animating) setPip(rectFrameDoc());
}
/* transição entre ancorado e flutuante = FADE (sem slide). O alvo é computado
DENTRO do timeout (a página pode ter rolado durante o fade). */
function fadeTo(floating) {
animating = true;
pip.style.opacity = ‘0’;
setTimeout(function () {
pip.classList.toggle(‘is-floating’, floating);
setPip(floating ? cornerRect() : rectFrameDoc());
pip.style.opacity = ‘1’;
animating = false;
}, 200);
}
function goAnchored() {
if (state === ‘ancorado’) return;
setState(‘ancorado’);
fadeTo(false);
}
function goFloating() {
if (state === ‘flutuante’ || !mqDesktop.matches) return;
setState(‘flutuante’);
fadeTo(true);
}
function goFechado() {
setState(‘fechado’);
pip.classList.remove(‘is-floating’);
pip.style.display = ‘none’;
}
/* — entra/sai do quadro principal (chamado por IO e por scroll/resize) — */
function visibleRatio() {
var r = frame.getBoundingClientRect();
var vh = window.innerHeight || document.documentElement.clientHeight;
var vis = Math.max(0, Math.min(r.bottom, vh) – Math.max(r.top, 0));
return r.height ? vis / r.height : 0;
}
function enterMain() {
activated = true;
pip.style.display = ”;
if (autoAllowed) ensureSrc(); // produção: autoplay mudo ao entrar na viewport
if (state !== ‘ancorado’) goAnchored(); // reentrou: reseta inclusive o “fechado”
}
function leaveMain() {
if (activated && mqDesktop.matches && state !== ‘fechado’) goFloating();
}
function evaluate() {
var ratio = visibleRatio();
// re-ancora quando o vídeo está majoritariamente visível; flutua quando passa de ~50% omitido
if (ratio >= 0.6) enterMain();
else if (ratio < 0.5) leaveMain();
}
/* — observador do quadro principal (primário; scroll é o fallback) — */
var io = (‘IntersectionObserver’ in window) ? new IntersectionObserver(function () {
evaluate();
}, { threshold: [0, 0.25, 0.45, 0.5, 0.55, 0.6, 0.75, 1] }) : null;
if (io) io.observe(frame);
// fallback universal (e robustez): reavalia no scroll/resize, throttle por rAF
var pending = false;
function onScroll() { if (pending) return; pending = true; requestAnimationFrame(function () { pending = false; evaluate(); }); }
window.addEventListener(‘scroll’, onScroll, { passive: true });
window.addEventListener(‘resize’, onScroll);
// loop leve: acompanha MUDANÇAS DE LAYOUT (reveal, resize). Ancorado escreve
// coordenadas de documento (estáveis durante scroll puro: nada a repintar).
(function tick() {
if (!animating) { if (state === ‘ancorado’) setPip(rectFrameDoc()); else if (state === ‘flutuante’) setPip(cornerRect()); }
requestAnimationFrame(tick);
})();
// estado inicial: facade (thumbnail + play) até o autoplay/clique
showFacade();
setPip(rectFrameDoc());
evaluate();
/* — controles — */
// “voltar” existe no poster (host) e na barra do PiP (agora no body)
[].slice.call(host.querySelectorAll(‘[data-voltar]’))
.concat([].slice.call(pip.querySelectorAll(‘[data-voltar]’)))
.forEach(function (b) {
b.addEventListener(‘click’, function () {
var sec = document.getElementById(‘ao-vivo’) || host;
var alvo = sec.querySelector(‘.av__selo, .av__head’) || sec;
var y = alvo.getBoundingClientRect().top + (window.pageYOffset || 0) – 16;
try { window.scrollTo({ top: y, behavior: ‘smooth’ }); } catch (e) { window.scrollTo(0, y); }
});
});
var btnFechar = pip.querySelector(‘[data-fechar]’);
if (btnFechar) btnFechar.addEventListener(‘click’, goFechado);
// some o mini ao virar mobile
mqDesktop.addEventListener(‘change’, function () { if (!mqDesktop.matches && state === ‘flutuante’) goAnchored(); });
}
function register() {
if (!window.LP) { document.addEventListener(‘DOMContentLoaded’, register); return; }
LP.registerSection(‘ao-vivo’, mount);
}
register();
})();
/* ======================================================================== */
/* >>> CONSOLIDADO de sessoes/noticias.js */
/* ============================================================================
Sessão 3 — NOTÍCIAS DA COBERTURA · sessoes/noticias.js
A LP só delimita o slot; em produção, o componente related posts da plataforma
injeta os
no servidor. Este módulo apenas
preenche um MOCK no slot quando a seção está marcada com [data-mock] (demo),
usando exatamente o MESMO markup do componente (seox-related*), para que o CSS
e o layout reflitam o real. Em produção o mock não roda (sem data-mock).
========================================================================== */
(function () {
‘use strict’;
// chapéus + manchetes genéricos (placeholder; sem fatos/datas reais)
var ITENS = [
{ cat: ‘Cobertura’, titulo: ‘Acompanhe a cobertura completa das Eleições 2026 feita pela nossa redação’ },
{ cat: ‘Campanha’, titulo: ‘Candidatos intensificam a agenda na reta final do primeiro turno’ },
{ cat: ‘Pesquisas’, titulo: ‘O que dizem os últimos levantamentos de intenção de voto’ },
{ cat: ‘Debates’, titulo: ‘Veja onde assistir aos próximos debates entre os candidatos’ },
{ cat: ‘Guia’, titulo: ‘Documentos, horário e regras: como votar sem complicação’ },
{ cat: ‘Calendário’, titulo: ‘As datas que todo eleitor precisa marcar até a apuração’ },
{ cat: ‘Análise’, titulo: ‘Os temas que devem definir a disputa neste ano’ },
{ cat: ‘Bastidores’, titulo: ‘Como as campanhas se preparam para a noite da apuração’ },
{ cat: ‘Serviço’, titulo: ‘Justiça Eleitoral abre canais de atendimento ao eleitor’ }
];
// imagens reais de placeholder (mesmo bucket do projeto; mock de demonstração)
var IMGS = [
‘https://uploads.seox.com.br/2026/05/hero-32bebc49-cd99-4c22-b604-14136c16b289-750×449.webp’,
‘https://uploads.seox.com.br/2026/05/hero-46506b2a-be2c-42cc-8423-00ac51b43385-750×422.jpeg’,
‘https://uploads.seox.com.br/2026/05/hero-34d34173-40a0-4a1e-a0a9-24501fd26bb8-750×422.jpeg’,
‘https://uploads.seox.com.br/2026/03/seox-cover-1774536952015-750×422.webp’
];
// fallback se a imagem não carregar (gradiente da identidade)
var GRADS = [
‘linear-gradient(135deg,#30B3D3,#10586B)’, ‘linear-gradient(135deg,#10586B,#164324)’,
‘linear-gradient(135deg,#31B75B,#164324)’, ‘linear-gradient(135deg,#2E6FAE,#16335d)’,
‘linear-gradient(135deg,#4E6B9E,#10586B)’, ‘linear-gradient(135deg,#C99700,#8a6d00)’,
‘linear-gradient(135deg,#2E8B57,#164324)’, ‘linear-gradient(135deg,#30B3D3,#2E8B57)’,
‘linear-gradient(135deg,#10586B,#30B3D3)’
];
function item(it, i) {
var src = IMGS[i % IMGS.length];
var grad = GRADS[i % GRADS.length];
// thumb = (mesmo do componente real); fallback p/ gradiente se falhar
return ‘
function fillMock(ul) {
ul.innerHTML = ITENS.map(item).join(”);
}
function mount(host) {
if (!host.hasAttribute(‘data-mock’)) return;
var ul = host.querySelector(‘ul.seox-related[data-block=”related-posts”]’);
if (ul && !ul.firstElementChild) fillMock(ul);
}
function register() {
if (!window.LP) { document.addEventListener(‘DOMContentLoaded’, register); return; }
LP.registerSection(‘noticias’, mount);
}
register();
})();
/* ======================================================================== */
/* >>> CONSOLIDADO de sessoes/debates.js */
/* ============================================================================
Sessão 6 — DEBATES · sessoes/debates.js
Calendário único de debates (Presidente nacionais + Governador da UF). Lê o
calendario.json (eventos type=debate), filtra por UF, ordena por data, marca
o próximo e renderiza em 3 formatos (lista/grade/timeline) conforme
[data-debates-fmt]. Copies/labels do content.json (debates.*). Resiliente:
loading, vazio (geral/filtro) e erro isolados por seção.
========================================================================== */
(function () {
‘use strict’;
var UFS = [
[‘AC’,’Acre’],[‘AL’,’Alagoas’],[‘AP’,’Amapá’],[‘AM’,’Amazonas’],[‘BA’,’Bahia’],
[‘CE’,’Ceará’],[‘DF’,’Distrito Federal’],[‘ES’,’Espírito Santo’],[‘GO’,’Goiás’],
[‘MA’,’Maranhão’],[‘MT’,’Mato Grosso’],[‘MS’,’Mato Grosso do Sul’],[‘MG’,’Minas Gerais’],
[‘PA’,’Pará’],[‘PB’,’Paraíba’],[‘PR’,’Paraná’],[‘PE’,’Pernambuco’],[‘PI’,’Piauí’],
[‘RJ’,’Rio de Janeiro’],[‘RN’,’Rio Grande do Norte’],[‘RS’,’Rio Grande do Sul’],
[‘RO’,’Rondônia’],[‘RR’,’Roraima’],[‘SC’,’Santa Catarina’],[‘SP’,’São Paulo’],
[‘SE’,’Sergipe’],[‘TO’,’Tocantins’]
];
var MESES = [‘jan’,’fev’,’mar’,’abr’,’mai’,’jun’,’jul’,’ago’,’set’,’out’,’nov’,’dez’];
var WD = [‘dom’,’seg’,’ter’,’qua’,’qui’,’sex’,’sáb’];
var REDES = { band: ‘Band’, globo: ‘Globo’, record: ‘Record’, sbt: ‘SBT’ };
// Fallback embutido dos debates (mesma filosofia do content.json): o
// calendario.json sofre com o cache do Cloudflare e pode não propagar. Cópia
// dos eventos type=debate da v2; re-sincronizar a cada publish.
var DEBATES_FALLBACK_VERSION = 2;
var DEBATES_FALLBACK = [
{ id: ‘E101’, type: ‘debate’, date: ‘2026-08-16′, time: ’20:00’, title: ‘Debate presidencial’, description: ‘Debate entre os candidatos à Presidência. Data anunciada pela emissora, sujeita a confirmação.’, cargo: ‘presidente’, uf: null, broadcaster: ‘Band’, broadcasterId: ‘band’, link: null, status: ‘agendado’ },
{ id: ‘E102’, type: ‘debate’, date: ‘2026-09-14’, time: null, title: ‘Debate presidencial’, description: ‘Debate entre os candidatos à Presidência, organizado por um consórcio de veículos. Data anunciada pela organização, sujeita a confirmação.’, cargo: ‘presidente’, uf: null, broadcaster: ‘Consórcio A Hora da Decisão’, broadcasterId: ‘sbt’, link: null, status: ‘agendado’ },
{ id: ‘E103’, type: ‘debate’, date: ‘2026-09-27′, time: ’21:00’, title: ‘Debate presidencial’, description: ‘Debate entre os candidatos à Presidência. Data anunciada pela emissora, sujeita a confirmação.’, cargo: ‘presidente’, uf: null, broadcaster: ‘Record’, broadcasterId: ‘record’, link: null, status: ‘agendado’ },
{ id: ‘E104’, type: ‘debate’, date: ‘2026-09-29’, time: null, title: ‘Debate de governo do estado’, description: ‘Debate entre os candidatos ao governo do estado. Data anunciada pela emissora, sujeita a confirmação.’, cargo: ‘governador’, uf: ‘RS’, broadcaster: ‘RBS TV’, broadcasterId: ‘globo’, link: null, status: ‘agendado’ },
{ id: ‘E105’, type: ‘debate’, date: ‘2026-09-22’, time: null, title: ‘Debate de governo do estado’, description: ‘Debate entre os candidatos ao governo do estado, transmitido ao vivo pela rádio.’, cargo: ‘governador’, uf: ‘ES’, broadcaster: ‘Rádio (nome do veículo)’, broadcasterId: null, link: null, status: ‘agendado’ }
];
function elt(tag, cls, html) { var e = document.createElement(tag); if (cls) e.className = cls; if (html != null) e.innerHTML = html; return e; }
function parseDate(s) { var p = String(s).split(‘-‘); return new Date(+p[0], +p[1] – 1, +p[2]); }
function esc(s) { return String(s == null ? ” : s).replace(/[&<>”]/g, function (c) { return ({ ‘&’: ‘&’, ‘<‘: ‘<‘, ‘>’: ‘>’, ‘”‘: ‘”‘ })[c]; }); }
function logoSrc(id) { return id && REDES[id] ? window.LP.assetUrl(‘assets/logos-emissoras/’ + id + ‘-logo.svg’) : ”; }
function mount(host, LP) {
var cp = (LP.getContent().debates) || {};
var comum = LP.getContent().global.comum || {};
var fmt = host.getAttribute(‘data-debates-fmt’) || ‘lista’;
var ufInicial = (LP.config.portal.uf || ‘ES’).toUpperCase();
// permite o toggle de formato (barra de demo) trocar lista/grade/timeline
host.__setFmt = function (novo) { fmt = novo; host.setAttribute(‘data-debates-fmt’, novo); render(); };
// estrutura base
host.innerHTML =
‘
‘ +
‘
‘ +
‘
‘ +
‘
‘ + esc(cp.titulo || ‘Debates’) + ‘
‘ +
‘
‘ + esc(cp.descricao || ‘Quando e onde assistir aos debates das Eleições 2026, para presidente e para o governo do seu estado.’) + ‘
‘ +
‘
‘ +
‘
‘ +
‘‘ +
” +
‘
‘ +
‘
‘ +
” +
” +
‘
‘;
var sel = host.querySelector(‘[data-uf]’);
UFS.forEach(function (u) {
var o = elt(‘option’); o.value = u[0]; o.textContent = u[0] + ‘ · ‘ + u[1];
if (u[0] === ufInicial) o.selected = true;
sel.appendChild(o);
});
var itemsEl = host.querySelector(‘[data-items]’);
var pagerEl = host.querySelector(‘[data-pager]’);
var PAGE = 6; // itens por página (evita lista muito vertical)
var state = { uf: ufInicial, events: null, loading: true, erro: false, page: 1 };
// O GET /config chega DEPOIS do boot: quando o defaultUf do portal aparecer,
// o seletor acompanha. Se o visitante ja escolheu um estado na mao, respeita.
if (!host._debCfgBound) {
host._debCfgBound = true;
document.addEventListener(‘lp:config’, function () {
var uf = (LP.config.portal.uf || ”).toUpperCase();
if (!uf || state.ufManual || uf === state.uf) return;
state.uf = uf; state.page = 1;
if (sel.querySelector(‘option[value=”‘ + uf + ‘”]’)) sel.value = uf;
render();
});
}
// loading inicial
renderLoading();
// dados: o LP entrega content via SWR; o calendario é fetch próprio com fallback
carregar();
function carregar() {
// demo: permite injetar exemplos de estados (realizado/cancelado)
var extra = (window.DEBATES_DEMO_STATES ? demoExtras() : []);
// entrega já com o fallback embutido (página nunca espera / nunca vazia)
state.events = DEBATES_FALLBACK.concat(extra);
state.loading = false; render();
// revalida em segundo plano (SWR): adota a rede se vier
LP.fetchJSON(LP.cdnBase + ‘data/calendario.json’, { timeout: 6000 })
.then(function (data) {
// Mesma regra do content.json: só adota a rede se a versão for >= a do
// fallback (o bucket ainda serve a v1, sem o caso rádio).
var v = (data && data.meta && data.meta.version) || 1;
if (v < DEBATES_FALLBACK_VERSION) return;
var live = (data.events || []).filter(function (e) { return e.type === ‘debate’; });
if (live.length) { state.events = live.concat(extra); render(); }
})
.catch(function () { /* mantém o fallback — silencioso por design */ });
}
function filtrados() {
var arr = (state.events || []).filter(function (e) {
if (e.cargo === ‘presidente’) return true; // nacionais sempre
if (e.cargo === ‘governador’) return e.uf === state.uf;
return false;
});
arr.sort(function (a, b) { return parseDate(a.date) – parseDate(b.date); });
return arr;
}
function proximoId(arr) {
var now = Date.now();
var cand = arr.filter(function (e) { return e.status === ‘agendado’ && parseDate(e.date).getTime() >= now; });
return cand.length ? cand[0].id : null;
}
function render() {
if (state.loading) return renderLoading();
if (state.erro) { pagerEl.innerHTML = ”; return renderEstado(comum.erro || ‘Não foi possível carregar agora.’, true); }
var arr = filtrados();
if (!(state.events && state.events.length)) { pagerEl.innerHTML = ”; return renderEstado(cp.emptyState || ‘Agenda em breve.’); }
if (!arr.length) { pagerEl.innerHTML = ”; return renderEstado(cp.emptyStateFiltro || ‘Nada para esta seleção.’); }
var prox = proximoId(arr);
var totalPages = Math.ceil(arr.length / PAGE);
if (state.page > totalPages) state.page = totalPages;
var slice = arr.slice((state.page – 1) * PAGE, state.page * PAGE);
itemsEl.innerHTML = slice.map(function (e) { return cardHTML(e, e.id === prox); }).join(”);
renderPager(totalPages);
}
function renderPager(totalPages) {
if (totalPages <= 1) { pagerEl.innerHTML = ”; return; }
var html=”‘;
for (var p = 1; p <= totalPages; p++) html += ‘‘;
html += ‘‘;
pagerEl.innerHTML = html;
LP.$all(‘[data-pg]’, pagerEl).forEach(function (b) {
b.addEventListener(‘click’, function () {
var n = +b.getAttribute(‘data-pg’);
if (n >= 1 && n <= totalPages) { state.page = n; render(); itemsEl.scrollIntoView ? null : null; }
});
});
}
function renderLoading() {
pagerEl.innerHTML = ”;
itemsEl.innerHTML = ”;
}
function renderEstado(msg, erro) {
itemsEl.innerHTML = ”;
}
function linkHTML(e) {
if (e.status === ‘cancelado’) return ”;
var realizado = e.status === ‘realizado’;
var label = realizado ? (cp.linkGravacao || ‘Ver como foi’) : ‘Onde:’;
var src = logoSrc(e.broadcasterId);
// Nome: rede conhecida pelo id, senão o campo livre (rádios e outros veículos).
var nome = REDES[e.broadcasterId] || e.broadcaster || ”;
if (!src && !nome) return ”; // sem veículo identificado → sem campo
// Sem logo (rádio ou veículo fora das 4 redes): só o nome, padding simétrico.
var logo = src ? ‘‘ : ”;
var nomeEl = nome ? ‘‘ + esc(nome) + ‘‘ : ”;
return ‘
‘ +
‘‘ + logo + nomeEl + ‘‘ +
‘
‘;
}
/* — templates por formato — */
function cardHTML(e, isProx) {
var attrs=” data-status=”” + esc(e.status) + ‘”‘ + (isProx ? ” : ”);
var cls=”deb-card” + (isProx ? ‘ is-proximo’ : ”);
var dp = dateParts(e);
var dateBlock = ‘‘ + dp.d + ‘‘ + dp.m + ‘‘ + dp.wd + ‘‘;
var tags=”
if (fmt === ‘timeline’) {
var d = parseDate(e.date);
var dataTxt = dp.wd + ‘, ‘ + dp.d + ‘ ‘ + dp.m + (e.time ? ‘ · ‘ + e.time.replace(‘:’, ‘h’) : ”);
return ‘
‘ +
” +
‘
‘ +
‘
‘ + dataTxt + ‘‘ + proxBadge(isProx) + ‘
‘ +
‘
‘ + cargoTag(e) + statusExtra(e) + ‘
‘ +
titulo + desc +
‘
‘ + linkHTML(e) + ‘
‘ +
‘
‘ +
‘
‘;
}
// lista (default): título com a pílula “Próxima data” à direita; tipo/horário abaixo
var titleRow = ‘
‘ + titulo + proxBadge(isProx) + ‘
‘;
var metaTags=”
” + cargoTag(e) + timeTxt(e) + statusExtra(e) + ‘
‘;
return ‘
‘ +
‘
‘ + dateBlock + ‘
‘ +
‘
‘ + titleRow + metaTags + desc + ‘
‘ +
‘
‘ + linkHTML(e) + ‘
‘ +
‘
‘;
}
}
// exemplos só de demo p/ ilustrar realizado/cancelado (não vão pro real)
function demoExtras() {
return [
{ id: ‘D-REAL’, type: ‘debate’, date: ‘2026-08-02′, time: ’21:00’, title: ‘Debate presidencial’,
description: ‘Debate entre os candidatos à Presidência, já realizado.’,
cargo: ‘presidente’, uf: null, broadcaster: ‘Globo’, broadcasterId: ‘globo’, link: ‘https://example.com’, status: ‘realizado’ },
{ id: ‘D-CANC’, type: ‘debate’, date: ‘2026-08-09’, time: null, title: ‘Debate de governo do estado’,
description: ‘Debate cancelado pela organização.’,
cargo: ‘governador’, uf: ‘ES’, broadcaster: ‘SBT’, broadcasterId: ‘sbt’, link: null, status: ‘cancelado’ }
];
}
function register() {
if (!window.LP) { document.addEventListener(‘DOMContentLoaded’, register); return; }
// registra cada instância .debates por seu id (1 na LP; 3 na comparação)
LP.$all(‘.debates[id]’).forEach(function (el) { LP.registerSection(el.id, mount); });
}
register();
})();
/* ======================================================================== */
/* >>> CONSOLIDADO de sessoes/faq.js */
/* ============================================================================
Sessão 7 — FAQ / GUIA DO ELEITOR · acordeão (lista única, máx. 7) com SEO
schema.org/FAQPage (microdata, reflete o conteúdo renderizado). Conteúdo vem
do faq.json (SWR + fallback embutido, igual ao content.json). 1ª aberta;
múltiplas podem abrir. Resiliente: loading/vazio/erro isolados.
========================================================================== */
(function () {
‘use strict’;
// Fallback embutido (faq.json v1) — o bucket sofre com o cache do Cloudflare.
// Re-sincronizar a cada publish. Ordem do array = ordem de exibição.
var FAQ_FALLBACK = {
meta: { version: 1 },
items: [
{ id: ‘F01’, question: ‘Quando são as Eleições 2026 e quais cargos estão em disputa?’, answer: ‘O 1º turno será em 4 de outubro e o 2º turno, se houver, em 25 de outubro. Você vai eleger Presidente, Governador, dois Senadores, Deputado Federal e Deputado Estadual (ou Distrital, no DF).’ },
{ id: ‘F02’, question: ‘Qual é a ordem de votação na urna em 2026?’, answer: ‘A ordem é: Deputado Federal, Deputado Estadual, Senador (1ª vaga), Senador (2ª vaga), Governador e, por último, Presidente. São seis votos no total. Vale levar uma cola com os seus números.’ },
{ id: ‘F03’, question: ‘Por que em 2026 eu voto duas vezes para senador?’, answer: ‘Porque esta eleição renova dois terços do Senado: cada estado elege dois senadores. Escolha dois candidatos diferentes, pois se o mesmo número for digitado nas duas vezes a urna anula o segundo voto.’ },
{ id: ‘F04’, question: ‘Como funciona o voto para deputado?’, answer: ‘Deputados são eleitos pelo sistema proporcional: os votos somam para o partido, que conquista um número de cadeiras, ocupadas pelos seus candidatos mais votados. Por isso, votar em um candidato também ajuda a eleger outros da mesma legenda. Dá para votar só na legenda, digitando apenas o número do partido.’ },
{ id: ‘F05’, question: ‘O que preciso levar para votar?’, answer: ‘Um documento oficial com foto: RG, CNH, passaporte, carteira de trabalho ou o e-Título com fotografia, válido para quem já fez a biometria. O título físico não é obrigatório e documentos com foto vencidos são aceitos. Celular não entra na cabine de votação.’ },
{ id: ‘F06’, question: ‘Qual é o horário de votação e como descubro meu local de voto?’, answer: ‘A votação vai das 8h às 17h, sempre no horário de Brasília. Quem chega à fila até as 17h recebe senha e tem o direito de votar. Para saber onde votar, consulte o aplicativo e-Título ou o serviço Onde Votar, no site do TSE, com seu nome ou número do título. Locais de votação mudam de uma eleição para outra, então confira antes do dia 4 de outubro.’ },
{ id: ‘F07’, question: ‘Vou estar fora da minha cidade no dia da eleição. Como justifico?’, answer: ‘No próprio dia, justifique pelo aplicativo e-Título ou em qualquer local de votação, sem precisar de comprovante. Depois disso, o prazo é de até 60 dias após cada turno. Cada turno conta como uma eleição separada, então justifique os dois.’ }
]
};
var MAX = 7;
var TSE_URL = ‘https://www.tse.jus.br’;
// Ícones temáticos (Lucide, MIT — biblioteca aberta). Mapeados por id do FAQ;
// animados via CSS no estado aberto. Default genérico se o id não casar.
var ICONS = {
F01: ‘‘,
F02: ‘‘,
F03: ‘‘,
F04: ‘‘,
F05: ‘‘,
F06: ‘‘,
F07: ‘‘
};
var ICON_DEFAULT = ‘‘;
function iconFor(id) {
return ‘‘;
}
function mount(host, LP) {
var cp = (LP.getContent().faq) || {};
var comum = LP.getContent().global.comum || {};
var listEl = host.querySelector(‘[data-faq-list]’);
var footEl = host.querySelector(‘[data-faq-foot]’);
// loading discreto enquanto o faq.json chega
listEl.innerHTML = ”;
// entrega já com fallback embutido; revalida em segundo plano (SWR)
render(FAQ_FALLBACK.items);
LP.fetchJSON(LP.cdnBase + ‘data/faq.json’, { timeout: 6000 })
.then(function (data) { if (data && data.items) render(data.items); })
.catch(function () { /* mantém o fallback — silencioso por design */ });
function render(items) {
items = (items || []).slice(0, MAX);
if (!items.length) {
listEl.innerHTML = ‘
‘ +
esc(cp.emptyState || ‘O guia do eleitor está sendo preparado.’) + ‘
‘;
if (footEl) footEl.innerHTML = ”;
return;
}
var chev = ‘‘;
listEl.innerHTML = items.map(function (it, i) {
var open = i === 0; // 1ª aberta por padrão
var qid = host.id + ‘-q-‘ + it.id;
var aid = host.id + ‘-a-‘ + it.id;
return ‘
‘ +
‘
‘ +
‘‘ +
‘
‘ +
” +
‘
‘;
}).join(”);
// interações: acordeão exclusivo (abrir um fecha os demais)
LP.$all(‘.faq__q’, listEl).forEach(function (btn) {
btn.addEventListener(‘click’, function () {
var item = btn.closest(‘.faq__item’);
var jaAberto = item.classList.contains(‘is-open’);
LP.$all(‘.faq__item’, listEl).forEach(function (it) {
it.classList.remove(‘is-open’);
var b = it.querySelector(‘.faq__q’);
if (b) b.setAttribute(‘aria-expanded’, ‘false’);
});
if (!jaAberto) {
item.classList.add(‘is-open’);
btn.setAttribute(‘aria-expanded’, ‘true’);
}
});
});
// rodapé: CTA reforçado (“Não achou sua dúvida?”) + botão TSE
if (footEl) {
footEl.innerHTML = ”;
}
}
}
function register() {
if (!window.LP) { document.addEventListener(‘DOMContentLoaded’, register); return; }
LP.registerSection(‘faq’, mount);
}
register();
})();
/* ============================================================================
Calendário eleitoral — linha do tempo dos marcos legais (mock dos dados de
calendario.json). Auto-monta [data-calendario]. Estilo da marca (.cal).
============================================================================ */
(function () {
/* Data de referência do calendário.
Produção: usa a data do SERVIDOR (fuso de Brasília), nunca o relógio do dispositivo.
O app.js publica window.LP_SERVER_NOW (ISO) quando disponível; enquanto não houver,
cai no relógio local normalizado para -03:00. Para congelar em demos, defina
window.LP_CAL_HOJE = ‘2026-06-25T12:00:00-03:00’ antes de carregar este arquivo. */
function resolveHoje() {
var W = (typeof window !== ‘undefined’) ? window : {};
var fonte = W.LP_CAL_HOJE || W.LP_SERVER_NOW || null; // override de demo, depois servidor
if (fonte) { var d = new Date(fonte); if (!isNaN(d)) return d; }
// fallback: relógio local convertido para o fuso de Brasília (-03:00)
var agora = new Date();
var utc = agora.getTime() + agora.getTimezoneOffset() * 60000;
return new Date(utc – 3 * 3600000);
}
var HOJE = resolveHoje();
var MESES = [‘JAN’, ‘FEV’, ‘MAR’, ‘ABR’, ‘MAI’, ‘JUN’, ‘JUL’, ‘AGO’, ‘SET’, ‘OUT’, ‘NOV’, ‘DEZ’];
// Marcos legais (espelho dos type:”marco” de data/calendario.json). Fallback embutido.
var MARCOS = [
{ date: ‘2026-05-06’, ic: ‘id’, title: ‘Prazo final para o título de eleitor’, desc: ‘Último dia para tirar o primeiro título, transferir o domicílio eleitoral, atualizar dados ou regularizar pendências.’ },
{ date: ‘2026-07-20’, dateEnd: ‘2026-08-05’, ic: ‘users’, title: ‘Convenções partidárias’, desc: ‘Período em que os partidos realizam convenções para escolher candidatos e definir coligações.’ },
{ date: ‘2026-07-20’, dateEnd: ‘2026-08-20’, ic: ‘pin’, title: ‘Habilitação do voto em trânsito’, desc: ‘Período para pedir, alterar ou cancelar a habilitação para votar fora do domicílio eleitoral.’ },
{ date: ‘2026-08-15′, time: ’19:00’, ic: ‘file’, title: ‘Prazo final para registro de candidaturas’, desc: ‘Último dia para os partidos registrarem oficialmente seus candidatos na Justiça Eleitoral, até as 19h.’ },
{ date: ‘2026-08-16’, ic: ‘mega’, title: ‘Início da propaganda eleitoral’, desc: ‘Fica liberada a propaganda eleitoral nas ruas e na internet, marcando o início oficial da campanha.’ },
{ date: ‘2026-08-28’, dateEnd: ‘2026-10-01’, ic: ‘tv’, title: ‘Horário eleitoral gratuito (1º turno)’, desc: ‘Propaganda eleitoral gratuita no rádio e na TV para o primeiro turno.’ },
{ date: ‘2026-10-04′, time: ’08:00’, ic: ‘vote’, title: ‘1º turno’, desc: ‘Dia da votação do primeiro turno, com as urnas abertas das 8h às 17h pelo horário de Brasília.’, destaque: true },
{ date: ‘2026-10-09’, dateEnd: ‘2026-10-23’, ic: ‘tv’, title: ‘Horário eleitoral gratuito (2º turno)’, desc: ‘Propaganda eleitoral gratuita no rádio e na TV para o segundo turno, onde houver.’ },
{ date: ‘2026-10-25′, time: ’08:00’, ic: ‘vote’, title: ‘2º turno’, desc: ‘Dia da votação do segundo turno, onde houver, com as urnas abertas das 8h às 17h.’, destaque: true },
{ date: ‘2026-11-03’, ic: ‘refresh’, title: ‘Reabertura do cadastro eleitoral’, desc: ‘O cadastro eleitoral volta a funcionar para serviços como tirar, transferir e regularizar o título.’ },
{ date: ‘2026-12-03’, ic: ‘filetext’, title: ‘Prazo para justificar ausência no 1º turno’, desc: ‘Último dia para quem não votou no primeiro turno apresentar justificativa.’ },
{ date: ‘2026-12-18’, ic: ‘award’, title: ‘Diplomação dos eleitos’, desc: ‘Data limite para a diplomação dos eleitos, ato que confirma o resultado e habilita a posse.’ },
{ date: ‘2027-01-05’, ic: ‘landmark’, title: ‘Posse de presidente e vice’, desc: ‘Posse do presidente e do vice-presidente eleitos.’ },
{ date: ‘2027-01-06’, ic: ‘landmark’, title: ‘Posse de governadores’, desc: ‘Posse dos governadores e vices eleitos.’ }
];
/* Dados em uso (SWR). O array acima é o fallback offline (v1): renderiza na hora e
serve se a rede falhar. A rede (uploads.seox.com.br, meta.version 2+) traz os 15
marcos oficiais com as descrições por extenso e VENCE quando version for maior. */
var CAL_URL = (typeof window !== ‘undefined’ && window.LP_CAL_URL) ||
‘https://uploads.seox.com.br/eleicoes/2026/data/calendario.json’;
var FALLBACK_VERSION = 1;
var MARCOS_DATA = MARCOS;
var DATA_VERSION = FALLBACK_VERSION;
var MOUNTED = [];
function statusOf(m, proximoIdx, idx) {
var ini = pd(m.date);
var fim = m.dateEnd ? pd(m.dateEnd) : ini;
if (fim < HOJE) return ‘passado’;
if (ini <= HOJE) return ‘andamento’; // periodo aberto: comecou e nao terminou
if (idx === proximoIdx) return ‘proximo’;
return ‘futuro’;
}
function mount(root) {
root.classList.add(‘cal’);
if (MOUNTED.indexOf(root) < 0) MOUNTED.push(root);
var ordenados = MARCOS_DATA.slice().sort(function (a, b) { return pd(a.date) – pd(b.date); });
// proximo marco = primeiro que ainda NAO comecou (pelo inicio, nao pelo fim:
// um periodo em andamento nao e “proxima data”, ele ja esta acontecendo)
var proximoIdx = -1;
for (var i = 0; i < ordenados.length; i++) {
if (pd(ordenados[i].date) >= HOJE) { proximoIdx = i; break; }
}
var items = ordenados.map(function (m, i) {
var st = statusOf(m, proximoIdx, i);
var prox = st === ‘proximo’ ? ‘Próxima data‘ : ”;
// marcos de turno ganham pilula propria (o card deles e branco como os demais)
if (m.destaque) prox = ‘‘ + m.title + ‘‘ + prox;
var mesAno = mm(m.date) + (ano(m.date) !== 2026 ? ‘ ‘ + ano(m.date) : ”);
return ‘
A linha do tempo oficial das Eleições 2026, dos prazos do título à diplomação dos eleitos.
‘ +
‘
‘ +
‘
‘ +
‘‘ +
‘‘ +
‘
‘ +
‘
‘ +
” +
‘
‘;
wireScroll(root, proximoIdx);
}
function arrow(dir) {
var d = dir === ‘l’ ? ‘M15 18l-6-6 6-6’ : ‘M9 18l6-6-6-6’;
return ‘‘;
}
function wireScroll(root, proximoIdx) {
var vp = root.querySelector(‘.cal-viewport’);
var tl = root.querySelector(‘.cal-tl’);
var btnPrev = root.querySelector(‘[data-go=”prev”]’);
var btnNext = root.querySelector(‘[data-go=”next”]’);
function step() {
var it = tl.querySelector(‘.cal-item’);
var gap = parseFloat(getComputedStyle(tl).columnGap || getComputedStyle(tl).gap || 0) || 0;
return it ? it.getBoundingClientRect().width + gap : 280;
}
function update() {
var max = vp.scrollWidth – vp.clientWidth – 1;
btnPrev.disabled = vp.scrollLeft <= 1;
btnNext.disabled = vp.scrollLeft >= max;
}
var userMoved = false;
function flagMove() { userMoved = true; }
btnPrev.addEventListener(‘click’, function () { flagMove(); vp.scrollBy({ left: -step() * 2, behavior: ‘smooth’ }); });
btnNext.addEventListener(‘click’, function () { flagMove(); vp.scrollBy({ left: step() * 2, behavior: ‘smooth’ }); });
vp.addEventListener(‘scroll’, function () { update(); });
vp.addEventListener(‘wheel’, flagMove, { passive: true });
// arrasto (pointer) p/ desktop
var down = false, sx = 0, sl = 0, moved = false;
vp.addEventListener(‘pointerdown’, function (e) {
down = true; moved = false; sx = e.clientX; sl = vp.scrollLeft; vp.classList.add(‘is-drag’); userMoved = true;
});
window.addEventListener(‘pointermove’, function (e) {
if (!down) return; var dx = e.clientX – sx; if (Math.abs(dx) > 3) moved = true;
vp.scrollLeft = sl – dx;
});
window.addEventListener(‘pointerup’, function () { down = false; vp.classList.remove(‘is-drag’); });
vp.addEventListener(‘click’, function (e) { if (moved) { e.preventDefault(); e.stopPropagation(); } }, true);
// posiciona no “próximo” sem animar (deixa alguns passados à esquerda)
function positionNext() {
var alvo = tl.querySelectorAll(‘.cal-item’)[Math.max(0, proximoIdx)];
if (!alvo || vp.scrollWidth <= vp.clientWidth) { update(); return; }
var padL = parseFloat(getComputedStyle(vp).paddingLeft) || 0;
var prevBeh = vp.style.scrollBehavior;
vp.style.scrollBehavior=”auto”; // instantâneo (smooth engole o set inicial)
vp.scrollLeft = Math.max(0, alvo.offsetLeft – padL);
// correção fina: alinha o próximo exatamente à borda esquerda, contra
// arredondamento sub-pixel / drift de layout em telas grandes (Full HD / 4K)
var delta = alvo.getBoundingClientRect().left – vp.getBoundingClientRect().left – padL;
if (Math.abs(delta) > 1) vp.scrollLeft += delta;
vp.style.scrollBehavior = prevBeh || ”;
update();
}
function reassert() { if (!userMoved) positionNext(); }
requestAnimationFrame(function () { requestAnimationFrame(reassert); });
window.addEventListener(‘load’, reassert);
[120, 350, 700, 1200, 2000].forEach(function (ms) { setTimeout(reassert, ms); });
// Reposiciona o próximo de forma robusta a: reflow tardio (notícias/imagens abaixo)
// E mudança de largura do viewport (troca de resolução no cockpit: HD -> Full HD/4K).
// O track (tl) tem largura constante (soma dos cards), então observar só ele NÃO
// capta o resize do viewport; por isso observamos o vp também.
if (‘ResizeObserver’ in window) {
var lastVpW = vp.clientWidth;
var ro = new ResizeObserver(function () {
if (Math.abs(vp.clientWidth – lastVpW) > 1) { lastVpW = vp.clientWidth; userMoved = false; }
reassert();
});
ro.observe(vp);
ro.observe(tl);
}
window.addEventListener(‘resize’, function () { update(); reassert(); });
}
/* —- camada de dados: fetch + timeout + fallback + SWR ——————
Normaliza o calendario.json: filtra type “marco”, mapeia description -> desc,
preserva date/dateEnd/time/ic/destaque. Ícone ausente cai no fallback do icoSvg. */
function normalizeRemote(json) {
var arr = (json && (json.events || json.marcos || json.items)) ||
(Array.isArray(json) ? json : []);
var out = [];
arr.forEach(function (e) {
if (!e || (e.type && e.type !== ‘marco’)) return; // exclui debates (type “debate”)
if (!e.date || !e.title) return;
out.push({
date: e.date,
dateEnd: e.dateEnd || e.date_end || null,
time: e.time || null,
ic: e.ic || null,
destaque: !!e.destaque,
title: e.title,
desc: (e.description != null ? e.description : (e.desc || ”))
});
});
return out;
}
function fetchJSON(url, ms) {
if (typeof fetch !== ‘function’) return Promise.reject(new Error(‘no-fetch’));
var ctrl = (typeof AbortController !== ‘undefined’) ? new AbortController() : null;
var timer = ctrl ? setTimeout(function () { ctrl.abort(); }, ms || 6000) : null;
return fetch(url, { signal: ctrl ? ctrl.signal : undefined, cache: ‘default’ })
.then(function (r) { if (!r.ok) throw new Error(‘http ‘ + r.status); return r.json(); })
.then(function (j) { if (timer) clearTimeout(timer); return j; },
function (e) { if (timer) clearTimeout(timer); throw e; });
}
function applyData(marcos, version) {
if (!marcos || !marcos.length || !(version > DATA_VERSION)) return;
MARCOS_DATA = marcos; DATA_VERSION = version;
MOUNTED.slice().forEach(function (r) { mount(r); }); // re-render com os dados da rede
}
function loadRemote() {
fetchJSON(CAL_URL, 6000).then(function (json) {
var v = (json && json.meta && +json.meta.version) || 0;
if (v > DATA_VERSION) applyData(normalizeRemote(json), v);
})[‘catch’](function () { /* rede falhou: mantém o fallback embutido */ });
}
function init() {
document.querySelectorAll(‘[data-calendario]’).forEach(mount);
loadRemote(); // SWR: já renderizou o fallback; troca pela rede se vier versão maior
}
if (document.readyState === ‘loading’) document.addEventListener(‘DOMContentLoaded’, init);
else init();
// setData: permite ao app.js injetar dados da camada central (opcional)
window.LCalendario = { mount: mount, setData: applyData };
})();
Nós usamos cookies e outras tecnologias semelhantes para melhorar a sua experiência em nossos serviços, personalizar publicidade e recomendar conteúdo de seu interesse. Ao utilizar nossos serviços, você está ciente dessa funcionalidade.
Termos de Uso
e
Política de Privacidade