From 72a4da25b0d16d991b64817c4f2b8d14e1de0bb6 Mon Sep 17 00:00:00 2001 From: Satya Date: Wed, 14 Oct 2026 19:37:51 +0800 Subject: [PATCH 01/11] Docs: Homebrew setup and devkit.properties user settings - New Getting Started page for the Homebrew formulas (yaci-devkit, yaci-devkit-beta): install, switching from npm, upgrade, uninstall - New User Settings page for ~/.yaci-cli/devkit.properties: read by the Homebrew, npm and zip distributions (not Docker), precedence, and when to re-create the devnet - Link both from the introduction, npm, zip and known-issues pages; macOS 15 in the introduction Co-Authored-By: Claude Opus 5.5 --- docs/pages/_meta.json | 1 + docs/pages/getting-started/_meta.json | 5 +- docs/pages/getting-started/homebrew.mdx | 121 ++++++++++++++++++++++++ docs/pages/getting-started/npm.mdx | 6 +- docs/pages/getting-started/zip.mdx | 6 ++ docs/pages/introduction.mdx | 16 +++- docs/pages/known-issues.mdx | 5 +- docs/pages/user-settings.mdx | 58 ++++++++++++ 8 files changed, 211 insertions(+), 7 deletions(-) create mode 100644 docs/pages/getting-started/homebrew.mdx create mode 100644 docs/pages/user-settings.mdx diff --git a/docs/pages/_meta.json b/docs/pages/_meta.json index d80b4beb..7bff63d6 100644 --- a/docs/pages/_meta.json +++ b/docs/pages/_meta.json @@ -15,6 +15,7 @@ "mcp-integration": "MCP Integration", "commands": "Yaci CLI Commands", "node-configuration": "Node Configuration", + "user-settings": "User Settings", "multi-node-setup": "Multi-node setup", "rollback-testing": "Rollback Testing", "ci-integration": "CI/CD Integration", diff --git a/docs/pages/getting-started/_meta.json b/docs/pages/getting-started/_meta.json index f2c3726a..78d3d5e3 100644 --- a/docs/pages/getting-started/_meta.json +++ b/docs/pages/getting-started/_meta.json @@ -1,5 +1,6 @@ { "docker": "Docker Setup", - "zip": "Zip Setup", - "npm": "NPM Setup" + "zip": "Zip Setup", + "npm": "NPM Setup", + "homebrew": "Homebrew Setup" } \ No newline at end of file diff --git a/docs/pages/getting-started/homebrew.mdx b/docs/pages/getting-started/homebrew.mdx new file mode 100644 index 00000000..b7b7ca1c --- /dev/null +++ b/docs/pages/getting-started/homebrew.mdx @@ -0,0 +1,121 @@ +import { Callout } from 'nextra/components' + +# Homebrew Setup + +The Homebrew distribution installs the native Yaci CLI and its configuration with `brew`, and keeps it up to date with `brew upgrade`. It provides the same `yaci-devkit` command as the [NPM distribution](/getting-started/npm), without needing Node.js. + +## Prerequisites + +### Supported Platforms + +1. **macOS 15 (Arm64) or later** +2. **Linux x86**: Tested on Ubuntu 22.04 and later ([Homebrew on Linux](https://docs.brew.sh/Homebrew-on-Linux)) + +### Requirements + +- **Homebrew**: [brew.sh](https://brew.sh) +- **Available ports**: Default ports should be free (3001, 8080, 10000) + +For Ogmios and Kupo, ensure ports 1337 and 1442 are also available if you plan to use these services. + + +PV11-compatible Ogmios/Kupo binaries are currently published only as Linux x86_64 builds. On macOS arm64, those specific Ogmios/Kupo defaults cannot run directly. Use Docker as a workaround, or set custom `ogmios.url` / `kupo.url` values when compatible native builds are available. + + +## Installation + +For latest stable version: +```bash +brew install bloxbean/tap/yaci-devkit +``` + +For latest beta version: +```bash +brew install bloxbean/tap/yaci-devkit-beta +``` + +Both formulas install the same `yaci-devkit` command, so install only one of them. To switch, uninstall the other first: + +```bash +brew uninstall yaci-devkit-beta +brew install bloxbean/tap/yaci-devkit +``` + +Check that the command comes from Homebrew: + +```bash +which yaci-devkit +``` + +It should print `/opt/homebrew/bin/yaci-devkit` on macOS or `/home/linuxbrew/.linuxbrew/bin/yaci-devkit` on Linux. + + +**Switching from NPM?** An NPM install also provides a `yaci-devkit` command, and an older `sudo npm install -g` puts it in `/usr/local/bin`, which usually comes before Homebrew on the `PATH`. Remove it first: + +```bash +npm uninstall -g @bloxbean/yaci-devkit +# for an install made with sudo: +sudo npm uninstall -g --prefix /usr/local @bloxbean/yaci-devkit +``` + +Devnets and downloaded components in `$HOME/.yaci-cli` are kept, and are used by the Homebrew install too. + + +### Upgrade + +```bash +brew update +brew upgrade yaci-devkit +``` + +### Uninstall + +```bash +brew uninstall yaci-devkit +``` + +This does not remove `$HOME/.yaci-cli` (devnets, downloaded components and `devkit.properties`). + +## Getting Started + +The `yaci-devkit` command takes the same options as with the NPM distribution: + +```bash +# Devnet with Cardano node only +yaci-devkit up + +# Devnet with Yaci Store (Blockfrost-compatible APIs) +yaci-devkit up --enable-yaci-store + +# Sub-second block time (200ms) +yaci-devkit up --enable-yaci-store --block-time 0.2 --slot-length 0.2 + +# Devnet with the interactive Yaci CLI prompt +yaci-devkit up --interactive +``` + +To see all supported options: +```bash +yaci-devkit up --help +``` + +See the [NPM Setup](/getting-started/npm#basic-usage) page for more examples, service URLs and CLI commands. + +Yaci Viewer is not included. Install it separately through NPM: see [Install Yaci Viewer](/getting-started/npm#step-2-install-yaci-viewer-optional). + +## Configuration + +The bundled `application.properties`, `node.properties` and `download.properties` are inside the Homebrew installation and are replaced on every upgrade. Put your settings in `$HOME/.yaci-cli/devkit.properties` instead: + +```properties +yaci.store.enabled=true +prometheus.port=12799 +``` + +See [User Settings](/user-settings) for details. + +## Next Steps + +- **Learn all commands**: Check out [Yaci CLI Commands](/commands) +- **CI/CD integration**: Set up [automated testing](/ci-integration) +- **Compare options**: Consider [Docker Setup](/getting-started/docker) for local development diff --git a/docs/pages/getting-started/npm.mdx b/docs/pages/getting-started/npm.mdx index e5283cbe..d06ae2be 100644 --- a/docs/pages/getting-started/npm.mdx +++ b/docs/pages/getting-started/npm.mdx @@ -177,6 +177,10 @@ yaci-devkit up --port 3002 --interactive yaci-devkit up -e 30 --interactive ``` +## Configuration + +The bundled configuration files are inside the NPM package and are replaced on every upgrade. Put your own settings, for example `yaci.store.enabled=true` or `prometheus.port=12799`, in `$HOME/.yaci-cli/devkit.properties`. See [User Settings](/user-settings). + ## Yaci Viewer Setup ### Starting Yaci Viewer @@ -312,4 +316,4 @@ See [Known Issues](../known-issues#devnet-node-does-not-start-while-another-card - **Learn all commands**: Check out [Yaci CLI Commands](/commands) - **CI/CD integration**: Set up [automated testing](/ci-integration) -- **Compare options**: Consider [Docker Setup](/getting-started/docker) for local development +- **Compare options**: Consider [Docker Setup](/getting-started/docker) for local development, or [Homebrew Setup](/getting-started/homebrew) to install without Node.js diff --git a/docs/pages/getting-started/zip.mdx b/docs/pages/getting-started/zip.mdx index c5f6b825..a39c9f81 100644 --- a/docs/pages/getting-started/zip.mdx +++ b/docs/pages/getting-started/zip.mdx @@ -40,6 +40,8 @@ Yaci CLI is currently supported on: ```bash rm -rf $HOME/.yaci-cli ``` + +This also removes your [User Settings](/user-settings) file, `$HOME/.yaci-cli/devkit.properties`. Copy it somewhere else first if you have one. ## Installation @@ -173,6 +175,10 @@ yaci-cli:> create-node -o --block-time 5 --slot-length 5 --start The `config` folder contains three important configuration files: + +A new release comes with a new `config` folder. To keep your settings across upgrades, put them in `$HOME/.yaci-cli/devkit.properties` instead: it can hold any key from these three files and wins over them. See [User Settings](/user-settings). + + ### application.properties Main configuration file for Yaci CLI functionality. diff --git a/docs/pages/introduction.mdx b/docs/pages/introduction.mdx index 431034a7..81669e36 100644 --- a/docs/pages/introduction.mdx +++ b/docs/pages/introduction.mdx @@ -35,7 +35,7 @@ The ZIP distribution includes only the Yaci CLI and allows selective downloading **Supported Environments:** - Linux x64 -- macOS arm64 +- macOS 15 (arm64) or later ### 3. NPM Distribution @@ -50,7 +50,18 @@ Supported environments include: Linux x64, MacOS arm64 **Supported Environments:** - Linux x64 -- macOS arm64 +- macOS 15 (arm64) or later + +### 4. Homebrew Distribution + +The Homebrew distribution installs the same native Yaci CLI and `yaci-devkit` command as the NPM distribution, through `brew`, without needing Node.js. + +- `brew install bloxbean/tap/yaci-devkit` +- Updates with `brew upgrade` + +**Supported Environments:** +- Linux x64 +- macOS 15 (arm64) or later ## Getting Started @@ -61,6 +72,7 @@ For the quickest setup experience, we recommend starting with the [Docker Setup] - [Docker Setup](/getting-started/docker) - Installation method - [Zip Setup](/getting-started/zip) - Non-Docker installation - [NPM Setup](/getting-started/npm) - NPM package installation +- [Homebrew Setup](/getting-started/homebrew) - Homebrew installation - [Yaci CLI Commands](/commands) - Learn about available CLI commands ## Important Notes diff --git a/docs/pages/known-issues.mdx b/docs/pages/known-issues.mdx index c36c234b..5f3f20f7 100644 --- a/docs/pages/known-issues.mdx +++ b/docs/pages/known-issues.mdx @@ -106,7 +106,7 @@ For technical details about this issue, see: [GitHub Issue #65](https://github.c ### Devnet node does not start while another cardano-node runs -Applies to the zip, npm and native/JAR distributions. Docker is not affected: the devnet node runs in the container's own network, and its Prometheus port is not published to the host. +Applies to the zip, npm, Homebrew and native/JAR distributions. Docker is not affected: the devnet node runs in the container's own network, and its Prometheus port is not published to the host. #### Current Behavior @@ -128,7 +128,8 @@ Stop the other node, or give the devnet a free port. The port is written into th |---|---|---| | Zip / native binary | `create-node -o --prometheus-port 12799` | `prometheus.port=12799` in `config/application.properties`, or `./yaci-cli -Dprometheus.port=12799` | | JAR | `create-node -o --prometheus-port 12799` | `java -Dprometheus.port=12799 -jar yaci-cli.jar` | -| NPM | `yaci-devkit up --prometheus-port 12799` | `PROMETHEUS_PORT=12799 yaci-devkit up` | +| NPM / Homebrew | `yaci-devkit up --prometheus-port 12799` | `PROMETHEUS_PORT=12799 yaci-devkit up` | +| Zip / NPM / Homebrew (v0.12.0+) | | `prometheus.port=12799` in `$HOME/.yaci-cli/devkit.properties` (see [User Settings](/user-settings)) | The `PROMETHEUS_PORT=12799` environment variable works with every distribution. Use one of the "Every time" forms if you also create devnets through the admin API or Yaci Viewer, which use the `prometheus.port` property. diff --git a/docs/pages/user-settings.mdx b/docs/pages/user-settings.mdx new file mode 100644 index 00000000..d482792d --- /dev/null +++ b/docs/pages/user-settings.mdx @@ -0,0 +1,58 @@ +import { Callout } from 'nextra/components' + +# User Settings (devkit.properties) + +`$HOME/.yaci-cli/devkit.properties` holds your own Yaci CLI settings. It is read by the [Homebrew](/getting-started/homebrew), [NPM](/getting-started/npm) and [Zip](/getting-started/zip) distributions, and is kept across upgrades: an upgrade replaces the bundled configuration files, but never this file. + + +Available from Yaci DevKit `v0.12.0`. The [Docker distribution](/getting-started/docker) does not read it: use its `config/env` and `config/node.properties` files instead. + + +## Create the file + +The file is optional. Create it when you need it: + +```bash +mkdir -p $HOME/.yaci-cli +nano $HOME/.yaci-cli/devkit.properties +``` + +It can hold any key from the bundled `application.properties`, `node.properties` or `download.properties`, in the same `key=value` format. For example: + +```properties +# Start Yaci Store with every devnet +yaci.store.enabled=true + +# Devnet node's Prometheus port, when another cardano-node on this machine uses 12798 +prometheus.port=12799 + +# Node mode (node.properties) +nodeMode=yano-only + +# Your own Plutus cost models (a folder with plutus-costmodels-v10.json and plutus-costmodels-v11.json) +yaci.cli.plutus-costmodels-path=/Users/me/cardano/costmodels +``` + +The settings are read when `yaci-devkit` (or `yaci-cli`) starts: restart it after changing the file. + +## Precedence + +From highest to lowest: + +1. Command options, such as `yaci-devkit up --prometheus-port 12799` or `create-node --block-time 2`, for that command +2. `-D` options, such as `yaci-devkit -Dprometheus.port=12799 up` or `./yaci-cli -Dprometheus.port=12799` +3. Environment variables, such as `PROMETHEUS_PORT=12799` or `YACI_STORE_ENABLED=true` +4. `$HOME/.yaci-cli/devkit.properties` +5. The bundled `application.properties`, `node.properties` and `download.properties` + +A setting in `devkit.properties` therefore wins over the same setting in the bundled files, including the `config` folder of the Zip distribution. + +The environment variable for a property is its name in upper case, with `.` replaced by `_` and `-` removed: `yaci.store.enabled` becomes `YACI_STORE_ENABLED`. + +## Things to know + +- **Location**: the file is always `$HOME/.yaci-cli/devkit.properties`, even when you set `yaci.cli.home` to store devnets and components elsewhere. +- **Devnet settings**: genesis and node settings (from `node.properties`, and properties such as `prometheus.port`) are written into the devnet when it is created. Re-create the devnet after changing them: `create-node -o`, or `yaci-devkit up` (which creates a new devnet). +- **Paths**: use absolute paths. A relative path resolves against the directory you run `yaci-devkit` or `yaci-cli` from. +- **Shared**: Homebrew, NPM and Zip installs on the same machine all use `$HOME/.yaci-cli`, so they share this file, as they share devnets and downloaded components. +- **Clean reinstall**: removing `$HOME/.yaci-cli` removes this file too. Copy it somewhere else first if you want to keep it. From 52514e007b33f889bbad8319b649f2fbbd9b919d Mon Sep 17 00:00:00 2001 From: Satya Date: Wed, 14 Oct 2026 19:43:33 +0800 Subject: [PATCH 02/11] Docs: drop stale macOS arm64 Ogmios/Kupo warning Ogmios v7.0.0 and Kupo v2.12.0 publish aarch64-macos builds, which DownloadService already selects (as Known Issues and the npm README say). Remove the warning from the Zip and Homebrew pages. Co-Authored-By: Claude Opus 5.5 --- docs/pages/getting-started/homebrew.mdx | 4 ---- docs/pages/getting-started/zip.mdx | 4 ---- 2 files changed, 8 deletions(-) diff --git a/docs/pages/getting-started/homebrew.mdx b/docs/pages/getting-started/homebrew.mdx index b7b7ca1c..1364b1c2 100644 --- a/docs/pages/getting-started/homebrew.mdx +++ b/docs/pages/getting-started/homebrew.mdx @@ -18,10 +18,6 @@ The Homebrew distribution installs the native Yaci CLI and its configuration wit For Ogmios and Kupo, ensure ports 1337 and 1442 are also available if you plan to use these services. - -PV11-compatible Ogmios/Kupo binaries are currently published only as Linux x86_64 builds. On macOS arm64, those specific Ogmios/Kupo defaults cannot run directly. Use Docker as a workaround, or set custom `ogmios.url` / `kupo.url` values when compatible native builds are available. - - ## Installation For latest stable version: diff --git a/docs/pages/getting-started/zip.mdx b/docs/pages/getting-started/zip.mdx index a39c9f81..725e7f31 100644 --- a/docs/pages/getting-started/zip.mdx +++ b/docs/pages/getting-started/zip.mdx @@ -15,10 +15,6 @@ The Zip distribution is perfect for: **Note**: Yaci Viewer is not included in the Zip distribution. To add viewer functionality, you can install Yaci Viewer separately through NPM. See the [NPM installation guide](/getting-started/npm#step-2-install-yaci-viewer-optional) for details. - -PV11-compatible Ogmios/Kupo binaries are currently published only as Linux x86_64 builds. On macOS arm64 native CLI, those specific Ogmios/Kupo defaults cannot run directly. Use Docker as a workaround, or set custom `ogmios.url` / `kupo.url` values when compatible native builds are available. - - ## Key Advantages - **Reduced Disk Space**: Requires less disk space than Docker, containing only the Yaci CLI binary and configuration files From 967239a6fcab80c930196ba8af2428aaebcf13c9 Mon Sep 17 00:00:00 2001 From: Satya Date: Wed, 14 Oct 2026 20:09:17 +0800 Subject: [PATCH 03/11] Docs: redesign the landing page A new landing page in the style of store.yaci.xyz, in DevKit's colours, with dark and light themes: - Hero with a live console: yaci-devkit up boots a devnet, then produces blocks with an event log - A three.js chain of blocks between the hero and the SDK strip, loaded lazily and only on wide screens with motion allowed - "How it works" diagram: off-chain SDKs (MeshJS, Evolution SDK, Lucid Evolution, Cardano Client Lib, PyCardano, Sutra), provider APIs, the devnet (companion / yano-only) and the tools - Feature cards with animations: block time, node modes, Blockfrost APIs, MCP, CIP-30 wallet, rollback testing, catch-up after a pause - Install tabs for Homebrew, npm, Docker and Zip, what's new in v0.12.0, and a final CTA Also replaces the v0.12.0-beta5 references in the docs with v0.12.0, and drops the old landing styles from globals.css. Co-Authored-By: Claude Opus 5.5 --- docs/components/landing/Architecture.jsx | 317 +++ docs/components/landing/ChainScene.jsx | 218 ++ docs/components/landing/FeatureArt.jsx | 398 ++++ docs/components/landing/HeroConsole.jsx | 163 ++ docs/components/landing/Landing.jsx | 733 ++++++ docs/components/landing/hooks.js | 148 ++ docs/components/landing/icons.jsx | 204 ++ docs/components/landing/landing.css | 2615 ++++++++++++++++++++++ docs/package-lock.json | 9 +- docs/package.json | 3 +- docs/pages/_app.tsx | 4 +- docs/pages/_document.tsx | 18 + docs/pages/cip30-wallet.mdx | 2 +- docs/pages/getting-started/docker.mdx | 2 +- docs/pages/getting-started/npm.mdx | 4 +- docs/pages/getting-started/zip.mdx | 2 +- docs/pages/index.tsx | 1547 +------------ docs/pages/mcp-integration.mdx | 2 +- docs/pages/services.mdx | 4 +- docs/pages/yano-node-modes.mdx | 4 +- docs/styles/globals.css | 711 +----- docs/yarn.lock | 95 + 22 files changed, 4946 insertions(+), 2257 deletions(-) create mode 100644 docs/components/landing/Architecture.jsx create mode 100644 docs/components/landing/ChainScene.jsx create mode 100644 docs/components/landing/FeatureArt.jsx create mode 100644 docs/components/landing/HeroConsole.jsx create mode 100644 docs/components/landing/Landing.jsx create mode 100644 docs/components/landing/hooks.js create mode 100644 docs/components/landing/icons.jsx create mode 100644 docs/components/landing/landing.css create mode 100644 docs/pages/_document.tsx diff --git a/docs/components/landing/Architecture.jsx b/docs/components/landing/Architecture.jsx new file mode 100644 index 00000000..5f0673f4 --- /dev/null +++ b/docs/components/landing/Architecture.jsx @@ -0,0 +1,317 @@ +const EMBER = "#ff5a36"; +const AMBER = "#fbbf24"; +const CYAN = "#22d3ee"; +const BLUE = "#3b8bff"; +const VIOLET = "#a78bfa"; +const MINT = "#34d399"; +const PINK = "#f472b6"; + +const SDKS = [ + { name: "MeshJS", sub: "TypeScript", c: CYAN, mark: "M" }, + { name: "Evolution SDK", sub: "TypeScript", c: EMBER, mark: "Ev" }, + { name: "Lucid Evolution", sub: "TypeScript", c: VIOLET, mark: "L" }, + { name: "Cardano Client Lib", sub: "Java", c: BLUE, mark: "CL" }, + { name: "PyCardano", sub: "Python", c: MINT, mark: "Py" }, + { name: "Sutra", sub: "Elixir", c: PINK, mark: "S" }, +]; +const SDK_X = 20; +const SDK_W = 220; +const SDK_H = 46; +const sdkY = (i) => 56 + i * 58; + +const APIS = [ + { name: "Ogmios", sub: "WebSocket :1337 · optional", y: 240, c: VIOLET }, + { name: "Kupo", sub: "UTxO index :1442 · optional", y: 298, c: VIOLET }, + { name: "Submit API", sub: "tx submission :8090", y: 356, c: MINT }, +]; +const API_X = 300; +const API_W = 190; + +const CORE = { x: 550, y: 44, w: 330, h: 400 }; + +const TOOLS = [ + { name: "Yaci CLI", sub: "create-node · topup · tip", c: EMBER }, + { name: "Admin API", sub: ":10000 · REST · Swagger", c: AMBER }, + { name: "Yaci Viewer", sub: ":5173 · block explorer", c: CYAN }, + { name: "CIP-30 wallet", sub: "window.cardano.yacidevkit", c: MINT }, + { name: "MCP server", sub: ":10000/mcp · AI agents", c: PINK }, +]; +const TOOL_X = 930; +const TOOL_W = 210; +const TOOL_H = 52; +const toolY = (i) => 56 + i * 66; + +const curve = (x1, y1, x2, y2) => { + const mx = (x1 + x2) / 2; + return `M${x1} ${y1} C${mx} ${y1} ${mx} ${y2} ${x2} ${y2}`; +}; + +function Wire({ d, color, dur = 2.4, dots = 1, delay = 0, faint }) { + return ( + + + + {Array.from({ length: dots }).map((_, i) => ( + + + + ))} + + ); +} + +function Chip({ x, y, w, h = 28, label, c }) { + return ( + + + + {label} + + + ); +} + +/** The DevKit mark, drawn at (x, y) with the given size. */ +function MarkAt({ x, y, size }) { + const s = size / 64; + return ( + + + + + ); +} + +export default function Architecture() { + const { x: cx, y: cy, w: cw, h: ch } = CORE; + const nodeY = 172; + const chainY = 268; + + return ( +
+
+ + How Yaci DevKit fits together + + Off-chain SDKs such as MeshJS, Evolution SDK, Lucid Evolution, Cardano Client Lib, PyCardano and Sutra + connect to the devnet through Yaci Store's Blockfrost-compatible API, Ogmios, Kupo and the Submit API. The + local devnet runs Yano and cardano-node, in companion or yano-only mode. The Yaci CLI, admin API, Yaci + Viewer, the CIP-30 wallet and the MCP server let you operate it. + + + + + + + + + + + + + + + + + + 01 · BUILD + 02 · CONNECT + 03 · RUN + 04 · OPERATE + + {/* Wires first, so the boxes sit on top */} + {SDKS.map((s, i) => ( + + ))} + + + + + + + + + {TOOLS.map((t, i) => ( + + ))} + + {/* 01 · SDKs */} + {SDKS.map((s, i) => { + const y = sdkY(i); + return ( + + + + + {s.mark} + + + {s.name} + + + {s.sub} + + + ); + })} + + + any Blockfrost-compatible client + + + {/* 02 · Provider APIs */} + + + + + + + + Yaci Store + Blockfrost-compatible + {["GET /blocks/latest", "GET /addresses/…/utxos", "POST /tx/submit"].map((l, i) => ( + + {l} + + ))} + + + + {APIS.map((a) => ( + + + + + {a.name} + + + {a.sub} + + + ))} + + {/* 03 · Local devnet */} + + + + + + + Local devnet + + + one command · seconds to first block + + + {/* Node mode switch: companion, then yano-only */} + + + + + companion + yano-only + + {/* Yano and cardano-node */} + + + Yano + fast bootstrap + + + + + + + + cardano-node + Haskell node + + + handover + + + {/* Blocks being produced */} + {Array.from({ length: 6 }).map((_, i) => { + const bx = cx + 26 + i * 48; + return ( + + {i > 0 && } + + + + + + ); + })} + + block time 1s · down to 100ms + + + + + + + + Docker · npm · Homebrew · Zip + + + + {/* 04 · Tools */} + {TOOLS.map((t, i) => { + const y = toolY(i); + return ( + + + + + {t.name} + + + {t.sub} + + + ); + })} + + terminal · browser · AI agent + + +
+
+ + + Off-chain SDKs + + + + Optional Ogmios / Kupo + + + + Devnet + + + + Tools & AI agents + +
+
+ ); +} diff --git a/docs/components/landing/ChainScene.jsx b/docs/components/landing/ChainScene.jsx new file mode 100644 index 00000000..d2ea5cad --- /dev/null +++ b/docs/components/landing/ChainScene.jsx @@ -0,0 +1,218 @@ +import { useEffect, useRef } from "react"; + +/** + * A 3D chain of blocks drifting behind the hero: blocks flow along a gentle wave, new ones light up at the head, + * and transactions travel along the links. three.js is loaded only on wide screens with WebGL and motion allowed, + * so phones and reduced-motion visitors never download it. + */ + +const COUNT = 30; +const SPACING = 1.45; +const SPEED = 0.55; // world units per second +const PARTICLES = 70; + +const pathY = (x) => Math.sin(x * 0.3) * 0.8; +const pathZ = (x) => Math.cos(x * 0.27) * 1.8 - 1.5; + +function webglAvailable() { + try { + const c = document.createElement("canvas"); + return !!(c.getContext("webgl2") || c.getContext("webgl")); + } catch { + return false; + } +} + +export default function ChainScene() { + const host = useRef(null); + + useEffect(() => { + const el = host.current; + if (!el) return; + if (window.matchMedia("(prefers-reduced-motion: reduce)").matches) return; + if (window.innerWidth < 900 || !webglAvailable()) return; + + let disposed = false; + let cleanup = () => {}; + + import("three").then((THREE) => { + if (disposed) return; + + const renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true, powerPreference: "low-power" }); + renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2)); + renderer.setClearColor(0x000000, 0); + el.appendChild(renderer.domElement); + + const scene = new THREE.Scene(); + scene.fog = new THREE.Fog(0x07080d, 7, 18); + const camera = new THREE.PerspectiveCamera(38, 1, 0.1, 100); + camera.position.set(0, 2.2, 8); + + scene.add(new THREE.AmbientLight(0xffffff, 0.35)); + const warm = new THREE.PointLight(0xff5a36, 30, 30, 1.6); + warm.position.set(6, 3, 6); + scene.add(warm); + const cool = new THREE.PointLight(0xa78bfa, 30, 30, 1.6); + cool.position.set(-8, -2, 5); + scene.add(cool); + + const geo = new THREE.BoxGeometry(0.62, 0.62, 0.62); + const edgesGeo = new THREE.EdgesGeometry(geo); + const span = COUNT * SPACING; + const minX = -span / 2; + + const blocks = Array.from({ length: COUNT }, (_, i) => { + const mat = new THREE.MeshStandardMaterial({ + color: 0x0d0a10, + emissive: 0xff4e2d, + emissiveIntensity: 0.05, + metalness: 0.6, + roughness: 0.25, + transparent: true, + opacity: 0.7, + }); + const mesh = new THREE.Mesh(geo, mat); + const edges = new THREE.LineSegments(edgesGeo, new THREE.LineBasicMaterial({ color: 0xff7a45, transparent: true, opacity: 0.85 })); + mesh.add(edges); + mesh.userData = { x: minX + i * SPACING, spin: (Math.random() - 0.5) * 0.4, born: -10 }; + mesh.rotation.set(Math.random() * 0.6, Math.random() * Math.PI, 0); + scene.add(mesh); + return mesh; + }); + + // Links between consecutive blocks + const linkGeo = new THREE.BufferGeometry(); + const linkPos = new Float32Array(COUNT * 3); + linkGeo.setAttribute("position", new THREE.BufferAttribute(linkPos, 3)); + const link = new THREE.Line(linkGeo, new THREE.LineBasicMaterial({ color: 0xff8a3d, transparent: true, opacity: 0.28 })); + scene.add(link); + + // Transactions travelling along the chain + const pGeo = new THREE.BufferGeometry(); + const pPos = new Float32Array(PARTICLES * 3); + const pOffset = Array.from({ length: PARTICLES }, () => ({ t: Math.random(), v: 0.04 + Math.random() * 0.05, r: Math.random() * 0.5 })); + pGeo.setAttribute("position", new THREE.BufferAttribute(pPos, 3)); + const particles = new THREE.Points( + pGeo, + new THREE.PointsMaterial({ color: 0x22d3ee, size: 0.11, transparent: true, opacity: 0.9, depthWrite: false }) + ); + scene.add(particles); + + const pointer = { x: 0, y: 0 }; + const onPointer = (e) => { + pointer.x = e.clientX / window.innerWidth - 0.5; + pointer.y = e.clientY / window.innerHeight - 0.5; + }; + window.addEventListener("pointermove", onPointer, { passive: true }); + + const resize = () => { + const w = el.clientWidth; + const h = el.clientHeight; + if (!w || !h) return; + renderer.setSize(w, h, false); + camera.aspect = w / h; + camera.updateProjectionMatrix(); + }; + const ro = new ResizeObserver(resize); + ro.observe(el); + resize(); + + let visible = true; + const io = new IntersectionObserver(([entry]) => { + visible = entry.isIntersecting; + }); + io.observe(el); + + const clock = new THREE.Clock(); + let raf = 0; + const tick = () => { + raf = requestAnimationFrame(tick); + if (!visible) { + clock.getDelta(); + return; + } + const dt = Math.min(clock.getDelta(), 0.05); + const now = clock.elapsedTime; + const light = document.documentElement.getAttribute("data-dk-theme") === "light"; + scene.fog.color.set(light ? 0xfaf8f6 : 0x07080d); + + let headX = -Infinity; + blocks.forEach((b) => { + b.userData.x -= SPEED * dt; + if (b.userData.x < minX) { + b.userData.x += span; + b.userData.born = now; // a new block appears at the head + } + headX = Math.max(headX, b.userData.x); + }); + + blocks.forEach((b) => { + const { x, spin, born } = b.userData; + b.position.set(x, pathY(x), pathZ(x)); + b.rotation.y += spin * dt; + b.rotation.x += spin * 0.4 * dt; + const age = now - born; + const grow = Math.min(1, age / 0.6); + b.scale.setScalar(0.35 + 0.65 * (1 - Math.pow(1 - grow, 3))); + const isHead = x === headX; + const flash = Math.max(0, 1 - age / 1.6); + b.material.emissiveIntensity = 0.05 + (isHead ? 1.1 : 0) + flash * 1.6; + b.material.opacity = isHead || flash > 0 ? 0.95 : 0.7; + b.material.color.set(light ? 0xffb493 : 0x0d0a10); + b.children[0].material.opacity = light ? 0.9 : 0.85; + }); + // Keep the link line ordered by x so it never jumps across the scene + const order = blocks.map((b) => b.userData.x).sort((a, c) => a - c); + order.forEach((x, i) => { + linkPos[i * 3] = x; + linkPos[i * 3 + 1] = pathY(x); + linkPos[i * 3 + 2] = pathZ(x); + }); + linkGeo.attributes.position.needsUpdate = true; + + pOffset.forEach((p, i) => { + p.t = (p.t + p.v * dt) % 1; + const x = minX + p.t * span; + const a = now * 2 + i; + pPos[i * 3] = x; + pPos[i * 3 + 1] = pathY(x) + Math.sin(a) * p.r * 0.5; + pPos[i * 3 + 2] = pathZ(x) + Math.cos(a) * p.r * 0.5; + }); + pGeo.attributes.position.needsUpdate = true; + + camera.position.x += (pointer.x * 1.6 - camera.position.x) * 0.04; + camera.position.y += (2.2 - pointer.y * 0.8 - camera.position.y) * 0.04; + camera.lookAt(0, 0, -1.5); + renderer.render(scene, camera); + }; + tick(); + requestAnimationFrame(() => el.classList.add("on")); + + cleanup = () => { + cancelAnimationFrame(raf); + window.removeEventListener("pointermove", onPointer); + ro.disconnect(); + io.disconnect(); + blocks.forEach((b) => { + b.material.dispose(); + b.children[0].material.dispose(); + }); + geo.dispose(); + edgesGeo.dispose(); + linkGeo.dispose(); + link.material.dispose(); + pGeo.dispose(); + particles.material.dispose(); + renderer.dispose(); + renderer.domElement.remove(); + }; + }); + + return () => { + disposed = true; + cleanup(); + }; + }, []); + + return