Pre-launch — Gluecron is in final validation. Public signups and git hosting for non-owner users open after launch review.
CodeIssuesDiscussionsWikiPull RequestsProjectsCommitsActionsReleasesContributorsPulse● GatesSecuritySettingsDeploymentsPipelineInsightsAgents✨ Explain✨ Ask AI✨ Workspace✨ Spec✨ Tests▓ Debt Map✨ NL Search🏛 Archaeology
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.tsxBlame3842 lines · 3 contributors
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">
dc26881CC LABS App121 {/* ---------- Hero ----------
122 Block U1 — Senior polish pass. Rebuilt for tighter rhythm:
123 · 2 primary CTAs (sign up + Claude Desktop)
124 · "Try the live demo" / "Compare to GitHub" demoted to
125 a tertiary text-link row
126 · Install snippet moved BELOW the CTAs as a "power users"
127 panel — used to sit above and crowded the buttons
128 · 4-stat rail kept but rendered as a tighter horizontal
129 strip with the gradient accent rule
130 · One new muted gradient orb absolutely positioned behind
131 everything so the section reads as a product page, not
132 a tutorial
133 · vertical rhythm = var(--space-6) between every block
134 */}
4c47454Claude135 <section class="landing-hero">
958d26aClaude136 <div class="landing-hero-bg" aria-hidden="true">
137 <div class="landing-hero-blob landing-hero-blob-1" />
138 <div class="landing-hero-blob landing-hero-blob-2" />
dc26881CC LABS App139 <div class="landing-hero-blob landing-hero-orb" />
958d26aClaude140 <div class="landing-hero-grid" />
2b821b7Claude141 </div>
958d26aClaude142
143 <div class="landing-hero-inner stagger">
144 <div class="eyebrow landing-hero-eyebrow">
145 <span class="landing-hero-pulse" />
146 v1 · pre-launch · {new Date().getFullYear()}
147 </div>
148
149 <h1 class="landing-hero-title display">
5f2e749Claude150 <span class="gradient-text">The git host built around Claude.</span>
958d26aClaude151 </h1>
152
153 <p class="landing-hero-sub">
5f2e749Claude154 Label an issue. Walk away. Wake up to a merged PR.
958d26aClaude155 </p>
156
dc26881CC LABS App157 {/* U1 — primary CTA row, demoted to 2 buttons. */}
158 <div class="landing-hero-ctas" data-testid="hero-primary-ctas">
958d26aClaude159 <a href="/register" class="btn btn-primary btn-xl landing-cta-primary">
5f2e749Claude160 Sign up free
958d26aClaude161 <span class="landing-cta-arrow" aria-hidden="true">{"→"}</span>
162 </a>
dc26881CC LABS App163 {/* BLOCK Q1 — one-click Claude Desktop install. */}
93fe97eClaude164 <a
165 href="/gluecron.dxt"
166 class="btn btn-xl landing-cta-dxt"
167 download
168 data-testid="cta-dxt"
169 >
170 Add to Claude Desktop
171 <span class="landing-cta-arrow" aria-hidden="true">{"→"}</span>
172 </a>
dc26881CC LABS App173 </div>
174
175 {/* U1 — tertiary text-link row.
176 Visually subordinate to the buttons above. Keeps the
177 "Try the live demo" + "Compare to GitHub" affordances
178 from L10 + the Q3 "try without signing up" /play link
179 without crowding the primary CTA row. */}
180 <div class="landing-hero-tertiary" data-testid="hero-tertiary-row">
181 <a href="/demo" class="landing-hero-tertiary-link" data-testid="cta-tertiary-demo">
5f2e749Claude182 Try the live demo
dc26881CC LABS App183 <span aria-hidden="true">{" →"}</span>
958d26aClaude184 </a>
dc26881CC LABS App185 <span class="landing-hero-tertiary-sep" aria-hidden="true">·</span>
186 <a href="/vs-github" class="landing-hero-tertiary-link" data-testid="cta-tertiary-vs">
52ad8b1Claude187 Compare to GitHub
dc26881CC LABS App188 <span aria-hidden="true">{" →"}</span>
52ad8b1Claude189 </a>
dc26881CC LABS App190 <span class="landing-hero-tertiary-sep" aria-hidden="true">·</span>
191 <a href="/play" class="landing-hero-tertiary-link" data-testid="cta-play">
192 Try it without signing up
cd4f63bTest User193 <span aria-hidden="true">{" →"}</span>
194 </a>
195 </div>
196
dc26881CC LABS App197 {/* U1 — power-users install snippet panel.
198 Moved BELOW the CTAs so it doesn't compete for the eye
199 with the primary "Sign up free" button. */}
200 <div class="landing-hero-install-wrap" aria-label="Power users install panel">
201 <div class="landing-hero-install-label">For power users</div>
202 <div class="landing-hero-install" aria-label="One-line install">
203 <code class="landing-hero-install-code">
204 <span class="landing-hero-install-prompt" aria-hidden="true">$</span>
205 <span id="landing-install-text">curl -sSL gluecron.com/install | bash</span>
206 </code>
207 <button
208 type="button"
209 class="landing-hero-install-copy"
210 data-copy-target="landing-install-text"
211 aria-label="Copy install command"
212 >
213 Copy
214 </button>
215 </div>
216 </div>
217
218 {/* U1 — tightened "what just happened" rail.
219 Same data as before, rendered as a single horizontal
220 rule with the gradient accent line on top. Numbers
221 smaller, copy still scannable. */}
5f2e749Claude222 {publicStats && (
223 <ul class="landing-hero-rail" aria-label="What just happened on Gluecron">
224 <li>
225 <strong>{publicStats.weeklyPrsAutoMerged.toLocaleString()}</strong>
dc26881CC LABS App226 <span class="landing-hero-rail-label">PRs auto-merged</span>
5f2e749Claude227 </li>
228 <li>
229 <strong>{publicStats.weeklyIssuesBuiltByAi.toLocaleString()}</strong>
dc26881CC LABS App230 <span class="landing-hero-rail-label">issues built by AI</span>
5f2e749Claude231 </li>
232 <li>
233 <strong>{publicStats.weeklyDeploysShipped.toLocaleString()}</strong>
dc26881CC LABS App234 <span class="landing-hero-rail-label">deploys overnight</span>
5f2e749Claude235 </li>
236 <li>
dc26881CC LABS App237 <strong>{`~${Math.round(publicStats.weeklyHoursSaved).toLocaleString()}`}</strong>
238 <span class="landing-hero-rail-label">hours saved by AI</span>
5f2e749Claude239 </li>
240 </ul>
241 )}
242
243 {/* L8 — free-tier reassurance link. Keeps anxiety low for the AI-curious. */}
244 <p class="landing-hero-freenote">
245 Free forever for the AI-curious.{" "}
246 <a href="/pricing" class="landing-hero-freenote-link">
247 See pricing &rarr;
248 </a>
249 </p>
250
958d26aClaude251 <p class="landing-hero-caption">
252 Already have a repo?
253 <span class="landing-hero-cmd">
254 <span class="kbd">git</span>
255 <span class="kbd">remote</span>
256 <span class="kbd">add</span>
257 <span class="kbd">gluecron</span>
258 <span class="landing-hero-arrow">{"→"}</span>
259 <span class="kbd">git push</span>
260 </span>
261 </p>
262
263 {hasStats && (
264 <p class="landing-stats">
265 {stats!.publicRepos !== undefined && stats!.publicRepos > 0 && (
266 <span>
267 <strong>{stats!.publicRepos.toLocaleString()}</strong>
268 {stats!.publicRepos === 1 ? " repo" : " repos"}
269 </span>
270 )}
271 {stats!.publicRepos !== undefined &&
272 stats!.publicRepos > 0 &&
273 stats!.users !== undefined &&
274 stats!.users > 0 && <span class="landing-stats-sep">·</span>}
275 {stats!.users !== undefined && stats!.users > 0 && (
276 <span>
277 <strong>{stats!.users.toLocaleString()}</strong>
278 {stats!.users === 1 ? " developer" : " developers"}
279 </span>
280 )}
281 <span class="landing-stats-sep">·</span>
4c47454Claude282 <span>
958d26aClaude283 <strong>100%</strong> AI-native
4c47454Claude284 </span>
958d26aClaude285 </p>
286 )}
287 </div>
c963db5Claude288 </section>
c475ee6Claude289
534f04aClaude290 {/* ---------- Block M1 — Live-now demo feed ---------- */}
291 <LiveNowSection
292 queued={liveQueued}
293 merges={liveMerges}
294 reviews={liveReviews}
295 reviewCount={liveReviewCount}
296 feed={liveEntries}
297 />
298
52ad8b1Claude299 {/* ---------- L4 social-proof counters (animated count-up) ---------- */}
300 {tiles && (
301 <section class="landing-counters" aria-label="Gluecron live counters">
302 <div class="landing-counters-grid">
303 {tiles.map((t) => (
304 <div class="landing-counter">
305 <div
306 class="landing-counter-num"
307 data-counter-target={String(t.value)}
308 data-counter-suffix={t.suffix ?? ""}
309 data-counter-prefix={t.prefix ?? ""}
310 >
311 {t.prefix ?? ""}
312 {t.value.toLocaleString()}
313 {t.suffix ?? ""}
314 </div>
315 <div class="landing-counter-label">{t.label}</div>
316 </div>
317 ))}
318 </div>
319 <script dangerouslySetInnerHTML={{ __html: landingCountersJs }} />
320 </section>
321 )}
322
5f2e749Claude323 {/* ---------- L10 — Three reasons to switch ---------- */}
324 <section class="landing-section landing-reasons" aria-label="Three reasons to switch">
325 <div class="section-header">
326 <div class="eyebrow">Three reasons to switch</div>
327 <h2>Built so Claude can do the work.</h2>
328 </div>
329 <div class="landing-reasons-grid">
330 <ReasonCard
331 icon={
332 <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">
333 <path d="M21 12.8A9 9 0 1 1 11.2 3a7 7 0 0 0 9.8 9.8z" />
334 </svg>
335 }
336 title="Toggle Sleep Mode"
337 body="Claude does the work overnight. You get a 9 AM digest of what shipped — PRs merged, deploys live, incidents triaged."
338 link={{ href: "/sleep-mode", label: "Turn on Sleep Mode" }}
339 />
340 <ReasonCard
341 icon={
342 <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">
343 <polyline points="4 17 10 11 4 5" />
344 <line x1="12" y1="19" x2="20" y2="19" />
345 </svg>
346 }
347 title="One command to migrate"
348 body="Drop a single curl into your shell. Gluecron rehosts your repo, your issues, your branches — no SaaS rip-and-replace project required."
349 extra={
350 <code class="landing-reasons-code">curl -sSL gluecron.com/install | bash</code>
351 }
352 link={{ href: "/import", label: "Or import from GitHub" }}
353 />
354 <ReasonCard
355 icon={
356 <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">
357 <polygon points="5 3 19 12 5 21 5 3" />
358 </svg>
359 }
360 title="Open the demo, watch it work"
361 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."
362 link={{ href: "/demo", label: "Open the live demo" }}
363 />
364 </div>
365 </section>
366
c963db5Claude367 {/* ---------- Capability strip — uppercase tracked grid (crontech-style) ---------- */}
368 <section class="landing-caps">
369 <div class="landing-caps-grid">
370 <span class="landing-cap">Claude-powered AI</span>
371 <span class="landing-cap">Spec-to-PR</span>
372 <span class="landing-cap">Auto-repair</span>
373 <span class="landing-cap">Real-time gates</span>
374 <span class="landing-cap">MCP-native</span>
375 <span class="landing-cap">Workflow runner</span>
376 <span class="landing-cap">Self-hostable</span>
377 <span class="landing-cap">Branch protection</span>
378 <span class="landing-cap">Bun + Hono</span>
379 <span class="landing-cap">Drizzle + Postgres</span>
380 <span class="landing-cap">JSX server-rendered</span>
381 <span class="landing-cap">Type-safe end to end</span>
c475ee6Claude382 </div>
958d26aClaude383 </section>
384
c963db5Claude385 {/* ---------- Big stat row (crontech-style hero closer) ---------- */}
386 <section class="landing-bigstats">
387 <div class="landing-bigstats-grid">
388 <div class="landing-bigstat">
389 <div class="landing-bigstat-num">Claude-powered</div>
390 <div class="landing-bigstat-label">The best AI, native</div>
391 </div>
392 <div class="landing-bigstat">
393 <div class="landing-bigstat-num">Self-hosted</div>
394 <div class="landing-bigstat-label">On your hardware</div>
395 </div>
396 <div class="landing-bigstat">
397 <div class="landing-bigstat-num">MCP-native</div>
398 <div class="landing-bigstat-label">Claude · Cursor · Code</div>
399 </div>
400 <div class="landing-bigstat">
401 <div class="landing-bigstat-num">Real-time</div>
402 <div class="landing-bigstat-label">SSE everywhere</div>
403 </div>
958d26aClaude404 </div>
4c47454Claude405 </section>
406
407 {/* ---------- Feature grid ---------- */}
958d26aClaude408 <section class="landing-section">
409 <div class="section-header">
410 <div class="eyebrow">The platform</div>
411 <h2>An IDE for your repo, not just a host.</h2>
412 <p>
413 Gluecron ships the surfaces GitHub charges extra for, and the
414 ones it never built. AI is a teammate with its own commits, not
415 a sidebar.
2b821b7Claude416 </p>
417 </div>
4c47454Claude418
958d26aClaude419 <div class="landing-features stagger">
420 <FeatureCard
421 icon={
422 <svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round">
423 <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" />
424 </svg>
425 }
426 title="AI as a teammate"
427 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."
428 />
429 <FeatureCard
430 icon={
431 <svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round">
432 <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" />
433 <path d="M9 12l2 2 4-4" />
434 </svg>
435 }
436 title="Quality gate that learns"
437 desc="GateTest scans every push. Auto-repair fixes regressions before you see them. Required checks block bad PRs from merging. Your software self-corrects."
438 />
439 <FeatureCard
440 icon={
441 <svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round">
442 <path d="M13 2L4 14h7l-1 8 9-12h-7z" />
443 </svg>
444 }
445 title="Real-time everything"
446 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."
447 />
448 <FeatureCard
449 icon={
450 <svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round">
451 <rect x="3" y="3" width="18" height="18" rx="3" />
452 <path d="M3 9h18M9 21V9" />
453 </svg>
454 }
455 title="Workflow runner"
456 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."
457 />
458 <FeatureCard
459 icon={
460 <svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round">
461 <circle cx="12" cy="12" r="9" />
462 <path d="M3 12h18M12 3a14 14 0 010 18M12 3a14 14 0 000 18" />
463 </svg>
464 }
465 title="MCP-native"
466 desc="Claude, Cursor, Code — they speak Model Context Protocol. Gluecron exposes search, file read, issues, codebase explain as MCP tools by default."
467 />
468 <FeatureCard
469 icon={
470 <svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round">
471 <path d="M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
472 <path d="M12 7v5l3 2" />
473 </svg>
474 }
475 title="Yours, on your hardware"
476 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."
477 />
2b821b7Claude478 </div>
958d26aClaude479 </section>
2b821b7Claude480
958d26aClaude481 {/* ---------- Workflow walkthrough ---------- */}
482 <section class="landing-section landing-walk">
483 <div class="section-header">
484 <div class="eyebrow">How it works</div>
485 <h2>Push code. Watch it ship.</h2>
486 <p>
487 Every push triggers the same pipeline whether the commit came
488 from you, from CI, or from an AI agent.
4c47454Claude489 </p>
490 </div>
958d26aClaude491
492 <div class="landing-walk-grid">
493 <WalkStep n="01" title="Push" desc="git push to gluecron — Smart-HTTP, SSH, or via the web editor." />
494 <WalkStep n="02" title="Gate" desc="GateTest runs. Secret scanner runs. AI security review posts inline comments." />
495 <WalkStep n="03" title="Repair" desc="If a gate fails, auto-repair tries to fix it. New commit gets re-gated." />
496 <WalkStep n="04" title="Ship" desc="Green push to default branch fires deploy webhook. Crontech, Fly, your prod." />
497 </div>
4c47454Claude498 </section>
2b821b7Claude499
4c47454Claude500 {/* ---------- Terminal block ---------- */}
958d26aClaude501 <section class="landing-section landing-terminal-section">
502 <div class="landing-terminal-wrap">
503 <div class="landing-terminal" role="img" aria-label="Example git push to gluecron with passing gates">
504 <div class="landing-terminal-chrome">
505 <span class="landing-terminal-dot landing-terminal-dot-r" />
506 <span class="landing-terminal-dot landing-terminal-dot-y" />
507 <span class="landing-terminal-dot landing-terminal-dot-g" />
508 <span class="landing-terminal-title">~/your-repo &mdash; zsh</span>
509 </div>
510 <div class="landing-terminal-body">
511 <div class="landing-term-line">
512 <span class="landing-term-prompt">$</span>
513 <span>git remote add gluecron https://gluecron.com/you/your-repo.git</span>
514 </div>
515 <div class="landing-term-line">
516 <span class="landing-term-prompt">$</span>
517 <span>git push -u gluecron main</span>
518 </div>
519 <div class="landing-term-line landing-term-out">
520 <span class="landing-term-meta">remote:</span>
521 <span>Resolving deltas… 100% (24/24)</span>
522 </div>
523 <div class="landing-term-line landing-term-out landing-term-ok-line">
524 <span class="landing-term-ok">{"✓"}</span>
525 <span>pushed to gluecron.com/you/your-repo</span>
526 </div>
527 <div class="landing-term-line landing-term-out landing-term-ok-line">
528 <span class="landing-term-ok">{"✓"}</span>
529 <span>GateTest passed (12 rules, 0 violations)</span>
530 </div>
531 <div class="landing-term-line landing-term-out landing-term-ok-line">
532 <span class="landing-term-ok">{"✓"}</span>
533 <span>AI review posted (2 suggestions, 0 blockers)</span>
534 </div>
535 <div class="landing-term-line landing-term-out landing-term-ok-line">
536 <span class="landing-term-ok">{"✓"}</span>
537 <span>deployed to your-repo.gluecron.com <span class="landing-term-meta">(4.1s)</span></span>
538 </div>
539 <div class="landing-term-line landing-term-cursor">
540 <span class="landing-term-prompt">$</span>
541 <span class="landing-term-blink">▍</span>
542 </div>
543 </div>
4c47454Claude544 </div>
958d26aClaude545 </div>
546 </section>
547
548 {/* ---------- Comparison ---------- */}
549 <section class="landing-section">
550 <div class="section-header">
551 <div class="eyebrow">vs the incumbent</div>
552 <h2>Everything GitHub charges for. And the parts they didn't build.</h2>
553 </div>
554
555 <div class="landing-compare">
556 <CompareRow feature="Git hosting + Smart-HTTP push" them="✓" us="✓" />
557 <CompareRow feature="Issues, PRs, code review" them="✓" us="✓" />
558 <CompareRow feature="Workflow runner (Actions-equivalent)" them="paid minutes" us="self-hosted, unmetered" highlight />
559 <CompareRow feature="AI code review on every PR" them="Copilot subscription" us="built in" highlight />
560 <CompareRow feature="Spec-to-PR (NL feature → draft PR)" them="—" us="✓" highlight />
561 <CompareRow feature="Auto-repair on failed gates" them="—" us="✓" highlight />
562 <CompareRow feature="Real-time SSE for logs + PRs" them="polling" us="streaming" highlight />
563 <CompareRow feature="MCP server (Claude / Cursor)" them="—" us="✓" highlight />
564 <CompareRow feature="Self-host on your own infra" them="enterprise tier" us="single binary" highlight />
565 <CompareRow feature="Pre-receive policy enforcement" them="rulesets (GHE)" us="✓" />
566 </div>
567 </section>
568
569 {/* ---------- Pricing teaser ---------- */}
570 <section class="landing-section">
571 <div class="section-header">
572 <div class="eyebrow">Pricing</div>
573 <h2>Free to start. Honest at scale.</h2>
574 <p>
575 Self-hosting is free forever. Hosted plans price the AI calls,
576 not the seats.
577 </p>
578 </div>
579
580 <div class="landing-pricing">
581 <PricingCard
582 tier="Free"
583 price="$0"
584 cadence="forever"
585 desc="For personal projects + open source. Public + private repos, full AI suite, fair quotas."
586 features={["Unlimited public repos", "3 private repos", "5K AI calls / mo", "Community support"]}
587 cta="Start free"
588 href="/register"
589 />
590 <PricingCard
591 tier="Pro"
592 price="$12"
593 cadence="per user / mo"
594 desc="For working developers. Lifts every quota, adds priority routing, no Gluecron branding on deploys."
595 features={["Unlimited private repos", "100K AI calls / mo", "Priority queue", "Custom domains"]}
596 cta="Go Pro"
597 href="/settings/billing"
598 highlight
599 />
600 <PricingCard
601 tier="Team"
602 price="Talk to us"
603 cadence="custom"
604 desc="For orgs running production on Gluecron. SSO, audit retention, enterprise SLA, on-prem."
605 features={["SSO + SCIM", "On-prem deploy", "Dedicated capacity", "24/7 incident response"]}
606 cta="Contact"
607 href="mailto:hello@gluecron.com"
608 />
609 </div>
610 </section>
611
5f2e749Claude612 {/* ---------- L10 — "How is this different?" pull-quote ---------- */}
613 <section class="landing-pullquote-section" aria-label="How is this different from GitHub?">
614 <figure class="landing-pullquote">
615 <div class="landing-pullquote-eyebrow">How is this different from GitHub?</div>
616 <blockquote class="landing-pullquote-text">
617 Every other host bolts AI on as a sidecar. Gluecron is the first
618 git host where Claude is a first-class developer. Built to be
619 operated by AI agents, not just augmented by them.
620 </blockquote>
621 <a href="/vs-github" class="landing-pullquote-link">
622 See the full comparison
623 <span class="landing-cta-arrow" aria-hidden="true">{"→"}</span>
624 </a>
625 </figure>
626 </section>
627
958d26aClaude628 {/* ---------- Closing CTA ---------- */}
629 <section class="landing-cta-section">
630 <div class="landing-cta-card">
631 <div class="landing-cta-bg" aria-hidden="true" />
632 <div class="eyebrow">Ready when you are</div>
633 <h2 class="landing-cta-title">
634 Stop maintaining the platform.<br />
635 <span class="gradient-text">Start shipping the product.</span>
636 </h2>
637 <p class="landing-cta-sub">
638 Free to start, self-hosted-friendly, MCP-native. Migrate from
639 GitHub in one click.
640 </p>
641 <div class="landing-cta-buttons">
642 <a href="/register" class="btn btn-primary btn-xl">
643 Create your account
644 <span class="landing-cta-arrow" aria-hidden="true">{"→"}</span>
645 </a>
646 <a href="/import" class="btn btn-ghost btn-xl">
647 Migrate a repo
648 </a>
4c47454Claude649 </div>
650 </div>
651 </section>
5f2e749Claude652
653 {/* L10 — clipboard copy script for the hero install snippet. */}
654 <script dangerouslySetInnerHTML={{ __html: landingCopyJs }} />
4c47454Claude655 </div>
2b821b7Claude656 </>
657 );
658};
659
958d26aClaude660const FeatureCard: FC<{ icon: any; title: string; desc: string }> = ({
661 icon,
662 title,
663 desc,
664}) => (
665 <div class="landing-feature">
666 <div class="landing-feature-icon" aria-hidden="true">
667 {icon}
668 </div>
669 <h3 class="landing-feature-title">{title}</h3>
670 <p class="landing-feature-desc">{desc}</p>
671 </div>
672);
673
5f2e749Claude674// Block L10 — "Three reasons to switch" column.
675const ReasonCard: FC<{
676 icon: any;
677 title: string;
678 body: string;
679 link: { href: string; label: string };
680 extra?: any;
681}> = ({ icon, title, body, link, extra }) => (
682 <div class="landing-reason">
683 <div class="landing-reason-icon" aria-hidden="true">
684 {icon}
685 </div>
686 <h3 class="landing-reason-title">{title}</h3>
687 <p class="landing-reason-body">{body}</p>
688 {extra}
689 <a href={link.href} class="landing-reason-link">
690 {link.label}
691 <span class="landing-cta-arrow" aria-hidden="true">{"→"}</span>
692 </a>
693 </div>
694);
695
958d26aClaude696const WalkStep: FC<{ n: string; title: string; desc: string }> = ({
697 n,
698 title,
699 desc,
700}) => (
701 <div class="landing-walk-step">
702 <div class="landing-walk-num">{n}</div>
703 <h3 class="landing-walk-title">{title}</h3>
704 <p class="landing-walk-desc">{desc}</p>
705 </div>
706);
707
708const CompareRow: FC<{
709 feature: string;
710 them: string;
711 us: string;
712 highlight?: boolean;
713}> = ({ feature, them, us, highlight }) => (
714 <div class={`landing-compare-row${highlight ? " landing-compare-hl" : ""}`}>
715 <div class="landing-compare-feature">{feature}</div>
716 <div class="landing-compare-them">{them}</div>
717 <div class="landing-compare-us">{us === "✓" ? "✓" : us}</div>
718 </div>
719);
720
721const PricingCard: FC<{
722 tier: string;
723 price: string;
724 cadence: string;
725 desc: string;
726 features: string[];
727 cta: string;
728 href: string;
729 highlight?: boolean;
730}> = ({ tier, price, cadence, desc, features, cta, href, highlight }) => (
731 <div class={`landing-price-card${highlight ? " landing-price-hl" : ""}`}>
732 {highlight && <div class="landing-price-badge">Most popular</div>}
733 <div class="landing-price-tier">{tier}</div>
734 <div class="landing-price-amount">
735 <span class="landing-price-num">{price}</span>
736 <span class="landing-price-cad">{cadence}</span>
737 </div>
738 <p class="landing-price-desc">{desc}</p>
739 <ul class="landing-price-features">
740 {features.map((f) => (
741 <li>
742 <span class="landing-price-check" aria-hidden="true">{"✓"}</span>
743 {f}
744 </li>
745 ))}
746 </ul>
747 <a
748 href={href}
749 class={`btn ${highlight ? "btn-primary" : "btn-secondary"} btn-block landing-price-cta`}
750 >
751 {cta}
752 </a>
753 </div>
754);
755
52ad8b1Claude756// ─────────────────────────────────────────────────────────────────
757// Block L4 — social-proof tile builder.
758//
759// Pure: takes the cached PublicStats payload and emits the six
760// landing-page tiles in render order. Exported so tests / future
761// surfaces (dashboard, /about, …) can share the exact same copy.
762// ─────────────────────────────────────────────────────────────────
763
764export interface SocialProofTile {
765 label: string;
766 value: number;
767 prefix?: string;
768 suffix?: string;
769}
770
771export function buildSocialProofTiles(s: PublicStats): SocialProofTile[] {
772 return [
773 { label: "Public repos", value: s.totalPublicRepos },
774 { label: "Developers", value: s.totalUsers },
775 {
776 label: "PRs auto-merged this week",
777 value: s.weeklyPrsAutoMerged,
778 },
779 {
780 label: "Issues built by AI this week",
781 value: s.weeklyIssuesBuiltByAi,
782 },
783 {
784 label: "Deploys shipped this week",
785 value: s.weeklyDeploysShipped,
786 },
787 {
788 label: "Hours saved this week",
789 // Round to whole hours for the tile — the precise 0.1 figure
790 // lives on the dashboard widget; the marketing surface keeps
791 // the number scannable.
792 value: Math.round(s.weeklyHoursSaved),
793 prefix: "~",
794 suffix: "h",
795 },
796 ];
797}
798
534f04aClaude799// ─────────────────────────────────────────────────────────────────
800// Block M1 — Live-now demo feed.
801//
802// A 4-tile row inserted between the hero and the L4 counters tile
803// section, surfacing real autopilot activity from the seeded `demo`
804// owner's repos:
805// 1. Issues queued for AI build (ai:build label, open)
806// 2. PRs auto-merged in the last 24h
807// 3. AI reviews posted today (count + latest 3)
808// 4. Combined activity feed (last 10)
809//
810// SSR-renders an initial snapshot, then re-fetches every 30s via the
811// L3 JSON endpoints so the page feels alive without a websocket.
812// Pure presentational; the route layer owns the DB reads.
813// ─────────────────────────────────────────────────────────────────
814
815/**
816 * Render an ISO timestamp (or Date) as a coarse "about N units ago"
817 * string. Tolerates strings, Dates, NaN, and future timestamps.
818 *
819 * Exported for unit testing.
820 */
821export function relativeTimeFromNow(
822 value: string | Date | number | null | undefined,
823 now: number = Date.now()
824): string {
825 if (value === null || value === undefined) return "just now";
826 let t: number;
827 if (value instanceof Date) {
828 t = value.getTime();
829 } else if (typeof value === "number") {
830 t = value;
831 } else {
832 t = new Date(value).getTime();
833 }
834 if (!Number.isFinite(t)) return "just now";
835 const delta = now - t;
836 // Future timestamps (clock skew) — treat as "just now" rather than
837 // surfacing a confusing negative.
838 if (delta < 0) return "just now";
839 const s = Math.floor(delta / 1000);
840 if (s < 60) return "just now";
841 const m = Math.floor(s / 60);
842 if (m < 60) return `about ${m} minute${m === 1 ? "" : "s"} ago`;
843 const h = Math.floor(m / 60);
844 if (h < 24) return `about ${h} hour${h === 1 ? "" : "s"} ago`;
845 const d = Math.floor(h / 24);
846 return `about ${d} day${d === 1 ? "" : "s"} ago`;
847}
848
849function feedEntryId(e: LandingLiveFeedEntry): string {
850 const at = e.at instanceof Date ? e.at.toISOString() : String(e.at);
851 return `${e.kind}|${e.repo}|${e.ref.type}|${e.ref.number}|${at}`;
852}
853
854function feedEntryLabel(kind: LandingLiveFeedEntry["kind"]): string {
855 switch (kind) {
856 case "auto_merge.merged":
857 return "auto-merged";
858 case "ai_build.dispatched":
859 return "AI-build queued";
860 case "ai_review.posted":
861 return "AI review posted";
862 }
863}
864
865interface LiveNowSectionProps {
866 queued: LandingLiveFeedQueued[];
867 merges: LandingLiveFeedMerge[];
868 reviews: LandingLiveFeedReview[];
869 reviewCount: number;
870 feed: LandingLiveFeedEntry[];
871}
872
873const LiveNowSection: FC<LiveNowSectionProps> = ({
874 queued,
875 merges,
876 reviews,
877 reviewCount,
878 feed,
879}) => {
880 return (
881 <section class="landing-livenow" aria-labelledby="landing-livenow-h">
882 <div class="landing-livenow-head">
883 <div class="landing-livenow-eyebrow">
884 <span class="landing-livenow-pulse" aria-hidden="true" />
885 Live now
886 </div>
887 <h2 id="landing-livenow-h" class="landing-livenow-title">
888 Claude is working on demo repos as you read this.
889 </h2>
890 <p class="landing-livenow-sub">
891 Every card below is real data from the public{" "}
892 <code>{DEMO_USERNAME}/*</code> repos. Refreshes every 30 seconds.
893 </p>
894 </div>
895
896 <div class="landing-livenow-grid" data-livenow-grid>
897 {/* Card 1 — queued issues */}
898 <article class="landing-livecard" aria-labelledby="lc-queued-h">
899 <header class="landing-livecard-head">
900 <span class="landing-livecard-dot" aria-hidden="true" />
901 <h3 id="lc-queued-h" class="landing-livecard-title">
902 Issues queued for AI
903 </h3>
904 </header>
905 <ul class="landing-livecard-list" data-livecard="queued">
906 {queued.length === 0 ? (
907 <li class="landing-livecard-empty">
908 No queued AI builds — quiet right now.
909 </li>
910 ) : (
911 queued.slice(0, 3).map((i) => (
912 <li
913 class="landing-livecard-row"
914 data-row-id={`queued|${i.repo}|${i.number}`}
915 >
916 <a
917 class="landing-livecard-link"
918 href={`/${DEMO_USERNAME}/${i.repo}/issues/${i.number}`}
919 >
920 <span class="landing-livecard-num">#{i.number}</span>{" "}
921 <span class="landing-livecard-title-text">{i.title}</span>
922 </a>
923 <div class="landing-livecard-meta">
924 <span class="landing-livecard-repo">{i.repo}</span>
925 </div>
926 </li>
927 ))
928 )}
929 </ul>
930 </article>
931
932 {/* Card 2 — recently merged */}
933 <article class="landing-livecard" aria-labelledby="lc-merges-h">
934 <header class="landing-livecard-head">
935 <span class="landing-livecard-dot" aria-hidden="true" />
936 <h3 id="lc-merges-h" class="landing-livecard-title">
937 Recently merged by AI
938 </h3>
939 </header>
940 <ul class="landing-livecard-list" data-livecard="merges">
941 {merges.length === 0 ? (
942 <li class="landing-livecard-empty">
943 No auto-merges in the last 24h.
944 </li>
945 ) : (
946 merges.slice(0, 3).map((m) => (
947 <li
948 class="landing-livecard-row"
949 data-row-id={`merges|${m.repo}|${m.number}`}
950 >
951 <a
952 class="landing-livecard-link"
953 href={`/${DEMO_USERNAME}/${m.repo}/pulls/${m.number}`}
954 >
955 <span class="landing-livecard-num">#{m.number}</span>{" "}
956 <span class="landing-livecard-title-text">{m.title}</span>
957 </a>
958 <div class="landing-livecard-meta">
959 AI merged in{" "}
960 <span class="landing-livecard-repo">{m.repo}</span>{" "}
961 <span
962 class="landing-livecard-rel"
963 data-rel={
964 m.mergedAt instanceof Date
965 ? m.mergedAt.toISOString()
966 : String(m.mergedAt)
967 }
968 >
969 {relativeTimeFromNow(m.mergedAt)}
970 </span>
971 </div>
972 </li>
973 ))
974 )}
975 </ul>
976 </article>
977
978 {/* Card 3 — AI reviews */}
979 <article class="landing-livecard" aria-labelledby="lc-reviews-h">
980 <header class="landing-livecard-head">
981 <span class="landing-livecard-dot" aria-hidden="true" />
982 <h3 id="lc-reviews-h" class="landing-livecard-title">
983 AI reviews posted
984 </h3>
985 </header>
986 <div class="landing-livecard-bignum">
987 <span
988 class="landing-livecard-bignum-n"
989 data-livecard-count="reviews"
990 data-tick-target={String(reviewCount)}
991 >
992 {reviewCount.toLocaleString()}
993 </span>
994 <span class="landing-livecard-bignum-label">reviews today</span>
995 </div>
996 <ul class="landing-livecard-list" data-livecard="reviews">
997 {reviews.length === 0 ? (
998 <li class="landing-livecard-empty">
999 No AI reviews in the last 24h.
1000 </li>
1001 ) : (
1002 reviews.slice(0, 3).map((r) => (
1003 <li
1004 class="landing-livecard-row"
1005 data-row-id={`reviews|${r.repo}|${r.prNumber}`}
1006 >
1007 <a
1008 class="landing-livecard-link"
1009 href={`/${DEMO_USERNAME}/${r.repo}/pulls/${r.prNumber}`}
1010 >
1011 <span class="landing-livecard-num">#{r.prNumber}</span>{" "}
1012 <span class="landing-livecard-snippet">
1013 {r.commentSnippet}
1014 </span>
1015 </a>
1016 <div class="landing-livecard-meta">
1017 <span class="landing-livecard-repo">{r.repo}</span>
1018 </div>
1019 </li>
1020 ))
1021 )}
1022 </ul>
1023 </article>
1024
1025 {/* Card 4 — activity feed */}
1026 <article class="landing-livecard" aria-labelledby="lc-feed-h">
1027 <header class="landing-livecard-head">
1028 <span class="landing-livecard-dot" aria-hidden="true" />
1029 <h3 id="lc-feed-h" class="landing-livecard-title">
1030 Activity feed
1031 </h3>
1032 </header>
1033 <ul class="landing-livecard-list landing-livecard-feed" data-livecard="feed">
1034 {feed.length === 0 ? (
1035 <li class="landing-livecard-empty">
1036 Quiet right now — check back in a minute.
1037 </li>
1038 ) : (
1039 feed.slice(0, 10).map((e) => {
1040 const path = e.ref.type === "pr" ? "pulls" : "issues";
1041 const id = feedEntryId(e);
1042 return (
1043 <li class="landing-livecard-feedrow" data-row-id={id}>
1044 <span
1045 class={`landing-livecard-kind landing-livecard-kind-${e.kind.replace(/\./g, "-")}`}
1046 >
1047 {feedEntryLabel(e.kind)}
1048 </span>{" "}
1049 <a
1050 class="landing-livecard-link"
1051 href={`/${DEMO_USERNAME}/${e.repo}/${path}/${e.ref.number}`}
1052 >
1053 {e.repo} #{e.ref.number}
1054 </a>{" "}
1055 <span
1056 class="landing-livecard-rel"
1057 data-rel={
1058 e.at instanceof Date ? e.at.toISOString() : String(e.at)
1059 }
1060 >
1061 {relativeTimeFromNow(e.at)}
1062 </span>
1063 </li>
1064 );
1065 })
1066 )}
1067 </ul>
1068 </article>
1069 </div>
1070
1071 <div class="landing-livenow-cta">
1072 <span class="landing-livenow-cta-text">
1073 Want this for your repos?
1074 </span>
1075 <a class="landing-livenow-cta-link" href="/register">
1076 Sign up free
1077 <span class="landing-cta-arrow" aria-hidden="true">{"→"}</span>
1078 </a>
1079 <span class="landing-livenow-cta-sep" aria-hidden="true">·</span>
1080 <a class="landing-livenow-cta-link" href="/demo">
1081 Try the live demo
1082 <span class="landing-cta-arrow" aria-hidden="true">{"→"}</span>
1083 </a>
1084 </div>
1085
1086 <script dangerouslySetInnerHTML={{ __html: liveNowJs }} />
1087 </section>
1088 );
1089};
1090
1091// Inline poller. Plain JS so we don't ship a separate bundle. Hits the
1092// four L3 JSON endpoints every 30s, re-renders the four cards, ticks
1093// the big number, refreshes relative timestamps, flashes new rows.
1094const liveNowJs = `
1095(function(){
1096try{
1097 var DEMO=${JSON.stringify(DEMO_USERNAME)};
1098 var INTERVAL=30000;
1099 function esc(s){return String(s==null?'':s).replace(/[&<>"']/g,function(c){return {'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#39;'}[c];});}
1100 function rel(v){
1101 if(v==null) return 'just now';
1102 var t=(v instanceof Date)?v.getTime():(typeof v==='number'?v:new Date(v).getTime());
1103 if(!isFinite(t)) return 'just now';
1104 var d=Date.now()-t;
1105 if(d<0) return 'just now';
1106 var s=Math.floor(d/1000);
1107 if(s<60) return 'just now';
1108 var m=Math.floor(s/60);
1109 if(m<60) return 'about '+m+' minute'+(m===1?'':'s')+' ago';
1110 var h=Math.floor(m/60);
1111 if(h<24) return 'about '+h+' hour'+(h===1?'':'s')+' ago';
1112 var dd=Math.floor(h/24);
1113 return 'about '+dd+' day'+(dd===1?'':'s')+' ago';
1114 }
1115 function tickNumber(el,target){
1116 if(!el) return;
1117 var start=parseInt(el.getAttribute('data-tick-current')||'0',10)||0;
1118 if(start===target){el.textContent=target.toLocaleString();el.setAttribute('data-tick-current',String(target));return;}
1119 var dur=800,t0=performance.now();
1120 function step(now){
1121 var p=Math.min(1,(now-t0)/dur);
1122 var eased=1-Math.pow(1-p,3);
1123 var v=Math.round(start+(target-start)*eased);
1124 el.textContent=v.toLocaleString();
1125 if(p<1) requestAnimationFrame(step); else el.setAttribute('data-tick-current',String(target));
1126 }
1127 requestAnimationFrame(step);
1128 }
1129 function flashRow(li){
1130 if(!li) return;
1131 li.classList.add('landing-livecard-flash');
1132 setTimeout(function(){li.classList.remove('landing-livecard-flash');},1100);
1133 }
1134 function diffMount(ul,newHtml,newIds){
1135 if(!ul) return;
1136 var prev={};
1137 var nodes=ul.querySelectorAll('[data-row-id]');
1138 for(var i=0;i<nodes.length;i++){prev[nodes[i].getAttribute('data-row-id')]=true;}
1139 ul.innerHTML=newHtml;
1140 var fresh=ul.querySelectorAll('[data-row-id]');
1141 for(var j=0;j<fresh.length;j++){
1142 var id=fresh[j].getAttribute('data-row-id');
1143 if(id && !prev[id]) flashRow(fresh[j]);
1144 }
1145 }
1146 function pollQueued(){
1147 return fetch('/api/v2/demo/queued',{credentials:'omit'}).then(function(r){return r.json();}).then(function(d){
1148 var ul=document.querySelector('[data-livecard="queued"]');if(!ul) return;
1149 var items=(d&&d.items)||[];
1150 if(items.length===0){ul.innerHTML='<li class="landing-livecard-empty">No queued AI builds — quiet right now.</li>';return;}
1151 var ids=[];
1152 var html=items.slice(0,3).map(function(i){
1153 var id='queued|'+i.repo+'|'+i.number;ids.push(id);
1154 return '<li class="landing-livecard-row" data-row-id="'+esc(id)+'">'+
1155 '<a class="landing-livecard-link" href="/'+esc(DEMO)+'/'+esc(i.repo)+'/issues/'+i.number+'">'+
1156 '<span class="landing-livecard-num">#'+i.number+'</span> '+
1157 '<span class="landing-livecard-title-text">'+esc(i.title)+'</span></a>'+
1158 '<div class="landing-livecard-meta"><span class="landing-livecard-repo">'+esc(i.repo)+'</span></div></li>';
1159 }).join('');
1160 diffMount(ul,html,ids);
1161 }).catch(function(){});
1162 }
1163 function pollMerges(){
1164 return fetch('/api/v2/demo/merges',{credentials:'omit'}).then(function(r){return r.json();}).then(function(d){
1165 var ul=document.querySelector('[data-livecard="merges"]');if(!ul) return;
1166 var items=(d&&d.items)||[];
1167 if(items.length===0){ul.innerHTML='<li class="landing-livecard-empty">No auto-merges in the last 24h.</li>';return;}
1168 var ids=[];
1169 var html=items.slice(0,3).map(function(m){
1170 var id='merges|'+m.repo+'|'+m.number;ids.push(id);
1171 return '<li class="landing-livecard-row" data-row-id="'+esc(id)+'">'+
1172 '<a class="landing-livecard-link" href="/'+esc(DEMO)+'/'+esc(m.repo)+'/pulls/'+m.number+'">'+
1173 '<span class="landing-livecard-num">#'+m.number+'</span> '+
1174 '<span class="landing-livecard-title-text">'+esc(m.title)+'</span></a>'+
1175 '<div class="landing-livecard-meta">AI merged in <span class="landing-livecard-repo">'+esc(m.repo)+'</span> '+
1176 '<span class="landing-livecard-rel" data-rel="'+esc(m.mergedAt)+'">'+esc(rel(m.mergedAt))+'</span></div></li>';
1177 }).join('');
1178 diffMount(ul,html,ids);
1179 }).catch(function(){});
1180 }
1181 function pollReviews(){
1182 return fetch('/api/v2/demo/reviews',{credentials:'omit'}).then(function(r){return r.json();}).then(function(d){
1183 var ul=document.querySelector('[data-livecard="reviews"]');if(!ul) return;
1184 var bigEl=document.querySelector('[data-livecard-count="reviews"]');
1185 var n=(d&&typeof d.count==='number')?d.count:0;
1186 if(bigEl) tickNumber(bigEl,n);
1187 var items=(d&&d.items)||[];
1188 if(items.length===0){ul.innerHTML='<li class="landing-livecard-empty">No AI reviews in the last 24h.</li>';return;}
1189 var ids=[];
1190 var html=items.slice(0,3).map(function(r){
1191 var id='reviews|'+r.repo+'|'+r.prNumber;ids.push(id);
1192 return '<li class="landing-livecard-row" data-row-id="'+esc(id)+'">'+
1193 '<a class="landing-livecard-link" href="/'+esc(DEMO)+'/'+esc(r.repo)+'/pulls/'+r.prNumber+'">'+
1194 '<span class="landing-livecard-num">#'+r.prNumber+'</span> '+
1195 '<span class="landing-livecard-snippet">'+esc(r.commentSnippet)+'</span></a>'+
1196 '<div class="landing-livecard-meta"><span class="landing-livecard-repo">'+esc(r.repo)+'</span></div></li>';
1197 }).join('');
1198 diffMount(ul,html,ids);
1199 }).catch(function(){});
1200 }
1201 function pollFeed(){
1202 return fetch('/api/v2/demo/activity',{credentials:'omit'}).then(function(r){return r.json();}).then(function(d){
1203 var ul=document.querySelector('[data-livecard="feed"]');if(!ul) return;
1204 var entries=(d&&d.entries)||[];
1205 if(entries.length===0){ul.innerHTML='<li class="landing-livecard-empty">Quiet right now — check back in a minute.</li>';return;}
1206 var ids=[];
1207 var html=entries.slice(0,10).map(function(e){
1208 var path=(e.ref&&e.ref.type==='pr')?'pulls':'issues';
1209 var num=(e.ref&&e.ref.number)||0;
1210 var label=e.kind==='auto_merge.merged'?'auto-merged':(e.kind==='ai_build.dispatched'?'AI-build queued':'AI review posted');
1211 var kindCls=String(e.kind||'').replace(/\\./g,'-');
1212 var id=e.kind+'|'+e.repo+'|'+(e.ref&&e.ref.type)+'|'+num+'|'+e.at;ids.push(id);
1213 return '<li class="landing-livecard-feedrow" data-row-id="'+esc(id)+'">'+
1214 '<span class="landing-livecard-kind landing-livecard-kind-'+esc(kindCls)+'">'+esc(label)+'</span> '+
1215 '<a class="landing-livecard-link" href="/'+esc(DEMO)+'/'+esc(e.repo)+'/'+path+'/'+num+'">'+esc(e.repo)+' #'+num+'</a> '+
1216 '<span class="landing-livecard-rel" data-rel="'+esc(e.at)+'">'+esc(rel(e.at))+'</span></li>';
1217 }).join('');
1218 diffMount(ul,html,ids);
1219 }).catch(function(){});
1220 }
1221 function refreshRel(){
1222 var spans=document.querySelectorAll('.landing-livecard-rel[data-rel]');
1223 for(var i=0;i<spans.length;i++){
1224 spans[i].textContent=rel(spans[i].getAttribute('data-rel'));
1225 }
1226 }
1227 function tickAll(){pollQueued();pollMerges();pollReviews();pollFeed();}
1228 // Initial counter tick (count-up from 0) on first paint.
1229 var bigEl0=document.querySelector('[data-livecard-count="reviews"]');
1230 if(bigEl0){
1231 var target=parseInt(bigEl0.getAttribute('data-tick-target')||'0',10)||0;
1232 bigEl0.textContent='0';
1233 tickNumber(bigEl0,target);
1234 }
1235 refreshRel();
1236 setInterval(tickAll,INTERVAL);
1237 setInterval(refreshRel,INTERVAL);
1238 document.addEventListener('visibilitychange',function(){
1239 if(document.visibilityState==='visible'){tickAll();refreshRel();}
1240 });
1241}catch(_){}})();
1242`.trim();
1243
0c3eee5Claude1244// ============================================================
1245// Land2030 — 2030 homepage prelude (closed-loop showcase).
1246// ============================================================
1247//
1248// Renders BEFORE the existing LandingHero so every L10/U1/Q1/M1
1249// regression assertion keeps passing. CSS is scoped under
1250// `.land-2030-*` to avoid colliding with the older `.landing-*`
1251// styles. Inline SVG only — no extra deps.
1252
1253const ClosedLoopDiagram: FC = () => (
1254 <svg
1255 viewBox="0 0 1100 280"
1256 class="land-2030-loop-svg"
1257 role="img"
1258 aria-label="Closed loop: Spec to Code to AI Review to Tests to Merge to Deploy to Monitor to Patch"
1259 >
1260 <defs>
1261 <linearGradient id="land2030LoopLine" x1="0" y1="0" x2="1" y2="0">
1262 <stop offset="0%" stop-color="#8c6dff" stop-opacity="0.0" />
1263 <stop offset="20%" stop-color="#8c6dff" stop-opacity="0.6" />
1264 <stop offset="80%" stop-color="#36c5d6" stop-opacity="0.6" />
1265 <stop offset="100%" stop-color="#36c5d6" stop-opacity="0.0" />
1266 </linearGradient>
1267 <radialGradient id="land2030LoopNode" cx="0.5" cy="0.5" r="0.5">
1268 <stop offset="0%" stop-color="#a48bff" stop-opacity="0.95" />
1269 <stop offset="100%" stop-color="#36c5d6" stop-opacity="0.85" />
1270 </radialGradient>
1271 </defs>
1272 {/* connecting curve */}
1273 <path
1274 d="M 60 140 C 220 40, 880 40, 1040 140 C 880 240, 220 240, 60 140 Z"
1275 fill="none"
1276 stroke="url(#land2030LoopLine)"
1277 stroke-width="2"
1278 class="land-2030-loop-path"
1279 />
1280 {[
1281 { x: 70, y: 140, label: "Spec" },
1282 { x: 215, y: 70, label: "Code" },
1283 { x: 410, y: 50, label: "AI Review" },
1284 { x: 605, y: 50, label: "Tests" },
1285 { x: 800, y: 70, label: "Merge" },
1286 { x: 950, y: 140, label: "Deploy" },
1287 { x: 800, y: 210, label: "Monitor" },
1288 { x: 410, y: 230, label: "Patch" },
1289 ].map((n) => (
1290 <g class="land-2030-loop-node">
1291 <circle cx={n.x} cy={n.y} r="22" fill="url(#land2030LoopNode)" />
1292 <circle
1293 cx={n.x}
1294 cy={n.y}
1295 r="22"
1296 fill="none"
1297 stroke="rgba(255,255,255,0.18)"
1298 stroke-width="1"
1299 />
1300 <text
1301 x={n.x}
1302 y={n.y + (n.y > 140 ? 48 : -32)}
1303 text-anchor="middle"
1304 class="land-2030-loop-text"
1305 fill="currentColor"
1306 >
1307 {n.label}
1308 </text>
1309 </g>
1310 ))}
1311 </svg>
1312);
1313
1314interface Land2030CardProps {
1315 icon: any;
1316 title: string;
1317 desc: string;
1318 href: string;
1319 cta?: string;
1320}
1321const Land2030Card: FC<Land2030CardProps> = ({ icon, title, desc, href, cta }) => (
1322 <a href={href} class="land-2030-card">
1323 <div class="land-2030-card-icon" aria-hidden="true">
1324 {icon}
1325 </div>
1326 <h3 class="land-2030-card-title">{title}</h3>
1327 <p class="land-2030-card-desc">{desc}</p>
1328 <span class="land-2030-card-cta">
1329 {cta ?? "Try it"}
1330 <span aria-hidden="true">{" →"}</span>
1331 </span>
1332 </a>
1333);
1334
1335interface Land2030FeatureItem {
1336 title: string;
1337 desc: string;
1338 href: string;
1339 status: "live" | "beta" | "soon";
1340}
1341const LAND_2030_FEATURES: Land2030FeatureItem[] = [
1342 { title: "Spec-to-PR", desc: "Write English. Ship code.", href: "/specs", status: "live" },
1343 { title: "Voice-to-PR", desc: "Talk. Ship code.", href: "/voice-to-pr", status: "live" },
1344 { title: "Repo chat", desc: "Rubber-duck with a semantic index of your repo.", href: "/ask", status: "live" },
1345 { title: "AI CI healer", desc: "Broken CI? Claude opens a fix PR.", href: "/inbox", status: "live" },
1346 { title: "AI patch generator", desc: "Security finding → patch PR, signed by Claude.", href: "/code-scanning", status: "live" },
1347 { title: "AI proactive monitor", desc: "Claude opens issues unprompted when it spots smells.", href: "/standups", status: "live" },
1348 { title: "AI commit messages", desc: "`gluecron commit` writes a great message for the staged diff.", href: "/help", status: "live" },
1349 { title: "AI release notes", desc: "Claude reads merged PRs and writes the changelog.", href: "/ai-changelog", status: "live" },
1350 { title: "Multi-repo refactor", desc: "One English request → coordinated PRs across N repos.", href: "/refactors", status: "live" },
1351 { title: "Migration assistant", desc: "Major-version upgrades drafted PR-by-PR.", href: "/migration-assistant", status: "live" },
1352 { title: "AI test generator", desc: "Every PR auto-gets tests for the new diff.", href: "/ai-tests", status: "beta" },
1353 { title: "PR slash commands", desc: "/merge, /rebase, /explain, /test — Claude runs them.", href: "/help", status: "live" },
1354 { title: "Live co-editing on PRs", desc: "Figma-style cursors on PR descriptions and reviews.", href: "/pulls", status: "beta" },
1355 { title: "Branch preview URLs", desc: "Every push → a sharable preview URL.", href: "/previews", status: "live" },
1356 { title: "Agent multiplayer", desc: "Per-agent sessions, budgets, and branch namespacing.", href: "/settings/agents", status: "live" },
1357 { title: "Continuous semantic index", desc: "Push-time embeddings keep search and chat fresh.", href: "/semantic-search", status: "live" },
1358 { title: "VS Code extension", desc: "Inbox, PRs, and repo chat — in your editor.", href: "/help", status: "soon" },
1359 { title: "Slack / Discord bot", desc: "Mentions, reviews, deploys — in your team chat.", href: "/help", status: "soon" },
1360];
1361
1362const Land2030: FC = () => (
1363 <>
1364 <style dangerouslySetInnerHTML={{ __html: land2030Css }} />
1365 <div class="land-2030-root">
1366 {/* ---------- 2030 HERO ---------- */}
1367 <section class="land-2030-hero" aria-label="Gluecron 2030">
1368 <div class="land-2030-hairline" aria-hidden="true" />
1369 <div class="land-2030-orb" aria-hidden="true" />
1370 <div class="land-2030-hero-inner">
1371 <div class="land-2030-eyebrow">
1372 <span class="land-2030-pulse" aria-hidden="true" />
1373 Gluecron — built for 2030
1374 </div>
1375 <h1 class="land-2030-display">
1376 <span class="land-2030-grad-1">Write</span>{" "}
1377 <span class="land-2030-grad-2">English.</span>{" "}
1378 <span class="land-2030-grad-3">Ship</span>{" "}
1379 <span class="land-2030-grad-4">code.</span>{" "}
1380 <span class="land-2030-grad-5">Gluecron.</span>
1381 </h1>
1382 <p class="land-2030-sub">
1383 The git platform built for the era when AI ships most of the code.
1384 </p>
1385 <div class="land-2030-cta-row">
1386 <a href="/register" class="btn btn-primary btn-xl land-2030-cta-primary">
1387 Start free
1388 <span aria-hidden="true">{" →"}</span>
1389 </a>
1390 <a href="#land-2030-loop" class="btn btn-xl land-2030-cta-secondary">
1391 Watch the loop
1392 <span aria-hidden="true">{" ↓"}</span>
1393 </a>
1394 </div>
1395 </div>
1396 </section>
1397
1398 {/* ---------- THE CLOSED LOOP ---------- */}
1399 <section id="land-2030-loop" class="land-2030-section">
1400 <div class="land-2030-section-head">
1401 <div class="land-2030-eyebrow land-2030-eyebrow-mini">The closed loop</div>
1402 <h2 class="land-2030-h2">One platform. No glue code.</h2>
1403 <p class="land-2030-lede">
1404 Spec → Code → AI Review → Tests → Merge → Deploy → Monitor → Patch.
1405 All on Gluecron. No GitHub + Copilot + Vercel + Sentry stitching
1406 needed.
1407 </p>
1408 </div>
1409 <div class="land-2030-loop-wrap">
1410 <ClosedLoopDiagram />
1411 </div>
1412 </section>
1413
1414 {/* ---------- THE 6 THINGS NOBODY ELSE CAN DO ---------- */}
1415 <section class="land-2030-section">
1416 <div class="land-2030-section-head">
1417 <div class="land-2030-eyebrow land-2030-eyebrow-mini">Unfair advantages</div>
1418 <h2 class="land-2030-h2">What Gluecron does that nobody else can.</h2>
1419 </div>
1420 <div class="land-2030-card-grid">
1421 <Land2030Card
1422 href="/voice-to-pr"
1423 title="Voice-to-PR"
1424 desc="Speak the change you want. Claude opens the PR. Works on your commute."
1425 icon={
1426 <svg viewBox="0 0 24 24" width="24" height="24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="9" y="2" width="6" height="12" rx="3" /><path d="M5 10v2a7 7 0 0 0 14 0v-2" /><path d="M12 19v3" /></svg>
1427 }
1428 />
1429 <Land2030Card
1430 href="/specs"
1431 title="Spec-to-PR"
1432 desc="Write the spec in plain English. Claude implements it, opens a PR, and asks for review."
1433 icon={
1434 <svg viewBox="0 0 24 24" width="24" height="24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z" /><path d="M14 2v6h6" /><path d="M9 13h6M9 17h4" /></svg>
1435 }
1436 />
1437 <Land2030Card
1438 href="/workflows"
1439 title="AI CI self-healing"
1440 desc="Tests go red? Claude reads the log, finds the cause, and pushes the fix to your PR branch."
1441 icon={
1442 <svg viewBox="0 0 24 24" width="24" height="24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M22 11.08V12a10 10 0 1 1-5.93-9.14" /><polyline points="22 4 12 14.01 9 11.01" /></svg>
1443 }
1444 />
1445 <Land2030Card
1446 href="/refactors"
1447 title="Multi-repo refactor agent"
1448 desc="One English request → coordinated PRs across every repo that uses the symbol."
1449 icon={
1450 <svg viewBox="0 0 24 24" width="24" height="24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="6" cy="6" r="3" /><circle cx="18" cy="6" r="3" /><circle cx="12" cy="18" r="3" /><path d="M6 9v3a3 3 0 0 0 3 3h6a3 3 0 0 0 3-3V9" /></svg>
1451 }
1452 />
1453 <Land2030Card
1454 href="/ask"
1455 title="Repo chat with semantic search"
1456 desc="Continuous push-time embeddings. Ask the repo anything; it cites real files and commits."
1457 icon={
1458 <svg viewBox="0 0 24 24" width="24" height="24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z" /><circle cx="9" cy="10" r="1" fill="currentColor" /><circle cx="13" cy="10" r="1" fill="currentColor" /><circle cx="17" cy="10" r="1" fill="currentColor" /></svg>
1459 }
1460 />
1461 <Land2030Card
1462 href="/pulls"
1463 title="Per-PR live co-editing"
1464 desc="Figma-style cursors and presence on PR descriptions and reviews. Goodbye stale tabs."
1465 icon={
1466 <svg viewBox="0 0 24 24" width="24" height="24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M9 11l3 3L22 4" /><path d="M21 12v7a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11" /></svg>
1467 }
1468 />
1469 </div>
1470 </section>
1471
1472 {/* ---------- GLOBAL DASHBOARDS ---------- */}
1473 <section class="land-2030-section">
1474 <div class="land-2030-section-head">
1475 <div class="land-2030-eyebrow land-2030-eyebrow-mini">Mission control</div>
1476 <h2 class="land-2030-h2">Every signal, one inbox.</h2>
1477 <p class="land-2030-lede">
1478 Five global dashboards that span every repo you touch — no more
1479 tab-juggling across orgs.
1480 </p>
1481 </div>
1482 <div class="land-2030-dash-grid">
1483 <a href="/pulls" class="land-2030-dash">
1484 <span class="land-2030-dash-name">/pulls</span>
1485 <span class="land-2030-dash-desc">PR command center across every repo you can touch.</span>
1486 </a>
1487 <a href="/issues" class="land-2030-dash">
1488 <span class="land-2030-dash-name">/issues</span>
1489 <span class="land-2030-dash-desc">Global issue dashboard. Triage from one screen.</span>
1490 </a>
1491 <a href="/inbox" class="land-2030-dash">
1492 <span class="land-2030-dash-name">/inbox</span>
1493 <span class="land-2030-dash-desc">Unified mentions, reviews, CI, and AI events.</span>
1494 </a>
1495 <a href="/activity" class="land-2030-dash">
1496 <span class="land-2030-dash-name">/activity</span>
1497 <span class="land-2030-dash-desc">A timeline of everything that moved on your repos.</span>
1498 </a>
1499 <a href="/standups" class="land-2030-dash">
1500 <span class="land-2030-dash-name">/standups</span>
1501 <span class="land-2030-dash-desc">Daily AI-generated brief of what shipped and what's stuck.</span>
1502 </a>
1503 </div>
1504 </section>
1505
1506 {/* ---------- FULL AI FEATURE GRID ---------- */}
1507 <section class="land-2030-section">
1508 <div class="land-2030-section-head">
1509 <div class="land-2030-eyebrow land-2030-eyebrow-mini">Closed-loop AI</div>
1510 <h2 class="land-2030-h2">18 AI features. One platform.</h2>
1511 <p class="land-2030-lede">
1512 Most of these don't exist anywhere else. None of them require a
1513 second SaaS subscription.
1514 </p>
1515 </div>
1516 <div class="land-2030-feat-grid">
1517 {LAND_2030_FEATURES.map((f) => (
1518 <a href={f.href} class="land-2030-feat">
1519 <div class="land-2030-feat-head">
1520 <span class="land-2030-feat-title">{f.title}</span>
1521 <span class={`land-2030-pill land-2030-pill-${f.status}`}>
1522 {f.status === "live" ? "live" : f.status === "beta" ? "beta" : "soon"}
1523 </span>
1524 </div>
1525 <p class="land-2030-feat-desc">{f.desc}</p>
1526 </a>
1527 ))}
1528 </div>
1529 </section>
1530
1531 {/* ---------- DEVELOPER EXPERIENCE ---------- */}
1532 <section class="land-2030-section">
1533 <div class="land-2030-section-head">
1534 <div class="land-2030-eyebrow land-2030-eyebrow-mini">Developer surface</div>
1535 <h2 class="land-2030-h2">Built where you already are.</h2>
1536 </div>
1537 <div class="land-2030-dx-grid">
1538 <div class="land-2030-dx">
1539 <h3 class="land-2030-dx-title">gluecron CLI</h3>
1540 <pre class="land-2030-code"><code>$ gluecron spec "add CSV export to /api/orders"
1541{"→ Drafting PR…"}
1542{"→ Opened #482 with 3 commits"}
1543{"→ AI review queued"}</code></pre>
1544 </div>
1545 <div class="land-2030-dx">
1546 <h3 class="land-2030-dx-title">PR slash commands</h3>
1547 <pre class="land-2030-code"><code>/merge — squash + merge when checks pass
1548/rebase — rebase onto base, push --force-with-lease
1549/explain — Claude explains the diff in plain English
1550/test — Claude writes tests for the new code</code></pre>
1551 </div>
1552 <div class="land-2030-dx">
1553 <h3 class="land-2030-dx-title">Branch preview URLs</h3>
1554 <pre class="land-2030-code"><code>{"→ git push gluecron HEAD"}
1555{"→ preview: https://pr-482.preview.gluecron.com"}
1556{"→ commented on PR #482"}</code></pre>
1557 </div>
1558 </div>
1559 </section>
1560
1561 {/* ---------- BUILT FOR AGENTS ---------- */}
1562 <section class="land-2030-section land-2030-section-dark">
1563 <div class="land-2030-section-head">
1564 <div class="land-2030-eyebrow land-2030-eyebrow-mini">Agent era</div>
1565 <h2 class="land-2030-h2">Built for agents, not just humans.</h2>
1566 <p class="land-2030-lede">
1567 Per-agent tokens, per-agent budgets, per-agent branch namespaces,
1568 and a lease primitive so 50 agents don't trample one repo.
1569 </p>
1570 </div>
1571 <div class="land-2030-agent-wrap">
1572 <pre class="land-2030-code land-2030-code-wide"><code>{'# agent gets a scoped token + lease before writing'}
1573{'curl -H "Authorization: Bearer agt_3p9x…" \\\\'}
1574{' -X POST https://gluecron.com/api/v2/leases \\\\'}
1575{' -d \'{"repo":"acme/api","branch":"agent/jules/checkout-fix","ttl":300}\''}
1576{''}
1577{'{ "lease_id": "lse_8a2f", "expires_at": "2030-05-25T14:05:11Z" }'}</code></pre>
1578 <div class="land-2030-agent-stat">
1579 <div class="land-2030-agent-big">10,000</div>
1580 <div class="land-2030-agent-label">
1581 agents pushing to your repo per day. Welcome to 2030.
1582 </div>
1583 <a href="/docs/build-agent-integration" class="land-2030-agent-link">
1584 Build an agent integration{" →"}
1585 </a>
1586 </div>
1587 </div>
1588 </section>
1589
1590 {/* ---------- VS GITHUB ---------- */}
1591 <section class="land-2030-section">
1592 <div class="land-2030-section-head">
1593 <div class="land-2030-eyebrow land-2030-eyebrow-mini">vs GitHub</div>
1594 <h2 class="land-2030-h2">One platform replaces five.</h2>
1595 </div>
1596 <div class="land-2030-vs">
1597 <div class="land-2030-vs-row land-2030-vs-head">
1598 <div>Today</div>
1599 <div>On Gluecron</div>
1600 </div>
1601 <div class="land-2030-vs-row">
1602 <div>GitHub + Copilot + Vercel + Sentry + Linear</div>
1603 <div class="land-2030-vs-us">Gluecron</div>
1604 </div>
1605 <a href="/vs-github" class="land-2030-vs-link">
1606 See the full comparison{" →"}
1607 </a>
1608 </div>
1609 </section>
1610 </div>
1611 </>
1612);
1613
4c47454Claude1614// Backwards-compatible default — web.tsx imports `LandingPage`.
a819f96Claude1615// Single landing surface. The bolted-on <Land2030 /> prelude was rendering a
1616// SECOND full page above LandingHero — two stacked <h1> documents as you
1617// scrolled. Render exactly one page; the 2030 reboot replaces this wholesale.
4c47454Claude1618export const LandingPage: FC<LandingPageProps> = (props) => (
a819f96Claude1619 <LandingHero {...props} />
2b821b7Claude1620);
1621
4c47454Claude1622export default LandingPage;
1623
2b821b7Claude1624const landingCss = `
958d26aClaude1625 /* ============================================================ */
1626 /* Landing — Editorial-Technical 2026.05 */
1627 /* ============================================================ */
4c47454Claude1628 .landing-root {
1629 position: relative;
958d26aClaude1630 max-width: 1180px;
4c47454Claude1631 margin: 0 auto;
1632 padding: 0 16px;
958d26aClaude1633 }
1634 .landing-root > section { position: relative; }
1635
1636 /* ---------- Hero ---------- */
1637 .landing-hero {
1638 position: relative;
c475ee6Claude1639 padding: var(--s-16) 0 var(--s-20);
958d26aClaude1640 text-align: center;
4c47454Claude1641 overflow: hidden;
1642 }
c475ee6Claude1643 .landing-hero-blob-1 {
1644 animation: hero-blob-drift-1 18s var(--ease, ease) infinite alternate;
1645 }
1646 .landing-hero-blob-2 {
1647 animation: hero-blob-drift-2 22s var(--ease, ease) infinite alternate;
1648 }
1649 @keyframes hero-blob-drift-1 {
1650 0% { transform: translate(0, 0) scale(1); opacity: 0.55; }
1651 100% { transform: translate(8%, 6%) scale(1.18); opacity: 0.75; }
1652 }
1653 @keyframes hero-blob-drift-2 {
1654 0% { transform: translate(0, 0) scale(1); opacity: 0.40; }
1655 100% { transform: translate(-10%, -4%) scale(1.25); opacity: 0.60; }
1656 }
1657
1658 /* ---------- Hero product visual: live AI PR review card ---------- */
1659 .landing-hero-visual {
1660 position: relative;
1661 max-width: 760px;
1662 margin: var(--s-12) auto 0;
1663 padding: 0 16px;
1664 perspective: 1400px;
1665 z-index: 2;
1666 opacity: 0;
1667 animation: hero-visual-in 700ms var(--ease-out-expo, cubic-bezier(0.19, 1, 0.22, 1)) 400ms forwards;
1668 }
1669 @keyframes hero-visual-in {
1670 from { opacity: 0; transform: translateY(20px); }
1671 to { opacity: 1; transform: translateY(0); }
1672 }
1673 .hero-pr-card {
1674 position: relative;
1675 background: linear-gradient(180deg, rgba(15,17,26,0.96) 0%, rgba(8,9,15,0.96) 100%);
1676 border: 1px solid var(--border-strong);
1677 border-radius: var(--r-xl);
1678 overflow: hidden;
1679 text-align: left;
1680 box-shadow:
1681 0 30px 80px -20px rgba(0,0,0,0.65),
1682 0 0 0 1px rgba(140,109,255,0.18),
1683 0 0 60px -10px rgba(140,109,255,0.30);
1684 transform: rotateX(2deg) rotateY(-2deg);
1685 transition: transform 600ms var(--ease, ease);
1686 backdrop-filter: blur(12px);
1687 -webkit-backdrop-filter: blur(12px);
1688 }
1689 .landing-hero-visual:hover .hero-pr-card {
1690 transform: rotateX(0deg) rotateY(0deg);
1691 }
1692 .hero-pr-card::before {
1693 content: '';
1694 position: absolute;
1695 inset: 0;
1696 background: linear-gradient(135deg, rgba(140,109,255,0.10), transparent 35%, transparent 65%, rgba(54,197,214,0.08));
1697 pointer-events: none;
1698 }
1699 .hero-pr-header {
1700 display: flex;
1701 align-items: center;
1702 gap: 12px;
1703 padding: 14px 18px;
1704 border-bottom: 1px solid rgba(255,255,255,0.06);
1705 background: rgba(255,255,255,0.025);
1706 font-size: 13px;
1707 }
1708 .hero-pr-dot {
1709 width: 10px; height: 10px;
1710 border-radius: 50%;
1711 background: var(--green);
1712 box-shadow: 0 0 10px rgba(52,211,153,0.6);
1713 flex-shrink: 0;
1714 }
1715 .hero-pr-title {
1716 color: var(--text-strong);
1717 font-weight: 600;
1718 flex: 1;
1719 overflow: hidden;
1720 text-overflow: ellipsis;
1721 white-space: nowrap;
1722 }
1723 .hero-pr-num {
1724 color: var(--text-faint);
1725 font-family: var(--font-mono);
1726 font-weight: 500;
1727 margin-right: 8px;
1728 }
1729 .hero-pr-status {
1730 display: inline-flex;
1731 align-items: center;
1732 gap: 6px;
1733 padding: 3px 10px;
1734 border-radius: var(--r-full);
1735 background: var(--accent-gradient-faint);
1736 border: 1px solid rgba(140,109,255,0.30);
1737 color: var(--accent);
1738 font-family: var(--font-mono);
1739 font-size: 11px;
1740 letter-spacing: 0.04em;
1741 flex-shrink: 0;
1742 }
1743 .hero-pr-status-pulse {
1744 width: 6px; height: 6px;
1745 border-radius: 50%;
1746 background: var(--accent);
1747 box-shadow: 0 0 0 0 rgba(140,109,255,0.6);
1748 animation: hero-pulse 1.6s ease-out infinite;
1749 }
1750 @keyframes hero-pulse {
1751 0% { box-shadow: 0 0 0 0 rgba(140,109,255,0.55); }
1752 70% { box-shadow: 0 0 0 8px rgba(140,109,255,0); }
1753 100% { box-shadow: 0 0 0 0 rgba(140,109,255,0); }
1754 }
1755
1756 .hero-pr-body {
1757 padding: 0;
1758 }
1759 .hero-pr-file {
1760 display: flex;
1761 align-items: center;
1762 gap: 10px;
1763 padding: 10px 18px;
1764 border-bottom: 1px solid rgba(255,255,255,0.05);
1765 font-family: var(--font-mono);
1766 font-size: 12px;
1767 background: rgba(255,255,255,0.012);
1768 }
1769 .hero-pr-file-icon { color: var(--accent-2); }
1770 .hero-pr-file-name { color: var(--text); flex: 1; }
1771 .hero-pr-file-stats { display: inline-flex; gap: 8px; }
1772 .hero-pr-add { color: var(--green); font-weight: 600; }
1773 .hero-pr-del { color: var(--red); font-weight: 600; }
1774
1775 .hero-pr-diff {
1776 padding: 12px 18px;
1777 font-family: var(--font-mono);
1778 font-feature-settings: var(--mono-feat, 'calt');
1779 font-size: 12.5px;
1780 line-height: 1.7;
1781 color: rgba(237,237,242,0.85);
1782 overflow-x: auto;
1783 }
1784 .hero-pr-hunk {
1785 color: rgba(140,109,255,0.85);
1786 background: rgba(140,109,255,0.06);
1787 padding: 2px 8px;
1788 margin: 0 -8px 4px;
1789 border-radius: 4px;
1790 }
1791 .hero-pr-line-add {
1792 background: rgba(52,211,153,0.08);
1793 color: rgba(167,243,208,0.95);
1794 padding: 0 8px;
1795 margin: 0 -8px;
1796 border-left: 2px solid var(--green);
1797 padding-left: 8px;
1798 }
1799
1800 .hero-pr-comment {
1801 margin: 14px 18px;
1802 padding: 14px 16px;
1803 background: linear-gradient(135deg, rgba(140,109,255,0.08), rgba(54,197,214,0.05));
1804 border: 1px solid rgba(140,109,255,0.25);
1805 border-radius: var(--r-md);
1806 }
1807 .hero-pr-bot-row {
1808 display: flex;
1809 align-items: center;
1810 gap: 8px;
1811 margin-bottom: 8px;
1812 font-size: 12px;
1813 }
1814 .hero-pr-bot-avatar {
1815 width: 22px; height: 22px;
1816 display: inline-flex;
1817 align-items: center;
1818 justify-content: center;
1819 border-radius: 50%;
1820 background: var(--accent-gradient);
1821 font-size: 11px;
1822 box-shadow: 0 0 12px rgba(140,109,255,0.40);
1823 }
1824 .hero-pr-bot-name {
1825 color: var(--text-strong);
1826 font-weight: 600;
1827 }
1828 .hero-pr-bot-meta {
1829 color: var(--text-faint);
1830 font-family: var(--font-mono);
1831 }
1832 .hero-pr-bot-text {
1833 color: var(--text);
1834 font-size: 13px;
1835 line-height: 1.55;
1836 margin: 0;
1837 }
1838 .hero-pr-bot-text code {
1839 background: rgba(255,255,255,0.06);
1840 border: 1px solid rgba(255,255,255,0.10);
1841 padding: 1px 6px;
1842 border-radius: 4px;
1843 font-size: 11.5px;
1844 color: var(--accent);
1845 }
1846 .hero-pr-bot-link { color: var(--accent-2); text-decoration: underline; text-decoration-style: dotted; }
1847
1848 .hero-pr-gates {
1849 display: flex;
1850 flex-wrap: wrap;
1851 gap: 6px;
1852 padding: 12px 18px 16px;
1853 border-top: 1px solid rgba(255,255,255,0.06);
1854 background: rgba(255,255,255,0.012);
1855 }
1856 .hero-pr-gate {
1857 display: inline-flex;
1858 align-items: center;
1859 gap: 6px;
1860 padding: 4px 10px;
1861 border-radius: var(--r-full);
1862 font-family: var(--font-mono);
1863 font-size: 11px;
1864 border: 1px solid;
1865 }
1866 .hero-pr-gate-pass {
1867 color: var(--green);
1868 background: rgba(52,211,153,0.08);
1869 border-color: rgba(52,211,153,0.30);
1870 }
1871 .hero-pr-gate-running {
1872 color: var(--accent);
1873 background: var(--accent-gradient-faint);
1874 border-color: rgba(140,109,255,0.40);
1875 }
1876 .hero-pr-gate-spin {
1877 width: 9px; height: 9px;
1878 border: 1.5px solid rgba(140,109,255,0.30);
1879 border-top-color: var(--accent);
1880 border-radius: 50%;
1881 animation: hero-spin 800ms linear infinite;
1882 }
1883 @keyframes hero-spin {
1884 to { transform: rotate(360deg); }
1885 }
1886
1887 /* Floating accent badges around the card */
1888 .hero-float {
1889 position: absolute;
1890 display: inline-flex;
1891 align-items: center;
1892 gap: 6px;
1893 padding: 6px 12px;
1894 background: rgba(15,17,26,0.92);
1895 border: 1px solid rgba(140,109,255,0.35);
1896 border-radius: var(--r-full);
1897 font-family: var(--font-mono);
1898 font-size: 11px;
1899 color: var(--text);
1900 box-shadow: 0 12px 24px -8px rgba(0,0,0,0.5), 0 0 18px -4px rgba(140,109,255,0.30);
1901 backdrop-filter: blur(8px);
1902 -webkit-backdrop-filter: blur(8px);
1903 }
1904 .hero-float-icon { color: var(--accent); }
1905 .hero-float-1 {
1906 top: -14px;
1907 left: -8px;
1908 animation: hero-float-bob-1 5s var(--ease, ease) infinite alternate;
1909 }
1910 .hero-float-2 {
1911 bottom: -14px;
1912 right: -8px;
1913 animation: hero-float-bob-2 6s var(--ease, ease) infinite alternate;
1914 }
1915 @keyframes hero-float-bob-1 {
1916 from { transform: translate(0, 0); }
1917 to { transform: translate(-8px, -10px); }
1918 }
1919 @keyframes hero-float-bob-2 {
1920 from { transform: translate(0, 0); }
1921 to { transform: translate(8px, 8px); }
1922 }
1923
1924 @media (max-width: 720px) {
1925 .landing-hero-visual { padding: 0 8px; }
1926 .hero-pr-card { transform: none; }
1927 .hero-pr-title { font-size: 12px; }
1928 .hero-pr-diff { font-size: 11px; line-height: 1.6; }
1929 .hero-float { display: none; }
1930 }
958d26aClaude1931 .landing-hero-bg {
1932 position: absolute;
1933 inset: -10% -20%;
1934 pointer-events: none;
1935 z-index: 0;
4c47454Claude1936 }
958d26aClaude1937 .landing-hero-blob {
1938 position: absolute;
1939 border-radius: 50%;
1940 filter: blur(80px);
1941 opacity: 0.55;
3a5755eClaude1942 will-change: transform;
958d26aClaude1943 }
1944 .landing-hero-blob-1 {
1945 top: -10%;
1946 left: 30%;
1947 width: 480px;
1948 height: 480px;
3a5755eClaude1949 background: radial-gradient(circle, rgba(140,109,255,0.65), transparent 65%);
1950 /* 2026 polish — slow drift gives the hero "this is a live product"
1951 feel without being distracting. 24s loop, eased, contained motion. */
1952 animation: landingBlobDrift1 24s ease-in-out infinite;
958d26aClaude1953 }
1954 .landing-hero-blob-2 {
1955 top: 10%;
1956 left: 50%;
1957 width: 380px;
1958 height: 380px;
3a5755eClaude1959 background: radial-gradient(circle, rgba(54,197,214,0.50), transparent 65%);
1960 animation: landingBlobDrift2 28s ease-in-out infinite;
958d26aClaude1961 }
dc26881CC LABS App1962 /* U1 — subtle, low-opacity accent-gradient orb behind the headline.
1963 Sits dead-centre, very blurred, so the hero reads as a real product
3a5755eClaude1964 surface rather than flat-bg + text. 2026 polish — gentle breathing
1965 pulse to give the surface a soft heartbeat. */
dc26881CC LABS App1966 .landing-hero-orb {
1967 top: 18%;
1968 left: 50%;
1969 transform: translateX(-50%);
1970 width: 720px;
1971 height: 720px;
3a5755eClaude1972 background: radial-gradient(circle, rgba(140,109,255,0.28), rgba(54,197,214,0.16) 45%, transparent 70%);
dc26881CC LABS App1973 filter: blur(120px);
3a5755eClaude1974 opacity: 0.6;
dc26881CC LABS App1975 z-index: 0;
3a5755eClaude1976 animation: landingOrbBreath 12s ease-in-out infinite;
dc26881CC LABS App1977 }
1978 :root[data-theme='light'] .landing-hero-orb {
1979 opacity: 0.32;
1980 }
3a5755eClaude1981 @keyframes landingBlobDrift1 {
1982 0%, 100% { transform: translate(0, 0) scale(1); }
1983 33% { transform: translate(40px, -30px) scale(1.08); }
1984 66% { transform: translate(-30px, 25px) scale(0.95); }
1985 }
1986 @keyframes landingBlobDrift2 {
1987 0%, 100% { transform: translate(0, 0) scale(1); }
1988 50% { transform: translate(-50px, 35px) scale(1.12); }
1989 }
1990 @keyframes landingOrbBreath {
1991 0%, 100% { opacity: 0.55; transform: translateX(-50%) scale(1); }
1992 50% { opacity: 0.72; transform: translateX(-50%) scale(1.06); }
1993 }
dc26881CC LABS App1994 @media (prefers-reduced-motion: reduce) {
1995 .landing-hero-blob-1,
1996 .landing-hero-blob-2,
1997 .landing-hero-orb {
1998 animation: none;
1999 }
2000 }
958d26aClaude2001 .landing-hero-grid {
4c47454Claude2002 position: absolute;
2003 inset: 0;
958d26aClaude2004 background-image:
2005 linear-gradient(to right, rgba(255,255,255,0.04) 1px, transparent 1px),
2006 linear-gradient(to bottom, rgba(255,255,255,0.04) 1px, transparent 1px);
2007 background-size: 60px 60px;
2008 mask-image: radial-gradient(ellipse 50% 50% at 50% 30%, #000 0%, transparent 75%);
2009 -webkit-mask-image: radial-gradient(ellipse 50% 50% at 50% 30%, #000 0%, transparent 75%);
2010 }
2011 :root[data-theme='light'] .landing-hero-grid {
2012 background-image:
2013 linear-gradient(to right, rgba(15,16,28,0.06) 1px, transparent 1px),
2014 linear-gradient(to bottom, rgba(15,16,28,0.06) 1px, transparent 1px);
4c47454Claude2015 }
2016
958d26aClaude2017 .landing-hero-inner {
2018 position: relative;
2019 z-index: 1;
2020 max-width: 960px;
2b821b7Claude2021 margin: 0 auto;
2022 }
dc26881CC LABS App2023 /* U1 — every block below the headline obeys a single rhythm. */
958d26aClaude2024 .landing-hero-eyebrow {
dc26881CC LABS App2025 margin: 0 auto var(--space-6);
958d26aClaude2026 color: var(--accent);
2027 }
2028 .landing-hero-eyebrow::before { display: none; }
2029 .landing-hero-pulse {
2030 width: 7px;
2031 height: 7px;
2032 border-radius: 50%;
2033 background: var(--accent);
2034 box-shadow: 0 0 0 0 rgba(140,109,255,0.6);
2035 animation: pulse 1.8s ease-out infinite;
2036 flex-shrink: 0;
2037 }
2038 @keyframes pulse {
2039 0% { box-shadow: 0 0 0 0 rgba(140,109,255,0.55); }
2040 70% { box-shadow: 0 0 0 10px rgba(140,109,255,0); }
2041 100% { box-shadow: 0 0 0 0 rgba(140,109,255,0); }
2042 }
2043
2b821b7Claude2044 .landing-hero-title {
3a5755eClaude2045 /* 2026 polish — bigger, bolder, tighter. Inter Tight at 800 weight
2046 with -0.03em tracking is the modern "AI-startup hero" look that
2047 Vercel/Linear/Cursor all use. clamp() scales gracefully on mobile. */
2048 font-size: clamp(40px, 7vw, 84px);
2049 line-height: 1.02;
2050 letter-spacing: -0.032em;
2051 font-weight: 800;
2052 font-family: var(--font-display);
dc26881CC LABS App2053 margin: 0 0 var(--space-6);
958d26aClaude2054 color: var(--text-strong);
2b821b7Claude2055 }
c963db5Claude2056 .landing-hero-title .gradient-text {
3a5755eClaude2057 /* Richer gradient with a third stop for more depth. Drop-shadow
2058 gives the impression of subtle glow without overpowering the type. */
2059 background-image: linear-gradient(135deg, #c2a8ff 0%, #8c6dff 40%, #5d3dff 70%, #36c5d6 100%);
c963db5Claude2060 -webkit-background-clip: text;
2061 background-clip: text;
2062 -webkit-text-fill-color: transparent;
2063 color: transparent;
3a5755eClaude2064 filter: drop-shadow(0 4px 32px rgba(140, 109, 255, 0.18));
c963db5Claude2065 }
958d26aClaude2066
2b821b7Claude2067 .landing-hero-sub {
3a5755eClaude2068 font-size: clamp(16px, 1.8vw, 22px);
2b821b7Claude2069 color: var(--text-muted);
958d26aClaude2070 max-width: 680px;
dc26881CC LABS App2071 margin: 0 auto var(--space-6);
3a5755eClaude2072 line-height: 1.5;
2073 letter-spacing: -0.008em;
2074 font-weight: 400;
2b821b7Claude2075 }
4c47454Claude2076
2b821b7Claude2077 .landing-hero-ctas {
2078 display: flex;
dc26881CC LABS App2079 gap: var(--space-3);
2b821b7Claude2080 justify-content: center;
2081 flex-wrap: wrap;
dc26881CC LABS App2082 margin-top: 0;
2083 margin-bottom: var(--space-4);
2b821b7Claude2084 }
958d26aClaude2085 .landing-cta-arrow {
2086 transition: transform var(--t-base) var(--ease-spring);
2087 display: inline-block;
2b821b7Claude2088 }
cd4f63bTest User2089
dc26881CC LABS App2090 /* U1 — tertiary text-link row.
2091 Sits directly under the 2-button primary CTA row. Smaller, muted,
2092 so it reads as "by the way" rather than competing for the eye. */
2093 .landing-hero-tertiary {
2094 margin-top: 0;
2095 margin-bottom: var(--space-6);
cd4f63bTest User2096 text-align: center;
dc26881CC LABS App2097 display: inline-flex;
2098 flex-wrap: wrap;
2099 gap: var(--space-2) var(--space-3);
2100 justify-content: center;
2101 align-items: baseline;
2102 width: 100%;
cd4f63bTest User2103 font-size: 13px;
2104 color: var(--text-muted);
dc26881CC LABS App2105 }
2106 .landing-hero-tertiary-link {
2107 color: var(--text-muted);
cd4f63bTest User2108 text-decoration: none;
2109 border-bottom: 1px dashed transparent;
2110 padding-bottom: 1px;
dc26881CC LABS App2111 transition: color var(--t-fast) var(--ease),
2112 border-color var(--t-fast) var(--ease);
cd4f63bTest User2113 }
dc26881CC LABS App2114 .landing-hero-tertiary-link:hover {
2115 color: var(--text-strong);
cd4f63bTest User2116 border-bottom-color: var(--text-muted);
2117 }
dc26881CC LABS App2118 .landing-hero-tertiary-sep {
2119 color: var(--text-faint);
2120 user-select: none;
2121 }
958d26aClaude2122 .btn:hover .landing-cta-arrow,
2123 .landing-cta-primary:hover .landing-cta-arrow {
2124 transform: translateX(4px);
8e9f1d9Claude2125 }
2b821b7Claude2126
93fe97eClaude2127 /* BLOCK Q1 — flagship "Add to Claude Desktop" CTA.
2128 Gradient-bordered + accent text so it reads as a peer of the primary
bc7654bCC LABS App2129 Sign-up CTA, not a third secondary. Theme-aware: inner fill uses
2130 --bg-elevated so it's white on light and dark on dark, never the
2131 jarring near-black on white we shipped first time. Subtle elevation
2132 on hover; static when the visitor opts out of motion. */
93fe97eClaude2133 .landing-cta-dxt {
2134 position: relative;
bc7654bCC LABS App2135 background: var(--bg-elevated);
2136 color: var(--text-strong);
93fe97eClaude2137 border: 1px solid transparent;
2138 background-image:
bc7654bCC LABS App2139 linear-gradient(var(--bg-elevated), var(--bg-elevated)),
93fe97eClaude2140 linear-gradient(90deg, #8c6dff 0%, #36c5d6 100%);
2141 background-origin: border-box;
2142 background-clip: padding-box, border-box;
2143 transition: transform var(--t-base, 180ms) var(--ease-spring, ease),
2144 box-shadow var(--t-base, 180ms) var(--ease-spring, ease);
2145 }
2146 .landing-cta-dxt:hover {
2147 transform: translateY(-2px);
2148 box-shadow: 0 8px 24px -8px rgba(140, 109, 255, 0.45);
2149 }
2150 @media (prefers-reduced-motion: reduce) {
2151 .landing-cta-dxt,
2152 .landing-cta-dxt:hover {
2153 transform: none;
2154 transition: none;
2155 }
2156 }
2157
dc26881CC LABS App2158 /* L8 — free-tier reassurance link beneath the CTA row.
2159 U1 — rhythm snapped to var(--space-6). */
5f2e749Claude2160 .landing-hero-freenote {
dc26881CC LABS App2161 margin: 0 auto var(--space-6);
5f2e749Claude2162 font-size: var(--t-sm);
2163 color: var(--text-muted);
2164 text-align: center;
2165 }
2166 .landing-hero-freenote-link {
2167 color: var(--accent);
2168 text-decoration: none;
2169 font-weight: 500;
2170 border-bottom: 1px dotted rgba(140,109,255,0.4);
2171 transition: color var(--t-fast) var(--ease), border-color var(--t-fast) var(--ease);
2172 }
2173 .landing-hero-freenote-link:hover {
2174 color: var(--text-strong);
2175 border-bottom-color: var(--accent);
2176 }
2177
4c47454Claude2178 .landing-hero-caption {
dc26881CC LABS App2179 margin: 0 auto var(--space-6);
958d26aClaude2180 font-size: var(--t-sm);
4c47454Claude2181 color: var(--text-muted);
958d26aClaude2182 display: flex;
2183 flex-wrap: wrap;
2184 align-items: center;
2185 justify-content: center;
dc26881CC LABS App2186 gap: var(--space-3);
2b821b7Claude2187 }
958d26aClaude2188 .landing-hero-cmd {
2189 display: inline-flex;
2190 align-items: center;
2191 gap: 4px;
2192 padding: 6px 12px;
2193 background: var(--bg-elevated);
4c47454Claude2194 border: 1px solid var(--border);
958d26aClaude2195 border-radius: var(--r-full);
2196 box-shadow: var(--elev-1);
2197 }
2198 .landing-hero-cmd .kbd {
2199 border: 0;
2200 background: transparent;
2201 padding: 0 4px;
2202 color: var(--text-muted);
2203 font-size: 12px;
2204 }
2205 .landing-hero-cmd .kbd:nth-last-of-type(1) { color: var(--accent); }
2206 .landing-hero-arrow {
2207 color: var(--text-faint);
2208 font-size: 13px;
2209 margin: 0 2px;
2b821b7Claude2210 }
4c47454Claude2211
2212 .landing-stats {
dc26881CC LABS App2213 margin: 0 auto;
958d26aClaude2214 font-family: var(--font-mono);
2215 font-size: 12px;
2216 color: var(--text-muted);
2217 display: flex;
2218 align-items: center;
2219 justify-content: center;
dc26881CC LABS App2220 gap: var(--space-2);
958d26aClaude2221 flex-wrap: wrap;
2222 letter-spacing: 0.02em;
2223 }
2224 .landing-stats strong {
2225 color: var(--text-strong);
2226 font-weight: 600;
2227 font-feature-settings: 'tnum';
2228 }
2229 .landing-stats-sep { opacity: 0.4; }
2230
c963db5Claude2231 /* ---------- Capability grid (crontech-style uppercase tracked) ---------- */
2232 .landing-caps {
2233 margin: var(--s-12) auto var(--s-16);
2234 max-width: 1080px;
2235 padding: var(--s-7) var(--s-4);
958d26aClaude2236 border-top: 1px solid var(--border-subtle);
2237 border-bottom: 1px solid var(--border-subtle);
c963db5Claude2238 }
2239 .landing-caps-grid {
2240 display: grid;
2241 grid-template-columns: repeat(4, 1fr);
2242 gap: 24px 16px;
958d26aClaude2243 text-align: center;
2244 }
c963db5Claude2245 .landing-cap {
958d26aClaude2246 font-family: var(--font-mono);
2247 font-size: 11px;
c963db5Claude2248 font-weight: 600;
958d26aClaude2249 text-transform: uppercase;
c963db5Claude2250 letter-spacing: 0.16em;
2251 color: var(--text-muted);
2252 transition: color var(--t-fast) var(--ease);
958d26aClaude2253 }
c963db5Claude2254 .landing-cap:hover { color: var(--text-strong); }
2255 @media (max-width: 800px) {
2256 .landing-caps-grid { grid-template-columns: repeat(2, 1fr); gap: 18px 12px; }
2257 }
2258 @media (max-width: 480px) {
2259 .landing-caps-grid { grid-template-columns: 1fr; }
2260 }
2261
2262 /* ---------- Big stat row (crontech-style hero closer) ---------- */
2263 .landing-bigstats {
2264 margin: var(--s-10) auto var(--s-20);
2265 max-width: 1180px;
2266 padding: 0 var(--s-4);
2267 }
2268 .landing-bigstats-grid {
2269 display: grid;
2270 grid-template-columns: repeat(4, 1fr);
2271 gap: 32px;
2272 text-align: left;
958d26aClaude2273 }
c963db5Claude2274 .landing-bigstat {
2275 padding: var(--s-2) 0;
2276 }
2277 .landing-bigstat-num {
958d26aClaude2278 font-family: var(--font-display);
c963db5Claude2279 font-size: clamp(28px, 3.5vw, 44px);
2280 line-height: 1.05;
2281 letter-spacing: -0.03em;
2282 font-weight: 700;
2283 color: var(--text-strong);
2284 margin-bottom: var(--s-2);
2285 }
2286 .landing-bigstat-label {
2287 font-family: var(--font-mono);
2288 font-size: 11px;
958d26aClaude2289 font-weight: 500;
c963db5Claude2290 text-transform: uppercase;
2291 letter-spacing: 0.14em;
2292 color: var(--text-faint);
2b821b7Claude2293 }
c963db5Claude2294 @media (max-width: 800px) {
2295 .landing-bigstats-grid { grid-template-columns: repeat(2, 1fr); gap: 28px 16px; }
2296 }
2297 @media (max-width: 480px) {
2298 .landing-bigstats-grid { grid-template-columns: 1fr; gap: 24px; }
958d26aClaude2299 }
2300
2301 /* ---------- Section base ---------- */
2302 .landing-section { margin: var(--s-20) auto; }
2b821b7Claude2303
4c47454Claude2304 /* ---------- Feature grid ---------- */
2305 .landing-features {
2b821b7Claude2306 display: grid;
2307 grid-template-columns: repeat(3, 1fr);
958d26aClaude2308 gap: 16px;
2b821b7Claude2309 }
2310 .landing-feature {
958d26aClaude2311 background: var(--bg-elevated);
2b821b7Claude2312 border: 1px solid var(--border);
958d26aClaude2313 border-radius: var(--r-lg);
2314 padding: var(--s-7);
2315 position: relative;
2316 overflow: hidden;
2317 isolation: isolate;
2318 transition:
2319 transform var(--t-base) var(--ease-out-quart),
2320 border-color var(--t-base) var(--ease),
2321 box-shadow var(--t-base) var(--ease);
2322 }
2323 .landing-feature::before {
2324 content: '';
2325 position: absolute;
2326 inset: 0;
2327 background: radial-gradient(120% 100% at 0% 0%, rgba(140,109,255,0.08), transparent 55%);
2328 opacity: 0;
2329 transition: opacity var(--t-base) var(--ease);
2330 z-index: -1;
4c47454Claude2331 }
2332 .landing-feature:hover {
958d26aClaude2333 transform: translateY(-3px);
2334 border-color: var(--border-strong);
2335 box-shadow: var(--elev-2);
2b821b7Claude2336 }
958d26aClaude2337 .landing-feature:hover::before { opacity: 1; }
2b821b7Claude2338 .landing-feature-icon {
4c47454Claude2339 display: inline-flex;
2340 align-items: center;
2341 justify-content: center;
958d26aClaude2342 width: 40px;
2343 height: 40px;
2344 border-radius: var(--r);
2345 background: var(--accent-gradient-soft);
2346 color: var(--accent);
2347 margin-bottom: var(--s-4);
2348 border: 1px solid rgba(140,109,255,0.20);
2b821b7Claude2349 }
2350 .landing-feature-title {
958d26aClaude2351 font-family: var(--font-display);
2352 font-size: 19px;
2353 font-weight: 600;
2354 letter-spacing: -0.018em;
2355 margin: 0 0 var(--s-2);
2356 color: var(--text-strong);
2b821b7Claude2357 }
2358 .landing-feature-desc {
958d26aClaude2359 font-size: var(--t-sm);
2360 color: var(--text-muted);
2361 line-height: 1.6;
2362 margin: 0;
2363 }
2364
2365 /* ---------- Walkthrough ---------- */
2366 .landing-walk-grid {
2367 display: grid;
2368 grid-template-columns: repeat(4, 1fr);
2369 gap: 16px;
2370 counter-reset: walk;
2371 }
2372 .landing-walk-step {
2373 position: relative;
2374 padding: var(--s-7) var(--s-6) var(--s-6);
2375 background: var(--bg-elevated);
2376 border: 1px solid var(--border);
2377 border-radius: var(--r-lg);
2378 }
2379 .landing-walk-step::after {
2380 content: '';
2381 position: absolute;
2382 top: 50%;
2383 right: -12px;
2384 width: 12px;
2385 height: 1px;
2386 background: var(--border-strong);
2387 }
2388 .landing-walk-step:last-child::after { display: none; }
2389 .landing-walk-num {
2390 display: inline-block;
2391 font-family: var(--font-mono);
2392 font-size: 11px;
2393 color: var(--accent);
2394 background: var(--accent-gradient-faint);
2395 border: 1px solid rgba(140,109,255,0.30);
2396 padding: 3px 8px;
2397 border-radius: var(--r-full);
2398 letter-spacing: 0.06em;
2399 margin-bottom: var(--s-3);
2400 }
2401 .landing-walk-title {
2402 font-family: var(--font-display);
2403 font-size: 22px;
2404 font-weight: 600;
2405 letter-spacing: -0.022em;
2406 margin: 0 0 var(--s-2);
2407 color: var(--text-strong);
2408 }
2409 .landing-walk-desc {
2410 font-size: var(--t-sm);
2b821b7Claude2411 color: var(--text-muted);
2412 line-height: 1.55;
2413 margin: 0;
2414 }
2415
958d26aClaude2416 /* ---------- Terminal ---------- */
2417 .landing-terminal-section { margin-top: var(--s-16); }
4c47454Claude2418 .landing-terminal-wrap {
2419 display: flex;
2b821b7Claude2420 justify-content: center;
2421 }
4c47454Claude2422 .landing-terminal {
2423 width: 100%;
958d26aClaude2424 max-width: 820px;
2425 background: linear-gradient(180deg, #0a0b12 0%, #06070c 100%);
2426 border: 1px solid var(--border-strong);
2427 border-radius: var(--r-lg);
2428 overflow: hidden;
2429 box-shadow: var(--elev-3), 0 0 60px -10px rgba(140,109,255,0.18);
4c47454Claude2430 text-align: left;
2b821b7Claude2431 }
958d26aClaude2432 :root[data-theme='light'] .landing-terminal {
2433 background: linear-gradient(180deg, #0f111a 0%, #06070c 100%);
2434 }
2435 .landing-terminal-chrome {
2436 display: flex;
2437 align-items: center;
2438 gap: 7px;
2439 padding: 11px 14px;
2440 background: rgba(255,255,255,0.025);
2441 border-bottom: 1px solid rgba(255,255,255,0.06);
2442 position: relative;
2443 }
2444 .landing-terminal-dot {
2445 width: 11px;
2446 height: 11px;
2447 border-radius: 50%;
2448 flex-shrink: 0;
2449 }
2450 .landing-terminal-dot-r { background: #ff5f57; }
2451 .landing-terminal-dot-y { background: #febc2e; }
2452 .landing-terminal-dot-g { background: #28c840; }
2453 .landing-terminal-title {
2454 position: absolute;
2455 left: 50%;
2456 transform: translateX(-50%);
2457 font-family: var(--font-mono);
2458 font-size: 11px;
2459 color: rgba(237,237,242,0.55);
2460 letter-spacing: 0.01em;
2461 }
2462 .landing-terminal-body {
2463 padding: var(--s-6) var(--s-7);
2464 font-family: var(--font-mono);
2465 font-feature-settings: var(--mono-feat);
2466 font-size: 13.5px;
2467 line-height: 1.85;
2468 color: rgba(237,237,242,0.92);
2469 }
4c47454Claude2470 .landing-term-line {
2471 display: flex;
2472 gap: 10px;
2473 white-space: pre-wrap;
2474 word-break: break-all;
2b821b7Claude2475 }
958d26aClaude2476 .landing-term-out { color: rgba(237,237,242,0.7); }
2477 .landing-term-prompt { color: rgba(140,109,255,0.85); user-select: none; flex-shrink: 0; }
2478 .landing-term-meta { color: rgba(237,237,242,0.45); }
2479 .landing-term-ok { color: var(--green); user-select: none; flex-shrink: 0; }
2480 .landing-term-ok-line { color: rgba(237,237,242,0.92); }
2481 .landing-term-cursor { margin-top: 4px; }
2482 .landing-term-blink {
2483 animation: blink 1.05s steps(2) infinite;
2484 color: var(--accent);
2485 }
2486 @keyframes blink { 50% { opacity: 0; } }
2487
2488 /* ---------- Comparison ---------- */
2489 .landing-compare {
2490 max-width: 920px;
2491 margin: 0 auto;
2492 border: 1px solid var(--border);
2493 border-radius: var(--r-lg);
2494 overflow: hidden;
2495 background: var(--bg-elevated);
2496 }
2497 .landing-compare-row {
2498 display: grid;
2499 grid-template-columns: 1fr 180px 180px;
2500 align-items: center;
2501 padding: 14px 20px;
2502 border-bottom: 1px solid var(--border-subtle);
2503 font-size: var(--t-sm);
2504 transition: background var(--t-fast) var(--ease);
2505 }
2506 .landing-compare-row:last-child { border-bottom: none; }
2507 .landing-compare-row:hover { background: var(--bg-hover); }
2508 .landing-compare-feature {
2509 color: var(--text-strong);
2510 font-weight: 500;
2511 }
2512 .landing-compare-them, .landing-compare-us {
2513 text-align: center;
2514 font-family: var(--font-mono);
2515 font-size: 12px;
2516 color: var(--text-muted);
2517 }
2518 .landing-compare-us { color: var(--green); font-weight: 500; }
2519 .landing-compare-hl .landing-compare-us {
2520 color: var(--accent);
2521 font-weight: 600;
2b821b7Claude2522 }
958d26aClaude2523 .landing-compare-hl .landing-compare-feature::after {
2524 content: 'NEW';
2525 margin-left: 8px;
2526 padding: 1px 6px;
2527 border-radius: 4px;
2528 background: var(--accent-gradient-faint);
2529 color: var(--accent);
2530 font-family: var(--font-mono);
2531 font-size: 9px;
2532 letter-spacing: 0.1em;
2533 font-weight: 600;
2534 vertical-align: 1px;
2535 }
2536 @media (max-width: 720px) {
2537 .landing-compare-row { grid-template-columns: 1fr 80px 80px; padding: 12px 14px; }
2538 .landing-compare-hl .landing-compare-feature::after { display: none; }
2539 }
2540
2541 /* ---------- Pricing ---------- */
2542 .landing-pricing {
2543 display: grid;
2544 grid-template-columns: repeat(3, 1fr);
2545 gap: 16px;
2546 max-width: 1080px;
2547 margin: 0 auto;
2548 align-items: stretch;
2549 }
2550 .landing-price-card {
2551 position: relative;
2552 background: var(--bg-elevated);
2553 border: 1px solid var(--border);
2554 border-radius: var(--r-lg);
2555 padding: var(--s-7);
2556 display: flex;
2557 flex-direction: column;
2558 gap: var(--s-4);
2559 transition: border-color var(--t-base) var(--ease), transform var(--t-base) var(--ease);
2560 }
2561 .landing-price-card:hover {
2562 border-color: var(--border-strong);
2563 transform: translateY(-2px);
2564 }
2565 .landing-price-hl {
2566 border-color: rgba(140,109,255,0.35);
2567 box-shadow: var(--elev-2), 0 0 0 1px rgba(140,109,255,0.25);
2568 background:
2569 linear-gradient(180deg, rgba(140,109,255,0.05), transparent 50%),
2570 var(--bg-elevated);
2571 }
2572 .landing-price-hl:hover { border-color: rgba(140,109,255,0.55); }
2573 .landing-price-badge {
2574 position: absolute;
2575 top: -10px;
2576 left: 50%;
2577 transform: translateX(-50%);
2578 padding: 3px 12px;
2579 background: var(--accent-gradient);
2580 color: #fff;
2581 font-family: var(--font-mono);
2582 font-size: 10px;
2583 letter-spacing: 0.1em;
2584 text-transform: uppercase;
2585 font-weight: 600;
2586 border-radius: var(--r-full);
2587 box-shadow: 0 4px 12px -2px rgba(140,109,255,0.4);
2588 }
2589 .landing-price-tier {
2590 font-family: var(--font-mono);
2591 font-size: 11px;
2592 text-transform: uppercase;
2593 letter-spacing: 0.16em;
2594 color: var(--text-muted);
2595 }
2596 .landing-price-amount {
2597 display: flex;
2598 align-items: baseline;
2599 gap: 8px;
2600 }
2601 .landing-price-num {
2602 font-family: var(--font-display);
2603 font-size: 40px;
2604 font-weight: 600;
2605 letter-spacing: -0.03em;
2606 color: var(--text-strong);
2607 }
2608 .landing-price-cad {
2609 font-size: var(--t-sm);
2610 color: var(--text-faint);
2611 }
2612 .landing-price-desc {
2613 font-size: var(--t-sm);
2614 color: var(--text-muted);
2615 line-height: 1.55;
2616 margin: 0;
2617 }
2618 .landing-price-features {
2619 list-style: none;
2620 padding: 0;
2621 margin: 0;
2622 display: flex;
2623 flex-direction: column;
2624 gap: 8px;
2625 font-size: var(--t-sm);
2626 color: var(--text);
2627 }
2628 .landing-price-features li {
2629 display: flex;
2630 align-items: center;
2631 gap: 9px;
2632 }
2633 .landing-price-check {
2634 color: var(--accent);
2635 font-weight: 600;
4c47454Claude2636 flex-shrink: 0;
2b821b7Claude2637 }
958d26aClaude2638 .landing-price-cta { margin-top: auto; }
2639
2640 /* ---------- Closing CTA ---------- */
2641 .landing-cta-section { margin: var(--s-20) auto var(--s-16); }
2642 .landing-cta-card {
2643 position: relative;
2644 text-align: center;
2645 padding: var(--s-16) var(--s-7);
2646 border: 1px solid var(--border-strong);
2647 border-radius: var(--r-2xl);
2648 background: var(--bg-elevated);
2649 overflow: hidden;
2650 isolation: isolate;
2651 }
2652 .landing-cta-bg {
2653 position: absolute;
2654 inset: 0;
2655 z-index: -1;
2656 background:
2657 radial-gradient(60% 100% at 50% 0%, rgba(140,109,255,0.16), transparent 65%),
2658 radial-gradient(40% 80% at 80% 100%, rgba(54,197,214,0.10), transparent 65%);
2659 }
2660 .landing-cta-card::after {
2661 content: '';
2662 position: absolute;
2663 inset: 0;
2664 z-index: -1;
2665 background-image: radial-gradient(rgba(255,255,255,0.04) 1px, transparent 1px);
2666 background-size: 24px 24px;
2667 mask-image: radial-gradient(ellipse at center, #000 0%, transparent 65%);
2668 -webkit-mask-image: radial-gradient(ellipse at center, #000 0%, transparent 65%);
2669 opacity: 0.6;
2670 }
2671 :root[data-theme='light'] .landing-cta-card::after {
2672 background-image: radial-gradient(rgba(15,16,28,0.07) 1px, transparent 1px);
2673 }
2674 .landing-cta-card .eyebrow { justify-content: center; }
2675 .landing-cta-title {
2676 font-family: var(--font-display);
2677 font-size: clamp(28px, 4.4vw, 56px);
2678 line-height: 1.05;
2679 letter-spacing: -0.03em;
2680 font-weight: 600;
2681 margin: var(--s-3) 0 var(--s-4);
2682 color: var(--text-strong);
2683 }
2684 .landing-cta-sub {
2685 font-size: var(--t-md);
2686 color: var(--text-muted);
2687 max-width: 560px;
2688 margin: 0 auto var(--s-8);
2689 line-height: 1.55;
2690 }
2691 .landing-cta-buttons {
2692 display: flex;
2693 gap: 12px;
2694 justify-content: center;
2695 flex-wrap: wrap;
2696 }
2b821b7Claude2697
2698 /* ---------- Responsive ---------- */
958d26aClaude2699 @media (max-width: 960px) {
2700 .landing-features { grid-template-columns: repeat(2, 1fr); }
2701 .landing-walk-grid { grid-template-columns: repeat(2, 1fr); }
2702 .landing-walk-step::after { display: none; }
2703 .landing-pricing { grid-template-columns: 1fr; max-width: 480px; }
2704 }
2705 @media (max-width: 640px) {
2706 .landing-hero { padding: var(--s-14) 0 var(--s-10); }
2707 .landing-hero-cmd { flex-wrap: wrap; justify-content: center; }
2708 .landing-hero-ctas { flex-direction: column; align-items: stretch; }
2709 .landing-hero-ctas .btn { width: 100%; justify-content: center; }
2710 .landing-features { grid-template-columns: 1fr; }
2711 .landing-walk-grid { grid-template-columns: 1fr; }
2712 .landing-section { margin: var(--s-12) auto; }
2713 .landing-cta-card { padding: var(--s-10) var(--s-5); }
2714 .landing-cta-buttons .btn { width: 100%; justify-content: center; }
2b821b7Claude2715 }
52ad8b1Claude2716
2717 /* ---------- L4 social-proof counters ---------- */
2718 .landing-counters {
2719 margin: var(--s-10) auto var(--s-12);
2720 max-width: 1180px;
2721 padding: 0 var(--s-4);
2722 }
2723 .landing-counters-grid {
2724 display: grid;
2725 grid-template-columns: repeat(6, 1fr);
2726 gap: 20px;
2727 text-align: left;
2728 }
2729 .landing-counter {
2730 padding: var(--s-3) 0;
2731 border-top: 1px solid var(--border-subtle);
2732 }
2733 .landing-counter-num {
2734 font-family: var(--font-display);
2735 font-size: clamp(24px, 3vw, 38px);
2736 line-height: 1.05;
2737 letter-spacing: -0.03em;
2738 font-weight: 700;
2739 margin-bottom: 6px;
2740 font-feature-settings: 'tnum';
2741 background-image: var(--accent-gradient);
2742 -webkit-background-clip: text;
2743 background-clip: text;
2744 -webkit-text-fill-color: transparent;
2745 color: transparent;
2746 }
2747 .landing-counter-label {
2748 font-family: var(--font-mono);
2749 font-size: 10.5px;
2750 font-weight: 500;
2751 text-transform: uppercase;
2752 letter-spacing: 0.12em;
2753 color: var(--text-faint);
2754 line-height: 1.4;
2755 }
2756 @media (max-width: 960px) {
2757 .landing-counters-grid { grid-template-columns: repeat(3, 1fr); gap: 20px 16px; }
2758 }
2759 @media (max-width: 540px) {
2760 .landing-counters-grid { grid-template-columns: repeat(2, 1fr); gap: 18px 12px; }
2761 }
5f2e749Claude2762
dc26881CC LABS App2763 /* ---------- L10/U1 hero install snippet ----------
2764 U1: wrapped in a labelled "power users" panel and re-located
2765 beneath the CTA + tertiary rows so it no longer competes with
2766 the primary calls to action. */
2767 .landing-hero-install-wrap {
2768 margin: 0 auto var(--space-6);
2769 text-align: center;
2770 }
2771 .landing-hero-install-label {
2772 display: inline-block;
2773 margin-bottom: var(--space-2);
2774 font-family: var(--font-mono);
2775 font-size: 11px;
2776 letter-spacing: 0.08em;
2777 text-transform: uppercase;
2778 color: var(--text-faint);
2779 }
5f2e749Claude2780 .landing-hero-install {
2781 display: inline-flex;
2782 align-items: stretch;
2783 gap: 0;
dc26881CC LABS App2784 margin: 0 auto;
5f2e749Claude2785 background: var(--bg-elevated);
2786 border: 1px solid var(--border-strong);
2787 border-radius: var(--r);
2788 box-shadow: var(--elev-1);
2789 overflow: hidden;
2790 max-width: 100%;
2791 font-family: var(--font-mono);
2792 }
2793 .landing-hero-install-code {
2794 display: inline-flex;
2795 align-items: center;
2796 gap: 10px;
2797 padding: 10px 14px;
2798 font-size: 13.5px;
2799 color: var(--text-strong);
2800 background: transparent;
2801 border: 0;
2802 white-space: nowrap;
2803 overflow-x: auto;
2804 }
2805 .landing-hero-install-prompt {
2806 color: var(--accent);
2807 user-select: none;
2808 }
2809 .landing-hero-install-copy {
2810 appearance: none;
2811 border: 0;
2812 border-left: 1px solid var(--border);
2813 background: transparent;
2814 color: var(--text-muted);
2815 font-family: var(--font-mono);
2816 font-size: 12px;
2817 font-weight: 600;
2818 letter-spacing: 0.06em;
2819 text-transform: uppercase;
2820 padding: 0 16px;
2821 cursor: pointer;
2822 transition: background var(--t-fast) var(--ease), color var(--t-fast) var(--ease);
2823 }
2824 .landing-hero-install-copy:hover {
2825 background: var(--accent-gradient-faint);
2826 color: var(--accent);
2827 }
2828 .landing-hero-install-copy[data-copied="1"] {
2829 color: var(--green, #34d399);
2830 }
2831
dc26881CC LABS App2832 /* ---------- L10/U1 hero "what just happened" rail ----------
2833 U1 — tightened into a single horizontal strip. The 1px gradient
2834 rule on top is the same accent the headline uses, so the rail
2835 reads as part of the hero composition rather than a stray list. */
5f2e749Claude2836 .landing-hero-rail {
2837 list-style: none;
dc26881CC LABS App2838 padding: var(--space-4) 0 0;
2839 margin: 0 auto var(--space-6);
5f2e749Claude2840 display: flex;
2841 flex-wrap: wrap;
2842 justify-content: center;
dc26881CC LABS App2843 gap: var(--space-2) var(--space-6);
5f2e749Claude2844 font-family: var(--font-sans);
dc26881CC LABS App2845 font-size: 12px;
2846 color: var(--text-faint);
5f2e749Claude2847 max-width: 760px;
dc26881CC LABS App2848 position: relative;
2849 }
2850 .landing-hero-rail::before {
2851 content: '';
2852 position: absolute;
2853 top: 0;
2854 left: 50%;
2855 transform: translateX(-50%);
2856 width: 120px;
2857 height: 1px;
2858 background: var(--accent-gradient);
2859 opacity: 0.45;
2860 border-radius: 9999px;
5f2e749Claude2861 }
2862 .landing-hero-rail li {
2863 display: inline-flex;
dc26881CC LABS App2864 align-items: baseline;
2865 gap: var(--space-2);
5f2e749Claude2866 line-height: 1.4;
2867 }
2868 .landing-hero-rail strong {
2869 color: var(--text-strong);
2870 font-weight: 600;
2871 font-feature-settings: 'tnum';
dc26881CC LABS App2872 font-size: 14px;
2873 letter-spacing: -0.01em;
5f2e749Claude2874 }
dc26881CC LABS App2875 .landing-hero-rail-label {
2876 color: var(--text-muted);
2877 letter-spacing: 0.01em;
5f2e749Claude2878 }
dc26881CC LABS App2879 /* Backwards-compat: nothing references this any more but if a stale
2880 fragment lingers it's still hidden cleanly rather than orphaned. */
2881 .landing-hero-rail-check { display: none; }
5f2e749Claude2882
2883 /* ---------- L10 three-reasons section ---------- */
2884 .landing-reasons { margin-top: var(--s-12); }
2885 .landing-reasons-grid {
2886 display: grid;
2887 grid-template-columns: repeat(3, 1fr);
2888 gap: 16px;
2889 }
2890 .landing-reason {
2891 background: var(--bg-elevated);
2892 border: 1px solid var(--border);
2893 border-radius: var(--r-lg);
2894 padding: var(--s-7);
2895 display: flex;
2896 flex-direction: column;
2897 gap: var(--s-3);
2898 transition: border-color var(--t-base) var(--ease), transform var(--t-base) var(--ease);
2899 }
2900 .landing-reason:hover {
2901 border-color: var(--border-strong);
2902 transform: translateY(-2px);
2903 }
2904 .landing-reason-icon {
2905 display: inline-flex;
2906 align-items: center;
2907 justify-content: center;
2908 width: 40px;
2909 height: 40px;
2910 border-radius: var(--r);
2911 background: var(--accent-gradient-soft);
2912 color: var(--accent);
2913 border: 1px solid rgba(140,109,255,0.20);
2914 }
2915 .landing-reason-title {
2916 font-family: var(--font-display);
2917 font-size: 20px;
2918 font-weight: 600;
2919 letter-spacing: -0.02em;
2920 margin: 0;
2921 color: var(--text-strong);
2922 }
2923 .landing-reason-body {
2924 font-size: var(--t-sm);
2925 color: var(--text-muted);
2926 line-height: 1.55;
2927 margin: 0;
2928 }
2929 .landing-reasons-code {
2930 display: block;
2931 font-family: var(--font-mono);
2932 font-size: 12px;
2933 color: var(--text-strong);
2934 background: var(--bg);
2935 border: 1px solid var(--border);
2936 border-radius: var(--r);
2937 padding: 8px 12px;
2938 overflow-x: auto;
2939 white-space: nowrap;
2940 }
2941 .landing-reason-link {
2942 margin-top: auto;
2943 display: inline-flex;
2944 align-items: center;
2945 gap: 6px;
2946 color: var(--accent);
2947 font-size: var(--t-sm);
2948 font-weight: 500;
2949 text-decoration: none;
2950 }
2951 .landing-reason-link:hover { text-decoration: underline; }
2952 @media (max-width: 960px) {
2953 .landing-reasons-grid { grid-template-columns: 1fr; max-width: 520px; margin: 0 auto; }
2954 }
2955
2956 /* ---------- L10 "How is this different" pull-quote ---------- */
2957 .landing-pullquote-section {
2958 margin: var(--s-20) auto var(--s-12);
2959 max-width: 920px;
2960 padding: 0 var(--s-4);
2961 text-align: center;
2962 }
2963 .landing-pullquote {
2964 margin: 0;
2965 padding: var(--s-10) var(--s-7);
2966 background:
2967 radial-gradient(80% 100% at 50% 0%, rgba(140,109,255,0.10), transparent 65%),
2968 var(--bg-elevated);
2969 border: 1px solid var(--border-strong);
2970 border-radius: var(--r-xl);
2971 position: relative;
2972 overflow: hidden;
2973 }
2974 .landing-pullquote-eyebrow {
2975 font-family: var(--font-mono);
2976 font-size: 11px;
2977 font-weight: 600;
2978 letter-spacing: 0.16em;
2979 text-transform: uppercase;
2980 color: var(--accent);
2981 margin-bottom: var(--s-4);
2982 }
2983 .landing-pullquote-text {
2984 font-family: var(--font-display);
2985 font-size: clamp(20px, 2.4vw, 28px);
2986 line-height: 1.4;
2987 letter-spacing: -0.018em;
2988 color: var(--text-strong);
2989 margin: 0 auto;
2990 max-width: 760px;
2991 quotes: "\\201C" "\\201D";
2992 }
2993 .landing-pullquote-text::before { content: open-quote; color: var(--accent); margin-right: 4px; }
2994 .landing-pullquote-text::after { content: close-quote; color: var(--accent); margin-left: 4px; }
2995 .landing-pullquote-link {
2996 display: inline-flex;
2997 align-items: center;
2998 gap: 6px;
2999 margin-top: var(--s-6);
3000 color: var(--accent);
3001 font-size: var(--t-sm);
3002 font-weight: 500;
3003 text-decoration: none;
3004 }
3005 .landing-pullquote-link:hover { text-decoration: underline; }
3006
3007 /* ---------- L10 hero responsive overrides ---------- */
3008 @media (max-width: 640px) {
3009 .landing-hero-install { width: 100%; }
3010 .landing-hero-install-code { flex: 1; font-size: 12px; }
3011 .landing-hero-rail { flex-direction: column; align-items: flex-start; gap: 6px; padding: 0 var(--s-3); }
3012 .landing-hero-rail li { width: 100%; }
3013 }
534f04aClaude3014
3015 /* ============================================================ */
3016 /* Block M1 — Live-now demo feed */
3017 /* ============================================================ */
3018 .landing-livenow {
3019 margin: var(--s-8) 0 var(--s-6);
3020 padding: var(--s-6) 0 var(--s-4);
3021 }
3022 .landing-livenow-head {
3023 text-align: center;
3024 margin-bottom: var(--s-6);
3025 }
3026 .landing-livenow-eyebrow {
3027 display: inline-flex;
3028 align-items: center;
3029 gap: 8px;
3030 padding: 4px 12px;
3031 border-radius: var(--r-full);
3032 background: rgba(52,211,153,0.08);
3033 border: 1px solid rgba(52,211,153,0.25);
3034 color: var(--green);
3035 font-family: var(--font-mono, ui-monospace, monospace);
3036 font-size: 11px;
3037 letter-spacing: 0.06em;
3038 text-transform: uppercase;
3039 margin-bottom: var(--s-3);
3040 }
3041 .landing-livenow-pulse {
3042 width: 8px; height: 8px;
3043 border-radius: 50%;
3044 background: var(--green);
3045 box-shadow: 0 0 0 0 rgba(52,211,153,0.6);
3046 animation: landing-livenow-pulse 1.6s ease-out infinite;
3047 }
3048 @keyframes landing-livenow-pulse {
3049 0% { box-shadow: 0 0 0 0 rgba(52,211,153,0.55); transform: scale(1); }
3050 70% { box-shadow: 0 0 0 10px rgba(52,211,153,0); transform: scale(1.05); }
3051 100% { box-shadow: 0 0 0 0 rgba(52,211,153,0); transform: scale(1); }
3052 }
3053 .landing-livenow-title {
3054 font-size: 22px;
3055 line-height: 1.25;
3056 margin: 0 auto;
3057 max-width: 720px;
3058 color: var(--text-strong);
3059 font-weight: 600;
3060 letter-spacing: -0.01em;
3061 }
3062 .landing-livenow-sub {
3063 margin: var(--s-2) auto 0;
3064 color: var(--text-muted);
3065 font-size: 13px;
3066 max-width: 560px;
3067 }
3068 .landing-livenow-sub code {
3069 background: rgba(255,255,255,0.05);
3070 border: 1px solid var(--border);
3071 padding: 1px 6px;
3072 border-radius: 4px;
3073 font-size: 12px;
3074 color: var(--accent);
3075 }
3076
3077 .landing-livenow-grid {
3078 display: grid;
3079 grid-template-columns: repeat(2, minmax(0, 1fr));
3080 gap: var(--s-3);
3081 }
3082 @media (min-width: 980px) {
3083 .landing-livenow-grid {
3084 grid-template-columns: repeat(4, minmax(0, 1fr));
3085 }
3086 }
3087
3088 .landing-livecard {
3089 background: linear-gradient(180deg, rgba(255,255,255,0.025), rgba(255,255,255,0.005));
3090 border: 1px solid var(--border);
3091 border-radius: var(--r-md, 10px);
3092 padding: 14px 14px 12px;
3093 display: flex;
3094 flex-direction: column;
3095 min-height: 180px;
3096 position: relative;
3097 overflow: hidden;
3098 }
3099 .landing-livecard::before {
3100 content: '';
3101 position: absolute;
3102 inset: 0;
3103 background: var(--accent-gradient-faint);
3104 opacity: 0;
3105 transition: opacity 200ms var(--ease, ease);
3106 pointer-events: none;
3107 }
3108 .landing-livecard:hover::before { opacity: 1; }
3109
3110 .landing-livecard-head {
3111 display: flex;
3112 align-items: center;
3113 gap: 8px;
3114 margin-bottom: 10px;
3115 }
3116 .landing-livecard-dot {
3117 width: 7px; height: 7px;
3118 border-radius: 50%;
3119 background: var(--green);
3120 box-shadow: 0 0 8px rgba(52,211,153,0.55);
3121 animation: landing-livenow-pulse 1.8s ease-out infinite;
3122 flex-shrink: 0;
3123 }
3124 .landing-livecard-title {
3125 margin: 0;
3126 font-size: 12px;
3127 font-weight: 600;
3128 text-transform: uppercase;
3129 letter-spacing: 0.06em;
3130 color: var(--text-muted);
3131 }
3132
3133 .landing-livecard-bignum {
3134 display: flex;
3135 align-items: baseline;
3136 gap: 8px;
3137 margin: 4px 0 10px;
3138 }
3139 .landing-livecard-bignum-n {
3140 font-size: 30px;
3141 font-weight: 700;
3142 color: var(--text-strong);
3143 font-variant-numeric: tabular-nums;
3144 background: var(--accent-gradient);
3145 -webkit-background-clip: text;
3146 background-clip: text;
3147 -webkit-text-fill-color: transparent;
3148 color: transparent;
3149 }
3150 .landing-livecard-bignum-label {
3151 font-size: 12px;
3152 color: var(--text-muted);
3153 }
3154
3155 .landing-livecard-list {
3156 list-style: none;
3157 margin: 0;
3158 padding: 0;
3159 font-size: 13px;
3160 line-height: 1.45;
3161 flex: 1;
3162 }
3163 .landing-livecard-row, .landing-livecard-feedrow {
3164 padding: 6px 0;
3165 border-bottom: 1px dashed rgba(255,255,255,0.05);
3166 transition: background-color 1s var(--ease, ease);
3167 border-radius: 4px;
3168 margin: 0 -4px;
3169 padding-left: 4px;
3170 padding-right: 4px;
3171 }
3172 .landing-livecard-row:last-child,
3173 .landing-livecard-feedrow:last-child { border-bottom: 0; }
3174 .landing-livecard-feedrow { padding: 4px; font-size: 12.5px; }
3175
3176 .landing-livecard-flash {
3177 background-color: rgba(52,211,153,0.18) !important;
3178 animation: landing-livecard-flash-fade 1.1s ease-out forwards;
3179 }
3180 @keyframes landing-livecard-flash-fade {
3181 0% { background-color: rgba(52,211,153,0.22); }
3182 100% { background-color: rgba(52,211,153,0); }
3183 }
3184
3185 .landing-livecard-link {
3186 color: var(--text-strong);
3187 text-decoration: none;
3188 display: inline-block;
3189 max-width: 100%;
3190 overflow: hidden;
3191 text-overflow: ellipsis;
3192 white-space: nowrap;
3193 }
3194 .landing-livecard-link:hover { color: var(--accent); }
3195 .landing-livecard-num {
3196 font-family: var(--font-mono, ui-monospace, monospace);
3197 color: var(--text-faint);
3198 font-weight: 500;
3199 font-size: 12px;
3200 }
3201 .landing-livecard-title-text { color: var(--text-strong); }
3202 .landing-livecard-snippet {
3203 color: var(--text-muted);
3204 font-style: italic;
3205 font-size: 12px;
3206 }
3207 .landing-livecard-meta {
3208 font-size: 11px;
3209 color: var(--text-faint);
3210 margin-top: 2px;
3211 font-family: var(--font-mono, ui-monospace, monospace);
3212 }
3213 .landing-livecard-repo {
3214 color: var(--accent-2);
3215 }
3216 .landing-livecard-rel {
3217 color: var(--text-muted);
3218 }
3219 .landing-livecard-empty {
3220 color: var(--text-faint);
3221 font-size: 12px;
3222 font-style: italic;
3223 padding: 8px 0;
3224 }
3225 .landing-livecard-kind {
3226 display: inline-block;
3227 padding: 1px 7px;
3228 border-radius: var(--r-full);
3229 font-family: var(--font-mono, ui-monospace, monospace);
3230 font-size: 10px;
3231 letter-spacing: 0.04em;
3232 text-transform: uppercase;
3233 font-weight: 600;
3234 }
3235 .landing-livecard-kind-auto_merge-merged,
3236 .landing-livecard-kind-auto-merge-merged {
3237 background: rgba(52,211,153,0.12);
3238 color: var(--green);
3239 border: 1px solid rgba(52,211,153,0.25);
3240 }
3241 .landing-livecard-kind-ai_build-dispatched,
3242 .landing-livecard-kind-ai-build-dispatched {
3243 background: rgba(140,109,255,0.12);
3244 color: var(--accent);
3245 border: 1px solid rgba(140,109,255,0.30);
3246 }
3247 .landing-livecard-kind-ai_review-posted,
3248 .landing-livecard-kind-ai-review-posted {
3249 background: rgba(54,197,214,0.12);
3250 color: var(--accent-2);
3251 border: 1px solid rgba(54,197,214,0.30);
3252 }
3253
3254 .landing-livenow-cta {
3255 margin-top: var(--s-5);
3256 display: flex;
3257 flex-wrap: wrap;
3258 align-items: center;
3259 justify-content: center;
3260 gap: 10px;
3261 font-size: 13px;
3262 color: var(--text-muted);
3263 }
3264 .landing-livenow-cta-link {
3265 color: var(--accent);
3266 text-decoration: none;
3267 font-weight: 500;
3268 display: inline-flex;
3269 align-items: center;
3270 gap: 4px;
3271 }
3272 .landing-livenow-cta-link:hover { color: var(--accent-hover); }
3273 .landing-livenow-cta-sep { color: var(--text-faint); }
3274
3275 @media (prefers-reduced-motion: reduce) {
3276 .landing-livenow-pulse,
3277 .landing-livecard-dot { animation: none; }
3278 .landing-livecard-flash { animation: none; background-color: transparent !important; }
3279 }
52ad8b1Claude3280`;
3281
3282/**
3283 * Block L4 — count-up animation.
3284 *
3285 * Reads each `[data-counter-target]` and animates the in-DOM text from
3286 * 0 → target over ~1.2s when the element first scrolls into view.
3287 *
3288 * Render-once semantics: each tile already contains the final value as
3289 * HTML, so visitors with JS disabled — or anyone before the script
3290 * loads — sees the correct number. The script just animates the text.
3291 *
3292 * Falls back to the static value (no animation) when IntersectionObserver
3293 * isn't available, or when the user prefers reduced motion.
3294 */
3295const landingCountersJs = `
3296(function(){
3297 try {
3298 var els = document.querySelectorAll('[data-counter-target]');
3299 if (!els.length) return;
3300 var reduced = window.matchMedia && window.matchMedia('(prefers-reduced-motion: reduce)').matches;
3301 if (reduced || typeof IntersectionObserver !== 'function') return;
3302
3303 function animate(el) {
3304 var target = parseInt(el.getAttribute('data-counter-target') || '0', 10);
3305 if (!isFinite(target) || target <= 0) return;
3306 var prefix = el.getAttribute('data-counter-prefix') || '';
3307 var suffix = el.getAttribute('data-counter-suffix') || '';
3308 var duration = 1200;
3309 var start = performance.now();
3310 function frame(now) {
3311 var t = Math.min(1, (now - start) / duration);
3312 // ease-out cubic
3313 var eased = 1 - Math.pow(1 - t, 3);
3314 var v = Math.floor(eased * target);
3315 el.textContent = prefix + v.toLocaleString() + suffix;
3316 if (t < 1) requestAnimationFrame(frame);
3317 else el.textContent = prefix + target.toLocaleString() + suffix;
3318 }
3319 // Reset to zero before animating in.
3320 el.textContent = prefix + '0' + suffix;
3321 requestAnimationFrame(frame);
3322 }
3323
3324 var io = new IntersectionObserver(function(entries) {
3325 entries.forEach(function(entry){
3326 if (entry.isIntersecting) {
3327 animate(entry.target);
3328 io.unobserve(entry.target);
3329 }
3330 });
3331 }, { threshold: 0.4 });
3332 els.forEach(function(el){ io.observe(el); });
3333 } catch (_) { /* swallow — static numbers remain */ }
3334})();
2b821b7Claude3335`;
5f2e749Claude3336
3337/**
3338 * Block L10 — clipboard copy for the hero install snippet.
3339 *
3340 * Pure progressive enhancement. Without JS the user can still
3341 * triple-click + Cmd/Ctrl-C the snippet — the button is the
3342 * speed-bump, not the only path.
3343 */
3344const landingCopyJs = `
3345(function(){
3346 try {
3347 var btns = document.querySelectorAll('[data-copy-target]');
3348 if (!btns.length) return;
3349 btns.forEach(function(btn){
3350 btn.addEventListener('click', function(){
3351 var id = btn.getAttribute('data-copy-target') || '';
3352 var src = document.getElementById(id);
3353 if (!src) return;
3354 var text = src.textContent || '';
3355 var done = function(){
3356 var prev = btn.textContent;
3357 btn.textContent = 'Copied';
3358 btn.setAttribute('data-copied', '1');
3359 setTimeout(function(){
3360 btn.textContent = prev || 'Copy';
3361 btn.removeAttribute('data-copied');
3362 }, 1500);
3363 };
3364 if (navigator.clipboard && navigator.clipboard.writeText) {
3365 navigator.clipboard.writeText(text).then(done, function(){ done(); });
3366 } else {
3367 // Legacy fallback — temp textarea + execCommand.
3368 try {
3369 var ta = document.createElement('textarea');
3370 ta.value = text;
3371 ta.style.position = 'fixed';
3372 ta.style.opacity = '0';
3373 document.body.appendChild(ta);
3374 ta.select();
3375 document.execCommand('copy');
3376 document.body.removeChild(ta);
3377 done();
3378 } catch (_) {}
3379 }
3380 });
3381 });
3382 } catch (_) { /* swallow */ }
3383})();
3384`;
0c3eee5Claude3385
3386// ============================================================
3387// land2030Css — scoped CSS for the 2030 homepage prelude.
3388// Every selector is prefixed `.land-2030-*` so it can't bleed
3389// into the older `.landing-*` (L10/U1/Q1/M1) styles below.
3390// ============================================================
3391const land2030Css = `
3392 .land-2030-root {
3393 position: relative;
3394 max-width: 1240px;
3395 margin: 0 auto;
3396 padding: 0 16px var(--space-6, 32px);
3397 color: var(--text, #e6e6f0);
3398 }
3399
3400 /* ---------- Hero ---------- */
3401 .land-2030-hero {
3402 position: relative;
3403 padding: clamp(48px, 8vw, 120px) 8px clamp(40px, 6vw, 80px);
3404 text-align: center;
3405 overflow: hidden;
3406 }
3407 .land-2030-hairline {
3408 position: absolute;
3409 top: 0; left: 0; right: 0;
3410 height: 2px;
3411 background: linear-gradient(90deg, transparent 0%, #8c6dff 30%, #36c5d6 70%, transparent 100%);
3412 opacity: 0.75;
3413 pointer-events: none;
3414 }
3415 .land-2030-orb {
3416 position: absolute;
3417 top: -10%;
3418 left: 50%;
3419 width: 720px;
3420 height: 720px;
3421 transform: translateX(-50%);
3422 background: radial-gradient(circle, rgba(140,109,255,0.30), rgba(54,197,214,0.16) 40%, transparent 70%);
3423 filter: blur(90px);
3424 pointer-events: none;
3425 z-index: 0;
3426 }
3427 .land-2030-hero-inner {
3428 position: relative;
3429 z-index: 1;
3430 max-width: 1080px;
3431 margin: 0 auto;
3432 }
3433 .land-2030-eyebrow {
3434 display: inline-flex;
3435 align-items: center;
3436 gap: 8px;
3437 padding: 6px 12px;
3438 border-radius: 999px;
3439 background: rgba(140,109,255,0.10);
3440 color: #cbb7ff;
3441 font-size: 12px;
3442 letter-spacing: 0.04em;
3443 text-transform: uppercase;
3444 border: 1px solid rgba(140,109,255,0.30);
3445 margin-bottom: 24px;
3446 }
3447 .land-2030-eyebrow-mini {
3448 margin-bottom: 12px;
3449 }
3450 .land-2030-pulse {
3451 width: 8px; height: 8px;
3452 border-radius: 50%;
3453 background: #8c6dff;
3454 box-shadow: 0 0 12px rgba(140,109,255,0.8);
3455 animation: land2030-pulse 2s ease-in-out infinite;
3456 }
3457 @keyframes land2030-pulse {
3458 0%, 100% { opacity: 0.5; transform: scale(1); }
3459 50% { opacity: 1; transform: scale(1.25); }
3460 }
3461 .land-2030-display {
3462 font-size: clamp(60px, 9vw, 96px);
3463 font-family: var(--font-display, inherit);
3464 font-weight: 800;
3465 line-height: 1.02;
3466 letter-spacing: -0.035em;
3467 margin: 0 auto 24px;
3468 max-width: 1040px;
3469 }
3470 .land-2030-display span { background-clip: text; -webkit-background-clip: text; -webkit-text-fill-color: transparent; color: transparent; }
3471 .land-2030-grad-1 { background-image: linear-gradient(135deg, #a48bff 0%, #8c6dff 100%); }
3472 .land-2030-grad-2 { background-image: linear-gradient(135deg, #c4b1ff 0%, #36c5d6 100%); }
3473 .land-2030-grad-3 { background-image: linear-gradient(135deg, #36c5d6 0%, #5be0a9 100%); }
3474 .land-2030-grad-4 { background-image: linear-gradient(135deg, #5be0a9 0%, #ffd16b 100%); }
3475 .land-2030-grad-5 { background-image: linear-gradient(135deg, #ffd16b 0%, #ff6bd1 50%, #8c6dff 100%); }
3476 .land-2030-sub {
3477 font-size: clamp(17px, 1.6vw, 22px);
3478 color: var(--text-muted, #a0a0b8);
3479 max-width: 720px;
3480 margin: 0 auto 36px;
3481 line-height: 1.45;
3482 }
3483 .land-2030-cta-row {
3484 display: flex;
3485 flex-wrap: wrap;
3486 justify-content: center;
3487 gap: 12px;
3488 }
3489 .land-2030-cta-primary,
3490 .land-2030-cta-secondary {
3491 min-width: 200px;
3492 }
3493
3494 /* ---------- Section scaffold ---------- */
3495 .land-2030-section {
3496 margin: clamp(56px, 8vw, 96px) 0;
3497 position: relative;
3498 }
3499 .land-2030-section-dark {
3500 padding: 48px 32px;
3501 background: linear-gradient(180deg, rgba(15,17,26,0.6), rgba(8,9,15,0.6));
3502 border: 1px solid rgba(140,109,255,0.18);
3503 border-radius: 20px;
3504 }
3505 .land-2030-section-head {
3506 text-align: center;
3507 max-width: 760px;
3508 margin: 0 auto 40px;
3509 }
3510 .land-2030-h2 {
3511 font-size: clamp(28px, 4vw, 44px);
3512 font-family: var(--font-display, inherit);
3513 font-weight: 700;
3514 letter-spacing: -0.025em;
3515 line-height: 1.1;
3516 margin: 0 0 12px;
3517 }
3518 .land-2030-lede {
3519 font-size: 16px;
3520 color: var(--text-muted, #a0a0b8);
3521 line-height: 1.55;
3522 margin: 0;
3523 }
3524
3525 /* ---------- Closed loop diagram ---------- */
3526 .land-2030-loop-wrap {
3527 max-width: 1100px;
3528 margin: 0 auto;
3529 padding: 16px;
3530 background: rgba(255,255,255,0.02);
3531 border: 1px solid rgba(140,109,255,0.16);
3532 border-radius: 20px;
3533 color: var(--text-muted, #a0a0b8);
3534 }
3535 .land-2030-loop-svg {
3536 display: block;
3537 width: 100%;
3538 height: auto;
3539 }
3540 .land-2030-loop-path {
3541 stroke-dasharray: 6 4;
3542 animation: land2030-loop-dash 30s linear infinite;
3543 }
3544 @keyframes land2030-loop-dash {
3545 from { stroke-dashoffset: 0; }
3546 to { stroke-dashoffset: -400; }
3547 }
3548 .land-2030-loop-text {
3549 font-size: 14px;
3550 font-weight: 600;
3551 letter-spacing: -0.005em;
3552 }
3553
3554 /* ---------- 6-card "unfair advantages" grid ---------- */
3555 .land-2030-card-grid {
3556 display: grid;
3557 grid-template-columns: repeat(3, minmax(0, 1fr));
3558 gap: 16px;
3559 max-width: 1180px;
3560 margin: 0 auto;
3561 }
3562 .land-2030-card {
3563 display: flex;
3564 flex-direction: column;
3565 gap: 10px;
3566 padding: 24px;
3567 background: linear-gradient(180deg, rgba(255,255,255,0.04), rgba(255,255,255,0.015));
3568 border: 1px solid rgba(255,255,255,0.08);
3569 border-radius: 16px;
3570 text-decoration: none;
3571 color: inherit;
3572 transition: transform 200ms ease, border-color 200ms ease, box-shadow 200ms ease;
3573 }
3574 .land-2030-card:hover {
3575 transform: translateY(-2px);
3576 border-color: rgba(140,109,255,0.45);
3577 box-shadow: 0 8px 30px -8px rgba(140,109,255,0.40);
3578 }
3579 .land-2030-card-icon {
3580 width: 44px; height: 44px;
3581 display: inline-flex;
3582 align-items: center;
3583 justify-content: center;
3584 border-radius: 12px;
3585 background: rgba(140,109,255,0.12);
3586 color: #cbb7ff;
3587 border: 1px solid rgba(140,109,255,0.30);
3588 margin-bottom: 4px;
3589 }
3590 .land-2030-card-title {
3591 margin: 0;
3592 font-size: 18px;
3593 font-weight: 700;
3594 letter-spacing: -0.01em;
3595 }
3596 .land-2030-card-desc {
3597 margin: 0;
3598 font-size: 14.5px;
3599 color: var(--text-muted, #a0a0b8);
3600 line-height: 1.55;
3601 }
3602 .land-2030-card-cta {
3603 margin-top: auto;
3604 color: #b69dff;
3605 font-size: 14px;
3606 font-weight: 600;
3607 }
3608
3609 /* ---------- Global dashboards grid ---------- */
3610 .land-2030-dash-grid {
3611 display: grid;
3612 grid-template-columns: repeat(5, minmax(0, 1fr));
3613 gap: 12px;
3614 max-width: 1180px;
3615 margin: 0 auto;
3616 }
3617 .land-2030-dash {
3618 display: flex;
3619 flex-direction: column;
3620 gap: 6px;
3621 padding: 16px 18px;
3622 background: rgba(255,255,255,0.03);
3623 border: 1px solid rgba(255,255,255,0.08);
3624 border-radius: 12px;
3625 text-decoration: none;
3626 color: inherit;
3627 transition: transform 180ms ease, border-color 180ms ease;
3628 }
3629 .land-2030-dash:hover {
3630 transform: translateY(-1px);
3631 border-color: rgba(54,197,214,0.45);
3632 }
3633 .land-2030-dash-name {
3634 font-family: var(--font-mono, ui-monospace, monospace);
3635 font-size: 14px;
3636 color: #36c5d6;
3637 font-weight: 600;
3638 }
3639 .land-2030-dash-desc {
3640 font-size: 13px;
3641 color: var(--text-muted, #a0a0b8);
3642 line-height: 1.45;
3643 }
3644
3645 /* ---------- 18-feature grid + status pills ---------- */
3646 .land-2030-feat-grid {
3647 display: grid;
3648 grid-template-columns: repeat(3, minmax(0, 1fr));
3649 gap: 10px;
3650 max-width: 1180px;
3651 margin: 0 auto;
3652 }
3653 .land-2030-feat {
3654 display: flex;
3655 flex-direction: column;
3656 gap: 6px;
3657 padding: 14px 16px;
3658 background: rgba(255,255,255,0.025);
3659 border: 1px solid rgba(255,255,255,0.07);
3660 border-radius: 10px;
3661 text-decoration: none;
3662 color: inherit;
3663 transition: border-color 160ms ease, background 160ms ease;
3664 }
3665 .land-2030-feat:hover {
3666 border-color: rgba(140,109,255,0.35);
3667 background: rgba(140,109,255,0.05);
3668 }
3669 .land-2030-feat-head {
3670 display: flex;
3671 align-items: center;
3672 justify-content: space-between;
3673 gap: 10px;
3674 }
3675 .land-2030-feat-title {
3676 font-size: 14.5px;
3677 font-weight: 700;
3678 letter-spacing: -0.005em;
3679 }
3680 .land-2030-feat-desc {
3681 margin: 0;
3682 font-size: 13px;
3683 color: var(--text-muted, #a0a0b8);
3684 line-height: 1.5;
3685 }
3686 .land-2030-pill {
3687 font-size: 10.5px;
3688 letter-spacing: 0.04em;
3689 text-transform: uppercase;
3690 padding: 3px 8px;
3691 border-radius: 999px;
3692 border: 1px solid currentColor;
3693 font-weight: 700;
3694 line-height: 1;
3695 }
3696 .land-2030-pill-live { color: #5be0a9; border-color: rgba(91,224,169,0.5); background: rgba(91,224,169,0.10); }
3697 .land-2030-pill-beta { color: #ffd16b; border-color: rgba(255,209,107,0.5); background: rgba(255,209,107,0.10); }
3698 .land-2030-pill-soon { color: #a0a0b8; border-color: rgba(160,160,184,0.4); background: rgba(160,160,184,0.08); }
3699
3700 /* ---------- Developer surface ---------- */
3701 .land-2030-dx-grid {
3702 display: grid;
3703 grid-template-columns: repeat(3, minmax(0, 1fr));
3704 gap: 16px;
3705 max-width: 1180px;
3706 margin: 0 auto;
3707 }
3708 .land-2030-dx {
3709 padding: 18px;
3710 background: linear-gradient(180deg, rgba(15,17,26,0.7), rgba(8,9,15,0.7));
3711 border: 1px solid rgba(140,109,255,0.18);
3712 border-radius: 14px;
3713 }
3714 .land-2030-dx-title {
3715 margin: 0 0 10px;
3716 font-size: 15px;
3717 font-weight: 700;
3718 color: var(--text-strong, #fff);
3719 }
3720 .land-2030-code {
3721 margin: 0;
3722 padding: 12px 14px;
3723 background: rgba(0,0,0,0.30);
3724 border: 1px solid rgba(255,255,255,0.06);
3725 border-radius: 10px;
3726 font-family: var(--font-mono, ui-monospace, monospace);
3727 font-size: 12.5px;
3728 color: #cbb7ff;
3729 line-height: 1.6;
3730 overflow-x: auto;
3731 }
3732 .land-2030-code code { color: inherit; background: transparent; padding: 0; }
3733 .land-2030-code-wide {
3734 font-size: 13px;
3735 color: #d6d6e4;
3736 }
3737
3738 /* ---------- Agent multiplayer ---------- */
3739 .land-2030-agent-wrap {
3740 display: grid;
3741 grid-template-columns: 1.4fr 1fr;
3742 gap: 24px;
3743 max-width: 1080px;
3744 margin: 0 auto;
3745 align-items: center;
3746 }
3747 .land-2030-agent-stat { text-align: left; }
3748 .land-2030-agent-big {
3749 font-size: clamp(48px, 7vw, 80px);
3750 font-weight: 800;
3751 line-height: 1;
3752 background-image: linear-gradient(135deg, #a48bff 0%, #36c5d6 100%);
3753 background-clip: text;
3754 -webkit-background-clip: text;
3755 -webkit-text-fill-color: transparent;
3756 color: transparent;
3757 letter-spacing: -0.03em;
3758 }
3759 .land-2030-agent-label {
3760 margin-top: 12px;
3761 color: var(--text-muted, #a0a0b8);
3762 font-size: 15px;
3763 line-height: 1.5;
3764 }
3765 .land-2030-agent-link {
3766 display: inline-block;
3767 margin-top: 16px;
3768 color: #36c5d6;
3769 text-decoration: none;
3770 font-weight: 600;
3771 font-size: 14px;
3772 }
3773 .land-2030-agent-link:hover { text-decoration: underline; }
3774
3775 /* ---------- vs GitHub strip ---------- */
3776 .land-2030-vs {
3777 max-width: 880px;
3778 margin: 0 auto;
3779 background: rgba(255,255,255,0.03);
3780 border: 1px solid rgba(255,255,255,0.08);
3781 border-radius: 14px;
3782 overflow: hidden;
3783 text-align: center;
3784 padding: 0 0 18px;
3785 }
3786 .land-2030-vs-row {
3787 display: grid;
3788 grid-template-columns: 1fr 1fr;
3789 gap: 0;
3790 padding: 16px 18px;
3791 border-bottom: 1px solid rgba(255,255,255,0.06);
3792 font-size: 15px;
3793 }
3794 .land-2030-vs-row:last-of-type { border-bottom: 0; }
3795 .land-2030-vs-head {
3796 font-size: 12px;
3797 letter-spacing: 0.04em;
3798 text-transform: uppercase;
3799 color: var(--text-muted, #a0a0b8);
3800 background: rgba(255,255,255,0.02);
3801 }
3802 .land-2030-vs-us {
3803 color: #5be0a9;
3804 font-weight: 700;
3805 }
3806 .land-2030-vs-link {
3807 display: inline-block;
3808 margin-top: 12px;
3809 color: #b69dff;
3810 text-decoration: none;
3811 font-weight: 600;
3812 font-size: 14px;
3813 }
3814 .land-2030-vs-link:hover { text-decoration: underline; }
3815
3816 /* ---------- Responsive ---------- */
3817 @media (max-width: 960px) {
3818 .land-2030-card-grid,
3819 .land-2030-feat-grid,
3820 .land-2030-dx-grid,
3821 .land-2030-dash-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
3822 .land-2030-agent-wrap { grid-template-columns: 1fr; }
3823 }
3824 @media (max-width: 640px) {
3825 .land-2030-card-grid,
3826 .land-2030-feat-grid,
3827 .land-2030-dx-grid,
3828 .land-2030-dash-grid { grid-template-columns: 1fr; }
3829 .land-2030-section-dark { padding: 28px 16px; }
3830 .land-2030-vs-row { grid-template-columns: 1fr; gap: 4px; }
3831 }
3832 @media (max-width: 375px) {
3833 .land-2030-display { font-size: 44px; }
3834 .land-2030-cta-primary,
3835 .land-2030-cta-secondary { width: 100%; min-width: 0; }
3836 }
3837
3838 @media (prefers-reduced-motion: reduce) {
3839 .land-2030-loop-path,
3840 .land-2030-pulse { animation: none; }
3841 }
3842`;