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

marketing.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.

marketing.tsxBlame1164 lines · 1 contributor
b0148e9Claude1/**
2 * Marketing surface — public pages that don't fit the app shell.
3 * Pricing, features, about. Logged-out and logged-in safe (softAuth).
4 *
5 * All pages use the new Editorial-Technical design system: .display,
6 * .eyebrow, .section-header, .stagger, .gradient-text utilities are
7 * defined globally in src/views/layout.tsx.
8 */
9
10import { Hono } from "hono";
11import type { FC } from "hono/jsx";
12import { Layout } from "../views/layout";
13import { softAuth } from "../middleware/auth";
14import type { AuthEnv } from "../middleware/auth";
15
16const marketing = new Hono<AuthEnv>();
17marketing.use("*", softAuth);
18
19// ============================================================
20// /pricing
21// ============================================================
22
23marketing.get("/pricing", (c) => {
24 const user = c.get("user");
25 return c.html(
26 <Layout title="Pricing — gluecron" user={user}>
27 <PricingPage />
28 </Layout>,
29 );
30});
31
32const PricingPage: FC = () => (
33 <>
34 <style>{pricingCss}</style>
35 <div class="mkt-root">
36 <header class="mkt-hero">
37 <div class="eyebrow">Pricing</div>
38 <h1 class="display mkt-hero-title">
39 Honest pricing for{" "}
40 <span class="gradient-text">teams that ship.</span>
41 </h1>
42 <p class="mkt-hero-sub">
43 Self-hosting is free forever. Hosted plans price the AI calls, not
44 the seats. No one pays per developer.
45 </p>
46 </header>
47
48 <section class="mkt-pricing-grid stagger">
49 <PricingTier
50 tier="Free"
51 price="$0"
52 cadence="forever"
53 desc="For personal projects + open source. Full AI suite included."
54 features={[
55 "Unlimited public repos",
56 "3 private repos",
57 "5,000 AI calls / month",
58 "GateTest + auto-repair",
59 "Webhooks + workflows",
60 "Community support",
61 ]}
62 cta="Start free"
63 href="/register"
64 />
65 <PricingTier
66 tier="Pro"
67 price="$12"
68 cadence="per user / month"
69 desc="For working developers. Lifts every quota and adds priority routing."
70 features={[
71 "Unlimited private repos",
72 "100,000 AI calls / month",
73 "Priority AI queue",
74 "Custom domains",
75 "Advanced analytics",
76 "Email support",
77 ]}
78 cta="Go Pro"
79 href="/settings/billing"
80 highlight
81 />
82 <PricingTier
83 tier="Team"
84 price="$29"
85 cadence="per user / month"
86 desc="For organisations running production on Gluecron."
87 features={[
88 "Everything in Pro",
89 "Unlimited AI calls",
90 "Org-level SSO + SCIM",
91 "Audit log retention",
92 "SLA-backed uptime",
93 "Slack support channel",
94 ]}
95 cta="Talk to us"
96 href="mailto:hello@gluecron.com"
97 />
98 <PricingTier
99 tier="Enterprise"
100 price="Custom"
101 cadence="contact us"
102 desc="On-prem deploy, dedicated capacity, 24/7 incident response."
103 features={[
104 "Everything in Team",
105 "On-prem / VPC deploy",
106 "Dedicated AI capacity",
107 "Private model routing",
108 "DPA + custom contracts",
109 "24/7 incident response",
110 ]}
111 cta="Contact sales"
112 href="mailto:enterprise@gluecron.com"
113 />
114 </section>
115
116 <section class="mkt-section">
117 <div class="section-header">
118 <div class="eyebrow">Self-hosted</div>
119 <h2>Run it on your own metal. No license, no telemetry.</h2>
120 <p>
121 Gluecron is a single Bun binary plus Postgres. Deploy to Fly,
122 Railway, your own VPS, or air-gapped infra. Free forever for
123 self-hosters of any size.
124 </p>
125 </div>
126 <div class="mkt-selfhost-card surface-glow">
127 <div class="mkt-selfhost-grid">
128 <div class="mkt-selfhost-cell">
129 <div class="mkt-selfhost-num">$0</div>
130 <div class="mkt-selfhost-label">License</div>
131 </div>
132 <div class="mkt-selfhost-cell">
133 <div class="mkt-selfhost-num">∞</div>
134 <div class="mkt-selfhost-label">Users</div>
135 </div>
136 <div class="mkt-selfhost-cell">
137 <div class="mkt-selfhost-num">∞</div>
138 <div class="mkt-selfhost-label">Repos</div>
139 </div>
140 <div class="mkt-selfhost-cell">
141 <div class="mkt-selfhost-num">0</div>
142 <div class="mkt-selfhost-label">Telemetry</div>
143 </div>
144 </div>
145 <div class="mkt-selfhost-cta">
146 <a href="/help" class="btn btn-primary btn-lg">Self-host guide</a>
147 <a
148 href="https://github.com/ccantynz-alt/Gluecron.com"
149 class="btn btn-ghost btn-lg"
150 >
151 View source
152 </a>
153 </div>
154 </div>
155 </section>
156
157 <section class="mkt-section">
158 <div class="section-header">
159 <div class="eyebrow">Questions</div>
160 <h2>The fine print, in plain English.</h2>
161 </div>
162 <div class="mkt-faq">
163 <FaqItem
164 q="What counts as an AI call?"
165 a="Every Claude inference: PR review, security scan, spec-to-PR draft, commit message suggestion, chat reply. We don't bill for failed calls or retries on our end."
166 />
167 <FaqItem
168 q="Can I bring my own Anthropic key?"
169 a="Yes. Pro and above can supply ANTHROPIC_API_KEY; calls run against your account and don't count toward our quota. Useful for orgs with prepaid commits or enterprise rate limits."
170 />
171 <FaqItem
172 q="What happens if I hit my AI quota?"
173 a="AI features degrade gracefully — gates still run, code still hosts. AI suggestions queue at the back of the line. No surprise overage bills, ever."
174 />
175 <FaqItem
176 q="Do you charge for runner minutes?"
177 a="No. Workflow runs are unmetered on hosted plans. Self-hosters bring their own compute, of course."
178 />
179 <FaqItem
180 q="What's the uptime SLA?"
181 a="Team and Enterprise carry a 99.9% monthly SLA with credits. Free and Pro are best-effort but we publish live status at /status and historical incidents in CHANGELOG.md."
182 />
183 <FaqItem
184 q="How do I migrate off Gluecron?"
185 a="Same way you migrate off GitHub: git remote set-url and push. We're git-compatible to the byte. No vendor lock, no migration tax."
186 />
187 </div>
188 </section>
189
190 <CtaBlock />
191 </div>
192 </>
193);
194
195const PricingTier: FC<{
196 tier: string;
197 price: string;
198 cadence: string;
199 desc: string;
200 features: string[];
201 cta: string;
202 href: string;
203 highlight?: boolean;
204}> = ({ tier, price, cadence, desc, features, cta, href, highlight }) => (
205 <div class={`mkt-tier${highlight ? " mkt-tier-hl" : ""}`}>
206 {highlight && <div class="mkt-tier-badge">Most popular</div>}
207 <div class="mkt-tier-name">{tier}</div>
208 <div class="mkt-tier-amount">
209 <span class="mkt-tier-num">{price}</span>
210 <span class="mkt-tier-cad">{cadence}</span>
211 </div>
212 <p class="mkt-tier-desc">{desc}</p>
213 <ul class="mkt-tier-features">
214 {features.map((f) => (
215 <li>
216 <span class="mkt-tier-check">{"✓"}</span>
217 {f}
218 </li>
219 ))}
220 </ul>
221 <a
222 href={href}
223 class={`btn ${highlight ? "btn-primary" : "btn-secondary"} btn-block`}
224 style="margin-top:auto"
225 >
226 {cta}
227 </a>
228 </div>
229);
230
231const FaqItem: FC<{ q: string; a: string }> = ({ q, a }) => (
232 <details class="mkt-faq-item">
233 <summary class="mkt-faq-q">
234 <span>{q}</span>
235 <span class="mkt-faq-toggle" aria-hidden="true">{"+"}</span>
236 </summary>
237 <p class="mkt-faq-a">{a}</p>
238 </details>
239);
240
241// ============================================================
242// /features
243// ============================================================
244
245marketing.get("/features", (c) => {
246 const user = c.get("user");
247 return c.html(
248 <Layout title="Features — gluecron" user={user}>
249 <FeaturesPage />
250 </Layout>,
251 );
252});
253
254const FeaturesPage: FC = () => (
255 <>
256 <style>{featuresCss}</style>
257 <div class="mkt-root">
258 <header class="mkt-hero">
259 <div class="eyebrow">Features</div>
260 <h1 class="display mkt-hero-title">
261 A complete dev platform.{" "}
262 <span class="gradient-text">Nothing extra to buy.</span>
263 </h1>
264 <p class="mkt-hero-sub">
265 Hosting, CI, AI review, security scanning, deploy webhooks,
266 marketplace, packages, pages — every surface you need, ready on
267 day one.
268 </p>
269 </header>
270
271 <FeatureCategory
272 eyebrow="Code intelligence"
273 title="The AI is a teammate, not an upsell."
274 items={[
275 {
276 title: "AI code review",
277 desc: "Real Claude review on every PR open. Inline file/line comments. Idempotent across re-runs.",
278 },
279 {
280 title: "Spec-to-PR",
281 desc: "Drop a feature spec in plain English. AI drafts the entire PR — branch, commits, description.",
282 },
283 {
284 title: "AI security review",
285 desc: "Sonnet 4 reads diffs for OWASP-class issues. Posts as inline review comments, not noise.",
286 },
287 {
288 title: "Auto-repair",
289 desc: "Failed gate? AI tries to fix it and pushes a follow-up commit. Your repo self-corrects.",
290 },
291 {
292 title: "AI commit messages",
293 desc: "One-click 'suggest with AI' on the web editor. Concise, conventional-commit format.",
294 },
295 {
296 title: "AI changelogs",
297 desc: "Generated on release create. Plus an arbitrary-range viewer at /:repo/ai/changelog.",
298 },
299 {
300 title: "AI incident responder",
301 desc: "Failed deploy? AI opens an issue with the failing logs + suggested fix + linked PR.",
302 },
303 {
304 title: "AI test generation",
305 desc: "Drops test stubs for uncovered functions. You review, edit, commit.",
306 },
307 ]}
308 />
309
310 <FeatureCategory
311 eyebrow="Quality gate"
312 title="Nothing broken ever reaches production."
313 items={[
314 {
315 title: "GateTest integration",
316 desc: "Push triggers GateTest. Results post back as inline annotations on the commit.",
317 },
318 {
319 title: "Secret scanner",
320 desc: "15 patterns, runs on every push. Blocks the push if a real secret leaks.",
321 },
322 {
323 title: "Branch protection",
324 desc: "Required checks, code-owner reviews, push restrictions, force-push blocking.",
325 },
326 {
327 title: "Repository rulesets",
328 desc: "Named policy bundles. Six rule types from commit message regex to max file size.",
329 },
330 {
331 title: "Required checks matrix",
332 desc: "Per branch-protection list of named checks that must pass before merge.",
333 },
334 {
335 title: "Protected tags",
336 desc: "Owners declare patterns (v*, release-*) that only owners can push.",
337 },
338 {
339 title: "Merge queue",
340 desc: "Serialised merge with re-test against latest base. No more 'green when merged, red on main'.",
341 },
342 {
343 title: "Pre-receive policy",
344 desc: "Ref-name patterns and push policies enforced at the HTTP layer with 403s.",
345 },
346 ]}
347 />
348
349 <FeatureCategory
350 eyebrow="Real-time"
351 title="No polling, no refresh, no waiting."
352 items={[
353 {
354 title: "Live workflow logs",
355 desc: "Step-by-step output streams over SSE the moment your runner emits it.",
356 },
357 {
358 title: "Live PR comments",
359 desc: "New comment in another tab? You see a 'reload to view' banner immediately.",
360 },
361 {
362 title: "Live deploy events",
363 desc: "Crontech-Gluecron event bus pushes deploy state. Watch deploys happen.",
364 },
365 {
366 title: "Live presence",
367 desc: "See who's looking at the same PR right now. Avoid double review work.",
368 },
369 ]}
370 />
371
372 <FeatureCategory
373 eyebrow="Platform"
374 title="Everything GitHub charges extra for."
375 items={[
376 {
377 title: "Workflow runner",
378 desc: "Drop yaml in `.gluecron/workflows/`. Runs on push. Cron triggers, secrets, matrix.",
379 },
380 {
381 title: "Packages registry",
382 desc: "npm protocol. Publish, install, yank with `glc_` PAT auth. Container registry deferred.",
383 },
384 {
385 title: "Pages hosting",
386 desc: "Serves blobs from the latest gh-pages commit. Custom domains, short cache headers.",
387 },
388 {
389 title: "Marketplace + apps",
390 desc: "Install third-party apps with permission scopes. App-bot push auth via ghi_ tokens.",
391 },
392 {
393 title: "Discussions",
394 desc: "Categorised threads, pinned, locked, Q&A answers. Zero extra config.",
395 },
396 {
397 title: "Wikis + Gists + Projects",
398 desc: "All shipped, all free, all integrated. No upsell tier.",
399 },
400 ]}
401 />
402
403 <FeatureCategory
404 eyebrow="Identity + governance"
405 title="Enterprise-tier auth from day one."
406 items={[
407 {
408 title: "TOTP + WebAuthn",
409 desc: "Both 2FA paths shipped. Passkey-only login if you want it.",
410 },
411 {
412 title: "OIDC SSO",
413 desc: "Okta, Azure AD, Auth0, Google Workspace. Auto-create users. Email-domain allowlist.",
414 },
415 {
416 title: "OAuth provider",
417 desc: "Third-party apps request scoped access to user repos. Standard auth-code flow.",
418 },
419 {
420 title: "Personal access tokens",
421 desc: "SHA-256 hashed, scoped, revocable. The clean way to script Gluecron.",
422 },
423 {
424 title: "Audit log (per-user + per-repo)",
425 desc: "Every sensitive action recorded. Browseable at /settings/audit.",
426 },
427 {
428 title: "Site admin panel",
429 desc: "/admin for site-wide flags: registration locks, banners, read-only mode.",
430 },
431 ]}
432 />
433
434 <FeatureCategory
435 eyebrow="Integrations"
436 title="Speak every protocol your tools use."
437 items={[
438 {
439 title: "MCP server",
440 desc: "Claude Desktop, Cursor, Code, Cline plug in natively. Read + scoped write tools.",
441 },
442 {
443 title: "REST API v2",
444 desc: "Full CRUD across resources. Versioned, documented, stable.",
445 },
446 {
447 title: "GraphQL endpoint",
448 desc: "Single-request fetch for complex client views. GraphiQL explorer at /api/graphql.",
449 },
450 {
451 title: "Webhooks",
452 desc: "HMAC-signed outbound to your URLs on push, issue, PR, star, comment, deploy.",
453 },
454 {
455 title: "Smart-HTTP git",
456 desc: "git push, git pull, git clone — exactly what your tools already speak.",
457 },
458 {
459 title: "VS Code extension",
460 desc: "Explain, open-on-web, semantic search, generate tests — all from the editor.",
461 },
462 ]}
463 />
464
465 <CtaBlock />
466 </div>
467 </>
468);
469
470const FeatureCategory: FC<{
471 eyebrow: string;
472 title: string;
473 items: Array<{ title: string; desc: string }>;
474}> = ({ eyebrow, title, items }) => (
475 <section class="mkt-section">
476 <div class="section-header left">
477 <div class="eyebrow">{eyebrow}</div>
478 <h2>{title}</h2>
479 </div>
480 <div class="mkt-feat-grid stagger">
481 {items.map((it) => (
482 <div class="mkt-feat-cell">
483 <h3 class="mkt-feat-title">{it.title}</h3>
484 <p class="mkt-feat-desc">{it.desc}</p>
485 </div>
486 ))}
487 </div>
488 </section>
489);
490
491// ============================================================
492// /about
493// ============================================================
494
495marketing.get("/about", (c) => {
496 const user = c.get("user");
497 return c.html(
498 <Layout title="About — gluecron" user={user}>
499 <AboutPage />
500 </Layout>,
501 );
502});
503
504const AboutPage: FC = () => (
505 <>
506 <style>{aboutCss}</style>
507 <div class="mkt-root">
508 <header class="mkt-hero">
509 <div class="eyebrow">About</div>
510 <h1 class="display mkt-hero-title">
511 We're building the platform{" "}
512 <span class="gradient-text">software writes itself on.</span>
513 </h1>
514 <p class="mkt-hero-sub">
515 Most code in 2026 is written by AI. Most reviews are too. The
516 platforms hosting that code were built for a previous era.
517 Gluecron is built for this one.
518 </p>
519 </header>
520
521 <section class="mkt-section">
522 <div class="mkt-prose">
523 <div class="eyebrow">Mission</div>
524 <h2>An IDE for your repo. A teammate, not a sidebar.</h2>
525 <p>
526 GitHub treats AI as a feature you bolt onto your workflow. We
527 treat it as a peer that opens PRs, reviews diffs, fixes
528 regressions, and ships its own changes — visibly, accountably,
529 with a real identity in your history.
530 </p>
531 <p>
532 Everything is green by default. Every new repo auto-configures
533 gates, branch protection, labels, CODEOWNERS, and a welcome issue.
534 Users opt out per feature. Defaults are maximum-green so
535 <strong> nothing broken ever reaches production, the website,
536 or the customer.</strong>
537 </p>
538 </div>
539 </section>
540
541 <section class="mkt-section">
542 <div class="section-header">
543 <div class="eyebrow">Principles</div>
544 <h2>Six rules we don't compromise on.</h2>
545 </div>
546 <div class="mkt-principles stagger">
547 <PrincipleCard
548 n="01"
549 title="No vendor lock"
550 desc="We're git-compatible to the byte. Migrate off Gluecron the same way you migrate off GitHub: git remote set-url. Your code is yours."
551 />
552 <PrincipleCard
553 n="02"
554 title="No surprise bills"
555 desc="AI quotas degrade gracefully when hit. No overage, no automatic upgrade. We publish prices in dollars, not credits."
556 />
557 <PrincipleCard
558 n="03"
559 title="Self-host is first-class"
560 desc="Single Bun binary, single Postgres, zero telemetry. The same product the hosted version runs on, free forever for self-hosters."
561 />
562 <PrincipleCard
563 n="04"
564 title="AI is accountable"
565 desc="Every AI commit is signed by an app-bot identity. Every AI comment is labelled. You can audit, revert, or disable any AI agent at any time."
566 />
567 <PrincipleCard
568 n="05"
569 title="Real-time over polling"
570 desc="SSE for logs, comments, deploys, presence. The web should feel like a desktop app. No spinners on a tab you've already loaded."
571 />
572 <PrincipleCard
573 n="06"
574 title="Open by default"
575 desc="REST + GraphQL + MCP + Smart-HTTP + webhooks. Your tools speak our platform without asking. Programmatic access is not an enterprise tier."
576 />
577 </div>
578 </section>
579
580 <section class="mkt-section">
581 <div class="section-header">
582 <div class="eyebrow">Stack</div>
583 <h2>Built on the boring, fast parts.</h2>
584 </div>
585 <div class="mkt-stack">
586 <StackPill name="Bun" desc="runtime" />
587 <StackPill name="Hono" desc="server framework" />
588 <StackPill name="Drizzle" desc="ORM" />
589 <StackPill name="Neon Postgres" desc="primary database" />
590 <StackPill name="Claude Sonnet 4" desc="AI review + chat" />
591 <StackPill name="Claude Haiku 4.5" desc="AI commits + summaries" />
592 <StackPill name="Smart-HTTP git" desc="protocol" />
593 <StackPill name="Fly.io" desc="deploy target" />
594 </div>
595 </section>
596
597 <section class="mkt-section mkt-contact">
598 <div class="section-header">
599 <div class="eyebrow">Contact</div>
600 <h2>Three places to find us.</h2>
601 </div>
602 <div class="mkt-contact-grid">
603 <ContactCard
604 label="Product + sales"
605 email="hello@gluecron.com"
606 line="For demos, pricing questions, partnership ideas."
607 />
608 <ContactCard
609 label="Security"
610 email="security@gluecron.com"
611 line="Responsible disclosure. PGP key on request."
612 />
613 <ContactCard
614 label="Support"
615 email="support@gluecron.com"
616 line="Bugs, account help, anything that's broken."
617 />
618 </div>
619 </section>
620
621 <CtaBlock />
622 </div>
623 </>
624);
625
626const PrincipleCard: FC<{ n: string; title: string; desc: string }> = ({
627 n,
628 title,
629 desc,
630}) => (
631 <div class="mkt-principle">
632 <div class="mkt-principle-num">{n}</div>
633 <h3 class="mkt-principle-title">{title}</h3>
634 <p class="mkt-principle-desc">{desc}</p>
635 </div>
636);
637
638const StackPill: FC<{ name: string; desc: string }> = ({ name, desc }) => (
639 <div class="mkt-stack-pill">
640 <span class="mkt-stack-name">{name}</span>
641 <span class="mkt-stack-desc">{desc}</span>
642 </div>
643);
644
645const ContactCard: FC<{ label: string; email: string; line: string }> = ({
646 label,
647 email,
648 line,
649}) => (
650 <a href={`mailto:${email}`} class="mkt-contact-card">
651 <div class="mkt-contact-label">{label}</div>
652 <div class="mkt-contact-email">{email}</div>
653 <p class="mkt-contact-line">{line}</p>
654 </a>
655);
656
657// ============================================================
658// Shared closing CTA block
659// ============================================================
660
661const CtaBlock: FC = () => (
662 <section class="mkt-cta">
663 <div class="mkt-cta-card">
664 <div class="mkt-cta-bg" aria-hidden="true" />
665 <div class="eyebrow">Get started</div>
666 <h2 class="mkt-cta-title">
667 Stop maintaining the platform.<br />
668 <span class="gradient-text">Start shipping the product.</span>
669 </h2>
670 <div class="mkt-cta-buttons">
671 <a href="/register" class="btn btn-primary btn-xl">
672 Create your account
673 <span aria-hidden="true">{"→"}</span>
674 </a>
675 <a href="/import" class="btn btn-ghost btn-xl">
676 Migrate from GitHub
677 </a>
678 </div>
679 </div>
680 </section>
681);
682
683// ============================================================
684// Styles — namespaced under .mkt- so they don't leak
685// ============================================================
686
687const sharedMktCss = `
688 .mkt-root {
689 max-width: 1180px;
690 margin: 0 auto;
691 padding: 0 16px;
692 }
693
694 /* Hero */
695 .mkt-hero {
696 text-align: center;
697 padding: var(--s-16) 0 var(--s-12);
698 max-width: 920px;
699 margin: 0 auto;
700 position: relative;
701 }
702 .mkt-hero::before {
703 content: '';
704 position: absolute;
705 top: 0; left: 50%;
706 transform: translateX(-50%);
707 width: 70%; height: 60%;
708 background: radial-gradient(ellipse at center, rgba(140,109,255,0.14), transparent 65%);
709 z-index: -1;
710 pointer-events: none;
711 }
712 .mkt-hero .eyebrow { justify-content: center; margin: 0 auto var(--s-4); }
713 .mkt-hero-title {
714 font-size: clamp(36px, 6.5vw, 76px);
715 line-height: 1.02;
716 letter-spacing: -0.038em;
717 margin: 0 0 var(--s-5);
718 }
719 .mkt-hero-sub {
720 font-size: clamp(15px, 1.5vw, 18px);
721 color: var(--text-muted);
722 max-width: 640px;
723 margin: 0 auto;
724 line-height: 1.55;
725 }
726
727 /* Section spacing */
728 .mkt-section { margin: var(--s-16) auto; }
729
730 /* Closing CTA */
731 .mkt-cta { margin: var(--s-20) auto var(--s-12); }
732 .mkt-cta-card {
733 position: relative;
734 text-align: center;
735 padding: var(--s-14) var(--s-7);
736 border: 1px solid var(--border-strong);
737 border-radius: var(--r-2xl);
738 background: var(--bg-elevated);
739 overflow: hidden;
740 isolation: isolate;
741 }
742 .mkt-cta-bg {
743 position: absolute;
744 inset: 0;
745 z-index: -1;
746 background:
747 radial-gradient(60% 100% at 50% 0%, rgba(140,109,255,0.16), transparent 65%),
748 radial-gradient(40% 80% at 80% 100%, rgba(54,197,214,0.10), transparent 65%);
749 }
750 .mkt-cta-card .eyebrow { justify-content: center; }
751 .mkt-cta-title {
752 font-family: var(--font-display);
753 font-size: clamp(28px, 4vw, 52px);
754 line-height: 1.05;
755 letter-spacing: -0.03em;
756 font-weight: 600;
757 margin: var(--s-3) 0 var(--s-7);
758 color: var(--text-strong);
759 }
760 .mkt-cta-buttons {
761 display: flex;
762 gap: 12px;
763 justify-content: center;
764 flex-wrap: wrap;
765 }
766
767 @media (max-width: 640px) {
768 .mkt-hero { padding: var(--s-10) 0 var(--s-8); }
769 .mkt-section { margin: var(--s-12) auto; }
770 .mkt-cta-buttons .btn { width: 100%; justify-content: center; }
771 }
772`;
773
774const pricingCss = sharedMktCss + `
775 .mkt-pricing-grid {
776 display: grid;
777 grid-template-columns: repeat(4, 1fr);
778 gap: 12px;
779 margin: var(--s-12) auto var(--s-16);
780 align-items: stretch;
781 }
782 .mkt-tier {
783 position: relative;
784 background: var(--bg-elevated);
785 border: 1px solid var(--border);
786 border-radius: var(--r-lg);
787 padding: var(--s-7) var(--s-6);
788 display: flex;
789 flex-direction: column;
790 gap: var(--s-4);
791 transition: border-color var(--t-base) var(--ease), transform var(--t-base) var(--ease-out-quart);
792 }
793 .mkt-tier:hover { border-color: var(--border-strong); transform: translateY(-3px); }
794 .mkt-tier-hl {
795 border-color: rgba(140,109,255,0.40);
796 box-shadow: var(--elev-2), 0 0 0 1px rgba(140,109,255,0.30);
797 background:
798 linear-gradient(180deg, rgba(140,109,255,0.05), transparent 50%),
799 var(--bg-elevated);
800 }
801 .mkt-tier-hl:hover { border-color: rgba(140,109,255,0.60); }
802 .mkt-tier-badge {
803 position: absolute;
804 top: -10px;
805 left: 50%;
806 transform: translateX(-50%);
807 padding: 3px 12px;
808 background: var(--accent-gradient);
809 color: #fff;
810 font-family: var(--font-mono);
811 font-size: 10px;
812 letter-spacing: 0.1em;
813 text-transform: uppercase;
814 font-weight: 600;
815 border-radius: var(--r-full);
816 box-shadow: 0 4px 14px -2px rgba(140,109,255,0.45);
817 white-space: nowrap;
818 }
819 .mkt-tier-name {
820 font-family: var(--font-mono);
821 font-size: 11px;
822 text-transform: uppercase;
823 letter-spacing: 0.16em;
824 color: var(--text-muted);
825 }
826 .mkt-tier-amount {
827 display: flex;
828 align-items: baseline;
829 gap: 6px;
830 flex-wrap: wrap;
831 }
832 .mkt-tier-num {
833 font-family: var(--font-display);
834 font-size: 36px;
835 font-weight: 600;
836 letter-spacing: -0.03em;
837 color: var(--text-strong);
838 }
839 .mkt-tier-cad {
840 font-size: 12px;
841 color: var(--text-faint);
842 }
843 .mkt-tier-desc {
844 font-size: var(--t-sm);
845 color: var(--text-muted);
846 line-height: 1.5;
847 margin: 0;
848 }
849 .mkt-tier-features {
850 list-style: none;
851 padding: 0;
852 margin: 0;
853 display: flex;
854 flex-direction: column;
855 gap: 7px;
856 font-size: var(--t-sm);
857 color: var(--text);
858 }
859 .mkt-tier-features li {
860 display: flex;
861 align-items: flex-start;
862 gap: 9px;
863 line-height: 1.45;
864 }
865 .mkt-tier-check {
866 color: var(--accent);
867 font-weight: 600;
868 flex-shrink: 0;
869 line-height: 1.45;
870 }
871
872 .mkt-selfhost-card {
873 max-width: 880px;
874 margin: 0 auto;
875 padding: var(--s-10);
876 text-align: center;
877 }
878 .mkt-selfhost-grid {
879 display: grid;
880 grid-template-columns: repeat(4, 1fr);
881 gap: var(--s-6);
882 margin-bottom: var(--s-8);
883 padding-bottom: var(--s-8);
884 border-bottom: 1px solid var(--border-subtle);
885 }
886 .mkt-selfhost-cell { text-align: center; }
887 .mkt-selfhost-num {
888 font-family: var(--font-display);
889 font-size: 44px;
890 font-weight: 600;
891 letter-spacing: -0.03em;
892 color: var(--text-strong);
893 line-height: 1;
894 background: var(--accent-gradient);
895 -webkit-background-clip: text;
896 background-clip: text;
897 -webkit-text-fill-color: transparent;
898 }
899 .mkt-selfhost-label {
900 font-family: var(--font-mono);
901 font-size: 11px;
902 text-transform: uppercase;
903 letter-spacing: 0.14em;
904 color: var(--text-muted);
905 margin-top: var(--s-2);
906 }
907 .mkt-selfhost-cta {
908 display: flex;
909 gap: 12px;
910 justify-content: center;
911 flex-wrap: wrap;
912 }
913
914 .mkt-faq {
915 max-width: 760px;
916 margin: 0 auto;
917 border: 1px solid var(--border);
918 border-radius: var(--r-lg);
919 overflow: hidden;
920 background: var(--bg-elevated);
921 }
922 .mkt-faq-item {
923 border-bottom: 1px solid var(--border-subtle);
924 }
925 .mkt-faq-item:last-child { border-bottom: none; }
926 .mkt-faq-q {
927 display: flex;
928 justify-content: space-between;
929 align-items: center;
930 gap: 16px;
931 padding: 18px 24px;
932 cursor: pointer;
933 font-size: var(--t-md);
934 font-weight: 500;
935 color: var(--text-strong);
936 list-style: none;
937 transition: background var(--t-fast) var(--ease);
938 }
939 .mkt-faq-q::-webkit-details-marker { display: none; }
940 .mkt-faq-q:hover { background: var(--bg-hover); }
941 .mkt-faq-toggle {
942 font-family: var(--font-mono);
943 font-size: 18px;
944 color: var(--text-muted);
945 transition: transform var(--t-base) var(--ease-spring);
946 flex-shrink: 0;
947 }
948 .mkt-faq-item[open] .mkt-faq-toggle { transform: rotate(45deg); color: var(--accent); }
949 .mkt-faq-a {
950 padding: 0 24px 20px;
951 color: var(--text-muted);
952 font-size: var(--t-sm);
953 line-height: 1.6;
954 margin: 0;
955 }
956
957 @media (max-width: 960px) {
958 .mkt-pricing-grid { grid-template-columns: repeat(2, 1fr); }
959 }
960 @media (max-width: 640px) {
961 .mkt-pricing-grid { grid-template-columns: 1fr; }
962 .mkt-selfhost-grid { grid-template-columns: repeat(2, 1fr); }
963 .mkt-selfhost-card { padding: var(--s-7); }
964 }
965`;
966
967const featuresCss = sharedMktCss + `
968 .mkt-feat-grid {
969 display: grid;
970 grid-template-columns: repeat(2, 1fr);
971 gap: 1px;
972 background: var(--border-subtle);
973 border: 1px solid var(--border);
974 border-radius: var(--r-lg);
975 overflow: hidden;
976 }
977 .mkt-feat-cell {
978 padding: var(--s-6);
979 background: var(--bg-elevated);
980 transition: background var(--t-fast) var(--ease);
981 position: relative;
982 }
983 .mkt-feat-cell:hover { background: var(--bg-surface); }
984 .mkt-feat-cell::before {
985 content: '';
986 position: absolute;
987 left: var(--s-6);
988 top: var(--s-6);
989 width: 4px;
990 height: 4px;
991 border-radius: 50%;
992 background: var(--accent);
993 opacity: 0;
994 transition: opacity var(--t-fast) var(--ease);
995 }
996 .mkt-feat-cell:hover::before { opacity: 1; }
997 .mkt-feat-title {
998 font-family: var(--font-display);
999 font-size: var(--t-md);
1000 font-weight: 600;
1001 letter-spacing: -0.012em;
1002 margin: 0 0 var(--s-2);
1003 color: var(--text-strong);
1004 padding-left: 14px;
1005 }
1006 .mkt-feat-desc {
1007 font-size: var(--t-sm);
1008 color: var(--text-muted);
1009 line-height: 1.55;
1010 margin: 0;
1011 padding-left: 14px;
1012 }
1013 @media (max-width: 720px) {
1014 .mkt-feat-grid { grid-template-columns: 1fr; }
1015 }
1016`;
1017
1018const aboutCss = sharedMktCss + `
1019 .mkt-prose {
1020 max-width: 720px;
1021 margin: 0 auto;
1022 }
1023 .mkt-prose h2 {
1024 font-size: clamp(24px, 3vw, 36px);
1025 line-height: 1.15;
1026 letter-spacing: -0.025em;
1027 margin: var(--s-3) 0 var(--s-5);
1028 }
1029 .mkt-prose p {
1030 color: var(--text);
1031 font-size: var(--t-md);
1032 line-height: 1.7;
1033 margin: 0 0 var(--s-4);
1034 }
1035 .mkt-prose p strong { color: var(--text-strong); font-weight: 600; }
1036
1037 .mkt-principles {
1038 display: grid;
1039 grid-template-columns: repeat(3, 1fr);
1040 gap: 16px;
1041 }
1042 .mkt-principle {
1043 background: var(--bg-elevated);
1044 border: 1px solid var(--border);
1045 border-radius: var(--r-lg);
1046 padding: var(--s-7);
1047 transition: border-color var(--t-base) var(--ease), transform var(--t-base) var(--ease-out-quart);
1048 }
1049 .mkt-principle:hover { border-color: var(--border-strong); transform: translateY(-2px); }
1050 .mkt-principle-num {
1051 font-family: var(--font-mono);
1052 font-size: 11px;
1053 color: var(--accent);
1054 background: var(--accent-gradient-faint);
1055 border: 1px solid rgba(140,109,255,0.30);
1056 padding: 3px 9px;
1057 border-radius: var(--r-full);
1058 letter-spacing: 0.06em;
1059 display: inline-block;
1060 margin-bottom: var(--s-4);
1061 }
1062 .mkt-principle-title {
1063 font-family: var(--font-display);
1064 font-size: 19px;
1065 font-weight: 600;
1066 letter-spacing: -0.018em;
1067 margin: 0 0 var(--s-2);
1068 color: var(--text-strong);
1069 }
1070 .mkt-principle-desc {
1071 font-size: var(--t-sm);
1072 color: var(--text-muted);
1073 line-height: 1.6;
1074 margin: 0;
1075 }
1076
1077 .mkt-stack {
1078 display: flex;
1079 flex-wrap: wrap;
1080 gap: 10px;
1081 justify-content: center;
1082 max-width: 880px;
1083 margin: 0 auto;
1084 }
1085 .mkt-stack-pill {
1086 display: inline-flex;
1087 align-items: center;
1088 gap: 8px;
1089 padding: 9px 16px;
1090 background: var(--bg-elevated);
1091 border: 1px solid var(--border);
1092 border-radius: var(--r-full);
1093 transition: border-color var(--t-fast) var(--ease), transform var(--t-fast) var(--ease);
1094 }
1095 .mkt-stack-pill:hover { border-color: rgba(140,109,255,0.4); transform: translateY(-1px); }
1096 .mkt-stack-name {
1097 font-family: var(--font-display);
1098 font-weight: 600;
1099 color: var(--text-strong);
1100 font-size: var(--t-sm);
1101 letter-spacing: -0.01em;
1102 }
1103 .mkt-stack-desc {
1104 font-family: var(--font-mono);
1105 font-size: 11px;
1106 color: var(--text-faint);
1107 letter-spacing: 0.04em;
1108 }
1109
1110 .mkt-contact-grid {
1111 display: grid;
1112 grid-template-columns: repeat(3, 1fr);
1113 gap: 16px;
1114 max-width: 880px;
1115 margin: 0 auto;
1116 }
1117 .mkt-contact-card {
1118 display: block;
1119 padding: var(--s-7);
1120 background: var(--bg-elevated);
1121 border: 1px solid var(--border);
1122 border-radius: var(--r-lg);
1123 text-decoration: none;
1124 transition: border-color var(--t-base) var(--ease), transform var(--t-base) var(--ease-out-quart);
1125 }
1126 .mkt-contact-card:hover {
1127 border-color: rgba(140,109,255,0.4);
1128 transform: translateY(-2px);
1129 text-decoration: none;
1130 }
1131 .mkt-contact-label {
1132 font-family: var(--font-mono);
1133 font-size: 11px;
1134 text-transform: uppercase;
1135 letter-spacing: 0.14em;
1136 color: var(--text-muted);
1137 margin-bottom: var(--s-3);
1138 }
1139 .mkt-contact-email {
1140 font-family: var(--font-display);
1141 font-size: var(--t-md);
1142 font-weight: 600;
1143 letter-spacing: -0.012em;
1144 color: var(--accent);
1145 margin-bottom: var(--s-2);
1146 word-break: break-all;
1147 }
1148 .mkt-contact-line {
1149 font-size: var(--t-sm);
1150 color: var(--text-muted);
1151 line-height: 1.5;
1152 margin: 0;
1153 }
1154
1155 @media (max-width: 880px) {
1156 .mkt-principles { grid-template-columns: repeat(2, 1fr); }
1157 .mkt-contact-grid { grid-template-columns: 1fr; }
1158 }
1159 @media (max-width: 560px) {
1160 .mkt-principles { grid-template-columns: 1fr; }
1161 }
1162`;
1163
1164export default marketing;