feat(ui): complete structural rewrite of content components to strict engineering blueprint aesthetic
Some checks failed
Build & Deploy / 🔍 Prepare (push) Successful in 5s
Build & Deploy / 🏗️ Build (push) Failing after 27s
Build & Deploy / 🧪 QA (push) Failing after 1m15s
Build & Deploy / 🚀 Deploy (push) Has been skipped
Build & Deploy / 🩺 Health Check (push) Has been skipped
Build & Deploy / 🔔 Notify (push) Successful in 2s
Some checks failed
Build & Deploy / 🔍 Prepare (push) Successful in 5s
Build & Deploy / 🏗️ Build (push) Failing after 27s
Build & Deploy / 🧪 QA (push) Failing after 1m15s
Build & Deploy / 🚀 Deploy (push) Has been skipped
Build & Deploy / 🩺 Health Check (push) Has been skipped
Build & Deploy / 🔔 Notify (push) Successful in 2s
This commit is contained in:
75
apps/web/src/components/WebVitalsScore.tsx
Normal file
75
apps/web/src/components/WebVitalsScore.tsx
Normal file
@@ -0,0 +1,75 @@
|
||||
'use client';
|
||||
|
||||
import React from 'react';
|
||||
|
||||
interface WebVitalsScoreProps {
|
||||
values: {
|
||||
/** Largest Contentful Paint in seconds (e.g. 2.5) */
|
||||
lcp: number;
|
||||
/** Interaction to Next Paint in milliseconds (e.g. 200) */
|
||||
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-emerald-600 border-emerald-500';
|
||||
if (status === 'needs-improvement') return 'text-amber-500 border-amber-400';
|
||||
return 'text-red-500 border-red-500';
|
||||
};
|
||||
|
||||
return (
|
||||
<section className="my-16 not-prose border-[2px] border-slate-900 p-8 md:p-12 relative bg-white">
|
||||
<div className="absolute -top-[14px] left-8 bg-white px-4">
|
||||
<h3 className="text-xl font-bold text-slate-900 tracking-tight m-0 uppercase flex items-center gap-3">
|
||||
<div className="w-3 h-3 bg-slate-900 rotate-45" />
|
||||
Core Web Vitals
|
||||
</h3>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-8 md:gap-12 mt-4">
|
||||
{metrics.map((m) => {
|
||||
const colors = getColors(m.stat);
|
||||
return (
|
||||
<div key={m.id} className="flex flex-col">
|
||||
<span className="text-[10px] font-mono text-slate-500 uppercase tracking-widest mb-1">{m.label}</span>
|
||||
<div className={`text-4xl md:text-5xl font-black tracking-tighter tabular-nums ${colors.split(' ')[0]} border-b-[3px] ${colors.split(' ')[1]} pb-2 mb-2`}>
|
||||
{m.value}<span className="text-lg ml-1 font-bold">{m.unit}</span>
|
||||
</div>
|
||||
<div className="flex justify-between items-start gap-2">
|
||||
<span className={`text-[10px] font-mono font-bold uppercase ${colors.split(' ')[0]}`}>{m.stat.replace('-', ' ')}</span>
|
||||
<span className="text-[10px] text-slate-500 leading-snug text-right max-w-[120px]">{m.desc}</span>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
|
||||
{description && (
|
||||
<div className="mt-10 p-5 bg-slate-50 border-l-2 border-slate-900">
|
||||
<p className="text-sm text-slate-800 m-0 leading-relaxed font-serif">
|
||||
<span className="font-mono text-[10px] font-bold uppercase text-slate-900 tracking-widest block mb-2">Analyse</span>
|
||||
{description}
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
</section>
|
||||
);
|
||||
};
|
||||
Reference in New Issue
Block a user