Skip to content

Commit 78070af

Browse files
ajitpratap0Ajit Pratap Singhclaude
authored
perf(website): fix CLS 0.382 -> target <0.1 (#384)
Root causes fixed: - Stats counter: removed JS animation that reset values to 0 then animated up, causing major layout shift. Now renders static values. - Hero placeholder: added min-height: 600px container around client:only React component to reserve space before hydration. - Logo images: added explicit width/height to navbar (73x40) and footer (58x32) logos to prevent layout shift on load. - Font swap: added font-size-adjust to body (0.5) and headings (0.45) to minimize shift when custom fonts replace system fonts. Co-authored-by: Ajit Pratap Singh <ajitpratapsingh@Ajits-Mac-mini-2655.local> Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 2b4bc8a commit 78070af

5 files changed

Lines changed: 13 additions & 67 deletions

File tree

website/src/components/Footer.astro

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ const columns = [
3434
<a href="/" class="block mb-8">
3535
<picture>
3636
<source srcset="/images/logo.webp" type="image/webp">
37-
<img src="/images/logo.png" alt="GoSQLX" class="h-8" />
37+
<img src="/images/logo.png" alt="GoSQLX" class="h-8" width="58" height="32" />
3838
</picture>
3939
</a>
4040
<div class="grid grid-cols-1 md:grid-cols-3 gap-8">

website/src/components/Navbar.astro

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ const navLinks = [
1414
<a href="/" class="flex items-center gap-2">
1515
<picture>
1616
<source srcset="/images/logo.webp" type="image/webp">
17-
<img src="/images/logo.png" alt="GoSQLX" class="h-10" />
17+
<img src="/images/logo.png" alt="GoSQLX" class="h-10" width="73" height="40" />
1818
</picture>
1919
</a>
2020

Lines changed: 6 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,18 @@
11
---
22
const stats = [
3-
{ value: "1.25M+", label: "ops/sec", color: "text-orange-400", target: 1.25 },
4-
{ value: "<1μs", label: "latency", color: "text-blue-400", target: 1 },
5-
{ value: "85%", label: "SQL-99", color: "text-green-400", target: 85 },
6-
{ value: "6", label: "Dialects", color: "text-purple-400", target: 6 },
3+
{ value: "1.25M+", label: "ops/sec", color: "text-orange-400" },
4+
{ value: "<1\u00B5s", label: "latency", color: "text-blue-400" },
5+
{ value: "85%", label: "SQL-99", color: "text-green-400" },
6+
{ value: "6", label: "Dialects", color: "text-purple-400" },
77
];
88
---
99

1010
<section class="py-16 border-y border-slate-800">
1111
<div class="container mx-auto px-4">
1212
<div class="flex flex-wrap justify-center gap-12 md:gap-20">
1313
{stats.map((stat) => (
14-
<div class="text-center stat-item" data-target={stat.target} data-display={stat.value}>
15-
<div class={`text-4xl font-extrabold ${stat.color} stat-value`} style="font-variant-numeric: tabular-nums; min-width: 5ch;">
14+
<div class="text-center">
15+
<div class={`text-4xl font-extrabold ${stat.color}`} style="font-variant-numeric: tabular-nums; min-width: 5ch; line-height: 1.2;">
1616
{stat.value}
1717
</div>
1818
<div class="text-sm text-slate-400 mt-1">{stat.label}</div>
@@ -21,61 +21,3 @@ const stats = [
2121
</div>
2222
</div>
2323
</section>
24-
25-
<script>
26-
function animateStats() {
27-
const items = document.querySelectorAll('.stat-item');
28-
29-
const observer = new IntersectionObserver(
30-
(entries) => {
31-
entries.forEach((entry) => {
32-
if (entry.isIntersecting) {
33-
const el = entry.target as HTMLElement;
34-
const valueEl = el.querySelector('.stat-value') as HTMLElement;
35-
const display = el.dataset.display || '';
36-
const target = parseFloat(el.dataset.target || '0');
37-
38-
// Start from 0 and count up
39-
let start = 0;
40-
const duration = 1200;
41-
const startTime = performance.now();
42-
43-
function tick(now: number) {
44-
const elapsed = now - startTime;
45-
const progress = Math.min(elapsed / duration, 1);
46-
// Ease out cubic
47-
const eased = 1 - Math.pow(1 - progress, 3);
48-
const current = start + (target - start) * eased;
49-
50-
if (progress < 1) {
51-
// Show intermediate number
52-
if (display.includes('M')) {
53-
valueEl.textContent = current.toFixed(2) + 'M+';
54-
} else if (display.includes('%')) {
55-
valueEl.textContent = Math.round(current) + '%';
56-
} else if (display.includes('μ')) {
57-
valueEl.textContent = '<' + Math.max(1, Math.round(current)) + 'μs';
58-
} else {
59-
valueEl.textContent = String(Math.round(current));
60-
}
61-
requestAnimationFrame(tick);
62-
} else {
63-
valueEl.textContent = display;
64-
}
65-
}
66-
67-
requestAnimationFrame(tick);
68-
observer.unobserve(el);
69-
}
70-
});
71-
},
72-
{ threshold: 0.3 }
73-
);
74-
75-
items.forEach((item) => observer.observe(item));
76-
}
77-
78-
// Run on load and on Astro page transitions
79-
animateStats();
80-
document.addEventListener('astro:page-load', animateStats);
81-
</script>

website/src/pages/index.astro

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,9 @@ import SocialProof from '../components/SocialProof.astro';
99
---
1010
<BaseLayout title="GoSQLX - High-Performance SQL Parser for Go" description="Production-ready, zero-copy SQL parsing SDK for Go with multi-dialect support">
1111
<main>
12-
<HeroPlayground client:only="react" />
12+
<div style="min-height: 600px;">
13+
<HeroPlayground client:only="react" />
14+
</div>
1315
<!-- Angled divider -->
1416
<div class="relative h-16 -mt-1">
1517
<svg class="absolute bottom-0 w-full h-16" viewBox="0 0 1440 64" preserveAspectRatio="none">

website/src/styles/global.css

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,12 @@
55
body {
66
@apply bg-slate-900 text-slate-50;
77
font-family: 'Instrument Sans', system-ui, sans-serif;
8+
font-size-adjust: 0.5;
89
}
910

1011
h1, h2, h3, h4, h5, h6 {
1112
font-family: 'IBM Plex Mono', monospace;
13+
font-size-adjust: 0.45;
1214
}
1315

1416
button:focus-visible,

0 commit comments

Comments
 (0)