Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion website-next/app/(content)/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import type { ReactNode } from "react";
export default function ContentLayout({ children }: { children: ReactNode }) {
return (
<div className="px-5 py-8 sm:px-12">
<div className="mx-auto max-w-5xl">{children}</div>
<div className="mx-auto max-w-7xl">{children}</div>
</div>
);
}
63 changes: 59 additions & 4 deletions website-next/app/(content)/pricing/page.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,68 @@
import type { Metadata } from "next";

import { Typography } from "@/src/design-system/Typography";
import { ClosingCta } from "@/src/components/pricing/ClosingCta";
import { CompareTable } from "@/src/components/pricing/CompareTable";
import { PlanSelector } from "@/src/components/pricing/PlanSelector";
import { PricingFaq } from "@/src/components/pricing/PricingFaq";
import { RegulatedBand } from "@/src/components/pricing/RegulatedBand";
import { OutlineButton, SolidButton } from "@/src/design-system/Button";

export const metadata: Metadata = {
title: "Pricing",
description:
"Explore pricing for the ChilliCream GraphQL Platform. Compare plans and find the right option for your team, from open source to enterprise.",
"Nitro pricing: start free on the shared cloud, pay as you go at $20/mo, run a dedicated single-tenant instance from $400, or self-host. Compare every plan.",
keywords: [
"ChilliCream pricing",
"Nitro pricing",
"GraphQL platform pricing",
"GraphQL plans",
"dedicated instance",
"self-hosted",
"schema registry pricing",
"GraphQL observability pricing",
],
openGraph: {
title: "Nitro Pricing",
description:
"Start free on the shared cloud, pay as you go at $20/mo, run a dedicated single-tenant instance from $400, or self-host on your own infrastructure.",
},
};

