diff --git a/.github/workflows/build-docs.yml b/.github/workflows/build-docs.yml index 6ca99d764a..9df8040d4d 100644 --- a/.github/workflows/build-docs.yml +++ b/.github/workflows/build-docs.yml @@ -1,4 +1,8 @@ -name: Build Docs +name: Build Site + +# Reusable build of the full site: the React landing (website/) overlaid onto the +# MkDocs docs + blog build, via scripts/docs/build_site.sh. Produces the `site` artifact +# consumed by docs.yaml (deploy) and build.yml (PR build check). on: workflow_call: @@ -11,13 +15,18 @@ jobs: - uses: astral-sh/setup-uv@v5 with: python-version: 3.11 + - uses: actions/setup-node@v4 + with: + node-version: 20 + cache: npm + cache-dependency-path: website/package-lock.json - name: Install dstack run: | uv sync --extra server - name: Build run: | sudo apt-get update && sudo apt-get install -y libcairo2-dev libfreetype6-dev libffi-dev libjpeg-dev libpng-dev libz-dev - uv run mkdocs build -s + ./scripts/docs/build_site.sh - uses: actions/upload-artifact@v4 with: name: site diff --git a/.github/workflows/docs.yaml b/.github/workflows/docs.yaml index 3e0d5f3a75..78212c6fda 100644 --- a/.github/workflows/docs.yaml +++ b/.github/workflows/docs.yaml @@ -1,4 +1,7 @@ -name: Build & Deploy Docs +name: Build & Deploy Site + +# Builds the full site (React landing + MkDocs docs + blog, see build-docs.yml) and +# cross-repo deploys it to the GitHub Pages repo serving dstack.ai. on: workflow_dispatch: diff --git a/.justfile b/.justfile index efa8c87f61..c419685edc 100644 --- a/.justfile +++ b/.justfile @@ -8,6 +8,7 @@ # * runner/.justfile – Building and uploading dstack runner and shim # * frontend/.justfile – Building and running the frontend # * mkdocs/.justfile – Building and previewing the docs site +# * website/.justfile – Building and previewing the React landing page default: @just --list @@ -19,3 +20,5 @@ import "runner/.justfile" import "frontend/.justfile" import "mkdocs/.justfile" + +import "website/.justfile" diff --git a/contributing/DOCS.md b/contributing/DOCS.md index 663e5c4c77..a0b034a181 100644 --- a/contributing/DOCS.md +++ b/contributing/DOCS.md @@ -1,5 +1,11 @@ # Documentation setup +> **The dstack.ai site has three parts on one origin:** the **landing** page (`/`) is a React +> app in [`website/`](../website); the **docs** (`/docs`) and **blog** (`/blog`) are built with +> MkDocs from `mkdocs/`. This guide covers the **docs and blog** (MkDocs). For the landing and +> for building everything together, see [The landing page](#the-landing-page-website) and +> [Building the whole site](#building-the-whole-site) below. + ## 1. Clone the repo: ```shell @@ -36,7 +42,7 @@ uv run pre-commit install ## 5. Preview documentation -To preview the documentation, run the follow command: +To preview the **docs and blog** (MkDocs), run the follow command: ```shell uv run mkdocs serve --livereload -s @@ -44,12 +50,44 @@ uv run mkdocs serve --livereload -s The `--livereload` flag is required to work around live-reload bugs in recent `mkdocs` versions. +This serves the docs and blog only. The landing page (`/`) is a separate React app — when you +run `mkdocs serve` on its own, `/` simply redirects to `/docs/`. To work on the landing, see +[The landing page](#the-landing-page-website) below. + If you want to build static files, you can use the following command: ```shell uv run mkdocs build -s ``` +## The landing page (website/) + +The landing page at `/` is a React (Vite) app in [`website/`](../website), not MkDocs. It has +its own `package.json`/`node_modules`. Preview it on its own (requires Node 20+): + +```shell +just website-dev # Vite dev server on http://127.0.0.1:5173 +``` + +Docs/blog links on the landing resolve same-origin (`/docs`, `/blog`), which 404 in standalone +dev. Point them at a live site while iterating: `just website-dev https://dstack.ai`. + +The `/old` route is kept as a template for building future product pages (reachable in dev; not +part of the production deploy). Google Analytics and the social/OG image reuse the same property +and MkDocs-generated card as the rest of the site. + +## Building the whole site + +CI builds the landing and the MkDocs docs/blog and overlays them into a single `site/`: + +```shell +just site-build # website/dist + `mkdocs build` -> ./site (scripts/docs/build_site.sh) +just site-serve # preview the combined site on http://127.0.0.1:8001 +``` + +In the combined build the React `index.html` owns `/`, while MkDocs serves `/docs`, `/blog`, and +the shared `/assets`. This is what the `Build & Deploy Site` workflow deploys. + ## Documentation build system The documentation uses a custom build system with MkDocs hooks to generate various files dynamically. @@ -141,7 +179,7 @@ we should not reintroduce per-tag OpenAPI files unless there is a concrete reaso ``` mkdocs/ # docs_dir for the mkdocs site -├── index.md # Homepage +├── index.md # Redirects to /docs/ (the landing "/" is the React app in website/) ├── docs/ # /docs/ URL section │ ├── index.md # Getting started │ ├── installation.md @@ -157,7 +195,13 @@ mkdocs/ # docs_dir for the mkdocs site ├── layouts/ # Social card layouts └── assets/ # Stylesheets, images, fonts +website/ # React (Vite) landing page — served at "/" +├── index.html # Entry; title, OG/meta, Google Analytics +├── src/ # App, pages (Home, Old), components, routes +└── public/static/ # Landing assets (namespaced to avoid clashing with /assets) + scripts/docs/ +├── build_site.sh # Build landing + docs/blog and overlay into ./site ├── hooks.py # MkDocs build hooks ├── gen_llms_files.py # llms.txt generation ├── gen_schema_reference.py # Schema expansion diff --git a/mkdocs/index.md b/mkdocs/index.md index 571c05ae6e..50475eafce 100644 --- a/mkdocs/index.md +++ b/mkdocs/index.md @@ -1,8 +1,11 @@ --- -template: home.html +# The landing page ("/") is now the React app in website/. When the docs are served on +# their own (e.g. `mkdocs serve`), this page redirects to /docs/. The title/description +# below are only used to render the social card that the React landing reuses as its OG +# image (assets/images/social/index.png). +template: redirect.html title: The orchestration stack for AI infrastructure -hide: - - navigation - - toc - - footer +description: dstack is a unified control plane for GPU provisioning and orchestration that works with any GPU cloud, Kubernetes, or on-prem clusters. +search: + exclude: true --- diff --git a/mkdocs/overrides/assets/images/quotes/spott.jpg b/mkdocs/overrides/assets/images/quotes/spott.jpg deleted file mode 100644 index cc9e0ae625..0000000000 Binary files a/mkdocs/overrides/assets/images/quotes/spott.jpg and /dev/null differ diff --git a/mkdocs/overrides/home.html b/mkdocs/overrides/home.html deleted file mode 100644 index c876938795..0000000000 --- a/mkdocs/overrides/home.html +++ /dev/null @@ -1,1011 +0,0 @@ -{% extends "landing.html" %} - -{% block scripts %} -{{ super() }} - - - -{% endblock %} - -{% block content %} -
-
-
-
-

The orchestration stack
for AI infrastructure

- -

- dstack is a unified control plane for GPU provisioning and orchestration that works with any GPU cloud, Kubernetes, or on-prem clusters. - It streamlines development, training, and inference, and is compatible with any hardware, open-source tools, and frameworks. -

-
- - -
- Finally, an orchestration stack that doesn’t suck. -
-
- -
-
-
-

One control plane for AI compute

-

- Managing AI infrastructure requires first-class primitives for accelerator provisioning, - workload scheduling, and observability across clouds, clusters, and open-source frameworks. -

- -

- dstack unifies fleets, dev environments, tasks, services, - volumes, and gateways in one control plane for AI workloads. -

- -

- It’s built for containerized AI workloads with a simple CLI, UI, and API. - No Kubernetes or Slurm hassle required. -

- - - -
- -
- -
-
-
- -
-
-
- -
- -
-

Provision GPU fleets across clouds

- -
- - - - - - - - - - - - -
- -

- dstack provisions GPU VMs directly through cloud APIs—no Kubernetes needed. -

- -

- If you already have a Kubernetes cluster, dstack can manage it too. -

- -

- Once a backend fleet is created, dstack will let you run - dev environments, tasks, and services on this fleet. -

- -

- - Backends - - - - -

-
-
-
- -
-
-
-

Bring your own clusters

- -

- Have bare-metal servers or pre-provisioned VMs? Use SSH fleets to connect them to dstack. -

- -

- Just provide SSH credentials and host addresses, and dstack creates an SSH fleet. -

- -

- Once created, dstack will let you run - dev environments, tasks, and services on this fleet. -

- -

- - SSH fleets - - - - -

-
- -
- -
-
-
- - -
-
-
- -
- -
-

Launch GPU dev environments

-

- If you need a remote development environment with a GPU, let dstack create you a dev environment. -

- -

If you plan to work with it yourself, you can access it using your desktop IDE such as VS - Code, Cursor, and - Windsurf. dstack apply prints both the IDE URL and SSH command. -

- -

- - Dev environments - - -

-
-
-
- -
-
-
-

Run training and batch jobs at scale

- -

- Run training or batch workloads on a single GPU, or scale to multi-GPU and multi-node clusters using simple task configurations. - dstack automates cluster provisioning, resource allocation, and job scheduling. -

- -

- During execution, dstack reports GPU utilization, memory usage, and GPU health metrics for each job. -

- -

- - Tasks - - -

-
- -
- -
-
-
- -
-
-
- -
- -
-

Deploy production inference services

- -

- With dstack, you can deploy models as secure, - auto-scaling, OpenAI-compatible endpoints, integrating with top open-source serving frameworks - such as SGLang, vLLM, - TensorRT-LLM, or any other. -

- -

- dstack enables Disaggregated Prefill/Decode and cache-aware routing, providing - production-grade, optimized inference. -

- -

- - Services - - - - -

-
-
-
- -
-
-

FAQ

-
- -
-
-
- How does dstack differ from Slurm? -
-
-
- -
-

- Slurm is a battle-tested system with decades of production use in HPC environments. - dstack by contrast, is built for modern ML/AI workloads with cloud-native provisioning and a container-first architecture. - While both support distributed training and batch jobs, dstack - also natively supports development and production-grade inference. -

- -

- See the migration guide for a detailed comparison. -

-
-
- -
-
- How does dstack compare to Kubernetes? -
-
-
- -
-

- Kubernetes is a general-purpose container orchestrator. dstack also - orchestrates containers, but it provides a lightweight and streamlined interface that is purpose - built for ML. -

- -

- You declare - dev environments, - tasks, - services, and - fleets - with simple configuration. dstack provisions GPUs, manages clusters via fleets with fine-grained - controls, and optimizes cost and utilization, while keeping a simple UI and CLI. -

- -

- If you already use Kubernetes, you can run dstack on it via the Kubernetes backend. -

-
-
- -
-
- Can I use dstack with Kubernetes? -
-
-
- -
-

- Yes. You can connect existing Kubernetes clusters using the Kubernetes backend and run - dev environments, - tasks, and - services on it. - Choose the Kubernetes backend if your GPUs already run on Kubernetes and your team depends on its - ecosystem and tooling. - See the - Kubernetes guide for setup and best practices. -

-

- If your priority is orchestrating cloud GPUs and Kubernetes isn’t a must, VM-based backends are a better fit - thanks to their native cloud integration. - For on-prem GPUs where Kubernetes is optional, SSH fleets provide a simpler and more lightweight alternative. -

-
-
- -
-
- When should I use dstack? -
-
-
- -
-

- dstack accelerates ML development with a simple, ML‑native interface. - Spin up dev environments, run - single‑node or distributed tasks, and deploy services without infrastructure overhead. -

- -

- It radically reduces GPU costs via smart orchestration and fine‑grained fleet controls, including efficient reuse, - right‑sizing, and support for spot, on‑demand, and reserved capacity. -

- -

- It is 100% interoperable with your stack and works with any open‑source frameworks and tools, as - well as your own Docker images and code, across GPU clouds, Kubernetes, and on‑prem GPUs. -

-
-
-
-
- -
-

- Have questions, or need help? -
- - Discord - - - Talk to us - -

-
- -
-
-

Trusted by thousands of engineers across 100+ AI-first companies

- -
-
-
- -
-

Wah Loon Keng

- -

Sr. AI Engineer @Electronic Arts

- -

- With dstack, AI researchers at EA can spin up and scale experiments without touching - infrastructure. It supports everything from quick prototyping to multi-node training on any cloud. -

-
- -
-
- -
-

Aleksandr Movchan

- -

ML Engineer @Mobius Labs

- -

- Thanks to dstack, my team can quickly tap into affordable - GPUs and streamline our workflows - from testing and development to full-scale application deployment. -

-
- -
-
- -
-

Alvaro Bartolome

- -

ML Engineer @Argilla

- -

- With dstack it's incredibly easy to define a configuration - within a - repository - and run it without worrying about GPU availability. It lets you focus on - data and your research. -

-
- -
-
- -
-

Park Chansung

- -

ML Researcher @ETRI

- -

- Thanks to dstack, I can effortlessly access the top GPU - options across - different clouds, - saving me time and money while pushing my AI work forward. -

-
- -
-
- -
-

Eckart Burgwedel

- -

CEO @Uberchord

- -

- With dstack, running LLMs on a cloud GPU is as - easy as running a local Docker container. - It combines the ease of Docker with the auto-scaling capabilities of K8S. -

-
- -
-
- -
-

Jon Stevens

- -

CEO @Hot Aisle

- -

- dstack 's advantages over Slurm are clear: it's a modern, ground-up approach to running workloads at scale. If you're choosing an orchestration platform, dstack is the place to start. -

-
-
-
-
- - - -


- -
-

Get started in minutes

-
- -
-
-
-

- Install dstack on your laptop with uv, - or deploy it anywhere using the dstackai/dstack Docker image. -

- -

Bring your compute via backends or SSH fleets, then bring your team.

- -

- - Quickstart - - - - - Installation - - -

-
- -
- -
-
-
- -
-
-

dstack Sky

- -
-
-

Hosted by us. Bring your own clouds, or tap into GPU marketplace.

-
-
- - -
- Get $5 in GPU marketplace credits. Have an account? Sign in -
-
-
-
- -
-

dstack Enterprise

- -
-
-

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

-
-
- - -
- See how dstack fits your infrastructure. -
-
-
-
-
-
-{% endblock %} diff --git a/mkdocs/overrides/redirect.html b/mkdocs/overrides/redirect.html new file mode 100644 index 0000000000..d3222c452c --- /dev/null +++ b/mkdocs/overrides/redirect.html @@ -0,0 +1,18 @@ + + + + + Redirecting… + + + + + +

Redirecting to the documentation

