Skip to content

Commit 06bfb43

Browse files
fylornclaude
andcommitted
feat(landing): cinematic polish — stats strip, live pill, pipeline rail
- Hero: LIVE req/s + tokens/min pill (jitter every 1.8s) and a 4-column stats strip (2 MB · 3+1 · 5 · Rust) pinned below the fold. - HowItWorks: replaced the 3-col grid with a 6-step horizontal pipeline rail that auto-advances every 2.4s, pauses when out of view, and is click-to-jump. Top progress bar fills as the active step moves. - CompatibleWith: mono-chip marquee (32s loop) with masked fade edges and hover-to-pause. - Problem: 80ms stagger fade-in on intersection + 01–05 mono numbering. - Features: cards now lead with a glowing dot + 01/02 mono index. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 78c53b6 commit 06bfb43

6 files changed

Lines changed: 256 additions & 86 deletions

File tree

‎src/components/sections/CompatibleWith.astro‎

Lines changed: 29 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -13,19 +13,38 @@ const items = [
1313
"LangChain",
1414
"LlamaIndex",
1515
];
16+
// Doubled for seamless loop
17+
const doubled = [...items, ...items];
1618
---
1719

18-
<section class="py-12 border-t border-white/5" aria-label="Compatible clients and SDKs">
19-
<div class="container-page">
20-
<p class="text-center text-[10px] uppercase tracking-[0.2em] text-[var(--color-dim)] mb-6">
20+
<section class="py-14 border-t border-white/5" aria-label="Compatible clients and SDKs">
21+
<div class="container-page flex flex-col md:flex-row md:items-center gap-6 md:gap-10">
22+
<p class="shrink-0 text-[10px] uppercase tracking-[0.2em] text-[var(--color-dim)] font-mono">
2123
{s.label}
2224
</p>
23-
<ul class="flex flex-wrap items-center justify-center gap-x-10 gap-y-4 text-[var(--color-muted)]">
24-
{items.map((name) => (
25-
<li class="text-sm font-medium tracking-tight hover:text-white transition-colors">
26-
{name}
27-
</li>
28-
))}
29-
</ul>
25+
<div class="compat-track-wrap flex-1 overflow-hidden relative">
26+
<ul class="compat-track flex gap-10 w-max animate-[marquee_32s_linear_infinite]" aria-hidden="false">
27+
{doubled.map((name) => (
28+
<li class="font-mono text-[13px] tracking-tight text-[var(--color-muted)] whitespace-nowrap hover:text-white transition-colors">
29+
{name}
30+
</li>
31+
))}
32+
</ul>
33+
</div>
3034
</div>
3135
</section>
36+
37+
<style>
38+
.compat-track-wrap {
39+
-webkit-mask-image: linear-gradient(to right, transparent, #000 8%, #000 92%, transparent);
40+
mask-image: linear-gradient(to right, transparent, #000 8%, #000 92%, transparent);
41+
}
42+
@keyframes marquee {
43+
from { transform: translateX(0); }
44+
to { transform: translateX(-50%); }
45+
}
46+
@media (prefers-reduced-motion: reduce) {
47+
.compat-track { animation: none !important; }
48+
}
49+
.compat-track-wrap:hover .compat-track { animation-play-state: paused; }
50+
</style>

‎src/components/sections/Features.tsx‎

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -51,16 +51,17 @@ export default function Features({ modules }: { modules: readonly FeatureModule[
5151
{current.bullets.map((b, i) => (
5252
<div
5353
key={b.title}
54-
className="rounded-xl border border-white/10 bg-[var(--color-surface)]/60 p-6 hover:border-white/25 transition-colors animate-[fadeUp_400ms_ease-out_both]"
54+
className="group rounded-xl border border-white/10 bg-[var(--color-surface)]/60 p-6 hover:border-white/25 transition-colors animate-[fadeUp_400ms_ease-out_both]"
5555
style={{ animationDelay: `${i * 60}ms` }}
5656
>
57-
<div className="flex items-start gap-3">
58-
<span className="mt-1 inline-block w-1.5 h-1.5 rounded-full bg-[var(--color-brand-1)] shrink-0" />
59-
<div>
60-
<h4 className="font-semibold tracking-tight mb-1.5">{b.title}</h4>
61-
<p className="text-sm text-[var(--color-muted)] leading-relaxed">{b.body}</p>
62-
</div>
57+
<div className="flex items-center justify-between mb-4">
58+
<span className="inline-block w-1.5 h-1.5 rounded-full bg-[var(--color-brand-1)] shadow-[0_0_10px_var(--color-brand-1)] group-hover:scale-110 transition-transform" />
59+
<span className="font-mono text-[11px] tracking-[0.12em] text-[var(--color-dim)]">
60+
{String(i + 1).padStart(2, "0")}
61+
</span>
6362
</div>
63+
<h4 className="font-semibold tracking-tight mb-1.5">{b.title}</h4>
64+
<p className="text-sm text-[var(--color-muted)] leading-relaxed">{b.body}</p>
6465
</div>
6566
))}
6667
</div>

‎src/components/sections/Hero.astro‎

Lines changed: 46 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -52,13 +52,17 @@ const version = latest?.data.version ?? "0.1.0";
5252
</MagneticButton>
5353
</div>
5454

55-
<div class="mt-10 sm:mt-12 flex flex-wrap gap-x-5 sm:gap-x-8 gap-y-2 sm:gap-y-3 text-[10px] sm:text-xs text-[var(--color-dim)] uppercase tracking-widest">
56-
{s.stack.map((item, i) => (
57-
<>
58-
{i > 0 && <span aria-hidden="true" class="hidden sm:inline">·</span>}
59-
<span>{item}</span>
60-
</>
61-
))}
55+
<div class="mt-7 sm:mt-8 inline-flex items-center gap-3 rounded-md border border-white/10 bg-white/[0.02] px-3 py-2 text-xs">
56+
<span class="inline-flex items-center gap-1.5 font-mono text-[11px] font-medium text-[var(--color-accent)]">
57+
<span class="w-1.5 h-1.5 rounded-full bg-[var(--color-accent)] animate-pulse" aria-hidden="true"></span>
58+
{s.live.label}
59+
</span>
60+
<span class="w-px h-3 bg-white/10" aria-hidden="true"></span>
61+
<span class="font-mono tabular-nums text-white" data-live-reqs>1,284</span>
62+
<span class="font-mono text-[var(--color-dim)]">{s.live.reqPerSec}</span>
63+
<span class="text-[var(--color-dim)]" aria-hidden="true">/</span>
64+
<span class="font-mono tabular-nums text-white" data-live-tokens>42,730</span>
65+
<span class="font-mono text-[var(--color-dim)]">{s.live.tokensPerMin}</span>
6266
</div>
6367
</div>
6468

@@ -68,5 +72,40 @@ const version = latest?.data.version ?? "0.1.0";
6872
</div>
6973
</div>
7074

75+
<div class="container-page relative z-10 mt-14 sm:mt-20">
76+
<dl class="grid grid-cols-2 md:grid-cols-4 border-t border-l border-white/10">
77+
{s.stats.map((stat) => (
78+
<div class="border-r border-b border-white/10 px-5 py-5 sm:px-6 sm:py-6">
79+
<dt class="font-mono text-2xl sm:text-3xl text-white tracking-tight tabular-nums">
80+
{stat.value}
81+
</dt>
82+
<dd class="mt-1.5 text-[10px] sm:text-[11px] uppercase tracking-[0.14em] text-[var(--color-dim)]">
83+
{stat.label}
84+
</dd>
85+
</div>
86+
))}
87+
</dl>
88+
</div>
89+
7190
<div class="absolute inset-x-0 bottom-0 h-24 sm:h-32 bg-gradient-to-b from-transparent to-[var(--color-bg)] pointer-events-none"></div>
7291
</section>
92+
93+
<script>
94+
// Subtle jitter so the LIVE pill feels alive without looking fake.
95+
const reqsEl = document.querySelector<HTMLSpanElement>("[data-live-reqs]");
96+
const tokensEl = document.querySelector<HTMLSpanElement>("[data-live-tokens]");
97+
if (reqsEl && tokensEl) {
98+
const fmt = (n: number) => n.toLocaleString("en-US");
99+
let reqs = 1284;
100+
let tokens = 42730;
101+
const tick = () => {
102+
reqs += Math.round((Math.random() - 0.45) * 32);
103+
tokens += Math.round((Math.random() - 0.4) * 900);
104+
reqs = Math.max(900, Math.min(1700, reqs));
105+
tokens = Math.max(30000, Math.min(55000, tokens));
106+
reqsEl.textContent = fmt(reqs);
107+
tokensEl.textContent = fmt(tokens);
108+
};
109+
setInterval(tick, 1800);
110+
}
111+
</script>

‎src/components/sections/HowItWorks.astro‎

Lines changed: 96 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -105,17 +105,30 @@ const providers = [
105105
</div>
106106
</div>
107107

108-
<!-- Pipeline steps -->
108+
<!-- Pipeline rail -->
109109
<div class="mt-16">
110110
<div class="text-xs uppercase tracking-[0.18em] text-[var(--color-muted)] mb-6">{s.pipelineLabel}</div>
111-
<ol class="grid gap-4 md:grid-cols-2 lg:grid-cols-3 pipeline-steps">
112-
{s.pipeline.map((step) => (
113-
<li class="pipeline-step relative rounded-xl border border-white/10 bg-[var(--color-surface)]/60 p-5 transition-all duration-500">
114-
<div class="flex items-baseline gap-3 mb-2">
115-
<span class="step-num font-mono text-[10px] text-[var(--color-dim)] transition-colors duration-500">{step.step}</span>
116-
<span class="font-semibold tracking-tight">{step.label}</span>
117-
</div>
118-
<p class="text-sm text-[var(--color-muted)] leading-relaxed" set:html={step.body.replace(/`([^`]+)`/g, '<code class="font-mono text-[12px] text-[var(--color-brand-1)] bg-white/5 rounded px-1 py-0.5">$1</code>')}></p>
111+
112+
<div class="pipeline-rail relative h-px bg-white/10 overflow-hidden mb-6">
113+
<div class="pipeline-rail-fill absolute inset-y-0 left-0 bg-gradient-to-r from-[var(--color-brand-1)] via-[var(--color-brand-2)] to-[var(--color-accent)] transition-[width] duration-500 ease-[cubic-bezier(.22,.9,.3,1)]" style="width: 16.66%"></div>
114+
</div>
115+
116+
<ol class="pipeline-steps grid grid-cols-2 sm:grid-cols-3 lg:grid-cols-6" role="tablist">
117+
{s.pipeline.map((step, i) => (
118+
<li>
119+
<button
120+
type="button"
121+
class="pipeline-step group w-full text-left py-4 pr-4 sm:pr-5 border-r border-white/10 last:border-r-0 transition-colors"
122+
data-pipeline-step={i}
123+
aria-label={`Pipeline step ${step.step}: ${step.label}`}
124+
>
125+
<div class="flex items-center justify-between mb-3">
126+
<span class="pipeline-step-num font-mono text-[11px] tracking-[0.12em] text-[var(--color-dim)] transition-colors">{step.step}</span>
127+
<span class="pipeline-step-tick w-2 h-2 rounded-full bg-white/15 transition-all" aria-hidden="true"></span>
128+
</div>
129+
<h4 class="pipeline-step-title font-medium text-sm text-white mb-1.5 tracking-tight">{step.label}</h4>
130+
<p class="text-xs text-[var(--color-muted)] leading-[1.55]" set:html={step.body.replace(/`([^`]+)`/g, '<code class="font-mono text-[11px] text-[var(--color-brand-1)] bg-white/5 rounded px-1 py-0.5">$1</code>')}></p>
131+
</button>
119132
</li>
120133
))}
121134
</ol>
@@ -125,42 +138,29 @@ const providers = [
125138

126139
<style>
127140
.pipeline-step {
128-
transform: translateY(8px);
129141
opacity: 0.55;
142+
cursor: pointer;
143+
background: transparent;
144+
border: 0;
145+
border-right: 1px solid rgba(255, 255, 255, 0.1);
146+
font-family: inherit;
147+
transition: opacity 300ms ease;
130148
}
131-
.pipeline-step.is-active {
132-
transform: translateY(0);
133-
opacity: 1;
134-
border-color: rgba(167, 139, 250, 0.45);
135-
box-shadow:
136-
0 0 0 1px rgba(34, 211, 238, 0.18),
137-
0 12px 40px -12px rgba(167, 139, 250, 0.35);
138-
}
139-
.pipeline-step.is-active .step-num {
140-
color: var(--color-brand-1);
141-
}
142-
.pipeline-step::before {
143-
content: "";
144-
position: absolute;
145-
inset: 0;
146-
border-radius: inherit;
147-
pointer-events: none;
148-
background: linear-gradient(135deg, rgba(34, 211, 238, 0.08), rgba(167, 139, 250, 0.04));
149-
opacity: 0;
150-
transition: opacity 500ms ease;
151-
}
152-
.pipeline-step.is-active::before {
153-
opacity: 1;
149+
.pipeline-step:hover { opacity: 0.85; }
150+
.pipeline-step.is-active,
151+
.pipeline-step.is-done { opacity: 1; }
152+
.pipeline-step.is-active .pipeline-step-num { color: var(--color-brand-1); }
153+
.pipeline-step.is-active .pipeline-step-tick {
154+
background: var(--color-brand-1);
155+
box-shadow: 0 0 14px var(--color-brand-1);
154156
}
157+
.pipeline-step.is-done .pipeline-step-tick { background: var(--color-brand-2); }
158+
155159
@media (prefers-reduced-motion: reduce) {
156-
.pipeline-step {
157-
transform: none;
158-
opacity: 1;
159-
}
160+
.pipeline-rail-fill,
161+
.pipeline-step { transition: none !important; }
160162
.arch-flow,
161-
.arch-glow {
162-
animation: none !important;
163-
}
163+
.arch-glow { animation: none !important; }
164164
}
165165

166166
@keyframes archDash {
@@ -186,18 +186,62 @@ const providers = [
186186
</style>
187187

188188
<script>
189-
const steps = document.querySelectorAll(".pipeline-step");
190-
if (steps.length && "IntersectionObserver" in window) {
191-
const obs = new IntersectionObserver(
192-
(entries) => {
193-
for (const entry of entries) {
194-
entry.target.classList.toggle("is-active", entry.isIntersecting);
195-
}
196-
},
197-
{ rootMargin: "-25% 0px -25% 0px", threshold: 0 },
189+
// Auto-advance the pipeline as the section scrolls into view; pause when out of
190+
// view. Users can click any step to jump there — the timer resumes afterwards.
191+
const section = document.getElementById("how");
192+
const steps = Array.from(
193+
document.querySelectorAll<HTMLButtonElement>("[data-pipeline-step]")
194+
);
195+
const rail = document.querySelector<HTMLElement>(".pipeline-rail-fill");
196+
197+
if (section && steps.length && rail) {
198+
const total = steps.length;
199+
let active = 0;
200+
let visible = false;
201+
let timer: number | undefined;
202+
203+
const prefersReduced = window.matchMedia("(prefers-reduced-motion: reduce)").matches;
204+
205+
const render = () => {
206+
steps.forEach((el, i) => {
207+
el.classList.toggle("is-active", i === active);
208+
el.classList.toggle("is-done", i < active);
209+
});
210+
rail.style.width = `${((active + 1) / total) * 100}%`;
211+
};
212+
213+
const schedule = () => {
214+
if (timer) window.clearTimeout(timer);
215+
if (!visible || prefersReduced) return;
216+
timer = window.setTimeout(() => {
217+
active = (active + 1) % total;
218+
render();
219+
schedule();
220+
}, 2400);
221+
};
222+
223+
steps.forEach((el, i) =>
224+
el.addEventListener("click", () => {
225+
active = i;
226+
render();
227+
schedule();
228+
})
198229
);
199-
steps.forEach((s) => obs.observe(s));
200-
} else {
201-
steps.forEach((s) => s.classList.add("is-active"));
230+
231+
if ("IntersectionObserver" in window) {
232+
const obs = new IntersectionObserver(
233+
(entries) => {
234+
visible = entries[0]?.isIntersecting ?? false;
235+
schedule();
236+
},
237+
{ threshold: 0.2 }
238+
);
239+
obs.observe(section);
240+
} else {
241+
visible = true;
242+
schedule();
243+
}
244+
245+
render();
202246
}
203247
</script>

‎src/components/sections/Problem.astro‎

Lines changed: 55 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -20,17 +20,24 @@ const s = t(getLang(Astro)).problem;
2020
</div>
2121

2222
<div class="mt-12 sm:mt-16 grid gap-4 sm:gap-5 sm:grid-cols-2 lg:grid-cols-3">
23-
{s.pains.map((p) => (
24-
<TiltCard client:visible className="rounded-2xl">
25-
<div class="relative rounded-2xl border border-white/10 bg-[var(--color-surface)]/60 p-5 sm:p-6 hover:border-white/25 transition-colors overflow-hidden h-full">
26-
<div class="text-3xl mb-4 grayscale group-hover:grayscale-0 transition" aria-hidden="true">
27-
{p.icon}
23+
{s.pains.map((p, i) => (
24+
<div class="pain-item" style={`--pain-delay:${i * 80}ms`}>
25+
<TiltCard client:visible className="rounded-2xl">
26+
<div class="relative rounded-2xl border border-white/10 bg-[var(--color-surface)]/60 p-5 sm:p-6 hover:border-white/25 transition-colors overflow-hidden h-full">
27+
<div class="flex items-start justify-between mb-4">
28+
<div class="text-3xl grayscale group-hover:grayscale-0 transition" aria-hidden="true">
29+
{p.icon}
30+
</div>
31+
<span class="font-mono text-[11px] tracking-[0.12em] text-[var(--color-dim)]">
32+
{String(i + 1).padStart(2, "0")}
33+
</span>
34+
</div>
35+
<h3 class="text-lg font-semibold tracking-tight mb-2">{p.title}</h3>
36+
<p class="text-sm text-[var(--color-muted)] leading-relaxed">{p.body}</p>
37+
<div class="absolute bottom-0 left-0 right-0 h-px bg-gradient-to-r from-transparent via-pink-400/40 to-transparent opacity-0 group-hover:opacity-100 transition-opacity"></div>
2838
</div>
29-
<h3 class="text-lg font-semibold tracking-tight mb-2">{p.title}</h3>
30-
<p class="text-sm text-[var(--color-muted)] leading-relaxed">{p.body}</p>
31-
<div class="absolute bottom-0 left-0 right-0 h-px bg-gradient-to-r from-transparent via-pink-400/40 to-transparent opacity-0 group-hover:opacity-100 transition-opacity"></div>
32-
</div>
33-
</TiltCard>
39+
</TiltCard>
40+
</div>
3441
))}
3542
</div>
3643

@@ -41,3 +48,41 @@ const s = t(getLang(Astro)).problem;
4148
</div>
4249
</div>
4350
</section>
51+
52+
<style>
53+
.pain-item {
54+
opacity: 0;
55+
transform: translateY(14px);
56+
}
57+
.pain-item.is-visible {
58+
animation: painFadeUp 600ms cubic-bezier(.2,.7,.25,1) both;
59+
animation-delay: var(--pain-delay, 0ms);
60+
}
61+
@keyframes painFadeUp {
62+
from { opacity: 0; transform: translateY(14px); }
63+
to { opacity: 1; transform: translateY(0); }
64+
}
65+
@media (prefers-reduced-motion: reduce) {
66+
.pain-item { opacity: 1; transform: none; animation: none !important; }
67+
}
68+
</style>
69+
70+
<script>
71+
const cards = document.querySelectorAll(".pain-item");
72+
if (cards.length && "IntersectionObserver" in window) {
73+
const obs = new IntersectionObserver(
74+
(entries) => {
75+
for (const entry of entries) {
76+
if (entry.isIntersecting) {
77+
entry.target.classList.add("is-visible");
78+
obs.unobserve(entry.target);
79+
}
80+
}
81+
},
82+
{ threshold: 0.15 }
83+
);
84+
cards.forEach((c) => obs.observe(c));
85+
} else {
86+
cards.forEach((c) => c.classList.add("is-visible"));
87+
}
88+
</script>

0 commit comments

Comments
 (0)