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