+ + diff --git a/scripts/docs/build_site.sh b/scripts/docs/build_site.sh new file mode 100755 index 0000000000..4e84eb947c --- /dev/null +++ b/scripts/docs/build_site.sh @@ -0,0 +1,41 @@ +#!/usr/bin/env bash +# +# Build the combined dstack website: the React landing (website/) overlaid onto the +# MkDocs docs+blog build (site/). Produces a single publishable tree in ./site: +# +# / -> React landing (website/dist/index.html) +# /website-assets, /static -> React landing assets +# /docs, /blog, /assets, ... -> MkDocs (untouched) +# /404.html -> MkDocs (the landing build produces none) +# +# Used by both `just site-build` and the GitHub Action. Run from anywhere; the repo +# root is resolved from this script's location. +# +# Env: +# SKIP_NPM_INSTALL=1 reuse website/node_modules instead of running `npm ci` +# (handy for fast local iteration; CI leaves it unset). +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +REPO_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)" +cd "$REPO_ROOT" + +echo "==> Building the React landing (website/)" +( + cd website + if [ "${SKIP_NPM_INSTALL:-}" != "1" ]; then + npm ci + fi + npm run build +) + +echo "==> Building the MkDocs docs + blog (site/)" +uv run mkdocs build -s + +echo "==> Overlaying the landing onto site/" +# React owns only '/': its index.html replaces the MkDocs landing; website-assets/ and +# static/ are added alongside the MkDocs output. MkDocs's 404.html, /assets/, /docs/ and +# /blog/ are left untouched (the landing build intentionally produces no 404.html). +cp -R website/dist/. site/ + +echo "==> Done. Combined site is in $REPO_ROOT/site" diff --git a/website/.gitignore b/website/.gitignore new file mode 100644 index 0000000000..de4d1f007d --- /dev/null +++ b/website/.gitignore @@ -0,0 +1,2 @@ +dist +node_modules diff --git a/website/.justfile b/website/.justfile new file mode 100644 index 0000000000..5ec2476dc7 --- /dev/null +++ b/website/.justfile @@ -0,0 +1,27 @@ +# Justfile for the React landing page (website/) +# +# Run `just` from the repo root to see all available commands. +# Recipes run from the repo root, so paths below are repo-relative. + +# Install the landing's dependencies +website-install: + cd website && npm ci + +# Live-edit the landing (Vite dev server on http://127.0.0.1:5173). +# By default docs/blog links are same-origin (/docs, /blog) — which 404 in standalone dev. +# Pass a base to point them at a live site while iterating, e.g.: +# just website-dev https://dstack.ai +website-dev base="": + cd website && VITE_DOCS_BASE="{{base}}" npm run dev + +# Build only the landing to website/dist +website-build: + cd website && npm run build + +# Build the combined site (landing + docs + blog) into ./site +site-build: + ./scripts/docs/build_site.sh + +# Serve the combined ./site locally for integrated preview (http://127.0.0.1:8001) +site-serve: + uv run python -m http.server 8001 --directory site diff --git a/website/index.html b/website/index.html new file mode 100644 index 0000000000..a9c793f41c --- /dev/null +++ b/website/index.html @@ -0,0 +1,56 @@ + + + + + + dstack — The orchestration stack for AI infrastructure + + + + + + + + + + + + + + + + + + + + + + + +
+ + + diff --git a/website/package-lock.json b/website/package-lock.json new file mode 100644 index 0000000000..4bbcd544bc --- /dev/null +++ b/website/package-lock.json @@ -0,0 +1,2308 @@ +{ + "name": "cloudscape-reverse-engineering", + "version": "0.1.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "cloudscape-reverse-engineering", + "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", + "react": "^18.2.0", + "react-dom": "^18.2.0", + "react-router-dom": "^6.30.4" + }, + "devDependencies": { + "@types/react": "^19.2.17", + "@types/react-dom": "^19.2.3", + "@vitejs/plugin-react": "^5.0.0", + "typescript": "^5.6.3", + "vite": "^7.0.0" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.7.tgz", + "integrity": "sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.29.7", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.7.tgz", + "integrity": "sha512-locTkQyKvwIEgBzVrn8693ebc97F2U8ZHjbXwDXJ5Fn2TCpNwTlKcaKLkdHop5c/icOFE7qt7Q9JC5hnKNa6Gg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.7.tgz", + "integrity": "sha512-RgHBCvtjbOK2gXSNBNIkNoEc9qoVEtau3hj8gEqKQuL3HZAibKarWFEI3Lfm6EYKkLalOh8eSrj9b+ch9H/VBA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.7", + "@babel/generator": "^7.29.7", + "@babel/helper-compilation-targets": "^7.29.7", + "@babel/helper-module-transforms": "^7.29.7", + "@babel/helpers": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/template": "^7.29.7", + "@babel/traverse": "^7.29.7", + "@babel/types": "^7.29.7", + "@jridgewell/remapping": "^2.3.5", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/generator": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.7.tgz", + "integrity": "sha512-DkXD5OJQaAQIdZ1bt3UZdEnHAn9Imd3IVBdX03UFe+ony9Ojw5pzr9YVKGDY1jt+Gcn/FnGkNf8r+Vj5NOJWtQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.7", + "@babel/types": "^7.29.7", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", + "jsesc": "^3.0.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.29.7.tgz", + "integrity": "sha512-wem6WaBj4NaVYVdNhLPPVacES6ZJ+KBBfSkTMD3YZxbP3rm3Di85tJU5ljaUNhaOynt+Aj0xruhYuzQBt8n71g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.29.7", + "@babel/helper-validator-option": "^7.29.7", + "browserslist": "^4.24.0", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-globals": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.29.7.tgz", + "integrity": "sha512-3nQVUAtvkKH9zahfWgw96Jc/uFOmjACE1kQz82E2lqWmHBgjzbNlsC22nuQTfahmWeQtTq5nQ/4Nnd2A1wj4zA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.29.7.tgz", + "integrity": "sha512-ejHwrQQYcm9xnTivShn2IDOlIzInN34AXskvq9QicvCtEzq1Vzclu/tKF8Jq1Cg8JG2GL6/EmjgsCT7lXepE3g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.29.7", + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.29.7.tgz", + "integrity": "sha512-UPUVSyXbOh627KiCIGQSgwWzGeBKLkaJ9PJEdrngIwMSzxLR4jS4+f1f1jb7VzBbg8nFLaYotvVPFCTqdrmTAg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7", + "@babel/traverse": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.29.7.tgz", + "integrity": "sha512-G7sHYigPY17oO5SYWnfD/0MTBwVR781S/JI643e/JhUYgVgWE/61SoW3NH9KWUKyKq5LVh3npif99Wkt6j86Jw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.29.7.tgz", + "integrity": "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz", + "integrity": "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.29.7.tgz", + "integrity": "sha512-N9ZErrD+yW5geCDtBqnOoxmR8+tNKiGuxKlDpuJxfsqpa2dFcexaziGAE/qoHLiDDreVNMupxGmSoNlyvsA3gw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.29.7.tgz", + "integrity": "sha512-1k2lAGRMfHTcwuNYcCNUmaUffmQv8KWMfh2iJUUeRlwlwH4FdNG7mfPI10NPfLHJFThE4Tyr4mv7kTNZOiPuBg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/template": "^7.29.7", + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.7.tgz", + "integrity": "sha512-hnORnjP/1P/zFEndoeX+n+t1RwWRJiJpM/jO7FW32Kn9r5+sJB2JWOdYo4L6k78j15eCwY3Gm/7364B1EMwtNg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.29.7" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-self": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.29.7.tgz", + "integrity": "sha512-TL0hMc9xzy86VD31nUiwzd5otRAcyEPcsegCxolO0PvcXuH1v0kECe/UIznYFihpkvU5wg/jk4v0TTEFfm53fw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-source": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.29.7.tgz", + "integrity": "sha512-06IyK09H3wi4cGbhDBwp5gUGo0IKtnYa8tyTiephirPCK6fbobVGiXMMI5zLQ4aKEYP3wZ3ArU44o+8KMrSG/Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/runtime": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.29.7.tgz", + "integrity": "sha512-Nq8OhGWiZIZGV6hLHoyAKLLcJihP/xFeBMGJoUrxTX2psI8dCifzLhZISFb+VWS3wFMRDmCGw5R+dOySCqPLhw==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/template": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.29.7.tgz", + "integrity": "sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.7.tgz", + "integrity": "sha512-EhlfNQtZ+NK22w5BM61ciuiq1m58ed33Wr1Xan//ZRTy6hgjnwyCffRYwzsGXdASJSUJ1guZILsErh1eQcl+zw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.7", + "@babel/generator": "^7.29.7", + "@babel/helper-globals": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/template": "^7.29.7", + "@babel/types": "^7.29.7", + "debug": "^4.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.7.tgz", + "integrity": "sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@cloudscape-design/code-view": { + "version": "3.0.142", + "resolved": "https://registry.npmjs.org/@cloudscape-design/code-view/-/code-view-3.0.142.tgz", + "integrity": "sha512-OIHumKtlHz5d1+ROT/7hKfML50YnGF0BALZkOp0VKrBIhZjqBIQofXnMdlRRodAyadPFoVWGcnYj8iyD4C2LHQ==", + "license": "Apache-2.0", + "dependencies": { + "@cloudscape-design/component-toolkit": "^1.0.0-beta", + "ace-code": "^1.32.3", + "clsx": "^1.2.1" + }, + "peerDependencies": { + "@cloudscape-design/components": "^3", + "react": ">=18.2.0" + } + }, + "node_modules/@cloudscape-design/collection-hooks": { + "version": "1.0.98", + "resolved": "https://registry.npmjs.org/@cloudscape-design/collection-hooks/-/collection-hooks-1.0.98.tgz", + "integrity": "sha512-/jI9JQrWonfdr4/PU/PwMmUBYl1m7+X+7M96o0N14k7R50NBi5v3eehexsWRnxX5x/J5Ddf5dQrgKNFGpMpTIg==", + "license": "Apache-2.0", + "dependencies": { + "@cloudscape-design/component-toolkit": "^1.0.0-beta" + }, + "peerDependencies": { + "react": ">=16.8.0" + } + }, + "node_modules/@cloudscape-design/component-toolkit": { + "version": "1.0.0-beta.166", + "resolved": "https://registry.npmjs.org/@cloudscape-design/component-toolkit/-/component-toolkit-1.0.0-beta.166.tgz", + "integrity": "sha512-TRUEghBi2u1zG4k3tT3MpEYX1gQOy6rxOKrO443bvF1/R+BhQH6r7zZjUNt8xTvBrPXmuJsSTfsRGHVTZvwcdg==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.3.1", + "weekstart": "^2.0.0" + }, + "peerDependencies": { + "react": ">=16.8.0" + } + }, + "node_modules/@cloudscape-design/component-toolkit/node_modules/weekstart": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/weekstart/-/weekstart-2.0.0.tgz", + "integrity": "sha512-HjYc14IQUwDcnGICuc8tVtqAd6EFpoAQMqgrqcNtWWZB+F1b7iTq44GzwM1qvnH4upFgbhJsaNHuK93NOFheSg==", + "license": "MIT" + }, + "node_modules/@cloudscape-design/components": { + "version": "3.0.1311", + "resolved": "https://registry.npmjs.org/@cloudscape-design/components/-/components-3.0.1311.tgz", + "integrity": "sha512-X1PuzOUOXjfQ/GcXcdnqO7wMNocEJXE72nDHwJrV+oXAu12FpMyuggYi/AVs1W00E19u/fiqdTld6tOYy/Om1A==", + "license": "Apache-2.0", + "dependencies": { + "@cloudscape-design/collection-hooks": "^1.0.0", + "@cloudscape-design/component-toolkit": "^1.0.0-beta", + "@cloudscape-design/test-utils-core": "^1.0.0", + "@cloudscape-design/theming-runtime": "^1.0.0", + "@dnd-kit/core": "^6.0.8", + "@dnd-kit/sortable": "^7.0.2", + "@dnd-kit/utilities": "^3.2.1", + "ace-builds": "^1.34.0", + "clsx": "^1.1.0", + "d3-shape": "^1.3.7", + "date-fns": "^2.25.0", + "intl-messageformat": "^10.3.1", + "mnth": "^2.0.0", + "react-is": "^18.2.0", + "react-transition-group": "^4.4.2", + "tslib": "^2.4.0", + "weekstart": "^1.1.0" + }, + "peerDependencies": { + "react": ">=16.8.0" + } + }, + "node_modules/@cloudscape-design/components/node_modules/react-transition-group": { + "version": "4.4.5", + "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.5.tgz", + "integrity": "sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g==", + "license": "BSD-3-Clause", + "dependencies": { + "@babel/runtime": "^7.5.5", + "dom-helpers": "^5.0.1", + "loose-envify": "^1.4.0", + "prop-types": "^15.6.2" + }, + "peerDependencies": { + "react": ">=16.6.0", + "react-dom": ">=16.6.0" + } + }, + "node_modules/@cloudscape-design/global-styles": { + "version": "1.0.60", + "resolved": "https://registry.npmjs.org/@cloudscape-design/global-styles/-/global-styles-1.0.60.tgz", + "integrity": "sha512-GIAjVl726yUIj8EXYeSjMQYg6toJ55i8dq3U4SqHAz03cgdUO0QUICHSLHrGAHKRiu/KgyrSomuhXwT40D09wg==", + "license": "Apache-2.0" + }, + "node_modules/@cloudscape-design/test-utils-core": { + "version": "1.0.83", + "resolved": "https://registry.npmjs.org/@cloudscape-design/test-utils-core/-/test-utils-core-1.0.83.tgz", + "integrity": "sha512-BLHxBxjppGYf+lT0n18ny0QnWnWJylKhj49GG1fHNA823RAVifu2V/cyOu5GhN3hJKx4yDoyLUruw7y3PQrbyA==", + "license": "Apache-2.0", + "dependencies": { + "css-selector-tokenizer": "^0.8.0", + "css.escape": "^1.5.1" + } + }, + "node_modules/@cloudscape-design/theming-runtime": { + "version": "1.0.115", + "resolved": "https://registry.npmjs.org/@cloudscape-design/theming-runtime/-/theming-runtime-1.0.115.tgz", + "integrity": "sha512-TbuEwXXp+73+L2q604eGDZLrzqIE4bC0KWnoQ1hqzMsifKABKWqCGRYJTJBXa66WdPgsepM5V2icM5yDi7QNwA==", + "license": "Apache-2.0", + "dependencies": { + "@material/material-color-utilities": "^0.3.0", + "tslib": "^2.4.0" + } + }, + "node_modules/@dnd-kit/accessibility": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@dnd-kit/accessibility/-/accessibility-3.1.1.tgz", + "integrity": "sha512-2P+YgaXF+gRsIihwwY1gCsQSYnu9Zyj2py8kY5fFvUM1qm2WA2u639R6YNVfU4GWr+ZM5mqEsfHZZLoRONbemw==", + "license": "MIT", + "dependencies": { + "tslib": "^2.0.0" + }, + "peerDependencies": { + "react": ">=16.8.0" + } + }, + "node_modules/@dnd-kit/core": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/@dnd-kit/core/-/core-6.3.1.tgz", + "integrity": "sha512-xkGBRQQab4RLwgXxoqETICr6S5JlogafbhNsidmrkVv2YRs5MLwpjoF2qpiGjQt8S9AoxtIV603s0GIUpY5eYQ==", + "license": "MIT", + "dependencies": { + "@dnd-kit/accessibility": "^3.1.1", + "@dnd-kit/utilities": "^3.2.2", + "tslib": "^2.0.0" + }, + "peerDependencies": { + "react": ">=16.8.0", + "react-dom": ">=16.8.0" + } + }, + "node_modules/@dnd-kit/sortable": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@dnd-kit/sortable/-/sortable-7.0.2.tgz", + "integrity": "sha512-wDkBHHf9iCi1veM834Gbk1429bd4lHX4RpAwT0y2cHLf246GAvU2sVw/oxWNpPKQNQRQaeGXhAVgrOl1IT+iyA==", + "license": "MIT", + "dependencies": { + "@dnd-kit/utilities": "^3.2.0", + "tslib": "^2.0.0" + }, + "peerDependencies": { + "@dnd-kit/core": "^6.0.7", + "react": ">=16.8.0" + } + }, + "node_modules/@dnd-kit/utilities": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/@dnd-kit/utilities/-/utilities-3.2.2.tgz", + "integrity": "sha512-+MKAJEOfaBe5SmV6t34p80MMKhjvUz0vRrvVJbPT0WElzaOJ/1xs+D+KDv+tD/NE5ujfrChEcshd4fLn0wpiqg==", + "license": "MIT", + "dependencies": { + "tslib": "^2.0.0" + }, + "peerDependencies": { + "react": ">=16.8.0" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.7.tgz", + "integrity": "sha512-EKX3Qwmhz1eMdEJokhALr0YiD0lhQNwDqkPYyPhiSwKrh7/4KRjQc04sZ8db+5DVVnZ1LmbNDI1uAMPEUBnQPg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.7.tgz", + "integrity": "sha512-jbPXvB4Yj2yBV7HUfE2KHe4GJX51QplCN1pGbYjvsyCZbQmies29EoJbkEc+vYuU5o45AfQn37vZlyXy4YJ8RQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.7.tgz", + "integrity": "sha512-62dPZHpIXzvChfvfLJow3q5dDtiNMkwiRzPylSCfriLvZeq0a1bWChrGx/BbUbPwOrsWKMn8idSllklzBy+dgQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.7.tgz", + "integrity": "sha512-x5VpMODneVDb70PYV2VQOmIUUiBtY3D3mPBG8NxVk5CogneYhkR7MmM3yR/uMdITLrC1ml/NV1rj4bMJuy9MCg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.7.tgz", + "integrity": "sha512-5lckdqeuBPlKUwvoCXIgI2D9/ABmPq3Rdp7IfL70393YgaASt7tbju3Ac+ePVi3KDH6N2RqePfHnXkaDtY9fkw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.7.tgz", + "integrity": "sha512-rYnXrKcXuT7Z+WL5K980jVFdvVKhCHhUwid+dDYQpH+qu+TefcomiMAJpIiC2EM3Rjtq0sO3StMV/+3w3MyyqQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.7.tgz", + "integrity": "sha512-B48PqeCsEgOtzME2GbNM2roU29AMTuOIN91dsMO30t+Ydis3z/3Ngoj5hhnsOSSwNzS+6JppqWsuhTp6E82l2w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.7.tgz", + "integrity": "sha512-jOBDK5XEjA4m5IJK3bpAQF9/Lelu/Z9ZcdhTRLf4cajlB+8VEhFFRjWgfy3M1O4rO2GQ/b2dLwCUGpiF/eATNQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.7.tgz", + "integrity": "sha512-RkT/YXYBTSULo3+af8Ib0ykH8u2MBh57o7q/DAs3lTJlyVQkgQvlrPTnjIzzRPQyavxtPtfg0EopvDyIt0j1rA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.7.tgz", + "integrity": "sha512-RZPHBoxXuNnPQO9rvjh5jdkRmVizktkT7TCDkDmQ0W2SwHInKCAV95GRuvdSvA7w4VMwfCjUiPwDi0ZO6Nfe9A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.7.tgz", + "integrity": "sha512-GA48aKNkyQDbd3KtkplYWT102C5sn/EZTY4XROkxONgruHPU72l+gW+FfF8tf2cFjeHaRbWpOYa/uRBz/Xq1Pg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.7.tgz", + "integrity": "sha512-a4POruNM2oWsD4WKvBSEKGIiWQF8fZOAsycHOt6JBpZ+JN2n2JH9WAv56SOyu9X5IqAjqSIPTaJkqN8F7XOQ5Q==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.7.tgz", + "integrity": "sha512-KabT5I6StirGfIz0FMgl1I+R1H73Gp0ofL9A3nG3i/cYFJzKHhouBV5VWK1CSgKvVaG4q1RNpCTR2LuTVB3fIw==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.7.tgz", + "integrity": "sha512-gRsL4x6wsGHGRqhtI+ifpN/vpOFTQtnbsupUF5R5YTAg+y/lKelYR1hXbnBdzDjGbMYjVJLJTd2OFmMewAgwlQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.7.tgz", + "integrity": "sha512-hL25LbxO1QOngGzu2U5xeXtxXcW+/GvMN3ejANqXkxZ/opySAZMrc+9LY/WyjAan41unrR3YrmtTsUpwT66InQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.7.tgz", + "integrity": "sha512-2k8go8Ycu1Kb46vEelhu1vqEP+UeRVj2zY1pSuPdgvbd5ykAw82Lrro28vXUrRmzEsUV0NzCf54yARIK8r0fdw==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.7.tgz", + "integrity": "sha512-hzznmADPt+OmsYzw1EE33ccA+HPdIqiCRq7cQeL1Jlq2gb1+OyWBkMCrYGBJ+sxVzve2ZJEVeePbLM2iEIZSxA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.7.tgz", + "integrity": "sha512-b6pqtrQdigZBwZxAn1UpazEisvwaIDvdbMbmrly7cDTMFnw/+3lVxxCTGOrkPVnsYIosJJXAsILG9XcQS+Yu6w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.7.tgz", + "integrity": "sha512-OfatkLojr6U+WN5EDYuoQhtM+1xco+/6FSzJJnuWiUw5eVcicbyK3dq5EeV/QHT1uy6GoDhGbFpprUiHUYggrw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.7.tgz", + "integrity": "sha512-AFuojMQTxAz75Fo8idVcqoQWEHIXFRbOc1TrVcFSgCZtQfSdc1RXgB3tjOn/krRHENUB4j00bfGjyl2mJrU37A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.7.tgz", + "integrity": "sha512-+A1NJmfM8WNDv5CLVQYJ5PshuRm/4cI6WMZRg1by1GwPIQPCTs1GLEUHwiiQGT5zDdyLiRM/l1G0Pv54gvtKIg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.7.tgz", + "integrity": "sha512-+KrvYb/C8zA9CU/g0sR6w2RBw7IGc5J2BPnc3dYc5VJxHCSF1yNMxTV5LQ7GuKteQXZtspjFbiuW5/dOj7H4Yw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.7.tgz", + "integrity": "sha512-ikktIhFBzQNt/QDyOL580ti9+5mL/YZeUPKU2ivGtGjdTYoqz6jObj6nOMfhASpS4GU4Q/Clh1QtxWAvcYKamA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.7.tgz", + "integrity": "sha512-7yRhbHvPqSpRUV7Q20VuDwbjW5kIMwTHpptuUzV+AA46kiPze5Z7qgt6CLCK3pWFrHeNfDd1VKgyP4O+ng17CA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.7.tgz", + "integrity": "sha512-SmwKXe6VHIyZYbBLJrhOoCJRB/Z1tckzmgTLfFYOfpMAx63BJEaL9ExI8x7v0oAO3Zh6D/Oi1gVxEYr5oUCFhw==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.7.tgz", + "integrity": "sha512-56hiAJPhwQ1R4i+21FVF7V8kSD5zZTdHcVuRFMW0hn753vVfQN8xlx4uOPT4xoGH0Z/oVATuR82AiqSTDIpaHg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@formatjs/ecma402-abstract": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/@formatjs/ecma402-abstract/-/ecma402-abstract-2.3.6.tgz", + "integrity": "sha512-HJnTFeRM2kVFVr5gr5kH1XP6K0JcJtE7Lzvtr3FS/so5f1kpsqqqxy5JF+FRaO6H2qmcMfAUIox7AJteieRtVw==", + "license": "MIT", + "dependencies": { + "@formatjs/fast-memoize": "2.2.7", + "@formatjs/intl-localematcher": "0.6.2", + "decimal.js": "^10.4.3", + "tslib": "^2.8.0" + } + }, + "node_modules/@formatjs/fast-memoize": { + "version": "2.2.7", + "resolved": "https://registry.npmjs.org/@formatjs/fast-memoize/-/fast-memoize-2.2.7.tgz", + "integrity": "sha512-Yabmi9nSvyOMrlSeGGWDiH7rf3a7sIwplbvo/dlz9WCIjzIQAfy1RMf4S0X3yG724n5Ghu2GmEl5NJIV6O9sZQ==", + "license": "MIT", + "dependencies": { + "tslib": "^2.8.0" + } + }, + "node_modules/@formatjs/icu-messageformat-parser": { + "version": "2.11.4", + "resolved": "https://registry.npmjs.org/@formatjs/icu-messageformat-parser/-/icu-messageformat-parser-2.11.4.tgz", + "integrity": "sha512-7kR78cRrPNB4fjGFZg3Rmj5aah8rQj9KPzuLsmcSn4ipLXQvC04keycTI1F7kJYDwIXtT2+7IDEto842CfZBtw==", + "license": "MIT", + "dependencies": { + "@formatjs/ecma402-abstract": "2.3.6", + "@formatjs/icu-skeleton-parser": "1.8.16", + "tslib": "^2.8.0" + } + }, + "node_modules/@formatjs/icu-skeleton-parser": { + "version": "1.8.16", + "resolved": "https://registry.npmjs.org/@formatjs/icu-skeleton-parser/-/icu-skeleton-parser-1.8.16.tgz", + "integrity": "sha512-H13E9Xl+PxBd8D5/6TVUluSpxGNvFSlN/b3coUp0e0JpuWXXnQDiavIpY3NnvSp4xhEMoXyyBvVfdFX8jglOHQ==", + "license": "MIT", + "dependencies": { + "@formatjs/ecma402-abstract": "2.3.6", + "tslib": "^2.8.0" + } + }, + "node_modules/@formatjs/intl-localematcher": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/@formatjs/intl-localematcher/-/intl-localematcher-0.6.2.tgz", + "integrity": "sha512-XOMO2Hupl0wdd172Y06h6kLpBz6Dv+J4okPLl4LPtzbr8f66WbIoy4ev98EBuZ6ZK4h5ydTN6XneT4QVpD7cdA==", + "license": "MIT", + "dependencies": { + "tslib": "^2.8.0" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/remapping": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@material/material-color-utilities": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@material/material-color-utilities/-/material-color-utilities-0.3.0.tgz", + "integrity": "sha512-ztmtTd6xwnuh2/xu+Vb01btgV8SQWYCaK56CkRK8gEkWe5TuDyBcYJ0wgkMRn+2VcE9KUmhvkz+N9GHrqw/C0g==", + "license": "Apache-2.0" + }, + "node_modules/@remix-run/router": { + "version": "1.23.3", + "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.23.3.tgz", + "integrity": "sha512-4An71tdz9X8+3sI4Qqqd2LWd9vS39J7sqd9EU4Scw7TJE/qB10Flv/UuqbPVgfQV9XoK8Np6jNquZitnZq5i+Q==", + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@rolldown/pluginutils": { + "version": "1.0.0-rc.3", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-rc.3.tgz", + "integrity": "sha512-eybk3TjzzzV97Dlj5c+XrBFW57eTNhzod66y9HrBlzJ6NsCrWCp/2kaPS3K9wJmurBC0Tdw4yPjXKZqlznim3Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.62.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.62.0.tgz", + "integrity": "sha512-IPIQ55ythEHkfEd9jMEi32OQ7SxURsGA43JI22lj01OLZNt2NUbJX8YUHxkVWyQ6daHPNn0truF5nSj3DQp6YQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.62.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.62.0.tgz", + "integrity": "sha512-M6s9cr10MibETyo8JsOkq+Lo1+lU6hcvb1MApnUql5qte/5hMEgzlN8/ReIKNfRV8rrqX50W1BX9zoUhC192RA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.62.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.62.0.tgz", + "integrity": "sha512-BqCoMoIbn0keKys+dEAdBa70EtOwV1bEsQCUgU9FdiZmmMge/Zk7LlkYGqbrdHR+Frnt0E1FOanly+rlwvvQzw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.62.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.62.0.tgz", + "integrity": "sha512-SIMzST3VFNXDAbeIWDWiFCNM5qncUBDWaEV7NfE7oZbDt2mgfW4MvbKdbYiGOLoM32gbTv608UMd0XktEYSD7w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.62.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.62.0.tgz", + "integrity": "sha512-ezjfSQMP7ArdUsbBwbQIfwAlhE84I2iVnzQNCFSveqV42q+BmKlzVpf7mxv5EchLcoWU4y6/heFzVg1F+hodUQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.62.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.62.0.tgz", + "integrity": "sha512-9+qTWGW9AZRhnUgwtTwzNwcPlL87ngkeN0LA+q1bADvmY9aNvWaF2TFW8BZgnQPYxpDI7+rMVLivcd4V737TAQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.62.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.62.0.tgz", + "integrity": "sha512-T1dMEQhXA/jkJ/jyMIw9IovK8bSUq7A8kLIlvZTb/6YIVsp2zLavr4F3oyllHWo7eIVJRyE5n3tUjQJEbE1IuQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.62.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.62.0.tgz", + "integrity": "sha512-2as0LgT7qQpyceQq6VUJYnumUMUrgGQCWIiDIN9DE0/tglsk6o66uCB4f3djRawAltvfCNLyZZrsqbPA6inCsA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.62.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.62.0.tgz", + "integrity": "sha512-bVURMg+6eNN9C/yc0aVjooZcwTTtYF4YW3xta5pP0//r3o1V8gXEHXWCndj47w/HhwsFroZrFhR+6uQP5T0n0g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.62.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.62.0.tgz", + "integrity": "sha512-Ful8pM/2yYI83PViWdFdpZhdI8HJ5qsXANe5atypbHDf+KIBBDsZsbyy8hbXnULVvW9NsTh5DHwbcBftyLTfiw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.62.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.62.0.tgz", + "integrity": "sha512-9Gp/DgrkzfUBmNPVTyPTvay+4xEP7M/clXpj3efXBcm6uTIVIgDg4rqUpqKXvLEuFRVuEpSAOkhgNeecvaZ4Cg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-musl": { + "version": "4.62.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.62.0.tgz", + "integrity": "sha512-m9tsJz54LUXkSYM8+8PG81B9IKK5r+2T0clMq4QrS16xFosufU7firBDAZEsDheDs7wTlP7h3++S7lMsU955HA==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.62.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.62.0.tgz", + "integrity": "sha512-3UvJ5PNVU16aJf6M3tFI24pWzAl2/ynfbyRN3ICyQajK1lSkrnVYNnLz3v04J32qKa0FczJc22zeToc0lr2A3w==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-musl": { + "version": "4.62.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.62.0.tgz", + "integrity": "sha512-vRWUAbYLGHBZS6Q8Msb2sfnf1fvJf+47t8l/TwOerM2qArzy+IeNMTHrYLHXh95h8MoatPHI5hhSZNs+mGXKPg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.62.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.62.0.tgz", + "integrity": "sha512-c00T5SYENHAt86cfW47URaP3Us5vLC/4QO7GYud1G5VNRffCwwCuBspwqYrriuJB+5m0WFzClCn9wed0FBjKvg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.62.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.62.0.tgz", + "integrity": "sha512-krrCDilhXOwFkSkO3Wm9I/f9H0L92XHHwy2fwxjukxIbh0dem8gZqOW5Y8BsHrpJv5qwlRBV+Wl4ZFyRWhUpwg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.62.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.62.0.tgz", + "integrity": "sha512-7pfYFSTc4/rUC/FtAI0Qp6QthDBCIi6/AuP1xYqFk5vanI6KnL5dWKP60OM/05LOsbwTmIcvr6eXC4CJuJ75IA==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.62.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.62.0.tgz", + "integrity": "sha512-7SDIalKeIpG0Ifogbbdn58HmSotYMlf23K3dCJEmiVd9Fg36Vmni82iPQec27N3wY4Bvbxftkxz6vSx9OcouTg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.62.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.62.0.tgz", + "integrity": "sha512-eRZevouTH2i1HeAVLqJuLnt256krQkGY0TN6WsTmsIhuzbh457HuWDMakKwmi0Cjadux983CoSr8Lim2QhUIFw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-openbsd-x64": { + "version": "4.62.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.62.0.tgz", + "integrity": "sha512-3oVS7FLGa4U1qcvao9ylGxrjXZyUQqR8UwxEcnUEyPX53O/C/mKDZegNXTdHCP+h3e6ta/f1EN38Yif1mmZHYg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.62.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.62.0.tgz", + "integrity": "sha512-yTB9TgfWj5wHe5QgktAgXTLLot1gvEjl1NiPPAUiCs4oPrIWFl5V4nC3GrkNdj9LaAU4s94nVrGbGOCqUpyWsg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.62.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.62.0.tgz", + "integrity": "sha512-5LOhoaesY3doG1c+ac/2JtgREpKoJr5bUHH8tKY0V8di7+uSV6BwLs2PlR0/yzefGOkR+wE7ZolZphHCsyG5Rw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.62.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.62.0.tgz", + "integrity": "sha512-yYkWHhmbhRTWTnWos5HC4GcPQfjlzzCNbM9e/+GXrLuaBXYA3qSDR9f0Vgufd5S8yX81U8jPKp7ZnAjZFMtRnw==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.62.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.62.0.tgz", + "integrity": "sha512-SoTb6lPg25xZlA2ibwQ++ahCCnH+FP0qmEuafMJ4gznZKOlXioKEAeJLgCrqjM98ACziXM9V1amFjICVL4IFoA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.62.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.62.0.tgz", + "integrity": "sha512-5L+T1fMX4RIEBoZzT0+sQ0PhTS36NULFmMXtl1TZo44TMAROIMHbZufSOjVWt/Y622BtxgxtaNOokbTDvfsrZA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@types/babel__core": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", + "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" + } + }, + "node_modules/@types/babel__generator": { + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz", + "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__template": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz", + "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__traverse": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.28.0.tgz", + "integrity": "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.28.2" + } + }, + "node_modules/@types/estree": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz", + "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/react": { + "version": "19.2.17", + "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.17.tgz", + "integrity": "sha512-MXfmqaVPEVgkBT/aY0aGCkRWWtByiYQXo3xdQ8r5RzuFrPiRn8Gar2tQdXSUQ2GKV3bkXckek89V8wQBY2Q/Aw==", + "dev": true, + "license": "MIT", + "dependencies": { + "csstype": "^3.2.2" + } + }, + "node_modules/@types/react-dom": { + "version": "19.2.3", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.3.tgz", + "integrity": "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "@types/react": "^19.2.0" + } + }, + "node_modules/@vitejs/plugin-react": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-5.2.0.tgz", + "integrity": "sha512-YmKkfhOAi3wsB1PhJq5Scj3GXMn3WvtQ/JC0xoopuHoXSdmtdStOpFrYaT1kie2YgFBcIe64ROzMYRjCrYOdYw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.29.0", + "@babel/plugin-transform-react-jsx-self": "^7.27.1", + "@babel/plugin-transform-react-jsx-source": "^7.27.1", + "@rolldown/pluginutils": "1.0.0-rc.3", + "@types/babel__core": "^7.20.5", + "react-refresh": "^0.18.0" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "peerDependencies": { + "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/ace-builds": { + "version": "1.44.0", + "resolved": "https://registry.npmjs.org/ace-builds/-/ace-builds-1.44.0.tgz", + "integrity": "sha512-PFNMSYqFdEUkul2Ntud0HvA09AgY+F1ag0UYdpMH60wNI/qOA8cB8tlTgoALMEwIdUPJK2CjrIQ7OnbiSS/ugQ==", + "license": "BSD-3-Clause" + }, + "node_modules/ace-code": { + "version": "1.44.0", + "resolved": "https://registry.npmjs.org/ace-code/-/ace-code-1.44.0.tgz", + "integrity": "sha512-6hmMdpJ7DTB4xGSPfZcINgwL2v5/0u6Oh5GXbcH7KZqjWkVADxSbA1IMoWmk/WvgHAsbaYmZoWh7DS2zCfVbeQ==", + "license": "BSD-3-Clause", + "engines": { + "node": ">= 0.6.0" + } + }, + "node_modules/baseline-browser-mapping": { + "version": "2.10.37", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.37.tgz", + "integrity": "sha512-girxaJ7WZssDOFhzCGZTDKoTa1gk6A1TbflaYTpykLJ4UU9Fz9kx1aREM8JCuoVHbL8X8T/mJg7w2oYSq72Oig==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "baseline-browser-mapping": "dist/cli.cjs" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/browserslist": { + "version": "4.28.2", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.2.tgz", + "integrity": "sha512-48xSriZYYg+8qXna9kwqjIVzuQxi+KYWp2+5nCYnYKPTr0LvD89Jqk2Or5ogxz0NUMfIjhh2lIUX/LyX9B4oIg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "baseline-browser-mapping": "^2.10.12", + "caniuse-lite": "^1.0.30001782", + "electron-to-chromium": "^1.5.328", + "node-releases": "^2.0.36", + "update-browserslist-db": "^1.2.3" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001799", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001799.tgz", + "integrity": "sha512-hG1bReV+OUU+MOqK4t/ZWI0tZOyz3rqS9XuhOUz1cIcbwBKjOyJEJuw9ER5JuNyqxNk8u/JUVbGibBOL1yrjFw==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" + }, + "node_modules/clsx": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-1.2.1.tgz", + "integrity": "sha512-EcR6r5a8bj6pu3ycsa/E/cKVGuTgZJZdsyUYHOksG/UHIiKfjxzRxYJpyVBwYaQeOvghal9fcc4PidlgzugAQg==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true, + "license": "MIT" + }, + "node_modules/css-selector-tokenizer": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/css-selector-tokenizer/-/css-selector-tokenizer-0.8.0.tgz", + "integrity": "sha512-Jd6Ig3/pe62/qe5SBPTN8h8LeUg/pT4lLgtavPf7updwwHpvFzxvOQBHYj2LZDMjUnBzgvIUSjRcf6oT5HzHFg==", + "license": "MIT", + "dependencies": { + "cssesc": "^3.0.0", + "fastparse": "^1.1.2" + } + }, + "node_modules/css.escape": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/css.escape/-/css.escape-1.5.1.tgz", + "integrity": "sha512-YUifsXXuknHlUsmlgyY0PKzgPOr7/FjCePfHNt0jxm83wHZi44VDMQ7/fGNkjY3/jV1MC+1CmZbaHzugyeRtpg==", + "license": "MIT" + }, + "node_modules/cssesc": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", + "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", + "license": "MIT", + "bin": { + "cssesc": "bin/cssesc" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/csstype": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", + "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", + "license": "MIT" + }, + "node_modules/d3-path": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-1.0.9.tgz", + "integrity": "sha512-VLaYcn81dtHVTjEHd8B+pbe9yHWpXKZUC87PzoFmsFrJqgFwDe/qxfp5MlfsfM1V5E/iVt0MmEbWQ7FVIXh/bg==", + "license": "BSD-3-Clause" + }, + "node_modules/d3-shape": { + "version": "1.3.7", + "resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-1.3.7.tgz", + "integrity": "sha512-EUkvKjqPFUAZyOlhY5gzCxCeI0Aep04LwIRpsZ/mLFelJiUfnK56jo5JMDSE7yyP2kLSb6LtF+S5chMk7uqPqw==", + "license": "BSD-3-Clause", + "dependencies": { + "d3-path": "1" + } + }, + "node_modules/date-fns": { + "version": "2.30.0", + "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.30.0.tgz", + "integrity": "sha512-fnULvOpxnC5/Vg3NCiWelDsLiUc9bRwAPs/+LfTLNvetFCtCTN+yQz15C/fs4AwX1R9K5GLtLfn8QW+dWisaAw==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.21.0" + }, + "engines": { + "node": ">=0.11" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/date-fns" + } + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/decimal.js": { + "version": "10.6.0", + "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.6.0.tgz", + "integrity": "sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==", + "license": "MIT" + }, + "node_modules/dom-helpers": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.2.1.tgz", + "integrity": "sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.8.7", + "csstype": "^3.0.2" + } + }, + "node_modules/electron-to-chromium": { + "version": "1.5.372", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.372.tgz", + "integrity": "sha512-M3yhbAlilnwqC8D21t28UCDGHyitShTmmLRU/H+b74P6Ski16Nb9HONYEaVpMj/pwC7BEo5B95FpjODLCWbtfA==", + "dev": true, + "license": "ISC" + }, + "node_modules/esbuild": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.7.tgz", + "integrity": "sha512-IxpibTjyVnmrIQo5aqNpCgoACA/dTKLTlhMHihVHhdkxKyPO1uBBthumT0rdHmcsk9uMonIWS0m4FljWzILh3w==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.27.7", + "@esbuild/android-arm": "0.27.7", + "@esbuild/android-arm64": "0.27.7", + "@esbuild/android-x64": "0.27.7", + "@esbuild/darwin-arm64": "0.27.7", + "@esbuild/darwin-x64": "0.27.7", + "@esbuild/freebsd-arm64": "0.27.7", + "@esbuild/freebsd-x64": "0.27.7", + "@esbuild/linux-arm": "0.27.7", + "@esbuild/linux-arm64": "0.27.7", + "@esbuild/linux-ia32": "0.27.7", + "@esbuild/linux-loong64": "0.27.7", + "@esbuild/linux-mips64el": "0.27.7", + "@esbuild/linux-ppc64": "0.27.7", + "@esbuild/linux-riscv64": "0.27.7", + "@esbuild/linux-s390x": "0.27.7", + "@esbuild/linux-x64": "0.27.7", + "@esbuild/netbsd-arm64": "0.27.7", + "@esbuild/netbsd-x64": "0.27.7", + "@esbuild/openbsd-arm64": "0.27.7", + "@esbuild/openbsd-x64": "0.27.7", + "@esbuild/openharmony-arm64": "0.27.7", + "@esbuild/sunos-x64": "0.27.7", + "@esbuild/win32-arm64": "0.27.7", + "@esbuild/win32-ia32": "0.27.7", + "@esbuild/win32-x64": "0.27.7" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/fastparse": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/fastparse/-/fastparse-1.1.2.tgz", + "integrity": "sha512-483XLLxTVIwWK3QTrMGRqUfUpoOs/0hbQrl2oz4J0pAcm3A3bu84wxTFqGqkJzewCLdME38xJLJAxBABfQT8sQ==", + "license": "MIT" + }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/intl-messageformat": { + "version": "10.7.18", + "resolved": "https://registry.npmjs.org/intl-messageformat/-/intl-messageformat-10.7.18.tgz", + "integrity": "sha512-m3Ofv/X/tV8Y3tHXLohcuVuhWKo7BBq62cqY15etqmLxg2DZ34AGGgQDeR+SCta2+zICb1NX83af0GJmbQ1++g==", + "license": "BSD-3-Clause", + "dependencies": { + "@formatjs/ecma402-abstract": "2.3.6", + "@formatjs/fast-memoize": "2.2.7", + "@formatjs/icu-messageformat-parser": "2.11.4", + "tslib": "^2.8.0" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "license": "MIT" + }, + "node_modules/jsesc": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "dev": true, + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "license": "MIT", + "dependencies": { + "js-tokens": "^3.0.0 || ^4.0.0" + }, + "bin": { + "loose-envify": "cli.js" + } + }, + "node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/mnth": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mnth/-/mnth-2.0.0.tgz", + "integrity": "sha512-3ZH4UWBGpAwCKdfjynLQpUDVZWMe6vRHwarIpMdGLUp89CVR9hjzgyWERtMyqx+fPEqQ/PsAxFwvwPxLFxW40A==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.8.0" + }, + "engines": { + "node": ">=12.13.0" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/nanoid": { + "version": "3.3.12", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.12.tgz", + "integrity": "sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/node-releases": { + "version": "2.0.47", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.47.tgz", + "integrity": "sha512-Uzmd6LXpouKo8EUK68IjH4+E01w/hXyV3R3g/geCJo+rXLNfh1xucB+LOzYEOQPSiUK3h/xZf0cQGcSsmyL2Og==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/postcss": { + "version": "8.5.15", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.15.tgz", + "integrity": "sha512-FfR8sjd4em2T6fb3I2MwAJU7HWVMr9zba+enmQeeWFfCbm+UOC/0X4DS8XtpUTMwWMGbjKYP7xjfNekzyGmB3A==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.12", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/prop-types": { + "version": "15.8.1", + "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", + "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.4.0", + "object-assign": "^4.1.1", + "react-is": "^16.13.1" + } + }, + "node_modules/prop-types/node_modules/react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", + "license": "MIT" + }, + "node_modules/react": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", + "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-dom": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", + "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0", + "scheduler": "^0.23.2" + }, + "peerDependencies": { + "react": "^18.3.1" + } + }, + "node_modules/react-is": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", + "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", + "license": "MIT" + }, + "node_modules/react-refresh": { + "version": "0.18.0", + "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.18.0.tgz", + "integrity": "sha512-QgT5//D3jfjJb6Gsjxv0Slpj23ip+HtOpnNgnb2S5zU3CB26G/IDPGoy4RJB42wzFE46DRsstbW6tKHoKbhAxw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-router": { + "version": "6.30.4", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.30.4.tgz", + "integrity": "sha512-SVUsDe+DybHM/WmYKIVYhZh1o5Dcuf16yM6WjG02Q9XVFMZIJyHYhwrr6bFBXZkVP6z69kNkMyBCujt8FaFLJA==", + "license": "MIT", + "dependencies": { + "@remix-run/router": "1.23.3" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "react": ">=16.8" + } + }, + "node_modules/react-router-dom": { + "version": "6.30.4", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.30.4.tgz", + "integrity": "sha512-q4HvNl+mmDdkS0g+MqiBZNteQJCuimWoOyHMy4T/RQLAn9Z29+E91QXRaxOujeMl2HTzRSS0KFPd7lxX3PjV0Q==", + "license": "MIT", + "dependencies": { + "@remix-run/router": "1.23.3", + "react-router": "6.30.4" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "react": ">=16.8", + "react-dom": ">=16.8" + } + }, + "node_modules/rollup": { + "version": "4.62.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.62.0.tgz", + "integrity": "sha512-nc72Wgq62I7rtDV4izT5/aaS0zxy3kttkinf9586ApknY3jZO9NYsmtc24fUckA0X7Q2v+ML4a15pdUlV5V/jA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.9" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.62.0", + "@rollup/rollup-android-arm64": "4.62.0", + "@rollup/rollup-darwin-arm64": "4.62.0", + "@rollup/rollup-darwin-x64": "4.62.0", + "@rollup/rollup-freebsd-arm64": "4.62.0", + "@rollup/rollup-freebsd-x64": "4.62.0", + "@rollup/rollup-linux-arm-gnueabihf": "4.62.0", + "@rollup/rollup-linux-arm-musleabihf": "4.62.0", + "@rollup/rollup-linux-arm64-gnu": "4.62.0", + "@rollup/rollup-linux-arm64-musl": "4.62.0", + "@rollup/rollup-linux-loong64-gnu": "4.62.0", + "@rollup/rollup-linux-loong64-musl": "4.62.0", + "@rollup/rollup-linux-ppc64-gnu": "4.62.0", + "@rollup/rollup-linux-ppc64-musl": "4.62.0", + "@rollup/rollup-linux-riscv64-gnu": "4.62.0", + "@rollup/rollup-linux-riscv64-musl": "4.62.0", + "@rollup/rollup-linux-s390x-gnu": "4.62.0", + "@rollup/rollup-linux-x64-gnu": "4.62.0", + "@rollup/rollup-linux-x64-musl": "4.62.0", + "@rollup/rollup-openbsd-x64": "4.62.0", + "@rollup/rollup-openharmony-arm64": "4.62.0", + "@rollup/rollup-win32-arm64-msvc": "4.62.0", + "@rollup/rollup-win32-ia32-msvc": "4.62.0", + "@rollup/rollup-win32-x64-gnu": "4.62.0", + "@rollup/rollup-win32-x64-msvc": "4.62.0", + "fsevents": "~2.3.2" + } + }, + "node_modules/scheduler": { + "version": "0.23.2", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz", + "integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0" + } + }, + "node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/tinyglobby": { + "version": "0.2.17", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz", + "integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.4" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/update-browserslist-db": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", + "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/vite": { + "version": "7.3.5", + "resolved": "https://registry.npmjs.org/vite/-/vite-7.3.5.tgz", + "integrity": "sha512-KuOaNhcnGFN2zIPGA7wRmzF+lJA1sea7rHq17aiJ++9lzY1WWG6Jpwqwe1KNbRVPIqHmr8GLYx7jbrQcN/7/ww==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.27.0", + "fdir": "^6.5.0", + "picomatch": "^4.0.3", + "postcss": "^8.5.6", + "rollup": "^4.43.0", + "tinyglobby": "^0.2.15" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^20.19.0 || >=22.12.0", + "jiti": ">=1.21.0", + "less": "^4.0.0", + "lightningcss": "^1.21.0", + "sass": "^1.70.0", + "sass-embedded": "^1.70.0", + "stylus": ">=0.54.8", + "sugarss": "^5.0.0", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/vite/node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/weekstart": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/weekstart/-/weekstart-1.1.0.tgz", + "integrity": "sha512-ZO3I7c7J9nwGN1PZKZeBYAsuwWEsCOZi5T68cQoVNYrzrpp5Br0Bgi0OF4l8kH/Ez7nKfxa5mSsXjsgris3+qg==", + "license": "MIT" + }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true, + "license": "ISC" + } + } +} diff --git a/website/package.json b/website/package.json new file mode 100644 index 0000000000..c0d7723039 --- /dev/null +++ b/website/package.json @@ -0,0 +1,26 @@ +{ + "name": "dstack-website", + "version": "0.1.0", + "private": true, + "type": "module", + "scripts": { + "dev": "vite --host 127.0.0.1", + "build": "tsc && vite build", + "preview": "vite preview --host 127.0.0.1" + }, + "dependencies": { + "@cloudscape-design/code-view": "^3.0.142", + "@cloudscape-design/components": "^3.0.0", + "@cloudscape-design/global-styles": "^1.0.0", + "react": "^18.2.0", + "react-dom": "^18.2.0", + "react-router-dom": "^6.30.4" + }, + "devDependencies": { + "@types/react": "^19.2.17", + "@types/react-dom": "^19.2.3", + "@vitejs/plugin-react": "^5.0.0", + "typescript": "^5.6.3", + "vite": "^7.0.0" + } +} diff --git a/website/public/static/dstack-gpu-artwork-dark.svg b/website/public/static/dstack-gpu-artwork-dark.svg new file mode 100644 index 0000000000..eb5b68eeb7 --- /dev/null +++ b/website/public/static/dstack-gpu-artwork-dark.svg @@ -0,0 +1,49 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/website/public/static/dstack-gpu-artwork.svg b/website/public/static/dstack-gpu-artwork.svg new file mode 100644 index 0000000000..1c06e970a3 --- /dev/null +++ b/website/public/static/dstack-gpu-artwork.svg @@ -0,0 +1,49 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/website/public/static/logo-notext.svg b/website/public/static/logo-notext.svg new file mode 100644 index 0000000000..f5cd233feb --- /dev/null +++ b/website/public/static/logo-notext.svg @@ -0,0 +1,116 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/website/public/static/logos/amd.webp b/website/public/static/logos/amd.webp new file mode 100644 index 0000000000..a53f88bfc5 Binary files /dev/null and b/website/public/static/logos/amd.webp differ diff --git a/website/public/static/logos/aws.svg b/website/public/static/logos/aws.svg new file mode 100644 index 0000000000..80192a12d8 --- /dev/null +++ b/website/public/static/logos/aws.svg @@ -0,0 +1,38 @@ + + + + + + + + + + + + diff --git a/website/public/static/logos/gcp.svg b/website/public/static/logos/gcp.svg new file mode 100644 index 0000000000..b8478f1f93 --- /dev/null +++ b/website/public/static/logos/gcp.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/website/public/static/logos/huggingface.svg b/website/public/static/logos/huggingface.svg new file mode 100644 index 0000000000..cab717b750 --- /dev/null +++ b/website/public/static/logos/huggingface.svg @@ -0,0 +1,7 @@ + + + Huggingface Streamline Icon: https://streamlinehq.com + + Hugging Face + + diff --git a/website/public/static/logos/kubernetes.svg b/website/public/static/logos/kubernetes.svg new file mode 100644 index 0000000000..140c74aab4 --- /dev/null +++ b/website/public/static/logos/kubernetes.svg @@ -0,0 +1,16 @@ + + + Logo Kubernetes Streamline Icon: https://streamlinehq.com + + + + + + + + + + + + + diff --git a/website/public/static/logos/lambda.svg b/website/public/static/logos/lambda.svg new file mode 100644 index 0000000000..6143f02db0 --- /dev/null +++ b/website/public/static/logos/lambda.svg @@ -0,0 +1 @@ + diff --git a/website/public/static/logos/meta.svg b/website/public/static/logos/meta.svg new file mode 100644 index 0000000000..ac4cdd3f5a --- /dev/null +++ b/website/public/static/logos/meta.svg @@ -0,0 +1,19 @@ + + +Logo of Meta Platforms -- Graphic created by Detmar Owen + + + + + + + + + + + + + + + + diff --git a/website/public/static/logos/nebius.svg b/website/public/static/logos/nebius.svg new file mode 100644 index 0000000000..55a25a6b2c --- /dev/null +++ b/website/public/static/logos/nebius.svg @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/website/public/static/logos/nvidia.svg b/website/public/static/logos/nvidia.svg new file mode 100644 index 0000000000..7514d0ed39 --- /dev/null +++ b/website/public/static/logos/nvidia.svg @@ -0,0 +1 @@ + diff --git a/website/public/static/logos/pytorch.svg b/website/public/static/logos/pytorch.svg new file mode 100644 index 0000000000..9dcafc39af --- /dev/null +++ b/website/public/static/logos/pytorch.svg @@ -0,0 +1,12 @@ + + + + + + + + + diff --git a/website/public/static/logos/runpod.svg b/website/public/static/logos/runpod.svg new file mode 100644 index 0000000000..f6fab58239 --- /dev/null +++ b/website/public/static/logos/runpod.svg @@ -0,0 +1,3 @@ + + + diff --git a/website/public/static/logos/sglang.svg b/website/public/static/logos/sglang.svg new file mode 100644 index 0000000000..a82fa0aeb1 --- /dev/null +++ b/website/public/static/logos/sglang.svg @@ -0,0 +1 @@ + diff --git a/website/public/static/logos/tenstorrent.svg b/website/public/static/logos/tenstorrent.svg new file mode 100644 index 0000000000..570923ae35 --- /dev/null +++ b/website/public/static/logos/tenstorrent.svg @@ -0,0 +1,23 @@ + + + + + + + + + + + diff --git a/website/public/static/logos/vllm.svg b/website/public/static/logos/vllm.svg new file mode 100644 index 0000000000..07eaef09ca --- /dev/null +++ b/website/public/static/logos/vllm.svg @@ -0,0 +1 @@ +vLLM diff --git a/mkdocs/overrides/assets/images/quotes/alvarobartt.jpg b/website/public/static/quotes/alvarobartt.jpg similarity index 100% rename from mkdocs/overrides/assets/images/quotes/alvarobartt.jpg rename to website/public/static/quotes/alvarobartt.jpg diff --git a/mkdocs/overrides/assets/images/quotes/chansung.jpg b/website/public/static/quotes/chansung.jpg similarity index 100% rename from mkdocs/overrides/assets/images/quotes/chansung.jpg rename to website/public/static/quotes/chansung.jpg diff --git a/website/public/static/quotes/dmitry.jpg b/website/public/static/quotes/dmitry.jpg new file mode 100644 index 0000000000..c04ce9363d Binary files /dev/null and b/website/public/static/quotes/dmitry.jpg differ diff --git a/mkdocs/overrides/assets/images/quotes/eckart.png b/website/public/static/quotes/eckart.png similarity index 100% rename from mkdocs/overrides/assets/images/quotes/eckart.png rename to website/public/static/quotes/eckart.png diff --git a/mkdocs/overrides/assets/images/quotes/jon.jpeg b/website/public/static/quotes/jon.jpeg similarity index 100% rename from mkdocs/overrides/assets/images/quotes/jon.jpeg rename to website/public/static/quotes/jon.jpeg diff --git a/website/public/static/quotes/keng.png b/website/public/static/quotes/keng.png new file mode 100644 index 0000000000..5083742afd Binary files /dev/null and b/website/public/static/quotes/keng.png differ diff --git a/website/public/static/quotes/konstantin.png b/website/public/static/quotes/konstantin.png new file mode 100644 index 0000000000..cf505bb639 Binary files /dev/null and b/website/public/static/quotes/konstantin.png differ diff --git a/mkdocs/overrides/assets/images/quotes/movchan.jpg b/website/public/static/quotes/movchan.jpg similarity index 100% rename from mkdocs/overrides/assets/images/quotes/movchan.jpg rename to website/public/static/quotes/movchan.jpg diff --git a/website/public/static/quotes/nikita.jpeg b/website/public/static/quotes/nikita.jpeg new file mode 100644 index 0000000000..de8d384771 Binary files /dev/null and b/website/public/static/quotes/nikita.jpeg differ diff --git a/website/src/App/index.tsx b/website/src/App/index.tsx new file mode 100644 index 0000000000..84614152df --- /dev/null +++ b/website/src/App/index.tsx @@ -0,0 +1,47 @@ +import { useState } from 'react'; +import { Outlet, useLocation, useOutletContext } from 'react-router-dom'; +import { SiteBanner } from '../components/SiteBanner'; +import { SiteFooter } from '../components/SiteFooter'; +import { SiteNavigation } from '../components/SiteNavigation'; +import { ROUTES } from '../routes'; +import { useTheme, ThemeMode } from '../theme'; + +// State shared from the layout down to routed pages via the router Outlet context. +// Used by the Old page (its side-nav drawer + in-content footer) and the top-nav trigger. +export type LayoutContext = { + oldNavigationOpen: boolean; + setOldNavigationOpen: (open: boolean) => void; + theme: ThemeMode; + toggleTheme: () => void; +}; + +export function useLayoutContext() { + return useOutletContext(); +} + +// Layout shell: persistent top navigation and footer wrapping the routed page. +export function App() { + const { theme, toggleTheme } = useTheme(); + const { pathname } = useLocation(); + const [oldNavigationOpen, setOldNavigationOpen] = useState(true); + + const layoutContext: LayoutContext = { oldNavigationOpen, setOldNavigationOpen, theme, toggleTheme }; + + return ( + <> +
+ + setOldNavigationOpen(open => !open)} + /> +
+ + {/* The Old page renders its own footer inside the AppLayout content, so the side nav + runs full-height beside it; every other page uses the global footer here. */} + {pathname !== ROUTES.OLD && ( + + )} + + ); +} diff --git a/website/src/asset.ts b/website/src/asset.ts new file mode 100644 index 0000000000..4019a7d512 --- /dev/null +++ b/website/src/asset.ts @@ -0,0 +1,3 @@ +// Resolve a path to a file in public/ against the configured base URL, so assets work +// both at the site root (dev) and under the GitHub Pages project subpath in production. +export const asset = (path: string) => import.meta.env.BASE_URL + path.replace(/^\//, ''); diff --git a/website/src/cloudscape-overrides.css b/website/src/cloudscape-overrides.css new file mode 100644 index 0000000000..b59e691cb2 --- /dev/null +++ b/website/src/cloudscape-overrides.css @@ -0,0 +1,250 @@ +/* + Cloudscape internal overrides — the deliberate, isolated exception. + + Everything Cloudscape exposes as a themeable design token lives in cloudscape-theme.ts + (the supported `applyTheme` API). This file covers only the few visual details that + Cloudscape does NOT expose as tokens, so they have to be set against its generated CSS + variables / scoped class names directly. + + MAINTENANCE + - The hashed suffixes below (…-uwo8my, …-gudemr, awsui_…_sne0l) are pinned to the + installed @cloudscape-design/components version. + - After upgrading Cloudscape, re-verify each name. If one drifts, the rule simply stops + matching and the component falls back to its Cloudscape default (graceful degradation, + not a breakage). Re-derive with: + grep -roE '\-\-border-divider-section-width-[a-z0-9]+' node_modules/@cloudscape-design/components/container + grep -roE '\-\-color-border-tabs-[a-z-]+-[a-z0-9]+' node_modules/@cloudscape-design/components/tabs + grep -roE 'awsui_split-trigger-wrapper_[a-z0-9_]+' node_modules/@cloudscape-design/components/button-dropdown +*/ + +/* NB: Cloudscape defines these tokens on `body` inside `@layer awsui-base-theme`. Our + redeclarations are unlayered, so they win over Cloudscape's layered ones. The COLOR + tokens inherit, so `body` is enough. The border-WIDTH token does NOT inherit, so it + has to be declared on the elements that read it — hence the universal selector. */ + +/* 1) Hairline (0.5px) borders on Container / Tabs / Table rows (and anything else using + these divider widths). Border color + radius are themeable (cloudscape-theme.ts); the + widths are not, and they don't inherit — so set them on every element via `*`. + (section-width: containers/tabs; list-width: table rows + list dividers.) */ +* { + --border-divider-section-width-uwo8my: 1px; /* 1px outer border on containers / tabs / tables */ + --border-divider-list-width-tdfx1x: 0.5px; /* table rows + list dividers stay hairline (exception) */ +} + +/* 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-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 */ +} + +/* 3) Split ButtonDropdown ("Get started"): close the gap before the arrow (a 2px + margin on the main segment) and drop the main action's trailing padding so the + label and arrow sit flush. ButtonDropdown has no `style` prop, so this targets its + scoped classes by stable prefix (hash-independent). */ +/* !important is required: Cloudscape boosts its own rules' specificity with a :not(#\9) + trick, which an attribute selector can't otherwise outrank. */ +[class*='awsui_split-trigger-wrapper'] > [class*='awsui_trigger-item']:not(:last-child) > [class*='awsui_trigger-button'] { + margin-inline-end: 0 !important; /* close the 2px gap before the arrow */ + padding-inline-end: 16px !important; /* breathing room between the label and the divider */ +} +/* Match the other top-nav buttons' height (see menuButtonStyle, 7px block). */ +[class*='awsui_split-trigger-wrapper'] [class*='awsui_trigger-button'] { + padding-block: 7px !important; +} +/* Subtle 0.5px divider between the two segments — a hair lighter/darker than the fill + so the split reads, without breaking the single-button look. (Hover stays per-segment, + which is Cloudscape's default and the better affordance here.) */ +[class*='awsui_split-trigger-wrapper'] > [class*='awsui_trigger-item']:not(:first-child) > [class*='awsui_trigger-button'] { + border-inline-start: 0.5px solid var(--cs-seg-divider) !important; +} + +/* 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; +} +/* 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). */ +[class*='awsui_pagination-button-left-scrollable'], +[class*='awsui_pagination-button-right-scrollable'] { + box-shadow: none !important; +} +[class*='awsui_pagination-button-left'], +[class*='awsui_pagination-button-right'] { + border-inline: 0 !important; +} + +/* 5) Dropdown menu popups (the "Get started" and "Resources" menus). Targeted by stable + class prefix so the rules apply wherever the popup renders. + a) Fixed width, flat (no drop-shadow), and a single uniform 0.5px border on all four + sides. By default Cloudscape draws top/bottom on the wrapper (1px) and left/right on + a ::after — so we set the wrapper border ourselves and drop the ::after layer. */ +[class*='awsui_dropdown-content-wrapper'] { + inline-size: 300px !important; + box-shadow: none !important; + border: 0.5px solid var(--cs-text) !important; + border-radius: 12px !important; /* rounded popup */ + overflow: hidden; /* clip the menu items to the rounded corners */ +} +[class*='awsui_dropdown-content-wrapper']::after { + border: 0 !important; +} +/* b) Group headers ("Products" / "Login"). lighter (300) and a touch smaller + (15px) in the full text color (no longer muted). */ +[class*='awsui_header_16mm3'] { + font-weight: 300 !important; + font-size: 15px !important; + color: var(--cs-text) !important; + padding-inline: 16px !important; +} +/* c) Items: bold label (matching the group weight), tighter horizontal padding aligned + with the header. The description below keeps its normal/muted styling. */ +[class*='awsui_menu-item'] { + padding-inline: 16px !important; +} +[class*='awsui_menu-item'] [class*='awsui_main-row'] { + font-weight: 600; + font-size: 15px; /* (Cloudscape's default popup label is 14px) */ +} +/* The hovered item still picked up a border in dark mode (the token override didn't hold + there), so force it off on the highlighted item itself (the cue is the bg tint). */ +[class*='awsui_item-element'][class*='awsui_highlighted'] { + border-color: transparent !important; +} +/* Popup item descriptions: normal text color (not muted), 13px / weight 300. A hair of + separation (1.5px) from the label above so the two lines don't read as one block. */ +[class*='awsui_secondary-text'] { + color: var(--cs-text) !important; + margin-block-start: 1.5px; + font-size: 13px !important; + font-weight: 300 !important; +} +/* A little breathing room at the top and bottom of the popup (inside the border). Placed on + the first/last rows rather than on the list itself, so a hovered first/last item's + background fills that space instead of leaving a thin un-highlighted strip against the + border (the hover tint is painted on the item-element, which is what carries the padding). */ +[class*='awsui_options-list'] { + padding-block: 0 !important; + /* Cloudscape pulls the list 1px into the wrapper border (decrease-block-margin) to overlap + its default 1px divider. With our single hairline border that just lets a hovered + first/last item's fill paint over the border (most visible in dark mode) — so sit the + list flush inside the border instead. */ + margin-block: 0 !important; +} +[class*='awsui_options-list'] > :first-child { + padding-block-start: 6px !important; +} +/* Last item — flat list (ButtonDropdown without groups, e.g. the Resources menu). */ +[class*='awsui_options-list'] > [class*='awsui_item-element']:last-child { + padding-block-end: 6px !important; +} +/* Last item — grouped list: the last item inside the last category (e.g. "Get started"). */ +[class*='awsui_options-list'] > [class*='awsui_category']:last-child [class*='awsui_item-element']:last-child { + padding-block-end: 6px !important; +} +/* d) Push the external-link icon to the right edge of the item. It's rendered inline at + the end of the label, so make the label row fill the width and flex the icon out. + Scoped under `awsui_main-row` (dropdown-item only) so it doesn't affect the external + icons in SideNavigation, which share the `awsui_external-icon` class. */ +[class*='awsui_main-row'] > :first-child { + display: flex !important; + flex: 1 1 auto !important; + align-items: center; +} +[class*='awsui_main-row'] [class*='awsui_external-icon'] { + margin-inline-start: auto !important; +} + +/* 6) FAQ accordion (ExpandableSection): faint background tint on hover that covers the + whole block (question + answer) uniformly. Tint the section root, then neutralize + every inner background (header + content both carry their own white bg) so only the + single root tint shows — otherwise an opaque child paints over it and the question + ends up with an extra highlight vs the answer. FAQ items are plain text, so blanking + inner backgrounds is safe. */ +.faq-list [class*='awsui_root']:hover { + background: var(--cs-hover) !important; +} +.faq-list [class*='awsui_root']:hover * { + background: transparent !important; +} + +/* 7) Testimonial quote cards. solid 0.5px (was dotted 0.25px); radius comes from + the container token (12px). */ +.testimonial-grid [class*='awsui_fit-height'] { + border-style: solid !important; + border-width: 0.5px !important; +} + +/* 8) Mobile slide-out navigation (SideNavigation): render the expandable section headers + ("Resources" / "Get started") in the same weight and size as their sub-links, instead + of bold, for a flatter menu. Scoped to the mobile drawer; `awsui_header-text` is a + stable, hash-independent prefix. */ +.site-mobile-navigation [class*='awsui_header-text'] { + font-weight: 400 !important; + font-size: 14px !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). */ +[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). */ +[class*='awsui_tabs-tab-link'] { + color: var(--cs-text) !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']) { + 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; +} + +/* 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; +} + +/* 11) Thin (0.5px) dividers between stacked FAQ items. The block's outer corners + are clipped to 12px by .faq-list; these are the internal separators. */ +.faq-list * { + --border-divider-section-width-uwo8my: 0.5px; +} diff --git a/website/src/cloudscape-theme.ts b/website/src/cloudscape-theme.ts new file mode 100644 index 0000000000..18d98325f9 --- /dev/null +++ b/website/src/cloudscape-theme.ts @@ -0,0 +1,114 @@ +// dstack's visual customization of Cloudscape, expressed through the official theming +// API (@cloudscape-design/components/theming). We only override design tokens here — no +// CSS reaching into Cloudscape's internal (hashed) class/variable names — so this stays +// on the supported path and survives Cloudscape upgrades. Applied once before the first +// render (see index.tsx), which is the supported way to avoid a flash of the base theme. +import { applyTheme, Theme } from '@cloudscape-design/components/theming'; +import type { ButtonProps } from '@cloudscape-design/components/button'; + +// Mirrors the --cs-* palette in styles.css (light/dark). Token values accept either a +// single string or a { light, dark } pair, which Cloudscape maps to its color modes. +const TEXT = { light: '#16191f', dark: '#f2f3f3' }; // body text color +const SURFACE = { light: '#ffffff', dark: '#0f141d' }; // page background — used as the label color on filled buttons +// Hover states, light + theme-aware: filled (primary) buttons shift to a slightly softer +// shade; outlined (normal) buttons, cards, and dropdown items all share one faint tint +// (--cs-hover, defined per theme in styles.css) so every interactive surface hovers alike. +const HOVER_FILL = 'var(--cs-btn-hover)'; // filled-button hover fill (defined per theme in styles.css; shared with the split-button override) +const FONT = 'var(--font-base)'; // single source of truth: the Geist stack defined in styles.css + +const tokens = { + // Typography: render Cloudscape components in Geist (replaces the hashed-token CSS + // override that used to live in styles.css). Monospace is left as-is for code. + fontFamilyBase: FONT, + fontFamilyHeading: FONT, + fontFamilyDisplay: FONT, + + // rounded corners on buttons + containers (tabs/tables/quotes). 12px. + borderRadiusButton: '12px', + borderRadiusContainer: '12px', + borderRadiusInput: '0px', + borderRadiusDropdown: '0px', + borderRadiusItem: '0px', + borderRadiusBadge: '0px', + borderRadiusAlert: '0px', + borderRadiusPopover: '0px', + borderRadiusTiles: '0px', + borderRadiusCardDefault: '12px', + borderRadiusCardEmbedded: '12px', + borderRadiusActionCardDefault: '12px', + borderRadiusActionCardEmbedded: '12px', + borderRadiusFlashbar: '0px', + borderRadiusDropzone: '0px', + borderRadiusTutorialPanelItem: '0px', + borderRadiusStatusIndicator: '0px', + borderRadiusToken: '0px', + + // Hairline borders on structural controls. Icon stroke widths (borderWidthIcon*) are + // intentionally left alone — those drive icon rendering, not container borders. + borderWidthButton: '1px', /* 1px borders */ + borderWidthField: '0.5px', + borderWidthDropdown: '0.5px', + borderWidthPopover: '0.5px', + borderWidthCard: '1px', /* 1px borders */ + + // Neutral borders/dividers take the text color instead of gray. Semantic borders + // (status, badges, selected/focused) keep their defaults so they still read as such. + colorBorderDividerDefault: TEXT, + colorBorderDividerSecondary: TEXT, + colorBorderInputDefault: TEXT, + colorBorderControlDefault: TEXT, + colorBorderContainerTop: TEXT, + colorBorderDropdownContainer: TEXT, + colorBorderPopover: TEXT, + colorBorderLayout: TEXT, + colorBorderCard: TEXT, + colorBorderDialog: TEXT, + colorBorderExpandableSectionDefault: TEXT, + // Dropdown menu items: no outline box on the hovered/focused row; the cue is a faint + // background tint (matching the cards/buttons) via --cs-hover. + colorBorderDropdownItemHover: 'transparent', + colorBorderDropdownItemFocused: 'transparent', + colorBackgroundDropdownItemHover: 'var(--cs-hover)', + + // Primary button: filled in the text color, with the label in the surface color. + colorBackgroundButtonPrimaryDefault: TEXT, + colorBackgroundButtonPrimaryHover: HOVER_FILL, + colorBackgroundButtonPrimaryActive: HOVER_FILL, + colorBorderButtonPrimaryDefault: TEXT, + colorBorderButtonPrimaryHover: HOVER_FILL, + colorBorderButtonPrimaryActive: HOVER_FILL, + colorTextButtonPrimaryDefault: SURFACE, + colorTextButtonPrimaryHover: SURFACE, + colorTextButtonPrimaryActive: SURFACE, + + // Normal (secondary) button: outlined, transparent by default; on hover/active a faint + // tint (not a solid fill). Border + label stay the text color. + colorBackgroundButtonNormalDefault: 'transparent', + colorBackgroundButtonNormalHover: 'var(--cs-hover)', + colorBackgroundButtonNormalActive: 'var(--cs-hover)', + colorBorderButtonNormalDefault: TEXT, + colorBorderButtonNormalHover: TEXT, + colorBorderButtonNormalActive: TEXT, + colorTextButtonNormalDefault: TEXT, + colorTextButtonNormalHover: TEXT, + colorTextButtonNormalActive: TEXT, +} satisfies Theme['tokens']; + +// Apply on import. index.tsx imports this module for its side effect (alongside the CSS +// imports) so the theme is in place before the first render — no flash of the base theme. +applyTheme({ theme: { tokens } }); + +// Context-specific button padding. Cloudscape has no global button-padding token, so this +// uses the Button `style` prop (the supported per-instance route). Padding scales x and y +// proportionally: hero buttons are the most generous, main-area buttons a step below. +// (Transparent backgrounds for normal buttons are handled globally by the tokens above.) +export const heroButtonStyle: ButtonProps.Style = { + root: { paddingBlock: '10px', paddingInline: '30px' }, // trimmed a touch from 12/34 +}; +export const mainButtonStyle: ButtonProps.Style = { + root: { paddingBlock: '8px', paddingInline: '26px' }, +}; +// Top-nav buttons — slightly roomier than default but compact. +export const menuButtonStyle: ButtonProps.Style = { + root: { paddingBlock: '7px', paddingInline: '18px' }, +}; diff --git a/website/src/components/AlternatingDocBlock.tsx b/website/src/components/AlternatingDocBlock.tsx new file mode 100644 index 0000000000..4fea813ff9 --- /dev/null +++ b/website/src/components/AlternatingDocBlock.tsx @@ -0,0 +1,47 @@ +import { ReactNode } from 'react'; +import { ThemedImage } from '../data/images'; +import { highlightTerms } from './highlightTerms'; + +// In-content diagram: a plain string renders a single image, a themed pair swaps with the theme. +function ThemedDocImage({ image }: { image: string | ThemedImage }) { + if (typeof image === 'string') { + return ; + } + + return ( + <> + + + + ); +} + +// A documentation block with a visual on one side and copy on the other. Pass either +// `image` (rendered via ThemedDocImage) or an arbitrary `visual` node. `imageFirst` +// places the visual on the left, otherwise it sits on the right. +export function AlternatingDocBlock({ + image, + visual, + title, + children, + action, + imageFirst = false, +}: { + image?: string | ThemedImage; + visual?: ReactNode; + title: string; + children: ReactNode; + action?: ReactNode; + imageFirst?: boolean; +}) { + return ( +
+
{visual ?? (image && )}
+
+

