const { useEffect, useState } = React;
// Ícone por função do botão, desenhado em SVG inline: nada de arquivo externo e a
// cor vem do CSS via currentColor, que é o oposto da cor do botão.
const ICONES = {
seta: ,
conversa: ,
selo: <>>,
};
const GRAFISMOS = {
quote: ,
caminho: <>>,
livro: <>>,
pessoas: <>>,
selo: <>>,
};
function Grafismo({ tipo = 'caminho', className = '' }) {
return ;
}
function DisabledButton({ children, className = '', small = false, icone = 'seta', href = '' }) {
const desenho = ICONES[icone];
const conteudo = <>{children}{desenho && }>;
if (href) return {conteudo};
return ;
}
function Eyebrow({ children }) {
return
{children}
;
}
// `layout` decide a diagramação do cabeçalho da dobra:
// 'duplo' título à esquerda, introdução à direita, alinhados pela base.
// 'centro' cabeçalho centrado, para dobra sem introdução.
// padrão empilhado, para quando a coluna ao lado já tem conteúdo.
// Existe porque 5 das 13 dobras usavam só 51% da calha na faixa do título.
function SectionTitle({ eyebrow, title, intro, light = false, layout = '' }) {
const variante = layout ? `section-title--${layout}` : '';
return
{eyebrow && {eyebrow}}
;
}
// Um acordeão só serve o FAQ e a grade de módulos. `renderBody` deixa o corpo ser
// um parágrafo ou uma lista de tópicos sem duplicar estado, marcação e acesso.
function Accordion({ items, className = '', getKey, renderHead, renderBody, initialOpen = 0 }) {
const [open, setOpen] = useState(initialOpen);
const key = getKey || ((item) => item[0]);
const head = renderHead || ((item) => {item[0]});
const body = renderBody || ((item) =>
;
}
// Anima só o número que abre a frase e devolve o resto do texto intacto. Se a
// string não começar com número, renderiza verbatim e não faz nada.
function CountUpText({ children }) {
const texto = String(children);
const match = texto.match(/^([\d.]+)(\s[\s\S]*)$/);
const alvo = match ? Number(match[1].replace(/\./g, '')) : null;
const [valor, setValor] = useState(alvo);
const ref = React.useRef(null);
useEffect(() => {
if (alvo === null || !ref.current) return;
if (window.matchMedia('(prefers-reduced-motion: reduce)').matches) return;
if (!('IntersectionObserver' in window)) return;
let raf = 0;
const observer = new IntersectionObserver((entries) => {
if (!entries.some((e) => e.isIntersecting)) return;
observer.disconnect();
const inicio = performance.now();
const passo = (agora) => {
const t = Math.min(1, (agora - inicio) / 900);
setValor(Math.round(alvo * (1 - Math.pow(1 - t, 3))));
if (t < 1) raf = requestAnimationFrame(passo);
};
setValor(0);
raf = requestAnimationFrame(passo);
}, { threshold: 0.4 });
observer.observe(ref.current);
return () => { observer.disconnect(); cancelAnimationFrame(raf); };
}, [alvo]);
if (alvo === null) return {texto};
return {valor.toLocaleString('pt-BR')}{match[2]};
}
// Vegetação em três planos de profundidade. A leitura de 3D vem de escala,
// sobreposição, sangria pela borda e velocidade de parallax distinta por plano.
// Nunca de opacidade reduzida nem de desfoque: planta apagada foi rejeitada.
function Flora({ plano = 'fundo', especie = 'monstera', pos = 'dir-topo', espelho = false }) {
return ;
}
function TechnicalPlaceholder({ label, className = '' }) {
return