Pre-launch — Gluecron is in final validation. Public signups and git hosting for non-owner users open after launch review.
CodeIssuesPull RequestsActionsSecurityInsightsSettings
✨ AI
More
Blame · Line-by-line history

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.

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