{title}

+

{highlightTerms(children)}

+ {action &&
{action}
} +
+
+ ); +} diff --git a/website/src/components/ArchitectureDiagram.tsx b/website/src/components/ArchitectureDiagram.tsx new file mode 100644 index 0000000000..77e53d6853 --- /dev/null +++ b/website/src/components/ArchitectureDiagram.tsx @@ -0,0 +1,127 @@ +import { asset } from '../asset'; +import { DashedBorder } from './DashedBorder'; + +// Layered "vendor-agnostic" architecture diagram, rebuilt as HTML/CSS (replaces the previous +// static SVG). Logos are recolored to the current text color via CSS masking (see .arch-logo in +// styles.css) so they read monochrome and flip with the light/dark theme. Per-logo size/aspect +// lives in CSS (.arch-logo--); only the mask image URL is set inline, since it must carry +// the runtime base path (asset()). + +type Logo = { key: string; label: string; src?: string; initials?: string }; + +const logoSrc = (file: string) => asset(`/static/logos/${file}`); + +const FRAMEWORKS: Logo[] = [ + { key: 'pytorch', label: 'PyTorch', src: logoSrc('pytorch.svg') }, + { key: 'vllm', label: 'vLLM', src: logoSrc('vllm.svg') }, + { key: 'sglang', label: 'SGLang', src: logoSrc('sglang.svg') }, + { key: 'meta', label: 'Meta', src: logoSrc('meta.svg') }, + { key: 'huggingface', label: 'Hugging Face', src: logoSrc('huggingface.svg') }, +]; + +const GPU_CLOUDS: Logo[] = [ + { key: 'aws', label: 'AWS', src: logoSrc('aws.svg') }, + { key: 'gcp', label: 'Google Cloud', src: logoSrc('gcp.svg') }, + { key: 'lambda', label: 'Lambda', src: logoSrc('lambda.svg') }, + { key: 'nebius', label: 'Nebius', src: logoSrc('nebius.svg') }, + { key: 'runpod', label: 'RunPod', src: logoSrc('runpod.svg') }, +]; + +const KUBERNETES: Logo = { key: 'kubernetes', label: 'Kubernetes', src: logoSrc('kubernetes.svg') }; + +const HARDWARE: Logo[] = [ + { key: 'nvidia', label: 'NVIDIA', src: logoSrc('nvidia.svg') }, + { key: 'amd', label: 'AMD', src: logoSrc('amd.webp') }, + { key: 'tenstorrent', label: 'Tenstorrent', src: logoSrc('tenstorrent.svg') }, + { key: 'tpu', label: 'Google TPU', src: logoSrc('gcp.svg') }, // TPU shares the GCP mark +]; + +function LogoMark({ logo }: { logo: Logo }) { + if (logo.src) { + return ( + + ); + } + return ( + + {logo.initials} + + ); +} + +function LogoRow({ logos }: { logos: Logo[] }) { + return ( + + {logos.map(logo => ( + + ))} + + ); +} + +export function ArchitectureDiagram() { + return ( +
+
+ {/* Top: what plugs in on top of the orchestration layer */} +
+
+ + Any framework + +
+
+ + Your data +
+
+ + Any models +
+
+ + {/* Middle: the orchestration layer itself */} +
+
The AI-native orchestration stack
+
+ {['Fleets', 'Dev environments', 'Tasks', 'Services', 'Volumes'].map(name => ( +
+ + {name} +
+ ))} +
+
+ + {/* Bottom: where workloads run */} +
+
+ + + Any cloud +
+
+ + + Kubernetes +
+
+ + On-prem clusters +
+
+ +
+ + Any hardware + +
+
+
+ ); +} diff --git a/website/src/components/DashedBorder.tsx b/website/src/components/DashedBorder.tsx new file mode 100644 index 0000000000..8c1932c5b0 --- /dev/null +++ b/website/src/components/DashedBorder.tsx @@ -0,0 +1,11 @@ +// Dotted outline shared by the architecture diagram and the "AI-native orchestration" concept +// cards: a single rounded rect stroked with the exact 2-on / 6-off dash. Geometry, color, and +// radius live in CSS (.arch-dash / .arch-dash rect in styles.css), so it follows the element's +// rounded corners, stays crisp at any size, and takes the current text color (theme-adaptive). +export function DashedBorder() { + return ( + + ); +} diff --git a/website/src/components/SiteBanner.tsx b/website/src/components/SiteBanner.tsx new file mode 100644 index 0000000000..d092e8a4eb --- /dev/null +++ b/website/src/components/SiteBanner.tsx @@ -0,0 +1,33 @@ +import { BLOG_URL } from '../routes'; + +// Top announcement banner, mirroring the one on the MkDocs docs site. It sits above the top +// nav; the two stick to the top together (see .site-header). Update the copy/href here when +// the announcement changes. +const BANNER_TEXT = 'Infrastructure orchestration is an agent skill'; +const BANNER_HREF = `${BLOG_URL}/agentic-orchestration/`; + +export function SiteBanner() { + return ( + + ); +} diff --git a/website/src/components/SiteFooter.tsx b/website/src/components/SiteFooter.tsx new file mode 100644 index 0000000000..a836819096 --- /dev/null +++ b/website/src/components/SiteFooter.tsx @@ -0,0 +1,145 @@ +import Button from '@cloudscape-design/components/button'; +import Link from '@cloudscape-design/components/link'; +import SpaceBetween from '@cloudscape-design/components/space-between'; +import { asset } from '../asset'; +import { BLOG_URL, docsUrl, PRIVACY_URL, TERMS_URL } from '../routes'; +import { ThemeMode } from '../theme'; + +type FooterLinkItem = { label: string; href: string; external?: boolean }; +type FooterColumn = { heading: string; links: FooterLinkItem[] }; + +// Brand-area social links (single-color SVG paths drawn in currentColor), matching the +// icons configured for the MkDocs site footer. +const socialLinks: { label: string; href: string; path: string }[] = [ + { + label: 'GitHub', + href: 'https://github.com/dstackai/dstack', + path: 'M12 .297c-6.63 0-12 5.373-12 12 0 5.303 3.438 9.8 8.205 11.385.6.113.82-.258.82-.577 0-.285-.01-1.04-.015-2.04-3.338.724-4.042-1.61-4.042-1.61C4.422 18.07 3.633 17.7 3.633 17.7c-1.087-.744.084-.729.084-.729 1.205.084 1.838 1.236 1.838 1.236 1.07 1.835 2.809 1.305 3.495.998.108-.776.417-1.305.76-1.605-2.665-.3-5.466-1.332-5.466-5.93 0-1.31.465-2.38 1.235-3.22-.135-.303-.54-1.523.105-3.176 0 0 1.005-.322 3.3 1.23.96-.267 1.98-.399 3-.405 1.02.006 2.04.138 3 .405 2.28-1.552 3.285-1.23 3.285-1.23.645 1.653.24 2.873.12 3.176.765.84 1.23 1.91 1.23 3.22 0 4.61-2.805 5.625-5.475 5.92.42.36.81 1.096.81 2.22 0 1.606-.015 2.896-.015 3.286 0 .315.21.69.825.57C20.565 22.092 24 17.592 24 12.297c0-6.627-5.373-12-12-12', + }, + { + label: 'Discord', + href: 'https://discord.gg/u8SmfwPpMd', + path: 'M20.317 4.3698a19.7913 19.7913 0 00-4.8851-1.5152.0741.0741 0 00-.0785.0371c-.211.3753-.4447.8648-.6083 1.2495-1.8447-.2762-3.68-.2762-5.4868 0-.1636-.3933-.4058-.8742-.6177-1.2495a.077.077 0 00-.0785-.037 19.7363 19.7363 0 00-4.8852 1.515.0699.0699 0 00-.0321.0277C.5334 9.0458-.319 13.5799.0992 18.0578a.0824.0824 0 00.0312.0561c2.0528 1.5076 4.0413 2.4228 5.9929 3.0294a.0777.0777 0 00.0842-.0276c.4616-.6304.8731-1.2952 1.226-1.9942a.076.076 0 00-.0416-.1057c-.6528-.2476-1.2743-.5495-1.8722-.8923a.077.077 0 01-.0076-.1277c.1258-.0943.2517-.1923.3718-.2914a.0743.0743 0 01.0776-.0105c3.9278 1.7933 8.18 1.7933 12.0614 0a.0739.0739 0 01.0785.0095c.1202.099.246.1981.3728.2924a.077.077 0 01-.0066.1276 12.2986 12.2986 0 01-1.873.8914.0766.0766 0 00-.0407.1067c.3604.698.7719 1.3628 1.225 1.9932a.076.076 0 00.0842.0286c1.961-.6067 3.9495-1.5219 6.0023-3.0294a.077.077 0 00.0313-.0552c.5004-5.177-.8382-9.6739-3.5485-13.6604a.061.061 0 00-.0312-.0286zM8.02 15.3312c-1.1825 0-2.1569-1.0857-2.1569-2.419 0-1.3332.9555-2.4189 2.157-2.4189 1.2108 0 2.1757 1.0952 2.1568 2.419 0 1.3332-.9555 2.4189-2.1569 2.4189zm7.9748 0c-1.1825 0-2.1569-1.0857-2.1569-2.419 0-1.3332.9554-2.4189 2.1569-2.4189 1.2108 0 2.1757 1.0952 2.1568 2.419 0 1.3332-.946 2.4189-2.1568 2.4189Z', + }, + { + label: 'X', + href: 'https://x.com/dstackai', + path: 'M18.244 2.25h3.308l-7.227 8.26 8.502 11.24H16.17l-5.214-6.817L4.99 21.75H1.68l7.73-8.835L1.254 2.25H8.08l4.713 6.231zm-1.161 17.52h1.833L7.084 4.126H5.117z', + }, + { + label: 'LinkedIn', + href: 'https://www.linkedin.com/company/dstackai', + path: 'M20.447 20.452h-3.554v-5.569c0-1.328-.027-3.037-1.852-3.037-1.853 0-2.136 1.445-2.136 2.939v5.667H9.351V9h3.414v1.561h.046c.477-.9 1.637-1.85 3.37-1.85 3.601 0 4.267 2.37 4.267 5.455v6.286zM5.337 7.433c-1.144 0-2.063-.926-2.063-2.065 0-1.138.92-2.063 2.063-2.063 1.14 0 2.064.925 2.064 2.063 0 1.139-.925 2.065-2.064 2.065zm1.782 13.019H3.555V9h3.564v11.452zM22.225 0H1.771C.792 0 0 .774 0 1.729v20.542C0 23.227.792 24 1.771 24h20.451C23.2 24 24 23.227 24 22.271V1.729C24 .774 23.2 0 22.222 0h.003z', + }, +]; + +// Link columns for the footer, mirroring the dstack.ai site footer. Doc/blog/legal links +// resolve through the route table (same origin in production, VITE_DOCS_BASE in dev). +const footerColumns: FooterColumn[] = [ + { + heading: 'Documentation', + links: [ + { label: 'Getting started', href: docsUrl('installation') }, + { label: 'Concepts', href: docsUrl('concepts/backends/') }, + { label: 'Guides', href: docsUrl('guides/protips/') }, + { label: 'Reference', href: docsUrl('reference/dstack.yml/dev-environment/') }, + ], + }, + { + heading: 'Examples', + links: [ + { label: 'Training', href: docsUrl('examples/training/trl/') }, + { label: 'Clusters', href: docsUrl('examples/clusters/aws/') }, + { label: 'Inference', href: docsUrl('examples/inference/sglang/') }, + { label: 'Models', href: docsUrl('examples/models/deepseek-v4/') }, + { label: 'Accelerators', href: docsUrl('examples/accelerators/amd/') }, + ], + }, + { + heading: 'Community', + links: [ + { label: 'GitHub', href: 'https://github.com/dstackai/dstack', external: true }, + { label: 'Discord', href: 'https://discord.gg/u8SmfwPpMd', external: true }, + ], + }, + { + heading: 'Company', + links: [ + { label: 'Blog', href: BLOG_URL }, + { label: 'Talk to us', href: 'https://calendly.com/dstackai/discovery-call', external: true }, + { label: 'Terms of service', href: TERMS_URL }, + { label: 'Privacy policy', href: PRIVACY_URL }, + ], + }, +]; + +// Global footer: a brand block (logo + social) reserving the leading ~1.5 columns, +// then the multi-column link grid, with the theme toggle and copyright in a +// divider-separated bottom bar. On the home page it carries an extra gradient +// (.site-footer--home). +export function SiteFooter({ + home, + theme, + onToggleTheme, +}: { + home: boolean; + theme: ThemeMode; + onToggleTheme: () => void; +}) { + return ( +
+
+
+
+ + + dstack + +
    + {socialLinks.map(social => ( +
  • + + + +
  • + ))} +
