From 7c6f383bdf46d362503ecf82961ba1594b530ad8 Mon Sep 17 00:00:00 2001 From: Andrey Cheptsov Date: Wed, 24 Jun 2026 21:59:37 +0200 Subject: [PATCH 1/8] Refresh landing nav, CTAs, and AI-native cards Navigation: - Remove Benchmarks (landing + docs) and Discord (docs) menu items - Add a "Products" hover menu (desktop) and Products section (mobile) - Make GitHub the primary button, dstack Sky a normal button (landing + docs) Hero / CTAs: - Hero: "Install open-source" + primary "Try dstack Sky" - Get started: "dstack" block with primary "Install open-source" + "Quickstart" - FAQ: add primary "Talk to us" before Discord - "Access marketplace GPUs" CTA renamed to "Try dstack Sky" - Drop the Backends / Kubernetes / SSH fleets buttons AI-native orchestration cards: - Reuse the landing's dotted outline (shared DashedBorder), thin stroke - Add a category label with a gradient dot, tighter title, muted copy Cleanup: - Remove dead Cloudscape ButtonDropdown / cs-get-started CSS - Ignore website/.vite Co-Authored-By: Claude Opus 4.8 (1M context) --- mkdocs.yml | 2 - mkdocs/assets/stylesheets/cloudscape-docs.css | 152 +++--------------- mkdocs/overrides/header-2.html | 39 +---- website/.gitignore | 1 + website/src/cloudscape-overrides.css | 106 +----------- website/src/components/SiteNavigation.tsx | 146 ++++++++++------- website/src/pages/Home/ExploreSection.tsx | 19 +-- website/src/pages/Home/FaqSection.tsx | 12 +- website/src/pages/Home/GetStartedSection.tsx | 1 + website/src/pages/Home/HomePage.tsx | 13 +- website/src/styles.css | 149 +++++++++++++---- 11 files changed, 258 insertions(+), 382 deletions(-) diff --git a/mkdocs.yml b/mkdocs.yml index 2fc74935a..62d7feabf 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -397,10 +397,8 @@ nav: - llms-full.txt: https://dstack.ai/llms-full.txt - skill.md: https://dstack.ai/skill.md - Case studies: blog/case-studies.md - - Benchmarks: blog/benchmarks.md - Blog: - blog/index.md - - Discord: https://discord.gg/u8SmfwPpMd" target="_blank # - Changelog: https://github.com/dstackai/dstack/releases" target="_blank # - GitHub: https://github.com/dstackai/dstack" target="_blank # - Sign in: https://sky.dstack.ai" target="_blank diff --git a/mkdocs/assets/stylesheets/cloudscape-docs.css b/mkdocs/assets/stylesheets/cloudscape-docs.css index cabeb93b9..b9c7bec2e 100644 --- a/mkdocs/assets/stylesheets/cloudscape-docs.css +++ b/mkdocs/assets/stylesheets/cloudscape-docs.css @@ -204,15 +204,15 @@ white-space: nowrap; } -/* GitHub → outlined "GitHub" with a trailing external-link glyph (no star count). */ -[data-md-color-primary=white] .md-header__buttons .md-button--primary.github, -[data-md-color-primary=white] .md-header__buttons .md-button--primary.github:hover { - background: transparent; - color: var(--cs-text); - border: 1px solid var(--cs-border); +/* GitHub → filled (primary) "GitHub" with a trailing external-link glyph (no star count). */ +[data-md-color-primary=white] .md-header__buttons .md-button--primary.github { + background: var(--cs-text); + color: var(--cs-bg); + border: 1px solid var(--cs-text); + font-weight: 500 !important; /* filled primary uses 500; the outlined Try-Sky button stays 700 */ } -/* Gap between GitHub and Get started → 20px to match /old (was 5px). */ +/* Gap between GitHub and dstack Sky → 20px to match /old (was 5px). */ .md-header__buttons .md-button.github { margin-right: 20px; } @@ -235,138 +235,36 @@ mask: var(--cs-ext-icon) center / contain no-repeat; } -/* Get started → /old's split button (dark primary main + caret) with a dropdown menu. */ -.md-header__buttons .cs-get-started { - position: relative; - display: inline-flex; - align-items: stretch; - margin-right: 5px; -} - -[data-md-color-primary=white] .md-header__buttons .cs-get-started .md-button--primary, -[data-md-color-primary=white] .md-header__buttons .cs-get-started .md-button--primary:hover { - background: var(--cs-text); - color: var(--cs-bg); - border: 1px solid var(--cs-text); - margin: 0; - /* Primary (filled) button uses 500 weight per design — the GitHub outline button stays 700. */ - font-weight: 500 !important; +/* Try dstack Sky → outlined (normal) pill with a trailing external-link glyph, sitting after + the now-filled GitHub button (the two swapped emphasis vs the old GitHub + Get started). */ +[data-md-color-primary=white] .md-header__buttons .md-button--primary.cs-try-sky { + background: transparent; + color: var(--cs-text); + border: 1px solid var(--cs-border); } - -/* Hover states (Cloudscape): the outlined GitHub button fills subtly (NOT black — landing.css - forces `background:black !important` on primary hover, so these need !important too); the dark - Get-started button lightens to the button-hover token. */ -[data-md-color-primary=white] .md-header__buttons .md-button--primary.github:hover { +[data-md-color-primary=white] .md-header__buttons .md-button--primary.cs-try-sky:hover { background: var(--cs-hover) !important; border-color: var(--cs-border) !important; color: var(--cs-text) !important; } -/* Hovering EITHER segment lights up BOTH (the whole pill reads as one button that opens the menu). */ -[data-md-color-primary=white] .md-header__buttons .cs-get-started:hover .md-button--primary { - background: var(--cs-btn-hover) !important; - border-color: var(--cs-btn-hover) !important; - color: var(--cs-bg) !important; -} - -.md-header__buttons .cs-get-started__main { - border-radius: 12px 0 0 12px !important; - border-right: 0 !important; - cursor: pointer; - /* No separator now, so pull the caret in close to the label (was 18px + the toggle's 6px). */ - padding-right: 6px !important; -} - -/* No segment separator — the caret reads as part of the same button. */ -.md-header__buttons .cs-get-started__toggle { - border-radius: 0 12px 12px 0 !important; - padding: 0 6px !important; - border-left: 0 !important; - cursor: pointer; -} - -.md-header__buttons .cs-get-started__toggle svg { - width: 18px; - height: 18px; - fill: currentColor; -} - -/* Matches the new landing / old Get-started popup (cloudscape-overrides.css): fixed 300px, flat - (no shadow), a single 0.5px cs-text border, 12px radius clipped to rounded corners. */ -.md-header__buttons .cs-get-started__menu { - position: absolute; - top: calc(100% + 6px); - right: 0; - min-width: 300px; - padding: 8px 0; - display: flex; - flex-direction: column; - background: var(--cs-bg); - border: 0.5px solid var(--cs-text); - border-radius: 12px; - box-shadow: none; - overflow: hidden; - z-index: 1000; - /* Match the landing dropdown's subpixel smoothing (Material defaults to antialiased = thinner). */ - -webkit-font-smoothing: auto; - -moz-osx-font-smoothing: auto; -} - -.md-header__buttons .cs-get-started__menu[hidden] { - display: none; -} - -/* Group headers ("Products" / "Login") → 15px / 300, ~5.5px vertical padding, like the landing. */ -.md-header__buttons .cs-get-started__group { - padding: 5.5px 16px; - font-size: 15px; - font-weight: 300; - color: var(--cs-text); -} - -/* Items → 4px/16px padding; a 15/600 heading-color title with an optional description below. */ -.md-header__buttons .cs-get-started__menu a { - display: flex; - flex-direction: column; - padding: 4px 16px; - font-size: 15px; - font-weight: 600; - color: var(--cs-text); - text-decoration: none; -} -.md-header__buttons .cs-get-started__item-title { - color: var(--cs-nav-heading); -} -/* The generic external-link "↗" is an a::after rendered as a block, so the column flex dropped it - onto its own line below the description. Suppress it and put the icon inline after the TITLE - text instead (external items only), like the landing. */ -.md-header__buttons .cs-get-started__menu a::after { - content: none !important; - display: none !important; -} -.md-header__buttons .cs-get-started__menu a[target="_blank"] .cs-get-started__item-title::after { +.md-header__buttons .md-button.cs-try-sky::after { content: ""; display: inline-block; - width: 13px; - height: 13px; - margin-left: 5px; - vertical-align: middle; + width: 16px; + height: 16px; + margin-left: 6px; + flex: 0 0 auto; background-color: currentColor; -webkit-mask: var(--cs-ext-icon) center / contain no-repeat; mask: var(--cs-ext-icon) center / contain no-repeat; } -/* Description: 13px / 300 / full text color (light weight reads muted) / 16px line-height, like - the landing's secondary text. */ -.md-header__buttons .cs-get-started__item-desc { - margin-top: 1.5px; - font-size: 13px; - font-weight: 300; - line-height: 16px; - color: var(--cs-text); - white-space: normal; -} -.md-header__buttons .cs-get-started__menu a:hover { - background: var(--cs-hover); +/* GitHub hover: the filled GitHub button lightens to the button-hover token. landing.css forces + `background:black !important` on primary hover, so these need !important to win. */ +[data-md-color-primary=white] .md-header__buttons .md-button--primary.github:hover { + background: var(--cs-btn-hover) !important; + border-color: var(--cs-btn-hover) !important; + color: var(--cs-bg) !important; } /* Burger (desktop sidebar toggle) — far left, like /old. */ diff --git a/mkdocs/overrides/header-2.html b/mkdocs/overrides/header-2.html index f6887a7a3..a38ef5d80 100644 --- a/mkdocs/overrides/header-2.html +++ b/mkdocs/overrides/header-2.html @@ -101,31 +101,7 @@ GitHub - + dstack Sky diff --git a/website/mkdocs/docs/reference/plugins/rest/rest_plugin_openapi.json b/website/mkdocs/docs/reference/plugins/rest/rest_plugin_openapi.json new file mode 100644 index 000000000..d5955fcbe --- /dev/null +++ b/website/mkdocs/docs/reference/plugins/rest/rest_plugin_openapi.json @@ -0,0 +1 @@ +{"openapi": "3.1.0", "info": {"title": "REST Plugin OpenAPI Spec", "version": "0.0.0"}, "servers": [{"url": "http://localhost:8000", "description": "Local server"}], "paths": {"/apply_policies/on_run_apply": {"post": {"summary": "On Run Apply", "operationId": "on_run_apply_apply_policies_on_run_apply_post", "requestBody": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/SpecApplyRequest"}}}, "required": true}, "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/SpecApplyResponse"}}}}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}}, "/apply_policies/on_fleet_apply": {"post": {"summary": "On Fleet Apply", "operationId": "on_fleet_apply_apply_policies_on_fleet_apply_post", "requestBody": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/SpecApplyRequest"}}}, "required": true}, "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/SpecApplyResponse"}}}}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}}, "/apply_policies/on_volume_apply": {"post": {"summary": "On Volume Apply", "operationId": "on_volume_apply_apply_policies_on_volume_apply_post", "requestBody": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/SpecApplyRequest"}}}, "required": true}, "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/SpecApplyResponse"}}}}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}}, "/apply_policies/on_gateway_apply": {"post": {"summary": "On Gateway Apply", "operationId": "on_gateway_apply_apply_policies_on_gateway_apply_post", "requestBody": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/SpecApplyRequest"}}}, "required": true}, "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/SpecApplyResponse"}}}}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}}}, "components": {"schemas": {"ACMGatewayCertificateRequest": {"properties": {"type": {"type": "string", "enum": ["acm"], "title": "Type", "description": "Certificates by AWS Certificate Manager (ACM)", "default": "acm"}, "arn": {"type": "string", "title": "Arn", "description": "The ARN of the wildcard ACM certificate for the domain"}}, "additionalProperties": false, "type": "object", "required": ["arn"], "title": "ACMGatewayCertificateRequest"}, "AWSVolumeConfigurationRequest": {"properties": {"type": {"type": "string", "enum": ["volume"], "title": "Type", "default": "volume"}, "backend": {"type": "string", "enum": ["aws"], "title": "Backend", "description": "The volume backend", "default": "aws"}, "name": {"type": "string", "title": "Name", "description": "The volume name"}, "size": {"type": "number", "title": "Size", "description": "The volume size. Must be specified when creating new volumes"}, "auto_cleanup_duration": {"anyOf": [{"type": "integer"}, {"type": "string"}], "title": "Auto Cleanup Duration", "description": "Time to wait after volume is no longer used by any job before deleting it. Defaults to keep the volume indefinitely. Use the value `off` or `-1` to disable auto-cleanup"}, "tags": {"additionalProperties": {"type": "string"}, "type": "object", "title": "Tags", "description": "The custom tags to associate with the volume. The tags are also propagated to the underlying backend resources. If there is a conflict with backend-level tags, does not override them"}, "volume_id": {"type": "string", "title": "Volume Id", "description": "The volume ID. Must be specified when registering external volumes"}, "region": {"type": "string", "title": "Region", "description": "The volume region"}, "availability_zone": {"type": "string", "title": "Availability Zone", "description": "The volume availability zone"}}, "additionalProperties": false, "type": "object", "required": ["region"], "title": "AWSVolumeConfigurationRequest"}, "AcceleratorVendor": {"type": "string", "enum": ["nvidia", "amd", "google", "intel", "tenstorrent"], "title": "AcceleratorVendor", "description": "An enumeration."}, "BackendType": {"type": "string", "enum": ["amddevcloud", "aws", "azure", "cloudrift", "crusoe", "cudo", "datacrunch", "digitalocean", "dstack", "gcp", "hotaisle", "jarvislabs", "kubernetes", "lambda", "remote", "nebius", "oci", "runpod", "tensordock", "vastai", "verda", "vultr"], "title": "BackendType", "description": "Attributes:\n AMDDEVCLOUD (BackendType): AMD Developer Cloud\n AWS (BackendType): Amazon Web Services\n AZURE (BackendType): Microsoft Azure\n CLOUDRIFT (BackendType): CloudRift\n CRUSOE (BackendType): Crusoe\n CUDO (BackendType): Cudo\n DATACRUNCH (BackendType): DataCrunch (for backward compatibility)\n DIGITALOCEAN (BackendType): DigitalOcean\n DSTACK (BackendType): dstack Sky\n GCP (BackendType): Google Cloud Platform\n HOTAISLE (BackendType): Hot Aisle\n JARVISLABS (BackendType): JarvisLabs\n KUBERNETES (BackendType): Kubernetes\n LAMBDA (BackendType): Lambda Cloud\n NEBIUS (BackendType): Nebius AI Cloud\n OCI (BackendType): Oracle Cloud Infrastructure\n RUNPOD (BackendType): Runpod Cloud\n TENSORDOCK (BackendType): TensorDock Marketplace\n VASTAI (BackendType): Vast.ai Marketplace\n VERDA (BackendType): Verda Cloud\n VULTR (BackendType): Vultr"}, "CPUArchitecture": {"type": "string", "enum": ["x86", "arm"], "title": "CPUArchitecture", "description": "An enumeration."}, "CPUSpecRequest": {"properties": {"arch": {"allOf": [{"$ref": "#/components/schemas/CPUArchitecture"}], "description": "The CPU architecture, one of: `x86`, `arm`"}, "count": {"anyOf": [{"$ref": "#/components/schemas/Range_int_"}, {"type": "integer"}, {"type": "string"}], "title": "Count", "description": "The number of CPU cores", "default": {"min": 2}}}, "additionalProperties": false, "type": "object", "title": "CPUSpecRequest"}, "CreationPolicy": {"type": "string", "enum": ["reuse", "reuse-or-create"], "title": "CreationPolicy", "description": "An enumeration."}, "DevEnvironmentConfigurationRequest": {"properties": {"ide": {"anyOf": [{"type": "string", "enum": ["vscode"]}, {"type": "string", "enum": ["cursor"]}, {"type": "string", "enum": ["windsurf"]}, {"type": "string", "enum": ["zed"]}], "title": "Ide", "description": "The IDE to pre-install. Supported values include `vscode`, `cursor`, `windsurf`, and `zed`. Defaults to no IDE (SSH only)"}, "version": {"type": "string", "title": "Version", "description": "The version of the IDE. For `windsurf`, the version is in the format `version@commit`"}, "init": {"items": {"type": "string"}, "type": "array", "title": "Init", "description": "The shell commands to run on startup", "default": []}, "inactivity_duration": {"anyOf": [{"type": "string", "enum": ["off"]}, {"type": "integer"}, {"type": "boolean"}, {"type": "string"}], "title": "Inactivity Duration", "description": "The maximum amount of time the dev environment can be inactive (e.g., `2h`, `1d`, etc). After it elapses, the dev environment is automatically stopped. Inactivity is defined as the absence of SSH connections to the dev environment, including VS Code connections, `ssh ` shells, and attached `dstack apply` or `dstack attach` commands. Use `off` for unlimited duration. Can be updated in-place. Defaults to `off`"}, "ports": {"items": {"anyOf": [{"type": "integer", "maximum": 65536.0, "exclusiveMinimum": 0.0}, {"type": "string", "pattern": "^(?:[0-9]+|\\*):[0-9]+$"}, {"$ref": "#/components/schemas/PortMappingRequest"}]}, "type": "array", "title": "Ports", "description": "Port numbers/mapping to expose", "default": []}, "type": {"type": "string", "enum": ["dev-environment"], "title": "Type", "default": "dev-environment"}, "name": {"type": "string", "title": "Name", "description": "The run name. If not specified, a random name is generated"}, "image": {"type": "string", "title": "Image", "description": "The name of the Docker image to run"}, "user": {"type": "string", "title": "User", "description": "The user inside the container, `user_name_or_id[:group_name_or_id]` (e.g., `ubuntu`, `1000:1000`). Defaults to the default user from the `image`"}, "privileged": {"type": "boolean", "title": "Privileged", "description": "Run the container in privileged mode", "default": false}, "entrypoint": {"type": "string", "title": "Entrypoint", "description": "The Docker entrypoint"}, "working_dir": {"type": "string", "title": "Working Dir", "description": "The absolute path to the working directory inside the container. Defaults to the `image`'s default working directory"}, "home_dir": {"type": "string", "title": "Home Dir", "default": "/root"}, "registry_auth": {"allOf": [{"$ref": "#/components/schemas/RegistryAuthRequest"}], "title": "Registry Auth", "description": "Credentials for pulling a private Docker image"}, "python": {"allOf": [{"$ref": "#/components/schemas/PythonVersion"}], "description": "The major version of Python. Mutually exclusive with `image` and `docker`"}, "nvcc": {"type": "boolean", "title": "Nvcc", "description": "Use image with NVIDIA CUDA Compiler (NVCC) included. Mutually exclusive with `image` and `docker`"}, "single_branch": {"type": "boolean", "title": "Single Branch", "description": "Whether to clone and track only the current branch or all remote branches. Relevant only when using remote Git repos. Defaults to `false` for dev environments and to `true` for tasks and services"}, "env": {"allOf": [{"$ref": "#/components/schemas/Env"}], "title": "Env", "description": "The mapping or the list of environment variables", "default": {"__root__": {}}}, "shell": {"type": "string", "title": "Shell", "description": "The shell used to run commands. Allowed values are `sh`, `bash`, or an absolute path, e.g., `/usr/bin/zsh`. Defaults to `/bin/sh` if the `image` is specified, `/bin/bash` otherwise"}, "resources": {"allOf": [{"$ref": "#/components/schemas/ResourcesSpecRequest"}], "title": "Resources", "description": "The resources requirements to run the configuration", "default": {"cpu": {"min": 2}, "memory": {"min": 8.0}, "gpu": {"count": {"min": 0}}, "disk": {"size": {"min": 100.0}}}}, "priority": {"type": "integer", "maximum": 100.0, "minimum": 0.0, "title": "Priority", "description": "The priority of the run, an integer between `0` and `100`. `dstack` tries to provision runs with higher priority first. Defaults to `0`"}, "volumes": {"items": {"anyOf": [{"$ref": "#/components/schemas/VolumeMountPointRequest"}, {"$ref": "#/components/schemas/InstanceMountPointRequest"}, {"type": "string"}]}, "type": "array", "title": "Volumes", "description": "The volumes mount points", "default": []}, "docker": {"type": "boolean", "title": "Docker", "description": "Use Docker inside the container. Mutually exclusive with `image`, `python`, and `nvcc`. Overrides `privileged`"}, "repos": {"items": {"$ref": "#/components/schemas/RepoSpecRequest"}, "type": "array", "title": "Repos", "description": "The list of Git repos", "default": []}, "files": {"items": {"anyOf": [{"$ref": "#/components/schemas/FilePathMappingRequest"}, {"type": "string"}]}, "type": "array", "title": "Files", "description": "The local to container file path mappings", "default": []}, "setup": {"items": {"type": "string"}, "type": "array", "title": "Setup", "default": []}, "backends": {"items": {"$ref": "#/components/schemas/BackendType"}, "type": "array", "description": "The backends to consider for provisioning (e.g., `[aws, gcp]`)"}, "regions": {"items": {"type": "string"}, "type": "array", "title": "Regions", "description": "The regions to consider for provisioning (e.g., `[eu-west-1, us-west4, westeurope]`)"}, "availability_zones": {"items": {"type": "string"}, "type": "array", "title": "Availability Zones", "description": "The availability zones to consider for provisioning (e.g., `[eu-west-1a, us-west4-a]`)"}, "instance_types": {"items": {"type": "string"}, "type": "array", "title": "Instance Types", "description": "The cloud-specific instance types to consider for provisioning (e.g., `[g6e.24xlarge, n1-standard-4]`)"}, "reservation": {"type": "string", "title": "Reservation", "description": "The existing reservation to use for instance provisioning. Supports AWS Capacity Reservations, AWS Capacity Blocks, and GCP reservations"}, "spot_policy": {"allOf": [{"$ref": "#/components/schemas/SpotPolicy"}], "description": "The policy for provisioning spot or on-demand instances: `spot`, `on-demand`, `auto`. Defaults to `on-demand`"}, "retry": {"anyOf": [{"$ref": "#/components/schemas/ProfileRetryRequest"}, {"type": "boolean"}], "title": "Retry", "description": "The policy for resubmitting the run. Defaults to `false`"}, "max_duration": {"anyOf": [{"type": "string", "enum": ["off"]}, {"type": "integer"}, {"type": "boolean"}, {"type": "string"}], "title": "Max Duration", "description": "The maximum duration of a run (e.g., `2h`, `1d`, etc) in a running state, excluding provisioning and pulling. After it elapses, the run is automatically stopped. Use `off` for unlimited duration. Defaults to `off`"}, "stop_duration": {"anyOf": [{"type": "string", "enum": ["off"]}, {"type": "integer"}, {"type": "boolean"}, {"type": "string"}], "title": "Stop Duration", "description": "The maximum duration of a run graceful stopping. After it elapses, the run is automatically forced stopped. This includes force detaching volumes used by the run. Use `off` for unlimited duration. Defaults to `5m`"}, "max_price": {"type": "number", "exclusiveMinimum": 0.0, "title": "Max Price", "description": "The maximum instance price per hour, in dollars"}, "creation_policy": {"allOf": [{"$ref": "#/components/schemas/CreationPolicy"}], "description": "The policy for using instances from fleets: `reuse`, `reuse-or-create`. Defaults to `reuse-or-create`"}, "idle_duration": {"anyOf": [{"type": "integer"}, {"type": "string"}], "title": "Idle Duration", "description": "Time to wait before terminating idle instances. When the run reuses an existing fleet instance, the fleet's `idle_duration` applies. When the run provisions a new instance, the shorter of the fleet's and run's values is used. Defaults to `5m` for runs and `3d` for fleets. Use `off` for unlimited duration. Only applied for VM-based backends"}, "utilization_policy": {"allOf": [{"$ref": "#/components/schemas/UtilizationPolicyRequest"}], "title": "Utilization Policy", "description": "Run termination policy based on utilization"}, "startup_order": {"allOf": [{"$ref": "#/components/schemas/StartupOrder"}], "description": "The order in which master and workers jobs are started: `any`, `master-first`, `workers-first`. Defaults to `any`"}, "stop_criteria": {"allOf": [{"$ref": "#/components/schemas/StopCriteria"}], "description": "The criteria determining when a multi-node run should be considered finished: `all-done`, `master-done`. Defaults to `all-done`"}, "schedule": {"allOf": [{"$ref": "#/components/schemas/ScheduleRequest"}], "title": "Schedule", "description": "The schedule for starting the run at specified time"}, "fleets": {"items": {"anyOf": [{"$ref": "#/components/schemas/EntityReferenceRequest"}, {"type": "string"}]}, "type": "array", "title": "Fleets", "description": "The fleets considered for reuse. For fleets owned by the current project, specify fleet names. For imported fleets, specify `/`"}, "instances": {"items": {"anyOf": [{"$ref": "#/components/schemas/InstanceNameSelectorRequest"}, {"$ref": "#/components/schemas/InstanceHostnameSelectorRequest"}, {"$ref": "#/components/schemas/FleetInstanceSelectorRequest"}, {"type": "string", "minLength": 1}]}, "type": "array", "minItems": 1, "title": "Instances", "description": "The specific fleet instances to consider for reuse. Each value can be an instance name string, or an object with `name`, `hostname`, or `fleet` and `instance`. When set, the run is only placed on matching existing instances."}, "tags": {"additionalProperties": {"type": "string"}, "type": "object", "title": "Tags", "description": "The custom tags to associate with the resource. The tags are also propagated to the underlying backend resources. If there is a conflict with backend-level tags, does not override them"}, "backend_options": {"items": {"$ref": "#/components/schemas/VastAIProfileOptions"}, "type": "array", "title": "Backend Options", "description": "Backend-specific options, applied only to offers from that backend"}}, "additionalProperties": false, "type": "object", "title": "DevEnvironmentConfigurationRequest"}, "DiskSpecRequest": {"properties": {"size": {"anyOf": [{"$ref": "#/components/schemas/Range_Memory_"}, {"type": "integer"}, {"type": "string"}], "title": "Size", "description": "Disk size"}}, "additionalProperties": false, "type": "object", "required": ["size"], "title": "DiskSpecRequest"}, "EntityReferenceRequest": {"properties": {"project": {"type": "string", "title": "Project", "description": "The project name. If unspecified, refers to the current project"}, "name": {"type": "string", "title": "Name", "description": "The entity name"}}, "additionalProperties": false, "type": "object", "required": ["name"], "title": "EntityReferenceRequest", "description": "Cross-project entity reference."}, "Env": {"anyOf": [{"items": {"type": "string"}, "type": "array"}, {"additionalProperties": {"anyOf": [{"type": "string"}, {"$ref": "#/components/schemas/EnvSentinelRequest"}]}, "type": "object"}], "title": "Env", "description": "Env represents a mapping of process environment variables, as in environ(7).\nEnvironment values may be omitted, in that case the :class:`EnvSentinel`\nobject is used as a placeholder.\n\nTo create an instance from a `dict[str, str]` or a `list[str]` use pydantic's\n:meth:`BaseModel.parse_obj(dict | list)` method.\n\nNB: this is *NOT* a CoreModel, pydantic-duality, which is used as a base\nfor the CoreModel, doesn't play well with custom root models.", "default": {}}, "EnvSentinelRequest": {"properties": {"key": {"type": "string", "title": "Key"}}, "additionalProperties": false, "type": "object", "required": ["key"], "title": "EnvSentinelRequest"}, "FileArchiveMappingRequest": {"properties": {"id": {"type": "string", "format": "uuid", "title": "Id", "description": "The File archive ID"}, "path": {"type": "string", "title": "Path", "description": "The path in the container"}}, "additionalProperties": false, "type": "object", "required": ["id", "path"], "title": "FileArchiveMappingRequest"}, "FilePathMappingRequest": {"properties": {"local_path": {"type": "string", "title": "Local Path", "description": "The path on the user's machine. Relative paths are resolved relative to the parent directory of the the configuration file"}, "path": {"type": "string", "title": "Path", "description": "The path in the container. Relative paths are resolved relative to the working directory"}}, "additionalProperties": false, "type": "object", "required": ["local_path", "path"], "title": "FilePathMappingRequest"}, "FleetConfigurationRequest": {"properties": {"type": {"type": "string", "enum": ["fleet"], "title": "Type", "default": "fleet"}, "name": {"type": "string", "title": "Name", "description": "The fleet name"}, "placement": {"allOf": [{"$ref": "#/components/schemas/InstanceGroupPlacement"}], "description": "The placement of instances: `any` or `cluster`"}, "blocks": {"anyOf": [{"type": "string", "enum": ["auto"]}, {"type": "integer", "minimum": 1.0}], "title": "Blocks", "description": "The amount of blocks to split the instance into, a number or `auto`. `auto` means as many as possible. The number of GPUs and CPUs must be divisible by the number of blocks. Defaults to `1`, i.e. do not split", "default": 1}, "nodes": {"anyOf": [{"$ref": "#/components/schemas/FleetNodesSpecRequest"}, {"type": "integer"}, {"type": "string"}], "title": "Nodes", "description": "The number of instances"}, "reservation": {"type": "string", "title": "Reservation", "description": "The existing reservation to use for instance provisioning. Supports AWS Capacity Reservations, AWS Capacity Blocks, and GCP reservations"}, "resources": {"allOf": [{"$ref": "#/components/schemas/ResourcesSpecRequest"}], "title": "Resources", "description": "The resources requirements"}, "backends": {"items": {"$ref": "#/components/schemas/BackendType"}, "type": "array", "description": "The backends to consider for provisioning (e.g., `[aws, gcp]`)"}, "regions": {"items": {"type": "string"}, "type": "array", "title": "Regions", "description": "The regions to consider for provisioning (e.g., `[eu-west-1, us-west4, westeurope]`)"}, "availability_zones": {"items": {"type": "string"}, "type": "array", "title": "Availability Zones", "description": "The availability zones to consider for provisioning (e.g., `[eu-west-1a, us-west4-a]`)"}, "instance_types": {"items": {"type": "string"}, "type": "array", "title": "Instance Types", "description": "The cloud-specific instance types to consider for provisioning (e.g., `[g6e.24xlarge, n1-standard-4]`)"}, "spot_policy": {"allOf": [{"$ref": "#/components/schemas/SpotPolicy"}], "description": "The policy for provisioning spot or on-demand instances: `spot`, `on-demand`, `auto`. Defaults to `on-demand`"}, "retry": {"anyOf": [{"$ref": "#/components/schemas/ProfileRetryRequest"}, {"type": "boolean"}], "title": "Retry", "description": "The policy for provisioning retry. Defaults to `false`"}, "max_price": {"type": "number", "exclusiveMinimum": 0.0, "title": "Max Price", "description": "The maximum instance price per hour, in dollars"}, "idle_duration": {"anyOf": [{"type": "integer"}, {"type": "string"}], "title": "Idle Duration", "description": "Time to wait before terminating idle instances. Instances are not terminated if the fleet is already at `nodes.min`. Defaults to `5m` for runs and `3d` for fleets. Use `off` for unlimited duration"}, "tags": {"additionalProperties": {"type": "string"}, "type": "object", "title": "Tags", "description": "The custom tags to associate with the resource. The tags are also propagated to the underlying backend resources. If there is a conflict with backend-level tags, does not override them"}, "backend_options": {"items": {"$ref": "#/components/schemas/VastAIProfileOptions"}, "type": "array", "title": "Backend Options", "description": "Backend-specific options, applied only to offers from that backend"}, "ssh_config": {"allOf": [{"$ref": "#/components/schemas/SSHParamsRequest"}], "title": "Ssh Config", "description": "The parameters for adding instances via SSH"}, "env": {"allOf": [{"$ref": "#/components/schemas/Env"}], "title": "Env", "description": "The mapping or the list of environment variables", "default": {"__root__": {}}}}, "additionalProperties": false, "type": "object", "title": "FleetConfigurationRequest"}, "FleetInstanceSelectorRequest": {"properties": {"fleet": {"anyOf": [{"$ref": "#/components/schemas/EntityReferenceRequest"}, {"type": "string", "minLength": 1}], "title": "Fleet", "description": "The fleet reference. For fleets owned by the current project, specify the fleet name. For a fleet from another project, specify `/` or an object with `project` and `name`."}, "instance": {"type": "integer", "minimum": 0.0, "title": "Instance", "description": "The fleet instance number"}}, "additionalProperties": false, "type": "object", "required": ["fleet", "instance"], "title": "FleetInstanceSelectorRequest"}, "FleetNodesSpecRequest": {"properties": {"min": {"type": "integer", "title": "Min", "description": "The minimum number of instances to maintain in the fleet"}, "target": {"type": "integer", "title": "Target", "description": "The number of instances to provision on fleet apply. `min` <= `target` <= `max` Defaults to `min`"}, "max": {"type": "integer", "title": "Max", "description": "The maximum number of instances allowed in the fleet. Unlimited if not specified"}}, "additionalProperties": false, "type": "object", "required": ["min", "target"], "title": "FleetNodesSpecRequest"}, "FleetSpecRequest": {"properties": {"configuration": {"$ref": "#/components/schemas/FleetConfigurationRequest"}, "configuration_path": {"type": "string", "title": "Configuration Path"}, "profile": {"$ref": "#/components/schemas/ProfileRequest"}, "autocreated": {"type": "boolean", "title": "Autocreated", "default": false}}, "additionalProperties": false, "type": "object", "required": ["configuration", "profile"], "title": "FleetSpecRequest"}, "GCPVolumeConfigurationRequest": {"properties": {"type": {"type": "string", "enum": ["volume"], "title": "Type", "default": "volume"}, "backend": {"type": "string", "enum": ["gcp"], "title": "Backend", "description": "The volume backend", "default": "gcp"}, "name": {"type": "string", "title": "Name", "description": "The volume name"}, "size": {"type": "number", "title": "Size", "description": "The volume size. Must be specified when creating new volumes"}, "auto_cleanup_duration": {"anyOf": [{"type": "integer"}, {"type": "string"}], "title": "Auto Cleanup Duration", "description": "Time to wait after volume is no longer used by any job before deleting it. Defaults to keep the volume indefinitely. Use the value `off` or `-1` to disable auto-cleanup"}, "tags": {"additionalProperties": {"type": "string"}, "type": "object", "title": "Tags", "description": "The custom tags to associate with the volume. The tags are also propagated to the underlying backend resources. If there is a conflict with backend-level tags, does not override them"}, "volume_id": {"type": "string", "title": "Volume Id", "description": "The volume ID. Must be specified when registering external volumes"}, "region": {"type": "string", "title": "Region", "description": "The volume region"}, "availability_zone": {"type": "string", "title": "Availability Zone", "description": "The volume availability zone"}}, "additionalProperties": false, "type": "object", "required": ["region"], "title": "GCPVolumeConfigurationRequest"}, "GPUSpecRequest": {"properties": {"vendor": {"allOf": [{"$ref": "#/components/schemas/AcceleratorVendor"}], "description": "The vendor of the GPU/accelerator, one of: `nvidia`, `amd`, `google` (alias: `tpu`), `intel`"}, "name": {"anyOf": [{"type": "array"}, {"type": "string"}], "items": {"type": "string"}, "title": "Name", "description": "The name of the GPU (e.g., `A100` or `H100`)"}, "count": {"anyOf": [{"$ref": "#/components/schemas/Range_int_"}, {"type": "integer"}, {"type": "string"}], "title": "Count", "description": "The number of GPUs", "default": {"min": 1}}, "memory": {"anyOf": [{"$ref": "#/components/schemas/Range_Memory_"}, {"type": "integer"}, {"type": "string"}], "title": "Memory", "description": "The RAM size (e.g., `16GB`). Can be set to a range (e.g. `16GB..`, or `16GB..80GB`)"}, "total_memory": {"anyOf": [{"$ref": "#/components/schemas/Range_Memory_"}, {"type": "integer"}, {"type": "string"}], "title": "Total Memory", "description": "The total RAM size (e.g., `32GB`). Can be set to a range (e.g. `16GB..`, or `16GB..80GB`)"}, "compute_capability": {"items": {}, "type": "array", "title": "Compute Capability", "description": "The minimum compute capability of the GPU (e.g., `7.5`)"}}, "additionalProperties": false, "type": "object", "title": "GPUSpecRequest"}, "GatewayConfigurationRequest": {"properties": {"type": {"type": "string", "enum": ["gateway"], "title": "Type", "default": "gateway"}, "name": {"type": "string", "title": "Name", "description": "The gateway name"}, "default": {"type": "boolean", "title": "Default", "description": "Make the gateway default", "default": false}, "backend": {"allOf": [{"$ref": "#/components/schemas/BackendType"}], "description": "The gateway backend"}, "region": {"type": "string", "title": "Region", "description": "The gateway region"}, "instance_type": {"type": "string", "minLength": 1, "title": "Instance Type", "description": "Backend-specific instance type to use for the gateway instance. Omit to use the backend's default, which is typically a small non-GPU instance"}, "router": {"allOf": [{"$ref": "#/components/schemas/SGLangGatewayRouterConfigRequest"}], "title": "Router", "description": "The router configuration for this gateway. E.g. `{ type: sglang, policy: round_robin }`."}, "domain": {"type": "string", "title": "Domain", "description": "The gateway wildcard domain name, e.g. `example.com`. Service domain names are constructed as `./`"}, "instances": {"items": {"anyOf": [{"$ref": "#/components/schemas/InstanceNameSelectorRequest"}, {"$ref": "#/components/schemas/InstanceHostnameSelectorRequest"}, {"$ref": "#/components/schemas/FleetInstanceSelectorRequest"}, {"type": "string", "minLength": 1}]}, "type": "array", "minItems": 1, "title": "Instances", "description": "The specific fleet instances to consider for reuse. Each value can be an instance name string, or an object with `name`, `hostname`, or `fleet` and `instance`. When set, the run is only placed on matching existing instances."}, "tags": {"additionalProperties": {"type": "string"}, "type": "object", "title": "Tags", "description": "The custom tags to associate with the resource. The tags are also propagated to the underlying backend resources. If there is a conflict with backend-level tags, does not override them"}, "backend_options": {"items": {"$ref": "#/components/schemas/VastAIProfileOptions"}, "type": "array", "title": "Backend Options", "description": "Backend-specific options, applied only to offers from that backend"}, "name": {"type": "string", "title": "Name", "description": "The name of the profile that can be passed as `--profile` to `dstack apply`", "default": ""}, "default": {"type": "boolean", "title": "Default", "description": "If set to true, `dstack apply` will use this profile by default.", "default": false}}, "additionalProperties": false, "type": "object", "title": "ProfileRequest"}, "ProfileRetryRequest": {"properties": {"on_events": {"items": {"$ref": "#/components/schemas/RetryEvent"}, "type": "array", "description": "The list of events that should be handled with retry. Supported events are `no-capacity`, `interruption`, `error`. Omit to retry on all events"}, "duration": {"anyOf": [{"type": "integer"}, {"type": "string"}], "title": "Duration", "description": "The maximum period of retrying the run, e.g., `4h` or `1d`. The period is calculated as a run age for `no-capacity` event and as a time passed since the last `interruption` and `error` for `interruption` and `error` events."}}, "additionalProperties": false, "type": "object", "title": "ProfileRetryRequest"}, "PythonVersion": {"type": "string", "enum": ["3.9", "3.10", "3.11", "3.12", "3.13"], "title": "PythonVersion", "description": "An enumeration."}, "Range_Memory_": {"properties": {"min": {"type": "number", "title": "Min"}, "max": {"type": "number", "title": "Max"}}, "additionalProperties": false, "type": "object", "title": "Range[Memory]"}, "Range_int_": {"properties": {"min": {"type": "integer", "title": "Min"}, "max": {"type": "integer", "title": "Max"}}, "additionalProperties": false, "type": "object", "title": "Range[int]"}, "RateLimitRequest": {"properties": {"prefix": {"type": "string", "maxLength": 4094, "pattern": "^/[^\\s\\\\{}]*$", "title": "Prefix", "description": "URL path prefix to which this limit is applied. If an incoming request matches several prefixes, the longest prefix is applied", "default": "/"}, "key": {"oneOf": [{"$ref": "#/components/schemas/IPAddressPartitioningKeyRequest"}, {"$ref": "#/components/schemas/HeaderPartitioningKeyRequest"}], "title": "Key", "description": "The partitioning key. Each incoming request belongs to a partition and rate limits are applied per partition. Defaults to partitioning by client IP address", "default": {"type": "ip_address"}, "discriminator": {"propertyName": "type", "mapping": {"ip_address": "#/components/schemas/IPAddressPartitioningKeyRequest", "header": "#/components/schemas/HeaderPartitioningKeyRequest"}}}, "rps": {"type": "number", "maximum": 1.5372286728091293e+17, "minimum": 0.016666666666666666, "title": "Rps", "description": "Max allowed number of requests per second. Requests are tracked at millisecond granularity. For example, `rps: 10` means at most 1 request per 100ms"}, "burst": {"type": "integer", "maximum": 9.223372036854776e+18, "minimum": 0.0, "title": "Burst", "description": "Max number of requests that can be passed to the service ahead of the rate limit", "default": 0}}, "additionalProperties": false, "type": "object", "required": ["rps"], "title": "RateLimitRequest"}, "RegistryAuthRequest": {"properties": {"username": {"type": "string", "title": "Username", "description": "The username"}, "password": {"type": "string", "title": "Password", "description": "The password or access token"}}, "additionalProperties": false, "type": "object", "required": ["username", "password"], "title": "RegistryAuthRequest", "description": "Credentials for pulling a private Docker image.\n\nAttributes:\n username (str): The username\n password (str): The password or access token"}, "RemoteRunRepoDataRequest": {"properties": {"repo_type": {"type": "string", "enum": ["remote"], "title": "Repo Type", "default": "remote"}, "repo_name": {"type": "string", "title": "Repo Name"}, "repo_branch": {"type": "string", "title": "Repo Branch"}, "repo_hash": {"type": "string", "title": "Repo Hash"}, "repo_diff": {"type": "string", "format": "binary", "title": "Repo Diff"}, "repo_config_name": {"type": "string", "title": "Repo Config Name"}, "repo_config_email": {"type": "string", "title": "Repo Config Email"}}, "additionalProperties": false, "type": "object", "required": ["repo_name"], "title": "RemoteRunRepoDataRequest"}, "ReplicaGroupRequest": {"properties": {"name": {"type": "string", "title": "Name", "description": "The name of the replica group. If not provided, defaults to '0', '1', etc. based on position."}, "count": {"allOf": [{"$ref": "#/components/schemas/Range_int_"}], "title": "Count", "description": "The number of replicas. Can be a number (e.g. `2`) or a range (`0..4` or `1..8`). If it's a range, the `scaling` property is required"}, "scaling": {"allOf": [{"$ref": "#/components/schemas/ScalingSpecRequest"}], "title": "Scaling", "description": "The auto-scaling rules. Required if `count` is set to a range"}, "resources": {"allOf": [{"$ref": "#/components/schemas/ResourcesSpecRequest"}], "title": "Resources", "description": "The resources requirements for replicas in this group", "default": {"cpu": {"min": 2}, "memory": {"min": 8.0}, "gpu": {"count": {"min": 0}}, "disk": {"size": {"min": 100.0}}}}, "spot_policy": {"allOf": [{"$ref": "#/components/schemas/SpotPolicy"}], "description": "The policy for provisioning spot or on-demand instances for replicas in this group: `spot`, `on-demand`, `auto`"}, "reservation": {"type": "string", "title": "Reservation", "description": "The existing reservation to use for replicas in this group. Supports AWS Capacity Reservations, AWS Capacity Blocks, and GCP reservations"}, "commands": {"items": {"type": "string"}, "type": "array", "title": "Commands", "description": "The shell commands to run for replicas in this group", "default": []}, "image": {"type": "string", "title": "Image", "description": "The name of the Docker image to run for replicas in this group. Mutually exclusive with group-level `docker` and `python`."}, "python": {"allOf": [{"$ref": "#/components/schemas/PythonVersion"}], "description": "The major version of Python for replicas in this group. Mutually exclusive with group-level `image` and `docker`."}, "nvcc": {"type": "boolean", "title": "Nvcc", "description": "Use the image with NVIDIA CUDA Compiler (NVCC) included for replicas in this group. Mutually exclusive with group-level `docker`."}, "docker": {"type": "boolean", "title": "Docker", "description": "Use the docker-in-docker image for this group (injects `start-dockerd` and runs privileged). Mutually exclusive with group-level `image`, `python`, and `nvcc`."}, "privileged": {"type": "boolean", "title": "Privileged", "description": "Run replicas in this group in privileged mode."}, "router": {"allOf": [{"$ref": "#/components/schemas/ReplicaGroupRouterConfigRequest"}], "title": "Router", "description": "When set, replicas in this group run the in-service HTTP router (e.g. SGLang)."}}, "additionalProperties": false, "type": "object", "required": ["count"], "title": "ReplicaGroupRequest"}, "ReplicaGroupRouterConfigRequest": {"properties": {"type": {"type": "string", "enum": ["sglang", "dynamo"], "title": "Type", "description": "The router implementation for this replica group. `sglang` runs the SGLang router and dstack syncs worker URLs to it. `dynamo` runs the NVIDIA Dynamo frontend, which discovers workers itself via etcd/NATS.", "default": "sglang"}}, "additionalProperties": false, "type": "object", "title": "ReplicaGroupRouterConfigRequest"}, "RepoExistsAction": {"type": "string", "enum": ["error", "skip"], "title": "RepoExistsAction", "description": "An enumeration."}, "RepoSpecRequest": {"properties": {"local_path": {"type": "string", "title": "Local Path", "description": "The path to the Git repo on the user's machine. Relative paths are resolved relative to the parent directory of the the configuration file. Mutually exclusive with `url`"}, "url": {"type": "string", "title": "Url", "description": "The Git repo URL. Mutually exclusive with `local_path`"}, "branch": {"type": "string", "title": "Branch", "description": "The repo branch. Defaults to the active branch for local paths and the default branch for URLs"}, "hash": {"type": "string", "title": "Hash", "description": "The commit hash"}, "path": {"type": "string", "title": "Path", "description": "The repo path inside the run container. Relative paths are resolved relative to the working directory", "default": "."}, "if_exists": {"allOf": [{"$ref": "#/components/schemas/RepoExistsAction"}], "description": "The action to be taken if `path` exists and is not empty. One of: `error`, `skip`", "default": "error"}}, "additionalProperties": false, "type": "object", "title": "RepoSpecRequest"}, "ResourcesSpecRequest": {"properties": {"cpu": {"anyOf": [{"$ref": "#/components/schemas/CPUSpecRequest"}, {"$ref": "#/components/schemas/Range_int_"}, {"type": "integer"}, {"type": "string"}], "title": "Cpu", "description": "The CPU requirements", "default": {"count": {"min": 2}}}, "memory": {"anyOf": [{"$ref": "#/components/schemas/Range_Memory_"}, {"type": "integer"}, {"type": "string"}], "title": "Memory", "description": "The RAM size (e.g., `8GB`)", "default": {"min": 8.0}}, "shm_size": {"anyOf": [{"type": "number"}, {"type": "integer"}, {"type": "string"}], "title": "Shm Size", "description": "The size of shared memory (e.g., `8GB`). If you are using parallel communicating processes (e.g., dataloaders in PyTorch), you may need to configure this"}, "gpu": {"anyOf": [{"$ref": "#/components/schemas/GPUSpecRequest"}, {"type": "integer"}, {"type": "string"}], "title": "Gpu", "description": "The GPU requirements", "default": {"count": {"min": 0}}}, "disk": {"anyOf": [{"$ref": "#/components/schemas/DiskSpecRequest"}, {"type": "integer"}, {"type": "string"}], "title": "Disk", "description": "The disk resources", "default": {"size": {"min": 100.0}}}}, "additionalProperties": false, "type": "object", "title": "ResourcesSpecRequest"}, "RetryEvent": {"type": "string", "enum": ["no-capacity", "interruption", "error"], "title": "RetryEvent", "description": "An enumeration."}, "RunSpecRequest": {"properties": {"run_name": {"type": "string", "title": "Run Name", "description": "The run name. If not set, the run name is generated automatically."}, "repo_id": {"type": "string", "title": "Repo Id", "description": "Same `repo_id` that is specified when initializing the repo by calling the `/api/project/{project_name}/repos/init` endpoint. If not specified, a default virtual repo is used."}, "repo_data": {"oneOf": [{"$ref": "#/components/schemas/RemoteRunRepoDataRequest"}, {"$ref": "#/components/schemas/LocalRunRepoDataRequest"}, {"$ref": "#/components/schemas/VirtualRunRepoDataRequest"}], "title": "Repo Data", "description": "The repo data such as the current branch and commit.", "discriminator": {"propertyName": "repo_type", "mapping": {"remote": "#/components/schemas/RemoteRunRepoDataRequest", "local": "#/components/schemas/LocalRunRepoDataRequest", "virtual": "#/components/schemas/VirtualRunRepoDataRequest"}}}, "repo_code_hash": {"type": "string", "title": "Repo Code Hash", "description": "The hash of the repo diff. Can be omitted if there is no repo diff."}, "repo_dir": {"type": "string", "title": "Repo Dir", "description": "The repo path inside the container. Relative paths are resolved relative to the working directory."}, "file_archives": {"items": {"$ref": "#/components/schemas/FileArchiveMappingRequest"}, "type": "array", "title": "File Archives", "description": "The list of file archive ID to container path mappings.", "default": []}, "working_dir": {"type": "string", "title": "Working Dir"}, "configuration_path": {"type": "string", "title": "Configuration Path", "description": "The path to the run configuration YAML file. It can be omitted when using the programmatic API."}, "configuration": {"oneOf": [{"$ref": "#/components/schemas/DevEnvironmentConfigurationRequest"}, {"$ref": "#/components/schemas/TaskConfigurationRequest"}, {"$ref": "#/components/schemas/ServiceConfigurationRequest"}], "title": "Configuration", "discriminator": {"propertyName": "type", "mapping": {"dev-environment": "#/components/schemas/DevEnvironmentConfigurationRequest", "task": "#/components/schemas/TaskConfigurationRequest", "service": "#/components/schemas/ServiceConfigurationRequest"}}}, "profile": {"allOf": [{"$ref": "#/components/schemas/ProfileRequest"}], "title": "Profile", "description": "The profile parameters"}, "ssh_key_pub": {"type": "string", "title": "Ssh Key Pub", "description": "The contents of the SSH public key that will be used to connect to the run. Can be empty only before the run is submitted."}}, "additionalProperties": false, "type": "object", "required": ["configuration"], "title": "RunSpecRequest"}, "RunpodVolumeConfigurationRequest": {"properties": {"type": {"type": "string", "enum": ["volume"], "title": "Type", "default": "volume"}, "backend": {"type": "string", "enum": ["runpod"], "title": "Backend", "description": "The volume backend", "default": "runpod"}, "name": {"type": "string", "title": "Name", "description": "The volume name"}, "size": {"type": "number", "title": "Size", "description": "The volume size. Must be specified when creating new volumes"}, "auto_cleanup_duration": {"anyOf": [{"type": "integer"}, {"type": "string"}], "title": "Auto Cleanup Duration", "description": "Time to wait after volume is no longer used by any job before deleting it. Defaults to keep the volume indefinitely. Use the value `off` or `-1` to disable auto-cleanup"}, "tags": {"additionalProperties": {"type": "string"}, "type": "object", "title": "Tags", "description": "The custom tags to associate with the volume. The tags are also propagated to the underlying backend resources. If there is a conflict with backend-level tags, does not override them"}, "volume_id": {"type": "string", "title": "Volume Id", "description": "The volume ID. Must be specified when registering external volumes"}, "region": {"type": "string", "title": "Region", "description": "The volume region"}, "availability_zone": {"type": "string", "title": "Availability Zone"}}, "additionalProperties": false, "type": "object", "required": ["region"], "title": "RunpodVolumeConfigurationRequest"}, "SGLangGatewayRouterConfigRequest": {"properties": {"type": {"type": "string", "enum": ["sglang"], "title": "Type", "description": "The router type enabled on this gateway.", "default": "sglang"}, "policy": {"type": "string", "enum": ["random", "round_robin", "cache_aware", "power_of_two"], "title": "Policy", "description": "The routing policy. Deprecated: prefer setting policy in the service's router config. Options: `random`, `round_robin`, `cache_aware`, `power_of_two`", "default": "cache_aware"}}, "additionalProperties": false, "type": "object", "title": "SGLangGatewayRouterConfigRequest", "description": "Gateway-level router configuration. type and policy only. pd_disaggregation is service-level."}, "SGLangServiceRouterConfigRequest": {"properties": {"type": {"type": "string", "enum": ["sglang"], "title": "Type", "description": "The router type", "default": "sglang"}, "policy": {"type": "string", "enum": ["random", "round_robin", "cache_aware", "power_of_two"], "title": "Policy", "description": "The routing policy. Options: `random`, `round_robin`, `cache_aware`, `power_of_two`", "default": "cache_aware"}, "pd_disaggregation": {"type": "boolean", "title": "Pd Disaggregation", "description": "Enable PD disaggregation mode for the SGLang router", "default": false}}, "additionalProperties": false, "type": "object", "title": "SGLangServiceRouterConfigRequest"}, "SSHHostParamsRequest": {"properties": {"hostname": {"type": "string", "title": "Hostname", "description": "The IP address or domain to connect to"}, "port": {"type": "integer", "title": "Port", "description": "The SSH port to connect to for this host"}, "user": {"type": "string", "title": "User", "description": "The user to log in with for this host"}, "identity_file": {"type": "string", "title": "Identity File", "description": "The private key to use for this host"}, "proxy_jump": {"allOf": [{"$ref": "#/components/schemas/SSHProxyParamsRequest"}], "title": "Proxy Jump", "description": "The SSH proxy configuration for this host"}, "internal_ip": {"type": "string", "title": "Internal Ip", "description": "The internal IP of the host used for communication inside the cluster. If not specified, `dstack` will use the IP address from `network` or from the first found internal network."}, "ssh_key": {"$ref": "#/components/schemas/SSHKeyRequest"}, "blocks": {"anyOf": [{"type": "string", "enum": ["auto"]}, {"type": "integer", "minimum": 1.0}], "title": "Blocks", "description": "The amount of blocks to split the instance into, a number or `auto`. `auto` means as many as possible. The number of GPUs and CPUs must be divisible by the number of blocks. Defaults to the top-level `blocks` value"}}, "additionalProperties": false, "type": "object", "required": ["hostname"], "title": "SSHHostParamsRequest"}, "SSHKeyRequest": {"properties": {"public": {"type": "string", "title": "Public"}, "private": {"type": "string", "title": "Private"}}, "additionalProperties": false, "type": "object", "required": ["public"], "title": "SSHKeyRequest"}, "SSHParamsRequest": {"properties": {"user": {"type": "string", "title": "User", "description": "The user to log in with on all hosts"}, "port": {"type": "integer", "title": "Port", "description": "The SSH port to connect to"}, "identity_file": {"type": "string", "title": "Identity File", "description": "The private key to use for all hosts"}, "ssh_key": {"$ref": "#/components/schemas/SSHKeyRequest"}, "proxy_jump": {"allOf": [{"$ref": "#/components/schemas/SSHProxyParamsRequest"}], "title": "Proxy Jump", "description": "The SSH proxy configuration for all hosts"}, "hosts": {"items": {"anyOf": [{"$ref": "#/components/schemas/SSHHostParamsRequest"}, {"type": "string"}]}, "type": "array", "title": "Hosts", "description": "The per host connection parameters: a hostname or an object that overrides default ssh parameters"}, "network": {"type": "string", "title": "Network", "description": "The network address for cluster setup in the format `/`. `dstack` will use IP addresses from this network for communication between hosts. If not specified, `dstack` will use IPs from the first found internal network."}}, "additionalProperties": false, "type": "object", "required": ["hosts"], "title": "SSHParamsRequest"}, "SSHProxyParamsRequest": {"properties": {"hostname": {"type": "string", "title": "Hostname", "description": "The IP address or domain of proxy host"}, "port": {"type": "integer", "title": "Port", "description": "The SSH port of proxy host"}, "user": {"type": "string", "title": "User", "description": "The user to log in with for proxy host"}, "identity_file": {"type": "string", "title": "Identity File", "description": "The private key to use for proxy host"}, "ssh_key": {"$ref": "#/components/schemas/SSHKeyRequest"}}, "additionalProperties": false, "type": "object", "required": ["hostname", "user", "identity_file"], "title": "SSHProxyParamsRequest"}, "ScalingSpecRequest": {"properties": {"metric": {"type": "string", "enum": ["rps"], "title": "Metric", "description": "The target metric to track. Currently, the only supported value is `rps` (meaning requests per second)"}, "target": {"type": "number", "exclusiveMinimum": 0.0, "title": "Target", "description": "The target value of the metric. The number of replicas is calculated based on this number and automatically adjusts (scales up or down) as this metric changes"}, "window": {"type": "integer", "title": "Window", "description": "The time window used to calculate requests per second. Allowed values: `30s`, `60s`, `300s`. Defaults to `60s`"}, "scale_up_delay": {"type": "integer", "title": "Scale Up Delay", "description": "The minimum time, in seconds, between a scaling event and the next scale-up decision. Used to prevent overly frequent scaling", "default": 300}, "scale_down_delay": {"type": "integer", "title": "Scale Down Delay", "description": "The minimum time, in seconds, between a scaling event and the next scale-down decision. Used to prevent overly frequent scaling", "default": 600}}, "additionalProperties": false, "type": "object", "required": ["metric", "target"], "title": "ScalingSpecRequest"}, "ScheduleRequest": {"properties": {"cron": {"anyOf": [{"items": {"type": "string"}, "type": "array"}, {"type": "string"}], "title": "Cron", "description": "A cron expression or a list of cron expressions specifying the UTC time when the run needs to be started"}}, "additionalProperties": false, "type": "object", "required": ["cron"], "title": "ScheduleRequest"}, "ServiceConfigurationRequest": {"properties": {"port": {"anyOf": [{"type": "integer", "maximum": 65536.0, "exclusiveMinimum": 0.0}, {"type": "string", "pattern": "^[0-9]+:[0-9]+$"}, {"$ref": "#/components/schemas/PortMappingRequest"}], "title": "Port", "description": "The port the application listens on"}, "gateway": {"anyOf": [{"type": "boolean"}, {"$ref": "#/components/schemas/EntityReferenceRequest"}, {"type": "string"}], "title": "Gateway", "description": "The name of the gateway. Specify boolean `false` to run without a gateway. Specify boolean `true` to run with the default gateway. Omit to run with the default gateway if there is one, or without a gateway otherwise"}, "strip_prefix": {"type": "boolean", "title": "Strip Prefix", "description": "Strip the `/proxy/services///` path prefix when forwarding requests to the service. Only takes effect when running the service without a gateway", "default": true}, "model": {"anyOf": [{"$ref": "#/components/schemas/TGIChatModelRequest"}, {"$ref": "#/components/schemas/OpenAIChatModelRequest"}, {"type": "string"}], "title": "Model", "description": "Mapping of the model for the OpenAI-compatible endpoint provided by `dstack`. Can be a full model format definition or just a model name. If it's a name, the service is expected to expose an OpenAI-compatible API at the `/v1` path"}, "https": {"anyOf": [{"type": "boolean"}, {"type": "string", "enum": ["auto"]}], "title": "Https", "description": "Enable HTTPS if running with a gateway. Set to `auto` to determine automatically based on gateway configuration. Defaults to `true`"}, "auth": {"type": "boolean", "title": "Auth", "description": "Enable the authorization", "default": true}, "scaling": {"allOf": [{"$ref": "#/components/schemas/ScalingSpecRequest"}], "title": "Scaling", "description": "The auto-scaling rules. Required if `replicas` is set to a range"}, "rate_limits": {"items": {"$ref": "#/components/schemas/RateLimitRequest"}, "type": "array", "title": "Rate Limits", "description": "Rate limiting rules", "default": []}, "probes": {"items": {"$ref": "#/components/schemas/ProbeConfigRequest"}, "type": "array", "title": "Probes", "description": "The list of probes to determine service health. If `model` is set, defaults to a `/v1/chat/completions` probe. Set explicitly to override"}, "replicas": {"anyOf": [{"items": {"$ref": "#/components/schemas/ReplicaGroupRequest"}, "type": "array"}, {"$ref": "#/components/schemas/Range_int_"}, {"type": "integer"}, {"type": "string"}], "title": "Replicas", "description": "The number of replicas or a list of replica groups. Can be an integer (e.g., `2`), a range (e.g., `0..4`), or a list of replica groups. Each replica group defines replicas with shared configuration (commands, resources, scaling). When `replicas` is a list of replica groups, top-level `scaling`, `commands`, and `resources` are not allowed and must be specified in each replica group instead. "}, "router": {"allOf": [{"$ref": "#/components/schemas/SGLangServiceRouterConfigRequest"}], "title": "Router", "description": "Router configuration for the service. Requires a gateway with matching router enabled. "}, "commands": {"items": {"type": "string"}, "type": "array", "title": "Commands", "description": "The shell commands to run", "default": []}, "type": {"type": "string", "enum": ["service"], "title": "Type", "default": "service"}, "name": {"type": "string", "title": "Name", "description": "The run name. If not specified, a random name is generated"}, "image": {"type": "string", "title": "Image", "description": "The name of the Docker image to run"}, "user": {"type": "string", "title": "User", "description": "The user inside the container, `user_name_or_id[:group_name_or_id]` (e.g., `ubuntu`, `1000:1000`). Defaults to the default user from the `image`"}, "privileged": {"type": "boolean", "title": "Privileged", "description": "Run the container in privileged mode", "default": false}, "entrypoint": {"type": "string", "title": "Entrypoint", "description": "The Docker entrypoint"}, "working_dir": {"type": "string", "title": "Working Dir", "description": "The absolute path to the working directory inside the container. Defaults to the `image`'s default working directory"}, "home_dir": {"type": "string", "title": "Home Dir", "default": "/root"}, "registry_auth": {"allOf": [{"$ref": "#/components/schemas/RegistryAuthRequest"}], "title": "Registry Auth", "description": "Credentials for pulling a private Docker image"}, "python": {"allOf": [{"$ref": "#/components/schemas/PythonVersion"}], "description": "The major version of Python. Mutually exclusive with `image` and `docker`"}, "nvcc": {"type": "boolean", "title": "Nvcc", "description": "Use image with NVIDIA CUDA Compiler (NVCC) included. Mutually exclusive with `image` and `docker`"}, "single_branch": {"type": "boolean", "title": "Single Branch", "description": "Whether to clone and track only the current branch or all remote branches. Relevant only when using remote Git repos. Defaults to `false` for dev environments and to `true` for tasks and services"}, "env": {"allOf": [{"$ref": "#/components/schemas/Env"}], "title": "Env", "description": "The mapping or the list of environment variables", "default": {"__root__": {}}}, "shell": {"type": "string", "title": "Shell", "description": "The shell used to run commands. Allowed values are `sh`, `bash`, or an absolute path, e.g., `/usr/bin/zsh`. Defaults to `/bin/sh` if the `image` is specified, `/bin/bash` otherwise"}, "resources": {"allOf": [{"$ref": "#/components/schemas/ResourcesSpecRequest"}], "title": "Resources", "description": "The resources requirements to run the configuration", "default": {"cpu": {"min": 2}, "memory": {"min": 8.0}, "gpu": {"count": {"min": 0}}, "disk": {"size": {"min": 100.0}}}}, "priority": {"type": "integer", "maximum": 100.0, "minimum": 0.0, "title": "Priority", "description": "The priority of the run, an integer between `0` and `100`. `dstack` tries to provision runs with higher priority first. Defaults to `0`"}, "volumes": {"items": {"anyOf": [{"$ref": "#/components/schemas/VolumeMountPointRequest"}, {"$ref": "#/components/schemas/InstanceMountPointRequest"}, {"type": "string"}]}, "type": "array", "title": "Volumes", "description": "The volumes mount points", "default": []}, "docker": {"type": "boolean", "title": "Docker", "description": "Use Docker inside the container. Mutually exclusive with `image`, `python`, and `nvcc`. Overrides `privileged`"}, "repos": {"items": {"$ref": "#/components/schemas/RepoSpecRequest"}, "type": "array", "title": "Repos", "description": "The list of Git repos", "default": []}, "files": {"items": {"anyOf": [{"$ref": "#/components/schemas/FilePathMappingRequest"}, {"type": "string"}]}, "type": "array", "title": "Files", "description": "The local to container file path mappings", "default": []}, "setup": {"items": {"type": "string"}, "type": "array", "title": "Setup", "default": []}, "backends": {"items": {"$ref": "#/components/schemas/BackendType"}, "type": "array", "description": "The backends to consider for provisioning (e.g., `[aws, gcp]`)"}, "regions": {"items": {"type": "string"}, "type": "array", "title": "Regions", "description": "The regions to consider for provisioning (e.g., `[eu-west-1, us-west4, westeurope]`)"}, "availability_zones": {"items": {"type": "string"}, "type": "array", "title": "Availability Zones", "description": "The availability zones to consider for provisioning (e.g., `[eu-west-1a, us-west4-a]`)"}, "instance_types": {"items": {"type": "string"}, "type": "array", "title": "Instance Types", "description": "The cloud-specific instance types to consider for provisioning (e.g., `[g6e.24xlarge, n1-standard-4]`)"}, "reservation": {"type": "string", "title": "Reservation", "description": "The existing reservation to use for instance provisioning. Supports AWS Capacity Reservations, AWS Capacity Blocks, and GCP reservations"}, "spot_policy": {"allOf": [{"$ref": "#/components/schemas/SpotPolicy"}], "description": "The policy for provisioning spot or on-demand instances: `spot`, `on-demand`, `auto`. Defaults to `on-demand`"}, "retry": {"anyOf": [{"$ref": "#/components/schemas/ProfileRetryRequest"}, {"type": "boolean"}], "title": "Retry", "description": "The policy for resubmitting the run. Defaults to `false`"}, "max_duration": {"anyOf": [{"type": "string", "enum": ["off"]}, {"type": "integer"}, {"type": "boolean"}, {"type": "string"}], "title": "Max Duration", "description": "The maximum duration of a run (e.g., `2h`, `1d`, etc) in a running state, excluding provisioning and pulling. After it elapses, the run is automatically stopped. Use `off` for unlimited duration. Defaults to `off`"}, "stop_duration": {"anyOf": [{"type": "string", "enum": ["off"]}, {"type": "integer"}, {"type": "boolean"}, {"type": "string"}], "title": "Stop Duration", "description": "The maximum duration of a run graceful stopping. After it elapses, the run is automatically forced stopped. This includes force detaching volumes used by the run. Use `off` for unlimited duration. Defaults to `5m`"}, "max_price": {"type": "number", "exclusiveMinimum": 0.0, "title": "Max Price", "description": "The maximum instance price per hour, in dollars"}, "creation_policy": {"allOf": [{"$ref": "#/components/schemas/CreationPolicy"}], "description": "The policy for using instances from fleets: `reuse`, `reuse-or-create`. Defaults to `reuse-or-create`"}, "idle_duration": {"anyOf": [{"type": "integer"}, {"type": "string"}], "title": "Idle Duration", "description": "Time to wait before terminating idle instances. When the run reuses an existing fleet instance, the fleet's `idle_duration` applies. When the run provisions a new instance, the shorter of the fleet's and run's values is used. Defaults to `5m` for runs and `3d` for fleets. Use `off` for unlimited duration. Only applied for VM-based backends"}, "utilization_policy": {"allOf": [{"$ref": "#/components/schemas/UtilizationPolicyRequest"}], "title": "Utilization Policy", "description": "Run termination policy based on utilization"}, "startup_order": {"allOf": [{"$ref": "#/components/schemas/StartupOrder"}], "description": "The order in which master and workers jobs are started: `any`, `master-first`, `workers-first`. Defaults to `any`"}, "stop_criteria": {"allOf": [{"$ref": "#/components/schemas/StopCriteria"}], "description": "The criteria determining when a multi-node run should be considered finished: `all-done`, `master-done`. Defaults to `all-done`"}, "schedule": {"allOf": [{"$ref": "#/components/schemas/ScheduleRequest"}], "title": "Schedule", "description": "The schedule for starting the run at specified time"}, "fleets": {"items": {"anyOf": [{"$ref": "#/components/schemas/EntityReferenceRequest"}, {"type": "string"}]}, "type": "array", "title": "Fleets", "description": "The fleets considered for reuse. For fleets owned by the current project, specify fleet names. For imported fleets, specify `/`"}, "instances": {"items": {"anyOf": [{"$ref": "#/components/schemas/InstanceNameSelectorRequest"}, {"$ref": "#/components/schemas/InstanceHostnameSelectorRequest"}, {"$ref": "#/components/schemas/FleetInstanceSelectorRequest"}, {"type": "string", "minLength": 1}]}, "type": "array", "minItems": 1, "title": "Instances", "description": "The specific fleet instances to consider for reuse. Each value can be an instance name string, or an object with `name`, `hostname`, or `fleet` and `instance`. When set, the run is only placed on matching existing instances."}, "tags": {"additionalProperties": {"type": "string"}, "type": "object", "title": "Tags", "description": "The custom tags to associate with the resource. The tags are also propagated to the underlying backend resources. If there is a conflict with backend-level tags, does not override them"}, "backend_options": {"items": {"$ref": "#/components/schemas/VastAIProfileOptions"}, "type": "array", "title": "Backend Options", "description": "Backend-specific options, applied only to offers from that backend"}}, "additionalProperties": false, "type": "object", "required": ["port"], "title": "ServiceConfigurationRequest"}, "SpecApplyRequest": {"properties": {"user": {"type": "string", "title": "User", "description": "The name of the user making the apply request"}, "project": {"type": "string", "title": "Project", "description": "The name of the project the request is for"}, "spec": {"anyOf": [{"$ref": "#/components/schemas/RunSpecRequest"}, {"$ref": "#/components/schemas/FleetSpecRequest"}, {"$ref": "#/components/schemas/VolumeSpecRequest"}, {"$ref": "#/components/schemas/GatewaySpecRequest"}], "title": "Spec", "description": "The spec to be applied"}}, "type": "object", "required": ["user", "project", "spec"], "title": "SpecApplyRequest"}, "SpecApplyResponse": {"properties": {"spec": {"anyOf": [{"$ref": "#/components/schemas/RunSpecRequest"}, {"$ref": "#/components/schemas/FleetSpecRequest"}, {"$ref": "#/components/schemas/VolumeSpecRequest"}, {"$ref": "#/components/schemas/GatewaySpecRequest"}], "title": "Spec", "description": "The spec to apply, original spec if error otherwise original or mutated by plugin service if approved"}, "error": {"type": "string", "minLength": 1, "title": "Error", "description": "Error message if request is rejected"}}, "type": "object", "required": ["spec"], "title": "SpecApplyResponse"}, "SpotPolicy": {"type": "string", "enum": ["spot", "on-demand", "auto"], "title": "SpotPolicy", "description": "An enumeration."}, "StartupOrder": {"type": "string", "enum": ["any", "master-first", "workers-first"], "title": "StartupOrder", "description": "An enumeration."}, "StopCriteria": {"type": "string", "enum": ["all-done", "master-done"], "title": "StopCriteria", "description": "An enumeration."}, "TGIChatModelRequest": {"properties": {"type": {"type": "string", "enum": ["chat"], "title": "Type", "description": "The type of the model", "default": "chat"}, "name": {"type": "string", "title": "Name", "description": "The name of the model"}, "format": {"type": "string", "enum": ["tgi"], "title": "Format", "description": "The serving format. Must be set to `tgi`"}, "chat_template": {"type": "string", "title": "Chat Template", "description": "The custom prompt template for the model. If not specified, the default prompt template from the HuggingFace Hub configuration will be used"}, "eos_token": {"type": "string", "title": "Eos Token", "description": "The custom end of sentence token. If not specified, the default end of sentence token from the HuggingFace Hub configuration will be used"}}, "additionalProperties": false, "type": "object", "required": ["name", "format"], "title": "TGIChatModelRequest", "description": "Mapping of the model for the OpenAI-compatible endpoint.\n\nAttributes:\n type (str): The type of the model, e.g. \"chat\"\n name (str): The name of the model. This name will be used both to load model configuration from the HuggingFace Hub and in the OpenAI-compatible endpoint.\n format (str): The format of the model, e.g. \"tgi\" if the model is served with HuggingFace's Text Generation Inference.\n chat_template (Optional[str]): The custom prompt template for the model. If not specified, the default prompt template from the HuggingFace Hub configuration will be used.\n eos_token (Optional[str]): The custom end of sentence token. If not specified, the default end of sentence token from the HuggingFace Hub configuration will be used."}, "TaskConfigurationRequest": {"properties": {"nodes": {"type": "integer", "minimum": 1.0, "title": "Nodes", "description": "Number of nodes", "default": 1}, "ports": {"items": {"anyOf": [{"type": "integer", "maximum": 65536.0, "exclusiveMinimum": 0.0}, {"type": "string", "pattern": "^(?:[0-9]+|\\*):[0-9]+$"}, {"$ref": "#/components/schemas/PortMappingRequest"}]}, "type": "array", "title": "Ports", "description": "Port numbers/mapping to expose", "default": []}, "commands": {"items": {"type": "string"}, "type": "array", "title": "Commands", "description": "The shell commands to run", "default": []}, "type": {"type": "string", "enum": ["task"], "title": "Type", "default": "task"}, "name": {"type": "string", "title": "Name", "description": "The run name. If not specified, a random name is generated"}, "image": {"type": "string", "title": "Image", "description": "The name of the Docker image to run"}, "user": {"type": "string", "title": "User", "description": "The user inside the container, `user_name_or_id[:group_name_or_id]` (e.g., `ubuntu`, `1000:1000`). Defaults to the default user from the `image`"}, "privileged": {"type": "boolean", "title": "Privileged", "description": "Run the container in privileged mode", "default": false}, "entrypoint": {"type": "string", "title": "Entrypoint", "description": "The Docker entrypoint"}, "working_dir": {"type": "string", "title": "Working Dir", "description": "The absolute path to the working directory inside the container. Defaults to the `image`'s default working directory"}, "home_dir": {"type": "string", "title": "Home Dir", "default": "/root"}, "registry_auth": {"allOf": [{"$ref": "#/components/schemas/RegistryAuthRequest"}], "title": "Registry Auth", "description": "Credentials for pulling a private Docker image"}, "python": {"allOf": [{"$ref": "#/components/schemas/PythonVersion"}], "description": "The major version of Python. Mutually exclusive with `image` and `docker`"}, "nvcc": {"type": "boolean", "title": "Nvcc", "description": "Use image with NVIDIA CUDA Compiler (NVCC) included. Mutually exclusive with `image` and `docker`"}, "single_branch": {"type": "boolean", "title": "Single Branch", "description": "Whether to clone and track only the current branch or all remote branches. Relevant only when using remote Git repos. Defaults to `false` for dev environments and to `true` for tasks and services"}, "env": {"allOf": [{"$ref": "#/components/schemas/Env"}], "title": "Env", "description": "The mapping or the list of environment variables", "default": {"__root__": {}}}, "shell": {"type": "string", "title": "Shell", "description": "The shell used to run commands. Allowed values are `sh`, `bash`, or an absolute path, e.g., `/usr/bin/zsh`. Defaults to `/bin/sh` if the `image` is specified, `/bin/bash` otherwise"}, "resources": {"allOf": [{"$ref": "#/components/schemas/ResourcesSpecRequest"}], "title": "Resources", "description": "The resources requirements to run the configuration", "default": {"cpu": {"min": 2}, "memory": {"min": 8.0}, "gpu": {"count": {"min": 0}}, "disk": {"size": {"min": 100.0}}}}, "priority": {"type": "integer", "maximum": 100.0, "minimum": 0.0, "title": "Priority", "description": "The priority of the run, an integer between `0` and `100`. `dstack` tries to provision runs with higher priority first. Defaults to `0`"}, "volumes": {"items": {"anyOf": [{"$ref": "#/components/schemas/VolumeMountPointRequest"}, {"$ref": "#/components/schemas/InstanceMountPointRequest"}, {"type": "string"}]}, "type": "array", "title": "Volumes", "description": "The volumes mount points", "default": []}, "docker": {"type": "boolean", "title": "Docker", "description": "Use Docker inside the container. Mutually exclusive with `image`, `python`, and `nvcc`. Overrides `privileged`"}, "repos": {"items": {"$ref": "#/components/schemas/RepoSpecRequest"}, "type": "array", "title": "Repos", "description": "The list of Git repos", "default": []}, "files": {"items": {"anyOf": [{"$ref": "#/components/schemas/FilePathMappingRequest"}, {"type": "string"}]}, "type": "array", "title": "Files", "description": "The local to container file path mappings", "default": []}, "setup": {"items": {"type": "string"}, "type": "array", "title": "Setup", "default": []}, "backends": {"items": {"$ref": "#/components/schemas/BackendType"}, "type": "array", "description": "The backends to consider for provisioning (e.g., `[aws, gcp]`)"}, "regions": {"items": {"type": "string"}, "type": "array", "title": "Regions", "description": "The regions to consider for provisioning (e.g., `[eu-west-1, us-west4, westeurope]`)"}, "availability_zones": {"items": {"type": "string"}, "type": "array", "title": "Availability Zones", "description": "The availability zones to consider for provisioning (e.g., `[eu-west-1a, us-west4-a]`)"}, "instance_types": {"items": {"type": "string"}, "type": "array", "title": "Instance Types", "description": "The cloud-specific instance types to consider for provisioning (e.g., `[g6e.24xlarge, n1-standard-4]`)"}, "reservation": {"type": "string", "title": "Reservation", "description": "The existing reservation to use for instance provisioning. Supports AWS Capacity Reservations, AWS Capacity Blocks, and GCP reservations"}, "spot_policy": {"allOf": [{"$ref": "#/components/schemas/SpotPolicy"}], "description": "The policy for provisioning spot or on-demand instances: `spot`, `on-demand`, `auto`. Defaults to `on-demand`"}, "retry": {"anyOf": [{"$ref": "#/components/schemas/ProfileRetryRequest"}, {"type": "boolean"}], "title": "Retry", "description": "The policy for resubmitting the run. Defaults to `false`"}, "max_duration": {"anyOf": [{"type": "string", "enum": ["off"]}, {"type": "integer"}, {"type": "boolean"}, {"type": "string"}], "title": "Max Duration", "description": "The maximum duration of a run (e.g., `2h`, `1d`, etc) in a running state, excluding provisioning and pulling. After it elapses, the run is automatically stopped. Use `off` for unlimited duration. Defaults to `off`"}, "stop_duration": {"anyOf": [{"type": "string", "enum": ["off"]}, {"type": "integer"}, {"type": "boolean"}, {"type": "string"}], "title": "Stop Duration", "description": "The maximum duration of a run graceful stopping. After it elapses, the run is automatically forced stopped. This includes force detaching volumes used by the run. Use `off` for unlimited duration. Defaults to `5m`"}, "max_price": {"type": "number", "exclusiveMinimum": 0.0, "title": "Max Price", "description": "The maximum instance price per hour, in dollars"}, "creation_policy": {"allOf": [{"$ref": "#/components/schemas/CreationPolicy"}], "description": "The policy for using instances from fleets: `reuse`, `reuse-or-create`. Defaults to `reuse-or-create`"}, "idle_duration": {"anyOf": [{"type": "integer"}, {"type": "string"}], "title": "Idle Duration", "description": "Time to wait before terminating idle instances. When the run reuses an existing fleet instance, the fleet's `idle_duration` applies. When the run provisions a new instance, the shorter of the fleet's and run's values is used. Defaults to `5m` for runs and `3d` for fleets. Use `off` for unlimited duration. Only applied for VM-based backends"}, "utilization_policy": {"allOf": [{"$ref": "#/components/schemas/UtilizationPolicyRequest"}], "title": "Utilization Policy", "description": "Run termination policy based on utilization"}, "startup_order": {"allOf": [{"$ref": "#/components/schemas/StartupOrder"}], "description": "The order in which master and workers jobs are started: `any`, `master-first`, `workers-first`. Defaults to `any`"}, "stop_criteria": {"allOf": [{"$ref": "#/components/schemas/StopCriteria"}], "description": "The criteria determining when a multi-node run should be considered finished: `all-done`, `master-done`. Defaults to `all-done`"}, "schedule": {"allOf": [{"$ref": "#/components/schemas/ScheduleRequest"}], "title": "Schedule", "description": "The schedule for starting the run at specified time"}, "fleets": {"items": {"anyOf": [{"$ref": "#/components/schemas/EntityReferenceRequest"}, {"type": "string"}]}, "type": "array", "title": "Fleets", "description": "The fleets considered for reuse. For fleets owned by the current project, specify fleet names. For imported fleets, specify `/`"}, "instances": {"items": {"anyOf": [{"$ref": "#/components/schemas/InstanceNameSelectorRequest"}, {"$ref": "#/components/schemas/InstanceHostnameSelectorRequest"}, {"$ref": "#/components/schemas/FleetInstanceSelectorRequest"}, {"type": "string", "minLength": 1}]}, "type": "array", "minItems": 1, "title": "Instances", "description": "The specific fleet instances to consider for reuse. Each value can be an instance name string, or an object with `name`, `hostname`, or `fleet` and `instance`. When set, the run is only placed on matching existing instances."}, "tags": {"additionalProperties": {"type": "string"}, "type": "object", "title": "Tags", "description": "The custom tags to associate with the resource. The tags are also propagated to the underlying backend resources. If there is a conflict with backend-level tags, does not override them"}, "backend_options": {"items": {"$ref": "#/components/schemas/VastAIProfileOptions"}, "type": "array", "title": "Backend Options", "description": "Backend-specific options, applied only to offers from that backend"}}, "additionalProperties": false, "type": "object", "title": "TaskConfigurationRequest"}, "UtilizationPolicyRequest": {"properties": {"min_gpu_utilization": {"type": "integer", "maximum": 100.0, "minimum": 0.0, "title": "Min Gpu Utilization", "description": "Minimum required GPU utilization, percent. If any GPU has utilization below specified value during the whole time window, the run is terminated"}, "time_window": {"anyOf": [{"type": "integer"}, {"type": "string"}], "title": "Time Window", "description": "The time window of metric samples taking into account to measure utilization (e.g., `30m`, `1h`). Minimum is `5m`"}}, "additionalProperties": false, "type": "object", "required": ["min_gpu_utilization", "time_window"], "title": "UtilizationPolicyRequest"}, "ValidationError": {"properties": {"loc": {"items": {"anyOf": [{"type": "string"}, {"type": "integer"}]}, "type": "array", "title": "Location"}, "msg": {"type": "string", "title": "Message"}, "type": {"type": "string", "title": "Error Type"}}, "type": "object", "required": ["loc", "msg", "type"], "title": "ValidationError"}, "VastAIOfferOrder": {"type": "string", "enum": ["score", "price"], "title": "VastAIOfferOrder", "description": "An enumeration."}, "VastAIProfileOptions": {"properties": {"type": {"type": "string", "enum": ["vastai"], "title": "Type", "default": "vastai"}, "offer_order": {"allOf": [{"$ref": "#/components/schemas/VastAIOfferOrder"}], "description": "Controls the order in which offers are considered for provisioning. Use `score` to prioritize the highest overall score first (the default order in the Vast.ai console), or `price` to prioritize the lowest-cost offers first. Lower-cost offers are often less reliable, so consider applying stricter filters when using `price`. Defaults to `score`"}, "min_reliability": {"type": "number", "maximum": 1.0, "minimum": 0.0, "title": "Min Reliability", "description": "The minimum reliability threshold for offers, on a scale from `0` to `1`. Defaults to `0.9`"}, "min_score": {"type": "integer", "minimum": 0.0, "title": "Min Score", "description": "The minimum overall score required for offers to be considered. The scoring scale varies and may require experimentation. Starting with a value in the low hundreds is generally recommended"}}, "additionalProperties": false, "type": "object", "title": "VastAIProfileOptions"}, "VirtualRunRepoDataRequest": {"properties": {"repo_type": {"type": "string", "enum": ["virtual"], "title": "Repo Type", "default": "virtual"}}, "additionalProperties": false, "type": "object", "title": "VirtualRunRepoDataRequest"}, "VolumeMountPointRequest": {"properties": {"name": {"anyOf": [{"type": "string"}, {"items": {"type": "string"}, "type": "array"}], "title": "Name", "description": "The network volume name or the list of network volume names to mount. If a list is specified, one of the volumes in the list will be mounted. Specify volumes from different backends/regions to increase availability"}, "path": {"type": "string", "title": "Path", "description": "The absolute container path to mount the volume at"}}, "additionalProperties": false, "type": "object", "required": ["name", "path"], "title": "VolumeMountPointRequest"}, "VolumeSpecRequest": {"properties": {"configuration": {"oneOf": [{"$ref": "#/components/schemas/AWSVolumeConfigurationRequest"}, {"$ref": "#/components/schemas/GCPVolumeConfigurationRequest"}, {"$ref": "#/components/schemas/RunpodVolumeConfigurationRequest"}, {"$ref": "#/components/schemas/KubernetesVolumeConfigurationRequest"}], "title": "Configuration", "discriminator": {"propertyName": "backend", "mapping": {"aws": "#/components/schemas/AWSVolumeConfigurationRequest", "gcp": "#/components/schemas/GCPVolumeConfigurationRequest", "runpod": "#/components/schemas/RunpodVolumeConfigurationRequest", "kubernetes": "#/components/schemas/KubernetesVolumeConfigurationRequest"}}}, "configuration_path": {"type": "string", "title": "Configuration Path"}}, "additionalProperties": false, "type": "object", "required": ["configuration"], "title": "VolumeSpecRequest"}}}} diff --git a/website/package-lock.json b/website/package-lock.json index 4bbcd544b..e92b87475 100644 --- a/website/package-lock.json +++ b/website/package-lock.json @@ -1,16 +1,17 @@ { - "name": "cloudscape-reverse-engineering", + "name": "dstack-website", "version": "0.1.0", "lockfileVersion": 3, "requires": true, "packages": { "": { - "name": "cloudscape-reverse-engineering", + "name": "dstack-website", "version": "0.1.0", "dependencies": { "@cloudscape-design/code-view": "^3.0.142", "@cloudscape-design/components": "^3.0.0", "@cloudscape-design/global-styles": "^1.0.0", + "@dstackai/sqircle": "^0.1.6", "react": "^18.2.0", "react-dom": "^18.2.0", "react-router-dom": "^6.30.4" @@ -483,6 +484,16 @@ "react": ">=16.8.0" } }, + "node_modules/@dstackai/sqircle": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/@dstackai/sqircle/-/sqircle-0.1.6.tgz", + "integrity": "sha512-OJ5e1lqBre4F/ObFAjonQFjbXEH9evZjTX82HK7MvVyAu3FmccF69EY5NLzXrjhmoFDYjp7Lx7KbsR93zjaDkQ==", + "license": "MIT", + "peerDependencies": { + "react": ">=18.0.0", + "react-dom": ">=18.0.0" + } + }, "node_modules/@esbuild/aix-ppc64": { "version": "0.27.7", "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.7.tgz", diff --git a/website/package.json b/website/package.json index c0d772303..176b0be91 100644 --- a/website/package.json +++ b/website/package.json @@ -12,6 +12,7 @@ "@cloudscape-design/code-view": "^3.0.142", "@cloudscape-design/components": "^3.0.0", "@cloudscape-design/global-styles": "^1.0.0", + "@dstackai/sqircle": "^0.1.6", "react": "^18.2.0", "react-dom": "^18.2.0", "react-router-dom": "^6.30.4" diff --git a/website/public/static/dstack-gpu-artwork-dark.svg b/website/public/static/dstack-gpu-artwork-dark.svg deleted file mode 100644 index eb5b68eeb..000000000 --- a/website/public/static/dstack-gpu-artwork-dark.svg +++ /dev/null @@ -1,49 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/website/public/static/dstack-gpu-artwork.svg b/website/public/static/dstack-gpu-artwork.svg deleted file mode 100644 index 1c06e970a..000000000 --- a/website/public/static/dstack-gpu-artwork.svg +++ /dev/null @@ -1,49 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/website/src/cloudscape-overrides.css b/website/src/cloudscape-overrides.css index a1845b412..42230d504 100644 --- a/website/src/cloudscape-overrides.css +++ b/website/src/cloudscape-overrides.css @@ -33,19 +33,18 @@ /* 2) Tabs: the bottom divider and the active-tab underline use the text color. Tabs use tab-specific border-color tokens that the theming API doesn't expose. */ body { - --color-border-tabs-divider-f5t9va: var(--cs-text); + --color-border-tabs-divider-f5t9va: color-mix(in srgb, var(--cs-text) 14%, transparent); --color-border-tabs-underline-gudemr: var(--cs-text); --color-border-dropdown-group-ylcnh8: transparent; /* no divider between dropdown groups */ --color-text-expandable-section-hover-ojzwhd: var(--cs-text); /* FAQ: don't turn the question blue on hover */ } /* 4) Content tabs. - a) Full-height vertical separators between tabs (default insets them 12px top/bottom). - `inset-block: 0` spans only the container's padding box, leaving a 1px gap at top/bottom - against the 1px transparent border; the negative inset extends them over that border so - they match the height of the edge separators (real borders on the border box). */ -[class*='awsui_tabs-tab_']:not(:last-child) > [class*='awsui_tabs-tab-header-container']::before { - inset-block: calc(-1 * var(--border-divider-section-width-uwo8my, 1px)) !important; + a) Drop the vertical separators Cloudscape draws between tabs. Selection is shown by the + active tab's pill background (section 9), so the dividers just add noise — the tabs read + as a row of pills separated by the cells' own padding (matching the landing's tab design). */ +[class*='awsui_tabs-tab_'] > [class*='awsui_tabs-tab-header-container']::before { + display: none !important; } /* b) Drop the gray border + shadow that Cloudscape shows on the scroll arrows when the tab strip overflows (the box-shadow renders both; border-inline is the divider). */ @@ -57,6 +56,12 @@ body { [class*='awsui_pagination-button-right'] { border-inline: 0 !important; } +/* c) The scroll-arrow buttons carry 12px block margins, so they are taller than the tab row and + stretch the whole strip (and its tabs) ~10px taller than a chevron-free strip. Zero the block + margin so the arrows no longer inflate the tab height — the strip matches the others. */ +[class*='awsui_pagination-button'] { + margin-block: 0 !important; +} /* 6) FAQ accordion (ExpandableSection): faint background tint on hover that covers the whole block (question + answer) uniformly. Tint the section root, then neutralize @@ -100,57 +105,45 @@ body { font-weight: 500 !important; } -/* 9) Content tabs: highlight the selected (and hovered) tab with a background - tint instead of blue text + an underline indicator. The background uses the same tint as - the dropdown popup items (--cs-hover), so every hover surface matches. */ -/* a) The under-tab horizontal divider and the vertical tab separators keep the 1px outer - border width (they inherit --border-divider-section-width, no override needed here). */ -/* b) Drop the per-tab active underline indicator — selection is now shown by the background - (this also removes that indicator's rounded ends). */ +/* 9) Content tabs — "pill" selection, matching the landing's own tab design: the active tab is a + panel-grey rounded-top pill (a hovered tab gets the faint --cs-hover tint), inactive labels + are muted, and there are no separators (section 4a). The pill is painted on the inner link + so each cell's inline padding becomes the gap between pills; a little top padding on the + strip lets the pills float above the softened (section 2) under-tab divider. */ +/* a) Drop the per-tab active underline indicator — selection is shown by the pill background. */ [class*='awsui_tabs-tab-header-container']::after { display: none !important; } -/* c) Keep tab labels in the body color at rest, on hover, and when selected (no blue accent). */ +/* b) Float the pills: top padding above the strip, and tighter cell padding so neighbouring + pills sit close (the cell's inline padding is the inter-pill gap). */ +[class*='awsui_tabs-header-list'] { + padding: 6px 4px 0 !important; +} +[class*='awsui_tabs-tab-header-container'] { + padding-inline: 3px !important; +} +/* c) The pill: muted label at rest, body color + faint tint on hover, panel-grey when selected. + Rounded top only — the flat bottom sits on the under-tab divider. aria-selected lives on + the inner link. */ [class*='awsui_tabs-tab-link'] { - color: var(--cs-text) !important; + color: var(--cs-muted) !important; + padding-block: 13px !important; /* with the label padding zeroed (d), this matches .gs-tab's 15px/14px content-tab height */ + padding-inline: 14px !important; + border-radius: 8px 8px 0 0 !important; } -/* d) Background highlight for the hovered and selected tab — painted on the whole tab cell - (the header container fills edge to edge; the inner link has padding-inline:0, so painting - the link alone leaves gaps). aria-selected lives on the inner link, hence :has(). */ -[class*='awsui_tabs-tab-header-container']:hover, -[class*='awsui_tabs-tab-header-container']:has([aria-selected='true']) { +[class*='awsui_tabs-tab-link']:hover { + color: var(--cs-text) !important; background: var(--cs-hover) !important; } -/* e) 1px vertical separators bounding the strip (Cloudscape only draws them between tabs). - After the last tab: always. Before the first tab: ONLY when the strip overflows (a - scroll arrow is present). With no scrolling the first tab sits against the container's - left border, so a leading separator just reads as a doubled line — so we omit it. */ -[class*='awsui_tabs-tab_']:last-child > [class*='awsui_tabs-tab-header-container'] { - border-inline-end: 1px solid var(--color-border-tabs-divider-f5t9va) !important; -} -[class*='awsui_tab-header-scroll-container']:has([class*='pagination-button-left-scrollable'], [class*='pagination-button-right-scrollable']) - [class*='awsui_tabs-tab_']:first-child - > [class*='awsui_tabs-tab-header-container'] { - border-inline-start: 1px solid var(--color-border-tabs-divider-f5t9va) !important; +[class*='awsui_tabs-tab-link'][aria-selected='true'] { + color: var(--cs-text) !important; + background: var(--cs-panel) !important; } - -/* 10) GPU table: 0.5px row separators that run edge to edge, while keeping the - container's breathing room. The Container padding lives on `content-inner` (the - `with-paddings` element). We drop only its INLINE padding so the table — and therefore - the row separators — spans the full width; the top/bottom padding is kept. A 20px inset - is then restored on just the outer cells so the text isn't flush against the border. */ -.gpu-scroll * { - --border-divider-list-width-tdfx1x: 0.5px; -} -[class*='awsui_content-inner']:has(.gpu-scroll) { - padding-inline: 0 !important; - padding-block: 8px !important; /* a little top/bottom breathing room, trimmed from the default */ -} -.gpu-scroll tr > :first-child { - padding-inline-start: 20px !important; -} -.gpu-scroll tr > :last-child { - padding-inline-end: 20px !important; +/* d) The label span carries its own inner padding (4px 8px) on top of the link's padding, which + makes the Cloudscape pill ~12px taller than the landing's own tabs. Zero it so the pill height + is just link-padding + text — matching .gs-tab / .gs-skytab exactly. */ +[class*='awsui_tabs-tab-label'] { + padding: 0 !important; } /* 11) Thin (0.5px) dividers between stacked FAQ items. The block's outer corners diff --git a/website/src/cloudscape-theme.ts b/website/src/cloudscape-theme.ts index 18d98325f..f5da772ff 100644 --- a/website/src/cloudscape-theme.ts +++ b/website/src/cloudscape-theme.ts @@ -112,3 +112,8 @@ export const mainButtonStyle: ButtonProps.Style = { export const menuButtonStyle: ButtonProps.Style = { root: { paddingBlock: '7px', paddingInline: '18px' }, }; +// Header "Get started" — same as menuButtonStyle but with 1px extra on the right so the label +// doesn't sit a hair close to the edge (inline-end 19px vs inline-start 18px). +export const getStartedButtonStyle: ButtonProps.Style = { + root: { paddingBlock: '7px', paddingInline: '18px 19px' }, +}; diff --git a/website/src/components/HeroSquircle.tsx b/website/src/components/HeroSquircle.tsx new file mode 100644 index 000000000..3f9cb8467 --- /dev/null +++ b/website/src/components/HeroSquircle.tsx @@ -0,0 +1,103 @@ +import { + SquircleScene, + type SquircleGeometryConfig, + type SquircleLayerConfig, + type SquircleLayerHoverContext, +} from '@dstackai/sqircle'; +import '@dstackai/sqircle/style.css'; +import type { ThemeMode } from '../theme'; + +// Live hero object, composed in the squircle constructor (@dstackai/sqircle) and pasted here. +// Layers, back → front: wireframe slab (dashed inlay) → wireframe slab → solid +// "GPU" face (dotted inlay). Layers cross-react on hover, and clicking any layer scrolls to +// Get-started. `theme` stays driven by the app toggle (the snippet hardcodes "light"). +const HERO_GEOMETRY: SquircleGeometryConfig = { + angleDegrees: 20, +}; + +const HERO_LAYERS: SquircleLayerConfig[] = [ + { + id: 'layer-1', + visible: true, + offset: { x: 0, y: 176 }, + geometry: { exponent: 24 }, + base: { + material: 'wireframe', + paletteId: '15', + line: 'dashed', + lineColor: 'auto', + effect: 'off', + }, + stroke: { wire: 1.6, line: 2.2, face: 0, wireLine: 2.2 }, + // Hovering this bottom slab itself turns it into a solid metal "dstack" face (no inlay line). + hover: (ctx: SquircleLayerHoverContext) => { + if (ctx.hoveredLayerId === 'layer-1') + return { material: 'solid', paletteId: '20', effect: 'metal', text: 'dstack', line: false } + return false + } + }, + { + id: 'layer-2', + visible: true, + offset: { x: 0, y: 88 }, + geometry: { exponent: 24 }, + base: { + material: 'transparent', + paletteId: '20', + // line: false, + }, + stroke: { face: 0 }, + // Cross-layer hover (0.1.4 resolver API): hovering the top (layer-3) or bottom (layer-1) + // slab keeps this middle a wireframe; hovering the middle itself turns it transparent. + hover: (ctx: SquircleLayerHoverContext) => { + if (ctx.hoveredLayerId === 'layer-2') return { material: 'wireframe' }; + if (ctx.hoveredLayerId === 'layer-1') + return { material: 'wireframe' }; + return false; + }, + }, + { + id: 'layer-3', + visible: true, + offset: { x: 0, y: 0 }, + geometry: { exponent: 24 }, + base: { + material: 'solid', + paletteId: '20', + effect: 'metal', + text: 'GPU', + textColor: 'auto', + textStyle: 'solid', + // line: 'dotted', + lineColor: 'auto', + grain: true, + }, + // Top "GPU" face hover behavior: + // - hovering itself (layer-3): becomes solid palette 20 with the metal effect; + // - hovering the bottom (layer-1): wireframe, "GPU" text outlined, inlay line removed; + // - hovering the middle (layer-2): wireframe, "GPU" text outlined (line kept). + hover: (ctx: SquircleLayerHoverContext) => { + if (ctx.hoveredLayerId === 'layer-1') return { material: 'wireframe', line: 'dotted', palletteId: 15, textStyle: 'wireframe' }; + // if (ctx.hoveredLayerId === 'layer-2') return { material: 'wireframe', line: 'dotted', palletteId: 15 }; + if (ctx.hoveredLayerId === 'layer-3') return { material: 'wireframe', line: 'dotted', palletteId: 15 }; + return false; + }, + }, +]; + +export function HeroSquircle({ theme }: { theme: ThemeMode }) { + return ( +
+ + document.getElementById('resources')?.scrollIntoView({ behavior: 'smooth' }) + } + /> +
+ ); +} diff --git a/website/src/components/SiteNavigation.tsx b/website/src/components/SiteNavigation.tsx index 8fe7eda1b..4eac7b1bc 100644 --- a/website/src/components/SiteNavigation.tsx +++ b/website/src/components/SiteNavigation.tsx @@ -4,7 +4,7 @@ import Button from '@cloudscape-design/components/button'; import Icon from '@cloudscape-design/components/icon'; import SideNavigation, { SideNavigationProps } from '@cloudscape-design/components/side-navigation'; import SpaceBetween from '@cloudscape-design/components/space-between'; -import { menuButtonStyle } from '../cloudscape-theme'; +import { getStartedButtonStyle, menuButtonStyle } from '../cloudscape-theme'; import { ThemeToggle } from './ThemeToggle'; import { asset } from '../asset'; import { BLOG_URL, DOCS_URL, ROUTES } from '../routes'; @@ -90,7 +90,6 @@ const mobileNavigationItems: SideNavigationProps.Item[] = [ { type: 'link', text: 'Case studies', href: `${BLOG_URL}/case-studies/` }, { type: 'link', text: 'Blog', href: BLOG_URL }, { type: 'link', text: 'GitHub', href: dstackGithubUrl, external: true, externalIconAriaLabel }, - { type: 'link', text: 'dstack Sky', href: 'https://sky.dstack.ai', external: true, externalIconAriaLabel }, ]; // Standalone "Products" top-nav menu. The popup opens on hover (and on keyboard focus) and @@ -149,15 +148,16 @@ function ProductsHoverMenu() {
{/* Open-source featured on the brand gradient; the whole panel links to install. */} - {stars !== null && ( - - - {formatStars(stars)} - - )} - {products[0].text} - {products[0].secondaryText} - Documentation + + + {stars !== null && ( + {formatStars(stars)} + )} + + + {products[0].text} + {products[0].secondaryText} +
{products.slice(1).map(product => ( @@ -210,6 +210,19 @@ export function SiteNavigation({ setMobileNavigationOpen(false); }; + // "Get started" scrolls to the Get-started section on the home page (id="resources"). From any + // other route, go home first, then scroll once it has rendered. + const goToGetStarted = (event: { preventDefault: () => void }) => { + event.preventDefault(); + setMobileNavigationOpen(false); + if (pathname === ROUTES.HOME) { + document.getElementById('resources')?.scrollIntoView({ behavior: 'smooth' }); + } else { + navigate(ROUTES.HOME); + window.setTimeout(() => document.getElementById('resources')?.scrollIntoView({ behavior: 'smooth' }), 120); + } + }; + return (
@@ -250,7 +263,6 @@ export function SiteNavigation({ tablet/mobile it moves to the footer (the whole nav collapses into the burger menu). */} diff --git a/website/src/data/gpus.ts b/website/src/data/gpus.ts new file mode 100644 index 000000000..152625a9c --- /dev/null +++ b/website/src/data/gpus.ts @@ -0,0 +1,14 @@ +// Rough per-GPU/hour price ranges across backends, in the spirit of `dstack offer --group-by gpu`. +// Single source of truth, shared by the "Access marketplace GPUs" block (ExploreSection) and the +// dstack Sky "GPU marketplace" pane (GetStartedSection). Prices use a compact en-dash range; a +// "/hr" suffix is appended at render time. +export const gpuOffers = [ + { name: 'B300', memory: '288GB', price: '$6.00–12.00' }, + { name: 'B200', memory: '192GB', price: '$4.00–9.00' }, + { name: 'H200', memory: '141GB', price: '$3.10–7.49' }, + { name: 'H100', memory: '80GB', price: '$1.90–5.99' }, + { name: 'RTX PRO 6000', memory: '96GB', price: '$1.79–3.50' }, + { name: 'A100', memory: '80GB', price: '$1.20–3.40' }, + { name: 'A100', memory: '40GB', price: '$0.83–2.30' }, + { name: 'L40S', memory: '48GB', price: '$0.80–1.40' }, +]; diff --git a/website/src/data/images.ts b/website/src/data/images.ts index c525c5b88..0201d8251 100644 --- a/website/src/data/images.ts +++ b/website/src/data/images.ts @@ -1,18 +1,10 @@ -// Image assets used across the site. Local SVGs live in /public/static; the architecture -// diagram is served from the dstack static-assets host; the Old-page placeholders are +// Image assets used across the site. The home hero is now a live component and +// the "Vendor-agnostic, open-source" architecture diagram is an HTML/CSS component (see +// components/ArchitectureDiagram.tsx) — neither is an image. The Old-page placeholders below are // pulled from the Cloudscape foundation image set. -import { asset } from '../asset'; - const img = (path: string) => `https://cloudscape.design${path}`; export const images = { - // Home hero artwork (light/dark variants). - hero: { - light: asset('/static/dstack-gpu-artwork.svg'), - dark: asset('/static/dstack-gpu-artwork-dark.svg'), - }, - // (The "Vendor-agnostic, open-source" architecture diagram is now an HTML/CSS component — - // see components/ArchitectureDiagram.tsx — not an image.) // Old page imagery (kept for comparison / as a template for future product pages). meet: img('/__images/yvlrib0vb3vb/3RkANdWu0IRLpTcBJYSPg5/2397551327a83cfbddd1fe4db9f58188/homepage--meet-cloudscape--os-light.png'), familiar: img('/__images/yvlrib0vb3vb/3CJGtMGSx07lhdtgwL8Ncb/0e33dc1bac3936239e2bc856ee268e80/homepage--get-familiar-with-system--os-light.png'), diff --git a/website/src/pages/Home/ExploreSection.tsx b/website/src/pages/Home/ExploreSection.tsx index a160bd5f5..d4de38682 100644 --- a/website/src/pages/Home/ExploreSection.tsx +++ b/website/src/pages/Home/ExploreSection.tsx @@ -1,14 +1,14 @@ import CodeView from '@cloudscape-design/code-view/code-view'; import yamlHighlight from '@cloudscape-design/code-view/highlight/yaml'; import Button from '@cloudscape-design/components/button'; -import Container from '@cloudscape-design/components/container'; import Icon from '@cloudscape-design/components/icon'; -import Table from '@cloudscape-design/components/table'; import Tabs from '@cloudscape-design/components/tabs'; import { mainButtonStyle } from '../../cloudscape-theme'; import { AlternatingDocBlock } from '../../components/AlternatingDocBlock'; import { ArchitectureDiagram } from '../../components/ArchitectureDiagram'; +import { DashedBorder } from '../../components/DashedBorder'; import { highlightTerms } from '../../components/highlightTerms'; +import { gpuOffers } from '../../data/gpus'; import { docsUrl } from '../../routes'; import { backendConfigs, @@ -26,18 +26,6 @@ const keyConcepts = [ { name: 'Services', label: 'Model inference', href: docsUrl('concepts/services'), description: 'Deploy model inference as secure and scalable endpoints.' }, ]; -// Rough per-GPU/hour ranges across backends, in the spirit of `dstack offer --group-by gpu`. -const gpuOffers = [ - { name: 'B300', memory: '288GB', price: '$6.00 - $12.00' }, - { name: 'B200', memory: '192GB', price: '$4.00 - $9.00' }, - { name: 'H200', memory: '141GB', price: '$3.10 - $7.49' }, - { name: 'H100', memory: '80GB', price: '$1.90 - $5.99' }, - { name: 'RTX PRO 6000', memory: '96GB', price: '$1.79 - $3.50' }, - { name: 'A100', memory: '80GB', price: '$1.20 - $3.40' }, - { name: 'A100', memory: '40GB', price: '$0.83 - $2.30' }, - { name: 'L40S', memory: '48GB', price: '$0.80 - $1.40' }, -]; - // Read-only YAML snippet. Line wrapping is left off so one line maps to one row, // which keeps padded snippets equal height across tabs (see padYamlToLines). function YamlCode({ content }: { content: string }) { @@ -48,23 +36,20 @@ function YamlCode({ content }: { content: string }) { ); } -// Scrollable GPU price list. The column header is hidden via CSS (.gpu-scroll thead) -// and the table uses the embedded variant so it sits flush inside the container. +// GPU price list — a plain monospace name/price list in a bordered card, matching the dstack Sky +// "GPU marketplace" pane in the Get started section (same .gs-mkt__row treatment, single source). function GpuMarketplaceTable() { return ( - -
- <>{offer.name} ({offer.memory}), isRowHeader: true }, - { id: 'price', header: '$/hour', cell: offer => offer.price }, - ]} - items={gpuOffers} - /> - - +
+
    + {gpuOffers.map(offer => ( +
  • + {offer.name}{' '}{offer.memory} + {offer.price}/hr +
  • + ))} +
+
); } @@ -140,6 +125,7 @@ function KeyConceptsBlock() { // arrow. Kept as a real (open-in-new-tab / SEO) rather than Cloudscape's // onClick-only ActionCard component. + {concept.label}

{concept.name} diff --git a/website/src/pages/Home/FaqSection.tsx b/website/src/pages/Home/FaqSection.tsx index 66916e8e1..4a9e81d43 100644 --- a/website/src/pages/Home/FaqSection.tsx +++ b/website/src/pages/Home/FaqSection.tsx @@ -50,12 +50,12 @@ export function FaqSection() { title="FAQ" action={ - - + } > diff --git a/website/src/pages/Home/GetStartedSection.tsx b/website/src/pages/Home/GetStartedSection.tsx index 1832baa15..bd09fa1d8 100644 --- a/website/src/pages/Home/GetStartedSection.tsx +++ b/website/src/pages/Home/GetStartedSection.tsx @@ -1,13 +1,79 @@ +import { KeyboardEvent, useEffect, 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 SpaceBetween from '@cloudscape-design/components/space-between'; -import Tabs from '@cloudscape-design/components/tabs'; import { mainButtonStyle } from '../../cloudscape-theme'; -import { AlternatingDocBlock } from '../../components/AlternatingDocBlock'; +import { gpuOffers } from '../../data/gpus'; import { installMethods, maxInstallLines, padYamlToLines } from '../../data/snippets'; import { docsUrl } from '../../routes'; +const GITHUB_API_URL = 'https://api.github.com/repos/dstackai/dstack'; + +// Compact star count: 1340 → "1.3k", 12000 → "12k", 980 → "980" (mirrors the Products menu). +function formatStars(count: number): string { + if (count < 1000) return String(count); + const thousands = count / 1000; + return `${thousands >= 10 ? Math.round(thousands) : Number(thousands.toFixed(1))}k`; +} + +// Product glyphs. GitHub mark doubles as the open-source star badge; cloud / fingerprint mark the +// hosted / self-hosted rows (thin-line, matching the Products menu). +const GithubGlyph = () => ( + +); +const CloudUploadGlyph = () => ( + +); +const FingerprintGlyph = () => ( + +); +const CheckGlyph = () => ( + +); +// Enterprise capability glyphs (thin-line, matching the Products menu). Distinct from the +// fingerprint product mark in the switcher — one icon per capability. +const KeyGlyph = () => ( + +); +const ShieldGlyph = () => ( + +); +const SupportGlyph = () => ( + +); +const AuditGlyph = () => ( + +); + // Read-only shell snippet. Line wrapping is left off so padded snippets stay // equal height across tabs (see padYamlToLines). function ShellCode({ content }: { content: string }) { @@ -18,70 +84,203 @@ function ShellCode({ content }: { content: string }) { ); } -// Closing "Get started" section: the open-source install path, then the hosted/enterprise -// options under "Looking for more?". +type DeployTab = 'oss' | 'sky' | 'ent'; + +// dstack Sky: a sample of the GPU marketplace (price ranges, no provider/region — the point is +// "on-demand GPUs at a price"; same offers as the "Access marketplace GPUs" block) and the clouds +// you can bring instead. Both lists scroll. +const SKY_CLOUDS = ['AWS', 'GCP', 'Azure', 'Kubernetes', 'Lambda', 'RunPod', 'Nebius', 'OCI', 'Vast.ai', 'CloudRift', 'Crusoe', 'SSH fleets']; + +// Enterprise "Extra" capabilities (beyond open-source). One tab for now; more can be added later. +const ENT_CAPS = [ + { icon: , title: 'Single Sign-On (SSO)', sub: 'Okta, Microsoft Entra, Google Workspace' }, + { icon: , title: 'Air-gapped deployment', sub: 'Run fully offline, in your own VPC' }, + { icon: , title: 'Dedicated support', sub: 'Bug fixes, feature prioritization, SLAs' }, + { icon: , title: 'Audit logs & RBAC', sub: 'Fine-grained roles and audit trails' }, +]; + +// Closing "Get started" section. The Products-popup component is reused as a vertical switcher +// (open-source featured + selected by default; dstack Sky / Enterprise as rows). Each tab's detail +// is a bordered box with a footer-bar CTA: open-source shows the install code, Sky shows the GPU +// marketplace alongside the clouds you can bring, Enterprise is a placeholder for now. export function GetStartedSection() { + const [tab, setTab] = useState('oss'); + const [method, setMethod] = useState<(typeof installMethods)[number]['id']>(installMethods[0].id); + const [stars, setStars] = useState(null); + + // Live star count for the open-source tile, fetched once. Best-effort: if the API is rate-limited + // or errors, the badge simply doesn't render. + useEffect(() => { + let active = true; + fetch(GITHUB_API_URL) + .then(response => (response.ok ? response.json() : null)) + .then(data => { + if (active && data && typeof data.stargazers_count === 'number') setStars(data.stargazers_count); + }) + .catch(() => {}); + return () => { + active = false; + }; + }, []); + + // Each option behaves like a tab: click or Enter/Space selects it. + const optionProps = (id: DeployTab) => ({ + role: 'tab', + 'aria-selected': tab === id, + tabIndex: 0, + onClick: () => setTab(id), + onKeyDown: (event: KeyboardEvent) => { + if (event.key === 'Enter' || event.key === ' ') { + event.preventDefault(); + setTab(id); + } + }, + }); + + const activeInstall = installMethods.find(m => m.id === method) ?? installMethods[0]; + return (

Get started

- ({ - id: method.id, - label: method.label, - content: , - }))} - /> - } - title="dstack" - imageFirst - action={ - - - - - } - > - dstack is fully open-source. Install it on your laptop with uv, or deploy it anywhere using - the dstackai/dstack Docker image. - -
-
- - Once it's running, manage your workloads from the CLI, or let agents do it for you. -
- - -
-
-

