diff --git a/astro.config.ts b/astro.config.ts index 424375c6a4a8b80..e8dbb39a4cf4ea5 100644 --- a/astro.config.ts +++ b/astro.config.ts @@ -60,6 +60,7 @@ async function autogenStyles() { ) .filter((x) => x.isFile()) .map((x) => x.parentPath + x.name) + .filter((x) => x !== "./src/styles/landing.css") .sort((a) => (a === "./src/styles/tailwind.css" ? -1 : 1)); return styles; diff --git a/public/favicon.png b/public/favicon.png index 8bd0c9b482173af..ddfa0fb1eea556b 100644 Binary files a/public/favicon.png and b/public/favicon.png differ diff --git a/public/logo.svg b/public/logo.svg index 4b93dd11b4ad173..f7f592ce23c749c 100644 --- a/public/logo.svg +++ b/public/logo.svg @@ -1 +1,13 @@ - \ No newline at end of file + + + + + + + + + + + + + diff --git a/public/pattern.png b/public/pattern.png new file mode 100644 index 000000000000000..d82422818cbaf33 Binary files /dev/null and b/public/pattern.png differ diff --git a/src/assets/logo.svg b/src/assets/logo.svg index d3758724507c945..f7f592ce23c749c 100644 --- a/src/assets/logo.svg +++ b/src/assets/logo.svg @@ -1 +1,13 @@ - \ No newline at end of file + + + + + + + + + + + + + diff --git a/src/components/landing/AccelerateSection.astro b/src/components/landing/AccelerateSection.astro new file mode 100644 index 000000000000000..8ec99e5ea93cd6f --- /dev/null +++ b/src/components/landing/AccelerateSection.astro @@ -0,0 +1,46 @@ +--- +import ProductCard from "./ProductCard.astro"; +import ProductCardGrid from "./ProductCardGrid.astro"; +import SectionHeading from "./SectionHeading.astro"; +import ViewAllLink from "./ViewAllLink.astro"; +import { products } from "./accelerate-data"; +--- + +
+
+
+ + Faster web performance + +

+ Accelerate websites and applications with Cloudflare CDN caching, image + optimization, smart routing, load balancing, and web analytics. +

+
+ + Explore Directory + +
+ + + { + products.map((p) => ( + + )) + } + +
diff --git a/src/components/landing/AgentSetup.astro b/src/components/landing/AgentSetup.astro new file mode 100644 index 000000000000000..45b13594c3dbf0e --- /dev/null +++ b/src/components/landing/AgentSetup.astro @@ -0,0 +1,61 @@ +--- +import { Icon } from "@astrojs/starlight/components"; +import CornerMarks from "./CornerMarks.astro"; +import HalftoneBackground from "./HalftoneBackground.astro"; +import SectionHeading from "./SectionHeading.astro"; +import CopyPromptButton from "./CopyPromptButton.astro"; +import Button from "./Button.astro"; + +const ALL_GUIDES_HREF = "/agent-setup/"; +--- + +
+
+ + +
+
+ + Build with your favorite AI agent + + +

+ Paste into any AI coding agent to install Cloudflare agent tooling: +