+
+ +
+
+
+
+
+ ); +} diff --git a/website/src/components/SiteNavigation.tsx b/website/src/components/SiteNavigation.tsx new file mode 100644 index 0000000000..7c0dd18668 --- /dev/null +++ b/website/src/components/SiteNavigation.tsx @@ -0,0 +1,231 @@ +import { useRef, useState } from 'react'; +import { useLocation, useNavigate } from 'react-router-dom'; +import Button from '@cloudscape-design/components/button'; +import ButtonDropdown, { ButtonDropdownProps } from '@cloudscape-design/components/button-dropdown'; +import SideNavigation, { SideNavigationProps } from '@cloudscape-design/components/side-navigation'; +import SpaceBetween from '@cloudscape-design/components/space-between'; +import { menuButtonStyle } from '../cloudscape-theme'; +import { asset } from '../asset'; +import { BLOG_URL, DOCS_URL, ROUTES, docsUrl } from '../routes'; + +const dstackGithubUrl = 'https://github.com/dstackai/dstack'; +const externalIconAriaLabel = 'External link icon'; + +// Primary links in the desktop top navigation (plain same-origin MkDocs links). +const audienceNavItems: Array<{ label: string; href: string }> = [ + { label: 'Documentation', href: DOCS_URL }, +]; + +// "Resources" top-nav dropdown: the blog landing plus its two main categories (all +// same-origin MkDocs pages). +const resourcesDropdownItems: ButtonDropdownProps.Items = [ + { + id: 'case-studies', + text: 'Case studies', + secondaryText: 'How AI teams run training and inference with dstack.', + href: `${BLOG_URL}/case-studies/`, + }, + { + id: 'benchmarks', + text: 'Benchmarks', + secondaryText: 'Comparing hardware, inference engines, and deployment setups for AI.', + href: `${BLOG_URL}/benchmarks/`, + }, + { + id: 'blog', + text: 'Blog', + secondaryText: 'Major releases, industry reports, and product updates.', + href: BLOG_URL, + }, +]; + +// "Resources" dropdown that opens on hover and stays open while the cursor is over the +// trigger OR the popup (the popup renders inside this wrapper, so hovering it still counts as +// hovering the wrapper). Cloudscape's ButtonDropdown is click-only, so we open/close it by +// reading aria-expanded and synthesizing a click on the trigger — click and keyboard keep +// working unchanged. A short close delay bridges the gap between trigger and popup so moving +// the cursor across it doesn't dismiss the menu. Desktop top-nav only (mobile uses SideNavigation). +function ResourcesHoverMenu() { + const wrapRef = useRef(null); + const closeTimer = useRef(undefined); + + const trigger = () => wrapRef.current?.querySelector('button') ?? null; + const isOpen = () => trigger()?.getAttribute('aria-expanded') === 'true'; + const cancelClose = () => { + if (closeTimer.current !== undefined) { + window.clearTimeout(closeTimer.current); + closeTimer.current = undefined; + } + }; + const openNow = () => { + cancelClose(); + if (!isOpen()) trigger()?.click(); + }; + const closeSoon = () => { + cancelClose(); + closeTimer.current = window.setTimeout(() => { + if (isOpen()) trigger()?.click(); + }, 140); + }; + + return ( +
+ + Resources + +
+ ); +} + +// "Get started" dropdown items. secondaryText is shown under each label. +const productDropdownItems: ButtonDropdownProps.Items = [ + { + text: 'Products', + items: [ + { id: 'open-source', text: 'dstack', secondaryText: 'The open-source control plane that works across clouds, Kubernetes, and on-prem.', href: docsUrl('installation') }, + { id: 'sky-product', text: 'dstack Sky', secondaryText: 'Access GPU marketplace, or bring your own clouds. Hosted by us.', href: 'https://sky.dstack.ai', external: true, externalIconAriaLabel }, + { id: 'enterprise', text: 'dstack Enterprise', secondaryText: 'Self-hosted with SSO, air-gapped setup, dedicated support, and more.', href: 'https://calendly.com/dstackai/discovery-call', external: true, externalIconAriaLabel }, + ], + }, + { + text: 'Login', + items: [ + { id: 'sky-login', text: 'dstack Sky', href: 'https://sky.dstack.ai', external: true, externalIconAriaLabel }, + ], + }, +]; + +// Items for the mobile slide-out navigation. +const mobileNavigationItems: SideNavigationProps.Item[] = [ + { type: 'link', text: 'Documentation', href: DOCS_URL }, + // The desktop "Resources" dropdown becomes an expandable section on mobile (SideNavigation + // has no popups), matching the "Get started" section pattern below. + { + type: 'section', + text: 'Resources', + defaultExpanded: true, + items: [ + { type: 'link', text: 'Case studies', href: `${BLOG_URL}/case-studies/` }, + { type: 'link', text: 'Benchmarks', href: `${BLOG_URL}/benchmarks/` }, + { type: 'link', text: 'Blog', href: BLOG_URL }, + ], + }, + { type: 'link', text: 'GitHub', href: dstackGithubUrl, external: true, externalIconAriaLabel }, + { + type: 'section', + text: 'Get started', + defaultExpanded: true, + items: [ + { type: 'link', text: 'dstack', href: docsUrl('installation'), external: true, externalIconAriaLabel }, + { type: 'link', text: 'dstack Sky', href: 'https://sky.dstack.ai', external: true, externalIconAriaLabel }, + { type: 'link', text: 'dstack Enterprise', href: 'https://calendly.com/dstackai/discovery-call', external: true, externalIconAriaLabel }, + ], + }, +]; + +// Global top navigation. On the Old page it also renders the trigger that toggles +// that page's side navigation drawer (state owned by the App layout). +export function SiteNavigation({ + oldNavigationOpen, + onToggleOldNavigation, +}: { + oldNavigationOpen: boolean; + onToggleOldNavigation: () => void; +}) { + const navigate = useNavigate(); + const { pathname } = useLocation(); + const [mobileNavigationOpen, setMobileNavigationOpen] = useState(false); + + const isHome = pathname === ROUTES.HOME; + const isOldPage = pathname === ROUTES.OLD; + + const go = (to: string) => { + navigate(to); + setMobileNavigationOpen(false); + }; + + // Scroll to the "Get started" section, navigating home first if we're on another page. + const scrollToResources = () => { + const target = document.getElementById('resources'); + if (target) { + target.scrollIntoView({ behavior: 'smooth', block: 'start' }); + return; + } + + go(ROUTES.HOME); + window.requestAnimationFrame(() => { + window.requestAnimationFrame(() => { + document.getElementById('resources')?.scrollIntoView({ behavior: 'smooth', block: 'start' }); + }); + }); + }; + + return ( +
+
+ {isOldPage && ( +
+
+ )} +
+
+ + + + {mobileNavigationOpen && ( +
+ setMobileNavigationOpen(false)} + /> +
+ )} +
+ ); +} diff --git a/website/src/components/highlightTerms.tsx b/website/src/components/highlightTerms.tsx new file mode 100644 index 0000000000..94cf5c098f --- /dev/null +++ b/website/src/components/highlightTerms.tsx @@ -0,0 +1,37 @@ +import { Fragment, ReactNode } from 'react'; + +// Terms wrapped with the .highlight outline in body copy (never headers or menus — those just +// don't call this). Order matters: longer / compound terms come first so e.g. "dstack Sky" and +// "dstackai/dstack" win over a bare "dstack". Case-sensitive, each bounded by \b so we don't +// match inside larger words (e.g. "uv" won't hit "uvicorn", "pip" won't hit "pipeline"). +const TERMS_RE = + /\bdstackai\/dstack\b|\bdstack Sky\b|\bdstack\b|\bKubernetes\b|\bTenstorrent\b|\bNVIDIA\b|\bSlurm\b|\bAMD\b|\bTPU\b|\buv\b|\bpip\b/g; + +function markString(text: string): ReactNode { + const parts: ReactNode[] = []; + let lastIndex = 0; + let match: RegExpExecArray | null; + TERMS_RE.lastIndex = 0; + while ((match = TERMS_RE.exec(text)) !== null) { + if (match.index > lastIndex) parts.push(text.slice(lastIndex, match.index)); + parts.push( + + {match[0]} + , + ); + lastIndex = match.index + match[0].length; + } + if (parts.length === 0) return text; + if (lastIndex < text.length) parts.push(text.slice(lastIndex)); + return parts; +} + +// Walks a node tree and outlines the known terms inside string leaves. Non-string nodes (e.g. +//
) pass through untouched; element children aren't recursed into. Apply to body copy only. +export function highlightTerms(node: ReactNode): ReactNode { + if (typeof node === 'string') return markString(node); + if (Array.isArray(node)) { + return node.map((child, i) => {highlightTerms(child)}); + } + return node; +} diff --git a/website/src/data/images.ts b/website/src/data/images.ts new file mode 100644 index 0000000000..c525c5b889 --- /dev/null +++ b/website/src/data/images.ts @@ -0,0 +1,26 @@ +// 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 +// 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'), + github: img('/__images/yvlrib0vb3vb/5WFt79VY8lv19rULhh7kss/a47ddc4dceb91f020910445feeebc306/homepage--cloudscape-on-github--os-light.png'), + mode: img('/__images/yvlrib0vb3vb/6dN8hKQOPLKko1MdoXZWXc/b2a5390038d3a7e2604c2550a12c5641/foundation-overview--visual-modes--core-light.png'), + theming: img('/__images/yvlrib0vb3vb/2GUtUNLAIC2tfIePMVbM4z/f054770207e01200a6e03f140c838437/foundation-overview--theming--os-light.png'), + accessibility: img('/__images/yvlrib0vb3vb/1Bv6Ju9XwvoRt4YzZys5eI/cdc470297880845185849edb739cb5b6/foundation-overview--accessibility--vr-light.png'), + responsive: img('/__images/yvlrib0vb3vb/4AYh8LuIrJO3AN0S0jtTzB/906ff95fd94ec7b20891e7327d1fc53f/foundation-overview--responsive-design--vr-light.png'), +} as const; + +export type ThemedImage = { light: string; dark: string }; diff --git a/website/src/data/snippets.ts b/website/src/data/snippets.ts new file mode 100644 index 0000000000..23d10dff69 --- /dev/null +++ b/website/src/data/snippets.ts @@ -0,0 +1,228 @@ +// Code snippets shown in the home page tab groups: cloud backends and on-prem +// clusters (YAML), plus the open-source install commands (shell). The first line +// of each YAML snippet is a comment naming the file it represents. +const BACKEND_HEADER = '# ~/.dstack/server/config.yml'; +const FLEET_HEADER = '# my-fleet.dstack.yml'; + +export const backendConfigs = ( + [ + { + id: 'aws', + label: 'AWS', + yaml: `projects: + - name: main + backends: + - type: aws + creds: + type: access_key + access_key: KKAAUKLIZ5EHKICAOASV + secret_key: pn158lMqSBJiySwpQ9ubwmI6VUU3/W2fdJdFwfgO`, + }, + { + id: 'gcp', + label: 'GCP', + yaml: `projects: + - name: main + backends: + - type: gcp + project_id: my-gcp-project + creds: + type: service_account + filename: ~/.dstack/server/gcp-024ed630eab5.json`, + }, + { + id: 'lambda', + label: 'Lambda', + yaml: `projects: + - name: main + backends: + - type: lambda + creds: + type: api_key + api_key: eersct_yrpiey-naaeedst-tk-_cb6ba38e1128464aea9bcc619e4ba2a5.iijPMi07obgt6TZ87v5qAEj61RVxhd0p`, + }, + { + id: 'nebius', + label: 'Nebius', + yaml: `projects: + - name: main + backends: + - type: nebius + creds: + type: service_account + service_account_id: serviceaccount-e00dhnv9ftgb3cqmej + public_key_id: publickey-e00ngaex668htswqy4 + private_key_file: ~/path/to/key.pem`, + }, + { + id: 'crusoe', + label: 'Crusoe', + yaml: `projects: + - name: main + backends: + - type: crusoe + project_id: my-crusoe-project + creds: + type: access_key + access_key: CRUSOE3X9PLANROAQ2KZ + secret_key: 8fJqV2mWcR7tY1nB6xD4eL0pS5gH3aZ9uK7iO2wQ`, + }, + { + id: 'runpod', + label: 'RunPod', + yaml: `projects: + - name: main + backends: + - type: runpod + creds: + type: api_key + api_key: US9XTPDIV8AR42MMINY8TCKRB8S4E7LNRQ6CAUQ9`, + }, + { + id: 'azure', + label: 'Azure', + yaml: `projects: + - name: main + backends: + - type: azure + subscription_id: 06c82ce3-28ff-4285-a146-c5e981a9d808 + tenant_id: f84a7584-88e4-4fd2-8e97-623f0a715ee1 + creds: + type: client + client_id: acf3f73a-597b-46b6-98d9-748d75018ed0 + client_secret: 1Kb8Q~o3Q2hdEvrul9yaj5DJDFkuL3RG7lger2VQ`, + }, + { + id: 'verda', + label: 'Verda', + yaml: `projects: + - name: main + backends: + - type: verda + creds: + type: api_key + client_id: xfaHBqYEsArqhKWX-e52x3HH7w8T + client_secret: B5ZU5Qx9Nt8oGMlmMhNI3iglK8bjMhagTbylZy4WzncZe39995f7Vxh8`, + }, + { + id: 'digitalocean', + label: 'Digital Ocean', + yaml: `projects: + - name: main + backends: + - type: digitalocean + project_name: my-digital-ocean-project + creds: + type: api_key + api_key: dop_v1_examplekey3f8a1c9e2b7d6045a1c8e3f9b2d7a6c4e1f0b9d8`, + }, + { + id: 'jarvislabs', + label: 'JarvisLabs', + yaml: `projects: + - name: main + backends: + - type: jarvislabs + creds: + type: api_key + api_key: jlab_8Kd2Pq7Vn4Rt9Wm3Xy6Zb1Lc5Fg0HsJ4Tn7Vr2Wq9`, + }, + { + id: 'cloudrift', + label: 'CloudRift', + yaml: `projects: + - name: main + backends: + - type: cloudrift + creds: + type: api_key + api_key: rift_2prgY1d0laOrf2BblTwx2B2d1zcf1zIp4tZYpj5j88qmNgz38pxNlpX3vAo`, + }, + ] as const +).map(backend => ({ ...backend, yaml: `${BACKEND_HEADER}\n${backend.yaml}` })); + +export const clusterConfigs = [ + { + id: 'kubernetes', + label: 'Kubernetes', + yaml: `${BACKEND_HEADER} +projects: + - name: main + backends: + - type: kubernetes + + kubeconfig: + filename: ~/.kube/config + + contexts: + - name: gpu-cluster-a + - name: gpu-cluster-b`, + }, + { + id: 'ssh', + label: 'SSH', + yaml: `${FLEET_HEADER} +type: fleet +name: my-fleet + +placement: cluster + +ssh_config: + user: ubuntu + identity_file: ~/.ssh/id_rsa + hosts: + - 3.255.177.51 + - 3.255.177.52`, + }, +] as const; + +// Pad every snippet in a tab group to the same line count so all tabs are equal height. Filler +// lines hold a single space rather than being empty: the highlighter omits the trailing newline +// after the last line, so an empty final line would collapse to zero height. Requires wrapLines +// off (1 line = 1 row), otherwise long values would wrap and break the line-count = height mapping. +export const padYamlToLines = (yaml: string, maxLines: number) => + yaml + '\n '.repeat(maxLines - yaml.split('\n').length); + +export const maxBackendYamlLines = Math.max(...backendConfigs.map(backend => backend.yaml.split('\n').length)); +export const maxClusterYamlLines = Math.max(...clusterConfigs.map(cluster => cluster.yaml.split('\n').length)); + +// Open-source install commands (uv / pip / Docker), each followed by the +// `dstack server` startup output. Rendered as shell in the "Get started" block. +export const installMethods = [ + { + id: 'uv', + label: 'uv', + code: `$ uv tool install "dstack[all]" -U +$ dstack server + +Applying ~/.dstack/server/config.yml... + +The admin token is "bbae0f28-d3dd-4820-bf61-8f4bb40815da" +The server is running at http://127.0.0.1:3000/`, + }, + { + id: 'pip', + label: 'pip', + code: `$ pip install "dstack[all]" -U +$ dstack server + +Applying ~/.dstack/server/config.yml... + +The admin token is "bbae0f28-d3dd-4820-bf61-8f4bb40815da" +The server is running at http://127.0.0.1:3000/`, + }, + { + id: 'docker', + label: 'Docker', + code: `$ docker run -p 3000:3000 \\ + -v $HOME/.dstack/server/:/root/.dstack/server \\ + dstackai/dstack + +Applying ~/.dstack/server/config.yml... + +The admin token is "bbae0f28-d3dd-4820-bf61-8f4bb40815da" +The server is running at http://127.0.0.1:3000/`, + }, +] as const; + +export const maxInstallLines = Math.max(...installMethods.map(method => method.code.split('\n').length)); diff --git a/website/src/fonts/Geist-Variable.woff2 b/website/src/fonts/Geist-Variable.woff2 new file mode 100644 index 0000000000..b2f0121062 Binary files /dev/null and b/website/src/fonts/Geist-Variable.woff2 differ diff --git a/website/src/fonts/GeistPixel-Square.woff2 b/website/src/fonts/GeistPixel-Square.woff2 new file mode 100644 index 0000000000..232cae2c11 Binary files /dev/null and b/website/src/fonts/GeistPixel-Square.woff2 differ diff --git a/website/src/index.tsx b/website/src/index.tsx new file mode 100644 index 0000000000..c03484046e --- /dev/null +++ b/website/src/index.tsx @@ -0,0 +1,14 @@ +import React from 'react'; +import ReactDOM from 'react-dom/client'; +import { RouterProvider } from 'react-router-dom'; +import '@cloudscape-design/global-styles/index.css'; +import './styles.css'; +import './cloudscape-overrides.css'; // non-themeable details (hairline borders, tab colors, split-button spacing) +import './cloudscape-theme'; // applies dstack's Cloudscape design-token overrides (runs on import, before render) +import { router } from './router'; + +ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render( + + + , +); diff --git a/website/src/pages/Home/ExploreSection.tsx b/website/src/pages/Home/ExploreSection.tsx new file mode 100644 index 0000000000..ed37bde82d --- /dev/null +++ b/website/src/pages/Home/ExploreSection.tsx @@ -0,0 +1,185 @@ +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 SpaceBetween from '@cloudscape-design/components/space-between'; +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 { docsUrl } from '../../routes'; +import { + backendConfigs, + clusterConfigs, + maxBackendYamlLines, + maxClusterYamlLines, + padYamlToLines, +} from '../../data/snippets'; + +// Core orchestration primitives shown in the "AI-native orchestration" block. +const keyConcepts = [ + { name: 'Fleets', href: docsUrl('concepts/fleets'), description: 'Provision and manage clusters across clouds, Kubernetes, and on-prem.' }, + { name: 'Dev environments', href: docsUrl('concepts/dev-environments'), description: 'Launch dev environments to be accessed by agents or from your IDE.' }, + { name: 'Tasks', href: docsUrl('concepts/tasks'), description: 'Run training and batch jobs across a single node or clusters.' }, + { name: 'Services', 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 }) { + return ( +
+ +
+ ); +} + +// 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. +function GpuMarketplaceTable() { + return ( + +
+ <>{offer.name} ({offer.memory}), isRowHeader: true }, + { id: 'price', header: '$/hour', cell: offer => offer.price }, + ]} + items={gpuOffers} + /> + + + ); +} + +// The main marketing content: a sequence of alternating documentation blocks. +export function ExploreSection() { + return ( +
+ } title="Vendor-agnostic, open-source" imageFirst> + dstack unifies fleets, dev environments, tasks, services, volumes, and gateways in one control plane for AI workloads. +
+
+ It’s built for containerized AI workloads with a simple CLI, UI, and API. No Kubernetes or Slurm hassle required. +
+ + + + ({ + id: backend.id, + label: backend.label, + content: , + }))} + /> + } + title="Bring your own clouds" + imageFirst + action={} + > + dstack natively integrates with the major GPU clouds and automates provisioning of clusters. +
+
+ Authorize dstack by providing credentials, and dstack will provision compute and schedule workloads + in your own cloud account. +
+ + ({ + id: cluster.id, + label: cluster.label, + content: , + }))} + /> + } + title="Bring on-prem clusters" + action={ + + + + + } + > + Have an existing Kubernetes cluster? Point dstack to the kubeconfig, and dstack + will schedule workloads on it as it was a cloud cluster. +
+
+ Have bare-metal servers or VMs with SSH access? Point dstack to those hosts and provide SSH credentials, and dstack will + schedule workloads on them alongside Kubernetes and cloud clusters. +
+ + +
+ ); +} + +function KeyConceptsBlock() { + return ( + + {keyConcepts.map(concept => ( + // Whole card is the link so it reads as clickable, with an ActionCard-style + // arrow. Kept as a real (open-in-new-tab / SEO) rather than Cloudscape's + // onClick-only ActionCard component. + + + +

{concept.name}

+

{highlightTerms(concept.description)}

+
+ ))} + + } + title="AI-native orchestration" + > + Managing AI infrastructure requires first-class primitives for accelerator provisioning, workload scheduling, and observability. +
+
+ dstack offers a streamlined interface for development, training, and inference built for heterogeneous AI compute. +
+ ); +} + +function GpuMarketplaceBlock() { + return ( + } + title="Access marketplace GPUs" + imageFirst + action={} + > + Don't have your own cloud accounts or on-prem clusters? No problem. You can access compute + through dstack Sky, our hosted GPU marketplace. +
+
+ It's possible to use dstack Sky alongside with your own cloud accounts or on-prem clusters. +
+ ); +} diff --git a/website/src/pages/Home/FaqSection.tsx b/website/src/pages/Home/FaqSection.tsx new file mode 100644 index 0000000000..5e8f9de6c2 --- /dev/null +++ b/website/src/pages/Home/FaqSection.tsx @@ -0,0 +1,60 @@ +import { useState } from 'react'; +import Button from '@cloudscape-design/components/button'; +import ExpandableSection from '@cloudscape-design/components/expandable-section'; +import { mainButtonStyle } from '../../cloudscape-theme'; +import { AlternatingDocBlock } from '../../components/AlternatingDocBlock'; +import { highlightTerms } from '../../components/highlightTerms'; + +const faqItems = [ + { + q: 'How does dstack differ from Slurm?', + a: 'Slurm is a battle-tested system with decades of production use in HPC environments. dstack, by contrast, is built for modern ML/AI workloads with cloud-native provisioning and a container-first architecture. While both support distributed training and batch jobs, dstack also natively supports development and production-grade inference.', + }, + { + q: 'How does dstack compare to Kubernetes?', + a: "Kubernetes is a general-purpose container orchestrator. dstack also orchestrates containers, but it provides a lightweight, streamlined interface that's purpose-built for ML. You declare dev environments, tasks, services, and fleets with simple configuration, and dstack provisions GPUs, manages clusters via fleets with fine-grained controls, and optimizes cost and utilization, all while keeping a simple CLI and UI.", + }, + { + q: 'Can I use dstack with Kubernetes?', + a: 'Yes. You can connect existing Kubernetes clusters using the Kubernetes backend and run dev environments, tasks, and services on them. Choose the Kubernetes backend if your GPUs already run on Kubernetes and your team depends on its ecosystem and tooling. Otherwise, VM-based backends (for cloud GPUs) or SSH fleets (for on-prem) are often a better fit.', + }, + { + q: 'When should I use dstack?', + a: "dstack accelerates ML development with a simple, ML-native interface: spin up dev environments, run single-node or distributed tasks, and deploy services without infrastructure overhead. It radically reduces GPU costs through smart orchestration and fine-grained fleet controls, including efficient reuse, right-sizing, and support for spot, on-demand, and reserved capacity. It's 100% interoperable with your stack, working with any open-source frameworks and tools and your own Docker images and code, across GPU clouds, Kubernetes, and on-prem GPUs.", + }, +]; + +// FAQ block: a single-open accordion of questions beside contact actions. +export function FaqSection() { + const [openQuestion, setOpenQuestion] = useState(null); + + return ( +
+ + {faqItems.map(item => ( + setOpenQuestion(detail.expanded ? item.q : null)} + > + {highlightTerms(item.a)} + + ))} + + } + title="FAQ" + action={ + + } + > + Have questions, or need help? Reach out to us on Discord or directly. + +
+ ); +} diff --git a/website/src/pages/Home/GetStartedSection.tsx b/website/src/pages/Home/GetStartedSection.tsx new file mode 100644 index 0000000000..92eafeb239 --- /dev/null +++ b/website/src/pages/Home/GetStartedSection.tsx @@ -0,0 +1,86 @@ +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 { installMethods, maxInstallLines, padYamlToLines } from '../../data/snippets'; +import { docsUrl } from '../../routes'; + +// Read-only shell snippet. Line wrapping is left off so padded snippets stay +// equal height across tabs (see padYamlToLines). +function ShellCode({ content }: { content: string }) { + return ( +
+ +
+ ); +} + +// Closing "Get started" section: the open-source install path, then the hosted/enterprise +// options under "Looking for more?". +export function GetStartedSection() { + 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.

