CodeIssuesDiscussionsWikiPull RequestsProjectsCommitsActionsReleasesContributorsPulse● GatesSecuritySettingsDeploymentsPipelineInsightsAgents✨ Explain✨ Ask AI✨ Workspace✨ Spec✨ Tests▓ Debt Map✨ NL Search🏛 Archaeology
Blame · Line-by-line history
landing-pro.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.
| f8e5fea | 1 | import type { FC } from "hono/jsx"; |
| 2 | import type { PublicStats } from "../lib/public-stats"; | |
| 3 | import type { | |
| 4 | QueuedAiBuildIssue, | |
| 5 | RecentAutoMerge, | |
| 6 | RecentAiReview, | |
| 7 | DemoActivityEntry, | |
| 8 | } from "../lib/demo-activity"; | |
| 9 | import { DEMO_USERNAME } from "../lib/demo-seed"; | |
| 10 | ||
| 11 | export interface LandingProLiveFeed { | |
| 12 | queued: QueuedAiBuildIssue[]; | |
| 13 | merges: RecentAutoMerge[]; | |
| 14 | reviews: RecentAiReview[]; | |
| 15 | reviewCount: number; | |
| 16 | feed: DemoActivityEntry[]; | |
| 17 | } | |
| 18 | ||
| 19 | export interface LandingProProps { | |
| 20 | stats?: { publicRepos?: number; users?: number }; | |
| 21 | publicStats?: PublicStats | null; | |
| 22 | liveFeed?: LandingProLiveFeed | null; | |
| 23 | } | |
| 24 | ||
| 25 | export function relTime( | |
| 26 | value: string | Date | null | undefined, | |
| 27 | now: number = Date.now() | |
| 28 | ): string { | |
| 29 | if (value === null || value === undefined) return "just now"; | |
| 30 | const t = value instanceof Date ? value.getTime() : new Date(value).getTime(); | |
| 31 | if (!Number.isFinite(t)) return "just now"; | |
| 32 | const d = now - t; | |
| 33 | if (d < 0) return "just now"; | |
| 34 | const s = Math.floor(d / 1000); | |
| 35 | if (s < 60) return "just now"; | |
| 36 | const m = Math.floor(s / 60); | |
| 37 | if (m < 60) return `${m}m ago`; | |
| 38 | const h = Math.floor(m / 60); | |
| 39 | if (h < 24) return `${h}h ago`; | |
| 40 | return `${Math.floor(h / 24)}d ago`; | |
| 41 | } | |
| 42 | ||
| 54f2e2a | 43 | // ─── inline SVG icon helper ──────────────────────────────────────── |
| 44 | ||
| 45 | function icon(paths: string, color = "#4353c9", size = 20) { | |
| 46 | return `<svg width="${size}" height="${size}" viewBox="0 0 24 24" fill="none" stroke="${color}" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">${paths}</svg>`; | |
| 47 | } | |
| 48 | ||
| 49 | const ICONS: Record<string, string> = { | |
| 50 | code: icon('<polyline points="16 18 22 12 16 6"/><polyline points="8 6 2 12 8 18"/>'), | |
| 51 | fork: icon('<circle cx="18" cy="18" r="3"/><circle cx="6" cy="6" r="3"/><circle cx="6" cy="18" r="3"/><line x1="6" y1="9" x2="6" y2="15"/><path d="M18 9V7a2 2 0 0 0-2-2H8a2 2 0 0 0-2 2"/><path d="M18 15a3 3 0 0 0 0-6"/>'), | |
| 52 | ai: icon('<polygon points="12 2 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26 12 2"/>', "#7c3aed"), | |
| 53 | shield: icon('<path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"/>', "#059669"), | |
| 54 | workflow: icon('<circle cx="12" cy="12" r="3"/><path d="M12 3v3"/><path d="M12 18v3"/><path d="M4.22 4.22l2.12 2.12"/><path d="M17.66 17.66l2.12 2.12"/><path d="M3 12h3"/><path d="M18 12h3"/><path d="M4.22 19.78l2.12-2.12"/><path d="M17.66 6.34l2.12-2.12"/>', "#b45309"), | |
| 55 | grid: icon('<rect x="3" y="3" width="7" height="7"/><rect x="14" y="3" width="7" height="7"/><rect x="14" y="14" width="7" height="7"/><rect x="3" y="14" width="7" height="7"/>', "#475569"), | |
| 56 | chart: icon('<line x1="18" y1="20" x2="18" y2="10"/><line x1="12" y1="20" x2="12" y2="4"/><line x1="6" y1="20" x2="6" y2="14"/>', "#0891b2"), | |
| 57 | link: icon('<path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"/><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"/>', "#dc2626"), | |
| 58 | team: icon('<path d="M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2"/><circle cx="9" cy="7" r="4"/><path d="M23 21v-2a4 4 0 0 0-3-3.87"/><path d="M16 3.13a4 4 0 0 1 0 7.75"/>', "#0d9488"), | |
| 59 | }; | |
| 60 | ||
| f8e5fea | 61 | // ─── page ────────────────────────────────────────────────────────── |
| 62 | ||
| 63 | export const LandingProPage: FC<LandingProProps> = ({ | |
| 64 | publicStats, | |
| 65 | liveFeed, | |
| 66 | } = {}) => { | |
| 67 | const title = "Gluecron — The AI-native git platform"; | |
| 68 | const desc = | |
| 69 | "The git platform that does the work. AI code review on every PR, secrets scanned at push, features built from plain-English issues. Self-hosted, git-native, Claude-first."; | |
| 70 | ||
| 71 | const liveQueued = liveFeed?.queued ?? []; | |
| 72 | const liveMerges = liveFeed?.merges ?? []; | |
| 73 | const liveReviews = liveFeed?.reviews ?? []; | |
| 74 | const liveReviewCount = liveFeed?.reviewCount ?? 0; | |
| 75 | const liveEntries = liveFeed?.feed ?? []; | |
| 76 | ||
| 77 | return ( | |
| 78 | <html lang="en"> | |
| 79 | <head> | |
| 80 | <meta charset="UTF-8" /> | |
| 81 | <meta name="viewport" content="width=device-width, initial-scale=1.0" /> | |
| 82 | <meta name="theme-color" content="#ffffff" /> | |
| 83 | <title>{title}</title> | |
| 84 | <meta name="description" content={desc} /> | |
| 85 | <meta property="og:title" content={title} /> | |
| 86 | <meta property="og:description" content={desc} /> | |
| 87 | <meta property="og:type" content="website" /> | |
| 88 | <meta name="twitter:card" content="summary_large_image" /> | |
| 89 | <link rel="icon" type="image/svg+xml" href="/icon.svg" /> | |
| 90 | <link rel="preconnect" href="https://fonts.googleapis.com" /> | |
| 91 | <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin="" /> | |
| 54f2e2a | 92 | <link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Inter:wght@400;450;500;600&family=Inter+Tight:wght@600;700;800&family=JetBrains+Mono:wght@400;500&display=swap" /> |
| f8e5fea | 93 | <style dangerouslySetInnerHTML={{ __html: css }} /> |
| 94 | </head> | |
| 95 | <body> | |
| 96 | ||
| 54f2e2a | 97 | {/* ── Nav ── */} |
| f8e5fea | 98 | <header class="lp-nav" id="lp-nav"> |
| 99 | <div class="lp-nav-in"> | |
| 100 | <a href="/" class="lp-logo" aria-label="Gluecron home"> | |
| 101 | <span class="lp-logo-mark" aria-hidden="true" /> | |
| 102 | gluecron | |
| 103 | </a> | |
| 104 | <nav class="lp-nav-links" aria-label="Primary"> | |
| 105 | <a href="#platform">Platform</a> | |
| 106 | <a href="#ai">AI suite</a> | |
| 107 | <a href="#security">Security</a> | |
| 108 | <a href="/pricing">Pricing</a> | |
| 109 | <a href="/explore">Explore</a> | |
| 110 | </nav> | |
| 111 | <div class="lp-nav-ctas"> | |
| 112 | <a href="/login" class="lp-btn lp-btn-ghost">Sign in</a> | |
| 113 | <a href="/register" class="lp-btn lp-btn-solid">Start building</a> | |
| 114 | </div> | |
| 115 | </div> | |
| 116 | </header> | |
| 117 | ||
| 54f2e2a | 118 | {/* ── Hero ── */} |
| f8e5fea | 119 | <section class="lp-hero"> |
| 120 | <div class="lp-wrap lp-hero-in"> | |
| 121 | <div class="lp-hero-text"> | |
| 122 | <div class="lp-kicker">The AI-native git platform</div> | |
| 54f2e2a | 123 | <h1 class="lp-h1">The platform<br />that does the work.</h1> |
| f8e5fea | 124 | <p class="lp-hero-sub"> |
| 125 | AI code review on every PR. Secrets scanned at push. | |
| 54f2e2a | 126 | Features built from plain-English issues. Gluecron closes |
| 127 | the loop from spec to deployed code — while you focus on | |
| 128 | what matters. | |
| f8e5fea | 129 | </p> |
| 130 | <div class="lp-hero-ctas"> | |
| 54f2e2a | 131 | <a href="/register" class="lp-btn lp-btn-solid lp-btn-lg">Sign up free</a> |
| 132 | <a href="/import" class="lp-btn lp-btn-outline lp-btn-lg">Migrate from GitHub</a> | |
| f8e5fea | 133 | </div> |
| 134 | <div class="lp-hero-links"> | |
| 135 | <a href="/demo">Try the live demo</a> | |
| 136 | <span aria-hidden="true">·</span> | |
| 137 | <a href="/vs-github">Compare to GitHub</a> | |
| 138 | <span aria-hidden="true">·</span> | |
| 139 | <a href="/pricing">See pricing</a> | |
| 140 | </div> | |
| 141 | ||
| 142 | {publicStats && ( | |
| 143 | <dl class="lp-hero-stats"> | |
| 144 | <div class="lp-hero-stat"> | |
| 54f2e2a | 145 | <dt |
| 146 | class="lp-counter" | |
| 147 | data-counter-target={String(publicStats.weeklyPrsAutoMerged)} | |
| 148 | > | |
| 149 | {publicStats.weeklyPrsAutoMerged.toLocaleString()} | |
| 150 | </dt> | |
| f8e5fea | 151 | <dd>PRs auto-merged this week</dd> |
| 152 | </div> | |
| 153 | <div class="lp-hero-stat"> | |
| 54f2e2a | 154 | <dt |
| 155 | class="lp-counter" | |
| 156 | data-counter-target={String(publicStats.weeklyIssuesBuiltByAi)} | |
| 157 | > | |
| 158 | {publicStats.weeklyIssuesBuiltByAi.toLocaleString()} | |
| 159 | </dt> | |
| f8e5fea | 160 | <dd>issues built by AI</dd> |
| 161 | </div> | |
| 162 | <div class="lp-hero-stat"> | |
| 54f2e2a | 163 | <dt |
| 164 | class="lp-counter" | |
| 165 | data-counter-target={String(Math.round(publicStats.weeklyHoursSaved))} | |
| 166 | data-counter-suffix="h" | |
| 167 | > | |
| 168 | {`~${Math.round(publicStats.weeklyHoursSaved)}h`} | |
| 169 | </dt> | |
| f8e5fea | 170 | <dd>saved by AI this week</dd> |
| 171 | </div> | |
| 172 | <div class="lp-hero-stat"> | |
| 54f2e2a | 173 | <dt |
| 174 | class="lp-counter" | |
| 175 | data-counter-target={String(publicStats.weeklyDeploysShipped)} | |
| 176 | > | |
| 177 | {publicStats.weeklyDeploysShipped.toLocaleString()} | |
| 178 | </dt> | |
| f8e5fea | 179 | <dd>deploys shipped</dd> |
| 180 | </div> | |
| 181 | </dl> | |
| 182 | )} | |
| 183 | </div> | |
| 184 | ||
| 54f2e2a | 185 | {/* Rich PR review mock card */} |
| f8e5fea | 186 | <div class="lp-hero-card" aria-hidden="true"> |
| 54f2e2a | 187 | <div class="lp-hc-chrome"> |
| 188 | <span class="lp-hc-dot lp-hc-dot-r" /> | |
| 189 | <span class="lp-hc-dot lp-hc-dot-y" /> | |
| 190 | <span class="lp-hc-dot lp-hc-dot-g" /> | |
| 191 | <span class="lp-hc-path">auth/middleware.ts · PR #128 · main</span> | |
| 192 | </div> | |
| 193 | <div class="lp-hc-pr-row"> | |
| 194 | <span class="lp-badge lp-badge-merged">Merged</span> | |
| 195 | <span class="lp-hc-pr-title">Harden token validation</span> | |
| f8e5fea | 196 | </div> |
| 54f2e2a | 197 | <div class="lp-diff"> |
| 198 | <div class="lp-diff-line lp-diff-add"> | |
| 199 | <span class="lp-ln">80</span> | |
| 200 | <span class="lp-op">+</span> | |
| 201 | <code><span class="lp-kw">const</span> validateToken = <span class="lp-kw">async</span> (token: <span class="lp-ty">string</span>) => {"{"}</code> | |
| f8e5fea | 202 | </div> |
| 54f2e2a | 203 | <div class="lp-diff-line lp-diff-add lp-diff-focus"> |
| 204 | <span class="lp-ln">81</span> | |
| 205 | <span class="lp-op">+</span> | |
| 206 | <code>{" "}<span class="lp-kw">const</span> hash = <span class="lp-kw">await</span> <span class="lp-fn">sha256</span>(token);</code> | |
| f8e5fea | 207 | </div> |
| 54f2e2a | 208 | <div class="lp-diff-line lp-diff-add"> |
| 209 | <span class="lp-ln">82</span> | |
| 210 | <span class="lp-op">+</span> | |
| 211 | <code>{" "}<span class="lp-kw">const</span> user = <span class="lp-kw">await</span> db.<span class="lp-fn">findByHash</span>(hash);</code> | |
| f8e5fea | 212 | </div> |
| 54f2e2a | 213 | <div class="lp-diff-line lp-diff-add"> |
| 214 | <span class="lp-ln">83</span> | |
| 215 | <span class="lp-op">+</span> | |
| 216 | <code>{" "}<span class="lp-kw">if</span> (!user) <span class="lp-kw">throw new</span> <span class="lp-ty">AuthError</span>(<span class="lp-str">"invalid"</span>);</code> | |
| f8e5fea | 217 | </div> |
| 218 | </div> | |
| 54f2e2a | 219 | <div class="lp-inline-comment"> |
| 220 | <div class="lp-ic-ava">C</div> | |
| 221 | <div class="lp-ic-body"> | |
| 222 | <div class="lp-ic-head">Claude <span class="lp-ic-loc">middleware.ts:81</span></div> | |
| 223 | <div class="lp-ic-text">SHA-256 via Web Crypto — constant-time, no timing-attack surface. Correct approach. ✓</div> | |
| 224 | </div> | |
| 225 | </div> | |
| 226 | <div class="lp-gates"> | |
| 227 | <span class="lp-gate lp-gate-ok">✓ Security</span> | |
| 228 | <span class="lp-gate lp-gate-ok">✓ Tests (412)</span> | |
| 229 | <span class="lp-gate lp-gate-ok">✓ Claude review</span> | |
| 230 | <span class="lp-gate lp-gate-spin">⊙ Deploying</span> | |
| 231 | </div> | |
| 232 | <div class="lp-automerge-bar">Auto-merging to main · deploy in ~4s</div> | |
| f8e5fea | 233 | </div> |
| 234 | </div> | |
| 235 | </section> | |
| 236 | ||
| 54f2e2a | 237 | {/* ── Trust strip ── */} |
| f8e5fea | 238 | <div class="lp-trust-strip"> |
| 239 | <div class="lp-wrap lp-trust-in"> | |
| 240 | <span>Self-hosted on your hardware</span> | |
| 241 | <span class="lp-sep" aria-hidden="true" /> | |
| 242 | <span>Git-native · Smart HTTP + SSH</span> | |
| 243 | <span class="lp-sep" aria-hidden="true" /> | |
| 244 | <span>Claude-powered AI review</span> | |
| 245 | <span class="lp-sep" aria-hidden="true" /> | |
| 54f2e2a | 246 | <span>MCP-native for Claude Code</span> |
| f8e5fea | 247 | <span class="lp-sep" aria-hidden="true" /> |
| 248 | <span>Free to start</span> | |
| 249 | </div> | |
| 250 | </div> | |
| 251 | ||
| 54f2e2a | 252 | {/* ── Impact stat band ── */} |
| 253 | <div class="lp-impact-band"> | |
| 254 | <div class="lp-wrap lp-impact-in"> | |
| 255 | <div class="lp-impact-stat lp-reveal"> | |
| 256 | <span class="lp-impact-n">2<span class="lp-impact-x">×</span></span> | |
| 257 | <span class="lp-impact-label">faster dev velocity</span> | |
| 258 | </div> | |
| 259 | <div class="lp-impact-sep" aria-hidden="true" /> | |
| 260 | <div class="lp-impact-stat lp-reveal"> | |
| 261 | <span class="lp-impact-n">100<span class="lp-impact-x">%</span></span> | |
| 262 | <span class="lp-impact-label">audit log coverage</span> | |
| 263 | </div> | |
| 264 | <div class="lp-impact-sep" aria-hidden="true" /> | |
| 265 | <div class="lp-impact-stat lp-reveal"> | |
| 266 | <span class="lp-impact-n"><30<span class="lp-impact-x">s</span></span> | |
| 267 | <span class="lp-impact-label">AI review latency</span> | |
| 268 | </div> | |
| 269 | </div> | |
| 270 | </div> | |
| 271 | ||
| 272 | {/* ── Live-now ── */} | |
| f8e5fea | 273 | <section class="lp-sec lp-sec-soft" aria-labelledby="lp-live-h"> |
| 274 | <div class="lp-wrap"> | |
| 275 | <div class="lp-sec-head"> | |
| 276 | <div class="lp-kicker"> | |
| 277 | <span class="lp-live-dot" aria-hidden="true">●</span> Live now | |
| 278 | </div> | |
| 54f2e2a | 279 | <h2 id="lp-live-h" class="lp-h2">Claude is working on demo repos as you read this.</h2> |
| 280 | <p class="lp-sub">Real data from the public <code>{DEMO_USERNAME}/*</code> repos. Refreshes every 30 seconds.</p> | |
| f8e5fea | 281 | </div> |
| 282 | ||
| 283 | <div class="lp-live-grid" data-livenow-grid> | |
| 54f2e2a | 284 | <LiveCard title="Issues queued for AI" variant="violet"> |
| f8e5fea | 285 | <ul class="lp-live-list" data-livecard="queued"> |
| 286 | {liveQueued.length === 0 ? ( | |
| 287 | <li class="lp-live-empty">Quiet right now.</li> | |
| 54f2e2a | 288 | ) : liveQueued.slice(0, 3).map((i) => ( |
| 289 | <li class="lp-live-row" data-row-id={`queued|${i.repo}|${i.number}`}> | |
| 290 | <a href={`/${DEMO_USERNAME}/${i.repo}/issues/${i.number}`} class="lp-live-link"> | |
| 291 | <span class="lp-live-num">#{i.number}</span> {i.title} | |
| 292 | </a> | |
| 293 | <span class="lp-live-meta">{i.repo}</span> | |
| 294 | </li> | |
| 295 | ))} | |
| f8e5fea | 296 | </ul> |
| 297 | </LiveCard> | |
| 298 | ||
| 54f2e2a | 299 | <LiveCard title="Recently merged by AI" variant="green"> |
| f8e5fea | 300 | <ul class="lp-live-list" data-livecard="merges"> |
| 301 | {liveMerges.length === 0 ? ( | |
| 302 | <li class="lp-live-empty">No auto-merges in the last 24h.</li> | |
| 54f2e2a | 303 | ) : liveMerges.slice(0, 3).map((m) => ( |
| 304 | <li class="lp-live-row" data-row-id={`merges|${m.repo}|${m.number}`}> | |
| 305 | <a href={`/${DEMO_USERNAME}/${m.repo}/pulls/${m.number}`} class="lp-live-link"> | |
| 306 | <span class="lp-live-num">#{m.number}</span> {m.title} | |
| 307 | </a> | |
| 308 | <span class="lp-live-meta"> | |
| 309 | {m.repo} · <span data-rel={m.mergedAt instanceof Date ? m.mergedAt.toISOString() : String(m.mergedAt)}>{relTime(m.mergedAt)}</span> | |
| 310 | </span> | |
| 311 | </li> | |
| 312 | ))} | |
| f8e5fea | 313 | </ul> |
| 314 | </LiveCard> | |
| 315 | ||
| 54f2e2a | 316 | <LiveCard title="AI reviews posted" variant="brand"> |
| f8e5fea | 317 | <div class="lp-live-bignum"> |
| 54f2e2a | 318 | <span data-livecard-count="reviews">{liveReviewCount}</span> |
| f8e5fea | 319 | <span class="lp-live-bignum-label">today</span> |
| 320 | </div> | |
| 321 | <ul class="lp-live-list" data-livecard="reviews"> | |
| 322 | {liveReviews.length === 0 ? ( | |
| 323 | <li class="lp-live-empty">No reviews in the last 24h.</li> | |
| 54f2e2a | 324 | ) : liveReviews.slice(0, 2).map((r) => ( |
| 325 | <li class="lp-live-row" data-row-id={`reviews|${r.repo}|${r.prNumber}`}> | |
| 326 | <a href={`/${DEMO_USERNAME}/${r.repo}/pulls/${r.prNumber}`} class="lp-live-link"> | |
| 327 | <span class="lp-live-num">#{r.prNumber}</span> {r.commentSnippet} | |
| 328 | </a> | |
| 329 | <span class="lp-live-meta">{r.repo}</span> | |
| 330 | </li> | |
| 331 | ))} | |
| f8e5fea | 332 | </ul> |
| 333 | </LiveCard> | |
| 334 | ||
| 54f2e2a | 335 | <LiveCard title="Activity feed" variant="neutral"> |
| f8e5fea | 336 | <ul class="lp-live-list" data-livecard="feed"> |
| 337 | {liveEntries.length === 0 ? ( | |
| 338 | <li class="lp-live-empty">Quiet right now — check back in a minute.</li> | |
| 54f2e2a | 339 | ) : liveEntries.slice(0, 6).map((e) => { |
| 340 | const path = e.ref.type === "pr" ? "pulls" : "issues"; | |
| 341 | const kindLabel = e.kind === "auto_merge.merged" ? "auto-merged" : e.kind === "ai_build.dispatched" ? "AI-built" : "AI review"; | |
| 342 | const id = `${e.kind}|${e.repo}|${e.ref.type}|${e.ref.number}`; | |
| 343 | return ( | |
| 344 | <li class="lp-live-feedrow" data-row-id={id}> | |
| 345 | <span class={`lp-feed-kind lp-feed-kind-${e.kind.replace(/\./g, "-")}`}>{kindLabel}</span> | |
| 346 | {" "}<a class="lp-live-link" href={`/${DEMO_USERNAME}/${e.repo}/${path}/${e.ref.number}`}>{e.repo} #{e.ref.number}</a> | |
| 347 | {" "}<span class="lp-live-rel" data-rel={e.at instanceof Date ? e.at.toISOString() : String(e.at)}>{relTime(e.at)}</span> | |
| 348 | </li> | |
| 349 | ); | |
| 350 | })} | |
| f8e5fea | 351 | </ul> |
| 352 | </LiveCard> | |
| 353 | </div> | |
| 354 | ||
| 355 | <div class="lp-live-cta"> | |
| 356 | <a href="/register" class="lp-btn lp-btn-solid">Sign up free</a> | |
| 357 | <a href="/demo" class="lp-text-link">Open the live demo →</a> | |
| 358 | </div> | |
| 359 | </div> | |
| 360 | <script dangerouslySetInnerHTML={{ __html: liveNowJs }} /> | |
| 361 | </section> | |
| 362 | ||
| 54f2e2a | 363 | {/* ── Platform breadth ── */} |
| f8e5fea | 364 | <section class="lp-sec" id="platform" aria-labelledby="lp-platform-h"> |
| 365 | <div class="lp-wrap"> | |
| 366 | <div class="lp-sec-head"> | |
| 367 | <div class="lp-kicker">The platform</div> | |
| 54f2e2a | 368 | <h2 id="lp-platform-h" class="lp-h2">Everything your team needs.<br />Nothing you don't.</h2> |
| 369 | <p class="lp-sub">Gluecron ships the surfaces GitHub charges extra for — and the ones it never built. AI is a first-class contributor, not a plugin.</p> | |
| f8e5fea | 370 | </div> |
| 371 | ||
| 372 | <div class="lp-platform-grid"> | |
| 54f2e2a | 373 | <PlatformGroup icon={ICONS.code} iconColor="#4353c9" title="Code hosting"> |
| f8e5fea | 374 | {["Git Smart HTTP · SSH keys", "Forks · Stars · Topics", "Templates · Mirroring", "Releases · Protected tags", "Push policy rulesets", "Commit signature verify"]} |
| 375 | </PlatformGroup> | |
| 54f2e2a | 376 | <PlatformGroup icon={ICONS.team} iconColor="#0d9488" title="Collaboration"> |
| f8e5fea | 377 | {["Issues · PRs · Inline review", "Draft PRs · Merge queues", "Discussions · Wikis", "Projects / kanban", "Gists · Reactions", "Mentions · Notifications"]} |
| 378 | </PlatformGroup> | |
| 54f2e2a | 379 | <PlatformGroup icon={ICONS.ai} iconColor="#7c3aed" title="AI suite"> |
| 380 | {["Spec-to-PR from plain English", "AI code review on every PR", "AI auto-merge when gates pass", "Sleep Mode — ships overnight", "Auto-repair on gate failure", "AI changelog · AI test stubs"]} | |
| f8e5fea | 381 | </PlatformGroup> |
| 54f2e2a | 382 | <PlatformGroup icon={ICONS.shield} iconColor="#059669" title="Security"> |
| f8e5fea | 383 | {["Secret scanner (15 patterns)", "AI semantic security review", "GateTest integration", "Dependency CVE alerts", "2FA · Passkeys · SAML SSO", "Audit log (100% coverage)"]} |
| 384 | </PlatformGroup> | |
| 54f2e2a | 385 | <PlatformGroup icon={ICONS.workflow} iconColor="#b45309" title="CI / CD"> |
| f8e5fea | 386 | {["Workflow runner (YAML-native)", "Cron triggers · Secrets", "Branch protection · Gates", "Required status checks", "Auto-repair on gate failure", "Protected tags enforcement"]} |
| 387 | </PlatformGroup> | |
| 54f2e2a | 388 | <PlatformGroup icon={ICONS.grid} iconColor="#475569" title="Platform"> |
| f8e5fea | 389 | {["Organizations · Teams · Roles", "npm package registry", "Pages / static hosting", "App marketplace", "Protected environments", "Billing · Quotas"]} |
| 390 | </PlatformGroup> | |
| 54f2e2a | 391 | <PlatformGroup icon={ICONS.chart} iconColor="#0891b2" title="Observability"> |
| f8e5fea | 392 | {["DORA metrics dashboard", "Developer velocity", "Repository health score", "Hot files heatmap", "Traffic analytics", "Org insights"]} |
| 393 | </PlatformGroup> | |
| 54f2e2a | 394 | <PlatformGroup icon={ICONS.link} iconColor="#dc2626" title="Integrations"> |
| f8e5fea | 395 | {["REST API v2 · GraphQL", "MCP server (Claude, Cursor)", "Official CLI · VS Code ext", "Webhooks (HMAC-signed)", "OAuth provider · GitHub Apps", "GitHub import (single + bulk)"]} |
| 396 | </PlatformGroup> | |
| 397 | </div> | |
| 398 | </div> | |
| 399 | </section> | |
| 400 | ||
| 54f2e2a | 401 | {/* ── Developer painkiller ── */} |
| f8e5fea | 402 | <section class="lp-sec lp-sec-soft"> |
| 403 | <div class="lp-wrap"> | |
| 404 | <div class="lp-sec-head"> | |
| 405 | <div class="lp-kicker">Why developers switch</div> | |
| 406 | <h2 class="lp-h2">Pain out. Productivity in.</h2> | |
| 54f2e2a | 407 | <p class="lp-sub">Every friction point in modern development has a specific answer in Gluecron.</p> |
| f8e5fea | 408 | </div> |
| 409 | <div class="lp-pain-table"> | |
| 54f2e2a | 410 | <PainRow problem="Waiting on CI to queue before a gate runs" fix="Gates fire at the moment of push — before the branch even moves" /> |
| 411 | <PainRow problem="Manual code review as a bottleneck" fix="AI reviews every PR in under 30 seconds, line-level, with risk flags" /> | |
| 412 | <PainRow problem="A failed gate halts your whole day" fix="Auto-repair tries to fix it, re-runs the gate, continues — you may never see it" /> | |
| 413 | <PainRow problem="AI as a sidebar you talk at" fix="Claude has a bot account, makes commits, appears in your git history like a teammate" /> | |
| 414 | <PainRow problem="Platform lock-in and surprise bills" fix="Self-hosted, single Bun binary, git-native — run it on a $6 VPS" /> | |
| 415 | <PainRow problem="Dependency CVEs discovered after merge" fix="CVE scanner runs on every push touching a manifest; opens issues automatically" /> | |
| f8e5fea | 416 | </div> |
| 417 | </div> | |
| 418 | </section> | |
| 419 | ||
| 54f2e2a | 420 | {/* ── AI showcase ── */} |
| f8e5fea | 421 | <section class="lp-sec" id="ai" aria-labelledby="lp-ai-h"> |
| 422 | <div class="lp-wrap"> | |
| 423 | <div class="lp-sec-head"> | |
| 424 | <div class="lp-kicker">AI-native, not AI-bolted-on</div> | |
| 54f2e2a | 425 | <h2 id="lp-ai-h" class="lp-h2">Claude does the work.<br />You stay in control.</h2> |
| 426 | <p class="lp-sub">Every AI action is attributed, auditable, and reversible. The manual path is always one click away.</p> | |
| f8e5fea | 427 | </div> |
| 428 | ||
| 429 | <div class="lp-ai-features"> | |
| 54f2e2a | 430 | <AiFeatureCard |
| f8e5fea | 431 | n="01" |
| 432 | title="Spec-to-PR" | |
| 433 | body="Describe a feature in plain English — or just label an issue ai:build. Claude opens a branch, writes the change, and submits a draft PR against your gates. Spec to PR in under 90 seconds." | |
| 434 | link={{ href: "/demo", label: "Try on a live repo" }} | |
| 54f2e2a | 435 | mock={ |
| 436 | <div class="lp-review-mock"> | |
| 437 | <div class="lp-rm-header"> | |
| 438 | <span class="lp-rm-label">Issue #42 · labelled <code>ai:build</code></span> | |
| 439 | <span class="lp-badge lp-badge-ai">AI</span> | |
| 440 | </div> | |
| 441 | <div class="lp-rm-steps"> | |
| 442 | <div class="lp-rm-step lp-rm-done"><span class="lp-rm-icon">✓</span> Branch opened: <code>feat/add-auth-provider</code></div> | |
| 443 | <div class="lp-rm-step lp-rm-done"><span class="lp-rm-icon">✓</span> Files written: <code>auth/provider.ts</code>, <code>auth/types.ts</code></div> | |
| 444 | <div class="lp-rm-step lp-rm-active"><span class="lp-rm-icon">⊙</span> PR #43 open — awaiting your review</div> | |
| 445 | </div> | |
| 446 | <div class="lp-rm-time">Spec → draft PR in 74s</div> | |
| 447 | </div> | |
| 448 | } | |
| f8e5fea | 449 | /> |
| 450 | ||
| 54f2e2a | 451 | <AiFeatureCard |
| f8e5fea | 452 | n="02" |
| 453 | title="AI code review on every PR" | |
| 454 | body="The moment a PR opens, Claude reviews the diff: line-level comments, security flags, logic issues, and a verdict. Under 30 seconds. Every PR, every time, before a human has to look." | |
| 455 | link={{ href: "/vs-github", label: "Compare to Copilot" }} | |
| 456 | reverse | |
| 54f2e2a | 457 | mock={ |
| 458 | <div class="lp-review-mock"> | |
| 459 | <div class="lp-rm-header"> | |
| 460 | <span class="lp-rm-label">PR #128 · Claude review</span> | |
| 461 | <span class="lp-badge lp-badge-approved">Approved</span> | |
| 462 | </div> | |
| 463 | <div class="lp-rm-comment"> | |
| 464 | <div class="lp-rm-ava">C</div> | |
| 465 | <div class="lp-rm-bubble"> | |
| 466 | <div class="lp-rm-file">auth/middleware.ts:81</div> | |
| 467 | SHA-256 via Web Crypto — constant-time, no timing-attack surface. Correct approach. | |
| 468 | </div> | |
| 469 | </div> | |
| 470 | <div class="lp-rm-verdict"> | |
| 471 | <span class="lp-rm-v-ok">✓ No security issues · Approved for auto-merge</span> | |
| 472 | </div> | |
| 473 | <div class="lp-rm-time">Review posted in 28s</div> | |
| 474 | </div> | |
| 475 | } | |
| f8e5fea | 476 | /> |
| 477 | ||
| 54f2e2a | 478 | <AiFeatureCard |
| f8e5fea | 479 | n="03" |
| 480 | title="Sleep Mode" | |
| 54f2e2a | 481 | body="Enable Sleep Mode and set your wake-up hour. Gluecron's autopilot works overnight: merging ready PRs, building labelled issues, repairing failed gates, fixing secrets. You wake up to a digest." |
| f8e5fea | 482 | link={{ href: "/sleep-mode", label: "See Sleep Mode" }} |
| 54f2e2a | 483 | mock={ |
| 484 | <div class="lp-review-mock lp-digest-mock"> | |
| 485 | <div class="lp-rm-header"> | |
| 486 | <span class="lp-rm-label">Overnight digest · 9:04 AM</span> | |
| 487 | </div> | |
| 488 | <div class="lp-digest-rows"> | |
| 489 | <div class="lp-digest-row"><span class="lp-digest-ok">✓</span> 3 PRs auto-merged to main</div> | |
| 490 | <div class="lp-digest-row"><span class="lp-digest-ok">✓</span> Issue #37 built and in review</div> | |
| 491 | <div class="lp-digest-row"><span class="lp-digest-ok">✓</span> 2 secrets auto-repaired before push</div> | |
| 492 | <div class="lp-digest-row"><span class="lp-digest-ok">✓</span> 1 failing gate repaired, re-run green</div> | |
| 493 | </div> | |
| 494 | <div class="lp-rm-time">While you slept · 0 pages sent</div> | |
| 495 | </div> | |
| 496 | } | |
| f8e5fea | 497 | /> |
| 498 | ||
| 54f2e2a | 499 | <AiFeatureCard |
| f8e5fea | 500 | n="04" |
| 501 | title="Auto-repair on gate failure" | |
| 54f2e2a | 502 | body="When a gate fails, auto-repair reads the failure, drafts a fix, commits it with a bot account, and re-runs the gate. If it can't fix it, it opens an incident issue with a root-cause summary." |
| f8e5fea | 503 | link={{ href: "/demo", label: "Watch a live repair" }} |
| 504 | reverse | |
| 54f2e2a | 505 | mock={ |
| 506 | <div class="lp-review-mock"> | |
| 507 | <div class="lp-rm-header"> | |
| 508 | <span class="lp-rm-label">Gate: tests · 2 failing</span> | |
| 509 | <span class="lp-badge lp-badge-warn">Repairing</span> | |
| 510 | </div> | |
| 511 | <div class="lp-rm-steps"> | |
| 512 | <div class="lp-rm-step lp-rm-done"><span class="lp-rm-icon lp-rm-fail">✗</span> gate:tests — 2 assertions failed</div> | |
| 513 | <div class="lp-rm-step lp-rm-done"><span class="lp-rm-icon">✓</span> Patch drafted: <code>+9 −3</code></div> | |
| 514 | <div class="lp-rm-step lp-rm-active"><span class="lp-rm-icon">⊙</span> Re-running gate · 412 passing</div> | |
| 515 | </div> | |
| 516 | <div class="lp-rm-verdict"> | |
| 517 | <span class="lp-rm-v-ok">✓ All gates green · Proceeding to merge</span> | |
| 518 | </div> | |
| 519 | </div> | |
| 520 | } | |
| f8e5fea | 521 | /> |
| 522 | </div> | |
| 523 | </div> | |
| 524 | </section> | |
| 525 | ||
| 54f2e2a | 526 | {/* ── Security ── */} |
| 527 | <section class="lp-sec lp-sec-soft" id="security" aria-labelledby="lp-secsec-h"> | |
| f8e5fea | 528 | <div class="lp-wrap"> |
| 529 | <div class="lp-sec-head"> | |
| 530 | <div class="lp-kicker">Security</div> | |
| 54f2e2a | 531 | <h2 id="lp-secsec-h" class="lp-h2">Secure by default at every layer.</h2> |
| 532 | <p class="lp-sub">Security isn't a settings toggle. On Gluecron it runs automatically on every push, every PR, and every dependency update.</p> | |
| f8e5fea | 533 | </div> |
| 534 | <div class="lp-security-grid"> | |
| 54f2e2a | 535 | <SecurityCard title="Push-time secret scanner" body="15 regex patterns + AI semantic scan runs before the branch moves. AWS keys, GitHub tokens, private keys, database URLs — stopped cold." /> |
| 536 | <SecurityCard title="AI security review" body="Claude reviews every diff for SSRF, SQL injection, XSS, and unsafe deserialization patterns. Line-level findings posted as PR comments." /> | |
| 537 | <SecurityCard title="Dependency CVE alerts" body="Scans package.json, go.mod, Cargo.toml, requirements.txt on every push. Critical findings open issues automatically." /> | |
| 538 | <SecurityCard title="Push policy rulesets" body="Enforce commit message patterns, block file paths, cap file sizes, forbid force-pushes — enforced at the HTTP layer, not advisory." /> | |
| 539 | <SecurityCard title="Commit signature verify" body="GPG and SSH key registration with Verified badges on every commit. Full OpenPGP packet parsing, SSHSIG support." /> | |
| 540 | <SecurityCard title="Enterprise auth" body="TOTP 2FA, passkeys (WebAuthn), SAML/OIDC SSO (Okta, Azure AD, Google Workspace), personal access tokens, OAuth provider." /> | |
| f8e5fea | 541 | </div> |
| 542 | </div> | |
| 543 | </section> | |
| 544 | ||
| 54f2e2a | 545 | {/* ── For teams ── */} |
| f8e5fea | 546 | <section class="lp-sec"> |
| 547 | <div class="lp-wrap"> | |
| 548 | <div class="lp-sec-head"> | |
| 549 | <div class="lp-kicker">For teams</div> | |
| 550 | <h2 class="lp-h2">Built for organisations, not just solo developers.</h2> | |
| 54f2e2a | 551 | <p class="lp-sub">Everything a growing engineering team needs — including the parts enterprise platforms charge separately for.</p> |
| f8e5fea | 552 | </div> |
| 553 | <div class="lp-teams-grid"> | |
| 54f2e2a | 554 | <TeamCard title="Organizations & teams" items={["Org-owned repositories", "Team-based CODEOWNERS (@org/team)", "Role-based access (read / write / admin)", "Per-repo collaborator invites", "Org-wide secrets manager (AES-256-GCM)"]} /> |
| 555 | <TeamCard title="Enterprise auth" items={["SAML/OIDC SSO (Okta, Azure AD, Google)", "TOTP 2FA + recovery codes", "Passkeys / WebAuthn", "Email-domain allowlists", "Auto-provision on first SSO login"]} /> | |
| 556 | <TeamCard title="Compliance & audit" items={["100% audit log coverage", "Per-repo + org-level audit views", "Signed commits with Verified badges", "Protected environments (reviewer-gated)", "DORA metrics for engineering health"]} /> | |
| 557 | <TeamCard title="Developer insights" items={["DORA metrics (deploy freq, lead time, MTTR)", "Developer velocity dashboard", "Repository health score (0–100)", "Hot files heatmap (churn-based risk tiers)", "Org-wide green rate and PR activity"]} /> | |
| f8e5fea | 558 | </div> |
| 559 | </div> | |
| 560 | </section> | |
| 561 | ||
| 54f2e2a | 562 | {/* ── Ecosystem ── */} |
| f8e5fea | 563 | <section class="lp-sec lp-sec-soft"> |
| 564 | <div class="lp-wrap"> | |
| 565 | <div class="lp-sec-head"> | |
| 566 | <div class="lp-kicker">Developer ecosystem</div> | |
| 567 | <h2 class="lp-h2">Meet developers where they work.</h2> | |
| 568 | </div> | |
| 569 | <div class="lp-eco-grid"> | |
| 54f2e2a | 570 | <EcoCard title="Official CLI" sub="gluecron repo ls · gluecron issues · gluecron gql" body="A Bun-compiled single binary. Login, repo CRUD, issue listing, GraphQL queries, and server info from your terminal." link="/install" /> |
| 571 | <EcoCard title="VS Code extension" sub="Explain · Semantic search · Generate tests · Open on web" body="Four commands wired to your active Gluecron remote. AI explain-this-file and test generation without leaving the editor." link="/vscode" /> | |
| 572 | <EcoCard title="MCP server" sub="Claude Code · Claude Desktop · Cursor" body="Model Context Protocol tools for repo search, file reads, issue and PR management — all natively available to Claude and Cursor." link="/mcp" /> | |
| 573 | <EcoCard title="Full API surface" sub="REST v2 · GraphQL · Webhooks" body="A comprehensive REST API v2, a GraphQL mirror, HMAC-signed webhook delivery, and an OAuth 2.0 provider for third-party apps." link="/api" /> | |
| f8e5fea | 574 | </div> |
| 575 | </div> | |
| 576 | </section> | |
| 577 | ||
| 54f2e2a | 578 | {/* ── Comparison ── */} |
| f8e5fea | 579 | <section class="lp-sec"> |
| 580 | <div class="lp-wrap"> | |
| 581 | <div class="lp-sec-head"> | |
| 582 | <div class="lp-kicker">vs the incumbent</div> | |
| 583 | <h2 class="lp-h2">Everything GitHub charges for.<br />And the parts they never built.</h2> | |
| 584 | </div> | |
| 585 | <div class="lp-compare"> | |
| 586 | <div class="lp-compare-head" aria-hidden="true"> | |
| 587 | <div /> | |
| 588 | <div>GitHub</div> | |
| 589 | <div>Gluecron</div> | |
| 590 | </div> | |
| 591 | <CompareRow feature="Git hosting + Smart HTTP push" them="✓" us="✓" /> | |
| 592 | <CompareRow feature="Issues, PRs, code review" them="✓" us="✓" /> | |
| 593 | <CompareRow feature="Workflow runner" them="Metered minutes" us="Self-hosted, unmetered" ours /> | |
| 594 | <CompareRow feature="AI code review on every PR" them="Copilot subscription" us="Built in, always on" ours /> | |
| 595 | <CompareRow feature="Spec-to-PR (issue → draft PR)" them="—" us="✓" ours /> | |
| 596 | <CompareRow feature="Auto-repair on gate failure" them="—" us="✓" ours /> | |
| 597 | <CompareRow feature="Secret scanner on push" them="Paid add-on" us="Built in" ours /> | |
| 598 | <CompareRow feature="MCP server (Claude / Cursor)" them="—" us="✓" ours /> | |
| 599 | <CompareRow feature="DORA metrics + velocity dashboard" them="Paid add-on" us="Built in" ours /> | |
| 600 | <CompareRow feature="Self-host on your own hardware" them="Enterprise tier" us="Single binary, free" ours /> | |
| 601 | <CompareRow feature="npm package registry" them="✓" us="✓" /> | |
| 602 | <CompareRow feature="Pre-receive policy enforcement" them="GitHub Enterprise" us="✓ on all plans" ours /> | |
| 603 | </div> | |
| 604 | <div class="lp-compare-foot"> | |
| 605 | <a href="/vs-github" class="lp-text-link">See the full 26-row comparison →</a> | |
| 606 | </div> | |
| 607 | </div> | |
| 608 | </section> | |
| 609 | ||
| 54f2e2a | 610 | {/* ── Pricing ── */} |
| f8e5fea | 611 | <section class="lp-sec lp-sec-soft"> |
| 612 | <div class="lp-wrap"> | |
| 613 | <div class="lp-sec-head"> | |
| 614 | <div class="lp-kicker">Pricing</div> | |
| 615 | <h2 class="lp-h2">Free to start. Honest at scale.</h2> | |
| 54f2e2a | 616 | <p class="lp-sub">Self-hosting is free forever. Hosted plans price the AI calls, not the seats. You own your data either way.</p> |
| f8e5fea | 617 | </div> |
| 618 | <div class="lp-price-grid"> | |
| 54f2e2a | 619 | <PricingCard tier="Free" price="$0" cadence="forever" desc="For personal projects and open source. Public and private repos, full AI suite, fair quotas." features={["Unlimited public repos", "3 private repos", "5K AI calls / month", "Community support"]} cta="Start free" href="/register" /> |
| 620 | <PricingCard tier="Pro" price="$12" cadence="per user / month" desc="For working developers. Lifts every quota, adds priority routing, no Gluecron branding on deploys." features={["Unlimited private repos", "100K AI calls / month", "Priority queue", "Custom domains"]} cta="Go Pro" href="/settings/billing" highlight /> | |
| 621 | <PricingCard tier="Team" price="Talk to us" cadence="custom" desc="For orgs running production on Gluecron. SSO, audit retention, enterprise SLA, on-prem." features={["SSO + SCIM", "On-prem deploy", "Dedicated capacity", "24/7 incident response"]} cta="Contact us" href="mailto:hello@gluecron.com" /> | |
| f8e5fea | 622 | </div> |
| 623 | <div class="lp-price-foot"> | |
| 624 | <a href="/pricing" class="lp-text-link">Full pricing details →</a> | |
| 625 | </div> | |
| 626 | </div> | |
| 627 | </section> | |
| 628 | ||
| 54f2e2a | 629 | {/* ── Install ── */} |
| f8e5fea | 630 | <section class="lp-sec"> |
| 631 | <div class="lp-wrap lp-install-wrap"> | |
| 632 | <div class="lp-install-text"> | |
| 633 | <div class="lp-kicker">One command to start</div> | |
| 54f2e2a | 634 | <h2 class="lp-h2 lp-h2-tight">Already on GitHub?<br />Migrate in 30 seconds.</h2> |
| 635 | <p class="lp-sub">Signs you in, mints a PAT, imports your repo, wires the Claude Desktop MCP server, and drops the Gluecron skill files — all in one shot.</p> | |
| 636 | <a href="/import" class="lp-btn lp-btn-solid lp-btn-lg" style="margin-top:20px">Or import a single repo →</a> | |
| f8e5fea | 637 | </div> |
| 54f2e2a | 638 | <div class="lp-install-terminal"> |
| f8e5fea | 639 | <div class="lp-term-bar"> |
| 640 | <span class="lp-term-dot" /><span class="lp-term-dot" /><span class="lp-term-dot" /> | |
| 641 | <span class="lp-term-title">Terminal</span> | |
| 642 | </div> | |
| 643 | <div class="lp-term-body"> | |
| 644 | <div class="lp-term-line"> | |
| 645 | <span class="lp-term-prompt">$</span> | |
| 646 | <span id="lp-install-cmd">curl -sSL gluecron.com/install | bash</span> | |
| 54f2e2a | 647 | <button type="button" class="lp-copy-btn" data-copy-target="lp-install-cmd" aria-label="Copy install command">Copy</button> |
| f8e5fea | 648 | </div> |
| 649 | <div class="lp-term-out lp-term-ok">✓ signed in as you@example.com</div> | |
| 650 | <div class="lp-term-out lp-term-ok">✓ PAT created (admin scope)</div> | |
| 651 | <div class="lp-term-out lp-term-ok">✓ your-repo imported from GitHub</div> | |
| 652 | <div class="lp-term-out lp-term-ok">✓ MCP server added to Claude Desktop</div> | |
| 653 | <div class="lp-term-out lp-term-ok">✓ gluecron-pr, gluecron-issue skills ready</div> | |
| 654 | </div> | |
| 655 | </div> | |
| 656 | </div> | |
| 657 | </section> | |
| 658 | ||
| 54f2e2a | 659 | {/* ── Dark CTA ── */} |
| 660 | <section class="lp-cta-dark"> | |
| f8e5fea | 661 | <div class="lp-wrap lp-cta-in"> |
| 54f2e2a | 662 | <div class="lp-kicker lp-kicker-dark">Ready when you are</div> |
| f8e5fea | 663 | <h2 class="lp-cta-h">Stop managing the platform.<br />Start shipping the product.</h2> |
| 54f2e2a | 664 | <p class="lp-cta-sub">Free to start. Self-hosted-friendly. MCP-native. Migrate from GitHub in one command.</p> |
| f8e5fea | 665 | <div class="lp-cta-btns"> |
| 54f2e2a | 666 | <a href="/register" class="lp-btn lp-btn-cta-solid lp-btn-xl">Create your account</a> |
| 667 | <a href="/import" class="lp-btn lp-btn-cta-outline lp-btn-xl">Migrate a repo</a> | |
| f8e5fea | 668 | </div> |
| 54f2e2a | 669 | <div class="lp-cta-links lp-cta-links-dark"> |
| f8e5fea | 670 | <a href="/demo">Try the live demo</a> |
| 671 | <span aria-hidden="true">·</span> | |
| 672 | <a href="/vs-github">Compare to GitHub</a> | |
| 673 | <span aria-hidden="true">·</span> | |
| 674 | <a href="/pricing">See pricing</a> | |
| 675 | </div> | |
| 676 | </div> | |
| 677 | </section> | |
| 678 | ||
| 54f2e2a | 679 | {/* ── Footer ── */} |
| f8e5fea | 680 | <footer class="lp-footer"> |
| 681 | <div class="lp-wrap lp-footer-in"> | |
| 682 | <div class="lp-footer-brand"> | |
| 54f2e2a | 683 | <a href="/" class="lp-logo lp-logo-light"> |
| f8e5fea | 684 | <span class="lp-logo-mark" aria-hidden="true" /> |
| 685 | gluecron | |
| 686 | </a> | |
| 54f2e2a | 687 | <p class="lp-footer-tag">The AI-native git platform.<br />Self-hosted, auditable, git-native.</p> |
| f8e5fea | 688 | </div> |
| 689 | <div class="lp-footer-cols"> | |
| 690 | <div class="lp-footer-col"> | |
| 691 | <h4>Product</h4> | |
| 692 | <a href="#platform">Features</a> | |
| 693 | <a href="#ai">AI suite</a> | |
| 694 | <a href="#security">Security</a> | |
| 695 | <a href="/pricing">Pricing</a> | |
| 696 | <a href="/vs-github">vs GitHub</a> | |
| 697 | <a href="/sleep-mode">Sleep Mode</a> | |
| 698 | </div> | |
| 699 | <div class="lp-footer-col"> | |
| 700 | <h4>Platform</h4> | |
| 701 | <a href="/explore">Explore repos</a> | |
| 702 | <a href="/demo">Live demo</a> | |
| 703 | <a href="/marketplace">Marketplace</a> | |
| 704 | <a href="/install">Install script</a> | |
| 705 | <a href="/api">API docs</a> | |
| 706 | <a href="/mcp">MCP server</a> | |
| 707 | </div> | |
| 708 | <div class="lp-footer-col"> | |
| 709 | <h4>Developers</h4> | |
| 710 | <a href="/install">CLI install</a> | |
| 711 | <a href="/vscode">VS Code ext</a> | |
| 712 | <a href="/api/graphql">GraphQL explorer</a> | |
| 713 | <a href="/help">Migration guide</a> | |
| 714 | <a href="/import">Import from GitHub</a> | |
| 715 | <a href="/connect/claude-guide">Claude Code guide</a> | |
| 716 | </div> | |
| 717 | <div class="lp-footer-col"> | |
| 718 | <h4>Account</h4> | |
| 719 | <a href="/login">Sign in</a> | |
| 720 | <a href="/register">Register</a> | |
| 721 | <a href="/settings">Settings</a> | |
| 722 | <a href="/settings/billing">Billing</a> | |
| 723 | <a href="/settings/tokens">API tokens</a> | |
| 724 | <a href="/status">Platform status</a> | |
| 725 | </div> | |
| 726 | </div> | |
| 727 | </div> | |
| 728 | <div class="lp-footer-bottom"> | |
| 729 | <div class="lp-wrap lp-footer-bottom-in"> | |
| 730 | <span>© {new Date().getFullYear()} Gluecron</span> | |
| 731 | <span>Self-hosted · Git-native · Claude-first</span> | |
| 732 | </div> | |
| 733 | </div> | |
| 734 | </footer> | |
| 735 | ||
| 54f2e2a | 736 | <script dangerouslySetInnerHTML={{ __html: pageJs }} /> |
| f8e5fea | 737 | </body> |
| 738 | </html> | |
| 739 | ); | |
| 740 | }; | |
| 741 | ||
| 742 | // ─── sub-components ──────────────────────────────────────────────── | |
| 743 | ||
| 54f2e2a | 744 | const LiveCard: FC<{ title: string; variant: "violet"|"green"|"brand"|"neutral"; children?: any }> = ({ title, variant, children }) => ( |
| 745 | <article class={`lp-live-card lp-live-card-${variant} lp-reveal`}> | |
| f8e5fea | 746 | <h3 class="lp-live-card-title">{title}</h3> |
| 747 | {children} | |
| 748 | </article> | |
| 749 | ); | |
| 750 | ||
| 54f2e2a | 751 | const PlatformGroup: FC<{ icon: string; iconColor: string; title: string; children: string[] }> = ({ icon: iconHtml, title, children }) => ( |
| 752 | <div class="lp-plat-group lp-reveal"> | |
| 753 | <div class="lp-plat-header"> | |
| 754 | <span class="lp-plat-icon" dangerouslySetInnerHTML={{ __html: iconHtml }} /> | |
| 755 | <h3 class="lp-plat-title">{title}</h3> | |
| 756 | </div> | |
| f8e5fea | 757 | <ul class="lp-plat-list"> |
| 54f2e2a | 758 | {children.map((item) => <li>{item}</li>)} |
| f8e5fea | 759 | </ul> |
| 760 | </div> | |
| 761 | ); | |
| 762 | ||
| 763 | const PainRow: FC<{ problem: string; fix: string }> = ({ problem, fix }) => ( | |
| 54f2e2a | 764 | <div class="lp-pain-row lp-reveal"> |
| f8e5fea | 765 | <div class="lp-pain-problem"> |
| 54f2e2a | 766 | <span class="lp-pain-icon lp-pain-no" aria-label="Before">✗</span>{problem} |
| f8e5fea | 767 | </div> |
| 768 | <div class="lp-pain-arrow" aria-hidden="true">→</div> | |
| 769 | <div class="lp-pain-fix"> | |
| 54f2e2a | 770 | <span class="lp-pain-icon lp-pain-yes" aria-label="After">✓</span>{fix} |
| f8e5fea | 771 | </div> |
| 772 | </div> | |
| 773 | ); | |
| 774 | ||
| 54f2e2a | 775 | const AiFeatureCard: FC<{ |
| 776 | n: string; title: string; body: string; | |
| f8e5fea | 777 | link: { href: string; label: string }; |
| 54f2e2a | 778 | mock: any; reverse?: boolean; |
| f8e5fea | 779 | }> = ({ n, title, body, link, mock, reverse }) => ( |
| 54f2e2a | 780 | <div class={`lp-ai-feature lp-reveal${reverse ? " lp-ai-feature-rev" : ""}`}> |
| f8e5fea | 781 | <div class="lp-ai-text"> |
| 782 | <div class="lp-ai-n">{n}</div> | |
| 783 | <h3 class="lp-ai-title">{title}</h3> | |
| 784 | <p class="lp-ai-body">{body}</p> | |
| 785 | <a href={link.href} class="lp-text-link">{link.label} →</a> | |
| 786 | </div> | |
| 54f2e2a | 787 | <div class="lp-ai-mock" aria-hidden="true">{mock}</div> |
| f8e5fea | 788 | </div> |
| 789 | ); | |
| 790 | ||
| 791 | const SecurityCard: FC<{ title: string; body: string }> = ({ title, body }) => ( | |
| 54f2e2a | 792 | <div class="lp-sec-card lp-reveal"> |
| f8e5fea | 793 | <h3 class="lp-sec-card-title">{title}</h3> |
| 794 | <p class="lp-sec-card-body">{body}</p> | |
| 795 | </div> | |
| 796 | ); | |
| 797 | ||
| 798 | const TeamCard: FC<{ title: string; items: string[] }> = ({ title, items }) => ( | |
| 54f2e2a | 799 | <div class="lp-team-card lp-reveal"> |
| f8e5fea | 800 | <h3 class="lp-team-title">{title}</h3> |
| 801 | <ul class="lp-team-list"> | |
| 54f2e2a | 802 | {items.map((item) => <li><span aria-hidden="true">✓</span>{item}</li>)} |
| f8e5fea | 803 | </ul> |
| 804 | </div> | |
| 805 | ); | |
| 806 | ||
| 54f2e2a | 807 | const EcoCard: FC<{ title: string; sub: string; body: string; link: string }> = ({ title, sub, body, link }) => ( |
| 808 | <div class="lp-eco-card lp-reveal"> | |
| f8e5fea | 809 | <h3 class="lp-eco-title">{title}</h3> |
| 810 | <div class="lp-eco-sub">{sub}</div> | |
| 811 | <p class="lp-eco-body">{body}</p> | |
| 812 | <a href={link} class="lp-text-link">Learn more →</a> | |
| 813 | </div> | |
| 814 | ); | |
| 815 | ||
| 54f2e2a | 816 | const CompareRow: FC<{ feature: string; them: string; us: string; ours?: boolean }> = ({ feature, them, us, ours }) => ( |
| f8e5fea | 817 | <div class={`lp-cmp-row${ours ? " lp-cmp-ours" : ""}`}> |
| 818 | <div class="lp-cmp-feature">{feature}</div> | |
| 819 | <div class="lp-cmp-them">{them}</div> | |
| 820 | <div class={`lp-cmp-us${ours ? " lp-cmp-us-hl" : ""}`}>{us}</div> | |
| 821 | </div> | |
| 822 | ); | |
| 823 | ||
| 824 | const PricingCard: FC<{ | |
| 825 | tier: string; price: string; cadence: string; desc: string; | |
| 826 | features: string[]; cta: string; href: string; highlight?: boolean; | |
| 827 | }> = ({ tier, price, cadence, desc, features, cta, href, highlight }) => ( | |
| 54f2e2a | 828 | <div class={`lp-price-card lp-reveal${highlight ? " lp-price-hl" : ""}`}> |
| f8e5fea | 829 | {highlight && <div class="lp-price-badge">Most popular</div>} |
| 830 | <div class="lp-price-tier">{tier}</div> | |
| 831 | <div class="lp-price-amount"> | |
| 832 | <span class="lp-price-num">{price}</span> | |
| 833 | <span class="lp-price-cad">{cadence}</span> | |
| 834 | </div> | |
| 835 | <p class="lp-price-desc">{desc}</p> | |
| 836 | <ul class="lp-price-features"> | |
| 837 | {features.map((f) => <li><span aria-hidden="true">✓</span>{f}</li>)} | |
| 838 | </ul> | |
| 54f2e2a | 839 | <a href={href} class={`lp-btn ${highlight ? "lp-btn-solid" : "lp-btn-outline"} lp-btn-block`}>{cta}</a> |
| f8e5fea | 840 | </div> |
| 841 | ); | |
| 842 | ||
| 843 | // ─── JavaScript ──────────────────────────────────────────────────── | |
| 844 | ||
| 845 | const liveNowJs = ` | |
| 846 | (function(){ | |
| 847 | try{ | |
| 848 | var DEMO=${JSON.stringify(DEMO_USERNAME)}; | |
| 849 | var INTERVAL=30000; | |
| 850 | function esc(s){return String(s==null?'':s).replace(/[&<>"']/g,function(c){return{'&':'&','<':'<','>':'>','"':'"',"'":'''}[c];});} | |
| 851 | function rel(v){ | |
| 852 | if(v==null) return 'just now'; | |
| 54f2e2a | 853 | var t=(typeof v==='number'?v:new Date(v).getTime()); |
| f8e5fea | 854 | if(!isFinite(t)) return 'just now'; |
| 855 | var d=Date.now()-t;if(d<0) return 'just now'; | |
| 856 | var s=Math.floor(d/1000);if(s<60) return 'just now'; | |
| 857 | var m=Math.floor(s/60);if(m<60) return m+'m ago'; | |
| 858 | var h=Math.floor(m/60);if(h<24) return h+'h ago'; | |
| 859 | return Math.floor(h/24)+'d ago'; | |
| 860 | } | |
| 861 | function diffMount(ul,newHtml){ | |
| 862 | if(!ul) return; | |
| 863 | var prev={}; | |
| 864 | ul.querySelectorAll('[data-row-id]').forEach(function(n){prev[n.getAttribute('data-row-id')]=true;}); | |
| 865 | ul.innerHTML=newHtml; | |
| 866 | ul.querySelectorAll('[data-row-id]').forEach(function(n){ | |
| 54f2e2a | 867 | if(!prev[n.getAttribute('data-row-id')]){ |
| 868 | n.style.transition='none';n.style.background='rgba(67,83,201,.07)'; | |
| 869 | setTimeout(function(){n.style.transition='background .8s';n.style.background='';},50); | |
| 870 | } | |
| f8e5fea | 871 | }); |
| 872 | } | |
| 54f2e2a | 873 | function pollQueued(){fetch('/api/v2/demo/queued',{credentials:'omit'}).then(function(r){return r.json();}).then(function(d){var ul=document.querySelector('[data-livecard="queued"]');if(!ul) return;var items=(d&&d.items)||[];if(!items.length){ul.innerHTML='<li class="lp-live-empty">Quiet right now.</li>';return;}diffMount(ul,items.slice(0,3).map(function(i){var id='queued|'+i.repo+'|'+i.number;return'<li class="lp-live-row" data-row-id="'+esc(id)+'"><a href="/'+esc(DEMO)+'/'+esc(i.repo)+'/issues/'+i.number+'" class="lp-live-link"><span class="lp-live-num">#'+i.number+'</span> '+esc(i.title)+'</a><span class="lp-live-meta">'+esc(i.repo)+'</span></li>';}).join(''));}).catch(function(){});} |
| 874 | function pollMerges(){fetch('/api/v2/demo/merges',{credentials:'omit'}).then(function(r){return r.json();}).then(function(d){var ul=document.querySelector('[data-livecard="merges"]');if(!ul) return;var items=(d&&d.items)||[];if(!items.length){ul.innerHTML='<li class="lp-live-empty">No auto-merges in the last 24h.</li>';return;}diffMount(ul,items.slice(0,3).map(function(m){var id='merges|'+m.repo+'|'+m.number;return'<li class="lp-live-row" data-row-id="'+esc(id)+'"><a href="/'+esc(DEMO)+'/'+esc(m.repo)+'/pulls/'+m.number+'" class="lp-live-link"><span class="lp-live-num">#'+m.number+'</span> '+esc(m.title)+'</a><span class="lp-live-meta">'+esc(m.repo)+' <span data-rel="'+esc(m.mergedAt)+'">'+esc(rel(m.mergedAt))+'</span></span></li>';}).join(''));}).catch(function(){});} | |
| 875 | function pollReviews(){fetch('/api/v2/demo/reviews',{credentials:'omit'}).then(function(r){return r.json();}).then(function(d){var ul=document.querySelector('[data-livecard="reviews"]');if(!ul) return;var cnt=document.querySelector('[data-livecard-count="reviews"]');if(cnt&&typeof d.count==='number') cnt.textContent=d.count;var items=(d&&d.items)||[];if(!items.length){ul.innerHTML='<li class="lp-live-empty">No reviews in the last 24h.</li>';return;}diffMount(ul,items.slice(0,2).map(function(r){var id='reviews|'+r.repo+'|'+r.prNumber;return'<li class="lp-live-row" data-row-id="'+esc(id)+'"><a href="/'+esc(DEMO)+'/'+esc(r.repo)+'/pulls/'+r.prNumber+'" class="lp-live-link"><span class="lp-live-num">#'+r.prNumber+'</span> '+esc(r.commentSnippet)+'</a><span class="lp-live-meta">'+esc(r.repo)+'</span></li>';}).join(''));}).catch(function(){});} | |
| 876 | function pollFeed(){fetch('/api/v2/demo/activity',{credentials:'omit'}).then(function(r){return r.json();}).then(function(d){var ul=document.querySelector('[data-livecard="feed"]');if(!ul) return;var entries=(d&&d.entries)||[];if(!entries.length){ul.innerHTML='<li class="lp-live-empty">Quiet right now.</li>';return;}diffMount(ul,entries.slice(0,6).map(function(e){var path=(e.ref&&e.ref.type==='pr')?'pulls':'issues';var num=(e.ref&&e.ref.number)||0;var label=e.kind==='auto_merge.merged'?'auto-merged':e.kind==='ai_build.dispatched'?'AI-built':'AI review';var kc=String(e.kind||'').replace(/\\./g,'-');var id=e.kind+'|'+e.repo+'|'+(e.ref&&e.ref.type)+'|'+num;return'<li class="lp-live-feedrow" data-row-id="'+esc(id)+'"><span class="lp-feed-kind lp-feed-kind-'+esc(kc)+'">'+esc(label)+'</span> <a class="lp-live-link" href="/'+esc(DEMO)+'/'+esc(e.repo)+'/'+path+'/'+num+'">'+esc(e.repo)+' #'+num+'</a> <span data-rel="'+esc(e.at)+'">'+esc(rel(e.at))+'</span></li>';}).join(''));}).catch(function(){});} | |
| 877 | function refreshRel(){document.querySelectorAll('[data-rel]').forEach(function(el){el.textContent=rel(el.getAttribute('data-rel'));});} | |
| f8e5fea | 878 | function tick(){pollQueued();pollMerges();pollReviews();pollFeed();} |
| 54f2e2a | 879 | setInterval(tick,INTERVAL);setInterval(refreshRel,60000); |
| 880 | }catch(err){} | |
| 881 | })(); | |
| 882 | `; | |
| 883 | ||
| 884 | const pageJs = ` | |
| 885 | (function(){ | |
| 886 | /* ── copy button ── */ | |
| 887 | document.addEventListener('click',function(e){ | |
| 888 | var btn=e.target.closest('[data-copy-target]'); | |
| 889 | if(!btn) return; | |
| 890 | var el=document.getElementById(btn.getAttribute('data-copy-target')); | |
| 891 | if(!el) return; | |
| 892 | navigator.clipboard.writeText(el.textContent.trim()).then(function(){ | |
| 893 | var o=btn.textContent;btn.textContent='Copied!'; | |
| 894 | setTimeout(function(){btn.textContent=o;},1500); | |
| 895 | }).catch(function(){}); | |
| 896 | }); | |
| 897 | ||
| 898 | /* ── scroll reveal ── */ | |
| 899 | try{ | |
| 900 | var obs=new IntersectionObserver(function(entries){ | |
| 901 | entries.forEach(function(e){ | |
| 902 | if(e.isIntersecting){e.target.classList.add('lp-visible');obs.unobserve(e.target);} | |
| 903 | }); | |
| 904 | },{threshold:0.08,rootMargin:'0px 0px -40px 0px'}); | |
| 905 | document.querySelectorAll('.lp-reveal').forEach(function(el){obs.observe(el);}); | |
| 906 | }catch(err){} | |
| 907 | ||
| 908 | /* ── counter animation ── */ | |
| 909 | try{ | |
| 910 | function animCount(el,target,dur){ | |
| 911 | var start=performance.now(); | |
| 912 | var suffix=el.getAttribute('data-counter-suffix')||''; | |
| 913 | function step(now){ | |
| 914 | var p=Math.min((now-start)/dur,1); | |
| 915 | var ease=1-Math.pow(1-p,3); | |
| 916 | var v=Math.round(ease*target); | |
| 917 | el.textContent=(v>=1000?(v/1000).toFixed(v>=10000?0:1)+'k':String(v))+suffix; | |
| 918 | if(p<1) requestAnimationFrame(step); | |
| 919 | else el.textContent=target>=1000?(target/1000).toFixed(target>=10000?0:1)+'k'+suffix:String(target)+suffix; | |
| 920 | } | |
| 921 | requestAnimationFrame(step); | |
| 922 | } | |
| 923 | var cObs=new IntersectionObserver(function(entries){ | |
| 924 | entries.forEach(function(e){ | |
| 925 | if(e.isIntersecting){ | |
| 926 | var t=parseInt(e.target.getAttribute('data-counter-target')||'0',10); | |
| 927 | if(t>0) animCount(e.target,t,1200); | |
| 928 | cObs.unobserve(e.target); | |
| 929 | } | |
| 930 | }); | |
| 931 | },{threshold:0.5}); | |
| 932 | document.querySelectorAll('[data-counter-target]').forEach(function(el){cObs.observe(el);}); | |
| 933 | }catch(err){} | |
| 934 | ||
| 935 | /* ── nav scroll class ── */ | |
| 936 | try{ | |
| 937 | var nav=document.getElementById('lp-nav'); | |
| 938 | window.addEventListener('scroll',function(){ | |
| 939 | nav&&nav.classList.toggle('lp-stuck',window.scrollY>20); | |
| 940 | },{passive:true}); | |
| f8e5fea | 941 | }catch(err){} |
| 942 | })(); | |
| 943 | `; | |
| 944 | ||
| 945 | // ─── CSS ─────────────────────────────────────────────────────────── | |
| 946 | ||
| 947 | const css = ` | |
| 54f2e2a | 948 | /* ── tokens ── */ |
| f8e5fea | 949 | :root{ |
| 54f2e2a | 950 | --lp-brand:#4353c9;--lp-brand-h:#3848b6; |
| 951 | --lp-bg:#ffffff;--lp-soft:#fafafb; | |
| 952 | --lp-ink:#0a0b0d;--lp-ink-2:#3a3d45;--lp-muted:#6b7280; | |
| 953 | --lp-border:rgba(0,0,0,.08); | |
| 954 | --lp-shadow:0 1px 3px rgba(0,0,0,.04),0 8px 20px rgba(0,0,0,.06); | |
| 955 | --lp-shadow-lg:0 4px 12px rgba(0,0,0,.07),0 24px 48px rgba(0,0,0,.09); | |
| 956 | --lp-green:#059669;--lp-red:#dc2626;--lp-violet:#7c3aed;--lp-amber:#b45309; | |
| 957 | --lp-max:1200px;--lp-r:10px; | |
| 958 | --lp-navy:#0a0d1a; | |
| f8e5fea | 959 | } |
| 960 | *{box-sizing:border-box;margin:0;padding:0} | |
| 54f2e2a | 961 | html{scroll-behavior:smooth;font-size:17px;line-height:1.6;-webkit-font-smoothing:antialiased;text-rendering:optimizeLegibility} |
| 962 | body{background:var(--lp-bg);color:var(--lp-ink);font-family:'Inter',-apple-system,BlinkMacSystemFont,'Segoe UI',system-ui,sans-serif} | |
| f8e5fea | 963 | a{color:inherit;text-decoration:none} |
| 964 | ||
| 54f2e2a | 965 | /* ── scroll reveal ── */ |
| 966 | .lp-reveal{opacity:0;transform:translateY(16px);transition:opacity .5s ease,transform .5s ease} | |
| 967 | .lp-reveal.lp-visible{opacity:1;transform:none} | |
| 968 | ||
| 969 | /* ── layout ── */ | |
| f8e5fea | 970 | .lp-wrap{max-width:var(--lp-max);margin:0 auto;padding:0 24px} |
| 971 | .lp-sec{padding:96px 0} | |
| 972 | .lp-sec-soft{background:var(--lp-soft)} | |
| 973 | ||
| 54f2e2a | 974 | /* ── typography ── */ |
| 975 | .lp-h1{font-family:'Inter Tight','Inter',sans-serif;font-weight:800;font-size:clamp(42px,6.5vw,76px);line-height:1.02;letter-spacing:-.03em;color:var(--lp-ink)} | |
| 976 | .lp-h2{font-family:'Inter Tight','Inter',sans-serif;font-weight:700;font-size:clamp(30px,4vw,50px);line-height:1.08;letter-spacing:-.022em;color:var(--lp-ink)} | |
| 977 | .lp-h2-tight{font-size:clamp(26px,3.2vw,40px)} | |
| 978 | .lp-kicker{font-size:12.5px;font-weight:600;letter-spacing:.07em;text-transform:uppercase;color:var(--lp-brand);margin-bottom:12px} | |
| f8e5fea | 979 | .lp-sub{font-size:18px;color:var(--lp-ink-2);line-height:1.65;max-width:60ch} |
| 980 | .lp-text-link{color:var(--lp-brand);font-size:15px;font-weight:500} | |
| 981 | .lp-text-link:hover{color:var(--lp-brand-h);text-decoration:underline} | |
| 54f2e2a | 982 | code{font-family:'JetBrains Mono',ui-monospace,monospace;font-size:.86em;background:rgba(0,0,0,.05);padding:2px 5px;border-radius:4px} |
| 983 | ||
| 984 | /* ── buttons ── */ | |
| 985 | .lp-btn{display:inline-flex;align-items:center;gap:6px;font-family:inherit;font-weight:600;font-size:15px;border-radius:var(--lp-r);padding:10px 18px;border:1.5px solid transparent;cursor:pointer;text-decoration:none;transition:transform .15s,box-shadow .2s,background .15s,border-color .15s,color .15s;white-space:nowrap} | |
| f8e5fea | 986 | .lp-btn:hover{transform:translateY(-1px)} |
| 987 | .lp-btn-solid{background:var(--lp-ink);color:#fff;border-color:var(--lp-ink)} | |
| 54f2e2a | 988 | .lp-btn-solid:hover{box-shadow:0 8px 24px rgba(10,11,13,.25)} |
| f8e5fea | 989 | .lp-btn-outline{color:var(--lp-ink);border-color:rgba(0,0,0,.2)} |
| 990 | .lp-btn-outline:hover{border-color:var(--lp-ink);background:rgba(0,0,0,.03)} | |
| 991 | .lp-btn-ghost{color:var(--lp-ink-2);border-color:var(--lp-border)} | |
| 992 | .lp-btn-ghost:hover{color:var(--lp-ink);border-color:rgba(0,0,0,.2)} | |
| 993 | .lp-btn-lg{padding:13px 22px;font-size:16px;border-radius:12px} | |
| 54f2e2a | 994 | .lp-btn-xl{padding:15px 30px;font-size:17px;border-radius:14px} |
| f8e5fea | 995 | .lp-btn-block{width:100%;justify-content:center} |
| 54f2e2a | 996 | /* dark section buttons */ |
| 997 | .lp-btn-cta-solid{background:#fff;color:var(--lp-navy);border-color:#fff} | |
| 998 | .lp-btn-cta-solid:hover{background:#f1f5f9;box-shadow:0 8px 24px rgba(0,0,0,.35)} | |
| 999 | .lp-btn-cta-outline{color:rgba(255,255,255,.9);border-color:rgba(255,255,255,.28)} | |
| 1000 | .lp-btn-cta-outline:hover{border-color:rgba(255,255,255,.6);background:rgba(255,255,255,.07)} | |
| 1001 | ||
| 1002 | /* ── badges ── */ | |
| 1003 | .lp-badge{display:inline-flex;align-items:center;font-size:11.5px;font-weight:700;padding:3px 8px;border-radius:6px} | |
| f8e5fea | 1004 | .lp-badge-merged{color:var(--lp-green);background:rgba(5,150,105,.1)} |
| 54f2e2a | 1005 | .lp-badge-approved{color:var(--lp-green);background:rgba(5,150,105,.1)} |
| 1006 | .lp-badge-ai{color:var(--lp-violet);background:rgba(124,58,237,.1)} | |
| 1007 | .lp-badge-warn{color:var(--lp-amber);background:rgba(180,83,9,.1)} | |
| f8e5fea | 1008 | |
| 54f2e2a | 1009 | /* ── nav ── */ |
| 1010 | .lp-nav{position:sticky;top:0;z-index:50;background:rgba(255,255,255,.88);backdrop-filter:blur(14px) saturate(180%);border-bottom:1px solid transparent;transition:border-color .2s} | |
| 1011 | .lp-nav.lp-stuck{border-bottom-color:var(--lp-border)} | |
| 1012 | .lp-nav-in{max-width:var(--lp-max);margin:0 auto;padding:14px 24px;display:flex;align-items:center;gap:20px} | |
| 1013 | .lp-logo{display:inline-flex;align-items:center;gap:9px;font-family:'Inter Tight',sans-serif;font-weight:700;font-size:18px;letter-spacing:-.02em;color:var(--lp-ink)} | |
| 1014 | .lp-logo-light{color:rgba(255,255,255,.9)} | |
| 1015 | .lp-logo-mark{width:18px;height:18px;border-radius:6px;background:var(--lp-brand);display:inline-block;flex-shrink:0} | |
| f8e5fea | 1016 | .lp-nav-links{display:flex;gap:24px;margin-left:10px} |
| 1017 | .lp-nav-links a{font-size:15px;font-weight:500;color:var(--lp-ink-2);transition:color .15s} | |
| 1018 | .lp-nav-links a:hover{color:var(--lp-ink)} | |
| 1019 | .lp-nav-ctas{margin-left:auto;display:flex;align-items:center;gap:10px} | |
| 1020 | @media(max-width:720px){.lp-nav-links{display:none}.lp-nav-ctas .lp-btn-ghost{display:none}} | |
| 1021 | ||
| 54f2e2a | 1022 | /* ── hero ── */ |
| f8e5fea | 1023 | .lp-hero{padding:80px 0 60px;border-bottom:1px solid var(--lp-border)} |
| 1024 | .lp-hero-in{display:grid;grid-template-columns:1fr 1fr;gap:64px;align-items:center} | |
| 54f2e2a | 1025 | .lp-hero-text{display:flex;flex-direction:column} |
| f8e5fea | 1026 | .lp-hero-text .lp-kicker{margin-bottom:16px} |
| 1027 | .lp-hero-sub{font-size:18px;color:var(--lp-ink-2);line-height:1.65;margin:20px 0 0} | |
| 1028 | .lp-hero-ctas{display:flex;gap:12px;flex-wrap:wrap;margin-top:28px} | |
| 1029 | .lp-hero-links{display:flex;gap:10px;align-items:center;margin-top:14px;font-size:14px;color:var(--lp-muted)} | |
| 1030 | .lp-hero-links a{color:var(--lp-brand);font-weight:500} | |
| 1031 | .lp-hero-links span{color:var(--lp-border)} | |
| 54f2e2a | 1032 | .lp-hero-stats{display:flex;gap:28px;margin-top:32px;padding-top:24px;border-top:1px solid var(--lp-border);list-style:none;flex-wrap:wrap} |
| 1033 | .lp-hero-stat dt{font-family:'Inter Tight',sans-serif;font-weight:700;font-size:22px;color:var(--lp-ink);letter-spacing:-.01em} | |
| 1034 | .lp-hero-stat dd{font-size:12.5px;color:var(--lp-muted);margin-top:2px} | |
| 1035 | @media(max-width:900px){.lp-hero-in{grid-template-columns:1fr}.lp-hero-card{order:-1}} | |
| 1036 | ||
| 1037 | /* ── hero PR mock card ── */ | |
| 1038 | .lp-hero-card{background:#fff;border:1px solid var(--lp-border);border-radius:16px;box-shadow:var(--lp-shadow-lg);overflow:hidden;font-size:13px} | |
| 1039 | .lp-hc-chrome{display:flex;align-items:center;gap:6px;padding:10px 14px;background:var(--lp-soft);border-bottom:1px solid var(--lp-border)} | |
| 1040 | .lp-hc-dot{width:11px;height:11px;border-radius:50%} | |
| 1041 | .lp-hc-dot-r{background:#ff5f57} | |
| 1042 | .lp-hc-dot-y{background:#ffbd2e} | |
| 1043 | .lp-hc-dot-g{background:#28c840} | |
| 1044 | .lp-hc-path{margin-left:8px;font-family:'JetBrains Mono',monospace;font-size:11.5px;color:var(--lp-muted)} | |
| 1045 | .lp-hc-pr-row{display:flex;align-items:center;gap:10px;padding:12px 16px;border-bottom:1px solid var(--lp-border)} | |
| 1046 | .lp-hc-pr-title{font-size:13.5px;font-weight:600;color:var(--lp-ink)} | |
| 1047 | /* diff */ | |
| 1048 | .lp-diff{font-family:'JetBrains Mono',monospace;font-size:12.5px;background:#fafafa;border-bottom:1px solid var(--lp-border)} | |
| 1049 | .lp-diff-line{display:flex;align-items:baseline;gap:0;line-height:1.7;padding:0 0} | |
| 1050 | .lp-diff-add{background:rgba(5,150,105,.07)} | |
| 1051 | .lp-diff-focus{background:rgba(5,150,105,.13)} | |
| 1052 | .lp-ln{width:36px;text-align:right;color:#9ca3af;padding-right:8px;user-select:none;font-size:11px} | |
| 1053 | .lp-op{width:16px;text-align:center;color:var(--lp-green);font-weight:700;flex-shrink:0} | |
| 1054 | .lp-diff-line code{font-family:'JetBrains Mono',monospace;font-size:12px;background:none;padding:0;color:#1f2937} | |
| 1055 | .lp-kw{color:#7c3aed;font-style:italic} | |
| 1056 | .lp-ty{color:#059669} | |
| 1057 | .lp-fn{color:#2563eb} | |
| 1058 | .lp-str{color:#d97706} | |
| 1059 | /* inline comment */ | |
| 1060 | .lp-inline-comment{display:flex;gap:10px;padding:12px 16px;background:rgba(67,83,201,.04);border-bottom:1px solid rgba(67,83,201,.12)} | |
| 1061 | .lp-ic-ava{width:26px;height:26px;border-radius:50%;background:var(--lp-brand);color:#fff;font-size:12px;font-weight:700;display:flex;align-items:center;justify-content:center;flex-shrink:0;margin-top:1px} | |
| 1062 | .lp-ic-head{font-size:12px;font-weight:600;color:var(--lp-ink-2);margin-bottom:3px} | |
| 1063 | .lp-ic-loc{color:var(--lp-brand);font-family:'JetBrains Mono',monospace;font-size:11px} | |
| 1064 | .lp-ic-text{font-size:13px;color:var(--lp-ink-2);line-height:1.5} | |
| 1065 | /* gates */ | |
| 1066 | .lp-gates{display:flex;gap:6px;flex-wrap:wrap;padding:10px 16px;border-bottom:1px solid var(--lp-border)} | |
| 1067 | .lp-gate{font-size:11.5px;font-weight:600;padding:3px 9px;border-radius:20px} | |
| 1068 | .lp-gate-ok{color:var(--lp-green);background:rgba(5,150,105,.09)} | |
| 1069 | .lp-gate-spin{color:var(--lp-brand);background:rgba(67,83,201,.09)} | |
| 1070 | /* auto-merge banner */ | |
| 1071 | .lp-automerge-bar{background:var(--lp-brand);color:#fff;font-size:12px;font-weight:600;text-align:center;padding:8px;letter-spacing:.01em} | |
| 1072 | ||
| 1073 | /* ── trust strip ── */ | |
| f8e5fea | 1074 | .lp-trust-strip{border-bottom:1px solid var(--lp-border);padding:14px 0;background:var(--lp-soft)} |
| 54f2e2a | 1075 | .lp-trust-in{display:flex;align-items:center;gap:20px;flex-wrap:wrap;font-size:13.5px;font-weight:500;color:var(--lp-muted);justify-content:center} |
| f8e5fea | 1076 | .lp-sep{width:1px;height:14px;background:var(--lp-border)} |
| 1077 | ||
| 54f2e2a | 1078 | /* ── impact band ── */ |
| 1079 | .lp-impact-band{padding:52px 0;border-bottom:1px solid var(--lp-border)} | |
| 1080 | .lp-impact-in{display:flex;align-items:center;justify-content:center;gap:0} | |
| 1081 | .lp-impact-stat{text-align:center;padding:0 60px;display:flex;flex-direction:column;align-items:center;gap:6px} | |
| 1082 | .lp-impact-n{font-family:'Inter Tight',sans-serif;font-size:clamp(40px,6vw,68px);font-weight:800;color:var(--lp-ink);letter-spacing:-.03em;line-height:1} | |
| 1083 | .lp-impact-x{color:var(--lp-brand);font-size:.7em} | |
| 1084 | .lp-impact-label{font-size:13.5px;color:var(--lp-muted);font-weight:500} | |
| 1085 | .lp-impact-sep{width:1px;height:64px;background:var(--lp-border);flex-shrink:0} | |
| 1086 | @media(max-width:600px){.lp-impact-in{flex-direction:column;gap:32px}.lp-impact-sep{width:64px;height:1px}.lp-impact-stat{padding:0}} | |
| 1087 | ||
| 1088 | /* ── section header ── */ | |
| f8e5fea | 1089 | .lp-sec-head{max-width:680px;margin-bottom:52px} |
| 1090 | .lp-sec-head .lp-h2{margin:8px 0 16px} | |
| 1091 | ||
| 54f2e2a | 1092 | /* ── live-now ── */ |
| 1093 | .lp-live-dot{color:var(--lp-brand);font-size:9px;margin-right:4px} | |
| f8e5fea | 1094 | .lp-live-grid{display:grid;grid-template-columns:repeat(4,1fr);gap:16px;margin-bottom:32px} |
| 1095 | @media(max-width:1000px){.lp-live-grid{grid-template-columns:repeat(2,1fr)}} | |
| 1096 | @media(max-width:560px){.lp-live-grid{grid-template-columns:1fr}} | |
| 54f2e2a | 1097 | .lp-live-card{background:#fff;border:1px solid var(--lp-border);border-radius:var(--lp-r);padding:20px;box-shadow:var(--lp-shadow);border-left:3px solid transparent;transition:transform .18s,box-shadow .2s} |
| 1098 | .lp-live-card:hover{transform:translateY(-2px);box-shadow:var(--lp-shadow-lg)} | |
| 1099 | .lp-live-card-violet{border-left-color:rgba(124,58,237,.4);background:rgba(124,58,237,.02)} | |
| 1100 | .lp-live-card-green{border-left-color:rgba(5,150,105,.4);background:rgba(5,150,105,.02)} | |
| 1101 | .lp-live-card-brand{border-left-color:rgba(67,83,201,.4);background:rgba(67,83,201,.02)} | |
| 1102 | .lp-live-card-neutral{border-left-color:var(--lp-border)} | |
| 1103 | .lp-live-card-title{font-size:12.5px;font-weight:700;color:var(--lp-ink-2);text-transform:uppercase;letter-spacing:.05em;margin-bottom:14px} | |
| f8e5fea | 1104 | .lp-live-list{list-style:none;display:flex;flex-direction:column;gap:10px} |
| 1105 | .lp-live-empty{font-size:13.5px;color:var(--lp-muted)} | |
| 54f2e2a | 1106 | .lp-live-row{display:flex;flex-direction:column;gap:3px;transition:background .3s} |
| f8e5fea | 1107 | .lp-live-link{font-size:13.5px;color:var(--lp-ink);font-weight:500;line-height:1.4} |
| 1108 | .lp-live-link:hover{color:var(--lp-brand)} | |
| 1109 | .lp-live-num{font-family:'JetBrains Mono',monospace;font-size:11.5px;color:var(--lp-muted)} | |
| 1110 | .lp-live-meta{font-size:12px;color:var(--lp-muted)} | |
| 1111 | .lp-live-bignum{display:flex;align-items:baseline;gap:6px;margin-bottom:10px} | |
| 54f2e2a | 1112 | .lp-live-bignum span:first-child{font-family:'Inter Tight',sans-serif;font-size:32px;font-weight:700;color:var(--lp-ink)} |
| f8e5fea | 1113 | .lp-live-bignum-label{font-size:13.5px;color:var(--lp-muted)} |
| 54f2e2a | 1114 | .lp-live-feedrow{display:flex;align-items:baseline;gap:5px;flex-wrap:wrap;font-size:13px;line-height:1.5} |
| 1115 | .lp-live-rel{color:var(--lp-muted)} | |
| f8e5fea | 1116 | .lp-feed-kind{font-size:11.5px;font-weight:600;padding:2px 6px;border-radius:4px} |
| 1117 | .lp-feed-kind-auto_merge-merged{color:var(--lp-green);background:rgba(5,150,105,.09)} | |
| 1118 | .lp-feed-kind-ai_build-dispatched{color:var(--lp-brand);background:rgba(67,83,201,.09)} | |
| 54f2e2a | 1119 | .lp-feed-kind-ai_review-posted{color:var(--lp-violet);background:rgba(124,58,237,.09)} |
| f8e5fea | 1120 | .lp-live-cta{display:flex;align-items:center;gap:16px} |
| 1121 | ||
| 54f2e2a | 1122 | /* ── platform grid ── */ |
| 1123 | .lp-platform-grid{display:grid;grid-template-columns:repeat(4,1fr);gap:2px;border:1px solid var(--lp-border);border-radius:14px;overflow:hidden;background:var(--lp-border)} | |
| f8e5fea | 1124 | @media(max-width:900px){.lp-platform-grid{grid-template-columns:repeat(2,1fr)}} |
| 1125 | @media(max-width:500px){.lp-platform-grid{grid-template-columns:1fr}} | |
| 54f2e2a | 1126 | .lp-plat-group{background:#fff;padding:22px;display:flex;flex-direction:column;gap:12px;transition:background .15s} |
| 1127 | .lp-plat-group:hover{background:#fafbff} | |
| 1128 | .lp-plat-header{display:flex;align-items:center;gap:8px;margin-bottom:2px} | |
| 1129 | .lp-plat-icon{display:flex;flex-shrink:0} | |
| 1130 | .lp-plat-icon svg{display:block} | |
| 1131 | .lp-plat-title{font-size:13px;font-weight:700;color:var(--lp-ink)} | |
| 1132 | .lp-plat-list{list-style:none;display:flex;flex-direction:column;gap:5px} | |
| 1133 | .lp-plat-list li{font-size:13px;color:var(--lp-ink-2)} | |
| 1134 | ||
| 1135 | /* ── pain table ── */ | |
| 1136 | .lp-pain-table{display:flex;flex-direction:column;border:1px solid var(--lp-border);border-radius:14px;overflow:hidden} | |
| 1137 | .lp-pain-row{display:grid;grid-template-columns:1fr 40px 1fr;align-items:center;gap:16px;padding:20px 24px;border-bottom:1px solid var(--lp-border);transition:background .15s} | |
| f8e5fea | 1138 | .lp-pain-row:last-child{border-bottom:0} |
| 1139 | .lp-pain-row:hover{background:var(--lp-soft)} | |
| 54f2e2a | 1140 | .lp-pain-problem{display:flex;align-items:flex-start;gap:10px;font-size:15px;color:#6b7280} |
| f8e5fea | 1141 | .lp-pain-fix{display:flex;align-items:flex-start;gap:10px;font-size:15px;color:var(--lp-ink);font-weight:500} |
| 1142 | .lp-pain-arrow{font-size:18px;color:var(--lp-muted);text-align:center} | |
| 54f2e2a | 1143 | .lp-pain-icon{font-size:12.5px;font-weight:700;margin-top:3px;flex-shrink:0} |
| 1144 | .lp-pain-no{color:#b91c1c} | |
| f8e5fea | 1145 | .lp-pain-yes{color:var(--lp-green)} |
| 54f2e2a | 1146 | @media(max-width:700px){.lp-pain-row{grid-template-columns:1fr;gap:8px}.lp-pain-arrow{display:none}} |
| f8e5fea | 1147 | |
| 54f2e2a | 1148 | /* ── AI features ── */ |
| 1149 | .lp-ai-features{display:flex;flex-direction:column;gap:72px} | |
| f8e5fea | 1150 | .lp-ai-feature{display:grid;grid-template-columns:1fr 1fr;gap:64px;align-items:center} |
| 1151 | .lp-ai-feature-rev{direction:rtl} | |
| 1152 | .lp-ai-feature-rev>*{direction:ltr} | |
| 1153 | @media(max-width:800px){.lp-ai-feature,.lp-ai-feature-rev{grid-template-columns:1fr;direction:ltr}} | |
| 54f2e2a | 1154 | .lp-ai-n{font-family:'JetBrains Mono',monospace;font-size:11px;font-weight:500;color:var(--lp-muted);margin-bottom:10px;letter-spacing:.04em} |
| 1155 | .lp-ai-title{font-family:'Inter Tight',sans-serif;font-weight:700;font-size:clamp(22px,3vw,30px);line-height:1.12;letter-spacing:-.018em;color:var(--lp-ink);margin-bottom:14px} | |
| f8e5fea | 1156 | .lp-ai-body{font-size:16px;color:var(--lp-ink-2);line-height:1.65;margin-bottom:18px} |
| 54f2e2a | 1157 | .lp-ai-mock{background:#fff;border:1px solid var(--lp-border);border-radius:14px;overflow:hidden;box-shadow:var(--lp-shadow-lg)} |
| 1158 | /* review mock */ | |
| 1159 | .lp-review-mock{display:flex;flex-direction:column;gap:0;font-size:13.5px} | |
| 1160 | .lp-rm-header{display:flex;align-items:center;justify-content:space-between;padding:12px 16px;border-bottom:1px solid var(--lp-border);background:var(--lp-soft)} | |
| 1161 | .lp-rm-label{font-size:12.5px;font-weight:600;color:var(--lp-ink-2)} | |
| 1162 | .lp-rm-steps{padding:14px 16px;display:flex;flex-direction:column;gap:10px} | |
| 1163 | .lp-rm-step{display:flex;align-items:flex-start;gap:10px;font-size:13px;color:var(--lp-ink-2);line-height:1.45} | |
| 1164 | .lp-rm-icon{font-family:'JetBrains Mono',monospace;font-size:12px;color:var(--lp-green);flex-shrink:0;width:14px} | |
| 1165 | .lp-rm-fail{color:var(--lp-red)} | |
| 1166 | .lp-rm-done{opacity:.7} | |
| 1167 | .lp-rm-active{color:var(--lp-ink);font-weight:500} | |
| 1168 | .lp-rm-time{padding:8px 16px;font-size:11.5px;color:var(--lp-muted);border-top:1px solid var(--lp-border);font-family:'JetBrains Mono',monospace} | |
| 1169 | .lp-rm-comment{display:flex;gap:10px;padding:12px 16px;background:rgba(67,83,201,.03)} | |
| 1170 | .lp-rm-ava{width:28px;height:28px;border-radius:50%;background:var(--lp-brand);color:#fff;font-size:12px;font-weight:700;display:flex;align-items:center;justify-content:center;flex-shrink:0;margin-top:2px} | |
| 1171 | .lp-rm-bubble{flex:1;font-size:13px;color:var(--lp-ink-2);line-height:1.5} | |
| 1172 | .lp-rm-file{font-family:'JetBrains Mono',monospace;font-size:11px;color:var(--lp-brand);margin-bottom:4px} | |
| 1173 | .lp-rm-verdict{padding:10px 16px;border-top:1px solid var(--lp-border)} | |
| 1174 | .lp-rm-v-ok{font-size:13px;font-weight:600;color:var(--lp-green)} | |
| 1175 | /* digest mock */ | |
| 1176 | .lp-digest-rows{padding:14px 16px;display:flex;flex-direction:column;gap:9px} | |
| 1177 | .lp-digest-row{display:flex;align-items:flex-start;gap:8px;font-size:13.5px;color:var(--lp-ink-2)} | |
| 1178 | .lp-digest-ok{color:var(--lp-green);font-weight:700;flex-shrink:0} | |
| 1179 | ||
| 1180 | /* ── security cards ── */ | |
| f8e5fea | 1181 | .lp-security-grid{display:grid;grid-template-columns:repeat(3,1fr);gap:16px} |
| 1182 | @media(max-width:900px){.lp-security-grid{grid-template-columns:repeat(2,1fr)}} | |
| 1183 | @media(max-width:560px){.lp-security-grid{grid-template-columns:1fr}} | |
| 54f2e2a | 1184 | .lp-sec-card{background:#fff;border:1px solid var(--lp-border);border-radius:var(--lp-r);padding:24px;box-shadow:var(--lp-shadow);transition:transform .18s,box-shadow .2s} |
| 1185 | .lp-sec-card:hover{transform:translateY(-3px);box-shadow:var(--lp-shadow-lg)} | |
| f8e5fea | 1186 | .lp-sec-card-title{font-size:15px;font-weight:700;color:var(--lp-ink);margin-bottom:10px} |
| 54f2e2a | 1187 | .lp-sec-card-body{font-size:13.5px;color:var(--lp-ink-2);line-height:1.6} |
| f8e5fea | 1188 | |
| 54f2e2a | 1189 | /* ── teams ── */ |
| f8e5fea | 1190 | .lp-teams-grid{display:grid;grid-template-columns:repeat(4,1fr);gap:16px} |
| 1191 | @media(max-width:1000px){.lp-teams-grid{grid-template-columns:repeat(2,1fr)}} | |
| 1192 | @media(max-width:560px){.lp-teams-grid{grid-template-columns:1fr}} | |
| 54f2e2a | 1193 | .lp-team-card{background:#fff;border:1px solid var(--lp-border);border-radius:var(--lp-r);padding:24px;box-shadow:var(--lp-shadow);transition:transform .18s,box-shadow .2s} |
| 1194 | .lp-team-card:hover{transform:translateY(-3px);box-shadow:var(--lp-shadow-lg)} | |
| 1195 | .lp-team-title{font-size:14.5px;font-weight:700;color:var(--lp-ink);margin-bottom:14px;padding-bottom:12px;border-bottom:1px solid var(--lp-border)} | |
| f8e5fea | 1196 | .lp-team-list{list-style:none;display:flex;flex-direction:column;gap:8px} |
| 54f2e2a | 1197 | .lp-team-list li{display:flex;align-items:flex-start;gap:8px;font-size:13.5px;color:var(--lp-ink-2);line-height:1.4} |
| f8e5fea | 1198 | .lp-team-list li span{color:var(--lp-green);font-size:12px;margin-top:2px;flex-shrink:0} |
| 1199 | ||
| 54f2e2a | 1200 | /* ── ecosystem ── */ |
| f8e5fea | 1201 | .lp-eco-grid{display:grid;grid-template-columns:repeat(4,1fr);gap:16px} |
| 1202 | @media(max-width:900px){.lp-eco-grid{grid-template-columns:repeat(2,1fr)}} | |
| 1203 | @media(max-width:500px){.lp-eco-grid{grid-template-columns:1fr}} | |
| 54f2e2a | 1204 | .lp-eco-card{background:#fff;border:1px solid var(--lp-border);border-radius:var(--lp-r);padding:24px;box-shadow:var(--lp-shadow);display:flex;flex-direction:column;gap:10px;transition:transform .18s,box-shadow .2s} |
| 1205 | .lp-eco-card:hover{transform:translateY(-3px);box-shadow:var(--lp-shadow-lg)} | |
| f8e5fea | 1206 | .lp-eco-title{font-size:15px;font-weight:700;color:var(--lp-ink)} |
| 54f2e2a | 1207 | .lp-eco-sub{font-family:'JetBrains Mono',monospace;font-size:11.5px;color:var(--lp-brand)} |
| 1208 | .lp-eco-body{font-size:13.5px;color:var(--lp-ink-2);line-height:1.6;flex:1} | |
| f8e5fea | 1209 | |
| 54f2e2a | 1210 | /* ── comparison ── */ |
| f8e5fea | 1211 | .lp-compare{border:1px solid var(--lp-border);border-radius:14px;overflow:hidden} |
| 54f2e2a | 1212 | .lp-compare-head{display:grid;grid-template-columns:1fr 160px 160px;padding:12px 20px;background:var(--lp-soft);font-size:12.5px;font-weight:700;color:var(--lp-muted);text-transform:uppercase;letter-spacing:.04em;border-bottom:1px solid var(--lp-border)} |
| 1213 | .lp-cmp-row{display:grid;grid-template-columns:1fr 160px 160px;padding:14px 20px;border-bottom:1px solid var(--lp-border);font-size:14px;align-items:center;transition:background .15s} | |
| f8e5fea | 1214 | .lp-cmp-row:last-child{border-bottom:0} |
| 1215 | .lp-cmp-ours{background:rgba(67,83,201,.025)} | |
| 54f2e2a | 1216 | .lp-cmp-ours:hover{background:rgba(67,83,201,.04)} |
| f8e5fea | 1217 | .lp-cmp-feature{color:var(--lp-ink-2)} |
| 54f2e2a | 1218 | .lp-cmp-them{color:var(--lp-muted);font-size:13.5px} |
| 1219 | .lp-cmp-us{color:var(--lp-muted);font-size:13.5px} | |
| 1220 | .lp-cmp-us-hl{color:var(--lp-green);font-weight:700} | |
| f8e5fea | 1221 | .lp-compare-foot{margin-top:16px} |
| 54f2e2a | 1222 | @media(max-width:700px){.lp-compare-head,.lp-cmp-row{grid-template-columns:1fr 90px 90px;font-size:12.5px}} |
| f8e5fea | 1223 | |
| 54f2e2a | 1224 | /* ── pricing ── */ |
| f8e5fea | 1225 | .lp-price-grid{display:grid;grid-template-columns:repeat(3,1fr);gap:16px} |
| 1226 | @media(max-width:800px){.lp-price-grid{grid-template-columns:1fr}} | |
| 54f2e2a | 1227 | .lp-price-card{background:#fff;border:1px solid var(--lp-border);border-radius:14px;padding:28px;box-shadow:var(--lp-shadow);display:flex;flex-direction:column;gap:0;position:relative;transition:transform .18s,box-shadow .2s} |
| 1228 | .lp-price-card:hover{transform:translateY(-3px);box-shadow:var(--lp-shadow-lg)} | |
| 1229 | .lp-price-hl{border-color:var(--lp-brand);box-shadow:0 0 0 1px var(--lp-brand),var(--lp-shadow)} | |
| 1230 | .lp-price-badge{position:absolute;top:-13px;left:50%;transform:translateX(-50%);background:var(--lp-brand);color:#fff;font-size:11.5px;font-weight:700;padding:4px 14px;border-radius:999px} | |
| 1231 | .lp-price-tier{font-size:12.5px;font-weight:700;text-transform:uppercase;letter-spacing:.06em;color:var(--lp-brand);margin-bottom:12px} | |
| f8e5fea | 1232 | .lp-price-amount{display:flex;align-items:baseline;gap:6px;margin-bottom:12px} |
| 54f2e2a | 1233 | .lp-price-num{font-family:'Inter Tight',sans-serif;font-size:36px;font-weight:800;color:var(--lp-ink);letter-spacing:-.02em} |
| 1234 | .lp-price-cad{font-size:13.5px;color:var(--lp-muted)} | |
| 1235 | .lp-price-desc{font-size:13.5px;color:var(--lp-ink-2);line-height:1.6;margin-bottom:20px} | |
| 1236 | .lp-price-features{list-style:none;display:flex;flex-direction:column;gap:8px;margin-bottom:24px;flex:1} | |
| 1237 | .lp-price-features li{display:flex;align-items:flex-start;gap:8px;font-size:13.5px;color:var(--lp-ink-2)} | |
| f8e5fea | 1238 | .lp-price-features li span{color:var(--lp-green);font-size:12px;margin-top:2px} |
| 1239 | .lp-price-foot{margin-top:16px} | |
| 1240 | ||
| 54f2e2a | 1241 | /* ── install ── */ |
| f8e5fea | 1242 | .lp-install-wrap{display:grid;grid-template-columns:1fr 1fr;gap:64px;align-items:center} |
| 1243 | @media(max-width:800px){.lp-install-wrap{grid-template-columns:1fr}} | |
| 1244 | .lp-install-text{display:flex;flex-direction:column} | |
| 1245 | .lp-install-text .lp-kicker{margin-bottom:16px} | |
| 54f2e2a | 1246 | .lp-install-terminal{background:#fff;border:1px solid var(--lp-border);border-radius:14px;overflow:hidden;box-shadow:var(--lp-shadow-lg)} |
| 1247 | .lp-term-bar{display:flex;align-items:center;gap:6px;padding:10px 16px;background:var(--lp-soft);border-bottom:1px solid var(--lp-border)} | |
| f8e5fea | 1248 | .lp-term-dot{width:10px;height:10px;border-radius:50%;background:#d1d5db} |
| 1249 | .lp-term-title{margin-left:8px;font-size:12px;color:var(--lp-muted);font-family:'JetBrains Mono',monospace} | |
| 1250 | .lp-term-body{padding:16px;display:flex;flex-direction:column;gap:8px} | |
| 1251 | .lp-term-line{display:flex;align-items:center;gap:10px;font-family:'JetBrains Mono',monospace;font-size:13px} | |
| 1252 | .lp-term-prompt{color:var(--lp-brand);font-weight:500} | |
| 54f2e2a | 1253 | .lp-term-out{font-family:'JetBrains Mono',monospace;font-size:12.5px;color:var(--lp-muted);padding-left:20px} |
| f8e5fea | 1254 | .lp-term-ok{color:var(--lp-green)} |
| 54f2e2a | 1255 | .lp-copy-btn{margin-left:auto;font-size:12px;font-family:'Inter',sans-serif;font-weight:600;padding:4px 10px;border:1px solid var(--lp-border);border-radius:6px;background:#fff;color:var(--lp-ink-2);cursor:pointer;transition:background .15s,color .15s} |
| f8e5fea | 1256 | .lp-copy-btn:hover{background:var(--lp-soft);color:var(--lp-ink)} |
| 1257 | ||
| 54f2e2a | 1258 | /* ── dark CTA ── */ |
| 1259 | .lp-cta-dark{background:var(--lp-navy);padding:96px 0;border-top:1px solid rgba(255,255,255,.06)} | |
| 1260 | .lp-cta-in{text-align:center;display:flex;flex-direction:column;align-items:center} | |
| 1261 | .lp-kicker-dark{color:rgba(163,178,238,.9)} | |
| 1262 | .lp-cta-h{font-family:'Inter Tight',sans-serif;font-weight:800;font-size:clamp(32px,5vw,56px);line-height:1.06;letter-spacing:-.025em;color:#fff;margin:12px 0 18px} | |
| 1263 | .lp-cta-sub{font-size:18px;color:rgba(255,255,255,.65);max-width:52ch;margin-bottom:36px} | |
| 1264 | .lp-cta-btns{display:flex;gap:12px;flex-wrap:wrap;justify-content:center;margin-bottom:24px} | |
| 1265 | .lp-cta-links-dark{display:flex;gap:12px;align-items:center;font-size:14px} | |
| 1266 | .lp-cta-links-dark a{color:rgba(163,178,238,.85);font-weight:500} | |
| 1267 | .lp-cta-links-dark a:hover{color:#fff} | |
| 1268 | .lp-cta-links-dark span{color:rgba(255,255,255,.2)} | |
| 1269 | ||
| 1270 | /* ── footer ── */ | |
| 1271 | .lp-footer{background:var(--lp-navy);border-top:1px solid rgba(255,255,255,.07);padding:64px 0 0} | |
| 1272 | .lp-footer-in{display:grid;grid-template-columns:260px 1fr;gap:64px;margin-bottom:48px} | |
| f8e5fea | 1273 | @media(max-width:800px){.lp-footer-in{grid-template-columns:1fr;gap:32px}} |
| 1274 | .lp-footer-brand{display:flex;flex-direction:column;gap:12px} | |
| 54f2e2a | 1275 | .lp-footer-tag{font-size:13.5px;color:rgba(255,255,255,.4);line-height:1.6} |
| f8e5fea | 1276 | .lp-footer-cols{display:grid;grid-template-columns:repeat(4,1fr);gap:32px} |
| 1277 | @media(max-width:700px){.lp-footer-cols{grid-template-columns:repeat(2,1fr)}} | |
| 1278 | .lp-footer-col{display:flex;flex-direction:column;gap:10px} | |
| 54f2e2a | 1279 | .lp-footer-col h4{font-size:12.5px;font-weight:700;color:rgba(255,255,255,.9);text-transform:uppercase;letter-spacing:.05em;margin-bottom:4px} |
| 1280 | .lp-footer-col a{font-size:13.5px;color:rgba(255,255,255,.45);transition:color .15s} | |
| 1281 | .lp-footer-col a:hover{color:rgba(255,255,255,.9)} | |
| 1282 | .lp-footer-bottom{border-top:1px solid rgba(255,255,255,.07);padding:20px 0} | |
| 1283 | .lp-footer-bottom-in{display:flex;justify-content:space-between;align-items:center;font-size:13px;color:rgba(255,255,255,.3)} | |
| f8e5fea | 1284 | `; |