+ +
+
+ +
+ + Browse all agent setup guides + +
+ + +
+
+
+
diff --git a/src/components/landing/BuildFromScratch.astro b/src/components/landing/BuildFromScratch.astro new file mode 100644 index 000000000000000..552c47ec3dc1a51 --- /dev/null +++ b/src/components/landing/BuildFromScratch.astro @@ -0,0 +1,187 @@ +--- +import { Icon } from "astro-icon/components"; +import CommandBlock from "./CommandBlock.astro"; +import CornerMarks from "./CornerMarks.astro"; +import Button from "./Button.astro"; +import RightArrow from "./RightArrow.astro"; +import SectionHeading from "./SectionHeading.astro"; +import TabBar from "./TabBar.astro"; + +const primitives = [ + { + id: "compute", + label: "Compute", + icon: "workers-vpc", + iconColor: "#0a95ff", + iconBg: "rgba(10, 149, 255, 0.1)", + iconBgDark: "rgba(10, 149, 255, 0.16)", + heading: "Deploy with one command", + description: + "Deploy serverless functions and full-stack apps on Cloudflare's global network. No servers to manage. No cold starts or region complexity.", + command: "npx wrangler init my-app", + cta: { + label: "Create your first Worker", + href: "/workers/get-started/guide/", + }, + tags: [ + { label: "Workers", href: "/workers/" }, + { label: "Containers", href: "/containers/" }, + { label: "Durable Objects", href: "/durable-objects/" }, + { label: "Queues", href: "/queues/" }, + ], + }, + { + id: "ai", + label: "AI", + icon: "workers-ai", + iconColor: "#19e306", + iconBg: "#f2f5e1", + iconBgDark: "rgba(25, 227, 6, 0.16)", + heading: "The AI inference platform", + description: + "Run AI inference globally with one API call, build agents, and search across your data — no GPUs to manage, no capacity planning.", + command: "npx wrangler ai models", + cta: { label: "Browse available models", href: "/workers-ai/models/" }, + tags: [ + { label: "Workers AI", href: "/workers-ai/" }, + { label: "AI Gateway", href: "/ai-gateway/" }, + { label: "Agents", href: "/agents/" }, + { label: "Vectorize", href: "/vectorize/" }, + { label: "Browser Run", href: "/browser-run/" }, + ], + }, + { + id: "storage", + label: "Storage & Databases", + icon: "d1", + iconColor: "#ee0ddb", + iconBg: "rgba(238, 13, 219, 0.1)", + iconBgDark: "rgba(238, 13, 219, 0.16)", + heading: "Make your database feel instant, everywhere", + description: + "Serverless SQL, globally distributed key-value, and global database acceleration — query directly from Workers with no connection management.", + command: "npx wrangler d1 create my-database", + cta: { label: "Get started with D1", href: "/d1/get-started/" }, + tags: [ + { label: "R2", href: "/r2/" }, + { label: "D1", href: "/d1/" }, + { label: "KV", href: "/kv/" }, + { label: "Hyperdrive", href: "/hyperdrive/" }, + ], + }, + { + id: "media", + label: "Media", + icon: "images", + iconColor: "#9616ff", + iconBg: "rgba(150, 22, 255, 0.1)", + iconBgDark: "rgba(150, 22, 255, 0.22)", + heading: "Build media pipelines without infrastructure headaches", + description: + "Cloudflare Images helps teams build scalable, reliable media pipelines to store, optimize, and deliver images.", + command: + "curl --request POST https://api.cloudflare.com/client/v4/accounts/$ACCOUNT_ID/images/v1", + cta: { + label: "Get started with Images", + href: "/images/get-started/introduction/", + }, + tags: [ + { label: "Images", href: "/images/" }, + { label: "Stream", href: "/stream/" }, + { label: "Realtime", href: "/realtime/" }, + ], + }, +]; +--- + +
+ + Powerful primitives, seamlessly integrated + + +
+ + + ({ id: p.id, label: p.label }))} + /> + + { + primitives.map((p, i) => ( + + )) + } +
+
+ + diff --git a/src/components/landing/Button.astro b/src/components/landing/Button.astro new file mode 100644 index 000000000000000..9657e3f7861a030 --- /dev/null +++ b/src/components/landing/Button.astro @@ -0,0 +1,94 @@ +--- +type Variant = "primary" | "secondary" | "ghost"; +type Size = "lg" | "md" | "sm"; + +interface Props { + href?: string; + variant?: Variant; + size?: Size; + id?: string; + type?: "button" | "submit" | "reset"; + class?: string; + target?: string; + rel?: string; + "aria-label"?: string; + "data-tooltip"?: string; +} + +const { + href, + variant = "primary", + size = "md", + id, + type = "button", + class: className = "", + target, + rel, + "aria-label": ariaLabel, + "data-tooltip": dataTooltip, +} = Astro.props; + +const base = [ + "relative shrink-0 isolate inline-flex items-center justify-center", + "origin-center gap-2 whitespace-nowrap font-medium no-underline", + "outline-1 outline-transparent outline-offset-2", + "transition-[scale,translate] ease-[cubic-bezier(0.25,0.46,0.45,0.94)] duration-[160ms]", + "focus-visible:ring-[3px] focus-visible:ring-accent-100/50", + "disabled:pointer-events-none disabled:opacity-50", + "before:content-[''] before:absolute before:-inset-[1px] before:opacity-0 before:rounded-[inherit] before:pointer-events-none before:z-[3]", + "before:bg-current before:transition-opacity before:duration-[160ms] before:ease-[cubic-bezier(0.55,0.085,0.68,0.53)]", + "after:content-[''] after:absolute after:-inset-[1px] after:opacity-0 after:rounded-[inherit] after:pointer-events-none after:z-[2]", + "after:border after:border-current", + "after:transition-opacity after:duration-[600ms] after:delay-[100ms] after:ease-[cubic-bezier(0.25,0.46,0.45,0.94)]", + "active:after:transition-none", + "active:translate-y-[1px] active:scale-[0.98]", + "hover:opacity-95", + "[&>*]:relative [&>*]:z-[4]", + "rounded-full", +]; + +const variantClasses: Record = { + primary: [ + "bg-accent-100 text-light-foreground", + "hover:bg-accent-100 hover:text-light-foreground", + "hover:before:opacity-[0.18] focus-visible:before:opacity-[0.18]", + ].join(" "), + secondary: [ + "text-foreground border-border-100 bg-background", + "border border-zinc-200 dark:border-[#2a2a2a]", + "hover:before:opacity-[0.04] focus-visible:before:opacity-[0.04]", + ].join(" "), + ghost: [ + "bg-transparent text-foreground-muted border-transparent", + "hover:text-foreground", + ].join(" "), +}; + +const sizeClasses: Record = { + lg: "px-6 py-3 md:text-lg", + md: "px-4 py-2 text-sm", + sm: "px-3 py-1.5 text-xs", +}; + +const cls = [ + ...base, + variantClasses[variant], + sizeClasses[size], + className, +].join(" "); + +const Tag = href ? "a" : "button"; +--- + + + + diff --git a/src/components/landing/ChangelogSection.astro b/src/components/landing/ChangelogSection.astro new file mode 100644 index 000000000000000..418d93e09913aeb --- /dev/null +++ b/src/components/landing/ChangelogSection.astro @@ -0,0 +1,166 @@ +--- +import { getChangelogs } from "~/util/changelog"; +import { getEntries } from "astro:content"; +import { format } from "date-fns"; +import CornerMarks from "./CornerMarks.astro"; +import HalftoneBackground from "./HalftoneBackground.astro"; +import RightArrow from "./RightArrow.astro"; +import SectionHeading from "./SectionHeading.astro"; +import ViewAllLink from "./ViewAllLink.astro"; + +const COMPUTE = new Set([ + "workers", + "pages", + "containers", + "workflows", + "durable-objects", + "queues", + "pipelines", + "cloudflare-for-platforms", + "workers-vpc", +]); +const STORAGE = new Set(["r2", "d1", "kv", "hyperdrive"]); +const AI = new Set([ + "workers-ai", + "ai-gateway", + "ai-search", + "agents", + "vectorize", + "browser-rendering", + "ai-crawl-control", +]); +const MEDIA = new Set(["images", "stream", "realtime"]); + +function catColor(id: string) { + if (COMPUTE.has(id)) return "#0a95ff"; + if (STORAGE.has(id)) return "#ee0ddb"; + if (AI.has(id)) return "#19e306"; + if (MEDIA.has(id)) return "#9616ff"; + return "#f56500"; +} + +const all = await getChangelogs({ + filter: (e) => !e.data.hidden, +}); + +const entries = await Promise.all( + all.slice(0, 8).map(async (entry) => { + const products = await getEntries(entry.data.products); + return { + title: entry.data.title, + description: entry.data.description, + dateFormatted: format(entry.data.date, "MMM dd, yyyy"), + dateShort: format(entry.data.date, "MMM dd"), + products: products.map((p) => ({ + name: p.data.name, + id: p.id, + color: catColor(p.id), + })), + url: `/changelog/post/${entry.id}/`, + }; + }), +); + +const featured = entries[0]; +const grid = entries.slice(1); +--- + +{ + entries.length > 0 && ( +
+
+
+ + What's new + +

+ The latest features and improvements shipping across Cloudflare. +

+
+ View Changelog +
+ +
+ ) +} diff --git a/src/components/landing/CommandBlock.astro b/src/components/landing/CommandBlock.astro new file mode 100644 index 000000000000000..7c148d92694650a --- /dev/null +++ b/src/components/landing/CommandBlock.astro @@ -0,0 +1,126 @@ +--- +interface Props { + id: string; + command: string; + showPrompt?: boolean; + rounded?: boolean; + class?: string; +} + +const { + id, + command, + showPrompt = false, + rounded = false, + class: className = "", +} = Astro.props; +--- + +
+ { + showPrompt && ( + + ) + } + + + {command} + + + +
+ + + + diff --git a/src/components/landing/CommunityGrid.astro b/src/components/landing/CommunityGrid.astro new file mode 100644 index 000000000000000..c4e2699202fde8f --- /dev/null +++ b/src/components/landing/CommunityGrid.astro @@ -0,0 +1,85 @@ +--- +import CornerMarks from "./CornerMarks.astro"; +import SectionHeading from "./SectionHeading.astro"; + +const cards = [ + { + label: "Community", + heading: "Join the conversation", + desc: "Share ideas, answers, and code with the Cloudflare community.", + links: [ + { label: "Discord", href: "https://discord.cloudflare.com/" }, + { label: "X", href: "https://x.com/cloudflare" }, + { label: "Forum", href: "https://community.cloudflare.com/" }, + ], + }, + { + label: "Open Source", + heading: "View the source", + desc: "Cloudflare contributes to the open-source ecosystem in a variety of ways, including:", + links: [ + { label: "GitHub", href: "https://github.com/cloudflare" }, + { label: "Sponsors", href: "https://github.com/sponsors/cloudflare" }, + { label: "Style guide", href: "/style-guide/" }, + ], + }, + { + label: "Blog", + heading: "Read the latest", + desc: "Get the latest news on Cloudflare products, technologies, and culture.", + links: [ + { label: "blog.cloudflare.com", href: "https://blog.cloudflare.com/" }, + ], + }, +]; +--- + +
+
+ + Connect with Cloudflare + +

