feat: add theme consistency guidelines and pin restrictions component

- Introduced DESIGN.md to outline rules for maintaining consistent light and dark themes, including color scales, symmetry rules, and contrast requirements.
- Added PinRestrictions component to display pin usage restrictions based on series data, ensuring accurate representation of pin functionalities.
- Implemented auditing script (auditar-temas.mjs) to check for theme consistency issues, including nonexistent tones, inverted pairs, low contrast, and inline fixed colors.
This commit is contained in:
Lucas Rayan committed 2026-08-20 05:37:56 -03:00
1 parent d4872fcf07
commit 8a52e79d21
23 files changed
+1249 -278

No files matched your search

+200
View File
@@ -0,0 +1,200 @@
# Diretrizes de tema — claro e escuro
Regras para manter os dois temas consistentes. Não tratam da identidade visual
(degradê da marca, arredondamentos, hierarquia): essas decisões já estão tomadas
e não mudam.
---
## 1. Só existem os tons declarados
A escala do Tailwind anda de 100 em 100: `50, 100, 200, 300 … 900, 950`.
Os meios-tons (`150, 250, 350, 450, 550, 650, 750, 850`) **não existem por
padrão** — uma classe como `text-slate-350` não gera CSS nenhum, e o elemento
herda silenciosamente a cor do pai. Era a causa da maior parte das
inconsistências: o mesmo texto ficava certo num tema e lavado no outro.
Os oito meios-tons estão declarados em [`app/globals.css`](app/globals.css),
cada um no ponto médio exato entre os dois degraus oficiais vizinhos:
| tom | hex | entre |
|-----|-----|-------|
| `slate-150` | `#eaeef4` | 100 e 200 |
| `slate-250` | `#d6dee8` | 200 e 300 |
| `slate-350` | `#b0bccc` | 300 e 400 |
| `slate-450` | `#7c8ca2` | 400 e 500 |
| `slate-550` | `#56647a` | 500 e 600 |
| `slate-650` | `#3d4b5f` | 600 e 700 |
| `slate-750` | `#283548` | 700 e 800 |
| `slate-850` | `#162032` | 800 e 900 |
**Precisa de um tom novo?** Declare no `@theme` antes de usar. Nunca escreva uma
classe com um valor que não está na escala.
---
## 2. A regra da simetria
No tema escuro o fundo é escuro, então o texto precisa **clarear**.
- **Texto:** o número do `dark:` é sempre **menor** que o do claro.
`text-slate-800 dark:text-slate-200` ✅ — `text-slate-400 dark:text-slate-500` ❌
- **Fundo e borda:** o número do `dark:` é sempre **maior**.
`bg-white dark:bg-slate-900` ✅ — `border-slate-200 dark:border-slate-800` ✅
Um par invertido não quebra o build e não aparece em revisão de código: só
enfraquece o contraste justamente onde ele deveria aumentar. Havia 71 deles.
---
## 3. Pares canônicos por papel
Use estes. Os números são a razão de contraste no tema claro (sobre branco) e no
escuro (sobre `slate-900`). O mínimo para texto é **4.5:1**; para ícone ou
elemento gráfico, **3:1**.
| Papel | Par | Claro | Escuro |
|-------|-----|-------|--------|
| Título / texto principal | `text-slate-850 dark:text-slate-100` | 16.3 | 16.3 |
| Texto principal alternativo | `text-slate-800 dark:text-slate-200` | 14.6 | 14.5 |
| Texto secundário | `text-slate-700 dark:text-slate-300` | 10.4 | 12.0 |
| Corpo de leitura | `text-slate-650 dark:text-slate-350` | 8.9 | 9.3 |
| Rótulo / apoio | `text-slate-500 dark:text-slate-400` | 4.8 | 7.0 |
| Menor ênfase | `text-slate-500 dark:text-slate-450` | 4.8 | 5.2 |
`text-slate-500 dark:text-slate-400` é o par mais usado do projeto (100
ocorrências) — é o rótulo em caixa alta, a legenda, o texto de apoio.
**Não use** `slate-400` como cor de texto no tema claro: sobre branco dá 2.56:1.
Era o erro mais repetido.
---
## 4. Exceção: superfícies escuras nos dois temas
Alguns blocos são escuros independente do tema — painéis de CTA com degradê,
bloco de código, tooltip do diagrama de pinos. Dentro deles:
- **Não use variante `dark:`.** O fundo não muda, então o texto também não deve.
- Escreva um valor só: `text-slate-200`, `text-white`, `text-slate-350`.
Exemplo correto, no bloco de código ([`CodeExample.jsx`](components/CodeExample.jsx)):
```jsx
<div className="flex bg-slate-950"> {/* escuro sempre */}
<span className="text-slate-500 pr-4 ...">{idx + 1}</span> {/* sem dark: */}
```
---
## 4.1. Botão nunca inverte preto ↔ branco
O padrão abaixo **não é aceitável**, apesar de tecnicamente "acompanhar" o tema:
```jsx
/* ERRADO: preto no tema claro, branco no escuro */
className="bg-slate-900 dark:bg-slate-100 text-white dark:text-slate-950"
```
Ele deixa a interface sem cor e faz o mesmo botão trocar completamente de
aparência entre os temas. Botão carrega a **própria cor**, igual nos dois temas:
| Papel | Classe | Contraste com texto branco |
|-------|--------|---------------------------|
| Ação principal | `bg-gradient-to-r from-purple-600 to-indigo-600 text-white` | 5.4 – 6.3 |
| Ação secundária | `bg-blue-600 hover:bg-blue-700 text-white` | 5.2 |
| Ação terciária | `bg-white dark:bg-slate-900` + borda + texto neutro | — |
Como a cor é a mesma nos dois temas, o texto branco também é — sem variante
`dark:`. Isso vale inclusive dentro de painel escuro: o botão de gradiente
funciona sobre fundo claro e escuro.
---
## 4.2. Painel de destaque acompanha o tema
Painéis de CTA não podem ficar escuros no tema claro. O padrão é um degradê
suave da marca no claro e o degradê escuro no escuro:
```jsx
className="bg-gradient-to-br from-indigo-50 to-purple-50
dark:from-slate-950 dark:to-purple-950/20
border border-indigo-200 dark:border-slate-800/80"
```
Com o conteúdo pareado: título `text-slate-900 dark:text-white`, corpo
`text-slate-600 dark:text-slate-400`, etiqueta
`bg-indigo-500/10 text-indigo-700 dark:bg-white/10 dark:text-slate-350`.
---
## 4.3. Separação de superfície no tema claro
Entre near-whites o contraste é sempre baixo — quem separa o card do fundo é a
**borda**, não o fundo. Por isso:
| Camada | Valor | Contraste |
|--------|-------|-----------|
| Fundo da página | `from-slate-100 via-slate-50 to-purple-100/40` | — |
| Card | `bg-white` (sólido, sem `/70` ou `/80`) | 1.10 vs fundo |
| Borda do card | `border-slate-300` | 1.48 vs card |
| Divisória interna | `border-slate-200` | mais sutil |
`border-slate-200/60` sobre card branco dava 1.14 — imperceptível. Não use borda
com opacidade para delimitar card.
---
## 5. Cor de série vem do dado, não da classe
Cada série tem sua cor em [`public/series.json`](public/series.json) (campo
`cor`). Ela entra por `style`, nunca por classe Tailwind:
```jsx
<span style={{ backgroundColor: `${serie.cor}18`, color: serie.cor }} />
```
Esses valores são iguais nos dois temas. Ao usá-los como **fundo**, mantenha o
alfa baixo (`18`–`30` em hex) para que funcione sobre superfície clara e escura.
Como **texto**, só sobre superfície neutra — nunca sobre outra cor de série.
### Nunca escreva cor fixa em `style` inline
`style` inline vence qualquer classe — inclusive as variantes `dark:` e
`hover:`, que deixam de existir para aquele elemento. Só cor **vinda do dado**
pode ir no inline.
```jsx
/* ERRADO: anula border-slate-300 e dark:border-slate-800/80 */
<div className="border border-slate-300 dark:border-slate-800/80"
style={{ borderColor: 'rgba(148, 163, 184, 0.1)' }} />
/* ERRADO: 'transparent' anula também o hover:border-purple-400/50 */
style={{ borderColor: isSelected ? serie.cor : 'transparent' }}
/* CERTO: sem valor, a classe assume o estado de repouso */
style={{ borderColor: isSelected ? serie.cor : undefined }}
```
Esse bug é traiçoeiro porque **some depois do primeiro hover**: o
`onMouseLeave` faz `style.borderColor = ''`, que apaga o inline e devolve o
controle à classe. A borda passa a aparecer e nunca mais some — dando a
impressão de que só falha "antes de passar o mouse".
---
## 6. Auditar antes de commitar
```bash
npm run auditar-temas
```
Aponta quatro coisas: tom que não existe na escala, par invertido, par abaixo de
AA e cor fixa em `style` inline. Saída esperada: tudo zero.
Sai com código 1 se achar problema, então serve direto em hook de pre-commit ou
em CI:
```bash
npm run auditar-temas && npm run build
```
+17 -17
View File
@@ -149,13 +149,13 @@ export default function PlacasPage() {
if (loading) { if (loading) {
return ( return (
<div className="bg-gradient-to-br from-slate-50 via-white to-purple-50/40 dark:from-slate-950 dark:via-slate-950 dark:to-slate-900 min-h-screen text-slate-900 dark:text-slate-100 flex flex-col justify-between"> <div className="bg-gradient-to-br from-slate-100 via-slate-50 to-purple-100/40 dark:from-slate-950 dark:via-slate-950 dark:to-slate-900 min-h-screen text-slate-900 dark:text-slate-100 flex flex-col justify-between">
<Header /> <Header />
<main className="max-w-7xl mx-auto px-6 py-24 flex-1 flex items-center justify-center"> <main className="max-w-7xl mx-auto px-6 py-24 flex-1 flex items-center justify-center">
<div className="text-center select-none animate-pulse"> <div className="text-center select-none animate-pulse">
<RefreshCw className="w-12 h-12 text-purple-500 animate-spin mx-auto mb-4" /> <RefreshCw className="w-12 h-12 text-purple-500 animate-spin mx-auto mb-4" />
<p className="text-xs font-bold text-slate-400 dark:text-slate-500 uppercase tracking-widest">Consultando banco de placas...</p> <p className="text-xs font-bold text-slate-500 dark:text-slate-400 uppercase tracking-widest">Consultando banco de placas...</p>
</div> </div>
</main> </main>
@@ -166,20 +166,20 @@ export default function PlacasPage() {
if (error) { if (error) {
return ( return (
<div className="bg-gradient-to-br from-slate-50 via-white to-purple-50/40 dark:from-slate-950 dark:via-slate-950 dark:to-slate-900 min-h-screen text-slate-900 dark:text-slate-100 flex flex-col justify-between"> <div className="bg-gradient-to-br from-slate-100 via-slate-50 to-purple-100/40 dark:from-slate-950 dark:via-slate-950 dark:to-slate-900 min-h-screen text-slate-900 dark:text-slate-100 flex flex-col justify-between">
<Header /> <Header />
<main className="max-w-7xl mx-auto px-6 py-24 flex-1 flex items-center justify-center"> <main className="max-w-7xl mx-auto px-6 py-24 flex-1 flex items-center justify-center">
<div className="text-center bg-white/80 dark:bg-slate-900/40 backdrop-blur-xl border border-slate-200/60 dark:border-slate-800/80 rounded-3xl p-8 max-w-md shadow-2xl select-none"> <div className="text-center bg-white dark:bg-slate-900/40 backdrop-blur-xl border border-slate-300 dark:border-slate-800/80 rounded-3xl p-8 max-w-md shadow-2xl select-none">
<div className="w-12 h-12 bg-red-500/10 border border-red-500/20 rounded-2xl flex items-center justify-center text-red-500 mx-auto mb-4"> <div className="w-12 h-12 bg-red-500/10 border border-red-500/20 rounded-2xl flex items-center justify-center text-red-500 mx-auto mb-4">
<AlertTriangle className="w-6 h-6" /> <AlertTriangle className="w-6 h-6" />
</div> </div>
<p className="text-sm font-bold text-red-600 dark:text-red-400 mb-2 uppercase tracking-widest">Falha ao sincronizar dados</p> <p className="text-sm font-bold text-red-600 dark:text-red-400 mb-2 uppercase tracking-widest">Falha ao sincronizar dados</p>
<p className="text-xs text-slate-450 dark:text-slate-550 mb-6 leading-relaxed">{error}</p> <p className="text-xs text-slate-500 dark:text-slate-400 mb-6 leading-relaxed">{error}</p>
<button <button
onClick={fetchBoards} onClick={fetchBoards}
className="inline-flex items-center gap-2 bg-slate-900 dark:bg-slate-100 text-white dark:text-slate-950 px-5 py-3 rounded-xl font-bold text-xs uppercase tracking-wider hover:opacity-90 active:scale-95 transition-all shadow-md cursor-pointer" className="inline-flex items-center gap-2 bg-gradient-to-r from-purple-600 to-indigo-600 text-white px-5 py-3 rounded-xl font-bold text-xs uppercase tracking-wider hover:opacity-90 active:scale-95 transition-all shadow-md cursor-pointer"
> >
<RefreshCw className="w-4 h-4" /> <RefreshCw className="w-4 h-4" />
<span>Tentar Novamente</span> <span>Tentar Novamente</span>
@@ -195,13 +195,13 @@ export default function PlacasPage() {
const filterableColumns = headers.filter(h => !excludedColumns.includes(h)); const filterableColumns = headers.filter(h => !excludedColumns.includes(h));
return ( return (
<div className="bg-gradient-to-br from-slate-50 via-white to-purple-50/40 dark:from-slate-950 dark:via-slate-950 dark:to-slate-900 min-h-screen text-slate-900 dark:text-slate-100 transition-colors duration-300"> <div className="bg-gradient-to-br from-slate-100 via-slate-50 to-purple-100/40 dark:from-slate-950 dark:via-slate-950 dark:to-slate-900 min-h-screen text-slate-900 dark:text-slate-100 transition-colors duration-300">
<Header /> <Header />
<main id="conteudo" className="max-w-7xl mx-auto px-6 pt-16 pb-24"> <main id="conteudo" className="max-w-7xl mx-auto px-6 pt-16 pb-24">
{/* Top Hero Heading Block */} {/* Top Hero Heading Block */}
<section className="text-center mb-16 select-none"> <section className="text-center mb-16 select-none">
<div className="inline-flex items-center gap-2 bg-white/70 dark:bg-slate-900/50 backdrop-blur-md px-4 py-2 rounded-full mb-6 border border-slate-200/60 dark:border-slate-800/60 shadow-xs"> <div className="inline-flex items-center gap-2 bg-white dark:bg-slate-900/50 backdrop-blur-md px-4 py-2 rounded-full mb-6 border border-slate-300 dark:border-slate-800/60 shadow-xs">
<ShoppingCart className="w-4 h-4 text-purple-500" /> <ShoppingCart className="w-4 h-4 text-purple-500" />
<span className="text-xs font-bold text-slate-700 dark:text-slate-350 uppercase tracking-wider">Encontre sua placa ideal</span> <span className="text-xs font-bold text-slate-700 dark:text-slate-350 uppercase tracking-wider">Encontre sua placa ideal</span>
</div> </div>
@@ -225,7 +225,7 @@ export default function PlacasPage() {
<div className="grid lg:grid-cols-4 gap-8"> <div className="grid lg:grid-cols-4 gap-8">
{/* Left Filters Sidebar */} {/* Left Filters Sidebar */}
<div className="lg:col-span-1"> <div className="lg:col-span-1">
<div className="bg-white/80 dark:bg-slate-900/40 backdrop-blur-xl border border-slate-200/60 dark:border-slate-800/80 rounded-3xl shadow-xl overflow-hidden sticky top-4"> <div className="bg-white dark:bg-slate-900/40 backdrop-blur-xl border border-slate-300 dark:border-slate-800/80 rounded-3xl shadow-xl overflow-hidden sticky top-4">
{/* Sidebar Banner Header */} {/* Sidebar Banner Header */}
<div className="bg-gradient-to-r from-blue-600 to-purple-600 p-6 text-white select-none"> <div className="bg-gradient-to-r from-blue-600 to-purple-600 p-6 text-white select-none">
@@ -257,7 +257,7 @@ export default function PlacasPage() {
<div className="p-6 space-y-6"> <div className="p-6 space-y-6">
{/* Text search by name */} {/* Text search by name */}
<div> <div>
<label className="block text-[9px] font-bold text-slate-400 dark:text-slate-500 mb-2.5 uppercase tracking-widest select-none"> <label className="block text-[9px] font-bold text-slate-500 dark:text-slate-400 mb-2.5 uppercase tracking-widest select-none">
Buscar Placa Buscar Placa
</label> </label>
<div className="relative"> <div className="relative">
@@ -266,13 +266,13 @@ export default function PlacasPage() {
placeholder="Digite o nome da placa..." placeholder="Digite o nome da placa..."
value={searchTerm} value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)} onChange={(e) => setSearchTerm(e.target.value)}
className="w-full px-4 py-3 pl-10 border border-slate-200 dark:border-slate-800 rounded-2xl focus:ring-2 focus:ring-purple-500 dark:focus:ring-purple-500 focus:outline-none transition-colors text-xs bg-slate-50/50 dark:bg-slate-950/20 text-slate-800 dark:text-slate-100" className="w-full px-4 py-3 pl-10 border border-slate-300 dark:border-slate-800 rounded-2xl focus:ring-2 focus:ring-purple-500 dark:focus:ring-purple-500 focus:outline-none transition-colors text-xs bg-slate-50/50 dark:bg-slate-950/20 text-slate-800 dark:text-slate-100"
/> />
<Search className="absolute left-3.5 top-3.5 w-4 h-4 text-slate-400 dark:text-slate-500" /> <Search className="absolute left-3.5 top-3.5 w-4 h-4 text-slate-500 dark:text-slate-400" />
</div> </div>
</div> </div>
<div className="border-t border-slate-100 dark:border-slate-850/45 my-2"></div> <div className="border-t border-slate-200 dark:border-slate-850/45 my-2"></div>
{/* Dinamic filters container with customized scrollbar */} {/* Dinamic filters container with customized scrollbar */}
<div className="space-y-4 max-h-[calc(100vh-450px)] overflow-y-auto pr-2 custom-scrollbar"> <div className="space-y-4 max-h-[calc(100vh-450px)] overflow-y-auto pr-2 custom-scrollbar">
@@ -283,7 +283,7 @@ export default function PlacasPage() {
const isActive = activeFilters[column]?.length > 0; const isActive = activeFilters[column]?.length > 0;
return ( return (
<div key={column} className={`pb-3 border-b border-slate-100 dark:border-slate-850/30 last:border-b-0 ${isActive ? 'bg-purple-500/5 dark:bg-purple-400/5 p-3 rounded-2xl border border-purple-500/10 dark:border-purple-400/10' : ''}`}> <div key={column} className={`pb-3 border-b border-slate-200 dark:border-slate-850/30 last:border-b-0 ${isActive ? 'bg-purple-500/5 dark:bg-purple-400/5 p-3 rounded-2xl border border-purple-500/10 dark:border-purple-400/10' : ''}`}>
{filterData.isBinary ? ( {filterData.isBinary ? (
// Binary filter (checkbox inside title wrapper) // Binary filter (checkbox inside title wrapper)
<label className="flex justify-between items-center cursor-pointer p-1 rounded-lg transition-all group select-none"> <label className="flex justify-between items-center cursor-pointer p-1 rounded-lg transition-all group select-none">
@@ -341,14 +341,14 @@ export default function PlacasPage() {
{/* Right Catalog Boards Grid */} {/* Right Catalog Boards Grid */}
<div className="lg:col-span-3"> <div className="lg:col-span-3">
<div className="mb-6 flex justify-between items-center select-none"> <div className="mb-6 flex justify-between items-center select-none">
<p className="text-xs font-bold text-slate-400 dark:text-slate-500 uppercase tracking-widest"> <p className="text-xs font-bold text-slate-500 dark:text-slate-400 uppercase tracking-widest">
Total Encontrado: <span className="text-purple-650 dark:text-purple-400">{filteredBoards.length}</span> Placa(s) Total Encontrado: <span className="text-purple-650 dark:text-purple-400">{filteredBoards.length}</span> Placa(s)
</p> </p>
</div> </div>
{filteredBoards.length === 0 ? ( {filteredBoards.length === 0 ? (
<div className="text-center py-20 bg-white/80 dark:bg-slate-900/40 backdrop-blur-xl border border-slate-200/60 dark:border-slate-800/80 rounded-3xl p-8 shadow-md select-none"> <div className="text-center py-20 bg-white dark:bg-slate-900/40 backdrop-blur-xl border border-slate-300 dark:border-slate-800/80 rounded-3xl p-8 shadow-md select-none">
<Search className="w-12 h-12 text-slate-300 dark:text-slate-700 mx-auto mb-4" /> <Search className="w-12 h-12 text-slate-450 mx-auto mb-4" />
<p className="text-base font-display font-extrabold text-slate-800 dark:text-slate-200 mb-1">Nenhuma Placa Correspondente</p> <p className="text-base font-display font-extrabold text-slate-800 dark:text-slate-200 mb-1">Nenhuma Placa Correspondente</p>
<p className="text-xs text-slate-500 dark:text-slate-400">Tente ajustar ou redefinir seus filtros laterais para expandir a busca.</p> <p className="text-xs text-slate-500 dark:text-slate-400">Tente ajustar ou redefinir seus filtros laterais para expandir a busca.</p>
</div> </div>
+46 -20
View File
@@ -22,7 +22,8 @@ import {
Activity, Activity,
HelpCircle, HelpCircle,
Thermometer, Thermometer,
ShieldCheck ShieldCheck,
Package
} from "lucide-react"; } from "lucide-react";
// Category to Icon mapping // Category to Icon mapping
@@ -34,10 +35,16 @@ const categoryIcons = {
"Periféricos Digitais": <Sliders className="w-4 h-4 text-sky-500" />, "Periféricos Digitais": <Sliders className="w-4 h-4 text-sky-500" />,
"Periféricos Analógicos": <Activity className="w-4 h-4 text-emerald-500" />, "Periféricos Analógicos": <Activity className="w-4 h-4 text-emerald-500" />,
"Segurança": <ShieldCheck className="w-4 h-4 text-teal-500" />, "Segurança": <ShieldCheck className="w-4 h-4 text-teal-500" />,
"Disponibilidade e Ferramentas": <Package className="w-4 h-4 text-amber-500" />,
"Interfaces Especiais": <Activity className="w-4 h-4 text-cyan-500" />, "Interfaces Especiais": <Activity className="w-4 h-4 text-cyan-500" />,
"Especificações Ambientais": <Thermometer className="w-4 h-4 text-red-500" />, "Especificações Ambientais": <Thermometer className="w-4 h-4 text-red-500" />,
}; };
/* Lê "seguranca.ecc" além de chaves simples. */
function valorDe(objeto, caminho) {
return caminho.split(".").reduce((atual, parte) => atual?.[parte], objeto);
}
export default function Comparacao() { export default function Comparacao() {
const [selectedSeries, setSelectedSeries] = useState(["ESP32", "ESP32-S3"]); const [selectedSeries, setSelectedSeries] = useState(["ESP32", "ESP32-S3"]);
const [collapsedCategories, setCollapsedCategories] = useState({ const [collapsedCategories, setCollapsedCategories] = useState({
@@ -71,6 +78,13 @@ export default function Comparacao() {
{ key: "nome_completo", label: "Nome Completo" }, { key: "nome_completo", label: "Nome Completo" },
{ key: "descricao", label: "Descrição" }, { key: "descricao", label: "Descrição" },
]}, ]},
{ category: "Disponibilidade e Ferramentas", fields: [
{ key: "esp_idf_minimo", label: "ESP-IDF mínimo" },
{ key: "arduino_core", label: "Core Arduino" },
{ key: "depuracao", label: "Depuração / JTAG" },
{ key: "encapsulamento", label: "Encapsulamento" },
{ key: "modulos", label: "Módulos disponíveis" },
]},
{ category: "Processador", fields: [ { category: "Processador", fields: [
{ key: "arquitetura", label: "Arquitetura" }, { key: "arquitetura", label: "Arquitetura" },
{ key: "nucleos", label: "Núcleos" }, { key: "nucleos", label: "Núcleos" },
@@ -101,6 +115,7 @@ export default function Comparacao() {
{ key: "pwm", label: "PWM" }, { key: "pwm", label: "PWM" },
{ key: "can", label: "CAN" }, { key: "can", label: "CAN" },
{ key: "usb", label: "USB" }, { key: "usb", label: "USB" },
{ key: "depuracao", label: "Depuração / JTAG" },
{ key: "sdio", label: "SDIO" }, { key: "sdio", label: "SDIO" },
]}, ]},
{ category: "Periféricos Analógicos", fields: [ { category: "Periféricos Analógicos", fields: [
@@ -111,7 +126,18 @@ export default function Comparacao() {
{ key: "sensor_temperatura", label: "Sensor de Temperatura" }, { key: "sensor_temperatura", label: "Sensor de Temperatura" },
]}, ]},
{ category: "Segurança", fields: [ { category: "Segurança", fields: [
{ key: "aceleradores_cripto", label: "Aceleradores Criptográficos" }, { key: "seguranca.aes", label: "AES" },
{ key: "seguranca.sha", label: "SHA" },
{ key: "seguranca.rsa", label: "RSA / MPI" },
{ key: "seguranca.ecc", label: "ECC (curvas elípticas)" },
{ key: "seguranca.ecdsa", label: "ECDSA em hardware" },
{ key: "seguranca.hmac", label: "HMAC" },
{ key: "seguranca.assinatura_digital", label: "Assinatura Digital (DS)" },
{ key: "seguranca.key_manager", label: "Key Manager" },
{ key: "seguranca.rng", label: "Gerador aleatório (RNG)" },
{ key: "seguranca.criptografia_flash", label: "Criptografia de flash" },
{ key: "seguranca.secure_boot", label: "Secure Boot" },
{ key: "seguranca.protecao_dpa", label: "Proteção contra DPA" },
]}, ]},
{ category: "Interfaces Especiais", fields: [ { category: "Interfaces Especiais", fields: [
{ key: "lcd", label: "LCD" }, { key: "lcd", label: "LCD" },
@@ -128,7 +154,7 @@ export default function Comparacao() {
const renderValue = (value) => { const renderValue = (value) => {
if (!value || value === "Não") { if (!value || value === "Não") {
return <span className="text-slate-400 dark:text-slate-600 font-medium select-none">—</span>; return <span className="text-slate-500 dark:text-slate-400 font-medium select-none">—</span>;
} }
if (value === "Sim") { if (value === "Sim") {
return ( return (
@@ -141,7 +167,7 @@ export default function Comparacao() {
}; };
return ( return (
<div className="bg-gradient-to-br from-slate-50 via-white to-purple-50/40 dark:from-slate-950 dark:via-slate-950 dark:to-slate-900 min-h-screen text-slate-900 dark:text-slate-100 transition-colors duration-300"> <div className="bg-gradient-to-br from-slate-100 via-slate-50 to-purple-100/40 dark:from-slate-950 dark:via-slate-950 dark:to-slate-900 min-h-screen text-slate-900 dark:text-slate-100 transition-colors duration-300">
<Header /> <Header />
<main id="conteudo" className="px-6 pt-16 pb-24 max-w-7xl mx-auto"> <main id="conteudo" className="px-6 pt-16 pb-24 max-w-7xl mx-auto">
@@ -163,13 +189,13 @@ export default function Comparacao() {
{/* Model Selector Card */} {/* Model Selector Card */}
<section className="mb-12"> <section className="mb-12">
<div className="bg-white/80 dark:bg-slate-900/40 backdrop-blur-xl border border-slate-200/60 dark:border-slate-800/80 rounded-3xl p-6 md:p-8 shadow-2xl relative overflow-hidden"> <div className="bg-white dark:bg-slate-900/40 backdrop-blur-xl border border-slate-300 dark:border-slate-800/80 rounded-3xl p-6 md:p-8 shadow-2xl relative overflow-hidden">
<div className="flex flex-col md:flex-row md:items-center md:justify-between gap-4 mb-6"> <div className="flex flex-col md:flex-row md:items-center md:justify-between gap-4 mb-6">
<div> <div>
<h2 className="text-lg font-bold text-slate-800 dark:text-slate-100 mb-1"> <h2 className="text-lg font-bold text-slate-800 dark:text-slate-100 mb-1">
Escolha as Séries Escolha as Séries
</h2> </h2>
<p className="text-xs text-slate-400 dark:text-slate-500"> <p className="text-xs text-slate-500 dark:text-slate-400">
Selecione de 2 a até 4 modelos simultâneos para montar sua planilha de comparação. Selecione de 2 a até 4 modelos simultâneos para montar sua planilha de comparação.
</p> </p>
</div> </div>
@@ -189,12 +215,12 @@ export default function Comparacao() {
key={key} key={key}
onClick={() => toggleSeries(key)} onClick={() => toggleSeries(key)}
style={{ style={{
borderColor: isSelected ? seriesItem.cor : 'transparent' borderColor: isSelected ? seriesItem.cor : undefined
}} }}
className={`p-4 border rounded-2xl transition-all duration-300 cursor-pointer active:scale-95 text-center flex flex-col items-center justify-center shadow-xs ${ className={`p-4 border rounded-2xl transition-all duration-300 cursor-pointer active:scale-95 text-center flex flex-col items-center justify-center shadow-xs ${
isSelected isSelected
? 'bg-slate-100 dark:bg-slate-950 shadow-md scale-[1.02] border-2' ? 'bg-slate-100 dark:bg-slate-950 shadow-md scale-[1.02] border-2'
: 'border-slate-200/60 dark:border-slate-800/80 bg-white/70 dark:bg-slate-900/30 hover:border-purple-400/50 hover:bg-purple-500/5' : 'border-slate-300 dark:border-slate-800/80 bg-white dark:bg-slate-900/30 hover:border-purple-400/50 hover:bg-purple-500/5'
}`} }`}
> >
<div className="text-3xl mb-2 select-none transform hover:scale-110 transition-transform">{seriesItem.icone}</div> <div className="text-3xl mb-2 select-none transform hover:scale-110 transition-transform">{seriesItem.icone}</div>
@@ -214,7 +240,7 @@ export default function Comparacao() {
</section> </section>
{/* Side by side interactive Table */} {/* Side by side interactive Table */}
<div className="bg-white/80 dark:bg-slate-900/40 backdrop-blur-xl border border-slate-200/60 dark:border-slate-800/80 rounded-3xl overflow-hidden shadow-2xl"> <div className="bg-white dark:bg-slate-900/40 backdrop-blur-xl border border-slate-300 dark:border-slate-800/80 rounded-3xl overflow-hidden shadow-2xl">
<div className="overflow-x-auto scrollbar-thin"> <div className="overflow-x-auto scrollbar-thin">
<table className="w-full border-collapse"> <table className="w-full border-collapse">
<thead> <thead>
@@ -267,14 +293,14 @@ export default function Comparacao() {
{!isCollapsed && fields.map(({ key, label }) => ( {!isCollapsed && fields.map(({ key, label }) => (
<tr <tr
key={key} key={key}
className="border-b border-slate-100 dark:border-slate-800/40 hover:bg-purple-500/5 dark:hover:bg-purple-400/5 transition-colors group" className="border-b border-slate-200 dark:border-slate-800/40 hover:bg-purple-500/5 dark:hover:bg-purple-400/5 transition-colors group"
> >
<td className="px-6 py-4 font-bold text-slate-600 dark:text-slate-400 text-xs bg-white dark:bg-slate-900/10 sticky left-0 border-r border-slate-150/60 dark:border-slate-800/60 shrink-0 z-10"> <td className="px-6 py-4 font-bold text-slate-600 dark:text-slate-400 text-xs bg-white dark:bg-slate-900/10 sticky left-0 border-r border-slate-150/60 dark:border-slate-800/60 shrink-0 z-10">
{label} {label}
</td> </td>
{selectedSeries.map(seriesKey => { {selectedSeries.map(seriesKey => {
const seriesItem = seriesData[seriesKey]; const seriesItem = seriesData[seriesKey];
const value = seriesItem[key]; const value = valorDe(seriesItem, key);
return ( return (
<td key={seriesKey} className="px-6 py-4 text-center text-xs"> <td key={seriesKey} className="px-6 py-4 text-center text-xs">
{renderValue(value)} {renderValue(value)}
@@ -292,26 +318,26 @@ export default function Comparacao() {
</div> </div>
{/* Professional CTA Banner for the Catalog */} {/* Professional CTA Banner for the Catalog */}
<div className="my-20 bg-gradient-to-br from-slate-900 to-indigo-950 dark:from-slate-950 dark:to-purple-950/20 border border-slate-800/80 rounded-3xl p-8 md:p-12 shadow-2xl relative overflow-hidden"> <div className="my-20 bg-gradient-to-br from-indigo-50 to-purple-50 dark:from-slate-950 dark:to-purple-950/20 border border-indigo-200 dark:border-slate-800/80 rounded-3xl p-8 md:p-12 shadow-2xl relative overflow-hidden">
<div className="absolute right-0 bottom-0 w-96 h-96 bg-purple-500/5 rounded-full blur-3xl pointer-events-none" /> <div className="absolute right-0 bottom-0 w-96 h-96 bg-purple-500/5 rounded-full blur-3xl pointer-events-none" />
<div className="flex flex-col md:flex-row items-center justify-between gap-8 relative z-10"> <div className="flex flex-col md:flex-row items-center justify-between gap-8 relative z-10">
<div className="flex-1 max-w-2xl text-center md:text-left"> <div className="flex-1 max-w-2xl text-center md:text-left">
<div className="inline-flex items-center gap-2 bg-white/10 text-slate-350 px-3 py-1 rounded-full mb-4 text-xs font-semibold select-none"> <div className="inline-flex items-center gap-2 bg-indigo-500/10 text-indigo-700 dark:bg-white/10 dark:text-slate-350 px-3 py-1 rounded-full mb-4 text-xs font-semibold select-none">
<ShoppingBag className="w-3.5 h-3.5" /> <ShoppingBag className="w-3.5 h-3.5" />
<span>Hardware Adicional</span> <span>Hardware Adicional</span>
</div> </div>
<h3 className="text-2xl md:text-3xl font-display font-extrabold text-white mb-4"> <h3 className="text-2xl md:text-3xl font-display font-extrabold text-slate-900 dark:text-white mb-4">
Catálogo de Placas de Desenvolvimento Catálogo de Placas de Desenvolvimento
</h3> </h3>
<p className="text-sm text-slate-400 leading-relaxed"> <p className="text-sm text-slate-600 dark:text-slate-400 leading-relaxed">
Quer testar o chip selecionado na bancada? Compare placas de desenvolvimento com layouts de pinos mapeados, conexões de periféricos e links oficiais dos melhores fornecedores. Quer testar o chip selecionado na bancada? Compare placas de desenvolvimento com layouts de pinos mapeados, conexões de periféricos e links oficiais dos melhores fornecedores.
</p> </p>
</div> </div>
<Link <Link
href="/catalogo" href="/catalogo"
className="inline-flex items-center justify-center gap-2 bg-white text-slate-900 px-6 py-3.5 rounded-xl font-bold text-sm hover:bg-slate-100 transition-all duration-300 shrink-0 shadow-md active:scale-95" className="inline-flex items-center justify-center gap-2 bg-gradient-to-r from-purple-600 to-indigo-600 text-white px-6 py-3.5 rounded-xl font-bold text-sm hover:opacity-90 transition-all duration-300 shrink-0 shadow-md active:scale-95"
> >
<span>Acessar Catálogo</span> <span>Acessar Catálogo</span>
<ArrowRight className="w-4 h-4" /> <ArrowRight className="w-4 h-4" />
@@ -330,7 +356,7 @@ export default function Comparacao() {
return ( return (
<div <div
key={seriesKey} key={seriesKey}
className="bg-white/70 dark:bg-slate-900/40 backdrop-blur-xl border border-slate-200/80 dark:border-slate-800/80 p-5 rounded-2xl hover:border-purple-500/50 dark:hover:border-purple-400/50 hover:shadow-lg transition-all duration-300 flex flex-col justify-between" className="bg-white dark:bg-slate-900/40 backdrop-blur-xl border border-slate-300 dark:border-slate-800/80 p-5 rounded-2xl hover:border-purple-500/50 dark:hover:border-purple-400/50 hover:shadow-lg transition-all duration-300 flex flex-col justify-between"
> >
<div className="flex gap-4 items-start mb-4"> <div className="flex gap-4 items-start mb-4">
<div className="text-4xl select-none">{seriesItem.icone}</div> <div className="text-4xl select-none">{seriesItem.icone}</div>
@@ -340,7 +366,7 @@ export default function Comparacao() {
{seriesKey} {seriesKey}
</h4> </h4>
</Link> </Link>
<p className="text-[10px] text-slate-400 dark:text-slate-500 font-bold uppercase tracking-wider mt-1">Guia de Hardware</p> <p className="text-[10px] text-slate-500 dark:text-slate-400 font-bold uppercase tracking-wider mt-1">Guia de Hardware</p>
</div> </div>
</div> </div>
@@ -349,13 +375,13 @@ export default function Comparacao() {
href={seriesItem.datasheet} href={seriesItem.datasheet}
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"
className="inline-flex items-center justify-center gap-1.5 w-full text-center py-2 bg-slate-100 dark:bg-slate-950 hover:bg-purple-500/10 dark:hover:bg-purple-400/10 border border-slate-200 dark:border-slate-800 rounded-xl text-xs font-bold text-slate-650 dark:text-slate-350 hover:text-purple-600 dark:hover:text-purple-400 transition-colors" className="inline-flex items-center justify-center gap-1.5 w-full text-center py-2 bg-slate-100 dark:bg-slate-950 hover:bg-purple-500/10 dark:hover:bg-purple-400/10 border border-slate-300 dark:border-slate-800 rounded-xl text-xs font-bold text-slate-650 dark:text-slate-350 hover:text-purple-600 dark:hover:text-purple-400 transition-colors"
> >
<span>Datasheet Oficial</span> <span>Datasheet Oficial</span>
<ExternalLink className="w-3.5 h-3.5" /> <ExternalLink className="w-3.5 h-3.5" />
</a> </a>
) : ( ) : (
<span className="inline-flex items-center justify-center gap-1.5 w-full text-center py-2 bg-slate-100 dark:bg-slate-950 border border-slate-200 dark:border-slate-800 rounded-xl text-xs font-bold text-slate-400 dark:text-slate-500 select-none"> <span className="inline-flex items-center justify-center gap-1.5 w-full text-center py-2 bg-slate-100 dark:bg-slate-950 border border-slate-300 dark:border-slate-800 rounded-xl text-xs font-bold text-slate-500 dark:text-slate-400 select-none">
<span>Datasheet ainda não publicado</span> <span>Datasheet ainda não publicado</span>
</span> </span>
)} )}
+20 -20
View File
@@ -31,7 +31,7 @@ export default async function FrameworkDetail({ params }) {
const useCases = framework.casos_uso || []; const useCases = framework.casos_uso || [];
return ( return (
<div className="bg-gradient-to-br from-slate-50 via-white to-purple-50/40 dark:from-slate-950 dark:via-slate-950 dark:to-slate-900 min-h-screen text-slate-900 dark:text-slate-100 transition-colors duration-300"> <div className="bg-gradient-to-br from-slate-100 via-slate-50 to-purple-100/40 dark:from-slate-950 dark:via-slate-950 dark:to-slate-900 min-h-screen text-slate-900 dark:text-slate-100 transition-colors duration-300">
<Header /> <Header />
<main className="px-6 pt-16 pb-24 max-w-7xl mx-auto"> <main className="px-6 pt-16 pb-24 max-w-7xl mx-auto">
@@ -47,7 +47,7 @@ export default async function FrameworkDetail({ params }) {
{/* Top Info Banner Panel */} {/* Top Info Banner Panel */}
<div <div
className="bg-white/80 dark:bg-slate-900/40 backdrop-blur-xl rounded-3xl p-6 md:p-8 shadow-2xl border relative overflow-hidden" className="bg-white dark:bg-slate-900/40 backdrop-blur-xl rounded-3xl p-6 md:p-8 shadow-2xl border relative overflow-hidden"
style={{ borderColor: `${framework.cor}40` }} style={{ borderColor: `${framework.cor}40` }}
> >
<div className="flex flex-col lg:flex-row items-start lg:items-center justify-between gap-8"> <div className="flex flex-col lg:flex-row items-start lg:items-center justify-between gap-8">
@@ -57,7 +57,7 @@ export default async function FrameworkDetail({ params }) {
<h1 className="text-4xl font-display font-extrabold text-slate-850 dark:text-slate-100 tracking-tight leading-none mb-3"> <h1 className="text-4xl font-display font-extrabold text-slate-850 dark:text-slate-100 tracking-tight leading-none mb-3">
{framework.nome} {framework.nome}
</h1> </h1>
<p className="text-sm font-semibold text-slate-400 dark:text-slate-500 mb-4">{framework.nome_completo}</p> <p className="text-sm font-semibold text-slate-500 dark:text-slate-400 mb-4">{framework.nome_completo}</p>
<span <span
className="inline-block px-3 py-1 rounded-full text-[10px] font-bold uppercase tracking-widest select-none" className="inline-block px-3 py-1 rounded-full text-[10px] font-bold uppercase tracking-widest select-none"
style={{ backgroundColor: `${framework.cor}18`, color: framework.cor, border: `1px solid ${framework.cor}30` }} style={{ backgroundColor: `${framework.cor}18`, color: framework.cor, border: `1px solid ${framework.cor}30` }}
@@ -74,7 +74,7 @@ export default async function FrameworkDetail({ params }) {
href={framework.documentacao} href={framework.documentacao}
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"
className="inline-flex items-center justify-center gap-2 bg-slate-900 dark:bg-slate-100 text-white dark:text-slate-950 px-5 py-3 rounded-xl font-bold text-xs uppercase tracking-wider hover:opacity-90 transition-opacity shadow-lg" className="inline-flex items-center justify-center gap-2 bg-gradient-to-r from-purple-600 to-indigo-600 text-white px-5 py-3 rounded-xl font-bold text-xs uppercase tracking-wider hover:opacity-90 transition-opacity shadow-lg"
> >
<BookOpen className="w-4 h-4" /> <BookOpen className="w-4 h-4" />
<span>Documentação Oficial</span> <span>Documentação Oficial</span>
@@ -104,10 +104,10 @@ export default async function FrameworkDetail({ params }) {
{/* Framework Core Specifications Grid */} {/* Framework Core Specifications Grid */}
<div className="grid md:grid-cols-3 gap-6 mb-8 select-none animate-fadeIn"> <div className="grid md:grid-cols-3 gap-6 mb-8 select-none animate-fadeIn">
<div <div
className="bg-white/80 dark:bg-slate-900/40 backdrop-blur-xl rounded-2xl shadow-lg p-6 border-l-4" className="bg-white dark:bg-slate-900/40 backdrop-blur-xl rounded-2xl shadow-lg p-6 border-l-4"
style={{ borderColor: framework.cor }} style={{ borderColor: framework.cor }}
> >
<h3 className="text-[10px] font-bold text-slate-400 dark:text-slate-500 uppercase tracking-widest mb-1.5 flex items-center gap-1.5"> <h3 className="text-[10px] font-bold text-slate-500 dark:text-slate-400 uppercase tracking-widest mb-1.5 flex items-center gap-1.5">
<Code className="w-3.5 h-3.5 text-blue-500" /> <Code className="w-3.5 h-3.5 text-blue-500" />
<span>LINGUAGEM DE DESENVOLVIMENTO</span> <span>LINGUAGEM DE DESENVOLVIMENTO</span>
</h3> </h3>
@@ -116,10 +116,10 @@ export default async function FrameworkDetail({ params }) {
</div> </div>
<div <div
className="bg-white/80 dark:bg-slate-900/40 backdrop-blur-xl rounded-2xl shadow-lg p-6 border-l-4" className="bg-white dark:bg-slate-900/40 backdrop-blur-xl rounded-2xl shadow-lg p-6 border-l-4"
style={{ borderColor: framework.cor }} style={{ borderColor: framework.cor }}
> >
<h3 className="text-[10px] font-bold text-slate-400 dark:text-slate-500 uppercase tracking-widest mb-1.5 flex items-center gap-1.5"> <h3 className="text-[10px] font-bold text-slate-500 dark:text-slate-400 uppercase tracking-widest mb-1.5 flex items-center gap-1.5">
<Layers className="w-3.5 h-3.5 text-purple-500" /> <Layers className="w-3.5 h-3.5 text-purple-500" />
<span>CATEGORIA DO FRAMEWORK</span> <span>CATEGORIA DO FRAMEWORK</span>
</h3> </h3>
@@ -128,10 +128,10 @@ export default async function FrameworkDetail({ params }) {
</div> </div>
<div <div
className="bg-white/80 dark:bg-slate-900/40 backdrop-blur-xl rounded-2xl shadow-lg p-6 border-l-4" className="bg-white dark:bg-slate-900/40 backdrop-blur-xl rounded-2xl shadow-lg p-6 border-l-4"
style={{ borderColor: framework.cor }} style={{ borderColor: framework.cor }}
> >
<h3 className="text-[10px] font-bold text-slate-400 dark:text-slate-450 mb-1.5 flex items-center gap-1.5"> <h3 className="text-[10px] font-bold text-slate-500 dark:text-slate-400 mb-1.5 flex items-center gap-1.5">
<Target className="w-3.5 h-3.5 text-emerald-500" /> <Target className="w-3.5 h-3.5 text-emerald-500" />
<span>OBJETIVO PRINCIPAL</span> <span>OBJETIVO PRINCIPAL</span>
</h3> </h3>
@@ -142,7 +142,7 @@ export default async function FrameworkDetail({ params }) {
{/* side-by-side details block */} {/* side-by-side details block */}
<div className="grid lg:grid-cols-3 gap-6 mb-8 items-start"> <div className="grid lg:grid-cols-3 gap-6 mb-8 items-start">
{/* Left specs card */} {/* Left specs card */}
<div className="lg:col-span-2 bg-white/80 dark:bg-slate-900/45 backdrop-blur-xl rounded-3xl p-8 border border-slate-200/60 dark:border-slate-800/80 shadow-2xl space-y-8"> <div className="lg:col-span-2 bg-white dark:bg-slate-900/45 backdrop-blur-xl rounded-3xl p-8 border border-slate-300 dark:border-slate-800/80 shadow-2xl space-y-8">
<div> <div>
<h2 <h2
className="text-lg font-display font-extrabold text-slate-850 dark:text-slate-100 mb-6 pb-3 border-b flex items-center gap-2.5 select-none" className="text-lg font-display font-extrabold text-slate-850 dark:text-slate-100 mb-6 pb-3 border-b flex items-center gap-2.5 select-none"
@@ -183,7 +183,7 @@ export default async function FrameworkDetail({ params }) {
</div> </div>
{/* Right summary metadata card */} {/* Right summary metadata card */}
<div className="bg-white/80 dark:bg-slate-900/45 backdrop-blur-xl rounded-3xl p-8 border border-slate-200/60 dark:border-slate-800/80 shadow-2xl select-none"> <div className="bg-white dark:bg-slate-900/45 backdrop-blur-xl rounded-3xl p-8 border border-slate-300 dark:border-slate-800/80 shadow-2xl select-none">
<h2 <h2
className="text-lg font-display font-extrabold text-slate-850 dark:text-slate-100 mb-6 pb-3 border-b flex items-center gap-2.5" className="text-lg font-display font-extrabold text-slate-850 dark:text-slate-100 mb-6 pb-3 border-b flex items-center gap-2.5"
style={{ borderBottomColor: `${framework.cor}30` }} style={{ borderBottomColor: `${framework.cor}30` }}
@@ -193,20 +193,20 @@ export default async function FrameworkDetail({ params }) {
</h2> </h2>
<div className="space-y-4 text-xs font-bold text-slate-650 dark:text-slate-200"> <div className="space-y-4 text-xs font-bold text-slate-650 dark:text-slate-200">
<div className="flex justify-between items-center py-2 border-b border-slate-100 dark:border-slate-850/45"> <div className="flex justify-between items-center py-2 border-b border-slate-200 dark:border-slate-850/45">
<span className="text-slate-400 dark:text-slate-500 uppercase tracking-wider text-[9px]">Nome Oficial</span> <span className="text-slate-500 dark:text-slate-400 uppercase tracking-wider text-[9px]">Nome Oficial</span>
<span className="text-slate-800 dark:text-slate-200 font-extrabold">{framework.nome}</span> <span className="text-slate-800 dark:text-slate-200 font-extrabold">{framework.nome}</span>
</div> </div>
<div className="flex justify-between items-center py-2 border-b border-slate-100 dark:border-slate-850/45"> <div className="flex justify-between items-center py-2 border-b border-slate-200 dark:border-slate-850/45">
<span className="text-slate-400 dark:text-slate-500 uppercase tracking-wider text-[9px]">Linguagem</span> <span className="text-slate-500 dark:text-slate-400 uppercase tracking-wider text-[9px]">Linguagem</span>
<span className="text-slate-800 dark:text-slate-200 font-extrabold">{framework.linguagem}</span> <span className="text-slate-800 dark:text-slate-200 font-extrabold">{framework.linguagem}</span>
</div> </div>
<div className="flex justify-between items-center py-2 border-b border-slate-100 dark:border-slate-850/45"> <div className="flex justify-between items-center py-2 border-b border-slate-200 dark:border-slate-850/45">
<span className="text-slate-400 dark:text-slate-500 uppercase tracking-wider text-[9px]">Tipo / Suite</span> <span className="text-slate-500 dark:text-slate-400 uppercase tracking-wider text-[9px]">Tipo / Suite</span>
<span className="text-slate-800 dark:text-slate-200 font-extrabold">{framework.tipo}</span> <span className="text-slate-800 dark:text-slate-200 font-extrabold">{framework.tipo}</span>
</div> </div>
<div className="flex justify-between items-center py-2"> <div className="flex justify-between items-center py-2">
<span className="text-slate-400 dark:text-slate-500 uppercase tracking-wider text-[9px]">Cor Temática</span> <span className="text-slate-500 dark:text-slate-400 uppercase tracking-wider text-[9px]">Cor Temática</span>
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<code className="text-[10px] text-slate-500 uppercase">{framework.cor}</code> <code className="text-[10px] text-slate-500 uppercase">{framework.cor}</code>
<span className="inline-block w-4 h-4 rounded-md shadow-xs border border-white/20" style={{ backgroundColor: framework.cor }} /> <span className="inline-block w-4 h-4 rounded-md shadow-xs border border-white/20" style={{ backgroundColor: framework.cor }} />
@@ -230,7 +230,7 @@ export default async function FrameworkDetail({ params }) {
href={framework.documentacao} href={framework.documentacao}
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"
className="inline-flex items-center justify-center gap-2 bg-slate-900 dark:bg-slate-100 text-white dark:text-slate-950 px-6 py-3.5 rounded-xl font-bold text-xs uppercase tracking-wider hover:opacity-90 transition-opacity shadow-lg" className="inline-flex items-center justify-center gap-2 bg-gradient-to-r from-purple-600 to-indigo-600 text-white px-6 py-3.5 rounded-xl font-bold text-xs uppercase tracking-wider hover:opacity-90 transition-opacity shadow-lg"
> >
<BookOpen className="w-4 h-4" /> <BookOpen className="w-4 h-4" />
<span>Documentação do Desenvolvedor</span> <span>Documentação do Desenvolvedor</span>
+18 -18
View File
@@ -23,13 +23,13 @@ export default function Frameworks() {
const frameworks = Object.entries(frameworksData); const frameworks = Object.entries(frameworksData);
return ( return (
<div className="bg-gradient-to-br from-slate-50 via-white to-purple-50/40 dark:from-slate-950 dark:via-slate-950 dark:to-slate-900 min-h-screen text-slate-900 dark:text-slate-100 transition-colors duration-300"> <div className="bg-gradient-to-br from-slate-100 via-slate-50 to-purple-100/40 dark:from-slate-950 dark:via-slate-950 dark:to-slate-900 min-h-screen text-slate-900 dark:text-slate-100 transition-colors duration-300">
<Header /> <Header />
<main id="conteudo" className="px-6 pt-16 pb-24 max-w-7xl mx-auto"> <main id="conteudo" className="px-6 pt-16 pb-24 max-w-7xl mx-auto">
{/* Top Hero Section */} {/* Top Hero Section */}
<section className="text-center mb-16 select-none"> <section className="text-center mb-16 select-none">
<div className="inline-flex items-center gap-2 bg-white/70 dark:bg-slate-900/50 backdrop-blur-md px-4 py-2 rounded-full mb-6 border border-slate-200/60 dark:border-slate-800/60 shadow-xs"> <div className="inline-flex items-center gap-2 bg-white dark:bg-slate-900/50 backdrop-blur-md px-4 py-2 rounded-full mb-6 border border-slate-300 dark:border-slate-800/60 shadow-xs">
<Layers className="w-4 h-4 text-purple-500" /> <Layers className="w-4 h-4 text-purple-500" />
<span className="text-xs font-bold text-slate-700 dark:text-slate-350 uppercase tracking-wider">Ferramentas de Desenvolvimento</span> <span className="text-xs font-bold text-slate-700 dark:text-slate-350 uppercase tracking-wider">Ferramentas de Desenvolvimento</span>
</div> </div>
@@ -53,11 +53,11 @@ export default function Frameworks() {
className="group flex" className="group flex"
> >
<div <div
className="bg-white/70 dark:bg-slate-900/40 backdrop-blur-xl border border-slate-200/60 dark:border-slate-800/80 rounded-3xl shadow-lg hover:shadow-2xl transition-all duration-500 hover:-translate-y-1.5 flex flex-col justify-between overflow-hidden w-full" className="bg-white dark:bg-slate-900/40 backdrop-blur-xl border border-slate-300 dark:border-slate-800/80 rounded-3xl shadow-lg hover:shadow-2xl transition-all duration-500 hover:-translate-y-1.5 flex flex-col justify-between overflow-hidden w-full"
style={{ borderTop: `4px solid ${framework.cor}` }} style={{ borderTop: `4px solid ${framework.cor}` }}
> >
<div className="p-6 pb-4 border-b border-slate-100 dark:border-slate-850/40 flex-1"> <div className="p-6 pb-4 border-b border-slate-200 dark:border-slate-850/40 flex-1">
<div className="flex items-center justify-between mb-4"> <div className="flex items-center justify-between mb-4">
<span className="text-5xl select-none filter drop-shadow-md">{framework.icone}</span> <span className="text-5xl select-none filter drop-shadow-md">{framework.icone}</span>
<div className="flex flex-col gap-1 items-end select-none"> <div className="flex flex-col gap-1 items-end select-none">
@@ -78,7 +78,7 @@ export default function Frameworks() {
{framework.nome} {framework.nome}
</h2> </h2>
<p className="text-[10px] text-slate-400 dark:text-slate-500 font-bold uppercase tracking-wider mb-4 leading-none select-none"> <p className="text-[10px] text-slate-500 dark:text-slate-400 font-bold uppercase tracking-wider mb-4 leading-none select-none">
{framework.nome_completo} {framework.nome_completo}
</p> </p>
@@ -91,7 +91,7 @@ export default function Frameworks() {
<div className="space-y-4"> <div className="space-y-4">
{/* Main Feature */} {/* Main Feature */}
<div> <div>
<h4 className="text-[9px] font-bold text-slate-400 dark:text-slate-450 mb-1.5 uppercase tracking-widest flex items-center gap-1.5"> <h4 className="text-[9px] font-bold text-slate-500 dark:text-slate-400 mb-1.5 uppercase tracking-widest flex items-center gap-1.5">
<Target className="w-3.5 h-3.5 text-blue-500" /> <Target className="w-3.5 h-3.5 text-blue-500" />
<span>Objetivo Principal</span> <span>Objetivo Principal</span>
</h4> </h4>
@@ -102,7 +102,7 @@ export default function Frameworks() {
{/* Key Specs */} {/* Key Specs */}
<div> <div>
<h4 className="text-[9px] font-bold text-slate-400 dark:text-slate-450 mb-2 uppercase tracking-widest flex items-center gap-1.5"> <h4 className="text-[9px] font-bold text-slate-500 dark:text-slate-400 mb-2 uppercase tracking-widest flex items-center gap-1.5">
<Zap className="w-3.5 h-3.5 text-amber-500" /> <Zap className="w-3.5 h-3.5 text-amber-500" />
<span>Recursos Chave</span> <span>Recursos Chave</span>
</h4> </h4>
@@ -118,13 +118,13 @@ export default function Frameworks() {
{/* Use Cases tags */} {/* Use Cases tags */}
<div> <div>
<h4 className="text-[9px] font-bold text-slate-400 dark:text-slate-450 mb-2 uppercase tracking-widest flex items-center gap-1.5"> <h4 className="text-[9px] font-bold text-slate-500 dark:text-slate-400 mb-2 uppercase tracking-widest flex items-center gap-1.5">
<Compass className="w-3.5 h-3.5 text-emerald-500" /> <Compass className="w-3.5 h-3.5 text-emerald-500" />
<span>Aplicações Ideais</span> <span>Aplicações Ideais</span>
</h4> </h4>
<div className="flex flex-wrap gap-1.5"> <div className="flex flex-wrap gap-1.5">
{framework.casos_uso.slice(0, 3).map((caso, idx) => ( {framework.casos_uso.slice(0, 3).map((caso, idx) => (
<span key={idx} className="text-[9px] font-bold bg-slate-100 dark:bg-slate-900/60 text-slate-650 dark:text-slate-200 px-2.5 py-1 rounded-md border border-slate-200/40 dark:border-slate-800/80"> <span key={idx} className="text-[9px] font-bold bg-slate-100 dark:bg-slate-900/60 text-slate-650 dark:text-slate-200 px-2.5 py-1 rounded-md border border-slate-300 dark:border-slate-800/80">
{caso} {caso}
</span> </span>
))} ))}
@@ -133,7 +133,7 @@ export default function Frameworks() {
</div> </div>
{/* CTA Link */} {/* CTA Link */}
<div className="pt-4 border-t border-slate-100 dark:border-slate-850/40 mt-auto flex items-center justify-between text-xs font-bold text-slate-700 dark:text-slate-300 group-hover:text-purple-650 dark:group-hover:text-purple-400 transition-colors"> <div className="pt-4 border-t border-slate-200 dark:border-slate-850/40 mt-auto flex items-center justify-between text-xs font-bold text-slate-700 dark:text-slate-300 group-hover:text-purple-650 dark:group-hover:text-purple-400 transition-colors">
<span>Acessar Detalhes e Exemplos</span> <span>Acessar Detalhes e Exemplos</span>
<ArrowRight className="w-4 h-4 transform group-hover:translate-x-1.5 transition-transform" /> <ArrowRight className="w-4 h-4 transform group-hover:translate-x-1.5 transition-transform" />
</div> </div>
@@ -146,7 +146,7 @@ export default function Frameworks() {
{/* Bottom Informative Cards Section */} {/* Bottom Informative Cards Section */}
<div className="mt-16 grid md:grid-cols-2 gap-8 select-none"> <div className="mt-16 grid md:grid-cols-2 gap-8 select-none">
{/* Card 1: Beginner Guide */} {/* Card 1: Beginner Guide */}
<div className="bg-white/80 dark:bg-slate-900/40 backdrop-blur-xl rounded-3xl shadow-xl p-8 border border-slate-200/60 dark:border-slate-800/80 flex flex-col justify-between relative overflow-hidden"> <div className="bg-white dark:bg-slate-900/40 backdrop-blur-xl rounded-3xl shadow-xl p-8 border border-slate-300 dark:border-slate-800/80 flex flex-col justify-between relative overflow-hidden">
<div> <div>
<div className="flex items-center gap-3.5 mb-5"> <div className="flex items-center gap-3.5 mb-5">
<div className="w-12 h-12 bg-blue-500/10 border border-blue-500/20 rounded-2xl flex items-center justify-center text-blue-500"> <div className="w-12 h-12 bg-blue-500/10 border border-blue-500/20 rounded-2xl flex items-center justify-center text-blue-500">
@@ -169,7 +169,7 @@ export default function Frameworks() {
</p> </p>
</div> </div>
<div className="flex flex-wrap gap-2.5 pt-4 border-t border-slate-100 dark:border-slate-850/40 mt-auto"> <div className="flex flex-wrap gap-2.5 pt-4 border-t border-slate-200 dark:border-slate-850/40 mt-auto">
<Link href="/frameworks/Arduino" className="inline-flex items-center gap-1.5 px-4 py-2.5 bg-blue-500/10 text-blue-600 dark:text-blue-400 border border-blue-500/20 rounded-xl text-xs font-bold hover:bg-blue-500/20 transition-all"> <Link href="/frameworks/Arduino" className="inline-flex items-center gap-1.5 px-4 py-2.5 bg-blue-500/10 text-blue-600 dark:text-blue-400 border border-blue-500/20 rounded-xl text-xs font-bold hover:bg-blue-500/20 transition-all">
<span>Arduino</span> <span>Arduino</span>
<ArrowRight className="w-3.5 h-3.5" /> <ArrowRight className="w-3.5 h-3.5" />
@@ -186,7 +186,7 @@ export default function Frameworks() {
</div> </div>
{/* Card 2: Specialized Solutions */} {/* Card 2: Specialized Solutions */}
<div className="bg-white/80 dark:bg-slate-900/40 backdrop-blur-xl rounded-3xl shadow-xl p-8 border border-slate-200/60 dark:border-slate-800/80 flex flex-col justify-between relative overflow-hidden"> <div className="bg-white dark:bg-slate-900/40 backdrop-blur-xl rounded-3xl shadow-xl p-8 border border-slate-300 dark:border-slate-800/80 flex flex-col justify-between relative overflow-hidden">
<div> <div>
<div className="flex items-center gap-3.5 mb-5"> <div className="flex items-center gap-3.5 mb-5">
<div className="w-12 h-12 bg-purple-500/10 border border-purple-500/20 rounded-2xl flex items-center justify-center text-purple-500"> <div className="w-12 h-12 bg-purple-500/10 border border-purple-500/20 rounded-2xl flex items-center justify-center text-purple-500">
@@ -210,20 +210,20 @@ export default function Frameworks() {
</p> </p>
</div> </div>
<div className="flex flex-wrap gap-2.5 pt-4 border-t border-slate-100 dark:border-slate-850/40 mt-auto"> <div className="flex flex-wrap gap-2.5 pt-4 border-t border-slate-200 dark:border-slate-850/40 mt-auto">
<Link href="/frameworks/ESP-ADF" className="inline-flex items-center gap-1.5 px-3.5 py-2 bg-slate-150/45 dark:bg-slate-850 text-slate-700 dark:text-slate-300 border border-slate-200/60 dark:border-slate-850/60 rounded-xl text-xs font-bold hover:bg-slate-200 dark:hover:bg-slate-800 transition-all"> <Link href="/frameworks/ESP-ADF" className="inline-flex items-center gap-1.5 px-3.5 py-2 bg-slate-150/45 dark:bg-slate-850 text-slate-700 dark:text-slate-300 border border-slate-300 dark:border-slate-850/60 rounded-xl text-xs font-bold hover:bg-slate-200 dark:hover:bg-slate-800 transition-all">
<Music className="w-3.5 h-3.5 text-blue-500" /> <Music className="w-3.5 h-3.5 text-blue-500" />
<span>Áudio</span> <span>Áudio</span>
</Link> </Link>
<Link href="/frameworks/ESP-WHO" className="inline-flex items-center gap-1.5 px-3.5 py-2 bg-slate-150/45 dark:bg-slate-850 text-slate-700 dark:text-slate-300 border border-slate-200/60 dark:border-slate-850/60 rounded-xl text-xs font-bold hover:bg-slate-200 dark:hover:bg-slate-800 transition-all"> <Link href="/frameworks/ESP-WHO" className="inline-flex items-center gap-1.5 px-3.5 py-2 bg-slate-150/45 dark:bg-slate-850 text-slate-700 dark:text-slate-300 border border-slate-300 dark:border-slate-850/60 rounded-xl text-xs font-bold hover:bg-slate-200 dark:hover:bg-slate-800 transition-all">
<Camera className="w-3.5 h-3.5 text-orange-500" /> <Camera className="w-3.5 h-3.5 text-orange-500" />
<span>Visão</span> <span>Visão</span>
</Link> </Link>
<Link href="/frameworks/ESP-NN" className="inline-flex items-center gap-1.5 px-3.5 py-2 bg-slate-150/45 dark:bg-slate-850 text-slate-700 dark:text-slate-300 border border-slate-200/60 dark:border-slate-850/60 rounded-xl text-xs font-bold hover:bg-slate-200 dark:hover:bg-slate-800 transition-all"> <Link href="/frameworks/ESP-NN" className="inline-flex items-center gap-1.5 px-3.5 py-2 bg-slate-150/45 dark:bg-slate-850 text-slate-700 dark:text-slate-300 border border-slate-300 dark:border-slate-850/60 rounded-xl text-xs font-bold hover:bg-slate-200 dark:hover:bg-slate-800 transition-all">
<Brain className="w-3.5 h-3.5 text-teal-500" /> <Brain className="w-3.5 h-3.5 text-teal-500" />
<span>Inteligência Artificial</span> <span>Inteligência Artificial</span>
</Link> </Link>
<Link href="/frameworks/ESP-Matter" className="inline-flex items-center gap-1.5 px-3.5 py-2 bg-slate-150/45 dark:bg-slate-850 text-slate-700 dark:text-slate-300 border border-slate-200/60 dark:border-slate-850/60 rounded-xl text-xs font-bold hover:bg-slate-200 dark:hover:bg-slate-800 transition-all"> <Link href="/frameworks/ESP-Matter" className="inline-flex items-center gap-1.5 px-3.5 py-2 bg-slate-150/45 dark:bg-slate-850 text-slate-700 dark:text-slate-300 border border-slate-300 dark:border-slate-850/60 rounded-xl text-xs font-bold hover:bg-slate-200 dark:hover:bg-slate-800 transition-all">
<Home className="w-3.5 h-3.5 text-yellow-500" /> <Home className="w-3.5 h-3.5 text-yellow-500" />
<span>Matter</span> <span>Matter</span>
</Link> </Link>
+31 -3
View File
@@ -8,6 +8,26 @@
--color-brand-purple: #8b5cf6; --color-brand-purple: #8b5cf6;
--color-brand-blue: #3b82f6; --color-brand-blue: #3b82f6;
--color-brand-pink: #ec4899; --color-brand-pink: #ec4899;
/* ---------------------------------------------------------------------
Meios-tons de slate.
A escala do Tailwind anda de 100 em 100 (50, 100, 200 ... 950), então
classes como text-slate-350 ou border-slate-850 — usadas em ~190 pontos
do projeto — não geravam CSS nenhum: o elemento herdava a cor do pai e
ficava diferente em cada tema, sem padrão.
Cada valor abaixo é o ponto médio exato entre os dois degraus oficiais
vizinhos, então a intenção original de cada classe é preservada.
--------------------------------------------------------------------- */
--color-slate-150: #eaeef4; /* entre 100 e 200 */
--color-slate-250: #d6dee8; /* entre 200 e 300 */
--color-slate-350: #b0bccc; /* entre 300 e 400 */
--color-slate-450: #7c8ca2; /* entre 400 e 500 */
--color-slate-550: #56647a; /* entre 500 e 600 */
--color-slate-650: #3d4b5f; /* entre 600 e 700 */
--color-slate-750: #283548; /* entre 700 e 800 */
--color-slate-850: #162032; /* entre 800 e 900 */
} }
/* Enable class-based dark mode selector in Tailwind v4 */ /* Enable class-based dark mode selector in Tailwind v4 */
@@ -22,13 +42,21 @@
@apply bg-slate-50 text-slate-900 transition-colors duration-300; @apply bg-slate-50 text-slate-900 transition-colors duration-300;
} }
body.dark { /* next-themes aplica a classe .dark no <html>, não no <body>.
O seletor body.dark nunca casava, então o fundo escuro do body vinha
só das classes utilitárias do layout. */
.dark body {
@apply bg-slate-950 text-slate-50; @apply bg-slate-950 text-slate-50;
} }
/* Sleek selection highlighting */ /* Seleção de texto: o texto precisa de contraste contra o realce em cada
tema. Roxo claro sobre fundo claro era ilegível no tema claro. */
::selection { ::selection {
@apply bg-purple-500/30 text-purple-200; @apply bg-purple-500/30 text-purple-950;
}
.dark ::selection {
@apply bg-purple-500/40 text-purple-50;
} }
/* Scrollbar customization for a premium, non-default OS look */ /* Scrollbar customization for a premium, non-default OS look */
+38 -3
View File
@@ -41,8 +41,43 @@ export default function Glossario() {
}, },
{ {
termo: "Aceleradores Criptográficos", termo: "Aceleradores Criptográficos",
definicao: "Blocos de hardware dedicados que executam algoritmos de criptografia (AES, SHA, RSA, ECC/ECDSA, HMAC) muito mais rápido e com menor consumo do que a CPU, além de sustentarem recursos como Secure Boot, criptografia de flash e assinatura digital.", definicao: "Blocos de hardware dedicados que executam algoritmos de criptografia muito mais rápido e com menor consumo do que a CPU. Praticamente toda série tem algum, mas o conjunto varia bastante — por isso vale olhar item por item.",
exemplo: "ESP32-C5 e ESP32-P4 incluem AES, SHA, RSA, ECC/ECDSA e Key Manager para provisionamento seguro de chaves.", exemplo: "ESP32-C2 e ESP32-C61 não têm acelerador AES: só SHA e curvas elípticas.",
},
{
termo: "ECC (Criptografia de Curvas Elípticas)",
definicao: "Família de algoritmos de chave pública que atinge a mesma segurança do RSA com chaves muito menores, o que economiza memória e tempo em microcontrolador. As curvas usadas são identificadas por nome, como P-256.",
exemplo: "ESP32, ESP32-S2, ESP32-S3 e ESP32-C3 não têm ECC em hardware; ESP32-C5, P4, S31 e H4 chegam à curva P-384.",
},
{
termo: "ECDSA",
definicao: "Algoritmo de assinatura digital baseado em curvas elípticas. Ter o periférico ECDSA no silício significa assinar e verificar sem carregar a CPU — é o que o Matter exige para o atestado de dispositivo.",
exemplo: "Só ESP32-S31, C5, C61, P4, H2 e H4 têm ECDSA em hardware.",
},
{
termo: "Key Manager",
definicao: "Periférico que provisiona e usa chaves criptográficas sem que elas fiquem legíveis para o firmware. Mesmo que alguém extraia a imagem do firmware, a chave privada não está lá.",
exemplo: "Disponível apenas no ESP32-S31, ESP32-C5 e ESP32-P4.",
},
{
termo: "Secure Boot",
definicao: "Verificação da assinatura do firmware durante o boot: o chip se recusa a executar código que não tenha sido assinado pela sua chave. A versão V2 usa RSA-3072 ou ECDSA; a V1, do ESP32 original, é um esquema mais antigo.",
exemplo: "ESP32 tem apenas o Secure Boot V1; do ESP32-S2 em diante todos têm V2.",
},
{
termo: "Criptografia de Flash",
definicao: "Cifra o conteúdo da memória flash externa, impedindo que alguém leia o firmware soldando um gravador no chip de memória. O esquema atual usa XTS-AES.",
exemplo: "XTS-AES-128 na maioria das séries; ESP32-S2, S3, S31, C5, P4 e H4 também suportam XTS-AES-256.",
},
{
termo: "Assinatura Digital (DS)",
definicao: "Periférico que assina dados usando uma chave RSA guardada em eFuse de forma que o firmware nunca a enxerga. É o antecessor mais simples do Key Manager.",
exemplo: "Ausente no ESP32 original, no C2, no C61 e no H4.",
},
{
termo: "Proteção contra DPA",
definicao: "Defesa contra Differential Power Analysis, ataque que deduz a chave secreta medindo variações no consumo de energia do chip durante a operação criptográfica. Exige acesso físico ao dispositivo.",
exemplo: "Presente no ESP32-C5, C6, C61 e H2.",
}, },
], ],
}, },
@@ -334,7 +369,7 @@ export default function Glossario() {
<main id="conteudo" className="px-4 pt-16 pb-20 max-w-7xl mx-auto"> <main id="conteudo" className="px-4 pt-16 pb-20 max-w-7xl mx-auto">
{/* Hero Section */} {/* Hero Section */}
<section className="text-center mb-16"> <section className="text-center mb-16">
<div className="inline-flex items-center gap-2 bg-white/70 backdrop-blur-sm px-4 py-2 rounded-full mb-6 border border-purple-200 shadow-sm"> <div className="inline-flex items-center gap-2 bg-white backdrop-blur-sm px-4 py-2 rounded-full mb-6 border border-purple-200 shadow-sm">
<span className="text-xl">📖</span> <span className="text-xl">📖</span>
<span className="text-sm font-semibold text-gray-700">Aprenda os Conceitos</span> <span className="text-sm font-semibold text-gray-700">Aprenda os Conceitos</span>
</div> </div>
+1 -1
View File
@@ -23,7 +23,7 @@ export const metadata = {
export default function RootLayout({ children }) { export default function RootLayout({ children }) {
return ( return (
<html lang="pt-BR" className={`${outfit.variable} ${inter.variable}`} suppressHydrationWarning> <html lang="pt-BR" className={`${outfit.variable} ${inter.variable}`} suppressHydrationWarning>
<body className="font-sans antialiased bg-slate-50 text-slate-900 dark:bg-slate-950 dark:text-slate-50 transition-colors duration-300"> <body className="font-sans antialiased bg-slate-50 text-slate-900 dark:bg-slate-950 dark:text-slate-50 transition-colors duration-300" suppressHydrationWarning>
<Providers> <Providers>
{children} {children}
</Providers> </Providers>
+7 -7
View File
@@ -85,7 +85,7 @@ export default function Home() {
]; ];
return ( return (
<div className="bg-gradient-to-br from-slate-50 via-white to-purple-50/40 dark:from-slate-950 dark:via-slate-950 dark:to-slate-900 min-h-screen text-slate-900 dark:text-slate-100 transition-colors duration-300"> <div className="bg-gradient-to-br from-slate-100 via-slate-50 to-purple-100/40 dark:from-slate-950 dark:via-slate-950 dark:to-slate-900 min-h-screen text-slate-900 dark:text-slate-100 transition-colors duration-300">
<Header /> <Header />
<main className="px-6 pt-16 pb-24 max-w-7xl mx-auto"> <main className="px-6 pt-16 pb-24 max-w-7xl mx-auto">
@@ -115,7 +115,7 @@ export default function Home() {
</Link> </Link>
<Link <Link
className="group inline-flex items-center gap-2.5 bg-slate-900 hover:bg-slate-800 dark:bg-slate-800 dark:hover:bg-slate-700 text-white px-7 py-3.5 rounded-xl font-bold text-sm shadow-md hover:-translate-y-0.5 transition-all duration-300 cursor-pointer active:scale-95" className="group inline-flex items-center gap-2.5 bg-blue-600 hover:bg-blue-700 text-white px-7 py-3.5 rounded-xl font-bold text-sm shadow-md hover:-translate-y-0.5 transition-all duration-300 cursor-pointer active:scale-95"
href="/series" href="/series"
> >
<Cpu className="w-4 h-4" /> <Cpu className="w-4 h-4" />
@@ -123,7 +123,7 @@ export default function Home() {
</Link> </Link>
<Link <Link
className="group inline-flex items-center gap-2.5 bg-white dark:bg-slate-900 text-slate-700 dark:text-slate-300 px-7 py-3.5 rounded-xl font-bold text-sm shadow-xs border border-slate-200 dark:border-slate-800 hover:border-purple-500 dark:hover:border-purple-400 hover:text-purple-600 dark:hover:text-purple-400 hover:-translate-y-0.5 transition-all duration-300 cursor-pointer active:scale-95" className="group inline-flex items-center gap-2.5 bg-white dark:bg-slate-900 text-slate-700 dark:text-slate-300 px-7 py-3.5 rounded-xl font-bold text-sm shadow-xs border border-slate-300 dark:border-slate-800 hover:border-purple-500 dark:hover:border-purple-400 hover:text-purple-600 dark:hover:text-purple-400 hover:-translate-y-0.5 transition-all duration-300 cursor-pointer active:scale-95"
href="/frameworks" href="/frameworks"
> >
<Terminal className="w-4 h-4" /> <Terminal className="w-4 h-4" />
@@ -131,7 +131,7 @@ export default function Home() {
</Link> </Link>
<Link <Link
className="group inline-flex items-center gap-2.5 bg-white dark:bg-slate-900 text-slate-700 dark:text-slate-300 px-7 py-3.5 rounded-xl font-bold text-sm shadow-xs border border-slate-200 dark:border-slate-800 hover:border-purple-500 dark:hover:border-purple-400 hover:text-purple-600 dark:hover:text-purple-400 hover:-translate-y-0.5 transition-all duration-300 cursor-pointer active:scale-95" className="group inline-flex items-center gap-2.5 bg-white dark:bg-slate-900 text-slate-700 dark:text-slate-300 px-7 py-3.5 rounded-xl font-bold text-sm shadow-xs border border-slate-300 dark:border-slate-800 hover:border-purple-500 dark:hover:border-purple-400 hover:text-purple-600 dark:hover:text-purple-400 hover:-translate-y-0.5 transition-all duration-300 cursor-pointer active:scale-95"
href="/catalogo" href="/catalogo"
> >
<ShoppingBag className="w-4 h-4" /> <ShoppingBag className="w-4 h-4" />
@@ -155,7 +155,7 @@ export default function Home() {
{resources.map((resource) => ( {resources.map((resource) => (
<div <div
key={resource.name} key={resource.name}
className="group bg-white/70 dark:bg-slate-900/40 backdrop-blur-md p-6 rounded-2xl border border-slate-200/60 dark:border-slate-800/80 hover:border-purple-500/40 dark:hover:border-purple-400/40 shadow-xs hover:shadow-xl hover:shadow-purple-500/5 hover:-translate-y-1 transition-all duration-300" className="group bg-white dark:bg-slate-900/40 backdrop-blur-md p-6 rounded-2xl border border-slate-300 dark:border-slate-800/80 hover:border-purple-500/40 dark:hover:border-purple-400/40 shadow-xs hover:shadow-xl hover:shadow-purple-500/5 hover:-translate-y-1 transition-all duration-300"
> >
<div className="flex items-start gap-4"> <div className="flex items-start gap-4">
<div className={`w-12 h-12 rounded-xl flex items-center justify-center shrink-0 transform group-hover:scale-105 transition-transform duration-300 shadow-xs ${resource.colorClass}`}> <div className={`w-12 h-12 rounded-xl flex items-center justify-center shrink-0 transform group-hover:scale-105 transition-transform duration-300 shadow-xs ${resource.colorClass}`}>
@@ -177,7 +177,7 @@ export default function Home() {
{/* Ciência Embarcada Section */} {/* Ciência Embarcada Section */}
<section className="mb-8"> <section className="mb-8">
<div className="bg-gradient-to-br from-white to-purple-50/50 dark:from-slate-900/40 dark:to-purple-950/10 rounded-3xl shadow-xs border border-slate-200/80 dark:border-slate-800/80 p-8 md:p-12 relative overflow-hidden"> <div className="bg-gradient-to-br from-white to-purple-50/50 dark:from-slate-900/40 dark:to-purple-950/10 rounded-3xl shadow-xs border border-slate-300 dark:border-slate-800/80 p-8 md:p-12 relative overflow-hidden">
{/* Background soft glow decoration */} {/* Background soft glow decoration */}
<div className="absolute right-0 bottom-0 w-80 h-80 bg-purple-500/5 rounded-full blur-3xl pointer-events-none" /> <div className="absolute right-0 bottom-0 w-80 h-80 bg-purple-500/5 rounded-full blur-3xl pointer-events-none" />
@@ -223,7 +223,7 @@ export default function Home() {
</div> </div>
{/* Disclaimer */} {/* Disclaimer */}
<div className="border-t border-slate-200/80 dark:border-slate-800/80 pt-8 mt-10"> <div className="border-t border-slate-300 dark:border-slate-800/80 pt-8 mt-10">
<div className="bg-slate-100/60 dark:bg-slate-900/60 border-l-4 border-purple-500 p-5 rounded-r-2xl"> <div className="bg-slate-100/60 dark:bg-slate-900/60 border-l-4 border-purple-500 p-5 rounded-r-2xl">
<div className="flex gap-4"> <div className="flex gap-4">
<Info className="w-5 h-5 text-purple-600 dark:text-purple-400 mt-0.5 shrink-0" /> <Info className="w-5 h-5 text-purple-600 dark:text-purple-400 mt-0.5 shrink-0" />
+89 -55
View File
@@ -33,7 +33,9 @@ import {
BookOpen, BookOpen,
Compass, Compass,
Smartphone, Smartphone,
ShieldCheck ShieldCheck,
KeyRound,
Lock
} from "lucide-react"; } from "lucide-react";
// Dynamic icon mapper to convert choices to sleek Lucide vectors // Dynamic icon mapper to convert choices to sleek Lucide vectors
@@ -48,7 +50,7 @@ const iconMap = {
"connectivity_wifi-ble": <Wifi className="w-6 h-6 text-blue-500" />, "connectivity_wifi-ble": <Wifi className="w-6 h-6 text-blue-500" />,
"connectivity_mesh": <Network className="w-6 h-6 text-orange-500" />, "connectivity_mesh": <Network className="w-6 h-6 text-orange-500" />,
"connectivity_ethernet": <Cable className="w-6 h-6 text-emerald-500" />, "connectivity_ethernet": <Cable className="w-6 h-6 text-emerald-500" />,
"connectivity_none": <Ban className="w-6 h-6 text-slate-450 dark:text-slate-500" />, "connectivity_none": <Ban className="w-6 h-6 text-slate-500 dark:text-slate-450" />,
// Alimentacao // Alimentacao
"power_battery": <Battery className="w-6 h-6 text-green-500" />, "power_battery": <Battery className="w-6 h-6 text-green-500" />,
@@ -63,15 +65,15 @@ const iconMap = {
), ),
"hardware_usb-native": <Cable className="w-6 h-6 text-teal-500" />, "hardware_usb-native": <Cable className="w-6 h-6 text-teal-500" />,
"hardware_many-gpios": <Sliders className="w-6 h-6 text-sky-500" />, "hardware_many-gpios": <Sliders className="w-6 h-6 text-sky-500" />,
"hardware_basic": <HelpCircle className="w-6 h-6 text-slate-450 dark:text-slate-550" />, "hardware_basic": <HelpCircle className="w-6 h-6 text-slate-500 dark:text-slate-400" />,
// IA // IA
"ai_ai-ml": <Bot className="w-6 h-6 text-pink-500" />, "ai_ai-ml": <Bot className="w-6 h-6 text-pink-500" />,
"ai_standard": <Ban className="w-6 h-6 text-slate-450 dark:text-slate-550" />, "ai_standard": <Ban className="w-6 h-6 text-slate-500 dark:text-slate-400" />,
// Segurança // Segurança
"security_critical": <ShieldCheck className="w-6 h-6 text-teal-500" />, "security_critical": <ShieldCheck className="w-6 h-6 text-teal-500" />,
"security_standard": <HelpCircle className="w-6 h-6 text-slate-450 dark:text-slate-550" />, "security_standard": <HelpCircle className="w-6 h-6 text-slate-500 dark:text-slate-400" />,
}; };
const getOptionIcon = (questionId, value) => { const getOptionIcon = (questionId, value) => {
@@ -147,11 +149,13 @@ export default function Seletor() {
}, },
{ {
id: "security", id: "security",
question: "Qual o nível de segurança criptográfica exigido pelo produto?", question: "Qual requisito de segurança o produto precisa atender?",
description: "Aceleradores criptográficos no silício executam AES, SHA, RSA e curvas elípticas em hardware, viabilizando Secure Boot, criptografia de flash e provisionamento seguro de chaves sem penalizar a CPU.", description: "As séries diferem bastante aqui: nem todas têm curvas elípticas (ECC), ECDSA em hardware ou Key Manager, e duas delas sequer têm acelerador AES. Escolha o requisito mais forte do seu projeto.",
options: [ options: [
{ value: "critical", label: "Crítico (Matter, pagamentos, provisionamento seguro de chaves)", icon: "security_critical" }, { value: "matter", label: "Atestado de dispositivo para Matter ou assinatura digital (ECDSA)", icon: "security_matter" },
{ value: "standard", label: "Padrão (TLS comum e projetos sem requisitos regulatórios)", icon: "security_standard" }, { value: "chaves", label: "Chave privada que nunca pode ser lida pelo firmware (Key Manager)", icon: "security_chaves" },
{ value: "boot", label: "Boot seguro e flash criptografada contra cópia do firmware", icon: "security_boot" },
{ value: "tls", label: "Só TLS/HTTPS comum, sem requisito regulatório", icon: "security_tls" },
] ]
} }
]; ];
@@ -372,24 +376,54 @@ export default function Seletor() {
} }
// 6. SEGURANÇA CRIPTOGRÁFICA (security) // 6. SEGURANÇA CRIPTOGRÁFICA (security)
if (answers.security === "critical") { // Agora lê os campos destrinchados, então cada requisito pontua o
const cripto = serie.aceleradores_cripto || ""; // bloco de hardware que realmente o atende.
const seg = serie.seguranca || {};
const temECDSA = String(seg.ecdsa).startsWith("Sim");
const temECC = String(seg.ecc).startsWith("Sim");
const temAES = String(seg.aes).startsWith("AES");
if (cripto.includes("Key Manager")) { if (answers.security === "matter") {
if (temECDSA) {
scores[key] += 30; scores[key] += 30;
reasons[key].push("Suíte criptográfica completa em hardware com Key Manager (provisionamento e uso de chaves sem exposição ao firmware)"); reasons[key].push(`ECDSA em hardware (${seg.ecc}) — atestado de dispositivo Matter assinado sem custo de CPU`);
} else if (cripto.includes("ECDSA")) { } else if (temECC) {
scores[key] += 20;
reasons[key].push("Aceleradores criptográficos com ECC/ECDSA dedicados (assinatura digital e Secure Boot eficientes)");
} else if (cripto) {
scores[key] += 10; scores[key] += 10;
reasons[key].push("Aceleradores criptográficos básicos em hardware (AES, SHA, Secure Boot e criptografia de flash)"); reasons[key].push(`Tem ECC (${seg.ecc}) mas não o periférico ECDSA: a assinatura roda parcialmente em software`);
} else {
scores[key] -= 25;
reasons[key].push("Sem curvas elípticas em hardware: assinatura ECDSA inteiramente por software, lenta no provisionamento");
}
} else if (answers.security === "chaves") {
if (seg.key_manager === "Sim") {
scores[key] += 30;
reasons[key].push("Key Manager: a chave é provisionada e usada sem nunca ficar legível para o firmware");
} else if (seg.assinatura_digital === "Sim") {
scores[key] += 15;
reasons[key].push("Periférico Digital Signature protege a chave RSA via eFuse, mas sem Key Manager para as demais");
} else { } else {
scores[key] -= 20; scores[key] -= 20;
reasons[key].push("Sem aceleração criptográfica dedicada: operações de segurança ficam a cargo da CPU"); reasons[key].push("Sem Key Manager nem Digital Signature: a chave privada fica exposta na memória do firmware");
} }
} else if (answers.security === "standard") { } else if (answers.security === "boot") {
if (String(seg.secure_boot).includes("V2")) {
scores[key] += 20;
reasons[key].push(`Secure Boot ${seg.secure_boot} com criptografia de flash ${seg.criptografia_flash}`);
} else if (String(seg.secure_boot).includes("V1")) {
scores[key] += 5; scores[key] += 5;
reasons[key].push("Secure Boot apenas na versão V1 (legada), sem as proteções do esquema V2");
} else {
scores[key] -= 20;
reasons[key].push(`Secure Boot indisponível: ${seg.secure_boot}`);
}
} else if (answers.security === "tls") {
if (temAES) {
scores[key] += 10;
reasons[key].push(`Handshake e tráfego TLS acelerados por hardware (${seg.aes}, ${seg.sha})`);
} else {
scores[key] -= 10;
reasons[key].push(`Sem acelerador AES (só ${seg.sha}): o tráfego TLS é cifrado pela CPU e pesa no desempenho`);
}
} }
}); });
@@ -418,7 +452,7 @@ export default function Seletor() {
const progress = ((currentStep + 1) / questions.length) * 100; const progress = ((currentStep + 1) / questions.length) * 100;
return ( return (
<div className="bg-gradient-to-br from-slate-50 via-white to-purple-50/40 dark:from-slate-950 dark:via-slate-950 dark:to-slate-900 min-h-screen text-slate-900 dark:text-slate-100 transition-colors duration-300"> <div className="bg-gradient-to-br from-slate-100 via-slate-50 to-purple-100/40 dark:from-slate-950 dark:via-slate-950 dark:to-slate-900 min-h-screen text-slate-900 dark:text-slate-100 transition-colors duration-300">
<Header /> <Header />
<main id="conteudo" className="px-6 pt-16 pb-24 max-w-7xl mx-auto"> <main id="conteudo" className="px-6 pt-16 pb-24 max-w-7xl mx-auto">
@@ -442,7 +476,7 @@ export default function Seletor() {
{!showResults && !showSummary && ( {!showResults && !showSummary && (
<div className="max-w-2xl mx-auto mb-10"> <div className="max-w-2xl mx-auto mb-10">
<div className="flex justify-between items-center mb-3"> <div className="flex justify-between items-center mb-3">
<span className="text-[10px] font-bold text-slate-400 dark:text-slate-500 uppercase tracking-widest"> <span className="text-[10px] font-bold text-slate-500 dark:text-slate-400 uppercase tracking-widest">
Etapa {currentStep + 1} de {questions.length} Etapa {currentStep + 1} de {questions.length}
</span> </span>
<span className="text-xs font-extrabold text-purple-600 dark:text-purple-400"> <span className="text-xs font-extrabold text-purple-600 dark:text-purple-400">
@@ -463,12 +497,12 @@ export default function Seletor() {
{/* STEP 1: Quiz Card Interface */} {/* STEP 1: Quiz Card Interface */}
{currentStep < questions.length && !showResults && !showSummary && ( {currentStep < questions.length && !showResults && !showSummary && (
<div className="bg-white/80 dark:bg-slate-900/40 backdrop-blur-xl rounded-3xl border border-slate-200/60 dark:border-slate-800/80 p-6 md:p-10 max-w-2xl mx-auto shadow-2xl transition-all duration-300"> <div className="bg-white dark:bg-slate-900/40 backdrop-blur-xl rounded-3xl border border-slate-300 dark:border-slate-800/80 p-6 md:p-10 max-w-2xl mx-auto shadow-2xl transition-all duration-300">
<div className="text-center mb-8"> <div className="text-center mb-8">
<h2 className="text-xl md:text-2xl font-display font-extrabold text-slate-850 dark:text-slate-100 mb-3 leading-snug"> <h2 className="text-xl md:text-2xl font-display font-extrabold text-slate-850 dark:text-slate-100 mb-3 leading-snug">
{questions[currentStep].question} {questions[currentStep].question}
</h2> </h2>
<p className="text-xs md:text-sm text-slate-450 dark:text-slate-400 max-w-xl mx-auto leading-relaxed"> <p className="text-xs md:text-sm text-slate-500 dark:text-slate-400 max-w-xl mx-auto leading-relaxed">
{questions[currentStep].description} {questions[currentStep].description}
</p> </p>
</div> </div>
@@ -483,7 +517,7 @@ export default function Seletor() {
className={`group flex items-center gap-4 text-left border rounded-2xl p-4 transition-all duration-300 cursor-pointer hover:shadow-md active:scale-[0.99] ${ className={`group flex items-center gap-4 text-left border rounded-2xl p-4 transition-all duration-300 cursor-pointer hover:shadow-md active:scale-[0.99] ${
isSelected isSelected
? 'bg-purple-500/10 dark:bg-purple-400/10 border-purple-500 dark:border-purple-400 shadow-xs' ? 'bg-purple-500/10 dark:bg-purple-400/10 border-purple-500 dark:border-purple-400 shadow-xs'
: 'bg-slate-50/50 dark:bg-slate-900/20 hover:bg-purple-500/5 dark:hover:bg-purple-400/5 border-slate-200 dark:border-slate-800/80 hover:border-purple-400/50' : 'bg-slate-50/50 dark:bg-slate-900/20 hover:bg-purple-500/5 dark:hover:bg-purple-400/5 border-slate-300 dark:border-slate-800/80 hover:border-purple-400/50'
}`} }`}
> >
{/* Icon Badge */} {/* Icon Badge */}
@@ -514,13 +548,13 @@ export default function Seletor() {
</div> </div>
{/* Bottom Navigation Buttons */} {/* Bottom Navigation Buttons */}
<div className="flex flex-col sm:flex-row justify-between items-stretch sm:items-center gap-3 mt-8 pt-6 border-t border-slate-200/60 dark:border-slate-800/60"> <div className="flex flex-col sm:flex-row justify-between items-stretch sm:items-center gap-3 mt-8 pt-6 border-t border-slate-300 dark:border-slate-800/60">
<button <button
onClick={goBack} onClick={goBack}
disabled={currentStep === 0} disabled={currentStep === 0}
className={`inline-flex items-center justify-center gap-2 px-4 py-2.5 rounded-xl font-bold text-xs uppercase tracking-wider transition-all duration-300 cursor-pointer ${ className={`inline-flex items-center justify-center gap-2 px-4 py-2.5 rounded-xl font-bold text-xs uppercase tracking-wider transition-all duration-300 cursor-pointer ${
currentStep === 0 currentStep === 0
? 'bg-slate-100 dark:bg-slate-900 text-slate-400 dark:text-slate-600 cursor-not-allowed border border-transparent' ? 'bg-slate-100 dark:bg-slate-900 text-slate-500 dark:text-slate-400 cursor-not-allowed border border-transparent'
: 'bg-slate-200/60 dark:bg-slate-800 text-slate-700 dark:text-slate-300 hover:bg-slate-250 dark:hover:bg-slate-700 hover:shadow-xs' : 'bg-slate-200/60 dark:bg-slate-800 text-slate-700 dark:text-slate-300 hover:bg-slate-250 dark:hover:bg-slate-700 hover:shadow-xs'
}`} }`}
> >
@@ -541,7 +575,7 @@ export default function Seletor() {
disabled={currentAnswer === null} disabled={currentAnswer === null}
className={`inline-flex items-center justify-center gap-2 px-6 py-2.5 rounded-xl font-bold text-xs uppercase tracking-wider transition-all duration-300 cursor-pointer ${ className={`inline-flex items-center justify-center gap-2 px-6 py-2.5 rounded-xl font-bold text-xs uppercase tracking-wider transition-all duration-300 cursor-pointer ${
currentAnswer === null currentAnswer === null
? 'bg-slate-100 dark:bg-slate-900 text-slate-400 dark:text-slate-600 cursor-not-allowed border border-transparent' ? 'bg-slate-100 dark:bg-slate-900 text-slate-500 dark:text-slate-400 cursor-not-allowed border border-transparent'
: 'bg-gradient-to-r from-blue-600 via-purple-600 to-pink-600 text-white hover:shadow-lg transform hover:-translate-y-0.5 active:translate-y-0' : 'bg-gradient-to-r from-blue-600 via-purple-600 to-pink-600 text-white hover:shadow-lg transform hover:-translate-y-0.5 active:translate-y-0'
}`} }`}
> >
@@ -554,7 +588,7 @@ export default function Seletor() {
{/* STEP 2: Choice Summary */} {/* STEP 2: Choice Summary */}
{showSummary && !showResults && ( {showSummary && !showResults && (
<div className="bg-white/80 dark:bg-slate-900/40 backdrop-blur-xl rounded-3xl border border-slate-200/60 dark:border-slate-800/80 p-6 md:p-10 max-w-3xl mx-auto shadow-2xl transition-all duration-300"> <div className="bg-white dark:bg-slate-900/40 backdrop-blur-xl rounded-3xl border border-slate-300 dark:border-slate-800/80 p-6 md:p-10 max-w-3xl mx-auto shadow-2xl transition-all duration-300">
<div className="text-center mb-8"> <div className="text-center mb-8">
<div className="inline-flex items-center justify-center w-14 h-14 bg-purple-500/10 text-purple-600 dark:text-purple-400 rounded-full mb-4 border border-purple-500/20"> <div className="inline-flex items-center justify-center w-14 h-14 bg-purple-500/10 text-purple-600 dark:text-purple-400 rounded-full mb-4 border border-purple-500/20">
<Award className="w-6 h-6" /> <Award className="w-6 h-6" />
@@ -562,7 +596,7 @@ export default function Seletor() {
<h2 className="text-xl md:text-2xl font-display font-extrabold text-slate-850 dark:text-slate-100 mb-2"> <h2 className="text-xl md:text-2xl font-display font-extrabold text-slate-850 dark:text-slate-100 mb-2">
Resumo das suas Configurações Resumo das suas Configurações
</h2> </h2>
<p className="text-xs text-slate-450 dark:text-slate-400 max-w-xl mx-auto leading-relaxed"> <p className="text-xs text-slate-500 dark:text-slate-400 max-w-xl mx-auto leading-relaxed">
Revise suas escolhas de arquitetura abaixo. Você pode ajustar qualquer especificação individual antes de processar as recomendações. Revise suas escolhas de arquitetura abaixo. Você pode ajustar qualquer especificação individual antes de processar as recomendações.
</p> </p>
</div> </div>
@@ -574,9 +608,9 @@ export default function Seletor() {
const selectedOption = question.options.find(opt => opt.value === answer); const selectedOption = question.options.find(opt => opt.value === answer);
return ( return (
<div key={question.id} className="bg-slate-50/50 dark:bg-slate-900/20 rounded-xl p-4 border border-slate-200/60 dark:border-slate-800/80 hover:shadow-xs transition-all flex justify-between items-center gap-3"> <div key={question.id} className="bg-slate-50/50 dark:bg-slate-900/20 rounded-xl p-4 border border-slate-300 dark:border-slate-800/80 hover:shadow-xs transition-all flex justify-between items-center gap-3">
<div className="flex-1 min-w-0"> <div className="flex-1 min-w-0">
<h3 className="text-[10px] font-bold text-slate-400 dark:text-slate-500 uppercase tracking-widest mb-1 truncate select-none"> <h3 className="text-[10px] font-bold text-slate-500 dark:text-slate-400 uppercase tracking-widest mb-1 truncate select-none">
{question.id.replace('connectivity', 'conectividade').replace('hardware', 'periféricos').replace('power', 'alimentação').toUpperCase()} {question.id.replace('connectivity', 'conectividade').replace('hardware', 'periféricos').replace('power', 'alimentação').toUpperCase()}
</h3> </h3>
{selectedOption && ( {selectedOption && (
@@ -597,7 +631,7 @@ export default function Seletor() {
setCurrentAnswer(answers[question.id]); setCurrentAnswer(answers[question.id]);
setShowSummary(false); setShowSummary(false);
}} }}
className="p-2 border border-slate-200 dark:border-slate-800 hover:border-purple-500 dark:hover:border-purple-400 bg-white dark:bg-slate-900 hover:bg-purple-50 dark:hover:bg-purple-950/20 rounded-lg text-slate-400 hover:text-purple-650 dark:hover:text-purple-400 transition-all cursor-pointer shrink-0" className="p-2 border border-slate-300 dark:border-slate-800 hover:border-purple-500 dark:hover:border-purple-400 bg-white dark:bg-slate-900 hover:bg-purple-50 dark:hover:bg-purple-950/20 rounded-lg text-slate-400 hover:text-purple-650 dark:hover:text-purple-400 transition-all cursor-pointer shrink-0"
title="Editar esta resposta" title="Editar esta resposta"
> >
<Edit2 className="w-3.5 h-3.5" /> <Edit2 className="w-3.5 h-3.5" />
@@ -607,7 +641,7 @@ export default function Seletor() {
})} })}
</div> </div>
<div className="flex flex-col sm:flex-row gap-3 justify-center items-stretch sm:items-center border-t border-slate-200/60 dark:border-slate-800/60 pt-6"> <div className="flex flex-col sm:flex-row gap-3 justify-center items-stretch sm:items-center border-t border-slate-300 dark:border-slate-800/60 pt-6">
<button <button
onClick={() => { onClick={() => {
setCurrentStep(questions.length - 1); setCurrentStep(questions.length - 1);
@@ -654,7 +688,7 @@ export default function Seletor() {
}, },
{ {
badge: "🥈 2º Recomendado", badge: "🥈 2º Recomendado",
cardClass: "border-slate-200/80 dark:border-slate-800 bg-linear-to-b from-slate-500/5 to-transparent min-h-[540px] opacity-95 hover:opacity-100", cardClass: "border-slate-300 dark:border-slate-800 bg-linear-to-b from-slate-500/5 to-transparent min-h-[540px] opacity-95 hover:opacity-100",
badgeClass: "text-slate-650 bg-slate-500/10 border-slate-500/20 dark:text-slate-350 dark:bg-slate-850", badgeClass: "text-slate-650 bg-slate-500/10 border-slate-500/20 dark:text-slate-350 dark:bg-slate-850",
scoreClass: "text-slate-500 dark:text-slate-300" scoreClass: "text-slate-500 dark:text-slate-300"
}, },
@@ -669,7 +703,7 @@ export default function Seletor() {
return ( return (
<div <div
key={seriesKey} key={seriesKey}
className={`relative flex flex-col bg-white/70 dark:bg-slate-900/40 backdrop-blur-xl border rounded-3xl overflow-hidden transition-all duration-500 hover:scale-[1.05] hover:shadow-2xl hover:z-20 ${rankConfig.cardClass}`} className={`relative flex flex-col bg-white dark:bg-slate-900/40 backdrop-blur-xl border rounded-3xl overflow-hidden transition-all duration-500 hover:scale-[1.05] hover:shadow-2xl hover:z-20 ${rankConfig.cardClass}`}
> >
{/* Top Color Band */} {/* Top Color Band */}
<div className="h-1 w-full shrink-0" style={{ backgroundColor: serie.cor }} /> <div className="h-1 w-full shrink-0" style={{ backgroundColor: serie.cor }} />
@@ -687,11 +721,11 @@ export default function Seletor() {
<h3 className="text-xl font-display font-extrabold text-slate-850 dark:text-slate-100 mb-1 leading-none"> <h3 className="text-xl font-display font-extrabold text-slate-850 dark:text-slate-100 mb-1 leading-none">
{seriesKey} {seriesKey}
</h3> </h3>
<p className="text-[10px] text-slate-400 dark:text-slate-500 font-semibold uppercase tracking-wider truncate"> <p className="text-[10px] text-slate-500 dark:text-slate-400 font-semibold uppercase tracking-wider truncate">
{serie.nome_completo} {serie.nome_completo}
</p> </p>
<div className="mt-3.5 inline-flex items-center gap-1.5 px-3 py-1 rounded-full bg-slate-50 dark:bg-slate-950 border border-slate-200/60 dark:border-slate-800 shadow-xs"> <div className="mt-3.5 inline-flex items-center gap-1.5 px-3 py-1 rounded-full bg-slate-50 dark:bg-slate-950 border border-slate-300 dark:border-slate-800 shadow-xs">
<span className="text-[9px] font-bold text-slate-450 uppercase tracking-wider">Afinidade:</span> <span className="text-[9px] font-bold text-slate-450 uppercase tracking-wider">Afinidade:</span>
<span className={`text-xs font-extrabold ${rankConfig.scoreClass}`}> <span className={`text-xs font-extrabold ${rankConfig.scoreClass}`}>
{score} pts {score} pts
@@ -700,8 +734,8 @@ export default function Seletor() {
</div> </div>
{/* Specifications Checklist */} {/* Specifications Checklist */}
<div className="space-y-2 border-t border-slate-200/50 dark:border-slate-850/60 pt-4 mb-6 select-none"> <div className="space-y-2 border-t border-slate-300 dark:border-slate-850/60 pt-4 mb-6 select-none">
<h4 className="text-[9px] font-bold text-slate-400 dark:text-slate-500 uppercase tracking-widest mb-2.5">Principais Vantagens</h4> <h4 className="text-[9px] font-bold text-slate-500 dark:text-slate-400 uppercase tracking-widest mb-2.5">Principais Vantagens</h4>
<ul className="space-y-2"> <ul className="space-y-2">
{matchReasons.slice(0, 3).map((reason, idx) => ( {matchReasons.slice(0, 3).map((reason, idx) => (
<li key={idx} className="flex items-start gap-2 text-xs leading-normal text-slate-600 dark:text-slate-350"> <li key={idx} className="flex items-start gap-2 text-xs leading-normal text-slate-600 dark:text-slate-350">
@@ -710,7 +744,7 @@ export default function Seletor() {
</li> </li>
))} ))}
{matchReasons.length > 3 && ( {matchReasons.length > 3 && (
<li className="text-[10px] text-slate-400 dark:text-slate-500 italic pl-5.5"> <li className="text-[10px] text-slate-500 dark:text-slate-400 italic pl-5.5">
+{matchReasons.length - 3} outros requisitos atendidos +{matchReasons.length - 3} outros requisitos atendidos
</li> </li>
)} )}
@@ -719,7 +753,7 @@ export default function Seletor() {
</div> </div>
{/* Spec Mini Badges */} {/* Spec Mini Badges */}
<div className="grid grid-cols-2 gap-2 bg-slate-50/50 dark:bg-slate-950/40 p-2.5 rounded-xl border border-slate-100 dark:border-slate-800/40 mb-4 select-none"> <div className="grid grid-cols-2 gap-2 bg-slate-50/50 dark:bg-slate-950/40 p-2.5 rounded-xl border border-slate-200 dark:border-slate-800/40 mb-4 select-none">
<div className="flex flex-col"> <div className="flex flex-col">
<span className="text-[8px] font-bold uppercase text-slate-400 tracking-wider">Frequência</span> <span className="text-[8px] font-bold uppercase text-slate-400 tracking-wider">Frequência</span>
<span className="text-xs font-extrabold text-slate-700 dark:text-slate-200 truncate">{serie.frequencia}</span> <span className="text-xs font-extrabold text-slate-700 dark:text-slate-200 truncate">{serie.frequencia}</span>
@@ -750,7 +784,7 @@ export default function Seletor() {
{/* Stacked Layout for mobile */} {/* Stacked Layout for mobile */}
<div className="lg:hidden space-y-6 max-w-xl mx-auto"> <div className="lg:hidden space-y-6 max-w-xl mx-auto">
<h3 className="text-sm font-bold text-slate-400 dark:text-slate-500 uppercase tracking-widest text-center select-none mb-1">Grade de Resultados</h3> <h3 className="text-sm font-bold text-slate-500 dark:text-slate-400 uppercase tracking-widest text-center select-none mb-1">Grade de Resultados</h3>
{topThree.map(([seriesKey, score], index) => { {topThree.map(([seriesKey, score], index) => {
const serie = seriesData[seriesKey]; const serie = seriesData[seriesKey];
const matchReasons = allReasons[seriesKey] || []; const matchReasons = allReasons[seriesKey] || [];
@@ -760,8 +794,8 @@ export default function Seletor() {
return ( return (
<div <div
key={seriesKey} key={seriesKey}
className={`bg-white/70 dark:bg-slate-900/40 backdrop-blur-xl border rounded-3xl overflow-hidden shadow-xl flex flex-col ${ className={`bg-white dark:bg-slate-900/40 backdrop-blur-xl border rounded-3xl overflow-hidden shadow-xl flex flex-col ${
isFirst ? "border-amber-500 dark:border-amber-400" : "border-slate-200/80 dark:border-slate-800" isFirst ? "border-amber-500 dark:border-amber-400" : "border-slate-300 dark:border-slate-800"
}`} }`}
> >
<div className="p-5 flex-1"> <div className="p-5 flex-1">
@@ -780,7 +814,7 @@ export default function Seletor() {
<span className="text-4xl select-none shrink-0">{serie.icone}</span> <span className="text-4xl select-none shrink-0">{serie.icone}</span>
<div> <div>
<h4 className="text-base font-bold text-slate-850 dark:text-slate-100 leading-tight">{seriesKey}</h4> <h4 className="text-base font-bold text-slate-850 dark:text-slate-100 leading-tight">{seriesKey}</h4>
<p className="text-xs text-slate-450 dark:text-slate-400 leading-snug line-clamp-2 mt-1">{serie.descricao}</p> <p className="text-xs text-slate-500 dark:text-slate-400 leading-snug line-clamp-2 mt-1">{serie.descricao}</p>
</div> </div>
</div> </div>
@@ -808,7 +842,7 @@ export default function Seletor() {
{/* Other options */} {/* Other options */}
{otherRecommendations.length > 0 && ( {otherRecommendations.length > 0 && (
<div className="bg-slate-50/50 dark:bg-slate-900/10 backdrop-blur-xl rounded-3xl p-6 md:p-8 border border-slate-200/50 dark:border-slate-850/50 max-w-6xl mx-auto shadow-inner"> <div className="bg-slate-50/50 dark:bg-slate-900/10 backdrop-blur-xl rounded-3xl p-6 md:p-8 border border-slate-300 dark:border-slate-850/50 max-w-6xl mx-auto shadow-inner">
<h3 className="text-lg font-display font-extrabold text-slate-850 dark:text-slate-150 mb-6 text-center select-none"> <h3 className="text-lg font-display font-extrabold text-slate-850 dark:text-slate-150 mb-6 text-center select-none">
Outras Opções Compatíveis Outras Opções Compatíveis
</h3> </h3>
@@ -818,12 +852,12 @@ export default function Seletor() {
return ( return (
<div <div
key={seriesKey} key={seriesKey}
className="bg-white/80 dark:bg-slate-900/60 border border-slate-200 dark:border-slate-800/80 rounded-2xl p-4 hover:shadow-lg hover:-translate-y-0.5 transition-all duration-300 flex flex-col justify-between" className="bg-white dark:bg-slate-900/60 border border-slate-300 dark:border-slate-800/80 rounded-2xl p-4 hover:shadow-lg hover:-translate-y-0.5 transition-all duration-300 flex flex-col justify-between"
> >
<div className="text-center mb-4"> <div className="text-center mb-4">
<div className="text-3xl mb-2 select-none">{serie.icone}</div> <div className="text-3xl mb-2 select-none">{serie.icone}</div>
<h4 className="text-xs font-bold text-slate-850 dark:text-slate-250 truncate leading-none">{seriesKey}</h4> <h4 className="text-xs font-bold text-slate-850 dark:text-slate-250 truncate leading-none">{seriesKey}</h4>
<span className="text-[10px] text-slate-450 dark:text-slate-500 font-semibold">Afinidade: {score} pts</span> <span className="text-[10px] text-slate-500 dark:text-slate-450 font-semibold">Afinidade: {score} pts</span>
</div> </div>
<Link <Link
href={`/series/${seriesKey}`} href={`/series/${seriesKey}`}
@@ -840,10 +874,10 @@ export default function Seletor() {
)} )}
{/* Bottom navigation buttons */} {/* Bottom navigation buttons */}
<div className="flex flex-col sm:flex-row gap-3 justify-center items-center max-w-2xl mx-auto border-t border-slate-200 dark:border-slate-850/60 pt-8 select-none"> <div className="flex flex-col sm:flex-row gap-3 justify-center items-center max-w-2xl mx-auto border-t border-slate-300 dark:border-slate-850/60 pt-8 select-none">
<Link <Link
href="/comparacao" href="/comparacao"
className="inline-flex items-center justify-center gap-2 bg-white dark:bg-slate-900 text-slate-700 dark:text-slate-300 px-6 py-3 rounded-xl font-bold text-xs uppercase tracking-wider shadow-xs border border-slate-200 dark:border-slate-800 hover:border-purple-500 dark:hover:border-purple-400 hover:text-purple-600 dark:hover:text-purple-400 hover:-translate-y-0.5 transition-all duration-300 cursor-pointer w-full sm:w-auto" className="inline-flex items-center justify-center gap-2 bg-white dark:bg-slate-900 text-slate-700 dark:text-slate-300 px-6 py-3 rounded-xl font-bold text-xs uppercase tracking-wider shadow-xs border border-slate-300 dark:border-slate-800 hover:border-purple-500 dark:hover:border-purple-400 hover:text-purple-600 dark:hover:text-purple-400 hover:-translate-y-0.5 transition-all duration-300 cursor-pointer w-full sm:w-auto"
> >
<SlidersHorizontal className="w-3.5 h-3.5" /> <SlidersHorizontal className="w-3.5 h-3.5" />
<span>Comparar Lado a Lado</span> <span>Comparar Lado a Lado</span>
@@ -859,7 +893,7 @@ export default function Seletor() {
</div> </div>
{/* Secondary showcase tools */} {/* Secondary showcase tools */}
<div className="bg-white/80 dark:bg-slate-900/40 backdrop-blur-xl border border-slate-200/60 dark:border-slate-800/80 rounded-3xl p-6 md:p-8 max-w-6xl mx-auto shadow-2xl"> <div className="bg-white dark:bg-slate-900/40 backdrop-blur-xl border border-slate-300 dark:border-slate-800/80 rounded-3xl p-6 md:p-8 max-w-6xl mx-auto shadow-2xl">
<h3 className="text-lg font-display font-extrabold text-slate-850 dark:text-slate-100 mb-6 text-center select-none"> <h3 className="text-lg font-display font-extrabold text-slate-850 dark:text-slate-100 mb-6 text-center select-none">
Próximos Passos de Desenvolvimento Próximos Passos de Desenvolvimento
</h3> </h3>
@@ -915,7 +949,7 @@ export default function Seletor() {
{/* STEP 3B: No Results Found */} {/* STEP 3B: No Results Found */}
{showResults && recommendations.length === 0 && ( {showResults && recommendations.length === 0 && (
<div className="bg-white/80 dark:bg-slate-900/40 backdrop-blur-xl rounded-3xl border border-slate-200/60 dark:border-slate-800/80 p-8 md:p-10 max-w-2xl mx-auto shadow-2xl transition-all duration-300"> <div className="bg-white dark:bg-slate-900/40 backdrop-blur-xl rounded-3xl border border-slate-300 dark:border-slate-800/80 p-8 md:p-10 max-w-2xl mx-auto shadow-2xl transition-all duration-300">
<div className="text-center mb-8"> <div className="text-center mb-8">
<div className="inline-flex items-center justify-center w-16 h-16 bg-amber-500/10 text-amber-600 dark:text-amber-400 rounded-full mb-4 border border-amber-500/20"> <div className="inline-flex items-center justify-center w-16 h-16 bg-amber-500/10 text-amber-600 dark:text-amber-400 rounded-full mb-4 border border-amber-500/20">
<HelpCircle className="w-8 h-8" /> <HelpCircle className="w-8 h-8" />
@@ -927,7 +961,7 @@ export default function Seletor() {
Não foi possível encontrar um único microcontrolador ESP32 que atenda simultaneamente a todos os critérios. Isso ocorre quando requisitamos combinações extremas (ex: Ethernet industrial cabeada + BLE portátil com economia de bateria ultra-baixa). Não foi possível encontrar um único microcontrolador ESP32 que atenda simultaneamente a todos os critérios. Isso ocorre quando requisitamos combinações extremas (ex: Ethernet industrial cabeada + BLE portátil com economia de bateria ultra-baixa).
</p> </p>
<div className="bg-slate-50/50 dark:bg-slate-950/40 border border-slate-200/60 dark:border-slate-850 p-5 rounded-2xl max-w-xl mx-auto mb-6 text-left select-none"> <div className="bg-slate-50/50 dark:bg-slate-950/40 border border-slate-300 dark:border-slate-850 p-5 rounded-2xl max-w-xl mx-auto mb-6 text-left select-none">
<h3 className="text-xs font-bold text-slate-800 dark:text-slate-200 mb-2">💡 Dicas Rápidas de Seleção:</h3> <h3 className="text-xs font-bold text-slate-800 dark:text-slate-200 mb-2">💡 Dicas Rápidas de Seleção:</h3>
<ul className="space-y-2 text-xs text-slate-500 dark:text-slate-400 leading-relaxed"> <ul className="space-y-2 text-xs text-slate-500 dark:text-slate-400 leading-relaxed">
<li className="flex items-start gap-2"> <li className="flex items-start gap-2">
@@ -952,7 +986,7 @@ export default function Seletor() {
<Link <Link
href="/comparacao" href="/comparacao"
className="inline-flex items-center justify-center gap-2 bg-white dark:bg-slate-900 text-slate-700 dark:text-slate-350 px-6 py-2.5 rounded-xl font-bold text-xs uppercase tracking-wider border border-slate-200 dark:border-slate-800 hover:border-purple-500 dark:hover:border-purple-400 hover:text-purple-600 dark:hover:text-purple-400 transition-all duration-300 cursor-pointer" className="inline-flex items-center justify-center gap-2 bg-white dark:bg-slate-900 text-slate-700 dark:text-slate-350 px-6 py-2.5 rounded-xl font-bold text-xs uppercase tracking-wider border border-slate-300 dark:border-slate-800 hover:border-purple-500 dark:hover:border-purple-400 hover:text-purple-600 dark:hover:text-purple-400 transition-all duration-300 cursor-pointer"
> >
<SlidersHorizontal className="w-4 h-4" /> <SlidersHorizontal className="w-4 h-4" />
<span>Ver Grade Completa</span> <span>Ver Grade Completa</span>
+69 -36
View File
@@ -6,6 +6,7 @@ import { notFound } from "next/navigation";
import seriesData from "@/public/series.json"; import seriesData from "@/public/series.json";
import ConnectionsDiagram from "@/components/ConnectionsDiagram"; import ConnectionsDiagram from "@/components/ConnectionsDiagram";
import SeriesTabMenu from "@/components/SeriesTabMenu"; import SeriesTabMenu from "@/components/SeriesTabMenu";
import PinRestrictions from "@/components/PinRestrictions";
import { import {
Cpu, Cpu,
Activity, Activity,
@@ -41,7 +42,17 @@ import {
ChevronRight, ChevronRight,
Lightbulb, Lightbulb,
ShieldCheck, ShieldCheck,
KeyRound KeyRound,
Lock,
Hash,
Spline,
FileSignature,
Dices,
Waves,
Bug,
Box,
Package,
Terminal
} from "lucide-react"; } from "lucide-react";
export default async function SerieDetail({ params }) { export default async function SerieDetail({ params }) {
@@ -72,9 +83,28 @@ export default async function SerieDetail({ params }) {
{ label: "Aceleradores IA", value: serie.aceleradores_ia || "Não", icon: <Sparkles className="w-5 h-5 text-purple-500" /> }, { label: "Aceleradores IA", value: serie.aceleradores_ia || "Não", icon: <Sparkles className="w-5 h-5 text-purple-500" /> },
]; ];
const seg = serie.seguranca || {};
const segurancaSpecs = [ const segurancaSpecs = [
{ label: "Aceleradores Criptográficos", value: serie.aceleradores_cripto || "Não", icon: <ShieldCheck className="w-5 h-5 text-teal-500" /> }, { label: "AES", value: seg.aes || "Não", icon: <Lock className="w-5 h-5 text-teal-500" /> },
{ label: "Gerador de Números Aleatórios", value: serie.aceleradores_cripto ? "Sim (RNG por hardware)" : "Não", icon: <KeyRound className="w-5 h-5 text-teal-500" /> }, { label: "SHA", value: seg.sha || "Não", icon: <Hash className="w-5 h-5 text-teal-500" /> },
{ label: "RSA / MPI", value: seg.rsa || "Não", icon: <KeyRound className="w-5 h-5 text-teal-500" /> },
{ label: "ECC (curvas elípticas)", value: seg.ecc || "Não", icon: <Spline className="w-5 h-5 text-teal-500" /> },
{ label: "ECDSA em hardware", value: seg.ecdsa || "Não", icon: <FileSignature className="w-5 h-5 text-teal-500" /> },
{ label: "HMAC", value: seg.hmac || "Não", icon: <Fingerprint className="w-5 h-5 text-teal-500" /> },
{ label: "Assinatura Digital (DS)", value: seg.assinatura_digital || "Não", icon: <FileSignature className="w-5 h-5 text-teal-500" /> },
{ label: "Key Manager", value: seg.key_manager || "Não", icon: <KeyRound className="w-5 h-5 text-teal-500" /> },
{ label: "Gerador aleatório (RNG)", value: seg.rng || "Não", icon: <Dices className="w-5 h-5 text-teal-500" /> },
{ label: "Criptografia de flash", value: seg.criptografia_flash || "Não", icon: <HardDrive className="w-5 h-5 text-teal-500" /> },
{ label: "Secure Boot", value: seg.secure_boot || "Não", icon: <ShieldCheck className="w-5 h-5 text-teal-500" /> },
{ label: "Proteção contra DPA", value: seg.protecao_dpa || "Não", icon: <Waves className="w-5 h-5 text-teal-500" /> },
];
const ferramentasSpecs = [
{ label: "ESP-IDF mínimo", value: serie.esp_idf_minimo || "—", icon: <Code className="w-5 h-5 text-amber-500" /> },
{ label: "Core Arduino", value: serie.arduino_core || "—", icon: <Terminal className="w-5 h-5 text-amber-500" /> },
{ label: "Depuração", value: serie.depuracao || "—", icon: <Bug className="w-5 h-5 text-amber-500" /> },
{ label: "Encapsulamento", value: serie.encapsulamento || "—", icon: <Box className="w-5 h-5 text-amber-500" /> },
{ label: "Módulos disponíveis", value: serie.modulos || "—", icon: <Package className="w-5 h-5 text-amber-500" /> },
]; ];
const conectividadeSpecs = [ const conectividadeSpecs = [
@@ -149,13 +179,13 @@ export default async function SerieDetail({ params }) {
label: 'Placa de Desenvolvimento', label: 'Placa de Desenvolvimento',
available: serie.placa, available: serie.placa,
content: ( content: (
<div className="bg-white/80 dark:bg-slate-900/40 backdrop-blur-xl rounded-3xl p-6 md:p-8 border border-slate-200/60 dark:border-slate-800/80 shadow-2xl"> <div className="bg-white dark:bg-slate-900/40 backdrop-blur-xl rounded-3xl p-6 md:p-8 border border-slate-300 dark:border-slate-800/80 shadow-2xl">
<h2 className="text-xl font-bold text-slate-800 dark:text-slate-100 mb-6 pb-3 border-b border-slate-200/65 dark:border-slate-800/65 flex items-center gap-3"> <h2 className="text-xl font-bold text-slate-800 dark:text-slate-100 mb-6 pb-3 border-b border-slate-300 dark:border-slate-800/65 flex items-center gap-3">
<Sliders className="w-5 h-5" style={{ color: serie.cor }} /> <Sliders className="w-5 h-5" style={{ color: serie.cor }} />
<span>Kit de Desenvolvimento Oficial</span> <span>Kit de Desenvolvimento Oficial</span>
</h2> </h2>
<div className="flex justify-center"> <div className="flex justify-center">
<div className="relative w-full max-w-3xl bg-white dark:bg-white overflow-hidden rounded-2xl border border-slate-200/50 dark:border-slate-850/50 shadow-lg p-4"> <div className="relative w-full max-w-3xl bg-white dark:bg-white overflow-hidden rounded-2xl border border-slate-300 dark:border-slate-850/50 shadow-lg p-4">
<Image <Image
src={serie.placa} src={serie.placa}
alt={`${key} DevKit - Placa de Desenvolvimento`} alt={`${key} DevKit - Placa de Desenvolvimento`}
@@ -177,7 +207,7 @@ export default async function SerieDetail({ params }) {
label: 'Diagrama de Conexões', label: 'Diagrama de Conexões',
available: conexoes.length > 0, available: conexoes.length > 0,
content: ( content: (
<div className="bg-white/80 dark:bg-slate-900/40 backdrop-blur-xl rounded-3xl p-6 md:p-8 border border-slate-200/60 dark:border-slate-800/80 shadow-2xl"> <div className="bg-white dark:bg-slate-900/40 backdrop-blur-xl rounded-3xl p-6 md:p-8 border border-slate-300 dark:border-slate-800/80 shadow-2xl">
<div className="bg-amber-500/10 text-amber-600 dark:text-amber-400 border border-amber-500/20 p-5 mb-8 rounded-2xl"> <div className="bg-amber-500/10 text-amber-600 dark:text-amber-400 border border-amber-500/20 p-5 mb-8 rounded-2xl">
<div className="flex items-start gap-3"> <div className="flex items-start gap-3">
<AlertTriangle className="w-5 h-5 shrink-0 mt-0.5" /> <AlertTriangle className="w-5 h-5 shrink-0 mt-0.5" />
@@ -192,7 +222,7 @@ export default async function SerieDetail({ params }) {
<ConnectionsDiagram connections={conexoes} serie={serie} /> <ConnectionsDiagram connections={conexoes} serie={serie} />
<div className="bg-slate-100/50 dark:bg-slate-950/40 rounded-2xl p-6 border border-slate-200/60 dark:border-slate-800/80 mt-8"> <div className="bg-slate-100/50 dark:bg-slate-950/40 rounded-2xl p-6 border border-slate-300 dark:border-slate-800/80 mt-8">
<h3 className="text-sm font-bold text-slate-800 dark:text-slate-100 mb-3 flex items-center gap-2 select-none"> <h3 className="text-sm font-bold text-slate-800 dark:text-slate-100 mb-3 flex items-center gap-2 select-none">
<Lightbulb className="w-4 h-4 text-purple-500" /> <Lightbulb className="w-4 h-4 text-purple-500" />
<span>Flexibilidade das Portas GPIO</span> <span>Flexibilidade das Portas GPIO</span>
@@ -217,7 +247,7 @@ export default async function SerieDetail({ params }) {
]; ];
return ( return (
<div className="bg-gradient-to-br from-slate-50 via-white to-purple-50/40 dark:from-slate-950 dark:via-slate-950 dark:to-slate-900 min-h-screen text-slate-900 dark:text-slate-100 transition-colors duration-300"> <div className="bg-gradient-to-br from-slate-100 via-slate-50 to-purple-100/40 dark:from-slate-950 dark:via-slate-950 dark:to-slate-900 min-h-screen text-slate-900 dark:text-slate-100 transition-colors duration-300">
<Header /> <Header />
<main className="px-6 pt-16 pb-24 max-w-7xl mx-auto"> <main className="px-6 pt-16 pb-24 max-w-7xl mx-auto">
@@ -234,7 +264,7 @@ export default async function SerieDetail({ params }) {
{/* Top Info Banner Card */} {/* Top Info Banner Card */}
<div <div
className="bg-white/80 dark:bg-slate-900/40 backdrop-blur-xl rounded-3xl p-6 md:p-8 shadow-2xl border relative overflow-hidden" className="bg-white dark:bg-slate-900/40 backdrop-blur-xl rounded-3xl p-6 md:p-8 shadow-2xl border relative overflow-hidden"
style={{ borderColor: `${serie.cor}40` }} style={{ borderColor: `${serie.cor}40` }}
> >
<div className="flex flex-col lg:flex-row items-start lg:items-center justify-between gap-8"> <div className="flex flex-col lg:flex-row items-start lg:items-center justify-between gap-8">
@@ -244,7 +274,7 @@ export default async function SerieDetail({ params }) {
<h1 className="text-4xl md:text-5xl font-display font-extrabold text-slate-850 dark:text-slate-100 tracking-tight leading-none mb-3"> <h1 className="text-4xl md:text-5xl font-display font-extrabold text-slate-850 dark:text-slate-100 tracking-tight leading-none mb-3">
{key} {key}
</h1> </h1>
<p className="text-sm font-semibold text-slate-400 dark:text-slate-500 mb-4">{serie.nome_completo}</p> <p className="text-sm font-semibold text-slate-500 dark:text-slate-400 mb-4">{serie.nome_completo}</p>
<span <span
className="inline-block px-3 py-1 rounded-full text-[10px] font-bold uppercase tracking-widest" className="inline-block px-3 py-1 rounded-full text-[10px] font-bold uppercase tracking-widest"
style={{ backgroundColor: `${serie.cor}18`, color: serie.cor, border: `1px solid ${serie.cor}30` }} style={{ backgroundColor: `${serie.cor}18`, color: serie.cor, border: `1px solid ${serie.cor}30` }}
@@ -261,7 +291,7 @@ export default async function SerieDetail({ params }) {
href={serie.datasheet} href={serie.datasheet}
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"
className="inline-flex items-center justify-center gap-2 bg-slate-900 dark:bg-slate-100 text-white dark:text-slate-950 px-5 py-3 rounded-xl font-bold text-xs uppercase tracking-wider hover:opacity-90 transition-opacity shadow-lg" className="inline-flex items-center justify-center gap-2 bg-gradient-to-r from-purple-600 to-indigo-600 text-white px-5 py-3 rounded-xl font-bold text-xs uppercase tracking-wider hover:opacity-90 transition-opacity shadow-lg"
> >
<FileText className="w-4 h-4" /> <FileText className="w-4 h-4" />
<span>Datasheet Oficial</span> <span>Datasheet Oficial</span>
@@ -318,28 +348,28 @@ export default async function SerieDetail({ params }) {
{/* Primary Features Highlight Grid */} {/* Primary Features Highlight Grid */}
<div className="grid md:grid-cols-3 gap-6 mb-8"> <div className="grid md:grid-cols-3 gap-6 mb-8">
<div <div
className="bg-white/80 dark:bg-slate-900/40 backdrop-blur-xl rounded-2xl shadow-lg p-6 border-l-4" className="bg-white dark:bg-slate-900/40 backdrop-blur-xl rounded-2xl shadow-lg p-6 border-l-4"
style={{ borderColor: serie.cor }} style={{ borderColor: serie.cor }}
> >
<h3 className="text-[10px] font-bold text-slate-400 dark:text-slate-500 uppercase tracking-widest mb-1.5 select-none">PROCESSADOR</h3> <h3 className="text-[10px] font-bold text-slate-500 dark:text-slate-400 uppercase tracking-widest mb-1.5 select-none">PROCESSADOR</h3>
<p className="text-2xl font-extrabold text-slate-800 dark:text-slate-100">{serie.nucleos.includes("2") ? "Dual-Core" : "Single-Core"}</p> <p className="text-2xl font-extrabold text-slate-800 dark:text-slate-100">{serie.nucleos.includes("2") ? "Dual-Core" : "Single-Core"}</p>
<p className="text-xs text-slate-400 mt-1">{serie.frequencia}</p> <p className="text-xs text-slate-400 mt-1">{serie.frequencia}</p>
</div> </div>
<div <div
className="bg-white/80 dark:bg-slate-900/40 backdrop-blur-xl rounded-2xl shadow-lg p-6 border-l-4" className="bg-white dark:bg-slate-900/40 backdrop-blur-xl rounded-2xl shadow-lg p-6 border-l-4"
style={{ borderColor: serie.cor }} style={{ borderColor: serie.cor }}
> >
<h3 className="text-[10px] font-bold text-slate-400 dark:text-slate-500 uppercase tracking-widest mb-1.5 select-none">MEMÓRIA SRAM</h3> <h3 className="text-[10px] font-bold text-slate-500 dark:text-slate-400 uppercase tracking-widest mb-1.5 select-none">MEMÓRIA SRAM</h3>
<p className="text-2xl font-extrabold text-slate-800 dark:text-slate-100">{serie.memoria_sram}</p> <p className="text-2xl font-extrabold text-slate-800 dark:text-slate-100">{serie.memoria_sram}</p>
<p className="text-xs text-slate-400 mt-1">+ {serie.memoria_sram_rtc} RTC SRAM</p> <p className="text-xs text-slate-400 mt-1">+ {serie.memoria_sram_rtc} RTC SRAM</p>
</div> </div>
<div <div
className="bg-white/80 dark:bg-slate-900/40 backdrop-blur-xl rounded-2xl shadow-lg p-6 border-l-4" className="bg-white dark:bg-slate-900/40 backdrop-blur-xl rounded-2xl shadow-lg p-6 border-l-4"
style={{ borderColor: serie.cor }} style={{ borderColor: serie.cor }}
> >
<h3 className="text-[10px] font-bold text-slate-400 dark:text-slate-500 uppercase tracking-widest mb-1.5 select-none">GPIO DISPONÍVEIS</h3> <h3 className="text-[10px] font-bold text-slate-500 dark:text-slate-400 uppercase tracking-widest mb-1.5 select-none">GPIO DISPONÍVEIS</h3>
<p className="text-2xl font-extrabold text-slate-800 dark:text-slate-100">{serie.gpio}</p> <p className="text-2xl font-extrabold text-slate-800 dark:text-slate-100">{serie.gpio}</p>
<p className="text-xs text-slate-400 mt-1">Pinos I/O programáveis</p> <p className="text-xs text-slate-400 mt-1">Pinos I/O programáveis</p>
</div> </div>
@@ -348,17 +378,20 @@ export default async function SerieDetail({ params }) {
{/* DevBoard and Connections Tabs */} {/* DevBoard and Connections Tabs */}
<SeriesTabMenu tabs={tabs} color={serie.cor} /> <SeriesTabMenu tabs={tabs} color={serie.cor} />
<PinRestrictions conexoes={conexoes} serieKey={key} cor={serie.cor} />
{/* Specs List Cards */} {/* Specs List Cards */}
<div className="space-y-6 mt-12"> <div className="space-y-6 mt-12">
<SpecSection title="Processador" specs={processadorSpecs} cor={serie.cor} icon={<Cpu className="w-5 h-5" style={{ color: serie.cor }} />} /> <SpecSection title="Processador" specs={processadorSpecs} cor={serie.cor} icon={<Cpu className="w-5 h-5" style={{ color: serie.cor }} />} />
<SpecSection title="Conectividade RF" specs={conectividadeSpecs} cor={serie.cor} icon={<Wifi className="w-5 h-5" style={{ color: serie.cor }} />} /> <SpecSection title="Conectividade RF" specs={conectividadeSpecs} cor={serie.cor} icon={<Wifi className="w-5 h-5" style={{ color: serie.cor }} />} />
<SpecSection title="Disponibilidade e Ferramentas" specs={ferramentasSpecs} cor={serie.cor} icon={<Package className="w-5 h-5" style={{ color: serie.cor }} />} />
<SpecSection title="Segurança e Criptografia" specs={segurancaSpecs} cor={serie.cor} icon={<ShieldCheck className="w-5 h-5" style={{ color: serie.cor }} />} /> <SpecSection title="Segurança e Criptografia" specs={segurancaSpecs} cor={serie.cor} icon={<ShieldCheck className="w-5 h-5" style={{ color: serie.cor }} />} />
<SpecSection title="Estrutura de Memória" specs={memoriaSpecs} cor={serie.cor} icon={<Database className="w-5 h-5" style={{ color: serie.cor }} />} /> <SpecSection title="Estrutura de Memória" specs={memoriaSpecs} cor={serie.cor} icon={<Database className="w-5 h-5" style={{ color: serie.cor }} />} />
{/* Power Consumption section */} {/* Power Consumption section */}
{consumoEnergia.length > 0 && ( {consumoEnergia.length > 0 && (
<div className="bg-white/80 dark:bg-slate-900/40 backdrop-blur-xl rounded-2xl p-6 md:p-8 border border-slate-200/60 dark:border-slate-800/80 shadow-2xl"> <div className="bg-white dark:bg-slate-900/40 backdrop-blur-xl rounded-2xl p-6 md:p-8 border border-slate-300 dark:border-slate-800/80 shadow-2xl">
<h2 className="text-xl font-bold text-slate-800 dark:text-slate-100 mb-6 pb-3 border-b border-slate-200/65 dark:border-slate-800/65 flex items-center gap-3 select-none"> <h2 className="text-xl font-bold text-slate-800 dark:text-slate-100 mb-6 pb-3 border-b border-slate-300 dark:border-slate-800/65 flex items-center gap-3 select-none">
<Battery className="w-5 h-5" style={{ color: serie.cor }} /> <Battery className="w-5 h-5" style={{ color: serie.cor }} />
<span>Consumo de Energia</span> <span>Consumo de Energia</span>
</h2> </h2>
@@ -375,11 +408,11 @@ export default async function SerieDetail({ params }) {
<div className="grid md:grid-cols-2 lg:grid-cols-3 gap-4"> <div className="grid md:grid-cols-2 lg:grid-cols-3 gap-4">
{consumoEnergia.map((spec, index) => ( {consumoEnergia.map((spec, index) => (
<div key={index} className="flex items-center gap-3.5 p-3.5 bg-slate-50/50 dark:bg-slate-950/20 rounded-xl hover:bg-purple-500/5 dark:hover:bg-purple-400/5 border border-slate-150/40 dark:border-slate-850/40 transition-colors"> <div key={index} className="flex items-center gap-3.5 p-3.5 bg-slate-50/50 dark:bg-slate-950/20 rounded-xl hover:bg-purple-500/5 dark:hover:bg-purple-400/5 border border-slate-150/40 dark:border-slate-850/40 transition-colors">
<div className="p-2 bg-white dark:bg-slate-900 rounded-lg shadow-xs border border-slate-200/30 dark:border-slate-850/30 shrink-0"> <div className="p-2 bg-white dark:bg-slate-900 rounded-lg shadow-xs border border-slate-300 dark:border-slate-850/30 shrink-0">
{spec.icon} {spec.icon}
</div> </div>
<div className="min-w-0"> <div className="min-w-0">
<p className="text-[10px] text-slate-400 dark:text-slate-500 font-bold uppercase tracking-wider">{spec.label}</p> <p className="text-[10px] text-slate-500 dark:text-slate-400 font-bold uppercase tracking-wider">{spec.label}</p>
<p className="text-sm font-extrabold text-slate-800 dark:text-slate-200 mt-0.5">{spec.value}</p> <p className="text-sm font-extrabold text-slate-800 dark:text-slate-200 mt-0.5">{spec.value}</p>
</div> </div>
</div> </div>
@@ -406,26 +439,26 @@ export default async function SerieDetail({ params }) {
</div> </div>
{/* DevBoard CTA Panel */} {/* DevBoard CTA Panel */}
<div className="my-20 bg-gradient-to-br from-slate-900 to-indigo-950 dark:from-slate-950 dark:to-purple-950/20 border border-slate-800/80 rounded-3xl p-8 md:p-12 shadow-2xl relative overflow-hidden"> <div className="my-20 bg-gradient-to-br from-indigo-50 to-purple-50 dark:from-slate-950 dark:to-purple-950/20 border border-indigo-200 dark:border-slate-800/80 rounded-3xl p-8 md:p-12 shadow-2xl relative overflow-hidden">
<div className="absolute right-0 bottom-0 w-96 h-96 bg-purple-500/5 rounded-full blur-3xl pointer-events-none" /> <div className="absolute right-0 bottom-0 w-96 h-96 bg-purple-500/5 rounded-full blur-3xl pointer-events-none" />
<div className="flex flex-col md:flex-row items-center justify-between gap-8 relative z-10"> <div className="flex flex-col md:flex-row items-center justify-between gap-8 relative z-10">
<div className="flex-1 max-w-2xl text-center md:text-left"> <div className="flex-1 max-w-2xl text-center md:text-left">
<div className="inline-flex items-center gap-2 bg-white/10 text-slate-350 px-3 py-1 rounded-full mb-4 text-xs font-semibold select-none"> <div className="inline-flex items-center gap-2 bg-indigo-500/10 text-indigo-700 dark:bg-white/10 dark:text-slate-350 px-3 py-1 rounded-full mb-4 text-xs font-semibold select-none">
<Home className="w-3.5 h-3.5" /> <Home className="w-3.5 h-3.5" />
<span>Hardware Fisico</span> <span>Hardware Fisico</span>
</div> </div>
<h3 className="text-2xl md:text-3xl font-display font-extrabold text-white mb-4"> <h3 className="text-2xl md:text-3xl font-display font-extrabold text-slate-900 dark:text-white mb-4">
Pronto para começar a prototipar? Pronto para começar a prototipar?
</h3> </h3>
<p className="text-sm text-slate-400 leading-relaxed"> <p className="text-sm text-slate-600 dark:text-slate-400 leading-relaxed">
Explore toda a nossa grade de placas de desenvolvimento de parceiros oficiais para a série {key}. Compare dimensões, conversores USB-serial e pinagens prontas para protoboards. Explore toda a nossa grade de placas de desenvolvimento de parceiros oficiais para a série {key}. Compare dimensões, conversores USB-serial e pinagens prontas para protoboards.
</p> </p>
</div> </div>
<Link <Link
href="/catalogo" href="/catalogo"
className="inline-flex items-center justify-center gap-2 bg-white text-slate-900 px-6 py-3.5 rounded-xl font-bold text-sm hover:bg-slate-100 transition-all duration-300 shrink-0 shadow-md active:scale-95" className="inline-flex items-center justify-center gap-2 bg-gradient-to-r from-purple-600 to-indigo-600 text-white px-6 py-3.5 rounded-xl font-bold text-sm hover:opacity-90 transition-all duration-300 shrink-0 shadow-md active:scale-95"
> >
<span>Ver Placas Disponíveis</span> <span>Ver Placas Disponíveis</span>
<ArrowRight className="w-4 h-4" /> <ArrowRight className="w-4 h-4" />
@@ -438,11 +471,11 @@ export default async function SerieDetail({ params }) {
{previousKey ? ( {previousKey ? (
<Link <Link
href={`/series/${previousKey}`} href={`/series/${previousKey}`}
className="flex items-center gap-3 bg-white/70 dark:bg-slate-900/40 backdrop-blur-xl px-5 py-3.5 rounded-2xl shadow-lg border border-slate-200/60 dark:border-slate-800/80 hover:border-purple-500/55 dark:hover:border-purple-400/55 transition-all group shrink-0" className="flex items-center gap-3 bg-white dark:bg-slate-900/40 backdrop-blur-xl px-5 py-3.5 rounded-2xl shadow-lg border border-slate-300 dark:border-slate-800/80 hover:border-purple-500/55 dark:hover:border-purple-400/55 transition-all group shrink-0"
> >
<ArrowLeft className="w-4 h-4 text-slate-400 group-hover:-translate-x-1.5 transition-transform" /> <ArrowLeft className="w-4 h-4 text-slate-400 group-hover:-translate-x-1.5 transition-transform" />
<div className="text-left"> <div className="text-left">
<p className="text-[10px] text-slate-400 dark:text-slate-500 font-bold uppercase tracking-wider">Anterior</p> <p className="text-[10px] text-slate-500 dark:text-slate-400 font-bold uppercase tracking-wider">Anterior</p>
<p className="text-sm font-extrabold text-slate-800 dark:text-slate-200">{previousKey}</p> <p className="text-sm font-extrabold text-slate-800 dark:text-slate-200">{previousKey}</p>
</div> </div>
</Link> </Link>
@@ -452,7 +485,7 @@ export default async function SerieDetail({ params }) {
<Link <Link
href="/comparacao" href="/comparacao"
className="inline-flex items-center justify-center gap-2 bg-slate-900 dark:bg-slate-100 text-white dark:text-slate-950 px-6 py-3.5 rounded-xl font-bold text-xs uppercase tracking-wider hover:opacity-90 transition-opacity shadow-md" className="inline-flex items-center justify-center gap-2 bg-gradient-to-r from-purple-600 to-indigo-600 text-white px-6 py-3.5 rounded-xl font-bold text-xs uppercase tracking-wider hover:opacity-90 transition-opacity shadow-md"
> >
<Scale className="w-4 h-4" /> <Scale className="w-4 h-4" />
<span>Comparar Séries</span> <span>Comparar Séries</span>
@@ -461,10 +494,10 @@ export default async function SerieDetail({ params }) {
{nextKey ? ( {nextKey ? (
<Link <Link
href={`/series/${nextKey}`} href={`/series/${nextKey}`}
className="flex items-center gap-3 bg-white/70 dark:bg-slate-900/40 backdrop-blur-xl px-5 py-3.5 rounded-2xl shadow-lg border border-slate-200/60 dark:border-slate-800/80 hover:border-purple-500/55 dark:hover:border-purple-400/55 transition-all group shrink-0" className="flex items-center gap-3 bg-white dark:bg-slate-900/40 backdrop-blur-xl px-5 py-3.5 rounded-2xl shadow-lg border border-slate-300 dark:border-slate-800/80 hover:border-purple-500/55 dark:hover:border-purple-400/55 transition-all group shrink-0"
> >
<div className="text-right"> <div className="text-right">
<p className="text-[10px] text-slate-400 dark:text-slate-500 font-bold uppercase tracking-wider">Próximo</p> <p className="text-[10px] text-slate-500 dark:text-slate-400 font-bold uppercase tracking-wider">Próximo</p>
<p className="text-sm font-extrabold text-slate-800 dark:text-slate-200">{nextKey}</p> <p className="text-sm font-extrabold text-slate-800 dark:text-slate-200">{nextKey}</p>
</div> </div>
<ArrowRight className="w-4 h-4 text-slate-400 group-hover:translate-x-1.5 transition-transform" /> <ArrowRight className="w-4 h-4 text-slate-400 group-hover:translate-x-1.5 transition-transform" />
@@ -482,19 +515,19 @@ export default async function SerieDetail({ params }) {
function SpecSection({ title, specs, cor, icon }) { function SpecSection({ title, specs, cor, icon }) {
return ( return (
<div className="bg-white/80 dark:bg-slate-900/40 backdrop-blur-xl rounded-2xl p-6 md:p-8 border border-slate-200/60 dark:border-slate-800/80 shadow-2xl"> <div className="bg-white dark:bg-slate-900/40 backdrop-blur-xl rounded-2xl p-6 md:p-8 border border-slate-300 dark:border-slate-800/80 shadow-2xl">
<h2 className="text-xl font-bold text-slate-800 dark:text-slate-100 mb-6 pb-3 border-b border-slate-200/65 dark:border-slate-800/65 flex items-center gap-3 select-none"> <h2 className="text-xl font-bold text-slate-800 dark:text-slate-100 mb-6 pb-3 border-b border-slate-300 dark:border-slate-800/65 flex items-center gap-3 select-none">
{icon} {icon}
<span>{title}</span> <span>{title}</span>
</h2> </h2>
<div className="grid md:grid-cols-2 lg:grid-cols-3 gap-4"> <div className="grid md:grid-cols-2 lg:grid-cols-3 gap-4">
{specs.map((spec, index) => ( {specs.map((spec, index) => (
<div key={index} className="flex items-center gap-3.5 p-3.5 bg-slate-50/50 dark:bg-slate-950/20 rounded-xl hover:bg-purple-500/5 dark:hover:bg-purple-400/5 border border-slate-150/40 dark:border-slate-850/40 transition-colors"> <div key={index} className="flex items-center gap-3.5 p-3.5 bg-slate-50/50 dark:bg-slate-950/20 rounded-xl hover:bg-purple-500/5 dark:hover:bg-purple-400/5 border border-slate-150/40 dark:border-slate-850/40 transition-colors">
<div className="p-2 bg-white dark:bg-slate-900 rounded-lg shadow-xs border border-slate-200/30 dark:border-slate-850/30 shrink-0"> <div className="p-2 bg-white dark:bg-slate-900 rounded-lg shadow-xs border border-slate-300 dark:border-slate-850/30 shrink-0">
{spec.icon} {spec.icon}
</div> </div>
<div className="min-w-0"> <div className="min-w-0">
<p className="text-[10px] text-slate-400 dark:text-slate-500 font-bold uppercase tracking-wider">{spec.label}</p> <p className="text-[10px] text-slate-500 dark:text-slate-400 font-bold uppercase tracking-wider">{spec.label}</p>
<p className="text-sm font-extrabold text-slate-800 dark:text-slate-200 mt-0.5">{spec.value}</p> <p className="text-sm font-extrabold text-slate-800 dark:text-slate-200 mt-0.5">{spec.value}</p>
</div> </div>
</div> </div>
+33 -23
View File
@@ -24,7 +24,7 @@ export default function Series() {
const series = Object.entries(seriesData); const series = Object.entries(seriesData);
return ( return (
<div className="bg-gradient-to-br from-slate-50 via-white to-purple-50/40 dark:from-slate-950 dark:via-slate-950 dark:to-slate-900 min-h-screen text-slate-900 dark:text-slate-100 transition-colors duration-300"> <div className="bg-gradient-to-br from-slate-100 via-slate-50 to-purple-100/40 dark:from-slate-950 dark:via-slate-950 dark:to-slate-900 min-h-screen text-slate-900 dark:text-slate-100 transition-colors duration-300">
<Header /> <Header />
<main id="conteudo" className="px-6 pt-16 pb-24 max-w-7xl mx-auto"> <main id="conteudo" className="px-6 pt-16 pb-24 max-w-7xl mx-auto">
@@ -57,9 +57,8 @@ export default function Series() {
> >
{/* Glassmorphic card container with dynamic color borders and shadows */} {/* Glassmorphic card container with dynamic color borders and shadows */}
<div <div
className="bg-white/80 dark:bg-slate-900/40 backdrop-blur-xl rounded-3xl p-6 md:p-8 border border-slate-200/60 dark:border-slate-800/80 hover:shadow-2xl transition-all duration-500 h-full flex flex-col justify-between" className="bg-white dark:bg-slate-900/40 backdrop-blur-xl rounded-3xl p-6 md:p-8 border border-slate-300 dark:border-slate-800/80 hover:shadow-2xl transition-all duration-500 h-full flex flex-col justify-between"
style={{ style={{
borderColor: 'rgba(148, 163, 184, 0.1)',
transition: 'border-color 0.3s, box-shadow 0.3s, transform 0.3s' transition: 'border-color 0.3s, box-shadow 0.3s, transform 0.3s'
}} }}
onMouseEnter={(e) => { onMouseEnter={(e) => {
@@ -90,14 +89,14 @@ export default function Series() {
{key} {key}
</h2> </h2>
<p className="text-xs text-slate-450 dark:text-slate-500 line-clamp-2 leading-relaxed mb-6"> <p className="text-xs text-slate-500 dark:text-slate-450 line-clamp-2 leading-relaxed mb-6">
{serie.descricao} {serie.descricao}
</p> </p>
{/* Detailed specs micro-grid */} {/* Detailed specs micro-grid */}
<div className="space-y-3.5 border-t border-slate-100 dark:border-slate-800/60 pt-6"> <div className="space-y-3.5 border-t border-slate-200 dark:border-slate-800/60 pt-6">
<div className="flex items-center justify-between text-xs"> <div className="flex items-center justify-between text-xs">
<div className="flex items-center gap-2 text-slate-400 dark:text-slate-500 font-semibold select-none"> <div className="flex items-center gap-2 text-slate-500 dark:text-slate-400 font-semibold select-none">
<Cpu className="w-3.5 h-3.5 text-blue-500/80" /> <Cpu className="w-3.5 h-3.5 text-blue-500/80" />
<span>Núcleos:</span> <span>Núcleos:</span>
</div> </div>
@@ -105,7 +104,7 @@ export default function Series() {
</div> </div>
<div className="flex items-center justify-between text-xs"> <div className="flex items-center justify-between text-xs">
<div className="flex items-center gap-2 text-slate-400 dark:text-slate-500 font-semibold select-none"> <div className="flex items-center gap-2 text-slate-500 dark:text-slate-400 font-semibold select-none">
<Activity className="w-3.5 h-3.5 text-amber-500/80" /> <Activity className="w-3.5 h-3.5 text-amber-500/80" />
<span>Frequência:</span> <span>Frequência:</span>
</div> </div>
@@ -113,7 +112,7 @@ export default function Series() {
</div> </div>
<div className="flex items-center justify-between text-xs"> <div className="flex items-center justify-between text-xs">
<div className="flex items-center gap-2 text-slate-400 dark:text-slate-500 font-semibold select-none"> <div className="flex items-center gap-2 text-slate-500 dark:text-slate-400 font-semibold select-none">
<Database className="w-3.5 h-3.5 text-pink-500/80" /> <Database className="w-3.5 h-3.5 text-pink-500/80" />
<span>SRAM:</span> <span>SRAM:</span>
</div> </div>
@@ -121,13 +120,13 @@ export default function Series() {
</div> </div>
<div className="flex items-center justify-between text-xs"> <div className="flex items-center justify-between text-xs">
<div className="flex items-center gap-2 text-slate-400 dark:text-slate-500 font-semibold select-none"> <div className="flex items-center gap-2 text-slate-500 dark:text-slate-400 font-semibold select-none">
<Wifi className="w-3.5 h-3.5 text-emerald-500/80" /> <Wifi className="w-3.5 h-3.5 text-emerald-500/80" />
<span>Wi-Fi:</span> <span>Wi-Fi:</span>
</div> </div>
<span className="font-bold text-slate-800 dark:text-slate-200"> <span className="font-bold text-slate-800 dark:text-slate-200">
{String(serie.wifi).includes("Não") ? ( {String(serie.wifi).includes("Não") ? (
<span className="text-slate-350 dark:text-slate-650">—</span> <span className="text-slate-500 dark:text-slate-400">—</span>
) : ( ) : (
<span className="text-emerald-500 dark:text-emerald-400 font-extrabold">Sim</span> <span className="text-emerald-500 dark:text-emerald-400 font-extrabold">Sim</span>
)} )}
@@ -135,13 +134,13 @@ export default function Series() {
</div> </div>
<div className="flex items-center justify-between text-xs"> <div className="flex items-center justify-between text-xs">
<div className="flex items-center gap-2 text-slate-400 dark:text-slate-500 font-semibold select-none"> <div className="flex items-center gap-2 text-slate-500 dark:text-slate-400 font-semibold select-none">
<Bluetooth className="w-3.5 h-3.5 text-indigo-500/80" /> <Bluetooth className="w-3.5 h-3.5 text-indigo-500/80" />
<span>Bluetooth:</span> <span>Bluetooth:</span>
</div> </div>
<span className="font-bold text-slate-800 dark:text-slate-200"> <span className="font-bold text-slate-800 dark:text-slate-200">
{serie.bluetooth === "Não" ? ( {serie.bluetooth === "Não" ? (
<span className="text-slate-350 dark:text-slate-650">—</span> <span className="text-slate-500 dark:text-slate-400">—</span>
) : ( ) : (
<span className="text-indigo-500 dark:text-indigo-400 font-extrabold">Sim</span> <span className="text-indigo-500 dark:text-indigo-400 font-extrabold">Sim</span>
)} )}
@@ -150,7 +149,7 @@ export default function Series() {
{serie.consumo_energia?.deep_sleep && ( {serie.consumo_energia?.deep_sleep && (
<div className="flex items-center justify-between text-xs"> <div className="flex items-center justify-between text-xs">
<div className="flex items-center gap-2 text-slate-400 dark:text-slate-500 font-semibold select-none"> <div className="flex items-center gap-2 text-slate-500 dark:text-slate-400 font-semibold select-none">
<Battery className="w-3.5 h-3.5 text-green-500/80" /> <Battery className="w-3.5 h-3.5 text-green-500/80" />
<span>Deep Sleep:</span> <span>Deep Sleep:</span>
</div> </div>
@@ -177,12 +176,23 @@ export default function Series() {
</span> </span>
)} )}
{serie.aceleradores_cripto && ( {/* Marcador de segurança: mostra o recurso que DIFERENCIA a série,
<span className="inline-flex items-center gap-1 px-2.5 py-1 bg-teal-500/10 text-teal-600 dark:text-teal-400 text-[10px] font-bold uppercase rounded-lg border border-teal-500/20 shadow-xs select-none"> não o que todas têm. Ordem = do mais raro ao mais comum. */}
{(() => {
const seg = serie.seguranca || {};
const nivel =
seg.key_manager === "Sim" ? "Key Manager"
: String(seg.ecdsa).startsWith("Sim") ? "ECDSA em hardware"
: String(seg.ecc).startsWith("Sim") ? "ECC em hardware"
: String(seg.aes).startsWith("AES") ? "AES + SHA"
: "Só SHA";
return (
<span className="inline-flex items-center gap-1 px-2.5 py-1 bg-teal-500/10 text-teal-600 dark:text-teal-400 text-[10px] font-bold uppercase rounded-lg border border-teal-500/20 select-none">
<ShieldCheck className="w-3 h-3" /> <ShieldCheck className="w-3 h-3" />
<span>Cripto por Hardware</span> <span>{nivel}</span>
</span> </span>
)} );
})()}
{serie.matter === "Sim" && ( {serie.matter === "Sim" && (
<span className="inline-flex items-center gap-1 px-2.5 py-1 bg-emerald-500/10 text-emerald-600 dark:text-emerald-400 text-[10px] font-bold uppercase rounded-lg border border-emerald-500/20 shadow-xs select-none"> <span className="inline-flex items-center gap-1 px-2.5 py-1 bg-emerald-500/10 text-emerald-600 dark:text-emerald-400 text-[10px] font-bold uppercase rounded-lg border border-emerald-500/20 shadow-xs select-none">
@@ -192,7 +202,7 @@ export default function Series() {
)} )}
</div> </div>
<div className="flex items-center justify-between text-xs font-bold text-slate-500 group-hover:text-purple-600 dark:group-hover:text-purple-400 transition-colors uppercase tracking-wider select-none border-t border-slate-100 dark:border-slate-800/60 pt-4"> <div className="flex items-center justify-between text-xs font-bold text-slate-500 group-hover:text-purple-600 dark:group-hover:text-purple-400 transition-colors uppercase tracking-wider select-none border-t border-slate-200 dark:border-slate-800/60 pt-4">
<span>Ver Detalhes</span> <span>Ver Detalhes</span>
<ArrowRight className="w-4 h-4 transform group-hover:translate-x-2 transition-transform duration-300" /> <ArrowRight className="w-4 h-4 transform group-hover:translate-x-2 transition-transform duration-300" />
</div> </div>
@@ -204,26 +214,26 @@ export default function Series() {
</div> </div>
{/* Quick comparison CTA Box */} {/* Quick comparison CTA Box */}
<div className="mt-20 bg-gradient-to-br from-slate-900 to-indigo-950 dark:from-slate-950 dark:to-purple-950/20 rounded-3xl p-8 md:p-12 shadow-2xl relative overflow-hidden border border-slate-800/80"> <div className="mt-20 bg-gradient-to-br from-indigo-50 to-purple-50 dark:from-slate-950 dark:to-purple-950/20 rounded-3xl p-8 md:p-12 shadow-2xl relative overflow-hidden border border-indigo-200 dark:border-slate-800/80">
<div className="absolute right-0 bottom-0 w-96 h-96 bg-purple-500/5 rounded-full blur-3xl pointer-events-none" /> <div className="absolute right-0 bottom-0 w-96 h-96 bg-purple-500/5 rounded-full blur-3xl pointer-events-none" />
<div className="flex flex-col md:flex-row items-center justify-between gap-8 relative z-10"> <div className="flex flex-col md:flex-row items-center justify-between gap-8 relative z-10">
<div className="flex-1 max-w-2xl text-center md:text-left"> <div className="flex-1 max-w-2xl text-center md:text-left">
<div className="inline-flex items-center gap-2 bg-white/10 text-slate-350 px-3 py-1 rounded-full mb-4 text-xs font-semibold select-none animate-pulse"> <div className="inline-flex items-center gap-2 bg-indigo-500/10 text-indigo-700 dark:bg-white/10 dark:text-slate-350 px-3 py-1 rounded-full mb-4 text-xs font-semibold select-none animate-pulse">
<Scale className="w-3.5 h-3.5" /> <Scale className="w-3.5 h-3.5" />
<span>Decisão Técnica</span> <span>Decisão Técnica</span>
</div> </div>
<h3 className="text-2xl md:text-3xl font-display font-extrabold text-white mb-4"> <h3 className="text-2xl md:text-3xl font-display font-extrabold text-slate-900 dark:text-white mb-4">
Ficou em dúvida de qual escolher? Ficou em dúvida de qual escolher?
</h3> </h3>
<p className="text-sm text-slate-400 leading-relaxed"> <p className="text-sm text-slate-600 dark:text-slate-400 leading-relaxed">
Use nosso comparador técnico dinâmico para posicionar as especificações de conectividade RF, potência de rádio e suporte a barramentos de periféricos lado a lado. Use nosso comparador técnico dinâmico para posicionar as especificações de conectividade RF, potência de rádio e suporte a barramentos de periféricos lado a lado.
</p> </p>
</div> </div>
<Link <Link
href="/comparacao" href="/comparacao"
className="inline-flex items-center justify-center gap-2 bg-white text-slate-900 px-6 py-3.5 rounded-xl font-bold text-sm hover:bg-slate-100 transition-all duration-300 shrink-0 shadow-md active:scale-95" className="inline-flex items-center justify-center gap-2 bg-gradient-to-r from-purple-600 to-indigo-600 text-white px-6 py-3.5 rounded-xl font-bold text-sm hover:opacity-90 transition-all duration-300 shrink-0 shadow-md active:scale-95"
> >
<span>Comparar Séries</span> <span>Comparar Séries</span>
<ArrowRight className="w-4 h-4" /> <ArrowRight className="w-4 h-4" />
+9 -9
View File
@@ -27,7 +27,7 @@ export default function Sobre() {
]; ];
return ( return (
<div className="bg-gradient-to-br from-slate-50 via-white to-purple-50/40 dark:from-slate-950 dark:via-slate-950 dark:to-slate-900 min-h-screen text-slate-900 dark:text-slate-100 transition-colors duration-300"> <div className="bg-gradient-to-br from-slate-100 via-slate-50 to-purple-100/40 dark:from-slate-950 dark:via-slate-950 dark:to-slate-900 min-h-screen text-slate-900 dark:text-slate-100 transition-colors duration-300">
<a href="#conteudo" className="sr-only focus:not-sr-only focus:absolute focus:top-4 focus:left-4 focus:bg-white focus:px-4 focus:py-2 focus:rounded-md focus:shadow focus:ring-2 focus:ring-purple-500"> <a href="#conteudo" className="sr-only focus:not-sr-only focus:absolute focus:top-4 focus:left-4 focus:bg-white focus:px-4 focus:py-2 focus:rounded-md focus:shadow focus:ring-2 focus:ring-purple-500">
Ir para o conteúdo Ir para o conteúdo
</a> </a>
@@ -75,7 +75,7 @@ export default function Sobre() {
<div className="mt-12 grid grid-cols-1 lg:grid-cols-3 gap-8"> <div className="mt-12 grid grid-cols-1 lg:grid-cols-3 gap-8">
<section className="lg:col-span-2 space-y-8"> <section className="lg:col-span-2 space-y-8">
{/* Missao article */} {/* Missao article */}
<article className="bg-white/80 dark:bg-slate-900/40 backdrop-blur-xl rounded-3xl p-8 shadow-xl border border-slate-200/60 dark:border-slate-800/80 hover:border-purple-500/20 dark:hover:border-purple-400/20 transition-all"> <article className="bg-white dark:bg-slate-900/40 backdrop-blur-xl rounded-3xl p-8 shadow-xl border border-slate-300 dark:border-slate-800/80 hover:border-purple-500/20 dark:hover:border-purple-400/20 transition-all">
<div className="flex items-center gap-3.5 mb-5 select-none"> <div className="flex items-center gap-3.5 mb-5 select-none">
<div className="w-12 h-12 bg-purple-500/10 border border-purple-500/20 rounded-2xl flex items-center justify-center text-purple-500"> <div className="w-12 h-12 bg-purple-500/10 border border-purple-500/20 rounded-2xl flex items-center justify-center text-purple-500">
<Target className="w-6 h-6" /> <Target className="w-6 h-6" />
@@ -91,7 +91,7 @@ export default function Sobre() {
</article> </article>
{/* Por que o ESPDocs article */} {/* Por que o ESPDocs article */}
<article className="bg-white/80 dark:bg-slate-900/40 backdrop-blur-xl rounded-3xl p-8 shadow-xl border border-slate-200/60 dark:border-slate-800/80"> <article className="bg-white dark:bg-slate-900/40 backdrop-blur-xl rounded-3xl p-8 shadow-xl border border-slate-300 dark:border-slate-800/80">
<div className="flex items-center gap-3.5 mb-5 select-none"> <div className="flex items-center gap-3.5 mb-5 select-none">
<div className="w-12 h-12 bg-blue-500/10 border border-blue-500/20 rounded-2xl flex items-center justify-center text-blue-500"> <div className="w-12 h-12 bg-blue-500/10 border border-blue-500/20 rounded-2xl flex items-center justify-center text-blue-500">
<HelpCircle className="w-6 h-6" /> <HelpCircle className="w-6 h-6" />
@@ -120,7 +120,7 @@ export default function Sobre() {
</article> </article>
{/* Contribuir article */} {/* Contribuir article */}
<article className="bg-white/80 dark:bg-slate-900/40 backdrop-blur-xl rounded-3xl p-8 shadow-xl border border-slate-200/60 dark:border-slate-800/80"> <article className="bg-white dark:bg-slate-900/40 backdrop-blur-xl rounded-3xl p-8 shadow-xl border border-slate-300 dark:border-slate-800/80">
<div className="flex items-center gap-3.5 mb-5 select-none"> <div className="flex items-center gap-3.5 mb-5 select-none">
<div className="w-12 h-12 bg-emerald-500/10 border border-emerald-500/20 rounded-2xl flex items-center justify-center text-emerald-500"> <div className="w-12 h-12 bg-emerald-500/10 border border-emerald-500/20 rounded-2xl flex items-center justify-center text-emerald-500">
<Users className="w-6 h-6" /> <Users className="w-6 h-6" />
@@ -160,7 +160,7 @@ export default function Sobre() {
{/* Sidebar area */} {/* Sidebar area */}
<aside className="space-y-6"> <aside className="space-y-6">
{/* Author Card */} {/* Author Card */}
<div className="bg-white/80 dark:bg-slate-900/40 backdrop-blur-xl border border-slate-200/60 dark:border-slate-800/80 rounded-3xl p-8 shadow-xl"> <div className="bg-white dark:bg-slate-900/40 backdrop-blur-xl border border-slate-300 dark:border-slate-800/80 rounded-3xl p-8 shadow-xl">
<div className="flex flex-col items-center mb-6 select-none"> <div className="flex flex-col items-center mb-6 select-none">
<div className="w-20 h-20 bg-gradient-to-br from-purple-500/10 to-pink-500/10 rounded-2xl flex items-center justify-center mb-4 border border-purple-500/20 shadow-inner"> <div className="w-20 h-20 bg-gradient-to-br from-purple-500/10 to-pink-500/10 rounded-2xl flex items-center justify-center mb-4 border border-purple-500/20 shadow-inner">
<img <img
@@ -206,7 +206,7 @@ export default function Sobre() {
</div> </div>
{/* Repository / Contributions card */} {/* Repository / Contributions card */}
<div className="bg-white/80 dark:bg-slate-900/40 backdrop-blur-xl border border-slate-200/60 dark:border-slate-800/80 rounded-3xl p-6 shadow-xl"> <div className="bg-white dark:bg-slate-900/40 backdrop-blur-xl border border-slate-300 dark:border-slate-800/80 rounded-3xl p-6 shadow-xl">
<div className="flex items-center justify-between mb-4 select-none"> <div className="flex items-center justify-between mb-4 select-none">
<h3 className="text-sm font-display font-extrabold text-slate-800 dark:text-slate-200">Contribua</h3> <h3 className="text-sm font-display font-extrabold text-slate-800 dark:text-slate-200">Contribua</h3>
<span className="text-[9px] bg-emerald-500/10 text-emerald-600 dark:text-emerald-450 px-2.5 py-0.5 rounded-full font-bold uppercase tracking-wider border border-emerald-500/20">Open Source</span> <span className="text-[9px] bg-emerald-500/10 text-emerald-600 dark:text-emerald-450 px-2.5 py-0.5 rounded-full font-bold uppercase tracking-wider border border-emerald-500/20">Open Source</span>
@@ -220,7 +220,7 @@ export default function Sobre() {
href="https://github.com/lucasrguerra/ESPDocs" href="https://github.com/lucasrguerra/ESPDocs"
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"
className="inline-flex items-center justify-center gap-2 w-full px-4 py-3 rounded-xl bg-slate-900 dark:bg-slate-100 text-white dark:text-slate-950 font-bold text-xs uppercase tracking-wider shadow-md hover:opacity-90 active:scale-98 transition-all" className="inline-flex items-center justify-center gap-2 w-full px-4 py-3 rounded-xl bg-gradient-to-r from-purple-600 to-indigo-600 text-white font-bold text-xs uppercase tracking-wider shadow-md hover:opacity-90 active:scale-98 transition-all"
aria-label="Abrir repositório no GitHub" aria-label="Abrir repositório no GitHub"
> >
<svg className="w-4 h-4" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"> <svg className="w-4 h-4" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true">
@@ -229,13 +229,13 @@ export default function Sobre() {
<span>Ver Repositório</span> <span>Ver Repositório</span>
</a> </a>
<div className="mt-4 text-[10px] text-slate-450 dark:text-slate-500 leading-relaxed font-semibold border-t border-slate-100 dark:border-slate-850/45 pt-4"> <div className="mt-4 text-[10px] text-slate-500 dark:text-slate-450 leading-relaxed font-semibold border-t border-slate-200 dark:border-slate-850/45 pt-4">
<strong>Aviso Legal:</strong> O ESPDocs é um agregador e organizador. Manuais técnicos detalhados e repositórios oficiais referenciados são de propriedade exclusiva da Espressif Systems. Para projetos críticos comerciais, consulte sempre a documentação nativa oficial. <strong>Aviso Legal:</strong> O ESPDocs é um agregador e organizador. Manuais técnicos detalhados e repositórios oficiais referenciados são de propriedade exclusiva da Espressif Systems. Para projetos críticos comerciais, consulte sempre a documentação nativa oficial.
</div> </div>
</div> </div>
{/* Summary sidebar card */} {/* Summary sidebar card */}
<div className="bg-white/80 dark:bg-slate-900/40 backdrop-blur-xl border border-slate-200/60 dark:border-slate-800/80 rounded-3xl p-6 shadow-xl select-none"> <div className="bg-white dark:bg-slate-900/40 backdrop-blur-xl border border-slate-300 dark:border-slate-800/80 rounded-3xl p-6 shadow-xl select-none">
<h4 className="text-xs font-display font-extrabold text-slate-800 dark:text-slate-200 mb-3 uppercase tracking-wider">Conteúdo Técnico</h4> <h4 className="text-xs font-display font-extrabold text-slate-800 dark:text-slate-200 mb-3 uppercase tracking-wider">Conteúdo Técnico</h4>
<ul className="text-xs text-slate-600 dark:text-slate-400 space-y-2.5 font-bold"> <ul className="text-xs text-slate-600 dark:text-slate-400 space-y-2.5 font-bold">
<li className="flex items-center gap-2"> <li className="flex items-center gap-2">
+4 -4
View File
@@ -21,11 +21,11 @@ export default function BoardCard({ board }) {
); );
return ( return (
<div className="bg-white/80 dark:bg-slate-900/40 backdrop-blur-xl border border-slate-200/60 dark:border-slate-800/80 rounded-3xl shadow-lg hover:shadow-2xl hover:-translate-y-1.5 transition-all duration-300 overflow-hidden group flex flex-col h-full animate-fadeIn select-none"> <div className="bg-white dark:bg-slate-900/40 backdrop-blur-xl border border-slate-300 dark:border-slate-800/80 rounded-3xl shadow-lg hover:shadow-2xl hover:-translate-y-1.5 transition-all duration-300 overflow-hidden group flex flex-col h-full animate-fadeIn select-none">
{/* Image Container with premium glass background */} {/* Image Container with premium glass background */}
{board.Imagem && ( {board.Imagem && (
<div <div
className="mx-auto w-full p-4 flex justify-center bg-white dark:bg-white border-b border-slate-100 dark:border-slate-850/40 shrink-0" className="mx-auto w-full p-4 flex justify-center bg-white dark:bg-white border-b border-slate-200 dark:border-slate-850/40 shrink-0"
dangerouslySetInnerHTML={{ __html: board.Imagem }} dangerouslySetInnerHTML={{ __html: board.Imagem }}
/> />
)} )}
@@ -50,7 +50,7 @@ export default function BoardCard({ board }) {
{/* Características Técnicas */} {/* Características Técnicas */}
{availableFeatures.length > 0 && ( {availableFeatures.length > 0 && (
<div className="mb-6"> <div className="mb-6">
<h4 className="text-[9px] font-bold text-slate-400 dark:text-slate-500 uppercase tracking-widest flex items-center gap-1.5 mb-2.5"> <h4 className="text-[9px] font-bold text-slate-500 dark:text-slate-400 uppercase tracking-widest flex items-center gap-1.5 mb-2.5">
<CheckCircle className="w-3.5 h-3.5 text-emerald-500" /> <CheckCircle className="w-3.5 h-3.5 text-emerald-500" />
<span>Recursos Embarcados</span> <span>Recursos Embarcados</span>
</h4> </h4>
@@ -87,7 +87,7 @@ export default function BoardCard({ board }) {
href={board.Link} href={board.Link}
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"
className="block w-full bg-slate-900 dark:bg-slate-100 text-white dark:text-slate-950 text-center px-4 py-3 rounded-xl font-bold text-xs uppercase tracking-wider hover:opacity-90 active:scale-98 transition-all shadow-md mt-auto flex items-center justify-center gap-1.5" className="block w-full bg-gradient-to-r from-purple-600 to-indigo-600 text-white text-center px-4 py-3 rounded-xl font-bold text-xs uppercase tracking-wider hover:opacity-90 active:scale-98 transition-all shadow-md mt-auto flex items-center justify-center gap-1.5"
> >
<span>Adquirir no AliExpress</span> <span>Adquirir no AliExpress</span>
<ExternalLink className="w-3.5 h-3.5" /> <ExternalLink className="w-3.5 h-3.5" />
+6 -6
View File
@@ -70,7 +70,7 @@ export default function CodeExample({ framework }) {
} }
return ( return (
<div className="bg-white/80 dark:bg-slate-900/40 backdrop-blur-xl rounded-3xl p-6 md:p-8 border border-slate-200/60 dark:border-slate-800/80 shadow-2xl mb-8 animate-fadeIn"> <div className="bg-white dark:bg-slate-900/40 backdrop-blur-xl rounded-3xl p-6 md:p-8 border border-slate-300 dark:border-slate-800/80 shadow-2xl mb-8 animate-fadeIn">
<div className="md:flex items-center justify-between gap-6 mb-6"> <div className="md:flex items-center justify-between gap-6 mb-6">
<div className="select-none"> <div className="select-none">
<h3 className="text-xl font-display font-extrabold text-slate-850 dark:text-slate-100 mb-1 flex items-center gap-2"> <h3 className="text-xl font-display font-extrabold text-slate-850 dark:text-slate-100 mb-1 flex items-center gap-2">
@@ -94,7 +94,7 @@ export default function CodeExample({ framework }) {
<button <button
onClick={handleCopy} onClick={handleCopy}
className="inline-flex items-center gap-1.5 bg-slate-50 dark:bg-slate-850 hover:bg-slate-100 dark:hover:bg-slate-800 text-slate-700 dark:text-slate-250 px-3.5 py-2 rounded-xl font-bold text-xs shadow-xs border border-slate-200/60 dark:border-slate-800/80 cursor-pointer transition active:scale-95" className="inline-flex items-center gap-1.5 bg-slate-50 dark:bg-slate-850 hover:bg-slate-100 dark:hover:bg-slate-800 text-slate-700 dark:text-slate-250 px-3.5 py-2 rounded-xl font-bold text-xs shadow-xs border border-slate-300 dark:border-slate-800/80 cursor-pointer transition active:scale-95"
title="Copiar código para a área de transferência" title="Copiar código para a área de transferência"
> >
{copied ? ( {copied ? (
@@ -129,16 +129,16 @@ export default function CodeExample({ framework }) {
</div> </div>
</div> </div>
<div className="rounded-2xl overflow-hidden border border-slate-200/60 dark:border-slate-800/80 shadow-md"> <div className="rounded-2xl overflow-hidden border border-slate-300 dark:border-slate-800/80 shadow-md">
<div className="select-none"> <div className="select-none">
<div className="flex items-center justify-between bg-slate-100/80 dark:bg-slate-950/70 border-b border-slate-200/60 dark:border-slate-850/60 px-4 py-3.5"> <div className="flex items-center justify-between bg-slate-100/80 dark:bg-slate-950/70 border-b border-slate-300 dark:border-slate-850/60 px-4 py-3.5">
<div className="text-xs font-bold text-slate-750 dark:text-slate-250 flex items-center gap-1.5"> <div className="text-xs font-bold text-slate-750 dark:text-slate-250 flex items-center gap-1.5">
<span className="w-2.5 h-2.5 rounded-full bg-red-500/80" /> <span className="w-2.5 h-2.5 rounded-full bg-red-500/80" />
<span className="w-2.5 h-2.5 rounded-full bg-yellow-500/80" /> <span className="w-2.5 h-2.5 rounded-full bg-yellow-500/80" />
<span className="w-2.5 h-2.5 rounded-full bg-green-500/80" /> <span className="w-2.5 h-2.5 rounded-full bg-green-500/80" />
<span className="ml-2 font-mono text-[10px] text-slate-500 dark:text-slate-400">{framework?.nome} example</span> <span className="ml-2 font-mono text-[10px] text-slate-500 dark:text-slate-400">{framework?.nome} example</span>
</div> </div>
<div className="text-[9px] font-bold text-slate-400 dark:text-slate-500 uppercase tracking-widest"> <div className="text-[9px] font-bold text-slate-500 dark:text-slate-400 uppercase tracking-widest">
{loading ? 'Buscando exemplo...' : (framework?.exemplo ? 'Local Source' : 'External GitHub')} {loading ? 'Buscando exemplo...' : (framework?.exemplo ? 'Local Source' : 'External GitHub')}
</div> </div>
</div> </div>
@@ -151,7 +151,7 @@ export default function CodeExample({ framework }) {
> >
{lines.map((line, idx) => ( {lines.map((line, idx) => (
<div key={idx} className="flex"> <div key={idx} className="flex">
<span className="text-slate-600 dark:text-slate-650 pr-4 text-right w-8 select-none border-r border-slate-900/60 mr-4 font-semibold">{idx + 1}</span> <span className="text-slate-500 pr-4 text-right w-8 select-none border-r border-slate-900/60 mr-4 font-semibold">{idx + 1}</span>
<code className="whitespace-pre flex-1 text-left">{line === '' ? '\u00A0' : line}</code> <code className="whitespace-pre flex-1 text-left">{line === '' ? '\u00A0' : line}</code>
</div> </div>
))} ))}
+28 -28
View File
@@ -130,15 +130,15 @@ export default function ConnectionsDiagram({ connections, serie }) {
if (!connections || connections.length === 0) { if (!connections || connections.length === 0) {
return ( return (
<div className="flex flex-col items-center justify-center py-16 px-4"> <div className="flex flex-col items-center justify-center py-16 px-4">
<div className="w-20 h-20 mb-6 rounded-2xl bg-slate-100 dark:bg-slate-900 border border-slate-200 dark:border-slate-800 flex items-center justify-center shadow-lg"> <div className="w-20 h-20 mb-6 rounded-2xl bg-slate-100 dark:bg-slate-900 border border-slate-300 dark:border-slate-800 flex items-center justify-center shadow-lg">
<Sliders className="w-10 h-10 text-slate-400 dark:text-slate-600" /> <Sliders className="w-10 h-10 text-slate-500 dark:text-slate-400" />
</div> </div>
<p className="text-lg font-bold text-slate-800 dark:text-slate-200"> <p className="text-lg font-bold text-slate-800 dark:text-slate-200">
Diagrama de conexões indisponível Diagrama de conexões indisponível
</p> </p>
<p className="text-xs text-slate-400 dark:text-slate-500 mt-2"> <p className="text-xs text-slate-500 dark:text-slate-400 mt-2">
Este modelo ainda não possui um mapa detalhado de conexões e barramentos. Este modelo ainda não possui um mapa detalhado de conexões e barramentos.
</p> </p>
</div> </div>
@@ -150,7 +150,7 @@ export default function ConnectionsDiagram({ connections, serie }) {
{/* Header info */} {/* Header info */}
<div className="space-y-3"> <div className="space-y-3">
<h2 className="text-xl font-bold text-slate-850 dark:text-slate-100 mb-4 pb-3 border-b border-slate-200/60 dark:border-slate-800/65 flex items-center gap-3 select-none"> <h2 className="text-xl font-bold text-slate-850 dark:text-slate-100 mb-4 pb-3 border-b border-slate-300 dark:border-slate-800/65 flex items-center gap-3 select-none">
<Sliders className="w-5 h-5" style={{ color: serie.color }} /> <Sliders className="w-5 h-5" style={{ color: serie.color }} />
<span>Diagrama de Conexões Interativo</span> <span>Diagrama de Conexões Interativo</span>
</h2> </h2>
@@ -160,16 +160,16 @@ export default function ConnectionsDiagram({ connections, serie }) {
</div> </div>
{/* Search Panel */} {/* Search Panel */}
<div className="bg-white/60 dark:bg-slate-900/30 backdrop-blur-xl rounded-2xl border border-slate-200/60 dark:border-slate-800/80 p-5 space-y-4 shadow-xl"> <div className="bg-white/60 dark:bg-slate-900/30 backdrop-blur-xl rounded-2xl border border-slate-300 dark:border-slate-800/80 p-5 space-y-4 shadow-xl">
<div className="flex flex-col sm:flex-row gap-4"> <div className="flex flex-col sm:flex-row gap-4">
<div className="flex-1 relative"> <div className="flex-1 relative">
<Search className="absolute left-4 top-1/2 -translate-y-1/2 w-4.5 h-4.5 text-slate-400 dark:text-slate-500" /> <Search className="absolute left-4 top-1/2 -translate-y-1/2 w-4.5 h-4.5 text-slate-500 dark:text-slate-400" />
<input <input
type="text" type="text"
placeholder="Buscar por nome ou número da conexão (Ex: GPIO1, TXD, XTAL)..." placeholder="Buscar por nome ou número da conexão (Ex: GPIO1, TXD, XTAL)..."
value={searchQuery} value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)} onChange={(e) => setSearchQuery(e.target.value)}
className="w-full pl-12 pr-10 py-3 rounded-xl border border-slate-200/60 dark:border-slate-800 bg-white dark:bg-slate-950/40 focus:border-purple-500/80 focus:ring-4 focus:ring-purple-500/10 text-slate-800 dark:text-slate-200 transition-all outline-none text-sm font-semibold" className="w-full pl-12 pr-10 py-3 rounded-xl border border-slate-300 dark:border-slate-800 bg-white dark:bg-slate-950/40 focus:border-purple-500/80 focus:ring-4 focus:ring-purple-500/10 text-slate-800 dark:text-slate-200 transition-all outline-none text-sm font-semibold"
aria-label="Search connections" aria-label="Search connections"
/> />
{searchQuery && ( {searchQuery && (
@@ -217,14 +217,14 @@ export default function ConnectionsDiagram({ connections, serie }) {
</div> </div>
{/* Filters Panel */} {/* Filters Panel */}
<div className="bg-white/60 dark:bg-slate-900/30 backdrop-blur-xl rounded-2xl border border-slate-200/60 dark:border-slate-800/80 p-5 shadow-xl"> <div className="bg-white/60 dark:bg-slate-900/30 backdrop-blur-xl rounded-2xl border border-slate-300 dark:border-slate-800/80 p-5 shadow-xl">
<div className="flex items-center gap-3 mb-5 border-b border-slate-150/40 dark:border-slate-850/45 pb-3"> <div className="flex items-center gap-3 mb-5 border-b border-slate-150/40 dark:border-slate-850/45 pb-3">
<Filter className="w-4 h-4 text-purple-500" /> <Filter className="w-4 h-4 text-purple-500" />
<h4 className="text-sm font-bold text-slate-800 dark:text-slate-100 select-none"> <h4 className="text-sm font-bold text-slate-800 dark:text-slate-100 select-none">
Filtre por Função do Pino Filtre por Função do Pino
</h4> </h4>
<span className="ml-auto text-[10px] font-bold text-slate-400 dark:text-slate-500 bg-slate-100 dark:bg-slate-950 px-3 py-1 rounded-full border border-slate-200/30 dark:border-slate-850/30 shadow-xs select-none"> <span className="ml-auto text-[10px] font-bold text-slate-500 dark:text-slate-400 bg-slate-100 dark:bg-slate-950 px-3 py-1 rounded-full border border-slate-300 dark:border-slate-850/30 shadow-xs select-none">
{visibleConnections.length} de {connections.length} ativos {visibleConnections.length} de {connections.length} ativos
</span> </span>
</div> </div>
@@ -247,7 +247,7 @@ export default function ConnectionsDiagram({ connections, serie }) {
className={`group relative px-4 py-2 rounded-xl text-xs font-extrabold transition-all duration-300 focus:outline-none cursor-pointer active:scale-95 ${ className={`group relative px-4 py-2 rounded-xl text-xs font-extrabold transition-all duration-300 focus:outline-none cursor-pointer active:scale-95 ${
isActive isActive
? "shadow-lg text-white" ? "shadow-lg text-white"
: "bg-slate-50/70 dark:bg-slate-950/20 text-slate-650 dark:text-slate-350 hover:bg-slate-100/50 dark:hover:bg-slate-850/30 border border-slate-200/40 dark:border-slate-850/40" : "bg-slate-50/70 dark:bg-slate-950/20 text-slate-650 dark:text-slate-350 hover:bg-slate-100/50 dark:hover:bg-slate-850/30 border border-slate-300 dark:border-slate-850/40"
}`} }`}
style={{ style={{
backgroundColor: isActive ? filter.color : undefined, backgroundColor: isActive ? filter.color : undefined,
@@ -257,7 +257,7 @@ export default function ConnectionsDiagram({ connections, serie }) {
<span className="flex items-center gap-2"> <span className="flex items-center gap-2">
<span>{filter.name}</span> <span>{filter.name}</span>
<span className={`text-[9px] px-1.5 py-0.5 rounded-full shrink-0 font-bold ${ <span className={`text-[9px] px-1.5 py-0.5 rounded-full shrink-0 font-bold ${
isActive ? "bg-white/20 text-white" : "bg-slate-200/50 dark:bg-slate-800/80 text-slate-555 dark:text-slate-400" isActive ? "bg-white/20 text-white" : "bg-slate-200/50 dark:bg-slate-800/80 text-slate-600 dark:text-slate-400"
}`}> }`}>
{count} {count}
</span> </span>
@@ -269,8 +269,8 @@ export default function ConnectionsDiagram({ connections, serie }) {
{/* Active Categories badges */} {/* Active Categories badges */}
{activeFilter !== "All" && activeCategories.length > 0 && ( {activeFilter !== "All" && activeCategories.length > 0 && (
<div className="mt-5 pt-5 border-t border-slate-100 dark:border-slate-850/40 select-none"> <div className="mt-5 pt-5 border-t border-slate-200 dark:border-slate-850/40 select-none">
<p className="text-[10px] font-bold text-slate-400 dark:text-slate-500 mb-3 uppercase tracking-wider">Subcategorias ativadas por esse filtro:</p> <p className="text-[10px] font-bold text-slate-500 dark:text-slate-400 mb-3 uppercase tracking-wider">Subcategorias ativadas por esse filtro:</p>
<div className="flex flex-wrap gap-2"> <div className="flex flex-wrap gap-2">
{activeCategories.map(cat => ( {activeCategories.map(cat => (
<span <span
@@ -289,7 +289,7 @@ export default function ConnectionsDiagram({ connections, serie }) {
{/* Diagram Workspace */} {/* Diagram Workspace */}
{visibleConnections.length === 0 ? ( {visibleConnections.length === 0 ? (
<div className="bg-slate-50/50 dark:bg-slate-900/10 backdrop-blur-xl rounded-2xl p-12 border-2 border-dashed border-slate-200 dark:border-slate-850/80 text-center shadow-inner"> <div className="bg-slate-50/50 dark:bg-slate-900/10 backdrop-blur-xl rounded-2xl p-12 border-2 border-dashed border-slate-300 dark:border-slate-850/80 text-center shadow-inner">
<div className="w-16 h-16 mx-auto mb-4 rounded-full bg-slate-100 dark:bg-slate-950 flex items-center justify-center shadow-md"> <div className="w-16 h-16 mx-auto mb-4 rounded-full bg-slate-100 dark:bg-slate-950 flex items-center justify-center shadow-md">
<Search className="w-6 h-6 text-slate-400" /> <Search className="w-6 h-6 text-slate-400" />
</div> </div>
@@ -301,14 +301,14 @@ export default function ConnectionsDiagram({ connections, serie }) {
setActiveFilter("All"); setActiveFilter("All");
setSearchQuery(""); setSearchQuery("");
}} }}
className="mt-5 px-6 py-2.5 bg-slate-900 dark:bg-slate-100 text-white dark:text-slate-950 rounded-xl hover:opacity-90 transition-all font-bold text-xs uppercase tracking-wider cursor-pointer active:scale-95 shadow-lg" className="mt-5 px-6 py-2.5 bg-gradient-to-r from-purple-600 to-indigo-600 text-white rounded-xl hover:opacity-90 transition-all font-bold text-xs uppercase tracking-wider cursor-pointer active:scale-95 shadow-lg"
> >
Limpar Filtros e Busca Limpar Filtros e Busca
</button> </button>
</div> </div>
) : viewMode === "grid" ? ( ) : viewMode === "grid" ? (
/* Grid View Mode: Futuristic Electronic Chip Sockets */ /* Grid View Mode: Futuristic Electronic Chip Sockets */
<div className="bg-slate-100/50 dark:bg-slate-950/20 backdrop-blur-xl rounded-3xl p-6 md:p-8 border border-slate-200/50 dark:border-slate-850/60 shadow-inner"> <div className="bg-slate-100/50 dark:bg-slate-950/20 backdrop-blur-xl rounded-3xl p-6 md:p-8 border border-slate-300 dark:border-slate-850/60 shadow-inner">
<div className="grid grid-cols-2 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-5 xl:grid-cols-6 gap-4"> <div className="grid grid-cols-2 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-5 xl:grid-cols-6 gap-4">
{visibleConnections.map((connection, index) => { {visibleConnections.map((connection, index) => {
const isHovered = hoveredConnection === index; const isHovered = hoveredConnection === index;
@@ -367,7 +367,7 @@ export default function ConnectionsDiagram({ connections, serie }) {
/> />
))} ))}
{connection.categorias.length > 5 && ( {connection.categorias.length > 5 && (
<span className="text-[9px] text-slate-400 dark:text-slate-500 font-bold ml-0.5 leading-none"> <span className="text-[9px] text-slate-500 dark:text-slate-400 font-bold ml-0.5 leading-none">
+{connection.categorias.length - 5} +{connection.categorias.length - 5}
</span> </span>
)} )}
@@ -419,11 +419,11 @@ export default function ConnectionsDiagram({ connections, serie }) {
</div> </div>
) : ( ) : (
/* List View Mode: Interactive Zebra Table */ /* List View Mode: Interactive Zebra Table */
<div className="bg-white/80 dark:bg-slate-900/40 backdrop-blur-xl rounded-2xl border border-slate-200/60 dark:border-slate-800/80 overflow-hidden shadow-xl"> <div className="bg-white dark:bg-slate-900/40 backdrop-blur-xl rounded-2xl border border-slate-300 dark:border-slate-800/80 overflow-hidden shadow-xl">
<div className="overflow-x-auto scrollbar-thin"> <div className="overflow-x-auto scrollbar-thin">
<table className="w-full text-left"> <table className="w-full text-left">
<thead> <thead>
<tr className="bg-slate-100/50 dark:bg-slate-950/50 border-b border-slate-200/60 dark:border-slate-850/60 select-none"> <tr className="bg-slate-100/50 dark:bg-slate-950/50 border-b border-slate-300 dark:border-slate-850/60 select-none">
<th className="px-6 py-4 text-xs font-bold text-slate-500 dark:text-slate-400 uppercase tracking-widest">Pino</th> <th className="px-6 py-4 text-xs font-bold text-slate-500 dark:text-slate-400 uppercase tracking-widest">Pino</th>
<th className="px-6 py-4 text-xs font-bold text-slate-500 dark:text-slate-400 uppercase tracking-widest">Nome do Pad</th> <th className="px-6 py-4 text-xs font-bold text-slate-500 dark:text-slate-400 uppercase tracking-widest">Nome do Pad</th>
<th className="px-6 py-4 text-xs font-bold text-slate-500 dark:text-slate-400 uppercase tracking-widest">Categorias</th> <th className="px-6 py-4 text-xs font-bold text-slate-500 dark:text-slate-400 uppercase tracking-widest">Categorias</th>
@@ -532,11 +532,11 @@ export default function ConnectionsDiagram({ connections, serie }) {
aria-labelledby="modal-title" aria-labelledby="modal-title"
> >
<div <div
className="bg-white/95 dark:bg-slate-950/95 backdrop-blur-xl border border-slate-200/50 dark:border-slate-800/80 rounded-3xl shadow-2xl max-w-2xl w-full max-h-[85vh] overflow-hidden flex flex-col animate-slideUp" className="bg-white/95 dark:bg-slate-950/95 backdrop-blur-xl border border-slate-300 dark:border-slate-800/80 rounded-3xl shadow-2xl max-w-2xl w-full max-h-[85vh] overflow-hidden flex flex-col animate-slideUp"
onClick={(e) => e.stopPropagation()} onClick={(e) => e.stopPropagation()}
> >
{/* Dialog Header */} {/* Dialog Header */}
<div className="sticky top-0 bg-white/50 dark:bg-slate-950/50 border-b border-slate-200/60 dark:border-slate-850/60 p-6 md:p-8 z-10"> <div className="sticky top-0 bg-white/50 dark:bg-slate-950/50 border-b border-slate-300 dark:border-slate-850/60 p-6 md:p-8 z-10">
<div className="flex items-start justify-between gap-6"> <div className="flex items-start justify-between gap-6">
<div className="flex items-center gap-4 flex-1"> <div className="flex items-center gap-4 flex-1">
<div <div
@@ -551,7 +551,7 @@ export default function ConnectionsDiagram({ connections, serie }) {
<h3 id="modal-title" className="text-2xl font-display font-extrabold text-slate-850 dark:text-slate-100 mb-1 leading-none tracking-tight"> <h3 id="modal-title" className="text-2xl font-display font-extrabold text-slate-850 dark:text-slate-100 mb-1 leading-none tracking-tight">
{selectedConnection.nome} {selectedConnection.nome}
</h3> </h3>
<p className="text-xs text-slate-400 dark:text-slate-500 font-bold uppercase tracking-wider"> <p className="text-xs text-slate-500 dark:text-slate-400 font-bold uppercase tracking-wider">
Pino Físico {selectedConnection.numero} Pino Físico {selectedConnection.numero}
</p> </p>
</div> </div>
@@ -573,7 +573,7 @@ export default function ConnectionsDiagram({ connections, serie }) {
{/* Categories */} {/* Categories */}
{selectedConnection.categorias.length > 0 && ( {selectedConnection.categorias.length > 0 && (
<div> <div>
<h4 className="text-xs font-bold text-slate-400 dark:text-slate-500 uppercase tracking-widest mb-3 flex items-center gap-2 select-none"> <h4 className="text-xs font-bold text-slate-500 dark:text-slate-400 uppercase tracking-widest mb-3 flex items-center gap-2 select-none">
<Tag className="w-4 h-4 text-purple-500" /> <Tag className="w-4 h-4 text-purple-500" />
<span>Categorias de Multiplexação</span> <span>Categorias de Multiplexação</span>
</h4> </h4>
@@ -595,11 +595,11 @@ export default function ConnectionsDiagram({ connections, serie }) {
{/* Power Source */} {/* Power Source */}
{selectedConnection.alimentacao && ( {selectedConnection.alimentacao && (
<div> <div>
<h4 className="text-xs font-bold text-slate-400 dark:text-slate-500 uppercase tracking-widest mb-3 flex items-center gap-2 select-none"> <h4 className="text-xs font-bold text-slate-500 dark:text-slate-400 uppercase tracking-widest mb-3 flex items-center gap-2 select-none">
<Zap className="w-4 h-4 text-red-500" /> <Zap className="w-4 h-4 text-red-500" />
<span>Domínio de Alimentação Interna</span> <span>Domínio de Alimentação Interna</span>
</h4> </h4>
<div className="bg-slate-50 dark:bg-slate-900/30 rounded-2xl p-5 border border-slate-200/50 dark:border-slate-850/50"> <div className="bg-slate-50 dark:bg-slate-900/30 rounded-2xl p-5 border border-slate-300 dark:border-slate-850/50">
<ul className="space-y-2.5"> <ul className="space-y-2.5">
{selectedConnection.alimentacao.map((source, index) => ( {selectedConnection.alimentacao.map((source, index) => (
<li key={index} className="flex items-center gap-3 text-xs font-bold text-slate-700 dark:text-slate-300"> <li key={index} className="flex items-center gap-3 text-xs font-bold text-slate-700 dark:text-slate-300">
@@ -617,12 +617,12 @@ export default function ConnectionsDiagram({ connections, serie }) {
{/* Function Multiplexation */} {/* Function Multiplexation */}
<div> <div>
<h4 className="text-xs font-bold text-slate-400 dark:text-slate-500 uppercase tracking-widest mb-3 flex items-center gap-2 select-none"> <h4 className="text-xs font-bold text-slate-500 dark:text-slate-400 uppercase tracking-widest mb-3 flex items-center gap-2 select-none">
<Sliders className="w-4 h-4 text-emerald-500" /> <Sliders className="w-4 h-4 text-emerald-500" />
<span>Barramentos de Funções Disponíveis</span> <span>Barramentos de Funções Disponíveis</span>
</h4> </h4>
<div className="bg-slate-50 dark:bg-slate-900/30 rounded-2xl p-5 border border-slate-200/50 dark:border-slate-850/50"> <div className="bg-slate-50 dark:bg-slate-900/30 rounded-2xl p-5 border border-slate-300 dark:border-slate-850/50">
<ul className="grid grid-cols-1 sm:grid-cols-2 gap-3"> <ul className="grid grid-cols-1 sm:grid-cols-2 gap-3">
{selectedConnection.funcoes.map((func, index) => ( {selectedConnection.funcoes.map((func, index) => (
<li key={index} className="flex items-center gap-3 text-xs font-bold text-slate-700 dark:text-slate-300"> <li key={index} className="flex items-center gap-3 text-xs font-bold text-slate-700 dark:text-slate-300">
@@ -663,7 +663,7 @@ export default function ConnectionsDiagram({ connections, serie }) {
</div> </div>
{/* Dialog Footer */} {/* Dialog Footer */}
<div className="sticky bottom-0 bg-white/50 dark:bg-slate-950/50 border-t border-slate-200/60 dark:border-slate-850/60 p-5"> <div className="sticky bottom-0 bg-white/50 dark:bg-slate-950/50 border-t border-slate-300 dark:border-slate-850/60 p-5">
<button <button
onClick={() => setSelectedConnection(null)} onClick={() => setSelectedConnection(null)}
className="w-full py-3.5 rounded-xl font-bold text-white text-xs uppercase tracking-wider shadow-lg hover:opacity-90 active:scale-98 transition-all cursor-pointer" className="w-full py-3.5 rounded-xl font-bold text-white text-xs uppercase tracking-wider shadow-lg hover:opacity-90 active:scale-98 transition-all cursor-pointer"
+1 -1
View File
@@ -133,7 +133,7 @@ export default function Footer() {
</div> </div>
{/* Divisor & Isenção */} {/* Divisor & Isenção */}
<div className="mt-12 pt-8 border-t border-slate-205/60 dark:border-slate-800/80 flex flex-col md:flex-row justify-between items-center gap-4 text-xs text-slate-400 dark:text-slate-500"> <div className="mt-12 pt-8 border-t border-slate-300 dark:border-slate-800/80 flex flex-col md:flex-row justify-between items-center gap-4 text-xs text-slate-500 dark:text-slate-400">
<p className="text-center md:text-left leading-relaxed"> <p className="text-center md:text-left leading-relaxed">
© {year} ESPDocs. Esta plataforma independente de código aberto não possui afiliação com a Espressif Systems. © {year} ESPDocs. Esta plataforma independente de código aberto não possui afiliação com a Espressif Systems.
</p> </p>
+7 -7
View File
@@ -62,7 +62,7 @@ export default function Header() {
}, [drawerOpen]); }, [drawerOpen]);
return ( return (
<header className="bg-white/80 dark:bg-slate-950/80 backdrop-blur-md py-4 px-4 md:px-8 shadow-xs border-b border-slate-100 dark:border-slate-800/60 sticky top-0 z-50 transition-colors duration-300"> <header className="bg-white dark:bg-slate-950/80 backdrop-blur-md py-4 px-4 md:px-8 shadow-xs border-b border-slate-200 dark:border-slate-800/60 sticky top-0 z-50 transition-colors duration-300">
<div className="flex justify-between max-w-7xl mx-auto items-center gap-8"> <div className="flex justify-between max-w-7xl mx-auto items-center gap-8">
{/* LOGO */} {/* LOGO */}
<Link href="/" className="group shrink-0"> <Link href="/" className="group shrink-0">
@@ -103,7 +103,7 @@ export default function Header() {
{/* Full Menu Burger Trigger */} {/* Full Menu Burger Trigger */}
<button <button
onClick={() => setDrawerOpen(true)} onClick={() => setDrawerOpen(true)}
className="p-2.5 rounded-xl border border-slate-200 dark:border-slate-800 text-slate-600 dark:text-slate-300 hover:bg-slate-50 dark:hover:bg-slate-900/80 hover:border-purple-500 dark:hover:border-purple-400 hover:text-purple-600 dark:hover:text-purple-400 transition-all cursor-pointer" className="p-2.5 rounded-xl border border-slate-300 dark:border-slate-800 text-slate-600 dark:text-slate-300 hover:bg-slate-50 dark:hover:bg-slate-900/80 hover:border-purple-500 dark:hover:border-purple-400 hover:text-purple-600 dark:hover:text-purple-400 transition-all cursor-pointer"
aria-label="Abrir menu completo" aria-label="Abrir menu completo"
aria-expanded={drawerOpen} aria-expanded={drawerOpen}
> >
@@ -116,7 +116,7 @@ export default function Header() {
<ThemeToggle /> <ThemeToggle />
<button <button
onClick={() => setDrawerOpen(true)} onClick={() => setDrawerOpen(true)}
className="p-2.5 rounded-xl border border-slate-200 dark:border-slate-800 text-slate-600 dark:text-slate-300 hover:bg-slate-50 dark:hover:bg-slate-900/80 hover:border-purple-500 dark:hover:border-purple-400 transition-all cursor-pointer" className="p-2.5 rounded-xl border border-slate-300 dark:border-slate-800 text-slate-600 dark:text-slate-300 hover:bg-slate-50 dark:hover:bg-slate-900/80 hover:border-purple-500 dark:hover:border-purple-400 transition-all cursor-pointer"
aria-label="Abrir menu de navegação" aria-label="Abrir menu de navegação"
aria-expanded={drawerOpen} aria-expanded={drawerOpen}
> >
@@ -136,12 +136,12 @@ export default function Header() {
)} )}
<div <div
className={`fixed top-0 right-0 h-screen w-80 max-w-[85vw] bg-white dark:bg-slate-900 shadow-2xl z-[101] transform transition-transform duration-300 ease-in-out flex flex-col border-l border-slate-200 dark:border-slate-800 ${ className={`fixed top-0 right-0 h-screen w-80 max-w-[85vw] bg-white dark:bg-slate-900 shadow-2xl z-[101] transform transition-transform duration-300 ease-in-out flex flex-col border-l border-slate-300 dark:border-slate-800 ${
drawerOpen ? "translate-x-0" : "translate-x-full" drawerOpen ? "translate-x-0" : "translate-x-full"
}`} }`}
> >
{/* Drawer Header */} {/* Drawer Header */}
<div className="flex items-center justify-between p-6 border-b border-slate-100 dark:border-slate-800/80"> <div className="flex items-center justify-between p-6 border-b border-slate-200 dark:border-slate-800/80">
<h2 className="text-xl font-display font-bold bg-gradient-to-r from-blue-500 to-purple-500 bg-clip-text text-transparent"> <h2 className="text-xl font-display font-bold bg-gradient-to-r from-blue-500 to-purple-500 bg-clip-text text-transparent">
Menu Navegação Menu Navegação
</h2> </h2>
@@ -181,8 +181,8 @@ export default function Header() {
</nav> </nav>
{/* Drawer Footer Info */} {/* Drawer Footer Info */}
<div className="p-6 border-t border-slate-100 dark:border-slate-800/80 bg-slate-50/50 dark:bg-slate-900/50"> <div className="p-6 border-t border-slate-200 dark:border-slate-800/80 bg-slate-50/50 dark:bg-slate-900/50">
<p className="text-center text-xs text-slate-400 dark:text-slate-500 font-medium"> <p className="text-center text-xs text-slate-500 dark:text-slate-400 font-medium">
ESPDocs • Plataforma Não Oficial ESPDocs • Plataforma Não Oficial
</p> </p>
</div> </div>
+178
View File
@@ -0,0 +1,178 @@
import Link from "next/link";
import {
AlertTriangle,
ArrowDownToLine,
HardDrive,
Power,
Bug,
Usb,
Zap,
ArrowRight,
} from "lucide-react";
/**
* Restrições de pinos por série.
*
* Os grupos são DERIVADOS de public/conexoes/<serie>.json — o mesmo arquivo que
* alimenta o diagrama de conexões. Nada aqui é escrito à mão, então a seção e o
* diagrama nunca divergem: corrigir um pino no JSON corrige os dois.
*/
const GRUPOS = [
{
id: "entrada",
titulo: "Somente entrada",
icone: ArrowDownToLine,
cor: "text-sky-500",
explicacao:
"Aceitam apenas leitura. Não podem ser configurados como saída e não possuem resistores de pull-up ou pull-down internos — se precisar de um, coloque no circuito.",
combina: (avisos) => /apenas de entrada|input only/.test(avisos),
},
{
id: "flash",
titulo: "Ligados à memória flash/PSRAM",
icone: HardDrive,
cor: "text-rose-500",
explicacao:
"Já estão conectados à memória de onde o firmware é lido. Usá-los como GPIO comum normalmente trava o chip ou corrompe a execução. Em módulos com flash interna eles nem chegam ao encapsulamento.",
combina: (avisos) =>
/(flash|psram)/.test(avisos) &&
/(dedicad|utilizado pela mem|não deve ser utilizado|não é recomendado)/.test(avisos),
},
{
id: "strapping",
titulo: "Strapping (modo de boot)",
icone: Power,
cor: "text-amber-500",
explicacao:
"O chip lê o nível destes pinos no instante do reset para decidir como inicializar. Dá para usá-los depois do boot, mas o circuito externo não pode forçar o nível errado durante o reset — senão a placa não inicia ou entra em modo de gravação sozinha.",
combina: (avisos, cats) =>
cats.includes("Strapping") ||
/strapping|modo de boot|modo de inicialização/.test(avisos),
},
{
id: "debug",
titulo: "Gravação e depuração",
icone: Bug,
cor: "text-violet-500",
explicacao:
"São a UART de gravação e as linhas de JTAG. Continuam utilizáveis como GPIO, mas ao reaproveitá-los você perde o caminho de gravar e depurar a placa sem desconectar o que estiver ali.",
combina: (avisos, cats) => /debugging/.test(avisos) || cats.includes("JTAG"),
},
{
id: "usb",
titulo: "USB nativo",
icone: Usb,
cor: "text-teal-500",
explicacao:
"Saem de fábrica ligados ao USB Serial/JTAG interno. Para usá-los como GPIO é preciso desativar essa função — o que também remove a gravação por USB direto.",
combina: (avisos) => /usb serial\/jtag|usb_d[+-]/.test(avisos),
},
{
id: "eletrico",
titulo: "Não podem ficar flutuando",
icone: Zap,
cor: "text-orange-500",
explicacao:
"Precisam de nível definido por hardware: pull-up externo ou circuito RC de atraso. Deixá-los soltos causa reset aleatório ou inicialização instável.",
combina: (avisos) => /flutuando/.test(avisos),
},
];
/** Nome do pino + o GPIO correspondente, quando o nome é uma função (ex.: SENSOR_VP). */
function rotuloDoPino(pino) {
const gpio = (pino.funcoes || []).find((f) => /^GPIO\d+$/.test(f));
return gpio && gpio !== pino.nome ? `${pino.nome} · ${gpio}` : pino.nome;
}
function derivarGrupos(conexoes) {
return GRUPOS.map((grupo) => {
const vistos = new Map();
for (const pino of conexoes) {
if (!["io", "analog", "dedicated"].includes(pino.tipo)) continue;
const avisos = (pino.avisos || []).join(" ").toLowerCase();
const cats = pino.categorias || [];
if (grupo.combina(avisos, cats)) {
const rotulo = rotuloDoPino(pino);
if (!vistos.has(rotulo)) vistos.set(rotulo, pino);
}
}
return { ...grupo, pinos: [...vistos.keys()] };
}).filter((g) => g.pinos.length > 0);
}
export default function PinRestrictions({ conexoes, serieKey, cor }) {
if (!conexoes?.length) return null;
const grupos = derivarGrupos(conexoes);
if (!grupos.length) return null;
const totalPinos = new Set(grupos.flatMap((g) => g.pinos)).size;
return (
<div className="bg-white dark:bg-slate-900/40 backdrop-blur-xl rounded-3xl p-6 md:p-8 border border-slate-300 dark:border-slate-800/80 shadow-2xl mb-12">
<h2 className="text-xl font-bold text-slate-850 dark:text-slate-100 mb-3 pb-3 border-b border-slate-200 dark:border-slate-800/65 flex items-center gap-3">
<AlertTriangle className="w-5 h-5" style={{ color: cor }} />
<span>Pinos com Restrição de Uso</span>
</h2>
<p className="text-sm text-slate-650 dark:text-slate-350 leading-relaxed mb-8">
Nem todo pino do {serieKey} está livre para uso geral. {totalPinos} deles têm alguma
condição — já estão ocupados por memória, são lidos durante o boot ou só funcionam
como entrada. Vale conferir antes de fechar o esquemático.
</p>
<div className="grid md:grid-cols-2 gap-5">
{grupos.map(({ id, titulo, icone: Icone, cor: corIcone, explicacao, pinos }) => (
<div
key={id}
className="bg-slate-50 dark:bg-slate-950/40 rounded-2xl p-5 border border-slate-200 dark:border-slate-800/60"
>
<div className="flex items-center gap-2.5 mb-3">
<Icone className={`w-4 h-4 shrink-0 ${corIcone}`} />
<h3 className="text-sm font-bold text-slate-800 dark:text-slate-200">{titulo}</h3>
<span className="ml-auto text-[10px] font-bold text-slate-500 dark:text-slate-400 tabular">
{pinos.length} {pinos.length === 1 ? "pino" : "pinos"}
</span>
</div>
<div className="flex flex-wrap gap-1.5 mb-3.5">
{pinos.map((p) => (
<span
key={p}
className="px-2 py-1 rounded-lg text-[10px] font-bold font-mono"
style={{
backgroundColor: `${cor}18`,
color: cor,
border: `1px solid ${cor}30`,
}}
>
{p}
</span>
))}
</div>
<p className="text-xs text-slate-500 dark:text-slate-400 leading-relaxed">
{explicacao}
</p>
</div>
))}
</div>
<div className="mt-7 pt-6 border-t border-slate-200 dark:border-slate-800/65 flex flex-col sm:flex-row sm:items-center justify-between gap-4">
<p className="text-xs text-slate-500 dark:text-slate-400 leading-relaxed max-w-xl">
Estes grupos vêm dos mesmos dados do diagrama de conexões. Lá você vê pino a pino
todas as funções, o domínio de alimentação e os avisos completos de cada um.
</p>
<Link
href="#connections"
className="inline-flex items-center justify-center gap-2 shrink-0 px-5 py-3 rounded-xl font-bold text-xs uppercase tracking-wider text-white transition-opacity hover:opacity-90"
style={{ backgroundColor: cor }}
>
<span>Abrir diagrama de pinos</span>
<ArrowRight className="w-4 h-4" />
</Link>
</div>
</div>
);
}
+38 -7
View File
@@ -1,24 +1,55 @@
'use client'; 'use client';
import { useState } from 'react'; import { useState, useEffect } from 'react';
import { ChevronDown } from 'lucide-react'; import { ChevronDown } from 'lucide-react';
export default function SeriesTabMenu({ tabs, color }) { export default function SeriesTabMenu({ tabs, color }) {
const availableTabs = tabs.filter(tab => tab.available); const availableTabs = tabs.filter(tab => tab.available);
const [activeTab, setActiveTab] = useState(availableTabs[0]?.id || 'specs'); const [activeTab, setActiveTab] = useState(availableTabs[0]?.id || 'specs');
// Troca manual limpa o hash. Sem isso, depois de abrir o diagrama pelo link e
// mudar de aba na mão, o hash continuaria em #connections e um novo clique no
// link não dispararia hashchange — o botão pareceria quebrado.
const trocarAba = (id) => {
setActiveTab(id);
if (window.location.hash) {
history.replaceState(null, '', window.location.pathname + window.location.search);
}
};
// Abas endereçáveis por hash: #connections abre o diagrama de pinos direto.
// É o que permite a seção de restrições apontar para cá.
useEffect(() => {
const abrirPeloHash = () => {
const alvo = window.location.hash.replace('#', '');
if (alvo && availableTabs.some(t => t.id === alvo)) {
setActiveTab(alvo);
// Nenhum elemento tem id="connections", então o navegador não rola
// sozinho: levamos até as abas na mão.
const semAnimacao = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
document.getElementById('diagrama-abas')?.scrollIntoView({
behavior: semAnimacao ? 'auto' : 'smooth',
block: 'start',
});
}
};
abrirPeloHash();
window.addEventListener('hashchange', abrirPeloHash);
return () => window.removeEventListener('hashchange', abrirPeloHash);
}, [availableTabs]);
const activeTabContent = availableTabs.find(tab => tab.id === activeTab)?.content; const activeTabContent = availableTabs.find(tab => tab.id === activeTab)?.content;
return ( return (
<div className="mb-12"> <div className="mb-12" id="diagrama-abas">
{/* Desktop Tabs */} {/* Desktop Tabs */}
<div className="hidden md:flex bg-white/70 dark:bg-slate-900/40 backdrop-blur-xl rounded-2xl shadow-xl py-2 px-3 mb-6 border border-slate-200/60 dark:border-slate-800/80 gap-3"> <div className="hidden md:flex bg-white dark:bg-slate-900/40 backdrop-blur-xl rounded-2xl shadow-xl py-2 px-3 mb-6 border border-slate-300 dark:border-slate-800/80 gap-3">
{availableTabs.map((tab) => { {availableTabs.map((tab) => {
const isActive = activeTab === tab.id; const isActive = activeTab === tab.id;
return ( return (
<button <button
key={tab.id} key={tab.id}
onClick={() => setActiveTab(tab.id)} onClick={() => trocarAba(tab.id)}
className={`flex-1 flex items-center justify-center gap-2 py-3 px-5 rounded-xl font-bold text-xs uppercase tracking-wider transition-all duration-300 cursor-pointer active:scale-98 ${ className={`flex-1 flex items-center justify-center gap-2 py-3 px-5 rounded-xl font-bold text-xs uppercase tracking-wider transition-all duration-300 cursor-pointer active:scale-98 ${
isActive isActive
? 'text-white shadow-lg scale-[1.02]' ? 'text-white shadow-lg scale-[1.02]'
@@ -38,7 +69,7 @@ export default function SeriesTabMenu({ tabs, color }) {
{/* Mobile Dropdown Tab Selector */} {/* Mobile Dropdown Tab Selector */}
<div className="md:hidden mb-6"> <div className="md:hidden mb-6">
<div className="bg-white/70 dark:bg-slate-900/40 backdrop-blur-xl rounded-2xl shadow-lg border border-slate-200/60 dark:border-slate-800/80 overflow-hidden"> <div className="bg-white dark:bg-slate-900/40 backdrop-blur-xl rounded-2xl shadow-lg border border-slate-300 dark:border-slate-800/80 overflow-hidden">
<button <button
onClick={() => { onClick={() => {
const dropdown = document.getElementById('mobile-tab-dropdown'); const dropdown = document.getElementById('mobile-tab-dropdown');
@@ -61,10 +92,10 @@ export default function SeriesTabMenu({ tabs, color }) {
<button <button
key={tab.id} key={tab.id}
onClick={() => { onClick={() => {
setActiveTab(tab.id); trocarAba(tab.id);
document.getElementById('mobile-tab-dropdown').classList.add('hidden'); document.getElementById('mobile-tab-dropdown').classList.add('hidden');
}} }}
className="w-full flex items-center gap-3 py-4 px-6 text-slate-700 dark:text-slate-300 hover:bg-slate-150/40 dark:hover:bg-slate-800/30 transition-colors border-t border-slate-200/60 dark:border-slate-800/60 font-semibold text-xs uppercase tracking-wider text-left" className="w-full flex items-center gap-3 py-4 px-6 text-slate-700 dark:text-slate-300 hover:bg-slate-150/40 dark:hover:bg-slate-800/30 transition-colors border-t border-slate-300 dark:border-slate-800/60 font-semibold text-xs uppercase tracking-wider text-left"
> >
<span>{tab.label}</span> <span>{tab.label}</span>
</button> </button>
+2 -1
View File
@@ -6,7 +6,8 @@
"dev": "next dev --turbopack", "dev": "next dev --turbopack",
"build": "next build --turbopack", "build": "next build --turbopack",
"start": "next start", "start": "next start",
"lint": "eslint" "lint": "eslint",
"auditar-temas": "node scripts/auditar-temas.mjs"
}, },
"dependencies": { "dependencies": {
"@emotion/react": "^11.14.0", "@emotion/react": "^11.14.0",
+228 -12
View File
@@ -7,7 +7,20 @@
"nucleos": "1 (Single-core) ou 2 (Dual-core)", "nucleos": "1 (Single-core) ou 2 (Dual-core)",
"frequencia": "Até 240 MHz", "frequencia": "Até 240 MHz",
"coprocessador_ulp": "Sim", "coprocessador_ulp": "Sim",
"aceleradores_cripto": "Sim (AES-128/192/256, SHA-1/256/384/512, RSA/MPI até 4096 bits, RNG, Flash Encryption, Secure Boot V1)", "seguranca": {
"aes": "AES-128/192/256",
"sha": "SHA-1/256/384/512",
"rsa": "Sim (RSA/MPI até 4096 bits)",
"ecc": "Não",
"ecdsa": "Não",
"hmac": "Não",
"assinatura_digital": "Não",
"key_manager": "Não",
"rng": "Sim (TRNG por hardware)",
"criptografia_flash": "Sim (AES-256, esquema legado)",
"secure_boot": "V1 (legado)",
"protecao_dpa": "Não"
},
"wifi": "IEEE 802.11 b/g/n (2.4 GHz, até 150 Mbps)", "wifi": "IEEE 802.11 b/g/n (2.4 GHz, até 150 Mbps)",
"bluetooth": "Bluetooth 4.2 BR/EDR (Clássico) e Bluetooth 5.0 LE (certificado)", "bluetooth": "Bluetooth 4.2 BR/EDR (Clássico) e Bluetooth 5.0 LE (certificado)",
"ethernet": "Sim (Ethernet MAC integrado)", "ethernet": "Sim (Ethernet MAC integrado)",
@@ -40,6 +53,11 @@
}, },
"temperatura_operacao": "-40°C a 125°C", "temperatura_operacao": "-40°C a 125°C",
"temperatura_armazenamento": "-40°C a 150°C", "temperatura_armazenamento": "-40°C a 150°C",
"depuracao": "JTAG externo (requer sonda, ex.: ESP-PROG)",
"esp_idf_minimo": "Desde o início (v3.x e anteriores)",
"arduino_core": "Sim (147 definições de placa no core oficial)",
"encapsulamento": "QFN48 (6×6 mm) e QFN48 (5×5 mm)",
"modulos": "ESP32-MINI-1, ESP32-PICO-MINI-02, ESP32-PICO-V3, ESP32-PICO-V3-02 e mais 11",
"datasheet": "https://documentation.espressif.com/esp32_datasheet_en.html", "datasheet": "https://documentation.espressif.com/esp32_datasheet_en.html",
"manual_tecnico": "https://documentation.espressif.com/esp32_technical_reference_manual_en.html", "manual_tecnico": "https://documentation.espressif.com/esp32_technical_reference_manual_en.html",
"guia_de_programacao": "https://docs.espressif.com/projects/esp-idf/en/latest/esp32/get-started/index.html", "guia_de_programacao": "https://docs.espressif.com/projects/esp-idf/en/latest/esp32/get-started/index.html",
@@ -54,7 +72,20 @@
"nucleos": "1 (Single-core)", "nucleos": "1 (Single-core)",
"frequencia": "Até 240 MHz", "frequencia": "Até 240 MHz",
"coprocessador_ulp": "Sim (RISC-V)", "coprocessador_ulp": "Sim (RISC-V)",
"aceleradores_cripto": "Sim (AES-128/192/256 + GCM, SHA-1/224/256/384/512, RSA/MPI, HMAC, Digital Signature, RNG, XTS-AES-128/256, Secure Boot V2 (RSA-3072))", "seguranca": {
"aes": "AES-128/192/256 com GCM",
"sha": "SHA-1/224/256/384/512",
"rsa": "Sim (RSA/MPI até 4096 bits)",
"ecc": "Não",
"ecdsa": "Não",
"hmac": "Sim",
"assinatura_digital": "Sim",
"key_manager": "Não",
"rng": "Sim (TRNG por hardware)",
"criptografia_flash": "XTS-AES-128/256",
"secure_boot": "V2 (RSA-3072)",
"protecao_dpa": "Não"
},
"wifi": "IEEE 802.11 b/g/n (2.4 GHz, até 150 Mbps)", "wifi": "IEEE 802.11 b/g/n (2.4 GHz, até 150 Mbps)",
"bluetooth": "Não", "bluetooth": "Não",
"memoria_sram": "320 KB", "memoria_sram": "320 KB",
@@ -83,6 +114,11 @@
}, },
"temperatura_operacao": "-40°C a 105°C", "temperatura_operacao": "-40°C a 105°C",
"temperatura_armazenamento": "-40°C a 150°C", "temperatura_armazenamento": "-40°C a 150°C",
"depuracao": "JTAG externo (requer sonda, ex.: ESP-PROG)",
"esp_idf_minimo": "v4.2",
"arduino_core": "Sim (26 definições de placa no core oficial)",
"encapsulamento": "QFN56 (7×7 mm)",
"modulos": "ESP32-S2-MINI-1, ESP32-S2-MINI-2, ESP32-S2-SOLO, ESP32-S2-SOLO-2 e mais 4",
"datasheet": "https://documentation.espressif.com/esp32-s2_datasheet_en.html", "datasheet": "https://documentation.espressif.com/esp32-s2_datasheet_en.html",
"manual_tecnico": "https://documentation.espressif.com/esp32-s2_technical_reference_manual_en.html", "manual_tecnico": "https://documentation.espressif.com/esp32-s2_technical_reference_manual_en.html",
"guia_de_programacao": "https://docs.espressif.com/projects/esp-idf/en/latest/esp32s2/get-started/index.html", "guia_de_programacao": "https://docs.espressif.com/projects/esp-idf/en/latest/esp32s2/get-started/index.html",
@@ -98,7 +134,20 @@
"frequencia": "Até 240 MHz", "frequencia": "Até 240 MHz",
"coprocessador_ulp": "Sim (RISC-V)", "coprocessador_ulp": "Sim (RISC-V)",
"aceleradores_ia": "Sim (Vector Instructions para IA)", "aceleradores_ia": "Sim (Vector Instructions para IA)",
"aceleradores_cripto": "Sim (AES-128/256, SHA-1/224/256/384/512, RSA/MPI, HMAC, Digital Signature, RNG, XTS-AES-128/256, Secure Boot V2 (RSA-3072))", "seguranca": {
"aes": "AES-128/256",
"sha": "SHA-1/224/256/384/512",
"rsa": "Sim (RSA/MPI até 4096 bits)",
"ecc": "Não",
"ecdsa": "Não",
"hmac": "Sim",
"assinatura_digital": "Sim",
"key_manager": "Não",
"rng": "Sim (TRNG por hardware)",
"criptografia_flash": "XTS-AES-128/256",
"secure_boot": "V2 (RSA-3072)",
"protecao_dpa": "Não"
},
"wifi": "IEEE 802.11 b/g/n (2.4 GHz, até 150 Mbps)", "wifi": "IEEE 802.11 b/g/n (2.4 GHz, até 150 Mbps)",
"bluetooth": "Bluetooth 5.4 LE (certificado)", "bluetooth": "Bluetooth 5.4 LE (certificado)",
"memoria_sram": "512 KB", "memoria_sram": "512 KB",
@@ -128,6 +177,11 @@
}, },
"temperatura_operacao": "-40°C a 65/85/105°C", "temperatura_operacao": "-40°C a 65/85/105°C",
"temperatura_armazenamento": "-40°C a 150°C", "temperatura_armazenamento": "-40°C a 150°C",
"depuracao": "JTAG integrado via USB (não precisa de sonda externa)",
"esp_idf_minimo": "v4.4",
"arduino_core": "Sim (158 definições de placa no core oficial)",
"encapsulamento": "QFN56 (7×7 mm)",
"modulos": "ESP32-S3-MINI-1, ESP32-S3-WROOM-1, ESP32-S3-WROOM-2",
"datasheet": "https://documentation.espressif.com/esp32-s3_datasheet_en.html", "datasheet": "https://documentation.espressif.com/esp32-s3_datasheet_en.html",
"manual_tecnico": "https://documentation.espressif.com/esp32-s3_technical_reference_manual_en.html", "manual_tecnico": "https://documentation.espressif.com/esp32-s3_technical_reference_manual_en.html",
"guia_de_programacao": "https://docs.espressif.com/projects/esp-idf/en/latest/esp32s3/get-started/index.html", "guia_de_programacao": "https://docs.espressif.com/projects/esp-idf/en/latest/esp32s3/get-started/index.html",
@@ -143,7 +197,20 @@
"frequencia": "Até 320 MHz", "frequencia": "Até 320 MHz",
"coprocessador_ulp": "Sim (RISC-V, LP CPU de 40 MHz)", "coprocessador_ulp": "Sim (RISC-V, LP CPU de 40 MHz)",
"aceleradores_ia": "Sim (Custom AI and DSP extension XespV)", "aceleradores_ia": "Sim (Custom AI and DSP extension XespV)",
"aceleradores_cripto": "Sim (AES-128/256 + GCM, SHA-1/224/256/384/512, RSA/MPI, HMAC, Digital Signature, ECC e ECDSA (P-192/P-256/P-384), Key Manager, RNG, XTS-AES-128/256, Secure Boot V2 (RSA-3072))", "seguranca": {
"aes": "AES-128/256 com GCM",
"sha": "SHA-1/224/256/384/512",
"rsa": "Sim (RSA/MPI até 4096 bits)",
"ecc": "Sim (P-192, P-256, P-384)",
"ecdsa": "Sim (modo determinístico, RFC 6979)",
"hmac": "Sim",
"assinatura_digital": "Sim",
"key_manager": "Sim",
"rng": "Sim (TRNG por hardware)",
"criptografia_flash": "XTS-AES-128/256",
"secure_boot": "V2 (RSA-3072)",
"protecao_dpa": "Não"
},
"wifi": "Wi-Fi 6 (802.11ax) 2.4 GHz", "wifi": "Wi-Fi 6 (802.11ax) 2.4 GHz",
"bluetooth": "Bluetooth 5.4 LE e Bluetooth Classic", "bluetooth": "Bluetooth 5.4 LE e Bluetooth Classic",
"zigbee_thread": "IEEE 802.15.4 (Zigbee 3.0, Thread 1.4)", "zigbee_thread": "IEEE 802.15.4 (Zigbee 3.0, Thread 1.4)",
@@ -172,6 +239,11 @@
}, },
"temperatura_operacao": "-40°C a 85°C", "temperatura_operacao": "-40°C a 85°C",
"temperatura_armazenamento": "-40°C a 150°C", "temperatura_armazenamento": "-40°C a 150°C",
"depuracao": "JTAG integrado via USB (não precisa de sonda externa)",
"esp_idf_minimo": "Ainda sem versão estável (apenas no branch master)",
"arduino_core": "Ainda não suportado pelo core arduino-esp32",
"encapsulamento": "QFN80 (8×8 mm)",
"modulos": "ESP32-S31-WROOM-1, ESP32-S31-WROOM-3",
"datasheet": "https://documentation.espressif.com/esp32-s31_datasheet_en.html", "datasheet": "https://documentation.espressif.com/esp32-s31_datasheet_en.html",
"manual_tecnico": "https://documentation.espressif.com/esp32-s31_technical_reference_manual_en.html", "manual_tecnico": "https://documentation.espressif.com/esp32-s31_technical_reference_manual_en.html",
"guia_de_programacao": "https://docs.espressif.com/projects/esp-idf/en/latest/esp32s31/get-started/index.html", "guia_de_programacao": "https://docs.espressif.com/projects/esp-idf/en/latest/esp32s31/get-started/index.html",
@@ -185,7 +257,20 @@
"arquitetura": "RISC-V single-core de 32 bits", "arquitetura": "RISC-V single-core de 32 bits",
"nucleos": "1 (Single-core)", "nucleos": "1 (Single-core)",
"frequencia": "Até 120 MHz", "frequencia": "Até 120 MHz",
"aceleradores_cripto": "Sim (SHA-1/224/256, ECC, RNG, XTS-AES-128, Secure Boot V2 (ECDSA))", "seguranca": {
"aes": "Não",
"sha": "SHA-1/224/256",
"rsa": "Não",
"ecc": "Sim (P-192, P-256)",
"ecdsa": "Não",
"hmac": "Não",
"assinatura_digital": "Não",
"key_manager": "Não",
"rng": "Sim (TRNG por hardware)",
"criptografia_flash": "XTS-AES-128",
"secure_boot": "V2 (ECDSA)",
"protecao_dpa": "Não"
},
"wifi": "IEEE 802.11 b/g/n (2.4 GHz, até 72.2 Mbps)", "wifi": "IEEE 802.11 b/g/n (2.4 GHz, até 72.2 Mbps)",
"bluetooth": "Bluetooth 5.3 LE (certificado)", "bluetooth": "Bluetooth 5.3 LE (certificado)",
"memoria_sram": "272 KB (16 KB para cache)", "memoria_sram": "272 KB (16 KB para cache)",
@@ -210,6 +295,11 @@
}, },
"temperatura_operacao": "-40°C a 105°C", "temperatura_operacao": "-40°C a 105°C",
"temperatura_armazenamento": "-40°C a 150°C", "temperatura_armazenamento": "-40°C a 150°C",
"depuracao": "JTAG externo (requer sonda, ex.: ESP-PROG)",
"esp_idf_minimo": "v5.0",
"arduino_core": "Sim (1 definições de placa no core oficial)",
"encapsulamento": "QFN24 (4×4 mm)",
"modulos": "ESP8684-MINI-1, ESP8684-WROOM-01C, ESP8684-WROOM-02C, ESP8684-WROOM-02UC e mais 5",
"datasheet": "https://documentation.espressif.com/esp32-c2_datasheet_en.html", "datasheet": "https://documentation.espressif.com/esp32-c2_datasheet_en.html",
"manual_tecnico": "https://documentation.espressif.com/esp32-c2_technical_reference_manual_en.html", "manual_tecnico": "https://documentation.espressif.com/esp32-c2_technical_reference_manual_en.html",
"guia_de_programacao": "https://docs.espressif.com/projects/esp-idf/en/latest/esp32c2/get-started/index.html", "guia_de_programacao": "https://docs.espressif.com/projects/esp-idf/en/latest/esp32c2/get-started/index.html",
@@ -223,7 +313,20 @@
"arquitetura": "RISC-V single-core de 32 bits", "arquitetura": "RISC-V single-core de 32 bits",
"nucleos": "1 (Single-core)", "nucleos": "1 (Single-core)",
"frequencia": "Até 160 MHz", "frequencia": "Até 160 MHz",
"aceleradores_cripto": "Sim (AES-128/256, SHA-1/224/256, RSA/MPI, HMAC, Digital Signature, RNG, XTS-AES-128, Secure Boot V2 (RSA-3072))", "seguranca": {
"aes": "AES-128/256",
"sha": "SHA-1/224/256",
"rsa": "Sim (RSA/MPI até 3072 bits)",
"ecc": "Não",
"ecdsa": "Não",
"hmac": "Sim",
"assinatura_digital": "Sim",
"key_manager": "Não",
"rng": "Sim (TRNG por hardware)",
"criptografia_flash": "XTS-AES-128",
"secure_boot": "V2 (RSA-3072)",
"protecao_dpa": "Não"
},
"wifi": "IEEE 802.11 b/g/n (2.4 GHz, até 150 Mbps)", "wifi": "IEEE 802.11 b/g/n (2.4 GHz, até 150 Mbps)",
"bluetooth": "Bluetooth 5.4 LE (certificado)", "bluetooth": "Bluetooth 5.4 LE (certificado)",
"memoria_sram": "400 KB", "memoria_sram": "400 KB",
@@ -251,6 +354,11 @@
}, },
"temperatura_operacao": "-40°C a 125°C", "temperatura_operacao": "-40°C a 125°C",
"temperatura_armazenamento": "-40°C a 150°C", "temperatura_armazenamento": "-40°C a 150°C",
"depuracao": "JTAG integrado via USB (não precisa de sonda externa)",
"esp_idf_minimo": "v4.3",
"arduino_core": "Sim (29 definições de placa no core oficial)",
"encapsulamento": "QFN32 (5×5 mm) e QFN28 (4×4 mm)",
"modulos": "ESP32-C3-MINI-1, ESP32-C3-WROOM-02",
"datasheet": "https://documentation.espressif.com/esp32-c3_datasheet_en.html", "datasheet": "https://documentation.espressif.com/esp32-c3_datasheet_en.html",
"manual_tecnico": "https://documentation.espressif.com/esp32-c3_technical_reference_manual_en.html", "manual_tecnico": "https://documentation.espressif.com/esp32-c3_technical_reference_manual_en.html",
"guia_de_programacao": "https://docs.espressif.com/projects/esp-idf/en/latest/esp32c3/get-started/index.html", "guia_de_programacao": "https://docs.espressif.com/projects/esp-idf/en/latest/esp32c3/get-started/index.html",
@@ -265,7 +373,20 @@
"nucleos": "1 (Single-core HP) + 1 (Single-core LP)", "nucleos": "1 (Single-core HP) + 1 (Single-core LP)",
"frequencia": "Até 240 MHz", "frequencia": "Até 240 MHz",
"coprocessador_ulp": "Sim (RISC-V, LP CPU de 48 MHz)", "coprocessador_ulp": "Sim (RISC-V, LP CPU de 48 MHz)",
"aceleradores_cripto": "Sim (AES-128/256, SHA-1/224/256/384/512, RSA/MPI, HMAC, Digital Signature, ECC e ECDSA (P-192/P-256/P-384), Key Manager, RNG, XTS-AES-128/256, proteção contra DPA, Secure Boot V2 (RSA-3072 ou ECDSA))", "seguranca": {
"aes": "AES-128/256",
"sha": "SHA-1/224/256/384/512",
"rsa": "Sim (RSA/MPI até 3072 bits)",
"ecc": "Sim (P-192, P-256, P-384)",
"ecdsa": "Sim (modo determinístico, RFC 6979)",
"hmac": "Sim",
"assinatura_digital": "Sim",
"key_manager": "Sim",
"rng": "Sim (TRNG por hardware)",
"criptografia_flash": "XTS-AES-128/256",
"secure_boot": "V2 (RSA-3072 ou ECDSA)",
"protecao_dpa": "Sim"
},
"wifi": "Wi-Fi 6 (802.11ax) dual-band 2.4 GHz e 5 GHz", "wifi": "Wi-Fi 6 (802.11ax) dual-band 2.4 GHz e 5 GHz",
"bluetooth": "Bluetooth 6.0 LE (Core 6.0 certificado)", "bluetooth": "Bluetooth 6.0 LE (Core 6.0 certificado)",
"zigbee_thread": "IEEE 802.15.4 (Zigbee 3.0, Thread 1.4)", "zigbee_thread": "IEEE 802.15.4 (Zigbee 3.0, Thread 1.4)",
@@ -293,6 +414,11 @@
}, },
"temperatura_operacao": "-40°C a 105°C", "temperatura_operacao": "-40°C a 105°C",
"temperatura_armazenamento": "-40°C a 150°C", "temperatura_armazenamento": "-40°C a 150°C",
"depuracao": "JTAG integrado via USB (não precisa de sonda externa)",
"esp_idf_minimo": "v5.5.2",
"arduino_core": "Sim (10 definições de placa no core oficial)",
"encapsulamento": "QFN48 (6×6 mm)",
"modulos": "ESP32-C5-MINI-1, ESP32-C5-WROOM-1",
"datasheet": "https://documentation.espressif.com/esp32-c5_datasheet_en.html", "datasheet": "https://documentation.espressif.com/esp32-c5_datasheet_en.html",
"manual_tecnico": "https://documentation.espressif.com/esp32-c5_technical_reference_manual_en.html", "manual_tecnico": "https://documentation.espressif.com/esp32-c5_technical_reference_manual_en.html",
"guia_de_programacao": "https://docs.espressif.com/projects/esp-idf/en/latest/esp32c5/get-started/index.html", "guia_de_programacao": "https://docs.espressif.com/projects/esp-idf/en/latest/esp32c5/get-started/index.html",
@@ -307,7 +433,20 @@
"nucleos": "1 (Single-core HP) + 1 (Single-core LP)", "nucleos": "1 (Single-core HP) + 1 (Single-core LP)",
"frequencia": "Até 160 MHz", "frequencia": "Até 160 MHz",
"coprocessador_ulp": "Sim (RISC-V, LP CPU de 20 MHz)", "coprocessador_ulp": "Sim (RISC-V, LP CPU de 20 MHz)",
"aceleradores_cripto": "Sim (AES-128/256, SHA-1/224/256, RSA/MPI, HMAC, Digital Signature, ECC, RNG, XTS-AES-128, proteção contra DPA, Secure Boot V2 (RSA-3072 ou ECDSA))", "seguranca": {
"aes": "AES-128/256",
"sha": "SHA-1/224/256",
"rsa": "Sim (RSA/MPI até 3072 bits)",
"ecc": "Sim (P-192, P-256)",
"ecdsa": "Não",
"hmac": "Sim",
"assinatura_digital": "Sim",
"key_manager": "Não",
"rng": "Sim (TRNG por hardware)",
"criptografia_flash": "XTS-AES-128",
"secure_boot": "V2 (RSA-3072 ou ECDSA)",
"protecao_dpa": "Sim"
},
"wifi": "Wi-Fi 6 (802.11ax) 2.4 GHz", "wifi": "Wi-Fi 6 (802.11ax) 2.4 GHz",
"bluetooth": "Bluetooth 6.0 LE (Core 6.0 certificado)", "bluetooth": "Bluetooth 6.0 LE (Core 6.0 certificado)",
"zigbee_thread": "IEEE 802.15.4 (Zigbee 3.0, Thread 1.3)", "zigbee_thread": "IEEE 802.15.4 (Zigbee 3.0, Thread 1.3)",
@@ -336,6 +475,11 @@
}, },
"temperatura_operacao": "-40°C a 105°C", "temperatura_operacao": "-40°C a 105°C",
"temperatura_armazenamento": "-40°C a 150°C", "temperatura_armazenamento": "-40°C a 150°C",
"depuracao": "JTAG integrado via USB (não precisa de sonda externa)",
"esp_idf_minimo": "v5.1",
"arduino_core": "Sim (21 definições de placa no core oficial)",
"encapsulamento": "QFN40 (5×5 mm) e QFN32 (5×5 mm)",
"modulos": "ESP32-C6-MINI-1, ESP32-C6-WROOM-1",
"datasheet": "https://documentation.espressif.com/esp32-c6_datasheet_en.html", "datasheet": "https://documentation.espressif.com/esp32-c6_datasheet_en.html",
"manual_tecnico": "https://documentation.espressif.com/esp32-c6_technical_reference_manual_en.html", "manual_tecnico": "https://documentation.espressif.com/esp32-c6_technical_reference_manual_en.html",
"guia_de_programacao": "https://docs.espressif.com/projects/esp-idf/en/latest/esp32c6/get-started/index.html", "guia_de_programacao": "https://docs.espressif.com/projects/esp-idf/en/latest/esp32c6/get-started/index.html",
@@ -349,7 +493,20 @@
"arquitetura": "RISC-V single-core de 32 bits", "arquitetura": "RISC-V single-core de 32 bits",
"nucleos": "1 (Single-core)", "nucleos": "1 (Single-core)",
"frequencia": "Até 160 MHz", "frequencia": "Até 160 MHz",
"aceleradores_cripto": "Sim (SHA-1/224/256, ECC e ECDSA, RNG, XTS-AES-128, proteção contra DPA, Secure Boot V2 (ECDSA))", "seguranca": {
"aes": "Não",
"sha": "SHA-1/224/256",
"rsa": "Não",
"ecc": "Sim (P-192, P-256)",
"ecdsa": "Sim (modo determinístico, RFC 6979)",
"hmac": "Não",
"assinatura_digital": "Não",
"key_manager": "Não",
"rng": "Sim (TRNG por hardware)",
"criptografia_flash": "XTS-AES-128",
"secure_boot": "V2 (ECDSA)",
"protecao_dpa": "Sim"
},
"wifi": "Wi-Fi 6 (802.11ax) 2.4 GHz", "wifi": "Wi-Fi 6 (802.11ax) 2.4 GHz",
"bluetooth": "Bluetooth 6.0 LE (Core 6.0 certificado)", "bluetooth": "Bluetooth 6.0 LE (Core 6.0 certificado)",
"zigbee_thread": "Não", "zigbee_thread": "Não",
@@ -378,6 +535,11 @@
}, },
"temperatura_operacao": "-40°C a 105°C", "temperatura_operacao": "-40°C a 105°C",
"temperatura_armazenamento": "-40°C a 150°C", "temperatura_armazenamento": "-40°C a 150°C",
"depuracao": "JTAG integrado via USB (não precisa de sonda externa)",
"esp_idf_minimo": "v5.5.2",
"arduino_core": "Sim (1 definições de placa no core oficial)",
"encapsulamento": "QFN40 (5×5 mm)",
"modulos": "ESP32-C61-MINI-1, ESP32-C61-WROOM-1",
"datasheet": "https://documentation.espressif.com/esp32-c61_datasheet_en.html", "datasheet": "https://documentation.espressif.com/esp32-c61_datasheet_en.html",
"manual_tecnico": "https://documentation.espressif.com/esp32-c61_technical_reference_manual_en.html", "manual_tecnico": "https://documentation.espressif.com/esp32-c61_technical_reference_manual_en.html",
"guia_de_programacao": "https://docs.espressif.com/projects/esp-idf/en/latest/esp32c61/get-started/index.html", "guia_de_programacao": "https://docs.espressif.com/projects/esp-idf/en/latest/esp32c61/get-started/index.html",
@@ -392,7 +554,20 @@
"nucleos": "2 (Dual-core HP) + 1 (Single-core LP)", "nucleos": "2 (Dual-core HP) + 1 (Single-core LP)",
"frequencia": "Até 400 MHz", "frequencia": "Até 400 MHz",
"aceleradores_ia": "Sim (Vector Instructions, aceleradores de IA)", "aceleradores_ia": "Sim (Vector Instructions, aceleradores de IA)",
"aceleradores_cripto": "Sim (AES-128/256 + GCM, SHA-1/224/256/384/512, RSA/MPI, HMAC, Digital Signature, ECC e ECDSA (P-192/P-256/P-384), Key Manager, RNG, XTS-AES-128/256, Secure Boot V2 (RSA-3072 ou ECDSA))", "seguranca": {
"aes": "AES-128/256 com GCM",
"sha": "SHA-1/224/256/384/512",
"rsa": "Sim (RSA/MPI até 4096 bits)",
"ecc": "Sim (P-192, P-256, P-384)",
"ecdsa": "Sim (modo determinístico, RFC 6979)",
"hmac": "Sim",
"assinatura_digital": "Sim",
"key_manager": "Sim",
"rng": "Sim (TRNG por hardware)",
"criptografia_flash": "XTS-AES-128/256",
"secure_boot": "V2 (RSA-3072 ou ECDSA)",
"protecao_dpa": "Não"
},
"wifi": "Não", "wifi": "Não",
"bluetooth": "Não", "bluetooth": "Não",
"ethernet": "Sim (Ethernet MAC integrado)", "ethernet": "Sim (Ethernet MAC integrado)",
@@ -424,6 +599,11 @@
}, },
"temperatura_operacao": "-40°C a 85°C", "temperatura_operacao": "-40°C a 85°C",
"temperatura_armazenamento": "-40°C a 150°C", "temperatura_armazenamento": "-40°C a 150°C",
"depuracao": "JTAG integrado via USB (não precisa de sonda externa)",
"esp_idf_minimo": "v5.3",
"arduino_core": "Sim (7 definições de placa no core oficial)",
"encapsulamento": "QFN104 (10×10 mm)",
"modulos": "Sem módulo no catálogo oficial (apenas o chip nu)",
"datasheet": "https://documentation.espressif.com/esp32-p4-chip-revision-v1.3_datasheet_en.html", "datasheet": "https://documentation.espressif.com/esp32-p4-chip-revision-v1.3_datasheet_en.html",
"manual_tecnico": "https://documentation.espressif.com/esp32-p4-chip-revision-v1.3_technical_reference_manual_en.html", "manual_tecnico": "https://documentation.espressif.com/esp32-p4-chip-revision-v1.3_technical_reference_manual_en.html",
"guia_de_programacao": "https://docs.espressif.com/projects/esp-idf/en/latest/esp32p4/get-started/index.html", "guia_de_programacao": "https://docs.espressif.com/projects/esp-idf/en/latest/esp32p4/get-started/index.html",
@@ -437,7 +617,20 @@
"arquitetura": "RISC-V single-core de 32 bits", "arquitetura": "RISC-V single-core de 32 bits",
"nucleos": "1 (Single-core)", "nucleos": "1 (Single-core)",
"frequencia": "Até 96 MHz", "frequencia": "Até 96 MHz",
"aceleradores_cripto": "Sim (AES-128/256, SHA-1/224/256, RSA/MPI, HMAC, Digital Signature, ECC e ECDSA, RNG, XTS-AES-128, proteção contra DPA, Secure Boot V2 (RSA-3072 ou ECDSA))", "seguranca": {
"aes": "AES-128/256",
"sha": "SHA-1/224/256",
"rsa": "Sim (RSA/MPI até 3072 bits)",
"ecc": "Sim (P-192, P-256)",
"ecdsa": "Sim (modo determinístico, RFC 6979)",
"hmac": "Sim",
"assinatura_digital": "Sim",
"key_manager": "Não",
"rng": "Sim (TRNG por hardware)",
"criptografia_flash": "XTS-AES-128",
"secure_boot": "V2 (RSA-3072 ou ECDSA)",
"protecao_dpa": "Sim"
},
"wifi": "Não", "wifi": "Não",
"bluetooth": "Bluetooth 6.0 LE (Core 6.0 certificado)", "bluetooth": "Bluetooth 6.0 LE (Core 6.0 certificado)",
"zigbee_thread": "IEEE 802.15.4 (Zigbee 3.0, Thread 1.4)", "zigbee_thread": "IEEE 802.15.4 (Zigbee 3.0, Thread 1.4)",
@@ -465,6 +658,11 @@
}, },
"temperatura_operacao": "-40°C a 105°C", "temperatura_operacao": "-40°C a 105°C",
"temperatura_armazenamento": "-40°C a 150°C", "temperatura_armazenamento": "-40°C a 150°C",
"depuracao": "JTAG integrado via USB (não precisa de sonda externa)",
"esp_idf_minimo": "v5.1",
"arduino_core": "Sim (3 definições de placa no core oficial)",
"encapsulamento": "QFN32 (4×4 mm)",
"modulos": "ESP32-H2-MINI-1, ESP32-H2-WROOM-02C",
"datasheet": "https://documentation.espressif.com/esp32-h2_datasheet_en.html", "datasheet": "https://documentation.espressif.com/esp32-h2_datasheet_en.html",
"manual_tecnico": "https://documentation.espressif.com/esp32-h2_technical_reference_manual_en.html", "manual_tecnico": "https://documentation.espressif.com/esp32-h2_technical_reference_manual_en.html",
"guia_de_programacao": "https://docs.espressif.com/projects/esp-idf/en/latest/esp32h2/get-started/index.html", "guia_de_programacao": "https://docs.espressif.com/projects/esp-idf/en/latest/esp32h2/get-started/index.html",
@@ -479,7 +677,20 @@
"arquitetura": "RISC-V dual-core de 32 bits", "arquitetura": "RISC-V dual-core de 32 bits",
"nucleos": "2 (Dual-core)", "nucleos": "2 (Dual-core)",
"frequencia": "Até 96 MHz", "frequencia": "Até 96 MHz",
"aceleradores_cripto": "Sim (AES-128/256, SHA-1/224/256/384/512, HMAC, ECC e ECDSA determinístico (P-192/P-256/P-384), RNG, XTS-AES-128/256)", "seguranca": {
"aes": "AES-128/256",
"sha": "SHA-1/224/256/384/512",
"rsa": "Não",
"ecc": "Sim (P-192, P-256, P-384)",
"ecdsa": "Sim (modo determinístico, RFC 6979)",
"hmac": "Sim",
"assinatura_digital": "Não",
"key_manager": "Não",
"rng": "Sim (TRNG por hardware)",
"criptografia_flash": "XTS-AES-128/256",
"secure_boot": "Ainda não habilitado no ESP-IDF (documentação preliminar)",
"protecao_dpa": "Não"
},
"wifi": "Não", "wifi": "Não",
"bluetooth": "Bluetooth 5.4 LE (LE Audio, canais isócronos BIS/CIS, PAwR, Connection Subrating e Direction Finding AoA/AoD)", "bluetooth": "Bluetooth 5.4 LE (LE Audio, canais isócronos BIS/CIS, PAwR, Connection Subrating e Direction Finding AoA/AoD)",
"zigbee_thread": "IEEE 802.15.4 (Zigbee 3.0, Thread 1.4)", "zigbee_thread": "IEEE 802.15.4 (Zigbee 3.0, Thread 1.4)",
@@ -506,6 +717,11 @@
}, },
"temperatura_operacao": "Não divulgado (documentação preliminar)", "temperatura_operacao": "Não divulgado (documentação preliminar)",
"temperatura_armazenamento": "Não divulgado (documentação preliminar)", "temperatura_armazenamento": "Não divulgado (documentação preliminar)",
"depuracao": "JTAG integrado via USB (não precisa de sonda externa)",
"esp_idf_minimo": "Ainda sem versão estável (apenas no branch master)",
"arduino_core": "Ainda não suportado pelo core arduino-esp32",
"encapsulamento": "Não divulgado (documentação preliminar)",
"modulos": "ESP32-H4-WROOM-1",
"guia_de_programacao": "https://docs.espressif.com/projects/esp-idf/en/latest/esp32h4/get-started/index.html", "guia_de_programacao": "https://docs.espressif.com/projects/esp-idf/en/latest/esp32h4/get-started/index.html",
"cor": "#e83e8c" "cor": "#e83e8c"
} }
+179
View File
@@ -0,0 +1,179 @@
#!/usr/bin/env node
/**
* Auditor de consistência entre os temas claro e escuro.
*
* Verifica quatro coisas nos arquivos .jsx do projeto:
*
* 1. TOM INEXISTENTE — classe usando um valor fora da escala declarada.
* Não gera CSS: o elemento herda a cor do pai.
* 2. PAR INVERTIDO — no tema escuro o texto deveria clarear (número menor)
* e o fundo/borda deveria escurecer (número maior).
* 3. CONTRASTE BAIXO — par abaixo de 4.5:1 (AA para texto).
* 4. COR FIXA INLINE — style={{ borderColor: 'rgba(...)' }} com valor literal.
* Style inline vence classe, então anula a cor do tema
* (inclusive as variantes dark: e hover:). Cor vinda do
* dado (serie.cor) é permitida — ver DESIGN.md seção 5.
*
* Uso: npm run auditar-temas
* Sai com código 1 se encontrar problema. Ver DESIGN.md.
*/
import { readFileSync, readdirSync, statSync } from "node:fs";
import { join } from "node:path";
// Escala slate: oficial do Tailwind + os meios-tons declarados em globals.css
const ESCALA = {
"50": "#f8fafc", "100": "#f1f5f9", "150": "#eaeef4", "200": "#e2e8f0",
"250": "#d6dee8", "300": "#cbd5e1", "350": "#b0bccc", "400": "#94a3b8",
"450": "#7c8ca2", "500": "#64748b", "550": "#56647a", "600": "#475569",
"650": "#3d4b5f", "700": "#334155", "750": "#283548", "800": "#1e293b",
"850": "#162032", "900": "#0f172a", "950": "#020617",
};
const FUNDO_CLARO = "#ffffff";
const FUNDO_ESCURO = "#0f172a"; // slate-900, superfície de card no tema escuro
const AA = 4.5;
function luminancia(hexa) {
const limpo = hexa.replace("#", "");
const canal = (c) => {
c /= 255;
return c <= 0.03928 ? c / 12.92 : ((c + 0.055) / 1.055) ** 2.4;
};
const [r, g, b] = [0, 2, 4].map((i) => canal(parseInt(limpo.slice(i, i + 2), 16)));
return 0.2126 * r + 0.7152 * g + 0.0722 * b;
}
function contraste(a, b) {
const [maior, menor] = [luminancia(a), luminancia(b)].sort((x, y) => y - x);
return (maior + 0.05) / (menor + 0.05);
}
/** Caminha app/ e components/ recursivamente atrás de .jsx. */
function arquivos() {
const achados = [];
const anda = (dir) => {
let entradas;
try {
entradas = readdirSync(dir);
} catch {
return;
}
for (const nome of entradas) {
const caminho = join(dir, nome);
if (statSync(caminho).isDirectory()) anda(caminho);
else if (nome.endsWith(".jsx")) achados.push(caminho);
}
};
anda("app");
anda("components");
return achados.sort();
}
const RX_QUALQUER = /\b(?:text|bg|border|divide|ring|from|via|to)-slate-(\d+)\b/g;
const RX_TEXTO = /\btext-slate-(\d+) dark:text-slate-(\d+)\b/g;
const RX_SUPERFICIE = /\b(bg|border)-slate-(\d+)(?:\/\d+)? dark:\1-slate-(\d+)(?:\/\d+)?\b/g;
// texto que acompanha um componente invertido: claro -> escuro (sem /g: usado com .test)
const RX_TEXTO_INVERTIDO = /\btext-white dark:text-slate-(?:[5-9]\d0|950)\b/;
// Blocos style={{ ... }} — só dentro deles uma cor literal é problema.
// Fora disso, `color: "#6366f1"` costuma ser tabela de dados (categorias de pino).
const RX_BLOCO_STYLE = /style=\{\{([\s\S]*?)\}\}/g;
// Cor literal: aspas seguidas de #, rgb(), hsl() ou nome de cor.
const RX_COR_FIXA = /\b(borderColor|backgroundColor|color)\s*:\s*['"](?:#|rgb|hsl|transparent|white|black)/g;
/** Cor literal dentro de style={{}}: vence a classe e mata a variante dark:. */
function coresFixasInline(arq) {
const texto = readFileSync(arq, "utf8");
const achados = [];
for (const bloco of texto.matchAll(RX_BLOCO_STYLE)) {
for (const m of bloco[1].matchAll(RX_COR_FIXA)) {
const posicao = bloco.index + m.index;
const linha = texto.slice(0, posicao).split("\n").length;
achados.push([arq, linha, m[1], "cor literal inline anula a classe de tema"]);
}
}
return achados;
}
function main() {
const inexistentes = new Map();
const invertidos = [];
const fracos = [];
const fixas = [];
for (const arq of arquivos()) {
fixas.push(...coresFixasInline(arq));
const linhas = readFileSync(arq, "utf8").split("\n");
linhas.forEach((linha, i) => {
const n = i + 1;
for (const m of linha.matchAll(RX_QUALQUER)) {
if (!(m[1] in ESCALA)) {
const chave = `${arq}:${n} ${m[0]}`;
inexistentes.set(chave, (inexistentes.get(chave) ?? 0) + 1);
}
}
for (const m of linha.matchAll(RX_TEXTO)) {
const [, claro, escuro] = m;
if (!(claro in ESCALA) || !(escuro in ESCALA)) continue;
if (Number(escuro) > Number(claro)) {
invertidos.push([arq, n, m[0], "texto escurece no tema escuro"]);
}
const c1 = contraste(ESCALA[claro], FUNDO_CLARO);
const c2 = contraste(ESCALA[escuro], FUNDO_ESCURO);
if (c1 < AA || c2 < AA) {
fracos.push([arq, n, m[0], +c1.toFixed(2), +c2.toFixed(2)]);
}
}
for (const m of linha.matchAll(RX_SUPERFICIE)) {
const [, , claro, escuro] = m;
if (!(claro in ESCALA) || !(escuro in ESCALA)) continue;
if (Number(escuro) > Number(claro)) continue;
// Inversão deliberada (DESIGN.md seção 4): superfície escura nos DOIS
// temas — tooltip, bloco de código, botão escuro secundário.
if (Number(claro) >= 800 && Number(escuro) >= 800) continue;
// Botão que inverte preto<->branco NÃO é exceção: é bug.
// Ver DESIGN.md 4.1 — botão carrega a própria cor.
const textoInverte =
RX_TEXTO_INVERTIDO.test(linha) ||
[...linha.matchAll(RX_TEXTO)].some((t) => Number(t[2]) > Number(t[1]));
if (textoInverte) {
invertidos.push([arq, n, m[0],
"botão invertendo preto/branco — use cor própria (DESIGN.md 4.1)"]);
continue;
}
invertidos.push([arq, n, m[0], "fundo/borda não escurece no tema escuro"]);
}
});
}
const secao = (titulo, itens) => {
console.log(`\n${titulo}: ${itens.length}`);
for (const i of itens) console.log(" ", ...i);
};
if (inexistentes.size) {
const total = [...inexistentes.values()].reduce((a, b) => a + b, 0);
console.log(`\nTONS FORA DA ESCALA: ${total}`);
console.log(" (não geram CSS — declare em globals.css ou troque por um tom válido)");
for (const [k, v] of [...inexistentes].sort((a, b) => b[1] - a[1])) {
console.log(` ${v}x ${k}`);
}
}
secao("PARES INVERTIDOS", invertidos);
secao(`PARES ABAIXO DE AA (${AA}:1)`, fracos);
secao("COR FIXA EM STYLE INLINE", fixas);
const total =
[...inexistentes.values()].reduce((a, b) => a + b, 0) +
invertidos.length + fracos.length + fixas.length;
console.log(`\n${total === 0 ? "OK — nenhum problema." : `${total} problema(s).`}`);
console.log("Exceção legítima: painel escuro nos DOIS temas (tooltip, bloco de código).");
console.log("Nesses casos não use variante dark:. Ver DESIGN.md seção 4.");
return total ? 1 : 0;
}
process.exitCode = main();