Files
mintel.me/apps/web/src/components/WebVitalsScore.tsx
Marc Mintel b15c8408ff
Some checks failed
Build & Deploy / 🔍 Prepare (push) Successful in 5s
Build & Deploy / 🏗️ Build (push) Failing after 14s
Build & Deploy / 🧪 QA (push) Failing after 1m48s
Build & Deploy / 🚀 Deploy (push) Has been skipped
Build & Deploy / 🩺 Health Check (push) Has been skipped
Build & Deploy / 🔔 Notify (push) Successful in 2s
fix(blog): optimize component share logic, typography, and modal layouts
2026-02-22 11:41:28 +01:00

91 lines
5.3 KiB
TypeScript

"use client";
import React from 'react';
import { ComponentShareButton } from './ComponentShareButton';
import { Reveal } from './Reveal';
interface WebVitalsScoreProps {
values: {
lcp: number;
inp: number;
/** Cumulative Layout Shift (e.g. 0.1) */
cls: number;
};
description?: string;
showShare?: boolean;
}
export const WebVitalsScore: React.FC<WebVitalsScoreProps> = ({ values, description }) => {
const getStatus = (metric: 'lcp' | 'inp' | 'cls', value: number): 'good' | 'needs-improvement' | 'poor' => {
if (metric === 'lcp') return value <= 2.5 ? 'good' : value <= 4.0 ? 'needs-improvement' : 'poor';
if (metric === 'inp') return value <= 200 ? 'good' : value <= 500 ? 'needs-improvement' : 'poor';
if (metric === 'cls') return value <= 0.1 ? 'good' : value <= 0.25 ? 'needs-improvement' : 'poor';
return 'poor';
};
const metrics = [
{ id: 'lcp', label: 'Largest Contentful Paint', value: values.lcp, unit: 's', stat: getStatus('lcp', values.lcp), desc: 'Wann der Hauptinhalt geladen ist' },
{ id: 'inp', label: 'Interaction to Next Paint', value: values.inp, unit: 'ms', stat: getStatus('inp', values.inp), desc: 'Reaktionszeit auf Klicks' },
{ id: 'cls', label: 'Cumulative Layout Shift', value: values.cls, unit: '', stat: getStatus('cls', values.cls), desc: 'Visuelle Stabilität beim Laden' }
];
const getColors = (status: string) => {
if (status === 'good') return { text: 'text-slate-900', bg: 'bg-emerald-50/50', border: 'border-emerald-200/50', glow: '' };
if (status === 'needs-improvement') return { text: 'text-slate-900', bg: 'bg-amber-50/50', border: 'border-amber-200/50', glow: '' };
return { text: 'text-slate-900', bg: 'bg-red-50/50', border: 'border-red-200/50', glow: '' };
};
// Generate stable hash for share button
const shareId = `vitals-${React.useId().replace(/:/g, "")}`;
return (
<Reveal direction="up" delay={0.1}>
<figure id={shareId} className="not-prose my-16 group relative transition-all duration-500 ease-out z-10">
{/* Ambient Background Glow matching the homepage feel */}
<div className="absolute -inset-1 bg-gradient-to-r from-slate-100/50 to-slate-200/30 rounded-[2rem] blur opacity-20 group-hover:opacity-40 transition duration-1000 -z-10" />
<div className="glass bg-white/80 backdrop-blur-xl border border-slate-100 rounded-2xl shadow-sm group-hover:shadow-md group-hover:border-slate-200 transition-all duration-500 overflow-hidden relative">
{/* Subtle top shine */}
<div className="absolute inset-x-0 top-0 h-px bg-gradient-to-r from-transparent via-white to-transparent opacity-80" />
<div className="p-8 md:p-10 relative z-10">
{/* Share Button top right */}
<div className="absolute top-6 right-6 md:opacity-0 group-hover:opacity-100 transition-opacity duration-500 z-50">
<ComponentShareButton targetId={shareId} title="Core Web Vitals Scores" />
</div>
<header className="mb-10 flex flex-col gap-2 border-b border-slate-100 pb-4 pr-16 md:pr-0">
<div>
<h3 className="text-xl md:text-2xl font-bold text-slate-900 tracking-tight m-0 flex items-center gap-3">
<span className="w-2 h-2 rounded-full bg-slate-800" />
Core Web Vitals
</h3>
{description && <p className="text-sm text-slate-500 mt-1 leading-snug m-0">{description}</p>}
</div>
</header>
<div className="grid grid-cols-1 md:grid-cols-3 gap-6 md:gap-8">
{metrics.map((metric, i) => {
const colors = getColors(metric.stat);
return (
<div key={i} className={`flex flex-col gap-2 p-6 rounded-2xl border border-transparent hover:border-slate-200 hover:bg-slate-50 transition-all duration-300 group/metric`}>
<span className="text-[10px] font-bold text-slate-400 uppercase tracking-widest">{metric.label}</span>
<div className="flex items-baseline gap-1">
<span className={`text-4xl md:text-5xl font-bold tracking-tighter transition-transform duration-500 group-hover/metric:scale-110 origin-left ${colors.text}`}>
{metric.value}
</span>
{metric.unit && <span className="text-sm font-mono text-slate-400">{metric.unit}</span>}
</div>
<span className="text-xs leading-relaxed text-slate-600 font-medium mt-1">{metric.desc}</span>
</div>
);
})}
</div>
</div>
</div>
</figure>
</Reveal>
);
};