From dac3dc237613fc8e3250af53a2179b64d51bc932 Mon Sep 17 00:00:00 2001 From: Andrey Cheptsov Date: Thu, 24 Sep 2026 16:29:39 +0200 Subject: [PATCH 1/2] [Website] Update Slurm info on landing --- mkdocs/overrides/header-2.html | 8 +- website/public/static/logos/crusoe.svg | 5 + website/public/static/logos/slurm.svg | 5 + .../src/components/ArchitectureDiagram.tsx | 82 +++--- website/src/components/Capabilities.tsx | 79 ++++++ website/src/components/ComputeSourcesTabs.tsx | 5 +- website/src/components/SiteNavigation.tsx | 10 +- website/src/pages/Factory/CapacityTabs.tsx | 27 +- .../pages/Factory/FactoryGetStartedTabs.tsx | 38 +++ website/src/pages/Factory/InferenceTabs.tsx | 18 +- .../src/pages/Factory/OrchestrationTabs.tsx | 2 +- website/src/pages/Factory/TenantTabs.tsx | 40 +-- website/src/pages/Factory/capabilities.tsx | 69 +++++ website/src/pages/Home/ExploreSection.tsx | 10 +- website/src/pages/Home/FaqSection.tsx | 38 ++- website/src/pages/Home/GetStartedSection.tsx | 263 ++++++++---------- website/src/pages/Sky/MarketplaceTabs.tsx | 12 +- website/src/styles.css | 156 +++++++---- 18 files changed, 542 insertions(+), 325 deletions(-) create mode 100644 website/public/static/logos/crusoe.svg create mode 100644 website/public/static/logos/slurm.svg create mode 100644 website/src/components/Capabilities.tsx create mode 100644 website/src/pages/Factory/FactoryGetStartedTabs.tsx create mode 100644 website/src/pages/Factory/capabilities.tsx diff --git a/mkdocs/overrides/header-2.html b/mkdocs/overrides/header-2.html index 86a97c4b1..5b3183284 100644 --- a/mkdocs/overrides/header-2.html +++ b/mkdocs/overrides/header-2.html @@ -74,7 +74,7 @@ {% include "partials/search.html" %} {% endif %} - {# Keep product descriptions in sync with SiteNavigation.tsx and GetStartedSection.tsx. #} + {# Keep menu descriptions in sync with SiteNavigation.tsx. GetStartedSection.tsx uses a longer dstack description. #}
diff --git a/website/public/static/logos/crusoe.svg b/website/public/static/logos/crusoe.svg new file mode 100644 index 000000000..6de594c0c --- /dev/null +++ b/website/public/static/logos/crusoe.svg @@ -0,0 +1,5 @@ + + + + diff --git a/website/public/static/logos/slurm.svg b/website/public/static/logos/slurm.svg new file mode 100644 index 000000000..5d4de19ea --- /dev/null +++ b/website/public/static/logos/slurm.svg @@ -0,0 +1,5 @@ + + + + diff --git a/website/src/components/ArchitectureDiagram.tsx b/website/src/components/ArchitectureDiagram.tsx index c8d4ab118..4bb2082d1 100644 --- a/website/src/components/ArchitectureDiagram.tsx +++ b/website/src/components/ArchitectureDiagram.tsx @@ -1,4 +1,5 @@ import { asset } from '../asset'; +import { docsUrl } from '../routes'; // Layered "vendor-agnostic" architecture diagram, rebuilt as HTML/CSS (replaces the previous // static SVG). Logos are recolored to the current text color via CSS masking (see .arch-logo in @@ -32,16 +33,28 @@ const MODELS: Logo[] = [ const DOCKER: Logo = { key: 'docker', label: 'Docker', src: logoSrc('docker.svg') }; -const GPU_CLOUDS: Logo[] = [ - { key: 'aws', label: 'AWS', src: logoSrc('aws.svg') }, - { key: 'gcp', label: 'Google Cloud', src: logoSrc('gcp.svg') }, - { key: 'lambda', label: 'Lambda', src: logoSrc('lambda.svg') }, +const ORCHESTRATION = [ + { label: 'Fleets', href: docsUrl('concepts/fleets/') }, + { label: 'Tasks', href: docsUrl('concepts/tasks/') }, + { label: 'Services', href: docsUrl('concepts/services/') }, + { label: 'Presets', href: docsUrl('concepts/presets/') }, + { label: 'Gateways', href: docsUrl('concepts/gateways/') }, +]; + +const PLATFORMS = [ + { label: 'VMs', href: docsUrl('concepts/fleets/#ssh-fleets') }, + { label: 'Bare-metal', href: docsUrl('concepts/fleets/#ssh-fleets') }, + { label: 'K8S', href: docsUrl('concepts/backends/#kubernetes') }, + { label: 'Slurm', href: docsUrl('concepts/backends/#slurm') }, +]; + +const CLOUDS: Logo[] = [ { key: 'nebius', label: 'Nebius', src: logoSrc('nebius.svg') }, + { key: 'crusoe', label: 'Crusoe', src: logoSrc('crusoe.svg') }, { key: 'runpod', label: 'RunPod', src: logoSrc('runpod.svg') }, + { key: 'aws', label: 'AWS', src: logoSrc('aws.svg') }, ]; -const KUBERNETES: Logo = { key: 'kubernetes', label: 'Kubernetes', src: logoSrc('kubernetes.svg') }; - const HARDWARE: Logo[] = [ { key: 'nvidia', label: 'NVIDIA', src: logoSrc('nvidia.svg') }, { key: 'amd', label: 'AMD', src: logoSrc('amd.webp') }, @@ -80,7 +93,7 @@ function LogoRow({ logos }: { logos: Logo[] }) { export function ArchitectureDiagram() { return (
-
+
{/* Top: what plugs in on top of the orchestration layer. */}
@@ -93,38 +106,41 @@ export function ArchitectureDiagram() {
- {/* Middle: the orchestration layer itself */} -
-
- dstack orchestration - +
+ {/* The orchestration layer and the infrastructure it runs on. */} +
+
+ dstack orchestration + +
+
+ {ORCHESTRATION.map(({ label, href }) => ( + + {label} + + ))} +
-
- {['Fleets', 'Tasks', 'Services', 'Presets', 'Gateways'].map(name => ( -
- {name} -
- ))} -
-
- {/* Bottom: where workloads run */} -
-
- - Clouds -
-
- - Kubernetes -
-
- VMs or bare-metal +
+ + Any cloud + + + {PLATFORMS.map(({ label, href }) => ( + + {label} + + ))}
- Any hardware + Any accelerator
diff --git a/website/src/components/Capabilities.tsx b/website/src/components/Capabilities.tsx new file mode 100644 index 000000000..280ee1d74 --- /dev/null +++ b/website/src/components/Capabilities.tsx @@ -0,0 +1,79 @@ +import { ReactNode } from 'react'; +import { asset } from '../asset'; + +// Icon, title, and description rows shared by the landing and product pages. +export function CapList({ items }: { items: { icon: ReactNode; title: string; sub: string }[] }) { + return ( +
    + {items.map(cap => ( +
  • + {cap.icon} + + {cap.title} + {cap.sub} + +
  • + ))} +
+ ); +} + +export const BoxGlyph = () => ( + +); +export const CloudGlyph = () => ( + +); +export const LayersGlyph = () => ( + +); +// GPU chip glyph for the marketplace rows. +export const ChipGlyph = () => ( + +); +// On-prem glyphs (shared with the landing's "bring your own compute" block). +export const SlurmGlyph = () => ( + +); +export const ServerGlyph = () => ( + +); +// The Kubernetes helm wheel from /static/logos/kubernetes.svg, inlined with currentColor so it +// takes the capability-icon color in both themes (the asset has hardcoded black fills). +export const KubernetesGlyph = () => ( + +); diff --git a/website/src/components/ComputeSourcesTabs.tsx b/website/src/components/ComputeSourcesTabs.tsx index 7723eb449..41b1fd7b1 100644 --- a/website/src/components/ComputeSourcesTabs.tsx +++ b/website/src/components/ComputeSourcesTabs.tsx @@ -1,4 +1,4 @@ -import { CapList, CloudGlyph, KubernetesGlyph, ServerGlyph } from '../pages/Home/GetStartedSection'; +import { CapList, CloudGlyph, KubernetesGlyph, ServerGlyph, SlurmGlyph } from './Capabilities'; import { ContentTabs } from './ContentTabs'; const cloudGroups = [ @@ -10,6 +10,7 @@ const cloudGroups = [ const onPremItems = [ { icon: , title: 'SSH fleets', sub: 'Connect VMs or bare-metal clusters over SSH' }, { icon: , title: 'Kubernetes', sub: 'Connect your existing Kubernetes clusters' }, + { icon: , title: 'Slurm (experimental)', sub: 'Connect your existing Slurm clusters' }, ]; export function OnPremCapabilities() { @@ -48,7 +49,7 @@ export function ComputeSourcesTabs({ cloudsFirst = false }: { content: , footer: ( - Use your existing Kubernetes clusters, VMs, or bare-metal clusters + Use your existing Kubernetes or Slurm clusters, VMs, or bare-metal ), }; diff --git a/website/src/components/SiteNavigation.tsx b/website/src/components/SiteNavigation.tsx index 796ae7600..ceefad16a 100644 --- a/website/src/components/SiteNavigation.tsx +++ b/website/src/components/SiteNavigation.tsx @@ -50,12 +50,12 @@ type ProductLink = { // The products. products[0] (open-source) is featured at the top of the "Products" menu; the rest // follow as rows. Reused by the standalone top-nav hover menu and the mobile nav's "Products" // section. -// NOTE: the descriptions are duplicated in GetStartedSection.tsx (the product list) and -// mkdocs/overrides/header-2.html — keep all three in sync. +// Keep menu descriptions in sync with mkdocs/overrides/header-2.html. +// GetStartedSection.tsx uses a longer dstack description. const products: ProductLink[] = [ - { id: 'open-source', text: 'dstack', secondaryText: 'The open-source control plane for AI-native orchestration.', href: docsUrl('installation'), icon: , badge: 'Self-hosted', external: true }, - { id: 'factory', text: 'dstack Factory', secondaryText: 'A heterogeneous orchestration stack for AI token factories.', href: asset(ROUTES.FACTORY), icon: , badge: 'Self-hosted' }, - { id: 'sky-product', text: 'dstack Sky', secondaryText: 'One account across GPU clouds. Better prices and unified billing.', href: asset(ROUTES.SKY), icon: , badge: 'Hosted by us' }, + { id: 'open-source', text: 'dstack', secondaryText: 'A unified orchestration interface across GPU clouds, Kubernetes, VMs, and bare-metal.', href: docsUrl('installation'), icon: , badge: 'Self-hosted' }, + { id: 'factory', text: 'dstack Factory', secondaryText: 'A multi-tenant orchestration stack for AI labs, data centers, and AI token factories.', href: asset(ROUTES.FACTORY), icon: , badge: 'Self-hosted' }, + { id: 'sky-product', text: 'dstack Sky', secondaryText: 'Unified access to GPU clouds. Better prices and one billing.', href: asset(ROUTES.SKY), icon: , badge: 'Hosted by us' }, ]; // Items for the mobile slide-out navigation. The blog categories are top-level links (mirroring diff --git a/website/src/pages/Factory/CapacityTabs.tsx b/website/src/pages/Factory/CapacityTabs.tsx index 10739e705..b1079643d 100644 --- a/website/src/pages/Factory/CapacityTabs.tsx +++ b/website/src/pages/Factory/CapacityTabs.tsx @@ -1,28 +1,7 @@ +import { CapList } from '../../components/Capabilities'; import { OnPremCapabilities } from '../../components/ComputeSourcesTabs'; import { ContentTabs } from '../../components/ContentTabs'; -import { CapList } from '../Home/GetStartedSection'; - -const observabilityItems = [ - { - icon: ( - - ), - title: 'Events', - sub: 'Track resource state changes and operations', - }, - { - icon: ( - - ), - title: 'Metrics', - sub: 'Monitor utilization, accelerator health, and network health', - }, -]; +import { eventsCapability, metricsCapability } from './capabilities'; export function CapacityTabs() { return ( @@ -38,7 +17,7 @@ export function CapacityTabs() { { id: 'observability', label: 'Observability', - content: , + content: , }, ]} /> diff --git a/website/src/pages/Factory/FactoryGetStartedTabs.tsx b/website/src/pages/Factory/FactoryGetStartedTabs.tsx new file mode 100644 index 000000000..d28b4cb95 --- /dev/null +++ b/website/src/pages/Factory/FactoryGetStartedTabs.tsx @@ -0,0 +1,38 @@ +import { ReactNode } from 'react'; +import { CapList } from '../../components/Capabilities'; +import { ContentTabs } from '../../components/ContentTabs'; +import { + billingCapability, + eventsCapability, + exportsCapability, + registryCapability, +} from './capabilities'; + +export function FactoryGetStartedTabs({ footer }: { footer?: ReactNode }) { + return ( + , + footer, + }, + { + id: 'observability', + label: 'Observability', + content: , + footer, + }, + { + id: 'optimized-inference', + label: 'Serverless inference', + content: , + footer, + }, + ]} + /> + ); +} diff --git a/website/src/pages/Factory/InferenceTabs.tsx b/website/src/pages/Factory/InferenceTabs.tsx index 0a57fc5c5..860b34725 100644 --- a/website/src/pages/Factory/InferenceTabs.tsx +++ b/website/src/pages/Factory/InferenceTabs.tsx @@ -1,18 +1,6 @@ +import { CapList } from '../../components/Capabilities'; import { ContentTabs } from '../../components/ContentTabs'; -import { BoxGlyph, CapList, LayersGlyph } from '../Home/GetStartedSection'; - -const inferenceItems = [ - { - icon: , - title: 'Presets', - sub: 'Agent-based optimization and kernel generation', - }, - { - icon: , - title: 'Registry', - sub: 'Day-0 optimized presets for frontier open models', - }, -]; +import { presetsCapability, registryCapability } from './capabilities'; export function InferenceTabs() { return ( @@ -23,7 +11,7 @@ export function InferenceTabs() { { id: 'inference-optimization', label: 'Inference optimization', - content: , + content: , }, ]} /> diff --git a/website/src/pages/Factory/OrchestrationTabs.tsx b/website/src/pages/Factory/OrchestrationTabs.tsx index e8d8bec93..1cd46aeeb 100644 --- a/website/src/pages/Factory/OrchestrationTabs.tsx +++ b/website/src/pages/Factory/OrchestrationTabs.tsx @@ -1,5 +1,5 @@ +import { CapList, ServerGlyph } from '../../components/Capabilities'; import { ContentTabs } from '../../components/ContentTabs'; -import { CapList, ServerGlyph } from '../Home/GetStartedSection'; const orchestrationItems = [ { diff --git a/website/src/pages/Factory/TenantTabs.tsx b/website/src/pages/Factory/TenantTabs.tsx index 967b80cdc..09d421d05 100644 --- a/website/src/pages/Factory/TenantTabs.tsx +++ b/website/src/pages/Factory/TenantTabs.tsx @@ -1,40 +1,6 @@ +import { CapList } from '../../components/Capabilities'; import { ContentTabs } from '../../components/ContentTabs'; -import { CapList } from '../Home/GetStartedSection'; - -const tenantItems = [ - { - icon: ( - - ), - title: 'Projects', - sub: 'Tenant isolation and resource access control', - }, - { - icon: ( - - ), - title: 'Exports', - sub: 'Share selected fleets and gateways across projects', - }, - { - icon: ( - - ), - title: 'Billing', - sub: 'Usage metering and automated customer billing', - }, -]; +import { billingCapability, exportsCapability, projectsCapability } from './capabilities'; export function TenantTabs() { return ( @@ -45,7 +11,7 @@ export function TenantTabs() { { id: 'usage-and-billing', label: 'Usage and billing', - content: , + content: , }, ]} /> diff --git a/website/src/pages/Factory/capabilities.tsx b/website/src/pages/Factory/capabilities.tsx new file mode 100644 index 000000000..871f2ea46 --- /dev/null +++ b/website/src/pages/Factory/capabilities.tsx @@ -0,0 +1,69 @@ +import { BoxGlyph, LayersGlyph } from '../../components/Capabilities'; + +export const eventsCapability = { + icon: ( + + ), + title: 'Events', + sub: 'Audit user actions and resource lifecycle events across projects.', +}; + +export const metricsCapability = { + icon: ( + + ), + title: 'Metrics', + sub: 'Monitor utilization, accelerator health, and network health', +}; + +export const projectsCapability = { + icon: ( + + ), + title: 'Projects', + sub: 'Tenant isolation and resource access control', +}; + +export const exportsCapability = { + icon: ( + + ), + title: 'Exports', + sub: 'Share selected fleets and gateways across projects', +}; + +export const billingCapability = { + icon: ( + + ), + title: 'Billing', + sub: 'Compute and token metering and billing automation', +}; + +export const presetsCapability = { + icon: , + title: 'Presets', + sub: 'Agent-based optimization and kernel generation', +}; + +export const registryCapability = { + icon: , + title: 'Registry', + sub: 'Day-0 optimized presets for frontier open models', +}; diff --git a/website/src/pages/Home/ExploreSection.tsx b/website/src/pages/Home/ExploreSection.tsx index d49664ee1..0bc57fb4a 100644 --- a/website/src/pages/Home/ExploreSection.tsx +++ b/website/src/pages/Home/ExploreSection.tsx @@ -1,4 +1,5 @@ import { ReactNode } from 'react'; +import Link from '@cloudscape-design/components/link'; import { AlternatingDocBlock } from '../../components/AlternatingDocBlock'; import { ArchitectureDiagram } from '../../components/ArchitectureDiagram'; import { ComputeSourcesTabs } from '../../components/ComputeSourcesTabs'; @@ -23,7 +24,7 @@ export function ExploreSection() { dstack gives cloud tenants and data-center operators a unified control plane for managing compute and orchestrating AI workloads.

