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
+6484 -3824

No files matched your search

+337 -328
View File
@@ -4,380 +4,389 @@ import { useState, useEffect } from 'react';
import Header from "@/components/Header";
import Footer from "@/components/Footer";
import BoardCard from "@/components/BoardCard";
import {
Search,
Filter,
ShoppingCart,
CheckCircle,
AlertTriangle,
RefreshCw,
Layers,
ChevronDown
} from 'lucide-react';
export default function PlacasPage() {
const [boards, setBoards] = useState([]);
const [filteredBoards, setFilteredBoards] = useState([]);
const [headers, setHeaders] = useState([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
const [searchTerm, setSearchTerm] = useState('');
const [activeFilters, setActiveFilters] = useState({});
const [filterOptions, setFilterOptions] = useState({});
const [boards, setBoards] = useState([]);
const [filteredBoards, setFilteredBoards] = useState([]);
const [headers, setHeaders] = useState([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
const [searchTerm, setSearchTerm] = useState('');
const [activeFilters, setActiveFilters] = useState({});
const [filterOptions, setFilterOptions] = useState({});
const excludedColumns = ['Nome', 'Link', 'Imagem'];
const excludedColumns = ['Nome', 'Link', 'Imagem', 'Loja Oficial'];
useEffect(() => {
fetchBoards();
}, []);
useEffect(() => {
fetchBoards();
}, []);
useEffect(() => {
applyFilters();
}, [boards, searchTerm, activeFilters]);
useEffect(() => {
applyFilters();
}, [boards, searchTerm, activeFilters]);
const fetchBoards = async () => {
try {
setLoading(true);
const response = await fetch('/api/placas');
const fetchBoards = async () => {
try {
setLoading(true);
const response = await fetch('/api/placas');
if (!response.ok) {
throw new Error('Erro ao carregar dados das placas');
}
if (!response.ok) {
throw new Error('Erro ao carregar dados das placas');
}
const result = await response.json();
setBoards(result.data);
setHeaders(result.headers);
setFilteredBoards(result.data);
const result = await response.json();
setBoards(result.data);
setHeaders(result.headers);
setFilteredBoards(result.data);
generateFilterOptions(result.data, result.headers);
} catch (err) {
setError(err.message);
} finally {
setLoading(false);
}
};
generateFilterOptions(result.data, result.headers);
} catch (err) {
setError(err.message);
} finally {
setLoading(false);
}
};
const generateFilterOptions = (data, headers) => {
const options = {};
const filterableColumns = headers.filter(h => !excludedColumns.includes(h));
const generateFilterOptions = (data, headers) => {
const options = {};
const filterableColumns = headers.filter(h => !excludedColumns.includes(h));
filterableColumns.forEach(column => {
const uniqueValues = new Set();
data.forEach(item => {
const value = item[column];
if (value && value.toString().trim()) {
uniqueValues.add(value.toString().trim());
}
});
filterableColumns.forEach(column => {
const uniqueValues = new Set();
data.forEach(item => {
const value = item[column];
if (value && value.toString().trim()) {
uniqueValues.add(value.toString().trim());
}
});
const values = Array.from(uniqueValues).sort();
const values = Array.from(uniqueValues).sort();
// Verificar se é um filtro binário (apenas Sim/Não/Opcional)
const binaryValues = ['Sim', 'Não', 'Opcional'];
const isBinaryFilter = values.every(v => binaryValues.includes(v));
// Verificar se é um filtro binário (apenas Sim/Não/Opcional)
const binaryValues = ['Sim', 'Não', 'Opcional'];
const isBinaryFilter = values.every(v => binaryValues.includes(v));
options[column] = {
values: values,
isBinary: isBinaryFilter
};
});
options[column] = {
values: values,
isBinary: isBinaryFilter
};
});
setFilterOptions(options);
};
setFilterOptions(options);
};
const applyFilters = () => {
let filtered = [...boards];
const applyFilters = () => {
let filtered = [...boards];
if (searchTerm) {
filtered = filtered.filter(board => {
const searchLower = searchTerm.toLowerCase();
return (
board.Nome?.toLowerCase().includes(searchLower) ||
board.Descrição?.toLowerCase().includes(searchLower)
);
});
}
if (searchTerm) {
filtered = filtered.filter(board => {
const searchLower = searchTerm.toLowerCase();
return (
board.Nome?.toLowerCase().includes(searchLower) ||
board.Descrição?.toLowerCase().includes(searchLower)
);
});
}
Object.keys(activeFilters).forEach(column => {
if (activeFilters[column] && activeFilters[column].length > 0) {
filtered = filtered.filter(board => {
const boardValue = board[column]?.toString().trim();
Object.keys(activeFilters).forEach(column => {
if (activeFilters[column] && activeFilters[column].length > 0) {
filtered = filtered.filter(board => {
const boardValue = board[column]?.toString().trim();
// Para filtros binários, o valor armazenado é 'binary-enabled'
if (activeFilters[column].includes('binary-enabled')) {
return boardValue === 'Sim' || boardValue === 'Opcional';
}
// Para filtros binários, o valor armazenado é 'binary-enabled'
if (activeFilters[column].includes('binary-enabled')) {
return boardValue === 'Sim' || boardValue === 'Opcional';
}
return activeFilters[column].includes(boardValue);
});
}
});
return activeFilters[column].includes(boardValue);
});
}
});
setFilteredBoards(filtered);
};
setFilteredBoards(filtered);
};
const handleFilterToggle = (column, value) => {
setActiveFilters(prev => {
const newFilters = { ...prev };
const handleFilterToggle = (column, value) => {
setActiveFilters(prev => {
const newFilters = { ...prev };
if (!newFilters[column]) {
newFilters[column] = [value];
} else {
const index = newFilters[column].indexOf(value);
if (index > -1) {
// Remove o valor (criar novo array sem o valor)
newFilters[column] = newFilters[column].filter(v => v !== value);
if (newFilters[column].length === 0) {
delete newFilters[column];
}
} else {
// Adiciona o valor (criar novo array com o valor)
newFilters[column] = [...newFilters[column], value];
}
}
if (!newFilters[column]) {
newFilters[column] = [value];
} else {
const index = newFilters[column].indexOf(value);
if (index > -1) {
// Remove o valor
newFilters[column] = newFilters[column].filter(v => v !== value);
if (newFilters[column].length === 0) {
delete newFilters[column];
}
} else {
// Adiciona o valor
newFilters[column] = [...newFilters[column], value];
}
}
return newFilters;
});
};
return newFilters;
});
};
const clearFilters = () => {
setActiveFilters({});
setSearchTerm('');
};
const clearFilters = () => {
setActiveFilters({});
setSearchTerm('');
};
const activeFilterCount = Object.keys(activeFilters).length + (searchTerm ? 1 : 0);
const activeFilterCount = Object.keys(activeFilters).length + (searchTerm ? 1 : 0);
if (loading) {
return (
<div className="bg-linear-to-br from-blue-50 via-white to-purple-50 min-h-screen">
<Header />
if (loading) {
return (
<div className="bg-gradient-to-br from-slate-50 via-white to-purple-50/40 dark:from-slate-950 dark:via-slate-950 dark:to-slate-900 min-h-screen text-slate-900 dark:text-slate-100 flex flex-col justify-between">
<Header />
<main className="max-w-7xl mx-auto px-4 py-16">
<div className="text-center">
<div className="text-6xl mb-4">⏳</div>
<p className="text-xl text-gray-600">Carregando placas...</p>
</div>
</main>
<main className="max-w-7xl mx-auto px-6 py-24 flex-1 flex items-center justify-center">
<div className="text-center select-none animate-pulse">
<RefreshCw className="w-12 h-12 text-purple-500 animate-spin mx-auto mb-4" />
<p className="text-xs font-bold text-slate-400 dark:text-slate-500 uppercase tracking-widest">Consultando banco de placas...</p>
</div>
</main>
<Footer />
</div>
);
}
<Footer />
</div>
);
}
if (error) {
return (
<div className="bg-linear-to-br from-blue-50 via-white to-purple-50 min-h-screen">
<Header />
if (error) {
return (
<div className="bg-gradient-to-br from-slate-50 via-white to-purple-50/40 dark:from-slate-950 dark:via-slate-950 dark:to-slate-900 min-h-screen text-slate-900 dark:text-slate-100 flex flex-col justify-between">
<Header />
<main className="max-w-7xl mx-auto px-4 py-16">
<div className="text-center">
<div className="text-6xl mb-4">❌</div>
<p className="text-xl text-red-600 mb-4">Erro ao carregar dados</p>
<p className="text-gray-600 mb-6">{error}</p>
<main className="max-w-7xl mx-auto px-6 py-24 flex-1 flex items-center justify-center">
<div className="text-center bg-white/80 dark:bg-slate-900/40 backdrop-blur-xl border border-slate-200/60 dark:border-slate-800/80 rounded-3xl p-8 max-w-md shadow-2xl select-none">
<div className="w-12 h-12 bg-red-500/10 border border-red-500/20 rounded-2xl flex items-center justify-center text-red-500 mx-auto mb-4">
<AlertTriangle className="w-6 h-6" />
</div>
<p className="text-sm font-bold text-red-600 dark:text-red-400 mb-2 uppercase tracking-widest">Falha ao sincronizar dados</p>
<p className="text-xs text-slate-450 dark:text-slate-550 mb-6 leading-relaxed">{error}</p>
<button
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"
>
Tentar Novamente
</button>
</div>
</main>
<button
onClick={fetchBoards}
className="inline-flex items-center gap-2 bg-slate-900 dark:bg-slate-100 text-white dark:text-slate-950 px-5 py-3 rounded-xl font-bold text-xs uppercase tracking-wider hover:opacity-90 active:scale-95 transition-all shadow-md cursor-pointer"
>
<RefreshCw className="w-4 h-4" />
<span>Tentar Novamente</span>
</button>
</div>
</main>
<Footer />
</div>
);
}
<Footer />
</div>
);
}
const filterableColumns = headers.filter(h => !excludedColumns.includes(h));
const filterableColumns = headers.filter(h => !excludedColumns.includes(h));
return (
<div className="bg-linear-to-br from-blue-50 via-white to-purple-50 min-h-screen">
<Header />
return (
<div className="bg-gradient-to-br from-slate-50 via-white to-purple-50/40 dark:from-slate-950 dark:via-slate-950 dark:to-slate-900 min-h-screen text-slate-900 dark:text-slate-100 transition-colors duration-300">
<Header />
<main id="conteudo" className="max-w-7xl mx-auto px-4 py-16">
<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">
<span className="text-xl">🛒</span>
<span className="text-sm font-semibold text-gray-700">Encontre sua placa ideal</span>
</div>
<main id="conteudo" className="max-w-7xl mx-auto px-6 pt-16 pb-24">
{/* Top Hero Heading Block */}
<section className="text-center mb-16 select-none">
<div className="inline-flex items-center gap-2 bg-white/70 dark:bg-slate-900/50 backdrop-blur-md px-4 py-2 rounded-full mb-6 border border-slate-200/60 dark:border-slate-800/60 shadow-xs">
<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>
<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">
Catálogo de Placas ESP32
</h1>
<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
</h1>
<p className="text-xl md:text-2xl text-gray-700 mb-4 font-medium">
Encontre a placa perfeita para o seu projeto
</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">
<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" />
</svg>
<p className="text-sm text-green-700 font-bold">
Todos os links são verificados e de fontes confiáveis
</p>
</div>
</section>
<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">
Compare e selecione placas oficiais e alternativas de desenvolvimento do microcontrolador ESP32 para seus projetos.
</p>
<div className="grid lg:grid-cols-4 gap-8">
<div className="lg:col-span-1">
<div className="bg-white rounded-2xl shadow-lg overflow-hidden sticky top-4">
{/* Header dos Filtros */}
<div className="bg-linear-to-r from-blue-600 to-purple-600 p-6 text-white">
<div className="flex justify-between items-center">
<div>
<h2 className="text-xl font-bold flex items-center gap-2">
🔍 Filtros
</h2>
{activeFilterCount > 0 && (
<p className="text-sm text-blue-100 mt-1">
{activeFilterCount} filtro(s) ativo(s)
</p>
)}
</div>
{activeFilterCount > 0 && (
<button
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"
>
Limpar
</button>
)}
</div>
</div>
<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">
<CheckCircle className="w-4 h-4 shrink-0" />
<p className="text-[10px] font-bold uppercase tracking-wider">
Links de lojas integradas de fontes verificadas e confiáveis
</p>
</div>
</section>
{/* Conteúdo dos Filtros */}
<div className="p-6">
{/* Barra de Busca */}
<div className="mb-6">
<label className="block text-xs font-semibold text-gray-600 mb-2 uppercase tracking-wide">
Buscar por nome
</label>
<div className="relative">
<input
type="text"
placeholder="Digite para buscar..."
value={searchTerm}
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"
/>
<svg className="absolute left-3 top-3.5 w-5 h-5 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<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 className="grid lg:grid-cols-4 gap-8">
{/* Left Filters Sidebar */}
<div className="lg:col-span-1">
<div className="bg-white/80 dark:bg-slate-900/40 backdrop-blur-xl border border-slate-200/60 dark:border-slate-800/80 rounded-3xl shadow-xl overflow-hidden sticky top-4">
{/* Separador */}
<div className="border-t border-gray-200 my-2"></div>
{/* 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>
<h2 className="text-base font-display font-extrabold flex items-center gap-2">
<Filter className="w-4.5 h-4.5" />
<span>Filtros de Busca</span>
</h2>
{activeFilterCount > 0 && (
<p className="text-[10px] text-blue-150 font-bold uppercase tracking-wider mt-1.5 animate-pulse">
{activeFilterCount} selecionado(s)
</p>
)}
</div>
{/* Filtros por Categoria */}
<div className="space-y-2 max-h-[calc(100vh-450px)] overflow-y-auto pr-2 custom-scrollbar">
{filterableColumns.map((column) => {
const filterData = filterOptions[column];
if (!filterData) return null;
{activeFilterCount > 0 && (
<button
onClick={clearFilters}
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
</button>
)}
</div>
</div>
const isActive = activeFilters[column]?.length > 0;
{/* Sidebar Filters Area */}
<div className="p-6 space-y-6">
{/* Text search by name */}
<div>
<label className="block text-[9px] font-bold text-slate-400 dark:text-slate-500 mb-2.5 uppercase tracking-widest select-none">
Buscar Placa
</label>
<div className="relative">
<input
type="text"
placeholder="Digite o nome da placa..."
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
className="w-full px-4 py-3 pl-10 border border-slate-200 dark:border-slate-800 rounded-2xl focus:ring-2 focus:ring-purple-500 dark:focus:ring-purple-500 focus:outline-none transition-colors text-xs bg-slate-50/50 dark:bg-slate-950/20 text-slate-800 dark:text-slate-100"
/>
<Search className="absolute left-3.5 top-3.5 w-4 h-4 text-slate-400 dark:text-slate-500" />
</div>
</div>
return (
<div key={column} className={`pb-2 border-b border-gray-100 last:border-b-0 ${isActive ? 'bg-purple-50/50 rounded-lg' : ''}`}>
{filterData.isBinary ? (
// Filtro binário: checkbox integrado ao título
<label className="flex justify-between cursor-pointer hover:bg-purple-50 p-3 rounded-lg transition-all group">
<span className="font-semibold text-gray-700 group-hover:text-purple-700 transition-colors">
{column}
</span>
<div className="border-t border-slate-100 dark:border-slate-850/45 my-2"></div>
<div className="mt-0.5">
<input
type="checkbox"
checked={activeFilters[column]?.includes('binary-enabled') || false}
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"
/>
</div>
</label>
) : (
// Filtro normal: título separado + múltiplas opções
<>
<h3 className="font-bold text-gray-800 mb-3 text-sm uppercase tracking-wide flex items-center gap-2">
{column}
{isActive && (
<span className="bg-purple-600 text-white text-xs px-2 py-0.5 rounded-full">
{activeFilters[column].length}
</span>
)}
</h3>
<div className="space-y-2 max-h-44 overflow-y-auto pr-2">
{filterData.values.map((option) => (
<label
key={option}
className="flex items-center gap-3 cursor-pointer hover:bg-purple-50 p-2 rounded-lg transition-all group"
>
<input
type="checkbox"
checked={activeFilters[column]?.includes(option) || false}
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"
/>
<span className="text-sm text-gray-700 group-hover:text-purple-700 transition-colors">
{option}
</span>
</label>
))}
</div>
</>
)}
</div>
);
})}
</div>
</div>
</div>
</div>
{/* Dinamic filters container with customized scrollbar */}
<div className="space-y-4 max-h-[calc(100vh-450px)] overflow-y-auto pr-2 custom-scrollbar">
{filterableColumns.map((column) => {
const filterData = filterOptions[column];
if (!filterData) return null;
{/* Grid de Placas */}
<div className="lg:col-span-3">
<div className="mb-6 flex justify-between items-center">
<p className="text-gray-600">
<span className="font-bold text-purple-600">{filteredBoards.length}</span> placa(s) encontrada(s)
</p>
</div>
const isActive = activeFilters[column]?.length > 0;
{filteredBoards.length === 0 ? (
<div className="text-center py-16">
<div className="text-6xl mb-4">🔍</div>
<p className="text-xl text-gray-600 mb-2">Nenhuma placa encontrada</p>
<p className="text-gray-500">Tente ajustar os filtros</p>
</div>
) : (
<div className="grid md:grid-cols-2 xl:grid-cols-3 gap-6">
{filteredBoards.map((board, index) => (
<BoardCard key={index} board={board} />
))}
</div>
)}
</div>
</div>
</main>
return (
<div key={column} className={`pb-3 border-b border-slate-100 dark:border-slate-850/30 last:border-b-0 ${isActive ? 'bg-purple-500/5 dark:bg-purple-400/5 p-3 rounded-2xl border border-purple-500/10 dark:border-purple-400/10' : ''}`}>
{filterData.isBinary ? (
// Binary filter (checkbox inside title wrapper)
<label className="flex justify-between items-center cursor-pointer p-1 rounded-lg transition-all group select-none">
<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}
</span>
<Footer />
</div>
);
}
// Estilos customizados para scrollbar
const styles = `
.custom-scrollbar::-webkit-scrollbar {
width: 6px;
}
.custom-scrollbar::-webkit-scrollbar-track {
background: #f1f1f1;
border-radius: 10px;
}
.custom-scrollbar::-webkit-scrollbar-thumb {
background: linear-gradient(to bottom, #3b82f6, #9333ea);
border-radius: 10px;
}
.custom-scrollbar::-webkit-scrollbar-thumb:hover {
background: linear-gradient(to bottom, #2563eb, #7c3aed);
}
`;
if (typeof document !== 'undefined') {
const styleSheet = document.createElement('style');
styleSheet.textContent = styles;
document.head.appendChild(styleSheet);
<input
type="checkbox"
checked={activeFilters[column]?.includes('binary-enabled') || false}
onChange={() => handleFilterToggle(column, 'binary-enabled')}
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"
/>
</label>
) : (
// Standard options filter with dropdown listing
<>
<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">
<span>{column}</span>
{isActive && (
<span className="bg-purple-650 text-white text-[9px] px-2 py-0.5 rounded-full font-bold select-none">
{activeFilters[column].length}
</span>
)}
</h3>
<div className="space-y-2.5 max-h-44 overflow-y-auto pr-2 custom-scrollbar">
{filterData.values.map((option) => (
<label
key={option}
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
type="checkbox"
checked={activeFilters[column]?.includes(option) || false}
onChange={() => handleFilterToggle(column, option)}
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-xs font-semibold text-slate-600 dark:text-slate-400 group-hover:text-purple-650 dark:group-hover:text-purple-400 transition-colors">
{option}
</span>
</label>
))}
</div>
</>
)}
</div>
);
})}
</div>
</div>
</div>
</div>
{/* Right Catalog Boards Grid */}
<div className="lg:col-span-3">
<div className="mb-6 flex justify-between items-center select-none">
<p className="text-xs font-bold text-slate-400 dark:text-slate-500 uppercase tracking-widest">
Total Encontrado: <span className="text-purple-650 dark:text-purple-400">{filteredBoards.length}</span> Placa(s)
</p>
</div>
{filteredBoards.length === 0 ? (
<div className="text-center py-20 bg-white/80 dark:bg-slate-900/40 backdrop-blur-xl border border-slate-200/60 dark:border-slate-800/80 rounded-3xl p-8 shadow-md select-none">
<Search className="w-12 h-12 text-slate-300 dark:text-slate-700 mx-auto mb-4" />
<p className="text-base font-display font-extrabold text-slate-800 dark:text-slate-200 mb-1">Nenhuma Placa Correspondente</p>
<p className="text-xs text-slate-500 dark:text-slate-400">Tente ajustar ou redefinir seus filtros laterais para expandir a busca.</p>
</div>
) : (
<div className="grid md:grid-cols-2 xl:grid-cols-3 gap-6">
{filteredBoards.map((board, index) => (
<BoardCard key={index} board={board} />
))}
</div>
)}
</div>
</div>
</main>
<Footer />
<style dangerouslySetInnerHTML={{ __html: `
.custom-scrollbar::-webkit-scrollbar {
width: 4px;
}
.custom-scrollbar::-webkit-scrollbar-track {
background: transparent;
}
.custom-scrollbar::-webkit-scrollbar-thumb {
background: #cbd5e1;
border-radius: 10px;
}
.dark .custom-scrollbar::-webkit-scrollbar-thumb {
background: #334155;
}
.custom-scrollbar::-webkit-scrollbar-thumb:hover {
background: #94a3b8;
}
`}} />
</div>
);
}
+342 -256
View File
@@ -2,274 +2,360 @@
import Header from "@/components/Header";
import Footer from "@/components/Footer";
import { useState } from "react";
import { useState, Fragment } from "react";
import Link from "next/link";
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() {
const [selectedSeries, setSelectedSeries] = useState(["ESP32", "ESP32-S3"]);
const series = Object.entries(seriesData);
const [selectedSeries, setSelectedSeries] = useState(["ESP32", "ESP32-S3"]);
const [collapsedCategories, setCollapsedCategories] = useState({
"Interfaces Especiais": true,
"Especificações Ambientais": true,
});
const toggleSeries = (seriesKey) => {
if (selectedSeries.includes(seriesKey)) {
if (selectedSeries.length > 2) {
setSelectedSeries(selectedSeries.filter(s => s !== seriesKey));
}
} else {
if (selectedSeries.length < 4) {
setSelectedSeries([...selectedSeries, seriesKey]);
}
}
};
const series = Object.entries(seriesData);
const compareFields = [
{ category: "Geral", fields: [
{ key: "nome_completo", label: "Nome Completo" },
{ key: "descricao", label: "Descrição" },
]},
{ category: "Processador", fields: [
{ key: "arquitetura", label: "Arquitetura" },
{ key: "nucleos", label: "Núcleos" },
{ key: "frequencia", label: "Frequência" },
{ key: "coprocessador_ulp", label: "Coprocessador ULP" },
{ key: "aceleradores_ia", label: "Aceleradores de IA" },
]},
{ category: "Conectividade", fields: [
{ key: "wifi", label: "Wi-Fi" },
{ key: "bluetooth", label: "Bluetooth" },
{ key: "zigbee_thread", label: "Zigbee/Thread" },
{ key: "matter", label: "Matter" },
{ key: "ethernet", label: "Ethernet" },
]},
{ category: "Memória", fields: [
{ key: "memoria_sram", label: "SRAM" },
{ key: "memoria_sram_rtc", label: "SRAM RTC" },
{ key: "memoria_rom", label: "ROM" },
{ key: "flash_externa", label: "Flash Externa" },
{ key: "psram_externa", label: "PSRAM Externa" },
]},
{ category: "Periféricos Digitais", fields: [
{ key: "gpio", label: "GPIO" },
{ key: "uart", label: "UART" },
{ key: "spi", label: "SPI" },
{ key: "i2c", label: "I²C" },
{ key: "i2s", label: "I²S" },
{ key: "pwm", label: "PWM" },
{ key: "can", label: "CAN" },
{ key: "usb", label: "USB" },
{ key: "sdio", label: "SDIO" },
]},
{ category: "Periféricos Analógicos", fields: [
{ key: "adc", label: "ADC" },
{ key: "dac", label: "DAC" },
{ key: "touch", label: "Touch Capacitivo" },
{ key: "sensor_hall", label: "Sensor Hall" },
{ key: "sensor_temperatura", label: "Sensor de Temperatura" },
]},
{ category: "Interfaces Especiais", fields: [
{ key: "lcd", label: "LCD" },
{ key: "camera", label: "Câmera" },
{ key: "mipi_csi", label: "MIPI CSI" },
{ key: "mipi_dsi", label: "MIPI DSI" },
{ key: "ethernet_mac", label: "Ethernet MAC" },
]},
{ category: "Especificações Ambientais", fields: [
{ key: "temperatura_operacao", label: "Temp. Operação" },
{ key: "temperatura_armazenamento", label: "Temp. Armazenamento" },
]},
];
const toggleSeries = (seriesKey) => {
if (selectedSeries.includes(seriesKey)) {
if (selectedSeries.length > 2) {
setSelectedSeries(selectedSeries.filter(s => s !== seriesKey));
}
} else {
if (selectedSeries.length < 4) {
setSelectedSeries([...selectedSeries, seriesKey]);
}
}
};
const renderValue = (value) => {
if (!value || value === "Não") {
return <span className="text-slate-400 font-medium">—</span>;
}
if (value === "Sim") {
return <span className="text-slate-700 font-semibold">✓</span>;
}
return <span className="text-slate-800 font-medium">{value}</span>;
};
const toggleCategory = (category) => {
setCollapsedCategories({
...collapsedCategories,
[category]: !collapsedCategories[category]
});
};
return (
<div className="bg-gray-50 min-h-screen">
<Header />
const compareFields = [
{ category: "Geral", fields: [
{ key: "nome_completo", label: "Nome Completo" },
{ key: "descricao", label: "Descrição" },
]},
{ category: "Processador", fields: [
{ key: "arquitetura", label: "Arquitetura" },
{ key: "nucleos", label: "Núcleos" },
{ key: "frequencia", label: "Frequência" },
{ key: "coprocessador_ulp", label: "Coprocessador ULP" },
{ key: "aceleradores_ia", label: "Aceleradores de IA" },
]},
{ category: "Conectividade", fields: [
{ key: "wifi", label: "Wi-Fi" },
{ key: "bluetooth", label: "Bluetooth" },
{ key: "zigbee_thread", label: "Zigbee/Thread" },
{ key: "matter", label: "Matter" },
{ key: "ethernet", label: "Ethernet" },
]},
{ category: "Memória", fields: [
{ key: "memoria_sram", label: "SRAM" },
{ key: "memoria_sram_rtc", label: "SRAM RTC" },
{ key: "memoria_rom", label: "ROM" },
{ key: "flash_externa", label: "Flash Externa" },
{ key: "psram_externa", label: "PSRAM Externa" },
]},
{ category: "Periféricos Digitais", fields: [
{ key: "gpio", label: "GPIO" },
{ key: "uart", label: "UART" },
{ key: "spi", label: "SPI" },
{ key: "i2c", label: "I²C" },
{ key: "i2s", label: "I²S" },
{ key: "pwm", label: "PWM" },
{ key: "can", label: "CAN" },
{ key: "usb", label: "USB" },
{ key: "sdio", label: "SDIO" },
]},
{ category: "Periféricos Analógicos", fields: [
{ key: "adc", label: "ADC" },
{ key: "dac", label: "DAC" },
{ key: "touch", label: "Touch Capacitivo" },
{ key: "sensor_hall", label: "Sensor Hall" },
{ key: "sensor_temperatura", label: "Sensor de Temperatura" },
]},
{ category: "Interfaces Especiais", fields: [
{ key: "lcd", label: "LCD" },
{ key: "camera", label: "Câmera" },
{ key: "mipi_csi", label: "MIPI CSI" },
{ key: "mipi_dsi", label: "MIPI DSI" },
{ key: "ethernet_mac", label: "Ethernet MAC" },
]},
{ category: "Especificações Ambientais", fields: [
{ key: "temperatura_operacao", label: "Temp. Operação" },
{ key: "temperatura_armazenamento", label: "Temp. Armazenamento" },
]},
];
<main id="conteudo" className="px-4 pt-16 pb-20 max-w-7xl mx-auto">
<section className="mb-16">
<div className="border-l-4 border-slate-700 pl-6 mb-8">
<p className="text-sm font-semibold text-slate-600 uppercase tracking-wider mb-2">
Análise Técnica Comparativa
</p>
<h1 className="text-4xl md:text-5xl font-bold text-slate-900 mb-4">
Comparação de Séries ESP32
</h1>
<p className="text-base text-slate-600 max-w-3xl leading-relaxed">
Análise detalhada de especificações técnicas para seleção de microcontroladores ESP32
</p>
</div>
</section>
const renderValue = (value) => {
if (!value || value === "Não") {
return <span className="text-slate-400 dark:text-slate-600 font-medium select-none">—</span>;
}
if (value === "Sim") {
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 dark:text-slate-200 font-semibold">{value}</span>;
};
<section className="mb-12">
<div className="bg-white border border-slate-200 rounded-lg p-6 mb-6">
<h2 className="text-xl font-semibold text-slate-900 mb-2">
Seleção de Modelos
</h2>
<p className="text-sm text-slate-600 mb-6">
Selecione de 2 a 4 séries para análise comparativa
</p>
<div className="grid grid-cols-2 md:grid-cols-4 lg:grid-cols-8 gap-3">
{series.map(([key, seriesItem]) => (
<button
key={key}
onClick={() => toggleSeries(key)}
className={`p-4 border rounded-lg transition-all duration-200 ${
selectedSeries.includes(key)
? 'border-slate-700 bg-slate-50 shadow-sm'
: 'border-slate-200 bg-white hover:border-slate-300 hover:bg-slate-50'
}`}
>
<div className="text-3xl mb-2">{seriesItem.icone}</div>
<div className="text-xs font-semibold text-slate-800">{key}</div>
{selectedSeries.includes(key) && (
<div className="mt-2 flex justify-center">
<div className="w-2 h-2 rounded-full bg-slate-700"></div>
</div>
)}
</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>
</section>
return (
<div className="bg-gradient-to-br from-slate-50 via-white to-purple-50/40 dark:from-slate-950 dark:via-slate-950 dark:to-slate-900 min-h-screen text-slate-900 dark:text-slate-100 transition-colors duration-300">
<Header />
<div className="bg-white border border-slate-200 shadow-sm overflow-hidden">
<div className="overflow-x-auto">
<table className="w-full">
<thead>
<tr className="bg-slate-800 border-b border-slate-700">
<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">
Especificação
</th>
{selectedSeries.map(seriesKey => {
const seriesItem = seriesData[seriesKey];
return (
<th key={seriesKey} className="px-6 py-4 text-center text-white font-semibold text-sm uppercase tracking-wider min-w-48">
<div className="flex flex-col items-center gap-2">
<span className="text-2xl">{seriesItem.icone}</span>
<span>{seriesKey}</span>
</div>
</th>
);
})}
</tr>
</thead>
<tbody>
{compareFields.map(({ category, fields }) => (
<>
<tr key={category} className="bg-slate-100 border-y border-slate-200">
<td colSpan={selectedSeries.length + 1} className="px-6 py-3 font-semibold text-slate-800 text-sm uppercase tracking-wide">
{category}
</td>
</tr>
{fields.map(({ key, label }) => (
<tr key={key} className="border-b border-slate-100 hover:bg-slate-50 transition-colors">
<td className="px-6 py-3 font-medium text-slate-700 text-sm bg-white sticky left-0 border-r border-slate-100">
{label}
</td>
{selectedSeries.map(seriesKey => {
const seriesItem = seriesData[seriesKey];
const value = seriesItem[key];
return (
<td key={seriesKey} className="px-6 py-3 text-center text-sm">
{renderValue(value)}
</td>
);
})}
</tr>
))}
</>
))}
</tbody>
</table>
</div>
</div>
<main id="conteudo" className="px-6 pt-16 pb-24 max-w-7xl mx-auto">
{/* Call-to-Action para Catálogo de Placas */}
<div className="my-16 bg-slate-800 border border-slate-700 p-10">
<div className="flex flex-col md:flex-row items-center justify-between gap-8">
<div className="flex-1">
<p className="text-xs font-semibold text-slate-400 uppercase tracking-wider mb-3">
Recursos Adicionais
</p>
<h3 className="text-2xl md:text-3xl font-bold text-white mb-4">
Catálogo de Placas de Desenvolvimento
</h3>
<p className="text-base text-slate-300 leading-relaxed">
Acesse nossa base de placas de desenvolvimento verificadas com referências diretas para fornecedores oficiais.
</p>
</div>
<Link
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"
>
<span>Acessar Catálogo</span>
<svg
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>
</div>
</div>
{/* Top Section */}
<section className="mb-16">
<div className="border-l-4 border-purple-500 pl-6 max-w-3xl">
<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
</p>
<h1 className="text-4xl md:text-5xl font-display font-extrabold text-slate-800 dark:text-slate-100 mb-4 tracking-tight leading-tight">
Comparador de Séries ESP32
</h1>
<p className="text-base text-slate-500 dark:text-slate-400 leading-relaxed">
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>
</div>
</section>
<div className="mt-12">
<h3 className="text-lg font-semibold text-slate-900 mb-6 border-l-4 border-slate-700 pl-4">
Documentação Técnica
</h3>
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4">
{selectedSeries.map(seriesKey => {
const seriesItem = seriesData[seriesKey];
return (
<div
key={seriesKey}
className="bg-white border border-slate-200 p-5 hover:border-slate-300 hover:shadow-sm transition-all duration-200"
>
<div className="flex items-start gap-4">
<div className="text-3xl">{seriesItem.icone}</div>
<div className="flex-1 min-w-0">
<Link href={`/series/${seriesKey}`} className="no-underline">
<h4 className="text-base font-semibold text-slate-900 mb-1">{seriesKey}</h4>
</Link>
<p className="text-xs text-slate-600 mb-3">Documentação oficial</p>
<a
href={seriesItem.datasheet}
target="_blank"
rel="noopener noreferrer"
className="inline-flex items-center gap-2 text-xs font-medium text-slate-700 hover:text-slate-900"
>
<span>Datasheet</span>
<svg className="w-3 h-3" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<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>
</div>
</div>
</div>
);
})}
</div>
</div>
</main>
{/* Model Selector Card */}
<section className="mb-12">
<div className="bg-white/80 dark:bg-slate-900/40 backdrop-blur-xl border border-slate-200/60 dark:border-slate-800/80 rounded-3xl p-6 md:p-8 shadow-2xl relative overflow-hidden">
<div className="flex flex-col md:flex-row md:items-center md:justify-between gap-4 mb-6">
<div>
<h2 className="text-lg font-bold text-slate-800 dark:text-slate-100 mb-1">
Escolha as Séries
</h2>
<p className="text-xs text-slate-400 dark:text-slate-500">
Selecione de 2 a até 4 modelos simultâneos para montar sua planilha de comparação.
</p>
</div>
<Footer />
</div>
);
<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
key={key}
onClick={() => toggleSeries(key)}
style={{
borderColor: isSelected ? seriesItem.cor : 'transparent'
}}
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 select-none transform hover:scale-110 transition-transform">{seriesItem.icone}</div>
<div className="text-xs font-bold text-slate-800 dark:text-slate-200">{key}</div>
{isSelected && (
<div
className="mt-2.5 w-2 h-2 rounded-full shrink-0 animate-pulse"
style={{ backgroundColor: seriesItem.cor }}
/>
)}
</button>
);
})}
</div>
</div>
</section>
{/* Side by side interactive Table */}
<div className="bg-white/80 dark:bg-slate-900/40 backdrop-blur-xl border border-slate-200/60 dark:border-slate-800/80 rounded-3xl overflow-hidden shadow-2xl">
<div className="overflow-x-auto scrollbar-thin">
<table className="w-full border-collapse">
<thead>
<tr className="bg-slate-850 dark:bg-slate-950 border-b border-slate-750 dark:border-slate-900">
<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
</th>
{selectedSeries.map(seriesKey => {
const seriesItem = seriesData[seriesKey];
return (
<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 select-none">
<span className="text-3xl transform hover:scale-105 transition-transform">{seriesItem.icone}</span>
<span className="text-sm font-extrabold tracking-wide" style={{ color: seriesItem.cor }}>{seriesKey}</span>
</div>
</th>
);
})}
</tr>
</thead>
<tbody>
{compareFields.map(({ category, fields }) => {
const isCollapsed = collapsedCategories[category];
return (
<Fragment key={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>
</tr>
{/* Fields list within category (if expanded) */}
{!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}
</td>
{selectedSeries.map(seriesKey => {
const seriesItem = seriesData[seriesKey];
const value = seriesItem[key];
return (
<td key={seriesKey} className="px-6 py-4 text-center text-xs">
{renderValue(value)}
</td>
);
})}
</tr>
))}
</Fragment>
);
})}
</tbody>
</table>
</div>
</div>
{/* Professional CTA Banner for the Catalog */}
<div className="my-20 bg-gradient-to-br from-slate-900 to-indigo-950 dark:from-slate-950 dark:to-purple-950/20 border border-slate-800/80 rounded-3xl p-8 md:p-12 shadow-2xl relative overflow-hidden">
<div className="absolute right-0 bottom-0 w-96 h-96 bg-purple-500/5 rounded-full blur-3xl pointer-events-none" />
<div className="flex flex-col md:flex-row items-center justify-between gap-8 relative z-10">
<div className="flex-1 max-w-2xl text-center md:text-left">
<div className="inline-flex items-center gap-2 bg-white/10 text-slate-350 px-3 py-1 rounded-full mb-4 text-xs font-semibold select-none">
<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
</h3>
<p className="text-sm text-slate-400 leading-relaxed">
Quer testar o chip selecionado na bancada? Compare placas de desenvolvimento com layouts de pinos mapeados, conexões de periféricos e links oficiais dos melhores fornecedores.
</p>
</div>
<Link
href="/catalogo"
className="inline-flex items-center justify-center gap-2 bg-white text-slate-900 px-6 py-3.5 rounded-xl font-bold text-sm hover:bg-slate-100 transition-all duration-300 shrink-0 shadow-md active:scale-95"
>
<span>Acessar Catálogo</span>
<ArrowRight className="w-4 h-4" />
</Link>
</div>
</div>
{/* Detailed technical datasheets section */}
<div className="mt-12">
<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 Relacionada
</h3>
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4">
{selectedSeries.map(seriesKey => {
const seriesItem = seriesData[seriesKey];
return (
<div
key={seriesKey}
className="bg-white/70 dark:bg-slate-900/40 backdrop-blur-xl border border-slate-200/80 dark:border-slate-800/80 p-5 rounded-2xl hover:border-purple-500/50 dark:hover:border-purple-400/50 hover:shadow-lg transition-all duration-300 flex flex-col justify-between"
>
<div className="flex gap-4 items-start mb-4">
<div className="text-4xl select-none">{seriesItem.icone}</div>
<div className="min-w-0">
<Link href={`/series/${seriesKey}`} className="no-underline">
<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>
<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
href={seriesItem.datasheet}
target="_blank"
rel="noopener noreferrer"
className="inline-flex items-center justify-center gap-1.5 w-full text-center py-2 bg-slate-100 dark:bg-slate-950 hover:bg-purple-500/10 dark:hover:bg-purple-400/10 border border-slate-200 dark:border-slate-800 rounded-xl text-xs font-bold text-slate-650 dark:text-slate-350 hover:text-purple-600 dark:hover:text-purple-400 transition-colors"
>
<span>Datasheet Oficial</span>
<ExternalLink className="w-3.5 h-3.5" />
</a>
</div>
);
})}
</div>
</div>
</main>
<Footer />
</div>
);
}
+242 -147
View File
@@ -4,179 +4,274 @@ import Footer from "@/components/Footer";
import { notFound } from "next/navigation";
import CodeExample from "@/components/CodeExample";
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 }) {
const { key } = await params;
const framework = frameworksData[key];
const { key } = await params;
const framework = frameworksData[key];
if (!framework) {
notFound();
}
if (!framework) {
notFound();
}
const features = framework.caracteristicas || [];
const useCases = framework.casos_uso || [];
const features = framework.caracteristicas || [];
const useCases = framework.casos_uso || [];
return (
<div className="bg-linear-to-br from-blue-50 via-white to-purple-50 min-h-screen">
<Header />
return (
<div className="bg-gradient-to-br from-slate-50 via-white to-purple-50/40 dark:from-slate-950 dark:via-slate-950 dark:to-slate-900 min-h-screen text-slate-900 dark:text-slate-100 transition-colors duration-300">
<Header />
<main className="px-4 pt-16 pb-8 max-w-7xl mx-auto">
<div className="mb-8">
<Link href="/frameworks" className="inline-flex items-center text-gray-600 hover:text-purple-600 transition-colors mb-4">
<span className="mr-2">←</span> Voltar para Frameworks
</Link>
<main className="px-6 pt-16 pb-24 max-w-7xl mx-auto">
{/* Back navigation link */}
<div className="mb-8">
<Link
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>
<div className="bg-white rounded-3xl shadow-xl p-8 border-2 border-gray-100"
style={{ borderColor: framework.cor + '30' }}>
<div className="flex items-start justify-between flex-wrap gap-4">
<div className="flex items-center gap-4">
<span className="text-6xl">{framework.icone}</span>
<div>
<h1 className="text-4xl md:text-5xl font-extrabold text-gray-800 mb-2">
{framework.nome}
</h1>
<p className="text-xl text-gray-600 mb-2">{framework.nome_completo}</p>
<span className="inline-block px-4 py-2 rounded-full text-sm font-semibold text-white"
style={{ backgroundColor: framework.cor }}>
{framework.tipo}
</span>
</div>
</div>
{/* Top Info Banner Panel */}
<div
className="bg-white/80 dark:bg-slate-900/40 backdrop-blur-xl rounded-3xl p-6 md:p-8 shadow-2xl border relative overflow-hidden"
style={{ borderColor: `${framework.cor}40` }}
>
<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>
<h1 className="text-4xl font-display font-extrabold text-slate-850 dark:text-slate-100 tracking-tight leading-none mb-3">
{framework.nome}
</h1>
<p className="text-sm font-semibold text-slate-400 dark:text-slate-500 mb-4">{framework.nome_completo}</p>
<span
className="inline-block px-3 py-1 rounded-full text-[10px] font-bold uppercase tracking-widest select-none"
style={{ backgroundColor: `${framework.cor}18`, color: framework.cor, border: `1px solid ${framework.cor}30` }}
>
{framework.tipo}
</span>
</div>
</div>
<div className="mx-auto sm:mx-0 grid items-center gap-3">
{framework.documentacao && (
<a
href={framework.documentacao}
target="_blank"
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"
>
📘 Documentação
</a>
)}
{/* 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 && (
<a
href={framework.documentacao}
target="_blank"
rel="noopener noreferrer"
className="inline-flex items-center justify-center gap-2 bg-slate-900 dark:bg-slate-100 text-white dark:text-slate-950 px-5 py-3 rounded-xl font-bold text-xs uppercase tracking-wider hover:opacity-90 transition-opacity shadow-lg"
>
<BookOpen className="w-4 h-4" />
<span>Documentação Oficial</span>
</a>
)}
{framework.repositorio && (
<a
href={framework.repositorio}
target="_blank"
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"
>
🔗 Repositório
</a>
)}
</div>
</div>
{framework.repositorio && (
<a
href={framework.repositorio}
target="_blank"
rel="noopener noreferrer"
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"
>
<Code className="w-4 h-4" />
<span>Repositório no GitHub</span>
</a>
)}
</div>
</div>
<p className="text-lg text-gray-700 mt-6 leading-relaxed">
{framework.descricao}
</p>
</div>
</div>
<p className="text-sm text-slate-500 dark:text-slate-200 mt-6 leading-relaxed max-w-4xl font-semibold">
{framework.descricao}
</p>
</div>
</div>
<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: framework.cor }}>
<h3 className="text-sm font-semibold text-gray-600 mb-2">LINGUAGEM</h3>
<p className="text-3xl font-bold text-gray-800">{framework.linguagem}</p>
<p className="text-gray-600 mt-1">Principal</p>
</div>
{/* Framework Core Specifications Grid */}
<div className="grid md:grid-cols-3 gap-6 mb-8 select-none animate-fadeIn">
<div
className="bg-white/80 dark:bg-slate-900/40 backdrop-blur-xl rounded-2xl shadow-lg p-6 border-l-4"
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 className="bg-white rounded-2xl shadow-lg p-6 border-l-4" style={{ borderColor: framework.cor }}>
<h3 className="text-sm font-semibold text-gray-600 mb-2">TIPO</h3>
<p className="text-3xl font-bold text-gray-800">{framework.tipo}</p>
<p className="text-gray-600 mt-1">Categoria</p>
</div>
<div
className="bg-white/80 dark:bg-slate-900/40 backdrop-blur-xl rounded-2xl shadow-lg p-6 border-l-4"
style={{ borderColor: framework.cor }}
>
<h3 className="text-[10px] font-bold text-slate-400 dark:text-slate-500 uppercase tracking-widest mb-1.5 flex items-center gap-1.5">
<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 className="bg-white rounded-2xl shadow-lg p-6 border-l-4" style={{ borderColor: framework.cor }}>
<h3 className="text-sm font-semibold text-gray-600 mb-2">FUNÇÃO PRINCIPAL</h3>
<p className="text-xl font-bold text-gray-800">{framework.funcao_principal}</p>
</div>
</div>
<div
className="bg-white/80 dark:bg-slate-900/40 backdrop-blur-xl rounded-2xl shadow-lg p-6 border-l-4"
style={{ borderColor: framework.cor }}
>
<h3 className="text-[10px] font-bold text-slate-400 dark:text-slate-450 mb-1.5 flex items-center gap-1.5">
<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 className="grid lg:grid-cols-3 gap-6 mb-8">
<div className="lg:col-span-2 bg-white rounded-2xl shadow-lg p-6 border-2 border-gray-100">
<h2 className="text-2xl font-bold text-gray-800 mb-4 pb-3 border-b-2" style={{ borderColor: framework.cor }}>
✨ Principais Características
</h2>
<ul className="list-disc list-inside space-y-2 text-gray-700">
{features.map((f, i) => (
<li key={i} className="pl-2">{f}</li>
))}
</ul>
{/* side-by-side details block */}
<div className="grid lg:grid-cols-3 gap-6 mb-8 items-start">
{/* Left specs card */}
<div className="lg:col-span-2 bg-white/80 dark:bg-slate-900/45 backdrop-blur-xl rounded-3xl p-8 border border-slate-200/60 dark:border-slate-800/80 shadow-2xl space-y-8">
<div>
<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 className="text-2xl font-bold text-gray-800 mt-8 mb-4 pb-3 border-b-2" style={{ borderColor: framework.cor }}>
📦 Casos de Uso
</h2>
<div className="grid md:grid-cols-2 gap-3">
{useCases.map((u, i) => (
<div key={i} className="bg-gray-50 p-3 rounded-lg text-gray-700">{u}</div>
))}
</div>
</div>
<ul className="space-y-3">
{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>
</div>
<div className="bg-white rounded-2xl shadow-lg p-6 border-2 border-gray-100">
<h2 className="text-2xl font-bold text-gray-800 mb-4 pb-3 border-b-2" style={{ borderColor: framework.cor }}>
📋 Detalhes Rápidos
</h2>
<div className="space-y-3 text-gray-700">
<div><span className="font-semibold">Nome completo:</span> {framework.nome_completo}</div>
<div><span className="font-semibold">Linguagem:</span> {framework.linguagem}</div>
<div><span className="font-semibold">Tipo:</span> {framework.tipo}</div>
<div><span className="font-semibold">Cor (tema):</span> <span className="inline-block w-4 h-4 rounded ml-2" style={{ backgroundColor: framework.cor }} /></div>
</div>
</div>
</div>
<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` }}
>
<Compass className="w-5 h-5" style={{ color: framework.cor }} />
<span>Casos de Uso Recomendados</span>
</h2>
{framework.exemplos && (
<CodeExample framework={framework} />
)}
<div className="grid md:grid-cols-2 gap-3.5">
{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 className="text-center md:flex gap-4 mt-6">
{framework.documentacao && (
<a
href={framework.documentacao}
target="_blank"
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"
>
📚 Documentação Oficial
</a>
)}
{/* Right summary metadata card */}
<div className="bg-white/80 dark:bg-slate-900/45 backdrop-blur-xl rounded-3xl p-8 border border-slate-200/60 dark:border-slate-800/80 shadow-2xl select-none">
<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>
{framework.repositorio && (
<a
href={framework.repositorio}
target="_blank"
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"
>
💻 Repositório no GitHub
</a>
)}
</div>
</main>
<div className="space-y-4 text-xs font-bold text-slate-650 dark:text-slate-200">
<div className="flex justify-between items-center py-2 border-b border-slate-100 dark:border-slate-850/45">
<span className="text-slate-400 dark:text-slate-500 uppercase tracking-wider text-[9px]">Nome Oficial</span>
<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>
<Footer />
</div>
);
{/* Coding examples container (renders only if framework has examples defined) */}
{framework.exemplos && (
<div className="mt-12">
<CodeExample framework={framework} />
</div>
)}
{/* Technical documentation double CTAs */}
<div className="flex flex-wrap gap-4 mt-8 select-none">
{framework.documentacao && (
<a
href={framework.documentacao}
target="_blank"
rel="noopener noreferrer"
className="inline-flex items-center justify-center gap-2 bg-slate-900 dark:bg-slate-100 text-white dark:text-slate-950 px-6 py-3.5 rounded-xl font-bold text-xs uppercase tracking-wider hover:opacity-90 transition-opacity shadow-lg"
>
<BookOpen className="w-4 h-4" />
<span>Documentação do Desenvolvedor</span>
</a>
)}
{framework.repositorio && (
<a
href={framework.repositorio}
target="_blank"
rel="noopener noreferrer"
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"
>
<Code className="w-4 h-4" />
<span>Código Fonte no GitHub</span>
</a>
)}
</div>
</main>
<Footer />
</div>
);
}
export async function generateMetadata({ params }) {
const { key } = await params;
const framework = frameworksData[key];
const { key } = await params;
const framework = frameworksData[key];
if (!framework) {
return { title: "Framework não encontrado" };
}
if (!framework) {
return { title: "Framework não encontrado" };
}
return {
title: `ESPDocs - ${framework.nome_completo}`,
description: framework.descricao,
};
return {
title: `ESPDocs - ${framework.nome_completo}`,
description: framework.descricao,
};
}
export async function generateStaticParams() {
return Object.keys(frameworksData).map((key) => ({
key: key,
}));
return Object.keys(frameworksData).map((key) => ({
key: key,
}));
}
+214 -177
View File
@@ -2,200 +2,237 @@ import Header from "@/components/Header";
import Footer from "@/components/Footer";
import Link from "next/link";
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() {
const frameworks = Object.entries(frameworksData);
const frameworks = Object.entries(frameworksData);
return (
<div className="bg-linear-to-br from-blue-50 via-white to-purple-50 min-h-screen">
<Header />
return (
<div className="bg-gradient-to-br from-slate-50 via-white to-purple-50/40 dark:from-slate-950 dark:via-slate-950 dark:to-slate-900 min-h-screen text-slate-900 dark:text-slate-100 transition-colors duration-300">
<Header />
<main id="conteudo" className="px-4 pt-16 pb-20 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">
<span className="text-xl">🛠️</span>
<span className="text-sm font-semibold text-gray-700">Ferramentas de Desenvolvimento</span>
</div>
<main id="conteudo" className="px-6 pt-16 pb-24 max-w-7xl mx-auto">
{/* Top Hero Section */}
<section className="text-center mb-16 select-none">
<div className="inline-flex items-center gap-2 bg-white/70 dark:bg-slate-900/50 backdrop-blur-md px-4 py-2 rounded-full mb-6 border border-slate-200/60 dark:border-slate-800/60 shadow-xs">
<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>
<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">
Frameworks ESP32
</h1>
<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
</h1>
<p className="text-lg md:text-xl text-gray-600 max-w-3xl mx-auto leading-relaxed">
Descubra os frameworks e bibliotecas disponíveis para ESP32. De desenvolvimento geral a
aplicações especializadas em áudio, visão computacional, IA e IoT.
</p>
</section>
<p className="text-sm md:text-base text-slate-500 dark:text-slate-400 max-w-2xl mx-auto leading-relaxed font-semibold">
Descubra o ecossistema de software do ESP32. De desenvolvimento geral C/C++ a
soluções dedicadas a áudio de alto desempenho, visão computacional na borda e IoT inteligente.
</p>
</section>
<div className="grid md:grid-cols-2 lg:grid-cols-3 gap-6">
{frameworks.map(([key, framework]) => (
<Link
key={key}
href={`/frameworks/${key}`}
className="group"
>
<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">
{/* Cards Grid */}
<div className="grid md:grid-cols-2 lg:grid-cols-3 gap-6">
{frameworks.map(([key, framework]) => (
<Link
key={key}
href={`/frameworks/${key}`}
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="p-6 border-b border-gray-100">
<div className="flex items-center justify-between mb-3">
<span className="text-5xl">{framework.icone}</span>
<div className="flex flex-col gap-2 items-end">
<span className="px-3 py-1 rounded-full text-xs font-semibold text-white"
style={{ backgroundColor: framework.cor }}>
{framework.linguagem}
</span>
</div>
</div>
<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-4">
<span className="text-5xl select-none filter drop-shadow-md">{framework.icone}</span>
<div className="flex flex-col gap-1 items-end select-none">
<span
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}
</span>
</div>
</div>
<h2 className="text-2xl font-bold text-gray-800 mb-1 group-hover:text-purple-600 transition-colors">
{framework.nome}
</h2>
<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}
</h2>
<p className="text-xs text-gray-500 mb-3 font-medium">
{framework.nome_completo}
</p>
<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}
</p>
<p className="text-sm text-gray-600 line-clamp-3">
{framework.descricao}
</p>
</div>
<p className="text-xs text-slate-500 dark:text-slate-300 leading-relaxed line-clamp-3">
{framework.descricao}
</p>
</div>
<div className="p-6 space-y-4 grow">
<div>
<span className="inline-block px-3 py-1 rounded-lg text-xs font-semibold"
style={{
backgroundColor: `${framework.cor}20`,
color: framework.cor
}}>
{framework.tipo}
</span>
</div>
<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>
<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">
<Target className="w-3.5 h-3.5 text-blue-500" />
<span>Objetivo Principal</span>
</h4>
<p className="text-xs text-slate-650 dark:text-slate-200 font-medium leading-relaxed">
{framework.funcao_principal}
</p>
</div>
<div>
<h4 className="text-xs font-bold text-gray-700 mb-2 uppercase tracking-wide">
🎯 Função Principal
</h4>
<p className="text-sm text-gray-600 line-clamp-2">
{framework.funcao_principal}
</p>
</div>
{/* Key Specs */}
<div>
<h4 className="text-[9px] font-bold text-slate-400 dark:text-slate-450 mb-2 uppercase tracking-widest flex items-center gap-1.5">
<Zap className="w-3.5 h-3.5 text-amber-500" />
<span>Recursos Chave</span>
</h4>
<ul className="space-y-1">
{framework.caracteristicas.slice(0, 3).map((carac, idx) => (
<li key={idx} className="text-xs text-slate-600 dark:text-slate-300 flex items-start gap-1.5">
<span className="text-[8px] mt-1 shrink-0" style={{ color: framework.cor }}>■</span>
<span className="line-clamp-1">{carac}</span>
</li>
))}
</ul>
</div>
<div>
<h4 className="text-xs font-bold text-gray-700 mb-2 uppercase tracking-wide">
⚡ Características
</h4>
<ul className="space-y-1">
{framework.caracteristicas.slice(0, 3).map((carac, idx) => (
<li key={idx} className="text-xs text-gray-600 flex items-start">
<span className="mr-2 mt-0.5" style={{ color: framework.cor }}>▪</span>
<span className="line-clamp-1">{carac}</span>
</li>
))}
{framework.caracteristicas.length > 3 && (
<li className="text-xs text-gray-400 italic">
+{framework.caracteristicas.length - 3} mais...
</li>
)}
</ul>
</div>
{/* Use Cases tags */}
<div>
<h4 className="text-[9px] font-bold text-slate-400 dark:text-slate-450 mb-2 uppercase tracking-widest flex items-center gap-1.5">
<Compass className="w-3.5 h-3.5 text-emerald-500" />
<span>Aplicações Ideais</span>
</h4>
<div className="flex flex-wrap gap-1.5">
{framework.casos_uso.slice(0, 3).map((caso, idx) => (
<span key={idx} className="text-[9px] font-bold bg-slate-100 dark:bg-slate-900/60 text-slate-650 dark:text-slate-200 px-2.5 py-1 rounded-md border border-slate-200/40 dark:border-slate-800/80">
{caso}
</span>
))}
</div>
</div>
</div>
<div>
<h4 className="text-xs font-bold text-gray-700 mb-2 uppercase tracking-wide">
💼 Casos de Uso
</h4>
<div className="flex flex-wrap gap-2">
{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">
{caso}
</span>
))}
</div>
</div>
</div>
{/* CTA Link */}
<div className="pt-4 border-t border-slate-100 dark:border-slate-850/40 mt-auto flex items-center justify-between text-xs font-bold text-slate-700 dark:text-slate-300 group-hover:text-purple-650 dark:group-hover:text-purple-400 transition-colors">
<span>Acessar Detalhes e Exemplos</span>
<ArrowRight className="w-4 h-4 transform group-hover:translate-x-1.5 transition-transform" />
</div>
</div>
</div>
</Link>
))}
</div>
<div className="px-6 pb-6">
<div className="flex items-center justify-between text-sm font-semibold group-hover:text-purple-600 transition-colors">
<span>Ver Detalhes e Exemplos</span>
<span className="transform group-hover:translate-x-2 transition-transform">→</span>
</div>
</div>
</div>
</Link>
))}
</div>
{/* Bottom Informative Cards Section */}
<div className="mt-16 grid md:grid-cols-2 gap-8 select-none">
{/* Card 1: Beginner Guide */}
<div className="bg-white/80 dark:bg-slate-900/40 backdrop-blur-xl rounded-3xl shadow-xl p-8 border border-slate-200/60 dark:border-slate-800/80 flex flex-col justify-between relative overflow-hidden">
<div>
<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-xl font-display font-extrabold text-slate-850 dark:text-slate-100">
Iniciante no ESP32?
</h3>
<p className="text-[10px] font-bold text-blue-500 uppercase tracking-wider leading-none">Guia de Aprendizado</p>
</div>
</div>
<div className="mt-16 grid md:grid-cols-2 gap-8">
<div className="bg-linear-to-br from-white to-blue-50 rounded-3xl shadow-2xl p-8 border-2 border-blue-100">
<div className="flex items-center gap-3 mb-4">
<div className="w-12 h-12 bg-linear-to-br from-blue-500 to-blue-600 rounded-xl flex items-center justify-center">
<span className="text-2xl">🎓</span>
</div>
<h3 className="text-2xl font-bold text-gray-900">
Iniciante no ESP32?
</h3>
</div>
<p className="text-gray-700 mb-6 leading-relaxed text-base">
Se você está começando, recomendo iniciar com&nbsp;
<strong>Arduino</strong> ou&nbsp;
<strong>MicroPython</strong>&nbsp;
para prototipagem rápida, ou&nbsp;
<strong>ESP-IDF</strong> para projetos profissionais com controle total.
</p>
<div className="flex flex-wrap gap-3">
<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>
<span>Arduino</span>
</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">
<span className="text-lg">🐍</span>
<span>MicroPython</span>
</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">
<span className="text-lg">⚙️</span>
<span>ESP-IDF</span>
</Link>
</div>
</div>
<p className="text-xs text-slate-500 dark:text-slate-400 leading-relaxed mb-6 font-semibold">
Se você está iniciando a sua jornada de hardware, recomendamos adotar o ecossistema&nbsp;
<strong className="text-slate-750 dark:text-slate-300">Arduino</strong> ou&nbsp;
<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>
</div>
<div className="bg-linear-to-br from-white to-purple-50 rounded-3xl shadow-2xl p-8 border-2 border-purple-100">
<div className="flex items-center gap-3 mb-4">
<div className="w-12 h-12 bg-linear-to-br from-purple-500 to-pink-600 rounded-xl flex items-center justify-center">
<span className="text-2xl">🚀</span>
</div>
<h3 className="text-2xl font-bold text-gray-900">
Aplicações Especializadas?
</h3>
</div>
<p className="text-gray-700 mb-6 leading-relaxed text-base">
Para projetos específicos, explore frameworks especializados como&nbsp;
<strong>ESP-ADF</strong> (áudio),&nbsp;
<strong>ESP-WHO</strong> (visão),&nbsp;
<strong>ESP-NN</strong> (IA) ou&nbsp;
<strong>ESP-Matter</strong> (smart home).
</p>
<div className="flex flex-wrap gap-3">
<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>
<span>Áudio</span>
</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">
<span className="text-lg">📷</span>
<span>Visão</span>
</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">
<span className="text-lg">🧠</span>
<span>IA</span>
</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">
<span className="text-lg">🏠</span>
<span>Smart Home</span>
</Link>
</div>
</div>
</div>
</main>
<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>
<ArrowRight className="w-3.5 h-3.5" />
</Link>
<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>MicroPython</span>
<ArrowRight className="w-3.5 h-3.5" />
</Link>
<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>ESP-IDF</span>
<ArrowRight className="w-3.5 h-3.5" />
</Link>
</div>
</div>
<Footer />
</div>
);
{/* Card 2: Specialized Solutions */}
<div className="bg-white/80 dark:bg-slate-900/40 backdrop-blur-xl rounded-3xl shadow-xl p-8 border border-slate-200/60 dark:border-slate-800/80 flex flex-col justify-between relative overflow-hidden">
<div>
<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-xl font-display font-extrabold text-slate-850 dark:text-slate-100">
Soluções Especializadas
</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-xs text-slate-500 dark:text-slate-400 leading-relaxed mb-6 font-semibold">
Projetos complexos de IoT demandam bibliotecas altamente otimizadas na arquitetura.
A Espressif fornece extensões prontas como o&nbsp;
<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>
</div>
<div className="flex flex-wrap gap-2.5 pt-4 border-t border-slate-100 dark:border-slate-850/40 mt-auto">
<Link href="/frameworks/ESP-ADF" className="inline-flex items-center gap-1.5 px-3.5 py-2 bg-slate-150/45 dark:bg-slate-850 text-slate-700 dark:text-slate-300 border border-slate-200/60 dark:border-slate-850/60 rounded-xl text-xs font-bold hover:bg-slate-200 dark:hover:bg-slate-800 transition-all">
<Music className="w-3.5 h-3.5 text-blue-500" />
<span>Áudio</span>
</Link>
<Link href="/frameworks/ESP-WHO" className="inline-flex items-center gap-1.5 px-3.5 py-2 bg-slate-150/45 dark:bg-slate-850 text-slate-700 dark:text-slate-300 border border-slate-200/60 dark:border-slate-850/60 rounded-xl text-xs font-bold hover:bg-slate-200 dark:hover:bg-slate-800 transition-all">
<Camera className="w-3.5 h-3.5 text-orange-500" />
<span>Visão</span>
</Link>
<Link href="/frameworks/ESP-NN" className="inline-flex items-center gap-1.5 px-3.5 py-2 bg-slate-150/45 dark:bg-slate-850 text-slate-700 dark:text-slate-300 border border-slate-200/60 dark:border-slate-850/60 rounded-xl text-xs font-bold hover:bg-slate-200 dark:hover:bg-slate-800 transition-all">
<Brain className="w-3.5 h-3.5 text-teal-500" />
<span>Inteligência Artificial</span>
</Link>
<Link href="/frameworks/ESP-Matter" className="inline-flex items-center gap-1.5 px-3.5 py-2 bg-slate-150/45 dark:bg-slate-850 text-slate-700 dark:text-slate-300 border border-slate-200/60 dark:border-slate-850/60 rounded-xl text-xs font-bold hover:bg-slate-200 dark:hover:bg-slate-800 transition-all">
<Home className="w-3.5 h-3.5 text-yellow-500" />
<span>Matter</span>
</Link>
</div>
</div>
</div>
</main>
<Footer />
</div>
);
}
+46
View File
@@ -1 +1,47 @@
@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;
}
}
+18 -5
View File
@@ -1,7 +1,18 @@
import { Roboto } from "next/font/google";
import { Outfit, Inter } from "next/font/google";
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 = {
title: "ESPDocs",
@@ -11,9 +22,11 @@ export const metadata = {
export default function RootLayout({ children }) {
return (
<html lang="pt-BR">
<body className={`${roboto.className} font-sans`}>
{children}
<html lang="pt-BR" className={`${outfit.variable} ${inter.variable}`} suppressHydrationWarning>
<body className="font-sans antialiased bg-slate-50 text-slate-900 dark:bg-slate-950 dark:text-slate-50 transition-colors duration-300">
<Providers>
{children}
</Providers>
</body>
</html>
);
+206 -130
View File
@@ -1,176 +1,252 @@
"use client";
import Header from "@/components/Header";
import Footer from "@/components/Footer";
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() {
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: "Especificações Organizadas", description: "Especificações técnicas de cada série ESP32 organizadas em tabelas comparativas em português", icon: "🔧"},
{name: "Diagramas de Conexões", description: "Visualize diagramas interativos dos componentes e pinos de cada série ESP32", icon: "📊"},
{name: "Informações sobre Frameworks", description: "Conheça frameworks como ESP-IDF, Arduino e MicroPython com descrições, casos de uso e exemplos", icon: "⚡"},
{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: "Informações Atualizadas", description: "Dados atualizados sobre as séries e frameworks mais recentes do ecossistema ESP32", icon: "🔄"},
{name: "Interface em Português", description: "Navegação e organização de informações totalmente em português para facilitar a consulta", icon: "📚"},
]
{
name: "Seletor Inteligente",
description: "Responda perguntas rápidas sobre seu projeto e descubra a série ESP32 ideal em segundos.",
icon: <SlidersHorizontal className="w-6 h-6" />,
colorClass: "text-purple-600 bg-purple-50 dark:bg-purple-950/40 dark:text-purple-400"
},
{
name: "Catálogo de Placas",
description: "Encontre placas de desenvolvimento ESP32 verificadas com filtros avançados e links seguros.",
icon: <ShoppingBag className="w-6 h-6" />,
colorClass: "text-blue-600 bg-blue-50 dark:bg-blue-950/40 dark:text-blue-400"
},
{
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 (
<div className="bg-linear-to-br from-blue-50 via-white to-purple-50 min-h-screen">
return (
<div className="bg-gradient-to-br from-slate-50 via-white to-purple-50/40 dark:from-slate-950 dark:via-slate-950 dark:to-slate-900 min-h-screen text-slate-900 dark:text-slate-100 transition-colors duration-300">
<Header />
<main className="px-4 pt-12 pb-16 max-w-7xl mx-auto">
{/* Hero Section */}
<section className="text-center mb-20">
<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">
ESPDocs
</h1>
<main className="px-6 pt-16 pb-24 max-w-7xl mx-auto">
{/* Hero Section */}
<section className="text-center mb-24 max-w-5xl mx-auto">
<h1 className="text-5xl md:text-7xl lg:text-8xl font-display font-extrabold bg-gradient-to-r from-blue-500 via-purple-500 to-pink-500 bg-clip-text text-transparent mb-6 tracking-tight leading-tight select-none">
ESPDocs
</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">
Organização e acesso facilitado ao ecossistema ESP32
</h2>
<h2 className="text-xl md:text-3xl text-slate-800 dark:text-slate-200 font-bold mb-6 max-w-3xl mx-auto leading-snug">
Organização e acesso facilitado ao ecossistema ESP32
</h2>
<p className="text-base md:text-lg text-gray-600 max-w-2xl mx-auto leading-relaxed mb-10">
Plataforma não oficial com interface em português que organiza e centraliza
informações sobre ESP32. Compare especificações, visualize diagramas e acesse
a documentação oficial da Espressif.
</p>
<p className="text-base md:text-lg text-slate-500 dark:text-slate-400 max-w-2xl mx-auto leading-relaxed mb-12">
Plataforma independente criada para desenvolvedores que buscam comparar especificações, consultar pinouts, selecionar chips ideais e acessar referências em português de forma rápida e intuitiva.
</p>
<div className="flex flex-col sm:flex-row gap-4 justify-center items-center">
<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"
href="/seletor"
>
<span className="text-xl">🎯</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">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 5l7 7-7 7" />
</svg>
</Link>
{/* Botões de Ação */}
<div className="flex flex-wrap gap-4 justify-center items-center">
<Link
className="group inline-flex items-center gap-2.5 bg-gradient-to-r from-purple-600 to-indigo-600 text-white px-7 py-3.5 rounded-xl font-bold text-sm shadow-lg hover:shadow-purple-500/20 hover:-translate-y-0.5 transition-all duration-300 cursor-pointer active:scale-95"
href="/seletor"
>
<Compass className="w-4 h-4" />
<span>Encontrar ESP32 Ideal</span>
<ArrowRight className="w-4 h-4 transform group-hover:translate-x-1 transition-transform" />
</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"
href="/series"
>
<span className="text-xl">📚</span>
<span>Explorar Séries ESP32</span>
</Link>
<Link
className="group inline-flex items-center gap-2.5 bg-slate-900 hover:bg-slate-800 dark:bg-slate-800 dark:hover:bg-slate-700 text-white px-7 py-3.5 rounded-xl font-bold text-sm shadow-md hover:-translate-y-0.5 transition-all duration-300 cursor-pointer active:scale-95"
href="/series"
>
<Cpu className="w-4 h-4" />
<span>Explorar Séries</span>
</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"
href="/frameworks"
>
<span className="text-xl">⚡</span>
<span>Frameworks</span>
</Link>
<Link
className="group inline-flex items-center gap-2.5 bg-white dark:bg-slate-900 text-slate-700 dark:text-slate-300 px-7 py-3.5 rounded-xl font-bold text-sm shadow-xs border border-slate-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"
>
<Terminal className="w-4 h-4" />
<span>Frameworks</span>
</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"
href="/catalogo"
>
<span className="text-xl">🛒</span>
<span>Catálogo</span>
</Link>
</div>
</section>
<Link
className="group inline-flex items-center gap-2.5 bg-white dark:bg-slate-900 text-slate-700 dark:text-slate-300 px-7 py-3.5 rounded-xl font-bold text-sm shadow-xs border border-slate-200 dark:border-slate-800 hover:border-purple-500 dark:hover:border-purple-400 hover:text-purple-600 dark:hover:text-purple-400 hover:-translate-y-0.5 transition-all duration-300 cursor-pointer active:scale-95"
href="/catalogo"
>
<ShoppingBag className="w-4 h-4" />
<span>Placas</span>
</Link>
</div>
</section>
{/* Recursos Section */}
<section className="mb-20">
<div className="text-center mb-12">
<h2 className="text-3xl md:text-4xl font-bold text-gray-900 mb-4">Recursos da Plataforma</h2>
<p className="text-lg text-gray-600 max-w-2xl mx-auto">Ferramentas e informações organizadas para facilitar seu trabalho com ESP32</p>
</div>
{/* Recursos Section */}
<section className="mb-24">
<div className="text-center mb-16">
<h2 className="text-3xl md:text-4xl font-display font-extrabold text-slate-800 dark:text-slate-100 mb-4">
Recursos da Plataforma
</h2>
<p className="text-base text-slate-500 dark:text-slate-400 max-w-xl mx-auto leading-relaxed">
Conjunto completo de utilitários projetados para maximizar a produtividade e eliminar atritos no desenvolvimento de firmware.
</p>
</div>
<div className="grid md:grid-cols-2 lg:grid-cols-3 gap-6">
{resources.map((resource, index) => (
<div
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"
style={{ animationDelay: `${index * 50}ms` }}
>
<div className="flex items-start gap-4">
<div className="text-4xl shrink-0 transform group-hover:scale-110 transition-transform">
{resource.icon}
</div>
<div className="flex-1">
<h3 className="text-lg font-bold text-gray-900 mb-2 group-hover:text-purple-600 transition-colors">
{resource.name}
</h3>
<p className="text-sm text-gray-600 leading-relaxed">
{resource.description}
</p>
</div>
</div>
</div>
))}
</div>
</section>
<div className="grid md:grid-cols-2 lg:grid-cols-3 gap-6">
{resources.map((resource) => (
<div
key={resource.name}
className="group bg-white/70 dark:bg-slate-900/40 backdrop-blur-md p-6 rounded-2xl border border-slate-200/60 dark:border-slate-800/80 hover:border-purple-500/40 dark:hover:border-purple-400/40 shadow-xs hover:shadow-xl hover:shadow-purple-500/5 hover:-translate-y-1 transition-all duration-300"
>
<div className="flex items-start gap-4">
<div className={`w-12 h-12 rounded-xl flex items-center justify-center shrink-0 transform group-hover:scale-105 transition-transform duration-300 shadow-xs ${resource.colorClass}`}>
{resource.icon}
</div>
<div className="flex-1">
<h3 className="text-base font-bold text-slate-800 dark:text-slate-100 mb-2 group-hover:text-purple-600 dark:group-hover:text-purple-400 transition-colors">
{resource.name}
</h3>
<p className="text-sm text-slate-500 dark:text-slate-400 leading-relaxed">
{resource.description}
</p>
</div>
</div>
</div>
))}
</div>
</section>
{/* Ciência Embarcada Section */}
<section className="mb-12">
<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">
{/* Logo e Link do Ciência Embarcada */}
<div className="flex flex-col items-center mb-6">
{/* Ciência Embarcada Section */}
<section className="mb-8">
<div className="bg-gradient-to-br from-white to-purple-50/50 dark:from-slate-900/40 dark:to-purple-950/10 rounded-3xl shadow-xs border border-slate-200/80 dark:border-slate-800/80 p-8 md:p-12 relative overflow-hidden">
{/* Background soft glow decoration */}
<div className="absolute right-0 bottom-0 w-80 h-80 bg-purple-500/5 rounded-full blur-3xl pointer-events-none" />
<div className="flex flex-col items-center text-center relative z-10 max-w-3xl mx-auto">
<a
href="https://cienciaembarcada.com.br"
target="_blank"
rel="noopener noreferrer"
className="group"
className="group mb-6"
>
<img
src="/logo.png"
alt="Ciência Embarcada"
className="h-16 md:h-20 mb-4 transition-transform duration-300 group-hover:scale-110"
alt="Ciência Embarcada Logo"
className="h-16 md:h-20 mb-2 transition-transform duration-300 group-hover:scale-105"
/>
</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"
target="_blank"
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
</a>
</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.
Visite o blog para aprender com tutoriais práticos e projetos reais!
<p className="text-sm text-slate-500 dark:text-slate-400 leading-relaxed mb-8 max-w-xl">
Acesse artigos profundos sobre programação de microcontroladores, design de hardware, Internet das Coisas (IoT), laboratórios práticos e projetos reais em nosso blog parceiro oficial.
</p>
<a
href="https://cienciaembarcada.com.br"
target="_blank"
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
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<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>
<span>Visitar Blog Ciência Embarcada</span>
<ExternalLinkIcon />
</a>
</div>
{/* Disclaimer */}
<div className="border-t-2 border-purple-200 pt-6 mt-6">
<div className="bg-blue-50 border-l-4 border-blue-500 p-4 rounded-r-lg">
<div className="flex items-start gap-3">
<svg className="w-5 h-5 text-blue-600 mt-0.5 shrink-0" fill="currentColor" viewBox="0 0 20 20">
<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>
<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>
</div>
</div>
</div>
</div>
</section>
</main>
{/* Disclaimer */}
<div className="border-t border-slate-200/80 dark:border-slate-800/80 pt-8 mt-10">
<div className="bg-slate-100/60 dark:bg-slate-900/60 border-l-4 border-purple-500 p-5 rounded-r-2xl">
<div className="flex gap-4">
<Info className="w-5 h-5 text-purple-600 dark:text-purple-400 mt-0.5 shrink-0" />
<p className="text-xs leading-relaxed text-slate-500 dark:text-slate-400">
<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>
</div>
</div>
</div>
</div>
</section>
</main>
<Footer />
</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>
);
}
+870 -762
View File
File diff suppressed because it is too large. Load diff
+449 -374
View File
@@ -6,420 +6,495 @@ import { notFound } from "next/navigation";
import seriesData from "@/public/series.json";
import ConnectionsDiagram from "@/components/ConnectionsDiagram";
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 }) {
const { key } = await params;
const serie = seriesData[key];
const { key } = await params;
const serie = seriesData[key];
if (!serie) {
notFound();
}
if (!serie) {
notFound();
}
let conexoes = [];
try {
conexoes = require(`@/public/conexoes/${key}.json`);
} catch (error) {
console.log(`Arquivo de conexões não encontrado para ${key}`);
}
let conexoes = [];
try {
conexoes = require(`@/public/conexoes/${key}.json`);
} catch (error) {
console.log(`Arquivo de conexões não encontrado para ${key}`);
}
const seriesKeys = Object.keys(seriesData);
const currentIndex = seriesKeys.indexOf(key);
const previousKey = currentIndex > 0 ? seriesKeys[currentIndex - 1] : null;
const nextKey = currentIndex < seriesKeys.length - 1 ? seriesKeys[currentIndex + 1] : null;
const seriesKeys = Object.keys(seriesData);
const currentIndex = seriesKeys.indexOf(key);
const previousKey = currentIndex > 0 ? seriesKeys[currentIndex - 1] : null;
const nextKey = currentIndex < seriesKeys.length - 1 ? seriesKeys[currentIndex + 1] : null;
const processadorSpecs = [
{ label: "Arquitetura", value: serie.arquitetura, icon: "🔧" },
{ label: "Núcleos", value: serie.nucleos, icon: "⚙️" },
{ label: "Frequência", value: serie.frequencia, icon: "🚀" },
{ label: "Coprocessador ULP", value: serie.coprocessador_ulp || "Não", icon: "💤" },
{ label: "Aceleradores IA", value: serie.aceleradores_ia || "Não", icon: "🤖" },
];
const processadorSpecs = [
{ label: "Arquitetura", value: serie.arquitetura, icon: <Cpu className="w-5 h-5 text-blue-500" /> },
{ label: "Núcleos", value: serie.nucleos, icon: <Sliders className="w-5 h-5 text-blue-500" /> },
{ 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: <Moon className="w-5 h-5 text-blue-500" /> },
{ label: "Aceleradores IA", value: serie.aceleradores_ia || "Não", icon: <Sparkles className="w-5 h-5 text-purple-500" /> },
];
const conectividadeSpecs = [
{ label: "Wi-Fi", value: serie.wifi || "Não", icon: "📶" },
{ label: "Bluetooth", value: serie.bluetooth || "Não", icon: "📱" },
{ label: "Zigbee/Thread", value: serie.zigbee_thread || "Não", icon: "🌐" },
{ label: "Matter", value: serie.matter || "Não", icon: "🏠" },
{ label: "Ethernet", value: serie.ethernet || serie.ethernet_mac || "Não", icon: "🔌" },
{ label: "CAN", value: serie.can || "Não", icon: "🚗" },
];
const conectividadeSpecs = [
{ 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: <Bluetooth className="w-5 h-5 text-indigo-500" /> },
{ 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: <Home className="w-5 h-5 text-indigo-500" /> },
{ 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: <Activity className="w-5 h-5 text-indigo-500" /> },
];
const memoriaSpecs = [
{ label: "SRAM", value: serie.memoria_sram, icon: "💾" },
{ label: "SRAM RTC", value: serie.memoria_sram_rtc, icon: "⏰" },
{ label: "ROM", value: serie.memoria_rom, icon: "📀" },
{ label: "Flash Externa", value: serie.flash_externa, icon: "💿" },
{ label: "PSRAM Externa", value: serie.psram_externa || "Não", icon: "🎯" },
];
const memoriaSpecs = [
{ 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: <Clock className="w-5 h-5 text-pink-500" /> },
{ label: "ROM", value: serie.memoria_rom, icon: <Folder className="w-5 h-5 text-pink-500" /> },
{ 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: <Sliders className="w-5 h-5 text-pink-500" /> },
];
const perifericos = [
{ label: "GPIO", value: serie.gpio, icon: "📍" },
{ label: "ADC", value: serie.adc, icon: "📊" },
{ label: "DAC", value: serie.dac || "Não", icon: "🔊" },
{ label: "Touch", value: serie.touch || "Não", icon: "👆" },
{ label: "UART", value: serie.uart, icon: "💬" },
{ label: "SPI", value: serie.spi, icon: "🔄" },
{ label: "I2C", value: serie.i2c, icon: "🔗" },
{ label: "I2S", value: serie.i2s, icon: "🎵" },
{ label: "PWM", value: serie.pwm, icon: "〰️" },
{ label: "USB", value: serie.usb || "Não", icon: "🔌" },
];
const perifericos = [
{ label: "GPIO", value: serie.gpio, icon: <MapPin className="w-5 h-5 text-emerald-500" /> },
{ label: "ADC", value: serie.adc, icon: <Activity className="w-5 h-5 text-emerald-500" /> },
{ 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: <Fingerprint className="w-5 h-5 text-emerald-500" /> },
{ label: "UART", value: serie.uart, icon: <MessageSquare className="w-5 h-5 text-emerald-500" /> },
{ label: "SPI", value: serie.spi, icon: <Repeat className="w-5 h-5 text-emerald-500" /> },
{ label: "I2C", value: serie.i2c, icon: <Share2 className="w-5 h-5 text-emerald-500" /> },
{ label: "I2S", value: serie.i2s, icon: <Music className="w-5 h-5 text-emerald-500" /> },
{ label: "PWM", value: serie.pwm, icon: <Sliders className="w-5 h-5 text-emerald-500" /> },
{ label: "USB", value: serie.usb || "Não", icon: <Plug className="w-5 h-5 text-emerald-500" /> },
];
const interfacesEspeciais = [];
if (serie.lcd) interfacesEspeciais.push({ label: "LCD", value: serie.lcd, icon: "🖥️" });
if (serie.camera) interfacesEspeciais.push({ label: "Camera", value: serie.camera, icon: "📷" });
if (serie.mipi_csi) interfacesEspeciais.push({ label: "MIPI CSI", value: serie.mipi_csi, icon: "📹" });
if (serie.mipi_dsi) interfacesEspeciais.push({ label: "MIPI DSI", value: serie.mipi_dsi, icon: "🖼️" });
if (serie.sdio) interfacesEspeciais.push({ label: "SDIO", value: serie.sdio, icon: "💳" });
if (serie.sensor_hall) interfacesEspeciais.push({ label: "Sensor Hall", value: serie.sensor_hall, icon: "🧲" });
if (serie.sensor_temperatura) interfacesEspeciais.push({ label: "Sensor Temperatura", value: serie.sensor_temperatura, icon: "🌡️" });
const interfacesEspeciais = [];
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: "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: <Sliders className="w-5 h-5 text-cyan-500" /> });
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: <Sliders className="w-5 h-5 text-cyan-500" /> });
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: <Thermometer className="w-5 h-5 text-cyan-500" /> });
// Consumo de Energia
const consumoEnergia = [];
if (serie.consumo_energia) {
Object.entries(serie.consumo_energia).forEach(([modo, consumo]) => {
const modoLabels = {
'active': 'Modo Ativo',
'active_cpu_240mhz': 'Ativo (CPU 240MHz)',
'active_cpu_160mhz': 'Ativo (CPU 160MHz)',
'active_wifi_tx': 'Ativo (Wi-Fi TX)',
'active_wifi_tx_peak': 'Ativo (Wi-Fi TX Pico)',
'active_wifi': 'Ativo (Wi-Fi)',
'active_wifi6': 'Ativo (Wi-Fi 6)',
'modem_sleep': 'Modem Sleep',
'modem_sleep_cpu_160mhz': 'Modem Sleep (CPU 160MHz)',
'light_sleep': 'Light Sleep',
'deep_sleep': 'Deep Sleep',
'hibernation': 'Hibernação'
};
// Consumo de Energia
const consumoEnergia = [];
if (serie.consumo_energia) {
Object.entries(serie.consumo_energia).forEach(([modo, consumo]) => {
const modoLabels = {
'active': 'Modo Ativo',
'active_cpu_240mhz': 'Ativo (CPU 240MHz)',
'active_cpu_160mhz': 'Ativo (CPU 160MHz)',
'active_wifi_tx': 'Ativo (Wi-Fi TX)',
'active_wifi_tx_peak': 'Ativo (Wi-Fi TX Pico)',
'active_wifi': 'Ativo (Wi-Fi)',
'active_wifi6': 'Ativo (Wi-Fi 6)',
'modem_sleep': 'Modem Sleep',
'modem_sleep_cpu_160mhz': 'Modem Sleep (CPU 160MHz)',
'light_sleep': 'Light Sleep',
'deep_sleep': 'Deep Sleep',
'hibernation': 'Hibernação'
};
consumoEnergia.push({
label: modoLabels[modo] || modo.replace(/_/g, ' ').replace(/\b\w/g, l => l.toUpperCase()),
value: consumo,
icon: "🔋"
});
});
}
consumoEnergia.push({
label: modoLabels[modo] || modo.replace(/_/g, ' ').replace(/\b\w/g, l => l.toUpperCase()),
value: consumo,
icon: <Battery className="w-5 h-5 text-teal-500" />
});
});
}
const tabs = [
{
id: 'devboard',
label: 'Placa de Desenvolvimento',
available: serie.placa,
content: (
<div className="bg-white rounded-3xl shadow-xl p-8 border-2 border-gray-100">
<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 }}>
<span>🔧</span>
Placa de Desenvolvimento Oficial
</h2>
<div className="flex justify-center">
<div className="relative w-full max-w-4xl">
<Image
src={serie.placa}
alt={`${key} DevKit - Placa de Desenvolvimento`}
width={1200}
height={800}
className="rounded-xl shadow-lg w-full h-auto"
priority
/>
<p className="text-center text-sm text-gray-600 mt-4">
{key} - Placa de desenvolvimento oficial da Espressif
</p>
</div>
</div>
</div>
)
},
{
id: 'connections',
label: 'Diagrama de Conexões',
available: conexoes.length > 0,
content: (
<div className="bg-white rounded-3xl shadow-xl p-8 border-2 border-gray-100">
<div className="bg-amber-50 border-l-4 border-amber-500 p-4 mb-6 rounded-lg">
<div className="flex items-start gap-3">
<span className="text-2xl">⚠️</span>
<div>
<h4 className="font-bold text-amber-900 mb-1">Diagrama do SoC (System on Chip)</h4>
<p className="text-amber-800 text-sm leading-relaxed">
Este diagrama mostra as conexões e periféricos do chip <strong>{key}</strong> (SoC),
não da placa de desenvolvimento completa. As placas de desenvolvimento podem ter
configurações diferentes de pinos expostos e componentes adicionais.
</p>
</div>
</div>
</div>
const tabs = [
{
id: 'devboard',
label: 'Placa de Desenvolvimento',
available: serie.placa,
content: (
<div className="bg-white/80 dark:bg-slate-900/40 backdrop-blur-xl rounded-3xl p-6 md:p-8 border border-slate-200/60 dark:border-slate-800/80 shadow-2xl">
<h2 className="text-xl font-bold text-slate-800 dark:text-slate-100 mb-6 pb-3 border-b border-slate-200/65 dark:border-slate-800/65 flex items-center gap-3">
<Sliders className="w-5 h-5" style={{ color: serie.cor }} />
<span>Kit de Desenvolvimento Oficial</span>
</h2>
<div className="flex justify-center">
<div className="relative w-full max-w-3xl bg-white dark:bg-white overflow-hidden rounded-2xl border border-slate-200/50 dark:border-slate-850/50 shadow-lg p-4">
<Image
src={serie.placa}
alt={`${key} DevKit - Placa de Desenvolvimento`}
width={1200}
height={800}
className="rounded-xl object-contain w-full h-auto max-h-[450px]"
priority
/>
<p className="text-center text-xs text-slate-500 dark:text-slate-400 mt-4 select-none">
Placa de desenvolvimento padrão da Espressif Systems para a série {key}
</p>
</div>
</div>
</div>
)
},
{
id: 'connections',
label: 'Diagrama de Conexões',
available: conexoes.length > 0,
content: (
<div className="bg-white/80 dark:bg-slate-900/40 backdrop-blur-xl rounded-3xl p-6 md:p-8 border border-slate-200/60 dark:border-slate-800/80 shadow-2xl">
<div className="bg-amber-500/10 text-amber-600 dark:text-amber-400 border border-amber-500/20 p-5 mb-8 rounded-2xl">
<div className="flex items-start gap-3">
<AlertTriangle className="w-5 h-5 shrink-0 mt-0.5" />
<div>
<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-xs text-amber-800 dark:text-amber-400 leading-relaxed">
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.
</p>
</div>
</div>
</div>
<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">
<h3 className="text-lg font-bold text-gray-800 mb-3 flex items-center gap-2">
<svg className="w-5 h-5 text-blue-600" fill="currentColor" viewBox="0 0 20 20">
<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>
Sobre a Matriz de Conexões Flexível
</h3>
<p className="text-gray-700 leading-relaxed">
O ESP32 possui uma matriz de conexões flexível que permite mapear diversas funções para múltiplos pinos.
Isso significa que muitas conexões podem ser configuradas para desempenhar diferentes papéis, dependendo das necessidades do seu projeto.
Consulte a documentação oficial para entender como aproveitar ao máximo as GPIOs&nbsp;
<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">
nesse link.
</a>
</p>
</div>
</div>
)
}
];
<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-sm font-bold text-slate-800 dark:text-slate-100 mb-3 flex items-center gap-2 select-none">
<Lightbulb className="w-4 h-4 text-purple-500" />
<span>Flexibilidade das Portas GPIO</span>
</h3>
<p className="text-xs text-slate-500 dark:text-slate-400 leading-relaxed">
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.
Para mais detalhes sobre as restrições físicas de GPIOs strapping ou analógicas, consulte o&nbsp;
<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-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>
</div>
</div>
)
}
];
return (
<div className="bg-linear-to-br from-blue-50 via-white to-purple-50 min-h-screen">
<Header />
return (
<div className="bg-gradient-to-br from-slate-50 via-white to-purple-50/40 dark:from-slate-950 dark:via-slate-950 dark:to-slate-900 min-h-screen text-slate-900 dark:text-slate-100 transition-colors duration-300">
<Header />
<main className="px-4 pt-16 pb-8 max-w-7xl mx-auto">
<div className="mb-8">
<Link href="/series" className="inline-flex items-center text-gray-600 hover:text-purple-600 transition-colors mb-4">
<span className="mr-2">←</span> Voltar para Séries
</Link>
<main className="px-6 pt-16 pb-24 max-w-7xl mx-auto">
<div className="bg-white rounded-3xl shadow-xl p-8 border-2 border-gray-100"
style={{ borderColor: serie.cor + '30' }}>
<div className="flex items-start justify-between flex-wrap gap-4">
<div className="flex items-center gap-4">
<span className="text-6xl">{serie.icone}</span>
<div>
<h1 className="text-4xl md:text-5xl font-extrabold text-gray-800 mb-2">
{key}
</h1>
<p className="text-xl text-gray-600 mb-2">{serie.nome_completo}</p>
<span className="inline-block px-4 py-2 rounded-full text-sm font-semibold text-white"
style={{ backgroundColor: serie.cor }}>
{serie.arquitetura}
</span>
</div>
</div>
{/* Breadcrumb Navigation */}
<div className="mb-8">
<Link
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>
<div className="grid gap-3 mx-auto md:mx-0">
<a
href={serie.datasheet}
target="_blank"
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"
>
📄 Datasheet Oficial
</a>
{/* Top Info Banner Card */}
<div
className="bg-white/80 dark:bg-slate-900/40 backdrop-blur-xl rounded-3xl p-6 md:p-8 shadow-2xl border relative overflow-hidden"
style={{ borderColor: `${serie.cor}40` }}
>
<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>
<h1 className="text-4xl md:text-5xl font-display font-extrabold text-slate-850 dark:text-slate-100 tracking-tight leading-none mb-3">
{key}
</h1>
<p className="text-sm font-semibold text-slate-400 dark:text-slate-500 mb-4">{serie.nome_completo}</p>
<span
className="inline-block px-3 py-1 rounded-full text-[10px] font-bold uppercase tracking-widest"
style={{ backgroundColor: `${serie.cor}18`, color: serie.cor, border: `1px solid ${serie.cor}30` }}
>
{serie.arquitetura}
</span>
</div>
</div>
<a
href={serie.guia_de_programacao}
target="_blank"
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"
>
💻 Guia de Programação
</a>
{/* Links Block */}
<div className="grid sm:grid-cols-2 lg:grid-cols-1 gap-3 w-full lg:w-auto">
<a
href={serie.datasheet}
target="_blank"
rel="noopener noreferrer"
className="inline-flex items-center justify-center gap-2 bg-slate-900 dark:bg-slate-100 text-white dark:text-slate-950 px-5 py-3 rounded-xl font-bold text-xs uppercase tracking-wider hover:opacity-90 transition-opacity shadow-lg"
>
<FileText className="w-4 h-4" />
<span>Datasheet Oficial</span>
</a>
{serie.manual_tecnico && <a
href={serie.manual_tecnico}
target="_blank"
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"
>
📖 Manual Técnico
</a>}
</div>
</div>
<a
href={serie.guia_de_programacao}
target="_blank"
rel="noopener noreferrer"
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"
>
<Code className="w-4 h-4" />
<span>Guia de Programação</span>
</a>
<p className="text-lg text-gray-700 mt-6 leading-relaxed">
{serie.descricao}
</p>
</div>
</div>
{serie.manual_tecnico && (
<a
href={serie.manual_tecnico}
target="_blank"
rel="noopener noreferrer"
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"
>
<BookOpen className="w-4 h-4" />
<span>Manual Técnico</span>
</a>
)}
</div>
</div>
<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 }}>
<h3 className="text-sm font-semibold text-gray-600 mb-2">PROCESSADOR</h3>
<p className="text-3xl font-bold text-gray-800">{serie.nucleos.includes("2") ? "Dual-Core" : "Single-Core"}</p>
<p className="text-gray-600 mt-1">{serie.frequencia}</p>
</div>
<p className="text-sm text-slate-500 dark:text-slate-400 mt-6 leading-relaxed max-w-4xl">
{serie.descricao}
</p>
</div>
</div>
<div className="bg-white rounded-2xl shadow-lg p-6 border-l-4" style={{ borderColor: serie.cor }}>
<h3 className="text-sm font-semibold text-gray-600 mb-2">MEMÓRIA SRAM</h3>
<p className="text-3xl font-bold text-gray-800">{serie.memoria_sram}</p>
<p className="text-gray-600 mt-1">+ {serie.memoria_sram_rtc} RTC</p>
</div>
{/* Primary Features Highlight Grid */}
<div className="grid md:grid-cols-3 gap-6 mb-8">
<div
className="bg-white/80 dark:bg-slate-900/40 backdrop-blur-xl rounded-2xl shadow-lg p-6 border-l-4"
style={{ borderColor: serie.cor }}
>
<h3 className="text-[10px] font-bold text-slate-400 dark:text-slate-500 uppercase tracking-widest mb-1.5 select-none">PROCESSADOR</h3>
<p className="text-2xl font-extrabold text-slate-800 dark:text-slate-100">{serie.nucleos.includes("2") ? "Dual-Core" : "Single-Core"}</p>
<p className="text-xs text-slate-400 mt-1">{serie.frequencia}</p>
</div>
<div className="bg-white rounded-2xl shadow-lg p-6 border-l-4" style={{ borderColor: serie.cor }}>
<h3 className="text-sm font-semibold text-gray-600 mb-2">GPIO</h3>
<p className="text-3xl font-bold text-gray-800">{serie.gpio}</p>
<p className="text-gray-600 mt-1">Pinos disponíveis</p>
</div>
</div>
<div
className="bg-white/80 dark:bg-slate-900/40 backdrop-blur-xl rounded-2xl shadow-lg p-6 border-l-4"
style={{ borderColor: serie.cor }}
>
<h3 className="text-[10px] font-bold text-slate-400 dark:text-slate-500 uppercase tracking-widest mb-1.5 select-none">MEMÓRIA SRAM</h3>
<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>
<SeriesTabMenu tabs={tabs} color={serie.cor} />
<div
className="bg-white/80 dark:bg-slate-900/40 backdrop-blur-xl rounded-2xl shadow-lg p-6 border-l-4"
style={{ borderColor: serie.cor }}
>
<h3 className="text-[10px] font-bold text-slate-400 dark:text-slate-500 uppercase tracking-widest mb-1.5 select-none">GPIO DISPONÍVEIS</h3>
<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 className="space-y-6">
<SpecSection title="⚙️ Processador" specs={processadorSpecs} cor={serie.cor} />
<SpecSection title="📡 Conectividade" specs={conectividadeSpecs} cor={serie.cor} />
<SpecSection title="💾 Memória" specs={memoriaSpecs} cor={serie.cor} />
{consumoEnergia.length > 0 && (
<div className="bg-white rounded-2xl shadow-lg p-6 border-2 border-gray-100">
<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 }}>
<span>🔋</span>
Consumo de Energia
</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">
<span className="text-2xl">💡</span>
<div>
<p className="text-blue-900 text-sm 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.
Para precisão, consulte o datasheet oficial.
</p>
</div>
</div>
</div>
<div className="grid md:grid-cols-2 lg:grid-cols-3 gap-4">
{consumoEnergia.map((spec, index) => (
<div key={index} className="flex items-start gap-3 p-3 rounded-lg hover:bg-gray-50 transition-colors">
<span className="text-2xl">{spec.icon}</span>
<div className="flex-1">
<p className="text-sm font-semibold text-gray-600">{spec.label}</p>
<p className="text-base font-bold text-gray-800 mt-1">{spec.value}</p>
</div>
</div>
))}
</div>
</div>
)}
<SpecSection title="🔌 Periféricos" specs={perifericos} cor={serie.cor} />
{interfacesEspeciais.length > 0 && (
<SpecSection title="✨ Interfaces Especiais" specs={interfacesEspeciais} cor={serie.cor} />
)}
<SpecSection
title="🌡️ Temperatura"
specs={[
{ label: "Operação", value: serie.temperatura_operacao, icon: "🔧" },
{ label: "Armazenamento", value: serie.temperatura_armazenamento, icon: "📦" },
]}
cor={serie.cor}
/>
</div>
{/* DevBoard and Connections Tabs */}
<SeriesTabMenu tabs={tabs} color={serie.cor} />
{/* Call-to-Action para Catálogo de Placas */}
<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="flex flex-col md:flex-row items-center justify-between gap-8">
<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">
<span className="text-xl">🎯</span>
<span className="text-sm font-semibold">Próximo Passo</span>
</div>
<h3 className="text-3xl md:text-4xl font-black mb-4">
Pronto para começar?
</h3>
<p className="text-lg text-white/90 leading-relaxed">
Encontre placas de desenvolvimento {key} verificadas no nosso catálogo.
Links diretos para lojas oficiais e confiáveis com os melhores preços!
</p>
</div>
<Link
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"
>
<span className="text-xl">🛒</span>
<span>Ver Placas Disponíveis</span>
<svg
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>
</div>
</div>
{/* Specs List Cards */}
<div className="space-y-6 mt-12">
<SpecSection title="Processador" specs={processadorSpecs} cor={serie.cor} icon={<Cpu className="w-5 h-5" style={{ color: serie.cor }} />} />
<SpecSection title="Conectividade RF" specs={conectividadeSpecs} cor={serie.cor} icon={<Wifi className="w-5 h-5" style={{ color: serie.cor }} />} />
<SpecSection title="Estrutura de Memória" specs={memoriaSpecs} cor={serie.cor} icon={<Database className="w-5 h-5" style={{ color: serie.cor }} />} />
<div className="grid sm:flex sm:justify-between items-center gap-4 mt-12 md:mx-8">
{previousKey ? (
<Link
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"
>
<span>←</span>
<div className="text-left">
<p className="text-xs text-gray-600">Anterior</p>
<p className="font-semibold text-gray-800">{previousKey}</p>
</div>
</Link>
) : null}
{/* Power Consumption section */}
{consumoEnergia.length > 0 && (
<div className="bg-white/80 dark:bg-slate-900/40 backdrop-blur-xl rounded-2xl p-6 md:p-8 border border-slate-200/60 dark:border-slate-800/80 shadow-2xl">
<h2 className="text-xl font-bold text-slate-800 dark:text-slate-100 mb-6 pb-3 border-b border-slate-200/65 dark:border-slate-800/65 flex items-center gap-3 select-none">
<Battery className="w-5 h-5" style={{ color: serie.cor }} />
<span>Consumo de Energia</span>
</h2>
<Link
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"
>
📊 Comparar Séries
</Link>
<div className="bg-blue-500/10 border border-blue-500/20 p-4 mb-6 rounded-xl">
<div className="flex items-start gap-3 text-blue-650 dark:text-blue-400">
<Lightbulb className="w-5 h-5 shrink-0 mt-0.5" />
<p className="text-xs leading-relaxed">
<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.
</p>
</div>
</div>
{nextKey ? (
<Link
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"
>
<div className="text-right">
<p className="text-xs text-gray-600">Próximo</p>
<p className="font-semibold text-gray-800">{nextKey}</p>
</div>
<span>→</span>
</Link>
) : null}
</div>
</main>
<div className="grid md:grid-cols-2 lg:grid-cols-3 gap-4">
{consumoEnergia.map((spec, index) => (
<div key={index} className="flex items-center gap-3.5 p-3.5 bg-slate-50/50 dark:bg-slate-950/20 rounded-xl hover:bg-purple-500/5 dark:hover:bg-purple-400/5 border border-slate-150/40 dark:border-slate-850/40 transition-colors">
<div className="p-2 bg-white dark:bg-slate-900 rounded-lg shadow-xs border border-slate-200/30 dark:border-slate-850/30 shrink-0">
{spec.icon}
</div>
<div className="min-w-0">
<p className="text-[10px] text-slate-400 dark:text-slate-500 font-bold uppercase tracking-wider">{spec.label}</p>
<p className="text-sm font-extrabold text-slate-800 dark:text-slate-200 mt-0.5">{spec.value}</p>
</div>
</div>
))}
</div>
</div>
)}
<Footer />
</div>
);
<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 && (
<SpecSection title="Interfaces de Hardware Especiais" specs={interfacesEspeciais} cor={serie.cor} icon={<Sparkles className="w-5 h-5" style={{ color: serie.cor }} />} />
)}
<SpecSection
title="Limites Operacionais de Temperatura"
icon={<Thermometer className="w-5 h-5" style={{ color: serie.cor }} />}
specs={[
{ label: "Operação", value: serie.temperatura_operacao, icon: <Thermometer className="w-5 h-5 text-red-500" /> },
{ label: "Armazenamento", value: serie.temperatura_armazenamento, icon: <Folder className="w-5 h-5 text-red-500" /> },
]}
cor={serie.cor}
/>
</div>
{/* DevBoard CTA Panel */}
<div className="my-20 bg-gradient-to-br from-slate-900 to-indigo-950 dark:from-slate-950 dark:to-purple-950/20 border border-slate-800/80 rounded-3xl p-8 md:p-12 shadow-2xl relative overflow-hidden">
<div className="absolute right-0 bottom-0 w-96 h-96 bg-purple-500/5 rounded-full blur-3xl pointer-events-none" />
<div className="flex flex-col md:flex-row items-center justify-between gap-8 relative z-10">
<div className="flex-1 max-w-2xl text-center md:text-left">
<div className="inline-flex items-center gap-2 bg-white/10 text-slate-350 px-3 py-1 rounded-full mb-4 text-xs font-semibold select-none">
<Home className="w-3.5 h-3.5" />
<span>Hardware Fisico</span>
</div>
<h3 className="text-2xl md:text-3xl font-display font-extrabold text-white mb-4">
Pronto para começar a prototipar?
</h3>
<p className="text-sm text-slate-400 leading-relaxed">
Explore toda a nossa grade de placas de desenvolvimento de parceiros oficiais para a série {key}. Compare dimensões, conversores USB-serial e pinagens prontas para protoboards.
</p>
</div>
<Link
href="/catalogo"
className="inline-flex items-center justify-center gap-2 bg-white text-slate-900 px-6 py-3.5 rounded-xl font-bold text-sm hover:bg-slate-100 transition-all duration-300 shrink-0 shadow-md active:scale-95"
>
<span>Ver Placas Disponíveis</span>
<ArrowRight className="w-4 h-4" />
</Link>
</div>
</div>
{/* Previous / Next Nav bars */}
<div className="grid sm:flex sm:justify-between items-center gap-4 mt-12 px-2 select-none">
{previousKey ? (
<Link
href={`/series/${previousKey}`}
className="flex items-center gap-3 bg-white/70 dark:bg-slate-900/40 backdrop-blur-xl px-5 py-3.5 rounded-2xl shadow-lg border border-slate-200/60 dark:border-slate-800/80 hover:border-purple-500/55 dark:hover:border-purple-400/55 transition-all group shrink-0"
>
<ArrowLeft className="w-4 h-4 text-slate-400 group-hover:-translate-x-1.5 transition-transform" />
<div className="text-left">
<p className="text-[10px] text-slate-400 dark:text-slate-500 font-bold uppercase tracking-wider">Anterior</p>
<p className="text-sm font-extrabold text-slate-800 dark:text-slate-200">{previousKey}</p>
</div>
</Link>
) : (
<div className="hidden sm:block w-36" />
)}
<Link
href="/comparacao"
className="inline-flex items-center justify-center gap-2 bg-slate-900 dark:bg-slate-100 text-white dark:text-slate-950 px-6 py-3.5 rounded-xl font-bold text-xs uppercase tracking-wider hover:opacity-90 transition-opacity shadow-md"
>
<Scale className="w-4 h-4" />
<span>Comparar Séries</span>
</Link>
{nextKey ? (
<Link
href={`/series/${nextKey}`}
className="flex items-center gap-3 bg-white/70 dark:bg-slate-900/40 backdrop-blur-xl px-5 py-3.5 rounded-2xl shadow-lg border border-slate-200/60 dark:border-slate-800/80 hover:border-purple-500/55 dark:hover:border-purple-400/55 transition-all group shrink-0"
>
<div className="text-right">
<p className="text-[10px] text-slate-400 dark:text-slate-500 font-bold uppercase tracking-wider">Próximo</p>
<p className="text-sm font-extrabold text-slate-800 dark:text-slate-200">{nextKey}</p>
</div>
<ArrowRight className="w-4 h-4 text-slate-400 group-hover:translate-x-1.5 transition-transform" />
</Link>
) : (
<div className="hidden sm:block w-36" />
)}
</div>
</main>
<Footer />
</div>
);
}
function SpecSection({ title, specs, cor }) {
return (
<div className="bg-white rounded-2xl shadow-lg p-6 border-2 border-gray-100">
<h2 className="text-2xl font-bold text-gray-800 mb-4 pb-3 border-b-2" style={{ borderColor: cor }}>
{title}
</h2>
<div className="grid md:grid-cols-2 lg:grid-cols-3 gap-4">
{specs.map((spec, index) => (
<div key={index} className="flex items-start gap-3 p-3 rounded-lg hover:bg-gray-50 transition-colors">
<span className="text-2xl">{spec.icon}</span>
<div className="flex-1">
<p className="text-sm font-semibold text-gray-600">{spec.label}</p>
<p className="text-base font-bold text-gray-800 mt-1">{spec.value}</p>
</div>
</div>
))}
</div>
</div>
);
function SpecSection({ title, specs, cor, icon }) {
return (
<div className="bg-white/80 dark:bg-slate-900/40 backdrop-blur-xl rounded-2xl p-6 md:p-8 border border-slate-200/60 dark:border-slate-800/80 shadow-2xl">
<h2 className="text-xl font-bold text-slate-800 dark:text-slate-100 mb-6 pb-3 border-b border-slate-200/65 dark:border-slate-800/65 flex items-center gap-3 select-none">
{icon}
<span>{title}</span>
</h2>
<div className="grid md:grid-cols-2 lg:grid-cols-3 gap-4">
{specs.map((spec, index) => (
<div key={index} className="flex items-center gap-3.5 p-3.5 bg-slate-50/50 dark:bg-slate-950/20 rounded-xl hover:bg-purple-500/5 dark:hover:bg-purple-400/5 border border-slate-150/40 dark:border-slate-850/40 transition-colors">
<div className="p-2 bg-white dark:bg-slate-900 rounded-lg shadow-xs border border-slate-200/30 dark:border-slate-850/30 shrink-0">
{spec.icon}
</div>
<div className="min-w-0">
<p className="text-[10px] text-slate-400 dark:text-slate-500 font-bold uppercase tracking-wider">{spec.label}</p>
<p className="text-sm font-extrabold text-slate-800 dark:text-slate-200 mt-0.5">{spec.value}</p>
</div>
</div>
))}
</div>
</div>
);
}
export async function generateMetadata({ params }) {
const { key } = await params;
const serie = seriesData[key];
const { key } = await params;
const serie = seriesData[key];
if (!serie) {
return {
title: "Série não encontrada",
};
}
if (!serie) {
return {
title: "Série não encontrada",
};
}
return {
title: `ESPDocs - ${serie.nome_completo}`,
description: serie.descricao,
};
return {
title: `ESPDocs - ${serie.nome_completo}`,
description: serie.descricao,
};
}
export async function generateStaticParams() {
return Object.keys(seriesData).map((key) => ({
key: key,
}));
return Object.keys(seriesData).map((key) => ({
key: key,
}));
}
+196 -133
View File
@@ -1,159 +1,222 @@
"use client";
import Header from "@/components/Header";
import Footer from "@/components/Footer";
import Link from "next/link";
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() {
const series = Object.entries(seriesData);
const series = Object.entries(seriesData);
return (
<div className="bg-linear-to-br from-blue-50 via-white to-purple-50 min-h-screen">
<Header />
return (
<div className="bg-gradient-to-br from-slate-50 via-white to-purple-50/40 dark:from-slate-950 dark:via-slate-950 dark:to-slate-900 min-h-screen text-slate-900 dark:text-slate-100 transition-colors duration-300">
<Header />
<main id="conteudo" className="px-4 pt-16 pb-20 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">
<span className="text-xl">⚡</span>
<span className="text-sm font-semibold text-gray-700">Explore o Ecossistema ESP32</span>
</div>
<main id="conteudo" className="px-6 pt-16 pb-24 max-w-7xl mx-auto">
<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">
Séries ESP32
</h1>
{/* Top Section */}
<section className="text-center mb-20">
<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>
<p className="text-lg md:text-xl text-gray-600 max-w-3xl mx-auto leading-relaxed">
Explore toda a família ESP32. Cada série foi projetada para atender necessidades específicas,
desde projetos simples até aplicações de IA e IoT avançadas.
</p>
</section>
<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
</h1>
<div className="grid md:grid-cols-2 lg:grid-cols-3 gap-6">
{series.map(([key, serie]) => (
<Link
key={key}
href={`/series/${key}`}
className="group"
>
<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"
style={{ '--hover-color': serie.cor }}>
<p className="text-base md:text-lg text-slate-500 dark:text-slate-400 max-w-3xl mx-auto leading-relaxed">
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.
</p>
</section>
<div className="p-6 border-b border-gray-100">
<div className="flex items-center justify-between mb-3">
<span className="text-4xl">{serie.icone}</span>
<span className="px-3 py-1 rounded-full text-xs font-semibold text-white"
style={{ backgroundColor: serie.cor }}>
{serie.arquitetura.includes("RISC-V") ? "RISC-V" : "Xtensa"}
</span>
</div>
{/* Series Grid */}
<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
key={key}
href={`/series/${key}`}
className="group block"
>
{/* Glassmorphic card container with dynamic color borders and shadows */}
<div
className="bg-white/80 dark:bg-slate-900/40 backdrop-blur-xl rounded-3xl p-6 md:p-8 border border-slate-200/60 dark:border-slate-800/80 hover:shadow-2xl transition-all duration-500 h-full flex flex-col justify-between"
style={{
borderColor: 'rgba(148, 163, 184, 0.1)',
transition: 'border-color 0.3s, box-shadow 0.3s, transform 0.3s'
}}
onMouseEnter={(e) => {
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>
</div>
<h2 className="text-2xl font-bold text-gray-800 mb-2 group-hover:text-purple-600 transition-colors">
{key}
</h2>
{/* 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}
</h2>
<p className="text-sm text-gray-600 line-clamp-2">
{serie.descricao}
</p>
</div>
<p className="text-xs text-slate-450 dark:text-slate-500 line-clamp-2 leading-relaxed mb-6">
{serie.descricao}
</p>
<div className="p-6 space-y-3">
<div className="flex items-center justify-between text-sm">
<span className="text-gray-600">⚙️ Núcleos:</span>
<span className="font-semibold text-gray-800">{serie.nucleos}</span>
</div>
{/* 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 className="flex items-center justify-between text-sm">
<span className="text-gray-600">🚀 Frequência:</span>
<span className="font-semibold text-gray-800">{serie.frequencia}</span>
</div>
<div className="flex items-center justify-between text-xs">
<div className="flex items-center gap-2 text-slate-400 dark:text-slate-500 font-semibold select-none">
<Activity className="w-3.5 h-3.5 text-amber-500/80" />
<span>Frequência:</span>
</div>
<span className="font-bold text-slate-800 dark:text-slate-200">{serie.frequencia}</span>
</div>
<div className="flex items-center justify-between text-sm">
<span className="text-gray-600">💾 SRAM:</span>
<span className="font-semibold text-gray-800">{serie.memoria_sram}</span>
</div>
<div className="flex items-center justify-between text-xs">
<div className="flex items-center gap-2 text-slate-400 dark:text-slate-500 font-semibold select-none">
<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 className="flex items-center justify-between text-sm">
<span className="text-gray-600">📶 Wi-Fi:</span>
<span className="font-semibold text-gray-800">
{String(serie.wifi).includes("Não") ? "❌" : "✅"}
</span>
</div>
<div className="flex items-center justify-between text-xs">
<div className="flex items-center gap-2 text-slate-400 dark:text-slate-500 font-semibold select-none">
<Wifi className="w-3.5 h-3.5 text-emerald-500/80" />
<span>Wi-Fi:</span>
</div>
<span className="font-bold text-slate-800 dark:text-slate-200">
{String(serie.wifi).includes("Não") ? (
<span className="text-slate-350 dark:text-slate-650">—</span>
) : (
<span className="text-emerald-500 dark:text-emerald-400 font-extrabold">Sim</span>
)}
</span>
</div>
<div className="flex items-center justify-between text-sm">
<span className="text-gray-600">📱 Bluetooth:</span>
<span className="font-semibold text-gray-800">
{serie.bluetooth === "Não" ? "❌" : "✅"}
</span>
</div>
<div className="flex items-center justify-between text-xs">
<div className="flex items-center gap-2 text-slate-400 dark:text-slate-500 font-semibold select-none">
<Bluetooth className="w-3.5 h-3.5 text-indigo-500/80" />
<span>Bluetooth:</span>
</div>
<span className="font-bold text-slate-800 dark:text-slate-200">
{serie.bluetooth === "Não" ? (
<span className="text-slate-350 dark:text-slate-650">—</span>
) : (
<span className="text-indigo-500 dark:text-indigo-400 font-extrabold">Sim</span>
)}
</span>
</div>
{serie.consumo_energia?.deep_sleep && (
<div className="flex items-center justify-between text-sm">
<span className="text-gray-600">🔋 Deep Sleep:</span>
<span className="font-semibold text-gray-800">{serie.consumo_energia.deep_sleep}</span>
</div>
)}
{serie.consumo_energia?.deep_sleep && (
<div className="flex items-center justify-between text-xs">
<div className="flex items-center gap-2 text-slate-400 dark:text-slate-500 font-semibold select-none">
<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>
</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>
)}
{/* 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" && (
<div className="pt-2 border-t border-gray-100">
<span className="inline-block px-2 py-1 bg-green-100 text-green-700 text-xs font-semibold rounded">
🏠 Suporte Matter
</span>
</div>
)}
</div>
{serie.matter === "Sim" && (
<span className="inline-flex items-center gap-1 px-2.5 py-1 bg-emerald-500/10 text-emerald-600 dark:text-emerald-400 text-[10px] font-bold uppercase rounded-lg border border-emerald-500/20 shadow-xs select-none">
<Home className="w-3 h-3" />
<span>Suporte Matter</span>
</span>
)}
</div>
<div className="px-6 pb-6">
<div className="flex items-center justify-between text-sm font-semibold group-hover:text-purple-600 transition-colors">
<span>Ver Detalhes</span>
<span className="transform group-hover:translate-x-2 transition-transform">→</span>
</div>
</div>
</div>
</Link>
))}
</div>
<div className="flex items-center justify-between text-xs font-bold text-slate-500 group-hover:text-purple-600 dark:group-hover:text-purple-400 transition-colors uppercase tracking-wider select-none border-t border-slate-100 dark:border-slate-800/60 pt-4">
<span>Ver Detalhes</span>
<ArrowRight className="w-4 h-4 transform group-hover:translate-x-2 transition-transform duration-300" />
</div>
</div>
</div>
</Link>
);
})}
</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">
<div className="text-center mb-8">
<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">
<span className="text-3xl">💡</span>
</div>
<h3 className="text-3xl font-bold text-gray-900 mb-3">
Não sabe qual escolher?
</h3>
<p className="text-gray-600 max-w-2xl mx-auto leading-relaxed text-base">
Compare as especificações de cada série para encontrar a mais adequada ao seu projeto.
Considere requisitos de conectividade, processamento e recursos específicos.
</p>
</div>
<div className="flex justify-center">
<Link
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"
>
<span className="text-xl">📊</span>
<span>Comparar Séries</span>
<svg
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>
</div>
</div>
</main>
{/* Quick comparison CTA Box */}
<div className="mt-20 bg-gradient-to-br from-slate-900 to-indigo-950 dark:from-slate-950 dark:to-purple-950/20 rounded-3xl p-8 md:p-12 shadow-2xl relative overflow-hidden border border-slate-800/80">
<div className="absolute right-0 bottom-0 w-96 h-96 bg-purple-500/5 rounded-full blur-3xl pointer-events-none" />
<Footer />
</div>
);
<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>
<h3 className="text-2xl md:text-3xl font-display font-extrabold text-white mb-4">
Ficou em dúvida de qual escolher?
</h3>
<p className="text-sm text-slate-400 leading-relaxed">
Use nosso comparador técnico dinâmico para posicionar as especificações de conectividade RF, potência de rádio e suporte a barramentos de periféricos lado a lado.
</p>
</div>
<Link
href="/comparacao"
className="inline-flex items-center justify-center gap-2 bg-white text-slate-900 px-6 py-3.5 rounded-xl font-bold text-sm hover:bg-slate-100 transition-all duration-300 shrink-0 shadow-md active:scale-95"
>
<span>Comparar Séries</span>
<ArrowRight className="w-4 h-4" />
</Link>
</div>
</div>
</main>
<Footer />
</div>
);
}
+234 -199
View File
@@ -1,227 +1,262 @@
import Header from "@/components/Header";
import Footer from "@/components/Footer";
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() {
const features = [
{ icon: "📚", 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: "🌐", 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." },
];
const features = [
{ 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: <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: <Globe className="w-6 h-6 text-emerald-500" />, title: "Acesso Centralizado", desc: "Links diretos para documentação oficial, repositórios e exemplos." },
{ 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 (
<div className="bg-linear-to-br from-blue-50 via-white to-purple-50 min-h-screen text-gray-800">
<a href="#conteudo" className="sr-only focus:not-sr-only focus:absolute focus:top-4 focus:left-4 focus:bg-white focus:px-4 focus:py-2 focus:rounded-md focus:shadow focus:ring-2 focus:ring-purple-500">
Ir para o conteúdo
</a>
return (
<div className="bg-gradient-to-br from-slate-50 via-white to-purple-50/40 dark:from-slate-950 dark:via-slate-950 dark:to-slate-900 min-h-screen text-slate-900 dark:text-slate-100 transition-colors duration-300">
<a href="#conteudo" className="sr-only focus:not-sr-only focus:absolute focus:top-4 focus:left-4 focus:bg-white focus:px-4 focus:py-2 focus:rounded-md focus:shadow focus:ring-2 focus:ring-purple-500">
Ir para o conteúdo
</a>
<Header />
<Header />
<main id="conteudo" className="px-4 pt-12 pb-20 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">
<div className="absolute inset-0 bg-black/10"></div>
<div className="relative z-10 max-w-4xl mx-auto">
<h1 className="text-4xl md:text-6xl font-black leading-tight mb-6">
Sobre o ESPDocs
</h1>
<main id="conteudo" className="px-6 pt-16 pb-24 max-w-7xl mx-auto">
{/* 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="relative z-10 max-w-4xl mx-auto">
<h1 className="text-4xl md:text-6xl font-display font-extrabold leading-tight mb-6">
Sobre o ESPDocs
</h1>
<p className="text-lg md:text-xl opacity-95 max-w-3xl mb-8 leading-relaxed">
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
da Espressif. Feita por um entusiasta para a comunidade.
</p>
<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
informações técnicas do ESP32 e facilita o acesso à documentação oficial
da Espressif. Desenvolvida para a comunidade de hardware livre.
</p>
<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">
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M10 19l-7-7m0 0l7-7m-7 7h18" />
</svg>
<span>Voltar</span>
</Link>
<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 active:scale-98 transition-all" aria-label="Voltar para a página inicial">
<ArrowLeft className="w-5 h-5" />
<span>Início</span>
</Link>
<a
href="https://github.com/lucasrguerra/ESPDocs"
target="_blank"
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"
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">
<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>
<span>GitHub</span>
</a>
</div>
</div>
</header>
<a
href="https://github.com/lucasrguerra/ESPDocs"
target="_blank"
rel="noopener noreferrer"
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)"
>
<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" />
</svg>
<span>GitHub</span>
</a>
</div>
</div>
</header>
<div className="mt-12 grid grid-cols-1 lg:grid-cols-3 gap-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">
<div className="flex items-center gap-3 mb-4">
<div className="w-12 h-12 bg-linear-to-br from-blue-500 to-purple-600 rounded-xl flex items-center justify-center">
<span className="text-2xl">🎯</span>
</div>
<h2 className="text-3xl font-bold text-gray-900">Missão</h2>
</div>
<p className="text-gray-700 leading-relaxed text-base">
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,
apresenta frameworks e centraliza links para a documentação oficial da
Espressif. O objetivo é economizar tempo na busca por informações técnicas.
</p>
</article>
{/* Two columns details grid */}
<div className="mt-12 grid grid-cols-1 lg:grid-cols-3 gap-8">
<section className="lg:col-span-2 space-y-8">
{/* Missao article */}
<article className="bg-white/80 dark:bg-slate-900/40 backdrop-blur-xl rounded-3xl p-8 shadow-xl border border-slate-200/60 dark:border-slate-800/80 hover:border-purple-500/20 dark:hover:border-purple-400/20 transition-all">
<div className="flex items-center gap-3.5 mb-5 select-none">
<div className="w-12 h-12 bg-purple-500/10 border border-purple-500/20 rounded-2xl flex items-center justify-center text-purple-500">
<Target className="w-6 h-6" />
</div>
<h2 className="text-2xl font-display font-extrabold text-slate-850 dark:text-slate-100">Missão</h2>
</div>
<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
interface em português que organiza especificações técnicas, compara séries,
apresenta frameworks e centraliza links para a documentação oficial da
Espressif. O objetivo principal é otimizar o tempo no desenvolvimento de firmware e IoT.
</p>
</article>
<article className="bg-white rounded-3xl p-8 shadow-lg border-2 border-gray-100">
<div className="flex items-center gap-3 mb-4">
<div className="w-12 h-12 bg-linear-to-br from-purple-500 to-pink-600 rounded-xl flex items-center justify-center">
<span className="text-2xl">❓</span>
</div>
<h2 className="text-3xl font-bold text-gray-900">Por que o ESPDocs?</h2>
</div>
<p className="text-gray-700 leading-relaxed mb-6 text-base">
As informações sobre ESP32 estão dispersas em datasheets (PDF), repositórios
GitHub e documentação oficial — tudo em inglês. O ESPDocs organiza essas
informações em uma interface em português, facilitando comparações e consultas
rápidas antes de mergulhar na documentação técnica oficial.
</p>
{/* Por que o ESPDocs article */}
<article className="bg-white/80 dark:bg-slate-900/40 backdrop-blur-xl rounded-3xl p-8 shadow-xl border border-slate-200/60 dark:border-slate-800/80">
<div className="flex items-center gap-3.5 mb-5 select-none">
<div className="w-12 h-12 bg-blue-500/10 border border-blue-500/20 rounded-2xl flex items-center justify-center text-blue-500">
<HelpCircle className="w-6 h-6" />
</div>
<h2 className="text-2xl font-display font-extrabold text-slate-850 dark:text-slate-100">Por que o ESPDocs?</h2>
</div>
<p className="text-slate-650 dark:text-slate-350 leading-relaxed mb-6 font-semibold text-sm">
As informações sobre os microcontroladores ESP32 muitas vezes estão dispersas em datasheets densos,
manuais de referência (TRM), repositórios secundários e documentação técnica — tudo inteiramente em inglês.
O ESPDocs condensa e estrutura esses dados, auxiliando na triagem rápida antes de aprofundar na engenharia oficial.
</p>
<div className="grid sm:grid-cols-2 gap-4">
{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 className="text-3xl shrink-0" aria-hidden="true">{f.icon}</div>
<div className="flex-1">
<h3 className="font-bold text-gray-900 mb-1 text-base">{f.title}</h3>
<p className="text-sm text-gray-600 leading-relaxed">{f.desc}</p>
</div>
</div>
))}
</div>
</article>
<div className="grid sm:grid-cols-2 gap-4">
{features.map((f) => (
<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="shrink-0 p-1 bg-white dark:bg-slate-900 rounded-xl shadow-xs" aria-hidden="true">
{f.icon}
</div>
<div className="flex-1">
<h3 className="font-bold text-slate-800 dark:text-slate-200 mb-1 text-sm">{f.title}</h3>
<p className="text-xs text-slate-500 dark:text-slate-450 leading-relaxed font-semibold">{f.desc}</p>
</div>
</div>
))}
</div>
</article>
<article className="bg-white rounded-3xl p-8 shadow-lg border-2 border-gray-100">
<div className="flex items-center gap-3 mb-4">
<div className="w-12 h-12 bg-linear-to-br from-green-500 to-teal-600 rounded-xl flex items-center justify-center">
<span className="text-2xl">🤝</span>
</div>
<h2 className="text-3xl font-bold text-gray-900">Como contribuir</h2>
</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
issue ou um pull request no repositório. Busco por clareza nas
descrições e exemplos testáveis.
</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">
<li className="font-medium">Fork do repositório e crie uma branch clara.</li>
<li className="font-medium">Adicione exemplos ou corrija o conteúdo com referências quando possível.</li>
<li className="font-medium">Abra um PR e descreva o que foi alterado.</li>
</ol>
</div>
</article>
{/* Contribuir article */}
<article className="bg-white/80 dark:bg-slate-900/40 backdrop-blur-xl rounded-3xl p-8 shadow-xl border border-slate-200/60 dark:border-slate-800/80">
<div className="flex items-center gap-3.5 mb-5 select-none">
<div className="w-12 h-12 bg-emerald-500/10 border border-emerald-500/20 rounded-2xl flex items-center justify-center text-emerald-500">
<Users className="w-6 h-6" />
</div>
<h2 className="text-2xl font-display font-extrabold text-slate-850 dark:text-slate-100">Como Contribuir</h2>
</div>
<article className="bg-linear-to-br from-purple-50 to-pink-50 rounded-3xl p-8 shadow-lg border-2 border-purple-100">
<div className="flex items-center gap-3 mb-4">
<div className="w-12 h-12 bg-linear-to-br from-purple-600 to-pink-600 rounded-xl flex items-center justify-center">
<span className="text-2xl">♿</span>
</div>
<h2 className="text-3xl font-bold text-gray-900">Acessibilidade</h2>
</div>
<p className="text-gray-700 leading-relaxed text-base">
O site prioriza leitura, contraste legível e navegação por teclado.
Se encontrar barreiras, por favor relate para que eu possa melhorar.
</p>
</article>
</section>
<p className="text-slate-650 dark:text-slate-350 leading-relaxed mb-6 font-semibold text-sm">
Correções de tradução, refinamento de tabelas e novos trechos funcionais de exemplos são muito bem-vindos!
A plataforma cresce por meio da comunidade.
</p>
<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">
<div className="flex flex-col items-center mb-6">
<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">
<img
src="/logo.png"
alt="Ciência Embarcada"
className="h-16 w-16 object-contain"
/>
</div>
<h3 className="text-xl font-bold text-gray-900 text-center">Sobre o Autor</h3>
</div>
<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">
<ol className="list-decimal list-inside text-slate-700 dark:text-slate-300 space-y-2 text-xs font-semibold">
<li>Faça o fork do repositório no GitHub.</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>
</div>
</article>
<p className="text-gray-700 text-sm leading-relaxed mb-4 text-justify">
<strong>Lucas Rayan Guerra</strong>, criador do <a
href="https://cienciaembarcada.com.br"
target="_blank"
rel="noopener noreferrer"
className="text-purple-600 hover:text-purple-800 font-semibold"
>
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.
</p>
{/* Accessibility note */}
<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="flex items-center gap-3.5 mb-5 select-none">
<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>
<h2 className="text-2xl font-display font-extrabold text-slate-850 dark:text-slate-100">Acessibilidade</h2>
</div>
<p className="text-slate-650 dark:text-slate-350 leading-relaxed font-semibold text-sm">
O ESPDocs preza pela legibilidade, alto contraste cromático e compatibilidade de navegação por teclado.
Caso encontre alguma barreira técnica de leitura ou navegação, sinta-se encorajado a relatar via Issue.
</p>
</article>
</section>
<p className="text-gray-700 text-sm leading-relaxed mb-4 text-justify">
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.
</p>
{/* Sidebar area */}
<aside className="space-y-6">
{/* Author Card */}
<div className="bg-white/80 dark:bg-slate-900/40 backdrop-blur-xl border border-slate-200/60 dark:border-slate-800/80 rounded-3xl p-8 shadow-xl">
<div className="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
src="/logo.png"
alt="Ciência Embarcada Logo"
className="h-16 w-16 object-contain"
/>
</div>
<h3 className="text-lg font-display font-extrabold text-slate-850 dark:text-slate-100 text-center">Sobre o Autor</h3>
</div>
<div className="bg-purple-50 border border-purple-100 rounded-lg p-4 mb-4 text-justify">
<p className="text-sm text-gray-700 italic">
"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."
</p>
</div>
<div className="space-y-4 text-xs font-semibold text-slate-650 dark:text-slate-350 text-justify leading-relaxed">
<p>
<strong>Lucas Rayan Guerra</strong>, criador da iniciativa <a
href="https://cienciaembarcada.com.br"
target="_blank"
rel="noopener noreferrer"
className="text-purple-600 hover:text-purple-800 dark:text-purple-400 dark:hover:text-purple-300 font-bold"
>
Ciência Embarcada
</a>, é Técnico em Desenvolvimento de Sistemas, estudante de Ciência da Computação na UFRPE e estuda Blockchain e Criptografia.
</p>
<a
href="https://cienciaembarcada.com.br"
target="_blank"
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"
>
🚀 Visitar Ciência Embarcada
</a>
</div>
<p>
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>
</div>
<div className="bg-white rounded-2xl p-6 shadow-md border border-gray-100">
<div className="flex items-center justify-between mb-3">
<h3 className="text-lg font-semibold">Contribua</h3>
<span className="text-xs bg-green-100 text-green-800 px-2 py-1 rounded-full">Open Source</span>
</div>
<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-[10px] text-slate-650 dark:text-slate-400 italic font-semibold leading-relaxed">
"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>
</div>
<p className="text-gray-700 mb-4">
Ajude a melhorar o ESPDocs com exemplos, correções e traduções.
</p>
<a
href="https://cienciaembarcada.com.br"
target="_blank"
rel="noopener noreferrer"
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"
>
<span>Visitar Ciência Embarcada</span>
</a>
</div>
<div className="flex flex-col gap-3">
<a
href="https://github.com/lucasrguerra/ESPDocs"
target="_blank"
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"
aria-label="Abrir repositório no GitHub"
>
Ver repositório
</a>
</div>
{/* Repository / Contributions card */}
<div className="bg-white/80 dark:bg-slate-900/40 backdrop-blur-xl border border-slate-200/60 dark:border-slate-800/80 rounded-3xl p-6 shadow-xl">
<div className="flex items-center justify-between mb-4 select-none">
<h3 className="text-sm font-display font-extrabold text-slate-800 dark:text-slate-200">Contribua</h3>
<span className="text-[9px] bg-emerald-500/10 text-emerald-600 dark:text-emerald-450 px-2.5 py-0.5 rounded-full font-bold uppercase tracking-wider border border-emerald-500/20">Open Source</span>
</div>
<div className="mt-4 text-sm text-gray-500">
<strong>Importante:</strong> Esta é uma plataforma de organização de informações, não uma
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>
<p className="text-xs text-slate-500 dark:text-slate-450 leading-relaxed mb-4 font-semibold">
Ajude a expandir a cobertura do ESPDocs submetendo exemplos de códigos ou aprimoramentos técnicos.
</p>
<div className="bg-white/80 rounded-2xl p-4 shadow-sm border border-gray-100">
<h4 className="font-semibold mb-2">Resumo</h4>
<ul className="text-sm text-gray-700 space-y-2">
<li>🔎 Conteúdo técnico explicado.</li>
<li>🧪 Exemplos e projetos.</li>
<li>🤝 Comunidade e contribuições.</li>
</ul>
</div>
</aside>
</div>
</main>
<a
href="https://github.com/lucasrguerra/ESPDocs"
target="_blank"
rel="noopener noreferrer"
className="inline-flex items-center justify-center gap-2 w-full px-4 py-3 rounded-xl bg-slate-900 dark:bg-slate-100 text-white dark:text-slate-950 font-bold text-xs uppercase tracking-wider shadow-md hover:opacity-90 active:scale-98 transition-all"
aria-label="Abrir repositório no GitHub"
>
<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>
<Footer />
</div>
);
<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>Aviso Legal:</strong> O ESPDocs é um agregador e organizador. Manuais técnicos detalhados e repositórios oficiais referenciados são de propriedade exclusiva da Espressif Systems. Para projetos críticos comerciais, consulte sempre a documentação nativa oficial.
</div>
</div>
{/* Summary sidebar card */}
<div className="bg-white/80 dark:bg-slate-900/40 backdrop-blur-xl border border-slate-200/60 dark:border-slate-800/80 rounded-3xl p-6 shadow-xl select-none">
<h4 className="text-xs font-display font-extrabold text-slate-800 dark:text-slate-200 mb-3 uppercase tracking-wider">Conteúdo Técnico</h4>
<ul className="text-xs text-slate-600 dark:text-slate-400 space-y-2.5 font-bold">
<li className="flex items-center gap-2">
<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>
</div>
</aside>
</div>
</main>
<Footer />
</div>
);
}
+88 -90
View File
@@ -1,101 +1,99 @@
"use client";
'use client';
import { Award, CheckCircle, HelpCircle, ExternalLink } from 'lucide-react';
export default function BoardCard({ board }) {
const mainFields = ['Nome', 'Link', 'Imagem', 'Loja Oficial'];
const mainFields = ['Nome', 'Link', 'Imagem', 'Loja Oficial'];
const technicalFields = Object.keys(board).filter(
key => !mainFields.includes(key) && board[key]
);
const technicalFields = Object.keys(board).filter(
key => !mainFields.includes(key) && board[key]
);
const hasFeature = (value) => {
const yesValues = ['sim', 'opcional'];
return yesValues.includes(value?.toString().toLowerCase().trim());
};
const hasFeature = (value) => {
const yesValues = ['sim', 'opcional'];
return yesValues.includes(value?.toString().toLowerCase().trim());
};
// Filtrar apenas características que têm "Sim" ou "Opcional"
const availableFeatures = technicalFields.filter(field => hasFeature(board[field]));
const optionalFeatures = technicalFields.filter(field =>
board[field]?.toString().toLowerCase().trim() === 'opcional'
);
// Filtrar apenas características que têm "Sim" ou "Opcional"
const availableFeatures = technicalFields.filter(field => hasFeature(board[field]));
const optionalFeatures = technicalFields.filter(field =>
board[field]?.toString().toLowerCase().trim() === 'opcional'
);
return (
<div className="bg-white rounded-2xl shadow-lg hover:shadow-2xl transition-all duration-300 overflow-hidden group flex flex-col h-full">
{/* Imagem */}
{board.Imagem && (
<div
className="mx-auto w-fit"
dangerouslySetInnerHTML={{ __html: board.Imagem }}
/>
)}
return (
<div className="bg-white/80 dark:bg-slate-900/40 backdrop-blur-xl border border-slate-200/60 dark:border-slate-800/80 rounded-3xl shadow-lg hover:shadow-2xl hover:-translate-y-1.5 transition-all duration-300 overflow-hidden group flex flex-col h-full animate-fadeIn select-none">
{/* Image Container with premium glass background */}
{board.Imagem && (
<div
className="mx-auto w-full p-4 flex justify-center bg-white dark:bg-white border-b border-slate-100 dark:border-slate-850/40 shrink-0"
dangerouslySetInnerHTML={{ __html: board.Imagem }}
/>
)}
<div className="p-6 flex flex-col grow">
{/* Nome */}
<h3 className="text-xl font-bold text-gray-800 mb-2 line-clamp-2">
{board.Nome || 'Placa sem nome'}
</h3>
<div className="p-6 flex flex-col grow justify-between">
<div>
{/* Loja Oficial Badge */}
{board['Loja Oficial'] && board['Loja Oficial'].toLowerCase() === 'sim' && (
<div className="mb-2">
<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">
<Award className="w-3.5 h-3.5 shrink-0" />
<span>Loja Oficial</span>
</span>
</div>
)}
{/* Loja Oficial */}
{board['Loja Oficial'] && board['Loja Oficial'].toLowerCase() === 'sim' && (
<div className="mb-4">
<p className="text-sm text-blue-600 flex items-center gap-1.5">
<svg className="w-4 h-4" fill="currentColor" viewBox="0 0 20 20">
<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" />
</svg>
<span>Link da loja oficial do fabricante</span>
</p>
</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 */}
{availableFeatures.length > 0 && (
<div className="mb-4 grow">
<div className="flex items-center gap-2 mb-3">
<svg className="w-4 h-4 text-green-600" fill="currentColor" viewBox="0 0 20 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" />
</svg>
<h4 className="text-sm font-semibold text-gray-700">Recursos Disponíveis</h4>
</div>
<div className="flex flex-wrap gap-2">
{availableFeatures.map((field) => {
const isOptional = optionalFeatures.includes(field);
return (
<div
key={field}
className={`text-xs px-3 py-1.5 rounded-lg font-medium flex items-center gap-1 ${
isOptional
? 'bg-yellow-100 text-yellow-700 border border-yellow-200'
: 'bg-green-100 text-green-700 border border-green-200'
}`}
>
{isOptional ? (
<svg className="w-3 h-3" fill="currentColor" viewBox="0 0 20 20">
<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">
<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>
</div>
);
})}
</div>
</div>
)}
{/* Características Técnicas */}
{availableFeatures.length > 0 && (
<div className="mb-6">
<h4 className="text-[9px] font-bold text-slate-400 dark:text-slate-500 uppercase tracking-widest flex items-center gap-1.5 mb-2.5">
<CheckCircle className="w-3.5 h-3.5 text-emerald-500" />
<span>Recursos Embarcados</span>
</h4>
{/* Link para mais informações - sempre no final */}
{board.Link && (
<a
href={board.Link}
target="_blank"
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"
>
Abrir no AliExpress 🔗
</a>
)}
</div>
</div>
);
<div className="flex flex-wrap gap-1.5">
{availableFeatures.map((field) => {
const isOptional = optionalFeatures.includes(field);
return (
<div
key={field}
className={`text-[9px] px-2 py-0.5 rounded-lg font-bold flex items-center gap-1 ${
isOptional
? 'bg-amber-500/10 dark:bg-amber-500/15 border border-amber-500/20 text-amber-600 dark:text-amber-400'
: 'bg-emerald-500/10 dark:bg-emerald-500/15 border border-emerald-500/20 text-emerald-600 dark:text-emerald-400'
}`}
>
{isOptional ? (
<HelpCircle className="w-2.5 h-2.5 shrink-0" />
) : (
<CheckCircle className="w-2.5 h-2.5 shrink-0" />
)}
<span>{field}</span>
</div>
);
})}
</div>
</div>
)}
</div>
{/* Link de Compra */}
{board.Link && (
<a
href={board.Link}
target="_blank"
rel="noopener noreferrer"
className="block w-full bg-slate-900 dark:bg-slate-100 text-white dark:text-slate-950 text-center px-4 py-3 rounded-xl font-bold text-xs uppercase tracking-wider hover:opacity-90 active:scale-98 transition-all shadow-md mt-auto flex items-center justify-center gap-1.5"
>
<span>Adquirir no AliExpress</span>
<ExternalLink className="w-3.5 h-3.5" />
</a>
)}
</div>
</div>
);
}
+155 -118
View File
@@ -1,141 +1,178 @@
'use client'
import { useEffect, useState } from 'react'
import { Code, Copy, CheckCircle, ExternalLink, HelpCircle } from 'lucide-react'
export default function CodeExample({ framework }) {
const examplesUrl = framework?.exemplos;
const color = framework?.cor || '#6B7280';
const examplesUrl = framework?.exemplos;
const color = framework?.cor || '#6B7280';
const [sample, setSample] = useState('');
const [loading, setLoading] = useState(false);
const [error, setError] = useState(null);
const [copied, setCopied] = useState(false);
const [sample, setSample] = useState('');
const [loading, setLoading] = useState(false);
const [error, setError] = useState(null);
const [copied, setCopied] = useState(false);
useEffect(() => {
setSample('');
setError(null);
setLoading(false);
useEffect(() => {
setSample('');
setError(null);
setLoading(false);
if (!framework) return;
if (!framework) return;
const exemploPath = framework.exemplo;
if (!exemploPath) {
const msg = `// Exemplos completos: ${examplesUrl || 'Ver repositório'}\n// Abra o link acima para ver exemplos e código completo.\n`;
setSample(msg);
return;
}
const exemploPath = framework.exemplo;
if (!exemploPath) {
const msg = `// Exemplos completos: ${examplesUrl || 'Ver repositório'}\n// Abra o link acima para ver exemplos e código completo.\n`;
setSample(msg);
return;
}
const url = exemploPath.startsWith('/') ? exemploPath : `/${exemploPath}`;
const url = exemploPath.startsWith('/') ? exemploPath : `/${exemploPath}`;
let cancelled = false;
setLoading(true);
let cancelled = false;
setLoading(true);
fetch(url)
.then((res) => {
if (!res.ok) throw new Error(`HTTP ${res.status}`);
return res.text();
})
.then((text) => {
if (cancelled) return;
if (text && text.trim().length > 0) {
setSample(text);
} else {
setSample(`// Arquivo de exemplo está vazio. Consulte: ${examplesUrl || 'repositório'}\n`);
setError('Arquivo vazio');
}
})
.catch((err) => {
if (cancelled) return;
setSample(`// Não foi possível carregar o exemplo local. Consulte: ${examplesUrl || 'repositório'}\n`);
setError(err.message || 'Falha ao carregar exemplo');
})
.finally(() => {
if (!cancelled) setLoading(false);
});
fetch(url)
.then((res) => {
if (!res.ok) throw new Error(`HTTP ${res.status}`);
return res.text();
})
.then((text) => {
if (cancelled) return;
if (text && text.trim().length > 0) {
setSample(text);
} else {
setSample(`// Arquivo de exemplo está vazio. Consulte o repositório oficial.\n`);
setError('Arquivo vazio');
}
})
.catch((err) => {
if (cancelled) return;
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');
})
.finally(() => {
if (!cancelled) setLoading(false);
});
return () => {
cancelled = true;
};
}, [framework?.exemplo, framework?.exemplos]);
return () => {
cancelled = true;
};
}, [framework?.exemplo, framework?.exemplos]);
const lines = (sample || '').split('\n');
const lines = (sample || '').split('\n');
async function handleCopy() {
try {
await navigator.clipboard.writeText(sample);
setCopied(true);
setTimeout(() => setCopied(false), 2000);
} catch {}
}
async function handleCopy() {
try {
await navigator.clipboard.writeText(sample);
setCopied(true);
setTimeout(() => setCopied(false), 2000);
} catch {}
}
return (
<div className="bg-white rounded-2xl shadow-lg p-6 border-2 border-gray-100 mb-8">
<div className="md:flex items-start justify-between gap-4 mb-4">
<div>
<h3 className="text-lg font-semibold text-gray-800 mb-1">Exemplo de Código</h3>
<p className="text-sm text-gray-600">Trecho rápido para começar — personalize conforme necessário.</p>
</div>
return (
<div className="bg-white/80 dark:bg-slate-900/40 backdrop-blur-xl rounded-3xl p-6 md:p-8 border border-slate-200/60 dark:border-slate-800/80 shadow-2xl mb-8 animate-fadeIn">
<div className="md:flex items-center justify-between gap-6 mb-6">
<div className="select-none">
<h3 className="text-xl font-display font-extrabold text-slate-850 dark:text-slate-100 mb-1 flex items-center gap-2">
<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 className="grid grid-cols-2 md:flex items-center gap-2">
<span
className="inline-flex items-center text-sm font-semibold px-3 py-1 rounded-full text-white"
style={{ backgroundColor: color }}
>
{framework?.linguagem || 'Código'}
</span>
<div className="flex flex-wrap items-center gap-2.5 mt-4 md:mt-0 select-none">
<span
className="inline-flex items-center text-[10px] font-bold px-2.5 py-1 rounded-full uppercase tracking-wider"
style={{
backgroundColor: `${color}18`,
color,
border: `1px solid ${color}25`
}}
>
{framework?.linguagem || 'Código'}
</span>
<button
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"
title="Copiar código"
>
{copied ? '✅ Copiado' : '📋 Copiar'}
</button>
<button
onClick={handleCopy}
className="inline-flex items-center gap-1.5 bg-slate-50 dark:bg-slate-850 hover:bg-slate-100 dark:hover:bg-slate-800 text-slate-700 dark:text-slate-250 px-3.5 py-2 rounded-xl font-bold text-xs shadow-xs border border-slate-200/60 dark:border-slate-800/80 cursor-pointer transition active:scale-95"
title="Copiar código para a área de transferência"
>
{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>
{examplesUrl && (
<a
href={examplesUrl}
target="_blank"
rel="noopener noreferrer"
className="inline-flex items-center gap-2 px-3 py-2 rounded-lg font-semibold text-white col-span-2"
style={{
background: `linear-gradient(90deg, ${color} 0%, rgba(124,58,237,1) 100%)`
}}
title="Abrir exemplos no GitHub"
>
🔗 Ver Outros no GitHub
</a>
)}
</div>
</div>
{examplesUrl && (
<a
href={examplesUrl}
target="_blank"
rel="noopener noreferrer"
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={{
background: `linear-gradient(90deg, ${color} 0%, rgba(147,51,234,1) 100%)`,
boxShadow: `0 8px 20px -8px ${color}`
}}
title="Abrir exemplos no GitHub"
>
<span>Mais Exemplos</span>
<ExternalLink className="w-3.5 h-3.5" />
</a>
)}
</div>
</div>
<div className="rounded-lg overflow-hidden border">
<div className="p-0">
<div className="flex items-center justify-between bg-linear-to-r from-gray-900 to-gray-800 p-3">
<div className="text-xs text-gray-300 font-medium">{framework?.nome}</div>
<div className="text-xs text-gray-400">
{loading ? 'Carregando exemplo...' : (framework?.exemplo ? 'Exemplo do framework' : 'Abrir no GitHub')}
</div>
</div>
</div>
<div className="rounded-2xl overflow-hidden border border-slate-200/60 dark:border-slate-800/80 shadow-md">
<div className="select-none">
<div className="flex items-center justify-between bg-slate-100/80 dark:bg-slate-950/70 border-b border-slate-200/60 dark:border-slate-850/60 px-4 py-3.5">
<div className="text-xs font-bold text-slate-750 dark:text-slate-250 flex items-center gap-1.5">
<span className="w-2.5 h-2.5 rounded-full bg-red-500/80" />
<span className="w-2.5 h-2.5 rounded-full bg-yellow-500/80" />
<span className="w-2.5 h-2.5 rounded-full bg-green-500/80" />
<span className="ml-2 font-mono text-[10px] text-slate-500 dark:text-slate-400">{framework?.nome} example</span>
</div>
<div className="text-[9px] font-bold text-slate-400 dark:text-slate-500 uppercase tracking-widest">
{loading ? 'Buscando exemplo...' : (framework?.exemplo ? 'Local Source' : 'External GitHub')}
</div>
</div>
</div>
<div className="flex">
<pre
className="bg-gray-900 text-gray-100 p-4 w-full overflow-auto"
style={{ fontFamily: 'ui-monospace, SFMono-Regular, Menlo, Monaco, "Roboto Mono", "Courier New", monospace' }}
>
{lines.map((line, idx) => (
<div key={idx} className="flex select-text">
<span className="text-gray-500 pr-4 text-right w-8 select-none">{idx + 1}</span>
<code className="whitespace-pre-wrap flex-1">{line === '' ? '\u00A0' : line}</code>
</div>
))}
</pre>
</div>
</div>
<div className="flex bg-slate-950">
<pre
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' }}
>
{lines.map((line, idx) => (
<div key={idx} className="flex">
<span className="text-slate-600 dark:text-slate-650 pr-4 text-right w-8 select-none border-r border-slate-900/60 mr-4 font-semibold">{idx + 1}</span>
<code className="whitespace-pre flex-1 text-left">{line === '' ? '\u00A0' : line}</code>
</div>
))}
</pre>
</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>}
<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>
);
{error && (
<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>
);
}
File diff suppressed because it is too large. Load diff
+136 -112
View File
@@ -1,123 +1,147 @@
"use client";
import { useEffect, useState } from "react";
import { ExternalLink } from "lucide-react";
export default function Footer() {
const year = new Date().getFullYear();
const [year, setYear] = useState(2026);
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">
<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="grid grid-cols-1 md:grid-cols-3 gap-6">
<div className="space-y-3">
<h3 className="text-2xl font-extrabold tracking-tight">ESPDocs</h3>
<p className="text-sm text-white/90 max-w-sm">
Documentação não oficial em português para ESP32 — focada em clareza, exemplos práticos e comunidade.
</p>
useEffect(() => {
setYear(new Date().getFullYear());
}, []);
<div className="">
<a
href="https://github.com/lucasrguerra/ESPDocs"
target="_blank"
rel="noopener noreferrer"
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"
>
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" aria-hidden="true" className="opacity-90">
<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>
Ver no GitHub
</a>
</div>
</div>
return (
<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-7xl mx-auto px-6 py-12 md:py-16">
<div className="grid grid-cols-1 md:grid-cols-3 gap-10 md:gap-16">
<nav aria-label="Links úteis" className="text-sm">
<h4 className="text-sm font-semibold mb-2">Links úteis</h4>
<ul className="space-y-2">
<li>
<a
href="https://github.com/lucasrguerra/ESPDocs"
target="_blank"
rel="noopener noreferrer"
className="hover:underline text-white/95 focus:outline-none focus:ring-2 focus:ring-white/20 rounded"
aria-label="Abrir repositório no GitHub"
>
Repositório no GitHub
</a>
</li>
<li>
<a
href="https://github.com/lucasrguerra/ESPDocs/issues"
target="_blank"
rel="noopener noreferrer"
className="hover:underline text-white/90 focus:outline-none focus:ring-2 focus:ring-white/20 rounded"
aria-label="Abrir issues do repositório"
>
Reportar bug / Abrir issue
</a>
</li>
<li>
<a
href="https://cienciaembarcada.com.br"
target="_blank"
rel="noopener noreferrer"
className="hover:underline text-white/90 focus:outline-none focus:ring-2 focus:ring-white/20 rounded"
aria-label="Visitar Ciência Embarcada (abre em nova aba)"
>
Ciência Embarcada
</a>
</li>
</ul>
</nav>
{/* Coluna 1: Logo & Info */}
<div className="space-y-4">
<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>
<div className="space-y-3">
<h4 className="text-sm font-semibold">Créditos</h4>
<p className="text-sm text-white/90">
Desenvolvido por{' '}
<a
href="https://linkedin.com/in/lucasrguerra"
target="_blank"
rel="noopener noreferrer"
className="underline hover:text-white"
aria-label="Perfil LinkedIn de Lucas Rayan Guerra (abre em nova aba)"
>
Lucas Rayan Guerra
</a>
.
</p>
<div className="pt-2">
<a
href="https://github.com/lucasrguerra/ESPDocs"
target="_blank"
rel="noopener noreferrer"
aria-label="Ver repositório do ESPDocs no GitHub (abre em nova aba)"
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 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"/>
</svg>
<span>Ver no GitHub</span>
</a>
</div>
</div>
<div className="flex items-center gap-3 mt-2">
<a
href="https://github.com/lucasrguerra/ESPDocs"
target="_blank"
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"
aria-label="GitHub do projeto"
>
<svg width="18" height="18" 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"/>
</svg>
</a>
{/* Coluna 2: Links Úteis */}
<nav aria-label="Links úteis" className="space-y-4">
<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>
<a
href="https://github.com/lucasrguerra/ESPDocs"
target="_blank"
rel="noopener noreferrer"
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"
>
<span>Repositório Principal</span>
<ExternalLink className="w-3.5 h-3.5 opacity-60" />
</a>
</li>
<li>
<a
href="https://github.com/lucasrguerra/ESPDocs/issues"
target="_blank"
rel="noopener noreferrer"
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"
>
<span>Reportar um Bug / Feedback</span>
<ExternalLink className="w-3.5 h-3.5 opacity-60" />
</a>
</li>
<li>
<a
href="https://cienciaembarcada.com.br"
target="_blank"
rel="noopener noreferrer"
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)"
>
<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>
</li>
</ul>
</nav>
<a
href="https://linkedin.com/in/lucasrguerra"
target="_blank"
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"
aria-label="LinkedIn do autor"
>
<svg width="18" height="18" 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"/>
</svg>
</a>
</div>
</div>
</div>
{/* Coluna 3: Autor & Social */}
<div className="space-y-4">
<h4 className="text-sm font-bold uppercase tracking-wider text-slate-800 dark:text-slate-200">
Sobre o Autor
</h4>
<p className="text-sm leading-relaxed text-slate-500 dark:text-slate-400">
Desenvolvido com dedicação por{' '}
<a
href="https://linkedin.com/in/lucasrguerra"
target="_blank"
rel="noopener noreferrer"
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)"
>
Lucas Rayan Guerra
</a>
, compartilhando eletrônica, IoT e computação no Brasil.
</p>
<div className="mt-6 pt-4 border-t border-white/10 text-sm text-white/80 text-center">
<div>
© {year} ESPDocs — Conteúdo não oficial. Consulte sempre datasheets e documentos oficiais para decisões críticas.
</div>
</div>
</div>
</footer>
);
<div className="flex items-center gap-3 pt-2">
<a
href="https://github.com/lucasrguerra/ESPDocs"
target="_blank"
rel="noopener noreferrer"
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"
>
<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"/>
</svg>
</a>
<a
href="https://linkedin.com/in/lucasrguerra"
target="_blank"
rel="noopener noreferrer"
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"
>
<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"/>
</svg>
</a>
</div>
</div>
</div>
{/* Divisor & Isenção */}
<div className="mt-12 pt-8 border-t border-slate-205/60 dark:border-slate-800/80 flex flex-col md:flex-row justify-between items-center gap-4 text-xs text-slate-400 dark:text-slate-500">
<p className="text-center md:text-left leading-relaxed">
© {year} ESPDocs. Esta plataforma independente de código aberto não possui afiliação com a Espressif Systems.
</p>
<p className="text-center md:text-right shrink-0">
Feito para a comunidade maker 🇧🇷
</p>
</div>
</div>
</footer>
);
}
+204 -162
View File
@@ -1,181 +1,223 @@
"use client";
import { usePathname } from "next/navigation";
import { useState } from "react";
import { useState, useEffect } from "react";
import Link from "next/link";
import Button from "@mui/material/Button";
import Drawer from "@mui/material/Drawer";
import List from "@mui/material/List";
import ListItem from "@mui/material/ListItem";
import MenuIcon from "@mui/icons-material/Menu";
import HomeIcon from "@mui/icons-material/Home";
import AboutIcon from "@mui/icons-material/Info";
import ModelsIcon from "@mui/icons-material/Memory";
import FrameworksIcon from "@mui/icons-material/Apps";
import CompareIcon from "@mui/icons-material/CompareArrows";
import ShoppingCartIcon from "@mui/icons-material/ShoppingCart";
import MenuBookIcon from "@mui/icons-material/MenuBook";
import FindInPageIcon from "@mui/icons-material/FindInPage";
import { useTheme } from "next-themes";
import { createPortal } from "react-dom";
import {
Home,
Info,
BookOpen,
Cpu,
SlidersHorizontal,
Terminal,
Scale,
ShoppingBag,
Menu,
X,
Sun,
Moon
} from "lucide-react";
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();
const menuItems = [
{ text: "Início", href: "/", icon: <HomeIcon />, featured: true },
{ text: "Sobre o Projeto", brief: "Sobre", href: "/sobre", icon: <AboutIcon /> },
{ text: "Glossário", href: "/glossario", icon: <MenuBookIcon /> },
{ text: "Séries", href: "/series", icon: <ModelsIcon />, featured: true },
{ text: "Seletor de ESP32", brief: "Seletor", href: "/seletor", icon: <FindInPageIcon />, featured: true },
{ text: "Frameworks", href: "/frameworks", icon: <FrameworksIcon />, featured: true },
{ text: "Comparar Séries", brief: "Comparar", href: "/comparacao", icon: <CompareIcon />, featured: true },
{ text: "Catálogo de Placas", brief: "Catálogo", href: "/catalogo", icon: <ShoppingCartIcon /> },
];
useEffect(() => {
setMounted(true);
}, []);
const featuredItems = menuItems.filter(item => item.featured);
const menuItems = [
{ text: "Início", href: "/", icon: <Home className="w-4 h-4" />, featured: true },
{ text: "Sobre o Projeto", brief: "Sobre", href: "/sobre", icon: <Info className="w-4 h-4" /> },
{ text: "Glossário", href: "/glossario", icon: <BookOpen className="w-4 h-4" /> },
{ text: "Séries", href: "/series", icon: <Cpu className="w-4 h-4" />, featured: true },
{ text: "Seletor de ESP32", brief: "Seletor", href: "/seletor", icon: <SlidersHorizontal className="w-4 h-4" />, featured: true },
{ text: "Frameworks", href: "/frameworks", icon: <Terminal className="w-4 h-4" />, featured: true },
{ text: "Comparar Séries", brief: "Comparar", href: "/comparacao", icon: <Scale className="w-4 h-4" />, featured: true },
{ text: "Catálogo de Placas", brief: "Catálogo", href: "/catalogo", icon: <ShoppingBag className="w-4 h-4" /> },
];
function isActive(href) {
if (href === "/") {
return pathname === "/";
}
const featuredItems = menuItems.filter(item => item.featured);
return String(pathname).includes(href) && href !== "/";
}
function isActive(href) {
if (href === "/") {
return pathname === "/";
}
return String(pathname).includes(href) && href !== "/";
}
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">
<div className="flex justify-between max-w-7xl mx-auto items-center gap-8">
<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">
ESPDocs
</h1>
</Link>
// 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]);
{/* Menu Desktop - visível em telas médias e grandes */}
<nav className="hidden lg:flex items-center gap-2 flex-1 justify-end" aria-label="Menu principal">
{featuredItems.map((item) => (
<Link
key={item.text}
href={item.href}
className={`group flex items-center gap-2 px-4 py-2.5 rounded-xl font-medium transition-all duration-300 ${
isActive(item.href)
? "bg-linear-to-r from-blue-600 to-purple-600 text-white shadow-lg shadow-purple-200"
: "text-gray-700 hover:bg-linear-to-r hover:from-blue-50 hover:to-purple-50 hover:shadow-md hover:-translate-y-0.5"
}`}
aria-label={item.brief || item.text}
aria-current={isActive(item.href) ? "page" : undefined}
>
<span className={`transition-transform duration-300 ${
isActive(item.href) ? "scale-110" : "group-hover:scale-110"
}`}>
{item.icon}
</span>
<span className="text-sm whitespace-nowrap">{item.brief || item.text}</span>
</Link>
))}
return (
<header className="bg-white/80 dark:bg-slate-950/80 backdrop-blur-md py-4 px-4 md:px-8 shadow-xs border-b border-slate-100 dark:border-slate-800/60 sticky top-0 z-50 transition-colors duration-300">
<div className="flex justify-between max-w-7xl mx-auto items-center gap-8">
{/* LOGO */}
<Link href="/" className="group shrink-0">
<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
</h1>
</Link>
{/* Botão Menu Completo Desktop */}
<Button
variant="outlined"
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"
aria-label="Abrir menu completo"
aria-expanded={drawerOpen}
aria-controls="mobile-menu-drawer"
>
<MenuIcon />
</Button>
</nav>
{/* Menu Desktop */}
<nav className="hidden lg:flex items-center gap-2 flex-1 justify-end" aria-label="Menu principal">
{featuredItems.map((item) => (
<Link
key={item.text}
href={item.href}
className={`group flex items-center gap-2 px-4 py-2 rounded-xl font-medium transition-all duration-300 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-600 dark:text-slate-300 hover:bg-slate-50 dark:hover:bg-slate-900/60 hover:text-purple-600 dark:hover:text-purple-400"
}`}
aria-label={item.brief || item.text}
aria-current={isActive(item.href) ? "page" : undefined}
>
<span className={`transition-transform duration-300 ${
isActive(item.href) ? "scale-110" : "group-hover:scale-110"
}`}>
{item.icon}
</span>
<span className="whitespace-nowrap">{item.brief || item.text}</span>
</Link>
))}
{/* Botão Menu Mobile - visível apenas em telas pequenas */}
<div className="lg:hidden">
<Button
variant="outlined"
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"
aria-label="Abrir menu de navegação"
aria-expanded={drawerOpen}
aria-controls="mobile-menu-drawer"
>
<MenuIcon />
</Button>
</div>
</div>
{/* Divider */}
<div className="h-5 w-px bg-slate-200 dark:bg-slate-800 mx-2" />
{/* Drawer Mobile */}
<Drawer
anchor="right"
open={drawerOpen}
onClose={() => setDrawerOpen(false)}
id="mobile-menu-drawer"
>
<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>
{/* Theme Switcher Button */}
<ThemeToggle />
<List component="nav" aria-label="Menu de navegação">
{menuItems.map((item, index) => (
<ListItem
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); }}
href={item.href}
aria-label={item.text}
aria-current={isActive(item.href) ? "page" : undefined}
>
<span className={`transition-transform duration-300 ${isActive(item.href) ? "scale-110" : "group-hover:scale-110"}`}>
{item.icon}
</span>
<span className="ml-3 font-medium">{item.text}</span>
{isActive(item.href) && (
<span className="ml-auto">
<span className="inline-block w-2 h-2 bg-white rounded-full animate-pulse"></span>
</span>
)}
</Link>
</div>
</ListItem>
))}
</List>
{/* Full Menu Burger Trigger */}
<button
onClick={() => setDrawerOpen(true)}
className="p-2.5 rounded-xl border border-slate-200 dark:border-slate-800 text-slate-600 dark:text-slate-300 hover:bg-slate-50 dark:hover:bg-slate-900/80 hover:border-purple-500 dark:hover:border-purple-400 hover:text-purple-600 dark:hover:text-purple-400 transition-all cursor-pointer"
aria-label="Abrir menu completo"
aria-expanded={drawerOpen}
>
<Menu className="w-5 h-5" />
</button>
</nav>
<div className="mt-8 p-4 bg-white/50 backdrop-blur-sm rounded-xl border border-gray-200">
<p className="text-xs text-gray-600 text-center">
Documentação não oficial
</p>
</div>
</div>
</Drawer>
{/* Botões Mobile (Theme + Burger) */}
<div className="flex lg:hidden items-center gap-2">
<ThemeToggle />
<button
onClick={() => setDrawerOpen(true)}
className="p-2.5 rounded-xl border border-slate-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-expanded={drawerOpen}
>
<Menu className="w-5 h-5" />
</button>
</div>
</div>
<style jsx global>{`
@keyframes slideIn {
from {
opacity: 0;
transform: translateX(20px);
}
to {
opacity: 1;
transform: translateX(0);
}
}
`}</style>
</header>
);
{/* Render side-panel and overlay at body level via React Portal to prevent layout leakage */}
{mounted && createPortal(
<>
{drawerOpen && (
<div
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={`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 ${
drawerOpen ? "translate-x-0" : "translate-x-full"
}`}
>
{/* Drawer Header */}
<div className="flex items-center justify-between p-6 border-b border-slate-100 dark:border-slate-800/80">
<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}
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}
>
<span className="shrink-0">{item.icon}</span>
<span className="grow text-left">{item.text}</span>
{isActive(item.href) && (
<span className="w-1.5 h-1.5 bg-white rounded-full animate-ping" />
)}
</Link>
</li>
))}
</ul>
</nav>
{/* Drawer Footer Info */}
<div className="p-6 border-t border-slate-100 dark:border-slate-800/80 bg-slate-50/50 dark:bg-slate-900/50">
<p className="text-center text-xs text-slate-400 dark:text-slate-500 font-medium">
ESPDocs • Plataforma Não Oficial
</p>
</div>
</div>
</>
, document.body
)}
</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>
);
}
+71 -69
View File
@@ -1,80 +1,82 @@
'use client';
import { useState } from 'react';
import { ChevronDown } from 'lucide-react';
export default function SeriesTabMenu({ tabs, color }) {
const availableTabs = tabs.filter(tab => tab.available);
const [activeTab, setActiveTab] = useState(availableTabs[0]?.id || 'specs');
const availableTabs = tabs.filter(tab => tab.available);
const [activeTab, setActiveTab] = useState(availableTabs[0]?.id || 'specs');
const activeTabContent = availableTabs.find(tab => tab.id === activeTab)?.content;
const activeTabContent = availableTabs.find(tab => tab.id === activeTab)?.content;
return (
<div className="mb-8">
<div className="hidden md:flex bg-white rounded-2xl shadow-lg py-2 px-4 mb-6 border-2 border-gray-100 gap-4">
{availableTabs.map((tab) => (
<button
key={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 ${
activeTab === tab.id
? 'text-white shadow-lg transform scale-105'
: 'text-gray-600 hover:bg-gray-50'
}`}
style={
activeTab === tab.id
? { backgroundColor: color }
: {}
}
>
<span className="hidden lg:inline">{tab.label}</span>
</button>
))}
</div>
return (
<div className="mb-12">
{/* Desktop Tabs */}
<div className="hidden md:flex bg-white/70 dark:bg-slate-900/40 backdrop-blur-xl rounded-2xl shadow-xl py-2 px-3 mb-6 border border-slate-200/60 dark:border-slate-800/80 gap-3">
{availableTabs.map((tab) => {
const isActive = activeTab === tab.id;
return (
<button
key={tab.id}
onClick={() => setActiveTab(tab.id)}
className={`flex-1 flex items-center justify-center gap-2 py-3 px-5 rounded-xl font-bold text-xs uppercase tracking-wider transition-all duration-300 cursor-pointer active:scale-98 ${
isActive
? 'text-white shadow-lg scale-[1.02]'
: '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={
isActive
? { backgroundColor: color, boxShadow: `0 10px 20px -10px ${color}` }
: {}
}
>
<span>{tab.label}</span>
</button>
);
})}
</div>
<div className="md:hidden mb-6">
<div className="bg-white rounded-2xl shadow-lg border-2 border-gray-100 overflow-hidden">
<button
onClick={() => {
const dropdown = document.getElementById('mobile-tab-dropdown');
dropdown.classList.toggle('hidden');
}}
className="w-full flex items-center justify-between py-4 px-6 font-semibold text-white"
style={{ backgroundColor: color }}
>
<div className="flex items-center gap-3">
<span className="text-xl">
{availableTabs.find(tab => tab.id === activeTab)?.icon}
</span>
<span>
{availableTabs.find(tab => tab.id === activeTab)?.label}
</span>
</div>
<span className="text-xl">▼</span>
</button>
{/* Mobile Dropdown Tab Selector */}
<div className="md:hidden mb-6">
<div className="bg-white/70 dark:bg-slate-900/40 backdrop-blur-xl rounded-2xl shadow-lg border border-slate-200/60 dark:border-slate-800/80 overflow-hidden">
<button
onClick={() => {
const dropdown = document.getElementById('mobile-tab-dropdown');
dropdown.classList.toggle('hidden');
}}
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 }}
>
<div className="flex items-center gap-3">
<span>
{availableTabs.find(tab => tab.id === activeTab)?.label}
</span>
</div>
<ChevronDown className="w-4 h-4 shrink-0 transition-transform duration-300" />
</button>
<div id="mobile-tab-dropdown" className="hidden">
{availableTabs.map((tab) => (
tab.id !== activeTab && (
<button
key={tab.id}
onClick={() => {
setActiveTab(tab.id);
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"
>
<span className="text-xl">{tab.icon}</span>
<span className="font-medium">{tab.label}</span>
</button>
)
))}
</div>
</div>
</div>
<div id="mobile-tab-dropdown" className="hidden">
{availableTabs.map((tab) => (
tab.id !== activeTab && (
<button
key={tab.id}
onClick={() => {
setActiveTab(tab.id);
document.getElementById('mobile-tab-dropdown').classList.add('hidden');
}}
className="w-full flex items-center gap-3 py-4 px-6 text-slate-700 dark:text-slate-300 hover:bg-slate-150/40 dark:hover:bg-slate-800/30 transition-colors border-t border-slate-200/60 dark:border-slate-800/60 font-semibold text-xs uppercase tracking-wider text-left"
>
<span>{tab.label}</span>
</button>
)
))}
</div>
</div>
</div>
<div className="animate-fadeIn">
{activeTabContent}
</div>
</div>
);
<div className="animate-fadeIn">
{activeTabContent}
</div>
</div>
);
}
+64 -25
View File
@@ -12,8 +12,11 @@
"@emotion/styled": "^11.14.1",
"@mui/icons-material": "^7.3.4",
"@mui/material": "^7.3.4",
"framer-motion": "^12.40.0",
"googleapis": "^165.0.0",
"lucide-react": "^1.16.0",
"next": "^15.5.9",
"next-themes": "^0.4.6",
"react": "19.1.0",
"react-dom": "19.1.0"
},
@@ -1877,16 +1880,6 @@
"integrity": "sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==",
"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": {
"version": "4.4.12",
"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_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": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
@@ -5453,6 +5473,15 @@
"integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==",
"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": {
"version": "0.30.21",
"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz",
@@ -5528,6 +5557,21 @@
"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": {
"version": "2.1.3",
"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": {
"version": "8.4.31",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz",
@@ -7098,21 +7152,6 @@
"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": {
"version": "1.1.0",
"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",
"@mui/icons-material": "^7.3.4",
"@mui/material": "^7.3.4",
"framer-motion": "^12.40.0",
"googleapis": "^165.0.0",
"lucide-react": "^1.16.0",
"next": "^15.5.9",
"next-themes": "^0.4.6",
"react": "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",
"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": {
"nome_completo": "ESP32-C3",
"icone": "💎",
@@ -174,20 +254,22 @@
"ESP32-C5": {
"nome_completo": "ESP32-C5",
"icone": "📶",
"descricao": "O primeiro com suporte a Wi-Fi 6 dual-band e Bluetooth 5.3, equipado com um processador RISC-V",
"arquitetura": "RISC-V single-core de 32 bits",
"nucleos": "1 (Single-core)",
"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 (HP) + RISC-V single-core (LP)",
"nucleos": "1 (Single-core HP) + 1 (Single-core LP)",
"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",
"bluetooth": "Bluetooth 5.3 LE",
"zigbee_thread": "IEEE 802.15.4",
"memoria_sram": "560 KB",
"bluetooth": "Bluetooth 6.0 LE",
"zigbee_thread": "IEEE 802.15.4 (Zigbee 3.0, Thread 1.4)",
"matter": "Sim",
"memoria_sram": "384 KB",
"memoria_sram_rtc": "16 KB",
"memoria_rom": "320 KB",
"flash_externa": "Até 16 MB via QSPI",
"psram_externa": "Não",
"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",
"touch": "Não",
"uart": "3",
@@ -213,20 +295,21 @@
"ESP32-C6": {
"nome_completo": "ESP32-C6",
"icone": "🌐",
"descricao": "Recursos de segurança avançada, Wi-Fi 6, Bluetooth, Zigbee e Thread com suporte a Matter",
"arquitetura": "RISC-V single-core de 32 bits",
"nucleos": "1 (Single-core)",
"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 (HP) + RISC-V single-core (LP)",
"nucleos": "1 (Single-core HP) + 1 (Single-core LP)",
"frequencia": "Até 160 MHz",
"coprocessador_ulp": "Sim (RISC-V, LP CPU de 20 MHz)",
"wifi": "Wi-Fi 6 (802.11ax) 2.4 GHz",
"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",
"memoria_sram": "512 KB",
"memoria_sram_rtc": "16 KB",
"memoria_rom": "320 KB",
"flash_externa": "Até 16 MB via QSPI",
"psram_externa": "Não",
"gpio": "30 ou 22",
"gpio": "30",
"adc": "1x SAR ADC de 12 bits, até 7 canais",
"dac": "Não",
"touch": "Não",
@@ -236,7 +319,7 @@
"i2s": "1",
"pwm": "LED PWM (até 6 canais), Motor PWM",
"usb": "1 (USB Serial/JTAG)",
"sdio": "1 (SD/SDIO/MMC Host)",
"sdio": "1 (SDIO 2.0 Slave)",
"sensor_temperatura": "1 (interno)",
"consumo_energia": {
"active_wifi6": "Varia conforme perfil TX/RX",
@@ -251,11 +334,52 @@
"placa": "/placas/esp32-c6.png",
"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": {
"nome_completo": "ESP32-P4",
"icone": "⚡",
"descricao": "Alto desempenho com processador RISC-V dual-core a 400MHz e aceleradores de IA",
"arquitetura": "RISC-V de 32 bits dual-core de alto desempenho + single-core de baixo consumo",
"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 (HP) + single-core de baixo consumo (LP)",
"nucleos": "2 (Dual-core HP) + 1 (Single-core LP)",
"frequencia": "Até 400 MHz",
"aceleradores_ia": "Sim (Vector Instructions, aceleradores de IA)",
@@ -263,9 +387,10 @@
"bluetooth": "Não",
"ethernet": "Sim (Ethernet MAC integrado)",
"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",
"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",
"adc": "1x SAR ADC de 12 bits, até 8 canais",
"dac": "Não",
@@ -298,13 +423,13 @@
"ESP32-H2": {
"nome_completo": "ESP32-H2",
"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",
"nucleos": "1 (Single-core)",
"frequencia": "Até 96 MHz",
"wifi": "Não",
"bluetooth": "Bluetooth 5.2 LE",
"zigbee_thread": "IEEE 802.15.4 (Zigbee, Thread)",
"bluetooth": "Bluetooth 5.3 LE",
"zigbee_thread": "IEEE 802.15.4 (Zigbee 3.0, Thread 1.3)",
"matter": "Sim",
"memoria_sram": "320 KB",
"memoria_sram_rtc": "16 KB",