Pre-launch — Gluecron is in final validation. Public signups and git hosting for non-owner users open after launch review.
CodeIssuesDiscussionsWikiPull RequestsProjectsCommitsActionsReleasesContributorsPulse● GatesSecuritySettingsDeploymentsPipelineInsightsAgents✨ Explain✨ Ask AI✨ Workspace✨ Spec✨ Tests▓ Debt Map✨ NL Search🏛 Archaeology
Blame · Line-by-line history

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