// LogicLabz one-pager — main app
const { useEffect, useRef, useState, useCallback } = React;
const TWEAK_DEFAULTS = /*EDITMODE-BEGIN*/{
"accent": "#A855F7",
"fontPair": "grotesk"
}/*EDITMODE-END*/;
// ── icons ──────────────────────────────────────────────────────────────────
const Ico = {
ads: (s = 22) => (
),
flow: (s = 22) => (
),
spark: (s = 22) => (
),
page: (s = 22) => (
),
cart: (s = 22) => (
),
arrow: (s = 14) => (
),
corner: (s = 16) => (
),
wa: (s = 22) => (
),
mail: (s = 22) => (
),
briefcase: (s = 22) => (
),
store: (s = 22) => (
),
laptop: (s = 22) => (
),
play: (s = 22) => (
),
alert: (s = 20) => (
),
question: (s = 20) => (
),
ghost: (s = 20) => (
),
clock: (s = 20) => (
),
ig: (s = 18) => (
),
check: (s = 12) => (
),
};
// ── data ───────────────────────────────────────────────────────────────────
const SERVICES = [
{ name: "Tráfego Pago", desc: "Campanhas em Meta Ads e Google Ads gerenciadas com foco em ROAS, não em vaidade.", tags: ["Meta Ads", "Google Ads", "TikTok Ads"], ico: "ads" },
{ name: "Automações de Marketing", desc: "Funis, fluxos e jornadas que vendem enquanto você dorme — sem retrabalho do time.", tags: ["E-mail", "WhatsApp", "CRM"], ico: "flow" },
{ name: "Produção de Criativos", desc: "Artes e vídeos pensados pra parar o scroll e converter — em volume e velocidade.", tags: ["Static", "Vídeo", "UGC"], ico: "spark" },
{ name: "Páginas de Vendas & Landing Pages", desc: "Páginas com copy persuasiva, design moderno e métricas amarradas. Cada elemento existe pra mover o lead pro próximo passo — testadas e otimizadas com base no comportamento real do usuário.", tags: ["Copy", "Design", "CRO", "A/B Test"], ico: "page" },
{ name: "Gestão Completa de E-commerce", desc: "Da loja ao anúncio, do criativo ao retargeting. Operação ponta-a-ponta pra escalar com previsibilidade e margem saudável — sem você coordenar cinco fornecedores diferentes.", tags: ["Shopify", "WooCommerce", "Performance", "CRM"], ico: "cart" },
];
const WHY = [
{ n: "01", title: "Estratégia + execução", desc: "Não somos consultores que entregam slides. Pensamos a estratégia, operamos a campanha, ajustamos no dia seguinte. Tudo no mesmo time." },
{ n: "02", title: "Operação direta, sem intermediário", desc: "Quem opera é quem rodou verba real. Sem camada de júnior intermediando seu acesso ao trabalho que importa." },
{ n: "03", title: "Dados em tempo real", desc: "Dashboard transparente, atualizado todos os dias. Você sabe pra onde vai cada R$, qual criativo está performando e o que muda na semana." },
];
const STATS = [
{ tag: "Operação", val: "Ativa desde 2022" },
{ tag: "Verba diária", val: "Em Meta, Google e TikTok" },
{ tag: "Cadência", val: "Revisão semanal por operação" },
];
const PROBLEMS = [
{ ico: "alert", q: "Você anuncia, mas o caixa não responde.", a: "Verba sai, lead entra, e mesmo assim a venda não fecha. A gente entra na operação e amarra anúncio, página e CRM pra cada R$ investido voltar mensurado.", to: "trafego", cta: "Como amarramos isso" },
{ ico: "question", q: "Não sabe pra onde vai o dinheiro do tráfego.", a: "Relatórios confusos, plataformas brigando entre si. Dashboard único e transparente , atualizado todo dia, mostrando o que performa e o que precisa morrer.", to: "trafego", cta: "Ver a operacionalização" },
{ ico: "ghost", q: "O criativo cansa antes da campanha escalar.", a: "Sem volume novo, a entrega cai. Produzimos artes e vídeos em ritmo de operação , testando ângulos pra você nunca depender de um anúncio só.", to: "crea", cta: "Ver o pipeline de criativo" },
{ ico: "clock", q: "Tempo todo no produto, nada sobra pro marketing.", a: "Você não tem que virar gestor de tráfego. Cuidamos da operação inteira — você acompanha o resultado , não a planilha.", to: "ecom", cta: "Ver gestão completa" },
];
const LOGOS = [
{ name: "Nova Co.", g: "◆" }, { name: "Helio", g: "◐" }, { name: "Patten", g: "▲" },
{ name: "Querida", g: "◇" }, { name: "Outline", g: "✦" }, { name: "Vértice", g: "▼" },
];
const TESTIMONIALS = [
{
quote: "Reposicionaram a operação inteira em 30 dias. Saímos da bagunça de três freelas pra um time só reportando todo dia.",
name: "Ana Resende",
role: "CEO · Onkel Studio",
result: "Receita +2.1× em 6 meses",
initials: "AR",
avatar: "a1",
},
{
quote: "Eles não entregam slide, entregam screen do BM. É outro nível de transparência — e o resultado veio.",
name: "Marina Salles",
role: "Diretora · Trilha Educação",
result: "CPA −42% no lançamento",
initials: "MS",
avatar: "a2",
},
{
quote: "Pela primeira vez tenho um relatório que eu mesmo entendo, no domingo à noite, sem ninguém do time pra traduzir.",
name: "João Camargo",
role: "Fundador · Verde Mercearia",
result: "Pedidos +60% em 90 dias",
initials: "JC",
avatar: "a3",
},
];
const AUDIENCE = [
{ ico: "cart", title: "E-commerce", desc: "De pequenas lojas a operações que faturam 7 dígitos por mês. Performance, conversão e escala.", tag: "Shopify · Woo · Nuvemshop" },
{ ico: "play", title: "Infoprodutos", desc: "Lançamentos, perpétuos e funis de alto ticket. Criativo, copy e mídia integrados.", tag: "Hotmart · Kiwify · Eduzz" },
{ ico: "store", title: "Comércios Locais", desc: "Operação física que quer aparecer no Google, lotar agenda e parar de depender de indicação. Cuidamos do funil local de ponta a ponta.", tag: "Google Meu Negócio · WhatsApp Business · Geo-targeting" },
{ ico: "briefcase", title: "Profissionais & Marcas Pessoais", desc: "Arquiteta, advogado, designer, consultor. Portfólio que vende o trabalho e funil pra captar cliente todo mês — sem você ter que postar nada.", tag: "Portfólio · Landing · Captação de leads" },
];
// ── reveal hook ────────────────────────────────────────────────────────────
function useReveal() {
useEffect(() => {
const els = document.querySelectorAll("[data-reveal]");
const io = new IntersectionObserver(
(entries) => entries.forEach((e) => {
if (e.isIntersecting) { e.target.classList.add("in"); io.unobserve(e.target); }
}),
{ threshold: 0.12, rootMargin: "0px 0px -40px 0px" }
);
els.forEach((el) => io.observe(el));
return () => io.disconnect();
}, []);
}
// ── in-view hook (returns ref + bool when intersected) ─────────────────────
function useInView(threshold = 0.4) {
const ref = useRef(null);
const [shown, setShown] = useState(false);
useEffect(() => {
const node = ref.current; if (!node) return;
const io = new IntersectionObserver(
(entries) => entries.forEach((e) => { if (e.isIntersecting) { setShown(true); io.unobserve(node); } }),
{ threshold }
);
io.observe(node);
return () => io.disconnect();
}, [threshold]);
return [ref, shown];
}
// ── animated counter ──────────────────────────────────────────────────────
function Counter({ to, decimals = 0, pre = "", suf = "", duration = 1600 }) {
const ref = useRef(null);
const [val, setVal] = useState(0);
const done = useRef(false);
useEffect(() => {
const node = ref.current; if (!node) return;
const io = new IntersectionObserver((entries) => entries.forEach((entry) => {
if (!entry.isIntersecting || done.current) return;
done.current = true;
const start = performance.now();
const tick = (t) => {
const p = Math.min(1, (t - start) / duration);
setVal(to * (1 - Math.pow(1 - p, 3)));
if (p < 1) requestAnimationFrame(tick);
};
requestAnimationFrame(tick);
}), { threshold: 0.5 });
io.observe(node);
return () => io.disconnect();
}, [to, duration]);
const display = decimals > 0 ? val.toFixed(decimals) : Math.round(val);
return (
{pre && {pre} }
{display}
{suf}
);
}
// ── line chart (used in hero dashboard) ────────────────────────────────────
function HeroChart() {
const data = [22, 28, 26, 35, 41, 48, 56, 64, 72, 79, 88, 96];
const months = ["J", "F", "M", "A", "M", "J", "J", "A", "S", "O", "N", "D"];
const [ref, shown] = useInView(0.4);
const W = 440, H = 160, P = 28;
const max = 100, min = 0;
const x = (i) => P + (i * (W - P * 2)) / (data.length - 1);
const y = (v) => H - P - ((v - min) / (max - min)) * (H - P * 2);
const points = data.map((v, i) => [x(i), y(v)]);
const linePath = points.map(([px, py], i) => (i ? "L" : "M") + px + " " + py).join(" ");
const areaPath = linePath + ` L ${x(data.length - 1)} ${H - P} L ${x(0)} ${H - P} Z`;
return (
{[0, 0.33, 0.66, 1].map((t, i) => (
))}
{months.map((m, i) => (
{m}
))}
{points.map(([px, py], i) => (
))}
R$ 96k · ROAS 5.2x
);
}
// ── ROAS line chart (results section) ──────────────────────────────────────
function ROASChart() {
const data = [1.8, 2.1, 2.4, 2.9, 3.3, 3.6, 4.2, 4.8, 5.1, 5.4, 5.9, 6.2];
const [ref, shown] = useInView(0.35);
const W = 460, H = 200, P = 24;
const max = 7, min = 1;
const x = (i) => P + (i * (W - P * 2)) / (data.length - 1);
const y = (v) => H - P - ((v - min) / (max - min)) * (H - P * 2);
const pts = data.map((v, i) => [x(i), y(v)]);
const lp = pts.map(([px, py], i) => (i ? "L" : "M") + px + " " + py).join(" ");
const ap = lp + ` L ${x(data.length - 1)} ${H - P} L ${x(0)} ${H - P} Z`;
return (
{[0, 0.5, 1].map((t, i) => (
))}
{pts.map(([px, py], i) => (
))}
{/* baseline label */}
1.8x
6.2x
);
}
// ── Bar chart (before/after) ───────────────────────────────────────────────
function BarChart() {
const groups = [
{ lbl: "CTR", a: 0.9, b: 2.4, unit: "%" },
{ lbl: "CPA", a: 84, b: 36, unit: "R$", invert: true },
{ lbl: "Conv.", a: 1.3, b: 3.8, unit: "%" },
];
const [ref, shown] = useInView(0.4);
const W = 280, H = 200, P = 28;
const gw = (W - P * 2) / groups.length;
return (
{groups.map((g, i) => {
const max = Math.max(g.a, g.b);
const ha = ((g.invert ? max - g.a + max * 0.2 : g.a) / (max * 1.2)) * (H - P * 2);
const hb = ((g.invert ? max - g.b + max * 0.2 : g.b) / (max * 1.2)) * (H - P * 2);
const cx = P + i * gw + gw / 2;
// 'Antes' bar shown muted, 'Depois' in accent — simpler: scale by relative value
const aH = (g.a / max) * (H - P * 2 - 20);
const bH = (g.b / max) * (H - P * 2 - 20);
// For inverted (lower is better, like CPA), flip visual so 'b' looks like improvement = shorter
return (
{g.lbl}
{g.unit === "R$" ? `R$${g.a}` : `${g.a}${g.unit}`}
{g.unit === "R$" ? `R$${g.b}` : `${g.b}${g.unit}`}
);
})}
);
}
// ── Donut chart (channel mix) ─────────────────────────────────────────────
function DonutChart() {
const slices = [
{ lbl: "Meta Ads", v: 48, color: "var(--accent)" },
{ lbl: "Google Ads", v: 32, color: "#C084FC" },
{ lbl: "TikTok / outros", v: 12, color: "#7E3AC3" },
{ lbl: "Orgânico", v: 8, color: "#4D3266" },
];
const [ref, shown] = useInView(0.4);
const total = slices.reduce((s, x) => s + x.v, 0);
const R = 60, r = 42, cx = 80, cy = 80, C = 2 * Math.PI * R;
let offset = 0;
return (
{slices.map((s, i) => {
const frac = s.v / total;
const len = frac * C;
const dasharray = `${shown ? len : 0} ${C}`;
const rot = -90 + (offset / C) * 360;
offset += len;
return (
);
})}
{total}%
ALOCAÇÃO
{slices.map((s, i) => (
{s.lbl}
{s.v}%
))}
);
}
// ── feature mocks ──────────────────────────────────────────────────────────
function AdsMock() {
const [ref, shown] = useInView(0.3);
const ads = [
{ name: "Reels · Promo Verão", platform: "Meta", thumb: "t1", spend: "R$ 4.8k", ctr: "3.1%", roas: "6.4x", win: true },
{ name: "Search · Termo principal", platform: "Google", thumb: "t2", spend: "R$ 2.1k", ctr: "5.4%", roas: "4.8x" },
{ name: "Carrossel · Catálogo", platform: "Meta", thumb: "t3", spend: "R$ 1.6k", ctr: "1.9%", roas: "3.1x" },
];
return (
painel · campanhas ativas
Meta Ads · 12
Google Ads · 7
TikTok · 3
{ads.map((a, i) => (
{a.name}
Spend {a.spend}
CTR {a.ctr}
ROAS
{a.roas}
))}
);
}
function FlowMock() {
const [ref, shown] = useInView(0.3);
return (
{/* Edges */}
{/* Trigger */}
Lead
ENTRA
{/* Segmenter */}
Segmentação
COMPORTAMENTO
+ INTERESSES
{/* Channels */}
E-mail
3 GATILHOS
WhatsApp
2 GATILHOS
CRM
SCORE + TAG
{/* Animated traveling dot */}
{shown && (
)}
{shown && (
)}
{shown && (
)}
);
}
function CreativesMock() {
const items = [
{ c: "c1", lbl: "Reels", play: true },
{ c: "c2", lbl: "Static" },
{ c: "c3", lbl: "Carrossel" },
{ c: "c4", lbl: "UGC", play: true },
{ c: "c5", lbl: "Stories" },
{ c: "c6", lbl: "Banner" },
];
return (
{items.map((it, i) => (
{it.play && (
)}
{it.lbl}
))}
12 peças produzidas / semana
+ 3 testes A/B em rotação
);
}
function LPMock() {
const [ref, shown] = useInView(0.3);
return (
https:// seu-cliente.com.br/oferta
TX. DE CONVERSÃO
3.8%
Oferta limitada
seu@email.com
Quero saber mais
);
}
function EcomMock() {
const [ref, shown] = useInView(0.3);
const products = [
{ name: "Kit Premium", thumb: "linear-gradient(135deg, #a855f7, #6366f1)", v: "R$ 18.2k", pct: 88 },
{ name: "Edição limitada", thumb: "linear-gradient(135deg, #f472b6, #a855f7)", v: "R$ 12.6k", pct: 62 },
{ name: "Combo Best-seller", thumb: "linear-gradient(135deg, #60a5fa, #34d399)", v: "R$ 9.4k", pct: 48 },
];
const bars = [42, 58, 51, 76, 64, 82, 70, 95, 84];
return (
Vendas / dia
{bars.map((h, i) => (
))}
Top produtos
{products.map((p, i) => (
{p.name}
{p.v}
))}
);
}
// ── feature row (alternating text + visual) ─────────────────────────────
function Feature({ id, num, eyebrow, title, desc, bullets, tags, fitYes, fitNo, alt, children }) {
return (
{num} · {eyebrow}
{title}
{desc}
{bullets.map((b, i) => {b} )}
{(fitYes || fitNo) && (
{fitYes && (
Faz sentido se você…
{fitYes}
)}
{fitNo && (
Não é pra você se…
{fitNo}
)}
)}
{tags.map((t) => {t} )}
{children}
);
}
// ── service card ───────────────────────────────────────────────────────────
function Service({ s, i }) {
const onMove = useCallback((e) => {
const r = e.currentTarget.getBoundingClientRect();
e.currentTarget.style.setProperty("--mx", `${e.clientX - r.left}px`);
e.currentTarget.style.setProperty("--my", `${e.clientY - r.top}px`);
}, []);
const IcoFn = Ico[s.ico];
return (
{String(i + 1).padStart(2, "0")} / 05
{IcoFn(20)}
{s.name}
{s.desc}
{s.tags.map((t) => {t} )}
{Ico.corner(18)}
);
}
// ── nav ───────────────────────────────────────────────────────────────────
function Nav() {
const [scrolled, setScrolled] = useState(false);
useEffect(() => {
const on = () => setScrolled(window.scrollY > 8);
on();
window.addEventListener("scroll", on, { passive: true });
return () => window.removeEventListener("scroll", on);
}, []);
return (
);
}
// ── hero dashboard ────────────────────────────────────────────────────────
function HeroDashboard() {
const [ref, shown] = useInView(0.2);
const kpis = [
{ lbl: "Receita", val: "R$ 412k", delta: "+38%", pos: true },
{ lbl: "ROAS", val: "5.2x", delta: "+1.4x", pos: true },
{ lbl: "CPA", val: "R$ 36", delta: "−57%", pos: true },
];
return (
logiclabz · console
EXEMPLO
{kpis.map((k, i) => (
{k.lbl}
{k.val}
▲ {k.delta}
))}
{[42, 68, 51, 88, 73, 95, 62, 80, 70, 92, 84, 100].map((h, i) => (
))}
);
}
// ── CTA Final (selector + dynamic links) ──────────────────────────────────
const PICK_OPTIONS = [
{ id: "trafego", label: "Tráfego Pago", ico: "ads" },
{ id: "auto", label: "Automações de Marketing", ico: "flow" },
{ id: "crea", label: "Produção de Criativos", ico: "spark" },
{ id: "lp", label: "Páginas de Vendas / LPs", ico: "page" },
{ id: "ecom", label: "Gestão de E-commerce", ico: "cart" },
{ id: "diag", label: "Não sei — quero um diagnóstico", ico: "question" },
];
function buildMessage(selectedIds, note) {
const items = selectedIds
.map((id) => PICK_OPTIONS.find((o) => o.id === id)?.label)
.filter(Boolean);
let msg = "Oi, LogicLabz! Vim pelo site";
if (items.length === 1) {
msg += ` e gostaria de conversar sobre ${items[0]}.`;
} else if (items.length > 1) {
msg += " e gostaria de conversar sobre:\n";
msg += items.map((l) => `• ${l}`).join("\n");
} else {
msg += " e gostaria de conversar sobre as soluções da LogicLabz.";
}
if (note.trim()) {
msg += `\n\nSobre meu negócio:\n${note.trim()}`;
}
return msg;
}
function CTAFinal() {
const [selected, setSelected] = useState([]);
const [note, setNote] = useState("");
const toggle = (id) =>
setSelected((prev) =>
prev.includes(id) ? prev.filter((x) => x !== id) : [...prev, id]
);
const message = buildMessage(selected, note);
const waLink = `https://wa.me/5511953649711?text=${encodeURIComponent(message)}`;
const items = selected
.map((id) => PICK_OPTIONS.find((o) => o.id === id)?.label)
.filter(Boolean);
const subject = items.length
? `Contato pelo site — ${items.join(", ")}`
: "Contato pelo site — LogicLabz";
const mailLink = `mailto:jvmgux@logiclabz.com.br?subject=${encodeURIComponent(subject)}&body=${encodeURIComponent(message)}`;
return (
Vamos conversar
Pedir diagnóstico em 24h úteis.
Sem pitch genérico, sem proposta padronizada. A gente devolve um plano de ação com o que dá pra fazer pelo seu negócio. Confidencial.
{/* PRIMARY: WhatsApp grande */}
{Ico.wa(26)}
Falar agora pelo WhatsApp
+55 11 95364-9711
Mais rápido — resposta em até 4h úteis.
{Ico.arrow(18)}
{/* DIVIDER */}
ou monte um pedido detalhado
Se preferir e-mail ou quiser deixar contexto antes da call, marque as frentes que fazem sentido. A mensagem do WhatsApp também já vai com tudo amarrado.
{PICK_OPTIONS.map((o) => {
const IcoFn = Ico[o.ico];
const on = selected.includes(o.id);
return (
toggle(o.id)}
aria-pressed={on}
>
{IcoFn(18)}
{o.label}
{on && Ico.check(12)}
);
})}
{selected.length > 0 ? (
<>
{selected.length} {selected.length === 1 ? "frente selecionada" : "frentes selecionadas"}
setSelected([])}>
limpar
>
) : (
Opcional — selecione pra refinar o pedido.
)}
);
}
// ── app ───────────────────────────────────────────────────────────────────
function App() {
const [t, setTweak] = useTweaks(TWEAK_DEFAULTS);
useReveal();
useEffect(() => {
const hex = t.accent.replace("#", "");
const r = parseInt(hex.slice(0, 2), 16);
const g = parseInt(hex.slice(2, 4), 16);
const b = parseInt(hex.slice(4, 6), 16);
document.documentElement.style.setProperty("--accent", t.accent);
document.documentElement.style.setProperty("--accent-glow", `${r}, ${g}, ${b}`);
document.documentElement.style.setProperty("--accent-soft", `rgba(${r},${g},${b},0.12)`);
}, [t.accent]);
useEffect(() => {
const pairs = {
grotesk: { d: '"Space Grotesk", ui-sans-serif, system-ui, sans-serif', b: '"Inter", ui-sans-serif, system-ui, sans-serif' },
manrope: { d: '"Manrope", ui-sans-serif, system-ui, sans-serif', b: '"Inter", ui-sans-serif, system-ui, sans-serif' },
monoMix: { d: '"DM Sans", ui-sans-serif, system-ui, sans-serif', b: '"Inter", ui-sans-serif, system-ui, sans-serif' },
};
const p = pairs[t.fontPair] || pairs.grotesk;
document.documentElement.style.setProperty("--font-display", p.d);
document.documentElement.style.setProperty("--font-body", p.b);
}, [t.fontPair]);
return (
<>
{/* HERO */}
{/* OPERATIONAL SCOPE */}
{STATS.map((s, i) => (
{s.tag}
{s.val}
{s.revisar && REVISAR }
))}
{/* PROBLEMAS */}
Problemas que resolvemos
Sabe o que está travando seu crescimento? A gente também.
Cada problema abaixo tem um plano de ação dentro da operação. Sem teoria, sem terceirização.
{PROBLEMS.map((p, i) => {
const IcoFn = Ico[p.ico];
return (
);
})}
{/* SOLUÇÕES (features) */}
Soluções
Cinco frentes. Uma operação só.
Você contrata um time, não cinco fornecedores. Cada serviço com entregável claro — e o que você vê abaixo é como cada um chega na prática.
{/* CASES */}
Cases anônimos
Operações reais, números defensáveis.
Cases anônimos pra preservar confidencialidade. Período, intervenção e resultado defensáveis em call.
Case 01
6 meses
E-commerce
Moda · Sudeste
+2.1× receita
Antes
Faturamento mensal estagnado, ROAS abaixo de 2× no Meta.
Fizemos
Refatoração do funil de aquisição, retake completo do criativo com UGC, recuperação de carrinho via WhatsApp.
Depois
ROAS 3.6× sustentado
Case 02
120 dias
Infoproduto
Educação · Perpétuo
−42% CPA
Antes
CPA inviável no lançamento, criativo único cansando rápido.
Fizemos
Pipeline de criativo semanal, automação de captura no WhatsApp, copy de página reescrita do zero.
Depois
2× de escala na verba diária
Mix de canais
Média da casa
Como diluímos risco entre plataformas. Distribuição varia por operação.
{/* PARA QUEM */}
Para quem fazemos
Negócios diferentes. Mesma exigência de resultado.
Atendemos quem quer crescer — do e-commerce que escala a sete dígitos ao profissional autônomo que quer captar cliente todo dia.
{AUDIENCE.map((a, i) => {
const IcoFn = Ico[a.ico];
return (
{IcoFn(22)}
{a.title}
{a.desc}
{a.tag}
);
})}
{/* WHY */}
Por que a LogicLabz
A diferença está em como a gente opera.
Três coisas que a gente faz diferente — e que mudam o resultado já no primeiro mês.
{/* DEPOIMENTOS */}
Depoimentos
Quem confiou a operação pra gente.
Quem confiou a operação e ficou. Cada caso com o número que importa.
{TESTIMONIALS.map((t, i) => (
{t.revisar && REVISAR }
{t.result}
{t.quote}
{t.initials}
{t.name}
{t.role}
))}
{/* CTA FINAL com seletor */}
setTweak("accent", v)}
/>
setTweak("fontPair", v)}
/>
>
);
}
ReactDOM.createRoot(document.getElementById("app")).render( );