markup.html · 310 linhas · 16,2 KB
<!-- ==========================================================================
PAINEL DE PATRIMÔNIO — o portal do cliente, vivo na página
Prefixo: wealth-
Requer: core (tokens, base, motion.css, motion.js) + estilo.css e script.js
Sem lib. Sem canvas: os dois gráficos são SVG inline.
A DOBRA. Cabeçalho centrado do core e, embaixo, UM painel grande que
encena o relatório consolidado que a assessoria entrega. Não é
ferramenta de entrada de dados: ninguém digita nada aqui. É interface
simulada, decorativa e viva — em cinco segundos o visitante entende o
que receberia todo mês.
AS QUATRO PEÇAS, e o que cada uma prova:
1. patrimônio consolidado, contando -> "é uma posição só"
2. rosca por classe de ativo + leitura -> "está alocado"
3. barras por instituição -> "atravessa custódias"
4. linha da janela (sparkline) -> "e você acompanha"
O SVG É DECORATIVO, O DADO É TEXTO. As duas peças gráficas levam
aria-hidden. Quem usa leitor de tela recebe a lista de classes com
percentual e valor, a lista de instituições com valor e a leitura
escrita da linha (mínima, máxima, marcações) — nunca a palavra
"gráfico".
ESTADO INICIAL NO MARKUP. Todo o período padrão (7d) está escrito aqui:
o valor, a variação, as cinco fatias com stroke-dasharray calculado, as
cinco barras com o próprio --wealth-w e o path da linha. Sem
JavaScript o painel está inteiro e correto — nunca uma casca vazia.
Os números deste bloco saem da MESMA conta que o script.js faz para os
outros dois períodos (ver PERIODOS no topo dele); a linha 0 daquela
tabela tem de repetir o que está aqui.
AS ABAS SOMEM SEM JAVASCRIPT. O script marca o painel com .is-live, e
só então as três abas aparecem; no lugar delas, sem script, fica o
rótulo estático da janela em cena. Três botões que ignoram o clique
seriam um defeito, e esconder sem dizer qual janela está na tela seria
outro.
ENTRADA. data-rv do core no invólucro do painel. O crescimento das
formas e a contagem do número são do script.js, disparados pelo
IntersectionObserver dele — nada aqui reimplementa reveal.
CONTEÚDO ILUSTRATIVO: ver a ressalva no pé do painel e a seção
Compliance da ficha.
========================================================================== -->
<section class="wealth sec-deep sec-pad" data-wealth>
<div class="wrap">
<div class="sec-head">
<p class="eyebrow">Portal do cliente</p>
<h2 class="sec-title ttl">O seu patrimônio, em <em>uma</em> tela</h2>
<p class="sec-lead">
O relatório consolidado chega no dia 5 de cada mês e reúne as suas
instituições numa posição só. Aqui ele está vivo: escolha a janela e
o painel inteiro recalcula.
</p>
</div>
<!-- CONTEÚDO ILUSTRATIVO — todos os valores, percentuais e instituições
deste painel são fictícios e existem só para dar forma à peça. -->
<div class="wealth-panel glass" data-rv data-wealth-panel>
<!-- -----------------------------------------------------------------
BARRA DE TOPO
Identidade do relatório à esquerda, janela à direita — a mesma
divisão que a barra de um aplicativo tem. A pílula "ilustrativo"
é a primitiva .pill do core e mora aqui de propósito: o aviso
aparece na própria interface, não só na letra do rodapé.
---------------------------------------------------------------- -->
<div class="wealth-bar">
<p class="wealth-app">
<span class="wealth-app-mark" aria-hidden="true">
<svg class="wealth-app-glyph" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"><path d="M4 18.5V9.2M9.3 18.5V5.5M14.7 18.5v-6.6M20 18.5V8"/></svg>
</span>
<span class="wealth-app-name">Relatório consolidado</span>
<span class="pill wealth-app-tag">ilustrativo</span>
</p>
<div class="wealth-tabs" role="group" aria-label="Janela do relatório" data-wealth-tabs>
<button class="wealth-tab is-on" type="button" aria-pressed="true"
aria-label="Janela de 7 dias" data-wealth-tab>7d</button>
<button class="wealth-tab" type="button" aria-pressed="false"
aria-label="Janela de 30 dias" data-wealth-tab>30d</button>
<button class="wealth-tab" type="button" aria-pressed="false"
aria-label="Janela de 12 meses" data-wealth-tab>12m</button>
</div>
<!-- O que ocupa o lugar das abas quando não há script. -->
<p class="wealth-static">janela de 7 dias</p>
</div>
<!-- A troca de janela é silenciosa para quem enxerga a tela inteira e
invisível para quem não enxerga: esta região anuncia o que mudou,
e só é preenchida quando alguém clica. -->
<p class="wealth-live" role="status" aria-live="polite" data-wealth-live></p>
<div class="wealth-grid">
<!-- ===============================================================
1. PATRIMÔNIO CONSOLIDADO
O número conta ao entrar na tela e reconta a cada troca de
janela. O valor final está escrito aqui: é ele que fica de pé
sem JavaScript e é dele que a contagem parte de volta.
=============================================================== -->
<div class="wealth-cell wealth-cell-total">
<h3 class="wealth-cell-title">Patrimônio consolidado</h3>
<p class="wealth-total">
<span class="wealth-total-cur">R$</span><span class="wealth-total-num" data-wealth-total>48,12</span><span class="wealth-total-suf">mi</span>
</p>
<!-- A variação vem SEMPRE com a janela escrita ao lado: é fato
passado de um período nomeado, não projeção. -->
<p class="wealth-delta">
<span class="wealth-delta-val" data-wealth-delta>+1,0%</span>
<span class="wealth-delta-lab" data-wealth-delta-lab>na janela de 7 dias</span>
</p>
<p class="wealth-close" data-wealth-close>fechamento de 28/08/2026</p>
</div>
<!-- ===============================================================
4. LINHA DA JANELA (sparkline)
Fica na primeira fila, ao lado do número: a linha é a
continuação visual dele, não uma quarta caixa perdida no pé.
O <svg> estica sem manter proporção (preserveAspectRatio="none")
e o traço se defende com vector-effect. O ponto do fim NÃO é um
<circle>: sob escala não uniforme ele viraria elipse. É um
elemento HTML posicionado por --wealth-spark-y, a altura do
último ponto em porcentagem da caixa — o x é constante por
construção (o último ponto sempre encosta na margem direita).
=============================================================== -->
<div class="wealth-cell wealth-cell-spark">
<div class="wealth-cell-head">
<h3 class="wealth-cell-title">Evolução da janela</h3>
<p class="wealth-spark-end">fecha em <b class="wealth-spark-end-val" data-wealth-spark-end>R$ 48,12 mi</b></p>
</div>
<div class="wealth-spark" style="--wealth-spark-y:15.07%" data-wealth-spark>
<svg class="wealth-spark-svg" viewBox="0 0 600 96" preserveAspectRatio="none"
aria-hidden="true" focusable="false">
<path class="wealth-spark-line" vector-effect="non-scaling-stroke" fill="none"
data-wealth-spark-path
d="M4 86 L57.82 75.57 L111.64 83.02 L165.45 57.69 L219.27 63.65 L273.09 39.8 L326.91 29.37 L380.73 41.29 L434.55 23.41 L488.36 10 L542.18 24.9 L596 14.47"/>
</svg>
<span class="wealth-spark-dot" aria-hidden="true"></span>
</div>
<p class="wealth-spark-read">
mínima <b class="wealth-spark-num" data-wealth-spark-min>R$ 47,64 mi</b> ·
máxima <b class="wealth-spark-num" data-wealth-spark-max>R$ 48,15 mi</b> ·
12 marcações na janela
</p>
</div>
<!-- ===============================================================
2. ROSCA POR CLASSE DE ATIVO
Cinco fatias. A rosca é aria-hidden; a lista ao lado é a
informação — classe, percentual e valor, em texto de verdade.
A cor de cada fatia e da bolinha correspondente sai da MESMA
fonte (--wealth-s1..s5, escolhidas por :nth-child no CSS), e
por isso não há como a legenda desencontrar do desenho.
=============================================================== -->
<div class="wealth-cell wealth-cell-mix">
<h3 class="wealth-cell-title">Por classe de ativo</h3>
<div class="wealth-mix">
<svg class="wealth-donut" viewBox="0 0 120 120" aria-hidden="true" focusable="false"
data-wealth-donut>
<!-- O anel de trás: sem ele os vãos entre as fatias leem como
buracos. Fica FORA do <g> para que os cinco arcos sejam os
filhos 1..5 dele — é por essa posição que o CSS escolhe a
cor de cada um. -->
<circle class="wealth-donut-track" cx="60" cy="60" r="46" fill="none"/>
<!-- rotate(-90) no atributo, e não no CSS: em SVG o
transform-origin do CSS precisa de transform-box, e um
atributo estático não depende da folha ter carregado. -->
<g transform="rotate(-90 60 60)" data-wealth-arcs>
<circle class="wealth-arc" cx="60" cy="60" r="46" fill="none"
stroke-dasharray="95.87 193.16" stroke-dashoffset="0"/>
<circle class="wealth-arc" cx="60" cy="60" r="46" fill="none"
stroke-dasharray="49.62 239.4" stroke-dashoffset="-98.27"/>
<circle class="wealth-arc" cx="60" cy="60" r="46" fill="none"
stroke-dasharray="66.97 222.06" stroke-dashoffset="-150.29"/>
<circle class="wealth-arc" cx="60" cy="60" r="46" fill="none"
stroke-dasharray="32.28 256.74" stroke-dashoffset="-219.66"/>
<circle class="wealth-arc" cx="60" cy="60" r="46" fill="none"
stroke-dasharray="32.28 256.74" stroke-dashoffset="-254.34"/>
</g>
</svg>
<ul class="wealth-mix-list" role="list" data-wealth-mix>
<li class="wealth-mix-item">
<span class="wealth-mix-dot" aria-hidden="true"></span>
<span class="wealth-mix-name">Renda fixa</span>
<span class="wealth-mix-pct" data-wealth-pct>34%</span>
<span class="wealth-mix-val" data-wealth-val>R$ 16,36 mi</span>
</li>
<li class="wealth-mix-item">
<span class="wealth-mix-dot" aria-hidden="true"></span>
<span class="wealth-mix-name">Crédito privado</span>
<span class="wealth-mix-pct" data-wealth-pct>18%</span>
<span class="wealth-mix-val" data-wealth-val>R$ 8,66 mi</span>
</li>
<li class="wealth-mix-item">
<span class="wealth-mix-dot" aria-hidden="true"></span>
<span class="wealth-mix-name">Ações e fundos</span>
<span class="wealth-mix-pct" data-wealth-pct>24%</span>
<span class="wealth-mix-val" data-wealth-val>R$ 11,55 mi</span>
</li>
<li class="wealth-mix-item">
<span class="wealth-mix-dot" aria-hidden="true"></span>
<span class="wealth-mix-name">Imobiliário</span>
<span class="wealth-mix-pct" data-wealth-pct>12%</span>
<span class="wealth-mix-val" data-wealth-val>R$ 5,77 mi</span>
</li>
<li class="wealth-mix-item">
<span class="wealth-mix-dot" aria-hidden="true"></span>
<span class="wealth-mix-name">Internacional</span>
<span class="wealth-mix-pct" data-wealth-pct>12%</span>
<span class="wealth-mix-val" data-wealth-val>R$ 5,78 mi</span>
</li>
</ul>
</div>
</div>
<!-- ===============================================================
3. POR INSTITUIÇÃO
Cinco barras horizontais. Nome e valor são texto; a barra é
enfeite e leva aria-hidden. Cada barra carrega a própria
proporção em --wealth-w (1 = a maior da janela) e cresce por
scaleX com origem à esquerda.
As instituições são MARCAS FICTÍCIAS da casa. Nome de banco ou
de corretora real é marca registrada e não atravessa.
=============================================================== -->
<div class="wealth-cell wealth-cell-inst">
<h3 class="wealth-cell-title">Por instituição</h3>
<ul class="wealth-inst-list" role="list" data-wealth-inst>
<li class="wealth-inst">
<span class="wealth-inst-name">Meridian</span>
<span class="wealth-inst-track" aria-hidden="true">
<span class="wealth-inst-fill" data-wealth-fill style="--wealth-w:1"></span>
</span>
<span class="wealth-inst-val" data-wealth-val>R$ 14,92 mi</span>
</li>
<li class="wealth-inst">
<span class="wealth-inst-name">Vantor</span>
<span class="wealth-inst-track" aria-hidden="true">
<span class="wealth-inst-fill" data-wealth-fill style="--wealth-w:.743"></span>
</span>
<span class="wealth-inst-val" data-wealth-val>R$ 11,08 mi</span>
</li>
<li class="wealth-inst">
<span class="wealth-inst-name">Quadrant</span>
<span class="wealth-inst-track" aria-hidden="true">
<span class="wealth-inst-fill" data-wealth-fill style="--wealth-w:.613"></span>
</span>
<span class="wealth-inst-val" data-wealth-val>R$ 9,15 mi</span>
</li>
<li class="wealth-inst">
<span class="wealth-inst-name">Altamira</span>
<span class="wealth-inst-track" aria-hidden="true">
<span class="wealth-inst-fill" data-wealth-fill style="--wealth-w:.484"></span>
</span>
<span class="wealth-inst-val" data-wealth-val>R$ 7,22 mi</span>
</li>
<li class="wealth-inst">
<span class="wealth-inst-name">Kessler</span>
<span class="wealth-inst-track" aria-hidden="true">
<span class="wealth-inst-fill" data-wealth-fill style="--wealth-w:.385"></span>
</span>
<span class="wealth-inst-val" data-wealth-val>R$ 5,75 mi</span>
</li>
</ul>
</div>
</div>
<!-- -----------------------------------------------------------------
PÉ DO PAINEL
UM detalhe vivo, e um só: o anel do ponto de sincronia, que
respira devagar. O sparkline não ganha ponto sozinho, as barras
não oscilam e nenhum número muda sem alguém pedir — dois detalhes
vivos ao mesmo tempo viram enfeite.
A ressalva fica AQUI, visível, ao lado do painel que ela cobre —
não em letra miúda no rodapé da página.
---------------------------------------------------------------- -->
<div class="wealth-foot">
<p class="wealth-sync">
<span class="wealth-sync-dot" aria-hidden="true" data-wealth-ping><span class="wealth-sync-ping"></span></span>
Custódias sincronizadas
</p>
<p class="nota wealth-nota">
Painel ilustrativo. Os números não representam carteira real nem
resultado de cliente. Variação de janela é fato passado e não
garante resultado futuro.
</p>
</div>
</div>
</div>
</section>
estilo.css · 477 linhas · 19,1 KB
/* ==========================================================================
PAINEL DE PATRIMÔNIO — prefixo wealth-
Um painel grande que encena o relatório consolidado: número principal,
rosca por classe de ativo, barras por instituição e a linha da janela.
Tokens usados:
--accent --accent-2 a série dos gráficos e o realce da variação
--bg o fundo do escopo, que dilui a série (ver abaixo)
--display --sans números e títulos / interface
--ease curva única, em toda transição daqui
--fg --fg-2 --fg-muted tinta, herdada do escopo de tom da seção
--light os brancos com alfa, sempre via color-mix
--radius-media canto do painel e das barras
--radius-pill canto das abas e da pílula "ilustrativo"
--rule --rule-2 as divisórias internas do painel
--sec-gap respiro vertical, via .sec-pad do core
A PALETA DA SÉRIE. Cinco fatias e duas cores de marca: qualquer tentativa
de esticar sage e tan em cinco passos distintos deixa dois pares quase
iguais no meio da rosca. A saída é a que todo painel de private banking
usa — UMA cor, cinco degraus — e o §3 do brief autoriza ("ou por
opacidade"). Aqui o degrau é color-mix com --bg, e não opacity, por dois
motivos: opacity criaria camada de composição em cinco elementos que
nunca mudam, e a bolinha da legenda teria de repetir a mesma conta em
outro contexto de empilhamento para dar a mesma cor. O último degrau
troca para --accent-2 justamente onde os passos ficam mais próximos: é o
par de fatias pequenas (12% e 12%) que precisa da diferença de matiz.
O PAINEL É .glass COM DESFOQUE ZERO. Da primitiva se quer a borda tênue e
o brilho radial vindo do topo — que é o desenho de uma superfície de
aplicativo. O desfoque não: atrás do painel há um fundo opaco de seção,
não há o que desfocar, e a camada custaria composição de graça. Mesmo
raciocínio do conteudo/trio-cards-ui-simulada.
O JS quase não escreve aparência: escreve DADO (dasharray da fatia,
proporção da barra, offset da linha, número) e uma classe. Duração,
curva, cor e forma estão todas aqui.
========================================================================== */
.wealth{
/* Canto do painel. Segue o tema: 0 num projeto flat, 16 num arredondado. */
--wealth-radius:var(--radius-media);
/* Respiro interno de cada célula da grade. É o mesmo da barra de topo e
do pé — o painel inteiro respira por esta variável. */
--wealth-cell-pad:clamp(18px,2vw,28px);
--wealth-donut:132px; /* lado da rosca */
--wealth-spark-h:96px; /* altura da caixa da linha */
--wealth-bar-h:8px; /* espessura da barra de instituição */
--wealth-bar-radius:var(--radius-media);
--wealth-inst-name:82px; /* coluna do nome da instituição */
--wealth-inst-val:92px; /* coluna do valor */
/* Durações das formas crescendo, lidas só pelo CSS. */
--wealth-grow:.9s; /* fatia e barra */
--wealth-draw:1.4s; /* traçado da linha */
/* Os dois tempos que o SCRIPT lê, em SEGUNDOS, com parseFloat. Ficam aqui
porque são ritmo de desenho, não lógica — quem achar a contagem
apressada mexe numa linha de CSS. Em ms a conta sairia mil vezes maior
e o painel pareceria travado. */
--wealth-count-dur:1.1s; /* contagem do número principal */
--wealth-ping-hold:2.9s; /* volta completa do anel de sincronia */
/* A série: cinco degraus de uma cor só, do maior peso ao menor. */
--wealth-s1:var(--accent);
--wealth-s2:color-mix(in srgb,var(--accent) 74%,var(--bg));
--wealth-s3:color-mix(in srgb,var(--accent) 56%,var(--bg));
--wealth-s4:color-mix(in srgb,var(--accent) 42%,var(--bg));
--wealth-s5:color-mix(in srgb,var(--accent-2) 62%,var(--bg));
}
/* ==========================================================================
O PAINEL
========================================================================== */
.wealth-panel{
--glass-bg:color-mix(in srgb,var(--light) 4%,transparent);
--glass-line:color-mix(in srgb,var(--light) 9%,transparent);
--glass-blur:0px;
margin-top:clamp(40px,4.6vw,64px);
border-radius:var(--wealth-radius);
overflow:hidden;
}
/* A região que anuncia a troca de janela para quem não vê a tela. Sai do
fluxo sem sair da árvore de acessibilidade: display:none e
visibility:hidden calariam o leitor de tela, que é o único público dela. */
.wealth-live{
position:absolute;width:1px;height:1px;
overflow:hidden;white-space:nowrap;
clip-path:inset(50%);
}
/* ==========================================================================
BARRA DE TOPO
Identidade à esquerda, janela à direita — a divisão de uma barra de
aplicativo. As abas só aparecem com .is-live, a classe que o script
carimba: sem JavaScript o lugar delas é do rótulo estático.
========================================================================== */
.wealth-bar{
display:flex;flex-wrap:wrap;
align-items:center;justify-content:space-between;
gap:12px 16px;
padding:14px var(--wealth-cell-pad);
border-bottom:1px solid var(--rule);
}
.wealth-app{display:flex;align-items:center;gap:10px;min-width:0}
.wealth-app-mark{
flex:0 0 28px;width:28px;height:28px;
display:flex;align-items:center;justify-content:center;
border:1px solid var(--rule-2);border-radius:var(--radius-media);
color:var(--accent);
}
.wealth-app-glyph{width:15px;height:15px}
.wealth-app-name{
font-family:var(--display);font-weight:700;
font-size:13px;line-height:1;letter-spacing:.01em;
color:var(--fg);
}
/* A pílula do core, um ponto menor: aqui ela é rótulo de estado dentro de
uma barra de 28px de altura, não classificação de card. */
.wealth-app-tag{font-size:9.5px;padding:4px 9px;letter-spacing:.14em}
.wealth-tabs{
display:none;gap:2px;padding:3px;
border-radius:var(--radius-pill);
background:color-mix(in srgb,var(--fg) 7%,transparent);
}
.wealth-panel.is-live .wealth-tabs{display:flex}
.wealth-tab{
height:26px;padding-inline:13px;
border:0;border-radius:var(--radius-pill);
background:transparent;color:var(--fg-muted);
font-family:var(--sans);font-weight:400;
font-size:12px;line-height:1;letter-spacing:normal;
transition:background .4s var(--ease),color .4s var(--ease);
}
.wealth-tab:hover{color:var(--fg-2)}
.wealth-tab.is-on{
background:color-mix(in srgb,var(--fg) 13%,transparent);
color:var(--fg);
}
.wealth-static{font-size:12px;line-height:1;letter-spacing:normal;color:var(--fg-muted)}
.wealth-panel.is-live .wealth-static{display:none}
/* ==========================================================================
A GRADE DE QUATRO PEÇAS
Doze colunas: número (5) ao lado da linha da janela (7), porque a linha é
a continuação visual do número; embaixo, as duas leituras de composição,
metade a metade. As células NÃO têm vão entre si: quem separa é uma
divisória de 1px, como num painel de verdade. Vão entre células daria
cartões flutuando dentro de um cartão.
========================================================================== */
.wealth-grid{
display:grid;
grid-template-columns:repeat(12,minmax(0,1fr));
grid-template-areas:
"total total total total total spark spark spark spark spark spark spark"
"mix mix mix mix mix mix inst inst inst inst inst inst";
}
/* min-width:0 em toda célula: sem ele, uma linha longa de valor impede a
coluna de encolher e a grade estoura para fora do painel. */
.wealth-cell{padding:var(--wealth-cell-pad);min-width:0}
.wealth-cell-total{grid-area:total;display:flex;flex-direction:column}
.wealth-cell-spark{grid-area:spark;border-left:1px solid var(--rule)}
.wealth-cell-mix{grid-area:mix;border-top:1px solid var(--rule)}
.wealth-cell-inst{grid-area:inst;border-top:1px solid var(--rule);border-left:1px solid var(--rule)}
.wealth-cell-title{
font-family:var(--sans);font-weight:400;
font-size:11px;line-height:1.2;letter-spacing:.16em;text-transform:uppercase;
color:var(--fg-muted);
}
.wealth-cell-head{
display:flex;flex-wrap:wrap;align-items:baseline;
justify-content:space-between;gap:6px 14px;
}
/* ==========================================================================
1. PATRIMÔNIO CONSOLIDADO
========================================================================== */
/* baseline alinha "R$", o número e "mi" pela linha de base, e não pelo
centro: é o que faz os três lerem como uma cifra só. */
.wealth-total{
display:flex;align-items:baseline;gap:8px;
margin-top:16px;
font-family:var(--display);font-weight:700;
line-height:1;letter-spacing:-.03em;
color:var(--fg);
}
.wealth-total-cur{font-size:clamp(17px,1.5vw,21px);color:var(--fg-2)}
/* tabular-nums é o que impede o número de tremer durante a contagem: sem
ele cada dígito tem largura própria e a cifra inteira dança. Fonte sem a
tabela de figuras tabulares ignora e nada quebra. */
.wealth-total-num{
font-size:clamp(38px,4.4vw,60px);
font-variant-numeric:tabular-nums;
}
.wealth-total-suf{font-size:clamp(15px,1.3vw,19px);color:var(--fg-2)}
.wealth-delta{
display:flex;flex-wrap:wrap;align-items:baseline;gap:8px;
margin-top:16px;
}
.wealth-delta-val{
font-family:var(--display);font-weight:700;
font-size:15px;line-height:1;letter-spacing:normal;
color:var(--accent);
}
.wealth-delta-lab{font-size:13.5px;line-height:1.4;letter-spacing:normal;color:var(--fg-2)}
/* margin-top:auto empurra o fechamento para o pé da célula: o número fica
com silêncio em volta, que é o que o faz pesar. */
.wealth-close{
margin-top:auto;padding-top:14px;
font-size:12.5px;line-height:1.4;letter-spacing:normal;
color:var(--fg-muted);
}
/* ==========================================================================
2. ROSCA POR CLASSE DE ATIVO
O <svg> é decorativo e leva aria-hidden no markup; a lista ao lado é a
informação. Cor da fatia e cor da bolinha saem da MESMA variável, pela
mesma posição — não há como a legenda desencontrar do desenho.
========================================================================== */
.wealth-mix{
display:flex;align-items:center;
gap:clamp(16px,2.2vw,30px);
margin-top:20px;
}
.wealth-donut{
flex:0 0 var(--wealth-donut);
width:var(--wealth-donut);height:var(--wealth-donut);
}
/* O anel de trás. Sem ele, os vãos de 2.4 unidades entre as fatias leem
como buracos na rosca em vez de separação. */
.wealth-donut-track{
stroke:color-mix(in srgb,var(--fg) 8%,transparent);
stroke-width:13;
}
/* A transição é de stroke-dasharray, e não de dashoffset: com dasharray
fixo o offset só GIRA a fatia — o comprimento do traço é a única coisa
que a faz crescer do próprio começo. */
.wealth-arc{
stroke-width:13;stroke-linecap:butt;
transition:stroke-dasharray var(--wealth-grow) var(--ease);
}
.wealth-arc:nth-child(1){stroke:var(--wealth-s1)}
.wealth-arc:nth-child(2){stroke:var(--wealth-s2);transition-delay:.06s}
.wealth-arc:nth-child(3){stroke:var(--wealth-s3);transition-delay:.12s}
.wealth-arc:nth-child(4){stroke:var(--wealth-s4);transition-delay:.18s}
.wealth-arc:nth-child(5){stroke:var(--wealth-s5);transition-delay:.24s}
.wealth-mix-list{flex:1 1 0;min-width:0;display:grid;gap:10px}
.wealth-mix-item{
display:grid;
grid-template-columns:9px minmax(0,1fr) 34px auto;
align-items:center;gap:10px;
font-size:13.5px;line-height:1.3;letter-spacing:normal;
}
.wealth-mix-dot{width:9px;height:9px;border-radius:50%}
.wealth-mix-item:nth-child(1) .wealth-mix-dot{background:var(--wealth-s1)}
.wealth-mix-item:nth-child(2) .wealth-mix-dot{background:var(--wealth-s2)}
.wealth-mix-item:nth-child(3) .wealth-mix-dot{background:var(--wealth-s3)}
.wealth-mix-item:nth-child(4) .wealth-mix-dot{background:var(--wealth-s4)}
.wealth-mix-item:nth-child(5) .wealth-mix-dot{background:var(--wealth-s5)}
.wealth-mix-name{color:var(--fg-2);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.wealth-mix-pct{text-align:right;color:var(--fg);font-variant-numeric:tabular-nums}
.wealth-mix-val{
text-align:right;color:var(--fg-muted);
font-variant-numeric:tabular-nums;white-space:nowrap;
}
/* ==========================================================================
3. POR INSTITUIÇÃO
Nome e valor são texto; a barra é enfeite e sai da árvore de
acessibilidade no markup.
========================================================================== */
.wealth-inst-list{margin-top:20px;display:grid;gap:14px}
.wealth-inst{
display:grid;
grid-template-columns:var(--wealth-inst-name) minmax(0,1fr) var(--wealth-inst-val);
align-items:center;gap:14px;
font-size:13.5px;line-height:1.3;letter-spacing:normal;
}
.wealth-inst-name{color:var(--fg-2);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.wealth-inst-val{
text-align:right;color:var(--fg);
font-variant-numeric:tabular-nums;white-space:nowrap;
}
.wealth-inst-track{
height:var(--wealth-bar-h);
border-radius:var(--wealth-bar-radius);
background:color-mix(in srgb,var(--fg) 8%,transparent);
overflow:hidden;
}
/* scaleX com origem à esquerda, e não width: a barra cresce numa
propriedade de composição, sem custar layout a cada quadro da transição.
O preço é que o canto direito do preenchimento é achatado junto — por
isso --wealth-bar-radius acompanha o tema em vez de ser pílula fixa. */
.wealth-inst-fill{
display:block;height:100%;
border-radius:inherit;
background:color-mix(in srgb,var(--accent) 80%,transparent);
transform:scaleX(var(--wealth-w,0));transform-origin:left center;
transition:transform var(--wealth-grow) var(--ease);
}
.wealth-inst:nth-child(2) .wealth-inst-fill{transition-delay:.05s}
.wealth-inst:nth-child(3) .wealth-inst-fill{transition-delay:.1s}
.wealth-inst:nth-child(4) .wealth-inst-fill{transition-delay:.15s}
.wealth-inst:nth-child(5) .wealth-inst-fill{transition-delay:.2s}
/* ==========================================================================
4. LINHA DA JANELA
O SVG estica sem manter proporção, para a linha ocupar a célula inteira
em qualquer largura; vector-effect segura a espessura do traço, que sob
escala não uniforme engordaria na horizontal.
========================================================================== */
.wealth-spark-end{font-size:12.5px;line-height:1.3;letter-spacing:normal;color:var(--fg-muted)}
.wealth-spark-end-val{font-weight:400;color:var(--fg-2);font-variant-numeric:tabular-nums}
.wealth-spark{
position:relative;
margin-top:18px;height:var(--wealth-spark-h);
}
/* A base do gráfico. Uma linha só, no tom de divisória do escopo: o
suficiente para a curva ter chão sem virar grade. */
.wealth-spark::after{
content:"";position:absolute;left:0;right:0;bottom:0;
height:1px;background:var(--rule);
}
.wealth-spark-svg{display:block;width:100%;height:100%}
.wealth-spark-line{
stroke:var(--accent);stroke-width:2;
stroke-linejoin:round;stroke-linecap:round;
transition:stroke-dashoffset var(--wealth-draw) var(--ease);
}
/* O ponto do fim é HTML, não <circle>: sob preserveAspectRatio="none" um
círculo do SVG viraria elipse. Aqui ele é redondo em qualquer largura.
O x é constante por construção — o último ponto da série sempre encosta
na margem direita do viewBox —, e só o y viaja em --wealth-spark-y. */
.wealth-spark-dot{
position:absolute;left:calc(100% - 4px);top:var(--wealth-spark-y,50%);
width:9px;height:9px;margin:-4.5px 0 0 -4.5px;
border-radius:50%;background:var(--accent);
box-shadow:0 0 0 4px color-mix(in srgb,var(--accent) 16%,transparent);
}
.wealth-spark-read{
margin-top:14px;
font-size:12.5px;line-height:1.4;letter-spacing:normal;
color:var(--fg-muted);
}
.wealth-spark-num{font-weight:400;color:var(--fg-2);font-variant-numeric:tabular-nums}
/* ==========================================================================
PÉ DO PAINEL — sincronia viva e ressalva
========================================================================== */
.wealth-foot{
display:flex;flex-wrap:wrap;
align-items:center;justify-content:space-between;
gap:10px 24px;
padding:14px var(--wealth-cell-pad);
border-top:1px solid var(--rule);
}
.wealth-sync{
display:inline-flex;align-items:center;gap:10px;
font-size:12.5px;line-height:1.3;letter-spacing:normal;
color:var(--fg-2);white-space:nowrap;
}
.wealth-sync-dot{
position:relative;flex:0 0 8px;width:8px;height:8px;
border-radius:50%;background:var(--accent);
}
/* O ÚNICO detalhe vivo do painel. O script escreve um número de 0 a 1 em
--wealth-ping; a aparência é decidida aqui. Sem script (ou com movimento
reduzido, quando o laço nem chega a ser criado) o valor fica no 0 do
fallback e o anel vira um halo parado — estado honesto, não defeito. */
.wealth-sync-ping{
position:absolute;inset:0;border-radius:50%;
border:1px solid var(--accent);
transform:scale(calc(1 + var(--wealth-ping,0) * 2.1));
opacity:calc((1 - var(--wealth-ping,0)) * .5);
}
/* A ressalva de compliance. Fica no painel que ela cobre, visível, e não em
letra miúda no rodapé da página. */
/* Tipografia pela primitiva .nota do core. --nota-max:none porque a ressalva
divide o pé do painel com o indicador de sincronia e é alinhada à DIREITA:
um teto de medida encolheria a caixa e desencostaria o texto da borda. */
.wealth-nota{
flex:1 1 340px;
--nota-max:none;
text-align:right;
}
/* ---------------------------------------------------------------- movimento reduzido */
/* Duas camadas, e as duas precisam existir. O script não liga o laço e
pinta os valores finais de uma vez; estas regras garantem que a pintura
não venha com transição atrás — inclusive na troca de janela, que
continua funcionando por clique (aba que ignora o clique é defeito, não
acessibilidade). */
@media(prefers-reduced-motion:reduce){
.wealth-arc,.wealth-inst-fill,.wealth-spark-line,.wealth-tab{transition:none}
}
/* A mesma suspensão, agora sob comando do script: .is-still marca o quadro
em que os valores são escritos sem que ninguém veja o caminho — o estado
zero da entrada e a pintura direta com movimento reduzido. */
.wealth.is-still .wealth-arc,
.wealth.is-still .wealth-inst-fill,
.wealth.is-still .wealth-spark-line{transition:none}
/* ---------------------------------------------------------------- mobile */
/* A grade de doze colunas precisa de ~900 de painel útil para a rosca e a
lista caberem lado a lado dentro da metade esquerda. Abaixo disso as
quatro peças viram quatro faixas, na mesma ordem de leitura. */
@media(max-width:960px){
.wealth-grid{
grid-template-columns:minmax(0,1fr);
grid-template-areas:"total" "spark" "mix" "inst";
}
.wealth-cell-spark,.wealth-cell-inst{border-left:0}
.wealth-cell-spark{border-top:1px solid var(--rule)}
.wealth-close{margin-top:14px;padding-top:0}
}
@media(max-width:640px){
.wealth{--wealth-donut:120px;--wealth-spark-h:78px;--wealth-inst-val:84px}
/* Rosca e lista lado a lado em 350px de largura espremeriam o nome da
classe até a elipse. Empilhadas, a rosca centraliza e a lista fica com
a linha inteira. */
.wealth-mix{flex-direction:column;align-items:stretch;gap:20px}
.wealth-donut{align-self:center}
.wealth-nota{text-align:left}
}
script.js · 402 linhas · 16,3 KB
/* ==========================================================================
PAINEL DE PATRIMÔNIO — a janela do relatório e o que ela recalcula.
UM laço de requestAnimationFrame por painel, e ele faz duas coisas: a
contagem do número principal e o anel de sincronia respirando. As formas
(fatia, barra, linha) crescem por TRANSIÇÃO de CSS — o script escreve o
valor final uma vez e o navegador interpola sozinho, fora da thread
principal. Animar quatro peças no rAF seria trabalho de graça.
O laço dorme fora da tela, por IntersectionObserver: sem isso o anel
continuaria escrevendo estilo sessenta vezes por segundo com a dobra a
três telas de distância. Medido: zero escritas em 60 quadros com a seção
fora de vista.
TEMPO POR dt, COM TETO. A contagem e o anel avançam pelo tempo decorrido,
não por quadro: em tela de 120Hz o número chegaria em metade do tempo se
fosse por quadro. O teto de 50ms existe para o retorno de uma aba em
segundo plano — sem ele o primeiro quadro traria segundos acumulados e o
anel daria um salto.
O QUE O SCRIPT NÃO FAZ. Não escreve cor, duração nem curva: escreve DADO
(o dasharray da fatia, a proporção da barra, o offset da linha, o número)
e troca classe. Aparência inteira no estilo.css.
MOVIMENTO REDUZIDO. O laço não é criado. O número aparece no valor final,
a rosca e as barras nascem prontas e a linha nasce inteira; o anel fica
parado no halo. As abas continuam trocando a janela por clique — aba que
ignora o clique é defeito, não acessibilidade.
SEM DADO PESSOAL. O painel não coleta nada, não lê nada do visitante e
não grava nada: as três janelas vivem na tabela abaixo e morrem no
recarregamento. Quem coleta é o conversao/cta-formulario-lead.
========================================================================== */
(function () {
var raizes = document.querySelectorAll('[data-wealth]');
if (!raizes.length) return; // guard
/* ======================================================================
A TABELA — o ÚNICO lugar que o consumidor edita.
Uma linha por janela, na ordem das abas. Tudo é ILUSTRATIVO: nenhum
número aqui é apurado, nenhuma instituição é real (ver a ficha,
seção Compliance).
As três janelas são três edições do mesmo relatório, cada uma com o
próprio fechamento — é isso que permite o total mudar de uma para
outra sem mentir: não é o mesmo dia visto de três ângulos, são três
documentos com três datas.
Regras que a tabela tem de respeitar, e que ninguém checa por você:
. mix soma 100 (é percentual de carteira);
. mixValor soma total (é o mesmo dinheiro, repartido);
. inst soma total (idem, agora por custódia);
. serie termina no total (a linha fecha onde o número está);
. variacao é a razão entre o primeiro e o último ponto da série, e
o texto de janela vem SEMPRE junto — variação de período é fato
passado de um período nomeado, nunca projeção.
A linha 0 tem de repetir o que está escrito no markup.html: é ela que
fica de pé sem JavaScript, e é para ela que a primeira aba volta.
====================================================================== */
var PERIODOS = [
{
rotulo:'7d',
janela:'na janela de 7 dias',
anuncio:'Janela de 7 dias',
fechamento:'fechamento de 28/08/2026',
total:48.12,
variacao:'+1,0%',
mix:[34,18,24,12,12],
mixValor:[16.36,8.66,11.55,5.77,5.78],
inst:[14.92,11.08,9.15,7.22,5.75],
serie:[47.64,47.71,47.66,47.83,47.79,47.95,48.02,47.94,48.06,48.15,48.05,48.12]
},
{
rotulo:'30d',
janela:'na janela de 30 dias',
anuncio:'Janela de 30 dias',
fechamento:'fechamento de 31/08/2026',
total:48.47,
variacao:'+2,1%',
mix:[36,17,23,12,12],
mixValor:[17.45,8.24,11.15,5.82,5.81],
inst:[14.71,11.19,9.03,7.57,5.97],
serie:[47.47,47.62,47.55,47.78,47.91,47.82,48.03,48.16,48.07,48.28,48.41,48.47]
},
{
rotulo:'12m',
janela:'na janela de 12 meses',
anuncio:'Janela de 12 meses',
fechamento:'fechamento de 31/12/2025',
total:44.08,
variacao:'+7,4%',
mix:[39,16,21,13,11],
mixValor:[17.19,7.05,9.26,5.73,4.85],
inst:[13.22,10.14,8.37,6.88,5.47],
serie:[41.05,41.42,41.28,41.96,42.34,42.15,42.78,43.10,42.92,43.44,43.86,44.08]
}
];
/* ---------------------------------------------------------- geometria */
/* Os mesmos números do <svg> do markup. Mexer aqui sem mexer lá deixa a
rosca do estado sem JavaScript com outro raio. */
var RAIO = 46;
var CIRC = 2 * Math.PI * RAIO; /* 289.03 — o dasharray é fatia deste bolo */
var VAO = 2.4; /* respiro entre fatias, em unidades de arco */
var SW = 600, SH = 96, PX = 4, PY = 10; /* viewBox e margens da linha */
function arred(n) { return Math.round(n * 100) / 100; }
function numero(v) { return v.toFixed(2).replace('.', ','); }
function moeda(v) { return 'R$ ' + numero(v) + ' mi'; }
/* Cada fatia é um traço de comprimento proporcional sobre a mesma
circunferência, deslocado até o próprio começo. O <g> já está girado
-90° no markup, então a primeira nasce às 12 horas. */
function arcos(mix) {
var fora = [], soma = 0, comp, i;
for (i = 0; i < mix.length; i++) {
comp = CIRC * mix[i] / 100 - VAO;
if (comp < 0) comp = 0;
fora.push({ dash: arred(comp), off: arred(-CIRC * soma / 100) });
soma += mix[i];
}
return fora;
}
/* A série é normalizada pela própria mínima e máxima: a linha usa a
altura inteira da caixa em qualquer janela. Uma escala fixa deixaria a
janela de 7 dias — meio ponto percentual de amplitude — reta. */
function caminho(serie) {
var mn = serie[0], mx = serie[0], i, x, y, faixa, d = '';
for (i = 1; i < serie.length; i++) {
if (serie[i] < mn) mn = serie[i];
if (serie[i] > mx) mx = serie[i];
}
faixa = (mx - mn) || 1;
for (i = 0; i < serie.length; i++) {
x = PX + i * (SW - 2 * PX) / (serie.length - 1);
y = (SH - PY) - ((serie[i] - mn) / faixa) * (SH - 2 * PY);
d += (i ? ' L' : 'M') + arred(x) + ' ' + arred(y);
}
y = (SH - PY) - ((serie[serie.length - 1] - mn) / faixa) * (SH - 2 * PY);
return { d: d, min: mn, max: mx, fimY: arred(y / SH * 100) };
}
/* ------------------------------------------------------------ auxílio */
/* A flag do core é a fonte. O fallback existe para o componente colado
sozinho, sem motion.js — e repete a regra do ?motion=on. */
function reduzido() {
if (window.LIB && typeof window.LIB.reduce === 'boolean') return window.LIB.reduce;
return matchMedia('(prefers-reduced-motion: reduce)').matches &&
location.search.indexOf('motion=on') < 0;
}
/* getPropertyValue devolve " 1.1s"; parseFloat resolve o espaço e a
unidade de uma vez. O CSS documenta que o valor é em segundos. */
function tempo(raiz, nome, padrao) {
var v = parseFloat(getComputedStyle(raiz).getPropertyValue(nome));
return (v > 0 ? v : padrao) * 1000;
}
function lista(el, seletor) {
return el ? [].slice.call(el.querySelectorAll(seletor)) : [];
}
/* ---------------------------------------------------------- instância */
function iniciar(raiz) {
var painel = raiz.querySelector('[data-wealth-panel]');
var arcosEl = raiz.querySelector('[data-wealth-arcs]');
var listaMix = raiz.querySelector('[data-wealth-mix]');
var listaIns = raiz.querySelector('[data-wealth-inst]');
var caixaSpk = raiz.querySelector('[data-wealth-spark]');
var linha = raiz.querySelector('[data-wealth-spark-path]');
var elTotal = raiz.querySelector('[data-wealth-total]');
if (!painel || !arcosEl || !listaMix || !listaIns || !caixaSpk || !linha || !elTotal) return;
var elDelta = raiz.querySelector('[data-wealth-delta]');
var elDeltaLab = raiz.querySelector('[data-wealth-delta-lab]');
var elClose = raiz.querySelector('[data-wealth-close]');
var elFim = raiz.querySelector('[data-wealth-spark-end]');
var elMin = raiz.querySelector('[data-wealth-spark-min]');
var elMax = raiz.querySelector('[data-wealth-spark-max]');
var elVivo = raiz.querySelector('[data-wealth-live]');
var elPonto = raiz.querySelector('[data-wealth-ping]');
var fatias = [].slice.call(arcosEl.children);
var pcts = lista(listaMix, '[data-wealth-pct]');
var valsMix = lista(listaMix, '[data-wealth-val]');
var valsIns = lista(listaIns, '[data-wealth-val]');
var barras = lista(listaIns, '[data-wealth-fill]');
var abas = lista(raiz, '[data-wealth-tab]');
if (fatias.length < 2 || !abas.length) return;
var reduz = reduzido();
var durConta = tempo(raiz, '--wealth-count-dur', 1.1);
var durPing = tempo(raiz, '--wealth-ping-hold', 2.9);
var atual = 0;
var mostrado = 0; /* o número que está escrito na tela, em milhões */
var conta = null; /* {de, para, t} enquanto a contagem corre */
var ping = 0; /* 0..1 — a volta do anel de sincronia */
var laco = 0, dentro = false, marca = 0;
/* As abas só existem para quem tem script: é esta classe que as revela
e some com o rótulo estático que ocupava o lugar delas. */
painel.classList.add('is-live');
/* ------------------------------------------------------------ formas */
/* Escreve com as transições suspensas, e devolve o painel ao normal no
mesmo quadro. O reflow no meio é o que sela o estado antes de as
transições voltarem — sem ele o navegador junta as duas escritas e
anima o caminho que ninguém deveria ver. */
function semTransicao(fn) {
raiz.classList.add('is-still');
fn();
void raiz.offsetHeight;
raiz.classList.remove('is-still');
}
function zerarFormas() {
var i;
for (i = 0; i < fatias.length; i++) {
fatias[i].setAttribute('stroke-dasharray', '0 ' + arred(CIRC));
}
for (i = 0; i < barras.length; i++) barras[i].style.setProperty('--wealth-w', '0');
/* O traçado parte do fim: dasharray do tamanho do caminho inteiro e
offset igual — a linha existe, mas nenhum pedaço dela está visível. */
var L = linha.getTotalLength();
linha.style.strokeDasharray = L;
linha.style.strokeDashoffset = L;
}
function aplicarFormas(p) {
var arc = arcos(p.mix), i;
for (i = 0; i < fatias.length && i < arc.length; i++) {
fatias[i].setAttribute('stroke-dasharray', arc[i].dash + ' ' + arred(CIRC - arc[i].dash));
fatias[i].setAttribute('stroke-dashoffset', arc[i].off);
}
var maior = 0;
for (i = 0; i < p.inst.length; i++) if (p.inst[i] > maior) maior = p.inst[i];
for (i = 0; i < barras.length && i < p.inst.length; i++) {
barras[i].style.setProperty('--wealth-w', maior ? arred(p.inst[i] / maior * 1000) / 1000 : 0);
}
linha.style.strokeDashoffset = 0;
}
/* ------------------------------------------------------------- texto */
function escreverTexto(p) {
var cam = caminho(p.serie), i;
if (elDelta) elDelta.textContent = p.variacao;
if (elDeltaLab) elDeltaLab.textContent = p.janela;
if (elClose) elClose.textContent = p.fechamento;
if (elFim) elFim.textContent = moeda(p.serie[p.serie.length - 1]);
if (elMin) elMin.textContent = moeda(cam.min);
if (elMax) elMax.textContent = moeda(cam.max);
for (i = 0; i < pcts.length && i < p.mix.length; i++) pcts[i].textContent = p.mix[i] + '%';
for (i = 0; i < valsMix.length && i < p.mixValor.length; i++) valsMix[i].textContent = moeda(p.mixValor[i]);
for (i = 0; i < valsIns.length && i < p.inst.length; i++) valsIns[i].textContent = moeda(p.inst[i]);
/* O atributo d muda ANTES de qualquer conta de comprimento: getTotalLength
mede o caminho que está lá, não o anterior. */
linha.setAttribute('d', cam.d);
caixaSpk.style.setProperty('--wealth-spark-y', cam.fimY + '%');
}
/* ---------------------------------------------------------- contagem */
function escreverTotal(v) {
mostrado = v;
elTotal.textContent = numero(v);
}
function contarAte(v, animar) {
if (!animar) { conta = null; escreverTotal(v); return; }
conta = { de: mostrado, para: v, t: 0 };
}
/* -------------------------------------------------------------- pintura */
function pintar(i, animar) {
var p = PERIODOS[i];
if (!p) return;
atual = i;
for (var b = 0; b < abas.length; b++) {
if (b === i) abas[b].classList.add('is-on');
else abas[b].classList.remove('is-on');
abas[b].setAttribute('aria-pressed', b === i ? 'true' : 'false');
}
escreverTexto(p);
if (animar) {
semTransicao(function () { zerarFormas(); });
aplicarFormas(p);
} else {
semTransicao(function () { aplicarFormas(p); });
}
contarAte(p.total, animar);
}
/* ------------------------------------------------------------- laço */
function quadro(agora) {
if (!marca) marca = agora;
var dt = agora - marca;
if (dt > 50) dt = 50; /* teto: aba em segundo plano */
marca = agora;
ping += dt / durPing;
while (ping >= 1) ping -= 1;
if (elPonto) elPonto.style.setProperty('--wealth-ping', ping.toFixed(3));
if (conta) {
conta.t += dt;
var q = conta.t / durConta;
if (q > 1) q = 1;
var e = 1 - Math.pow(1 - q, 3); /* saída suave; a curva do CSS não
se lê de dentro do script */
escreverTotal(conta.de + (conta.para - conta.de) * e);
if (q === 1) conta = null;
}
laco = dentro ? requestAnimationFrame(quadro) : 0;
}
function ligar() {
if (laco || !dentro) return;
marca = 0; /* zera o relógio: voltar à dobra
depois de um tempo fora não pode
despejar o acumulado num quadro */
laco = requestAnimationFrame(quadro);
}
function desligar() {
if (!laco) return;
cancelAnimationFrame(laco);
laco = 0;
/* Sair de cena no meio de uma contagem deixaria um número errado
congelado na tela até a próxima visita. Fecha na hora. */
if (conta) { escreverTotal(conta.para); conta = null; }
ping = 0;
if (elPonto) elPonto.style.setProperty('--wealth-ping', '0');
}
/* -------------------------------------------------------------- abas */
for (var k = 0; k < abas.length; k++) {
(function (i) {
abas[i].addEventListener('click', function () {
if (i === atual) return;
pintar(i, !reduz);
/* A troca é visível para quem enxerga o painel e silenciosa para
quem não enxerga. A região só é preenchida a partir daqui — no
carregamento ela ficaria anunciando um estado que ninguém pediu. */
if (elVivo) elVivo.textContent = PERIODOS[i].anuncio + '. Patrimônio consolidado ' +
moeda(PERIODOS[i].total) + '.';
});
})(k);
}
/* ------------------------------------------------------------ partida */
/* Com movimento reduzido a dobra nasce inteira e parada: valores finais,
formas prontas, nenhum laço, nenhum observador para desligar depois. */
if (reduz) { pintar(0, false); return; }
/* Sem movimento reduzido, o painel começa VAZIO de formas e com o número
em zero — atrás do [data-rv] do core, que ainda não revelou nada. */
escreverTexto(PERIODOS[0]);
escreverTotal(0);
semTransicao(function () { zerarFormas(); });
var primeira = true;
var io = new IntersectionObserver(function (entradas) {
for (var i = 0; i < entradas.length; i++) {
dentro = entradas[i].isIntersecting;
if (dentro) {
if (primeira) { primeira = false; pintar(0, true); }
ligar();
} else {
desligar();
}
}
}, { threshold: .15 });
io.observe(raiz);
}
for (var r = 0; r < raizes.length; r++) iniciar(raizes[r]);
})();