- Docs
+ TODO: Landing page
diff --git a/website-next/app/(content)/platform/analytics/page.tsx b/website-next/app/(content)/platform/analytics/page.tsx
index 7a86b63d088..f041fa97946 100644
--- a/website-next/app/(content)/platform/analytics/page.tsx
+++ b/website-next/app/(content)/platform/analytics/page.tsx
@@ -1,3 +1,63 @@
-export default function Page() {
- return
Analytics ;
+import Image from "next/image";
+
+import { ContentSection } from "@/src/components/ContentSection";
+import { NextStepsSection } from "@/src/components/NextStepsSection";
+import { PageHero } from "@/src/components/PageHero";
+import { SolidButton } from "@/src/design-system/Button";
+
+export default function AnalyticsPage() {
+ return (
+ <>
+
+
+
+ Get Started
+
+
+
+
+
+
+
+
+
+ >
+ );
}
diff --git a/website-next/app/(content)/platform/continuous-integration/page.tsx b/website-next/app/(content)/platform/continuous-integration/page.tsx
index 103cc03a5ac..b65ad7051ea 100644
--- a/website-next/app/(content)/platform/continuous-integration/page.tsx
+++ b/website-next/app/(content)/platform/continuous-integration/page.tsx
@@ -1,3 +1,58 @@
-export default function Page() {
- return
Continuous Integration ;
+import { ContentSection } from "@/src/components/ContentSection";
+import { NextStepsSection } from "@/src/components/NextStepsSection";
+import { PageHero } from "@/src/components/PageHero";
+import { SolidButton } from "@/src/design-system/Button";
+
+export default function ContinuousIntegrationPage() {
+ return (
+ <>
+
+
+ Get Started
+
+
+
+
+
+
+
+ >
+ );
}
diff --git a/website-next/app/(content)/platform/ecosystem/page.tsx b/website-next/app/(content)/platform/ecosystem/page.tsx
index 5a794236c5d..b3b1db131aa 100644
--- a/website-next/app/(content)/platform/ecosystem/page.tsx
+++ b/website-next/app/(content)/platform/ecosystem/page.tsx
@@ -1,3 +1,102 @@
-export default function Page() {
- return
Ecosystem ;
+import { ContentSection } from "@/src/components/ContentSection";
+import { NextStepsSection } from "@/src/components/NextStepsSection";
+import { PageHero } from "@/src/components/PageHero";
+import { Section } from "@/src/components/Section";
+
+interface FeatureCard {
+ title: string;
+ description: string;
+}
+
+const FEATURES: FeatureCard[] = [
+ {
+ title: "Authentication Flows",
+ description:
+ "Choose between various authentication flows like basic, bearer or OAuth 2.",
+ },
+ {
+ title: "Organization Workspaces",
+ description:
+ "Organize your GraphQL APIs and collaborate with colleagues across your organization with ease.",
+ },
+ {
+ title: "Document Synchronization",
+ description:
+ "Keep your documents safe across all your devices and your teams.",
+ },
+ {
+ title: "PWA Support",
+ description:
+ "Use your favorite Browser to install Nitro as a PWA on your Device without requiring administrative privileges.",
+ },
+ {
+ title: "Beautiful Themes",
+ description:
+ "Choose your single preferred theme or let the system automatically switch between dark and light theme.",
+ },
+ {
+ title: "GraphQL File Upload",
+ description:
+ "Implements the latest version of the GraphQL multipart request spec.",
+ },
+ {
+ title: "Subscriptions over SSE",
+ description: "Supports GraphQL subscriptions over Server-Sent Events.",
+ },
+ {
+ title: "Performant GraphQL IDE",
+ description:
+ "Lagging apps can be frustrating. We do not accept that and keep always an eye on performance so that you can get your task done fast.",
+ },
+ {
+ title: "Subscriptions over WS",
+ description:
+ "Supports GraphQL subscriptions over WebSocket as well as the Apollo subscription protocol.",
+ },
+];
+
+export default function EcosystemPage() {
+ return (
+ <>
+
+
+
+
+ Everything you need to build great APIs — and more
+
+
+ {FEATURES.map((feature) => (
+
+
+ {feature.title}
+
+
+ {feature.description}
+
+
+ ))}
+
+
+
+ >
+ );
}
diff --git a/website-next/app/(content)/platform/page.tsx b/website-next/app/(content)/platform/page.tsx
new file mode 100644
index 00000000000..6903bc628e5
--- /dev/null
+++ b/website-next/app/(content)/platform/page.tsx
@@ -0,0 +1,54 @@
+import Link from "next/link";
+
+import { PageHero } from "@/src/components/PageHero";
+import { Section } from "@/src/components/Section";
+
+const PLATFORM_SECTIONS = [
+ {
+ href: "/platform/analytics",
+ title: "Analytics",
+ description: "Instant Insights. Enhanced Performance.",
+ },
+ {
+ href: "/platform/continuous-integration",
+ title: "Continuous Integration",
+ description: "Innovate with Confidence. Deliver with Quality.",
+ },
+ {
+ href: "/platform/ecosystem",
+ title: "Ecosystem",
+ description: "An Ecosystem You Trust and Love.",
+ },
+];
+
+export default function PlatformPage() {
+ return (
+ <>
+
+
+
+ {PLATFORM_SECTIONS.map((section) => (
+
+
+ {section.title}
+
+
+ {section.description}
+
+
+ Learn more →
+
+
+ ))}
+
+
+ >
+ );
+}
diff --git a/website-next/app/(content)/pricing/page.tsx b/website-next/app/(content)/pricing/page.tsx
index e237c4eb630..05d7200be67 100644
--- a/website-next/app/(content)/pricing/page.tsx
+++ b/website-next/app/(content)/pricing/page.tsx
@@ -1,3 +1,5 @@
+import { Typography } from "@/src/design-system/Typography";
+
export default function Page() {
- return
Pricing ;
+ return
TODO: Pricing page ;
}
diff --git a/website-next/app/(content)/products/hotchocolate/page.tsx b/website-next/app/(content)/products/hotchocolate/page.tsx
index 9483870d84c..e117811b563 100644
--- a/website-next/app/(content)/products/hotchocolate/page.tsx
+++ b/website-next/app/(content)/products/hotchocolate/page.tsx
@@ -1,3 +1,78 @@
-export default function Page() {
- return
Hot Chocolate ;
+import { ContentSection } from "@/src/components/ContentSection";
+import { PageHero } from "@/src/components/PageHero";
+import { Section } from "@/src/components/Section";
+import { OutlineButton, SolidButton } from "@/src/design-system/Button";
+
+const FEATURES = [
+ {
+ title: "Compile-time Composition",
+ description:
+ "Fusion composes subgraph schemas at planning time, not runtime. The gateway stays fast and queries stay typed end-to-end.",
+ },
+ {
+ title: "Code-first or Schema-first",
+ description:
+ "Author your GraphQL schema however your team prefers. Hot Chocolate supports both styles with full type safety.",
+ },
+ {
+ title: "DataLoader Batching",
+ description:
+ "Green Donut batches loads at the federation layer so cross-service N+1 disappears automatically.",
+ },
+ {
+ title: "Realtime Subscriptions",
+ description:
+ "Server-sent events and WebSocket subscriptions are first-class — no extra wiring required.",
+ },
+ {
+ title: "OpenTelemetry Built In",
+ description:
+ "Traces, errors, and per-resolver latency wire into your existing OTel backend (Jaeger, Tempo, Datadog, Honeycomb).",
+ },
+ {
+ title: "Federation-ready",
+ description:
+ "Compose with other Hot Chocolate services via Fusion or with Apollo subgraphs via the Federation spec.",
+ },
+];
+
+export default function HotChocolatePage() {
+ return (
+ <>
+
+
+ Get Started
+
+ View on GitHub
+
+
+
+
+
+ {FEATURES.map((feature) => (
+
+
+ {feature.title}
+
+
+ {feature.description}
+
+
+ ))}
+
+
+
+
+ >
+ );
}
diff --git a/website-next/app/(content)/products/nitro/page.tsx b/website-next/app/(content)/products/nitro/page.tsx
index 0cf12188c90..eab5ef37f2e 100644
--- a/website-next/app/(content)/products/nitro/page.tsx
+++ b/website-next/app/(content)/products/nitro/page.tsx
@@ -1,3 +1,92 @@
-export default function Page() {
- return
Nitro ;
+import { ContentSection } from "@/src/components/ContentSection";
+import { PageHero } from "@/src/components/PageHero";
+import { Section } from "@/src/components/Section";
+import { OutlineButton, SolidButton } from "@/src/design-system/Button";
+
+const FEATURES: { title: string; description: string }[] = [
+ {
+ title: "Authentication Flows",
+ description:
+ "Choose between various authentication flows like basic, bearer or OAuth 2.",
+ },
+ {
+ title: "Organization Workspaces",
+ description:
+ "Organize your GraphQL APIs and collaborate with colleagues across your organization with ease.",
+ },
+ {
+ title: "Document Synchronization",
+ description:
+ "Keep your documents safe across all your devices and your teams.",
+ },
+ {
+ title: "PWA Support",
+ description:
+ "Install Nitro as a PWA on your device without administrative privileges.",
+ },
+ {
+ title: "Beautiful Themes",
+ description:
+ "Choose your preferred theme or auto-switch between dark and light.",
+ },
+ {
+ title: "GraphQL File Upload",
+ description:
+ "Implements the latest GraphQL multipart request spec for uploads.",
+ },
+ {
+ title: "Subscriptions over SSE",
+ description: "Supports GraphQL subscriptions over Server-Sent Events.",
+ },
+ {
+ title: "Performant GraphQL IDE",
+ description:
+ "Fast, snappy IDE — we keep an eye on performance so you get your work done fast.",
+ },
+ {
+ title: "Subscriptions over WS",
+ description:
+ "Supports GraphQL subscriptions over WebSockets and the Apollo subscription protocol.",
+ },
+];
+
+export default function NitroPage() {
+ return (
+ <>
+
+
+
+ Launch Web App
+
+ Read the Docs
+
+
+
+
+ {FEATURES.map((feature) => (
+
+
+ {feature.title}
+
+
+ {feature.description}
+
+
+ ))}
+
+
+
+
+ >
+ );
}
diff --git a/website-next/app/(content)/products/strawberryshake/page.tsx b/website-next/app/(content)/products/strawberryshake/page.tsx
index 7055bbe3d48..359a45ec5bd 100644
--- a/website-next/app/(content)/products/strawberryshake/page.tsx
+++ b/website-next/app/(content)/products/strawberryshake/page.tsx
@@ -1,3 +1,68 @@
-export default function Page() {
- return
Strawberry Shake ;
+import { ContentSection } from "@/src/components/ContentSection";
+import { PageHero } from "@/src/components/PageHero";
+import { Section } from "@/src/components/Section";
+import { OutlineButton, SolidButton } from "@/src/design-system/Button";
+
+const FEATURES = [
+ {
+ title: "Strongly-typed Client",
+ description:
+ "Generate strongly-typed .NET clients from your GraphQL schema and queries. No more runtime parsing surprises.",
+ },
+ {
+ title: "Reactive Store",
+ description:
+ "Built-in reactive store with caching, optimistic updates, and offline support. Just wire it up.",
+ },
+ {
+ title: "Subscriptions",
+ description:
+ "First-class GraphQL subscriptions over WebSockets or Server-Sent Events.",
+ },
+ {
+ title: "Source Generators",
+ description:
+ "Roslyn source generators emit strongly-typed code at compile time — no IL weaving, no runtime cost.",
+ },
+];
+
+export default function StrawberryShakePage() {
+ return (
+ <>
+
+
+ Get Started
+
+ View on GitHub
+
+
+
+
+
+ {FEATURES.map((feature) => (
+
+
+ {feature.title}
+
+
+ {feature.description}
+
+
+ ))}
+
+
+
+
+ >
+ );
}
diff --git a/website-next/app/(content)/resources/page.tsx b/website-next/app/(content)/resources/page.tsx
index c728fe1c3a2..4478bd41015 100644
--- a/website-next/app/(content)/resources/page.tsx
+++ b/website-next/app/(content)/resources/page.tsx
@@ -1,17 +1,78 @@
-import { Typography } from "@/src/design-system/Typography";
+import Link from "next/link";
+
+import { PageHero } from "@/src/components/PageHero";
+import { Section } from "@/src/components/Section";
export const metadata = {
title: "Resources",
description: "ChilliCream brand resources and downloads.",
};
+const COMPANY_LINKS = [
+ {
+ href: "mailto:contact@chillicream.com",
+ title: "Contact",
+ description: "Get in touch with the team.",
+ },
+ {
+ href: "https://store.chillicream.com",
+ title: "Shop",
+ description: "ChilliCream merch and goodies.",
+ external: true,
+ },
+ {
+ href: "/legal/acceptable-use-policy",
+ title: "Acceptable Use Policy",
+ description: "Rules for using ChilliCream services.",
+ },
+ {
+ href: "/legal/cookie-policy",
+ title: "Cookie Policy",
+ description: "How we use cookies.",
+ },
+ {
+ href: "/legal/privacy-policy",
+ title: "Privacy Policy",
+ description: "How we handle your data.",
+ },
+ {
+ href: "/legal/terms-of-service",
+ title: "Terms of Service",
+ description: "The agreement between you and us.",
+ },
+ {
+ href: "/licensing/chillicream-license",
+ title: "ChilliCream License",
+ description: "Commercial license terms.",
+ },
+];
+
export default function ResourcesPage() {
return (
<>
-
Resources
-
- Brand assets, banners, and other resources are coming soon.
-
+
+
+
+ {COMPANY_LINKS.map((link) => (
+
+
+ {link.title}
+
+
{link.description}
+
+ ))}
+
+
>
);
}
diff --git a/website-next/app/(content)/services/advisory/page.tsx b/website-next/app/(content)/services/advisory/page.tsx
index ae76a36b74a..57fa8b15926 100644
--- a/website-next/app/(content)/services/advisory/page.tsx
+++ b/website-next/app/(content)/services/advisory/page.tsx
@@ -1,3 +1,76 @@
-export default function Page() {
- return
Advisory ;
+import { PageHero } from "@/src/components/PageHero";
+
+interface InquiryPlan {
+ title: string;
+ description: string;
+ features: string[];
+ ctaText: string;
+ ctaLink: string;
+}
+
+const PLANS: InquiryPlan[] = [
+ {
+ title: "Consulting",
+ description:
+ "Hourly consulting services to get the help you need at any stage of your project. This is the best way to get started.",
+ features: [
+ "Mentoring and guidance",
+ "Architecture",
+ "Troubleshooting",
+ "Code Review",
+ "Best practices education",
+ ],
+ ctaText: "Talk to an Expert",
+ ctaLink: "mailto:contact@chillicream.com?subject=Consulting",
+ },
+ {
+ title: "Contracting",
+ description:
+ "Options for teams who don't have the time, bandwidth, and/or expertise to implement their own GraphQL solutions.",
+ features: ["Proof of concept", "Implementation"],
+ ctaText: "Talk to an Expert",
+ ctaLink: "mailto:contact@chillicream.com?subject=Contracting",
+ },
+];
+
+export default function AdvisoryPage() {
+ return (
+ <>
+
+
+
+ {PLANS.map((plan) => (
+
+
+ {plan.title}
+
+
{plan.description}
+
+ {plan.features.map((feature) => (
+
+
+ ✓
+
+ {feature}
+
+ ))}
+
+
+ {plan.ctaText}
+
+
+ ))}
+
+
+ >
+ );
}
diff --git a/website-next/app/(content)/services/page.tsx b/website-next/app/(content)/services/page.tsx
new file mode 100644
index 00000000000..c92a9d6b4cb
--- /dev/null
+++ b/website-next/app/(content)/services/page.tsx
@@ -0,0 +1,54 @@
+import Link from "next/link";
+
+import { PageHero } from "@/src/components/PageHero";
+import { Section } from "@/src/components/Section";
+
+const SERVICE_SECTIONS = [
+ {
+ href: "/services/advisory",
+ title: "Advisory",
+ description: "Consulting & contracting from GraphQL experts.",
+ },
+ {
+ href: "/services/support",
+ title: "Support",
+ description: "Get help from experts with SLAs you can rely on.",
+ },
+ {
+ href: "/services/training",
+ title: "Training",
+ description: "Increase your team's productivity with focused training.",
+ },
+];
+
+export default function ServicesPage() {
+ return (
+ <>
+
+
+
+ {SERVICE_SECTIONS.map((section) => (
+
+
+ {section.title}
+
+
+ {section.description}
+
+
+ Learn more →
+
+
+ ))}
+
+
+ >
+ );
+}
diff --git a/website-next/app/(content)/services/support/contact/page.tsx b/website-next/app/(content)/services/support/contact/page.tsx
index 55a8db4fe87..326bcf51bdf 100644
--- a/website-next/app/(content)/services/support/contact/page.tsx
+++ b/website-next/app/(content)/services/support/contact/page.tsx
@@ -1,3 +1,100 @@
-export default function Page() {
- return
Contact Support ;
+import { PageHero } from "@/src/components/PageHero";
+
+const SUBJECTS = [
+ "Schedule a Demo",
+ "Pricing & Plans",
+ "Sales",
+ "Technical Support",
+ "Partnership",
+ "Other",
+];
+
+export default function ContactPage() {
+ return (
+ <>
+
+
+ >
+ );
+}
+
+function Field({
+ label,
+ name,
+ type,
+ required,
+}: {
+ label: string;
+ name: string;
+ type: string;
+ required?: boolean;
+}) {
+ return (
+
+
+ {label}
+ {required && * }
+
+
+
+ );
}
diff --git a/website-next/app/(content)/services/support/page.tsx b/website-next/app/(content)/services/support/page.tsx
index c4ef375eb1a..698f65adb48 100644
--- a/website-next/app/(content)/services/support/page.tsx
+++ b/website-next/app/(content)/services/support/page.tsx
@@ -1,3 +1,143 @@
-export default function Page() {
- return
Support ;
+import { type Plan, PlanGrid } from "@/src/components/PlanGrid";
+import { PageHero } from "@/src/components/PageHero";
+import { Section } from "@/src/components/Section";
+
+const SUPPORT_PLANS: Plan[] = [
+ {
+ title: "Community",
+ price: 0,
+ period: "month",
+ description: "For personal or non-commercial projects, to start hacking.",
+ features: ["Public Slack Channel"],
+ ctaText: "Join Slack",
+ ctaLink: "https://slack.chillicream.com/",
+ },
+ {
+ title: "Startup",
+ price: 450,
+ period: "month",
+ description:
+ "For small teams with moderate bandwidth and projects of low to medium complexity.",
+ features: ["Private Slack Channel", "2 critical incidents"],
+ ctaText: "Contact Us",
+ ctaLink: "/services/support/contact?plan=Startup",
+ },
+ {
+ title: "Business",
+ price: 1300,
+ period: "month",
+ description: "For larger teams with business-critical projects.",
+ features: [
+ "Private Slack Channel",
+ "5 critical incidents",
+ "2 non-critical incidents",
+ "Email support",
+ ],
+ ctaText: "Contact Us",
+ ctaLink: "/services/support/contact?plan=Business",
+ },
+ {
+ title: "Enterprise",
+ price: "custom",
+ description:
+ "For the whole organization, all your teams and business units, and with tailor made SLAs.",
+ features: [
+ "Private Slack Channel",
+ "Unlimited critical incidents",
+ "10 non-critical incidents",
+ "Phone support",
+ "Dedicated account manager",
+ "Status reviews",
+ ],
+ ctaText: "Contact Us",
+ ctaLink: "/services/support/contact?plan=Enterprise",
+ },
+];
+
+interface FeatureValue {
+ title: string;
+ values: (boolean | string)[];
+}
+
+const COMPARISON: FeatureValue[] = [
+ {
+ title: "Critical Incidents",
+ values: [
+ false,
+ "2 (next business day)",
+ "5 (next business day)",
+ "∞ (24 hours)",
+ ],
+ },
+ {
+ title: "Non-critical Incidents",
+ values: [false, false, "5 (3 business days)", "10 (next business day)"],
+ },
+ { title: "Public Slack Channel", values: [true, true, true, true] },
+ { title: "Private Slack Channel", values: [false, true, true, true] },
+ { title: "Private Issue Tracking Board", values: [false, false, true, true] },
+ { title: "Email Support", values: [false, false, true, true] },
+ { title: "Phone Support", values: [false, false, false, true] },
+ { title: "Dedicated Account Manager", values: [false, false, false, true] },
+ { title: "Status Reviews", values: [false, false, false, true] },
+];
+
+const PLAN_NAMES = ["Community", "Startup", "Business", "Enterprise"];
+
+export default function SupportPage() {
+ return (
+ <>
+
+
+
+
+
+
+
+
+
+
+ Support
+
+ {PLAN_NAMES.map((name) => (
+
+ {name}
+
+ ))}
+
+
+
+ {COMPARISON.map((row) => (
+
+ {row.title}
+ {row.values.map((v, i) => (
+
+ {v === true ? (
+ ✓
+ ) : v === false ? (
+ —
+ ) : (
+ v
+ )}
+
+ ))}
+
+ ))}
+
+
+
+
+ >
+ );
}
diff --git a/website-next/app/(content)/services/support/thank-you/page.tsx b/website-next/app/(content)/services/support/thank-you/page.tsx
index 615e0f87307..0106b0584ab 100644
--- a/website-next/app/(content)/services/support/thank-you/page.tsx
+++ b/website-next/app/(content)/services/support/thank-you/page.tsx
@@ -1,3 +1,19 @@
-export default function Page() {
- return
Thank You ;
+import { PageHero } from "@/src/components/PageHero";
+import { OutlineButton, SolidButton } from "@/src/design-system/Button";
+
+export default function ThankYouPage() {
+ return (
+ <>
+
+
+ Read the Docs
+
+ Join Slack
+
+
+ >
+ );
}
diff --git a/website-next/app/(content)/services/training/page.tsx b/website-next/app/(content)/services/training/page.tsx
index 8e13697f63c..59a075f0684 100644
--- a/website-next/app/(content)/services/training/page.tsx
+++ b/website-next/app/(content)/services/training/page.tsx
@@ -1,3 +1,79 @@
-export default function Page() {
- return
Training ;
+import { PageHero } from "@/src/components/PageHero";
+import { Section } from "@/src/components/Section";
+
+interface CorporateService {
+ kind: string;
+ description: string;
+ perks: string[];
+}
+
+const SERVICES: CorporateService[] = [
+ {
+ kind: "Corporate Training",
+ description:
+ "Get your team trained in GraphQL, any of our products, and even React/Relay. Beginner Team? Advanced Team? Or Mixed? Don't panic! Our curriculum is designed to teach in-depth and works really well, but isn't set in stone.",
+ perks: [
+ "Level up their proficiency",
+ "Catered to different skills",
+ "Overcome challenges they've been wrestling with",
+ "Get everybody on the same technical page",
+ ],
+ },
+ {
+ kind: "Corporate Workshop",
+ description:
+ "We will look at how to build a GraphQL server with ASP.NET Core 7 and Hot Chocolate. You will learn how to explore and manage large schemas. Further, we will dive into React and explore how to efficiently build fast and fluent web interfaces using Relay.",
+ perks: [
+ "Core concepts and advanced",
+ "Deepen knowledge of GraphQL API",
+ "Work on a real project",
+ "Scale and production quirks",
+ "Level up your entire team at once",
+ "Have Lots of Fun!",
+ ],
+ },
+];
+
+export default function TrainingPage() {
+ return (
+ <>
+
+
+
+ {SERVICES.map((service) => (
+
+
+ {service.kind}
+
+
+ {service.description}
+
+
+ {service.perks.map((perk) => (
+
+
+ ✓
+
+ {perk}
+
+ ))}
+
+
+ Talk to us
+
+
+ ))}
+
+
+ >
+ );
}
diff --git a/website-next/app/globals.css b/website-next/app/globals.css
index a624a2a2690..3b02b082493 100644
--- a/website-next/app/globals.css
+++ b/website-next/app/globals.css
@@ -165,8 +165,17 @@
so the pattern doesn't scroll with content. */
background-image: var(--cc-dark-surface);
background-size:
- 100vw 100vh, 100vw 100vh, 100vw 100vh, 100vw 100vh, 100vw 100vh,
- 100vw 100vh, 100vw 100vh, 100vw 100vh, 100vw 100vh, auto, auto;
+ 100vw 100vh,
+ 100vw 100vh,
+ 100vw 100vh,
+ 100vw 100vh,
+ 100vw 100vh,
+ 100vw 100vh,
+ 100vw 100vh,
+ 100vw 100vh,
+ 100vw 100vh,
+ auto,
+ auto;
background-attachment: fixed;
}
}
@@ -180,8 +189,17 @@
background-color: var(--color-cc-bg);
background-image: var(--cc-dark-surface);
background-size:
- 100vw 100vh, 100vw 100vh, 100vw 100vh, 100vw 100vh, 100vw 100vh,
- 100vw 100vh, 100vw 100vh, 100vw 100vh, 100vw 100vh, auto, auto;
+ 100vw 100vh,
+ 100vw 100vh,
+ 100vw 100vh,
+ 100vw 100vh,
+ 100vw 100vh,
+ 100vw 100vh,
+ 100vw 100vh,
+ 100vw 100vh,
+ 100vw 100vh,
+ auto,
+ auto;
background-attachment: fixed;
}
diff --git a/website-next/src/components/ContentSection.tsx b/website-next/src/components/ContentSection.tsx
new file mode 100644
index 00000000000..a648afbd4e2
--- /dev/null
+++ b/website-next/src/components/ContentSection.tsx
@@ -0,0 +1,95 @@
+import NextImage from "next/image";
+import type { ReactNode } from "react";
+
+interface ContentSectionProps {
+ title: ReactNode;
+ text: ReactNode;
+ image?: ReactNode;
+ imageSrc?: string;
+ imageAlt?: string;
+ imageMaxWidth?: number;
+ imagePosition?: "left" | "right" | "bottom";
+}
+
+function resolveImage(
+ imageSrc?: string,
+ imageAlt?: string,
+ imageMaxWidth?: number,
+ image?: ReactNode
+): ReactNode | undefined {
+ if (image) return image;
+ if (!imageSrc) return undefined;
+ return (
+
+ );
+}
+
+export function ContentSection({
+ title,
+ text,
+ image: imageProp,
+ imageSrc,
+ imageAlt,
+ imageMaxWidth,
+ imagePosition = "bottom",
+}: ContentSectionProps) {
+ const image = resolveImage(imageSrc, imageAlt, imageMaxWidth, imageProp);
+ if (!image) {
+ return (
+
+
+
+ {title}
+
+
+ {text}
+
+
+
+ );
+ }
+
+ if (imagePosition === "bottom") {
+ return (
+
+
+
+ {title}
+
+
+ {text}
+
+
+ {image}
+
+ );
+ }
+
+ return (
+
+ *:first-child]:order-2" : ""
+ }`}
+ >
+
+
+ {title}
+
+
+ {text}
+
+
+
{image}
+
+
+ );
+}
diff --git a/website-next/src/components/NextStepsSection.tsx b/website-next/src/components/NextStepsSection.tsx
new file mode 100644
index 00000000000..b977e4444ff
--- /dev/null
+++ b/website-next/src/components/NextStepsSection.tsx
@@ -0,0 +1,36 @@
+import type { ReactNode } from "react";
+
+import { OutlineButton, SolidButton } from "@/src/design-system/Button";
+
+interface NextStepsProps {
+ title: string;
+ text: ReactNode;
+ primaryLink: string;
+ primaryLinkText: string;
+ secondaryLink: string;
+ secondaryLinkText: string;
+}
+
+export function NextStepsSection({
+ title,
+ text,
+ primaryLink,
+ primaryLinkText,
+ secondaryLink,
+ secondaryLinkText,
+}: NextStepsProps) {
+ return (
+
+
+ {title}
+
+
+ {text}
+
+
+ {primaryLinkText}
+ {secondaryLinkText}
+
+
+ );
+}
diff --git a/website-next/src/components/PageHero.tsx b/website-next/src/components/PageHero.tsx
new file mode 100644
index 00000000000..8450149edab
--- /dev/null
+++ b/website-next/src/components/PageHero.tsx
@@ -0,0 +1,34 @@
+export function PageHero({
+ eyebrow,
+ title,
+ subtitle,
+ teaser,
+}: {
+ eyebrow?: string;
+ title: string;
+ subtitle?: string;
+ teaser?: string;
+}) {
+ return (
+
+ {eyebrow && (
+
+ {eyebrow}
+
+ )}
+
+ {title}
+
+ {subtitle && (
+
+ {subtitle}
+
+ )}
+ {teaser && (
+
+ {teaser}
+
+ )}
+
+ );
+}
diff --git a/website-next/src/components/PlanGrid.stories.tsx b/website-next/src/components/PlanGrid.stories.tsx
new file mode 100644
index 00000000000..7f69551c163
--- /dev/null
+++ b/website-next/src/components/PlanGrid.stories.tsx
@@ -0,0 +1,89 @@
+import type { Meta, StoryObj } from "@storybook/nextjs-vite";
+import { type Plan, PlanGrid } from "./PlanGrid";
+
+const meta = {
+ title: "Components/PlanGrid",
+ component: PlanGrid,
+ decorators: [
+ (Story) => (
+
+ ),
+ ],
+} satisfies Meta
;
+
+export default meta;
+type Story = StoryObj;
+
+// A free tier: zero price with a period, short feature list, external CTA.
+const community: Plan = {
+ title: "Community",
+ price: 0,
+ period: "month",
+ description: "Get help from the community and help others along the way.",
+ features: ["Public Slack channel", "7000+ developers"],
+ ctaText: "Join Slack",
+ ctaLink: "https://slack.chillicream.com/",
+};
+
+// A paid tier: "from" pricing with a period and a longer feature list.
+const pro: Plan = {
+ title: "Pro",
+ price: 49,
+ period: "month",
+ fromPrice: true,
+ description: "Everything a growing team needs to ship with confidence.",
+ features: [
+ "Private Slack channel",
+ "Priority email support",
+ "Schema registry",
+ "CI/CD integration",
+ "Usage analytics",
+ ],
+ ctaText: "Start Free Trial",
+ ctaLink: "/pricing",
+};
+
+// A custom tier: no numeric price, internal CTA.
+const enterprise: Plan = {
+ title: "Enterprise",
+ price: "custom",
+ description: "Tailored support and onboarding for organizations at scale.",
+ features: [
+ "Dedicated account manager",
+ "SLA-backed support",
+ "On-site training",
+ "Security review",
+ ],
+ ctaText: "Contact Sales",
+ ctaLink: "mailto:contact@chillicream.com?subject=Enterprise",
+};
+
+// A flat one-time price: no period, single feature, mailto CTA.
+const oneTime: Plan = {
+ title: "Audit",
+ price: 2500,
+ description: "A one-off architecture and performance review of your API.",
+ features: ["Full written report"],
+ ctaText: "Request Audit",
+ ctaLink: "mailto:contact@chillicream.com?subject=Audit",
+};
+
+export const Default: Story = {
+ args: { plans: [community, pro, enterprise] },
+};
+
+export const TwoTiers: Story = {
+ args: { plans: [community, enterprise] },
+};
+
+export const FourTiers: Story = {
+ args: { plans: [community, oneTime, pro, enterprise] },
+};
+
+export const SingleTier: Story = {
+ args: { plans: [pro] },
+};
diff --git a/website-next/src/components/PlanGrid.tsx b/website-next/src/components/PlanGrid.tsx
new file mode 100644
index 00000000000..b40c694c79c
--- /dev/null
+++ b/website-next/src/components/PlanGrid.tsx
@@ -0,0 +1,69 @@
+import { SolidButton } from "@/src/design-system/Button";
+
+import { CheckIcon } from "./pricing/CheckIcon";
+
+export interface Plan {
+ title: string;
+ price: number | "custom";
+ period?: string;
+ fromPrice?: boolean;
+ description: string;
+ features: string[];
+ ctaText: string;
+ ctaLink: string;
+}
+
+export function PlanCard({ plan }: { plan: Plan }) {
+ return (
+
+
+ {plan.title}
+
+
+
+ {plan.price === "custom" ? (
+ custom
+ ) : (
+ <>
+ {plan.fromPrice && (
+ from
+ )}
+
+ ${plan.price.toLocaleString()}
+
+ {plan.period && (
+ /{plan.period}
+ )}
+ >
+ )}
+
+
+
{plan.description}
+
+
+ {plan.features.map((feature) => (
+
+
+
+
+ {feature}
+
+ ))}
+
+
+
+ {plan.ctaText}
+
+
+ );
+}
+
+export function PlanGrid({ plans }: { plans: Plan[] }) {
+ return (
+
+ {plans.map((plan) => (
+
+ ))}
+
+ );
+}
diff --git a/website-next/src/components/Section.tsx b/website-next/src/components/Section.tsx
new file mode 100644
index 00000000000..d1c1617a972
--- /dev/null
+++ b/website-next/src/components/Section.tsx
@@ -0,0 +1,18 @@
+import type { ReactNode } from "react";
+
+interface SectionProps {
+ title: string;
+ children: ReactNode;
+ className?: string;
+}
+
+export function Section({ title, children, className = "" }: SectionProps) {
+ return (
+
+
+ {title}
+
+ {children}
+
+ );
+}
diff --git a/website-next/src/components/pricing/CheckIcon.tsx b/website-next/src/components/pricing/CheckIcon.tsx
new file mode 100644
index 00000000000..da3ec07bbe0
--- /dev/null
+++ b/website-next/src/components/pricing/CheckIcon.tsx
@@ -0,0 +1,14 @@
+export function CheckIcon({ size = 14 }: { size?: number }) {
+ return (
+
+
+
+ );
+}
diff --git a/website-next/src/design-system/Button.stories.tsx b/website-next/src/design-system/Button.stories.tsx
new file mode 100644
index 00000000000..954c0c86cc4
--- /dev/null
+++ b/website-next/src/design-system/Button.stories.tsx
@@ -0,0 +1,27 @@
+import type { Meta, StoryObj } from "@storybook/nextjs-vite";
+import { OutlineButton, SolidButton } from "./Button";
+
+const meta = {
+ title: "Design System/Button",
+ component: SolidButton,
+ decorators: [
+ (Story) => (
+
+
+
+ ),
+ ],
+} satisfies Meta;
+
+export default meta;
+type Story = StoryObj;
+
+export const Default: Story = {
+ args: { children: "Book a Demo" },
+ render: () => (
+ <>
+ Book a Demo
+ Launch
+ >
+ ),
+};
diff --git a/website-next/src/design-system/Button.tsx b/website-next/src/design-system/Button.tsx
new file mode 100644
index 00000000000..e5f4c28f626
--- /dev/null
+++ b/website-next/src/design-system/Button.tsx
@@ -0,0 +1,72 @@
+import NextLink from "next/link";
+import type { ReactNode } from "react";
+
+export type ButtonProps = {
+ children: ReactNode;
+ /**
+ * Optional destination. Internal paths (`/...`) render a Next.js `Link`,
+ * `#`/`mailto:`/`tel:` links stay in the same tab, and any other URL opens in
+ * a new tab. When omitted the button renders as a ``.
+ */
+ href?: string;
+ className?: string;
+};
+
+const BASE_CLASSES =
+ "inline-flex items-center justify-center rounded-full px-7 py-3 text-sm font-medium no-underline transition-colors";
+
+// Filled pill: ink surface with the dark page color as the label.
+const SOLID_CLASSES = "bg-cc-ink text-cc-surface hover:bg-cc-white";
+
+// Outlined pill: hairline border that brightens on hover.
+const OUTLINE_CLASSES =
+ "border border-cc-card-border text-cc-ink hover:border-cc-card-border-hover";
+
+function renderButton(variantClasses: string, props: ButtonProps) {
+ const { children, href, className } = props;
+ const cls = [BASE_CLASSES, variantClasses, className ?? ""]
+ .filter(Boolean)
+ .join(" ");
+
+ if (href === undefined) {
+ return (
+
+ {children}
+
+ );
+ }
+
+ if (href.startsWith("/")) {
+ return (
+
+ {children}
+
+ );
+ }
+
+ if (
+ href.startsWith("#") ||
+ href.startsWith("mailto:") ||
+ href.startsWith("tel:")
+ ) {
+ return (
+
+ {children}
+
+ );
+ }
+
+ return (
+
+ {children}
+
+ );
+}
+
+export function SolidButton(props: ButtonProps) {
+ return renderButton(SOLID_CLASSES, props);
+}
+
+export function OutlineButton(props: ButtonProps) {
+ return renderButton(OUTLINE_CLASSES, props);
+}
diff --git a/website-next/src/design-system/Link.tsx b/website-next/src/design-system/Link.tsx
index fae53c5feae..1f7eca13b5c 100644
--- a/website-next/src/design-system/Link.tsx
+++ b/website-next/src/design-system/Link.tsx
@@ -19,7 +19,11 @@ export function Link({
);
}
- if (href.startsWith("#")) {
+ if (
+ href.startsWith("#") ||
+ href.startsWith("mailto:") ||
+ href.startsWith("tel:")
+ ) {
return (
{children}
From 460248ea7b5f207f04f00434a083194bdfe44dfa Mon Sep 17 00:00:00 2001
From: tobias-tengler <45513122+tobias-tengler@users.noreply.github.com>
Date: Sat, 30 May 2026 16:38:36 +0200
Subject: [PATCH 2/2] More changes
---
website-next/app/globals.css | 2 +
.../components/{pricing => }/CheckIcon.tsx | 0
website-next/src/components/Header.tsx | 2 +-
.../src/components/NotFoundActions.tsx | 4 +-
website-next/src/components/PlanGrid.tsx | 2 +-
website-next/src/components/SpilledDrink.tsx | 82 -------------------
website-next/src/design-system/List.tsx | 2 +-
website-next/src/design-system/Quote.tsx | 2 +-
website-next/src/design-system/Table.tsx | 2 +-
.../src/design-system/TableOfContents.tsx | 6 +-
website-next/src/design-system/Typography.tsx | 4 +-
website-next/src/mdx-plugins.ts | 17 ----
12 files changed, 14 insertions(+), 111 deletions(-)
rename website-next/src/components/{pricing => }/CheckIcon.tsx (100%)
delete mode 100644 website-next/src/components/SpilledDrink.tsx
diff --git a/website-next/app/globals.css b/website-next/app/globals.css
index 3b02b082493..4c17bed0f3a 100644
--- a/website-next/app/globals.css
+++ b/website-next/app/globals.css
@@ -10,6 +10,8 @@
--color-*: initial;
--color-cc-ink: #f5f1ea;
+ /* Long-form body prose: lighter than `cc-ink-dim` so paragraphs stay readable. */
+ --color-cc-prose: rgba(245, 241, 234, 0.8);
--color-cc-ink-dim: rgba(245, 241, 234, 0.62);
--color-cc-ink-faint: rgba(245, 241, 234, 0.16);
/* Faint light wash for hover/active surfaces (nav links, sidebar items). */
diff --git a/website-next/src/components/pricing/CheckIcon.tsx b/website-next/src/components/CheckIcon.tsx
similarity index 100%
rename from website-next/src/components/pricing/CheckIcon.tsx
rename to website-next/src/components/CheckIcon.tsx
diff --git a/website-next/src/components/Header.tsx b/website-next/src/components/Header.tsx
index 734b761c23d..88b94449bc1 100644
--- a/website-next/src/components/Header.tsx
+++ b/website-next/src/components/Header.tsx
@@ -468,7 +468,7 @@ function GetInTouchPanel() {
>
Get in touch