export default function Page() {
return <Typography variant="body">TODO: Pricing page</Typography>;
export default function PricingPage() {
return (
<>
<Hero />
<PlanSelector />
<CompareTable />
<PricingFaq />
<RegulatedBand />
<ClosingCta />
</>
);
}

function Hero() {
return (
<section className="pt-10 pb-14 text-center sm:pt-16 sm:pb-20">
<p className="text-cc-nav-label font-mono text-xs tracking-[0.18em] uppercase">
Nitro pricing
</p>
<h1 className="font-heading text-cc-heading sm:text-h2 mt-5 text-4xl font-semibold text-balance">
Pricing that scales with your platform.
</h1>
<p className="text-cc-ink mx-auto mt-6 max-w-2xl text-base text-pretty sm:text-lg">
Start free on the shared cloud. Pay as you go as traffic grows, run a
dedicated single-tenant instance when you need your own region and
isolation, or self-host on your own infrastructure.
</p>
<div className="mt-9 flex flex-wrap items-center justify-center gap-3">
<SolidButton href="https://nitro.chillicream.com">
Start for Free
</SolidButton>
<OutlineButton href="/services/support/contact">
Talk to Sales
</OutlineButton>
</div>
</section>
);
}
2 changes: 1 addition & 1 deletion website-next/app/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ export default function Home() {
<NitroPricing />
<GrabADrink />
<div className="px-5 py-8 sm:px-12">
<div className="mx-auto flex max-w-6xl flex-col gap-12">
<div className="mx-auto flex max-w-7xl flex-col gap-12">
<FromOurBlog />
</div>
</div>
Expand Down
64 changes: 19 additions & 45 deletions website-next/src/components/Offering.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,11 @@ import type { ComponentType, CSSProperties } from "react";
import { OutlineButton, SolidButton } from "@/src/design-system/Button";

import { CheckIcon } from "./CheckIcon";
import { PopularBadge } from "./PopularBadge";

// Rainbow accent gradient for the popular card's border.
const RING_GRADIENT =
"linear-gradient(140deg, #16b9e4 0%, #7c92c6 50%, #f0786a 100%)";

interface OfferingProps {
readonly title: string;
Expand Down Expand Up @@ -41,14 +46,23 @@ export function Offering({
// must match the number of section cells rendered.
const rowCount = 2 + (Icon ? 1 : 0) + (description ? 1 : 0) + (price ? 1 : 0);

// The popular card gets a rainbow gradient border (the gradient paints the
// border-box layer, the opaque surface fill paints the padding-box layer on
// top), so the rainbow shows only on the edge and the interior stays solid.
const cardStyle: CSSProperties = popular
? {
gridRow: `span ${rowCount}`,
border: "1.5px solid transparent",
background: `linear-gradient(var(--color-cc-surface), var(--color-cc-surface)) padding-box, ${RING_GRADIENT} border-box`,
}
: { gridRow: `span ${rowCount}` };

return (
<div
className={`relative grid h-full grid-rows-subgrid gap-0 rounded-3xl border p-6 sm:p-7 ${
popular
? "bg-cc-card-bg border-cc-accent"
: "border-cc-card-border bg-cc-card-bg/60"
className={`relative grid h-full grid-rows-subgrid gap-0 rounded-3xl p-6 sm:p-7 ${
popular ? "" : "border-cc-card-border bg-cc-card-bg/60 border"
}`}
style={{ gridRow: `span ${rowCount}` }}
style={cardStyle}
>
{popular && <PopularBadge />}

Expand Down Expand Up @@ -112,43 +126,3 @@ function Dots({ className = "" }: { readonly className?: string }) {
/>
);
}

const RING_COLOR = "var(--color-cc-accent)";

const HEX_CLIP =
"polygon(0 50%, var(--t) 0, calc(100% - var(--t)) 0, 100% 50%, calc(100% - var(--t)) 100%, var(--t) 100%)";

const hairline = (dir: string) =>
`linear-gradient(to ${dir}, transparent calc(50% - 0.6px), ${RING_COLOR} calc(50% - 0.6px), ${RING_COLOR} calc(50% + 0.6px), transparent calc(50% + 0.6px))`;

const BADGE_OUTLINE = [
`linear-gradient(${RING_COLOR}, ${RING_COLOR}) center top / calc(100% - var(--t) * 2) 1px no-repeat`,
`linear-gradient(${RING_COLOR}, ${RING_COLOR}) center bottom / calc(100% - var(--t) * 2) 1px no-repeat`,
`${hairline("top left")} left top / var(--t) 50% no-repeat`,
`${hairline("bottom left")} left bottom / var(--t) 50% no-repeat`,
`${hairline("top right")} right top / var(--t) 50% no-repeat`,
`${hairline("bottom right")} right bottom / var(--t) 50% no-repeat`,
].join(", ");

function PopularBadge() {
return (
<span
className="absolute top-0 left-1/2 inline-grid -translate-x-1/2 -translate-y-1/2"
style={{ "--t": "13px" } as CSSProperties}
>
<span
aria-hidden="true"
className="bg-cc-surface relative z-0 [grid-area:1/1]"
style={{ clipPath: HEX_CLIP }}
/>
<span
aria-hidden="true"
className="relative z-10 [grid-area:1/1]"
style={{ background: BADGE_OUTLINE }}
/>
<span className="text-cc-heading relative z-20 px-7 py-2 font-mono text-[0.65rem] tracking-[0.15em] whitespace-nowrap uppercase [grid-area:1/1]">
Most Popular
</span>
</span>
);
}
46 changes: 46 additions & 0 deletions website-next/src/components/PopularBadge.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
// Cyan-to-blue slice of the card's border gradient (the tab sits where the
// border is in that range), used for the hexagon stroke.
const STROKE_FROM = "#16b9e4";
const STROKE_TO = "#5b94cf";

// Hexagon outline in a stretched viewBox: the tips touch the left/right edges at
// mid-height and the slants inset from the top/bottom corners. preserveAspect
// "none" stretches it to the badge box (sized by the text), and a non-scaling
// stroke keeps a uniform 1.5px through that stretch, matching the card border.
const HEX_POINTS = "0,20 11,0 89,0 100,20 89,40 11,40";

/**
* The "Most Popular" hexagon tab that sits on the top border of a popular
* `Offering` card. Drawn as a single SVG polygon stroked with the cyan-to-blue
* slice of the border gradient so it reads as part of the border, with a solid
* fill behind the label.
*/
export function PopularBadge() {
return (
<span className="absolute -top-[0.75px] left-1/2 -translate-x-1/2 -translate-y-1/2">
<svg
aria-hidden="true"
viewBox="0 0 100 40"
preserveAspectRatio="none"
className="absolute inset-0 h-full w-full overflow-visible"
>
<defs>
<linearGradient id="popular-badge-stroke" x1="0" y1="0" x2="1" y2="0">
<stop offset="0" stopColor={STROKE_FROM} />
<stop offset="1" stopColor={STROKE_TO} />
</linearGradient>
</defs>
<polygon
points={HEX_POINTS}
fill="var(--color-cc-surface)"
stroke="url(#popular-badge-stroke)"
strokeWidth="1.5"
vectorEffect="non-scaling-stroke"
/>
</svg>
<span className="text-cc-heading relative block px-7 py-3 indent-[0.15em] font-mono text-[0.65rem] leading-none tracking-[0.15em] whitespace-nowrap uppercase">
Most Popular
</span>
</span>
);
}
4 changes: 2 additions & 2 deletions website-next/src/components/home/BuildYourWay.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -56,8 +56,8 @@ const PRODUCTS = [
*/
export function BuildYourWay() {
return (
<section className="mx-auto max-w-6xl px-5 py-8 sm:px-12">
<div className="mx-auto max-w-6xl overflow-hidden rounded-3xl shadow-[0_24px_50px_rgba(0,0,0,0.4)]">
<section className="mx-auto max-w-7xl px-5 py-8 sm:px-12">
<div className="mx-auto max-w-7xl overflow-hidden rounded-3xl shadow-[0_24px_50px_rgba(0,0,0,0.4)]">
{/* Pitch region (top of the card). */}
<div
className="px-8 pt-12 pb-12 text-[#0b1018] sm:px-12 sm:pt-14 lg:px-16"
Expand Down
2 changes: 1 addition & 1 deletion website-next/src/components/home/FusionFlow.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ const SCRIM =
*/
export function FusionFlow() {
return (
<section className="mx-auto max-w-6xl px-5 sm:px-12">
<section className="mx-auto max-w-7xl px-5 sm:px-12">
{/* Built apart / queried together */}
<div className="py-16 text-center sm:py-20">
<h2 className="font-heading text-cc-heading text-h2 sm:text-h1 leading-[1.05] font-semibold text-balance">
Expand Down
2 changes: 1 addition & 1 deletion website-next/src/components/home/HomeHero.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ function position(
*/
export function HomeHero() {
return (
<section className="relative isolate mx-auto flex min-h-[34rem] max-w-6xl flex-col items-center justify-center px-5 py-20 text-center sm:min-h-[40rem] sm:px-12 lg:min-h-[46rem]">
<section className="relative isolate mx-auto flex min-h-[34rem] max-w-7xl flex-col items-center justify-center px-5 py-20 text-center sm:min-h-[40rem] sm:px-12 lg:min-h-[46rem]">
{/* Decorative scatter, behind the headline. Trimmed corner composition on
phones, full scatter from the `sm` breakpoint up. */}
<div
Expand Down
2 changes: 1 addition & 1 deletion website-next/src/components/home/LogoCloud.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import { SwissLifeLogo } from "@/src/icons/SwissLifeLogo";
*/
export function LogoCloud() {
return (
<section className="mx-auto max-w-6xl px-5 py-12 text-center sm:px-12 sm:py-16">
<section className="mx-auto max-w-7xl px-5 py-12 text-center sm:px-12 sm:py-16">
<p className="text-cc-nav-label font-mono text-xs tracking-[0.2em] uppercase">
Trusted by Enterprises
</p>
Expand Down
Loading
Loading