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.tsxBlame1601 lines · 1 contributor
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 *
958d26aClaude8 * Pure presentational. Drops into <Layout user={null}>.
9 * All styles scoped under `.landing-` so they don't bleed into app views.
2b821b7Claude10 */
11
12import type { FC } from "hono/jsx";
52ad8b1Claude13import type { PublicStats } from "../lib/public-stats";
2b821b7Claude14
15export interface LandingPageProps {
16 stats?: {
17 publicRepos?: number;
18 users?: number;
19 };
52ad8b1Claude20 /**
21 * Block L4 — full public-stats payload (lifetime + trailing-7-day
22 * AI-highlight counters). When present, the hero renders an animated
23 * six-tile social-proof row beneath the eyebrow.
24 */
25 publicStats?: PublicStats | null;
2b821b7Claude26}
27
52ad8b1Claude28export const LandingHero: FC<LandingPageProps> = ({ stats, publicStats } = {}) => {
8e9f1d9Claude29 const hasStats =
30 stats &&
31 ((stats.publicRepos !== undefined && stats.publicRepos > 0) ||
32 (stats.users !== undefined && stats.users > 0));
4c47454Claude33
52ad8b1Claude34 // Block L4 — six-tile social proof row. Rendered only when the
35 // cached public-stats payload is available; absent → fall back to
36 // the small text-only `landing-stats` row.
37 const tiles = publicStats
38 ? buildSocialProofTiles(publicStats)
39 : null;
40
2b821b7Claude41 return (
42 <>
fa880f2Claude43 <style dangerouslySetInnerHTML={{ __html: landingCss }} />
2b821b7Claude44
4c47454Claude45 <div class="landing-root">
46 {/* ---------- Hero ---------- */}
47 <section class="landing-hero">
958d26aClaude48 <div class="landing-hero-bg" aria-hidden="true">
49 <div class="landing-hero-blob landing-hero-blob-1" />
50 <div class="landing-hero-blob landing-hero-blob-2" />
51 <div class="landing-hero-grid" />
2b821b7Claude52 </div>
958d26aClaude53
54 <div class="landing-hero-inner stagger">
55 <div class="eyebrow landing-hero-eyebrow">
56 <span class="landing-hero-pulse" />
57 v1 · pre-launch · {new Date().getFullYear()}
58 </div>
59
60 <h1 class="landing-hero-title display">
61 Where software{" "}
62 <span class="gradient-text">writes itself.</span>
63 </h1>
64
65 <p class="landing-hero-sub">
66 Gluecron is the operator-tier replacement for GitHub. Push code,
67 and the platform reviews it, fixes it, ships it. Spec-to-PR. Auto-repair.
68 Real-time gates. Built for the era when most code is written by AI
69 and most reviews are too.
70 </p>
71
72 <div class="landing-hero-ctas">
73 <a href="/register" class="btn btn-primary btn-xl landing-cta-primary">
74 Start shipping
75 <span class="landing-cta-arrow" aria-hidden="true">{"→"}</span>
76 </a>
77 <a href="/explore" class="btn btn-secondary btn-xl">
78 Explore repos
79 </a>
52ad8b1Claude80 <a href="/vs-github" class="btn btn-ghost btn-xl">
81 Compare to GitHub
82 </a>
958d26aClaude83 </div>
84
85 <p class="landing-hero-caption">
86 Already have a repo?
87 <span class="landing-hero-cmd">
88 <span class="kbd">git</span>
89 <span class="kbd">remote</span>
90 <span class="kbd">add</span>
91 <span class="kbd">gluecron</span>
92 <span class="landing-hero-arrow">{"→"}</span>
93 <span class="kbd">git push</span>
94 </span>
95 </p>
96
97 {hasStats && (
98 <p class="landing-stats">
99 {stats!.publicRepos !== undefined && stats!.publicRepos > 0 && (
100 <span>
101 <strong>{stats!.publicRepos.toLocaleString()}</strong>
102 {stats!.publicRepos === 1 ? " repo" : " repos"}
103 </span>
104 )}
105 {stats!.publicRepos !== undefined &&
106 stats!.publicRepos > 0 &&
107 stats!.users !== undefined &&
108 stats!.users > 0 && <span class="landing-stats-sep">·</span>}
109 {stats!.users !== undefined && stats!.users > 0 && (
110 <span>
111 <strong>{stats!.users.toLocaleString()}</strong>
112 {stats!.users === 1 ? " developer" : " developers"}
113 </span>
114 )}
115 <span class="landing-stats-sep">·</span>
4c47454Claude116 <span>
958d26aClaude117 <strong>100%</strong> AI-native
4c47454Claude118 </span>
958d26aClaude119 </p>
120 )}
121 </div>
c963db5Claude122 </section>
c475ee6Claude123
52ad8b1Claude124 {/* ---------- L4 social-proof counters (animated count-up) ---------- */}
125 {tiles && (
126 <section class="landing-counters" aria-label="Gluecron live counters">
127 <div class="landing-counters-grid">
128 {tiles.map((t) => (
129 <div class="landing-counter">
130 <div
131 class="landing-counter-num"
132 data-counter-target={String(t.value)}
133 data-counter-suffix={t.suffix ?? ""}
134 data-counter-prefix={t.prefix ?? ""}
135 >
136 {t.prefix ?? ""}
137 {t.value.toLocaleString()}
138 {t.suffix ?? ""}
139 </div>
140 <div class="landing-counter-label">{t.label}</div>
141 </div>
142 ))}
143 </div>
144 <script dangerouslySetInnerHTML={{ __html: landingCountersJs }} />
145 </section>
146 )}
147
c963db5Claude148 {/* ---------- Capability strip — uppercase tracked grid (crontech-style) ---------- */}
149 <section class="landing-caps">
150 <div class="landing-caps-grid">
151 <span class="landing-cap">Claude-powered AI</span>
152 <span class="landing-cap">Spec-to-PR</span>
153 <span class="landing-cap">Auto-repair</span>
154 <span class="landing-cap">Real-time gates</span>
155 <span class="landing-cap">MCP-native</span>
156 <span class="landing-cap">Workflow runner</span>
157 <span class="landing-cap">Self-hostable</span>
158 <span class="landing-cap">Branch protection</span>
159 <span class="landing-cap">Bun + Hono</span>
160 <span class="landing-cap">Drizzle + Postgres</span>
161 <span class="landing-cap">JSX server-rendered</span>
162 <span class="landing-cap">Type-safe end to end</span>
c475ee6Claude163 </div>
958d26aClaude164 </section>
165
c963db5Claude166 {/* ---------- Big stat row (crontech-style hero closer) ---------- */}
167 <section class="landing-bigstats">
168 <div class="landing-bigstats-grid">
169 <div class="landing-bigstat">
170 <div class="landing-bigstat-num">Claude-powered</div>
171 <div class="landing-bigstat-label">The best AI, native</div>
172 </div>
173 <div class="landing-bigstat">
174 <div class="landing-bigstat-num">Self-hosted</div>
175 <div class="landing-bigstat-label">On your hardware</div>
176 </div>
177 <div class="landing-bigstat">
178 <div class="landing-bigstat-num">MCP-native</div>
179 <div class="landing-bigstat-label">Claude · Cursor · Code</div>
180 </div>
181 <div class="landing-bigstat">
182 <div class="landing-bigstat-num">Real-time</div>
183 <div class="landing-bigstat-label">SSE everywhere</div>
184 </div>
958d26aClaude185 </div>
4c47454Claude186 </section>
187
188 {/* ---------- Feature grid ---------- */}
958d26aClaude189 <section class="landing-section">
190 <div class="section-header">
191 <div class="eyebrow">The platform</div>
192 <h2>An IDE for your repo, not just a host.</h2>
193 <p>
194 Gluecron ships the surfaces GitHub charges extra for, and the
195 ones it never built. AI is a teammate with its own commits, not
196 a sidebar.
2b821b7Claude197 </p>
198 </div>
4c47454Claude199
958d26aClaude200 <div class="landing-features stagger">
201 <FeatureCard
202 icon={
203 <svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round">
204 <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" />
205 </svg>
206 }
207 title="AI as a teammate"
208 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."
209 />
210 <FeatureCard
211 icon={
212 <svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round">
213 <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" />
214 <path d="M9 12l2 2 4-4" />
215 </svg>
216 }
217 title="Quality gate that learns"
218 desc="GateTest scans every push. Auto-repair fixes regressions before you see them. Required checks block bad PRs from merging. Your software self-corrects."
219 />
220 <FeatureCard
221 icon={
222 <svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round">
223 <path d="M13 2L4 14h7l-1 8 9-12h-7z" />
224 </svg>
225 }
226 title="Real-time everything"
227 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."
228 />
229 <FeatureCard
230 icon={
231 <svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round">
232 <rect x="3" y="3" width="18" height="18" rx="3" />
233 <path d="M3 9h18M9 21V9" />
234 </svg>
235 }
236 title="Workflow runner"
237 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."
238 />
239 <FeatureCard
240 icon={
241 <svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round">
242 <circle cx="12" cy="12" r="9" />
243 <path d="M3 12h18M12 3a14 14 0 010 18M12 3a14 14 0 000 18" />
244 </svg>
245 }
246 title="MCP-native"
247 desc="Claude, Cursor, Code — they speak Model Context Protocol. Gluecron exposes search, file read, issues, codebase explain as MCP tools by default."
248 />
249 <FeatureCard
250 icon={
251 <svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round">
252 <path d="M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
253 <path d="M12 7v5l3 2" />
254 </svg>
255 }
256 title="Yours, on your hardware"
257 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."
258 />
2b821b7Claude259 </div>
958d26aClaude260 </section>
2b821b7Claude261
958d26aClaude262 {/* ---------- Workflow walkthrough ---------- */}
263 <section class="landing-section landing-walk">
264 <div class="section-header">
265 <div class="eyebrow">How it works</div>
266 <h2>Push code. Watch it ship.</h2>
267 <p>
268 Every push triggers the same pipeline whether the commit came
269 from you, from CI, or from an AI agent.
4c47454Claude270 </p>
271 </div>
958d26aClaude272
273 <div class="landing-walk-grid">
274 <WalkStep n="01" title="Push" desc="git push to gluecron — Smart-HTTP, SSH, or via the web editor." />
275 <WalkStep n="02" title="Gate" desc="GateTest runs. Secret scanner runs. AI security review posts inline comments." />
276 <WalkStep n="03" title="Repair" desc="If a gate fails, auto-repair tries to fix it. New commit gets re-gated." />
277 <WalkStep n="04" title="Ship" desc="Green push to default branch fires deploy webhook. Crontech, Fly, your prod." />
278 </div>
4c47454Claude279 </section>
2b821b7Claude280
4c47454Claude281 {/* ---------- Terminal block ---------- */}
958d26aClaude282 <section class="landing-section landing-terminal-section">
283 <div class="landing-terminal-wrap">
284 <div class="landing-terminal" role="img" aria-label="Example git push to gluecron with passing gates">
285 <div class="landing-terminal-chrome">
286 <span class="landing-terminal-dot landing-terminal-dot-r" />
287 <span class="landing-terminal-dot landing-terminal-dot-y" />
288 <span class="landing-terminal-dot landing-terminal-dot-g" />
289 <span class="landing-terminal-title">~/your-repo &mdash; zsh</span>
290 </div>
291 <div class="landing-terminal-body">
292 <div class="landing-term-line">
293 <span class="landing-term-prompt">$</span>
294 <span>git remote add gluecron https://gluecron.com/you/your-repo.git</span>
295 </div>
296 <div class="landing-term-line">
297 <span class="landing-term-prompt">$</span>
298 <span>git push -u gluecron main</span>
299 </div>
300 <div class="landing-term-line landing-term-out">
301 <span class="landing-term-meta">remote:</span>
302 <span>Resolving deltas… 100% (24/24)</span>
303 </div>
304 <div class="landing-term-line landing-term-out landing-term-ok-line">
305 <span class="landing-term-ok">{"✓"}</span>
306 <span>pushed to gluecron.com/you/your-repo</span>
307 </div>
308 <div class="landing-term-line landing-term-out landing-term-ok-line">
309 <span class="landing-term-ok">{"✓"}</span>
310 <span>GateTest passed (12 rules, 0 violations)</span>
311 </div>
312 <div class="landing-term-line landing-term-out landing-term-ok-line">
313 <span class="landing-term-ok">{"✓"}</span>
314 <span>AI review posted (2 suggestions, 0 blockers)</span>
315 </div>
316 <div class="landing-term-line landing-term-out landing-term-ok-line">
317 <span class="landing-term-ok">{"✓"}</span>
318 <span>deployed to your-repo.gluecron.com <span class="landing-term-meta">(4.1s)</span></span>
319 </div>
320 <div class="landing-term-line landing-term-cursor">
321 <span class="landing-term-prompt">$</span>
322 <span class="landing-term-blink">▍</span>
323 </div>
324 </div>
4c47454Claude325 </div>
958d26aClaude326 </div>
327 </section>
328
329 {/* ---------- Comparison ---------- */}
330 <section class="landing-section">
331 <div class="section-header">
332 <div class="eyebrow">vs the incumbent</div>
333 <h2>Everything GitHub charges for. And the parts they didn't build.</h2>
334 </div>
335
336 <div class="landing-compare">
337 <CompareRow feature="Git hosting + Smart-HTTP push" them="✓" us="✓" />
338 <CompareRow feature="Issues, PRs, code review" them="✓" us="✓" />
339 <CompareRow feature="Workflow runner (Actions-equivalent)" them="paid minutes" us="self-hosted, unmetered" highlight />
340 <CompareRow feature="AI code review on every PR" them="Copilot subscription" us="built in" highlight />
341 <CompareRow feature="Spec-to-PR (NL feature → draft PR)" them="—" us="✓" highlight />
342 <CompareRow feature="Auto-repair on failed gates" them="—" us="✓" highlight />
343 <CompareRow feature="Real-time SSE for logs + PRs" them="polling" us="streaming" highlight />
344 <CompareRow feature="MCP server (Claude / Cursor)" them="—" us="✓" highlight />
345 <CompareRow feature="Self-host on your own infra" them="enterprise tier" us="single binary" highlight />
346 <CompareRow feature="Pre-receive policy enforcement" them="rulesets (GHE)" us="✓" />
347 </div>
348 </section>
349
350 {/* ---------- Pricing teaser ---------- */}
351 <section class="landing-section">
352 <div class="section-header">
353 <div class="eyebrow">Pricing</div>
354 <h2>Free to start. Honest at scale.</h2>
355 <p>
356 Self-hosting is free forever. Hosted plans price the AI calls,
357 not the seats.
358 </p>
359 </div>
360
361 <div class="landing-pricing">
362 <PricingCard
363 tier="Free"
364 price="$0"
365 cadence="forever"
366 desc="For personal projects + open source. Public + private repos, full AI suite, fair quotas."
367 features={["Unlimited public repos", "3 private repos", "5K AI calls / mo", "Community support"]}
368 cta="Start free"
369 href="/register"
370 />
371 <PricingCard
372 tier="Pro"
373 price="$12"
374 cadence="per user / mo"
375 desc="For working developers. Lifts every quota, adds priority routing, no Gluecron branding on deploys."
376 features={["Unlimited private repos", "100K AI calls / mo", "Priority queue", "Custom domains"]}
377 cta="Go Pro"
378 href="/settings/billing"
379 highlight
380 />
381 <PricingCard
382 tier="Team"
383 price="Talk to us"
384 cadence="custom"
385 desc="For orgs running production on Gluecron. SSO, audit retention, enterprise SLA, on-prem."
386 features={["SSO + SCIM", "On-prem deploy", "Dedicated capacity", "24/7 incident response"]}
387 cta="Contact"
388 href="mailto:hello@gluecron.com"
389 />
390 </div>
391 </section>
392
393 {/* ---------- Closing CTA ---------- */}
394 <section class="landing-cta-section">
395 <div class="landing-cta-card">
396 <div class="landing-cta-bg" aria-hidden="true" />
397 <div class="eyebrow">Ready when you are</div>
398 <h2 class="landing-cta-title">
399 Stop maintaining the platform.<br />
400 <span class="gradient-text">Start shipping the product.</span>
401 </h2>
402 <p class="landing-cta-sub">
403 Free to start, self-hosted-friendly, MCP-native. Migrate from
404 GitHub in one click.
405 </p>
406 <div class="landing-cta-buttons">
407 <a href="/register" class="btn btn-primary btn-xl">
408 Create your account
409 <span class="landing-cta-arrow" aria-hidden="true">{"→"}</span>
410 </a>
411 <a href="/import" class="btn btn-ghost btn-xl">
412 Migrate a repo
413 </a>
4c47454Claude414 </div>
415 </div>
416 </section>
417 </div>
2b821b7Claude418 </>
419 );
420};
421
958d26aClaude422const FeatureCard: FC<{ icon: any; title: string; desc: string }> = ({
423 icon,
424 title,
425 desc,
426}) => (
427 <div class="landing-feature">
428 <div class="landing-feature-icon" aria-hidden="true">
429 {icon}
430 </div>
431 <h3 class="landing-feature-title">{title}</h3>
432 <p class="landing-feature-desc">{desc}</p>
433 </div>
434);
435
436const WalkStep: FC<{ n: string; title: string; desc: string }> = ({
437 n,
438 title,
439 desc,
440}) => (
441 <div class="landing-walk-step">
442 <div class="landing-walk-num">{n}</div>
443 <h3 class="landing-walk-title">{title}</h3>
444 <p class="landing-walk-desc">{desc}</p>
445 </div>
446);
447
448const CompareRow: FC<{
449 feature: string;
450 them: string;
451 us: string;
452 highlight?: boolean;
453}> = ({ feature, them, us, highlight }) => (
454 <div class={`landing-compare-row${highlight ? " landing-compare-hl" : ""}`}>
455 <div class="landing-compare-feature">{feature}</div>
456 <div class="landing-compare-them">{them}</div>
457 <div class="landing-compare-us">{us === "✓" ? "✓" : us}</div>
458 </div>
459);
460
461const PricingCard: FC<{
462 tier: string;
463 price: string;
464 cadence: string;
465 desc: string;
466 features: string[];
467 cta: string;
468 href: string;
469 highlight?: boolean;
470}> = ({ tier, price, cadence, desc, features, cta, href, highlight }) => (
471 <div class={`landing-price-card${highlight ? " landing-price-hl" : ""}`}>
472 {highlight && <div class="landing-price-badge">Most popular</div>}
473 <div class="landing-price-tier">{tier}</div>
474 <div class="landing-price-amount">
475 <span class="landing-price-num">{price}</span>
476 <span class="landing-price-cad">{cadence}</span>
477 </div>
478 <p class="landing-price-desc">{desc}</p>
479 <ul class="landing-price-features">
480 {features.map((f) => (
481 <li>
482 <span class="landing-price-check" aria-hidden="true">{"✓"}</span>
483 {f}
484 </li>
485 ))}
486 </ul>
487 <a
488 href={href}
489 class={`btn ${highlight ? "btn-primary" : "btn-secondary"} btn-block landing-price-cta`}
490 >
491 {cta}
492 </a>
493 </div>
494);
495
52ad8b1Claude496// ─────────────────────────────────────────────────────────────────
497// Block L4 — social-proof tile builder.
498//
499// Pure: takes the cached PublicStats payload and emits the six
500// landing-page tiles in render order. Exported so tests / future
501// surfaces (dashboard, /about, …) can share the exact same copy.
502// ─────────────────────────────────────────────────────────────────
503
504export interface SocialProofTile {
505 label: string;
506 value: number;
507 prefix?: string;
508 suffix?: string;
509}
510
511export function buildSocialProofTiles(s: PublicStats): SocialProofTile[] {
512 return [
513 { label: "Public repos", value: s.totalPublicRepos },
514 { label: "Developers", value: s.totalUsers },
515 {
516 label: "PRs auto-merged this week",
517 value: s.weeklyPrsAutoMerged,
518 },
519 {
520 label: "Issues built by AI this week",
521 value: s.weeklyIssuesBuiltByAi,
522 },
523 {
524 label: "Deploys shipped this week",
525 value: s.weeklyDeploysShipped,
526 },
527 {
528 label: "Hours saved this week",
529 // Round to whole hours for the tile — the precise 0.1 figure
530 // lives on the dashboard widget; the marketing surface keeps
531 // the number scannable.
532 value: Math.round(s.weeklyHoursSaved),
533 prefix: "~",
534 suffix: "h",
535 },
536 ];
537}
538
4c47454Claude539// Backwards-compatible default — web.tsx imports `LandingPage`.
540export const LandingPage: FC<LandingPageProps> = (props) => (
541 <LandingHero {...props} />
2b821b7Claude542);
543
4c47454Claude544export default LandingPage;
545
2b821b7Claude546const landingCss = `
958d26aClaude547 /* ============================================================ */
548 /* Landing — Editorial-Technical 2026.05 */
549 /* ============================================================ */
4c47454Claude550 .landing-root {
551 position: relative;
958d26aClaude552 max-width: 1180px;
4c47454Claude553 margin: 0 auto;
554 padding: 0 16px;
958d26aClaude555 }
556 .landing-root > section { position: relative; }
557
558 /* ---------- Hero ---------- */
559 .landing-hero {
560 position: relative;
c475ee6Claude561 padding: var(--s-16) 0 var(--s-20);
958d26aClaude562 text-align: center;
4c47454Claude563 overflow: hidden;
564 }
c475ee6Claude565 .landing-hero-blob-1 {
566 animation: hero-blob-drift-1 18s var(--ease, ease) infinite alternate;
567 }
568 .landing-hero-blob-2 {
569 animation: hero-blob-drift-2 22s var(--ease, ease) infinite alternate;
570 }
571 @keyframes hero-blob-drift-1 {
572 0% { transform: translate(0, 0) scale(1); opacity: 0.55; }
573 100% { transform: translate(8%, 6%) scale(1.18); opacity: 0.75; }
574 }
575 @keyframes hero-blob-drift-2 {
576 0% { transform: translate(0, 0) scale(1); opacity: 0.40; }
577 100% { transform: translate(-10%, -4%) scale(1.25); opacity: 0.60; }
578 }
579
580 /* ---------- Hero product visual: live AI PR review card ---------- */
581 .landing-hero-visual {
582 position: relative;
583 max-width: 760px;
584 margin: var(--s-12) auto 0;
585 padding: 0 16px;
586 perspective: 1400px;
587 z-index: 2;
588 opacity: 0;
589 animation: hero-visual-in 700ms var(--ease-out-expo, cubic-bezier(0.19, 1, 0.22, 1)) 400ms forwards;
590 }
591 @keyframes hero-visual-in {
592 from { opacity: 0; transform: translateY(20px); }
593 to { opacity: 1; transform: translateY(0); }
594 }
595 .hero-pr-card {
596 position: relative;
597 background: linear-gradient(180deg, rgba(15,17,26,0.96) 0%, rgba(8,9,15,0.96) 100%);
598 border: 1px solid var(--border-strong);
599 border-radius: var(--r-xl);
600 overflow: hidden;
601 text-align: left;
602 box-shadow:
603 0 30px 80px -20px rgba(0,0,0,0.65),
604 0 0 0 1px rgba(140,109,255,0.18),
605 0 0 60px -10px rgba(140,109,255,0.30);
606 transform: rotateX(2deg) rotateY(-2deg);
607 transition: transform 600ms var(--ease, ease);
608 backdrop-filter: blur(12px);
609 -webkit-backdrop-filter: blur(12px);
610 }
611 .landing-hero-visual:hover .hero-pr-card {
612 transform: rotateX(0deg) rotateY(0deg);
613 }
614 .hero-pr-card::before {
615 content: '';
616 position: absolute;
617 inset: 0;
618 background: linear-gradient(135deg, rgba(140,109,255,0.10), transparent 35%, transparent 65%, rgba(54,197,214,0.08));
619 pointer-events: none;
620 }
621 .hero-pr-header {
622 display: flex;
623 align-items: center;
624 gap: 12px;
625 padding: 14px 18px;
626 border-bottom: 1px solid rgba(255,255,255,0.06);
627 background: rgba(255,255,255,0.025);
628 font-size: 13px;
629 }
630 .hero-pr-dot {
631 width: 10px; height: 10px;
632 border-radius: 50%;
633 background: var(--green);
634 box-shadow: 0 0 10px rgba(52,211,153,0.6);
635 flex-shrink: 0;
636 }
637 .hero-pr-title {
638 color: var(--text-strong);
639 font-weight: 600;
640 flex: 1;
641 overflow: hidden;
642 text-overflow: ellipsis;
643 white-space: nowrap;
644 }
645 .hero-pr-num {
646 color: var(--text-faint);
647 font-family: var(--font-mono);
648 font-weight: 500;
649 margin-right: 8px;
650 }
651 .hero-pr-status {
652 display: inline-flex;
653 align-items: center;
654 gap: 6px;
655 padding: 3px 10px;
656 border-radius: var(--r-full);
657 background: var(--accent-gradient-faint);
658 border: 1px solid rgba(140,109,255,0.30);
659 color: var(--accent);
660 font-family: var(--font-mono);
661 font-size: 11px;
662 letter-spacing: 0.04em;
663 flex-shrink: 0;
664 }
665 .hero-pr-status-pulse {
666 width: 6px; height: 6px;
667 border-radius: 50%;
668 background: var(--accent);
669 box-shadow: 0 0 0 0 rgba(140,109,255,0.6);
670 animation: hero-pulse 1.6s ease-out infinite;
671 }
672 @keyframes hero-pulse {
673 0% { box-shadow: 0 0 0 0 rgba(140,109,255,0.55); }
674 70% { box-shadow: 0 0 0 8px rgba(140,109,255,0); }
675 100% { box-shadow: 0 0 0 0 rgba(140,109,255,0); }
676 }
677
678 .hero-pr-body {
679 padding: 0;
680 }
681 .hero-pr-file {
682 display: flex;
683 align-items: center;
684 gap: 10px;
685 padding: 10px 18px;
686 border-bottom: 1px solid rgba(255,255,255,0.05);
687 font-family: var(--font-mono);
688 font-size: 12px;
689 background: rgba(255,255,255,0.012);
690 }
691 .hero-pr-file-icon { color: var(--accent-2); }
692 .hero-pr-file-name { color: var(--text); flex: 1; }
693 .hero-pr-file-stats { display: inline-flex; gap: 8px; }
694 .hero-pr-add { color: var(--green); font-weight: 600; }
695 .hero-pr-del { color: var(--red); font-weight: 600; }
696
697 .hero-pr-diff {
698 padding: 12px 18px;
699 font-family: var(--font-mono);
700 font-feature-settings: var(--mono-feat, 'calt');
701 font-size: 12.5px;
702 line-height: 1.7;
703 color: rgba(237,237,242,0.85);
704 overflow-x: auto;
705 }
706 .hero-pr-hunk {
707 color: rgba(140,109,255,0.85);
708 background: rgba(140,109,255,0.06);
709 padding: 2px 8px;
710 margin: 0 -8px 4px;
711 border-radius: 4px;
712 }
713 .hero-pr-line-add {
714 background: rgba(52,211,153,0.08);
715 color: rgba(167,243,208,0.95);
716 padding: 0 8px;
717 margin: 0 -8px;
718 border-left: 2px solid var(--green);
719 padding-left: 8px;
720 }
721
722 .hero-pr-comment {
723 margin: 14px 18px;
724 padding: 14px 16px;
725 background: linear-gradient(135deg, rgba(140,109,255,0.08), rgba(54,197,214,0.05));
726 border: 1px solid rgba(140,109,255,0.25);
727 border-radius: var(--r-md);
728 }
729 .hero-pr-bot-row {
730 display: flex;
731 align-items: center;
732 gap: 8px;
733 margin-bottom: 8px;
734 font-size: 12px;
735 }
736 .hero-pr-bot-avatar {
737 width: 22px; height: 22px;
738 display: inline-flex;
739 align-items: center;
740 justify-content: center;
741 border-radius: 50%;
742 background: var(--accent-gradient);
743 font-size: 11px;
744 box-shadow: 0 0 12px rgba(140,109,255,0.40);
745 }
746 .hero-pr-bot-name {
747 color: var(--text-strong);
748 font-weight: 600;
749 }
750 .hero-pr-bot-meta {
751 color: var(--text-faint);
752 font-family: var(--font-mono);
753 }
754 .hero-pr-bot-text {
755 color: var(--text);
756 font-size: 13px;
757 line-height: 1.55;
758 margin: 0;
759 }
760 .hero-pr-bot-text code {
761 background: rgba(255,255,255,0.06);
762 border: 1px solid rgba(255,255,255,0.10);
763 padding: 1px 6px;
764 border-radius: 4px;
765 font-size: 11.5px;
766 color: var(--accent);
767 }
768 .hero-pr-bot-link { color: var(--accent-2); text-decoration: underline; text-decoration-style: dotted; }
769
770 .hero-pr-gates {
771 display: flex;
772 flex-wrap: wrap;
773 gap: 6px;
774 padding: 12px 18px 16px;
775 border-top: 1px solid rgba(255,255,255,0.06);
776 background: rgba(255,255,255,0.012);
777 }
778 .hero-pr-gate {
779 display: inline-flex;
780 align-items: center;
781 gap: 6px;
782 padding: 4px 10px;
783 border-radius: var(--r-full);
784 font-family: var(--font-mono);
785 font-size: 11px;
786 border: 1px solid;
787 }
788 .hero-pr-gate-pass {
789 color: var(--green);
790 background: rgba(52,211,153,0.08);
791 border-color: rgba(52,211,153,0.30);
792 }
793 .hero-pr-gate-running {
794 color: var(--accent);
795 background: var(--accent-gradient-faint);
796 border-color: rgba(140,109,255,0.40);
797 }
798 .hero-pr-gate-spin {
799 width: 9px; height: 9px;
800 border: 1.5px solid rgba(140,109,255,0.30);
801 border-top-color: var(--accent);
802 border-radius: 50%;
803 animation: hero-spin 800ms linear infinite;
804 }
805 @keyframes hero-spin {
806 to { transform: rotate(360deg); }
807 }
808
809 /* Floating accent badges around the card */
810 .hero-float {
811 position: absolute;
812 display: inline-flex;
813 align-items: center;
814 gap: 6px;
815 padding: 6px 12px;
816 background: rgba(15,17,26,0.92);
817 border: 1px solid rgba(140,109,255,0.35);
818 border-radius: var(--r-full);
819 font-family: var(--font-mono);
820 font-size: 11px;
821 color: var(--text);
822 box-shadow: 0 12px 24px -8px rgba(0,0,0,0.5), 0 0 18px -4px rgba(140,109,255,0.30);
823 backdrop-filter: blur(8px);
824 -webkit-backdrop-filter: blur(8px);
825 }
826 .hero-float-icon { color: var(--accent); }
827 .hero-float-1 {
828 top: -14px;
829 left: -8px;
830 animation: hero-float-bob-1 5s var(--ease, ease) infinite alternate;
831 }
832 .hero-float-2 {
833 bottom: -14px;
834 right: -8px;
835 animation: hero-float-bob-2 6s var(--ease, ease) infinite alternate;
836 }
837 @keyframes hero-float-bob-1 {
838 from { transform: translate(0, 0); }
839 to { transform: translate(-8px, -10px); }
840 }
841 @keyframes hero-float-bob-2 {
842 from { transform: translate(0, 0); }
843 to { transform: translate(8px, 8px); }
844 }
845
846 @media (max-width: 720px) {
847 .landing-hero-visual { padding: 0 8px; }
848 .hero-pr-card { transform: none; }
849 .hero-pr-title { font-size: 12px; }
850 .hero-pr-diff { font-size: 11px; line-height: 1.6; }
851 .hero-float { display: none; }
852 }
958d26aClaude853 .landing-hero-bg {
854 position: absolute;
855 inset: -10% -20%;
856 pointer-events: none;
857 z-index: 0;
4c47454Claude858 }
958d26aClaude859 .landing-hero-blob {
860 position: absolute;
861 border-radius: 50%;
862 filter: blur(80px);
863 opacity: 0.55;
864 }
865 .landing-hero-blob-1 {
866 top: -10%;
867 left: 30%;
868 width: 480px;
869 height: 480px;
870 background: radial-gradient(circle, rgba(140,109,255,0.55), transparent 65%);
871 }
872 .landing-hero-blob-2 {
873 top: 10%;
874 left: 50%;
875 width: 380px;
876 height: 380px;
877 background: radial-gradient(circle, rgba(54,197,214,0.40), transparent 65%);
878 }
879 .landing-hero-grid {
4c47454Claude880 position: absolute;
881 inset: 0;
958d26aClaude882 background-image:
883 linear-gradient(to right, rgba(255,255,255,0.04) 1px, transparent 1px),
884 linear-gradient(to bottom, rgba(255,255,255,0.04) 1px, transparent 1px);
885 background-size: 60px 60px;
886 mask-image: radial-gradient(ellipse 50% 50% at 50% 30%, #000 0%, transparent 75%);
887 -webkit-mask-image: radial-gradient(ellipse 50% 50% at 50% 30%, #000 0%, transparent 75%);
888 }
889 :root[data-theme='light'] .landing-hero-grid {
890 background-image:
891 linear-gradient(to right, rgba(15,16,28,0.06) 1px, transparent 1px),
892 linear-gradient(to bottom, rgba(15,16,28,0.06) 1px, transparent 1px);
4c47454Claude893 }
894
958d26aClaude895 .landing-hero-inner {
896 position: relative;
897 z-index: 1;
898 max-width: 960px;
2b821b7Claude899 margin: 0 auto;
900 }
958d26aClaude901 .landing-hero-eyebrow {
902 margin: 0 auto var(--s-6);
903 color: var(--accent);
904 }
905 .landing-hero-eyebrow::before { display: none; }
906 .landing-hero-pulse {
907 width: 7px;
908 height: 7px;
909 border-radius: 50%;
910 background: var(--accent);
911 box-shadow: 0 0 0 0 rgba(140,109,255,0.6);
912 animation: pulse 1.8s ease-out infinite;
913 flex-shrink: 0;
914 }
915 @keyframes pulse {
916 0% { box-shadow: 0 0 0 0 rgba(140,109,255,0.55); }
917 70% { box-shadow: 0 0 0 10px rgba(140,109,255,0); }
918 100% { box-shadow: 0 0 0 0 rgba(140,109,255,0); }
919 }
920
2b821b7Claude921 .landing-hero-title {
c963db5Claude922 font-size: clamp(48px, 9.5vw, 124px);
923 line-height: 0.96;
924 letter-spacing: -0.045em;
925 font-weight: 700;
926 margin: 0 0 var(--s-7);
958d26aClaude927 color: var(--text-strong);
2b821b7Claude928 }
c963db5Claude929 .landing-hero-title .gradient-text {
930 background-image: linear-gradient(135deg, #a48bff 0%, #8c6dff 50%, #6d4dff 100%);
931 -webkit-background-clip: text;
932 background-clip: text;
933 -webkit-text-fill-color: transparent;
934 color: transparent;
935 }
958d26aClaude936
2b821b7Claude937 .landing-hero-sub {
958d26aClaude938 font-size: clamp(15px, 1.6vw, 19px);
2b821b7Claude939 color: var(--text-muted);
958d26aClaude940 max-width: 680px;
941 margin: 0 auto;
4c47454Claude942 line-height: 1.55;
958d26aClaude943 letter-spacing: -0.005em;
2b821b7Claude944 }
4c47454Claude945
2b821b7Claude946 .landing-hero-ctas {
947 display: flex;
958d26aClaude948 gap: 12px;
2b821b7Claude949 justify-content: center;
950 flex-wrap: wrap;
958d26aClaude951 margin-top: var(--s-10);
2b821b7Claude952 }
958d26aClaude953 .landing-cta-arrow {
954 transition: transform var(--t-base) var(--ease-spring);
955 display: inline-block;
2b821b7Claude956 }
958d26aClaude957 .btn:hover .landing-cta-arrow,
958 .landing-cta-primary:hover .landing-cta-arrow {
959 transform: translateX(4px);
8e9f1d9Claude960 }
2b821b7Claude961
4c47454Claude962 .landing-hero-caption {
958d26aClaude963 margin-top: var(--s-8);
964 font-size: var(--t-sm);
4c47454Claude965 color: var(--text-muted);
958d26aClaude966 display: flex;
967 flex-wrap: wrap;
968 align-items: center;
969 justify-content: center;
970 gap: 12px;
2b821b7Claude971 }
958d26aClaude972 .landing-hero-cmd {
973 display: inline-flex;
974 align-items: center;
975 gap: 4px;
976 padding: 6px 12px;
977 background: var(--bg-elevated);
4c47454Claude978 border: 1px solid var(--border);
958d26aClaude979 border-radius: var(--r-full);
980 box-shadow: var(--elev-1);
981 }
982 .landing-hero-cmd .kbd {
983 border: 0;
984 background: transparent;
985 padding: 0 4px;
986 color: var(--text-muted);
987 font-size: 12px;
988 }
989 .landing-hero-cmd .kbd:nth-last-of-type(1) { color: var(--accent); }
990 .landing-hero-arrow {
991 color: var(--text-faint);
992 font-size: 13px;
993 margin: 0 2px;
2b821b7Claude994 }
4c47454Claude995
996 .landing-stats {
958d26aClaude997 margin-top: var(--s-7);
998 font-family: var(--font-mono);
999 font-size: 12px;
1000 color: var(--text-muted);
1001 display: flex;
1002 align-items: center;
1003 justify-content: center;
1004 gap: 10px;
1005 flex-wrap: wrap;
1006 letter-spacing: 0.02em;
1007 }
1008 .landing-stats strong {
1009 color: var(--text-strong);
1010 font-weight: 600;
1011 font-feature-settings: 'tnum';
1012 }
1013 .landing-stats-sep { opacity: 0.4; }
1014
c963db5Claude1015 /* ---------- Capability grid (crontech-style uppercase tracked) ---------- */
1016 .landing-caps {
1017 margin: var(--s-12) auto var(--s-16);
1018 max-width: 1080px;
1019 padding: var(--s-7) var(--s-4);
958d26aClaude1020 border-top: 1px solid var(--border-subtle);
1021 border-bottom: 1px solid var(--border-subtle);
c963db5Claude1022 }
1023 .landing-caps-grid {
1024 display: grid;
1025 grid-template-columns: repeat(4, 1fr);
1026 gap: 24px 16px;
958d26aClaude1027 text-align: center;
1028 }
c963db5Claude1029 .landing-cap {
958d26aClaude1030 font-family: var(--font-mono);
1031 font-size: 11px;
c963db5Claude1032 font-weight: 600;
958d26aClaude1033 text-transform: uppercase;
c963db5Claude1034 letter-spacing: 0.16em;
1035 color: var(--text-muted);
1036 transition: color var(--t-fast) var(--ease);
958d26aClaude1037 }
c963db5Claude1038 .landing-cap:hover { color: var(--text-strong); }
1039 @media (max-width: 800px) {
1040 .landing-caps-grid { grid-template-columns: repeat(2, 1fr); gap: 18px 12px; }
1041 }
1042 @media (max-width: 480px) {
1043 .landing-caps-grid { grid-template-columns: 1fr; }
1044 }
1045
1046 /* ---------- Big stat row (crontech-style hero closer) ---------- */
1047 .landing-bigstats {
1048 margin: var(--s-10) auto var(--s-20);
1049 max-width: 1180px;
1050 padding: 0 var(--s-4);
1051 }
1052 .landing-bigstats-grid {
1053 display: grid;
1054 grid-template-columns: repeat(4, 1fr);
1055 gap: 32px;
1056 text-align: left;
958d26aClaude1057 }
c963db5Claude1058 .landing-bigstat {
1059 padding: var(--s-2) 0;
1060 }
1061 .landing-bigstat-num {
958d26aClaude1062 font-family: var(--font-display);
c963db5Claude1063 font-size: clamp(28px, 3.5vw, 44px);
1064 line-height: 1.05;
1065 letter-spacing: -0.03em;
1066 font-weight: 700;
1067 color: var(--text-strong);
1068 margin-bottom: var(--s-2);
1069 }
1070 .landing-bigstat-label {
1071 font-family: var(--font-mono);
1072 font-size: 11px;
958d26aClaude1073 font-weight: 500;
c963db5Claude1074 text-transform: uppercase;
1075 letter-spacing: 0.14em;
1076 color: var(--text-faint);
2b821b7Claude1077 }
c963db5Claude1078 @media (max-width: 800px) {
1079 .landing-bigstats-grid { grid-template-columns: repeat(2, 1fr); gap: 28px 16px; }
1080 }
1081 @media (max-width: 480px) {
1082 .landing-bigstats-grid { grid-template-columns: 1fr; gap: 24px; }
958d26aClaude1083 }
1084
1085 /* ---------- Section base ---------- */
1086 .landing-section { margin: var(--s-20) auto; }
2b821b7Claude1087
4c47454Claude1088 /* ---------- Feature grid ---------- */
1089 .landing-features {
2b821b7Claude1090 display: grid;
1091 grid-template-columns: repeat(3, 1fr);
958d26aClaude1092 gap: 16px;
2b821b7Claude1093 }
1094 .landing-feature {
958d26aClaude1095 background: var(--bg-elevated);
2b821b7Claude1096 border: 1px solid var(--border);
958d26aClaude1097 border-radius: var(--r-lg);
1098 padding: var(--s-7);
1099 position: relative;
1100 overflow: hidden;
1101 isolation: isolate;
1102 transition:
1103 transform var(--t-base) var(--ease-out-quart),
1104 border-color var(--t-base) var(--ease),
1105 box-shadow var(--t-base) var(--ease);
1106 }
1107 .landing-feature::before {
1108 content: '';
1109 position: absolute;
1110 inset: 0;
1111 background: radial-gradient(120% 100% at 0% 0%, rgba(140,109,255,0.08), transparent 55%);
1112 opacity: 0;
1113 transition: opacity var(--t-base) var(--ease);
1114 z-index: -1;
4c47454Claude1115 }
1116 .landing-feature:hover {
958d26aClaude1117 transform: translateY(-3px);
1118 border-color: var(--border-strong);
1119 box-shadow: var(--elev-2);
2b821b7Claude1120 }
958d26aClaude1121 .landing-feature:hover::before { opacity: 1; }
2b821b7Claude1122 .landing-feature-icon {
4c47454Claude1123 display: inline-flex;
1124 align-items: center;
1125 justify-content: center;
958d26aClaude1126 width: 40px;
1127 height: 40px;
1128 border-radius: var(--r);
1129 background: var(--accent-gradient-soft);
1130 color: var(--accent);
1131 margin-bottom: var(--s-4);
1132 border: 1px solid rgba(140,109,255,0.20);
2b821b7Claude1133 }
1134 .landing-feature-title {
958d26aClaude1135 font-family: var(--font-display);
1136 font-size: 19px;
1137 font-weight: 600;
1138 letter-spacing: -0.018em;
1139 margin: 0 0 var(--s-2);
1140 color: var(--text-strong);
2b821b7Claude1141 }
1142 .landing-feature-desc {
958d26aClaude1143 font-size: var(--t-sm);
1144 color: var(--text-muted);
1145 line-height: 1.6;
1146 margin: 0;
1147 }
1148
1149 /* ---------- Walkthrough ---------- */
1150 .landing-walk-grid {
1151 display: grid;
1152 grid-template-columns: repeat(4, 1fr);
1153 gap: 16px;
1154 counter-reset: walk;
1155 }
1156 .landing-walk-step {
1157 position: relative;
1158 padding: var(--s-7) var(--s-6) var(--s-6);
1159 background: var(--bg-elevated);
1160 border: 1px solid var(--border);
1161 border-radius: var(--r-lg);
1162 }
1163 .landing-walk-step::after {
1164 content: '';
1165 position: absolute;
1166 top: 50%;
1167 right: -12px;
1168 width: 12px;
1169 height: 1px;
1170 background: var(--border-strong);
1171 }
1172 .landing-walk-step:last-child::after { display: none; }
1173 .landing-walk-num {
1174 display: inline-block;
1175 font-family: var(--font-mono);
1176 font-size: 11px;
1177 color: var(--accent);
1178 background: var(--accent-gradient-faint);
1179 border: 1px solid rgba(140,109,255,0.30);
1180 padding: 3px 8px;
1181 border-radius: var(--r-full);
1182 letter-spacing: 0.06em;
1183 margin-bottom: var(--s-3);
1184 }
1185 .landing-walk-title {
1186 font-family: var(--font-display);
1187 font-size: 22px;
1188 font-weight: 600;
1189 letter-spacing: -0.022em;
1190 margin: 0 0 var(--s-2);
1191 color: var(--text-strong);
1192 }
1193 .landing-walk-desc {
1194 font-size: var(--t-sm);
2b821b7Claude1195 color: var(--text-muted);
1196 line-height: 1.55;
1197 margin: 0;
1198 }
1199
958d26aClaude1200 /* ---------- Terminal ---------- */
1201 .landing-terminal-section { margin-top: var(--s-16); }
4c47454Claude1202 .landing-terminal-wrap {
1203 display: flex;
2b821b7Claude1204 justify-content: center;
1205 }
4c47454Claude1206 .landing-terminal {
1207 width: 100%;
958d26aClaude1208 max-width: 820px;
1209 background: linear-gradient(180deg, #0a0b12 0%, #06070c 100%);
1210 border: 1px solid var(--border-strong);
1211 border-radius: var(--r-lg);
1212 overflow: hidden;
1213 box-shadow: var(--elev-3), 0 0 60px -10px rgba(140,109,255,0.18);
4c47454Claude1214 text-align: left;
2b821b7Claude1215 }
958d26aClaude1216 :root[data-theme='light'] .landing-terminal {
1217 background: linear-gradient(180deg, #0f111a 0%, #06070c 100%);
1218 }
1219 .landing-terminal-chrome {
1220 display: flex;
1221 align-items: center;
1222 gap: 7px;
1223 padding: 11px 14px;
1224 background: rgba(255,255,255,0.025);
1225 border-bottom: 1px solid rgba(255,255,255,0.06);
1226 position: relative;
1227 }
1228 .landing-terminal-dot {
1229 width: 11px;
1230 height: 11px;
1231 border-radius: 50%;
1232 flex-shrink: 0;
1233 }
1234 .landing-terminal-dot-r { background: #ff5f57; }
1235 .landing-terminal-dot-y { background: #febc2e; }
1236 .landing-terminal-dot-g { background: #28c840; }
1237 .landing-terminal-title {
1238 position: absolute;
1239 left: 50%;
1240 transform: translateX(-50%);
1241 font-family: var(--font-mono);
1242 font-size: 11px;
1243 color: rgba(237,237,242,0.55);
1244 letter-spacing: 0.01em;
1245 }
1246 .landing-terminal-body {
1247 padding: var(--s-6) var(--s-7);
1248 font-family: var(--font-mono);
1249 font-feature-settings: var(--mono-feat);
1250 font-size: 13.5px;
1251 line-height: 1.85;
1252 color: rgba(237,237,242,0.92);
1253 }
4c47454Claude1254 .landing-term-line {
1255 display: flex;
1256 gap: 10px;
1257 white-space: pre-wrap;
1258 word-break: break-all;
2b821b7Claude1259 }
958d26aClaude1260 .landing-term-out { color: rgba(237,237,242,0.7); }
1261 .landing-term-prompt { color: rgba(140,109,255,0.85); user-select: none; flex-shrink: 0; }
1262 .landing-term-meta { color: rgba(237,237,242,0.45); }
1263 .landing-term-ok { color: var(--green); user-select: none; flex-shrink: 0; }
1264 .landing-term-ok-line { color: rgba(237,237,242,0.92); }
1265 .landing-term-cursor { margin-top: 4px; }
1266 .landing-term-blink {
1267 animation: blink 1.05s steps(2) infinite;
1268 color: var(--accent);
1269 }
1270 @keyframes blink { 50% { opacity: 0; } }
1271
1272 /* ---------- Comparison ---------- */
1273 .landing-compare {
1274 max-width: 920px;
1275 margin: 0 auto;
1276 border: 1px solid var(--border);
1277 border-radius: var(--r-lg);
1278 overflow: hidden;
1279 background: var(--bg-elevated);
1280 }
1281 .landing-compare-row {
1282 display: grid;
1283 grid-template-columns: 1fr 180px 180px;
1284 align-items: center;
1285 padding: 14px 20px;
1286 border-bottom: 1px solid var(--border-subtle);
1287 font-size: var(--t-sm);
1288 transition: background var(--t-fast) var(--ease);
1289 }
1290 .landing-compare-row:last-child { border-bottom: none; }
1291 .landing-compare-row:hover { background: var(--bg-hover); }
1292 .landing-compare-feature {
1293 color: var(--text-strong);
1294 font-weight: 500;
1295 }
1296 .landing-compare-them, .landing-compare-us {
1297 text-align: center;
1298 font-family: var(--font-mono);
1299 font-size: 12px;
1300 color: var(--text-muted);
1301 }
1302 .landing-compare-us { color: var(--green); font-weight: 500; }
1303 .landing-compare-hl .landing-compare-us {
1304 color: var(--accent);
1305 font-weight: 600;
2b821b7Claude1306 }
958d26aClaude1307 .landing-compare-hl .landing-compare-feature::after {
1308 content: 'NEW';
1309 margin-left: 8px;
1310 padding: 1px 6px;
1311 border-radius: 4px;
1312 background: var(--accent-gradient-faint);
1313 color: var(--accent);
1314 font-family: var(--font-mono);
1315 font-size: 9px;
1316 letter-spacing: 0.1em;
1317 font-weight: 600;
1318 vertical-align: 1px;
1319 }
1320 @media (max-width: 720px) {
1321 .landing-compare-row { grid-template-columns: 1fr 80px 80px; padding: 12px 14px; }
1322 .landing-compare-hl .landing-compare-feature::after { display: none; }
1323 }
1324
1325 /* ---------- Pricing ---------- */
1326 .landing-pricing {
1327 display: grid;
1328 grid-template-columns: repeat(3, 1fr);
1329 gap: 16px;
1330 max-width: 1080px;
1331 margin: 0 auto;
1332 align-items: stretch;
1333 }
1334 .landing-price-card {
1335 position: relative;
1336 background: var(--bg-elevated);
1337 border: 1px solid var(--border);
1338 border-radius: var(--r-lg);
1339 padding: var(--s-7);
1340 display: flex;
1341 flex-direction: column;
1342 gap: var(--s-4);
1343 transition: border-color var(--t-base) var(--ease), transform var(--t-base) var(--ease);
1344 }
1345 .landing-price-card:hover {
1346 border-color: var(--border-strong);
1347 transform: translateY(-2px);
1348 }
1349 .landing-price-hl {
1350 border-color: rgba(140,109,255,0.35);
1351 box-shadow: var(--elev-2), 0 0 0 1px rgba(140,109,255,0.25);
1352 background:
1353 linear-gradient(180deg, rgba(140,109,255,0.05), transparent 50%),
1354 var(--bg-elevated);
1355 }
1356 .landing-price-hl:hover { border-color: rgba(140,109,255,0.55); }
1357 .landing-price-badge {
1358 position: absolute;
1359 top: -10px;
1360 left: 50%;
1361 transform: translateX(-50%);
1362 padding: 3px 12px;
1363 background: var(--accent-gradient);
1364 color: #fff;
1365 font-family: var(--font-mono);
1366 font-size: 10px;
1367 letter-spacing: 0.1em;
1368 text-transform: uppercase;
1369 font-weight: 600;
1370 border-radius: var(--r-full);
1371 box-shadow: 0 4px 12px -2px rgba(140,109,255,0.4);
1372 }
1373 .landing-price-tier {
1374 font-family: var(--font-mono);
1375 font-size: 11px;
1376 text-transform: uppercase;
1377 letter-spacing: 0.16em;
1378 color: var(--text-muted);
1379 }
1380 .landing-price-amount {
1381 display: flex;
1382 align-items: baseline;
1383 gap: 8px;
1384 }
1385 .landing-price-num {
1386 font-family: var(--font-display);
1387 font-size: 40px;
1388 font-weight: 600;
1389 letter-spacing: -0.03em;
1390 color: var(--text-strong);
1391 }
1392 .landing-price-cad {
1393 font-size: var(--t-sm);
1394 color: var(--text-faint);
1395 }
1396 .landing-price-desc {
1397 font-size: var(--t-sm);
1398 color: var(--text-muted);
1399 line-height: 1.55;
1400 margin: 0;
1401 }
1402 .landing-price-features {
1403 list-style: none;
1404 padding: 0;
1405 margin: 0;
1406 display: flex;
1407 flex-direction: column;
1408 gap: 8px;
1409 font-size: var(--t-sm);
1410 color: var(--text);
1411 }
1412 .landing-price-features li {
1413 display: flex;
1414 align-items: center;
1415 gap: 9px;
1416 }
1417 .landing-price-check {
1418 color: var(--accent);
1419 font-weight: 600;
4c47454Claude1420 flex-shrink: 0;
2b821b7Claude1421 }
958d26aClaude1422 .landing-price-cta { margin-top: auto; }
1423
1424 /* ---------- Closing CTA ---------- */
1425 .landing-cta-section { margin: var(--s-20) auto var(--s-16); }
1426 .landing-cta-card {
1427 position: relative;
1428 text-align: center;
1429 padding: var(--s-16) var(--s-7);
1430 border: 1px solid var(--border-strong);
1431 border-radius: var(--r-2xl);
1432 background: var(--bg-elevated);
1433 overflow: hidden;
1434 isolation: isolate;
1435 }
1436 .landing-cta-bg {
1437 position: absolute;
1438 inset: 0;
1439 z-index: -1;
1440 background:
1441 radial-gradient(60% 100% at 50% 0%, rgba(140,109,255,0.16), transparent 65%),
1442 radial-gradient(40% 80% at 80% 100%, rgba(54,197,214,0.10), transparent 65%);
1443 }
1444 .landing-cta-card::after {
1445 content: '';
1446 position: absolute;
1447 inset: 0;
1448 z-index: -1;
1449 background-image: radial-gradient(rgba(255,255,255,0.04) 1px, transparent 1px);
1450 background-size: 24px 24px;
1451 mask-image: radial-gradient(ellipse at center, #000 0%, transparent 65%);
1452 -webkit-mask-image: radial-gradient(ellipse at center, #000 0%, transparent 65%);
1453 opacity: 0.6;
1454 }
1455 :root[data-theme='light'] .landing-cta-card::after {
1456 background-image: radial-gradient(rgba(15,16,28,0.07) 1px, transparent 1px);
1457 }
1458 .landing-cta-card .eyebrow { justify-content: center; }
1459 .landing-cta-title {
1460 font-family: var(--font-display);
1461 font-size: clamp(28px, 4.4vw, 56px);
1462 line-height: 1.05;
1463 letter-spacing: -0.03em;
1464 font-weight: 600;
1465 margin: var(--s-3) 0 var(--s-4);
1466 color: var(--text-strong);
1467 }
1468 .landing-cta-sub {
1469 font-size: var(--t-md);
1470 color: var(--text-muted);
1471 max-width: 560px;
1472 margin: 0 auto var(--s-8);
1473 line-height: 1.55;
1474 }
1475 .landing-cta-buttons {
1476 display: flex;
1477 gap: 12px;
1478 justify-content: center;
1479 flex-wrap: wrap;
1480 }
2b821b7Claude1481
1482 /* ---------- Responsive ---------- */
958d26aClaude1483 @media (max-width: 960px) {
1484 .landing-features { grid-template-columns: repeat(2, 1fr); }
1485 .landing-walk-grid { grid-template-columns: repeat(2, 1fr); }
1486 .landing-walk-step::after { display: none; }
1487 .landing-pricing { grid-template-columns: 1fr; max-width: 480px; }
1488 }
1489 @media (max-width: 640px) {
1490 .landing-hero { padding: var(--s-14) 0 var(--s-10); }
1491 .landing-hero-cmd { flex-wrap: wrap; justify-content: center; }
1492 .landing-hero-ctas { flex-direction: column; align-items: stretch; }
1493 .landing-hero-ctas .btn { width: 100%; justify-content: center; }
1494 .landing-features { grid-template-columns: 1fr; }
1495 .landing-walk-grid { grid-template-columns: 1fr; }
1496 .landing-section { margin: var(--s-12) auto; }
1497 .landing-cta-card { padding: var(--s-10) var(--s-5); }
1498 .landing-cta-buttons .btn { width: 100%; justify-content: center; }
2b821b7Claude1499 }
52ad8b1Claude1500
1501 /* ---------- L4 social-proof counters ---------- */
1502 .landing-counters {
1503 margin: var(--s-10) auto var(--s-12);
1504 max-width: 1180px;
1505 padding: 0 var(--s-4);
1506 }
1507 .landing-counters-grid {
1508 display: grid;
1509 grid-template-columns: repeat(6, 1fr);
1510 gap: 20px;
1511 text-align: left;
1512 }
1513 .landing-counter {
1514 padding: var(--s-3) 0;
1515 border-top: 1px solid var(--border-subtle);
1516 }
1517 .landing-counter-num {
1518 font-family: var(--font-display);
1519 font-size: clamp(24px, 3vw, 38px);
1520 line-height: 1.05;
1521 letter-spacing: -0.03em;
1522 font-weight: 700;
1523 margin-bottom: 6px;
1524 font-feature-settings: 'tnum';
1525 background-image: var(--accent-gradient);
1526 -webkit-background-clip: text;
1527 background-clip: text;
1528 -webkit-text-fill-color: transparent;
1529 color: transparent;
1530 }
1531 .landing-counter-label {
1532 font-family: var(--font-mono);
1533 font-size: 10.5px;
1534 font-weight: 500;
1535 text-transform: uppercase;
1536 letter-spacing: 0.12em;
1537 color: var(--text-faint);
1538 line-height: 1.4;
1539 }
1540 @media (max-width: 960px) {
1541 .landing-counters-grid { grid-template-columns: repeat(3, 1fr); gap: 20px 16px; }
1542 }
1543 @media (max-width: 540px) {
1544 .landing-counters-grid { grid-template-columns: repeat(2, 1fr); gap: 18px 12px; }
1545 }
1546`;
1547
1548/**
1549 * Block L4 — count-up animation.
1550 *
1551 * Reads each `[data-counter-target]` and animates the in-DOM text from
1552 * 0 → target over ~1.2s when the element first scrolls into view.
1553 *
1554 * Render-once semantics: each tile already contains the final value as
1555 * HTML, so visitors with JS disabled — or anyone before the script
1556 * loads — sees the correct number. The script just animates the text.
1557 *
1558 * Falls back to the static value (no animation) when IntersectionObserver
1559 * isn't available, or when the user prefers reduced motion.
1560 */
1561const landingCountersJs = `
1562(function(){
1563 try {
1564 var els = document.querySelectorAll('[data-counter-target]');
1565 if (!els.length) return;
1566 var reduced = window.matchMedia && window.matchMedia('(prefers-reduced-motion: reduce)').matches;
1567 if (reduced || typeof IntersectionObserver !== 'function') return;
1568
1569 function animate(el) {
1570 var target = parseInt(el.getAttribute('data-counter-target') || '0', 10);
1571 if (!isFinite(target) || target <= 0) return;
1572 var prefix = el.getAttribute('data-counter-prefix') || '';
1573 var suffix = el.getAttribute('data-counter-suffix') || '';
1574 var duration = 1200;
1575 var start = performance.now();
1576 function frame(now) {
1577 var t = Math.min(1, (now - start) / duration);
1578 // ease-out cubic
1579 var eased = 1 - Math.pow(1 - t, 3);
1580 var v = Math.floor(eased * target);
1581 el.textContent = prefix + v.toLocaleString() + suffix;
1582 if (t < 1) requestAnimationFrame(frame);
1583 else el.textContent = prefix + target.toLocaleString() + suffix;
1584 }
1585 // Reset to zero before animating in.
1586 el.textContent = prefix + '0' + suffix;
1587 requestAnimationFrame(frame);
1588 }
1589
1590 var io = new IntersectionObserver(function(entries) {
1591 entries.forEach(function(entry){
1592 if (entry.isIntersecting) {
1593 animate(entry.target);
1594 io.unobserve(entry.target);
1595 }
1596 });
1597 }, { threshold: 0.4 });
1598 els.forEach(function(el){ io.observe(el); });
1599 } catch (_) { /* swallow — static numbers remain */ }
1600})();
2b821b7Claude1601`;