feat: control room hero, SEO/AEO optimization and design system fixes
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).
This commit is contained in:
1 parent
0920370f30
commit
b791b7de9e
94 files changed
+2621
-2889
No files matched your search
@@ -0,0 +1,193 @@
|
||||
"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>
|
||||
);
|
||||
}
|
||||
Reference in new issue
Block a user