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
+5040 -1269

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({
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.",
@@ -13,5 +8,32 @@ export const metadata = paginaMeta({
});
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({
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.",
@@ -12,6 +7,54 @@ export const metadata = paginaMeta({
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 }) {
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}
</>
);
}
+560 -517
View File
File diff suppressed because it is too large. Load diff
+395 -121
View File
@@ -5,81 +5,197 @@ import Image from "next/image";
import {
SlidersHorizontal,
ShoppingBag,
Table2,
Cpu,
Layers,
Scale,
FileText,
Code,
RefreshCw,
Globe,
ArrowRight,
Terminal,
BookOpen,
AlertTriangle,
Terminal,
Compass,
Info
ArrowRight,
Sparkles,
CheckCircle2,
Zap,
ExternalLink,
Info,
Radio,
ShieldCheck,
Code,
HelpCircle
} from "lucide-react";
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",
description: "Responda perguntas rápidas sobre seu projeto e descubra a série ESP32 ideal em segundos.",
title: "Seletor Inteligente",
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" />,
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",
description: "Encontre placas de desenvolvimento ESP32 verificadas com filtros avançados e links seguros.",
icon: <ShoppingBag className="w-6 h-6" />,
colorClass: "text-blue-600 bg-blue-50 dark:bg-blue-950/40 dark:text-blue-400"
title: "Diagnóstico de Falhas",
badge: "30+ Soluções",
badgeColor: "bg-amber-100 text-amber-700 dark:bg-amber-950/60 dark:text-amber-300",
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",
description: "Especificações técnicas completas de cada microcontrolador organizadas para fácil leitura.",
icon: <Table2 className="w-6 h-6" />,
colorClass: "text-pink-600 bg-pink-50 dark:bg-pink-950/40 dark:text-pink-400"
},
{
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.",
title: "Comparador de Séries",
badge: "Lado a Lado",
badgeColor: "bg-indigo-100 text-indigo-700 dark:bg-indigo-950/60 dark:text-indigo-300",
description: "Compare especificações de até 4 séries simultaneamente: núcleos, clock, memória SRAM/PSRAM, interfaces e conexões.",
href: "/comparacao",
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",
description: "Acesso direto aos datasheets oficiais, manuais de referência e guias da Espressif.",
icon: <FileText className="w-6 h-6" />,
colorClass: "text-red-600 bg-red-50 dark:bg-red-950/40 dark:text-red-400"
title: "Explorador de Séries & Pinouts",
badge: "12 Chips",
badgeColor: "bg-blue-100 text-blue-700 dark:bg-blue-950/60 dark:text-blue-300",
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",
description: "Gabaritos e snippets funcionais de código para iniciar seus projetos rapidamente.",
icon: <Code className="w-6 h-6" />,
colorClass: "text-indigo-600 bg-indigo-50 dark:bg-indigo-950/40 dark:text-indigo-400"
title: "Catálogo de Placas",
badge: "Hardware Real",
badgeColor: "bg-emerald-100 text-emerald-700 dark:bg-emerald-950/60 dark:text-emerald-300",
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",
description: "Dados sempre revisados e sincronizados com os lançamentos mais recentes de silício.",
icon: <RefreshCw className="w-6 h-6" />,
colorClass: "text-teal-600 bg-teal-50 dark:bg-teal-950/40 dark:text-teal-400"
title: "Frameworks & SDKs",
badge: "C++, Python & Rust",
badgeColor: "bg-teal-100 text-teal-700 dark:bg-teal-950/60 dark:text-teal-300",
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",
description: "Navegação, termos técnicos e explicações adaptadas ao nosso idioma nativo.",
icon: <Globe className="w-6 h-6" />,
colorClass: "text-sky-600 bg-sky-50 dark:bg-sky-950/40 dark:text-sky-400"
title: "Glossário Técnico",
badge: "60+ Conceitos",
badgeColor: "bg-pink-100 text-pink-700 dark:bg-pink-950/60 dark:text-pink-300",
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">
<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 */}
<section className="text-center mb-24 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">
<section className="text-center mb-20 max-w-5xl mx-auto">
{/* 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
</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">
Organização e acesso facilitado ao ecossistema ESP32
<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">
Tudo o que você precisa para dominar o ecossistema ESP32
</h2>
<p className="text-base md:text-lg text-slate-500 dark:text-slate-400 max-w-2xl mx-auto leading-relaxed mb-12">
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.
<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">
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>
{/* Botões de Ação */}
<div className="flex flex-wrap gap-4 justify-center items-center">
{/* Botões de Ação Hero */}
<div className="flex flex-wrap gap-3.5 justify-center items-center mb-16">
<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"
>
<Compass className="w-4 h-4" />
@@ -114,67 +238,226 @@ export default function Home() {
</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"
>
<Cpu className="w-4 h-4" />
<span>Explorar Séries</span>
<span>Explorar 12 Séries</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="/frameworks"
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="/comparacao"
>
<Terminal className="w-4 h-4" />
<span>Frameworks</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>
<Scale className="w-4 h-4" />
<span>Comparar Chips</span>
</Link>
</div>
</section>
{/* Recursos Section */}
<section className="mb-24">
<div className="text-center mb-16">
<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) => (
{/* Faixa de Estatísticas */}
<div className="grid grid-cols-2 md:grid-cols-4 gap-4 max-w-4xl mx-auto">
{stats.map((stat) => (
<div
key={resource.name}
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"
key={stat.label}
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={`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}`}>
{resource.icon}
</div>
<div className="flex-1">
<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">
{resource.name}
</h3>
<p className="text-sm text-slate-500 dark:text-slate-400 leading-relaxed">
{resource.description}
</p>
</div>
<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">
{stat.value}
</div>
<div className="text-xs sm:text-sm font-bold text-slate-850 dark:text-slate-100 mb-0.5">
{stat.label}
</div>
<div className="text-[11px] text-slate-500 dark:text-slate-400 leading-tight">
{stat.desc}
</div>
</div>
))}
</div>
</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">
<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 */}
@@ -196,7 +479,7 @@ export default function Home() {
/>
</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{' '}
<a
href="https://cienciaembarcada.com.br"
@@ -206,9 +489,9 @@ export default function Home() {
>
Ciência Embarcada
</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.
</p>
@@ -216,14 +499,14 @@ export default function Home() {
href="https://cienciaembarcada.com.br"
target="_blank"
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>
<ExternalLinkIcon />
<ExternalLink className="w-4 h-4" />
</a>
</div>
{/* Disclaimer */}
{/* Disclaimer Oficial */}
<div className="border-t border-slate-300 dark:border-slate-800/80 pt-8 mt-10">
<div className="bg-slate-100/60 dark:bg-slate-900/60 border-l-4 border-purple-500 p-5 rounded-r-2xl">
<div className="flex gap-4">
@@ -241,13 +524,4 @@ export default function Home() {
<Footer />
</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({
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",
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 }) {
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}
</>
);
}
+1291 -474
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({
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.",
@@ -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"],
});
// 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" };
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 }) {
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.
*/
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 = [
{ caminho: "", prioridade: 1.0, frequencia: "weekly" },
{ caminho: "/series", prioridade: 0.9, frequencia: "weekly" },
{ caminho: "/comparacao", prioridade: 0.9, frequencia: "weekly" },
{ caminho: "/seletor", prioridade: 0.8, frequencia: "monthly" },
{ caminho: "/frameworks", prioridade: 0.8, frequencia: "monthly" },
{ caminho: "/glossario", prioridade: 0.7, frequencia: "monthly" },
{ caminho: "/catalogo", prioridade: 0.6, frequencia: "weekly" },
{ caminho: "/sobre", prioridade: 0.4, frequencia: "yearly" },
{ caminho: "", prioridade: 1.0, frequencia: "weekly", lastModified: dataRevisao },
{ caminho: "/series", prioridade: 0.9, frequencia: "weekly", lastModified: dataRevisao },
{ caminho: "/comparacao", prioridade: 0.9, frequencia: "weekly", lastModified: dataRevisao },
{ caminho: "/diagnostico", prioridade: 0.9, frequencia: "weekly", lastModified: dataRevisao },
{ caminho: "/seletor", prioridade: 0.8, frequencia: "monthly", lastModified: dataRevisao },
{ caminho: "/frameworks", prioridade: 0.8, frequencia: "monthly", lastModified: dataRevisao },
{ caminho: "/glossario", prioridade: 0.7, frequencia: "monthly", lastModified: dataRevisao },
{ 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) => ({
caminho: `/series/${chave}`,
prioridade: 0.9,
frequencia: "monthly",
lastModified: dataRevisao,
}));
const frameworks = Object.keys(frameworksData).map((chave) => ({
caminho: `/frameworks/${encodeURIComponent(chave)}`,
prioridade: 0.6,
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}`,
lastModified: agora,
lastModified,
changeFrequency: frequencia,
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 Footer from "@/components/Footer";
import Link from "next/link";
@@ -38,6 +38,23 @@ export default function Sobre() {
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">
{/* 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">
Ir para o conteúdo
</a>