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.tsxBlame3886 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>
9a8b8deClaude661 <div style="margin-top:var(--space-3);font-size:13.5px;color:var(--text-muted)">
662 Migrating from GitHub?{" "}
663 <a href="/migrate" style="color:var(--accent)">
664 Import your entire org in one click &rarr;
665 </a>
666 </div>
4c47454Claude667 </div>
668 </section>
5f2e749Claude669
670 {/* L10 — clipboard copy script for the hero install snippet. */}
671 <script dangerouslySetInnerHTML={{ __html: landingCopyJs }} />
4c47454Claude672 </div>
2b821b7Claude673 </>
674 );
675};
676
958d26aClaude677const FeatureCard: FC<{ icon: any; title: string; desc: string }> = ({
678 icon,
679 title,
680 desc,
681}) => (
682 <div class="landing-feature">
683 <div class="landing-feature-icon" aria-hidden="true">
684 {icon}
685 </div>
686 <h3 class="landing-feature-title">{title}</h3>
687 <p class="landing-feature-desc">{desc}</p>
688 </div>
689);
690
5f2e749Claude691// Block L10 — "Three reasons to switch" column.
692const ReasonCard: FC<{
693 icon: any;
694 title: string;
695 body: string;
696 link: { href: string; label: string };
697 extra?: any;
698}> = ({ icon, title, body, link, extra }) => (
699 <div class="landing-reason">
700 <div class="landing-reason-icon" aria-hidden="true">
701 {icon}
702 </div>
703 <h3 class="landing-reason-title">{title}</h3>
704 <p class="landing-reason-body">{body}</p>
705 {extra}
706 <a href={link.href} class="landing-reason-link">
707 {link.label}
708 <span class="landing-cta-arrow" aria-hidden="true">{"→"}</span>
709 </a>
710 </div>
711);
712
958d26aClaude713const WalkStep: FC<{ n: string; title: string; desc: string }> = ({
714 n,
715 title,
716 desc,
717}) => (
718 <div class="landing-walk-step">
719 <div class="landing-walk-num">{n}</div>
720 <h3 class="landing-walk-title">{title}</h3>
721 <p class="landing-walk-desc">{desc}</p>
722 </div>
723);
724
725const CompareRow: FC<{
726 feature: string;
727 them: string;
728 us: string;
729 highlight?: boolean;
730}> = ({ feature, them, us, highlight }) => (
731 <div class={`landing-compare-row${highlight ? " landing-compare-hl" : ""}`}>
732 <div class="landing-compare-feature">{feature}</div>
733 <div class="landing-compare-them">{them}</div>
734 <div class="landing-compare-us">{us === "✓" ? "✓" : us}</div>
735 </div>
736);
737
738const PricingCard: FC<{
739 tier: string;
740 price: string;
741 cadence: string;
742 desc: string;
743 features: string[];
744 cta: string;
745 href: string;
746 highlight?: boolean;
747}> = ({ tier, price, cadence, desc, features, cta, href, highlight }) => (
748 <div class={`landing-price-card${highlight ? " landing-price-hl" : ""}`}>
749 {highlight && <div class="landing-price-badge">Most popular</div>}
750 <div class="landing-price-tier">{tier}</div>
751 <div class="landing-price-amount">
752 <span class="landing-price-num">{price}</span>
753 <span class="landing-price-cad">{cadence}</span>
754 </div>
755 <p class="landing-price-desc">{desc}</p>
756 <ul class="landing-price-features">
757 {features.map((f) => (
758 <li>
759 <span class="landing-price-check" aria-hidden="true">{"✓"}</span>
760 {f}
761 </li>
762 ))}
763 </ul>
764 <a
765 href={href}
766 class={`btn ${highlight ? "btn-primary" : "btn-secondary"} btn-block landing-price-cta`}
767 >
768 {cta}
769 </a>
770 </div>
771);
772
52ad8b1Claude773// ─────────────────────────────────────────────────────────────────
774// Block L4 — social-proof tile builder.
775//
776// Pure: takes the cached PublicStats payload and emits the six
777// landing-page tiles in render order. Exported so tests / future
778// surfaces (dashboard, /about, …) can share the exact same copy.
779// ─────────────────────────────────────────────────────────────────
780
781export interface SocialProofTile {
782 label: string;
783 value: number;
784 prefix?: string;
785 suffix?: string;
786}
787
788export function buildSocialProofTiles(s: PublicStats): SocialProofTile[] {
789 return [
790 { label: "Public repos", value: s.totalPublicRepos },
791 { label: "Developers", value: s.totalUsers },
792 {
793 label: "PRs auto-merged this week",
794 value: s.weeklyPrsAutoMerged,
795 },
796 {
797 label: "Issues built by AI this week",
798 value: s.weeklyIssuesBuiltByAi,
799 },
800 {
801 label: "Deploys shipped this week",
802 value: s.weeklyDeploysShipped,
803 },
804 {
805 label: "Hours saved this week",
806 // Round to whole hours for the tile — the precise 0.1 figure
807 // lives on the dashboard widget; the marketing surface keeps
808 // the number scannable.
809 value: Math.round(s.weeklyHoursSaved),
810 prefix: "~",
811 suffix: "h",
812 },
813 ];
814}
815
534f04aClaude816// ─────────────────────────────────────────────────────────────────
817// Block M1 — Live-now demo feed.
818//
819// A 4-tile row inserted between the hero and the L4 counters tile
820// section, surfacing real autopilot activity from the seeded `demo`
821// owner's repos:
822// 1. Issues queued for AI build (ai:build label, open)
823// 2. PRs auto-merged in the last 24h
824// 3. AI reviews posted today (count + latest 3)
825// 4. Combined activity feed (last 10)
826//
827// SSR-renders an initial snapshot, then re-fetches every 30s via the
828// L3 JSON endpoints so the page feels alive without a websocket.
829// Pure presentational; the route layer owns the DB reads.
830// ─────────────────────────────────────────────────────────────────
831
832/**
833 * Render an ISO timestamp (or Date) as a coarse "about N units ago"
834 * string. Tolerates strings, Dates, NaN, and future timestamps.
835 *
836 * Exported for unit testing.
837 */
838export function relativeTimeFromNow(
839 value: string | Date | number | null | undefined,
840 now: number = Date.now()
841): string {
842 if (value === null || value === undefined) return "just now";
843 let t: number;
844 if (value instanceof Date) {
845 t = value.getTime();
846 } else if (typeof value === "number") {
847 t = value;
848 } else {
849 t = new Date(value).getTime();
850 }
851 if (!Number.isFinite(t)) return "just now";
852 const delta = now - t;
853 // Future timestamps (clock skew) — treat as "just now" rather than
854 // surfacing a confusing negative.
855 if (delta < 0) return "just now";
856 const s = Math.floor(delta / 1000);
857 if (s < 60) return "just now";
858 const m = Math.floor(s / 60);
859 if (m < 60) return `about ${m} minute${m === 1 ? "" : "s"} ago`;
860 const h = Math.floor(m / 60);
861 if (h < 24) return `about ${h} hour${h === 1 ? "" : "s"} ago`;
862 const d = Math.floor(h / 24);
863 return `about ${d} day${d === 1 ? "" : "s"} ago`;
864}
865
866function feedEntryId(e: LandingLiveFeedEntry): string {
867 const at = e.at instanceof Date ? e.at.toISOString() : String(e.at);
868 return `${e.kind}|${e.repo}|${e.ref.type}|${e.ref.number}|${at}`;
869}
870
871function feedEntryLabel(kind: LandingLiveFeedEntry["kind"]): string {
872 switch (kind) {
873 case "auto_merge.merged":
874 return "auto-merged";
875 case "ai_build.dispatched":
876 return "AI-build queued";
877 case "ai_review.posted":
878 return "AI review posted";
879 }
880}
881
882interface LiveNowSectionProps {
883 queued: LandingLiveFeedQueued[];
884 merges: LandingLiveFeedMerge[];
885 reviews: LandingLiveFeedReview[];
886 reviewCount: number;
887 feed: LandingLiveFeedEntry[];
888}
889
890const LiveNowSection: FC<LiveNowSectionProps> = ({
891 queued,
892 merges,
893 reviews,
894 reviewCount,
895 feed,
896}) => {
897 return (
898 <section class="landing-livenow" aria-labelledby="landing-livenow-h">
899 <div class="landing-livenow-head">
900 <div class="landing-livenow-eyebrow">
901 <span class="landing-livenow-pulse" aria-hidden="true" />
902 Live now
903 </div>
904 <h2 id="landing-livenow-h" class="landing-livenow-title">
905 Claude is working on demo repos as you read this.
906 </h2>
907 <p class="landing-livenow-sub">
908 Every card below is real data from the public{" "}
909 <code>{DEMO_USERNAME}/*</code> repos. Refreshes every 30 seconds.
910 </p>
911 </div>
912
913 <div class="landing-livenow-grid" data-livenow-grid>
914 {/* Card 1 — queued issues */}
915 <article class="landing-livecard" aria-labelledby="lc-queued-h">
916 <header class="landing-livecard-head">
917 <span class="landing-livecard-dot" aria-hidden="true" />
918 <h3 id="lc-queued-h" class="landing-livecard-title">
919 Issues queued for AI
920 </h3>
921 </header>
922 <ul class="landing-livecard-list" data-livecard="queued">
923 {queued.length === 0 ? (
924 <li class="landing-livecard-empty">
925 No queued AI builds — quiet right now.
926 </li>
927 ) : (
928 queued.slice(0, 3).map((i) => (
929 <li
930 class="landing-livecard-row"
931 data-row-id={`queued|${i.repo}|${i.number}`}
932 >
933 <a
934 class="landing-livecard-link"
935 href={`/${DEMO_USERNAME}/${i.repo}/issues/${i.number}`}
936 >
937 <span class="landing-livecard-num">#{i.number}</span>{" "}
938 <span class="landing-livecard-title-text">{i.title}</span>
939 </a>
940 <div class="landing-livecard-meta">
941 <span class="landing-livecard-repo">{i.repo}</span>
942 </div>
943 </li>
944 ))
945 )}
946 </ul>
947 </article>
948
949 {/* Card 2 — recently merged */}
950 <article class="landing-livecard" aria-labelledby="lc-merges-h">
951 <header class="landing-livecard-head">
952 <span class="landing-livecard-dot" aria-hidden="true" />
953 <h3 id="lc-merges-h" class="landing-livecard-title">
954 Recently merged by AI
955 </h3>
956 </header>
957 <ul class="landing-livecard-list" data-livecard="merges">
958 {merges.length === 0 ? (
959 <li class="landing-livecard-empty">
960 No auto-merges in the last 24h.
961 </li>
962 ) : (
963 merges.slice(0, 3).map((m) => (
964 <li
965 class="landing-livecard-row"
966 data-row-id={`merges|${m.repo}|${m.number}`}
967 >
968 <a
969 class="landing-livecard-link"
970 href={`/${DEMO_USERNAME}/${m.repo}/pulls/${m.number}`}
971 >
972 <span class="landing-livecard-num">#{m.number}</span>{" "}
973 <span class="landing-livecard-title-text">{m.title}</span>
974 </a>
975 <div class="landing-livecard-meta">
976 AI merged in{" "}
977 <span class="landing-livecard-repo">{m.repo}</span>{" "}
978 <span
979 class="landing-livecard-rel"
980 data-rel={
981 m.mergedAt instanceof Date
982 ? m.mergedAt.toISOString()
983 : String(m.mergedAt)
984 }
985 >
986 {relativeTimeFromNow(m.mergedAt)}
987 </span>
988 </div>
989 </li>
990 ))
991 )}
992 </ul>
993 </article>
994
995 {/* Card 3 — AI reviews */}
996 <article class="landing-livecard" aria-labelledby="lc-reviews-h">
997 <header class="landing-livecard-head">
998 <span class="landing-livecard-dot" aria-hidden="true" />
999 <h3 id="lc-reviews-h" class="landing-livecard-title">
1000 AI reviews posted
1001 </h3>
1002 </header>
1003 <div class="landing-livecard-bignum">
1004 <span
1005 class="landing-livecard-bignum-n"
1006 data-livecard-count="reviews"
1007 data-tick-target={String(reviewCount)}
1008 >
1009 {reviewCount.toLocaleString()}
1010 </span>
1011 <span class="landing-livecard-bignum-label">reviews today</span>
1012 </div>
1013 <ul class="landing-livecard-list" data-livecard="reviews">
1014 {reviews.length === 0 ? (
1015 <li class="landing-livecard-empty">
1016 No AI reviews in the last 24h.
1017 </li>
1018 ) : (
1019 reviews.slice(0, 3).map((r) => (
1020 <li
1021 class="landing-livecard-row"
1022 data-row-id={`reviews|${r.repo}|${r.prNumber}`}
1023 >
1024 <a
1025 class="landing-livecard-link"
1026 href={`/${DEMO_USERNAME}/${r.repo}/pulls/${r.prNumber}`}
1027 >
1028 <span class="landing-livecard-num">#{r.prNumber}</span>{" "}
1029 <span class="landing-livecard-snippet">
1030 {r.commentSnippet}
1031 </span>
1032 </a>
1033 <div class="landing-livecard-meta">
1034 <span class="landing-livecard-repo">{r.repo}</span>
1035 </div>
1036 </li>
1037 ))
1038 )}
1039 </ul>
1040 </article>
1041
1042 {/* Card 4 — activity feed */}
1043 <article class="landing-livecard" aria-labelledby="lc-feed-h">
1044 <header class="landing-livecard-head">
1045 <span class="landing-livecard-dot" aria-hidden="true" />
1046 <h3 id="lc-feed-h" class="landing-livecard-title">
1047 Activity feed
1048 </h3>
1049 </header>
1050 <ul class="landing-livecard-list landing-livecard-feed" data-livecard="feed">
1051 {feed.length === 0 ? (
1052 <li class="landing-livecard-empty">
1053 Quiet right now — check back in a minute.
1054 </li>
1055 ) : (
1056 feed.slice(0, 10).map((e) => {
1057 const path = e.ref.type === "pr" ? "pulls" : "issues";
1058 const id = feedEntryId(e);
1059 return (
1060 <li class="landing-livecard-feedrow" data-row-id={id}>
1061 <span
1062 class={`landing-livecard-kind landing-livecard-kind-${e.kind.replace(/\./g, "-")}`}
1063 >
1064 {feedEntryLabel(e.kind)}
1065 </span>{" "}
1066 <a
1067 class="landing-livecard-link"
1068 href={`/${DEMO_USERNAME}/${e.repo}/${path}/${e.ref.number}`}
1069 >
1070 {e.repo} #{e.ref.number}
1071 </a>{" "}
1072 <span
1073 class="landing-livecard-rel"
1074 data-rel={
1075 e.at instanceof Date ? e.at.toISOString() : String(e.at)
1076 }
1077 >
1078 {relativeTimeFromNow(e.at)}
1079 </span>
1080 </li>
1081 );
1082 })
1083 )}
1084 </ul>
1085 </article>
1086 </div>
1087
1088 <div class="landing-livenow-cta">
1089 <span class="landing-livenow-cta-text">
1090 Want this for your repos?
1091 </span>
1092 <a class="landing-livenow-cta-link" href="/register">
1093 Sign up free
1094 <span class="landing-cta-arrow" aria-hidden="true">{"→"}</span>
1095 </a>
1096 <span class="landing-livenow-cta-sep" aria-hidden="true">·</span>
1097 <a class="landing-livenow-cta-link" href="/demo">
1098 Try the live demo
1099 <span class="landing-cta-arrow" aria-hidden="true">{"→"}</span>
1100 </a>
1101 </div>
1102
1103 <script dangerouslySetInnerHTML={{ __html: liveNowJs }} />
1104 </section>
1105 );
1106};
1107
1108// Inline poller. Plain JS so we don't ship a separate bundle. Hits the
1109// four L3 JSON endpoints every 30s, re-renders the four cards, ticks
1110// the big number, refreshes relative timestamps, flashes new rows.
1111const liveNowJs = `
1112(function(){
1113try{
1114 var DEMO=${JSON.stringify(DEMO_USERNAME)};
1115 var INTERVAL=30000;
1116 function esc(s){return String(s==null?'':s).replace(/[&<>"']/g,function(c){return {'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#39;'}[c];});}
1117 function rel(v){
1118 if(v==null) return 'just now';
1119 var t=(v instanceof Date)?v.getTime():(typeof v==='number'?v:new Date(v).getTime());
1120 if(!isFinite(t)) return 'just now';
1121 var d=Date.now()-t;
1122 if(d<0) return 'just now';
1123 var s=Math.floor(d/1000);
1124 if(s<60) return 'just now';
1125 var m=Math.floor(s/60);
1126 if(m<60) return 'about '+m+' minute'+(m===1?'':'s')+' ago';
1127 var h=Math.floor(m/60);
1128 if(h<24) return 'about '+h+' hour'+(h===1?'':'s')+' ago';
1129 var dd=Math.floor(h/24);
1130 return 'about '+dd+' day'+(dd===1?'':'s')+' ago';
1131 }
1132 function tickNumber(el,target){
1133 if(!el) return;
1134 var start=parseInt(el.getAttribute('data-tick-current')||'0',10)||0;
1135 if(start===target){el.textContent=target.toLocaleString();el.setAttribute('data-tick-current',String(target));return;}
1136 var dur=800,t0=performance.now();
1137 function step(now){
1138 var p=Math.min(1,(now-t0)/dur);
1139 var eased=1-Math.pow(1-p,3);
1140 var v=Math.round(start+(target-start)*eased);
1141 el.textContent=v.toLocaleString();
1142 if(p<1) requestAnimationFrame(step); else el.setAttribute('data-tick-current',String(target));
1143 }
1144 requestAnimationFrame(step);
1145 }
1146 function flashRow(li){
1147 if(!li) return;
1148 li.classList.add('landing-livecard-flash');
1149 setTimeout(function(){li.classList.remove('landing-livecard-flash');},1100);
1150 }
1151 function diffMount(ul,newHtml,newIds){
1152 if(!ul) return;
1153 var prev={};
1154 var nodes=ul.querySelectorAll('[data-row-id]');
1155 for(var i=0;i<nodes.length;i++){prev[nodes[i].getAttribute('data-row-id')]=true;}
1156 ul.innerHTML=newHtml;
1157 var fresh=ul.querySelectorAll('[data-row-id]');
1158 for(var j=0;j<fresh.length;j++){
1159 var id=fresh[j].getAttribute('data-row-id');
1160 if(id && !prev[id]) flashRow(fresh[j]);
1161 }
1162 }
1163 function pollQueued(){
1164 return fetch('/api/v2/demo/queued',{credentials:'omit'}).then(function(r){return r.json();}).then(function(d){
1165 var ul=document.querySelector('[data-livecard="queued"]');if(!ul) return;
1166 var items=(d&&d.items)||[];
1167 if(items.length===0){ul.innerHTML='<li class="landing-livecard-empty">No queued AI builds — quiet right now.</li>';return;}
1168 var ids=[];
1169 var html=items.slice(0,3).map(function(i){
1170 var id='queued|'+i.repo+'|'+i.number;ids.push(id);
1171 return '<li class="landing-livecard-row" data-row-id="'+esc(id)+'">'+
1172 '<a class="landing-livecard-link" href="/'+esc(DEMO)+'/'+esc(i.repo)+'/issues/'+i.number+'">'+
1173 '<span class="landing-livecard-num">#'+i.number+'</span> '+
1174 '<span class="landing-livecard-title-text">'+esc(i.title)+'</span></a>'+
1175 '<div class="landing-livecard-meta"><span class="landing-livecard-repo">'+esc(i.repo)+'</span></div></li>';
1176 }).join('');
1177 diffMount(ul,html,ids);
1178 }).catch(function(){});
1179 }
1180 function pollMerges(){
1181 return fetch('/api/v2/demo/merges',{credentials:'omit'}).then(function(r){return r.json();}).then(function(d){
1182 var ul=document.querySelector('[data-livecard="merges"]');if(!ul) return;
1183 var items=(d&&d.items)||[];
1184 if(items.length===0){ul.innerHTML='<li class="landing-livecard-empty">No auto-merges in the last 24h.</li>';return;}
1185 var ids=[];
1186 var html=items.slice(0,3).map(function(m){
1187 var id='merges|'+m.repo+'|'+m.number;ids.push(id);
1188 return '<li class="landing-livecard-row" data-row-id="'+esc(id)+'">'+
1189 '<a class="landing-livecard-link" href="/'+esc(DEMO)+'/'+esc(m.repo)+'/pulls/'+m.number+'">'+
1190 '<span class="landing-livecard-num">#'+m.number+'</span> '+
1191 '<span class="landing-livecard-title-text">'+esc(m.title)+'</span></a>'+
1192 '<div class="landing-livecard-meta">AI merged in <span class="landing-livecard-repo">'+esc(m.repo)+'</span> '+
1193 '<span class="landing-livecard-rel" data-rel="'+esc(m.mergedAt)+'">'+esc(rel(m.mergedAt))+'</span></div></li>';
1194 }).join('');
1195 diffMount(ul,html,ids);
1196 }).catch(function(){});
1197 }
1198 function pollReviews(){
1199 return fetch('/api/v2/demo/reviews',{credentials:'omit'}).then(function(r){return r.json();}).then(function(d){
1200 var ul=document.querySelector('[data-livecard="reviews"]');if(!ul) return;
1201 var bigEl=document.querySelector('[data-livecard-count="reviews"]');
1202 var n=(d&&typeof d.count==='number')?d.count:0;
1203 if(bigEl) tickNumber(bigEl,n);
1204 var items=(d&&d.items)||[];
1205 if(items.length===0){ul.innerHTML='<li class="landing-livecard-empty">No AI reviews in the last 24h.</li>';return;}
1206 var ids=[];
1207 var html=items.slice(0,3).map(function(r){
1208 var id='reviews|'+r.repo+'|'+r.prNumber;ids.push(id);
1209 return '<li class="landing-livecard-row" data-row-id="'+esc(id)+'">'+
1210 '<a class="landing-livecard-link" href="/'+esc(DEMO)+'/'+esc(r.repo)+'/pulls/'+r.prNumber+'">'+
1211 '<span class="landing-livecard-num">#'+r.prNumber+'</span> '+
1212 '<span class="landing-livecard-snippet">'+esc(r.commentSnippet)+'</span></a>'+
1213 '<div class="landing-livecard-meta"><span class="landing-livecard-repo">'+esc(r.repo)+'</span></div></li>';
1214 }).join('');
1215 diffMount(ul,html,ids);
1216 }).catch(function(){});
1217 }
1218 function pollFeed(){
1219 return fetch('/api/v2/demo/activity',{credentials:'omit'}).then(function(r){return r.json();}).then(function(d){
1220 var ul=document.querySelector('[data-livecard="feed"]');if(!ul) return;
1221 var entries=(d&&d.entries)||[];
1222 if(entries.length===0){ul.innerHTML='<li class="landing-livecard-empty">Quiet right now — check back in a minute.</li>';return;}
1223 var ids=[];
1224 var html=entries.slice(0,10).map(function(e){
1225 var path=(e.ref&&e.ref.type==='pr')?'pulls':'issues';
1226 var num=(e.ref&&e.ref.number)||0;
1227 var label=e.kind==='auto_merge.merged'?'auto-merged':(e.kind==='ai_build.dispatched'?'AI-build queued':'AI review posted');
1228 var kindCls=String(e.kind||'').replace(/\\./g,'-');
1229 var id=e.kind+'|'+e.repo+'|'+(e.ref&&e.ref.type)+'|'+num+'|'+e.at;ids.push(id);
1230 return '<li class="landing-livecard-feedrow" data-row-id="'+esc(id)+'">'+
1231 '<span class="landing-livecard-kind landing-livecard-kind-'+esc(kindCls)+'">'+esc(label)+'</span> '+
1232 '<a class="landing-livecard-link" href="/'+esc(DEMO)+'/'+esc(e.repo)+'/'+path+'/'+num+'">'+esc(e.repo)+' #'+num+'</a> '+
1233 '<span class="landing-livecard-rel" data-rel="'+esc(e.at)+'">'+esc(rel(e.at))+'</span></li>';
1234 }).join('');
1235 diffMount(ul,html,ids);
1236 }).catch(function(){});
1237 }
1238 function refreshRel(){
1239 var spans=document.querySelectorAll('.landing-livecard-rel[data-rel]');
1240 for(var i=0;i<spans.length;i++){
1241 spans[i].textContent=rel(spans[i].getAttribute('data-rel'));
1242 }
1243 }
1244 function tickAll(){pollQueued();pollMerges();pollReviews();pollFeed();}
1245 // Initial counter tick (count-up from 0) on first paint.
1246 var bigEl0=document.querySelector('[data-livecard-count="reviews"]');
1247 if(bigEl0){
1248 var target=parseInt(bigEl0.getAttribute('data-tick-target')||'0',10)||0;
1249 bigEl0.textContent='0';
1250 tickNumber(bigEl0,target);
1251 }
1252 refreshRel();
1253 setInterval(tickAll,INTERVAL);
1254 setInterval(refreshRel,INTERVAL);
1255 document.addEventListener('visibilitychange',function(){
1256 if(document.visibilityState==='visible'){tickAll();refreshRel();}
1257 });
1258}catch(_){}})();
1259`.trim();
1260
0c3eee5Claude1261// ============================================================
1262// Land2030 — 2030 homepage prelude (closed-loop showcase).
1263// ============================================================
1264//
1265// Renders BEFORE the existing LandingHero so every L10/U1/Q1/M1
1266// regression assertion keeps passing. CSS is scoped under
1267// `.land-2030-*` to avoid colliding with the older `.landing-*`
1268// styles. Inline SVG only — no extra deps.
1269
1270const ClosedLoopDiagram: FC = () => (
1271 <svg
1272 viewBox="0 0 1100 280"
1273 class="land-2030-loop-svg"
1274 role="img"
1275 aria-label="Closed loop: Spec to Code to AI Review to Tests to Merge to Deploy to Monitor to Patch"
1276 >
1277 <defs>
1278 <linearGradient id="land2030LoopLine" x1="0" y1="0" x2="1" y2="0">
1279 <stop offset="0%" stop-color="#8c6dff" stop-opacity="0.0" />
1280 <stop offset="20%" stop-color="#8c6dff" stop-opacity="0.6" />
1281 <stop offset="80%" stop-color="#36c5d6" stop-opacity="0.6" />
1282 <stop offset="100%" stop-color="#36c5d6" stop-opacity="0.0" />
1283 </linearGradient>
1284 <radialGradient id="land2030LoopNode" cx="0.5" cy="0.5" r="0.5">
1285 <stop offset="0%" stop-color="#a48bff" stop-opacity="0.95" />
1286 <stop offset="100%" stop-color="#36c5d6" stop-opacity="0.85" />
1287 </radialGradient>
1288 </defs>
1289 {/* connecting curve */}
1290 <path
1291 d="M 60 140 C 220 40, 880 40, 1040 140 C 880 240, 220 240, 60 140 Z"
1292 fill="none"
1293 stroke="url(#land2030LoopLine)"
1294 stroke-width="2"
1295 class="land-2030-loop-path"
1296 />
1297 {[
1298 { x: 70, y: 140, label: "Spec" },
1299 { x: 215, y: 70, label: "Code" },
1300 { x: 410, y: 50, label: "AI Review" },
1301 { x: 605, y: 50, label: "Tests" },
1302 { x: 800, y: 70, label: "Merge" },
1303 { x: 950, y: 140, label: "Deploy" },
1304 { x: 800, y: 210, label: "Monitor" },
1305 { x: 410, y: 230, label: "Patch" },
1306 ].map((n) => (
1307 <g class="land-2030-loop-node">
1308 <circle cx={n.x} cy={n.y} r="22" fill="url(#land2030LoopNode)" />
1309 <circle
1310 cx={n.x}
1311 cy={n.y}
1312 r="22"
1313 fill="none"
1314 stroke="rgba(255,255,255,0.18)"
1315 stroke-width="1"
1316 />
1317 <text
1318 x={n.x}
1319 y={n.y + (n.y > 140 ? 48 : -32)}
1320 text-anchor="middle"
1321 class="land-2030-loop-text"
1322 fill="currentColor"
1323 >
1324 {n.label}
1325 </text>
1326 </g>
1327 ))}
1328 </svg>
1329);
1330
1331interface Land2030CardProps {
1332 icon: any;
1333 title: string;
1334 desc: string;
1335 href: string;
1336 cta?: string;
1337}
1338const Land2030Card: FC<Land2030CardProps> = ({ icon, title, desc, href, cta }) => (
1339 <a href={href} class="land-2030-card">
1340 <div class="land-2030-card-icon" aria-hidden="true">
1341 {icon}
1342 </div>
1343 <h3 class="land-2030-card-title">{title}</h3>
1344 <p class="land-2030-card-desc">{desc}</p>
1345 <span class="land-2030-card-cta">
1346 {cta ?? "Try it"}
1347 <span aria-hidden="true">{" →"}</span>
1348 </span>
1349 </a>
1350);
1351
1352interface Land2030FeatureItem {
1353 title: string;
1354 desc: string;
1355 href: string;
1356 status: "live" | "beta" | "soon";
1357}
1358const LAND_2030_FEATURES: Land2030FeatureItem[] = [
1359 { title: "Spec-to-PR", desc: "Write English. Ship code.", href: "/specs", status: "live" },
1360 { title: "Voice-to-PR", desc: "Talk. Ship code.", href: "/voice-to-pr", status: "live" },
1361 { title: "Repo chat", desc: "Rubber-duck with a semantic index of your repo.", href: "/ask", status: "live" },
1362 { title: "AI CI healer", desc: "Broken CI? Claude opens a fix PR.", href: "/inbox", status: "live" },
1363 { title: "AI patch generator", desc: "Security finding → patch PR, signed by Claude.", href: "/code-scanning", status: "live" },
1364 { title: "AI proactive monitor", desc: "Claude opens issues unprompted when it spots smells.", href: "/standups", status: "live" },
1365 { title: "AI commit messages", desc: "`gluecron commit` writes a great message for the staged diff.", href: "/help", status: "live" },
1366 { title: "AI release notes", desc: "Claude reads merged PRs and writes the changelog.", href: "/ai-changelog", status: "live" },
1367 { title: "Multi-repo refactor", desc: "One English request → coordinated PRs across N repos.", href: "/refactors", status: "live" },
1368 { title: "Migration assistant", desc: "Major-version upgrades drafted PR-by-PR.", href: "/migration-assistant", status: "live" },
1369 { title: "AI test generator", desc: "Every PR auto-gets tests for the new diff.", href: "/ai-tests", status: "beta" },
1370 { title: "PR slash commands", desc: "/merge, /rebase, /explain, /test — Claude runs them.", href: "/help", status: "live" },
1371 { title: "Live co-editing on PRs", desc: "Figma-style cursors on PR descriptions and reviews.", href: "/pulls", status: "beta" },
1372 { title: "Branch preview URLs", desc: "Every push → a sharable preview URL.", href: "/previews", status: "live" },
1373 { title: "Agent multiplayer", desc: "Per-agent sessions, budgets, and branch namespacing.", href: "/settings/agents", status: "live" },
1374 { title: "Continuous semantic index", desc: "Push-time embeddings keep search and chat fresh.", href: "/semantic-search", status: "live" },
1375 { title: "VS Code extension", desc: "Inbox, PRs, and repo chat — in your editor.", href: "/help", status: "soon" },
1376 { title: "Slack / Discord bot", desc: "Mentions, reviews, deploys — in your team chat.", href: "/help", status: "soon" },
1377];
1378
1379const Land2030: FC = () => (
1380 <>
1381 <style dangerouslySetInnerHTML={{ __html: land2030Css }} />
1382 <div class="land-2030-root">
1383 {/* ---------- 2030 HERO ---------- */}
1384 <section class="land-2030-hero" aria-label="Gluecron 2030">
1385 <div class="land-2030-hairline" aria-hidden="true" />
1386 <div class="land-2030-orb" aria-hidden="true" />
1387 <div class="land-2030-hero-inner">
1388 <div class="land-2030-eyebrow">
1389 <span class="land-2030-pulse" aria-hidden="true" />
1390 Gluecron — built for 2030
1391 </div>
1392 <h1 class="land-2030-display">
1393 <span class="land-2030-grad-1">Write</span>{" "}
1394 <span class="land-2030-grad-2">English.</span>{" "}
1395 <span class="land-2030-grad-3">Ship</span>{" "}
1396 <span class="land-2030-grad-4">code.</span>{" "}
1397 <span class="land-2030-grad-5">Gluecron.</span>
1398 </h1>
1399 <p class="land-2030-sub">
1400 The git platform built for the era when AI ships most of the code.
1401 </p>
1402 <div class="land-2030-cta-row">
1403 <a href="/register" class="btn btn-primary btn-xl land-2030-cta-primary">
1404 Start free
1405 <span aria-hidden="true">{" →"}</span>
1406 </a>
1407 <a href="#land-2030-loop" class="btn btn-xl land-2030-cta-secondary">
1408 Watch the loop
1409 <span aria-hidden="true">{" ↓"}</span>
1410 </a>
1411 </div>
1412 </div>
1413 </section>
1414
1415 {/* ---------- THE CLOSED LOOP ---------- */}
1416 <section id="land-2030-loop" class="land-2030-section">
1417 <div class="land-2030-section-head">
1418 <div class="land-2030-eyebrow land-2030-eyebrow-mini">The closed loop</div>
1419 <h2 class="land-2030-h2">One platform. No glue code.</h2>
1420 <p class="land-2030-lede">
1421 Spec → Code → AI Review → Tests → Merge → Deploy → Monitor → Patch.
1422 All on Gluecron. No GitHub + Copilot + Vercel + Sentry stitching
1423 needed.
1424 </p>
1425 </div>
1426 <div class="land-2030-loop-wrap">
1427 <ClosedLoopDiagram />
1428 </div>
1429 </section>
1430
1431 {/* ---------- THE 6 THINGS NOBODY ELSE CAN DO ---------- */}
1432 <section class="land-2030-section">
1433 <div class="land-2030-section-head">
1434 <div class="land-2030-eyebrow land-2030-eyebrow-mini">Unfair advantages</div>
1435 <h2 class="land-2030-h2">What Gluecron does that nobody else can.</h2>
1436 </div>
1437 <div class="land-2030-card-grid">
1438 <Land2030Card
1439 href="/voice-to-pr"
1440 title="Voice-to-PR"
1441 desc="Speak the change you want. Claude opens the PR. Works on your commute."
1442 icon={
1443 <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>
1444 }
1445 />
1446 <Land2030Card
1447 href="/specs"
1448 title="Spec-to-PR"
1449 desc="Write the spec in plain English. Claude implements it, opens a PR, and asks for review."
1450 icon={
1451 <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>
1452 }
1453 />
1454 <Land2030Card
1455 href="/workflows"
1456 title="AI CI self-healing"
1457 desc="Tests go red? Claude reads the log, finds the cause, and pushes the fix to your PR branch."
1458 icon={
1459 <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>
1460 }
1461 />
1462 <Land2030Card
1463 href="/refactors"
1464 title="Multi-repo refactor agent"
1465 desc="One English request → coordinated PRs across every repo that uses the symbol."
1466 icon={
1467 <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>
1468 }
1469 />
1470 <Land2030Card
1471 href="/ask"
1472 title="Repo chat with semantic search"
1473 desc="Continuous push-time embeddings. Ask the repo anything; it cites real files and commits."
1474 icon={
1475 <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>
1476 }
1477 />
1478 <Land2030Card
1479 href="/pulls"
1480 title="Per-PR live co-editing"
1481 desc="Figma-style cursors and presence on PR descriptions and reviews. Goodbye stale tabs."
1482 icon={
1483 <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>
1484 }
1485 />
1486 </div>
1487 </section>
1488
1489 {/* ---------- GLOBAL DASHBOARDS ---------- */}
1490 <section class="land-2030-section">
1491 <div class="land-2030-section-head">
1492 <div class="land-2030-eyebrow land-2030-eyebrow-mini">Mission control</div>
1493 <h2 class="land-2030-h2">Every signal, one inbox.</h2>
1494 <p class="land-2030-lede">
1495 Five global dashboards that span every repo you touch — no more
1496 tab-juggling across orgs.
1497 </p>
1498 </div>
1499 <div class="land-2030-dash-grid">
1500 <a href="/pulls" class="land-2030-dash">
1501 <span class="land-2030-dash-name">/pulls</span>
1502 <span class="land-2030-dash-desc">PR command center across every repo you can touch.</span>
1503 </a>
1504 <a href="/issues" class="land-2030-dash">
1505 <span class="land-2030-dash-name">/issues</span>
1506 <span class="land-2030-dash-desc">Global issue dashboard. Triage from one screen.</span>
1507 </a>
1508 <a href="/inbox" class="land-2030-dash">
1509 <span class="land-2030-dash-name">/inbox</span>
1510 <span class="land-2030-dash-desc">Unified mentions, reviews, CI, and AI events.</span>
1511 </a>
1512 <a href="/activity" class="land-2030-dash">
1513 <span class="land-2030-dash-name">/activity</span>
1514 <span class="land-2030-dash-desc">A timeline of everything that moved on your repos.</span>
1515 </a>
1516 <a href="/standups" class="land-2030-dash">
1517 <span class="land-2030-dash-name">/standups</span>
1518 <span class="land-2030-dash-desc">Daily AI-generated brief of what shipped and what's stuck.</span>
1519 </a>
1520 </div>
1521 </section>
1522
1523 {/* ---------- FULL AI FEATURE GRID ---------- */}
1524 <section class="land-2030-section">
1525 <div class="land-2030-section-head">
1526 <div class="land-2030-eyebrow land-2030-eyebrow-mini">Closed-loop AI</div>
1527 <h2 class="land-2030-h2">18 AI features. One platform.</h2>
1528 <p class="land-2030-lede">
1529 Most of these don't exist anywhere else. None of them require a
1530 second SaaS subscription.
1531 </p>
1532 </div>
1533 <div class="land-2030-feat-grid">
1534 {LAND_2030_FEATURES.map((f) => (
1535 <a href={f.href} class="land-2030-feat">
1536 <div class="land-2030-feat-head">
1537 <span class="land-2030-feat-title">{f.title}</span>
1538 <span class={`land-2030-pill land-2030-pill-${f.status}`}>
1539 {f.status === "live" ? "live" : f.status === "beta" ? "beta" : "soon"}
1540 </span>
1541 </div>
1542 <p class="land-2030-feat-desc">{f.desc}</p>
1543 </a>
1544 ))}
1545 </div>
1546 </section>
1547
1548 {/* ---------- DEVELOPER EXPERIENCE ---------- */}
1549 <section class="land-2030-section">
1550 <div class="land-2030-section-head">
1551 <div class="land-2030-eyebrow land-2030-eyebrow-mini">Developer surface</div>
1552 <h2 class="land-2030-h2">Built where you already are.</h2>
1553 </div>
1554 <div class="land-2030-dx-grid">
1555 <div class="land-2030-dx">
1556 <h3 class="land-2030-dx-title">gluecron CLI</h3>
1557 <pre class="land-2030-code"><code>$ gluecron spec "add CSV export to /api/orders"
1558{"→ Drafting PR…"}
1559{"→ Opened #482 with 3 commits"}
1560{"→ AI review queued"}</code></pre>
1561 </div>
1562 <div class="land-2030-dx">
1563 <h3 class="land-2030-dx-title">PR slash commands</h3>
1564 <pre class="land-2030-code"><code>/merge — squash + merge when checks pass
1565/rebase — rebase onto base, push --force-with-lease
1566/explain — Claude explains the diff in plain English
1567/test — Claude writes tests for the new code</code></pre>
1568 </div>
1569 <div class="land-2030-dx">
1570 <h3 class="land-2030-dx-title">Branch preview URLs</h3>
1571 <pre class="land-2030-code"><code>{"→ git push gluecron HEAD"}
1572{"→ preview: https://pr-482.preview.gluecron.com"}
1573{"→ commented on PR #482"}</code></pre>
1574 </div>
1575 </div>
1576 </section>
1577
1578 {/* ---------- BUILT FOR AGENTS ---------- */}
1579 <section class="land-2030-section land-2030-section-dark">
1580 <div class="land-2030-section-head">
1581 <div class="land-2030-eyebrow land-2030-eyebrow-mini">Agent era</div>
1582 <h2 class="land-2030-h2">Built for agents, not just humans.</h2>
1583 <p class="land-2030-lede">
1584 Per-agent tokens, per-agent budgets, per-agent branch namespaces,
1585 and a lease primitive so 50 agents don't trample one repo.
1586 </p>
1587 </div>
1588 <div class="land-2030-agent-wrap">
1589 <pre class="land-2030-code land-2030-code-wide"><code>{'# agent gets a scoped token + lease before writing'}
1590{'curl -H "Authorization: Bearer agt_3p9x…" \\\\'}
1591{' -X POST https://gluecron.com/api/v2/leases \\\\'}
1592{' -d \'{"repo":"acme/api","branch":"agent/jules/checkout-fix","ttl":300}\''}
1593{''}
1594{'{ "lease_id": "lse_8a2f", "expires_at": "2030-05-25T14:05:11Z" }'}</code></pre>
1595 <div class="land-2030-agent-stat">
1596 <div class="land-2030-agent-big">10,000</div>
1597 <div class="land-2030-agent-label">
1598 agents pushing to your repo per day. Welcome to 2030.
1599 </div>
1600 <a href="/docs/build-agent-integration" class="land-2030-agent-link">
1601 Build an agent integration{" →"}
1602 </a>
1603 </div>
1604 </div>
1605 </section>
1606
1607 {/* ---------- VS GITHUB ---------- */}
1608 <section class="land-2030-section">
1609 <div class="land-2030-section-head">
1610 <div class="land-2030-eyebrow land-2030-eyebrow-mini">vs GitHub</div>
1611 <h2 class="land-2030-h2">One platform replaces five.</h2>
1612 </div>
1613 <div class="land-2030-vs">
1614 <div class="land-2030-vs-row land-2030-vs-head">
1615 <div>Today</div>
1616 <div>On Gluecron</div>
1617 </div>
1618 <div class="land-2030-vs-row">
1619 <div>GitHub + Copilot + Vercel + Sentry + Linear</div>
1620 <div class="land-2030-vs-us">Gluecron</div>
1621 </div>
1622 <a href="/vs-github" class="land-2030-vs-link">
1623 See the full comparison{" →"}
1624 </a>
1625 </div>
1626 </section>
1627 </div>
1628 </>
1629);
1630
4c47454Claude1631// Backwards-compatible default — web.tsx imports `LandingPage`.
a819f96Claude1632// Single landing surface. The bolted-on <Land2030 /> prelude was rendering a
1633// SECOND full page above LandingHero — two stacked <h1> documents as you
1634// scrolled. Render exactly one page; the 2030 reboot replaces this wholesale.
4c47454Claude1635export const LandingPage: FC<LandingPageProps> = (props) => (
a819f96Claude1636 <LandingHero {...props} />
2b821b7Claude1637);
1638
4c47454Claude1639export default LandingPage;
1640
2b821b7Claude1641const landingCss = `
958d26aClaude1642 /* ============================================================ */
1643 /* Landing — Editorial-Technical 2026.05 */
1644 /* ============================================================ */
4c47454Claude1645 .landing-root {
1646 position: relative;
958d26aClaude1647 max-width: 1180px;
4c47454Claude1648 margin: 0 auto;
1649 padding: 0 16px;
958d26aClaude1650 }
1651 .landing-root > section { position: relative; }
1652
1653 /* ---------- Hero ---------- */
1654 .landing-hero {
1655 position: relative;
c475ee6Claude1656 padding: var(--s-16) 0 var(--s-20);
958d26aClaude1657 text-align: center;
4c47454Claude1658 overflow: hidden;
1659 }
c475ee6Claude1660 .landing-hero-blob-1 {
1661 animation: hero-blob-drift-1 18s var(--ease, ease) infinite alternate;
1662 }
1663 .landing-hero-blob-2 {
1664 animation: hero-blob-drift-2 22s var(--ease, ease) infinite alternate;
1665 }
1666 @keyframes hero-blob-drift-1 {
1667 0% { transform: translate(0, 0) scale(1); opacity: 0.55; }
1668 100% { transform: translate(8%, 6%) scale(1.18); opacity: 0.75; }
1669 }
1670 @keyframes hero-blob-drift-2 {
1671 0% { transform: translate(0, 0) scale(1); opacity: 0.40; }
1672 100% { transform: translate(-10%, -4%) scale(1.25); opacity: 0.60; }
1673 }
1674
1675 /* ---------- Hero product visual: live AI PR review card ---------- */
1676 .landing-hero-visual {
1677 position: relative;
1678 max-width: 760px;
1679 margin: var(--s-12) auto 0;
1680 padding: 0 16px;
1681 perspective: 1400px;
1682 z-index: 2;
1683 opacity: 0;
1684 animation: hero-visual-in 700ms var(--ease-out-expo, cubic-bezier(0.19, 1, 0.22, 1)) 400ms forwards;
1685 }
1686 @keyframes hero-visual-in {
1687 from { opacity: 0; transform: translateY(20px); }
1688 to { opacity: 1; transform: translateY(0); }
1689 }
1690 .hero-pr-card {
1691 position: relative;
1692 background: linear-gradient(180deg, rgba(15,17,26,0.96) 0%, rgba(8,9,15,0.96) 100%);
1693 border: 1px solid var(--border-strong);
1694 border-radius: var(--r-xl);
1695 overflow: hidden;
1696 text-align: left;
1697 box-shadow:
1698 0 30px 80px -20px rgba(0,0,0,0.65),
1699 0 0 0 1px rgba(140,109,255,0.18),
1700 0 0 60px -10px rgba(140,109,255,0.30);
1701 transform: rotateX(2deg) rotateY(-2deg);
1702 transition: transform 600ms var(--ease, ease);
1703 backdrop-filter: blur(12px);
1704 -webkit-backdrop-filter: blur(12px);
1705 }
1706 .landing-hero-visual:hover .hero-pr-card {
1707 transform: rotateX(0deg) rotateY(0deg);
1708 }
1709 .hero-pr-card::before {
1710 content: '';
1711 position: absolute;
1712 inset: 0;
1713 background: linear-gradient(135deg, rgba(140,109,255,0.10), transparent 35%, transparent 65%, rgba(54,197,214,0.08));
1714 pointer-events: none;
1715 }
1716 .hero-pr-header {
1717 display: flex;
1718 align-items: center;
1719 gap: 12px;
1720 padding: 14px 18px;
1721 border-bottom: 1px solid rgba(255,255,255,0.06);
1722 background: rgba(255,255,255,0.025);
1723 font-size: 13px;
1724 }
1725 .hero-pr-dot {
1726 width: 10px; height: 10px;
1727 border-radius: 50%;
1728 background: var(--green);
1729 box-shadow: 0 0 10px rgba(52,211,153,0.6);
1730 flex-shrink: 0;
1731 }
1732 .hero-pr-title {
1733 color: var(--text-strong);
1734 font-weight: 600;
1735 flex: 1;
1736 overflow: hidden;
1737 text-overflow: ellipsis;
1738 white-space: nowrap;
1739 }
1740 .hero-pr-num {
1741 color: var(--text-faint);
1742 font-family: var(--font-mono);
1743 font-weight: 500;
1744 margin-right: 8px;
1745 }
1746 .hero-pr-status {
1747 display: inline-flex;
1748 align-items: center;
1749 gap: 6px;
1750 padding: 3px 10px;
1751 border-radius: var(--r-full);
1752 background: var(--accent-gradient-faint);
1753 border: 1px solid rgba(140,109,255,0.30);
1754 color: var(--accent);
1755 font-family: var(--font-mono);
1756 font-size: 11px;
1757 letter-spacing: 0.04em;
1758 flex-shrink: 0;
1759 }
1760 .hero-pr-status-pulse {
1761 width: 6px; height: 6px;
1762 border-radius: 50%;
1763 background: var(--accent);
1764 box-shadow: 0 0 0 0 rgba(140,109,255,0.6);
1765 animation: hero-pulse 1.6s ease-out infinite;
1766 }
1767 @keyframes hero-pulse {
1768 0% { box-shadow: 0 0 0 0 rgba(140,109,255,0.55); }
1769 70% { box-shadow: 0 0 0 8px rgba(140,109,255,0); }
1770 100% { box-shadow: 0 0 0 0 rgba(140,109,255,0); }
1771 }
1772
1773 .hero-pr-body {
1774 padding: 0;
1775 }
1776 .hero-pr-file {
1777 display: flex;
1778 align-items: center;
1779 gap: 10px;
1780 padding: 10px 18px;
1781 border-bottom: 1px solid rgba(255,255,255,0.05);
1782 font-family: var(--font-mono);
1783 font-size: 12px;
1784 background: rgba(255,255,255,0.012);
1785 }
1786 .hero-pr-file-icon { color: var(--accent-2); }
1787 .hero-pr-file-name { color: var(--text); flex: 1; }
1788 .hero-pr-file-stats { display: inline-flex; gap: 8px; }
1789 .hero-pr-add { color: var(--green); font-weight: 600; }
1790 .hero-pr-del { color: var(--red); font-weight: 600; }
1791
1792 .hero-pr-diff {
1793 padding: 12px 18px;
1794 font-family: var(--font-mono);
1795 font-feature-settings: var(--mono-feat, 'calt');
1796 font-size: 12.5px;
1797 line-height: 1.7;
1798 color: rgba(237,237,242,0.85);
1799 overflow-x: auto;
1800 }
1801 .hero-pr-hunk {
1802 color: rgba(140,109,255,0.85);
1803 background: rgba(140,109,255,0.06);
1804 padding: 2px 8px;
1805 margin: 0 -8px 4px;
1806 border-radius: 4px;
1807 }
1808 .hero-pr-line-add {
1809 background: rgba(52,211,153,0.08);
1810 color: rgba(167,243,208,0.95);
1811 padding: 0 8px;
1812 margin: 0 -8px;
1813 border-left: 2px solid var(--green);
1814 padding-left: 8px;
1815 }
1816
1817 .hero-pr-comment {
1818 margin: 14px 18px;
1819 padding: 14px 16px;
1820 background: linear-gradient(135deg, rgba(140,109,255,0.08), rgba(54,197,214,0.05));
1821 border: 1px solid rgba(140,109,255,0.25);
1822 border-radius: var(--r-md);
1823 }
1824 .hero-pr-bot-row {
1825 display: flex;
1826 align-items: center;
1827 gap: 8px;
1828 margin-bottom: 8px;
1829 font-size: 12px;
1830 }
1831 .hero-pr-bot-avatar {
1832 width: 22px; height: 22px;
1833 display: inline-flex;
1834 align-items: center;
1835 justify-content: center;
1836 border-radius: 50%;
1837 background: var(--accent-gradient);
1838 font-size: 11px;
1839 box-shadow: 0 0 12px rgba(140,109,255,0.40);
1840 }
1841 .hero-pr-bot-name {
1842 color: var(--text-strong);
1843 font-weight: 600;
1844 }
1845 .hero-pr-bot-meta {
1846 color: var(--text-faint);
1847 font-family: var(--font-mono);
1848 }
1849 .hero-pr-bot-text {
1850 color: var(--text);
1851 font-size: 13px;
1852 line-height: 1.55;
1853 margin: 0;
1854 }
1855 .hero-pr-bot-text code {
1856 background: rgba(255,255,255,0.06);
1857 border: 1px solid rgba(255,255,255,0.10);
1858 padding: 1px 6px;
1859 border-radius: 4px;
1860 font-size: 11.5px;
1861 color: var(--accent);
1862 }
1863 .hero-pr-bot-link { color: var(--accent-2); text-decoration: underline; text-decoration-style: dotted; }
1864
1865 .hero-pr-gates {
1866 display: flex;
1867 flex-wrap: wrap;
1868 gap: 6px;
1869 padding: 12px 18px 16px;
1870 border-top: 1px solid rgba(255,255,255,0.06);
1871 background: rgba(255,255,255,0.012);
1872 }
1873 .hero-pr-gate {
1874 display: inline-flex;
1875 align-items: center;
1876 gap: 6px;
1877 padding: 4px 10px;
1878 border-radius: var(--r-full);
1879 font-family: var(--font-mono);
1880 font-size: 11px;
1881 border: 1px solid;
1882 }
1883 .hero-pr-gate-pass {
1884 color: var(--green);
1885 background: rgba(52,211,153,0.08);
1886 border-color: rgba(52,211,153,0.30);
1887 }
1888 .hero-pr-gate-running {
1889 color: var(--accent);
1890 background: var(--accent-gradient-faint);
1891 border-color: rgba(140,109,255,0.40);
1892 }
1893 .hero-pr-gate-spin {
1894 width: 9px; height: 9px;
1895 border: 1.5px solid rgba(140,109,255,0.30);
1896 border-top-color: var(--accent);
1897 border-radius: 50%;
1898 animation: hero-spin 800ms linear infinite;
1899 }
1900 @keyframes hero-spin {
1901 to { transform: rotate(360deg); }
1902 }
1903
1904 /* Floating accent badges around the card */
1905 .hero-float {
1906 position: absolute;
1907 display: inline-flex;
1908 align-items: center;
1909 gap: 6px;
1910 padding: 6px 12px;
1911 background: rgba(15,17,26,0.92);
1912 border: 1px solid rgba(140,109,255,0.35);
1913 border-radius: var(--r-full);
1914 font-family: var(--font-mono);
1915 font-size: 11px;
1916 color: var(--text);
1917 box-shadow: 0 12px 24px -8px rgba(0,0,0,0.5), 0 0 18px -4px rgba(140,109,255,0.30);
1918 backdrop-filter: blur(8px);
1919 -webkit-backdrop-filter: blur(8px);
1920 }
1921 .hero-float-icon { color: var(--accent); }
1922 .hero-float-1 {
1923 top: -14px;
1924 left: -8px;
1925 animation: hero-float-bob-1 5s var(--ease, ease) infinite alternate;
1926 }
1927 .hero-float-2 {
1928 bottom: -14px;
1929 right: -8px;
1930 animation: hero-float-bob-2 6s var(--ease, ease) infinite alternate;
1931 }
1932 @keyframes hero-float-bob-1 {
1933 from { transform: translate(0, 0); }
1934 to { transform: translate(-8px, -10px); }
1935 }
1936 @keyframes hero-float-bob-2 {
1937 from { transform: translate(0, 0); }
1938 to { transform: translate(8px, 8px); }
1939 }
1940
1941 @media (max-width: 720px) {
1942 .landing-hero-visual { padding: 0 8px; }
1943 .hero-pr-card { transform: none; }
1944 .hero-pr-title { font-size: 12px; }
1945 .hero-pr-diff { font-size: 11px; line-height: 1.6; }
1946 .hero-float { display: none; }
1947 }
958d26aClaude1948 .landing-hero-bg {
1949 position: absolute;
1950 inset: -10% -20%;
1951 pointer-events: none;
1952 z-index: 0;
4c47454Claude1953 }
958d26aClaude1954 .landing-hero-blob {
1955 position: absolute;
1956 border-radius: 50%;
1957 filter: blur(80px);
1958 opacity: 0.55;
3a5755eClaude1959 will-change: transform;
958d26aClaude1960 }
1961 .landing-hero-blob-1 {
1962 top: -10%;
1963 left: 30%;
1964 width: 480px;
1965 height: 480px;
3a5755eClaude1966 background: radial-gradient(circle, rgba(140,109,255,0.65), transparent 65%);
1967 /* 2026 polish — slow drift gives the hero "this is a live product"
1968 feel without being distracting. 24s loop, eased, contained motion. */
1969 animation: landingBlobDrift1 24s ease-in-out infinite;
958d26aClaude1970 }
1971 .landing-hero-blob-2 {
1972 top: 10%;
1973 left: 50%;
1974 width: 380px;
1975 height: 380px;
3a5755eClaude1976 background: radial-gradient(circle, rgba(54,197,214,0.50), transparent 65%);
1977 animation: landingBlobDrift2 28s ease-in-out infinite;
958d26aClaude1978 }
dc26881CC LABS App1979 /* U1 — subtle, low-opacity accent-gradient orb behind the headline.
1980 Sits dead-centre, very blurred, so the hero reads as a real product
3a5755eClaude1981 surface rather than flat-bg + text. 2026 polish — gentle breathing
1982 pulse to give the surface a soft heartbeat. */
dc26881CC LABS App1983 .landing-hero-orb {
1984 top: 18%;
1985 left: 50%;
1986 transform: translateX(-50%);
1987 width: 720px;
1988 height: 720px;
3a5755eClaude1989 background: radial-gradient(circle, rgba(140,109,255,0.28), rgba(54,197,214,0.16) 45%, transparent 70%);
dc26881CC LABS App1990 filter: blur(120px);
3a5755eClaude1991 opacity: 0.6;
dc26881CC LABS App1992 z-index: 0;
3a5755eClaude1993 animation: landingOrbBreath 12s ease-in-out infinite;
dc26881CC LABS App1994 }
1995 :root[data-theme='light'] .landing-hero-orb {
1996 opacity: 0.32;
1997 }
3a5755eClaude1998 @keyframes landingBlobDrift1 {
1999 0%, 100% { transform: translate(0, 0) scale(1); }
2000 33% { transform: translate(40px, -30px) scale(1.08); }
2001 66% { transform: translate(-30px, 25px) scale(0.95); }
2002 }
2003 @keyframes landingBlobDrift2 {
2004 0%, 100% { transform: translate(0, 0) scale(1); }
2005 50% { transform: translate(-50px, 35px) scale(1.12); }
2006 }
2007 @keyframes landingOrbBreath {
2008 0%, 100% { opacity: 0.55; transform: translateX(-50%) scale(1); }
2009 50% { opacity: 0.72; transform: translateX(-50%) scale(1.06); }
2010 }
dc26881CC LABS App2011 @media (prefers-reduced-motion: reduce) {
2012 .landing-hero-blob-1,
2013 .landing-hero-blob-2,
2014 .landing-hero-orb {
2015 animation: none;
2016 }
2017 }
958d26aClaude2018 .landing-hero-grid {
4c47454Claude2019 position: absolute;
2020 inset: 0;
958d26aClaude2021 background-image:
2022 linear-gradient(to right, rgba(255,255,255,0.04) 1px, transparent 1px),
2023 linear-gradient(to bottom, rgba(255,255,255,0.04) 1px, transparent 1px);
2024 background-size: 60px 60px;
2025 mask-image: radial-gradient(ellipse 50% 50% at 50% 30%, #000 0%, transparent 75%);
2026 -webkit-mask-image: radial-gradient(ellipse 50% 50% at 50% 30%, #000 0%, transparent 75%);
2027 }
2028 :root[data-theme='light'] .landing-hero-grid {
2029 background-image:
2030 linear-gradient(to right, rgba(15,16,28,0.06) 1px, transparent 1px),
2031 linear-gradient(to bottom, rgba(15,16,28,0.06) 1px, transparent 1px);
4c47454Claude2032 }
2033
958d26aClaude2034 .landing-hero-inner {
2035 position: relative;
2036 z-index: 1;
2037 max-width: 960px;
2b821b7Claude2038 margin: 0 auto;
2039 }
dc26881CC LABS App2040 /* U1 — every block below the headline obeys a single rhythm. */
958d26aClaude2041 .landing-hero-eyebrow {
dc26881CC LABS App2042 margin: 0 auto var(--space-6);
958d26aClaude2043 color: var(--accent);
2044 }
2045 .landing-hero-eyebrow::before { display: none; }
2046 .landing-hero-pulse {
2047 width: 7px;
2048 height: 7px;
2049 border-radius: 50%;
2050 background: var(--accent);
2051 box-shadow: 0 0 0 0 rgba(140,109,255,0.6);
2052 animation: pulse 1.8s ease-out infinite;
2053 flex-shrink: 0;
2054 }
2055 @keyframes pulse {
2056 0% { box-shadow: 0 0 0 0 rgba(140,109,255,0.55); }
2057 70% { box-shadow: 0 0 0 10px rgba(140,109,255,0); }
2058 100% { box-shadow: 0 0 0 0 rgba(140,109,255,0); }
2059 }
2060
2b821b7Claude2061 .landing-hero-title {
3a5755eClaude2062 /* 2026 polish — bigger, bolder, tighter. Inter Tight at 800 weight
2063 with -0.03em tracking is the modern "AI-startup hero" look that
2064 Vercel/Linear/Cursor all use. clamp() scales gracefully on mobile. */
2065 font-size: clamp(40px, 7vw, 84px);
2066 line-height: 1.02;
2067 letter-spacing: -0.032em;
2068 font-weight: 800;
2069 font-family: var(--font-display);
dc26881CC LABS App2070 margin: 0 0 var(--space-6);
958d26aClaude2071 color: var(--text-strong);
2b821b7Claude2072 }
c963db5Claude2073 .landing-hero-title .gradient-text {
3a5755eClaude2074 /* Richer gradient with a third stop for more depth. Drop-shadow
2075 gives the impression of subtle glow without overpowering the type. */
2076 background-image: linear-gradient(135deg, #c2a8ff 0%, #8c6dff 40%, #5d3dff 70%, #36c5d6 100%);
c963db5Claude2077 -webkit-background-clip: text;
2078 background-clip: text;
2079 -webkit-text-fill-color: transparent;
2080 color: transparent;
3a5755eClaude2081 filter: drop-shadow(0 4px 32px rgba(140, 109, 255, 0.18));
c963db5Claude2082 }
958d26aClaude2083
2b821b7Claude2084 .landing-hero-sub {
3a5755eClaude2085 font-size: clamp(16px, 1.8vw, 22px);
2b821b7Claude2086 color: var(--text-muted);
958d26aClaude2087 max-width: 680px;
dc26881CC LABS App2088 margin: 0 auto var(--space-6);
3a5755eClaude2089 line-height: 1.5;
2090 letter-spacing: -0.008em;
2091 font-weight: 400;
2b821b7Claude2092 }
4c47454Claude2093
2b821b7Claude2094 .landing-hero-ctas {
2095 display: flex;
dc26881CC LABS App2096 gap: var(--space-3);
2b821b7Claude2097 justify-content: center;
2098 flex-wrap: wrap;
dc26881CC LABS App2099 margin-top: 0;
2100 margin-bottom: var(--space-4);
2b821b7Claude2101 }
958d26aClaude2102 .landing-cta-arrow {
2103 transition: transform var(--t-base) var(--ease-spring);
2104 display: inline-block;
2b821b7Claude2105 }
cd4f63bTest User2106
dc26881CC LABS App2107 /* U1 — tertiary text-link row.
2108 Sits directly under the 2-button primary CTA row. Smaller, muted,
2109 so it reads as "by the way" rather than competing for the eye. */
2110 .landing-hero-tertiary {
2111 margin-top: 0;
2112 margin-bottom: var(--space-6);
cd4f63bTest User2113 text-align: center;
dc26881CC LABS App2114 display: inline-flex;
2115 flex-wrap: wrap;
2116 gap: var(--space-2) var(--space-3);
2117 justify-content: center;
2118 align-items: baseline;
2119 width: 100%;
cd4f63bTest User2120 font-size: 13px;
2121 color: var(--text-muted);
dc26881CC LABS App2122 }
2123 .landing-hero-tertiary-link {
2124 color: var(--text-muted);
cd4f63bTest User2125 text-decoration: none;
2126 border-bottom: 1px dashed transparent;
2127 padding-bottom: 1px;
dc26881CC LABS App2128 transition: color var(--t-fast) var(--ease),
2129 border-color var(--t-fast) var(--ease);
cd4f63bTest User2130 }
dc26881CC LABS App2131 .landing-hero-tertiary-link:hover {
2132 color: var(--text-strong);
cd4f63bTest User2133 border-bottom-color: var(--text-muted);
2134 }
dc26881CC LABS App2135 .landing-hero-tertiary-sep {
2136 color: var(--text-faint);
2137 user-select: none;
2138 }
958d26aClaude2139 .btn:hover .landing-cta-arrow,
2140 .landing-cta-primary:hover .landing-cta-arrow {
2141 transform: translateX(4px);
8e9f1d9Claude2142 }
2b821b7Claude2143
93fe97eClaude2144 /* BLOCK Q1 — flagship "Add to Claude Desktop" CTA.
2145 Gradient-bordered + accent text so it reads as a peer of the primary
bc7654bCC LABS App2146 Sign-up CTA, not a third secondary. Theme-aware: inner fill uses
2147 --bg-elevated so it's white on light and dark on dark, never the
2148 jarring near-black on white we shipped first time. Subtle elevation
2149 on hover; static when the visitor opts out of motion. */
93fe97eClaude2150 .landing-cta-dxt {
2151 position: relative;
bc7654bCC LABS App2152 background: var(--bg-elevated);
2153 color: var(--text-strong);
93fe97eClaude2154 border: 1px solid transparent;
2155 background-image:
bc7654bCC LABS App2156 linear-gradient(var(--bg-elevated), var(--bg-elevated)),
93fe97eClaude2157 linear-gradient(90deg, #8c6dff 0%, #36c5d6 100%);
2158 background-origin: border-box;
2159 background-clip: padding-box, border-box;
2160 transition: transform var(--t-base, 180ms) var(--ease-spring, ease),
2161 box-shadow var(--t-base, 180ms) var(--ease-spring, ease);
2162 }
2163 .landing-cta-dxt:hover {
2164 transform: translateY(-2px);
2165 box-shadow: 0 8px 24px -8px rgba(140, 109, 255, 0.45);
2166 }
2167 @media (prefers-reduced-motion: reduce) {
2168 .landing-cta-dxt,
2169 .landing-cta-dxt:hover {
2170 transform: none;
2171 transition: none;
2172 }
2173 }
2174
adf5e18Claude2175 /* "Migrate from GitHub" CTA — secondary, but strong enough to stand
2176 alongside the primary. Uses a subtle amber/violet mix so it reads as
2177 action-oriented without competing with the green primary CTA. */
2178 .landing-cta-migrate {
2179 position: relative;
2180 background: var(--bg-elevated);
2181 color: var(--text-strong);
2182 border: 1px solid var(--border-strong);
2183 transition: border-color var(--t-base, 180ms) var(--ease, ease),
2184 transform var(--t-base, 180ms) var(--ease-spring, ease),
2185 box-shadow var(--t-base, 180ms) var(--ease, ease);
2186 }
2187 .landing-cta-migrate:hover {
2188 border-color: rgba(140,109,255,0.55);
2189 transform: translateY(-2px);
2190 box-shadow: 0 8px 22px -8px rgba(140,109,255,0.30);
2191 text-decoration: none;
2192 color: var(--text-strong);
2193 }
2194 @media (prefers-reduced-motion: reduce) {
2195 .landing-cta-migrate,
2196 .landing-cta-migrate:hover {
2197 transform: none;
2198 transition: none;
2199 }
2200 }
2201
dc26881CC LABS App2202 /* L8 — free-tier reassurance link beneath the CTA row.
2203 U1 — rhythm snapped to var(--space-6). */
5f2e749Claude2204 .landing-hero-freenote {
dc26881CC LABS App2205 margin: 0 auto var(--space-6);
5f2e749Claude2206 font-size: var(--t-sm);
2207 color: var(--text-muted);
2208 text-align: center;
2209 }
2210 .landing-hero-freenote-link {
2211 color: var(--accent);
2212 text-decoration: none;
2213 font-weight: 500;
2214 border-bottom: 1px dotted rgba(140,109,255,0.4);
2215 transition: color var(--t-fast) var(--ease), border-color var(--t-fast) var(--ease);
2216 }
2217 .landing-hero-freenote-link:hover {
2218 color: var(--text-strong);
2219 border-bottom-color: var(--accent);
2220 }
2221
4c47454Claude2222 .landing-hero-caption {
dc26881CC LABS App2223 margin: 0 auto var(--space-6);
958d26aClaude2224 font-size: var(--t-sm);
4c47454Claude2225 color: var(--text-muted);
958d26aClaude2226 display: flex;
2227 flex-wrap: wrap;
2228 align-items: center;
2229 justify-content: center;
dc26881CC LABS App2230 gap: var(--space-3);
2b821b7Claude2231 }
958d26aClaude2232 .landing-hero-cmd {
2233 display: inline-flex;
2234 align-items: center;
2235 gap: 4px;
2236 padding: 6px 12px;
2237 background: var(--bg-elevated);
4c47454Claude2238 border: 1px solid var(--border);
958d26aClaude2239 border-radius: var(--r-full);
2240 box-shadow: var(--elev-1);
2241 }
2242 .landing-hero-cmd .kbd {
2243 border: 0;
2244 background: transparent;
2245 padding: 0 4px;
2246 color: var(--text-muted);
2247 font-size: 12px;
2248 }
2249 .landing-hero-cmd .kbd:nth-last-of-type(1) { color: var(--accent); }
2250 .landing-hero-arrow {
2251 color: var(--text-faint);
2252 font-size: 13px;
2253 margin: 0 2px;
2b821b7Claude2254 }
4c47454Claude2255
2256 .landing-stats {
dc26881CC LABS App2257 margin: 0 auto;
958d26aClaude2258 font-family: var(--font-mono);
2259 font-size: 12px;
2260 color: var(--text-muted);
2261 display: flex;
2262 align-items: center;
2263 justify-content: center;
dc26881CC LABS App2264 gap: var(--space-2);
958d26aClaude2265 flex-wrap: wrap;
2266 letter-spacing: 0.02em;
2267 }
2268 .landing-stats strong {
2269 color: var(--text-strong);
2270 font-weight: 600;
2271 font-feature-settings: 'tnum';
2272 }
2273 .landing-stats-sep { opacity: 0.4; }
2274
c963db5Claude2275 /* ---------- Capability grid (crontech-style uppercase tracked) ---------- */
2276 .landing-caps {
2277 margin: var(--s-12) auto var(--s-16);
2278 max-width: 1080px;
2279 padding: var(--s-7) var(--s-4);
958d26aClaude2280 border-top: 1px solid var(--border-subtle);
2281 border-bottom: 1px solid var(--border-subtle);
c963db5Claude2282 }
2283 .landing-caps-grid {
2284 display: grid;
2285 grid-template-columns: repeat(4, 1fr);
2286 gap: 24px 16px;
958d26aClaude2287 text-align: center;
2288 }
c963db5Claude2289 .landing-cap {
958d26aClaude2290 font-family: var(--font-mono);
2291 font-size: 11px;
c963db5Claude2292 font-weight: 600;
958d26aClaude2293 text-transform: uppercase;
c963db5Claude2294 letter-spacing: 0.16em;
2295 color: var(--text-muted);
2296 transition: color var(--t-fast) var(--ease);
958d26aClaude2297 }
c963db5Claude2298 .landing-cap:hover { color: var(--text-strong); }
2299 @media (max-width: 800px) {
2300 .landing-caps-grid { grid-template-columns: repeat(2, 1fr); gap: 18px 12px; }
2301 }
2302 @media (max-width: 480px) {
2303 .landing-caps-grid { grid-template-columns: 1fr; }
2304 }
2305
2306 /* ---------- Big stat row (crontech-style hero closer) ---------- */
2307 .landing-bigstats {
2308 margin: var(--s-10) auto var(--s-20);
2309 max-width: 1180px;
2310 padding: 0 var(--s-4);
2311 }
2312 .landing-bigstats-grid {
2313 display: grid;
2314 grid-template-columns: repeat(4, 1fr);
2315 gap: 32px;
2316 text-align: left;
958d26aClaude2317 }
c963db5Claude2318 .landing-bigstat {
2319 padding: var(--s-2) 0;
2320 }
2321 .landing-bigstat-num {
958d26aClaude2322 font-family: var(--font-display);
c963db5Claude2323 font-size: clamp(28px, 3.5vw, 44px);
2324 line-height: 1.05;
2325 letter-spacing: -0.03em;
2326 font-weight: 700;
2327 color: var(--text-strong);
2328 margin-bottom: var(--s-2);
2329 }
2330 .landing-bigstat-label {
2331 font-family: var(--font-mono);
2332 font-size: 11px;
958d26aClaude2333 font-weight: 500;
c963db5Claude2334 text-transform: uppercase;
2335 letter-spacing: 0.14em;
2336 color: var(--text-faint);
2b821b7Claude2337 }
c963db5Claude2338 @media (max-width: 800px) {
2339 .landing-bigstats-grid { grid-template-columns: repeat(2, 1fr); gap: 28px 16px; }
2340 }
2341 @media (max-width: 480px) {
2342 .landing-bigstats-grid { grid-template-columns: 1fr; gap: 24px; }
958d26aClaude2343 }
2344
2345 /* ---------- Section base ---------- */
2346 .landing-section { margin: var(--s-20) auto; }
2b821b7Claude2347
4c47454Claude2348 /* ---------- Feature grid ---------- */
2349 .landing-features {
2b821b7Claude2350 display: grid;
2351 grid-template-columns: repeat(3, 1fr);
958d26aClaude2352 gap: 16px;
2b821b7Claude2353 }
2354 .landing-feature {
958d26aClaude2355 background: var(--bg-elevated);
2b821b7Claude2356 border: 1px solid var(--border);
958d26aClaude2357 border-radius: var(--r-lg);
2358 padding: var(--s-7);
2359 position: relative;
2360 overflow: hidden;
2361 isolation: isolate;
2362 transition:
2363 transform var(--t-base) var(--ease-out-quart),
2364 border-color var(--t-base) var(--ease),
2365 box-shadow var(--t-base) var(--ease);
2366 }
2367 .landing-feature::before {
2368 content: '';
2369 position: absolute;
2370 inset: 0;
2371 background: radial-gradient(120% 100% at 0% 0%, rgba(140,109,255,0.08), transparent 55%);
2372 opacity: 0;
2373 transition: opacity var(--t-base) var(--ease);
2374 z-index: -1;
4c47454Claude2375 }
2376 .landing-feature:hover {
958d26aClaude2377 transform: translateY(-3px);
2378 border-color: var(--border-strong);
2379 box-shadow: var(--elev-2);
2b821b7Claude2380 }
958d26aClaude2381 .landing-feature:hover::before { opacity: 1; }
2b821b7Claude2382 .landing-feature-icon {
4c47454Claude2383 display: inline-flex;
2384 align-items: center;
2385 justify-content: center;
958d26aClaude2386 width: 40px;
2387 height: 40px;
2388 border-radius: var(--r);
2389 background: var(--accent-gradient-soft);
2390 color: var(--accent);
2391 margin-bottom: var(--s-4);
2392 border: 1px solid rgba(140,109,255,0.20);
2b821b7Claude2393 }
2394 .landing-feature-title {
958d26aClaude2395 font-family: var(--font-display);
2396 font-size: 19px;
2397 font-weight: 600;
2398 letter-spacing: -0.018em;
2399 margin: 0 0 var(--s-2);
2400 color: var(--text-strong);
2b821b7Claude2401 }
2402 .landing-feature-desc {
958d26aClaude2403 font-size: var(--t-sm);
2404 color: var(--text-muted);
2405 line-height: 1.6;
2406 margin: 0;
2407 }
2408
2409 /* ---------- Walkthrough ---------- */
2410 .landing-walk-grid {
2411 display: grid;
2412 grid-template-columns: repeat(4, 1fr);
2413 gap: 16px;
2414 counter-reset: walk;
2415 }
2416 .landing-walk-step {
2417 position: relative;
2418 padding: var(--s-7) var(--s-6) var(--s-6);
2419 background: var(--bg-elevated);
2420 border: 1px solid var(--border);
2421 border-radius: var(--r-lg);
2422 }
2423 .landing-walk-step::after {
2424 content: '';
2425 position: absolute;
2426 top: 50%;
2427 right: -12px;
2428 width: 12px;
2429 height: 1px;
2430 background: var(--border-strong);
2431 }
2432 .landing-walk-step:last-child::after { display: none; }
2433 .landing-walk-num {
2434 display: inline-block;
2435 font-family: var(--font-mono);
2436 font-size: 11px;
2437 color: var(--accent);
2438 background: var(--accent-gradient-faint);
2439 border: 1px solid rgba(140,109,255,0.30);
2440 padding: 3px 8px;
2441 border-radius: var(--r-full);
2442 letter-spacing: 0.06em;
2443 margin-bottom: var(--s-3);
2444 }
2445 .landing-walk-title {
2446 font-family: var(--font-display);
2447 font-size: 22px;
2448 font-weight: 600;
2449 letter-spacing: -0.022em;
2450 margin: 0 0 var(--s-2);
2451 color: var(--text-strong);
2452 }
2453 .landing-walk-desc {
2454 font-size: var(--t-sm);
2b821b7Claude2455 color: var(--text-muted);
2456 line-height: 1.55;
2457 margin: 0;
2458 }
2459
958d26aClaude2460 /* ---------- Terminal ---------- */
2461 .landing-terminal-section { margin-top: var(--s-16); }
4c47454Claude2462 .landing-terminal-wrap {
2463 display: flex;
2b821b7Claude2464 justify-content: center;
2465 }
4c47454Claude2466 .landing-terminal {
2467 width: 100%;
958d26aClaude2468 max-width: 820px;
2469 background: linear-gradient(180deg, #0a0b12 0%, #06070c 100%);
2470 border: 1px solid var(--border-strong);
2471 border-radius: var(--r-lg);
2472 overflow: hidden;
2473 box-shadow: var(--elev-3), 0 0 60px -10px rgba(140,109,255,0.18);
4c47454Claude2474 text-align: left;
2b821b7Claude2475 }
958d26aClaude2476 :root[data-theme='light'] .landing-terminal {
2477 background: linear-gradient(180deg, #0f111a 0%, #06070c 100%);
2478 }
2479 .landing-terminal-chrome {
2480 display: flex;
2481 align-items: center;
2482 gap: 7px;
2483 padding: 11px 14px;
2484 background: rgba(255,255,255,0.025);
2485 border-bottom: 1px solid rgba(255,255,255,0.06);
2486 position: relative;
2487 }
2488 .landing-terminal-dot {
2489 width: 11px;
2490 height: 11px;
2491 border-radius: 50%;
2492 flex-shrink: 0;
2493 }
2494 .landing-terminal-dot-r { background: #ff5f57; }
2495 .landing-terminal-dot-y { background: #febc2e; }
2496 .landing-terminal-dot-g { background: #28c840; }
2497 .landing-terminal-title {
2498 position: absolute;
2499 left: 50%;
2500 transform: translateX(-50%);
2501 font-family: var(--font-mono);
2502 font-size: 11px;
2503 color: rgba(237,237,242,0.55);
2504 letter-spacing: 0.01em;
2505 }
2506 .landing-terminal-body {
2507 padding: var(--s-6) var(--s-7);
2508 font-family: var(--font-mono);
2509 font-feature-settings: var(--mono-feat);
2510 font-size: 13.5px;
2511 line-height: 1.85;
2512 color: rgba(237,237,242,0.92);
2513 }
4c47454Claude2514 .landing-term-line {
2515 display: flex;
2516 gap: 10px;
2517 white-space: pre-wrap;
2518 word-break: break-all;
2b821b7Claude2519 }
958d26aClaude2520 .landing-term-out { color: rgba(237,237,242,0.7); }
2521 .landing-term-prompt { color: rgba(140,109,255,0.85); user-select: none; flex-shrink: 0; }
2522 .landing-term-meta { color: rgba(237,237,242,0.45); }
2523 .landing-term-ok { color: var(--green); user-select: none; flex-shrink: 0; }
2524 .landing-term-ok-line { color: rgba(237,237,242,0.92); }
2525 .landing-term-cursor { margin-top: 4px; }
2526 .landing-term-blink {
2527 animation: blink 1.05s steps(2) infinite;
2528 color: var(--accent);
2529 }
2530 @keyframes blink { 50% { opacity: 0; } }
2531
2532 /* ---------- Comparison ---------- */
2533 .landing-compare {
2534 max-width: 920px;
2535 margin: 0 auto;
2536 border: 1px solid var(--border);
2537 border-radius: var(--r-lg);
2538 overflow: hidden;
2539 background: var(--bg-elevated);
2540 }
2541 .landing-compare-row {
2542 display: grid;
2543 grid-template-columns: 1fr 180px 180px;
2544 align-items: center;
2545 padding: 14px 20px;
2546 border-bottom: 1px solid var(--border-subtle);
2547 font-size: var(--t-sm);
2548 transition: background var(--t-fast) var(--ease);
2549 }
2550 .landing-compare-row:last-child { border-bottom: none; }
2551 .landing-compare-row:hover { background: var(--bg-hover); }
2552 .landing-compare-feature {
2553 color: var(--text-strong);
2554 font-weight: 500;
2555 }
2556 .landing-compare-them, .landing-compare-us {
2557 text-align: center;
2558 font-family: var(--font-mono);
2559 font-size: 12px;
2560 color: var(--text-muted);
2561 }
2562 .landing-compare-us { color: var(--green); font-weight: 500; }
2563 .landing-compare-hl .landing-compare-us {
2564 color: var(--accent);
2565 font-weight: 600;
2b821b7Claude2566 }
958d26aClaude2567 .landing-compare-hl .landing-compare-feature::after {
2568 content: 'NEW';
2569 margin-left: 8px;
2570 padding: 1px 6px;
2571 border-radius: 4px;
2572 background: var(--accent-gradient-faint);
2573 color: var(--accent);
2574 font-family: var(--font-mono);
2575 font-size: 9px;
2576 letter-spacing: 0.1em;
2577 font-weight: 600;
2578 vertical-align: 1px;
2579 }
2580 @media (max-width: 720px) {
2581 .landing-compare-row { grid-template-columns: 1fr 80px 80px; padding: 12px 14px; }
2582 .landing-compare-hl .landing-compare-feature::after { display: none; }
2583 }
2584
2585 /* ---------- Pricing ---------- */
2586 .landing-pricing {
2587 display: grid;
2588 grid-template-columns: repeat(3, 1fr);
2589 gap: 16px;
2590 max-width: 1080px;
2591 margin: 0 auto;
2592 align-items: stretch;
2593 }
2594 .landing-price-card {
2595 position: relative;
2596 background: var(--bg-elevated);
2597 border: 1px solid var(--border);
2598 border-radius: var(--r-lg);
2599 padding: var(--s-7);
2600 display: flex;
2601 flex-direction: column;
2602 gap: var(--s-4);
2603 transition: border-color var(--t-base) var(--ease), transform var(--t-base) var(--ease);
2604 }
2605 .landing-price-card:hover {
2606 border-color: var(--border-strong);
2607 transform: translateY(-2px);
2608 }
2609 .landing-price-hl {
2610 border-color: rgba(140,109,255,0.35);
2611 box-shadow: var(--elev-2), 0 0 0 1px rgba(140,109,255,0.25);
2612 background:
2613 linear-gradient(180deg, rgba(140,109,255,0.05), transparent 50%),
2614 var(--bg-elevated);
2615 }
2616 .landing-price-hl:hover { border-color: rgba(140,109,255,0.55); }
2617 .landing-price-badge {
2618 position: absolute;
2619 top: -10px;
2620 left: 50%;
2621 transform: translateX(-50%);
2622 padding: 3px 12px;
2623 background: var(--accent-gradient);
2624 color: #fff;
2625 font-family: var(--font-mono);
2626 font-size: 10px;
2627 letter-spacing: 0.1em;
2628 text-transform: uppercase;
2629 font-weight: 600;
2630 border-radius: var(--r-full);
2631 box-shadow: 0 4px 12px -2px rgba(140,109,255,0.4);
2632 }
2633 .landing-price-tier {
2634 font-family: var(--font-mono);
2635 font-size: 11px;
2636 text-transform: uppercase;
2637 letter-spacing: 0.16em;
2638 color: var(--text-muted);
2639 }
2640 .landing-price-amount {
2641 display: flex;
2642 align-items: baseline;
2643 gap: 8px;
2644 }
2645 .landing-price-num {
2646 font-family: var(--font-display);
2647 font-size: 40px;
2648 font-weight: 600;
2649 letter-spacing: -0.03em;
2650 color: var(--text-strong);
2651 }
2652 .landing-price-cad {
2653 font-size: var(--t-sm);
2654 color: var(--text-faint);
2655 }
2656 .landing-price-desc {
2657 font-size: var(--t-sm);
2658 color: var(--text-muted);
2659 line-height: 1.55;
2660 margin: 0;
2661 }
2662 .landing-price-features {
2663 list-style: none;
2664 padding: 0;
2665 margin: 0;
2666 display: flex;
2667 flex-direction: column;
2668 gap: 8px;
2669 font-size: var(--t-sm);
2670 color: var(--text);
2671 }
2672 .landing-price-features li {
2673 display: flex;
2674 align-items: center;
2675 gap: 9px;
2676 }
2677 .landing-price-check {
2678 color: var(--accent);
2679 font-weight: 600;
4c47454Claude2680 flex-shrink: 0;
2b821b7Claude2681 }
958d26aClaude2682 .landing-price-cta { margin-top: auto; }
2683
2684 /* ---------- Closing CTA ---------- */
2685 .landing-cta-section { margin: var(--s-20) auto var(--s-16); }
2686 .landing-cta-card {
2687 position: relative;
2688 text-align: center;
2689 padding: var(--s-16) var(--s-7);
2690 border: 1px solid var(--border-strong);
2691 border-radius: var(--r-2xl);
2692 background: var(--bg-elevated);
2693 overflow: hidden;
2694 isolation: isolate;
2695 }
2696 .landing-cta-bg {
2697 position: absolute;
2698 inset: 0;
2699 z-index: -1;
2700 background:
2701 radial-gradient(60% 100% at 50% 0%, rgba(140,109,255,0.16), transparent 65%),
2702 radial-gradient(40% 80% at 80% 100%, rgba(54,197,214,0.10), transparent 65%);
2703 }
2704 .landing-cta-card::after {
2705 content: '';
2706 position: absolute;
2707 inset: 0;
2708 z-index: -1;
2709 background-image: radial-gradient(rgba(255,255,255,0.04) 1px, transparent 1px);
2710 background-size: 24px 24px;
2711 mask-image: radial-gradient(ellipse at center, #000 0%, transparent 65%);
2712 -webkit-mask-image: radial-gradient(ellipse at center, #000 0%, transparent 65%);
2713 opacity: 0.6;
2714 }
2715 :root[data-theme='light'] .landing-cta-card::after {
2716 background-image: radial-gradient(rgba(15,16,28,0.07) 1px, transparent 1px);
2717 }
2718 .landing-cta-card .eyebrow { justify-content: center; }
2719 .landing-cta-title {
2720 font-family: var(--font-display);
2721 font-size: clamp(28px, 4.4vw, 56px);
2722 line-height: 1.05;
2723 letter-spacing: -0.03em;
2724 font-weight: 600;
2725 margin: var(--s-3) 0 var(--s-4);
2726 color: var(--text-strong);
2727 }
2728 .landing-cta-sub {
2729 font-size: var(--t-md);
2730 color: var(--text-muted);
2731 max-width: 560px;
2732 margin: 0 auto var(--s-8);
2733 line-height: 1.55;
2734 }
2735 .landing-cta-buttons {
2736 display: flex;
2737 gap: 12px;
2738 justify-content: center;
2739 flex-wrap: wrap;
2740 }
2b821b7Claude2741
2742 /* ---------- Responsive ---------- */
958d26aClaude2743 @media (max-width: 960px) {
2744 .landing-features { grid-template-columns: repeat(2, 1fr); }
2745 .landing-walk-grid { grid-template-columns: repeat(2, 1fr); }
2746 .landing-walk-step::after { display: none; }
2747 .landing-pricing { grid-template-columns: 1fr; max-width: 480px; }
2748 }
2749 @media (max-width: 640px) {
2750 .landing-hero { padding: var(--s-14) 0 var(--s-10); }
2751 .landing-hero-cmd { flex-wrap: wrap; justify-content: center; }
2752 .landing-hero-ctas { flex-direction: column; align-items: stretch; }
2753 .landing-hero-ctas .btn { width: 100%; justify-content: center; }
2754 .landing-features { grid-template-columns: 1fr; }
2755 .landing-walk-grid { grid-template-columns: 1fr; }
2756 .landing-section { margin: var(--s-12) auto; }
2757 .landing-cta-card { padding: var(--s-10) var(--s-5); }
2758 .landing-cta-buttons .btn { width: 100%; justify-content: center; }
2b821b7Claude2759 }
52ad8b1Claude2760
2761 /* ---------- L4 social-proof counters ---------- */
2762 .landing-counters {
2763 margin: var(--s-10) auto var(--s-12);
2764 max-width: 1180px;
2765 padding: 0 var(--s-4);
2766 }
2767 .landing-counters-grid {
2768 display: grid;
2769 grid-template-columns: repeat(6, 1fr);
2770 gap: 20px;
2771 text-align: left;
2772 }
2773 .landing-counter {
2774 padding: var(--s-3) 0;
2775 border-top: 1px solid var(--border-subtle);
2776 }
2777 .landing-counter-num {
2778 font-family: var(--font-display);
2779 font-size: clamp(24px, 3vw, 38px);
2780 line-height: 1.05;
2781 letter-spacing: -0.03em;
2782 font-weight: 700;
2783 margin-bottom: 6px;
2784 font-feature-settings: 'tnum';
2785 background-image: var(--accent-gradient);
2786 -webkit-background-clip: text;
2787 background-clip: text;
2788 -webkit-text-fill-color: transparent;
2789 color: transparent;
2790 }
2791 .landing-counter-label {
2792 font-family: var(--font-mono);
2793 font-size: 10.5px;
2794 font-weight: 500;
2795 text-transform: uppercase;
2796 letter-spacing: 0.12em;
2797 color: var(--text-faint);
2798 line-height: 1.4;
2799 }
2800 @media (max-width: 960px) {
2801 .landing-counters-grid { grid-template-columns: repeat(3, 1fr); gap: 20px 16px; }
2802 }
2803 @media (max-width: 540px) {
2804 .landing-counters-grid { grid-template-columns: repeat(2, 1fr); gap: 18px 12px; }
2805 }
5f2e749Claude2806
dc26881CC LABS App2807 /* ---------- L10/U1 hero install snippet ----------
2808 U1: wrapped in a labelled "power users" panel and re-located
2809 beneath the CTA + tertiary rows so it no longer competes with
2810 the primary calls to action. */
2811 .landing-hero-install-wrap {
2812 margin: 0 auto var(--space-6);
2813 text-align: center;
2814 }
2815 .landing-hero-install-label {
2816 display: inline-block;
2817 margin-bottom: var(--space-2);
2818 font-family: var(--font-mono);
2819 font-size: 11px;
2820 letter-spacing: 0.08em;
2821 text-transform: uppercase;
2822 color: var(--text-faint);
2823 }
5f2e749Claude2824 .landing-hero-install {
2825 display: inline-flex;
2826 align-items: stretch;
2827 gap: 0;
dc26881CC LABS App2828 margin: 0 auto;
5f2e749Claude2829 background: var(--bg-elevated);
2830 border: 1px solid var(--border-strong);
2831 border-radius: var(--r);
2832 box-shadow: var(--elev-1);
2833 overflow: hidden;
2834 max-width: 100%;
2835 font-family: var(--font-mono);
2836 }
2837 .landing-hero-install-code {
2838 display: inline-flex;
2839 align-items: center;
2840 gap: 10px;
2841 padding: 10px 14px;
2842 font-size: 13.5px;
2843 color: var(--text-strong);
2844 background: transparent;
2845 border: 0;
2846 white-space: nowrap;
2847 overflow-x: auto;
2848 }
2849 .landing-hero-install-prompt {
2850 color: var(--accent);
2851 user-select: none;
2852 }
2853 .landing-hero-install-copy {
2854 appearance: none;
2855 border: 0;
2856 border-left: 1px solid var(--border);
2857 background: transparent;
2858 color: var(--text-muted);
2859 font-family: var(--font-mono);
2860 font-size: 12px;
2861 font-weight: 600;
2862 letter-spacing: 0.06em;
2863 text-transform: uppercase;
2864 padding: 0 16px;
2865 cursor: pointer;
2866 transition: background var(--t-fast) var(--ease), color var(--t-fast) var(--ease);
2867 }
2868 .landing-hero-install-copy:hover {
2869 background: var(--accent-gradient-faint);
2870 color: var(--accent);
2871 }
2872 .landing-hero-install-copy[data-copied="1"] {
2873 color: var(--green, #34d399);
2874 }
2875
b5d207bClaude2876 /* ---------- L10/U1 hero activity rail ----------
dc26881CC LABS App2877 U1 — tightened into a single horizontal strip. The 1px gradient
2878 rule on top is the same accent the headline uses, so the rail
2879 reads as part of the hero composition rather than a stray list. */
5f2e749Claude2880 .landing-hero-rail {
2881 list-style: none;
dc26881CC LABS App2882 padding: var(--space-4) 0 0;
2883 margin: 0 auto var(--space-6);
5f2e749Claude2884 display: flex;
2885 flex-wrap: wrap;
2886 justify-content: center;
dc26881CC LABS App2887 gap: var(--space-2) var(--space-6);
5f2e749Claude2888 font-family: var(--font-sans);
dc26881CC LABS App2889 font-size: 12px;
2890 color: var(--text-faint);
5f2e749Claude2891 max-width: 760px;
dc26881CC LABS App2892 position: relative;
2893 }
2894 .landing-hero-rail::before {
2895 content: '';
2896 position: absolute;
2897 top: 0;
2898 left: 50%;
2899 transform: translateX(-50%);
2900 width: 120px;
2901 height: 1px;
2902 background: var(--accent-gradient);
2903 opacity: 0.45;
2904 border-radius: 9999px;
5f2e749Claude2905 }
2906 .landing-hero-rail li {
2907 display: inline-flex;
dc26881CC LABS App2908 align-items: baseline;
2909 gap: var(--space-2);
5f2e749Claude2910 line-height: 1.4;
2911 }
2912 .landing-hero-rail strong {
2913 color: var(--text-strong);
2914 font-weight: 600;
2915 font-feature-settings: 'tnum';
dc26881CC LABS App2916 font-size: 14px;
2917 letter-spacing: -0.01em;
5f2e749Claude2918 }
dc26881CC LABS App2919 .landing-hero-rail-label {
2920 color: var(--text-muted);
2921 letter-spacing: 0.01em;
5f2e749Claude2922 }
dc26881CC LABS App2923 /* Backwards-compat: nothing references this any more but if a stale
2924 fragment lingers it's still hidden cleanly rather than orphaned. */
2925 .landing-hero-rail-check { display: none; }
5f2e749Claude2926
2927 /* ---------- L10 three-reasons section ---------- */
2928 .landing-reasons { margin-top: var(--s-12); }
2929 .landing-reasons-grid {
2930 display: grid;
2931 grid-template-columns: repeat(3, 1fr);
2932 gap: 16px;
2933 }
2934 .landing-reason {
2935 background: var(--bg-elevated);
2936 border: 1px solid var(--border);
2937 border-radius: var(--r-lg);
2938 padding: var(--s-7);
2939 display: flex;
2940 flex-direction: column;
2941 gap: var(--s-3);
2942 transition: border-color var(--t-base) var(--ease), transform var(--t-base) var(--ease);
2943 }
2944 .landing-reason:hover {
2945 border-color: var(--border-strong);
2946 transform: translateY(-2px);
2947 }
2948 .landing-reason-icon {
2949 display: inline-flex;
2950 align-items: center;
2951 justify-content: center;
2952 width: 40px;
2953 height: 40px;
2954 border-radius: var(--r);
2955 background: var(--accent-gradient-soft);
2956 color: var(--accent);
2957 border: 1px solid rgba(140,109,255,0.20);
2958 }
2959 .landing-reason-title {
2960 font-family: var(--font-display);
2961 font-size: 20px;
2962 font-weight: 600;
2963 letter-spacing: -0.02em;
2964 margin: 0;
2965 color: var(--text-strong);
2966 }
2967 .landing-reason-body {
2968 font-size: var(--t-sm);
2969 color: var(--text-muted);
2970 line-height: 1.55;
2971 margin: 0;
2972 }
2973 .landing-reasons-code {
2974 display: block;
2975 font-family: var(--font-mono);
2976 font-size: 12px;
2977 color: var(--text-strong);
2978 background: var(--bg);
2979 border: 1px solid var(--border);
2980 border-radius: var(--r);
2981 padding: 8px 12px;
2982 overflow-x: auto;
2983 white-space: nowrap;
2984 }
2985 .landing-reason-link {
2986 margin-top: auto;
2987 display: inline-flex;
2988 align-items: center;
2989 gap: 6px;
2990 color: var(--accent);
2991 font-size: var(--t-sm);
2992 font-weight: 500;
2993 text-decoration: none;
2994 }
2995 .landing-reason-link:hover { text-decoration: underline; }
2996 @media (max-width: 960px) {
2997 .landing-reasons-grid { grid-template-columns: 1fr; max-width: 520px; margin: 0 auto; }
2998 }
2999
3000 /* ---------- L10 "How is this different" pull-quote ---------- */
3001 .landing-pullquote-section {
3002 margin: var(--s-20) auto var(--s-12);
3003 max-width: 920px;
3004 padding: 0 var(--s-4);
3005 text-align: center;
3006 }
3007 .landing-pullquote {
3008 margin: 0;
3009 padding: var(--s-10) var(--s-7);
3010 background:
3011 radial-gradient(80% 100% at 50% 0%, rgba(140,109,255,0.10), transparent 65%),
3012 var(--bg-elevated);
3013 border: 1px solid var(--border-strong);
3014 border-radius: var(--r-xl);
3015 position: relative;
3016 overflow: hidden;
3017 }
3018 .landing-pullquote-eyebrow {
3019 font-family: var(--font-mono);
3020 font-size: 11px;
3021 font-weight: 600;
3022 letter-spacing: 0.16em;
3023 text-transform: uppercase;
3024 color: var(--accent);
3025 margin-bottom: var(--s-4);
3026 }
3027 .landing-pullquote-text {
3028 font-family: var(--font-display);
3029 font-size: clamp(20px, 2.4vw, 28px);
3030 line-height: 1.4;
3031 letter-spacing: -0.018em;
3032 color: var(--text-strong);
3033 margin: 0 auto;
3034 max-width: 760px;
3035 quotes: "\\201C" "\\201D";
3036 }
3037 .landing-pullquote-text::before { content: open-quote; color: var(--accent); margin-right: 4px; }
3038 .landing-pullquote-text::after { content: close-quote; color: var(--accent); margin-left: 4px; }
3039 .landing-pullquote-link {
3040 display: inline-flex;
3041 align-items: center;
3042 gap: 6px;
3043 margin-top: var(--s-6);
3044 color: var(--accent);
3045 font-size: var(--t-sm);
3046 font-weight: 500;
3047 text-decoration: none;
3048 }
3049 .landing-pullquote-link:hover { text-decoration: underline; }
3050
3051 /* ---------- L10 hero responsive overrides ---------- */
3052 @media (max-width: 640px) {
3053 .landing-hero-install { width: 100%; }
3054 .landing-hero-install-code { flex: 1; font-size: 12px; }
3055 .landing-hero-rail { flex-direction: column; align-items: flex-start; gap: 6px; padding: 0 var(--s-3); }
3056 .landing-hero-rail li { width: 100%; }
3057 }
534f04aClaude3058
3059 /* ============================================================ */
3060 /* Block M1 — Live-now demo feed */
3061 /* ============================================================ */
3062 .landing-livenow {
3063 margin: var(--s-8) 0 var(--s-6);
3064 padding: var(--s-6) 0 var(--s-4);
3065 }
3066 .landing-livenow-head {
3067 text-align: center;
3068 margin-bottom: var(--s-6);
3069 }
3070 .landing-livenow-eyebrow {
3071 display: inline-flex;
3072 align-items: center;
3073 gap: 8px;
3074 padding: 4px 12px;
3075 border-radius: var(--r-full);
3076 background: rgba(52,211,153,0.08);
3077 border: 1px solid rgba(52,211,153,0.25);
3078 color: var(--green);
3079 font-family: var(--font-mono, ui-monospace, monospace);
3080 font-size: 11px;
3081 letter-spacing: 0.06em;
3082 text-transform: uppercase;
3083 margin-bottom: var(--s-3);
3084 }
3085 .landing-livenow-pulse {
3086 width: 8px; height: 8px;
3087 border-radius: 50%;
3088 background: var(--green);
3089 box-shadow: 0 0 0 0 rgba(52,211,153,0.6);
3090 animation: landing-livenow-pulse 1.6s ease-out infinite;
3091 }
3092 @keyframes landing-livenow-pulse {
3093 0% { box-shadow: 0 0 0 0 rgba(52,211,153,0.55); transform: scale(1); }
3094 70% { box-shadow: 0 0 0 10px rgba(52,211,153,0); transform: scale(1.05); }
3095 100% { box-shadow: 0 0 0 0 rgba(52,211,153,0); transform: scale(1); }
3096 }
3097 .landing-livenow-title {
3098 font-size: 22px;
3099 line-height: 1.25;
3100 margin: 0 auto;
3101 max-width: 720px;
3102 color: var(--text-strong);
3103 font-weight: 600;
3104 letter-spacing: -0.01em;
3105 }
3106 .landing-livenow-sub {
3107 margin: var(--s-2) auto 0;
3108 color: var(--text-muted);
3109 font-size: 13px;
3110 max-width: 560px;
3111 }
3112 .landing-livenow-sub code {
3113 background: rgba(255,255,255,0.05);
3114 border: 1px solid var(--border);
3115 padding: 1px 6px;
3116 border-radius: 4px;
3117 font-size: 12px;
3118 color: var(--accent);
3119 }
3120
3121 .landing-livenow-grid {
3122 display: grid;
3123 grid-template-columns: repeat(2, minmax(0, 1fr));
3124 gap: var(--s-3);
3125 }
3126 @media (min-width: 980px) {
3127 .landing-livenow-grid {
3128 grid-template-columns: repeat(4, minmax(0, 1fr));
3129 }
3130 }
3131
3132 .landing-livecard {
3133 background: linear-gradient(180deg, rgba(255,255,255,0.025), rgba(255,255,255,0.005));
3134 border: 1px solid var(--border);
3135 border-radius: var(--r-md, 10px);
3136 padding: 14px 14px 12px;
3137 display: flex;
3138 flex-direction: column;
3139 min-height: 180px;
3140 position: relative;
3141 overflow: hidden;
3142 }
3143 .landing-livecard::before {
3144 content: '';
3145 position: absolute;
3146 inset: 0;
3147 background: var(--accent-gradient-faint);
3148 opacity: 0;
3149 transition: opacity 200ms var(--ease, ease);
3150 pointer-events: none;
3151 }
3152 .landing-livecard:hover::before { opacity: 1; }
3153
3154 .landing-livecard-head {
3155 display: flex;
3156 align-items: center;
3157 gap: 8px;
3158 margin-bottom: 10px;
3159 }
3160 .landing-livecard-dot {
3161 width: 7px; height: 7px;
3162 border-radius: 50%;
3163 background: var(--green);
3164 box-shadow: 0 0 8px rgba(52,211,153,0.55);
3165 animation: landing-livenow-pulse 1.8s ease-out infinite;
3166 flex-shrink: 0;
3167 }
3168 .landing-livecard-title {
3169 margin: 0;
3170 font-size: 12px;
3171 font-weight: 600;
3172 text-transform: uppercase;
3173 letter-spacing: 0.06em;
3174 color: var(--text-muted);
3175 }
3176
3177 .landing-livecard-bignum {
3178 display: flex;
3179 align-items: baseline;
3180 gap: 8px;
3181 margin: 4px 0 10px;
3182 }
3183 .landing-livecard-bignum-n {
3184 font-size: 30px;
3185 font-weight: 700;
3186 color: var(--text-strong);
3187 font-variant-numeric: tabular-nums;
3188 background: var(--accent-gradient);
3189 -webkit-background-clip: text;
3190 background-clip: text;
3191 -webkit-text-fill-color: transparent;
3192 color: transparent;
3193 }
3194 .landing-livecard-bignum-label {
3195 font-size: 12px;
3196 color: var(--text-muted);
3197 }
3198
3199 .landing-livecard-list {
3200 list-style: none;
3201 margin: 0;
3202 padding: 0;
3203 font-size: 13px;
3204 line-height: 1.45;
3205 flex: 1;
3206 }
3207 .landing-livecard-row, .landing-livecard-feedrow {
3208 padding: 6px 0;
3209 border-bottom: 1px dashed rgba(255,255,255,0.05);
3210 transition: background-color 1s var(--ease, ease);
3211 border-radius: 4px;
3212 margin: 0 -4px;
3213 padding-left: 4px;
3214 padding-right: 4px;
3215 }
3216 .landing-livecard-row:last-child,
3217 .landing-livecard-feedrow:last-child { border-bottom: 0; }
3218 .landing-livecard-feedrow { padding: 4px; font-size: 12.5px; }
3219
3220 .landing-livecard-flash {
3221 background-color: rgba(52,211,153,0.18) !important;
3222 animation: landing-livecard-flash-fade 1.1s ease-out forwards;
3223 }
3224 @keyframes landing-livecard-flash-fade {
3225 0% { background-color: rgba(52,211,153,0.22); }
3226 100% { background-color: rgba(52,211,153,0); }
3227 }
3228
3229 .landing-livecard-link {
3230 color: var(--text-strong);
3231 text-decoration: none;
3232 display: inline-block;
3233 max-width: 100%;
3234 overflow: hidden;
3235 text-overflow: ellipsis;
3236 white-space: nowrap;
3237 }
3238 .landing-livecard-link:hover { color: var(--accent); }
3239 .landing-livecard-num {
3240 font-family: var(--font-mono, ui-monospace, monospace);
3241 color: var(--text-faint);
3242 font-weight: 500;
3243 font-size: 12px;
3244 }
3245 .landing-livecard-title-text { color: var(--text-strong); }
3246 .landing-livecard-snippet {
3247 color: var(--text-muted);
3248 font-style: italic;
3249 font-size: 12px;
3250 }
3251 .landing-livecard-meta {
3252 font-size: 11px;
3253 color: var(--text-faint);
3254 margin-top: 2px;
3255 font-family: var(--font-mono, ui-monospace, monospace);
3256 }
3257 .landing-livecard-repo {
3258 color: var(--accent-2);
3259 }
3260 .landing-livecard-rel {
3261 color: var(--text-muted);
3262 }
3263 .landing-livecard-empty {
3264 color: var(--text-faint);
3265 font-size: 12px;
3266 font-style: italic;
3267 padding: 8px 0;
3268 }
3269 .landing-livecard-kind {
3270 display: inline-block;
3271 padding: 1px 7px;
3272 border-radius: var(--r-full);
3273 font-family: var(--font-mono, ui-monospace, monospace);
3274 font-size: 10px;
3275 letter-spacing: 0.04em;
3276 text-transform: uppercase;
3277 font-weight: 600;
3278 }
3279 .landing-livecard-kind-auto_merge-merged,
3280 .landing-livecard-kind-auto-merge-merged {
3281 background: rgba(52,211,153,0.12);
3282 color: var(--green);
3283 border: 1px solid rgba(52,211,153,0.25);
3284 }
3285 .landing-livecard-kind-ai_build-dispatched,
3286 .landing-livecard-kind-ai-build-dispatched {
3287 background: rgba(140,109,255,0.12);
3288 color: var(--accent);
3289 border: 1px solid rgba(140,109,255,0.30);
3290 }
3291 .landing-livecard-kind-ai_review-posted,
3292 .landing-livecard-kind-ai-review-posted {
3293 background: rgba(54,197,214,0.12);
3294 color: var(--accent-2);
3295 border: 1px solid rgba(54,197,214,0.30);
3296 }
3297
3298 .landing-livenow-cta {
3299 margin-top: var(--s-5);
3300 display: flex;
3301 flex-wrap: wrap;
3302 align-items: center;
3303 justify-content: center;
3304 gap: 10px;
3305 font-size: 13px;
3306 color: var(--text-muted);
3307 }
3308 .landing-livenow-cta-link {
3309 color: var(--accent);
3310 text-decoration: none;
3311 font-weight: 500;
3312 display: inline-flex;
3313 align-items: center;
3314 gap: 4px;
3315 }
3316 .landing-livenow-cta-link:hover { color: var(--accent-hover); }
3317 .landing-livenow-cta-sep { color: var(--text-faint); }
3318
3319 @media (prefers-reduced-motion: reduce) {
3320 .landing-livenow-pulse,
3321 .landing-livecard-dot { animation: none; }
3322 .landing-livecard-flash { animation: none; background-color: transparent !important; }
3323 }
52ad8b1Claude3324`;
3325
3326/**
3327 * Block L4 — count-up animation.
3328 *
3329 * Reads each `[data-counter-target]` and animates the in-DOM text from
3330 * 0 → target over ~1.2s when the element first scrolls into view.
3331 *
3332 * Render-once semantics: each tile already contains the final value as
3333 * HTML, so visitors with JS disabled — or anyone before the script
3334 * loads — sees the correct number. The script just animates the text.
3335 *
3336 * Falls back to the static value (no animation) when IntersectionObserver
3337 * isn't available, or when the user prefers reduced motion.
3338 */
3339const landingCountersJs = `
3340(function(){
3341 try {
3342 var els = document.querySelectorAll('[data-counter-target]');
3343 if (!els.length) return;
3344 var reduced = window.matchMedia && window.matchMedia('(prefers-reduced-motion: reduce)').matches;
3345 if (reduced || typeof IntersectionObserver !== 'function') return;
3346
3347 function animate(el) {
3348 var target = parseInt(el.getAttribute('data-counter-target') || '0', 10);
3349 if (!isFinite(target) || target <= 0) return;
3350 var prefix = el.getAttribute('data-counter-prefix') || '';
3351 var suffix = el.getAttribute('data-counter-suffix') || '';
3352 var duration = 1200;
3353 var start = performance.now();
3354 function frame(now) {
3355 var t = Math.min(1, (now - start) / duration);
3356 // ease-out cubic
3357 var eased = 1 - Math.pow(1 - t, 3);
3358 var v = Math.floor(eased * target);
3359 el.textContent = prefix + v.toLocaleString() + suffix;
3360 if (t < 1) requestAnimationFrame(frame);
3361 else el.textContent = prefix + target.toLocaleString() + suffix;
3362 }
3363 // Reset to zero before animating in.
3364 el.textContent = prefix + '0' + suffix;
3365 requestAnimationFrame(frame);
3366 }
3367
3368 var io = new IntersectionObserver(function(entries) {
3369 entries.forEach(function(entry){
3370 if (entry.isIntersecting) {
3371 animate(entry.target);
3372 io.unobserve(entry.target);
3373 }
3374 });
3375 }, { threshold: 0.4 });
3376 els.forEach(function(el){ io.observe(el); });
3377 } catch (_) { /* swallow — static numbers remain */ }
3378})();
2b821b7Claude3379`;
5f2e749Claude3380
3381/**
3382 * Block L10 — clipboard copy for the hero install snippet.
3383 *
3384 * Pure progressive enhancement. Without JS the user can still
3385 * triple-click + Cmd/Ctrl-C the snippet — the button is the
3386 * speed-bump, not the only path.
3387 */
3388const landingCopyJs = `
3389(function(){
3390 try {
3391 var btns = document.querySelectorAll('[data-copy-target]');
3392 if (!btns.length) return;
3393 btns.forEach(function(btn){
3394 btn.addEventListener('click', function(){
3395 var id = btn.getAttribute('data-copy-target') || '';
3396 var src = document.getElementById(id);
3397 if (!src) return;
3398 var text = src.textContent || '';
3399 var done = function(){
3400 var prev = btn.textContent;
3401 btn.textContent = 'Copied';
3402 btn.setAttribute('data-copied', '1');
3403 setTimeout(function(){
3404 btn.textContent = prev || 'Copy';
3405 btn.removeAttribute('data-copied');
3406 }, 1500);
3407 };
3408 if (navigator.clipboard && navigator.clipboard.writeText) {
3409 navigator.clipboard.writeText(text).then(done, function(){ done(); });
3410 } else {
3411 // Legacy fallback — temp textarea + execCommand.
3412 try {
3413 var ta = document.createElement('textarea');
3414 ta.value = text;
3415 ta.style.position = 'fixed';
3416 ta.style.opacity = '0';
3417 document.body.appendChild(ta);
3418 ta.select();
3419 document.execCommand('copy');
3420 document.body.removeChild(ta);
3421 done();
3422 } catch (_) {}
3423 }
3424 });
3425 });
3426 } catch (_) { /* swallow */ }
3427})();
3428`;
0c3eee5Claude3429
3430// ============================================================
3431// land2030Css — scoped CSS for the 2030 homepage prelude.
3432// Every selector is prefixed `.land-2030-*` so it can't bleed
3433// into the older `.landing-*` (L10/U1/Q1/M1) styles below.
3434// ============================================================
3435const land2030Css = `
3436 .land-2030-root {
3437 position: relative;
3438 max-width: 1240px;
3439 margin: 0 auto;
3440 padding: 0 16px var(--space-6, 32px);
3441 color: var(--text, #e6e6f0);
3442 }
3443
3444 /* ---------- Hero ---------- */
3445 .land-2030-hero {
3446 position: relative;
3447 padding: clamp(48px, 8vw, 120px) 8px clamp(40px, 6vw, 80px);
3448 text-align: center;
3449 overflow: hidden;
3450 }
3451 .land-2030-hairline {
3452 position: absolute;
3453 top: 0; left: 0; right: 0;
3454 height: 2px;
3455 background: linear-gradient(90deg, transparent 0%, #8c6dff 30%, #36c5d6 70%, transparent 100%);
3456 opacity: 0.75;
3457 pointer-events: none;
3458 }
3459 .land-2030-orb {
3460 position: absolute;
3461 top: -10%;
3462 left: 50%;
3463 width: 720px;
3464 height: 720px;
3465 transform: translateX(-50%);
3466 background: radial-gradient(circle, rgba(140,109,255,0.30), rgba(54,197,214,0.16) 40%, transparent 70%);
3467 filter: blur(90px);
3468 pointer-events: none;
3469 z-index: 0;
3470 }
3471 .land-2030-hero-inner {
3472 position: relative;
3473 z-index: 1;
3474 max-width: 1080px;
3475 margin: 0 auto;
3476 }
3477 .land-2030-eyebrow {
3478 display: inline-flex;
3479 align-items: center;
3480 gap: 8px;
3481 padding: 6px 12px;
3482 border-radius: 999px;
3483 background: rgba(140,109,255,0.10);
3484 color: #cbb7ff;
3485 font-size: 12px;
3486 letter-spacing: 0.04em;
3487 text-transform: uppercase;
3488 border: 1px solid rgba(140,109,255,0.30);
3489 margin-bottom: 24px;
3490 }
3491 .land-2030-eyebrow-mini {
3492 margin-bottom: 12px;
3493 }
3494 .land-2030-pulse {
3495 width: 8px; height: 8px;
3496 border-radius: 50%;
3497 background: #8c6dff;
3498 box-shadow: 0 0 12px rgba(140,109,255,0.8);
3499 animation: land2030-pulse 2s ease-in-out infinite;
3500 }
3501 @keyframes land2030-pulse {
3502 0%, 100% { opacity: 0.5; transform: scale(1); }
3503 50% { opacity: 1; transform: scale(1.25); }
3504 }
3505 .land-2030-display {
3506 font-size: clamp(60px, 9vw, 96px);
3507 font-family: var(--font-display, inherit);
3508 font-weight: 800;
3509 line-height: 1.02;
3510 letter-spacing: -0.035em;
3511 margin: 0 auto 24px;
3512 max-width: 1040px;
3513 }
3514 .land-2030-display span { background-clip: text; -webkit-background-clip: text; -webkit-text-fill-color: transparent; color: transparent; }
3515 .land-2030-grad-1 { background-image: linear-gradient(135deg, #a48bff 0%, #8c6dff 100%); }
3516 .land-2030-grad-2 { background-image: linear-gradient(135deg, #c4b1ff 0%, #36c5d6 100%); }
3517 .land-2030-grad-3 { background-image: linear-gradient(135deg, #36c5d6 0%, #5be0a9 100%); }
3518 .land-2030-grad-4 { background-image: linear-gradient(135deg, #5be0a9 0%, #ffd16b 100%); }
3519 .land-2030-grad-5 { background-image: linear-gradient(135deg, #ffd16b 0%, #ff6bd1 50%, #8c6dff 100%); }
3520 .land-2030-sub {
3521 font-size: clamp(17px, 1.6vw, 22px);
3522 color: var(--text-muted, #a0a0b8);
3523 max-width: 720px;
3524 margin: 0 auto 36px;
3525 line-height: 1.45;
3526 }
3527 .land-2030-cta-row {
3528 display: flex;
3529 flex-wrap: wrap;
3530 justify-content: center;
3531 gap: 12px;
3532 }
3533 .land-2030-cta-primary,
3534 .land-2030-cta-secondary {
3535 min-width: 200px;
3536 }
3537
3538 /* ---------- Section scaffold ---------- */
3539 .land-2030-section {
3540 margin: clamp(56px, 8vw, 96px) 0;
3541 position: relative;
3542 }
3543 .land-2030-section-dark {
3544 padding: 48px 32px;
3545 background: linear-gradient(180deg, rgba(15,17,26,0.6), rgba(8,9,15,0.6));
3546 border: 1px solid rgba(140,109,255,0.18);
3547 border-radius: 20px;
3548 }
3549 .land-2030-section-head {
3550 text-align: center;
3551 max-width: 760px;
3552 margin: 0 auto 40px;
3553 }
3554 .land-2030-h2 {
3555 font-size: clamp(28px, 4vw, 44px);
3556 font-family: var(--font-display, inherit);
3557 font-weight: 700;
3558 letter-spacing: -0.025em;
3559 line-height: 1.1;
3560 margin: 0 0 12px;
3561 }
3562 .land-2030-lede {
3563 font-size: 16px;
3564 color: var(--text-muted, #a0a0b8);
3565 line-height: 1.55;
3566 margin: 0;
3567 }
3568
3569 /* ---------- Closed loop diagram ---------- */
3570 .land-2030-loop-wrap {
3571 max-width: 1100px;
3572 margin: 0 auto;
3573 padding: 16px;
3574 background: rgba(255,255,255,0.02);
3575 border: 1px solid rgba(140,109,255,0.16);
3576 border-radius: 20px;
3577 color: var(--text-muted, #a0a0b8);
3578 }
3579 .land-2030-loop-svg {
3580 display: block;
3581 width: 100%;
3582 height: auto;
3583 }
3584 .land-2030-loop-path {
3585 stroke-dasharray: 6 4;
3586 animation: land2030-loop-dash 30s linear infinite;
3587 }
3588 @keyframes land2030-loop-dash {
3589 from { stroke-dashoffset: 0; }
3590 to { stroke-dashoffset: -400; }
3591 }
3592 .land-2030-loop-text {
3593 font-size: 14px;
3594 font-weight: 600;
3595 letter-spacing: -0.005em;
3596 }
3597
3598 /* ---------- 6-card "unfair advantages" grid ---------- */
3599 .land-2030-card-grid {
3600 display: grid;
3601 grid-template-columns: repeat(3, minmax(0, 1fr));
3602 gap: 16px;
3603 max-width: 1180px;
3604 margin: 0 auto;
3605 }
3606 .land-2030-card {
3607 display: flex;
3608 flex-direction: column;
3609 gap: 10px;
3610 padding: 24px;
3611 background: linear-gradient(180deg, rgba(255,255,255,0.04), rgba(255,255,255,0.015));
3612 border: 1px solid rgba(255,255,255,0.08);
3613 border-radius: 16px;
3614 text-decoration: none;
3615 color: inherit;
3616 transition: transform 200ms ease, border-color 200ms ease, box-shadow 200ms ease;
3617 }
3618 .land-2030-card:hover {
3619 transform: translateY(-2px);
3620 border-color: rgba(140,109,255,0.45);
3621 box-shadow: 0 8px 30px -8px rgba(140,109,255,0.40);
3622 }
3623 .land-2030-card-icon {
3624 width: 44px; height: 44px;
3625 display: inline-flex;
3626 align-items: center;
3627 justify-content: center;
3628 border-radius: 12px;
3629 background: rgba(140,109,255,0.12);
3630 color: #cbb7ff;
3631 border: 1px solid rgba(140,109,255,0.30);
3632 margin-bottom: 4px;
3633 }
3634 .land-2030-card-title {
3635 margin: 0;
3636 font-size: 18px;
3637 font-weight: 700;
3638 letter-spacing: -0.01em;
3639 }
3640 .land-2030-card-desc {
3641 margin: 0;
3642 font-size: 14.5px;
3643 color: var(--text-muted, #a0a0b8);
3644 line-height: 1.55;
3645 }
3646 .land-2030-card-cta {
3647 margin-top: auto;
3648 color: #b69dff;
3649 font-size: 14px;
3650 font-weight: 600;
3651 }
3652
3653 /* ---------- Global dashboards grid ---------- */
3654 .land-2030-dash-grid {
3655 display: grid;
3656 grid-template-columns: repeat(5, minmax(0, 1fr));
3657 gap: 12px;
3658 max-width: 1180px;
3659 margin: 0 auto;
3660 }
3661 .land-2030-dash {
3662 display: flex;
3663 flex-direction: column;
3664 gap: 6px;
3665 padding: 16px 18px;
3666 background: rgba(255,255,255,0.03);
3667 border: 1px solid rgba(255,255,255,0.08);
3668 border-radius: 12px;
3669 text-decoration: none;
3670 color: inherit;
3671 transition: transform 180ms ease, border-color 180ms ease;
3672 }
3673 .land-2030-dash:hover {
3674 transform: translateY(-1px);
3675 border-color: rgba(54,197,214,0.45);
3676 }
3677 .land-2030-dash-name {
3678 font-family: var(--font-mono, ui-monospace, monospace);
3679 font-size: 14px;
3680 color: #36c5d6;
3681 font-weight: 600;
3682 }
3683 .land-2030-dash-desc {
3684 font-size: 13px;
3685 color: var(--text-muted, #a0a0b8);
3686 line-height: 1.45;
3687 }
3688
3689 /* ---------- 18-feature grid + status pills ---------- */
3690 .land-2030-feat-grid {
3691 display: grid;
3692 grid-template-columns: repeat(3, minmax(0, 1fr));
3693 gap: 10px;
3694 max-width: 1180px;
3695 margin: 0 auto;
3696 }
3697 .land-2030-feat {
3698 display: flex;
3699 flex-direction: column;
3700 gap: 6px;
3701 padding: 14px 16px;
3702 background: rgba(255,255,255,0.025);
3703 border: 1px solid rgba(255,255,255,0.07);
3704 border-radius: 10px;
3705 text-decoration: none;
3706 color: inherit;
3707 transition: border-color 160ms ease, background 160ms ease;
3708 }
3709 .land-2030-feat:hover {
3710 border-color: rgba(140,109,255,0.35);
3711 background: rgba(140,109,255,0.05);
3712 }
3713 .land-2030-feat-head {
3714 display: flex;
3715 align-items: center;
3716 justify-content: space-between;
3717 gap: 10px;
3718 }
3719 .land-2030-feat-title {
3720 font-size: 14.5px;
3721 font-weight: 700;
3722 letter-spacing: -0.005em;
3723 }
3724 .land-2030-feat-desc {
3725 margin: 0;
3726 font-size: 13px;
3727 color: var(--text-muted, #a0a0b8);
3728 line-height: 1.5;
3729 }
3730 .land-2030-pill {
3731 font-size: 10.5px;
3732 letter-spacing: 0.04em;
3733 text-transform: uppercase;
3734 padding: 3px 8px;
3735 border-radius: 999px;
3736 border: 1px solid currentColor;
3737 font-weight: 700;
3738 line-height: 1;
3739 }
3740 .land-2030-pill-live { color: #5be0a9; border-color: rgba(91,224,169,0.5); background: rgba(91,224,169,0.10); }
3741 .land-2030-pill-beta { color: #ffd16b; border-color: rgba(255,209,107,0.5); background: rgba(255,209,107,0.10); }
3742 .land-2030-pill-soon { color: #a0a0b8; border-color: rgba(160,160,184,0.4); background: rgba(160,160,184,0.08); }
3743
3744 /* ---------- Developer surface ---------- */
3745 .land-2030-dx-grid {
3746 display: grid;
3747 grid-template-columns: repeat(3, minmax(0, 1fr));
3748 gap: 16px;
3749 max-width: 1180px;
3750 margin: 0 auto;
3751 }
3752 .land-2030-dx {
3753 padding: 18px;
3754 background: linear-gradient(180deg, rgba(15,17,26,0.7), rgba(8,9,15,0.7));
3755 border: 1px solid rgba(140,109,255,0.18);
3756 border-radius: 14px;
3757 }
3758 .land-2030-dx-title {
3759 margin: 0 0 10px;
3760 font-size: 15px;
3761 font-weight: 700;
3762 color: var(--text-strong, #fff);
3763 }
3764 .land-2030-code {
3765 margin: 0;
3766 padding: 12px 14px;
3767 background: rgba(0,0,0,0.30);
3768 border: 1px solid rgba(255,255,255,0.06);
3769 border-radius: 10px;
3770 font-family: var(--font-mono, ui-monospace, monospace);
3771 font-size: 12.5px;
3772 color: #cbb7ff;
3773 line-height: 1.6;
3774 overflow-x: auto;
3775 }
3776 .land-2030-code code { color: inherit; background: transparent; padding: 0; }
3777 .land-2030-code-wide {
3778 font-size: 13px;
3779 color: #d6d6e4;
3780 }
3781
3782 /* ---------- Agent multiplayer ---------- */
3783 .land-2030-agent-wrap {
3784 display: grid;
3785 grid-template-columns: 1.4fr 1fr;
3786 gap: 24px;
3787 max-width: 1080px;
3788 margin: 0 auto;
3789 align-items: center;
3790 }
3791 .land-2030-agent-stat { text-align: left; }
3792 .land-2030-agent-big {
3793 font-size: clamp(48px, 7vw, 80px);
3794 font-weight: 800;
3795 line-height: 1;
3796 background-image: linear-gradient(135deg, #a48bff 0%, #36c5d6 100%);
3797 background-clip: text;
3798 -webkit-background-clip: text;
3799 -webkit-text-fill-color: transparent;
3800 color: transparent;
3801 letter-spacing: -0.03em;
3802 }
3803 .land-2030-agent-label {
3804 margin-top: 12px;
3805 color: var(--text-muted, #a0a0b8);
3806 font-size: 15px;
3807 line-height: 1.5;
3808 }
3809 .land-2030-agent-link {
3810 display: inline-block;
3811 margin-top: 16px;
3812 color: #36c5d6;
3813 text-decoration: none;
3814 font-weight: 600;
3815 font-size: 14px;
3816 }
3817 .land-2030-agent-link:hover { text-decoration: underline; }
3818
3819 /* ---------- vs GitHub strip ---------- */
3820 .land-2030-vs {
3821 max-width: 880px;
3822 margin: 0 auto;
3823 background: rgba(255,255,255,0.03);
3824 border: 1px solid rgba(255,255,255,0.08);
3825 border-radius: 14px;
3826 overflow: hidden;
3827 text-align: center;
3828 padding: 0 0 18px;
3829 }
3830 .land-2030-vs-row {
3831 display: grid;
3832 grid-template-columns: 1fr 1fr;
3833 gap: 0;
3834 padding: 16px 18px;
3835 border-bottom: 1px solid rgba(255,255,255,0.06);
3836 font-size: 15px;
3837 }
3838 .land-2030-vs-row:last-of-type { border-bottom: 0; }
3839 .land-2030-vs-head {
3840 font-size: 12px;
3841 letter-spacing: 0.04em;
3842 text-transform: uppercase;
3843 color: var(--text-muted, #a0a0b8);
3844 background: rgba(255,255,255,0.02);
3845 }
3846 .land-2030-vs-us {
3847 color: #5be0a9;
3848 font-weight: 700;
3849 }
3850 .land-2030-vs-link {
3851 display: inline-block;
3852 margin-top: 12px;
3853 color: #b69dff;
3854 text-decoration: none;
3855 font-weight: 600;
3856 font-size: 14px;
3857 }
3858 .land-2030-vs-link:hover { text-decoration: underline; }
3859
3860 /* ---------- Responsive ---------- */
3861 @media (max-width: 960px) {
3862 .land-2030-card-grid,
3863 .land-2030-feat-grid,
3864 .land-2030-dx-grid,
3865 .land-2030-dash-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
3866 .land-2030-agent-wrap { grid-template-columns: 1fr; }
3867 }
3868 @media (max-width: 640px) {
3869 .land-2030-card-grid,
3870 .land-2030-feat-grid,
3871 .land-2030-dx-grid,
3872 .land-2030-dash-grid { grid-template-columns: 1fr; }
3873 .land-2030-section-dark { padding: 28px 16px; }
3874 .land-2030-vs-row { grid-template-columns: 1fr; gap: 4px; }
3875 }
3876 @media (max-width: 375px) {
3877 .land-2030-display { font-size: 44px; }
3878 .land-2030-cta-primary,
3879 .land-2030-cta-secondary { width: 100%; min-width: 0; }
3880 }
3881
3882 @media (prefers-reduced-motion: reduce) {
3883 .land-2030-loop-path,
3884 .land-2030-pulse { animation: none; }
3885 }
3886`;