CodeIssuesDiscussionsWikiPull RequestsProjectsCommitsActionsReleasesContributorsPulse● GatesSecuritySettingsDeploymentsPipelineInsightsAgents✨ Explain✨ Ask AI✨ Workspace✨ Spec✨ Tests▓ Debt Map✨ NL Search🏛 Archaeology
Blame · Line-by-line history
pricing.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.
| 5f2e749 | 1 | /** |
| 2 | * Block L8 — public `/pricing` page. | |
| 3 | * | |
| 4 | * Anonymous-safe GET /pricing. Reads the real plan rows from | |
| 5 | * `billing_plans` (or `FALLBACK_PLANS` when seeds aren't loaded yet — both | |
| 6 | * mirror migration 0020) so the price column never drifts from the actual | |
| 7 | * billing config. This route is mounted BEFORE `routes/marketing.tsx` in | |
| 8 | * `app.tsx` so the new editorial layout wins; the legacy marketing /pricing | |
| 9 | * remains as a safety net. | |
| 10 | * | |
| 11 | * Anchors: | |
| 12 | * #free → "What you get on the free tier" block | |
| 13 | * #self-host → Self-host vs Cloud comparison | |
| 14 | * #faq → Frequently asked questions | |
| 15 | * | |
| 16 | * Pure presentational — no billing logic is created here. The page only | |
| 17 | * surfaces what `src/lib/billing.ts` already ships. | |
| 18 | */ | |
| 19 | ||
| 20 | import { Hono } from "hono"; | |
| 21 | import type { FC } from "hono/jsx"; | |
| 22 | import { Layout } from "../views/layout"; | |
| 23 | import { softAuth } from "../middleware/auth"; | |
| 24 | import type { AuthEnv } from "../middleware/auth"; | |
| 25 | import { formatPrice, listPlans } from "../lib/billing"; | |
| 26 | ||
| 27 | const pricing = new Hono<AuthEnv>(); | |
| 28 | pricing.use("*", softAuth); | |
| 29 | ||
| 30 | // ---- Per-slug copy: tagline + included-bullet list ------------------------ | |
| 31 | // Indexed by the seeded plan slugs. Anything not in this map falls back to | |
| 32 | // generic copy derived from the plan's numeric limits so we never miss a | |
| 33 | // row even if a future migration adds a new tier. | |
| 34 | const PLAN_COPY: Record< | |
| 35 | string, | |
| 36 | { tagline: string; supportTier: string } | |
| 37 | > = { | |
| 38 | free: { | |
| a65653b | 39 | tagline: "AI review on every PR, auto-merge, and spec-to-PR — on unlimited public repos, forever.", |
| 5f2e749 | 40 | supportTier: "Community support", |
| 41 | }, | |
| 42 | pro: { | |
| a65653b | 43 | tagline: "Private repos + higher AI quota. AI review, auto-merge, and spec-to-PR for working developers shipping daily.", |
| 5f2e749 | 44 | supportTier: "Email support, priority AI queue", |
| 45 | }, | |
| 46 | team: { | |
| a65653b | 47 | tagline: "Full AI suite for production teams — AI review, auto-merge when gates pass, and spec-to-PR at scale.", |
| 5f2e749 | 48 | supportTier: "Slack channel + 24h response", |
| 49 | }, | |
| 50 | enterprise: { | |
| a65653b | 51 | tagline: "SSO, audit log, on-prem deploy — with the full AI suite: review, auto-merge, and spec-to-PR.", |
| 5f2e749 | 52 | supportTier: "24/7 incident response + DPA", |
| 53 | }, | |
| 54 | }; | |
| 55 | ||
| 56 | pricing.get("/pricing", async (c) => { | |
| 57 | const user = c.get("user"); | |
| 58 | const plans = await listPlans(); | |
| 59 | return c.html( | |
| 5acce80 | 60 | <Layout |
| 61 | title="Pricing — Gluecron" | |
| 62 | user={user} | |
| 63 | description="Simple pricing for AI-native git hosting. Unlimited repos, AI review, auto-merge, spec-to-PR. Start free." | |
| 64 | ogTitle="Pricing — Gluecron" | |
| 65 | ogDescription="Simple pricing for AI-native git hosting. Unlimited repos, AI review, auto-merge, spec-to-PR. Start free." | |
| 66 | twitterCard="summary" | |
| 67 | > | |
| 5f2e749 | 68 | <PricingPage plans={plans} loggedIn={!!user} /> |
| 69 | </Layout> | |
| 70 | ); | |
| 71 | }); | |
| 72 | ||
| 73 | interface Plan { | |
| 74 | slug: string; | |
| 75 | name: string; | |
| 76 | priceCents: number; | |
| 77 | repoLimit: number; | |
| 78 | storageMbLimit: number; | |
| 79 | aiTokensMonthly: number; | |
| 80 | bandwidthGbMonthly: number; | |
| 81 | privateRepos: boolean; | |
| 82 | } | |
| 83 | ||
| 84 | const PricingPage: FC<{ plans: Plan[]; loggedIn: boolean }> = ({ | |
| 85 | plans, | |
| 86 | loggedIn, | |
| 87 | }) => ( | |
| 88 | <> | |
| 89 | <style dangerouslySetInnerHTML={{ __html: pricingCss }} /> | |
| 90 | <div class="pl-root"> | |
| 91 | {/* ------------------- Hero ------------------- */} | |
| 92 | <header class="pl-hero"> | |
| 8929744 | 93 | <div class="pl-hero-hairline" aria-hidden="true" /> |
| 94 | <div class="pl-hero-orb" aria-hidden="true" /> | |
| 5f2e749 | 95 | <div class="eyebrow">Pricing</div> |
| a65653b | 96 | <p class="pl-hero-speed">Spec to PR in 90 seconds.</p> |
| 5f2e749 | 97 | <h1 class="display pl-hero-title"> |
| 13ac035 | 98 | One subscription.{" "} |
| 99 | <span class="gradient-text"> | |
| 100 | Replaces three on GitHub. | |
| 101 | </span> | |
| 5f2e749 | 102 | </h1> |
| 103 | <p class="pl-hero-sub"> | |
| a65653b | 104 | AI review fires in ~8s. Merges the instant gates pass. Spec to PR in |
| 105 | 90 seconds. What costs $89/user on GitHub + Copilot + Advanced | |
| 13ac035 | 106 | Security starts at $0 here. |
| 5f2e749 | 107 | </p> |
| 108 | <div class="pl-hero-jumps"> | |
| 13ac035 | 109 | <a href="#compare" class="pl-jump">Bundle math vs GitHub →</a> |
| 110 | <a href="#free" class="pl-jump">What's free</a> | |
| 5f2e749 | 111 | <a href="#self-host" class="pl-jump">Self-host vs Cloud</a> |
| 112 | <a href="#faq" class="pl-jump">FAQ</a> | |
| 113 | </div> | |
| 114 | </header> | |
| 115 | ||
| 116 | {/* ------------------- Plan cards ------------------- */} | |
| 117 | <section class="pl-plans stagger"> | |
| 118 | {plans.map((p) => ( | |
| 119 | <PlanCard plan={p} loggedIn={loggedIn} /> | |
| 120 | ))} | |
| 121 | </section> | |
| 122 | ||
| a65653b | 123 | {/* ------------------- Included in every plan ------------------- */} |
| 124 | <section class="pl-section pl-every-plan"> | |
| 125 | <div class="pl-every-plan-inner"> | |
| 126 | <div class="pl-every-plan-label">Included in every plan</div> | |
| 127 | <ul class="pl-every-plan-list"> | |
| 128 | <li>AI review on every PR</li> | |
| 129 | <li>Auto-merge when gates pass</li> | |
| 130 | <li>Spec to PR in 90 seconds</li> | |
| 131 | <li>Push Watch live stream</li> | |
| 132 | </ul> | |
| 133 | </div> | |
| 134 | </section> | |
| 135 | ||
| 13ac035 | 136 | {/* ------------------- Bundle math vs GitHub ------------------- */} |
| 137 | <section id="compare" class="pl-section pl-compare"> | |
| 138 | <div class="section-header"> | |
| 139 | <div class="eyebrow">Bundle math</div> | |
| 140 | <h2> | |
| 141 | What you'd actually pay on GitHub | |
| 142 | <span class="gradient-text"> for the same features.</span> | |
| 143 | </h2> | |
| 144 | <p> | |
| 145 | GitHub charges separately for Copilot, Advanced Security, and the | |
| 146 | base plan. Gluecron bundles everything. Here's the receipt. | |
| 147 | </p> | |
| 148 | </div> | |
| 149 | <div class="pl-compare-grid"> | |
| 150 | <div class="pl-compare-col"> | |
| 151 | <div class="pl-compare-name">GitHub stack</div> | |
| 152 | <div class="pl-compare-price">$89<span class="pl-compare-per">/user/mo</span></div> | |
| 153 | <ul class="pl-compare-feats"> | |
| 154 | <li><span class="pl-compare-bullet">$21</span> GitHub Enterprise (per-user)</li> | |
| 155 | <li><span class="pl-compare-bullet">$19</span> GitHub Copilot Business</li> | |
| 156 | <li><span class="pl-compare-bullet">$49</span> GitHub Advanced Security add-on</li> | |
| 157 | <li class="pl-compare-missing">— No AI auto-merge</li> | |
| 158 | <li class="pl-compare-missing">— No AI incident responder</li> | |
| 159 | <li class="pl-compare-missing">— No AI spec-to-PR</li> | |
| 160 | <li class="pl-compare-missing">— No MCP-native integration</li> | |
| 161 | </ul> | |
| 162 | </div> | |
| 163 | <div class="pl-compare-col pl-compare-us"> | |
| 164 | <div class="pl-compare-name">Gluecron Team</div> | |
| 165 | <div class="pl-compare-price">$29<span class="pl-compare-per">/user/mo</span></div> | |
| 166 | <ul class="pl-compare-feats"> | |
| 167 | <li><span class="pl-compare-check">✓</span> Unlimited private repos + git host</li> | |
| 168 | <li><span class="pl-compare-check">✓</span> AI code review on every PR (Claude Sonnet)</li> | |
| 169 | <li><span class="pl-compare-check">✓</span> AI auto-merge when gates pass</li> | |
| 170 | <li><span class="pl-compare-check">✓</span> AI incident responder on deploy failure</li> | |
| 171 | <li><span class="pl-compare-check">✓</span> AI spec-to-PR (label an issue, get a PR)</li> | |
| 172 | <li><span class="pl-compare-check">✓</span> AI completion + commit messages + tests</li> | |
| 173 | <li><span class="pl-compare-check">✓</span> MCP server — drive it from Claude Desktop</li> | |
| 174 | <li><span class="pl-compare-check">✓</span> SSO, audit log, branch protection, rulesets</li> | |
| 175 | </ul> | |
| 176 | </div> | |
| 177 | </div> | |
| 178 | <p class="pl-compare-footer"> | |
| 179 | For a 10-person team, the math is{" "} | |
| 180 | <strong>$10,680/year on GitHub vs $3,480/year on Gluecron</strong> | |
| 181 | {" — "} | |
| 182 | and you get the AI features that don't exist on GitHub at any price. | |
| 183 | </p> | |
| 184 | </section> | |
| 185 | ||
| 5f2e749 | 186 | {/* ------------------- What's on the free tier ------------------- */} |
| 187 | <section id="free" class="pl-section pl-free"> | |
| 188 | <div class="section-header"> | |
| 189 | <div class="eyebrow">Free tier</div> | |
| 190 | <h2>Everything below is yours on the free tier.</h2> | |
| 191 | <p> | |
| 192 | All the AI features. Not a "try it for 14 days" trial. Not a | |
| 193 | "core features" stub. The whole Claude-powered platform — on | |
| 194 | unlimited public repos, forever. | |
| 195 | </p> | |
| 196 | </div> | |
| 197 | <ul class="pl-free-grid"> | |
| 198 | <FreeItem label="Unlimited public repos" /> | |
| 199 | <FreeItem label="AI code review on every PR (Sonnet 4)" /> | |
| 200 | <FreeItem label="AI auto-merge when checks pass (K2)" /> | |
| 201 | <FreeItem label="ai:build label → spec-to-PR (K3)" /> | |
| 202 | <FreeItem label="Sleep Mode digest (L1)" /> | |
| 203 | <FreeItem label="AI hours saved counter (L9)" /> | |
| 204 | <FreeItem label="MCP server access (K1)" /> | |
| 205 | <FreeItem label="Claude Code skill bundle (L7)" /> | |
| 206 | <FreeItem label="One-command install" /> | |
| 207 | <FreeItem label="GitHub OIDC sign-in" /> | |
| 208 | <FreeItem label="Webhooks + REST API v2 + GraphQL" /> | |
| 209 | <FreeItem label="Package registry + Pages hosting" /> | |
| 210 | </ul> | |
| 211 | </section> | |
| 212 | ||
| 213 | {/* ------------------- Self-host vs Cloud ------------------- */} | |
| 214 | <section id="self-host" class="pl-section"> | |
| 215 | <div class="section-header"> | |
| 216 | <div class="eyebrow">Two ways to run it</div> | |
| 217 | <h2>Self-host on your metal. Or let us run it.</h2> | |
| 218 | <p> | |
| 219 | Same product, same code, same Claude-powered features. The only | |
| 220 | difference is who pays the electricity bill. | |
| 221 | </p> | |
| 222 | </div> | |
| 223 | <div class="pl-host-grid"> | |
| 224 | <div class="pl-host-col"> | |
| 225 | <div class="pl-host-name">Self-host</div> | |
| 226 | <div class="pl-host-price">Free forever</div> | |
| 227 | <ul class="pl-host-feats"> | |
| 228 | <li>Free forever — no license, no per-seat fee</li> | |
| 229 | <li>Your database, your disk, your control</li> | |
| 230 | <li>You pay your Anthropic API key directly</li> | |
| 231 | <li>Run via <code>curl gluecron.com/install</code></li> | |
| 232 | <li>Or the Hetzner bootstrap script in 30 seconds</li> | |
| 233 | </ul> | |
| 234 | <a href="/install" class="btn btn-secondary btn-block pl-host-cta"> | |
| 235 | Self-host guide | |
| 236 | </a> | |
| 237 | </div> | |
| 238 | <div class="pl-host-col pl-host-cloud"> | |
| 239 | <div class="pl-host-name">Gluecron Cloud</div> | |
| 240 | <div class="pl-host-price">From $0/mo</div> | |
| 241 | <ul class="pl-host-feats"> | |
| 242 | <li>Managed — we run the server, you push code</li> | |
| 243 | <li>Opinionated stack, zero ops on your end</li> | |
| 244 | <li>Automatic upgrades to every new block</li> | |
| 245 | <li>Support included on paid plans</li> | |
| 246 | <li>Plan-based pricing, no surprise overage</li> | |
| 247 | </ul> | |
| 248 | <a | |
| 249 | href={loggedIn ? "/settings/billing" : "/register?next=/settings/billing"} | |
| 250 | class="btn btn-primary btn-block pl-host-cta" | |
| 251 | > | |
| 252 | Start on Cloud | |
| 253 | </a> | |
| 254 | </div> | |
| 255 | </div> | |
| 256 | </section> | |
| 257 | ||
| 258 | {/* ------------------- FAQ ------------------- */} | |
| 259 | <section id="faq" class="pl-section"> | |
| 260 | <div class="section-header"> | |
| 261 | <div class="eyebrow">Questions</div> | |
| 262 | <h2>The fine print, in plain English.</h2> | |
| 263 | </div> | |
| 264 | <div class="pl-faq"> | |
| 265 | <FaqItem | |
| 266 | q="Is it really free? What's the catch?" | |
| 267 | a="Really free. The free tier exists because we want every Claude-curious developer to try Gluecron without a credit card. The catch — if you can call it that — is that we hope you'll upgrade to Pro once you're shipping production traffic and need higher AI quotas." | |
| 268 | /> | |
| 269 | <FaqItem | |
| 270 | q="Do I need to bring my own Anthropic API key on the free tier?" | |
| 271 | a="No. The free tier includes a generous monthly AI quota powered by our keys. If you'd rather use your own key (for cost control or enterprise rate limits), you can plug it in at /settings — Pro and above can route AI through your account." | |
| 272 | /> | |
| 273 | <FaqItem | |
| 274 | q="What happens when I exceed my plan's quota?" | |
| 275 | a="AI features degrade gracefully — git push, hosting, and gates keep working. AI suggestions queue at the back of the line until the next cycle. We never auto-bill you for overage or auto-upgrade your plan." | |
| 276 | /> | |
| 277 | <FaqItem | |
| 278 | q="Can I migrate from GitHub for free?" | |
| 5472bea | 279 | a="Yes. The migration tool is on every tier, free included. Point it at a GitHub repo URL and we import code, issues, PRs, and releases in one shot. No vendor lock — you can migrate back the same way." |
| 5f2e749 | 280 | /> |
| 281 | <FaqItem | |
| 282 | q="Does the free tier include private repos?" | |
| 283 | a="The free tier is built around unlimited public repos. Private repos start on the Pro plan — that's the main paid-tier perk along with the higher AI quota. If you're self-hosting, all repos are private by default and there's no plan to worry about." | |
| 284 | /> | |
| 285 | </div> | |
| 286 | </section> | |
| 287 | ||
| 288 | {/* ------------------- CTA ------------------- */} | |
| 289 | <section class="pl-section pl-cta-wrap"> | |
| 290 | <div class="pl-cta"> | |
| 291 | <h2 class="pl-cta-title"> | |
| 292 | Ready to push your first repo? | |
| 293 | </h2> | |
| 294 | <p class="pl-cta-sub"> | |
| 295 | Free, no credit card, full AI suite from minute one. | |
| 296 | </p> | |
| 297 | <div class="pl-cta-buttons"> | |
| 298 | <a href="/register" class="btn btn-primary btn-xl"> | |
| 299 | Start free | |
| 300 | </a> | |
| 301 | <a href="/vs-github" class="btn btn-ghost btn-xl"> | |
| 302 | Compare to GitHub | |
| 303 | </a> | |
| 304 | </div> | |
| 305 | </div> | |
| 306 | </section> | |
| 307 | </div> | |
| 308 | </> | |
| 309 | ); | |
| 310 | ||
| 311 | // ---- Sub-components ------------------------------------------------------- | |
| 312 | ||
| 313 | const PlanCard: FC<{ plan: Plan; loggedIn: boolean }> = ({ plan, loggedIn }) => { | |
| 314 | const copy = PLAN_COPY[plan.slug] || { | |
| 315 | tagline: `${plan.name} plan.`, | |
| 316 | supportTier: "Support included", | |
| 317 | }; | |
| 318 | const href = loggedIn | |
| 319 | ? `/settings/billing?plan=${plan.slug}` | |
| 320 | : `/register?next=/settings/billing?plan=${plan.slug}`; | |
| 321 | const isPro = plan.slug === "pro"; | |
| 322 | return ( | |
| 323 | <div class={`pl-card${isPro ? " pl-card-hl" : ""}`}> | |
| 324 | {isPro && <div class="pl-card-badge">Most popular</div>} | |
| 325 | <div class="pl-card-name">{plan.name}</div> | |
| 326 | <div class="pl-card-price"> | |
| 327 | <span class="pl-card-num">{formatPrice(plan.priceCents)}</span> | |
| 328 | </div> | |
| 329 | <p class="pl-card-tag">{copy.tagline}</p> | |
| 330 | <ul class="pl-card-feats"> | |
| 331 | <li> | |
| 332 | <span class="pl-check">{"✓"}</span> | |
| 333 | {plan.repoLimit.toLocaleString()} repos | |
| 334 | {plan.privateRepos ? " (public + private)" : " (public only)"} | |
| 335 | </li> | |
| 336 | <li> | |
| 337 | <span class="pl-check">{"✓"}</span> | |
| 338 | {plan.aiTokensMonthly.toLocaleString()} AI tokens / month | |
| 339 | </li> | |
| 340 | <li> | |
| 341 | <span class="pl-check">{"✓"}</span> | |
| 342 | {plan.storageMbLimit.toLocaleString()} MB storage | |
| 343 | </li> | |
| 344 | <li> | |
| 345 | <span class="pl-check">{"✓"}</span> | |
| 346 | {plan.bandwidthGbMonthly.toLocaleString()} GB bandwidth / month | |
| 347 | </li> | |
| 348 | <li> | |
| 349 | <span class="pl-check">{"✓"}</span> | |
| 350 | {copy.supportTier} | |
| 351 | </li> | |
| 352 | </ul> | |
| 353 | <a | |
| 354 | href={href} | |
| 355 | class={`btn ${isPro ? "btn-primary" : "btn-secondary"} btn-block pl-card-cta`} | |
| 356 | > | |
| 357 | Choose {plan.name} | |
| 358 | </a> | |
| 359 | </div> | |
| 360 | ); | |
| 361 | }; | |
| 362 | ||
| 363 | const FreeItem: FC<{ label: string }> = ({ label }) => ( | |
| 364 | <li class="pl-free-item"> | |
| 365 | <span class="pl-free-check">{"✓"}</span> | |
| 366 | <span>{label}</span> | |
| 367 | </li> | |
| 368 | ); | |
| 369 | ||
| 370 | const FaqItem: FC<{ q: string; a: string }> = ({ q, a }) => ( | |
| 371 | <details class="pl-faq-item"> | |
| 372 | <summary class="pl-faq-q"> | |
| 373 | <span>{q}</span> | |
| 374 | <span class="pl-faq-toggle" aria-hidden="true">{"+"}</span> | |
| 375 | </summary> | |
| 376 | <p class="pl-faq-a">{a}</p> | |
| 377 | </details> | |
| 378 | ); | |
| 379 | ||
| 380 | // ---- Styles (scoped under .pl-) ------------------------------------------- | |
| 381 | ||
| 382 | const pricingCss = ` | |
| 383 | .pl-root { max-width: 1180px; margin: 0 auto; padding: 0 16px; } | |
| 384 | ||
| 385 | /* Hero */ | |
| 386 | .pl-hero { | |
| 387 | text-align: center; | |
| 388 | padding: var(--s-16) 0 var(--s-10); | |
| 389 | max-width: 920px; | |
| 390 | margin: 0 auto; | |
| 391 | position: relative; | |
| 392 | } | |
| 393 | .pl-hero::before { | |
| 394 | content: ''; | |
| 395 | position: absolute; | |
| 396 | top: 0; left: 50%; | |
| 397 | transform: translateX(-50%); | |
| 398 | width: 70%; height: 60%; | |
| 6fd5915 | 399 | background: radial-gradient(ellipse at center, rgba(91,110,232,0.14), transparent 65%); |
| 5f2e749 | 400 | z-index: -1; |
| 401 | pointer-events: none; | |
| 402 | } | |
| 8929744 | 403 | /* 2026 polish — gradient hairline accent across the top of the hero |
| 404 | (matches /admin, /admin/ops, error pages). */ | |
| 405 | .pl-hero-hairline { | |
| 406 | position: absolute; | |
| 407 | top: 0; left: 8%; right: 8%; | |
| 408 | height: 2px; | |
| 6fd5915 | 409 | background: linear-gradient(90deg, transparent 0%, #5b6ee8 30%, #5f8fa0 70%, transparent 100%); |
| 8929744 | 410 | opacity: 0.65; |
| 411 | pointer-events: none; | |
| 412 | z-index: 0; | |
| 413 | border-radius: 2px; | |
| 414 | } | |
| 415 | /* 2026 polish — soft animated radial orb behind the headline. */ | |
| 416 | .pl-hero-orb { | |
| 417 | position: absolute; | |
| 418 | top: 8%; left: 50%; | |
| 419 | transform: translateX(-50%); | |
| 420 | width: 420px; height: 420px; | |
| 6fd5915 | 421 | background: radial-gradient(circle, rgba(91,110,232,0.20), rgba(95,143,160,0.10) 45%, transparent 70%); |
| 8929744 | 422 | filter: blur(80px); |
| 423 | opacity: 0.7; | |
| 424 | pointer-events: none; | |
| 425 | z-index: -1; | |
| 426 | animation: plHeroOrb 16s ease-in-out infinite; | |
| 427 | } | |
| 428 | @keyframes plHeroOrb { | |
| 429 | 0%, 100% { transform: translateX(-50%) scale(1); opacity: 0.55; } | |
| 430 | 50% { transform: translateX(-50%) scale(1.12); opacity: 0.85; } | |
| 431 | } | |
| 432 | @media (prefers-reduced-motion: reduce) { | |
| 433 | .pl-hero-orb { animation: none; } | |
| 434 | } | |
| 5f2e749 | 435 | .pl-hero .eyebrow { justify-content: center; margin: 0 auto var(--s-4); } |
| a65653b | 436 | .pl-hero-speed { |
| 437 | font-family: var(--font-mono); | |
| 438 | font-size: clamp(13px, 1.4vw, 16px); | |
| 439 | font-weight: 600; | |
| 440 | letter-spacing: 0.04em; | |
| 441 | color: var(--accent); | |
| 442 | margin: 0 0 var(--s-3); | |
| 443 | text-transform: uppercase; | |
| 444 | } | |
| 5f2e749 | 445 | .pl-hero-title { |
| 446 | font-size: clamp(36px, 6.5vw, 72px); | |
| 447 | line-height: 1.02; | |
| 448 | letter-spacing: -0.038em; | |
| 449 | margin: 0 0 var(--s-5); | |
| 450 | } | |
| 451 | .pl-hero-sub { | |
| 452 | font-size: clamp(15px, 1.5vw, 18px); | |
| 453 | color: var(--text-muted); | |
| 454 | max-width: 640px; | |
| 455 | margin: 0 auto; | |
| 456 | line-height: 1.55; | |
| 457 | } | |
| 458 | .pl-hero-jumps { | |
| 459 | display: flex; | |
| 460 | gap: 18px; | |
| 461 | justify-content: center; | |
| 462 | flex-wrap: wrap; | |
| 463 | margin-top: var(--s-7); | |
| 464 | } | |
| 465 | .pl-jump { | |
| 466 | font-family: var(--font-mono); | |
| 467 | font-size: 12px; | |
| 468 | color: var(--text-muted); | |
| 469 | text-decoration: none; | |
| 470 | padding: 6px 12px; | |
| 471 | border: 1px solid var(--border-subtle); | |
| 472 | border-radius: var(--r-full); | |
| 473 | transition: color var(--t-fast) var(--ease), border-color var(--t-fast) var(--ease); | |
| 474 | } | |
| 6fd5915 | 475 | .pl-jump:hover { color: var(--accent); border-color: rgba(91,110,232,0.35); } |
| 5f2e749 | 476 | |
| 477 | /* Plan cards */ | |
| 478 | .pl-plans { | |
| 479 | display: grid; | |
| 480 | grid-template-columns: repeat(4, 1fr); | |
| 481 | gap: 14px; | |
| 482 | margin: var(--s-10) auto var(--s-14); | |
| 483 | align-items: stretch; | |
| 484 | } | |
| 485 | .pl-card { | |
| 486 | position: relative; | |
| 487 | background: var(--bg-elevated); | |
| 488 | border: 1px solid var(--border); | |
| 489 | border-radius: var(--r-lg); | |
| 490 | padding: var(--s-7) var(--s-6); | |
| 491 | display: flex; | |
| 492 | flex-direction: column; | |
| 493 | gap: var(--s-4); | |
| 494 | transition: border-color var(--t-base) var(--ease), transform var(--t-base) var(--ease-out-quart); | |
| 495 | } | |
| 496 | .pl-card:hover { border-color: var(--border-strong); transform: translateY(-3px); } | |
| 497 | .pl-card-hl { | |
| 6fd5915 | 498 | border-color: rgba(91,110,232,0.40); |
| 499 | box-shadow: var(--elev-2), 0 0 0 1px rgba(91,110,232,0.30); | |
| 5f2e749 | 500 | background: |
| 6fd5915 | 501 | linear-gradient(180deg, rgba(91,110,232,0.05), transparent 50%), |
| 5f2e749 | 502 | var(--bg-elevated); |
| 503 | } | |
| 504 | .pl-card-badge { | |
| 505 | position: absolute; | |
| 506 | top: -10px; | |
| 507 | left: 50%; | |
| 508 | transform: translateX(-50%); | |
| 509 | padding: 3px 12px; | |
| 510 | background: var(--accent-gradient); | |
| 511 | color: #fff; | |
| 512 | font-family: var(--font-mono); | |
| 513 | font-size: 10px; | |
| 514 | letter-spacing: 0.1em; | |
| 515 | text-transform: uppercase; | |
| 516 | font-weight: 600; | |
| 517 | border-radius: var(--r-full); | |
| 6fd5915 | 518 | box-shadow: 0 4px 14px -2px rgba(91,110,232,0.45); |
| 5f2e749 | 519 | white-space: nowrap; |
| 520 | } | |
| 521 | .pl-card-name { | |
| 522 | font-family: var(--font-mono); | |
| 523 | font-size: 11px; | |
| 524 | text-transform: uppercase; | |
| 525 | letter-spacing: 0.16em; | |
| 526 | color: var(--text-muted); | |
| 527 | } | |
| 528 | .pl-card-price { display: flex; align-items: baseline; gap: 6px; } | |
| 529 | .pl-card-num { | |
| 530 | font-family: var(--font-display); | |
| 531 | font-size: 32px; | |
| 532 | font-weight: 600; | |
| 533 | letter-spacing: -0.03em; | |
| 534 | color: var(--text-strong); | |
| 535 | } | |
| 536 | .pl-card-tag { | |
| 537 | font-size: var(--t-sm); | |
| 538 | color: var(--text-muted); | |
| 539 | line-height: 1.5; | |
| 540 | margin: 0; | |
| 541 | } | |
| 542 | .pl-card-feats { | |
| 543 | list-style: none; | |
| 544 | padding: 0; | |
| 545 | margin: 0; | |
| 546 | display: flex; | |
| 547 | flex-direction: column; | |
| 548 | gap: 7px; | |
| 549 | font-size: 13px; | |
| 550 | color: var(--text); | |
| 551 | } | |
| 552 | .pl-card-feats li { | |
| 553 | display: flex; | |
| 554 | align-items: flex-start; | |
| 555 | gap: 9px; | |
| 556 | line-height: 1.45; | |
| 557 | } | |
| 558 | .pl-check { | |
| 559 | color: var(--accent); | |
| 560 | font-weight: 600; | |
| 561 | flex-shrink: 0; | |
| 562 | line-height: 1.45; | |
| 563 | } | |
| 564 | .pl-card-cta { margin-top: auto; } | |
| 565 | ||
| 566 | /* Section base */ | |
| 567 | .pl-section { margin: var(--s-14) auto; } | |
| 568 | ||
| 569 | /* Free-tier block */ | |
| 570 | .pl-free-grid { | |
| 571 | list-style: none; | |
| 572 | padding: 0; | |
| 573 | margin: var(--s-6) auto 0; | |
| 574 | display: grid; | |
| 575 | grid-template-columns: repeat(2, 1fr); | |
| 576 | gap: 10px 32px; | |
| 577 | max-width: 880px; | |
| 578 | } | |
| 579 | .pl-free-item { | |
| 580 | display: flex; | |
| 581 | align-items: flex-start; | |
| 582 | gap: 10px; | |
| 583 | padding: 12px 16px; | |
| 584 | background: var(--bg-elevated); | |
| 585 | border: 1px solid var(--border-subtle); | |
| 586 | border-radius: var(--r); | |
| 587 | font-size: var(--t-sm); | |
| 588 | color: var(--text); | |
| 589 | line-height: 1.45; | |
| 590 | transition: border-color var(--t-fast) var(--ease); | |
| 591 | } | |
| 6fd5915 | 592 | .pl-free-item:hover { border-color: rgba(91,110,232,0.35); } |
| 5f2e749 | 593 | .pl-free-check { |
| 594 | color: var(--green); | |
| 595 | font-weight: 700; | |
| 596 | flex-shrink: 0; | |
| 597 | } | |
| 598 | ||
| 599 | /* Self-host vs Cloud */ | |
| 600 | .pl-host-grid { | |
| 601 | display: grid; | |
| 602 | grid-template-columns: 1fr 1fr; | |
| 603 | gap: 16px; | |
| 604 | max-width: 920px; | |
| 605 | margin: 0 auto; | |
| 606 | } | |
| 607 | .pl-host-col { | |
| 608 | background: var(--bg-elevated); | |
| 609 | border: 1px solid var(--border); | |
| 610 | border-radius: var(--r-lg); | |
| 611 | padding: var(--s-7) var(--s-6); | |
| 612 | display: flex; | |
| 613 | flex-direction: column; | |
| 614 | gap: var(--s-4); | |
| 615 | } | |
| 616 | .pl-host-cloud { | |
| 6fd5915 | 617 | border-color: rgba(91,110,232,0.35); |
| 618 | box-shadow: 0 0 0 1px rgba(91,110,232,0.20); | |
| 5f2e749 | 619 | background: |
| 6fd5915 | 620 | linear-gradient(180deg, rgba(91,110,232,0.04), transparent 60%), |
| 5f2e749 | 621 | var(--bg-elevated); |
| 622 | } | |
| 623 | .pl-host-name { | |
| 624 | font-family: var(--font-mono); | |
| 625 | font-size: 11px; | |
| 626 | text-transform: uppercase; | |
| 627 | letter-spacing: 0.16em; | |
| 628 | color: var(--text-muted); | |
| 629 | } | |
| 630 | .pl-host-price { | |
| 631 | font-family: var(--font-display); | |
| 632 | font-size: 28px; | |
| 633 | font-weight: 600; | |
| 634 | letter-spacing: -0.025em; | |
| 635 | color: var(--text-strong); | |
| 636 | } | |
| 637 | .pl-host-feats { | |
| 638 | list-style: none; | |
| 639 | padding: 0; | |
| 640 | margin: 0; | |
| 641 | display: flex; | |
| 642 | flex-direction: column; | |
| 643 | gap: 8px; | |
| 644 | font-size: var(--t-sm); | |
| 645 | color: var(--text); | |
| 646 | } | |
| 647 | .pl-host-feats li { | |
| 648 | display: flex; | |
| 649 | gap: 9px; | |
| 650 | line-height: 1.5; | |
| 651 | } | |
| 652 | .pl-host-feats li::before { | |
| 653 | content: '→'; | |
| 654 | color: var(--accent); | |
| 655 | flex-shrink: 0; | |
| 656 | } | |
| 657 | .pl-host-feats code { | |
| 658 | background: var(--bg-secondary); | |
| 659 | border: 1px solid var(--border-subtle); | |
| 660 | padding: 1px 6px; | |
| 661 | border-radius: 4px; | |
| 662 | font-size: 11.5px; | |
| 663 | font-family: var(--font-mono); | |
| 664 | color: var(--accent); | |
| 665 | } | |
| 666 | .pl-host-cta { margin-top: auto; } | |
| 667 | ||
| 13ac035 | 668 | /* ------------------- Bundle-math comparison (vs GitHub) ------------------- */ |
| 669 | /* 2026 polish — side-by-side cost comparison that makes the AI-bundled | |
| 670 | value proposition unmissable. Same visual rhythm as .pl-host-grid; the | |
| 671 | "us" side wins via accent border + subtle gradient glow. */ | |
| 672 | .pl-compare { | |
| 41a1450 | 673 | margin-top: var(--s-14); |
| 13ac035 | 674 | } |
| 675 | .pl-compare-grid { | |
| 676 | display: grid; | |
| 677 | grid-template-columns: 1fr 1fr; | |
| 678 | gap: var(--space-5); | |
| 679 | max-width: 1040px; | |
| 680 | margin: 0 auto; | |
| 681 | } | |
| 682 | .pl-compare-col { | |
| 683 | background: var(--bg-elevated); | |
| 684 | border: 1px solid var(--border); | |
| 685 | border-radius: 16px; | |
| 686 | padding: var(--space-5) var(--space-5) var(--space-6); | |
| 687 | display: flex; | |
| 688 | flex-direction: column; | |
| 689 | transition: transform var(--t-base, 180ms) var(--ease, ease), | |
| 690 | box-shadow var(--t-base, 180ms) var(--ease, ease); | |
| 691 | } | |
| 692 | .pl-compare-col:hover { transform: translateY(-2px); } | |
| 693 | .pl-compare-us { | |
| 694 | position: relative; | |
| 695 | border: 1px solid transparent; | |
| 696 | background-image: | |
| 697 | linear-gradient(var(--bg-elevated), var(--bg-elevated)), | |
| 6fd5915 | 698 | linear-gradient(135deg, #5b6ee8 0%, #5f8fa0 100%); |
| 13ac035 | 699 | background-origin: border-box; |
| 700 | background-clip: padding-box, border-box; | |
| 6fd5915 | 701 | box-shadow: 0 16px 56px -12px rgba(91,110,232, 0.20); |
| 13ac035 | 702 | } |
| 703 | .pl-compare-us::before { | |
| 704 | content: 'Better deal'; | |
| 705 | position: absolute; | |
| 706 | top: -12px; | |
| 707 | left: 50%; | |
| 708 | transform: translateX(-50%); | |
| 709 | padding: 4px 12px; | |
| 710 | border-radius: 999px; | |
| 6fd5915 | 711 | background: linear-gradient(135deg, #5b6ee8 0%, #5f8fa0 100%); |
| 13ac035 | 712 | color: #fff; |
| 713 | font-size: 11px; | |
| 714 | font-weight: 600; | |
| 715 | letter-spacing: 0.04em; | |
| 716 | text-transform: uppercase; | |
| 717 | white-space: nowrap; | |
| 718 | } | |
| 719 | .pl-compare-name { | |
| 720 | font-size: 14px; | |
| 721 | font-weight: 600; | |
| 722 | color: var(--text-muted); | |
| 723 | text-transform: uppercase; | |
| 724 | letter-spacing: 0.08em; | |
| 725 | margin-bottom: var(--space-2); | |
| 726 | } | |
| 727 | .pl-compare-us .pl-compare-name { | |
| 728 | color: var(--accent); | |
| 729 | } | |
| 730 | .pl-compare-price { | |
| 731 | font-family: var(--font-display); | |
| 732 | font-size: 56px; | |
| 733 | font-weight: 800; | |
| 734 | letter-spacing: -0.03em; | |
| 735 | line-height: 1; | |
| 736 | color: var(--text-strong); | |
| 737 | margin-bottom: var(--space-5); | |
| 738 | } | |
| 739 | .pl-compare-per { | |
| 740 | font-size: 16px; | |
| 741 | font-weight: 500; | |
| 742 | color: var(--text-muted); | |
| 743 | margin-left: 4px; | |
| 744 | letter-spacing: 0; | |
| 745 | } | |
| 746 | .pl-compare-feats { | |
| 747 | list-style: none; | |
| 748 | padding: 0; | |
| 749 | margin: 0; | |
| 750 | display: flex; | |
| 751 | flex-direction: column; | |
| 752 | gap: 10px; | |
| 753 | font-size: 14.5px; | |
| 754 | line-height: 1.5; | |
| 755 | color: var(--text); | |
| 756 | } | |
| 757 | .pl-compare-bullet { | |
| 758 | display: inline-block; | |
| 759 | min-width: 44px; | |
| 760 | font-family: var(--font-mono); | |
| 761 | font-weight: 600; | |
| 762 | color: var(--text-muted); | |
| 763 | margin-right: 8px; | |
| 764 | } | |
| 765 | .pl-compare-check { | |
| 766 | display: inline-block; | |
| 767 | min-width: 22px; | |
| 768 | color: var(--accent); | |
| 769 | font-weight: 700; | |
| 770 | } | |
| 771 | .pl-compare-missing { | |
| 772 | color: var(--text-muted); | |
| 773 | font-style: italic; | |
| 774 | } | |
| 775 | .pl-compare-footer { | |
| 776 | max-width: 720px; | |
| 777 | margin: var(--space-6) auto 0; | |
| 778 | text-align: center; | |
| 779 | font-size: 16px; | |
| 780 | line-height: 1.55; | |
| 781 | color: var(--text); | |
| 782 | } | |
| 783 | .pl-compare-footer strong { | |
| 784 | color: var(--text-strong); | |
| 6fd5915 | 785 | background-image: linear-gradient(transparent 62%, rgba(91,110,232, 0.18) 62%); |
| 13ac035 | 786 | } |
| 787 | @media (max-width: 720px) { | |
| 788 | .pl-compare-grid { grid-template-columns: 1fr; } | |
| 789 | .pl-compare-price { font-size: 44px; } | |
| 790 | } | |
| 791 | ||
| 5f2e749 | 792 | /* FAQ */ |
| 793 | .pl-faq { | |
| 794 | max-width: 760px; | |
| 795 | margin: 0 auto; | |
| 796 | border: 1px solid var(--border); | |
| 797 | border-radius: var(--r-lg); | |
| 798 | overflow: hidden; | |
| 799 | background: var(--bg-elevated); | |
| 800 | } | |
| 801 | .pl-faq-item { border-bottom: 1px solid var(--border-subtle); } | |
| 802 | .pl-faq-item:last-child { border-bottom: none; } | |
| 803 | .pl-faq-q { | |
| 804 | display: flex; | |
| 805 | justify-content: space-between; | |
| 806 | align-items: center; | |
| 807 | gap: 16px; | |
| 808 | padding: 18px 24px; | |
| 809 | cursor: pointer; | |
| 810 | font-size: var(--t-md); | |
| 811 | font-weight: 500; | |
| 812 | color: var(--text-strong); | |
| 813 | list-style: none; | |
| 814 | transition: background var(--t-fast) var(--ease); | |
| 815 | } | |
| 816 | .pl-faq-q::-webkit-details-marker { display: none; } | |
| 817 | .pl-faq-q:hover { background: var(--bg-hover); } | |
| 818 | .pl-faq-toggle { | |
| 819 | font-family: var(--font-mono); | |
| 820 | font-size: 18px; | |
| 821 | color: var(--text-muted); | |
| 822 | transition: transform var(--t-base) var(--ease-spring); | |
| 823 | flex-shrink: 0; | |
| 824 | } | |
| 825 | .pl-faq-item[open] .pl-faq-toggle { transform: rotate(45deg); color: var(--accent); } | |
| 826 | .pl-faq-a { | |
| 827 | padding: 0 24px 20px; | |
| 828 | color: var(--text-muted); | |
| 829 | font-size: var(--t-sm); | |
| 830 | line-height: 1.6; | |
| 831 | margin: 0; | |
| 832 | } | |
| 833 | ||
| 834 | /* CTA */ | |
| 835 | .pl-cta-wrap { margin: var(--s-16) auto var(--s-10); } | |
| 836 | .pl-cta { | |
| 837 | position: relative; | |
| 838 | text-align: center; | |
| 839 | padding: var(--s-12) var(--s-6); | |
| 840 | border: 1px solid var(--border-strong); | |
| 841 | border-radius: var(--r-2xl); | |
| 842 | background: | |
| 6fd5915 | 843 | radial-gradient(60% 100% at 50% 0%, rgba(91,110,232,0.14), transparent 65%), |
| 5f2e749 | 844 | var(--bg-elevated); |
| 845 | overflow: hidden; | |
| 846 | } | |
| 847 | .pl-cta-title { | |
| 848 | font-family: var(--font-display); | |
| 849 | font-size: clamp(24px, 3.5vw, 40px); | |
| 850 | line-height: 1.1; | |
| 851 | letter-spacing: -0.025em; | |
| 852 | font-weight: 600; | |
| 853 | margin: 0 0 var(--s-3); | |
| 854 | color: var(--text-strong); | |
| 855 | } | |
| 856 | .pl-cta-sub { | |
| 857 | font-size: var(--t-md); | |
| 858 | color: var(--text-muted); | |
| 859 | margin: 0 auto var(--s-6); | |
| 860 | max-width: 480px; | |
| 861 | } | |
| 862 | .pl-cta-buttons { | |
| 863 | display: flex; | |
| 864 | gap: 12px; | |
| 865 | justify-content: center; | |
| 866 | flex-wrap: wrap; | |
| 867 | } | |
| 868 | ||
| a65653b | 869 | /* Every-plan strip */ |
| 870 | .pl-every-plan { margin: calc(var(--s-14) * -0.5) auto var(--s-10); } | |
| 871 | .pl-every-plan-inner { | |
| 872 | display: flex; | |
| 873 | align-items: center; | |
| 874 | gap: 20px; | |
| 875 | flex-wrap: wrap; | |
| 876 | justify-content: center; | |
| 877 | padding: 14px 28px; | |
| 878 | border: 1px solid var(--border-subtle); | |
| 879 | border-radius: var(--r-full); | |
| 880 | background: var(--bg-elevated); | |
| 881 | max-width: 760px; | |
| 882 | margin: 0 auto; | |
| 883 | font-size: 13px; | |
| 884 | } | |
| 885 | .pl-every-plan-label { | |
| 886 | font-family: var(--font-mono); | |
| 887 | font-size: 11px; | |
| 888 | text-transform: uppercase; | |
| 889 | letter-spacing: 0.14em; | |
| 890 | color: var(--text-muted); | |
| 891 | white-space: nowrap; | |
| 892 | } | |
| 893 | .pl-every-plan-list { | |
| 894 | list-style: none; | |
| 895 | padding: 0; | |
| 896 | margin: 0; | |
| 897 | display: flex; | |
| 898 | gap: 0; | |
| 899 | flex-wrap: wrap; | |
| 900 | justify-content: center; | |
| 901 | } | |
| 902 | .pl-every-plan-list li { | |
| 903 | color: var(--text); | |
| 904 | display: flex; | |
| 905 | align-items: center; | |
| 906 | gap: 10px; | |
| 907 | } | |
| 908 | .pl-every-plan-list li + li::before { | |
| 909 | content: '·'; | |
| 910 | color: var(--text-muted); | |
| 911 | margin-right: 10px; | |
| 912 | } | |
| 913 | ||
| 5f2e749 | 914 | /* Responsive */ |
| 915 | @media (max-width: 960px) { | |
| 916 | .pl-plans { grid-template-columns: repeat(2, 1fr); } | |
| 917 | } | |
| 918 | @media (max-width: 720px) { | |
| 919 | .pl-host-grid { grid-template-columns: 1fr; } | |
| 920 | .pl-free-grid { grid-template-columns: 1fr; } | |
| 921 | } | |
| 922 | @media (max-width: 560px) { | |
| 923 | .pl-plans { grid-template-columns: 1fr; } | |
| 924 | .pl-cta-buttons .btn { width: 100%; justify-content: center; } | |
| 925 | } | |
| 926 | `; | |
| 927 | ||
| 928 | export default pricing; |