Feat: Add marketing site

Home page with the live ping demo and architecture diagram, and a
dev-only switcher linking to the other portals.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-09-03 14:05:22 +05:45
co-authored by Claude Opus 5
parent 62eeda9080
commit 158be8240b
15 changed files with 978 additions and 5 deletions
+7 -5
View File
@@ -1,17 +1,19 @@
# Multi-stage, one image, both planes -- which one runs is picked at container
# start via APP (core|cloud), not at build time. This keeps a single build
# cacheable across both instead of rebuilding the whole workspace twice.
FROM node:20-alpine AS deps
WORKDIR /w
COPY package.json package-lock.json ./
COPY packages/shared/package.json packages/shared/package.json
COPY apps/core/package.json apps/core/package.json
COPY apps/cloud/package.json apps/cloud/package.json
COPY apps/community/package.json apps/community/package.json
COPY apps/community-provider/package.json apps/community-provider/package.json
COPY apps/community-admin/package.json apps/community-admin/package.json
COPY apps/marketing/package.json apps/marketing/package.json
RUN npm install
FROM deps AS runtime
WORKDIR /w
COPY . .
COPY --from=deps /w/node_modules /w/node_modules
EXPOSE 8080 8083
CMD sh -c 'npm run build -w @xcloudify/${APP:?set APP=core|cloud} && npm run preview -w @xcloudify/${APP} -- --host 0.0.0.0 --port ${PORT:-8080}'
EXPOSE 8080 8081 8082 8083 8084 8086
CMD sh -c 'npm run build -w @xcloudify/${APP:?set APP=core|cloud|community|community-provider|community-admin|marketing} && npm run preview -w @xcloudify/${APP} -- --host 0.0.0.0 --port ${PORT:-8080}'
+9
View File
@@ -0,0 +1,9 @@
CLOUD_URL=http://localhost:8080
COMMUNITY_URL=http://localhost:8081
CORE_URL=http://localhost:8083
COMMUNITY_API_BASE_URL=http://localhost:5002/api
# true|false to force the floating dev portal switcher on/off; unset follows
# Vite's DEV flag (on for `vite dev`, off for a production build/preview).
SHOW_DEV_NAV=
ALLOWED_HOST=localhost
+20
View File
@@ -0,0 +1,20 @@
services:
marketing:
build:
context: ../..
dockerfile: Dockerfile
container_name: xcloudify-marketing
environment:
APP: marketing
PORT: "8082"
ALLOWED_HOST: ${ALLOWED_HOST:-localhost}
VITE_CLOUD_URL: ${CLOUD_URL:-}
VITE_COMMUNITY_URL: ${COMMUNITY_URL:-}
VITE_CORE_URL: ${CORE_URL:-}
VITE_COMMUNITY_API_BASE_URL: ${COMMUNITY_API_BASE_URL:-http://localhost:5002/api}
# Shows the floating dev portal switcher regardless of build mode --
# unset falls back to Vite's DEV flag (see DevPortalSwitcher.tsx).
VITE_SHOW_DEV_NAV: ${SHOW_DEV_NAV:-}
ports:
- "8082:8082"
+13
View File
@@ -0,0 +1,13 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>xcloudify</title>
<meta name="description" content="Core, public cloud, and community cloud on one platform" />
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.tsx"></script>
</body>
</html>
+12
View File
@@ -0,0 +1,12 @@
{
"name": "@xcloudify/marketing",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "vite build",
"preview": "vite preview"
},
"dependencies": { "@xcloudify/shared": "*" }
}
+1
View File
@@ -0,0 +1 @@
export default { plugins: { tailwindcss: {}, autoprefixer: {} } };
+29
View File
@@ -0,0 +1,29 @@
/**
* Public marketing site. No auth, no session, no sidebar -- it is a separate
* deployable from the portals precisely so it can be served publicly without
* carrying any authenticated surface.
*
* The one live thing it does is the latency demo, which calls the community
* plane's anonymous endpoints directly (nodes/available + ping-session) and
* then measures browser-to-worker RTT over a real WebSocket. That is real
* infrastructure, not a mock.
*/
import { BrowserRouter, Routes, Route } from "react-router-dom";
import { TooltipProvider } from "@/components/ui/tooltip";
import { HomePage } from "@/components/HomePage";
import { DevPortalSwitcher } from "./DevPortalSwitcher";
import NotFound from "@/pages/NotFound";
export default function App() {
return (
<TooltipProvider>
<BrowserRouter>
<DevPortalSwitcher />
<Routes>
<Route path="/" element={<HomePage />} />
<Route path="*" element={<NotFound />} />
</Routes>
</BrowserRouter>
</TooltipProvider>
);
}
+75
View File
@@ -0,0 +1,75 @@
/**
* Dev-only quick nav. The public page has no portal-chooser section --
* every "sign in" CTA on HomePage links straight to cloud (see goToCloud
* there), and core has no public login at all. During local/staging work
* this is the only on-page way to reach core or community without typing
* the URL by hand.
*
* Visibility is VITE_SHOW_DEV_NAV (set via SHOW_DEV_NAV in .env / compose),
* not just Vite's built-in DEV flag -- the Docker path always runs `vite
* build` + `vite preview`, a production build, where DEV is false even on a
* box you're using for local testing. Leave the env var unset and it still
* falls back to DEV, so a plain `vite dev` shows it with zero config; set it
* explicitly to force the button on (or off) regardless of build mode.
*/
import { useState } from "react";
import {
DropdownMenu, DropdownMenuContent, DropdownMenuItem,
DropdownMenuLabel, DropdownMenuSeparator, DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu";
import { Boxes, Cloud as CloudIcon, Users, Wrench } from "lucide-react";
type DevTarget = { label: string; href?: string; icon: React.ComponentType<any> };
const DEV_TARGETS: DevTarget[] = [
{ label: "Core — operator console", href: import.meta.env.VITE_CORE_URL, icon: Boxes },
{ label: "Cloud portal", href: import.meta.env.VITE_CLOUD_URL, icon: CloudIcon },
{ label: "Community portal", href: import.meta.env.VITE_COMMUNITY_URL, icon: Users },
];
const explicitShowDevNav = import.meta.env.VITE_SHOW_DEV_NAV;
const SHOW_DEV_NAV =
explicitShowDevNav != null ? explicitShowDevNav === "true" : import.meta.env.DEV;
export function DevPortalSwitcher() {
const [open, setOpen] = useState(false);
if (!SHOW_DEV_NAV) return null;
return (
<div className="fixed right-4 top-4 z-50">
<DropdownMenu open={open} onOpenChange={setOpen}>
<DropdownMenuTrigger
className="group relative flex items-center gap-1.5 rounded-full border border-amber-400/60 bg-amber-400/10 px-3 py-1.5 text-xs font-medium text-amber-700 shadow-sm outline-none backdrop-blur transition-colors hover:bg-amber-400/20 dark:text-amber-300"
>
<span className="relative flex h-2 w-2">
{!open && (
<span className="absolute inline-flex h-full w-full animate-ping rounded-full bg-amber-400 opacity-75" />
)}
<span className="relative inline-flex h-2 w-2 rounded-full bg-amber-500" />
</span>
<Wrench className="h-3.5 w-3.5" />
Dev
</DropdownMenuTrigger>
<DropdownMenuContent align="end" className="w-64">
<DropdownMenuLabel className="text-xs text-muted-foreground">
Jump to a running portal
</DropdownMenuLabel>
<DropdownMenuSeparator />
{DEV_TARGETS.map(({ label, href, icon: Icon }) => (
<DropdownMenuItem
key={label}
disabled={!href}
onClick={() => href && (window.location.href = href)}
className="gap-2"
>
<Icon className="h-4 w-4 text-primary" />
<span className="flex-1">{label}</span>
{!href && <span className="text-[10px] text-muted-foreground">not set</span>}
</DropdownMenuItem>
))}
</DropdownMenuContent>
</DropdownMenu>
</div>
);
}
+5
View File
@@ -0,0 +1,5 @@
import { createRoot } from "react-dom/client";
import App from "./App";
import "@/index.css";
createRoot(document.getElementById("root")!).render(<App />);
+5
View File
@@ -0,0 +1,5 @@
// Re-exports the shared theme so all four apps look identical. Content globs
// are relative to this file, so each app scans its own src plus the shared
// component library.
import shared from "../../packages/shared/tailwind.config";
export default shared;
+13
View File
@@ -0,0 +1,13 @@
{
"compilerOptions": {
"target": "ES2020", "useDefineForClassFields": true,
"lib": ["ES2020", "DOM", "DOM.Iterable"], "module": "ESNext",
"skipLibCheck": true, "moduleResolution": "bundler",
"allowImportingTsExtensions": true, "isolatedModules": true,
"moduleDetection": "force", "noEmit": true, "jsx": "react-jsx",
"strict": false, "noUnusedLocals": false, "noUnusedParameters": false,
"noImplicitAny": false, "strictNullChecks": false, "allowJs": true,
"baseUrl": ".", "paths": { "@/*": ["../../packages/shared/src/*"] }
},
"include": ["src", "../../packages/shared/src"]
}
+16
View File
@@ -0,0 +1,16 @@
import { defineConfig } from "vite";
import react from "@vitejs/plugin-react-swc";
import path from "path";
// Every app resolves "@/" to the SHARED package, not its own src. That is
// what lets ~110 shared components keep their existing imports untouched
// while each app stays a thin entry point that only picks which portals to
// mount. App-local files import relatively.
const PORT = parseInt(process.env.PORT ?? "8082", 10);
export default defineConfig({
server: { host: "::", port: PORT, allowedHosts: (process.env.ALLOWED_HOST ?? "localhost").split(",") },
preview: { host: "::", port: PORT, allowedHosts: (process.env.ALLOWED_HOST ?? "localhost").split(",") },
plugins: [react()],
resolve: { alias: { "@": path.resolve(__dirname, "../../packages/shared/src") } },
});
+484
View File
@@ -0,0 +1,484 @@
import { useState } from "react";
import { Button } from "@/components/ui/button";
import { Card, CardContent } from "@/components/ui/card";
import { Badge } from "@/components/ui/badge";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
import {
DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu";
import { useNavigate } from "react-router-dom";
import {
Shield, Zap, Globe, Users, ArrowRight, Check, Boxes,
Cloud as CloudIcon, Server, Network, Database, ChevronDown,
} from "lucide-react";
import { CoreArchitectureDiagram } from "@/components/marketing/CoreArchitectureDiagram";
import { LivePingDemo } from "@/components/marketing/LivePingDemo";
type PlatformTab = "core" | "cloud" | "community";
const PLATFORM_MODES: { value: PlatformTab; icon: typeof Boxes; label: string }[] = [
{ value: "core", icon: Boxes, label: "Core" },
{ value: "cloud", icon: CloudIcon, label: "Cloud" },
{ value: "community", icon: Users, label: "Community" },
];
const HERO_STACK_ROWS = [
{ icon: Server, label: "Compute" },
{ icon: Network, label: "Network" },
{ icon: Database, label: "Storage" },
{ icon: Boxes, label: "Nodes" },
];
const HERO_MODE_BARS = [
{ label: "Core", pct: 70 },
{ label: "Cloud", pct: 45 },
{ label: "Community", pct: 85 },
];
const CORE_POINTS = [
"One primitive for compute, network, and storage",
"Runs underneath any orchestrator you already use",
"Fewer nodes for the same headroom",
];
const CLOUD_FEATURES = [
{ icon: Shield, title: "OIDC login", desc: "Single sign-on, no shared credentials to leak." },
{ icon: Globe, title: "Projects & VDCs", desc: "Organize resources the way your org is structured." },
{ icon: Users, title: "Role-based access", desc: "Invite teammates as readers or writers, per VDC." },
];
const COMMUNITY_POINTS = [
"Real machines, not a mockup",
"Latency measured directly browser → worker",
"No signup required to try it",
];
const CAPABILITIES = [
{
icon: Shield,
title: "Enterprise Security",
description: "Military-grade security with end-to-end encryption and compliance"
},
{
icon: Zap,
title: "Lightning Fast",
description: "Deploy and scale workloads instantly across global infrastructure"
},
{
icon: Globe,
title: "Global Reach",
description: "Multi-region deployment with edge computing capabilities"
},
{
icon: Users,
title: "Team Collaboration",
description: "Built for teams with role-based access and real-time collaboration"
}
];
const BENEFITS = [
"99.99% uptime SLA",
"Auto-scaling infrastructure",
"24/7 expert support",
"Enterprise-grade security",
"Global CDN network",
"One-click deployments"
];
function SectionHeading({ title, description }: { title: string; description: string }) {
return (
<div className="mb-14 max-w-2xl space-y-4">
<h2 className="text-3xl md:text-4xl font-bold">{title}</h2>
<p className="text-muted-foreground text-lg">{description}</p>
</div>
);
}
export const HomePage = () => {
const navigate = useNavigate();
// The marketing site hosts no authenticated surface; every "sign in" CTA
// is a real link out to wherever the cloud portal is actually deployed --
// that's the plane customers sign up on day to day (see the Cloud tab
// below). A no-op with no configured URL is fine here: on any real
// deploy this is set, and locally the dev switcher covers the gap.
const cloudUrl = import.meta.env.VITE_CLOUD_URL as string | undefined;
const goToCloud = () => {
if (cloudUrl) window.location.href = cloudUrl;
};
const [platformTab, setPlatformTab] = useState<PlatformTab>("core");
const goToPlatform = (tab: PlatformTab) => {
setPlatformTab(tab);
document.getElementById("platform")?.scrollIntoView({ behavior: "smooth", block: "start" });
};
return (
<div className="min-h-screen relative bg-gradient-to-br from-background via-background to-muted/30">
{/* Background texture */}
<div className="pointer-events-none absolute inset-0 z-0 bg-grid-soft"></div>
<div className="relative z-10">
{/* Navigation */}
<nav className="border-b bg-background/95 backdrop-blur supports-[backdrop-filter]:bg-background/60">
<div className="container mx-auto px-6">
<div className="flex h-16 items-center">
<div className="flex items-center gap-10">
<div className="text-2xl font-bold text-primary">XCloudify</div>
<div className="hidden md:flex items-center gap-6 text-sm font-medium text-muted-foreground">
<DropdownMenu>
<DropdownMenuTrigger className="flex items-center gap-1 outline-none hover:text-foreground transition-colors">
Platform <ChevronDown className="h-3.5 w-3.5" />
</DropdownMenuTrigger>
<DropdownMenuContent
align="start"
className="w-48"
onCloseAutoFocus={(e) => e.preventDefault()}
>
{PLATFORM_MODES.map((mode) => (
<DropdownMenuItem key={mode.value} onClick={() => goToPlatform(mode.value)} className="gap-2">
<mode.icon className="h-4 w-4 text-primary" /> {mode.label}
</DropdownMenuItem>
))}
</DropdownMenuContent>
</DropdownMenu>
<a href="#capabilities" className="hover:text-foreground transition-colors">Capabilities</a>
</div>
</div>
<div className="ml-auto flex items-center gap-3">
<Button onClick={() => goToCloud()} className="gap-2">
Login <ArrowRight className="h-4 w-4" />
</Button>
</div>
</div>
</div>
</nav>
{/* Hero Section */}
<section className="container mx-auto px-6 pt-20 pb-28">
<div className="grid gap-16 lg:grid-cols-12 lg:items-center">
<div className="lg:col-span-6 space-y-8">
<Badge variant="outline" className="gap-1.5">
<Boxes className="h-3.5 w-3.5" /> Core &middot; Cloud &middot; Community
</Badge>
<h1 className="text-4xl md:text-6xl font-bold tracking-tight leading-[1.08]">
One infrastructure platform.
<span className="block text-primary">Run it your way.</span>
</h1>
<p className="text-lg text-muted-foreground max-w-xl leading-relaxed">
The same compute, network, and storage primitives &mdash; self-hosted as
core, consumed managed as cloud, or leased from community hardware.
Pick the model that fits, switch anytime.
</p>
<div className="flex flex-col sm:flex-row gap-4">
<Button size="lg" onClick={() => goToCloud()} className="gap-2">
Get Started <ArrowRight className="h-4 w-4" />
</Button>
<Button size="lg" variant="outline" onClick={() => goToPlatform("core")}>
See how it works
</Button>
</div>
<div className="flex flex-wrap gap-3 pt-2">
{PLATFORM_MODES.map((mode) => (
<button
key={mode.value}
onClick={() => goToPlatform(mode.value)}
className="flex items-center gap-2 rounded-full border bg-background/70 px-4 py-2 text-sm text-muted-foreground hover:text-foreground hover:border-primary/40 transition-colors"
>
<mode.icon className="h-4 w-4" /> {mode.label}
</button>
))}
</div>
</div>
<div className="lg:col-span-6">
<div className="relative">
<div
aria-hidden
className="absolute -inset-6 -z-10 rounded-[2rem] bg-gradient-to-br from-primary/15 via-primary/5 to-transparent blur-2xl"
/>
<Card className="overflow-hidden border-muted/60 shadow-2xl">
<div className="flex items-center gap-1.5 border-b bg-muted/40 px-4 py-3">
<span className="h-2.5 w-2.5 rounded-full bg-red-400/60" />
<span className="h-2.5 w-2.5 rounded-full bg-yellow-400/60" />
<span className="h-2.5 w-2.5 rounded-full bg-green-400/60" />
<span className="ml-3 text-xs text-muted-foreground">console.xcloudify</span>
</div>
<CardContent className="grid grid-cols-5 gap-5 p-6">
<div className="col-span-2 space-y-2.5">
{HERO_STACK_ROWS.map((row) => (
<div key={row.label} className="flex items-center gap-2 rounded-md bg-muted/40 px-3 py-2.5">
<row.icon className="h-3.5 w-3.5 text-primary shrink-0" />
<span className="text-xs font-medium">{row.label}</span>
</div>
))}
</div>
<div className="col-span-3 space-y-4">
<div className="grid grid-cols-3 gap-3">
{HERO_MODE_BARS.map((m) => (
<div key={m.label} className="space-y-2 rounded-md border bg-background p-3">
<div className="text-[11px] text-muted-foreground">{m.label}</div>
<div className="h-10 overflow-hidden rounded bg-primary/15">
<div
className="w-full rounded bg-primary/60"
style={{ height: `${m.pct}%`, marginTop: `${100 - m.pct}%` }}
/>
</div>
</div>
))}
</div>
<div className="space-y-2">
{[0, 1, 2].map((i) => (
<div key={i} className="flex items-center gap-3 rounded-md border px-3 py-2.5">
<span className="h-2 w-2 shrink-0 rounded-full bg-green-500" />
<div className="h-2 flex-1 rounded bg-muted" />
<div className="h-2 w-10 rounded bg-muted" />
</div>
))}
</div>
</div>
</CardContent>
</Card>
</div>
</div>
</div>
</section>
{/* Platform: three modalities */}
<section id="platform" className="border-t bg-muted/10 scroll-mt-16">
<div className="container mx-auto px-6 py-24">
<SectionHeading
title="Three ways to run it"
description="The same infrastructure primitives, exposed however your team needs to consume them."
/>
<Tabs value={platformTab} onValueChange={(v) => setPlatformTab(v as PlatformTab)}>
<TabsList className="mb-12">
{PLATFORM_MODES.map((mode) => (
<TabsTrigger key={mode.value} value={mode.value} className="gap-1.5">
<mode.icon className="h-4 w-4" /> {mode.label}
</TabsTrigger>
))}
</TabsList>
<TabsContent value="core">
<div className="grid gap-10 lg:grid-cols-12 lg:items-start">
<div className="lg:col-span-4 space-y-5 lg:sticky lg:top-24">
<h3 className="text-xl font-semibold">Sits under your own cloud stack</h3>
<p className="text-muted-foreground">
Core is identity-free infrastructure &mdash; compute, network, and
storage as one set of primitives underneath whatever orchestrator
you already run.
</p>
<ul className="space-y-3">
{CORE_POINTS.map((point) => (
<li key={point} className="flex gap-2.5 text-sm">
<Check className="h-4 w-4 shrink-0 text-primary mt-0.5" />
<span>{point}</span>
</li>
))}
</ul>
</div>
<div className="lg:col-span-8">
<Card className="border-muted/50">
<CardContent className="p-6 md:p-8">
<CoreArchitectureDiagram />
</CardContent>
</Card>
</div>
</div>
</TabsContent>
<TabsContent value="cloud">
<div className="grid gap-10 lg:grid-cols-12 lg:items-start">
<div className="lg:col-span-5 space-y-5 lg:sticky lg:top-24">
<h3 className="text-xl font-semibold">The managed control plane for teams</h3>
<p className="text-muted-foreground">
Cloud is what most customers use day to day: sign in, get a
project provisioned automatically, organize work into virtual
data centers, and invite teammates with read or write roles
scoped per VDC.
</p>
<Button onClick={() => goToCloud()} className="gap-2">
Try the cloud console <ArrowRight className="h-4 w-4" />
</Button>
</div>
<div className="lg:col-span-7 space-y-4">
{CLOUD_FEATURES.map((f) => (
<div key={f.title} className="flex items-start gap-4 rounded-lg border bg-background p-5">
<div className="w-10 h-10 shrink-0 bg-primary/10 rounded-lg flex items-center justify-center">
<f.icon className="h-5 w-5 text-primary" />
</div>
<div className="space-y-1">
<h4 className="font-semibold">{f.title}</h4>
<p className="text-sm text-muted-foreground">{f.desc}</p>
</div>
</div>
))}
</div>
</div>
</TabsContent>
<TabsContent value="community">
<div className="grid gap-10 lg:grid-cols-12 lg:items-start">
<div className="lg:col-span-4 space-y-5 lg:sticky lg:top-24">
<h3 className="text-xl font-semibold">Leased hardware, real machines</h3>
<p className="text-muted-foreground">
Community is spare capacity from third-party operators: register
your own machine to earn from it, or lease someone else's after
checking the latency yourself.
</p>
<ul className="space-y-3">
{COMMUNITY_POINTS.map((point) => (
<li key={point} className="flex gap-2.5 text-sm">
<Check className="h-4 w-4 shrink-0 text-primary mt-0.5" />
<span>{point}</span>
</li>
))}
</ul>
</div>
<div className="lg:col-span-8">
<LivePingDemo />
</div>
</div>
</TabsContent>
</Tabs>
</div>
</section>
{/* Capabilities Grid */}
<section id="capabilities" className="container mx-auto px-6 py-24 scroll-mt-16">
<SectionHeading
title="Why choose xCloudify?"
description="Everything you need to build, deploy, and scale modern applications."
/>
<div className="grid md:grid-cols-2 lg:grid-cols-4 gap-6">
{CAPABILITIES.map((feature) => (
<Card key={feature.title} className="border-muted/50 hover:border-primary/20 transition-colors">
<CardContent className="p-6 space-y-4">
<div className="w-11 h-11 bg-primary/10 rounded-lg flex items-center justify-center">
<feature.icon className="h-5 w-5 text-primary" />
</div>
<h3 className="font-semibold text-lg">{feature.title}</h3>
<p className="text-muted-foreground text-sm leading-relaxed">
{feature.description}
</p>
</CardContent>
</Card>
))}
</div>
</section>
{/* Benefits Section */}
<section className="border-t bg-muted/10">
<div className="container mx-auto px-6 py-24">
<div className="grid lg:grid-cols-2 gap-16 items-center">
<div className="space-y-8">
<h2 className="text-3xl md:text-4xl font-bold">
Built for Modern Teams
</h2>
<p className="text-muted-foreground text-lg leading-relaxed">
From startups to Fortune 500 companies, XCloudify provides the tools
and infrastructure you need to succeed in the cloud-first world.
</p>
<div className="grid sm:grid-cols-2 gap-4">
{BENEFITS.map((benefit) => (
<div key={benefit} className="flex items-center gap-3">
<div className="w-5 h-5 bg-green-500/20 rounded-full flex items-center justify-center flex-shrink-0">
<Check className="h-3 w-3 text-green-600" />
</div>
<span className="text-sm">{benefit}</span>
</div>
))}
</div>
<Button size="lg" onClick={() => goToCloud()} className="gap-2">
Start Your Journey <ArrowRight className="h-4 w-4" />
</Button>
</div>
<div className="relative">
<Card className="p-8 bg-gradient-to-br from-primary/5 to-primary/10 border-primary/20">
<CardContent className="space-y-6">
<div className="space-y-2">
<div className="text-3xl font-bold text-primary">99.99%</div>
<div className="text-sm text-muted-foreground">Uptime SLA</div>
</div>
<div className="space-y-2">
<div className="text-3xl font-bold text-primary">&lt; 100ms</div>
<div className="text-sm text-muted-foreground">Global Latency</div>
</div>
<div className="space-y-2">
<div className="text-3xl font-bold text-primary">24/7</div>
<div className="text-sm text-muted-foreground">Expert Support</div>
</div>
</CardContent>
</Card>
</div>
</div>
</div>
</section>
{/* CTA Section */}
<section className="border-t bg-muted/20">
<div className="container mx-auto px-6 py-24 text-center">
<div className="max-w-2xl mx-auto space-y-8">
<h2 className="text-3xl md:text-4xl font-bold">
Ready to Get Started?
</h2>
<p className="text-muted-foreground text-lg">
Join thousands of developers already building on XCloudify
</p>
<Button size="lg" onClick={() => goToCloud()} className="gap-2">
Access Your Dashboard <ArrowRight className="h-4 w-4" />
</Button>
</div>
</div>
</section>
{/* Footer */}
<footer className="border-t bg-background">
<div className="container mx-auto px-6 py-16">
<div className="grid gap-12 md:grid-cols-2 lg:grid-cols-4">
<div className="space-y-3 lg:col-span-2">
<div className="text-xl font-bold text-primary">XCloudify</div>
<p className="text-sm text-muted-foreground max-w-xs">
One infrastructure platform &mdash; run it yourself as core, consume it
managed as cloud, or lease community hardware.
</p>
</div>
<div className="space-y-3">
<div className="text-sm font-semibold">Platform</div>
<ul className="space-y-2 text-sm text-muted-foreground">
<li><a href="#platform" className="hover:text-foreground transition-colors">Core</a></li>
<li><a href="#platform" className="hover:text-foreground transition-colors">Cloud</a></li>
<li><a href="#platform" className="hover:text-foreground transition-colors">Community</a></li>
</ul>
</div>
<div className="space-y-3">
<div className="text-sm font-semibold">Get started</div>
<ul className="space-y-2 text-sm text-muted-foreground">
<li>
<button onClick={() => goToCloud()} className="hover:text-foreground transition-colors">
Sign in
</button>
</li>
<li><a href="#capabilities" className="hover:text-foreground transition-colors">Capabilities</a></li>
</ul>
</div>
</div>
<div className="mt-12 flex flex-col sm:flex-row items-center justify-between gap-4 border-t pt-8 text-xs text-muted-foreground">
<span>&copy; {new Date().getFullYear()} XCloudify. All rights reserved.</span>
<span>Community hardware is operator-owned &mdash; see the marketplace for details.</span>
</div>
</div>
</footer>
</div>
</div>
);
};
@@ -0,0 +1,85 @@
import { Boxes, Database, Network, Server } from "lucide-react";
const SELF_MANAGED_LAYERS = [
"Your applications",
"Load balancer",
"Orchestrator (Kubernetes)",
"Storage layer — self-managed",
"Network / SDN — self-managed",
];
const CORE_LAYERS = ["Your applications", "Orchestrator (Kubernetes, or bring your own)"];
/**
* Visualizes why xCloudify core exists: it collapses the storage/network
* layers teams otherwise hand-roll into one primitive underneath whatever
* orchestrator they already run, so the same headroom needs fewer nodes.
*/
export function CoreArchitectureDiagram() {
return (
<div className="grid gap-10 lg:grid-cols-2">
<div className="flex h-full flex-col">
<p className="text-sm font-medium text-muted-foreground">Building it yourself</p>
<div className="mt-3 space-y-2">
{SELF_MANAGED_LAYERS.map((label) => (
<div key={label} className="rounded-md border bg-muted/40 px-4 py-3 text-sm">
{label}
</div>
))}
</div>
<div className="mt-auto pt-6">
<div className="grid grid-cols-6 gap-1.5">
{Array.from({ length: 12 }).map((_, i) => (
<div key={i} className="aspect-square rounded bg-muted-foreground/20" />
))}
</div>
<p className="mt-3 text-center text-xs text-muted-foreground">
12 nodes, each running a slice of every layer
</p>
</div>
</div>
<div className="flex h-full flex-col">
<p className="text-sm font-medium text-primary">With xCloudify Core</p>
<div className="mt-3 space-y-2">
{CORE_LAYERS.map((label) => (
<div key={label} className="rounded-md border bg-muted/40 px-4 py-3 text-sm">
{label}
</div>
))}
<div className="relative overflow-hidden rounded-md border-2 border-primary/40 bg-gradient-to-br from-primary/10 to-primary/5 px-4 py-4">
<span
aria-hidden
className="absolute left-1/2 top-0 h-1.5 w-1.5 -translate-x-1/2 rounded-full bg-primary animate-flow-down"
/>
<div className="flex items-center gap-2 font-medium text-primary">
<Boxes className="h-4 w-4" />
xCloudify Core
</div>
<div className="mt-2 flex flex-wrap gap-3 text-xs text-muted-foreground">
<span className="flex items-center gap-1">
<Server className="h-3 w-3" /> compute
</span>
<span className="flex items-center gap-1">
<Network className="h-3 w-3" /> network
</span>
<span className="flex items-center gap-1">
<Database className="h-3 w-3" /> storage
</span>
</div>
</div>
</div>
<div className="mt-auto pt-6">
<div className="grid grid-cols-6 gap-1.5">
{Array.from({ length: 4 }).map((_, i) => (
<div key={i} className="aspect-square rounded bg-primary/30 ring-1 ring-primary/50" />
))}
</div>
<p className="mt-3 text-center text-xs font-medium text-primary">
4 nodes, same headroom
</p>
</div>
</div>
</div>
);
}
@@ -0,0 +1,204 @@
import { useCallback, useEffect, useState } from "react";
import { Cpu, Gauge, MapPin, MemoryStick, ShieldCheck, Wifi, WifiOff } from "lucide-react";
import { Button } from "@/components/ui/button";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { Badge } from "@/components/ui/badge";
import { communityApi, CommunityNode, measurePing } from "@/services/communityApi";
interface PingResult {
medianMs: number | null;
path: string;
error?: string;
}
/**
* Decorative browser<->worker link. Dashed and static when there is no
* community plane to reach; pulsing when there is, so the demo's own visual
* matches what it is honestly able to claim in the copy above it.
*/
function ConnectionIllustration({ reachable }: { reachable: boolean }) {
return (
<svg viewBox="0 0 220 90" className="h-24 w-full max-w-xs text-muted-foreground/50" aria-hidden="true">
<rect x="6" y="27" width="56" height="38" rx="7" className="fill-background stroke-current" strokeWidth="1.5" />
<circle cx="17" cy="36" r="2" className="fill-current" />
<circle cx="24" cy="36" r="2" className="fill-current" />
<circle cx="31" cy="36" r="2" className="fill-current" />
<rect x="14" y="44" width="40" height="4" rx="1" className="fill-current opacity-30" />
<rect x="14" y="52" width="28" height="4" rx="1" className="fill-current opacity-30" />
<line
x1="62" y1="46" x2="158" y2="46"
strokeDasharray="5 5"
className={reachable ? "stroke-primary" : "stroke-current"}
strokeWidth="1.5"
/>
{reachable ? (
<circle cx="110" cy="46" r="3" className="fill-primary">
<animate attributeName="opacity" values="1;0.15;1" dur="1.4s" repeatCount="indefinite" />
</circle>
) : (
<circle cx="110" cy="46" r="3" className="fill-destructive" />
)}
<rect x="158" y="18" width="56" height="54" rx="7" className="fill-background stroke-current" strokeWidth="1.5" />
<rect x="166" y="28" width="40" height="6" rx="1" className="fill-current opacity-30" />
<rect x="166" y="40" width="40" height="6" rx="1" className="fill-current opacity-30" />
<rect x="166" y="52" width="40" height="6" rx="1" className="fill-current opacity-30" />
</svg>
);
}
export function LivePingDemo() {
const [nodes, setNodes] = useState<CommunityNode[]>([]);
const [loading, setLoading] = useState(true);
const [offline, setOffline] = useState(false);
const [pings, setPings] = useState<Record<string, PingResult | "pending">>({});
const load = useCallback(async () => {
setLoading(true);
try {
setNodes(await communityApi.listAvailableNodes());
setOffline(false);
} catch (err) {
// A network-level failure just means this install isn't running a
// community plane -- expected on plenty of deploys. Fold it into the
// offline illustration below instead of surfacing the raw fetch
// error, but keep it in the console for anyone actually debugging.
console.error("LivePingDemo: could not reach the community plane", err);
setOffline(true);
} finally {
setLoading(false);
}
}, []);
useEffect(() => {
load();
}, [load]);
const runPing = async (node: CommunityNode) => {
setPings((p) => ({ ...p, [node.id]: "pending" }));
try {
const session = await communityApi.createPingSession(node.id);
const result = await measurePing(session);
setPings((p) => ({
...p,
[node.id]: { medianMs: result.medianMs, path: result.path },
}));
} catch (err) {
setPings((p) => ({
...p,
[node.id]: {
medianMs: null,
path: "unknown",
error: err instanceof Error ? err.message : String(err),
},
}));
}
};
return (
<div className="space-y-6">
<div className="flex items-start gap-3 rounded-md border bg-muted/30 p-3 text-sm">
<ShieldCheck className="mt-0.5 h-4 w-4 shrink-0 text-primary" />
<p className="text-muted-foreground">
{offline
? "This demo pings real community-hosted machines directly from your browser, not through our servers — it just needs a community plane to talk to, and this install isn't running one."
: "These are real, community-hosted machines on the marketplace right now. Ping one straight from your browser — no signup required. We only introduce the two ends; the measurement runs over a direct peer-to-peer connection to the machine, not through our servers."}
</p>
</div>
{loading && <p className="text-sm text-muted-foreground">Loading live nodes…</p>}
{!loading && offline && (
<Card>
<CardContent className="flex flex-col items-center gap-4 py-10 text-center">
<ConnectionIllustration reachable={false} />
<div className="flex items-center gap-2 text-sm font-medium">
<WifiOff className="h-4 w-4 text-muted-foreground" />
Demo unavailable on this install
</div>
<p className="max-w-sm text-xs text-muted-foreground">
No community plane is configured here, so there's nothing to ping yet.
</p>
</CardContent>
</Card>
)}
{!loading && !offline && nodes.length === 0 && (
<Card>
<CardContent className="flex flex-col items-center gap-4 py-10 text-center">
<ConnectionIllustration reachable />
<p className="text-sm text-muted-foreground">
No community nodes are online right now — check back shortly.
</p>
</CardContent>
</Card>
)}
<div className="grid gap-4 sm:grid-cols-2 lg:grid-cols-3">
{!offline && nodes.slice(0, 6).map((node) => {
const ping = pings[node.id];
return (
<Card key={node.id}>
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
<CardTitle className="text-base">{node.name}</CardTitle>
<Badge variant="outline">{node.trust_tier}</Badge>
</CardHeader>
<CardContent className="space-y-3">
<div className="flex flex-wrap gap-3 text-xs text-muted-foreground">
<span className="flex items-center gap-1">
<Cpu className="h-3 w-3" />
{node.specs.cpu_cores ?? "?"} cores
</span>
<span className="flex items-center gap-1">
<MemoryStick className="h-3 w-3" />
{Math.round((node.specs.memory_mb ?? 0) / 1024)} GB
</span>
<span className="flex items-center gap-1">
<MapPin className="h-3 w-3" />
{node.city ? `${node.city}, ${node.country}` : node.country}
</span>
</div>
<div className="flex items-center gap-2 border-t pt-3 text-sm">
<Gauge className="h-4 w-4 text-muted-foreground" />
{ping === "pending" && <span className="text-muted-foreground">Measuring…</span>}
{ping && ping !== "pending" && ping.medianMs !== null && (
<span className="font-medium">
{ping.medianMs.toFixed(0)} ms
<span className="font-normal text-muted-foreground"> via {ping.path}</span>
</span>
)}
{ping && ping !== "pending" && ping.error && (
<span className="text-xs text-destructive">{ping.error}</span>
)}
{!ping && (
<span className="text-muted-foreground">
{node.pingable === false ? "Worker offline" : "Not measured yet"}
</span>
)}
</div>
<Button
variant="outline"
size="sm"
className="w-full gap-2"
onClick={() => runPing(node)}
disabled={ping === "pending" || node.pingable === false}
>
<Wifi className="h-4 w-4" />
{ping === "pending"
? "Measuring…"
: node.pingable === false
? "Unreachable right now"
: "Ping"}
</Button>
</CardContent>
</Card>
);
})}
</div>
</div>
);
}