dstack Sky

-

Hosted by us. Bring your own clouds, or access marketplace GPUs.

- +
+ {/* Left: the popup-style selector. */} +
+
+ + + {stars !== null && ( + {formatStars(stars)} + )} + + + dstack + The open-source control plane that works across clouds, Kubernetes, and on-prem. + +
+ +
+ + + dstack Sky + Access GPU marketplace, or bring your own clouds. Hosted and managed by us. + +
+ +
+ + + Enterprise + Self-hosted with SSO, air-gapped setup, dedicated support, and more. + +
+
+ + {/* Open-source: install-method tabs + read-only code + footer CTA bar. */} + {tab === 'oss' && ( +
+
+
+ {installMethods.map(m => ( + + ))} +
+
+ +
+
+ + Use with your own clouds, Kubernetes, and on-prem clusters + Use with your own clouds & clusters + + +
+
+
+ )} + + {/* dstack Sky: GPU marketplace + bring-your-own-clouds, equal columns; footer CTA. */} + {tab === 'sky' && ( +
+
+ {/* Two panes with tab-styled headers. Source order is header→list, header→list, so on + mobile (single column) each header sits directly above its own list; on desktop a + 2-row grid (auto-flow column) puts both headers in a row and both lists beneath. */} +
+
GPU marketplace
+
+
    + {gpuOffers.map(gpu => ( +
  • + {gpu.name}{' '}{gpu.memory} + {gpu.price}/hr +
  • + ))} +
+
+
Bring your own clouds
+
+
    + {SKY_CLOUDS.map(cloud => ( +
  • {cloud}
  • + ))} +
+
+
+
+ + Sign up to get $5 credit for on-demand and spot instances + Sign up to get $5 in credits + +
+ + +
+
+
+
+ )} + + {/* Enterprise: the open-source box's tabbed shell, with a single "Extra" tab for now. */} + {tab === 'ent' && ( +
+
+
+ Self-managed +
+
+
    + {ENT_CAPS.map(cap => ( +
  • + {cap.icon} + + {cap.title} + {cap.sub} + +
  • + ))} +
-
-
-
-

Enterprise

-

Self-hosted with SSO, air-gapped setup, dedicated support, and more.

- +
+ + Talk to our team to get answers and a free trial + Talk to our team for a free trial + +
-
+ - } - title="Looking for more?" - > - We can host and operate dstack for you, or back your own self-hosted deployment with enterprise security and support. -
+ )} +
); } diff --git a/website/src/pages/Home/HomePage.tsx b/website/src/pages/Home/HomePage.tsx index d44dc09cb..96c414001 100644 --- a/website/src/pages/Home/HomePage.tsx +++ b/website/src/pages/Home/HomePage.tsx @@ -1,30 +1,22 @@ import Button from '@cloudscape-design/components/button'; +import { useLayoutContext } from '../../App'; import { heroButtonStyle } from '../../cloudscape-theme'; +import { HeroSquircle } from '../../components/HeroSquircle'; import { highlightTerms } from '../../components/highlightTerms'; -import { images, ThemedImage } from '../../data/images'; -import { docsUrl } from '../../routes'; +import { DOCS_URL } from '../../routes'; import { ExploreSection } from './ExploreSection'; import { FaqSection } from './FaqSection'; import { GetStartedSection } from './GetStartedSection'; import { TrustedBySection } from './TrustedBySection'; -// Hero artwork: both variants are rendered and CSS shows the one matching the theme. -function ThemedHeroImage({ image }: { image: ThemedImage }) { - return ( - <> - - - - ); -} - export function HomePage() { + const { theme } = useLayoutContext(); return (
@@ -41,11 +33,19 @@ export function HomePage() { )}

