* feat(design): redesign das páginas internas no novo design system Padroniza todas as páginas e componentes internos nos tokens e primitivos de app/globals.css, com contraste AAA (7:1) nos dois temas e alvos de toque de 44px. - Páginas reescritas: glossário, séries, série, seletor, comparador, diagnóstico, catálogo, component registry, frameworks, framework, sobre e a nova 404. - Componentes: PageHero e SectionHead novos; BoardCard, CodeExample, PinRestrictions, SeriesTabMenu e ConnectionsDiagram refeitos, com abas e diálogo acessíveis. - Remove emojis, cores avulsas por série, caixa alta com tracking, texto menor que 12px e os pares slate-*/dark:*. - Comparador: "mostrar só as diferenças" e leitura de ?chips=. - Corrige overflow em 320/390px e ajusta accent/copper do tema claro para fechar 7:1. - scripts/auditar-temas.mjs passa a travar paleta crua, dark:, texto pequeno, emoji e contraste dos tokens. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016W1xmizFcmXLrmabSvfSuc * ci: roda lint, auditorias de design e build em cada PR Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016W1xmizFcmXLrmabSvfSuc --------- Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>
155 lines
5.8 KiB
React
155 lines
5.8 KiB
React
"use client";
|
|
|
|
import { useMemo, useState } from "react";
|
|
import Header from "@/components/Header";
|
|
import Footer from "@/components/Footer";
|
|
import PageHero from "@/components/PageHero";
|
|
import Link from "next/link";
|
|
import seriesData from "@/public/series.json";
|
|
import {
|
|
AlertTriangle,
|
|
ArrowRight,
|
|
ArrowUpRight,
|
|
Compass,
|
|
Home,
|
|
Scale,
|
|
ShieldCheck,
|
|
Sparkles,
|
|
} from "lucide-react";
|
|
|
|
const FILTROS = [
|
|
{ id: "todas", rotulo: "Todas", testa: () => true },
|
|
{ id: "riscv", rotulo: "RISC-V", testa: (s) => s.arquitetura.includes("RISC-V") },
|
|
{ id: "xtensa", rotulo: "Xtensa", testa: (s) => !s.arquitetura.includes("RISC-V") },
|
|
{ id: "matter", rotulo: "Matter", testa: (s) => s.matter === "Sim" },
|
|
{ id: "ia", rotulo: "Aceleração de IA", testa: (s) => Boolean(s.aceleradores_ia) },
|
|
];
|
|
|
|
/** Marcador de segurança: o recurso que diferencia a série, não o que todas têm. */
|
|
function nivelSeguranca(serie) {
|
|
const seg = serie.seguranca || {};
|
|
if (seg.key_manager === "Sim") return "Key Manager";
|
|
if (String(seg.ecdsa).startsWith("Sim")) return "ECDSA em hardware";
|
|
if (String(seg.ecc).startsWith("Sim")) return "ECC em hardware";
|
|
if (String(seg.aes).startsWith("AES")) return "AES + SHA";
|
|
return "Só SHA";
|
|
}
|
|
|
|
const sim = (v) => !String(v).includes("Não");
|
|
|
|
export default function Series() {
|
|
const [filtro, setFiltro] = useState("todas");
|
|
const todas = Object.entries(seriesData);
|
|
const ativo = FILTROS.find((f) => f.id === filtro);
|
|
const series = useMemo(() => todas.filter(([, s]) => ativo.testa(s)), [todas, ativo]);
|
|
|
|
return (
|
|
<div className="site-canvas min-h-screen">
|
|
<Header />
|
|
|
|
<main id="conteudo">
|
|
<PageHero
|
|
trail={[{ label: "Séries" }]}
|
|
icon={Compass}
|
|
label={`${todas.length} famílias da Espressif`}
|
|
title="Séries ESP32"
|
|
lede="Cada série foi desenhada para um tipo de projeto, do sensor a bateria ao acelerador de IA. Abra a ficha para ver pinagem, periféricos e segurança."
|
|
/>
|
|
|
|
<section className="shell pt-10 sm:pt-12" aria-label="Lista de séries">
|
|
<div role="group" aria-label="Filtrar séries" className="flex flex-wrap gap-2">
|
|
{FILTROS.map((f) => (
|
|
<button key={f.id} type="button" aria-pressed={filtro === f.id} onClick={() => setFiltro(f.id)} className="chip-btn">
|
|
{f.rotulo}
|
|
</button>
|
|
))}
|
|
</div>
|
|
<p role="status" aria-live="polite" className="mt-4 text-sm text-muted">
|
|
{series.length} {series.length === 1 ? "série" : "séries"}
|
|
</p>
|
|
|
|
<div className="mt-4 grid gap-4 md:grid-cols-2 lg:grid-cols-3">
|
|
{series.map(([key, serie]) => {
|
|
const riscv = serie.arquitetura.includes("RISC-V");
|
|
const fatos = [
|
|
["Núcleos", serie.nucleos],
|
|
["Frequência", serie.frequencia],
|
|
["SRAM", serie.memoria_sram],
|
|
["Wi-Fi", sim(serie.wifi) ? "Sim" : "Não"],
|
|
["Bluetooth", serie.bluetooth === "Não" ? "Não" : "Sim"],
|
|
serie.consumo_energia?.deep_sleep ? ["Deep sleep", serie.consumo_energia.deep_sleep] : null,
|
|
].filter(Boolean);
|
|
return (
|
|
<Link key={key} href={`/series/${key}`} className="card card-interactive group flex flex-col p-5 sm:p-6">
|
|
<div className="flex items-start justify-between gap-3">
|
|
<h2 className="font-display text-2xl font-semibold tracking-[-0.035em] text-ink">{key}</h2>
|
|
<span className="chip">{riscv ? "RISC-V" : "Xtensa"}</span>
|
|
</div>
|
|
<p className="mt-2 line-clamp-2 min-h-[2.9rem] text-sm leading-relaxed text-muted">{serie.descricao}</p>
|
|
|
|
<dl className="mt-5 divide-y divide-line border-y border-line text-sm">
|
|
{fatos.map(([rotulo, valor]) => (
|
|
<div key={rotulo} className="flex items-baseline justify-between gap-4 py-2">
|
|
<dt className="text-muted">{rotulo}</dt>
|
|
<dd className={`text-right font-medium ${valor === "Não" ? "text-muted" : "text-ink"}`}>{valor}</dd>
|
|
</div>
|
|
))}
|
|
</dl>
|
|
|
|
<ul className="mt-4 flex flex-1 flex-wrap content-start gap-1.5">
|
|
{serie.aceleradores_ia && (
|
|
<li className="chip chip-accent">
|
|
<Sparkles className="h-3 w-3" aria-hidden="true" />
|
|
Aceleradores de IA
|
|
</li>
|
|
)}
|
|
<li className="chip">
|
|
<ShieldCheck className="h-3 w-3 text-accent" aria-hidden="true" />
|
|
{nivelSeguranca(serie)}
|
|
</li>
|
|
{serie.matter === "Sim" && (
|
|
<li className="chip">
|
|
<Home className="h-3 w-3 text-accent" aria-hidden="true" />
|
|
Matter
|
|
</li>
|
|
)}
|
|
{serie.status_documentacao && (
|
|
<li className="chip">
|
|
<AlertTriangle className="h-3 w-3 text-gold" aria-hidden="true" />
|
|
Documentação preliminar
|
|
</li>
|
|
)}
|
|
</ul>
|
|
|
|
<span className="mt-5 inline-flex items-center gap-1 text-sm font-medium text-accent">
|
|
Abrir ficha
|
|
<ArrowUpRight className="h-4 w-4 transition-transform group-hover:-translate-y-0.5 group-hover:translate-x-0.5" aria-hidden="true" />
|
|
</span>
|
|
</Link>
|
|
);
|
|
})}
|
|
</div>
|
|
</section>
|
|
|
|
<section className="shell section">
|
|
<div className="panel flex flex-col items-start justify-between gap-6 p-7 sm:p-10 md:flex-row md:items-center">
|
|
<div className="max-w-2xl">
|
|
<h2 className="section-title">Ficou em dúvida entre duas séries?</h2>
|
|
<p className="mt-3 text-base text-muted">
|
|
O comparador coloca até quatro modelos lado a lado: núcleos, rádio, memória, periféricos e segurança.
|
|
</p>
|
|
</div>
|
|
<Link href="/comparacao" className="btn btn-primary shrink-0">
|
|
<Scale className="h-4 w-4" aria-hidden="true" />
|
|
Comparar séries
|
|
<ArrowRight className="h-4 w-4" aria-hidden="true" />
|
|
</Link>
|
|
</div>
|
|
</section>
|
|
</main>
|
|
|
|
<Footer />
|
|
</div>
|
|
);
|
|
}
|