- It improves operational efficiency and removes vendor lock-in. No more hassle of building your own compute stack on top of Kubernetes or Slurm. + It improves operational efficiency and removes vendor lock-in. Use your existing infrastructure without building and maintaining your own AI compute stack. @@ -43,9 +44,10 @@ function BringComputeBlock() { imageFirst > Have bare-metal servers or VMs with SSH access? Point dstack to those hosts and provide SSH - credentials to create an SSH fleet. Have an existing Kubernetes cluster? Point dstack's - Kubernetes backend to the kubeconfig. dstack will schedule workloads on them alongside cloud - clusters. + credentials to create an SSH fleet. Have an existing Kubernetes or Slurm cluster? Connect it + through the Kubernetes backend or the {highlightTerms('experimental Slurm backend')}. + dstack provides a unified workload interface while Kubernetes or Slurm handles scheduling + within the cluster.

dstack natively integrates with the major GPU clouds and automates provisioning of clusters. diff --git a/website/src/pages/Home/FaqSection.tsx b/website/src/pages/Home/FaqSection.tsx index a51dc60c3..c9aa7c731 100644 --- a/website/src/pages/Home/FaqSection.tsx +++ b/website/src/pages/Home/FaqSection.tsx @@ -1,32 +1,58 @@ import { ReactNode, useState } from 'react'; import Button from '@cloudscape-design/components/button'; import ExpandableSection from '@cloudscape-design/components/expandable-section'; +import Link from '@cloudscape-design/components/link'; import SpaceBetween from '@cloudscape-design/components/space-between'; import { mainButtonStyle } from '../../cloudscape-theme'; import { AlternatingDocBlock } from '../../components/AlternatingDocBlock'; import { highlightTerms } from '../../components/highlightTerms'; +import { docsUrl } from '../../routes'; type FaqItem = { q: string; - a: string | string[]; + a: ReactNode; }; const faqItems: FaqItem[] = [ { - q: 'How does dstack differ from Slurm?', - a: 'Slurm is a battle-tested workload manager with decades of production use in HPC environments. dstack is a unified orchestration layer built for containerized AI workloads and heterogeneous AI compute. While both support batch jobs and distributed training, dstack also provides first-class primitives for compute management, inference, and observability, including native cloud provisioning.', + q: 'How does dstack compare to Slurm?', + a: [ + 'Slurm is a battle-tested workload manager used in HPC and large-scale AI training. dstack is a unified orchestration layer built for containerized AI workloads and heterogeneous AI compute, with first-class primitives for compute management, training, inference, and observability.', + 'dstack can be used instead of Slurm to orchestrate AI workloads directly on cloud GPUs, pre-provisioned VMs, or bare-metal. It can also run on top of existing Slurm clusters through the Slurm backend.', + ], }, { q: 'How does dstack compare to Kubernetes?', - a: 'Kubernetes is a general-purpose container orchestrator. dstack also orchestrates containers, but provides a lightweight, streamlined interface purpose-built for AI workloads, with first-class primitives for compute management, training, inference, and observability. It can use Kubernetes as a compute backend or work directly with cloud GPUs, pre-provisioned VMs, or bare-metal, helping cloud tenants and data-center operators improve utilization without building and maintaining their own AI orchestration stack.', + a: [ + 'Kubernetes is a general-purpose container orchestrator. dstack also orchestrates containers, but provides a lightweight, streamlined interface purpose-built for AI workloads, with first-class primitives for compute management, training, inference, and observability.', + 'dstack can be used instead of Kubernetes to orchestrate AI workloads directly on cloud GPUs, pre-provisioned VMs, or bare-metal. It can also run on top of existing Kubernetes clusters through the Kubernetes backend.', + ], }, { q: 'Can I use dstack with Kubernetes?', - a: 'Yes. Connect existing Kubernetes clusters through the Kubernetes backend, and dstack will schedule AI workloads on them alongside cloud GPUs, VMs, and bare-metal. Use the Kubernetes backend when your GPUs already run on Kubernetes or your team relies on its ecosystem and tooling. Otherwise, cloud backends are often simpler for cloud GPUs, and SSH fleets for pre-provisioned VMs or bare-metal.', + a: [ + <> + {highlightTerms('Yes. Connect existing Kubernetes clusters through the ')} + {highlightTerms('Kubernetes backend')} + {highlightTerms(', and dstack will schedule AI workloads on them alongside cloud GPUs, VMs, and bare-metal.')} + , + 'Use the Kubernetes backend when your GPUs already run on Kubernetes or your team relies on its ecosystem and tooling. Otherwise, cloud backends are often simpler for cloud GPUs, and SSH fleets for pre-provisioned VMs or bare-metal.', + ], + }, + { + q: 'Can I use dstack with Slurm?', + a: [ + <> + {highlightTerms('Yes. Connect existing Slurm clusters through the experimental ')} + {highlightTerms('Slurm backend')} + {highlightTerms('. dstack connects to each cluster’s login node over SSH and submits runs as Slurm jobs.')} + , + 'Slurm retains resource allocation and scheduling, while dstack provides the same interface for development, training, and inference used with other backends. The cluster must have Pyxis and enroot installed and configured.', + ], }, { q: 'When should I use dstack?', - a: 'Use dstack as a unified orchestration layer for cluster management, training, and inference, built for heterogeneous AI compute. It is designed for cloud tenants, data-center operators, and teams running their own AI token factory at planet scale.', + a: 'Use dstack as a unified orchestration layer for cluster management, training, and inference, built for heterogeneous AI compute. It is designed for cloud tenants, data-center operators, and AI token factories.', }, ]; diff --git a/website/src/pages/Home/GetStartedSection.tsx b/website/src/pages/Home/GetStartedSection.tsx index a07fb08e7..31ee42f1e 100644 --- a/website/src/pages/Home/GetStartedSection.tsx +++ b/website/src/pages/Home/GetStartedSection.tsx @@ -1,164 +1,147 @@ -import { ReactNode, useState } from 'react'; +import { Fragment, useId, useState } from 'react'; import CodeView from '@cloudscape-design/code-view/code-view'; import shHighlight from '@cloudscape-design/code-view/highlight/sh'; import Button from '@cloudscape-design/components/button'; import { asset } from '../../asset'; import { mainButtonStyle } from '../../cloudscape-theme'; +import { BoxGlyph, CloudGlyph, LayersGlyph } from '../../components/Capabilities'; +import { ContentTabs } from '../../components/ContentTabs'; +import { highlightTerms } from '../../components/highlightTerms'; import { installMethods, maxInstallLines, padYamlToLines } from '../../data/snippets'; import { DOCS_URL, ROUTES, docsUrl } from '../../routes'; +import { FactoryGetStartedTabs } from '../Factory/FactoryGetStartedTabs'; +import { MarketplaceTabs } from '../Sky/MarketplaceTabs'; -export const BoxGlyph = () => ( - -); -export const CloudGlyph = () => ( - -); -export const LayersGlyph = () => ( - -); -// GPU chip glyph for the marketplace rows. -export const ChipGlyph = () => ( - -); -// On-prem glyphs (shared with the landing's "bring your own compute" block). -export const ServerGlyph = () => ( - -); -// The Kubernetes helm wheel from /static/logos/kubernetes.svg, inlined with currentColor so it -// takes the capability-icon color in both themes (the asset has hardcoded black fills). -export const KubernetesGlyph = () => ( - -); +// Product menus in SiteNavigation.tsx and mkdocs/overrides/header-2.html use a shorter +// dstack description. +const products = [ + { + id: 'dstack', + name: 'dstack', + description: 'A unified orchestration interface across GPU clouds, Kubernetes, VMs, and bare-metal clusters.', + group: 'Self-hosted', + icon: , + }, + { + id: 'factory', + name: 'dstack Factory', + description: 'A multi-tenant orchestration stack for AI labs, data centers, and AI token factories.', + group: 'Self-hosted', + icon: , + }, + { + id: 'sky', + name: 'dstack Sky', + description: 'Unified access to GPU clouds. Better prices and one billing.', + group: 'Hosted by us', + icon: , + }, +] as const; -// Read-only shell snippet. Line wrapping is left off so padded snippets stay -// equal height across tabs (see padYamlToLines). -function ShellCode({ content }: { content: string }) { - return ( -
- -
- ); -} +type ProductId = (typeof products)[number]['id']; -// Icon, title, and description rows shared by the Home and Sky pages. -export function CapList({ items }: { items: { icon: ReactNode; title: string; sub: string }[] }) { - return ( -
    - {items.map(cap => ( -
  • - {cap.icon} - - {cap.title} - {cap.sub} - -
  • - ))} -
