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
321 queue — pre-wired on every new repo. Nothing broken ships.
322 </p>
323 </div>
324 <div class="mkt-teaser-card">
325 <div class="mkt-teaser-icon" aria-hidden="true">
326 <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
327 <polyline points="22 12 18 12 15 21 9 3 6 12 2 12" />
328 </svg>
329 </div>
330 <h3 class="mkt-teaser-title">Real-time everything</h3>
331 <p class="mkt-teaser-desc">
332 SSE for logs, comments, deploys, presence. No polling, no
333 spinners on tabs you've already loaded.
334 </p>
335 </div>
336 </div>
337 </section>
338
b0148e9Claude339 <FeatureCategory
340 eyebrow="Code intelligence"
341 title="The AI is a teammate, not an upsell."
342 items={[
343 {
344 title: "AI code review",
345 desc: "Real Claude review on every PR open. Inline file/line comments. Idempotent across re-runs.",
346 },
347 {
348 title: "Spec-to-PR",
349 desc: "Drop a feature spec in plain English. AI drafts the entire PR — branch, commits, description.",
350 },
351 {
352 title: "AI security review",
353 desc: "Sonnet 4 reads diffs for OWASP-class issues. Posts as inline review comments, not noise.",
354 },
355 {
356 title: "Auto-repair",
357 desc: "Failed gate? AI tries to fix it and pushes a follow-up commit. Your repo self-corrects.",
358 },
359 {
360 title: "AI commit messages",
361 desc: "One-click 'suggest with AI' on the web editor. Concise, conventional-commit format.",
362 },
363 {
364 title: "AI changelogs",
365 desc: "Generated on release create. Plus an arbitrary-range viewer at /:repo/ai/changelog.",
366 },
367 {
368 title: "AI incident responder",
369 desc: "Failed deploy? AI opens an issue with the failing logs + suggested fix + linked PR.",
370 },
371 {
372 title: "AI test generation",
373 desc: "Drops test stubs for uncovered functions. You review, edit, commit.",
374 },
375 ]}
376 />
377
378 <FeatureCategory
379 eyebrow="Quality gate"
380 title="Nothing broken ever reaches production."
381 items={[
382 {
383 title: "GateTest integration",
384 desc: "Push triggers GateTest. Results post back as inline annotations on the commit.",
385 },
386 {
387 title: "Secret scanner",
388 desc: "15 patterns, runs on every push. Blocks the push if a real secret leaks.",
389 },
390 {
391 title: "Branch protection",
392 desc: "Required checks, code-owner reviews, push restrictions, force-push blocking.",
393 },
394 {
395 title: "Repository rulesets",
396 desc: "Named policy bundles. Six rule types from commit message regex to max file size.",
397 },
398 {
399 title: "Required checks matrix",
400 desc: "Per branch-protection list of named checks that must pass before merge.",
401 },
402 {
403 title: "Protected tags",
404 desc: "Owners declare patterns (v*, release-*) that only owners can push.",
405 },
406 {
407 title: "Merge queue",
408 desc: "Serialised merge with re-test against latest base. No more 'green when merged, red on main'.",
409 },
410 {
411 title: "Pre-receive policy",
412 desc: "Ref-name patterns and push policies enforced at the HTTP layer with 403s.",
413 },
414 ]}
415 />
416
417 <FeatureCategory
418 eyebrow="Real-time"
419 title="No polling, no refresh, no waiting."
420 items={[
421 {
422 title: "Live workflow logs",
423 desc: "Step-by-step output streams over SSE the moment your runner emits it.",
424 },
425 {
426 title: "Live PR comments",
427 desc: "New comment in another tab? You see a 'reload to view' banner immediately.",
428 },
429 {
430 title: "Live deploy events",
431 desc: "Crontech-Gluecron event bus pushes deploy state. Watch deploys happen.",
432 },
433 {
434 title: "Live presence",
435 desc: "See who's looking at the same PR right now. Avoid double review work.",
436 },
437 ]}
438 />
439
440 <FeatureCategory
441 eyebrow="Platform"
442 title="Everything GitHub charges extra for."
443 items={[
444 {
445 title: "Workflow runner",
446 desc: "Drop yaml in `.gluecron/workflows/`. Runs on push. Cron triggers, secrets, matrix.",
447 },
448 {
449 title: "Packages registry",
450 desc: "npm protocol. Publish, install, yank with `glc_` PAT auth. Container registry deferred.",
451 },
452 {
453 title: "Pages hosting",
454 desc: "Serves blobs from the latest gh-pages commit. Custom domains, short cache headers.",
455 },
456 {
457 title: "Marketplace + apps",
458 desc: "Install third-party apps with permission scopes. App-bot push auth via ghi_ tokens.",
459 },
460 {
461 title: "Discussions",
462 desc: "Categorised threads, pinned, locked, Q&A answers. Zero extra config.",
463 },
464 {
465 title: "Wikis + Gists + Projects",
466 desc: "All shipped, all free, all integrated. No upsell tier.",
467 },
468 ]}
469 />
470
471 <FeatureCategory
472 eyebrow="Identity + governance"
473 title="Enterprise-tier auth from day one."
474 items={[
475 {
476 title: "TOTP + WebAuthn",
477 desc: "Both 2FA paths shipped. Passkey-only login if you want it.",
478 },
479 {
480 title: "OIDC SSO",
481 desc: "Okta, Azure AD, Auth0, Google Workspace. Auto-create users. Email-domain allowlist.",
482 },
483 {
484 title: "OAuth provider",
485 desc: "Third-party apps request scoped access to user repos. Standard auth-code flow.",
486 },
487 {
488 title: "Personal access tokens",
489 desc: "SHA-256 hashed, scoped, revocable. The clean way to script Gluecron.",
490 },
491 {
492 title: "Audit log (per-user + per-repo)",
493 desc: "Every sensitive action recorded. Browseable at /settings/audit.",
494 },
495 {
496 title: "Site admin panel",
497 desc: "/admin for site-wide flags: registration locks, banners, read-only mode.",
498 },
499 ]}
500 />
501
502 <FeatureCategory
503 eyebrow="Integrations"
504 title="Speak every protocol your tools use."
505 items={[
506 {
507 title: "MCP server",
508 desc: "Claude Desktop, Cursor, Code, Cline plug in natively. Read + scoped write tools.",
509 },
510 {
511 title: "REST API v2",
512 desc: "Full CRUD across resources. Versioned, documented, stable.",
513 },
514 {
515 title: "GraphQL endpoint",
516 desc: "Single-request fetch for complex client views. GraphiQL explorer at /api/graphql.",
517 },
518 {
519 title: "Webhooks",
520 desc: "HMAC-signed outbound to your URLs on push, issue, PR, star, comment, deploy.",
521 },
522 {
523 title: "Smart-HTTP git",
524 desc: "git push, git pull, git clone — exactly what your tools already speak.",
525 },
526 {
527 title: "VS Code extension",
528 desc: "Explain, open-on-web, semantic search, generate tests — all from the editor.",
529 },
530 ]}
531 />
532
533 <CtaBlock />
534 </div>
535 </>
536);
537
538const FeatureCategory: FC<{
539 eyebrow: string;
540 title: string;
541 items: Array<{ title: string; desc: string }>;
542}> = ({ eyebrow, title, items }) => (
543 <section class="mkt-section">
544 <div class="section-header left">
545 <div class="eyebrow">{eyebrow}</div>
546 <h2>{title}</h2>
547 </div>
548 <div class="mkt-feat-grid stagger">
549 {items.map((it) => (
550 <div class="mkt-feat-cell">
551 <h3 class="mkt-feat-title">{it.title}</h3>
552 <p class="mkt-feat-desc">{it.desc}</p>
553 </div>
554 ))}
555 </div>
556 </section>
557);
558
559// ============================================================
560// /about
561// ============================================================
562
563marketing.get("/about", (c) => {
564 const user = c.get("user");
565 return c.html(
566 <Layout title="About — gluecron" user={user}>
567 <AboutPage />
568 </Layout>,
569 );
570});
571
572const AboutPage: FC = () => (
573 <>
fa880f2Claude574 <style dangerouslySetInnerHTML={{ __html: aboutCss }} />
93812e4Claude575 <div class="mkt-page-about mkt-root">
b0148e9Claude576 <header class="mkt-hero">
93812e4Claude577 <div class="mkt-hero-orb" aria-hidden="true" />
578 <div class="mkt-hero-inner">
579 <div class="mkt-eyebrow">
580 <span class="mkt-eyebrow-pill" aria-hidden="true">
581 <svg width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round">
582 <circle cx="12" cy="12" r="10" />
583 <line x1="12" y1="16" x2="12" y2="12" />
584 <line x1="12" y1="8" x2="12.01" y2="8" />
585 </svg>
586 </span>
587 About
588 </div>
589 <h1 class="display mkt-hero-title">
590 We're building the platform{" "}
591 <span class="mkt-grad">software writes itself on.</span>
592 </h1>
593 <p class="mkt-hero-sub">
594 Most code in 2026 is written by AI. Most reviews are too. The
595 platforms hosting that code were built for a previous era.
596 Gluecron is built for this one.
597 </p>
598 </div>
b0148e9Claude599 </header>
600
601 <section class="mkt-section">
602 <div class="mkt-prose">
603 <div class="eyebrow">Mission</div>
604 <h2>An IDE for your repo. A teammate, not a sidebar.</h2>
605 <p>
606 GitHub treats AI as a feature you bolt onto your workflow. We
607 treat it as a peer that opens PRs, reviews diffs, fixes
608 regressions, and ships its own changes — visibly, accountably,
609 with a real identity in your history.
610 </p>
611 <p>
612 Everything is green by default. Every new repo auto-configures
613 gates, branch protection, labels, CODEOWNERS, and a welcome issue.
614 Users opt out per feature. Defaults are maximum-green so
615 <strong> nothing broken ever reaches production, the website,
616 or the customer.</strong>
617 </p>
618 </div>
619 </section>
620
621 <section class="mkt-section">
622 <div class="section-header">
623 <div class="eyebrow">Principles</div>
624 <h2>Six rules we don't compromise on.</h2>
625 </div>
626 <div class="mkt-principles stagger">
627 <PrincipleCard
628 n="01"
629 title="No vendor lock"
630 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."
631 />
632 <PrincipleCard
633 n="02"
634 title="No surprise bills"
635 desc="AI quotas degrade gracefully when hit. No overage, no automatic upgrade. We publish prices in dollars, not credits."
636 />
637 <PrincipleCard
638 n="03"
639 title="Self-host is first-class"
640 desc="Single Bun binary, single Postgres, zero telemetry. The same product the hosted version runs on, free forever for self-hosters."
641 />
642 <PrincipleCard
643 n="04"
644 title="AI is accountable"
645 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."
646 />
647 <PrincipleCard
648 n="05"
649 title="Real-time over polling"
650 desc="SSE for logs, comments, deploys, presence. The web should feel like a desktop app. No spinners on a tab you've already loaded."
651 />
652 <PrincipleCard
653 n="06"
654 title="Open by default"
655 desc="REST + GraphQL + MCP + Smart-HTTP + webhooks. Your tools speak our platform without asking. Programmatic access is not an enterprise tier."
656 />
657 </div>
658 </section>
659
660 <section class="mkt-section">
661 <div class="section-header">
662 <div class="eyebrow">Stack</div>
663 <h2>Built on the boring, fast parts.</h2>
664 </div>
665 <div class="mkt-stack">
666 <StackPill name="Bun" desc="runtime" />
667 <StackPill name="Hono" desc="server framework" />
668 <StackPill name="Drizzle" desc="ORM" />
669 <StackPill name="Neon Postgres" desc="primary database" />
670 <StackPill name="Claude Sonnet 4" desc="AI review + chat" />
671 <StackPill name="Claude Haiku 4.5" desc="AI commits + summaries" />
672 <StackPill name="Smart-HTTP git" desc="protocol" />
673 <StackPill name="Fly.io" desc="deploy target" />
674 </div>
675 </section>
676
677 <section class="mkt-section mkt-contact">
678 <div class="section-header">
679 <div class="eyebrow">Contact</div>
680 <h2>Three places to find us.</h2>
681 </div>
682 <div class="mkt-contact-grid">
683 <ContactCard
684 label="Product + sales"
685 email="hello@gluecron.com"
686 line="For demos, pricing questions, partnership ideas."
687 />
688 <ContactCard
689 label="Security"
690 email="security@gluecron.com"
691 line="Responsible disclosure. PGP key on request."
692 />
693 <ContactCard
694 label="Support"
695 email="support@gluecron.com"
696 line="Bugs, account help, anything that's broken."
697 />
698 </div>
699 </section>
700
701 <CtaBlock />
702 </div>
703 </>
704);
705
706const PrincipleCard: FC<{ n: string; title: string; desc: string }> = ({
707 n,
708 title,
709 desc,
710}) => (
711 <div class="mkt-principle">
712 <div class="mkt-principle-num">{n}</div>
713 <h3 class="mkt-principle-title">{title}</h3>
714 <p class="mkt-principle-desc">{desc}</p>
715 </div>
716);
717
718const StackPill: FC<{ name: string; desc: string }> = ({ name, desc }) => (
719 <div class="mkt-stack-pill">
720 <span class="mkt-stack-name">{name}</span>
721 <span class="mkt-stack-desc">{desc}</span>
722 </div>
723);
724
725const ContactCard: FC<{ label: string; email: string; line: string }> = ({
726 label,
727 email,
728 line,
729}) => (
730 <a href={`mailto:${email}`} class="mkt-contact-card">
731 <div class="mkt-contact-label">{label}</div>
732 <div class="mkt-contact-email">{email}</div>
733 <p class="mkt-contact-line">{line}</p>
734 </a>
735);
736
737// ============================================================
738// Shared closing CTA block
739// ============================================================
740
741const CtaBlock: FC = () => (
742 <section class="mkt-cta">
743 <div class="mkt-cta-card">
744 <div class="mkt-cta-bg" aria-hidden="true" />
745 <div class="eyebrow">Get started</div>
746 <h2 class="mkt-cta-title">
747 Stop maintaining the platform.<br />
748 <span class="gradient-text">Start shipping the product.</span>
749 </h2>
750 <div class="mkt-cta-buttons">
751 <a href="/register" class="btn btn-primary btn-xl">
752 Create your account
753 <span aria-hidden="true">{"→"}</span>
754 </a>
755 <a href="/import" class="btn btn-ghost btn-xl">
756 Migrate from GitHub
757 </a>
758 </div>
759 </div>
760 </section>
761);
762
763// ============================================================
764// Styles — namespaced under .mkt- so they don't leak
765// ============================================================
766
767const sharedMktCss = `
768 .mkt-root {
769 max-width: 1180px;
770 margin: 0 auto;
771 padding: 0 16px;
772 }
773
93812e4Claude774 /* ───── 2026 hero polish ─────
775 Three pages share the same shell (.mkt-page-pricing/-features/-about);
776 each adds its own page-scoped class to qualify selectors so this
777 block can't leak. */
778 .mkt-page-pricing .mkt-hero,
779 .mkt-page-features .mkt-hero,
780 .mkt-page-about .mkt-hero {
b0148e9Claude781 position: relative;
93812e4Claude782 text-align: center;
783 margin: var(--s-10) auto var(--s-12);
784 max-width: 980px;
785 padding: clamp(28px, 4vw, 56px) clamp(24px, 4vw, 48px);
786 background: var(--bg-elevated);
787 border: 1px solid var(--border);
788 border-radius: 22px;
789 overflow: hidden;
790 box-shadow: 0 1px 0 rgba(255,255,255,0.04), 0 22px 56px -20px rgba(0,0,0,0.45);
b0148e9Claude791 }
93812e4Claude792 .mkt-page-pricing .mkt-hero::before,
793 .mkt-page-features .mkt-hero::before,
794 .mkt-page-about .mkt-hero::before {
b0148e9Claude795 content: '';
796 position: absolute;
93812e4Claude797 top: 0; left: 0; right: 0;
798 height: 2px;
799 background: linear-gradient(90deg, transparent 0%, #8c6dff 30%, #36c5d6 70%, transparent 100%);
800 opacity: 0.78;
801 pointer-events: none;
802 z-index: 2;
803 }
804 .mkt-page-pricing .mkt-hero-orb,
805 .mkt-page-features .mkt-hero-orb,
806 .mkt-page-about .mkt-hero-orb {
807 position: absolute;
808 inset: -28% -10% auto auto;
809 width: 520px; height: 520px;
810 background: radial-gradient(circle, rgba(140,109,255,0.22), rgba(54,197,214,0.10) 45%, transparent 70%);
811 filter: blur(80px);
812 opacity: 0.75;
b0148e9Claude813 pointer-events: none;
93812e4Claude814 z-index: 0;
815 }
816 .mkt-page-pricing .mkt-hero-inner,
817 .mkt-page-features .mkt-hero-inner,
818 .mkt-page-about .mkt-hero-inner { position: relative; z-index: 1; }
819
820 .mkt-page-pricing .mkt-eyebrow,
821 .mkt-page-features .mkt-eyebrow,
822 .mkt-page-about .mkt-eyebrow {
823 display: inline-flex;
824 align-items: center;
825 gap: 8px;
826 font-family: var(--font-mono);
827 font-size: 11.5px;
828 text-transform: uppercase;
829 letter-spacing: 0.14em;
830 color: var(--text-muted);
831 font-weight: 600;
832 margin-bottom: 14px;
b0148e9Claude833 }
93812e4Claude834 .mkt-page-pricing .mkt-eyebrow-pill,
835 .mkt-page-features .mkt-eyebrow-pill,
836 .mkt-page-about .mkt-eyebrow-pill {
837 display: inline-flex;
838 align-items: center;
839 justify-content: center;
840 width: 18px; height: 18px;
841 border-radius: 6px;
842 background: rgba(140,109,255,0.14);
843 color: #b69dff;
844 box-shadow: inset 0 0 0 1px rgba(140,109,255,0.35);
845 }
846 .mkt-page-pricing .mkt-grad,
847 .mkt-page-features .mkt-grad,
848 .mkt-page-about .mkt-grad {
849 background-image: linear-gradient(135deg, #a48bff 0%, #8c6dff 50%, #36c5d6 100%);
850 -webkit-background-clip: text;
851 background-clip: text;
852 -webkit-text-fill-color: transparent;
853 color: transparent;
854 }
855 .mkt-page-pricing .mkt-hero-title,
856 .mkt-page-features .mkt-hero-title,
857 .mkt-page-about .mkt-hero-title {
858 font-family: var(--font-display);
b0148e9Claude859 font-size: clamp(36px, 6.5vw, 76px);
860 line-height: 1.02;
861 letter-spacing: -0.038em;
93812e4Claude862 font-weight: 800;
b0148e9Claude863 margin: 0 0 var(--s-5);
93812e4Claude864 color: var(--text-strong);
b0148e9Claude865 }
93812e4Claude866 .mkt-page-pricing .mkt-hero-sub,
867 .mkt-page-features .mkt-hero-sub,
868 .mkt-page-about .mkt-hero-sub {
b0148e9Claude869 font-size: clamp(15px, 1.5vw, 18px);
870 color: var(--text-muted);
871 max-width: 640px;
872 margin: 0 auto;
873 line-height: 1.55;
874 }
875
876 /* Section spacing */
877 .mkt-section { margin: var(--s-16) auto; }
878
879 /* Closing CTA */
880 .mkt-cta { margin: var(--s-20) auto var(--s-12); }
881 .mkt-cta-card {
882 position: relative;
883 text-align: center;
884 padding: var(--s-14) var(--s-7);
885 border: 1px solid var(--border-strong);
886 border-radius: var(--r-2xl);
887 background: var(--bg-elevated);
888 overflow: hidden;
889 isolation: isolate;
890 }
891 .mkt-cta-bg {
892 position: absolute;
893 inset: 0;
894 z-index: -1;
895 background:
896 radial-gradient(60% 100% at 50% 0%, rgba(140,109,255,0.16), transparent 65%),
897 radial-gradient(40% 80% at 80% 100%, rgba(54,197,214,0.10), transparent 65%);
898 }
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 {
944 border-color: rgba(140,109,255,0.40);
945 box-shadow: var(--elev-2), 0 0 0 1px rgba(140,109,255,0.30);
946 background:
947 linear-gradient(180deg, rgba(140,109,255,0.05), transparent 50%),
948 var(--bg-elevated);
949 }
950 .mkt-tier-hl:hover { border-color: rgba(140,109,255,0.60); }
951 .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);
965 box-shadow: 0 4px 14px -2px rgba(140,109,255,0.45);
966 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;
1138 background: linear-gradient(90deg, transparent 0%, rgba(140,109,255,0.40) 30%, rgba(54,197,214,0.40) 70%, transparent 100%);
1139 opacity: 0.7;
1140 }
1141 .mkt-page-features .mkt-teaser-card:hover {
1142 border-color: rgba(140,109,255,0.40);
1143 transform: translateY(-3px);
1144 box-shadow: 0 14px 36px -18px rgba(140,109,255,0.30);
1145 }
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;
1152 background: linear-gradient(135deg, rgba(140,109,255,0.18), rgba(54,197,214,0.10));
1153 color: #b69dff;
1154 box-shadow: inset 0 0 0 1px rgba(140,109,255,0.30);
1155 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);
1262 border: 1px solid rgba(140,109,255,0.30);
1263 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 }
1302 .mkt-stack-pill:hover { border-color: rgba(140,109,255,0.4); transform: translateY(-1px); }
1303 .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 {
1334 border-color: rgba(140,109,255,0.4);
1335 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;