feat: add theme consistency guidelines and pin restrictions component

- Introduced DESIGN.md to outline rules for maintaining consistent light and dark themes, including color scales, symmetry rules, and contrast requirements.
- Added PinRestrictions component to display pin usage restrictions based on series data, ensuring accurate representation of pin functionalities.
- Implemented auditing script (auditar-temas.mjs) to check for theme consistency issues, including nonexistent tones, inverted pairs, low contrast, and inline fixed colors.
This commit is contained in:
Lucas Rayan committed 2026-08-20 05:37:56 -03:00
1 parent d4872fcf07
commit 8a52e79d21
23 files changed
+1253 -282

No files matched your search

+4 -4
View File
@@ -21,11 +21,11 @@ export default function BoardCard({ board }) {
);
return (
<div className="bg-white/80 dark:bg-slate-900/40 backdrop-blur-xl border border-slate-200/60 dark:border-slate-800/80 rounded-3xl shadow-lg hover:shadow-2xl hover:-translate-y-1.5 transition-all duration-300 overflow-hidden group flex flex-col h-full animate-fadeIn select-none">
<div className="bg-white dark:bg-slate-900/40 backdrop-blur-xl border border-slate-300 dark:border-slate-800/80 rounded-3xl shadow-lg hover:shadow-2xl hover:-translate-y-1.5 transition-all duration-300 overflow-hidden group flex flex-col h-full animate-fadeIn select-none">
{/* Image Container with premium glass background */}
{board.Imagem && (
<div
className="mx-auto w-full p-4 flex justify-center bg-white dark:bg-white border-b border-slate-100 dark:border-slate-850/40 shrink-0"
className="mx-auto w-full p-4 flex justify-center bg-white dark:bg-white border-b border-slate-200 dark:border-slate-850/40 shrink-0"
dangerouslySetInnerHTML={{ __html: board.Imagem }}
/>
)}
@@ -50,7 +50,7 @@ export default function BoardCard({ board }) {
{/* Características Técnicas */}
{availableFeatures.length > 0 && (
<div className="mb-6">
<h4 className="text-[9px] font-bold text-slate-400 dark:text-slate-500 uppercase tracking-widest flex items-center gap-1.5 mb-2.5">
<h4 className="text-[9px] font-bold text-slate-500 dark:text-slate-400 uppercase tracking-widest flex items-center gap-1.5 mb-2.5">
<CheckCircle className="w-3.5 h-3.5 text-emerald-500" />
<span>Recursos Embarcados</span>
</h4>
@@ -87,7 +87,7 @@ export default function BoardCard({ board }) {
href={board.Link}
target="_blank"
rel="noopener noreferrer"
className="block w-full bg-slate-900 dark:bg-slate-100 text-white dark:text-slate-950 text-center px-4 py-3 rounded-xl font-bold text-xs uppercase tracking-wider hover:opacity-90 active:scale-98 transition-all shadow-md mt-auto flex items-center justify-center gap-1.5"
className="block w-full bg-gradient-to-r from-purple-600 to-indigo-600 text-white text-center px-4 py-3 rounded-xl font-bold text-xs uppercase tracking-wider hover:opacity-90 active:scale-98 transition-all shadow-md mt-auto flex items-center justify-center gap-1.5"
>
<span>Adquirir no AliExpress</span>
<ExternalLink className="w-3.5 h-3.5" />
+6 -6
View File
@@ -70,7 +70,7 @@ export default function CodeExample({ framework }) {
}
return (
<div className="bg-white/80 dark:bg-slate-900/40 backdrop-blur-xl rounded-3xl p-6 md:p-8 border border-slate-200/60 dark:border-slate-800/80 shadow-2xl mb-8 animate-fadeIn">
<div className="bg-white dark:bg-slate-900/40 backdrop-blur-xl rounded-3xl p-6 md:p-8 border border-slate-300 dark:border-slate-800/80 shadow-2xl mb-8 animate-fadeIn">
<div className="md:flex items-center justify-between gap-6 mb-6">
<div className="select-none">
<h3 className="text-xl font-display font-extrabold text-slate-850 dark:text-slate-100 mb-1 flex items-center gap-2">
@@ -94,7 +94,7 @@ export default function CodeExample({ framework }) {
<button
onClick={handleCopy}
className="inline-flex items-center gap-1.5 bg-slate-50 dark:bg-slate-850 hover:bg-slate-100 dark:hover:bg-slate-800 text-slate-700 dark:text-slate-250 px-3.5 py-2 rounded-xl font-bold text-xs shadow-xs border border-slate-200/60 dark:border-slate-800/80 cursor-pointer transition active:scale-95"
className="inline-flex items-center gap-1.5 bg-slate-50 dark:bg-slate-850 hover:bg-slate-100 dark:hover:bg-slate-800 text-slate-700 dark:text-slate-250 px-3.5 py-2 rounded-xl font-bold text-xs shadow-xs border border-slate-300 dark:border-slate-800/80 cursor-pointer transition active:scale-95"
title="Copiar código para a área de transferência"
>
{copied ? (
@@ -129,16 +129,16 @@ export default function CodeExample({ framework }) {
</div>
</div>
<div className="rounded-2xl overflow-hidden border border-slate-200/60 dark:border-slate-800/80 shadow-md">
<div className="rounded-2xl overflow-hidden border border-slate-300 dark:border-slate-800/80 shadow-md">
<div className="select-none">
<div className="flex items-center justify-between bg-slate-100/80 dark:bg-slate-950/70 border-b border-slate-200/60 dark:border-slate-850/60 px-4 py-3.5">
<div className="flex items-center justify-between bg-slate-100/80 dark:bg-slate-950/70 border-b border-slate-300 dark:border-slate-850/60 px-4 py-3.5">
<div className="text-xs font-bold text-slate-750 dark:text-slate-250 flex items-center gap-1.5">
<span className="w-2.5 h-2.5 rounded-full bg-red-500/80" />
<span className="w-2.5 h-2.5 rounded-full bg-yellow-500/80" />
<span className="w-2.5 h-2.5 rounded-full bg-green-500/80" />
<span className="ml-2 font-mono text-[10px] text-slate-500 dark:text-slate-400">{framework?.nome} example</span>
</div>
<div className="text-[9px] font-bold text-slate-400 dark:text-slate-500 uppercase tracking-widest">
<div className="text-[9px] font-bold text-slate-500 dark:text-slate-400 uppercase tracking-widest">
{loading ? 'Buscando exemplo...' : (framework?.exemplo ? 'Local Source' : 'External GitHub')}
</div>
</div>
@@ -151,7 +151,7 @@ export default function CodeExample({ framework }) {
>
{lines.map((line, idx) => (
<div key={idx} className="flex">
<span className="text-slate-600 dark:text-slate-650 pr-4 text-right w-8 select-none border-r border-slate-900/60 mr-4 font-semibold">{idx + 1}</span>
<span className="text-slate-500 pr-4 text-right w-8 select-none border-r border-slate-900/60 mr-4 font-semibold">{idx + 1}</span>
<code className="whitespace-pre flex-1 text-left">{line === '' ? '\u00A0' : line}</code>
</div>
))}
+28 -28
View File
@@ -130,15 +130,15 @@ export default function ConnectionsDiagram({ connections, serie }) {
if (!connections || connections.length === 0) {
return (
<div className="flex flex-col items-center justify-center py-16 px-4">
<div className="w-20 h-20 mb-6 rounded-2xl bg-slate-100 dark:bg-slate-900 border border-slate-200 dark:border-slate-800 flex items-center justify-center shadow-lg">
<Sliders className="w-10 h-10 text-slate-400 dark:text-slate-600" />
<div className="w-20 h-20 mb-6 rounded-2xl bg-slate-100 dark:bg-slate-900 border border-slate-300 dark:border-slate-800 flex items-center justify-center shadow-lg">
<Sliders className="w-10 h-10 text-slate-500 dark:text-slate-400" />
</div>
<p className="text-lg font-bold text-slate-800 dark:text-slate-200">
Diagrama de conexões indisponível
</p>
<p className="text-xs text-slate-400 dark:text-slate-500 mt-2">
<p className="text-xs text-slate-500 dark:text-slate-400 mt-2">
Este modelo ainda não possui um mapa detalhado de conexões e barramentos.
</p>
</div>
@@ -150,7 +150,7 @@ export default function ConnectionsDiagram({ connections, serie }) {
{/* Header info */}
<div className="space-y-3">
<h2 className="text-xl font-bold text-slate-850 dark:text-slate-100 mb-4 pb-3 border-b border-slate-200/60 dark:border-slate-800/65 flex items-center gap-3 select-none">
<h2 className="text-xl font-bold text-slate-850 dark:text-slate-100 mb-4 pb-3 border-b border-slate-300 dark:border-slate-800/65 flex items-center gap-3 select-none">
<Sliders className="w-5 h-5" style={{ color: serie.color }} />
<span>Diagrama de Conexões Interativo</span>
</h2>
@@ -160,16 +160,16 @@ export default function ConnectionsDiagram({ connections, serie }) {
</div>
{/* Search Panel */}
<div className="bg-white/60 dark:bg-slate-900/30 backdrop-blur-xl rounded-2xl border border-slate-200/60 dark:border-slate-800/80 p-5 space-y-4 shadow-xl">
<div className="bg-white/60 dark:bg-slate-900/30 backdrop-blur-xl rounded-2xl border border-slate-300 dark:border-slate-800/80 p-5 space-y-4 shadow-xl">
<div className="flex flex-col sm:flex-row gap-4">
<div className="flex-1 relative">
<Search className="absolute left-4 top-1/2 -translate-y-1/2 w-4.5 h-4.5 text-slate-400 dark:text-slate-500" />
<Search className="absolute left-4 top-1/2 -translate-y-1/2 w-4.5 h-4.5 text-slate-500 dark:text-slate-400" />
<input
type="text"
placeholder="Buscar por nome ou número da conexão (Ex: GPIO1, TXD, XTAL)..."
value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)}
className="w-full pl-12 pr-10 py-3 rounded-xl border border-slate-200/60 dark:border-slate-800 bg-white dark:bg-slate-950/40 focus:border-purple-500/80 focus:ring-4 focus:ring-purple-500/10 text-slate-800 dark:text-slate-200 transition-all outline-none text-sm font-semibold"
className="w-full pl-12 pr-10 py-3 rounded-xl border border-slate-300 dark:border-slate-800 bg-white dark:bg-slate-950/40 focus:border-purple-500/80 focus:ring-4 focus:ring-purple-500/10 text-slate-800 dark:text-slate-200 transition-all outline-none text-sm font-semibold"
aria-label="Search connections"
/>
{searchQuery && (
@@ -217,14 +217,14 @@ export default function ConnectionsDiagram({ connections, serie }) {
</div>
{/* Filters Panel */}
<div className="bg-white/60 dark:bg-slate-900/30 backdrop-blur-xl rounded-2xl border border-slate-200/60 dark:border-slate-800/80 p-5 shadow-xl">
<div className="bg-white/60 dark:bg-slate-900/30 backdrop-blur-xl rounded-2xl border border-slate-300 dark:border-slate-800/80 p-5 shadow-xl">
<div className="flex items-center gap-3 mb-5 border-b border-slate-150/40 dark:border-slate-850/45 pb-3">
<Filter className="w-4 h-4 text-purple-500" />
<h4 className="text-sm font-bold text-slate-800 dark:text-slate-100 select-none">
Filtre por Função do Pino
</h4>
<span className="ml-auto text-[10px] font-bold text-slate-400 dark:text-slate-500 bg-slate-100 dark:bg-slate-950 px-3 py-1 rounded-full border border-slate-200/30 dark:border-slate-850/30 shadow-xs select-none">
<span className="ml-auto text-[10px] font-bold text-slate-500 dark:text-slate-400 bg-slate-100 dark:bg-slate-950 px-3 py-1 rounded-full border border-slate-300 dark:border-slate-850/30 shadow-xs select-none">
{visibleConnections.length} de {connections.length} ativos
</span>
</div>
@@ -247,7 +247,7 @@ export default function ConnectionsDiagram({ connections, serie }) {
className={`group relative px-4 py-2 rounded-xl text-xs font-extrabold transition-all duration-300 focus:outline-none cursor-pointer active:scale-95 ${
isActive
? "shadow-lg text-white"
: "bg-slate-50/70 dark:bg-slate-950/20 text-slate-650 dark:text-slate-350 hover:bg-slate-100/50 dark:hover:bg-slate-850/30 border border-slate-200/40 dark:border-slate-850/40"
: "bg-slate-50/70 dark:bg-slate-950/20 text-slate-650 dark:text-slate-350 hover:bg-slate-100/50 dark:hover:bg-slate-850/30 border border-slate-300 dark:border-slate-850/40"
}`}
style={{
backgroundColor: isActive ? filter.color : undefined,
@@ -257,7 +257,7 @@ export default function ConnectionsDiagram({ connections, serie }) {
<span className="flex items-center gap-2">
<span>{filter.name}</span>
<span className={`text-[9px] px-1.5 py-0.5 rounded-full shrink-0 font-bold ${
isActive ? "bg-white/20 text-white" : "bg-slate-200/50 dark:bg-slate-800/80 text-slate-555 dark:text-slate-400"
isActive ? "bg-white/20 text-white" : "bg-slate-200/50 dark:bg-slate-800/80 text-slate-600 dark:text-slate-400"
}`}>
{count}
</span>
@@ -269,8 +269,8 @@ export default function ConnectionsDiagram({ connections, serie }) {
{/* Active Categories badges */}
{activeFilter !== "All" && activeCategories.length > 0 && (
<div className="mt-5 pt-5 border-t border-slate-100 dark:border-slate-850/40 select-none">
<p className="text-[10px] font-bold text-slate-400 dark:text-slate-500 mb-3 uppercase tracking-wider">Subcategorias ativadas por esse filtro:</p>
<div className="mt-5 pt-5 border-t border-slate-200 dark:border-slate-850/40 select-none">
<p className="text-[10px] font-bold text-slate-500 dark:text-slate-400 mb-3 uppercase tracking-wider">Subcategorias ativadas por esse filtro:</p>
<div className="flex flex-wrap gap-2">
{activeCategories.map(cat => (
<span
@@ -289,7 +289,7 @@ export default function ConnectionsDiagram({ connections, serie }) {
{/* Diagram Workspace */}
{visibleConnections.length === 0 ? (
<div className="bg-slate-50/50 dark:bg-slate-900/10 backdrop-blur-xl rounded-2xl p-12 border-2 border-dashed border-slate-200 dark:border-slate-850/80 text-center shadow-inner">
<div className="bg-slate-50/50 dark:bg-slate-900/10 backdrop-blur-xl rounded-2xl p-12 border-2 border-dashed border-slate-300 dark:border-slate-850/80 text-center shadow-inner">
<div className="w-16 h-16 mx-auto mb-4 rounded-full bg-slate-100 dark:bg-slate-950 flex items-center justify-center shadow-md">
<Search className="w-6 h-6 text-slate-400" />
</div>
@@ -301,14 +301,14 @@ export default function ConnectionsDiagram({ connections, serie }) {
setActiveFilter("All");
setSearchQuery("");
}}
className="mt-5 px-6 py-2.5 bg-slate-900 dark:bg-slate-100 text-white dark:text-slate-950 rounded-xl hover:opacity-90 transition-all font-bold text-xs uppercase tracking-wider cursor-pointer active:scale-95 shadow-lg"
className="mt-5 px-6 py-2.5 bg-gradient-to-r from-purple-600 to-indigo-600 text-white rounded-xl hover:opacity-90 transition-all font-bold text-xs uppercase tracking-wider cursor-pointer active:scale-95 shadow-lg"
>
Limpar Filtros e Busca
</button>
</div>
) : viewMode === "grid" ? (
/* Grid View Mode: Futuristic Electronic Chip Sockets */
<div className="bg-slate-100/50 dark:bg-slate-950/20 backdrop-blur-xl rounded-3xl p-6 md:p-8 border border-slate-200/50 dark:border-slate-850/60 shadow-inner">
<div className="bg-slate-100/50 dark:bg-slate-950/20 backdrop-blur-xl rounded-3xl p-6 md:p-8 border border-slate-300 dark:border-slate-850/60 shadow-inner">
<div className="grid grid-cols-2 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-5 xl:grid-cols-6 gap-4">
{visibleConnections.map((connection, index) => {
const isHovered = hoveredConnection === index;
@@ -367,7 +367,7 @@ export default function ConnectionsDiagram({ connections, serie }) {
/>
))}
{connection.categorias.length > 5 && (
<span className="text-[9px] text-slate-400 dark:text-slate-500 font-bold ml-0.5 leading-none">
<span className="text-[9px] text-slate-500 dark:text-slate-400 font-bold ml-0.5 leading-none">
+{connection.categorias.length - 5}
</span>
)}
@@ -419,11 +419,11 @@ export default function ConnectionsDiagram({ connections, serie }) {
</div>
) : (
/* List View Mode: Interactive Zebra Table */
<div className="bg-white/80 dark:bg-slate-900/40 backdrop-blur-xl rounded-2xl border border-slate-200/60 dark:border-slate-800/80 overflow-hidden shadow-xl">
<div className="bg-white dark:bg-slate-900/40 backdrop-blur-xl rounded-2xl border border-slate-300 dark:border-slate-800/80 overflow-hidden shadow-xl">
<div className="overflow-x-auto scrollbar-thin">
<table className="w-full text-left">
<thead>
<tr className="bg-slate-100/50 dark:bg-slate-950/50 border-b border-slate-200/60 dark:border-slate-850/60 select-none">
<tr className="bg-slate-100/50 dark:bg-slate-950/50 border-b border-slate-300 dark:border-slate-850/60 select-none">
<th className="px-6 py-4 text-xs font-bold text-slate-500 dark:text-slate-400 uppercase tracking-widest">Pino</th>
<th className="px-6 py-4 text-xs font-bold text-slate-500 dark:text-slate-400 uppercase tracking-widest">Nome do Pad</th>
<th className="px-6 py-4 text-xs font-bold text-slate-500 dark:text-slate-400 uppercase tracking-widest">Categorias</th>
@@ -532,11 +532,11 @@ export default function ConnectionsDiagram({ connections, serie }) {
aria-labelledby="modal-title"
>
<div
className="bg-white/95 dark:bg-slate-950/95 backdrop-blur-xl border border-slate-200/50 dark:border-slate-800/80 rounded-3xl shadow-2xl max-w-2xl w-full max-h-[85vh] overflow-hidden flex flex-col animate-slideUp"
className="bg-white/95 dark:bg-slate-950/95 backdrop-blur-xl border border-slate-300 dark:border-slate-800/80 rounded-3xl shadow-2xl max-w-2xl w-full max-h-[85vh] overflow-hidden flex flex-col animate-slideUp"
onClick={(e) => e.stopPropagation()}
>
{/* Dialog Header */}
<div className="sticky top-0 bg-white/50 dark:bg-slate-950/50 border-b border-slate-200/60 dark:border-slate-850/60 p-6 md:p-8 z-10">
<div className="sticky top-0 bg-white/50 dark:bg-slate-950/50 border-b border-slate-300 dark:border-slate-850/60 p-6 md:p-8 z-10">
<div className="flex items-start justify-between gap-6">
<div className="flex items-center gap-4 flex-1">
<div
@@ -551,7 +551,7 @@ export default function ConnectionsDiagram({ connections, serie }) {
<h3 id="modal-title" className="text-2xl font-display font-extrabold text-slate-850 dark:text-slate-100 mb-1 leading-none tracking-tight">
{selectedConnection.nome}
</h3>
<p className="text-xs text-slate-400 dark:text-slate-500 font-bold uppercase tracking-wider">
<p className="text-xs text-slate-500 dark:text-slate-400 font-bold uppercase tracking-wider">
Pino Físico {selectedConnection.numero}
</p>
</div>
@@ -573,7 +573,7 @@ export default function ConnectionsDiagram({ connections, serie }) {
{/* Categories */}
{selectedConnection.categorias.length > 0 && (
<div>
<h4 className="text-xs font-bold text-slate-400 dark:text-slate-500 uppercase tracking-widest mb-3 flex items-center gap-2 select-none">
<h4 className="text-xs font-bold text-slate-500 dark:text-slate-400 uppercase tracking-widest mb-3 flex items-center gap-2 select-none">
<Tag className="w-4 h-4 text-purple-500" />
<span>Categorias de Multiplexação</span>
</h4>
@@ -595,11 +595,11 @@ export default function ConnectionsDiagram({ connections, serie }) {
{/* Power Source */}
{selectedConnection.alimentacao && (
<div>
<h4 className="text-xs font-bold text-slate-400 dark:text-slate-500 uppercase tracking-widest mb-3 flex items-center gap-2 select-none">
<h4 className="text-xs font-bold text-slate-500 dark:text-slate-400 uppercase tracking-widest mb-3 flex items-center gap-2 select-none">
<Zap className="w-4 h-4 text-red-500" />
<span>Domínio de Alimentação Interna</span>
</h4>
<div className="bg-slate-50 dark:bg-slate-900/30 rounded-2xl p-5 border border-slate-200/50 dark:border-slate-850/50">
<div className="bg-slate-50 dark:bg-slate-900/30 rounded-2xl p-5 border border-slate-300 dark:border-slate-850/50">
<ul className="space-y-2.5">
{selectedConnection.alimentacao.map((source, index) => (
<li key={index} className="flex items-center gap-3 text-xs font-bold text-slate-700 dark:text-slate-300">
@@ -617,12 +617,12 @@ export default function ConnectionsDiagram({ connections, serie }) {
{/* Function Multiplexation */}
<div>
<h4 className="text-xs font-bold text-slate-400 dark:text-slate-500 uppercase tracking-widest mb-3 flex items-center gap-2 select-none">
<h4 className="text-xs font-bold text-slate-500 dark:text-slate-400 uppercase tracking-widest mb-3 flex items-center gap-2 select-none">
<Sliders className="w-4 h-4 text-emerald-500" />
<span>Barramentos de Funções Disponíveis</span>
</h4>
<div className="bg-slate-50 dark:bg-slate-900/30 rounded-2xl p-5 border border-slate-200/50 dark:border-slate-850/50">
<div className="bg-slate-50 dark:bg-slate-900/30 rounded-2xl p-5 border border-slate-300 dark:border-slate-850/50">
<ul className="grid grid-cols-1 sm:grid-cols-2 gap-3">
{selectedConnection.funcoes.map((func, index) => (
<li key={index} className="flex items-center gap-3 text-xs font-bold text-slate-700 dark:text-slate-300">
@@ -663,7 +663,7 @@ export default function ConnectionsDiagram({ connections, serie }) {
</div>
{/* Dialog Footer */}
<div className="sticky bottom-0 bg-white/50 dark:bg-slate-950/50 border-t border-slate-200/60 dark:border-slate-850/60 p-5">
<div className="sticky bottom-0 bg-white/50 dark:bg-slate-950/50 border-t border-slate-300 dark:border-slate-850/60 p-5">
<button
onClick={() => setSelectedConnection(null)}
className="w-full py-3.5 rounded-xl font-bold text-white text-xs uppercase tracking-wider shadow-lg hover:opacity-90 active:scale-98 transition-all cursor-pointer"
+1 -1
View File
@@ -133,7 +133,7 @@ export default function Footer() {
</div>
{/* Divisor & Isenção */}
<div className="mt-12 pt-8 border-t border-slate-205/60 dark:border-slate-800/80 flex flex-col md:flex-row justify-between items-center gap-4 text-xs text-slate-400 dark:text-slate-500">
<div className="mt-12 pt-8 border-t border-slate-300 dark:border-slate-800/80 flex flex-col md:flex-row justify-between items-center gap-4 text-xs text-slate-500 dark:text-slate-400">
<p className="text-center md:text-left leading-relaxed">
© {year} ESPDocs. Esta plataforma independente de código aberto não possui afiliação com a Espressif Systems.
</p>
+7 -7
View File
@@ -62,7 +62,7 @@ export default function Header() {
}, [drawerOpen]);
return (
<header className="bg-white/80 dark:bg-slate-950/80 backdrop-blur-md py-4 px-4 md:px-8 shadow-xs border-b border-slate-100 dark:border-slate-800/60 sticky top-0 z-50 transition-colors duration-300">
<header className="bg-white dark:bg-slate-950/80 backdrop-blur-md py-4 px-4 md:px-8 shadow-xs border-b border-slate-200 dark:border-slate-800/60 sticky top-0 z-50 transition-colors duration-300">
<div className="flex justify-between max-w-7xl mx-auto items-center gap-8">
{/* LOGO */}
<Link href="/" className="group shrink-0">
@@ -103,7 +103,7 @@ export default function Header() {
{/* Full Menu Burger Trigger */}
<button
onClick={() => setDrawerOpen(true)}
className="p-2.5 rounded-xl border border-slate-200 dark:border-slate-800 text-slate-600 dark:text-slate-300 hover:bg-slate-50 dark:hover:bg-slate-900/80 hover:border-purple-500 dark:hover:border-purple-400 hover:text-purple-600 dark:hover:text-purple-400 transition-all cursor-pointer"
className="p-2.5 rounded-xl border border-slate-300 dark:border-slate-800 text-slate-600 dark:text-slate-300 hover:bg-slate-50 dark:hover:bg-slate-900/80 hover:border-purple-500 dark:hover:border-purple-400 hover:text-purple-600 dark:hover:text-purple-400 transition-all cursor-pointer"
aria-label="Abrir menu completo"
aria-expanded={drawerOpen}
>
@@ -116,7 +116,7 @@ export default function Header() {
<ThemeToggle />
<button
onClick={() => setDrawerOpen(true)}
className="p-2.5 rounded-xl border border-slate-200 dark:border-slate-800 text-slate-600 dark:text-slate-300 hover:bg-slate-50 dark:hover:bg-slate-900/80 hover:border-purple-500 dark:hover:border-purple-400 transition-all cursor-pointer"
className="p-2.5 rounded-xl border border-slate-300 dark:border-slate-800 text-slate-600 dark:text-slate-300 hover:bg-slate-50 dark:hover:bg-slate-900/80 hover:border-purple-500 dark:hover:border-purple-400 transition-all cursor-pointer"
aria-label="Abrir menu de navegação"
aria-expanded={drawerOpen}
>
@@ -136,12 +136,12 @@ export default function Header() {
)}
<div
className={`fixed top-0 right-0 h-screen w-80 max-w-[85vw] bg-white dark:bg-slate-900 shadow-2xl z-[101] transform transition-transform duration-300 ease-in-out flex flex-col border-l border-slate-200 dark:border-slate-800 ${
className={`fixed top-0 right-0 h-screen w-80 max-w-[85vw] bg-white dark:bg-slate-900 shadow-2xl z-[101] transform transition-transform duration-300 ease-in-out flex flex-col border-l border-slate-300 dark:border-slate-800 ${
drawerOpen ? "translate-x-0" : "translate-x-full"
}`}
>
{/* Drawer Header */}
<div className="flex items-center justify-between p-6 border-b border-slate-100 dark:border-slate-800/80">
<div className="flex items-center justify-between p-6 border-b border-slate-200 dark:border-slate-800/80">
<h2 className="text-xl font-display font-bold bg-gradient-to-r from-blue-500 to-purple-500 bg-clip-text text-transparent">
Menu Navegação
</h2>
@@ -181,8 +181,8 @@ export default function Header() {
</nav>
{/* Drawer Footer Info */}
<div className="p-6 border-t border-slate-100 dark:border-slate-800/80 bg-slate-50/50 dark:bg-slate-900/50">
<p className="text-center text-xs text-slate-400 dark:text-slate-500 font-medium">
<div className="p-6 border-t border-slate-200 dark:border-slate-800/80 bg-slate-50/50 dark:bg-slate-900/50">
<p className="text-center text-xs text-slate-500 dark:text-slate-400 font-medium">
ESPDocs • Plataforma Não Oficial
</p>
</div>
+178
View File
@@ -0,0 +1,178 @@
import Link from "next/link";
import {
AlertTriangle,
ArrowDownToLine,
HardDrive,
Power,
Bug,
Usb,
Zap,
ArrowRight,
} from "lucide-react";
/**
* Restrições de pinos por série.
*
* Os grupos são DERIVADOS de public/conexoes/<serie>.json — o mesmo arquivo que
* alimenta o diagrama de conexões. Nada aqui é escrito à mão, então a seção e o
* diagrama nunca divergem: corrigir um pino no JSON corrige os dois.
*/
const GRUPOS = [
{
id: "entrada",
titulo: "Somente entrada",
icone: ArrowDownToLine,
cor: "text-sky-500",
explicacao:
"Aceitam apenas leitura. Não podem ser configurados como saída e não possuem resistores de pull-up ou pull-down internos — se precisar de um, coloque no circuito.",
combina: (avisos) => /apenas de entrada|input only/.test(avisos),
},
{
id: "flash",
titulo: "Ligados à memória flash/PSRAM",
icone: HardDrive,
cor: "text-rose-500",
explicacao:
"Já estão conectados à memória de onde o firmware é lido. Usá-los como GPIO comum normalmente trava o chip ou corrompe a execução. Em módulos com flash interna eles nem chegam ao encapsulamento.",
combina: (avisos) =>
/(flash|psram)/.test(avisos) &&
/(dedicad|utilizado pela mem|não deve ser utilizado|não é recomendado)/.test(avisos),
},
{
id: "strapping",
titulo: "Strapping (modo de boot)",
icone: Power,
cor: "text-amber-500",
explicacao:
"O chip lê o nível destes pinos no instante do reset para decidir como inicializar. Dá para usá-los depois do boot, mas o circuito externo não pode forçar o nível errado durante o reset — senão a placa não inicia ou entra em modo de gravação sozinha.",
combina: (avisos, cats) =>
cats.includes("Strapping") ||
/strapping|modo de boot|modo de inicialização/.test(avisos),
},
{
id: "debug",
titulo: "Gravação e depuração",
icone: Bug,
cor: "text-violet-500",
explicacao:
"São a UART de gravação e as linhas de JTAG. Continuam utilizáveis como GPIO, mas ao reaproveitá-los você perde o caminho de gravar e depurar a placa sem desconectar o que estiver ali.",
combina: (avisos, cats) => /debugging/.test(avisos) || cats.includes("JTAG"),
},
{
id: "usb",
titulo: "USB nativo",
icone: Usb,
cor: "text-teal-500",
explicacao:
"Saem de fábrica ligados ao USB Serial/JTAG interno. Para usá-los como GPIO é preciso desativar essa função — o que também remove a gravação por USB direto.",
combina: (avisos) => /usb serial\/jtag|usb_d[+-]/.test(avisos),
},
{
id: "eletrico",
titulo: "Não podem ficar flutuando",
icone: Zap,
cor: "text-orange-500",
explicacao:
"Precisam de nível definido por hardware: pull-up externo ou circuito RC de atraso. Deixá-los soltos causa reset aleatório ou inicialização instável.",
combina: (avisos) => /flutuando/.test(avisos),
},
];
/** Nome do pino + o GPIO correspondente, quando o nome é uma função (ex.: SENSOR_VP). */
function rotuloDoPino(pino) {
const gpio = (pino.funcoes || []).find((f) => /^GPIO\d+$/.test(f));
return gpio && gpio !== pino.nome ? `${pino.nome} · ${gpio}` : pino.nome;
}
function derivarGrupos(conexoes) {
return GRUPOS.map((grupo) => {
const vistos = new Map();
for (const pino of conexoes) {
if (!["io", "analog", "dedicated"].includes(pino.tipo)) continue;
const avisos = (pino.avisos || []).join(" ").toLowerCase();
const cats = pino.categorias || [];
if (grupo.combina(avisos, cats)) {
const rotulo = rotuloDoPino(pino);
if (!vistos.has(rotulo)) vistos.set(rotulo, pino);
}
}
return { ...grupo, pinos: [...vistos.keys()] };
}).filter((g) => g.pinos.length > 0);
}
export default function PinRestrictions({ conexoes, serieKey, cor }) {
if (!conexoes?.length) return null;
const grupos = derivarGrupos(conexoes);
if (!grupos.length) return null;
const totalPinos = new Set(grupos.flatMap((g) => g.pinos)).size;
return (
<div className="bg-white dark:bg-slate-900/40 backdrop-blur-xl rounded-3xl p-6 md:p-8 border border-slate-300 dark:border-slate-800/80 shadow-2xl mb-12">
<h2 className="text-xl font-bold text-slate-850 dark:text-slate-100 mb-3 pb-3 border-b border-slate-200 dark:border-slate-800/65 flex items-center gap-3">
<AlertTriangle className="w-5 h-5" style={{ color: cor }} />
<span>Pinos com Restrição de Uso</span>
</h2>
<p className="text-sm text-slate-650 dark:text-slate-350 leading-relaxed mb-8">
Nem todo pino do {serieKey} está livre para uso geral. {totalPinos} deles têm alguma
condição — já estão ocupados por memória, são lidos durante o boot ou só funcionam
como entrada. Vale conferir antes de fechar o esquemático.
</p>
<div className="grid md:grid-cols-2 gap-5">
{grupos.map(({ id, titulo, icone: Icone, cor: corIcone, explicacao, pinos }) => (
<div
key={id}
className="bg-slate-50 dark:bg-slate-950/40 rounded-2xl p-5 border border-slate-200 dark:border-slate-800/60"
>
<div className="flex items-center gap-2.5 mb-3">
<Icone className={`w-4 h-4 shrink-0 ${corIcone}`} />
<h3 className="text-sm font-bold text-slate-800 dark:text-slate-200">{titulo}</h3>
<span className="ml-auto text-[10px] font-bold text-slate-500 dark:text-slate-400 tabular">
{pinos.length} {pinos.length === 1 ? "pino" : "pinos"}
</span>
</div>
<div className="flex flex-wrap gap-1.5 mb-3.5">
{pinos.map((p) => (
<span
key={p}
className="px-2 py-1 rounded-lg text-[10px] font-bold font-mono"
style={{
backgroundColor: `${cor}18`,
color: cor,
border: `1px solid ${cor}30`,
}}
>
{p}
</span>
))}
</div>
<p className="text-xs text-slate-500 dark:text-slate-400 leading-relaxed">
{explicacao}
</p>
</div>
))}
</div>
<div className="mt-7 pt-6 border-t border-slate-200 dark:border-slate-800/65 flex flex-col sm:flex-row sm:items-center justify-between gap-4">
<p className="text-xs text-slate-500 dark:text-slate-400 leading-relaxed max-w-xl">
Estes grupos vêm dos mesmos dados do diagrama de conexões. Lá você vê pino a pino
todas as funções, o domínio de alimentação e os avisos completos de cada um.
</p>
<Link
href="#connections"
className="inline-flex items-center justify-center gap-2 shrink-0 px-5 py-3 rounded-xl font-bold text-xs uppercase tracking-wider text-white transition-opacity hover:opacity-90"
style={{ backgroundColor: cor }}
>
<span>Abrir diagrama de pinos</span>
<ArrowRight className="w-4 h-4" />
</Link>
</div>
</div>
);
}
+38 -7
View File
@@ -1,24 +1,55 @@
'use client';
import { useState } from 'react';
import { useState, useEffect } from 'react';
import { ChevronDown } from 'lucide-react';
export default function SeriesTabMenu({ tabs, color }) {
const availableTabs = tabs.filter(tab => tab.available);
const [activeTab, setActiveTab] = useState(availableTabs[0]?.id || 'specs');
// Troca manual limpa o hash. Sem isso, depois de abrir o diagrama pelo link e
// mudar de aba na mão, o hash continuaria em #connections e um novo clique no
// link não dispararia hashchange — o botão pareceria quebrado.
const trocarAba = (id) => {
setActiveTab(id);
if (window.location.hash) {
history.replaceState(null, '', window.location.pathname + window.location.search);
}
};
// Abas endereçáveis por hash: #connections abre o diagrama de pinos direto.
// É o que permite a seção de restrições apontar para cá.
useEffect(() => {
const abrirPeloHash = () => {
const alvo = window.location.hash.replace('#', '');
if (alvo && availableTabs.some(t => t.id === alvo)) {
setActiveTab(alvo);
// Nenhum elemento tem id="connections", então o navegador não rola
// sozinho: levamos até as abas na mão.
const semAnimacao = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
document.getElementById('diagrama-abas')?.scrollIntoView({
behavior: semAnimacao ? 'auto' : 'smooth',
block: 'start',
});
}
};
abrirPeloHash();
window.addEventListener('hashchange', abrirPeloHash);
return () => window.removeEventListener('hashchange', abrirPeloHash);
}, [availableTabs]);
const activeTabContent = availableTabs.find(tab => tab.id === activeTab)?.content;
return (
<div className="mb-12">
<div className="mb-12" id="diagrama-abas">
{/* Desktop Tabs */}
<div className="hidden md:flex bg-white/70 dark:bg-slate-900/40 backdrop-blur-xl rounded-2xl shadow-xl py-2 px-3 mb-6 border border-slate-200/60 dark:border-slate-800/80 gap-3">
<div className="hidden md:flex bg-white dark:bg-slate-900/40 backdrop-blur-xl rounded-2xl shadow-xl py-2 px-3 mb-6 border border-slate-300 dark:border-slate-800/80 gap-3">
{availableTabs.map((tab) => {
const isActive = activeTab === tab.id;
return (
<button
key={tab.id}
onClick={() => setActiveTab(tab.id)}
onClick={() => trocarAba(tab.id)}
className={`flex-1 flex items-center justify-center gap-2 py-3 px-5 rounded-xl font-bold text-xs uppercase tracking-wider transition-all duration-300 cursor-pointer active:scale-98 ${
isActive
? 'text-white shadow-lg scale-[1.02]'
@@ -38,7 +69,7 @@ export default function SeriesTabMenu({ tabs, color }) {
{/* Mobile Dropdown Tab Selector */}
<div className="md:hidden mb-6">
<div className="bg-white/70 dark:bg-slate-900/40 backdrop-blur-xl rounded-2xl shadow-lg border border-slate-200/60 dark:border-slate-800/80 overflow-hidden">
<div className="bg-white dark:bg-slate-900/40 backdrop-blur-xl rounded-2xl shadow-lg border border-slate-300 dark:border-slate-800/80 overflow-hidden">
<button
onClick={() => {
const dropdown = document.getElementById('mobile-tab-dropdown');
@@ -61,10 +92,10 @@ export default function SeriesTabMenu({ tabs, color }) {
<button
key={tab.id}
onClick={() => {
setActiveTab(tab.id);
trocarAba(tab.id);
document.getElementById('mobile-tab-dropdown').classList.add('hidden');
}}
className="w-full flex items-center gap-3 py-4 px-6 text-slate-700 dark:text-slate-300 hover:bg-slate-150/40 dark:hover:bg-slate-800/30 transition-colors border-t border-slate-200/60 dark:border-slate-800/60 font-semibold text-xs uppercase tracking-wider text-left"
className="w-full flex items-center gap-3 py-4 px-6 text-slate-700 dark:text-slate-300 hover:bg-slate-150/40 dark:hover:bg-slate-800/30 transition-colors border-t border-slate-300 dark:border-slate-800/60 font-semibold text-xs uppercase tracking-wider text-left"
>
<span>{tab.label}</span>
</button>