Pre-launch — Gluecron is in final validation. Public signups and git hosting for non-owner users open after launch review.
CodeIssuesDiscussionsWikiPull RequestsProjectsCommitsActionsReleasesContributorsPulse● GatesSecuritySettingsDeploymentsPipelineInsightsAgents✨ Explain✨ Ask AI✨ Workspace✨ Spec✨ Tests▓ Debt Map✨ NL Search🏛 Archaeology
Blame · Line-by-line history

landing.tsx

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

landing.tsxBlame2983 lines · 3 contributors
2b821b7Claude1/**
2 * Marketing landing page for logged-out visitors.
3 *
958d26aClaude4 * Editorial-Technical redesign — 2026.05.
5 * Hero · trust strip · feature grid · workflow walkthrough ·
6 * comparison · terminal · pricing teaser · closing CTA.
2b821b7Claude7 *
5f2e749Claude8 * Block L10 — hero rewrite. The hero now lands the Block L positioning
9 * ("the git host built around Claude"): gradient headline, one-line
10 * install snippet w/ copy button, three CTAs (Sign up / Demo / vs-GitHub),
11 * and a four-line "what just happened" rail driven off the L4 publicStats
12 * payload. The L4 counters tile section and L5 vs-GitHub CTA are both
13 * preserved — additive only.
14 *
15 * Also adds two new editorial sections below the L4 counters:
16 * - "Three reasons to switch" (Sleep Mode / Migrate / Demo)
17 * - "How is this different from GitHub?" pull-quote → /vs-github
18 *
958d26aClaude19 * Pure presentational. Drops into <Layout user={null}>.
20 * All styles scoped under `.landing-` so they don't bleed into app views.
2b821b7Claude21 */
22
23import type { FC } from "hono/jsx";
52ad8b1Claude24import type { PublicStats } from "../lib/public-stats";
534f04aClaude25import { DEMO_USERNAME } from "../lib/demo-seed";
26
27export interface LandingLiveFeedQueued {
28 repo: string;
29 number: number;
30 title: string;
31 createdAt: string | Date;
32}
33
34export interface LandingLiveFeedMerge {
35 repo: string;
36 number: number;
37 title: string;
38 mergedAt: string | Date;
39}
40
41export interface LandingLiveFeedReview {
42 repo: string;
43 prNumber: number;
44 commentSnippet: string;
45 createdAt: string | Date;
46}
47
48export interface LandingLiveFeedEntry {
49 kind: "auto_merge.merged" | "ai_build.dispatched" | "ai_review.posted";
50 repo: string;
51 ref: { type: "issue" | "pr"; number: number };
52 at: string | Date;
53}
54
55/**
56 * Block M1 — server-rendered snapshot of the live-now feed. The same
57 * fields are also fetched client-side every 30s from
58 * `/api/v2/demo/{queued,merges,reviews,activity}`. Optional so existing
59 * call-sites (and tests that don't care about the live block) keep
60 * compiling.
61 */
62export interface LandingLiveFeed {
63 queued: LandingLiveFeedQueued[];
64 merges: LandingLiveFeedMerge[];
65 reviews: LandingLiveFeedReview[];
66 reviewCount: number;
67 feed: LandingLiveFeedEntry[];
68}
2b821b7Claude69
70export interface LandingPageProps {
71 stats?: {
72 publicRepos?: number;
73 users?: number;
74 };
52ad8b1Claude75 /**
76 * Block L4 — full public-stats payload (lifetime + trailing-7-day
77 * AI-highlight counters). When present, the hero renders an animated
78 * six-tile social-proof row beneath the eyebrow.
79 */
80 publicStats?: PublicStats | null;
534f04aClaude81 /**
82 * Block M1 — initial SSR snapshot for the live-now feed block.
83 * The same endpoints poll client-side every 30s. When undefined the
84 * section still renders, but with empty-state copy until the first
85 * client poll lands.
86 */
87 liveFeed?: LandingLiveFeed | null;
2b821b7Claude88}
89
534f04aClaude90export const LandingHero: FC<LandingPageProps> = ({
91 stats,
92 publicStats,
93 liveFeed,
94} = {}) => {
8e9f1d9Claude95 const hasStats =
96 stats &&
97 ((stats.publicRepos !== undefined && stats.publicRepos > 0) ||
98 (stats.users !== undefined && stats.users > 0));
4c47454Claude99
52ad8b1Claude100 // Block L4 — six-tile social proof row. Rendered only when the
101 // cached public-stats payload is available; absent → fall back to
102 // the small text-only `landing-stats` row.
103 const tiles = publicStats
104 ? buildSocialProofTiles(publicStats)
105 : null;
106
534f04aClaude107 // Block M1 — SSR-friendly fallbacks so the no-JS path still renders
108 // a populated block. The client-side poller will overwrite these
109 // every 30s anyway.
110 const liveQueued = liveFeed?.queued ?? [];
111 const liveMerges = liveFeed?.merges ?? [];
112 const liveReviews = liveFeed?.reviews ?? [];
113 const liveReviewCount = liveFeed?.reviewCount ?? 0;
114 const liveEntries = liveFeed?.feed ?? [];
115
2b821b7Claude116 return (
117 <>
fa880f2Claude118 <style dangerouslySetInnerHTML={{ __html: landingCss }} />
2b821b7Claude119
4c47454Claude120 <div class="landing-root">
dc26881CC LABS App121 {/* ---------- Hero ----------
122 Block U1 — Senior polish pass. Rebuilt for tighter rhythm:
123 · 2 primary CTAs (sign up + Claude Desktop)
124 · "Try the live demo" / "Compare to GitHub" demoted to
125 a tertiary text-link row
126 · Install snippet moved BELOW the CTAs as a "power users"
127 panel — used to sit above and crowded the buttons
128 · 4-stat rail kept but rendered as a tighter horizontal
129 strip with the gradient accent rule
130 · One new muted gradient orb absolutely positioned behind
131 everything so the section reads as a product page, not
132 a tutorial
133 · vertical rhythm = var(--space-6) between every block
134 */}
4c47454Claude135 <section class="landing-hero">
958d26aClaude136 <div class="landing-hero-bg" aria-hidden="true">
137 <div class="landing-hero-blob landing-hero-blob-1" />
138 <div class="landing-hero-blob landing-hero-blob-2" />
dc26881CC LABS App139 <div class="landing-hero-blob landing-hero-orb" />
958d26aClaude140 <div class="landing-hero-grid" />
2b821b7Claude141 </div>
958d26aClaude142
143 <div class="landing-hero-inner stagger">
144 <div class="eyebrow landing-hero-eyebrow">
145 <span class="landing-hero-pulse" />
146 v1 · pre-launch · {new Date().getFullYear()}
147 </div>
148
149 <h1 class="landing-hero-title display">
5f2e749Claude150 <span class="gradient-text">The git host built around Claude.</span>
958d26aClaude151 </h1>
152
153 <p class="landing-hero-sub">
5f2e749Claude154 Label an issue. Walk away. Wake up to a merged PR.
958d26aClaude155 </p>
156
dc26881CC LABS App157 {/* U1 — primary CTA row, demoted to 2 buttons. */}
158 <div class="landing-hero-ctas" data-testid="hero-primary-ctas">
958d26aClaude159 <a href="/register" class="btn btn-primary btn-xl landing-cta-primary">
5f2e749Claude160 Sign up free
958d26aClaude161 <span class="landing-cta-arrow" aria-hidden="true">{"→"}</span>
162 </a>
dc26881CC LABS App163 {/* BLOCK Q1 — one-click Claude Desktop install. */}
93fe97eClaude164 <a
165 href="/gluecron.dxt"
166 class="btn btn-xl landing-cta-dxt"
167 download
168 data-testid="cta-dxt"
169 >
170 Add to Claude Desktop
171 <span class="landing-cta-arrow" aria-hidden="true">{"→"}</span>
172 </a>
dc26881CC LABS App173 </div>
174
175 {/* U1 — tertiary text-link row.
176 Visually subordinate to the buttons above. Keeps the
177 "Try the live demo" + "Compare to GitHub" affordances
178 from L10 + the Q3 "try without signing up" /play link
179 without crowding the primary CTA row. */}
180 <div class="landing-hero-tertiary" data-testid="hero-tertiary-row">
181 <a href="/demo" class="landing-hero-tertiary-link" data-testid="cta-tertiary-demo">
5f2e749Claude182 Try the live demo
dc26881CC LABS App183 <span aria-hidden="true">{" →"}</span>
958d26aClaude184 </a>
dc26881CC LABS App185 <span class="landing-hero-tertiary-sep" aria-hidden="true">·</span>
186 <a href="/vs-github" class="landing-hero-tertiary-link" data-testid="cta-tertiary-vs">
52ad8b1Claude187 Compare to GitHub
dc26881CC LABS App188 <span aria-hidden="true">{" →"}</span>
52ad8b1Claude189 </a>
dc26881CC LABS App190 <span class="landing-hero-tertiary-sep" aria-hidden="true">·</span>
191 <a href="/play" class="landing-hero-tertiary-link" data-testid="cta-play">
192 Try it without signing up
cd4f63bTest User193 <span aria-hidden="true">{" →"}</span>
194 </a>
195 </div>
196
dc26881CC LABS App197 {/* U1 — power-users install snippet panel.
198 Moved BELOW the CTAs so it doesn't compete for the eye
199 with the primary "Sign up free" button. */}
200 <div class="landing-hero-install-wrap" aria-label="Power users install panel">
201 <div class="landing-hero-install-label">For power users</div>
202 <div class="landing-hero-install" aria-label="One-line install">
203 <code class="landing-hero-install-code">
204 <span class="landing-hero-install-prompt" aria-hidden="true">$</span>
205 <span id="landing-install-text">curl -sSL gluecron.com/install | bash</span>
206 </code>
207 <button
208 type="button"
209 class="landing-hero-install-copy"
210 data-copy-target="landing-install-text"
211 aria-label="Copy install command"
212 >
213 Copy
214 </button>
215 </div>
216 </div>
217
218 {/* U1 — tightened "what just happened" rail.
219 Same data as before, rendered as a single horizontal
220 rule with the gradient accent line on top. Numbers
221 smaller, copy still scannable. */}
5f2e749Claude222 {publicStats && (
223 <ul class="landing-hero-rail" aria-label="What just happened on Gluecron">
224 <li>
225 <strong>{publicStats.weeklyPrsAutoMerged.toLocaleString()}</strong>
dc26881CC LABS App226 <span class="landing-hero-rail-label">PRs auto-merged</span>
5f2e749Claude227 </li>
228 <li>
229 <strong>{publicStats.weeklyIssuesBuiltByAi.toLocaleString()}</strong>
dc26881CC LABS App230 <span class="landing-hero-rail-label">issues built by AI</span>
5f2e749Claude231 </li>
232 <li>
233 <strong>{publicStats.weeklyDeploysShipped.toLocaleString()}</strong>
dc26881CC LABS App234 <span class="landing-hero-rail-label">deploys overnight</span>
5f2e749Claude235 </li>
236 <li>
dc26881CC LABS App237 <strong>{`~${Math.round(publicStats.weeklyHoursSaved).toLocaleString()}`}</strong>
238 <span class="landing-hero-rail-label">hours saved by AI</span>
5f2e749Claude239 </li>
240 </ul>
241 )}
242
243 {/* L8 — free-tier reassurance link. Keeps anxiety low for the AI-curious. */}
244 <p class="landing-hero-freenote">
245 Free forever for the AI-curious.{" "}
246 <a href="/pricing" class="landing-hero-freenote-link">
247 See pricing &rarr;
248 </a>
249 </p>
250
958d26aClaude251 <p class="landing-hero-caption">
252 Already have a repo?
253 <span class="landing-hero-cmd">
254 <span class="kbd">git</span>
255 <span class="kbd">remote</span>
256 <span class="kbd">add</span>
257 <span class="kbd">gluecron</span>
258 <span class="landing-hero-arrow">{"→"}</span>
259 <span class="kbd">git push</span>
260 </span>
261 </p>
262
263 {hasStats && (
264 <p class="landing-stats">
265 {stats!.publicRepos !== undefined && stats!.publicRepos > 0 && (
266 <span>
267 <strong>{stats!.publicRepos.toLocaleString()}</strong>
268 {stats!.publicRepos === 1 ? " repo" : " repos"}
269 </span>
270 )}
271 {stats!.publicRepos !== undefined &&
272 stats!.publicRepos > 0 &&
273 stats!.users !== undefined &&
274 stats!.users > 0 && <span class="landing-stats-sep">·</span>}
275 {stats!.users !== undefined && stats!.users > 0 && (
276 <span>
277 <strong>{stats!.users.toLocaleString()}</strong>
278 {stats!.users === 1 ? " developer" : " developers"}
279 </span>
280 )}
281 <span class="landing-stats-sep">·</span>
4c47454Claude282 <span>
958d26aClaude283 <strong>100%</strong> AI-native
4c47454Claude284 </span>
958d26aClaude285 </p>
286 )}
287 </div>
c963db5Claude288 </section>
c475ee6Claude289
534f04aClaude290 {/* ---------- Block M1 — Live-now demo feed ---------- */}
291 <LiveNowSection
292 queued={liveQueued}
293 merges={liveMerges}
294 reviews={liveReviews}
295 reviewCount={liveReviewCount}
296 feed={liveEntries}
297 />
298
52ad8b1Claude299 {/* ---------- L4 social-proof counters (animated count-up) ---------- */}
300 {tiles && (
301 <section class="landing-counters" aria-label="Gluecron live counters">
302 <div class="landing-counters-grid">
303 {tiles.map((t) => (
304 <div class="landing-counter">
305 <div
306 class="landing-counter-num"
307 data-counter-target={String(t.value)}
308 data-counter-suffix={t.suffix ?? ""}
309 data-counter-prefix={t.prefix ?? ""}
310 >
311 {t.prefix ?? ""}
312 {t.value.toLocaleString()}
313 {t.suffix ?? ""}
314 </div>
315 <div class="landing-counter-label">{t.label}</div>
316 </div>
317 ))}
318 </div>
319 <script dangerouslySetInnerHTML={{ __html: landingCountersJs }} />
320 </section>
321 )}
322
5f2e749Claude323 {/* ---------- L10 — Three reasons to switch ---------- */}
324 <section class="landing-section landing-reasons" aria-label="Three reasons to switch">
325 <div class="section-header">
326 <div class="eyebrow">Three reasons to switch</div>
327 <h2>Built so Claude can do the work.</h2>
328 </div>
329 <div class="landing-reasons-grid">
330 <ReasonCard
331 icon={
332 <svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
333 <path d="M21 12.8A9 9 0 1 1 11.2 3a7 7 0 0 0 9.8 9.8z" />
334 </svg>
335 }
336 title="Toggle Sleep Mode"
337 body="Claude does the work overnight. You get a 9 AM digest of what shipped — PRs merged, deploys live, incidents triaged."
338 link={{ href: "/sleep-mode", label: "Turn on Sleep Mode" }}
339 />
340 <ReasonCard
341 icon={
342 <svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
343 <polyline points="4 17 10 11 4 5" />
344 <line x1="12" y1="19" x2="20" y2="19" />
345 </svg>
346 }
347 title="One command to migrate"
348 body="Drop a single curl into your shell. Gluecron rehosts your repo, your issues, your branches — no SaaS rip-and-replace project required."
349 extra={
350 <code class="landing-reasons-code">curl -sSL gluecron.com/install | bash</code>
351 }
352 link={{ href: "/import", label: "Or import from GitHub" }}
353 />
354 <ReasonCard
355 icon={
356 <svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
357 <polygon points="5 3 19 12 5 21 5 3" />
358 </svg>
359 }
360 title="Open the demo, watch it work"
361 body="The demo repo is real. Label an issue, hit refresh, see Claude open the PR. Inspect the diff. Approve the merge. Zero setup, zero credit card."
362 link={{ href: "/demo", label: "Open the live demo" }}
363 />
364 </div>
365 </section>
366
c963db5Claude367 {/* ---------- Capability strip — uppercase tracked grid (crontech-style) ---------- */}
368 <section class="landing-caps">
369 <div class="landing-caps-grid">
370 <span class="landing-cap">Claude-powered AI</span>
371 <span class="landing-cap">Spec-to-PR</span>
372 <span class="landing-cap">Auto-repair</span>
373 <span class="landing-cap">Real-time gates</span>
374 <span class="landing-cap">MCP-native</span>
375 <span class="landing-cap">Workflow runner</span>
376 <span class="landing-cap">Self-hostable</span>
377 <span class="landing-cap">Branch protection</span>
378 <span class="landing-cap">Bun + Hono</span>
379 <span class="landing-cap">Drizzle + Postgres</span>
380 <span class="landing-cap">JSX server-rendered</span>
381 <span class="landing-cap">Type-safe end to end</span>
c475ee6Claude382 </div>
958d26aClaude383 </section>
384
c963db5Claude385 {/* ---------- Big stat row (crontech-style hero closer) ---------- */}
386 <section class="landing-bigstats">
387 <div class="landing-bigstats-grid">
388 <div class="landing-bigstat">
389 <div class="landing-bigstat-num">Claude-powered</div>
390 <div class="landing-bigstat-label">The best AI, native</div>
391 </div>
392 <div class="landing-bigstat">
393 <div class="landing-bigstat-num">Self-hosted</div>
394 <div class="landing-bigstat-label">On your hardware</div>
395 </div>
396 <div class="landing-bigstat">
397 <div class="landing-bigstat-num">MCP-native</div>
398 <div class="landing-bigstat-label">Claude · Cursor · Code</div>
399 </div>
400 <div class="landing-bigstat">
401 <div class="landing-bigstat-num">Real-time</div>
402 <div class="landing-bigstat-label">SSE everywhere</div>
403 </div>
958d26aClaude404 </div>
4c47454Claude405 </section>
406
407 {/* ---------- Feature grid ---------- */}
958d26aClaude408 <section class="landing-section">
409 <div class="section-header">
410 <div class="eyebrow">The platform</div>
411 <h2>An IDE for your repo, not just a host.</h2>
412 <p>
413 Gluecron ships the surfaces GitHub charges extra for, and the
414 ones it never built. AI is a teammate with its own commits, not
415 a sidebar.
2b821b7Claude416 </p>
417 </div>
4c47454Claude418
958d26aClaude419 <div class="landing-features stagger">
420 <FeatureCard
421 icon={
422 <svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round">
423 <path d="M12 3l1.9 4.6L18.5 9l-3.6 3 1 4.8L12 14.5 8.1 16.8l1-4.8L5.5 9l4.6-1.4z" />
424 </svg>
425 }
426 title="AI as a teammate"
427 desc="Spec-to-PR drafts entire features from plain English. Auto-explain reviews every diff. The AI commits with its own bot account, visible in your history."
428 />
429 <FeatureCard
430 icon={
431 <svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round">
432 <path d="M12 2.5l8 3.5v6c0 5-3.5 8.5-8 9.5-4.5-1-8-4.5-8-9.5v-6z" />
433 <path d="M9 12l2 2 4-4" />
434 </svg>
435 }
436 title="Quality gate that learns"
437 desc="GateTest scans every push. Auto-repair fixes regressions before you see them. Required checks block bad PRs from merging. Your software self-corrects."
438 />
439 <FeatureCard
440 icon={
441 <svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round">
442 <path d="M13 2L4 14h7l-1 8 9-12h-7z" />
443 </svg>
444 }
445 title="Real-time everything"
446 desc="Live workflow logs over SSE. Live PR review presence. Live deploys you watch happen. No polling, no refresh, no waiting on a CI tab."
447 />
448 <FeatureCard
449 icon={
450 <svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round">
451 <rect x="3" y="3" width="18" height="18" rx="3" />
452 <path d="M3 9h18M9 21V9" />
453 </svg>
454 }
455 title="Workflow runner"
456 desc="Drop a yaml in `.gluecron/workflows/` and it runs on every push. Cron triggers, secret substitution, matrix runs, artifacts. No SaaS provider in the loop."
457 />
458 <FeatureCard
459 icon={
460 <svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round">
461 <circle cx="12" cy="12" r="9" />
462 <path d="M3 12h18M12 3a14 14 0 010 18M12 3a14 14 0 000 18" />
463 </svg>
464 }
465 title="MCP-native"
466 desc="Claude, Cursor, Code — they speak Model Context Protocol. Gluecron exposes search, file read, issues, codebase explain as MCP tools by default."
467 />
468 <FeatureCard
469 icon={
470 <svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round">
471 <path d="M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
472 <path d="M12 7v5l3 2" />
473 </svg>
474 }
475 title="Yours, on your hardware"
476 desc="Single-binary Bun runtime. Postgres + bare git repos on a volume. Deploy to Fly, Railway, your own VPS. No vendor lock, no surprise bills."
477 />
2b821b7Claude478 </div>
958d26aClaude479 </section>
2b821b7Claude480
958d26aClaude481 {/* ---------- Workflow walkthrough ---------- */}
482 <section class="landing-section landing-walk">
483 <div class="section-header">
484 <div class="eyebrow">How it works</div>
485 <h2>Push code. Watch it ship.</h2>
486 <p>
487 Every push triggers the same pipeline whether the commit came
488 from you, from CI, or from an AI agent.
4c47454Claude489 </p>
490 </div>
958d26aClaude491
492 <div class="landing-walk-grid">
493 <WalkStep n="01" title="Push" desc="git push to gluecron — Smart-HTTP, SSH, or via the web editor." />
494 <WalkStep n="02" title="Gate" desc="GateTest runs. Secret scanner runs. AI security review posts inline comments." />
495 <WalkStep n="03" title="Repair" desc="If a gate fails, auto-repair tries to fix it. New commit gets re-gated." />
496 <WalkStep n="04" title="Ship" desc="Green push to default branch fires deploy webhook. Crontech, Fly, your prod." />
497 </div>
4c47454Claude498 </section>
2b821b7Claude499
4c47454Claude500 {/* ---------- Terminal block ---------- */}
958d26aClaude501 <section class="landing-section landing-terminal-section">
502 <div class="landing-terminal-wrap">
503 <div class="landing-terminal" role="img" aria-label="Example git push to gluecron with passing gates">
504 <div class="landing-terminal-chrome">
505 <span class="landing-terminal-dot landing-terminal-dot-r" />
506 <span class="landing-terminal-dot landing-terminal-dot-y" />
507 <span class="landing-terminal-dot landing-terminal-dot-g" />
508 <span class="landing-terminal-title">~/your-repo &mdash; zsh</span>
509 </div>
510 <div class="landing-terminal-body">
511 <div class="landing-term-line">
512 <span class="landing-term-prompt">$</span>
513 <span>git remote add gluecron https://gluecron.com/you/your-repo.git</span>
514 </div>
515 <div class="landing-term-line">
516 <span class="landing-term-prompt">$</span>
517 <span>git push -u gluecron main</span>
518 </div>
519 <div class="landing-term-line landing-term-out">
520 <span class="landing-term-meta">remote:</span>
521 <span>Resolving deltas… 100% (24/24)</span>
522 </div>
523 <div class="landing-term-line landing-term-out landing-term-ok-line">
524 <span class="landing-term-ok">{"✓"}</span>
525 <span>pushed to gluecron.com/you/your-repo</span>
526 </div>
527 <div class="landing-term-line landing-term-out landing-term-ok-line">
528 <span class="landing-term-ok">{"✓"}</span>
529 <span>GateTest passed (12 rules, 0 violations)</span>
530 </div>
531 <div class="landing-term-line landing-term-out landing-term-ok-line">
532 <span class="landing-term-ok">{"✓"}</span>
533 <span>AI review posted (2 suggestions, 0 blockers)</span>
534 </div>
535 <div class="landing-term-line landing-term-out landing-term-ok-line">
536 <span class="landing-term-ok">{"✓"}</span>
537 <span>deployed to your-repo.gluecron.com <span class="landing-term-meta">(4.1s)</span></span>
538 </div>
539 <div class="landing-term-line landing-term-cursor">
540 <span class="landing-term-prompt">$</span>
541 <span class="landing-term-blink">▍</span>
542 </div>
543 </div>
4c47454Claude544 </div>
958d26aClaude545 </div>
546 </section>
547
548 {/* ---------- Comparison ---------- */}
549 <section class="landing-section">
550 <div class="section-header">
551 <div class="eyebrow">vs the incumbent</div>
552 <h2>Everything GitHub charges for. And the parts they didn't build.</h2>
553 </div>
554
555 <div class="landing-compare">
556 <CompareRow feature="Git hosting + Smart-HTTP push" them="✓" us="✓" />
557 <CompareRow feature="Issues, PRs, code review" them="✓" us="✓" />
558 <CompareRow feature="Workflow runner (Actions-equivalent)" them="paid minutes" us="self-hosted, unmetered" highlight />
559 <CompareRow feature="AI code review on every PR" them="Copilot subscription" us="built in" highlight />
560 <CompareRow feature="Spec-to-PR (NL feature → draft PR)" them="—" us="✓" highlight />
561 <CompareRow feature="Auto-repair on failed gates" them="—" us="✓" highlight />
562 <CompareRow feature="Real-time SSE for logs + PRs" them="polling" us="streaming" highlight />
563 <CompareRow feature="MCP server (Claude / Cursor)" them="—" us="✓" highlight />
564 <CompareRow feature="Self-host on your own infra" them="enterprise tier" us="single binary" highlight />
565 <CompareRow feature="Pre-receive policy enforcement" them="rulesets (GHE)" us="✓" />
566 </div>
567 </section>
568
569 {/* ---------- Pricing teaser ---------- */}
570 <section class="landing-section">
571 <div class="section-header">
572 <div class="eyebrow">Pricing</div>
573 <h2>Free to start. Honest at scale.</h2>
574 <p>
575 Self-hosting is free forever. Hosted plans price the AI calls,
576 not the seats.
577 </p>
578 </div>
579
580 <div class="landing-pricing">
581 <PricingCard
582 tier="Free"
583 price="$0"
584 cadence="forever"
585 desc="For personal projects + open source. Public + private repos, full AI suite, fair quotas."
586 features={["Unlimited public repos", "3 private repos", "5K AI calls / mo", "Community support"]}
587 cta="Start free"
588 href="/register"
589 />
590 <PricingCard
591 tier="Pro"
592 price="$12"
593 cadence="per user / mo"
594 desc="For working developers. Lifts every quota, adds priority routing, no Gluecron branding on deploys."
595 features={["Unlimited private repos", "100K AI calls / mo", "Priority queue", "Custom domains"]}
596 cta="Go Pro"
597 href="/settings/billing"
598 highlight
599 />
600 <PricingCard
601 tier="Team"
602 price="Talk to us"
603 cadence="custom"
604 desc="For orgs running production on Gluecron. SSO, audit retention, enterprise SLA, on-prem."
605 features={["SSO + SCIM", "On-prem deploy", "Dedicated capacity", "24/7 incident response"]}
606 cta="Contact"
607 href="mailto:hello@gluecron.com"
608 />
609 </div>
610 </section>
611
5f2e749Claude612 {/* ---------- L10 — "How is this different?" pull-quote ---------- */}
613 <section class="landing-pullquote-section" aria-label="How is this different from GitHub?">
614 <figure class="landing-pullquote">
615 <div class="landing-pullquote-eyebrow">How is this different from GitHub?</div>
616 <blockquote class="landing-pullquote-text">
617 Every other host bolts AI on as a sidecar. Gluecron is the first
618 git host where Claude is a first-class developer. Built to be
619 operated by AI agents, not just augmented by them.
620 </blockquote>
621 <a href="/vs-github" class="landing-pullquote-link">
622 See the full comparison
623 <span class="landing-cta-arrow" aria-hidden="true">{"→"}</span>
624 </a>
625 </figure>
626 </section>
627
958d26aClaude628 {/* ---------- Closing CTA ---------- */}
629 <section class="landing-cta-section">
630 <div class="landing-cta-card">
631 <div class="landing-cta-bg" aria-hidden="true" />
632 <div class="eyebrow">Ready when you are</div>
633 <h2 class="landing-cta-title">
634 Stop maintaining the platform.<br />
635 <span class="gradient-text">Start shipping the product.</span>
636 </h2>
637 <p class="landing-cta-sub">
638 Free to start, self-hosted-friendly, MCP-native. Migrate from
639 GitHub in one click.
640 </p>
641 <div class="landing-cta-buttons">
642 <a href="/register" class="btn btn-primary btn-xl">
643 Create your account
644 <span class="landing-cta-arrow" aria-hidden="true">{"→"}</span>
645 </a>
646 <a href="/import" class="btn btn-ghost btn-xl">
647 Migrate a repo
648 </a>
4c47454Claude649 </div>
650 </div>
651 </section>
5f2e749Claude652
653 {/* L10 — clipboard copy script for the hero install snippet. */}
654 <script dangerouslySetInnerHTML={{ __html: landingCopyJs }} />
4c47454Claude655 </div>
2b821b7Claude656 </>
657 );
658};
659
958d26aClaude660const FeatureCard: FC<{ icon: any; title: string; desc: string }> = ({
661 icon,
662 title,
663 desc,
664}) => (
665 <div class="landing-feature">
666 <div class="landing-feature-icon" aria-hidden="true">
667 {icon}
668 </div>
669 <h3 class="landing-feature-title">{title}</h3>
670 <p class="landing-feature-desc">{desc}</p>
671 </div>
672);
673
5f2e749Claude674// Block L10 — "Three reasons to switch" column.
675const ReasonCard: FC<{
676 icon: any;
677 title: string;
678 body: string;
679 link: { href: string; label: string };
680 extra?: any;
681}> = ({ icon, title, body, link, extra }) => (
682 <div class="landing-reason">
683 <div class="landing-reason-icon" aria-hidden="true">
684 {icon}
685 </div>
686 <h3 class="landing-reason-title">{title}</h3>
687 <p class="landing-reason-body">{body}</p>
688 {extra}
689 <a href={link.href} class="landing-reason-link">
690 {link.label}
691 <span class="landing-cta-arrow" aria-hidden="true">{"→"}</span>
692 </a>
693 </div>
694);
695
958d26aClaude696const WalkStep: FC<{ n: string; title: string; desc: string }> = ({
697 n,
698 title,
699 desc,
700}) => (
701 <div class="landing-walk-step">
702 <div class="landing-walk-num">{n}</div>
703 <h3 class="landing-walk-title">{title}</h3>
704 <p class="landing-walk-desc">{desc}</p>
705 </div>
706);
707
708const CompareRow: FC<{
709 feature: string;
710 them: string;
711 us: string;
712 highlight?: boolean;
713}> = ({ feature, them, us, highlight }) => (
714 <div class={`landing-compare-row${highlight ? " landing-compare-hl" : ""}`}>
715 <div class="landing-compare-feature">{feature}</div>
716 <div class="landing-compare-them">{them}</div>
717 <div class="landing-compare-us">{us === "✓" ? "✓" : us}</div>
718 </div>
719);
720
721const PricingCard: FC<{
722 tier: string;
723 price: string;
724 cadence: string;
725 desc: string;
726 features: string[];
727 cta: string;
728 href: string;
729 highlight?: boolean;
730}> = ({ tier, price, cadence, desc, features, cta, href, highlight }) => (
731 <div class={`landing-price-card${highlight ? " landing-price-hl" : ""}`}>
732 {highlight && <div class="landing-price-badge">Most popular</div>}
733 <div class="landing-price-tier">{tier}</div>
734 <div class="landing-price-amount">
735 <span class="landing-price-num">{price}</span>
736 <span class="landing-price-cad">{cadence}</span>
737 </div>
738 <p class="landing-price-desc">{desc}</p>
739 <ul class="landing-price-features">
740 {features.map((f) => (
741 <li>
742 <span class="landing-price-check" aria-hidden="true">{"✓"}</span>
743 {f}
744 </li>
745 ))}
746 </ul>
747 <a
748 href={href}
749 class={`btn ${highlight ? "btn-primary" : "btn-secondary"} btn-block landing-price-cta`}
750 >
751 {cta}
752 </a>
753 </div>
754);
755
52ad8b1Claude756// ─────────────────────────────────────────────────────────────────
757// Block L4 — social-proof tile builder.
758//
759// Pure: takes the cached PublicStats payload and emits the six
760// landing-page tiles in render order. Exported so tests / future
761// surfaces (dashboard, /about, …) can share the exact same copy.
762// ─────────────────────────────────────────────────────────────────
763
764export interface SocialProofTile {
765 label: string;
766 value: number;
767 prefix?: string;
768 suffix?: string;
769}
770
771export function buildSocialProofTiles(s: PublicStats): SocialProofTile[] {
772 return [
773 { label: "Public repos", value: s.totalPublicRepos },
774 { label: "Developers", value: s.totalUsers },
775 {
776 label: "PRs auto-merged this week",
777 value: s.weeklyPrsAutoMerged,
778 },
779 {
780 label: "Issues built by AI this week",
781 value: s.weeklyIssuesBuiltByAi,
782 },
783 {
784 label: "Deploys shipped this week",
785 value: s.weeklyDeploysShipped,
786 },
787 {
788 label: "Hours saved this week",
789 // Round to whole hours for the tile — the precise 0.1 figure
790 // lives on the dashboard widget; the marketing surface keeps
791 // the number scannable.
792 value: Math.round(s.weeklyHoursSaved),
793 prefix: "~",
794 suffix: "h",
795 },
796 ];
797}
798
534f04aClaude799// ─────────────────────────────────────────────────────────────────
800// Block M1 — Live-now demo feed.
801//
802// A 4-tile row inserted between the hero and the L4 counters tile
803// section, surfacing real autopilot activity from the seeded `demo`
804// owner's repos:
805// 1. Issues queued for AI build (ai:build label, open)
806// 2. PRs auto-merged in the last 24h
807// 3. AI reviews posted today (count + latest 3)
808// 4. Combined activity feed (last 10)
809//
810// SSR-renders an initial snapshot, then re-fetches every 30s via the
811// L3 JSON endpoints so the page feels alive without a websocket.
812// Pure presentational; the route layer owns the DB reads.
813// ─────────────────────────────────────────────────────────────────
814
815/**
816 * Render an ISO timestamp (or Date) as a coarse "about N units ago"
817 * string. Tolerates strings, Dates, NaN, and future timestamps.
818 *
819 * Exported for unit testing.
820 */
821export function relativeTimeFromNow(
822 value: string | Date | number | null | undefined,
823 now: number = Date.now()
824): string {
825 if (value === null || value === undefined) return "just now";
826 let t: number;
827 if (value instanceof Date) {
828 t = value.getTime();
829 } else if (typeof value === "number") {
830 t = value;
831 } else {
832 t = new Date(value).getTime();
833 }
834 if (!Number.isFinite(t)) return "just now";
835 const delta = now - t;
836 // Future timestamps (clock skew) — treat as "just now" rather than
837 // surfacing a confusing negative.
838 if (delta < 0) return "just now";
839 const s = Math.floor(delta / 1000);
840 if (s < 60) return "just now";
841 const m = Math.floor(s / 60);
842 if (m < 60) return `about ${m} minute${m === 1 ? "" : "s"} ago`;
843 const h = Math.floor(m / 60);
844 if (h < 24) return `about ${h} hour${h === 1 ? "" : "s"} ago`;
845 const d = Math.floor(h / 24);
846 return `about ${d} day${d === 1 ? "" : "s"} ago`;
847}
848
849function feedEntryId(e: LandingLiveFeedEntry): string {
850 const at = e.at instanceof Date ? e.at.toISOString() : String(e.at);
851 return `${e.kind}|${e.repo}|${e.ref.type}|${e.ref.number}|${at}`;
852}
853
854function feedEntryLabel(kind: LandingLiveFeedEntry["kind"]): string {
855 switch (kind) {
856 case "auto_merge.merged":
857 return "auto-merged";
858 case "ai_build.dispatched":
859 return "AI-build queued";
860 case "ai_review.posted":
861 return "AI review posted";
862 }
863}
864
865interface LiveNowSectionProps {
866 queued: LandingLiveFeedQueued[];
867 merges: LandingLiveFeedMerge[];
868 reviews: LandingLiveFeedReview[];
869 reviewCount: number;
870 feed: LandingLiveFeedEntry[];
871}
872
873const LiveNowSection: FC<LiveNowSectionProps> = ({
874 queued,
875 merges,
876 reviews,
877 reviewCount,
878 feed,
879}) => {
880 return (
881 <section class="landing-livenow" aria-labelledby="landing-livenow-h">
882 <div class="landing-livenow-head">
883 <div class="landing-livenow-eyebrow">
884 <span class="landing-livenow-pulse" aria-hidden="true" />
885 Live now
886 </div>
887 <h2 id="landing-livenow-h" class="landing-livenow-title">
888 Claude is working on demo repos as you read this.
889 </h2>
890 <p class="landing-livenow-sub">
891 Every card below is real data from the public{" "}
892 <code>{DEMO_USERNAME}/*</code> repos. Refreshes every 30 seconds.
893 </p>
894 </div>
895
896 <div class="landing-livenow-grid" data-livenow-grid>
897 {/* Card 1 — queued issues */}
898 <article class="landing-livecard" aria-labelledby="lc-queued-h">
899 <header class="landing-livecard-head">
900 <span class="landing-livecard-dot" aria-hidden="true" />
901 <h3 id="lc-queued-h" class="landing-livecard-title">
902 Issues queued for AI
903 </h3>
904 </header>
905 <ul class="landing-livecard-list" data-livecard="queued">
906 {queued.length === 0 ? (
907 <li class="landing-livecard-empty">
908 No queued AI builds — quiet right now.
909 </li>
910 ) : (
911 queued.slice(0, 3).map((i) => (
912 <li
913 class="landing-livecard-row"
914 data-row-id={`queued|${i.repo}|${i.number}`}
915 >
916 <a
917 class="landing-livecard-link"
918 href={`/${DEMO_USERNAME}/${i.repo}/issues/${i.number}`}
919 >
920 <span class="landing-livecard-num">#{i.number}</span>{" "}
921 <span class="landing-livecard-title-text">{i.title}</span>
922 </a>
923 <div class="landing-livecard-meta">
924 <span class="landing-livecard-repo">{i.repo}</span>
925 </div>
926 </li>
927 ))
928 )}
929 </ul>
930 </article>
931
932 {/* Card 2 — recently merged */}
933 <article class="landing-livecard" aria-labelledby="lc-merges-h">
934 <header class="landing-livecard-head">
935 <span class="landing-livecard-dot" aria-hidden="true" />
936 <h3 id="lc-merges-h" class="landing-livecard-title">
937 Recently merged by AI
938 </h3>
939 </header>
940 <ul class="landing-livecard-list" data-livecard="merges">
941 {merges.length === 0 ? (
942 <li class="landing-livecard-empty">
943 No auto-merges in the last 24h.
944 </li>
945 ) : (
946 merges.slice(0, 3).map((m) => (
947 <li
948 class="landing-livecard-row"
949 data-row-id={`merges|${m.repo}|${m.number}`}
950 >
951 <a
952 class="landing-livecard-link"
953 href={`/${DEMO_USERNAME}/${m.repo}/pulls/${m.number}`}
954 >
955 <span class="landing-livecard-num">#{m.number}</span>{" "}
956 <span class="landing-livecard-title-text">{m.title}</span>
957 </a>
958 <div class="landing-livecard-meta">
959 AI merged in{" "}
960 <span class="landing-livecard-repo">{m.repo}</span>{" "}
961 <span
962 class="landing-livecard-rel"
963 data-rel={
964 m.mergedAt instanceof Date
965 ? m.mergedAt.toISOString()
966 : String(m.mergedAt)
967 }
968 >
969 {relativeTimeFromNow(m.mergedAt)}
970 </span>
971 </div>
972 </li>
973 ))
974 )}
975 </ul>
976 </article>
977
978 {/* Card 3 — AI reviews */}
979 <article class="landing-livecard" aria-labelledby="lc-reviews-h">
980 <header class="landing-livecard-head">
981 <span class="landing-livecard-dot" aria-hidden="true" />
982 <h3 id="lc-reviews-h" class="landing-livecard-title">
983 AI reviews posted
984 </h3>
985 </header>
986 <div class="landing-livecard-bignum">
987 <span
988 class="landing-livecard-bignum-n"
989 data-livecard-count="reviews"
990 data-tick-target={String(reviewCount)}
991 >
992 {reviewCount.toLocaleString()}
993 </span>
994 <span class="landing-livecard-bignum-label">reviews today</span>
995 </div>
996 <ul class="landing-livecard-list" data-livecard="reviews">
997 {reviews.length === 0 ? (
998 <li class="landing-livecard-empty">
999 No AI reviews in the last 24h.
1000 </li>
1001 ) : (
1002 reviews.slice(0, 3).map((r) => (
1003 <li
1004 class="landing-livecard-row"
1005 data-row-id={`reviews|${r.repo}|${r.prNumber}`}
1006 >
1007 <a
1008 class="landing-livecard-link"
1009 href={`/${DEMO_USERNAME}/${r.repo}/pulls/${r.prNumber}`}
1010 >
1011 <span class="landing-livecard-num">#{r.prNumber}</span>{" "}
1012 <span class="landing-livecard-snippet">
1013 {r.commentSnippet}
1014 </span>
1015 </a>
1016 <div class="landing-livecard-meta">
1017 <span class="landing-livecard-repo">{r.repo}</span>
1018 </div>
1019 </li>
1020 ))
1021 )}
1022 </ul>
1023 </article>
1024
1025 {/* Card 4 — activity feed */}
1026 <article class="landing-livecard" aria-labelledby="lc-feed-h">
1027 <header class="landing-livecard-head">
1028 <span class="landing-livecard-dot" aria-hidden="true" />
1029 <h3 id="lc-feed-h" class="landing-livecard-title">
1030 Activity feed
1031 </h3>
1032 </header>
1033 <ul class="landing-livecard-list landing-livecard-feed" data-livecard="feed">
1034 {feed.length === 0 ? (
1035 <li class="landing-livecard-empty">
1036 Quiet right now — check back in a minute.
1037 </li>
1038 ) : (
1039 feed.slice(0, 10).map((e) => {
1040 const path = e.ref.type === "pr" ? "pulls" : "issues";
1041 const id = feedEntryId(e);
1042 return (
1043 <li class="landing-livecard-feedrow" data-row-id={id}>
1044 <span
1045 class={`landing-livecard-kind landing-livecard-kind-${e.kind.replace(/\./g, "-")}`}
1046 >
1047 {feedEntryLabel(e.kind)}
1048 </span>{" "}
1049 <a
1050 class="landing-livecard-link"
1051 href={`/${DEMO_USERNAME}/${e.repo}/${path}/${e.ref.number}`}
1052 >
1053 {e.repo} #{e.ref.number}
1054 </a>{" "}
1055 <span
1056 class="landing-livecard-rel"
1057 data-rel={
1058 e.at instanceof Date ? e.at.toISOString() : String(e.at)
1059 }
1060 >
1061 {relativeTimeFromNow(e.at)}
1062 </span>
1063 </li>
1064 );
1065 })
1066 )}
1067 </ul>
1068 </article>
1069 </div>
1070
1071 <div class="landing-livenow-cta">
1072 <span class="landing-livenow-cta-text">
1073 Want this for your repos?
1074 </span>
1075 <a class="landing-livenow-cta-link" href="/register">
1076 Sign up free
1077 <span class="landing-cta-arrow" aria-hidden="true">{"→"}</span>
1078 </a>
1079 <span class="landing-livenow-cta-sep" aria-hidden="true">·</span>
1080 <a class="landing-livenow-cta-link" href="/demo">
1081 Try the live demo
1082 <span class="landing-cta-arrow" aria-hidden="true">{"→"}</span>
1083 </a>
1084 </div>
1085
1086 <script dangerouslySetInnerHTML={{ __html: liveNowJs }} />
1087 </section>
1088 );
1089};
1090
1091// Inline poller. Plain JS so we don't ship a separate bundle. Hits the
1092// four L3 JSON endpoints every 30s, re-renders the four cards, ticks
1093// the big number, refreshes relative timestamps, flashes new rows.
1094const liveNowJs = `
1095(function(){
1096try{
1097 var DEMO=${JSON.stringify(DEMO_USERNAME)};
1098 var INTERVAL=30000;
1099 function esc(s){return String(s==null?'':s).replace(/[&<>"']/g,function(c){return {'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#39;'}[c];});}
1100 function rel(v){
1101 if(v==null) return 'just now';
1102 var t=(v instanceof Date)?v.getTime():(typeof v==='number'?v:new Date(v).getTime());
1103 if(!isFinite(t)) return 'just now';
1104 var d=Date.now()-t;
1105 if(d<0) return 'just now';
1106 var s=Math.floor(d/1000);
1107 if(s<60) return 'just now';
1108 var m=Math.floor(s/60);
1109 if(m<60) return 'about '+m+' minute'+(m===1?'':'s')+' ago';
1110 var h=Math.floor(m/60);
1111 if(h<24) return 'about '+h+' hour'+(h===1?'':'s')+' ago';
1112 var dd=Math.floor(h/24);
1113 return 'about '+dd+' day'+(dd===1?'':'s')+' ago';
1114 }
1115 function tickNumber(el,target){
1116 if(!el) return;
1117 var start=parseInt(el.getAttribute('data-tick-current')||'0',10)||0;
1118 if(start===target){el.textContent=target.toLocaleString();el.setAttribute('data-tick-current',String(target));return;}
1119 var dur=800,t0=performance.now();
1120 function step(now){
1121 var p=Math.min(1,(now-t0)/dur);
1122 var eased=1-Math.pow(1-p,3);
1123 var v=Math.round(start+(target-start)*eased);
1124 el.textContent=v.toLocaleString();
1125 if(p<1) requestAnimationFrame(step); else el.setAttribute('data-tick-current',String(target));
1126 }
1127 requestAnimationFrame(step);
1128 }
1129 function flashRow(li){
1130 if(!li) return;
1131 li.classList.add('landing-livecard-flash');
1132 setTimeout(function(){li.classList.remove('landing-livecard-flash');},1100);
1133 }
1134 function diffMount(ul,newHtml,newIds){
1135 if(!ul) return;
1136 var prev={};
1137 var nodes=ul.querySelectorAll('[data-row-id]');
1138 for(var i=0;i<nodes.length;i++){prev[nodes[i].getAttribute('data-row-id')]=true;}
1139 ul.innerHTML=newHtml;
1140 var fresh=ul.querySelectorAll('[data-row-id]');
1141 for(var j=0;j<fresh.length;j++){
1142 var id=fresh[j].getAttribute('data-row-id');
1143 if(id && !prev[id]) flashRow(fresh[j]);
1144 }
1145 }
1146 function pollQueued(){
1147 return fetch('/api/v2/demo/queued',{credentials:'omit'}).then(function(r){return r.json();}).then(function(d){
1148 var ul=document.querySelector('[data-livecard="queued"]');if(!ul) return;
1149 var items=(d&&d.items)||[];
1150 if(items.length===0){ul.innerHTML='<li class="landing-livecard-empty">No queued AI builds — quiet right now.</li>';return;}
1151 var ids=[];
1152 var html=items.slice(0,3).map(function(i){
1153 var id='queued|'+i.repo+'|'+i.number;ids.push(id);
1154 return '<li class="landing-livecard-row" data-row-id="'+esc(id)+'">'+
1155 '<a class="landing-livecard-link" href="/'+esc(DEMO)+'/'+esc(i.repo)+'/issues/'+i.number+'">'+
1156 '<span class="landing-livecard-num">#'+i.number+'</span> '+
1157 '<span class="landing-livecard-title-text">'+esc(i.title)+'</span></a>'+
1158 '<div class="landing-livecard-meta"><span class="landing-livecard-repo">'+esc(i.repo)+'</span></div></li>';
1159 }).join('');
1160 diffMount(ul,html,ids);
1161 }).catch(function(){});
1162 }
1163 function pollMerges(){
1164 return fetch('/api/v2/demo/merges',{credentials:'omit'}).then(function(r){return r.json();}).then(function(d){
1165 var ul=document.querySelector('[data-livecard="merges"]');if(!ul) return;
1166 var items=(d&&d.items)||[];
1167 if(items.length===0){ul.innerHTML='<li class="landing-livecard-empty">No auto-merges in the last 24h.</li>';return;}
1168 var ids=[];
1169 var html=items.slice(0,3).map(function(m){
1170 var id='merges|'+m.repo+'|'+m.number;ids.push(id);
1171 return '<li class="landing-livecard-row" data-row-id="'+esc(id)+'">'+
1172 '<a class="landing-livecard-link" href="/'+esc(DEMO)+'/'+esc(m.repo)+'/pulls/'+m.number+'">'+
1173 '<span class="landing-livecard-num">#'+m.number+'</span> '+
1174 '<span class="landing-livecard-title-text">'+esc(m.title)+'</span></a>'+
1175 '<div class="landing-livecard-meta">AI merged in <span class="landing-livecard-repo">'+esc(m.repo)+'</span> '+
1176 '<span class="landing-livecard-rel" data-rel="'+esc(m.mergedAt)+'">'+esc(rel(m.mergedAt))+'</span></div></li>';
1177 }).join('');
1178 diffMount(ul,html,ids);
1179 }).catch(function(){});
1180 }
1181 function pollReviews(){
1182 return fetch('/api/v2/demo/reviews',{credentials:'omit'}).then(function(r){return r.json();}).then(function(d){
1183 var ul=document.querySelector('[data-livecard="reviews"]');if(!ul) return;
1184 var bigEl=document.querySelector('[data-livecard-count="reviews"]');
1185 var n=(d&&typeof d.count==='number')?d.count:0;
1186 if(bigEl) tickNumber(bigEl,n);
1187 var items=(d&&d.items)||[];
1188 if(items.length===0){ul.innerHTML='<li class="landing-livecard-empty">No AI reviews in the last 24h.</li>';return;}
1189 var ids=[];
1190 var html=items.slice(0,3).map(function(r){
1191 var id='reviews|'+r.repo+'|'+r.prNumber;ids.push(id);
1192 return '<li class="landing-livecard-row" data-row-id="'+esc(id)+'">'+
1193 '<a class="landing-livecard-link" href="/'+esc(DEMO)+'/'+esc(r.repo)+'/pulls/'+r.prNumber+'">'+
1194 '<span class="landing-livecard-num">#'+r.prNumber+'</span> '+
1195 '<span class="landing-livecard-snippet">'+esc(r.commentSnippet)+'</span></a>'+
1196 '<div class="landing-livecard-meta"><span class="landing-livecard-repo">'+esc(r.repo)+'</span></div></li>';
1197 }).join('');
1198 diffMount(ul,html,ids);
1199 }).catch(function(){});
1200 }
1201 function pollFeed(){
1202 return fetch('/api/v2/demo/activity',{credentials:'omit'}).then(function(r){return r.json();}).then(function(d){
1203 var ul=document.querySelector('[data-livecard="feed"]');if(!ul) return;
1204 var entries=(d&&d.entries)||[];
1205 if(entries.length===0){ul.innerHTML='<li class="landing-livecard-empty">Quiet right now — check back in a minute.</li>';return;}
1206 var ids=[];
1207 var html=entries.slice(0,10).map(function(e){
1208 var path=(e.ref&&e.ref.type==='pr')?'pulls':'issues';
1209 var num=(e.ref&&e.ref.number)||0;
1210 var label=e.kind==='auto_merge.merged'?'auto-merged':(e.kind==='ai_build.dispatched'?'AI-build queued':'AI review posted');
1211 var kindCls=String(e.kind||'').replace(/\\./g,'-');
1212 var id=e.kind+'|'+e.repo+'|'+(e.ref&&e.ref.type)+'|'+num+'|'+e.at;ids.push(id);
1213 return '<li class="landing-livecard-feedrow" data-row-id="'+esc(id)+'">'+
1214 '<span class="landing-livecard-kind landing-livecard-kind-'+esc(kindCls)+'">'+esc(label)+'</span> '+
1215 '<a class="landing-livecard-link" href="/'+esc(DEMO)+'/'+esc(e.repo)+'/'+path+'/'+num+'">'+esc(e.repo)+' #'+num+'</a> '+
1216 '<span class="landing-livecard-rel" data-rel="'+esc(e.at)+'">'+esc(rel(e.at))+'</span></li>';
1217 }).join('');
1218 diffMount(ul,html,ids);
1219 }).catch(function(){});
1220 }
1221 function refreshRel(){
1222 var spans=document.querySelectorAll('.landing-livecard-rel[data-rel]');
1223 for(var i=0;i<spans.length;i++){
1224 spans[i].textContent=rel(spans[i].getAttribute('data-rel'));
1225 }
1226 }
1227 function tickAll(){pollQueued();pollMerges();pollReviews();pollFeed();}
1228 // Initial counter tick (count-up from 0) on first paint.
1229 var bigEl0=document.querySelector('[data-livecard-count="reviews"]');
1230 if(bigEl0){
1231 var target=parseInt(bigEl0.getAttribute('data-tick-target')||'0',10)||0;
1232 bigEl0.textContent='0';
1233 tickNumber(bigEl0,target);
1234 }
1235 refreshRel();
1236 setInterval(tickAll,INTERVAL);
1237 setInterval(refreshRel,INTERVAL);
1238 document.addEventListener('visibilitychange',function(){
1239 if(document.visibilityState==='visible'){tickAll();refreshRel();}
1240 });
1241}catch(_){}})();
1242`.trim();
1243
4c47454Claude1244// Backwards-compatible default — web.tsx imports `LandingPage`.
1245export const LandingPage: FC<LandingPageProps> = (props) => (
1246 <LandingHero {...props} />
2b821b7Claude1247);
1248
4c47454Claude1249export default LandingPage;
1250
2b821b7Claude1251const landingCss = `
958d26aClaude1252 /* ============================================================ */
1253 /* Landing — Editorial-Technical 2026.05 */
1254 /* ============================================================ */
4c47454Claude1255 .landing-root {
1256 position: relative;
958d26aClaude1257 max-width: 1180px;
4c47454Claude1258 margin: 0 auto;
1259 padding: 0 16px;
958d26aClaude1260 }
1261 .landing-root > section { position: relative; }
1262
1263 /* ---------- Hero ---------- */
1264 .landing-hero {
1265 position: relative;
c475ee6Claude1266 padding: var(--s-16) 0 var(--s-20);
958d26aClaude1267 text-align: center;
4c47454Claude1268 overflow: hidden;
1269 }
c475ee6Claude1270 .landing-hero-blob-1 {
1271 animation: hero-blob-drift-1 18s var(--ease, ease) infinite alternate;
1272 }
1273 .landing-hero-blob-2 {
1274 animation: hero-blob-drift-2 22s var(--ease, ease) infinite alternate;
1275 }
1276 @keyframes hero-blob-drift-1 {
1277 0% { transform: translate(0, 0) scale(1); opacity: 0.55; }
1278 100% { transform: translate(8%, 6%) scale(1.18); opacity: 0.75; }
1279 }
1280 @keyframes hero-blob-drift-2 {
1281 0% { transform: translate(0, 0) scale(1); opacity: 0.40; }
1282 100% { transform: translate(-10%, -4%) scale(1.25); opacity: 0.60; }
1283 }
1284
1285 /* ---------- Hero product visual: live AI PR review card ---------- */
1286 .landing-hero-visual {
1287 position: relative;
1288 max-width: 760px;
1289 margin: var(--s-12) auto 0;
1290 padding: 0 16px;
1291 perspective: 1400px;
1292 z-index: 2;
1293 opacity: 0;
1294 animation: hero-visual-in 700ms var(--ease-out-expo, cubic-bezier(0.19, 1, 0.22, 1)) 400ms forwards;
1295 }
1296 @keyframes hero-visual-in {
1297 from { opacity: 0; transform: translateY(20px); }
1298 to { opacity: 1; transform: translateY(0); }
1299 }
1300 .hero-pr-card {
1301 position: relative;
1302 background: linear-gradient(180deg, rgba(15,17,26,0.96) 0%, rgba(8,9,15,0.96) 100%);
1303 border: 1px solid var(--border-strong);
1304 border-radius: var(--r-xl);
1305 overflow: hidden;
1306 text-align: left;
1307 box-shadow:
1308 0 30px 80px -20px rgba(0,0,0,0.65),
1309 0 0 0 1px rgba(140,109,255,0.18),
1310 0 0 60px -10px rgba(140,109,255,0.30);
1311 transform: rotateX(2deg) rotateY(-2deg);
1312 transition: transform 600ms var(--ease, ease);
1313 backdrop-filter: blur(12px);
1314 -webkit-backdrop-filter: blur(12px);
1315 }
1316 .landing-hero-visual:hover .hero-pr-card {
1317 transform: rotateX(0deg) rotateY(0deg);
1318 }
1319 .hero-pr-card::before {
1320 content: '';
1321 position: absolute;
1322 inset: 0;
1323 background: linear-gradient(135deg, rgba(140,109,255,0.10), transparent 35%, transparent 65%, rgba(54,197,214,0.08));
1324 pointer-events: none;
1325 }
1326 .hero-pr-header {
1327 display: flex;
1328 align-items: center;
1329 gap: 12px;
1330 padding: 14px 18px;
1331 border-bottom: 1px solid rgba(255,255,255,0.06);
1332 background: rgba(255,255,255,0.025);
1333 font-size: 13px;
1334 }
1335 .hero-pr-dot {
1336 width: 10px; height: 10px;
1337 border-radius: 50%;
1338 background: var(--green);
1339 box-shadow: 0 0 10px rgba(52,211,153,0.6);
1340 flex-shrink: 0;
1341 }
1342 .hero-pr-title {
1343 color: var(--text-strong);
1344 font-weight: 600;
1345 flex: 1;
1346 overflow: hidden;
1347 text-overflow: ellipsis;
1348 white-space: nowrap;
1349 }
1350 .hero-pr-num {
1351 color: var(--text-faint);
1352 font-family: var(--font-mono);
1353 font-weight: 500;
1354 margin-right: 8px;
1355 }
1356 .hero-pr-status {
1357 display: inline-flex;
1358 align-items: center;
1359 gap: 6px;
1360 padding: 3px 10px;
1361 border-radius: var(--r-full);
1362 background: var(--accent-gradient-faint);
1363 border: 1px solid rgba(140,109,255,0.30);
1364 color: var(--accent);
1365 font-family: var(--font-mono);
1366 font-size: 11px;
1367 letter-spacing: 0.04em;
1368 flex-shrink: 0;
1369 }
1370 .hero-pr-status-pulse {
1371 width: 6px; height: 6px;
1372 border-radius: 50%;
1373 background: var(--accent);
1374 box-shadow: 0 0 0 0 rgba(140,109,255,0.6);
1375 animation: hero-pulse 1.6s ease-out infinite;
1376 }
1377 @keyframes hero-pulse {
1378 0% { box-shadow: 0 0 0 0 rgba(140,109,255,0.55); }
1379 70% { box-shadow: 0 0 0 8px rgba(140,109,255,0); }
1380 100% { box-shadow: 0 0 0 0 rgba(140,109,255,0); }
1381 }
1382
1383 .hero-pr-body {
1384 padding: 0;
1385 }
1386 .hero-pr-file {
1387 display: flex;
1388 align-items: center;
1389 gap: 10px;
1390 padding: 10px 18px;
1391 border-bottom: 1px solid rgba(255,255,255,0.05);
1392 font-family: var(--font-mono);
1393 font-size: 12px;
1394 background: rgba(255,255,255,0.012);
1395 }
1396 .hero-pr-file-icon { color: var(--accent-2); }
1397 .hero-pr-file-name { color: var(--text); flex: 1; }
1398 .hero-pr-file-stats { display: inline-flex; gap: 8px; }
1399 .hero-pr-add { color: var(--green); font-weight: 600; }
1400 .hero-pr-del { color: var(--red); font-weight: 600; }
1401
1402 .hero-pr-diff {
1403 padding: 12px 18px;
1404 font-family: var(--font-mono);
1405 font-feature-settings: var(--mono-feat, 'calt');
1406 font-size: 12.5px;
1407 line-height: 1.7;
1408 color: rgba(237,237,242,0.85);
1409 overflow-x: auto;
1410 }
1411 .hero-pr-hunk {
1412 color: rgba(140,109,255,0.85);
1413 background: rgba(140,109,255,0.06);
1414 padding: 2px 8px;
1415 margin: 0 -8px 4px;
1416 border-radius: 4px;
1417 }
1418 .hero-pr-line-add {
1419 background: rgba(52,211,153,0.08);
1420 color: rgba(167,243,208,0.95);
1421 padding: 0 8px;
1422 margin: 0 -8px;
1423 border-left: 2px solid var(--green);
1424 padding-left: 8px;
1425 }
1426
1427 .hero-pr-comment {
1428 margin: 14px 18px;
1429 padding: 14px 16px;
1430 background: linear-gradient(135deg, rgba(140,109,255,0.08), rgba(54,197,214,0.05));
1431 border: 1px solid rgba(140,109,255,0.25);
1432 border-radius: var(--r-md);
1433 }
1434 .hero-pr-bot-row {
1435 display: flex;
1436 align-items: center;
1437 gap: 8px;
1438 margin-bottom: 8px;
1439 font-size: 12px;
1440 }
1441 .hero-pr-bot-avatar {
1442 width: 22px; height: 22px;
1443 display: inline-flex;
1444 align-items: center;
1445 justify-content: center;
1446 border-radius: 50%;
1447 background: var(--accent-gradient);
1448 font-size: 11px;
1449 box-shadow: 0 0 12px rgba(140,109,255,0.40);
1450 }
1451 .hero-pr-bot-name {
1452 color: var(--text-strong);
1453 font-weight: 600;
1454 }
1455 .hero-pr-bot-meta {
1456 color: var(--text-faint);
1457 font-family: var(--font-mono);
1458 }
1459 .hero-pr-bot-text {
1460 color: var(--text);
1461 font-size: 13px;
1462 line-height: 1.55;
1463 margin: 0;
1464 }
1465 .hero-pr-bot-text code {
1466 background: rgba(255,255,255,0.06);
1467 border: 1px solid rgba(255,255,255,0.10);
1468 padding: 1px 6px;
1469 border-radius: 4px;
1470 font-size: 11.5px;
1471 color: var(--accent);
1472 }
1473 .hero-pr-bot-link { color: var(--accent-2); text-decoration: underline; text-decoration-style: dotted; }
1474
1475 .hero-pr-gates {
1476 display: flex;
1477 flex-wrap: wrap;
1478 gap: 6px;
1479 padding: 12px 18px 16px;
1480 border-top: 1px solid rgba(255,255,255,0.06);
1481 background: rgba(255,255,255,0.012);
1482 }
1483 .hero-pr-gate {
1484 display: inline-flex;
1485 align-items: center;
1486 gap: 6px;
1487 padding: 4px 10px;
1488 border-radius: var(--r-full);
1489 font-family: var(--font-mono);
1490 font-size: 11px;
1491 border: 1px solid;
1492 }
1493 .hero-pr-gate-pass {
1494 color: var(--green);
1495 background: rgba(52,211,153,0.08);
1496 border-color: rgba(52,211,153,0.30);
1497 }
1498 .hero-pr-gate-running {
1499 color: var(--accent);
1500 background: var(--accent-gradient-faint);
1501 border-color: rgba(140,109,255,0.40);
1502 }
1503 .hero-pr-gate-spin {
1504 width: 9px; height: 9px;
1505 border: 1.5px solid rgba(140,109,255,0.30);
1506 border-top-color: var(--accent);
1507 border-radius: 50%;
1508 animation: hero-spin 800ms linear infinite;
1509 }
1510 @keyframes hero-spin {
1511 to { transform: rotate(360deg); }
1512 }
1513
1514 /* Floating accent badges around the card */
1515 .hero-float {
1516 position: absolute;
1517 display: inline-flex;
1518 align-items: center;
1519 gap: 6px;
1520 padding: 6px 12px;
1521 background: rgba(15,17,26,0.92);
1522 border: 1px solid rgba(140,109,255,0.35);
1523 border-radius: var(--r-full);
1524 font-family: var(--font-mono);
1525 font-size: 11px;
1526 color: var(--text);
1527 box-shadow: 0 12px 24px -8px rgba(0,0,0,0.5), 0 0 18px -4px rgba(140,109,255,0.30);
1528 backdrop-filter: blur(8px);
1529 -webkit-backdrop-filter: blur(8px);
1530 }
1531 .hero-float-icon { color: var(--accent); }
1532 .hero-float-1 {
1533 top: -14px;
1534 left: -8px;
1535 animation: hero-float-bob-1 5s var(--ease, ease) infinite alternate;
1536 }
1537 .hero-float-2 {
1538 bottom: -14px;
1539 right: -8px;
1540 animation: hero-float-bob-2 6s var(--ease, ease) infinite alternate;
1541 }
1542 @keyframes hero-float-bob-1 {
1543 from { transform: translate(0, 0); }
1544 to { transform: translate(-8px, -10px); }
1545 }
1546 @keyframes hero-float-bob-2 {
1547 from { transform: translate(0, 0); }
1548 to { transform: translate(8px, 8px); }
1549 }
1550
1551 @media (max-width: 720px) {
1552 .landing-hero-visual { padding: 0 8px; }
1553 .hero-pr-card { transform: none; }
1554 .hero-pr-title { font-size: 12px; }
1555 .hero-pr-diff { font-size: 11px; line-height: 1.6; }
1556 .hero-float { display: none; }
1557 }
958d26aClaude1558 .landing-hero-bg {
1559 position: absolute;
1560 inset: -10% -20%;
1561 pointer-events: none;
1562 z-index: 0;
4c47454Claude1563 }
958d26aClaude1564 .landing-hero-blob {
1565 position: absolute;
1566 border-radius: 50%;
1567 filter: blur(80px);
1568 opacity: 0.55;
1569 }
1570 .landing-hero-blob-1 {
1571 top: -10%;
1572 left: 30%;
1573 width: 480px;
1574 height: 480px;
1575 background: radial-gradient(circle, rgba(140,109,255,0.55), transparent 65%);
1576 }
1577 .landing-hero-blob-2 {
1578 top: 10%;
1579 left: 50%;
1580 width: 380px;
1581 height: 380px;
1582 background: radial-gradient(circle, rgba(54,197,214,0.40), transparent 65%);
1583 }
dc26881CC LABS App1584 /* U1 — subtle, low-opacity accent-gradient orb behind the headline.
1585 Sits dead-centre, very blurred, so the hero reads as a real product
1586 surface rather than flat-bg + text. */
1587 .landing-hero-orb {
1588 top: 18%;
1589 left: 50%;
1590 transform: translateX(-50%);
1591 width: 720px;
1592 height: 720px;
1593 background: radial-gradient(circle, rgba(140,109,255,0.22), rgba(54,197,214,0.12) 45%, transparent 70%);
1594 filter: blur(120px);
1595 opacity: 0.55;
1596 z-index: 0;
1597 }
1598 :root[data-theme='light'] .landing-hero-orb {
1599 opacity: 0.32;
1600 }
1601 @media (prefers-reduced-motion: reduce) {
1602 .landing-hero-blob-1,
1603 .landing-hero-blob-2,
1604 .landing-hero-orb {
1605 animation: none;
1606 }
1607 }
958d26aClaude1608 .landing-hero-grid {
4c47454Claude1609 position: absolute;
1610 inset: 0;
958d26aClaude1611 background-image:
1612 linear-gradient(to right, rgba(255,255,255,0.04) 1px, transparent 1px),
1613 linear-gradient(to bottom, rgba(255,255,255,0.04) 1px, transparent 1px);
1614 background-size: 60px 60px;
1615 mask-image: radial-gradient(ellipse 50% 50% at 50% 30%, #000 0%, transparent 75%);
1616 -webkit-mask-image: radial-gradient(ellipse 50% 50% at 50% 30%, #000 0%, transparent 75%);
1617 }
1618 :root[data-theme='light'] .landing-hero-grid {
1619 background-image:
1620 linear-gradient(to right, rgba(15,16,28,0.06) 1px, transparent 1px),
1621 linear-gradient(to bottom, rgba(15,16,28,0.06) 1px, transparent 1px);
4c47454Claude1622 }
1623
958d26aClaude1624 .landing-hero-inner {
1625 position: relative;
1626 z-index: 1;
1627 max-width: 960px;
2b821b7Claude1628 margin: 0 auto;
1629 }
dc26881CC LABS App1630 /* U1 — every block below the headline obeys a single rhythm. */
958d26aClaude1631 .landing-hero-eyebrow {
dc26881CC LABS App1632 margin: 0 auto var(--space-6);
958d26aClaude1633 color: var(--accent);
1634 }
1635 .landing-hero-eyebrow::before { display: none; }
1636 .landing-hero-pulse {
1637 width: 7px;
1638 height: 7px;
1639 border-radius: 50%;
1640 background: var(--accent);
1641 box-shadow: 0 0 0 0 rgba(140,109,255,0.6);
1642 animation: pulse 1.8s ease-out infinite;
1643 flex-shrink: 0;
1644 }
1645 @keyframes pulse {
1646 0% { box-shadow: 0 0 0 0 rgba(140,109,255,0.55); }
1647 70% { box-shadow: 0 0 0 10px rgba(140,109,255,0); }
1648 100% { box-shadow: 0 0 0 0 rgba(140,109,255,0); }
1649 }
1650
2b821b7Claude1651 .landing-hero-title {
93fe97eClaude1652 font-size: clamp(32px, 5.5vw, 64px);
1653 line-height: 1.05;
1654 letter-spacing: -0.025em;
c963db5Claude1655 font-weight: 700;
dc26881CC LABS App1656 margin: 0 0 var(--space-6);
958d26aClaude1657 color: var(--text-strong);
2b821b7Claude1658 }
c963db5Claude1659 .landing-hero-title .gradient-text {
1660 background-image: linear-gradient(135deg, #a48bff 0%, #8c6dff 50%, #6d4dff 100%);
1661 -webkit-background-clip: text;
1662 background-clip: text;
1663 -webkit-text-fill-color: transparent;
1664 color: transparent;
1665 }
958d26aClaude1666
2b821b7Claude1667 .landing-hero-sub {
958d26aClaude1668 font-size: clamp(15px, 1.6vw, 19px);
2b821b7Claude1669 color: var(--text-muted);
958d26aClaude1670 max-width: 680px;
dc26881CC LABS App1671 margin: 0 auto var(--space-6);
4c47454Claude1672 line-height: 1.55;
958d26aClaude1673 letter-spacing: -0.005em;
2b821b7Claude1674 }
4c47454Claude1675
2b821b7Claude1676 .landing-hero-ctas {
1677 display: flex;
dc26881CC LABS App1678 gap: var(--space-3);
2b821b7Claude1679 justify-content: center;
1680 flex-wrap: wrap;
dc26881CC LABS App1681 margin-top: 0;
1682 margin-bottom: var(--space-4);
2b821b7Claude1683 }
958d26aClaude1684 .landing-cta-arrow {
1685 transition: transform var(--t-base) var(--ease-spring);
1686 display: inline-block;
2b821b7Claude1687 }
cd4f63bTest User1688
dc26881CC LABS App1689 /* U1 — tertiary text-link row.
1690 Sits directly under the 2-button primary CTA row. Smaller, muted,
1691 so it reads as "by the way" rather than competing for the eye. */
1692 .landing-hero-tertiary {
1693 margin-top: 0;
1694 margin-bottom: var(--space-6);
cd4f63bTest User1695 text-align: center;
dc26881CC LABS App1696 display: inline-flex;
1697 flex-wrap: wrap;
1698 gap: var(--space-2) var(--space-3);
1699 justify-content: center;
1700 align-items: baseline;
1701 width: 100%;
cd4f63bTest User1702 font-size: 13px;
1703 color: var(--text-muted);
dc26881CC LABS App1704 }
1705 .landing-hero-tertiary-link {
1706 color: var(--text-muted);
cd4f63bTest User1707 text-decoration: none;
1708 border-bottom: 1px dashed transparent;
1709 padding-bottom: 1px;
dc26881CC LABS App1710 transition: color var(--t-fast) var(--ease),
1711 border-color var(--t-fast) var(--ease);
cd4f63bTest User1712 }
dc26881CC LABS App1713 .landing-hero-tertiary-link:hover {
1714 color: var(--text-strong);
cd4f63bTest User1715 border-bottom-color: var(--text-muted);
1716 }
dc26881CC LABS App1717 .landing-hero-tertiary-sep {
1718 color: var(--text-faint);
1719 user-select: none;
1720 }
958d26aClaude1721 .btn:hover .landing-cta-arrow,
1722 .landing-cta-primary:hover .landing-cta-arrow {
1723 transform: translateX(4px);
8e9f1d9Claude1724 }
2b821b7Claude1725
93fe97eClaude1726 /* BLOCK Q1 — flagship "Add to Claude Desktop" CTA.
1727 Gradient-bordered + accent text so it reads as a peer of the primary
bc7654bCC LABS App1728 Sign-up CTA, not a third secondary. Theme-aware: inner fill uses
1729 --bg-elevated so it's white on light and dark on dark, never the
1730 jarring near-black on white we shipped first time. Subtle elevation
1731 on hover; static when the visitor opts out of motion. */
93fe97eClaude1732 .landing-cta-dxt {
1733 position: relative;
bc7654bCC LABS App1734 background: var(--bg-elevated);
1735 color: var(--text-strong);
93fe97eClaude1736 border: 1px solid transparent;
1737 background-image:
bc7654bCC LABS App1738 linear-gradient(var(--bg-elevated), var(--bg-elevated)),
93fe97eClaude1739 linear-gradient(90deg, #8c6dff 0%, #36c5d6 100%);
1740 background-origin: border-box;
1741 background-clip: padding-box, border-box;
1742 transition: transform var(--t-base, 180ms) var(--ease-spring, ease),
1743 box-shadow var(--t-base, 180ms) var(--ease-spring, ease);
1744 }
1745 .landing-cta-dxt:hover {
1746 transform: translateY(-2px);
1747 box-shadow: 0 8px 24px -8px rgba(140, 109, 255, 0.45);
1748 }
1749 @media (prefers-reduced-motion: reduce) {
1750 .landing-cta-dxt,
1751 .landing-cta-dxt:hover {
1752 transform: none;
1753 transition: none;
1754 }
1755 }
1756
dc26881CC LABS App1757 /* L8 — free-tier reassurance link beneath the CTA row.
1758 U1 — rhythm snapped to var(--space-6). */
5f2e749Claude1759 .landing-hero-freenote {
dc26881CC LABS App1760 margin: 0 auto var(--space-6);
5f2e749Claude1761 font-size: var(--t-sm);
1762 color: var(--text-muted);
1763 text-align: center;
1764 }
1765 .landing-hero-freenote-link {
1766 color: var(--accent);
1767 text-decoration: none;
1768 font-weight: 500;
1769 border-bottom: 1px dotted rgba(140,109,255,0.4);
1770 transition: color var(--t-fast) var(--ease), border-color var(--t-fast) var(--ease);
1771 }
1772 .landing-hero-freenote-link:hover {
1773 color: var(--text-strong);
1774 border-bottom-color: var(--accent);
1775 }
1776
4c47454Claude1777 .landing-hero-caption {
dc26881CC LABS App1778 margin: 0 auto var(--space-6);
958d26aClaude1779 font-size: var(--t-sm);
4c47454Claude1780 color: var(--text-muted);
958d26aClaude1781 display: flex;
1782 flex-wrap: wrap;
1783 align-items: center;
1784 justify-content: center;
dc26881CC LABS App1785 gap: var(--space-3);
2b821b7Claude1786 }
958d26aClaude1787 .landing-hero-cmd {
1788 display: inline-flex;
1789 align-items: center;
1790 gap: 4px;
1791 padding: 6px 12px;
1792 background: var(--bg-elevated);
4c47454Claude1793 border: 1px solid var(--border);
958d26aClaude1794 border-radius: var(--r-full);
1795 box-shadow: var(--elev-1);
1796 }
1797 .landing-hero-cmd .kbd {
1798 border: 0;
1799 background: transparent;
1800 padding: 0 4px;
1801 color: var(--text-muted);
1802 font-size: 12px;
1803 }
1804 .landing-hero-cmd .kbd:nth-last-of-type(1) { color: var(--accent); }
1805 .landing-hero-arrow {
1806 color: var(--text-faint);
1807 font-size: 13px;
1808 margin: 0 2px;
2b821b7Claude1809 }
4c47454Claude1810
1811 .landing-stats {
dc26881CC LABS App1812 margin: 0 auto;
958d26aClaude1813 font-family: var(--font-mono);
1814 font-size: 12px;
1815 color: var(--text-muted);
1816 display: flex;
1817 align-items: center;
1818 justify-content: center;
dc26881CC LABS App1819 gap: var(--space-2);
958d26aClaude1820 flex-wrap: wrap;
1821 letter-spacing: 0.02em;
1822 }
1823 .landing-stats strong {
1824 color: var(--text-strong);
1825 font-weight: 600;
1826 font-feature-settings: 'tnum';
1827 }
1828 .landing-stats-sep { opacity: 0.4; }
1829
c963db5Claude1830 /* ---------- Capability grid (crontech-style uppercase tracked) ---------- */
1831 .landing-caps {
1832 margin: var(--s-12) auto var(--s-16);
1833 max-width: 1080px;
1834 padding: var(--s-7) var(--s-4);
958d26aClaude1835 border-top: 1px solid var(--border-subtle);
1836 border-bottom: 1px solid var(--border-subtle);
c963db5Claude1837 }
1838 .landing-caps-grid {
1839 display: grid;
1840 grid-template-columns: repeat(4, 1fr);
1841 gap: 24px 16px;
958d26aClaude1842 text-align: center;
1843 }
c963db5Claude1844 .landing-cap {
958d26aClaude1845 font-family: var(--font-mono);
1846 font-size: 11px;
c963db5Claude1847 font-weight: 600;
958d26aClaude1848 text-transform: uppercase;
c963db5Claude1849 letter-spacing: 0.16em;
1850 color: var(--text-muted);
1851 transition: color var(--t-fast) var(--ease);
958d26aClaude1852 }
c963db5Claude1853 .landing-cap:hover { color: var(--text-strong); }
1854 @media (max-width: 800px) {
1855 .landing-caps-grid { grid-template-columns: repeat(2, 1fr); gap: 18px 12px; }
1856 }
1857 @media (max-width: 480px) {
1858 .landing-caps-grid { grid-template-columns: 1fr; }
1859 }
1860
1861 /* ---------- Big stat row (crontech-style hero closer) ---------- */
1862 .landing-bigstats {
1863 margin: var(--s-10) auto var(--s-20);
1864 max-width: 1180px;
1865 padding: 0 var(--s-4);
1866 }
1867 .landing-bigstats-grid {
1868 display: grid;
1869 grid-template-columns: repeat(4, 1fr);
1870 gap: 32px;
1871 text-align: left;
958d26aClaude1872 }
c963db5Claude1873 .landing-bigstat {
1874 padding: var(--s-2) 0;
1875 }
1876 .landing-bigstat-num {
958d26aClaude1877 font-family: var(--font-display);
c963db5Claude1878 font-size: clamp(28px, 3.5vw, 44px);
1879 line-height: 1.05;
1880 letter-spacing: -0.03em;
1881 font-weight: 700;
1882 color: var(--text-strong);
1883 margin-bottom: var(--s-2);
1884 }
1885 .landing-bigstat-label {
1886 font-family: var(--font-mono);
1887 font-size: 11px;
958d26aClaude1888 font-weight: 500;
c963db5Claude1889 text-transform: uppercase;
1890 letter-spacing: 0.14em;
1891 color: var(--text-faint);
2b821b7Claude1892 }
c963db5Claude1893 @media (max-width: 800px) {
1894 .landing-bigstats-grid { grid-template-columns: repeat(2, 1fr); gap: 28px 16px; }
1895 }
1896 @media (max-width: 480px) {
1897 .landing-bigstats-grid { grid-template-columns: 1fr; gap: 24px; }
958d26aClaude1898 }
1899
1900 /* ---------- Section base ---------- */
1901 .landing-section { margin: var(--s-20) auto; }
2b821b7Claude1902
4c47454Claude1903 /* ---------- Feature grid ---------- */
1904 .landing-features {
2b821b7Claude1905 display: grid;
1906 grid-template-columns: repeat(3, 1fr);
958d26aClaude1907 gap: 16px;
2b821b7Claude1908 }
1909 .landing-feature {
958d26aClaude1910 background: var(--bg-elevated);
2b821b7Claude1911 border: 1px solid var(--border);
958d26aClaude1912 border-radius: var(--r-lg);
1913 padding: var(--s-7);
1914 position: relative;
1915 overflow: hidden;
1916 isolation: isolate;
1917 transition:
1918 transform var(--t-base) var(--ease-out-quart),
1919 border-color var(--t-base) var(--ease),
1920 box-shadow var(--t-base) var(--ease);
1921 }
1922 .landing-feature::before {
1923 content: '';
1924 position: absolute;
1925 inset: 0;
1926 background: radial-gradient(120% 100% at 0% 0%, rgba(140,109,255,0.08), transparent 55%);
1927 opacity: 0;
1928 transition: opacity var(--t-base) var(--ease);
1929 z-index: -1;
4c47454Claude1930 }
1931 .landing-feature:hover {
958d26aClaude1932 transform: translateY(-3px);
1933 border-color: var(--border-strong);
1934 box-shadow: var(--elev-2);
2b821b7Claude1935 }
958d26aClaude1936 .landing-feature:hover::before { opacity: 1; }
2b821b7Claude1937 .landing-feature-icon {
4c47454Claude1938 display: inline-flex;
1939 align-items: center;
1940 justify-content: center;
958d26aClaude1941 width: 40px;
1942 height: 40px;
1943 border-radius: var(--r);
1944 background: var(--accent-gradient-soft);
1945 color: var(--accent);
1946 margin-bottom: var(--s-4);
1947 border: 1px solid rgba(140,109,255,0.20);
2b821b7Claude1948 }
1949 .landing-feature-title {
958d26aClaude1950 font-family: var(--font-display);
1951 font-size: 19px;
1952 font-weight: 600;
1953 letter-spacing: -0.018em;
1954 margin: 0 0 var(--s-2);
1955 color: var(--text-strong);
2b821b7Claude1956 }
1957 .landing-feature-desc {
958d26aClaude1958 font-size: var(--t-sm);
1959 color: var(--text-muted);
1960 line-height: 1.6;
1961 margin: 0;
1962 }
1963
1964 /* ---------- Walkthrough ---------- */
1965 .landing-walk-grid {
1966 display: grid;
1967 grid-template-columns: repeat(4, 1fr);
1968 gap: 16px;
1969 counter-reset: walk;
1970 }
1971 .landing-walk-step {
1972 position: relative;
1973 padding: var(--s-7) var(--s-6) var(--s-6);
1974 background: var(--bg-elevated);
1975 border: 1px solid var(--border);
1976 border-radius: var(--r-lg);
1977 }
1978 .landing-walk-step::after {
1979 content: '';
1980 position: absolute;
1981 top: 50%;
1982 right: -12px;
1983 width: 12px;
1984 height: 1px;
1985 background: var(--border-strong);
1986 }
1987 .landing-walk-step:last-child::after { display: none; }
1988 .landing-walk-num {
1989 display: inline-block;
1990 font-family: var(--font-mono);
1991 font-size: 11px;
1992 color: var(--accent);
1993 background: var(--accent-gradient-faint);
1994 border: 1px solid rgba(140,109,255,0.30);
1995 padding: 3px 8px;
1996 border-radius: var(--r-full);
1997 letter-spacing: 0.06em;
1998 margin-bottom: var(--s-3);
1999 }
2000 .landing-walk-title {
2001 font-family: var(--font-display);
2002 font-size: 22px;
2003 font-weight: 600;
2004 letter-spacing: -0.022em;
2005 margin: 0 0 var(--s-2);
2006 color: var(--text-strong);
2007 }
2008 .landing-walk-desc {
2009 font-size: var(--t-sm);
2b821b7Claude2010 color: var(--text-muted);
2011 line-height: 1.55;
2012 margin: 0;
2013 }
2014
958d26aClaude2015 /* ---------- Terminal ---------- */
2016 .landing-terminal-section { margin-top: var(--s-16); }
4c47454Claude2017 .landing-terminal-wrap {
2018 display: flex;
2b821b7Claude2019 justify-content: center;
2020 }
4c47454Claude2021 .landing-terminal {
2022 width: 100%;
958d26aClaude2023 max-width: 820px;
2024 background: linear-gradient(180deg, #0a0b12 0%, #06070c 100%);
2025 border: 1px solid var(--border-strong);
2026 border-radius: var(--r-lg);
2027 overflow: hidden;
2028 box-shadow: var(--elev-3), 0 0 60px -10px rgba(140,109,255,0.18);
4c47454Claude2029 text-align: left;
2b821b7Claude2030 }
958d26aClaude2031 :root[data-theme='light'] .landing-terminal {
2032 background: linear-gradient(180deg, #0f111a 0%, #06070c 100%);
2033 }
2034 .landing-terminal-chrome {
2035 display: flex;
2036 align-items: center;
2037 gap: 7px;
2038 padding: 11px 14px;
2039 background: rgba(255,255,255,0.025);
2040 border-bottom: 1px solid rgba(255,255,255,0.06);
2041 position: relative;
2042 }
2043 .landing-terminal-dot {
2044 width: 11px;
2045 height: 11px;
2046 border-radius: 50%;
2047 flex-shrink: 0;
2048 }
2049 .landing-terminal-dot-r { background: #ff5f57; }
2050 .landing-terminal-dot-y { background: #febc2e; }
2051 .landing-terminal-dot-g { background: #28c840; }
2052 .landing-terminal-title {
2053 position: absolute;
2054 left: 50%;
2055 transform: translateX(-50%);
2056 font-family: var(--font-mono);
2057 font-size: 11px;
2058 color: rgba(237,237,242,0.55);
2059 letter-spacing: 0.01em;
2060 }
2061 .landing-terminal-body {
2062 padding: var(--s-6) var(--s-7);
2063 font-family: var(--font-mono);
2064 font-feature-settings: var(--mono-feat);
2065 font-size: 13.5px;
2066 line-height: 1.85;
2067 color: rgba(237,237,242,0.92);
2068 }
4c47454Claude2069 .landing-term-line {
2070 display: flex;
2071 gap: 10px;
2072 white-space: pre-wrap;
2073 word-break: break-all;
2b821b7Claude2074 }
958d26aClaude2075 .landing-term-out { color: rgba(237,237,242,0.7); }
2076 .landing-term-prompt { color: rgba(140,109,255,0.85); user-select: none; flex-shrink: 0; }
2077 .landing-term-meta { color: rgba(237,237,242,0.45); }
2078 .landing-term-ok { color: var(--green); user-select: none; flex-shrink: 0; }
2079 .landing-term-ok-line { color: rgba(237,237,242,0.92); }
2080 .landing-term-cursor { margin-top: 4px; }
2081 .landing-term-blink {
2082 animation: blink 1.05s steps(2) infinite;
2083 color: var(--accent);
2084 }
2085 @keyframes blink { 50% { opacity: 0; } }
2086
2087 /* ---------- Comparison ---------- */
2088 .landing-compare {
2089 max-width: 920px;
2090 margin: 0 auto;
2091 border: 1px solid var(--border);
2092 border-radius: var(--r-lg);
2093 overflow: hidden;
2094 background: var(--bg-elevated);
2095 }
2096 .landing-compare-row {
2097 display: grid;
2098 grid-template-columns: 1fr 180px 180px;
2099 align-items: center;
2100 padding: 14px 20px;
2101 border-bottom: 1px solid var(--border-subtle);
2102 font-size: var(--t-sm);
2103 transition: background var(--t-fast) var(--ease);
2104 }
2105 .landing-compare-row:last-child { border-bottom: none; }
2106 .landing-compare-row:hover { background: var(--bg-hover); }
2107 .landing-compare-feature {
2108 color: var(--text-strong);
2109 font-weight: 500;
2110 }
2111 .landing-compare-them, .landing-compare-us {
2112 text-align: center;
2113 font-family: var(--font-mono);
2114 font-size: 12px;
2115 color: var(--text-muted);
2116 }
2117 .landing-compare-us { color: var(--green); font-weight: 500; }
2118 .landing-compare-hl .landing-compare-us {
2119 color: var(--accent);
2120 font-weight: 600;
2b821b7Claude2121 }
958d26aClaude2122 .landing-compare-hl .landing-compare-feature::after {
2123 content: 'NEW';
2124 margin-left: 8px;
2125 padding: 1px 6px;
2126 border-radius: 4px;
2127 background: var(--accent-gradient-faint);
2128 color: var(--accent);
2129 font-family: var(--font-mono);
2130 font-size: 9px;
2131 letter-spacing: 0.1em;
2132 font-weight: 600;
2133 vertical-align: 1px;
2134 }
2135 @media (max-width: 720px) {
2136 .landing-compare-row { grid-template-columns: 1fr 80px 80px; padding: 12px 14px; }
2137 .landing-compare-hl .landing-compare-feature::after { display: none; }
2138 }
2139
2140 /* ---------- Pricing ---------- */
2141 .landing-pricing {
2142 display: grid;
2143 grid-template-columns: repeat(3, 1fr);
2144 gap: 16px;
2145 max-width: 1080px;
2146 margin: 0 auto;
2147 align-items: stretch;
2148 }
2149 .landing-price-card {
2150 position: relative;
2151 background: var(--bg-elevated);
2152 border: 1px solid var(--border);
2153 border-radius: var(--r-lg);
2154 padding: var(--s-7);
2155 display: flex;
2156 flex-direction: column;
2157 gap: var(--s-4);
2158 transition: border-color var(--t-base) var(--ease), transform var(--t-base) var(--ease);
2159 }
2160 .landing-price-card:hover {
2161 border-color: var(--border-strong);
2162 transform: translateY(-2px);
2163 }
2164 .landing-price-hl {
2165 border-color: rgba(140,109,255,0.35);
2166 box-shadow: var(--elev-2), 0 0 0 1px rgba(140,109,255,0.25);
2167 background:
2168 linear-gradient(180deg, rgba(140,109,255,0.05), transparent 50%),
2169 var(--bg-elevated);
2170 }
2171 .landing-price-hl:hover { border-color: rgba(140,109,255,0.55); }
2172 .landing-price-badge {
2173 position: absolute;
2174 top: -10px;
2175 left: 50%;
2176 transform: translateX(-50%);
2177 padding: 3px 12px;
2178 background: var(--accent-gradient);
2179 color: #fff;
2180 font-family: var(--font-mono);
2181 font-size: 10px;
2182 letter-spacing: 0.1em;
2183 text-transform: uppercase;
2184 font-weight: 600;
2185 border-radius: var(--r-full);
2186 box-shadow: 0 4px 12px -2px rgba(140,109,255,0.4);
2187 }
2188 .landing-price-tier {
2189 font-family: var(--font-mono);
2190 font-size: 11px;
2191 text-transform: uppercase;
2192 letter-spacing: 0.16em;
2193 color: var(--text-muted);
2194 }
2195 .landing-price-amount {
2196 display: flex;
2197 align-items: baseline;
2198 gap: 8px;
2199 }
2200 .landing-price-num {
2201 font-family: var(--font-display);
2202 font-size: 40px;
2203 font-weight: 600;
2204 letter-spacing: -0.03em;
2205 color: var(--text-strong);
2206 }
2207 .landing-price-cad {
2208 font-size: var(--t-sm);
2209 color: var(--text-faint);
2210 }
2211 .landing-price-desc {
2212 font-size: var(--t-sm);
2213 color: var(--text-muted);
2214 line-height: 1.55;
2215 margin: 0;
2216 }
2217 .landing-price-features {
2218 list-style: none;
2219 padding: 0;
2220 margin: 0;
2221 display: flex;
2222 flex-direction: column;
2223 gap: 8px;
2224 font-size: var(--t-sm);
2225 color: var(--text);
2226 }
2227 .landing-price-features li {
2228 display: flex;
2229 align-items: center;
2230 gap: 9px;
2231 }
2232 .landing-price-check {
2233 color: var(--accent);
2234 font-weight: 600;
4c47454Claude2235 flex-shrink: 0;
2b821b7Claude2236 }
958d26aClaude2237 .landing-price-cta { margin-top: auto; }
2238
2239 /* ---------- Closing CTA ---------- */
2240 .landing-cta-section { margin: var(--s-20) auto var(--s-16); }
2241 .landing-cta-card {
2242 position: relative;
2243 text-align: center;
2244 padding: var(--s-16) var(--s-7);
2245 border: 1px solid var(--border-strong);
2246 border-radius: var(--r-2xl);
2247 background: var(--bg-elevated);
2248 overflow: hidden;
2249 isolation: isolate;
2250 }
2251 .landing-cta-bg {
2252 position: absolute;
2253 inset: 0;
2254 z-index: -1;
2255 background:
2256 radial-gradient(60% 100% at 50% 0%, rgba(140,109,255,0.16), transparent 65%),
2257 radial-gradient(40% 80% at 80% 100%, rgba(54,197,214,0.10), transparent 65%);
2258 }
2259 .landing-cta-card::after {
2260 content: '';
2261 position: absolute;
2262 inset: 0;
2263 z-index: -1;
2264 background-image: radial-gradient(rgba(255,255,255,0.04) 1px, transparent 1px);
2265 background-size: 24px 24px;
2266 mask-image: radial-gradient(ellipse at center, #000 0%, transparent 65%);
2267 -webkit-mask-image: radial-gradient(ellipse at center, #000 0%, transparent 65%);
2268 opacity: 0.6;
2269 }
2270 :root[data-theme='light'] .landing-cta-card::after {
2271 background-image: radial-gradient(rgba(15,16,28,0.07) 1px, transparent 1px);
2272 }
2273 .landing-cta-card .eyebrow { justify-content: center; }
2274 .landing-cta-title {
2275 font-family: var(--font-display);
2276 font-size: clamp(28px, 4.4vw, 56px);
2277 line-height: 1.05;
2278 letter-spacing: -0.03em;
2279 font-weight: 600;
2280 margin: var(--s-3) 0 var(--s-4);
2281 color: var(--text-strong);
2282 }
2283 .landing-cta-sub {
2284 font-size: var(--t-md);
2285 color: var(--text-muted);
2286 max-width: 560px;
2287 margin: 0 auto var(--s-8);
2288 line-height: 1.55;
2289 }
2290 .landing-cta-buttons {
2291 display: flex;
2292 gap: 12px;
2293 justify-content: center;
2294 flex-wrap: wrap;
2295 }
2b821b7Claude2296
2297 /* ---------- Responsive ---------- */
958d26aClaude2298 @media (max-width: 960px) {
2299 .landing-features { grid-template-columns: repeat(2, 1fr); }
2300 .landing-walk-grid { grid-template-columns: repeat(2, 1fr); }
2301 .landing-walk-step::after { display: none; }
2302 .landing-pricing { grid-template-columns: 1fr; max-width: 480px; }
2303 }
2304 @media (max-width: 640px) {
2305 .landing-hero { padding: var(--s-14) 0 var(--s-10); }
2306 .landing-hero-cmd { flex-wrap: wrap; justify-content: center; }
2307 .landing-hero-ctas { flex-direction: column; align-items: stretch; }
2308 .landing-hero-ctas .btn { width: 100%; justify-content: center; }
2309 .landing-features { grid-template-columns: 1fr; }
2310 .landing-walk-grid { grid-template-columns: 1fr; }
2311 .landing-section { margin: var(--s-12) auto; }
2312 .landing-cta-card { padding: var(--s-10) var(--s-5); }
2313 .landing-cta-buttons .btn { width: 100%; justify-content: center; }
2b821b7Claude2314 }
52ad8b1Claude2315
2316 /* ---------- L4 social-proof counters ---------- */
2317 .landing-counters {
2318 margin: var(--s-10) auto var(--s-12);
2319 max-width: 1180px;
2320 padding: 0 var(--s-4);
2321 }
2322 .landing-counters-grid {
2323 display: grid;
2324 grid-template-columns: repeat(6, 1fr);
2325 gap: 20px;
2326 text-align: left;
2327 }
2328 .landing-counter {
2329 padding: var(--s-3) 0;
2330 border-top: 1px solid var(--border-subtle);
2331 }
2332 .landing-counter-num {
2333 font-family: var(--font-display);
2334 font-size: clamp(24px, 3vw, 38px);
2335 line-height: 1.05;
2336 letter-spacing: -0.03em;
2337 font-weight: 700;
2338 margin-bottom: 6px;
2339 font-feature-settings: 'tnum';
2340 background-image: var(--accent-gradient);
2341 -webkit-background-clip: text;
2342 background-clip: text;
2343 -webkit-text-fill-color: transparent;
2344 color: transparent;
2345 }
2346 .landing-counter-label {
2347 font-family: var(--font-mono);
2348 font-size: 10.5px;
2349 font-weight: 500;
2350 text-transform: uppercase;
2351 letter-spacing: 0.12em;
2352 color: var(--text-faint);
2353 line-height: 1.4;
2354 }
2355 @media (max-width: 960px) {
2356 .landing-counters-grid { grid-template-columns: repeat(3, 1fr); gap: 20px 16px; }
2357 }
2358 @media (max-width: 540px) {
2359 .landing-counters-grid { grid-template-columns: repeat(2, 1fr); gap: 18px 12px; }
2360 }
5f2e749Claude2361
dc26881CC LABS App2362 /* ---------- L10/U1 hero install snippet ----------
2363 U1: wrapped in a labelled "power users" panel and re-located
2364 beneath the CTA + tertiary rows so it no longer competes with
2365 the primary calls to action. */
2366 .landing-hero-install-wrap {
2367 margin: 0 auto var(--space-6);
2368 text-align: center;
2369 }
2370 .landing-hero-install-label {
2371 display: inline-block;
2372 margin-bottom: var(--space-2);
2373 font-family: var(--font-mono);
2374 font-size: 11px;
2375 letter-spacing: 0.08em;
2376 text-transform: uppercase;
2377 color: var(--text-faint);
2378 }
5f2e749Claude2379 .landing-hero-install {
2380 display: inline-flex;
2381 align-items: stretch;
2382 gap: 0;
dc26881CC LABS App2383 margin: 0 auto;
5f2e749Claude2384 background: var(--bg-elevated);
2385 border: 1px solid var(--border-strong);
2386 border-radius: var(--r);
2387 box-shadow: var(--elev-1);
2388 overflow: hidden;
2389 max-width: 100%;
2390 font-family: var(--font-mono);
2391 }
2392 .landing-hero-install-code {
2393 display: inline-flex;
2394 align-items: center;
2395 gap: 10px;
2396 padding: 10px 14px;
2397 font-size: 13.5px;
2398 color: var(--text-strong);
2399 background: transparent;
2400 border: 0;
2401 white-space: nowrap;
2402 overflow-x: auto;
2403 }
2404 .landing-hero-install-prompt {
2405 color: var(--accent);
2406 user-select: none;
2407 }
2408 .landing-hero-install-copy {
2409 appearance: none;
2410 border: 0;
2411 border-left: 1px solid var(--border);
2412 background: transparent;
2413 color: var(--text-muted);
2414 font-family: var(--font-mono);
2415 font-size: 12px;
2416 font-weight: 600;
2417 letter-spacing: 0.06em;
2418 text-transform: uppercase;
2419 padding: 0 16px;
2420 cursor: pointer;
2421 transition: background var(--t-fast) var(--ease), color var(--t-fast) var(--ease);
2422 }
2423 .landing-hero-install-copy:hover {
2424 background: var(--accent-gradient-faint);
2425 color: var(--accent);
2426 }
2427 .landing-hero-install-copy[data-copied="1"] {
2428 color: var(--green, #34d399);
2429 }
2430
dc26881CC LABS App2431 /* ---------- L10/U1 hero "what just happened" rail ----------
2432 U1 — tightened into a single horizontal strip. The 1px gradient
2433 rule on top is the same accent the headline uses, so the rail
2434 reads as part of the hero composition rather than a stray list. */
5f2e749Claude2435 .landing-hero-rail {
2436 list-style: none;
dc26881CC LABS App2437 padding: var(--space-4) 0 0;
2438 margin: 0 auto var(--space-6);
5f2e749Claude2439 display: flex;
2440 flex-wrap: wrap;
2441 justify-content: center;
dc26881CC LABS App2442 gap: var(--space-2) var(--space-6);
5f2e749Claude2443 font-family: var(--font-sans);
dc26881CC LABS App2444 font-size: 12px;
2445 color: var(--text-faint);
5f2e749Claude2446 max-width: 760px;
dc26881CC LABS App2447 position: relative;
2448 }
2449 .landing-hero-rail::before {
2450 content: '';
2451 position: absolute;
2452 top: 0;
2453 left: 50%;
2454 transform: translateX(-50%);
2455 width: 120px;
2456 height: 1px;
2457 background: var(--accent-gradient);
2458 opacity: 0.45;
2459 border-radius: 9999px;
5f2e749Claude2460 }
2461 .landing-hero-rail li {
2462 display: inline-flex;
dc26881CC LABS App2463 align-items: baseline;
2464 gap: var(--space-2);
5f2e749Claude2465 line-height: 1.4;
2466 }
2467 .landing-hero-rail strong {
2468 color: var(--text-strong);
2469 font-weight: 600;
2470 font-feature-settings: 'tnum';
dc26881CC LABS App2471 font-size: 14px;
2472 letter-spacing: -0.01em;
5f2e749Claude2473 }
dc26881CC LABS App2474 .landing-hero-rail-label {
2475 color: var(--text-muted);
2476 letter-spacing: 0.01em;
5f2e749Claude2477 }
dc26881CC LABS App2478 /* Backwards-compat: nothing references this any more but if a stale
2479 fragment lingers it's still hidden cleanly rather than orphaned. */
2480 .landing-hero-rail-check { display: none; }
5f2e749Claude2481
2482 /* ---------- L10 three-reasons section ---------- */
2483 .landing-reasons { margin-top: var(--s-12); }
2484 .landing-reasons-grid {
2485 display: grid;
2486 grid-template-columns: repeat(3, 1fr);
2487 gap: 16px;
2488 }
2489 .landing-reason {
2490 background: var(--bg-elevated);
2491 border: 1px solid var(--border);
2492 border-radius: var(--r-lg);
2493 padding: var(--s-7);
2494 display: flex;
2495 flex-direction: column;
2496 gap: var(--s-3);
2497 transition: border-color var(--t-base) var(--ease), transform var(--t-base) var(--ease);
2498 }
2499 .landing-reason:hover {
2500 border-color: var(--border-strong);
2501 transform: translateY(-2px);
2502 }
2503 .landing-reason-icon {
2504 display: inline-flex;
2505 align-items: center;
2506 justify-content: center;
2507 width: 40px;
2508 height: 40px;
2509 border-radius: var(--r);
2510 background: var(--accent-gradient-soft);
2511 color: var(--accent);
2512 border: 1px solid rgba(140,109,255,0.20);
2513 }
2514 .landing-reason-title {
2515 font-family: var(--font-display);
2516 font-size: 20px;
2517 font-weight: 600;
2518 letter-spacing: -0.02em;
2519 margin: 0;
2520 color: var(--text-strong);
2521 }
2522 .landing-reason-body {
2523 font-size: var(--t-sm);
2524 color: var(--text-muted);
2525 line-height: 1.55;
2526 margin: 0;
2527 }
2528 .landing-reasons-code {
2529 display: block;
2530 font-family: var(--font-mono);
2531 font-size: 12px;
2532 color: var(--text-strong);
2533 background: var(--bg);
2534 border: 1px solid var(--border);
2535 border-radius: var(--r);
2536 padding: 8px 12px;
2537 overflow-x: auto;
2538 white-space: nowrap;
2539 }
2540 .landing-reason-link {
2541 margin-top: auto;
2542 display: inline-flex;
2543 align-items: center;
2544 gap: 6px;
2545 color: var(--accent);
2546 font-size: var(--t-sm);
2547 font-weight: 500;
2548 text-decoration: none;
2549 }
2550 .landing-reason-link:hover { text-decoration: underline; }
2551 @media (max-width: 960px) {
2552 .landing-reasons-grid { grid-template-columns: 1fr; max-width: 520px; margin: 0 auto; }
2553 }
2554
2555 /* ---------- L10 "How is this different" pull-quote ---------- */
2556 .landing-pullquote-section {
2557 margin: var(--s-20) auto var(--s-12);
2558 max-width: 920px;
2559 padding: 0 var(--s-4);
2560 text-align: center;
2561 }
2562 .landing-pullquote {
2563 margin: 0;
2564 padding: var(--s-10) var(--s-7);
2565 background:
2566 radial-gradient(80% 100% at 50% 0%, rgba(140,109,255,0.10), transparent 65%),
2567 var(--bg-elevated);
2568 border: 1px solid var(--border-strong);
2569 border-radius: var(--r-xl);
2570 position: relative;
2571 overflow: hidden;
2572 }
2573 .landing-pullquote-eyebrow {
2574 font-family: var(--font-mono);
2575 font-size: 11px;
2576 font-weight: 600;
2577 letter-spacing: 0.16em;
2578 text-transform: uppercase;
2579 color: var(--accent);
2580 margin-bottom: var(--s-4);
2581 }
2582 .landing-pullquote-text {
2583 font-family: var(--font-display);
2584 font-size: clamp(20px, 2.4vw, 28px);
2585 line-height: 1.4;
2586 letter-spacing: -0.018em;
2587 color: var(--text-strong);
2588 margin: 0 auto;
2589 max-width: 760px;
2590 quotes: "\\201C" "\\201D";
2591 }
2592 .landing-pullquote-text::before { content: open-quote; color: var(--accent); margin-right: 4px; }
2593 .landing-pullquote-text::after { content: close-quote; color: var(--accent); margin-left: 4px; }
2594 .landing-pullquote-link {
2595 display: inline-flex;
2596 align-items: center;
2597 gap: 6px;
2598 margin-top: var(--s-6);
2599 color: var(--accent);
2600 font-size: var(--t-sm);
2601 font-weight: 500;
2602 text-decoration: none;
2603 }
2604 .landing-pullquote-link:hover { text-decoration: underline; }
2605
2606 /* ---------- L10 hero responsive overrides ---------- */
2607 @media (max-width: 640px) {
2608 .landing-hero-install { width: 100%; }
2609 .landing-hero-install-code { flex: 1; font-size: 12px; }
2610 .landing-hero-rail { flex-direction: column; align-items: flex-start; gap: 6px; padding: 0 var(--s-3); }
2611 .landing-hero-rail li { width: 100%; }
2612 }
534f04aClaude2613
2614 /* ============================================================ */
2615 /* Block M1 — Live-now demo feed */
2616 /* ============================================================ */
2617 .landing-livenow {
2618 margin: var(--s-8) 0 var(--s-6);
2619 padding: var(--s-6) 0 var(--s-4);
2620 }
2621 .landing-livenow-head {
2622 text-align: center;
2623 margin-bottom: var(--s-6);
2624 }
2625 .landing-livenow-eyebrow {
2626 display: inline-flex;
2627 align-items: center;
2628 gap: 8px;
2629 padding: 4px 12px;
2630 border-radius: var(--r-full);
2631 background: rgba(52,211,153,0.08);
2632 border: 1px solid rgba(52,211,153,0.25);
2633 color: var(--green);
2634 font-family: var(--font-mono, ui-monospace, monospace);
2635 font-size: 11px;
2636 letter-spacing: 0.06em;
2637 text-transform: uppercase;
2638 margin-bottom: var(--s-3);
2639 }
2640 .landing-livenow-pulse {
2641 width: 8px; height: 8px;
2642 border-radius: 50%;
2643 background: var(--green);
2644 box-shadow: 0 0 0 0 rgba(52,211,153,0.6);
2645 animation: landing-livenow-pulse 1.6s ease-out infinite;
2646 }
2647 @keyframes landing-livenow-pulse {
2648 0% { box-shadow: 0 0 0 0 rgba(52,211,153,0.55); transform: scale(1); }
2649 70% { box-shadow: 0 0 0 10px rgba(52,211,153,0); transform: scale(1.05); }
2650 100% { box-shadow: 0 0 0 0 rgba(52,211,153,0); transform: scale(1); }
2651 }
2652 .landing-livenow-title {
2653 font-size: 22px;
2654 line-height: 1.25;
2655 margin: 0 auto;
2656 max-width: 720px;
2657 color: var(--text-strong);
2658 font-weight: 600;
2659 letter-spacing: -0.01em;
2660 }
2661 .landing-livenow-sub {
2662 margin: var(--s-2) auto 0;
2663 color: var(--text-muted);
2664 font-size: 13px;
2665 max-width: 560px;
2666 }
2667 .landing-livenow-sub code {
2668 background: rgba(255,255,255,0.05);
2669 border: 1px solid var(--border);
2670 padding: 1px 6px;
2671 border-radius: 4px;
2672 font-size: 12px;
2673 color: var(--accent);
2674 }
2675
2676 .landing-livenow-grid {
2677 display: grid;
2678 grid-template-columns: repeat(2, minmax(0, 1fr));
2679 gap: var(--s-3);
2680 }
2681 @media (min-width: 980px) {
2682 .landing-livenow-grid {
2683 grid-template-columns: repeat(4, minmax(0, 1fr));
2684 }
2685 }
2686
2687 .landing-livecard {
2688 background: linear-gradient(180deg, rgba(255,255,255,0.025), rgba(255,255,255,0.005));
2689 border: 1px solid var(--border);
2690 border-radius: var(--r-md, 10px);
2691 padding: 14px 14px 12px;
2692 display: flex;
2693 flex-direction: column;
2694 min-height: 180px;
2695 position: relative;
2696 overflow: hidden;
2697 }
2698 .landing-livecard::before {
2699 content: '';
2700 position: absolute;
2701 inset: 0;
2702 background: var(--accent-gradient-faint);
2703 opacity: 0;
2704 transition: opacity 200ms var(--ease, ease);
2705 pointer-events: none;
2706 }
2707 .landing-livecard:hover::before { opacity: 1; }
2708
2709 .landing-livecard-head {
2710 display: flex;
2711 align-items: center;
2712 gap: 8px;
2713 margin-bottom: 10px;
2714 }
2715 .landing-livecard-dot {
2716 width: 7px; height: 7px;
2717 border-radius: 50%;
2718 background: var(--green);
2719 box-shadow: 0 0 8px rgba(52,211,153,0.55);
2720 animation: landing-livenow-pulse 1.8s ease-out infinite;
2721 flex-shrink: 0;
2722 }
2723 .landing-livecard-title {
2724 margin: 0;
2725 font-size: 12px;
2726 font-weight: 600;
2727 text-transform: uppercase;
2728 letter-spacing: 0.06em;
2729 color: var(--text-muted);
2730 }
2731
2732 .landing-livecard-bignum {
2733 display: flex;
2734 align-items: baseline;
2735 gap: 8px;
2736 margin: 4px 0 10px;
2737 }
2738 .landing-livecard-bignum-n {
2739 font-size: 30px;
2740 font-weight: 700;
2741 color: var(--text-strong);
2742 font-variant-numeric: tabular-nums;
2743 background: var(--accent-gradient);
2744 -webkit-background-clip: text;
2745 background-clip: text;
2746 -webkit-text-fill-color: transparent;
2747 color: transparent;
2748 }
2749 .landing-livecard-bignum-label {
2750 font-size: 12px;
2751 color: var(--text-muted);
2752 }
2753
2754 .landing-livecard-list {
2755 list-style: none;
2756 margin: 0;
2757 padding: 0;
2758 font-size: 13px;
2759 line-height: 1.45;
2760 flex: 1;
2761 }
2762 .landing-livecard-row, .landing-livecard-feedrow {
2763 padding: 6px 0;
2764 border-bottom: 1px dashed rgba(255,255,255,0.05);
2765 transition: background-color 1s var(--ease, ease);
2766 border-radius: 4px;
2767 margin: 0 -4px;
2768 padding-left: 4px;
2769 padding-right: 4px;
2770 }
2771 .landing-livecard-row:last-child,
2772 .landing-livecard-feedrow:last-child { border-bottom: 0; }
2773 .landing-livecard-feedrow { padding: 4px; font-size: 12.5px; }
2774
2775 .landing-livecard-flash {
2776 background-color: rgba(52,211,153,0.18) !important;
2777 animation: landing-livecard-flash-fade 1.1s ease-out forwards;
2778 }
2779 @keyframes landing-livecard-flash-fade {
2780 0% { background-color: rgba(52,211,153,0.22); }
2781 100% { background-color: rgba(52,211,153,0); }
2782 }
2783
2784 .landing-livecard-link {
2785 color: var(--text-strong);
2786 text-decoration: none;
2787 display: inline-block;
2788 max-width: 100%;
2789 overflow: hidden;
2790 text-overflow: ellipsis;
2791 white-space: nowrap;
2792 }
2793 .landing-livecard-link:hover { color: var(--accent); }
2794 .landing-livecard-num {
2795 font-family: var(--font-mono, ui-monospace, monospace);
2796 color: var(--text-faint);
2797 font-weight: 500;
2798 font-size: 12px;
2799 }
2800 .landing-livecard-title-text { color: var(--text-strong); }
2801 .landing-livecard-snippet {
2802 color: var(--text-muted);
2803 font-style: italic;
2804 font-size: 12px;
2805 }
2806 .landing-livecard-meta {
2807 font-size: 11px;
2808 color: var(--text-faint);
2809 margin-top: 2px;
2810 font-family: var(--font-mono, ui-monospace, monospace);
2811 }
2812 .landing-livecard-repo {
2813 color: var(--accent-2);
2814 }
2815 .landing-livecard-rel {
2816 color: var(--text-muted);
2817 }
2818 .landing-livecard-empty {
2819 color: var(--text-faint);
2820 font-size: 12px;
2821 font-style: italic;
2822 padding: 8px 0;
2823 }
2824 .landing-livecard-kind {
2825 display: inline-block;
2826 padding: 1px 7px;
2827 border-radius: var(--r-full);
2828 font-family: var(--font-mono, ui-monospace, monospace);
2829 font-size: 10px;
2830 letter-spacing: 0.04em;
2831 text-transform: uppercase;
2832 font-weight: 600;
2833 }
2834 .landing-livecard-kind-auto_merge-merged,
2835 .landing-livecard-kind-auto-merge-merged {
2836 background: rgba(52,211,153,0.12);
2837 color: var(--green);
2838 border: 1px solid rgba(52,211,153,0.25);
2839 }
2840 .landing-livecard-kind-ai_build-dispatched,
2841 .landing-livecard-kind-ai-build-dispatched {
2842 background: rgba(140,109,255,0.12);
2843 color: var(--accent);
2844 border: 1px solid rgba(140,109,255,0.30);
2845 }
2846 .landing-livecard-kind-ai_review-posted,
2847 .landing-livecard-kind-ai-review-posted {
2848 background: rgba(54,197,214,0.12);
2849 color: var(--accent-2);
2850 border: 1px solid rgba(54,197,214,0.30);
2851 }
2852
2853 .landing-livenow-cta {
2854 margin-top: var(--s-5);
2855 display: flex;
2856 flex-wrap: wrap;
2857 align-items: center;
2858 justify-content: center;
2859 gap: 10px;
2860 font-size: 13px;
2861 color: var(--text-muted);
2862 }
2863 .landing-livenow-cta-link {
2864 color: var(--accent);
2865 text-decoration: none;
2866 font-weight: 500;
2867 display: inline-flex;
2868 align-items: center;
2869 gap: 4px;
2870 }
2871 .landing-livenow-cta-link:hover { color: var(--accent-hover); }
2872 .landing-livenow-cta-sep { color: var(--text-faint); }
2873
2874 @media (prefers-reduced-motion: reduce) {
2875 .landing-livenow-pulse,
2876 .landing-livecard-dot { animation: none; }
2877 .landing-livecard-flash { animation: none; background-color: transparent !important; }
2878 }
52ad8b1Claude2879`;
2880
2881/**
2882 * Block L4 — count-up animation.
2883 *
2884 * Reads each `[data-counter-target]` and animates the in-DOM text from
2885 * 0 → target over ~1.2s when the element first scrolls into view.
2886 *
2887 * Render-once semantics: each tile already contains the final value as
2888 * HTML, so visitors with JS disabled — or anyone before the script
2889 * loads — sees the correct number. The script just animates the text.
2890 *
2891 * Falls back to the static value (no animation) when IntersectionObserver
2892 * isn't available, or when the user prefers reduced motion.
2893 */
2894const landingCountersJs = `
2895(function(){
2896 try {
2897 var els = document.querySelectorAll('[data-counter-target]');
2898 if (!els.length) return;
2899 var reduced = window.matchMedia && window.matchMedia('(prefers-reduced-motion: reduce)').matches;
2900 if (reduced || typeof IntersectionObserver !== 'function') return;
2901
2902 function animate(el) {
2903 var target = parseInt(el.getAttribute('data-counter-target') || '0', 10);
2904 if (!isFinite(target) || target <= 0) return;
2905 var prefix = el.getAttribute('data-counter-prefix') || '';
2906 var suffix = el.getAttribute('data-counter-suffix') || '';
2907 var duration = 1200;
2908 var start = performance.now();
2909 function frame(now) {
2910 var t = Math.min(1, (now - start) / duration);
2911 // ease-out cubic
2912 var eased = 1 - Math.pow(1 - t, 3);
2913 var v = Math.floor(eased * target);
2914 el.textContent = prefix + v.toLocaleString() + suffix;
2915 if (t < 1) requestAnimationFrame(frame);
2916 else el.textContent = prefix + target.toLocaleString() + suffix;
2917 }
2918 // Reset to zero before animating in.
2919 el.textContent = prefix + '0' + suffix;
2920 requestAnimationFrame(frame);
2921 }
2922
2923 var io = new IntersectionObserver(function(entries) {
2924 entries.forEach(function(entry){
2925 if (entry.isIntersecting) {
2926 animate(entry.target);
2927 io.unobserve(entry.target);
2928 }
2929 });
2930 }, { threshold: 0.4 });
2931 els.forEach(function(el){ io.observe(el); });
2932 } catch (_) { /* swallow — static numbers remain */ }
2933})();
2b821b7Claude2934`;
5f2e749Claude2935
2936/**
2937 * Block L10 — clipboard copy for the hero install snippet.
2938 *
2939 * Pure progressive enhancement. Without JS the user can still
2940 * triple-click + Cmd/Ctrl-C the snippet — the button is the
2941 * speed-bump, not the only path.
2942 */
2943const landingCopyJs = `
2944(function(){
2945 try {
2946 var btns = document.querySelectorAll('[data-copy-target]');
2947 if (!btns.length) return;
2948 btns.forEach(function(btn){
2949 btn.addEventListener('click', function(){
2950 var id = btn.getAttribute('data-copy-target') || '';
2951 var src = document.getElementById(id);
2952 if (!src) return;
2953 var text = src.textContent || '';
2954 var done = function(){
2955 var prev = btn.textContent;
2956 btn.textContent = 'Copied';
2957 btn.setAttribute('data-copied', '1');
2958 setTimeout(function(){
2959 btn.textContent = prev || 'Copy';
2960 btn.removeAttribute('data-copied');
2961 }, 1500);
2962 };
2963 if (navigator.clipboard && navigator.clipboard.writeText) {
2964 navigator.clipboard.writeText(text).then(done, function(){ done(); });
2965 } else {
2966 // Legacy fallback — temp textarea + execCommand.
2967 try {
2968 var ta = document.createElement('textarea');
2969 ta.value = text;
2970 ta.style.position = 'fixed';
2971 ta.style.opacity = '0';
2972 document.body.appendChild(ta);
2973 ta.select();
2974 document.execCommand('copy');
2975 document.body.removeChild(ta);
2976 done();
2977 } catch (_) {}
2978 }
2979 });
2980 });
2981 } catch (_) { /* swallow */ }
2982})();
2983`;