+ Find community, read the blog, and explore open source projects. +

+
+ +
+ + + { + cards.map((card) => ( +
+ + {card.label} + +

+ {card.heading} +

+

+ {card.desc} +

+ +
+ {card.links.map((link) => ( + + {link.label} + + ))} +
+
+ )) + } +
+
diff --git a/src/components/landing/CopyPromptButton.astro b/src/components/landing/CopyPromptButton.astro new file mode 100644 index 000000000000000..5bd79913158bcdd --- /dev/null +++ b/src/components/landing/CopyPromptButton.astro @@ -0,0 +1,117 @@ +--- +import { AGENTS, type AgentId } from "./constants"; +import Button from "./Button.astro"; + +interface Props { + /** + * Agent ids to feature in the icon row, in order. Defaults to a + * recognizable 4-agent subset; the AgentSetup section below the hero + * still surfaces all six. + */ + agentIds?: readonly AgentId[]; + /** + * Optional class hooks merged onto the underlying secondary Button. + */ + class?: string; + /** + * Optional hover/focus tooltip shown above the button. When omitted, no + * tooltip is rendered (e.g., the duplicate button in the AgentSetup + * section, where the surrounding copy already provides context). + */ + tooltip?: string; +} + +const { + agentIds = ["claude", "cursor", "codex", "opencode"] as const, + class: className = "", + tooltip, +} = Astro.props; + +const featuredAgents = AGENTS.filter((a) => + (agentIds as readonly string[]).includes(a.id), +); +--- + + + + diff --git a/src/components/landing/CornerMarks.astro b/src/components/landing/CornerMarks.astro new file mode 100644 index 000000000000000..2543d3c86becf80 --- /dev/null +++ b/src/components/landing/CornerMarks.astro @@ -0,0 +1,45 @@ +--- +type Corner = "tl" | "tr" | "bl" | "br"; +type Tone = "neutral" | "accent"; + +interface Props { + corners?: Corner[]; + size?: number; + offset?: number; + tone?: Tone; + class?: string; +} + +const { + corners = ["tl", "tr", "bl", "br"] as Corner[], + size = 14, + offset = -7, + tone = "neutral", + class: className = "", +} = Astro.props; + +const positions: Record = { + tl: `left:${offset}px;top:${offset}px;`, + tr: `right:${offset}px;top:${offset}px;`, + bl: `left:${offset}px;bottom:${offset}px;`, + br: `right:${offset}px;bottom:${offset}px;`, +}; + +const borderColor = + tone === "accent" ? "rgba(255, 251, 245, 0.32)" : "var(--color-border-100)"; + +const baseStyle = `width:${size}px;height:${size}px;border:1px solid ${borderColor};border-radius:3px;`; + +const fillClasses = + tone === "accent" ? "bg-accent-100" : "bg-background dark:bg-background"; +--- + +{ + corners.map((c) => ( +