+ +
+
+
+
+

dstack Enterprise

+

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

+ +
+
+ + } + 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 new file mode 100644 index 0000000000..7b78ad83ba --- /dev/null +++ b/website/src/pages/Home/HomePage.tsx @@ -0,0 +1,75 @@ +import Button from '@cloudscape-design/components/button'; +import { heroButtonStyle } from '../../cloudscape-theme'; +import { highlightTerms } from '../../components/highlightTerms'; +import { images, ThemedImage } from '../../data/images'; +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 scrollToResources = () => + document.getElementById('resources')?.scrollIntoView({ behavior: 'smooth', block: 'start' }); + + return ( +
+
+ +
+

+ The orchestration stack +
+ for AI infrastructure +

+

+ {highlightTerms( + 'dstack is an open-source orchestration layer for heterogeneous AI compute. ' + + 'It standardizes how workloads run across GPU clouds, Kubernetes, and on-prem clusters, ' + + 'across NVIDIA, AMD, Tenstorrent, and TPU accelerators.', + )} +

+
+ + +
+
+
+ +
+
+
+ + + + +
+
+
+ + {/* 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/pages/Home/TrustedBySection.tsx b/website/src/pages/Home/TrustedBySection.tsx new file mode 100644 index 0000000000..a056d3df04 --- /dev/null +++ b/website/src/pages/Home/TrustedBySection.tsx @@ -0,0 +1,113 @@ +import Box from '@cloudscape-design/components/box'; +import Container from '@cloudscape-design/components/container'; +import SpaceBetween from '@cloudscape-design/components/space-between'; +import { asset } from '../../asset'; +import { highlightTerms } from '../../components/highlightTerms'; + +const testimonials = [ + { + name: 'Wah Loon Keng', + title: 'Sr. AI Engineer', + company: 'Electronic Arts', + photo: '/static/quotes/keng.png', + quote: + 'With dstack, AI researchers at EA can spin up and scale experiments without touching infrastructure. It supports everything from quick prototyping to multi-node training on any cloud.', + }, + /* Replaced Aleksandr Movchan, kept hidden for reference: + { + name: 'Aleksandr Movchan', + title: 'ML Engineer', + company: 'Mobius Labs', + photo: '/static/quotes/movchan.jpg', + quote: + 'Thanks to dstack, my team can quickly tap into affordable GPUs and streamline our workflows from testing and development to full-scale application deployment.', + }, + */ + { + name: 'Konstantin Willeke', + title: 'AI Researcher', + company: 'Metamorphic', + photo: '/static/quotes/konstantin.png', + quote: + 'Fantastic tool if you have heterogeneous compute across different clusters or clouds. dstack ties it all together behind one interface, so we can run experiments without rethinking our setup.', + }, + /* Replaced Alvaro Bartolome, kept hidden for reference: + { + name: 'Alvaro Bartolome', + title: 'ML Engineer', + company: 'Argilla', + photo: '/static/quotes/alvarobartt.jpg', + quote: + "With dstack it's incredibly easy to define a configuration within a repository and run it without worrying about GPU availability. It lets you focus on data and your research.", + }, + */ + { + name: 'Dmitry Melikyan', + title: 'CEO', + company: 'Graphsignal', + photo: '/static/quotes/dmitry.jpg', + quote: + "dstack gives us a unified layer for GPU development and inference across on-prem systems and GPU clouds. It's one workflow from local experiments to production — no custom orchestration to build or maintain for each environment.", + }, + { + name: 'Park Chansung', + title: 'ML Researcher', + company: 'ETRI', + photo: '/static/quotes/chansung.jpg', + quote: + 'Thanks to dstack, I can effortlessly access the top GPU options across different clouds, saving me time and money while pushing my AI work forward.', + }, + /* Replaced Eckart Burgwedel, kept hidden for reference: + { + name: 'Eckart Burgwedel', + title: 'CEO', + company: 'Uberchord', + photo: '/static/quotes/eckart.png', + quote: + 'With dstack, running LLMs on a cloud GPU is as easy as running a local Docker container. It combines the ease of Docker with the auto-scaling capabilities of K8S.', + }, + */ + { + name: 'Nikita Shupeyko', + title: 'AI Infra', + company: 'Toffee', + photo: '/static/quotes/nikita.jpeg', + quote: + "Since we switched to dstack, we've cut the overhead of GPU-cloud orchestration by more than 50%. Running across multiple GPU clouds from a single config also let us reduce our effective GPU spend by 2–3×.", + }, + { + name: 'Jon Stevens', + title: 'CEO', + company: 'Hot Aisle', + photo: '/static/quotes/jon.jpeg', + quote: + "dstack's advantages over Slurm are clear: it's a modern, ground-up approach to running workloads at scale. If you're choosing an orchestration platform, dstack is the place to start.", + }, +]; + +// Social proof: a grid of customer testimonials shown above the "Get started" section. +export function TrustedBySection() { + return ( +
+

Trusted by AI teams

+
+ {testimonials.map(testimonial => ( + + +
+ + + {testimonial.name} + + {testimonial.title} at {testimonial.company} + + +
+ {highlightTerms(testimonial.quote)} +
+
+ ))} +
+
+ ); +} diff --git a/website/src/pages/Home/index.ts b/website/src/pages/Home/index.ts new file mode 100644 index 0000000000..0799f479a2 --- /dev/null +++ b/website/src/pages/Home/index.ts @@ -0,0 +1 @@ +export { HomePage } from './HomePage'; diff --git a/website/src/pages/Old/OldPage.tsx b/website/src/pages/Old/OldPage.tsx new file mode 100644 index 0000000000..dec0a97bf6 --- /dev/null +++ b/website/src/pages/Old/OldPage.tsx @@ -0,0 +1,287 @@ +import { ReactNode } from 'react'; +import { useNavigate } from 'react-router-dom'; +import AnchorNavigation from '@cloudscape-design/components/anchor-navigation'; +import AppLayoutToolbar from '@cloudscape-design/components/app-layout-toolbar'; +import BreadcrumbGroup from '@cloudscape-design/components/breadcrumb-group'; +import Button from '@cloudscape-design/components/button'; +import Link from '@cloudscape-design/components/link'; +import SideNavigation, { SideNavigationProps } from '@cloudscape-design/components/side-navigation'; +import { mainButtonStyle } from '../../cloudscape-theme'; +import { SiteFooter } from '../../components/SiteFooter'; +import { images } from '../../data/images'; +import { DOCS_URL, ROUTES } from '../../routes'; +import { useLayoutContext } from '../../App'; + +// Side navigation for the Old page (kept for comparison; links are illustrative anchors). +const docsNavigationItems: SideNavigationProps.Item[] = [ + { + type: 'section-group', + title: 'For designers', + items: [ + { type: 'link', text: 'Start designing', href: '#/designers/start-designing' }, + { type: 'link', text: 'Design resources', href: '#/designers/design-resources' }, + ], + }, + { type: 'divider' }, + { + type: 'section-group', + title: 'For developers', + items: [ + { type: 'link', text: 'Start developing', href: '#/developers/start-developing' }, + { + type: 'expandable-link-group', + text: 'Using Cloudscape components', + href: '#/developers/using-components', + defaultExpanded: true, + items: [ + { type: 'link', text: 'Component playground', href: '#/developers/using-components/playground' }, + { type: 'link', text: 'Sample code', href: '#/developers/using-components/sample-code' }, + ], + }, + { type: 'link', text: 'Global styles', href: '#/developers/global-styles' }, + { type: 'link', text: 'Built-in internationalization', href: '#/developers/i18n' }, + { type: 'link', text: 'AI Tools Support', href: '#/developers/ai-tools' }, + ], + }, +]; + +// The previous home page content, kept for comparison while we iterate. The side +// navigation drawer state is owned by the App layout (shared with the top nav trigger). +export function OldPage() { + const navigate = useNavigate(); + const { oldNavigationOpen, setOldNavigationOpen, theme, toggleTheme } = useLayoutContext(); + + return ( + setOldNavigationOpen(event.detail.open)} + navigationTriggerHide + navigationWidth={280} + toolsHide + maxContentWidth={Number.MAX_VALUE} + ariaLabels={{ + navigation: 'Side navigation', + navigationClose: 'Close side navigation', + navigationToggle: 'Open side navigation', + }} + navigation={ + { + event.preventDefault(); + if (event.detail.href === '#/') navigate(ROUTES.HOME); + }} + /> + } + content={ +
+ { + event.preventDefault(); + if (event.detail.href === '#/') navigate(ROUTES.HOME); + }} + /> +
+

