Files
portifolio-evandrodevbr/app/servicos/_components/ServiceDiagram.tsx
T
Evandro Fonseca Junior fc44a69fb3 feat(servicos): ofertas no topo de /servicos, capa por assunto e diagramas
/servicos abre com as 3 ofertas e depois outros serviços; H1 de criação de sites e de engenharia com o nome da oferta; capa de cada serviço pelo assunto (SEV fora do próprio case cai de ~11 para 2 imagens); diagrama de fluxo em SVG nos serviços sem case; cases com o que o cliente passou a poder fazer e contato logo depois de Como ficou; FAQ num componente só; lastmod por rota; ProfessionalService com areaServed.
2026-10-14 19:14:46 -03:00

164 lines
4.6 KiB
TypeScript

import type { ServiceDiagramKind } from "@/lib/site-content";
/**
* Esquema de fluxo no topo do serviço que não tem case com captura gravada, no lugar da coluna vazia ao lado do título.
* SVG em linha, só com as cores do site (papel e branco sobre o fundo escuro, sem o verde-limão, que é do contato),
* sem dado real e sem logo de cliente. Para quem lê a tela, o esquema é uma imagem com o fluxo descrito em `aria-label`.
*/
const WIDTH = 480;
const HEIGHT = 404;
const ARROW_HEAD = 12;
interface DiagramNode {
label: string;
x: number;
y: number;
width: number;
height: number;
}
interface DiagramArrow {
x: number;
from: number;
to: number;
/** Ponta nas duas extremidades: a informação anda nos dois sentidos. */
both?: boolean;
}
interface DiagramShape {
description: string;
nodes: DiagramNode[];
arrows: DiagramArrow[];
}
/** Quatro etapas em coluna, de cima para baixo, ligadas por setas. */
function column(labels: [string, string, string, string]) {
const nodes = labels.map((label, index) => ({
label,
x: 90,
y: 24 + index * 100,
width: 300,
height: 56,
}));
const arrows = nodes.slice(1).map((node, index) => ({
x: WIDTH / 2,
from: nodes[index].y + nodes[index].height,
to: node.y,
}));
return { nodes, arrows };
}
const diagrams: Record<ServiceDiagramKind, DiagramShape> = {
iot: {
description:
"Esquema do caminho dos dados: o dispositivo envia as leituras ao gateway, o gateway repassa à API e a API alimenta o painel.",
...column(["Dispositivo ou sensor", "Gateway", "API", "Painel"]),
},
arquitetura: {
description:
"Esquema das camadas de um sistema: a interface chama a API, a API aciona os serviços e os serviços guardam e consultam o banco de dados.",
...column(["Interface", "API", "Serviços", "Banco de dados"]),
},
desktop: {
description:
"Esquema de um aplicativo desktop: o aplicativo no computador troca informações, nos dois sentidos, com os arquivos e com os sistemas locais.",
nodes: [
{
label: "Aplicativo no computador",
x: 90,
y: 60,
width: 300,
height: 64,
},
{ label: "Arquivos", x: 30, y: 280, width: 190, height: 64 },
{ label: "Sistemas locais", x: 260, y: 280, width: 190, height: 64 },
],
arrows: [
{ x: 125, from: 124, to: 280, both: true },
{ x: 355, from: 124, to: 280, both: true },
],
},
};
function Arrow({ x, from, to, both = false }: DiagramArrow) {
const lineStart = both ? from + ARROW_HEAD : from;
return (
<g>
<line
x1={x}
y1={lineStart}
x2={x}
y2={to - ARROW_HEAD}
stroke="var(--paper)"
strokeOpacity={0.7}
strokeWidth={2}
/>
<polygon
points={`${x - 7},${to - ARROW_HEAD} ${x + 7},${to - ARROW_HEAD} ${x},${to}`}
fill="var(--paper)"
fillOpacity={0.7}
/>
{both ? (
<polygon
points={`${x - 7},${from + ARROW_HEAD} ${x + 7},${from + ARROW_HEAD} ${x},${from}`}
fill="var(--paper)"
fillOpacity={0.7}
/>
) : null}
</g>
);
}
export function ServiceDiagram({ kind }: { kind: ServiceDiagramKind }) {
const { description, nodes, arrows } = diagrams[kind];
return (
<figure className="mx-auto w-full min-w-0 max-w-[520px]">
<svg
role="img"
aria-label={description}
viewBox={`0 0 ${WIDTH} ${HEIGHT}`}
className="block h-auto w-full border border-white/15 bg-[#0a0a0a]"
data-diagram={kind}
>
{arrows.map((arrow) => (
<Arrow key={`${arrow.x}-${arrow.from}`} {...arrow} />
))}
{nodes.map(({ label, x, y, width, height }) => (
<g key={label}>
<rect
x={x}
y={y}
width={width}
height={height}
fill="var(--white)"
fillOpacity={0.06}
stroke="var(--paper)"
strokeOpacity={0.35}
strokeWidth={1.5}
/>
<text
x={x + width / 2}
y={y + height / 2}
textAnchor="middle"
dominantBaseline="central"
fill="var(--paper)"
style={{
fontFamily: "var(--font-display)",
fontSize: 20,
fontWeight: 600,
}}
>
{label}
</text>
</g>
))}
</svg>
<figcaption className="mt-sp-3 text-[15px] leading-6 text-white/70">
Esquema ilustrativo, sem dados de clientes.
</figcaption>
</figure>
);
}