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

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.tsxBlame1372 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 <>
fa880f2Claude34 <style dangerouslySetInnerHTML={{ __html: pricingCss }} />
93812e4Claude35 <div class="mkt-page-pricing mkt-root">
b0148e9Claude36 <header class="mkt-hero">
93812e4Claude37 <div class="mkt-hero-orb" aria-hidden="true" />
38 <div class="mkt-hero-inner">
39 <div class="mkt-eyebrow">
40 <span class="mkt-eyebrow-pill" aria-hidden="true">
41 <svg width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round">
42 <line x1="12" y1="1" x2="12" y2="23" />
43 <path d="M17 5H9.5a3.5 3.5 0 0 0 0 7h5a3.5 3.5 0 0 1 0 7H6" />
44 </svg>
45 </span>
46 Pricing
47 </div>
48 <h1 class="display mkt-hero-title">
49 Honest pricing for{" "}
50 <span class="mkt-grad">teams that ship.</span>
51 </h1>
52 <p class="mkt-hero-sub">
53 Self-hosting is free forever. Hosted plans price the AI calls, not
54 the seats. No one pays per developer.
55 </p>
56 </div>
b0148e9Claude57 </header>
58
59 <section class="mkt-pricing-grid stagger">
60 <PricingTier
61 tier="Free"
62 price="$0"
63 cadence="forever"
64 desc="For personal projects + open source. Full AI suite included."
65 features={[
66 "Unlimited public repos",
67 "3 private repos",
68 "5,000 AI calls / month",
69 "GateTest + auto-repair",
70 "Webhooks + workflows",
71 "Community support",
72 ]}
73 cta="Start free"
74 href="/register"
75 />
76 <PricingTier
77 tier="Pro"
78 price="$12"
79 cadence="per user / month"
80 desc="For working developers. Lifts every quota and adds priority routing."
81 features={[
82 "Unlimited private repos",
83 "100,000 AI calls / month",
84 "Priority AI queue",
85 "Custom domains",
86 "Advanced analytics",
44f1a02Claude87 "EU data residency (Frankfurt)",
b0148e9Claude88 "Email support",
89 ]}
90 cta="Go Pro"
91 href="/settings/billing"
92 highlight
93 />
94 <PricingTier
95 tier="Team"
96 price="$29"
97 cadence="per user / month"
98 desc="For organisations running production on Gluecron."
99 features={[
100 "Everything in Pro",
101 "Unlimited AI calls",
102 "Org-level SSO + SCIM",
103 "Audit log retention",
104 "SLA-backed uptime",
105 "Slack support channel",
106 ]}
107 cta="Talk to us"
108 href="mailto:hello@gluecron.com"
109 />
110 <PricingTier
111 tier="Enterprise"
112 price="Custom"
113 cadence="contact us"
114 desc="On-prem deploy, dedicated capacity, 24/7 incident response."
115 features={[
116 "Everything in Team",
117 "On-prem / VPC deploy",
118 "Dedicated AI capacity",
119 "Private model routing",
120 "DPA + custom contracts",
121 "24/7 incident response",
122 ]}
123 cta="Contact sales"
124 href="mailto:enterprise@gluecron.com"
125 />
126 </section>
127
128 <section class="mkt-section">
129 <div class="section-header">
130 <div class="eyebrow">Self-hosted</div>
131 <h2>Run it on your own metal. No license, no telemetry.</h2>
132 <p>
133 Gluecron is a single Bun binary plus Postgres. Deploy to Fly,
134 Railway, your own VPS, or air-gapped infra. Free forever for
135 self-hosters of any size.
136 </p>
137 </div>
138 <div class="mkt-selfhost-card surface-glow">
139 <div class="mkt-selfhost-grid">
140 <div class="mkt-selfhost-cell">
141 <div class="mkt-selfhost-num">$0</div>
142 <div class="mkt-selfhost-label">License</div>
143 </div>
144 <div class="mkt-selfhost-cell">
145 <div class="mkt-selfhost-num">∞</div>
146 <div class="mkt-selfhost-label">Users</div>
147 </div>
148 <div class="mkt-selfhost-cell">
149 <div class="mkt-selfhost-num">∞</div>
150 <div class="mkt-selfhost-label">Repos</div>
151 </div>
152 <div class="mkt-selfhost-cell">
153 <div class="mkt-selfhost-num">0</div>
154 <div class="mkt-selfhost-label">Telemetry</div>
155 </div>
156 </div>
157 <div class="mkt-selfhost-cta">
158 <a href="/help" class="btn btn-primary btn-lg">Self-host guide</a>
159 <a
160 href="https://github.com/ccantynz-alt/Gluecron.com"
161 class="btn btn-ghost btn-lg"
162 >
163 View source
164 </a>
165 </div>
166 </div>
167 </section>
168
169 <section class="mkt-section">
170 <div class="section-header">
171 <div class="eyebrow">Questions</div>
172 <h2>The fine print, in plain English.</h2>
173 </div>
174 <div class="mkt-faq">
175 <FaqItem
176 q="What counts as an AI call?"
177 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."
178 />
179 <FaqItem
180 q="Can I bring my own Anthropic key?"
181 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."
182 />
183 <FaqItem
184 q="What happens if I hit my AI quota?"
185 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."
186 />
187 <FaqItem
188 q="Do you charge for runner minutes?"
189 a="No. Workflow runs are unmetered on hosted plans. Self-hosters bring their own compute, of course."
190 />
191 <FaqItem
192 q="What's the uptime SLA?"
193 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."
194 />
195 <FaqItem
196 q="How do I migrate off Gluecron?"
197 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."
198 />
44f1a02Claude199 <FaqItem
200 q="Is EU data residency available?"
201 a="Yes. Pro plan and above can choose the EU (Frankfurt) data region when creating a repository. All repository data — git objects, issues, PRs — is stored and processed in the EU region. The region is set at creation time and cannot be changed afterwards. You can configure it in the New Repository form or see the current region on your repository Settings page."
202 />
b0148e9Claude203 </div>
204 </section>
205
206 <CtaBlock />
207 </div>
208 </>
209);
210
211const PricingTier: FC<{
212 tier: string;
213 price: string;
214 cadence: string;
215 desc: string;
216 features: string[];
217 cta: string;
218 href: string;
219 highlight?: boolean;
220}> = ({ tier, price, cadence, desc, features, cta, href, highlight }) => (
221 <div class={`mkt-tier${highlight ? " mkt-tier-hl" : ""}`}>
222 {highlight && <div class="mkt-tier-badge">Most popular</div>}
223 <div class="mkt-tier-name">{tier}</div>
224 <div class="mkt-tier-amount">
225 <span class="mkt-tier-num">{price}</span>
226 <span class="mkt-tier-cad">{cadence}</span>
227 </div>
228 <p class="mkt-tier-desc">{desc}</p>
229 <ul class="mkt-tier-features">
230 {features.map((f) => (
231 <li>
232 <span class="mkt-tier-check">{"✓"}</span>
233 {f}
234 </li>
235 ))}
236 </ul>
237 <a
238 href={href}
239 class={`btn ${highlight ? "btn-primary" : "btn-secondary"} btn-block`}
240 style="margin-top:auto"
241 >
242 {cta}
243 </a>
244 </div>
245);
246
247const FaqItem: FC<{ q: string; a: string }> = ({ q, a }) => (
248 <details class="mkt-faq-item">
249 <summary class="mkt-faq-q">
250 <span>{q}</span>
251 <span class="mkt-faq-toggle" aria-hidden="true">{"+"}</span>
252 </summary>
253 <p class="mkt-faq-a">{a}</p>
254 </details>
255);
256
257// ============================================================
258// /features
259// ============================================================
260
261marketing.get("/features", (c) => {
262 const user = c.get("user");
263 return c.html(
264 <Layout title="Features — gluecron" user={user}>
265 <FeaturesPage />
266 </Layout>,
267 );
268});
269
270const FeaturesPage: FC = () => (
271 <>
fa880f2Claude272 <style dangerouslySetInnerHTML={{ __html: featuresCss }} />
93812e4Claude273 <div class="mkt-page-features mkt-root">
b0148e9Claude274 <header class="mkt-hero">
93812e4Claude275 <div class="mkt-hero-orb" aria-hidden="true" />
276 <div class="mkt-hero-inner">
277 <div class="mkt-eyebrow">
278 <span class="mkt-eyebrow-pill" aria-hidden="true">
279 <svg width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round">
280 <polygon points="12 2 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26 12 2" />
281 </svg>
282 </span>
283 Features
284 </div>
285 <h1 class="display mkt-hero-title">
286 A complete dev platform.{" "}
287 <span class="mkt-grad">Nothing extra to buy.</span>
288 </h1>
289 <p class="mkt-hero-sub">
290 Hosting, CI, AI review, security scanning, deploy webhooks,
291 marketplace, packages, pages — every surface you need, ready on
292 day one.
293 </p>
294 </div>
b0148e9Claude295 </header>
296
93812e4Claude297 {/* 3-column feature grid teaser — at-a-glance pitch above the deep dives. */}
298 <section class="mkt-section mkt-feature-teaser">
299 <div class="mkt-teaser-grid">
300 <div class="mkt-teaser-card">
301 <div class="mkt-teaser-icon" aria-hidden="true">
302 <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
303 <path d="M21 11.5a8.38 8.38 0 0 1-.9 3.8 8.5 8.5 0 0 1-7.6 4.7 8.38 8.38 0 0 1-3.8-.9L3 21l1.9-5.7a8.38 8.38 0 0 1-.9-3.8 8.5 8.5 0 0 1 4.7-7.6 8.38 8.38 0 0 1 3.8-.9h.5a8.48 8.48 0 0 1 8 8v.5z" />
304 </svg>
305 </div>
306 <h3 class="mkt-teaser-title">AI is a teammate</h3>
307 <p class="mkt-teaser-desc">
308 Claude reviews every PR, drafts changelogs, opens incident
309 issues, fixes failing gates — labelled and revertable.
310 </p>
311 </div>
312 <div class="mkt-teaser-card">
313 <div class="mkt-teaser-icon" aria-hidden="true">
314 <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
315 <path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z" />
316 </svg>
317 </div>
318 <h3 class="mkt-teaser-title">Green by default</h3>
319 <p class="mkt-teaser-desc">
320 Branch protection, secret scanning, required checks, merge
479dcd9Claude321 queue — pre-wired on every new repo, so problems are caught
322 before they ship.
93812e4Claude323 </p>
324 </div>
325 <div class="mkt-teaser-card">
326 <div class="mkt-teaser-icon" aria-hidden="true">
327 <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
328 <polyline points="22 12 18 12 15 21 9 3 6 12 2 12" />
329 </svg>
330 </div>
331 <h3 class="mkt-teaser-title">Real-time everything</h3>
332 <p class="mkt-teaser-desc">
333 SSE for logs, comments, deploys, presence. No polling, no
334 spinners on tabs you've already loaded.
335 </p>
336 </div>
337 </div>
338 </section>
339
b0148e9Claude340 <FeatureCategory
341 eyebrow="Code intelligence"
342 title="The AI is a teammate, not an upsell."
343 items={[
344 {
345 title: "AI code review",
346 desc: "Real Claude review on every PR open. Inline file/line comments. Idempotent across re-runs.",
347 },
348 {
349 title: "Spec-to-PR",
350 desc: "Drop a feature spec in plain English. AI drafts the entire PR — branch, commits, description.",
351 },
352 {
353 title: "AI security review",
354 desc: "Sonnet 4 reads diffs for OWASP-class issues. Posts as inline review comments, not noise.",
355 },
356 {
357 title: "Auto-repair",
358 desc: "Failed gate? AI tries to fix it and pushes a follow-up commit. Your repo self-corrects.",
359 },
360 {
361 title: "AI commit messages",
362 desc: "One-click 'suggest with AI' on the web editor. Concise, conventional-commit format.",
363 },
364 {
365 title: "AI changelogs",
366 desc: "Generated on release create. Plus an arbitrary-range viewer at /:repo/ai/changelog.",
367 },
368 {
369 title: "AI incident responder",
370 desc: "Failed deploy? AI opens an issue with the failing logs + suggested fix + linked PR.",
371 },
372 {
373 title: "AI test generation",
374 desc: "Drops test stubs for uncovered functions. You review, edit, commit.",
375 },
376 ]}
377 />
378
379 <FeatureCategory
380 eyebrow="Quality gate"
479dcd9Claude381 title="Catch problems before they reach production."
b0148e9Claude382 items={[
383 {
384 title: "GateTest integration",
385 desc: "Push triggers GateTest. Results post back as inline annotations on the commit.",
386 },
387 {
388 title: "Secret scanner",
389 desc: "15 patterns, runs on every push. Blocks the push if a real secret leaks.",
390 },
391 {
392 title: "Branch protection",
393 desc: "Required checks, code-owner reviews, push restrictions, force-push blocking.",
394 },
395 {
396 title: "Repository rulesets",
397 desc: "Named policy bundles. Six rule types from commit message regex to max file size.",
398 },
399 {
400 title: "Required checks matrix",
401 desc: "Per branch-protection list of named checks that must pass before merge.",
402 },
403 {
404 title: "Protected tags",
405 desc: "Owners declare patterns (v*, release-*) that only owners can push.",
406 },
407 {
408 title: "Merge queue",
409 desc: "Serialised merge with re-test against latest base. No more 'green when merged, red on main'.",
410 },
411 {
412 title: "Pre-receive policy",
413 desc: "Ref-name patterns and push policies enforced at the HTTP layer with 403s.",
414 },
415 ]}
416 />
417
418 <FeatureCategory
419 eyebrow="Real-time"
420 title="No polling, no refresh, no waiting."
421 items={[
422 {
423 title: "Live workflow logs",
424 desc: "Step-by-step output streams over SSE the moment your runner emits it.",
425 },
426 {
427 title: "Live PR comments",
428 desc: "New comment in another tab? You see a 'reload to view' banner immediately.",
429 },
430 {
431 title: "Live deploy events",
432 desc: "Crontech-Gluecron event bus pushes deploy state. Watch deploys happen.",
433 },
434 {
435 title: "Live presence",
436 desc: "See who's looking at the same PR right now. Avoid double review work.",
437 },
438 ]}
439 />
440
441 <FeatureCategory
442 eyebrow="Platform"
443 title="Everything GitHub charges extra for."
444 items={[
445 {
446 title: "Workflow runner",
447 desc: "Drop yaml in `.gluecron/workflows/`. Runs on push. Cron triggers, secrets, matrix.",
448 },
449 {
450 title: "Packages registry",
451 desc: "npm protocol. Publish, install, yank with `glc_` PAT auth. Container registry deferred.",
452 },
453 {
454 title: "Pages hosting",
455 desc: "Serves blobs from the latest gh-pages commit. Custom domains, short cache headers.",
456 },
457 {
458 title: "Marketplace + apps",
459 desc: "Install third-party apps with permission scopes. App-bot push auth via ghi_ tokens.",
460 },
461 {
462 title: "Discussions",
463 desc: "Categorised threads, pinned, locked, Q&A answers. Zero extra config.",
464 },
465 {
466 title: "Wikis + Gists + Projects",
467 desc: "All shipped, all free, all integrated. No upsell tier.",
468 },
469 ]}
470 />
471
472 <FeatureCategory
473 eyebrow="Identity + governance"
474 title="Enterprise-tier auth from day one."
475 items={[
476 {
477 title: "TOTP + WebAuthn",
478 desc: "Both 2FA paths shipped. Passkey-only login if you want it.",
479 },
480 {
481 title: "OIDC SSO",
482 desc: "Okta, Azure AD, Auth0, Google Workspace. Auto-create users. Email-domain allowlist.",
483 },
484 {
485 title: "OAuth provider",
486 desc: "Third-party apps request scoped access to user repos. Standard auth-code flow.",
487 },
488 {
489 title: "Personal access tokens",
490 desc: "SHA-256 hashed, scoped, revocable. The clean way to script Gluecron.",
491 },
492 {
493 title: "Audit log (per-user + per-repo)",
494 desc: "Every sensitive action recorded. Browseable at /settings/audit.",
495 },
496 {
497 title: "Site admin panel",
498 desc: "/admin for site-wide flags: registration locks, banners, read-only mode.",
499 },
500 ]}
501 />
502
503 <FeatureCategory
504 eyebrow="Integrations"
505 title="Speak every protocol your tools use."
506 items={[
507 {
508 title: "MCP server",
509 desc: "Claude Desktop, Cursor, Code, Cline plug in natively. Read + scoped write tools.",
510 },
511 {
512 title: "REST API v2",
513 desc: "Full CRUD across resources. Versioned, documented, stable.",
514 },
515 {
516 title: "GraphQL endpoint",
517 desc: "Single-request fetch for complex client views. GraphiQL explorer at /api/graphql.",
518 },
519 {
520 title: "Webhooks",
521 desc: "HMAC-signed outbound to your URLs on push, issue, PR, star, comment, deploy.",
522 },
523 {
524 title: "Smart-HTTP git",
525 desc: "git push, git pull, git clone — exactly what your tools already speak.",
526 },
527 {
528 title: "VS Code extension",
529 desc: "Explain, open-on-web, semantic search, generate tests — all from the editor.",
530 },
531 ]}
532 />
533
534 <CtaBlock />
535 </div>
536 </>
537);
538
539const FeatureCategory: FC<{
540 eyebrow: string;
541 title: string;
542 items: Array<{ title: string; desc: string }>;
543}> = ({ eyebrow, title, items }) => (
544 <section class="mkt-section">
545 <div class="section-header left">
546 <div class="eyebrow">{eyebrow}</div>
547 <h2>{title}</h2>
548 </div>
549 <div class="mkt-feat-grid stagger">
550 {items.map((it) => (
551 <div class="mkt-feat-cell">
552 <h3 class="mkt-feat-title">{it.title}</h3>
553 <p class="mkt-feat-desc">{it.desc}</p>
554 </div>
555 ))}
556 </div>
557 </section>
558);
559
560// ============================================================
561// /about
562// ============================================================
563
564marketing.get("/about", (c) => {
565 const user = c.get("user");
566 return c.html(
567 <Layout title="About — gluecron" user={user}>
568 <AboutPage />
569 </Layout>,
570 );
571});
572
573const AboutPage: FC = () => (
574 <>
fa880f2Claude575 <style dangerouslySetInnerHTML={{ __html: aboutCss }} />
93812e4Claude576 <div class="mkt-page-about mkt-root">
b0148e9Claude577 <header class="mkt-hero">
93812e4Claude578 <div class="mkt-hero-orb" aria-hidden="true" />
579 <div class="mkt-hero-inner">
580 <div class="mkt-eyebrow">
581 <span class="mkt-eyebrow-pill" aria-hidden="true">
582 <svg width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round">
583 <circle cx="12" cy="12" r="10" />
584 <line x1="12" y1="16" x2="12" y2="12" />
585 <line x1="12" y1="8" x2="12.01" y2="8" />
586 </svg>
587 </span>
588 About
589 </div>
590 <h1 class="display mkt-hero-title">
479dcd9Claude591 We're building the git platform{" "}
592 <span class="mkt-grad">for AI-assisted teams.</span>
93812e4Claude593 </h1>
594 <p class="mkt-hero-sub">
479dcd9Claude595 More and more code is written — and reviewed — with AI assistance.
596 The platforms hosting that code were designed for an earlier
597 workflow. Gluecron is built for this one.
93812e4Claude598 </p>
599 </div>
b0148e9Claude600 </header>
601
602 <section class="mkt-section">
603 <div class="mkt-prose">
604 <div class="eyebrow">Mission</div>
605 <h2>An IDE for your repo. A teammate, not a sidebar.</h2>
606 <p>
607 GitHub treats AI as a feature you bolt onto your workflow. We
608 treat it as a peer that opens PRs, reviews diffs, fixes
609 regressions, and ships its own changes — visibly, accountably,
610 with a real identity in your history.
611 </p>
612 <p>
613 Everything is green by default. Every new repo auto-configures
614 gates, branch protection, labels, CODEOWNERS, and a welcome issue.
615 Users opt out per feature. Defaults are maximum-green so
479dcd9Claude616 <strong> broken changes are caught before they reach production
617 — or your customers.</strong>
b0148e9Claude618 </p>
619 </div>
620 </section>
621
622 <section class="mkt-section">
623 <div class="section-header">
624 <div class="eyebrow">Principles</div>
625 <h2>Six rules we don't compromise on.</h2>
626 </div>
627 <div class="mkt-principles stagger">
628 <PrincipleCard
629 n="01"
630 title="No vendor lock"
631 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."
632 />
633 <PrincipleCard
634 n="02"
635 title="No surprise bills"
636 desc="AI quotas degrade gracefully when hit. No overage, no automatic upgrade. We publish prices in dollars, not credits."
637 />
638 <PrincipleCard
639 n="03"
640 title="Self-host is first-class"
641 desc="Single Bun binary, single Postgres, zero telemetry. The same product the hosted version runs on, free forever for self-hosters."
642 />
643 <PrincipleCard
644 n="04"
645 title="AI is accountable"
646 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."
647 />
648 <PrincipleCard
649 n="05"
650 title="Real-time over polling"
651 desc="SSE for logs, comments, deploys, presence. The web should feel like a desktop app. No spinners on a tab you've already loaded."
652 />
653 <PrincipleCard
654 n="06"
655 title="Open by default"
656 desc="REST + GraphQL + MCP + Smart-HTTP + webhooks. Your tools speak our platform without asking. Programmatic access is not an enterprise tier."
657 />
658 </div>
659 </section>
660
661 <section class="mkt-section">
662 <div class="section-header">
663 <div class="eyebrow">Stack</div>
664 <h2>Built on the boring, fast parts.</h2>
665 </div>
666 <div class="mkt-stack">
667 <StackPill name="Bun" desc="runtime" />
668 <StackPill name="Hono" desc="server framework" />
669 <StackPill name="Drizzle" desc="ORM" />
670 <StackPill name="Neon Postgres" desc="primary database" />
671 <StackPill name="Claude Sonnet 4" desc="AI review + chat" />
672 <StackPill name="Claude Haiku 4.5" desc="AI commits + summaries" />
673 <StackPill name="Smart-HTTP git" desc="protocol" />
674 <StackPill name="Fly.io" desc="deploy target" />
675 </div>
676 </section>
677
678 <section class="mkt-section mkt-contact">
679 <div class="section-header">
680 <div class="eyebrow">Contact</div>
681 <h2>Three places to find us.</h2>
682 </div>
683 <div class="mkt-contact-grid">
684 <ContactCard
685 label="Product + sales"
686 email="hello@gluecron.com"
687 line="For demos, pricing questions, partnership ideas."
688 />
689 <ContactCard
690 label="Security"
691 email="security@gluecron.com"
692 line="Responsible disclosure. PGP key on request."
693 />
694 <ContactCard
695 label="Support"
696 email="support@gluecron.com"
697 line="Bugs, account help, anything that's broken."
698 />
699 </div>
700 </section>
701
702 <CtaBlock />
703 </div>
704 </>
705);
706
707const PrincipleCard: FC<{ n: string; title: string; desc: string }> = ({
708 n,
709 title,
710 desc,
711}) => (
712 <div class="mkt-principle">
713 <div class="mkt-principle-num">{n}</div>
714 <h3 class="mkt-principle-title">{title}</h3>
715 <p class="mkt-principle-desc">{desc}</p>
716 </div>
717);
718
719const StackPill: FC<{ name: string; desc: string }> = ({ name, desc }) => (
720 <div class="mkt-stack-pill">
721 <span class="mkt-stack-name">{name}</span>
722 <span class="mkt-stack-desc">{desc}</span>
723 </div>
724);
725
726const ContactCard: FC<{ label: string; email: string; line: string }> = ({
727 label,
728 email,
729 line,
730}) => (
731 <a href={`mailto:${email}`} class="mkt-contact-card">
732 <div class="mkt-contact-label">{label}</div>
733 <div class="mkt-contact-email">{email}</div>
734 <p class="mkt-contact-line">{line}</p>
735 </a>
736);
737
738// ============================================================
739// Shared closing CTA block
740// ============================================================
741
742const CtaBlock: FC = () => (
743 <section class="mkt-cta">
744 <div class="mkt-cta-card">
745 <div class="mkt-cta-bg" aria-hidden="true" />
746 <div class="eyebrow">Get started</div>
747 <h2 class="mkt-cta-title">
748 Stop maintaining the platform.<br />
749 <span class="gradient-text">Start shipping the product.</span>
750 </h2>
751 <div class="mkt-cta-buttons">
752 <a href="/register" class="btn btn-primary btn-xl">
753 Create your account
754 <span aria-hidden="true">{"→"}</span>
755 </a>
756 <a href="/import" class="btn btn-ghost btn-xl">
757 Migrate from GitHub
758 </a>
759 </div>
760 </div>
761 </section>
762);
763
764// ============================================================
765// Styles — namespaced under .mkt- so they don't leak
766// ============================================================
767
768const sharedMktCss = `
769 .mkt-root {
770 max-width: 1180px;
771 margin: 0 auto;
772 padding: 0 16px;
773 }
774
93812e4Claude775 /* ───── 2026 hero polish ─────
776 Three pages share the same shell (.mkt-page-pricing/-features/-about);
777 each adds its own page-scoped class to qualify selectors so this
778 block can't leak. */
779 .mkt-page-pricing .mkt-hero,
780 .mkt-page-features .mkt-hero,
781 .mkt-page-about .mkt-hero {
b0148e9Claude782 position: relative;
93812e4Claude783 text-align: center;
784 margin: var(--s-10) auto var(--s-12);
785 max-width: 980px;
786 padding: clamp(28px, 4vw, 56px) clamp(24px, 4vw, 48px);
787 background: var(--bg-elevated);
788 border: 1px solid var(--border);
789 border-radius: 22px;
790 overflow: hidden;
791 box-shadow: 0 1px 0 rgba(255,255,255,0.04), 0 22px 56px -20px rgba(0,0,0,0.45);
b0148e9Claude792 }
93812e4Claude793 .mkt-page-pricing .mkt-hero::before,
794 .mkt-page-features .mkt-hero::before,
795 .mkt-page-about .mkt-hero::before {
b0148e9Claude796 content: '';
797 position: absolute;
93812e4Claude798 top: 0; left: 0; right: 0;
799 height: 2px;
479dcd9Claude800 background: linear-gradient(90deg, transparent 0%, rgba(91,110,232,0.55) 50%, transparent 100%);
801 opacity: 0.6;
93812e4Claude802 pointer-events: none;
803 z-index: 2;
804 }
805 .mkt-page-pricing .mkt-hero-orb,
806 .mkt-page-features .mkt-hero-orb,
807 .mkt-page-about .mkt-hero-orb {
808 position: absolute;
809 inset: -28% -10% auto auto;
810 width: 520px; height: 520px;
479dcd9Claude811 background: radial-gradient(circle, rgba(91,110,232,0.08), transparent 70%);
93812e4Claude812 filter: blur(80px);
479dcd9Claude813 opacity: 0.6;
b0148e9Claude814 pointer-events: none;
93812e4Claude815 z-index: 0;
816 }
817 .mkt-page-pricing .mkt-hero-inner,
818 .mkt-page-features .mkt-hero-inner,
819 .mkt-page-about .mkt-hero-inner { position: relative; z-index: 1; }
820
821 .mkt-page-pricing .mkt-eyebrow,
822 .mkt-page-features .mkt-eyebrow,
823 .mkt-page-about .mkt-eyebrow {
824 display: inline-flex;
825 align-items: center;
826 gap: 8px;
827 font-family: var(--font-mono);
828 font-size: 11.5px;
829 text-transform: uppercase;
830 letter-spacing: 0.14em;
831 color: var(--text-muted);
832 font-weight: 600;
833 margin-bottom: 14px;
b0148e9Claude834 }
93812e4Claude835 .mkt-page-pricing .mkt-eyebrow-pill,
836 .mkt-page-features .mkt-eyebrow-pill,
837 .mkt-page-about .mkt-eyebrow-pill {
838 display: inline-flex;
839 align-items: center;
840 justify-content: center;
841 width: 18px; height: 18px;
842 border-radius: 6px;
479dcd9Claude843 background: rgba(91,110,232,0.12);
844 color: var(--text-link);
845 box-shadow: inset 0 0 0 1px rgba(91,110,232,0.30);
93812e4Claude846 }
847 .mkt-page-pricing .mkt-grad,
848 .mkt-page-features .mkt-grad,
849 .mkt-page-about .mkt-grad {
479dcd9Claude850 background-image: linear-gradient(135deg, var(--accent-hover) 0%, var(--accent) 100%);
93812e4Claude851 -webkit-background-clip: text;
852 background-clip: text;
853 -webkit-text-fill-color: transparent;
854 color: transparent;
855 }
856 .mkt-page-pricing .mkt-hero-title,
857 .mkt-page-features .mkt-hero-title,
858 .mkt-page-about .mkt-hero-title {
859 font-family: var(--font-display);
b0148e9Claude860 font-size: clamp(36px, 6.5vw, 76px);
861 line-height: 1.02;
862 letter-spacing: -0.038em;
93812e4Claude863 font-weight: 800;
b0148e9Claude864 margin: 0 0 var(--s-5);
93812e4Claude865 color: var(--text-strong);
b0148e9Claude866 }
93812e4Claude867 .mkt-page-pricing .mkt-hero-sub,
868 .mkt-page-features .mkt-hero-sub,
869 .mkt-page-about .mkt-hero-sub {
b0148e9Claude870 font-size: clamp(15px, 1.5vw, 18px);
871 color: var(--text-muted);
872 max-width: 640px;
873 margin: 0 auto;
874 line-height: 1.55;
875 }
876
877 /* Section spacing */
878 .mkt-section { margin: var(--s-16) auto; }
879
880 /* Closing CTA */
881 .mkt-cta { margin: var(--s-20) auto var(--s-12); }
882 .mkt-cta-card {
883 position: relative;
884 text-align: center;
885 padding: var(--s-14) var(--s-7);
886 border: 1px solid var(--border-strong);
887 border-radius: var(--r-2xl);
888 background: var(--bg-elevated);
889 overflow: hidden;
890 isolation: isolate;
891 }
892 .mkt-cta-bg {
893 position: absolute;
894 inset: 0;
895 z-index: -1;
896 background:
479dcd9Claude897 radial-gradient(60% 100% at 50% 0%, rgba(91,110,232,0.08), transparent 65%);
b0148e9Claude898 }
899 .mkt-cta-card .eyebrow { justify-content: center; }
900 .mkt-cta-title {
901 font-family: var(--font-display);
902 font-size: clamp(28px, 4vw, 52px);
903 line-height: 1.05;
904 letter-spacing: -0.03em;
905 font-weight: 600;
906 margin: var(--s-3) 0 var(--s-7);
907 color: var(--text-strong);
908 }
909 .mkt-cta-buttons {
910 display: flex;
911 gap: 12px;
912 justify-content: center;
913 flex-wrap: wrap;
914 }
915
916 @media (max-width: 640px) {
917 .mkt-hero { padding: var(--s-10) 0 var(--s-8); }
918 .mkt-section { margin: var(--s-12) auto; }
919 .mkt-cta-buttons .btn { width: 100%; justify-content: center; }
920 }
921`;
922
923const pricingCss = sharedMktCss + `
924 .mkt-pricing-grid {
925 display: grid;
926 grid-template-columns: repeat(4, 1fr);
927 gap: 12px;
928 margin: var(--s-12) auto var(--s-16);
929 align-items: stretch;
930 }
931 .mkt-tier {
932 position: relative;
933 background: var(--bg-elevated);
934 border: 1px solid var(--border);
935 border-radius: var(--r-lg);
936 padding: var(--s-7) var(--s-6);
937 display: flex;
938 flex-direction: column;
939 gap: var(--s-4);
940 transition: border-color var(--t-base) var(--ease), transform var(--t-base) var(--ease-out-quart);
941 }
942 .mkt-tier:hover { border-color: var(--border-strong); transform: translateY(-3px); }
943 .mkt-tier-hl {
479dcd9Claude944 border-color: rgba(91,110,232,0.40);
945 box-shadow: var(--elev-2), 0 0 0 1px rgba(91,110,232,0.30);
b0148e9Claude946 background:
479dcd9Claude947 linear-gradient(180deg, rgba(91,110,232,0.05), transparent 50%),
b0148e9Claude948 var(--bg-elevated);
949 }
479dcd9Claude950 .mkt-tier-hl:hover { border-color: rgba(91,110,232,0.60); }
b0148e9Claude951 .mkt-tier-badge {
952 position: absolute;
953 top: -10px;
954 left: 50%;
955 transform: translateX(-50%);
956 padding: 3px 12px;
957 background: var(--accent-gradient);
958 color: #fff;
959 font-family: var(--font-mono);
960 font-size: 10px;
961 letter-spacing: 0.1em;
962 text-transform: uppercase;
963 font-weight: 600;
964 border-radius: var(--r-full);
479dcd9Claude965 box-shadow: 0 4px 14px -2px rgba(0,0,0,0.40);
b0148e9Claude966 white-space: nowrap;
967 }
968 .mkt-tier-name {
969 font-family: var(--font-mono);
970 font-size: 11px;
971 text-transform: uppercase;
972 letter-spacing: 0.16em;
973 color: var(--text-muted);
974 }
975 .mkt-tier-amount {
976 display: flex;
977 align-items: baseline;
978 gap: 6px;
979 flex-wrap: wrap;
980 }
981 .mkt-tier-num {
982 font-family: var(--font-display);
983 font-size: 36px;
984 font-weight: 600;
985 letter-spacing: -0.03em;
986 color: var(--text-strong);
987 }
988 .mkt-tier-cad {
989 font-size: 12px;
990 color: var(--text-faint);
991 }
992 .mkt-tier-desc {
993 font-size: var(--t-sm);
994 color: var(--text-muted);
995 line-height: 1.5;
996 margin: 0;
997 }
998 .mkt-tier-features {
999 list-style: none;
1000 padding: 0;
1001 margin: 0;
1002 display: flex;
1003 flex-direction: column;
1004 gap: 7px;
1005 font-size: var(--t-sm);
1006 color: var(--text);
1007 }
1008 .mkt-tier-features li {
1009 display: flex;
1010 align-items: flex-start;
1011 gap: 9px;
1012 line-height: 1.45;
1013 }
1014 .mkt-tier-check {
1015 color: var(--accent);
1016 font-weight: 600;
1017 flex-shrink: 0;
1018 line-height: 1.45;
1019 }
1020
1021 .mkt-selfhost-card {
1022 max-width: 880px;
1023 margin: 0 auto;
1024 padding: var(--s-10);
1025 text-align: center;
1026 }
1027 .mkt-selfhost-grid {
1028 display: grid;
1029 grid-template-columns: repeat(4, 1fr);
1030 gap: var(--s-6);
1031 margin-bottom: var(--s-8);
1032 padding-bottom: var(--s-8);
1033 border-bottom: 1px solid var(--border-subtle);
1034 }
1035 .mkt-selfhost-cell { text-align: center; }
1036 .mkt-selfhost-num {
1037 font-family: var(--font-display);
1038 font-size: 44px;
1039 font-weight: 600;
1040 letter-spacing: -0.03em;
1041 color: var(--text-strong);
1042 line-height: 1;
1043 background: var(--accent-gradient);
1044 -webkit-background-clip: text;
1045 background-clip: text;
1046 -webkit-text-fill-color: transparent;
1047 }
1048 .mkt-selfhost-label {
1049 font-family: var(--font-mono);
1050 font-size: 11px;
1051 text-transform: uppercase;
1052 letter-spacing: 0.14em;
1053 color: var(--text-muted);
1054 margin-top: var(--s-2);
1055 }
1056 .mkt-selfhost-cta {
1057 display: flex;
1058 gap: 12px;
1059 justify-content: center;
1060 flex-wrap: wrap;
1061 }
1062
1063 .mkt-faq {
1064 max-width: 760px;
1065 margin: 0 auto;
1066 border: 1px solid var(--border);
1067 border-radius: var(--r-lg);
1068 overflow: hidden;
1069 background: var(--bg-elevated);
1070 }
1071 .mkt-faq-item {
1072 border-bottom: 1px solid var(--border-subtle);
1073 }
1074 .mkt-faq-item:last-child { border-bottom: none; }
1075 .mkt-faq-q {
1076 display: flex;
1077 justify-content: space-between;
1078 align-items: center;
1079 gap: 16px;
1080 padding: 18px 24px;
1081 cursor: pointer;
1082 font-size: var(--t-md);
1083 font-weight: 500;
1084 color: var(--text-strong);
1085 list-style: none;
1086 transition: background var(--t-fast) var(--ease);
1087 }
1088 .mkt-faq-q::-webkit-details-marker { display: none; }
1089 .mkt-faq-q:hover { background: var(--bg-hover); }
1090 .mkt-faq-toggle {
1091 font-family: var(--font-mono);
1092 font-size: 18px;
1093 color: var(--text-muted);
1094 transition: transform var(--t-base) var(--ease-spring);
1095 flex-shrink: 0;
1096 }
1097 .mkt-faq-item[open] .mkt-faq-toggle { transform: rotate(45deg); color: var(--accent); }
1098 .mkt-faq-a {
1099 padding: 0 24px 20px;
1100 color: var(--text-muted);
1101 font-size: var(--t-sm);
1102 line-height: 1.6;
1103 margin: 0;
1104 }
1105
1106 @media (max-width: 960px) {
1107 .mkt-pricing-grid { grid-template-columns: repeat(2, 1fr); }
1108 }
1109 @media (max-width: 640px) {
1110 .mkt-pricing-grid { grid-template-columns: 1fr; }
1111 .mkt-selfhost-grid { grid-template-columns: repeat(2, 1fr); }
1112 .mkt-selfhost-card { padding: var(--s-7); }
1113 }
1114`;
1115
1116const featuresCss = sharedMktCss + `
93812e4Claude1117 /* 3-col feature teaser — sits between the hero and the deep category grids */
1118 .mkt-page-features .mkt-feature-teaser { margin: var(--s-8) auto var(--s-12); }
1119 .mkt-page-features .mkt-teaser-grid {
1120 display: grid;
1121 grid-template-columns: repeat(3, 1fr);
1122 gap: 14px;
1123 }
1124 .mkt-page-features .mkt-teaser-card {
1125 position: relative;
1126 padding: var(--s-7) var(--s-6);
1127 background: var(--bg-elevated);
1128 border: 1px solid var(--border);
1129 border-radius: 16px;
1130 transition: border-color 180ms ease, transform 180ms ease, box-shadow 180ms ease;
1131 overflow: hidden;
1132 }
1133 .mkt-page-features .mkt-teaser-card::before {
1134 content: '';
1135 position: absolute;
1136 top: 0; left: 0; right: 0;
1137 height: 1px;
479dcd9Claude1138 background: linear-gradient(90deg, transparent 0%, rgba(91,110,232,0.40) 50%, transparent 100%);
1139 opacity: 0.6;
93812e4Claude1140 }
1141 .mkt-page-features .mkt-teaser-card:hover {
479dcd9Claude1142 border-color: rgba(91,110,232,0.40);
93812e4Claude1143 transform: translateY(-3px);
479dcd9Claude1144 box-shadow: 0 14px 36px -18px rgba(0,0,0,0.45);
93812e4Claude1145 }
1146 .mkt-page-features .mkt-teaser-icon {
1147 display: inline-flex;
1148 align-items: center;
1149 justify-content: center;
1150 width: 40px; height: 40px;
1151 border-radius: 12px;
479dcd9Claude1152 background: rgba(91,110,232,0.12);
1153 color: var(--text-link);
1154 box-shadow: inset 0 0 0 1px rgba(91,110,232,0.28);
93812e4Claude1155 margin-bottom: var(--s-4);
1156 }
1157 .mkt-page-features .mkt-teaser-title {
1158 font-family: var(--font-display);
1159 font-size: 18px;
1160 font-weight: 700;
1161 letter-spacing: -0.018em;
1162 color: var(--text-strong);
1163 margin: 0 0 var(--s-2);
1164 }
1165 .mkt-page-features .mkt-teaser-desc {
1166 font-size: var(--t-sm);
1167 color: var(--text-muted);
1168 line-height: 1.55;
1169 margin: 0;
1170 }
1171 @media (max-width: 880px) {
1172 .mkt-page-features .mkt-teaser-grid { grid-template-columns: 1fr; }
1173 }
1174
b0148e9Claude1175 .mkt-feat-grid {
1176 display: grid;
1177 grid-template-columns: repeat(2, 1fr);
1178 gap: 1px;
1179 background: var(--border-subtle);
1180 border: 1px solid var(--border);
1181 border-radius: var(--r-lg);
1182 overflow: hidden;
1183 }
1184 .mkt-feat-cell {
1185 padding: var(--s-6);
1186 background: var(--bg-elevated);
1187 transition: background var(--t-fast) var(--ease);
1188 position: relative;
1189 }
1190 .mkt-feat-cell:hover { background: var(--bg-surface); }
1191 .mkt-feat-cell::before {
1192 content: '';
1193 position: absolute;
1194 left: var(--s-6);
1195 top: var(--s-6);
1196 width: 4px;
1197 height: 4px;
1198 border-radius: 50%;
1199 background: var(--accent);
1200 opacity: 0;
1201 transition: opacity var(--t-fast) var(--ease);
1202 }
1203 .mkt-feat-cell:hover::before { opacity: 1; }
1204 .mkt-feat-title {
1205 font-family: var(--font-display);
1206 font-size: var(--t-md);
1207 font-weight: 600;
1208 letter-spacing: -0.012em;
1209 margin: 0 0 var(--s-2);
1210 color: var(--text-strong);
1211 padding-left: 14px;
1212 }
1213 .mkt-feat-desc {
1214 font-size: var(--t-sm);
1215 color: var(--text-muted);
1216 line-height: 1.55;
1217 margin: 0;
1218 padding-left: 14px;
1219 }
1220 @media (max-width: 720px) {
1221 .mkt-feat-grid { grid-template-columns: 1fr; }
1222 }
1223`;
1224
1225const aboutCss = sharedMktCss + `
1226 .mkt-prose {
1227 max-width: 720px;
1228 margin: 0 auto;
1229 }
1230 .mkt-prose h2 {
1231 font-size: clamp(24px, 3vw, 36px);
1232 line-height: 1.15;
1233 letter-spacing: -0.025em;
1234 margin: var(--s-3) 0 var(--s-5);
1235 }
1236 .mkt-prose p {
1237 color: var(--text);
1238 font-size: var(--t-md);
1239 line-height: 1.7;
1240 margin: 0 0 var(--s-4);
1241 }
1242 .mkt-prose p strong { color: var(--text-strong); font-weight: 600; }
1243
1244 .mkt-principles {
1245 display: grid;
1246 grid-template-columns: repeat(3, 1fr);
1247 gap: 16px;
1248 }
1249 .mkt-principle {
1250 background: var(--bg-elevated);
1251 border: 1px solid var(--border);
1252 border-radius: var(--r-lg);
1253 padding: var(--s-7);
1254 transition: border-color var(--t-base) var(--ease), transform var(--t-base) var(--ease-out-quart);
1255 }
1256 .mkt-principle:hover { border-color: var(--border-strong); transform: translateY(-2px); }
1257 .mkt-principle-num {
1258 font-family: var(--font-mono);
1259 font-size: 11px;
1260 color: var(--accent);
1261 background: var(--accent-gradient-faint);
479dcd9Claude1262 border: 1px solid rgba(91,110,232,0.30);
b0148e9Claude1263 padding: 3px 9px;
1264 border-radius: var(--r-full);
1265 letter-spacing: 0.06em;
1266 display: inline-block;
1267 margin-bottom: var(--s-4);
1268 }
1269 .mkt-principle-title {
1270 font-family: var(--font-display);
1271 font-size: 19px;
1272 font-weight: 600;
1273 letter-spacing: -0.018em;
1274 margin: 0 0 var(--s-2);
1275 color: var(--text-strong);
1276 }
1277 .mkt-principle-desc {
1278 font-size: var(--t-sm);
1279 color: var(--text-muted);
1280 line-height: 1.6;
1281 margin: 0;
1282 }
1283
1284 .mkt-stack {
1285 display: flex;
1286 flex-wrap: wrap;
1287 gap: 10px;
1288 justify-content: center;
1289 max-width: 880px;
1290 margin: 0 auto;
1291 }
1292 .mkt-stack-pill {
1293 display: inline-flex;
1294 align-items: center;
1295 gap: 8px;
1296 padding: 9px 16px;
1297 background: var(--bg-elevated);
1298 border: 1px solid var(--border);
1299 border-radius: var(--r-full);
1300 transition: border-color var(--t-fast) var(--ease), transform var(--t-fast) var(--ease);
1301 }
479dcd9Claude1302 .mkt-stack-pill:hover { border-color: rgba(91,110,232,0.4); transform: translateY(-1px); }
b0148e9Claude1303 .mkt-stack-name {
1304 font-family: var(--font-display);
1305 font-weight: 600;
1306 color: var(--text-strong);
1307 font-size: var(--t-sm);
1308 letter-spacing: -0.01em;
1309 }
1310 .mkt-stack-desc {
1311 font-family: var(--font-mono);
1312 font-size: 11px;
1313 color: var(--text-faint);
1314 letter-spacing: 0.04em;
1315 }
1316
1317 .mkt-contact-grid {
1318 display: grid;
1319 grid-template-columns: repeat(3, 1fr);
1320 gap: 16px;
1321 max-width: 880px;
1322 margin: 0 auto;
1323 }
1324 .mkt-contact-card {
1325 display: block;
1326 padding: var(--s-7);
1327 background: var(--bg-elevated);
1328 border: 1px solid var(--border);
1329 border-radius: var(--r-lg);
1330 text-decoration: none;
1331 transition: border-color var(--t-base) var(--ease), transform var(--t-base) var(--ease-out-quart);
1332 }
1333 .mkt-contact-card:hover {
479dcd9Claude1334 border-color: rgba(91,110,232,0.4);
b0148e9Claude1335 transform: translateY(-2px);
1336 text-decoration: none;
1337 }
1338 .mkt-contact-label {
1339 font-family: var(--font-mono);
1340 font-size: 11px;
1341 text-transform: uppercase;
1342 letter-spacing: 0.14em;
1343 color: var(--text-muted);
1344 margin-bottom: var(--s-3);
1345 }
1346 .mkt-contact-email {
1347 font-family: var(--font-display);
1348 font-size: var(--t-md);
1349 font-weight: 600;
1350 letter-spacing: -0.012em;
1351 color: var(--accent);
1352 margin-bottom: var(--s-2);
1353 word-break: break-all;
1354 }
1355 .mkt-contact-line {
1356 font-size: var(--t-sm);
1357 color: var(--text-muted);
1358 line-height: 1.5;
1359 margin: 0;
1360 }
1361
1362 @media (max-width: 880px) {
1363 .mkt-principles { grid-template-columns: repeat(2, 1fr); }
1364 .mkt-contact-grid { grid-template-columns: 1fr; }
1365 }
1366 @media (max-width: 560px) {
1367 .mkt-principles { grid-template-columns: 1fr; }
1368 }
1369`;
1370
4551df3Claude1371
b0148e9Claude1372export default marketing;