- ); -} - -// NOTE: the three product descriptions below are duplicated in SiteNavigation.tsx (products) -// and mkdocs/overrides/header-2.html — keep all three in sync. export function GetStartedSection() { - const [method, setMethod] = useState<(typeof installMethods)[number]['id']>(installMethods[0].id); - - const activeInstall = installMethods.find(m => m.id === method) ?? installMethods[0]; + const [product, setProduct] = useState('dstack'); + const id = useId(); + const footer = ; return (

Get started

-
-
Self-hosted
- - - - dstack - The open-source control plane for AI-native orchestration. - - - - - - - dstack Factory - A heterogeneous orchestration stack for AI token factories. - - - -
Hosted by us
- - - - dstack Sky - One account across GPU clouds. Better prices and unified billing. - - +
+ {products.map((option, index) => ( + + {option.group !== products[index - 1]?.group && ( +
{option.group}
+ )} + +
+ ))}
- {/* Open-source: install-method tabs + read-only code + footer CTA bar. */} -
-
-
- {installMethods.map(m => ( - - ))} -
-
- -
-
- - -
-
+
+ {product === 'dstack' && ( + ({ + id: method.id, + label: method.label, + content: , + footer, + }))} + /> + )} + {product === 'factory' && } + {product === 'sky' && }
); } + +function ProductFooter({ product }: { product: ProductId }) { + if (product === 'dstack') { + return ( + <> + + {highlightTerms('Set up the dstack server and bring your own compute')} + +
+ + +
+ + ); + } + + const isSky = product === 'sky'; + return ( + <> + + {highlightTerms( + isSky + ? 'All in dstack Factory, plus on-demand GPUs from our marketplace' + : 'All in dstack, plus multi-tenancy, billing, and serverless', + )} + +
+ + +
+ + ); +} + +// Keep padded snippets the same height across installation tabs. +function ShellCode({ content }: { content: string }) { + return ( +
+ +
+ ); +} diff --git a/website/src/pages/Sky/MarketplaceTabs.tsx b/website/src/pages/Sky/MarketplaceTabs.tsx index 852d431ba..7153858c4 100644 --- a/website/src/pages/Sky/MarketplaceTabs.tsx +++ b/website/src/pages/Sky/MarketplaceTabs.tsx @@ -1,7 +1,8 @@ +import { ReactNode } from 'react'; import Button from '@cloudscape-design/components/button'; import { mainButtonStyle } from '../../cloudscape-theme'; +import { ChipGlyph, CloudGlyph } from '../../components/Capabilities'; import { ContentTabs } from '../../components/ContentTabs'; -import { ChipGlyph, CloudGlyph } from '../Home/GetStartedSection'; import { gpuPrices } from './pricing'; // Default marketplace clouds from dstack Sky's /api/backends/list_base_types. @@ -11,7 +12,10 @@ const partnerGroups = [ ['Verda', 'Nebius'], ]; -export function MarketplaceTabs() { +export function MarketplaceTabs({ marketplaceOnly = false, footer }: { + marketplaceOnly?: boolean; + footer?: ReactNode; +}) { return ( ), - footer: ( + footer: footer ?? ( One account and unified billing across GPU clouds @@ -86,7 +90,7 @@ export function MarketplaceTabs() { ), }, - ]} + ].filter(tab => !marketplaceOnly || tab.id === 'marketplace')} /> ); } diff --git a/website/src/styles.css b/website/src/styles.css index fd7e718a8..d04158944 100644 --- a/website/src/styles.css +++ b/website/src/styles.css @@ -628,11 +628,21 @@ p { .arch-diagram { display: flex; flex-direction: column; - gap: 2.66cqw; /* 24u between the four layers */ + gap: 2.66cqw; /* 24u between the diagram blocks */ width: 100%; color: var(--cs-text); --arch-border-width: max(1px, 0.22cqw); /* placeholder badge stroke */ --arch-gradient: var(--cs-text); + --arch-label-padding: 2cqw; +} + +.arch-stack { + display: flex; + flex-direction: column; + overflow: hidden; + border: 0.5px solid currentColor; + border-radius: 12px; + background: var(--cs-panel); } .arch-row { @@ -645,9 +655,29 @@ p { grid-template-columns: repeat(2, minmax(0, 1fr)); } -/* Keep the logo-heavy cloud block wide, followed by Kubernetes and VMs/bare-metal. */ +/* Give cloud examples the remaining space after the compact compute labels. */ .arch-row--compute { - grid-template-columns: minmax(0, 1fr) max-content max-content; + grid-template-columns: minmax(0, 1fr) repeat(4, max-content); + gap: 1.26cqw; + padding: 3.2cqw var(--arch-label-padding) 3.48cqw; +} + +.arch-row--compute .arch-cell { + justify-content: center; + gap: 0.55cqw; + min-height: calc(8cqw + 6px); + padding-inline: 1.5cqw; + border: 1px dotted currentColor; + border-radius: 12px; +} + +.arch-row--compute .arch-cell--cloud { + justify-content: space-between; + gap: 1.55cqw; +} + +.arch-cell--cloud .arch-logos { + flex-shrink: 0; } /* Pin both input labels left and their logo groups right. */ @@ -661,7 +691,7 @@ p { align-items: center; gap: 1.55cqw; min-height: 9.83cqw; /* 88.7u */ - padding: 0 2cqw; + padding: 0 var(--arch-label-padding); border: 1px solid currentColor; border-radius: 12px; } @@ -680,17 +710,6 @@ p { min-height: 9.83cqw; } -/* "Any GPU cloud": logos left, label pushed to the right edge. */ -.arch-cell--gpu { - justify-content: space-between; - gap: 0.9cqw; - padding-inline: 2.1cqw; -} - -.arch-cell--gpu .arch-logos { - gap: 0.8cqw; -} - .arch-cell--platform { justify-content: center; gap: 0.55cqw; @@ -699,20 +718,17 @@ p { .arch-cell--platform .arch-cell__label { font-size: 2.55cqw; + text-align: center; + white-space: normal; } -.arch-cell--platform .arch-logo--kubernetes { - width: 4.8cqw; - height: 4.8cqw; -} - -/* "Any hardware": label pinned left, logos centered across the full row. */ +/* "Any accelerator": label pinned left, logos centered across the full row. */ .arch-cell--hw { justify-content: center; } .arch-cell--hw > .arch-cell__label { position: absolute; - left: 2cqw; + left: var(--arch-label-padding); } .arch-cell__label { @@ -722,15 +738,14 @@ p { white-space: nowrap; } -/* Orchestration layer — gradient band. Title gets more room above the cells than below - (≈73 vs ≈31u); cells inset ≈5% each side. */ +/* Orchestration and infrastructure share an outer border and aligned label padding. */ .arch-orchestration { display: flex; flex-direction: column; align-items: center; gap: 3cqw; - padding: 3.2cqw 3.2cqw 3.48cqw; - border-radius: 1.6cqw; + padding: 3.2cqw var(--arch-label-padding) 3.48cqw; + border-radius: 0 0 12px 12px; background: var(--arch-gradient); color: var(--cs-bg); } @@ -766,7 +781,17 @@ p { font-size: 2.25cqw; font-weight: 500; line-height: 1.2; - border: 1px solid currentColor; + border: 1px dotted currentColor; +} + +.arch-diagram .arch-link { + color: inherit; + text-decoration: none !important; +} + +.arch-diagram .arch-link:focus-visible { + outline: 2px solid currentColor; + outline-offset: 2px; } .arch-logos { @@ -790,14 +815,12 @@ p { mask-size: contain; } -/* Per-logo height (cqw, so it scales) + aspect from each source's viewBox. Wide marks (AWS, - NVIDIA) are set shorter so they don't dominate; tall ones (PyTorch) a touch taller. */ -.arch-logo--aws { height: 4.05cqw; aspect-ratio: 304 / 182; } -.arch-logo--gcp { height: 4.55cqw; aspect-ratio: 256 / 206; } -.arch-logo--lambda { height: 4.9cqw; aspect-ratio: 1; } +/* Per-logo height (cqw, so it scales) + aspect from each source's viewBox. Wide marks (NVIDIA) + are set shorter so they don't dominate; tall ones (PyTorch) a touch taller. */ .arch-logo--nebius { height: 4.6cqw; aspect-ratio: 1; } -.arch-logo--runpod { height: 5.04cqw; aspect-ratio: 120 / 130; } -.arch-logo--kubernetes { height: 4.98cqw; aspect-ratio: 1; } +.arch-logo--crusoe { height: 4.8cqw; aspect-ratio: 1; } +.arch-logo--runpod { height: calc(5.04cqw - 1.5px); aspect-ratio: 120 / 130; } +.arch-logo--aws { height: calc(4.05cqw - 1.5px); aspect-ratio: 304 / 182; } .arch-logo--pytorch { height: 4.9cqw; aspect-ratio: 90.3 / 109.1; } .arch-logo--vllm { height: 4.8cqw; aspect-ratio: 1; } .arch-logo--sglang { height: 4.8cqw; aspect-ratio: 596 / 683; } @@ -805,7 +828,7 @@ p { .arch-logo--nvidia { height: 4cqw; aspect-ratio: 271.7 / 179.7; } .arch-logo--amd { height: 4.8cqw; margin-left: 2px; aspect-ratio: 1600 / 1408; } .arch-logo--tenstorrent { height: 4.8cqw; aspect-ratio: 170 / 169; } -.arch-logo--tpu { height: 4.2cqw; aspect-ratio: 256 / 206; } /* shares the GCP mark, sized down a touch */ +.arch-logo--tpu { height: calc(4.2cqw - 1px); aspect-ratio: 256 / 206; } /* shares the GCP mark, sized down a touch */ /* "Any model" marks (top row) — square icons; plus the Docker marker on the orchestration band. */ /* Heights tuned per-logo for similar optical weight, compensating for differing padding and visual density inside each source viewBox. */ @@ -839,26 +862,26 @@ p { color: var(--cs-muted); } -/* Safety net for very narrow containers (phones): stop shrinking, stack the rows, drop the +/* Safety net for narrow containers (phones): stop shrinking, stack the rows, drop the primitives to two columns, and switch to fixed legible type. */ -@container (max-width: 360px) { - .arch-diagram { gap: 12px; } - /* Two columns: the wide first cell ("Any framework" / "Any cloud") spans the full width on its - own row, and the two narrower cells pair up on the row below. */ +@container (max-width: 480px) { + .arch-diagram { gap: 12px; --arch-label-padding: 14px; } + .arch-stack { border-radius: 8px; } + /* Stack the inputs and cloud examples, then pair the other compute sources. */ .arch-row { grid-template-columns: 1fr 1fr; gap: 8px; } .arch-row > :first-child { grid-column: 1 / -1; } .arch-row--inputs > .arch-cell { grid-column: 1 / -1; } - .arch-row--compute { grid-template-columns: repeat(2, minmax(0, 1fr)); } + .arch-row--compute { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 8px; padding: 16px var(--arch-label-padding); } .arch-row--compute > :first-child { grid-column: 1 / -1; } - .arch-cell { min-height: 52px; padding: 10px 14px; gap: 12px; border-width: 1px; border-radius: 8px; } + .arch-row--compute .arch-cell { min-height: 54px; padding: 11px 8px; border-radius: 8px; } + .arch-cell { min-height: 52px; padding: 10px var(--arch-label-padding); gap: 12px; border-width: 1px; border-radius: 8px; } .arch-cell__label { font-size: 16px; white-space: normal; } .arch-cell--platform { min-height: 52px; gap: 6px; padding: 8px 4px; } .arch-cell--platform .arch-cell__label { font-size: 16px; } - .arch-cell--platform .arch-logo--kubernetes { width: 22px; height: 22px; } - /* "Any hardware": label left, logos pushed to the right (the desktop layout centers them). */ + /* "Any accelerator": label left, logos pushed to the right (the desktop layout centers them). */ .arch-cell--hw { justify-content: space-between; } .arch-cell--hw > .arch-cell__label { position: static; } - .arch-orchestration { gap: 12px; padding: 16px; border-radius: 12px; } + .arch-orchestration { gap: 12px; padding: 16px var(--arch-label-padding); border-radius: 0 0 8px 8px; } .arch-orchestration__title { font-size: 15px; } .arch-orchestration__cells { grid-template-columns: repeat(2, 1fr); gap: 8px; } .arch-orchestration__cells > :last-child { grid-column: 1 / -1; } @@ -866,6 +889,9 @@ p { .arch-logos { gap: 12px; } /* !important to beat the per-logo cqw heights. */ .arch-logo { height: 22px !important; } + .arch-logo--runpod, + .arch-logo--aws { height: 20.5px !important; } + .arch-logo--tpu { height: 21px !important; } .arch-logo--placeholder { height: 22px !important; min-width: 22px !important; font-size: 12px; border-width: 1px; border-radius: 5px; } } @@ -1025,7 +1051,7 @@ p { margin-top: 0; } -/* Product links beside the dstack installation controls. */ +/* Product selector and its matching preview. */ .gs-deploy { display: grid; grid-template-columns: 1fr 2fr; /* selector 1/3, detail 2/3 */ @@ -1057,12 +1083,14 @@ p { position: relative; display: block; width: 100%; + border: 0; border-radius: 8px; /* same radius as the tab chips */ background: var(--cs-panel); /* each row paints its own ground (the rail is transparent) */ color: var(--cs-text); font: inherit; line-height: 1.15; /* pinned (not inherited) so the rail renders identically on the docs site */ text-align: left; + cursor: pointer; } /* Adjacent rows inside a group square off their shared edge, so the group's ground reads as one @@ -1082,6 +1110,10 @@ p { background: var(--cs-bg); box-shadow: inset 0 0 0 0.5px var(--cs-text); } +.gs-rail button.gs-opt:focus-visible { + outline: 2px solid var(--cs-text); + outline-offset: -3px; +} /* Row names mirror the tab typography: 16px / 700. */ .gs-opt__name { @@ -1187,6 +1219,13 @@ p { .gs-detail { min-width: 0; } +.gs-detail .gs-tabs { + flex-shrink: 0; + overflow-x: auto; +} +.gs-detail .gs-tab { + white-space: nowrap; +} /* The detail box — shared shell for all three tabs: a bordered container with a fixed height, holding a tab strip, content, and a footer CTA bar. The height is tuned a touch taller than the rail, giving the content room to breathe and matching the tabbed code cards higher up the page; all three tabs reuse this shell, so they stay equal height. */ @@ -1266,6 +1305,25 @@ p { min-width: 0; /* allow the note to shrink/wrap so it shares the row with the CTA on narrow screens */ margin-inline-end: auto; /* note left, button(s) right — the footer itself is flex-end */ } +.gs-foot__actions { + display: flex; + flex: 0 0 auto; + gap: 14px; +} + +@media (max-width: 640px) { + .gs-detail .gs-boxfoot { + flex-wrap: wrap; + } + .gs-detail .gs-foot__note { + flex-basis: 100%; + } + .gs-foot__actions { + flex-wrap: wrap; + gap: 10px; + max-width: 100%; + } +} /* The footer CTA(s) keep their width (never shrink/wrap mid-word); the note takes the rest. */ .gs-boxfoot > [class*='awsui_button'] { @@ -1310,11 +1368,11 @@ p { } /* Open-source: read-only code fills the body. */ -.gs-codebody { +.gs-box--install .gs-skybody { flex: 1 1 auto; min-height: 0; padding: 10px 12px; - overflow: hidden; + overflow: auto; } /* dstack Sky: two panes (GPU marketplace | bring your own clouds). Source order is header→list, @@ -1497,8 +1555,6 @@ p { /* Separate the second pane (its header follows the first list). */ .gs-sky .gs-col + .gs-skyhalf { margin-top: 12px; } .gs-caps { grid-template-columns: 1fr; } - /* Read-only install code is wider than the phone; let it scroll instead of clipping. */ - .gs-codebody { overflow-x: auto; } /* Keep footer notes and buttons on one row. */ .gs-boxfoot { align-items: center; gap: 10px; } .gs-boxfoot [class*='awsui_button'] { padding-inline: 14px !important; } From 85f0992dc3189801fbd76a882acceed08789402d Mon Sep 17 00:00:00 2001 From: Andrey Cheptsov Date: Thu, 24 Sep 2026 16:35:48 +0200 Subject: [PATCH 2/2] [Website] Use dotted outer architecture border --- website/src/styles.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/src/styles.css b/website/src/styles.css index d04158944..282b26eb3 100644 --- a/website/src/styles.css +++ b/website/src/styles.css @@ -640,7 +640,7 @@ p { display: flex; flex-direction: column; overflow: hidden; - border: 0.5px solid currentColor; + border: 0.5px dotted currentColor; border-radius: 12px; background: var(--cs-panel); }