feat: overhaul homepage with platform statistics, structured feature cards, and highlighted ESP32 models while cleaning up project metadata.
This commit is contained in:
1 parent
031b35d7dd
commit
07dac4f6de
18 files changed
+5040
-1269
No files matched your search
+29
-7
@@ -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}
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -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}
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -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}
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
@@ -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
File diff suppressed because it is too large.
Load diff
+395
-121
@@ -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
@@ -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
File diff suppressed because it is too large.
Load diff
+34
-10
@@ -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
@@ -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
@@ -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>
|
||||
|
||||
Reference in new issue
Block a user