feat: overhaul homepage with platform statistics, structured feature cards, and highlighted ESP32 models while cleaning up project metadata.

This commit is contained in:
Lucas Rayan committed 2026-08-25 16:08:39 -03:00
1 parent 031b35d7dd
commit 07dac4f6de
18 files changed
+4929 -1158

No files matched your search

+29 -7
View File
@@ -1,10 +1,5 @@
import { paginaMeta } from "@/lib/seo"; import { paginaMeta, jsonLd } from "@/lib/seo";
/**
* A página desta rota é um client component, e client component não pode
* exportar metadata. Sem este layout, ela herdaria o título genérico do site,
* e o Google veria várias páginas com o mesmo título e a mesma descrição.
*/
export const metadata = paginaMeta({ export const metadata = paginaMeta({
titulo: "Catálogo de placas ESP32", titulo: "Catálogo de placas ESP32",
descricao: "Placas de desenvolvimento com ESP32 disponíveis no Brasil, com filtro por série, conector e conversor USB-serial.", descricao: "Placas de desenvolvimento com ESP32 disponíveis no Brasil, com filtro por série, conector e conversor USB-serial.",
@@ -13,5 +8,32 @@ export const metadata = paginaMeta({
}); });
export default function Layout({ children }) { export default function Layout({ children }) {
return children; return (
<>
<script
type="application/ld+json"
dangerouslySetInnerHTML={{
__html: JSON.stringify(
jsonLd.aplicacaoWeb({
nome: "Catálogo de Placas ESP32",
descricao: "Guia e catálogo interativo de placas de desenvolvimento e DevKits com microcontroladores ESP32.",
caminho: "/catalogo",
})
),
}}
/>
<script
type="application/ld+json"
dangerouslySetInnerHTML={{
__html: JSON.stringify(
jsonLd.trilha([
{ nome: "Início", caminho: "/" },
{ nome: "Catálogo de Placas", caminho: "/catalogo" },
])
),
}}
/>
{children}
</>
);
} }
+50 -7
View File
@@ -1,10 +1,5 @@
import { paginaMeta } from "@/lib/seo"; import { paginaMeta, jsonLd } from "@/lib/seo";
/**
* A página desta rota é um client component, e client component não pode
* exportar metadata. Sem este layout, ela herdaria o título genérico do site,
* e o Google veria várias páginas com o mesmo título e a mesma descrição.
*/
export const metadata = paginaMeta({ export const metadata = paginaMeta({
titulo: "Comparar séries ESP32", titulo: "Comparar séries ESP32",
descricao: "Compare até quatro séries de ESP32 lado a lado: processador, Wi-Fi, Bluetooth, memória, GPIO, periféricos e segurança em hardware, com os dados dos datasheets oficiais.", descricao: "Compare até quatro séries de ESP32 lado a lado: processador, Wi-Fi, Bluetooth, memória, GPIO, periféricos e segurança em hardware, com os dados dos datasheets oficiais.",
@@ -12,6 +7,54 @@ export const metadata = paginaMeta({
keywords: ["comparar ESP32", "ESP32 vs ESP32-S3", "tabela comparativa ESP32", "qual ESP32 é melhor"], keywords: ["comparar ESP32", "ESP32 vs ESP32-S3", "tabela comparativa ESP32", "qual ESP32 é melhor"],
}); });
const faqs = [
{
pergunta: "Como comparar microcontroladores ESP32 lado a lado?",
resposta: "O comparador do ESPDocs permite selecionar até quatro séries simultaneamente para confrontar arquitetura, frequência de clock, memória SRAM/PSRAM, padrões de rádio (Wi-Fi 4/6, Bluetooth 5/6, 802.15.4), interfaces de periféricos e blocos de segurança em hardware.",
},
{
pergunta: "Qual a diferença entre o ESP32 clássico e o ESP32-S3?",
resposta: "O ESP32 clássico usa núcleos Xtensa LX6 e Bluetooth clássico/LE. O ESP32-S3 traz núcleos Xtensa LX7 com instruções vetoriais para inteligência artificial (TinyML), USB nativo OTG, mais GPIOs (45 contra 36) e Bluetooth 5.0 LE.",
},
{
pergunta: "Qual a diferença entre ESP32-C3 e ESP32-C6?",
resposta: "O ESP32-C3 é um microcontrolador RISC-V com Wi-Fi 4 e Bluetooth 5 LE. O ESP32-C6 evolui para Wi-Fi 6 (802.11ax), Bluetooth 5.4 LE, rádio IEEE 802.15.4 para Zigbee 3.0 e Thread 1.3, além de coprocessador de ultrabaixo consumo.",
},
];
export default function Layout({ children }) { export default function Layout({ children }) {
return children; return (
<>
<script
type="application/ld+json"
dangerouslySetInnerHTML={{
__html: JSON.stringify(
jsonLd.aplicacaoWeb({
nome: "Comparador de Séries ESP32",
descricao: "Tabela comparativa técnica para analisar especificações, pinagens e recursos de múltiplas séries de ESP32.",
caminho: "/comparacao",
})
),
}}
/>
<script
type="application/ld+json"
dangerouslySetInnerHTML={{
__html: JSON.stringify(
jsonLd.trilha([
{ nome: "Início", caminho: "/" },
{ nome: "Comparar Séries", caminho: "/comparacao" },
])
),
}}
/>
<script
type="application/ld+json"
dangerouslySetInnerHTML={{
__html: JSON.stringify(jsonLd.perguntas(faqs)),
}}
/>
{children}
</>
);
} }
+75
View File
@@ -0,0 +1,75 @@
import { paginaMeta, jsonLd } from "@/lib/seo";
export const metadata = paginaMeta({
titulo: "Diagnóstico de Erros Comuns no ESP32: Guia Profissional",
descricao: "Guia técnico definitivo para identificar e resolver Brownout, Guru Meditation, Task Watchdog, Stack Overflow, vazamento de memória e falhas de boot no ESP32 (Arduino e ESP-IDF).",
caminho: "/diagnostico",
keywords: [
"erros comuns ESP32",
"brownout detector was triggered",
"guru meditation error",
"task watchdog got triggered",
"stack canary watchpoint triggered",
"como debugar ESP32",
"ESP32 reiniciando em loop",
"failed to connect to esp32",
],
});
const faqs = [
{
pergunta: "O que causa o erro 'Brownout detector was triggered' no ESP32?",
resposta: "Ocorre quando a tensão no pino 3V3 cai abaixo de 2.8V ~ 2.43V devido a picos rápidos de corrente do rádio Wi-Fi/BLE (superando 500mA), cabos USB finos ou regulador LDO inadequado. A solução de engenharia envolve adicionar capacitores Low-ESR de 10µF a 100µF próximos ao VCC e usar LDOs robustos de 600mA+.",
},
{
pergunta: "Como resolver o 'Guru Meditation Error (LoadProhibited)'?",
resposta: "LoadProhibited indica tentativa de acessar um ponteiro nulo (NULL) ou inválido na memória. A solução consiste em usar o Espressif Exception Decoder ou addr2line para localizar a linha exata no código e implementar validações defensivas como if (ponteiro != NULL).",
},
{
pergunta: "Por que o Task Watchdog Timer (TWDT) dispara no FreeRTOS?",
resposta: "O TWDT dispara quando uma tarefa de prioridade maior monopoliza a CPU em um loop bloqueante (ex: while(1) sem vTaskDelay), impedindo a tarefa IDLE de alimentar o watchdog do sistema operacional.",
},
{
pergunta: "Por que conexões HTTPS e TLS falham com código -0x2700 no ESP32?",
resposta: "Porque ao ligar, o ESP32 inicia o relógio interno no ano de 1970. O validador de certificados TLS considera o certificado do servidor inválido por estar no 'futuro'. Sincronizar o relógio via SNTP (configTime) antes da conexão resolve o problema.",
},
];
export default function DiagnosticoLayout({ children }) {
return (
<>
<script
type="application/ld+json"
dangerouslySetInnerHTML={{
__html: JSON.stringify(
jsonLd.trilha([
{ nome: "Início", caminho: "/" },
{ nome: "Diagnóstico & Erros Comuns", caminho: "/diagnostico" },
])
),
}}
/>
<script
type="application/ld+json"
dangerouslySetInnerHTML={{
__html: JSON.stringify(
jsonLd.perguntas(faqs)
),
}}
/>
<script
type="application/ld+json"
dangerouslySetInnerHTML={{
__html: JSON.stringify(
jsonLd.aplicacaoWeb({
nome: "Guia de Diagnóstico e Erros no ESP32",
descricao: "Ferramenta e guia técnico de análise de falhas, pânicos de CPU e decodificação de logs no ESP32.",
caminho: "/diagnostico",
})
),
}}
/>
{children}
</>
);
}
+820
View File
@@ -0,0 +1,820 @@
"use client";
import { useState, useMemo, useRef } from "react";
import Header from "@/components/Header";
import Footer from "@/components/Footer";
import Link from "next/link";
import {
categoriasDiagnostico,
sintomasRapidos,
errosCatalogo,
regrasDecodificadorLog,
} from "@/lib/diagnosticoData";
import {
AlertTriangle,
Zap,
Cpu,
Layers,
Activity,
Terminal,
Wifi,
Search,
CheckCircle2,
XCircle,
Copy,
Check,
Sparkles,
HelpCircle,
ArrowRight,
ExternalLink,
ShieldAlert,
Wrench,
Code2,
ChevronDown,
ChevronUp,
X,
Filter,
RotateCcw,
Tag,
} from "lucide-react";
export default function DiagnosticoPage() {
const [termoBusca, setTermoBusca] = useState("");
const [categoriaAtiva, setCategoriaAtiva] = useState("todos");
const [sintomaAtivo, setSintomaAtivo] = useState(null);
const [cardAberto, setCardAberto] = useState(null);
const [linguagemCodigo, setLinguagemCodigo] = useState({}); // { [id]: 'arduino' | 'espidf' }
const [copiadoId, setCopiadoId] = useState(null);
const searchInputRef = useRef(null);
// Estado do Analisador de Logs
const [logInput, setLogInput] = useState("");
const [resultadoLog, setResultadoLog] = useState(null);
// Mapeamento de ícones e emojis por categoria
const iconMap = {
Zap: <Zap className="w-4 h-4" />,
Cpu: <Cpu className="w-4 h-4" />,
Layers: <Layers className="w-4 h-4" />,
Activity: <Activity className="w-4 h-4" />,
Terminal: <Terminal className="w-4 h-4" />,
Wifi: <Wifi className="w-4 h-4" />,
};
const emojiMap = {
alimentacao: "⚡",
"cpu-panics": "💥",
freertos: "🐕",
memoria: "💾",
"boot-flash": "🔌",
conectividade: "📡",
};
// Função de Análise do Log
const analisarLog = (texto) => {
setLogInput(texto);
if (!texto || texto.trim() === "") {
setResultadoLog(null);
return;
}
for (const regra of regrasDecodificadorLog) {
if (regra.padrao.test(texto)) {
setResultadoLog(regra);
return;
}
}
setResultadoLog({
titulo: "Padrão de erro não categorizado automaticamente",
tipo: "Desconhecido",
explicacao:
"Não identificamos uma assinatura clássica de Guru Meditation ou Brownout neste trecho. Verifique se a taxa de baud rate (ex: 115200) está correta e se o cabeçalho 'rst:' ou 'panic' está presente.",
acaoRecomendada:
"Procure por palavras-chave como 'Guru Meditation', 'rst:', 'LoadProhibited', 'watchdog' ou 'panic' no console serial.",
});
};
const copiarParaClipboard = (texto, id) => {
navigator.clipboard.writeText(texto);
setCopiadoId(id);
setTimeout(() => setCopiadoId(null), 2000);
};
// Filtragem dos erros
const errosFiltrados = useMemo(() => {
const termo = termoBusca.toLowerCase().trim();
return errosCatalogo.filter((erro) => {
const matchCategoria =
categoriaAtiva === "todos" || erro.categoria === categoriaAtiva;
const matchSintoma =
!sintomaAtivo || erro.sintomas.includes(sintomaAtivo);
const matchBusca =
!termo ||
erro.titulo.toLowerCase().includes(termo) ||
erro.resumo.toLowerCase().includes(termo) ||
erro.causaRaiz.toLowerCase().includes(termo) ||
erro.logSerial.toLowerCase().includes(termo);
return matchCategoria && matchSintoma && matchBusca;
});
}, [categoriaAtiva, sintomaAtivo, termoBusca]);
const temFiltroAtivo =
termoBusca.trim() !== "" ||
categoriaAtiva !== "todos" ||
sintomaAtivo !== null;
const limparTodosFiltros = () => {
setTermoBusca("");
setCategoriaAtiva("todos");
setSintomaAtivo(null);
searchInputRef.current?.focus();
};
const toggleCard = (id) => {
setCardAberto((prev) => (prev === id ? null : id));
};
const setLang = (id, lang) => {
setLinguagemCodigo((prev) => ({ ...prev, [id]: lang }));
};
const catAtivaObj = categoriasDiagnostico.find((c) => c.id === categoriaAtiva);
const sintomaAtivoObj = sintomasRapidos.find((s) => s.id === sintomaAtivo);
return (
<div className="bg-gradient-to-br from-slate-100 via-slate-50 to-amber-100/30 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-4 sm:px-6 pt-12 pb-24 max-w-7xl mx-auto">
{/* Hero Section */}
<section className="text-center mb-10 select-none">
<div className="inline-flex items-center gap-2 bg-white dark:bg-slate-900/60 backdrop-blur-md px-4 py-2 rounded-full mb-5 border border-slate-250 dark:border-slate-800/80 shadow-xs">
<AlertTriangle className="w-4 h-4 text-amber-500" />
<span className="text-xs font-bold text-slate-700 dark:text-slate-300 uppercase tracking-wider">
Debugging & Engenharia de Firmware
</span>
</div>
<h1 className="text-3xl sm:text-5xl md:text-6xl font-display font-extrabold bg-gradient-to-r from-amber-600 via-red-600 to-purple-600 dark:from-amber-400 dark:via-red-400 dark:to-purple-400 bg-clip-text text-transparent mb-4 leading-tight tracking-tight">
Diagnóstico & Erros Comuns
</h1>
<p className="text-sm sm:text-base text-slate-600 dark:text-slate-400 max-w-3xl mx-auto leading-relaxed font-normal">
Guia técnico definitivo para diagnosticar <strong>Brownout</strong>,{" "}
<strong>Guru Meditation</strong>, <strong>Watchdog Timeouts</strong>,{" "}
<strong>Stack Overflow</strong> e falhas de hardware no ESP32. Entenda a causa raiz no silício e aplique soluções profissionais.
</p>
<div className="mt-3 text-xs text-slate-500 dark:text-slate-400">
Baseado no artigo publicado no{" "}
<a
href="https://cienciaembarcada.com.br/publicacoes/esp32-5-erros-comuns-e-como-resolve-los-profissionalmente/"
target="_blank"
rel="noopener noreferrer"
className="text-amber-600 dark:text-amber-400 underline font-semibold hover:opacity-80 inline-flex items-center gap-1"
>
Ciência Embarcada
<ExternalLink className="w-3 h-3" />
</a>
, ampliado com novos cenários e decodificador interativo.
</div>
</section>
{/* 1. DECODIFICADOR RÁPIDO DE LOG SERIAL */}
<section className="mb-8 bg-white/90 dark:bg-slate-900/90 backdrop-blur-xl rounded-3xl border border-slate-200 dark:border-slate-800 p-5 sm:p-7 shadow-sm">
<div className="flex flex-col md:flex-row md:items-center justify-between gap-4 mb-5">
<div className="flex items-center gap-3">
<div className="p-2.5 rounded-2xl bg-amber-500/10 text-amber-600 dark:text-amber-400 border border-amber-500/20">
<Sparkles className="w-5 h-5" />
</div>
<div>
<h2 className="text-lg sm:text-xl font-bold text-slate-900 dark:text-slate-100">
Decodificador Rápido de Log Serial
</h2>
<p className="text-xs text-slate-500 dark:text-slate-400">
Cole o trecho do monitor serial para diagnosticar a falha instantaneamente.
</p>
</div>
</div>
{/* Exemplos Rápidos para Teste */}
<div className="flex flex-wrap items-center gap-1.5 text-xs">
<span className="text-slate-500 dark:text-slate-400 font-bold mr-1 text-[11px] uppercase tracking-wider">
Exemplos:
</span>
<button
type="button"
onClick={() =>
analisarLog(
"Brownout detector was triggered\n\nrst:0xc (SW_CPU_RESET),boot:0x13"
)
}
className="px-2.5 py-1 rounded-lg bg-slate-100 dark:bg-slate-800 text-slate-700 dark:text-slate-300 hover:bg-amber-100 dark:hover:bg-amber-950/40 border border-slate-200 dark:border-slate-700 font-medium transition-colors cursor-pointer"
>
⚡ Brownout
</button>
<button
type="button"
onClick={() =>
analisarLog(
"Guru Meditation Error: Core 0 panic'ed (LoadProhibited).\nEXCVADDR: 0x00000000"
)
}
className="px-2.5 py-1 rounded-lg bg-slate-100 dark:bg-slate-800 text-slate-700 dark:text-slate-300 hover:bg-red-100 dark:hover:bg-red-950/40 border border-slate-200 dark:border-slate-700 font-medium transition-colors cursor-pointer"
>
💥 LoadProhibited
</button>
<button
type="button"
onClick={() =>
analisarLog(
"Task watchdog got triggered. The following tasks/users did not reset the watchdog: - IDLE0"
)
}
className="px-2.5 py-1 rounded-lg bg-slate-100 dark:bg-slate-800 text-slate-700 dark:text-slate-300 hover:bg-purple-100 dark:hover:bg-purple-950/40 border border-slate-200 dark:border-slate-700 font-medium transition-colors cursor-pointer"
>
🐕 Task Watchdog
</button>
<button
type="button"
onClick={() =>
analisarLog(
"mbedtls_ssl_handshake returned -0x2700\nThe certificate is not yet valid"
)
}
className="px-2.5 py-1 rounded-lg bg-slate-100 dark:bg-slate-800 text-slate-700 dark:text-slate-300 hover:bg-cyan-100 dark:hover:bg-cyan-950/40 border border-slate-200 dark:border-slate-700 font-medium transition-colors cursor-pointer"
>
🔒 Handshake TLS
</button>
</div>
</div>
<div className="relative">
<textarea
rows={3}
value={logInput}
onChange={(e) => analisarLog(e.target.value)}
placeholder="Cole aqui o log do monitor serial (ex: Guru Meditation Error, Brownout detector was triggered, Task watchdog, rst:0x1...)"
className="w-full bg-slate-950 text-emerald-400 font-mono text-xs sm:text-sm p-4 rounded-2xl border border-slate-800 focus:outline-none focus:ring-2 focus:ring-amber-500 placeholder:text-slate-600 resize-y shadow-inner"
/>
{logInput && (
<button
type="button"
onClick={() => analisarLog("")}
className="absolute top-3.5 right-3.5 text-xs text-slate-400 hover:text-slate-200 bg-slate-800 px-2.5 py-1 rounded-lg border border-slate-700 font-medium cursor-pointer"
>
Limpar
</button>
)}
</div>
{/* Card de Resultado da Análise */}
{resultadoLog && (
<div className="mt-4 p-4 sm:p-5 rounded-2xl bg-amber-50 dark:bg-amber-950/30 border border-amber-200 dark:border-amber-900/50 transition-all">
<div className="flex items-start gap-3.5">
<div className="p-2 rounded-xl bg-amber-500 text-white shrink-0 mt-0.5 shadow-xs">
<ShieldAlert className="w-5 h-5" />
</div>
<div className="space-y-2 flex-1">
<div className="flex flex-wrap items-center gap-2">
<h3 className="font-bold text-slate-900 dark:text-slate-100 text-sm sm:text-base">
{resultadoLog.titulo}
</h3>
<span className="text-xs px-2.5 py-0.5 rounded-full font-bold bg-amber-200 dark:bg-amber-900 text-amber-800 dark:text-amber-200">
{resultadoLog.tipo}
</span>
</div>
<p className="text-xs sm:text-sm text-slate-700 dark:text-slate-300 leading-relaxed">
<strong>Causa provável:</strong> {resultadoLog.explicacao}
</p>
<p className="text-xs sm:text-sm text-amber-900 dark:text-amber-300 font-medium">
<strong>Ação recomendada:</strong> {resultadoLog.acaoRecomendada}
</p>
</div>
</div>
</div>
)}
</section>
{/* 2. PAINEL UNIFICADO DE BUSCA & FILTRAGEM (COMMAND DECK) */}
<section className="mb-10 bg-white/90 dark:bg-slate-900/90 backdrop-blur-xl rounded-3xl border border-slate-200 dark:border-slate-800 p-5 sm:p-7 shadow-sm transition-all">
{/* Barra de Pesquisa Principal */}
<div className="relative mb-5">
<div className="absolute left-4 top-1/2 -translate-y-1/2 p-1.5 rounded-xl bg-amber-50 dark:bg-amber-950/50 text-amber-600 dark:text-amber-400 pointer-events-none">
<Search className="w-4 h-4 sm:w-5 sm:h-5" />
</div>
<input
ref={searchInputRef}
type="text"
value={termoBusca}
onChange={(e) => setTermoBusca(e.target.value)}
placeholder="Buscar por erro, sintoma, registrador ou mensagem serial (ex: LoadProhibited, Brownout, IRAM, Stack Canary)..."
className="w-full bg-slate-50 dark:bg-slate-950/70 pl-13 sm:pl-14 pr-24 sm:pr-28 py-3.5 sm:py-4 rounded-2xl border border-slate-250 dark:border-slate-800 text-sm sm:text-base font-medium placeholder:text-slate-400 dark:placeholder:text-slate-500 focus:outline-none focus:ring-2 focus:ring-amber-500 focus:border-transparent transition-all shadow-inner"
/>
<div className="absolute right-3 top-1/2 -translate-y-1/2 flex items-center gap-2">
{termoBusca ? (
<button
type="button"
onClick={() => {
setTermoBusca("");
searchInputRef.current?.focus();
}}
className="text-xs px-2.5 py-1 rounded-xl bg-slate-200 dark:bg-slate-800 text-slate-600 dark:text-slate-300 hover:bg-slate-300 dark:hover:bg-slate-700 font-semibold flex items-center gap-1 transition-colors cursor-pointer"
>
<X className="w-3.5 h-3.5" />
<span>Limpar</span>
</button>
) : (
<span className="hidden sm:inline-flex text-[11px] font-bold text-slate-500 dark:text-slate-400 bg-slate-200/60 dark:bg-slate-800/80 px-2.5 py-1 rounded-lg border border-slate-250 dark:border-slate-700">
{errosFiltrados.length} {errosFiltrados.length === 1 ? "falha" : "falhas"}
</span>
)}
</div>
</div>
{/* Atalhos Rápidos por Sintoma */}
<div className="flex items-center gap-2 mb-6 overflow-x-auto pb-1.5 scrollbar-none text-xs">
<span className="text-slate-500 dark:text-slate-400 font-bold shrink-0 flex items-center gap-1 text-[11px] uppercase tracking-wider">
<Tag className="w-3 h-3" />
Sintomas:
</span>
{sintomasRapidos.map((sintoma) => {
const ativo = sintomaAtivo === sintoma.id;
return (
<button
key={sintoma.id}
type="button"
onClick={() => setSintomaAtivo(ativo ? null : sintoma.id)}
className={`px-2.5 py-1 rounded-lg font-medium transition-all shrink-0 cursor-pointer border ${
ativo
? "bg-amber-600 text-white border-amber-600 shadow-xs font-bold"
: "bg-slate-100/80 dark:bg-slate-800/60 text-slate-600 dark:text-slate-300 border-slate-200 dark:border-slate-750 hover:bg-amber-50 dark:hover:bg-amber-950/40 hover:border-amber-300 dark:hover:border-amber-800"
}`}
>
<span>{sintoma.rotulo}</span>
</button>
);
})}
</div>
{/* Seletor de Categorias em Grid Tátil */}
<div className="space-y-2">
<div className="flex items-center justify-between text-xs text-slate-500 dark:text-slate-400 font-bold uppercase tracking-wider">
<span className="flex items-center gap-1.5">
<Filter className="w-3.5 h-3.5 text-amber-500" />
Filtrar por Domínio Técnico
</span>
{categoriaAtiva !== "todos" && (
<button
type="button"
onClick={() => setCategoriaAtiva("todos")}
className="text-amber-600 dark:text-amber-400 hover:underline cursor-pointer lowercase first-letter:uppercase text-[11px]"
>
Ver todas ({errosCatalogo.length})
</button>
)}
</div>
<div className="grid grid-cols-2 sm:grid-cols-4 lg:grid-cols-7 gap-2">
{/* Botão Todas as Categorias */}
<button
type="button"
onClick={() => setCategoriaAtiva("todos")}
className={`p-2.5 rounded-xl text-xs font-bold transition-all text-left flex flex-col justify-between gap-1 cursor-pointer border ${
categoriaAtiva === "todos"
? "bg-amber-600 text-white border-amber-600 shadow-sm ring-2 ring-amber-500/20"
: "bg-slate-50 dark:bg-slate-950/60 text-slate-700 dark:text-slate-300 border-slate-200 dark:border-slate-800 hover:border-amber-300 dark:hover:border-amber-700"
}`}
>
<span className="text-base">✨</span>
<div className="flex items-center justify-between w-full">
<span className="truncate">Todas</span>
<span
className={`text-[10px] px-1.5 py-0.2 rounded-full font-bold ${
categoriaAtiva === "todos"
? "bg-white/20 text-white"
: "bg-slate-200 dark:bg-slate-800 text-slate-600 dark:text-slate-400"
}`}
>
{errosCatalogo.length}
</span>
</div>
</button>
{/* Categorias Individuais */}
{categoriasDiagnostico.map((cat) => {
const totalNaCat = errosCatalogo.filter(
(e) => e.categoria === cat.id
).length;
const ativa = categoriaAtiva === cat.id;
return (
<button
key={cat.id}
type="button"
onClick={() => setCategoriaAtiva(cat.id)}
className={`p-2.5 rounded-xl text-xs font-bold transition-all text-left flex flex-col justify-between gap-1 cursor-pointer border ${
ativa
? "bg-amber-600 text-white border-amber-600 shadow-sm ring-2 ring-amber-500/20"
: "bg-slate-50 dark:bg-slate-950/60 text-slate-700 dark:text-slate-300 border-slate-200 dark:border-slate-800 hover:border-amber-300 dark:hover:border-amber-700"
}`}
>
<span className="text-base">{emojiMap[cat.id] || "⚠️"}</span>
<div className="flex items-center justify-between w-full">
<span className="truncate">{cat.nome.split(" ")[0]}</span>
<span
className={`text-[10px] px-1.5 py-0.2 rounded-full font-bold ${
ativa
? "bg-white/20 text-white"
: "bg-slate-200 dark:bg-slate-800 text-slate-600 dark:text-slate-400"
}`}
>
{totalNaCat}
</span>
</div>
</button>
);
})}
</div>
</div>
{/* Fita de Filtros Ativos */}
{temFiltroAtivo && (
<div className="mt-4 pt-3 border-t border-slate-150 dark:border-slate-800/80 flex flex-wrap items-center justify-between gap-2">
<div className="flex flex-wrap items-center gap-1.5 text-xs">
<span className="text-slate-500 dark:text-slate-400 font-semibold mr-1">
Filtros ativos:
</span>
{termoBusca && (
<span className="inline-flex items-center gap-1 bg-amber-50 dark:bg-amber-950/50 text-amber-800 dark:text-amber-300 px-2.5 py-0.5 rounded-full border border-amber-200 dark:border-amber-800 font-medium">
Busca: <strong>"{termoBusca}"</strong>
<button
type="button"
onClick={() => setTermoBusca("")}
className="hover:text-amber-950 dark:hover:text-amber-100"
>
<X className="w-3 h-3" />
</button>
</span>
)}
{categoriaAtiva !== "todos" && (
<span className="inline-flex items-center gap-1 bg-amber-50 dark:bg-amber-950/50 text-amber-800 dark:text-amber-300 px-2.5 py-0.5 rounded-full border border-amber-200 dark:border-amber-800 font-medium">
Categoria: <strong>{catAtivaObj?.nome}</strong>
<button
type="button"
onClick={() => setCategoriaAtiva("todos")}
className="hover:text-amber-950 dark:hover:text-amber-100"
>
<X className="w-3 h-3" />
</button>
</span>
)}
{sintomaAtivo && (
<span className="inline-flex items-center gap-1 bg-amber-50 dark:bg-amber-950/50 text-amber-800 dark:text-amber-300 px-2.5 py-0.5 rounded-full border border-amber-200 dark:border-amber-800 font-medium">
Sintoma: <strong>{sintomaAtivoObj?.rotulo}</strong>
<button
type="button"
onClick={() => setSintomaAtivo(null)}
className="hover:text-amber-950 dark:hover:text-amber-100"
>
<X className="w-3 h-3" />
</button>
</span>
)}
</div>
<button
type="button"
onClick={limparTodosFiltros}
className="text-xs font-semibold text-red-600 dark:text-red-400 hover:underline flex items-center gap-1 cursor-pointer"
>
<RotateCcw className="w-3 h-3" />
<span>Redefinir filtros</span>
</button>
</div>
)}
</section>
{/* 3. LISTA DE ERROS CATALOGADOS */}
<section className="space-y-6">
{errosFiltrados.length === 0 ? (
<div className="text-center py-16 bg-white dark:bg-slate-900 rounded-3xl border border-slate-250 dark:border-slate-800 shadow-sm p-8">
<HelpCircle className="w-12 h-12 text-slate-400 mx-auto mb-3" />
<h3 className="text-lg font-bold text-slate-700 dark:text-slate-300">
Nenhuma falha encontrada com os filtros atuais
</h3>
<p className="text-sm text-slate-500 dark:text-slate-400 mt-1 max-w-md mx-auto">
Tente buscar por termos mais genéricos ou limpe os filtros de sintoma e categoria.
</p>
<button
type="button"
onClick={limparTodosFiltros}
className="mt-4 px-4 py-2 bg-amber-600 text-white rounded-xl text-xs font-bold hover:bg-amber-700 cursor-pointer shadow-xs inline-flex items-center gap-1.5"
>
<RotateCcw className="w-3.5 h-3.5" />
<span>Limpar todos os filtros</span>
</button>
</div>
) : (
errosFiltrados.map((erro) => {
const estaAberto = cardAberto === erro.id;
const lang = linguagemCodigo[erro.id] || "arduino";
const codigoAtual =
lang === "arduino" ? erro.codigoArduino : erro.codigoEspIdf;
const catInfo = categoriasDiagnostico.find(
(c) => c.id === erro.categoria
);
return (
<article
key={erro.id}
id={erro.id}
className={`bg-white dark:bg-slate-900/90 rounded-3xl border transition-all duration-200 overflow-hidden shadow-xs ${
estaAberto
? "border-amber-400/80 dark:border-amber-500/50 ring-2 ring-amber-400/20"
: "border-slate-250 dark:border-slate-800 hover:border-slate-350 dark:hover:border-slate-700"
}`}
>
{/* Cabeçalho do Card */}
<div
onClick={() => toggleCard(erro.id)}
className="p-5 sm:p-6 cursor-pointer flex flex-col md:flex-row md:items-center justify-between gap-4 select-none"
>
<div className="space-y-2 flex-1">
<div className="flex flex-wrap items-center gap-2">
<span className="inline-flex items-center gap-1 text-xs px-2.5 py-0.5 rounded-full font-semibold bg-slate-100 dark:bg-slate-800 text-slate-700 dark:text-slate-300 border border-slate-250 dark:border-slate-700">
<span>{emojiMap[erro.categoria] || "⚠️"}</span>
<span>{catInfo?.nome}</span>
</span>
<span
className={`text-xs px-2.5 py-0.5 rounded-full font-semibold ${
erro.gravidade === "Crítica"
? "bg-red-100 dark:bg-red-950 text-red-700 dark:text-red-300 border border-red-200 dark:border-red-900"
: erro.gravidade === "Alta"
? "bg-amber-100 dark:bg-amber-950 text-amber-700 dark:text-amber-300 border border-amber-200 dark:border-amber-900"
: "bg-blue-100 dark:bg-blue-950 text-blue-700 dark:text-blue-300 border border-blue-200 dark:border-blue-900"
}`}
>
Gravidade: {erro.gravidade}
</span>
</div>
<h3 className="text-lg sm:text-xl font-bold text-slate-900 dark:text-slate-100">
{erro.titulo}
</h3>
<p className="text-xs sm:text-sm text-slate-600 dark:text-slate-400">
{erro.resumo}
</p>
</div>
<div className="flex items-center gap-3 self-end md:self-center shrink-0">
<span className="text-xs font-semibold text-amber-600 dark:text-amber-400 flex items-center gap-1">
{estaAberto ? "Ocultar detalhes" : "Ver diagnóstico & solução"}
</span>
<div className="p-2 rounded-full bg-slate-100 dark:bg-slate-800 text-slate-600 dark:text-slate-300">
{estaAberto ? (
<ChevronUp className="w-4 h-4" />
) : (
<ChevronDown className="w-4 h-4" />
)}
</div>
</div>
</div>
{/* Conteúdo Expandido */}
{estaAberto && (
<div className="px-5 sm:px-6 pb-6 pt-2 border-t border-slate-200 dark:border-slate-800/80 space-y-6">
{/* Snippet do Log Serial */}
<div>
<div className="flex items-center justify-between mb-1.5">
<span className="text-xs font-bold uppercase tracking-wider text-slate-500 dark:text-slate-400 flex items-center gap-1.5">
<Terminal className="w-3.5 h-3.5" />
Assinatura no Monitor Serial (Log Típico)
</span>
<button
type="button"
onClick={() =>
copiarParaClipboard(erro.logSerial, `log-${erro.id}`)
}
className="text-xs text-slate-400 hover:text-slate-200 inline-flex items-center gap-1 cursor-pointer"
>
{copiadoId === `log-${erro.id}` ? (
<>
<Check className="w-3 h-3 text-emerald-400" />
<span className="text-emerald-400 font-semibold">Copiado!</span>
</>
) : (
<>
<Copy className="w-3 h-3" />
<span>Copiar log</span>
</>
)}
</button>
</div>
<pre className="bg-slate-950 text-emerald-400 font-mono text-xs sm:text-sm p-3.5 rounded-2xl overflow-x-auto border border-slate-800 leading-relaxed">
{erro.logSerial}
</pre>
</div>
{/* Causa Raiz Técnica */}
<div>
<h4 className="text-sm font-bold text-slate-900 dark:text-slate-100 mb-1.5 flex items-center gap-1.5">
<Cpu className="w-4 h-4 text-amber-500" />
Causa Raiz no Silício / RTOS / Hardware
</h4>
<p className="text-xs sm:text-sm text-slate-700 dark:text-slate-300 leading-relaxed bg-slate-50 dark:bg-slate-900/50 p-4 rounded-2xl border border-slate-200 dark:border-slate-800">
{erro.causaRaiz}
</p>
</div>
{/* Comparativo Didático: Gambiarra vs Solução de Engenharia */}
<div className="grid md:grid-cols-2 gap-4">
{/* Abordagem Incorreta (Gambiarra) */}
<div className="p-4 sm:p-5 rounded-2xl bg-red-50/70 dark:bg-red-950/20 border border-red-200 dark:border-red-900/40 space-y-2">
<div className="flex items-center gap-2 text-red-700 dark:text-red-400 font-bold text-sm">
<XCircle className="w-4 h-4 shrink-0" />
<span>Abordagem Amadora ("Gambiarra")</span>
</div>
<p className="text-xs font-semibold text-slate-800 dark:text-slate-200">
{erro.gambiarra.titulo}
</p>
<p className="text-xs text-slate-600 dark:text-slate-400">
{erro.gambiarra.descricao}
</p>
<div className="text-xs text-red-800 dark:text-red-300 bg-red-100/80 dark:bg-red-900/40 p-2.5 rounded-xl font-medium">
<strong>Consequência:</strong> {erro.gambiarra.consequencia}
</div>
</div>
{/* Solução de Engenharia (Boas Práticas) */}
<div className="p-4 sm:p-5 rounded-2xl bg-emerald-50/70 dark:bg-emerald-950/20 border border-emerald-200 dark:border-emerald-900/40 space-y-2">
<div className="flex items-center gap-2 text-emerald-700 dark:text-emerald-400 font-bold text-sm">
<CheckCircle2 className="w-4 h-4 shrink-0" />
<span>Solução Profissional (Engenharia)</span>
</div>
<p className="text-xs font-semibold text-slate-800 dark:text-slate-200">
{erro.solucaoEngenharia.titulo}
</p>
<ul className="space-y-1.5 pt-1">
{erro.solucaoEngenharia.passos.map((passo, idx) => (
<li
key={idx}
className="text-xs text-slate-700 dark:text-slate-300 flex items-start gap-1.5"
>
<span className="text-emerald-500 font-bold">✓</span>
<span>{passo}</span>
</li>
))}
</ul>
</div>
</div>
{/* Bloco de Código (Arduino IDE vs ESP-IDF) */}
<div className="space-y-2">
<div className="flex flex-wrap items-center justify-between gap-2">
<div className="flex items-center gap-1 bg-slate-200 dark:bg-slate-800 p-1 rounded-xl">
<button
type="button"
onClick={() => setLang(erro.id, "arduino")}
className={`text-xs px-3 py-1 rounded-lg font-bold transition-all cursor-pointer ${
lang === "arduino"
? "bg-white dark:bg-slate-900 text-teal-600 dark:text-teal-400 shadow-xs"
: "text-slate-600 dark:text-slate-400 hover:text-slate-900"
}`}
>
Arduino C++
</button>
<button
type="button"
onClick={() => setLang(erro.id, "espidf")}
className={`text-xs px-3 py-1 rounded-lg font-bold transition-all cursor-pointer ${
lang === "espidf"
? "bg-white dark:bg-slate-900 text-red-600 dark:text-red-400 shadow-xs"
: "text-slate-600 dark:text-slate-400 hover:text-slate-900"
}`}
>
ESP-IDF (C)
</button>
</div>
<button
type="button"
onClick={() =>
copiarParaClipboard(codigoAtual, `code-${erro.id}`)
}
className="text-xs px-3 py-1.5 rounded-xl bg-slate-100 dark:bg-slate-800 text-slate-700 dark:text-slate-300 hover:bg-slate-200 dark:hover:bg-slate-700 inline-flex items-center gap-1.5 border border-slate-250 dark:border-slate-700 cursor-pointer font-medium"
>
{copiadoId === `code-${erro.id}` ? (
<>
<Check className="w-3.5 h-3.5 text-emerald-500" />
<span className="text-emerald-500 font-semibold">Copiado!</span>
</>
) : (
<>
<Copy className="w-3.5 h-3.5" />
<span>Copiar código</span>
</>
)}
</button>
</div>
<pre className="bg-slate-950 text-slate-200 font-mono text-xs sm:text-sm p-4 rounded-2xl overflow-x-auto border border-slate-800 leading-relaxed">
{codigoAtual}
</pre>
</div>
{/* Dica Pro */}
<div className="p-3.5 rounded-2xl bg-amber-500/10 border border-amber-500/30 flex items-start gap-2.5">
<Wrench className="w-4 h-4 text-amber-600 dark:text-amber-400 shrink-0 mt-0.5" />
<p className="text-xs text-amber-900 dark:text-amber-200">
<strong>Dica Pro de Engenharia:</strong> {erro.dicaPro}
</p>
</div>
</div>
)}
</article>
);
})
)}
</section>
{/* 4. CHECKLIST DE PREVENÇÃO EM HARDWARE E FIRMWARE */}
<section className="mt-16 bg-gradient-to-br from-slate-900 to-slate-950 text-white rounded-3xl p-6 sm:p-10 border border-slate-800 shadow-xl">
<div className="flex items-center gap-3 mb-6">
<div className="p-3 rounded-2xl bg-amber-500/20 text-amber-400 border border-amber-500/30">
<ShieldAlert className="w-6 h-6" />
</div>
<div>
<h2 className="text-xl sm:text-2xl font-display font-bold">
Checklist de Prevenção para Produção
</h2>
<p className="text-xs sm:text-sm text-slate-400">
Itens essenciais para validar antes de enviar seu hardware ou firmware para campo.
</p>
</div>
</div>
<div className="grid md:grid-cols-2 gap-6 text-xs sm:text-sm">
<div className="space-y-3 bg-slate-800/50 p-5 rounded-2xl border border-slate-700/60">
<h3 className="font-bold text-amber-400 uppercase tracking-wider text-xs flex items-center gap-2">
<Zap className="w-4 h-4" /> Checklist de Hardware & Alimentação
</h3>
<ul className="space-y-2.5 text-slate-300">
<li className="flex items-start gap-2">
<CheckCircle2 className="w-4 h-4 text-emerald-400 shrink-0 mt-0.5" />
<span>Capacitor de 10µF ~ 100µF Low-ESR no VCC + cerâmico de 100nF próximo ao chip.</span>
</li>
<li className="flex items-start gap-2">
<CheckCircle2 className="w-4 h-4 text-emerald-400 shrink-0 mt-0.5" />
<span>LDO dimensionado para suportar picos de corrente de no mínimo 600mA a 1A com folga térmica.</span>
</li>
<li className="flex items-start gap-2">
<CheckCircle2 className="w-4 h-4 text-emerald-400 shrink-0 mt-0.5" />
<span>Nenhum periférico forçando níveis lógicos em Strapping Pins no momento do boot.</span>
</li>
<li className="flex items-start gap-2">
<CheckCircle2 className="w-4 h-4 text-emerald-400 shrink-0 mt-0.5" />
<span>Capacitor de 10µF ou 1µF no pino EN (RST) para auto-reset estável no upload.</span>
</li>
</ul>
</div>
<div className="space-y-3 bg-slate-800/50 p-5 rounded-2xl border border-slate-700/60">
<h3 className="font-bold text-purple-400 uppercase tracking-wider text-xs flex items-center gap-2">
<Layers className="w-4 h-4" /> Checklist de Firmware & FreeRTOS
</h3>
<ul className="space-y-2.5 text-slate-300">
<li className="flex items-start gap-2">
<CheckCircle2 className="w-4 h-4 text-emerald-400 shrink-0 mt-0.5" />
<span>Todas as tarefas FreeRTOS cedem CPU através de vTaskDelay ou filas/semáforos.</span>
</li>
<li className="flex items-start gap-2">
<CheckCircle2 className="w-4 h-4 text-emerald-400 shrink-0 mt-0.5" />
<span>Todas as ISRs decoradas com `IRAM_ATTR` e sem chamadas bloqueantes.</span>
</li>
<li className="flex items-start gap-2">
<CheckCircle2 className="w-4 h-4 text-emerald-400 shrink-0 mt-0.5" />
<span>Sem uso contínuo de objetos `String` do Arduino em loops para evitar fragmentação de Heap.</span>
</li>
<li className="flex items-start gap-2">
<CheckCircle2 className="w-4 h-4 text-emerald-400 shrink-0 mt-0.5" />
<span>Sincronização de horário SNTP antes de iniciar conexões criptografadas TLS/HTTPS.</span>
</li>
</ul>
</div>
</div>
</section>
</main>
<Footer />
</div>
);
}
+54
View File
@@ -0,0 +1,54 @@
import { paginaMeta, jsonLd } from "@/lib/seo";
import { categoriasGlossario } from "@/lib/glossarioData";
export const metadata = paginaMeta({
titulo: "Glossário Técnico do ESP32: Termos e Conceitos",
descricao: "Mais de 50 termos e conceitos essenciais do ecossistema ESP32 explicados em português: GPIO, Strapping Pins, ULP, PSRAM, FreeRTOS, eFuse, Deep Sleep, OTA, Secure Boot, ECDSA, Matter e Thread.",
caminho: "/glossario",
keywords: [
"glossário ESP32",
"o que é GPIO",
"o que é strapping pins",
"o que é ULP ESP32",
"o que é PSRAM ESP32",
"termos técnicos microcontrolador",
"conceitos ESP32 português",
],
});
export default function GlossarioLayout({ children }) {
return (
<>
<script
type="application/ld+json"
dangerouslySetInnerHTML={{
__html: JSON.stringify(jsonLd.glossario(categoriasGlossario)),
}}
/>
<script
type="application/ld+json"
dangerouslySetInnerHTML={{
__html: JSON.stringify(
jsonLd.trilha([
{ nome: "Início", caminho: "/" },
{ nome: "Glossário Técnico", caminho: "/glossario" },
])
),
}}
/>
<script
type="application/ld+json"
dangerouslySetInnerHTML={{
__html: JSON.stringify(
jsonLd.aplicacaoWeb({
nome: "Glossário Técnico do ESP32",
descricao: "Enciclopédia de termos técnicos e conceitos de hardware e software do ecossistema ESP32.",
caminho: "/glossario",
})
),
}}
/>
{children}
</>
);
}
+518 -475
View File
File diff suppressed because it is too large. Load diff
+392 -118
View File
@@ -5,81 +5,197 @@ import Image from "next/image";
import { import {
SlidersHorizontal, SlidersHorizontal,
ShoppingBag, ShoppingBag,
Table2,
Cpu, Cpu,
Layers, Layers,
Scale, Scale,
FileText, BookOpen,
Code, AlertTriangle,
RefreshCw,
Globe,
ArrowRight,
Terminal, Terminal,
Compass, Compass,
Info ArrowRight,
Sparkles,
CheckCircle2,
Zap,
ExternalLink,
Info,
Radio,
ShieldCheck,
Code,
HelpCircle
} from "lucide-react"; } from "lucide-react";
export default function Home() { export default function Home() {
const resources = [ // Estatísticas da Plataforma
const stats = [
{ value: "12", label: "Séries Documentadas", desc: "Do ESP32 clássico ao P4 e C61" },
{ value: "8", label: "Frameworks & SDKs", desc: "ESP-IDF, Arduino, MicroPython, etc." },
{ value: "30+", label: "Guias de Diagnóstico", desc: "Soluções para bootloops, brownouts e WDT" },
{ value: "60+", label: "Termos Técnicos", desc: "Glossário completo de arquitetura e IoT" },
];
// Ferramentas da Plataforma (100% Clicáveis)
const tools = [
{ {
name: "Seletor Inteligente", title: "Seletor Inteligente",
description: "Responda perguntas rápidas sobre seu projeto e descubra a série ESP32 ideal em segundos.", badge: "Recomendado",
badgeColor: "bg-purple-100 text-purple-700 dark:bg-purple-950/60 dark:text-purple-300",
description: "Responda algumas perguntas sobre seu projeto e descubra o chip ESP32 ideal com análise técnica e justificativa.",
href: "/seletor",
icon: <SlidersHorizontal className="w-6 h-6" />, icon: <SlidersHorizontal className="w-6 h-6" />,
colorClass: "text-purple-600 bg-purple-50 dark:bg-purple-950/40 dark:text-purple-400" iconBg: "text-purple-600 bg-purple-50 dark:bg-purple-950/40 dark:text-purple-400",
ctaText: "Iniciar Quiz de Seleção",
}, },
{ {
name: "Catálogo de Placas", title: "Diagnóstico de Falhas",
description: "Encontre placas de desenvolvimento ESP32 verificadas com filtros avançados e links seguros.", badge: "30+ Soluções",
icon: <ShoppingBag className="w-6 h-6" />, badgeColor: "bg-amber-100 text-amber-700 dark:bg-amber-950/60 dark:text-amber-300",
colorClass: "text-blue-600 bg-blue-50 dark:bg-blue-950/40 dark:text-blue-400" description: "Encontre a causa e o passo a passo para corrigir erros frequentes como Brownout, Guru Meditation, Bootloops e falhas de Flash.",
href: "/diagnostico",
icon: <AlertTriangle className="w-6 h-6" />,
iconBg: "text-amber-600 bg-amber-50 dark:bg-amber-950/40 dark:text-amber-400",
ctaText: "Resolver Erro no Código",
}, },
{ {
name: "Especificações Organizadas", title: "Comparador de Séries",
description: "Especificações técnicas completas de cada microcontrolador organizadas para fácil leitura.", badge: "Lado a Lado",
icon: <Table2 className="w-6 h-6" />, badgeColor: "bg-indigo-100 text-indigo-700 dark:bg-indigo-950/60 dark:text-indigo-300",
colorClass: "text-pink-600 bg-pink-50 dark:bg-pink-950/40 dark:text-pink-400" description: "Compare especificações de até 4 séries simultaneamente: núcleos, clock, memória SRAM/PSRAM, interfaces e conexões.",
}, href: "/comparacao",
{
name: "Diagramas de Conexões",
description: "Visualize diagramas de pinos interativos e mapeamentos de periféricos de forma dinâmica.",
icon: <Cpu className="w-6 h-6" />,
colorClass: "text-emerald-600 bg-emerald-50 dark:bg-emerald-950/40 dark:text-emerald-400"
},
{
name: "Suporte a Frameworks",
description: "Encontre casos de uso, documentações e snippets para ESP-IDF, Arduino e MicroPython.",
icon: <Layers className="w-6 h-6" />,
colorClass: "text-amber-600 bg-amber-50 dark:bg-amber-950/40 dark:text-amber-400"
},
{
name: "Comparação de Modelos",
description: "Compare lado a lado as especificações de múltiplos chips em uma visualização única.",
icon: <Scale className="w-6 h-6" />, icon: <Scale className="w-6 h-6" />,
colorClass: "text-cyan-600 bg-cyan-50 dark:bg-cyan-950/40 dark:text-cyan-400" iconBg: "text-indigo-600 bg-indigo-50 dark:bg-indigo-950/40 dark:text-indigo-400",
ctaText: "Comparar Modelos",
}, },
{ {
name: "Links para Documentação", title: "Explorador de Séries & Pinouts",
description: "Acesso direto aos datasheets oficiais, manuais de referência e guias da Espressif.", badge: "12 Chips",
icon: <FileText className="w-6 h-6" />, badgeColor: "bg-blue-100 text-blue-700 dark:bg-blue-950/60 dark:text-blue-300",
colorClass: "text-red-600 bg-red-50 dark:bg-red-950/40 dark:text-red-400" description: "Consulte diagramas interativos de conexões, mapas de periféricos, datasheets oficiais e restrições de pinos (strapping, ADC2).",
href: "/series",
icon: <Cpu className="w-6 h-6" />,
iconBg: "text-blue-600 bg-blue-50 dark:bg-blue-950/40 dark:text-blue-400",
ctaText: "Ver Todas as Séries",
}, },
{ {
name: "Exemplos de Código", title: "Catálogo de Placas",
description: "Gabaritos e snippets funcionais de código para iniciar seus projetos rapidamente.", badge: "Hardware Real",
icon: <Code className="w-6 h-6" />, badgeColor: "bg-emerald-100 text-emerald-700 dark:bg-emerald-950/60 dark:text-emerald-300",
colorClass: "text-indigo-600 bg-indigo-50 dark:bg-indigo-950/40 dark:text-indigo-400" description: "Filtre placas de desenvolvimento (DevKit, Xiao, Wemos, LilyGO) por série, interface USB, sensores integrados e links de compra.",
href: "/catalogo",
icon: <ShoppingBag className="w-6 h-6" />,
iconBg: "text-emerald-600 bg-emerald-50 dark:bg-emerald-950/40 dark:text-emerald-400",
ctaText: "Explorar Placas Físicas",
}, },
{ {
name: "Informações Atualizadas", title: "Frameworks & SDKs",
description: "Dados sempre revisados e sincronizados com os lançamentos mais recentes de silício.", badge: "C++, Python & Rust",
icon: <RefreshCw className="w-6 h-6" />, badgeColor: "bg-teal-100 text-teal-700 dark:bg-teal-950/60 dark:text-teal-300",
colorClass: "text-teal-600 bg-teal-50 dark:bg-teal-950/40 dark:text-teal-400" description: "Documentação de frameworks oficiais e comunitários: ESP-IDF, Arduino Core, MicroPython, ESP-Matter, ESP-WHO e mais.",
href: "/frameworks",
icon: <Terminal className="w-6 h-6" />,
iconBg: "text-teal-600 bg-teal-50 dark:bg-teal-950/40 dark:text-teal-400",
ctaText: "Acessar Guias de SDK",
}, },
{ {
name: "Interface em Português", title: "Glossário Técnico",
description: "Navegação, termos técnicos e explicações adaptadas ao nosso idioma nativo.", badge: "60+ Conceitos",
icon: <Globe className="w-6 h-6" />, badgeColor: "bg-pink-100 text-pink-700 dark:bg-pink-950/60 dark:text-pink-300",
colorClass: "text-sky-600 bg-sky-50 dark:bg-sky-950/40 dark:text-sky-400" description: "Dicionário de termos técnicos essenciais: entenda RTOS, NVS, Partições OTA, eFuse, Brownout, RISC-V e protocolos industriais.",
href: "/glossario",
icon: <BookOpen className="w-6 h-6" />,
iconBg: "text-pink-600 bg-pink-50 dark:bg-pink-950/40 dark:text-pink-400",
ctaText: "Consultar Termos",
},
{
title: "Sobre a Plataforma",
badge: "Comunidade",
badgeColor: "bg-slate-200 text-slate-700 dark:bg-slate-800 dark:text-slate-300",
description: "Conheça a missão da ESPDocs, a autoria de Lucas Rayan Guerra e a parceria educacional com o blog Ciência Embarcada.",
href: "/sobre",
icon: <Info className="w-6 h-6" />,
iconBg: "text-slate-650 bg-slate-100 dark:bg-slate-800 dark:text-slate-350",
ctaText: "Conhecer o Projeto",
},
];
// Chips em Destaque
const featuredChips = [
{
name: "ESP32",
slug: "esp32",
badge: "Padrão da Indústria",
arch: "Xtensa LX6 Dual-Core @ 240 MHz",
wireless: "Wi-Fi 4 (802.11 b/g/n) + BLE 4.2 / Clássico",
highlights: "Suporte maduro, 36 pinos GPIO, ADC duplo, DAC de 8 bits",
cor: "#007bff",
},
{
name: "ESP32-S3",
slug: "esp32-s3",
badge: "IA & USB Nativo",
arch: "Xtensa LX7 Dual-Core @ 240 MHz",
wireless: "Wi-Fi 4 + BLE 5.0 (Long Range)",
highlights: "Instruções vetoriais para IA, USB OTG nativo, até 45 GPIOs",
cor: "#fd7e14",
},
{
name: "ESP32-C6",
slug: "esp32-c6",
badge: "Wi-Fi 6 + Zigbee/Matter",
arch: "RISC-V 32-bit Single-Core @ 160 MHz + LP Core",
wireless: "Wi-Fi 6 (ax) + BLE 5.3 + Zigbee 3.0 & Thread (802.15.4)",
highlights: "Protocolo Matter, Target Wake Time (TWT), segurança de hardware",
cor: "#17a2b8",
},
{
name: "ESP32-C3",
slug: "esp32-c3",
badge: "Custo-Benefício RISC-V",
arch: "RISC-V 32-bit Single-Core @ 160 MHz",
wireless: "Wi-Fi 4 + BLE 5.0 (Mesh & Long Range)",
highlights: "Substituto direto do ESP8266, pinout compacto, baixo consumo",
cor: "#20c997",
},
{
name: "ESP32-P4",
slug: "esp32-p4",
badge: "Superprocessador 400MHz",
arch: "Dual-Core RISC-V HP @ 400 MHz + LP Core @ 40 MHz",
wireless: "Sem rádio interno (Focado em processamento multimídia)",
highlights: "MIPI-CSI/DSI, codificador H.264, Ethernet 100M, 50+ GPIOs",
cor: "#ffc107",
},
{
name: "ESP32-H2",
slug: "esp32-h2",
badge: "Zigbee & Thread",
arch: "RISC-V 32-bit Single-Core @ 96 MHz",
wireless: "IEEE 802.15.4 (Zigbee 3.0, Thread) + BLE 5.3",
highlights: "Automação residencial ultrabaixo consumo, nós finais de rede",
cor: "#6c757d",
},
];
// Problemas Comuns do Diagnóstico
const commonErrors = [
{
title: "Brownout detector was triggered",
cause: "Queda de tensão súbita na alimentação ao inicializar o Wi-Fi",
solution: "Adicionar capacitor de desacoplamento de 10µF–100µF e usar fonte externa de 5V 1A+",
},
{
title: "Guru Meditation Error: IllegalInstruction",
cause: "Acesso a ponteiro nulo, estouro de pilha (stack overflow) ou interrupção incorreta",
solution: "Inspecione o Backtrace usando o addr2line e aumente o tamanho da stack da task FreeRTOS",
},
{
title: "ADC2 leitura inválida com Wi-Fi",
cause: "O rádio Wi-Fi bloqueia o conversor analógico-digital ADC2 enquanto está ativo",
solution: "Utilize exclusivamente pinos do ADC1 (GPIOs 32 a 39 no ESP32) para sensores analógicos",
},
{
title: "Flash Read err / Bootloader falhou",
cause: "Tensão de Flash incorreta (1.8V vs 3.3V) ou pino de strapping (GPIO 0, 2, 12, 15) travado",
solution: "Verifique resistores pull-up/pull-down nos pinos de boot e confirme a frequência de Flash no esptool",
}, },
]; ];
@@ -87,25 +203,33 @@ export default function Home() {
<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"> <div className="bg-gradient-to-br from-slate-100 via-slate-50 to-purple-100/40 dark:from-slate-950 dark:via-slate-950 dark:to-slate-900 min-h-screen text-slate-900 dark:text-slate-100 transition-colors duration-300">
<Header /> <Header />
<main className="px-6 pt-16 pb-24 max-w-7xl mx-auto"> <main className="px-4 sm:px-6 pt-10 md:pt-16 pb-24 max-w-7xl mx-auto">
{/* Hero Section */} {/* Hero Section */}
<section className="text-center mb-24 max-w-5xl mx-auto"> <section className="text-center mb-20 max-w-5xl mx-auto">
<h1 className="text-5xl md:text-7xl lg:text-8xl font-display font-extrabold bg-gradient-to-r from-blue-500 via-purple-500 to-pink-500 bg-clip-text text-transparent mb-6 tracking-tight leading-tight select-none"> {/* Pill Tag */}
<div className="inline-flex items-center gap-2 px-4 py-1.5 rounded-full bg-purple-50 dark:bg-purple-950/50 border border-purple-200 dark:border-purple-800/80 text-purple-700 dark:text-purple-300 text-xs md:text-sm font-bold mb-6 shadow-xs">
<Sparkles className="w-3.5 h-3.5 text-purple-600 dark:text-purple-400" />
<span>O Maior Hub Técnico de ESP32 em Português</span>
</div>
<h1 className="text-4xl sm:text-6xl md:text-7xl lg:text-8xl font-display font-extrabold bg-gradient-to-r from-blue-500 via-purple-500 to-pink-500 bg-clip-text text-transparent mb-6 tracking-tight leading-[1.1] select-none">
ESPDocs ESPDocs
</h1> </h1>
<h2 className="text-xl md:text-3xl text-slate-800 dark:text-slate-200 font-bold mb-6 max-w-3xl mx-auto leading-snug"> <h2 className="text-xl sm:text-2xl md:text-3xl text-slate-850 dark:text-slate-100 font-extrabold mb-6 max-w-3xl mx-auto leading-snug">
Organização e acesso facilitado ao ecossistema ESP32 Tudo o que você precisa para dominar o ecossistema ESP32
</h2> </h2>
<p className="text-base md:text-lg text-slate-500 dark:text-slate-400 max-w-2xl mx-auto leading-relaxed mb-12"> <p className="text-sm sm:text-base md:text-lg text-slate-650 dark:text-slate-350 max-w-3xl mx-auto leading-relaxed mb-10">
Plataforma independente criada para desenvolvedores que buscam comparar especificações, consultar pinouts, selecionar chips ideais e acessar referências em português de forma rápida e intuitiva. Chega de perder tempo traduzindo datasheets dispersos ou decifrando bootloops no escuro.
Compare 12 séries de chips, diagnostique erros comuns de firmware, descubra o ESP32 ideal
para o seu projeto e consulte pinouts interativos 100% em português brasileiro.
</p> </p>
{/* Botões de Ação */} {/* Botões de Ação Hero */}
<div className="flex flex-wrap gap-4 justify-center items-center"> <div className="flex flex-wrap gap-3.5 justify-center items-center mb-16">
<Link <Link
className="group inline-flex items-center gap-2.5 bg-gradient-to-r from-purple-600 to-indigo-600 text-white px-7 py-3.5 rounded-xl font-bold text-sm shadow-lg hover:shadow-purple-500/20 hover:-translate-y-0.5 transition-all duration-300 cursor-pointer active:scale-95" className="group inline-flex items-center gap-2.5 bg-gradient-to-r from-purple-600 to-indigo-600 text-white px-7 py-3.5 rounded-xl font-bold text-sm shadow-md hover:shadow-purple-500/25 hover:-translate-y-0.5 transition-all duration-200 cursor-pointer active:scale-95"
href="/seletor" href="/seletor"
> >
<Compass className="w-4 h-4" /> <Compass className="w-4 h-4" />
@@ -114,67 +238,226 @@ export default function Home() {
</Link> </Link>
<Link <Link
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" className="group inline-flex items-center gap-2.5 bg-amber-600 hover:bg-amber-700 text-white px-7 py-3.5 rounded-xl font-bold text-sm shadow-md hover:-translate-y-0.5 transition-all duration-200 cursor-pointer active:scale-95"
href="/diagnostico"
>
<AlertTriangle className="w-4 h-4" />
<span>Diagnosticar Falhas</span>
</Link>
<Link
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-200 cursor-pointer active:scale-95"
href="/series" href="/series"
> >
<Cpu className="w-4 h-4" /> <Cpu className="w-4 h-4" />
<span>Explorar Séries</span> <span>Explorar 12 Séries</span>
</Link> </Link>
<Link <Link
className="group inline-flex items-center gap-2.5 bg-white dark:bg-slate-900 text-slate-700 dark:text-slate-300 px-7 py-3.5 rounded-xl font-bold text-sm shadow-xs border border-slate-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" 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-200 cursor-pointer active:scale-95"
href="/frameworks" href="/comparacao"
> >
<Terminal className="w-4 h-4" /> <Scale className="w-4 h-4" />
<span>Frameworks</span> <span>Comparar Chips</span>
</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-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" />
<span>Placas</span>
</Link> </Link>
</div> </div>
</section>
{/* Recursos Section */} {/* Faixa de Estatísticas */}
<section className="mb-24"> <div className="grid grid-cols-2 md:grid-cols-4 gap-4 max-w-4xl mx-auto">
<div className="text-center mb-16"> {stats.map((stat) => (
<h2 className="text-3xl md:text-4xl font-display font-extrabold text-slate-800 dark:text-slate-100 mb-4">
Recursos da Plataforma
</h2>
<p className="text-base text-slate-500 dark:text-slate-400 max-w-xl mx-auto leading-relaxed">
Conjunto completo de utilitários projetados para maximizar a produtividade e eliminar atritos no desenvolvimento de firmware.
</p>
</div>
<div className="grid md:grid-cols-2 lg:grid-cols-3 gap-6">
{resources.map((resource) => (
<div <div
key={resource.name} key={stat.label}
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" className="bg-white dark:bg-slate-900/60 backdrop-blur-md p-4 sm:p-5 rounded-2xl border border-slate-300 dark:border-slate-800/80 shadow-xs text-center"
> >
<div className="flex items-start gap-4"> <div className="text-2xl sm:text-3xl font-display font-extrabold bg-gradient-to-r from-blue-600 to-purple-600 dark:from-blue-400 dark:to-purple-400 bg-clip-text text-transparent mb-1">
<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}`}> {stat.value}
{resource.icon}
</div> </div>
<div className="flex-1"> <div className="text-xs sm:text-sm font-bold text-slate-850 dark:text-slate-100 mb-0.5">
<h3 className="text-base font-bold text-slate-800 dark:text-slate-100 mb-2 group-hover:text-purple-600 dark:group-hover:text-purple-400 transition-colors"> {stat.label}
{resource.name}
</h3>
<p className="text-sm text-slate-500 dark:text-slate-400 leading-relaxed">
{resource.description}
</p>
</div> </div>
<div className="text-[11px] text-slate-500 dark:text-slate-400 leading-tight">
{stat.desc}
</div> </div>
</div> </div>
))} ))}
</div> </div>
</section> </section>
{/* Ciência Embarcada Section */} {/* Seção 2: Central de Ferramentas & Recursos (Cards 100% Clicáveis) */}
<section className="mb-24">
<div className="text-center mb-12">
<h2 className="text-2xl sm:text-3xl md:text-4xl font-display font-extrabold text-slate-850 dark:text-slate-100 mb-3">
Central de Ferramentas e Guias
</h2>
<p className="text-sm sm:text-base text-slate-650 dark:text-slate-350 max-w-2xl mx-auto leading-relaxed">
Escolha o que você deseja fazer hoje. Cada ferramenta foi desenvolvida para acelerar seu desenvolvimento e eliminar gargalos.
</p>
</div>
<div className="grid sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-5">
{tools.map((tool) => (
<Link
key={tool.title}
href={tool.href}
className="group flex flex-col justify-between bg-white dark:bg-slate-900/50 backdrop-blur-md p-6 rounded-2xl border border-slate-300 dark:border-slate-800/80 hover:border-purple-500 dark:hover:border-purple-400 shadow-xs hover:shadow-xl hover:shadow-purple-500/5 hover:-translate-y-1 transition-all duration-300 cursor-pointer"
>
<div>
{/* Top Bar: Icon + Badge */}
<div className="flex items-center justify-between gap-2 mb-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 ${tool.iconBg}`}>
{tool.icon}
</div>
<span className={`text-[11px] font-bold px-2.5 py-1 rounded-full ${tool.badgeColor}`}>
{tool.badge}
</span>
</div>
{/* Content */}
<h3 className="text-base font-bold text-slate-850 dark:text-slate-100 mb-2 group-hover:text-purple-600 dark:group-hover:text-purple-400 transition-colors">
{tool.title}
</h3>
<p className="text-xs sm:text-sm text-slate-650 dark:text-slate-350 leading-relaxed mb-6">
{tool.description}
</p>
</div>
{/* Bottom Action Link */}
<div className="pt-3 border-t border-slate-100 dark:border-slate-800/60 flex items-center justify-between text-xs font-bold text-purple-600 dark:text-purple-400">
<span>{tool.ctaText}</span>
<ArrowRight className="w-3.5 h-3.5 transform group-hover:translate-x-1 transition-transform" />
</div>
</Link>
))}
</div>
</section>
{/* Seção 3: Explore as Séries em Destaque */}
<section className="mb-24">
<div className="flex flex-col md:flex-row md:items-end justify-between mb-10 gap-4">
<div>
<div className="inline-flex items-center gap-1.5 text-xs font-bold uppercase tracking-wider text-purple-600 dark:text-purple-400 mb-2">
<Cpu className="w-4 h-4" />
<span>Hardware & Silício</span>
</div>
<h2 className="text-2xl sm:text-3xl md:text-4xl font-display font-extrabold text-slate-850 dark:text-slate-100">
Séries Populares de ESP32
</h2>
<p className="text-sm text-slate-650 dark:text-slate-350 mt-1">
Conheça as principais famílias de chips da Espressif, da arquitetura aos protocolos sem fio.
</p>
</div>
<Link
href="/series"
className="inline-flex items-center gap-2 text-sm font-bold text-purple-600 dark:text-purple-400 hover:text-purple-700 dark:hover:text-purple-300 shrink-0"
>
<span>Ver todas as 12 Séries</span>
<ArrowRight className="w-4 h-4" />
</Link>
</div>
<div className="grid md:grid-cols-2 lg:grid-cols-3 gap-6">
{featuredChips.map((chip) => (
<Link
key={chip.slug}
href={`/series/${chip.slug}`}
className="group bg-white dark:bg-slate-900/50 backdrop-blur-md p-6 rounded-2xl border border-slate-300 dark:border-slate-800/80 hover:border-purple-500 dark:hover:border-purple-400 shadow-xs hover:shadow-lg transition-all duration-300 flex flex-col justify-between"
>
<div>
<div className="flex items-center justify-between mb-3">
<h3 className="text-xl font-display font-extrabold text-slate-850 dark:text-slate-100 group-hover:text-purple-600 dark:group-hover:text-purple-400 transition-colors">
{chip.name}
</h3>
<span className="text-[11px] font-bold px-2.5 py-0.5 rounded-full bg-slate-100 dark:bg-slate-800 text-slate-700 dark:text-slate-300">
{chip.badge}
</span>
</div>
<div className="space-y-2 mb-5">
<div className="flex items-start gap-2 text-xs text-slate-650 dark:text-slate-350">
<Zap className="w-3.5 h-3.5 text-amber-500 shrink-0 mt-0.5" />
<span className="font-semibold">{chip.arch}</span>
</div>
<div className="flex items-start gap-2 text-xs text-slate-650 dark:text-slate-350">
<Radio className="w-3.5 h-3.5 text-blue-500 shrink-0 mt-0.5" />
<span>{chip.wireless}</span>
</div>
<div className="flex items-start gap-2 text-xs text-slate-500 dark:text-slate-400">
<CheckCircle2 className="w-3.5 h-3.5 text-emerald-500 shrink-0 mt-0.5" />
<span>{chip.highlights}</span>
</div>
</div>
</div>
<div className="pt-4 border-t border-slate-100 dark:border-slate-800/60 flex items-center justify-between text-xs font-bold text-purple-600 dark:text-purple-400">
<span>Ver Pinout e Especificações</span>
<ArrowRight className="w-3.5 h-3.5 transform group-hover:translate-x-1 transition-transform" />
</div>
</Link>
))}
</div>
</section>
{/* Seção 4: Problemas Comuns & Diagnóstico Rápido */}
<section className="mb-24">
<div className="bg-gradient-to-br from-indigo-50/70 via-purple-50/50 to-white dark:from-slate-900/60 dark:via-slate-900/40 dark:to-purple-950/20 rounded-3xl p-6 sm:p-10 border border-indigo-200/80 dark:border-slate-800 shadow-xs">
<div className="flex flex-col lg:flex-row lg:items-center justify-between gap-6 mb-8">
<div>
<div className="inline-flex items-center gap-1.5 text-xs font-bold uppercase tracking-wider text-amber-600 dark:text-amber-400 mb-2">
<AlertTriangle className="w-4 h-4" />
<span>Resolução Imediata</span>
</div>
<h2 className="text-2xl sm:text-3xl font-display font-extrabold text-slate-850 dark:text-slate-100">
Enfrentando Erros ou Travamentos?
</h2>
<p className="text-sm text-slate-650 dark:text-slate-350 mt-1 max-w-xl">
Acesse guias práticos com causas comprovadas e soluções passo a passo para as falhas mais comuns de hardware e firmware.
</p>
</div>
<Link
href="/diagnostico"
className="inline-flex items-center gap-2 bg-gradient-to-r from-amber-500 to-orange-500 text-white px-6 py-3 rounded-xl font-bold text-sm shadow-md hover:-translate-y-0.5 transition-all duration-200 shrink-0 self-start lg:self-auto cursor-pointer"
>
<AlertTriangle className="w-4 h-4" />
<span>Abrir Guia de 30+ Erros</span>
</Link>
</div>
<div className="grid md:grid-cols-2 gap-4">
{commonErrors.map((err) => (
<Link
key={err.title}
href="/diagnostico"
className="group bg-white dark:bg-slate-900/80 p-5 rounded-2xl border border-slate-200 dark:border-slate-800 hover:border-amber-400 dark:hover:border-amber-400 transition-all duration-200 flex flex-col justify-between"
>
<div>
<div className="font-mono text-xs font-bold text-amber-600 dark:text-amber-400 mb-2 group-hover:underline">
{err.title}
</div>
<p className="text-xs text-slate-650 dark:text-slate-350 mb-3">
<strong className="text-slate-800 dark:text-slate-200">Causa provável: </strong>
{err.cause}
</p>
<p className="text-xs text-slate-500 dark:text-slate-400">
<strong className="text-emerald-700 dark:text-emerald-400">Solução rápida: </strong>
{err.solution}
</p>
</div>
<div className="mt-4 pt-3 border-t border-slate-100 dark:border-slate-800/60 flex items-center justify-between text-[11px] font-bold text-amber-600 dark:text-amber-400">
<span>Ver diagnóstico completo</span>
<ArrowRight className="w-3.5 h-3.5 transform group-hover:translate-x-1 transition-transform" />
</div>
</Link>
))}
</div>
</div>
</section>
{/* Seção 5: Ciência Embarcada & Curadoria */}
<section className="mb-8"> <section className="mb-8">
<div className="bg-gradient-to-br from-white to-purple-50/50 dark:from-slate-900/40 dark:to-purple-950/10 rounded-3xl shadow-xs border border-slate-300 dark:border-slate-800/80 p-8 md:p-12 relative overflow-hidden"> <div className="bg-gradient-to-br from-white to-purple-50/50 dark:from-slate-900/40 dark:to-purple-950/10 rounded-3xl shadow-xs border border-slate-300 dark:border-slate-800/80 p-8 md:p-12 relative overflow-hidden">
{/* Background soft glow decoration */} {/* Background soft glow decoration */}
@@ -196,7 +479,7 @@ export default function Home() {
/> />
</a> </a>
<p className="text-lg font-bold text-slate-800 dark:text-slate-200 mb-3"> <h2 className="text-xl sm:text-2xl font-bold text-slate-850 dark:text-slate-100 mb-3">
Conteúdo e Curadoria por{' '} Conteúdo e Curadoria por{' '}
<a <a
href="https://cienciaembarcada.com.br" href="https://cienciaembarcada.com.br"
@@ -206,9 +489,9 @@ export default function Home() {
> >
Ciência Embarcada Ciência Embarcada
</a> </a>
</p> </h2>
<p className="text-sm text-slate-500 dark:text-slate-400 leading-relaxed mb-8 max-w-xl"> <p className="text-sm text-slate-650 dark:text-slate-350 leading-relaxed mb-8 max-w-xl">
Acesse artigos profundos sobre programação de microcontroladores, design de hardware, Internet das Coisas (IoT), laboratórios práticos e projetos reais em nosso blog parceiro oficial. Acesse artigos profundos sobre programação de microcontroladores, design de hardware, Internet das Coisas (IoT), laboratórios práticos e projetos reais em nosso blog parceiro oficial.
</p> </p>
@@ -216,14 +499,14 @@ export default function Home() {
href="https://cienciaembarcada.com.br" href="https://cienciaembarcada.com.br"
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"
className="inline-flex items-center gap-2 bg-gradient-to-r from-purple-600 to-indigo-600 text-white px-6 py-3 rounded-xl font-bold text-sm shadow-md hover:shadow-purple-500/10 hover:-translate-y-0.5 transition-all duration-300 cursor-pointer active:scale-95" className="inline-flex items-center gap-2 bg-gradient-to-r from-purple-600 to-indigo-600 text-white px-6 py-3 rounded-xl font-bold text-sm shadow-md hover:shadow-purple-500/10 hover:-translate-y-0.5 transition-all duration-200 cursor-pointer active:scale-95"
> >
<span>Visitar Blog Ciência Embarcada</span> <span>Visitar Blog Ciência Embarcada</span>
<ExternalLinkIcon /> <ExternalLink className="w-4 h-4" />
</a> </a>
</div> </div>
{/* Disclaimer */} {/* Disclaimer Oficial */}
<div className="border-t border-slate-300 dark:border-slate-800/80 pt-8 mt-10"> <div className="border-t border-slate-300 dark:border-slate-800/80 pt-8 mt-10">
<div className="bg-slate-100/60 dark:bg-slate-900/60 border-l-4 border-purple-500 p-5 rounded-r-2xl"> <div className="bg-slate-100/60 dark:bg-slate-900/60 border-l-4 border-purple-500 p-5 rounded-r-2xl">
<div className="flex gap-4"> <div className="flex gap-4">
@@ -242,12 +525,3 @@ export default function Home() {
</div> </div>
); );
} }
/* Local mini-icon component for external link */
function ExternalLinkIcon() {
return (
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2.5} d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14" />
</svg>
);
}
+56 -9
View File
@@ -1,17 +1,64 @@
import { paginaMeta } from "@/lib/seo"; import { paginaMeta, jsonLd } from "@/lib/seo";
/**
* A página desta rota é um client component, e client component não pode
* exportar metadata. Sem este layout, ela herdaria o título genérico do site,
* e o Google veria várias páginas com o mesmo título e a mesma descrição.
*/
export const metadata = paginaMeta({ export const metadata = paginaMeta({
titulo: "Qual ESP32 escolher", titulo: "Qual ESP32 escolher",
descricao: "Responda seis perguntas sobre conectividade, energia, periféricos e segurança e receba a série de ESP32 recomendada para o seu projeto, com a justificativa técnica de cada ponto.", descricao: "Responda a perguntas simples sobre conectividade (Wi-Fi 6, 5GHz, Matter), energia, periféricos e segurança ou use o filtro paramétrico para encontrar o ESP32 perfeito.",
caminho: "/seletor", caminho: "/seletor",
keywords: ["qual ESP32 escolher", "qual ESP32 usar", "escolher microcontrolador", "ESP32 para meu projeto"], keywords: ["qual ESP32 escolher", "qual ESP32 usar", "escolher microcontrolador", "ESP32 para meu projeto", "comparar ESP32"],
}); });
const faqs = [
{
pergunta: "Qual ESP32 escolher para iniciantes?",
resposta: "Para quem está começando no mundo Maker e IoT, o ESP32 clássico (NodeMCU/DevKitC) ou o ESP32-C3 são as melhores opções devido à vasta quantidade de bibliotecas, exemplos prontos no Arduino IDE e farta documentação em português.",
},
{
pergunta: "Qual ESP32 tem suporte a Wi-Fi 6, Bluetooth 6 e Thread/Zigbee/Matter?",
resposta: "O ESP32-C6 e o ESP32-C5 são as melhores séries para redes modernas. O ESP32-C6 oferece Wi-Fi 6 (2.4 GHz) e rádio 802.15.4 (Zigbee 3.0 / Thread / Matter). Já o ESP32-C5 é o primeiro a oferecer suporte a Wi-Fi 6 Dual-Band operando em 2.4 GHz e 5 GHz simultaneamente.",
},
{
pergunta: "Qual o ESP32 mais potente para Inteligência Artificial (TinyML) e Displays?",
resposta: "O ESP32-P4 (dual-core RISC-V a 400 MHz com extensões vetoriais de IA, interfaces MIPI-CSI/DSI para câmeras/telas e até 64 MB de PSRAM) e o ESP32-S3 (dual-core Xtensa a 240 MHz com aceleradores vetoriais para redes neurais e telas LCD RGB) são os mais velozes para IA na borda e interfaces gráficas ricas.",
},
{
pergunta: "Qual ESP32 consome menos energia para projetos a bateria?",
resposta: "Microcontroladores com arquitetura RISC-V compacta e coprocessador ULP, como o ESP32-C2, ESP32-C3 e ESP32-H2 (focado em 802.15.4 sem rádio Wi-Fi), oferecem correntes de deep sleep extremamente baixas (na faixa de 5 µA a 10 µA).",
},
];
export default function Layout({ children }) { export default function Layout({ children }) {
return children; return (
<>
<script
type="application/ld+json"
dangerouslySetInnerHTML={{
__html: JSON.stringify(
jsonLd.aplicacaoWeb({
nome: "Seletor Inteligente de ESP32",
descricao: "Ferramenta interativa que analisa conectividade, consumo, periféricos e segurança para indicar a série ideal de ESP32.",
caminho: "/seletor",
})
),
}}
/>
<script
type="application/ld+json"
dangerouslySetInnerHTML={{
__html: JSON.stringify(
jsonLd.trilha([
{ nome: "Início", caminho: "/" },
{ nome: "Seletor de ESP32", caminho: "/seletor" },
])
),
}}
/>
<script
type="application/ld+json"
dangerouslySetInnerHTML={{
__html: JSON.stringify(jsonLd.perguntas(faqs)),
}}
/>
{children}
</>
);
} }
+1241 -424
View File
File diff suppressed because it is too large. Load diff
+34 -10
View File
@@ -1,10 +1,5 @@
import { paginaMeta } from "@/lib/seo"; import { paginaMeta, jsonLd } from "@/lib/seo";
/**
* A página desta rota é um client component, e client component não pode
* exportar metadata. Sem este layout, ela herdaria o título genérico do site,
* e o Google veria várias páginas com o mesmo título e a mesma descrição.
*/
export const metadata = paginaMeta({ export const metadata = paginaMeta({
titulo: "Séries ESP32", titulo: "Séries ESP32",
descricao: "As 12 séries de ESP32 lado a lado: ESP32, S2, S3, S31, C2, C3, C5, C6, C61, P4, H2 e H4. Arquitetura, memória, rádio, periféricos e pinagem de cada uma, em português.", descricao: "As 12 séries de ESP32 lado a lado: ESP32, S2, S3, S31, C2, C3, C5, C6, C61, P4, H2 e H4. Arquitetura, memória, rádio, periféricos e pinagem de cada uma, em português.",
@@ -12,11 +7,40 @@ export const metadata = paginaMeta({
keywords: ["séries ESP32", "modelos de ESP32", "ESP32 S3", "ESP32 C6", "ESP32 P4", "diferença entre ESP32"], keywords: ["séries ESP32", "modelos de ESP32", "ESP32 S3", "ESP32 C6", "ESP32 P4", "diferença entre ESP32"],
}); });
// Um layout que define title como string simples substitui o template do root
// para toda a subárvore, e as páginas de série perdiam o sufixo "· ESPDocs".
// Redeclarar o template aqui devolve o comportamento às filhas.
metadata.title = { default: "Séries ESP32", template: "%s · ESPDocs" }; metadata.title = { default: "Séries ESP32", template: "%s · ESPDocs" };
const faqs = [
{
pergunta: "Quantas séries de ESP32 existem atualmente?",
resposta: "Atualmente o ecossistema Espressif conta com 12 séries principais: ESP32 clássico, ESP32-S2, ESP32-S3, ESP32-S31, ESP32-C2, ESP32-C3, ESP32-C5, ESP32-C6, ESP32-C61, ESP32-P4, ESP32-H2 e ESP32-H4.",
},
{
pergunta: "Quais são as séries mais recentes da Espressif?",
resposta: "As séries mais recentes incluem o ESP32-P4 (focado em alto desempenho com dual-core RISC-V a 400 MHz), o ESP32-C5 (primeiro chip com Wi-Fi dual-band 2.4/5 GHz da Espressif) e o ESP32-C61 (Wi-Fi 6 e Bluetooth 6 de baixo custo).",
},
];
export default function Layout({ children }) { export default function Layout({ children }) {
return children; return (
<>
<script
type="application/ld+json"
dangerouslySetInnerHTML={{
__html: JSON.stringify(
jsonLd.trilha([
{ nome: "Início", caminho: "/" },
{ nome: "Séries ESP32", caminho: "/series" },
])
),
}}
/>
<script
type="application/ld+json"
dangerouslySetInnerHTML={{
__html: JSON.stringify(jsonLd.perguntas(faqs)),
}}
/>
{children}
</>
);
} }
+16 -11
View File
@@ -10,34 +10,39 @@ import { SITE } from "@/lib/seo";
* JSON, uma série nova entra aqui sozinha. * JSON, uma série nova entra aqui sozinha.
*/ */
export default function sitemap() { export default function sitemap() {
const agora = new Date(); // Usar uma data de revisão estável evita que todo deploy force o Googlebot
// a reprocessar páginas idênticas ao mesmo tempo.
const dataRevisao = new Date("2026-08-25T00:00:00.000Z");
const fixas = [ const fixas = [
{ caminho: "", prioridade: 1.0, frequencia: "weekly" }, { caminho: "", prioridade: 1.0, frequencia: "weekly", lastModified: dataRevisao },
{ caminho: "/series", prioridade: 0.9, frequencia: "weekly" }, { caminho: "/series", prioridade: 0.9, frequencia: "weekly", lastModified: dataRevisao },
{ caminho: "/comparacao", prioridade: 0.9, frequencia: "weekly" }, { caminho: "/comparacao", prioridade: 0.9, frequencia: "weekly", lastModified: dataRevisao },
{ caminho: "/seletor", prioridade: 0.8, frequencia: "monthly" }, { caminho: "/diagnostico", prioridade: 0.9, frequencia: "weekly", lastModified: dataRevisao },
{ caminho: "/frameworks", prioridade: 0.8, frequencia: "monthly" }, { caminho: "/seletor", prioridade: 0.8, frequencia: "monthly", lastModified: dataRevisao },
{ caminho: "/glossario", prioridade: 0.7, frequencia: "monthly" }, { caminho: "/frameworks", prioridade: 0.8, frequencia: "monthly", lastModified: dataRevisao },
{ caminho: "/catalogo", prioridade: 0.6, frequencia: "weekly" }, { caminho: "/glossario", prioridade: 0.7, frequencia: "monthly", lastModified: dataRevisao },
{ caminho: "/sobre", prioridade: 0.4, frequencia: "yearly" }, { caminho: "/catalogo", prioridade: 0.6, frequencia: "weekly", lastModified: dataRevisao },
{ caminho: "/sobre", prioridade: 0.4, frequencia: "yearly", lastModified: dataRevisao },
]; ];
const series = Object.keys(seriesData).map((chave) => ({ const series = Object.keys(seriesData).map((chave) => ({
caminho: `/series/${chave}`, caminho: `/series/${chave}`,
prioridade: 0.9, prioridade: 0.9,
frequencia: "monthly", frequencia: "monthly",
lastModified: dataRevisao,
})); }));
const frameworks = Object.keys(frameworksData).map((chave) => ({ const frameworks = Object.keys(frameworksData).map((chave) => ({
caminho: `/frameworks/${encodeURIComponent(chave)}`, caminho: `/frameworks/${encodeURIComponent(chave)}`,
prioridade: 0.6, prioridade: 0.6,
frequencia: "monthly", frequencia: "monthly",
lastModified: dataRevisao,
})); }));
return [...fixas, ...series, ...frameworks].map(({ caminho, prioridade, frequencia }) => ({ return [...fixas, ...series, ...frameworks].map(({ caminho, prioridade, frequencia, lastModified }) => ({
url: `${SITE.url}${caminho}`, url: `${SITE.url}${caminho}`,
lastModified: agora, lastModified,
changeFrequency: frequencia, changeFrequency: frequencia,
priority: prioridade, priority: prioridade,
})); }));
+18 -1
View File
@@ -1,4 +1,4 @@
import { paginaMeta } from "@/lib/seo"; import { paginaMeta, jsonLd } from "@/lib/seo";
import Header from "@/components/Header"; import Header from "@/components/Header";
import Footer from "@/components/Footer"; import Footer from "@/components/Footer";
import Link from "next/link"; import Link from "next/link";
@@ -38,6 +38,23 @@ export default function Sobre() {
return ( return (
<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"> <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">
{/* Metadados E-E-A-T de autoria e organização para o Google */}
<script
type="application/ld+json"
dangerouslySetInnerHTML={{ __html: JSON.stringify(jsonLd.paginaSobre()) }}
/>
<script
type="application/ld+json"
dangerouslySetInnerHTML={{
__html: JSON.stringify(
jsonLd.trilha([
{ nome: "Início", caminho: "/" },
{ nome: "Sobre o Projeto", caminho: "/sobre" },
])
),
}}
/>
<a href="#conteudo" className="sr-only focus:not-sr-only focus:absolute focus:top-4 focus:left-4 focus:bg-white focus:px-4 focus:py-2 focus:rounded-md focus:shadow focus:ring-2 focus:ring-purple-500"> <a href="#conteudo" className="sr-only focus:not-sr-only focus:absolute focus:top-4 focus:left-4 focus:bg-white focus:px-4 focus:py-2 focus:rounded-md focus:shadow focus:ring-2 focus:ring-purple-500">
Ir para o conteúdo Ir para o conteúdo
</a> </a>
+9
View File
@@ -1,6 +1,7 @@
"use client"; "use client";
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import Link from "next/link";
import { ExternalLink } from "lucide-react"; import { ExternalLink } from "lucide-react";
export default function Footer() { export default function Footer() {
@@ -46,6 +47,14 @@ export default function Footer() {
Recursos Recursos
</h4> </h4>
<ul className="space-y-3 text-sm"> <ul className="space-y-3 text-sm">
<li>
<Link
href="/diagnostico"
className="hover:text-purple-600 dark:hover:text-purple-400 transition-colors flex items-center gap-1.5 font-medium"
>
<span>Guia de Diagnóstico & Erros</span>
</Link>
</li>
<li> <li>
<a <a
href="https://github.com/lucasrguerra/ESPDocs" href="https://github.com/lucasrguerra/ESPDocs"
+552 -96
View File
@@ -1,7 +1,7 @@
"use client"; "use client";
import { usePathname } from "next/navigation"; import { usePathname } from "next/navigation";
import { useState, useEffect } from "react"; import { useState, useEffect, useRef } from "react";
import Link from "next/link"; import Link from "next/link";
import { useTheme } from "next-themes"; import { useTheme } from "next-themes";
import { createPortal } from "react-dom"; import { createPortal } from "react-dom";
@@ -14,40 +14,51 @@ import {
Terminal, Terminal,
Scale, Scale,
ShoppingBag, ShoppingBag,
AlertTriangle,
Menu, Menu,
X, X,
Sun, Sun,
Moon Moon,
ChevronDown,
Sparkles,
ArrowRight,
ExternalLink,
Compass,
Layers
} from "lucide-react"; } from "lucide-react";
export default function Header() { export default function Header() {
const [drawerOpen, setDrawerOpen] = useState(false); const [drawerOpen, setDrawerOpen] = useState(false);
const [hardwareDropdown, setHardwareDropdown] = useState(false);
const [toolsDropdown, setToolsDropdown] = useState(false);
const [docsDropdown, setDocsDropdown] = useState(false);
const [mounted, setMounted] = useState(false); const [mounted, setMounted] = useState(false);
const pathname = usePathname(); const pathname = usePathname();
const hardwareRef = useRef(null);
const toolsRef = useRef(null);
const docsRef = useRef(null);
useEffect(() => { useEffect(() => {
setMounted(true); setMounted(true);
}, []); }, []);
const menuItems = [ // Close dropdowns on outside click
{ text: "Início", href: "/", icon: <Home className="w-4 h-4" />, featured: true }, useEffect(() => {
{ text: "Sobre o Projeto", brief: "Sobre", href: "/sobre", icon: <Info className="w-4 h-4" /> }, function handleClickOutside(e) {
{ text: "Glossário", href: "/glossario", icon: <BookOpen className="w-4 h-4" /> }, if (hardwareRef.current && !hardwareRef.current.contains(e.target)) {
{ text: "Séries", href: "/series", icon: <Cpu className="w-4 h-4" />, featured: true }, setHardwareDropdown(false);
{ text: "Seletor de ESP32", brief: "Seletor", href: "/seletor", icon: <SlidersHorizontal className="w-4 h-4" />, featured: true },
{ text: "Frameworks", href: "/frameworks", icon: <Terminal className="w-4 h-4" />, featured: true },
{ text: "Comparar Séries", brief: "Comparar", href: "/comparacao", icon: <Scale className="w-4 h-4" />, featured: true },
{ text: "Catálogo de Placas", brief: "Catálogo", href: "/catalogo", icon: <ShoppingBag className="w-4 h-4" /> },
];
const featuredItems = menuItems.filter(item => item.featured);
function isActive(href) {
if (href === "/") {
return pathname === "/";
} }
return String(pathname).includes(href) && href !== "/"; if (toolsRef.current && !toolsRef.current.contains(e.target)) {
setToolsDropdown(false);
} }
if (docsRef.current && !docsRef.current.contains(e.target)) {
setDocsDropdown(false);
}
}
document.addEventListener("mousedown", handleClickOutside);
return () => document.removeEventListener("mousedown", handleClickOutside);
}, []);
// Lock scroll when mobile menu is open // Lock scroll when mobile menu is open
useEffect(() => { useEffect(() => {
@@ -61,134 +72,579 @@ export default function Header() {
}; };
}, [drawerOpen]); }, [drawerOpen]);
// Close menu on route change
useEffect(() => {
setDrawerOpen(false);
setHardwareDropdown(false);
setToolsDropdown(false);
setDocsDropdown(false);
}, [pathname]);
function isActive(href) {
if (href === "/") {
return pathname === "/";
}
return String(pathname).startsWith(href);
}
const popularChips = [
{ name: "ESP32", desc: "Clássico Dual-Core", href: "/series/esp32" },
{ name: "ESP32-S3", desc: "IA & USB Nativo", href: "/series/esp32-s3" },
{ name: "ESP32-C6", desc: "Wi-Fi 6 + Zigbee/Thread", href: "/series/esp32-c6" },
{ name: "ESP32-P4", desc: "Alto Desempenho 400MHz", href: "/series/esp32-p4" },
];
return ( return (
<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"> <header className="bg-white/90 dark:bg-slate-950/80 backdrop-blur-md py-3.5 px-4 md:px-8 shadow-xs border-b border-slate-200 dark:border-slate-800/60 sticky top-0 z-50 transition-colors duration-300">
<div className="flex justify-between max-w-7xl mx-auto items-center gap-8"> <div className="flex justify-between max-w-7xl mx-auto items-center gap-4 lg:gap-8">
{/* LOGO */} {/* LOGO */}
<Link href="/" className="group shrink-0"> <Link href="/" className="group shrink-0 flex items-center gap-2.5">
<span className="flex items-center gap-2.5">
{/* eslint-disable-next-line @next/next/no-img-element */} {/* eslint-disable-next-line @next/next/no-img-element */}
<img src="/marca/espdocs-marca.svg" alt="" aria-hidden="true" width={32} height={32} className="w-7 h-7 md:w-8 md:h-8 shrink-0" /> <img
src="/marca/espdocs-marca.svg"
alt="ESPDocs Logo"
width={32}
height={32}
className="w-7 h-7 md:w-8 md:h-8 shrink-0 transition-transform duration-300 group-hover:scale-105"
/>
<h1 className="text-2xl md:text-3xl font-display font-extrabold bg-gradient-to-r from-blue-500 via-purple-500 to-pink-500 bg-clip-text text-transparent transition-all duration-300 group-hover:scale-[1.02]"> <h1 className="text-2xl md:text-3xl font-display font-extrabold bg-gradient-to-r from-blue-500 via-purple-500 to-pink-500 bg-clip-text text-transparent transition-all duration-300 group-hover:scale-[1.02]">
ESPDocs ESPDocs
</h1> </h1>
</span>
</Link> </Link>
{/* Menu Desktop */} {/* Desktop Navigation */}
<nav className="hidden lg:flex items-center gap-2 flex-1 justify-end" aria-label="Menu principal"> <nav className="hidden lg:flex items-center gap-1 xl:gap-2 flex-1 justify-center" aria-label="Menu principal">
{featuredItems.map((item) => ( {/* Link Início */}
<Link <Link
key={item.text} href="/"
href={item.href} className={`flex items-center gap-1.5 px-3 py-2 rounded-xl font-semibold text-sm transition-all duration-200 ${
className={`group flex items-center gap-2 px-4 py-2 rounded-xl font-medium transition-all duration-300 text-sm ${ isActive("/") && pathname === "/"
isActive(item.href) ? "bg-purple-50 dark:bg-purple-950/40 text-purple-600 dark:text-purple-400"
? "bg-gradient-to-r from-blue-600 to-purple-600 text-white shadow-md shadow-purple-500/10" : "text-slate-650 dark:text-slate-350 hover:bg-slate-100/70 dark:hover:bg-slate-800/60 hover:text-slate-850 dark:hover:text-slate-100"
: "text-slate-600 dark:text-slate-300 hover:bg-slate-50 dark:hover:bg-slate-900/60 hover:text-purple-600 dark:hover:text-purple-400"
}`} }`}
aria-label={item.brief || item.text}
aria-current={isActive(item.href) ? "page" : undefined}
> >
<span className={`transition-transform duration-300 ${ <Home className="w-4 h-4" />
isActive(item.href) ? "scale-110" : "group-hover:scale-110" <span>Início</span>
}`}> </Link>
{item.icon}
{/* Dropdown: Séries & Hardware */}
<div className="relative" ref={hardwareRef}>
<button
onClick={() => {
setHardwareDropdown(!hardwareDropdown);
setToolsDropdown(false);
setDocsDropdown(false);
}}
className={`flex items-center gap-1.5 px-3 py-2 rounded-xl font-semibold text-sm transition-all duration-200 cursor-pointer ${
isActive("/series") || isActive("/comparacao") || isActive("/catalogo")
? "bg-purple-50 dark:bg-purple-950/40 text-purple-600 dark:text-purple-400"
: "text-slate-650 dark:text-slate-350 hover:bg-slate-100/70 dark:hover:bg-slate-800/60 hover:text-slate-850 dark:hover:text-slate-100"
}`}
aria-expanded={hardwareDropdown}
>
<Cpu className="w-4 h-4" />
<span>Séries & Chips</span>
<ChevronDown className={`w-3.5 h-3.5 transition-transform duration-200 ${hardwareDropdown ? "rotate-180" : ""}`} />
</button>
{hardwareDropdown && (
<div className="absolute left-0 mt-2 w-80 bg-white dark:bg-slate-900 rounded-2xl shadow-2xl border border-slate-200 dark:border-slate-800 p-3 z-50 animate-fadeIn">
<div className="space-y-1 mb-2">
<Link
href="/series"
className="flex items-start gap-3 p-2.5 rounded-xl hover:bg-slate-100 dark:hover:bg-slate-800/70 transition-colors group"
>
<div className="p-2 rounded-lg bg-blue-50 dark:bg-blue-950/40 text-blue-600 dark:text-blue-400 shrink-0">
<Cpu className="w-4 h-4" />
</div>
<div>
<div className="font-bold text-sm text-slate-850 dark:text-slate-100 group-hover:text-purple-600 dark:group-hover:text-purple-400 flex items-center gap-1.5">
Todas as 12 Séries
<span className="text-[10px] bg-blue-100 dark:bg-blue-950/60 text-blue-700 dark:text-blue-300 font-bold px-1.5 py-0.5 rounded-md">12</span>
</div>
<div className="text-xs text-slate-500 dark:text-slate-400 mt-0.5">Pinouts, especificações e restrições</div>
</div>
</Link>
<Link
href="/comparacao"
className="flex items-start gap-3 p-2.5 rounded-xl hover:bg-slate-100 dark:hover:bg-slate-800/70 transition-colors group"
>
<div className="p-2 rounded-lg bg-indigo-50 dark:bg-indigo-950/40 text-indigo-600 dark:text-indigo-400 shrink-0">
<Scale className="w-4 h-4" />
</div>
<div>
<div className="font-bold text-sm text-slate-850 dark:text-slate-100 group-hover:text-purple-600 dark:group-hover:text-purple-400">
Comparador de Séries
</div>
<div className="text-xs text-slate-500 dark:text-slate-400 mt-0.5">Compare até 4 modelos lado a lado</div>
</div>
</Link>
<Link
href="/catalogo"
className="flex items-start gap-3 p-2.5 rounded-xl hover:bg-slate-100 dark:hover:bg-slate-800/70 transition-colors group"
>
<div className="p-2 rounded-lg bg-emerald-50 dark:bg-emerald-950/40 text-emerald-600 dark:text-emerald-400 shrink-0">
<ShoppingBag className="w-4 h-4" />
</div>
<div>
<div className="font-bold text-sm text-slate-850 dark:text-slate-100 group-hover:text-purple-600 dark:group-hover:text-purple-400">
Catálogo de Placas
</div>
<div className="text-xs text-slate-500 dark:text-slate-400 mt-0.5">Placas de dev, conectores e compras</div>
</div>
</Link>
</div>
{/* Atalhos Rápidos Séries Populares */}
<div className="pt-2 border-t border-slate-200 dark:border-slate-800/80">
<div className="text-[11px] font-bold text-slate-500 dark:text-slate-400 uppercase tracking-wider px-2.5 mb-1.5">
Séries Populares
</div>
<div className="grid grid-cols-2 gap-1">
{popularChips.map((chip) => (
<Link
key={chip.name}
href={chip.href}
className="p-2 rounded-lg hover:bg-slate-100 dark:hover:bg-slate-800/50 transition-colors text-left"
>
<span className="text-xs font-bold text-slate-850 dark:text-slate-100 block truncate">
{chip.name}
</span>
<span className="text-[10px] text-slate-500 dark:text-slate-400 block truncate">
{chip.desc}
</span> </span>
<span className="whitespace-nowrap">{item.brief || item.text}</span>
</Link> </Link>
))} ))}
</div>
</div>
</div>
)}
</div>
{/* Divider */} {/* Dropdown: Ferramentas */}
<div className="h-5 w-px bg-slate-200 dark:bg-slate-800 mx-2" /> <div className="relative" ref={toolsRef}>
<button
onClick={() => {
setToolsDropdown(!toolsDropdown);
setHardwareDropdown(false);
setDocsDropdown(false);
}}
className={`flex items-center gap-1.5 px-3 py-2 rounded-xl font-semibold text-sm transition-all duration-200 cursor-pointer ${
isActive("/seletor") || isActive("/diagnostico")
? "bg-purple-50 dark:bg-purple-950/40 text-purple-600 dark:text-purple-400"
: "text-slate-650 dark:text-slate-350 hover:bg-slate-100/70 dark:hover:bg-slate-800/60 hover:text-slate-850 dark:hover:text-slate-100"
}`}
aria-expanded={toolsDropdown}
>
<Sparkles className="w-4 h-4" />
<span>Ferramentas</span>
<ChevronDown className={`w-3.5 h-3.5 transition-transform duration-200 ${toolsDropdown ? "rotate-180" : ""}`} />
</button>
{toolsDropdown && (
<div className="absolute left-0 mt-2 w-80 bg-white dark:bg-slate-900 rounded-2xl shadow-2xl border border-slate-200 dark:border-slate-800 p-3 z-50 animate-fadeIn">
<div className="space-y-1">
<Link
href="/seletor"
className="flex items-start gap-3 p-2.5 rounded-xl hover:bg-slate-100 dark:hover:bg-slate-800/70 transition-colors group"
>
<div className="p-2 rounded-lg bg-purple-50 dark:bg-purple-950/40 text-purple-600 dark:text-purple-400 shrink-0">
<SlidersHorizontal className="w-4 h-4" />
</div>
<div>
<div className="font-bold text-sm text-slate-850 dark:text-slate-100 group-hover:text-purple-600 dark:group-hover:text-purple-400 flex items-center gap-1.5">
Seletor Inteligente
<span className="text-[10px] bg-purple-100 dark:bg-purple-950/60 text-purple-700 dark:text-purple-300 font-bold px-1.5 py-0.5 rounded-md">Quiz</span>
</div>
<div className="text-xs text-slate-500 dark:text-slate-400 mt-0.5">Descubra o chip ideal para o seu projeto</div>
</div>
</Link>
<Link
href="/diagnostico"
className="flex items-start gap-3 p-2.5 rounded-xl hover:bg-slate-100 dark:hover:bg-slate-800/70 transition-colors group"
>
<div className="p-2 rounded-lg bg-amber-50 dark:bg-amber-950/40 text-amber-600 dark:text-amber-400 shrink-0">
<AlertTriangle className="w-4 h-4" />
</div>
<div>
<div className="font-bold text-sm text-slate-850 dark:text-slate-100 group-hover:text-amber-600 dark:group-hover:text-amber-400 flex items-center gap-1.5">
Diagnóstico de Erros
<span className="text-[10px] bg-amber-100 dark:bg-amber-950/60 text-amber-700 dark:text-amber-300 font-bold px-1.5 py-0.5 rounded-md">30+ Erros</span>
</div>
<div className="text-xs text-slate-500 dark:text-slate-400 mt-0.5">Solucione Brownout, Bootloops, WDT e falhas</div>
</div>
</Link>
</div>
</div>
)}
</div>
{/* Dropdown: Documentação */}
<div className="relative" ref={docsRef}>
<button
onClick={() => {
setDocsDropdown(!docsDropdown);
setHardwareDropdown(false);
setToolsDropdown(false);
}}
className={`flex items-center gap-1.5 px-3 py-2 rounded-xl font-semibold text-sm transition-all duration-200 cursor-pointer ${
isActive("/frameworks") || isActive("/glossario") || isActive("/sobre")
? "bg-purple-50 dark:bg-purple-950/40 text-purple-600 dark:text-purple-400"
: "text-slate-650 dark:text-slate-350 hover:bg-slate-100/70 dark:hover:bg-slate-800/60 hover:text-slate-850 dark:hover:text-slate-100"
}`}
aria-expanded={docsDropdown}
>
<BookOpen className="w-4 h-4" />
<span>Documentação</span>
<ChevronDown className={`w-3.5 h-3.5 transition-transform duration-200 ${docsDropdown ? "rotate-180" : ""}`} />
</button>
{docsDropdown && (
<div className="absolute left-0 mt-2 w-72 bg-white dark:bg-slate-900 rounded-2xl shadow-2xl border border-slate-200 dark:border-slate-800 p-3 z-50 animate-fadeIn">
<div className="space-y-1">
<Link
href="/frameworks"
className="flex items-start gap-3 p-2.5 rounded-xl hover:bg-slate-100 dark:hover:bg-slate-800/70 transition-colors group"
>
<div className="p-2 rounded-lg bg-teal-50 dark:bg-teal-950/40 text-teal-600 dark:text-teal-400 shrink-0">
<Terminal className="w-4 h-4" />
</div>
<div>
<div className="font-bold text-sm text-slate-850 dark:text-slate-100 group-hover:text-purple-600 dark:group-hover:text-purple-400">
Frameworks & SDKs
</div>
<div className="text-xs text-slate-500 dark:text-slate-400 mt-0.5">ESP-IDF, Arduino, MicroPython e mais</div>
</div>
</Link>
<Link
href="/glossario"
className="flex items-start gap-3 p-2.5 rounded-xl hover:bg-slate-100 dark:hover:bg-slate-800/70 transition-colors group"
>
<div className="p-2 rounded-lg bg-pink-50 dark:bg-pink-950/40 text-pink-600 dark:text-pink-400 shrink-0">
<BookOpen className="w-4 h-4" />
</div>
<div>
<div className="font-bold text-sm text-slate-850 dark:text-slate-100 group-hover:text-purple-600 dark:group-hover:text-purple-400">
Glossário Técnico
</div>
<div className="text-xs text-slate-500 dark:text-slate-400 mt-0.5">Conceitos essenciais de hardware e software</div>
</div>
</Link>
<Link
href="/sobre"
className="flex items-start gap-3 p-2.5 rounded-xl hover:bg-slate-100 dark:hover:bg-slate-800/70 transition-colors group"
>
<div className="p-2 rounded-lg bg-slate-100 dark:bg-slate-800 text-slate-650 dark:text-slate-350 shrink-0">
<Info className="w-4 h-4" />
</div>
<div>
<div className="font-bold text-sm text-slate-850 dark:text-slate-100 group-hover:text-purple-600 dark:group-hover:text-purple-400">
Sobre o Projeto
</div>
<div className="text-xs text-slate-500 dark:text-slate-400 mt-0.5">História, autor e Ciência Embarcada</div>
</div>
</Link>
</div>
</div>
)}
</div>
</nav>
{/* Right CTA + Theme + Full Drawer Button */}
<div className="flex items-center gap-2 md:gap-3">
{/* CTA Button Desktop */}
<Link
href="/seletor"
className="hidden sm:inline-flex items-center gap-2 bg-gradient-to-r from-purple-600 to-indigo-600 text-white px-4 py-2 rounded-xl font-bold text-xs shadow-sm hover:shadow-purple-500/20 hover:-translate-y-0.5 transition-all duration-200 active:scale-95 shrink-0 cursor-pointer"
>
<Compass className="w-3.5 h-3.5" />
<span>Seletor de ESP32</span>
</Link>
<div className="h-5 w-px bg-slate-200 dark:bg-slate-800 mx-1 hidden sm:block" />
{/* Theme Switcher Button */} {/* Theme Switcher Button */}
<ThemeToggle /> <ThemeToggle />
{/* Full Menu Burger Trigger */} {/* Full Drawer Trigger Button */}
<button <button
onClick={() => setDrawerOpen(true)} onClick={() => setDrawerOpen(true)}
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" className="p-2 md:p-2.5 rounded-xl border border-slate-300 dark:border-slate-800 text-slate-700 dark:text-slate-300 hover:bg-slate-100 dark:hover:bg-slate-800/80 hover:border-purple-500 dark:hover:border-purple-400 hover:text-purple-600 dark:hover:text-purple-400 transition-all cursor-pointer flex items-center gap-1.5"
aria-label="Abrir menu completo" aria-label="Abrir menu de navegação completo"
aria-expanded={drawerOpen}
>
<Menu className="w-5 h-5" />
</button>
</nav>
{/* Botões Mobile (Theme + Burger) */}
<div className="flex lg:hidden items-center gap-2">
<ThemeToggle />
<button
onClick={() => setDrawerOpen(true)}
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} aria-expanded={drawerOpen}
> >
<Menu className="w-5 h-5" /> <Menu className="w-5 h-5" />
<span className="text-xs font-bold hidden xl:inline">Menu</span>
</button> </button>
</div> </div>
</div> </div>
{/* Render side-panel and overlay at body level via React Portal to prevent layout leakage */} {/* Full Screen / Side Drawer Portal */}
{mounted && createPortal( {mounted && createPortal(
<> <>
{drawerOpen && ( {drawerOpen && (
<div <div
className="fixed inset-0 bg-slate-950/50 dark:bg-black/75 backdrop-blur-xs z-[100] transition-opacity duration-300" className="fixed inset-0 bg-slate-950/60 dark:bg-black/80 backdrop-blur-xs z-[100] transition-opacity duration-300"
onClick={() => setDrawerOpen(false)} onClick={() => setDrawerOpen(false)}
/> />
)} )}
<div <div
className={`fixed top-0 right-0 h-screen w-80 max-w-[85vw] bg-white dark:bg-slate-900 shadow-2xl z-[101] transform transition-transform duration-300 ease-in-out flex flex-col border-l border-slate-300 dark:border-slate-800 ${ className={`fixed top-0 right-0 h-screen w-96 max-w-[88vw] 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 ${
drawerOpen ? "translate-x-0" : "translate-x-full" drawerOpen ? "translate-x-0" : "translate-x-full"
}`} }`}
> >
{/* Drawer Header */} {/* Drawer Header */}
<div className="flex items-center justify-between p-6 border-b border-slate-200 dark:border-slate-800/80"> <div className="flex items-center justify-between p-5 border-b border-slate-200 dark:border-slate-800">
<h2 className="text-xl font-display font-bold bg-gradient-to-r from-blue-500 to-purple-500 bg-clip-text text-transparent"> <div className="flex items-center gap-2.5">
Menu Navegação {/* eslint-disable-next-line @next/next/no-img-element */}
<img src="/marca/espdocs-marca.svg" alt="" aria-hidden="true" width={28} height={28} className="w-7 h-7" />
<div>
<h2 className="text-lg font-display font-extrabold bg-gradient-to-r from-blue-500 to-purple-500 bg-clip-text text-transparent">
Navegação ESPDocs
</h2> </h2>
<p className="text-[11px] text-slate-500 dark:text-slate-400">Guia Técnico em Português</p>
</div>
</div>
<button <button
onClick={() => setDrawerOpen(false)} onClick={() => setDrawerOpen(false)}
className="p-2 text-slate-500 hover:text-slate-800 dark:text-slate-400 dark:hover:text-white rounded-xl hover:bg-slate-50 dark:hover:bg-slate-800/60 transition-all cursor-pointer" className="p-2 text-slate-500 hover:text-slate-800 dark:text-slate-400 dark:hover:text-white rounded-xl hover:bg-slate-100 dark:hover:bg-slate-800 transition-all cursor-pointer"
aria-label="Fechar menu" aria-label="Fechar menu lateral"
> >
<X className="w-5 h-5" /> <X className="w-5 h-5" />
</button> </button>
</div> </div>
{/* Drawer Menu Items */} {/* Quick Action Highlights */}
<nav className="flex-1 overflow-y-auto p-6 animate-fadeIn" aria-label="Navegação móvel"> <div className="p-4 bg-slate-50 dark:bg-slate-950/50 border-b border-slate-200 dark:border-slate-800 space-y-2">
<ul className="space-y-2">
{menuItems.map((item) => (
<li key={item.text}>
<Link <Link
href={item.href} href="/seletor"
className={`flex items-center gap-3 w-full p-3.5 rounded-xl font-medium transition-all duration-205 text-sm ${
isActive(item.href)
? "bg-gradient-to-r from-blue-600 to-purple-600 text-white shadow-md shadow-purple-500/10"
: "text-slate-650 dark:text-slate-350 hover:bg-slate-50 dark:hover:bg-slate-800/40 hover:text-purple-600 dark:hover:text-purple-400"
}`}
onClick={() => setDrawerOpen(false)} onClick={() => setDrawerOpen(false)}
aria-current={isActive(item.href) ? "page" : undefined} className="flex items-center justify-between p-3 rounded-xl bg-gradient-to-r from-purple-600 to-indigo-600 text-white shadow-sm hover:shadow-md transition-all group"
> >
<span className="shrink-0">{item.icon}</span> <div className="flex items-center gap-2.5">
<span className="grow text-left">{item.text}</span> <Compass className="w-4 h-4 shrink-0" />
{isActive(item.href) && ( <div className="text-left">
<span className="w-1.5 h-1.5 bg-white rounded-full animate-ping" /> <div className="font-bold text-xs">Seletor Inteligente</div>
)} <div className="text-[10px] text-purple-100">Encontre o ESP32 certo</div>
</div>
</div>
<ArrowRight className="w-4 h-4 transform group-hover:translate-x-1 transition-transform" />
</Link>
<Link
href="/diagnostico"
onClick={() => setDrawerOpen(false)}
className="flex items-center justify-between p-3 rounded-xl bg-gradient-to-r from-amber-500 to-orange-500 text-white shadow-sm hover:shadow-md transition-all group"
>
<div className="flex items-center gap-2.5">
<AlertTriangle className="w-4 h-4 shrink-0" />
<div className="text-left">
<div className="font-bold text-xs">Diagnóstico de Falhas</div>
<div className="text-[10px] text-amber-100">Resolva Brownout, bootloop e erros</div>
</div>
</div>
<ArrowRight className="w-4 h-4 transform group-hover:translate-x-1 transition-transform" />
</Link>
</div>
{/* Drawer Categorized Menu */}
<nav className="flex-1 overflow-y-auto p-5 space-y-6" aria-label="Navegação móvel completa">
{/* Seção 1: Hardware & Microcontroladores */}
<div>
<div className="text-[11px] font-extrabold text-slate-500 dark:text-slate-400 uppercase tracking-wider mb-2.5 px-1">
Hardware & Séries
</div>
<div className="space-y-1">
<Link
href="/series"
onClick={() => setDrawerOpen(false)}
className={`flex items-center justify-between p-2.5 rounded-xl font-medium text-sm transition-colors ${
isActive("/series") && pathname === "/series"
? "bg-blue-50 dark:bg-blue-950/40 text-blue-600 dark:text-blue-400 font-bold"
: "text-slate-700 dark:text-slate-300 hover:bg-slate-100 dark:hover:bg-slate-800/60"
}`}
>
<div className="flex items-center gap-2.5">
<Cpu className="w-4 h-4 text-blue-600 dark:text-blue-400" />
<span>Todas as Séries</span>
</div>
<span className="text-[11px] bg-slate-200 dark:bg-slate-800 text-slate-650 dark:text-slate-350 px-2 py-0.5 rounded-full font-bold">
12
</span>
</Link>
<Link
href="/comparacao"
onClick={() => setDrawerOpen(false)}
className={`flex items-center gap-2.5 p-2.5 rounded-xl font-medium text-sm transition-colors ${
isActive("/comparacao")
? "bg-indigo-50 dark:bg-indigo-950/40 text-indigo-600 dark:text-indigo-400 font-bold"
: "text-slate-700 dark:text-slate-300 hover:bg-slate-100 dark:hover:bg-slate-800/60"
}`}
>
<Scale className="w-4 h-4 text-indigo-600 dark:text-indigo-400" />
<span>Comparar Séries</span>
</Link>
<Link
href="/catalogo"
onClick={() => setDrawerOpen(false)}
className={`flex items-center gap-2.5 p-2.5 rounded-xl font-medium text-sm transition-colors ${
isActive("/catalogo")
? "bg-emerald-50 dark:bg-emerald-950/40 text-emerald-600 dark:text-emerald-400 font-bold"
: "text-slate-700 dark:text-slate-300 hover:bg-slate-100 dark:hover:bg-slate-800/60"
}`}
>
<ShoppingBag className="w-4 h-4 text-emerald-600 dark:text-emerald-400" />
<span>Catálogo de Placas</span>
</Link>
</div>
{/* Atalhos Rápidos com Cores */}
<div className="mt-3 pt-3 border-t border-slate-200 dark:border-slate-800/80">
<div className="text-[10px] font-bold text-slate-500 dark:text-slate-400 uppercase tracking-wider mb-2 px-1">
Acesso Rápido a Chips
</div>
<div className="grid grid-cols-2 gap-1.5">
{popularChips.map((chip) => (
<Link
key={chip.name}
href={chip.href}
onClick={() => setDrawerOpen(false)}
className="p-2 rounded-xl bg-slate-50 dark:bg-slate-800/40 hover:bg-slate-100 dark:hover:bg-slate-800 border border-slate-200/60 dark:border-slate-750 transition-colors"
>
<span className="text-xs font-bold text-slate-850 dark:text-slate-100 block">
{chip.name}
</span>
<span className="text-[10px] text-slate-500 dark:text-slate-400 block truncate">
{chip.desc}
</span>
</Link> </Link>
</li>
))} ))}
</ul> </div>
</div>
</div>
{/* Seção 2: Ferramentas & Diagnóstico */}
<div>
<div className="text-[11px] font-extrabold text-slate-500 dark:text-slate-400 uppercase tracking-wider mb-2.5 px-1">
Ferramentas Interativas
</div>
<div className="space-y-1">
<Link
href="/seletor"
onClick={() => setDrawerOpen(false)}
className={`flex items-center gap-2.5 p-2.5 rounded-xl font-medium text-sm transition-colors ${
isActive("/seletor")
? "bg-purple-50 dark:bg-purple-950/40 text-purple-600 dark:text-purple-400 font-bold"
: "text-slate-700 dark:text-slate-300 hover:bg-slate-100 dark:hover:bg-slate-800/60"
}`}
>
<SlidersHorizontal className="w-4 h-4 text-purple-600 dark:text-purple-400" />
<span>Seletor Inteligente</span>
</Link>
<Link
href="/diagnostico"
onClick={() => setDrawerOpen(false)}
className={`flex items-center gap-2.5 p-2.5 rounded-xl font-medium text-sm transition-colors ${
isActive("/diagnostico")
? "bg-amber-50 dark:bg-amber-950/40 text-amber-600 dark:text-amber-400 font-bold"
: "text-slate-700 dark:text-slate-300 hover:bg-slate-100 dark:hover:bg-slate-800/60"
}`}
>
<AlertTriangle className="w-4 h-4 text-amber-600 dark:text-amber-400" />
<span>Diagnóstico & Solução de Erros</span>
</Link>
</div>
</div>
{/* Seção 3: Documentação & Referência */}
<div>
<div className="text-[11px] font-extrabold text-slate-500 dark:text-slate-400 uppercase tracking-wider mb-2.5 px-1">
Guias & Documentação
</div>
<div className="space-y-1">
<Link
href="/frameworks"
onClick={() => setDrawerOpen(false)}
className={`flex items-center gap-2.5 p-2.5 rounded-xl font-medium text-sm transition-colors ${
isActive("/frameworks")
? "bg-teal-50 dark:bg-teal-950/40 text-teal-600 dark:text-teal-400 font-bold"
: "text-slate-700 dark:text-slate-300 hover:bg-slate-100 dark:hover:bg-slate-800/60"
}`}
>
<Terminal className="w-4 h-4 text-teal-600 dark:text-teal-400" />
<span>Frameworks & SDKs</span>
</Link>
<Link
href="/glossario"
onClick={() => setDrawerOpen(false)}
className={`flex items-center gap-2.5 p-2.5 rounded-xl font-medium text-sm transition-colors ${
isActive("/glossario")
? "bg-pink-50 dark:bg-pink-950/40 text-pink-600 dark:text-pink-400 font-bold"
: "text-slate-700 dark:text-slate-300 hover:bg-slate-100 dark:hover:bg-slate-800/60"
}`}
>
<BookOpen className="w-4 h-4 text-pink-600 dark:text-pink-400" />
<span>Glossário Técnico</span>
</Link>
</div>
</div>
{/* Seção 4: Institucional & Comunidade */}
<div>
<div className="text-[11px] font-extrabold text-slate-500 dark:text-slate-400 uppercase tracking-wider mb-2.5 px-1">
Comunidade & Projeto
</div>
<div className="space-y-1">
<Link
href="/sobre"
onClick={() => setDrawerOpen(false)}
className={`flex items-center gap-2.5 p-2.5 rounded-xl font-medium text-sm transition-colors ${
isActive("/sobre")
? "bg-slate-100 dark:bg-slate-800 text-slate-850 dark:text-slate-100 font-bold"
: "text-slate-700 dark:text-slate-300 hover:bg-slate-100 dark:hover:bg-slate-800/60"
}`}
>
<Info className="w-4 h-4 text-slate-500 dark:text-slate-400" />
<span>Sobre a Plataforma</span>
</Link>
<a
href="https://cienciaembarcada.com.br"
target="_blank"
rel="noopener noreferrer"
className="flex items-center justify-between p-2.5 rounded-xl font-medium text-sm text-slate-700 dark:text-slate-300 hover:bg-slate-100 dark:hover:bg-slate-800/60 transition-colors group"
>
<div className="flex items-center gap-2.5">
<Layers className="w-4 h-4 text-purple-600 dark:text-purple-400" />
<span>Blog Ciência Embarcada</span>
</div>
<ExternalLink className="w-3.5 h-3.5 text-slate-500 dark:text-slate-400 group-hover:text-purple-600 dark:group-hover:text-purple-400" />
</a>
</div>
</div>
</nav> </nav>
{/* Drawer Footer Info */} {/* Drawer Footer */}
<div className="p-6 border-t border-slate-200 dark:border-slate-800/80 bg-slate-50/50 dark:bg-slate-900/50"> <div className="p-4 border-t border-slate-200 dark:border-slate-800 bg-slate-50 dark:bg-slate-950/50 flex items-center justify-between">
<p className="text-center text-xs text-slate-500 dark:text-slate-400 font-medium"> <div>
ESPDocs • Plataforma Não Oficial <p className="text-xs font-bold text-slate-700 dark:text-slate-300">
ESPDocs • 100% em Português
</p> </p>
<p className="text-[10px] text-slate-500 dark:text-slate-400">
12 Séries • 8 Frameworks • 30+ Erros
</p>
</div>
<ThemeToggle />
</div> </div>
</div> </div>
</> </>
@@ -198,7 +654,7 @@ export default function Header() {
); );
} }
/* Internal ThemeToggle Component */ /* ThemeToggle Component */
function ThemeToggle() { function ThemeToggle() {
const { resolvedTheme, setTheme } = useTheme(); const { resolvedTheme, setTheme } = useTheme();
const [mounted, setMounted] = useState(false); const [mounted, setMounted] = useState(false);
@@ -207,20 +663,20 @@ function ThemeToggle() {
if (!mounted) { if (!mounted) {
return ( return (
<div className="w-10 h-10 rounded-xl bg-slate-100 dark:bg-slate-900 animate-pulse border border-transparent" /> <div className="w-9 h-9 md:w-10 md:h-10 rounded-xl bg-slate-100 dark:bg-slate-900 animate-pulse border border-transparent" />
); );
} }
return ( return (
<button <button
onClick={() => setTheme(resolvedTheme === "dark" ? "light" : "dark")} onClick={() => setTheme(resolvedTheme === "dark" ? "light" : "dark")}
className="w-10 h-10 rounded-xl bg-slate-100 dark:bg-slate-900 text-slate-600 dark:text-slate-300 hover:bg-slate-200 dark:hover:bg-slate-800/80 hover:text-purple-600 dark:hover:text-purple-400 border border-slate-250/20 dark:border-slate-800/60 shadow-xs flex items-center justify-center transition-all duration-300 cursor-pointer active:scale-95 shrink-0" className="w-9 h-9 md:w-10 md:h-10 rounded-xl bg-slate-100 dark:bg-slate-900 text-slate-650 dark:text-slate-350 hover:bg-slate-200 dark:hover:bg-slate-800/80 hover:text-purple-600 dark:hover:text-purple-400 border border-slate-200 dark:border-slate-800 shadow-xs flex items-center justify-center transition-all duration-200 cursor-pointer active:scale-95 shrink-0"
aria-label="Alternar tema" aria-label="Alternar tema claro/escuro"
> >
{resolvedTheme === "dark" ? ( {resolvedTheme === "dark" ? (
<Sun className="w-5 h-5 text-yellow-500" /> <Sun className="w-4 h-4 md:w-5 md:h-5 text-yellow-500" />
) : ( ) : (
<Moon className="w-5 h-5 text-indigo-600" /> <Moon className="w-4 h-4 md:w-5 md:h-5 text-indigo-600" />
)} )}
</button> </button>
); );
+598
View File
@@ -0,0 +1,598 @@
/**
* Base de dados estruturada para o Guia de Diagnóstico, Erros Comuns e Resolução de Problemas no ESP32.
* Abrange 6 categorias técnicas com causas raízes, comparativos de boas práticas vs gambiarras e exemplos de código.
*/
export const categoriasDiagnostico = [
{
id: "alimentacao",
nome: "Alimentação & Hardware",
icone: "Zap",
cor: "amber",
descricao: "Quedas de tensão por picos de RF Wi-Fi/BLE, strapping pins conflitantes, ruído eletromagnético e problemas de LDO.",
},
{
id: "cpu-panics",
nome: "Pânicos de CPU & Guru Meditation",
icone: "Cpu",
cor: "red",
descricao: "Ponteiros nulos, violações de memória (Load/StoreProhibited), instruções ilegais e cache panics em ISR.",
},
{
id: "freertos",
nome: "FreeRTOS & Multitarefa",
icone: "Layers",
cor: "purple",
descricao: "Task Watchdog (TWDT), Interrupt Watchdog (IWDT), estouro de pilha (Stack Overflow) e bloqueios de escalonador.",
},
{
id: "memoria",
nome: "Memória, Heap & PSRAM",
icone: "Activity",
cor: "blue",
descricao: "Fragmentação de Heap, vazamento de memória (Memory Leaks), alocações para DMA e limitações de PSRAM.",
},
{
id: "boot-flash",
nome: "Bootloader & Gravação",
icone: "Terminal",
cor: "emerald",
descricao: "Falha de comunicação serial (DTR/RTS), erros de leitura SPI Flash, incompatibilidade de clock e partições.",
},
{
id: "conectividade",
nome: "Conectividade & Protocolos",
icone: "Wifi",
cor: "cyan",
descricao: "Wi-Fi Beacon Timeout, falhas de handshake TLS/HTTPS por falta de relógio SNTP e instabilidade de sinal.",
},
];
export const sintomasRapidos = [
{ id: "bootloop", rotulo: "🔄 Reiniciando em loop (Bootloop)", categoria: "todos" },
{ id: "wifi-crash", rotulo: "📶 Trava ao ligar Wi-Fi/BLE", categoria: "alimentacao" },
{ id: "upload-fail", rotulo: "🔌 Falha ao gravar código (Upload)", categoria: "boot-flash" },
{ id: "random-freeze", rotulo: "⏱️ Trava após minutos/horas", categoria: "memoria" },
{ id: "null-pointer", rotulo: "💥 Guru Meditation (Crash)", categoria: "cpu-panics" },
{ id: "wdt-reset", rotulo: "🐕 Watchdog Triggered", categoria: "freertos" },
{ id: "tls-fail", rotulo: "🔒 Falha em HTTPS / SSL", categoria: "conectividade" },
];
export const errosCatalogo = [
{
id: "brownout-reset",
titulo: "Brownout Detector Reset (Queda de Tensão)",
categoria: "alimentacao",
sintomas: ["bootloop", "wifi-crash"],
gravidade: "Alta",
logSerial: "Brownout detector was triggered\n\nrst:0xc (SW_CPU_RESET),boot:0x13 (SPI_FAST_FLASH_BOOT)",
resumo: "A tensão de alimentação do ESP32 caiu abaixo do limiar operacional seguro (aproximadamente 2.8V ~ 2.43V).",
causaRaiz:
"Quando o rádio Wi-Fi ou BLE é ligado, o estágio de potência (PA) consome pulsos de corrente muito rápidos que podem superar 500mA em menos de 1 milissegundo. Se a fonte de alimentação, os cabos USB longos/finos, os trilhos de protoboard ou o regulador LDO não fornecerem corrente instantânea com baixa impedância, a tensão no pino 3V3 do chip afunda momentaneamente, disparando o circuito comparador interno de Brownout para evitar corrupção de memória Flash e estados indeterminados.",
gambiarra: {
titulo: "Desativar o Brownout por software",
descricao: "Chamar WRITE_PERI_REG(RTC_CNTL_BROWN_OUT_REG, 0) para silenciar o erro no código.",
consequencia: "Extremamente perigoso: o ESP32 continuará executando instruções sob subtensão, corrompendo a memória Flash SPI, travando registradores e causando comportamento imprevisível em campo sem aviso.",
},
solucaoEngenharia: {
titulo: "Dimensionamento correto de hardware e desacoplamento",
passos: [
"Adicione um capacitor de tântalo ou cerâmico de 10µF a 100µF de baixo ESR (Low-ESR) em paralelo com um cerâmico de 100nF o mais próximo possível dos pinos 3V3 e GND do ESP32.",
"Utilize reguladores LDO projetados para picos de no mínimo 600mA a 1A com excelente resposta a transitórios (ex: AP2112K, ME6211, TPS73733 ou XC6206 robusto). Evite clones de baixa qualidade do antigo AMS1117.",
"Evite alimentar o ESP32 via trilhos compridos de protoboard com cabos jumper finos quando o Wi-Fi estiver transmitindo em potência máxima.",
],
},
codigoArduino: `// NÃO desative o Brownout! Se precisar reduzir o pico em testes:
#include <WiFi.h>
void setup() {
Serial.begin(115200);
// Opcional: Reduzir ligeiramente a potência máxima de transmissão do Wi-Fi
// de 20dBm para 17dBm diminui o pico de corrente em ~20%
WiFi.setTxPower(WIFI_POWER_17dBm);
WiFi.begin("SSID", "SENHA");
}`,
codigoEspIdf: `// Configuração no ESP-IDF:
#include "esp_wifi.h"
#include "esp_log.h"
void app_main(void) {
// No menuconfig: Component config -> ESP32-specific -> Hardware Brownout Reset (Mantenha sempre ATIVO)
// Para mitigar picos em ambientes com limite de potência:
esp_wifi_set_max_tx_power(68); // 68 = 17dBm (unidades de 0.25dBm)
}`,
dicaPro: "Se estiver projetando sua própria PCB, coloque um plano de terra sólido sob o módulo e trilhas de alimentação com largura mínima de 0.5mm a 1mm para suportar os surtos de corrente sem queda resistiva (IR drop).",
},
{
id: "strapping-pins",
titulo: "Conflito de Strapping Pins no Boot",
categoria: "alimentacao",
sintomas: ["bootloop", "upload-fail"],
gravidade: "Crítica",
logSerial: "rst:0x1 (POWERON_RESET),boot:0x12 (SPI_FAST_FLASH_BOOT)\nflash read err, 1000\nwaiting for download",
resumo: "O ESP32 não inicializa o firmware da Flash ou entra acidentalmente em modo de download porque um pino de strapping foi forçado a um nível lógico incorreto no reset.",
causaRaiz:
"No momento exato em que o pino EN/CHIP_PU sobe (Power-On Reset), a CPU amostra o estado de pinos específicos (Strapping Pins) para configurar tensão de Flash (1.8V vs 3.3V), modo de boot (Download UART vs SPI Flash) e saída de clock. Se você conectar sensores, relés ou LEDs com pull-up/pull-down nesses pinos, o microcontrolador lerá um nível lógico inválido e travará o boot.",
gambiarra: {
titulo: "Colocar delays no código ou desconectar na mão toda vez que liga",
descricao: "Tentar tratar o problema via software no setup() ou depender de desconectar fios manualmente antes de cada reinicialização.",
consequencia: "Inútil: os Strapping Pins são lidos por hardware antes mesmo da CPU carregar qualquer linha do seu código ou do bootloader.",
},
solucaoEngenharia: {
titulo: "Mapeamento seguro de GPIOs e isolamento no reset",
passos: [
"ESP32 Clássico: Evite usar GPIO 0 (Boot), GPIO 2 (deve estar LOW ou flutuando no flash), GPIO 12 (define tensão de Flash: se puxado para HIGH no reset, queima/trava a flash de 3.3V) e GPIO 15 (silencia mensagens de log de boot se LOW).",
"ESP32-C3: Evite puxar GPIO 8 para LOW ou GPIO 9 para LOW no reset.",
"ESP32-S3: Atenção com GPIO 0, GPIO 45 (tensão VDD_SPI) e GPIO 46.",
"Se for estritamente necessário usar esses pinos para periféricos, utilize transistores ou buffers para isolá-los até que o sinal EN esteja totalmente estabilizado.",
],
},
codigoArduino: `// RECOMENDAÇÃO DE PINAGEM SEGURA (ESP32 Clássico):
// Pinos ideais para I2C / SPI / Sensores gerais:
// SDA: GPIO 21 | SCL: GPIO 22
// SPI: MOSI 23 | MISO 19 | SCK 18 | CS 5
// ATENÇÃO: GPIOs 34, 35, 36 (VP), 39 (VN) são APENAS ENTRADA (sem pull-up interno).
// Nunca tente usá-los como pinMode(..., OUTPUT); nem para I2C/SPI.`,
codigoEspIdf: `// No ESP-IDF, proteja a configuração de tensão de Flash via eFuse (CUIDADO: IRREVERSÍVEL):
// Se você usa ESP32 clássico e precisa liberar GPIO12, pode queimar o eFuse de 3.3V:
// espefuse.py --port /dev/ttyUSB0 set_flash_voltage 3.3V
// Isso desativa a amostragem de hardware no GPIO12 para sempre.`,
dicaPro: "Lembre-se sempre: pinos 'Input-Only' (GPI 34 a 39 no ESP32 clássico) não possuem circuitos de pull-up/pull-down de silício nem transistores de saída. Sensores analógicos ou botões neles exigem resistores de pull-up externos.",
},
{
id: "guru-load-prohibited",
titulo: "Guru Meditation: LoadProhibited / StoreProhibited",
categoria: "cpu-panics",
sintomas: ["bootloop", "null-pointer"],
gravidade: "Crítica",
logSerial: "Guru Meditation Error: Core 0 panic'ed (LoadProhibited). Exception was unhandled.\nCore 0 register dump:\nPC : 0x400d14bc PS : 0x00060830 A0 : 0x800d1512 A1 : 0x3ffb1f20 \nEXCVADDR: 0x00000000",
resumo: "A CPU tentou ler (Load) ou escrever (Store) em um endereço de memória inacessível ou inválido, tipicamente o endereço nulo 0x00000000.",
causaRaiz:
"Essa é a clássica desreferência de ponteiro nulo (Null Pointer Dereference) ou uso de ponteiro solto (Dangling Pointer). O registrador EXCVADDR indica o endereço que causou a falha: se EXCVADDR for 0x00000000 ou muito próximo de zero (ex: 0x00000008, que é um offset de struct), significa que você tentou acessar membros de um objeto/ponteiro que não foi inicializado ou cujo malloc/criação falhou.",
gambiarra: {
titulo: "Adicionar delays aleatórios ou reiniciar no loop sem tratar",
descricao: "Achar que é problema de temporização e encher o código de delay() ou mascarar o reinício automático sem investigar a causa.",
consequencia: "O sistema continuará quebrando aleatoriamente em produção quando a condição de ponteiro nulo se repetir.",
},
solucaoEngenharia: {
titulo: "Validação defensiva e Decodificação de Backtrace",
passos: [
"Verifique sempre se retornos de funções de alocação ou busca (malloc, WiFi.client(), JSON parsing, xQueueReceive) não retornaram NULL antes de acessar seus campos.",
"Instale o 'Espressif Exception Decoder' na sua IDE (Arduino IDE / PlatformIO) ou use o comando 'xtensa-esp32-elf-addr2line -pfia -e firmware.elf 0x400d14bc' para descobrir exatamente a linha do seu código .c/.cpp onde o crash aconteceu.",
"No ESP-IDF, execute 'idf.py monitor' que decodifica o backtrace de pilha automaticamente em tempo real.",
],
},
codigoArduino: `// PROGRAMAÇÃO DEFENSIVA:
struct SensorData {
float temperatura;
float umidade;
};
void processarSensor(SensorData* sensor) {
// Verificação obrigatória contra LoadProhibited:
if (sensor == nullptr) {
Serial.println("[ERRO] Ponteiro de sensor nulo!");
return;
}
Serial.printf("Temp: %.1f C\\n", sensor->temperatura);
}`,
codigoEspIdf: `// No ESP-IDF, use ESP_ERROR_CHECK ou assertivas explícitas:
#include "esp_log.h"
#include "esp_check.h"
static const char* TAG = "SENSOR";
esp_err_t ler_dado(char* buffer, size_t max_len) {
ESP_RETURN_ON_FALSE(buffer != NULL, ESP_ERR_INVALID_ARG, TAG, "Buffer nulo fornecido!");
// Operação segura...
return ESP_OK;
}`,
dicaPro: "Se EXCVADDR for algo como 0x3fxxxxxx ou 0x40xxxxxx mas seu código falha, você pode estar acessando um array fora do limite (buffer overflow) que sobrescreveu ponteiros na memória.",
},
{
id: "guru-iram-cache",
titulo: "Cache Error / InstructionFetchError em ISRs",
categoria: "cpu-panics",
sintomas: ["bootloop", "null-pointer"],
gravidade: "Alta",
logSerial: "Guru Meditation Error: Core 0 panic'ed (Cache disabled but cached memory region accessed)\nGuru Meditation Error: Core 0 panic'ed (InstructionFetchError)",
resumo: "Uma rotina de interrupção (ISR) tentou executar código ou ler dados da Flash SPI enquanto a Flash estava desativada para uma operação de escrita/leitura.",
causaRaiz:
"Por padrão, o código do ESP32 fica armazenado na memória Flash externa e é carregado sob demanda para a CPU através da memória Cache interna. Quando o ESP32 grava dados na Flash (como em gravações NVS, LittleFS, SPIFFS ou atualização OTA), o controlador desativa o Cache de instruções temporariamente. Se uma interrupção de hardware (timer, GPIO, etc.) disparar nesse instante e a função ISR estiver na Flash, a CPU não consegue buscar a próxima instrução e entra em pânico imediato.",
gambiarra: {
titulo: "Desativar interrupções antes de gravar na Flash",
descricao: "Tentar envolver operações de gravação de arquivos em blocos de noInterrupts() indiscriminadamente.",
consequencia: "Gera perda de pulsos de sensores de alta velocidade, instabilidade nos timers do sistema e potenciais estouros de Watchdog.",
},
solucaoEngenharia: {
titulo: "Alocar ISRs e constantes em IRAM / DRAM",
passos: [
"Decore TODA função de callback de interrupção com o atributo IRAM_ATTR (ou IRAM_ATTR static void...).",
"Garanta que variáveis ou strings acessadas dentro da ISR também residam em DRAM (evite usar strings literais diretas dentro da ISR sem a tag DRAM_ATTR no ESP-IDF).",
"Nunca chame funções pesadas de bibliotecas externas (como Serial.print ou cálculos de float pesados) dentro de uma ISR.",
],
},
codigoArduino: `// ISR declarada corretamente na IRAM:
volatile uint32_t contadorPulsos = 0;
void IRAM_ATTR tratarInterrupcaoGPIO() {
contadorPulsos++;
}
void setup() {
pinMode(4, INPUT_PULLUP);
attachInterrupt(digitalPinToInterrupt(4), tratarInterrupcaoGPIO, FALLING);
}`,
codigoEspIdf: `// No ESP-IDF:
#include "esp_attr.h"
#include "driver/gpio.h"
static volatile uint32_t pulsos = 0;
// Função colocada explicitamente na memória interna IRAM:
static void IRAM_ATTR gpio_isr_handler(void* arg) {
pulsos++;
}
void app_main(void) {
gpio_install_isr_service(ESP_INTR_FLAG_IRAM);
gpio_isr_handler_add(GPIO_NUM_4, gpio_isr_handler, NULL);
}`,
dicaPro: "Se você passar ESP_INTR_FLAG_IRAM ao registrar a interrupção no ESP-IDF, qualquer função chamada por essa interrupção (inclusive funções do FreeRTOS) DEVE suportar IRAM (ex: xQueueSendFromISR).",
},
{
id: "freertos-twdt",
titulo: "Task Watchdog Timer (TWDT) Timeout",
categoria: "freertos",
sintomas: ["bootloop", "wdt-reset"],
gravidade: "Crítica",
logSerial: "E (10532) task_wdt: Task watchdog got triggered. The following tasks/users did not reset the watchdog in time:\nE (10532) task_wdt: - IDLE0 (CPU 0)\nTask watchdog got triggered. System will be reset.",
resumo: "Uma tarefa de alta prioridade entrou em um loop bloqueante e não cedeu tempo de CPU, impedindo que a tarefa IDLE alimente o cão de guarda.",
causaRaiz:
"O FreeRTOS no ESP32 executa uma tarefa essencial de prioridade 0 chamada IDLE em cada núcleo de CPU. O Task Watchdog monitora se a tarefa IDLE consegue rodar periodicamente (por padrão, a cada 5 segundos). Se você criar uma tarefa com prioridade >= 1 que contenha um loop while(1) sem nenhuma chamada bloqueante (vTaskDelay, leitura de fila, semáforo), a CPU fica 100% ocupada e o Watchdog dispara um reset de emergência.",
gambiarra: {
titulo: "Desabilitar o Task Watchdog no menuconfig ou aumentar para 60s",
descricao: "Desligar o TWDT para o código parar de reiniciar.",
consequencia: "Se o seu código travar em um loop infinito ou deadlock, o microcontrolador ficará congelado para sempre, exigindo que alguém vá até o local desligar da tomada.",
},
solucaoEngenharia: {
titulo: "Arquitetura orientada a eventos e concessão de CPU (Yield)",
passos: [
"Nunca utilize loops de espera ativa vazios while(!condicao); em tarefas FreeRTOS.",
"Sempre insira vTaskDelay(pdMS_TO_TICKS(10)) ou espere por um evento através de xQueueReceive(), ulTaskNotifyTake() ou xSemaphoreTake() com timeout.",
"Se for necessário executar um processamento matemático contínuo e pesado, alimente o Watchdog explicitamente com esp_task_wdt_reset().",
],
},
codigoArduino: `// FORMA CORRETA NO ARDUINO COM FREERTOS:
void minhaTarefa(void *pvParameters) {
for (;;) {
// Executa a leitura do sensor
float valor = analogRead(36);
// LIBERA a CPU para o FreeRTOS e a tarefa IDLE:
vTaskDelay(pdMS_TO_TICKS(100)); // Aguarda 100ms de forma não-bloqueante
}
}`,
codigoEspIdf: `// ESP-IDF com Task Watchdog:
#include "esp_task_wdt.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
void tarefa_processamento(void *pvParameters) {
// Opcional: Inscrever a própria tarefa no TWDT
esp_task_wdt_add(NULL);
while (1) {
// Processamento...
// Alimenta o Watchdog se o bloco for longo:
esp_task_wdt_reset();
vTaskDelay(pdMS_TO_TICKS(50));
}
}`,
dicaPro: "No Arduino, a função delay(ms) na verdade chama internamente o vTaskDelay do FreeRTOS. Porém, loops que usam delayMicroseconds() ou operações puras de CPU não cedem tempo e podem disparar o TWDT se durarem mais de alguns segundos.",
},
{
id: "freertos-stack-overflow",
titulo: "Stack Overflow (Estouro de Pilha da Tarefa)",
categoria: "freertos",
sintomas: ["bootloop", "null-pointer"],
gravidade: "Crítica",
logSerial: "Guru Meditation Error: Core 1 panic'ed (Unhandled debug exception).\nDebug exception reason: Stack canary watchpoint triggered (minha_tarefa)",
resumo: "A tarefa consumiu mais memória de pilha (Stack) do que a quantidade alocada em sua criação, corrompendo o canário de proteção do RTOS.",
causaRaiz:
"Cada tarefa no FreeRTOS recebe um bloco fixo de memória contígua para suas variáveis locais, argumentos de funções e endereços de retorno de chamadas. Se você declarar arrays grandes (ex: char buffer[2048];), instanciar buffers JSON pesados (ex: DynamicJsonDocument doc(4096);), ou fizer chamadas recursivas profundas dentro de uma tarefa configurada com stack padrão de 2048 bytes, a pilha ultrapassa o limite e sobrescreve áreas críticas do sistema.",
gambiarra: {
titulo: "Colocar 64KB de stack em todas as tarefas sem critério",
descricao: "Alocar stacks gigantescas para não se preocupar com estouro.",
consequencia: "A memória interna SRAM do ESP32 (cerca de 320KB úteis) se esgotará rapidamente, causando falhas de inicialização do Wi-Fi e alocação de heap.",
},
solucaoEngenharia: {
titulo: "Dimensionamento consciente e monitoramento com HighWaterMark",
passos: [
"Meça o consumo real da pilha usando a função uxTaskGetStackHighWaterMark(). Ela retorna o menor número de bytes/words livres restantes que a pilha teve desde o boot.",
"Mova buffers volumosos da pilha para a Heap dinâmica (usando malloc/free temporário) ou declare-os como static fora do escopo da função se forem reutilizados.",
"Tarefas que utilizam conexões seguras TLS/HTTPS ou MQTT com SSL exigem no mínimo 6KB a 8KB de stack devido à complexidade das rotinas criptográficas do MbedTLS.",
],
},
codigoArduino: `// MONITORANDO O STACK NO ARDUINO:
void tarefaSensor(void *pvParameters) {
for (;;) {
// Código da tarefa...
// Verifica o espaço livre mínimo da pilha (em Words no FreeRTOS clássico / Bytes no ESP32)
UBaseType_t espacoLivre = uxTaskGetStackHighWaterMark(NULL);
Serial.printf("[STACK] Espaço livre mínimo: %d bytes\\n", (int)espacoLivre);
vTaskDelay(pdMS_TO_TICKS(5000));
}
}
void setup() {
Serial.begin(115200);
// Aloca 4096 bytes (4KB) de forma equilibrada:
xTaskCreatePinnedToCore(tarefaSensor, "tarefaSensor", 4096, NULL, 1, NULL, 1);
}`,
codigoEspIdf: `// Habilite a checagem dupla de Stack no menuconfig:
// Component config -> FreeRTOS -> Set a end of stack check -> Option 2 (Check by Stack Canary)
// Isso detecta o estouro ANTES que dados sejam corrompidos em tempo de execução.`,
dicaPro: "Se a sua função precisa processar um JSON ou pacote HTTP esporadicamente, aloque o buffer no Heap apenas durante o processamento e libere-o logo em seguida, em vez de inflar o Stack permanente da tarefa.",
},
{
id: "memoria-fragmentacao-heap",
titulo: "Fragmentação de Heap e Memory Leaks",
categoria: "memoria",
sintomas: ["random-freeze", "tls-fail"],
gravidade: "Alta",
logSerial: "E (45892) mbedtls: mbedtls_ssl_setup returned -0x7f00\nE (45892) esp-tls: create_ssl_handle failed\nE (45893) HTTP_CLIENT: Connection failed, out of memory",
resumo: "O microcontrolador falha ao alocar memória mesmo reportando uma quantidade total de memória livre aparentemente alta.",
causaRaiz:
"A memória Heap fica fragmentada quando ocorrem milhares de alocações e desalocações de tamanhos variados ao longo do tempo (muito comum ao usar objetos String do Arduino com concatenação contínua de texto em loops). O heap se divide em pequenos blocos livres separados por blocos ocupados. Quando o driver de Wi-Fi ou a biblioteca TLS precisa de um único bloco contíguo de 40KB para o buffer de criptografia SSL, o malloc falha mesmo que existam 100KB de memória livre total dispersa.",
gambiarra: {
titulo: "Reiniciar o ESP32 periodicamente com ESP.restart() a cada X horas",
descricao: "Agendar um reboot diário para limpar a memória fragmentada.",
consequencia: "Apenas camufla o defeito: em condições de pico de requisições o dispositivo cairá muito antes do reboot programado.",
},
solucaoEngenharia: {
titulo: "Evitar strings dinâmicas e utilizar buffers estáticos pré-alocados",
passos: [
"No Arduino, evite a classe String. Utilize buffers de caracteres em estilo C (char buffer[128];) com funções seguras como snprintf().",
"Monitore a fragmentação acompanhando a relação entre a memória livre total (heap_caps_get_free_size) e o maior bloco contíguo livre (heap_caps_get_largest_free_block).",
"Em firmwares complexos no ESP-IDF, ative o Heap Memory Tracing (CONFIG_HEAP_TRACING) para listar exatamente quais arquivos e linhas esqueceram de chamar free().",
],
},
codigoArduino: `// COMPARAÇÃO:
// ❌ EVITE (Gera fragmentação severa de Heap):
// String json = "{\\"temp\\":";
// json += String(temperatura) + "}";
// ✅ CORRETO (Sem alocação dinâmica no Heap):
char payload[64];
snprintf(payload, sizeof(payload), "{\\"temp\\":%.2f,\\"umid\\":%.2f}", temperatura, umidade);
// Monitoramento:
Serial.printf("Heap Livre: %u | Maior Bloco: %u\\n",
ESP.getFreeHeap(),
ESP.getMaxAllocHeap());`,
codigoEspIdf: `// Monitoramento de Heap no ESP-IDF:
#include "esp_heap_caps.h"
#include "esp_log.h"
void inspecionar_memoria(void) {
size_t livre_total = heap_caps_get_free_size(MALLOC_CAP_8BIT);
size_t maior_bloco = heap_caps_get_largest_free_block(MALLOC_CAP_8BIT);
ESP_LOGI("HEAP", "Total livre: %d bytes | Maior bloco continuo: %d bytes", livre_total, maior_bloco);
}`,
dicaPro: "Se o maior bloco contíguo livre for menor que 35KB ~ 40KB, conexões HTTPS/TLS modernas falharão durante o handshake de certificados.",
},
{
id: "memoria-dma-psram",
titulo: "Falha de Alocação DMA em Memória Externa (PSRAM)",
categoria: "memoria",
sintomas: ["bootloop", "random-freeze"],
gravidade: "Alta",
logSerial: "E (1204) spi_master: spi_bus_initialize: DMA channel not compatible with external RAM buffer\nESP_ERROR_CHECK failed: esp_err_t 0x102 (ESP_ERR_INVALID_ARG)",
resumo: "O firmware tentou passar um buffer alocado em PSRAM externa para um periférico com DMA (SPI, I2S, LCD, SDMMC) que só tem acesso à SRAM interna.",
causaRaiz:
"Nos chips da família ESP32 (especialmente ESP32 clássico, ESP32-S2 e ESP32-S3 com barramento padrão), o controlador de DMA dos periféricos não possui barramento físico com acesso direto à memória PSRAM externa conectada via SPI/QSPI. O DMA exige que os buffers de transmissão e recepção estejam obrigatoriamente localizados na SRAM interna do chip.",
gambiarra: {
titulo: "Desativar o DMA nos periféricos",
descricao: "Configurar o barramento SPI/I2S para operar em modo por polling sem DMA.",
consequencia: "A taxa de transferência cai drasticamente e a CPU fica 100% ocupada durante o envio de frames para displays ou streaming de áudio.",
},
solucaoEngenharia: {
titulo: "Alocação explícita com MALLOC_CAP_DMA e MALLOC_CAP_INTERNAL",
passos: [
"Sempre aloque buffers dedicados a DMA utilizando a função heap_caps_malloc(tamanho, MALLOC_CAP_DMA | MALLOC_CAP_INTERNAL).",
"Mantenha dados pesados não-DMA (como imagens decodificadas, modelos de machine learning e áudio em repouso) na PSRAM, e copie blocos menores para buffers de DMA internos sob demanda (ping-pong buffering).",
],
},
codigoArduino: `// Alocando buffer para DMA no Arduino ESP32:
#include <esp_heap_caps.h>
uint8_t* dmaBuffer = nullptr;
void setup() {
size_t tamanho = 4096;
// Garante que o buffer reside na SRAM interna compatível com DMA:
dmaBuffer = (uint8_t*) heap_caps_malloc(tamanho, MALLOC_CAP_DMA | MALLOC_CAP_INTERNAL);
if (dmaBuffer == nullptr) {
Serial.println("[ERRO] Falha ao alocar buffer DMA na memória interna!");
}
}`,
codigoEspIdf: `// ESP-IDF:
#include "esp_heap_caps.h"
uint8_t* alocar_buffer_spi(size_t len) {
return (uint8_t*) heap_caps_aligned_alloc(4, len, MALLOC_CAP_DMA | MALLOC_CAP_INTERNAL);
}`,
dicaPro: "No ESP32-P4, a arquitetura avançada de barramento AXI permite DMA em certas regiões externas, mas para toda a linha ESP32/S2/S3/C3 a regra da SRAM interna é estrita.",
},
{
id: "boot-failed-connect",
titulo: "Failed to connect to ESP32: Timed out waiting for packet header",
categoria: "boot-flash",
sintomas: ["upload-fail"],
gravidade: "Média",
logSerial: "A fatal error occurred: Failed to connect to ESP32: Timed out waiting for packet header\nesptool.py v4.6\nConnecting...................",
resumo: "A ferramenta de gravação (esptool / Arduino IDE / PlatformIO) não conseguiu colocar o microcontrolador no modo de bootloader UART.",
causaRaiz:
"Para iniciar a gravação do firmware, o chip de conversão USB-Serial (CH340, CP2102, FTDI) utiliza as linhas DTR e RTS para puxar o pino GPIO 0 para LOW enquanto pulsa o pino EN/RST para LOW e depois HIGH. Em muitas placas de desenvolvimento populares, o circuito de temporização RC (resistor-capacitor) do pino EN não possui capacitância suficiente para segurar o reset durante a transição do sinal DTR.",
gambiarra: {
titulo: "Ficar segurando o botão BOOT com o dedo a cada upload",
descricao: "Pressionar o botão físico BOOT/GPIO0 toda vez que o compilador terminar de gerar o binário.",
consequencia: "Inconveniente para desenvolvimento ágil e impossível em placas seladas ou embutidas dentro de gabinetes.",
},
solucaoEngenharia: {
titulo: "Capacitor de 10µF no pino EN ou verificação do cabo de dados",
passos: [
"Conecte um capacitor eletrolítico ou cerâmico de 10µF (ou 1µF a 10µF) entre o pino EN (RST) e o GND da placa. Isso estabiliza a rampa de reset automático.",
"Verifique se o cabo USB possui linhas de dados (D+ e D-) ativas. Muitos cabos de carregador de celular baratos possuem apenas os fios de alimentação VBUS e GND.",
"No Linux, certifique-se de que seu usuário pertence ao grupo 'dialout' (sudo usermod -a -G dialout $USER) e que o serviço 'brltty' não está sequestrando a porta serial.",
],
},
codigoArduino: `// Dica de Linha de Comando para testar a comunicação serial com o esptool:
// python -m esptool --port /dev/ttyUSB0 chip_id
// Se retornar o MAC e o modelo do chip, a comunicação física está perfeita.`,
codigoEspIdf: `// No ESP-IDF:
// idf.py -p /dev/ttyUSB0 -b 460800 flash
// Se o upload falhar em altas velocidades, reduza a taxa de baud rate para 115200:
// idf.py -p /dev/ttyUSB0 -b 115200 flash`,
dicaPro: "Se estiver usando módulos ESP32-S3 ou C3 com USB nativo (Direct USB-JTAG), certifique-se de que a porta correta selecionada é a porta CDC/JTAG e não a UART legada.",
},
{
id: "tls-sntp-clock-fail",
titulo: "Falha de Handshake TLS/HTTPS por Relógio Desincronizado",
categoria: "conectividade",
sintomas: ["tls-fail", "random-freeze"],
gravidade: "Alta",
logSerial: "E (15420) esp-tls-mbedtls: mbedtls_ssl_handshake returned -0x2700\nE (15420) esp-tls: Failed to verify peer certificate!\nE (15421) esp-tls: Verification info: The certificate is not yet valid (or has expired)",
resumo: "Conexões seguras HTTPS, AWS IoT, MQTT com TLS ou APIs Web falham porque o relógio interno do ESP32 está na data padrão de 01/01/1970.",
causaRaiz:
"Certificados de segurança digitais (X.509 SSL/TLS) possuem um período estrito de validade (Not Before e Not After). Como o ESP32 não possui uma bateria de RTC externa por padrão, ao ligar seu relógio interno sempre inicia na época Unix zero (1970). Quando o cliente MbedTLS valida a data do certificado do servidor, considera que o certificado 'ainda não é válido' (pois 2026 é no futuro em relação a 1970) e aborta a conexão criptografada imediatamente com o erro -0x2700 (MBEDTLS_ERR_X509_CERT_VERIFY_FAILED).",
gambiarra: {
titulo: "Desativar a validação de certificados SSL (Insecure / No-Verify)",
descricao: "Usar client.setInsecure() no Arduino ou desabilitar checagem de CA no ESP-IDF.",
consequencia: "Abre brecha gravíssima de segurança: seu dispositivo fica vulnerável a ataques de Man-in-the-Middle (MitM), permitindo que invasores interceptem senhas e firmwares.",
},
solucaoEngenharia: {
titulo: "Sincronização obrigatória de data e hora via SNTP antes de conexões TLS",
passos: [
"Logo após obter IP no Wi-Fi, inicie o cliente SNTP apontando para servidores confiáveis (ex: pool.ntp.br, time.google.com).",
"Aguarde o status do relógio atingir o estado sincronizado antes de iniciar a primeira conexão HTTPS/MQTT.",
"Configure o fuso horário (Timezone) correto para formatar logs com horários precisos.",
],
},
codigoArduino: `// SINCRONIZAÇÃO SNTP ANTES DO HTTPS NO ARDUINO:
#include <WiFi.h>
#include <time.h>
void sincronizarRelogio() {
// Configura servidores NTP (NTP Brasil e Google)
configTime(-3 * 3600, 0, "a.st1.ntp.br", "time.google.com");
Serial.print("Aguardando sincronizacao de horario");
time_t now = time(nullptr);
// Ano 2020 em Unix time é 1577836800. Se for menor, ainda está em 1970:
while (now < 1577836800) {
delay(500);
Serial.print(".");
now = time(nullptr);
}
Serial.println("\\n[OK] Horario sincronizado! Conexoes TLS liberadas.");
}`,
codigoEspIdf: `// SNTP no ESP-IDF:
#include "esp_sntp.h"
#include "esp_log.h"
void inicializar_sntp(void) {
ESP_LOGI("SNTP", "Iniciando cliente SNTP...");
esp_sntp_setoperatingmode(SNTP_OPMODE_POLL);
esp_sntp_setservername(0, "pool.ntp.br");
esp_sntp_init();
// Aguarda sincronização do relógio...
}`,
dicaPro: "Se seu projeto opera em ambientes sem acesso à internet externa (rede local isolada), configure um servidor NTP local no roteador ou adicione um módulo RTC físico como o DS3231 via I2C.",
},
];
export const regrasDecodificadorLog = [
{
padrao: /Brownout detector was triggered/i,
titulo: "Brownout Detector (Subtensão Crítica)",
tipo: "Alimentação / Hardware",
explicacao:
"A tensão no pino 3V3 do ESP32 caiu abaixo do nível seguro (geralmente por pico de corrente do rádio Wi-Fi/BLE ou fonte subdimensionada).",
acaoRecomendada:
"Adicione capacitor de 10µF a 100µF Low-ESR no VCC, use fonte estável e verifique o regulador LDO.",
},
{
padrao: /LoadProhibited|StoreProhibited/i,
titulo: "Guru Meditation: Load/StoreProhibited (Ponteiro Nulo ou Inválido)",
tipo: "CPU Panic / Exceção",
explicacao:
"Tentativa de ler ou gravar em um endereço inválido na memória (quase sempre desreferência de ponteiro NULL ou array fora de limite).",
acaoRecomendada:
"Decodifique o Backtrace com o Exception Decoder ou 'addr2line' para identificar a linha exata. Adicione checagens defensivas `if (ptr != NULL)`.",
},
{
padrao: /Cache disabled but cached memory region accessed|InstructionFetchError/i,
titulo: "Cache Error em Interrupção (ISR fora da IRAM)",
tipo: "CPU Panic / Flash",
explicacao:
"Uma rotina de interrupção (ISR) tentou rodar a partir da memória Flash enquanto ela estava ocupada gravando dados (NVS, LittleFS ou OTA).",
acaoRecomendada:
"Adicione o atributo `IRAM_ATTR` na função de interrupção e garanta que ela não acesse variáveis na Flash sem DRAM_ATTR.",
},
{
padrao: /Task watchdog got triggered/i,
titulo: "Task Watchdog Timer (TWDT) Timeout",
tipo: "FreeRTOS / Multitarefa",
explicacao:
"Uma tarefa de prioridade alta monopolizou a CPU sem ceder tempo (sem vTaskDelay ou bloqueio), impedindo o watchdog da tarefa IDLE.",
acaoRecomendada:
"Adicione `vTaskDelay(pdMS_TO_TICKS(10))` em loops longos ou use filas/semáforos em vez de loops `while(1)` contínuos.",
},
{
padrao: /Stack canary watchpoint triggered/i,
titulo: "Stack Overflow (Estouro de Pilha da Tarefa)",
tipo: "FreeRTOS / Memória",
explicacao:
"A tarefa excedeu a quantidade de memória de pilha (Stack) alocada na sua criação, corrompendo o canário de proteção.",
acaoRecomendada:
"Aumente o tamanho da pilha no `xTaskCreate` (ex: de 2048 para 4096 bytes) e mova arrays grandes para o Heap ou variáveis estáticas.",
},
{
padrao: /Timed out waiting for packet header|Failed to connect to ESP32/i,
titulo: "Falha de Conexão UART / Upload",
tipo: "Bootloader / Conexão",
explicacao:
"O computador não conseguiu colocar o ESP32 em modo de gravação via DTR/RTS ou o cabo USB não possui linhas de dados.",
acaoRecomendada:
"Coloque um capacitor de 10µF entre o pino EN e o GND, troque o cabo USB por um com fios de dados e teste segurar o botão BOOT durante a conexão.",
},
{
padrao: /The certificate is not yet valid|-0x2700/i,
titulo: "Falha de Certificado SSL/TLS (Relógio em 1970)",
tipo: "Conectividade / TLS",
explicacao:
"O ESP32 tentou autenticar um certificado HTTPS/TLS com a data zerada na época Unix (01/01/1970).",
acaoRecomendada:
"Sincronize o horário via SNTP (`configTime`) antes de iniciar requisições HTTPS seguras.",
},
{
padrao: /rst:0x1 \(POWERON_RESET\),boot:0x[0-9a-fA-F]+/i,
titulo: "Reset por Power-On / Strapping Pins",
tipo: "Hardware / Boot",
explicacao:
"O microcontrolador reiniciou e reportou o registrador de strapping pins. Se travar após isso, um pino de boot pode estar em nível lógico incorreto.",
acaoRecomendada:
"Verifique se GPIOs como 0, 2, 12, 15 (ESP32) ou 8, 9 (C3) estão conectados a resistores ou periféricos que forçam níveis inadequados no boot.",
},
];
+408
View File
@@ -0,0 +1,408 @@
/**
* Base de dados estruturada e abrangente de termos técnicos do Glossário ESP32.
* Abrange todas as séries de chips da Espressif (ESP32, S2, S3, S31, C2, C3, C5, C6, C61, H2, H4, P4)
* e seus recursos de hardware, periféricos, rádio, segurança e software.
*/
export const categoriasGlossario = [
{
id: "processamento",
nome: "Processamento, CPU & Silício",
icone: "Cpu",
emoji: "🧠",
cor: "blue",
descricao: "Arquiteturas Xtensa e RISC-V, núcleos, coprocessadores ULP, aceleradores vetoriais de IA e unidades de ponto flutuante.",
termos: [
{
termo: "Arquitetura Xtensa vs RISC-V",
definicao: "Conjunto de instruções base do processador. O ESP32 clássico, S2 e S3 usam núcleos proprietários Tensilica Xtensa (LX6/LX7). As séries modernas C (C2, C3, C5, C6, C61), H (H2, H4) e P (P4) adotam a arquitetura aberta, modular e eficiente RISC-V (32-bit).",
exemplo: "ESP32-S3 usa Xtensa LX7 dual-core, enquanto ESP32-C6 usa RISC-V single-core e ESP32-P4 usa RISC-V dual-core a 400 MHz.",
},
{
termo: "Núcleos de CPU (Dual-Core vs Single-Core)",
definicao: "Unidades centrais de processamento independentes. Chips dual-core permitem dedicar um núcleo (Core 0) exclusivamente ao rádio Wi-Fi/Bluetooth e escalonamento do FreeRTOS, deixando o segundo núcleo (Core 1) 100% livre para a aplicação e cálculos do usuário.",
exemplo: "ESP32, ESP32-S3 e ESP32-P4 são dual-core; ESP32-S2, C2, C3, C6 e H2 são single-core.",
},
{
termo: "Frequência de Clock (MHz)",
definicao: "Velocidade de processamento medida em Megahertz (MHz). O ESP-IDF ajusta dinamicamente a frequência (Dynamic Frequency Scaling - DFS) para economizar energia em momentos ociosos.",
exemplo: "O ESP32-C2 opera a até 120 MHz; ESP32, S2, S3 e C6 chegam a 160/240 MHz; e o ESP32-P4 atinge 400 MHz.",
},
{
termo: "ULP (Ultra Low Power Coprocessor)",
definicao: "Processador secundário independente de consumo ultra baixo que continua rodando instruções, lendo sensores analógicos (ADC), I2C e pinos GPIO enquanto os núcleos principais permanecem desligados em Deep Sleep.",
exemplo: "O ESP32 original tem ULP FSM; ESP32-S2, S3 e C6 possuem ULP RISC-V programável em C; ESP32-P4 possui ULP de baixa potência dedicado.",
},
{
termo: "Extensões Vetoriais de IA (Vector Extension / ESP-NN)",
definicao: "Instruções especializadas de silício que executam operações matemáticas vetoriais e matriciais com inteiros (int8/int16) em paralelo, acelerando redes neurais convolucionais (CNNs) e processamento de sinal.",
exemplo: "ESP32-S3 e ESP32-P4 aceleram inferências de reconhecimento facial, detecção de voz e modelos TensorFlow Lite for Microcontrollers.",
},
{
termo: "FPU (Floating Point Unit)",
definicao: "Unidade de processamento de ponto flutuante por hardware que realiza somas, multiplicações e divisões com números decimais (`float`) em um único ciclo de clock, sem sobrecarga de software.",
exemplo: "Presente nos núcleos Xtensa do ESP32 e ESP32-S3, e nos núcleos RISC-V de alto desempenho do ESP32-P4.",
},
{
termo: "PPA (Pixel Processing Accelerator)",
definicao: "Acelerador gráfico 2D por hardware exclusivo do ESP32-P4 para manipulação acelerada de imagens: rotação de frames, redimensionamento (scaling), mistura de canais alfa (alpha blending) e conversão de cores (RGB565, ARGB8888, YUV420).",
exemplo: "Renderização de interfaces gráficas complexas (LVGL) a 60 FPS sem ocupar tempo dos núcleos de CPU principais.",
},
],
},
{
id: "seguranca",
nome: "Segurança, Criptografia & eFuses",
icone: "ShieldCheck",
emoji: "🔒",
cor: "red",
descricao: "eFuses invioláveis, Secure Boot V2, Criptografia de Flash XTS-AES, Key Manager, ECC e proteção contra DPA.",
termos: [
{
termo: "eFuse (Electronic Fuses)",
definicao: "Conjunto de fusíveis microscópicos de silício de gravação única e irreversível (One-Time Programmable - OTP). Armazenam chaves secretas de criptografia, o endereço MAC de fábrica e bloqueiam interfaces de debug em produtos comerciais.",
exemplo: "Usado com a ferramenta `espefuse.py` para gravar chaves de Secure Boot e desativar portas JTAG antes do envio ao consumidor.",
},
{
termo: "Secure Boot (V1 e V2)",
definicao: "Protocolo de inicialização segura em que o código da ROM verifica criptograficamente a assinatura RSA-3072 ou ECDSA do bootloader e firmware antes de permitir sua execução, impedindo injeção de código adulterado.",
exemplo: "ESP32 clássico usa Secure Boot V1; do ESP32-S2 em diante todos adotam o Secure Boot V2 com múltiplas chaves de assinatura.",
},
{
termo: "Criptografia de Flash (Flash Encryption)",
definicao: "Mecanismo de hardware que cifra e decifra em tempo real o conteúdo da memória Flash SPI externa usando o algoritmo XTS-AES. Impede a leitura física do firmware caso a placa seja roubada ou a memória dessoldada.",
exemplo: "XTS-AES-128 na maioria dos chips; ESP32-S3, C5, P4 e H4 também suportam XTS-AES-256.",
},
{
termo: "Key Manager",
definicao: "Módulo de segurança avançado que provisiona, armazena e opera chaves criptográficas em silício sem nunca expô-las à memória SRAM ou ao firmware, impedindo extração de chaves mesmo sob vulnerabilidades de software.",
exemplo: "Presente nas séries de alta segurança como ESP32-S31, ESP32-C5 e ESP32-P4.",
},
{
termo: "ECC & ECDSA em Hardware",
definicao: "Aceleradores de curvas elípticas (como P-256 e P-384) e algoritmos de assinatura digital ECDSA em silício, fundamentais para conexão rápida e segura com a nuvem e requisitos de atestação do padrão Matter.",
exemplo: "ESP32-S31, C5, C61, P4, H2 e H4 possuem acelerador de hardware para ECDSA.",
},
{
termo: "HMAC & Assinatura Digital (DS)",
definicao: "Periféricos de autenticação criptográfica que calculam códigos HMAC e assinam mensagens com chaves secretas gravadas em eFuses de forma invisível para o firmware.",
exemplo: "Permite autenticação segura e rápida com AWS IoT Core e Microsoft Azure IoT sem armazenar certificados em texto plano.",
},
{
termo: "TRNG (True Random Number Generator)",
definicao: "Gerador de números aleatórios verdadeiros baseado no ruído térmico analógico dos circuitos de rádio e silício, essencial para geração de chaves de sessão em handshakes TLS/HTTPS.",
exemplo: "Disponível em todas as séries do ESP32 para garantir entropia segura em conexões HTTPS/MbedTLS.",
},
{
termo: "Proteção contra DPA (Differential Power Analysis)",
definicao: "Mecanismo físico de hardware que adiciona ruído e balanceamento no consumo de corrente elétrica do chip para impedir que invasores descubram chaves criptográficas medindo o consumo com osciloscópios.",
exemplo: "Presente nas séries ESP32-C5, C6, C61 e H2.",
},
],
},
{
id: "memoria",
nome: "Memória, Barramentos & Armazenamento",
icone: "Database",
emoji: "💾",
cor: "purple",
descricao: "SRAM interna, ROM de boot, memórias Flash SPI, PSRAM QSPI/Octal, RTC Memory e alocações de DMA.",
termos: [
{
termo: "SRAM Interna",
definicao: "Memória volátil ultrarrápida integrada diretamente no silício da CPU. Utilizada para variáveis do programa, estruturas do FreeRTOS, buffers de drivers e pilha de execução. Perde os dados ao desligar a alimentação.",
exemplo: "O ESP32 clássico possui 520 KB de SRAM; ESP32-S3 tem 512 KB; ESP32-C3 tem 400 KB; ESP32-C6 tem 512 KB; ESP32-P4 tem 768 KB.",
},
{
termo: "ROM de Boot",
definicao: "Memória somente-leitura gravada permanentemente na fábrica da Espressif contendo o First-Stage Bootloader, tabelas matemáticas e rotinas básicas de inicialização que nunca podem ser alteradas.",
exemplo: "Ao ligar, a ROM executa primeiro, verifica os Strapping Pins e carrega o bootloader do usuário da Flash.",
},
{
termo: "Flash SPI (Quad-SPI vs Octal-SPI)",
definicao: "Memória não-volátil externa onde reside o firmware da aplicação. Conectada via 4 vias (Quad SPI / QSPI) ou 8 vias de dados (Octal SPI / OPI / OSPI) com o dobro de largura de banda e taxas de transferência.",
exemplo: "ESP32-S3 e ESP32-P4 suportam memórias Flash e PSRAM em modo Octal SPI de até 120 MHz para máxima velocidade de carregamento de texturas e código.",
},
{
termo: "PSRAM (Pseudo-Static RAM Externa)",
definicao: "Memória RAM externa de alta densidade conectada via SPI/QSPI/OPI que estende a memória do microcontrolador em até dezenas de Megabytes para aplicações que demandam buffers gigantescos.",
exemplo: "ESP32-WROVER e ESP32-S3-WROOM-1 com 8 MB de PSRAM externa para decodificação JPEG de câmeras e buffers gráficos de displays TFT.",
},
{
termo: "RTC Fast & Slow SRAM",
definicao: "Blocos de memória SRAM dedicados ao domínio de energia do Real-Time Clock (RTC). Permanecem alimentados durante o modo Deep Sleep para armazenar código do ULP e variáveis marcadas com `RTC_DATA_ATTR`.",
exemplo: "Manter contadores de leituras ou dados de calibração intactos enquanto o ESP32 dorme em Deep Sleep consumindo 10 µA.",
},
{
termo: "Alocação DMA (MALLOC_CAP_DMA)",
definicao: "Memória compatível com Direct Memory Access (DMA), permitindo que periféricos (SPI, I2S, LCD, SDMMC) transfiram dados diretamente para a RAM sem consumir tempo da CPU.",
exemplo: "Na maioria dos chips ESP32, o DMA exige alocação exclusiva na SRAM interna através da flag `MALLOC_CAP_DMA | MALLOC_CAP_INTERNAL`.",
},
{
termo: "Fragmentação de Heap",
definicao: "Fenômeno em que a memória dinâmica fica dividida em blocos pequenos dispersos após múltiplas alocações e desalocações (muito comum ao usar objetos `String` do Arduino), impedindo alocações contíguas grandes para TLS/Wi-Fi.",
exemplo: "Monitorado no ESP-IDF através de `heap_caps_get_largest_free_block()` para prevenir falhas de conexão HTTPS.",
},
],
},
{
id: "conectividade",
nome: "Conectividade Sem Fio & Protocolos",
icone: "Wifi",
emoji: "📡",
cor: "emerald",
descricao: "Wi-Fi 4/6, Dual-Band 5 GHz, OFDMA, TWT, Bluetooth Classic, BLE 5.x, LE Audio, Zigbee 3.0, Thread e Matter.",
termos: [
{
termo: "Wi-Fi 6 (802.11ax)",
definicao: "Padrão moderno de rede sem fio que traz suporte a OFDMA (comunicação paralela com menor latência), modulação 1024-QAM e o recurso TWT (Target Wake Time) para economizar drasticamente a bateria de sensores IoT.",
exemplo: "Presente no ESP32-C6, ESP32-C61 e ESP32-C5.",
},
{
termo: "Wi-Fi Dual-Band (2.4 GHz e 5 GHz)",
definicao: "Capacidade de operar tanto na faixa tradicional de 2.4 GHz quanto na faixa de 5 GHz com menor congestionamento eletromagnético, canais de 20 MHz / 40 MHz e maior estabilidade de conexão.",
exemplo: "O ESP32-C5 é o primeiro chip da Espressif com rádio Wi-Fi 6 Dual-Band integrado.",
},
{
termo: "OFDMA (Orthogonal Frequency Division Multiple Access)",
definicao: "Tecnologia do Wi-Fi 6 que divide um único canal de rádio em dezenas de subportadoras menores (Resource Units), permitindo que o roteador atenda múltiplos dispositivos ESP32 simultaneamente sem colisões.",
exemplo: "Reduz a latência de resposta em ambientes residenciais ou industriais densos com dezenas de nós conectados.",
},
{
termo: "TWT (Target Wake Time)",
definicao: "Protocolo do Wi-Fi 6 onde o ESP32 e o roteador combinam horários exatos para troca de pacotes, permitindo que o microcontrolador mantenha seu rádio totalmente desligado por longos períodos sem se desconectar da rede.",
exemplo: "Sensores alimentados por bateria com ESP32-C6 operando por anos conectados ao Wi-Fi 6 sem perder sincronismo.",
},
{
termo: "Bluetooth Classic (BR/EDR)",
definicao: "Protocolo Bluetooth tradicional para transmissão contínua de áudio estéreo (A2DP), portas seriais virtuais (SPP) e dispositivos que exigem alta taxa de transferência sustentada.",
exemplo: "Implementação de caixas de som Bluetooth, receptores de áudio e teclados HID no ESP32 clássico.",
},
{
termo: "Bluetooth Low Energy (BLE 5.0 a 5.4)",
definicao: "Versão de baixo consumo elétrico do Bluetooth com suporte a taxas de 2 Mbps (2M PHY), longo alcance de centenas de metros (Coded PHY / Long Range), extensões de publicidade e redes mesh Bluetooth.",
exemplo: "ESP32-S3 suporta BLE 5.0; ESP32-C6 e ESP32-H2 suportam Bluetooth 5.4 LE.",
},
{
termo: "Bluetooth LE Audio & Auracast",
definicao: "Nova geração de áudio Bluetooth de alta fidelidade e baixíssimo consumo com o codec LC3 e suporte a transmissões de áudio para múltiplos ouvintes simultâneos (Broadcast Audio / Auracast).",
exemplo: "Disponível na série ESP32-H4 para novos ecossistemas de áudio e fones inteligentes.",
},
{
termo: "IEEE 802.15.4 (Zigbee 3.0 & Thread)",
definicao: "Padrão de rádio em 2.4 GHz de baixo consumo e baixa taxa de dados que forma a base física das redes em malha (Mesh) industriais e residenciais Zigbee 3.0 e Thread 1.3.",
exemplo: "ESP32-C6, ESP32-H2 e ESP32-H4 possuem transceptor IEEE 802.15.4 integrado no silício.",
},
{
termo: "Matter Protocol",
definicao: "Padrão universal e aberto de conectividade para automação residencial mantido pela CSA, funcionando sobre redes Wi-Fi, Ethernet e Thread, compatível nativamente com Apple Home, Google Home, Alexa e Home Assistant.",
exemplo: "Lâmpadas e tomadas inteligentes desenvolvidas com ESP-Matter no ESP32-C6 ou ESP32-H2 sem depender de nuvens proprietárias.",
},
],
},
{
id: "perifericos",
nome: "Entradas, Saídas & Periféricos",
icone: "Sliders",
emoji: "🔌",
cor: "amber",
descricao: "Pinos GPIO, strapping pins, conversores ADC/DAC, PWM/LEDC, MCPWM, RMT, PCNT, TWAI®/CAN, I2C, SPI e I2S.",
termos: [
{
termo: "GPIO (General Purpose Input/Output)",
definicao: "Pinos multifuncionais configuráveis como entradas ou saídas digitais, com resistores de pull-up/pull-down internos selecionáveis e suporte a interrupções externas em qualquer pino.",
exemplo: "Leitura de botões, chaveamento de relés e controle de LEDs com `gpio_set_level()` ou `digitalWrite()`.",
},
{
termo: "Strapping Pins",
definicao: "Pinos GPIO específicos amostrados por hardware no milissegundo em que o chip sai do reset para determinar o modo de boot (Download UART vs SPI Flash) e níveis de tensão.",
exemplo: "GPIO 0 no ESP32 clássico/S3 ou GPIO 9 no ESP32-C3: se puxados para LOW durante o reset, colocam o chip em modo de gravação de firmware.",
},
{
termo: "ADC (Analog-to-Digital Converter)",
definicao: "Conversor que transforma níveis contínuos de tensão analógica (0 a 3.3V) em valores numéricos digitais de 12 ou 13 bits através de atenuadores configuráveis (0dB, 2.5dB, 6dB, 11dB/12dB).",
exemplo: "Leitura de sensores LDR, potenciômetros, termistores e medição do nível de carga de baterias de lítio.",
},
{
termo: "DAC (Digital-to-Analog Converter)",
definicao: "Conversor que gera tensões analógicas contínuas proporcionais a valores digitais gerados por software sem necessidade de circuitos externos.",
exemplo: "Disponível no ESP32 clássico e ESP32-S2 para sintetizar áudio analógico ou sinais de controle direto.",
},
{
termo: "LEDC (LED PWM Controller)",
definicao: "Periférico dedicado a gerar múltiplos canais de modulação por largura de pulso (PWM) com controle de frequência, resolução de até 14-20 bits e transições graduais (fade por hardware) sem ocupar a CPU.",
exemplo: "Controle suave de brilho de fitas de LED RGBW e posicionamento de servomotores.",
},
{
termo: "MCPWM (Motor Control PWM)",
definicao: "Controlador PWM avançado projetado para acionamento de motores elétricos, inversores de frequência trifásicos, pontes H com tempo morto (dead-time) configurável e captura de encoders em quadratura.",
exemplo: "Controle preciso de motores sem escovas (BLDC) e robôs móveis no ESP32 clássico, S3 e P4.",
},
{
termo: "RMT (Remote Control Peripheral)",
definicao: "Transceptor e gerador de pulsos de temporização precisa em microssegundos projetado originalmente para controles remotos infravermelhos (NEC, RC5, Sony) e amplamente usado para controlar fitas de LED digitais WS2812B / NeoPixel.",
exemplo: "Atualização de centenas de LEDs NeoPixel sem desativar interrupções do sistema.",
},
{
termo: "PCNT (Pulse Counter)",
definicao: "Contador de pulsos de hardware que mede a contagem de bordas de subida/descida de sinais externos em alta frequência sem gerar interrupções por software para a CPU.",
exemplo: "Leitura de encoders rotativos industriais, tacômetros de velocidade e medidores de vazão de água.",
},
{
termo: "TWAI® (Two-Wire Automotive Interface / CAN Bus)",
definicao: "Controlador de barramento veicular e industrial CAN 2.0B compatível com a norma ISO 11898-1 integrado no silício, necessitando apenas de um transceptor físico externo (como SN65HVD230).",
exemplo: "Diagnóstico automotivo OBD-II, telemetria de veículos elétricos e automação industrial robusta.",
},
{
termo: "Touch Capacitivo",
definicao: "Pinos especializados que medem a variação da capacitância eletrostática quando um dedo se aproxima, permitindo criar botões e sliders sensíveis ao toque através de vidro ou acrílico.",
exemplo: "Painéis de comando touch modernos com até 14 canais no ESP32-S2 e S3.",
},
{
termo: "PARLIO (Parallel IO Controller)",
definicao: "Barramento paralelo configurável de alta velocidade presente nas séries mais novas (C6, H2, P4) para transferir dados síncronos com clock customizado para displays LCD, câmeras e sensores externos.",
exemplo: "Comunicação paralela eficiente com chips periféricos sem recorrer a bit-banging na CPU.",
},
],
},
{
id: "multimidia",
nome: "Multimídia, USB & Interfaces Avançadas",
icone: "Sparkles",
emoji: "✨",
cor: "pink",
descricao: "USB OTG nativo, USB Serial/JTAG, USB High-Speed 480Mbps, MIPI CSI/DSI, codificador H.264 e Ethernet MAC.",
termos: [
{
termo: "USB OTG (On-The-Go) Nativo",
definicao: "Controlador USB Full-Speed (12 Mbps) embutido no chip que permite ao ESP32 operar como dispositivo periférico (teclado HID, mouse, pendrive Mass Storage) ou como USB Host (lendo pen drives ou periféricos).",
exemplo: "Nativamente disponível no ESP32-S2 e ESP32-S3 para criação de teclados customizados e emuladores.",
},
{
termo: "USB Serial/JTAG Nativo",
definicao: "Interface USB integrada no silício que fornece simultaneamente uma porta serial de comunicação para o console e uma sonda de depuração JTAG completa sem a necessidade de chips conversores externos (CH340/CP2102).",
exemplo: "Permite gravar firmware e debugar com breakpoints no VS Code no ESP32-C3, S3, C6 e H2 apenas com o cabo USB conectado aos pinos D+/D-.",
},
{
termo: "USB 2.0 High-Speed (480 Mbps)",
definicao: "Controlador USB 2.0 de alta velocidade capaz de transferir até 480 Mbps com PHY integrado, proporcionando largura de banda 40x superior ao USB Full-Speed.",
exemplo: "Exclusivo do ESP32-P4 para streaming de vídeo em alta definição e transferência em massa de dados.",
},
{
termo: "MIPI CSI & MIPI DSI",
definicao: "Interfaces seriais diferenciais de alta velocidade padrão da indústria de smartphones: MIPI-CSI (Camera Serial Interface) para sensores de câmera de alta resolução e MIPI-DSI (Display Serial Interface) para telas sensíveis ao toque.",
exemplo: "Conexão de telas de 1080p e câmeras de alta taxa de quadros no SoC ESP32-P4.",
},
{
termo: "Codificador de Vídeo H.264 por Hardware",
definicao: "Bloco acelerador dedicado que comprime fluxos de vídeo brutos de câmeras no formato padrão H.264 (AVC) em tempo real em resoluções de até 1080p a 30 FPS sem sobrecarregar a CPU.",
exemplo: "Câmeras de segurança inteligentes e porteiros eletrônicos com compressão eficiente no ESP32-P4.",
},
{
termo: "Ethernet MAC 10/100 Mbps",
definicao: "Controlador de camada de enlace de dados (Media Access Control) de rede cabeada integrado no silício, que exige apenas um chip transceptor físico externo (PHY como LAN8720 ou IP101) via interface RMII.",
exemplo: "Conexão Ethernet RJ45 estável imune a ruídos de rádio para automação predial no ESP32 clássico e ESP32-P4.",
},
{
termo: "Interface de Câmera DVP (8-bit / 16-bit)",
definicao: "Barramento paralelo com sinais VSYNC, HREF, PCLK e barramento de dados com suporte a DMA para captura direta de frames de módulos de câmera populares (OV2640, OV5640).",
exemplo: "Usado em placas ESP32-CAM e ESP32-S3-CAM para captura de fotos e streaming de vídeo MJPEG.",
},
],
},
{
id: "energia",
nome: "Gerenciamento de Energia & Sleep Modes",
icone: "BatteryCharging",
emoji: "🔋",
cor: "teal",
descricao: "Active Mode, Modem Sleep, Light Sleep, Deep Sleep, Hibernation, Brownout Detector e fontes de despertar.",
termos: [
{
termo: "Active Mode",
definicao: "Modo de operação normal onde a CPU executa instruções na frequência máxima e todos os periféricos e rádios estão prontos para uso, com consumo elétrico variando de 30 mA a ~240 mA (durante transmissões Wi-Fi).",
exemplo: "ESP32 processando áudio, atualizando displays ou transmitindo dados de sensores em tempo real.",
},
{
termo: "Modem Sleep",
definicao: "Modo em que os circuitos de rádio Wi-Fi e Bluetooth são desligados temporariamente nos intervalos entre transmissões enquanto a CPU continua rodando seu código normalmente, reduzindo o consumo para ~20 a 40 mA.",
exemplo: "Modo padrão em aplicações conectadas ao Wi-Fi que aguardam eventos de rede sem perder a associação ao roteador.",
},
{
termo: "Light Sleep",
definicao: "Modo de economia intermediário: a CPU tem seu clock pausado e os rádios são desligados, mas a memória SRAM interna, os periféricos e o estado das tarefas são preservados intactos. O chip acorda em menos de 1 ms.",
exemplo: "Consumo de ~0.8 mA em dispositivos que precisam acordar rapidamente por interrupção de botão ou timer.",
},
{
termo: "Deep Sleep",
definicao: "Modo de sono profundo em que todos os domínios digitais e a CPU são desligados, mantendo alimentado apenas o domínio RTC (e opcionalmente a RTC Memory e o ULP). O consumo cai para 5 a 15 µA e o chip reinicia a execução ao acordar.",
exemplo: "Estações meteorológicas remotas alimentadas por bateria e painel solar que realizam leituras a cada hora.",
},
{
termo: "Hibernation Mode",
definicao: "O estado de menor consumo elétrico do silício: desliga inclusive o oscilador de cristal do RTC e domínios lentos, mantendo apenas um circuito analógico de recuperação que acorda por botão externo ou reset.",
exemplo: "Consumo residual de ~5 µA em dispositivos que operam por anos com bateria botão tipo moeda (CR2032).",
},
{
termo: "Fontes de Despertar (Wakeup Sources)",
definicao: "Eventos de hardware configurados para tirar o ESP32 do modo Deep Sleep ou Light Sleep: temporizadores RTC, pinos GPIO externos (EXT0 / EXT1), toque capacitivo ou sinal do coprocessador ULP.",
exemplo: "`esp_sleep_enable_timer_wakeup(60 * 1000000)` para acordar exatamente a cada 60 segundos.",
},
{
termo: "Brownout Detector",
definicao: "Circuito comparador analógico interno de hardware que monitora a tensão de alimentação no pino 3V3 e força um reset de emergência seguro caso a tensão caia abaixo de ~2.8V para prevenir corrupção da memória Flash.",
exemplo: "Protege o microcontrolador contra resets parciais causados por fontes fracas durante picos de transmissão de rádio.",
},
],
},
{
id: "desenvolvimento",
nome: "Firmware, Boot & Sistema Operacional",
icone: "Code2",
emoji: "🚀",
cor: "rose",
descricao: "Bootloader, FreeRTOS, macros IRAM_ATTR, partições da Flash, atualização OTA com Rollback e armazenamento NVS.",
termos: [
{
termo: "Bootloader (First & Second Stage)",
definicao: "Código que executa logo após o reset elétrico. O primeiro estágio reside na ROM do chip; o segundo estágio reside na Flash, valida a tabela de partições, checa as assinaturas de segurança e carrega o firmware do usuário.",
exemplo: "Controla a alternância entre partições OTA e inicializa a memória PSRAM antes do `app_main`.",
},
{
termo: "FreeRTOS (Sistema Operacional de Tempo Real)",
definicao: "Kernel multitarefa preemptivo padrão do ESP-IDF que divide a CPU entre múltiplas tarefas (Tasks), filas de mensagens (Queues), semáforos, mutexes e timers de software com controle de prioridades.",
exemplo: "`xTaskCreatePinnedToCore()` para alocar tarefas pesadas em núcleos específicos da CPU.",
},
{
termo: "IRAM_ATTR & DRAM_ATTR",
definicao: "Atributos especiais do compilador GCC para o ESP32 que forçam funções e variáveis a residirem na memória interna ultrarrápida IRAM/DRAM em vez da Flash externa, obrigatório para rotinas de interrupção (ISRs).",
exemplo: "Evita que uma interrupção cause travamento (*Cache Error*) enquanto a Flash estiver ocupada gravando dados.",
},
{
termo: "Tabela de Partições (partitions.csv)",
definicao: "Arquivo de configuração que mapeia o espaço de armazenamento da memória Flash em blocos lógicos dedicados ao bootloader, dados NVS, arquivos LittleFS e múltiplas imagens de firmware (Factory, OTA_0, OTA_1).",
exemplo: "Personalizado para acomodar firmwares grandes com bibliotecas de Bluetooth, Wi-Fi e criptografia.",
},
{
termo: "OTA (Over-The-Air Update) com Rollback",
definicao: "Atualização sem fio de firmware onde a nova versão é gravada em uma partição secundária. Se o novo firmware falhar ao inicializar, o sistema reverte automaticamente (*Rollback*) para a versão funcional anterior.",
exemplo: "`esp_ota_mark_app_valid_cancel_rollback()` confirma que a nova atualização está saudável em campo.",
},
{
termo: "NVS (Non-Volatile Storage) & NVS Encryption",
definicao: "Mecanismo estruturado de chave-valor na memória Flash para persistir credenciais de rede, parâmetros de calibração e contadores, com suporte a criptografia em hardware via chave AES nos eFuses.",
exemplo: "Salvar nome de rede Wi-Fi e senhas criptografadas para reconexão automática após desligamento.",
},
{
termo: "LittleFS & SPIFFS",
definicao: "Sistemas de arquivos desenvolvidos para memórias Flash com nivelamento de desgaste (*wear leveling*), permitindo que a aplicação leia e grave arquivos como páginas HTML, certificados SSL e logs.",
exemplo: "Armazenar páginas HTML/CSS do servidor web embutido do ESP32 para configuração de rede via navegador.",
},
{
termo: "JTAG & OpenOCD",
definicao: "Padrão de depuração em nível de hardware que permite pausar a CPU, colocar breakpoints e inspecionar registradores e variáveis em tempo real no VS Code ou Eclipse via sondas USB-JTAG.",
exemplo: "Depuração de crashes complexos e ponteiros nulos sem depender apenas de comandos `Serial.println()`.",
},
],
},
];
+59
View File
@@ -214,4 +214,63 @@ export const jsonLd = {
})), })),
}; };
}, },
/** Aplicação web / ferramenta interativa (Seletor, Comparador, Catálogo). */
aplicacaoWeb({ nome, descricao, caminho }) {
return {
"@context": "https://schema.org",
"@type": "WebApplication",
"@id": `${SITE.url}${caminho}#app`,
name: nome,
description: descricao,
url: `${SITE.url}${caminho}`,
applicationCategory: "DeveloperApplication",
operatingSystem: "All",
browserRequirements: "Requires JavaScript",
inLanguage: "pt-BR",
author: {
"@type": "Person",
name: SITE.autor,
url: SITE.autorUrl,
},
publisher: {
"@type": "Organization",
name: "Ciência Embarcada",
url: SITE.blog,
logo: {
"@type": "ImageObject",
url: `${SITE.url}/marca/espdocs-marca.svg`,
},
},
offers: {
"@type": "Offer",
price: "0",
priceCurrency: "BRL",
},
};
},
/** Página Sobre com metadados E-E-A-T de autoria e reputação. */
paginaSobre() {
return {
"@context": "https://schema.org",
"@type": "AboutPage",
"@id": `${SITE.url}/sobre#sobre`,
name: "Sobre o ESPDocs",
description: "O ESPDocs é uma plataforma independente que reúne a documentação do ecossistema ESP32 em português brasileiro.",
url: `${SITE.url}/sobre`,
inLanguage: "pt-BR",
mainEntity: {
"@type": "Person",
name: SITE.autor,
url: SITE.autorUrl,
email: SITE.autorEmail,
sameAs: [
SITE.autorLinkedin,
SITE.repositorio,
SITE.blog,
],
},
};
},
}; };
Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.1 KiB