Old page

+

The previous home page content kept for comparison while we iterate.

+
+
+
+ +
+ +
+ +
+ } + /> + ); +} + +// "On this page" right rail with scroll-spy anchors and feedback blocks. +function DocsRightRail({ + onThisPageId, + anchors, +}: { + onThisPageId: string; + anchors: Array<{ text: string; href: string; level: 1 }>; +}) { + return ( + + ); +} + +function OldHomeSections() { + return ( + <> + +

+ Cloudscape is an open source design system to create web applications. It was built for + and is used by Amazon Web Services (AWS) products and services. +

+

+ We created it in 2016 to improve the user experience across web applications owned by AWS + services, and also to help teams implement those applications faster. Since then, we have + continued enhancing the system based on customer feedback and research. Learn more{' '} + about the system. +

+
+ +

+ Each component has a playground where designers and + developers can see how the component behaves, along with sample code. To save you time and + effort when building, we offer extensive guidance on accessibility options and design + solutions. Head over to our demos for examples of Cloudscape in action. +

+
+ +

+ We publish our source code on GitHub under the Apache 2.0 License{' '} + in the cloudscape-design organization. In our{' '} + main components repository you can find information about our support + and contribution model, versioning strategy, and change logs. You can also{' '} + open issues and ask a question. +

