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.tsxBlame2815 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>
5f2e749Claude164 <a href="/demo" class="btn btn-secondary btn-xl">
165 Try the live demo
166 <span class="landing-cta-arrow" aria-hidden="true">{"→"}</span>
958d26aClaude167 </a>
52ad8b1Claude168 <a href="/vs-github" class="btn btn-ghost btn-xl">
169 Compare to GitHub
5f2e749Claude170 <span class="landing-cta-arrow" aria-hidden="true">{"→"}</span>
52ad8b1Claude171 </a>
958d26aClaude172 </div>
173
5f2e749Claude174 {/* L10 — "what just happened" rail. Mini, secondary,
175 separate from the BIG L4 counters tile section below. */}
176 {publicStats && (
177 <ul class="landing-hero-rail" aria-label="What just happened on Gluecron">
178 <li>
179 <span class="landing-hero-rail-check" aria-hidden="true">{"✓"}</span>
180 <strong>{publicStats.weeklyPrsAutoMerged.toLocaleString()}</strong>
181 {" PRs auto-merged this week"}
182 </li>
183 <li>
184 <span class="landing-hero-rail-check" aria-hidden="true">{"✓"}</span>
185 <strong>{publicStats.weeklyIssuesBuiltByAi.toLocaleString()}</strong>
186 {" issues built by AI"}
187 </li>
188 <li>
189 <span class="landing-hero-rail-check" aria-hidden="true">{"✓"}</span>
190 <strong>{publicStats.weeklyDeploysShipped.toLocaleString()}</strong>
191 {" deploys shipped overnight"}
192 </li>
193 <li>
194 <span class="landing-hero-rail-check" aria-hidden="true">{"✓"}</span>
195 {"~"}
196 <strong>{Math.round(publicStats.weeklyHoursSaved).toLocaleString()}</strong>
197 {" hours saved by AI"}
198 </li>
199 </ul>
200 )}
201
202 {/* L8 — free-tier reassurance link. Keeps anxiety low for the AI-curious. */}
203 <p class="landing-hero-freenote">
204 Free forever for the AI-curious.{" "}
205 <a href="/pricing" class="landing-hero-freenote-link">
206 See pricing &rarr;
207 </a>
208 </p>
209
958d26aClaude210 <p class="landing-hero-caption">
211 Already have a repo?
212 <span class="landing-hero-cmd">
213 <span class="kbd">git</span>
214 <span class="kbd">remote</span>
215 <span class="kbd">add</span>
216 <span class="kbd">gluecron</span>
217 <span class="landing-hero-arrow">{"→"}</span>
218 <span class="kbd">git push</span>
219 </span>
220 </p>
221
222 {hasStats && (
223 <p class="landing-stats">
224 {stats!.publicRepos !== undefined && stats!.publicRepos > 0 && (
225 <span>
226 <strong>{stats!.publicRepos.toLocaleString()}</strong>
227 {stats!.publicRepos === 1 ? " repo" : " repos"}
228 </span>
229 )}
230 {stats!.publicRepos !== undefined &&
231 stats!.publicRepos > 0 &&
232 stats!.users !== undefined &&
233 stats!.users > 0 && <span class="landing-stats-sep">·</span>}
234 {stats!.users !== undefined && stats!.users > 0 && (
235 <span>
236 <strong>{stats!.users.toLocaleString()}</strong>
237 {stats!.users === 1 ? " developer" : " developers"}
238 </span>
239 )}
240 <span class="landing-stats-sep">·</span>
4c47454Claude241 <span>
958d26aClaude242 <strong>100%</strong> AI-native
4c47454Claude243 </span>
958d26aClaude244 </p>
245 )}
246 </div>
c963db5Claude247 </section>
c475ee6Claude248
534f04aClaude249 {/* ---------- Block M1 — Live-now demo feed ---------- */}
250 <LiveNowSection
251 queued={liveQueued}
252 merges={liveMerges}
253 reviews={liveReviews}
254 reviewCount={liveReviewCount}
255 feed={liveEntries}
256 />
257
52ad8b1Claude258 {/* ---------- L4 social-proof counters (animated count-up) ---------- */}
259 {tiles && (
260 <section class="landing-counters" aria-label="Gluecron live counters">
261 <div class="landing-counters-grid">
262 {tiles.map((t) => (
263 <div class="landing-counter">
264 <div
265 class="landing-counter-num"
266 data-counter-target={String(t.value)}
267 data-counter-suffix={t.suffix ?? ""}
268 data-counter-prefix={t.prefix ?? ""}
269 >
270 {t.prefix ?? ""}
271 {t.value.toLocaleString()}
272 {t.suffix ?? ""}
273 </div>
274 <div class="landing-counter-label">{t.label}</div>
275 </div>
276 ))}
277 </div>
278 <script dangerouslySetInnerHTML={{ __html: landingCountersJs }} />
279 </section>
280 )}
281
5f2e749Claude282 {/* ---------- L10 — Three reasons to switch ---------- */}
283 <section class="landing-section landing-reasons" aria-label="Three reasons to switch">
284 <div class="section-header">
285 <div class="eyebrow">Three reasons to switch</div>
286 <h2>Built so Claude can do the work.</h2>
287 </div>
288 <div class="landing-reasons-grid">
289 <ReasonCard
290 icon={
291 <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">
292 <path d="M21 12.8A9 9 0 1 1 11.2 3a7 7 0 0 0 9.8 9.8z" />
293 </svg>
294 }
295 title="Toggle Sleep Mode"
296 body="Claude does the work overnight. You get a 9 AM digest of what shipped — PRs merged, deploys live, incidents triaged."
297 link={{ href: "/sleep-mode", label: "Turn on Sleep Mode" }}
298 />
299 <ReasonCard
300 icon={
301 <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">
302 <polyline points="4 17 10 11 4 5" />
303 <line x1="12" y1="19" x2="20" y2="19" />
304 </svg>
305 }
306 title="One command to migrate"
307 body="Drop a single curl into your shell. Gluecron rehosts your repo, your issues, your branches — no SaaS rip-and-replace project required."
308 extra={
309 <code class="landing-reasons-code">curl -sSL gluecron.com/install | bash</code>
310 }
311 link={{ href: "/import", label: "Or import from GitHub" }}
312 />
313 <ReasonCard
314 icon={
315 <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">
316 <polygon points="5 3 19 12 5 21 5 3" />
317 </svg>
318 }
319 title="Open the demo, watch it work"
320 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."
321 link={{ href: "/demo", label: "Open the live demo" }}
322 />
323 </div>
324 </section>
325
c963db5Claude326 {/* ---------- Capability strip — uppercase tracked grid (crontech-style) ---------- */}
327 <section class="landing-caps">
328 <div class="landing-caps-grid">
329 <span class="landing-cap">Claude-powered AI</span>
330 <span class="landing-cap">Spec-to-PR</span>
331 <span class="landing-cap">Auto-repair</span>
332 <span class="landing-cap">Real-time gates</span>
333 <span class="landing-cap">MCP-native</span>
334 <span class="landing-cap">Workflow runner</span>
335 <span class="landing-cap">Self-hostable</span>
336 <span class="landing-cap">Branch protection</span>
337 <span class="landing-cap">Bun + Hono</span>
338 <span class="landing-cap">Drizzle + Postgres</span>
339 <span class="landing-cap">JSX server-rendered</span>
340 <span class="landing-cap">Type-safe end to end</span>
c475ee6Claude341 </div>
958d26aClaude342 </section>
343
c963db5Claude344 {/* ---------- Big stat row (crontech-style hero closer) ---------- */}
345 <section class="landing-bigstats">
346 <div class="landing-bigstats-grid">
347 <div class="landing-bigstat">
348 <div class="landing-bigstat-num">Claude-powered</div>
349 <div class="landing-bigstat-label">The best AI, native</div>
350 </div>
351 <div class="landing-bigstat">
352 <div class="landing-bigstat-num">Self-hosted</div>
353 <div class="landing-bigstat-label">On your hardware</div>
354 </div>
355 <div class="landing-bigstat">
356 <div class="landing-bigstat-num">MCP-native</div>
357 <div class="landing-bigstat-label">Claude · Cursor · Code</div>
358 </div>
359 <div class="landing-bigstat">
360 <div class="landing-bigstat-num">Real-time</div>
361 <div class="landing-bigstat-label">SSE everywhere</div>
362 </div>
958d26aClaude363 </div>
4c47454Claude364 </section>
365
366 {/* ---------- Feature grid ---------- */}
958d26aClaude367 <section class="landing-section">
368 <div class="section-header">
369 <div class="eyebrow">The platform</div>
370 <h2>An IDE for your repo, not just a host.</h2>
371 <p>
372 Gluecron ships the surfaces GitHub charges extra for, and the
373 ones it never built. AI is a teammate with its own commits, not
374 a sidebar.
2b821b7Claude375 </p>
376 </div>
4c47454Claude377
958d26aClaude378 <div class="landing-features stagger">
379 <FeatureCard
380 icon={
381 <svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round">
382 <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" />
383 </svg>
384 }
385 title="AI as a teammate"
386 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."
387 />
388 <FeatureCard
389 icon={
390 <svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round">
391 <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" />
392 <path d="M9 12l2 2 4-4" />
393 </svg>
394 }
395 title="Quality gate that learns"
396 desc="GateTest scans every push. Auto-repair fixes regressions before you see them. Required checks block bad PRs from merging. Your software self-corrects."
397 />
398 <FeatureCard
399 icon={
400 <svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round">
401 <path d="M13 2L4 14h7l-1 8 9-12h-7z" />
402 </svg>
403 }
404 title="Real-time everything"
405 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."
406 />
407 <FeatureCard
408 icon={
409 <svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round">
410 <rect x="3" y="3" width="18" height="18" rx="3" />
411 <path d="M3 9h18M9 21V9" />
412 </svg>
413 }
414 title="Workflow runner"
415 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."
416 />
417 <FeatureCard
418 icon={
419 <svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round">
420 <circle cx="12" cy="12" r="9" />
421 <path d="M3 12h18M12 3a14 14 0 010 18M12 3a14 14 0 000 18" />
422 </svg>
423 }
424 title="MCP-native"
425 desc="Claude, Cursor, Code — they speak Model Context Protocol. Gluecron exposes search, file read, issues, codebase explain as MCP tools by default."
426 />
427 <FeatureCard
428 icon={
429 <svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round">
430 <path d="M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
431 <path d="M12 7v5l3 2" />
432 </svg>
433 }
434 title="Yours, on your hardware"
435 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."
436 />
2b821b7Claude437 </div>
958d26aClaude438 </section>
2b821b7Claude439
958d26aClaude440 {/* ---------- Workflow walkthrough ---------- */}
441 <section class="landing-section landing-walk">
442 <div class="section-header">
443 <div class="eyebrow">How it works</div>
444 <h2>Push code. Watch it ship.</h2>
445 <p>
446 Every push triggers the same pipeline whether the commit came
447 from you, from CI, or from an AI agent.
4c47454Claude448 </p>
449 </div>
958d26aClaude450
451 <div class="landing-walk-grid">
452 <WalkStep n="01" title="Push" desc="git push to gluecron — Smart-HTTP, SSH, or via the web editor." />
453 <WalkStep n="02" title="Gate" desc="GateTest runs. Secret scanner runs. AI security review posts inline comments." />
454 <WalkStep n="03" title="Repair" desc="If a gate fails, auto-repair tries to fix it. New commit gets re-gated." />
455 <WalkStep n="04" title="Ship" desc="Green push to default branch fires deploy webhook. Crontech, Fly, your prod." />
456 </div>
4c47454Claude457 </section>
2b821b7Claude458
4c47454Claude459 {/* ---------- Terminal block ---------- */}
958d26aClaude460 <section class="landing-section landing-terminal-section">
461 <div class="landing-terminal-wrap">
462 <div class="landing-terminal" role="img" aria-label="Example git push to gluecron with passing gates">
463 <div class="landing-terminal-chrome">
464 <span class="landing-terminal-dot landing-terminal-dot-r" />
465 <span class="landing-terminal-dot landing-terminal-dot-y" />
466 <span class="landing-terminal-dot landing-terminal-dot-g" />
467 <span class="landing-terminal-title">~/your-repo &mdash; zsh</span>
468 </div>
469 <div class="landing-terminal-body">
470 <div class="landing-term-line">
471 <span class="landing-term-prompt">$</span>
472 <span>git remote add gluecron https://gluecron.com/you/your-repo.git</span>
473 </div>
474 <div class="landing-term-line">
475 <span class="landing-term-prompt">$</span>
476 <span>git push -u gluecron main</span>
477 </div>
478 <div class="landing-term-line landing-term-out">
479 <span class="landing-term-meta">remote:</span>
480 <span>Resolving deltas… 100% (24/24)</span>
481 </div>
482 <div class="landing-term-line landing-term-out landing-term-ok-line">
483 <span class="landing-term-ok">{"✓"}</span>
484 <span>pushed to gluecron.com/you/your-repo</span>
485 </div>
486 <div class="landing-term-line landing-term-out landing-term-ok-line">
487 <span class="landing-term-ok">{"✓"}</span>
488 <span>GateTest passed (12 rules, 0 violations)</span>
489 </div>
490 <div class="landing-term-line landing-term-out landing-term-ok-line">
491 <span class="landing-term-ok">{"✓"}</span>
492 <span>AI review posted (2 suggestions, 0 blockers)</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>deployed to your-repo.gluecron.com <span class="landing-term-meta">(4.1s)</span></span>
497 </div>
498 <div class="landing-term-line landing-term-cursor">
499 <span class="landing-term-prompt">$</span>
500 <span class="landing-term-blink">▍</span>
501 </div>
502 </div>
4c47454Claude503 </div>
958d26aClaude504 </div>
505 </section>
506
507 {/* ---------- Comparison ---------- */}
508 <section class="landing-section">
509 <div class="section-header">
510 <div class="eyebrow">vs the incumbent</div>
511 <h2>Everything GitHub charges for. And the parts they didn't build.</h2>
512 </div>
513
514 <div class="landing-compare">
515 <CompareRow feature="Git hosting + Smart-HTTP push" them="✓" us="✓" />
516 <CompareRow feature="Issues, PRs, code review" them="✓" us="✓" />
517 <CompareRow feature="Workflow runner (Actions-equivalent)" them="paid minutes" us="self-hosted, unmetered" highlight />
518 <CompareRow feature="AI code review on every PR" them="Copilot subscription" us="built in" highlight />
519 <CompareRow feature="Spec-to-PR (NL feature → draft PR)" them="—" us="✓" highlight />
520 <CompareRow feature="Auto-repair on failed gates" them="—" us="✓" highlight />
521 <CompareRow feature="Real-time SSE for logs + PRs" them="polling" us="streaming" highlight />
522 <CompareRow feature="MCP server (Claude / Cursor)" them="—" us="✓" highlight />
523 <CompareRow feature="Self-host on your own infra" them="enterprise tier" us="single binary" highlight />
524 <CompareRow feature="Pre-receive policy enforcement" them="rulesets (GHE)" us="✓" />
525 </div>
526 </section>
527
528 {/* ---------- Pricing teaser ---------- */}
529 <section class="landing-section">
530 <div class="section-header">
531 <div class="eyebrow">Pricing</div>
532 <h2>Free to start. Honest at scale.</h2>
533 <p>
534 Self-hosting is free forever. Hosted plans price the AI calls,
535 not the seats.
536 </p>
537 </div>
538
539 <div class="landing-pricing">
540 <PricingCard
541 tier="Free"
542 price="$0"
543 cadence="forever"
544 desc="For personal projects + open source. Public + private repos, full AI suite, fair quotas."
545 features={["Unlimited public repos", "3 private repos", "5K AI calls / mo", "Community support"]}
546 cta="Start free"
547 href="/register"
548 />
549 <PricingCard
550 tier="Pro"
551 price="$12"
552 cadence="per user / mo"
553 desc="For working developers. Lifts every quota, adds priority routing, no Gluecron branding on deploys."
554 features={["Unlimited private repos", "100K AI calls / mo", "Priority queue", "Custom domains"]}
555 cta="Go Pro"
556 href="/settings/billing"
557 highlight
558 />
559 <PricingCard
560 tier="Team"
561 price="Talk to us"
562 cadence="custom"
563 desc="For orgs running production on Gluecron. SSO, audit retention, enterprise SLA, on-prem."
564 features={["SSO + SCIM", "On-prem deploy", "Dedicated capacity", "24/7 incident response"]}
565 cta="Contact"
566 href="mailto:hello@gluecron.com"
567 />
568 </div>
569 </section>
570
5f2e749Claude571 {/* ---------- L10 — "How is this different?" pull-quote ---------- */}
572 <section class="landing-pullquote-section" aria-label="How is this different from GitHub?">
573 <figure class="landing-pullquote">
574 <div class="landing-pullquote-eyebrow">How is this different from GitHub?</div>
575 <blockquote class="landing-pullquote-text">
576 Every other host bolts AI on as a sidecar. Gluecron is the first
577 git host where Claude is a first-class developer. Built to be
578 operated by AI agents, not just augmented by them.
579 </blockquote>
580 <a href="/vs-github" class="landing-pullquote-link">
581 See the full comparison
582 <span class="landing-cta-arrow" aria-hidden="true">{"→"}</span>
583 </a>
584 </figure>
585 </section>
586
958d26aClaude587 {/* ---------- Closing CTA ---------- */}
588 <section class="landing-cta-section">
589 <div class="landing-cta-card">
590 <div class="landing-cta-bg" aria-hidden="true" />
591 <div class="eyebrow">Ready when you are</div>
592 <h2 class="landing-cta-title">
593 Stop maintaining the platform.<br />
594 <span class="gradient-text">Start shipping the product.</span>
595 </h2>
596 <p class="landing-cta-sub">
597 Free to start, self-hosted-friendly, MCP-native. Migrate from
598 GitHub in one click.
599 </p>
600 <div class="landing-cta-buttons">
601 <a href="/register" class="btn btn-primary btn-xl">
602 Create your account
603 <span class="landing-cta-arrow" aria-hidden="true">{"→"}</span>
604 </a>
605 <a href="/import" class="btn btn-ghost btn-xl">
606 Migrate a repo
607 </a>
4c47454Claude608 </div>
609 </div>
610 </section>
5f2e749Claude611
612 {/* L10 — clipboard copy script for the hero install snippet. */}
613 <script dangerouslySetInnerHTML={{ __html: landingCopyJs }} />
4c47454Claude614 </div>
2b821b7Claude615 </>
616 );
617};
618
958d26aClaude619const FeatureCard: FC<{ icon: any; title: string; desc: string }> = ({
620 icon,
621 title,
622 desc,
623}) => (
624 <div class="landing-feature">
625 <div class="landing-feature-icon" aria-hidden="true">
626 {icon}
627 </div>
628 <h3 class="landing-feature-title">{title}</h3>
629 <p class="landing-feature-desc">{desc}</p>
630 </div>
631);
632
5f2e749Claude633// Block L10 — "Three reasons to switch" column.
634const ReasonCard: FC<{
635 icon: any;
636 title: string;
637 body: string;
638 link: { href: string; label: string };
639 extra?: any;
640}> = ({ icon, title, body, link, extra }) => (
641 <div class="landing-reason">
642 <div class="landing-reason-icon" aria-hidden="true">
643 {icon}
644 </div>
645 <h3 class="landing-reason-title">{title}</h3>
646 <p class="landing-reason-body">{body}</p>
647 {extra}
648 <a href={link.href} class="landing-reason-link">
649 {link.label}
650 <span class="landing-cta-arrow" aria-hidden="true">{"→"}</span>
651 </a>
652 </div>
653);
654
958d26aClaude655const WalkStep: FC<{ n: string; title: string; desc: string }> = ({
656 n,
657 title,
658 desc,
659}) => (
660 <div class="landing-walk-step">
661 <div class="landing-walk-num">{n}</div>
662 <h3 class="landing-walk-title">{title}</h3>
663 <p class="landing-walk-desc">{desc}</p>
664 </div>
665);
666
667const CompareRow: FC<{
668 feature: string;
669 them: string;
670 us: string;
671 highlight?: boolean;
672}> = ({ feature, them, us, highlight }) => (
673 <div class={`landing-compare-row${highlight ? " landing-compare-hl" : ""}`}>
674 <div class="landing-compare-feature">{feature}</div>
675 <div class="landing-compare-them">{them}</div>
676 <div class="landing-compare-us">{us === "✓" ? "✓" : us}</div>
677 </div>
678);
679
680const PricingCard: FC<{
681 tier: string;
682 price: string;
683 cadence: string;
684 desc: string;
685 features: string[];
686 cta: string;
687 href: string;
688 highlight?: boolean;
689}> = ({ tier, price, cadence, desc, features, cta, href, highlight }) => (
690 <div class={`landing-price-card${highlight ? " landing-price-hl" : ""}`}>
691 {highlight && <div class="landing-price-badge">Most popular</div>}
692 <div class="landing-price-tier">{tier}</div>
693 <div class="landing-price-amount">
694 <span class="landing-price-num">{price}</span>
695 <span class="landing-price-cad">{cadence}</span>
696 </div>
697 <p class="landing-price-desc">{desc}</p>
698 <ul class="landing-price-features">
699 {features.map((f) => (
700 <li>
701 <span class="landing-price-check" aria-hidden="true">{"✓"}</span>
702 {f}
703 </li>
704 ))}
705 </ul>
706 <a
707 href={href}
708 class={`btn ${highlight ? "btn-primary" : "btn-secondary"} btn-block landing-price-cta`}
709 >
710 {cta}
711 </a>
712 </div>
713);
714
52ad8b1Claude715// ─────────────────────────────────────────────────────────────────
716// Block L4 — social-proof tile builder.
717//
718// Pure: takes the cached PublicStats payload and emits the six
719// landing-page tiles in render order. Exported so tests / future
720// surfaces (dashboard, /about, …) can share the exact same copy.
721// ─────────────────────────────────────────────────────────────────
722
723export interface SocialProofTile {
724 label: string;
725 value: number;
726 prefix?: string;
727 suffix?: string;
728}
729
730export function buildSocialProofTiles(s: PublicStats): SocialProofTile[] {
731 return [
732 { label: "Public repos", value: s.totalPublicRepos },
733 { label: "Developers", value: s.totalUsers },
734 {
735 label: "PRs auto-merged this week",
736 value: s.weeklyPrsAutoMerged,
737 },
738 {
739 label: "Issues built by AI this week",
740 value: s.weeklyIssuesBuiltByAi,
741 },
742 {
743 label: "Deploys shipped this week",
744 value: s.weeklyDeploysShipped,
745 },
746 {
747 label: "Hours saved this week",
748 // Round to whole hours for the tile — the precise 0.1 figure
749 // lives on the dashboard widget; the marketing surface keeps
750 // the number scannable.
751 value: Math.round(s.weeklyHoursSaved),
752 prefix: "~",
753 suffix: "h",
754 },
755 ];
756}
757
534f04aClaude758// ─────────────────────────────────────────────────────────────────
759// Block M1 — Live-now demo feed.
760//
761// A 4-tile row inserted between the hero and the L4 counters tile
762// section, surfacing real autopilot activity from the seeded `demo`
763// owner's repos:
764// 1. Issues queued for AI build (ai:build label, open)
765// 2. PRs auto-merged in the last 24h
766// 3. AI reviews posted today (count + latest 3)
767// 4. Combined activity feed (last 10)
768//
769// SSR-renders an initial snapshot, then re-fetches every 30s via the
770// L3 JSON endpoints so the page feels alive without a websocket.
771// Pure presentational; the route layer owns the DB reads.
772// ─────────────────────────────────────────────────────────────────
773
774/**
775 * Render an ISO timestamp (or Date) as a coarse "about N units ago"
776 * string. Tolerates strings, Dates, NaN, and future timestamps.
777 *
778 * Exported for unit testing.
779 */
780export function relativeTimeFromNow(
781 value: string | Date | number | null | undefined,
782 now: number = Date.now()
783): string {
784 if (value === null || value === undefined) return "just now";
785 let t: number;
786 if (value instanceof Date) {
787 t = value.getTime();
788 } else if (typeof value === "number") {
789 t = value;
790 } else {
791 t = new Date(value).getTime();
792 }
793 if (!Number.isFinite(t)) return "just now";
794 const delta = now - t;
795 // Future timestamps (clock skew) — treat as "just now" rather than
796 // surfacing a confusing negative.
797 if (delta < 0) return "just now";
798 const s = Math.floor(delta / 1000);
799 if (s < 60) return "just now";
800 const m = Math.floor(s / 60);
801 if (m < 60) return `about ${m} minute${m === 1 ? "" : "s"} ago`;
802 const h = Math.floor(m / 60);
803 if (h < 24) return `about ${h} hour${h === 1 ? "" : "s"} ago`;
804 const d = Math.floor(h / 24);
805 return `about ${d} day${d === 1 ? "" : "s"} ago`;
806}
807
808function feedEntryId(e: LandingLiveFeedEntry): string {
809 const at = e.at instanceof Date ? e.at.toISOString() : String(e.at);
810 return `${e.kind}|${e.repo}|${e.ref.type}|${e.ref.number}|${at}`;
811}
812
813function feedEntryLabel(kind: LandingLiveFeedEntry["kind"]): string {
814 switch (kind) {
815 case "auto_merge.merged":
816 return "auto-merged";
817 case "ai_build.dispatched":
818 return "AI-build queued";
819 case "ai_review.posted":
820 return "AI review posted";
821 }
822}
823
824interface LiveNowSectionProps {
825 queued: LandingLiveFeedQueued[];
826 merges: LandingLiveFeedMerge[];
827 reviews: LandingLiveFeedReview[];
828 reviewCount: number;
829 feed: LandingLiveFeedEntry[];
830}
831
832const LiveNowSection: FC<LiveNowSectionProps> = ({
833 queued,
834 merges,
835 reviews,
836 reviewCount,
837 feed,
838}) => {
839 return (
840 <section class="landing-livenow" aria-labelledby="landing-livenow-h">
841 <div class="landing-livenow-head">
842 <div class="landing-livenow-eyebrow">
843 <span class="landing-livenow-pulse" aria-hidden="true" />
844 Live now
845 </div>
846 <h2 id="landing-livenow-h" class="landing-livenow-title">
847 Claude is working on demo repos as you read this.
848 </h2>
849 <p class="landing-livenow-sub">
850 Every card below is real data from the public{" "}
851 <code>{DEMO_USERNAME}/*</code> repos. Refreshes every 30 seconds.
852 </p>
853 </div>
854
855 <div class="landing-livenow-grid" data-livenow-grid>
856 {/* Card 1 — queued issues */}
857 <article class="landing-livecard" aria-labelledby="lc-queued-h">
858 <header class="landing-livecard-head">
859 <span class="landing-livecard-dot" aria-hidden="true" />
860 <h3 id="lc-queued-h" class="landing-livecard-title">
861 Issues queued for AI
862 </h3>
863 </header>
864 <ul class="landing-livecard-list" data-livecard="queued">
865 {queued.length === 0 ? (
866 <li class="landing-livecard-empty">
867 No queued AI builds — quiet right now.
868 </li>
869 ) : (
870 queued.slice(0, 3).map((i) => (
871 <li
872 class="landing-livecard-row"
873 data-row-id={`queued|${i.repo}|${i.number}`}
874 >
875 <a
876 class="landing-livecard-link"
877 href={`/${DEMO_USERNAME}/${i.repo}/issues/${i.number}`}
878 >
879 <span class="landing-livecard-num">#{i.number}</span>{" "}
880 <span class="landing-livecard-title-text">{i.title}</span>
881 </a>
882 <div class="landing-livecard-meta">
883 <span class="landing-livecard-repo">{i.repo}</span>
884 </div>
885 </li>
886 ))
887 )}
888 </ul>
889 </article>
890
891 {/* Card 2 — recently merged */}
892 <article class="landing-livecard" aria-labelledby="lc-merges-h">
893 <header class="landing-livecard-head">
894 <span class="landing-livecard-dot" aria-hidden="true" />
895 <h3 id="lc-merges-h" class="landing-livecard-title">
896 Recently merged by AI
897 </h3>
898 </header>
899 <ul class="landing-livecard-list" data-livecard="merges">
900 {merges.length === 0 ? (
901 <li class="landing-livecard-empty">
902 No auto-merges in the last 24h.
903 </li>
904 ) : (
905 merges.slice(0, 3).map((m) => (
906 <li
907 class="landing-livecard-row"
908 data-row-id={`merges|${m.repo}|${m.number}`}
909 >
910 <a
911 class="landing-livecard-link"
912 href={`/${DEMO_USERNAME}/${m.repo}/pulls/${m.number}`}
913 >
914 <span class="landing-livecard-num">#{m.number}</span>{" "}
915 <span class="landing-livecard-title-text">{m.title}</span>
916 </a>
917 <div class="landing-livecard-meta">
918 AI merged in{" "}
919 <span class="landing-livecard-repo">{m.repo}</span>{" "}
920 <span
921 class="landing-livecard-rel"
922 data-rel={
923 m.mergedAt instanceof Date
924 ? m.mergedAt.toISOString()
925 : String(m.mergedAt)
926 }
927 >
928 {relativeTimeFromNow(m.mergedAt)}
929 </span>
930 </div>
931 </li>
932 ))
933 )}
934 </ul>
935 </article>
936
937 {/* Card 3 — AI reviews */}
938 <article class="landing-livecard" aria-labelledby="lc-reviews-h">
939 <header class="landing-livecard-head">
940 <span class="landing-livecard-dot" aria-hidden="true" />
941 <h3 id="lc-reviews-h" class="landing-livecard-title">
942 AI reviews posted
943 </h3>
944 </header>
945 <div class="landing-livecard-bignum">
946 <span
947 class="landing-livecard-bignum-n"
948 data-livecard-count="reviews"
949 data-tick-target={String(reviewCount)}
950 >
951 {reviewCount.toLocaleString()}
952 </span>
953 <span class="landing-livecard-bignum-label">reviews today</span>
954 </div>
955 <ul class="landing-livecard-list" data-livecard="reviews">
956 {reviews.length === 0 ? (
957 <li class="landing-livecard-empty">
958 No AI reviews in the last 24h.
959 </li>
960 ) : (
961 reviews.slice(0, 3).map((r) => (
962 <li
963 class="landing-livecard-row"
964 data-row-id={`reviews|${r.repo}|${r.prNumber}`}
965 >
966 <a
967 class="landing-livecard-link"
968 href={`/${DEMO_USERNAME}/${r.repo}/pulls/${r.prNumber}`}
969 >
970 <span class="landing-livecard-num">#{r.prNumber}</span>{" "}
971 <span class="landing-livecard-snippet">
972 {r.commentSnippet}
973 </span>
974 </a>
975 <div class="landing-livecard-meta">
976 <span class="landing-livecard-repo">{r.repo}</span>
977 </div>
978 </li>
979 ))
980 )}
981 </ul>
982 </article>
983
984 {/* Card 4 — activity feed */}
985 <article class="landing-livecard" aria-labelledby="lc-feed-h">
986 <header class="landing-livecard-head">
987 <span class="landing-livecard-dot" aria-hidden="true" />
988 <h3 id="lc-feed-h" class="landing-livecard-title">
989 Activity feed
990 </h3>
991 </header>
992 <ul class="landing-livecard-list landing-livecard-feed" data-livecard="feed">
993 {feed.length === 0 ? (
994 <li class="landing-livecard-empty">
995 Quiet right now — check back in a minute.
996 </li>
997 ) : (
998 feed.slice(0, 10).map((e) => {
999 const path = e.ref.type === "pr" ? "pulls" : "issues";
1000 const id = feedEntryId(e);
1001 return (
1002 <li class="landing-livecard-feedrow" data-row-id={id}>
1003 <span
1004 class={`landing-livecard-kind landing-livecard-kind-${e.kind.replace(/\./g, "-")}`}
1005 >
1006 {feedEntryLabel(e.kind)}
1007 </span>{" "}
1008 <a
1009 class="landing-livecard-link"
1010 href={`/${DEMO_USERNAME}/${e.repo}/${path}/${e.ref.number}`}
1011 >
1012 {e.repo} #{e.ref.number}
1013 </a>{" "}
1014 <span
1015 class="landing-livecard-rel"
1016 data-rel={
1017 e.at instanceof Date ? e.at.toISOString() : String(e.at)
1018 }
1019 >
1020 {relativeTimeFromNow(e.at)}
1021 </span>
1022 </li>
1023 );
1024 })
1025 )}
1026 </ul>
1027 </article>
1028 </div>
1029
1030 <div class="landing-livenow-cta">
1031 <span class="landing-livenow-cta-text">
1032 Want this for your repos?
1033 </span>
1034 <a class="landing-livenow-cta-link" href="/register">
1035 Sign up free
1036 <span class="landing-cta-arrow" aria-hidden="true">{"→"}</span>
1037 </a>
1038 <span class="landing-livenow-cta-sep" aria-hidden="true">·</span>
1039 <a class="landing-livenow-cta-link" href="/demo">
1040 Try the live demo
1041 <span class="landing-cta-arrow" aria-hidden="true">{"→"}</span>
1042 </a>
1043 </div>
1044
1045 <script dangerouslySetInnerHTML={{ __html: liveNowJs }} />
1046 </section>
1047 );
1048};
1049
1050// Inline poller. Plain JS so we don't ship a separate bundle. Hits the
1051// four L3 JSON endpoints every 30s, re-renders the four cards, ticks
1052// the big number, refreshes relative timestamps, flashes new rows.
1053const liveNowJs = `
1054(function(){
1055try{
1056 var DEMO=${JSON.stringify(DEMO_USERNAME)};
1057 var INTERVAL=30000;
1058 function esc(s){return String(s==null?'':s).replace(/[&<>"']/g,function(c){return {'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#39;'}[c];});}
1059 function rel(v){
1060 if(v==null) return 'just now';
1061 var t=(v instanceof Date)?v.getTime():(typeof v==='number'?v:new Date(v).getTime());
1062 if(!isFinite(t)) return 'just now';
1063 var d=Date.now()-t;
1064 if(d<0) return 'just now';
1065 var s=Math.floor(d/1000);
1066 if(s<60) return 'just now';
1067 var m=Math.floor(s/60);
1068 if(m<60) return 'about '+m+' minute'+(m===1?'':'s')+' ago';
1069 var h=Math.floor(m/60);
1070 if(h<24) return 'about '+h+' hour'+(h===1?'':'s')+' ago';
1071 var dd=Math.floor(h/24);
1072 return 'about '+dd+' day'+(dd===1?'':'s')+' ago';
1073 }
1074 function tickNumber(el,target){
1075 if(!el) return;
1076 var start=parseInt(el.getAttribute('data-tick-current')||'0',10)||0;
1077 if(start===target){el.textContent=target.toLocaleString();el.setAttribute('data-tick-current',String(target));return;}
1078 var dur=800,t0=performance.now();
1079 function step(now){
1080 var p=Math.min(1,(now-t0)/dur);
1081 var eased=1-Math.pow(1-p,3);
1082 var v=Math.round(start+(target-start)*eased);
1083 el.textContent=v.toLocaleString();
1084 if(p<1) requestAnimationFrame(step); else el.setAttribute('data-tick-current',String(target));
1085 }
1086 requestAnimationFrame(step);
1087 }
1088 function flashRow(li){
1089 if(!li) return;
1090 li.classList.add('landing-livecard-flash');
1091 setTimeout(function(){li.classList.remove('landing-livecard-flash');},1100);
1092 }
1093 function diffMount(ul,newHtml,newIds){
1094 if(!ul) return;
1095 var prev={};
1096 var nodes=ul.querySelectorAll('[data-row-id]');
1097 for(var i=0;i<nodes.length;i++){prev[nodes[i].getAttribute('data-row-id')]=true;}
1098 ul.innerHTML=newHtml;
1099 var fresh=ul.querySelectorAll('[data-row-id]');
1100 for(var j=0;j<fresh.length;j++){
1101 var id=fresh[j].getAttribute('data-row-id');
1102 if(id && !prev[id]) flashRow(fresh[j]);
1103 }
1104 }
1105 function pollQueued(){
1106 return fetch('/api/v2/demo/queued',{credentials:'omit'}).then(function(r){return r.json();}).then(function(d){
1107 var ul=document.querySelector('[data-livecard="queued"]');if(!ul) return;
1108 var items=(d&&d.items)||[];
1109 if(items.length===0){ul.innerHTML='<li class="landing-livecard-empty">No queued AI builds — quiet right now.</li>';return;}
1110 var ids=[];
1111 var html=items.slice(0,3).map(function(i){
1112 var id='queued|'+i.repo+'|'+i.number;ids.push(id);
1113 return '<li class="landing-livecard-row" data-row-id="'+esc(id)+'">'+
1114 '<a class="landing-livecard-link" href="/'+esc(DEMO)+'/'+esc(i.repo)+'/issues/'+i.number+'">'+
1115 '<span class="landing-livecard-num">#'+i.number+'</span> '+
1116 '<span class="landing-livecard-title-text">'+esc(i.title)+'</span></a>'+
1117 '<div class="landing-livecard-meta"><span class="landing-livecard-repo">'+esc(i.repo)+'</span></div></li>';
1118 }).join('');
1119 diffMount(ul,html,ids);
1120 }).catch(function(){});
1121 }
1122 function pollMerges(){
1123 return fetch('/api/v2/demo/merges',{credentials:'omit'}).then(function(r){return r.json();}).then(function(d){
1124 var ul=document.querySelector('[data-livecard="merges"]');if(!ul) return;
1125 var items=(d&&d.items)||[];
1126 if(items.length===0){ul.innerHTML='<li class="landing-livecard-empty">No auto-merges in the last 24h.</li>';return;}
1127 var ids=[];
1128 var html=items.slice(0,3).map(function(m){
1129 var id='merges|'+m.repo+'|'+m.number;ids.push(id);
1130 return '<li class="landing-livecard-row" data-row-id="'+esc(id)+'">'+
1131 '<a class="landing-livecard-link" href="/'+esc(DEMO)+'/'+esc(m.repo)+'/pulls/'+m.number+'">'+
1132 '<span class="landing-livecard-num">#'+m.number+'</span> '+
1133 '<span class="landing-livecard-title-text">'+esc(m.title)+'</span></a>'+
1134 '<div class="landing-livecard-meta">AI merged in <span class="landing-livecard-repo">'+esc(m.repo)+'</span> '+
1135 '<span class="landing-livecard-rel" data-rel="'+esc(m.mergedAt)+'">'+esc(rel(m.mergedAt))+'</span></div></li>';
1136 }).join('');
1137 diffMount(ul,html,ids);
1138 }).catch(function(){});
1139 }
1140 function pollReviews(){
1141 return fetch('/api/v2/demo/reviews',{credentials:'omit'}).then(function(r){return r.json();}).then(function(d){
1142 var ul=document.querySelector('[data-livecard="reviews"]');if(!ul) return;
1143 var bigEl=document.querySelector('[data-livecard-count="reviews"]');
1144 var n=(d&&typeof d.count==='number')?d.count:0;
1145 if(bigEl) tickNumber(bigEl,n);
1146 var items=(d&&d.items)||[];
1147 if(items.length===0){ul.innerHTML='<li class="landing-livecard-empty">No AI reviews in the last 24h.</li>';return;}
1148 var ids=[];
1149 var html=items.slice(0,3).map(function(r){
1150 var id='reviews|'+r.repo+'|'+r.prNumber;ids.push(id);
1151 return '<li class="landing-livecard-row" data-row-id="'+esc(id)+'">'+
1152 '<a class="landing-livecard-link" href="/'+esc(DEMO)+'/'+esc(r.repo)+'/pulls/'+r.prNumber+'">'+
1153 '<span class="landing-livecard-num">#'+r.prNumber+'</span> '+
1154 '<span class="landing-livecard-snippet">'+esc(r.commentSnippet)+'</span></a>'+
1155 '<div class="landing-livecard-meta"><span class="landing-livecard-repo">'+esc(r.repo)+'</span></div></li>';
1156 }).join('');
1157 diffMount(ul,html,ids);
1158 }).catch(function(){});
1159 }
1160 function pollFeed(){
1161 return fetch('/api/v2/demo/activity',{credentials:'omit'}).then(function(r){return r.json();}).then(function(d){
1162 var ul=document.querySelector('[data-livecard="feed"]');if(!ul) return;
1163 var entries=(d&&d.entries)||[];
1164 if(entries.length===0){ul.innerHTML='<li class="landing-livecard-empty">Quiet right now — check back in a minute.</li>';return;}
1165 var ids=[];
1166 var html=entries.slice(0,10).map(function(e){
1167 var path=(e.ref&&e.ref.type==='pr')?'pulls':'issues';
1168 var num=(e.ref&&e.ref.number)||0;
1169 var label=e.kind==='auto_merge.merged'?'auto-merged':(e.kind==='ai_build.dispatched'?'AI-build queued':'AI review posted');
1170 var kindCls=String(e.kind||'').replace(/\\./g,'-');
1171 var id=e.kind+'|'+e.repo+'|'+(e.ref&&e.ref.type)+'|'+num+'|'+e.at;ids.push(id);
1172 return '<li class="landing-livecard-feedrow" data-row-id="'+esc(id)+'">'+
1173 '<span class="landing-livecard-kind landing-livecard-kind-'+esc(kindCls)+'">'+esc(label)+'</span> '+
1174 '<a class="landing-livecard-link" href="/'+esc(DEMO)+'/'+esc(e.repo)+'/'+path+'/'+num+'">'+esc(e.repo)+' #'+num+'</a> '+
1175 '<span class="landing-livecard-rel" data-rel="'+esc(e.at)+'">'+esc(rel(e.at))+'</span></li>';
1176 }).join('');
1177 diffMount(ul,html,ids);
1178 }).catch(function(){});
1179 }
1180 function refreshRel(){
1181 var spans=document.querySelectorAll('.landing-livecard-rel[data-rel]');
1182 for(var i=0;i<spans.length;i++){
1183 spans[i].textContent=rel(spans[i].getAttribute('data-rel'));
1184 }
1185 }
1186 function tickAll(){pollQueued();pollMerges();pollReviews();pollFeed();}
1187 // Initial counter tick (count-up from 0) on first paint.
1188 var bigEl0=document.querySelector('[data-livecard-count="reviews"]');
1189 if(bigEl0){
1190 var target=parseInt(bigEl0.getAttribute('data-tick-target')||'0',10)||0;
1191 bigEl0.textContent='0';
1192 tickNumber(bigEl0,target);
1193 }
1194 refreshRel();
1195 setInterval(tickAll,INTERVAL);
1196 setInterval(refreshRel,INTERVAL);
1197 document.addEventListener('visibilitychange',function(){
1198 if(document.visibilityState==='visible'){tickAll();refreshRel();}
1199 });
1200}catch(_){}})();
1201`.trim();
1202
4c47454Claude1203// Backwards-compatible default — web.tsx imports `LandingPage`.
1204export const LandingPage: FC<LandingPageProps> = (props) => (
1205 <LandingHero {...props} />
2b821b7Claude1206);
1207
4c47454Claude1208export default LandingPage;
1209
2b821b7Claude1210const landingCss = `
958d26aClaude1211 /* ============================================================ */
1212 /* Landing — Editorial-Technical 2026.05 */
1213 /* ============================================================ */
4c47454Claude1214 .landing-root {
1215 position: relative;
958d26aClaude1216 max-width: 1180px;
4c47454Claude1217 margin: 0 auto;
1218 padding: 0 16px;
958d26aClaude1219 }
1220 .landing-root > section { position: relative; }
1221
1222 /* ---------- Hero ---------- */
1223 .landing-hero {
1224 position: relative;
c475ee6Claude1225 padding: var(--s-16) 0 var(--s-20);
958d26aClaude1226 text-align: center;
4c47454Claude1227 overflow: hidden;
1228 }
c475ee6Claude1229 .landing-hero-blob-1 {
1230 animation: hero-blob-drift-1 18s var(--ease, ease) infinite alternate;
1231 }
1232 .landing-hero-blob-2 {
1233 animation: hero-blob-drift-2 22s var(--ease, ease) infinite alternate;
1234 }
1235 @keyframes hero-blob-drift-1 {
1236 0% { transform: translate(0, 0) scale(1); opacity: 0.55; }
1237 100% { transform: translate(8%, 6%) scale(1.18); opacity: 0.75; }
1238 }
1239 @keyframes hero-blob-drift-2 {
1240 0% { transform: translate(0, 0) scale(1); opacity: 0.40; }
1241 100% { transform: translate(-10%, -4%) scale(1.25); opacity: 0.60; }
1242 }
1243
1244 /* ---------- Hero product visual: live AI PR review card ---------- */
1245 .landing-hero-visual {
1246 position: relative;
1247 max-width: 760px;
1248 margin: var(--s-12) auto 0;
1249 padding: 0 16px;
1250 perspective: 1400px;
1251 z-index: 2;
1252 opacity: 0;
1253 animation: hero-visual-in 700ms var(--ease-out-expo, cubic-bezier(0.19, 1, 0.22, 1)) 400ms forwards;
1254 }
1255 @keyframes hero-visual-in {
1256 from { opacity: 0; transform: translateY(20px); }
1257 to { opacity: 1; transform: translateY(0); }
1258 }
1259 .hero-pr-card {
1260 position: relative;
1261 background: linear-gradient(180deg, rgba(15,17,26,0.96) 0%, rgba(8,9,15,0.96) 100%);
1262 border: 1px solid var(--border-strong);
1263 border-radius: var(--r-xl);
1264 overflow: hidden;
1265 text-align: left;
1266 box-shadow:
1267 0 30px 80px -20px rgba(0,0,0,0.65),
1268 0 0 0 1px rgba(140,109,255,0.18),
1269 0 0 60px -10px rgba(140,109,255,0.30);
1270 transform: rotateX(2deg) rotateY(-2deg);
1271 transition: transform 600ms var(--ease, ease);
1272 backdrop-filter: blur(12px);
1273 -webkit-backdrop-filter: blur(12px);
1274 }
1275 .landing-hero-visual:hover .hero-pr-card {
1276 transform: rotateX(0deg) rotateY(0deg);
1277 }
1278 .hero-pr-card::before {
1279 content: '';
1280 position: absolute;
1281 inset: 0;
1282 background: linear-gradient(135deg, rgba(140,109,255,0.10), transparent 35%, transparent 65%, rgba(54,197,214,0.08));
1283 pointer-events: none;
1284 }
1285 .hero-pr-header {
1286 display: flex;
1287 align-items: center;
1288 gap: 12px;
1289 padding: 14px 18px;
1290 border-bottom: 1px solid rgba(255,255,255,0.06);
1291 background: rgba(255,255,255,0.025);
1292 font-size: 13px;
1293 }
1294 .hero-pr-dot {
1295 width: 10px; height: 10px;
1296 border-radius: 50%;
1297 background: var(--green);
1298 box-shadow: 0 0 10px rgba(52,211,153,0.6);
1299 flex-shrink: 0;
1300 }
1301 .hero-pr-title {
1302 color: var(--text-strong);
1303 font-weight: 600;
1304 flex: 1;
1305 overflow: hidden;
1306 text-overflow: ellipsis;
1307 white-space: nowrap;
1308 }
1309 .hero-pr-num {
1310 color: var(--text-faint);
1311 font-family: var(--font-mono);
1312 font-weight: 500;
1313 margin-right: 8px;
1314 }
1315 .hero-pr-status {
1316 display: inline-flex;
1317 align-items: center;
1318 gap: 6px;
1319 padding: 3px 10px;
1320 border-radius: var(--r-full);
1321 background: var(--accent-gradient-faint);
1322 border: 1px solid rgba(140,109,255,0.30);
1323 color: var(--accent);
1324 font-family: var(--font-mono);
1325 font-size: 11px;
1326 letter-spacing: 0.04em;
1327 flex-shrink: 0;
1328 }
1329 .hero-pr-status-pulse {
1330 width: 6px; height: 6px;
1331 border-radius: 50%;
1332 background: var(--accent);
1333 box-shadow: 0 0 0 0 rgba(140,109,255,0.6);
1334 animation: hero-pulse 1.6s ease-out infinite;
1335 }
1336 @keyframes hero-pulse {
1337 0% { box-shadow: 0 0 0 0 rgba(140,109,255,0.55); }
1338 70% { box-shadow: 0 0 0 8px rgba(140,109,255,0); }
1339 100% { box-shadow: 0 0 0 0 rgba(140,109,255,0); }
1340 }
1341
1342 .hero-pr-body {
1343 padding: 0;
1344 }
1345 .hero-pr-file {
1346 display: flex;
1347 align-items: center;
1348 gap: 10px;
1349 padding: 10px 18px;
1350 border-bottom: 1px solid rgba(255,255,255,0.05);
1351 font-family: var(--font-mono);
1352 font-size: 12px;
1353 background: rgba(255,255,255,0.012);
1354 }
1355 .hero-pr-file-icon { color: var(--accent-2); }
1356 .hero-pr-file-name { color: var(--text); flex: 1; }
1357 .hero-pr-file-stats { display: inline-flex; gap: 8px; }
1358 .hero-pr-add { color: var(--green); font-weight: 600; }
1359 .hero-pr-del { color: var(--red); font-weight: 600; }
1360
1361 .hero-pr-diff {
1362 padding: 12px 18px;
1363 font-family: var(--font-mono);
1364 font-feature-settings: var(--mono-feat, 'calt');
1365 font-size: 12.5px;
1366 line-height: 1.7;
1367 color: rgba(237,237,242,0.85);
1368 overflow-x: auto;
1369 }
1370 .hero-pr-hunk {
1371 color: rgba(140,109,255,0.85);
1372 background: rgba(140,109,255,0.06);
1373 padding: 2px 8px;
1374 margin: 0 -8px 4px;
1375 border-radius: 4px;
1376 }
1377 .hero-pr-line-add {
1378 background: rgba(52,211,153,0.08);
1379 color: rgba(167,243,208,0.95);
1380 padding: 0 8px;
1381 margin: 0 -8px;
1382 border-left: 2px solid var(--green);
1383 padding-left: 8px;
1384 }
1385
1386 .hero-pr-comment {
1387 margin: 14px 18px;
1388 padding: 14px 16px;
1389 background: linear-gradient(135deg, rgba(140,109,255,0.08), rgba(54,197,214,0.05));
1390 border: 1px solid rgba(140,109,255,0.25);
1391 border-radius: var(--r-md);
1392 }
1393 .hero-pr-bot-row {
1394 display: flex;
1395 align-items: center;
1396 gap: 8px;
1397 margin-bottom: 8px;
1398 font-size: 12px;
1399 }
1400 .hero-pr-bot-avatar {
1401 width: 22px; height: 22px;
1402 display: inline-flex;
1403 align-items: center;
1404 justify-content: center;
1405 border-radius: 50%;
1406 background: var(--accent-gradient);
1407 font-size: 11px;
1408 box-shadow: 0 0 12px rgba(140,109,255,0.40);
1409 }
1410 .hero-pr-bot-name {
1411 color: var(--text-strong);
1412 font-weight: 600;
1413 }
1414 .hero-pr-bot-meta {
1415 color: var(--text-faint);
1416 font-family: var(--font-mono);
1417 }
1418 .hero-pr-bot-text {
1419 color: var(--text);
1420 font-size: 13px;
1421 line-height: 1.55;
1422 margin: 0;
1423 }
1424 .hero-pr-bot-text code {
1425 background: rgba(255,255,255,0.06);
1426 border: 1px solid rgba(255,255,255,0.10);
1427 padding: 1px 6px;
1428 border-radius: 4px;
1429 font-size: 11.5px;
1430 color: var(--accent);
1431 }
1432 .hero-pr-bot-link { color: var(--accent-2); text-decoration: underline; text-decoration-style: dotted; }
1433
1434 .hero-pr-gates {
1435 display: flex;
1436 flex-wrap: wrap;
1437 gap: 6px;
1438 padding: 12px 18px 16px;
1439 border-top: 1px solid rgba(255,255,255,0.06);
1440 background: rgba(255,255,255,0.012);
1441 }
1442 .hero-pr-gate {
1443 display: inline-flex;
1444 align-items: center;
1445 gap: 6px;
1446 padding: 4px 10px;
1447 border-radius: var(--r-full);
1448 font-family: var(--font-mono);
1449 font-size: 11px;
1450 border: 1px solid;
1451 }
1452 .hero-pr-gate-pass {
1453 color: var(--green);
1454 background: rgba(52,211,153,0.08);
1455 border-color: rgba(52,211,153,0.30);
1456 }
1457 .hero-pr-gate-running {
1458 color: var(--accent);
1459 background: var(--accent-gradient-faint);
1460 border-color: rgba(140,109,255,0.40);
1461 }
1462 .hero-pr-gate-spin {
1463 width: 9px; height: 9px;
1464 border: 1.5px solid rgba(140,109,255,0.30);
1465 border-top-color: var(--accent);
1466 border-radius: 50%;
1467 animation: hero-spin 800ms linear infinite;
1468 }
1469 @keyframes hero-spin {
1470 to { transform: rotate(360deg); }
1471 }
1472
1473 /* Floating accent badges around the card */
1474 .hero-float {
1475 position: absolute;
1476 display: inline-flex;
1477 align-items: center;
1478 gap: 6px;
1479 padding: 6px 12px;
1480 background: rgba(15,17,26,0.92);
1481 border: 1px solid rgba(140,109,255,0.35);
1482 border-radius: var(--r-full);
1483 font-family: var(--font-mono);
1484 font-size: 11px;
1485 color: var(--text);
1486 box-shadow: 0 12px 24px -8px rgba(0,0,0,0.5), 0 0 18px -4px rgba(140,109,255,0.30);
1487 backdrop-filter: blur(8px);
1488 -webkit-backdrop-filter: blur(8px);
1489 }
1490 .hero-float-icon { color: var(--accent); }
1491 .hero-float-1 {
1492 top: -14px;
1493 left: -8px;
1494 animation: hero-float-bob-1 5s var(--ease, ease) infinite alternate;
1495 }
1496 .hero-float-2 {
1497 bottom: -14px;
1498 right: -8px;
1499 animation: hero-float-bob-2 6s var(--ease, ease) infinite alternate;
1500 }
1501 @keyframes hero-float-bob-1 {
1502 from { transform: translate(0, 0); }
1503 to { transform: translate(-8px, -10px); }
1504 }
1505 @keyframes hero-float-bob-2 {
1506 from { transform: translate(0, 0); }
1507 to { transform: translate(8px, 8px); }
1508 }
1509
1510 @media (max-width: 720px) {
1511 .landing-hero-visual { padding: 0 8px; }
1512 .hero-pr-card { transform: none; }
1513 .hero-pr-title { font-size: 12px; }
1514 .hero-pr-diff { font-size: 11px; line-height: 1.6; }
1515 .hero-float { display: none; }
1516 }
958d26aClaude1517 .landing-hero-bg {
1518 position: absolute;
1519 inset: -10% -20%;
1520 pointer-events: none;
1521 z-index: 0;
4c47454Claude1522 }
958d26aClaude1523 .landing-hero-blob {
1524 position: absolute;
1525 border-radius: 50%;
1526 filter: blur(80px);
1527 opacity: 0.55;
1528 }
1529 .landing-hero-blob-1 {
1530 top: -10%;
1531 left: 30%;
1532 width: 480px;
1533 height: 480px;
1534 background: radial-gradient(circle, rgba(140,109,255,0.55), transparent 65%);
1535 }
1536 .landing-hero-blob-2 {
1537 top: 10%;
1538 left: 50%;
1539 width: 380px;
1540 height: 380px;
1541 background: radial-gradient(circle, rgba(54,197,214,0.40), transparent 65%);
1542 }
1543 .landing-hero-grid {
4c47454Claude1544 position: absolute;
1545 inset: 0;
958d26aClaude1546 background-image:
1547 linear-gradient(to right, rgba(255,255,255,0.04) 1px, transparent 1px),
1548 linear-gradient(to bottom, rgba(255,255,255,0.04) 1px, transparent 1px);
1549 background-size: 60px 60px;
1550 mask-image: radial-gradient(ellipse 50% 50% at 50% 30%, #000 0%, transparent 75%);
1551 -webkit-mask-image: radial-gradient(ellipse 50% 50% at 50% 30%, #000 0%, transparent 75%);
1552 }
1553 :root[data-theme='light'] .landing-hero-grid {
1554 background-image:
1555 linear-gradient(to right, rgba(15,16,28,0.06) 1px, transparent 1px),
1556 linear-gradient(to bottom, rgba(15,16,28,0.06) 1px, transparent 1px);
4c47454Claude1557 }
1558
958d26aClaude1559 .landing-hero-inner {
1560 position: relative;
1561 z-index: 1;
1562 max-width: 960px;
2b821b7Claude1563 margin: 0 auto;
1564 }
958d26aClaude1565 .landing-hero-eyebrow {
1566 margin: 0 auto var(--s-6);
1567 color: var(--accent);
1568 }
1569 .landing-hero-eyebrow::before { display: none; }
1570 .landing-hero-pulse {
1571 width: 7px;
1572 height: 7px;
1573 border-radius: 50%;
1574 background: var(--accent);
1575 box-shadow: 0 0 0 0 rgba(140,109,255,0.6);
1576 animation: pulse 1.8s ease-out infinite;
1577 flex-shrink: 0;
1578 }
1579 @keyframes pulse {
1580 0% { box-shadow: 0 0 0 0 rgba(140,109,255,0.55); }
1581 70% { box-shadow: 0 0 0 10px rgba(140,109,255,0); }
1582 100% { box-shadow: 0 0 0 0 rgba(140,109,255,0); }
1583 }
1584
2b821b7Claude1585 .landing-hero-title {
c963db5Claude1586 font-size: clamp(48px, 9.5vw, 124px);
1587 line-height: 0.96;
1588 letter-spacing: -0.045em;
1589 font-weight: 700;
1590 margin: 0 0 var(--s-7);
958d26aClaude1591 color: var(--text-strong);
2b821b7Claude1592 }
c963db5Claude1593 .landing-hero-title .gradient-text {
1594 background-image: linear-gradient(135deg, #a48bff 0%, #8c6dff 50%, #6d4dff 100%);
1595 -webkit-background-clip: text;
1596 background-clip: text;
1597 -webkit-text-fill-color: transparent;
1598 color: transparent;
1599 }
958d26aClaude1600
2b821b7Claude1601 .landing-hero-sub {
958d26aClaude1602 font-size: clamp(15px, 1.6vw, 19px);
2b821b7Claude1603 color: var(--text-muted);
958d26aClaude1604 max-width: 680px;
1605 margin: 0 auto;
4c47454Claude1606 line-height: 1.55;
958d26aClaude1607 letter-spacing: -0.005em;
2b821b7Claude1608 }
4c47454Claude1609
2b821b7Claude1610 .landing-hero-ctas {
1611 display: flex;
958d26aClaude1612 gap: 12px;
2b821b7Claude1613 justify-content: center;
1614 flex-wrap: wrap;
958d26aClaude1615 margin-top: var(--s-10);
2b821b7Claude1616 }
958d26aClaude1617 .landing-cta-arrow {
1618 transition: transform var(--t-base) var(--ease-spring);
1619 display: inline-block;
2b821b7Claude1620 }
958d26aClaude1621 .btn:hover .landing-cta-arrow,
1622 .landing-cta-primary:hover .landing-cta-arrow {
1623 transform: translateX(4px);
8e9f1d9Claude1624 }
2b821b7Claude1625
5f2e749Claude1626 /* L8 — free-tier reassurance link beneath the CTA row. */
1627 .landing-hero-freenote {
1628 margin-top: var(--s-5);
1629 font-size: var(--t-sm);
1630 color: var(--text-muted);
1631 text-align: center;
1632 }
1633 .landing-hero-freenote-link {
1634 color: var(--accent);
1635 text-decoration: none;
1636 font-weight: 500;
1637 border-bottom: 1px dotted rgba(140,109,255,0.4);
1638 transition: color var(--t-fast) var(--ease), border-color var(--t-fast) var(--ease);
1639 }
1640 .landing-hero-freenote-link:hover {
1641 color: var(--text-strong);
1642 border-bottom-color: var(--accent);
1643 }
1644
4c47454Claude1645 .landing-hero-caption {
958d26aClaude1646 margin-top: var(--s-8);
1647 font-size: var(--t-sm);
4c47454Claude1648 color: var(--text-muted);
958d26aClaude1649 display: flex;
1650 flex-wrap: wrap;
1651 align-items: center;
1652 justify-content: center;
1653 gap: 12px;
2b821b7Claude1654 }
958d26aClaude1655 .landing-hero-cmd {
1656 display: inline-flex;
1657 align-items: center;
1658 gap: 4px;
1659 padding: 6px 12px;
1660 background: var(--bg-elevated);
4c47454Claude1661 border: 1px solid var(--border);
958d26aClaude1662 border-radius: var(--r-full);
1663 box-shadow: var(--elev-1);
1664 }
1665 .landing-hero-cmd .kbd {
1666 border: 0;
1667 background: transparent;
1668 padding: 0 4px;
1669 color: var(--text-muted);
1670 font-size: 12px;
1671 }
1672 .landing-hero-cmd .kbd:nth-last-of-type(1) { color: var(--accent); }
1673 .landing-hero-arrow {
1674 color: var(--text-faint);
1675 font-size: 13px;
1676 margin: 0 2px;
2b821b7Claude1677 }
4c47454Claude1678
1679 .landing-stats {
958d26aClaude1680 margin-top: var(--s-7);
1681 font-family: var(--font-mono);
1682 font-size: 12px;
1683 color: var(--text-muted);
1684 display: flex;
1685 align-items: center;
1686 justify-content: center;
1687 gap: 10px;
1688 flex-wrap: wrap;
1689 letter-spacing: 0.02em;
1690 }
1691 .landing-stats strong {
1692 color: var(--text-strong);
1693 font-weight: 600;
1694 font-feature-settings: 'tnum';
1695 }
1696 .landing-stats-sep { opacity: 0.4; }
1697
c963db5Claude1698 /* ---------- Capability grid (crontech-style uppercase tracked) ---------- */
1699 .landing-caps {
1700 margin: var(--s-12) auto var(--s-16);
1701 max-width: 1080px;
1702 padding: var(--s-7) var(--s-4);
958d26aClaude1703 border-top: 1px solid var(--border-subtle);
1704 border-bottom: 1px solid var(--border-subtle);
c963db5Claude1705 }
1706 .landing-caps-grid {
1707 display: grid;
1708 grid-template-columns: repeat(4, 1fr);
1709 gap: 24px 16px;
958d26aClaude1710 text-align: center;
1711 }
c963db5Claude1712 .landing-cap {
958d26aClaude1713 font-family: var(--font-mono);
1714 font-size: 11px;
c963db5Claude1715 font-weight: 600;
958d26aClaude1716 text-transform: uppercase;
c963db5Claude1717 letter-spacing: 0.16em;
1718 color: var(--text-muted);
1719 transition: color var(--t-fast) var(--ease);
958d26aClaude1720 }
c963db5Claude1721 .landing-cap:hover { color: var(--text-strong); }
1722 @media (max-width: 800px) {
1723 .landing-caps-grid { grid-template-columns: repeat(2, 1fr); gap: 18px 12px; }
1724 }
1725 @media (max-width: 480px) {
1726 .landing-caps-grid { grid-template-columns: 1fr; }
1727 }
1728
1729 /* ---------- Big stat row (crontech-style hero closer) ---------- */
1730 .landing-bigstats {
1731 margin: var(--s-10) auto var(--s-20);
1732 max-width: 1180px;
1733 padding: 0 var(--s-4);
1734 }
1735 .landing-bigstats-grid {
1736 display: grid;
1737 grid-template-columns: repeat(4, 1fr);
1738 gap: 32px;
1739 text-align: left;
958d26aClaude1740 }
c963db5Claude1741 .landing-bigstat {
1742 padding: var(--s-2) 0;
1743 }
1744 .landing-bigstat-num {
958d26aClaude1745 font-family: var(--font-display);
c963db5Claude1746 font-size: clamp(28px, 3.5vw, 44px);
1747 line-height: 1.05;
1748 letter-spacing: -0.03em;
1749 font-weight: 700;
1750 color: var(--text-strong);
1751 margin-bottom: var(--s-2);
1752 }
1753 .landing-bigstat-label {
1754 font-family: var(--font-mono);
1755 font-size: 11px;
958d26aClaude1756 font-weight: 500;
c963db5Claude1757 text-transform: uppercase;
1758 letter-spacing: 0.14em;
1759 color: var(--text-faint);
2b821b7Claude1760 }
c963db5Claude1761 @media (max-width: 800px) {
1762 .landing-bigstats-grid { grid-template-columns: repeat(2, 1fr); gap: 28px 16px; }
1763 }
1764 @media (max-width: 480px) {
1765 .landing-bigstats-grid { grid-template-columns: 1fr; gap: 24px; }
958d26aClaude1766 }
1767
1768 /* ---------- Section base ---------- */
1769 .landing-section { margin: var(--s-20) auto; }
2b821b7Claude1770
4c47454Claude1771 /* ---------- Feature grid ---------- */
1772 .landing-features {
2b821b7Claude1773 display: grid;
1774 grid-template-columns: repeat(3, 1fr);
958d26aClaude1775 gap: 16px;
2b821b7Claude1776 }
1777 .landing-feature {
958d26aClaude1778 background: var(--bg-elevated);
2b821b7Claude1779 border: 1px solid var(--border);
958d26aClaude1780 border-radius: var(--r-lg);
1781 padding: var(--s-7);
1782 position: relative;
1783 overflow: hidden;
1784 isolation: isolate;
1785 transition:
1786 transform var(--t-base) var(--ease-out-quart),
1787 border-color var(--t-base) var(--ease),
1788 box-shadow var(--t-base) var(--ease);
1789 }
1790 .landing-feature::before {
1791 content: '';
1792 position: absolute;
1793 inset: 0;
1794 background: radial-gradient(120% 100% at 0% 0%, rgba(140,109,255,0.08), transparent 55%);
1795 opacity: 0;
1796 transition: opacity var(--t-base) var(--ease);
1797 z-index: -1;
4c47454Claude1798 }
1799 .landing-feature:hover {
958d26aClaude1800 transform: translateY(-3px);
1801 border-color: var(--border-strong);
1802 box-shadow: var(--elev-2);
2b821b7Claude1803 }
958d26aClaude1804 .landing-feature:hover::before { opacity: 1; }
2b821b7Claude1805 .landing-feature-icon {
4c47454Claude1806 display: inline-flex;
1807 align-items: center;
1808 justify-content: center;
958d26aClaude1809 width: 40px;
1810 height: 40px;
1811 border-radius: var(--r);
1812 background: var(--accent-gradient-soft);
1813 color: var(--accent);
1814 margin-bottom: var(--s-4);
1815 border: 1px solid rgba(140,109,255,0.20);
2b821b7Claude1816 }
1817 .landing-feature-title {
958d26aClaude1818 font-family: var(--font-display);
1819 font-size: 19px;
1820 font-weight: 600;
1821 letter-spacing: -0.018em;
1822 margin: 0 0 var(--s-2);
1823 color: var(--text-strong);
2b821b7Claude1824 }
1825 .landing-feature-desc {
958d26aClaude1826 font-size: var(--t-sm);
1827 color: var(--text-muted);
1828 line-height: 1.6;
1829 margin: 0;
1830 }
1831
1832 /* ---------- Walkthrough ---------- */
1833 .landing-walk-grid {
1834 display: grid;
1835 grid-template-columns: repeat(4, 1fr);
1836 gap: 16px;
1837 counter-reset: walk;
1838 }
1839 .landing-walk-step {
1840 position: relative;
1841 padding: var(--s-7) var(--s-6) var(--s-6);
1842 background: var(--bg-elevated);
1843 border: 1px solid var(--border);
1844 border-radius: var(--r-lg);
1845 }
1846 .landing-walk-step::after {
1847 content: '';
1848 position: absolute;
1849 top: 50%;
1850 right: -12px;
1851 width: 12px;
1852 height: 1px;
1853 background: var(--border-strong);
1854 }
1855 .landing-walk-step:last-child::after { display: none; }
1856 .landing-walk-num {
1857 display: inline-block;
1858 font-family: var(--font-mono);
1859 font-size: 11px;
1860 color: var(--accent);
1861 background: var(--accent-gradient-faint);
1862 border: 1px solid rgba(140,109,255,0.30);
1863 padding: 3px 8px;
1864 border-radius: var(--r-full);
1865 letter-spacing: 0.06em;
1866 margin-bottom: var(--s-3);
1867 }
1868 .landing-walk-title {
1869 font-family: var(--font-display);
1870 font-size: 22px;
1871 font-weight: 600;
1872 letter-spacing: -0.022em;
1873 margin: 0 0 var(--s-2);
1874 color: var(--text-strong);
1875 }
1876 .landing-walk-desc {
1877 font-size: var(--t-sm);
2b821b7Claude1878 color: var(--text-muted);
1879 line-height: 1.55;
1880 margin: 0;
1881 }
1882
958d26aClaude1883 /* ---------- Terminal ---------- */
1884 .landing-terminal-section { margin-top: var(--s-16); }
4c47454Claude1885 .landing-terminal-wrap {
1886 display: flex;
2b821b7Claude1887 justify-content: center;
1888 }
4c47454Claude1889 .landing-terminal {
1890 width: 100%;
958d26aClaude1891 max-width: 820px;
1892 background: linear-gradient(180deg, #0a0b12 0%, #06070c 100%);
1893 border: 1px solid var(--border-strong);
1894 border-radius: var(--r-lg);
1895 overflow: hidden;
1896 box-shadow: var(--elev-3), 0 0 60px -10px rgba(140,109,255,0.18);
4c47454Claude1897 text-align: left;
2b821b7Claude1898 }
958d26aClaude1899 :root[data-theme='light'] .landing-terminal {
1900 background: linear-gradient(180deg, #0f111a 0%, #06070c 100%);
1901 }
1902 .landing-terminal-chrome {
1903 display: flex;
1904 align-items: center;
1905 gap: 7px;
1906 padding: 11px 14px;
1907 background: rgba(255,255,255,0.025);
1908 border-bottom: 1px solid rgba(255,255,255,0.06);
1909 position: relative;
1910 }
1911 .landing-terminal-dot {
1912 width: 11px;
1913 height: 11px;
1914 border-radius: 50%;
1915 flex-shrink: 0;
1916 }
1917 .landing-terminal-dot-r { background: #ff5f57; }
1918 .landing-terminal-dot-y { background: #febc2e; }
1919 .landing-terminal-dot-g { background: #28c840; }
1920 .landing-terminal-title {
1921 position: absolute;
1922 left: 50%;
1923 transform: translateX(-50%);
1924 font-family: var(--font-mono);
1925 font-size: 11px;
1926 color: rgba(237,237,242,0.55);
1927 letter-spacing: 0.01em;
1928 }
1929 .landing-terminal-body {
1930 padding: var(--s-6) var(--s-7);
1931 font-family: var(--font-mono);
1932 font-feature-settings: var(--mono-feat);
1933 font-size: 13.5px;
1934 line-height: 1.85;
1935 color: rgba(237,237,242,0.92);
1936 }
4c47454Claude1937 .landing-term-line {
1938 display: flex;
1939 gap: 10px;
1940 white-space: pre-wrap;
1941 word-break: break-all;
2b821b7Claude1942 }
958d26aClaude1943 .landing-term-out { color: rgba(237,237,242,0.7); }
1944 .landing-term-prompt { color: rgba(140,109,255,0.85); user-select: none; flex-shrink: 0; }
1945 .landing-term-meta { color: rgba(237,237,242,0.45); }
1946 .landing-term-ok { color: var(--green); user-select: none; flex-shrink: 0; }
1947 .landing-term-ok-line { color: rgba(237,237,242,0.92); }
1948 .landing-term-cursor { margin-top: 4px; }
1949 .landing-term-blink {
1950 animation: blink 1.05s steps(2) infinite;
1951 color: var(--accent);
1952 }
1953 @keyframes blink { 50% { opacity: 0; } }
1954
1955 /* ---------- Comparison ---------- */
1956 .landing-compare {
1957 max-width: 920px;
1958 margin: 0 auto;
1959 border: 1px solid var(--border);
1960 border-radius: var(--r-lg);
1961 overflow: hidden;
1962 background: var(--bg-elevated);
1963 }
1964 .landing-compare-row {
1965 display: grid;
1966 grid-template-columns: 1fr 180px 180px;
1967 align-items: center;
1968 padding: 14px 20px;
1969 border-bottom: 1px solid var(--border-subtle);
1970 font-size: var(--t-sm);
1971 transition: background var(--t-fast) var(--ease);
1972 }
1973 .landing-compare-row:last-child { border-bottom: none; }
1974 .landing-compare-row:hover { background: var(--bg-hover); }
1975 .landing-compare-feature {
1976 color: var(--text-strong);
1977 font-weight: 500;
1978 }
1979 .landing-compare-them, .landing-compare-us {
1980 text-align: center;
1981 font-family: var(--font-mono);
1982 font-size: 12px;
1983 color: var(--text-muted);
1984 }
1985 .landing-compare-us { color: var(--green); font-weight: 500; }
1986 .landing-compare-hl .landing-compare-us {
1987 color: var(--accent);
1988 font-weight: 600;
2b821b7Claude1989 }
958d26aClaude1990 .landing-compare-hl .landing-compare-feature::after {
1991 content: 'NEW';
1992 margin-left: 8px;
1993 padding: 1px 6px;
1994 border-radius: 4px;
1995 background: var(--accent-gradient-faint);
1996 color: var(--accent);
1997 font-family: var(--font-mono);
1998 font-size: 9px;
1999 letter-spacing: 0.1em;
2000 font-weight: 600;
2001 vertical-align: 1px;
2002 }
2003 @media (max-width: 720px) {
2004 .landing-compare-row { grid-template-columns: 1fr 80px 80px; padding: 12px 14px; }
2005 .landing-compare-hl .landing-compare-feature::after { display: none; }
2006 }
2007
2008 /* ---------- Pricing ---------- */
2009 .landing-pricing {
2010 display: grid;
2011 grid-template-columns: repeat(3, 1fr);
2012 gap: 16px;
2013 max-width: 1080px;
2014 margin: 0 auto;
2015 align-items: stretch;
2016 }
2017 .landing-price-card {
2018 position: relative;
2019 background: var(--bg-elevated);
2020 border: 1px solid var(--border);
2021 border-radius: var(--r-lg);
2022 padding: var(--s-7);
2023 display: flex;
2024 flex-direction: column;
2025 gap: var(--s-4);
2026 transition: border-color var(--t-base) var(--ease), transform var(--t-base) var(--ease);
2027 }
2028 .landing-price-card:hover {
2029 border-color: var(--border-strong);
2030 transform: translateY(-2px);
2031 }
2032 .landing-price-hl {
2033 border-color: rgba(140,109,255,0.35);
2034 box-shadow: var(--elev-2), 0 0 0 1px rgba(140,109,255,0.25);
2035 background:
2036 linear-gradient(180deg, rgba(140,109,255,0.05), transparent 50%),
2037 var(--bg-elevated);
2038 }
2039 .landing-price-hl:hover { border-color: rgba(140,109,255,0.55); }
2040 .landing-price-badge {
2041 position: absolute;
2042 top: -10px;
2043 left: 50%;
2044 transform: translateX(-50%);
2045 padding: 3px 12px;
2046 background: var(--accent-gradient);
2047 color: #fff;
2048 font-family: var(--font-mono);
2049 font-size: 10px;
2050 letter-spacing: 0.1em;
2051 text-transform: uppercase;
2052 font-weight: 600;
2053 border-radius: var(--r-full);
2054 box-shadow: 0 4px 12px -2px rgba(140,109,255,0.4);
2055 }
2056 .landing-price-tier {
2057 font-family: var(--font-mono);
2058 font-size: 11px;
2059 text-transform: uppercase;
2060 letter-spacing: 0.16em;
2061 color: var(--text-muted);
2062 }
2063 .landing-price-amount {
2064 display: flex;
2065 align-items: baseline;
2066 gap: 8px;
2067 }
2068 .landing-price-num {
2069 font-family: var(--font-display);
2070 font-size: 40px;
2071 font-weight: 600;
2072 letter-spacing: -0.03em;
2073 color: var(--text-strong);
2074 }
2075 .landing-price-cad {
2076 font-size: var(--t-sm);
2077 color: var(--text-faint);
2078 }
2079 .landing-price-desc {
2080 font-size: var(--t-sm);
2081 color: var(--text-muted);
2082 line-height: 1.55;
2083 margin: 0;
2084 }
2085 .landing-price-features {
2086 list-style: none;
2087 padding: 0;
2088 margin: 0;
2089 display: flex;
2090 flex-direction: column;
2091 gap: 8px;
2092 font-size: var(--t-sm);
2093 color: var(--text);
2094 }
2095 .landing-price-features li {
2096 display: flex;
2097 align-items: center;
2098 gap: 9px;
2099 }
2100 .landing-price-check {
2101 color: var(--accent);
2102 font-weight: 600;
4c47454Claude2103 flex-shrink: 0;
2b821b7Claude2104 }
958d26aClaude2105 .landing-price-cta { margin-top: auto; }
2106
2107 /* ---------- Closing CTA ---------- */
2108 .landing-cta-section { margin: var(--s-20) auto var(--s-16); }
2109 .landing-cta-card {
2110 position: relative;
2111 text-align: center;
2112 padding: var(--s-16) var(--s-7);
2113 border: 1px solid var(--border-strong);
2114 border-radius: var(--r-2xl);
2115 background: var(--bg-elevated);
2116 overflow: hidden;
2117 isolation: isolate;
2118 }
2119 .landing-cta-bg {
2120 position: absolute;
2121 inset: 0;
2122 z-index: -1;
2123 background:
2124 radial-gradient(60% 100% at 50% 0%, rgba(140,109,255,0.16), transparent 65%),
2125 radial-gradient(40% 80% at 80% 100%, rgba(54,197,214,0.10), transparent 65%);
2126 }
2127 .landing-cta-card::after {
2128 content: '';
2129 position: absolute;
2130 inset: 0;
2131 z-index: -1;
2132 background-image: radial-gradient(rgba(255,255,255,0.04) 1px, transparent 1px);
2133 background-size: 24px 24px;
2134 mask-image: radial-gradient(ellipse at center, #000 0%, transparent 65%);
2135 -webkit-mask-image: radial-gradient(ellipse at center, #000 0%, transparent 65%);
2136 opacity: 0.6;
2137 }
2138 :root[data-theme='light'] .landing-cta-card::after {
2139 background-image: radial-gradient(rgba(15,16,28,0.07) 1px, transparent 1px);
2140 }
2141 .landing-cta-card .eyebrow { justify-content: center; }
2142 .landing-cta-title {
2143 font-family: var(--font-display);
2144 font-size: clamp(28px, 4.4vw, 56px);
2145 line-height: 1.05;
2146 letter-spacing: -0.03em;
2147 font-weight: 600;
2148 margin: var(--s-3) 0 var(--s-4);
2149 color: var(--text-strong);
2150 }
2151 .landing-cta-sub {
2152 font-size: var(--t-md);
2153 color: var(--text-muted);
2154 max-width: 560px;
2155 margin: 0 auto var(--s-8);
2156 line-height: 1.55;
2157 }
2158 .landing-cta-buttons {
2159 display: flex;
2160 gap: 12px;
2161 justify-content: center;
2162 flex-wrap: wrap;
2163 }
2b821b7Claude2164
2165 /* ---------- Responsive ---------- */
958d26aClaude2166 @media (max-width: 960px) {
2167 .landing-features { grid-template-columns: repeat(2, 1fr); }
2168 .landing-walk-grid { grid-template-columns: repeat(2, 1fr); }
2169 .landing-walk-step::after { display: none; }
2170 .landing-pricing { grid-template-columns: 1fr; max-width: 480px; }
2171 }
2172 @media (max-width: 640px) {
2173 .landing-hero { padding: var(--s-14) 0 var(--s-10); }
2174 .landing-hero-cmd { flex-wrap: wrap; justify-content: center; }
2175 .landing-hero-ctas { flex-direction: column; align-items: stretch; }
2176 .landing-hero-ctas .btn { width: 100%; justify-content: center; }
2177 .landing-features { grid-template-columns: 1fr; }
2178 .landing-walk-grid { grid-template-columns: 1fr; }
2179 .landing-section { margin: var(--s-12) auto; }
2180 .landing-cta-card { padding: var(--s-10) var(--s-5); }
2181 .landing-cta-buttons .btn { width: 100%; justify-content: center; }
2b821b7Claude2182 }
52ad8b1Claude2183
2184 /* ---------- L4 social-proof counters ---------- */
2185 .landing-counters {
2186 margin: var(--s-10) auto var(--s-12);
2187 max-width: 1180px;
2188 padding: 0 var(--s-4);
2189 }
2190 .landing-counters-grid {
2191 display: grid;
2192 grid-template-columns: repeat(6, 1fr);
2193 gap: 20px;
2194 text-align: left;
2195 }
2196 .landing-counter {
2197 padding: var(--s-3) 0;
2198 border-top: 1px solid var(--border-subtle);
2199 }
2200 .landing-counter-num {
2201 font-family: var(--font-display);
2202 font-size: clamp(24px, 3vw, 38px);
2203 line-height: 1.05;
2204 letter-spacing: -0.03em;
2205 font-weight: 700;
2206 margin-bottom: 6px;
2207 font-feature-settings: 'tnum';
2208 background-image: var(--accent-gradient);
2209 -webkit-background-clip: text;
2210 background-clip: text;
2211 -webkit-text-fill-color: transparent;
2212 color: transparent;
2213 }
2214 .landing-counter-label {
2215 font-family: var(--font-mono);
2216 font-size: 10.5px;
2217 font-weight: 500;
2218 text-transform: uppercase;
2219 letter-spacing: 0.12em;
2220 color: var(--text-faint);
2221 line-height: 1.4;
2222 }
2223 @media (max-width: 960px) {
2224 .landing-counters-grid { grid-template-columns: repeat(3, 1fr); gap: 20px 16px; }
2225 }
2226 @media (max-width: 540px) {
2227 .landing-counters-grid { grid-template-columns: repeat(2, 1fr); gap: 18px 12px; }
2228 }
5f2e749Claude2229
2230 /* ---------- L10 hero install snippet ---------- */
2231 .landing-hero-install {
2232 display: inline-flex;
2233 align-items: stretch;
2234 gap: 0;
2235 margin: var(--s-8) auto 0;
2236 background: var(--bg-elevated);
2237 border: 1px solid var(--border-strong);
2238 border-radius: var(--r);
2239 box-shadow: var(--elev-1);
2240 overflow: hidden;
2241 max-width: 100%;
2242 font-family: var(--font-mono);
2243 }
2244 .landing-hero-install-code {
2245 display: inline-flex;
2246 align-items: center;
2247 gap: 10px;
2248 padding: 10px 14px;
2249 font-size: 13.5px;
2250 color: var(--text-strong);
2251 background: transparent;
2252 border: 0;
2253 white-space: nowrap;
2254 overflow-x: auto;
2255 }
2256 .landing-hero-install-prompt {
2257 color: var(--accent);
2258 user-select: none;
2259 }
2260 .landing-hero-install-copy {
2261 appearance: none;
2262 border: 0;
2263 border-left: 1px solid var(--border);
2264 background: transparent;
2265 color: var(--text-muted);
2266 font-family: var(--font-mono);
2267 font-size: 12px;
2268 font-weight: 600;
2269 letter-spacing: 0.06em;
2270 text-transform: uppercase;
2271 padding: 0 16px;
2272 cursor: pointer;
2273 transition: background var(--t-fast) var(--ease), color var(--t-fast) var(--ease);
2274 }
2275 .landing-hero-install-copy:hover {
2276 background: var(--accent-gradient-faint);
2277 color: var(--accent);
2278 }
2279 .landing-hero-install-copy[data-copied="1"] {
2280 color: var(--green, #34d399);
2281 }
2282
2283 /* ---------- L10 hero "what just happened" rail ---------- */
2284 .landing-hero-rail {
2285 list-style: none;
2286 padding: 0;
2287 margin: var(--s-7) auto 0;
2288 display: flex;
2289 flex-wrap: wrap;
2290 justify-content: center;
2291 gap: 8px 22px;
2292 font-family: var(--font-sans);
2293 font-size: var(--t-sm);
2294 color: var(--text-muted);
2295 max-width: 760px;
2296 }
2297 .landing-hero-rail li {
2298 display: inline-flex;
2299 align-items: center;
2300 gap: 7px;
2301 line-height: 1.4;
2302 }
2303 .landing-hero-rail strong {
2304 color: var(--text-strong);
2305 font-weight: 600;
2306 font-feature-settings: 'tnum';
2307 }
2308 .landing-hero-rail-check {
2309 color: var(--accent);
2310 font-weight: 700;
2311 flex-shrink: 0;
2312 }
2313
2314 /* ---------- L10 three-reasons section ---------- */
2315 .landing-reasons { margin-top: var(--s-12); }
2316 .landing-reasons-grid {
2317 display: grid;
2318 grid-template-columns: repeat(3, 1fr);
2319 gap: 16px;
2320 }
2321 .landing-reason {
2322 background: var(--bg-elevated);
2323 border: 1px solid var(--border);
2324 border-radius: var(--r-lg);
2325 padding: var(--s-7);
2326 display: flex;
2327 flex-direction: column;
2328 gap: var(--s-3);
2329 transition: border-color var(--t-base) var(--ease), transform var(--t-base) var(--ease);
2330 }
2331 .landing-reason:hover {
2332 border-color: var(--border-strong);
2333 transform: translateY(-2px);
2334 }
2335 .landing-reason-icon {
2336 display: inline-flex;
2337 align-items: center;
2338 justify-content: center;
2339 width: 40px;
2340 height: 40px;
2341 border-radius: var(--r);
2342 background: var(--accent-gradient-soft);
2343 color: var(--accent);
2344 border: 1px solid rgba(140,109,255,0.20);
2345 }
2346 .landing-reason-title {
2347 font-family: var(--font-display);
2348 font-size: 20px;
2349 font-weight: 600;
2350 letter-spacing: -0.02em;
2351 margin: 0;
2352 color: var(--text-strong);
2353 }
2354 .landing-reason-body {
2355 font-size: var(--t-sm);
2356 color: var(--text-muted);
2357 line-height: 1.55;
2358 margin: 0;
2359 }
2360 .landing-reasons-code {
2361 display: block;
2362 font-family: var(--font-mono);
2363 font-size: 12px;
2364 color: var(--text-strong);
2365 background: var(--bg);
2366 border: 1px solid var(--border);
2367 border-radius: var(--r);
2368 padding: 8px 12px;
2369 overflow-x: auto;
2370 white-space: nowrap;
2371 }
2372 .landing-reason-link {
2373 margin-top: auto;
2374 display: inline-flex;
2375 align-items: center;
2376 gap: 6px;
2377 color: var(--accent);
2378 font-size: var(--t-sm);
2379 font-weight: 500;
2380 text-decoration: none;
2381 }
2382 .landing-reason-link:hover { text-decoration: underline; }
2383 @media (max-width: 960px) {
2384 .landing-reasons-grid { grid-template-columns: 1fr; max-width: 520px; margin: 0 auto; }
2385 }
2386
2387 /* ---------- L10 "How is this different" pull-quote ---------- */
2388 .landing-pullquote-section {
2389 margin: var(--s-20) auto var(--s-12);
2390 max-width: 920px;
2391 padding: 0 var(--s-4);
2392 text-align: center;
2393 }
2394 .landing-pullquote {
2395 margin: 0;
2396 padding: var(--s-10) var(--s-7);
2397 background:
2398 radial-gradient(80% 100% at 50% 0%, rgba(140,109,255,0.10), transparent 65%),
2399 var(--bg-elevated);
2400 border: 1px solid var(--border-strong);
2401 border-radius: var(--r-xl);
2402 position: relative;
2403 overflow: hidden;
2404 }
2405 .landing-pullquote-eyebrow {
2406 font-family: var(--font-mono);
2407 font-size: 11px;
2408 font-weight: 600;
2409 letter-spacing: 0.16em;
2410 text-transform: uppercase;
2411 color: var(--accent);
2412 margin-bottom: var(--s-4);
2413 }
2414 .landing-pullquote-text {
2415 font-family: var(--font-display);
2416 font-size: clamp(20px, 2.4vw, 28px);
2417 line-height: 1.4;
2418 letter-spacing: -0.018em;
2419 color: var(--text-strong);
2420 margin: 0 auto;
2421 max-width: 760px;
2422 quotes: "\\201C" "\\201D";
2423 }
2424 .landing-pullquote-text::before { content: open-quote; color: var(--accent); margin-right: 4px; }
2425 .landing-pullquote-text::after { content: close-quote; color: var(--accent); margin-left: 4px; }
2426 .landing-pullquote-link {
2427 display: inline-flex;
2428 align-items: center;
2429 gap: 6px;
2430 margin-top: var(--s-6);
2431 color: var(--accent);
2432 font-size: var(--t-sm);
2433 font-weight: 500;
2434 text-decoration: none;
2435 }
2436 .landing-pullquote-link:hover { text-decoration: underline; }
2437
2438 /* ---------- L10 hero responsive overrides ---------- */
2439 @media (max-width: 640px) {
2440 .landing-hero-install { width: 100%; }
2441 .landing-hero-install-code { flex: 1; font-size: 12px; }
2442 .landing-hero-rail { flex-direction: column; align-items: flex-start; gap: 6px; padding: 0 var(--s-3); }
2443 .landing-hero-rail li { width: 100%; }
2444 }
534f04aClaude2445
2446 /* ============================================================ */
2447 /* Block M1 — Live-now demo feed */
2448 /* ============================================================ */
2449 .landing-livenow {
2450 margin: var(--s-8) 0 var(--s-6);
2451 padding: var(--s-6) 0 var(--s-4);
2452 }
2453 .landing-livenow-head {
2454 text-align: center;
2455 margin-bottom: var(--s-6);
2456 }
2457 .landing-livenow-eyebrow {
2458 display: inline-flex;
2459 align-items: center;
2460 gap: 8px;
2461 padding: 4px 12px;
2462 border-radius: var(--r-full);
2463 background: rgba(52,211,153,0.08);
2464 border: 1px solid rgba(52,211,153,0.25);
2465 color: var(--green);
2466 font-family: var(--font-mono, ui-monospace, monospace);
2467 font-size: 11px;
2468 letter-spacing: 0.06em;
2469 text-transform: uppercase;
2470 margin-bottom: var(--s-3);
2471 }
2472 .landing-livenow-pulse {
2473 width: 8px; height: 8px;
2474 border-radius: 50%;
2475 background: var(--green);
2476 box-shadow: 0 0 0 0 rgba(52,211,153,0.6);
2477 animation: landing-livenow-pulse 1.6s ease-out infinite;
2478 }
2479 @keyframes landing-livenow-pulse {
2480 0% { box-shadow: 0 0 0 0 rgba(52,211,153,0.55); transform: scale(1); }
2481 70% { box-shadow: 0 0 0 10px rgba(52,211,153,0); transform: scale(1.05); }
2482 100% { box-shadow: 0 0 0 0 rgba(52,211,153,0); transform: scale(1); }
2483 }
2484 .landing-livenow-title {
2485 font-size: 22px;
2486 line-height: 1.25;
2487 margin: 0 auto;
2488 max-width: 720px;
2489 color: var(--text-strong);
2490 font-weight: 600;
2491 letter-spacing: -0.01em;
2492 }
2493 .landing-livenow-sub {
2494 margin: var(--s-2) auto 0;
2495 color: var(--text-muted);
2496 font-size: 13px;
2497 max-width: 560px;
2498 }
2499 .landing-livenow-sub code {
2500 background: rgba(255,255,255,0.05);
2501 border: 1px solid var(--border);
2502 padding: 1px 6px;
2503 border-radius: 4px;
2504 font-size: 12px;
2505 color: var(--accent);
2506 }
2507
2508 .landing-livenow-grid {
2509 display: grid;
2510 grid-template-columns: repeat(2, minmax(0, 1fr));
2511 gap: var(--s-3);
2512 }
2513 @media (min-width: 980px) {
2514 .landing-livenow-grid {
2515 grid-template-columns: repeat(4, minmax(0, 1fr));
2516 }
2517 }
2518
2519 .landing-livecard {
2520 background: linear-gradient(180deg, rgba(255,255,255,0.025), rgba(255,255,255,0.005));
2521 border: 1px solid var(--border);
2522 border-radius: var(--r-md, 10px);
2523 padding: 14px 14px 12px;
2524 display: flex;
2525 flex-direction: column;
2526 min-height: 180px;
2527 position: relative;
2528 overflow: hidden;
2529 }
2530 .landing-livecard::before {
2531 content: '';
2532 position: absolute;
2533 inset: 0;
2534 background: var(--accent-gradient-faint);
2535 opacity: 0;
2536 transition: opacity 200ms var(--ease, ease);
2537 pointer-events: none;
2538 }
2539 .landing-livecard:hover::before { opacity: 1; }
2540
2541 .landing-livecard-head {
2542 display: flex;
2543 align-items: center;
2544 gap: 8px;
2545 margin-bottom: 10px;
2546 }
2547 .landing-livecard-dot {
2548 width: 7px; height: 7px;
2549 border-radius: 50%;
2550 background: var(--green);
2551 box-shadow: 0 0 8px rgba(52,211,153,0.55);
2552 animation: landing-livenow-pulse 1.8s ease-out infinite;
2553 flex-shrink: 0;
2554 }
2555 .landing-livecard-title {
2556 margin: 0;
2557 font-size: 12px;
2558 font-weight: 600;
2559 text-transform: uppercase;
2560 letter-spacing: 0.06em;
2561 color: var(--text-muted);
2562 }
2563
2564 .landing-livecard-bignum {
2565 display: flex;
2566 align-items: baseline;
2567 gap: 8px;
2568 margin: 4px 0 10px;
2569 }
2570 .landing-livecard-bignum-n {
2571 font-size: 30px;
2572 font-weight: 700;
2573 color: var(--text-strong);
2574 font-variant-numeric: tabular-nums;
2575 background: var(--accent-gradient);
2576 -webkit-background-clip: text;
2577 background-clip: text;
2578 -webkit-text-fill-color: transparent;
2579 color: transparent;
2580 }
2581 .landing-livecard-bignum-label {
2582 font-size: 12px;
2583 color: var(--text-muted);
2584 }
2585
2586 .landing-livecard-list {
2587 list-style: none;
2588 margin: 0;
2589 padding: 0;
2590 font-size: 13px;
2591 line-height: 1.45;
2592 flex: 1;
2593 }
2594 .landing-livecard-row, .landing-livecard-feedrow {
2595 padding: 6px 0;
2596 border-bottom: 1px dashed rgba(255,255,255,0.05);
2597 transition: background-color 1s var(--ease, ease);
2598 border-radius: 4px;
2599 margin: 0 -4px;
2600 padding-left: 4px;
2601 padding-right: 4px;
2602 }
2603 .landing-livecard-row:last-child,
2604 .landing-livecard-feedrow:last-child { border-bottom: 0; }
2605 .landing-livecard-feedrow { padding: 4px; font-size: 12.5px; }
2606
2607 .landing-livecard-flash {
2608 background-color: rgba(52,211,153,0.18) !important;
2609 animation: landing-livecard-flash-fade 1.1s ease-out forwards;
2610 }
2611 @keyframes landing-livecard-flash-fade {
2612 0% { background-color: rgba(52,211,153,0.22); }
2613 100% { background-color: rgba(52,211,153,0); }
2614 }
2615
2616 .landing-livecard-link {
2617 color: var(--text-strong);
2618 text-decoration: none;
2619 display: inline-block;
2620 max-width: 100%;
2621 overflow: hidden;
2622 text-overflow: ellipsis;
2623 white-space: nowrap;
2624 }
2625 .landing-livecard-link:hover { color: var(--accent); }
2626 .landing-livecard-num {
2627 font-family: var(--font-mono, ui-monospace, monospace);
2628 color: var(--text-faint);
2629 font-weight: 500;
2630 font-size: 12px;
2631 }
2632 .landing-livecard-title-text { color: var(--text-strong); }
2633 .landing-livecard-snippet {
2634 color: var(--text-muted);
2635 font-style: italic;
2636 font-size: 12px;
2637 }
2638 .landing-livecard-meta {
2639 font-size: 11px;
2640 color: var(--text-faint);
2641 margin-top: 2px;
2642 font-family: var(--font-mono, ui-monospace, monospace);
2643 }
2644 .landing-livecard-repo {
2645 color: var(--accent-2);
2646 }
2647 .landing-livecard-rel {
2648 color: var(--text-muted);
2649 }
2650 .landing-livecard-empty {
2651 color: var(--text-faint);
2652 font-size: 12px;
2653 font-style: italic;
2654 padding: 8px 0;
2655 }
2656 .landing-livecard-kind {
2657 display: inline-block;
2658 padding: 1px 7px;
2659 border-radius: var(--r-full);
2660 font-family: var(--font-mono, ui-monospace, monospace);
2661 font-size: 10px;
2662 letter-spacing: 0.04em;
2663 text-transform: uppercase;
2664 font-weight: 600;
2665 }
2666 .landing-livecard-kind-auto_merge-merged,
2667 .landing-livecard-kind-auto-merge-merged {
2668 background: rgba(52,211,153,0.12);
2669 color: var(--green);
2670 border: 1px solid rgba(52,211,153,0.25);
2671 }
2672 .landing-livecard-kind-ai_build-dispatched,
2673 .landing-livecard-kind-ai-build-dispatched {
2674 background: rgba(140,109,255,0.12);
2675 color: var(--accent);
2676 border: 1px solid rgba(140,109,255,0.30);
2677 }
2678 .landing-livecard-kind-ai_review-posted,
2679 .landing-livecard-kind-ai-review-posted {
2680 background: rgba(54,197,214,0.12);
2681 color: var(--accent-2);
2682 border: 1px solid rgba(54,197,214,0.30);
2683 }
2684
2685 .landing-livenow-cta {
2686 margin-top: var(--s-5);
2687 display: flex;
2688 flex-wrap: wrap;
2689 align-items: center;
2690 justify-content: center;
2691 gap: 10px;
2692 font-size: 13px;
2693 color: var(--text-muted);
2694 }
2695 .landing-livenow-cta-link {
2696 color: var(--accent);
2697 text-decoration: none;
2698 font-weight: 500;
2699 display: inline-flex;
2700 align-items: center;
2701 gap: 4px;
2702 }
2703 .landing-livenow-cta-link:hover { color: var(--accent-hover); }
2704 .landing-livenow-cta-sep { color: var(--text-faint); }
2705
2706 @media (prefers-reduced-motion: reduce) {
2707 .landing-livenow-pulse,
2708 .landing-livecard-dot { animation: none; }
2709 .landing-livecard-flash { animation: none; background-color: transparent !important; }
2710 }
52ad8b1Claude2711`;
2712
2713/**
2714 * Block L4 — count-up animation.
2715 *
2716 * Reads each `[data-counter-target]` and animates the in-DOM text from
2717 * 0 → target over ~1.2s when the element first scrolls into view.
2718 *
2719 * Render-once semantics: each tile already contains the final value as
2720 * HTML, so visitors with JS disabled — or anyone before the script
2721 * loads — sees the correct number. The script just animates the text.
2722 *
2723 * Falls back to the static value (no animation) when IntersectionObserver
2724 * isn't available, or when the user prefers reduced motion.
2725 */
2726const landingCountersJs = `
2727(function(){
2728 try {
2729 var els = document.querySelectorAll('[data-counter-target]');
2730 if (!els.length) return;
2731 var reduced = window.matchMedia && window.matchMedia('(prefers-reduced-motion: reduce)').matches;
2732 if (reduced || typeof IntersectionObserver !== 'function') return;
2733
2734 function animate(el) {
2735 var target = parseInt(el.getAttribute('data-counter-target') || '0', 10);
2736 if (!isFinite(target) || target <= 0) return;
2737 var prefix = el.getAttribute('data-counter-prefix') || '';
2738 var suffix = el.getAttribute('data-counter-suffix') || '';
2739 var duration = 1200;
2740 var start = performance.now();
2741 function frame(now) {
2742 var t = Math.min(1, (now - start) / duration);
2743 // ease-out cubic
2744 var eased = 1 - Math.pow(1 - t, 3);
2745 var v = Math.floor(eased * target);
2746 el.textContent = prefix + v.toLocaleString() + suffix;
2747 if (t < 1) requestAnimationFrame(frame);
2748 else el.textContent = prefix + target.toLocaleString() + suffix;
2749 }
2750 // Reset to zero before animating in.
2751 el.textContent = prefix + '0' + suffix;
2752 requestAnimationFrame(frame);
2753 }
2754
2755 var io = new IntersectionObserver(function(entries) {
2756 entries.forEach(function(entry){
2757 if (entry.isIntersecting) {
2758 animate(entry.target);
2759 io.unobserve(entry.target);
2760 }
2761 });
2762 }, { threshold: 0.4 });
2763 els.forEach(function(el){ io.observe(el); });
2764 } catch (_) { /* swallow — static numbers remain */ }
2765})();
2b821b7Claude2766`;
5f2e749Claude2767
2768/**
2769 * Block L10 — clipboard copy for the hero install snippet.
2770 *
2771 * Pure progressive enhancement. Without JS the user can still
2772 * triple-click + Cmd/Ctrl-C the snippet — the button is the
2773 * speed-bump, not the only path.
2774 */
2775const landingCopyJs = `
2776(function(){
2777 try {
2778 var btns = document.querySelectorAll('[data-copy-target]');
2779 if (!btns.length) return;
2780 btns.forEach(function(btn){
2781 btn.addEventListener('click', function(){
2782 var id = btn.getAttribute('data-copy-target') || '';
2783 var src = document.getElementById(id);
2784 if (!src) return;
2785 var text = src.textContent || '';
2786 var done = function(){
2787 var prev = btn.textContent;
2788 btn.textContent = 'Copied';
2789 btn.setAttribute('data-copied', '1');
2790 setTimeout(function(){
2791 btn.textContent = prev || 'Copy';
2792 btn.removeAttribute('data-copied');
2793 }, 1500);
2794 };
2795 if (navigator.clipboard && navigator.clipboard.writeText) {
2796 navigator.clipboard.writeText(text).then(done, function(){ done(); });
2797 } else {
2798 // Legacy fallback — temp textarea + execCommand.
2799 try {
2800 var ta = document.createElement('textarea');
2801 ta.value = text;
2802 ta.style.position = 'fixed';
2803 ta.style.opacity = '0';
2804 document.body.appendChild(ta);
2805 ta.select();
2806 document.execCommand('copy');
2807 document.body.removeChild(ta);
2808 done();
2809 } catch (_) {}
2810 }
2811 });
2812 });
2813 } catch (_) { /* swallow */ }
2814})();
2815`;