Hero: replace particle field with cobe globe (Capricorn view, multi-axis drag, reduced-motion safe), telemetry HUD with pointer parallax, particle text build effect, Space Grotesk display font. SEO: short titles with template, PT-BR accents, twitter cards on dynamic cases, escaped JSON-LD via lib/jsonld.ts, CSP report-only, 25 service FAQs answer-first. Design: fix TW4 layered reset killing spacing utilities, literal @theme tokens, mobile hero overflow, header overlap, empty case cards. Remove framer-motion (unified on motion/react), header version badge, phone number from hero CTA. Production build verified (24 static pages).
194 lines
5.7 KiB
TypeScript
194 lines
5.7 KiB
TypeScript
"use client";
|
|
|
|
import { motion, useMotionValue, useSpring, useTransform } from "motion/react";
|
|
import { useEffect, useRef, useState } from "react";
|
|
|
|
import { cn } from "@/lib/utils";
|
|
|
|
interface TelemetryMetric {
|
|
label: string;
|
|
value: number;
|
|
unit: string;
|
|
precision?: number;
|
|
min?: number;
|
|
max?: number;
|
|
}
|
|
|
|
const METRICS_CONFIG: TelemetryMetric[] = [
|
|
{
|
|
label: "UPTIME",
|
|
value: 99.97,
|
|
unit: "%",
|
|
precision: 2,
|
|
min: 99.9,
|
|
max: 100,
|
|
},
|
|
{
|
|
label: "INTEGRAÇÕES",
|
|
value: 12,
|
|
unit: " ativas",
|
|
precision: 0,
|
|
min: 8,
|
|
max: 16,
|
|
},
|
|
{
|
|
label: "BUILDS",
|
|
value: 247,
|
|
unit: " /dia",
|
|
precision: 0,
|
|
min: 200,
|
|
max: 300,
|
|
},
|
|
{ label: "LATÊNCIA", value: 12, unit: " ms", precision: 0, min: 8, max: 28 },
|
|
];
|
|
|
|
const UPDATE_INTERVAL = 3000;
|
|
|
|
const springConfigs = [
|
|
{ stiffness: 300, damping: 30, mass: 0.8 },
|
|
{ stiffness: 200, damping: 25, mass: 1 },
|
|
{ stiffness: 150, damping: 20, mass: 1.2 },
|
|
{ stiffness: 150, damping: 20, mass: 1.2 },
|
|
];
|
|
|
|
export function TelemetryStrip({
|
|
className,
|
|
reducedMotion = false,
|
|
}: {
|
|
className?: string;
|
|
reducedMotion?: boolean;
|
|
}) {
|
|
const [_metrics, setMetrics] = useState<TelemetryMetric[]>(METRICS_CONFIG);
|
|
const pointerX = useMotionValue(0);
|
|
const pointerY = useMotionValue(0);
|
|
const isHovering = useRef(false);
|
|
const intervalRef = useRef<ReturnType<typeof setInterval> | null>(null);
|
|
|
|
const metricValue0 = useSpring(
|
|
useMotionValue(METRICS_CONFIG[0].value),
|
|
springConfigs[0],
|
|
);
|
|
const metricValue1 = useSpring(
|
|
useMotionValue(METRICS_CONFIG[1].value),
|
|
springConfigs[1],
|
|
);
|
|
const metricValue2 = useSpring(
|
|
useMotionValue(METRICS_CONFIG[2].value),
|
|
springConfigs[2],
|
|
);
|
|
const metricValue3 = useSpring(
|
|
useMotionValue(METRICS_CONFIG[3].value),
|
|
springConfigs[3],
|
|
);
|
|
const metricValues = [metricValue0, metricValue1, metricValue2, metricValue3];
|
|
|
|
// Parallax sutil em px, sinal alternado por métrica — assinatura "reage ao ponteiro"
|
|
const pointerShiftX = useTransform(pointerX, [-1, 1], [-8, 8]);
|
|
const pointerShiftY = useTransform(pointerY, [-1, 1], [-5, 5]);
|
|
const pointerShiftXNeg = useTransform(pointerShiftX, (v) => -v);
|
|
const pointerShiftYNeg = useTransform(pointerShiftY, (v) => -v);
|
|
|
|
const generateVariation = useRef((metric: TelemetryMetric): number => {
|
|
const range =
|
|
(metric.max ?? metric.value * 1.1) - (metric.min ?? metric.value * 0.9);
|
|
const variation = (Math.random() - 0.5) * range * 0.15;
|
|
const newValue = metric.value + variation;
|
|
return Math.max(
|
|
metric.min ?? -Infinity,
|
|
Math.min(metric.max ?? Infinity, newValue),
|
|
);
|
|
});
|
|
|
|
const updateMetrics = useRef(() => {
|
|
setMetrics((prev) =>
|
|
prev.map((metric) => ({
|
|
...metric,
|
|
value: generateVariation.current(metric),
|
|
})),
|
|
);
|
|
});
|
|
|
|
useEffect(() => {
|
|
if (reducedMotion) return;
|
|
|
|
intervalRef.current = setInterval(updateMetrics.current, UPDATE_INTERVAL);
|
|
return () => {
|
|
if (intervalRef.current) clearInterval(intervalRef.current);
|
|
};
|
|
}, [reducedMotion]);
|
|
|
|
useEffect(() => {
|
|
const handlePointerMove = (e: MouseEvent) => {
|
|
const x = (e.clientX / window.innerWidth) * 2 - 1;
|
|
const y = -(e.clientY / window.innerHeight) * 2 + 1;
|
|
pointerX.set(x);
|
|
pointerY.set(y);
|
|
};
|
|
|
|
window.addEventListener("pointermove", handlePointerMove);
|
|
return () => window.removeEventListener("pointermove", handlePointerMove);
|
|
}, [pointerX, pointerY]);
|
|
|
|
return (
|
|
<section
|
|
className={cn(
|
|
"cinematic-glass border-[rgba(204,255,0,0.18)] bg-[rgba(10,10,10,0.82)] backdrop-blur-[24px] rounded-[var(--r-lg)] p-sp-4 lg:p-sp-5 grid grid-cols-2 gap-sp-4 lg:gap-sp-6",
|
|
className,
|
|
)}
|
|
aria-label="Telemetria operacional em tempo real"
|
|
onPointerEnter={() => {
|
|
isHovering.current = true;
|
|
}}
|
|
onPointerLeave={() => {
|
|
isHovering.current = false;
|
|
}}
|
|
>
|
|
{METRICS_CONFIG.map((metric, index) => (
|
|
<motion.div
|
|
key={metric.label}
|
|
className="relative flex flex-col items-start gap-1"
|
|
style={
|
|
reducedMotion
|
|
? undefined
|
|
: {
|
|
x: index % 2 === 0 ? pointerShiftX : pointerShiftXNeg,
|
|
y: index % 2 === 0 ? pointerShiftY : pointerShiftYNeg,
|
|
}
|
|
}
|
|
>
|
|
<div className="font-mono text-[11px] uppercase tracking-[0.14em] text-[rgba(244,242,237,.7)]">
|
|
{metric.label}
|
|
</div>
|
|
<div
|
|
className="whitespace-nowrap font-display font-extrabold text-[clamp(1.35rem,2.6vw,1.875rem)] leading-none text-[var(--paper)] tabular-nums"
|
|
aria-hidden="true"
|
|
>
|
|
{reducedMotion
|
|
? metric.value.toFixed(metric.precision ?? 0)
|
|
: metricValues[index].get().toFixed(metric.precision ?? 0)}
|
|
<span className="font-mono text-[11px] font-normal text-[rgba(244,242,237,.58)]">
|
|
{metric.unit}
|
|
</span>
|
|
</div>
|
|
<div
|
|
className="h-px w-full bg-[linear-gradient(90deg,var(--slash),transparent)] opacity-30"
|
|
style={{
|
|
transformOrigin: "left center",
|
|
transform: reducedMotion
|
|
? "scaleX(1)"
|
|
: `scaleX(${Math.max(0.15, Math.min(1, metric.value / (metric.max ?? 100)))})`,
|
|
}}
|
|
aria-hidden="true"
|
|
/>
|
|
</motion.div>
|
|
))}
|
|
<span className="sr-only">
|
|
Painel de telemetria operacional: uptime 99,97 por cento, doze
|
|
integrações ativas, duzentos e quarenta e sete builds por dia, latência
|
|
doze milissegundos. Valores decorativos simulando uma sala de controle
|
|
real.
|
|
</span>
|
|
</section>
|
|
);
|
|
}
|