- -
@@ -61,12 +61,6 @@ export function HomePage() { - - {/* On phones the hero artwork is relocated down here, just above the footer - (the top instance is hidden at the same breakpoint). */} -
); } diff --git a/website/src/styles.css b/website/src/styles.css index 62ab5b9d4..40b7f5784 100644 --- a/website/src/styles.css +++ b/website/src/styles.css @@ -41,13 +41,6 @@ --doc-right-gap: 2.5rem; --doc-gap: var(--doc-right-gap); --doc-main-width: calc(var(--doc-article-width) + var(--doc-right-gap) + var(--doc-rail-width)); - --hero-gradient: linear-gradient( - 145deg, - rgba(176, 65, 255, 0.38) 0%, - rgba(96, 106, 255, 0.33) 34%, - rgba(38, 59, 188, 0.06) 62%, - rgba(255, 255, 255, 0) 100% - ); } :root[data-theme='dark'] { @@ -61,13 +54,6 @@ --cs-hover: rgba(242, 243, 243, 0.05); /* softened from 0.085 */ --cs-btn-hover: #e2e5e8; --cs-seg-divider: rgba(0, 0, 0, 0.18); /* split-button segment divider (dark line on the light fill) */ - --hero-gradient: linear-gradient( - 145deg, - rgba(159, 98, 255, 0.2) 0%, - rgba(92, 105, 255, 0.13) 36%, - rgba(21, 36, 112, 0.12) 66%, - rgba(15, 20, 29, 0) 100% - ); } * { @@ -323,7 +309,7 @@ p { .site-hover-menu__caret { width: 12px; height: 12px; - opacity: 0.7; + opacity: 1; /* full text colour — the caret reads as black like the menu label */ transition: transform 0.15s ease; } .site-hover-menu__trigger[aria-expanded='true'] .site-hover-menu__caret { @@ -345,16 +331,45 @@ p { border-radius: 12px; } -/* Featured open-source panel — brand gradient; the whole panel links to the docs. */ +/* Featured open-source panel — brand gradient; the whole panel links to the docs. Carries an icon + tile (like the Sky/Enterprise rows) with the star count beneath, so all three read consistently. */ .site-products-menu__feat { position: relative; - display: block; + display: flex; + align-items: flex-start; + gap: 12px; padding: 14px; border-radius: 10px; - background: linear-gradient(135deg, #1e40ff, #8b2fd0); + background: linear-gradient(135deg, #002aff, #002aff, #e165fe); color: #fff; text-decoration: none; } +/* Left column: the icon tile (same as the Sky/Enterprise rows) with the star count beneath it. */ +.site-products-menu__feat-iccol { + display: flex; + flex-direction: column; + align-items: center; + gap: 6px; + flex: 0 0 auto; +} +.site-products-menu__feat-body { + flex: 1 1 auto; + min-width: 0; +} +.site-products-menu__feat-ic { + display: inline-flex; + align-items: center; + justify-content: center; + width: 34px; + height: 34px; + border-radius: 9px; + border: 0.5px solid rgba(255, 255, 255, 0.5); + color: #fff; +} +.site-products-menu__feat-ic svg { + width: 20px; + height: 20px; +} .site-products-menu__feat-name { display: block; color: #fff; @@ -386,23 +401,13 @@ p { background: #e9ebef; } -/* Live GitHub star count, top-right of the featured panel (only shown once the API responds). */ +/* Live GitHub star count, centered beneath the icon tile (only shown once the API responds). */ .site-products-menu__gh { - position: absolute; - top: 14px; - right: 14px; - display: inline-flex; - align-items: center; - gap: 5px; - color: #fff; - font-size: 12.5px; + color: rgba(255, 255, 255, 0.88); + font-size: 12px; font-weight: 600; font-variant-numeric: tabular-nums; } -.site-products-menu__gh svg { - width: 14px; - height: 14px; -} /* dstack Sky & Enterprise rows. */ .site-products-menu__list { @@ -463,25 +468,21 @@ p { background: var(--cs-bg); } -.home-hero::before { - position: absolute; - top: calc(-1 * var(--nav-height)); - right: 0; - bottom: -16rem; - left: 0; - z-index: 0; - content: ''; - pointer-events: none; - background: var(--hero-gradient); - -webkit-mask-image: linear-gradient(180deg, #000 0%, #000 46%, transparent 88%); - mask-image: linear-gradient(180deg, #000 0%, #000 46%, transparent 88%); -} - .home-hero__content { position: relative; z-index: 1; padding: 96px 0 64px; color: var(--cs-text); + /* The content column overlaps the absolutely-positioned art box on the right; let pointer + events fall through its empty areas so the live hero scene stays hoverable. The + interactive children (text + CTAs) opt back in below. */ + pointer-events: none; +} + +.home-hero h2, +.home-hero p, +.home-hero__actions { + pointer-events: auto; } .home-hero h2 { @@ -524,34 +525,14 @@ p { height: 100%; } -/* Art occupies the second content column: right edge at the frame edge, width of one - column, so its left edge lines up with the second column of the blocks below. */ -.hero-slice { +/* Live squircle hero scene occupies the second content column: right edge at the frame + edge, width of one column, so it lines up with the second column of the blocks below. + The SVG inside is width:100%, so it scales to this box. */ +.hero-squircle { position: absolute; - top: 68px; + top: 40px; right: 0; width: calc((100% - var(--doc-gap)) / 2); - height: auto; - pointer-events: none; - user-select: none; -} - -.hero-slice--dark { - display: none; -} - -:root[data-theme='dark'] .hero-slice--light { - display: none; -} - -:root[data-theme='dark'] .hero-slice--dark { - display: block; -} - -/* Phone-only copy of the hero artwork, placed just above the footer (see HomePage). The - light/dark swap is handled by the shared .hero-slice--light/--dark rules above. */ -.home-hero-mobile-art { - display: none; } /* Lifted above the footer gradient so the content/cards stay clean while the gradient @@ -640,11 +621,6 @@ p { font-size: 36px; } -/* Extra breathing room before "Looking for more?" (the second block in this section). */ -#resources .doc-alternating + .doc-alternating { - margin-top: 105px; -} - /* Keep the lower sections flowing as one continuous sequence (no dividers between them). */ #faq, #trusted-by { @@ -924,25 +900,32 @@ p { background-color: transparent !important; } -/* Cap the marketplace table height so the rows scroll inside the container. - Tuned so the block matches the height of the tab blocks above. */ -.gpu-scroll { - max-height: 307px; - overflow-y: auto; +/* "Access marketplace GPUs" price list — the same monospace row treatment as the dstack Sky + marketplace pane (.gs-mkt__row), inside a bordered card matching the landing's other boxes. */ +.gpu-mkt { + border: 1px solid var(--cs-border); + border-radius: 12px; + background: var(--cs-bg); + padding: 16px 20px; + overflow: hidden; +} +.gpu-mkt__list { + list-style: none; + margin: 0; + padding: 0; + display: flex; + flex-direction: column; + gap: 12px; } /* Cloudscape buttons render as
when given href; exclude them from the docs underline rule. Higher specificity than `.docs-article a` so the reset wins regardless of source order. */ .docs-article .doc-action a, -.docs-article .figma-card a { +.docs-article .figma-card a, +.docs-article .gs-deploy a { text-decoration: none !important; } -/* No column header for the marketplace list (Table has no prop to omit it). */ -.gpu-scroll thead { - display: none; -} - .doc-action { margin-top: 20px; } @@ -1268,10 +1251,10 @@ p { .concept-grid { display: grid; grid-template-columns: repeat(2, 1fr); + grid-auto-rows: 1fr; /* equal-height cards across both rows */ gap: var(--card-gap); - padding: 8px; - border: 1px solid var(--cs-border); - border-radius: 12px; + /* No outer border/padding — each card carries its own dotted outline (DashedBorder / .arch-dash), + matching the architecture diagram's pills. */ } /* Each concept card is a whole-card link to the docs: an uppercase label (with a small gradient @@ -1381,6 +1364,365 @@ p { margin-top: 0; } +/* "Get started" deployment switcher. The Products-popup component reused as a vertical switcher: + a bordered selector panel on the left whose options carry a caption + description, with the + selected option filled by the brand gradient. Open-source is featured (bigger, with a live star + count and a Documentation button) and selected by default; dstack Sky / Enterprise are compact + rows. The right column shows the selection's detail — install code for open-source, an included- + features list + CTA for the hosted / self-hosted tiers. align-items: stretch keeps the selector + and the detail equal height and top-aligned. */ +.gs-deploy { + display: grid; + grid-template-columns: 1fr 2fr; /* selector 1/3, detail 2/3 */ + align-items: start; /* the box sets its own (taller) height; the rail sits at the top, not stretched */ + gap: 36px; + margin-top: 8px; +} + +/* Left: the selector — a bare column of individually-bordered option cards (no panel chrome). */ +.gs-rail { + display: flex; + flex-direction: column; + gap: 10px; +} +.gs-opt { + position: relative; + display: block; + width: 100%; + border: 0.5px solid transparent; /* non-active: no visible border (space reserved so selecting never shifts) */ + border-radius: 10px; + background: transparent; + color: var(--cs-text); + font: inherit; + text-align: left; + cursor: pointer; + transition: background-color 0.15s ease, border-color 0.15s ease; +} +.gs-opt:not(.gs-opt--on):hover { + background: var(--cs-hover); +} +.gs-opt--on, +.gs-opt--on:hover { + background: linear-gradient(135deg, #002aff, #002aff, #e165fe); + color: #fff; +} +.gs-opt__name { + display: block; + font-size: 17px; + font-weight: 600; +} +.gs-opt__desc { + display: block; + margin-top: 4px; + font-size: 13px; + line-height: 1.5; + color: color-mix(in srgb, var(--cs-text) 68%, var(--cs-muted)); +} +.gs-opt--on .gs-opt__desc { + color: rgba(255, 255, 255, 0.88); +} + +/* Open-source: the featured card — bigger, but now with an icon tile (like the Sky/Enterprise rows) + and the star count moved beneath the name, so all three controls read consistently. */ +.gs-opt--feat { + padding: 20px; + display: flex; + align-items: flex-start; + gap: 14px; +} +/* Left column: the icon tile with the star count centered beneath it (the icon itself lays out + exactly like the Sky / Enterprise icon tiles). */ +.gs-opt__icwrap { + display: flex; + flex-direction: column; + align-items: center; + gap: 6px; + flex: 0 0 auto; +} +.gs-opt__stars { + font-size: 12px; + font-weight: 600; + font-variant-numeric: tabular-nums; + color: inherit; +} + +/* dstack Sky / Enterprise: compact rows with an icon. */ +.gs-opt--row { + display: flex; + align-items: flex-start; + gap: 12px; + padding: 13px 14px; +} +.gs-opt__ic { + display: inline-flex; + align-items: center; + justify-content: center; + flex: 0 0 auto; + width: 34px; + height: 34px; + border-radius: 9px; + border: 0.5px solid var(--cs-border); + color: inherit; +} +.gs-opt__ic svg { + width: 20px; + height: 20px; +} +.gs-opt--on .gs-opt__ic { + border-color: rgba(255, 255, 255, 0.5); +} +.gs-opt__body { + flex: 1 1 auto; + min-width: 0; +} + +/* Right: the selection's detail. */ +.gs-detail { + min-width: 0; +} +/* The detail box — shared shell for all three tabs: a bordered container with a fixed height (so its + own scrollable lists never inflate it), 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. */ +.gs-box { + height: 320px; + display: flex; + flex-direction: column; + background: var(--cs-bg); + border: 1px solid var(--cs-border); /* matches the landing's cards (.media-card / .figma-card) */ + border-radius: 12px; + overflow: hidden; +} +.gs-boxfoot { + display: flex; + align-items: center; + justify-content: space-between; + gap: 14px; + padding: 13px 16px 13px 20px; + border-top: 1px solid color-mix(in srgb, var(--cs-border) 14%, transparent); +} +.gs-foot__note { + font-size: 13px; + color: var(--cs-muted); + min-width: 0; /* allow the note to shrink/wrap so it shares the row with the CTA on narrow screens */ +} +/* Footer note has a full (desktop) and a shorter (mobile) wording; the mobile breakpoint swaps them. */ +.gs-foot__short { + display: none; +} +/* The footer CTA(s) keep their width (never shrink/wrap mid-word); the note takes the rest. */ +.gs-boxfoot > [class*='awsui_button'] { + flex: 0 0 auto; +} +.gs-foot__cta { + display: flex; + gap: 8px; + flex: 0 0 auto; +} + +/* Tab strip — shared by the open-source code box (uv / pip / Docker) and dstack Sky (GPU + marketplace / bring your own clouds), so both boxes read the same: tabs on top, content, footer. */ +.gs-tabs { + display: flex; + gap: 2px; + padding: 6px 8px 0; + border-bottom: 1px solid color-mix(in srgb, var(--cs-border) 14%, transparent); +} +.gs-tab { + padding: 15px 14px; + border: 0; + border-radius: 8px 8px 0 0; + background: transparent; + color: var(--cs-muted); + font-family: inherit; + font-size: 16px; /* match the page's other (Cloudscape) tab groups: 16px / 700 */ + font-weight: 700; + cursor: pointer; + transition: background-color 0.15s ease, color 0.15s ease; +} +.gs-tab:not(.gs-tab--on):hover { + color: var(--cs-text); + background: var(--cs-hover); /* match the Cloudscape tabs' hover tint */ +} +.gs-tab--on { + color: var(--cs-text); + background: var(--cs-panel); +} + +/* Open-source: read-only code fills the body. */ +.gs-codebody { + flex: 1 1 auto; + min-height: 0; + padding: 10px 12px; + overflow: hidden; +} + +/* dstack Sky: two panes (GPU marketplace | bring your own clouds). Source order is header→list, + header→list. Desktop: a 2-row grid with column auto-flow, so the two headers land in row 1 (the + tab strip) and the two lists in row 2 (the content). Mobile (media query): a single column, so + each header sits directly above its own list. The chips reuse the open-source tab look. */ +.gs-sky { + display: grid; + grid-template-rows: auto 1fr; + grid-auto-flow: column; + grid-auto-columns: 1fr; + flex: 1 1 auto; + min-height: 0; +} +.gs-skyhalf { + min-width: 0; + padding: 6px 0 0 8px; /* chip text (chip pad 14) lands at 22px, matching the column content below */ + border-bottom: 1px solid color-mix(in srgb, var(--cs-border) 14%, transparent); /* under-tab divider */ +} +.gs-skytab { + display: inline-block; + padding: 15px 14px; + border-radius: 8px 8px 0 0; + background: var(--cs-panel); + color: var(--cs-text); + font-size: 16px; /* match the page's other tab groups: 16px / 700 */ + font-weight: 700; +} +.gs-col { + min-width: 0; + min-height: 0; /* grid item: allow the 1fr row to cap it so the list scrolls instead of overflowing */ + display: flex; + flex-direction: column; + padding: 14px 22px 0; +} +/* Vertical divider between the two lists only (the 2nd list pane), not under the headers. */ +.gs-sky .gs-col ~ .gs-col { + border-left: 1px solid color-mix(in srgb, var(--cs-border) 14%, transparent); +} +.gs-col__list { + list-style: none; + margin: 0; + padding: 0 2px 14px 0; + flex: 1 1 auto; + min-height: 0; + display: flex; + flex-direction: column; + gap: 12px; + overflow-y: auto; + /* soften the cut-off row so the scroll reads cleanly into the footer */ + -webkit-mask-image: linear-gradient(to bottom, #000 calc(100% - 18px), transparent); + mask-image: linear-gradient(to bottom, #000 calc(100% - 18px), transparent); +} +.gs-mkt__row { + display: flex; + align-items: baseline; + justify-content: space-between; + gap: 12px; +} +.gs-mkt__g { + font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; + font-size: 13px; + font-weight: 400; /* only the GPU name is emphasized (below); VRAM stays regular */ +} +.gs-mkt__name { + font-weight: 600; /* emphasize the GPU name by weight, not size */ +} +.gs-mkt__p { + font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; + font-size: 13px; + font-weight: 400; /* price is not emphasized — only the GPU name is */ + white-space: nowrap; +} +.gs-cloud { + display: flex; + align-items: center; + gap: 10px; + font-size: var(--font-small); + line-height: 1.3; +} + +/* Enterprise: the "Extra" capabilities — a 2×2 grid of capability rows (icon + title + one-line + detail), centered in the body. No card borders. */ +.gs-entbody { + flex: 1 1 auto; + min-height: 0; + display: flex; + flex-direction: column; + justify-content: center; + padding: 20px 24px; +} +.gs-caps { + list-style: none; + margin: 0; + padding: 0; + display: grid; + grid-template-columns: 1fr 1fr; + gap: 18px 26px; +} +.gs-cap { + display: flex; + align-items: flex-start; + gap: 11px; +} +.gs-cap__ic { + flex: 0 0 auto; + margin-top: 1px; + color: #8b2fd0; +} +:root[data-theme='dark'] .gs-cap__ic { + color: #c08bff; +} +.gs-cap__ic svg { + width: 20px; + height: 20px; +} +.gs-cap__b { + display: flex; + flex-direction: column; + min-width: 0; +} +.gs-cap__t { + font-size: 14px; + font-weight: 600; + line-height: 1.3; +} +.gs-cap__s { + font-size: 12.5px; + color: var(--cs-muted); + margin-top: 2px; + line-height: 1.4; +} + +/* Brand-purple check (Sky clouds). */ +.gs-check__mark { + width: 17px; + height: 17px; + flex: 0 0 auto; + margin-top: 1px; + color: #8b2fd0; +} +:root[data-theme='dark'] .gs-check__mark { + color: #c08bff; +} + +@media (max-width: 860px) { + .gs-deploy { grid-template-columns: 1fr; gap: 24px; } + /* Stacked: the box flows naturally to fit its content (no fixed-height scroll). */ + .gs-box { height: auto; } + /* Sky: single column so each header sits directly above its own list (header→list→header→list). */ + .gs-sky { display: flex; flex-direction: column; } + .gs-sky .gs-col ~ .gs-col { border-left: 0; } + /* Separate the second pane (its header follows the first list). */ + .gs-sky .gs-col + .gs-skyhalf { margin-top: 12px; } + .gs-col__list { overflow-y: visible; -webkit-mask-image: none; mask-image: none; } + .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; } + /* Footer stays a single row (note + CTA, like desktop), never split onto separate rows. The note + uses its shorter wording and the CTAs use tighter padding so the note keeps room beside them. */ + .gs-boxfoot { align-items: center; gap: 10px; } + .gs-boxfoot [class*='awsui_button'] { padding-inline: 14px !important; } + /* Sky has two CTAs; on a phone keep only the primary ("Sign up") so the note fits beside it. */ + .gs-foot__cta > [class*='awsui_button']:first-child { display: none !important; } + .gs-foot__full { display: none; } + .gs-foot__short { display: inline; } +} + .right-rail-block { margin-top: 32px; padding-top: 28px; @@ -1710,8 +2052,8 @@ p { opacity: 0.85; } - .hero-slice { - top: 24px; + .hero-squircle { + top: 12px; } .feature-card-grid, @@ -1834,9 +2176,10 @@ p { border-bottom: 0.5px solid var(--cs-border); background: var(--cs-bg, #ffffff); } -} -@media (max-width: 640px) { + /* Below 1024 (where the nav also collapses to a hamburger) the side-by-side hero would + overlap the headline, so stack it: headline + CTAs first, then the squircle below them + in normal flow, centered — on tablet and phone alike. */ .home-hero { display: flex; flex-direction: column; @@ -1844,17 +2187,37 @@ p { .home-hero__content { order: 1; - padding: 64px 0 24px; + padding-bottom: 8px; } - /* On phones the hero artwork is dropped entirely (not worth the vertical space): hide both the - top instance and the relocated bottom copy. */ .home-hero__art { - display: none; + order: 2; + position: static; + height: auto; + opacity: 1; + padding-bottom: 8px; } - .home-hero-mobile-art { - display: none; + .home-hero__art-frame { + height: auto; + } + + .hero-squircle { + position: static; + width: min(100%, 460px); + margin: 8px auto 0; + } +} + +@media (max-width: 640px) { + .home-hero { + display: flex; + flex-direction: column; + } + + .home-hero__content { + order: 1; + padding: 64px 0 24px; } /* Hero CTAs share one full-width row on phones — two equal columns spanning the width. */ From 2cf5d2374ee6f9a60d3e555d076a94fcc38da63d Mon Sep 17 00:00:00 2001 From: Andrey Cheptsov Date: Sun, 28 Jun 2026 23:38:23 +0200 Subject: [PATCH 6/8] Drop per-layer exponent override on the hero squircle Remove geometry: { exponent: 24 } from all three layers so they use the scene's default corner roundness. Co-Authored-By: Claude Opus 4.8 (1M context) --- website/src/components/HeroSquircle.tsx | 3 --- 1 file changed, 3 deletions(-) diff --git a/website/src/components/HeroSquircle.tsx b/website/src/components/HeroSquircle.tsx index 3f9cb8467..f6c5e9939 100644 --- a/website/src/components/HeroSquircle.tsx +++ b/website/src/components/HeroSquircle.tsx @@ -20,7 +20,6 @@ const HERO_LAYERS: SquircleLayerConfig[] = [ id: 'layer-1', visible: true, offset: { x: 0, y: 176 }, - geometry: { exponent: 24 }, base: { material: 'wireframe', paletteId: '15', @@ -40,7 +39,6 @@ const HERO_LAYERS: SquircleLayerConfig[] = [ id: 'layer-2', visible: true, offset: { x: 0, y: 88 }, - geometry: { exponent: 24 }, base: { material: 'transparent', paletteId: '20', @@ -60,7 +58,6 @@ const HERO_LAYERS: SquircleLayerConfig[] = [ id: 'layer-3', visible: true, offset: { x: 0, y: 0 }, - geometry: { exponent: 24 }, base: { material: 'solid', paletteId: '20', From 6927f0786f270e99023ba7b921f52b93454c516a Mon Sep 17 00:00:00 2001 From: Andrey Cheptsov Date: Mon, 29 Jun 2026 01:10:34 +0200 Subject: [PATCH 7/8] Bump @dstackai/sqircle to 0.1.9 (Safari grain fix + perf) Fixes the grain overlay rendering as an unmasked gray box on iOS Safari, and includes upstream squircle performance improvements. Co-Authored-By: Claude Opus 4.8 (1M context) --- website/package-lock.json | 8 ++++---- website/package.json | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/website/package-lock.json b/website/package-lock.json index e92b87475..feba06fc1 100644 --- a/website/package-lock.json +++ b/website/package-lock.json @@ -11,7 +11,7 @@ "@cloudscape-design/code-view": "^3.0.142", "@cloudscape-design/components": "^3.0.0", "@cloudscape-design/global-styles": "^1.0.0", - "@dstackai/sqircle": "^0.1.6", + "@dstackai/sqircle": "^0.1.9", "react": "^18.2.0", "react-dom": "^18.2.0", "react-router-dom": "^6.30.4" @@ -485,9 +485,9 @@ } }, "node_modules/@dstackai/sqircle": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/@dstackai/sqircle/-/sqircle-0.1.6.tgz", - "integrity": "sha512-OJ5e1lqBre4F/ObFAjonQFjbXEH9evZjTX82HK7MvVyAu3FmccF69EY5NLzXrjhmoFDYjp7Lx7KbsR93zjaDkQ==", + "version": "0.1.9", + "resolved": "https://registry.npmjs.org/@dstackai/sqircle/-/sqircle-0.1.9.tgz", + "integrity": "sha512-/NrPXUILifHN8u8uGTZNVX6EK4BVut1fmhdHwXvOsvjJ9fU17ib6NkjAzp2PQSmCGUtnhin9crTxqV9iuZG/sg==", "license": "MIT", "peerDependencies": { "react": ">=18.0.0", diff --git a/website/package.json b/website/package.json index 176b0be91..09cff3cbb 100644 --- a/website/package.json +++ b/website/package.json @@ -12,7 +12,7 @@ "@cloudscape-design/code-view": "^3.0.142", "@cloudscape-design/components": "^3.0.0", "@cloudscape-design/global-styles": "^1.0.0", - "@dstackai/sqircle": "^0.1.6", + "@dstackai/sqircle": "^0.1.9", "react": "^18.2.0", "react-dom": "^18.2.0", "react-router-dom": "^6.30.4" From 770c2774eacee05b3a78a19458be843a7d37f405 Mon Sep 17 00:00:00 2001 From: Andrey Cheptsov Date: Mon, 29 Jun 2026 10:03:48 +0200 Subject: [PATCH 8/8] Remove "Reserve a cluster" CTA from the Get-started Sky tab Leaves "Sign up" as the single footer CTA (matching the other tabs); drops the now-unused .gs-foot__cta wrapper + its mobile first-child hide rule. To be re-added later. Co-Authored-By: Claude Opus 4.8 (1M context) --- website/src/pages/Home/GetStartedSection.tsx | 5 +---- website/src/styles.css | 7 ------- 2 files changed, 1 insertion(+), 11 deletions(-) diff --git a/website/src/pages/Home/GetStartedSection.tsx b/website/src/pages/Home/GetStartedSection.tsx index bd09fa1d8..d388cf362 100644 --- a/website/src/pages/Home/GetStartedSection.tsx +++ b/website/src/pages/Home/GetStartedSection.tsx @@ -241,10 +241,7 @@ export function GetStartedSection() { Sign up to get $5 credit for on-demand and spot instances Sign up to get $5 in credits -
- - -
+ diff --git a/website/src/styles.css b/website/src/styles.css index 40b7f5784..e5335dc98 100644 --- a/website/src/styles.css +++ b/website/src/styles.css @@ -1514,11 +1514,6 @@ p { .gs-boxfoot > [class*='awsui_button'] { flex: 0 0 auto; } -.gs-foot__cta { - display: flex; - gap: 8px; - flex: 0 0 auto; -} /* Tab strip — shared by the open-source code box (uv / pip / Docker) and dstack Sky (GPU marketplace / bring your own clouds), so both boxes read the same: tabs on top, content, footer. */ @@ -1717,8 +1712,6 @@ p { uses its shorter wording and the CTAs use tighter padding so the note keeps room beside them. */ .gs-boxfoot { align-items: center; gap: 10px; } .gs-boxfoot [class*='awsui_button'] { padding-inline: 14px !important; } - /* Sky has two CTAs; on a phone keep only the primary ("Sign up") so the note fits beside it. */ - .gs-foot__cta > [class*='awsui_button']:first-child { display: none !important; } .gs-foot__full { display: none; } .gs-foot__short { display: inline; } }