feat: add LighthouseMetrics component to display quality and performance scores on the homepage.
This commit is contained in:
1 parent
d00258dcd1
commit
9d779390e5
2 files changed
+111
No files matched your search
@@ -0,0 +1,109 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import { motion, useReducedMotion } from "framer-motion";
|
||||||
|
import { Section } from "./Section";
|
||||||
|
|
||||||
|
const METRICS = [
|
||||||
|
{
|
||||||
|
score: 100,
|
||||||
|
label: "Performance",
|
||||||
|
description:
|
||||||
|
"Cada milissegundo conta. Tempos de carregamento instantâneos retêm mais usuários, aumentam a conversão e garantem que a interface pareça nativa.",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
score: 100,
|
||||||
|
label: "Acessibilidade",
|
||||||
|
description:
|
||||||
|
"Acesso universal. Contraste perfeito, navegação por teclado e semântica adequada garantindo o uso por todos os perfis e mitigando riscos.",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
score: 100,
|
||||||
|
label: "Best Practices",
|
||||||
|
description:
|
||||||
|
"Código seguro e moderno. Uso das APIs mais recentes, prevenção inteligente contra vulnerabilidades comuns e front-end limpo.",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
score: 100,
|
||||||
|
label: "SEO",
|
||||||
|
description:
|
||||||
|
"Rankeamento orgânico superior. Estrutura impecável para indexação precisa pelos motores de busca e otimização para crawlers sociais.",
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
export default function LighthouseMetrics() {
|
||||||
|
const shouldReduceMotion = useReducedMotion();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Section
|
||||||
|
id="lighthouse"
|
||||||
|
eyebrow="Quality & Performance"
|
||||||
|
title="Engenharia de ponta a ponta: 100/100 não é vaidade."
|
||||||
|
description="Sistemas não podem apenas parecer bons. Eles precisam ser consistentes, rápidos, acessíveis, seguros e fáceis de processar. Entregar métricas máximas significa que a fundação técnica do projeto está preparada para escalar sem fricção."
|
||||||
|
dark
|
||||||
|
disableChildrenAnimation={false}
|
||||||
|
>
|
||||||
|
<div className="mt-sp-4 grid grid-cols-1 gap-sp-4 md:grid-cols-2 lg:grid-cols-4">
|
||||||
|
{METRICS.map((metric, i) => (
|
||||||
|
<motion.article
|
||||||
|
key={metric.label}
|
||||||
|
className="ds-card flex flex-col items-center gap-sp-6 text-center lg:items-start lg:text-left"
|
||||||
|
initial={shouldReduceMotion ? { opacity: 0 } : { opacity: 0, y: 20 }}
|
||||||
|
whileInView={
|
||||||
|
shouldReduceMotion ? { opacity: 1 } : { opacity: 1, y: 0 }
|
||||||
|
}
|
||||||
|
viewport={{ once: true, amount: 0.2 }}
|
||||||
|
transition={{ duration: 0.6, delay: i * 0.1 }}
|
||||||
|
>
|
||||||
|
{/* The Ring Container */}
|
||||||
|
<div className="relative flex h-[100px] w-[100px] shrink-0 items-center justify-center rounded-full bg-[rgba(0,0,0,0.3)] shadow-inner">
|
||||||
|
<svg
|
||||||
|
className="absolute inset-0 h-full w-full rotate-[-90deg] drop-shadow-[0_0_12px_rgba(12,206,107,0.35)]"
|
||||||
|
viewBox="0 0 36 36"
|
||||||
|
aria-hidden="true"
|
||||||
|
>
|
||||||
|
{/* Background Track */}
|
||||||
|
<path
|
||||||
|
className="text-[rgba(255,255,255,0.1)]"
|
||||||
|
d="M18 2.0845 a 15.9155 15.9155 0 0 1 0 31.831 a 15.9155 15.9155 0 0 1 0 -31.831"
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
strokeWidth="2.5"
|
||||||
|
/>
|
||||||
|
{/* Animated Value Track */}
|
||||||
|
<motion.path
|
||||||
|
className="text-[#0cce6b]"
|
||||||
|
initial={{ strokeDasharray: "0, 100" }}
|
||||||
|
whileInView={{ strokeDasharray: `${metric.score}, 100` }}
|
||||||
|
viewport={{ once: true }}
|
||||||
|
transition={{
|
||||||
|
duration: 1.5,
|
||||||
|
ease: "easeOut",
|
||||||
|
delay: i * 0.15 + 0.2,
|
||||||
|
}}
|
||||||
|
d="M18 2.0845 a 15.9155 15.9155 0 0 1 0 31.831 a 15.9155 15.9155 0 0 1 0 -31.831"
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
strokeWidth="2.5"
|
||||||
|
strokeLinecap="round"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
<div className="font-mono text-[24px] font-bold tracking-tight text-[#0cce6b]">
|
||||||
|
{metric.score}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Content Details */}
|
||||||
|
<div className="flex-1">
|
||||||
|
<h3 className="font-display text-[17px] font-bold text-[rgba(255,255,255,0.92)]">
|
||||||
|
{metric.label}
|
||||||
|
</h3>
|
||||||
|
<p className="mt-sp-3 text-[13px] leading-[1.8] text-[rgba(255,255,255,.75)]">
|
||||||
|
{metric.description}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</motion.article>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</Section>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -10,6 +10,7 @@ const Experience = dynamic(() => import("./components/Experience"));
|
|||||||
const WorkModalities = dynamic(() => import("./components/WorkModalities").then(mod => mod.WorkModalities));
|
const WorkModalities = dynamic(() => import("./components/WorkModalities").then(mod => mod.WorkModalities));
|
||||||
const Contact = dynamic(() => import("./components/Contact"));
|
const Contact = dynamic(() => import("./components/Contact"));
|
||||||
const Projects = dynamic(() => import("./components/Projects"));
|
const Projects = dynamic(() => import("./components/Projects"));
|
||||||
|
const LighthouseMetrics = dynamic(() => import("./components/LighthouseMetrics"));
|
||||||
|
|
||||||
export default function Home() {
|
export default function Home() {
|
||||||
return (
|
return (
|
||||||
@@ -24,6 +25,7 @@ export default function Home() {
|
|||||||
>
|
>
|
||||||
<Projects />
|
<Projects />
|
||||||
</Suspense>
|
</Suspense>
|
||||||
|
<LighthouseMetrics />
|
||||||
<WorkModalities />
|
<WorkModalities />
|
||||||
<Contact />
|
<Contact />
|
||||||
</main>
|
</main>
|
||||||
|
|||||||
Reference in new issue
Block a user