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

landing.tsx

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

landing.tsxBlame3844 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`.
0c3eee5Claude1615// The 2030 prelude is rendered above the existing LandingHero so every
1616// existing L10/U1/Q1/M1 regression assertion keeps passing untouched.
4c47454Claude1617export const LandingPage: FC<LandingPageProps> = (props) => (
0c3eee5Claude1618 <>
1619 <Land2030 />
1620 <LandingHero {...props} />
1621 </>
2b821b7Claude1622);
1623
4c47454Claude1624export default LandingPage;
1625
2b821b7Claude1626const landingCss = `
958d26aClaude1627 /* ============================================================ */
1628 /* Landing — Editorial-Technical 2026.05 */
1629 /* ============================================================ */
4c47454Claude1630 .landing-root {
1631 position: relative;
958d26aClaude1632 max-width: 1180px;
4c47454Claude1633 margin: 0 auto;
1634 padding: 0 16px;
958d26aClaude1635 }
1636 .landing-root > section { position: relative; }
1637
1638 /* ---------- Hero ---------- */
1639 .landing-hero {
1640 position: relative;
c475ee6Claude1641 padding: var(--s-16) 0 var(--s-20);
958d26aClaude1642 text-align: center;
4c47454Claude1643 overflow: hidden;
1644 }
c475ee6Claude1645 .landing-hero-blob-1 {
1646 animation: hero-blob-drift-1 18s var(--ease, ease) infinite alternate;
1647 }
1648 .landing-hero-blob-2 {
1649 animation: hero-blob-drift-2 22s var(--ease, ease) infinite alternate;
1650 }
1651 @keyframes hero-blob-drift-1 {
1652 0% { transform: translate(0, 0) scale(1); opacity: 0.55; }
1653 100% { transform: translate(8%, 6%) scale(1.18); opacity: 0.75; }
1654 }
1655 @keyframes hero-blob-drift-2 {
1656 0% { transform: translate(0, 0) scale(1); opacity: 0.40; }
1657 100% { transform: translate(-10%, -4%) scale(1.25); opacity: 0.60; }
1658 }
1659
1660 /* ---------- Hero product visual: live AI PR review card ---------- */
1661 .landing-hero-visual {
1662 position: relative;
1663 max-width: 760px;
1664 margin: var(--s-12) auto 0;
1665 padding: 0 16px;
1666 perspective: 1400px;
1667 z-index: 2;
1668 opacity: 0;
1669 animation: hero-visual-in 700ms var(--ease-out-expo, cubic-bezier(0.19, 1, 0.22, 1)) 400ms forwards;
1670 }
1671 @keyframes hero-visual-in {
1672 from { opacity: 0; transform: translateY(20px); }
1673 to { opacity: 1; transform: translateY(0); }
1674 }
1675 .hero-pr-card {
1676 position: relative;
1677 background: linear-gradient(180deg, rgba(15,17,26,0.96) 0%, rgba(8,9,15,0.96) 100%);
1678 border: 1px solid var(--border-strong);
1679 border-radius: var(--r-xl);
1680 overflow: hidden;
1681 text-align: left;
1682 box-shadow:
1683 0 30px 80px -20px rgba(0,0,0,0.65),
1684 0 0 0 1px rgba(140,109,255,0.18),
1685 0 0 60px -10px rgba(140,109,255,0.30);
1686 transform: rotateX(2deg) rotateY(-2deg);
1687 transition: transform 600ms var(--ease, ease);
1688 backdrop-filter: blur(12px);
1689 -webkit-backdrop-filter: blur(12px);
1690 }
1691 .landing-hero-visual:hover .hero-pr-card {
1692 transform: rotateX(0deg) rotateY(0deg);
1693 }
1694 .hero-pr-card::before {
1695 content: '';
1696 position: absolute;
1697 inset: 0;
1698 background: linear-gradient(135deg, rgba(140,109,255,0.10), transparent 35%, transparent 65%, rgba(54,197,214,0.08));
1699 pointer-events: none;
1700 }
1701 .hero-pr-header {
1702 display: flex;
1703 align-items: center;
1704 gap: 12px;
1705 padding: 14px 18px;
1706 border-bottom: 1px solid rgba(255,255,255,0.06);
1707 background: rgba(255,255,255,0.025);
1708 font-size: 13px;
1709 }
1710 .hero-pr-dot {
1711 width: 10px; height: 10px;
1712 border-radius: 50%;
1713 background: var(--green);
1714 box-shadow: 0 0 10px rgba(52,211,153,0.6);
1715 flex-shrink: 0;
1716 }
1717 .hero-pr-title {
1718 color: var(--text-strong);
1719 font-weight: 600;
1720 flex: 1;
1721 overflow: hidden;
1722 text-overflow: ellipsis;
1723 white-space: nowrap;
1724 }
1725 .hero-pr-num {
1726 color: var(--text-faint);
1727 font-family: var(--font-mono);
1728 font-weight: 500;
1729 margin-right: 8px;
1730 }
1731 .hero-pr-status {
1732 display: inline-flex;
1733 align-items: center;
1734 gap: 6px;
1735 padding: 3px 10px;
1736 border-radius: var(--r-full);
1737 background: var(--accent-gradient-faint);
1738 border: 1px solid rgba(140,109,255,0.30);
1739 color: var(--accent);
1740 font-family: var(--font-mono);
1741 font-size: 11px;
1742 letter-spacing: 0.04em;
1743 flex-shrink: 0;
1744 }
1745 .hero-pr-status-pulse {
1746 width: 6px; height: 6px;
1747 border-radius: 50%;
1748 background: var(--accent);
1749 box-shadow: 0 0 0 0 rgba(140,109,255,0.6);
1750 animation: hero-pulse 1.6s ease-out infinite;
1751 }
1752 @keyframes hero-pulse {
1753 0% { box-shadow: 0 0 0 0 rgba(140,109,255,0.55); }
1754 70% { box-shadow: 0 0 0 8px rgba(140,109,255,0); }
1755 100% { box-shadow: 0 0 0 0 rgba(140,109,255,0); }
1756 }
1757
1758 .hero-pr-body {
1759 padding: 0;
1760 }
1761 .hero-pr-file {
1762 display: flex;
1763 align-items: center;
1764 gap: 10px;
1765 padding: 10px 18px;
1766 border-bottom: 1px solid rgba(255,255,255,0.05);
1767 font-family: var(--font-mono);
1768 font-size: 12px;
1769 background: rgba(255,255,255,0.012);
1770 }
1771 .hero-pr-file-icon { color: var(--accent-2); }
1772 .hero-pr-file-name { color: var(--text); flex: 1; }
1773 .hero-pr-file-stats { display: inline-flex; gap: 8px; }
1774 .hero-pr-add { color: var(--green); font-weight: 600; }
1775 .hero-pr-del { color: var(--red); font-weight: 600; }
1776
1777 .hero-pr-diff {
1778 padding: 12px 18px;
1779 font-family: var(--font-mono);
1780 font-feature-settings: var(--mono-feat, 'calt');
1781 font-size: 12.5px;
1782 line-height: 1.7;
1783 color: rgba(237,237,242,0.85);
1784 overflow-x: auto;
1785 }
1786 .hero-pr-hunk {
1787 color: rgba(140,109,255,0.85);
1788 background: rgba(140,109,255,0.06);
1789 padding: 2px 8px;
1790 margin: 0 -8px 4px;
1791 border-radius: 4px;
1792 }
1793 .hero-pr-line-add {
1794 background: rgba(52,211,153,0.08);
1795 color: rgba(167,243,208,0.95);
1796 padding: 0 8px;
1797 margin: 0 -8px;
1798 border-left: 2px solid var(--green);
1799 padding-left: 8px;
1800 }
1801
1802 .hero-pr-comment {
1803 margin: 14px 18px;
1804 padding: 14px 16px;
1805 background: linear-gradient(135deg, rgba(140,109,255,0.08), rgba(54,197,214,0.05));
1806 border: 1px solid rgba(140,109,255,0.25);
1807 border-radius: var(--r-md);
1808 }
1809 .hero-pr-bot-row {
1810 display: flex;
1811 align-items: center;
1812 gap: 8px;
1813 margin-bottom: 8px;
1814 font-size: 12px;
1815 }
1816 .hero-pr-bot-avatar {
1817 width: 22px; height: 22px;
1818 display: inline-flex;
1819 align-items: center;
1820 justify-content: center;
1821 border-radius: 50%;
1822 background: var(--accent-gradient);
1823 font-size: 11px;
1824 box-shadow: 0 0 12px rgba(140,109,255,0.40);
1825 }
1826 .hero-pr-bot-name {
1827 color: var(--text-strong);
1828 font-weight: 600;
1829 }
1830 .hero-pr-bot-meta {
1831 color: var(--text-faint);
1832 font-family: var(--font-mono);
1833 }
1834 .hero-pr-bot-text {
1835 color: var(--text);
1836 font-size: 13px;
1837 line-height: 1.55;
1838 margin: 0;
1839 }
1840 .hero-pr-bot-text code {
1841 background: rgba(255,255,255,0.06);
1842 border: 1px solid rgba(255,255,255,0.10);
1843 padding: 1px 6px;
1844 border-radius: 4px;
1845 font-size: 11.5px;
1846 color: var(--accent);
1847 }
1848 .hero-pr-bot-link { color: var(--accent-2); text-decoration: underline; text-decoration-style: dotted; }
1849
1850 .hero-pr-gates {
1851 display: flex;
1852 flex-wrap: wrap;
1853 gap: 6px;
1854 padding: 12px 18px 16px;
1855 border-top: 1px solid rgba(255,255,255,0.06);
1856 background: rgba(255,255,255,0.012);
1857 }
1858 .hero-pr-gate {
1859 display: inline-flex;
1860 align-items: center;
1861 gap: 6px;
1862 padding: 4px 10px;
1863 border-radius: var(--r-full);
1864 font-family: var(--font-mono);
1865 font-size: 11px;
1866 border: 1px solid;
1867 }
1868 .hero-pr-gate-pass {
1869 color: var(--green);
1870 background: rgba(52,211,153,0.08);
1871 border-color: rgba(52,211,153,0.30);
1872 }
1873 .hero-pr-gate-running {
1874 color: var(--accent);
1875 background: var(--accent-gradient-faint);
1876 border-color: rgba(140,109,255,0.40);
1877 }
1878 .hero-pr-gate-spin {
1879 width: 9px; height: 9px;
1880 border: 1.5px solid rgba(140,109,255,0.30);
1881 border-top-color: var(--accent);
1882 border-radius: 50%;
1883 animation: hero-spin 800ms linear infinite;
1884 }
1885 @keyframes hero-spin {
1886 to { transform: rotate(360deg); }
1887 }
1888
1889 /* Floating accent badges around the card */
1890 .hero-float {
1891 position: absolute;
1892 display: inline-flex;
1893 align-items: center;
1894 gap: 6px;
1895 padding: 6px 12px;
1896 background: rgba(15,17,26,0.92);
1897 border: 1px solid rgba(140,109,255,0.35);
1898 border-radius: var(--r-full);
1899 font-family: var(--font-mono);
1900 font-size: 11px;
1901 color: var(--text);
1902 box-shadow: 0 12px 24px -8px rgba(0,0,0,0.5), 0 0 18px -4px rgba(140,109,255,0.30);
1903 backdrop-filter: blur(8px);
1904 -webkit-backdrop-filter: blur(8px);
1905 }
1906 .hero-float-icon { color: var(--accent); }
1907 .hero-float-1 {
1908 top: -14px;
1909 left: -8px;
1910 animation: hero-float-bob-1 5s var(--ease, ease) infinite alternate;
1911 }
1912 .hero-float-2 {
1913 bottom: -14px;
1914 right: -8px;
1915 animation: hero-float-bob-2 6s var(--ease, ease) infinite alternate;
1916 }
1917 @keyframes hero-float-bob-1 {
1918 from { transform: translate(0, 0); }
1919 to { transform: translate(-8px, -10px); }
1920 }
1921 @keyframes hero-float-bob-2 {
1922 from { transform: translate(0, 0); }
1923 to { transform: translate(8px, 8px); }
1924 }
1925
1926 @media (max-width: 720px) {
1927 .landing-hero-visual { padding: 0 8px; }
1928 .hero-pr-card { transform: none; }
1929 .hero-pr-title { font-size: 12px; }
1930 .hero-pr-diff { font-size: 11px; line-height: 1.6; }
1931 .hero-float { display: none; }
1932 }
958d26aClaude1933 .landing-hero-bg {
1934 position: absolute;
1935 inset: -10% -20%;
1936 pointer-events: none;
1937 z-index: 0;
4c47454Claude1938 }
958d26aClaude1939 .landing-hero-blob {
1940 position: absolute;
1941 border-radius: 50%;
1942 filter: blur(80px);
1943 opacity: 0.55;
3a5755eClaude1944 will-change: transform;
958d26aClaude1945 }
1946 .landing-hero-blob-1 {
1947 top: -10%;
1948 left: 30%;
1949 width: 480px;
1950 height: 480px;
3a5755eClaude1951 background: radial-gradient(circle, rgba(140,109,255,0.65), transparent 65%);
1952 /* 2026 polish — slow drift gives the hero "this is a live product"
1953 feel without being distracting. 24s loop, eased, contained motion. */
1954 animation: landingBlobDrift1 24s ease-in-out infinite;
958d26aClaude1955 }
1956 .landing-hero-blob-2 {
1957 top: 10%;
1958 left: 50%;
1959 width: 380px;
1960 height: 380px;
3a5755eClaude1961 background: radial-gradient(circle, rgba(54,197,214,0.50), transparent 65%);
1962 animation: landingBlobDrift2 28s ease-in-out infinite;
958d26aClaude1963 }
dc26881CC LABS App1964 /* U1 — subtle, low-opacity accent-gradient orb behind the headline.
1965 Sits dead-centre, very blurred, so the hero reads as a real product
3a5755eClaude1966 surface rather than flat-bg + text. 2026 polish — gentle breathing
1967 pulse to give the surface a soft heartbeat. */
dc26881CC LABS App1968 .landing-hero-orb {
1969 top: 18%;
1970 left: 50%;
1971 transform: translateX(-50%);
1972 width: 720px;
1973 height: 720px;
3a5755eClaude1974 background: radial-gradient(circle, rgba(140,109,255,0.28), rgba(54,197,214,0.16) 45%, transparent 70%);
dc26881CC LABS App1975 filter: blur(120px);
3a5755eClaude1976 opacity: 0.6;
dc26881CC LABS App1977 z-index: 0;
3a5755eClaude1978 animation: landingOrbBreath 12s ease-in-out infinite;
dc26881CC LABS App1979 }
1980 :root[data-theme='light'] .landing-hero-orb {
1981 opacity: 0.32;
1982 }
3a5755eClaude1983 @keyframes landingBlobDrift1 {
1984 0%, 100% { transform: translate(0, 0) scale(1); }
1985 33% { transform: translate(40px, -30px) scale(1.08); }
1986 66% { transform: translate(-30px, 25px) scale(0.95); }
1987 }
1988 @keyframes landingBlobDrift2 {
1989 0%, 100% { transform: translate(0, 0) scale(1); }
1990 50% { transform: translate(-50px, 35px) scale(1.12); }
1991 }
1992 @keyframes landingOrbBreath {
1993 0%, 100% { opacity: 0.55; transform: translateX(-50%) scale(1); }
1994 50% { opacity: 0.72; transform: translateX(-50%) scale(1.06); }
1995 }
dc26881CC LABS App1996 @media (prefers-reduced-motion: reduce) {
1997 .landing-hero-blob-1,
1998 .landing-hero-blob-2,
1999 .landing-hero-orb {
2000 animation: none;
2001 }
2002 }
958d26aClaude2003 .landing-hero-grid {
4c47454Claude2004 position: absolute;
2005 inset: 0;
958d26aClaude2006 background-image:
2007 linear-gradient(to right, rgba(255,255,255,0.04) 1px, transparent 1px),
2008 linear-gradient(to bottom, rgba(255,255,255,0.04) 1px, transparent 1px);
2009 background-size: 60px 60px;
2010 mask-image: radial-gradient(ellipse 50% 50% at 50% 30%, #000 0%, transparent 75%);
2011 -webkit-mask-image: radial-gradient(ellipse 50% 50% at 50% 30%, #000 0%, transparent 75%);
2012 }
2013 :root[data-theme='light'] .landing-hero-grid {
2014 background-image:
2015 linear-gradient(to right, rgba(15,16,28,0.06) 1px, transparent 1px),
2016 linear-gradient(to bottom, rgba(15,16,28,0.06) 1px, transparent 1px);
4c47454Claude2017 }
2018
958d26aClaude2019 .landing-hero-inner {
2020 position: relative;
2021 z-index: 1;
2022 max-width: 960px;
2b821b7Claude2023 margin: 0 auto;
2024 }
dc26881CC LABS App2025 /* U1 — every block below the headline obeys a single rhythm. */
958d26aClaude2026 .landing-hero-eyebrow {
dc26881CC LABS App2027 margin: 0 auto var(--space-6);
958d26aClaude2028 color: var(--accent);
2029 }
2030 .landing-hero-eyebrow::before { display: none; }
2031 .landing-hero-pulse {
2032 width: 7px;
2033 height: 7px;
2034 border-radius: 50%;
2035 background: var(--accent);
2036 box-shadow: 0 0 0 0 rgba(140,109,255,0.6);
2037 animation: pulse 1.8s ease-out infinite;
2038 flex-shrink: 0;
2039 }
2040 @keyframes pulse {
2041 0% { box-shadow: 0 0 0 0 rgba(140,109,255,0.55); }
2042 70% { box-shadow: 0 0 0 10px rgba(140,109,255,0); }
2043 100% { box-shadow: 0 0 0 0 rgba(140,109,255,0); }
2044 }
2045
2b821b7Claude2046 .landing-hero-title {
3a5755eClaude2047 /* 2026 polish — bigger, bolder, tighter. Inter Tight at 800 weight
2048 with -0.03em tracking is the modern "AI-startup hero" look that
2049 Vercel/Linear/Cursor all use. clamp() scales gracefully on mobile. */
2050 font-size: clamp(40px, 7vw, 84px);
2051 line-height: 1.02;
2052 letter-spacing: -0.032em;
2053 font-weight: 800;
2054 font-family: var(--font-display);
dc26881CC LABS App2055 margin: 0 0 var(--space-6);
958d26aClaude2056 color: var(--text-strong);
2b821b7Claude2057 }
c963db5Claude2058 .landing-hero-title .gradient-text {
3a5755eClaude2059 /* Richer gradient with a third stop for more depth. Drop-shadow
2060 gives the impression of subtle glow without overpowering the type. */
2061 background-image: linear-gradient(135deg, #c2a8ff 0%, #8c6dff 40%, #5d3dff 70%, #36c5d6 100%);
c963db5Claude2062 -webkit-background-clip: text;
2063 background-clip: text;
2064 -webkit-text-fill-color: transparent;
2065 color: transparent;
3a5755eClaude2066 filter: drop-shadow(0 4px 32px rgba(140, 109, 255, 0.18));
c963db5Claude2067 }
958d26aClaude2068
2b821b7Claude2069 .landing-hero-sub {
3a5755eClaude2070 font-size: clamp(16px, 1.8vw, 22px);
2b821b7Claude2071 color: var(--text-muted);
958d26aClaude2072 max-width: 680px;
dc26881CC LABS App2073 margin: 0 auto var(--space-6);
3a5755eClaude2074 line-height: 1.5;
2075 letter-spacing: -0.008em;
2076 font-weight: 400;
2b821b7Claude2077 }
4c47454Claude2078
2b821b7Claude2079 .landing-hero-ctas {
2080 display: flex;
dc26881CC LABS App2081 gap: var(--space-3);
2b821b7Claude2082 justify-content: center;
2083 flex-wrap: wrap;
dc26881CC LABS App2084 margin-top: 0;
2085 margin-bottom: var(--space-4);
2b821b7Claude2086 }
958d26aClaude2087 .landing-cta-arrow {
2088 transition: transform var(--t-base) var(--ease-spring);
2089 display: inline-block;
2b821b7Claude2090 }
cd4f63bTest User2091
dc26881CC LABS App2092 /* U1 — tertiary text-link row.
2093 Sits directly under the 2-button primary CTA row. Smaller, muted,
2094 so it reads as "by the way" rather than competing for the eye. */
2095 .landing-hero-tertiary {
2096 margin-top: 0;
2097 margin-bottom: var(--space-6);
cd4f63bTest User2098 text-align: center;
dc26881CC LABS App2099 display: inline-flex;
2100 flex-wrap: wrap;
2101 gap: var(--space-2) var(--space-3);
2102 justify-content: center;
2103 align-items: baseline;
2104 width: 100%;
cd4f63bTest User2105 font-size: 13px;
2106 color: var(--text-muted);
dc26881CC LABS App2107 }
2108 .landing-hero-tertiary-link {
2109 color: var(--text-muted);
cd4f63bTest User2110 text-decoration: none;
2111 border-bottom: 1px dashed transparent;
2112 padding-bottom: 1px;
dc26881CC LABS App2113 transition: color var(--t-fast) var(--ease),
2114 border-color var(--t-fast) var(--ease);
cd4f63bTest User2115 }
dc26881CC LABS App2116 .landing-hero-tertiary-link:hover {
2117 color: var(--text-strong);
cd4f63bTest User2118 border-bottom-color: var(--text-muted);
2119 }
dc26881CC LABS App2120 .landing-hero-tertiary-sep {
2121 color: var(--text-faint);
2122 user-select: none;
2123 }
958d26aClaude2124 .btn:hover .landing-cta-arrow,
2125 .landing-cta-primary:hover .landing-cta-arrow {
2126 transform: translateX(4px);
8e9f1d9Claude2127 }
2b821b7Claude2128
93fe97eClaude2129 /* BLOCK Q1 — flagship "Add to Claude Desktop" CTA.
2130 Gradient-bordered + accent text so it reads as a peer of the primary
bc7654bCC LABS App2131 Sign-up CTA, not a third secondary. Theme-aware: inner fill uses
2132 --bg-elevated so it's white on light and dark on dark, never the
2133 jarring near-black on white we shipped first time. Subtle elevation
2134 on hover; static when the visitor opts out of motion. */
93fe97eClaude2135 .landing-cta-dxt {
2136 position: relative;
bc7654bCC LABS App2137 background: var(--bg-elevated);
2138 color: var(--text-strong);
93fe97eClaude2139 border: 1px solid transparent;
2140 background-image:
bc7654bCC LABS App2141 linear-gradient(var(--bg-elevated), var(--bg-elevated)),
93fe97eClaude2142 linear-gradient(90deg, #8c6dff 0%, #36c5d6 100%);
2143 background-origin: border-box;
2144 background-clip: padding-box, border-box;
2145 transition: transform var(--t-base, 180ms) var(--ease-spring, ease),
2146 box-shadow var(--t-base, 180ms) var(--ease-spring, ease);
2147 }
2148 .landing-cta-dxt:hover {
2149 transform: translateY(-2px);
2150 box-shadow: 0 8px 24px -8px rgba(140, 109, 255, 0.45);
2151 }
2152 @media (prefers-reduced-motion: reduce) {
2153 .landing-cta-dxt,
2154 .landing-cta-dxt:hover {
2155 transform: none;
2156 transition: none;
2157 }
2158 }
2159
dc26881CC LABS App2160 /* L8 — free-tier reassurance link beneath the CTA row.
2161 U1 — rhythm snapped to var(--space-6). */
5f2e749Claude2162 .landing-hero-freenote {
dc26881CC LABS App2163 margin: 0 auto var(--space-6);
5f2e749Claude2164 font-size: var(--t-sm);
2165 color: var(--text-muted);
2166 text-align: center;
2167 }
2168 .landing-hero-freenote-link {
2169 color: var(--accent);
2170 text-decoration: none;
2171 font-weight: 500;
2172 border-bottom: 1px dotted rgba(140,109,255,0.4);
2173 transition: color var(--t-fast) var(--ease), border-color var(--t-fast) var(--ease);
2174 }
2175 .landing-hero-freenote-link:hover {
2176 color: var(--text-strong);
2177 border-bottom-color: var(--accent);
2178 }
2179
4c47454Claude2180 .landing-hero-caption {
dc26881CC LABS App2181 margin: 0 auto var(--space-6);
958d26aClaude2182 font-size: var(--t-sm);
4c47454Claude2183 color: var(--text-muted);
958d26aClaude2184 display: flex;
2185 flex-wrap: wrap;
2186 align-items: center;
2187 justify-content: center;
dc26881CC LABS App2188 gap: var(--space-3);
2b821b7Claude2189 }
958d26aClaude2190 .landing-hero-cmd {
2191 display: inline-flex;
2192 align-items: center;
2193 gap: 4px;
2194 padding: 6px 12px;
2195 background: var(--bg-elevated);
4c47454Claude2196 border: 1px solid var(--border);
958d26aClaude2197 border-radius: var(--r-full);
2198 box-shadow: var(--elev-1);
2199 }
2200 .landing-hero-cmd .kbd {
2201 border: 0;
2202 background: transparent;
2203 padding: 0 4px;
2204 color: var(--text-muted);
2205 font-size: 12px;
2206 }
2207 .landing-hero-cmd .kbd:nth-last-of-type(1) { color: var(--accent); }
2208 .landing-hero-arrow {
2209 color: var(--text-faint);
2210 font-size: 13px;
2211 margin: 0 2px;
2b821b7Claude2212 }
4c47454Claude2213
2214 .landing-stats {
dc26881CC LABS App2215 margin: 0 auto;
958d26aClaude2216 font-family: var(--font-mono);
2217 font-size: 12px;
2218 color: var(--text-muted);
2219 display: flex;
2220 align-items: center;
2221 justify-content: center;
dc26881CC LABS App2222 gap: var(--space-2);
958d26aClaude2223 flex-wrap: wrap;
2224 letter-spacing: 0.02em;
2225 }
2226 .landing-stats strong {
2227 color: var(--text-strong);
2228 font-weight: 600;
2229 font-feature-settings: 'tnum';
2230 }
2231 .landing-stats-sep { opacity: 0.4; }
2232
c963db5Claude2233 /* ---------- Capability grid (crontech-style uppercase tracked) ---------- */
2234 .landing-caps {
2235 margin: var(--s-12) auto var(--s-16);
2236 max-width: 1080px;
2237 padding: var(--s-7) var(--s-4);
958d26aClaude2238 border-top: 1px solid var(--border-subtle);
2239 border-bottom: 1px solid var(--border-subtle);
c963db5Claude2240 }
2241 .landing-caps-grid {
2242 display: grid;
2243 grid-template-columns: repeat(4, 1fr);
2244 gap: 24px 16px;
958d26aClaude2245 text-align: center;
2246 }
c963db5Claude2247 .landing-cap {
958d26aClaude2248 font-family: var(--font-mono);
2249 font-size: 11px;
c963db5Claude2250 font-weight: 600;
958d26aClaude2251 text-transform: uppercase;
c963db5Claude2252 letter-spacing: 0.16em;
2253 color: var(--text-muted);
2254 transition: color var(--t-fast) var(--ease);
958d26aClaude2255 }
c963db5Claude2256 .landing-cap:hover { color: var(--text-strong); }
2257 @media (max-width: 800px) {
2258 .landing-caps-grid { grid-template-columns: repeat(2, 1fr); gap: 18px 12px; }
2259 }
2260 @media (max-width: 480px) {
2261 .landing-caps-grid { grid-template-columns: 1fr; }
2262 }
2263
2264 /* ---------- Big stat row (crontech-style hero closer) ---------- */
2265 .landing-bigstats {
2266 margin: var(--s-10) auto var(--s-20);
2267 max-width: 1180px;
2268 padding: 0 var(--s-4);
2269 }
2270 .landing-bigstats-grid {
2271 display: grid;
2272 grid-template-columns: repeat(4, 1fr);
2273 gap: 32px;
2274 text-align: left;
958d26aClaude2275 }
c963db5Claude2276 .landing-bigstat {
2277 padding: var(--s-2) 0;
2278 }
2279 .landing-bigstat-num {
958d26aClaude2280 font-family: var(--font-display);
c963db5Claude2281 font-size: clamp(28px, 3.5vw, 44px);
2282 line-height: 1.05;
2283 letter-spacing: -0.03em;
2284 font-weight: 700;
2285 color: var(--text-strong);
2286 margin-bottom: var(--s-2);
2287 }
2288 .landing-bigstat-label {
2289 font-family: var(--font-mono);
2290 font-size: 11px;
958d26aClaude2291 font-weight: 500;
c963db5Claude2292 text-transform: uppercase;
2293 letter-spacing: 0.14em;
2294 color: var(--text-faint);
2b821b7Claude2295 }
c963db5Claude2296 @media (max-width: 800px) {
2297 .landing-bigstats-grid { grid-template-columns: repeat(2, 1fr); gap: 28px 16px; }
2298 }
2299 @media (max-width: 480px) {
2300 .landing-bigstats-grid { grid-template-columns: 1fr; gap: 24px; }
958d26aClaude2301 }
2302
2303 /* ---------- Section base ---------- */
2304 .landing-section { margin: var(--s-20) auto; }
2b821b7Claude2305
4c47454Claude2306 /* ---------- Feature grid ---------- */
2307 .landing-features {
2b821b7Claude2308 display: grid;
2309 grid-template-columns: repeat(3, 1fr);
958d26aClaude2310 gap: 16px;
2b821b7Claude2311 }
2312 .landing-feature {
958d26aClaude2313 background: var(--bg-elevated);
2b821b7Claude2314 border: 1px solid var(--border);
958d26aClaude2315 border-radius: var(--r-lg);
2316 padding: var(--s-7);
2317 position: relative;
2318 overflow: hidden;
2319 isolation: isolate;
2320 transition:
2321 transform var(--t-base) var(--ease-out-quart),
2322 border-color var(--t-base) var(--ease),
2323 box-shadow var(--t-base) var(--ease);
2324 }
2325 .landing-feature::before {
2326 content: '';
2327 position: absolute;
2328 inset: 0;
2329 background: radial-gradient(120% 100% at 0% 0%, rgba(140,109,255,0.08), transparent 55%);
2330 opacity: 0;
2331 transition: opacity var(--t-base) var(--ease);
2332 z-index: -1;
4c47454Claude2333 }
2334 .landing-feature:hover {
958d26aClaude2335 transform: translateY(-3px);
2336 border-color: var(--border-strong);
2337 box-shadow: var(--elev-2);
2b821b7Claude2338 }
958d26aClaude2339 .landing-feature:hover::before { opacity: 1; }
2b821b7Claude2340 .landing-feature-icon {
4c47454Claude2341 display: inline-flex;
2342 align-items: center;
2343 justify-content: center;
958d26aClaude2344 width: 40px;
2345 height: 40px;
2346 border-radius: var(--r);
2347 background: var(--accent-gradient-soft);
2348 color: var(--accent);
2349 margin-bottom: var(--s-4);
2350 border: 1px solid rgba(140,109,255,0.20);
2b821b7Claude2351 }
2352 .landing-feature-title {
958d26aClaude2353 font-family: var(--font-display);
2354 font-size: 19px;
2355 font-weight: 600;
2356 letter-spacing: -0.018em;
2357 margin: 0 0 var(--s-2);
2358 color: var(--text-strong);
2b821b7Claude2359 }
2360 .landing-feature-desc {
958d26aClaude2361 font-size: var(--t-sm);
2362 color: var(--text-muted);
2363 line-height: 1.6;
2364 margin: 0;
2365 }
2366
2367 /* ---------- Walkthrough ---------- */
2368 .landing-walk-grid {
2369 display: grid;
2370 grid-template-columns: repeat(4, 1fr);
2371 gap: 16px;
2372 counter-reset: walk;
2373 }
2374 .landing-walk-step {
2375 position: relative;
2376 padding: var(--s-7) var(--s-6) var(--s-6);
2377 background: var(--bg-elevated);
2378 border: 1px solid var(--border);
2379 border-radius: var(--r-lg);
2380 }
2381 .landing-walk-step::after {
2382 content: '';
2383 position: absolute;
2384 top: 50%;
2385 right: -12px;
2386 width: 12px;
2387 height: 1px;
2388 background: var(--border-strong);
2389 }
2390 .landing-walk-step:last-child::after { display: none; }
2391 .landing-walk-num {
2392 display: inline-block;
2393 font-family: var(--font-mono);
2394 font-size: 11px;
2395 color: var(--accent);
2396 background: var(--accent-gradient-faint);
2397 border: 1px solid rgba(140,109,255,0.30);
2398 padding: 3px 8px;
2399 border-radius: var(--r-full);
2400 letter-spacing: 0.06em;
2401 margin-bottom: var(--s-3);
2402 }
2403 .landing-walk-title {
2404 font-family: var(--font-display);
2405 font-size: 22px;
2406 font-weight: 600;
2407 letter-spacing: -0.022em;
2408 margin: 0 0 var(--s-2);
2409 color: var(--text-strong);
2410 }
2411 .landing-walk-desc {
2412 font-size: var(--t-sm);
2b821b7Claude2413 color: var(--text-muted);
2414 line-height: 1.55;
2415 margin: 0;
2416 }
2417
958d26aClaude2418 /* ---------- Terminal ---------- */
2419 .landing-terminal-section { margin-top: var(--s-16); }
4c47454Claude2420 .landing-terminal-wrap {
2421 display: flex;
2b821b7Claude2422 justify-content: center;
2423 }
4c47454Claude2424 .landing-terminal {
2425 width: 100%;
958d26aClaude2426 max-width: 820px;
2427 background: linear-gradient(180deg, #0a0b12 0%, #06070c 100%);
2428 border: 1px solid var(--border-strong);
2429 border-radius: var(--r-lg);
2430 overflow: hidden;
2431 box-shadow: var(--elev-3), 0 0 60px -10px rgba(140,109,255,0.18);
4c47454Claude2432 text-align: left;
2b821b7Claude2433 }
958d26aClaude2434 :root[data-theme='light'] .landing-terminal {
2435 background: linear-gradient(180deg, #0f111a 0%, #06070c 100%);
2436 }
2437 .landing-terminal-chrome {
2438 display: flex;
2439 align-items: center;
2440 gap: 7px;
2441 padding: 11px 14px;
2442 background: rgba(255,255,255,0.025);
2443 border-bottom: 1px solid rgba(255,255,255,0.06);
2444 position: relative;
2445 }
2446 .landing-terminal-dot {
2447 width: 11px;
2448 height: 11px;
2449 border-radius: 50%;
2450 flex-shrink: 0;
2451 }
2452 .landing-terminal-dot-r { background: #ff5f57; }
2453 .landing-terminal-dot-y { background: #febc2e; }
2454 .landing-terminal-dot-g { background: #28c840; }
2455 .landing-terminal-title {
2456 position: absolute;
2457 left: 50%;
2458 transform: translateX(-50%);
2459 font-family: var(--font-mono);
2460 font-size: 11px;
2461 color: rgba(237,237,242,0.55);
2462 letter-spacing: 0.01em;
2463 }
2464 .landing-terminal-body {
2465 padding: var(--s-6) var(--s-7);
2466 font-family: var(--font-mono);
2467 font-feature-settings: var(--mono-feat);
2468 font-size: 13.5px;
2469 line-height: 1.85;
2470 color: rgba(237,237,242,0.92);
2471 }
4c47454Claude2472 .landing-term-line {
2473 display: flex;
2474 gap: 10px;
2475 white-space: pre-wrap;
2476 word-break: break-all;
2b821b7Claude2477 }
958d26aClaude2478 .landing-term-out { color: rgba(237,237,242,0.7); }
2479 .landing-term-prompt { color: rgba(140,109,255,0.85); user-select: none; flex-shrink: 0; }
2480 .landing-term-meta { color: rgba(237,237,242,0.45); }
2481 .landing-term-ok { color: var(--green); user-select: none; flex-shrink: 0; }
2482 .landing-term-ok-line { color: rgba(237,237,242,0.92); }
2483 .landing-term-cursor { margin-top: 4px; }
2484 .landing-term-blink {
2485 animation: blink 1.05s steps(2) infinite;
2486 color: var(--accent);
2487 }
2488 @keyframes blink { 50% { opacity: 0; } }
2489
2490 /* ---------- Comparison ---------- */
2491 .landing-compare {
2492 max-width: 920px;
2493 margin: 0 auto;
2494 border: 1px solid var(--border);
2495 border-radius: var(--r-lg);
2496 overflow: hidden;
2497 background: var(--bg-elevated);
2498 }
2499 .landing-compare-row {
2500 display: grid;
2501 grid-template-columns: 1fr 180px 180px;
2502 align-items: center;
2503 padding: 14px 20px;
2504 border-bottom: 1px solid var(--border-subtle);
2505 font-size: var(--t-sm);
2506 transition: background var(--t-fast) var(--ease);
2507 }
2508 .landing-compare-row:last-child { border-bottom: none; }
2509 .landing-compare-row:hover { background: var(--bg-hover); }
2510 .landing-compare-feature {
2511 color: var(--text-strong);
2512 font-weight: 500;
2513 }
2514 .landing-compare-them, .landing-compare-us {
2515 text-align: center;
2516 font-family: var(--font-mono);
2517 font-size: 12px;
2518 color: var(--text-muted);
2519 }
2520 .landing-compare-us { color: var(--green); font-weight: 500; }
2521 .landing-compare-hl .landing-compare-us {
2522 color: var(--accent);
2523 font-weight: 600;
2b821b7Claude2524 }
958d26aClaude2525 .landing-compare-hl .landing-compare-feature::after {
2526 content: 'NEW';
2527 margin-left: 8px;
2528 padding: 1px 6px;
2529 border-radius: 4px;
2530 background: var(--accent-gradient-faint);
2531 color: var(--accent);
2532 font-family: var(--font-mono);
2533 font-size: 9px;
2534 letter-spacing: 0.1em;
2535 font-weight: 600;
2536 vertical-align: 1px;
2537 }
2538 @media (max-width: 720px) {
2539 .landing-compare-row { grid-template-columns: 1fr 80px 80px; padding: 12px 14px; }
2540 .landing-compare-hl .landing-compare-feature::after { display: none; }
2541 }
2542
2543 /* ---------- Pricing ---------- */
2544 .landing-pricing {
2545 display: grid;
2546 grid-template-columns: repeat(3, 1fr);
2547 gap: 16px;
2548 max-width: 1080px;
2549 margin: 0 auto;
2550 align-items: stretch;
2551 }
2552 .landing-price-card {
2553 position: relative;
2554 background: var(--bg-elevated);
2555 border: 1px solid var(--border);
2556 border-radius: var(--r-lg);
2557 padding: var(--s-7);
2558 display: flex;
2559 flex-direction: column;
2560 gap: var(--s-4);
2561 transition: border-color var(--t-base) var(--ease), transform var(--t-base) var(--ease);
2562 }
2563 .landing-price-card:hover {
2564 border-color: var(--border-strong);
2565 transform: translateY(-2px);
2566 }
2567 .landing-price-hl {
2568 border-color: rgba(140,109,255,0.35);
2569 box-shadow: var(--elev-2), 0 0 0 1px rgba(140,109,255,0.25);
2570 background:
2571 linear-gradient(180deg, rgba(140,109,255,0.05), transparent 50%),
2572 var(--bg-elevated);
2573 }
2574 .landing-price-hl:hover { border-color: rgba(140,109,255,0.55); }
2575 .landing-price-badge {
2576 position: absolute;
2577 top: -10px;
2578 left: 50%;
2579 transform: translateX(-50%);
2580 padding: 3px 12px;
2581 background: var(--accent-gradient);
2582 color: #fff;
2583 font-family: var(--font-mono);
2584 font-size: 10px;
2585 letter-spacing: 0.1em;
2586 text-transform: uppercase;
2587 font-weight: 600;
2588 border-radius: var(--r-full);
2589 box-shadow: 0 4px 12px -2px rgba(140,109,255,0.4);
2590 }
2591 .landing-price-tier {
2592 font-family: var(--font-mono);
2593 font-size: 11px;
2594 text-transform: uppercase;
2595 letter-spacing: 0.16em;
2596 color: var(--text-muted);
2597 }
2598 .landing-price-amount {
2599 display: flex;
2600 align-items: baseline;
2601 gap: 8px;
2602 }
2603 .landing-price-num {
2604 font-family: var(--font-display);
2605 font-size: 40px;
2606 font-weight: 600;
2607 letter-spacing: -0.03em;
2608 color: var(--text-strong);
2609 }
2610 .landing-price-cad {
2611 font-size: var(--t-sm);
2612 color: var(--text-faint);
2613 }
2614 .landing-price-desc {
2615 font-size: var(--t-sm);
2616 color: var(--text-muted);
2617 line-height: 1.55;
2618 margin: 0;
2619 }
2620 .landing-price-features {
2621 list-style: none;
2622 padding: 0;
2623 margin: 0;
2624 display: flex;
2625 flex-direction: column;
2626 gap: 8px;
2627 font-size: var(--t-sm);
2628 color: var(--text);
2629 }
2630 .landing-price-features li {
2631 display: flex;
2632 align-items: center;
2633 gap: 9px;
2634 }
2635 .landing-price-check {
2636 color: var(--accent);
2637 font-weight: 600;
4c47454Claude2638 flex-shrink: 0;
2b821b7Claude2639 }
958d26aClaude2640 .landing-price-cta { margin-top: auto; }
2641
2642 /* ---------- Closing CTA ---------- */
2643 .landing-cta-section { margin: var(--s-20) auto var(--s-16); }
2644 .landing-cta-card {
2645 position: relative;
2646 text-align: center;
2647 padding: var(--s-16) var(--s-7);
2648 border: 1px solid var(--border-strong);
2649 border-radius: var(--r-2xl);
2650 background: var(--bg-elevated);
2651 overflow: hidden;
2652 isolation: isolate;
2653 }
2654 .landing-cta-bg {
2655 position: absolute;
2656 inset: 0;
2657 z-index: -1;
2658 background:
2659 radial-gradient(60% 100% at 50% 0%, rgba(140,109,255,0.16), transparent 65%),
2660 radial-gradient(40% 80% at 80% 100%, rgba(54,197,214,0.10), transparent 65%);
2661 }
2662 .landing-cta-card::after {
2663 content: '';
2664 position: absolute;
2665 inset: 0;
2666 z-index: -1;
2667 background-image: radial-gradient(rgba(255,255,255,0.04) 1px, transparent 1px);
2668 background-size: 24px 24px;
2669 mask-image: radial-gradient(ellipse at center, #000 0%, transparent 65%);
2670 -webkit-mask-image: radial-gradient(ellipse at center, #000 0%, transparent 65%);
2671 opacity: 0.6;
2672 }
2673 :root[data-theme='light'] .landing-cta-card::after {
2674 background-image: radial-gradient(rgba(15,16,28,0.07) 1px, transparent 1px);
2675 }
2676 .landing-cta-card .eyebrow { justify-content: center; }
2677 .landing-cta-title {
2678 font-family: var(--font-display);
2679 font-size: clamp(28px, 4.4vw, 56px);
2680 line-height: 1.05;
2681 letter-spacing: -0.03em;
2682 font-weight: 600;
2683 margin: var(--s-3) 0 var(--s-4);
2684 color: var(--text-strong);
2685 }
2686 .landing-cta-sub {
2687 font-size: var(--t-md);
2688 color: var(--text-muted);
2689 max-width: 560px;
2690 margin: 0 auto var(--s-8);
2691 line-height: 1.55;
2692 }
2693 .landing-cta-buttons {
2694 display: flex;
2695 gap: 12px;
2696 justify-content: center;
2697 flex-wrap: wrap;
2698 }
2b821b7Claude2699
2700 /* ---------- Responsive ---------- */
958d26aClaude2701 @media (max-width: 960px) {
2702 .landing-features { grid-template-columns: repeat(2, 1fr); }
2703 .landing-walk-grid { grid-template-columns: repeat(2, 1fr); }
2704 .landing-walk-step::after { display: none; }
2705 .landing-pricing { grid-template-columns: 1fr; max-width: 480px; }
2706 }
2707 @media (max-width: 640px) {
2708 .landing-hero { padding: var(--s-14) 0 var(--s-10); }
2709 .landing-hero-cmd { flex-wrap: wrap; justify-content: center; }
2710 .landing-hero-ctas { flex-direction: column; align-items: stretch; }
2711 .landing-hero-ctas .btn { width: 100%; justify-content: center; }
2712 .landing-features { grid-template-columns: 1fr; }
2713 .landing-walk-grid { grid-template-columns: 1fr; }
2714 .landing-section { margin: var(--s-12) auto; }
2715 .landing-cta-card { padding: var(--s-10) var(--s-5); }
2716 .landing-cta-buttons .btn { width: 100%; justify-content: center; }
2b821b7Claude2717 }
52ad8b1Claude2718
2719 /* ---------- L4 social-proof counters ---------- */
2720 .landing-counters {
2721 margin: var(--s-10) auto var(--s-12);
2722 max-width: 1180px;
2723 padding: 0 var(--s-4);
2724 }
2725 .landing-counters-grid {
2726 display: grid;
2727 grid-template-columns: repeat(6, 1fr);
2728 gap: 20px;
2729 text-align: left;
2730 }
2731 .landing-counter {
2732 padding: var(--s-3) 0;
2733 border-top: 1px solid var(--border-subtle);
2734 }
2735 .landing-counter-num {
2736 font-family: var(--font-display);
2737 font-size: clamp(24px, 3vw, 38px);
2738 line-height: 1.05;
2739 letter-spacing: -0.03em;
2740 font-weight: 700;
2741 margin-bottom: 6px;
2742 font-feature-settings: 'tnum';
2743 background-image: var(--accent-gradient);
2744 -webkit-background-clip: text;
2745 background-clip: text;
2746 -webkit-text-fill-color: transparent;
2747 color: transparent;
2748 }
2749 .landing-counter-label {
2750 font-family: var(--font-mono);
2751 font-size: 10.5px;
2752 font-weight: 500;
2753 text-transform: uppercase;
2754 letter-spacing: 0.12em;
2755 color: var(--text-faint);
2756 line-height: 1.4;
2757 }
2758 @media (max-width: 960px) {
2759 .landing-counters-grid { grid-template-columns: repeat(3, 1fr); gap: 20px 16px; }
2760 }
2761 @media (max-width: 540px) {
2762 .landing-counters-grid { grid-template-columns: repeat(2, 1fr); gap: 18px 12px; }
2763 }
5f2e749Claude2764
dc26881CC LABS App2765 /* ---------- L10/U1 hero install snippet ----------
2766 U1: wrapped in a labelled "power users" panel and re-located
2767 beneath the CTA + tertiary rows so it no longer competes with
2768 the primary calls to action. */
2769 .landing-hero-install-wrap {
2770 margin: 0 auto var(--space-6);
2771 text-align: center;
2772 }
2773 .landing-hero-install-label {
2774 display: inline-block;
2775 margin-bottom: var(--space-2);
2776 font-family: var(--font-mono);
2777 font-size: 11px;
2778 letter-spacing: 0.08em;
2779 text-transform: uppercase;
2780 color: var(--text-faint);
2781 }
5f2e749Claude2782 .landing-hero-install {
2783 display: inline-flex;
2784 align-items: stretch;
2785 gap: 0;
dc26881CC LABS App2786 margin: 0 auto;
5f2e749Claude2787 background: var(--bg-elevated);
2788 border: 1px solid var(--border-strong);
2789 border-radius: var(--r);
2790 box-shadow: var(--elev-1);
2791 overflow: hidden;
2792 max-width: 100%;
2793 font-family: var(--font-mono);
2794 }
2795 .landing-hero-install-code {
2796 display: inline-flex;
2797 align-items: center;
2798 gap: 10px;
2799 padding: 10px 14px;
2800 font-size: 13.5px;
2801 color: var(--text-strong);
2802 background: transparent;
2803 border: 0;
2804 white-space: nowrap;
2805 overflow-x: auto;
2806 }
2807 .landing-hero-install-prompt {
2808 color: var(--accent);
2809 user-select: none;
2810 }
2811 .landing-hero-install-copy {
2812 appearance: none;
2813 border: 0;
2814 border-left: 1px solid var(--border);
2815 background: transparent;
2816 color: var(--text-muted);
2817 font-family: var(--font-mono);
2818 font-size: 12px;
2819 font-weight: 600;
2820 letter-spacing: 0.06em;
2821 text-transform: uppercase;
2822 padding: 0 16px;
2823 cursor: pointer;
2824 transition: background var(--t-fast) var(--ease), color var(--t-fast) var(--ease);
2825 }
2826 .landing-hero-install-copy:hover {
2827 background: var(--accent-gradient-faint);
2828 color: var(--accent);
2829 }
2830 .landing-hero-install-copy[data-copied="1"] {
2831 color: var(--green, #34d399);
2832 }
2833
dc26881CC LABS App2834 /* ---------- L10/U1 hero "what just happened" rail ----------
2835 U1 — tightened into a single horizontal strip. The 1px gradient
2836 rule on top is the same accent the headline uses, so the rail
2837 reads as part of the hero composition rather than a stray list. */
5f2e749Claude2838 .landing-hero-rail {
2839 list-style: none;
dc26881CC LABS App2840 padding: var(--space-4) 0 0;
2841 margin: 0 auto var(--space-6);
5f2e749Claude2842 display: flex;
2843 flex-wrap: wrap;
2844 justify-content: center;
dc26881CC LABS App2845 gap: var(--space-2) var(--space-6);
5f2e749Claude2846 font-family: var(--font-sans);
dc26881CC LABS App2847 font-size: 12px;
2848 color: var(--text-faint);
5f2e749Claude2849 max-width: 760px;
dc26881CC LABS App2850 position: relative;
2851 }
2852 .landing-hero-rail::before {
2853 content: '';
2854 position: absolute;
2855 top: 0;
2856 left: 50%;
2857 transform: translateX(-50%);
2858 width: 120px;
2859 height: 1px;
2860 background: var(--accent-gradient);
2861 opacity: 0.45;
2862 border-radius: 9999px;
5f2e749Claude2863 }
2864 .landing-hero-rail li {
2865 display: inline-flex;
dc26881CC LABS App2866 align-items: baseline;
2867 gap: var(--space-2);
5f2e749Claude2868 line-height: 1.4;
2869 }
2870 .landing-hero-rail strong {
2871 color: var(--text-strong);
2872 font-weight: 600;
2873 font-feature-settings: 'tnum';
dc26881CC LABS App2874 font-size: 14px;
2875 letter-spacing: -0.01em;
5f2e749Claude2876 }
dc26881CC LABS App2877 .landing-hero-rail-label {
2878 color: var(--text-muted);
2879 letter-spacing: 0.01em;
5f2e749Claude2880 }
dc26881CC LABS App2881 /* Backwards-compat: nothing references this any more but if a stale
2882 fragment lingers it's still hidden cleanly rather than orphaned. */
2883 .landing-hero-rail-check { display: none; }
5f2e749Claude2884
2885 /* ---------- L10 three-reasons section ---------- */
2886 .landing-reasons { margin-top: var(--s-12); }
2887 .landing-reasons-grid {
2888 display: grid;
2889 grid-template-columns: repeat(3, 1fr);
2890 gap: 16px;
2891 }
2892 .landing-reason {
2893 background: var(--bg-elevated);
2894 border: 1px solid var(--border);
2895 border-radius: var(--r-lg);
2896 padding: var(--s-7);
2897 display: flex;
2898 flex-direction: column;
2899 gap: var(--s-3);
2900 transition: border-color var(--t-base) var(--ease), transform var(--t-base) var(--ease);
2901 }
2902 .landing-reason:hover {
2903 border-color: var(--border-strong);
2904 transform: translateY(-2px);
2905 }
2906 .landing-reason-icon {
2907 display: inline-flex;
2908 align-items: center;
2909 justify-content: center;
2910 width: 40px;
2911 height: 40px;
2912 border-radius: var(--r);
2913 background: var(--accent-gradient-soft);
2914 color: var(--accent);
2915 border: 1px solid rgba(140,109,255,0.20);
2916 }
2917 .landing-reason-title {
2918 font-family: var(--font-display);
2919 font-size: 20px;
2920 font-weight: 600;
2921 letter-spacing: -0.02em;
2922 margin: 0;
2923 color: var(--text-strong);
2924 }
2925 .landing-reason-body {
2926 font-size: var(--t-sm);
2927 color: var(--text-muted);
2928 line-height: 1.55;
2929 margin: 0;
2930 }
2931 .landing-reasons-code {
2932 display: block;
2933 font-family: var(--font-mono);
2934 font-size: 12px;
2935 color: var(--text-strong);
2936 background: var(--bg);
2937 border: 1px solid var(--border);
2938 border-radius: var(--r);
2939 padding: 8px 12px;
2940 overflow-x: auto;
2941 white-space: nowrap;
2942 }
2943 .landing-reason-link {
2944 margin-top: auto;
2945 display: inline-flex;
2946 align-items: center;
2947 gap: 6px;
2948 color: var(--accent);
2949 font-size: var(--t-sm);
2950 font-weight: 500;
2951 text-decoration: none;
2952 }
2953 .landing-reason-link:hover { text-decoration: underline; }
2954 @media (max-width: 960px) {
2955 .landing-reasons-grid { grid-template-columns: 1fr; max-width: 520px; margin: 0 auto; }
2956 }
2957
2958 /* ---------- L10 "How is this different" pull-quote ---------- */
2959 .landing-pullquote-section {
2960 margin: var(--s-20) auto var(--s-12);
2961 max-width: 920px;
2962 padding: 0 var(--s-4);
2963 text-align: center;
2964 }
2965 .landing-pullquote {
2966 margin: 0;
2967 padding: var(--s-10) var(--s-7);
2968 background:
2969 radial-gradient(80% 100% at 50% 0%, rgba(140,109,255,0.10), transparent 65%),
2970 var(--bg-elevated);
2971 border: 1px solid var(--border-strong);
2972 border-radius: var(--r-xl);
2973 position: relative;
2974 overflow: hidden;
2975 }
2976 .landing-pullquote-eyebrow {
2977 font-family: var(--font-mono);
2978 font-size: 11px;
2979 font-weight: 600;
2980 letter-spacing: 0.16em;
2981 text-transform: uppercase;
2982 color: var(--accent);
2983 margin-bottom: var(--s-4);
2984 }
2985 .landing-pullquote-text {
2986 font-family: var(--font-display);
2987 font-size: clamp(20px, 2.4vw, 28px);
2988 line-height: 1.4;
2989 letter-spacing: -0.018em;
2990 color: var(--text-strong);
2991 margin: 0 auto;
2992 max-width: 760px;
2993 quotes: "\\201C" "\\201D";
2994 }
2995 .landing-pullquote-text::before { content: open-quote; color: var(--accent); margin-right: 4px; }
2996 .landing-pullquote-text::after { content: close-quote; color: var(--accent); margin-left: 4px; }
2997 .landing-pullquote-link {
2998 display: inline-flex;
2999 align-items: center;
3000 gap: 6px;
3001 margin-top: var(--s-6);
3002 color: var(--accent);
3003 font-size: var(--t-sm);
3004 font-weight: 500;
3005 text-decoration: none;
3006 }
3007 .landing-pullquote-link:hover { text-decoration: underline; }
3008
3009 /* ---------- L10 hero responsive overrides ---------- */
3010 @media (max-width: 640px) {
3011 .landing-hero-install { width: 100%; }
3012 .landing-hero-install-code { flex: 1; font-size: 12px; }
3013 .landing-hero-rail { flex-direction: column; align-items: flex-start; gap: 6px; padding: 0 var(--s-3); }
3014 .landing-hero-rail li { width: 100%; }
3015 }
534f04aClaude3016
3017 /* ============================================================ */
3018 /* Block M1 — Live-now demo feed */
3019 /* ============================================================ */
3020 .landing-livenow {
3021 margin: var(--s-8) 0 var(--s-6);
3022 padding: var(--s-6) 0 var(--s-4);
3023 }
3024 .landing-livenow-head {
3025 text-align: center;
3026 margin-bottom: var(--s-6);
3027 }
3028 .landing-livenow-eyebrow {
3029 display: inline-flex;
3030 align-items: center;
3031 gap: 8px;
3032 padding: 4px 12px;
3033 border-radius: var(--r-full);
3034 background: rgba(52,211,153,0.08);
3035 border: 1px solid rgba(52,211,153,0.25);
3036 color: var(--green);
3037 font-family: var(--font-mono, ui-monospace, monospace);
3038 font-size: 11px;
3039 letter-spacing: 0.06em;
3040 text-transform: uppercase;
3041 margin-bottom: var(--s-3);
3042 }
3043 .landing-livenow-pulse {
3044 width: 8px; height: 8px;
3045 border-radius: 50%;
3046 background: var(--green);
3047 box-shadow: 0 0 0 0 rgba(52,211,153,0.6);
3048 animation: landing-livenow-pulse 1.6s ease-out infinite;
3049 }
3050 @keyframes landing-livenow-pulse {
3051 0% { box-shadow: 0 0 0 0 rgba(52,211,153,0.55); transform: scale(1); }
3052 70% { box-shadow: 0 0 0 10px rgba(52,211,153,0); transform: scale(1.05); }
3053 100% { box-shadow: 0 0 0 0 rgba(52,211,153,0); transform: scale(1); }
3054 }
3055 .landing-livenow-title {
3056 font-size: 22px;
3057 line-height: 1.25;
3058 margin: 0 auto;
3059 max-width: 720px;
3060 color: var(--text-strong);
3061 font-weight: 600;
3062 letter-spacing: -0.01em;
3063 }
3064 .landing-livenow-sub {
3065 margin: var(--s-2) auto 0;
3066 color: var(--text-muted);
3067 font-size: 13px;
3068 max-width: 560px;
3069 }
3070 .landing-livenow-sub code {
3071 background: rgba(255,255,255,0.05);
3072 border: 1px solid var(--border);
3073 padding: 1px 6px;
3074 border-radius: 4px;
3075 font-size: 12px;
3076 color: var(--accent);
3077 }
3078
3079 .landing-livenow-grid {
3080 display: grid;
3081 grid-template-columns: repeat(2, minmax(0, 1fr));
3082 gap: var(--s-3);
3083 }
3084 @media (min-width: 980px) {
3085 .landing-livenow-grid {
3086 grid-template-columns: repeat(4, minmax(0, 1fr));
3087 }
3088 }
3089
3090 .landing-livecard {
3091 background: linear-gradient(180deg, rgba(255,255,255,0.025), rgba(255,255,255,0.005));
3092 border: 1px solid var(--border);
3093 border-radius: var(--r-md, 10px);
3094 padding: 14px 14px 12px;
3095 display: flex;
3096 flex-direction: column;
3097 min-height: 180px;
3098 position: relative;
3099 overflow: hidden;
3100 }
3101 .landing-livecard::before {
3102 content: '';
3103 position: absolute;
3104 inset: 0;
3105 background: var(--accent-gradient-faint);
3106 opacity: 0;
3107 transition: opacity 200ms var(--ease, ease);
3108 pointer-events: none;
3109 }
3110 .landing-livecard:hover::before { opacity: 1; }
3111
3112 .landing-livecard-head {
3113 display: flex;
3114 align-items: center;
3115 gap: 8px;
3116 margin-bottom: 10px;
3117 }
3118 .landing-livecard-dot {
3119 width: 7px; height: 7px;
3120 border-radius: 50%;
3121 background: var(--green);
3122 box-shadow: 0 0 8px rgba(52,211,153,0.55);
3123 animation: landing-livenow-pulse 1.8s ease-out infinite;
3124 flex-shrink: 0;
3125 }
3126 .landing-livecard-title {
3127 margin: 0;
3128 font-size: 12px;
3129 font-weight: 600;
3130 text-transform: uppercase;
3131 letter-spacing: 0.06em;
3132 color: var(--text-muted);
3133 }
3134
3135 .landing-livecard-bignum {
3136 display: flex;
3137 align-items: baseline;
3138 gap: 8px;
3139 margin: 4px 0 10px;
3140 }
3141 .landing-livecard-bignum-n {
3142 font-size: 30px;
3143 font-weight: 700;
3144 color: var(--text-strong);
3145 font-variant-numeric: tabular-nums;
3146 background: var(--accent-gradient);
3147 -webkit-background-clip: text;
3148 background-clip: text;
3149 -webkit-text-fill-color: transparent;
3150 color: transparent;
3151 }
3152 .landing-livecard-bignum-label {
3153 font-size: 12px;
3154 color: var(--text-muted);
3155 }
3156
3157 .landing-livecard-list {
3158 list-style: none;
3159 margin: 0;
3160 padding: 0;
3161 font-size: 13px;
3162 line-height: 1.45;
3163 flex: 1;
3164 }
3165 .landing-livecard-row, .landing-livecard-feedrow {
3166 padding: 6px 0;
3167 border-bottom: 1px dashed rgba(255,255,255,0.05);
3168 transition: background-color 1s var(--ease, ease);
3169 border-radius: 4px;
3170 margin: 0 -4px;
3171 padding-left: 4px;
3172 padding-right: 4px;
3173 }
3174 .landing-livecard-row:last-child,
3175 .landing-livecard-feedrow:last-child { border-bottom: 0; }
3176 .landing-livecard-feedrow { padding: 4px; font-size: 12.5px; }
3177
3178 .landing-livecard-flash {
3179 background-color: rgba(52,211,153,0.18) !important;
3180 animation: landing-livecard-flash-fade 1.1s ease-out forwards;
3181 }
3182 @keyframes landing-livecard-flash-fade {
3183 0% { background-color: rgba(52,211,153,0.22); }
3184 100% { background-color: rgba(52,211,153,0); }
3185 }
3186
3187 .landing-livecard-link {
3188 color: var(--text-strong);
3189 text-decoration: none;
3190 display: inline-block;
3191 max-width: 100%;
3192 overflow: hidden;
3193 text-overflow: ellipsis;
3194 white-space: nowrap;
3195 }
3196 .landing-livecard-link:hover { color: var(--accent); }
3197 .landing-livecard-num {
3198 font-family: var(--font-mono, ui-monospace, monospace);
3199 color: var(--text-faint);
3200 font-weight: 500;
3201 font-size: 12px;
3202 }
3203 .landing-livecard-title-text { color: var(--text-strong); }
3204 .landing-livecard-snippet {
3205 color: var(--text-muted);
3206 font-style: italic;
3207 font-size: 12px;
3208 }
3209 .landing-livecard-meta {
3210 font-size: 11px;
3211 color: var(--text-faint);
3212 margin-top: 2px;
3213 font-family: var(--font-mono, ui-monospace, monospace);
3214 }
3215 .landing-livecard-repo {
3216 color: var(--accent-2);
3217 }
3218 .landing-livecard-rel {
3219 color: var(--text-muted);
3220 }
3221 .landing-livecard-empty {
3222 color: var(--text-faint);
3223 font-size: 12px;
3224 font-style: italic;
3225 padding: 8px 0;
3226 }
3227 .landing-livecard-kind {
3228 display: inline-block;
3229 padding: 1px 7px;
3230 border-radius: var(--r-full);
3231 font-family: var(--font-mono, ui-monospace, monospace);
3232 font-size: 10px;
3233 letter-spacing: 0.04em;
3234 text-transform: uppercase;
3235 font-weight: 600;
3236 }
3237 .landing-livecard-kind-auto_merge-merged,
3238 .landing-livecard-kind-auto-merge-merged {
3239 background: rgba(52,211,153,0.12);
3240 color: var(--green);
3241 border: 1px solid rgba(52,211,153,0.25);
3242 }
3243 .landing-livecard-kind-ai_build-dispatched,
3244 .landing-livecard-kind-ai-build-dispatched {
3245 background: rgba(140,109,255,0.12);
3246 color: var(--accent);
3247 border: 1px solid rgba(140,109,255,0.30);
3248 }
3249 .landing-livecard-kind-ai_review-posted,
3250 .landing-livecard-kind-ai-review-posted {
3251 background: rgba(54,197,214,0.12);
3252 color: var(--accent-2);
3253 border: 1px solid rgba(54,197,214,0.30);
3254 }
3255
3256 .landing-livenow-cta {
3257 margin-top: var(--s-5);
3258 display: flex;
3259 flex-wrap: wrap;
3260 align-items: center;
3261 justify-content: center;
3262 gap: 10px;
3263 font-size: 13px;
3264 color: var(--text-muted);
3265 }
3266 .landing-livenow-cta-link {
3267 color: var(--accent);
3268 text-decoration: none;
3269 font-weight: 500;
3270 display: inline-flex;
3271 align-items: center;
3272 gap: 4px;
3273 }
3274 .landing-livenow-cta-link:hover { color: var(--accent-hover); }
3275 .landing-livenow-cta-sep { color: var(--text-faint); }
3276
3277 @media (prefers-reduced-motion: reduce) {
3278 .landing-livenow-pulse,
3279 .landing-livecard-dot { animation: none; }
3280 .landing-livecard-flash { animation: none; background-color: transparent !important; }
3281 }
52ad8b1Claude3282`;
3283
3284/**
3285 * Block L4 — count-up animation.
3286 *
3287 * Reads each `[data-counter-target]` and animates the in-DOM text from
3288 * 0 → target over ~1.2s when the element first scrolls into view.
3289 *
3290 * Render-once semantics: each tile already contains the final value as
3291 * HTML, so visitors with JS disabled — or anyone before the script
3292 * loads — sees the correct number. The script just animates the text.
3293 *
3294 * Falls back to the static value (no animation) when IntersectionObserver
3295 * isn't available, or when the user prefers reduced motion.
3296 */
3297const landingCountersJs = `
3298(function(){
3299 try {
3300 var els = document.querySelectorAll('[data-counter-target]');
3301 if (!els.length) return;
3302 var reduced = window.matchMedia && window.matchMedia('(prefers-reduced-motion: reduce)').matches;
3303 if (reduced || typeof IntersectionObserver !== 'function') return;
3304
3305 function animate(el) {
3306 var target = parseInt(el.getAttribute('data-counter-target') || '0', 10);
3307 if (!isFinite(target) || target <= 0) return;
3308 var prefix = el.getAttribute('data-counter-prefix') || '';
3309 var suffix = el.getAttribute('data-counter-suffix') || '';
3310 var duration = 1200;
3311 var start = performance.now();
3312 function frame(now) {
3313 var t = Math.min(1, (now - start) / duration);
3314 // ease-out cubic
3315 var eased = 1 - Math.pow(1 - t, 3);
3316 var v = Math.floor(eased * target);
3317 el.textContent = prefix + v.toLocaleString() + suffix;
3318 if (t < 1) requestAnimationFrame(frame);
3319 else el.textContent = prefix + target.toLocaleString() + suffix;
3320 }
3321 // Reset to zero before animating in.
3322 el.textContent = prefix + '0' + suffix;
3323 requestAnimationFrame(frame);
3324 }
3325
3326 var io = new IntersectionObserver(function(entries) {
3327 entries.forEach(function(entry){
3328 if (entry.isIntersecting) {
3329 animate(entry.target);
3330 io.unobserve(entry.target);
3331 }
3332 });
3333 }, { threshold: 0.4 });
3334 els.forEach(function(el){ io.observe(el); });
3335 } catch (_) { /* swallow — static numbers remain */ }
3336})();
2b821b7Claude3337`;
5f2e749Claude3338
3339/**
3340 * Block L10 — clipboard copy for the hero install snippet.
3341 *
3342 * Pure progressive enhancement. Without JS the user can still
3343 * triple-click + Cmd/Ctrl-C the snippet — the button is the
3344 * speed-bump, not the only path.
3345 */
3346const landingCopyJs = `
3347(function(){
3348 try {
3349 var btns = document.querySelectorAll('[data-copy-target]');
3350 if (!btns.length) return;
3351 btns.forEach(function(btn){
3352 btn.addEventListener('click', function(){
3353 var id = btn.getAttribute('data-copy-target') || '';
3354 var src = document.getElementById(id);
3355 if (!src) return;
3356 var text = src.textContent || '';
3357 var done = function(){
3358 var prev = btn.textContent;
3359 btn.textContent = 'Copied';
3360 btn.setAttribute('data-copied', '1');
3361 setTimeout(function(){
3362 btn.textContent = prev || 'Copy';
3363 btn.removeAttribute('data-copied');
3364 }, 1500);
3365 };
3366 if (navigator.clipboard && navigator.clipboard.writeText) {
3367 navigator.clipboard.writeText(text).then(done, function(){ done(); });
3368 } else {
3369 // Legacy fallback — temp textarea + execCommand.
3370 try {
3371 var ta = document.createElement('textarea');
3372 ta.value = text;
3373 ta.style.position = 'fixed';
3374 ta.style.opacity = '0';
3375 document.body.appendChild(ta);
3376 ta.select();
3377 document.execCommand('copy');
3378 document.body.removeChild(ta);
3379 done();
3380 } catch (_) {}
3381 }
3382 });
3383 });
3384 } catch (_) { /* swallow */ }
3385})();
3386`;
0c3eee5Claude3387
3388// ============================================================
3389// land2030Css — scoped CSS for the 2030 homepage prelude.
3390// Every selector is prefixed `.land-2030-*` so it can't bleed
3391// into the older `.landing-*` (L10/U1/Q1/M1) styles below.
3392// ============================================================
3393const land2030Css = `
3394 .land-2030-root {
3395 position: relative;
3396 max-width: 1240px;
3397 margin: 0 auto;
3398 padding: 0 16px var(--space-6, 32px);
3399 color: var(--text, #e6e6f0);
3400 }
3401
3402 /* ---------- Hero ---------- */
3403 .land-2030-hero {
3404 position: relative;
3405 padding: clamp(48px, 8vw, 120px) 8px clamp(40px, 6vw, 80px);
3406 text-align: center;
3407 overflow: hidden;
3408 }
3409 .land-2030-hairline {
3410 position: absolute;
3411 top: 0; left: 0; right: 0;
3412 height: 2px;
3413 background: linear-gradient(90deg, transparent 0%, #8c6dff 30%, #36c5d6 70%, transparent 100%);
3414 opacity: 0.75;
3415 pointer-events: none;
3416 }
3417 .land-2030-orb {
3418 position: absolute;
3419 top: -10%;
3420 left: 50%;
3421 width: 720px;
3422 height: 720px;
3423 transform: translateX(-50%);
3424 background: radial-gradient(circle, rgba(140,109,255,0.30), rgba(54,197,214,0.16) 40%, transparent 70%);
3425 filter: blur(90px);
3426 pointer-events: none;
3427 z-index: 0;
3428 }
3429 .land-2030-hero-inner {
3430 position: relative;
3431 z-index: 1;
3432 max-width: 1080px;
3433 margin: 0 auto;
3434 }
3435 .land-2030-eyebrow {
3436 display: inline-flex;
3437 align-items: center;
3438 gap: 8px;
3439 padding: 6px 12px;
3440 border-radius: 999px;
3441 background: rgba(140,109,255,0.10);
3442 color: #cbb7ff;
3443 font-size: 12px;
3444 letter-spacing: 0.04em;
3445 text-transform: uppercase;
3446 border: 1px solid rgba(140,109,255,0.30);
3447 margin-bottom: 24px;
3448 }
3449 .land-2030-eyebrow-mini {
3450 margin-bottom: 12px;
3451 }
3452 .land-2030-pulse {
3453 width: 8px; height: 8px;
3454 border-radius: 50%;
3455 background: #8c6dff;
3456 box-shadow: 0 0 12px rgba(140,109,255,0.8);
3457 animation: land2030-pulse 2s ease-in-out infinite;
3458 }
3459 @keyframes land2030-pulse {
3460 0%, 100% { opacity: 0.5; transform: scale(1); }
3461 50% { opacity: 1; transform: scale(1.25); }
3462 }
3463 .land-2030-display {
3464 font-size: clamp(60px, 9vw, 96px);
3465 font-family: var(--font-display, inherit);
3466 font-weight: 800;
3467 line-height: 1.02;
3468 letter-spacing: -0.035em;
3469 margin: 0 auto 24px;
3470 max-width: 1040px;
3471 }
3472 .land-2030-display span { background-clip: text; -webkit-background-clip: text; -webkit-text-fill-color: transparent; color: transparent; }
3473 .land-2030-grad-1 { background-image: linear-gradient(135deg, #a48bff 0%, #8c6dff 100%); }
3474 .land-2030-grad-2 { background-image: linear-gradient(135deg, #c4b1ff 0%, #36c5d6 100%); }
3475 .land-2030-grad-3 { background-image: linear-gradient(135deg, #36c5d6 0%, #5be0a9 100%); }
3476 .land-2030-grad-4 { background-image: linear-gradient(135deg, #5be0a9 0%, #ffd16b 100%); }
3477 .land-2030-grad-5 { background-image: linear-gradient(135deg, #ffd16b 0%, #ff6bd1 50%, #8c6dff 100%); }
3478 .land-2030-sub {
3479 font-size: clamp(17px, 1.6vw, 22px);
3480 color: var(--text-muted, #a0a0b8);
3481 max-width: 720px;
3482 margin: 0 auto 36px;
3483 line-height: 1.45;
3484 }
3485 .land-2030-cta-row {
3486 display: flex;
3487 flex-wrap: wrap;
3488 justify-content: center;
3489 gap: 12px;
3490 }
3491 .land-2030-cta-primary,
3492 .land-2030-cta-secondary {
3493 min-width: 200px;
3494 }
3495
3496 /* ---------- Section scaffold ---------- */
3497 .land-2030-section {
3498 margin: clamp(56px, 8vw, 96px) 0;
3499 position: relative;
3500 }
3501 .land-2030-section-dark {
3502 padding: 48px 32px;
3503 background: linear-gradient(180deg, rgba(15,17,26,0.6), rgba(8,9,15,0.6));
3504 border: 1px solid rgba(140,109,255,0.18);
3505 border-radius: 20px;
3506 }
3507 .land-2030-section-head {
3508 text-align: center;
3509 max-width: 760px;
3510 margin: 0 auto 40px;
3511 }
3512 .land-2030-h2 {
3513 font-size: clamp(28px, 4vw, 44px);
3514 font-family: var(--font-display, inherit);
3515 font-weight: 700;
3516 letter-spacing: -0.025em;
3517 line-height: 1.1;
3518 margin: 0 0 12px;
3519 }
3520 .land-2030-lede {
3521 font-size: 16px;
3522 color: var(--text-muted, #a0a0b8);
3523 line-height: 1.55;
3524 margin: 0;
3525 }
3526
3527 /* ---------- Closed loop diagram ---------- */
3528 .land-2030-loop-wrap {
3529 max-width: 1100px;
3530 margin: 0 auto;
3531 padding: 16px;
3532 background: rgba(255,255,255,0.02);
3533 border: 1px solid rgba(140,109,255,0.16);
3534 border-radius: 20px;
3535 color: var(--text-muted, #a0a0b8);
3536 }
3537 .land-2030-loop-svg {
3538 display: block;
3539 width: 100%;
3540 height: auto;
3541 }
3542 .land-2030-loop-path {
3543 stroke-dasharray: 6 4;
3544 animation: land2030-loop-dash 30s linear infinite;
3545 }
3546 @keyframes land2030-loop-dash {
3547 from { stroke-dashoffset: 0; }
3548 to { stroke-dashoffset: -400; }
3549 }
3550 .land-2030-loop-text {
3551 font-size: 14px;
3552 font-weight: 600;
3553 letter-spacing: -0.005em;
3554 }
3555
3556 /* ---------- 6-card "unfair advantages" grid ---------- */
3557 .land-2030-card-grid {
3558 display: grid;
3559 grid-template-columns: repeat(3, minmax(0, 1fr));
3560 gap: 16px;
3561 max-width: 1180px;
3562 margin: 0 auto;
3563 }
3564 .land-2030-card {
3565 display: flex;
3566 flex-direction: column;
3567 gap: 10px;
3568 padding: 24px;
3569 background: linear-gradient(180deg, rgba(255,255,255,0.04), rgba(255,255,255,0.015));
3570 border: 1px solid rgba(255,255,255,0.08);
3571 border-radius: 16px;
3572 text-decoration: none;
3573 color: inherit;
3574 transition: transform 200ms ease, border-color 200ms ease, box-shadow 200ms ease;
3575 }
3576 .land-2030-card:hover {
3577 transform: translateY(-2px);
3578 border-color: rgba(140,109,255,0.45);
3579 box-shadow: 0 8px 30px -8px rgba(140,109,255,0.40);
3580 }
3581 .land-2030-card-icon {
3582 width: 44px; height: 44px;
3583 display: inline-flex;
3584 align-items: center;
3585 justify-content: center;
3586 border-radius: 12px;
3587 background: rgba(140,109,255,0.12);
3588 color: #cbb7ff;
3589 border: 1px solid rgba(140,109,255,0.30);
3590 margin-bottom: 4px;
3591 }
3592 .land-2030-card-title {
3593 margin: 0;
3594 font-size: 18px;
3595 font-weight: 700;
3596 letter-spacing: -0.01em;
3597 }
3598 .land-2030-card-desc {
3599 margin: 0;
3600 font-size: 14.5px;
3601 color: var(--text-muted, #a0a0b8);
3602 line-height: 1.55;
3603 }
3604 .land-2030-card-cta {
3605 margin-top: auto;
3606 color: #b69dff;
3607 font-size: 14px;
3608 font-weight: 600;
3609 }
3610
3611 /* ---------- Global dashboards grid ---------- */
3612 .land-2030-dash-grid {
3613 display: grid;
3614 grid-template-columns: repeat(5, minmax(0, 1fr));
3615 gap: 12px;
3616 max-width: 1180px;
3617 margin: 0 auto;
3618 }
3619 .land-2030-dash {
3620 display: flex;
3621 flex-direction: column;
3622 gap: 6px;
3623 padding: 16px 18px;
3624 background: rgba(255,255,255,0.03);
3625 border: 1px solid rgba(255,255,255,0.08);
3626 border-radius: 12px;
3627 text-decoration: none;
3628 color: inherit;
3629 transition: transform 180ms ease, border-color 180ms ease;
3630 }
3631 .land-2030-dash:hover {
3632 transform: translateY(-1px);
3633 border-color: rgba(54,197,214,0.45);
3634 }
3635 .land-2030-dash-name {
3636 font-family: var(--font-mono, ui-monospace, monospace);
3637 font-size: 14px;
3638 color: #36c5d6;
3639 font-weight: 600;
3640 }
3641 .land-2030-dash-desc {
3642 font-size: 13px;
3643 color: var(--text-muted, #a0a0b8);
3644 line-height: 1.45;
3645 }
3646
3647 /* ---------- 18-feature grid + status pills ---------- */
3648 .land-2030-feat-grid {
3649 display: grid;
3650 grid-template-columns: repeat(3, minmax(0, 1fr));
3651 gap: 10px;
3652 max-width: 1180px;
3653 margin: 0 auto;
3654 }
3655 .land-2030-feat {
3656 display: flex;
3657 flex-direction: column;
3658 gap: 6px;
3659 padding: 14px 16px;
3660 background: rgba(255,255,255,0.025);
3661 border: 1px solid rgba(255,255,255,0.07);
3662 border-radius: 10px;
3663 text-decoration: none;
3664 color: inherit;
3665 transition: border-color 160ms ease, background 160ms ease;
3666 }
3667 .land-2030-feat:hover {
3668 border-color: rgba(140,109,255,0.35);
3669 background: rgba(140,109,255,0.05);
3670 }
3671 .land-2030-feat-head {
3672 display: flex;
3673 align-items: center;
3674 justify-content: space-between;
3675 gap: 10px;
3676 }
3677 .land-2030-feat-title {
3678 font-size: 14.5px;
3679 font-weight: 700;
3680 letter-spacing: -0.005em;
3681 }
3682 .land-2030-feat-desc {
3683 margin: 0;
3684 font-size: 13px;
3685 color: var(--text-muted, #a0a0b8);
3686 line-height: 1.5;
3687 }
3688 .land-2030-pill {
3689 font-size: 10.5px;
3690 letter-spacing: 0.04em;
3691 text-transform: uppercase;
3692 padding: 3px 8px;
3693 border-radius: 999px;
3694 border: 1px solid currentColor;
3695 font-weight: 700;
3696 line-height: 1;
3697 }
3698 .land-2030-pill-live { color: #5be0a9; border-color: rgba(91,224,169,0.5); background: rgba(91,224,169,0.10); }
3699 .land-2030-pill-beta { color: #ffd16b; border-color: rgba(255,209,107,0.5); background: rgba(255,209,107,0.10); }
3700 .land-2030-pill-soon { color: #a0a0b8; border-color: rgba(160,160,184,0.4); background: rgba(160,160,184,0.08); }
3701
3702 /* ---------- Developer surface ---------- */
3703 .land-2030-dx-grid {
3704 display: grid;
3705 grid-template-columns: repeat(3, minmax(0, 1fr));
3706 gap: 16px;
3707 max-width: 1180px;
3708 margin: 0 auto;
3709 }
3710 .land-2030-dx {
3711 padding: 18px;
3712 background: linear-gradient(180deg, rgba(15,17,26,0.7), rgba(8,9,15,0.7));
3713 border: 1px solid rgba(140,109,255,0.18);
3714 border-radius: 14px;
3715 }
3716 .land-2030-dx-title {
3717 margin: 0 0 10px;
3718 font-size: 15px;
3719 font-weight: 700;
3720 color: var(--text-strong, #fff);
3721 }
3722 .land-2030-code {
3723 margin: 0;
3724 padding: 12px 14px;
3725 background: rgba(0,0,0,0.30);
3726 border: 1px solid rgba(255,255,255,0.06);
3727 border-radius: 10px;
3728 font-family: var(--font-mono, ui-monospace, monospace);
3729 font-size: 12.5px;
3730 color: #cbb7ff;
3731 line-height: 1.6;
3732 overflow-x: auto;
3733 }
3734 .land-2030-code code { color: inherit; background: transparent; padding: 0; }
3735 .land-2030-code-wide {
3736 font-size: 13px;
3737 color: #d6d6e4;
3738 }
3739
3740 /* ---------- Agent multiplayer ---------- */
3741 .land-2030-agent-wrap {
3742 display: grid;
3743 grid-template-columns: 1.4fr 1fr;
3744 gap: 24px;
3745 max-width: 1080px;
3746 margin: 0 auto;
3747 align-items: center;
3748 }
3749 .land-2030-agent-stat { text-align: left; }
3750 .land-2030-agent-big {
3751 font-size: clamp(48px, 7vw, 80px);
3752 font-weight: 800;
3753 line-height: 1;
3754 background-image: linear-gradient(135deg, #a48bff 0%, #36c5d6 100%);
3755 background-clip: text;
3756 -webkit-background-clip: text;
3757 -webkit-text-fill-color: transparent;
3758 color: transparent;
3759 letter-spacing: -0.03em;
3760 }
3761 .land-2030-agent-label {
3762 margin-top: 12px;
3763 color: var(--text-muted, #a0a0b8);
3764 font-size: 15px;
3765 line-height: 1.5;
3766 }
3767 .land-2030-agent-link {
3768 display: inline-block;
3769 margin-top: 16px;
3770 color: #36c5d6;
3771 text-decoration: none;
3772 font-weight: 600;
3773 font-size: 14px;
3774 }
3775 .land-2030-agent-link:hover { text-decoration: underline; }
3776
3777 /* ---------- vs GitHub strip ---------- */
3778 .land-2030-vs {
3779 max-width: 880px;
3780 margin: 0 auto;
3781 background: rgba(255,255,255,0.03);
3782 border: 1px solid rgba(255,255,255,0.08);
3783 border-radius: 14px;
3784 overflow: hidden;
3785 text-align: center;
3786 padding: 0 0 18px;
3787 }
3788 .land-2030-vs-row {
3789 display: grid;
3790 grid-template-columns: 1fr 1fr;
3791 gap: 0;
3792 padding: 16px 18px;
3793 border-bottom: 1px solid rgba(255,255,255,0.06);
3794 font-size: 15px;
3795 }
3796 .land-2030-vs-row:last-of-type { border-bottom: 0; }
3797 .land-2030-vs-head {
3798 font-size: 12px;
3799 letter-spacing: 0.04em;
3800 text-transform: uppercase;
3801 color: var(--text-muted, #a0a0b8);
3802 background: rgba(255,255,255,0.02);
3803 }
3804 .land-2030-vs-us {
3805 color: #5be0a9;
3806 font-weight: 700;
3807 }
3808 .land-2030-vs-link {
3809 display: inline-block;
3810 margin-top: 12px;
3811 color: #b69dff;
3812 text-decoration: none;
3813 font-weight: 600;
3814 font-size: 14px;
3815 }
3816 .land-2030-vs-link:hover { text-decoration: underline; }
3817
3818 /* ---------- Responsive ---------- */
3819 @media (max-width: 960px) {
3820 .land-2030-card-grid,
3821 .land-2030-feat-grid,
3822 .land-2030-dx-grid,
3823 .land-2030-dash-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
3824 .land-2030-agent-wrap { grid-template-columns: 1fr; }
3825 }
3826 @media (max-width: 640px) {
3827 .land-2030-card-grid,
3828 .land-2030-feat-grid,
3829 .land-2030-dx-grid,
3830 .land-2030-dash-grid { grid-template-columns: 1fr; }
3831 .land-2030-section-dark { padding: 28px 16px; }
3832 .land-2030-vs-row { grid-template-columns: 1fr; gap: 4px; }
3833 }
3834 @media (max-width: 375px) {
3835 .land-2030-display { font-size: 44px; }
3836 .land-2030-cta-primary,
3837 .land-2030-cta-secondary { width: 100%; min-width: 0; }
3838 }
3839
3840 @media (prefers-reduced-motion: reduce) {
3841 .land-2030-loop-path,
3842 .land-2030-pulse { animation: none; }
3843 }
3844`;