<!-- ==========================================================================
TRIO DE CARDS COM INTERFACE SIMULADA
Prefixo: trio-
Requer: core (tokens, base, motion.css, motion.js) + estilo.css e script.js
Sem lib.
A DOBRA. Cabeçalho em duas colunas — título e botão de um lado, apoio do
outro — e embaixo três cards altos, de mesma altura, cada um com um
PEDAÇO DE INTERFACE dentro. O card não descreve o recurso: mostra o
recurso funcionando, em miniatura.
AS TRÊS PEÇAS, e o que cada uma prova:
1. fila de classes de ativo que rotaciona sozinha -> "atravessa tudo"
2. foto com pílulas de imposto evitado trocando -> "a estrutura paga"
3. card claro com seletor de período e gráfico -> "você acompanha"
PAINEL CORTADO. A peça do card 1 e a foto do card 2 encostam no rodapé do
card e só têm canto arredondado EM CIMA. É o que faz a interface parecer
continuar para fora do quadro em vez de ser um adesivo colado dentro
dele. Quem corta é o overflow:hidden do próprio card.
CARD CLARO EM DOBRA ESCURA. O terceiro card declara .sec-light nele
mesmo. Sem isso, o texto de dentro leria o --fg da seção — que ali é
claro — e sumiria no fundo areia. A classe de escopo troca --fg, --fg-2 e
--fg-muted para o par escuro, e nenhuma regra do componente precisa
escrever cor de texto (contrato §3).
ESTADO INICIAL NO MARKUP. Cada peça já nasce no primeiro estado: o
primeiro método com .is-on, as duas primeiras pílulas com .is-on e
.is-next, o primeiro período com .is-on e aria-pressed. Assim a dobra
está correta antes do script rodar — e continua correta se ele não
rodar, seja por movimento reduzido, seja porque alguém colou só o HTML.
ENTRADA. data-rv + data-d do core, um card por vez. Nada aqui
reimplementa reveal.
========================================================================== -->
<sectionclass="trio sec-dark sec-pad"data-trio>
<divclass="wrap">
<!-- ---------------------------------------------------------------------
CABEÇALHO EM DUAS COLUNAS
Título e ação à esquerda, apoio à direita, os dois alinhados pelo
topo. Não é o .sec-head do core: aquele é centralizado e de coluna
única, e aqui a assimetria é o desenho.
-------------------------------------------------------------------- -->
<divclass="trio-head">
<divclass="trio-head-col">
<h2class="sec-title trio-title ttl">Uma estrutura montada antes da primeira conversa</h2>
<aclass="btn btn-primary trio-cta"href="#"data-rvdata-d="1">Agende seu diagnóstico</a>
</div>
<pclass="trio-lead"data-rvdata-d="1">
Carteira, proteção, relatório e sucessão no mesmo lugar. Você decide o
rumo — a estrutura técnica fica por nossa conta.
</p>
</div>
<divclass="trio-grid">
<!-- =================================================================
CARD 1 — a carteira que atravessa instituições
O fundo é a primitiva .glass do core: o brilho radial vindo do
canto de cima e a borda tênue são exatamente os dela. Só os
parafusos mudam, no estilo.css.
================================================================= -->
<articleclass="glass trio-card trio-card-a"data-rv>
<divclass="trio-copy">
<h3class="trio-card-title">Uma carteira que atravessa instituições</h3>
<pclass="trio-card-text">
Cada classe de ativo entra na carteira pela instituição que paga melhor
por ela, sem vínculo e sem taxa escondida.
</p>
</div>
<!-- Painel ancorado no rodapé do card. A fila de dentro tem NOVE
itens e a janela mostra três e meio: o corte embaixo é o que
sugere que a lista continua. O script duplica os primeiros
itens para o laço fechar sem buraco — as cópias saem marcadas
com aria-hidden, para o leitor de tela ouvir nove classes de
ativo e não treze. -->
<divclass="trio-panel">
<ulclass="trio-methods"role="list"data-trio-methodsaria-label="Classes de ativo cobertas">
<liclass="trio-method is-on">
<spanclass="trio-ico"aria-hidden="true">
<svgviewBox="0 0 24 24"fill="none"stroke="currentColor"stroke-width="1.6"stroke-linecap="round"stroke-linejoin="round"><rectx="2.5"y="5.5"width="19"height="13"rx="2.5"/><pathd="M2.5 10h19"/></svg>
</span>
<spanclass="trio-method-name">Renda fixa</span>
</li>
<liclass="trio-method">
<spanclass="trio-ico"aria-hidden="true">
<svgviewBox="0 0 24 24"fill="none"stroke="currentColor"stroke-width="1.6"stroke-linecap="round"stroke-linejoin="round"><rectx="2.5"y="5.5"width="19"height="13"rx="2.5"/><pathd="M2.5 10h19"/><pathd="M6 14.5h4"/></svg>
</span>
<spanclass="trio-method-name">Crédito privado</span>
</li>
<liclass="trio-method">
<spanclass="trio-ico"aria-hidden="true">
<svgviewBox="0 0 24 24"fill="none"stroke="currentColor"stroke-width="1.6"stroke-linecap="round"stroke-linejoin="round"><pathd="M13 2.6 5.6 13.4h5.2l-.8 8 7.6-11h-5z"/></svg>
</span>
<spanclass="trio-method-name">Fundos multimercado</span>
</li>
<liclass="trio-method">
<spanclass="trio-ico"aria-hidden="true">
<svgviewBox="0 0 24 24"fill="none"stroke="currentColor"stroke-width="1.6"stroke-linecap="round"stroke-linejoin="round"><circlecx="12"cy="12"r="8.6"/><pathd="M12 7.4V12l3.1 2"/></svg>
</span>
<spanclass="trio-method-name">Previdência privada</span>
</li>
<liclass="trio-method">
<spanclass="trio-ico"aria-hidden="true">
<svgviewBox="0 0 24 24"fill="none"stroke="currentColor"stroke-width="1.6"stroke-linecap="round"stroke-linejoin="round"><pathd="M4 6.4v11.2M7.2 6.4v11.2M10.4 6.4v11.2M13.6 6.4v11.2M16.8 6.4v11.2M20 6.4v11.2"/></svg>
</span>
<spanclass="trio-method-name">Fundos imobiliários</span>
</li>
<liclass="trio-method">
<spanclass="trio-ico"aria-hidden="true">
<svgviewBox="0 0 24 24"fill="none"stroke="currentColor"stroke-width="1.6"stroke-linecap="round"stroke-linejoin="round"><rectx="2.6"y="6"width="18.8"height="12.5"rx="2.6"/><pathd="M2.6 9.6h12M16.6 12.3h2.6"/></svg>
</span>
<spanclass="trio-method-name">Ações e ETFs</span>
</li>
<liclass="trio-method">
<spanclass="trio-ico"aria-hidden="true">
<svgviewBox="0 0 24 24"fill="none"stroke="currentColor"stroke-width="1.6"stroke-linecap="round"stroke-linejoin="round"><circlecx="12"cy="12"r="8.6"/><circlecx="12"cy="12"r="3.6"/></svg>
</span>
<spanclass="trio-method-name">Estrutura offshore</span>
</li>
<liclass="trio-method">
<spanclass="trio-ico"aria-hidden="true">
<svgviewBox="0 0 24 24"fill="none"stroke="currentColor"stroke-width="1.6"stroke-linecap="round"stroke-linejoin="round"><rectx="3.6"y="9.2"width="16.8"height="10.8"rx="2"/><pathd="M2.6 9.2h18.8M12 9.2V20M12 9.2S10.6 4.2 8.2 4.2a2.3 2.3 0 0 0 0 5m3.8 0s1.4-5 3.8-5a2.3 2.3 0 0 1 0 5"/></svg>
</span>
<spanclass="trio-method-name">Câmbio e proteção</span>
</li>
<liclass="trio-method">
<spanclass="trio-ico"aria-hidden="true">
<svgviewBox="0 0 24 24"fill="none"stroke="currentColor"stroke-width="1.6"stroke-linecap="round"stroke-linejoin="round"><pathd="M12 3.6 20.4 9H3.6zM5.4 9v8.4M9.8 9v8.4M14.2 9v8.4M18.6 9v8.4M3.2 19.4h17.6"/></svg>
</span>
<spanclass="trio-method-name">Private equity</span>
</li>
</ul>
</div>
</article>
<!-- =================================================================
CARD 2 — as camadas que protegem o patrimônio
Foto encostada no rodapé e, por cima dela, um rotador vertical de
três pílulas. Duas ficam à vista: a de cima é a destacada, a de
baixo é a próxima. A terceira espera fora, transparente.
================================================================= -->
<articleclass="trio-card trio-card-b"data-rvdata-d="1">
<divclass="trio-copy">
<h3class="trio-card-title">Camadas que protegem o patrimônio</h3>
<pclass="trio-card-text">
Uma holding para organizar, um seguro para blindar e uma reserva
para não vender ativo na hora errada.
</p>
<ulclass="trio-chips"role="list">
<liclass="trio-chip">Holding</li>
<liclass="trio-chip">Seguro</li>
<liclass="trio-chip">Reserva</li>
</ul>
</div>
<!-- A foto é ilustração de produto, não conteúdo: alt vazio. O
rotador inteiro sai do alcance do leitor de tela — três valores
ilustrativos de imposto evitado lidos em sequência não informam
nada, e o que a peça diz já está no título e no parágrafo
acima. Conteúdo ilustrativo: nenhum destes números é apurado. -->
<divclass="trio-shot">
<imgclass="trio-shot-img"src="../../../showroom/demo/ph-5.webp"alt=""aria-hidden="true"loading="lazy"decoding="async">
<divclass="trio-offers"data-trio-offersaria-hidden="true">
<spanclass="trio-offer is-on">
<spanclass="trio-offer-dot">
<svgviewBox="0 0 12 12"fill="none"stroke="currentColor"stroke-width="1.8"stroke-linecap="round"stroke-linejoin="round"><pathd="M2.6 6.3 4.9 8.6 9.4 3.7"/></svg>
</span>
Imposto evitado: <bclass="trio-offer-val">R$ 340 mil</b>
</span>
<spanclass="trio-offer is-next">
<spanclass="trio-offer-dot">
<svgviewBox="0 0 12 12"fill="none"stroke="currentColor"stroke-width="1.8"stroke-linecap="round"stroke-linejoin="round"><pathd="M2.6 6.3 4.9 8.6 9.4 3.7"/></svg>
</span>
Imposto evitado: <bclass="trio-offer-val">R$ 96 mil</b>
</span>
<spanclass="trio-offer">
<spanclass="trio-offer-dot">
<svgviewBox="0 0 12 12"fill="none"stroke="currentColor"stroke-width="1.8"stroke-linecap="round"stroke-linejoin="round"><pathd="M2.6 6.3 4.9 8.6 9.4 3.7"/></svg>
</span>
Imposto evitado: <bclass="trio-offer-val">R$ 18,4 mi</b>
</span>
</div>
</div>
</article>
<!-- =================================================================
CARD 3 — o relatório consolidado
Card CLARO dentro de dobra escura: o .sec-light aqui é o que dá o
par de tinta escura para o que está dentro. O fundo areia vem de
--accent-2 do tema, no estilo.css.
As três alturas de cada barra viajam no style da própria barra
(--trio-h-a/b/c, em porcentagem da altura do gráfico). Trocar de
período é trocar UMA classe no gráfico: o CSS reescolhe a altura
e a transição faz a barra crescer. O script não escreve altura
nenhuma — e por isso o card já nasce certo no período 7d, mesmo
sem script.
================================================================= -->
<articleclass="trio-card trio-card-c sec-light"data-rvdata-d="2">
<divclass="trio-card-head">
<h3class="trio-card-title trio-card-title-c">Seu patrimônio consolidado</h3>
<divclass="trio-track"role="group"aria-label="Janela do relatório">
<buttonclass="trio-per is-on"type="button"aria-pressed="true"data-trio-per>7d</button>
<buttonclass="trio-per"type="button"aria-pressed="false"data-trio-per>30d</button>
<buttonclass="trio-per"type="button"aria-pressed="false"data-trio-per>12m</button>
</div>
</div>
<divclass="trio-chart"data-trio-chartaria-hidden="true">
<spanclass="trio-bar"style="--trio-h-a:33%;--trio-h-b:21%;--trio-h-c:18%"><spanclass="trio-bar-fill"></span></span>
<spanclass="trio-bar"style="--trio-h-a:16%;--trio-h-b:44%;--trio-h-c:34%"><spanclass="trio-bar-fill"></span></span>
<spanclass="trio-bar"style="--trio-h-a:72%;--trio-h-b:30%;--trio-h-c:27%"><spanclass="trio-bar-fill"></span></span>
<spanclass="trio-bar"style="--trio-h-a:36%;--trio-h-b:62%;--trio-h-c:52%"><spanclass="trio-bar-fill"></span></span>
<spanclass="trio-bar"style="--trio-h-a:88%;--trio-h-b:47%;--trio-h-c:61%"><spanclass="trio-bar-fill"></span></span>
<spanclass="trio-bar"style="--trio-h-a:22%;--trio-h-b:88%;--trio-h-c:88%"><spanclass="trio-bar-fill"></span></span>
</div>
<!-- Os três números ficam empilhados na mesma célula de grade: o bloco
tem a altura do mais alto e nada salta na troca. O que não está em
cena leva visibility:hidden, e some também do leitor de tela — sem
isso, ele leria os três valores em sequência.
OS TRÊS SÃO DE ESTOQUE E DE ESTRUTURA — patrimônio, distribuição,
liquidez. NENHUM é de resultado, e isso não é escolha de redação:
é a mesma regra que o irmão conteudo/lista-beneficios-palco
declara na ficha dele com estas palavras — "rentabilidade, ganho,
comparação com índice: nada disso entra". As duas peças são
interface simulada na mesma voz, e a regra é a mesma nas duas.
Até 2026-09-08 este card mostrava "+R$ 620 mil de resultado no
trimestre" e "14,6% de retorno em 12 meses". Números ILUSTRATIVOS
em qualquer caso — trocar antes de publicar. -->
<divclass="trio-figs"data-trio-figs>
<pclass="trio-fig is-on">
<bclass="trio-fig-val">R$ 18,4 mi</b>
<spanclass="trio-fig-lab">sob assessoria, 4 instituições</span>
</p>
<pclass="trio-fig">
<bclass="trio-fig-val">R$ 3,1 mi</b>
<spanclass="trio-fig-lab">com liquidez em até 30 dias</span>
</p>
<pclass="trio-fig">
<bclass="trio-fig-val">5 classes</b>
<spanclass="trio-fig-lab">de ativo na carteira consolidada</span>
</p>
</div>
</article>
</div>
</div>
</section>
estilo.css · 411 linhas · 15,9 KB
/* ==========================================================================
TRIO DE CARDS COM INTERFACE SIMULADA — prefixo trio-
Cabeçalho em duas colunas e três cards altos, cada um com uma peça de
interface funcionando dentro.
Tokens usados:
--display família dos títulos e dos números
--ease curva única, em toda transição daqui
--fg --fg-2 --fg-muted tinta, herdada do escopo de tom de cada card
--accent cor de ação (bolinha de confirmação, hachura)
--accent-2 superfície areia do card claro
--dark --dark-2 --light --light-2 as superfícies do projeto
--radius-media canto do painel e da foto (o card usa 1.5x isto)
--radius-pill canto de pílula e de botão de período
--sec-gap respiro vertical, via .sec-pad do core
TRÊS SUPERFÍCIES, TRÊS CAMINHOS. O card 1 é a primitiva .glass do core
com os parafusos dela trocados; o card 2 é um dark um passo mais claro,
feito por color-mix sobre --dark-2; o card 3 é o areia de --accent-2 e
declara .sec-light nele mesmo, para a tinta de dentro virar escura.
Nenhum dos três escreve cor crua e nenhum sobrescreve --fg.
PORQUÊ O .glass NO CARD 1. A medição da referência descreve o fundo dele
como cor sólida mais um radial-gradient a partir do topo — que é, linha
por linha, o desenho da primitiva. Recopiar seria a terceira cópia da
mesma regra, exatamente o que o §6 do contrato manda promover em vez de
repetir. O que muda vem pelos ganchos que a própria primitiva expõe:
--glass-bg vira a segunda superfície escura do tema (a referência mede
#161616, que é o --dark-2 da Kirvano) e --glass-blur vai a zero, porque
sobre um fundo opaco não há o que desfocar atrás.
O JS não escreve estilo de aparência: ele troca classe. Altura de barra,
posição de pílula e destaque de linha estão todos aqui.
========================================================================== */.trio{
/* Altura dos cards. É medida e não proporção: as três peças de interface
de dentro têm altura fixa em px, e um card em aspect-ratio faria a peça
descolar do rodapé em telas estreitas. */--trio-card-h:520px;
/* Canto do card. A referência mede 24 num projeto de canto 16 — o card de
dobra é uma vez e meia o canto de mídia do tema. Projeto flat
(--radius-media:0) devolve 0 aqui, e os cards ficam retos, que é o
comportamento certo: quem decide a forma é o tema. */--trio-radius:calc(var(--radius-media) * 1.5);
--trio-gap:30px;
--trio-panel-h:240px;
--trio-row-h:56px;
--trio-row-gap:8px;
--trio-shot-h:240px;
--trio-chart-h:170px;
/* A terceira superfície escura do conjunto: um passo acima de --dark-2,
ainda dentro da regra do tom vizinho do §3. Serve ao card 2 e ao botão
de período aceso do card 3 — dois lugares, uma fonte só. */--trio-surface-2:color-mix(in srgb,var(--light) 7%,var(--dark-2));
/* Os três tempos, em SEGUNDOS, lidos pelo script com parseFloat. Ficam
aqui, e não no JS, porque são ajuste de ritmo da dobra e não lógica:
quem acha a fila de pagamentos apressada mexe numa linha de CSS.
Valor em ms quebraria a conta — a unidade é parte do contrato. */--trio-metodo-hold:1.9s;
--trio-oferta-hold:2.5s;
--trio-periodo-hold:4.26s;
}
/* ==========================================================================
CABEÇALHO EM DUAS COLUNAS
Não é o .sec-head do core: aquele é centralizado e de coluna única, e o
que dá caráter a esta dobra é justamente a assimetria — título e ação
pesando à esquerda, apoio leve à direita, os dois alinhados pelo topo.
========================================================================== */.trio-head{
display:flex;flex-wrap:wrap;
align-items:flex-start;justify-content:space-between;
gap:clamp(28px,4.4vw,64px);
}
/* 480 e 520 são as duas caixas medidas na referência. flex-basis e não
width: com o gap, as duas cabem na coluna de 1136 e encolhem juntas antes
de quebrar. */.trio-head-col{flex:01480px}
.trio-lead{
flex:01520px;
font-size:clamp(16px,1.25vw,18px);line-height:1.55;
color:var(--fg-2);
}
/* Dois ajustes no h2.sec-title do core, e nenhuma regra dele reescrita:
sem o olho por cima, o margin-top de 16 desalinharia o título do
parágrafo vizinho; e o teto de 22ch é estreito demais para a caixa de
480 desta dobra. Dois seletores de classe vencem o "h2.sec-title" do
core sem precisar de peso artificial. */.trio-head .trio-title{margin-top:0;max-width:none}
.trio-cta{margin-top:32px}
/* ==========================================================================
A GRADE
========================================================================== */.trio-grid{
display:grid;grid-template-columns:repeat(3,1fr);
gap:var(--trio-gap);
margin-top:clamp(40px,4.6vw,64px);
}
/* isolation:isolate dá contexto de empilhamento próprio a cada card: as
pílulas flutuantes do card 2 não têm como passar por cima do vizinho.
O overflow:hidden é o que CORTA a peça de interface no rodapé — é ele
quem arredonda o canto de baixo do painel e da foto. */.trio-card{
position:relative;display:flex;flex-direction:column;
min-height:var(--trio-card-h);
border-radius:var(--trio-radius);
overflow:hidden;isolation:isolate;
}
/* Os dois cards de topo têm o mesmo respiro de cabeça; a peça de baixo é
empurrada pelo margin-top:auto de cada uma. O card 3 tem outro desenho e
traz o próprio padding. */.trio-card-a,.trio-card-b{padding-top:48px}
.trio-copy{padding-inline:24px;text-align:center}
.trio-card-title{
font-family:var(--display);font-weight:500;
font-size:clamp(21px,2vw,30px);line-height:1.25;letter-spacing:-.02em;
color:var(--fg);
}
.trio-card-text{
margin-top:24px;
font-size:16px;line-height:1.5;
color:var(--fg-muted);
}
/* ==========================================================================
CARD 1 — fila de meios de pagamento
========================================================================== *//* Os parafusos da primitiva .glass. Ver o cabeçalho deste arquivo para o
porquê de cada um. */.trio-card-a{
--glass-bg:var(--dark-2);
--glass-line:color-mix(in srgb,var(--light) 6%,transparent);
--glass-blur:0px;
}
/* margin:auto 20px 0 — o auto em cima é o que ancora o painel no rodapé do
card, sem posicionamento absoluto: assim, se o parágrafo crescer, o card
cresce junto e o texto nunca corre por baixo do painel.
A borda de baixo sai: ela cairia exatamente sobre a aresta do card e
apareceria como um risco de 1px atravessando o canto arredondado. */.trio-panel{
margin:auto 20px0;
height:var(--trio-panel-h);
overflow:hidden;
padding:12px12px0;
border:1px solid color-mix(in srgb,var(--light) 5%,transparent);
border-bottom:0;
border-radius:var(--radius-media) var(--radius-media) 00;
background:color-mix(in srgb,var(--light) 5%,transparent);
}
/* A fila inteira é UM elemento que translada. O script move em múltiplos de
(altura da linha + vão), e o painel recorta. */.trio-methods{
display:flex;flex-direction:column;gap:var(--trio-row-gap);
will-change:transform;
transition:transform .55svar(--ease);
}
/* flex-basis fixo além da altura: numa coluna flex, uma linha sem base
encolhe para caber, e a conta do passo do script deixa de fechar. */.trio-method{
display:flex;align-items:center;gap:12px;
flex:00var(--trio-row-h);height:var(--trio-row-h);
padding-inline:10px;
border-radius:12px;
background:transparent;
transition:background .45svar(--ease);
}
.trio-method.is-on{background:color-mix(in srgb,var(--light) 5%,transparent)}
.trio-ico{
flex:0036px;width:36px;height:36px;
display:flex;align-items:center;justify-content:center;
border-radius:8px;
border:1px solid color-mix(in srgb,var(--light) 10%,transparent);
background:color-mix(in srgb,var(--light) 6%,transparent);
color:var(--fg-2);
}
.trio-ico svg{width:18px;height:18px}
.trio-method-name{font-size:14px;line-height:1.4;color:var(--fg-2)}
/* ==========================================================================
CARD 2 — foto no rodapé e pílulas de comissão girando
========================================================================== */.trio-card-b{background:var(--trio-surface-2)}
.trio-chips{
display:flex;flex-wrap:wrap;justify-content:center;gap:8px;
margin-top:16px;
}
.trio-chip{
height:24px;padding-inline:10px;
font-size:12px;line-height:24px;
border-radius:6px;
color:var(--fg-2);
background:color-mix(in srgb,var(--light) 10%,transparent);
}
/* A foto não recorta a si mesma por overflow: o canto vive na própria
imagem. É o que deixa as pílulas vazarem para fora da largura dela
quando o valor é longo — quem corta continua sendo o card. */.trio-shot{
position:relative;
margin-top:auto;align-self:center;
width:64%;
}
.trio-shot-img{
width:100%;height:var(--trio-shot-h);
object-fit:cover;
border-radius:var(--radius-media) var(--radius-media) 00;
}
/* O rotador é uma célula de grade só, com as três pílulas empilhadas nela.
Assim a caixa tem a largura da mais larga, as três nascem centradas e o
deslocamento de cada uma é só o próprio translateY — sem left/top e sem
uma conta de posição por pílula. */.trio-offers{
position:absolute;top:34px;left:50%;
transform:translateX(-50%);
display:grid;justify-items:center;
}
/* Três estados, um por posição do rotador:
.is-on em cima, destacada
.is-next logo abaixo, a próxima da fila
nenhuma fora de cena, mais abaixo e transparente
O ciclo é on -> fora -> next -> on. Quem sai DESCE e apaga; quem entra
sobe da posição de fora, acende no meio do caminho e assume o destaque.
A cor do texto é --dark porque a pílula é uma superfície clara sobre foto,
e não texto de seção: seguir --fg aqui a deixaria ilegível em dobra
escura. Mesmo caso do .pil-title do conteudo/pilares-texto-na-foto. */.trio-offer{
grid-area:1/1;
display:inline-flex;align-items:center;gap:6px;
height:32px;padding:014px06px;
border-radius:var(--radius-pill);
white-space:nowrap;letter-spacing:normal;
font-size:12px;font-weight:500;line-height:1;
color:var(--dark);
background:color-mix(in srgb,var(--light) 95%,transparent);
opacity:0;transform:translateY(96px);
transition:opacity .6svar(--ease),transform .6svar(--ease);
}
.trio-offer.is-next{opacity:1;transform:translateY(48px)}
.trio-offer.is-on{opacity:1;transform:translateY(0)}
.trio-offer-val{font-weight:600}
.trio-offer-dot{
flex:0020px;width:20px;height:20px;
display:flex;align-items:center;justify-content:center;
border-radius:50%;
background:var(--accent);color:var(--dark);
}
.trio-offer-dot svg{width:11px;height:11px}
/* ==========================================================================
CARD 3 — painel claro de vendas
O fundo areia é --accent-2, o apoio de marca do tema. A tinta escura vem
do .sec-light declarado no próprio card, no markup: aqui não se escreve
cor de texto nenhuma, e por isso o card acompanha qualquer tema.
========================================================================== */.trio-card-c{background:var(--accent-2);padding:24px}
.trio-card-head{
display:flex;flex-wrap:wrap;
align-items:flex-start;justify-content:space-between;
gap:12px;
}
.trio-card-title-c{flex:11140px}
.trio-track{
flex:00 auto;
display:flex;gap:2px;padding:4px;
border-radius:var(--radius-pill);
background:color-mix(in srgb,var(--dark) 6%,transparent);
}
.trio-per{
height:24px;padding-inline:11px;
border:0;border-radius:var(--radius-pill);
background:transparent;color:var(--fg-2);
font-size:12px;font-weight:500;line-height:1;letter-spacing:normal;
transition:background .4svar(--ease),color .4svar(--ease);
}
.trio-per.is-on{background:var(--trio-surface-2);color:var(--light-2)}
/* margin-top:auto empurra o gráfico para o pé do card e deixa o vão morto no
meio — que é o desenho da referência, não sobra: o número embaixo precisa
de silêncio em volta para pesar. */.trio-chart{
margin-top:auto;
height:var(--trio-chart-h);
display:flex;align-items:flex-end;gap:8px;
}
/* A altura de cada barra em cada período viaja no style da própria barra,
como --trio-h-a/b/c. Trocar de período é trocar UMA classe no gráfico: o
CSS reescolhe qual das três ler, e a transição faz a barra crescer. O
script não escreve altura — e sem script o cartão fica no período 7d,
correto e legível. */.trio-bar{
position:relative;flex:110;
height:var(--trio-h-a,40%);
border-radius:8px;
background:color-mix(in srgb,var(--dark) 88%,transparent);
transition:height .7svar(--ease);
}
.trio-chart.is-p1 .trio-bar{height:var(--trio-h-b,40%)}
.trio-chart.is-p2 .trio-bar{height:var(--trio-h-c,40%)}
/* As seis barras não crescem juntas: um atraso curto por posição transforma
a troca de período em varredura da esquerda para a direita, que é o que
faz o gráfico parecer estar recalculando. */.trio-bar:nth-child(2){transition-delay:.05s}
.trio-bar:nth-child(3){transition-delay:.1s}
.trio-bar:nth-child(4){transition-delay:.15s}
.trio-bar:nth-child(5){transition-delay:.2s}
.trio-bar:nth-child(6){transition-delay:.25s}
/* Preenchimento hachurado, 3px menor em cima e embaixo — 6px no total, como
na referência. É o que impede a barra de ler como bloco chapado e dá a
ela a textura de dado projetado. */.trio-bar-fill{
position:absolute;left:0;right:0;top:3px;bottom:3px;
border-radius:6px;
background-image:repeating-linear-gradient(135deg,
color-mix(in srgb,var(--accent) 34%,transparent) 02px,
transparent 2px6px);
}
/* Os três resultados na MESMA célula de grade: a caixa fica com a altura do
mais alto e nada salta na troca. visibility, além da opacidade, para o
que está fora de cena sair também do leitor de tela — sem ela, os três
valores seriam lidos em sequência. O atraso de 0s na saída é o truque de
sempre: a visibilidade só cai depois da fusão terminar. */.trio-figs{display:grid;margin-top:28px}
.trio-fig{
grid-area:1/1;
display:flex;flex-wrap:wrap;align-items:baseline;gap:8px;
opacity:0;filter:blur(8px);visibility:hidden;
transition:opacity .5svar(--ease),filter .5svar(--ease),visibility 0s.5s;
}
.trio-fig.is-on{
opacity:1;filter:blur(0);visibility:visible;
transition:opacity .5svar(--ease),filter .5svar(--ease),visibility 0s;
}
.trio-fig-val{
font-family:var(--display);font-weight:600;letter-spacing:-.02em;
font-size:clamp(24px,2.2vw,32px);line-height:1.1;
color:var(--fg);
}
.trio-fig-lab{font-size:14px;line-height:1.4;color:var(--fg-2)}
/* ---------------------------------------------------------------- movimento reduzido *//* O script nem liga o rodízio quando a preferência está ativa — o que sobra
aqui é o caso do botão de período, que continua clicável de propósito
(botão morto na tela é defeito). Sem transição, a troca é instantânea. */@media(prefers-reduced-motion:reduce){
.trio-methods,.trio-method,.trio-offer,.trio-per,.trio-bar,.trio-fig{transition:none}
.trio-fig{filter:none}
}
/* ---------------------------------------------------------------- mobile *//* Três colunas de 359 pedem 1137 de coluna útil. Abaixo disso a grade vira
duas, e o card do gráfico ocupa a linha inteira: é o único dos três que
melhora ao ficar largo, porque o gráfico ganha respiro entre as barras. */@media(max-width:1080px){
.trio-grid{grid-template-columns:repeat(2,1fr)}
.trio-card-c{grid-column:span 2}
}
@media(max-width:900px){
.trio-head{flex-direction:column}
.trio-head-col,.trio-lead{flex:11 auto;max-width:100%}
}
@media(max-width:720px){
.trio{--trio-card-h:470px;--trio-gap:16px}
.trio-grid{grid-template-columns:1fr}
.trio-card-c{grid-column:auto}
/* Card estreito e foto em 64% viram um selo pequeno demais no meio do
quadro; em uma coluna a foto ocupa quase a largura toda. */.trio-shot{width:78%}
}
script.js · 293 linhas · 11,2 KB
/* ==========================================================================
TRIO DE CARDS COM INTERFACE SIMULADA — os três rodízios.
Três peças, três ritmos, UM laço. Cada peça devolve { espera, tique } e o
laço decide de quem é a vez a cada quadro. Três setInterval fariam a mesma
coisa, mas cada um continuaria contando com a aba em segundo plano e a
dobra fora da tela; um requestAnimationFrame só dorme com a aba e é
desligado pelo IntersectionObserver quando a dobra sai de vista.
O que cada peça faz:
1. MÉTODOS — translada a fila e marca a linha do topo com .is-on.
A fila é duplicada no início para o laço fechar sem buraco.
2. OFERTAS — gira .is-on / .is-next entre as três pílulas.
3. PERÍODO — troca a classe do gráfico, do botão e do resultado.
O script NÃO escreve aparência. Altura de barra, posição de pílula e
destaque de linha estão todos no estilo.css; aqui só se troca classe. A
única exceção é o transform da fila do card 1, que depende de uma medida
feita no navegador e não teria como morar no CSS.
TEMPOS NO CSS. Os três intervalos são custom properties da raiz do
componente (--trio-metodo-hold e companhia), em segundos. Mesmo padrão do
--apres-hold do conteudo/apresentacao-foto-alternada: ritmo é ajuste de
desenho, e ajuste de desenho mora na folha de estilo.
MOVIMENTO REDUZIDO. Nada cicla: as três peças ficam no primeiro estado, que
o markup já traz. O laço nem chega a ser criado. O que continua vivo é o
clique nos botões de período — botão que ignora o clique é defeito, não
acessibilidade — e a troca, sem transição, é instantânea (ver o
@media(prefers-reduced-motion) do estilo.css).
Sem listener de scroll: quem observa a entrada é o IntersectionObserver.
========================================================================== */
(function () {
var raizes = document.querySelectorAll('[data-trio]');
if (!raizes.length) return; // guard/* 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.9s"; parseFloat resolve o espaço e a
unidade de uma vez. O CSS documenta que o valor é em segundos — em ms a
conta sairia mil vezes maior e a peça pareceria travada. */function espera(raiz, nome, padrao) {
var v = parseFloat(getComputedStyle(raiz).getPropertyValue(nome));
return (v > 0 ? v : padrao) * 1000;
}
/* ---------------------------------------------------------- 1. métodos */function pecaMetodos(raiz, reduz) {
var trilho = raiz.querySelector('[data-trio-methods]');
if (!trilho) returnnull;
var originais = [].slice.call(trilho.children);
if (originais.length < 3) returnnull;
/* Com movimento reduzido a fila fica parada na primeira linha, e então
não há por que clonar: sai antes de tocar no DOM. */if (reduz) returnnull;
/* A fila é duplicada INTEIRA, e não só o tanto que cabe na janela do
painel. É o que torna a volta do laço independente de medida: na
posição n = 9 o que está no topo é a cópia do item 0, e a partir dali
a janela mostra cópias dos itens 0, 1, 2… qualquer que seja a altura
do painel ou da linha. Clonar "o que cabe" obrigaria a recontar as
cópias a cada mudança de tela.
As cópias saem com aria-hidden: quem usa leitor de tela ouve nove
meios de pagamento, não dezoito. */for (var c = 0; c < originais.length; c++) {
var copia = originais[c].cloneNode(true);
copia.setAttribute('aria-hidden', 'true');
trilho.appendChild(copia);
}
var itens = [].slice.call(trilho.children);
var total = originais.length;
var passo = 0;
var n = 0;
/* A distância de uma linha à seguinte, medida no navegador — inclui o
vão automaticamente. Somar altura mais gap na mão erraria toda vez que
o CSS mexesse em um dos dois. */function remedir() {
passo = itens.length > 1 ? itens[1].offsetTop - itens[0].offsetTop : 0;
trilho.style.transition = 'none';
posicionar();
void trilho.offsetHeight;
trilho.style.transition = '';
}
function posicionar() {
trilho.style.transform = 'translate3d(0,' + (-n * passo) + 'px,0)';
}
/* Acende a linha que está (ou vai ficar) no topo, e também a cópia dela:
na volta do laço quem aparece é a cópia, e é ela que precisa do
destaque. A que estiver fora da janela não custa nada. */function marcar() {
var alvo = n % total;
for (var i = 0; i < itens.length; i++) {
if (i === alvo || i === alvo + total) itens[i].classList.add('is-on');
else itens[i].classList.remove('is-on');
}
}
function tique() {
/* Chegamos ao fim da fila original: a posição n = total desenha
exatamente o mesmo que a posição 0, então dá para voltar ao começo
sem transição e sem ninguém ver. O reflow no meio é o que sela o
salto antes de a transição ser religada — sem ele o navegador junta
as duas escritas e anima a volta inteira. */if (n >= total) {
trilho.style.transition = 'none';
n = 0;
posicionar();
void trilho.offsetHeight;
trilho.style.transition = '';
}
n++;
posicionar();
marcar();
}
remedir();
return {
espera: espera(raiz, '--trio-metodo-hold', 1.9),
tique: tique,
remedir: remedir
};
}
/* ---------------------------------------------------------- 2. ofertas */function pecaOfertas(raiz, reduz) {
var caixa = raiz.querySelector('[data-trio-offers]');
if (!caixa) returnnull;
var pilulas = [].slice.call(caixa.children);
if (pilulas.length < 3 || reduz) returnnull;
var atual = 0;
/* Um passo do rotador: a próxima assume o destaque, a de trás dela vira
a próxima, e a que estava em cima fica sem classe — o estilo.css a
manda descer e apagar. */function tique() {
atual = (atual + 1) % pilulas.length;
for (var i = 0; i < pilulas.length; i++) {
pilulas[i].classList.remove('is-on');
pilulas[i].classList.remove('is-next');
}
pilulas[atual].classList.add('is-on');
pilulas[(atual + 1) % pilulas.length].classList.add('is-next');
}
return { espera: espera(raiz, '--trio-oferta-hold', 2.5), tique: tique };
}
/* ---------------------------------------------------------- 3. período *//* Esta peça é construída SEMPRE, inclusive com movimento reduzido: é ela
que liga o clique dos botões. O que o movimento reduzido desliga é o
rodízio automático, e isso quem decide é o laço lá embaixo. */function pecaPeriodo(raiz) {
var grafico = raiz.querySelector('[data-trio-chart]');
var caixaFig = raiz.querySelector('[data-trio-figs]');
var botoes = [].slice.call(raiz.querySelectorAll('[data-trio-per]'));
if (!grafico || !caixaFig || botoes.length < 2) returnnull;
var figuras = [].slice.call(caixaFig.children);
var atual = 0;
var manual = false;
function mostrar(i) {
atual = i;
for (var b = 0; b < botoes.length; b++) {
if (b === i) botoes[b].classList.add('is-on');
else botoes[b].classList.remove('is-on');
botoes[b].setAttribute('aria-pressed', b === i ? 'true' : 'false');
}
for (var f = 0; f < figuras.length; f++) {
if (f === i) figuras[f].classList.add('is-on');
else figuras[f].classList.remove('is-on');
}
/* O primeiro período é o estado sem classe: é ele que o CSS desenha
por omissão, e é por isso que o card já nasce certo sem script. */for (var g = 0; g < botoes.length; g++) grafico.classList.remove('is-p' + g);
if (i > 0) grafico.classList.add('is-p' + i);
}
for (var k = 0; k < botoes.length; k++) {
(function (i) {
botoes[i].addEventListener('click', function () {
/* A pessoa assumiu o controle: o rodízio automático não volta a
mexer no card. Um seletor que continua girando sozinho depois de
ser clicado é um seletor que ninguém consegue usar. */
manual = true;
mostrar(i);
});
})(k);
}
function tique() {
if (manual) return;
mostrar((atual + 1) % botoes.length);
}
return { espera: espera(raiz, '--trio-periodo-hold', 4.26), tique: tique };
}
/* ---------------------------------------------------------------- laço */function iniciar(raiz) {
var reduz = reduzido();
var metodos = pecaMetodos(raiz, reduz);
var ofertas = pecaOfertas(raiz, reduz);
var periodo = pecaPeriodo(raiz); /* construído sempre: liga os botões *//* Daqui para baixo é só rodízio automático. Com movimento reduzido a
dobra fica inteira e legível no primeiro estado de cada card, sem
temporizador e sem observador para desligar depois. */if (reduz) return;
var pecas = [];
if (metodos) pecas.push(metodos);
if (ofertas) pecas.push(ofertas);
if (periodo) pecas.push(periodo);
if (!pecas.length) return;
var laco = 0;
var dentro = false;
/* Cada peça guarda a marca do próprio último tique. O quadro só compara
relógios — quando não há nada vencido, custa três subtrações. */function quadro(agora) {
for (var i = 0; i < pecas.length; i++) {
var p = pecas[i];
if (!p.marca) p.marca = agora;
if (agora - p.marca >= p.espera) {
p.marca = agora;
p.tique();
}
}
laco = dentro ? requestAnimationFrame(quadro) : 0;
}
/* Zerar as marcas ao religar é o que impede a dobra de disparar os três
rodízios de uma vez quando volta à tela depois de um tempo fora. */function ligar() {
if (laco || !dentro) return;
for (var i = 0; i < pecas.length; i++) pecas[i].marca = 0;
laco = requestAnimationFrame(quadro);
}
function desligar() {
if (!laco) return;
cancelAnimationFrame(laco);
laco = 0;
}
/* Uma medida por quadro: remedir() lê layout, e fazer isso a cada evento
de resize trava a janela ao redimensionar. */var pedido = 0;
addEventListener('resize', function () {
if (pedido || !metodos) return;
pedido = requestAnimationFrame(function () {
pedido = 0;
metodos.remedir();
});
}, { passive: true });
/* Limiar baixo de propósito: os rodízios têm de já estar rodando quando
a dobra termina de subir, não começar depois que ela parou. */var io = new IntersectionObserver(function (entradas) {
for (var i = 0; i < entradas.length; i++) {
dentro = entradas[i].isIntersecting;
if (dentro) ligar(); else desligar();
}
}, { threshold: .12 });
io.observe(raiz);
}
for (var r = 0; r < raizes.length; r++) iniciar(raizes[r]);
})();
Um cabeçalho assimétrico — título e botão pesando à esquerda, um parágrafo de apoio à direita — e embaixo três cards altos de mesma altura. O que faz a dobra não é a grade: é o que está dentro de cada card. Um traz uma fila de classes de ativo que sobe sozinha, um item a cada dois segundos, cortada pelo rodapé do quadro como se a lista continuasse para fora dele. Outro traz uma foto encostada no pé do card e, flutuando por cima, pílulas de valor que se revezam. O terceiro é um card claro dentro da dobra escura, com seletor de período e um gráfico de seis barras que se recalcula a cada troca.
Resolve a dobra de "o que a casa faz" sem lista de ícone e frase, que é o formato padrão e o que ninguém lê. A diferença é de prova: um card que afirma ser multicustódia pede confiança; um card que mostra nove classes de ativo passando, uma por vez, entrega a mesma informação já demonstrada. É caro de montar e por isso não serve para qualquer recurso — serve para os dois ou três que decidem a conversa.
Quando usar
Página de assessoria, de produto ou de plataforma, logo depois do hero, quando existem exatamente três frentes que carregam o argumento.
Quando cada um desses três tem uma interface reconhecível: uma lista, um valor, um gráfico, um estado. Recurso que não tem tela não tem o que mostrar aqui.
Quando o texto de cada card cabe em duas linhas de título e três de apoio. A peça de interface ocupa a metade de baixo do card e não abre mão dela.
Quando não usar
Mais de três recursos, ou recursos de pesos diferentes. Com quatro a grade quebra em duas linhas e o desenho perde a cadência; com um forte e dois fracos, os dois fracos denunciam o forte.
Quando a interface do produto ainda não existe. Simular uma tela que não foi desenhada é comprar dívida: a dobra vira a especificação, e alguém vai ter que construir aquilo.
Como grade de números ou indicadores. O gráfico do card 3 é ilustração de produto, não visualização de dado — para dado de verdade, prova/stats-contador-regua.
Padrão editorial
Slot
Regra
Título da dobra
Uma promessa, não uma categoria. Duas linhas na caixa de 480. Sem <br>: aqui a quebra automática está certa.
Apoio
Três linhas. Enumera o que os três cards vão mostrar, na mesma ordem em que eles aparecem.
Botão
Uma ação só, a mesma do hero. Se a dobra pedir uma segunda ação, ela não é desta dobra.
Título do card
Duas linhas, no máximo trinta caracteres. É a frase que a peça de interface embaixo vai provar.
Apoio do card
Três linhas, até cem caracteres. Diz o que a peça não consegue mostrar sozinha: preço, condição, ausência de plugin.
Itens da fila (card 1)
Sete a doze. Menos que sete e o rodízio se repete rápido demais; mais que doze e ninguém chega ao fim. Rótulo curto, de uma linha ("Renda fixa", "Estrutura offshore"), e um ícone desenhado — nunca a marca de terceiro.
Pílulas (card 2)
Exatamente três valores. Ordem irregular de propósito: três valores crescentes leem como gráfico, e a peça não é um gráfico.
Períodos (card 3)
Três, do mais curto ao mais longo. O valor de cada um cresce junto, e o rótulo diz o recorte de tempo por extenso.
O que mantém a dobra coesa é o paralelismo dos três títulos: mesmo comprimento, mesma forma de frase, mesmo tempo verbal. Se um deles for uma pergunta e os outros dois afirmações, a grade desmancha antes de o olho chegar às peças de interface.
Compliance
Nenhum número desta peça é de resultado. Os três valores do card de
relatório são de estoque e de estrutura — patrimônio sob assessoria,
parcela com liquidez, número de classes na carteira. Rentabilidade, ganho no
período e comparação com índice não entram, e a regra não é de redação: é a
mesma que o irmão
conteudo/lista-beneficios-palco declara
com estas palavras. As duas peças são interface simulada na mesma voz de
assessoria, e uma tela inventada que mostra rentabilidade é a forma mais
convincente de insinuar retorno — justamente porque parece um extrato.
Até 2026-09-08 este card mostrava "+R$ 620 mil de resultado no trimestre" e
"14,6% de retorno em 12 meses". A divergência só apareceu quando os dois
componentes foram descritos no mesmo vocabulário editorial, lado a lado.
Antes de publicar: trocar os três números por dados do cliente, mantendo a
natureza (estoque, não resultado), e conferir com a área técnica dele. Os
valores do markup são ilustrativos e estão marcados como tal em comentário.
Especificação dos assets
O único asset real é a foto do card 2. Ícones e gráfico são SVG e CSS, desenhados aqui.
Formato e dimensão mínima: retrato, 900×1200, WebP. O recorte final é 230×240 — quase quadrado.
Peso alvo: até 140 KB. É uma foto só, mas ela carrega junto com o resto da dobra.
Composição / enquadramento: assunto na metade inferior do quadro. As pílulas flutuam sobre os primeiros ~115px dos 240 do recorte — que, com o object-fit:cover de um retrato 900×1200, correspondem a mais ou menos 22% a 48% da altura do arquivo original. Rosto ali fica coberto.
O que não pode ter: texto embutido, interface de outro produto, marca de terceiro, logotipo de instituição financeira. Os ícones da fila do card 1 são desenhados a mão em SVG genérico exatamente por isso — logo de banco ou de corretora é marca registrada e não atravessa (contrato §11).
O card 3 precisa da classe de escopo sec-lightnele mesmo, junto de trio-card-c. Sem ela, o texto de dentro lê o --fg da seção — que numa dobra escura é claro — e some no fundo areia. Nenhuma regra do componente escreve cor de texto: quem faz o par é o escopo.
Ganchos
Gancho
Papel
[data-trio]
Raiz. O script parte daqui e nunca do documento — a dobra pode repetir na página.
[data-trio-methods]
A fila do card 1. É este elemento que o script translada.
[data-trio-offers]
O rotador de pílulas do card 2.
[data-trio-chart]
O gráfico do card 3. Recebe .is-p1 / .is-p2 conforme o período.
[data-trio-figs]
A caixa dos três resultados do card 3.
[data-trio-per]
Cada botão de período. O script liga o clique de cada um.
--trio-card-h
Altura dos cards. 520px no padrão.
--trio-radius
Canto do card. calc(var(--radius-media) * 1.5) — 24 num tema de canto 16, 0 num tema flat.
--trio-gap
Vão da grade. 30px.
--trio-panel-h · --trio-row-h · --trio-row-gap
Painel e linhas do card 1. 240px, 56px, 8px.
--trio-shot-h
Altura da foto do card 2. 240px.
--trio-chart-h
Altura do gráfico do card 3. 170px.
--trio-surface-2
A terceira superfície escura: fundo do card 2 e do botão de período aceso.
--trio-metodo-hold
Intervalo do rodízio da fila, em segundos. 1.9s.
--trio-oferta-hold
Intervalo do rotador de pílulas, em segundos. 2.5s.
--trio-periodo-hold
Intervalo da troca de período, em segundos. 4.26s.
--trio-h-a · --trio-h-b · --trio-h-c
No style de cada barra: a altura dela em cada um dos três períodos, em porcentagem do gráfico.
.is-on
Linha acesa da fila, pílula em destaque, botão de período ativo, resultado em cena.
.is-next
A pílula que está logo abaixo da destacada, esperando a vez.
.is-p1 · .is-p2
No gráfico: qual dos três conjuntos de altura ler. O primeiro período é o estado sem classe.
Variações
dois cards — apague um <article> e troque repeat(3,1fr) por repeat(2,1fr). As peças são independentes; o script só não encontra o gancho que sumiu.
em dobra clara — .sec-light na seção. Cabeçalho e textos seguem sozinhos, mas as superfícies dos cards 1 e 2 são branco sobre escuro e sumiriam: repense --glass-bg e --trio-surface-2 no tema antes.
sem rodízio — não carregue o script.js. Os três cards ficam no primeiro estado, que o markup já traz; nesse caso troque os <button> do card 3 por <span>, para não deixar botão morto na tela.
período manual — --trio-periodo-hold:60s e o card 3 vira um seletor comum, que só troca quando alguém clica.
Gotchas
Os três intervalos estão no CSS, não no JS. São lidos com getPropertyValue na inicialização, o que traz duas consequências: o valor tem de estar em segundos (1.9s; um 1900ms no tema faria a fila trocar de item a cada meia hora), e mexer na variável depois que a página carregou não tem efeito — o script leu uma vez.
O card 1 usa a primitiva .glass com o desfoque zerado. Parece contradição e não é. Da primitiva se quer o brilho radial vindo do topo e a borda tênue — que é exatamente o que a referência tem, e que seria a terceira cópia da mesma regra se fosse reescrita aqui. Já o --glass-bg vira --dark-2, uma superfície opaca, e sobre superfície opaca não existe fundo para desfocar: --glass-blur:0px só evita pagar por uma camada de composição que ninguém vê. Trocar --glass-bg de volta para o translúcido padrão faz o card ficar refém do tom da seção atrás dele.
A altura das barras mora no markup, não no script. Cada barra carrega os três valores como --trio-h-a/b/c no próprio style, e trocar de período é trocar uma classe no gráfico. É o que faz o card 3 nascer correto no período 7d mesmo sem JavaScript, e é o que permite editar um número sem abrir o script.
O botão de período continua clicável com movimento reduzido. O rodízio automático não liga, mas o clique liga — botão que ignora o clique é defeito, não acessibilidade. A troca acontece sem transição, porque o @media(prefers-reduced-motion) do estilo desliga as transições da dobra inteira.
Clicar num período desliga o rodízio para sempre. De propósito: um seletor que volta a girar sozinho três segundos depois de ser clicado é um seletor que ninguém consegue usar. Não há como religar sem recarregar a página, e não deveria haver.
A fila do card 1 é duplicada inteira, e não só o tanto que cabe. Clonar "o que cabe na janela" obrigaria a recontar as cópias toda vez que a altura do painel ou da linha mudasse. Com a fila inteira duplicada, a volta do laço é uma identidade geométrica: a posição n = 9 desenha exatamente o mesmo que a posição 0, e o script salta de uma para a outra sem transição e sem ninguém ver. As cópias saem com aria-hidden — o leitor de tela ouve nove classes de ativo, não dezoito.
O painel do card 1 não tem borda embaixo. A aresta dele coincide com a aresta do card, e a borda apareceria como um risco de 1px atravessando o canto arredondado.
Os três resultados do card 3 levam visibility, além de opacidade. Sem ela, o leitor de tela leria os três valores em sequência — "R$ 18,4 mi, +R$ 620 mil, 14,6%" — como se fossem uma lista. O atraso de 0s na saída é o que segura a visibilidade até a fusão terminar.
A dobra parada fora da tela é de propósito. O IntersectionObserver desliga o laço quando a seção sai de vista, e ao religar zera as marcas de tempo: quem rola de volta reencontra as peças onde deixou, e não os três rodízios disparando ao mesmo tempo por causa do tempo acumulado.
min-height, e não height, nos cards. As peças de interface são ancoradas por margin-top:auto e não por posicionamento absoluto — assim, se um parágrafo crescer, o card cresce junto e o texto nunca corre por baixo do painel. O preço é que os três cards ficam sempre da altura do mais alto, o que aqui é o comportamento desejado.
Histórico
Projeto
Onde
Data
Diferenças
referência: Kirvano
kirvano.com — dobra "Tudo o que você precisa para escalar"
2026-09-01
Referência visual, não origem de código. Reescrito do zero (contrato §11).
O que foi medido na referência (navegador, 1440×900): seção com padding:120px 0 21px e coluna interna de 1136; cabeçalho em flexspace-between com gap:64, caixa esquerda de 480 (título de duas linhas + 32 + CTA de 199×56) e parágrafo direito de 520 a 18px; grade de três colunas de 359 com gap:30, cards de 520 de altura e raio 24. Card 1: fundo #161616 mais radial-gradient(at 31% -7%, …), padding:48px 24px 0, painel de métodos de 319×240 ancorado no rodapé com raio 16 só em cima, linhas de 293×56 com gap:8, ícone de 36×36, rotação de um item a cada 1.9s sobre nove itens. Card 2: fundo #262626, chips de 24 de altura, foto de 64% × 240 no rodapé, duas pílulas claras de 30–32 de altura com bolinha menta de 20×20, rotador vertical de três posições a cada 2.5s. Card 3: fundo #d0c9b9 com texto escuro, seletor de 127×32 com três botões de 24, gráfico de seis barras de 45 com alturas 56/27/122/61/150/37 sobre 170 e preenchimento hachurado 6px menor, rodapé com valor de 32px, ciclo de 4.26s na ordem 7d → 30d → 12m.
Normalizações aplicadas na tradução:
Prefixo trio- em tudo. Nenhuma classe solta.
Toda cor virou token. #161616 é --dark-2; #d0c9b9 é --accent-2 (o tema kirvano já o descreve como "superfície de card claro dentro de dobra escura"); #262626 virou --trio-surface-2, um color-mix de 7% de --light sobre --dark-2 que devolve exatamente aquele valor e continua certo em qualquer outro tema. Os brancos com alfa da referência viraram color-mix sobre --light, e os pretos com alfa, sobre --dark.
O raio de 24 do card saiu de calc(var(--radius-media) * 1.5). Num projeto flat o card fica reto, que é o comportamento certo: quem decide a forma é o tema, não o componente.
O fundo do card 1 virou a primitiva .glass do core, com os parafusos dela trocados, em vez de uma segunda cópia da mesma regra (§6).
O título da dobra é h2.sec-title do core; o botão é .btn + .btn-primary. Nenhum dos dois foi reescrito.
A entrada, que na referência é um translateY seco, virou data-rv + data-d do core — com o blur que o §5 exige.
Os nove meios de pagamento da referência viraram nove classes de ativo, com ícones desenhados em SVG na hora. Nenhuma marca de terceiro atravessou.
Fonte, fotos e ícones da referência não atravessaram. O preview usa showroom/demo/ph-5.webp.
O leitor de tela recebeu tratamento que o original não tem: aria-label na fila, aria-hidden nas cópias, aria-pressed nos botões de período e visibility nos resultados fora de cena.
Divergências conhecidas vs. a referência medida:
Respiro da seção. A referência usa 120px 0 21px — quase cola esta dobra na seguinte, que tem 96 de respiro próprio. Aqui é .sec-pad, o ritmo único da biblioteca, e portanto simétrico. O §4 do contrato não deixa inventar clamp solto para separar seção.
Corpo do título da dobra. A referência roda ~32px; h2.sec-title do core roda até 40px. A caixa de 480 e as duas linhas se mantêm; o bloco fica um pouco mais alto.
Botão. A referência tem 199×56 em caixa baixa de 16px; o .btn da biblioteca é pílula de 11.5px em caixa alta e sai mais baixo. Corrigir isso seria reescrever o botão, que o contrato não permite — é ajuste de tema, se um dia for preciso.
Direção da pílula que sai (card 2). A medição descreve a anterior descendo e apagando enquanto a nova sobe. Foi reproduzido literalmente: o rotador tem três posições — destaque, próxima e uma terceira fora de cena, mais abaixo — e o ciclo é destaque → fora → próxima → destaque. Quem sai desce até a posição de fora e apaga no caminho; quem entra sobe de lá, acende e assume o destaque. É o que fecha o ciclo sem que ninguém atravesse ninguém.
Alturas das barras nos períodos 30d e 12m. Só o conjunto de 7d foi medido (56/27/122/61/150/37). Os outros dois foram inventados com a mesma lógica: seis valores irregulares, o último sempre no topo da caixa, para o gráfico nunca parecer vazio.
Hachura das barras. A referência não registrou a cor do preenchimento interno. Aqui ele é --accent a 34% sobre a barra escura — o que amarra o gráfico à cor de ação do projeto em vez de inventar um cinza.
Botões de período funcionam. Na referência o seletor só cicla sozinho. Aqui ele também responde ao clique, e o clique desliga o rodízio. Três <button> que ignoram o clique seriam um defeito de acessibilidade herdado sem necessidade.
Apoio dos cards 1 e 2. A referência apaga o parágrafo com opacity:.4 sobre o título claro, o que
devolve pouco mais de 3:1 de contraste. Aqui ele lê --fg-muted, o tom de apoio do escopo, que no par
escuro passa de 5:1. O peso visual é quase o mesmo e o texto deixa de ser um problema de acessibilidade
herdado — além de acompanhar o tema, que opacity não faria.
Corpo do título do card 3. A referência mede a caixa dele em 171×90, três linhas de 30 — leitura mais
apertada que a dos cards 1 e 2, que rodam 30/37.5. Aqui os três títulos usam a MESMA regra
(.trio-card-title), porque três escalas de título dentro de uma grade de três cards é o tipo de
diferença que ninguém consegue justificar depois. O bloco do card 3 sai alguns pixels mais alto que na
referência; a folga do card absorve.
Borda do card 1. A referência mede só fundo e brilho radial, sem borda. A primitiva .glass traz a
borda tênue junto — é parte do desenho dela, e separá-la custaria uma segunda regra de fundo só para
este card. Fica, atenuada a 6% de --light.