Redesign das páginas internas no novo design system (AAA) (#2)
* feat(design): redesign das páginas internas no novo design system Padroniza todas as páginas e componentes internos nos tokens e primitivos de app/globals.css, com contraste AAA (7:1) nos dois temas e alvos de toque de 44px. - Páginas reescritas: glossário, séries, série, seletor, comparador, diagnóstico, catálogo, component registry, frameworks, framework, sobre e a nova 404. - Componentes: PageHero e SectionHead novos; BoardCard, CodeExample, PinRestrictions, SeriesTabMenu e ConnectionsDiagram refeitos, com abas e diálogo acessíveis. - Remove emojis, cores avulsas por série, caixa alta com tracking, texto menor que 12px e os pares slate-*/dark:*. - Comparador: "mostrar só as diferenças" e leitura de ?chips=. - Corrige overflow em 320/390px e ajusta accent/copper do tema claro para fechar 7:1. - scripts/auditar-temas.mjs passa a travar paleta crua, dark:, texto pequeno, emoji e contraste dos tokens. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016W1xmizFcmXLrmabSvfSuc * ci: roda lint, auditorias de design e build em cada PR Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016W1xmizFcmXLrmabSvfSuc --------- Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
4f27f5858b
commit
bc9ae5c036
34 files changed
+4911
-6874
No files matched your search
+46
-78
@@ -1,99 +1,67 @@
|
||||
'use client';
|
||||
"use client";
|
||||
|
||||
import { Award, CheckCircle, HelpCircle, ExternalLink } from 'lucide-react';
|
||||
import { ArrowUpRight, Award, Check, CircleHelp } from "lucide-react";
|
||||
|
||||
export default function BoardCard({ board }) {
|
||||
const mainFields = ['Nome', 'Link', 'Imagem', 'Loja Oficial'];
|
||||
|
||||
const technicalFields = Object.keys(board).filter(
|
||||
key => !mainFields.includes(key) && board[key]
|
||||
);
|
||||
const mainFields = ["Nome", "Link", "Imagem", "Loja Oficial"];
|
||||
|
||||
const hasFeature = (value) => {
|
||||
const yesValues = ['sim', 'opcional'];
|
||||
return yesValues.includes(value?.toString().toLowerCase().trim());
|
||||
};
|
||||
const technicalFields = Object.keys(board).filter((key) => !mainFields.includes(key) && board[key]);
|
||||
|
||||
// Filtrar apenas características que têm "Sim" ou "Opcional"
|
||||
const availableFeatures = technicalFields.filter(field => hasFeature(board[field]));
|
||||
const optionalFeatures = technicalFields.filter(field =>
|
||||
board[field]?.toString().toLowerCase().trim() === 'opcional'
|
||||
);
|
||||
const valor = (campo) => board[campo]?.toString().toLowerCase().trim();
|
||||
const recursos = technicalFields.filter((campo) => ["sim", "opcional"].includes(valor(campo)));
|
||||
const lojaOficial = valor("Loja Oficial") === "sim";
|
||||
|
||||
return (
|
||||
<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 */}
|
||||
<article className="card card-interactive flex h-full flex-col overflow-hidden">
|
||||
{board.Imagem && (
|
||||
<div
|
||||
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"
|
||||
<div
|
||||
className="flex shrink-0 justify-center border-b border-line bg-surface-2 p-4 [&_img]:h-44 [&_img]:w-auto [&_img]:max-w-full [&_img]:object-contain"
|
||||
dangerouslySetInnerHTML={{ __html: board.Imagem }}
|
||||
/>
|
||||
)}
|
||||
|
||||
<div className="p-6 flex flex-col grow justify-between">
|
||||
<div>
|
||||
{/* Loja Oficial Badge */}
|
||||
{board['Loja Oficial'] && board['Loja Oficial'].toLowerCase() === 'sim' && (
|
||||
<div className="mb-2">
|
||||
<span className="inline-flex items-center gap-1 bg-blue-500/10 text-blue-600 dark:text-blue-400 border border-blue-500/20 rounded-full px-2.5 py-0.5 text-[9px] font-bold uppercase tracking-wider">
|
||||
<Award className="w-3.5 h-3.5 shrink-0" />
|
||||
<span>Loja Oficial</span>
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
<div className="flex grow flex-col p-5">
|
||||
{lojaOficial && (
|
||||
<span className="chip chip-accent mb-3 w-fit">
|
||||
<Award className="h-3.5 w-3.5" aria-hidden="true" />
|
||||
Loja oficial
|
||||
</span>
|
||||
)}
|
||||
|
||||
{/* Nome */}
|
||||
<h3 className="text-base font-display font-extrabold text-slate-850 dark:text-slate-100 mb-4 line-clamp-2 leading-snug group-hover:text-purple-650 dark:group-hover:text-purple-400 transition-colors">
|
||||
{board.Nome || 'Placa Genérica'}
|
||||
</h3>
|
||||
<h3 className="line-clamp-2 font-display text-lg font-semibold leading-snug tracking-[-0.02em] text-ink">
|
||||
{board.Nome || "Placa genérica"}
|
||||
</h3>
|
||||
|
||||
{/* Características Técnicas */}
|
||||
{availableFeatures.length > 0 && (
|
||||
<div className="mb-6">
|
||||
<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>
|
||||
|
||||
<div className="flex flex-wrap gap-1.5">
|
||||
{availableFeatures.map((field) => {
|
||||
const isOptional = optionalFeatures.includes(field);
|
||||
return (
|
||||
<div
|
||||
key={field}
|
||||
className={`text-[9px] px-2 py-0.5 rounded-lg font-bold flex items-center gap-1 ${
|
||||
isOptional
|
||||
? 'bg-amber-500/10 dark:bg-amber-500/15 border border-amber-500/20 text-amber-600 dark:text-amber-400'
|
||||
: 'bg-emerald-500/10 dark:bg-emerald-500/15 border border-emerald-500/20 text-emerald-600 dark:text-emerald-400'
|
||||
}`}
|
||||
>
|
||||
{isOptional ? (
|
||||
<HelpCircle className="w-2.5 h-2.5 shrink-0" />
|
||||
) : (
|
||||
<CheckCircle className="w-2.5 h-2.5 shrink-0" />
|
||||
)}
|
||||
<span>{field}</span>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
{recursos.length > 0 && (
|
||||
<div className="mt-4">
|
||||
<p className="text-xs font-medium text-muted">Recursos embarcados</p>
|
||||
<ul className="mt-2 flex flex-wrap gap-1.5">
|
||||
{recursos.map((campo) => {
|
||||
const opcional = valor(campo) === "opcional";
|
||||
return (
|
||||
<li key={campo} className={`chip ${opcional ? "text-gold" : "text-ink-soft"}`}>
|
||||
{opcional ? (
|
||||
<CircleHelp className="h-3 w-3 text-gold" aria-hidden="true" />
|
||||
) : (
|
||||
<Check className="h-3 w-3 text-ok" aria-hidden="true" />
|
||||
)}
|
||||
{campo}
|
||||
{opcional && <span className="sr-only"> (opcional)</span>}
|
||||
</li>
|
||||
);
|
||||
})}
|
||||
</ul>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Link de Compra */}
|
||||
{board.Link && (
|
||||
<a
|
||||
href={board.Link}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
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" />
|
||||
<a href={board.Link} target="_blank" rel="noopener noreferrer" className="btn btn-primary mt-6 w-full">
|
||||
Ver no AliExpress
|
||||
<ArrowUpRight className="h-4 w-4" aria-hidden="true" />
|
||||
<span className="sr-only"> (abre em nova aba)</span>
|
||||
</a>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</article>
|
||||
);
|
||||
}
|
||||
Reference in new issue
Block a user