* 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>
68 lines
2.3 KiB
React
68 lines
2.3 KiB
React
"use client";
|
|
|
|
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 valor = (campo) => board[campo]?.toString().toLowerCase().trim();
|
|
const recursos = technicalFields.filter((campo) => ["sim", "opcional"].includes(valor(campo)));
|
|
const lojaOficial = valor("Loja Oficial") === "sim";
|
|
|
|
return (
|
|
<article className="card card-interactive flex h-full flex-col overflow-hidden">
|
|
{board.Imagem && (
|
|
<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="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>
|
|
)}
|
|
|
|
<h3 className="line-clamp-2 font-display text-lg font-semibold leading-snug tracking-[-0.02em] text-ink">
|
|
{board.Nome || "Placa genérica"}
|
|
</h3>
|
|
|
|
{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>
|
|
)}
|
|
|
|
{board.Link && (
|
|
<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>
|
|
</article>
|
|
);
|
|
}
|