Adição dos novos modelos e do tema escuro

This commit is contained in:
Lucas Rayan committed 2026-05-28 09:06:39 -03:00
1 parent e3b4600300
commit de4abf7cc7
42 files changed
+4468 -1808

No files matched your search

+102 -93
View File
@@ -4,6 +4,16 @@ import { useState, useEffect } from 'react';
import Header from "@/components/Header"; import Header from "@/components/Header";
import Footer from "@/components/Footer"; import Footer from "@/components/Footer";
import BoardCard from "@/components/BoardCard"; import BoardCard from "@/components/BoardCard";
import {
Search,
Filter,
ShoppingCart,
CheckCircle,
AlertTriangle,
RefreshCw,
Layers,
ChevronDown
} from 'lucide-react';
export default function PlacasPage() { export default function PlacasPage() {
const [boards, setBoards] = useState([]); const [boards, setBoards] = useState([]);
@@ -15,7 +25,7 @@ export default function PlacasPage() {
const [activeFilters, setActiveFilters] = useState({}); const [activeFilters, setActiveFilters] = useState({});
const [filterOptions, setFilterOptions] = useState({}); const [filterOptions, setFilterOptions] = useState({});
const excludedColumns = ['Nome', 'Link', 'Imagem']; const excludedColumns = ['Nome', 'Link', 'Imagem', 'Loja Oficial'];
useEffect(() => { useEffect(() => {
fetchBoards(); fetchBoards();
@@ -115,13 +125,13 @@ export default function PlacasPage() {
} else { } else {
const index = newFilters[column].indexOf(value); const index = newFilters[column].indexOf(value);
if (index > -1) { if (index > -1) {
// Remove o valor (criar novo array sem o valor) // Remove o valor
newFilters[column] = newFilters[column].filter(v => v !== value); newFilters[column] = newFilters[column].filter(v => v !== value);
if (newFilters[column].length === 0) { if (newFilters[column].length === 0) {
delete newFilters[column]; delete newFilters[column];
} }
} else { } else {
// Adiciona o valor (criar novo array com o valor) // Adiciona o valor
newFilters[column] = [...newFilters[column], value]; newFilters[column] = [...newFilters[column], value];
} }
} }
@@ -139,13 +149,13 @@ export default function PlacasPage() {
if (loading) { if (loading) {
return ( return (
<div className="bg-linear-to-br from-blue-50 via-white to-purple-50 min-h-screen"> <div className="bg-gradient-to-br from-slate-50 via-white to-purple-50/40 dark:from-slate-950 dark:via-slate-950 dark:to-slate-900 min-h-screen text-slate-900 dark:text-slate-100 flex flex-col justify-between">
<Header /> <Header />
<main className="max-w-7xl mx-auto px-4 py-16"> <main className="max-w-7xl mx-auto px-6 py-24 flex-1 flex items-center justify-center">
<div className="text-center"> <div className="text-center select-none animate-pulse">
<div className="text-6xl mb-4">⏳</div> <RefreshCw className="w-12 h-12 text-purple-500 animate-spin mx-auto mb-4" />
<p className="text-xl text-gray-600">Carregando placas...</p> <p className="text-xs font-bold text-slate-400 dark:text-slate-500 uppercase tracking-widest">Consultando banco de placas...</p>
</div> </div>
</main> </main>
@@ -156,20 +166,23 @@ export default function PlacasPage() {
if (error) { if (error) {
return ( return (
<div className="bg-linear-to-br from-blue-50 via-white to-purple-50 min-h-screen"> <div className="bg-gradient-to-br from-slate-50 via-white to-purple-50/40 dark:from-slate-950 dark:via-slate-950 dark:to-slate-900 min-h-screen text-slate-900 dark:text-slate-100 flex flex-col justify-between">
<Header /> <Header />
<main className="max-w-7xl mx-auto px-4 py-16"> <main className="max-w-7xl mx-auto px-6 py-24 flex-1 flex items-center justify-center">
<div className="text-center"> <div className="text-center bg-white/80 dark:bg-slate-900/40 backdrop-blur-xl border border-slate-200/60 dark:border-slate-800/80 rounded-3xl p-8 max-w-md shadow-2xl select-none">
<div className="text-6xl mb-4">❌</div> <div className="w-12 h-12 bg-red-500/10 border border-red-500/20 rounded-2xl flex items-center justify-center text-red-500 mx-auto mb-4">
<p className="text-xl text-red-600 mb-4">Erro ao carregar dados</p> <AlertTriangle className="w-6 h-6" />
<p className="text-gray-600 mb-6">{error}</p> </div>
<p className="text-sm font-bold text-red-600 dark:text-red-400 mb-2 uppercase tracking-widest">Falha ao sincronizar dados</p>
<p className="text-xs text-slate-450 dark:text-slate-550 mb-6 leading-relaxed">{error}</p>
<button <button
onClick={fetchBoards} onClick={fetchBoards}
className="bg-linear-to-r from-blue-600 to-purple-600 text-white px-6 py-3 rounded-xl font-semibold hover:shadow-lg transition-all" className="inline-flex items-center gap-2 bg-slate-900 dark:bg-slate-100 text-white dark:text-slate-950 px-5 py-3 rounded-xl font-bold text-xs uppercase tracking-wider hover:opacity-90 active:scale-95 transition-all shadow-md cursor-pointer"
> >
Tentar Novamente <RefreshCw className="w-4 h-4" />
<span>Tentar Novamente</span>
</button> </button>
</div> </div>
</main> </main>
@@ -182,53 +195,57 @@ export default function PlacasPage() {
const filterableColumns = headers.filter(h => !excludedColumns.includes(h)); const filterableColumns = headers.filter(h => !excludedColumns.includes(h));
return ( return (
<div className="bg-linear-to-br from-blue-50 via-white to-purple-50 min-h-screen"> <div className="bg-gradient-to-br from-slate-50 via-white to-purple-50/40 dark:from-slate-950 dark:via-slate-950 dark:to-slate-900 min-h-screen text-slate-900 dark:text-slate-100 transition-colors duration-300">
<Header /> <Header />
<main id="conteudo" className="max-w-7xl mx-auto px-4 py-16"> <main id="conteudo" className="max-w-7xl mx-auto px-6 pt-16 pb-24">
<section className="text-center mb-16"> {/* Top Hero Heading Block */}
<div className="inline-flex items-center gap-2 bg-white/70 backdrop-blur-sm px-4 py-2 rounded-full mb-6 border border-purple-200 shadow-sm"> <section className="text-center mb-16 select-none">
<span className="text-xl">🛒</span> <div className="inline-flex items-center gap-2 bg-white/70 dark:bg-slate-900/50 backdrop-blur-md px-4 py-2 rounded-full mb-6 border border-slate-200/60 dark:border-slate-800/60 shadow-xs">
<span className="text-sm font-semibold text-gray-700">Encontre sua placa ideal</span> <ShoppingCart className="w-4 h-4 text-purple-500" />
<span className="text-xs font-bold text-slate-700 dark:text-slate-350 uppercase tracking-wider">Encontre sua placa ideal</span>
</div> </div>
<h1 className="text-5xl md:text-7xl font-black bg-linear-to-r from-blue-600 via-purple-600 to-pink-600 bg-clip-text text-transparent leading-tight mb-6 tracking-tight"> <h1 className="text-4xl md:text-6xl font-display leading-tight font-extrabold bg-gradient-to-r from-blue-600 via-purple-600 to-pink-600 dark:from-blue-400 dark:via-purple-400 dark:to-pink-400 bg-clip-text text-transparent mb-6 tracking-tight">
Catálogo de Placas ESP32 Catálogo de Placas ESP32
</h1> </h1>
<p className="text-xl md:text-2xl text-gray-700 mb-4 font-medium"> <p className="text-sm md:text-base text-slate-500 dark:text-slate-400 max-w-2xl mx-auto leading-relaxed mb-6 font-semibold">
Encontre a placa perfeita para o seu projeto Compare e selecione placas oficiais e alternativas de desenvolvimento do microcontrolador ESP32 para seus projetos.
</p> </p>
<div className="inline-flex items-center gap-2 bg-green-50 px-6 py-3 rounded-full border-2 border-green-200">
<svg className="w-5 h-5 text-green-600" fill="currentColor" viewBox="0 0 20 20"> <div className="inline-flex items-center gap-2 bg-emerald-500/10 text-emerald-600 dark:text-emerald-400 px-4 py-2 rounded-full border border-emerald-500/20">
<path fillRule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z" clipRule="evenodd" /> <CheckCircle className="w-4 h-4 shrink-0" />
</svg> <p className="text-[10px] font-bold uppercase tracking-wider">
<p className="text-sm text-green-700 font-bold"> Links de lojas integradas de fontes verificadas e confiáveis
Todos os links são verificados e de fontes confiáveis
</p> </p>
</div> </div>
</section> </section>
<div className="grid lg:grid-cols-4 gap-8"> <div className="grid lg:grid-cols-4 gap-8">
{/* Left Filters Sidebar */}
<div className="lg:col-span-1"> <div className="lg:col-span-1">
<div className="bg-white rounded-2xl shadow-lg overflow-hidden sticky top-4"> <div className="bg-white/80 dark:bg-slate-900/40 backdrop-blur-xl border border-slate-200/60 dark:border-slate-800/80 rounded-3xl shadow-xl overflow-hidden sticky top-4">
{/* Header dos Filtros */}
<div className="bg-linear-to-r from-blue-600 to-purple-600 p-6 text-white"> {/* Sidebar Banner Header */}
<div className="bg-gradient-to-r from-blue-600 to-purple-600 p-6 text-white select-none">
<div className="flex justify-between items-center"> <div className="flex justify-between items-center">
<div> <div>
<h2 className="text-xl font-bold flex items-center gap-2"> <h2 className="text-base font-display font-extrabold flex items-center gap-2">
🔍 Filtros <Filter className="w-4.5 h-4.5" />
<span>Filtros de Busca</span>
</h2> </h2>
{activeFilterCount > 0 && ( {activeFilterCount > 0 && (
<p className="text-sm text-blue-100 mt-1"> <p className="text-[10px] text-blue-150 font-bold uppercase tracking-wider mt-1.5 animate-pulse">
{activeFilterCount} filtro(s) ativo(s) {activeFilterCount} selecionado(s)
</p> </p>
)} )}
</div> </div>
{activeFilterCount > 0 && ( {activeFilterCount > 0 && (
<button <button
onClick={clearFilters} onClick={clearFilters}
className="bg-white/20 hover:bg-white/30 text-white px-3 py-1.5 rounded-lg font-semibold text-xs transition-all hover:scale-105" className="bg-white/20 hover:bg-white/30 text-white px-3 py-1.5 rounded-xl font-bold text-[10px] uppercase tracking-wider transition-all hover:scale-105 active:scale-95 cursor-pointer"
> >
Limpar Limpar
</button> </button>
@@ -236,32 +253,29 @@ export default function PlacasPage() {
</div> </div>
</div> </div>
{/* Conteúdo dos Filtros */} {/* Sidebar Filters Area */}
<div className="p-6"> <div className="p-6 space-y-6">
{/* Barra de Busca */} {/* Text search by name */}
<div className="mb-6"> <div>
<label className="block text-xs font-semibold text-gray-600 mb-2 uppercase tracking-wide"> <label className="block text-[9px] font-bold text-slate-400 dark:text-slate-500 mb-2.5 uppercase tracking-widest select-none">
Buscar por nome Buscar Placa
</label> </label>
<div className="relative"> <div className="relative">
<input <input
type="text" type="text"
placeholder="Digite para buscar..." placeholder="Digite o nome da placa..."
value={searchTerm} value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)} onChange={(e) => setSearchTerm(e.target.value)}
className="w-full px-4 py-3 pl-10 border-2 border-gray-200 rounded-xl focus:border-purple-500 focus:outline-none transition-colors text-sm bg-gray-50 focus:bg-white" className="w-full px-4 py-3 pl-10 border border-slate-200 dark:border-slate-800 rounded-2xl focus:ring-2 focus:ring-purple-500 dark:focus:ring-purple-500 focus:outline-none transition-colors text-xs bg-slate-50/50 dark:bg-slate-950/20 text-slate-800 dark:text-slate-100"
/> />
<svg className="absolute left-3 top-3.5 w-5 h-5 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <Search className="absolute left-3.5 top-3.5 w-4 h-4 text-slate-400 dark:text-slate-500" />
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z" />
</svg>
</div> </div>
</div> </div>
{/* Separador */} <div className="border-t border-slate-100 dark:border-slate-850/45 my-2"></div>
<div className="border-t border-gray-200 my-2"></div>
{/* Filtros por Categoria */} {/* Dinamic filters container with customized scrollbar */}
<div className="space-y-2 max-h-[calc(100vh-450px)] overflow-y-auto pr-2 custom-scrollbar"> <div className="space-y-4 max-h-[calc(100vh-450px)] overflow-y-auto pr-2 custom-scrollbar">
{filterableColumns.map((column) => { {filterableColumns.map((column) => {
const filterData = filterOptions[column]; const filterData = filterOptions[column];
if (!filterData) return null; if (!filterData) return null;
@@ -269,47 +283,46 @@ export default function PlacasPage() {
const isActive = activeFilters[column]?.length > 0; const isActive = activeFilters[column]?.length > 0;
return ( return (
<div key={column} className={`pb-2 border-b border-gray-100 last:border-b-0 ${isActive ? 'bg-purple-50/50 rounded-lg' : ''}`}> <div key={column} className={`pb-3 border-b border-slate-100 dark:border-slate-850/30 last:border-b-0 ${isActive ? 'bg-purple-500/5 dark:bg-purple-400/5 p-3 rounded-2xl border border-purple-500/10 dark:border-purple-400/10' : ''}`}>
{filterData.isBinary ? ( {filterData.isBinary ? (
// Filtro binário: checkbox integrado ao título // Binary filter (checkbox inside title wrapper)
<label className="flex justify-between cursor-pointer hover:bg-purple-50 p-3 rounded-lg transition-all group"> <label className="flex justify-between items-center cursor-pointer p-1 rounded-lg transition-all group select-none">
<span className="font-semibold text-gray-700 group-hover:text-purple-700 transition-colors"> <span className="text-xs font-bold text-slate-700 dark:text-slate-350 group-hover:text-purple-650 dark:group-hover:text-purple-400 transition-colors">
{column} {column}
</span> </span>
<div className="mt-0.5">
<input <input
type="checkbox" type="checkbox"
checked={activeFilters[column]?.includes('binary-enabled') || false} checked={activeFilters[column]?.includes('binary-enabled') || false}
onChange={() => handleFilterToggle(column, 'binary-enabled')} onChange={() => handleFilterToggle(column, 'binary-enabled')}
className="w-5 h-5 rounded-md border-2 border-gray-300 text-purple-600 focus:ring-2 focus:ring-purple-500 focus:ring-offset-2 transition-all cursor-pointer" className="w-4.5 h-4.5 rounded-md border border-slate-300 dark:border-slate-750 text-purple-650 focus:ring-2 focus:ring-purple-500 focus:ring-offset-2 transition-all cursor-pointer bg-white/50 dark:bg-slate-950/30"
/> />
</div>
</label> </label>
) : ( ) : (
// Filtro normal: título separado + múltiplas opções // Standard options filter with dropdown listing
<> <>
<h3 className="font-bold text-gray-800 mb-3 text-sm uppercase tracking-wide flex items-center gap-2"> <h3 className="font-bold text-slate-700 dark:text-slate-300 mb-3.5 text-xs uppercase tracking-widest flex items-center gap-2 select-none">
{column} <span>{column}</span>
{isActive && ( {isActive && (
<span className="bg-purple-600 text-white text-xs px-2 py-0.5 rounded-full"> <span className="bg-purple-650 text-white text-[9px] px-2 py-0.5 rounded-full font-bold select-none">
{activeFilters[column].length} {activeFilters[column].length}
</span> </span>
)} )}
</h3> </h3>
<div className="space-y-2 max-h-44 overflow-y-auto pr-2">
<div className="space-y-2.5 max-h-44 overflow-y-auto pr-2 custom-scrollbar">
{filterData.values.map((option) => ( {filterData.values.map((option) => (
<label <label
key={option} key={option}
className="flex items-center gap-3 cursor-pointer hover:bg-purple-50 p-2 rounded-lg transition-all group" className="flex items-center gap-3 cursor-pointer hover:bg-purple-500/5 dark:hover:bg-purple-400/5 p-1.5 rounded-lg transition-all group select-none"
> >
<input <input
type="checkbox" type="checkbox"
checked={activeFilters[column]?.includes(option) || false} checked={activeFilters[column]?.includes(option) || false}
onChange={() => handleFilterToggle(column, option)} onChange={() => handleFilterToggle(column, option)}
className="w-4 h-4 rounded border-2 border-gray-300 text-purple-600 focus:ring-2 focus:ring-purple-500 focus:ring-offset-2 transition-all cursor-pointer" className="w-4 h-4 rounded border border-slate-350 dark:border-slate-750 text-purple-650 focus:ring-2 focus:ring-purple-500 focus:ring-offset-2 transition-all cursor-pointer bg-white/50 dark:bg-slate-950/30"
/> />
<span className="text-sm text-gray-700 group-hover:text-purple-700 transition-colors"> <span className="text-xs font-semibold text-slate-600 dark:text-slate-400 group-hover:text-purple-650 dark:group-hover:text-purple-400 transition-colors">
{option} {option}
</span> </span>
</label> </label>
@@ -325,19 +338,19 @@ export default function PlacasPage() {
</div> </div>
</div> </div>
{/* Grid de Placas */} {/* Right Catalog Boards Grid */}
<div className="lg:col-span-3"> <div className="lg:col-span-3">
<div className="mb-6 flex justify-between items-center"> <div className="mb-6 flex justify-between items-center select-none">
<p className="text-gray-600"> <p className="text-xs font-bold text-slate-400 dark:text-slate-500 uppercase tracking-widest">
<span className="font-bold text-purple-600">{filteredBoards.length}</span> placa(s) encontrada(s) Total Encontrado: <span className="text-purple-650 dark:text-purple-400">{filteredBoards.length}</span> Placa(s)
</p> </p>
</div> </div>
{filteredBoards.length === 0 ? ( {filteredBoards.length === 0 ? (
<div className="text-center py-16"> <div className="text-center py-20 bg-white/80 dark:bg-slate-900/40 backdrop-blur-xl border border-slate-200/60 dark:border-slate-800/80 rounded-3xl p-8 shadow-md select-none">
<div className="text-6xl mb-4">🔍</div> <Search className="w-12 h-12 text-slate-300 dark:text-slate-700 mx-auto mb-4" />
<p className="text-xl text-gray-600 mb-2">Nenhuma placa encontrada</p> <p className="text-base font-display font-extrabold text-slate-800 dark:text-slate-200 mb-1">Nenhuma Placa Correspondente</p>
<p className="text-gray-500">Tente ajustar os filtros</p> <p className="text-xs text-slate-500 dark:text-slate-400">Tente ajustar ou redefinir seus filtros laterais para expandir a busca.</p>
</div> </div>
) : ( ) : (
<div className="grid md:grid-cols-2 xl:grid-cols-3 gap-6"> <div className="grid md:grid-cols-2 xl:grid-cols-3 gap-6">
@@ -351,33 +364,29 @@ export default function PlacasPage() {
</main> </main>
<Footer /> <Footer />
</div>
);
}
// Estilos customizados para scrollbar <style dangerouslySetInnerHTML={{ __html: `
const styles = `
.custom-scrollbar::-webkit-scrollbar { .custom-scrollbar::-webkit-scrollbar {
width: 6px; width: 4px;
} }
.custom-scrollbar::-webkit-scrollbar-track { .custom-scrollbar::-webkit-scrollbar-track {
background: #f1f1f1; background: transparent;
border-radius: 10px;
} }
.custom-scrollbar::-webkit-scrollbar-thumb { .custom-scrollbar::-webkit-scrollbar-thumb {
background: linear-gradient(to bottom, #3b82f6, #9333ea); background: #cbd5e1;
border-radius: 10px; border-radius: 10px;
} }
.custom-scrollbar::-webkit-scrollbar-thumb:hover { .dark .custom-scrollbar::-webkit-scrollbar-thumb {
background: linear-gradient(to bottom, #2563eb, #7c3aed); background: #334155;
} }
`;
if (typeof document !== 'undefined') { .custom-scrollbar::-webkit-scrollbar-thumb:hover {
const styleSheet = document.createElement('style'); background: #94a3b8;
styleSheet.textContent = styles; }
document.head.appendChild(styleSheet); `}} />
</div>
);
} }
+177 -91
View File
@@ -2,12 +2,47 @@
import Header from "@/components/Header"; import Header from "@/components/Header";
import Footer from "@/components/Footer"; import Footer from "@/components/Footer";
import { useState } from "react"; import { useState, Fragment } from "react";
import Link from "next/link"; import Link from "next/link";
import seriesData from "@/public/series.json"; import seriesData from "@/public/series.json";
import {
Scale,
GitCompare,
Check,
ChevronDown,
ChevronUp,
ArrowRight,
FileText,
ExternalLink,
ShoppingBag,
Cpu,
Network,
Database,
Sliders,
Activity,
HelpCircle,
Thermometer
} from "lucide-react";
// Category to Icon mapping
const categoryIcons = {
"Geral": <HelpCircle className="w-4 h-4 text-purple-500" />,
"Processador": <Cpu className="w-4 h-4 text-blue-500" />,
"Conectividade": <Network className="w-4 h-4 text-indigo-500" />,
"Memória": <Database className="w-4 h-4 text-pink-500" />,
"Periféricos Digitais": <Sliders className="w-4 h-4 text-sky-500" />,
"Periféricos Analógicos": <Activity className="w-4 h-4 text-emerald-500" />,
"Interfaces Especiais": <Activity className="w-4 h-4 text-cyan-500" />,
"Especificações Ambientais": <Thermometer className="w-4 h-4 text-red-500" />,
};
export default function Comparacao() { export default function Comparacao() {
const [selectedSeries, setSelectedSeries] = useState(["ESP32", "ESP32-S3"]); const [selectedSeries, setSelectedSeries] = useState(["ESP32", "ESP32-S3"]);
const [collapsedCategories, setCollapsedCategories] = useState({
"Interfaces Especiais": true,
"Especificações Ambientais": true,
});
const series = Object.entries(seriesData); const series = Object.entries(seriesData);
const toggleSeries = (seriesKey) => { const toggleSeries = (seriesKey) => {
@@ -22,6 +57,13 @@ export default function Comparacao() {
} }
}; };
const toggleCategory = (category) => {
setCollapsedCategories({
...collapsedCategories,
[category]: !collapsedCategories[category]
});
};
const compareFields = [ const compareFields = [
{ category: "Geral", fields: [ { category: "Geral", fields: [
{ key: "nome_completo", label: "Nome Completo" }, { key: "nome_completo", label: "Nome Completo" },
@@ -81,87 +123,107 @@ export default function Comparacao() {
const renderValue = (value) => { const renderValue = (value) => {
if (!value || value === "Não") { if (!value || value === "Não") {
return <span className="text-slate-400 font-medium">—</span>; return <span className="text-slate-400 dark:text-slate-600 font-medium select-none">—</span>;
} }
if (value === "Sim") { if (value === "Sim") {
return <span className="text-slate-700 font-semibold">✓</span>; return (
<span className="inline-flex items-center justify-center w-6 h-6 rounded-full bg-emerald-500/10 text-emerald-600 dark:text-emerald-400 border border-emerald-500/20 shadow-xs">
<Check className="w-3.5 h-3.5" />
</span>
);
} }
return <span className="text-slate-800 font-medium">{value}</span>; return <span className="text-slate-800 dark:text-slate-200 font-semibold">{value}</span>;
}; };
return ( return (
<div className="bg-gray-50 min-h-screen"> <div className="bg-gradient-to-br from-slate-50 via-white to-purple-50/40 dark:from-slate-950 dark:via-slate-950 dark:to-slate-900 min-h-screen text-slate-900 dark:text-slate-100 transition-colors duration-300">
<Header /> <Header />
<main id="conteudo" className="px-4 pt-16 pb-20 max-w-7xl mx-auto"> <main id="conteudo" className="px-6 pt-16 pb-24 max-w-7xl mx-auto">
{/* Top Section */}
<section className="mb-16"> <section className="mb-16">
<div className="border-l-4 border-slate-700 pl-6 mb-8"> <div className="border-l-4 border-purple-500 pl-6 max-w-3xl">
<p className="text-sm font-semibold text-slate-600 uppercase tracking-wider mb-2"> <p className="text-xs font-bold text-purple-600 dark:text-purple-400 uppercase tracking-widest mb-2 select-none">
Análise Técnica Comparativa Análise Técnica Comparativa
</p> </p>
<h1 className="text-4xl md:text-5xl font-bold text-slate-900 mb-4"> <h1 className="text-4xl md:text-5xl font-display font-extrabold text-slate-800 dark:text-slate-100 mb-4 tracking-tight leading-tight">
Comparação de Séries ESP32 Comparador de Séries ESP32
</h1> </h1>
<p className="text-base text-slate-600 max-w-3xl leading-relaxed"> <p className="text-base text-slate-500 dark:text-slate-400 leading-relaxed">
Análise detalhada de especificações técnicas para seleção de microcontroladores ESP32 Coloque lado a lado as especificações completas de clock, memória, interfaces de rádio e periféricos analógicos para validar a engenharia e o custo-benefício de cada microcontrolador.
</p> </p>
</div> </div>
</section> </section>
{/* Model Selector Card */}
<section className="mb-12"> <section className="mb-12">
<div className="bg-white border border-slate-200 rounded-lg p-6 mb-6"> <div className="bg-white/80 dark:bg-slate-900/40 backdrop-blur-xl border border-slate-200/60 dark:border-slate-800/80 rounded-3xl p-6 md:p-8 shadow-2xl relative overflow-hidden">
<h2 className="text-xl font-semibold text-slate-900 mb-2"> <div className="flex flex-col md:flex-row md:items-center md:justify-between gap-4 mb-6">
Seleção de Modelos <div>
<h2 className="text-lg font-bold text-slate-800 dark:text-slate-100 mb-1">
Escolha as Séries
</h2> </h2>
<p className="text-sm text-slate-600 mb-6"> <p className="text-xs text-slate-400 dark:text-slate-500">
Selecione de 2 a 4 séries para análise comparativa Selecione de 2 a até 4 modelos simultâneos para montar sua planilha de comparação.
</p> </p>
<div className="grid grid-cols-2 md:grid-cols-4 lg:grid-cols-8 gap-3"> </div>
{series.map(([key, seriesItem]) => (
<div className="inline-flex items-center gap-2 bg-purple-500/10 text-purple-600 dark:text-purple-400 px-3.5 py-1.5 rounded-full border border-purple-500/20 text-xs font-bold shadow-xs select-none">
<GitCompare className="w-3.5 h-3.5" />
<span>{selectedSeries.length} de 4 selecionados</span>
</div>
</div>
{/* Brand-colored dynamic button selector grid */}
<div className="grid grid-cols-2 sm:grid-cols-4 lg:grid-cols-8 gap-3">
{series.map(([key, seriesItem]) => {
const isSelected = selectedSeries.includes(key);
return (
<button <button
key={key} key={key}
onClick={() => toggleSeries(key)} onClick={() => toggleSeries(key)}
className={`p-4 border rounded-lg transition-all duration-200 ${ style={{
selectedSeries.includes(key) borderColor: isSelected ? seriesItem.cor : 'transparent'
? 'border-slate-700 bg-slate-50 shadow-sm' }}
: 'border-slate-200 bg-white hover:border-slate-300 hover:bg-slate-50' className={`p-4 border rounded-2xl transition-all duration-300 cursor-pointer active:scale-95 text-center flex flex-col items-center justify-center shadow-xs ${
isSelected
? 'bg-slate-100 dark:bg-slate-950 shadow-md scale-[1.02] border-2'
: 'border-slate-200/60 dark:border-slate-800/80 bg-white/70 dark:bg-slate-900/30 hover:border-purple-400/50 hover:bg-purple-500/5'
}`} }`}
> >
<div className="text-3xl mb-2">{seriesItem.icone}</div> <div className="text-3xl mb-2 select-none transform hover:scale-110 transition-transform">{seriesItem.icone}</div>
<div className="text-xs font-semibold text-slate-800">{key}</div> <div className="text-xs font-bold text-slate-800 dark:text-slate-200">{key}</div>
{selectedSeries.includes(key) && (
<div className="mt-2 flex justify-center"> {isSelected && (
<div className="w-2 h-2 rounded-full bg-slate-700"></div> <div
</div> className="mt-2.5 w-2 h-2 rounded-full shrink-0 animate-pulse"
style={{ backgroundColor: seriesItem.cor }}
/>
)} )}
</button> </button>
))} );
</div> })}
</div>
<div className="flex justify-end">
<div className="inline-flex items-center gap-2 bg-slate-100 px-4 py-2 rounded border border-slate-200">
<span className="text-slate-700 font-medium text-sm">
{selectedSeries.length} / 4 modelos selecionados
</span>
</div> </div>
</div> </div>
</section> </section>
<div className="bg-white border border-slate-200 shadow-sm overflow-hidden"> {/* Side by side interactive Table */}
<div className="overflow-x-auto"> <div className="bg-white/80 dark:bg-slate-900/40 backdrop-blur-xl border border-slate-200/60 dark:border-slate-800/80 rounded-3xl overflow-hidden shadow-2xl">
<table className="w-full"> <div className="overflow-x-auto scrollbar-thin">
<table className="w-full border-collapse">
<thead> <thead>
<tr className="bg-slate-800 border-b border-slate-700"> <tr className="bg-slate-850 dark:bg-slate-950 border-b border-slate-750 dark:border-slate-900">
<th className="px-6 py-4 text-left text-white font-semibold text-sm uppercase tracking-wider sticky left-0 bg-slate-800 z-10"> <th className="px-6 py-5 text-left text-white font-bold text-xs uppercase tracking-widest sticky left-0 bg-slate-850 dark:bg-slate-950 z-20 shadow-xs border-r border-slate-700/30">
Especificação Especificação
</th> </th>
{selectedSeries.map(seriesKey => { {selectedSeries.map(seriesKey => {
const seriesItem = seriesData[seriesKey]; const seriesItem = seriesData[seriesKey];
return ( return (
<th key={seriesKey} className="px-6 py-4 text-center text-white font-semibold text-sm uppercase tracking-wider min-w-48"> <th key={seriesKey} className="px-6 py-5 text-center text-white font-bold text-xs uppercase tracking-widest min-w-48">
<div className="flex flex-col items-center gap-2"> <div className="flex flex-col items-center gap-2 select-none">
<span className="text-2xl">{seriesItem.icone}</span> <span className="text-3xl transform hover:scale-105 transition-transform">{seriesItem.icone}</span>
<span>{seriesKey}</span> <span className="text-sm font-extrabold tracking-wide" style={{ color: seriesItem.cor }}>{seriesKey}</span>
</div> </div>
</th> </th>
); );
@@ -169,70 +231,93 @@ export default function Comparacao() {
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
{compareFields.map(({ category, fields }) => ( {compareFields.map(({ category, fields }) => {
<> const isCollapsed = collapsedCategories[category];
<tr key={category} className="bg-slate-100 border-y border-slate-200"> return (
<td colSpan={selectedSeries.length + 1} className="px-6 py-3 font-semibold text-slate-800 text-sm uppercase tracking-wide"> <Fragment key={category}>
{category} {/* Collapsible Category Accordion Header */}
<tr
onClick={() => toggleCategory(category)}
className="bg-slate-100/80 dark:bg-slate-950/60 border-y border-slate-250/60 dark:border-slate-800/80 cursor-pointer select-none hover:bg-slate-200/60 dark:hover:bg-slate-900/80 transition-colors"
>
<td
colSpan={selectedSeries.length + 1}
className="px-6 py-3.5 font-bold text-slate-800 dark:text-slate-200 text-xs uppercase tracking-wider"
>
<div className="flex items-center justify-between">
<div className="flex items-center gap-2">
{categoryIcons[category] || <HelpCircle className="w-4 h-4 text-purple-500" />}
<span>{category}</span>
</div>
{isCollapsed ? (
<ChevronDown className="w-4 h-4 opacity-60 text-purple-600 dark:text-purple-400" />
) : (
<ChevronUp className="w-4 h-4 opacity-60 text-purple-600 dark:text-purple-400" />
)}
</div>
</td> </td>
</tr> </tr>
{fields.map(({ key, label }) => (
<tr key={key} className="border-b border-slate-100 hover:bg-slate-50 transition-colors"> {/* Fields list within category (if expanded) */}
<td className="px-6 py-3 font-medium text-slate-700 text-sm bg-white sticky left-0 border-r border-slate-100"> {!isCollapsed && fields.map(({ key, label }) => (
<tr
key={key}
className="border-b border-slate-100 dark:border-slate-800/40 hover:bg-purple-500/5 dark:hover:bg-purple-400/5 transition-colors group"
>
<td className="px-6 py-4 font-bold text-slate-600 dark:text-slate-400 text-xs bg-white dark:bg-slate-900/10 sticky left-0 border-r border-slate-150/60 dark:border-slate-800/60 shrink-0 z-10">
{label} {label}
</td> </td>
{selectedSeries.map(seriesKey => { {selectedSeries.map(seriesKey => {
const seriesItem = seriesData[seriesKey]; const seriesItem = seriesData[seriesKey];
const value = seriesItem[key]; const value = seriesItem[key];
return ( return (
<td key={seriesKey} className="px-6 py-3 text-center text-sm"> <td key={seriesKey} className="px-6 py-4 text-center text-xs">
{renderValue(value)} {renderValue(value)}
</td> </td>
); );
})} })}
</tr> </tr>
))} ))}
</> </Fragment>
))} );
})}
</tbody> </tbody>
</table> </table>
</div> </div>
</div> </div>
{/* Call-to-Action para Catálogo de Placas */} {/* Professional CTA Banner for the Catalog */}
<div className="my-16 bg-slate-800 border border-slate-700 p-10"> <div className="my-20 bg-gradient-to-br from-slate-900 to-indigo-950 dark:from-slate-950 dark:to-purple-950/20 border border-slate-800/80 rounded-3xl p-8 md:p-12 shadow-2xl relative overflow-hidden">
<div className="flex flex-col md:flex-row items-center justify-between gap-8"> <div className="absolute right-0 bottom-0 w-96 h-96 bg-purple-500/5 rounded-full blur-3xl pointer-events-none" />
<div className="flex-1">
<p className="text-xs font-semibold text-slate-400 uppercase tracking-wider mb-3"> <div className="flex flex-col md:flex-row items-center justify-between gap-8 relative z-10">
Recursos Adicionais <div className="flex-1 max-w-2xl text-center md:text-left">
</p> <div className="inline-flex items-center gap-2 bg-white/10 text-slate-350 px-3 py-1 rounded-full mb-4 text-xs font-semibold select-none">
<h3 className="text-2xl md:text-3xl font-bold text-white mb-4"> <ShoppingBag className="w-3.5 h-3.5" />
<span>Hardware Adicional</span>
</div>
<h3 className="text-2xl md:text-3xl font-display font-extrabold text-white mb-4">
Catálogo de Placas de Desenvolvimento Catálogo de Placas de Desenvolvimento
</h3> </h3>
<p className="text-base text-slate-300 leading-relaxed"> <p className="text-sm text-slate-400 leading-relaxed">
Acesse nossa base de placas de desenvolvimento verificadas com referências diretas para fornecedores oficiais. Quer testar o chip selecionado na bancada? Compare placas de desenvolvimento com layouts de pinos mapeados, conexões de periféricos e links oficiais dos melhores fornecedores.
</p> </p>
</div> </div>
<Link <Link
href="/catalogo" href="/catalogo"
className="inline-flex items-center justify-center gap-3 bg-white text-slate-800 px-6 py-3 font-semibold text-sm hover:bg-slate-100 transition-colors duration-200 whitespace-nowrap border border-slate-200" className="inline-flex items-center justify-center gap-2 bg-white text-slate-900 px-6 py-3.5 rounded-xl font-bold text-sm hover:bg-slate-100 transition-all duration-300 shrink-0 shadow-md active:scale-95"
> >
<span>Acessar Catálogo</span> <span>Acessar Catálogo</span>
<svg <ArrowRight className="w-4 h-4" />
className="w-4 h-4"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 5l7 7-7 7" />
</svg>
</Link> </Link>
</div> </div>
</div> </div>
{/* Detailed technical datasheets section */}
<div className="mt-12"> <div className="mt-12">
<h3 className="text-lg font-semibold text-slate-900 mb-6 border-l-4 border-slate-700 pl-4"> <h3 className="text-xl font-display font-extrabold text-slate-800 dark:text-slate-100 mb-6 border-l-4 border-purple-500 pl-4 select-none">
Documentação Técnica Documentação Técnica Relacionada
</h3> </h3>
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4"> <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4">
{selectedSeries.map(seriesKey => { {selectedSeries.map(seriesKey => {
@@ -240,29 +325,30 @@ export default function Comparacao() {
return ( return (
<div <div
key={seriesKey} key={seriesKey}
className="bg-white border border-slate-200 p-5 hover:border-slate-300 hover:shadow-sm transition-all duration-200" className="bg-white/70 dark:bg-slate-900/40 backdrop-blur-xl border border-slate-200/80 dark:border-slate-800/80 p-5 rounded-2xl hover:border-purple-500/50 dark:hover:border-purple-400/50 hover:shadow-lg transition-all duration-300 flex flex-col justify-between"
> >
<div className="flex items-start gap-4"> <div className="flex gap-4 items-start mb-4">
<div className="text-3xl">{seriesItem.icone}</div> <div className="text-4xl select-none">{seriesItem.icone}</div>
<div className="flex-1 min-w-0"> <div className="min-w-0">
<Link href={`/series/${seriesKey}`} className="no-underline"> <Link href={`/series/${seriesKey}`} className="no-underline">
<h4 className="text-base font-semibold text-slate-900 mb-1">{seriesKey}</h4> <h4 className="text-sm font-extrabold text-slate-850 dark:text-slate-100 hover:text-purple-600 dark:hover:text-purple-400 transition-colors">
{seriesKey}
</h4>
</Link> </Link>
<p className="text-xs text-slate-600 mb-3">Documentação oficial</p> <p className="text-[10px] text-slate-400 dark:text-slate-500 font-bold uppercase tracking-wider mt-1">Guia de Hardware</p>
</div>
</div>
<a <a
href={seriesItem.datasheet} href={seriesItem.datasheet}
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"
className="inline-flex items-center gap-2 text-xs font-medium text-slate-700 hover:text-slate-900" className="inline-flex items-center justify-center gap-1.5 w-full text-center py-2 bg-slate-100 dark:bg-slate-950 hover:bg-purple-500/10 dark:hover:bg-purple-400/10 border border-slate-200 dark:border-slate-800 rounded-xl text-xs font-bold text-slate-650 dark:text-slate-350 hover:text-purple-600 dark:hover:text-purple-400 transition-colors"
> >
<span>Datasheet</span> <span>Datasheet Oficial</span>
<svg className="w-3 h-3" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <ExternalLink className="w-3.5 h-3.5" />
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14" />
</svg>
</a> </a>
</div> </div>
</div>
</div>
); );
})} })}
</div> </div>
+151 -56
View File
@@ -4,6 +4,20 @@ import Footer from "@/components/Footer";
import { notFound } from "next/navigation"; import { notFound } from "next/navigation";
import CodeExample from "@/components/CodeExample"; import CodeExample from "@/components/CodeExample";
import frameworksData from "@/public/frameworks.json"; import frameworksData from "@/public/frameworks.json";
import {
ArrowLeft,
BookOpen,
Code,
Layers,
Zap,
Compass,
CheckCircle,
Terminal,
FileText,
Sliders,
Award,
Target
} from "lucide-react";
export default async function FrameworkDetail({ params }) { export default async function FrameworkDetail({ params }) {
const { key } = await params; const { key } = await params;
@@ -17,41 +31,53 @@ export default async function FrameworkDetail({ params }) {
const useCases = framework.casos_uso || []; const useCases = framework.casos_uso || [];
return ( return (
<div className="bg-linear-to-br from-blue-50 via-white to-purple-50 min-h-screen"> <div className="bg-gradient-to-br from-slate-50 via-white to-purple-50/40 dark:from-slate-950 dark:via-slate-950 dark:to-slate-900 min-h-screen text-slate-900 dark:text-slate-100 transition-colors duration-300">
<Header /> <Header />
<main className="px-4 pt-16 pb-8 max-w-7xl mx-auto"> <main className="px-6 pt-16 pb-24 max-w-7xl mx-auto">
{/* Back navigation link */}
<div className="mb-8"> <div className="mb-8">
<Link href="/frameworks" className="inline-flex items-center text-gray-600 hover:text-purple-600 transition-colors mb-4"> <Link
<span className="mr-2">←</span> Voltar para Frameworks href="/frameworks"
className="inline-flex items-center gap-2 text-xs font-bold text-slate-500 dark:text-slate-400 hover:text-purple-600 dark:hover:text-purple-400 transition-colors uppercase tracking-wider mb-6"
>
<ArrowLeft className="w-3.5 h-3.5" />
<span>Voltar para Frameworks</span>
</Link> </Link>
<div className="bg-white rounded-3xl shadow-xl p-8 border-2 border-gray-100" {/* Top Info Banner Panel */}
style={{ borderColor: framework.cor + '30' }}> <div
<div className="flex items-start justify-between flex-wrap gap-4"> className="bg-white/80 dark:bg-slate-900/40 backdrop-blur-xl rounded-3xl p-6 md:p-8 shadow-2xl border relative overflow-hidden"
<div className="flex items-center gap-4"> style={{ borderColor: `${framework.cor}40` }}
<span className="text-6xl">{framework.icone}</span> >
<div className="flex flex-col lg:flex-row items-start lg:items-center justify-between gap-8">
<div className="flex items-center gap-5">
<span className="text-6xl select-none filter drop-shadow-md">{framework.icone}</span>
<div> <div>
<h1 className="text-4xl md:text-5xl font-extrabold text-gray-800 mb-2"> <h1 className="text-4xl font-display font-extrabold text-slate-850 dark:text-slate-100 tracking-tight leading-none mb-3">
{framework.nome} {framework.nome}
</h1> </h1>
<p className="text-xl text-gray-600 mb-2">{framework.nome_completo}</p> <p className="text-sm font-semibold text-slate-400 dark:text-slate-500 mb-4">{framework.nome_completo}</p>
<span className="inline-block px-4 py-2 rounded-full text-sm font-semibold text-white" <span
style={{ backgroundColor: framework.cor }}> className="inline-block px-3 py-1 rounded-full text-[10px] font-bold uppercase tracking-widest select-none"
style={{ backgroundColor: `${framework.cor}18`, color: framework.cor, border: `1px solid ${framework.cor}30` }}
>
{framework.tipo} {framework.tipo}
</span> </span>
</div> </div>
</div> </div>
<div className="mx-auto sm:mx-0 grid items-center gap-3"> {/* Call to Actions CTA Grid */}
<div className="grid sm:grid-cols-2 lg:grid-cols-1 gap-3 w-full lg:w-auto">
{framework.documentacao && ( {framework.documentacao && (
<a <a
href={framework.documentacao} href={framework.documentacao}
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"
className="inline-flex items-center gap-2 bg-linear-to-r from-blue-600 to-purple-600 text-white px-5 py-2 rounded-xl font-semibold shadow hover:shadow-lg transition" className="inline-flex items-center justify-center gap-2 bg-slate-900 dark:bg-slate-100 text-white dark:text-slate-950 px-5 py-3 rounded-xl font-bold text-xs uppercase tracking-wider hover:opacity-90 transition-opacity shadow-lg"
> >
📘 Documentação <BookOpen className="w-4 h-4" />
<span>Documentação Oficial</span>
</a> </a>
)} )}
@@ -60,86 +86,154 @@ export default async function FrameworkDetail({ params }) {
href={framework.repositorio} href={framework.repositorio}
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"
className="inline-flex items-center gap-2 bg-white text-gray-800 px-5 py-2 rounded-xl font-semibold shadow border-2 border-gray-100 hover:border-purple-300 transition" className="inline-flex items-center justify-center gap-2 bg-purple-500/10 text-purple-600 dark:text-purple-400 border border-purple-500/20 px-5 py-3 rounded-xl font-bold text-xs uppercase tracking-wider hover:bg-purple-500/20 transition-all shadow-xs"
> >
🔗 Repositório <Code className="w-4 h-4" />
<span>Repositório no GitHub</span>
</a> </a>
)} )}
</div> </div>
</div> </div>
<p className="text-lg text-gray-700 mt-6 leading-relaxed"> <p className="text-sm text-slate-500 dark:text-slate-200 mt-6 leading-relaxed max-w-4xl font-semibold">
{framework.descricao} {framework.descricao}
</p> </p>
</div> </div>
</div> </div>
<div className="grid md:grid-cols-3 gap-6 mb-8"> {/* Framework Core Specifications Grid */}
<div className="bg-white rounded-2xl shadow-lg p-6 border-l-4" style={{ borderColor: framework.cor }}> <div className="grid md:grid-cols-3 gap-6 mb-8 select-none animate-fadeIn">
<h3 className="text-sm font-semibold text-gray-600 mb-2">LINGUAGEM</h3> <div
<p className="text-3xl font-bold text-gray-800">{framework.linguagem}</p> className="bg-white/80 dark:bg-slate-900/40 backdrop-blur-xl rounded-2xl shadow-lg p-6 border-l-4"
<p className="text-gray-600 mt-1">Principal</p> style={{ borderColor: framework.cor }}
>
<h3 className="text-[10px] font-bold text-slate-400 dark:text-slate-500 uppercase tracking-widest mb-1.5 flex items-center gap-1.5">
<Code className="w-3.5 h-3.5 text-blue-500" />
<span>LINGUAGEM DE DESENVOLVIMENTO</span>
</h3>
<p className="text-2xl font-extrabold text-slate-800 dark:text-slate-100">{framework.linguagem}</p>
<p className="text-xs text-slate-400 mt-1">Sintaxe principal do ecossistema</p>
</div> </div>
<div className="bg-white rounded-2xl shadow-lg p-6 border-l-4" style={{ borderColor: framework.cor }}> <div
<h3 className="text-sm font-semibold text-gray-600 mb-2">TIPO</h3> className="bg-white/80 dark:bg-slate-900/40 backdrop-blur-xl rounded-2xl shadow-lg p-6 border-l-4"
<p className="text-3xl font-bold text-gray-800">{framework.tipo}</p> style={{ borderColor: framework.cor }}
<p className="text-gray-600 mt-1">Categoria</p> >
<h3 className="text-[10px] font-bold text-slate-400 dark:text-slate-500 uppercase tracking-widest mb-1.5 flex items-center gap-1.5">
<Layers className="w-3.5 h-3.5 text-purple-500" />
<span>CATEGORIA DO FRAMEWORK</span>
</h3>
<p className="text-2xl font-extrabold text-slate-800 dark:text-slate-100">{framework.tipo}</p>
<p className="text-xs text-slate-400 mt-1">Classificação de arquitetura</p>
</div> </div>
<div className="bg-white rounded-2xl shadow-lg p-6 border-l-4" style={{ borderColor: framework.cor }}> <div
<h3 className="text-sm font-semibold text-gray-600 mb-2">FUNÇÃO PRINCIPAL</h3> className="bg-white/80 dark:bg-slate-900/40 backdrop-blur-xl rounded-2xl shadow-lg p-6 border-l-4"
<p className="text-xl font-bold text-gray-800">{framework.funcao_principal}</p> style={{ borderColor: framework.cor }}
>
<h3 className="text-[10px] font-bold text-slate-400 dark:text-slate-450 mb-1.5 flex items-center gap-1.5">
<Target className="w-3.5 h-3.5 text-emerald-500" />
<span>OBJETIVO PRINCIPAL</span>
</h3>
<p className="text-sm font-bold text-slate-700 dark:text-slate-200 leading-relaxed mt-1.5">{framework.funcao_principal}</p>
</div> </div>
</div> </div>
<div className="grid lg:grid-cols-3 gap-6 mb-8"> {/* side-by-side details block */}
<div className="lg:col-span-2 bg-white rounded-2xl shadow-lg p-6 border-2 border-gray-100"> <div className="grid lg:grid-cols-3 gap-6 mb-8 items-start">
<h2 className="text-2xl font-bold text-gray-800 mb-4 pb-3 border-b-2" style={{ borderColor: framework.cor }}> {/* Left specs card */}
✨ Principais Características <div className="lg:col-span-2 bg-white/80 dark:bg-slate-900/45 backdrop-blur-xl rounded-3xl p-8 border border-slate-200/60 dark:border-slate-800/80 shadow-2xl space-y-8">
<div>
<h2
className="text-lg font-display font-extrabold text-slate-850 dark:text-slate-100 mb-6 pb-3 border-b flex items-center gap-2.5 select-none"
style={{ borderBottomColor: `${framework.cor}30` }}
>
<Zap className="w-5 h-5" style={{ color: framework.cor }} />
<span>Características de Destaque</span>
</h2> </h2>
<ul className="list-disc list-inside space-y-2 text-gray-700">
{features.map((f, i) => ( <ul className="space-y-3">
<li key={i} className="pl-2">{f}</li> {features.map((feature, i) => (
<li key={i} className="flex items-start gap-2.5 text-xs font-semibold text-slate-650 dark:text-slate-200">
<CheckCircle className="w-4 h-4 text-emerald-500 dark:text-emerald-450 mt-0.5 shrink-0" />
<span>{feature}</span>
</li>
))} ))}
</ul> </ul>
</div>
<h2 className="text-2xl font-bold text-gray-800 mt-8 mb-4 pb-3 border-b-2" style={{ borderColor: framework.cor }}> <div>
📦 Casos de Uso <h2
className="text-lg font-display font-extrabold text-slate-850 dark:text-slate-100 mb-6 pb-3 border-b flex items-center gap-2.5 select-none"
style={{ borderBottomColor: `${framework.cor}30` }}
>
<Compass className="w-5 h-5" style={{ color: framework.cor }} />
<span>Casos de Uso Recomendados</span>
</h2> </h2>
<div className="grid md:grid-cols-2 gap-3">
{useCases.map((u, i) => ( <div className="grid md:grid-cols-2 gap-3.5">
<div key={i} className="bg-gray-50 p-3 rounded-lg text-gray-700">{u}</div> {useCases.map((useCase, i) => (
<div key={i} className="bg-slate-50/50 dark:bg-slate-900/60 p-4 rounded-2xl border border-slate-150/40 dark:border-slate-800/80 text-xs font-bold text-slate-700 dark:text-slate-200 flex items-center gap-2 hover:bg-purple-500/5 dark:hover:bg-purple-400/5 transition-colors select-none">
<span className="w-2.5 h-2.5 rounded-full shrink-0" style={{ backgroundColor: framework.cor }} />
<span>{useCase}</span>
</div>
))} ))}
</div> </div>
</div> </div>
</div>
<div className="bg-white rounded-2xl shadow-lg p-6 border-2 border-gray-100"> {/* Right summary metadata card */}
<h2 className="text-2xl font-bold text-gray-800 mb-4 pb-3 border-b-2" style={{ borderColor: framework.cor }}> <div className="bg-white/80 dark:bg-slate-900/45 backdrop-blur-xl rounded-3xl p-8 border border-slate-200/60 dark:border-slate-800/80 shadow-2xl select-none">
📋 Detalhes Rápidos <h2
className="text-lg font-display font-extrabold text-slate-850 dark:text-slate-100 mb-6 pb-3 border-b flex items-center gap-2.5"
style={{ borderBottomColor: `${framework.cor}30` }}
>
<FileText className="w-5 h-5" style={{ color: framework.cor }} />
<span>Resumo Técnico</span>
</h2> </h2>
<div className="space-y-3 text-gray-700">
<div><span className="font-semibold">Nome completo:</span> {framework.nome_completo}</div> <div className="space-y-4 text-xs font-bold text-slate-650 dark:text-slate-200">
<div><span className="font-semibold">Linguagem:</span> {framework.linguagem}</div> <div className="flex justify-between items-center py-2 border-b border-slate-100 dark:border-slate-850/45">
<div><span className="font-semibold">Tipo:</span> {framework.tipo}</div> <span className="text-slate-400 dark:text-slate-500 uppercase tracking-wider text-[9px]">Nome Oficial</span>
<div><span className="font-semibold">Cor (tema):</span> <span className="inline-block w-4 h-4 rounded ml-2" style={{ backgroundColor: framework.cor }} /></div> <span className="text-slate-800 dark:text-slate-200 font-extrabold">{framework.nome}</span>
</div>
<div className="flex justify-between items-center py-2 border-b border-slate-100 dark:border-slate-850/45">
<span className="text-slate-400 dark:text-slate-500 uppercase tracking-wider text-[9px]">Linguagem</span>
<span className="text-slate-800 dark:text-slate-200 font-extrabold">{framework.linguagem}</span>
</div>
<div className="flex justify-between items-center py-2 border-b border-slate-100 dark:border-slate-850/45">
<span className="text-slate-400 dark:text-slate-500 uppercase tracking-wider text-[9px]">Tipo / Suite</span>
<span className="text-slate-800 dark:text-slate-200 font-extrabold">{framework.tipo}</span>
</div>
<div className="flex justify-between items-center py-2">
<span className="text-slate-400 dark:text-slate-500 uppercase tracking-wider text-[9px]">Cor Temática</span>
<div className="flex items-center gap-2">
<code className="text-[10px] text-slate-500 uppercase">{framework.cor}</code>
<span className="inline-block w-4 h-4 rounded-md shadow-xs border border-white/20" style={{ backgroundColor: framework.cor }} />
</div>
</div>
</div> </div>
</div> </div>
</div> </div>
{/* Coding examples container (renders only if framework has examples defined) */}
{framework.exemplos && ( {framework.exemplos && (
<div className="mt-12">
<CodeExample framework={framework} /> <CodeExample framework={framework} />
</div>
)} )}
<div className="text-center md:flex gap-4 mt-6"> {/* Technical documentation double CTAs */}
<div className="flex flex-wrap gap-4 mt-8 select-none">
{framework.documentacao && ( {framework.documentacao && (
<a <a
href={framework.documentacao} href={framework.documentacao}
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"
className="inline-flex items-center mb-4 gap-2 bg-linear-to-r from-blue-600 to-purple-600 text-white px-6 py-3 rounded-xl font-semibold shadow hover:shadow-xl transition" className="inline-flex items-center justify-center gap-2 bg-slate-900 dark:bg-slate-100 text-white dark:text-slate-950 px-6 py-3.5 rounded-xl font-bold text-xs uppercase tracking-wider hover:opacity-90 transition-opacity shadow-lg"
> >
📚 Documentação Oficial <BookOpen className="w-4 h-4" />
<span>Documentação do Desenvolvedor</span>
</a> </a>
)} )}
@@ -148,9 +242,10 @@ export default async function FrameworkDetail({ params }) {
href={framework.repositorio} href={framework.repositorio}
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"
className="inline-flex items-center mb-4 gap-2 bg-white text-gray-800 px-6 py-3 rounded-xl font-semibold shadow border-2 border-gray-100 hover:border-purple-300 transition" className="inline-flex items-center justify-center gap-2 bg-purple-500/10 text-purple-600 dark:text-purple-400 border border-purple-500/20 px-6 py-3.5 rounded-xl font-bold text-xs uppercase tracking-wider hover:bg-purple-500/20 transition-all shadow-xs"
> >
💻 Repositório no GitHub <Code className="w-4 h-4" />
<span>Código Fonte no GitHub</span>
</a> </a>
)} )}
</div> </div>
+131 -94
View File
@@ -2,110 +2,129 @@ import Header from "@/components/Header";
import Footer from "@/components/Footer"; import Footer from "@/components/Footer";
import Link from "next/link"; import Link from "next/link";
import frameworksData from "@/public/frameworks.json"; import frameworksData from "@/public/frameworks.json";
import {
Cpu,
Target,
Zap,
Compass,
ExternalLink,
GraduationCap,
ArrowRight,
Code,
Layers,
Sparkles,
Music,
Camera,
Brain,
Home
} from "lucide-react";
export default function Frameworks() { export default function Frameworks() {
const frameworks = Object.entries(frameworksData); const frameworks = Object.entries(frameworksData);
return ( return (
<div className="bg-linear-to-br from-blue-50 via-white to-purple-50 min-h-screen"> <div className="bg-gradient-to-br from-slate-50 via-white to-purple-50/40 dark:from-slate-950 dark:via-slate-950 dark:to-slate-900 min-h-screen text-slate-900 dark:text-slate-100 transition-colors duration-300">
<Header /> <Header />
<main id="conteudo" className="px-4 pt-16 pb-20 max-w-7xl mx-auto"> <main id="conteudo" className="px-6 pt-16 pb-24 max-w-7xl mx-auto">
<section className="text-center mb-16"> {/* Top Hero Section */}
<div className="inline-flex items-center gap-2 bg-white/70 backdrop-blur-sm px-4 py-2 rounded-full mb-6 border border-purple-200 shadow-sm"> <section className="text-center mb-16 select-none">
<span className="text-xl">🛠️</span> <div className="inline-flex items-center gap-2 bg-white/70 dark:bg-slate-900/50 backdrop-blur-md px-4 py-2 rounded-full mb-6 border border-slate-200/60 dark:border-slate-800/60 shadow-xs">
<span className="text-sm font-semibold text-gray-700">Ferramentas de Desenvolvimento</span> <Layers className="w-4 h-4 text-purple-500" />
<span className="text-xs font-bold text-slate-700 dark:text-slate-350 uppercase tracking-wider">Ferramentas de Desenvolvimento</span>
</div> </div>
<h1 className="text-5xl md:text-7xl font-black bg-linear-to-r from-blue-600 via-purple-600 to-pink-600 bg-clip-text text-transparent mb-6 leading-tight tracking-tight"> <h1 className="text-4xl md:text-6xl font-display font-extrabold bg-gradient-to-r from-blue-600 via-purple-600 to-pink-600 dark:from-blue-400 dark:via-purple-400 dark:to-pink-400 bg-clip-text text-transparent mb-6 leading-none tracking-tight">
Frameworks ESP32 Frameworks ESP32
</h1> </h1>
<p className="text-lg md:text-xl text-gray-600 max-w-3xl mx-auto leading-relaxed"> <p className="text-sm md:text-base text-slate-500 dark:text-slate-400 max-w-2xl mx-auto leading-relaxed font-semibold">
Descubra os frameworks e bibliotecas disponíveis para ESP32. De desenvolvimento geral a Descubra o ecossistema de software do ESP32. De desenvolvimento geral C/C++ a
aplicações especializadas em áudio, visão computacional, IA e IoT. soluções dedicadas a áudio de alto desempenho, visão computacional na borda e IoT inteligente.
</p> </p>
</section> </section>
{/* Cards Grid */}
<div className="grid md:grid-cols-2 lg:grid-cols-3 gap-6"> <div className="grid md:grid-cols-2 lg:grid-cols-3 gap-6">
{frameworks.map(([key, framework]) => ( {frameworks.map(([key, framework]) => (
<Link <Link
key={key} key={key}
href={`/frameworks/${key}`} href={`/frameworks/${key}`}
className="group" className="group flex"
>
<div
className="bg-white/70 dark:bg-slate-900/40 backdrop-blur-xl border border-slate-200/60 dark:border-slate-800/80 rounded-3xl shadow-lg hover:shadow-2xl transition-all duration-500 hover:-translate-y-1.5 flex flex-col justify-between overflow-hidden w-full"
style={{ borderTop: `4px solid ${framework.cor}` }}
> >
<div className="bg-white rounded-2xl shadow-lg hover:shadow-2xl transform hover:-translate-y-2 transition-all duration-300 border-2 border-gray-100 hover:border-opacity-50 h-full flex flex-col">
<div className="p-6 border-b border-gray-100"> <div className="p-6 pb-4 border-b border-slate-100 dark:border-slate-850/40 flex-1">
<div className="flex items-center justify-between mb-3"> <div className="flex items-center justify-between mb-4">
<span className="text-5xl">{framework.icone}</span> <span className="text-5xl select-none filter drop-shadow-md">{framework.icone}</span>
<div className="flex flex-col gap-2 items-end"> <div className="flex flex-col gap-1 items-end select-none">
<span className="px-3 py-1 rounded-full text-xs font-semibold text-white" <span
style={{ backgroundColor: framework.cor }}> className="px-2.5 py-1 rounded-full text-[10px] font-bold uppercase tracking-wider"
style={{
backgroundColor: `${framework.cor}18`,
color: framework.cor,
border: `1px solid ${framework.cor}25`
}}
>
{framework.linguagem} {framework.linguagem}
</span> </span>
</div> </div>
</div> </div>
<h2 className="text-2xl font-bold text-gray-800 mb-1 group-hover:text-purple-600 transition-colors"> <h2 className="text-xl font-display font-extrabold text-slate-850 dark:text-slate-100 mb-1 group-hover:text-purple-650 dark:group-hover:text-purple-400 transition-colors">
{framework.nome} {framework.nome}
</h2> </h2>
<p className="text-xs text-gray-500 mb-3 font-medium"> <p className="text-[10px] text-slate-400 dark:text-slate-500 font-bold uppercase tracking-wider mb-4 leading-none select-none">
{framework.nome_completo} {framework.nome_completo}
</p> </p>
<p className="text-sm text-gray-600 line-clamp-3"> <p className="text-xs text-slate-500 dark:text-slate-300 leading-relaxed line-clamp-3">
{framework.descricao} {framework.descricao}
</p> </p>
</div> </div>
<div className="p-6 space-y-4 grow"> <div className="p-6 py-5 space-y-4 flex-1 flex flex-col justify-between select-none">
<div className="space-y-4">
{/* Main Feature */}
<div> <div>
<span className="inline-block px-3 py-1 rounded-lg text-xs font-semibold" <h4 className="text-[9px] font-bold text-slate-400 dark:text-slate-450 mb-1.5 uppercase tracking-widest flex items-center gap-1.5">
style={{ <Target className="w-3.5 h-3.5 text-blue-500" />
backgroundColor: `${framework.cor}20`, <span>Objetivo Principal</span>
color: framework.cor
}}>
{framework.tipo}
</span>
</div>
<div>
<h4 className="text-xs font-bold text-gray-700 mb-2 uppercase tracking-wide">
🎯 Função Principal
</h4> </h4>
<p className="text-sm text-gray-600 line-clamp-2"> <p className="text-xs text-slate-650 dark:text-slate-200 font-medium leading-relaxed">
{framework.funcao_principal} {framework.funcao_principal}
</p> </p>
</div> </div>
{/* Key Specs */}
<div> <div>
<h4 className="text-xs font-bold text-gray-700 mb-2 uppercase tracking-wide"> <h4 className="text-[9px] font-bold text-slate-400 dark:text-slate-450 mb-2 uppercase tracking-widest flex items-center gap-1.5">
⚡ Características <Zap className="w-3.5 h-3.5 text-amber-500" />
<span>Recursos Chave</span>
</h4> </h4>
<ul className="space-y-1"> <ul className="space-y-1">
{framework.caracteristicas.slice(0, 3).map((carac, idx) => ( {framework.caracteristicas.slice(0, 3).map((carac, idx) => (
<li key={idx} className="text-xs text-gray-600 flex items-start"> <li key={idx} className="text-xs text-slate-600 dark:text-slate-300 flex items-start gap-1.5">
<span className="mr-2 mt-0.5" style={{ color: framework.cor }}>▪</span> <span className="text-[8px] mt-1 shrink-0" style={{ color: framework.cor }}>■</span>
<span className="line-clamp-1">{carac}</span> <span className="line-clamp-1">{carac}</span>
</li> </li>
))} ))}
{framework.caracteristicas.length > 3 && (
<li className="text-xs text-gray-400 italic">
+{framework.caracteristicas.length - 3} mais...
</li>
)}
</ul> </ul>
</div> </div>
{/* Use Cases tags */}
<div> <div>
<h4 className="text-xs font-bold text-gray-700 mb-2 uppercase tracking-wide"> <h4 className="text-[9px] font-bold text-slate-400 dark:text-slate-450 mb-2 uppercase tracking-widest flex items-center gap-1.5">
💼 Casos de Uso <Compass className="w-3.5 h-3.5 text-emerald-500" />
<span>Aplicações Ideais</span>
</h4> </h4>
<div className="flex flex-wrap gap-2"> <div className="flex flex-wrap gap-1.5">
{framework.casos_uso.slice(0, 3).map((caso, idx) => ( {framework.casos_uso.slice(0, 3).map((caso, idx) => (
<span key={idx} className="text-xs bg-gray-100 text-gray-700 px-2 py-1 rounded"> <span key={idx} className="text-[9px] font-bold bg-slate-100 dark:bg-slate-900/60 text-slate-650 dark:text-slate-200 px-2.5 py-1 rounded-md border border-slate-200/40 dark:border-slate-800/80">
{caso} {caso}
</span> </span>
))} ))}
@@ -113,10 +132,10 @@ export default function Frameworks() {
</div> </div>
</div> </div>
<div className="px-6 pb-6"> {/* CTA Link */}
<div className="flex items-center justify-between text-sm font-semibold group-hover:text-purple-600 transition-colors"> <div className="pt-4 border-t border-slate-100 dark:border-slate-850/40 mt-auto flex items-center justify-between text-xs font-bold text-slate-700 dark:text-slate-300 group-hover:text-purple-650 dark:group-hover:text-purple-400 transition-colors">
<span>Ver Detalhes e Exemplos</span> <span>Acessar Detalhes e Exemplos</span>
<span className="transform group-hover:translate-x-2 transition-transform">→</span> <ArrowRight className="w-4 h-4 transform group-hover:translate-x-1.5 transition-transform" />
</div> </div>
</div> </div>
</div> </div>
@@ -124,71 +143,89 @@ export default function Frameworks() {
))} ))}
</div> </div>
<div className="mt-16 grid md:grid-cols-2 gap-8"> {/* Bottom Informative Cards Section */}
<div className="bg-linear-to-br from-white to-blue-50 rounded-3xl shadow-2xl p-8 border-2 border-blue-100"> <div className="mt-16 grid md:grid-cols-2 gap-8 select-none">
<div className="flex items-center gap-3 mb-4"> {/* Card 1: Beginner Guide */}
<div className="w-12 h-12 bg-linear-to-br from-blue-500 to-blue-600 rounded-xl flex items-center justify-center"> <div className="bg-white/80 dark:bg-slate-900/40 backdrop-blur-xl rounded-3xl shadow-xl p-8 border border-slate-200/60 dark:border-slate-800/80 flex flex-col justify-between relative overflow-hidden">
<span className="text-2xl">🎓</span> <div>
<div className="flex items-center gap-3.5 mb-5">
<div className="w-12 h-12 bg-blue-500/10 border border-blue-500/20 rounded-2xl flex items-center justify-center text-blue-500">
<GraduationCap className="w-6 h-6" />
</div> </div>
<h3 className="text-2xl font-bold text-gray-900"> <div>
<h3 className="text-xl font-display font-extrabold text-slate-850 dark:text-slate-100">
Iniciante no ESP32? Iniciante no ESP32?
</h3> </h3>
<p className="text-[10px] font-bold text-blue-500 uppercase tracking-wider leading-none">Guia de Aprendizado</p>
</div> </div>
<p className="text-gray-700 mb-6 leading-relaxed text-base"> </div>
Se você está começando, recomendo iniciar com&nbsp;
<strong>Arduino</strong> ou&nbsp; <p className="text-xs text-slate-500 dark:text-slate-400 leading-relaxed mb-6 font-semibold">
<strong>MicroPython</strong>&nbsp; Se você está iniciando a sua jornada de hardware, recomendamos adotar o ecossistema&nbsp;
para prototipagem rápida, ou&nbsp; <strong className="text-slate-750 dark:text-slate-300">Arduino</strong> ou&nbsp;
<strong>ESP-IDF</strong> para projetos profissionais com controle total. <strong className="text-slate-750 dark:text-slate-300">MicroPython</strong>&nbsp;
para testar sensores e APIs. Para projetos profissionais industriais de alta performance e eficiência energética, o framework nativo&nbsp;
<strong className="text-slate-750 dark:text-slate-300">ESP-IDF</strong> é o padrão exigido no mercado.
</p> </p>
<div className="flex flex-wrap gap-3"> </div>
<Link href="/frameworks/Arduino" className="inline-flex items-center gap-2 px-5 py-3 bg-blue-100 text-blue-700 rounded-xl text-sm font-bold hover:bg-blue-200 transition-all shadow-sm hover:shadow-md transform hover:-translate-y-0.5">
<span className="text-lg">📘</span> <div className="flex flex-wrap gap-2.5 pt-4 border-t border-slate-100 dark:border-slate-850/40 mt-auto">
<Link href="/frameworks/Arduino" className="inline-flex items-center gap-1.5 px-4 py-2.5 bg-blue-500/10 text-blue-600 dark:text-blue-400 border border-blue-500/20 rounded-xl text-xs font-bold hover:bg-blue-500/20 transition-all">
<span>Arduino</span> <span>Arduino</span>
<ArrowRight className="w-3.5 h-3.5" />
</Link> </Link>
<Link href="/frameworks/MicroPython" className="inline-flex items-center gap-2 px-5 py-3 bg-purple-100 text-purple-700 rounded-xl text-sm font-bold hover:bg-purple-200 transition-all shadow-sm hover:shadow-md transform hover:-translate-y-0.5"> <Link href="/frameworks/MicroPython" className="inline-flex items-center gap-1.5 px-4 py-2.5 bg-purple-500/10 text-purple-600 dark:text-purple-400 border border-purple-500/20 rounded-xl text-xs font-bold hover:bg-purple-500/20 transition-all">
<span className="text-lg">🐍</span>
<span>MicroPython</span> <span>MicroPython</span>
<ArrowRight className="w-3.5 h-3.5" />
</Link> </Link>
<Link href="/frameworks/ESP-IDF" className="inline-flex items-center gap-2 px-5 py-3 bg-red-100 text-red-700 rounded-xl text-sm font-bold hover:bg-red-200 transition-all shadow-sm hover:shadow-md transform hover:-translate-y-0.5"> <Link href="/frameworks/ESP-IDF" className="inline-flex items-center gap-1.5 px-4 py-2.5 bg-red-500/10 text-red-600 dark:text-red-450 border border-red-500/20 rounded-xl text-xs font-bold hover:bg-red-500/20 transition-all">
<span className="text-lg">⚙️</span>
<span>ESP-IDF</span> <span>ESP-IDF</span>
<ArrowRight className="w-3.5 h-3.5" />
</Link> </Link>
</div> </div>
</div> </div>
<div className="bg-linear-to-br from-white to-purple-50 rounded-3xl shadow-2xl p-8 border-2 border-purple-100"> {/* Card 2: Specialized Solutions */}
<div className="flex items-center gap-3 mb-4"> <div className="bg-white/80 dark:bg-slate-900/40 backdrop-blur-xl rounded-3xl shadow-xl p-8 border border-slate-200/60 dark:border-slate-800/80 flex flex-col justify-between relative overflow-hidden">
<div className="w-12 h-12 bg-linear-to-br from-purple-500 to-pink-600 rounded-xl flex items-center justify-center"> <div>
<span className="text-2xl">🚀</span> <div className="flex items-center gap-3.5 mb-5">
<div className="w-12 h-12 bg-purple-500/10 border border-purple-500/20 rounded-2xl flex items-center justify-center text-purple-500">
<Sparkles className="w-6 h-6" />
</div> </div>
<h3 className="text-2xl font-bold text-gray-900"> <div>
Aplicações Especializadas? <h3 className="text-xl font-display font-extrabold text-slate-850 dark:text-slate-100">
Soluções Especializadas
</h3> </h3>
<p className="text-[10px] font-bold text-purple-500 uppercase tracking-wider leading-none">Domínios Avançados</p>
</div> </div>
<p className="text-gray-700 mb-6 leading-relaxed text-base"> </div>
Para projetos específicos, explore frameworks especializados como&nbsp;
<strong>ESP-ADF</strong> (áudio),&nbsp; <p className="text-xs text-slate-500 dark:text-slate-400 leading-relaxed mb-6 font-semibold">
<strong>ESP-WHO</strong> (visão),&nbsp; Projetos complexos de IoT demandam bibliotecas altamente otimizadas na arquitetura.
<strong>ESP-NN</strong> (IA) ou&nbsp; A Espressif fornece extensões prontas como o&nbsp;
<strong>ESP-Matter</strong> (smart home). <strong className="text-slate-750 dark:text-slate-300">ESP-ADF</strong> para processamento de áudio digital,&nbsp;
<strong className="text-slate-750 dark:text-slate-300">ESP-WHO</strong> para processamento de imagem por câmera,&nbsp;
<strong className="text-slate-750 dark:text-slate-300">ESP-NN</strong> para redes neurais locais e&nbsp;
<strong className="text-slate-750 dark:text-slate-300">ESP-Matter</strong> para automação residencial.
</p> </p>
<div className="flex flex-wrap gap-3"> </div>
<Link href="/frameworks/ESP-ADF" className="inline-flex items-center gap-2 px-5 py-3 bg-blue-100 text-blue-700 rounded-xl text-sm font-bold hover:bg-blue-200 transition-all shadow-sm hover:shadow-md transform hover:-translate-y-0.5">
<span className="text-lg">🎵</span> <div className="flex flex-wrap gap-2.5 pt-4 border-t border-slate-100 dark:border-slate-850/40 mt-auto">
<Link href="/frameworks/ESP-ADF" className="inline-flex items-center gap-1.5 px-3.5 py-2 bg-slate-150/45 dark:bg-slate-850 text-slate-700 dark:text-slate-300 border border-slate-200/60 dark:border-slate-850/60 rounded-xl text-xs font-bold hover:bg-slate-200 dark:hover:bg-slate-800 transition-all">
<Music className="w-3.5 h-3.5 text-blue-500" />
<span>Áudio</span> <span>Áudio</span>
</Link> </Link>
<Link href="/frameworks/ESP-WHO" className="inline-flex items-center gap-2 px-5 py-3 bg-orange-100 text-orange-700 rounded-xl text-sm font-bold hover:bg-orange-200 transition-all shadow-sm hover:shadow-md transform hover:-translate-y-0.5"> <Link href="/frameworks/ESP-WHO" className="inline-flex items-center gap-1.5 px-3.5 py-2 bg-slate-150/45 dark:bg-slate-850 text-slate-700 dark:text-slate-300 border border-slate-200/60 dark:border-slate-850/60 rounded-xl text-xs font-bold hover:bg-slate-200 dark:hover:bg-slate-800 transition-all">
<span className="text-lg">📷</span> <Camera className="w-3.5 h-3.5 text-orange-500" />
<span>Visão</span> <span>Visão</span>
</Link> </Link>
<Link href="/frameworks/ESP-NN" className="inline-flex items-center gap-2 px-5 py-3 bg-teal-100 text-teal-700 rounded-xl text-sm font-bold hover:bg-teal-200 transition-all shadow-sm hover:shadow-md transform hover:-translate-y-0.5"> <Link href="/frameworks/ESP-NN" className="inline-flex items-center gap-1.5 px-3.5 py-2 bg-slate-150/45 dark:bg-slate-850 text-slate-700 dark:text-slate-300 border border-slate-200/60 dark:border-slate-850/60 rounded-xl text-xs font-bold hover:bg-slate-200 dark:hover:bg-slate-800 transition-all">
<span className="text-lg">🧠</span> <Brain className="w-3.5 h-3.5 text-teal-500" />
<span>IA</span> <span>Inteligência Artificial</span>
</Link> </Link>
<Link href="/frameworks/ESP-Matter" className="inline-flex items-center gap-2 px-5 py-3 bg-yellow-100 text-yellow-700 rounded-xl text-sm font-bold hover:bg-yellow-200 transition-all shadow-sm hover:shadow-md transform hover:-translate-y-0.5"> <Link href="/frameworks/ESP-Matter" className="inline-flex items-center gap-1.5 px-3.5 py-2 bg-slate-150/45 dark:bg-slate-850 text-slate-700 dark:text-slate-300 border border-slate-200/60 dark:border-slate-850/60 rounded-xl text-xs font-bold hover:bg-slate-200 dark:hover:bg-slate-800 transition-all">
<span className="text-lg">🏠</span> <Home className="w-3.5 h-3.5 text-yellow-500" />
<span>Smart Home</span> <span>Matter</span>
</Link> </Link>
</div> </div>
</div> </div>
+46
View File
@@ -1 +1,47 @@
@import "tailwindcss"; @import "tailwindcss";
@theme {
--font-sans: var(--font-inter), ui-sans-serif, system-ui, -apple-system, sans-serif;
--font-display: var(--font-outfit), ui-sans-serif, system-ui, -apple-system, sans-serif;
/* Premium dark mode palette accents */
--color-brand-purple: #8b5cf6;
--color-brand-blue: #3b82f6;
--color-brand-pink: #ec4899;
}
/* Enable class-based dark mode selector in Tailwind v4 */
@variant dark (&:where(.dark, .dark *));
@layer base {
html {
scroll-behavior: smooth;
}
body {
@apply bg-slate-50 text-slate-900 transition-colors duration-300;
}
body.dark {
@apply bg-slate-950 text-slate-50;
}
/* Sleek selection highlighting */
::selection {
@apply bg-purple-500/30 text-purple-200;
}
/* Scrollbar customization for a premium, non-default OS look */
::-webkit-scrollbar {
width: 8px;
height: 8px;
}
::-webkit-scrollbar-track {
@apply bg-transparent;
}
::-webkit-scrollbar-thumb {
@apply bg-slate-300 dark:bg-slate-800 rounded-full hover:bg-slate-400 dark:hover:bg-slate-700 transition-colors;
}
}
+17 -4
View File
@@ -1,7 +1,18 @@
import { Roboto } from "next/font/google"; import { Outfit, Inter } from "next/font/google";
import "./globals.css"; import "./globals.css";
import Providers from "@/components/Providers";
const roboto = Roboto({ subsets: ["latin"] }); const outfit = Outfit({
subsets: ["latin"],
variable: "--font-outfit",
display: "swap",
});
const inter = Inter({
subsets: ["latin"],
variable: "--font-inter",
display: "swap",
});
export const metadata = { export const metadata = {
title: "ESPDocs", title: "ESPDocs",
@@ -11,9 +22,11 @@ export const metadata = {
export default function RootLayout({ children }) { export default function RootLayout({ children }) {
return ( return (
<html lang="pt-BR"> <html lang="pt-BR" className={`${outfit.variable} ${inter.variable}`} suppressHydrationWarning>
<body className={`${roboto.className} font-sans`}> <body className="font-sans antialiased bg-slate-50 text-slate-900 dark:bg-slate-950 dark:text-slate-50 transition-colors duration-300">
<Providers>
{children} {children}
</Providers>
</body> </body>
</html> </html>
); );
+148 -72
View File
@@ -1,103 +1,171 @@
"use client";
import Header from "@/components/Header"; import Header from "@/components/Header";
import Footer from "@/components/Footer"; import Footer from "@/components/Footer";
import Link from "next/link"; import Link from "next/link";
import {
SlidersHorizontal,
ShoppingBag,
Table2,
Cpu,
Layers,
Scale,
FileText,
Code,
RefreshCw,
Globe,
ArrowRight,
Terminal,
Compass,
Info
} from "lucide-react";
export default function Home() { export default function Home() {
const resources = [ const resources = [
{name: "Seletor Inteligente", description: "Responda perguntas sobre seu projeto e descubra qual série ESP32 é ideal para você", icon: "🎯"}, {
{name: "Catálogo de Placas", description: "Encontre placas de desenvolvimento ESP32 verificadas com filtros avançados e links seguros para compra", icon: "🛒"}, name: "Seletor Inteligente",
{name: "Especificações Organizadas", description: "Especificações técnicas de cada série ESP32 organizadas em tabelas comparativas em português", icon: "🔧"}, description: "Responda perguntas rápidas sobre seu projeto e descubra a série ESP32 ideal em segundos.",
{name: "Diagramas de Conexões", description: "Visualize diagramas interativos dos componentes e pinos de cada série ESP32", icon: "📊"}, icon: <SlidersHorizontal className="w-6 h-6" />,
{name: "Informações sobre Frameworks", description: "Conheça frameworks como ESP-IDF, Arduino e MicroPython com descrições, casos de uso e exemplos", icon: "⚡"}, colorClass: "text-purple-600 bg-purple-50 dark:bg-purple-950/40 dark:text-purple-400"
{name: "Comparação de Séries", description: "Compare lado a lado as especificações de diferentes séries ESP32 em uma única visualização", icon: "📈"}, },
{name: "Links para Documentação", description: "Acesso direto aos datasheets, manuais técnicos e guias oficiais da Espressif (em inglês)", icon: "📄"}, {
{name: "Exemplos de Código", description: "Snippets de código básico para diferentes frameworks com links para repositórios oficiais", icon: "💻"}, name: "Catálogo de Placas",
{name: "Informações Atualizadas", description: "Dados atualizados sobre as séries e frameworks mais recentes do ecossistema ESP32", icon: "🔄"}, description: "Encontre placas de desenvolvimento ESP32 verificadas com filtros avançados e links seguros.",
{name: "Interface em Português", description: "Navegação e organização de informações totalmente em português para facilitar a consulta", icon: "📚"}, icon: <ShoppingBag className="w-6 h-6" />,
] colorClass: "text-blue-600 bg-blue-50 dark:bg-blue-950/40 dark:text-blue-400"
},
{
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.",
icon: <Scale className="w-6 h-6" />,
colorClass: "text-cyan-600 bg-cyan-50 dark:bg-cyan-950/40 dark:text-cyan-400"
},
{
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"
},
{
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"
},
{
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"
},
{
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"
},
];
return ( return (
<div className="bg-linear-to-br from-blue-50 via-white to-purple-50 min-h-screen"> <div className="bg-gradient-to-br from-slate-50 via-white to-purple-50/40 dark:from-slate-950 dark:via-slate-950 dark:to-slate-900 min-h-screen text-slate-900 dark:text-slate-100 transition-colors duration-300">
<Header /> <Header />
<main className="px-4 pt-12 pb-16 max-w-7xl mx-auto"> <main className="px-6 pt-16 pb-24 max-w-7xl mx-auto">
{/* Hero Section */} {/* Hero Section */}
<section className="text-center mb-20"> <section className="text-center mb-24 max-w-5xl mx-auto">
<h1 className="text-5xl md:text-7xl lg:text-8xl font-black bg-linear-to-r from-blue-600 via-purple-600 to-pink-600 bg-clip-text text-transparent mb-6 tracking-tight"> <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">
ESPDocs ESPDocs
</h1> </h1>
<h2 className="text-xl md:text-2xl lg:text-3xl text-gray-800 font-bold mb-6 max-w-4xl mx-auto leading-tight"> <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 Organização e acesso facilitado ao ecossistema ESP32
</h2> </h2>
<p className="text-base md:text-lg text-gray-600 max-w-2xl mx-auto leading-relaxed mb-10"> <p className="text-base md:text-lg text-slate-500 dark:text-slate-400 max-w-2xl mx-auto leading-relaxed mb-12">
Plataforma não oficial com interface em português que organiza e centraliza 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.
informações sobre ESP32. Compare especificações, visualize diagramas e acesse
a documentação oficial da Espressif.
</p> </p>
<div className="flex flex-col sm:flex-row gap-4 justify-center items-center"> {/* Botões de Ação */}
<div className="flex flex-wrap gap-4 justify-center items-center">
<Link <Link
className="group inline-flex items-center gap-3 bg-linear-to-r from-green-600 to-emerald-600 text-white px-8 py-4 rounded-xl font-bold text-base shadow-lg hover:shadow-2xl transform hover:-translate-y-1 transition-all duration-300" 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"
href="/seletor" href="/seletor"
> >
<span className="text-xl">🎯</span> <Compass className="w-4 h-4" />
<span>Encontrar ESP32 Ideal</span> <span>Encontrar ESP32 Ideal</span>
<svg className="w-5 h-5 transform group-hover:translate-x-1 transition-transform" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <ArrowRight className="w-4 h-4 transform group-hover:translate-x-1 transition-transform" />
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 5l7 7-7 7" />
</svg>
</Link> </Link>
<Link <Link
className="group inline-flex items-center gap-3 bg-linear-to-r from-blue-600 to-purple-600 text-white px-8 py-4 rounded-xl font-bold text-base shadow-lg hover:shadow-2xl transform hover:-translate-y-1 transition-all duration-300" className="group inline-flex items-center gap-2.5 bg-slate-900 hover:bg-slate-800 dark:bg-slate-800 dark:hover:bg-slate-700 text-white px-7 py-3.5 rounded-xl font-bold text-sm shadow-md hover:-translate-y-0.5 transition-all duration-300 cursor-pointer active:scale-95"
href="/series" href="/series"
> >
<span className="text-xl">📚</span> <Cpu className="w-4 h-4" />
<span>Explorar Séries ESP32</span> <span>Explorar Séries</span>
</Link> </Link>
<Link <Link
className="group inline-flex items-center gap-3 bg-white text-gray-800 px-8 py-4 rounded-xl font-bold text-base shadow-lg hover:shadow-xl border-2 border-gray-200 hover:border-purple-500 transform hover:-translate-y-1 transition-all duration-300" className="group inline-flex items-center gap-2.5 bg-white dark:bg-slate-900 text-slate-700 dark:text-slate-300 px-7 py-3.5 rounded-xl font-bold text-sm shadow-xs border border-slate-200 dark:border-slate-800 hover:border-purple-500 dark:hover:border-purple-400 hover:text-purple-600 dark:hover:text-purple-400 hover:-translate-y-0.5 transition-all duration-300 cursor-pointer active:scale-95"
href="/frameworks" href="/frameworks"
> >
<span className="text-xl">⚡</span> <Terminal className="w-4 h-4" />
<span>Frameworks</span> <span>Frameworks</span>
</Link> </Link>
<Link <Link
className="group inline-flex items-center gap-3 bg-white text-gray-800 px-8 py-4 rounded-xl font-bold text-base shadow-lg hover:shadow-xl border-2 border-gray-200 hover:border-purple-500 transform hover:-translate-y-1 transition-all duration-300" className="group inline-flex items-center gap-2.5 bg-white dark:bg-slate-900 text-slate-700 dark:text-slate-300 px-7 py-3.5 rounded-xl font-bold text-sm shadow-xs border border-slate-200 dark:border-slate-800 hover:border-purple-500 dark:hover:border-purple-400 hover:text-purple-600 dark:hover:text-purple-400 hover:-translate-y-0.5 transition-all duration-300 cursor-pointer active:scale-95"
href="/catalogo" href="/catalogo"
> >
<span className="text-xl">🛒</span> <ShoppingBag className="w-4 h-4" />
<span>Catálogo</span> <span>Placas</span>
</Link> </Link>
</div> </div>
</section> </section>
{/* Recursos Section */} {/* Recursos Section */}
<section className="mb-20"> <section className="mb-24">
<div className="text-center mb-12"> <div className="text-center mb-16">
<h2 className="text-3xl md:text-4xl font-bold text-gray-900 mb-4">Recursos da Plataforma</h2> <h2 className="text-3xl md:text-4xl font-display font-extrabold text-slate-800 dark:text-slate-100 mb-4">
<p className="text-lg text-gray-600 max-w-2xl mx-auto">Ferramentas e informações organizadas para facilitar seu trabalho com ESP32</p> 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>
<div className="grid md:grid-cols-2 lg:grid-cols-3 gap-6"> <div className="grid md:grid-cols-2 lg:grid-cols-3 gap-6">
{resources.map((resource, index) => ( {resources.map((resource) => (
<div <div
key={resource.name} key={resource.name}
className="group bg-white p-6 rounded-2xl shadow-md hover:shadow-xl transition-all duration-300 border-2 border-gray-100 hover:border-purple-200" className="group bg-white/70 dark:bg-slate-900/40 backdrop-blur-md p-6 rounded-2xl border border-slate-200/60 dark:border-slate-800/80 hover:border-purple-500/40 dark:hover:border-purple-400/40 shadow-xs hover:shadow-xl hover:shadow-purple-500/5 hover:-translate-y-1 transition-all duration-300"
style={{ animationDelay: `${index * 50}ms` }}
> >
<div className="flex items-start gap-4"> <div className="flex items-start gap-4">
<div className="text-4xl shrink-0 transform group-hover:scale-110 transition-transform"> <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} {resource.icon}
</div> </div>
<div className="flex-1"> <div className="flex-1">
<h3 className="text-lg font-bold text-gray-900 mb-2 group-hover:text-purple-600 transition-colors"> <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} {resource.name}
</h3> </h3>
<p className="text-sm text-gray-600 leading-relaxed"> <p className="text-sm text-slate-500 dark:text-slate-400 leading-relaxed">
{resource.description} {resource.description}
</p> </p>
</div> </div>
@@ -108,60 +176,59 @@ export default function Home() {
</section> </section>
{/* Ciência Embarcada Section */} {/* Ciência Embarcada Section */}
<section className="mb-12"> <section className="mb-8">
<div className="bg-linear-to-br from-white to-purple-50 rounded-3xl shadow-xl p-8 md:p-12 border-2 border-purple-100"> <div className="bg-gradient-to-br from-white to-purple-50/50 dark:from-slate-900/40 dark:to-purple-950/10 rounded-3xl shadow-xs border border-slate-200/80 dark:border-slate-800/80 p-8 md:p-12 relative overflow-hidden">
{/* Logo e Link do Ciência Embarcada */} {/* Background soft glow decoration */}
<div className="flex flex-col items-center mb-6"> <div className="absolute right-0 bottom-0 w-80 h-80 bg-purple-500/5 rounded-full blur-3xl pointer-events-none" />
<div className="flex flex-col items-center text-center relative z-10 max-w-3xl mx-auto">
<a <a
href="https://cienciaembarcada.com.br" href="https://cienciaembarcada.com.br"
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"
className="group" className="group mb-6"
> >
<img <img
src="/logo.png" src="/logo.png"
alt="Ciência Embarcada" alt="Ciência Embarcada Logo"
className="h-16 md:h-20 mb-4 transition-transform duration-300 group-hover:scale-110" className="h-16 md:h-20 mb-2 transition-transform duration-300 group-hover:scale-105"
/> />
</a> </a>
<p className="text-lg font-semibold text-gray-800 mb-2">
Conteúdo por <a <p className="text-lg font-bold text-slate-800 dark:text-slate-200 mb-3">
Conteúdo e Curadoria por{' '}
<a
href="https://cienciaembarcada.com.br" href="https://cienciaembarcada.com.br"
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"
className="text-purple-600 hover:text-purple-800 transition-colors" className="text-purple-600 dark:text-purple-400 hover:underline transition-colors font-extrabold"
> >
Ciência Embarcada Ciência Embarcada
</a> </a>
</p> </p>
<p className="text-sm text-gray-600 max-w-xl text-center mb-4">
Artigos sobre ESP32, Eletrônica, IoT, Cibersegurança e muito mais. <p className="text-sm text-slate-500 dark:text-slate-400 leading-relaxed mb-8 max-w-xl">
Visite o blog para aprender com tutoriais práticos e projetos reais! Acesse artigos profundos sobre programação de microcontroladores, design de hardware, Internet das Coisas (IoT), laboratórios práticos e projetos reais em nosso blog parceiro oficial.
</p> </p>
<a <a
href="https://cienciaembarcada.com.br" href="https://cienciaembarcada.com.br"
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"
className="inline-flex items-center gap-2 bg-linear-to-r from-blue-600 to-purple-600 text-white px-6 py-3 rounded-xl font-semibold hover:shadow-lg transform hover:-translate-y-1 transition-all duration-300" 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"
> >
🚀 Visitar Blog <span>Visitar Blog Ciência Embarcada</span>
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <ExternalLinkIcon />
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14" />
</svg>
</a> </a>
</div> </div>
{/* Disclaimer */} {/* Disclaimer */}
<div className="border-t-2 border-purple-200 pt-6 mt-6"> <div className="border-t border-slate-200/80 dark:border-slate-800/80 pt-8 mt-10">
<div className="bg-blue-50 border-l-4 border-blue-500 p-4 rounded-r-lg"> <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 items-start gap-3"> <div className="flex gap-4">
<svg className="w-5 h-5 text-blue-600 mt-0.5 shrink-0" fill="currentColor" viewBox="0 0 20 20"> <Info className="w-5 h-5 text-purple-600 dark:text-purple-400 mt-0.5 shrink-0" />
<path fillRule="evenodd" d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7-4a1 1 0 11-2 0 1 1 0 012 0zM9 9a1 1 0 000 2v3a1 1 0 001 1h1a1 1 0 100-2v-3a1 1 0 00-1-1H9z" clipRule="evenodd" /> <p className="text-xs leading-relaxed text-slate-500 dark:text-slate-400">
</svg> <strong className="font-bold text-slate-700 dark:text-slate-200">Plataforma independente e não oficial:</strong> O ESPDocs é um projeto educativo que compila, filtra e expõe dados públicos e links de documentações oficiais fornecidas pela fabricante Espressif Systems. Para projetos industriais ou decisões de engenharia críticas, sempre consulte a documentação técnica nativa do fabricante.
<p className="text-sm text-gray-700 leading-relaxed">
<strong className="font-semibold text-gray-900">Plataforma não oficial:</strong> O ESPDocs é independente e sem afiliação com a Espressif Systems.
Organiza informações públicas e links para documentação oficial. Para dados técnicos críticos,
consulte sempre os datasheets e manuais oficiais da Espressif.
</p> </p>
</div> </div>
</div> </div>
@@ -174,3 +241,12 @@ export default function Home() {
</div> </div>
); );
} }
/* Local mini-icon component for external link */
function ExternalLinkIcon() {
return (
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2.5} d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14" />
</svg>
);
}
+567 -459
View File
File diff suppressed because it is too large. Load diff
+243 -168
View File
@@ -6,6 +6,41 @@ import { notFound } from "next/navigation";
import seriesData from "@/public/series.json"; import seriesData from "@/public/series.json";
import ConnectionsDiagram from "@/components/ConnectionsDiagram"; import ConnectionsDiagram from "@/components/ConnectionsDiagram";
import SeriesTabMenu from "@/components/SeriesTabMenu"; import SeriesTabMenu from "@/components/SeriesTabMenu";
import {
Cpu,
Activity,
Database,
Wifi,
Bluetooth,
Battery,
Sparkles,
Home,
ArrowRight,
ArrowLeft,
ExternalLink,
Scale,
FileText,
Code,
BookOpen,
AlertTriangle,
Moon,
Plug,
Compass,
Sliders,
MapPin,
Volume2,
Fingerprint,
MessageSquare,
Repeat,
Share2,
Music,
Clock,
HardDrive,
Folder,
Thermometer,
ChevronRight,
Lightbulb
} from "lucide-react";
export default async function SerieDetail({ params }) { export default async function SerieDetail({ params }) {
const { key } = await params; const { key } = await params;
@@ -28,51 +63,51 @@ export default async function SerieDetail({ params }) {
const nextKey = currentIndex < seriesKeys.length - 1 ? seriesKeys[currentIndex + 1] : null; const nextKey = currentIndex < seriesKeys.length - 1 ? seriesKeys[currentIndex + 1] : null;
const processadorSpecs = [ const processadorSpecs = [
{ label: "Arquitetura", value: serie.arquitetura, icon: "🔧" }, { label: "Arquitetura", value: serie.arquitetura, icon: <Cpu className="w-5 h-5 text-blue-500" /> },
{ label: "Núcleos", value: serie.nucleos, icon: "⚙️" }, { label: "Núcleos", value: serie.nucleos, icon: <Sliders className="w-5 h-5 text-blue-500" /> },
{ label: "Frequência", value: serie.frequencia, icon: "🚀" }, { label: "Frequência", value: serie.frequencia, icon: <Activity className="w-5 h-5 text-blue-500" /> },
{ label: "Coprocessador ULP", value: serie.coprocessador_ulp || "Não", icon: "💤" }, { label: "Coprocessador ULP", value: serie.coprocessador_ulp || "Não", icon: <Moon className="w-5 h-5 text-blue-500" /> },
{ label: "Aceleradores IA", value: serie.aceleradores_ia || "Não", icon: "🤖" }, { label: "Aceleradores IA", value: serie.aceleradores_ia || "Não", icon: <Sparkles className="w-5 h-5 text-purple-500" /> },
]; ];
const conectividadeSpecs = [ const conectividadeSpecs = [
{ label: "Wi-Fi", value: serie.wifi || "Não", icon: "📶" }, { label: "Wi-Fi", value: serie.wifi || "Não", icon: <Wifi className="w-5 h-5 text-indigo-500" /> },
{ label: "Bluetooth", value: serie.bluetooth || "Não", icon: "📱" }, { label: "Bluetooth", value: serie.bluetooth || "Não", icon: <Bluetooth className="w-5 h-5 text-indigo-500" /> },
{ label: "Zigbee/Thread", value: serie.zigbee_thread || "Não", icon: "🌐" }, { label: "Zigbee/Thread", value: serie.zigbee_thread || "Não", icon: <Compass className="w-5 h-5 text-indigo-500" /> },
{ label: "Matter", value: serie.matter || "Não", icon: "🏠" }, { label: "Matter", value: serie.matter || "Não", icon: <Home className="w-5 h-5 text-indigo-500" /> },
{ label: "Ethernet", value: serie.ethernet || serie.ethernet_mac || "Não", icon: "🔌" }, { label: "Ethernet", value: serie.ethernet || serie.ethernet_mac || "Não", icon: <Plug className="w-5 h-5 text-indigo-500" /> },
{ label: "CAN", value: serie.can || "Não", icon: "🚗" }, { label: "CAN", value: serie.can || "Não", icon: <Activity className="w-5 h-5 text-indigo-500" /> },
]; ];
const memoriaSpecs = [ const memoriaSpecs = [
{ label: "SRAM", value: serie.memoria_sram, icon: "💾" }, { label: "SRAM", value: serie.memoria_sram, icon: <Database className="w-5 h-5 text-pink-500" /> },
{ label: "SRAM RTC", value: serie.memoria_sram_rtc, icon: "⏰" }, { label: "SRAM RTC", value: serie.memoria_sram_rtc, icon: <Clock className="w-5 h-5 text-pink-500" /> },
{ label: "ROM", value: serie.memoria_rom, icon: "📀" }, { label: "ROM", value: serie.memoria_rom, icon: <Folder className="w-5 h-5 text-pink-500" /> },
{ label: "Flash Externa", value: serie.flash_externa, icon: "💿" }, { label: "Flash Externa", value: serie.flash_externa, icon: <HardDrive className="w-5 h-5 text-pink-500" /> },
{ label: "PSRAM Externa", value: serie.psram_externa || "Não", icon: "🎯" }, { label: "PSRAM Externa", value: serie.psram_externa || "Não", icon: <Sliders className="w-5 h-5 text-pink-500" /> },
]; ];
const perifericos = [ const perifericos = [
{ label: "GPIO", value: serie.gpio, icon: "📍" }, { label: "GPIO", value: serie.gpio, icon: <MapPin className="w-5 h-5 text-emerald-500" /> },
{ label: "ADC", value: serie.adc, icon: "📊" }, { label: "ADC", value: serie.adc, icon: <Activity className="w-5 h-5 text-emerald-500" /> },
{ label: "DAC", value: serie.dac || "Não", icon: "🔊" }, { label: "DAC", value: serie.dac || "Não", icon: <Volume2 className="w-5 h-5 text-emerald-500" /> },
{ label: "Touch", value: serie.touch || "Não", icon: "👆" }, { label: "Touch", value: serie.touch || "Não", icon: <Fingerprint className="w-5 h-5 text-emerald-500" /> },
{ label: "UART", value: serie.uart, icon: "💬" }, { label: "UART", value: serie.uart, icon: <MessageSquare className="w-5 h-5 text-emerald-500" /> },
{ label: "SPI", value: serie.spi, icon: "🔄" }, { label: "SPI", value: serie.spi, icon: <Repeat className="w-5 h-5 text-emerald-500" /> },
{ label: "I2C", value: serie.i2c, icon: "🔗" }, { label: "I2C", value: serie.i2c, icon: <Share2 className="w-5 h-5 text-emerald-500" /> },
{ label: "I2S", value: serie.i2s, icon: "🎵" }, { label: "I2S", value: serie.i2s, icon: <Music className="w-5 h-5 text-emerald-500" /> },
{ label: "PWM", value: serie.pwm, icon: "〰️" }, { label: "PWM", value: serie.pwm, icon: <Sliders className="w-5 h-5 text-emerald-500" /> },
{ label: "USB", value: serie.usb || "Não", icon: "🔌" }, { label: "USB", value: serie.usb || "Não", icon: <Plug className="w-5 h-5 text-emerald-500" /> },
]; ];
const interfacesEspeciais = []; const interfacesEspeciais = [];
if (serie.lcd) interfacesEspeciais.push({ label: "LCD", value: serie.lcd, icon: "🖥️" }); if (serie.lcd) interfacesEspeciais.push({ label: "LCD", value: serie.lcd, icon: <Sliders className="w-5 h-5 text-cyan-500" /> });
if (serie.camera) interfacesEspeciais.push({ label: "Camera", value: serie.camera, icon: "📷" }); if (serie.camera) interfacesEspeciais.push({ label: "Câmera", value: serie.camera, icon: <Sliders className="w-5 h-5 text-cyan-500" /> });
if (serie.mipi_csi) interfacesEspeciais.push({ label: "MIPI CSI", value: serie.mipi_csi, icon: "📹" }); if (serie.mipi_csi) interfacesEspeciais.push({ label: "MIPI CSI", value: serie.mipi_csi, icon: <Sliders className="w-5 h-5 text-cyan-500" /> });
if (serie.mipi_dsi) interfacesEspeciais.push({ label: "MIPI DSI", value: serie.mipi_dsi, icon: "🖼️" }); if (serie.mipi_dsi) interfacesEspeciais.push({ label: "MIPI DSI", value: serie.mipi_dsi, icon: <Sliders className="w-5 h-5 text-cyan-500" /> });
if (serie.sdio) interfacesEspeciais.push({ label: "SDIO", value: serie.sdio, icon: "💳" }); if (serie.sdio) interfacesEspeciais.push({ label: "SDIO", value: serie.sdio, icon: <Sliders className="w-5 h-5 text-cyan-500" /> });
if (serie.sensor_hall) interfacesEspeciais.push({ label: "Sensor Hall", value: serie.sensor_hall, icon: "🧲" }); if (serie.sensor_hall) interfacesEspeciais.push({ label: "Sensor Hall", value: serie.sensor_hall, icon: <Sliders className="w-5 h-5 text-cyan-500" /> });
if (serie.sensor_temperatura) interfacesEspeciais.push({ label: "Sensor Temperatura", value: serie.sensor_temperatura, icon: "🌡️" }); if (serie.sensor_temperatura) interfacesEspeciais.push({ label: "Sensor Temperatura", value: serie.sensor_temperatura, icon: <Thermometer className="w-5 h-5 text-cyan-500" /> });
// Consumo de Energia // Consumo de Energia
const consumoEnergia = []; const consumoEnergia = [];
@@ -96,7 +131,7 @@ export default async function SerieDetail({ params }) {
consumoEnergia.push({ consumoEnergia.push({
label: modoLabels[modo] || modo.replace(/_/g, ' ').replace(/\b\w/g, l => l.toUpperCase()), label: modoLabels[modo] || modo.replace(/_/g, ' ').replace(/\b\w/g, l => l.toUpperCase()),
value: consumo, value: consumo,
icon: "🔋" icon: <Battery className="w-5 h-5 text-teal-500" />
}); });
}); });
} }
@@ -107,23 +142,23 @@ export default async function SerieDetail({ params }) {
label: 'Placa de Desenvolvimento', label: 'Placa de Desenvolvimento',
available: serie.placa, available: serie.placa,
content: ( content: (
<div className="bg-white rounded-3xl shadow-xl p-8 border-2 border-gray-100"> <div className="bg-white/80 dark:bg-slate-900/40 backdrop-blur-xl rounded-3xl p-6 md:p-8 border border-slate-200/60 dark:border-slate-800/80 shadow-2xl">
<h2 className="text-2xl font-bold text-gray-800 mb-6 pb-3 border-b-2 flex items-center gap-3" style={{ borderColor: serie.cor }}> <h2 className="text-xl font-bold text-slate-800 dark:text-slate-100 mb-6 pb-3 border-b border-slate-200/65 dark:border-slate-800/65 flex items-center gap-3">
<span>🔧</span> <Sliders className="w-5 h-5" style={{ color: serie.cor }} />
Placa de Desenvolvimento Oficial <span>Kit de Desenvolvimento Oficial</span>
</h2> </h2>
<div className="flex justify-center"> <div className="flex justify-center">
<div className="relative w-full max-w-4xl"> <div className="relative w-full max-w-3xl bg-white dark:bg-white overflow-hidden rounded-2xl border border-slate-200/50 dark:border-slate-850/50 shadow-lg p-4">
<Image <Image
src={serie.placa} src={serie.placa}
alt={`${key} DevKit - Placa de Desenvolvimento`} alt={`${key} DevKit - Placa de Desenvolvimento`}
width={1200} width={1200}
height={800} height={800}
className="rounded-xl shadow-lg w-full h-auto" className="rounded-xl object-contain w-full h-auto max-h-[450px]"
priority priority
/> />
<p className="text-center text-sm text-gray-600 mt-4"> <p className="text-center text-xs text-slate-500 dark:text-slate-400 mt-4 select-none">
{key} - Placa de desenvolvimento oficial da Espressif Placa de desenvolvimento padrão da Espressif Systems para a série {key}
</p> </p>
</div> </div>
</div> </div>
@@ -135,16 +170,14 @@ export default async function SerieDetail({ params }) {
label: 'Diagrama de Conexões', label: 'Diagrama de Conexões',
available: conexoes.length > 0, available: conexoes.length > 0,
content: ( content: (
<div className="bg-white rounded-3xl shadow-xl p-8 border-2 border-gray-100"> <div className="bg-white/80 dark:bg-slate-900/40 backdrop-blur-xl rounded-3xl p-6 md:p-8 border border-slate-200/60 dark:border-slate-800/80 shadow-2xl">
<div className="bg-amber-50 border-l-4 border-amber-500 p-4 mb-6 rounded-lg"> <div className="bg-amber-500/10 text-amber-600 dark:text-amber-400 border border-amber-500/20 p-5 mb-8 rounded-2xl">
<div className="flex items-start gap-3"> <div className="flex items-start gap-3">
<span className="text-2xl">⚠️</span> <AlertTriangle className="w-5 h-5 shrink-0 mt-0.5" />
<div> <div>
<h4 className="font-bold text-amber-900 mb-1">Diagrama do SoC (System on Chip)</h4> <h4 className="font-bold text-sm text-amber-850 dark:text-amber-300 mb-1 select-none">Diagrama do SoC (System on Chip)</h4>
<p className="text-amber-800 text-sm leading-relaxed"> <p className="text-xs text-amber-800 dark:text-amber-400 leading-relaxed">
Este diagrama mostra as conexões e periféricos do chip <strong>{key}</strong> (SoC), Este mapa refere-se exclusivamente às portas e conexões do silício bruto do microcontrolador <strong>{key}</strong>. As placas de desenvolvimento podem expor apenas uma parcela destas conexões, ou roteá-las de formas diferentes dependendo do circuito impresso.
não da placa de desenvolvimento completa. As placas de desenvolvimento podem ter
configurações diferentes de pinos expostos e componentes adicionais.
</p> </p>
</div> </div>
</div> </div>
@@ -152,20 +185,23 @@ export default async function SerieDetail({ params }) {
<ConnectionsDiagram connections={conexoes} serie={serie} /> <ConnectionsDiagram connections={conexoes} serie={serie} />
<div className="bg-white rounded-2xl shadow-lg p-6 border-2 border-gray-100 mt-6"> <div className="bg-slate-100/50 dark:bg-slate-950/40 rounded-2xl p-6 border border-slate-200/60 dark:border-slate-800/80 mt-8">
<h3 className="text-lg font-bold text-gray-800 mb-3 flex items-center gap-2"> <h3 className="text-sm font-bold text-slate-800 dark:text-slate-100 mb-3 flex items-center gap-2 select-none">
<svg className="w-5 h-5 text-blue-600" fill="currentColor" viewBox="0 0 20 20"> <Lightbulb className="w-4 h-4 text-purple-500" />
<path fillRule="evenodd" d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7-4a1 1 0 11-2 0 1 1 0 012 0zM9 9a1 1 0 000 2v3a1 1 0 001 1h1a1 1 0 100-2v-3a1 1 0 00-1-1H9z" clipRule="evenodd" /> <span>Flexibilidade das Portas GPIO</span>
</svg>
Sobre a Matriz de Conexões Flexível
</h3> </h3>
<p className="text-gray-700 leading-relaxed"> <p className="text-xs text-slate-500 dark:text-slate-400 leading-relaxed">
O ESP32 possui uma matriz de conexões flexível que permite mapear diversas funções para múltiplos pinos. O {key} integra uma matriz IO multiplexada e de baixa latência (IO MUX) que permite reconfigurar a grande maioria dos pinos digitais para assumir funções de SPI, I2C, UART, LEDC PWM ou barramentos de câmera e display.
Isso significa que muitas conexões podem ser configuradas para desempenhar diferentes papéis, dependendo das necessidades do seu projeto. Para mais detalhes sobre as restrições físicas de GPIOs strapping ou analógicas, consulte o&nbsp;
Consulte a documentação oficial para entender como aproveitar ao máximo as GPIOs&nbsp; <a
<a href={`https://docs.espressif.com/projects/esp-idf/en/latest/${String(key).toLowerCase().replace("-", "")}/api-reference/peripherals/gpio.html`} target="_blank" rel="noopener noreferrer" className="text-blue-600 hover:underline"> href={`https://docs.espressif.com/projects/esp-idf/en/latest/${String(key).toLowerCase().replace("-", "")}/api-reference/peripherals/gpio.html`}
nesse link. target="_blank"
</a> rel="noopener noreferrer"
className="text-purple-600 dark:text-purple-400 hover:underline inline-flex items-center gap-0.5"
>
<span>Guia Oficial de GPIO</span>
<ExternalLink className="w-2.5 h-2.5" />
</a>.
</p> </p>
</div> </div>
</div> </div>
@@ -174,205 +210,241 @@ export default async function SerieDetail({ params }) {
]; ];
return ( return (
<div className="bg-linear-to-br from-blue-50 via-white to-purple-50 min-h-screen"> <div className="bg-gradient-to-br from-slate-50 via-white to-purple-50/40 dark:from-slate-950 dark:via-slate-950 dark:to-slate-900 min-h-screen text-slate-900 dark:text-slate-100 transition-colors duration-300">
<Header /> <Header />
<main className="px-4 pt-16 pb-8 max-w-7xl mx-auto"> <main className="px-6 pt-16 pb-24 max-w-7xl mx-auto">
{/* Breadcrumb Navigation */}
<div className="mb-8"> <div className="mb-8">
<Link href="/series" className="inline-flex items-center text-gray-600 hover:text-purple-600 transition-colors mb-4"> <Link
<span className="mr-2">←</span> Voltar para Séries href="/series"
className="inline-flex items-center gap-2 text-xs font-bold text-slate-500 dark:text-slate-400 hover:text-purple-600 dark:hover:text-purple-400 transition-colors uppercase tracking-wider mb-6"
>
<ArrowLeft className="w-3.5 h-3.5" />
<span>Voltar para Séries</span>
</Link> </Link>
<div className="bg-white rounded-3xl shadow-xl p-8 border-2 border-gray-100" {/* Top Info Banner Card */}
style={{ borderColor: serie.cor + '30' }}> <div
<div className="flex items-start justify-between flex-wrap gap-4"> className="bg-white/80 dark:bg-slate-900/40 backdrop-blur-xl rounded-3xl p-6 md:p-8 shadow-2xl border relative overflow-hidden"
<div className="flex items-center gap-4"> style={{ borderColor: `${serie.cor}40` }}
<span className="text-6xl">{serie.icone}</span> >
<div className="flex flex-col lg:flex-row items-start lg:items-center justify-between gap-8">
<div className="flex items-center gap-5">
<span className="text-6xl select-none filter drop-shadow-md">{serie.icone}</span>
<div> <div>
<h1 className="text-4xl md:text-5xl font-extrabold text-gray-800 mb-2"> <h1 className="text-4xl md:text-5xl font-display font-extrabold text-slate-850 dark:text-slate-100 tracking-tight leading-none mb-3">
{key} {key}
</h1> </h1>
<p className="text-xl text-gray-600 mb-2">{serie.nome_completo}</p> <p className="text-sm font-semibold text-slate-400 dark:text-slate-500 mb-4">{serie.nome_completo}</p>
<span className="inline-block px-4 py-2 rounded-full text-sm font-semibold text-white" <span
style={{ backgroundColor: serie.cor }}> className="inline-block px-3 py-1 rounded-full text-[10px] font-bold uppercase tracking-widest"
style={{ backgroundColor: `${serie.cor}18`, color: serie.cor, border: `1px solid ${serie.cor}30` }}
>
{serie.arquitetura} {serie.arquitetura}
</span> </span>
</div> </div>
</div> </div>
<div className="grid gap-3 mx-auto md:mx-0"> {/* Links Block */}
<div className="grid sm:grid-cols-2 lg:grid-cols-1 gap-3 w-full lg:w-auto">
<a <a
href={serie.datasheet} href={serie.datasheet}
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"
className="inline-flex items-center gap-2 bg-blue-600 text-white px-6 py-3 rounded-xl font-semibold shadow-lg hover:shadow-xl transform hover:-translate-y-1 transition-all duration-300" className="inline-flex items-center justify-center gap-2 bg-slate-900 dark:bg-slate-100 text-white dark:text-slate-950 px-5 py-3 rounded-xl font-bold text-xs uppercase tracking-wider hover:opacity-90 transition-opacity shadow-lg"
> >
📄 Datasheet Oficial <FileText className="w-4 h-4" />
<span>Datasheet Oficial</span>
</a> </a>
<a <a
href={serie.guia_de_programacao} href={serie.guia_de_programacao}
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"
className="inline-flex items-center gap-2 bg-green-600 text-white px-6 py-3 rounded-xl font-semibold shadow-lg hover:shadow-xl transform hover:-translate-y-1 transition-all duration-300" className="inline-flex items-center justify-center gap-2 bg-emerald-500/10 text-emerald-600 dark:text-emerald-400 border border-emerald-500/20 px-5 py-3 rounded-xl font-bold text-xs uppercase tracking-wider hover:bg-emerald-500/20 transition-all shadow-xs"
> >
💻 Guia de Programação <Code className="w-4 h-4" />
<span>Guia de Programação</span>
</a> </a>
{serie.manual_tecnico && <a {serie.manual_tecnico && (
<a
href={serie.manual_tecnico} href={serie.manual_tecnico}
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"
className="inline-flex items-center gap-2 bg-purple-600 text-white px-6 py-3 rounded-xl font-semibold shadow-lg hover:shadow-xl transform hover:-translate-y-1 transition-all duration-300" className="inline-flex items-center justify-center gap-2 bg-purple-500/10 text-purple-600 dark:text-purple-400 border border-purple-500/20 px-5 py-3 rounded-xl font-bold text-xs uppercase tracking-wider hover:bg-purple-500/20 transition-all shadow-xs"
> >
📖 Manual Técnico <BookOpen className="w-4 h-4" />
</a>} <span>Manual Técnico</span>
</a>
)}
</div> </div>
</div> </div>
<p className="text-lg text-gray-700 mt-6 leading-relaxed"> <p className="text-sm text-slate-500 dark:text-slate-400 mt-6 leading-relaxed max-w-4xl">
{serie.descricao} {serie.descricao}
</p> </p>
</div> </div>
</div> </div>
{/* Primary Features Highlight Grid */}
<div className="grid md:grid-cols-3 gap-6 mb-8"> <div className="grid md:grid-cols-3 gap-6 mb-8">
<div className="bg-white rounded-2xl shadow-lg p-6 border-l-4" style={{ borderColor: serie.cor }}> <div
<h3 className="text-sm font-semibold text-gray-600 mb-2">PROCESSADOR</h3> className="bg-white/80 dark:bg-slate-900/40 backdrop-blur-xl rounded-2xl shadow-lg p-6 border-l-4"
<p className="text-3xl font-bold text-gray-800">{serie.nucleos.includes("2") ? "Dual-Core" : "Single-Core"}</p> style={{ borderColor: serie.cor }}
<p className="text-gray-600 mt-1">{serie.frequencia}</p> >
<h3 className="text-[10px] font-bold text-slate-400 dark:text-slate-500 uppercase tracking-widest mb-1.5 select-none">PROCESSADOR</h3>
<p className="text-2xl font-extrabold text-slate-800 dark:text-slate-100">{serie.nucleos.includes("2") ? "Dual-Core" : "Single-Core"}</p>
<p className="text-xs text-slate-400 mt-1">{serie.frequencia}</p>
</div> </div>
<div className="bg-white rounded-2xl shadow-lg p-6 border-l-4" style={{ borderColor: serie.cor }}> <div
<h3 className="text-sm font-semibold text-gray-600 mb-2">MEMÓRIA SRAM</h3> className="bg-white/80 dark:bg-slate-900/40 backdrop-blur-xl rounded-2xl shadow-lg p-6 border-l-4"
<p className="text-3xl font-bold text-gray-800">{serie.memoria_sram}</p> style={{ borderColor: serie.cor }}
<p className="text-gray-600 mt-1">+ {serie.memoria_sram_rtc} RTC</p> >
<h3 className="text-[10px] font-bold text-slate-400 dark:text-slate-500 uppercase tracking-widest mb-1.5 select-none">MEMÓRIA SRAM</h3>
<p className="text-2xl font-extrabold text-slate-800 dark:text-slate-100">{serie.memoria_sram}</p>
<p className="text-xs text-slate-400 mt-1">+ {serie.memoria_sram_rtc} RTC SRAM</p>
</div> </div>
<div className="bg-white rounded-2xl shadow-lg p-6 border-l-4" style={{ borderColor: serie.cor }}> <div
<h3 className="text-sm font-semibold text-gray-600 mb-2">GPIO</h3> className="bg-white/80 dark:bg-slate-900/40 backdrop-blur-xl rounded-2xl shadow-lg p-6 border-l-4"
<p className="text-3xl font-bold text-gray-800">{serie.gpio}</p> style={{ borderColor: serie.cor }}
<p className="text-gray-600 mt-1">Pinos disponíveis</p> >
<h3 className="text-[10px] font-bold text-slate-400 dark:text-slate-500 uppercase tracking-widest mb-1.5 select-none">GPIO DISPONÍVEIS</h3>
<p className="text-2xl font-extrabold text-slate-800 dark:text-slate-100">{serie.gpio}</p>
<p className="text-xs text-slate-400 mt-1">Pinos I/O programáveis</p>
</div> </div>
</div> </div>
{/* DevBoard and Connections Tabs */}
<SeriesTabMenu tabs={tabs} color={serie.cor} /> <SeriesTabMenu tabs={tabs} color={serie.cor} />
<div className="space-y-6"> {/* Specs List Cards */}
<SpecSection title="⚙️ Processador" specs={processadorSpecs} cor={serie.cor} /> <div className="space-y-6 mt-12">
<SpecSection title="📡 Conectividade" specs={conectividadeSpecs} cor={serie.cor} /> <SpecSection title="Processador" specs={processadorSpecs} cor={serie.cor} icon={<Cpu className="w-5 h-5" style={{ color: serie.cor }} />} />
<SpecSection title="💾 Memória" specs={memoriaSpecs} cor={serie.cor} /> <SpecSection title="Conectividade RF" specs={conectividadeSpecs} cor={serie.cor} icon={<Wifi className="w-5 h-5" style={{ color: serie.cor }} />} />
<SpecSection title="Estrutura de Memória" specs={memoriaSpecs} cor={serie.cor} icon={<Database className="w-5 h-5" style={{ color: serie.cor }} />} />
{/* Power Consumption section */}
{consumoEnergia.length > 0 && ( {consumoEnergia.length > 0 && (
<div className="bg-white rounded-2xl shadow-lg p-6 border-2 border-gray-100"> <div className="bg-white/80 dark:bg-slate-900/40 backdrop-blur-xl rounded-2xl p-6 md:p-8 border border-slate-200/60 dark:border-slate-800/80 shadow-2xl">
<h2 className="text-2xl font-bold text-gray-800 mb-4 pb-3 border-b-2 flex items-center gap-3" style={{ borderColor: serie.cor }}> <h2 className="text-xl font-bold text-slate-800 dark:text-slate-100 mb-6 pb-3 border-b border-slate-200/65 dark:border-slate-800/65 flex items-center gap-3 select-none">
<span>🔋</span> <Battery className="w-5 h-5" style={{ color: serie.cor }} />
Consumo de Energia <span>Consumo de Energia</span>
</h2> </h2>
<div className="bg-blue-50 border-l-4 border-blue-500 p-4 mb-6 rounded-lg">
<div className="flex items-start gap-3"> <div className="bg-blue-500/10 border border-blue-500/20 p-4 mb-6 rounded-xl">
<span className="text-2xl">💡</span> <div className="flex items-start gap-3 text-blue-650 dark:text-blue-400">
<div> <Lightbulb className="w-5 h-5 shrink-0 mt-0.5" />
<p className="text-blue-900 text-sm leading-relaxed"> <p className="text-xs leading-relaxed">
<strong>Valores típicos @3.3V, 25°C.</strong> O consumo real varia com clock, estado de RF (Wi-Fi/Bluetooth ligado/desligado) e configurações. <strong>Especificações típicas medidas sob 3.3V com 25°C de temperatura ambiente.</strong> O consumo absoluto depende ativamente do ciclo de trabalho (duty cycle) das interfaces Wi-Fi/Bluetooth e da frequência de clock configurada via software.
Para precisão, consulte o datasheet oficial.
</p> </p>
</div> </div>
</div> </div>
</div>
<div className="grid md:grid-cols-2 lg:grid-cols-3 gap-4"> <div className="grid md:grid-cols-2 lg:grid-cols-3 gap-4">
{consumoEnergia.map((spec, index) => ( {consumoEnergia.map((spec, index) => (
<div key={index} className="flex items-start gap-3 p-3 rounded-lg hover:bg-gray-50 transition-colors"> <div key={index} className="flex items-center gap-3.5 p-3.5 bg-slate-50/50 dark:bg-slate-950/20 rounded-xl hover:bg-purple-500/5 dark:hover:bg-purple-400/5 border border-slate-150/40 dark:border-slate-850/40 transition-colors">
<span className="text-2xl">{spec.icon}</span> <div className="p-2 bg-white dark:bg-slate-900 rounded-lg shadow-xs border border-slate-200/30 dark:border-slate-850/30 shrink-0">
<div className="flex-1"> {spec.icon}
<p className="text-sm font-semibold text-gray-600">{spec.label}</p> </div>
<p className="text-base font-bold text-gray-800 mt-1">{spec.value}</p> <div className="min-w-0">
<p className="text-[10px] text-slate-400 dark:text-slate-500 font-bold uppercase tracking-wider">{spec.label}</p>
<p className="text-sm font-extrabold text-slate-800 dark:text-slate-200 mt-0.5">{spec.value}</p>
</div> </div>
</div> </div>
))} ))}
</div> </div>
</div> </div>
)} )}
<SpecSection title="🔌 Periféricos" specs={perifericos} cor={serie.cor} />
<SpecSection title="Periféricos Disponíveis" specs={perifericos} cor={serie.cor} icon={<Sliders className="w-5 h-5" style={{ color: serie.cor }} />} />
{interfacesEspeciais.length > 0 && ( {interfacesEspeciais.length > 0 && (
<SpecSection title="✨ Interfaces Especiais" specs={interfacesEspeciais} cor={serie.cor} /> <SpecSection title="Interfaces de Hardware Especiais" specs={interfacesEspeciais} cor={serie.cor} icon={<Sparkles className="w-5 h-5" style={{ color: serie.cor }} />} />
)} )}
<SpecSection <SpecSection
title="🌡️ Temperatura" title="Limites Operacionais de Temperatura"
icon={<Thermometer className="w-5 h-5" style={{ color: serie.cor }} />}
specs={[ specs={[
{ label: "Operação", value: serie.temperatura_operacao, icon: "🔧" }, { label: "Operação", value: serie.temperatura_operacao, icon: <Thermometer className="w-5 h-5 text-red-500" /> },
{ label: "Armazenamento", value: serie.temperatura_armazenamento, icon: "📦" }, { label: "Armazenamento", value: serie.temperatura_armazenamento, icon: <Folder className="w-5 h-5 text-red-500" /> },
]} ]}
cor={serie.cor} cor={serie.cor}
/> />
</div> </div>
{/* Call-to-Action para Catálogo de Placas */} {/* DevBoard CTA Panel */}
<div className="my-16 bg-linear-to-br from-blue-600 via-purple-600 to-pink-600 rounded-3xl shadow-2xl p-10 text-white border-2 border-purple-400"> <div className="my-20 bg-gradient-to-br from-slate-900 to-indigo-950 dark:from-slate-950 dark:to-purple-950/20 border border-slate-800/80 rounded-3xl p-8 md:p-12 shadow-2xl relative overflow-hidden">
<div className="flex flex-col md:flex-row items-center justify-between gap-8"> <div className="absolute right-0 bottom-0 w-96 h-96 bg-purple-500/5 rounded-full blur-3xl pointer-events-none" />
<div className="flex-1">
<div className="inline-flex items-center gap-2 bg-white/20 backdrop-blur-sm px-4 py-2 rounded-full mb-4 border border-white/30"> <div className="flex flex-col md:flex-row items-center justify-between gap-8 relative z-10">
<span className="text-xl">🎯</span> <div className="flex-1 max-w-2xl text-center md:text-left">
<span className="text-sm font-semibold">Próximo Passo</span> <div className="inline-flex items-center gap-2 bg-white/10 text-slate-350 px-3 py-1 rounded-full mb-4 text-xs font-semibold select-none">
<Home className="w-3.5 h-3.5" />
<span>Hardware Fisico</span>
</div> </div>
<h3 className="text-3xl md:text-4xl font-black mb-4"> <h3 className="text-2xl md:text-3xl font-display font-extrabold text-white mb-4">
Pronto para começar? Pronto para começar a prototipar?
</h3> </h3>
<p className="text-lg text-white/90 leading-relaxed"> <p className="text-sm text-slate-400 leading-relaxed">
Encontre placas de desenvolvimento {key} verificadas no nosso catálogo. Explore toda a nossa grade de placas de desenvolvimento de parceiros oficiais para a série {key}. Compare dimensões, conversores USB-serial e pinagens prontas para protoboards.
Links diretos para lojas oficiais e confiáveis com os melhores preços!
</p> </p>
</div> </div>
<Link <Link
href="/catalogo" href="/catalogo"
className="inline-flex items-center justify-center gap-2 bg-white text-purple-700 px-8 py-4 rounded-xl font-bold text-lg shadow-xl hover:shadow-2xl transform hover:-translate-y-1 transition-all duration-300 whitespace-nowrap group" className="inline-flex items-center justify-center gap-2 bg-white text-slate-900 px-6 py-3.5 rounded-xl font-bold text-sm hover:bg-slate-100 transition-all duration-300 shrink-0 shadow-md active:scale-95"
> >
<span className="text-xl">🛒</span>
<span>Ver Placas Disponíveis</span> <span>Ver Placas Disponíveis</span>
<svg <ArrowRight className="w-4 h-4" />
className="w-5 h-5 transition-transform group-hover:translate-x-1"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 5l7 7-7 7" />
</svg>
</Link> </Link>
</div> </div>
</div> </div>
<div className="grid sm:flex sm:justify-between items-center gap-4 mt-12 md:mx-8"> {/* Previous / Next Nav bars */}
<div className="grid sm:flex sm:justify-between items-center gap-4 mt-12 px-2 select-none">
{previousKey ? ( {previousKey ? (
<Link <Link
href={`/series/${previousKey}`} href={`/series/${previousKey}`}
className="flex items-center gap-2 bg-white px-6 py-3 rounded-xl shadow-lg hover:shadow-xl transform hover:-translate-x-1 transition-all duration-300 border-2 border-gray-100 hover:border-purple-300" className="flex items-center gap-3 bg-white/70 dark:bg-slate-900/40 backdrop-blur-xl px-5 py-3.5 rounded-2xl shadow-lg border border-slate-200/60 dark:border-slate-800/80 hover:border-purple-500/55 dark:hover:border-purple-400/55 transition-all group shrink-0"
> >
<span>←</span> <ArrowLeft className="w-4 h-4 text-slate-400 group-hover:-translate-x-1.5 transition-transform" />
<div className="text-left"> <div className="text-left">
<p className="text-xs text-gray-600">Anterior</p> <p className="text-[10px] text-slate-400 dark:text-slate-500 font-bold uppercase tracking-wider">Anterior</p>
<p className="font-semibold text-gray-800">{previousKey}</p> <p className="text-sm font-extrabold text-slate-800 dark:text-slate-200">{previousKey}</p>
</div> </div>
</Link> </Link>
) : null} ) : (
<div className="hidden sm:block w-36" />
)}
<Link <Link
href="/comparacao" href="/comparacao"
className="bg-linear-to-r from-blue-600 to-purple-600 text-white px-6 py-3 rounded-xl font-semibold shadow-lg hover:shadow-xl transform hover:-translate-y-1 transition-all duration-300" className="inline-flex items-center justify-center gap-2 bg-slate-900 dark:bg-slate-100 text-white dark:text-slate-950 px-6 py-3.5 rounded-xl font-bold text-xs uppercase tracking-wider hover:opacity-90 transition-opacity shadow-md"
> >
📊 Comparar Séries <Scale className="w-4 h-4" />
<span>Comparar Séries</span>
</Link> </Link>
{nextKey ? ( {nextKey ? (
<Link <Link
href={`/series/${nextKey}`} href={`/series/${nextKey}`}
className="flex items-center gap-2 bg-white px-6 py-3 rounded-xl shadow-lg hover:shadow-xl transform hover:translate-x-1 transition-all duration-300 border-2 border-gray-100 hover:border-purple-300" className="flex items-center gap-3 bg-white/70 dark:bg-slate-900/40 backdrop-blur-xl px-5 py-3.5 rounded-2xl shadow-lg border border-slate-200/60 dark:border-slate-800/80 hover:border-purple-500/55 dark:hover:border-purple-400/55 transition-all group shrink-0"
> >
<div className="text-right"> <div className="text-right">
<p className="text-xs text-gray-600">Próximo</p> <p className="text-[10px] text-slate-400 dark:text-slate-500 font-bold uppercase tracking-wider">Próximo</p>
<p className="font-semibold text-gray-800">{nextKey}</p> <p className="text-sm font-extrabold text-slate-800 dark:text-slate-200">{nextKey}</p>
</div> </div>
<span>→</span> <ArrowRight className="w-4 h-4 text-slate-400 group-hover:translate-x-1.5 transition-transform" />
</Link> </Link>
) : null} ) : (
<div className="hidden sm:block w-36" />
)}
</div> </div>
</main> </main>
@@ -381,19 +453,22 @@ export default async function SerieDetail({ params }) {
); );
} }
function SpecSection({ title, specs, cor }) { function SpecSection({ title, specs, cor, icon }) {
return ( return (
<div className="bg-white rounded-2xl shadow-lg p-6 border-2 border-gray-100"> <div className="bg-white/80 dark:bg-slate-900/40 backdrop-blur-xl rounded-2xl p-6 md:p-8 border border-slate-200/60 dark:border-slate-800/80 shadow-2xl">
<h2 className="text-2xl font-bold text-gray-800 mb-4 pb-3 border-b-2" style={{ borderColor: cor }}> <h2 className="text-xl font-bold text-slate-800 dark:text-slate-100 mb-6 pb-3 border-b border-slate-200/65 dark:border-slate-800/65 flex items-center gap-3 select-none">
{title} {icon}
<span>{title}</span>
</h2> </h2>
<div className="grid md:grid-cols-2 lg:grid-cols-3 gap-4"> <div className="grid md:grid-cols-2 lg:grid-cols-3 gap-4">
{specs.map((spec, index) => ( {specs.map((spec, index) => (
<div key={index} className="flex items-start gap-3 p-3 rounded-lg hover:bg-gray-50 transition-colors"> <div key={index} className="flex items-center gap-3.5 p-3.5 bg-slate-50/50 dark:bg-slate-950/20 rounded-xl hover:bg-purple-500/5 dark:hover:bg-purple-400/5 border border-slate-150/40 dark:border-slate-850/40 transition-colors">
<span className="text-2xl">{spec.icon}</span> <div className="p-2 bg-white dark:bg-slate-900 rounded-lg shadow-xs border border-slate-200/30 dark:border-slate-850/30 shrink-0">
<div className="flex-1"> {spec.icon}
<p className="text-sm font-semibold text-gray-600">{spec.label}</p> </div>
<p className="text-base font-bold text-gray-800 mt-1">{spec.value}</p> <div className="min-w-0">
<p className="text-[10px] text-slate-400 dark:text-slate-500 font-bold uppercase tracking-wider">{spec.label}</p>
<p className="text-sm font-extrabold text-slate-800 dark:text-slate-200 mt-0.5">{spec.value}</p>
</div> </div>
</div> </div>
))} ))}
+143 -80
View File
@@ -1,153 +1,216 @@
"use client";
import Header from "@/components/Header"; import Header from "@/components/Header";
import Footer from "@/components/Footer"; import Footer from "@/components/Footer";
import Link from "next/link"; import Link from "next/link";
import seriesData from "@/public/series.json"; import seriesData from "@/public/series.json";
import {
Cpu,
Activity,
Database,
Wifi,
Bluetooth,
Battery,
Sparkles,
Home,
ArrowRight,
Scale,
Compass
} from "lucide-react";
export default function Series() { export default function Series() {
const series = Object.entries(seriesData); const series = Object.entries(seriesData);
return ( return (
<div className="bg-linear-to-br from-blue-50 via-white to-purple-50 min-h-screen"> <div className="bg-gradient-to-br from-slate-50 via-white to-purple-50/40 dark:from-slate-950 dark:via-slate-950 dark:to-slate-900 min-h-screen text-slate-900 dark:text-slate-100 transition-colors duration-300">
<Header /> <Header />
<main id="conteudo" className="px-4 pt-16 pb-20 max-w-7xl mx-auto"> <main id="conteudo" className="px-6 pt-16 pb-24 max-w-7xl mx-auto">
<section className="text-center mb-16">
<div className="inline-flex items-center gap-2 bg-white/70 backdrop-blur-sm px-4 py-2 rounded-full mb-6 border border-purple-200 shadow-sm"> {/* Top Section */}
<span className="text-xl">⚡</span> <section className="text-center mb-20">
<span className="text-sm font-semibold text-gray-700">Explore o Ecossistema ESP32</span> <div className="inline-flex items-center gap-2 bg-purple-500/10 dark:bg-purple-400/5 px-4 py-2 rounded-full mb-6 border border-purple-500/20 shadow-xs select-none">
<Compass className="w-4 h-4 text-purple-600 dark:text-purple-400" />
<span className="text-xs font-bold text-purple-600 dark:text-purple-400 uppercase tracking-widest">Explore o Ecossistema ESP32</span>
</div> </div>
<h1 className="text-5xl md:text-7xl font-black bg-linear-to-r from-blue-600 via-purple-600 to-pink-600 bg-clip-text text-transparent mb-6 leading-tight tracking-tight"> <h1 className="text-5xl md:text-7xl font-display font-extrabold bg-gradient-to-r from-blue-600 via-purple-600 to-pink-600 dark:from-blue-400 dark:via-purple-400 dark:to-pink-400 bg-clip-text text-transparent mb-6 tracking-tight leading-tight">
Séries ESP32 Séries ESP32
</h1> </h1>
<p className="text-lg md:text-xl text-gray-600 max-w-3xl mx-auto leading-relaxed"> <p className="text-base md:text-lg text-slate-500 dark:text-slate-400 max-w-3xl mx-auto leading-relaxed">
Explore toda a família ESP32. Cada série foi projetada para atender necessidades específicas, Conheça toda a família de microcontroladores da Espressif. Cada série foi projetada com foco em engenharia especializada, desde nós de sensores autônomos de baixo consumo até poderosos aceleradores de inteligência artificial.
desde projetos simples até aplicações de IA e IoT avançadas.
</p> </p>
</section> </section>
<div className="grid md:grid-cols-2 lg:grid-cols-3 gap-6"> {/* Series Grid */}
{series.map(([key, serie]) => ( <div className="grid md:grid-cols-2 lg:grid-cols-3 gap-8">
{series.map(([key, serie]) => {
const isRiscV = serie.arquitetura.includes("RISC-V");
return (
<Link <Link
key={key} key={key}
href={`/series/${key}`} href={`/series/${key}`}
className="group" className="group block"
> >
<div className="bg-white rounded-2xl shadow-lg hover:shadow-2xl transform hover:-translate-y-2 transition-all duration-300 border-2 border-gray-100 hover:border-opacity-50 h-full" {/* Glassmorphic card container with dynamic color borders and shadows */}
style={{ '--hover-color': serie.cor }}> <div
className="bg-white/80 dark:bg-slate-900/40 backdrop-blur-xl rounded-3xl p-6 md:p-8 border border-slate-200/60 dark:border-slate-800/80 hover:shadow-2xl transition-all duration-500 h-full flex flex-col justify-between"
<div className="p-6 border-b border-gray-100"> style={{
<div className="flex items-center justify-between mb-3"> borderColor: 'rgba(148, 163, 184, 0.1)',
<span className="text-4xl">{serie.icone}</span> transition: 'border-color 0.3s, box-shadow 0.3s, transform 0.3s'
<span className="px-3 py-1 rounded-full text-xs font-semibold text-white" }}
style={{ backgroundColor: serie.cor }}> onMouseEnter={(e) => {
{serie.arquitetura.includes("RISC-V") ? "RISC-V" : "Xtensa"} e.currentTarget.style.borderColor = serie.cor;
e.currentTarget.style.boxShadow = `0 15px 35px -12px ${serie.cor}35`;
e.currentTarget.style.transform = 'translateY(-6px)';
}}
onMouseLeave={(e) => {
e.currentTarget.style.borderColor = '';
e.currentTarget.style.boxShadow = '';
e.currentTarget.style.transform = '';
}}
>
<div>
{/* Header block with Icon & Architecture */}
<div className="flex items-center justify-between mb-6">
<span className="text-4xl select-none filter drop-shadow-md transform group-hover:scale-110 transition-transform duration-300">{serie.icone}</span>
<span
className={`px-3 py-1 rounded-full text-[10px] font-bold tracking-widest uppercase select-none`}
style={{ backgroundColor: `${serie.cor}18`, color: serie.cor, border: `1px solid ${serie.cor}30` }}
>
{isRiscV ? "RISC-V" : "Xtensa"}
</span> </span>
</div> </div>
<h2 className="text-2xl font-bold text-gray-800 mb-2 group-hover:text-purple-600 transition-colors"> {/* Title and description */}
<h2 className="text-2xl font-display font-extrabold text-slate-850 dark:text-slate-100 mb-3 group-hover:text-purple-600 dark:group-hover:text-purple-400 transition-colors">
{key} {key}
</h2> </h2>
<p className="text-sm text-gray-600 line-clamp-2"> <p className="text-xs text-slate-450 dark:text-slate-500 line-clamp-2 leading-relaxed mb-6">
{serie.descricao} {serie.descricao}
</p> </p>
{/* Detailed specs micro-grid */}
<div className="space-y-3.5 border-t border-slate-100 dark:border-slate-800/60 pt-6">
<div className="flex items-center justify-between text-xs">
<div className="flex items-center gap-2 text-slate-400 dark:text-slate-500 font-semibold select-none">
<Cpu className="w-3.5 h-3.5 text-blue-500/80" />
<span>Núcleos:</span>
</div>
<span className="font-bold text-slate-800 dark:text-slate-200">{serie.nucleos}</span>
</div> </div>
<div className="p-6 space-y-3"> <div className="flex items-center justify-between text-xs">
<div className="flex items-center justify-between text-sm"> <div className="flex items-center gap-2 text-slate-400 dark:text-slate-500 font-semibold select-none">
<span className="text-gray-600">⚙️ Núcleos:</span> <Activity className="w-3.5 h-3.5 text-amber-500/80" />
<span className="font-semibold text-gray-800">{serie.nucleos}</span> <span>Frequência:</span>
</div>
<span className="font-bold text-slate-800 dark:text-slate-200">{serie.frequencia}</span>
</div> </div>
<div className="flex items-center justify-between text-sm"> <div className="flex items-center justify-between text-xs">
<span className="text-gray-600">🚀 Frequência:</span> <div className="flex items-center gap-2 text-slate-400 dark:text-slate-500 font-semibold select-none">
<span className="font-semibold text-gray-800">{serie.frequencia}</span> <Database className="w-3.5 h-3.5 text-pink-500/80" />
<span>SRAM:</span>
</div>
<span className="font-bold text-slate-800 dark:text-slate-200">{serie.memoria_sram}</span>
</div> </div>
<div className="flex items-center justify-between text-sm"> <div className="flex items-center justify-between text-xs">
<span className="text-gray-600">💾 SRAM:</span> <div className="flex items-center gap-2 text-slate-400 dark:text-slate-500 font-semibold select-none">
<span className="font-semibold text-gray-800">{serie.memoria_sram}</span> <Wifi className="w-3.5 h-3.5 text-emerald-500/80" />
<span>Wi-Fi:</span>
</div> </div>
<span className="font-bold text-slate-800 dark:text-slate-200">
<div className="flex items-center justify-between text-sm"> {String(serie.wifi).includes("Não") ? (
<span className="text-gray-600">📶 Wi-Fi:</span> <span className="text-slate-350 dark:text-slate-650">—</span>
<span className="font-semibold text-gray-800"> ) : (
{String(serie.wifi).includes("Não") ? "❌" : "✅"} <span className="text-emerald-500 dark:text-emerald-400 font-extrabold">Sim</span>
)}
</span> </span>
</div> </div>
<div className="flex items-center justify-between text-sm"> <div className="flex items-center justify-between text-xs">
<span className="text-gray-600">📱 Bluetooth:</span> <div className="flex items-center gap-2 text-slate-400 dark:text-slate-500 font-semibold select-none">
<span className="font-semibold text-gray-800"> <Bluetooth className="w-3.5 h-3.5 text-indigo-500/80" />
{serie.bluetooth === "Não" ? "❌" : "✅"} <span>Bluetooth:</span>
</div>
<span className="font-bold text-slate-800 dark:text-slate-200">
{serie.bluetooth === "Não" ? (
<span className="text-slate-350 dark:text-slate-650">—</span>
) : (
<span className="text-indigo-500 dark:text-indigo-400 font-extrabold">Sim</span>
)}
</span> </span>
</div> </div>
{serie.consumo_energia?.deep_sleep && ( {serie.consumo_energia?.deep_sleep && (
<div className="flex items-center justify-between text-sm"> <div className="flex items-center justify-between text-xs">
<span className="text-gray-600">🔋 Deep Sleep:</span> <div className="flex items-center gap-2 text-slate-400 dark:text-slate-500 font-semibold select-none">
<span className="font-semibold text-gray-800">{serie.consumo_energia.deep_sleep}</span> <Battery className="w-3.5 h-3.5 text-green-500/80" />
<span>Deep Sleep:</span>
</div>
<span className="font-bold text-slate-800 dark:text-slate-200">{serie.consumo_energia.deep_sleep}</span>
</div> </div>
)} )}
{serie.aceleradores_ia && (
<div className="pt-2 border-t border-gray-100">
<span className="inline-block px-2 py-1 bg-purple-100 text-purple-700 text-xs font-semibold rounded">
🤖 Aceleradores IA
</span>
</div> </div>
</div>
{/* IA / Matter Badges & Action Footer */}
<div className="mt-8">
<div className="flex flex-wrap gap-2 mb-6">
{serie.aceleradores_ia && (
<span className="inline-flex items-center gap-1 px-2.5 py-1 bg-purple-500/10 text-purple-600 dark:text-purple-400 text-[10px] font-bold uppercase rounded-lg border border-purple-500/20 shadow-xs select-none">
<Sparkles className="w-3 h-3" />
<span>Aceleradores IA</span>
</span>
)} )}
{serie.matter === "Sim" && ( {serie.matter === "Sim" && (
<div className="pt-2 border-t border-gray-100"> <span className="inline-flex items-center gap-1 px-2.5 py-1 bg-emerald-500/10 text-emerald-600 dark:text-emerald-400 text-[10px] font-bold uppercase rounded-lg border border-emerald-500/20 shadow-xs select-none">
<span className="inline-block px-2 py-1 bg-green-100 text-green-700 text-xs font-semibold rounded"> <Home className="w-3 h-3" />
🏠 Suporte Matter <span>Suporte Matter</span>
</span> </span>
</div>
)} )}
</div> </div>
<div className="px-6 pb-6"> <div className="flex items-center justify-between text-xs font-bold text-slate-500 group-hover:text-purple-600 dark:group-hover:text-purple-400 transition-colors uppercase tracking-wider select-none border-t border-slate-100 dark:border-slate-800/60 pt-4">
<div className="flex items-center justify-between text-sm font-semibold group-hover:text-purple-600 transition-colors">
<span>Ver Detalhes</span> <span>Ver Detalhes</span>
<span className="transform group-hover:translate-x-2 transition-transform">→</span> <ArrowRight className="w-4 h-4 transform group-hover:translate-x-2 transition-transform duration-300" />
</div> </div>
</div> </div>
</div> </div>
</Link> </Link>
))} );
})}
</div> </div>
<div className="mt-16 bg-linear-to-br from-white to-purple-50 rounded-3xl shadow-2xl p-10 border-2 border-purple-100"> {/* Quick comparison CTA Box */}
<div className="text-center mb-8"> <div className="mt-20 bg-gradient-to-br from-slate-900 to-indigo-950 dark:from-slate-950 dark:to-purple-950/20 rounded-3xl p-8 md:p-12 shadow-2xl relative overflow-hidden border border-slate-800/80">
<div className="inline-flex items-center justify-center w-16 h-16 bg-linear-to-br from-blue-500 to-purple-600 rounded-2xl mb-4"> <div className="absolute right-0 bottom-0 w-96 h-96 bg-purple-500/5 rounded-full blur-3xl pointer-events-none" />
<span className="text-3xl">💡</span>
<div className="flex flex-col md:flex-row items-center justify-between gap-8 relative z-10">
<div className="flex-1 max-w-2xl text-center md:text-left">
<div className="inline-flex items-center gap-2 bg-white/10 text-slate-350 px-3 py-1 rounded-full mb-4 text-xs font-semibold select-none animate-pulse">
<Scale className="w-3.5 h-3.5" />
<span>Decisão Técnica</span>
</div> </div>
<h3 className="text-3xl font-bold text-gray-900 mb-3"> <h3 className="text-2xl md:text-3xl font-display font-extrabold text-white mb-4">
Não sabe qual escolher? Ficou em dúvida de qual escolher?
</h3> </h3>
<p className="text-gray-600 max-w-2xl mx-auto leading-relaxed text-base"> <p className="text-sm text-slate-400 leading-relaxed">
Compare as especificações de cada série para encontrar a mais adequada ao seu projeto. Use nosso comparador técnico dinâmico para posicionar as especificações de conectividade RF, potência de rádio e suporte a barramentos de periféricos lado a lado.
Considere requisitos de conectividade, processamento e recursos específicos.
</p> </p>
</div> </div>
<div className="flex justify-center">
<Link <Link
href="/comparacao" href="/comparacao"
className="inline-flex items-center justify-center gap-2 bg-linear-to-r from-blue-600 via-purple-600 to-pink-600 text-white px-8 py-4 rounded-xl font-bold shadow-lg hover:shadow-xl transform hover:-translate-y-1 transition-all duration-300 group" className="inline-flex items-center justify-center gap-2 bg-white text-slate-900 px-6 py-3.5 rounded-xl font-bold text-sm hover:bg-slate-100 transition-all duration-300 shrink-0 shadow-md active:scale-95"
> >
<span className="text-xl">📊</span>
<span>Comparar Séries</span> <span>Comparar Séries</span>
<svg <ArrowRight className="w-4 h-4" />
className="w-5 h-5 transition-transform group-hover:translate-x-1"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 5l7 7-7 7" />
</svg>
</Link> </Link>
</div> </div>
</div> </div>
+133 -98
View File
@@ -1,54 +1,69 @@
import Header from "@/components/Header"; import Header from "@/components/Header";
import Footer from "@/components/Footer"; import Footer from "@/components/Footer";
import Link from "next/link"; import Link from "next/link";
import {
Languages,
GitCompare,
Globe,
Code,
Target,
HelpCircle,
Users,
Accessibility,
ArrowLeft,
Award,
BookOpen,
Compass,
Terminal,
Layers
} from "lucide-react";
export default function Sobre() { export default function Sobre() {
const features = [ const features = [
{ icon: "📚", title: "Interface em Português", desc: "Organização e apresentação de dados técnicos totalmente em português." }, { icon: <Languages className="w-6 h-6 text-purple-500" />, title: "Interface em Português", desc: "Organização e apresentação de dados técnicos totalmente em português." },
{ icon: "🔧", title: "Comparações Visuais", desc: "Tabelas comparativas e diagramas para facilitar a escolha da série ideal." }, { icon: <GitCompare className="w-6 h-6 text-blue-500" />, title: "Comparações Visuais", desc: "Tabelas comparativas e diagramas para facilitar a escolha da série ideal." },
{ icon: "🌐", title: "Acesso Centralizado", desc: "Links diretos para documentação oficial, repositórios e exemplos." }, { icon: <Globe className="w-6 h-6 text-emerald-500" />, title: "Acesso Centralizado", desc: "Links diretos para documentação oficial, repositórios e exemplos." },
{ icon: "♿", title: "Código Aberto", desc: "Projeto open-source com contribuições bem-vindas no GitHub." }, { icon: <Code className="w-6 h-6 text-amber-500" />, title: "Código Aberto", desc: "Projeto open-source com contribuições bem-vindas no GitHub." },
]; ];
return ( return (
<div className="bg-linear-to-br from-blue-50 via-white to-purple-50 min-h-screen text-gray-800"> <div className="bg-gradient-to-br from-slate-50 via-white to-purple-50/40 dark:from-slate-950 dark:via-slate-950 dark:to-slate-900 min-h-screen text-slate-900 dark:text-slate-100 transition-colors duration-300">
<a href="#conteudo" className="sr-only focus:not-sr-only focus:absolute focus:top-4 focus:left-4 focus:bg-white focus:px-4 focus:py-2 focus:rounded-md focus:shadow focus:ring-2 focus:ring-purple-500"> <a href="#conteudo" className="sr-only focus:not-sr-only focus:absolute focus:top-4 focus:left-4 focus:bg-white focus:px-4 focus:py-2 focus:rounded-md focus:shadow focus:ring-2 focus:ring-purple-500">
Ir para o conteúdo Ir para o conteúdo
</a> </a>
<Header /> <Header />
<main id="conteudo" className="px-4 pt-12 pb-20 max-w-7xl mx-auto"> <main id="conteudo" className="px-6 pt-16 pb-24 max-w-7xl mx-auto">
<header className="relative overflow-hidden rounded-3xl p-10 md:p-16 bg-linear-to-br from-blue-600 via-purple-600 to-pink-600 text-white shadow-2xl mb-12"> {/* Top Hero Heading Block */}
<header className="relative overflow-hidden rounded-3xl p-10 md:p-16 bg-gradient-to-br from-blue-600 via-purple-600 to-pink-600 text-white shadow-2xl mb-12 select-none">
<div className="absolute inset-0 bg-black/10"></div> <div className="absolute inset-0 bg-black/10"></div>
<div className="relative z-10 max-w-4xl mx-auto"> <div className="relative z-10 max-w-4xl mx-auto">
<h1 className="text-4xl md:text-6xl font-black leading-tight mb-6"> <h1 className="text-4xl md:text-6xl font-display font-extrabold leading-tight mb-6">
Sobre o ESPDocs Sobre o ESPDocs
</h1> </h1>
<p className="text-lg md:text-xl opacity-95 max-w-3xl mb-8 leading-relaxed"> <p className="text-sm md:text-base opacity-95 max-w-3xl mb-8 leading-relaxed font-semibold">
Uma plataforma não oficial com interface em português que organiza Uma plataforma não oficial com interface em português que organiza
informações técnicas do ESP32 e facilita o acesso à documentação oficial informações técnicas do ESP32 e facilita o acesso à documentação oficial
da Espressif. Feita por um entusiasta para a comunidade. da Espressif. Desenvolvida para a comunidade de hardware livre.
</p> </p>
<div className="flex flex-col sm:flex-row gap-4 items-start sm:items-center"> <div className="flex flex-col sm:flex-row gap-4 items-start sm:items-center">
<Link href="/" className="inline-flex items-center justify-center gap-2 bg-white text-blue-700 px-6 py-3 rounded-xl font-bold shadow-lg hover:shadow-xl transition-all transform hover:-translate-y-1" aria-label="Voltar para a página inicial"> <Link href="/" className="inline-flex items-center justify-center gap-2 bg-white text-blue-700 px-6 py-3 rounded-xl font-bold shadow-lg hover:shadow-xl active:scale-98 transition-all" aria-label="Voltar para a página inicial">
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <ArrowLeft className="w-5 h-5" />
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M10 19l-7-7m0 0l7-7m-7 7h18" /> <span>Início</span>
</svg>
<span>Voltar</span>
</Link> </Link>
<a <a
href="https://github.com/lucasrguerra/ESPDocs" href="https://github.com/lucasrguerra/ESPDocs"
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"
className="inline-flex items-center justify-center gap-2 bg-white/10 backdrop-blur-sm border-2 border-white/30 text-white px-6 py-3 rounded-xl font-bold hover:bg-white/20 transition-all" className="inline-flex items-center justify-center gap-2 bg-white/10 backdrop-blur-xs border border-white/30 text-white px-6 py-3 rounded-xl font-bold hover:bg-white/20 active:scale-98 transition-all"
aria-label="Abrir repositório do projeto no GitHub (abre em nova aba)" aria-label="Abrir repositório do projeto no GitHub (abre em nova aba)"
> >
<svg className="w-5 h-5" fill="currentColor" viewBox="0 0 24 24"> <svg className="w-5 h-5" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true">
<path d="M12 .5a12 12 0 00-3.8 23.4c.6.1.8-.3.8-.6v-2.1c-3.3.7-4-1.6-4-1.6-.5-1.2-1.2-1.5-1.2-1.5-1-.7.1-.7.1-.7 1.1.1 1.7 1.1 1.7 1.1 1 .1 1.6-.7 1.9-1 .1-.8.4-1.3.8-1.6-2.6-.3-5.3-1.3-5.3-5.8 0-1.3.5-2.3 1.2-3.1-.1-.3-.5-1.6.1-3.3 0 0 1-.3 3.3 1.2a11.4 11.4 0 016 0c2.3-1.5 3.3-1.2 3.3-1.2.6 1.7.2 3 .1 3.3.8.8 1.2 1.9 1.2 3.1 0 4.5-2.7 5.5-5.3 5.8.5.4.9 1.1.9 2.2v3.2c0 .3.2.7.8.6A12 12 0 0012 .5z" fill="currentColor"/> <path d="M12 .5a12 12 0 00-3.8 23.4c.6.1.8-.3.8-.6v-2.1c-3.3.7-4-1.6-4-1.6-.5-1.2-1.2-1.5-1.2-1.5-1-.7.1-.7.1-.7 1.1.1 1.7 1.1 1.7 1.1 1 .1 1.6-.7 1.9-1 .1-.8.4-1.3.8-1.6-2.6-.3-5.3-1.3-5.3-5.8 0-1.3.5-2.3 1.2-3.1-.1-.3-.5-1.6.1-3.3 0 0 1-.3 3.3 1.2a11.4 11.4 0 016 0c2.3-1.5 3.3-1.2 3.3-1.2.6 1.7.2 3 .1 3.3.8.8 1.2 1.9 1.2 3.1 0 4.5-2.7 5.5-5.3 5.8.5.4.9 1.1.9 2.2v3.2c0 .3.2.7.8.6A12 12 0 0012 .5z" />
</svg> </svg>
<span>GitHub</span> <span>GitHub</span>
</a> </a>
@@ -56,116 +71,127 @@ export default function Sobre() {
</div> </div>
</header> </header>
{/* Two columns details grid */}
<div className="mt-12 grid grid-cols-1 lg:grid-cols-3 gap-8"> <div className="mt-12 grid grid-cols-1 lg:grid-cols-3 gap-8">
<section className="lg:col-span-2 space-y-8"> <section className="lg:col-span-2 space-y-8">
<article className="bg-white rounded-3xl p-8 shadow-lg border-2 border-gray-100 hover:border-purple-200 transition-colors"> {/* Missao article */}
<div className="flex items-center gap-3 mb-4"> <article className="bg-white/80 dark:bg-slate-900/40 backdrop-blur-xl rounded-3xl p-8 shadow-xl border border-slate-200/60 dark:border-slate-800/80 hover:border-purple-500/20 dark:hover:border-purple-400/20 transition-all">
<div className="w-12 h-12 bg-linear-to-br from-blue-500 to-purple-600 rounded-xl flex items-center justify-center"> <div className="flex items-center gap-3.5 mb-5 select-none">
<span className="text-2xl">🎯</span> <div className="w-12 h-12 bg-purple-500/10 border border-purple-500/20 rounded-2xl flex items-center justify-center text-purple-500">
<Target className="w-6 h-6" />
</div> </div>
<h2 className="text-3xl font-bold text-gray-900">Missão</h2> <h2 className="text-2xl font-display font-extrabold text-slate-850 dark:text-slate-100">Missão</h2>
</div> </div>
<p className="text-gray-700 leading-relaxed text-base"> <p className="text-slate-650 dark:text-slate-350 leading-relaxed font-semibold text-sm">
Facilitar o acesso e a compreensão do ecossistema ESP32 através de uma Facilitar o acesso e a compreensão do ecossistema ESP32 através de uma
interface em português que organiza especificações técnicas, compara séries, interface em português que organiza especificações técnicas, compara séries,
apresenta frameworks e centraliza links para a documentação oficial da apresenta frameworks e centraliza links para a documentação oficial da
Espressif. O objetivo é economizar tempo na busca por informações técnicas. Espressif. O objetivo principal é otimizar o tempo no desenvolvimento de firmware e IoT.
</p> </p>
</article> </article>
<article className="bg-white rounded-3xl p-8 shadow-lg border-2 border-gray-100"> {/* Por que o ESPDocs article */}
<div className="flex items-center gap-3 mb-4"> <article className="bg-white/80 dark:bg-slate-900/40 backdrop-blur-xl rounded-3xl p-8 shadow-xl border border-slate-200/60 dark:border-slate-800/80">
<div className="w-12 h-12 bg-linear-to-br from-purple-500 to-pink-600 rounded-xl flex items-center justify-center"> <div className="flex items-center gap-3.5 mb-5 select-none">
<span className="text-2xl">❓</span> <div className="w-12 h-12 bg-blue-500/10 border border-blue-500/20 rounded-2xl flex items-center justify-center text-blue-500">
<HelpCircle className="w-6 h-6" />
</div> </div>
<h2 className="text-3xl font-bold text-gray-900">Por que o ESPDocs?</h2> <h2 className="text-2xl font-display font-extrabold text-slate-850 dark:text-slate-100">Por que o ESPDocs?</h2>
</div> </div>
<p className="text-gray-700 leading-relaxed mb-6 text-base"> <p className="text-slate-650 dark:text-slate-350 leading-relaxed mb-6 font-semibold text-sm">
As informações sobre ESP32 estão dispersas em datasheets (PDF), repositórios As informações sobre os microcontroladores ESP32 muitas vezes estão dispersas em datasheets densos,
GitHub e documentação oficial — tudo em inglês. O ESPDocs organiza essas manuais de referência (TRM), repositórios secundários e documentação técnica — tudo inteiramente em inglês.
informações em uma interface em português, facilitando comparações e consultas O ESPDocs condensa e estrutura esses dados, auxiliando na triagem rápida antes de aprofundar na engenharia oficial.
rápidas antes de mergulhar na documentação técnica oficial.
</p> </p>
<div className="grid sm:grid-cols-2 gap-4"> <div className="grid sm:grid-cols-2 gap-4">
{features.map((f) => ( {features.map((f) => (
<div key={f.title} className="flex gap-4 items-start bg-linear-to-br from-gray-50 to-white p-5 rounded-xl border-2 border-gray-100 hover:border-purple-200 transition-colors"> <div key={f.title} className="flex gap-4 items-start bg-slate-50/50 dark:bg-slate-950/20 p-5 rounded-2xl border border-slate-150/40 dark:border-slate-850/40 hover:border-purple-500/10 dark:hover:border-purple-400/10 transition-colors">
<div className="text-3xl shrink-0" aria-hidden="true">{f.icon}</div> <div className="shrink-0 p-1 bg-white dark:bg-slate-900 rounded-xl shadow-xs" aria-hidden="true">
{f.icon}
</div>
<div className="flex-1"> <div className="flex-1">
<h3 className="font-bold text-gray-900 mb-1 text-base">{f.title}</h3> <h3 className="font-bold text-slate-800 dark:text-slate-200 mb-1 text-sm">{f.title}</h3>
<p className="text-sm text-gray-600 leading-relaxed">{f.desc}</p> <p className="text-xs text-slate-500 dark:text-slate-450 leading-relaxed font-semibold">{f.desc}</p>
</div> </div>
</div> </div>
))} ))}
</div> </div>
</article> </article>
<article className="bg-white rounded-3xl p-8 shadow-lg border-2 border-gray-100"> {/* Contribuir article */}
<div className="flex items-center gap-3 mb-4"> <article className="bg-white/80 dark:bg-slate-900/40 backdrop-blur-xl rounded-3xl p-8 shadow-xl border border-slate-200/60 dark:border-slate-800/80">
<div className="w-12 h-12 bg-linear-to-br from-green-500 to-teal-600 rounded-xl flex items-center justify-center"> <div className="flex items-center gap-3.5 mb-5 select-none">
<span className="text-2xl">🤝</span> <div className="w-12 h-12 bg-emerald-500/10 border border-emerald-500/20 rounded-2xl flex items-center justify-center text-emerald-500">
<Users className="w-6 h-6" />
</div> </div>
<h2 className="text-3xl font-bold text-gray-900">Como contribuir</h2> <h2 className="text-2xl font-display font-extrabold text-slate-850 dark:text-slate-100">Como Contribuir</h2>
</div> </div>
<p className="text-gray-700 leading-relaxed mb-4 text-base">
Correções, traduções e novos exemplos são bem-vindos. Abra uma <p className="text-slate-650 dark:text-slate-350 leading-relaxed mb-6 font-semibold text-sm">
issue ou um pull request no repositório. Busco por clareza nas Correções de tradução, refinamento de tabelas e novos trechos funcionais de exemplos são muito bem-vindos!
descrições e exemplos testáveis. A plataforma cresce por meio da comunidade.
</p> </p>
<div className="bg-linear-to-r from-blue-50 to-purple-50 rounded-xl p-5 border-2 border-blue-100">
<ol className="list-decimal list-inside text-gray-700 space-y-2 text-sm"> <div className="bg-slate-50/50 dark:bg-slate-950/20 rounded-2xl p-5 border border-slate-150/40 dark:border-slate-850/40">
<li className="font-medium">Fork do repositório e crie uma branch clara.</li> <ol className="list-decimal list-inside text-slate-700 dark:text-slate-300 space-y-2 text-xs font-semibold">
<li className="font-medium">Adicione exemplos ou corrija o conteúdo com referências quando possível.</li> <li>Faça o fork do repositório no GitHub.</li>
<li className="font-medium">Abra um PR e descreva o que foi alterado.</li> <li>Implemente melhorias e revise referências técnicas oficiais.</li>
<li>Abra o seu Pull Request (PR) detalhando os ajustes efetuados.</li>
</ol> </ol>
</div> </div>
</article> </article>
<article className="bg-linear-to-br from-purple-50 to-pink-50 rounded-3xl p-8 shadow-lg border-2 border-purple-100"> {/* Accessibility note */}
<div className="flex items-center gap-3 mb-4"> <article className="bg-gradient-to-br from-purple-500/5 to-pink-500/5 dark:from-purple-500/10 dark:to-pink-500/10 rounded-3xl p-8 shadow-lg border border-purple-500/20 dark:border-pink-500/20">
<div className="w-12 h-12 bg-linear-to-br from-purple-600 to-pink-600 rounded-xl flex items-center justify-center"> <div className="flex items-center gap-3.5 mb-5 select-none">
<span className="text-2xl">♿</span> <div className="w-12 h-12 bg-pink-500/10 border border-pink-500/20 rounded-2xl flex items-center justify-center text-pink-500">
<Accessibility className="w-6 h-6" />
</div> </div>
<h2 className="text-3xl font-bold text-gray-900">Acessibilidade</h2> <h2 className="text-2xl font-display font-extrabold text-slate-850 dark:text-slate-100">Acessibilidade</h2>
</div> </div>
<p className="text-gray-700 leading-relaxed text-base"> <p className="text-slate-650 dark:text-slate-350 leading-relaxed font-semibold text-sm">
O site prioriza leitura, contraste legível e navegação por teclado. O ESPDocs preza pela legibilidade, alto contraste cromático e compatibilidade de navegação por teclado.
Se encontrar barreiras, por favor relate para que eu possa melhorar. Caso encontre alguma barreira técnica de leitura ou navegação, sinta-se encorajado a relatar via Issue.
</p> </p>
</article> </article>
</section> </section>
{/* Sidebar area */}
<aside className="space-y-6"> <aside className="space-y-6">
<div className="bg-white rounded-3xl p-8 shadow-lg border-2 border-gray-100 hover:border-purple-200 transition-colors"> {/* Author Card */}
<div className="flex flex-col items-center mb-6"> <div className="bg-white/80 dark:bg-slate-900/40 backdrop-blur-xl border border-slate-200/60 dark:border-slate-800/80 rounded-3xl p-8 shadow-xl">
<div className="w-20 h-20 bg-linear-to-br from-purple-100 to-pink-100 rounded-2xl flex items-center justify-center mb-4"> <div className="flex flex-col items-center mb-6 select-none">
<div className="w-20 h-20 bg-gradient-to-br from-purple-500/10 to-pink-500/10 rounded-2xl flex items-center justify-center mb-4 border border-purple-500/20 shadow-inner">
<img <img
src="/logo.png" src="/logo.png"
alt="Ciência Embarcada" alt="Ciência Embarcada Logo"
className="h-16 w-16 object-contain" className="h-16 w-16 object-contain"
/> />
</div> </div>
<h3 className="text-xl font-bold text-gray-900 text-center">Sobre o Autor</h3> <h3 className="text-lg font-display font-extrabold text-slate-850 dark:text-slate-100 text-center">Sobre o Autor</h3>
</div> </div>
<p className="text-gray-700 text-sm leading-relaxed mb-4 text-justify"> <div className="space-y-4 text-xs font-semibold text-slate-650 dark:text-slate-350 text-justify leading-relaxed">
<strong>Lucas Rayan Guerra</strong>, criador do <a <p>
<strong>Lucas Rayan Guerra</strong>, criador da iniciativa <a
href="https://cienciaembarcada.com.br" href="https://cienciaembarcada.com.br"
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"
className="text-purple-600 hover:text-purple-800 font-semibold" className="text-purple-600 hover:text-purple-800 dark:text-purple-400 dark:hover:text-purple-300 font-bold"
> >
Ciência Embarcada Ciência Embarcada
</a>, é Técnico em Desenvolvimento de Sistemas, estudante de Ciência da Computação na UFRPE e de Blockchain e Criptografia Digital na FMU. </a>, é Técnico em Desenvolvimento de Sistemas, estudante de Ciência da Computação na UFRPE e estuda Blockchain e Criptografia.
</p> </p>
<p className="text-gray-700 text-sm leading-relaxed mb-4 text-justify"> <p>
Recifense apaixonado por Pernambuco, Diretor de Software da Semine AgriTech e palestrante em eventos como o REC'n'Play, Lucas compartilha conhecimento sobre ESP32, Eletrônica, IoT, Cibersegurança e muito mais. Diretor de Software na Semine AgriTech e palestrante em feiras e conferências (como o REC'n'Play), compartilha ativamente material sobre hardware, internet das coisas e cibersegurança.
</p> </p>
</div>
<div className="bg-purple-50 border border-purple-100 rounded-lg p-4 mb-4 text-justify"> <div className="bg-purple-500/5 dark:bg-purple-400/5 border border-purple-500/10 dark:border-purple-400/10 rounded-2xl p-4 my-5 text-justify">
<p className="text-sm text-gray-700 italic"> <p className="text-[10px] text-slate-650 dark:text-slate-400 italic font-semibold leading-relaxed">
"O Ciência Embarcada nasceu do desejo de criar um espaço livre e organizado para compartilhar conhecimentos e experiências sobre tecnologia e ciência." "O Ciência Embarcada nasceu para criar um espaço de curadoria técnica livre, promovendo a popularização científica e do hardware embarcado."
</p> </p>
</div> </div>
@@ -173,48 +199,57 @@ export default function Sobre() {
href="https://cienciaembarcada.com.br" href="https://cienciaembarcada.com.br"
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"
className="inline-flex items-center justify-center w-full px-4 py-2 rounded-lg bg-linear-to-r from-blue-600 to-purple-600 text-white font-semibold shadow-sm hover:shadow-md focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-purple-500 transition-all" className="inline-flex items-center justify-center gap-1.5 w-full px-4 py-3 rounded-xl bg-gradient-to-r from-blue-600 to-purple-600 text-white font-bold text-xs uppercase tracking-wider shadow-md hover:shadow-lg active:scale-98 transition-all"
> >
🚀 Visitar Ciência Embarcada <span>Visitar Ciência Embarcada</span>
</a> </a>
</div> </div>
<div className="bg-white rounded-2xl p-6 shadow-md border border-gray-100"> {/* Repository / Contributions card */}
<div className="flex items-center justify-between mb-3"> <div className="bg-white/80 dark:bg-slate-900/40 backdrop-blur-xl border border-slate-200/60 dark:border-slate-800/80 rounded-3xl p-6 shadow-xl">
<h3 className="text-lg font-semibold">Contribua</h3> <div className="flex items-center justify-between mb-4 select-none">
<span className="text-xs bg-green-100 text-green-800 px-2 py-1 rounded-full">Open Source</span> <h3 className="text-sm font-display font-extrabold text-slate-800 dark:text-slate-200">Contribua</h3>
<span className="text-[9px] bg-emerald-500/10 text-emerald-600 dark:text-emerald-450 px-2.5 py-0.5 rounded-full font-bold uppercase tracking-wider border border-emerald-500/20">Open Source</span>
</div> </div>
<p className="text-gray-700 mb-4"> <p className="text-xs text-slate-500 dark:text-slate-450 leading-relaxed mb-4 font-semibold">
Ajude a melhorar o ESPDocs com exemplos, correções e traduções. Ajude a expandir a cobertura do ESPDocs submetendo exemplos de códigos ou aprimoramentos técnicos.
</p> </p>
<div className="flex flex-col gap-3">
<a <a
href="https://github.com/lucasrguerra/ESPDocs" href="https://github.com/lucasrguerra/ESPDocs"
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"
className="inline-flex items-center justify-center px-4 py-2 rounded-lg bg-linear-to-r from-blue-600 to-purple-600 text-white font-semibold shadow-sm hover:shadow-md focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-purple-500" className="inline-flex items-center justify-center gap-2 w-full px-4 py-3 rounded-xl bg-slate-900 dark:bg-slate-100 text-white dark:text-slate-950 font-bold text-xs uppercase tracking-wider shadow-md hover:opacity-90 active:scale-98 transition-all"
aria-label="Abrir repositório no GitHub" aria-label="Abrir repositório no GitHub"
> >
Ver repositório <svg className="w-4 h-4" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true">
<path d="M12 .5a12 12 0 00-3.8 23.4c.6.1.8-.3.8-.6v-2.1c-3.3.7-4-1.6-4-1.6-.5-1.2-1.2-1.5-1.2-1.5-1-.7.1-.7.1-.7 1.1.1 1.7 1.1 1.7 1.1 1 .1 1.6-.7 1.9-1 .1-.8.4-1.3.8-1.6-2.6-.3-5.3-1.3-5.3-5.8 0-1.3.5-2.3 1.2-3.1-.1-.3-.5-1.6.1-3.3 0 0 1-.3 3.3 1.2a11.4 11.4 0 016 0c2.3-1.5 3.3-1.2 3.3-1.2.6 1.7.2 3 .1 3.3.8.8 1.2 1.9 1.2 3.1 0 4.5-2.7 5.5-5.3 5.8.5.4.9 1.1.9 2.2v3.2c0 .3.2.7.8.6A12 12 0 0012 .5z" />
</svg>
<span>Ver Repositório</span>
</a> </a>
</div>
<div className="mt-4 text-sm text-gray-500"> <div className="mt-4 text-[10px] text-slate-450 dark:text-slate-500 leading-relaxed font-semibold border-t border-slate-100 dark:border-slate-850/45 pt-4">
<strong>Importante:</strong> Esta é uma plataforma de organização de informações, não uma <strong>Aviso Legal:</strong> O ESPDocs é um agregador e organizador. Manuais técnicos detalhados e repositórios oficiais referenciados são de propriedade exclusiva da Espressif Systems. Para projetos críticos comerciais, consulte sempre a documentação nativa oficial.
tradução da documentação oficial. A interface está em português, mas datasheets, manuais
técnicos e repositórios linkados são da Espressif e estão em inglês. Para decisões
técnicas críticas, sempre consulte a documentação oficial.
</div> </div>
</div> </div>
<div className="bg-white/80 rounded-2xl p-4 shadow-sm border border-gray-100"> {/* Summary sidebar card */}
<h4 className="font-semibold mb-2">Resumo</h4> <div className="bg-white/80 dark:bg-slate-900/40 backdrop-blur-xl border border-slate-200/60 dark:border-slate-800/80 rounded-3xl p-6 shadow-xl select-none">
<ul className="text-sm text-gray-700 space-y-2"> <h4 className="text-xs font-display font-extrabold text-slate-800 dark:text-slate-200 mb-3 uppercase tracking-wider">Conteúdo Técnico</h4>
<li>🔎 Conteúdo técnico explicado.</li> <ul className="text-xs text-slate-600 dark:text-slate-400 space-y-2.5 font-bold">
<li>🧪 Exemplos e projetos.</li> <li className="flex items-center gap-2">
<li>🤝 Comunidade e contribuições.</li> <span className="w-1.5 h-1.5 rounded-full bg-purple-500" />
<span>Focado em clareza técnica</span>
</li>
<li className="flex items-center gap-2">
<span className="w-1.5 h-1.5 rounded-full bg-blue-500" />
<span>Exemplos práticos de códigos</span>
</li>
<li className="flex items-center gap-2">
<span className="w-1.5 h-1.5 rounded-full bg-emerald-500" />
<span>Links oficiais mapeados</span>
</li>
</ul> </ul>
</div> </div>
</aside> </aside>
+36 -38
View File
@@ -1,4 +1,6 @@
"use client"; 'use client';
import { Award, CheckCircle, HelpCircle, ExternalLink } from 'lucide-react';
export default function BoardCard({ board }) { export default function BoardCard({ board }) {
const mainFields = ['Nome', 'Link', 'Imagem', 'Loja Oficial']; const mainFields = ['Nome', 'Link', 'Imagem', 'Loja Oficial'];
@@ -19,62 +21,56 @@ export default function BoardCard({ board }) {
); );
return ( return (
<div className="bg-white rounded-2xl shadow-lg hover:shadow-2xl transition-all duration-300 overflow-hidden group flex flex-col h-full"> <div className="bg-white/80 dark:bg-slate-900/40 backdrop-blur-xl border border-slate-200/60 dark:border-slate-800/80 rounded-3xl shadow-lg hover:shadow-2xl hover:-translate-y-1.5 transition-all duration-300 overflow-hidden group flex flex-col h-full animate-fadeIn select-none">
{/* Imagem */} {/* Image Container with premium glass background */}
{board.Imagem && ( {board.Imagem && (
<div <div
className="mx-auto w-fit" className="mx-auto w-full p-4 flex justify-center bg-white dark:bg-white border-b border-slate-100 dark:border-slate-850/40 shrink-0"
dangerouslySetInnerHTML={{ __html: board.Imagem }} dangerouslySetInnerHTML={{ __html: board.Imagem }}
/> />
)} )}
<div className="p-6 flex flex-col grow"> <div className="p-6 flex flex-col grow justify-between">
{/* Nome */} <div>
<h3 className="text-xl font-bold text-gray-800 mb-2 line-clamp-2"> {/* Loja Oficial Badge */}
{board.Nome || 'Placa sem nome'}
</h3>
{/* Loja Oficial */}
{board['Loja Oficial'] && board['Loja Oficial'].toLowerCase() === 'sim' && ( {board['Loja Oficial'] && board['Loja Oficial'].toLowerCase() === 'sim' && (
<div className="mb-4"> <div className="mb-2">
<p className="text-sm text-blue-600 flex items-center gap-1.5"> <span className="inline-flex items-center gap-1 bg-blue-500/10 text-blue-600 dark:text-blue-400 border border-blue-500/20 rounded-full px-2.5 py-0.5 text-[9px] font-bold uppercase tracking-wider">
<svg className="w-4 h-4" fill="currentColor" viewBox="0 0 20 20"> <Award className="w-3.5 h-3.5 shrink-0" />
<path fillRule="evenodd" d="M6.267 3.455a3.066 3.066 0 001.745-.723 3.066 3.066 0 013.976 0 3.066 3.066 0 001.745.723 3.066 3.066 0 012.812 2.812c.051.643.304 1.254.723 1.745a3.066 3.066 0 010 3.976 3.066 3.066 0 00-.723 1.745 3.066 3.066 0 01-2.812 2.812 3.066 3.066 0 00-1.745.723 3.066 3.066 0 01-3.976 0 3.066 3.066 0 00-1.745-.723 3.066 3.066 0 01-2.812-2.812 3.066 3.066 0 00-.723-1.745 3.066 3.066 0 010-3.976 3.066 3.066 0 00.723-1.745 3.066 3.066 0 012.812-2.812zm7.44 5.252a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z" clipRule="evenodd" /> <span>Loja Oficial</span>
</svg> </span>
<span>Link da loja oficial do fabricante</span>
</p>
</div> </div>
)} )}
{/* Nome */}
<h3 className="text-base font-display font-extrabold text-slate-850 dark:text-slate-100 mb-4 line-clamp-2 leading-snug group-hover:text-purple-650 dark:group-hover:text-purple-400 transition-colors">
{board.Nome || 'Placa Genérica'}
</h3>
{/* Características Técnicas */} {/* Características Técnicas */}
{availableFeatures.length > 0 && ( {availableFeatures.length > 0 && (
<div className="mb-4 grow"> <div className="mb-6">
<div className="flex items-center gap-2 mb-3"> <h4 className="text-[9px] font-bold text-slate-400 dark:text-slate-500 uppercase tracking-widest flex items-center gap-1.5 mb-2.5">
<svg className="w-4 h-4 text-green-600" fill="currentColor" viewBox="0 0 20 20"> <CheckCircle className="w-3.5 h-3.5 text-emerald-500" />
<path fillRule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z" clipRule="evenodd" /> <span>Recursos Embarcados</span>
</svg> </h4>
<h4 className="text-sm font-semibold text-gray-700">Recursos Disponíveis</h4>
</div> <div className="flex flex-wrap gap-1.5">
<div className="flex flex-wrap gap-2">
{availableFeatures.map((field) => { {availableFeatures.map((field) => {
const isOptional = optionalFeatures.includes(field); const isOptional = optionalFeatures.includes(field);
return ( return (
<div <div
key={field} key={field}
className={`text-xs px-3 py-1.5 rounded-lg font-medium flex items-center gap-1 ${ className={`text-[9px] px-2 py-0.5 rounded-lg font-bold flex items-center gap-1 ${
isOptional isOptional
? 'bg-yellow-100 text-yellow-700 border border-yellow-200' ? 'bg-amber-500/10 dark:bg-amber-500/15 border border-amber-500/20 text-amber-600 dark:text-amber-400'
: 'bg-green-100 text-green-700 border border-green-200' : 'bg-emerald-500/10 dark:bg-emerald-500/15 border border-emerald-500/20 text-emerald-600 dark:text-emerald-400'
}`} }`}
> >
{isOptional ? ( {isOptional ? (
<svg className="w-3 h-3" fill="currentColor" viewBox="0 0 20 20"> <HelpCircle className="w-2.5 h-2.5 shrink-0" />
<path fillRule="evenodd" d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7-4a1 1 0 11-2 0 1 1 0 012 0zM9 9a1 1 0 000 2v3a1 1 0 001 1h1a1 1 0 100-2v-3a1 1 0 00-1-1H9z" clipRule="evenodd" />
</svg>
) : ( ) : (
<svg className="w-3 h-3" fill="currentColor" viewBox="0 0 20 20"> <CheckCircle className="w-2.5 h-2.5 shrink-0" />
<path fillRule="evenodd" d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z" clipRule="evenodd" />
</svg>
)} )}
<span>{field}</span> <span>{field}</span>
</div> </div>
@@ -83,16 +79,18 @@ export default function BoardCard({ board }) {
</div> </div>
</div> </div>
)} )}
</div>
{/* Link para mais informações - sempre no final */} {/* Link de Compra */}
{board.Link && ( {board.Link && (
<a <a
href={board.Link} href={board.Link}
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"
className="block w-full bg-linear-to-r from-blue-600 to-purple-600 text-white text-center px-4 py-3 rounded-xl font-semibold hover:shadow-lg transform hover:-translate-y-1 transition-all duration-300 mt-auto" className="block w-full bg-slate-900 dark:bg-slate-100 text-white dark:text-slate-950 text-center px-4 py-3 rounded-xl font-bold text-xs uppercase tracking-wider hover:opacity-90 active:scale-98 transition-all shadow-md mt-auto flex items-center justify-center gap-1.5"
> >
Abrir no AliExpress 🔗 <span>Adquirir no AliExpress</span>
<ExternalLink className="w-3.5 h-3.5" />
</a> </a>
)} )}
</div> </div>
+66 -29
View File
@@ -1,6 +1,7 @@
'use client' 'use client'
import { useEffect, useState } from 'react' import { useEffect, useState } from 'react'
import { Code, Copy, CheckCircle, ExternalLink, HelpCircle } from 'lucide-react'
export default function CodeExample({ framework }) { export default function CodeExample({ framework }) {
const examplesUrl = framework?.exemplos; const examplesUrl = framework?.exemplos;
@@ -40,13 +41,13 @@ export default function CodeExample({ framework }) {
if (text && text.trim().length > 0) { if (text && text.trim().length > 0) {
setSample(text); setSample(text);
} else { } else {
setSample(`// Arquivo de exemplo está vazio. Consulte: ${examplesUrl || 'repositório'}\n`); setSample(`// Arquivo de exemplo está vazio. Consulte o repositório oficial.\n`);
setError('Arquivo vazio'); setError('Arquivo vazio');
} }
}) })
.catch((err) => { .catch((err) => {
if (cancelled) return; if (cancelled) return;
setSample(`// Não foi possível carregar o exemplo local. Consulte: ${examplesUrl || 'repositório'}\n`); setSample(`// Não foi possível carregar o exemplo localmente.\n// Consulte o link do GitHub acima para exemplos funcionais.\n`);
setError(err.message || 'Falha ao carregar exemplo'); setError(err.message || 'Falha ao carregar exemplo');
}) })
.finally(() => { .finally(() => {
@@ -69,27 +70,44 @@ export default function CodeExample({ framework }) {
} }
return ( return (
<div className="bg-white rounded-2xl shadow-lg p-6 border-2 border-gray-100 mb-8"> <div className="bg-white/80 dark:bg-slate-900/40 backdrop-blur-xl rounded-3xl p-6 md:p-8 border border-slate-200/60 dark:border-slate-800/80 shadow-2xl mb-8 animate-fadeIn">
<div className="md:flex items-start justify-between gap-4 mb-4"> <div className="md:flex items-center justify-between gap-6 mb-6">
<div> <div className="select-none">
<h3 className="text-lg font-semibold text-gray-800 mb-1">Exemplo de Código</h3> <h3 className="text-xl font-display font-extrabold text-slate-850 dark:text-slate-100 mb-1 flex items-center gap-2">
<p className="text-sm text-gray-600">Trecho rápido para começar — personalize conforme necessário.</p> <Code className="w-5 h-5" style={{ color }} />
<span>Demonstração de Código</span>
</h3>
<p className="text-xs text-slate-500 dark:text-slate-400 font-semibold">Trecho de inicialização rápida — copie e personalize em seu projeto.</p>
</div> </div>
<div className="grid grid-cols-2 md:flex items-center gap-2"> <div className="flex flex-wrap items-center gap-2.5 mt-4 md:mt-0 select-none">
<span <span
className="inline-flex items-center text-sm font-semibold px-3 py-1 rounded-full text-white" className="inline-flex items-center text-[10px] font-bold px-2.5 py-1 rounded-full uppercase tracking-wider"
style={{ backgroundColor: color }} style={{
backgroundColor: `${color}18`,
color,
border: `1px solid ${color}25`
}}
> >
{framework?.linguagem || 'Código'} {framework?.linguagem || 'Código'}
</span> </span>
<button <button
onClick={handleCopy} onClick={handleCopy}
className="inline-flex items-center gap-2 bg-white text-gray-800 px-3 py-2 rounded-lg font-medium shadow border-2 border-gray-100 cursor-pointer hover:shadow-lg transition" className="inline-flex items-center gap-1.5 bg-slate-50 dark:bg-slate-850 hover:bg-slate-100 dark:hover:bg-slate-800 text-slate-700 dark:text-slate-250 px-3.5 py-2 rounded-xl font-bold text-xs shadow-xs border border-slate-200/60 dark:border-slate-800/80 cursor-pointer transition active:scale-95"
title="Copiar código" title="Copiar código para a área de transferência"
> >
{copied ? '✅ Copiado' : '📋 Copiar'} {copied ? (
<>
<CheckCircle className="w-3.5 h-3.5 text-emerald-500" />
<span className="text-emerald-600 dark:text-emerald-450">Copiado!</span>
</>
) : (
<>
<Copy className="w-3.5 h-3.5" />
<span>Copiar</span>
</>
)}
</button> </button>
{examplesUrl && ( {examplesUrl && (
@@ -97,45 +115,64 @@ export default function CodeExample({ framework }) {
href={examplesUrl} href={examplesUrl}
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"
className="inline-flex items-center gap-2 px-3 py-2 rounded-lg font-semibold text-white col-span-2" className="inline-flex items-center gap-1.5 px-4 py-2 rounded-xl text-xs font-bold text-white shadow-lg active:scale-95 transition hover:opacity-95"
style={{ style={{
background: `linear-gradient(90deg, ${color} 0%, rgba(124,58,237,1) 100%)` background: `linear-gradient(90deg, ${color} 0%, rgba(147,51,234,1) 100%)`,
boxShadow: `0 8px 20px -8px ${color}`
}} }}
title="Abrir exemplos no GitHub" title="Abrir exemplos no GitHub"
> >
🔗 Ver Outros no GitHub <span>Mais Exemplos</span>
<ExternalLink className="w-3.5 h-3.5" />
</a> </a>
)} )}
</div> </div>
</div> </div>
<div className="rounded-lg overflow-hidden border"> <div className="rounded-2xl overflow-hidden border border-slate-200/60 dark:border-slate-800/80 shadow-md">
<div className="p-0"> <div className="select-none">
<div className="flex items-center justify-between bg-linear-to-r from-gray-900 to-gray-800 p-3"> <div className="flex items-center justify-between bg-slate-100/80 dark:bg-slate-950/70 border-b border-slate-200/60 dark:border-slate-850/60 px-4 py-3.5">
<div className="text-xs text-gray-300 font-medium">{framework?.nome}</div> <div className="text-xs font-bold text-slate-750 dark:text-slate-250 flex items-center gap-1.5">
<div className="text-xs text-gray-400"> <span className="w-2.5 h-2.5 rounded-full bg-red-500/80" />
{loading ? 'Carregando exemplo...' : (framework?.exemplo ? 'Exemplo do framework' : 'Abrir no GitHub')} <span className="w-2.5 h-2.5 rounded-full bg-yellow-500/80" />
<span className="w-2.5 h-2.5 rounded-full bg-green-500/80" />
<span className="ml-2 font-mono text-[10px] text-slate-500 dark:text-slate-400">{framework?.nome} example</span>
</div>
<div className="text-[9px] font-bold text-slate-400 dark:text-slate-500 uppercase tracking-widest">
{loading ? 'Buscando exemplo...' : (framework?.exemplo ? 'Local Source' : 'External GitHub')}
</div> </div>
</div> </div>
</div> </div>
<div className="flex"> <div className="flex bg-slate-950">
<pre <pre
className="bg-gray-900 text-gray-100 p-4 w-full overflow-auto" className="text-slate-200 p-5 w-full overflow-auto text-xs leading-relaxed scrollbar-thin select-text"
style={{ fontFamily: 'ui-monospace, SFMono-Regular, Menlo, Monaco, "Roboto Mono", "Courier New", monospace' }} style={{ fontFamily: 'ui-monospace, SFMono-Regular, Menlo, Monaco, "Roboto Mono", "Courier New", monospace' }}
> >
{lines.map((line, idx) => ( {lines.map((line, idx) => (
<div key={idx} className="flex select-text"> <div key={idx} className="flex">
<span className="text-gray-500 pr-4 text-right w-8 select-none">{idx + 1}</span> <span className="text-slate-600 dark:text-slate-650 pr-4 text-right w-8 select-none border-r border-slate-900/60 mr-4 font-semibold">{idx + 1}</span>
<code className="whitespace-pre-wrap flex-1">{line === '' ? '\u00A0' : line}</code> <code className="whitespace-pre flex-1 text-left">{line === '' ? '\u00A0' : line}</code>
</div> </div>
))} ))}
</pre> </pre>
</div> </div>
</div> </div>
{error && <p className="text-xs text-red-500 mt-2">Não foi possível carregar o arquivo de exemplo: {error}. Usando instrução para o repositório.</p>} {error && (
<p className="text-xs text-gray-500 mt-3">Dica: personalize este trecho conforme o seu projeto. Os exemplos completos estão no repositório.</p> <div className="bg-red-500/10 border border-red-500/20 rounded-xl p-3.5 mt-3 select-none">
<p className="text-[10px] font-bold text-red-600 dark:text-red-400">
Nota: Não foi possível carregar o arquivo de exemplo local ({error}). Use o botão "Mais Exemplos" para ver os códigos oficiais.
</p>
</div>
)}
<div className="mt-4 flex items-start gap-2 bg-slate-50/50 dark:bg-slate-950/20 border border-slate-150/40 dark:border-slate-850/40 p-4 rounded-2xl select-none">
<HelpCircle className="w-4.5 h-4.5 text-purple-500 shrink-0 mt-0.5" />
<p className="text-[10px] font-semibold text-slate-500 dark:text-slate-400 leading-relaxed">
<strong>Dica de Desenvolvimento:</strong> Personalize o trecho de inicialização com as pinagens e configurações corretas correspondentes à série do seu microcontrolador (ex: ESP32-S3, ESP32-C6). A documentação detalhada da API pode ser acessada na barra superior do framework.
</p>
</div>
</div> </div>
); );
} }
+255 -318
View File
@@ -1,37 +1,52 @@
"use client"; "use client";
import { useState, useEffect, useMemo } from "react"; import { useState, useEffect, useMemo } from "react";
import { createPortal } from "react-dom";
import {
Search,
X,
Grid,
List,
Filter,
AlertTriangle,
Info,
Tag,
Zap,
Sliders,
CheckCircle,
ExternalLink
} from "lucide-react";
const FILTER_CATEGORIES = [ const FILTER_CATEGORIES = [
{ id: "All", name: "Todos", color: "#374151" }, { id: "All", name: "Todos", color: "#8b5cf6" }, // purple
{ id: "GND", name: "Terra (GND)", color: "#64748b" }, { id: "GND", name: "Terra (GND)", color: "#64748b" }, // slate
{ id: "Power", name: "Alimentação", color: "#ef4444" }, { id: "Power", name: "Alimentação", color: "#ef4444" }, // red
{ id: "GPIO", name: "GPIO", color: "#22c55e" }, { id: "GPIO", name: "GPIO", color: "#22c55e" }, // green
{ id: "Low Power", name: "GPIO LP", color: "#f59e0b" }, { id: "Low Power", name: "GPIO LP", color: "#f59e0b" }, // amber
{ id: "ADC", name: "ADC", color: "#ec4899" }, { id: "ADC", name: "ADC", color: "#ec4899" }, // pink
{ id: "DAC", name: "DAC", color: "#a855f7" }, { id: "DAC", name: "DAC", color: "#a855f7" }, // purple
{ id: "Touch", name: "Touch", color: "#06b6d4" }, { id: "Touch", name: "Touch", color: "#06b6d4" }, // cyan
{ id: "UART", name: "UART", color: "#3b82f6" }, { id: "UART", name: "UART", color: "#3b82f6" }, // blue
{ id: "SPI", name: "SPI", color: "#ef4444" }, { id: "SPI", name: "SPI", color: "#f43f5e" }, // rose
{ id: "USB", name: "USB", color: "#8b5cf6" }, { id: "USB", name: "USB", color: "#a855f7" }, // purple
{ id: "Clock", name: "Clock", color: "#eab308" }, { id: "Clock", name: "Clock", color: "#eab308" }, // yellow
{ id: "JTAG", name: "JTAG", color: "#8b5cf6" }, { id: "JTAG", name: "JTAG", color: "#6366f1" }, // indigo
{ id: "Strapping", name: "Inicialização", color: "#dc2626" }, { id: "Strapping", name: "Inicialização", color: "#dc2626" }, // red
{ id: "DSI", name: "Display (DSI)", color: "#f97316" }, { id: "DSI", name: "Display (DSI)", color: "#f97316" }, // orange
{ id: "CSI", name: "Câmera (CSI)", color: "#14b8a6" }, { id: "CSI", name: "Câmera (CSI)", color: "#14b8a6" }, // teal
{ id: "Ethernet", name: "Ethernet", color: "#3b82f6" }, { id: "Ethernet", name: "Ethernet", color: "#0284c7" }, // sky
{ id: "SD", name: "Cartão SD", color: "#10b981" }, { id: "SD", name: "Cartão SD", color: "#10b981" }, // emerald
{ id: "I2C", name: "I2C", color: "#f59e0b" }, { id: "I2C", name: "I2C", color: "#f59e0b" }, // amber
{ id: "Antena", name: "Antena", color: "#22d3ee" }, { id: "Antena", name: "Antena", color: "#06b6d4" }, // cyan
]; ];
const type_colors = { const type_colors = {
power: "#ef4444", power: "#ef4444", // high-voltage red
ground: "#64748b", ground: "#64748b", // slate gray
io: "#22c55e", io: "#22c55e", // laser green
analog: "#ec4899", analog: "#ec4899", // dynamic pink
dedicated: "#3b82f6", dedicated: "#3b82f6", // electric blue
nc: "#9ca3af", nc: "#94a3b8", // muted gray
}; };
export default function ConnectionsDiagram({ connections, serie }) { export default function ConnectionsDiagram({ connections, serie }) {
@@ -41,6 +56,11 @@ export default function ConnectionsDiagram({ connections, serie }) {
const [searchQuery, setSearchQuery] = useState(""); const [searchQuery, setSearchQuery] = useState("");
const [viewMode, setViewMode] = useState("grid"); const [viewMode, setViewMode] = useState("grid");
const [showTooltip, setShowTooltip] = useState(null); const [showTooltip, setShowTooltip] = useState(null);
const [mounted, setMounted] = useState(false);
useEffect(() => {
setMounted(true);
}, []);
const isConnectionVisible = (connection) => { const isConnectionVisible = (connection) => {
const matchesFilter = activeFilter === "All" || connection.categorias.includes(activeFilter); const matchesFilter = activeFilter === "All" || connection.categorias.includes(activeFilter);
@@ -110,26 +130,16 @@ export default function ConnectionsDiagram({ connections, serie }) {
if (!connections || connections.length === 0) { if (!connections || connections.length === 0) {
return ( return (
<div className="flex flex-col items-center justify-center py-16 px-4"> <div className="flex flex-col items-center justify-center py-16 px-4">
<div className="w-20 h-20 mb-6 rounded-full bg-gray-100 flex items-center justify-center"> <div className="w-20 h-20 mb-6 rounded-2xl bg-slate-100 dark:bg-slate-900 border border-slate-200 dark:border-slate-800 flex items-center justify-center shadow-lg">
<svg <Sliders className="w-10 h-10 text-slate-400 dark:text-slate-600" />
className="w-10 h-10 text-gray-400"
fill="none"
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="2"
viewBox="0 0 24 24"
stroke="currentColor"
>
<path d="M9 3v2m6-2v2M9 19v2m6-2v2M5 9H3m2 6H3m18-6h-2m2 6h-2M7 19h10a2 2 0 002-2V7a2 2 0 00-2-2H7a2 2 0 00-2 2v10a2 2 0 002 2zM9 9h6v6H9V9z" />
</svg>
</div> </div>
<p className="text-lg font-medium text-gray-600"> <p className="text-lg font-bold text-slate-800 dark:text-slate-200">
Diagrama de conexões indisponível Diagrama de conexões indisponível
</p> </p>
<p className="text-sm text-gray-500 mt-2"> <p className="text-xs text-slate-400 dark:text-slate-500 mt-2">
Este modelo ainda não possui dados de conexão Este modelo ainda não possui um mapa detalhado de conexões e barramentos.
</p> </p>
</div> </div>
); );
@@ -137,136 +147,117 @@ export default function ConnectionsDiagram({ connections, serie }) {
return ( return (
<div className="space-y-8 pb-8"> <div className="space-y-8 pb-8">
{/* Header info */}
<div className="space-y-3"> <div className="space-y-3">
<h2 className="text-2xl font-bold text-gray-800 mb-6 pb-3 border-b-2 flex items-center gap-3" style={{ borderColor: serie.color }}> <h2 className="text-xl font-bold text-slate-850 dark:text-slate-100 mb-4 pb-3 border-b border-slate-200/60 dark:border-slate-800/65 flex items-center gap-3 select-none">
<span>📍</span> <Sliders className="w-5 h-5" style={{ color: serie.color }} />
Diagrama de Conexões Interativo <span>Diagrama de Conexões Interativo</span>
</h2> </h2>
<p className="text-lg text-gray-600 leading-relaxed"> <p className="text-sm text-slate-550 dark:text-slate-400 leading-relaxed">
Explore as conexões do {serie.nome_completo}. Use a busca, filtros por função ou passe o mouse para ver detalhes rápidos. Explore todas as conexões e multiplexação do SoC do {serie.nome_completo}. Utilize o painel de busca rápida ou filtre por categoria para acender conexões específicas e ver mapas de barramentos.
</p> </p>
</div> </div>
<div className="bg-white rounded-2xl shadow-sm border border-gray-200 p-6 space-y-4"> {/* Search Panel */}
<div className="bg-white/60 dark:bg-slate-900/30 backdrop-blur-xl rounded-2xl border border-slate-200/60 dark:border-slate-800/80 p-5 space-y-4 shadow-xl">
<div className="flex flex-col sm:flex-row gap-4"> <div className="flex flex-col sm:flex-row gap-4">
<div className="flex-1 relative"> <div className="flex-1 relative">
<svg <Search className="absolute left-4 top-1/2 -translate-y-1/2 w-4.5 h-4.5 text-slate-400 dark:text-slate-500" />
className="absolute left-4 top-1/2 -translate-y-1/2 w-5 h-5 text-gray-400"
fill="none"
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="2"
viewBox="0 0 24 24"
stroke="currentColor"
>
<path d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z" />
</svg>
<input <input
type="text" type="text"
placeholder="Buscar por nome ou número da conexão..." placeholder="Buscar por nome ou número da conexão (Ex: GPIO1, TXD, XTAL)..."
value={searchQuery} value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)} onChange={(e) => setSearchQuery(e.target.value)}
className="w-full pl-12 pr-4 py-3 rounded-xl border-2 border-gray-200 focus:border-blue-500 focus:ring-2 focus:ring-blue-200 transition-all outline-none text-gray-700" className="w-full pl-12 pr-10 py-3 rounded-xl border border-slate-200/60 dark:border-slate-800 bg-white dark:bg-slate-950/40 focus:border-purple-500/80 focus:ring-4 focus:ring-purple-500/10 text-slate-800 dark:text-slate-200 transition-all outline-none text-sm font-semibold"
aria-label="Search connections" aria-label="Search connections"
/> />
{searchQuery && ( {searchQuery && (
<button <button
onClick={() => setSearchQuery("")} onClick={() => setSearchQuery("")}
className="absolute right-4 top-1/2 -translate-y-1/2 text-gray-400 hover:text-gray-600 transition-colors" className="absolute right-4 top-1/2 -translate-y-1/2 text-slate-400 hover:text-slate-600 transition-colors"
aria-label="Clear search" aria-label="Clear search"
> >
<svg className="w-5 h-5" fill="currentColor" viewBox="0 0 20 20"> <X className="w-4 h-4" />
<path fillRule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zM8.707 7.293a1 1 0 00-1.414 1.414L8.586 10l-1.293 1.293a1 1 0 101.414 1.414L10 11.414l1.293 1.293a1 1 0 001.414-1.414L11.414 10l1.293-1.293a1 1 0 00-1.414-1.414L10 8.586 8.707 7.293z" clipRule="evenodd" />
</svg>
</button> </button>
)} )}
</div> </div>
<div className="flex gap-2 bg-gray-100 rounded-xl p-1"> {/* View Mode Toggle */}
<div className="flex gap-1.5 bg-slate-100 dark:bg-slate-950/80 rounded-xl p-1 shrink-0 border border-slate-250/30 dark:border-slate-850/30">
<button <button
onClick={() => setViewMode("grid")} onClick={() => setViewMode("grid")}
className={`flex items-center gap-2 px-4 py-2 rounded-lg transition-all ${ className={`flex items-center gap-2 px-4 py-2 rounded-lg transition-all text-xs font-bold uppercase tracking-wider cursor-pointer active:scale-95 ${
viewMode === "grid" viewMode === "grid"
? "bg-white shadow-sm text-gray-900" ? "bg-white dark:bg-slate-900 shadow-md text-slate-900 dark:text-slate-100"
: "text-gray-600 hover:text-gray-900" : "text-slate-400 hover:text-slate-700 dark:hover:text-slate-200"
}`} }`}
aria-label="Grid view" aria-label="Grid view"
aria-pressed={viewMode === "grid"} aria-pressed={viewMode === "grid"}
> >
<svg className="w-5 h-5" fill="currentColor" viewBox="0 0 20 20"> <Grid className="w-3.5 h-3.5" />
<path d="M5 3a2 2 0 00-2 2v2a2 2 0 002 2h2a2 2 0 002-2V5a2 2 0 00-2-2H5zM5 11a2 2 0 00-2 2v2a2 2 0 002 2h2a2 2 0 002-2v-2a2 2 0 00-2-2H5zM11 5a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2h-2a2 2 0 01-2-2V5zM11 13a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2h-2a2 2 0 01-2-2v-2z" /> <span>Grade</span>
</svg>
<span className="hidden sm:inline font-medium">Grade</span>
</button> </button>
<button <button
onClick={() => setViewMode("list")} onClick={() => setViewMode("list")}
className={`flex items-center gap-2 px-4 py-2 rounded-lg transition-all ${ className={`flex items-center gap-2 px-4 py-2 rounded-lg transition-all text-xs font-bold uppercase tracking-wider cursor-pointer active:scale-95 ${
viewMode === "list" viewMode === "list"
? "bg-white shadow-sm text-gray-900" ? "bg-white dark:bg-slate-900 shadow-md text-slate-900 dark:text-slate-100"
: "text-gray-600 hover:text-gray-900" : "text-slate-400 hover:text-slate-700 dark:hover:text-slate-200"
}`} }`}
aria-label="List view" aria-label="List view"
aria-pressed={viewMode === "list"} aria-pressed={viewMode === "list"}
> >
<svg className="w-5 h-5" fill="currentColor" viewBox="0 0 20 20"> <List className="w-3.5 h-3.5" />
<path fillRule="evenodd" d="M3 4a1 1 0 011-1h12a1 1 0 110 2H4a1 1 0 01-1-1zm0 4a1 1 0 011-1h12a1 1 0 110 2H4a1 1 0 01-1-1zm0 4a1 1 0 011-1h12a1 1 0 110 2H4a1 1 0 01-1-1zm0 4a1 1 0 011-1h12a1 1 0 110 2H4a1 1 0 01-1-1z" clipRule="evenodd" /> <span>Lista</span>
</svg>
<span className="hidden sm:inline font-medium">Lista</span>
</button> </button>
</div> </div>
</div> </div>
</div> </div>
<div className="bg-white rounded-2xl shadow-sm border border-gray-200 p-6"> {/* Filters Panel */}
<div className="flex items-center gap-3 mb-4"> <div className="bg-white/60 dark:bg-slate-900/30 backdrop-blur-xl rounded-2xl border border-slate-200/60 dark:border-slate-800/80 p-5 shadow-xl">
<svg <div className="flex items-center gap-3 mb-5 border-b border-slate-150/40 dark:border-slate-850/45 pb-3">
className="w-5 h-5 text-gray-700" <Filter className="w-4 h-4 text-purple-500" />
fill="none" <h4 className="text-sm font-bold text-slate-800 dark:text-slate-100 select-none">
strokeLinecap="round" Filtre por Função do Pino
strokeLinejoin="round"
strokeWidth="2"
viewBox="0 0 24 24"
stroke="currentColor"
>
<path d="M3 4a1 1 0 011-1h16a1 1 0 011 1v2.586a1 1 0 01-.293.707l-6.414 6.414a1 1 0 00-.293.707V17l-4 4v-6.586a1 1 0 00-.293-.707L3.293 7.293A1 1 0 013 6.586V4z" />
</svg>
<h4 className="text-lg font-semibold text-gray-900">
Filtre por Função
</h4> </h4>
<span className="ml-auto text-sm font-medium text-gray-600 bg-gray-100 px-3 py-1 rounded-full"> <span className="ml-auto text-[10px] font-bold text-slate-400 dark:text-slate-500 bg-slate-100 dark:bg-slate-950 px-3 py-1 rounded-full border border-slate-200/30 dark:border-slate-850/30 shadow-xs select-none">
{visibleConnections.length} de {connections.length} {visibleConnections.length} de {connections.length} ativos
</span> </span>
</div> </div>
<div className="flex flex-wrap gap-2.5"> <div className="flex flex-wrap gap-2">
{FILTER_CATEGORIES.map((filter) => { {FILTER_CATEGORIES.map((filter) => {
const isActive = activeFilter === filter.id; const isActive = activeFilter === filter.id;
const count = connections.filter(c => const count = connections.filter(c =>
filter.id === "All" ? true : c.categorias.includes(filter.id) filter.id === "All" ? true : c.categorias.includes(filter.id)
).length; ).length;
if (count === 0 && !isActive) return null; // Hide filters that don't apply to this chip
return ( return (
<button <button
key={filter.id} key={filter.id}
onClick={() => setActiveFilter(filter.id)} onClick={() => setActiveFilter(filter.id)}
aria-pressed={isActive} aria-pressed={isActive}
aria-label={`Filter by ${filter.name}`} aria-label={`Filter by ${filter.name}`}
className={`group relative px-5 py-2.5 rounded-xl text-sm font-semibold transition-all duration-200 focus:outline-none focus:ring-2 focus:ring-offset-2 ${ className={`group relative px-4 py-2 rounded-xl text-xs font-extrabold transition-all duration-300 focus:outline-none cursor-pointer active:scale-95 ${
isActive isActive
? "shadow-lg transform scale-105 text-white" ? "shadow-lg text-white"
: "bg-gray-50 text-gray-700 hover:bg-gray-100 hover:shadow-md" : "bg-slate-50/70 dark:bg-slate-950/20 text-slate-650 dark:text-slate-350 hover:bg-slate-100/50 dark:hover:bg-slate-850/30 border border-slate-200/40 dark:border-slate-850/40"
}`} }`}
style={{ style={{
backgroundColor: isActive ? filter.color : undefined, backgroundColor: isActive ? filter.color : undefined,
borderColor: isActive ? filter.color : undefined, boxShadow: isActive ? `0 8px 15px -8px ${filter.color}` : undefined
}} }}
> >
<span className="flex items-center gap-2"> <span className="flex items-center gap-2">
{filter.name} <span>{filter.name}</span>
<span className={`text-xs px-1.5 py-0.5 rounded-full ${ <span className={`text-[9px] px-1.5 py-0.5 rounded-full shrink-0 font-bold ${
isActive ? "bg-white/20" : "bg-gray-200" isActive ? "bg-white/20 text-white" : "bg-slate-200/50 dark:bg-slate-800/80 text-slate-555 dark:text-slate-400"
}`}> }`}>
{count} {count}
</span> </span>
@@ -276,18 +267,19 @@ export default function ConnectionsDiagram({ connections, serie }) {
})} })}
</div> </div>
{/* Active Categories badges */}
{activeFilter !== "All" && activeCategories.length > 0 && ( {activeFilter !== "All" && activeCategories.length > 0 && (
<div className="mt-6 pt-6 border-t border-gray-200"> <div className="mt-5 pt-5 border-t border-slate-100 dark:border-slate-850/40 select-none">
<p className="text-sm font-medium text-gray-600 mb-3">Categorias visíveis:</p> <p className="text-[10px] font-bold text-slate-400 dark:text-slate-500 mb-3 uppercase tracking-wider">Subcategorias ativadas por esse filtro:</p>
<div className="flex flex-wrap gap-2"> <div className="flex flex-wrap gap-2">
{activeCategories.map(cat => ( {activeCategories.map(cat => (
<span <span
key={cat.id} key={cat.id}
className="inline-flex items-center gap-1.5 px-3 py-1 rounded-lg text-xs font-medium text-white shadow-sm" className="inline-flex items-center gap-1.5 px-3 py-1 rounded-lg text-[10px] font-bold text-white shadow-xs"
style={{ backgroundColor: cat.color }} style={{ backgroundColor: cat.color }}
> >
<span className="w-1.5 h-1.5 rounded-full bg-white"></span> <span className="w-1.5 h-1.5 rounded-full bg-white shrink-0"></span>
{cat.name} <span>{cat.name}</span>
</span> </span>
))} ))}
</div> </div>
@@ -295,31 +287,33 @@ export default function ConnectionsDiagram({ connections, serie }) {
)} )}
</div> </div>
{/* Diagram Workspace */}
{visibleConnections.length === 0 ? ( {visibleConnections.length === 0 ? (
<div className="bg-linear-to-br from-gray-50 to-gray-100 rounded-2xl p-12 border-2 border-dashed border-gray-300 text-center"> <div className="bg-slate-50/50 dark:bg-slate-900/10 backdrop-blur-xl rounded-2xl p-12 border-2 border-dashed border-slate-200 dark:border-slate-850/80 text-center shadow-inner">
<div className="w-16 h-16 mx-auto mb-4 rounded-full bg-gray-200 flex items-center justify-center"> <div className="w-16 h-16 mx-auto mb-4 rounded-full bg-slate-100 dark:bg-slate-950 flex items-center justify-center shadow-md">
<svg className="w-8 h-8 text-gray-400" fill="none" strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" viewBox="0 0 24 24" stroke="currentColor"> <Search className="w-6 h-6 text-slate-400" />
<path d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z" />
</svg>
</div> </div>
<p className="text-lg font-semibold text-gray-700 mb-2">Nenhuma conexão encontrada</p> <p className="text-base font-bold text-slate-800 dark:text-slate-200 mb-1 select-none">Nenhuma conexão encontrada</p>
<p className="text-sm text-gray-500">Tente ajustar os filtros ou termo de busca</p> <p className="text-xs text-slate-500 dark:text-slate-400">Tente ajustar seus termos de pesquisa ou limpe os filtros rápidos.</p>
<button <button
onClick={() => { onClick={() => {
setActiveFilter("All"); setActiveFilter("All");
setSearchQuery(""); setSearchQuery("");
}} }}
className="mt-4 px-6 py-2 bg-gray-900 text-white rounded-xl hover:bg-gray-800 transition-colors font-medium" className="mt-5 px-6 py-2.5 bg-slate-900 dark:bg-slate-100 text-white dark:text-slate-950 rounded-xl hover:opacity-90 transition-all font-bold text-xs uppercase tracking-wider cursor-pointer active:scale-95 shadow-lg"
> >
Limpar filtros Limpar Filtros e Busca
</button> </button>
</div> </div>
) : viewMode === "grid" ? ( ) : viewMode === "grid" ? (
<div className="bg-linear-to-br from-gray-50 to-gray-100 rounded-2xl p-8 border-2 border-gray-200 shadow-inner"> /* Grid View Mode: Futuristic Electronic Chip Sockets */
<div className="grid grid-cols-2 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-6 xl:grid-cols-8 gap-4"> <div className="bg-slate-100/50 dark:bg-slate-950/20 backdrop-blur-xl rounded-3xl p-6 md:p-8 border border-slate-200/50 dark:border-slate-850/60 shadow-inner">
<div className="grid grid-cols-2 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-5 xl:grid-cols-6 gap-4">
{visibleConnections.map((connection, index) => { {visibleConnections.map((connection, index) => {
const isHovered = hoveredConnection === index; const isHovered = hoveredConnection === index;
const connectionId = `connection-${index}`; const connectionId = `connection-${index}`;
const baseColor = type_colors[connection.tipo] || "#94a3b8";
return ( return (
<div key={index} className="relative"> <div key={index} className="relative">
@@ -337,90 +331,85 @@ export default function ConnectionsDiagram({ connections, serie }) {
}} }}
onFocus={() => setHoveredConnection(index)} onFocus={() => setHoveredConnection(index)}
onBlur={() => setHoveredConnection(null)} onBlur={() => setHoveredConnection(null)}
aria-label={`Connection ${connection.nome}, number ${connection.numero}`} aria-label={`Pino ${connection.nome}, número ${connection.numero}`}
aria-describedby={showTooltip === index ? `tooltip-${index}` : undefined} aria-describedby={showTooltip === index ? `tooltip-${index}` : undefined}
className="relative w-full flex flex-col items-center justify-center p-3 rounded-2xl border-2 transition-all duration-300 opacity-100 hover:scale-110 hover:shadow-2xl hover:z-10 cursor-pointer active:scale-95 focus:outline-none focus:border-4" className="w-full flex flex-col items-center justify-center p-4 rounded-2xl border-2 transition-all duration-300 cursor-pointer active:scale-95 focus:outline-none"
style={{ style={{
backgroundColor: type_colors[connection.tipo] + (isHovered ? "20" : "10"), backgroundColor: isHovered ? `${baseColor}24` : `${baseColor}0c`,
borderColor: isHovered ? type_colors[connection.tipo] : type_colors[connection.tipo] + "60", borderColor: isHovered ? baseColor : `${baseColor}40`,
transform: isHovered ? "translateY(-2px)" : undefined, boxShadow: isHovered ? `0 0 20px -5px ${baseColor}60` : 'none',
focusRingColor: type_colors[connection.tipo], transform: isHovered ? "translateY(-4px)" : undefined,
}} }}
> >
{/* Pad Contact Core Circle */}
<div <div
className="w-14 h-14 rounded-full flex items-center justify-center text-white font-bold text-sm mb-3 shadow-lg" className="w-12 h-12 rounded-full flex items-center justify-center text-white font-extrabold text-xs mb-3 shadow-lg select-none"
style={{ backgroundColor: type_colors[connection.tipo] }} style={{ backgroundColor: baseColor, boxShadow: `0 4px 10px -3px ${baseColor}` }}
> >
{typeof connection.numero === "number" {typeof connection.numero === "number"
? connection.numero ? connection.numero
: connection.numero.substring(0, 4)} : connection.numero.substring(0, 4)}
</div> </div>
<div className="text-xs font-bold text-center text-gray-900 leading-tight mb-2"> {/* Pad Name label */}
<div className="text-xs font-bold text-center text-slate-850 dark:text-slate-200 leading-tight mb-2 select-none group-hover:text-purple-600 transition-colors">
{connection.nome} {connection.nome}
</div> </div>
<div className="flex flex-wrap gap-1 justify-center leading-2"> {/* Small Category dots indicator */}
<div className="flex flex-wrap gap-1 justify-center select-none">
{connection.categorias.slice(0, 5).map((cat) => ( {connection.categorias.slice(0, 5).map((cat) => (
<span <span
key={cat} key={cat}
className="w-1.5 h-1.5 my-auto rounded-full" className="w-1.5 h-1.5 rounded-full shrink-0"
style={{ backgroundColor: getCategoryColor(cat) }} style={{ backgroundColor: getCategoryColor(cat) }}
title={cat} title={cat === "Strapping" ? "Inicialização" : cat}
/> />
))} ))}
{connection.categorias.length > 5 && ( {connection.categorias.length > 5 && (
<span className="text-[10px] text-gray-500 font-medium leading-2"> <span className="text-[9px] text-slate-400 dark:text-slate-500 font-bold ml-0.5 leading-none">
+{connection.categorias.length - 5} +{connection.categorias.length - 5}
</span> </span>
)} )}
</div> </div>
{/* High-Voltage warnings tags */}
{connection.avisos && ( {connection.avisos && (
<div <div
className="absolute -top-2 -right-2 bg-red-500 rounded-full p-1.5 shadow-lg ring-2 ring-white" className="absolute -top-2 -right-2 bg-red-500 text-white rounded-full p-1 shadow-lg ring-2 ring-white dark:ring-slate-950 animate-pulse"
aria-label="Has restrictions" aria-label="Possui restrições de uso"
> >
<svg <AlertTriangle className="w-3 h-3" />
className="w-3.5 h-3.5 text-white"
fill="none"
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="2"
viewBox="0 0 24 24"
stroke="currentColor"
>
<path d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z" />
</svg>
</div> </div>
)} )}
</button> </button>
{/* Sleek Tooltip Popover */}
{showTooltip === index && ( {showTooltip === index && (
<div <div
id={`tooltip-${index}`} id={`tooltip-${index}`}
role="tooltip" role="tooltip"
className="absolute z-50 bottom-full left-1/2 -translate-x-1/2 mb-2 p-3 bg-gray-900 text-white text-xs rounded-lg shadow-xl max-w-xs pointer-events-none animate-fadeIn" className="absolute z-40 bottom-full left-1/2 -translate-x-1/2 mb-3 p-3 bg-slate-950/90 dark:bg-slate-900/90 backdrop-blur-md text-white text-[10px] rounded-xl shadow-2xl max-w-xs pointer-events-none animate-fadeIn border border-slate-850/50"
style={{ minWidth: "200px" }} style={{ minWidth: "180px" }}
> >
<div className="font-bold mb-1">{connection.nome}</div> <div className="font-extrabold text-sm mb-1">{connection.nome}</div>
<div className="text-gray-300 mb-2">Pino {connection.numero}</div> <div className="text-slate-400 mb-2 font-semibold">Pino Físico {connection.numero}</div>
<div className="flex flex-wrap gap-1 mb-2"> <div className="flex flex-wrap gap-1 mb-2">
{connection.categorias.map((cat) => ( {connection.categorias.map((cat) => (
<span <span
key={cat} key={cat}
className="px-1.5 py-0.5 rounded text-[10px] font-medium" className="px-1.5 py-0.5 rounded text-[8px] font-bold"
style={{ style={{
backgroundColor: getCategoryColor(cat) + "40", backgroundColor: `${getCategoryColor(cat)}24`,
color: "#fff" color: getCategoryColor(cat)
}} }}
> >
{cat === "Strapping" ? "Inicialização" : cat} {cat === "Strapping" ? "Inicialização" : cat}
</span> </span>
))} ))}
</div> </div>
<div className="text-gray-400 text-[10px]">Clique para detalhes</div> <div className="text-purple-400 font-bold text-[9px] uppercase tracking-wider">Clique para detalhes</div>
<div className="absolute top-full left-1/2 -translate-x-1/2 -mt-1 border-4 border-transparent border-t-gray-900"></div> <div className="absolute top-full left-1/2 -translate-x-1/2 -mt-1 border-4 border-transparent border-t-slate-950/95 dark:border-t-slate-900/95"></div>
</div> </div>
)} )}
</div> </div>
@@ -429,46 +418,51 @@ export default function ConnectionsDiagram({ connections, serie }) {
</div> </div>
</div> </div>
) : ( ) : (
<div className="bg-white rounded-2xl shadow-sm border border-gray-200 overflow-hidden"> /* List View Mode: Interactive Zebra Table */
<div className="overflow-x-auto"> <div className="bg-white/80 dark:bg-slate-900/40 backdrop-blur-xl rounded-2xl border border-slate-200/60 dark:border-slate-800/80 overflow-hidden shadow-xl">
<table className="w-full"> <div className="overflow-x-auto scrollbar-thin">
<thead className="bg-linear-to-r from-gray-50 to-gray-100 border-b-2 border-gray-200"> <table className="w-full text-left">
<tr> <thead>
<th className="px-6 py-4 text-left text-xs font-bold text-gray-700 uppercase tracking-wider">Pin</th> <tr className="bg-slate-100/50 dark:bg-slate-950/50 border-b border-slate-200/60 dark:border-slate-850/60 select-none">
<th className="px-6 py-4 text-left text-xs font-bold text-gray-700 uppercase tracking-wider">Nome</th> <th className="px-6 py-4 text-xs font-bold text-slate-500 dark:text-slate-400 uppercase tracking-widest">Pino</th>
<th className="px-6 py-4 text-left text-xs font-bold text-gray-700 uppercase tracking-wider">Categorias</th> <th className="px-6 py-4 text-xs font-bold text-slate-500 dark:text-slate-400 uppercase tracking-widest">Nome do Pad</th>
<th className="px-6 py-4 text-left text-xs font-bold text-gray-700 uppercase tracking-wider">Funções</th> <th className="px-6 py-4 text-xs font-bold text-slate-500 dark:text-slate-400 uppercase tracking-widest">Categorias</th>
<th className="px-6 py-4 text-center text-xs font-bold text-gray-700 uppercase tracking-wider">Avisos</th> <th className="px-6 py-4 text-xs font-bold text-slate-500 dark:text-slate-400 uppercase tracking-widest">Multiplexação de Funções</th>
<th className="px-6 py-4 text-center text-xs font-bold text-slate-500 dark:text-slate-400 uppercase tracking-widest">Restrições</th>
</tr> </tr>
</thead> </thead>
<tbody className="divide-y divide-gray-200"> <tbody className="divide-y divide-slate-100 dark:divide-slate-850/30">
{visibleConnections.map((connection, index) => ( {visibleConnections.map((connection, index) => {
const baseColor = type_colors[connection.tipo] || "#94a3b8";
return (
<tr <tr
key={index} key={index}
onClick={() => setSelectedConnection(connection)} onClick={() => setSelectedConnection(connection)}
className="hover:bg-gray-50 cursor-pointer transition-colors group" className="hover:bg-purple-500/5 dark:hover:bg-purple-400/5 cursor-pointer transition-colors group"
> >
<td className="px-6 py-4 whitespace-nowrap"> <td className="px-6 py-4 whitespace-nowrap">
<div <div
className="w-10 h-10 rounded-full flex items-center justify-center text-white font-bold text-sm shadow-md" className="w-10 h-10 rounded-full flex items-center justify-center text-white font-extrabold text-xs shadow-md select-none"
style={{ backgroundColor: type_colors[connection.tipo] }} style={{ backgroundColor: baseColor }}
> >
{typeof connection.numero === "number" {typeof connection.numero === "number"
? connection.numero ? connection.numero
: connection.numero.substring(0, 4)} : connection.numero.substring(0, 4)}
</div> </div>
</td> </td>
<td className="px-6 py-4 whitespace-nowrap"> <td className="px-6 py-4 whitespace-nowrap">
<div className="text-sm font-bold text-gray-900 group-hover:text-blue-600 transition-colors"> <div className="text-sm font-bold text-slate-800 dark:text-slate-200 group-hover:text-purple-600 dark:group-hover:text-purple-400 transition-colors">
{connection.nome} {connection.nome}
</div> </div>
</td> </td>
<td className="px-6 py-4"> <td className="px-6 py-4">
<div className="flex flex-wrap gap-1.5"> <div className="flex flex-wrap gap-1.5 select-none">
{connection.categorias.map((cat) => ( {connection.categorias.map((cat) => (
<span <span
key={cat} key={cat}
className="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium text-white" className="inline-flex items-center px-2 py-0.5 rounded-md text-[9px] font-bold text-white shadow-xs"
style={{ backgroundColor: getCategoryColor(cat) }} style={{ backgroundColor: getCategoryColor(cat) }}
> >
{cat === "Strapping" ? "Inicialização" : cat} {cat === "Strapping" ? "Inicialização" : cat}
@@ -476,170 +470,144 @@ export default function ConnectionsDiagram({ connections, serie }) {
))} ))}
</div> </div>
</td> </td>
<td className="px-6 py-4"> <td className="px-6 py-4">
<div className="text-sm text-gray-600 max-w-md"> <div className="text-xs text-slate-500 dark:text-slate-400 font-semibold max-w-md line-clamp-2 leading-relaxed">
{connection.funcoes.join(", ")} {connection.funcoes.join(", ")}
</div> </div>
</td> </td>
<td className="px-6 py-4 text-center">
<td className="px-6 py-4 text-center whitespace-nowrap">
{connection.avisos ? ( {connection.avisos ? (
<span className="inline-flex items-center gap-1 px-2 py-1 rounded-full bg-red-100 text-red-700 text-xs font-medium"> <span className="inline-flex items-center gap-1.5 px-2.5 py-1 rounded-full bg-red-500/10 text-red-600 dark:text-red-400 text-[10px] font-bold uppercase border border-red-500/20 select-none">
<svg className="w-3.5 h-3.5" fill="currentColor" viewBox="0 0 20 20"> <AlertTriangle className="w-3.5 h-3.5 shrink-0" />
<path fillRule="evenodd" d="M8.257 3.099c.765-1.36 2.722-1.36 3.486 0l5.58 9.92c.75 1.334-.213 2.98-1.742 2.98H4.42c-1.53 0-2.493-1.646-1.743-2.98l5.58-9.92zM11 13a1 1 0 11-2 0 1 1 0 012 0zm-1-8a1 1 0 00-1 1v3a1 1 0 002 0V6a1 1 0 00-1-1z" clipRule="evenodd" /> <span>Restrito</span>
</svg>
Sim
</span> </span>
) : ( ) : (
<span className="inline-flex items-center gap-1 px-2 py-1 rounded-full bg-green-100 text-green-700 text-xs font-medium"> <span className="inline-flex items-center gap-1.5 px-2.5 py-1 rounded-full bg-emerald-500/10 text-emerald-600 dark:text-emerald-400 text-[10px] font-bold uppercase border border-emerald-500/20 select-none">
<svg className="w-3.5 h-3.5" fill="currentColor" viewBox="0 0 20 20"> <CheckCircle className="w-3.5 h-3.5 shrink-0" />
<path fillRule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z" clipRule="evenodd" /> <span>Livre</span>
</svg>
Não
</span> </span>
)} )}
</td> </td>
</tr> </tr>
))} );
})}
</tbody> </tbody>
</table> </table>
</div> </div>
</div> </div>
)} )}
{/* Global Warning notice */}
{connections.some(c => c.avisos) && ( {connections.some(c => c.avisos) && (
<div className="bg-linear-to-r from-amber-50 to-yellow-50 border-2 border-amber-200 rounded-2xl p-6 shadow-sm"> <div className="bg-amber-500/10 text-amber-600 dark:text-amber-400 border border-amber-500/20 rounded-2xl p-6 shadow-sm select-none">
<div className="flex gap-4"> <div className="flex gap-4">
<div className="shrink-0"> <div className="shrink-0">
<div className="w-12 h-12 rounded-full bg-amber-100 flex items-center justify-center"> <div className="w-12 h-12 rounded-xl bg-amber-500/10 border border-amber-500/20 flex items-center justify-center">
<svg <AlertTriangle className="w-6 h-6" />
className="w-6 h-6 text-amber-600"
fill="none"
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="2"
viewBox="0 0 24 24"
stroke="currentColor"
>
<path d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z" />
</svg>
</div> </div>
</div> </div>
<div className="flex-1"> <div className="flex-1">
<p className="text-lg font-bold text-amber-900 mb-3"> <p className="text-base font-bold text-amber-850 dark:text-amber-300 mb-1">
Informações Importantes Notas Importantes sobre a Pinagem
</p> </p>
<ul className="space-y-2.5 text-sm text-amber-800"> <ul className="space-y-1.5 text-xs text-amber-800 dark:text-amber-400 leading-relaxed">
<li className="flex items-start gap-3"> <li>• Pinos contendo tags de <strong>Avisos/Restrições</strong> (cor vermelha) possuem papéis especiais de Strapping na inicialização, ou limitações analógicas. Clique neles para ver detalhes.</li>
<span className="text-amber-600 mt-0.5 font-bold">•</span> <li>• Sempre garanta isolamento de circuitos capacitivos de toque em pinos com ruído de RF acoplado.</li>
<span className="leading-relaxed">
Conexões com ícones de aviso possuem restrições de uso. Clique para mais detalhes.
</span>
</li>
<li className="flex items-start gap-3">
<span className="text-amber-600 mt-0.5 font-bold">•</span>
<span className="leading-relaxed">
Sempre consulte o datasheet oficial antes de usar conexões com funções especiais.
</span>
</li>
</ul> </ul>
</div> </div>
</div> </div>
</div> </div>
)} )}
{selectedConnection && ( {/* Interactive Overlay Dialog Details Sheet */}
{selectedConnection && mounted && createPortal(
<div <div
className="fixed inset-0 bg-opacity-80 backdrop-blur-xs flex items-center justify-center p-4 z-50 animate-fadeIn" className="fixed inset-0 bg-slate-950/80 backdrop-blur-sm flex items-center justify-center p-4 z-50 animate-fadeIn"
onClick={() => setSelectedConnection(null)} onClick={() => setSelectedConnection(null)}
role="dialog" role="dialog"
aria-modal="true" aria-modal="true"
aria-labelledby="modal-title" aria-labelledby="modal-title"
> >
<div <div
className="bg-white rounded-3xl shadow-2xl max-w-3xl w-full max-h-[90vh] overflow-hidden flex flex-col animate-slideUp" className="bg-white/95 dark:bg-slate-950/95 backdrop-blur-xl border border-slate-200/50 dark:border-slate-800/80 rounded-3xl shadow-2xl max-w-2xl w-full max-h-[85vh] overflow-hidden flex flex-col animate-slideUp"
onClick={(e) => e.stopPropagation()} onClick={(e) => e.stopPropagation()}
> >
<div className="sticky top-0 bg-linear-to-r from-white to-gray-50 border-b-2 border-gray-200 p-8 z-10"> {/* Dialog Header */}
<div className="sticky top-0 bg-white/50 dark:bg-slate-950/50 border-b border-slate-200/60 dark:border-slate-850/60 p-6 md:p-8 z-10">
<div className="flex items-start justify-between gap-6"> <div className="flex items-start justify-between gap-6">
<div className="flex items-center gap-5 flex-1"> <div className="flex items-center gap-4 flex-1">
<div <div
className="w-16 h-16 rounded-2xl flex items-center justify-center text-white font-bold text-xl shadow-xl ring-4 ring-white shrink-0" className="w-14 h-14 rounded-2xl flex items-center justify-center text-white font-extrabold text-lg shadow-xl shrink-0"
style={{ backgroundColor: type_colors[selectedConnection.tipo] }} style={{ backgroundColor: type_colors[selectedConnection.tipo] || "#64748b" }}
> >
{typeof selectedConnection.numero === "number" {typeof selectedConnection.numero === "number"
? selectedConnection.numero ? selectedConnection.numero
: selectedConnection.numero.substring(0, 4)} : selectedConnection.numero.substring(0, 4)}
</div> </div>
<div className="min-w-0"> <div className="min-w-0">
<h3 id="modal-title" className="text-3xl font-bold text-gray-900 mb-1"> <h3 id="modal-title" className="text-2xl font-display font-extrabold text-slate-850 dark:text-slate-100 mb-1 leading-none tracking-tight">
{selectedConnection.nome} {selectedConnection.nome}
</h3> </h3>
<p className="text-base text-gray-600"> <p className="text-xs text-slate-400 dark:text-slate-500 font-bold uppercase tracking-wider">
Pino {selectedConnection.numero} Pino Físico {selectedConnection.numero}
</p> </p>
</div> </div>
</div> </div>
<button <button
onClick={() => setSelectedConnection(null)} onClick={() => setSelectedConnection(null)}
aria-label="Close modal" aria-label="Fechar"
className="text-gray-400 hover:text-gray-600 transition-colors p-2 hover:bg-gray-100 rounded-xl shrink-0" className="text-slate-400 hover:text-slate-600 dark:hover:text-slate-200 transition-colors p-2 bg-slate-100 dark:bg-slate-900 rounded-xl shrink-0 cursor-pointer active:scale-95"
> >
<svg <X className="w-5 h-5" />
className="w-7 h-7"
fill="none"
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="2"
viewBox="0 0 24 24"
stroke="currentColor"
>
<path d="M6 18L18 6M6 6l12 12" />
</svg>
</button> </button>
</div> </div>
</div> </div>
<div className="flex-1 overflow-y-auto p-8 space-y-8"> {/* Dialog Body Contents */}
{selectedConnection.categorias.length > 0 && (<div> <div className="flex-1 overflow-y-auto p-6 md:p-8 space-y-8 scrollbar-thin">
<h4 className="text-lg font-bold text-gray-900 mb-4 flex items-center gap-2">
<svg className="w-5 h-5" fill="currentColor" viewBox="0 0 20 20"> {/* Categories */}
<path d="M7 3a1 1 0 000 2h6a1 1 0 100-2H7zM4 7a1 1 0 011-1h10a1 1 0 110 2H5a1 1 0 01-1-1zM2 11a2 2 0 012-2h12a2 2 0 012 2v4a2 2 0 01-2 2H4a2 2 0 01-2-2v-4z" /> {selectedConnection.categorias.length > 0 && (
</svg> <div>
Categorias <h4 className="text-xs font-bold text-slate-400 dark:text-slate-500 uppercase tracking-widest mb-3 flex items-center gap-2 select-none">
<Tag className="w-4 h-4 text-purple-500" />
<span>Categorias de Multiplexação</span>
</h4> </h4>
<div className="flex flex-wrap gap-2.5"> <div className="flex flex-wrap gap-2">
{selectedConnection.categorias.map((cat) => ( {selectedConnection.categorias.map((cat) => (
<span <span
key={cat} key={cat}
className="px-4 py-2 rounded-xl text-sm font-bold text-white shadow-md" className="px-3 py-1.5 rounded-xl text-xs font-bold text-white shadow-md select-none"
style={{ backgroundColor: getCategoryColor(cat) }} style={{ backgroundColor: getCategoryColor(cat) }}
> >
{cat === "Strapping" ? "Inicialização" : cat} {cat === "Strapping" ? "Inicialização" : cat}
</span> </span>
))} ))}
</div> </div>
</div>)} </div>
)}
{/* Power Source */}
{selectedConnection.alimentacao && ( {selectedConnection.alimentacao && (
<div> <div>
<h4 className="text-lg font-bold text-gray-900 mb-4 flex items-center gap-2"> <h4 className="text-xs font-bold text-slate-400 dark:text-slate-500 uppercase tracking-widest mb-3 flex items-center gap-2 select-none">
<svg className="w-5 h-5" fill="currentColor" viewBox="0 0 20 20"> <Zap className="w-4 h-4 text-red-500" />
<path fillRule="evenodd" d="M11.3 1.046A1 1 0 0112 2v5h4a1 1 0 01.82 1.573l-7 10A1 1 0 018 18v-5H4a1 1 0 01-.82-1.573l7-10a1 1 0 011.12-.38z" clipRule="evenodd" /> <span>Domínio de Alimentação Interna</span>
</svg>
Alimentado por:
</h4> </h4>
<div className="bg-linear-to-br from-blue-50 to-indigo-50 rounded-2xl p-6 border border-blue-200"> <div className="bg-slate-50 dark:bg-slate-900/30 rounded-2xl p-5 border border-slate-200/50 dark:border-slate-850/50">
<ul className="space-y-3"> <ul className="space-y-2.5">
{selectedConnection.alimentacao.map((source, index) => ( {selectedConnection.alimentacao.map((source, index) => (
<li key={index} className="flex items-start gap-4 text-sm group"> <li key={index} className="flex items-center gap-3 text-xs font-bold text-slate-700 dark:text-slate-300">
<span <span
className="mt-1.5 shrink-0 w-2 h-2 rounded-full" className="shrink-0 w-2 h-2 rounded-full"
style={{ backgroundColor: type_colors["power"] }} style={{ backgroundColor: type_colors["power"] }}
/> />
<span className="text-gray-800 font-medium leading-relaxed">{source}</span> <span>{source}</span>
</li> </li>
))} ))}
</ul> </ul>
@@ -647,57 +615,45 @@ export default function ConnectionsDiagram({ connections, serie }) {
</div> </div>
)} )}
{/* Function Multiplexation */}
<div> <div>
<h4 className="text-lg font-bold text-gray-900 mb-4 flex items-center gap-2"> <h4 className="text-xs font-bold text-slate-400 dark:text-slate-500 uppercase tracking-widest mb-3 flex items-center gap-2 select-none">
<svg className="w-5 h-5" fill="currentColor" viewBox="0 0 20 20"> <Sliders className="w-4 h-4 text-emerald-500" />
<path d="M13 7H7v6h6V7zM5 3a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2V5a2 2 0 00-2-2H5z" /> <span>Barramentos de Funções Disponíveis</span>
</svg>
Funções da Conexão
</h4> </h4>
<div className="bg-linear-to-br from-gray-50 to-gray-100 rounded-2xl p-6 border border-gray-200"> <div className="bg-slate-50 dark:bg-slate-900/30 rounded-2xl p-5 border border-slate-200/50 dark:border-slate-850/50">
<ul className="space-y-3"> <ul className="grid grid-cols-1 sm:grid-cols-2 gap-3">
{selectedConnection.funcoes.map((func, index) => ( {selectedConnection.funcoes.map((func, index) => (
<li key={index} className="flex items-start gap-4 text-sm group"> <li key={index} className="flex items-center gap-3 text-xs font-bold text-slate-700 dark:text-slate-300">
<span <span
className="mt-1.5 shrink-0 w-2 h-2 rounded-full" className="shrink-0 w-2 h-2 rounded-full"
style={{ backgroundColor: type_colors[selectedConnection.tipo] }} style={{ backgroundColor: type_colors[selectedConnection.tipo] || "#64748b" }}
/> />
<span className="text-gray-800 font-medium leading-relaxed">{func}</span> <span>{func}</span>
</li> </li>
))} ))}
</ul> </ul>
</div> </div>
</div> </div>
{/* Alerts/Warnings */}
{selectedConnection.avisos && ( {selectedConnection.avisos && (
<div className="bg-linear-to-br from-red-50 to-rose-50 border-2 border-red-200 rounded-2xl p-6 shadow-sm"> <div className="bg-red-500/10 border border-red-500/20 rounded-2xl p-5 shadow-xs">
<div className="flex gap-4"> <div className="flex gap-4">
<div className="shrink-0"> <div className="shrink-0">
<div className="w-12 h-12 rounded-full bg-red-100 flex items-center justify-center"> <div className="w-10 h-10 rounded-xl bg-red-500/15 border border-red-500/25 flex items-center justify-center text-red-500">
<svg <AlertTriangle className="w-5 h-5" />
className="w-6 h-6 text-red-600"
fill="none"
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="2"
viewBox="0 0 24 24"
stroke="currentColor"
>
<path d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z" />
</svg>
</div> </div>
</div> </div>
<div className="flex-1"> <div className="flex-1">
<p className="text-lg font-bold text-red-900 mb-3"> <p className="text-sm font-bold text-red-900 dark:text-red-300 mb-2">
Avisos Importantes Avisos Importantes de Engenharia
</p> </p>
{selectedConnection.avisos.map((aviso, index) => ( {selectedConnection.avisos.map((aviso, index) => (
<div key={index} className="flex items-start gap-3 mb-2"> <div key={index} className="flex items-start gap-2.5 mb-1.5 text-xs text-red-800 dark:text-red-400 font-semibold leading-relaxed">
<span className="text-red-600 font-bold">•</span> <span>•</span>
<span className="text-sm text-red-800 leading-relaxed"> <span>{aviso}</span>
{aviso}
</span>
</div> </div>
))} ))}
</div> </div>
@@ -706,18 +662,23 @@ export default function ConnectionsDiagram({ connections, serie }) {
)} )}
</div> </div>
<div className="sticky bottom-0 bg-linear-to-t from-gray-50 to-white border-t-2 border-gray-200 p-6"> {/* Dialog Footer */}
<div className="sticky bottom-0 bg-white/50 dark:bg-slate-950/50 border-t border-slate-200/60 dark:border-slate-850/60 p-5">
<button <button
onClick={() => setSelectedConnection(null)} onClick={() => setSelectedConnection(null)}
className="w-full py-4 rounded-2xl font-bold text-white shadow-lg hover:shadow-xl transform hover:-translate-y-0.5 active:translate-y-0 transition-all duration-200 focus:outline-none focus:ring-4 focus:ring-offset-2" className="w-full py-3.5 rounded-xl font-bold text-white text-xs uppercase tracking-wider shadow-lg hover:opacity-90 active:scale-98 transition-all cursor-pointer"
style={{ backgroundColor: serie.color }} style={{
backgroundColor: serie.color,
boxShadow: `0 8px 25px -8px ${serie.color}`
}}
aria-label="Close modal" aria-label="Close modal"
> >
Fechar Confirmar e Fechar
</button> </button>
</div> </div>
</div> </div>
</div> </div>,
document.body
)} )}
<style jsx>{` <style jsx>{`
@@ -748,30 +709,6 @@ export default function ConnectionsDiagram({ connections, serie }) {
.animate-slideUp { .animate-slideUp {
animation: slideUp 0.3s ease-out; animation: slideUp 0.3s ease-out;
} }
/* Smooth scroll for modal */
.overflow-y-auto {
scrollbar-width: thin;
scrollbar-color: #cbd5e0 #f7fafc;
}
.overflow-y-auto::-webkit-scrollbar {
width: 8px;
}
.overflow-y-auto::-webkit-scrollbar-track {
background: #f7fafc;
border-radius: 10px;
}
.overflow-y-auto::-webkit-scrollbar-thumb {
background: #cbd5e0;
border-radius: 10px;
}
.overflow-y-auto::-webkit-scrollbar-thumb:hover {
background: #a0aec0;
}
`}</style> `}</style>
</div> </div>
); );
+60 -36
View File
@@ -1,46 +1,61 @@
"use client"; "use client";
import { useEffect, useState } from "react";
import { ExternalLink } from "lucide-react";
export default function Footer() { export default function Footer() {
const year = new Date().getFullYear(); const [year, setYear] = useState(2026);
useEffect(() => {
setYear(new Date().getFullYear());
}, []);
return ( return (
<footer role="contentinfo" aria-label="Rodapé do site ESPDocs" className="bg-linear-to-r from-blue-600 to-purple-600 text-white mt-12"> <footer role="contentinfo" aria-label="Rodapé do site ESPDocs" className="bg-slate-50 dark:bg-slate-950 text-slate-600 dark:text-slate-400 mt-24 border-t border-slate-250/30 dark:border-slate-800/80 transition-all duration-300">
<div className="max-w-6xl mx-auto px-6 pt-8 pb-4 rounded-t-2xl shadow-inner border-t border-white/10"> <div className="max-w-7xl mx-auto px-6 py-12 md:py-16">
<div className="grid grid-cols-1 md:grid-cols-3 gap-6"> <div className="grid grid-cols-1 md:grid-cols-3 gap-10 md:gap-16">
<div className="space-y-3">
<h3 className="text-2xl font-extrabold tracking-tight">ESPDocs</h3> {/* Coluna 1: Logo & Info */}
<p className="text-sm text-white/90 max-w-sm"> <div className="space-y-4">
Documentação não oficial em português para ESP32 — focada em clareza, exemplos práticos e comunidade. <h3 className="text-2xl font-display font-bold text-slate-800 dark:text-slate-100">
ESPDocs
</h3>
<p className="text-sm leading-relaxed text-slate-500 dark:text-slate-400">
Plataforma brasileira e independente que centraliza e organiza informações técnicas sobre o ecossistema ESP32. Focada em clareza, usabilidade e agilidade no desenvolvimento de hardware.
</p> </p>
<div className=""> <div className="pt-2">
<a <a
href="https://github.com/lucasrguerra/ESPDocs" href="https://github.com/lucasrguerra/ESPDocs"
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"
aria-label="Ver repositório do ESPDocs no GitHub (abre em nova aba)" aria-label="Ver repositório do ESPDocs no GitHub (abre em nova aba)"
className="inline-flex items-center gap-2 bg-white/10 hover:bg-white/20 px-3 py-2 rounded-md text-sm font-semibold focus:outline-none focus:ring-2 focus:ring-white/30 transition" className="inline-flex items-center gap-2 bg-slate-200/60 dark:bg-slate-900 hover:bg-slate-200 dark:hover:bg-slate-800 text-slate-700 dark:text-slate-200 px-4 py-2.5 rounded-xl text-sm font-semibold transition-all cursor-pointer hover:shadow-xs active:scale-[0.98]"
> >
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" aria-hidden="true" className="opacity-90"> <svg className="w-4 h-4" viewBox="0 0 24 24" fill="none" aria-hidden="true">
<path d="M12 .5a12 12 0 00-3.8 23.4c.6.1.8-.3.8-.6v-2.1c-3.3.7-4-1.6-4-1.6-.5-1.2-1.2-1.5-1.2-1.5-1-.7.1-.7.1-.7 1.1.1 1.7 1.1 1.7 1.1 1 .1 1.6-.7 1.9-1 .1-.8.4-1.3.8-1.6-2.6-.3-5.3-1.3-5.3-5.8 0-1.3.5-2.3 1.2-3.1-.1-.3-.5-1.6.1-3.3 0 0 1-.3 3.3 1.2a11.4 11.4 0 016 0c2.3-1.5 3.3-1.2 3.3-1.2.6 1.7.2 3 .1 3.3.8.8 1.2 1.9 1.2 3.1 0 4.5-2.7 5.5-5.3 5.8.5.4.9 1.1.9 2.2v3.2c0 .3.2.7.8.6A12 12 0 0012 .5z" fill="currentColor"/> <path d="M12 .5a12 12 0 00-3.8 23.4c.6.1.8-.3.8-.6v-2.1c-3.3.7-4-1.6-4-1.6-.5-1.2-1.2-1.5-1.2-1.5-1-.7.1-.7.1-.7 1.1.1 1.7 1.1 1.7 1.1 1 .1 1.6-.7 1.9-1 .1-.8.4-1.3.8-1.6-2.6-.3-5.3-1.3-5.3-5.8 0-1.3.5-2.3 1.2-3.1-.1-.3-.5-1.6.1-3.3 0 0 1-.3 3.3 1.2a11.4 11.4 0 016 0c2.3-1.5 3.3-1.2 3.3-1.2.6 1.7.2 3 .1 3.3.8.8 1.2 1.9 1.2 3.1 0 4.5-2.7 5.5-5.3 5.8.5.4.9 1.1.9 2.2v3.2c0 .3.2.7.8.6A12 12 0 0012 .5z" fill="currentColor"/>
</svg> </svg>
Ver no GitHub <span>Ver no GitHub</span>
</a> </a>
</div> </div>
</div> </div>
<nav aria-label="Links úteis" className="text-sm"> {/* Coluna 2: Links Úteis */}
<h4 className="text-sm font-semibold mb-2">Links úteis</h4> <nav aria-label="Links úteis" className="space-y-4">
<ul className="space-y-2"> <h4 className="text-sm font-bold uppercase tracking-wider text-slate-800 dark:text-slate-200">
Recursos
</h4>
<ul className="space-y-3 text-sm">
<li> <li>
<a <a
href="https://github.com/lucasrguerra/ESPDocs" href="https://github.com/lucasrguerra/ESPDocs"
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"
className="hover:underline text-white/95 focus:outline-none focus:ring-2 focus:ring-white/20 rounded" className="hover:text-purple-600 dark:hover:text-purple-400 transition-colors flex items-center gap-1.5"
aria-label="Abrir repositório no GitHub" aria-label="Abrir repositório no GitHub"
> >
Repositório no GitHub <span>Repositório Principal</span>
<ExternalLink className="w-3.5 h-3.5 opacity-60" />
</a> </a>
</li> </li>
<li> <li>
@@ -48,10 +63,11 @@ export default function Footer() {
href="https://github.com/lucasrguerra/ESPDocs/issues" href="https://github.com/lucasrguerra/ESPDocs/issues"
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"
className="hover:underline text-white/90 focus:outline-none focus:ring-2 focus:ring-white/20 rounded" className="hover:text-purple-600 dark:hover:text-purple-400 transition-colors flex items-center gap-1.5"
aria-label="Abrir issues do repositório" aria-label="Abrir issues do repositório"
> >
Reportar bug / Abrir issue <span>Reportar um Bug / Feedback</span>
<ExternalLink className="w-3.5 h-3.5 opacity-60" />
</a> </a>
</li> </li>
<li> <li>
@@ -59,40 +75,44 @@ export default function Footer() {
href="https://cienciaembarcada.com.br" href="https://cienciaembarcada.com.br"
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"
className="hover:underline text-white/90 focus:outline-none focus:ring-2 focus:ring-white/20 rounded" className="hover:text-purple-600 dark:hover:text-purple-400 transition-colors flex items-center gap-1.5"
aria-label="Visitar Ciência Embarcada (abre em nova aba)" aria-label="Visitar Ciência Embarcada (abre em nova aba)"
> >
Ciência Embarcada <span className="font-semibold text-purple-600 dark:text-purple-400">Ciência Embarcada</span>
<ExternalLink className="w-3.5 h-3.5 opacity-60 text-purple-600 dark:text-purple-400" />
</a> </a>
</li> </li>
</ul> </ul>
</nav> </nav>
<div className="space-y-3"> {/* Coluna 3: Autor & Social */}
<h4 className="text-sm font-semibold">Créditos</h4> <div className="space-y-4">
<p className="text-sm text-white/90"> <h4 className="text-sm font-bold uppercase tracking-wider text-slate-800 dark:text-slate-200">
Desenvolvido por{' '} Sobre o Autor
</h4>
<p className="text-sm leading-relaxed text-slate-500 dark:text-slate-400">
Desenvolvido com dedicação por{' '}
<a <a
href="https://linkedin.com/in/lucasrguerra" href="https://linkedin.com/in/lucasrguerra"
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"
className="underline hover:text-white" className="font-semibold underline text-slate-700 dark:text-slate-200 hover:text-purple-600 dark:hover:text-purple-400 transition-colors"
aria-label="Perfil LinkedIn de Lucas Rayan Guerra (abre em nova aba)" aria-label="Perfil LinkedIn de Lucas Rayan Guerra (abre em nova aba)"
> >
Lucas Rayan Guerra Lucas Rayan Guerra
</a> </a>
. , compartilhando eletrônica, IoT e computação no Brasil.
</p> </p>
<div className="flex items-center gap-3 mt-2"> <div className="flex items-center gap-3 pt-2">
<a <a
href="https://github.com/lucasrguerra/ESPDocs" href="https://github.com/lucasrguerra/ESPDocs"
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"
className="inline-flex items-center justify-center w-9 h-9 rounded-md bg-white/10 hover:bg-white/20 focus:outline-none focus:ring-2 focus:ring-white/20" className="inline-flex items-center justify-center w-10 h-10 rounded-xl bg-slate-200/60 dark:bg-slate-900 hover:bg-slate-200 dark:hover:bg-slate-800 text-slate-700 dark:text-slate-200 transition-all cursor-pointer shadow-xs"
aria-label="GitHub do projeto" aria-label="GitHub do projeto"
> >
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" aria-hidden="true"> <svg className="w-5 h-5" viewBox="0 0 24 24" fill="none" aria-hidden="true">
<path d="M12 .5a12 12 0 00-3.8 23.4c.6.1.8-.3.8-.6v-2.1c-3.3.7-4-1.6-4-1.6-.5-1.2-1.2-1.5-1.2-1.5-1-.7.1-.7.1-.7 1.1.1 1.7 1.1 1.7 1.1 1 .1 1.6-.7 1.9-1 .1-.8.4-1.3.8-1.6-2.6-.3-5.3-1.3-5.3-5.8 0-1.3.5-2.3 1.2-3.1-.1-.3-.5-1.6.1-3.3 0 0 1-.3 3.3 1.2a11.4 11.4 0 016 0c2.3-1.5 3.3-1.2 3.3-1.2.6 1.7.2 3 .1 3.3.8.8 1.2 1.9 1.2 3.1 0 4.5-2.7 5.5-5.3 5.8.5.4.9 1.1.9 2.2v3.2c0 .3.2.7.8.6A12 12 0 0012 .5z" fill="currentColor"/> <path d="M12 .5a12 12 0 00-3.8 23.4c.6.1.8-.3.8-.6v-2.1c-3.3.7-4-1.6-4-1.6-.5-1.2-1.2-1.5-1.2-1.5-1-.7.1-.7.1-.7 1.1.1 1.7 1.1 1.7 1.1 1 .1 1.6-.7 1.9-1 .1-.8.4-1.3.8-1.6-2.6-.3-5.3-1.3-5.3-5.8 0-1.3.5-2.3 1.2-3.1-.1-.3-.5-1.6.1-3.3 0 0 1-.3 3.3 1.2a11.4 11.4 0 016 0c2.3-1.5 3.3-1.2 3.3-1.2.6 1.7.2 3 .1 3.3.8.8 1.2 1.9 1.2 3.1 0 4.5-2.7 5.5-5.3 5.8.5.4.9 1.1.9 2.2v3.2c0 .3.2.7.8.6A12 12 0 0012 .5z" fill="currentColor"/>
</svg> </svg>
</a> </a>
@@ -101,10 +121,10 @@ export default function Footer() {
href="https://linkedin.com/in/lucasrguerra" href="https://linkedin.com/in/lucasrguerra"
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"
className="inline-flex items-center justify-center w-9 h-9 rounded-md bg-white/10 hover:bg-white/20 focus:outline-none focus:ring-2 focus:ring-white/20" className="inline-flex items-center justify-center w-10 h-10 rounded-xl bg-slate-200/60 dark:bg-slate-900 hover:bg-slate-200 dark:hover:bg-slate-800 text-slate-700 dark:text-slate-200 transition-all cursor-pointer shadow-xs"
aria-label="LinkedIn do autor" aria-label="LinkedIn do autor"
> >
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" aria-hidden="true"> <svg className="w-5 h-5" viewBox="0 0 24 24" fill="none" aria-hidden="true">
<path d="M4.98 3.5a2.5 2.5 0 11.001 5.001A2.5 2.5 0 014.98 3.5zM3 8.98h4v12H3v-12zM9.5 8.98h3.8v1.6h.1c.5-.9 1.8-1.9 3.6-1.9 3.8 0 4.5 2.5 4.5 5.8v6.5h-4v-5.8c0-1.4-.1-3.2-2-3.2-2 0-2.3 1.6-2.3 3.1v5.9h-4v-12z" fill="currentColor"/> <path d="M4.98 3.5a2.5 2.5 0 11.001 5.001A2.5 2.5 0 014.98 3.5zM3 8.98h4v12H3v-12zM9.5 8.98h3.8v1.6h.1c.5-.9 1.8-1.9 3.6-1.9 3.8 0 4.5 2.5 4.5 5.8v6.5h-4v-5.8c0-1.4-.1-3.2-2-3.2-2 0-2.3 1.6-2.3 3.1v5.9h-4v-12z" fill="currentColor"/>
</svg> </svg>
</a> </a>
@@ -112,10 +132,14 @@ export default function Footer() {
</div> </div>
</div> </div>
<div className="mt-6 pt-4 border-t border-white/10 text-sm text-white/80 text-center"> {/* Divisor & Isenção */}
<div> <div className="mt-12 pt-8 border-t border-slate-205/60 dark:border-slate-800/80 flex flex-col md:flex-row justify-between items-center gap-4 text-xs text-slate-400 dark:text-slate-500">
© {year} ESPDocs — Conteúdo não oficial. Consulte sempre datasheets e documentos oficiais para decisões críticas. <p className="text-center md:text-left leading-relaxed">
</div> © {year} ESPDocs. Esta plataforma independente de código aberto não possui afiliação com a Espressif Systems.
</p>
<p className="text-center md:text-right shrink-0">
Feito para a comunidade maker 🇧🇷
</p>
</div> </div>
</div> </div>
</footer> </footer>
+149 -107
View File
@@ -1,37 +1,43 @@
"use client"; "use client";
import { usePathname } from "next/navigation"; import { usePathname } from "next/navigation";
import { useState } from "react"; import { useState, useEffect } from "react";
import Link from "next/link"; import Link from "next/link";
import { useTheme } from "next-themes";
import Button from "@mui/material/Button"; import { createPortal } from "react-dom";
import Drawer from "@mui/material/Drawer"; import {
import List from "@mui/material/List"; Home,
import ListItem from "@mui/material/ListItem"; Info,
BookOpen,
import MenuIcon from "@mui/icons-material/Menu"; Cpu,
import HomeIcon from "@mui/icons-material/Home"; SlidersHorizontal,
import AboutIcon from "@mui/icons-material/Info"; Terminal,
import ModelsIcon from "@mui/icons-material/Memory"; Scale,
import FrameworksIcon from "@mui/icons-material/Apps"; ShoppingBag,
import CompareIcon from "@mui/icons-material/CompareArrows"; Menu,
import ShoppingCartIcon from "@mui/icons-material/ShoppingCart"; X,
import MenuBookIcon from "@mui/icons-material/MenuBook"; Sun,
import FindInPageIcon from "@mui/icons-material/FindInPage"; Moon
} from "lucide-react";
export default function Header() { export default function Header() {
const [drawerOpen, setDrawerOpen] = useState(false); const [drawerOpen, setDrawerOpen] = useState(false);
const [mounted, setMounted] = useState(false);
const pathname = usePathname(); const pathname = usePathname();
useEffect(() => {
setMounted(true);
}, []);
const menuItems = [ const menuItems = [
{ text: "Início", href: "/", icon: <HomeIcon />, featured: true }, { text: "Início", href: "/", icon: <Home className="w-4 h-4" />, featured: true },
{ text: "Sobre o Projeto", brief: "Sobre", href: "/sobre", icon: <AboutIcon /> }, { text: "Sobre o Projeto", brief: "Sobre", href: "/sobre", icon: <Info className="w-4 h-4" /> },
{ text: "Glossário", href: "/glossario", icon: <MenuBookIcon /> }, { text: "Glossário", href: "/glossario", icon: <BookOpen className="w-4 h-4" /> },
{ text: "Séries", href: "/series", icon: <ModelsIcon />, featured: true }, { text: "Séries", href: "/series", icon: <Cpu className="w-4 h-4" />, featured: true },
{ text: "Seletor de ESP32", brief: "Seletor", href: "/seletor", icon: <FindInPageIcon />, featured: true }, { text: "Seletor de ESP32", brief: "Seletor", href: "/seletor", icon: <SlidersHorizontal className="w-4 h-4" />, featured: true },
{ text: "Frameworks", href: "/frameworks", icon: <FrameworksIcon />, featured: true }, { text: "Frameworks", href: "/frameworks", icon: <Terminal className="w-4 h-4" />, featured: true },
{ text: "Comparar Séries", brief: "Comparar", href: "/comparacao", icon: <CompareIcon />, featured: true }, { text: "Comparar Séries", brief: "Comparar", href: "/comparacao", icon: <Scale className="w-4 h-4" />, featured: true },
{ text: "Catálogo de Placas", brief: "Catálogo", href: "/catalogo", icon: <ShoppingCartIcon /> }, { text: "Catálogo de Placas", brief: "Catálogo", href: "/catalogo", icon: <ShoppingBag className="w-4 h-4" /> },
]; ];
const featuredItems = menuItems.filter(item => item.featured); const featuredItems = menuItems.filter(item => item.featured);
@@ -40,29 +46,41 @@ export default function Header() {
if (href === "/") { if (href === "/") {
return pathname === "/"; return pathname === "/";
} }
return String(pathname).includes(href) && href !== "/"; return String(pathname).includes(href) && href !== "/";
} }
// Lock scroll when mobile menu is open
useEffect(() => {
if (drawerOpen) {
document.body.style.overflow = "hidden";
} else {
document.body.style.overflow = "";
}
return () => {
document.body.style.overflow = "";
};
}, [drawerOpen]);
return ( return (
<header className="bg-white/80 backdrop-blur-md py-4 px-4 md:px-8 text-black shadow-lg border-b border-gray-100 sticky top-0 z-50"> <header className="bg-white/80 dark:bg-slate-950/80 backdrop-blur-md py-4 px-4 md:px-8 shadow-xs border-b border-slate-100 dark:border-slate-800/60 sticky top-0 z-50 transition-colors duration-300">
<div className="flex justify-between max-w-7xl mx-auto items-center gap-8"> <div className="flex justify-between max-w-7xl mx-auto items-center gap-8">
{/* LOGO */}
<Link href="/" className="group shrink-0"> <Link href="/" className="group shrink-0">
<h1 className="text-2xl md:text-3xl font-bold bg-linear-to-r from-blue-600 via-purple-600 to-pink-600 bg-clip-text text-transparent transition-all duration-300 group-hover:scale-105"> <h1 className="text-2xl md:text-3xl font-display font-extrabold bg-gradient-to-r from-blue-500 via-purple-500 to-pink-500 bg-clip-text text-transparent transition-all duration-300 group-hover:scale-[1.02]">
ESPDocs ESPDocs
</h1> </h1>
</Link> </Link>
{/* Menu Desktop - visível em telas médias e grandes */} {/* Menu Desktop */}
<nav className="hidden lg:flex items-center gap-2 flex-1 justify-end" aria-label="Menu principal"> <nav className="hidden lg:flex items-center gap-2 flex-1 justify-end" aria-label="Menu principal">
{featuredItems.map((item) => ( {featuredItems.map((item) => (
<Link <Link
key={item.text} key={item.text}
href={item.href} href={item.href}
className={`group flex items-center gap-2 px-4 py-2.5 rounded-xl font-medium transition-all duration-300 ${ className={`group flex items-center gap-2 px-4 py-2 rounded-xl font-medium transition-all duration-300 text-sm ${
isActive(item.href) isActive(item.href)
? "bg-linear-to-r from-blue-600 to-purple-600 text-white shadow-lg shadow-purple-200" ? "bg-gradient-to-r from-blue-600 to-purple-600 text-white shadow-md shadow-purple-500/10"
: "text-gray-700 hover:bg-linear-to-r hover:from-blue-50 hover:to-purple-50 hover:shadow-md hover:-translate-y-0.5" : "text-slate-600 dark:text-slate-300 hover:bg-slate-50 dark:hover:bg-slate-900/60 hover:text-purple-600 dark:hover:text-purple-400"
}`} }`}
aria-label={item.brief || item.text} aria-label={item.brief || item.text}
aria-current={isActive(item.href) ? "page" : undefined} aria-current={isActive(item.href) ? "page" : undefined}
@@ -72,110 +90,134 @@ export default function Header() {
}`}> }`}>
{item.icon} {item.icon}
</span> </span>
<span className="text-sm whitespace-nowrap">{item.brief || item.text}</span> <span className="whitespace-nowrap">{item.brief || item.text}</span>
</Link> </Link>
))} ))}
{/* Botão Menu Completo Desktop */} {/* Divider */}
<Button <div className="h-5 w-px bg-slate-200 dark:bg-slate-800 mx-2" />
variant="outlined"
{/* Theme Switcher Button */}
<ThemeToggle />
{/* Full Menu Burger Trigger */}
<button
onClick={() => setDrawerOpen(true)} onClick={() => setDrawerOpen(true)}
className="border-2 border-gray-300 text-gray-700 hover:border-purple-500 hover:text-purple-600 transition-all duration-300 hover:shadow-lg rounded-xl! px-4" className="p-2.5 rounded-xl border border-slate-200 dark:border-slate-800 text-slate-600 dark:text-slate-300 hover:bg-slate-50 dark:hover:bg-slate-900/80 hover:border-purple-500 dark:hover:border-purple-400 hover:text-purple-600 dark:hover:text-purple-400 transition-all cursor-pointer"
aria-label="Abrir menu completo" aria-label="Abrir menu completo"
aria-expanded={drawerOpen} aria-expanded={drawerOpen}
aria-controls="mobile-menu-drawer"
> >
<MenuIcon /> <Menu className="w-5 h-5" />
</Button> </button>
</nav> </nav>
{/* Botão Menu Mobile - visível apenas em telas pequenas */} {/* Botões Mobile (Theme + Burger) */}
<div className="lg:hidden"> <div className="flex lg:hidden items-center gap-2">
<Button <ThemeToggle />
variant="outlined" <button
onClick={() => setDrawerOpen(true)} onClick={() => setDrawerOpen(true)}
className="border-2 border-gray-300 text-gray-700 hover:border-purple-500 hover:text-purple-600 transition-all duration-300 hover:shadow-lg rounded-xl! px-4" className="p-2.5 rounded-xl border border-slate-200 dark:border-slate-800 text-slate-600 dark:text-slate-300 hover:bg-slate-50 dark:hover:bg-slate-900/80 hover:border-purple-500 dark:hover:border-purple-400 transition-all cursor-pointer"
aria-label="Abrir menu de navegação" aria-label="Abrir menu de navegação"
aria-expanded={drawerOpen} aria-expanded={drawerOpen}
aria-controls="mobile-menu-drawer"
> >
<MenuIcon /> <Menu className="w-5 h-5" />
</Button> </button>
</div> </div>
</div> </div>
{/* Drawer Mobile */} {/* Render side-panel and overlay at body level via React Portal to prevent layout leakage */}
<Drawer {mounted && createPortal(
anchor="right" <>
open={drawerOpen} {drawerOpen && (
onClose={() => setDrawerOpen(false)} <div
id="mobile-menu-drawer" className="fixed inset-0 bg-slate-950/50 dark:bg-black/75 backdrop-blur-xs z-[100] transition-opacity duration-300"
> onClick={() => setDrawerOpen(false)}
<div className="p-6 w-72"> />
<div className="mb-6"> )}
<h2 className="text-2xl font-bold bg-linear-to-r from-blue-600 to-purple-600 bg-clip-text text-transparent">
Menu
</h2>
<div className="h-1 w-16 bg-linear-to-r from-blue-600 to-purple-600 rounded-full mt-2"></div>
</div>
<List component="nav" aria-label="Menu de navegação"> <div
{menuItems.map((item, index) => ( className={`fixed top-0 right-0 h-screen w-80 max-w-[85vw] bg-white dark:bg-slate-900 shadow-2xl z-[101] transform transition-transform duration-300 ease-in-out flex flex-col border-l border-slate-200 dark:border-slate-800 ${
<ListItem drawerOpen ? "translate-x-0" : "translate-x-full"
key={item.text}
disablePadding
className="mb-3"
style={{
animation: `slideIn 0.3s ease-out ${index * 0.1}s both`
}}
>
<div className="w-full">
<Link
className={`flex items-center w-full p-4 text-left rounded-xl transition-all duration-300 focus:outline-none focus:ring-2 focus:ring-purple-500 focus:ring-offset-2 ${
isActive(item.href)
? "bg-linear-to-r from-blue-600 to-purple-600 text-white shadow-lg transform scale-105"
: "hover:bg-white hover:shadow-md hover:transform hover:scale-102 text-gray-700"
}`} }`}
onClick={() => { setDrawerOpen(false); }} >
{/* Drawer Header */}
<div className="flex items-center justify-between p-6 border-b border-slate-100 dark:border-slate-800/80">
<h2 className="text-xl font-display font-bold bg-gradient-to-r from-blue-500 to-purple-500 bg-clip-text text-transparent">
Menu Navegação
</h2>
<button
onClick={() => setDrawerOpen(false)}
className="p-2 text-slate-500 hover:text-slate-800 dark:text-slate-400 dark:hover:text-white rounded-xl hover:bg-slate-50 dark:hover:bg-slate-800/60 transition-all cursor-pointer"
aria-label="Fechar menu"
>
<X className="w-5 h-5" />
</button>
</div>
{/* Drawer Menu Items */}
<nav className="flex-1 overflow-y-auto p-6 animate-fadeIn" aria-label="Navegação móvel">
<ul className="space-y-2">
{menuItems.map((item) => (
<li key={item.text}>
<Link
href={item.href} href={item.href}
aria-label={item.text} className={`flex items-center gap-3 w-full p-3.5 rounded-xl font-medium transition-all duration-205 text-sm ${
isActive(item.href)
? "bg-gradient-to-r from-blue-600 to-purple-600 text-white shadow-md shadow-purple-500/10"
: "text-slate-650 dark:text-slate-350 hover:bg-slate-50 dark:hover:bg-slate-800/40 hover:text-purple-600 dark:hover:text-purple-400"
}`}
onClick={() => setDrawerOpen(false)}
aria-current={isActive(item.href) ? "page" : undefined} aria-current={isActive(item.href) ? "page" : undefined}
> >
<span className={`transition-transform duration-300 ${isActive(item.href) ? "scale-110" : "group-hover:scale-110"}`}> <span className="shrink-0">{item.icon}</span>
{item.icon} <span className="grow text-left">{item.text}</span>
</span>
<span className="ml-3 font-medium">{item.text}</span>
{isActive(item.href) && ( {isActive(item.href) && (
<span className="ml-auto"> <span className="w-1.5 h-1.5 bg-white rounded-full animate-ping" />
<span className="inline-block w-2 h-2 bg-white rounded-full animate-pulse"></span>
</span>
)} )}
</Link> </Link>
</div> </li>
</ListItem>
))} ))}
</List> </ul>
</nav>
<div className="mt-8 p-4 bg-white/50 backdrop-blur-sm rounded-xl border border-gray-200"> {/* Drawer Footer Info */}
<p className="text-xs text-gray-600 text-center"> <div className="p-6 border-t border-slate-100 dark:border-slate-800/80 bg-slate-50/50 dark:bg-slate-900/50">
Documentação não oficial <p className="text-center text-xs text-slate-400 dark:text-slate-500 font-medium">
ESPDocs • Plataforma Não Oficial
</p> </p>
</div> </div>
</div> </div>
</Drawer> </>
, document.body
<style jsx global>{` )}
@keyframes slideIn {
from {
opacity: 0;
transform: translateX(20px);
}
to {
opacity: 1;
transform: translateX(0);
}
}
`}</style>
</header> </header>
); );
} }
/* Internal ThemeToggle Component */
function ThemeToggle() {
const { resolvedTheme, setTheme } = useTheme();
const [mounted, setMounted] = useState(false);
useEffect(() => setMounted(true), []);
if (!mounted) {
return (
<div className="w-10 h-10 rounded-xl bg-slate-100 dark:bg-slate-900 animate-pulse border border-transparent" />
);
}
return (
<button
onClick={() => setTheme(resolvedTheme === "dark" ? "light" : "dark")}
className="w-10 h-10 rounded-xl bg-slate-100 dark:bg-slate-900 text-slate-600 dark:text-slate-300 hover:bg-slate-200 dark:hover:bg-slate-800/80 hover:text-purple-600 dark:hover:text-purple-400 border border-slate-250/20 dark:border-slate-800/60 shadow-xs flex items-center justify-center transition-all duration-300 cursor-pointer active:scale-95 shrink-0"
aria-label="Alternar tema"
>
{resolvedTheme === "dark" ? (
<Sun className="w-5 h-5 text-yellow-500" />
) : (
<Moon className="w-5 h-5 text-indigo-600" />
)}
</button>
);
}
+11
View File
@@ -0,0 +1,11 @@
"use client";
import { ThemeProvider } from "next-themes";
export default function Providers({ children }) {
return (
<ThemeProvider attribute="class" defaultTheme="dark" enableSystem={true}>
{children}
</ThemeProvider>
);
}
+22 -20
View File
@@ -1,6 +1,7 @@
'use client'; 'use client';
import { useState } from 'react'; import { useState } from 'react';
import { ChevronDown } from 'lucide-react';
export default function SeriesTabMenu({ tabs, color }) { export default function SeriesTabMenu({ tabs, color }) {
const availableTabs = tabs.filter(tab => tab.available); const availableTabs = tabs.filter(tab => tab.available);
@@ -9,47 +10,49 @@ export default function SeriesTabMenu({ tabs, color }) {
const activeTabContent = availableTabs.find(tab => tab.id === activeTab)?.content; const activeTabContent = availableTabs.find(tab => tab.id === activeTab)?.content;
return ( return (
<div className="mb-8"> <div className="mb-12">
<div className="hidden md:flex bg-white rounded-2xl shadow-lg py-2 px-4 mb-6 border-2 border-gray-100 gap-4"> {/* Desktop Tabs */}
{availableTabs.map((tab) => ( <div className="hidden md:flex bg-white/70 dark:bg-slate-900/40 backdrop-blur-xl rounded-2xl shadow-xl py-2 px-3 mb-6 border border-slate-200/60 dark:border-slate-800/80 gap-3">
{availableTabs.map((tab) => {
const isActive = activeTab === tab.id;
return (
<button <button
key={tab.id} key={tab.id}
onClick={() => setActiveTab(tab.id)} onClick={() => setActiveTab(tab.id)}
className={`flex-1 flex items-center justify-center gap-2 py-4 px-6 rounded-xl font-semibold transition-all duration-300 cursor-pointer ${ className={`flex-1 flex items-center justify-center gap-2 py-3 px-5 rounded-xl font-bold text-xs uppercase tracking-wider transition-all duration-300 cursor-pointer active:scale-98 ${
activeTab === tab.id isActive
? 'text-white shadow-lg transform scale-105' ? 'text-white shadow-lg scale-[1.02]'
: 'text-gray-600 hover:bg-gray-50' : 'text-slate-650 dark:text-slate-350 hover:bg-slate-100/50 dark:hover:bg-slate-800/40 hover:text-slate-800 dark:hover:text-slate-100'
}`} }`}
style={ style={
activeTab === tab.id isActive
? { backgroundColor: color } ? { backgroundColor: color, boxShadow: `0 10px 20px -10px ${color}` }
: {} : {}
} }
> >
<span className="hidden lg:inline">{tab.label}</span> <span>{tab.label}</span>
</button> </button>
))} );
})}
</div> </div>
{/* Mobile Dropdown Tab Selector */}
<div className="md:hidden mb-6"> <div className="md:hidden mb-6">
<div className="bg-white rounded-2xl shadow-lg border-2 border-gray-100 overflow-hidden"> <div className="bg-white/70 dark:bg-slate-900/40 backdrop-blur-xl rounded-2xl shadow-lg border border-slate-200/60 dark:border-slate-800/80 overflow-hidden">
<button <button
onClick={() => { onClick={() => {
const dropdown = document.getElementById('mobile-tab-dropdown'); const dropdown = document.getElementById('mobile-tab-dropdown');
dropdown.classList.toggle('hidden'); dropdown.classList.toggle('hidden');
}} }}
className="w-full flex items-center justify-between py-4 px-6 font-semibold text-white" className="w-full flex items-center justify-between py-4 px-6 font-bold text-sm uppercase tracking-wider text-white transition-all duration-300"
style={{ backgroundColor: color }} style={{ backgroundColor: color }}
> >
<div className="flex items-center gap-3"> <div className="flex items-center gap-3">
<span className="text-xl">
{availableTabs.find(tab => tab.id === activeTab)?.icon}
</span>
<span> <span>
{availableTabs.find(tab => tab.id === activeTab)?.label} {availableTabs.find(tab => tab.id === activeTab)?.label}
</span> </span>
</div> </div>
<span className="text-xl">▼</span> <ChevronDown className="w-4 h-4 shrink-0 transition-transform duration-300" />
</button> </button>
<div id="mobile-tab-dropdown" className="hidden"> <div id="mobile-tab-dropdown" className="hidden">
@@ -61,10 +64,9 @@ export default function SeriesTabMenu({ tabs, color }) {
setActiveTab(tab.id); setActiveTab(tab.id);
document.getElementById('mobile-tab-dropdown').classList.add('hidden'); document.getElementById('mobile-tab-dropdown').classList.add('hidden');
}} }}
className="w-full flex items-center gap-3 py-4 px-6 text-gray-700 hover:bg-gray-50 transition-colors border-t border-gray-100" className="w-full flex items-center gap-3 py-4 px-6 text-slate-700 dark:text-slate-300 hover:bg-slate-150/40 dark:hover:bg-slate-800/30 transition-colors border-t border-slate-200/60 dark:border-slate-800/60 font-semibold text-xs uppercase tracking-wider text-left"
> >
<span className="text-xl">{tab.icon}</span> <span>{tab.label}</span>
<span className="font-medium">{tab.label}</span>
</button> </button>
) )
))} ))}
+64 -25
View File
@@ -12,8 +12,11 @@
"@emotion/styled": "^11.14.1", "@emotion/styled": "^11.14.1",
"@mui/icons-material": "^7.3.4", "@mui/icons-material": "^7.3.4",
"@mui/material": "^7.3.4", "@mui/material": "^7.3.4",
"framer-motion": "^12.40.0",
"googleapis": "^165.0.0", "googleapis": "^165.0.0",
"lucide-react": "^1.16.0",
"next": "^15.5.9", "next": "^15.5.9",
"next-themes": "^0.4.6",
"react": "19.1.0", "react": "19.1.0",
"react-dom": "19.1.0" "react-dom": "19.1.0"
}, },
@@ -1877,16 +1880,6 @@
"integrity": "sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==", "integrity": "sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==",
"license": "MIT" "license": "MIT"
}, },
"node_modules/@types/react": {
"version": "19.2.8",
"resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.8.tgz",
"integrity": "sha512-3MbSL37jEchWZz2p2mjntRZtPt837ij10ApxKfgmXCTuHWagYg7iA5bqPw6C8BMPfwidlvfPI/fxOc42HLhcyg==",
"license": "MIT",
"peer": true,
"dependencies": {
"csstype": "^3.2.2"
}
},
"node_modules/@types/react-transition-group": { "node_modules/@types/react-transition-group": {
"version": "4.4.12", "version": "4.4.12",
"resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.12.tgz", "resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.12.tgz",
@@ -4019,6 +4012,33 @@
"node": ">=12.20.0" "node": ">=12.20.0"
} }
}, },
"node_modules/framer-motion": {
"version": "12.40.0",
"resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-12.40.0.tgz",
"integrity": "sha512-uaBd3qC1v3KQqBEjwTUd183K6PbS+j0yR9w9VmEOLWA/tnUcSn8Xa3uck7t4dgpDoUss8xQTcj8W2L07lrnLFg==",
"license": "MIT",
"dependencies": {
"motion-dom": "^12.40.0",
"motion-utils": "^12.39.0",
"tslib": "^2.4.0"
},
"peerDependencies": {
"@emotion/is-prop-valid": "*",
"react": "^18.0.0 || ^19.0.0",
"react-dom": "^18.0.0 || ^19.0.0"
},
"peerDependenciesMeta": {
"@emotion/is-prop-valid": {
"optional": true
},
"react": {
"optional": true
},
"react-dom": {
"optional": true
}
}
},
"node_modules/function-bind": { "node_modules/function-bind": {
"version": "1.1.2", "version": "1.1.2",
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
@@ -5453,6 +5473,15 @@
"integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==",
"license": "ISC" "license": "ISC"
}, },
"node_modules/lucide-react": {
"version": "1.16.0",
"resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-1.16.0.tgz",
"integrity": "sha512-dYwyPzb4MEKpGUmNYk3WKWPnMrHs3FKM+q94kAnJrcDIqqn1hq2xY8scaS2ovsOCM5D51ey2gaRG3PBb1vgoYQ==",
"license": "ISC",
"peerDependencies": {
"react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0"
}
},
"node_modules/magic-string": { "node_modules/magic-string": {
"version": "0.30.21", "version": "0.30.21",
"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz",
@@ -5528,6 +5557,21 @@
"node": ">=16 || 14 >=14.17" "node": ">=16 || 14 >=14.17"
} }
}, },
"node_modules/motion-dom": {
"version": "12.40.0",
"resolved": "https://registry.npmjs.org/motion-dom/-/motion-dom-12.40.0.tgz",
"integrity": "sha512-HxU3ZaBwNPVQUBQf1xxgq+7JrPNZvjLVxgbpEZL7RrWJnsxOf0/OM+yrHG9ogLQ31Do/r57Oz2gQWPK+6q62mg==",
"license": "MIT",
"dependencies": {
"motion-utils": "^12.39.0"
}
},
"node_modules/motion-utils": {
"version": "12.39.0",
"resolved": "https://registry.npmjs.org/motion-utils/-/motion-utils-12.39.0.tgz",
"integrity": "sha512-8nadJAJjTtqRkmRF36FoJTrywK9nnFmnPwnSMyxaOCU7GDjN9RTMJIxx9De8ErM+vpPhMccr/6fo5WciyQLnMQ==",
"license": "MIT"
},
"node_modules/ms": { "node_modules/ms": {
"version": "2.1.3", "version": "2.1.3",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
@@ -5627,6 +5671,16 @@
} }
} }
}, },
"node_modules/next-themes": {
"version": "0.4.6",
"resolved": "https://registry.npmjs.org/next-themes/-/next-themes-0.4.6.tgz",
"integrity": "sha512-pZvgD5L0IEvX5/9GWyHMf3m8BKiVQwsCMHfoFosXtXBMnaS0ZnIJ9ST4b4NqLVKDEm8QBxoNNGNaBv2JNF6XNA==",
"license": "MIT",
"peerDependencies": {
"react": "^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc",
"react-dom": "^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc"
}
},
"node_modules/next/node_modules/postcss": { "node_modules/next/node_modules/postcss": {
"version": "8.4.31", "version": "8.4.31",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz",
@@ -7098,21 +7152,6 @@
"url": "https://github.com/sponsors/ljharb" "url": "https://github.com/sponsors/ljharb"
} }
}, },
"node_modules/typescript": {
"version": "5.9.3",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz",
"integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
"dev": true,
"license": "Apache-2.0",
"peer": true,
"bin": {
"tsc": "bin/tsc",
"tsserver": "bin/tsserver"
},
"engines": {
"node": ">=14.17"
}
},
"node_modules/unbox-primitive": { "node_modules/unbox-primitive": {
"version": "1.1.0", "version": "1.1.0",
"resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.1.0.tgz", "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.1.0.tgz",
+3
View File
@@ -13,8 +13,11 @@
"@emotion/styled": "^11.14.1", "@emotion/styled": "^11.14.1",
"@mui/icons-material": "^7.3.4", "@mui/icons-material": "^7.3.4",
"@mui/material": "^7.3.4", "@mui/material": "^7.3.4",
"framer-motion": "^12.40.0",
"googleapis": "^165.0.0", "googleapis": "^165.0.0",
"lucide-react": "^1.16.0",
"next": "^15.5.9", "next": "^15.5.9",
"next-themes": "^0.4.6",
"react": "19.1.0", "react": "19.1.0",
"react-dom": "19.1.0" "react-dom": "19.1.0"
}, },
+307
View File
@@ -0,0 +1,307 @@
[
{
"numero": "1",
"nome": "ANT",
"tipo": "analog",
"funcoes": ["Conexão de antena de RF (Wi-Fi e Bluetooth)"],
"categorias": ["Antena", "RF"],
"avisos": ["Pino de RF dedicado. Evite traçar pistas longas ou sem casamento de impedância de 50 ohms."]
},
{
"numero": "2",
"nome": "VDDA3P3",
"tipo": "power",
"funcoes": ["Alimentação analógica de 3.3V"],
"categorias": ["Power"],
"avisos": [
"Tensão recomendada: 3.3V (3.0V a 3.6V)",
"Fornece energia para os circuitos analógicos e de RF do chip"
]
},
{
"numero": "3",
"nome": "VDDA3P3",
"tipo": "power",
"funcoes": ["Alimentação analógica de 3.3V"],
"categorias": ["Power"],
"avisos": [
"Tensão recomendada: 3.3V (3.0V a 3.6V)",
"Fornece energia para os circuitos analógicos e de RF do chip"
]
},
{
"numero": "4",
"nome": "GPIO0",
"tipo": "io",
"alimentacao": ["VDD3P3_RTC"],
"funcoes": [
"GPIO0",
"ADC1_CH0"
],
"categorias": [
"GPIO",
"ADC"
],
"avisos": ["Pino de entrada analógica ADC1 Canal 0"]
},
{
"numero": "5",
"nome": "GPIO1",
"tipo": "io",
"alimentacao": ["VDD3P3_RTC"],
"funcoes": [
"GPIO1",
"ADC1_CH1"
],
"categorias": [
"GPIO",
"ADC"
],
"avisos": ["Pino de entrada analógica ADC1 Canal 1"]
},
{
"numero": "6",
"nome": "GPIO2",
"tipo": "io",
"alimentacao": ["VDD3P3_RTC"],
"funcoes": [
"GPIO2",
"ADC1_CH2",
"FSPIQ"
],
"categorias": [
"GPIO",
"ADC",
"SPI"
],
"avisos": ["Mapeado para a função SPI MISO (FSPIQ) por padrão na inicialização do flash"]
},
{
"numero": "7",
"nome": "CHIP_EN",
"tipo": "analog",
"funcoes": ["Habilitar o chip (Power-up / Reset)"],
"categorias": ["Power"],
"avisos": [
"Ativo Alto. Puxe para nível alto para inicializar o chip.",
"Não deixe este pino flutuando. Use um circuito RC de atraso externo para inicialização estável."
]
},
{
"numero": "8",
"nome": "GPIO3",
"tipo": "io",
"alimentacao": ["VDD3P3_RTC"],
"funcoes": [
"GPIO3",
"ADC1_CH3"
],
"categorias": [
"GPIO",
"ADC"
],
"avisos": ["Pino de entrada analógica ADC1 Canal 3"]
},
{
"numero": "9",
"nome": "GPIO4",
"tipo": "io",
"alimentacao": ["VDD3P3_RTC"],
"funcoes": [
"GPIO4",
"MTMS",
"FSPIQ",
"ADC1_CH4"
],
"categorias": [
"GPIO",
"JTAG",
"SPI",
"ADC"
],
"avisos": [
"Pino multifuncional: JTAG MTMS, SPI2 MISO e ADC1 Canal 4.",
"Pode ser usado para depuração JTAG em conjunto com outros pinos JTAG."
]
},
{
"numero": "10",
"nome": "GPIO5",
"tipo": "io",
"alimentacao": ["VDD3P3_RTC"],
"funcoes": [
"GPIO5",
"MTDI",
"FSPIWP"
],
"categorias": [
"GPIO",
"JTAG",
"SPI"
],
"avisos": ["Pino multifuncional: JTAG MTDI e SPI2 Write Protect (FSPIWP)."]
},
{
"numero": "11",
"nome": "VDD3P3_RTC",
"tipo": "power",
"funcoes": ["Alimentação do domínio de RTC e IOs de baixo consumo"],
"categorias": ["Power"],
"avisos": ["Tensão recomendada: 3.3V"]
},
{
"numero": "12",
"nome": "GPIO6",
"tipo": "io",
"alimentacao": ["VDD3P3_RTC"],
"funcoes": [
"GPIO6",
"MTCK",
"FSPICLK"
],
"categorias": [
"GPIO",
"JTAG",
"SPI"
],
"avisos": ["Pino multifuncional: JTAG MTCK e SPI2 Clock (FSPICLK)."]
},
{
"numero": "13",
"nome": "GPIO7",
"tipo": "io",
"alimentacao": ["VDD3P3_RTC"],
"funcoes": [
"GPIO7",
"MTDO",
"FSPID"
],
"categorias": [
"GPIO",
"JTAG",
"SPI"
],
"avisos": ["Pino multifuncional: JTAG MTDO e SPI2 MOSI (FSPID)."]
},
{
"numero": "14",
"nome": "GPIO8",
"tipo": "io",
"alimentacao": ["VDD3P3_CPU"],
"funcoes": ["GPIO8"],
"categorias": ["GPIO", "Strapping"],
"avisos": [
"Pino de Strapping para controle de boot.",
"Em conjunto com o GPIO9, seleciona o modo de inicialização do SoC (deve estar em 1 durante o reset para Joint Download Boot se GPIO9=0)."
]
},
{
"numero": "15",
"nome": "GPIO9",
"tipo": "io",
"alimentacao": ["VDD3P3_CPU"],
"funcoes": ["GPIO9"],
"categorias": ["GPIO", "Strapping"],
"avisos": [
"Pino de Strapping principal. Nível alto (1) no reset activa SPI Boot (padrão via pull-up fraco).",
"Nível baixo (0) no reset coloca o chip no modo de download UART/SPI."
]
},
{
"numero": "16",
"nome": "GPIO10",
"tipo": "io",
"alimentacao": ["VDD3P3_CPU"],
"funcoes": [
"GPIO10",
"FSPICS0"
],
"categorias": [
"GPIO",
"SPI"
],
"avisos": ["Pino multifuncional: SPI2 Chip Select 0 (FSPICS0)."]
},
{
"numero": "17",
"nome": "VDD3P3_CPU",
"tipo": "power",
"funcoes": ["Alimentação digital da CPU e IOs correspondentes (3.3V)"],
"categorias": ["Power"],
"avisos": ["Tensão recomendada: 3.3V"]
},
{
"numero": "18",
"nome": "GPIO18",
"tipo": "io",
"alimentacao": ["VDD3P3_CPU"],
"funcoes": ["GPIO18"],
"categorias": ["GPIO"]
},
{
"numero": "19",
"nome": "GPIO19",
"tipo": "io",
"alimentacao": ["VDD3P3_CPU"],
"funcoes": [
"GPIO19",
"U0RXD"
],
"categorias": [
"GPIO",
"UART"
],
"avisos": ["Pino de recepção UART0 (Serial RX por padrão para gravação e monitoramento)."]
},
{
"numero": "20",
"nome": "GPIO20",
"tipo": "io",
"alimentacao": ["VDD3P3_CPU"],
"funcoes": [
"GPIO20",
"U0TXD"
],
"categorias": [
"GPIO",
"UART"
],
"avisos": ["Pino de transmissão UART0 (Serial TX por padrão para gravação e monitoramento)."]
},
{
"numero": "21",
"nome": "VDDA",
"tipo": "power",
"funcoes": ["Alimentação de entrada analógica de 3.3V"],
"categorias": ["Power"]
},
{
"numero": "22",
"nome": "XTAL_N",
"tipo": "analog",
"funcoes": ["Conexão externa do cristal oscilador (Saída)"],
"categorias": ["Clock"]
},
{
"numero": "23",
"nome": "XTAL_P",
"tipo": "analog",
"funcoes": ["Conexão externa do cristal oscilador (Entrada)"],
"categorias": ["Clock"]
},
{
"numero": "24",
"nome": "VDDA",
"tipo": "power",
"funcoes": ["Alimentação de entrada analógica de 3.3V"],
"categorias": ["Power"]
},
{
"numero": "25",
"nome": "GND",
"tipo": "power",
"funcoes": ["Pino de aterramento central (Exposed Pad)"],
"categorias": ["Power"],
"avisos": ["Deve ser devidamente soldado ao plano de terra (GND) da PCB para desempenho estável e dissipação térmica."]
}
]
+536
View File
@@ -0,0 +1,536 @@
[
{
"numero": "1",
"nome": "ANT_2G",
"tipo": "analog",
"funcoes": ["Conexão de antena de RF (Wi-Fi e Bluetooth)"],
"categorias": ["Antena", "RF"],
"avisos": ["Pino de RF dedicado a 2.4 GHz. Requer casamento de impedância de 50 ohms."]
},
{
"numero": "2",
"nome": "VDDA3",
"tipo": "power",
"funcoes": ["Alimentação do domínio analógico de 3.3V"],
"categorias": ["Power"],
"avisos": ["Tensão recomendada: 3.3V (3.0V a 3.6V). Fornece energia para circuitos de RF e clock."]
},
{
"numero": "3",
"nome": "VDDA4",
"tipo": "power",
"funcoes": ["Alimentação do domínio analógico de 3.3V"],
"categorias": ["Power"],
"avisos": ["Tensão recomendada: 3.3V (3.0V a 3.6V). Fornece energia para circuitos de RF e clock."]
},
{
"numero": "4",
"nome": "CHIP_PU",
"tipo": "analog",
"funcoes": ["Habilitar o chip (Power-up / Reset)"],
"categorias": ["Power"],
"avisos": [
"Ativo Alto. Puxe para nível alto para inicializar o chip.",
"Não deixe este pino flutuando. Recomenda-se usar um circuito RC externo para reset limpo."
]
},
{
"numero": "5",
"nome": "VDDPST1",
"tipo": "power",
"funcoes": ["Alimentação digital dos domínios HP (High Performance) e LP (Low Power)"],
"categorias": ["Power"],
"avisos": ["Tensão recomendada: 3.3V. Alimenta a parte digital de baixo consumo e pinos associados."]
},
{
"numero": "6",
"nome": "GPIO0",
"tipo": "io",
"alimentacao": ["VDDPST1"],
"funcoes": [
"GPIO0",
"XTAL_32K_P"
],
"categorias": [
"GPIO",
"Clock"
],
"avisos": ["Pino multifuncional de baixo consumo. Pode ser conectado ao cristal oscilador de 32 kHz externo (Entrada P)."]
},
{
"numero": "7",
"nome": "GPIO1",
"tipo": "io",
"alimentacao": ["VDDPST1"],
"funcoes": [
"GPIO1",
"XTAL_32K_N",
"ADC1_CH0"
],
"categorias": [
"GPIO",
"Clock",
"ADC"
],
"avisos": ["Pino multifuncional. Conexão do cristal de 32 kHz externo (Saída N) e entrada analógica ADC1 Canal 0."]
},
{
"numero": "8",
"nome": "GPIO2",
"tipo": "io",
"alimentacao": ["VDDPST1"],
"funcoes": ["GPIO2"],
"categorias": ["GPIO"]
},
{
"numero": "9",
"nome": "GPIO3",
"tipo": "io",
"alimentacao": ["VDDPST1"],
"funcoes": [
"GPIO3",
"MTMS",
"ADC1_CH1"
],
"categorias": [
"GPIO",
"JTAG",
"ADC",
"Strapping"
],
"avisos": [
"Pino multifuncional: JTAG MTMS, ADC1 Canal 1 e pino de Strapping para controle de borda de clock do SDIO.",
"Flutuante por padrão durante o reset."
]
},
{
"numero": "10",
"nome": "GPIO4",
"tipo": "io",
"alimentacao": ["VDDPST1"],
"funcoes": [
"GPIO4",
"MTDI",
"ADC1_CH2"
],
"categorias": [
"GPIO",
"JTAG",
"ADC",
"Strapping"
],
"avisos": [
"Pino multifuncional: JTAG MTDI, ADC1 Canal 2 e pino de Strapping para controle de borda de clock do SDIO.",
"Flutuante por padrão durante o reset."
]
},
{
"numero": "11",
"nome": "GPIO5",
"tipo": "io",
"alimentacao": ["VDDPST1"],
"funcoes": [
"GPIO5",
"MTCK",
"ADC1_CH3"
],
"categorias": [
"GPIO",
"JTAG",
"ADC"
],
"avisos": ["Pino multifuncional: JTAG MTCK e entrada analógica ADC1 Canal 3."]
},
{
"numero": "12",
"nome": "GPIO6",
"tipo": "io",
"alimentacao": ["VDDPST1"],
"funcoes": [
"GPIO6",
"MTDO"
],
"categorias": [
"GPIO",
"JTAG"
],
"avisos": ["Pino multifuncional JTAG MTDO."]
},
{
"numero": "13",
"nome": "GPIO25",
"tipo": "io",
"alimentacao": ["VDDPST2"],
"funcoes": [
"GPIO25",
"SDIO_CMD",
"FSPICS0"
],
"categorias": [
"GPIO",
"SDIO",
"SPI"
],
"avisos": ["Pino multifuncional de alta velocidade: SDIO Comando ou SPI2 Chip Select 0."]
},
{
"numero": "14",
"nome": "GPIO26",
"tipo": "io",
"alimentacao": ["VDDPST2"],
"funcoes": [
"GPIO26",
"SDIO_CLK",
"FSPICLK"
],
"categorias": [
"GPIO",
"SDIO",
"SPI"
],
"avisos": ["Pino multifuncional de alta velocidade: SDIO Clock ou SPI2 Clock."]
},
{
"numero": "15",
"nome": "GPIO27",
"tipo": "io",
"alimentacao": ["VDDPST2"],
"funcoes": [
"GPIO27",
"SDIO_DATA0",
"FSPID"
],
"categorias": [
"GPIO",
"SDIO",
"SPI"
],
"avisos": ["Pino multifuncional de alta velocidade: SDIO Dados 0 ou SPI2 MOSI (FSPID)."]
},
{
"numero": "16",
"nome": "GPIO28",
"tipo": "io",
"alimentacao": ["VDDPST2"],
"funcoes": [
"GPIO28",
"SDIO_DATA1",
"FSPIQ"
],
"categorias": [
"GPIO",
"SDIO",
"SPI"
],
"avisos": ["Pino de alta velocidade: SDIO Dados 1 ou SPI2 MISO (FSPIQ)."]
},
{
"numero": "17",
"nome": "GPIO22",
"tipo": "io",
"alimentacao": ["VDDPST2"],
"funcoes": [
"GPIO22",
"SDIO_DATA2",
"FSPIWP"
],
"categorias": [
"GPIO",
"SDIO",
"SPI"
],
"avisos": ["Pino de alta velocidade: SDIO Dados 2 ou SPI2 Write Protect."]
},
{
"numero": "18",
"nome": "GPIO23",
"tipo": "io",
"alimentacao": ["VDDPST2"],
"funcoes": [
"GPIO23",
"SDIO_DATA3",
"FSPIHD"
],
"categorias": [
"GPIO",
"SDIO",
"SPI"
],
"avisos": ["Pino de alta velocidade: SDIO Dados 3 ou SPI2 Hold."]
},
{
"numero": "19",
"nome": "GPIO14",
"tipo": "io",
"alimentacao": ["VDD_SPI", "VDDPST2"],
"funcoes": [
"GPIO14",
"SPICS1"
],
"categorias": [
"GPIO",
"SPI"
],
"avisos": ["Pino dedicado para Chip Select 1 da interface de memória SPI Flash/PSRAM."]
},
{
"numero": "20",
"nome": "GPIO15",
"tipo": "io",
"alimentacao": ["VDD_SPI", "VDDPST2"],
"funcoes": [
"GPIO15",
"SPICS0"
],
"categorias": [
"GPIO",
"SPI"
],
"avisos": ["Pino dedicado para Chip Select 0 da interface de memória SPI Flash."]
},
{
"numero": "21",
"nome": "VDDPST2",
"tipo": "power",
"funcoes": ["Alimentação do domínio digital HP (High Performance)"],
"categorias": ["Power"],
"avisos": ["Tensão recomendada: 3.3V (3.0V a 3.6V). Alimenta as interfaces digitais rápidas."]
},
{
"numero": "22",
"nome": "GPIO16",
"tipo": "io",
"alimentacao": ["VDD_SPI", "VDDPST2"],
"funcoes": [
"GPIO16",
"SPIQ"
],
"categorias": [
"GPIO",
"SPI"
],
"avisos": ["Pino de dados Q (MISO) da interface de memória flash SPI."]
},
{
"numero": "23",
"nome": "GPIO17",
"tipo": "io",
"alimentacao": ["VDD_SPI", "VDDPST2"],
"funcoes": [
"GPIO17",
"SPIWP"
],
"categorias": [
"GPIO",
"SPI"
],
"avisos": ["Pino de proteção contra escrita (Write Protect) da interface de memória flash SPI."]
},
{
"numero": "24",
"nome": "VDD_SPI",
"tipo": "power",
"funcoes": ["Tensão de saída LDO gerada a partir de VDDPST2 para alimentar a flash e PSRAM"],
"categorias": ["Power"],
"avisos": [
"Tensão de saída configurável via software de acordo com as especificações da flash interna/externa (normalmente 3.3V ou 1.8V).",
"Não deve ser usado como fonte geral de energia de alta corrente."
]
},
{
"numero": "25",
"nome": "GPIO19",
"tipo": "io",
"alimentacao": ["VDD_SPI", "VDDPST2"],
"funcoes": [
"GPIO19",
"SPIHD"
],
"categorias": [
"GPIO",
"SPI"
],
"avisos": ["Pino de controle de Hold da interface de memória flash SPI."]
},
{
"numero": "26",
"nome": "GPIO20",
"tipo": "io",
"alimentacao": ["VDD_SPI", "VDDPST2"],
"funcoes": [
"GPIO20",
"SPICLK"
],
"categorias": [
"GPIO",
"SPI"
],
"avisos": ["Pino de sinal de clock da interface de memória flash SPI."]
},
{
"numero": "27",
"nome": "GPIO21",
"tipo": "io",
"alimentacao": ["VDD_SPI", "VDDPST2"],
"funcoes": [
"GPIO21",
"SPID"
],
"categorias": [
"GPIO",
"SPI"
],
"avisos": ["Pino de dados D (MOSI) da interface de memória flash SPI."]
},
{
"numero": "28",
"nome": "GPIO12",
"tipo": "io",
"alimentacao": ["VDDPST2"],
"funcoes": [
"GPIO12",
"USB_D-"
],
"categorias": [
"GPIO",
"USB"
],
"avisos": ["Pino de sinal negativo do transceptor USB Serial/JTAG integrado."]
},
{
"numero": "29",
"nome": "GPIO13",
"tipo": "io",
"alimentacao": ["VDDPST2"],
"funcoes": [
"GPIO13",
"USB_D+"
],
"categorias": [
"GPIO",
"USB"
],
"avisos": [
"Pino de sinal positivo do transceptor USB Serial/JTAG integrado.",
"Possui resistor de pull-up integrado ativado por padrão na inicialização do USB."
]
},
{
"numero": "30",
"nome": "GPIO24",
"tipo": "io",
"alimentacao": ["VDDPST2"],
"funcoes": ["GPIO24"],
"categorias": ["GPIO"]
},
{
"numero": "31",
"nome": "GPIO8",
"tipo": "io",
"alimentacao": ["VDDPST2"],
"funcoes": ["GPIO8"],
"categorias": ["GPIO", "Strapping"],
"avisos": [
"Pino de Strapping para seleção do modo de boot e controle de impressão das mensagens da ROM.",
"Flutuante por padrão durante o reset. Nível alto (1) ativa a impressão do log de inicialização."
]
},
{
"numero": "32",
"nome": "GPIO9",
"tipo": "io",
"alimentacao": ["VDDPST2"],
"funcoes": ["GPIO9"],
"categorias": ["GPIO", "Strapping"],
"avisos": [
"Pino de Strapping principal para seleção do modo de boot.",
"Equipado com pull-up fraco integrado. Nível alto (1) no reset ativa SPI Boot (carrega do flash). Nível baixo (0) força o modo de Download."
]
},
{
"numero": "33",
"nome": "GPIO10",
"tipo": "io",
"alimentacao": ["VDDPST2"],
"funcoes": [
"GPIO10",
"U0RXD"
],
"categorias": [
"GPIO",
"UART"
],
"avisos": ["Recepção serial UART0 (Serial RX para gravação do firmware externa e depuração)."]
},
{
"numero": "34",
"nome": "GPIO11",
"tipo": "io",
"alimentacao": ["VDDPST2"],
"funcoes": [
"GPIO11",
"U0TXD"
],
"categorias": [
"GPIO",
"UART"
],
"avisos": ["Transmissão serial UART0 (Serial TX para gravação do firmware externa e depuração)."]
},
{
"numero": "35",
"nome": "GPIO29",
"tipo": "io",
"alimentacao": ["VDDPST2"],
"funcoes": ["GPIO29"],
"categorias": ["GPIO"]
},
{
"numero": "36",
"nome": "GPIO7",
"tipo": "io",
"alimentacao": ["VDDPST2"],
"funcoes": ["GPIO7"],
"categorias": ["GPIO", "Strapping"],
"avisos": [
"Pino de Strapping para seleção da fonte dos sinais JTAG (inicialização precoce).",
"Sem resistores de pull internos no reset. Deve ser mantido estável por circuito externo se usado."
]
},
{
"numero": "37",
"nome": "VDDA1",
"tipo": "power",
"funcoes": ["Alimentação do domínio analógico de 3.3V"],
"categorias": ["Power"]
},
{
"numero": "38",
"nome": "XTAL_N",
"tipo": "analog",
"funcoes": ["Conexão externa do cristal oscilador de 40 MHz (Saída N)"],
"categorias": ["Clock"]
},
{
"numero": "39",
"nome": "XTAL_P",
"tipo": "analog",
"funcoes": ["Conexão externa do cristal oscilador de 40 MHz (Entrada P)"],
"categorias": ["Clock"]
},
{
"numero": "40",
"nome": "VDDA2",
"tipo": "power",
"funcoes": ["Alimentação do domínio analógico de 3.3V"],
"categorias": ["Power"]
},
{
"numero": "41",
"nome": "GND",
"tipo": "power",
"funcoes": ["Pino de aterramento central (Exposed Pad)"],
"categorias": ["Power"],
"avisos": ["Deve ser adequadamente soldado ao plano de terra (GND) da PCB para controle de RF e térmica estáveis."]
}
]
+956
View File
@@ -0,0 +1,956 @@
[
{
"numero": "1",
"nome": "ANT",
"tipo": "analog",
"funcoes": ["Conexão de antena de RF (Wi-Fi e Bluetooth)"],
"categorias": ["Antena", "RF"],
"avisos": ["Pino de RF dedicado para 2.4 GHz. Requer casamento de impedância de 50 ohms."]
},
{
"numero": "2",
"nome": "VDDA3",
"tipo": "power",
"funcoes": ["Alimentação do domínio analógico (3.3V)"],
"categorias": ["Power"],
"avisos": ["Tensão recomendada: 3.3V (3.0V a 3.6V). Alimenta a seção de RF e clock."]
},
{
"numero": "3",
"nome": "VDDA4",
"tipo": "power",
"funcoes": ["Alimentação do domínio analógico (3.3V)"],
"categorias": ["Power"],
"avisos": ["Tensão recomendada: 3.3V (3.0V a 3.6V). Alimenta a seção de RF e clock."]
},
{
"numero": "4",
"nome": "CHIP_PU",
"tipo": "analog",
"funcoes": ["Habilitar o chip (Power-up / Reset)"],
"categorias": ["Power"],
"avisos": [
"Ativo Alto. Puxe para nível alto para inicializar o chip.",
"Não deixe este pino flutuando. Recomenda-se usar um circuito RC externo de atraso para inicialização estável."
]
},
{
"numero": "5",
"nome": "GPIO0",
"tipo": "io",
"alimentacao": ["VDDPST_1"],
"funcoes": [
"GPIO0",
"XTAL_32K_N"
],
"categorias": [
"GPIO",
"Clock"
],
"avisos": ["Pino de clock de baixo consumo. Pode ser conectado ao cristal de 32.768 kHz externo (Pino N)."]
},
{
"numero": "6",
"nome": "GPIO1",
"tipo": "io",
"alimentacao": ["VDDPST_1"],
"funcoes": [
"GPIO1",
"XTAL_32K_P",
"ADC1_CH0"
],
"categorias": [
"GPIO",
"Clock",
"ADC"
],
"avisos": ["Pino multifuncional. Cristal de 32.768 kHz externo (Pino P) e entrada analógica ADC1 Canal 0."]
},
{
"numero": "7",
"nome": "GPIO2",
"tipo": "io",
"alimentacao": ["VDDPST_1"],
"funcoes": [
"GPIO2",
"ADC1_CH1"
],
"categorias": [
"GPIO",
"ADC"
],
"avisos": ["Pino de entrada analógica ADC1 Canal 1."]
},
{
"numero": "8",
"nome": "GPIO3",
"tipo": "io",
"alimentacao": ["VDDPST_1"],
"funcoes": [
"GPIO3",
"ADC1_CH2"
],
"categorias": [
"GPIO",
"ADC"
],
"avisos": ["Pino de entrada analógica ADC1 Canal 2."]
},
{
"numero": "9",
"nome": "GPIO4",
"tipo": "io",
"alimentacao": ["VDDPST_1"],
"funcoes": [
"GPIO4",
"ADC1_CH3"
],
"categorias": [
"GPIO",
"ADC"
],
"avisos": ["Pino de entrada analógica ADC1 Canal 3."]
},
{
"numero": "10",
"nome": "GPIO5",
"tipo": "io",
"alimentacao": ["VDDPST_1"],
"funcoes": [
"GPIO5",
"ADC1_CH4"
],
"categorias": [
"GPIO",
"ADC"
],
"avisos": ["Pino de entrada analógica ADC1 Canal 4."]
},
{
"numero": "11",
"nome": "VDDPST_1",
"tipo": "power",
"funcoes": ["Alimentação de I/O do domínio 1 (3.3V)"],
"categorias": ["Power"],
"avisos": ["Tensão recomendada: 3.3V."]
},
{
"numero": "12",
"nome": "GPIO6",
"tipo": "io",
"alimentacao": ["VDDPST_1"],
"funcoes": [
"GPIO6",
"ADC1_CH5"
],
"categorias": [
"GPIO",
"ADC"
],
"avisos": ["Pino de entrada analógica ADC1 Canal 5."]
},
{
"numero": "13",
"nome": "GPIO7",
"tipo": "io",
"alimentacao": ["VDDPST_1"],
"funcoes": [
"GPIO7",
"ADC1_CH6"
],
"categorias": [
"GPIO",
"ADC"
],
"avisos": ["Pino de entrada analógica ADC1 Canal 6."]
},
{
"numero": "14",
"nome": "GPIO8",
"tipo": "io",
"alimentacao": ["VDDPST_1"],
"funcoes": [
"GPIO8",
"ADC1_CH7"
],
"categorias": [
"GPIO",
"ADC"
],
"avisos": ["Pino de entrada analógica ADC1 Canal 7."]
},
{
"numero": "15",
"nome": "GPIO9",
"tipo": "io",
"alimentacao": ["VDDPST_1"],
"funcoes": [
"GPIO9",
"ADC1_CH8"
],
"categorias": [
"GPIO",
"ADC"
],
"avisos": ["Pino de entrada analógica ADC1 Canal 8."]
},
{
"numero": "16",
"nome": "GPIO10",
"tipo": "io",
"alimentacao": ["VDDPST_1"],
"funcoes": [
"GPIO10",
"ADC1_CH9"
],
"categorias": [
"GPIO",
"ADC"
],
"avisos": ["Pino de entrada analógica ADC1 Canal 9."]
},
{
"numero": "17",
"nome": "GPIO11",
"tipo": "io",
"alimentacao": ["VDDPST_1"],
"funcoes": [
"GPIO11",
"ADC1_CH10"
],
"categorias": [
"GPIO",
"ADC"
],
"avisos": ["Pino de entrada analógica ADC1 Canal 10."]
},
{
"numero": "18",
"nome": "VREF_TOUCH",
"tipo": "analog",
"funcoes": ["Tensão de referência para os pinos Touch capacitivos"],
"categorias": ["Touch"],
"avisos": ["Pino analógico dedicado para estabilização de leitura capacitiva."]
},
{
"numero": "19",
"nome": "GPIO12",
"tipo": "io",
"alimentacao": ["VDDPST_1"],
"funcoes": [
"GPIO12",
"ADC2_CH0"
],
"categorias": [
"GPIO",
"ADC"
],
"avisos": ["Pino de entrada analógica ADC2 Canal 0."]
},
{
"numero": "20",
"nome": "GPIO13",
"tipo": "io",
"alimentacao": ["VDDPST_1"],
"funcoes": [
"GPIO13",
"ADC2_CH1"
],
"categorias": [
"GPIO",
"ADC"
],
"avisos": ["Pino de entrada analógica ADC2 Canal 1."]
},
{
"numero": "21",
"nome": "GPIO14",
"tipo": "io",
"alimentacao": ["VDDPST_1"],
"funcoes": [
"GPIO14",
"ADC2_CH2"
],
"categorias": [
"GPIO",
"ADC"
],
"avisos": ["Pino de entrada analógica ADC2 Canal 2."]
},
{
"numero": "22",
"nome": "GPIO15",
"tipo": "io",
"alimentacao": ["VDDPST_1"],
"funcoes": [
"GPIO15",
"ADC2_CH3"
],
"categorias": [
"GPIO",
"ADC"
],
"avisos": ["Pino de entrada analógica ADC2 Canal 3."]
},
{
"numero": "23",
"nome": "GPIO16",
"tipo": "io",
"alimentacao": ["VDDPST_1"],
"funcoes": [
"GPIO16",
"ADC2_CH4"
],
"categorias": [
"GPIO",
"ADC"
],
"avisos": ["Pino de entrada analógica ADC2 Canal 4."]
},
{
"numero": "24",
"nome": "GPIO17",
"tipo": "io",
"alimentacao": ["VDDPST_1"],
"funcoes": [
"GPIO17",
"ADC2_CH5"
],
"categorias": [
"GPIO",
"ADC"
],
"avisos": ["Pino de entrada analógica ADC2 Canal 5."]
},
{
"numero": "25",
"nome": "GPIO18",
"tipo": "io",
"alimentacao": ["VDDPST_1"],
"funcoes": [
"GPIO18",
"ADC2_CH6"
],
"categorias": [
"GPIO",
"ADC"
],
"avisos": ["Pino de entrada analógica ADC2 Canal 6."]
},
{
"numero": "26",
"nome": "GPIO19",
"tipo": "io",
"alimentacao": ["VDDPST_1"],
"funcoes": [
"GPIO19",
"ADC2_CH7"
],
"categorias": [
"GPIO",
"ADC"
],
"avisos": ["Pino de entrada analógica ADC2 Canal 7."]
},
{
"numero": "27",
"nome": "SDIO_DATA0",
"tipo": "io",
"alimentacao": ["VDDPST_SD"],
"funcoes": [
"SDIO_DATA0",
"FSPID"
],
"categorias": [
"SDIO",
"SPI"
],
"avisos": ["Pino da interface SDIO de alta velocidade (Dados 0) ou SPI2 MOSI."]
},
{
"numero": "28",
"nome": "SDIO_DATA1",
"tipo": "io",
"alimentacao": ["VDDPST_SD"],
"funcoes": [
"SDIO_DATA1",
"FSPIQ"
],
"categorias": [
"SDIO",
"SPI"
],
"avisos": ["Pino da interface SDIO de alta velocidade (Dados 1) ou SPI2 MISO."]
},
{
"numero": "29",
"nome": "SDIO_DATA2",
"tipo": "io",
"alimentacao": ["VDDPST_SD"],
"funcoes": [
"SDIO_DATA2",
"FSPIWP"
],
"categorias": [
"SDIO",
"SPI"
],
"avisos": ["Pino da interface SDIO de alta velocidade (Dados 2) ou SPI2 Write Protect."]
},
{
"numero": "30",
"nome": "VDD_PSRAM_1P8_1",
"tipo": "power",
"funcoes": ["Alimentação de 1.8V para PSRAM integrada no encapsulamento"],
"categorias": ["Power"],
"avisos": ["Gerado pelo LDO interno para alimentar a PSRAM integrada. Não conectar a circuitos de potência externa."]
},
{
"numero": "31",
"nome": "SDIO_DATA3",
"tipo": "io",
"alimentacao": ["VDDPST_SD"],
"funcoes": [
"SDIO_DATA3",
"FSPIHD"
],
"categorias": [
"SDIO",
"SPI"
],
"avisos": ["Pino da interface SDIO de alta velocidade (Dados 3) ou SPI2 Hold."]
},
{
"numero": "32",
"nome": "SDIO_CLK",
"tipo": "io",
"alimentacao": ["VDDPST_SD"],
"funcoes": [
"SDIO_CLK",
"FSPICLK"
],
"categorias": [
"SDIO",
"SPI"
],
"avisos": ["Clock da interface SDIO de alta velocidade ou SPI2 Clock."]
},
{
"numero": "33",
"nome": "SDIO_CMD",
"tipo": "io",
"alimentacao": ["VDDPST_SD"],
"funcoes": [
"SDIO_CMD",
"FSPICS0"
],
"categorias": [
"SDIO",
"SPI"
],
"avisos": ["Comando da interface SDIO de alta velocidade ou SPI2 Chip Select 0."]
},
{
"numero": "34",
"nome": "VDD_PSRAM_1P8_2",
"tipo": "power",
"funcoes": ["Alimentação de 1.8V para PSRAM integrada no encapsulamento"],
"categorias": ["Power"]
},
{
"numero": "35",
"nome": "VDD_LDO_1P8",
"tipo": "power",
"funcoes": ["Pino de entrada/saída LDO de 1.8V"],
"categorias": ["Power"]
},
{
"numero": "36",
"nome": "SPICS",
"tipo": "io",
"alimentacao": ["VDD_SPI"],
"funcoes": ["SPICS"],
"categorias": ["SPI"],
"avisos": ["Chip Select dedicado para a Flash SPI externa ou interna."]
},
{
"numero": "37",
"nome": "SPIQ",
"tipo": "io",
"alimentacao": ["VDD_SPI"],
"funcoes": ["SPIQ"],
"categorias": ["SPI"],
"avisos": ["Dado Q (MISO) dedicado para a Flash SPI externa ou interna."]
},
{
"numero": "38",
"nome": "SPIWP",
"tipo": "io",
"alimentacao": ["VDD_SPI"],
"funcoes": ["SPIWP"],
"categorias": ["SPI"],
"avisos": ["Write Protect dedicado para a Flash SPI externa ou interna."]
},
{
"numero": "39",
"nome": "VDD_SPI",
"tipo": "power",
"funcoes": ["Tensão de alimentação dedicada para a interface Flash SPI"],
"categorias": ["Power"],
"avisos": ["Tensão típica: 3.3V ou 1.8V gerada internamente."]
},
{
"numero": "40",
"nome": "SPIHD",
"tipo": "io",
"alimentacao": ["VDD_SPI"],
"funcoes": ["SPIHD"],
"categorias": ["SPI"],
"avisos": ["Hold dedicado para a Flash SPI externa ou interna."]
},
{
"numero": "41",
"nome": "SPICLK",
"tipo": "io",
"alimentacao": ["VDD_SPI"],
"funcoes": ["SPICLK"],
"categorias": ["SPI"],
"avisos": ["Clock dedicado para a Flash SPI externa ou interna."]
},
{
"numero": "42",
"nome": "SPID",
"tipo": "io",
"alimentacao": ["VDD_SPI"],
"funcoes": ["SPID"],
"categorias": ["SPI"],
"avisos": ["Dado D (MOSI) dedicado para a Flash SPI externa ou interna."]
},
{
"numero": "43",
"nome": "VCCA/VDDPST_2",
"tipo": "power",
"funcoes": ["Alimentação analógica e digital do domínio 2 (3.3V)"],
"categorias": ["Power"]
},
{
"numero": "44",
"nome": "USB_DP",
"tipo": "analog",
"funcoes": ["Pino positivo do transceptor USB Serial/JTAG integrado"],
"categorias": ["USB"],
"avisos": ["USB D+. Possui pull-up de hardware interno."]
},
{
"numero": "45",
"nome": "USB_DM",
"tipo": "analog",
"funcoes": ["Pino negativo do transceptor USB Serial/JTAG integrado"],
"categorias": ["USB"],
"avisos": ["USB D-."]
},
{
"numero": "46",
"nome": "GPIO33",
"tipo": "io",
"alimentacao": ["VDDPST_3"],
"funcoes": [
"GPIO33",
"ADC2_CH8"
],
"categorias": [
"GPIO",
"ADC"
],
"avisos": ["Pino multifuncional: GPIO33 e ADC2 Canal 8."]
},
{
"numero": "47",
"nome": "GPIO34",
"tipo": "io",
"alimentacao": ["VDDPST_3"],
"funcoes": [
"GPIO34",
"ADC2_CH9"
],
"categorias": [
"GPIO",
"ADC"
],
"avisos": ["Pino multifuncional: GPIO34 e ADC2 Canal 9."]
},
{
"numero": "48",
"nome": "GPIO35",
"tipo": "io",
"alimentacao": ["VDDPST_3"],
"funcoes": [
"GPIO35",
"ADC2_CH10"
],
"categorias": [
"GPIO",
"ADC"
]
},
{
"numero": "49",
"nome": "GPIO36",
"tipo": "io",
"alimentacao": ["VDDPST_3"],
"funcoes": [
"GPIO36",
"ADC2_CH11"
],
"categorias": [
"GPIO",
"ADC"
]
},
{
"numero": "50",
"nome": "GPIO37",
"tipo": "io",
"alimentacao": ["VDDPST_3"],
"funcoes": [
"GPIO37",
"ADC2_CH12"
],
"categorias": [
"GPIO",
"ADC"
]
},
{
"numero": "51",
"nome": "GPIO38",
"tipo": "io",
"alimentacao": ["VDDPST_3"],
"funcoes": [
"GPIO38",
"ADC2_CH13"
],
"categorias": [
"GPIO",
"ADC"
]
},
{
"numero": "52",
"nome": "GPIO39",
"tipo": "io",
"alimentacao": ["VDDPST_3"],
"funcoes": [
"GPIO39",
"ADC2_CH14"
],
"categorias": [
"GPIO",
"ADC"
]
},
{
"numero": "53",
"nome": "GPIO40",
"tipo": "io",
"alimentacao": ["VDDPST_3"],
"funcoes": [
"GPIO40",
"ADC2_CH15"
],
"categorias": [
"GPIO",
"ADC"
]
},
{
"numero": "54",
"nome": "VDDPST_3",
"tipo": "power",
"funcoes": ["Alimentação de I/O do domínio 3 (3.3V)"],
"categorias": ["Power"]
},
{
"numero": "55",
"nome": "GPIO42",
"tipo": "io",
"alimentacao": ["VDDPST_3"],
"funcoes": [
"GPIO42",
"ADC2_CH16"
],
"categorias": [
"GPIO",
"ADC"
]
},
{
"numero": "56",
"nome": "GPIO43",
"tipo": "io",
"alimentacao": ["VDDPST_3"],
"funcoes": [
"GPIO43",
"ADC2_CH17"
],
"categorias": [
"GPIO",
"ADC"
]
},
{
"numero": "57",
"nome": "GPIO44",
"tipo": "io",
"alimentacao": ["VDDPST_3"],
"funcoes": [
"GPIO44",
"ADC2_CH18"
],
"categorias": [
"GPIO",
"ADC"
]
},
{
"numero": "58",
"nome": "GPIO45",
"tipo": "io",
"alimentacao": ["VDDPST_3"],
"funcoes": [
"GPIO45",
"ADC2_CH19"
],
"categorias": [
"GPIO",
"ADC"
]
},
{
"numero": "59",
"nome": "GPIO46",
"tipo": "io",
"alimentacao": ["VDDPST_3"],
"funcoes": [
"GPIO46",
"ADC2_CH20"
],
"categorias": [
"GPIO",
"ADC"
]
},
{
"numero": "60",
"nome": "GPIO47",
"tipo": "io",
"alimentacao": ["VDDPST_3"],
"funcoes": [
"GPIO47",
"ADC2_CH21"
],
"categorias": [
"GPIO",
"ADC"
]
},
{
"numero": "61",
"nome": "GPIO48",
"tipo": "io",
"alimentacao": ["VDDPST_3"],
"funcoes": ["GPIO48"],
"categorias": ["GPIO"]
},
{
"numero": "62",
"nome": "GPIO49",
"tipo": "io",
"alimentacao": ["VDDPST_3"],
"funcoes": ["GPIO49"],
"categorias": ["GPIO"]
},
{
"numero": "63",
"nome": "VREF_ADC",
"tipo": "analog",
"funcoes": ["Tensão de referência estável externa para calibração do ADC"],
"categorias": ["Power", "ADC"],
"avisos": ["Pino analógico dedicado para calibração precisa do conversor analógico-digital."]
},
{
"numero": "64",
"nome": "VDDPST_4",
"tipo": "power",
"funcoes": ["Alimentação de I/O do domínio 4 (3.3V)"],
"categorias": ["Power"]
},
{
"numero": "65",
"nome": "GPIO50",
"tipo": "io",
"alimentacao": ["VDDPST_4"],
"funcoes": ["GPIO50"],
"categorias": ["GPIO"]
},
{
"numero": "66",
"nome": "GPIO51",
"tipo": "io",
"alimentacao": ["VDDPST_4"],
"funcoes": ["GPIO51"],
"categorias": ["GPIO"]
},
{
"numero": "67",
"nome": "GPIO52",
"tipo": "io",
"alimentacao": ["VDDPST_4"],
"funcoes": ["GPIO52"],
"categorias": ["GPIO"]
},
{
"numero": "68",
"nome": "GPIO53",
"tipo": "io",
"alimentacao": ["VDDPST_4"],
"funcoes": ["GPIO53"],
"categorias": ["GPIO"]
},
{
"numero": "69",
"nome": "GPIO54",
"tipo": "io",
"alimentacao": ["VDDPST_4"],
"funcoes": [
"GPIO54",
"MTDO"
],
"categorias": [
"GPIO",
"JTAG"
],
"avisos": ["Pino multifuncional: JTAG MTDO."]
},
{
"numero": "70",
"nome": "GPIO55",
"tipo": "io",
"alimentacao": ["VDDPST_4"],
"funcoes": [
"GPIO55",
"MTCK"
],
"categorias": [
"GPIO",
"JTAG"
],
"avisos": ["Pino multifuncional: JTAG MTCK."]
},
{
"numero": "71",
"nome": "GPIO56",
"tipo": "io",
"alimentacao": ["VDDPST_4"],
"funcoes": [
"GPIO56",
"MTDI"
],
"categorias": [
"GPIO",
"JTAG"
],
"avisos": ["Pino multifuncional: JTAG MTDI."]
},
{
"numero": "72",
"nome": "GPIO57",
"tipo": "io",
"alimentacao": ["VDDPST_4"],
"funcoes": [
"GPIO57",
"MTMS"
],
"categorias": [
"GPIO",
"JTAG"
],
"avisos": ["Pino multifuncional: JTAG MTMS."]
},
{
"numero": "73",
"nome": "GPIO58",
"tipo": "io",
"alimentacao": ["VDDPST_4"],
"funcoes": ["GPIO58"],
"categorias": ["GPIO"]
},
{
"numero": "74",
"nome": "GPIO59",
"tipo": "io",
"alimentacao": ["VDDPST_4"],
"funcoes": ["GPIO59"],
"categorias": ["GPIO"]
},
{
"numero": "75",
"nome": "GPIO60",
"tipo": "io",
"alimentacao": ["VDDPST_4"],
"funcoes": ["GPIO60"],
"categorias": ["GPIO", "Strapping"],
"avisos": [
"Pino de Strapping para boot (junto com GPIO61).",
"Equipado com pull-up interno ativo por padrão."
]
},
{
"numero": "76",
"nome": "GPIO61",
"tipo": "io",
"alimentacao": ["VDDPST_4"],
"funcoes": ["GPIO61"],
"categorias": ["GPIO", "Strapping"],
"avisos": [
"Pino de Strapping principal para seleção de boot.",
"Equipado com pull-up interno ativo por padrão. Nível alto (1) no reset ativa SPI Boot. Nível baixo (0) força o modo Download."
]
},
{
"numero": "77",
"nome": "VDDA1",
"tipo": "power",
"funcoes": ["Alimentação do domínio analógico de 3.3V"],
"categorias": ["Power"]
},
{
"numero": "78",
"nome": "XTAL_N",
"tipo": "analog",
"funcoes": ["Conexão externa do cristal oscilador de alta velocidade (Saída N)"],
"categorias": ["Clock"]
},
{
"numero": "79",
"nome": "XTAL_P",
"tipo": "analog",
"funcoes": ["Conexão externa do cristal oscilador de alta velocidade (Entrada P)"],
"categorias": ["Clock"]
},
{
"numero": "80",
"nome": "VDDA2",
"tipo": "power",
"funcoes": ["Alimentação do domínio analógico de 3.3V"],
"categorias": ["Power"]
},
{
"numero": "81",
"nome": "GND",
"tipo": "power",
"funcoes": ["Pino de aterramento central (Exposed Pad)"],
"categorias": ["Power"],
"avisos": ["Deve ser adequadamente soldado ao plano de terra (GND) da PCB para controle de RF e dissipação térmica estáveis."]
}
]
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.

Before

Width:  |  Height:  |  Size: 337 KiB

After

Width:  |  Height:  |  Size: 372 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 365 KiB

After

Width:  |  Height:  |  Size: 399 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 301 KiB

After

Width:  |  Height:  |  Size: 336 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 321 KiB

After

Width:  |  Height:  |  Size: 350 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 MiB

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.6 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 472 KiB

After

Width:  |  Height:  |  Size: 542 KiB

+145 -20
View File
@@ -131,6 +131,86 @@
"placa": "/placas/esp32-s3.png", "placa": "/placas/esp32-s3.png",
"cor": "#fd7e14" "cor": "#fd7e14"
}, },
"ESP32-S31": {
"nome_completo": "ESP32-S31",
"icone": "🌀",
"descricao": "Processador dual-core RISC-V de alta performance a 320MHz com Wi-Fi 6, Bluetooth 5.4 e Classic, Zigbee/Thread, Matter e aceleração IA",
"arquitetura": "RISC-V dual-core de 32 bits (HP) + RISC-V single-core (LP)",
"nucleos": "2 (Dual-core HP) + 1 (Single-core LP)",
"frequencia": "Até 320 MHz",
"coprocessador_ulp": "Sim (RISC-V, LP CPU de 40 MHz)",
"aceleradores_ia": "Sim (Custom AI and DSP extension XespV)",
"wifi": "Wi-Fi 6 (802.11ax) 2.4 GHz",
"bluetooth": "Bluetooth 5.4 LE e Bluetooth Classic",
"zigbee_thread": "IEEE 802.15.4 (Zigbee 3.0, Thread 1.4)",
"matter": "Sim",
"memoria_sram": "512 KB (HP SRAM)",
"memoria_sram_rtc": "32 KB (LP SRAM)",
"memoria_rom": "320 KB",
"flash_externa": "Até 256 MB via QSPI",
"psram_externa": "Suporta até 64 MB (16 MB ou 32 MB DDR OPI integrados no encapsulamento)",
"gpio": "60",
"adc": "2x SAR ADC de 12 bits, até 16 canais",
"dac": "Sim (2x DAC de 10 bits e 2x DAC de 12 bits)",
"touch": "14 canais capacitivos",
"uart": "5 (4 UART + 1 LP UART)",
"spi": "7 (6 SPI + 1 LP SPI)",
"i2c": "3 (2 I2C + 1 LP I2C)",
"i2s": "2",
"pwm": "LED PWM (até 8 canais), Motor PWM (MCPWM)",
"usb": "1 (USB 2.0 High-Speed OTG e USB Serial/JTAG)",
"can": "Sim (CAN FD Controller)",
"sensor_temperatura": "1 (interno)",
"consumo_energia": {
"active": "Consumo otimizado de alta performance configurável",
"light_sleep": "0.1-0.8 mA",
"deep_sleep": "Baixo consumo com LP CPU ativa"
},
"temperatura_operacao": "-40°C a 85°C",
"temperatura_armazenamento": "-40°C a 150°C",
"datasheet": "https://documentation.espressif.com/esp32-s31_datasheet_en.html",
"manual_tecnico": "https://documentation.espressif.com/esp32-s31_technical_reference_manual_en.html",
"guia_de_programacao": "https://docs.espressif.com/projects/esp-idf/en/latest/esp32s31/get-started/index.html",
"placa": "/placas/esp32-s31.png",
"cor": "#ff2a5f"
},
"ESP32-C2": {
"nome_completo": "ESP32-C2 (ESP8684)",
"icone": "🌱",
"descricao": "Microcontrolador RISC-V ultra econômico e de baixo consumo com Wi-Fi 4 e Bluetooth 5.0 LE",
"arquitetura": "RISC-V single-core de 32 bits",
"nucleos": "1 (Single-core)",
"frequencia": "Até 120 MHz",
"wifi": "IEEE 802.11 b/g/n (2.4 GHz, até 72.2 Mbps)",
"bluetooth": "Bluetooth 5.0 LE",
"memoria_sram": "272 KB (16 KB para cache)",
"memoria_sram_rtc": "Não",
"memoria_rom": "576 KB",
"flash_externa": "Não suportada diretamente (2 MB ou 4 MB de Flash integrados no encapsulamento)",
"psram_externa": "Não",
"gpio": "14",
"adc": "1x SAR ADC de 12 bits, até 5 canais",
"dac": "Não",
"touch": "Não",
"uart": "2",
"spi": "3 (SPI0/1 para Flash interna, SPI2 para uso geral)",
"i2c": "1 (Master-only)",
"i2s": "Não",
"pwm": "LED PWM (até 6 canais)",
"sensor_temperatura": "1 (interno)",
"consumo_energia": {
"active_cpu_120mhz": "13 mA",
"light_sleep": "130 µA",
"deep_sleep": "5 µA"
},
"temperatura_operacao": "-40°C a 105°C",
"temperatura_armazenamento": "-40°C a 150°C",
"datasheet": "https://documentation.espressif.com/esp32-c2_datasheet_en.html",
"manual_tecnico": "https://documentation.espressif.com/esp32-c2_technical_reference_manual_en.html",
"guia_de_programacao": "https://docs.espressif.com/projects/esp-idf/en/latest/esp32c2/get-started/index.html",
"placa": "/placas/esp32-c2.png",
"cor": "#8c52ff"
},
"ESP32-C3": { "ESP32-C3": {
"nome_completo": "ESP32-C3", "nome_completo": "ESP32-C3",
"icone": "💎", "icone": "💎",
@@ -174,20 +254,22 @@
"ESP32-C5": { "ESP32-C5": {
"nome_completo": "ESP32-C5", "nome_completo": "ESP32-C5",
"icone": "📶", "icone": "📶",
"descricao": "O primeiro com suporte a Wi-Fi 6 dual-band e Bluetooth 5.3, equipado com um processador RISC-V", "descricao": "O primeiro com suporte a Wi-Fi 6 dual-band e Bluetooth 6.0, equipado com CPU RISC-V e coprocessador ULP",
"arquitetura": "RISC-V single-core de 32 bits", "arquitetura": "RISC-V single-core de 32 bits (HP) + RISC-V single-core (LP)",
"nucleos": "1 (Single-core)", "nucleos": "1 (Single-core HP) + 1 (Single-core LP)",
"frequencia": "Até 240 MHz", "frequencia": "Até 240 MHz",
"coprocessador_ulp": "Sim (RISC-V, LP CPU de 48 MHz)",
"wifi": "Wi-Fi 6 (802.11ax) dual-band 2.4 GHz e 5 GHz", "wifi": "Wi-Fi 6 (802.11ax) dual-band 2.4 GHz e 5 GHz",
"bluetooth": "Bluetooth 5.3 LE", "bluetooth": "Bluetooth 6.0 LE",
"zigbee_thread": "IEEE 802.15.4", "zigbee_thread": "IEEE 802.15.4 (Zigbee 3.0, Thread 1.4)",
"memoria_sram": "560 KB", "matter": "Sim",
"memoria_sram": "384 KB",
"memoria_sram_rtc": "16 KB", "memoria_sram_rtc": "16 KB",
"memoria_rom": "320 KB", "memoria_rom": "320 KB",
"flash_externa": "Até 16 MB via QSPI", "flash_externa": "Até 16 MB via QSPI",
"psram_externa": "Não", "psram_externa": "Não",
"gpio": "29", "gpio": "29",
"adc": "1x SAR ADC de 12 bits, até 7 canais", "adc": "1x SAR ADC de 12 bits, até 6 canais",
"dac": "Não", "dac": "Não",
"touch": "Não", "touch": "Não",
"uart": "3", "uart": "3",
@@ -213,20 +295,21 @@
"ESP32-C6": { "ESP32-C6": {
"nome_completo": "ESP32-C6", "nome_completo": "ESP32-C6",
"icone": "🌐", "icone": "🌐",
"descricao": "Recursos de segurança avançada, Wi-Fi 6, Bluetooth, Zigbee e Thread com suporte a Matter", "descricao": "Recursos de segurança avançada, Wi-Fi 6, Bluetooth 5.3, Zigbee e Thread com suporte a Matter",
"arquitetura": "RISC-V single-core de 32 bits", "arquitetura": "RISC-V single-core de 32 bits (HP) + RISC-V single-core (LP)",
"nucleos": "1 (Single-core)", "nucleos": "1 (Single-core HP) + 1 (Single-core LP)",
"frequencia": "Até 160 MHz", "frequencia": "Até 160 MHz",
"coprocessador_ulp": "Sim (RISC-V, LP CPU de 20 MHz)",
"wifi": "Wi-Fi 6 (802.11ax) 2.4 GHz", "wifi": "Wi-Fi 6 (802.11ax) 2.4 GHz",
"bluetooth": "Bluetooth 5.3 LE", "bluetooth": "Bluetooth 5.3 LE",
"zigbee_thread": "IEEE 802.15.4 (Zigbee, Thread)", "zigbee_thread": "IEEE 802.15.4 (Zigbee 3.0, Thread 1.3)",
"matter": "Sim", "matter": "Sim",
"memoria_sram": "512 KB", "memoria_sram": "512 KB",
"memoria_sram_rtc": "16 KB", "memoria_sram_rtc": "16 KB",
"memoria_rom": "320 KB", "memoria_rom": "320 KB",
"flash_externa": "Até 16 MB via QSPI", "flash_externa": "Até 16 MB via QSPI",
"psram_externa": "Não", "psram_externa": "Não",
"gpio": "30 ou 22", "gpio": "30",
"adc": "1x SAR ADC de 12 bits, até 7 canais", "adc": "1x SAR ADC de 12 bits, até 7 canais",
"dac": "Não", "dac": "Não",
"touch": "Não", "touch": "Não",
@@ -236,7 +319,7 @@
"i2s": "1", "i2s": "1",
"pwm": "LED PWM (até 6 canais), Motor PWM", "pwm": "LED PWM (até 6 canais), Motor PWM",
"usb": "1 (USB Serial/JTAG)", "usb": "1 (USB Serial/JTAG)",
"sdio": "1 (SD/SDIO/MMC Host)", "sdio": "1 (SDIO 2.0 Slave)",
"sensor_temperatura": "1 (interno)", "sensor_temperatura": "1 (interno)",
"consumo_energia": { "consumo_energia": {
"active_wifi6": "Varia conforme perfil TX/RX", "active_wifi6": "Varia conforme perfil TX/RX",
@@ -251,11 +334,52 @@
"placa": "/placas/esp32-c6.png", "placa": "/placas/esp32-c6.png",
"cor": "#17a2b8" "cor": "#17a2b8"
}, },
"ESP32-C61": {
"nome_completo": "ESP32-C61",
"icone": "🛰️",
"descricao": "Microcontrolador RISC-V econômico de 160MHz com Wi-Fi 6, Bluetooth 5.0 LE e suporte a Flash/PSRAM no encapsulamento",
"arquitetura": "RISC-V single-core de 32 bits",
"nucleos": "1 (Single-core)",
"frequencia": "Até 160 MHz",
"wifi": "Wi-Fi 6 (802.11ax) 2.4 GHz",
"bluetooth": "Bluetooth 5.0 LE",
"zigbee_thread": "Não",
"matter": "Não",
"memoria_sram": "320 KB",
"memoria_sram_rtc": "Não",
"memoria_rom": "256 KB",
"flash_externa": "Até 16 MB via QSPI (modelos com 4 MB ou 8 MB integrados no encapsulamento)",
"psram_externa": "Até 16 MB via QSPI (modelos com 2 MB ou 8 MB integrados no encapsulamento)",
"gpio": "30",
"adc": "1x SAR ADC de 12 bits, até 4 canais",
"dac": "Não",
"touch": "Não",
"uart": "3",
"spi": "3 (2 para Flash/PSRAM, 1 para uso geral)",
"i2c": "1",
"i2s": "1",
"pwm": "LED PWM (até 6 canais)",
"usb": "1 (USB Serial/JTAG)",
"sdio": "1 (SDIO 2.0 Slave)",
"sensor_temperatura": "1 (interno)",
"consumo_energia": {
"active": "Baixo consumo com clock dinâmico",
"light_sleep": "0.1-0.8 mA",
"deep_sleep": "7 µA"
},
"temperatura_operacao": "-40°C a 105°C",
"temperatura_armazenamento": "-40°C a 150°C",
"datasheet": "https://documentation.espressif.com/esp32-c61_datasheet_en.html",
"manual_tecnico": "https://documentation.espressif.com/esp32-c61_technical_reference_manual_en.html",
"guia_de_programacao": "https://docs.espressif.com/projects/esp-idf/en/latest/esp32c61/get-started/index.html",
"placa": "/placas/esp32-c61.png",
"cor": "#0dcaf0"
},
"ESP32-P4": { "ESP32-P4": {
"nome_completo": "ESP32-P4", "nome_completo": "ESP32-P4",
"icone": "⚡", "icone": "⚡",
"descricao": "Alto desempenho com processador RISC-V dual-core a 400MHz e aceleradores de IA", "descricao": "Alto desempenho com processador RISC-V dual-core a 400MHz, aceleradores de IA e ricas interfaces de mídia",
"arquitetura": "RISC-V de 32 bits dual-core de alto desempenho + single-core de baixo consumo", "arquitetura": "RISC-V de 32 bits dual-core de alto desempenho (HP) + single-core de baixo consumo (LP)",
"nucleos": "2 (Dual-core HP) + 1 (Single-core LP)", "nucleos": "2 (Dual-core HP) + 1 (Single-core LP)",
"frequencia": "Até 400 MHz", "frequencia": "Até 400 MHz",
"aceleradores_ia": "Sim (Vector Instructions, aceleradores de IA)", "aceleradores_ia": "Sim (Vector Instructions, aceleradores de IA)",
@@ -263,9 +387,10 @@
"bluetooth": "Não", "bluetooth": "Não",
"ethernet": "Sim (Ethernet MAC integrado)", "ethernet": "Sim (Ethernet MAC integrado)",
"memoria_sram": "768 KB", "memoria_sram": "768 KB",
"memoria_rom": "768 KB", "memoria_sram_rtc": "32 KB",
"memoria_rom": "128 KB",
"flash_externa": "Até 128 MB via QSPI/Octal SPI", "flash_externa": "Até 128 MB via QSPI/Octal SPI",
"psram_externa": "Até 32 MB via QSPI/Octal SPI", "psram_externa": "Suporta até 64 MB (16 MB ou 32 MB integrados no encapsulamento via OPI/HPI)",
"gpio": "55", "gpio": "55",
"adc": "1x SAR ADC de 12 bits, até 8 canais", "adc": "1x SAR ADC de 12 bits, até 8 canais",
"dac": "Não", "dac": "Não",
@@ -298,13 +423,13 @@
"ESP32-H2": { "ESP32-H2": {
"nome_completo": "ESP32-H2", "nome_completo": "ESP32-H2",
"icone": "🔋", "icone": "🔋",
"descricao": "Focado em ultra-baixo consumo com Bluetooth, Zigbee e Thread. Mas sem suporte a Wi-Fi", "descricao": "Focado em ultra-baixo consumo com Bluetooth 5.3, Zigbee e Thread. Mas sem suporte a Wi-Fi",
"arquitetura": "RISC-V single-core de 32 bits", "arquitetura": "RISC-V single-core de 32 bits",
"nucleos": "1 (Single-core)", "nucleos": "1 (Single-core)",
"frequencia": "Até 96 MHz", "frequencia": "Até 96 MHz",
"wifi": "Não", "wifi": "Não",
"bluetooth": "Bluetooth 5.2 LE", "bluetooth": "Bluetooth 5.3 LE",
"zigbee_thread": "IEEE 802.15.4 (Zigbee, Thread)", "zigbee_thread": "IEEE 802.15.4 (Zigbee 3.0, Thread 1.3)",
"matter": "Sim", "matter": "Sim",
"memoria_sram": "320 KB", "memoria_sram": "320 KB",
"memoria_sram_rtc": "16 KB", "memoria_sram_rtc": "16 KB",