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:
1 parent
d4872fcf07
commit
8a52e79d21
23 files changed
+1253
-282
No files matched your search
@@ -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" />
|
||||
|
||||
@@ -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>
|
||||
))}
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
@@ -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>
|
||||
|
||||
Reference in new issue
Block a user