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:
1 parent
d4872fcf07
commit
8a52e79d21
23 files changed
+1252
-281
No files matched your search
@@ -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
@@ -149,13 +149,13 @@ export default function PlacasPage() {
|
||||
|
||||
if (loading) {
|
||||
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 />
|
||||
|
||||
<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">
|
||||
<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>
|
||||
</main>
|
||||
|
||||
@@ -166,20 +166,20 @@ export default function PlacasPage() {
|
||||
|
||||
if (error) {
|
||||
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 />
|
||||
|
||||
<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">
|
||||
<AlertTriangle className="w-6 h-6" />
|
||||
</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-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
|
||||
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" />
|
||||
<span>Tentar Novamente</span>
|
||||
@@ -195,13 +195,13 @@ export default function PlacasPage() {
|
||||
const filterableColumns = headers.filter(h => !excludedColumns.includes(h));
|
||||
|
||||
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 />
|
||||
|
||||
<main id="conteudo" className="max-w-7xl mx-auto px-6 pt-16 pb-24">
|
||||
{/* Top Hero Heading Block */}
|
||||
<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" />
|
||||
<span className="text-xs font-bold text-slate-700 dark:text-slate-350 uppercase tracking-wider">Encontre sua placa ideal</span>
|
||||
</div>
|
||||
@@ -225,7 +225,7 @@ export default function PlacasPage() {
|
||||
<div className="grid lg:grid-cols-4 gap-8">
|
||||
{/* Left Filters Sidebar */}
|
||||
<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 */}
|
||||
<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">
|
||||
{/* Text search by name */}
|
||||
<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
|
||||
</label>
|
||||
<div className="relative">
|
||||
@@ -266,13 +266,13 @@ export default function PlacasPage() {
|
||||
placeholder="Digite o nome da placa..."
|
||||
value={searchTerm}
|
||||
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 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 */}
|
||||
<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;
|
||||
|
||||
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 ? (
|
||||
// Binary filter (checkbox inside title wrapper)
|
||||
<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 */}
|
||||
<div className="lg:col-span-3">
|
||||
<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)
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{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">
|
||||
<Search className="w-12 h-12 text-slate-300 dark:text-slate-700 mx-auto mb-4" />
|
||||
<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-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-xs text-slate-500 dark:text-slate-400">Tente ajustar ou redefinir seus filtros laterais para expandir a busca.</p>
|
||||
</div>
|
||||
|
||||
+46
-20
@@ -22,7 +22,8 @@ import {
|
||||
Activity,
|
||||
HelpCircle,
|
||||
Thermometer,
|
||||
ShieldCheck
|
||||
ShieldCheck,
|
||||
Package
|
||||
} from "lucide-react";
|
||||
|
||||
// Category to Icon mapping
|
||||
@@ -34,10 +35,16 @@ const categoryIcons = {
|
||||
"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" />,
|
||||
"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" />,
|
||||
"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() {
|
||||
const [selectedSeries, setSelectedSeries] = useState(["ESP32", "ESP32-S3"]);
|
||||
const [collapsedCategories, setCollapsedCategories] = useState({
|
||||
@@ -71,6 +78,13 @@ export default function Comparacao() {
|
||||
{ key: "nome_completo", label: "Nome Completo" },
|
||||
{ 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: [
|
||||
{ key: "arquitetura", label: "Arquitetura" },
|
||||
{ key: "nucleos", label: "Núcleos" },
|
||||
@@ -101,6 +115,7 @@ export default function Comparacao() {
|
||||
{ key: "pwm", label: "PWM" },
|
||||
{ key: "can", label: "CAN" },
|
||||
{ key: "usb", label: "USB" },
|
||||
{ key: "depuracao", label: "Depuração / JTAG" },
|
||||
{ key: "sdio", label: "SDIO" },
|
||||
]},
|
||||
{ category: "Periféricos Analógicos", fields: [
|
||||
@@ -111,7 +126,18 @@ export default function Comparacao() {
|
||||
{ key: "sensor_temperatura", label: "Sensor de Temperatura" },
|
||||
]},
|
||||
{ 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: [
|
||||
{ key: "lcd", label: "LCD" },
|
||||
@@ -128,7 +154,7 @@ export default function Comparacao() {
|
||||
|
||||
const renderValue = (value) => {
|
||||
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") {
|
||||
return (
|
||||
@@ -141,7 +167,7 @@ export default function Comparacao() {
|
||||
};
|
||||
|
||||
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 />
|
||||
|
||||
<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 */}
|
||||
<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>
|
||||
<h2 className="text-lg font-bold text-slate-800 dark:text-slate-100 mb-1">
|
||||
Escolha as Séries
|
||||
</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.
|
||||
</p>
|
||||
</div>
|
||||
@@ -189,12 +215,12 @@ export default function Comparacao() {
|
||||
key={key}
|
||||
onClick={() => toggleSeries(key)}
|
||||
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 ${
|
||||
isSelected
|
||||
? '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>
|
||||
@@ -214,7 +240,7 @@ export default function Comparacao() {
|
||||
</section>
|
||||
|
||||
{/* 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">
|
||||
<table className="w-full border-collapse">
|
||||
<thead>
|
||||
@@ -267,14 +293,14 @@ export default function Comparacao() {
|
||||
{!isCollapsed && fields.map(({ key, label }) => (
|
||||
<tr
|
||||
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">
|
||||
{label}
|
||||
</td>
|
||||
{selectedSeries.map(seriesKey => {
|
||||
const seriesItem = seriesData[seriesKey];
|
||||
const value = seriesItem[key];
|
||||
const value = valorDe(seriesItem, key);
|
||||
return (
|
||||
<td key={seriesKey} className="px-6 py-4 text-center text-xs">
|
||||
{renderValue(value)}
|
||||
@@ -292,26 +318,26 @@ export default function Comparacao() {
|
||||
</div>
|
||||
|
||||
{/* 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="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="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" />
|
||||
<span>Hardware Adicional</span>
|
||||
</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
|
||||
</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.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<Link
|
||||
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>
|
||||
<ArrowRight className="w-4 h-4" />
|
||||
@@ -330,7 +356,7 @@ export default function Comparacao() {
|
||||
return (
|
||||
<div
|
||||
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="text-4xl select-none">{seriesItem.icone}</div>
|
||||
@@ -340,7 +366,7 @@ export default function Comparacao() {
|
||||
{seriesKey}
|
||||
</h4>
|
||||
</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>
|
||||
|
||||
@@ -349,13 +375,13 @@ export default function Comparacao() {
|
||||
href={seriesItem.datasheet}
|
||||
target="_blank"
|
||||
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>
|
||||
<ExternalLink className="w-3.5 h-3.5" />
|
||||
</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>
|
||||
)}
|
||||
|
||||
@@ -31,7 +31,7 @@ export default async function FrameworkDetail({ params }) {
|
||||
const useCases = framework.casos_uso || [];
|
||||
|
||||
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 />
|
||||
|
||||
<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 */}
|
||||
<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` }}
|
||||
>
|
||||
<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">
|
||||
{framework.nome}
|
||||
</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
|
||||
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` }}
|
||||
@@ -74,7 +74,7 @@ export default async function FrameworkDetail({ params }) {
|
||||
href={framework.documentacao}
|
||||
target="_blank"
|
||||
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" />
|
||||
<span>Documentação Oficial</span>
|
||||
@@ -104,10 +104,10 @@ export default async function FrameworkDetail({ params }) {
|
||||
{/* Framework Core Specifications Grid */}
|
||||
<div className="grid md:grid-cols-3 gap-6 mb-8 select-none animate-fadeIn">
|
||||
<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 }}
|
||||
>
|
||||
<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" />
|
||||
<span>LINGUAGEM DE DESENVOLVIMENTO</span>
|
||||
</h3>
|
||||
@@ -116,10 +116,10 @@ export default async function FrameworkDetail({ params }) {
|
||||
</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 }}
|
||||
>
|
||||
<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" />
|
||||
<span>CATEGORIA DO FRAMEWORK</span>
|
||||
</h3>
|
||||
@@ -128,10 +128,10 @@ export default async function FrameworkDetail({ params }) {
|
||||
</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 }}
|
||||
>
|
||||
<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" />
|
||||
<span>OBJETIVO PRINCIPAL</span>
|
||||
</h3>
|
||||
@@ -142,7 +142,7 @@ export default async function FrameworkDetail({ params }) {
|
||||
{/* side-by-side details block */}
|
||||
<div className="grid lg:grid-cols-3 gap-6 mb-8 items-start">
|
||||
{/* 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>
|
||||
<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"
|
||||
@@ -183,7 +183,7 @@ export default async function FrameworkDetail({ params }) {
|
||||
</div>
|
||||
|
||||
{/* 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
|
||||
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` }}
|
||||
@@ -193,20 +193,20 @@ export default async function FrameworkDetail({ params }) {
|
||||
</h2>
|
||||
|
||||
<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">
|
||||
<span className="text-slate-400 dark:text-slate-500 uppercase tracking-wider text-[9px]">Nome Oficial</span>
|
||||
<div className="flex justify-between items-center py-2 border-b border-slate-200 dark:border-slate-850/45">
|
||||
<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>
|
||||
</div>
|
||||
<div className="flex justify-between items-center py-2 border-b border-slate-100 dark:border-slate-850/45">
|
||||
<span className="text-slate-400 dark:text-slate-500 uppercase tracking-wider text-[9px]">Linguagem</span>
|
||||
<div className="flex justify-between items-center py-2 border-b border-slate-200 dark:border-slate-850/45">
|
||||
<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>
|
||||
</div>
|
||||
<div className="flex justify-between items-center py-2 border-b border-slate-100 dark:border-slate-850/45">
|
||||
<span className="text-slate-400 dark:text-slate-500 uppercase tracking-wider text-[9px]">Tipo / Suite</span>
|
||||
<div className="flex justify-between items-center py-2 border-b border-slate-200 dark:border-slate-850/45">
|
||||
<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>
|
||||
</div>
|
||||
<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">
|
||||
<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 }} />
|
||||
@@ -230,7 +230,7 @@ export default async function FrameworkDetail({ params }) {
|
||||
href={framework.documentacao}
|
||||
target="_blank"
|
||||
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" />
|
||||
<span>Documentação do Desenvolvedor</span>
|
||||
|
||||
+18
-18
@@ -23,13 +23,13 @@ export default function Frameworks() {
|
||||
const frameworks = Object.entries(frameworksData);
|
||||
|
||||
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 />
|
||||
|
||||
<main id="conteudo" className="px-6 pt-16 pb-24 max-w-7xl mx-auto">
|
||||
{/* Top Hero Section */}
|
||||
<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" />
|
||||
<span className="text-xs font-bold text-slate-700 dark:text-slate-350 uppercase tracking-wider">Ferramentas de Desenvolvimento</span>
|
||||
</div>
|
||||
@@ -53,11 +53,11 @@ export default function Frameworks() {
|
||||
className="group flex"
|
||||
>
|
||||
<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}` }}
|
||||
>
|
||||
|
||||
<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">
|
||||
<span className="text-5xl select-none filter drop-shadow-md">{framework.icone}</span>
|
||||
<div className="flex flex-col gap-1 items-end select-none">
|
||||
@@ -78,7 +78,7 @@ export default function Frameworks() {
|
||||
{framework.nome}
|
||||
</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}
|
||||
</p>
|
||||
|
||||
@@ -91,7 +91,7 @@ export default function Frameworks() {
|
||||
<div className="space-y-4">
|
||||
{/* Main Feature */}
|
||||
<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" />
|
||||
<span>Objetivo Principal</span>
|
||||
</h4>
|
||||
@@ -102,7 +102,7 @@ export default function Frameworks() {
|
||||
|
||||
{/* Key Specs */}
|
||||
<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" />
|
||||
<span>Recursos Chave</span>
|
||||
</h4>
|
||||
@@ -118,13 +118,13 @@ export default function Frameworks() {
|
||||
|
||||
{/* Use Cases tags */}
|
||||
<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" />
|
||||
<span>Aplicações Ideais</span>
|
||||
</h4>
|
||||
<div className="flex flex-wrap gap-1.5">
|
||||
{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}
|
||||
</span>
|
||||
))}
|
||||
@@ -133,7 +133,7 @@ export default function Frameworks() {
|
||||
</div>
|
||||
|
||||
{/* 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>
|
||||
<ArrowRight className="w-4 h-4 transform group-hover:translate-x-1.5 transition-transform" />
|
||||
</div>
|
||||
@@ -146,7 +146,7 @@ export default function Frameworks() {
|
||||
{/* Bottom Informative Cards Section */}
|
||||
<div className="mt-16 grid md:grid-cols-2 gap-8 select-none">
|
||||
{/* 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 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">
|
||||
@@ -169,7 +169,7 @@ export default function Frameworks() {
|
||||
</p>
|
||||
</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">
|
||||
<span>Arduino</span>
|
||||
<ArrowRight className="w-3.5 h-3.5" />
|
||||
@@ -186,7 +186,7 @@ export default function Frameworks() {
|
||||
</div>
|
||||
|
||||
{/* 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 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">
|
||||
@@ -210,20 +210,20 @@ export default function Frameworks() {
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-wrap gap-2.5 pt-4 border-t border-slate-100 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">
|
||||
<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-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" />
|
||||
<span>Áudio</span>
|
||||
</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" />
|
||||
<span>Visão</span>
|
||||
</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" />
|
||||
<span>Inteligência Artificial</span>
|
||||
</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" />
|
||||
<span>Matter</span>
|
||||
</Link>
|
||||
|
||||
+31
-3
@@ -8,6 +8,26 @@
|
||||
--color-brand-purple: #8b5cf6;
|
||||
--color-brand-blue: #3b82f6;
|
||||
--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 */
|
||||
@@ -22,13 +42,21 @@
|
||||
@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;
|
||||
}
|
||||
|
||||
/* 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 {
|
||||
@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 */
|
||||
|
||||
+38
-3
@@ -41,8 +41,43 @@ export default function Glossario() {
|
||||
},
|
||||
{
|
||||
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.",
|
||||
exemplo: "ESP32-C5 e ESP32-P4 incluem AES, SHA, RSA, ECC/ECDSA e Key Manager para provisionamento seguro de chaves.",
|
||||
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-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">
|
||||
{/* Hero Section */}
|
||||
<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-sm font-semibold text-gray-700">Aprenda os Conceitos</span>
|
||||
</div>
|
||||
|
||||
+1
-1
@@ -23,7 +23,7 @@ export const metadata = {
|
||||
export default function RootLayout({ children }) {
|
||||
return (
|
||||
<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>
|
||||
{children}
|
||||
</Providers>
|
||||
|
||||
+7
-7
@@ -85,7 +85,7 @@ export default function Home() {
|
||||
];
|
||||
|
||||
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 />
|
||||
|
||||
<main className="px-6 pt-16 pb-24 max-w-7xl mx-auto">
|
||||
@@ -115,7 +115,7 @@ export default function Home() {
|
||||
</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"
|
||||
>
|
||||
<Cpu className="w-4 h-4" />
|
||||
@@ -123,7 +123,7 @@ export default function Home() {
|
||||
</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"
|
||||
>
|
||||
<Terminal className="w-4 h-4" />
|
||||
@@ -131,7 +131,7 @@ export default function Home() {
|
||||
</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"
|
||||
>
|
||||
<ShoppingBag className="w-4 h-4" />
|
||||
@@ -155,7 +155,7 @@ export default function Home() {
|
||||
{resources.map((resource) => (
|
||||
<div
|
||||
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={`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 */}
|
||||
<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 */}
|
||||
<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>
|
||||
|
||||
{/* 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="flex gap-4">
|
||||
<Info className="w-5 h-5 text-purple-600 dark:text-purple-400 mt-0.5 shrink-0" />
|
||||
|
||||
+90
-56
@@ -33,7 +33,9 @@ import {
|
||||
BookOpen,
|
||||
Compass,
|
||||
Smartphone,
|
||||
ShieldCheck
|
||||
ShieldCheck,
|
||||
KeyRound,
|
||||
Lock
|
||||
} from "lucide-react";
|
||||
|
||||
// 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_mesh": <Network className="w-6 h-6 text-orange-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
|
||||
"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_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
|
||||
"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
|
||||
"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) => {
|
||||
@@ -147,11 +149,13 @@ export default function Seletor() {
|
||||
},
|
||||
{
|
||||
id: "security",
|
||||
question: "Qual o nível de segurança criptográfica exigido pelo produto?",
|
||||
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.",
|
||||
question: "Qual requisito de segurança o produto precisa atender?",
|
||||
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: [
|
||||
{ value: "critical", label: "Crítico (Matter, pagamentos, provisionamento seguro de chaves)", icon: "security_critical" },
|
||||
{ value: "standard", label: "Padrão (TLS comum e projetos sem requisitos regulatórios)", icon: "security_standard" },
|
||||
{ value: "matter", label: "Atestado de dispositivo para Matter ou assinatura digital (ECDSA)", icon: "security_matter" },
|
||||
{ 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)
|
||||
if (answers.security === "critical") {
|
||||
const cripto = serie.aceleradores_cripto || "";
|
||||
// Agora lê os campos destrinchados, então cada requisito pontua o
|
||||
// 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;
|
||||
reasons[key].push("Suíte criptográfica completa em hardware com Key Manager (provisionamento e uso de chaves sem exposição ao firmware)");
|
||||
} else if (cripto.includes("ECDSA")) {
|
||||
scores[key] += 20;
|
||||
reasons[key].push("Aceleradores criptográficos com ECC/ECDSA dedicados (assinatura digital e Secure Boot eficientes)");
|
||||
} else if (cripto) {
|
||||
reasons[key].push(`ECDSA em hardware (${seg.ecc}) — atestado de dispositivo Matter assinado sem custo de CPU`);
|
||||
} else if (temECC) {
|
||||
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 {
|
||||
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 === "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;
|
||||
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`);
|
||||
}
|
||||
} else if (answers.security === "standard") {
|
||||
scores[key] += 5;
|
||||
}
|
||||
});
|
||||
|
||||
@@ -418,7 +452,7 @@ export default function Seletor() {
|
||||
const progress = ((currentStep + 1) / questions.length) * 100;
|
||||
|
||||
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 />
|
||||
|
||||
<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 && (
|
||||
<div className="max-w-2xl mx-auto mb-10">
|
||||
<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}
|
||||
</span>
|
||||
<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 */}
|
||||
{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">
|
||||
<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}
|
||||
</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}
|
||||
</p>
|
||||
</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] ${
|
||||
isSelected
|
||||
? '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 */}
|
||||
@@ -514,13 +548,13 @@ export default function Seletor() {
|
||||
</div>
|
||||
|
||||
{/* 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
|
||||
onClick={goBack}
|
||||
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 ${
|
||||
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'
|
||||
}`}
|
||||
>
|
||||
@@ -541,7 +575,7 @@ export default function Seletor() {
|
||||
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 ${
|
||||
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'
|
||||
}`}
|
||||
>
|
||||
@@ -554,7 +588,7 @@ export default function Seletor() {
|
||||
|
||||
{/* STEP 2: Choice Summary */}
|
||||
{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="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" />
|
||||
@@ -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">
|
||||
Resumo das suas Configurações
|
||||
</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.
|
||||
</p>
|
||||
</div>
|
||||
@@ -574,9 +608,9 @@ export default function Seletor() {
|
||||
const selectedOption = question.options.find(opt => opt.value === answer);
|
||||
|
||||
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">
|
||||
<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()}
|
||||
</h3>
|
||||
{selectedOption && (
|
||||
@@ -597,7 +631,7 @@ export default function Seletor() {
|
||||
setCurrentAnswer(answers[question.id]);
|
||||
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"
|
||||
>
|
||||
<Edit2 className="w-3.5 h-3.5" />
|
||||
@@ -607,7 +641,7 @@ export default function Seletor() {
|
||||
})}
|
||||
</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
|
||||
onClick={() => {
|
||||
setCurrentStep(questions.length - 1);
|
||||
@@ -654,7 +688,7 @@ export default function Seletor() {
|
||||
},
|
||||
{
|
||||
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",
|
||||
scoreClass: "text-slate-500 dark:text-slate-300"
|
||||
},
|
||||
@@ -669,7 +703,7 @@ export default function Seletor() {
|
||||
return (
|
||||
<div
|
||||
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 */}
|
||||
<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">
|
||||
{seriesKey}
|
||||
</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}
|
||||
</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-xs font-extrabold ${rankConfig.scoreClass}`}>
|
||||
{score} pts
|
||||
@@ -700,8 +734,8 @@ export default function Seletor() {
|
||||
</div>
|
||||
|
||||
{/* Specifications Checklist */}
|
||||
<div className="space-y-2 border-t border-slate-200/50 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>
|
||||
<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-500 dark:text-slate-400 uppercase tracking-widest mb-2.5">Principais Vantagens</h4>
|
||||
<ul className="space-y-2">
|
||||
{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">
|
||||
@@ -710,7 +744,7 @@ export default function Seletor() {
|
||||
</li>
|
||||
))}
|
||||
{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
|
||||
</li>
|
||||
)}
|
||||
@@ -719,7 +753,7 @@ export default function Seletor() {
|
||||
</div>
|
||||
|
||||
{/* 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">
|
||||
<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>
|
||||
@@ -750,7 +784,7 @@ export default function Seletor() {
|
||||
|
||||
{/* Stacked Layout for mobile */}
|
||||
<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) => {
|
||||
const serie = seriesData[seriesKey];
|
||||
const matchReasons = allReasons[seriesKey] || [];
|
||||
@@ -760,8 +794,8 @@ export default function Seletor() {
|
||||
return (
|
||||
<div
|
||||
key={seriesKey}
|
||||
className={`bg-white/70 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"
|
||||
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-300 dark:border-slate-800"
|
||||
}`}
|
||||
>
|
||||
<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>
|
||||
<div>
|
||||
<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>
|
||||
|
||||
@@ -808,7 +842,7 @@ export default function Seletor() {
|
||||
|
||||
{/* Other options */}
|
||||
{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">
|
||||
Outras Opções Compatíveis
|
||||
</h3>
|
||||
@@ -818,12 +852,12 @@ export default function Seletor() {
|
||||
return (
|
||||
<div
|
||||
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-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>
|
||||
<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>
|
||||
<Link
|
||||
href={`/series/${seriesKey}`}
|
||||
@@ -840,10 +874,10 @@ export default function Seletor() {
|
||||
)}
|
||||
|
||||
{/* 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
|
||||
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" />
|
||||
<span>Comparar Lado a Lado</span>
|
||||
@@ -859,7 +893,7 @@ export default function Seletor() {
|
||||
</div>
|
||||
|
||||
{/* 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">
|
||||
Próximos Passos de Desenvolvimento
|
||||
</h3>
|
||||
@@ -915,7 +949,7 @@ export default function Seletor() {
|
||||
|
||||
{/* STEP 3B: No Results Found */}
|
||||
{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="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" />
|
||||
@@ -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).
|
||||
</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>
|
||||
<ul className="space-y-2 text-xs text-slate-500 dark:text-slate-400 leading-relaxed">
|
||||
<li className="flex items-start gap-2">
|
||||
@@ -952,7 +986,7 @@ export default function Seletor() {
|
||||
|
||||
<Link
|
||||
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" />
|
||||
<span>Ver Grade Completa</span>
|
||||
|
||||
+69
-36
@@ -6,6 +6,7 @@ import { notFound } from "next/navigation";
|
||||
import seriesData from "@/public/series.json";
|
||||
import ConnectionsDiagram from "@/components/ConnectionsDiagram";
|
||||
import SeriesTabMenu from "@/components/SeriesTabMenu";
|
||||
import PinRestrictions from "@/components/PinRestrictions";
|
||||
import {
|
||||
Cpu,
|
||||
Activity,
|
||||
@@ -41,7 +42,17 @@ import {
|
||||
ChevronRight,
|
||||
Lightbulb,
|
||||
ShieldCheck,
|
||||
KeyRound
|
||||
KeyRound,
|
||||
Lock,
|
||||
Hash,
|
||||
Spline,
|
||||
FileSignature,
|
||||
Dices,
|
||||
Waves,
|
||||
Bug,
|
||||
Box,
|
||||
Package,
|
||||
Terminal
|
||||
} from "lucide-react";
|
||||
|
||||
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" /> },
|
||||
];
|
||||
|
||||
const seg = serie.seguranca || {};
|
||||
const segurancaSpecs = [
|
||||
{ label: "Aceleradores Criptográficos", value: serie.aceleradores_cripto || "Não", icon: <ShieldCheck 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: "AES", value: seg.aes || "Não", icon: <Lock 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 = [
|
||||
@@ -149,13 +179,13 @@ export default async function SerieDetail({ params }) {
|
||||
label: 'Placa de Desenvolvimento',
|
||||
available: serie.placa,
|
||||
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">
|
||||
<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">
|
||||
<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-300 dark:border-slate-800/65 flex items-center gap-3">
|
||||
<Sliders className="w-5 h-5" style={{ color: serie.cor }} />
|
||||
<span>Kit de Desenvolvimento Oficial</span>
|
||||
</h2>
|
||||
<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
|
||||
src={serie.placa}
|
||||
alt={`${key} DevKit - Placa de Desenvolvimento`}
|
||||
@@ -177,7 +207,7 @@ export default async function SerieDetail({ params }) {
|
||||
label: 'Diagrama de Conexões',
|
||||
available: conexoes.length > 0,
|
||||
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="flex items-start gap-3">
|
||||
<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} />
|
||||
|
||||
<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">
|
||||
<Lightbulb className="w-4 h-4 text-purple-500" />
|
||||
<span>Flexibilidade das Portas GPIO</span>
|
||||
@@ -217,7 +247,7 @@ export default async function SerieDetail({ params }) {
|
||||
];
|
||||
|
||||
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 />
|
||||
|
||||
<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 */}
|
||||
<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` }}
|
||||
>
|
||||
<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">
|
||||
{key}
|
||||
</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
|
||||
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` }}
|
||||
@@ -261,7 +291,7 @@ export default async function SerieDetail({ params }) {
|
||||
href={serie.datasheet}
|
||||
target="_blank"
|
||||
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" />
|
||||
<span>Datasheet Oficial</span>
|
||||
@@ -318,28 +348,28 @@ export default async function SerieDetail({ params }) {
|
||||
{/* Primary Features Highlight Grid */}
|
||||
<div className="grid md:grid-cols-3 gap-6 mb-8">
|
||||
<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 }}
|
||||
>
|
||||
<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-xs text-slate-400 mt-1">{serie.frequencia}</p>
|
||||
</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 }}
|
||||
>
|
||||
<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-xs text-slate-400 mt-1">+ {serie.memoria_sram_rtc} RTC SRAM</p>
|
||||
</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 }}
|
||||
>
|
||||
<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-xs text-slate-400 mt-1">Pinos I/O programáveis</p>
|
||||
</div>
|
||||
@@ -348,17 +378,20 @@ export default async function SerieDetail({ params }) {
|
||||
{/* DevBoard and Connections Tabs */}
|
||||
<SeriesTabMenu tabs={tabs} color={serie.cor} />
|
||||
|
||||
<PinRestrictions conexoes={conexoes} serieKey={key} cor={serie.cor} />
|
||||
|
||||
{/* Specs List Cards */}
|
||||
<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="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="Estrutura de Memória" specs={memoriaSpecs} cor={serie.cor} icon={<Database className="w-5 h-5" style={{ color: serie.cor }} />} />
|
||||
|
||||
{/* Power Consumption section */}
|
||||
{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">
|
||||
<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">
|
||||
<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-300 dark:border-slate-800/65 flex items-center gap-3 select-none">
|
||||
<Battery className="w-5 h-5" style={{ color: serie.cor }} />
|
||||
<span>Consumo de Energia</span>
|
||||
</h2>
|
||||
@@ -375,11 +408,11 @@ export default async function SerieDetail({ params }) {
|
||||
<div className="grid md:grid-cols-2 lg:grid-cols-3 gap-4">
|
||||
{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 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}
|
||||
</div>
|
||||
<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>
|
||||
</div>
|
||||
</div>
|
||||
@@ -406,26 +439,26 @@ export default async function SerieDetail({ params }) {
|
||||
</div>
|
||||
|
||||
{/* 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="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="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" />
|
||||
<span>Hardware Fisico</span>
|
||||
</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?
|
||||
</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.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<Link
|
||||
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>
|
||||
<ArrowRight className="w-4 h-4" />
|
||||
@@ -438,11 +471,11 @@ export default async function SerieDetail({ params }) {
|
||||
{previousKey ? (
|
||||
<Link
|
||||
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" />
|
||||
<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>
|
||||
</div>
|
||||
</Link>
|
||||
@@ -452,7 +485,7 @@ export default async function SerieDetail({ params }) {
|
||||
|
||||
<Link
|
||||
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" />
|
||||
<span>Comparar Séries</span>
|
||||
@@ -461,10 +494,10 @@ export default async function SerieDetail({ params }) {
|
||||
{nextKey ? (
|
||||
<Link
|
||||
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">
|
||||
<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>
|
||||
</div>
|
||||
<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 }) {
|
||||
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">
|
||||
<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">
|
||||
<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-300 dark:border-slate-800/65 flex items-center gap-3 select-none">
|
||||
{icon}
|
||||
<span>{title}</span>
|
||||
</h2>
|
||||
<div className="grid md:grid-cols-2 lg:grid-cols-3 gap-4">
|
||||
{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 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}
|
||||
</div>
|
||||
<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>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
+35
-25
@@ -24,7 +24,7 @@ export default function Series() {
|
||||
const series = Object.entries(seriesData);
|
||||
|
||||
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 />
|
||||
|
||||
<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 */}
|
||||
<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={{
|
||||
borderColor: 'rgba(148, 163, 184, 0.1)',
|
||||
transition: 'border-color 0.3s, box-shadow 0.3s, transform 0.3s'
|
||||
}}
|
||||
onMouseEnter={(e) => {
|
||||
@@ -90,14 +89,14 @@ export default function Series() {
|
||||
{key}
|
||||
</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}
|
||||
</p>
|
||||
|
||||
{/* 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 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" />
|
||||
<span>Núcleos:</span>
|
||||
</div>
|
||||
@@ -105,7 +104,7 @@ export default function Series() {
|
||||
</div>
|
||||
|
||||
<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" />
|
||||
<span>Frequência:</span>
|
||||
</div>
|
||||
@@ -113,7 +112,7 @@ export default function Series() {
|
||||
</div>
|
||||
|
||||
<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" />
|
||||
<span>SRAM:</span>
|
||||
</div>
|
||||
@@ -121,13 +120,13 @@ export default function Series() {
|
||||
</div>
|
||||
|
||||
<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" />
|
||||
<span>Wi-Fi:</span>
|
||||
</div>
|
||||
<span className="font-bold text-slate-800 dark:text-slate-200">
|
||||
{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>
|
||||
)}
|
||||
@@ -135,13 +134,13 @@ export default function Series() {
|
||||
</div>
|
||||
|
||||
<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" />
|
||||
<span>Bluetooth:</span>
|
||||
</div>
|
||||
<span className="font-bold text-slate-800 dark:text-slate-200">
|
||||
{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>
|
||||
)}
|
||||
@@ -150,7 +149,7 @@ export default function Series() {
|
||||
|
||||
{serie.consumo_energia?.deep_sleep && (
|
||||
<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" />
|
||||
<span>Deep Sleep:</span>
|
||||
</div>
|
||||
@@ -177,12 +176,23 @@ export default function Series() {
|
||||
</span>
|
||||
)}
|
||||
|
||||
{serie.aceleradores_cripto && (
|
||||
<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">
|
||||
<ShieldCheck className="w-3 h-3" />
|
||||
<span>Cripto por Hardware</span>
|
||||
</span>
|
||||
)}
|
||||
{/* Marcador de segurança: mostra o recurso que DIFERENCIA a série,
|
||||
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" />
|
||||
<span>{nivel}</span>
|
||||
</span>
|
||||
);
|
||||
})()}
|
||||
|
||||
{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">
|
||||
@@ -192,7 +202,7 @@ export default function Series() {
|
||||
)}
|
||||
</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>
|
||||
<ArrowRight className="w-4 h-4 transform group-hover:translate-x-2 transition-transform duration-300" />
|
||||
</div>
|
||||
@@ -204,26 +214,26 @@ export default function Series() {
|
||||
</div>
|
||||
|
||||
{/* 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="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="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" />
|
||||
<span>Decisão Técnica</span>
|
||||
</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?
|
||||
</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.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<Link
|
||||
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>
|
||||
<ArrowRight className="w-4 h-4" />
|
||||
|
||||
+9
-9
@@ -27,7 +27,7 @@ export default function Sobre() {
|
||||
];
|
||||
|
||||
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">
|
||||
Ir para o conteúdo
|
||||
</a>
|
||||
@@ -75,7 +75,7 @@ export default function Sobre() {
|
||||
<div className="mt-12 grid grid-cols-1 lg:grid-cols-3 gap-8">
|
||||
<section className="lg:col-span-2 space-y-8">
|
||||
{/* 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="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" />
|
||||
@@ -91,7 +91,7 @@ export default function Sobre() {
|
||||
</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="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" />
|
||||
@@ -120,7 +120,7 @@ export default function Sobre() {
|
||||
</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="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" />
|
||||
@@ -160,7 +160,7 @@ export default function Sobre() {
|
||||
{/* Sidebar area */}
|
||||
<aside className="space-y-6">
|
||||
{/* 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="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
|
||||
@@ -206,7 +206,7 @@ export default function Sobre() {
|
||||
</div>
|
||||
|
||||
{/* 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">
|
||||
<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>
|
||||
@@ -220,7 +220,7 @@ export default function Sobre() {
|
||||
href="https://github.com/lucasrguerra/ESPDocs"
|
||||
target="_blank"
|
||||
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"
|
||||
>
|
||||
<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>
|
||||
</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.
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 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>
|
||||
<ul className="text-xs text-slate-600 dark:text-slate-400 space-y-2.5 font-bold">
|
||||
<li className="flex items-center gap-2">
|
||||
|
||||
@@ -21,11 +21,11 @@ export default function BoardCard({ board }) {
|
||||
);
|
||||
|
||||
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 */}
|
||||
{board.Imagem && (
|
||||
<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 }}
|
||||
/>
|
||||
)}
|
||||
@@ -50,7 +50,7 @@ export default function BoardCard({ board }) {
|
||||
{/* Características Técnicas */}
|
||||
{availableFeatures.length > 0 && (
|
||||
<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" />
|
||||
<span>Recursos Embarcados</span>
|
||||
</h4>
|
||||
@@ -87,7 +87,7 @@ export default function BoardCard({ board }) {
|
||||
href={board.Link}
|
||||
target="_blank"
|
||||
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>
|
||||
<ExternalLink className="w-3.5 h-3.5" />
|
||||
|
||||
@@ -70,7 +70,7 @@ export default function CodeExample({ framework }) {
|
||||
}
|
||||
|
||||
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="select-none">
|
||||
<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
|
||||
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"
|
||||
>
|
||||
{copied ? (
|
||||
@@ -129,16 +129,16 @@ export default function CodeExample({ framework }) {
|
||||
</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="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">
|
||||
<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-green-500/80" />
|
||||
<span className="ml-2 font-mono text-[10px] text-slate-500 dark:text-slate-400">{framework?.nome} example</span>
|
||||
</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')}
|
||||
</div>
|
||||
</div>
|
||||
@@ -151,7 +151,7 @@ export default function CodeExample({ framework }) {
|
||||
>
|
||||
{lines.map((line, idx) => (
|
||||
<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>
|
||||
</div>
|
||||
))}
|
||||
|
||||
@@ -130,15 +130,15 @@ export default function ConnectionsDiagram({ connections, serie }) {
|
||||
if (!connections || connections.length === 0) {
|
||||
return (
|
||||
<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">
|
||||
<Sliders className="w-10 h-10 text-slate-400 dark:text-slate-600" />
|
||||
<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-500 dark:text-slate-400" />
|
||||
</div>
|
||||
|
||||
<p className="text-lg font-bold text-slate-800 dark:text-slate-200">
|
||||
Diagrama de conexões indisponível
|
||||
</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.
|
||||
</p>
|
||||
</div>
|
||||
@@ -150,7 +150,7 @@ export default function ConnectionsDiagram({ connections, serie }) {
|
||||
|
||||
{/* Header info */}
|
||||
<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 }} />
|
||||
<span>Diagrama de Conexões Interativo</span>
|
||||
</h2>
|
||||
@@ -160,16 +160,16 @@ export default function ConnectionsDiagram({ connections, serie }) {
|
||||
</div>
|
||||
|
||||
{/* 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-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
|
||||
type="text"
|
||||
placeholder="Buscar por nome ou número da conexão (Ex: GPIO1, TXD, XTAL)..."
|
||||
value={searchQuery}
|
||||
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"
|
||||
/>
|
||||
{searchQuery && (
|
||||
@@ -217,14 +217,14 @@ export default function ConnectionsDiagram({ connections, serie }) {
|
||||
</div>
|
||||
|
||||
{/* 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">
|
||||
<Filter className="w-4 h-4 text-purple-500" />
|
||||
<h4 className="text-sm font-bold text-slate-800 dark:text-slate-100 select-none">
|
||||
Filtre por Função do Pino
|
||||
</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
|
||||
</span>
|
||||
</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 ${
|
||||
isActive
|
||||
? "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={{
|
||||
backgroundColor: isActive ? filter.color : undefined,
|
||||
@@ -257,7 +257,7 @@ export default function ConnectionsDiagram({ connections, serie }) {
|
||||
<span className="flex items-center gap-2">
|
||||
<span>{filter.name}</span>
|
||||
<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}
|
||||
</span>
|
||||
@@ -269,8 +269,8 @@ export default function ConnectionsDiagram({ connections, serie }) {
|
||||
|
||||
{/* Active Categories badges */}
|
||||
{activeFilter !== "All" && activeCategories.length > 0 && (
|
||||
<div className="mt-5 pt-5 border-t border-slate-100 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>
|
||||
<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-500 dark:text-slate-400 mb-3 uppercase tracking-wider">Subcategorias ativadas por esse filtro:</p>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{activeCategories.map(cat => (
|
||||
<span
|
||||
@@ -289,7 +289,7 @@ export default function ConnectionsDiagram({ connections, serie }) {
|
||||
|
||||
{/* Diagram Workspace */}
|
||||
{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">
|
||||
<Search className="w-6 h-6 text-slate-400" />
|
||||
</div>
|
||||
@@ -301,14 +301,14 @@ export default function ConnectionsDiagram({ connections, serie }) {
|
||||
setActiveFilter("All");
|
||||
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
|
||||
</button>
|
||||
</div>
|
||||
) : viewMode === "grid" ? (
|
||||
/* 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">
|
||||
{visibleConnections.map((connection, index) => {
|
||||
const isHovered = hoveredConnection === index;
|
||||
@@ -367,7 +367,7 @@ export default function ConnectionsDiagram({ connections, serie }) {
|
||||
/>
|
||||
))}
|
||||
{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}
|
||||
</span>
|
||||
)}
|
||||
@@ -419,11 +419,11 @@ export default function ConnectionsDiagram({ connections, serie }) {
|
||||
</div>
|
||||
) : (
|
||||
/* 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">
|
||||
<table className="w-full text-left">
|
||||
<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">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>
|
||||
@@ -532,11 +532,11 @@ export default function ConnectionsDiagram({ connections, serie }) {
|
||||
aria-labelledby="modal-title"
|
||||
>
|
||||
<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()}
|
||||
>
|
||||
{/* 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-center gap-4 flex-1">
|
||||
<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">
|
||||
{selectedConnection.nome}
|
||||
</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}
|
||||
</p>
|
||||
</div>
|
||||
@@ -573,7 +573,7 @@ export default function ConnectionsDiagram({ connections, serie }) {
|
||||
{/* Categories */}
|
||||
{selectedConnection.categorias.length > 0 && (
|
||||
<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" />
|
||||
<span>Categorias de Multiplexação</span>
|
||||
</h4>
|
||||
@@ -595,11 +595,11 @@ export default function ConnectionsDiagram({ connections, serie }) {
|
||||
{/* Power Source */}
|
||||
{selectedConnection.alimentacao && (
|
||||
<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" />
|
||||
<span>Domínio de Alimentação Interna</span>
|
||||
</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">
|
||||
{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">
|
||||
@@ -617,12 +617,12 @@ export default function ConnectionsDiagram({ connections, serie }) {
|
||||
|
||||
{/* Function Multiplexation */}
|
||||
<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" />
|
||||
<span>Barramentos de Funções Disponíveis</span>
|
||||
</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">
|
||||
{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">
|
||||
@@ -663,7 +663,7 @@ export default function ConnectionsDiagram({ connections, serie }) {
|
||||
</div>
|
||||
|
||||
{/* 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
|
||||
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"
|
||||
|
||||
@@ -133,7 +133,7 @@ export default function Footer() {
|
||||
</div>
|
||||
|
||||
{/* 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">
|
||||
© {year} ESPDocs. Esta plataforma independente de código aberto não possui afiliação com a Espressif Systems.
|
||||
</p>
|
||||
|
||||
@@ -62,7 +62,7 @@ export default function Header() {
|
||||
}, [drawerOpen]);
|
||||
|
||||
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">
|
||||
{/* LOGO */}
|
||||
<Link href="/" className="group shrink-0">
|
||||
@@ -103,7 +103,7 @@ export default function Header() {
|
||||
{/* Full Menu Burger Trigger */}
|
||||
<button
|
||||
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-expanded={drawerOpen}
|
||||
>
|
||||
@@ -116,7 +116,7 @@ export default function Header() {
|
||||
<ThemeToggle />
|
||||
<button
|
||||
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-expanded={drawerOpen}
|
||||
>
|
||||
@@ -136,12 +136,12 @@ export default function Header() {
|
||||
)}
|
||||
|
||||
<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"
|
||||
}`}
|
||||
>
|
||||
{/* 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">
|
||||
Menu Navegação
|
||||
</h2>
|
||||
@@ -181,8 +181,8 @@ export default function Header() {
|
||||
</nav>
|
||||
|
||||
{/* 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">
|
||||
<p className="text-center text-xs text-slate-400 dark:text-slate-500 font-medium">
|
||||
<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-500 dark:text-slate-400 font-medium">
|
||||
ESPDocs • Plataforma Não Oficial
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
@@ -1,24 +1,55 @@
|
||||
'use client';
|
||||
|
||||
import { useState } from 'react';
|
||||
import { useState, useEffect } from 'react';
|
||||
import { ChevronDown } from 'lucide-react';
|
||||
|
||||
export default function SeriesTabMenu({ tabs, color }) {
|
||||
const availableTabs = tabs.filter(tab => tab.available);
|
||||
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;
|
||||
|
||||
return (
|
||||
<div className="mb-12">
|
||||
<div className="mb-12" id="diagrama-abas">
|
||||
{/* 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) => {
|
||||
const isActive = activeTab === tab.id;
|
||||
return (
|
||||
<button
|
||||
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 ${
|
||||
isActive
|
||||
? 'text-white shadow-lg scale-[1.02]'
|
||||
@@ -38,7 +69,7 @@ export default function SeriesTabMenu({ tabs, color }) {
|
||||
|
||||
{/* Mobile Dropdown Tab Selector */}
|
||||
<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
|
||||
onClick={() => {
|
||||
const dropdown = document.getElementById('mobile-tab-dropdown');
|
||||
@@ -61,10 +92,10 @@ export default function SeriesTabMenu({ tabs, color }) {
|
||||
<button
|
||||
key={tab.id}
|
||||
onClick={() => {
|
||||
setActiveTab(tab.id);
|
||||
trocarAba(tab.id);
|
||||
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>
|
||||
</button>
|
||||
|
||||
+2
-1
@@ -6,7 +6,8 @@
|
||||
"dev": "next dev --turbopack",
|
||||
"build": "next build --turbopack",
|
||||
"start": "next start",
|
||||
"lint": "eslint"
|
||||
"lint": "eslint",
|
||||
"auditar-temas": "node scripts/auditar-temas.mjs"
|
||||
},
|
||||
"dependencies": {
|
||||
"@emotion/react": "^11.14.0",
|
||||
|
||||
+228
-12
@@ -7,7 +7,20 @@
|
||||
"nucleos": "1 (Single-core) ou 2 (Dual-core)",
|
||||
"frequencia": "Até 240 MHz",
|
||||
"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)",
|
||||
"bluetooth": "Bluetooth 4.2 BR/EDR (Clássico) e Bluetooth 5.0 LE (certificado)",
|
||||
"ethernet": "Sim (Ethernet MAC integrado)",
|
||||
@@ -40,6 +53,11 @@
|
||||
},
|
||||
"temperatura_operacao": "-40°C a 125°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",
|
||||
"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",
|
||||
@@ -54,7 +72,20 @@
|
||||
"nucleos": "1 (Single-core)",
|
||||
"frequencia": "Até 240 MHz",
|
||||
"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)",
|
||||
"bluetooth": "Não",
|
||||
"memoria_sram": "320 KB",
|
||||
@@ -83,6 +114,11 @@
|
||||
},
|
||||
"temperatura_operacao": "-40°C a 105°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",
|
||||
"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",
|
||||
@@ -98,7 +134,20 @@
|
||||
"frequencia": "Até 240 MHz",
|
||||
"coprocessador_ulp": "Sim (RISC-V)",
|
||||
"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)",
|
||||
"bluetooth": "Bluetooth 5.4 LE (certificado)",
|
||||
"memoria_sram": "512 KB",
|
||||
@@ -128,6 +177,11 @@
|
||||
},
|
||||
"temperatura_operacao": "-40°C a 65/85/105°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",
|
||||
"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",
|
||||
@@ -143,7 +197,20 @@
|
||||
"frequencia": "Até 320 MHz",
|
||||
"coprocessador_ulp": "Sim (RISC-V, LP CPU de 40 MHz)",
|
||||
"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",
|
||||
"bluetooth": "Bluetooth 5.4 LE e Bluetooth Classic",
|
||||
"zigbee_thread": "IEEE 802.15.4 (Zigbee 3.0, Thread 1.4)",
|
||||
@@ -172,6 +239,11 @@
|
||||
},
|
||||
"temperatura_operacao": "-40°C a 85°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",
|
||||
"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",
|
||||
@@ -185,7 +257,20 @@
|
||||
"arquitetura": "RISC-V single-core de 32 bits",
|
||||
"nucleos": "1 (Single-core)",
|
||||
"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)",
|
||||
"bluetooth": "Bluetooth 5.3 LE (certificado)",
|
||||
"memoria_sram": "272 KB (16 KB para cache)",
|
||||
@@ -210,6 +295,11 @@
|
||||
},
|
||||
"temperatura_operacao": "-40°C a 105°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",
|
||||
"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",
|
||||
@@ -223,7 +313,20 @@
|
||||
"arquitetura": "RISC-V single-core de 32 bits",
|
||||
"nucleos": "1 (Single-core)",
|
||||
"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)",
|
||||
"bluetooth": "Bluetooth 5.4 LE (certificado)",
|
||||
"memoria_sram": "400 KB",
|
||||
@@ -251,6 +354,11 @@
|
||||
},
|
||||
"temperatura_operacao": "-40°C a 125°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",
|
||||
"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",
|
||||
@@ -265,7 +373,20 @@
|
||||
"nucleos": "1 (Single-core HP) + 1 (Single-core LP)",
|
||||
"frequencia": "Até 240 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",
|
||||
"bluetooth": "Bluetooth 6.0 LE (Core 6.0 certificado)",
|
||||
"zigbee_thread": "IEEE 802.15.4 (Zigbee 3.0, Thread 1.4)",
|
||||
@@ -293,6 +414,11 @@
|
||||
},
|
||||
"temperatura_operacao": "-40°C a 105°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",
|
||||
"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",
|
||||
@@ -307,7 +433,20 @@
|
||||
"nucleos": "1 (Single-core HP) + 1 (Single-core LP)",
|
||||
"frequencia": "Até 160 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",
|
||||
"bluetooth": "Bluetooth 6.0 LE (Core 6.0 certificado)",
|
||||
"zigbee_thread": "IEEE 802.15.4 (Zigbee 3.0, Thread 1.3)",
|
||||
@@ -336,6 +475,11 @@
|
||||
},
|
||||
"temperatura_operacao": "-40°C a 105°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",
|
||||
"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",
|
||||
@@ -349,7 +493,20 @@
|
||||
"arquitetura": "RISC-V single-core de 32 bits",
|
||||
"nucleos": "1 (Single-core)",
|
||||
"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",
|
||||
"bluetooth": "Bluetooth 6.0 LE (Core 6.0 certificado)",
|
||||
"zigbee_thread": "Não",
|
||||
@@ -378,6 +535,11 @@
|
||||
},
|
||||
"temperatura_operacao": "-40°C a 105°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",
|
||||
"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",
|
||||
@@ -392,7 +554,20 @@
|
||||
"nucleos": "2 (Dual-core HP) + 1 (Single-core LP)",
|
||||
"frequencia": "Até 400 MHz",
|
||||
"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",
|
||||
"bluetooth": "Não",
|
||||
"ethernet": "Sim (Ethernet MAC integrado)",
|
||||
@@ -424,6 +599,11 @@
|
||||
},
|
||||
"temperatura_operacao": "-40°C a 85°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",
|
||||
"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",
|
||||
@@ -437,7 +617,20 @@
|
||||
"arquitetura": "RISC-V single-core de 32 bits",
|
||||
"nucleos": "1 (Single-core)",
|
||||
"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",
|
||||
"bluetooth": "Bluetooth 6.0 LE (Core 6.0 certificado)",
|
||||
"zigbee_thread": "IEEE 802.15.4 (Zigbee 3.0, Thread 1.4)",
|
||||
@@ -465,6 +658,11 @@
|
||||
},
|
||||
"temperatura_operacao": "-40°C a 105°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",
|
||||
"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",
|
||||
@@ -479,7 +677,20 @@
|
||||
"arquitetura": "RISC-V dual-core de 32 bits",
|
||||
"nucleos": "2 (Dual-core)",
|
||||
"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",
|
||||
"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)",
|
||||
@@ -506,6 +717,11 @@
|
||||
},
|
||||
"temperatura_operacao": "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",
|
||||
"cor": "#e83e8c"
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
Reference in new issue
Block a user