+
+ + + + + ); +} + +function ImageTextRow({ + id, + image, + title, + children, + imageFirst = false, +}: { + id?: string; + image: string; + title: string; + children: ReactNode; + imageFirst?: boolean; +}) { + return ( +
+
+

{title}

+
{children}
+
+ +
+ ); +} + +function Overview() { + return ( +
+

Overview

+
+ + + +
+
+ ); +} + +function Stat({ value, text }: { value: string; text: string }) { + return ( +
+ {value} +

{text}

+
+ ); +} + +function CoreFeatureCards() { + const cards: Array<[string, string]> = [ + ['Light/Dark mode', images.mode], + ['Theming', images.theming], + ['Accessibility', images.accessibility], + ['Responsiveness', images.responsive], + ]; + return ( +
+

Core features

+

+ Cloudscape supports various visual modes, accessibility, responsive design, and broad browser + coverage. Services built with Cloudscape are designed for all customers, regardless of browser, + screen size, or ability. +

+
+ {cards.map(([title, image]) => ( +
+ +

+ {title} +

+
+ ))} +
+
+ ); +} + +function StartBuilding() { + return ( +
+

Start building

+
+
+

For designers

+

+ Get started{' '} + with designing accessible and intuitive interfaces. Use our{' '} + visual foundation, UX guidelines, and Figma{' '} + resources to reduce the time needed to get from project inception to + wireframe and prototype. +

+
+
+

For developers

+

+ Integrate with our system to start developing. Use our accessible + and responsive React components to quickly create high quality interfaces. +

+
+
+
+ ); +} diff --git a/website/src/pages/Old/index.ts b/website/src/pages/Old/index.ts new file mode 100644 index 0000000000..2456362322 --- /dev/null +++ b/website/src/pages/Old/index.ts @@ -0,0 +1 @@ +export { OldPage } from './OldPage'; diff --git a/website/src/router.tsx b/website/src/router.tsx new file mode 100644 index 0000000000..6b63521522 --- /dev/null +++ b/website/src/router.tsx @@ -0,0 +1,20 @@ +import { Navigate, createBrowserRouter } from 'react-router-dom'; +import { App } from './App'; +import { HomePage } from './pages/Home'; +import { OldPage } from './pages/Old'; +import { ROUTES } from './routes'; + +// Single data router. In production this app owns only `/` (the landing) — docs and blog +// are served by MkDocs on the same origin. `/old` is kept as a template for future product +// pages: reachable in dev, and harmless in production (MkDocs serves unknown paths). Stray +// paths redirect home. +export const router = createBrowserRouter([ + { + element: , + children: [ + { index: true, element: }, + { path: ROUTES.OLD, element: }, + { path: '*', element: }, + ], + }, +], { basename: import.meta.env.BASE_URL.replace(/\/$/, '') || '/' }); diff --git a/website/src/routes.ts b/website/src/routes.ts new file mode 100644 index 0000000000..dd9c5fac47 --- /dev/null +++ b/website/src/routes.ts @@ -0,0 +1,26 @@ +// Central route table + cross-links to the MkDocs-served parts of the site. +// +// This app (the landing) owns only `/`. Docs and blog are served by MkDocs on the +// SAME origin in production (`/docs`, `/blog`). For standalone landing development you +// can point those links at the live site by setting VITE_DOCS_BASE, e.g. +// VITE_DOCS_BASE=https://dstack.ai npm run dev +const SITE_BASE = (import.meta.env.VITE_DOCS_BASE ?? '').replace(/\/+$/, ''); + +export const ROUTES = { + HOME: '/', + // Kept as a template/reference for building future product pages. Reachable in dev + // (`npm run dev` at /old); not part of the integrated production deploy (where this app + // only owns `/` and MkDocs serves everything else). + OLD: '/old', +} as const; + +export type Route = (typeof ROUTES)[keyof typeof ROUTES]; + +// Cross-links into the MkDocs site (same origin unless VITE_DOCS_BASE is set). +export const DOCS_URL = `${SITE_BASE}/docs`; +export const BLOG_URL = `${SITE_BASE}/blog`; +export const TERMS_URL = `${SITE_BASE}/terms`; +export const PRIVACY_URL = `${SITE_BASE}/privacy`; + +// Deep link into the docs, e.g. docsUrl('concepts/fleets') -> `${SITE_BASE}/docs/concepts/fleets`. +export const docsUrl = (path: string) => `${DOCS_URL}/${path.replace(/^\/+/, '')}`; diff --git a/website/src/styles.css b/website/src/styles.css new file mode 100644 index 0000000000..cc6950d184 --- /dev/null +++ b/website/src/styles.css @@ -0,0 +1,1697 @@ +@font-face { + font-family: 'Geist'; + font-style: normal; + font-weight: 100 900; + font-display: swap; + src: url('./fonts/Geist-Variable.woff2') format('woff2'); +} + +@font-face { + font-family: 'Geist Pixel Square'; + font-style: normal; + font-weight: 400; + font-display: swap; + src: url('./fonts/GeistPixel-Square.woff2') format('woff2'); +} + +:root { + color-scheme: light; + --cs-text: #16191f; + --font-base: Geist, -apple-system, "system-ui", "Segoe UI", Roboto, Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue", sans-serif; + --cs-bg: #ffffff; + --cs-muted: #5f6b7a; + --cs-link: #0972d3; + --cs-border: #16191f; + --cs-panel: #f4f4f7; + --font-small: 15px; /* small body text: action-card descriptions, quotes, FAQ answers */ + --font-card-heading: 16px; /* card titles: action cards, dstack Sky/Enterprise, quote names */ + --cs-hover: rgba(22, 25, 31, 0.03); /* shared faint hover tint: normal buttons, cards, dropdown items (softened from 0.05) */ + --cs-btn-hover: #272d38; /* filled (primary) button hover fill — shared with the split-button override */ + --cs-seg-divider: rgba(255, 255, 255, 0.22); /* split-button segment divider (light line on the dark fill) */ + --cs-dark: #0f141d; + --nav-height: 4.875rem; + --frame: 80rem; + --page-gutter: clamp(1.5rem, 5.5vw, 5rem); + --section-gap: clamp(2.5rem, 4vw, 4rem); + --card-gap: 1.25rem; + --radius: 0; + --media-column: minmax(18rem, 40rem); + --doc-article-width: 49.5rem; + --doc-rail-width: 17.5rem; + --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'] { + color-scheme: dark; + --cs-text: #f2f3f3; + --cs-bg: #0f141d; + --cs-muted: #c6c6cd; + --cs-link: #58a6ff; + --cs-border: #f2f3f3; + --cs-panel: #1b212d; + --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% + ); +} + +* { + box-sizing: border-box; +} + +html { + scroll-behavior: smooth; + /* Offset anchor / scrollIntoView landings by the sticky header (banner + nav) so a targeted + section isn't hidden behind it — e.g. "Get started" scrolling to #resources. */ + scroll-padding-top: calc(var(--nav-height) + 3.25rem); +} + +body { + margin: 0; + min-width: 320px; + color: var(--cs-text); + background: var(--cs-bg, #ffffff); + font-family: var(--font-base); + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +/* Cloudscape's own font family is set via the theming API (see cloudscape-theme.ts); + this base rule covers the non-Cloudscape markup. */ + +img { + display: block; + max-width: 100%; +} + +h1, +h2, +h3, +p { + margin: 0; +} + +p { + line-height: 1.5; +} + +/* Inline highlight for key terms (e.g. "dstack"): a thin rounded outline with minor padding and + a transparent fill. Border takes a muted text color so it stays subtle and theme-adaptive. */ +.highlight { + padding: 0.06em 0.36em; + border: 0.25px solid currentColor; + border-radius: 6px; + background: transparent; + white-space: nowrap; +} + +.site-frame { + width: min(var(--frame), calc(100vw - (2 * var(--page-gutter)))); + margin: 0 auto; +} + +/* Top announcement banner. Filled with the text color and an inverted label — the same + treatment as the primary buttons — so it reads as a deliberate strip in either theme. It + sticks to the top together with the nav (see .site-header). */ +.site-banner { + display: flex; + align-items: center; + justify-content: center; + min-height: 38px; + padding: 9px 16px 7.5px; + background: var(--cs-text); + color: var(--cs-bg); + text-align: center; +} + +/* Plain inline link so the label and arrow wrap together as one centered paragraph on + narrow screens (the banner grows to contain them). */ +.site-banner__link { + color: var(--cs-bg) !important; + font-size: 16.5px; + font-weight: 300; + text-decoration: none !important; +} + +/* On dark, the banner is light-on-dark; nudge the weight up a hair for even visual weight. */ +:root[data-theme='dark'] .site-banner__link { + font-weight: 350; +} + +/* Icons follow the banner's (inverted) text color in both themes. */ +.site-banner [class*='awsui_icon'] { + color: var(--cs-bg) !important; +} + +.site-banner__arrow { + display: inline-flex; + vertical-align: middle; + margin-inline-start: 6px; + transition: transform 0.2s ease; +} + +.site-banner__link:hover .site-banner__arrow { + transform: translateX(3px); +} + +/* Banner + nav stick to the top together. Sticking the wrapper (rather than each child) + handles the banner's variable height when its text wraps on narrow screens, and keeps it + above the hero gradient (which can extend up into this area). */ +.site-header { + position: sticky; + top: 0; + z-index: 901; +} + +.site-nav { + position: relative; + z-index: 900; + height: var(--nav-height); + border-bottom: 0.5px solid var(--cs-border); + background: var(--cs-bg, #ffffff); +} + +.site-nav--home { + border-bottom: 0; + background: transparent; + -webkit-backdrop-filter: blur(5px); + backdrop-filter: blur(5px); +} + +.site-nav::after { + position: absolute; + bottom: -2px; + left: 0; + right: 0; + height: 4px; + content: ''; + background: linear-gradient(90deg, #8f41ff 0 29%, #5668ff 29% 61%, #2d49d8 61% 78%, #102f80 78% 100%); +} + +.site-nav--home::after { + display: none; +} + +/* On the Old page the nav drops the brand gradient accent for a plain 1px hairline in the + text color, matching the Cloudscape reference header. */ +.site-nav--old { + border-bottom: 1px solid var(--cs-text); +} + +.site-nav--old::after { + display: none; +} + +.site-nav__inner { + position: relative; + z-index: 1; + display: flex; + align-items: center; + gap: 20px; + height: 100%; + padding: 14px 24px; +} + +.site-logo, +.site-menu-button { + border: 0; + background: transparent; + color: var(--cs-text); + font: inherit; + font-weight: 700; + cursor: pointer; +} + +.site-logo { + display: inline-flex; + align-items: center; + gap: 8px; + flex: 0 0 auto; + font-size: 20px; + line-height: 1; +} + +.site-logo img { + width: 28px; + height: 28px; +} + +.site-logo span { + font-family: 'Geist Pixel Square', var(--font-base); + font-weight: 700; + font-size: 25px; +} + +.site-menu { + display: flex; + align-items: center; + justify-content: flex-end; + flex: 1 1 auto; + min-width: 0; +} + +.site-desktop-trigger { + flex: 0 0 auto; +} + +.site-mobile-trigger, +.site-mobile-spacer { + display: none; +} + +.site-menu-button, +.site-menu-link { + display: inline-flex; + align-items: center; + gap: 0.25rem; + min-height: 42px; + /* No horizontal padding: borderless text items are spaced purely by the nav's + SpaceBetween gap, so the whitespace to a neighboring text item matches the whitespace + to a bordered button (whose padding sits inside its border) — giving uniform gaps. */ + padding: 10px 0; + border-radius: 21px; + color: var(--cs-text); + font-weight: 700; + text-decoration: none; +} + +.site-menu-button:hover, +.site-menu-link:hover { + text-decoration: underline; + text-underline-offset: 0.2em; +} + +/* Keyboard focus only on the top-menu buttons. These are our own elements (not Cloudscape + components), so use plain values rather than Cloudscape's internal token variables. */ +.site-menu-button:focus-visible, +.site-menu-link:focus-visible { + background: var(--cs-panel); + color: var(--cs-text); +} + +.site-menu-button.active { + border: 2px solid #879596; +} + +/* "Resources" top-nav dropdown (ResourcesHoverMenu): make the ButtonDropdown trigger read + like the plain text menu links (.site-menu-link) — no border/background, bold 16px label, + underline on hover — rather than the bordered "normal" button look. */ +.site-menu-dropdown-wrap { + display: inline-flex; + align-items: center; +} +.site-menu-dropdown [class*='awsui_button'] { + border-color: transparent !important; + background: transparent !important; + color: var(--cs-text) !important; + font-size: 16px !important; /* match the .site-menu-link items (e.g. Documentation) */ + font-weight: 700 !important; + padding: 10px 0 !important; /* no horizontal padding — matches the text links */ +} +.site-menu-dropdown [class*='awsui_button']:hover { + background: transparent !important; + text-decoration: underline; + text-underline-offset: 0.2em; +} +/* The menu opens on hover, so the dropdown caret is redundant — hide it. */ +.site-menu-dropdown [class*='awsui_button'] [class*='awsui_icon'] { + display: none !important; +} + +.home-hero { + position: relative; + overflow: visible; + 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); +} + +.home-hero h2 { + margin-top: 0; + max-width: 720px; + color: var(--cs-text); + font-size: 42px; + line-height: 1.16; + font-weight: 700; +} + +.home-hero p { + margin-top: 16px; + max-width: 600px; + font-size: 17px; +} + +/* Hero CTAs sit side by side, each sized to its own label (not forced to equal width). */ +.home-hero__actions { + margin-top: 28px; + display: inline-flex; + flex-wrap: wrap; + gap: 8px; +} + +.home-hero__art { + position: absolute; + inset: 0; + z-index: 0; + opacity: 1; +} + +:root[data-theme='dark'] .home-hero__art { + opacity: 0.92; +} + +/* Centered content frame for the art, so it aligns with the columns of the blocks below. */ +.home-hero__art-frame { + position: relative; + 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 { + position: absolute; + top: 68px; + 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 + shows through behind them. */ +.home-main { + position: relative; + z-index: 1; +} + +.home-stack { + padding: 0 0 96px; +} + +.home-with-rail .docs-body { + grid-template-columns: + minmax(0, calc(100% - var(--doc-right-gap) - var(--doc-rail-width))) + var(--doc-right-gap) + var(--doc-rail-width); +} + +.home-with-rail .image-text-row, +.home-with-rail .image-text-row:not(.image-first) { + grid-template-columns: minmax(16rem, 1fr) minmax(0, 1fr); + gap: var(--doc-gap); +} + +.media-card h3, +.doc-media-card h3 { + margin-bottom: 8px; + font-size: 20px; + line-height: 1.2; +} + +.image-text-row { + display: grid; + grid-template-columns: var(--media-column) minmax(0, 1fr); + gap: var(--section-gap); + align-items: center; + margin-top: 40px; +} + +.image-text-row:not(.image-first) { + grid-template-columns: minmax(0, 1fr) var(--media-column); +} + +.image-text-row.image-first .landing-image { + order: -1; +} + +.docs-shell--old-page .image-text-row, +.docs-shell--old-page .image-text-row:not(.image-first) { + grid-template-columns: minmax(14rem, 24rem) minmax(0, 1fr); + gap: var(--doc-gap); +} + +.docs-shell--old-page .image-text-row:not(.image-first) { + grid-template-columns: minmax(0, 1fr) minmax(14rem, 24rem); +} + +.landing-image { + width: 100%; + aspect-ratio: 876 / 528; + height: auto; + object-fit: cover; + border-radius: var(--radius); +} + +.landing-copy { + max-width: 604px; +} + +.landing-copy h2, +.overview-section h2, +.start-building h2, +.docs-section > h2 { + margin-bottom: 12px; + font-size: 28px; + line-height: 1.25; +} + +/* "Get started" and "Trusted by" section headings: larger than the in-flow block + titles, closer to the hero size. */ +#resources > h2, +#trusted-by > h2 { + margin-bottom: 32px; + 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 { + border-bottom: 0; +} + +/* 1px outer frame on the FAQ block with rounded (12px) corners. ExpandableSection + has no radius token, so we border + clip the wrapper; overflow:hidden trims the items' square + corners to the radius. Internal dividers between questions stay 0.5px (see cloudscape-overrides). */ +.faq-list { + border: 1px solid var(--cs-border); + border-radius: 12px; + overflow: hidden; +} + +/* FAQ question headers. */ +.faq-list [class*="awsui_header-text"] { + font-size: 16px; +} + +.landing-copy p + p { + margin-top: 18px; +} + +.overview-section { + margin-top: 56px; +} + +.stats-grid { + display: grid; + grid-template-columns: repeat(3, 1fr); + gap: var(--card-gap); + margin-top: 20px; +} + +.stat-card { + display: grid; + grid-template-columns: 72px minmax(0, 1fr); + gap: var(--card-gap); + align-items: center; + min-height: 160px; + padding: 24px 40px; + border-radius: 0; + background: var(--cs-panel); +} + +.stat-card span { + color: #3867ff; + font-size: 44px; + line-height: 1; + font-weight: 400; +} + +.stat-card p { + max-width: 14ch; + font-size: 24px; + line-height: 1.15; +} + +.core-features { + margin-top: 36px; +} + +.core-features > h3 { + margin-bottom: 4px; + font-size: 20px; +} + +.core-features > p { + max-width: 1230px; +} + +.feature-card-grid { + display: grid; + grid-template-columns: repeat(4, 1fr); + gap: 20px; + margin-top: 32px; +} + +.media-card { + border: 1px solid var(--cs-border); /* 1px / 12px */ + border-radius: 12px; + overflow: hidden; + background: var(--cs-bg, #ffffff); +} + +.media-card img { + width: 100%; + height: 120px; + object-fit: cover; + background: #f3f5f9; +} + +.media-card h3, +.media-card p { + padding-inline: 20px; +} + +.media-card h3 { + padding-top: 16px; +} + +.media-card p { + padding-bottom: 22px; + font-size: var(--font-small); +} + +.start-building { + margin-top: 64px; + padding-top: 48px; + border-top: 0.5px solid var(--cs-border); +} + +.start-grid { + display: grid; + grid-template-columns: repeat(2, 1fr); + gap: var(--card-gap); + margin-top: 20px; +} + +.start-grid article { + min-height: 152px; + padding: 24px 20px; + border: 0.5px solid var(--cs-border); + border-radius: 0; +} + +.start-grid h3 { + margin-bottom: 18px; + font-size: 20px; +} + +.start-grid p { + font-size: 14px; +} + +.docs-shell { + min-height: calc(100vh - var(--nav-height)); +} + +.docs-main { + width: min(var(--doc-main-width), calc(100vw - (2 * var(--page-gutter)))); + padding: 1.625rem 0 6rem; +} + +.docs-shell--navigation-collapsed .docs-main { + width: min(var(--frame), calc(100vw - (2 * var(--page-gutter)))); + margin-inline: auto; +} + +.docs-body { + display: grid; + grid-template-columns: minmax(0, var(--doc-article-width)) var(--doc-right-gap) var(--doc-rail-width); + align-items: start; +} + +.docs-body--no-rail { + grid-template-columns: minmax(0, 1fr); +} + +.docs-shell--navigation-collapsed .docs-body { + grid-template-columns: + minmax(0, calc(100% - var(--doc-right-gap) - var(--doc-rail-width))) + var(--doc-right-gap) + var(--doc-rail-width); +} + +.docs-shell--old-page .docs-main { + width: min(var(--frame), 100%); + margin-inline: auto; + /* The footer is the last child here; sit it flush at the bottom of the content + column instead of leaving the article's bottom padding below it. */ + padding-bottom: 0; +} + +/* Cloudscape gives the AppLayout content region a 40px bottom margin. With the footer living + inside it, that leaves a 40px strip below the footer; drop it so the footer sits flush at the + page bottom, matching the global site footer. */ +.docs-shell--old-page [class*='awsui_main_'] { + margin-bottom: 0 !important; +} + +.docs-shell--old-page .docs-body { + grid-template-columns: + minmax(0, calc(100% - var(--doc-right-gap) - var(--doc-rail-width))) + var(--doc-right-gap) + var(--doc-rail-width); +} + +.docs-article { + grid-column: 1; +} + +.docs-right { + grid-column: 3; + position: sticky; + /* Clear the sticky header (banner + nav), matching scroll-padding-top above. */ + top: calc(var(--nav-height) + 3.25rem); + margin-top: 1.5rem; +} + +/* The section divider in the docs side nav is inset by default (unlike the header + divider); pull it out to the drawer edges so it spans the full sidebar width. */ +.docs-shell [class*='awsui_divider-default'] { + margin-left: -28px !important; + margin-right: -24px !important; +} + +/* Cloudscape's expandable nav group renders the caret before the label, indenting the parent + past its sibling links, and indents the children 40px. Pull the caret into the left gutter so + the parent label lines up with its siblings, and tighten the children to one indent level. */ +.docs-shell [class*='awsui_expandable-link-group'] [class*='awsui_expand-button'] { + margin-left: -22px !important; +} + +.docs-shell [class*='awsui_list-variant-expandable-link-group'] { + padding-left: 20px !important; +} + +.docs-title { + margin-top: 1rem; + padding-bottom: 1.125rem; + border-bottom: 0.5px solid var(--cs-border); +} + +.docs-title h1 { + font-size: 36px; + line-height: 1.2; +} + +.docs-title p { + margin-top: 8px; + color: var(--cs-muted); + font-size: 18px; +} + +.docs-section { + padding: 1.5rem 0 3rem; + border-bottom: 0.5px solid var(--cs-border); +} + +.docs-section:last-child { + border-bottom: 0; +} + +/* No divider between the explore section and Access GPU marketplace below it. */ +.docs-section.explore-section { + border-bottom: 0; +} + +.doc-alternating { + display: grid; + grid-template-columns: minmax(16rem, 1fr) minmax(0, 1fr); + gap: var(--doc-gap); + align-items: center; + margin-top: 18px; +} + +.doc-alternating + .doc-alternating { + margin-top: 48px; +} + +.doc-alternating:not(.image-first) { + grid-template-columns: minmax(0, 1fr) minmax(16rem, 1fr); +} + +.doc-alternating:not(.image-first) .doc-visual { + order: 2; +} + +.doc-visual { + min-width: 0; +} + +/* CodeView has no prop to disable its built-in fill; let the Container surface show through. */ +.code-snippet > div { + 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; +} + +/* 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 { + 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; +} + + +.doc-alternating img { + width: 100%; + aspect-ratio: 980 / 640; + height: auto; + object-fit: cover; + border-radius: var(--radius); +} + +.doc-alternating img.doc-diagram { + object-fit: contain; +} + +.doc-diagram--dark { + display: none; +} + +:root[data-theme='dark'] .doc-diagram--light { + display: none; +} + +:root[data-theme='dark'] .doc-diagram--dark { + display: block; +} + +.doc-alternating h2 { + margin-bottom: 12px; + font-size: 28px; +} + +/* Body copy in the alternating marketing blocks (normal page text). */ +.doc-alternating p { + font-size: 17px; +} + +/* Action-card (concept) descriptions are a step smaller than normal text. Higher specificity + than `.doc-alternating p` so it wins (the cards sit inside an alternating block). This is the + shared small-text size (--font-small), reused for quotes, FAQ answers, and popup descriptions. */ +.concept-grid .media-card p { + font-size: var(--font-small); +} + +/* "Vendor-agnostic, open-source" architecture diagram (components/ArchitectureDiagram.tsx), + rebuilt from a static SVG into HTML/CSS and sized 1:1 with it. Every dimension is in cqw + (% of the diagram's own width, resolved against the .arch-diagram-wrap container) so the + whole thing scales proportionally like the original SVG. Source design width is 901.8 + units, so 1 unit ≈ 0.111cqw. Logos are monochrome + theme-adaptive (see .arch-logo). */ +.arch-diagram-wrap { + container-type: inline-size; + width: 100%; +} + +.arch-diagram { + display: flex; + flex-direction: column; + gap: 2.66cqw; /* 24u between the four layers */ + width: 100%; + color: var(--cs-text); + --arch-border-width: max(1px, 0.22cqw); /* placeholder badge stroke */ + --arch-gradient: linear-gradient(45deg, #002aff, #002aff, #e165fe); +} + +/* Dotted outline: an inline with one rounded rect, stroked with the exact 2-on / 6-off + dash. Geometry is set in CSS (modern browsers allow x/y/width/height/rx as CSS so we can inset + by the half stroke with calc — no edge clipping, crisp dots) and the stroke takes the text + color so it matches the labels and flips with the theme. */ +.arch-dash { + position: absolute; + inset: 0; + width: 100%; + height: 100%; + overflow: visible; + pointer-events: none; +} +.arch-dash rect { + x: 0.75px; + y: 0.75px; + width: calc(100% - 1.5px); + height: calc(100% - 1.5px); + rx: 12px; + fill: none; + stroke: var(--arch-dash-stroke, var(--cs-text)); + stroke-width: 1.5px; + stroke-dasharray: 2 6; + stroke-linecap: butt; +} + +/* Top + bottom three-up rows share one grid so cells line up. Ratio 487.3 : 191.8 : 191.8. */ +.arch-row { + display: grid; + grid-template-columns: 2.54fr 1fr 1fr; + gap: 1.72cqw; /* 15.5u */ +} + +.arch-cell { + position: relative; + display: flex; + align-items: center; + gap: 1.55cqw; + min-height: 9.83cqw; /* 88.7u */ + padding: 0 2cqw; + border-radius: 12px; +} + +.arch-cell--center { + justify-content: center; +} + +/* Centered labels (e.g. "On-prem clusters") may wrap rather than overflow the narrow column. */ +.arch-cell--center .arch-cell__label { + white-space: normal; + text-align: center; +} + +.arch-cell--full { + min-height: 9.83cqw; +} + +/* "Any GPU cloud": logos left, label pushed to the right edge. */ +.arch-cell--gpu { + justify-content: space-between; +} + +/* "Any hardware": label pinned left, logos centered across the full row. */ +.arch-cell--hw { + justify-content: center; +} +.arch-cell--hw > .arch-cell__label { + position: absolute; + left: 2cqw; +} + +.arch-cell__label { + font-size: 2.55cqw; + font-weight: 500; + line-height: 1.2; + white-space: nowrap; +} + +/* Orchestration layer — gradient band. Title gets more room above the cells than below + (≈73 vs ≈31u); cells inset ≈5% each side. */ +.arch-orchestration { + display: flex; + flex-direction: column; + align-items: center; + gap: 3cqw; + padding: 3.2cqw 5.07cqw 3.48cqw; + border-radius: 1.6cqw; + background: var(--arch-gradient); + color: #fff; +} + +/* Same size/weight as the cell labels (e.g. "Any framework"). */ +.arch-orchestration__title { + font-size: 2.55cqw; + font-weight: 500; + text-align: center; +} + +/* Five primitives — NOT equal width: "Dev environments" (2nd) is wider so its label fits. */ +.arch-orchestration__cells { + display: grid; + grid-template-columns: 1fr 1.34fr 1fr 1fr 1fr; + gap: 1.26cqw; /* 11.4u */ + width: 100%; +} + +.arch-subcell { + position: relative; + display: flex; + align-items: center; + justify-content: center; + min-height: 5.54cqw; /* 50u */ + padding: 0 0.8cqw; + border-radius: 12px; + text-align: center; + font-size: 2.25cqw; + font-weight: 500; + line-height: 1.2; + --arch-dash-stroke: #fff; /* white dotted outline on the gradient */ +} + +.arch-logos { + display: inline-flex; + align-items: center; + gap: 1.15cqw; /* ~10u between logos in the SVG */ + flex-wrap: nowrap; /* keep each cell's logos on one line (mobile reflow re-enables wrap) */ +} + +/* Real logo: source SVG (or webp) used as a mask filled with the current text color, so it reads + monochrome and flips with the theme. The mask image URL is the only inline bit (it needs the + runtime base path); size + aspect live in the per-logo classes below. */ +.arch-logo { + display: inline-block; + background-color: currentColor; + -webkit-mask-repeat: no-repeat; + mask-repeat: no-repeat; + -webkit-mask-position: center; + mask-position: center; + -webkit-mask-size: contain; + mask-size: contain; +} + +/* Per-logo height (cqw, so it scales) + aspect from each source's viewBox. Wide marks (AWS, + Meta, NVIDIA) are set shorter so they don't dominate; tall ones (PyTorch) a touch taller. */ +.arch-logo--aws { height: 4.05cqw; aspect-ratio: 304 / 182; } +.arch-logo--gcp { height: 4.55cqw; aspect-ratio: 256 / 206; } +.arch-logo--lambda { height: 4.9cqw; aspect-ratio: 1; } +.arch-logo--nebius { height: 4.6cqw; aspect-ratio: 1; } +.arch-logo--runpod { height: 5.04cqw; aspect-ratio: 120 / 130; } +.arch-logo--kubernetes { height: 4.98cqw; aspect-ratio: 1; } +.arch-logo--pytorch { height: 5.2cqw; aspect-ratio: 90.3 / 109.1; } +.arch-logo--vllm { height: 4.8cqw; aspect-ratio: 1; } +.arch-logo--sglang { height: 4.8cqw; aspect-ratio: 596 / 683; } +.arch-logo--meta { height: 3.7cqw; aspect-ratio: 287.56 / 191; } +.arch-logo--huggingface { height: 5.1cqw; aspect-ratio: 1; } +.arch-logo--nvidia { height: 3.7cqw; aspect-ratio: 271.7 / 179.7; } +.arch-logo--amd { height: 4.6cqw; aspect-ratio: 1600 / 1408; } +.arch-logo--tenstorrent { height: 4.8cqw; aspect-ratio: 170 / 169; } +.arch-logo--tpu { height: 4.2cqw; aspect-ratio: 256 / 206; } /* shares the GCP mark, sized down a touch */ + +/* Placeholder badge (fallback if a logo ever lacks a source). Overrides the mask fill. */ +.arch-logo--placeholder { + display: inline-flex; + align-items: center; + justify-content: center; + width: auto; + /* height + min-width set inline per logo */ + padding: 0 0.6cqw; + border: var(--arch-border-width) dashed color-mix(in srgb, var(--cs-text) 42%, transparent); + border-radius: 0.7cqw; + background: none; + font-size: 1.5cqw; + font-weight: 600; + letter-spacing: 0.02em; + color: var(--cs-muted); +} + +/* Safety net for very narrow containers (phones): stop shrinking, stack the rows, drop the + primitives to two columns, and switch to fixed legible type. */ +@container (max-width: 360px) { + .arch-diagram { gap: 12px; } + /* Two columns: the wide first cell ("Any framework" / "Any cloud") spans the full width on its + own row, and the two narrower cells pair up on the row below. */ + .arch-row { grid-template-columns: 1fr 1fr; gap: 8px; } + .arch-row > :first-child { grid-column: 1 / -1; } + .arch-cell { min-height: 52px; padding: 10px 14px; gap: 12px; border-width: 1px; border-radius: 8px; } + .arch-cell__label { font-size: 14px; white-space: normal; } + /* "Any hardware": label left, logos pushed to the right (the desktop layout centers them). */ + .arch-cell--hw { justify-content: space-between; } + .arch-cell--hw > .arch-cell__label { position: static; } + .arch-orchestration { gap: 12px; padding: 16px; border-radius: 12px; } + .arch-orchestration__title { font-size: 15px; } + .arch-orchestration__cells { grid-template-columns: repeat(2, 1fr); gap: 8px; } + .arch-subcell { min-height: 44px; font-size: 13px; border-width: 1px; border-radius: 7px; } + .arch-logos { gap: 12px; } + /* !important to beat the per-logo cqw heights. */ + .arch-logo { height: 22px !important; } + .arch-logo--placeholder { height: 22px !important; min-width: 22px !important; font-size: 12px; border-width: 1px; border-radius: 5px; } +} + +.doc-card-grid { + display: grid; + grid-template-columns: repeat(3, 1fr); + gap: var(--card-gap); + margin-top: 24px; +} + +.doc-media-card img { + height: 98px; +} + +.doc-media-card h3 { + padding-top: 24px; +} + +.doc-media-card { + min-height: 272px; +} + +.alert-block { + margin-top: 1.5rem; +} + +.figma-card { + width: min(24rem, 100%); + margin-top: 1.5rem; + border: 1px solid var(--cs-border); /* 1px / 12px */ + border-radius: 12px; + overflow: hidden; + background: var(--cs-bg, #ffffff); +} + +.figma-card img { + width: 100%; + aspect-ratio: 980 / 560; + height: auto; + object-fit: cover; +} + +.figma-card div { + padding: 24px; +} + +.figma-card h3 { + margin-bottom: 12px; + font-size: var(--font-card-heading); +} + +.figma-card p { + margin-bottom: 20px; + font-size: var(--font-small); +} + +/* Core-concept cards: 2x2 grid inside the AlternatingDocBlock visual slot. */ +.concept-grid { + display: grid; + grid-template-columns: repeat(2, 1fr); + gap: var(--card-gap); +} + +/* Each concept card is a whole-card link to the docs: text-colored (not link-blue), + no underline, with a subtle background on hover to signal it's clickable. */ +.concept-grid .media-card { + display: block; + position: relative; /* for the arrow + the dotted border overlay */ + color: var(--cs-text); + text-decoration: none !important; + transition: background 0.15s ease; + border: none; /* dotted outline is drawn by the shared (.arch-dash) instead */ +} + +.concept-grid .media-card:hover { + background: var(--cs-hover); +} + +.concept-grid .media-card h3 { + font-size: var(--font-card-heading); + padding-right: 44px; /* clear the arrow */ +} + +/* Quote author name reuses the card-heading size. */ +.testimonial-name { + font-size: var(--font-card-heading) !important; +} + +/* ActionCard-style navigation arrow, top-right. */ +.concept-card__arrow { + position: absolute; + top: 16px; + right: 18px; + display: inline-flex; + color: var(--cs-text); +} + +/* Product cards reusing the figma-card style. Used inside an AlternatingDocBlock + visual slot, so it stays vertically centered (no top margin). */ +.product-grid { + display: grid; + grid-template-columns: repeat(3, 1fr); + gap: var(--card-gap); +} + +/* Two-up variant: dstack Sky and dstack Enterprise side by side. */ +.product-grid--pair { + grid-template-columns: repeat(2, 1fr); +} + +/* Testimonial cards in the "Trusted by" section. */ +.testimonial-grid { + display: grid; + grid-template-columns: repeat(3, 1fr); + gap: var(--card-gap); + margin-top: 24px; +} + +/* Small body text — quotes and FAQ answers — reuse the action-card description's size, line + height, and color so they read identically. !important to beat Cloudscape's boosted classes. */ +.testimonial-grid p, +.faq-list [class*='awsui_content'] { + font-size: var(--font-small) !important; + line-height: 1.5 !important; + color: var(--cs-text) !important; +} + + +.testimonial-person { + display: flex; + align-items: center; + gap: 12px; +} + +.testimonial-photo { + width: 48px; + height: 48px; + border-radius: 50%; + object-fit: cover; + flex: 0 0 auto; +} + +.product-grid .figma-card { + width: auto; + margin-top: 0; +} + +.right-rail-block { + margin-top: 32px; + padding-top: 28px; + border-top: 0.5px solid var(--cs-border); +} + +.docs-right > h2 { + margin-bottom: 0.5rem; + font-size: 1rem; + line-height: 1.4; +} + +.right-rail-block h2 { + margin-bottom: 8px; + font-size: 18px; +} + +.right-rail-block p { + margin-bottom: 10px; + color: var(--cs-muted); +} + +.site-footer { + position: relative; + border-top: 0.5px solid var(--cs-border); + background: var(--cs-bg, #ffffff); +} + +/* On home, drop the divider line and white block so the footer blends into the + colored band above (mirrors the transparent, borderless home header). */ +.site-footer--home { + border-top: 0; + background: transparent; +} + +/* Single colored band: rises from the bottom of the page up toward the divider (which + crops it visually), deepening in color toward the footer. Painted behind .home-main so + the content and cards stay clean on top. */ +.site-footer--home::before { + position: absolute; + left: 0; + right: 0; + top: -28rem; + bottom: 0; + z-index: 0; + content: ''; + pointer-events: none; + background: linear-gradient( + 180deg, + rgba(176, 65, 255, 0) 0%, + rgba(176, 65, 255, 0.1) 55%, + rgba(96, 106, 255, 0.22) 100% + ); +} + +.footer-content { + position: relative; + z-index: 1; + padding-top: 48px; + font-size: 13px; +} + +/* The Old page renders the footer inside the AppLayout content column (so the side nav runs + full-height beside it). Fill the column instead of the centered site frame, and reserve + space on the right so the last link column isn't flush with the edge (matches the column gap). */ +.docs-main .footer-content { + width: auto; + max-width: none; + margin-inline: 0; +} + +/* Drop the divider line and the opaque block: Cloudscape paints the content area a slightly + different grey than --cs-bg, so an opaque footer reads as a mismatched rectangle (most + visible in dark mode). Going transparent + borderless lets it blend into the column. */ +.docs-main .site-footer { + border-top: 0; + background: transparent; +} + +/* Top row: brand block (logo + social) on the left, the link group flush to the + right edge (justify-content on this outer row), so the columns themselves keep a + tight content width + fixed gap rather than being stretched apart. */ +.footer-top { + display: flex; + flex-wrap: wrap; + justify-content: space-between; + gap: 48px; + padding-bottom: 32px; +} + +.footer-brand { + display: flex; + flex-direction: column; + gap: 20px; + min-width: 0; +} + +/* Link columns: each sized to its content, separated by a fixed gap. */ +.footer-links { + display: flex; + flex-wrap: wrap; + gap: 32px 72px; +} + +.footer-brand__logo { + display: inline-flex; + align-items: center; + gap: 8px; + width: fit-content; + color: var(--cs-text); + font-size: 20px; + font-weight: 700; + line-height: 1; + text-decoration: none !important; +} + +.footer-brand__logo img { + width: 28px; + height: 28px; +} + +.footer-brand__logo span { + font-family: 'Geist Pixel Square', var(--font-base); + font-weight: 700; + font-size: 25px; +} + +.footer-channels { + display: flex; + gap: 12px; + margin: 0; + padding: 0; + list-style: none; +} + +.footer-channel-link { + display: inline-flex; + color: var(--cs-text) !important; + text-decoration: none !important; +} + +.footer-channel-link svg { + display: block; +} + +/* Column captions: match the top-menu link size (16px); links sit a step below. */ +.footer-col__heading { + margin: 0 0 14px; + font-size: 16px; + font-weight: 700; + color: var(--cs-text); +} + +/* Bottom bar: theme toggle + copyright, separated from the columns by a divider. */ +.footer-bar { + display: flex; + align-items: center; + gap: 10px; + min-width: 0; + min-height: 64px; +} + +/* The icon button's horizontal padding is removed via its `style` prop (see SiteFooter); + this just nudges the glyph up onto the copyright baseline. */ +.footer-theme-toggle svg { + transform: translateY(-1.5px); +} + +.footer-copyright { + text-align: left; +} + +.home-stack a, +.docs-article a, +.right-rail-block a, +.site-footer a { + text-decoration-line: underline !important; + text-decoration-color: currentColor !important; + text-underline-offset: 0.08em; +} + +.home-stack a:hover, +.docs-article a:hover, +.right-rail-block a:hover, +.site-footer a:hover { + text-decoration-thickness: 2px !important; +} + +/* The rail's "Need help?" Discord CTA is a Cloudscape Button (renders an ); keep the + link-underline rules above from underlining it, matching the landing's Discord button. */ +.right-rail-block a[class*='awsui_button'] { + text-decoration-line: none !important; +} + +/* Footer column links: full-strength text, same 16px as the column headings, no underline + until hover. */ +.footer-links a { + color: var(--cs-text) !important; + /* Cloudscape sets 14px via its boosted awsui_font-size-body-m class, so override + with !important to match the 16px column headings. */ + font-size: 16px !important; + text-decoration-line: none !important; +} + +.footer-links a:hover { + text-decoration-line: underline !important; +} + +/* The brand logo and social icons are s inside .site-footer, so the global + footer-link underline rule applies to them — override it (higher specificity). */ +.site-footer .footer-brand__logo, +.site-footer .footer-channel-link { + text-decoration: none !important; +} + +/* On home the divider would sit on the gradient band; soften it so the footer + keeps blending into the colored band above. */ +.site-footer--home .footer-bar { + border-top-color: transparent; +} + +.home-stack p a, +.docs-article p a { + font-size: inherit !important; + line-height: inherit !important; +} + +/* Footer responsive: below the desktop width the columns stack on top, with the brand + (logo + social) below them — the links are the primary footer nav, so they lead. */ +@media (max-width: 1100px) { + .footer-top { + flex-direction: column; + gap: 32px; + } + + /* Links first, brand (logo + social icons) second. */ + .footer-brand { + order: 2; + } + + .footer-links { + width: 100%; + gap: 28px 56px; + } +} + +/* Phones: four columns no longer fit in one row, and three would orphan the fourth, + so drop to a clean two-up grid. */ +@media (max-width: 720px) { + .footer-links { + display: grid; + grid-template-columns: repeat(2, minmax(0, 1fr)); + gap: 28px 32px; + } +} + +@media (max-width: 1100px) { + .site-frame { + width: calc(100vw - 48px); + } + + .home-hero__content { + padding: 64px 0; + } + + .home-hero__art { + opacity: 0.85; + } + + .hero-slice { + top: 24px; + } + + .feature-card-grid, + .stats-grid, + .start-grid, + .doc-card-grid, + .product-grid, + .concept-grid, + .testimonial-grid { + grid-template-columns: 1fr; + } + + .image-text-row, + .image-text-row:not(.image-first), + .home-with-rail .image-text-row, + .home-with-rail .image-text-row:not(.image-first), + .docs-shell--old-page .image-text-row, + .docs-shell--old-page .image-text-row:not(.image-first), + .doc-alternating, + .doc-alternating:not(.image-first) { + grid-template-columns: 1fr; + } + + .image-text-row.image-first .landing-image { + order: initial; + } + + /* Stacked on mobile, always lead with the text block; the visual follows below + (regardless of the desktop image-first / image-last alternation). */ + .doc-alternating .doc-visual { + order: 2; + } + + /* Tighter gap between blocks once they stack into a single column. */ + .doc-alternating + .doc-alternating { + margin-top: 32px; + } + + .landing-image, + .doc-alternating img { + width: 100%; + height: auto; + } + + .docs-shell { + display: block; + } + + .docs-main { + width: calc(100vw - 48px); + margin: 0 auto; + padding-top: 24px; + } + + .docs-body { + display: flex; + flex-direction: column; + } + + .docs-right { + display: block; + order: -1; + position: static; + width: 100%; + margin: 1.5rem 0 2rem; + } + + .right-rail-block { + display: none; + } +} + +@media (max-width: 640px) { + .site-nav { + height: 54px; + } + + .site-nav__inner { + position: relative; + justify-content: space-between; + gap: 0; + width: 100%; + padding: 0 13px; + } + + .site-menu { + display: none; + } + + .site-mobile-trigger, + .site-mobile-spacer { + display: block; + flex: 0 0 28px; + } + + .site-desktop-trigger { + display: none; + } + + .site-logo { + position: absolute; + left: 50%; + transform: translateX(-50%); + } + + .home-hero { + display: flex; + flex-direction: column; + } + + .home-hero__content { + order: 1; + padding: 64px 0 24px; + } + + /* On phones the hero artwork moves out of the hero and down above the footer: hide the + top instance and show the relocated copy (centered, in normal flow). */ + .home-hero__art { + display: none; + } + + .home-hero-mobile-art { + display: block; + margin: 20px 0 4px; + } + + .home-hero-mobile-art .hero-slice { + position: relative; + top: auto; + right: auto; + width: min(25rem, 88vw); + margin: 0 auto; + } + + /* Hero CTAs share one full-width row on phones — two equal columns spanning the width. */ + .home-hero__actions { + display: grid; + grid-template-columns: 1fr 1fr; + width: 100%; + } + /* Each button fills its half-column; center the label and trim the desktop 34px side + padding so both fit. !important: Cloudscape sets text-align:start via a boosted rule. */ + .home-hero__actions [class*='awsui_button'] { + width: 100%; + padding-inline: 16px !important; + text-align: center !important; + } + + .site-mobile-navigation { + position: absolute; + top: calc(100% + 4px); + left: 0; + right: 0; + z-index: -1; + max-height: calc(100vh - 58px); + overflow: auto; + padding: 20px; + border-bottom: 0.5px solid var(--cs-border); + background: var(--cs-bg, #ffffff); + } + + .home-hero h1 { + font-size: 36px; + } + + .home-hero h2 { + font-size: 32px; + } + + .home-stack { + padding-top: 0; + } + + .stat-card { + grid-template-columns: 1fr; + gap: 10px; + } + + .figma-card, + .figma-card img { + width: 100%; + } + + .footer-content { + padding-top: 32px; + } + + .footer-top { + padding-bottom: 24px; + } + + .footer-bar { + flex-wrap: wrap; + gap: 12px; + } +} diff --git a/website/src/theme.ts b/website/src/theme.ts new file mode 100644 index 0000000000..589fc870bd --- /dev/null +++ b/website/src/theme.ts @@ -0,0 +1,33 @@ +import { useEffect, useState } from 'react'; +import { Mode, applyMode } from '@cloudscape-design/global-styles'; + +export type ThemeMode = 'light' | 'dark'; + +const STORAGE_KEY = 'dstack-theme'; + +// Defaults to light; remembers the visitor's explicit choice. +function getInitialTheme(): ThemeMode { + return localStorage.getItem(STORAGE_KEY) === 'dark' ? 'dark' : 'light'; +} + +// Tracks light/dark mode and applies it to the Cloudscape global styles + the document. +export function useTheme() { + const [theme, setTheme] = useState(getInitialTheme); + + useEffect(() => { + const root = document.documentElement; + const body = document.body; + root.dataset.theme = theme; + body.classList.add('awsui-visual-refresh'); + applyMode(theme === 'dark' ? Mode.Dark : Mode.Light); + }, [theme]); + + const toggleTheme = () => + setTheme(current => { + const next = current === 'light' ? 'dark' : 'light'; + localStorage.setItem(STORAGE_KEY, next); + return next; + }); + + return { theme, toggleTheme }; +} diff --git a/website/src/vite-env.d.ts b/website/src/vite-env.d.ts new file mode 100644 index 0000000000..11f02fe2a0 --- /dev/null +++ b/website/src/vite-env.d.ts @@ -0,0 +1 @@ +/// diff --git a/website/tsconfig.json b/website/tsconfig.json new file mode 100644 index 0000000000..36197f5db2 --- /dev/null +++ b/website/tsconfig.json @@ -0,0 +1,21 @@ +{ + "compilerOptions": { + "target": "ES2020", + "useDefineForClassFields": true, + "lib": ["DOM", "DOM.Iterable", "ES2020"], + "allowJs": false, + "skipLibCheck": true, + "esModuleInterop": true, + "allowSyntheticDefaultImports": true, + "strict": true, + "forceConsistentCasingInFileNames": true, + "module": "ESNext", + "moduleResolution": "bundler", + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "react-jsx" + }, + "include": ["src"], + "references": [] +} diff --git a/website/vite.config.ts b/website/vite.config.ts new file mode 100644 index 0000000000..b4a54b2c88 --- /dev/null +++ b/website/vite.config.ts @@ -0,0 +1,17 @@ +import { defineConfig } from 'vite'; +import react from '@vitejs/plugin-react'; + +// The deploy path is injected via BASE_PATH (set by CI from the repo name), so nothing +// in the app hardcodes where it's hosted. Locally (dev/build/preview) it stays at root. +// +// `assetsDir` is namespaced to `website-assets/` (not Vite's default `assets/`) so the +// build can be overlaid onto the MkDocs `site/` output without colliding with MkDocs's +// own `/assets/...` tree. Public files live under `public/static/` for the same reason — +// after the overlay the only root file this app contributes is `index.html`. +export default defineConfig({ + base: process.env.BASE_PATH || '/', + plugins: [react()], + build: { + assetsDir: 'website-assets', + }, +});