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.
This commit is contained in:
1 parent
e63f3c2ebd
commit
fc44a69fb3
41 files changed
+2486
-550
No files matched your search
@@ -0,0 +1,163 @@
|
||||
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>
|
||||
);
|
||||
}
|
||||
Reference in new issue
Block a user