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