Pre-launch — Gluecron is in final validation. Public signups and git hosting for non-owner users open after launch review.
CodeIssuesPull RequestsActionsSecurityInsightsSettings
✨ AI
More
Blame · Line-by-line history

landing.tsx

Each line is annotated with the commit that last touched it. Click any SHA to jump to that commit and see the surrounding change.

landing.tsxBlame2856 lines · 1 contributor
2b821b7Claude1/**
2 * Marketing landing page for logged-out visitors.
3 *
958d26aClaude4 * Editorial-Technical redesign — 2026.05.
5 * Hero · trust strip · feature grid · workflow walkthrough ·
6 * comparison · terminal · pricing teaser · closing CTA.
2b821b7Claude7 *
5f2e749Claude8 * Block L10 — hero rewrite. The hero now lands the Block L positioning
9 * ("the git host built around Claude"): gradient headline, one-line
10 * install snippet w/ copy button, three CTAs (Sign up / Demo / vs-GitHub),
11 * and a four-line "what just happened" rail driven off the L4 publicStats
12 * payload. The L4 counters tile section and L5 vs-GitHub CTA are both
13 * preserved — additive only.
14 *
15 * Also adds two new editorial sections below the L4 counters:
16 * - "Three reasons to switch" (Sleep Mode / Migrate / Demo)
17 * - "How is this different from GitHub?" pull-quote → /vs-github
18 *
958d26aClaude19 * Pure presentational. Drops into <Layout user={null}>.
20 * All styles scoped under `.landing-` so they don't bleed into app views.
2b821b7Claude21 */
22
23import type { FC } from "hono/jsx";
52ad8b1Claude24import type { PublicStats } from "../lib/public-stats";
534f04aClaude25import { DEMO_USERNAME } from "../lib/demo-seed";
26
27export interface LandingLiveFeedQueued {
28 repo: string;
29 number: number;
30 title: string;
31 createdAt: string | Date;
32}
33
34export interface LandingLiveFeedMerge {
35 repo: string;
36 number: number;
37 title: string;
38 mergedAt: string | Date;
39}
40
41export interface LandingLiveFeedReview {
42 repo: string;
43 prNumber: number;
44 commentSnippet: string;
45 createdAt: string | Date;
46}
47
48export interface LandingLiveFeedEntry {
49 kind: "auto_merge.merged" | "ai_build.dispatched" | "ai_review.posted";
50 repo: string;
51 ref: { type: "issue" | "pr"; number: number };
52 at: string | Date;
53}
54
55/**
56 * Block M1 — server-rendered snapshot of the live-now feed. The same
57 * fields are also fetched client-side every 30s from
58 * `/api/v2/demo/{queued,merges,reviews,activity}`. Optional so existing
59 * call-sites (and tests that don't care about the live block) keep
60 * compiling.
61 */
62export interface LandingLiveFeed {
63 queued: LandingLiveFeedQueued[];
64 merges: LandingLiveFeedMerge[];
65 reviews: LandingLiveFeedReview[];
66 reviewCount: number;
67 feed: LandingLiveFeedEntry[];
68}
2b821b7Claude69
70export interface LandingPageProps {
71 stats?: {
72 publicRepos?: number;
73 users?: number;
74 };
52ad8b1Claude75 /**
76 * Block L4 — full public-stats payload (lifetime + trailing-7-day
77 * AI-highlight counters). When present, the hero renders an animated
78 * six-tile social-proof row beneath the eyebrow.
79 */
80 publicStats?: PublicStats | null;
534f04aClaude81 /**
82 * Block M1 — initial SSR snapshot for the live-now feed block.
83 * The same endpoints poll client-side every 30s. When undefined the
84 * section still renders, but with empty-state copy until the first
85 * client poll lands.
86 */
87 liveFeed?: LandingLiveFeed | null;
2b821b7Claude88}
89
534f04aClaude90export const LandingHero: FC<LandingPageProps> = ({
91 stats,
92 publicStats,
93 liveFeed,
94} = {}) => {
8e9f1d9Claude95 const hasStats =
96 stats &&
97 ((stats.publicRepos !== undefined && stats.publicRepos > 0) ||
98 (stats.users !== undefined && stats.users > 0));
4c47454Claude99
52ad8b1Claude100 // Block L4 — six-tile social proof row. Rendered only when the
101 // cached public-stats payload is available; absent → fall back to
102 // the small text-only `landing-stats` row.
103 const tiles = publicStats
104 ? buildSocialProofTiles(publicStats)
105 : null;
106
534f04aClaude107 // Block M1 — SSR-friendly fallbacks so the no-JS path still renders
108 // a populated block. The client-side poller will overwrite these
109 // every 30s anyway.
110 const liveQueued = liveFeed?.queued ?? [];
111 const liveMerges = liveFeed?.merges ?? [];
112 const liveReviews = liveFeed?.reviews ?? [];
113 const liveReviewCount = liveFeed?.reviewCount ?? 0;
114 const liveEntries = liveFeed?.feed ?? [];
115
2b821b7Claude116 return (
117 <>
fa880f2Claude118 <style dangerouslySetInnerHTML={{ __html: landingCss }} />
2b821b7Claude119
4c47454Claude120 <div class="landing-root">
121 {/* ---------- Hero ---------- */}
122 <section class="landing-hero">
958d26aClaude123 <div class="landing-hero-bg" aria-hidden="true">
124 <div class="landing-hero-blob landing-hero-blob-1" />
125 <div class="landing-hero-blob landing-hero-blob-2" />
126 <div class="landing-hero-grid" />
2b821b7Claude127 </div>
958d26aClaude128
129 <div class="landing-hero-inner stagger">
130 <div class="eyebrow landing-hero-eyebrow">
131 <span class="landing-hero-pulse" />
132 v1 · pre-launch · {new Date().getFullYear()}
133 </div>
134
135 <h1 class="landing-hero-title display">
5f2e749Claude136 <span class="gradient-text">The git host built around Claude.</span>
958d26aClaude137 </h1>
138
139 <p class="landing-hero-sub">
5f2e749Claude140 Label an issue. Walk away. Wake up to a merged PR.
958d26aClaude141 </p>
142
5f2e749Claude143 {/* L10 — one-line install snippet with copy button. */}
144 <div class="landing-hero-install" aria-label="One-line install">
145 <code class="landing-hero-install-code">
146 <span class="landing-hero-install-prompt" aria-hidden="true">$</span>
147 <span id="landing-install-text">curl -sSL gluecron.com/install | bash</span>
148 </code>
149 <button
150 type="button"
151 class="landing-hero-install-copy"
152 data-copy-target="landing-install-text"
153 aria-label="Copy install command"
154 >
155 Copy
156 </button>
157 </div>
158
958d26aClaude159 <div class="landing-hero-ctas">
160 <a href="/register" class="btn btn-primary btn-xl landing-cta-primary">
5f2e749Claude161 Sign up free
958d26aClaude162 <span class="landing-cta-arrow" aria-hidden="true">{"→"}</span>
163 </a>
93fe97eClaude164 {/* BLOCK Q1 — one-click Claude Desktop install. Gradient
165 border + accent so it reads as a distinct flagship CTA,
166 not just a third secondary option. */}
167 <a
168 href="/gluecron.dxt"
169 class="btn btn-xl landing-cta-dxt"
170 download
171 data-testid="cta-dxt"
172 >
173 Add to Claude Desktop
174 <span class="landing-cta-arrow" aria-hidden="true">{"→"}</span>
175 </a>
5f2e749Claude176 <a href="/demo" class="btn btn-secondary btn-xl">
177 Try the live demo
178 <span class="landing-cta-arrow" aria-hidden="true">{"→"}</span>
958d26aClaude179 </a>
52ad8b1Claude180 <a href="/vs-github" class="btn btn-ghost btn-xl">
181 Compare to GitHub
5f2e749Claude182 <span class="landing-cta-arrow" aria-hidden="true">{"→"}</span>
52ad8b1Claude183 </a>
958d26aClaude184 </div>
185
5f2e749Claude186 {/* L10 — "what just happened" rail. Mini, secondary,
187 separate from the BIG L4 counters tile section below. */}
188 {publicStats && (
189 <ul class="landing-hero-rail" aria-label="What just happened on Gluecron">
190 <li>
191 <span class="landing-hero-rail-check" aria-hidden="true">{"✓"}</span>
192 <strong>{publicStats.weeklyPrsAutoMerged.toLocaleString()}</strong>
193 {" PRs auto-merged this week"}
194 </li>
195 <li>
196 <span class="landing-hero-rail-check" aria-hidden="true">{"✓"}</span>
197 <strong>{publicStats.weeklyIssuesBuiltByAi.toLocaleString()}</strong>
198 {" issues built by AI"}
199 </li>
200 <li>
201 <span class="landing-hero-rail-check" aria-hidden="true">{"✓"}</span>
202 <strong>{publicStats.weeklyDeploysShipped.toLocaleString()}</strong>
203 {" deploys shipped overnight"}
204 </li>
205 <li>
206 <span class="landing-hero-rail-check" aria-hidden="true">{"✓"}</span>
207 {"~"}
208 <strong>{Math.round(publicStats.weeklyHoursSaved).toLocaleString()}</strong>
209 {" hours saved by AI"}
210 </li>
211 </ul>
212 )}
213
214 {/* L8 — free-tier reassurance link. Keeps anxiety low for the AI-curious. */}
215 <p class="landing-hero-freenote">
216 Free forever for the AI-curious.{" "}
217 <a href="/pricing" class="landing-hero-freenote-link">
218 See pricing &rarr;
219 </a>
220 </p>
221
958d26aClaude222 <p class="landing-hero-caption">
223 Already have a repo?
224 <span class="landing-hero-cmd">
225 <span class="kbd">git</span>
226 <span class="kbd">remote</span>
227 <span class="kbd">add</span>
228 <span class="kbd">gluecron</span>
229 <span class="landing-hero-arrow">{"→"}</span>
230 <span class="kbd">git push</span>
231 </span>
232 </p>
233
234 {hasStats && (
235 <p class="landing-stats">
236 {stats!.publicRepos !== undefined && stats!.publicRepos > 0 && (
237 <span>
238 <strong>{stats!.publicRepos.toLocaleString()}</strong>
239 {stats!.publicRepos === 1 ? " repo" : " repos"}
240 </span>
241 )}
242 {stats!.publicRepos !== undefined &&
243 stats!.publicRepos > 0 &&
244 stats!.users !== undefined &&
245 stats!.users > 0 && <span class="landing-stats-sep">·</span>}
246 {stats!.users !== undefined && stats!.users > 0 && (
247 <span>
248 <strong>{stats!.users.toLocaleString()}</strong>
249 {stats!.users === 1 ? " developer" : " developers"}
250 </span>
251 )}
252 <span class="landing-stats-sep">·</span>
4c47454Claude253 <span>
958d26aClaude254 <strong>100%</strong> AI-native
4c47454Claude255 </span>
958d26aClaude256 </p>
257 )}
258 </div>
c963db5Claude259 </section>
c475ee6Claude260
534f04aClaude261 {/* ---------- Block M1 — Live-now demo feed ---------- */}
262 <LiveNowSection
263 queued={liveQueued}
264 merges={liveMerges}
265 reviews={liveReviews}
266 reviewCount={liveReviewCount}
267 feed={liveEntries}
268 />
269
52ad8b1Claude270 {/* ---------- L4 social-proof counters (animated count-up) ---------- */}
271 {tiles && (
272 <section class="landing-counters" aria-label="Gluecron live counters">
273 <div class="landing-counters-grid">
274 {tiles.map((t) => (
275 <div class="landing-counter">
276 <div
277 class="landing-counter-num"
278 data-counter-target={String(t.value)}
279 data-counter-suffix={t.suffix ?? ""}
280 data-counter-prefix={t.prefix ?? ""}
281 >
282 {t.prefix ?? ""}
283 {t.value.toLocaleString()}
284 {t.suffix ?? ""}
285 </div>
286 <div class="landing-counter-label">{t.label}</div>
287 </div>
288 ))}
289 </div>
290 <script dangerouslySetInnerHTML={{ __html: landingCountersJs }} />
291 </section>
292 )}
293
5f2e749Claude294 {/* ---------- L10 — Three reasons to switch ---------- */}
295 <section class="landing-section landing-reasons" aria-label="Three reasons to switch">
296 <div class="section-header">
297 <div class="eyebrow">Three reasons to switch</div>
298 <h2>Built so Claude can do the work.</h2>
299 </div>
300 <div class="landing-reasons-grid">
301 <ReasonCard
302 icon={
303 <svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
304 <path d="M21 12.8A9 9 0 1 1 11.2 3a7 7 0 0 0 9.8 9.8z" />
305 </svg>
306 }
307 title="Toggle Sleep Mode"
308 body="Claude does the work overnight. You get a 9 AM digest of what shipped — PRs merged, deploys live, incidents triaged."
309 link={{ href: "/sleep-mode", label: "Turn on Sleep Mode" }}
310 />
311 <ReasonCard
312 icon={
313 <svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
314 <polyline points="4 17 10 11 4 5" />
315 <line x1="12" y1="19" x2="20" y2="19" />
316 </svg>
317 }
318 title="One command to migrate"
319 body="Drop a single curl into your shell. Gluecron rehosts your repo, your issues, your branches — no SaaS rip-and-replace project required."
320 extra={
321 <code class="landing-reasons-code">curl -sSL gluecron.com/install | bash</code>
322 }
323 link={{ href: "/import", label: "Or import from GitHub" }}
324 />
325 <ReasonCard
326 icon={
327 <svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
328 <polygon points="5 3 19 12 5 21 5 3" />
329 </svg>
330 }
331 title="Open the demo, watch it work"
332 body="The demo repo is real. Label an issue, hit refresh, see Claude open the PR. Inspect the diff. Approve the merge. Zero setup, zero credit card."
333 link={{ href: "/demo", label: "Open the live demo" }}
334 />
335 </div>
336 </section>
337
c963db5Claude338 {/* ---------- Capability strip — uppercase tracked grid (crontech-style) ---------- */}
339 <section class="landing-caps">
340 <div class="landing-caps-grid">
341 <span class="landing-cap">Claude-powered AI</span>
342 <span class="landing-cap">Spec-to-PR</span>
343 <span class="landing-cap">Auto-repair</span>
344 <span class="landing-cap">Real-time gates</span>
345 <span class="landing-cap">MCP-native</span>
346 <span class="landing-cap">Workflow runner</span>
347 <span class="landing-cap">Self-hostable</span>
348 <span class="landing-cap">Branch protection</span>
349 <span class="landing-cap">Bun + Hono</span>
350 <span class="landing-cap">Drizzle + Postgres</span>
351 <span class="landing-cap">JSX server-rendered</span>
352 <span class="landing-cap">Type-safe end to end</span>
c475ee6Claude353 </div>
958d26aClaude354 </section>
355
c963db5Claude356 {/* ---------- Big stat row (crontech-style hero closer) ---------- */}
357 <section class="landing-bigstats">
358 <div class="landing-bigstats-grid">
359 <div class="landing-bigstat">
360 <div class="landing-bigstat-num">Claude-powered</div>
361 <div class="landing-bigstat-label">The best AI, native</div>
362 </div>
363 <div class="landing-bigstat">
364 <div class="landing-bigstat-num">Self-hosted</div>
365 <div class="landing-bigstat-label">On your hardware</div>
366 </div>
367 <div class="landing-bigstat">
368 <div class="landing-bigstat-num">MCP-native</div>
369 <div class="landing-bigstat-label">Claude · Cursor · Code</div>
370 </div>
371 <div class="landing-bigstat">
372 <div class="landing-bigstat-num">Real-time</div>
373 <div class="landing-bigstat-label">SSE everywhere</div>
374 </div>
958d26aClaude375 </div>
4c47454Claude376 </section>
377
378 {/* ---------- Feature grid ---------- */}
958d26aClaude379 <section class="landing-section">
380 <div class="section-header">
381 <div class="eyebrow">The platform</div>
382 <h2>An IDE for your repo, not just a host.</h2>
383 <p>
384 Gluecron ships the surfaces GitHub charges extra for, and the
385 ones it never built. AI is a teammate with its own commits, not
386 a sidebar.
2b821b7Claude387 </p>
388 </div>
4c47454Claude389
958d26aClaude390 <div class="landing-features stagger">
391 <FeatureCard
392 icon={
393 <svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round">
394 <path d="M12 3l1.9 4.6L18.5 9l-3.6 3 1 4.8L12 14.5 8.1 16.8l1-4.8L5.5 9l4.6-1.4z" />
395 </svg>
396 }
397 title="AI as a teammate"
398 desc="Spec-to-PR drafts entire features from plain English. Auto-explain reviews every diff. The AI commits with its own bot account, visible in your history."
399 />
400 <FeatureCard
401 icon={
402 <svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round">
403 <path d="M12 2.5l8 3.5v6c0 5-3.5 8.5-8 9.5-4.5-1-8-4.5-8-9.5v-6z" />
404 <path d="M9 12l2 2 4-4" />
405 </svg>
406 }
407 title="Quality gate that learns"
408 desc="GateTest scans every push. Auto-repair fixes regressions before you see them. Required checks block bad PRs from merging. Your software self-corrects."
409 />
410 <FeatureCard
411 icon={
412 <svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round">
413 <path d="M13 2L4 14h7l-1 8 9-12h-7z" />
414 </svg>
415 }
416 title="Real-time everything"
417 desc="Live workflow logs over SSE. Live PR review presence. Live deploys you watch happen. No polling, no refresh, no waiting on a CI tab."
418 />
419 <FeatureCard
420 icon={
421 <svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round">
422 <rect x="3" y="3" width="18" height="18" rx="3" />
423 <path d="M3 9h18M9 21V9" />
424 </svg>
425 }
426 title="Workflow runner"
427 desc="Drop a yaml in `.gluecron/workflows/` and it runs on every push. Cron triggers, secret substitution, matrix runs, artifacts. No SaaS provider in the loop."
428 />
429 <FeatureCard
430 icon={
431 <svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round">
432 <circle cx="12" cy="12" r="9" />
433 <path d="M3 12h18M12 3a14 14 0 010 18M12 3a14 14 0 000 18" />
434 </svg>
435 }
436 title="MCP-native"
437 desc="Claude, Cursor, Code — they speak Model Context Protocol. Gluecron exposes search, file read, issues, codebase explain as MCP tools by default."
438 />
439 <FeatureCard
440 icon={
441 <svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round">
442 <path d="M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
443 <path d="M12 7v5l3 2" />
444 </svg>
445 }
446 title="Yours, on your hardware"
447 desc="Single-binary Bun runtime. Postgres + bare git repos on a volume. Deploy to Fly, Railway, your own VPS. No vendor lock, no surprise bills."
448 />
2b821b7Claude449 </div>
958d26aClaude450 </section>
2b821b7Claude451
958d26aClaude452 {/* ---------- Workflow walkthrough ---------- */}
453 <section class="landing-section landing-walk">
454 <div class="section-header">
455 <div class="eyebrow">How it works</div>
456 <h2>Push code. Watch it ship.</h2>
457 <p>
458 Every push triggers the same pipeline whether the commit came
459 from you, from CI, or from an AI agent.
4c47454Claude460 </p>
461 </div>
958d26aClaude462
463 <div class="landing-walk-grid">
464 <WalkStep n="01" title="Push" desc="git push to gluecron — Smart-HTTP, SSH, or via the web editor." />
465 <WalkStep n="02" title="Gate" desc="GateTest runs. Secret scanner runs. AI security review posts inline comments." />
466 <WalkStep n="03" title="Repair" desc="If a gate fails, auto-repair tries to fix it. New commit gets re-gated." />
467 <WalkStep n="04" title="Ship" desc="Green push to default branch fires deploy webhook. Crontech, Fly, your prod." />
468 </div>
4c47454Claude469 </section>
2b821b7Claude470
4c47454Claude471 {/* ---------- Terminal block ---------- */}
958d26aClaude472 <section class="landing-section landing-terminal-section">
473 <div class="landing-terminal-wrap">
474 <div class="landing-terminal" role="img" aria-label="Example git push to gluecron with passing gates">
475 <div class="landing-terminal-chrome">
476 <span class="landing-terminal-dot landing-terminal-dot-r" />
477 <span class="landing-terminal-dot landing-terminal-dot-y" />
478 <span class="landing-terminal-dot landing-terminal-dot-g" />
479 <span class="landing-terminal-title">~/your-repo &mdash; zsh</span>
480 </div>
481 <div class="landing-terminal-body">
482 <div class="landing-term-line">
483 <span class="landing-term-prompt">$</span>
484 <span>git remote add gluecron https://gluecron.com/you/your-repo.git</span>
485 </div>
486 <div class="landing-term-line">
487 <span class="landing-term-prompt">$</span>
488 <span>git push -u gluecron main</span>
489 </div>
490 <div class="landing-term-line landing-term-out">
491 <span class="landing-term-meta">remote:</span>
492 <span>Resolving deltas… 100% (24/24)</span>
493 </div>
494 <div class="landing-term-line landing-term-out landing-term-ok-line">
495 <span class="landing-term-ok">{"✓"}</span>
496 <span>pushed to gluecron.com/you/your-repo</span>
497 </div>
498 <div class="landing-term-line landing-term-out landing-term-ok-line">
499 <span class="landing-term-ok">{"✓"}</span>
500 <span>GateTest passed (12 rules, 0 violations)</span>
501 </div>
502 <div class="landing-term-line landing-term-out landing-term-ok-line">
503 <span class="landing-term-ok">{"✓"}</span>
504 <span>AI review posted (2 suggestions, 0 blockers)</span>
505 </div>
506 <div class="landing-term-line landing-term-out landing-term-ok-line">
507 <span class="landing-term-ok">{"✓"}</span>
508 <span>deployed to your-repo.gluecron.com <span class="landing-term-meta">(4.1s)</span></span>
509 </div>
510 <div class="landing-term-line landing-term-cursor">
511 <span class="landing-term-prompt">$</span>
512 <span class="landing-term-blink">▍</span>
513 </div>
514 </div>
4c47454Claude515 </div>
958d26aClaude516 </div>
517 </section>
518
519 {/* ---------- Comparison ---------- */}
520 <section class="landing-section">
521 <div class="section-header">
522 <div class="eyebrow">vs the incumbent</div>
523 <h2>Everything GitHub charges for. And the parts they didn't build.</h2>
524 </div>
525
526 <div class="landing-compare">
527 <CompareRow feature="Git hosting + Smart-HTTP push" them="✓" us="✓" />
528 <CompareRow feature="Issues, PRs, code review" them="✓" us="✓" />
529 <CompareRow feature="Workflow runner (Actions-equivalent)" them="paid minutes" us="self-hosted, unmetered" highlight />
530 <CompareRow feature="AI code review on every PR" them="Copilot subscription" us="built in" highlight />
531 <CompareRow feature="Spec-to-PR (NL feature → draft PR)" them="—" us="✓" highlight />
532 <CompareRow feature="Auto-repair on failed gates" them="—" us="✓" highlight />
533 <CompareRow feature="Real-time SSE for logs + PRs" them="polling" us="streaming" highlight />
534 <CompareRow feature="MCP server (Claude / Cursor)" them="—" us="✓" highlight />
535 <CompareRow feature="Self-host on your own infra" them="enterprise tier" us="single binary" highlight />
536 <CompareRow feature="Pre-receive policy enforcement" them="rulesets (GHE)" us="✓" />
537 </div>
538 </section>
539
540 {/* ---------- Pricing teaser ---------- */}
541 <section class="landing-section">
542 <div class="section-header">
543 <div class="eyebrow">Pricing</div>
544 <h2>Free to start. Honest at scale.</h2>
545 <p>
546 Self-hosting is free forever. Hosted plans price the AI calls,
547 not the seats.
548 </p>
549 </div>
550
551 <div class="landing-pricing">
552 <PricingCard
553 tier="Free"
554 price="$0"
555 cadence="forever"
556 desc="For personal projects + open source. Public + private repos, full AI suite, fair quotas."
557 features={["Unlimited public repos", "3 private repos", "5K AI calls / mo", "Community support"]}
558 cta="Start free"
559 href="/register"
560 />
561 <PricingCard
562 tier="Pro"
563 price="$12"
564 cadence="per user / mo"
565 desc="For working developers. Lifts every quota, adds priority routing, no Gluecron branding on deploys."
566 features={["Unlimited private repos", "100K AI calls / mo", "Priority queue", "Custom domains"]}
567 cta="Go Pro"
568 href="/settings/billing"
569 highlight
570 />
571 <PricingCard
572 tier="Team"
573 price="Talk to us"
574 cadence="custom"
575 desc="For orgs running production on Gluecron. SSO, audit retention, enterprise SLA, on-prem."
576 features={["SSO + SCIM", "On-prem deploy", "Dedicated capacity", "24/7 incident response"]}
577 cta="Contact"
578 href="mailto:hello@gluecron.com"
579 />
580 </div>
581 </section>
582
5f2e749Claude583 {/* ---------- L10 — "How is this different?" pull-quote ---------- */}
584 <section class="landing-pullquote-section" aria-label="How is this different from GitHub?">
585 <figure class="landing-pullquote">
586 <div class="landing-pullquote-eyebrow">How is this different from GitHub?</div>
587 <blockquote class="landing-pullquote-text">
588 Every other host bolts AI on as a sidecar. Gluecron is the first
589 git host where Claude is a first-class developer. Built to be
590 operated by AI agents, not just augmented by them.
591 </blockquote>
592 <a href="/vs-github" class="landing-pullquote-link">
593 See the full comparison
594 <span class="landing-cta-arrow" aria-hidden="true">{"→"}</span>
595 </a>
596 </figure>
597 </section>
598
958d26aClaude599 {/* ---------- Closing CTA ---------- */}
600 <section class="landing-cta-section">
601 <div class="landing-cta-card">
602 <div class="landing-cta-bg" aria-hidden="true" />
603 <div class="eyebrow">Ready when you are</div>
604 <h2 class="landing-cta-title">
605 Stop maintaining the platform.<br />
606 <span class="gradient-text">Start shipping the product.</span>
607 </h2>
608 <p class="landing-cta-sub">
609 Free to start, self-hosted-friendly, MCP-native. Migrate from
610 GitHub in one click.
611 </p>
612 <div class="landing-cta-buttons">
613 <a href="/register" class="btn btn-primary btn-xl">
614 Create your account
615 <span class="landing-cta-arrow" aria-hidden="true">{"→"}</span>
616 </a>
617 <a href="/import" class="btn btn-ghost btn-xl">
618 Migrate a repo
619 </a>
4c47454Claude620 </div>
621 </div>
622 </section>
5f2e749Claude623
624 {/* L10 — clipboard copy script for the hero install snippet. */}
625 <script dangerouslySetInnerHTML={{ __html: landingCopyJs }} />
4c47454Claude626 </div>
2b821b7Claude627 </>
628 );
629};
630
958d26aClaude631const FeatureCard: FC<{ icon: any; title: string; desc: string }> = ({
632 icon,
633 title,
634 desc,
635}) => (
636 <div class="landing-feature">
637 <div class="landing-feature-icon" aria-hidden="true">
638 {icon}
639 </div>
640 <h3 class="landing-feature-title">{title}</h3>
641 <p class="landing-feature-desc">{desc}</p>
642 </div>
643);
644
5f2e749Claude645// Block L10 — "Three reasons to switch" column.
646const ReasonCard: FC<{
647 icon: any;
648 title: string;
649 body: string;
650 link: { href: string; label: string };
651 extra?: any;
652}> = ({ icon, title, body, link, extra }) => (
653 <div class="landing-reason">
654 <div class="landing-reason-icon" aria-hidden="true">
655 {icon}
656 </div>
657 <h3 class="landing-reason-title">{title}</h3>
658 <p class="landing-reason-body">{body}</p>
659 {extra}
660 <a href={link.href} class="landing-reason-link">
661 {link.label}
662 <span class="landing-cta-arrow" aria-hidden="true">{"→"}</span>
663 </a>
664 </div>
665);
666
958d26aClaude667const WalkStep: FC<{ n: string; title: string; desc: string }> = ({
668 n,
669 title,
670 desc,
671}) => (
672 <div class="landing-walk-step">
673 <div class="landing-walk-num">{n}</div>
674 <h3 class="landing-walk-title">{title}</h3>
675 <p class="landing-walk-desc">{desc}</p>
676 </div>
677);
678
679const CompareRow: FC<{
680 feature: string;
681 them: string;
682 us: string;
683 highlight?: boolean;
684}> = ({ feature, them, us, highlight }) => (
685 <div class={`landing-compare-row${highlight ? " landing-compare-hl" : ""}`}>
686 <div class="landing-compare-feature">{feature}</div>
687 <div class="landing-compare-them">{them}</div>
688 <div class="landing-compare-us">{us === "✓" ? "✓" : us}</div>
689 </div>
690);
691
692const PricingCard: FC<{
693 tier: string;
694 price: string;
695 cadence: string;
696 desc: string;
697 features: string[];
698 cta: string;
699 href: string;
700 highlight?: boolean;
701}> = ({ tier, price, cadence, desc, features, cta, href, highlight }) => (
702 <div class={`landing-price-card${highlight ? " landing-price-hl" : ""}`}>
703 {highlight && <div class="landing-price-badge">Most popular</div>}
704 <div class="landing-price-tier">{tier}</div>
705 <div class="landing-price-amount">
706 <span class="landing-price-num">{price}</span>
707 <span class="landing-price-cad">{cadence}</span>
708 </div>
709 <p class="landing-price-desc">{desc}</p>
710 <ul class="landing-price-features">
711 {features.map((f) => (
712 <li>
713 <span class="landing-price-check" aria-hidden="true">{"✓"}</span>
714 {f}
715 </li>
716 ))}
717 </ul>
718 <a
719 href={href}
720 class={`btn ${highlight ? "btn-primary" : "btn-secondary"} btn-block landing-price-cta`}
721 >
722 {cta}
723 </a>
724 </div>
725);
726
52ad8b1Claude727// ─────────────────────────────────────────────────────────────────
728// Block L4 — social-proof tile builder.
729//
730// Pure: takes the cached PublicStats payload and emits the six
731// landing-page tiles in render order. Exported so tests / future
732// surfaces (dashboard, /about, …) can share the exact same copy.
733// ─────────────────────────────────────────────────────────────────
734
735export interface SocialProofTile {
736 label: string;
737 value: number;
738 prefix?: string;
739 suffix?: string;
740}
741
742export function buildSocialProofTiles(s: PublicStats): SocialProofTile[] {
743 return [
744 { label: "Public repos", value: s.totalPublicRepos },
745 { label: "Developers", value: s.totalUsers },
746 {
747 label: "PRs auto-merged this week",
748 value: s.weeklyPrsAutoMerged,
749 },
750 {
751 label: "Issues built by AI this week",
752 value: s.weeklyIssuesBuiltByAi,
753 },
754 {
755 label: "Deploys shipped this week",
756 value: s.weeklyDeploysShipped,
757 },
758 {
759 label: "Hours saved this week",
760 // Round to whole hours for the tile — the precise 0.1 figure
761 // lives on the dashboard widget; the marketing surface keeps
762 // the number scannable.
763 value: Math.round(s.weeklyHoursSaved),
764 prefix: "~",
765 suffix: "h",
766 },
767 ];
768}
769
534f04aClaude770// ─────────────────────────────────────────────────────────────────
771// Block M1 — Live-now demo feed.
772//
773// A 4-tile row inserted between the hero and the L4 counters tile
774// section, surfacing real autopilot activity from the seeded `demo`
775// owner's repos:
776// 1. Issues queued for AI build (ai:build label, open)
777// 2. PRs auto-merged in the last 24h
778// 3. AI reviews posted today (count + latest 3)
779// 4. Combined activity feed (last 10)
780//
781// SSR-renders an initial snapshot, then re-fetches every 30s via the
782// L3 JSON endpoints so the page feels alive without a websocket.
783// Pure presentational; the route layer owns the DB reads.
784// ─────────────────────────────────────────────────────────────────
785
786/**
787 * Render an ISO timestamp (or Date) as a coarse "about N units ago"
788 * string. Tolerates strings, Dates, NaN, and future timestamps.
789 *
790 * Exported for unit testing.
791 */
792export function relativeTimeFromNow(
793 value: string | Date | number | null | undefined,
794 now: number = Date.now()
795): string {
796 if (value === null || value === undefined) return "just now";
797 let t: number;
798 if (value instanceof Date) {
799 t = value.getTime();
800 } else if (typeof value === "number") {
801 t = value;
802 } else {
803 t = new Date(value).getTime();
804 }
805 if (!Number.isFinite(t)) return "just now";
806 const delta = now - t;
807 // Future timestamps (clock skew) — treat as "just now" rather than
808 // surfacing a confusing negative.
809 if (delta < 0) return "just now";
810 const s = Math.floor(delta / 1000);
811 if (s < 60) return "just now";
812 const m = Math.floor(s / 60);
813 if (m < 60) return `about ${m} minute${m === 1 ? "" : "s"} ago`;
814 const h = Math.floor(m / 60);
815 if (h < 24) return `about ${h} hour${h === 1 ? "" : "s"} ago`;
816 const d = Math.floor(h / 24);
817 return `about ${d} day${d === 1 ? "" : "s"} ago`;
818}
819
820function feedEntryId(e: LandingLiveFeedEntry): string {
821 const at = e.at instanceof Date ? e.at.toISOString() : String(e.at);
822 return `${e.kind}|${e.repo}|${e.ref.type}|${e.ref.number}|${at}`;
823}
824
825function feedEntryLabel(kind: LandingLiveFeedEntry["kind"]): string {
826 switch (kind) {
827 case "auto_merge.merged":
828 return "auto-merged";
829 case "ai_build.dispatched":
830 return "AI-build queued";
831 case "ai_review.posted":
832 return "AI review posted";
833 }
834}
835
836interface LiveNowSectionProps {
837 queued: LandingLiveFeedQueued[];
838 merges: LandingLiveFeedMerge[];
839 reviews: LandingLiveFeedReview[];
840 reviewCount: number;
841 feed: LandingLiveFeedEntry[];
842}
843
844const LiveNowSection: FC<LiveNowSectionProps> = ({
845 queued,
846 merges,
847 reviews,
848 reviewCount,
849 feed,
850}) => {
851 return (
852 <section class="landing-livenow" aria-labelledby="landing-livenow-h">
853 <div class="landing-livenow-head">
854 <div class="landing-livenow-eyebrow">
855 <span class="landing-livenow-pulse" aria-hidden="true" />
856 Live now
857 </div>
858 <h2 id="landing-livenow-h" class="landing-livenow-title">
859 Claude is working on demo repos as you read this.
860 </h2>
861 <p class="landing-livenow-sub">
862 Every card below is real data from the public{" "}
863 <code>{DEMO_USERNAME}/*</code> repos. Refreshes every 30 seconds.
864 </p>
865 </div>
866
867 <div class="landing-livenow-grid" data-livenow-grid>
868 {/* Card 1 — queued issues */}
869 <article class="landing-livecard" aria-labelledby="lc-queued-h">
870 <header class="landing-livecard-head">
871 <span class="landing-livecard-dot" aria-hidden="true" />
872 <h3 id="lc-queued-h" class="landing-livecard-title">
873 Issues queued for AI
874 </h3>
875 </header>
876 <ul class="landing-livecard-list" data-livecard="queued">
877 {queued.length === 0 ? (
878 <li class="landing-livecard-empty">
879 No queued AI builds — quiet right now.
880 </li>
881 ) : (
882 queued.slice(0, 3).map((i) => (
883 <li
884 class="landing-livecard-row"
885 data-row-id={`queued|${i.repo}|${i.number}`}
886 >
887 <a
888 class="landing-livecard-link"
889 href={`/${DEMO_USERNAME}/${i.repo}/issues/${i.number}`}
890 >
891 <span class="landing-livecard-num">#{i.number}</span>{" "}
892 <span class="landing-livecard-title-text">{i.title}</span>
893 </a>
894 <div class="landing-livecard-meta">
895 <span class="landing-livecard-repo">{i.repo}</span>
896 </div>
897 </li>
898 ))
899 )}
900 </ul>
901 </article>
902
903 {/* Card 2 — recently merged */}
904 <article class="landing-livecard" aria-labelledby="lc-merges-h">
905 <header class="landing-livecard-head">
906 <span class="landing-livecard-dot" aria-hidden="true" />
907 <h3 id="lc-merges-h" class="landing-livecard-title">
908 Recently merged by AI
909 </h3>
910 </header>
911 <ul class="landing-livecard-list" data-livecard="merges">
912 {merges.length === 0 ? (
913 <li class="landing-livecard-empty">
914 No auto-merges in the last 24h.
915 </li>
916 ) : (
917 merges.slice(0, 3).map((m) => (
918 <li
919 class="landing-livecard-row"
920 data-row-id={`merges|${m.repo}|${m.number}`}
921 >
922 <a
923 class="landing-livecard-link"
924 href={`/${DEMO_USERNAME}/${m.repo}/pulls/${m.number}`}
925 >
926 <span class="landing-livecard-num">#{m.number}</span>{" "}
927 <span class="landing-livecard-title-text">{m.title}</span>
928 </a>
929 <div class="landing-livecard-meta">
930 AI merged in{" "}
931 <span class="landing-livecard-repo">{m.repo}</span>{" "}
932 <span
933 class="landing-livecard-rel"
934 data-rel={
935 m.mergedAt instanceof Date
936 ? m.mergedAt.toISOString()
937 : String(m.mergedAt)
938 }
939 >
940 {relativeTimeFromNow(m.mergedAt)}
941 </span>
942 </div>
943 </li>
944 ))
945 )}
946 </ul>
947 </article>
948
949 {/* Card 3 — AI reviews */}
950 <article class="landing-livecard" aria-labelledby="lc-reviews-h">
951 <header class="landing-livecard-head">
952 <span class="landing-livecard-dot" aria-hidden="true" />
953 <h3 id="lc-reviews-h" class="landing-livecard-title">
954 AI reviews posted
955 </h3>
956 </header>
957 <div class="landing-livecard-bignum">
958 <span
959 class="landing-livecard-bignum-n"
960 data-livecard-count="reviews"
961 data-tick-target={String(reviewCount)}
962 >
963 {reviewCount.toLocaleString()}
964 </span>
965 <span class="landing-livecard-bignum-label">reviews today</span>
966 </div>
967 <ul class="landing-livecard-list" data-livecard="reviews">
968 {reviews.length === 0 ? (
969 <li class="landing-livecard-empty">
970 No AI reviews in the last 24h.
971 </li>
972 ) : (
973 reviews.slice(0, 3).map((r) => (
974 <li
975 class="landing-livecard-row"
976 data-row-id={`reviews|${r.repo}|${r.prNumber}`}
977 >
978 <a
979 class="landing-livecard-link"
980 href={`/${DEMO_USERNAME}/${r.repo}/pulls/${r.prNumber}`}
981 >
982 <span class="landing-livecard-num">#{r.prNumber}</span>{" "}
983 <span class="landing-livecard-snippet">
984 {r.commentSnippet}
985 </span>
986 </a>
987 <div class="landing-livecard-meta">
988 <span class="landing-livecard-repo">{r.repo}</span>
989 </div>
990 </li>
991 ))
992 )}
993 </ul>
994 </article>
995
996 {/* Card 4 — activity feed */}
997 <article class="landing-livecard" aria-labelledby="lc-feed-h">
998 <header class="landing-livecard-head">
999 <span class="landing-livecard-dot" aria-hidden="true" />
1000 <h3 id="lc-feed-h" class="landing-livecard-title">
1001 Activity feed
1002 </h3>
1003 </header>
1004 <ul class="landing-livecard-list landing-livecard-feed" data-livecard="feed">
1005 {feed.length === 0 ? (
1006 <li class="landing-livecard-empty">
1007 Quiet right now — check back in a minute.
1008 </li>
1009 ) : (
1010 feed.slice(0, 10).map((e) => {
1011 const path = e.ref.type === "pr" ? "pulls" : "issues";
1012 const id = feedEntryId(e);
1013 return (
1014 <li class="landing-livecard-feedrow" data-row-id={id}>
1015 <span
1016 class={`landing-livecard-kind landing-livecard-kind-${e.kind.replace(/\./g, "-")}`}
1017 >
1018 {feedEntryLabel(e.kind)}
1019 </span>{" "}
1020 <a
1021 class="landing-livecard-link"
1022 href={`/${DEMO_USERNAME}/${e.repo}/${path}/${e.ref.number}`}
1023 >
1024 {e.repo} #{e.ref.number}
1025 </a>{" "}
1026 <span
1027 class="landing-livecard-rel"
1028 data-rel={
1029 e.at instanceof Date ? e.at.toISOString() : String(e.at)
1030 }
1031 >
1032 {relativeTimeFromNow(e.at)}
1033 </span>
1034 </li>
1035 );
1036 })
1037 )}
1038 </ul>
1039 </article>
1040 </div>
1041
1042 <div class="landing-livenow-cta">
1043 <span class="landing-livenow-cta-text">
1044 Want this for your repos?
1045 </span>
1046 <a class="landing-livenow-cta-link" href="/register">
1047 Sign up free
1048 <span class="landing-cta-arrow" aria-hidden="true">{"→"}</span>
1049 </a>
1050 <span class="landing-livenow-cta-sep" aria-hidden="true">·</span>
1051 <a class="landing-livenow-cta-link" href="/demo">
1052 Try the live demo
1053 <span class="landing-cta-arrow" aria-hidden="true">{"→"}</span>
1054 </a>
1055 </div>
1056
1057 <script dangerouslySetInnerHTML={{ __html: liveNowJs }} />
1058 </section>
1059 );
1060};
1061
1062// Inline poller. Plain JS so we don't ship a separate bundle. Hits the
1063// four L3 JSON endpoints every 30s, re-renders the four cards, ticks
1064// the big number, refreshes relative timestamps, flashes new rows.
1065const liveNowJs = `
1066(function(){
1067try{
1068 var DEMO=${JSON.stringify(DEMO_USERNAME)};
1069 var INTERVAL=30000;
1070 function esc(s){return String(s==null?'':s).replace(/[&<>"']/g,function(c){return {'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#39;'}[c];});}
1071 function rel(v){
1072 if(v==null) return 'just now';
1073 var t=(v instanceof Date)?v.getTime():(typeof v==='number'?v:new Date(v).getTime());
1074 if(!isFinite(t)) return 'just now';
1075 var d=Date.now()-t;
1076 if(d<0) return 'just now';
1077 var s=Math.floor(d/1000);
1078 if(s<60) return 'just now';
1079 var m=Math.floor(s/60);
1080 if(m<60) return 'about '+m+' minute'+(m===1?'':'s')+' ago';
1081 var h=Math.floor(m/60);
1082 if(h<24) return 'about '+h+' hour'+(h===1?'':'s')+' ago';
1083 var dd=Math.floor(h/24);
1084 return 'about '+dd+' day'+(dd===1?'':'s')+' ago';
1085 }
1086 function tickNumber(el,target){
1087 if(!el) return;
1088 var start=parseInt(el.getAttribute('data-tick-current')||'0',10)||0;
1089 if(start===target){el.textContent=target.toLocaleString();el.setAttribute('data-tick-current',String(target));return;}
1090 var dur=800,t0=performance.now();
1091 function step(now){
1092 var p=Math.min(1,(now-t0)/dur);
1093 var eased=1-Math.pow(1-p,3);
1094 var v=Math.round(start+(target-start)*eased);
1095 el.textContent=v.toLocaleString();
1096 if(p<1) requestAnimationFrame(step); else el.setAttribute('data-tick-current',String(target));
1097 }
1098 requestAnimationFrame(step);
1099 }
1100 function flashRow(li){
1101 if(!li) return;
1102 li.classList.add('landing-livecard-flash');
1103 setTimeout(function(){li.classList.remove('landing-livecard-flash');},1100);
1104 }
1105 function diffMount(ul,newHtml,newIds){
1106 if(!ul) return;
1107 var prev={};
1108 var nodes=ul.querySelectorAll('[data-row-id]');
1109 for(var i=0;i<nodes.length;i++){prev[nodes[i].getAttribute('data-row-id')]=true;}
1110 ul.innerHTML=newHtml;
1111 var fresh=ul.querySelectorAll('[data-row-id]');
1112 for(var j=0;j<fresh.length;j++){
1113 var id=fresh[j].getAttribute('data-row-id');
1114 if(id && !prev[id]) flashRow(fresh[j]);
1115 }
1116 }
1117 function pollQueued(){
1118 return fetch('/api/v2/demo/queued',{credentials:'omit'}).then(function(r){return r.json();}).then(function(d){
1119 var ul=document.querySelector('[data-livecard="queued"]');if(!ul) return;
1120 var items=(d&&d.items)||[];
1121 if(items.length===0){ul.innerHTML='<li class="landing-livecard-empty">No queued AI builds — quiet right now.</li>';return;}
1122 var ids=[];
1123 var html=items.slice(0,3).map(function(i){
1124 var id='queued|'+i.repo+'|'+i.number;ids.push(id);
1125 return '<li class="landing-livecard-row" data-row-id="'+esc(id)+'">'+
1126 '<a class="landing-livecard-link" href="/'+esc(DEMO)+'/'+esc(i.repo)+'/issues/'+i.number+'">'+
1127 '<span class="landing-livecard-num">#'+i.number+'</span> '+
1128 '<span class="landing-livecard-title-text">'+esc(i.title)+'</span></a>'+
1129 '<div class="landing-livecard-meta"><span class="landing-livecard-repo">'+esc(i.repo)+'</span></div></li>';
1130 }).join('');
1131 diffMount(ul,html,ids);
1132 }).catch(function(){});
1133 }
1134 function pollMerges(){
1135 return fetch('/api/v2/demo/merges',{credentials:'omit'}).then(function(r){return r.json();}).then(function(d){
1136 var ul=document.querySelector('[data-livecard="merges"]');if(!ul) return;
1137 var items=(d&&d.items)||[];
1138 if(items.length===0){ul.innerHTML='<li class="landing-livecard-empty">No auto-merges in the last 24h.</li>';return;}
1139 var ids=[];
1140 var html=items.slice(0,3).map(function(m){
1141 var id='merges|'+m.repo+'|'+m.number;ids.push(id);
1142 return '<li class="landing-livecard-row" data-row-id="'+esc(id)+'">'+
1143 '<a class="landing-livecard-link" href="/'+esc(DEMO)+'/'+esc(m.repo)+'/pulls/'+m.number+'">'+
1144 '<span class="landing-livecard-num">#'+m.number+'</span> '+
1145 '<span class="landing-livecard-title-text">'+esc(m.title)+'</span></a>'+
1146 '<div class="landing-livecard-meta">AI merged in <span class="landing-livecard-repo">'+esc(m.repo)+'</span> '+
1147 '<span class="landing-livecard-rel" data-rel="'+esc(m.mergedAt)+'">'+esc(rel(m.mergedAt))+'</span></div></li>';
1148 }).join('');
1149 diffMount(ul,html,ids);
1150 }).catch(function(){});
1151 }
1152 function pollReviews(){
1153 return fetch('/api/v2/demo/reviews',{credentials:'omit'}).then(function(r){return r.json();}).then(function(d){
1154 var ul=document.querySelector('[data-livecard="reviews"]');if(!ul) return;
1155 var bigEl=document.querySelector('[data-livecard-count="reviews"]');
1156 var n=(d&&typeof d.count==='number')?d.count:0;
1157 if(bigEl) tickNumber(bigEl,n);
1158 var items=(d&&d.items)||[];
1159 if(items.length===0){ul.innerHTML='<li class="landing-livecard-empty">No AI reviews in the last 24h.</li>';return;}
1160 var ids=[];
1161 var html=items.slice(0,3).map(function(r){
1162 var id='reviews|'+r.repo+'|'+r.prNumber;ids.push(id);
1163 return '<li class="landing-livecard-row" data-row-id="'+esc(id)+'">'+
1164 '<a class="landing-livecard-link" href="/'+esc(DEMO)+'/'+esc(r.repo)+'/pulls/'+r.prNumber+'">'+
1165 '<span class="landing-livecard-num">#'+r.prNumber+'</span> '+
1166 '<span class="landing-livecard-snippet">'+esc(r.commentSnippet)+'</span></a>'+
1167 '<div class="landing-livecard-meta"><span class="landing-livecard-repo">'+esc(r.repo)+'</span></div></li>';
1168 }).join('');
1169 diffMount(ul,html,ids);
1170 }).catch(function(){});
1171 }
1172 function pollFeed(){
1173 return fetch('/api/v2/demo/activity',{credentials:'omit'}).then(function(r){return r.json();}).then(function(d){
1174 var ul=document.querySelector('[data-livecard="feed"]');if(!ul) return;
1175 var entries=(d&&d.entries)||[];
1176 if(entries.length===0){ul.innerHTML='<li class="landing-livecard-empty">Quiet right now — check back in a minute.</li>';return;}
1177 var ids=[];
1178 var html=entries.slice(0,10).map(function(e){
1179 var path=(e.ref&&e.ref.type==='pr')?'pulls':'issues';
1180 var num=(e.ref&&e.ref.number)||0;
1181 var label=e.kind==='auto_merge.merged'?'auto-merged':(e.kind==='ai_build.dispatched'?'AI-build queued':'AI review posted');
1182 var kindCls=String(e.kind||'').replace(/\\./g,'-');
1183 var id=e.kind+'|'+e.repo+'|'+(e.ref&&e.ref.type)+'|'+num+'|'+e.at;ids.push(id);
1184 return '<li class="landing-livecard-feedrow" data-row-id="'+esc(id)+'">'+
1185 '<span class="landing-livecard-kind landing-livecard-kind-'+esc(kindCls)+'">'+esc(label)+'</span> '+
1186 '<a class="landing-livecard-link" href="/'+esc(DEMO)+'/'+esc(e.repo)+'/'+path+'/'+num+'">'+esc(e.repo)+' #'+num+'</a> '+
1187 '<span class="landing-livecard-rel" data-rel="'+esc(e.at)+'">'+esc(rel(e.at))+'</span></li>';
1188 }).join('');
1189 diffMount(ul,html,ids);
1190 }).catch(function(){});
1191 }
1192 function refreshRel(){
1193 var spans=document.querySelectorAll('.landing-livecard-rel[data-rel]');
1194 for(var i=0;i<spans.length;i++){
1195 spans[i].textContent=rel(spans[i].getAttribute('data-rel'));
1196 }
1197 }
1198 function tickAll(){pollQueued();pollMerges();pollReviews();pollFeed();}
1199 // Initial counter tick (count-up from 0) on first paint.
1200 var bigEl0=document.querySelector('[data-livecard-count="reviews"]');
1201 if(bigEl0){
1202 var target=parseInt(bigEl0.getAttribute('data-tick-target')||'0',10)||0;
1203 bigEl0.textContent='0';
1204 tickNumber(bigEl0,target);
1205 }
1206 refreshRel();
1207 setInterval(tickAll,INTERVAL);
1208 setInterval(refreshRel,INTERVAL);
1209 document.addEventListener('visibilitychange',function(){
1210 if(document.visibilityState==='visible'){tickAll();refreshRel();}
1211 });
1212}catch(_){}})();
1213`.trim();
1214
4c47454Claude1215// Backwards-compatible default — web.tsx imports `LandingPage`.
1216export const LandingPage: FC<LandingPageProps> = (props) => (
1217 <LandingHero {...props} />
2b821b7Claude1218);
1219
4c47454Claude1220export default LandingPage;
1221
2b821b7Claude1222const landingCss = `
958d26aClaude1223 /* ============================================================ */
1224 /* Landing — Editorial-Technical 2026.05 */
1225 /* ============================================================ */
4c47454Claude1226 .landing-root {
1227 position: relative;
958d26aClaude1228 max-width: 1180px;
4c47454Claude1229 margin: 0 auto;
1230 padding: 0 16px;
958d26aClaude1231 }
1232 .landing-root > section { position: relative; }
1233
1234 /* ---------- Hero ---------- */
1235 .landing-hero {
1236 position: relative;
c475ee6Claude1237 padding: var(--s-16) 0 var(--s-20);
958d26aClaude1238 text-align: center;
4c47454Claude1239 overflow: hidden;
1240 }
c475ee6Claude1241 .landing-hero-blob-1 {
1242 animation: hero-blob-drift-1 18s var(--ease, ease) infinite alternate;
1243 }
1244 .landing-hero-blob-2 {
1245 animation: hero-blob-drift-2 22s var(--ease, ease) infinite alternate;
1246 }
1247 @keyframes hero-blob-drift-1 {
1248 0% { transform: translate(0, 0) scale(1); opacity: 0.55; }
1249 100% { transform: translate(8%, 6%) scale(1.18); opacity: 0.75; }
1250 }
1251 @keyframes hero-blob-drift-2 {
1252 0% { transform: translate(0, 0) scale(1); opacity: 0.40; }
1253 100% { transform: translate(-10%, -4%) scale(1.25); opacity: 0.60; }
1254 }
1255
1256 /* ---------- Hero product visual: live AI PR review card ---------- */
1257 .landing-hero-visual {
1258 position: relative;
1259 max-width: 760px;
1260 margin: var(--s-12) auto 0;
1261 padding: 0 16px;
1262 perspective: 1400px;
1263 z-index: 2;
1264 opacity: 0;
1265 animation: hero-visual-in 700ms var(--ease-out-expo, cubic-bezier(0.19, 1, 0.22, 1)) 400ms forwards;
1266 }
1267 @keyframes hero-visual-in {
1268 from { opacity: 0; transform: translateY(20px); }
1269 to { opacity: 1; transform: translateY(0); }
1270 }
1271 .hero-pr-card {
1272 position: relative;
1273 background: linear-gradient(180deg, rgba(15,17,26,0.96) 0%, rgba(8,9,15,0.96) 100%);
1274 border: 1px solid var(--border-strong);
1275 border-radius: var(--r-xl);
1276 overflow: hidden;
1277 text-align: left;
1278 box-shadow:
1279 0 30px 80px -20px rgba(0,0,0,0.65),
1280 0 0 0 1px rgba(140,109,255,0.18),
1281 0 0 60px -10px rgba(140,109,255,0.30);
1282 transform: rotateX(2deg) rotateY(-2deg);
1283 transition: transform 600ms var(--ease, ease);
1284 backdrop-filter: blur(12px);
1285 -webkit-backdrop-filter: blur(12px);
1286 }
1287 .landing-hero-visual:hover .hero-pr-card {
1288 transform: rotateX(0deg) rotateY(0deg);
1289 }
1290 .hero-pr-card::before {
1291 content: '';
1292 position: absolute;
1293 inset: 0;
1294 background: linear-gradient(135deg, rgba(140,109,255,0.10), transparent 35%, transparent 65%, rgba(54,197,214,0.08));
1295 pointer-events: none;
1296 }
1297 .hero-pr-header {
1298 display: flex;
1299 align-items: center;
1300 gap: 12px;
1301 padding: 14px 18px;
1302 border-bottom: 1px solid rgba(255,255,255,0.06);
1303 background: rgba(255,255,255,0.025);
1304 font-size: 13px;
1305 }
1306 .hero-pr-dot {
1307 width: 10px; height: 10px;
1308 border-radius: 50%;
1309 background: var(--green);
1310 box-shadow: 0 0 10px rgba(52,211,153,0.6);
1311 flex-shrink: 0;
1312 }
1313 .hero-pr-title {
1314 color: var(--text-strong);
1315 font-weight: 600;
1316 flex: 1;
1317 overflow: hidden;
1318 text-overflow: ellipsis;
1319 white-space: nowrap;
1320 }
1321 .hero-pr-num {
1322 color: var(--text-faint);
1323 font-family: var(--font-mono);
1324 font-weight: 500;
1325 margin-right: 8px;
1326 }
1327 .hero-pr-status {
1328 display: inline-flex;
1329 align-items: center;
1330 gap: 6px;
1331 padding: 3px 10px;
1332 border-radius: var(--r-full);
1333 background: var(--accent-gradient-faint);
1334 border: 1px solid rgba(140,109,255,0.30);
1335 color: var(--accent);
1336 font-family: var(--font-mono);
1337 font-size: 11px;
1338 letter-spacing: 0.04em;
1339 flex-shrink: 0;
1340 }
1341 .hero-pr-status-pulse {
1342 width: 6px; height: 6px;
1343 border-radius: 50%;
1344 background: var(--accent);
1345 box-shadow: 0 0 0 0 rgba(140,109,255,0.6);
1346 animation: hero-pulse 1.6s ease-out infinite;
1347 }
1348 @keyframes hero-pulse {
1349 0% { box-shadow: 0 0 0 0 rgba(140,109,255,0.55); }
1350 70% { box-shadow: 0 0 0 8px rgba(140,109,255,0); }
1351 100% { box-shadow: 0 0 0 0 rgba(140,109,255,0); }
1352 }
1353
1354 .hero-pr-body {
1355 padding: 0;
1356 }
1357 .hero-pr-file {
1358 display: flex;
1359 align-items: center;
1360 gap: 10px;
1361 padding: 10px 18px;
1362 border-bottom: 1px solid rgba(255,255,255,0.05);
1363 font-family: var(--font-mono);
1364 font-size: 12px;
1365 background: rgba(255,255,255,0.012);
1366 }
1367 .hero-pr-file-icon { color: var(--accent-2); }
1368 .hero-pr-file-name { color: var(--text); flex: 1; }
1369 .hero-pr-file-stats { display: inline-flex; gap: 8px; }
1370 .hero-pr-add { color: var(--green); font-weight: 600; }
1371 .hero-pr-del { color: var(--red); font-weight: 600; }
1372
1373 .hero-pr-diff {
1374 padding: 12px 18px;
1375 font-family: var(--font-mono);
1376 font-feature-settings: var(--mono-feat, 'calt');
1377 font-size: 12.5px;
1378 line-height: 1.7;
1379 color: rgba(237,237,242,0.85);
1380 overflow-x: auto;
1381 }
1382 .hero-pr-hunk {
1383 color: rgba(140,109,255,0.85);
1384 background: rgba(140,109,255,0.06);
1385 padding: 2px 8px;
1386 margin: 0 -8px 4px;
1387 border-radius: 4px;
1388 }
1389 .hero-pr-line-add {
1390 background: rgba(52,211,153,0.08);
1391 color: rgba(167,243,208,0.95);
1392 padding: 0 8px;
1393 margin: 0 -8px;
1394 border-left: 2px solid var(--green);
1395 padding-left: 8px;
1396 }
1397
1398 .hero-pr-comment {
1399 margin: 14px 18px;
1400 padding: 14px 16px;
1401 background: linear-gradient(135deg, rgba(140,109,255,0.08), rgba(54,197,214,0.05));
1402 border: 1px solid rgba(140,109,255,0.25);
1403 border-radius: var(--r-md);
1404 }
1405 .hero-pr-bot-row {
1406 display: flex;
1407 align-items: center;
1408 gap: 8px;
1409 margin-bottom: 8px;
1410 font-size: 12px;
1411 }
1412 .hero-pr-bot-avatar {
1413 width: 22px; height: 22px;
1414 display: inline-flex;
1415 align-items: center;
1416 justify-content: center;
1417 border-radius: 50%;
1418 background: var(--accent-gradient);
1419 font-size: 11px;
1420 box-shadow: 0 0 12px rgba(140,109,255,0.40);
1421 }
1422 .hero-pr-bot-name {
1423 color: var(--text-strong);
1424 font-weight: 600;
1425 }
1426 .hero-pr-bot-meta {
1427 color: var(--text-faint);
1428 font-family: var(--font-mono);
1429 }
1430 .hero-pr-bot-text {
1431 color: var(--text);
1432 font-size: 13px;
1433 line-height: 1.55;
1434 margin: 0;
1435 }
1436 .hero-pr-bot-text code {
1437 background: rgba(255,255,255,0.06);
1438 border: 1px solid rgba(255,255,255,0.10);
1439 padding: 1px 6px;
1440 border-radius: 4px;
1441 font-size: 11.5px;
1442 color: var(--accent);
1443 }
1444 .hero-pr-bot-link { color: var(--accent-2); text-decoration: underline; text-decoration-style: dotted; }
1445
1446 .hero-pr-gates {
1447 display: flex;
1448 flex-wrap: wrap;
1449 gap: 6px;
1450 padding: 12px 18px 16px;
1451 border-top: 1px solid rgba(255,255,255,0.06);
1452 background: rgba(255,255,255,0.012);
1453 }
1454 .hero-pr-gate {
1455 display: inline-flex;
1456 align-items: center;
1457 gap: 6px;
1458 padding: 4px 10px;
1459 border-radius: var(--r-full);
1460 font-family: var(--font-mono);
1461 font-size: 11px;
1462 border: 1px solid;
1463 }
1464 .hero-pr-gate-pass {
1465 color: var(--green);
1466 background: rgba(52,211,153,0.08);
1467 border-color: rgba(52,211,153,0.30);
1468 }
1469 .hero-pr-gate-running {
1470 color: var(--accent);
1471 background: var(--accent-gradient-faint);
1472 border-color: rgba(140,109,255,0.40);
1473 }
1474 .hero-pr-gate-spin {
1475 width: 9px; height: 9px;
1476 border: 1.5px solid rgba(140,109,255,0.30);
1477 border-top-color: var(--accent);
1478 border-radius: 50%;
1479 animation: hero-spin 800ms linear infinite;
1480 }
1481 @keyframes hero-spin {
1482 to { transform: rotate(360deg); }
1483 }
1484
1485 /* Floating accent badges around the card */
1486 .hero-float {
1487 position: absolute;
1488 display: inline-flex;
1489 align-items: center;
1490 gap: 6px;
1491 padding: 6px 12px;
1492 background: rgba(15,17,26,0.92);
1493 border: 1px solid rgba(140,109,255,0.35);
1494 border-radius: var(--r-full);
1495 font-family: var(--font-mono);
1496 font-size: 11px;
1497 color: var(--text);
1498 box-shadow: 0 12px 24px -8px rgba(0,0,0,0.5), 0 0 18px -4px rgba(140,109,255,0.30);
1499 backdrop-filter: blur(8px);
1500 -webkit-backdrop-filter: blur(8px);
1501 }
1502 .hero-float-icon { color: var(--accent); }
1503 .hero-float-1 {
1504 top: -14px;
1505 left: -8px;
1506 animation: hero-float-bob-1 5s var(--ease, ease) infinite alternate;
1507 }
1508 .hero-float-2 {
1509 bottom: -14px;
1510 right: -8px;
1511 animation: hero-float-bob-2 6s var(--ease, ease) infinite alternate;
1512 }
1513 @keyframes hero-float-bob-1 {
1514 from { transform: translate(0, 0); }
1515 to { transform: translate(-8px, -10px); }
1516 }
1517 @keyframes hero-float-bob-2 {
1518 from { transform: translate(0, 0); }
1519 to { transform: translate(8px, 8px); }
1520 }
1521
1522 @media (max-width: 720px) {
1523 .landing-hero-visual { padding: 0 8px; }
1524 .hero-pr-card { transform: none; }
1525 .hero-pr-title { font-size: 12px; }
1526 .hero-pr-diff { font-size: 11px; line-height: 1.6; }
1527 .hero-float { display: none; }
1528 }
958d26aClaude1529 .landing-hero-bg {
1530 position: absolute;
1531 inset: -10% -20%;
1532 pointer-events: none;
1533 z-index: 0;
4c47454Claude1534 }
958d26aClaude1535 .landing-hero-blob {
1536 position: absolute;
1537 border-radius: 50%;
1538 filter: blur(80px);
1539 opacity: 0.55;
1540 }
1541 .landing-hero-blob-1 {
1542 top: -10%;
1543 left: 30%;
1544 width: 480px;
1545 height: 480px;
1546 background: radial-gradient(circle, rgba(140,109,255,0.55), transparent 65%);
1547 }
1548 .landing-hero-blob-2 {
1549 top: 10%;
1550 left: 50%;
1551 width: 380px;
1552 height: 380px;
1553 background: radial-gradient(circle, rgba(54,197,214,0.40), transparent 65%);
1554 }
1555 .landing-hero-grid {
4c47454Claude1556 position: absolute;
1557 inset: 0;
958d26aClaude1558 background-image:
1559 linear-gradient(to right, rgba(255,255,255,0.04) 1px, transparent 1px),
1560 linear-gradient(to bottom, rgba(255,255,255,0.04) 1px, transparent 1px);
1561 background-size: 60px 60px;
1562 mask-image: radial-gradient(ellipse 50% 50% at 50% 30%, #000 0%, transparent 75%);
1563 -webkit-mask-image: radial-gradient(ellipse 50% 50% at 50% 30%, #000 0%, transparent 75%);
1564 }
1565 :root[data-theme='light'] .landing-hero-grid {
1566 background-image:
1567 linear-gradient(to right, rgba(15,16,28,0.06) 1px, transparent 1px),
1568 linear-gradient(to bottom, rgba(15,16,28,0.06) 1px, transparent 1px);
4c47454Claude1569 }
1570
958d26aClaude1571 .landing-hero-inner {
1572 position: relative;
1573 z-index: 1;
1574 max-width: 960px;
2b821b7Claude1575 margin: 0 auto;
1576 }
958d26aClaude1577 .landing-hero-eyebrow {
1578 margin: 0 auto var(--s-6);
1579 color: var(--accent);
1580 }
1581 .landing-hero-eyebrow::before { display: none; }
1582 .landing-hero-pulse {
1583 width: 7px;
1584 height: 7px;
1585 border-radius: 50%;
1586 background: var(--accent);
1587 box-shadow: 0 0 0 0 rgba(140,109,255,0.6);
1588 animation: pulse 1.8s ease-out infinite;
1589 flex-shrink: 0;
1590 }
1591 @keyframes pulse {
1592 0% { box-shadow: 0 0 0 0 rgba(140,109,255,0.55); }
1593 70% { box-shadow: 0 0 0 10px rgba(140,109,255,0); }
1594 100% { box-shadow: 0 0 0 0 rgba(140,109,255,0); }
1595 }
1596
2b821b7Claude1597 .landing-hero-title {
93fe97eClaude1598 font-size: clamp(32px, 5.5vw, 64px);
1599 line-height: 1.05;
1600 letter-spacing: -0.025em;
c963db5Claude1601 font-weight: 700;
1602 margin: 0 0 var(--s-7);
958d26aClaude1603 color: var(--text-strong);
2b821b7Claude1604 }
c963db5Claude1605 .landing-hero-title .gradient-text {
1606 background-image: linear-gradient(135deg, #a48bff 0%, #8c6dff 50%, #6d4dff 100%);
1607 -webkit-background-clip: text;
1608 background-clip: text;
1609 -webkit-text-fill-color: transparent;
1610 color: transparent;
1611 }
958d26aClaude1612
2b821b7Claude1613 .landing-hero-sub {
958d26aClaude1614 font-size: clamp(15px, 1.6vw, 19px);
2b821b7Claude1615 color: var(--text-muted);
958d26aClaude1616 max-width: 680px;
1617 margin: 0 auto;
4c47454Claude1618 line-height: 1.55;
958d26aClaude1619 letter-spacing: -0.005em;
2b821b7Claude1620 }
4c47454Claude1621
2b821b7Claude1622 .landing-hero-ctas {
1623 display: flex;
958d26aClaude1624 gap: 12px;
2b821b7Claude1625 justify-content: center;
1626 flex-wrap: wrap;
958d26aClaude1627 margin-top: var(--s-10);
2b821b7Claude1628 }
958d26aClaude1629 .landing-cta-arrow {
1630 transition: transform var(--t-base) var(--ease-spring);
1631 display: inline-block;
2b821b7Claude1632 }
958d26aClaude1633 .btn:hover .landing-cta-arrow,
1634 .landing-cta-primary:hover .landing-cta-arrow {
1635 transform: translateX(4px);
8e9f1d9Claude1636 }
2b821b7Claude1637
93fe97eClaude1638 /* BLOCK Q1 — flagship "Add to Claude Desktop" CTA.
1639 Gradient-bordered + accent text so it reads as a peer of the primary
1640 Sign-up CTA, not a third secondary. Subtle elevation on hover; static
1641 when the visitor opts out of motion. */
1642 .landing-cta-dxt {
1643 position: relative;
1644 background: var(--bg-elev-1, #161b22);
1645 color: var(--text-strong, #e6edf3);
1646 border: 1px solid transparent;
1647 background-image:
1648 linear-gradient(var(--bg-elev-1, #161b22), var(--bg-elev-1, #161b22)),
1649 linear-gradient(90deg, #8c6dff 0%, #36c5d6 100%);
1650 background-origin: border-box;
1651 background-clip: padding-box, border-box;
1652 transition: transform var(--t-base, 180ms) var(--ease-spring, ease),
1653 box-shadow var(--t-base, 180ms) var(--ease-spring, ease);
1654 }
1655 .landing-cta-dxt:hover {
1656 transform: translateY(-2px);
1657 box-shadow: 0 8px 24px -8px rgba(140, 109, 255, 0.45);
1658 }
1659 @media (prefers-reduced-motion: reduce) {
1660 .landing-cta-dxt,
1661 .landing-cta-dxt:hover {
1662 transform: none;
1663 transition: none;
1664 }
1665 }
1666
5f2e749Claude1667 /* L8 — free-tier reassurance link beneath the CTA row. */
1668 .landing-hero-freenote {
1669 margin-top: var(--s-5);
1670 font-size: var(--t-sm);
1671 color: var(--text-muted);
1672 text-align: center;
1673 }
1674 .landing-hero-freenote-link {
1675 color: var(--accent);
1676 text-decoration: none;
1677 font-weight: 500;
1678 border-bottom: 1px dotted rgba(140,109,255,0.4);
1679 transition: color var(--t-fast) var(--ease), border-color var(--t-fast) var(--ease);
1680 }
1681 .landing-hero-freenote-link:hover {
1682 color: var(--text-strong);
1683 border-bottom-color: var(--accent);
1684 }
1685
4c47454Claude1686 .landing-hero-caption {
958d26aClaude1687 margin-top: var(--s-8);
1688 font-size: var(--t-sm);
4c47454Claude1689 color: var(--text-muted);
958d26aClaude1690 display: flex;
1691 flex-wrap: wrap;
1692 align-items: center;
1693 justify-content: center;
1694 gap: 12px;
2b821b7Claude1695 }
958d26aClaude1696 .landing-hero-cmd {
1697 display: inline-flex;
1698 align-items: center;
1699 gap: 4px;
1700 padding: 6px 12px;
1701 background: var(--bg-elevated);
4c47454Claude1702 border: 1px solid var(--border);
958d26aClaude1703 border-radius: var(--r-full);
1704 box-shadow: var(--elev-1);
1705 }
1706 .landing-hero-cmd .kbd {
1707 border: 0;
1708 background: transparent;
1709 padding: 0 4px;
1710 color: var(--text-muted);
1711 font-size: 12px;
1712 }
1713 .landing-hero-cmd .kbd:nth-last-of-type(1) { color: var(--accent); }
1714 .landing-hero-arrow {
1715 color: var(--text-faint);
1716 font-size: 13px;
1717 margin: 0 2px;
2b821b7Claude1718 }
4c47454Claude1719
1720 .landing-stats {
958d26aClaude1721 margin-top: var(--s-7);
1722 font-family: var(--font-mono);
1723 font-size: 12px;
1724 color: var(--text-muted);
1725 display: flex;
1726 align-items: center;
1727 justify-content: center;
1728 gap: 10px;
1729 flex-wrap: wrap;
1730 letter-spacing: 0.02em;
1731 }
1732 .landing-stats strong {
1733 color: var(--text-strong);
1734 font-weight: 600;
1735 font-feature-settings: 'tnum';
1736 }
1737 .landing-stats-sep { opacity: 0.4; }
1738
c963db5Claude1739 /* ---------- Capability grid (crontech-style uppercase tracked) ---------- */
1740 .landing-caps {
1741 margin: var(--s-12) auto var(--s-16);
1742 max-width: 1080px;
1743 padding: var(--s-7) var(--s-4);
958d26aClaude1744 border-top: 1px solid var(--border-subtle);
1745 border-bottom: 1px solid var(--border-subtle);
c963db5Claude1746 }
1747 .landing-caps-grid {
1748 display: grid;
1749 grid-template-columns: repeat(4, 1fr);
1750 gap: 24px 16px;
958d26aClaude1751 text-align: center;
1752 }
c963db5Claude1753 .landing-cap {
958d26aClaude1754 font-family: var(--font-mono);
1755 font-size: 11px;
c963db5Claude1756 font-weight: 600;
958d26aClaude1757 text-transform: uppercase;
c963db5Claude1758 letter-spacing: 0.16em;
1759 color: var(--text-muted);
1760 transition: color var(--t-fast) var(--ease);
958d26aClaude1761 }
c963db5Claude1762 .landing-cap:hover { color: var(--text-strong); }
1763 @media (max-width: 800px) {
1764 .landing-caps-grid { grid-template-columns: repeat(2, 1fr); gap: 18px 12px; }
1765 }
1766 @media (max-width: 480px) {
1767 .landing-caps-grid { grid-template-columns: 1fr; }
1768 }
1769
1770 /* ---------- Big stat row (crontech-style hero closer) ---------- */
1771 .landing-bigstats {
1772 margin: var(--s-10) auto var(--s-20);
1773 max-width: 1180px;
1774 padding: 0 var(--s-4);
1775 }
1776 .landing-bigstats-grid {
1777 display: grid;
1778 grid-template-columns: repeat(4, 1fr);
1779 gap: 32px;
1780 text-align: left;
958d26aClaude1781 }
c963db5Claude1782 .landing-bigstat {
1783 padding: var(--s-2) 0;
1784 }
1785 .landing-bigstat-num {
958d26aClaude1786 font-family: var(--font-display);
c963db5Claude1787 font-size: clamp(28px, 3.5vw, 44px);
1788 line-height: 1.05;
1789 letter-spacing: -0.03em;
1790 font-weight: 700;
1791 color: var(--text-strong);
1792 margin-bottom: var(--s-2);
1793 }
1794 .landing-bigstat-label {
1795 font-family: var(--font-mono);
1796 font-size: 11px;
958d26aClaude1797 font-weight: 500;
c963db5Claude1798 text-transform: uppercase;
1799 letter-spacing: 0.14em;
1800 color: var(--text-faint);
2b821b7Claude1801 }
c963db5Claude1802 @media (max-width: 800px) {
1803 .landing-bigstats-grid { grid-template-columns: repeat(2, 1fr); gap: 28px 16px; }
1804 }
1805 @media (max-width: 480px) {
1806 .landing-bigstats-grid { grid-template-columns: 1fr; gap: 24px; }
958d26aClaude1807 }
1808
1809 /* ---------- Section base ---------- */
1810 .landing-section { margin: var(--s-20) auto; }
2b821b7Claude1811
4c47454Claude1812 /* ---------- Feature grid ---------- */
1813 .landing-features {
2b821b7Claude1814 display: grid;
1815 grid-template-columns: repeat(3, 1fr);
958d26aClaude1816 gap: 16px;
2b821b7Claude1817 }
1818 .landing-feature {
958d26aClaude1819 background: var(--bg-elevated);
2b821b7Claude1820 border: 1px solid var(--border);
958d26aClaude1821 border-radius: var(--r-lg);
1822 padding: var(--s-7);
1823 position: relative;
1824 overflow: hidden;
1825 isolation: isolate;
1826 transition:
1827 transform var(--t-base) var(--ease-out-quart),
1828 border-color var(--t-base) var(--ease),
1829 box-shadow var(--t-base) var(--ease);
1830 }
1831 .landing-feature::before {
1832 content: '';
1833 position: absolute;
1834 inset: 0;
1835 background: radial-gradient(120% 100% at 0% 0%, rgba(140,109,255,0.08), transparent 55%);
1836 opacity: 0;
1837 transition: opacity var(--t-base) var(--ease);
1838 z-index: -1;
4c47454Claude1839 }
1840 .landing-feature:hover {
958d26aClaude1841 transform: translateY(-3px);
1842 border-color: var(--border-strong);
1843 box-shadow: var(--elev-2);
2b821b7Claude1844 }
958d26aClaude1845 .landing-feature:hover::before { opacity: 1; }
2b821b7Claude1846 .landing-feature-icon {
4c47454Claude1847 display: inline-flex;
1848 align-items: center;
1849 justify-content: center;
958d26aClaude1850 width: 40px;
1851 height: 40px;
1852 border-radius: var(--r);
1853 background: var(--accent-gradient-soft);
1854 color: var(--accent);
1855 margin-bottom: var(--s-4);
1856 border: 1px solid rgba(140,109,255,0.20);
2b821b7Claude1857 }
1858 .landing-feature-title {
958d26aClaude1859 font-family: var(--font-display);
1860 font-size: 19px;
1861 font-weight: 600;
1862 letter-spacing: -0.018em;
1863 margin: 0 0 var(--s-2);
1864 color: var(--text-strong);
2b821b7Claude1865 }
1866 .landing-feature-desc {
958d26aClaude1867 font-size: var(--t-sm);
1868 color: var(--text-muted);
1869 line-height: 1.6;
1870 margin: 0;
1871 }
1872
1873 /* ---------- Walkthrough ---------- */
1874 .landing-walk-grid {
1875 display: grid;
1876 grid-template-columns: repeat(4, 1fr);
1877 gap: 16px;
1878 counter-reset: walk;
1879 }
1880 .landing-walk-step {
1881 position: relative;
1882 padding: var(--s-7) var(--s-6) var(--s-6);
1883 background: var(--bg-elevated);
1884 border: 1px solid var(--border);
1885 border-radius: var(--r-lg);
1886 }
1887 .landing-walk-step::after {
1888 content: '';
1889 position: absolute;
1890 top: 50%;
1891 right: -12px;
1892 width: 12px;
1893 height: 1px;
1894 background: var(--border-strong);
1895 }
1896 .landing-walk-step:last-child::after { display: none; }
1897 .landing-walk-num {
1898 display: inline-block;
1899 font-family: var(--font-mono);
1900 font-size: 11px;
1901 color: var(--accent);
1902 background: var(--accent-gradient-faint);
1903 border: 1px solid rgba(140,109,255,0.30);
1904 padding: 3px 8px;
1905 border-radius: var(--r-full);
1906 letter-spacing: 0.06em;
1907 margin-bottom: var(--s-3);
1908 }
1909 .landing-walk-title {
1910 font-family: var(--font-display);
1911 font-size: 22px;
1912 font-weight: 600;
1913 letter-spacing: -0.022em;
1914 margin: 0 0 var(--s-2);
1915 color: var(--text-strong);
1916 }
1917 .landing-walk-desc {
1918 font-size: var(--t-sm);
2b821b7Claude1919 color: var(--text-muted);
1920 line-height: 1.55;
1921 margin: 0;
1922 }
1923
958d26aClaude1924 /* ---------- Terminal ---------- */
1925 .landing-terminal-section { margin-top: var(--s-16); }
4c47454Claude1926 .landing-terminal-wrap {
1927 display: flex;
2b821b7Claude1928 justify-content: center;
1929 }
4c47454Claude1930 .landing-terminal {
1931 width: 100%;
958d26aClaude1932 max-width: 820px;
1933 background: linear-gradient(180deg, #0a0b12 0%, #06070c 100%);
1934 border: 1px solid var(--border-strong);
1935 border-radius: var(--r-lg);
1936 overflow: hidden;
1937 box-shadow: var(--elev-3), 0 0 60px -10px rgba(140,109,255,0.18);
4c47454Claude1938 text-align: left;
2b821b7Claude1939 }
958d26aClaude1940 :root[data-theme='light'] .landing-terminal {
1941 background: linear-gradient(180deg, #0f111a 0%, #06070c 100%);
1942 }
1943 .landing-terminal-chrome {
1944 display: flex;
1945 align-items: center;
1946 gap: 7px;
1947 padding: 11px 14px;
1948 background: rgba(255,255,255,0.025);
1949 border-bottom: 1px solid rgba(255,255,255,0.06);
1950 position: relative;
1951 }
1952 .landing-terminal-dot {
1953 width: 11px;
1954 height: 11px;
1955 border-radius: 50%;
1956 flex-shrink: 0;
1957 }
1958 .landing-terminal-dot-r { background: #ff5f57; }
1959 .landing-terminal-dot-y { background: #febc2e; }
1960 .landing-terminal-dot-g { background: #28c840; }
1961 .landing-terminal-title {
1962 position: absolute;
1963 left: 50%;
1964 transform: translateX(-50%);
1965 font-family: var(--font-mono);
1966 font-size: 11px;
1967 color: rgba(237,237,242,0.55);
1968 letter-spacing: 0.01em;
1969 }
1970 .landing-terminal-body {
1971 padding: var(--s-6) var(--s-7);
1972 font-family: var(--font-mono);
1973 font-feature-settings: var(--mono-feat);
1974 font-size: 13.5px;
1975 line-height: 1.85;
1976 color: rgba(237,237,242,0.92);
1977 }
4c47454Claude1978 .landing-term-line {
1979 display: flex;
1980 gap: 10px;
1981 white-space: pre-wrap;
1982 word-break: break-all;
2b821b7Claude1983 }
958d26aClaude1984 .landing-term-out { color: rgba(237,237,242,0.7); }
1985 .landing-term-prompt { color: rgba(140,109,255,0.85); user-select: none; flex-shrink: 0; }
1986 .landing-term-meta { color: rgba(237,237,242,0.45); }
1987 .landing-term-ok { color: var(--green); user-select: none; flex-shrink: 0; }
1988 .landing-term-ok-line { color: rgba(237,237,242,0.92); }
1989 .landing-term-cursor { margin-top: 4px; }
1990 .landing-term-blink {
1991 animation: blink 1.05s steps(2) infinite;
1992 color: var(--accent);
1993 }
1994 @keyframes blink { 50% { opacity: 0; } }
1995
1996 /* ---------- Comparison ---------- */
1997 .landing-compare {
1998 max-width: 920px;
1999 margin: 0 auto;
2000 border: 1px solid var(--border);
2001 border-radius: var(--r-lg);
2002 overflow: hidden;
2003 background: var(--bg-elevated);
2004 }
2005 .landing-compare-row {
2006 display: grid;
2007 grid-template-columns: 1fr 180px 180px;
2008 align-items: center;
2009 padding: 14px 20px;
2010 border-bottom: 1px solid var(--border-subtle);
2011 font-size: var(--t-sm);
2012 transition: background var(--t-fast) var(--ease);
2013 }
2014 .landing-compare-row:last-child { border-bottom: none; }
2015 .landing-compare-row:hover { background: var(--bg-hover); }
2016 .landing-compare-feature {
2017 color: var(--text-strong);
2018 font-weight: 500;
2019 }
2020 .landing-compare-them, .landing-compare-us {
2021 text-align: center;
2022 font-family: var(--font-mono);
2023 font-size: 12px;
2024 color: var(--text-muted);
2025 }
2026 .landing-compare-us { color: var(--green); font-weight: 500; }
2027 .landing-compare-hl .landing-compare-us {
2028 color: var(--accent);
2029 font-weight: 600;
2b821b7Claude2030 }
958d26aClaude2031 .landing-compare-hl .landing-compare-feature::after {
2032 content: 'NEW';
2033 margin-left: 8px;
2034 padding: 1px 6px;
2035 border-radius: 4px;
2036 background: var(--accent-gradient-faint);
2037 color: var(--accent);
2038 font-family: var(--font-mono);
2039 font-size: 9px;
2040 letter-spacing: 0.1em;
2041 font-weight: 600;
2042 vertical-align: 1px;
2043 }
2044 @media (max-width: 720px) {
2045 .landing-compare-row { grid-template-columns: 1fr 80px 80px; padding: 12px 14px; }
2046 .landing-compare-hl .landing-compare-feature::after { display: none; }
2047 }
2048
2049 /* ---------- Pricing ---------- */
2050 .landing-pricing {
2051 display: grid;
2052 grid-template-columns: repeat(3, 1fr);
2053 gap: 16px;
2054 max-width: 1080px;
2055 margin: 0 auto;
2056 align-items: stretch;
2057 }
2058 .landing-price-card {
2059 position: relative;
2060 background: var(--bg-elevated);
2061 border: 1px solid var(--border);
2062 border-radius: var(--r-lg);
2063 padding: var(--s-7);
2064 display: flex;
2065 flex-direction: column;
2066 gap: var(--s-4);
2067 transition: border-color var(--t-base) var(--ease), transform var(--t-base) var(--ease);
2068 }
2069 .landing-price-card:hover {
2070 border-color: var(--border-strong);
2071 transform: translateY(-2px);
2072 }
2073 .landing-price-hl {
2074 border-color: rgba(140,109,255,0.35);
2075 box-shadow: var(--elev-2), 0 0 0 1px rgba(140,109,255,0.25);
2076 background:
2077 linear-gradient(180deg, rgba(140,109,255,0.05), transparent 50%),
2078 var(--bg-elevated);
2079 }
2080 .landing-price-hl:hover { border-color: rgba(140,109,255,0.55); }
2081 .landing-price-badge {
2082 position: absolute;
2083 top: -10px;
2084 left: 50%;
2085 transform: translateX(-50%);
2086 padding: 3px 12px;
2087 background: var(--accent-gradient);
2088 color: #fff;
2089 font-family: var(--font-mono);
2090 font-size: 10px;
2091 letter-spacing: 0.1em;
2092 text-transform: uppercase;
2093 font-weight: 600;
2094 border-radius: var(--r-full);
2095 box-shadow: 0 4px 12px -2px rgba(140,109,255,0.4);
2096 }
2097 .landing-price-tier {
2098 font-family: var(--font-mono);
2099 font-size: 11px;
2100 text-transform: uppercase;
2101 letter-spacing: 0.16em;
2102 color: var(--text-muted);
2103 }
2104 .landing-price-amount {
2105 display: flex;
2106 align-items: baseline;
2107 gap: 8px;
2108 }
2109 .landing-price-num {
2110 font-family: var(--font-display);
2111 font-size: 40px;
2112 font-weight: 600;
2113 letter-spacing: -0.03em;
2114 color: var(--text-strong);
2115 }
2116 .landing-price-cad {
2117 font-size: var(--t-sm);
2118 color: var(--text-faint);
2119 }
2120 .landing-price-desc {
2121 font-size: var(--t-sm);
2122 color: var(--text-muted);
2123 line-height: 1.55;
2124 margin: 0;
2125 }
2126 .landing-price-features {
2127 list-style: none;
2128 padding: 0;
2129 margin: 0;
2130 display: flex;
2131 flex-direction: column;
2132 gap: 8px;
2133 font-size: var(--t-sm);
2134 color: var(--text);
2135 }
2136 .landing-price-features li {
2137 display: flex;
2138 align-items: center;
2139 gap: 9px;
2140 }
2141 .landing-price-check {
2142 color: var(--accent);
2143 font-weight: 600;
4c47454Claude2144 flex-shrink: 0;
2b821b7Claude2145 }
958d26aClaude2146 .landing-price-cta { margin-top: auto; }
2147
2148 /* ---------- Closing CTA ---------- */
2149 .landing-cta-section { margin: var(--s-20) auto var(--s-16); }
2150 .landing-cta-card {
2151 position: relative;
2152 text-align: center;
2153 padding: var(--s-16) var(--s-7);
2154 border: 1px solid var(--border-strong);
2155 border-radius: var(--r-2xl);
2156 background: var(--bg-elevated);
2157 overflow: hidden;
2158 isolation: isolate;
2159 }
2160 .landing-cta-bg {
2161 position: absolute;
2162 inset: 0;
2163 z-index: -1;
2164 background:
2165 radial-gradient(60% 100% at 50% 0%, rgba(140,109,255,0.16), transparent 65%),
2166 radial-gradient(40% 80% at 80% 100%, rgba(54,197,214,0.10), transparent 65%);
2167 }
2168 .landing-cta-card::after {
2169 content: '';
2170 position: absolute;
2171 inset: 0;
2172 z-index: -1;
2173 background-image: radial-gradient(rgba(255,255,255,0.04) 1px, transparent 1px);
2174 background-size: 24px 24px;
2175 mask-image: radial-gradient(ellipse at center, #000 0%, transparent 65%);
2176 -webkit-mask-image: radial-gradient(ellipse at center, #000 0%, transparent 65%);
2177 opacity: 0.6;
2178 }
2179 :root[data-theme='light'] .landing-cta-card::after {
2180 background-image: radial-gradient(rgba(15,16,28,0.07) 1px, transparent 1px);
2181 }
2182 .landing-cta-card .eyebrow { justify-content: center; }
2183 .landing-cta-title {
2184 font-family: var(--font-display);
2185 font-size: clamp(28px, 4.4vw, 56px);
2186 line-height: 1.05;
2187 letter-spacing: -0.03em;
2188 font-weight: 600;
2189 margin: var(--s-3) 0 var(--s-4);
2190 color: var(--text-strong);
2191 }
2192 .landing-cta-sub {
2193 font-size: var(--t-md);
2194 color: var(--text-muted);
2195 max-width: 560px;
2196 margin: 0 auto var(--s-8);
2197 line-height: 1.55;
2198 }
2199 .landing-cta-buttons {
2200 display: flex;
2201 gap: 12px;
2202 justify-content: center;
2203 flex-wrap: wrap;
2204 }
2b821b7Claude2205
2206 /* ---------- Responsive ---------- */
958d26aClaude2207 @media (max-width: 960px) {
2208 .landing-features { grid-template-columns: repeat(2, 1fr); }
2209 .landing-walk-grid { grid-template-columns: repeat(2, 1fr); }
2210 .landing-walk-step::after { display: none; }
2211 .landing-pricing { grid-template-columns: 1fr; max-width: 480px; }
2212 }
2213 @media (max-width: 640px) {
2214 .landing-hero { padding: var(--s-14) 0 var(--s-10); }
2215 .landing-hero-cmd { flex-wrap: wrap; justify-content: center; }
2216 .landing-hero-ctas { flex-direction: column; align-items: stretch; }
2217 .landing-hero-ctas .btn { width: 100%; justify-content: center; }
2218 .landing-features { grid-template-columns: 1fr; }
2219 .landing-walk-grid { grid-template-columns: 1fr; }
2220 .landing-section { margin: var(--s-12) auto; }
2221 .landing-cta-card { padding: var(--s-10) var(--s-5); }
2222 .landing-cta-buttons .btn { width: 100%; justify-content: center; }
2b821b7Claude2223 }
52ad8b1Claude2224
2225 /* ---------- L4 social-proof counters ---------- */
2226 .landing-counters {
2227 margin: var(--s-10) auto var(--s-12);
2228 max-width: 1180px;
2229 padding: 0 var(--s-4);
2230 }
2231 .landing-counters-grid {
2232 display: grid;
2233 grid-template-columns: repeat(6, 1fr);
2234 gap: 20px;
2235 text-align: left;
2236 }
2237 .landing-counter {
2238 padding: var(--s-3) 0;
2239 border-top: 1px solid var(--border-subtle);
2240 }
2241 .landing-counter-num {
2242 font-family: var(--font-display);
2243 font-size: clamp(24px, 3vw, 38px);
2244 line-height: 1.05;
2245 letter-spacing: -0.03em;
2246 font-weight: 700;
2247 margin-bottom: 6px;
2248 font-feature-settings: 'tnum';
2249 background-image: var(--accent-gradient);
2250 -webkit-background-clip: text;
2251 background-clip: text;
2252 -webkit-text-fill-color: transparent;
2253 color: transparent;
2254 }
2255 .landing-counter-label {
2256 font-family: var(--font-mono);
2257 font-size: 10.5px;
2258 font-weight: 500;
2259 text-transform: uppercase;
2260 letter-spacing: 0.12em;
2261 color: var(--text-faint);
2262 line-height: 1.4;
2263 }
2264 @media (max-width: 960px) {
2265 .landing-counters-grid { grid-template-columns: repeat(3, 1fr); gap: 20px 16px; }
2266 }
2267 @media (max-width: 540px) {
2268 .landing-counters-grid { grid-template-columns: repeat(2, 1fr); gap: 18px 12px; }
2269 }
5f2e749Claude2270
2271 /* ---------- L10 hero install snippet ---------- */
2272 .landing-hero-install {
2273 display: inline-flex;
2274 align-items: stretch;
2275 gap: 0;
2276 margin: var(--s-8) auto 0;
2277 background: var(--bg-elevated);
2278 border: 1px solid var(--border-strong);
2279 border-radius: var(--r);
2280 box-shadow: var(--elev-1);
2281 overflow: hidden;
2282 max-width: 100%;
2283 font-family: var(--font-mono);
2284 }
2285 .landing-hero-install-code {
2286 display: inline-flex;
2287 align-items: center;
2288 gap: 10px;
2289 padding: 10px 14px;
2290 font-size: 13.5px;
2291 color: var(--text-strong);
2292 background: transparent;
2293 border: 0;
2294 white-space: nowrap;
2295 overflow-x: auto;
2296 }
2297 .landing-hero-install-prompt {
2298 color: var(--accent);
2299 user-select: none;
2300 }
2301 .landing-hero-install-copy {
2302 appearance: none;
2303 border: 0;
2304 border-left: 1px solid var(--border);
2305 background: transparent;
2306 color: var(--text-muted);
2307 font-family: var(--font-mono);
2308 font-size: 12px;
2309 font-weight: 600;
2310 letter-spacing: 0.06em;
2311 text-transform: uppercase;
2312 padding: 0 16px;
2313 cursor: pointer;
2314 transition: background var(--t-fast) var(--ease), color var(--t-fast) var(--ease);
2315 }
2316 .landing-hero-install-copy:hover {
2317 background: var(--accent-gradient-faint);
2318 color: var(--accent);
2319 }
2320 .landing-hero-install-copy[data-copied="1"] {
2321 color: var(--green, #34d399);
2322 }
2323
2324 /* ---------- L10 hero "what just happened" rail ---------- */
2325 .landing-hero-rail {
2326 list-style: none;
2327 padding: 0;
2328 margin: var(--s-7) auto 0;
2329 display: flex;
2330 flex-wrap: wrap;
2331 justify-content: center;
2332 gap: 8px 22px;
2333 font-family: var(--font-sans);
2334 font-size: var(--t-sm);
2335 color: var(--text-muted);
2336 max-width: 760px;
2337 }
2338 .landing-hero-rail li {
2339 display: inline-flex;
2340 align-items: center;
2341 gap: 7px;
2342 line-height: 1.4;
2343 }
2344 .landing-hero-rail strong {
2345 color: var(--text-strong);
2346 font-weight: 600;
2347 font-feature-settings: 'tnum';
2348 }
2349 .landing-hero-rail-check {
2350 color: var(--accent);
2351 font-weight: 700;
2352 flex-shrink: 0;
2353 }
2354
2355 /* ---------- L10 three-reasons section ---------- */
2356 .landing-reasons { margin-top: var(--s-12); }
2357 .landing-reasons-grid {
2358 display: grid;
2359 grid-template-columns: repeat(3, 1fr);
2360 gap: 16px;
2361 }
2362 .landing-reason {
2363 background: var(--bg-elevated);
2364 border: 1px solid var(--border);
2365 border-radius: var(--r-lg);
2366 padding: var(--s-7);
2367 display: flex;
2368 flex-direction: column;
2369 gap: var(--s-3);
2370 transition: border-color var(--t-base) var(--ease), transform var(--t-base) var(--ease);
2371 }
2372 .landing-reason:hover {
2373 border-color: var(--border-strong);
2374 transform: translateY(-2px);
2375 }
2376 .landing-reason-icon {
2377 display: inline-flex;
2378 align-items: center;
2379 justify-content: center;
2380 width: 40px;
2381 height: 40px;
2382 border-radius: var(--r);
2383 background: var(--accent-gradient-soft);
2384 color: var(--accent);
2385 border: 1px solid rgba(140,109,255,0.20);
2386 }
2387 .landing-reason-title {
2388 font-family: var(--font-display);
2389 font-size: 20px;
2390 font-weight: 600;
2391 letter-spacing: -0.02em;
2392 margin: 0;
2393 color: var(--text-strong);
2394 }
2395 .landing-reason-body {
2396 font-size: var(--t-sm);
2397 color: var(--text-muted);
2398 line-height: 1.55;
2399 margin: 0;
2400 }
2401 .landing-reasons-code {
2402 display: block;
2403 font-family: var(--font-mono);
2404 font-size: 12px;
2405 color: var(--text-strong);
2406 background: var(--bg);
2407 border: 1px solid var(--border);
2408 border-radius: var(--r);
2409 padding: 8px 12px;
2410 overflow-x: auto;
2411 white-space: nowrap;
2412 }
2413 .landing-reason-link {
2414 margin-top: auto;
2415 display: inline-flex;
2416 align-items: center;
2417 gap: 6px;
2418 color: var(--accent);
2419 font-size: var(--t-sm);
2420 font-weight: 500;
2421 text-decoration: none;
2422 }
2423 .landing-reason-link:hover { text-decoration: underline; }
2424 @media (max-width: 960px) {
2425 .landing-reasons-grid { grid-template-columns: 1fr; max-width: 520px; margin: 0 auto; }
2426 }
2427
2428 /* ---------- L10 "How is this different" pull-quote ---------- */
2429 .landing-pullquote-section {
2430 margin: var(--s-20) auto var(--s-12);
2431 max-width: 920px;
2432 padding: 0 var(--s-4);
2433 text-align: center;
2434 }
2435 .landing-pullquote {
2436 margin: 0;
2437 padding: var(--s-10) var(--s-7);
2438 background:
2439 radial-gradient(80% 100% at 50% 0%, rgba(140,109,255,0.10), transparent 65%),
2440 var(--bg-elevated);
2441 border: 1px solid var(--border-strong);
2442 border-radius: var(--r-xl);
2443 position: relative;
2444 overflow: hidden;
2445 }
2446 .landing-pullquote-eyebrow {
2447 font-family: var(--font-mono);
2448 font-size: 11px;
2449 font-weight: 600;
2450 letter-spacing: 0.16em;
2451 text-transform: uppercase;
2452 color: var(--accent);
2453 margin-bottom: var(--s-4);
2454 }
2455 .landing-pullquote-text {
2456 font-family: var(--font-display);
2457 font-size: clamp(20px, 2.4vw, 28px);
2458 line-height: 1.4;
2459 letter-spacing: -0.018em;
2460 color: var(--text-strong);
2461 margin: 0 auto;
2462 max-width: 760px;
2463 quotes: "\\201C" "\\201D";
2464 }
2465 .landing-pullquote-text::before { content: open-quote; color: var(--accent); margin-right: 4px; }
2466 .landing-pullquote-text::after { content: close-quote; color: var(--accent); margin-left: 4px; }
2467 .landing-pullquote-link {
2468 display: inline-flex;
2469 align-items: center;
2470 gap: 6px;
2471 margin-top: var(--s-6);
2472 color: var(--accent);
2473 font-size: var(--t-sm);
2474 font-weight: 500;
2475 text-decoration: none;
2476 }
2477 .landing-pullquote-link:hover { text-decoration: underline; }
2478
2479 /* ---------- L10 hero responsive overrides ---------- */
2480 @media (max-width: 640px) {
2481 .landing-hero-install { width: 100%; }
2482 .landing-hero-install-code { flex: 1; font-size: 12px; }
2483 .landing-hero-rail { flex-direction: column; align-items: flex-start; gap: 6px; padding: 0 var(--s-3); }
2484 .landing-hero-rail li { width: 100%; }
2485 }
534f04aClaude2486
2487 /* ============================================================ */
2488 /* Block M1 — Live-now demo feed */
2489 /* ============================================================ */
2490 .landing-livenow {
2491 margin: var(--s-8) 0 var(--s-6);
2492 padding: var(--s-6) 0 var(--s-4);
2493 }
2494 .landing-livenow-head {
2495 text-align: center;
2496 margin-bottom: var(--s-6);
2497 }
2498 .landing-livenow-eyebrow {
2499 display: inline-flex;
2500 align-items: center;
2501 gap: 8px;
2502 padding: 4px 12px;
2503 border-radius: var(--r-full);
2504 background: rgba(52,211,153,0.08);
2505 border: 1px solid rgba(52,211,153,0.25);
2506 color: var(--green);
2507 font-family: var(--font-mono, ui-monospace, monospace);
2508 font-size: 11px;
2509 letter-spacing: 0.06em;
2510 text-transform: uppercase;
2511 margin-bottom: var(--s-3);
2512 }
2513 .landing-livenow-pulse {
2514 width: 8px; height: 8px;
2515 border-radius: 50%;
2516 background: var(--green);
2517 box-shadow: 0 0 0 0 rgba(52,211,153,0.6);
2518 animation: landing-livenow-pulse 1.6s ease-out infinite;
2519 }
2520 @keyframes landing-livenow-pulse {
2521 0% { box-shadow: 0 0 0 0 rgba(52,211,153,0.55); transform: scale(1); }
2522 70% { box-shadow: 0 0 0 10px rgba(52,211,153,0); transform: scale(1.05); }
2523 100% { box-shadow: 0 0 0 0 rgba(52,211,153,0); transform: scale(1); }
2524 }
2525 .landing-livenow-title {
2526 font-size: 22px;
2527 line-height: 1.25;
2528 margin: 0 auto;
2529 max-width: 720px;
2530 color: var(--text-strong);
2531 font-weight: 600;
2532 letter-spacing: -0.01em;
2533 }
2534 .landing-livenow-sub {
2535 margin: var(--s-2) auto 0;
2536 color: var(--text-muted);
2537 font-size: 13px;
2538 max-width: 560px;
2539 }
2540 .landing-livenow-sub code {
2541 background: rgba(255,255,255,0.05);
2542 border: 1px solid var(--border);
2543 padding: 1px 6px;
2544 border-radius: 4px;
2545 font-size: 12px;
2546 color: var(--accent);
2547 }
2548
2549 .landing-livenow-grid {
2550 display: grid;
2551 grid-template-columns: repeat(2, minmax(0, 1fr));
2552 gap: var(--s-3);
2553 }
2554 @media (min-width: 980px) {
2555 .landing-livenow-grid {
2556 grid-template-columns: repeat(4, minmax(0, 1fr));
2557 }
2558 }
2559
2560 .landing-livecard {
2561 background: linear-gradient(180deg, rgba(255,255,255,0.025), rgba(255,255,255,0.005));
2562 border: 1px solid var(--border);
2563 border-radius: var(--r-md, 10px);
2564 padding: 14px 14px 12px;
2565 display: flex;
2566 flex-direction: column;
2567 min-height: 180px;
2568 position: relative;
2569 overflow: hidden;
2570 }
2571 .landing-livecard::before {
2572 content: '';
2573 position: absolute;
2574 inset: 0;
2575 background: var(--accent-gradient-faint);
2576 opacity: 0;
2577 transition: opacity 200ms var(--ease, ease);
2578 pointer-events: none;
2579 }
2580 .landing-livecard:hover::before { opacity: 1; }
2581
2582 .landing-livecard-head {
2583 display: flex;
2584 align-items: center;
2585 gap: 8px;
2586 margin-bottom: 10px;
2587 }
2588 .landing-livecard-dot {
2589 width: 7px; height: 7px;
2590 border-radius: 50%;
2591 background: var(--green);
2592 box-shadow: 0 0 8px rgba(52,211,153,0.55);
2593 animation: landing-livenow-pulse 1.8s ease-out infinite;
2594 flex-shrink: 0;
2595 }
2596 .landing-livecard-title {
2597 margin: 0;
2598 font-size: 12px;
2599 font-weight: 600;
2600 text-transform: uppercase;
2601 letter-spacing: 0.06em;
2602 color: var(--text-muted);
2603 }
2604
2605 .landing-livecard-bignum {
2606 display: flex;
2607 align-items: baseline;
2608 gap: 8px;
2609 margin: 4px 0 10px;
2610 }
2611 .landing-livecard-bignum-n {
2612 font-size: 30px;
2613 font-weight: 700;
2614 color: var(--text-strong);
2615 font-variant-numeric: tabular-nums;
2616 background: var(--accent-gradient);
2617 -webkit-background-clip: text;
2618 background-clip: text;
2619 -webkit-text-fill-color: transparent;
2620 color: transparent;
2621 }
2622 .landing-livecard-bignum-label {
2623 font-size: 12px;
2624 color: var(--text-muted);
2625 }
2626
2627 .landing-livecard-list {
2628 list-style: none;
2629 margin: 0;
2630 padding: 0;
2631 font-size: 13px;
2632 line-height: 1.45;
2633 flex: 1;
2634 }
2635 .landing-livecard-row, .landing-livecard-feedrow {
2636 padding: 6px 0;
2637 border-bottom: 1px dashed rgba(255,255,255,0.05);
2638 transition: background-color 1s var(--ease, ease);
2639 border-radius: 4px;
2640 margin: 0 -4px;
2641 padding-left: 4px;
2642 padding-right: 4px;
2643 }
2644 .landing-livecard-row:last-child,
2645 .landing-livecard-feedrow:last-child { border-bottom: 0; }
2646 .landing-livecard-feedrow { padding: 4px; font-size: 12.5px; }
2647
2648 .landing-livecard-flash {
2649 background-color: rgba(52,211,153,0.18) !important;
2650 animation: landing-livecard-flash-fade 1.1s ease-out forwards;
2651 }
2652 @keyframes landing-livecard-flash-fade {
2653 0% { background-color: rgba(52,211,153,0.22); }
2654 100% { background-color: rgba(52,211,153,0); }
2655 }
2656
2657 .landing-livecard-link {
2658 color: var(--text-strong);
2659 text-decoration: none;
2660 display: inline-block;
2661 max-width: 100%;
2662 overflow: hidden;
2663 text-overflow: ellipsis;
2664 white-space: nowrap;
2665 }
2666 .landing-livecard-link:hover { color: var(--accent); }
2667 .landing-livecard-num {
2668 font-family: var(--font-mono, ui-monospace, monospace);
2669 color: var(--text-faint);
2670 font-weight: 500;
2671 font-size: 12px;
2672 }
2673 .landing-livecard-title-text { color: var(--text-strong); }
2674 .landing-livecard-snippet {
2675 color: var(--text-muted);
2676 font-style: italic;
2677 font-size: 12px;
2678 }
2679 .landing-livecard-meta {
2680 font-size: 11px;
2681 color: var(--text-faint);
2682 margin-top: 2px;
2683 font-family: var(--font-mono, ui-monospace, monospace);
2684 }
2685 .landing-livecard-repo {
2686 color: var(--accent-2);
2687 }
2688 .landing-livecard-rel {
2689 color: var(--text-muted);
2690 }
2691 .landing-livecard-empty {
2692 color: var(--text-faint);
2693 font-size: 12px;
2694 font-style: italic;
2695 padding: 8px 0;
2696 }
2697 .landing-livecard-kind {
2698 display: inline-block;
2699 padding: 1px 7px;
2700 border-radius: var(--r-full);
2701 font-family: var(--font-mono, ui-monospace, monospace);
2702 font-size: 10px;
2703 letter-spacing: 0.04em;
2704 text-transform: uppercase;
2705 font-weight: 600;
2706 }
2707 .landing-livecard-kind-auto_merge-merged,
2708 .landing-livecard-kind-auto-merge-merged {
2709 background: rgba(52,211,153,0.12);
2710 color: var(--green);
2711 border: 1px solid rgba(52,211,153,0.25);
2712 }
2713 .landing-livecard-kind-ai_build-dispatched,
2714 .landing-livecard-kind-ai-build-dispatched {
2715 background: rgba(140,109,255,0.12);
2716 color: var(--accent);
2717 border: 1px solid rgba(140,109,255,0.30);
2718 }
2719 .landing-livecard-kind-ai_review-posted,
2720 .landing-livecard-kind-ai-review-posted {
2721 background: rgba(54,197,214,0.12);
2722 color: var(--accent-2);
2723 border: 1px solid rgba(54,197,214,0.30);
2724 }
2725
2726 .landing-livenow-cta {
2727 margin-top: var(--s-5);
2728 display: flex;
2729 flex-wrap: wrap;
2730 align-items: center;
2731 justify-content: center;
2732 gap: 10px;
2733 font-size: 13px;
2734 color: var(--text-muted);
2735 }
2736 .landing-livenow-cta-link {
2737 color: var(--accent);
2738 text-decoration: none;
2739 font-weight: 500;
2740 display: inline-flex;
2741 align-items: center;
2742 gap: 4px;
2743 }
2744 .landing-livenow-cta-link:hover { color: var(--accent-hover); }
2745 .landing-livenow-cta-sep { color: var(--text-faint); }
2746
2747 @media (prefers-reduced-motion: reduce) {
2748 .landing-livenow-pulse,
2749 .landing-livecard-dot { animation: none; }
2750 .landing-livecard-flash { animation: none; background-color: transparent !important; }
2751 }
52ad8b1Claude2752`;
2753
2754/**
2755 * Block L4 — count-up animation.
2756 *
2757 * Reads each `[data-counter-target]` and animates the in-DOM text from
2758 * 0 → target over ~1.2s when the element first scrolls into view.
2759 *
2760 * Render-once semantics: each tile already contains the final value as
2761 * HTML, so visitors with JS disabled — or anyone before the script
2762 * loads — sees the correct number. The script just animates the text.
2763 *
2764 * Falls back to the static value (no animation) when IntersectionObserver
2765 * isn't available, or when the user prefers reduced motion.
2766 */
2767const landingCountersJs = `
2768(function(){
2769 try {
2770 var els = document.querySelectorAll('[data-counter-target]');
2771 if (!els.length) return;
2772 var reduced = window.matchMedia && window.matchMedia('(prefers-reduced-motion: reduce)').matches;
2773 if (reduced || typeof IntersectionObserver !== 'function') return;
2774
2775 function animate(el) {
2776 var target = parseInt(el.getAttribute('data-counter-target') || '0', 10);
2777 if (!isFinite(target) || target <= 0) return;
2778 var prefix = el.getAttribute('data-counter-prefix') || '';
2779 var suffix = el.getAttribute('data-counter-suffix') || '';
2780 var duration = 1200;
2781 var start = performance.now();
2782 function frame(now) {
2783 var t = Math.min(1, (now - start) / duration);
2784 // ease-out cubic
2785 var eased = 1 - Math.pow(1 - t, 3);
2786 var v = Math.floor(eased * target);
2787 el.textContent = prefix + v.toLocaleString() + suffix;
2788 if (t < 1) requestAnimationFrame(frame);
2789 else el.textContent = prefix + target.toLocaleString() + suffix;
2790 }
2791 // Reset to zero before animating in.
2792 el.textContent = prefix + '0' + suffix;
2793 requestAnimationFrame(frame);
2794 }
2795
2796 var io = new IntersectionObserver(function(entries) {
2797 entries.forEach(function(entry){
2798 if (entry.isIntersecting) {
2799 animate(entry.target);
2800 io.unobserve(entry.target);
2801 }
2802 });
2803 }, { threshold: 0.4 });
2804 els.forEach(function(el){ io.observe(el); });
2805 } catch (_) { /* swallow — static numbers remain */ }
2806})();
2b821b7Claude2807`;
5f2e749Claude2808
2809/**
2810 * Block L10 — clipboard copy for the hero install snippet.
2811 *
2812 * Pure progressive enhancement. Without JS the user can still
2813 * triple-click + Cmd/Ctrl-C the snippet — the button is the
2814 * speed-bump, not the only path.
2815 */
2816const landingCopyJs = `
2817(function(){
2818 try {
2819 var btns = document.querySelectorAll('[data-copy-target]');
2820 if (!btns.length) return;
2821 btns.forEach(function(btn){
2822 btn.addEventListener('click', function(){
2823 var id = btn.getAttribute('data-copy-target') || '';
2824 var src = document.getElementById(id);
2825 if (!src) return;
2826 var text = src.textContent || '';
2827 var done = function(){
2828 var prev = btn.textContent;
2829 btn.textContent = 'Copied';
2830 btn.setAttribute('data-copied', '1');
2831 setTimeout(function(){
2832 btn.textContent = prev || 'Copy';
2833 btn.removeAttribute('data-copied');
2834 }, 1500);
2835 };
2836 if (navigator.clipboard && navigator.clipboard.writeText) {
2837 navigator.clipboard.writeText(text).then(done, function(){ done(); });
2838 } else {
2839 // Legacy fallback — temp textarea + execCommand.
2840 try {
2841 var ta = document.createElement('textarea');
2842 ta.value = text;
2843 ta.style.position = 'fixed';
2844 ta.style.opacity = '0';
2845 document.body.appendChild(ta);
2846 ta.select();
2847 document.execCommand('copy');
2848 document.body.removeChild(ta);
2849 done();
2850 } catch (_) {}
2851 }
2852 });
2853 });
2854 } catch (_) { /* swallow */ }
2855})();
2856`;