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
+6551 -3891

No files matched your search

+346 -337
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');
if (!response.ok) {
throw new Error('Erro ao carregar dados das 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');
}
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);
}
};
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);
}
};
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());
}
});
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));
options[column] = {
values: values,
isBinary: isBinaryFilter
};
});
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();
// 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
};
});
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();
// 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);
});
}
});
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';
}
return activeFilters[column].includes(boardValue);
});
}
});
setFilteredBoards(filtered);
};
setFilteredBoards(filtered);
};
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];
}
}
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
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>
<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>
<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-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">
{/* 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>
{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>
{/* Separador */}
<div className="border-t border-gray-200 my-2"></div>
{/* 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>
{/* 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;
const isActive = activeFilters[column]?.length > 0;
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;
const isActive = activeFilters[column]?.length > 0;
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>
{/* 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>
<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>
{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>
{/* 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>
<Footer />
</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>
// 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);
}
`;
<Footer />
if (typeof document !== 'undefined') {
const styleSheet = document.createElement('style');
styleSheet.textContent = styles;
document.head.appendChild(styleSheet);
<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">
{/* 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>
{/* 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>
{/* 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>
<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>
<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>
<Footer />
</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>
<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>
<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>
<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>
<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="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>
{/* 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>
<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>
{framework.exemplos && (
<CodeExample framework={framework} />
)}
{/* Coding examples container (renders only if framework has examples defined) */}
{framework.exemplos && (
<div className="mt-12">
<CodeExample framework={framework} />
</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>
)}
{/* 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 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>
{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>
);
<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,
}));
}
+220 -183
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>
<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>
<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>
<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-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-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">
<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>
<h2 className="text-2xl font-bold text-gray-800 mb-1 group-hover:text-purple-600 transition-colors">
{framework.nome}
</h2>
<p className="text-xs text-gray-500 mb-3 font-medium">
{framework.nome_completo}
</p>
<p className="text-sm text-gray-600 line-clamp-3">
{framework.descricao}
</p>
</div>
{/* 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 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-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-[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-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>
<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="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>
<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>
<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>
{/* 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>
<Footer />
</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>
);
+209 -133
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>
<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>
<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>
<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-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-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>
<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>
{/* 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) => (
<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>
);
}
+884 -776
View File
File diff suppressed because it is too large. Load diff
+457 -382
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'
};
consumoEnergia.push({
label: modoLabels[modo] || modo.replace(/_/g, ' ').replace(/\b\w/g, l => l.toUpperCase()),
value: consumo,
icon: "🔋"
});
});
}
// 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: <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>
<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>
<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>
<main className="px-6 pt-16 pb-24 max-w-7xl mx-auto">
{/* 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>
{/* 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>
{/* 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>
<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>
{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>
<p className="text-lg text-gray-700 mt-6 leading-relaxed">
{serie.descricao}
</p>
</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>
{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>
<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="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>
<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>
<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>
{/* 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/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>
<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>
<SeriesTabMenu tabs={tabs} color={serie.cor} />
{/* DevBoard and Connections Tabs */}
<SeriesTabMenu tabs={tabs} color={serie.cor} />
<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>
{/* 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 }} />} />
{/* 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>
<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>
<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>
)}
<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>
{/* 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>
{/* 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="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}
<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>
<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>
{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>
{/* 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" />
)}
<Footer />
</div>
);
<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,
}));
}
+205 -142
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>
<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>
<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>
<main id="conteudo" className="px-6 pt-16 pb-24 max-w-7xl mx-auto">
{/* 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>
<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>
<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="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 }}>
<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>
<h2 className="text-2xl font-bold text-gray-800 mb-2 group-hover:text-purple-600 transition-colors">
{key}
</h2>
<p className="text-sm text-gray-600 line-clamp-2">
{serie.descricao}
</p>
</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>
{/* 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-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>
<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-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-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-sm">
<span className="text-gray-600">📱 Bluetooth:</span>
<span className="font-semibold text-gray-800">
{serie.bluetooth === "Não" ? "❌" : "✅"}
</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-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-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-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-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>
<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>
<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>
<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>
<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>
{/* 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">
<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>
{/* 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>
<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>
<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>
<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>
<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>
<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>
<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>
<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>
<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>
<p className="text-gray-700 mb-4">
Ajude a melhorar o ESPDocs com exemplos, correções e traduções.
</p>
{/* 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="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>
<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="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>
<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>
<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>
<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>
<Footer />
</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>
);
}