diff --git a/Cargo.toml b/Cargo.toml index e04d751..4d32ee6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -129,3 +129,7 @@ minijinja = "2" + +[[bench]] +name = "throughput" +harness = false diff --git a/README.md b/README.md index 1313bc5..56e8426 100644 --- a/README.md +++ b/README.md @@ -36,49 +36,52 @@ - [Install](#install) - [eget](#eget) - - [Homebrew](#homebrew-macos) + - [Homebrew (macOS)](#homebrew-macos) - [cargo](#cargo) - [Nix](#nix) -- [Reference](#reference) - [GET: Hello world](#get-hello-world) - [UNIX domain sockets](#unix-domain-sockets) - - [Watch Mode](#watch-mode) - - [Reading from stdin](#reading-from-stdin) +- [Requests & responses](#requests--responses) - [POST: echo](#post-echo) + - [Reading from stdin](#reading-from-stdin) - [Request metadata](#request-metadata) - [Response metadata](#response-metadata) - [Content-Type Inference](#content-type-inference) - - [TLS & HTTP/2 Support](#tls-support) - - [Logging](#logging) - - [Trusted Proxies](#trusted-proxies) - - [Serving Static Files](#serving-static-files) +- [Streaming & events](#streaming--events) - [Streaming responses](#streaming-responses) - [server-sent events](#server-sent-events) + - [Streaming Input](#streaming-input) +- [Serving & operations](#serving--operations) + - [Watch Mode](#watch-mode) + - [Serving Static Files](#serving-static-files) + - [Logging](#logging) + - [Trusted Proxies](#trusted-proxies) + - [TLS Support](#tls-support) +- [State & storage](#state--storage) - [In-memory SQLite](#in-memory-sqlite) - [Local Bus](#local-bus) - [Embedded cross.stream (full featured Persistent Event Stream)](#embedded-crossstream-full-featured-persistent-event-stream) - - [Reverse Proxy](#reverse-proxy) +- [Reverse Proxy](#reverse-proxy) + - [Basic Usage](#basic-usage) + - [Configuration Options](#configuration-options) + - [Examples](#examples) +- [Templates & output](#templates--output) - [Templates](#templates) - - [`.mj` - Render templates](#mj---render-templates) - - [`.mj compile` / `.mj render` - Precompiled templates](#mj-compile--mj-render---precompiled-templates) - [Syntax Highlighting](#syntax-highlighting) - [Markdown](#markdown) - - [Evaluating User-Submitted Scripts](#evaluating-user-submitted-scripts) - - [Streaming Input](#streaming-input) +- [Embedded Modules](#embedded-modules) + - [Routing](#routing) + - [HTML DSL](#html-dsl) + - [Datastar SDK](#datastar-sdk) + - [Cookies](#cookies) +- [Extending & eval](#extending--eval) + - [Eval Subcommand](#eval-subcommand) - [Plugins](#plugins) - [Module Paths](#module-paths) - - [Embedded Modules](#embedded-modules) - - [Routing](#routing) - - [HTML DSL](#html-dsl) - - [Datastar SDK](#datastar-sdk) - - [Cookies](#cookies) -- [Eval Subcommand](#eval-subcommand) - - [Unit Testing Endpoints](#unit-testing-endpoints) -- [Building and Releases](#building-and-releases) - - [Available Build Targets](#available-build-targets) - - [Examples](#examples) - - [GitHub Releases](#github-releases) -- [History](#history) + - [Runtime Constants](#runtime-constants) + - [Evaluating User-Submitted Scripts](#evaluating-user-submitted-scripts) + - [Building and Releases](#building-and-releases) + - [History](#history) @@ -124,8 +127,6 @@ http-nu is available in [nixpkgs](https://github.com/NixOS/nixpkgs). For packaging and maintenance documentation, see [NIXOS_PACKAGING_GUIDE.md](NIXOS_PACKAGING_GUIDE.md). -## Reference - ### GET: Hello world ```bash @@ -148,6 +149,16 @@ $ http-nu --datastar :3001 examples/serve.nu $ http-nu --datastar --store ./store :3001 examples/serve.nu # enables store-dependent examples ``` +You can also run a command or pipeline without starting a server, handy for +trying the examples throughout these docs: + +```bash +$ http-nu eval -c '1 + 2' +3 +``` + +See [Eval Subcommand](#eval-subcommand) for details. + ### UNIX domain sockets ```bash @@ -156,23 +167,18 @@ $ curl -s --unix-socket ./sock localhost Hello world ``` -### Watch Mode +## Requests & responses -Use `-w` / `--watch` to automatically reload when files change: +Read the request, shape the response, and infer content types. + +### POST: echo ```bash -$ http-nu :3001 -w ./serve.nu +$ http-nu :3001 -c '{|req| $in}' +$ curl -s -d Hai localhost:3001 +Hai ``` -This watches the script's directory for any changes (including included files) -and hot-reloads the handler. Active [SSE connections](#server-sent-events) are -aborted on reload to trigger client reconnection. - -> [!WARNING] -> The watch is recursive: keep only files that should trigger a reload in the -> script's directory (`serve.nu`, `templates/`, `static/`, ...). Anything else -> that churns there reloads the handler too. - ### Reading from stdin Pass `-` to read the script from stdin: @@ -189,14 +195,6 @@ $ (printf '{|req| "v1"}\0'; sleep 5; printf '{|req| "v2"}') | http-nu :3001 - -w Each `\0`-terminated script replaces the handler. -### POST: echo - -```bash -$ http-nu :3001 -c '{|req| $in}' -$ curl -s -d Hai localhost:3001 -Hai -``` - ### Request metadata The Request metadata is passed as an argument to the closure. @@ -315,100 +313,9 @@ To consume a JSONL endpoint from Nushell: http get http://localhost:3001 | from json --objects | each {|row| ... } ``` -### TLS Support - -Enable TLS by providing a PEM file containing both certificate and private key: - -```bash -$ http-nu :3001 --tls combined.pem -c '{|req| "Secure Hello"}' -$ curl -k https://localhost:3001 -Secure Hello -``` - -Generate a self-signed certificate for testing: - -```bash -$ openssl req -x509 -newkey rsa:4096 -keyout key.pem -out cert.pem -days 365 -nodes -$ cat cert.pem key.pem > combined.pem -``` - -HTTP/2 is automatically enabled for TLS connections: - -```bash -$ curl -k --http2 -si https://localhost:3001 | head -1 -HTTP/2 200 -``` - -### Logging - -Control log output with `--log-format`: - -- `human` (default): Live-updating terminal output with startup banner, - per-request progress lines showing timestamp, IP, method, path, status, - timing, and bytes -- `jsonl`: Structured JSON lines with `scru128` stamps for log aggregation - -Each request emits 3 phases: **request** (received), **response** (headers -sent), **complete** (body finished). - -**Human format** - -human format logging output - -**JSONL format** - -Events share a `request_id` for correlation: - -```bash -$ http-nu --log-format jsonl :3001 '{|req| "hello"}' -{"stamp":"...","message":"started","address":"http://127.0.0.1:3001","startup_ms":42} -{"stamp":"...","message":"request","request_id":"...","method":"GET","path":"/","request":{...}} -{"stamp":"...","message":"response","request_id":"...","status":200,"headers":{...},"latency_ms":1} -{"stamp":"...","message":"complete","request_id":"...","bytes":5,"duration_ms":2} -``` - -Lifecycle events: `started`, `reloaded`, `stopping`, `stopped`, `stop_timed_out` - -The `print` command outputs to the logging system (appears as `message: "print"` -in JSONL). - -### Trusted Proxies +## Streaming & events -When behind a reverse proxy, use `--trust-proxy` to extract client IP from -`X-Forwarded-For`. Accepts CIDR notation, repeatable: - -```bash -$ http-nu --trust-proxy 10.0.0.0/8 --trust-proxy 192.168.0.0/16 :3001 '{|req| $req.trusted_ip}' -``` - -The `trusted_ip` field is resolved by parsing `X-Forwarded-For` right-to-left, -stopping at the first IP not in a trusted range. Falls back to `remote_ip` when: - -- No `--trust-proxy` flags provided -- Remote IP is not in trusted ranges -- No `X-Forwarded-For` header present - -### Serving Static Files - -You can serve static files from a directory using the `.static` command. This -command takes two arguments: the root directory path and the request path. - -When you call `.static`, it sets the response to serve the specified file, and -any subsequent output in the closure will be ignored. The content type is -automatically inferred based on the file extension (e.g., `text/css` for `.css` -files). - -Here's an example: - -```bash -$ http-nu :3001 -c '{|req| .static "/path/to/static/dir" $req.path}' -``` - -For single page applications you can provide a fallback file: - -```bash -$ http-nu :3001 -c '{|req| .static "/path/to/static/dir" $req.path --fallback "index.html"}' -``` +Stream chunks as they are produced, and push server-sent events. ### Streaming responses @@ -501,6 +408,139 @@ data: {"date":"2025-01-31 04:01:28.390407 -05:00"} ... ``` +### Streaming Input + +In Nushell, input only streams when received implicitly. Referencing `$in` +collects the entire input into memory. + +```nushell +# Streams: command receives input implicitly +{|req| from json } + +# Buffers: $in collects before piping +{|req| $in | from json } +``` + +## Serving & operations + +Serve files, watch for changes, log requests, trust proxies, and enable TLS. + +### Watch Mode + +Use `-w` / `--watch` to automatically reload when files change: + +```bash +$ http-nu :3001 -w ./serve.nu +``` + +This watches the script's directory for any changes (including included files) +and hot-reloads the handler. Active [SSE connections](#server-sent-events) are +aborted on reload to trigger client reconnection. + +> [!WARNING] +> The watch is recursive: keep only files that should trigger a reload in the +> script's directory (`serve.nu`, `templates/`, `static/`, ...). Anything else +> that churns there reloads the handler too. + +### Serving Static Files + +You can serve static files from a directory using the `.static` command. This +command takes two arguments: the root directory path and the request path. + +When you call `.static`, it sets the response to serve the specified file, and +any subsequent output in the closure will be ignored. The content type is +automatically inferred based on the file extension (e.g., `text/css` for `.css` +files). + +Here's an example: + +```bash +$ http-nu :3001 -c '{|req| .static "/path/to/static/dir" $req.path}' +``` + +For single page applications you can provide a fallback file: + +```bash +$ http-nu :3001 -c '{|req| .static "/path/to/static/dir" $req.path --fallback "index.html"}' +``` + +### Logging + +Control log output with `--log-format`: + +- `human` (default): Live-updating terminal output with startup banner, + per-request progress lines showing timestamp, IP, method, path, status, + timing, and bytes +- `jsonl`: Structured JSON lines with `scru128` stamps for log aggregation + +Each request emits 3 phases: **request** (received), **response** (headers +sent), **complete** (body finished). + +**Human format** + +human format logging output + +**JSONL format** + +Events share a `request_id` for correlation: + +```bash +$ http-nu --log-format jsonl :3001 '{|req| "hello"}' +{"stamp":"...","message":"started","address":"http://127.0.0.1:3001","startup_ms":42} +{"stamp":"...","message":"request","request_id":"...","method":"GET","path":"/","request":{...}} +{"stamp":"...","message":"response","request_id":"...","status":200,"headers":{...},"latency_ms":1} +{"stamp":"...","message":"complete","request_id":"...","bytes":5,"duration_ms":2} +``` + +Lifecycle events: `started`, `reloaded`, `stopping`, `stopped`, `stop_timed_out` + +The `print` command outputs to the logging system (appears as `message: "print"` +in JSONL). + +### Trusted Proxies + +When behind a reverse proxy, use `--trust-proxy` to extract client IP from +`X-Forwarded-For`. Accepts CIDR notation, repeatable: + +```bash +$ http-nu --trust-proxy 10.0.0.0/8 --trust-proxy 192.168.0.0/16 :3001 '{|req| $req.trusted_ip}' +``` + +The `trusted_ip` field is resolved by parsing `X-Forwarded-For` right-to-left, +stopping at the first IP not in a trusted range. Falls back to `remote_ip` when: + +- No `--trust-proxy` flags provided +- Remote IP is not in trusted ranges +- No `X-Forwarded-For` header present + +### TLS Support + +Enable TLS by providing a PEM file containing both certificate and private key: + +```bash +$ http-nu :3001 --tls combined.pem -c '{|req| "Secure Hello"}' +$ curl -k https://localhost:3001 +Secure Hello +``` + +Generate a self-signed certificate for testing: + +```bash +$ openssl req -x509 -newkey rsa:4096 -keyout key.pem -out cert.pem -days 365 -nodes +$ cat cert.pem key.pem > combined.pem +``` + +HTTP/2 is automatically enabled for TLS connections: + +```bash +$ curl -k --http2 -si https://localhost:3001 | head -1 +HTTP/2 200 +``` + +## State & storage + +Keep state across requests: in-memory SQLite, an ephemeral bus, and a durable event store. + ### In-memory SQLite Nushell's [`stor`](https://www.nushell.sh/commands/docs/stor.html) commands @@ -617,11 +657,14 @@ Templates can also load from the store using `.mj --topic` and ```nushell {|req| .last quotes --follow - | each {|frame| $frame.meta | to datastar-patch-elements } + | each {|frame| {data: $frame.meta} } | to sse } ``` +`to sse` pairs naturally with the [Datastar SDK](#datastar-sdk) when you want +this stream to drive live DOM updates. + **Combining with the [Local Bus](#local-bus):** Bus events use `compose.close` or `editor.open` style UI signals; store events @@ -652,7 +695,7 @@ The bus differs from `.append` / `.cat`: See the [xs documentation](https://www.cross.stream) to learn more. -### Reverse Proxy +## Reverse Proxy You can proxy HTTP requests to backend servers using the `.reverse-proxy` command. This command takes a target URL and an optional configuration record. @@ -675,14 +718,14 @@ closure will be ignored. - With `preserve_host: false`: Sets Host header to match the target backend hostname -#### Basic Usage +### Basic Usage ```bash # Simple proxy to backend server $ http-nu :3001 -c '{|req| .reverse-proxy "http://localhost:8080"}' ``` -#### Configuration Options +### Configuration Options The optional second parameter allows you to customize the proxy behavior: @@ -695,7 +738,7 @@ The optional second parameter allows you to customize the proxy behavior: } ``` -#### Examples +### Examples **Add custom headers:** @@ -746,6 +789,10 @@ $ http-nu :3001 -c '{|req| # Force context-id=smidgeons, remove debug param, preserve others ``` +## Templates & output + +Render templates, and turn code and Markdown into HTML. + ### Templates Render [minijinja](https://github.com/mitsuhiko/minijinja) (Jinja2-compatible) @@ -851,100 +898,9 @@ fn main() {}
...
 ````
 
-### Evaluating User-Submitted Scripts
-
-The `.run` command parses, compiles, and evaluates a nushell script string. Use
-it to build web UIs that let users submit and run arbitrary commands -- an
-in-browser REPL, for example. Pipeline input is forwarded to the script.
-
-```nushell
-"hello" | .run 'str upcase'              # => HELLO
-[1 2 3] | .run 'math sum'                # => 6
-```
-
-Parse, compile, and runtime errors surface as distinct error types with source
-excerpts pointing at the offending span. Each call runs against a clone of the
-engine state, so any `def`, `let`, or `use` lives only for the call's duration;
-the caller's bindings and environment are also hidden from the script.
-
-> [!WARNING]
-> The submitted script has full access to whatever the http-nu process can do --
-> files, network, the embedded store. Only expose `.run` on localhost or in
-> trusted environments.
-
-### Streaming Input
-
-In Nushell, input only streams when received implicitly. Referencing `$in`
-collects the entire input into memory.
-
-```nushell
-# Streams: command receives input implicitly
-{|req| from json }
-
-# Buffers: $in collects before piping
-{|req| $in | from json }
-```
-
-For routing, `dispatch` must be first in the closure to receive the body. In
-handlers, put body-consuming commands first:
-
-```nushell
-{|req|
-  dispatch $req [
-    (route {method: "POST"} {|req ctx|
-      from json  # receives body implicitly
-    })
-  ]
-}
-```
-
-### Plugins
-
-Load Nushell plugins to extend available commands.
-
-```bash
-$ http-nu --plugin ~/.cargo/bin/nu_plugin_inc :3001 '{|req| 5 | inc}'
-$ curl -s localhost:3001
-6
-```
-
-Multiple plugins:
-
-```bash
-$ http-nu --plugin ~/.cargo/bin/nu_plugin_inc --plugin ~/.cargo/bin/nu_plugin_query :3001 '{|req| ...}'
-```
-
-Works with eval:
-
-```bash
-$ http-nu --plugin ~/.cargo/bin/nu_plugin_inc eval -c '1 | inc'
-2
-```
-
-### Module Paths
-
-Make module paths available with `-I` / `--include-path`:
-
-```bash
-$ http-nu -I ./lib -I ./vendor :3001 '{|req| use mymod.nu; ...}'
-```
-
-### Runtime Constants
-
-The `$HTTP_NU` const is available in all scripts and reflects the CLI options
-the server was started with:
-
-```nushell
-$HTTP_NU
-# => {dev: false, datastar: true, watch: false, store: "./store", topic: null, expose: null, tls: null, services: false}
-
-$HTTP_NU.store != null  # check if store is available
-$HTTP_NU.dev            # true when --dev was passed
-```
-
-### Embedded Modules
+## Embedded Modules
 
-#### Routing
+### Routing
 
 http-nu includes an embedded routing module for declarative request handling.
 The request body is available to handlers as `$in`.
@@ -984,6 +940,21 @@ Routes match in order. First match wins. Closure tests return a record (match,
 context passed to handler) or null (no match). If no routes match, returns
 `501 Not Implemented`.
 
+`dispatch` must come first in the closure to receive the request body, and
+inside a handler put body-consuming commands (`from json`, etc.) first, since
+input only streams when received implicitly (see
+[Streaming Input](#streaming-input)):
+
+```nushell
+{|req|
+  dispatch $req [
+    (route {method: "POST"} {|req ctx|
+      from json  # receives body implicitly
+    })
+  ]
+}
+```
+
 **Mounting sub-handlers:**
 
 `mount` serves a handler under a path prefix. Requests to `/prefix` redirect to
@@ -1014,7 +985,7 @@ $req | href "/about"
 # "/blog/about" when mounted under /blog, "/about" otherwise
 ```
 
-#### HTML DSL
+### HTML DSL
 
 Build HTML with Nushell. Lisp-style nesting with uppercase tags.
 
@@ -1088,7 +1059,7 @@ let tpl = .mj compile --inline (UL (_for {item: items} (LI (_var "item"))))
 # 
  • a
  • b
  • c
``` -#### Datastar SDK +### Datastar SDK Generate [Datastar](https://data-star.dev) SSE events for hypermedia interactions. Follows the @@ -1183,7 +1154,7 @@ to datastar-redirect []: string -> record # "/url" | to datastar-redirect from datastar-signals [req: record]: string -> record # $in | from datastar-signals $req ``` -#### Cookies +### Cookies Set and parse HTTP cookies with secure defaults. @@ -1242,7 +1213,11 @@ cookie delete [ ]: any -> any ``` -## Eval Subcommand +## Extending & eval + +The eval subcommand, plugins, module paths, evaluating user scripts, and releases. + +### Eval Subcommand Test http-nu commands without running a server. @@ -1263,7 +1238,7 @@ $ http-nu eval -c '.mj compile --inline "Hello, {{ name }}" | describe' CompiledTemplate ``` -### Unit Testing Endpoints +#### Unit Testing Endpoints `source` loads a handler script and returns the closure. `do` invokes it with a request record. `assert` checks the response. @@ -1285,20 +1260,85 @@ $ http-nu eval test.nu See [`examples/tao/test.nu`](examples/tao/test.nu). -## Building and Releases +### Plugins + +Load Nushell plugins to extend available commands. + +```bash +$ http-nu --plugin ~/.cargo/bin/nu_plugin_inc :3001 '{|req| 5 | inc}' +$ curl -s localhost:3001 +6 +``` + +Multiple plugins: + +```bash +$ http-nu --plugin ~/.cargo/bin/nu_plugin_inc --plugin ~/.cargo/bin/nu_plugin_query :3001 '{|req| ...}' +``` + +Works with eval: + +```bash +$ http-nu --plugin ~/.cargo/bin/nu_plugin_inc eval -c '1 | inc' +2 +``` + +### Module Paths + +Make module paths available with `-I` / `--include-path`: + +```bash +$ http-nu -I ./lib -I ./vendor :3001 '{|req| use mymod.nu; ...}' +``` + +### Runtime Constants + +The `$HTTP_NU` const is available in all scripts and reflects the CLI options +the server was started with: + +```nushell +$HTTP_NU +# => {dev: false, datastar: true, watch: false, store: "./store", topic: null, expose: null, tls: null, services: false} + +$HTTP_NU.store != null # check if store is available +$HTTP_NU.dev # true when --dev was passed +``` + +### Evaluating User-Submitted Scripts + +The `.run` command parses, compiles, and evaluates a nushell script string. Use +it to build web UIs that let users submit and run arbitrary commands -- an +in-browser REPL, for example. Pipeline input is forwarded to the script. + +```nushell +"hello" | .run 'str upcase' # => HELLO +[1 2 3] | .run 'math sum' # => 6 +``` + +Parse, compile, and runtime errors surface as distinct error types with source +excerpts pointing at the offending span. Each call runs against a clone of the +engine state, so any `def`, `let`, or `use` lives only for the call's duration; +the caller's bindings and environment are also hidden from the script. + +> [!WARNING] +> The submitted script has full access to whatever the http-nu process can do -- +> files, network, the embedded store. Only expose `.run` on localhost or in +> trusted environments. + +### Building and Releases This project uses [Dagger](https://dagger.io) for cross-platform containerized builds that run identically locally and in CI. This means you can test builds on your machine before pushing tags to trigger releases. -### Available Build Targets +#### Available Build Targets - **Windows** (`windows-build`) - **macOS ARM64** (`darwin-build`) - **Linux ARM64** (`linux-arm-64-build`) - **Linux AMD64** (`linux-amd-64-build`) -### Examples +#### Examples Build a Windows binary locally: @@ -1316,13 +1356,13 @@ dagger call windows-env --src upload --src "." terminal The `upload` function filters files to avoid uploading everything in your local directory. -### GitHub Releases +#### GitHub Releases The GitHub workflow automatically builds all platforms and creates releases when you push a version tag (e.g., `v1.0.0`). Development tags containing `-dev.` are marked as prereleases. -## History +### History If you prefer POSIX to [Nushell](https://www.nushell.sh), this project has a cousin called [http-sh](https://github.com/cablehead/http-sh). diff --git a/benches/throughput.rs b/benches/throughput.rs new file mode 100644 index 0000000..06bf4db --- /dev/null +++ b/benches/throughput.rs @@ -0,0 +1,119 @@ +// Throughput benchmarks for the request hot path. +// +// Run with: cargo bench --bench throughput +// +// Dimensions, each over N requests against an in-process `handle()` with a +// hello-world closure (no TCP, no TLS -- this isolates the engine/handler +// path: request -> eval thread -> closure eval -> response): +// +// hello sequential requests; per-request latency of the eval path +// hello-concurrent 16 requests in flight; what the path sustains across cores +// +// Output is one parseable line per dimension: +// requests= ms= req_per_s= us_per_req= +// +// Numbers are only comparable on the same hardware. + +use std::sync::Arc; +use std::time::{Duration, Instant}; + +use arc_swap::ArcSwap; +use http_body_util::{BodyExt, Empty}; +use hyper::body::Bytes; +use hyper::Request; + +use http_nu::commands::{MjCommand, PrintCommand, StaticCommand, ToSse}; +use http_nu::handler::{handle, AppConfig}; +use http_nu::Engine; + +const N: usize = 10_000; + +fn report(name: &str, n: usize, elapsed: Duration) { + let ms = elapsed.as_secs_f64() * 1e3; + let rate = n as f64 / elapsed.as_secs_f64(); + let us = elapsed.as_secs_f64() * 1e6 / n as f64; + println!("{name} requests={n} ms={ms:.0} req_per_s={rate:.0} us_per_req={us:.2}"); +} + +fn hello_engine() -> Engine { + let mut engine = Engine::new().unwrap(); + engine + .add_commands(vec![ + Box::new(StaticCommand::new()), + Box::new(ToSse {}), + Box::new(MjCommand::new()), + Box::new(PrintCommand::new()), + ]) + .unwrap(); + engine + .set_http_nu_const(&http_nu::engine::HttpNuOptions::default()) + .unwrap(); + engine + .parse_closure(r#"{|req| "hello world" }"#, None) + .unwrap(); + engine +} + +fn config() -> Arc { + Arc::new(AppConfig { + trusted_proxies: vec![], + datastar: false, + dev: false, + }) +} + +async fn one_request(engine: Arc>, config: Arc) { + let req = Request::builder() + .method("GET") + .uri("/") + .body(Empty::::new()) + .unwrap(); + let resp = handle(engine, None, config, req).await.unwrap(); + assert_eq!(resp.status(), 200); + let body = resp.into_body().collect().await.unwrap().to_bytes(); + assert_eq!(&body[..], b"hello world"); +} + +async fn bench_hello(engine: Arc>, config: Arc) { + let start = Instant::now(); + for _ in 0..N { + one_request(engine.clone(), config.clone()).await; + } + report("hello", N, start.elapsed()); +} + +async fn bench_hello_concurrent(engine: Arc>, config: Arc) { + const IN_FLIGHT: usize = 16; + let start = Instant::now(); + let mut handles = Vec::with_capacity(IN_FLIGHT); + for _ in 0..IN_FLIGHT { + let engine = engine.clone(); + let config = config.clone(); + handles.push(tokio::spawn(async move { + for _ in 0..N / IN_FLIGHT { + one_request(engine.clone(), config.clone()).await; + } + })); + } + for h in handles { + h.await.unwrap(); + } + report( + "hello-concurrent", + N / IN_FLIGHT * IN_FLIGHT, + start.elapsed(), + ); +} + +fn main() { + let engine = Arc::new(ArcSwap::from_pointee(hello_engine())); + let config = config(); + + let rt = tokio::runtime::Runtime::new().unwrap(); + rt.block_on(async { + // warm up: first eval pays one-time parse/compile costs + one_request(engine.clone(), config.clone()).await; + bench_hello(engine.clone(), config.clone()).await; + bench_hello_concurrent(engine, config).await; + }); +} diff --git a/examples/2048/static/stellar.css b/examples/2048/static/stellar.css new file mode 120000 index 0000000..0c98355 --- /dev/null +++ b/examples/2048/static/stellar.css @@ -0,0 +1 @@ +../../../www-next-gen/assets/stellar.css \ No newline at end of file diff --git a/examples/2048/static/styles.css b/examples/2048/static/styles.css index c92484f..a768577 100644 --- a/examples/2048/static/styles.css +++ b/examples/2048/static/styles.css @@ -1,3 +1,7 @@ +/* Shared Stellar design tokens (symlinked from http-nu/www-next-gen). Must + precede the @font-face rules below per the CSS @import ordering rule. */ +@import "stellar.css"; + /* Self-hosted Source Sans 3 + Source Code Pro. One variable-axis woff2 per family; both 400 and 700 declarations point at the same file and the browser picks the weight off the wght axis. Latin + smart-quote @@ -37,18 +41,17 @@ } :root { - /* Palette aligned with http-nu/www: deep blue body, warm-cream headers, - off-white primary text. Tile colors live in render.nu (untouched -- - the wood-grain palette reads fine against blue). */ - --bg: #0077b6; + /* Brand colors via the shared Stellar named tokens (stellar.css above). + Tile colors still live in render.nu (untouched). */ + --bg: var(--named-ocean-0); --fg: rgba(255, 255, 255, 0.85); - --fg-header: #f4d9a0; + --fg-header: var(--named-sand-0); --tile: #eee4da; - --accent: #f59563; - --accent-hover: #f67c5f; - --accent-press: #d97a45; - --brand: #00d4ff; /* cyan from http-nu/www -- the link/underline accent that pops on blue */ - --brand-hover: #5ae5ff; + --accent: var(--named-orange-0); + --accent-hover: var(--named-orange-1); + --accent-press: var(--named-orange--1); + --brand: var(--named-stream-0); + --brand-hover: var(--named-stream-1); --light: #f9f6f2; --gap: 8px; --radius: 4px; diff --git a/src/engine.rs b/src/engine.rs index c5ffccc..4dcbc07 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -9,7 +9,7 @@ use nu_command::add_shell_command_context; use nu_engine::eval_block_with_early_return; use nu_parser::parse; use nu_plugin_engine::{GetPlugin, PluginDeclaration}; -use nu_protocol::engine::Command; +use nu_protocol::engine::{Command, Job, ThreadJob}; use nu_protocol::format_cli_error; use nu_protocol::{ debugger::WithoutDebug, @@ -66,6 +66,22 @@ impl Engine { let init_cwd = std::env::current_dir()?; gather_parent_env_vars(&mut engine_state, init_cwd.as_ref()); + // One long-lived background job shared by every request eval. Request + // threads need a current job so externals they spawn are tracked and + // signalled; sharing one (the pid list is mutex'd) means the hot path + // never clones the engine state or touches the jobs table per request. + let (sender, _receiver) = std::sync::mpsc::channel(); + let job = ThreadJob::new( + engine_state.signals().clone(), + Some("HTTP".to_string()), + sender, + ); + { + let mut jobs = engine_state.jobs.lock().expect("jobs mutex poisoned"); + jobs.add_job(Job::Thread(job.clone())); + } + engine_state.current_job.background_thread_job = Some(job); + Ok(Self { state: engine_state, closure: None, @@ -374,9 +390,7 @@ impl Engine { }; let res = xs::nu::add_core_commands(&mut xe, store) .and_then(|()| xs::nu::add_read_commands(&mut xe, store, xs::nu::ReadMode::Stream)) - .and_then(|()| { - xs::nu::add_write_commands(&mut xe, store, xs::nu::AppendMode::Direct) - }); + .and_then(|()| xs::nu::add_write_commands(&mut xe, store, xs::nu::AppendMode::Direct)); self.state = xe.state; res.map_err(|e| Error::from(e.to_string())) } diff --git a/src/worker.rs b/src/worker.rs index 1e48d0b..4d0f6fb 100644 --- a/src/worker.rs +++ b/src/worker.rs @@ -5,14 +5,68 @@ use crate::response::{ extract_http_response_meta, value_to_bytes, value_to_json, HttpResponseMeta, Response, ResponseTransport, }; -use nu_protocol::{ - engine::{Job, StateWorkingSet, ThreadJob}, - format_cli_error, PipelineData, Value, -}; +use nu_protocol::{engine::StateWorkingSet, format_cli_error, PipelineData, Value}; use std::io::Read; -use std::sync::{mpsc, Arc}; +use std::sync::atomic::{AtomicUsize, Ordering}; +use std::sync::{Arc, Mutex, OnceLock}; use tokio::sync::{mpsc as tokio_mpsc, oneshot}; +type EvalJob = Box; + +/// Fixed pool of eval threads for the request hot path. Spawning a thread per +/// request cost ~10us; reusing workers removes that. Plain-value responses +/// complete on the worker in microseconds. Streaming responses (SSE, byte +/// streams) hand their drain loop to a freshly spawned thread -- amortized +/// over the connection's lifetime -- so a long-lived stream never pins a +/// worker. If every worker is busy (slow, blocking evals), execute falls back +/// to thread-per-request, so the pool can never starve the server. +struct EvalPool { + tx: std::sync::mpsc::Sender, + idle: Arc, +} + +static EVAL_POOL: OnceLock = OnceLock::new(); + +fn eval_pool() -> &'static EvalPool { + EVAL_POOL.get_or_init(|| { + let (tx, rx) = std::sync::mpsc::channel::(); + let rx = Arc::new(Mutex::new(rx)); + let idle = Arc::new(AtomicUsize::new(0)); + let workers = std::thread::available_parallelism() + .map(|n| n.get()) + .unwrap_or(4); + for i in 0..workers { + let rx = rx.clone(); + let idle = idle.clone(); + std::thread::Builder::new() + .name(format!("eval-{i}")) + .spawn(move || loop { + idle.fetch_add(1, Ordering::Relaxed); + let job = rx.lock().expect("eval pool rx poisoned").recv(); + idle.fetch_sub(1, Ordering::Relaxed); + match job { + Ok(job) => job(), + Err(_) => break, + } + }) + .expect("failed to spawn eval worker"); + } + EvalPool { tx, idle } + }) +} + +impl EvalPool { + fn execute(&self, job: EvalJob) { + if self.idle.load(Ordering::Relaxed) == 0 { + std::thread::spawn(job); + return; + } + if let Err(send_err) = self.tx.send(job) { + std::thread::spawn(send_err.0); + } + } +} + /// Check if a value is a record without __html field fn is_jsonl_record(value: &Value) -> bool { matches!(value, Value::Record { val, .. } if val.get("__html").is_none()) @@ -123,57 +177,66 @@ pub fn spawn_eval_thread( let (stream_tx, stream_rx) = tokio_mpsc::channel(32); let mut iter = stream.into_inner(); - // Peek first value to determine mode - let first = iter.next(); - let use_jsonl = first.as_ref().is_some_and(is_jsonl_record); - let content_type = if use_jsonl { - Some("application/x-ndjson".to_string()) - } else { - inferred_content_type - }; - - let _ = body_tx.send(( - content_type, - http_meta, - ResponseTransport::Stream(stream_rx), - )); - - // Helper to send a value - let send_value = |stream_tx: &tokio_mpsc::Sender>, value: Value| -> bool { - let bytes = if use_jsonl { - let mut line = - serde_json::to_vec(&value_to_json(&value)).unwrap_or_default(); - line.push(b'\n'); - line + // Everything from the first-value peek on runs handler code: + // the stream is lazy, and for a follow-style SSE handler even + // the *first* value can block for minutes. Hand the whole + // thing -- peek, response send, drain loop -- to a dedicated + // thread so it never pins a pool worker. The client sees the + // same timing as before: the response starts once the first + // value (and with it the content-type) is known. + std::thread::spawn(move || { + let first = iter.next(); + let use_jsonl = first.as_ref().is_some_and(is_jsonl_record); + let content_type = if use_jsonl { + Some("application/x-ndjson".to_string()) } else { - value_to_bytes(value) + inferred_content_type }; - stream_tx.blocking_send(bytes).is_ok() - }; - // Process first value - if let Some(value) = first { - if let Value::Error { error, .. } = &value { - let working_set = StateWorkingSet::new(&engine.state); - log_error(&format_cli_error(None, &working_set, error.as_ref(), None)); - return Ok(()); - } - if !send_value(&stream_tx, value) { - return Ok(()); - } - } + let _ = body_tx.send(( + content_type, + http_meta, + ResponseTransport::Stream(stream_rx), + )); + + // Helper to send a value + let send_value = + |stream_tx: &tokio_mpsc::Sender>, value: Value| -> bool { + let bytes = if use_jsonl { + let mut line = + serde_json::to_vec(&value_to_json(&value)).unwrap_or_default(); + line.push(b'\n'); + line + } else { + value_to_bytes(value) + }; + stream_tx.blocking_send(bytes).is_ok() + }; - // Process remaining values - for value in iter { - if let Value::Error { error, .. } = &value { - let working_set = StateWorkingSet::new(&engine.state); - log_error(&format_cli_error(None, &working_set, error.as_ref(), None)); - break; + // Process first value + if let Some(value) = first { + if let Value::Error { error, .. } = &value { + let working_set = StateWorkingSet::new(&engine.state); + log_error(&format_cli_error(None, &working_set, error.as_ref(), None)); + return; + } + if !send_value(&stream_tx, value) { + return; + } } - if !send_value(&stream_tx, value) { - break; + + // Process remaining values + for value in iter { + if let Value::Error { error, .. } = &value { + let working_set = StateWorkingSet::new(&engine.state); + log_error(&format_cli_error(None, &working_set, error.as_ref(), None)); + break; + } + if !send_value(&stream_tx, value) { + break; + } } - } + }); Ok(()) } PipelineData::ByteStream(stream, meta) => { @@ -191,61 +254,60 @@ pub fn spawn_eval_thread( let mut reader = stream .reader() .ok_or_else(|| "ByteStream has no reader".to_string())?; - let mut buf = vec![0; 8192]; - loop { - match reader.read(&mut buf) { - Ok(0) => break, // EOF - Ok(n) => { - if stream_tx.blocking_send(buf[..n].to_vec()).is_err() { - break; + // Drain on a dedicated thread; see the ListStream arm. + std::thread::spawn(move || { + let mut buf = vec![0; 8192]; + loop { + match reader.read(&mut buf) { + Ok(0) => break, // EOF + Ok(n) => { + if stream_tx.blocking_send(buf[..n].to_vec()).is_err() { + break; + } } - } - Err(err) => { - // Try to extract ShellError from the io::Error for proper formatting - use nu_protocol::shell_error::bridge::ShellErrorBridge; - if let Some(bridge) = err - .get_ref() - .and_then(|e| e.downcast_ref::()) - { - let working_set = StateWorkingSet::new(&engine.state); - log_error(&format_cli_error(None, &working_set, &bridge.0, None)); - break; // Error already logged, just stop streaming + Err(err) => { + // Try to extract ShellError from the io::Error for proper formatting + use nu_protocol::shell_error::bridge::ShellErrorBridge; + if let Some(bridge) = err + .get_ref() + .and_then(|e| e.downcast_ref::()) + { + let working_set = StateWorkingSet::new(&engine.state); + log_error(&format_cli_error( + None, + &working_set, + &bridge.0, + None, + )); + } else { + log_error(&err.to_string()); + } + break; } - return Err(err.into()); } } - } + }); Ok(()) } } } - // Create a thread job for this evaluation - let (sender, _receiver) = mpsc::channel(); - let signals = engine.state.signals().clone(); - let job = ThreadJob::new(signals, Some("HTTP Request".to_string()), sender); - - // Add the job to the engine's job table - let job_id = { - let mut jobs = engine.state.jobs.lock().expect("jobs mutex poisoned"); - jobs.add_job(Job::Thread(job.clone())) - }; - - std::thread::spawn(move || -> Result<(), std::convert::Infallible> { + eval_pool().execute(Box::new(move || { let mut meta_tx_opt = Some(meta_tx); let mut body_tx_opt = Some(body_tx); // Wrap the evaluation in catch_unwind so that panics don't poison the // async runtime and we can still send a response back to the caller. + // + // The engine is used as-is: run_closure never mutates the state, and + // the engine carries a long-lived background job (attached at + // construction) so externals spawned by the handler are tracked. let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| { - let mut local_engine = (*engine).clone(); - local_engine.state.current_job.background_thread_job = Some(job); - // Take the senders for the inner call. If the evaluation completes // successfully, these senders will have been consumed. Otherwise we // will use the remaining ones to send an error response. inner( - Arc::new(local_engine), + engine, request, stream, meta_tx_opt.take().unwrap(), @@ -276,15 +338,7 @@ pub fn spawn_eval_thread( )); } } - - // Clean up job when done - { - let mut jobs = engine.state.jobs.lock().expect("jobs mutex poisoned"); - jobs.remove_job(job_id); - } - - Ok(()) - }); + })); (meta_rx, body_rx) } diff --git a/www-next-gen/.gitignore b/www-next-gen/.gitignore new file mode 100644 index 0000000..8b5f1da --- /dev/null +++ b/www-next-gen/.gitignore @@ -0,0 +1,8 @@ +# Stellar license key - never commit +stellar.key + +# transient stellar gen output +/css/ + +# local cross.stream store, if used +/store/ diff --git a/www-next-gen/README.md b/www-next-gen/README.md new file mode 100644 index 0000000..4fa5710 --- /dev/null +++ b/www-next-gen/README.md @@ -0,0 +1,87 @@ +# www-next-gen + +A work-in-progress redesign of the http-nu site, served by http-nu itself. + +- The whole site rides a brand-blue surface generated from + [Stellar](https://github.com/starfederation/stellar) design tokens (ocean in + light, a deep navy in dark), with a light/dark toggle on every page. +- The docs are the project [`README.md`](../README.md) split into navigable + pages at request time by `readme.nu` (via Nushell's `from md --verbose` AST), + so the README stays the single source of truth. + +## Run + +```bash +http-nu --watch --datastar :3001 www-next-gen/www.nu +``` + +Then visit http://localhost:3001 (landing) and http://localhost:3001/docs. +`--watch` hot-reloads the handler when files in this directory change. + +## Styling tiers + +Stellar custom properties sit at the base; everything draws from them. + +- `assets/stellar.css` - the generated token dump (see below). +- `assets/base.css` - raw HTML elements (great typography with no classes), + then atomic utilities, then a small set of components. +- `assets/brand.css` - the brand layer: the blue surface, the nav treatment, + and the landing hero. Colors come from stellar named-color seeds. + +## Regenerating stellar.css + +`stellar.css` is generated from `stellar.config.json`, which carries the design +decisions: the www palette as named colors (ocean/navy/sand/orange/grape/red/ +green/stream), Source Sans 3 / Source Code Pro as the `sans` / `mono` families, +and a `1.125` base-font multiplier (18px root). Do typography in the config, not +in CSS overrides. + +The config is on the Stellar **v0.0.2** schema. The license key lives in +`~/.config/stellar/stellar.key` (found automatically; never commit it). + +```bash +cd www-next-gen +stellar gen -i stellar.config.json # writes css/stellar.css +mv css/stellar.css assets/stellar.css && rm -rf css +``` + +If `stellar` rejects the config (an older v1 schema), migrate it once first +(idempotent): `python3 ~/understand-stellar/tools/migrate-config.py +stellar.config.json`. + +## Fonts + +Both fonts are **self-hosted** `woff2` in `assets/`, declared with `@font-face` +in `base.css` whose family names match the `--font-sans` / `--font-mono` tokens +exactly. `settings.export.includeFontImports` is off, so `stellar.css` imports +no CDN font either - the page makes zero third-party font requests. The +`/assets/:file` route matches one path segment, so keep font files flat in +`assets/`. + +- **Prose** (`--font-sans`): Source Sans 3, 400 / 700. +- **Code** (`--font-mono`): **Iosevka X**, a custom no-ligature build, 400 / 700. + Chosen over Source Code Pro because it ships box-drawing glyphs, so Nushell + table output and ASCII line up (Source Code Pro lacked them, forcing a + mismatched-width fallback). Ligatures are off so readers see the literal + characters they would type. + +### Regenerating Iosevka X + +The build recipe is [`iosevka-x.build-plan.toml`](iosevka-x.build-plan.toml) +(no ligatures, dotted zero, a few variant touches; regular + bold, upright, +normal width). To rebuild: + +```bash +git clone --depth 1 -b v34.6.3 https://github.com/be5invis/Iosevka.git +cp iosevka-x.build-plan.toml Iosevka/private-build-plans.toml +cd Iosevka && npm install && npm run build -- ttf::iosevka-X # needs ttfautohint +``` + +Then subset each weight to the web (keeps it ~40 KB instead of ~10 MB): + +```bash +RANGES="U+0000-00FF,U+2010-2027,U+2030-205F,U+20AC,U+2190-21FF,U+2200-22FF,U+2500-259F,U+25A0-25FF" +pyftsubset dist/iosevka-X/TTF/iosevka-X-Regular.ttf --unicodes="$RANGES" \ + --layout-features='' --flavor=woff2 --output-file=assets/iosevka-x-400.woff2 +# repeat for Bold -> iosevka-x-700.woff2 +``` diff --git a/www-next-gen/assets/README.md b/www-next-gen/assets/README.md new file mode 100644 index 0000000..bf3f023 --- /dev/null +++ b/www-next-gen/assets/README.md @@ -0,0 +1,100 @@ +# Code syntax highlighting + +How fenced code in the docs gets colored, and how to keep the +TextMate-scope -> stellar-token mapping in `base.css` complete as languages +change. + +## Pipeline + +`readme.nu` renders each page with `... | .md | get __html`. http-nu's `.md` +highlights fenced code via TextMate grammars (the `.highlight` engine), +emitting nested ``, where `` is a TextMate +scope name flattened into space-separated classes - e.g. +`class="keyword operator comparison nu"` or `class="support function builtin bash"`. + +## The mapping (base.css) + +The `pre . { color: var(--code-) }` rules map those scope classes +to stellar's `--code-*` syntax tokens. CSS multi-class selectors +(`.keyword.operator`, `.entity.name.struct`) mean the most-specific matching +rule wins; anything unmatched inherits `pre .source` -> `--code-fg`. + +Stellar generates the `--code-*` palette (plus a coordinated dark variant) from +`colors.code` in `stellar.config.json`; we only consume the tokens. + +## Documented languages + +The README uses three fenced languages: **bash**, **nushell**, **rust**. The +mapping is audited against all three. + +## Re-running the audit + +1. Write an *exhaustive* sample per language covering every construct: comments + (line / block / doc), strings (single / double / interpolated / raw / byte / + char + escapes), numbers (int / float / hex / bin / oct / suffix), operators + (arithmetic, comparison, logical, assignment), keywords (control + + declaration), function calls, builtins / macros, variables / members / + params, types, structural punctuation, and a deliberately invalid token. + +2. Highlight each and capture the emitted scopes: + + ```bash + http-nu eval -c '(open --raw sample.nu) | .highlight nu | get __html' + ``` + +3. Cross-reference the emitted scopes against the CSS - resolve each scope to a + token (most-specific wins), and report scopes that fall through to + `--code-fg` plus `--code-*` tokens nothing uses: + + ```python + import re + base = open("base.css").read() + mapped = [] + for m in re.finditer(r'((?:pre \.[\w.-]+,\s*)*pre \.[\w.-]+)\s*\{\s*color:\s*var\((--code-[\w-]+)\)', base): + for sel in re.findall(r'pre (\.[\w.-]+)', m.group(1)): + mapped.append((frozenset(sel.strip(".").split(".")), m.group(2))) + + def resolve(classes): # classes = a span's class list, minus the lang tag + cs = set(classes) + hits = [(len(req), tok) for req, tok in mapped if req <= cs] + return max(hits)[1] if hits else "--code-fg (fall-through)" + ``` + +## Findings + +Every *meaningful* scope the three grammars emit maps to an appropriate token. +The only fall-throughs to `--code-fg` are **structural wrappers** - +`meta.braces / block / group / number`, `punctuation.section / separator` - +which is correct: their inner tokens are already colored, so the wrapper should +stay the default foreground (coloring it would just tint whitespace/brackets). + +Assignments added during the audit (the gaps rust/nu exposed): + +| scope | token | +| --- | --- | +| `entity.name.struct` / `trait` / `impl` | `--code-name-class` | +| `entity.name.label` (loop labels) | `--code-name-label` | +| `support.macro` (rust macros) | `--code-name-function` | +| `constant.other` | `--code-name-constant` | +| `constant.other.placeholder` (`{}` / `{name}`) | `--code-string-escape` | +| `invalid` (bad identifiers) | `--code-error` | + +### Stellar code tokens we don't use + +These have no scope in bash / nushell / rust (they serve languages and +constructs we don't document), so they are intentionally unused. They would +light up if we added the relevant language: + +- markup / diff / markdown: `--code-emph`, `--code-strong`, `--code-deleted`, + `--code-inserted`, `--code-comment-special` +- HTML / XML: `--code-name-tag`, `--code-name-attribute`, `--code-name-entity` +- other languages: `--code-name-decorator` (Python), `--code-name-exception`, + `--code-name-namespace`, `--code-keyword-const` (nu booleans scope as builtins) +- specialty strings: `--code-string-affix`, `--code-string-doc`, + `--code-string-regex` + +## Adding a language + +Add a fenced sample, re-run the audit, and map any new non-structural +fall-throughs to the closest `--code-*` token. If a needed concept has no +matching stellar token, that's a `colors.code` config question, not a CSS one. diff --git a/www-next-gen/assets/base.css b/www-next-gen/assets/base.css new file mode 100644 index 0000000..b3e7a80 --- /dev/null +++ b/www-next-gen/assets/base.css @@ -0,0 +1,1050 @@ +/* + * base.css - semantic HTML on stellar variables. + * + * Layer 1 styles raw elements only, so plain semantic markup gets good + * typography with no classes. Layer 2 is a small set of utility classes + * for the bits element selectors cannot cover. + * + * Pairs with stellar.css (regenerate with: stellar gen, with stellar.key + * next to stellar.config.json). Light/dark is handled by stellar's + * :root.dark variable overrides; toggle the `dark` class on . + */ + +/* ------------------------------------------------------------------ */ +/* MPA view transitions: opt into the browser's default cross-fade on */ +/* same-origin navigations (Themes -> Reference, etc). Progressive */ +/* enhancement - unsupported browsers just navigate instantly. */ +/* ------------------------------------------------------------------ */ +@view-transition { + navigation: auto; +} +/* the root cross-fade, tuned live by the dialog in vt-tuner.js via these custom + properties and classes on (persisted to localStorage). */ +::view-transition-old(root), +::view-transition-new(root) { + animation-duration: var(--vt-duration, var(--anim-duration-fast)); + animation-timing-function: var(--vt-ease, var(--anim-ease-standard)); +} +/* off: swap with no fade */ +:root.vt-off::view-transition-old(root), +:root.vt-off::view-transition-new(root) { + animation: none; +} +/* fade-in only: hold the outgoing page, fade the new one in over it */ +:root.vt-fadein::view-transition-old(root) { + animation: none; +} +/* slide: the new page rises as it fades in */ +:root.vt-slide::view-transition-new(root) { + animation-name: vt-slide-in; +} +@keyframes vt-slide-in { + from { opacity: 0; transform: translateY(0.8rem); } + to { opacity: 1; transform: translateY(0); } +} +/* scope: name the nav and main so they are their own groups; the page chrome + stays put while only the content cross-fades. */ +:root.vt-scope .nav { view-transition-name: vt-nav; } +:root.vt-scope main { view-transition-name: vt-main; } +@media (prefers-reduced-motion: reduce) { + ::view-transition-old(root), + ::view-transition-new(root) { + animation-duration: 0s; + } +} + +/* the tuner: launched from a toolbox button in the nav, opens this dialog. */ +.vt-toggle iconify-icon { + display: block; + transition: transform var(--anim-duration-base) var(--anim-ease-bounce); +} +.vt-toggle:hover iconify-icon { transform: rotate(-9deg) scale(1.14); } +.vt-toggle[aria-pressed="true"] iconify-icon { transform: rotate(-6deg); } +#vt-tuner { + border: var(--border-width-1) solid var(--neutral-5); + border-radius: var(--border-radius-2); + background: var(--surface); + color: var(--surface-on); + padding: var(--size-1) var(--size-2); + max-width: min(92vw, 24rem); + font-size: var(--font-size--1); +} +/* open as a non-modal panel docked to the right edge, so the page transition + stays visible behind it; named so it holds steady through the transition. */ +#vt-tuner[open] { + position: fixed; + inset: auto var(--size-1) auto auto; + top: 50%; + transform: translateY(-50%); + margin: 0; + max-height: 92vh; + overflow: auto; + view-transition-name: vt-panel; +} +#vt-tuner h2 { margin: 0 0 var(--size-0); font-size: var(--font-size-1); } +.vt-note { + margin: calc(-1 * var(--size--2)) 0 var(--size-0); + max-width: 34ch; + font-size: var(--font-size--2); + opacity: 0.72; +} +.vt-row { + display: flex; + align-items: center; + justify-content: space-between; + gap: var(--size-1); + margin-block: var(--size-0); +} +.vt-row input[type="range"] { flex: 1; } +.vt-row select, +.vt-row input, +#vt-tuner button { font: inherit; } +.vt-hint { margin: var(--size-1) 0 var(--size-0); opacity: 0.7; } +.vt-tryto { display: flex; gap: var(--size-1); flex-wrap: wrap; } +.vt-tryto a { + text-decoration: none; + border: var(--border-width-1) solid var(--neutral-5); + border-radius: var(--border-radius-1); + padding: 0.1em 0.7em; + color: inherit; +} +.vt-tryto a:hover { background: var(--named-ocean-1); } +.vt-actions { display: flex; gap: var(--size-1); justify-content: flex-end; margin-top: var(--size-1); } + +/* ------------------------------------------------------------------ */ +/* Fonts: self-hosted, names must match the stellar --font-* tokens. */ +/* Source Sans 3 for prose (--font-sans); Iosevka X for code */ +/* (--font-mono) - a custom no-ligature build with box-drawing glyphs, */ +/* so Nushell tables and ASCII line up (Source Code Pro lacked them). */ +/* 400 + 700 only; subset to Latin + punctuation + box/blocks/shapes. */ +/* ------------------------------------------------------------------ */ + +@font-face { + font-family: "Source Sans 3"; + src: url("/assets/source-sans-3-400.woff2") format("woff2"); + font-weight: 400; + font-display: swap; +} +@font-face { + font-family: "Source Sans 3"; + src: url("/assets/source-sans-3-700.woff2") format("woff2"); + font-weight: 700; + font-display: swap; +} +@font-face { + font-family: "Iosevka X"; + src: url("/assets/iosevka-x-400.woff2") format("woff2"); + font-weight: 400; + font-display: swap; +} +@font-face { + font-family: "Iosevka X"; + src: url("/assets/iosevka-x-700.woff2") format("woff2"); + font-weight: 700; + font-display: swap; +} + +/* ------------------------------------------------------------------ */ +/* Layer 1: elements */ +/* ------------------------------------------------------------------ */ + +*, +*::before, +*::after { + box-sizing: border-box; +} + +html { + color-scheme: light; +} + +html.dark { + color-scheme: dark; +} + +body { + margin: 0; + font-family: var(--font-sans); + font-size: var(--font-size-0); + line-height: var(--font-line-height-2); + background: var(--neutral-1); + color: var(--neutral-1-on); + -webkit-text-size-adjust: 100%; +} + +h1, +h2, +h3, +h4, +h5, +h6 { + color: inherit; + line-height: var(--font-line-height-0); + text-wrap: balance; +} + +h1 { + font-size: var(--font-size-5); + font-weight: var(--font-weight-bold); + letter-spacing: var(--font-letter-spacing--1); + margin: var(--size-3) 0 var(--size-1); +} + +h2 { + font-size: var(--font-size-3); + font-weight: var(--font-weight-semi-bold); + margin: var(--size-3) 0 var(--size-1); +} + +h3 { + font-size: var(--font-size-2); + font-weight: var(--font-weight-semi-bold); + margin: var(--size-2) 0 var(--size-0); +} + +h4 { + font-size: var(--font-size-1); + font-weight: var(--font-weight-semi-bold); + margin: var(--size-1) 0 var(--size-0); +} + +h5, +h6 { + font-size: var(--font-size-0); + font-weight: var(--font-weight-semi-bold); + margin: var(--size-1) 0 var(--size-0); +} + +/* Headings that are themselves links read as headings (no underline); they + inherit the heading color from the core `a` rule below. */ +h1 a, +h2 a, +h3 a, +h4 a, +h5 a, +h6 a { + text-decoration: none; +} + +h1 a:hover, +h2 a:hover, +h3 a:hover, +h4 a:hover, +h5 a:hover, +h6 a:hover { + text-decoration: underline; +} + +p { + margin: var(--size--2) 0; +} + +/* a paragraph that introduces a list hugs it, so a "label:" + its list read as + one unit. 0.4em is a justified literal - below the stellar size floor. */ +p:has(+ ul), +p:has(+ ol) { + margin-bottom: 0.4em; +} +p + ul, +p + ol { + margin-top: 0; +} + +/* links ride the surrounding text color, set apart by their underline; this + works on any surface (neutral or a brand blue), so no per-surface color */ +a { + color: inherit; + text-underline-offset: 0.15em; + /* the font's own (thin) underline metric; border-width tokens are too heavy + for a text underline. Thickens to a token on hover. */ + text-decoration-thickness: from-font; +} + +a:hover { + text-decoration-thickness: var(--border-width-1); +} + +strong { + font-weight: var(--font-weight-bold); +} + +small { + font-size: var(--font-size--1); + color: var(--neutral-1-dim); +} + +mark { + background: var(--tertiary-4); + color: var(--tertiary-4-on); +} + +code, +kbd, +samp { + font-family: var(--font-mono); + font-size: 0.9em; +} + +code { + background: var(--code-bg); + color: var(--code-fg); + /* justified literal: stellar's spacing scale is viewport-fluid and floors at + ~13px, too wide for an inline chip, so it can't express tight text-relative + padding. em keeps it proportional to the text; the vertical stays small so + the chip doesn't blow out the prose line-height. */ + padding: 0.2em 0.45em; + border-radius: var(--border-radius-1); + /* keep short code spans whole - don't break a token like --topic at its hyphen */ + white-space: nowrap; +} + +kbd { + background: var(--neutral-3); + color: var(--neutral-3-on); + border: var(--border-width-1) solid var(--neutral-5); + border-bottom-width: var(--border-width-2); + border-radius: var(--border-radius-1); + padding: var(--size--2) var(--size--1); +} + +/* code line-height: a tight terminal ratio so stacked box-drawing glyphs + (Nushell tables) join into continuous lines. A unitless literal on purpose - + stellar's line-heights are absolute rems tuned for prose (~1.5 on the code + font), too loose to connect box-drawing; this is the named exception. */ +:root { + --code-line-height: 1.25; +} + +pre { + font-family: var(--font-mono); + font-size: var(--font-size--1); + line-height: var(--code-line-height); + background: var(--code-bg); + color: var(--code-fg); + border: var(--border-width-1) solid var(--code-border); + border-radius: var(--border-radius-1); + padding: var(--size--2) var(--size--1); + overflow-x: auto; + scrollbar-gutter: stable; + margin: var(--size--1) 0; +} + +pre code { + background: none; + border-radius: 0; + padding: 0; + color: inherit; + font-size: inherit; + /* block code keeps the pre's preserved newlines; the inline `code` nowrap + must not collapse a fenced block onto one line */ + white-space: pre; +} + +ul, +ol { + margin: var(--size--2) 0; + padding-left: var(--size-3); + line-height: var(--font-line-height-0); +} + +li { + /* tight item spacing; --size--2 (the floor, ~13px) is far too padded for a + list. em-relative, justified literal like the other tight component gaps. */ + margin: 0.35em 0; +} + +li > ul, +li > ol { + margin: var(--size--2) 0; +} + +dl { + margin: var(--size-0) 0; +} + +dt { + font-weight: var(--font-weight-semi-bold); +} + +dd { + margin: 0 0 var(--size--1) var(--size-2); +} + +blockquote { + margin: var(--size-1) 0; + padding: var(--size--2) var(--size-2); + border-left: var(--border-width-3) solid var(--neutral-4); + color: var(--neutral-1-dim); +} + +blockquote > :first-child { + margin-top: 0; +} + +blockquote > :last-child { + margin-bottom: 0; +} + +table { + border-collapse: collapse; + width: 100%; + margin: var(--size-1) 0; +} + +th { + background: var(--neutral-3); + color: var(--neutral-3-on); + font-weight: var(--font-weight-semi-bold); + text-align: left; +} + +th, +td { + /* tight fixed cell padding: stellar's size scale floors at ~13px and is + viewport-fluid (layout spacing), too loose for compact table cells - the + same justified-literal case as inline code. */ + padding: 0.2rem 0.55rem; + border: var(--border-width-1) solid var(--neutral-4); +} + +hr { + border: none; + border-top: var(--border-width-1) solid var(--neutral-4); + margin: var(--size-2) 0; +} + +img, +video { + max-width: 100%; + height: auto; +} + +img { + border-radius: var(--border-radius-1); +} + +/* long-form content column: cap the reading measure (was the .prose utility) */ +article { + max-width: 99ch; +} + +figure { + margin: var(--size-1) 0; +} + +figcaption { + font-size: var(--font-size--1); + color: var(--neutral-1-dim); + text-align: center; + margin-top: var(--size--2); +} + +details { + margin: var(--size-0) 0; + border: var(--border-width-1) solid var(--neutral-4); + border-radius: var(--border-radius-1); + padding: var(--size--1) var(--size-0); +} + +summary { + cursor: pointer; + font-weight: var(--font-weight-semi-bold); +} + +/* one button: a mono, currentColor-outlined action that reads on any surface. + variants .btn-go (lit primary) and .btn-tab (segmented) live in brand.css. */ +button { + -webkit-appearance: none; + appearance: none; + font-family: var(--font-mono); + font-size: var(--font-size--1); + color: inherit; + background: transparent; + border: var(--border-width-1) solid color-mix(in oklch, currentColor 35%, transparent); + border-radius: var(--border-radius-1); + padding: 0.1em 0.6em; + cursor: pointer; + transition: background var(--anim-duration-fast) var(--anim-ease-standard), + border-color var(--anim-duration-fast) var(--anim-ease-standard); +} +button:hover { + background: color-mix(in oklch, currentColor 12%, transparent); + border-color: currentColor; +} +button:active { transform: translateY(1px); } + +input, +select, +textarea { + font: inherit; + color: var(--neutral-2-on); + background: var(--neutral-2); + border: var(--border-width-1) solid var(--neutral-5); + border-radius: var(--border-radius-1); + padding: var(--size--2) var(--size-0); +} + +:focus-visible { + outline: var(--border-width-2) solid var(--primary-9); + outline-offset: 2px; +} + +/* Syntax tokens emitted by .highlight / .md code blocks. */ + +pre .source { + color: var(--code-fg); +} + +pre .comment { + color: var(--code-comment); +} + +pre .string { + color: var(--code-string); +} + +pre .constant.numeric { + color: var(--code-number); +} + +pre .constant.language { + color: var(--code-keyword-const); +} + +pre .constant.character { + color: var(--code-string-escape); +} + +pre .keyword { + color: var(--code-keyword); +} + +pre .keyword.operator { + color: var(--code-operator); +} + +pre .storage, +pre .storage.type { + color: var(--code-keyword-decl); +} + +pre .entity.name.function { + color: var(--code-name-function); +} + +pre .entity.name.type, +pre .entity.name.class { + color: var(--code-name-class); +} + +pre .entity.name.tag { + color: var(--code-name-tag); +} + +pre .entity.other.attribute-name { + color: var(--code-name-attribute); +} + +pre .variable { + color: var(--code-name-variable); +} + +pre .variable.other.member { + color: var(--code-name-property); +} + +pre .variable.parameter.option { + color: var(--code-name-label); +} + +pre .support.function { + color: var(--code-name-builtin); +} + +pre .support.function.keywords { + color: var(--code-keyword-namespace); +} + +pre .support.type, +pre .support.class { + color: var(--code-type); +} + +pre .punctuation { + color: var(--code-fg); +} + +pre .punctuation.definition.string { + color: var(--code-string-delim); +} + +pre .punctuation.definition.comment { + color: var(--code-comment); +} + +pre .meta.function-call { + color: var(--code-name); +} + +pre .meta.string { + color: var(--code-string); +} + +/* type-like definitions (rust struct/trait/impl) */ +pre .entity.name.struct, +pre .entity.name.trait, +pre .entity.name.impl { + color: var(--code-name-class); +} + +pre .entity.name.label { + color: var(--code-name-label); +} + +/* rust macros invoke like functions */ +pre .support.macro { + color: var(--code-name-function); +} + +pre .constant.other { + color: var(--code-name-constant); +} + +/* format-string placeholders ({} / {name}) read as special string content */ +pre .constant.other.placeholder { + color: var(--code-string-escape); +} + +/* syntax errors (e.g. invalid variable names) */ +pre .invalid { + color: var(--code-error); +} + +/* ------------------------------------------------------------------ */ +/* Layer 2: utilities (atomic, single purpose, composed in markup) */ +/* ------------------------------------------------------------------ */ + +.container { width: 100%; max-width: 1200px; margin-inline: auto; padding-inline: var(--size-2); } +.center { text-align: center; } +.muted { color: var(--neutral-1-dim); } +.mx-auto { margin-inline: auto; } +.upper { text-transform: uppercase; letter-spacing: var(--font-letter-spacing-1); } +.grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); gap: var(--size-2); } + +/* ------------------------------------------------------------------ */ +/* Layer 3: components (a small, composable set) */ +/* ------------------------------------------------------------------ */ + +/* nav: brand left, links right; colors inherit from the surrounding context */ +.nav { + display: flex; + align-items: center; + justify-content: space-between; + gap: var(--size-1); + max-width: 1200px; + margin-inline: auto; + padding: var(--size-1) var(--size-2); +} + +.nav a { text-decoration: none; } +.nav a:hover { text-decoration: underline; } +.nav .brand { font-weight: var(--font-weight-bold); } +.nav .links { display: flex; align-items: center; gap: var(--size-2); } + +/* structure only; the surface comes from a `.panel` utility composed in markup */ +.card { + display: block; + border: var(--border-width-1) solid var(--neutral-4); + border-radius: var(--border-radius-2); + padding: var(--size-1); + box-shadow: var(--shadow-2); + text-decoration: none; + transition-property: transform, box-shadow, background; + transition-duration: var(--anim-duration-fast); + transition-timing-function: var(--anim-ease-standard); +} + +/* sub-text inside a card dims the card's own color, not the page surface */ +.card :is(small, .muted) { + color: inherit; + opacity: 0.7; +} + +/* sidebar layout: a flex row that stacks on small screens */ +.with-sidebar { display: flex; gap: var(--size-3); align-items: flex-start; } +.with-sidebar > * { min-width: 0; } + +/* docs menu: a sticky sidebar on desktop, a collapsible disclosure on narrow + screens, the page list collapses behind a toggle driven by a Datastar `nav` + signal (the toggle button only shows on mobile; the sidebar itself is the + `.docs-side` nav). */ +.docs-toggle { + font: inherit; + font-weight: var(--font-weight-semi-bold); + background: none; + border: none; + border-bottom: var(--border-width-1) solid var(--line, var(--neutral-4)); + border-radius: 0; + color: inherit; + cursor: pointer; + width: 100%; + text-align: left; + padding: var(--size--1) 0; +} +.docs-toggle:hover { background: none; } +.docs-toggle::after { content: " \25be"; } +.docs-menu.open .docs-toggle::after { content: " \25b4"; } + +/* 940px: below this, the sidebar would squeeze the reading column under 72ch, + so collapse it to the toggle and give the article the full width. */ +@media (min-width: 941px) { + .docs-menu { + flex-shrink: 0; + width: 210px; + position: sticky; + top: var(--size-2); + max-height: calc(100vh - var(--size-3)); + overflow-y: auto; + } + .docs-toggle { display: none; } +} + +@media (max-width: 940px) { + /* stretch (not flex-start) so the stacked article fills the column and + shrinks to the viewport; wide code then scrolls inside its own block + instead of pushing the page wider */ + .with-sidebar { flex-direction: column; align-items: stretch; } + .docs-menu { width: 100%; } + .docs-side { display: none; } + .docs-menu.open .docs-side { display: block; } +} + +/* toc: nav list with group labels and an active item */ +.toc ul { list-style: none; padding-left: 0; margin: 0; } +.toc ul ul { padding-left: var(--size-1); } +.toc li { margin: var(--size--2) 0; } +.toc a { color: var(--neutral-1-dim); text-decoration: none; } +.toc a:hover { color: var(--neutral-1-on); text-decoration: underline; } +.toc a.active { color: var(--neutral-1-on); font-weight: var(--font-weight-semi-bold); } +.toc-group { + display: block; + font-size: var(--font-size--1); + font-weight: var(--font-weight-semi-bold); + text-transform: uppercase; + letter-spacing: var(--font-letter-spacing-1); + color: var(--neutral-1-dim); + margin: var(--size-1) 0 var(--size--2); +} + +/* pager: prev / next cards beneath a doc page. Two columns so the next card + stays right even when there is no prev; each is a panel that lifts on hover. */ +.pager { + display: grid; + grid-template-columns: 1fr 1fr; + gap: var(--size-1); + margin-top: var(--size-3); +} +.pager-link { + display: flex; + flex-direction: column; + gap: 0.15em; + /* compact; the stellar size floor is too padded for a small nav card */ + padding: 0.5rem 0.85rem; + border: var(--border-width-1) solid var(--neutral-4); + border-radius: var(--border-radius-1); + text-decoration: none; + transition-property: transform, box-shadow, background; + transition-duration: var(--anim-duration-fast); + transition-timing-function: var(--anim-ease-standard); +} +.pager-link:hover { + transform: translateY(-2px); + box-shadow: var(--shadow-3); +} +.pager-prev { + grid-column: 1; + align-items: flex-start; +} +.pager-next { + grid-column: 2; + align-items: flex-end; + text-align: right; +} +.pager-dir { + display: inline-flex; + align-items: center; + gap: 0.3em; + font-size: var(--font-size--1); + text-transform: uppercase; + letter-spacing: var(--font-letter-spacing-1); +} +.pager-page { + font-weight: var(--font-weight-semi-bold); +} + +/* code block with a copy button (markup from inject-copy-btns) */ +.code-block { position: relative; } +.copy-btn { + position: absolute; + top: var(--size--1); + right: var(--size--1); + background: var(--code-bg); + border: var(--border-width-1) solid var(--code-border); + color: var(--code-fg); + cursor: pointer; + opacity: 0; + padding: 0.35rem; + border-radius: var(--border-radius-1); + display: flex; + align-items: center; + justify-content: center; + width: 28px; + height: 28px; + z-index: 1; +} + +.code-block:hover .copy-btn { + opacity: 0.7; + transition: opacity var(--anim-duration-fast) var(--anim-ease-standard); +} + +.copy-btn:hover { + opacity: 1; + background: var(--primary-6); + color: var(--primary-6-on); +} + +/* --- theme namespace: facet nav, playground, chips ------------------ */ + +/* facet switcher (Overview | Reference | Example) under the theme title */ +.facets { + display: flex; + gap: var(--size-1); + margin: var(--size--1) 0 var(--size-2); + border-bottom: var(--border-width-1) solid var(--neutral-4); +} +.facets a { + text-decoration: none; + padding: var(--size--2) 0; + font-weight: var(--font-weight-medium); + border-bottom: var(--border-width-2) solid transparent; + margin-bottom: calc(-1 * var(--border-width-1)); +} +.facets a.active { + border-bottom-color: currentColor; +} + +/* the .mj playground: two source panes, preset chips, a live output */ +.playground { + margin: var(--size-1) 0 var(--size-3); +} +.pg-inputs { + display: grid; + grid-template-columns: 1fr 1fr; + gap: var(--size-0); +} +.pg-field, +.pg-output { + display: flex; + flex-direction: column; + gap: var(--size--2); +} +.pg-field label, +.pg-output label, +.pg-presets { + font-size: var(--font-size--1); + text-transform: uppercase; + letter-spacing: var(--font-letter-spacing-1); +} +.playground textarea { + font-family: var(--font-mono); + font-size: var(--font-size--1); + line-height: var(--code-line-height); + padding: var(--size--1); + border: var(--border-width-1) solid var(--neutral-4); + border-radius: var(--border-radius-1); + background: var(--code-bg); + color: var(--code-fg); + resize: vertical; + outline: none; +} +.pg-presets { + display: flex; + align-items: center; + gap: var(--size--2); + margin: var(--size-0) 0; +} +.pg-output { + margin-top: var(--size-1); +} +.tpl-out { + padding: var(--size--1) var(--size-0); + border: var(--border-width-1) dashed var(--neutral-4); + border-radius: var(--border-radius-1); + min-height: 2.5em; +} +.tpl-out.is-err { + font-family: var(--font-mono); + font-size: var(--font-size--1); + color: var(--code-error); +} + +/* streaming theme: a live SSE row (buttons that patch in place) */ +.pg-live { + display: flex; + align-items: center; + flex-wrap: wrap; + gap: var(--size-0); + font-family: var(--font-mono); + font-size: var(--font-size--1); +} +.pg-val { + font-weight: var(--font-weight-bold); +} + +/* storage theme: the guestbook input + list */ +.pg-input { + font-family: var(--font-mono); + font-size: var(--font-size--1); + padding: 0.2em 0.6em; + border: var(--border-width-1) solid var(--neutral-4); + border-radius: var(--border-radius-1); + background: var(--code-bg); + color: var(--code-fg); + outline: none; +} +.guestbook ul { + margin: var(--size--1) 0 0; +} + +/* playground stays within narrow viewports: let grid cells shrink, stack inputs */ +.pg-field, +.pg-output { + min-width: 0; +} +.playground textarea { + width: 100%; +} +@media (max-width: 540px) { + .pg-inputs { + grid-template-columns: 1fr; + } +} + +/* narrow screens: let the top nav wrap instead of overflowing */ +@media (max-width: 540px) { + .nav { + flex-wrap: wrap; + } + .nav .links { + gap: var(--size-0); + } +} + +/* let the preset chips wrap rather than overflow on narrow screens */ +.pg-presets { + flex-wrap: wrap; +} + +/* shared site footer */ +.site-footer { + margin-top: var(--size-5); + padding: var(--size-2) 0; + border-top: var(--border-width-1) solid var(--neutral-4); + font-size: var(--font-size--1); +} + +/* hello-world tutorial: reuse the splash .terminal, two windows broken out to + full viewport width with economical chrome, so the log lines fit with little + scroll */ +.terminal-row { + display: flex; + flex-direction: column; + gap: var(--size-1); + margin-block: var(--size-2); +} +.terminal-row .terminal { + margin-block: 0; +} +.term-cmd { + display: flex; + align-items: center; + gap: var(--size--2); + flex-wrap: wrap; +} + +/* design section: brand palette table */ +.pal-table { border-collapse: collapse; width: 100%; max-width: 46rem; margin-block: var(--size-2); } +.pal-table th { + text-align: left; + font-size: var(--font-size--1); + font-weight: var(--font-weight-semi-bold); + color: var(--neutral-1-dim); + padding: var(--size--2) var(--size-0); + border-bottom: var(--border-width-2) solid var(--neutral-4); +} +.pal-table td { + padding: var(--size--2) var(--size-0); + border-bottom: var(--border-width-1) solid var(--neutral-3); + vertical-align: middle; +} +.pal-cell { + width: 2.6rem; + height: 1.4rem; + border-radius: var(--border-radius-1); + border: var(--border-width-1) solid var(--neutral-4); +} +.pal-name { font-weight: var(--font-weight-bold); } +.pal-ramp { display: flex; gap: var(--size--2); } +.pal-ramp .pal-cell { width: 3.2rem; } + +/* design section: component breakdown with per-part tokens */ +.dz-example { margin: var(--size-1) 0 var(--size-3); } +.dz-btns { display: flex; gap: var(--size-0); align-items: center; flex-wrap: wrap; } +.dz-part { margin-block: var(--size-2); } +.dz-part h3 { margin-bottom: var(--size--2); } +.dz-demo { margin: var(--size-0) 0; } +.dz-tokens { display: flex; flex-direction: column; gap: var(--size--2); font-size: var(--font-size--1); } +.tok { display: flex; align-items: center; gap: var(--size-0); } +.tok-label { min-width: 7em; color: var(--neutral-1-dim); } +.tok-sw { + flex: none; + width: 1.1em; + height: 1.1em; + border-radius: var(--border-radius-1); + border: var(--border-width-1) solid var(--neutral-4); +} +.tok-sw-none { border: 0; visibility: hidden; } +.terminal-title { + display: flex; + align-items: center; + gap: var(--size--1); +} +.terminal-action { + margin-left: auto; +} +.terminal-body code { + background: transparent; + padding: 0; + color: inherit; + white-space: pre; +} +.terminal-body pre { + background: transparent; + border: 0; + margin: 0; + padding: 0; + font: inherit; + color: inherit; + white-space: pre; +} +.term-out { + white-space: pre; + overflow-x: auto; +} +/* on wide viewports the splash demo runs server + client side by side; the + tutorial page keeps them stacked in its narrower reading column. */ +@media (min-width: 900px) { + .demo-wide .terminal-row { display: grid; grid-template-columns: 1fr 1fr; align-items: start; } + .demo-wide .terminal-row > * { min-width: 0; } +} diff --git a/www-next-gen/assets/brand.css b/www-next-gen/assets/brand.css new file mode 100644 index 0000000..fc8b9a7 --- /dev/null +++ b/www-next-gen/assets/brand.css @@ -0,0 +1,407 @@ +/* + * brand.css - the http-nu brand layer, loaded site-wide on top of base.css. + * + * Two brand colors, one per mode: ocean for light, a deeper navy for dark - + * hand-picked stellar named seeds (stellar.config.json, with the sand / orange + * grape / red / green / stream accents). Whichever mode is active, that one + * color IS the page surface; `--surface` / `--surface-on` / `--on-dim` / + * `--line` name its parts, and the `.dark body` block swaps the whole set + * ocean->navy. We choose the two colors by hand because they're exact brand + * blues stellar's tonal math wouldn't land on, and because the ocean->navy pair + * is a deliberate design choice, not two ends of one ramp. + * + * Within a mode, everything is built from the active color's ramp the way the + * reference builds from neutral: the page is step 0, raised panels and borders + * are higher steps (the `.panel` utility, `--line`), `-on` is the text, `-dim` + * is secondary text. No hand-mixed tints. + * + * A few hero geometry values (sticker shadow, tilt angles, tight badge padding) + * are literal on purpose - no stellar token matches them - and keep the landing + * pixel-faithful to the original ./www splash. Fonts also match the original. + */ + +/* --- the brand surface: ocean (light mode) ------------------------------ */ +/* the active brand color and its parts: the step is the page, -on is text, + -dim is secondary text, and a higher step is the border (raised panels use + the .panel utility). `.dark body` below swaps all of this to the navy color. */ +body { + --surface: var(--named-ocean-0); + --surface-on: var(--named-ocean-0-on); + --on-dim: var(--named-ocean-0-dim); + --line: var(--named-ocean-2); + /* hard offset "sticker" shadow shared by badges and cards; stellar only + models soft elevation, so this playful brand atom is a literal */ + --sticker-shadow: 2px 2px 0 rgba(0, 0, 0, 0.2); + background: var(--surface); + color: var(--surface-on); +} + +/* --- the brand surface: navy (dark mode) -------------------------------- */ +/* the second brand color; swapping these four tokens re-points the whole + surface system (panels, borders, dim text) at navy in one place. */ +.dark body { + --surface: var(--named-navy-0); + --surface-on: var(--named-navy-0-on); + --on-dim: var(--named-navy-0-dim); + --line: var(--named-navy-2); +} + +/* in the docs article, headings take the cream brand accent; their self-anchor + links inherit it (core `a` is `inherit`), so one rule covers both. Elsewhere + - card titles, nav - headings ride the local surface color from base's + `h1..h6: inherit`. */ +article :is(h1, h2, h3, h4, h5, h6) { + color: var(--named-sand-0); +} +body small, +body .muted, +body figcaption, +body blockquote { + color: var(--on-dim); +} +body hr, +body blockquote { + border-color: var(--line); +} +body :is(td, th) { + border-color: var(--line); +} + +/* raised surface utility: the +1 ramp step of the surface (a lighter ocean, + navy in dark) with its own -on for text - the reference's "surface step for + the background, its -on for text". Cards compose `card panel`; table headers + ride it too (they can't take a class). */ +.panel, +body th { + background: var(--named-ocean-1); + color: var(--named-ocean-1-on); +} +.dark .panel, +.dark body th { + background: var(--named-navy-1); + color: var(--named-navy-1-on); +} + +/* code blocks use base's --code-bg: stellar's primary code surface, the one its + syntax palette is tuned for; the hue is set by colors.code.index (a warm cream + index that echoes the sand brand accent - it reads as a light cream panel on the + blue and drops to a near-black on navy in dark). The brand only adds the line + border and gentle corners; inline keeps the secondary --code-inner-bg. */ +body pre { + border-color: var(--line); +} + +/* cards are blocky badge-style panels: the `.panel` surface step, gentle corners, + and the same hard sticker shadow as the splash badges. Each card sits at a + slight fixed tilt that varies card to card (a 5-step cycle, so it scatters + across the 3-column grid rather than lining up); hover does not change the + tilt - it lifts the card straight up and grows the shadow. The tilt is held + in --card-tilt so hover can keep the angle and add the lift. (The tilt + degrees and offset shadow are brand literals, like the badges.) */ +body .card { + border-color: var(--line); + box-shadow: var(--sticker-shadow); +} +.grid .card { + transform: rotate(var(--card-tilt, 0deg)); +} +.grid .card:nth-child(5n + 1) { --card-tilt: -1deg; } +.grid .card:nth-child(5n + 2) { --card-tilt: 0.7deg; } +.grid .card:nth-child(5n + 3) { --card-tilt: -0.5deg; } +.grid .card:nth-child(5n + 4) { --card-tilt: 1deg; } +.grid .card:nth-child(5n + 5) { --card-tilt: -0.8deg; } +body a.card:hover { + background: var(--named-ocean-2); + box-shadow: 4px 4px 0 rgba(0, 0, 0, 0.2); + transform: rotate(var(--card-tilt, 0deg)) translateY(-4px); +} +.dark body a.card:hover { + background: var(--named-navy-2); +} + +/* pager: prev/next ride the .panel surface; cream page title, dim direction + label, brightening one ramp step on hover */ +.pager-link { + border-color: var(--line); +} +.pager-link:hover { + background: var(--named-ocean-2); +} +.dark .pager-link:hover { + background: var(--named-navy-2); +} +.pager-dir { + color: var(--on-dim); +} +.pager-page { + color: var(--named-sand-0); +} + +/* sidebar */ +.toc a { + color: var(--on-dim); +} +.toc a:hover { + color: var(--surface-on); +} +.toc a.active { + color: var(--named-sand-0); +} +.toc-group { + color: var(--on-dim); +} + +/* --- nav: cream mono brand, ghost toggle (text inherits the body font) -- */ +.nav .brand a { + color: var(--named-sand-0); + font-family: var(--font-mono); + font-size: var(--font-size-3); + text-decoration: underline; +} +.nav .brand a:hover, +.nav .links a:hover { + color: var(--named-sand-0); +} +.nav .vt-toggle[aria-pressed="true"] { + color: var(--named-sand-0); + border-color: var(--named-sand-0); +} + +/* --- tone utilities: a surface and its readable foreground (badges) ----- */ +.tone-orange { + background: var(--named-orange-0); + color: var(--named-orange-0-on); +} +.tone-grape { + background: var(--named-grape-0); + color: var(--named-grape-0-on); +} +.tone-red { + background: var(--named-red-0); + color: var(--named-red-0-on); +} +.tone-green { + background: var(--named-green-0); + color: var(--named-green-0-on); +} + +/* --- landing hero (these selectors only match on the landing markup) ---- */ + +/* tagline card: just tilted, on the surface, like the original */ +.taglines { + max-width: 48rem; + margin: 0 auto; + text-align: center; + transform: rotate(-1deg); + padding: 3rem 1.5rem 0.5rem; +} + +.taglines img { + display: block; + margin: 0 auto; + max-width: 90%; +} + +.curve { + display: block; + width: 100%; +} + +.taglines .curve:last-child { + margin-top: var(--size-2); +} + +.stream { + color: var(--named-stream-0); +} + +/* badges */ +.badges { + display: flex; + flex-wrap: wrap; + justify-content: center; + align-items: center; +} + +.badge { + display: block; + width: fit-content; + margin-block: 0.5rem; + padding: 0.4rem 1.1rem; + font-weight: var(--font-weight-bold); + font-size: var(--font-size-2); + line-height: 1.5; + box-shadow: var(--sticker-shadow); +} + +.badge a { + text-decoration: none; +} + +/* the playful tilt, by position rather than a class per badge */ +.badges .badge:nth-child(1) { + transform: rotate(-5deg); +} +.badges .badge:nth-child(2) { + transform: rotate(2deg); +} +.badges .badge:nth-child(3) { + transform: rotate(-3deg); +} + +.wave { + display: block; + width: 100%; + height: 100px; +} + +/* --- terminal: a window-chrome code panel (install tabs + hello world) -- * + * Playful, like the original: a purple titlebar and a body that's just the + * page surface darkened with a black overlay (so it stays a dark terminal on + * either blue, no fixed color). On narrow screens it bleeds to both edges. */ +.terminal { + margin-block: var(--size-2); + border-radius: var(--border-radius-2); + overflow: hidden; + box-shadow: var(--shadow-2); + font-family: var(--font-mono); + font-size: var(--font-size--1); +} +.terminal-bar { + display: flex; + align-items: center; + gap: var(--size--1); + padding: 0.2rem var(--size--1); + background: var(--named-grape-0); + color: var(--named-grape-0-on); +} +.terminal-dots { + display: flex; + gap: 0.35rem; + margin-right: var(--size--1); +} +.terminal-dots span { + width: 0.7rem; + height: 0.7rem; + border-radius: 50%; +} +.terminal-dots span:nth-child(1) { + background: var(--named-red-0); +} +.terminal-dots span:nth-child(2) { + background: #ffbd2e; +} +.terminal-dots span:nth-child(3) { + background: var(--named-green-0); +} +.btn-tab { + font: inherit; + background: none; + border: none; + color: inherit; + opacity: 0.5; + cursor: pointer; + padding: 0.15rem 0.5rem; +} +.btn-tab:hover { + background: none; + opacity: 0.8; +} +.btn-tab.is-active { + opacity: 1; + font-weight: var(--font-weight-bold); +} +.terminal-body { + background: rgba(0, 0, 0, 0.25); + color: var(--surface-on); + padding: var(--size--2); + overflow-x: auto; + line-height: var(--code-line-height); +} +.terminal-body .prompt, +.terminal-body .out { + opacity: 0.6; +} + +/* "Start Server": a key in the title bar that lights up green once the server + is running (same lit-while-active idea as the splash badges and the 2048 + audio button). It stays put -- the bar's look is permanent, the key just + lights up. */ +.btn-go { + font-family: var(--font-mono); + font-size: var(--font-size--1); + color: var(--named-grape-0-on); + background: rgba(0, 0, 0, 0.22); + border: 0; + border-radius: var(--border-radius-1); + padding: 0.15em 0.7em; + cursor: pointer; + box-shadow: 0 2px 0 rgba(0, 0, 0, 0.3); + transition: transform var(--anim-duration-fast) var(--anim-ease-standard), + box-shadow var(--anim-duration-base) var(--anim-ease-standard), + background var(--anim-duration-base) var(--anim-ease-standard), + color var(--anim-duration-base) var(--anim-ease-standard); +} +.btn-go:hover { + background: rgba(0, 0, 0, 0.32); +} +.btn-go:active { + transform: translateY(2px); + box-shadow: none; +} +.btn-go.is-lit { + background: var(--named-green--1); + color: var(--named-green--1-on); + box-shadow: 0 2px 0 rgba(0, 0, 0, 0.2), 0 0 0.7rem -0.1rem var(--named-green--1); +} + +/* the client terminal fades in once its server is up. */ +.client-pane { + animation: term-fade-in var(--anim-duration-base) var(--anim-ease-standard); +} +@keyframes term-fade-in { + from { + opacity: 0; + transform: translateY(0.3rem); + } + to { + opacity: 1; + transform: translateY(0); + } +} + +/* animated rocket gif by the section heading */ +.rocket { + height: 1em; + margin-left: 0.25em; + vertical-align: -0.15em; + border-radius: 0; +} + +@media (max-width: 640px) { + .terminal { + margin-inline: calc(-1 * var(--size-2)); + border-radius: 0; + } +} + +/* theme facet nav: dim inactive, cream active (matches .toc a.active) */ +.facets a { + color: var(--on-dim); +} +.facets a:hover { + color: var(--surface-on); +} +.facets a.active { + color: var(--named-sand-0); +} +/* the tagline card is tilted for effect; clip the tilt so it never causes + horizontal overflow on narrow screens */ +.splash-hero { + overflow-x: clip; +} + +/* footer divider uses the brand line color */ +.site-footer { + border-color: var(--line); +} diff --git a/www-next-gen/assets/ellie.png b/www-next-gen/assets/ellie.png new file mode 100644 index 0000000..1ddefa7 Binary files /dev/null and b/www-next-gen/assets/ellie.png differ diff --git a/www-next-gen/assets/iosevka-x-400.woff2 b/www-next-gen/assets/iosevka-x-400.woff2 new file mode 100644 index 0000000..5e98f56 Binary files /dev/null and b/www-next-gen/assets/iosevka-x-400.woff2 differ diff --git a/www-next-gen/assets/iosevka-x-700.woff2 b/www-next-gen/assets/iosevka-x-700.woff2 new file mode 100644 index 0000000..3e28c32 Binary files /dev/null and b/www-next-gen/assets/iosevka-x-700.woff2 differ diff --git a/www-next-gen/assets/palette-test.html b/www-next-gen/assets/palette-test.html new file mode 100644 index 0000000..819ba6f --- /dev/null +++ b/www-next-gen/assets/palette-test.html @@ -0,0 +1,84 @@ +code palette index test + +

code palette — all index variations

+

The routing example on /docs/embedded-modules, each index left=light (on the ocean blue) / right=dark (on the navy). colors.code.index sets the code surface and its whole syntax palette.

+
+

index 0 — sand

+
+
light #ded5a9
+
dark #2a2608
+
+
+
+

index 1 — pale lime

+
+
light #eefedb
+
dark #030b00
+
+
+
+

index 2 — tan

+
+
light #e6d3a6
+
dark #2f2406
+
+
+
+

index 3 — sage

+
+
light #c2dcb8
+
dark #162a13
+
+
+
+

index 4 — aqua

+
+
light #acded6
+
dark #002c27
+
+
+
+

index 5 — mint  CURRENT

+
+
light #e7fff0
+
dark #000a05
+
+
+
+

index 6 — chartreuse

+
+
light #f1f0ab
+
dark #161600
+
+
+
+

index 7 — peach

+
+
light #ffcb8a
+
dark #382100
+
+
+
+

index 8 — apricot

+
+
light #ffcaa2
+
dark #3b1e04
+
+
+
+

index 9 — cream

+
+
light #fffae4
+
dark #080701
+
+
diff --git a/www-next-gen/assets/pt-idx0-dark.png b/www-next-gen/assets/pt-idx0-dark.png new file mode 100644 index 0000000..a885947 Binary files /dev/null and b/www-next-gen/assets/pt-idx0-dark.png differ diff --git a/www-next-gen/assets/pt-idx0-light.png b/www-next-gen/assets/pt-idx0-light.png new file mode 100644 index 0000000..d21152b Binary files /dev/null and b/www-next-gen/assets/pt-idx0-light.png differ diff --git a/www-next-gen/assets/pt-idx1-dark.png b/www-next-gen/assets/pt-idx1-dark.png new file mode 100644 index 0000000..f1b0770 Binary files /dev/null and b/www-next-gen/assets/pt-idx1-dark.png differ diff --git a/www-next-gen/assets/pt-idx1-light.png b/www-next-gen/assets/pt-idx1-light.png new file mode 100644 index 0000000..1095939 Binary files /dev/null and b/www-next-gen/assets/pt-idx1-light.png differ diff --git a/www-next-gen/assets/pt-idx2-dark.png b/www-next-gen/assets/pt-idx2-dark.png new file mode 100644 index 0000000..36bdf62 Binary files /dev/null and b/www-next-gen/assets/pt-idx2-dark.png differ diff --git a/www-next-gen/assets/pt-idx2-light.png b/www-next-gen/assets/pt-idx2-light.png new file mode 100644 index 0000000..c4dfaea Binary files /dev/null and b/www-next-gen/assets/pt-idx2-light.png differ diff --git a/www-next-gen/assets/pt-idx3-dark.png b/www-next-gen/assets/pt-idx3-dark.png new file mode 100644 index 0000000..9ca1164 Binary files /dev/null and b/www-next-gen/assets/pt-idx3-dark.png differ diff --git a/www-next-gen/assets/pt-idx3-light.png b/www-next-gen/assets/pt-idx3-light.png new file mode 100644 index 0000000..03630a5 Binary files /dev/null and b/www-next-gen/assets/pt-idx3-light.png differ diff --git a/www-next-gen/assets/pt-idx4-dark.png b/www-next-gen/assets/pt-idx4-dark.png new file mode 100644 index 0000000..9753eb4 Binary files /dev/null and b/www-next-gen/assets/pt-idx4-dark.png differ diff --git a/www-next-gen/assets/pt-idx4-light.png b/www-next-gen/assets/pt-idx4-light.png new file mode 100644 index 0000000..9ce166c Binary files /dev/null and b/www-next-gen/assets/pt-idx4-light.png differ diff --git a/www-next-gen/assets/pt-idx5-dark.png b/www-next-gen/assets/pt-idx5-dark.png new file mode 100644 index 0000000..c077552 Binary files /dev/null and b/www-next-gen/assets/pt-idx5-dark.png differ diff --git a/www-next-gen/assets/pt-idx5-light.png b/www-next-gen/assets/pt-idx5-light.png new file mode 100644 index 0000000..866b7ad Binary files /dev/null and b/www-next-gen/assets/pt-idx5-light.png differ diff --git a/www-next-gen/assets/pt-idx6-dark.png b/www-next-gen/assets/pt-idx6-dark.png new file mode 100644 index 0000000..684c748 Binary files /dev/null and b/www-next-gen/assets/pt-idx6-dark.png differ diff --git a/www-next-gen/assets/pt-idx6-light.png b/www-next-gen/assets/pt-idx6-light.png new file mode 100644 index 0000000..ad57a3c Binary files /dev/null and b/www-next-gen/assets/pt-idx6-light.png differ diff --git a/www-next-gen/assets/pt-idx7-dark.png b/www-next-gen/assets/pt-idx7-dark.png new file mode 100644 index 0000000..58b9d31 Binary files /dev/null and b/www-next-gen/assets/pt-idx7-dark.png differ diff --git a/www-next-gen/assets/pt-idx7-light.png b/www-next-gen/assets/pt-idx7-light.png new file mode 100644 index 0000000..dd697fd Binary files /dev/null and b/www-next-gen/assets/pt-idx7-light.png differ diff --git a/www-next-gen/assets/pt-idx8-dark.png b/www-next-gen/assets/pt-idx8-dark.png new file mode 100644 index 0000000..ce2a1ab Binary files /dev/null and b/www-next-gen/assets/pt-idx8-dark.png differ diff --git a/www-next-gen/assets/pt-idx8-light.png b/www-next-gen/assets/pt-idx8-light.png new file mode 100644 index 0000000..6c02aa7 Binary files /dev/null and b/www-next-gen/assets/pt-idx8-light.png differ diff --git a/www-next-gen/assets/pt-idx9-dark.png b/www-next-gen/assets/pt-idx9-dark.png new file mode 100644 index 0000000..bb911a5 Binary files /dev/null and b/www-next-gen/assets/pt-idx9-dark.png differ diff --git a/www-next-gen/assets/pt-idx9-light.png b/www-next-gen/assets/pt-idx9-light.png new file mode 100644 index 0000000..3f3f301 Binary files /dev/null and b/www-next-gen/assets/pt-idx9-light.png differ diff --git a/www-next-gen/assets/source-sans-3-400.woff2 b/www-next-gen/assets/source-sans-3-400.woff2 new file mode 100644 index 0000000..0c4242e Binary files /dev/null and b/www-next-gen/assets/source-sans-3-400.woff2 differ diff --git a/www-next-gen/assets/source-sans-3-700.woff2 b/www-next-gen/assets/source-sans-3-700.woff2 new file mode 100644 index 0000000..3cb944c Binary files /dev/null and b/www-next-gen/assets/source-sans-3-700.woff2 differ diff --git a/www-next-gen/assets/stellar.css b/www-next-gen/assets/stellar.css new file mode 100644 index 0000000..8d35fb4 --- /dev/null +++ b/www-next-gen/assets/stellar.css @@ -0,0 +1 @@ +:root{--primary-1: #aefffe;--primary-1: oklch(94.781% 0.0787 194.923);--primary-1-on: #003636;--primary-1-on: oklch(30.14% 0.0514 194.769);--primary-1-dim: #75c6c5;--primary-1-dim: oklch(77.468% 0.0796 194.612);--primary-2: #aafafa;--primary-2: oklch(93.373% 0.0779 195.898);--primary-2-on: #002d2e;--primary-2-on: oklch(26.951% 0.0459 197.099);--primary-2-dim: #71c0c1;--primary-2-dim: oklch(75.761% 0.078 196.682);--primary-3: #9deeed;--primary-3: oklch(89.693% 0.0791 194.842);--primary-3-on: #002c2c;--primary-3-on: oklch(26.54% 0.0453 194.769);--primary-3-dim: #62b3b2;--primary-3-dim: oklch(71.526% 0.0796 194.476);--primary-4: #89d8d8;--primary-4: oklch(83.145% 0.0777 195.76);--primary-4-on: #002c2d;--primary-4-on: oklch(26.586% 0.0452 197.139);--primary-4-dim: #4a9a9b;--primary-4-dim: oklch(63.674% 0.0782 196.462);--primary-5: #6cbbbb;--primary-5: oklch(74.164% 0.078 195.601);--primary-5-on: #002c2d;--primary-5-on: oklch(26.586% 0.0452 197.139);--primary-5-dim: #a4f4f3;--primary-5-dim: oklch(91.547% 0.0781 194.875);--primary-6: #469797;--primary-6: oklch(62.632% 0.0788 195.328);--primary-6-on: #ffffff;--primary-6-on: oklch(100% 0 89.876);--primary-6-dim: #83d3d3;--primary-6-dim: oklch(81.563% 0.0786 195.72);--primary-7: #0f6f6f;--primary-7: oklch(49.219% 0.0804 194.857);--primary-7-on: #ffffff;--primary-7-on: oklch(100% 0 89.876);--primary-7-dim: #61b0b0;--primary-7-dim: oklch(70.701% 0.078 195.533);--primary-8: #004c4d;--primary-8: oklch(37.755% 0.0643 196.296);--primary-8-on: #f1fffe;--primary-8-on: oklch(98.939% 0.0147 191.775);--primary-8-dim: #579393;--primary-8-dim: oklch(62.264% 0.0625 195.665);--primary-9: #003333;--primary-9: oklch(29.072% 0.0496 194.769);--primary-9-on: #c9f6f6;--primary-9-on: oklch(94.18% 0.0461 196.396);--primary-9-dim: #547f7e;--primary-9-dim: oklch(56.488% 0.0475 194.04);--primary-10: #002222;--primary-10: oklch(22.813% 0.0389 194.769);--primary-10-on: #cff1f0;--primary-10-on: oklch(93.355% 0.0354 194.382);--primary-10-dim: #597878;--primary-10-dim: oklch(54.885% 0.0355 196.164);--primary-11: #001818;--primary-11: oklch(18.926% 0.0323 194.769);--primary-11-on: #d4f0ef;--primary-11-on: oklch(93.456% 0.0293 194.035);--primary-11-dim: #5e7777;--primary-11-dim: oklch(54.952% 0.0291 196.335);--primary-12: #001414;--primary-12: oklch(17.316% 0.0296 194.769);--primary-12-on: #d7efee;--primary-12-on: oklch(93.43% 0.0253 193.681);--primary-12-dim: #607776;--primary-12-dim: oklch(55.06% 0.027 193.128)}:root{--secondary-1: #e9f1ff;--secondary-1: oklch(95.624% 0.0207 261.772);--secondary-1-on: #28303a;--secondary-1-on: oklch(30.588% 0.0212 254.148);--secondary-1-dim: #b0b9c6;--secondary-1-dim: oklch(78.275% 0.021 257.483);--secondary-2: #e1eeff;--secondary-2: oklch(94.449% 0.0269 255.09);--secondary-2-on: #1e2a37;--secondary-2-on: oklch(28.001% 0.0293 250.892);--secondary-2-dim: #a9b5c5;--secondary-2-dim: oklch(76.881% 0.0265 255.587);--secondary-3: #d1e1f7;--secondary-3: oklch(90.454% 0.0348 256.34);--secondary-3-on: #182838;--secondary-3-on: oklch(27.066% 0.0369 249.332);--secondary-3-dim: #97a7bb;--secondary-3-dim: oklch(72.248% 0.0343 254.055);--secondary-4: #bccde2;--secondary-4: oklch(84.205% 0.0347 253.731);--secondary-4-on: #182838;--secondary-4-on: oklch(27.066% 0.0369 249.332);--secondary-4-dim: #8090a4;--secondary-4-dim: oklch(64.747% 0.0352 254.085);--secondary-5: #a0b0c4;--secondary-5: oklch(75.131% 0.0339 254.044);--secondary-5-on: #182838;--secondary-5-on: oklch(27.066% 0.0369 249.332);--secondary-5-dim: #606f81;--secondary-5-dim: oklch(53.589% 0.0336 253.119);--secondary-6: #7c8da0;--secondary-6: oklch(63.642% 0.0347 251.276);--secondary-6-on: #ffffff;--secondary-6-on: oklch(100% 0 89.876);--secondary-6-dim: #b6c8dc;--secondary-6-dim: oklch(82.547% 0.0343 251.029);--secondary-7: #556577;--secondary-7: oklch(50.023% 0.0348 251.55);--secondary-7-on: #ffffff;--secondary-7-on: oklch(100% 0 89.876);--secondary-7-dim: #94a5b8;--secondary-7-dim: oklch(71.495% 0.0337 251.223);--secondary-8: #364557;--secondary-8: oklch(38.5% 0.0364 253.288);--secondary-8-on: #fcfcff;--secondary-8-on: oklch(99.191% 0.004 286.327);--secondary-8-dim: #7b8a9f;--secondary-8-dim: oklch(62.868% 0.0362 256.8);--secondary-9: #1e2e3e;--secondary-9: oklch(29.469% 0.0363 249.148);--secondary-9-on: #e3efff;--secondary-9-on: oklch(94.791% 0.0251 255.56);--secondary-9-dim: #68798b;--secondary-9-dim: oklch(56.875% 0.0344 249.928);--secondary-10: #0e1f2e;--secondary-10: oklch(23.257% 0.0371 246.811);--secondary-10-on: #dcecff;--secondary-10-on: oklch(93.693% 0.031 252.688);--secondary-10-dim: #647487;--secondary-10-dim: oklch(55.278% 0.0353 252.865);--secondary-11: #051625;--secondary-11: oklch(19.419% 0.0386 246.682);--secondary-11-on: #ddecff;--secondary-11-on: oklch(93.764% 0.0304 254.316);--secondary-11-dim: #647487;--secondary-11-dim: oklch(55.278% 0.0353 252.865);--secondary-12: #021221;--secondary-12: oklch(17.648% 0.0396 246.112);--secondary-12-on: #ddecff;--secondary-12-on: oklch(93.764% 0.0304 254.316);--secondary-12-dim: #647487;--secondary-12-dim: oklch(55.278% 0.0353 252.865)}:root{--tertiary-1: #ffedeb;--tertiary-1: oklch(95.984% 0.0198 25.169);--tertiary-1-on: #382e2d;--tertiary-1-on: oklch(31.183% 0.0148 24.71);--tertiary-1-dim: #c6b5b4;--tertiary-1-dim: oklch(78.71% 0.0196 21.641);--tertiary-2: #ffe7e5;--tertiary-2: oklch(94.678% 0.0265 23.358);--tertiary-2-on: #332524;--tertiary-2-on: oklch(28.07% 0.0215 23.225);--tertiary-2-dim: #c4afad;--tertiary-2-dim: oklch(77.138% 0.0245 24.209);--tertiary-3: #ffd6d2;--tertiary-3: oklch(90.973% 0.0465 24.592);--tertiary-3-on: #38201e;--tertiary-3-on: oklch(27.428% 0.0378 24.988);--tertiary-3-dim: #c19d99;--tertiary-3-dim: oklch(72.756% 0.0434 25.588);--tertiary-4: #ffbab3;--tertiary-4: oklch(85.083% 0.0811 25.438);--tertiary-4-on: #431a17;--tertiary-4-on: oklch(27.865% 0.0643 26.275);--tertiary-4-dim: #bb7f79;--tertiary-4-dim: oklch(65.587% 0.0757 25.649);--tertiary-5: #ff8f85;--tertiary-5: oklch(76.813% 0.137 26.093);--tertiary-5-on: #510a0a;--tertiary-5-on: oklch(28.322% 0.1021 26.762);--tertiary-5-dim: #af5149;--tertiary-5-dim: oklch(55.135% 0.1246 26.824);--tertiary-6: #d86a62;--tertiary-6: oklch(65.274% 0.1397 25.876);--tertiary-6-on: #ffffff;--tertiary-6-on: oklch(100% 0 89.876);--tertiary-6-dim: #7b2421;--tertiary-6-dim: oklch(39.66% 0.1212 26.259);--tertiary-7: #a6443e;--tertiary-7: oklch(51.653% 0.1309 26.168);--tertiary-7-on: #ffffff;--tertiary-7-on: oklch(100% 0 89.876);--tertiary-7-dim: #f68178;--tertiary-7-dim: oklch(73.235% 0.1446 25.759);--tertiary-8: #7c2522;--tertiary-8: oklch(39.979% 0.1211 26.178);--tertiary-8-on: #fffbff;--tertiary-8-on: oklch(99.227% 0.0067 325.627);--tertiary-8-dim: #d56860;--tertiary-8-dim: oklch(64.516% 0.1389 25.939);--tertiary-9: #5c0d0e;--tertiary-9: oklch(30.949% 0.1111 26.184);--tertiary-9-on: #ffe9e6;--tertiary-9-on: oklch(95.085% 0.0245 26.917);--tertiary-9-dim: #bf5750;--tertiary-9-dim: oklch(58.53% 0.1354 25.964);--tertiary-10: #440003;--tertiary-10: oklch(24.33% 0.099 26.458);--tertiary-10-on: #ffe4e2;--tertiary-10-on: oklch(94.028% 0.0299 22.775);--tertiary-10-dim: #b7544d;--tertiary-10-dim: oklch(56.848% 0.1299 26.088);--tertiary-11: #330002;--tertiary-11: oklch(20.211% 0.0821 26.021);--tertiary-11-on: #ffe4e2;--tertiary-11-on: oklch(94.028% 0.0299 22.775);--tertiary-11-dim: #ae5a54;--tertiary-11-dim: oklch(56.625% 0.1107 25.398);--tertiary-12: #2d0001;--tertiary-12: oklch(18.689% 0.0762 27.177);--tertiary-12-on: #ffe4e1;--tertiary-12-on: oklch(94.001% 0.0301 25.281);--tertiary-12-dim: #ab5c54;--tertiary-12-dim: oklch(56.538% 0.1046 26.911)}:root{--neutral-1: #e9f3f2;--neutral-1: oklch(95.637% 0.0107 189.821);--neutral-1-on: #283232;--neutral-1-on: oklch(30.783% 0.0136 196.466);--neutral-1-dim: #b0bbba;--neutral-1-dim: oklch(78.318% 0.0123 190.365);--neutral-2: #e4efee;--neutral-2: oklch(94.359% 0.0118 190.437);--neutral-2-on: #212b2a;--neutral-2-on: oklch(27.926% 0.014 188.906);--neutral-2-dim: #acb6b6;--neutral-2-dim: oklch(76.841% 0.0112 196.924);--neutral-3: #d8e2e1;--neutral-3: oklch(90.491% 0.0108 189.803);--neutral-3-on: #1f2828;--neutral-3-on: oklch(26.823% 0.0126 196.423);--neutral-3-dim: #9ea8a7;--neutral-3-dim: oklch(72.332% 0.0114 189.715);--neutral-4: #c3cdcc;--neutral-4: oklch(84.031% 0.011 189.777);--neutral-4-on: #1f2828;--neutral-4-on: oklch(26.823% 0.0126 196.423);--neutral-4-dim: #879090;--neutral-4-dim: oklch(64.598% 0.0105 196.897);--neutral-5: #a6b0b0;--neutral-5: oklch(74.93% 0.0112 196.917);--neutral-5-on: #1f2829;--neutral-5-on: oklch(26.864% 0.0128 204.72);--neutral-5-dim: #dde8e7;--neutral-5-dim: oklch(92.242% 0.0118 190.429);--neutral-6: #838d8c;--neutral-6: oklch(63.49% 0.0118 189.652);--neutral-6-on: #ffffff;--neutral-6-on: oklch(100% 0 89.876);--neutral-6-dim: #bec8c6;--neutral-6-dim: oklch(82.446% 0.0113 182.807);--neutral-7: #5c6665;--neutral-7: oklch(50.136% 0.0124 189.508);--neutral-7-on: #ffffff;--neutral-7-on: oklch(100% 0 89.876);--neutral-7-dim: #9ba6a4;--neutral-7-dim: oklch(71.579% 0.0128 183.933);--neutral-8: #3c4646;--neutral-8: oklch(38.518% 0.013 196.623);--neutral-8-on: #f4fffe;--neutral-8-on: oklch(99.156% 0.0116 190.454);--neutral-8-dim: #818b8b;--neutral-8-dim: oklch(62.855% 0.0117 196.859);--neutral-9: #252f2f;--neutral-9: oklch(29.583% 0.0137 196.434);--neutral-9-on: #e6f0f0;--neutral-9-on: oklch(94.763% 0.0106 196.977);--neutral-9-dim: #6f7979;--neutral-9-dim: oklch(56.772% 0.012 196.82);--neutral-10: #161f1f;--neutral-10: oklch(23.052% 0.013 196.287);--neutral-10-on: #e3ecec;--neutral-10-on: oklch(93.629% 0.0096 196.992);--neutral-10-dim: #6b7574;--neutral-10-dim: oklch(55.366% 0.0121 189.573);--neutral-11: #0d1616;--neutral-11: oklch(19.129% 0.0133 196.093);--neutral-11-on: #e3ecec;--neutral-11-on: oklch(93.629% 0.0096 196.992);--neutral-11-dim: #6b7574;--neutral-11-dim: oklch(55.366% 0.0121 189.573);--neutral-12: #091312;--neutral-12: oklch(17.653% 0.015 188.035);--neutral-12-on: #e1edea;--neutral-12-on: oklch(93.625% 0.0134 179.455);--neutral-12-dim: #6a7573;--neutral-12-dim: oklch(55.253% 0.0136 183.749)}:root{--neutral-variant-1: #daf6f5;--neutral-variant-1: oklch(95.264% 0.0292 194.049);--neutral-variant-1-on: #1a3333;--neutral-variant-1-on: oklch(30.097% 0.0311 195.627);--neutral-variant-1-dim: #a2bebd;--neutral-variant-1-dim: oklch(78.032% 0.0304 193.895);--neutral-variant-2: #d6f2f1;--neutral-variant-2: oklch(94.06% 0.0293 194.04);--neutral-variant-2-on: #132d2c;--neutral-variant-2-on: oklch(27.624% 0.0321 192.175);--neutral-variant-2-dim: #9eb9b9;--neutral-variant-2-dim: oklch(76.551% 0.0295 196.552);--neutral-variant-3: #c9e5e5;--neutral-variant-3: oklch(90.15% 0.0295 196.64);--neutral-variant-3-on: #102a2b;--neutral-variant-3-on: oklch(26.51% 0.0319 198.747);--neutral-variant-3-dim: #8fabab;--neutral-variant-3-dim: oklch(72.009% 0.0309 196.486);--neutral-variant-4: #b5d0d0;--neutral-variant-4: oklch(83.763% 0.0289 196.612);--neutral-variant-4-on: #112a2b;--neutral-variant-4-on: oklch(26.562% 0.031 198.891);--neutral-variant-4-dim: #799393;--neutral-variant-4-dim: oklch(64.319% 0.0294 196.444);--neutral-variant-5: #99b4b3;--neutral-variant-5: oklch(74.93% 0.0296 193.787);--neutral-variant-5-on: #112a2a;--neutral-variant-5-on: oklch(26.518% 0.031 195.446);--neutral-variant-5-dim: #d0ecea;--neutral-variant-5-dim: oklch(92.217% 0.0295 191.411);--neutral-variant-6: #769090;--neutral-variant-6: oklch(63.325% 0.0295 196.431);--neutral-variant-6-on: #ffffff;--neutral-variant-6-on: oklch(100% 0 89.876);--neutral-variant-6-dim: #2f4748;--neutral-variant-6-dim: oklch(37.873% 0.0296 199.26);--neutral-variant-7: #4f6868;--neutral-variant-7: oklch(49.736% 0.0296 196.24);--neutral-variant-7-on: #ffffff;--neutral-variant-7-on: oklch(100% 0 89.876);--neutral-variant-7-dim: #8ea8a8;--neutral-variant-7-dim: oklch(71.181% 0.0288 196.522);--neutral-variant-8: #2f4848;--neutral-variant-8: oklch(38.142% 0.0306 195.942);--neutral-variant-8-on: #f1fffe;--neutral-variant-8-on: oklch(98.939% 0.0147 191.775);--neutral-variant-8-dim: #738e8d;--neutral-variant-8-dim: oklch(62.557% 0.0306 193.611);--neutral-variant-9: #183131;--neutral-variant-9: oklch(29.309% 0.0311 195.59);--neutral-variant-9-on: #d8f3f3;--neutral-variant-9-on: oklch(94.459% 0.0282 196.684);--neutral-variant-9-dim: #627c7c;--neutral-variant-9-dim: oklch(56.594% 0.0301 196.333);--neutral-variant-10: #062121;--neutral-variant-10: oklch(22.744% 0.0324 195.114);--neutral-variant-10-on: #d3f0ef;--neutral-variant-10-on: oklch(93.388% 0.0303 194.105);--neutral-variant-10-dim: #5e7777;--neutral-variant-10-dim: oklch(54.952% 0.0291 196.335);--neutral-variant-11: #001818;--neutral-variant-11: oklch(18.926% 0.0323 194.769);--neutral-variant-11-on: #d4f0ef;--neutral-variant-11-on: oklch(93.456% 0.0293 194.035);--neutral-variant-11-dim: #5e7777;--neutral-variant-11-dim: oklch(54.952% 0.0291 196.335);--neutral-variant-12: #001414;--neutral-variant-12: oklch(17.316% 0.0296 194.769);--neutral-variant-12-on: #d7efee;--neutral-variant-12-on: oklch(93.43% 0.0253 193.681);--neutral-variant-12-dim: #607776;--neutral-variant-12-dim: oklch(55.06% 0.027 193.128)}:root{--error-1: #ffedeb;--error-1: oklch(95.984% 0.0198 25.169);--error-1-on: #382e2d;--error-1-on: oklch(31.183% 0.0148 24.71);--error-1-dim: #c6b5b4;--error-1-dim: oklch(78.71% 0.0196 21.641);--error-2: #ffe7e5;--error-2: oklch(94.678% 0.0265 23.358);--error-2-on: #332524;--error-2-on: oklch(28.07% 0.0215 23.225);--error-2-dim: #c4afad;--error-2-dim: oklch(77.138% 0.0245 24.209);--error-3: #ffd6d2;--error-3: oklch(90.973% 0.0465 24.592);--error-3-on: #38201e;--error-3-on: oklch(27.428% 0.0378 24.988);--error-3-dim: #c19d99;--error-3-dim: oklch(72.756% 0.0434 25.588);--error-4: #ffbab3;--error-4: oklch(85.083% 0.0811 25.438);--error-4-on: #431a17;--error-4-on: oklch(27.865% 0.0643 26.275);--error-4-dim: #bb7f79;--error-4-dim: oklch(65.587% 0.0757 25.649);--error-5: #ff8f85;--error-5: oklch(76.813% 0.137 26.093);--error-5-on: #510a0a;--error-5-on: oklch(28.322% 0.1021 26.762);--error-5-dim: #af5149;--error-5-dim: oklch(55.135% 0.1246 26.824);--error-6: #d86a62;--error-6: oklch(65.274% 0.1397 25.876);--error-6-on: #ffffff;--error-6-on: oklch(100% 0 89.876);--error-6-dim: #7b2421;--error-6-dim: oklch(39.66% 0.1212 26.259);--error-7: #a6443e;--error-7: oklch(51.653% 0.1309 26.168);--error-7-on: #ffffff;--error-7-on: oklch(100% 0 89.876);--error-7-dim: #f68178;--error-7-dim: oklch(73.235% 0.1446 25.759);--error-8: #7c2522;--error-8: oklch(39.979% 0.1211 26.178);--error-8-on: #fffbff;--error-8-on: oklch(99.227% 0.0067 325.627);--error-8-dim: #d56860;--error-8-dim: oklch(64.516% 0.1389 25.939);--error-9: #5c0d0e;--error-9: oklch(30.949% 0.1111 26.184);--error-9-on: #ffe9e6;--error-9-on: oklch(95.085% 0.0245 26.917);--error-9-dim: #bf5750;--error-9-dim: oklch(58.53% 0.1354 25.964);--error-10: #440003;--error-10: oklch(24.33% 0.099 26.458);--error-10-on: #ffe4e2;--error-10-on: oklch(94.028% 0.0299 22.775);--error-10-dim: #b7544d;--error-10-dim: oklch(56.848% 0.1299 26.088);--error-11: #330002;--error-11: oklch(20.211% 0.0821 26.021);--error-11-on: #ffe4e2;--error-11-on: oklch(94.028% 0.0299 22.775);--error-11-dim: #ae5a54;--error-11-dim: oklch(56.625% 0.1107 25.398);--error-12: #2d0001;--error-12: oklch(18.689% 0.0762 27.177);--error-12-on: #ffe4e1;--error-12-on: oklch(94.001% 0.0301 25.281);--error-12-dim: #ab5c54;--error-12-dim: oklch(56.538% 0.1046 26.911)}:root.dark{--primary-1: #001414;--primary-1: oklch(17.316% 0.0296 194.769);--primary-1-on: #d7efee;--primary-1-on: oklch(93.43% 0.0253 193.681);--primary-1-dim: #607776;--primary-1-dim: oklch(55.06% 0.027 193.128);--primary-2: #001818;--primary-2: oklch(18.926% 0.0323 194.769);--primary-2-on: #d4f0ef;--primary-2-on: oklch(93.456% 0.0293 194.035);--primary-2-dim: #5e7777;--primary-2-dim: oklch(54.952% 0.0291 196.335);--primary-3: #002222;--primary-3: oklch(22.813% 0.0389 194.769);--primary-3-on: #cff1f0;--primary-3-on: oklch(93.355% 0.0354 194.382);--primary-3-dim: #597878;--primary-3-dim: oklch(54.885% 0.0355 196.164);--primary-4: #003333;--primary-4: oklch(29.072% 0.0496 194.769);--primary-4-on: #c9f6f6;--primary-4-on: oklch(94.18% 0.0461 196.396);--primary-4-dim: #547f7e;--primary-4-dim: oklch(56.488% 0.0475 194.04);--primary-5: #004c4d;--primary-5: oklch(37.755% 0.0643 196.296);--primary-5-on: #f1fffe;--primary-5-on: oklch(98.939% 0.0147 191.775);--primary-5-dim: #579393;--primary-5-dim: oklch(62.264% 0.0625 195.665);--primary-6: #0f6f6f;--primary-6: oklch(49.219% 0.0804 194.857);--primary-6-on: #ffffff;--primary-6-on: oklch(100% 0 89.876);--primary-6-dim: #61b0b0;--primary-6-dim: oklch(70.701% 0.078 195.533);--primary-7: #469797;--primary-7: oklch(62.632% 0.0788 195.328);--primary-7-on: #ffffff;--primary-7-on: oklch(100% 0 89.876);--primary-7-dim: #83d3d3;--primary-7-dim: oklch(81.563% 0.0786 195.72);--primary-8: #6cbbbb;--primary-8: oklch(74.164% 0.078 195.601);--primary-8-on: #002c2d;--primary-8-on: oklch(26.586% 0.0452 197.139);--primary-8-dim: #a4f4f3;--primary-8-dim: oklch(91.547% 0.0781 194.875);--primary-9: #89d8d8;--primary-9: oklch(83.145% 0.0777 195.76);--primary-9-on: #002c2d;--primary-9-on: oklch(26.586% 0.0452 197.139);--primary-9-dim: #4a9a9b;--primary-9-dim: oklch(63.674% 0.0782 196.462);--primary-10: #9deeed;--primary-10: oklch(89.693% 0.0791 194.842);--primary-10-on: #002c2c;--primary-10-on: oklch(26.54% 0.0453 194.769);--primary-10-dim: #62b3b2;--primary-10-dim: oklch(71.526% 0.0796 194.476);--primary-11: #aafafa;--primary-11: oklch(93.373% 0.0779 195.898);--primary-11-on: #002d2e;--primary-11-on: oklch(26.951% 0.0459 197.099);--primary-11-dim: #71c0c1;--primary-11-dim: oklch(75.761% 0.078 196.682);--primary-12: #aefffe;--primary-12: oklch(94.781% 0.0787 194.923);--primary-12-on: #003636;--primary-12-on: oklch(30.14% 0.0514 194.769);--primary-12-dim: #75c6c5;--primary-12-dim: oklch(77.468% 0.0796 194.612)}:root.dark{--secondary-1: #021221;--secondary-1: oklch(17.648% 0.0396 246.112);--secondary-1-on: #ddecff;--secondary-1-on: oklch(93.764% 0.0304 254.316);--secondary-1-dim: #647487;--secondary-1-dim: oklch(55.278% 0.0353 252.865);--secondary-2: #051625;--secondary-2: oklch(19.419% 0.0386 246.682);--secondary-2-on: #ddecff;--secondary-2-on: oklch(93.764% 0.0304 254.316);--secondary-2-dim: #647487;--secondary-2-dim: oklch(55.278% 0.0353 252.865);--secondary-3: #0e1f2e;--secondary-3: oklch(23.257% 0.0371 246.811);--secondary-3-on: #dcecff;--secondary-3-on: oklch(93.693% 0.031 252.688);--secondary-3-dim: #647487;--secondary-3-dim: oklch(55.278% 0.0353 252.865);--secondary-4: #1e2e3e;--secondary-4: oklch(29.469% 0.0363 249.148);--secondary-4-on: #e3efff;--secondary-4-on: oklch(94.791% 0.0251 255.56);--secondary-4-dim: #68798b;--secondary-4-dim: oklch(56.875% 0.0344 249.928);--secondary-5: #364557;--secondary-5: oklch(38.5% 0.0364 253.288);--secondary-5-on: #fcfcff;--secondary-5-on: oklch(99.191% 0.004 286.327);--secondary-5-dim: #7b8a9f;--secondary-5-dim: oklch(62.868% 0.0362 256.8);--secondary-6: #556577;--secondary-6: oklch(50.023% 0.0348 251.55);--secondary-6-on: #ffffff;--secondary-6-on: oklch(100% 0 89.876);--secondary-6-dim: #94a5b8;--secondary-6-dim: oklch(71.495% 0.0337 251.223);--secondary-7: #7c8da0;--secondary-7: oklch(63.642% 0.0347 251.276);--secondary-7-on: #ffffff;--secondary-7-on: oklch(100% 0 89.876);--secondary-7-dim: #b6c8dc;--secondary-7-dim: oklch(82.547% 0.0343 251.029);--secondary-8: #a0b0c4;--secondary-8: oklch(75.131% 0.0339 254.044);--secondary-8-on: #182838;--secondary-8-on: oklch(27.066% 0.0369 249.332);--secondary-8-dim: #606f81;--secondary-8-dim: oklch(53.589% 0.0336 253.119);--secondary-9: #bccde2;--secondary-9: oklch(84.205% 0.0347 253.731);--secondary-9-on: #182838;--secondary-9-on: oklch(27.066% 0.0369 249.332);--secondary-9-dim: #8090a4;--secondary-9-dim: oklch(64.747% 0.0352 254.085);--secondary-10: #d1e1f7;--secondary-10: oklch(90.454% 0.0348 256.34);--secondary-10-on: #182838;--secondary-10-on: oklch(27.066% 0.0369 249.332);--secondary-10-dim: #97a7bb;--secondary-10-dim: oklch(72.248% 0.0343 254.055);--secondary-11: #e1eeff;--secondary-11: oklch(94.449% 0.0269 255.09);--secondary-11-on: #1e2a37;--secondary-11-on: oklch(28.001% 0.0293 250.892);--secondary-11-dim: #a9b5c5;--secondary-11-dim: oklch(76.881% 0.0265 255.587);--secondary-12: #e9f1ff;--secondary-12: oklch(95.624% 0.0207 261.772);--secondary-12-on: #28303a;--secondary-12-on: oklch(30.588% 0.0212 254.148);--secondary-12-dim: #b0b9c6;--secondary-12-dim: oklch(78.275% 0.021 257.483)}:root.dark{--tertiary-1: #2d0001;--tertiary-1: oklch(18.689% 0.0762 27.177);--tertiary-1-on: #ffe4e1;--tertiary-1-on: oklch(94.001% 0.0301 25.281);--tertiary-1-dim: #ab5c54;--tertiary-1-dim: oklch(56.538% 0.1046 26.911);--tertiary-2: #330002;--tertiary-2: oklch(20.211% 0.0821 26.021);--tertiary-2-on: #ffe4e2;--tertiary-2-on: oklch(94.028% 0.0299 22.775);--tertiary-2-dim: #ae5a54;--tertiary-2-dim: oklch(56.625% 0.1107 25.398);--tertiary-3: #440003;--tertiary-3: oklch(24.33% 0.099 26.458);--tertiary-3-on: #ffe4e2;--tertiary-3-on: oklch(94.028% 0.0299 22.775);--tertiary-3-dim: #b7544d;--tertiary-3-dim: oklch(56.848% 0.1299 26.088);--tertiary-4: #5c0d0e;--tertiary-4: oklch(30.949% 0.1111 26.184);--tertiary-4-on: #ffe9e6;--tertiary-4-on: oklch(95.085% 0.0245 26.917);--tertiary-4-dim: #bf5750;--tertiary-4-dim: oklch(58.53% 0.1354 25.964);--tertiary-5: #7c2522;--tertiary-5: oklch(39.979% 0.1211 26.178);--tertiary-5-on: #fffbff;--tertiary-5-on: oklch(99.227% 0.0067 325.627);--tertiary-5-dim: #d56860;--tertiary-5-dim: oklch(64.516% 0.1389 25.939);--tertiary-6: #a6443e;--tertiary-6: oklch(51.653% 0.1309 26.168);--tertiary-6-on: #ffffff;--tertiary-6-on: oklch(100% 0 89.876);--tertiary-6-dim: #f68178;--tertiary-6-dim: oklch(73.235% 0.1446 25.759);--tertiary-7: #d86a62;--tertiary-7: oklch(65.274% 0.1397 25.876);--tertiary-7-on: #ffffff;--tertiary-7-on: oklch(100% 0 89.876);--tertiary-7-dim: #7b2421;--tertiary-7-dim: oklch(39.66% 0.1212 26.259);--tertiary-8: #ff8f85;--tertiary-8: oklch(76.813% 0.137 26.093);--tertiary-8-on: #510a0a;--tertiary-8-on: oklch(28.322% 0.1021 26.762);--tertiary-8-dim: #af5149;--tertiary-8-dim: oklch(55.135% 0.1246 26.824);--tertiary-9: #ffbab3;--tertiary-9: oklch(85.083% 0.0811 25.438);--tertiary-9-on: #431a17;--tertiary-9-on: oklch(27.865% 0.0643 26.275);--tertiary-9-dim: #bb7f79;--tertiary-9-dim: oklch(65.587% 0.0757 25.649);--tertiary-10: #ffd6d2;--tertiary-10: oklch(90.973% 0.0465 24.592);--tertiary-10-on: #38201e;--tertiary-10-on: oklch(27.428% 0.0378 24.988);--tertiary-10-dim: #c19d99;--tertiary-10-dim: oklch(72.756% 0.0434 25.588);--tertiary-11: #ffe7e5;--tertiary-11: oklch(94.678% 0.0265 23.358);--tertiary-11-on: #332524;--tertiary-11-on: oklch(28.07% 0.0215 23.225);--tertiary-11-dim: #c4afad;--tertiary-11-dim: oklch(77.138% 0.0245 24.209);--tertiary-12: #ffedeb;--tertiary-12: oklch(95.984% 0.0198 25.169);--tertiary-12-on: #382e2d;--tertiary-12-on: oklch(31.183% 0.0148 24.71);--tertiary-12-dim: #c6b5b4;--tertiary-12-dim: oklch(78.71% 0.0196 21.641)}:root.dark{--neutral-1: #091312;--neutral-1: oklch(17.653% 0.015 188.035);--neutral-1-on: #e1edea;--neutral-1-on: oklch(93.625% 0.0134 179.455);--neutral-1-dim: #6a7573;--neutral-1-dim: oklch(55.253% 0.0136 183.749);--neutral-2: #0d1616;--neutral-2: oklch(19.129% 0.0133 196.093);--neutral-2-on: #e3ecec;--neutral-2-on: oklch(93.629% 0.0096 196.992);--neutral-2-dim: #6b7574;--neutral-2-dim: oklch(55.366% 0.0121 189.573);--neutral-3: #161f1f;--neutral-3: oklch(23.052% 0.013 196.287);--neutral-3-on: #e3ecec;--neutral-3-on: oklch(93.629% 0.0096 196.992);--neutral-3-dim: #6b7574;--neutral-3-dim: oklch(55.366% 0.0121 189.573);--neutral-4: #252f2f;--neutral-4: oklch(29.583% 0.0137 196.434);--neutral-4-on: #e6f0f0;--neutral-4-on: oklch(94.763% 0.0106 196.977);--neutral-4-dim: #6f7979;--neutral-4-dim: oklch(56.772% 0.012 196.82);--neutral-5: #3c4646;--neutral-5: oklch(38.518% 0.013 196.623);--neutral-5-on: #f4fffe;--neutral-5-on: oklch(99.156% 0.0116 190.454);--neutral-5-dim: #818b8b;--neutral-5-dim: oklch(62.855% 0.0117 196.859);--neutral-6: #5c6665;--neutral-6: oklch(50.136% 0.0124 189.508);--neutral-6-on: #ffffff;--neutral-6-on: oklch(100% 0 89.876);--neutral-6-dim: #9ba6a4;--neutral-6-dim: oklch(71.579% 0.0128 183.933);--neutral-7: #838d8c;--neutral-7: oklch(63.49% 0.0118 189.652);--neutral-7-on: #ffffff;--neutral-7-on: oklch(100% 0 89.876);--neutral-7-dim: #bec8c6;--neutral-7-dim: oklch(82.446% 0.0113 182.807);--neutral-8: #a6b0b0;--neutral-8: oklch(74.93% 0.0112 196.917);--neutral-8-on: #1f2829;--neutral-8-on: oklch(26.864% 0.0128 204.72);--neutral-8-dim: #dde8e7;--neutral-8-dim: oklch(92.242% 0.0118 190.429);--neutral-9: #c3cdcc;--neutral-9: oklch(84.031% 0.011 189.777);--neutral-9-on: #1f2828;--neutral-9-on: oklch(26.823% 0.0126 196.423);--neutral-9-dim: #879090;--neutral-9-dim: oklch(64.598% 0.0105 196.897);--neutral-10: #d8e2e1;--neutral-10: oklch(90.491% 0.0108 189.803);--neutral-10-on: #1f2828;--neutral-10-on: oklch(26.823% 0.0126 196.423);--neutral-10-dim: #9ea8a7;--neutral-10-dim: oklch(72.332% 0.0114 189.715);--neutral-11: #e4efee;--neutral-11: oklch(94.359% 0.0118 190.437);--neutral-11-on: #212b2a;--neutral-11-on: oklch(27.926% 0.014 188.906);--neutral-11-dim: #acb6b6;--neutral-11-dim: oklch(76.841% 0.0112 196.924);--neutral-12: #e9f3f2;--neutral-12: oklch(95.637% 0.0107 189.821);--neutral-12-on: #283232;--neutral-12-on: oklch(30.783% 0.0136 196.466);--neutral-12-dim: #b0bbba;--neutral-12-dim: oklch(78.318% 0.0123 190.365)}:root.dark{--neutral-variant-1: #001414;--neutral-variant-1: oklch(17.316% 0.0296 194.769);--neutral-variant-1-on: #d7efee;--neutral-variant-1-on: oklch(93.43% 0.0253 193.681);--neutral-variant-1-dim: #607776;--neutral-variant-1-dim: oklch(55.06% 0.027 193.128);--neutral-variant-2: #001818;--neutral-variant-2: oklch(18.926% 0.0323 194.769);--neutral-variant-2-on: #d4f0ef;--neutral-variant-2-on: oklch(93.456% 0.0293 194.035);--neutral-variant-2-dim: #5e7777;--neutral-variant-2-dim: oklch(54.952% 0.0291 196.335);--neutral-variant-3: #062121;--neutral-variant-3: oklch(22.744% 0.0324 195.114);--neutral-variant-3-on: #d3f0ef;--neutral-variant-3-on: oklch(93.388% 0.0303 194.105);--neutral-variant-3-dim: #5e7777;--neutral-variant-3-dim: oklch(54.952% 0.0291 196.335);--neutral-variant-4: #183131;--neutral-variant-4: oklch(29.309% 0.0311 195.59);--neutral-variant-4-on: #d8f3f3;--neutral-variant-4-on: oklch(94.459% 0.0282 196.684);--neutral-variant-4-dim: #627c7c;--neutral-variant-4-dim: oklch(56.594% 0.0301 196.333);--neutral-variant-5: #2f4848;--neutral-variant-5: oklch(38.142% 0.0306 195.942);--neutral-variant-5-on: #f1fffe;--neutral-variant-5-on: oklch(98.939% 0.0147 191.775);--neutral-variant-5-dim: #738e8d;--neutral-variant-5-dim: oklch(62.557% 0.0306 193.611);--neutral-variant-6: #4f6868;--neutral-variant-6: oklch(49.736% 0.0296 196.24);--neutral-variant-6-on: #ffffff;--neutral-variant-6-on: oklch(100% 0 89.876);--neutral-variant-6-dim: #8ea8a8;--neutral-variant-6-dim: oklch(71.181% 0.0288 196.522);--neutral-variant-7: #769090;--neutral-variant-7: oklch(63.325% 0.0295 196.431);--neutral-variant-7-on: #ffffff;--neutral-variant-7-on: oklch(100% 0 89.876);--neutral-variant-7-dim: #2f4748;--neutral-variant-7-dim: oklch(37.873% 0.0296 199.26);--neutral-variant-8: #99b4b3;--neutral-variant-8: oklch(74.93% 0.0296 193.787);--neutral-variant-8-on: #112a2a;--neutral-variant-8-on: oklch(26.518% 0.031 195.446);--neutral-variant-8-dim: #d0ecea;--neutral-variant-8-dim: oklch(92.217% 0.0295 191.411);--neutral-variant-9: #b5d0d0;--neutral-variant-9: oklch(83.763% 0.0289 196.612);--neutral-variant-9-on: #112a2b;--neutral-variant-9-on: oklch(26.562% 0.031 198.891);--neutral-variant-9-dim: #799393;--neutral-variant-9-dim: oklch(64.319% 0.0294 196.444);--neutral-variant-10: #c9e5e5;--neutral-variant-10: oklch(90.15% 0.0295 196.64);--neutral-variant-10-on: #102a2b;--neutral-variant-10-on: oklch(26.51% 0.0319 198.747);--neutral-variant-10-dim: #8fabab;--neutral-variant-10-dim: oklch(72.009% 0.0309 196.486);--neutral-variant-11: #d6f2f1;--neutral-variant-11: oklch(94.06% 0.0293 194.04);--neutral-variant-11-on: #132d2c;--neutral-variant-11-on: oklch(27.624% 0.0321 192.175);--neutral-variant-11-dim: #9eb9b9;--neutral-variant-11-dim: oklch(76.551% 0.0295 196.552);--neutral-variant-12: #daf6f5;--neutral-variant-12: oklch(95.264% 0.0292 194.049);--neutral-variant-12-on: #1a3333;--neutral-variant-12-on: oklch(30.097% 0.0311 195.627);--neutral-variant-12-dim: #a2bebd;--neutral-variant-12-dim: oklch(78.032% 0.0304 193.895)}:root.dark{--error-1: #2d0001;--error-1: oklch(18.689% 0.0762 27.177);--error-1-on: #ffe4e1;--error-1-on: oklch(94.001% 0.0301 25.281);--error-1-dim: #ab5c54;--error-1-dim: oklch(56.538% 0.1046 26.911);--error-2: #330002;--error-2: oklch(20.211% 0.0821 26.021);--error-2-on: #ffe4e2;--error-2-on: oklch(94.028% 0.0299 22.775);--error-2-dim: #ae5a54;--error-2-dim: oklch(56.625% 0.1107 25.398);--error-3: #440003;--error-3: oklch(24.33% 0.099 26.458);--error-3-on: #ffe4e2;--error-3-on: oklch(94.028% 0.0299 22.775);--error-3-dim: #b7544d;--error-3-dim: oklch(56.848% 0.1299 26.088);--error-4: #5c0d0e;--error-4: oklch(30.949% 0.1111 26.184);--error-4-on: #ffe9e6;--error-4-on: oklch(95.085% 0.0245 26.917);--error-4-dim: #bf5750;--error-4-dim: oklch(58.53% 0.1354 25.964);--error-5: #7c2522;--error-5: oklch(39.979% 0.1211 26.178);--error-5-on: #fffbff;--error-5-on: oklch(99.227% 0.0067 325.627);--error-5-dim: #d56860;--error-5-dim: oklch(64.516% 0.1389 25.939);--error-6: #a6443e;--error-6: oklch(51.653% 0.1309 26.168);--error-6-on: #ffffff;--error-6-on: oklch(100% 0 89.876);--error-6-dim: #f68178;--error-6-dim: oklch(73.235% 0.1446 25.759);--error-7: #d86a62;--error-7: oklch(65.274% 0.1397 25.876);--error-7-on: #ffffff;--error-7-on: oklch(100% 0 89.876);--error-7-dim: #7b2421;--error-7-dim: oklch(39.66% 0.1212 26.259);--error-8: #ff8f85;--error-8: oklch(76.813% 0.137 26.093);--error-8-on: #510a0a;--error-8-on: oklch(28.322% 0.1021 26.762);--error-8-dim: #af5149;--error-8-dim: oklch(55.135% 0.1246 26.824);--error-9: #ffbab3;--error-9: oklch(85.083% 0.0811 25.438);--error-9-on: #431a17;--error-9-on: oklch(27.865% 0.0643 26.275);--error-9-dim: #bb7f79;--error-9-dim: oklch(65.587% 0.0757 25.649);--error-10: #ffd6d2;--error-10: oklch(90.973% 0.0465 24.592);--error-10-on: #38201e;--error-10-on: oklch(27.428% 0.0378 24.988);--error-10-dim: #c19d99;--error-10-dim: oklch(72.756% 0.0434 25.588);--error-11: #ffe7e5;--error-11: oklch(94.678% 0.0265 23.358);--error-11-on: #332524;--error-11-on: oklch(28.07% 0.0215 23.225);--error-11-dim: #c4afad;--error-11-dim: oklch(77.138% 0.0245 24.209);--error-12: #ffedeb;--error-12: oklch(95.984% 0.0198 25.169);--error-12-on: #382e2d;--error-12-on: oklch(31.183% 0.0148 24.71);--error-12-dim: #c6b5b4;--error-12-dim: oklch(78.71% 0.0196 21.641)}:root{--named-adobe--2: #D4003B;--named-adobe--2: oklch(55.101% 0.2209 18.689);--named-adobe--2-on: #FFFFFF;--named-adobe--2-on: oklch(100% 0 89.876);--named-adobe--2-dim: #FF838B;--named-adobe--2-dim: oklch(75.124% 0.1507 17.348);--named-adobe--1: #E80028;--named-adobe--1: oklch(58.663% 0.2377 24.705);--named-adobe--1-on: #FFFFFF;--named-adobe--1-on: oklch(100% 0 89.876);--named-adobe--1-dim: #FF938C;--named-adobe--1-dim: oklch(77.591% 0.1313 24.463);--named-adobe-0: #FA0F00;--named-adobe-0: oklch(62.141% 0.2512 29.736);--named-adobe-0-on: #FFFFFF;--named-adobe-0-on: oklch(100% 0 89.876);--named-adobe-0-dim: #6A0300;--named-adobe-0-dim: oklch(33.112% 0.1333 29.869);--named-adobe-1: #FA4400;--named-adobe-1: oklch(65.085% 0.2256 35.505);--named-adobe-1-on: #FFFFFF;--named-adobe-1-on: oklch(100% 0 89.876);--named-adobe-1-dim: #791C00;--named-adobe-1-dim: oklch(38.07% 0.1318 35.533);--named-adobe-2: #FD5D00;--named-adobe-2: oklch(68.172% 0.2094 41.084);--named-adobe-2-on: #FFFFFF;--named-adobe-2-on: oklch(100% 0 89.876);--named-adobe-2-dim: #FFBDA5;--named-adobe-2-dim: oklch(85.298% 0.0835 40.854);--named-android--2: #4C8E2C;--named-android--2: oklch(58.186% 0.1478 137.167);--named-android--2-on: #FFFFFF;--named-android--2-on: oklch(100% 0 89.876);--named-android--2-dim: #86CD62;--named-android--2-dim: oklch(77.75% 0.158 136.157);--named-android--1: #439B3F;--named-android--1: oklch(61.476% 0.1545 142.688);--named-android--1-on: #FFFFFF;--named-android--1-on: oklch(100% 0 89.876);--named-android--1-dim: #004C09;--named-android--1-dim: oklch(36.154% 0.1176 143.895);--named-android-0: #34A853;--named-android-0: oklch(64.755% 0.1603 148.495);--named-android-0-on: #FFFFFF;--named-android-0-on: oklch(100% 0 89.876);--named-android-0-dim: #005A23;--named-android-0-dim: oklch(40.823% 0.1153 148.953);--named-android-1: #16B567;--named-android-1: oklch(68.042% 0.1663 154.192);--named-android-1-on: #FFFFFF;--named-android-1-on: oklch(100% 0 89.876);--named-android-1-dim: #66F19B;--named-android-1-dim: oklch(86.029% 0.1713 153.499);--named-android-2: #00C17F;--named-android-2: oklch(71.546% 0.162 159.929);--named-android-2-on: #002D1A;--named-android-2-on: oklch(26.213% 0.0596 159.706);--named-android-2-dim: #00774C;--named-android-2-dim: oklch(50.211% 0.1143 159.609);--named-aws--2: #EC7F25;--named-aws--2: oklch(70.557% 0.1636 54.012);--named-aws--2-on: #FFFFFF;--named-aws--2-on: oklch(100% 0 89.876);--named-aws--2-dim: #8B4400;--named-aws--2-dim: oklch(46.814% 0.1175 53.907);--named-aws--1: #F68C18;--named-aws--1: oklch(73.932% 0.1687 59.434);--named-aws--1-on: #3E1E00;--named-aws--1-on: oklch(27.412% 0.0649 59.104);--named-aws--1-dim: #985300;--named-aws--1-dim: oklch(51.363% 0.1207 59.777);--named-aws-0: #FF9900;--named-aws-0: oklch(77.195% 0.1738 64.552);--named-aws-0-on: #3C2000;--named-aws-0-on: oklch(27.51% 0.0619 64.672);--named-aws-0-dim: #FFE4CD;--named-aws-0-dim: oklch(93.495% 0.0426 62.712);--named-aws-1: #FFAA2D;--named-aws-1: oklch(80.285% 0.1611 70.542);--named-aws-1-on: #3A2100;--named-aws-1-on: oklch(27.415% 0.0598 68.926);--named-aws-1-dim: #FFF0E4;--named-aws-1-dim: oklch(96.374% 0.0228 61.238);--named-aws-2: #FFBB49;--named-aws-2: oklch(83.573% 0.1481 76.736);--named-aws-2-on: #372200;--named-aws-2-on: oklch(27.188% 0.0574 74.909);--named-aws-2-dim: #B97F04;--named-aws-2-dim: oklch(63.932% 0.1331 76.475);--named-bluesky--2: #006EB9;--named-bluesky--2: oklch(52.679% 0.1438 248.586);--named-bluesky--2-on: #FFFFFF;--named-bluesky--2-on: oklch(100% 0 89.876);--named-bluesky--2-dim: #62AEFD;--named-bluesky--2-dim: oklch(73.551% 0.138 251.03);--named-bluesky--1: #0075D9;--named-bluesky--1: oklch(56.44% 0.1761 253.385);--named-bluesky--1-on: #FFFFFF;--named-bluesky--1-on: oklch(100% 0 89.876);--named-bluesky--1-dim: #82B4FF;--named-bluesky--1-dim: oklch(76.479% 0.1209 258.281);--named-bluesky-0: #0A7AFF;--named-bluesky-0: oklch(60.351% 0.217 257.8);--named-bluesky-0-on: #FFFFFF;--named-bluesky-0-on: oklch(100% 0 89.876);--named-bluesky-0-dim: #9BBBFF;--named-bluesky-0-dim: oklch(79.404% 0.1034 264.736);--named-bluesky-1: #5382FF;--named-bluesky-1: oklch(63.86% 0.1927 265.693);--named-bluesky-1-on: #FFFFFF;--named-bluesky-1-on: oklch(100% 0 89.876);--named-bluesky-1-dim: #00389B;--named-bluesky-1-dim: oklch(38.266% 0.1696 261.394);--named-bluesky-2: #758BFF;--named-bluesky-2: oklch(67.453% 0.172 272.891);--named-bluesky-2-on: #FFFFFF;--named-bluesky-2-on: oklch(100% 0 89.876);--named-bluesky-2-dim: #C3CBFF;--named-bluesky-2-dim: oklch(85.35% 0.073 277.904);--named-c--2: #94A4B0;--named-c--2: oklch(71.003% 0.0252 240.243);--named-c--2-on: #192832;--named-c--2-on: oklch(26.809% 0.0276 238.332);--named-c--2-dim: #CCDDE9;--named-c--2-dim: oklch(88.842% 0.0247 238.435);--named-c--1: #9DAEBE;--named-c--1: oklch(74.281% 0.0299 246.59);--named-c--1-on: #182835;--named-c--1-on: oklch(26.898% 0.0325 243.919);--named-c--1-dim: #D4E6F7;--named-c--1-dim: oklch(91.671% 0.0302 246.602);--named-c-0: #A8B9CC;--named-c-0: oklch(77.88% 0.033 251.188);--named-c-0-on: #182837;--named-c-0-on: oklch(27.009% 0.0354 247.694);--named-c-0-dim: #69798B;--named-c-0-dim: oklch(56.95% 0.0338 251.477);--named-c-1: #B3C4DB;--named-c-1: oklch(81.473% 0.0375 255.973);--named-c-1-on: #172839;--named-c-1-on: oklch(27.057% 0.0392 249.454);--named-c-1-dim: #76869B;--named-c-1-dim: oklch(61.458% 0.037 255.287);--named-c-2: #BFCEE9;--named-c-2: oklch(84.862% 0.0411 262.273);--named-c-2-on: #19273C;--named-c-2-on: oklch(27.099% 0.0438 258.279);--named-c-2-dim: #8392AB;--named-c-2-dim: oklch(65.67% 0.0412 260.641);--named-clojure--2: #739500;--named-clojure--2: oklch(62.121% 0.1546 124.22);--named-clojure--2-on: #FFFFFF;--named-clojure--2-on: oklch(100% 0 89.876);--named-clojure--2-dim: #354700;--named-clojure--2-dim: oklch(36.883% 0.092 124.403);--named-clojure--1: #6CA316;--named-clojure--1: oklch(65.247% 0.1691 130.145);--named-clojure--1-on: #FFFFFF;--named-clojure--1-on: oklch(100% 0 89.876);--named-clojure--1-dim: #A4DF52;--named-clojure--1-dim: oklch(83.568% 0.1805 129.041);--named-clojure-0: #63B132;--named-clojure-0: oklch(68.488% 0.1771 135.913);--named-clojure-0-on: #FFFFFF;--named-clojure-0-on: oklch(100% 0 89.876);--named-clojure-0-dim: #2C6700;--named-clojure-0-dim: oklch(45.592% 0.1378 136.843);--named-clojure-1: #56BF49;--named-clojure-1: oklch(71.735% 0.1847 141.514);--named-clojure-1-on: #002E01;--named-clojure-1-on: oklch(26.109% 0.0877 142.902);--named-clojure-1-dim: #8EFA7B;--named-clojure-1-dim: oklch(89.215% 0.1931 140.74);--named-clojure-2: #41CD60;--named-clojure-2: oklch(74.979% 0.1918 147.262);--named-clojure-2-on: #002E0C;--named-clojure-2-on: oklch(26.258% 0.0766 147.708);--named-clojure-2-dim: #ABFFAF;--named-clojure-2-dim: oklch(92.647% 0.135 145.499);--named-cplusplus--2: #00496E;--named-cplusplus--2: oklch(38.644% 0.0883 239.883);--named-cplusplus--2-on: #FDFCFF;--named-cplusplus--2-on: oklch(99.267% 0.0041 301.427);--named-cplusplus--2-dim: #5A8FB7;--named-cplusplus--2-dim: oklch(62.908% 0.0829 242.304);--named-cplusplus--1: #005183;--named-cplusplus--1: oklch(42.069% 0.1066 245.316);--named-cplusplus--1-on: #FFFFFF;--named-cplusplus--1-on: oklch(100% 0 89.876);--named-cplusplus--1-dim: #5D95CC;--named-cplusplus--1-dim: oklch(65.402% 0.1017 249.213);--named-cplusplus-0: #00599C;--named-cplusplus-0: oklch(45.735% 0.1301 250.183);--named-cplusplus-0-on: #FFFFFF;--named-cplusplus-0-on: oklch(100% 0 89.876);--named-cplusplus-0-dim: #5F9CE3;--named-cplusplus-0-dim: oklch(68.187% 0.1234 253.28);--named-cplusplus-1: #2761AC;--named-cplusplus-1: oklch(49.488% 0.1341 256.318);--named-cplusplus-1-on: #FFFFFF;--named-cplusplus-1-on: oklch(100% 0 89.876);--named-cplusplus-1-dim: #70A2F1;--named-cplusplus-1-dim: oklch(71.04% 0.1282 259.236);--named-cplusplus-2: #3F68BB;--named-cplusplus-2: oklch(53.016% 0.1384 262.793);--named-cplusplus-2-on: #FFFFFF;--named-cplusplus-2-on: oklch(100% 0 89.876);--named-cplusplus-2-dim: #81A8FF;--named-cplusplus-2-dim: oklch(73.942% 0.1337 264.972);--named-csharp--2: #0C1FC1;--named-csharp--2: oklch(38.978% 0.2381 265.478);--named-csharp--2-on: #FBF7FF;--named-csharp--2-on: oklch(98.147% 0.0113 308.332);--named-csharp--2-dim: #6877FF;--named-csharp--2-dim: oklch(62.9% 0.2002 274.379);--named-csharp--1: #3625CB;--named-csharp--1: oklch(42.58% 0.2354 274.359);--named-csharp--1-on: #FFFFFF;--named-csharp--1-on: oklch(100% 0 89.876);--named-csharp--1-dim: #807CFF;--named-csharp--1-dim: oklch(65.42% 0.189 281.188);--named-csharp-0: #512BD4;--named-csharp-0: oklch(46.181% 0.2353 282.392);--named-csharp-0-on: #FFFFFF;--named-csharp-0-on: oklch(100% 0 89.876);--named-csharp-0-dim: #9681FF;--named-csharp-0-dim: oklch(68.008% 0.18 288.315);--named-csharp-1: #6931DC;--named-csharp-1: oklch(49.778% 0.2369 289.723);--named-csharp-1-on: #FFFFFF;--named-csharp-1-on: oklch(100% 0 89.876);--named-csharp-1-dim: #A987FF;--named-csharp-1-dim: oklch(70.659% 0.1717 294.665);--named-csharp-2: #8037E2;--named-csharp-2: oklch(53.308% 0.2384 296.752);--named-csharp-2-on: #FFFFFF;--named-csharp-2-on: oklch(100% 0 89.876);--named-csharp-2-dim: #BA8EFF;--named-csharp-2-dim: oklch(73.38% 0.1633 300.391);--named-css--2: #00618A;--named-css--2: oklch(46.596% 0.1004 236.132);--named-css--2-on: #FFFFFF;--named-css--2-on: oklch(100% 0 89.876);--named-css--2-dim: #5EA3CF;--named-css--2-dim: oklch(68.749% 0.0955 238.278);--named-css--1: #006AA0;--named-css--1: oklch(50.154% 0.1176 241.362);--named-css--1-on: #FFFFFF;--named-css--1-on: oklch(100% 0 89.876);--named-css--1-dim: #60ABE4;--named-css--1-dim: oklch(71.594% 0.1122 243.206);--named-css-0: #1572B6;--named-css-0: oklch(53.669% 0.1332 247.112);--named-css-0-on: #FFFFFF;--named-css-0-on: oklch(100% 0 89.876);--named-css-0-dim: #68B1FA;--named-css-0-dim: oklch(74.31% 0.1292 249.947);--named-css-1: #347AC7;--named-css-1: oklch(57.321% 0.1381 253.192);--named-css-1-on: #FFFFFF;--named-css-1-on: oklch(100% 0 89.876);--named-css-1-dim: #002B52;--named-css-1-dim: oklch(28.644% 0.0839 251.238);--named-css-2: #4C82D7;--named-css-2: oklch(60.994% 0.1419 259.135);--named-css-2-on: #FFFFFF;--named-css-2-on: oklch(100% 0 89.876);--named-css-2-dim: #003772;--named-css-2-dim: oklch(34.312% 0.1145 255.373);--named-dart--2: #006491;--named-dart--2: oklch(47.766% 0.1058 237.909);--named-dart--2-on: #FFFFFF;--named-dart--2-on: oklch(100% 0 89.876);--named-dart--2-dim: #5FA5D6;--named-dart--2-dim: oklch(69.561% 0.1012 240.834);--named-dart--1: #006DA8;--named-dart--1: oklch(51.372% 0.1242 243.003);--named-dart--1-on: #FFFFFF;--named-dart--1-on: oklch(100% 0 89.876);--named-dart--1-dim: #62ADEC;--named-dart--1-dim: oklch(72.5% 0.1188 245.78);--named-dart-0: #0175C2;--named-dart-0: oklch(54.911% 0.1475 247.993);--named-dart-0-on: #FFFFFF;--named-dart-0-on: oklch(100% 0 89.876);--named-dart-0-dim: #6BB4FF;--named-dart-0-dim: oklch(75.335% 0.1313 250.544);--named-dart-1: #317DD3;--named-dart-1: oklch(58.638% 0.1513 254.048);--named-dart-1-on: #FFFFFF;--named-dart-1-on: oklch(100% 0 89.876);--named-dart-1-dim: #00305C;--named-dart-1-dim: oklch(30.761% 0.092 251.977);--named-dart-2: #4C84E4;--named-dart-2: oklch(62.208% 0.157 260.453);--named-dart-2-on: #FFFFFF;--named-dart-2-on: oklch(100% 0 89.876);--named-dart-2-dim: #A4C1FF;--named-dart-2-dim: oklch(81.203% 0.0937 264.98);--named-docker--2: #005AA7;--named-docker--2: oklch(46.752% 0.1433 252.807);--named-docker--2-on: #FFFFFF;--named-docker--2-on: oklch(100% 0 89.876);--named-docker--2-dim: #609DEE;--named-docker--2-dim: oklch(69.022% 0.1359 256.025);--named-docker--1: #0060C9;--named-docker--1: oklch(50.665% 0.1804 257.079);--named-docker--1-on: #FFFFFF;--named-docker--1-on: oklch(100% 0 89.876);--named-docker--1-dim: #71A2FF;--named-docker--1-dim: oklch(71.824% 0.1462 262.262);--named-docker-0: #1D63ED;--named-docker-0: oklch(54.597% 0.2192 262.025);--named-docker-0-on: #FFFFFF;--named-docker-0-on: oklch(100% 0 89.876);--named-docker-0-dim: #8BA9FF;--named-docker-0-dim: oklch(74.747% 0.1291 268.552);--named-docker-1: #4869FC;--named-docker-1: oklch(58.393% 0.2213 268.796);--named-docker-1-on: #FFFFFF;--named-docker-1-on: oklch(100% 0 89.876);--named-docker-1-dim: #001C7A;--named-docker-1-dim: oklch(29.453% 0.1598 263.624);--named-docker-2: #6A71FF;--named-docker-2: oklch(61.954% 0.2071 276.384);--named-docker-2-on: #FFFFFF;--named-docker-2-on: oklch(100% 0 89.876);--named-docker-2-dim: #1211B3;--named-docker-2-dim: oklch(36.08% 0.2303 266.834);--named-elixir--2: #2F1A4F;--named-elixir--2: oklch(27.729% 0.0934 298.333);--named-elixir--2-on: #F2E5FF;--named-elixir--2-on: oklch(93.991% 0.0373 308.021);--named-elixir--2-dim: #7F68A2;--named-elixir--2-dim: oklch(56.265% 0.0911 301.784);--named-elixir--1: #3D2057;--named-elixir--1: oklch(31.155% 0.0976 305.817);--named-elixir--1-on: #F9E9FF;--named-elixir--1-on: oklch(95.313% 0.0337 316.89);--named-elixir--1-dim: #8B6BA7;--named-elixir--1-dim: oklch(58.26% 0.0962 308.103);--named-elixir-0: #4B275F;--named-elixir-0: oklch(34.718% 0.1008 311.956);--named-elixir-0-on: #FEEFFF;--named-elixir-0-on: oklch(96.852% 0.0264 323.905);--named-elixir-0-dim: #986FAC;--named-elixir-0-dim: oklch(60.562% 0.1009 314.216);--named-elixir-1: #5A2E66;--named-elixir-1: oklch(38.308% 0.1041 318.689);--named-elixir-1-on: #FFF8FA;--named-elixir-1-on: oklch(98.508% 0.0079 357.793);--named-elixir-1-dim: #A674B1;--named-elixir-1-dim: oklch(63.158% 0.1052 320.215);--named-elixir-2: #69356D;--named-elixir-2: oklch(41.859% 0.1082 324.594);--named-elixir-2-on: #FFFFFF;--named-elixir-2-on: oklch(100% 0 89.876);--named-elixir-2-dim: #B478B6;--named-elixir-2-dim: oklch(65.588% 0.1122 325.657);--named-firefox--2: #E45B4C;--named-firefox--2: oklch(64.575% 0.1736 28.974);--named-firefox--2-on: #FFFFFF;--named-firefox--2-on: oklch(100% 0 89.876);--named-firefox--2-dim: #7F110C;--named-firefox--2-dim: oklch(38.417% 0.1443 28.882);--named-firefox--1: #F26543;--named-firefox--1: oklch(67.865% 0.181 34.831);--named-firefox--1-on: #FFFFFF;--named-firefox--1-on: oklch(100% 0 89.876);--named-firefox--1-dim: #912003;--named-firefox--1-dim: oklch(43.079% 0.1526 33.922);--named-firefox-0: #FF7139;--named-firefox-0: oklch(71.285% 0.1866 40.529);--named-firefox-0-on: #FFFFFF;--named-firefox-0-on: oklch(100% 0 89.876);--named-firefox-0-dim: #FFCAB7;--named-firefox-0-dim: oklch(88.079% 0.066 40.636);--named-firefox-1: #FF8545;--named-firefox-1: oklch(74.294% 0.1672 46.175);--named-firefox-1-on: #471800;--named-firefox-1-on: oklch(27.907% 0.08 44.861);--named-firefox-1-dim: #A94603;--named-firefox-1-dim: oklch(51.853% 0.1455 45.548);--named-firefox-2: #FF9854;--named-firefox-2: oklch(77.492% 0.1488 52.179);--named-firefox-2-on: #431B00;--named-firefox-2-on: oklch(27.693% 0.0726 50.618);--named-firefox-2-dim: #B05A1A;--named-firefox-2-dim: oklch(56.13% 0.133 51.66);--named-go--2: #0098AE;--named-go--2: oklch(62.414% 0.109 212.565);--named-go--2-on: #FFFFFF;--named-go--2-on: oklch(100% 0 89.876);--named-go--2-dim: #65D4EC;--named-go--2-dim: oklch(81.4% 0.1063 213.934);--named-go--1: #00A3C2;--named-go--1: oklch(66.026% 0.1185 217.764);--named-go--1-on: #FFFFFF;--named-go--1-on: oklch(100% 0 89.876);--named-go--1-dim: #67DEFF;--named-go--1-dim: oklch(84.463% 0.1157 218.832);--named-go-0: #00ADD8;--named-go-0: oklch(69.451% 0.1303 223.853);--named-go-0-on: #FFFFFF;--named-go-0-on: oklch(100% 0 89.876);--named-go-0-dim: #00647F;--named-go-0-dim: oklch(46.787% 0.0883 224.566);--named-go-1: #2AB7EC;--named-go-1: oklch(73.143% 0.1354 229.525);--named-go-1-on: #002A3A;--named-go-1-on: oklch(26.663% 0.0527 229.3);--named-go-1-dim: #C2E8FF;--named-go-1-dim: oklch(91.123% 0.0507 234.681);--named-go-2: #46C0FF;--named-go-2: oklch(76.69% 0.1391 235.555);--named-go-2-on: #002A3D;--named-go-2-on: oklch(26.851% 0.0561 233.849);--named-go-2-dim: #DBEFFF;--named-go-2-dim: oklch(94.221% 0.0303 241.996);--named-html--2: #C7383B;--named-html--2: oklch(55.644% 0.1798 24.173);--named-html--2-on: #FFFFFF;--named-html--2-on: oklch(100% 0 89.876);--named-html--2-dim: #FF8985;--named-html--2-dim: oklch(75.888% 0.1439 23.06);--named-html--1: #D54332;--named-html--1: oklch(59.023% 0.1854 30.105);--named-html--1-on: #FFFFFF;--named-html--1-on: oklch(100% 0 89.876);--named-html--1-dim: #FF9889;--named-html--1-dim: oklch(78.332% 0.1264 29.077);--named-html-0: #E34F26;--named-html-0: oklch(62.544% 0.1913 35.712);--named-html-0-on: #FFFFFF;--named-html-0-on: oklch(100% 0 89.876);--named-html-0-dim: #FFA790;--named-html-0-dim: oklch(80.979% 0.1098 35.345);--named-html-1: #F05B12;--named-html-1: oklch(65.935% 0.1971 40.857);--named-html-1-on: #FFFFFF;--named-html-1-on: oklch(100% 0 89.876);--named-html-1-dim: #7B2800;--named-html-1-dim: oklch(39.857% 0.1236 40.604);--named-html-2: #F86B00;--named-html-2: oklch(69.179% 0.1939 46.19);--named-html-2-on: #FFFFFF;--named-html-2-on: oklch(100% 0 89.876);--named-html-2-dim: #883800;--named-html-2-dim: oklch(44.393% 0.1235 46.646);--named-java--2: #005F72;--named-java--2: oklch(44.799% 0.0803 217.665);--named-java--2-on: #FFFFFF;--named-java--2-on: oklch(100% 0 89.876);--named-java--2-dim: #5BA2B6;--named-java--2-dim: oklch(67.337% 0.0771 218.132);--named-java--1: #006983;--named-java--1: oklch(48.312% 0.0897 222.637);--named-java--1-on: #FFFFFF;--named-java--1-on: oklch(100% 0 89.876);--named-java--1-dim: #5EAAC7;--named-java--1-dim: oklch(70.016% 0.0864 225.018);--named-java-0: #007396;--named-java-0: oklch(51.874% 0.1011 227.97);--named-java-0-on: #FFFFFF;--named-java-0-on: oklch(100% 0 89.876);--named-java-0-dim: #60B3D8;--named-java-0-dim: oklch(72.872% 0.097 229.141);--named-java-1: #1E7CA7;--named-java-1: oklch(55.408% 0.106 234.011);--named-java-1-on: #FFFFFF;--named-java-1-on: oklch(100% 0 89.876);--named-java-1-dim: #6BBAE9;--named-java-1-dim: oklch(75.643% 0.1033 236.442);--named-java-2: #3385B8;--named-java-2: oklch(59.004% 0.1105 239.972);--named-java-2-on: #FFFFFF;--named-java-2-on: oklch(100% 0 89.876);--named-java-2-dim: #78C2F8;--named-java-2-dim: oklch(78.644% 0.107 241.576);--named-javascript--2: #F4C100;--named-javascript--2: oklch(83.272% 0.1702 89.358);--named-javascript--2-on: #312500;--named-javascript--2-on: oklch(27.082% 0.0553 90.277);--named-javascript--2-dim: #AA8600;--named-javascript--2-dim: oklch(63.621% 0.13 89.617);--named-javascript--1: #F6D003;--named-javascript--1: oklch(86.431% 0.1773 95.59);--named-javascript--1-on: #2F2600;--named-javascript--1-on: oklch(27.083% 0.0557 95.47);--named-javascript--1-dim: #B19500;--named-javascript--1-dim: oklch(67.515% 0.1388 95.575);--named-javascript-0: #F7DF1E;--named-javascript-0: oklch(89.591% 0.1823 101.215);--named-javascript-0-on: #2C2700;--named-javascript-0-on: oklch(26.981% 0.0565 102.089);--named-javascript-0-dim: #B7A400;--named-javascript-0-dim: oklch(71.308% 0.1488 101.049);--named-javascript-1: #F6EF33;--named-javascript-1: oklch(92.86% 0.1881 107.266);--named-javascript-1-on: #292800;--named-javascript-1-on: oklch(26.91% 0.0581 108.3);--named-javascript-1-dim: #BAB500;--named-javascript-1-dim: oklch(75.269% 0.162 107.728);--named-javascript-2: #F3FE47;--named-javascript-2: oklch(95.852% 0.1932 112.979);--named-javascript-2-on: #343800;--named-javascript-2-on: oklch(32.569% 0.0733 114.14);--named-javascript-2-dim: #BBC500;--named-javascript-2-dim: oklch(78.882% 0.1765 113.415);--named-kotlin--2: #4848EB;--named-kotlin--2: oklch(51.461% 0.2368 274.235);--named-kotlin--2-on: #FFFFFF;--named-kotlin--2-on: oklch(100% 0 89.876);--named-kotlin--2-dim: #9597FF;--named-kotlin--2-dim: oklch(71.909% 0.1495 281.114);--named-kotlin--1: #654DF6;--named-kotlin--1: oklch(55.169% 0.2388 281.641);--named-kotlin--1-on: #FFFFFF;--named-kotlin--1-on: oklch(100% 0 89.876);--named-kotlin--1-dim: #A89EFF;--named-kotlin--1-dim: oklch(74.665% 0.1378 287.289);--named-kotlin-0: #7F52FF;--named-kotlin-0: oklch(58.775% 0.2413 288.635);--named-kotlin-0-on: #FFFFFF;--named-kotlin-0-on: oklch(100% 0 89.876);--named-kotlin-0-dim: #BAA5FF;--named-kotlin-0-dim: oklch(77.417% 0.1277 293.928);--named-kotlin-1: #975BFF;--named-kotlin-1: oklch(62.234% 0.2312 295.747);--named-kotlin-1-on: #FFFFFF;--named-kotlin-1-on: oklch(100% 0 89.876);--named-kotlin-1-dim: #440095;--named-kotlin-1-dim: oklch(35.048% 0.1962 291.748);--named-kotlin-2: #AD63FF;--named-kotlin-2: oklch(65.552% 0.2239 302.464);--named-kotlin-2-on: #FFFFFF;--named-kotlin-2-on: oklch(100% 0 89.876);--named-kotlin-2-dim: #5E00A8;--named-kotlin-2-dim: oklch(40.294% 0.2155 299.611);--named-microsoft--2: #0091BD;--named-microsoft--2: oklch(61.36% 0.1203 228.479);--named-microsoft--2-on: #FFFFFF;--named-microsoft--2-on: oklch(100% 0 89.876);--named-microsoft--2-dim: #004259;--named-microsoft--2-dim: oklch(35.472% 0.0703 229.466);--named-microsoft--1: #009BD5;--named-microsoft--1: oklch(64.99% 0.1363 234.164);--named-microsoft--1-on: #FFFFFF;--named-microsoft--1-on: oklch(100% 0 89.876);--named-microsoft--1-dim: #00506F;--named-microsoft--1-dim: oklch(40.579% 0.0839 233.095);--named-microsoft-0: #00A4EF;--named-microsoft-0: oklch(68.472% 0.1559 239.642);--named-microsoft-0-on: #FFFFFF;--named-microsoft-0-on: oklch(100% 0 89.876);--named-microsoft-0-dim: #005C88;--named-microsoft-0-dim: oklch(45.193% 0.1022 239.244);--named-microsoft-1: #41ACFF;--named-microsoft-1: oklch(72.077% 0.1547 245.974);--named-microsoft-1-on: #002844;--named-microsoft-1-on: oklch(26.746% 0.0672 244.945);--named-microsoft-1-dim: #C6E1FF;--named-microsoft-1-dim: oklch(89.957% 0.0504 251.136);--named-microsoft-2: #74B4FF;--named-microsoft-2: oklch(75.754% 0.1272 253.285);--named-microsoft-2-on: #002749;--named-microsoft-2-on: oklch(26.845% 0.0757 249.879);--named-microsoft-2-dim: #DBE9FF;--named-microsoft-2-dim: oklch(93.025% 0.0335 259.419);--named-odin--2: #0072AB;--named-odin--2: oklch(52.775% 0.1232 241.09);--named-odin--2-on: #FFFFFF;--named-odin--2-on: oklch(100% 0 89.876);--named-odin--2-dim: #62B1EE;--named-odin--2-dim: oklch(73.479% 0.1183 243.719);--named-odin--1: #187AC1;--named-odin--1: oklch(56.272% 0.1382 246.78);--named-odin--1-on: #FFFFFF;--named-odin--1-on: oklch(100% 0 89.876);--named-odin--1-dim: #70B8FF;--named-odin--1-dim: oklch(76.422% 0.1257 249.464);--named-odin-0: #3882D2;--named-odin-0: oklch(59.915% 0.1426 252.827);--named-odin-0-on: #FFFFFF;--named-odin-0-on: oklch(100% 0 89.876);--named-odin-0-dim: #003563;--named-odin-0-dim: oklch(32.609% 0.0957 251.31);--named-odin-1: #508AE3;--named-odin-1: oklch(63.603% 0.1476 258.718);--named-odin-1-on: #FFFFFF;--named-odin-1-on: oklch(100% 0 89.876);--named-odin-1-dim: #A7C6FF;--named-odin-1-dim: oklch(82.415% 0.0875 262.383);--named-odin-2: #6791F3;--named-odin-2: oklch(67.127% 0.1528 265.068);--named-odin-2-on: #FFFFFF;--named-odin-2-on: oklch(100% 0 89.876);--named-odin-2-dim: #BCCDFF;--named-odin-2-dim: oklch(85.245% 0.0724 270.162);--named-php--2: #5A6A99;--named-php--2: oklch(53.099% 0.0768 269.292);--named-php--2-on: #FFFFFF;--named-php--2-on: oklch(100% 0 89.876);--named-php--2-dim: #98A9DB;--named-php--2-dim: oklch(73.971% 0.0751 269.936);--named-php--1: #6873A7;--named-php--1: oklch(56.785% 0.0816 274.371);--named-php--1-on: #FFFFFF;--named-php--1-on: oklch(100% 0 89.876);--named-php--1-dim: #A6B0E9;--named-php--1-dim: oklch(76.986% 0.0824 276.574);--named-php-0: #777BB4;--named-php-0: oklch(60.256% 0.0866 280.369);--named-php-0-on: #FFFFFF;--named-php-0-on: oklch(100% 0 89.876);--named-php-0-dim: #B3B7F4;--named-php-0-dim: oklch(79.795% 0.0863 281.329);--named-php-1: #8783C1;--named-php-1: oklch(63.789% 0.0921 286.428);--named-php-1-on: #FFFFFF;--named-php-1-on: oklch(100% 0 89.876);--named-php-1-dim: #C2BEFF;--named-php-1-dim: oklch(82.748% 0.0906 287.057);--named-php-2: #988BCE;--named-php-2: oklch(67.391% 0.0984 292.457);--named-php-2-on: #FFFFFF;--named-php-2-on: oklch(100% 0 89.876);--named-php-2-dim: #514582;--named-php-2-dim: oklch(43.21% 0.0986 290.72);--named-python--2: #0E658B;--named-python--2: oklch(47.854% 0.0961 234.411);--named-python--2-on: #FFFFFF;--named-python--2-on: oklch(100% 0 89.876);--named-python--2-dim: #62A6D0;--named-python--2-dim: oklch(69.659% 0.0928 237.263);--named-python--1: #256D9B;--named-python--1: oklch(51.241% 0.101 241.148);--named-python--1-on: #FFFFFF;--named-python--1-on: oklch(100% 0 89.876);--named-python--1-dim: #6DADDE;--named-python--1-dim: oklch(72.369% 0.0973 242.738);--named-python-0: #3776AB;--named-python-0: oklch(54.893% 0.1053 246.667);--named-python-0-on: #FFFFFF;--named-python-0-on: oklch(100% 0 89.876);--named-python-0-dim: #7AB4ED;--named-python-0-dim: oklch(75.224% 0.1025 248.914);--named-python-1: #497FBB;--named-python-1: oklch(58.609% 0.1092 252.392);--named-python-1-on: #FFFFFF;--named-python-1-on: oklch(100% 0 89.876);--named-python-1-dim: #88BCFC;--named-python-1-dim: oklch(78.351% 0.1071 254.107);--named-python-2: #5A87CB;--named-python-2: oklch(62.095% 0.1148 258.413);--named-python-2-on: #FFFFFF;--named-python-2-on: oklch(100% 0 89.876);--named-python-2-dim: #003C78;--named-python-2-dim: oklch(36.021% 0.1165 254.47);--named-ruby--2: #AF1C3E;--named-ruby--2: oklch(49.143% 0.1794 15.246);--named-ruby--2-on: #FFFFFF;--named-ruby--2-on: oklch(100% 0 89.876);--named-ruby--2-dim: #FF697E;--named-ruby--2-dim: oklch(71.298% 0.1828 14.54);--named-ruby--1: #BE2837;--named-ruby--1: oklch(52.6% 0.1848 21.63);--named-ruby--1-on: #FFFFFF;--named-ruby--1-on: oklch(100% 0 89.876);--named-ruby--1-dim: #FF7B7D;--named-ruby--1-dim: oklch(73.667% 0.1612 20.892);--named-ruby-0: #CC342D;--named-ruby-0: oklch(55.957% 0.1899 27.83);--named-ruby-0-on: #FFFFFF;--named-ruby-0-on: oklch(100% 0 89.876);--named-ruby-0-dim: #FF8B7E;--named-ruby-0-dim: oklch(76.051% 0.1428 27.581);--named-ruby-1: #DA4020;--named-ruby-1: oklch(59.396% 0.1958 33.303);--named-ruby-1-on: #FFFFFF;--named-ruby-1-on: oklch(100% 0 89.876);--named-ruby-1-dim: #FF9A83;--named-ruby-1-dim: oklch(78.539% 0.1262 33.664);--named-ruby-2: #E74D06;--named-ruby-2: oklch(62.827% 0.2007 38.625);--named-ruby-2-on: #FFFFFF;--named-ruby-2-on: oklch(100% 0 89.876);--named-ruby-2-dim: #691D00;--named-ruby-2-dim: oklch(35.102% 0.114 38.378);--named-rust--2: #C4907E;--named-rust--2: oklch(69.888% 0.0687 39.914);--named-rust--2-on: #FFFFFF;--named-rust--2-on: oklch(100% 0 89.876);--named-rust--2-dim: #784D3E;--named-rust--2-dim: oklch(46.48% 0.0634 39.995);--named-rust--1: #D19A81;--named-rust--1: oklch(73.243% 0.0751 45.485);--named-rust--1-on: #3F1D0C;--named-rust--1-on: oklch(27.531% 0.0595 45.62);--named-rust--1-dim: #875944;--named-rust--1-dim: oklch(50.899% 0.069 45.281);--named-rust-0: #DEA584;--named-rust-0: oklch(76.754% 0.0814 51.197);--named-rust-0-on: #3F1E06;--named-rust-0-on: oklch(27.656% 0.0619 52.87);--named-rust-0-dim: #966649;--named-rust-0-dim: oklch(55.406% 0.0751 51.75);--named-rust-1: #EAB087;--named-rust-1: oklch(80.127% 0.0873 56.367);--named-rust-1-on: #3F1E02;--named-rust-1-on: oklch(27.606% 0.0645 56.407);--named-rust-1-dim: #FFF0E7;--named-rust-1-dim: oklch(96.453% 0.0202 53.26);--named-rust-2: #F5BC89;--named-rust-2: oklch(83.537% 0.0934 62.823);--named-rust-2-on: #3D1F00;--named-rust-2-on: oklch(27.456% 0.0633 61.806);--named-rust-2-dim: #B18052;--named-rust-2-dim: oklch(63.831% 0.0868 63.437);--named-safari--2: #0063B0;--named-safari--2: oklch(49.493% 0.1446 251.158);--named-safari--2-on: #FFFFFF;--named-safari--2-on: oklch(100% 0 89.876);--named-safari--2-dim: #62A4F6;--named-safari--2-dim: oklch(71.038% 0.1385 254.732);--named-safari--1: #0069D1;--named-safari--1: oklch(53.226% 0.1799 255.732);--named-safari--1-on: #FFFFFF;--named-safari--1-on: oklch(100% 0 89.876);--named-safari--1-dim: #78AAFF;--named-safari--1-dim: oklch(73.853% 0.135 260.538);--named-safari-0: #006CFC;--named-safari-0: oklch(57.239% 0.2303 259.738);--named-safari-0-on: #FFFFFF;--named-safari-0-on: oklch(100% 0 89.876);--named-safari-0-dim: #92B1FF;--named-safari-0-dim: oklch(76.803% 0.1176 267.073);--named-safari-1: #4C74FF;--named-safari-1: oklch(60.839% 0.2112 267.538);--named-safari-1-on: #FFFFFF;--named-safari-1-on: oklch(100% 0 89.876);--named-safari-1-dim: #002989;--named-safari-1-dim: oklch(33.489% 0.1643 262.717);--named-safari-2: #6F7CFF;--named-safari-2: oklch(64.253% 0.1923 275.331);--named-safari-2-on: #FFFFFF;--named-safari-2-on: oklch(100% 0 89.876);--named-safari-2-dim: #1E2BB2;--named-safari-2-dim: oklch(39.235% 0.2075 268.485);--named-slack--2: #2F0740;--named-slack--2: oklch(23.762% 0.103 313.13);--named-slack--2-on: #FBE2FF;--named-slack--2-on: oklch(94.161% 0.0468 321.576);--named-slack--2-dim: #8E629E;--named-slack--2-dim: oklch(56.505% 0.1027 316.726);--named-slack--1: #3C0E46;--named-slack--1: oklch(27.168% 0.1057 319.831);--named-slack--1-on: #FFE1FE;--named-slack--1-on: oklch(94.28% 0.0501 326.99);--named-slack--1-dim: #94609B;--named-slack--1-dim: oklch(56.687% 0.1064 322.596);--named-slack-0: #4A154B;--named-slack-0: oklch(30.647% 0.1083 327.062);--named-slack-0-on: #FFE6F8;--named-slack-0-on: oklch(95.028% 0.0359 335.724);--named-slack-0-dim: #9E619B;--named-slack-0-dim: oklch(58.024% 0.1114 328.635);--named-slack-1: #581C50;--named-slack-1: oklch(34.114% 0.1114 333.374);--named-slack-1-on: #FFEEF7;--named-slack-1-on: oklch(96.509% 0.0217 344.2);--named-slack-1-dim: #AB659D;--named-slack-1-dim: oklch(60.331% 0.116 334.642);--named-slack-2: #672353;--named-slack-2: oklch(37.613% 0.1142 341.001);--named-slack-2-on: #FFF6F8;--named-slack-2-on: oklch(98.067% 0.01 1.977);--named-slack-2-dim: #BA699D;--named-slack-2-dim: oklch(62.819% 0.1218 342.126);--named-stripe--2: #1951E5;--named-stripe--2: oklch(50.475% 0.2305 263.878);--named-stripe--2-on: #FFFFFF;--named-stripe--2-on: oklch(100% 0 89.876);--named-stripe--2-dim: #819BFF;--named-stripe--2-dim: oklch(71.325% 0.1489 271.206);--named-stripe--1: #4556F3;--named-stripe--1: oklch(54.207% 0.2323 271.277);--named-stripe--1-on: #FFFFFF;--named-stripe--1-on: oklch(100% 0 89.876);--named-stripe--1-dim: #98A1FF;--named-stripe--1-dim: oklch(74.034% 0.1356 278.372);--named-stripe-0: #635BFF;--named-stripe-0: oklch(57.843% 0.2346 278.291);--named-stripe-0-on: #FFFFFF;--named-stripe-0-on: oklch(100% 0 89.876);--named-stripe-0-dim: #ABA8FF;--named-stripe-0-dim: oklch(76.776% 0.1233 284.675);--named-stripe-1: #7E64FF;--named-stripe-1: oklch(61.273% 0.2201 285.579);--named-stripe-1-on: #FFFFFF;--named-stripe-1-on: oklch(100% 0 89.876);--named-stripe-1-dim: #3000A1;--named-stripe-1-dim: oklch(34.46% 0.2103 278.805);--named-stripe-2: #966DFF;--named-stripe-2: oklch(64.735% 0.2077 292.586);--named-stripe-2-on: #FFFFFF;--named-stripe-2-on: oklch(100% 0 89.876);--named-stripe-2-dim: #4B0FB1;--named-stripe-2-dim: oklch(39.667% 0.2172 287.828);--named-swift--2: #DE5D52;--named-swift--2: oklch(64.02% 0.1638 27.487);--named-swift--2-on: #FFFFFF;--named-swift--2-on: oklch(100% 0 89.876);--named-swift--2-dim: #7A1412;--named-swift--2-dim: oklch(37.681% 0.1366 27.594);--named-swift--1: #ED684B;--named-swift--1: oklch(67.623% 0.1708 33.82);--named-swift--1-on: #FFFFFF;--named-swift--1-on: oklch(100% 0 89.876);--named-swift--1-dim: #8E230C;--named-swift--1-dim: oklch(42.848% 0.1461 33.3);--named-swift-0: #FA7343;--named-swift-0: oklch(70.935% 0.177 39.457);--named-swift-0-on: #FFFFFF;--named-swift-0-on: oklch(100% 0 89.876);--named-swift-0-dim: #9F3102;--named-swift-0-dim: oklch(47.413% 0.1531 38.246);--named-swift-1: #FF8447;--named-swift-1: oklch(74.161% 0.1672 45.17);--named-swift-1-on: #471800;--named-swift-1-on: oklch(27.907% 0.08 44.861);--named-swift-1-dim: #FFD6C6;--named-swift-1-dim: oklch(90.67% 0.051 42.449);--named-swift-2: #FF9655;--named-swift-2: oklch(77.172% 0.1494 50.688);--named-swift-2-on: #431A00;--named-swift-2-on: oklch(27.51% 0.0734 49.328);--named-swift-2-dim: #AF581B;--named-swift-2-dim: oklch(55.662% 0.1331 50.45);--named-typescript--2: #00689E;--named-typescript--2: oklch(49.538% 0.1171 241.797);--named-typescript--2-on: #FFFFFF;--named-typescript--2-on: oklch(100% 0 89.876);--named-typescript--2-dim: #60A9E2;--named-typescript--2-dim: oklch(71.055% 0.1113 243.85);--named-typescript--1: #0E70B5;--named-typescript--1: oklch(53.036% 0.1352 247.216);--named-typescript--1-on: #FFFFFF;--named-typescript--1-on: oklch(100% 0 89.876);--named-typescript--1-dim: #66AFF9;--named-typescript--1-dim: oklch(73.74% 0.1306 250.303);--named-typescript-0: #3178C6;--named-typescript-0: oklch(56.71% 0.1399 253.305);--named-typescript-0-on: #FFFFFF;--named-typescript-0-on: oklch(100% 0 89.876);--named-typescript-0-dim: #80B6FF;--named-typescript-0-dim: oklch(76.79% 0.1199 256.196);--named-typescript-1: #4A80D7;--named-typescript-1: oklch(60.472% 0.1448 259.51);--named-typescript-1-on: #FFFFFF;--named-typescript-1-on: oklch(100% 0 89.876);--named-typescript-1-dim: #9BBDFF;--named-typescript-1-dim: oklch(79.813% 0.1014 263.221);--named-typescript-2: #6087E7;--named-typescript-2: oklch(63.992% 0.1506 265.68);--named-typescript-2-on: #FFFFFF;--named-typescript-2-on: oklch(100% 0 89.876);--named-typescript-2-dim: #B1C4FF;--named-typescript-2-dim: oklch(82.68% 0.0859 270.61);--named-ubuntu--2: #CE3D39;--named-ubuntu--2: oklch(57.308% 0.1826 26.352);--named-ubuntu--2-on: #FFFFFF;--named-ubuntu--2-on: oklch(100% 0 89.876);--named-ubuntu--2-dim: #FF9187;--named-ubuntu--2-dim: oklch(77.169% 0.1344 26.109);--named-ubuntu--1: #DC482E;--named-ubuntu--1: oklch(60.683% 0.1888 32.34);--named-ubuntu--1-on: #FFFFFF;--named-ubuntu--1-on: oklch(100% 0 89.876);--named-ubuntu--1-dim: #FF9F8D;--named-ubuntu--1-dim: oklch(79.566% 0.1182 31.313);--named-ubuntu-0: #E95420;--named-ubuntu-0: oklch(64.051% 0.1941 37.756);--named-ubuntu-0-on: #FFFFFF;--named-ubuntu-0-on: oklch(100% 0 89.876);--named-ubuntu-0-dim: #731E00;--named-ubuntu-0-dim: oklch(37.133% 0.1239 37.151);--named-ubuntu-1: #F66003;--named-ubuntu-1: oklch(67.45% 0.2002 42.651);--named-ubuntu-1-on: #FFFFFF;--named-ubuntu-1-on: oklch(100% 0 89.876);--named-ubuntu-1-dim: #FFBBA0;--named-ubuntu-1-dim: oklch(84.83% 0.0874 42.589);--named-ubuntu-2: #FB7300;--named-ubuntu-2: oklch(70.725% 0.1908 48.603);--named-ubuntu-2-on: #FFFFFF;--named-ubuntu-2-on: oklch(100% 0 89.876);--named-ubuntu-2-dim: #FFC9AD;--named-ubuntu-2-dim: oklch(87.669% 0.0724 48.758);--named-zig--2: #E68A2A;--named-zig--2: oklch(71.632% 0.1516 61.09);--named-zig--2-on: #FFFFFF;--named-zig--2-on: oklch(100% 0 89.876);--named-zig--2-dim: #FFD0AB;--named-zig--2-dim: oklch(88.932% 0.0722 59.883);--named-zig--1: #EF9623;--named-zig--1: oklch(74.797% 0.1571 65.989);--named-zig--1-on: #3C2000;--named-zig--1-on: oklch(27.51% 0.0619 64.672);--named-zig--1-dim: #985A00;--named-zig--1-dim: oklch(52.645% 0.1178 65.321);--named-zig-0: #F7A41D;--named-zig-0: oklch(78.213% 0.1617 71.616);--named-zig-0-on: #392100;--named-zig-0-on: oklch(27.26% 0.059 70.201);--named-zig-0-dim: #A46900;--named-zig-0-dim: oklch(57.078% 0.1228 71.106);--named-zig-1: #FDB218;--named-zig-1: oklch(81.427% 0.166 77.364);--named-zig-1-on: #362300;--named-zig-1-on: oklch(27.28% 0.0568 78.393);--named-zig-1-dim: #FFF5EC;--named-zig-1-dim: oklch(97.549% 0.0161 64.676);--named-zig-2: #FFC234;--named-zig-2: oklch(84.746% 0.1606 83.298);--named-zig-2-on: #342400;--named-zig-2-on: oklch(27.239% 0.056 83.477);--named-zig-2-dim: #B78700;--named-zig-2-dim: oklch(65.269% 0.1341 83.302);--named-ocean--2: #006589;--named-ocean--2: oklch(47.575% 0.0967 231.709);--named-ocean--2-on: #FFFFFF;--named-ocean--2-on: oklch(100% 0 89.876);--named-ocean--2-dim: #5EA7CD;--named-ocean--2-dim: oklch(69.575% 0.092 232.913);--named-ocean--1: #006E9E;--named-ocean--1: oklch(51.036% 0.1121 237.388);--named-ocean--1-on: #FFFFFF;--named-ocean--1-on: oklch(100% 0 89.876);--named-ocean--1-dim: #61AEE2;--named-ocean--1-dim: oklch(72.216% 0.1078 240.079);--named-ocean-0: #0077B6;--named-ocean-0: oklch(54.639% 0.1313 242.683);--named-ocean-0-on: #FFFFFF;--named-ocean-0-on: oklch(100% 0 89.876);--named-ocean-0-dim: #62B6F8;--named-ocean-0-dim: oklch(75.052% 0.1259 244.348);--named-ocean-1: #2B7FC7;--named-ocean-1: oklch(58.196% 0.1359 248.676);--named-ocean-1-on: #FFFFFF;--named-ocean-1-on: oklch(100% 0 89.876);--named-ocean-1-dim: #7FBCFF;--named-ocean-1-dim: oklch(78.006% 0.1152 251.652);--named-ocean-2: #4587D8;--named-ocean-2: oklch(61.85% 0.1405 254.857);--named-ocean-2-on: #FFFFFF;--named-ocean-2-on: oklch(100% 0 89.876);--named-ocean-2-dim: #9AC3FF;--named-ocean-2-dim: oklch(80.986% 0.0961 257.8);--named-sand--2: #E2C094;--named-sand--2: oklch(82.654% 0.0699 73.762);--named-sand--2-on: #362304;--named-sand--2-on: oklch(27.331% 0.0528 75.477);--named-sand--2-dim: #A1835B;--named-sand--2-dim: oklch(62.853% 0.0664 74.11);--named-sand--1: #EBCD9A;--named-sand--1: oklch(86.155% 0.0744 80.659);--named-sand--1-on: #342401;--named-sand--1-on: oklch(27.252% 0.0549 82.851);--named-sand--1-dim: #AC9163;--named-sand--1-dim: oklch(67.027% 0.0703 80.396);--named-sand-0: #F4D9A0;--named-sand-0: oklch(89.435% 0.0791 85.447);--named-sand-0-on: #332400;--named-sand-0-on: oklch(27.099% 0.0555 85.044);--named-sand-0-dim: #B69F6A;--named-sand-0-dim: oklch(71.037% 0.0756 87.016);--named-sand-1: #FCE6A7;--named-sand-1: oklch(92.82% 0.0835 91.061);--named-sand-1-on: #302500;--named-sand-1-on: oklch(26.949% 0.0551 91.902);--named-sand-1-dim: #C0AD73;--named-sand-1-dim: oklch(75.042% 0.0794 92.014);--named-sand-2: #FFF3C5;--named-sand-2: oklch(96.228% 0.0607 95.129);--named-sand-2-on: #3A3314;--named-sand-2-on: oklch(32.076% 0.0482 96.792);--named-sand-2-dim: #C6BB90;--named-sand-2-dim: oklch(79.04% 0.059 95.43);--named-orange--2: #CD5947;--named-orange--2: oklch(60.747% 0.1513 30.864);--named-orange--2-on: #FFFFFF;--named-orange--2-on: oklch(100% 0 89.876);--named-orange--2-dim: #FFA192;--named-orange--2-dim: oklch(80.005% 0.1148 29.643);--named-orange--1: #DB6441;--named-orange--1: oklch(64.298% 0.1578 37.087);--named-orange--1-on: #FFFFFF;--named-orange--1-on: oklch(100% 0 89.876);--named-orange--1-dim: #FFB099;--named-orange--1-dim: oklch(82.726% 0.0986 36.922);--named-orange-0: #E86F3A;--named-orange-0: oklch(67.699% 0.1643 42.586);--named-orange-0-on: #FFFFFF;--named-orange-0-on: oklch(100% 0 89.876);--named-orange-0-dim: #872F00;--named-orange-0-dim: oklch(42.926% 0.1302 41.743);--named-orange-1: #F47B31;--named-orange-1: oklch(71.1% 0.1702 48.119);--named-orange-1-on: #FFFFFF;--named-orange-1-on: oklch(100% 0 89.876);--named-orange-1-dim: #FFCBB2;--named-orange-1-dim: oklch(88.15% 0.0683 46.989);--named-orange-2: #FF8726;--named-orange-2: oklch(74.374% 0.1758 53.13);--named-orange-2-on: #421C00;--named-orange-2-on: oklch(27.703% 0.0706 52.673);--named-orange-2-dim: #A14E00;--named-orange-2-dim: oklch(51.83% 0.132 52.744);--named-grape--2: #696B9B;--named-grape--2: oklch(54.567% 0.0746 281.784);--named-grape--2-on: #FFFFFF;--named-grape--2-on: oklch(100% 0 89.876);--named-grape--2-dim: #A7A9DD;--named-grape--2-dim: oklch(75.165% 0.0745 282.728);--named-grape--1: #7873A8;--named-grape--1: oklch(58.111% 0.0806 287.936);--named-grape--1-on: #FFFFFF;--named-grape--1-on: oklch(100% 0 89.876);--named-grape--1-dim: #2A2555;--named-grape--1-dim: oklch(29.802% 0.084 284.815);--named-grape-0: #887BB4;--named-grape-0: oklch(61.67% 0.0859 294.209);--named-grape-0-on: #FFFFFF;--named-grape-0-on: oklch(100% 0 89.876);--named-grape-0-dim: #C5B6F4;--named-grape-0-dim: oklch(81.017% 0.0876 295.476);--named-grape-1: #9883C0;--named-grape-1: oklch(65.207% 0.0919 299.669);--named-grape-1-on: #FFFFFF;--named-grape-1-on: oklch(100% 0 89.876);--named-grape-1-dim: #D4BDFF;--named-grape-1-dim: oklch(84.012% 0.0937 300.364);--named-grape-2: #A98BCB;--named-grape-2: oklch(68.769% 0.0974 305.441);--named-grape-2-on: #FFFFFF;--named-grape-2-on: oklch(100% 0 89.876);--named-grape-2-dim: #614681;--named-grape-2-dim: oklch(44.969% 0.0978 303.838);--named-red--2: #C13F5E;--named-red--2: oklch(56.072% 0.1662 10.037);--named-red--2-on: #FFFFFF;--named-red--2-on: oklch(100% 0 89.876);--named-red--2-dim: #FF899E;--named-red--2-dim: oklch(76.476% 0.1438 9.755);--named-red--1: #D14858;--named-red--1: oklch(59.52% 0.1721 17.331);--named-red--1-on: #FFFFFF;--named-red--1-on: oklch(100% 0 89.876);--named-red--1-dim: #5D0016;--named-red--1-dim: oklch(30.325% 0.1215 17.75);--named-red-0: #E05252;--named-red-0: oklch(62.945% 0.1776 23.724);--named-red-0-on: #FFFFFF;--named-red-0-on: oklch(100% 0 89.876);--named-red-0-dim: #770011;--named-red-0-dim: oklch(35.889% 0.1452 24.046);--named-red-1: #EF5D4A;--named-red-1: oklch(66.48% 0.1838 30.089);--named-red-1-on: #FFFFFF;--named-red-1-on: oklch(100% 0 89.876);--named-red-1-dim: #FFB6AA;--named-red-1-dim: oklch(84.159% 0.0873 29.459);--named-red-2: #FD6840;--named-red-2: oklch(69.867% 0.1905 35.88);--named-red-2-on: #FFFFFF;--named-red-2-on: oklch(100% 0 89.876);--named-red-2-dim: #9D2500;--named-red-2-dim: oklch(45.784% 0.161 34.907);--named-green--2: #448423;--named-green--2: oklch(55.027% 0.1451 137.072);--named-green--2-on: #FFFFFF;--named-green--2-on: oklch(100% 0 89.876);--named-green--2-dim: #7FC45B;--named-green--2-dim: oklch(75.101% 0.1555 136.033);--named-green--1: #3A9037;--named-green--1: oklch(58.078% 0.1507 142.796);--named-green--1-on: #FFFFFF;--named-green--1-on: oklch(100% 0 89.876);--named-green--1-dim: #003D06;--named-green--1-dim: oklch(31.25% 0.1015 143.941);--named-green-0: #2A9D4A;--named-green-0: oklch(61.409% 0.1574 148.364);--named-green-0-on: #FFFFFF;--named-green-0-on: oklch(100% 0 89.876);--named-green-0-dim: #6CDB7F;--named-green-0-dim: oklch(80.37% 0.1641 147.55);--named-green-1: #00AA5E;--named-green-1: oklch(64.765% 0.1632 154.041);--named-green-1-on: #FFFFFF;--named-green-1-on: oklch(100% 0 89.876);--named-green-1-dim: #60E793;--named-green-1-dim: oklch(83.246% 0.1678 153.281);--named-green-2: #00B576;--named-green-2: oklch(68.191% 0.1552 159.624);--named-green-2-on: #FFFFFF;--named-green-2-on: oklch(100% 0 89.876);--named-green-2-dim: #006842;--named-green-2-dim: oklch(45.631% 0.1037 159.701);--named-purple--2: #6B6AA0;--named-purple--2: oklch(54.769% 0.0834 284.14);--named-purple--2-on: #FFFFFF;--named-purple--2-on: oklch(100% 0 89.876);--named-purple--2-dim: #A9A8E2;--named-purple--2-dim: oklch(75.328% 0.0829 285.018);--named-purple--1: #7B72AC;--named-purple--1: oklch(58.359% 0.0881 290.513);--named-purple--1-on: #FFFFFF;--named-purple--1-on: oklch(100% 0 89.876);--named-purple--1-dim: #2C2459;--named-purple--1-dim: oklch(30.105% 0.0917 286.425);--named-purple-0: #8B7AB8;--named-purple-0: oklch(61.928% 0.0936 296.161);--named-purple-0-on: #FFFFFF;--named-purple-0-on: oklch(100% 0 89.876);--named-purple-0-dim: #C8B5F8;--named-purple-0-dim: oklch(81.239% 0.095 297.255);--named-purple-1: #9C82C3;--named-purple-1: oklch(65.52% 0.0986 302.13);--named-purple-1-on: #FFFFFF;--named-purple-1-on: oklch(100% 0 89.876);--named-purple-1-dim: #523B76;--named-purple-1-dim: oklch(40.613% 0.0983 300.05);--named-purple-2: #AE8ACE;--named-purple-2: oklch(69.188% 0.1047 308.078);--named-purple-2-on: #FFFFFF;--named-purple-2-on: oklch(100% 0 89.876);--named-purple-2-dim: #664584;--named-purple-2-dim: oklch(45.507% 0.1052 306.859);--named-stream--2: #00BED2;--named-stream--2: oklch(73.319% 0.1261 208.109);--named-stream--2-on: #002B31;--named-stream--2-on: oklch(26.465% 0.0456 209.054);--named-stream--2-dim: #A2F1FF;--named-stream--2-dim: oklch(91.179% 0.0794 209.818);--named-stream--1: #00C9E8;--named-stream--1: oklch(76.863% 0.1351 214.025);--named-stream--1-on: #002B34;--named-stream--1-on: oklch(26.623% 0.0472 215.7);--named-stream--1-dim: #008498;--named-stream--1-dim: oklch(56.352% 0.0987 213.126);--named-stream-0: #00D4FF;--named-stream-0: oklch(80.425% 0.1459 219.515);--named-stream-0-on: #002B36;--named-stream-0-on: oklch(26.734% 0.0486 219.817);--named-stream-0-dim: #0091AF;--named-stream-0-dim: oklch(60.696% 0.11 219.333);--named-stream-1: #7FD9FF;--named-stream-1: oklch(84.283% 0.1013 227.265);--named-stream-1-on: #002A38;--named-stream-1-on: oklch(26.543% 0.0507 225.893);--named-stream-1-dim: #3A9CBF;--named-stream-1-dim: oklch(65.051% 0.1032 225.628);--named-stream-2: #ABE0FF;--named-stream-2: oklch(88.049% 0.0693 234.252);--named-stream-2-on: #002A3B;--named-stream-2-on: oklch(26.725% 0.0538 230.889);--named-stream-2-dim: #70A5C2;--named-stream-2-dim: oklch(69.56% 0.0704 233.086);--named-navy--2: #002C41;--named-navy--2: oklch(27.699% 0.0592 235.592);--named-navy--2-on: #DAEEFF;--named-navy--2-on: oklch(93.951% 0.0314 243.7);--named-navy--2-dim: #567891;--named-navy--2-dim: oklch(55.66% 0.055 240.833);--named-navy--1: #003552;--named-navy--1: oklch(31.392% 0.0723 240.343);--named-navy--1-on: #E7F2FF;--named-navy--1-on: oklch(95.674% 0.0211 252.495);--named-navy--1-dim: #577F9F;--named-navy--1-dim: oklch(57.969% 0.0667 243.394);--named-navy-0: #0A3D62;--named-navy-0: oklch(34.875% 0.0822 246.063);--named-navy-0-on: #F2F6FF;--named-navy-0-on: oklch(97.271% 0.0128 266.701);--named-navy-0-dim: #5C85AD;--named-navy-0-dim: oklch(60.272% 0.0764 248.597);--named-navy-1: #1D4570;--named-navy-1: oklch(38.46% 0.0863 252.419);--named-navy-1-on: #FDFBFF;--named-navy-1-on: oklch(99.073% 0.0057 308.397);--named-navy-1-dim: #678BBA;--named-navy-1-dim: oklch(62.871% 0.0821 255.373);--named-navy-2: #2D4D7E;--named-navy-2: oklch(42.061% 0.0905 258.661);--named-navy-2-on: #FFFFFF;--named-navy-2-on: oklch(100% 0 89.876);--named-navy-2-dim: #7291C6;--named-navy-2-dim: oklch(65.423% 0.0871 260.865)}:root{--chart-qualitative-1: #614343;--chart-qualitative-1: oklch(41.632% 0.0418 18.988);--chart-qualitative-1-on: #ffffff;--chart-qualitative-1-on: oklch(100% 0 89.876);--chart-qualitative-1-dim: #aa8685;--chart-qualitative-1-dim: oklch(65.38% 0.0443 20.204);--chart-qualitative-2: #8b9a7c;--chart-qualitative-2: oklch(66.588% 0.0464 129.267);--chart-qualitative-2-on: #ffffff;--chart-qualitative-2-on: oklch(100% 0 89.876);--chart-qualitative-2-dim: #c5d4b4;--chart-qualitative-2-dim: oklch(84.996% 0.0463 127.878);--chart-qualitative-3: #916827;--chart-qualitative-3: oklch(54.728% 0.0961 75.448);--chart-qualitative-3-on: #ffffff;--chart-qualitative-3-on: oklch(100% 0 89.876);--chart-qualitative-3-dim: #d6a55e;--chart-qualitative-3-dim: oklch(75.269% 0.106 75.186);--chart-qualitative-4: #55665d;--chart-qualitative-4: oklch(49.318% 0.0251 162.281);--chart-qualitative-4-on: #ffffff;--chart-qualitative-4-on: oklch(100% 0 89.876);--chart-qualitative-4-dim: #94a69c;--chart-qualitative-4-dim: oklch(70.81% 0.0248 161.46);--chart-qualitative-5: #388090;--chart-qualitative-5: oklch(56.082% 0.0752 214.563);--chart-qualitative-5-on: #ffffff;--chart-qualitative-5-on: oklch(100% 0 89.876);--chart-qualitative-5-dim: #79bed0;--chart-qualitative-5-dim: oklch(76.215% 0.0741 216.138);--chart-qualitative-6: #55553a;--chart-qualitative-6: oklch(44.193% 0.0415 107.985);--chart-qualitative-6-on: #ffffff;--chart-qualitative-6-on: oklch(100% 0 89.876);--chart-qualitative-6-dim: #989778;--chart-qualitative-6-dim: oklch(66.888% 0.0438 105.995);--chart-qualitative-7: #b18446;--chart-qualitative-7: oklch(64.447% 0.0968 73.409);--chart-qualitative-7-on: #ffffff;--chart-qualitative-7-on: oklch(100% 0 89.876);--chart-qualitative-7-dim: #603d03;--chart-qualitative-7-dim: oklch(39.169% 0.082 72.163);--chart-qualitative-8: #69618a;--chart-qualitative-8: oklch(51.637% 0.0645 292.949);--chart-qualitative-8-on: #ffffff;--chart-qualitative-8-on: oklch(100% 0 89.876);--chart-qualitative-8-dim: #a9a0cc;--chart-qualitative-8-dim: oklch(72.853% 0.0637 294.14);--chart-qualitative-9: #6b8363;--chart-qualitative-9: oklch(58.191% 0.0553 137.95);--chart-qualitative-9-on: #ffffff;--chart-qualitative-9-on: oklch(100% 0 89.876);--chart-qualitative-9-dim: #a7c09d;--chart-qualitative-9-dim: oklch(77.944% 0.0558 136.762);--chart-qualitative-10: #8a4b31;--chart-qualitative-10: oklch(48.318% 0.0938 42.626);--chart-qualitative-10-on: #ffffff;--chart-qualitative-10-on: oklch(100% 0 89.876);--chart-qualitative-10-dim: #d68a6b;--chart-qualitative-10-dim: oklch(70.44% 0.1032 42.88);--chart-qualitative-11: #bcb538;--chart-qualitative-11: oklch(75.659% 0.1417 106.136);--chart-qualitative-11-on: #2a2700;--chart-qualitative-11-on: oklch(26.743% 0.0568 105.214);--chart-qualitative-11-dim: #f5ed6b;--chart-qualitative-11-dim: oklch(92.773% 0.1507 105.718);--chart-qualitative-12: #34525b;--chart-qualitative-12: oklch(41.877% 0.0386 218.7);--chart-qualitative-12-on: #ffffff;--chart-qualitative-12-on: oklch(100% 0 89.876);--chart-qualitative-12-dim: #77969f;--chart-qualitative-12-dim: oklch(65.271% 0.0371 217.373)}:root{--chart-diverging-1-step-1: #69618a;--chart-diverging-1-step-1: oklch(51.637% 0.0645 292.949);--chart-diverging-1-step-1-on: #ffffff;--chart-diverging-1-step-1-on: oklch(100% 0 89.876);--chart-diverging-1-step-1-dim: #a9a0cc;--chart-diverging-1-step-1-dim: oklch(72.853% 0.0637 294.14);--chart-diverging-1-step-2: #6c628b;--chart-diverging-1-step-2: oklch(52.168% 0.0646 295.263);--chart-diverging-1-step-2-on: #ffffff;--chart-diverging-1-step-2-on: oklch(100% 0 89.876);--chart-diverging-1-step-2-dim: #aca1cd;--chart-diverging-1-step-2-dim: oklch(73.341% 0.0639 296.25);--chart-diverging-1-step-3: #76638f;--chart-diverging-1-step-3: oklch(53.505% 0.0709 303.877);--chart-diverging-1-step-3-on: #ffffff;--chart-diverging-1-step-3-on: oklch(100% 0 89.876);--chart-diverging-1-step-3-dim: #b6a1d1;--chart-diverging-1-step-3-dim: oklch(74.355% 0.0716 304.636);--chart-diverging-1-step-4: #876591;--chart-diverging-1-step-4: oklch(55.716% 0.0774 318.409);--chart-diverging-1-step-4-on: #ffffff;--chart-diverging-1-step-4-on: oklch(100% 0 89.876);--chart-diverging-1-step-4-dim: #c8a2d1;--chart-diverging-1-step-4-dim: oklch(76.182% 0.078 319.777);--chart-diverging-1-step-5: #9f688e;--chart-diverging-1-step-5: oklch(58.82% 0.0872 338.89);--chart-diverging-1-step-5-on: #ffffff;--chart-diverging-1-step-5-on: oklch(100% 0 89.876);--chart-diverging-1-step-5-dim: #481c3d;--chart-diverging-1-step-5-dim: oklch(30.474% 0.0816 338.063);--chart-diverging-1-step-6: #bb6e7e;--chart-diverging-1-step-6: oklch(62.725% 0.0992 6.928);--chart-diverging-1-step-6-on: #ffffff;--chart-diverging-1-step-6-on: oklch(100% 0 89.876);--chart-diverging-1-step-6-dim: #632636;--chart-diverging-1-step-6-dim: oklch(36.198% 0.0896 6.362);--chart-diverging-1-step-7: #d27a5d;--chart-diverging-1-step-7: oklch(66.924% 0.1179 38.951);--chart-diverging-1-step-7-on: #ffffff;--chart-diverging-1-step-7-on: oklch(100% 0 89.876);--chart-diverging-1-step-7-dim: #ffbca7;--chart-diverging-1-step-7-dim: oklch(85.162% 0.0833 38.12);--chart-diverging-1-step-8: #d88c3e;--chart-diverging-1-step-8: oklch(70.412% 0.1305 63.262);--chart-diverging-1-step-8-on: #ffffff;--chart-diverging-1-step-8-on: oklch(100% 0 89.876);--chart-diverging-1-step-8-dim: #844b00;--chart-diverging-1-step-8-dim: oklch(47.037% 0.1072 63.059);--chart-diverging-1-step-9: #d29d2d;--chart-diverging-1-step-9: oklch(72.794% 0.1362 81.374);--chart-diverging-1-step-9-on: #352300;--chart-diverging-1-step-9-on: oklch(27.135% 0.0562 79.885);--chart-diverging-1-step-9-dim: #ffd997;--chart-diverging-1-step-9-dim: oklch(90.267% 0.0934 80.886);--chart-diverging-1-step-10: #c8aa2e;--chart-diverging-1-step-10: oklch(74.38% 0.1388 94.663);--chart-diverging-1-step-10-on: #2f2600;--chart-diverging-1-step-10-on: oklch(27.083% 0.0557 95.47);--chart-diverging-1-step-10-dim: #7f6900;--chart-diverging-1-step-10-dim: oklch(52.663% 0.108 94.474);--chart-diverging-1-step-11: #bfb235;--chart-diverging-1-step-11: oklch(75.26% 0.1406 103.26);--chart-diverging-1-step-11-on: #2c2700;--chart-diverging-1-step-11-on: oklch(26.981% 0.0565 102.089);--chart-diverging-1-step-11-dim: #f9ea68;--chart-diverging-1-step-11-dim: oklch(92.466% 0.1502 102.76);--chart-diverging-1-step-12: #bcb538;--chart-diverging-1-step-12: oklch(75.659% 0.1417 106.136);--chart-diverging-1-step-12-on: #2a2700;--chart-diverging-1-step-12-on: oklch(26.743% 0.0568 105.214);--chart-diverging-1-step-12-dim: #f5ed6b;--chart-diverging-1-step-12-dim: oklch(92.773% 0.1507 105.718)}:root{--chart-diverging-2-step-1: #8b9a7c;--chart-diverging-2-step-1: oklch(66.588% 0.0464 129.267);--chart-diverging-2-step-1-on: #ffffff;--chart-diverging-2-step-1-on: oklch(100% 0 89.876);--chart-diverging-2-step-1-dim: #c5d4b4;--chart-diverging-2-step-1-dim: oklch(84.996% 0.0463 127.878);--chart-diverging-2-step-2: #89997b;--chart-diverging-2-step-2: oklch(66.182% 0.0469 130.522);--chart-diverging-2-step-2-on: #ffffff;--chart-diverging-2-step-2-on: oklch(100% 0 89.876);--chart-diverging-2-step-2-dim: #445239;--chart-diverging-2-step-2-dim: oklch(41.898% 0.0438 131.845);--chart-diverging-2-step-3: #83957a;--chart-diverging-2-step-3: oklch(64.785% 0.0443 135.184);--chart-diverging-2-step-3-on: #ffffff;--chart-diverging-2-step-3-on: oklch(100% 0 89.876);--chart-diverging-2-step-3-dim: #3d4d37;--chart-diverging-2-step-3-dim: oklch(39.964% 0.0413 137.285);--chart-diverging-2-step-4: #7a9077;--chart-diverging-2-step-4: oklch(62.877% 0.0446 141.773);--chart-diverging-2-step-4-on: #ffffff;--chart-diverging-2-step-4-on: oklch(100% 0 89.876);--chart-diverging-2-step-4-dim: #334732;--chart-diverging-2-step-4-dim: oklch(37.438% 0.0434 143.484);--chart-diverging-2-step-5: #6e8874;--chart-diverging-2-step-5: oklch(60.045% 0.0424 151.936);--chart-diverging-2-step-5-on: #ffffff;--chart-diverging-2-step-5-on: oklch(100% 0 89.876);--chart-diverging-2-step-5-dim: #253c2c;--chart-diverging-2-step-5-dim: oklch(33.17% 0.0405 153.401);--chart-diverging-2-step-6: #607d70;--chart-diverging-2-step-6: oklch(56.31% 0.039 165.826);--chart-diverging-2-step-6-on: #ffffff;--chart-diverging-2-step-6-on: oklch(100% 0 89.876);--chart-diverging-2-step-6-dim: #9cbbac;--chart-diverging-2-step-6-dim: oklch(76.464% 0.0398 164.515);--chart-diverging-2-step-7: #50706a;--chart-diverging-2-step-7: oklch(51.855% 0.0382 181.878);--chart-diverging-2-step-7-on: #ffffff;--chart-diverging-2-step-7-on: oklch(100% 0 89.876);--chart-diverging-2-step-7-dim: #8eafa9;--chart-diverging-2-step-7-dim: oklch(72.767% 0.0371 182.991);--chart-diverging-2-step-8: #456565;--chart-diverging-2-step-8: oklch(48.211% 0.0372 195.987);--chart-diverging-2-step-8-on: #ffffff;--chart-diverging-2-step-8-on: oklch(100% 0 89.876);--chart-diverging-2-step-8-dim: #84a6a6;--chart-diverging-2-step-8-dim: oklch(69.989% 0.0373 196.331);--chart-diverging-2-step-9: #3d5d61;--chart-diverging-2-step-9: oklch(45.519% 0.0379 205.785);--chart-diverging-2-step-9-on: #ffffff;--chart-diverging-2-step-9-on: oklch(100% 0 89.876);--chart-diverging-2-step-9-dim: #7e9fa4;--chart-diverging-2-step-9-dim: oklch(67.947% 0.0372 207.683);--chart-diverging-2-step-10: #38575e;--chart-diverging-2-step-10: oklch(43.545% 0.0382 213.423);--chart-diverging-2-step-10-on: #ffffff;--chart-diverging-2-step-10-on: oklch(100% 0 89.876);--chart-diverging-2-step-10-dim: #7a9aa1;--chart-diverging-2-step-10-dim: oklch(66.448% 0.037 212.501);--chart-diverging-2-step-11: #35535c;--chart-diverging-2-step-11: oklch(42.241% 0.0385 218.678);--chart-diverging-2-step-11-on: #ffffff;--chart-diverging-2-step-11-on: oklch(100% 0 89.876);--chart-diverging-2-step-11-dim: #7897a0;--chart-diverging-2-step-11-dim: oklch(65.6% 0.0371 217.369);--chart-diverging-2-step-12: #34525b;--chart-diverging-2-step-12: oklch(41.877% 0.0386 218.7);--chart-diverging-2-step-12-on: #ffffff;--chart-diverging-2-step-12-on: oklch(100% 0 89.876);--chart-diverging-2-step-12-dim: #77969f;--chart-diverging-2-step-12-dim: oklch(65.271% 0.0371 217.373)}:root{--chart-diverging-3-step-1: #614343;--chart-diverging-3-step-1: oklch(41.632% 0.0418 18.988);--chart-diverging-3-step-1-on: #ffffff;--chart-diverging-3-step-1-on: oklch(100% 0 89.876);--chart-diverging-3-step-1-dim: #aa8685;--chart-diverging-3-step-1-dim: oklch(65.38% 0.0443 20.204);--chart-diverging-3-step-2: #624445;--chart-diverging-3-step-2: oklch(42.026% 0.0417 16.776);--chart-diverging-3-step-2-on: #ffffff;--chart-diverging-3-step-2-on: oklch(100% 0 89.876);--chart-diverging-3-step-2-dim: #ab8687;--chart-diverging-3-step-2-dim: oklch(65.54% 0.0455 16.569);--chart-diverging-3-step-3: #64454b;--chart-diverging-3-step-3: oklch(42.672% 0.0436 6.298);--chart-diverging-3-step-3-on: #ffffff;--chart-diverging-3-step-3-on: oklch(100% 0 89.876);--chart-diverging-3-step-3-dim: #ad878d;--chart-diverging-3-step-3-dim: oklch(66.117% 0.0471 7.846);--chart-diverging-3-step-4: #674855;--chart-diverging-3-step-4: oklch(43.997% 0.0463 352.296);--chart-diverging-3-step-4-on: #ffffff;--chart-diverging-3-step-4-on: oklch(100% 0 89.876);--chart-diverging-3-step-4-dim: #af8998;--chart-diverging-3-step-4-dim: oklch(67.047% 0.0504 352.963);--chart-diverging-3-step-5: #694d64;--chart-diverging-3-step-5: oklch(45.821% 0.0511 332.717);--chart-diverging-3-step-5-on: #ffffff;--chart-diverging-3-step-5-on: oklch(100% 0 89.876);--chart-diverging-3-step-5-dim: #ae8ea7;--chart-diverging-3-step-5-dim: oklch(68.409% 0.0518 333.826);--chart-diverging-3-step-6: #675676;--chart-diverging-3-step-6: oklch(48.224% 0.0543 308.572);--chart-diverging-3-step-6-on: #ffffff;--chart-diverging-3-step-6-on: oklch(100% 0 89.876);--chart-diverging-3-step-6-dim: #aa96b9;--chart-diverging-3-step-6-dim: oklch(70.239% 0.0549 310.604);--chart-diverging-3-step-7: #5f6187;--chart-diverging-3-step-7: oklch(50.622% 0.0602 281.679);--chart-diverging-3-step-7-on: #ffffff;--chart-diverging-3-step-7-on: oklch(100% 0 89.876);--chart-diverging-3-step-7-dim: #9fa0ca;--chart-diverging-3-step-7-dim: oklch(71.963% 0.0608 283.691);--chart-diverging-3-step-8: #536c90;--chart-diverging-3-step-8: oklch(52.626% 0.0644 257.461);--chart-diverging-3-step-8-on: #ffffff;--chart-diverging-3-step-8-on: oklch(100% 0 89.876);--chart-diverging-3-step-8-dim: #91abd2;--chart-diverging-3-step-8-dim: oklch(73.538% 0.0638 258.351);--chart-diverging-3-step-9: #477593;--chart-diverging-3-step-9: oklch(54.177% 0.0691 238.456);--chart-diverging-3-step-9-on: #ffffff;--chart-diverging-3-step-9-on: oklch(100% 0 89.876);--chart-diverging-3-step-9-dim: #86b3d4;--chart-diverging-3-step-9-dim: oklch(74.637% 0.0678 240.593);--chart-diverging-3-step-10: #3f7b92;--chart-diverging-3-step-10: oklch(55.208% 0.0717 225.421);--chart-diverging-3-step-10-on: #ffffff;--chart-diverging-3-step-10-on: oklch(100% 0 89.876);--chart-diverging-3-step-10-dim: #7fb9d2;--chart-diverging-3-step-10-dim: oklch(75.483% 0.0699 226.807);--chart-diverging-3-step-11: #3a7f91;--chart-diverging-3-step-11: oklch(55.95% 0.0743 217.418);--chart-diverging-3-step-11-on: #ffffff;--chart-diverging-3-step-11-on: oklch(100% 0 89.876);--chart-diverging-3-step-11-dim: #7bbdd0;--chart-diverging-3-step-11-dim: oklch(76.091% 0.0722 217.954);--chart-diverging-3-step-12: #388090;--chart-diverging-3-step-12: oklch(56.082% 0.0752 214.563);--chart-diverging-3-step-12-on: #ffffff;--chart-diverging-3-step-12-on: oklch(100% 0 89.876);--chart-diverging-3-step-12-dim: #79bed0;--chart-diverging-3-step-12-dim: oklch(76.215% 0.0741 216.138)}:root{--chart-diverging-4-step-1: #55665d;--chart-diverging-4-step-1: oklch(49.318% 0.0251 162.281);--chart-diverging-4-step-1-on: #ffffff;--chart-diverging-4-step-1-on: oklch(100% 0 89.876);--chart-diverging-4-step-1-dim: #94a69c;--chart-diverging-4-step-1-dim: oklch(70.81% 0.0248 161.46);--chart-diverging-4-step-2: #55665c;--chart-diverging-4-step-2: oklch(49.287% 0.026 159.487);--chart-diverging-4-step-2-on: #ffffff;--chart-diverging-4-step-2-on: oklch(100% 0 89.876);--chart-diverging-4-step-2-dim: #94a69b;--chart-diverging-4-step-2-dim: oklch(70.781% 0.0257 158.881);--chart-diverging-4-step-3: #546659;--chart-diverging-4-step-3: oklch(49.119% 0.03 153.808);--chart-diverging-4-step-3-on: #ffffff;--chart-diverging-4-step-3-on: oklch(100% 0 89.876);--chart-diverging-4-step-3-dim: #93a698;--chart-diverging-4-step-3-dim: oklch(70.621% 0.0293 153.64);--chart-diverging-4-step-4: #546653;--chart-diverging-4-step-4: oklch(48.946% 0.037 143.606);--chart-diverging-4-step-4-on: #ffffff;--chart-diverging-4-step-4-on: oklch(100% 0 89.876);--chart-diverging-4-step-4-dim: #93a791;--chart-diverging-4-step-4-dim: oklch(70.651% 0.0387 142.661);--chart-diverging-4-step-5: #576549;--chart-diverging-4-step-5: oklch(48.677% 0.0464 129.592);--chart-diverging-4-step-5-on: #ffffff;--chart-diverging-4-step-5-on: oklch(100% 0 89.876);--chart-diverging-4-step-5-dim: #96a685;--chart-diverging-4-step-5-dim: oklch(70.351% 0.0501 128.712);--chart-diverging-4-step-6: #60623b;--chart-diverging-4-step-6: oklch(48.419% 0.0576 110.879);--chart-diverging-4-step-6-on: #ffffff;--chart-diverging-4-step-6-on: oklch(100% 0 89.876);--chart-diverging-4-step-6-dim: #a1a376;--chart-diverging-4-step-6-dim: oklch(70.235% 0.0621 110.024);--chart-diverging-4-step-7: #6d5d2b;--chart-diverging-4-step-7: oklch(48.276% 0.0716 91.819);--chart-diverging-4-step-7-on: #ffffff;--chart-diverging-4-step-7-on: oklch(100% 0 89.876);--chart-diverging-4-step-7-dim: #b19d65;--chart-diverging-4-step-7-dim: oklch(70.056% 0.0781 90.554);--chart-diverging-4-step-8: #7a5724;--chart-diverging-4-step-8: oklch(48.362% 0.0814 73.812);--chart-diverging-4-step-8-on: #ffffff;--chart-diverging-4-step-8-on: oklch(100% 0 89.876);--chart-diverging-4-step-8-dim: #c1975d;--chart-diverging-4-step-8-dim: oklch(70.296% 0.0907 74.36);--chart-diverging-4-step-9: #825226;--chart-diverging-4-step-9: oklch(48.383% 0.0871 60.73);--chart-diverging-4-step-9-on: #ffffff;--chart-diverging-4-step-9-on: oklch(100% 0 89.876);--chart-diverging-4-step-9-dim: #cc9160;--chart-diverging-4-step-9-dim: oklch(70.375% 0.0966 60.014);--chart-diverging-4-step-10: #874e2b;--chart-diverging-4-step-10: oklch(48.356% 0.0912 50.696);--chart-diverging-4-step-10-on: #ffffff;--chart-diverging-4-step-10-on: oklch(100% 0 89.876);--chart-diverging-4-step-10-dim: #d28d65;--chart-diverging-4-step-10-dim: oklch(70.402% 0.1004 50.6);--chart-diverging-4-step-11: #894c2f;--chart-diverging-4-step-11: oklch(48.325% 0.0928 45.29);--chart-diverging-4-step-11-on: #ffffff;--chart-diverging-4-step-11-on: oklch(100% 0 89.876);--chart-diverging-4-step-11-dim: #d58b69;--chart-diverging-4-step-11-dim: oklch(70.463% 0.1024 45.312);--chart-diverging-4-step-12: #8a4b31;--chart-diverging-4-step-12: oklch(48.318% 0.0938 42.626);--chart-diverging-4-step-12-on: #ffffff;--chart-diverging-4-step-12-on: oklch(100% 0 89.876);--chart-diverging-4-step-12-dim: #d68a6b;--chart-diverging-4-step-12-dim: oklch(70.44% 0.1032 42.88)}:root{--chart-diverging-5-step-1: #55553a;--chart-diverging-5-step-1: oklch(44.193% 0.0415 107.985);--chart-diverging-5-step-1-on: #ffffff;--chart-diverging-5-step-1-on: oklch(100% 0 89.876);--chart-diverging-5-step-1-dim: #989778;--chart-diverging-5-step-1-dim: oklch(66.888% 0.0438 105.995);--chart-diverging-5-step-2: #575539;--chart-diverging-5-step-2: oklch(44.364% 0.0429 104.543);--chart-diverging-5-step-2-on: #ffffff;--chart-diverging-5-step-2-on: oklch(100% 0 89.876);--chart-diverging-5-step-2-dim: #9b9777;--chart-diverging-5-step-2-dim: oklch(67.125% 0.0453 101.594);--chart-diverging-5-step-3: #5b5537;--chart-diverging-5-step-3: oklch(44.718% 0.0459 98.25);--chart-diverging-5-step-3-on: #ffffff;--chart-diverging-5-step-3-on: oklch(100% 0 89.876);--chart-diverging-5-step-3-dim: #9f9774;--chart-diverging-5-step-3-dim: oklch(67.411% 0.0498 96.715);--chart-diverging-5-step-4: #645436;--chart-diverging-5-step-4: oklch(45.411% 0.0494 82.573);--chart-diverging-5-step-4-on: #ffffff;--chart-diverging-5-step-4-on: oklch(100% 0 89.876);--chart-diverging-5-step-4-dim: #a99573;--chart-diverging-5-step-4-dim: oklch(67.888% 0.0529 80.768);--chart-diverging-5-step-5: #6e5338;--chart-diverging-5-step-5: oklch(46.364% 0.054 65.831);--chart-diverging-5-step-5-on: #ffffff;--chart-diverging-5-step-5-on: oklch(100% 0 89.876);--chart-diverging-5-step-5-dim: #b49474;--chart-diverging-5-step-5-dim: oklch(68.78% 0.0588 66.587);--chart-diverging-5-step-6: #795244;--chart-diverging-5-step-6: oklch(47.747% 0.0572 40.555);--chart-diverging-5-step-6-on: #ffffff;--chart-diverging-5-step-6-on: oklch(100% 0 89.876);--chart-diverging-5-step-6-dim: #c09281;--chart-diverging-5-step-6-dim: oklch(69.912% 0.0612 41.201);--chart-diverging-5-step-7: #7e535a;--chart-diverging-5-step-7: oklch(49.162% 0.0582 8.743);--chart-diverging-5-step-7-on: #ffffff;--chart-diverging-5-step-7-on: oklch(100% 0 89.876);--chart-diverging-5-step-7-dim: #c59299;--chart-diverging-5-step-7-dim: oklch(71.066% 0.0623 9.627);--chart-diverging-5-step-8: #7b566e;--chart-diverging-5-step-8: oklch(50.072% 0.0599 340.381);--chart-diverging-5-step-8-on: #ffffff;--chart-diverging-5-step-8-on: oklch(100% 0 89.876);--chart-diverging-5-step-8-dim: #bf95af;--chart-diverging-5-step-8-dim: oklch(71.69% 0.0612 341.143);--chart-diverging-5-step-9: #755a7c;--chart-diverging-5-step-9: oklch(50.757% 0.0619 319.242);--chart-diverging-5-step-9-on: #ffffff;--chart-diverging-5-step-9-on: oklch(100% 0 89.876);--chart-diverging-5-step-9-dim: #b799be;--chart-diverging-5-step-9-dim: oklch(72.197% 0.0624 319.854);--chart-diverging-5-step-10: #6f5e85;--chart-diverging-5-step-10: oklch(51.349% 0.0636 304.185);--chart-diverging-5-step-10-on: #ffffff;--chart-diverging-5-step-10-on: oklch(100% 0 89.876);--chart-diverging-5-step-10-dim: #b09dc7;--chart-diverging-5-step-10-dim: oklch(72.67% 0.0633 305.417);--chart-diverging-5-step-11: #6b6089;--chart-diverging-5-step-11: oklch(51.565% 0.065 296.369);--chart-diverging-5-step-11-on: #ffffff;--chart-diverging-5-step-11-on: oklch(100% 0 89.876);--chart-diverging-5-step-11-dim: #ab9fcb;--chart-diverging-5-step-11-dim: oklch(72.784% 0.0642 297.279);--chart-diverging-5-step-12: #69618a;--chart-diverging-5-step-12: oklch(51.637% 0.0645 292.949);--chart-diverging-5-step-12-on: #ffffff;--chart-diverging-5-step-12-on: oklch(100% 0 89.876);--chart-diverging-5-step-12-dim: #a9a0cc;--chart-diverging-5-step-12-dim: oklch(72.853% 0.0637 294.14)}:root{--chart-diverging-6-step-1: #388090;--chart-diverging-6-step-1: oklch(56.082% 0.0752 214.563);--chart-diverging-6-step-1-on: #ffffff;--chart-diverging-6-step-1-on: oklch(100% 0 89.876);--chart-diverging-6-step-1-dim: #79bed0;--chart-diverging-6-step-1-dim: oklch(76.215% 0.0741 216.138);--chart-diverging-6-step-2: #37818f;--chart-diverging-6-step-2: oklch(56.255% 0.0756 211.947);--chart-diverging-6-step-2-on: #ffffff;--chart-diverging-6-step-2-on: oklch(100% 0 89.876);--chart-diverging-6-step-2-dim: #78bfce;--chart-diverging-6-step-2-dim: oklch(76.319% 0.0743 212.542);--chart-diverging-6-step-3: #37838b;--chart-diverging-6-step-3: oklch(56.601% 0.075 204.735);--chart-diverging-6-step-3-on: #ffffff;--chart-diverging-6-step-3-on: oklch(100% 0 89.876);--chart-diverging-6-step-3-dim: #78c1c9;--chart-diverging-6-step-3-dim: oklch(76.599% 0.0737 204.64);--chart-diverging-6-step-4: #398583;--chart-diverging-6-step-4: oklch(56.885% 0.0747 192.902);--chart-diverging-6-step-4-on: #ffffff;--chart-diverging-6-step-4-on: oklch(100% 0 89.876);--chart-diverging-6-step-4-dim: #79c3c0;--chart-diverging-6-step-4-dim: oklch(76.807% 0.0741 192.422);--chart-diverging-6-step-5: #428877;--chart-diverging-6-step-5: oklch(57.615% 0.0761 175.532);--chart-diverging-6-step-5-on: #ffffff;--chart-diverging-6-step-5-on: oklch(100% 0 89.876);--chart-diverging-6-step-5-dim: #7fc6b2;--chart-diverging-6-step-5-dim: oklch(77.378% 0.0774 174.439);--chart-diverging-6-step-6: #568b66;--chart-diverging-6-step-6: oklch(58.892% 0.0804 152.946);--chart-diverging-6-step-6-on: #ffffff;--chart-diverging-6-step-6-on: oklch(100% 0 89.876);--chart-diverging-6-step-6-dim: #023d20;--chart-diverging-6-step-6-dim: oklch(31.691% 0.0766 155.1);--chart-diverging-6-step-7: #728b53;--chart-diverging-6-step-7: oklch(60.301% 0.0848 128.309);--chart-diverging-6-step-7-on: #ffffff;--chart-diverging-6-step-7-on: oklch(100% 0 89.876);--chart-diverging-6-step-7-dim: #acc889;--chart-diverging-6-step-7-dim: oklch(79.634% 0.0902 127.783);--chart-diverging-6-step-8: #8a8a45;--chart-diverging-6-step-8: oklch(61.798% 0.0917 108.807);--chart-diverging-6-step-8-on: #ffffff;--chart-diverging-6-step-8-on: oklch(100% 0 89.876);--chart-diverging-6-step-8-dim: #c6c67a;--chart-diverging-6-step-8-dim: oklch(80.937% 0.0979 108.402);--chart-diverging-6-step-9: #9d8841;--chart-diverging-6-step-9: oklch(63.097% 0.0944 93.063);--chart-diverging-6-step-9-on: #ffffff;--chart-diverging-6-step-9-on: oklch(100% 0 89.876);--chart-diverging-6-step-9-dim: #dbc376;--chart-diverging-6-step-9-dim: oklch(82.077% 0.1007 92.661);--chart-diverging-6-step-10: #a88642;--chart-diverging-6-step-10: oklch(63.822% 0.0958 82.828);--chart-diverging-6-step-10-on: #ffffff;--chart-diverging-6-step-10-on: oklch(100% 0 89.876);--chart-diverging-6-step-10-dim: #e8c076;--chart-diverging-6-step-10-dim: oklch(82.702% 0.1032 81.89);--chart-diverging-6-step-11: #af8445;--chart-diverging-6-step-11: oklch(64.212% 0.0963 75.056);--chart-diverging-6-step-11-on: #ffffff;--chart-diverging-6-step-11-on: oklch(100% 0 89.876);--chart-diverging-6-step-11-dim: #5e3d01;--chart-diverging-6-step-11-dim: oklch(38.881% 0.0816 74.47);--chart-diverging-6-step-12: #b18446;--chart-diverging-6-step-12: oklch(64.447% 0.0968 73.409);--chart-diverging-6-step-12-on: #ffffff;--chart-diverging-6-step-12-on: oklch(100% 0 89.876);--chart-diverging-6-step-12-dim: #603d03;--chart-diverging-6-step-12-dim: oklch(39.169% 0.082 72.163)}:root.dark{--chart-qualitative-1: #34525b;--chart-qualitative-1: oklch(41.877% 0.0386 218.7);--chart-qualitative-1-on: #ffffff;--chart-qualitative-1-on: oklch(100% 0 89.876);--chart-qualitative-1-dim: #77969f;--chart-qualitative-1-dim: oklch(65.271% 0.0371 217.373);--chart-qualitative-2: #bcb538;--chart-qualitative-2: oklch(75.659% 0.1417 106.136);--chart-qualitative-2-on: #2a2700;--chart-qualitative-2-on: oklch(26.743% 0.0568 105.214);--chart-qualitative-2-dim: #f5ed6b;--chart-qualitative-2-dim: oklch(92.773% 0.1507 105.718);--chart-qualitative-3: #8a4b31;--chart-qualitative-3: oklch(48.318% 0.0938 42.626);--chart-qualitative-3-on: #ffffff;--chart-qualitative-3-on: oklch(100% 0 89.876);--chart-qualitative-3-dim: #d68a6b;--chart-qualitative-3-dim: oklch(70.44% 0.1032 42.88);--chart-qualitative-4: #6b8363;--chart-qualitative-4: oklch(58.191% 0.0553 137.95);--chart-qualitative-4-on: #ffffff;--chart-qualitative-4-on: oklch(100% 0 89.876);--chart-qualitative-4-dim: #a7c09d;--chart-qualitative-4-dim: oklch(77.944% 0.0558 136.762);--chart-qualitative-5: #69618a;--chart-qualitative-5: oklch(51.637% 0.0645 292.949);--chart-qualitative-5-on: #ffffff;--chart-qualitative-5-on: oklch(100% 0 89.876);--chart-qualitative-5-dim: #a9a0cc;--chart-qualitative-5-dim: oklch(72.853% 0.0637 294.14);--chart-qualitative-6: #b18446;--chart-qualitative-6: oklch(64.447% 0.0968 73.409);--chart-qualitative-6-on: #ffffff;--chart-qualitative-6-on: oklch(100% 0 89.876);--chart-qualitative-6-dim: #603d03;--chart-qualitative-6-dim: oklch(39.169% 0.082 72.163);--chart-qualitative-7: #55553a;--chart-qualitative-7: oklch(44.193% 0.0415 107.985);--chart-qualitative-7-on: #ffffff;--chart-qualitative-7-on: oklch(100% 0 89.876);--chart-qualitative-7-dim: #989778;--chart-qualitative-7-dim: oklch(66.888% 0.0438 105.995);--chart-qualitative-8: #388090;--chart-qualitative-8: oklch(56.082% 0.0752 214.563);--chart-qualitative-8-on: #ffffff;--chart-qualitative-8-on: oklch(100% 0 89.876);--chart-qualitative-8-dim: #79bed0;--chart-qualitative-8-dim: oklch(76.215% 0.0741 216.138);--chart-qualitative-9: #55665d;--chart-qualitative-9: oklch(49.318% 0.0251 162.281);--chart-qualitative-9-on: #ffffff;--chart-qualitative-9-on: oklch(100% 0 89.876);--chart-qualitative-9-dim: #94a69c;--chart-qualitative-9-dim: oklch(70.81% 0.0248 161.46);--chart-qualitative-10: #916827;--chart-qualitative-10: oklch(54.728% 0.0961 75.448);--chart-qualitative-10-on: #ffffff;--chart-qualitative-10-on: oklch(100% 0 89.876);--chart-qualitative-10-dim: #d6a55e;--chart-qualitative-10-dim: oklch(75.269% 0.106 75.186);--chart-qualitative-11: #8b9a7c;--chart-qualitative-11: oklch(66.588% 0.0464 129.267);--chart-qualitative-11-on: #ffffff;--chart-qualitative-11-on: oklch(100% 0 89.876);--chart-qualitative-11-dim: #c5d4b4;--chart-qualitative-11-dim: oklch(84.996% 0.0463 127.878);--chart-qualitative-12: #614343;--chart-qualitative-12: oklch(41.632% 0.0418 18.988);--chart-qualitative-12-on: #ffffff;--chart-qualitative-12-on: oklch(100% 0 89.876);--chart-qualitative-12-dim: #aa8685;--chart-qualitative-12-dim: oklch(65.38% 0.0443 20.204)}:root.dark{--chart-diverging-1-step-1: #bcb538;--chart-diverging-1-step-1: oklch(75.659% 0.1417 106.136);--chart-diverging-1-step-1-on: #2a2700;--chart-diverging-1-step-1-on: oklch(26.743% 0.0568 105.214);--chart-diverging-1-step-1-dim: #f5ed6b;--chart-diverging-1-step-1-dim: oklch(92.773% 0.1507 105.718);--chart-diverging-1-step-2: #bfb235;--chart-diverging-1-step-2: oklch(75.26% 0.1406 103.26);--chart-diverging-1-step-2-on: #2c2700;--chart-diverging-1-step-2-on: oklch(26.981% 0.0565 102.089);--chart-diverging-1-step-2-dim: #f9ea68;--chart-diverging-1-step-2-dim: oklch(92.466% 0.1502 102.76);--chart-diverging-1-step-3: #c8aa2e;--chart-diverging-1-step-3: oklch(74.38% 0.1388 94.663);--chart-diverging-1-step-3-on: #2f2600;--chart-diverging-1-step-3-on: oklch(27.083% 0.0557 95.47);--chart-diverging-1-step-3-dim: #7f6900;--chart-diverging-1-step-3-dim: oklch(52.663% 0.108 94.474);--chart-diverging-1-step-4: #d29d2d;--chart-diverging-1-step-4: oklch(72.794% 0.1362 81.374);--chart-diverging-1-step-4-on: #352300;--chart-diverging-1-step-4-on: oklch(27.135% 0.0562 79.885);--chart-diverging-1-step-4-dim: #ffd997;--chart-diverging-1-step-4-dim: oklch(90.267% 0.0934 80.886);--chart-diverging-1-step-5: #d88c3e;--chart-diverging-1-step-5: oklch(70.412% 0.1305 63.262);--chart-diverging-1-step-5-on: #ffffff;--chart-diverging-1-step-5-on: oklch(100% 0 89.876);--chart-diverging-1-step-5-dim: #844b00;--chart-diverging-1-step-5-dim: oklch(47.037% 0.1072 63.059);--chart-diverging-1-step-6: #d27a5d;--chart-diverging-1-step-6: oklch(66.924% 0.1179 38.951);--chart-diverging-1-step-6-on: #ffffff;--chart-diverging-1-step-6-on: oklch(100% 0 89.876);--chart-diverging-1-step-6-dim: #ffbca7;--chart-diverging-1-step-6-dim: oklch(85.162% 0.0833 38.12);--chart-diverging-1-step-7: #bb6e7e;--chart-diverging-1-step-7: oklch(62.725% 0.0992 6.928);--chart-diverging-1-step-7-on: #ffffff;--chart-diverging-1-step-7-on: oklch(100% 0 89.876);--chart-diverging-1-step-7-dim: #632636;--chart-diverging-1-step-7-dim: oklch(36.198% 0.0896 6.362);--chart-diverging-1-step-8: #9f688e;--chart-diverging-1-step-8: oklch(58.82% 0.0872 338.89);--chart-diverging-1-step-8-on: #ffffff;--chart-diverging-1-step-8-on: oklch(100% 0 89.876);--chart-diverging-1-step-8-dim: #481c3d;--chart-diverging-1-step-8-dim: oklch(30.474% 0.0816 338.063);--chart-diverging-1-step-9: #876591;--chart-diverging-1-step-9: oklch(55.716% 0.0774 318.409);--chart-diverging-1-step-9-on: #ffffff;--chart-diverging-1-step-9-on: oklch(100% 0 89.876);--chart-diverging-1-step-9-dim: #c8a2d1;--chart-diverging-1-step-9-dim: oklch(76.182% 0.078 319.777);--chart-diverging-1-step-10: #76638f;--chart-diverging-1-step-10: oklch(53.505% 0.0709 303.877);--chart-diverging-1-step-10-on: #ffffff;--chart-diverging-1-step-10-on: oklch(100% 0 89.876);--chart-diverging-1-step-10-dim: #b6a1d1;--chart-diverging-1-step-10-dim: oklch(74.355% 0.0716 304.636);--chart-diverging-1-step-11: #6c628b;--chart-diverging-1-step-11: oklch(52.168% 0.0646 295.263);--chart-diverging-1-step-11-on: #ffffff;--chart-diverging-1-step-11-on: oklch(100% 0 89.876);--chart-diverging-1-step-11-dim: #aca1cd;--chart-diverging-1-step-11-dim: oklch(73.341% 0.0639 296.25);--chart-diverging-1-step-12: #69618a;--chart-diverging-1-step-12: oklch(51.637% 0.0645 292.949);--chart-diverging-1-step-12-on: #ffffff;--chart-diverging-1-step-12-on: oklch(100% 0 89.876);--chart-diverging-1-step-12-dim: #a9a0cc;--chart-diverging-1-step-12-dim: oklch(72.853% 0.0637 294.14)}:root.dark{--chart-diverging-2-step-1: #34525b;--chart-diverging-2-step-1: oklch(41.877% 0.0386 218.7);--chart-diverging-2-step-1-on: #ffffff;--chart-diverging-2-step-1-on: oklch(100% 0 89.876);--chart-diverging-2-step-1-dim: #77969f;--chart-diverging-2-step-1-dim: oklch(65.271% 0.0371 217.373);--chart-diverging-2-step-2: #35535c;--chart-diverging-2-step-2: oklch(42.241% 0.0385 218.678);--chart-diverging-2-step-2-on: #ffffff;--chart-diverging-2-step-2-on: oklch(100% 0 89.876);--chart-diverging-2-step-2-dim: #7897a0;--chart-diverging-2-step-2-dim: oklch(65.6% 0.0371 217.369);--chart-diverging-2-step-3: #38575e;--chart-diverging-2-step-3: oklch(43.545% 0.0382 213.423);--chart-diverging-2-step-3-on: #ffffff;--chart-diverging-2-step-3-on: oklch(100% 0 89.876);--chart-diverging-2-step-3-dim: #7a9aa1;--chart-diverging-2-step-3-dim: oklch(66.448% 0.037 212.501);--chart-diverging-2-step-4: #3d5d61;--chart-diverging-2-step-4: oklch(45.519% 0.0379 205.785);--chart-diverging-2-step-4-on: #ffffff;--chart-diverging-2-step-4-on: oklch(100% 0 89.876);--chart-diverging-2-step-4-dim: #7e9fa4;--chart-diverging-2-step-4-dim: oklch(67.947% 0.0372 207.683);--chart-diverging-2-step-5: #456565;--chart-diverging-2-step-5: oklch(48.211% 0.0372 195.987);--chart-diverging-2-step-5-on: #ffffff;--chart-diverging-2-step-5-on: oklch(100% 0 89.876);--chart-diverging-2-step-5-dim: #84a6a6;--chart-diverging-2-step-5-dim: oklch(69.989% 0.0373 196.331);--chart-diverging-2-step-6: #50706a;--chart-diverging-2-step-6: oklch(51.855% 0.0382 181.878);--chart-diverging-2-step-6-on: #ffffff;--chart-diverging-2-step-6-on: oklch(100% 0 89.876);--chart-diverging-2-step-6-dim: #8eafa9;--chart-diverging-2-step-6-dim: oklch(72.767% 0.0371 182.991);--chart-diverging-2-step-7: #607d70;--chart-diverging-2-step-7: oklch(56.31% 0.039 165.826);--chart-diverging-2-step-7-on: #ffffff;--chart-diverging-2-step-7-on: oklch(100% 0 89.876);--chart-diverging-2-step-7-dim: #9cbbac;--chart-diverging-2-step-7-dim: oklch(76.464% 0.0398 164.515);--chart-diverging-2-step-8: #6e8874;--chart-diverging-2-step-8: oklch(60.045% 0.0424 151.936);--chart-diverging-2-step-8-on: #ffffff;--chart-diverging-2-step-8-on: oklch(100% 0 89.876);--chart-diverging-2-step-8-dim: #253c2c;--chart-diverging-2-step-8-dim: oklch(33.17% 0.0405 153.401);--chart-diverging-2-step-9: #7a9077;--chart-diverging-2-step-9: oklch(62.877% 0.0446 141.773);--chart-diverging-2-step-9-on: #ffffff;--chart-diverging-2-step-9-on: oklch(100% 0 89.876);--chart-diverging-2-step-9-dim: #334732;--chart-diverging-2-step-9-dim: oklch(37.438% 0.0434 143.484);--chart-diverging-2-step-10: #83957a;--chart-diverging-2-step-10: oklch(64.785% 0.0443 135.184);--chart-diverging-2-step-10-on: #ffffff;--chart-diverging-2-step-10-on: oklch(100% 0 89.876);--chart-diverging-2-step-10-dim: #3d4d37;--chart-diverging-2-step-10-dim: oklch(39.964% 0.0413 137.285);--chart-diverging-2-step-11: #89997b;--chart-diverging-2-step-11: oklch(66.182% 0.0469 130.522);--chart-diverging-2-step-11-on: #ffffff;--chart-diverging-2-step-11-on: oklch(100% 0 89.876);--chart-diverging-2-step-11-dim: #445239;--chart-diverging-2-step-11-dim: oklch(41.898% 0.0438 131.845);--chart-diverging-2-step-12: #8b9a7c;--chart-diverging-2-step-12: oklch(66.588% 0.0464 129.267);--chart-diverging-2-step-12-on: #ffffff;--chart-diverging-2-step-12-on: oklch(100% 0 89.876);--chart-diverging-2-step-12-dim: #c5d4b4;--chart-diverging-2-step-12-dim: oklch(84.996% 0.0463 127.878)}:root.dark{--chart-diverging-3-step-1: #388090;--chart-diverging-3-step-1: oklch(56.082% 0.0752 214.563);--chart-diverging-3-step-1-on: #ffffff;--chart-diverging-3-step-1-on: oklch(100% 0 89.876);--chart-diverging-3-step-1-dim: #79bed0;--chart-diverging-3-step-1-dim: oklch(76.215% 0.0741 216.138);--chart-diverging-3-step-2: #3a7f91;--chart-diverging-3-step-2: oklch(55.95% 0.0743 217.418);--chart-diverging-3-step-2-on: #ffffff;--chart-diverging-3-step-2-on: oklch(100% 0 89.876);--chart-diverging-3-step-2-dim: #7bbdd0;--chart-diverging-3-step-2-dim: oklch(76.091% 0.0722 217.954);--chart-diverging-3-step-3: #3f7b92;--chart-diverging-3-step-3: oklch(55.208% 0.0717 225.421);--chart-diverging-3-step-3-on: #ffffff;--chart-diverging-3-step-3-on: oklch(100% 0 89.876);--chart-diverging-3-step-3-dim: #7fb9d2;--chart-diverging-3-step-3-dim: oklch(75.483% 0.0699 226.807);--chart-diverging-3-step-4: #477593;--chart-diverging-3-step-4: oklch(54.177% 0.0691 238.456);--chart-diverging-3-step-4-on: #ffffff;--chart-diverging-3-step-4-on: oklch(100% 0 89.876);--chart-diverging-3-step-4-dim: #86b3d4;--chart-diverging-3-step-4-dim: oklch(74.637% 0.0678 240.593);--chart-diverging-3-step-5: #536c90;--chart-diverging-3-step-5: oklch(52.626% 0.0644 257.461);--chart-diverging-3-step-5-on: #ffffff;--chart-diverging-3-step-5-on: oklch(100% 0 89.876);--chart-diverging-3-step-5-dim: #91abd2;--chart-diverging-3-step-5-dim: oklch(73.538% 0.0638 258.351);--chart-diverging-3-step-6: #5f6187;--chart-diverging-3-step-6: oklch(50.622% 0.0602 281.679);--chart-diverging-3-step-6-on: #ffffff;--chart-diverging-3-step-6-on: oklch(100% 0 89.876);--chart-diverging-3-step-6-dim: #9fa0ca;--chart-diverging-3-step-6-dim: oklch(71.963% 0.0608 283.691);--chart-diverging-3-step-7: #675676;--chart-diverging-3-step-7: oklch(48.224% 0.0543 308.572);--chart-diverging-3-step-7-on: #ffffff;--chart-diverging-3-step-7-on: oklch(100% 0 89.876);--chart-diverging-3-step-7-dim: #aa96b9;--chart-diverging-3-step-7-dim: oklch(70.239% 0.0549 310.604);--chart-diverging-3-step-8: #694d64;--chart-diverging-3-step-8: oklch(45.821% 0.0511 332.717);--chart-diverging-3-step-8-on: #ffffff;--chart-diverging-3-step-8-on: oklch(100% 0 89.876);--chart-diverging-3-step-8-dim: #ae8ea7;--chart-diverging-3-step-8-dim: oklch(68.409% 0.0518 333.826);--chart-diverging-3-step-9: #674855;--chart-diverging-3-step-9: oklch(43.997% 0.0463 352.296);--chart-diverging-3-step-9-on: #ffffff;--chart-diverging-3-step-9-on: oklch(100% 0 89.876);--chart-diverging-3-step-9-dim: #af8998;--chart-diverging-3-step-9-dim: oklch(67.047% 0.0504 352.963);--chart-diverging-3-step-10: #64454b;--chart-diverging-3-step-10: oklch(42.672% 0.0436 6.298);--chart-diverging-3-step-10-on: #ffffff;--chart-diverging-3-step-10-on: oklch(100% 0 89.876);--chart-diverging-3-step-10-dim: #ad878d;--chart-diverging-3-step-10-dim: oklch(66.117% 0.0471 7.846);--chart-diverging-3-step-11: #624445;--chart-diverging-3-step-11: oklch(42.026% 0.0417 16.776);--chart-diverging-3-step-11-on: #ffffff;--chart-diverging-3-step-11-on: oklch(100% 0 89.876);--chart-diverging-3-step-11-dim: #ab8687;--chart-diverging-3-step-11-dim: oklch(65.54% 0.0455 16.569);--chart-diverging-3-step-12: #614343;--chart-diverging-3-step-12: oklch(41.632% 0.0418 18.988);--chart-diverging-3-step-12-on: #ffffff;--chart-diverging-3-step-12-on: oklch(100% 0 89.876);--chart-diverging-3-step-12-dim: #aa8685;--chart-diverging-3-step-12-dim: oklch(65.38% 0.0443 20.204)}:root.dark{--chart-diverging-4-step-1: #8a4b31;--chart-diverging-4-step-1: oklch(48.318% 0.0938 42.626);--chart-diverging-4-step-1-on: #ffffff;--chart-diverging-4-step-1-on: oklch(100% 0 89.876);--chart-diverging-4-step-1-dim: #d68a6b;--chart-diverging-4-step-1-dim: oklch(70.44% 0.1032 42.88);--chart-diverging-4-step-2: #894c2f;--chart-diverging-4-step-2: oklch(48.325% 0.0928 45.29);--chart-diverging-4-step-2-on: #ffffff;--chart-diverging-4-step-2-on: oklch(100% 0 89.876);--chart-diverging-4-step-2-dim: #d58b69;--chart-diverging-4-step-2-dim: oklch(70.463% 0.1024 45.312);--chart-diverging-4-step-3: #874e2b;--chart-diverging-4-step-3: oklch(48.356% 0.0912 50.696);--chart-diverging-4-step-3-on: #ffffff;--chart-diverging-4-step-3-on: oklch(100% 0 89.876);--chart-diverging-4-step-3-dim: #d28d65;--chart-diverging-4-step-3-dim: oklch(70.402% 0.1004 50.6);--chart-diverging-4-step-4: #825226;--chart-diverging-4-step-4: oklch(48.383% 0.0871 60.73);--chart-diverging-4-step-4-on: #ffffff;--chart-diverging-4-step-4-on: oklch(100% 0 89.876);--chart-diverging-4-step-4-dim: #cc9160;--chart-diverging-4-step-4-dim: oklch(70.375% 0.0966 60.014);--chart-diverging-4-step-5: #7a5724;--chart-diverging-4-step-5: oklch(48.362% 0.0814 73.812);--chart-diverging-4-step-5-on: #ffffff;--chart-diverging-4-step-5-on: oklch(100% 0 89.876);--chart-diverging-4-step-5-dim: #c1975d;--chart-diverging-4-step-5-dim: oklch(70.296% 0.0907 74.36);--chart-diverging-4-step-6: #6d5d2b;--chart-diverging-4-step-6: oklch(48.276% 0.0716 91.819);--chart-diverging-4-step-6-on: #ffffff;--chart-diverging-4-step-6-on: oklch(100% 0 89.876);--chart-diverging-4-step-6-dim: #b19d65;--chart-diverging-4-step-6-dim: oklch(70.056% 0.0781 90.554);--chart-diverging-4-step-7: #60623b;--chart-diverging-4-step-7: oklch(48.419% 0.0576 110.879);--chart-diverging-4-step-7-on: #ffffff;--chart-diverging-4-step-7-on: oklch(100% 0 89.876);--chart-diverging-4-step-7-dim: #a1a376;--chart-diverging-4-step-7-dim: oklch(70.235% 0.0621 110.024);--chart-diverging-4-step-8: #576549;--chart-diverging-4-step-8: oklch(48.677% 0.0464 129.592);--chart-diverging-4-step-8-on: #ffffff;--chart-diverging-4-step-8-on: oklch(100% 0 89.876);--chart-diverging-4-step-8-dim: #96a685;--chart-diverging-4-step-8-dim: oklch(70.351% 0.0501 128.712);--chart-diverging-4-step-9: #546653;--chart-diverging-4-step-9: oklch(48.946% 0.037 143.606);--chart-diverging-4-step-9-on: #ffffff;--chart-diverging-4-step-9-on: oklch(100% 0 89.876);--chart-diverging-4-step-9-dim: #93a791;--chart-diverging-4-step-9-dim: oklch(70.651% 0.0387 142.661);--chart-diverging-4-step-10: #546659;--chart-diverging-4-step-10: oklch(49.119% 0.03 153.808);--chart-diverging-4-step-10-on: #ffffff;--chart-diverging-4-step-10-on: oklch(100% 0 89.876);--chart-diverging-4-step-10-dim: #93a698;--chart-diverging-4-step-10-dim: oklch(70.621% 0.0293 153.64);--chart-diverging-4-step-11: #55665c;--chart-diverging-4-step-11: oklch(49.287% 0.026 159.487);--chart-diverging-4-step-11-on: #ffffff;--chart-diverging-4-step-11-on: oklch(100% 0 89.876);--chart-diverging-4-step-11-dim: #94a69b;--chart-diverging-4-step-11-dim: oklch(70.781% 0.0257 158.881);--chart-diverging-4-step-12: #55665d;--chart-diverging-4-step-12: oklch(49.318% 0.0251 162.281);--chart-diverging-4-step-12-on: #ffffff;--chart-diverging-4-step-12-on: oklch(100% 0 89.876);--chart-diverging-4-step-12-dim: #94a69c;--chart-diverging-4-step-12-dim: oklch(70.81% 0.0248 161.46)}:root.dark{--chart-diverging-5-step-1: #69618a;--chart-diverging-5-step-1: oklch(51.637% 0.0645 292.949);--chart-diverging-5-step-1-on: #ffffff;--chart-diverging-5-step-1-on: oklch(100% 0 89.876);--chart-diverging-5-step-1-dim: #a9a0cc;--chart-diverging-5-step-1-dim: oklch(72.853% 0.0637 294.14);--chart-diverging-5-step-2: #6b6089;--chart-diverging-5-step-2: oklch(51.565% 0.065 296.369);--chart-diverging-5-step-2-on: #ffffff;--chart-diverging-5-step-2-on: oklch(100% 0 89.876);--chart-diverging-5-step-2-dim: #ab9fcb;--chart-diverging-5-step-2-dim: oklch(72.784% 0.0642 297.279);--chart-diverging-5-step-3: #6f5e85;--chart-diverging-5-step-3: oklch(51.349% 0.0636 304.185);--chart-diverging-5-step-3-on: #ffffff;--chart-diverging-5-step-3-on: oklch(100% 0 89.876);--chart-diverging-5-step-3-dim: #b09dc7;--chart-diverging-5-step-3-dim: oklch(72.67% 0.0633 305.417);--chart-diverging-5-step-4: #755a7c;--chart-diverging-5-step-4: oklch(50.757% 0.0619 319.242);--chart-diverging-5-step-4-on: #ffffff;--chart-diverging-5-step-4-on: oklch(100% 0 89.876);--chart-diverging-5-step-4-dim: #b799be;--chart-diverging-5-step-4-dim: oklch(72.197% 0.0624 319.854);--chart-diverging-5-step-5: #7b566e;--chart-diverging-5-step-5: oklch(50.072% 0.0599 340.381);--chart-diverging-5-step-5-on: #ffffff;--chart-diverging-5-step-5-on: oklch(100% 0 89.876);--chart-diverging-5-step-5-dim: #bf95af;--chart-diverging-5-step-5-dim: oklch(71.69% 0.0612 341.143);--chart-diverging-5-step-6: #7e535a;--chart-diverging-5-step-6: oklch(49.162% 0.0582 8.743);--chart-diverging-5-step-6-on: #ffffff;--chart-diverging-5-step-6-on: oklch(100% 0 89.876);--chart-diverging-5-step-6-dim: #c59299;--chart-diverging-5-step-6-dim: oklch(71.066% 0.0623 9.627);--chart-diverging-5-step-7: #795244;--chart-diverging-5-step-7: oklch(47.747% 0.0572 40.555);--chart-diverging-5-step-7-on: #ffffff;--chart-diverging-5-step-7-on: oklch(100% 0 89.876);--chart-diverging-5-step-7-dim: #c09281;--chart-diverging-5-step-7-dim: oklch(69.912% 0.0612 41.201);--chart-diverging-5-step-8: #6e5338;--chart-diverging-5-step-8: oklch(46.364% 0.054 65.831);--chart-diverging-5-step-8-on: #ffffff;--chart-diverging-5-step-8-on: oklch(100% 0 89.876);--chart-diverging-5-step-8-dim: #b49474;--chart-diverging-5-step-8-dim: oklch(68.78% 0.0588 66.587);--chart-diverging-5-step-9: #645436;--chart-diverging-5-step-9: oklch(45.411% 0.0494 82.573);--chart-diverging-5-step-9-on: #ffffff;--chart-diverging-5-step-9-on: oklch(100% 0 89.876);--chart-diverging-5-step-9-dim: #a99573;--chart-diverging-5-step-9-dim: oklch(67.888% 0.0529 80.768);--chart-diverging-5-step-10: #5b5537;--chart-diverging-5-step-10: oklch(44.718% 0.0459 98.25);--chart-diverging-5-step-10-on: #ffffff;--chart-diverging-5-step-10-on: oklch(100% 0 89.876);--chart-diverging-5-step-10-dim: #9f9774;--chart-diverging-5-step-10-dim: oklch(67.411% 0.0498 96.715);--chart-diverging-5-step-11: #575539;--chart-diverging-5-step-11: oklch(44.364% 0.0429 104.543);--chart-diverging-5-step-11-on: #ffffff;--chart-diverging-5-step-11-on: oklch(100% 0 89.876);--chart-diverging-5-step-11-dim: #9b9777;--chart-diverging-5-step-11-dim: oklch(67.125% 0.0453 101.594);--chart-diverging-5-step-12: #55553a;--chart-diverging-5-step-12: oklch(44.193% 0.0415 107.985);--chart-diverging-5-step-12-on: #ffffff;--chart-diverging-5-step-12-on: oklch(100% 0 89.876);--chart-diverging-5-step-12-dim: #989778;--chart-diverging-5-step-12-dim: oklch(66.888% 0.0438 105.995)}:root.dark{--chart-diverging-6-step-1: #b18446;--chart-diverging-6-step-1: oklch(64.447% 0.0968 73.409);--chart-diverging-6-step-1-on: #ffffff;--chart-diverging-6-step-1-on: oklch(100% 0 89.876);--chart-diverging-6-step-1-dim: #603d03;--chart-diverging-6-step-1-dim: oklch(39.169% 0.082 72.163);--chart-diverging-6-step-2: #af8445;--chart-diverging-6-step-2: oklch(64.212% 0.0963 75.056);--chart-diverging-6-step-2-on: #ffffff;--chart-diverging-6-step-2-on: oklch(100% 0 89.876);--chart-diverging-6-step-2-dim: #5e3d01;--chart-diverging-6-step-2-dim: oklch(38.881% 0.0816 74.47);--chart-diverging-6-step-3: #a88642;--chart-diverging-6-step-3: oklch(63.822% 0.0958 82.828);--chart-diverging-6-step-3-on: #ffffff;--chart-diverging-6-step-3-on: oklch(100% 0 89.876);--chart-diverging-6-step-3-dim: #e8c076;--chart-diverging-6-step-3-dim: oklch(82.702% 0.1032 81.89);--chart-diverging-6-step-4: #9d8841;--chart-diverging-6-step-4: oklch(63.097% 0.0944 93.063);--chart-diverging-6-step-4-on: #ffffff;--chart-diverging-6-step-4-on: oklch(100% 0 89.876);--chart-diverging-6-step-4-dim: #dbc376;--chart-diverging-6-step-4-dim: oklch(82.077% 0.1007 92.661);--chart-diverging-6-step-5: #8a8a45;--chart-diverging-6-step-5: oklch(61.798% 0.0917 108.807);--chart-diverging-6-step-5-on: #ffffff;--chart-diverging-6-step-5-on: oklch(100% 0 89.876);--chart-diverging-6-step-5-dim: #c6c67a;--chart-diverging-6-step-5-dim: oklch(80.937% 0.0979 108.402);--chart-diverging-6-step-6: #728b53;--chart-diverging-6-step-6: oklch(60.301% 0.0848 128.309);--chart-diverging-6-step-6-on: #ffffff;--chart-diverging-6-step-6-on: oklch(100% 0 89.876);--chart-diverging-6-step-6-dim: #acc889;--chart-diverging-6-step-6-dim: oklch(79.634% 0.0902 127.783);--chart-diverging-6-step-7: #568b66;--chart-diverging-6-step-7: oklch(58.892% 0.0804 152.946);--chart-diverging-6-step-7-on: #ffffff;--chart-diverging-6-step-7-on: oklch(100% 0 89.876);--chart-diverging-6-step-7-dim: #023d20;--chart-diverging-6-step-7-dim: oklch(31.691% 0.0766 155.1);--chart-diverging-6-step-8: #428877;--chart-diverging-6-step-8: oklch(57.615% 0.0761 175.532);--chart-diverging-6-step-8-on: #ffffff;--chart-diverging-6-step-8-on: oklch(100% 0 89.876);--chart-diverging-6-step-8-dim: #7fc6b2;--chart-diverging-6-step-8-dim: oklch(77.378% 0.0774 174.439);--chart-diverging-6-step-9: #398583;--chart-diverging-6-step-9: oklch(56.885% 0.0747 192.902);--chart-diverging-6-step-9-on: #ffffff;--chart-diverging-6-step-9-on: oklch(100% 0 89.876);--chart-diverging-6-step-9-dim: #79c3c0;--chart-diverging-6-step-9-dim: oklch(76.807% 0.0741 192.422);--chart-diverging-6-step-10: #37838b;--chart-diverging-6-step-10: oklch(56.601% 0.075 204.735);--chart-diverging-6-step-10-on: #ffffff;--chart-diverging-6-step-10-on: oklch(100% 0 89.876);--chart-diverging-6-step-10-dim: #78c1c9;--chart-diverging-6-step-10-dim: oklch(76.599% 0.0737 204.64);--chart-diverging-6-step-11: #37818f;--chart-diverging-6-step-11: oklch(56.255% 0.0756 211.947);--chart-diverging-6-step-11-on: #ffffff;--chart-diverging-6-step-11-on: oklch(100% 0 89.876);--chart-diverging-6-step-11-dim: #78bfce;--chart-diverging-6-step-11-dim: oklch(76.319% 0.0743 212.542);--chart-diverging-6-step-12: #388090;--chart-diverging-6-step-12: oklch(56.082% 0.0752 214.563);--chart-diverging-6-step-12-on: #ffffff;--chart-diverging-6-step-12-on: oklch(100% 0 89.876);--chart-diverging-6-step-12-dim: #79bed0;--chart-diverging-6-step-12-dim: oklch(76.215% 0.0741 216.138)}:root{--code-bg: #fffae4;--code-bg: oklch(98.316% 0.0292 96.39);--code-inner-bg: #c3cdcc;--code-inner-bg: oklch(84.031% 0.011 189.777);--code-border: #99b4b3;--code-border: oklch(74.93% 0.0296 193.787);--code-border-subtle: #99b4b38c;--code-border-subtle: oklch(74.93% 0.0296 193.787 / 0.549);--code-fg: #513a13;--code-fg: oklch(36.608% 0.0629 77.485);--code-ln: #513a13;--code-ln: oklch(36.608% 0.0629 77.485);--code-ln-current: #513a13;--code-ln-current: oklch(36.608% 0.0629 77.485);--code-highlight-line: #e1bd8a3d;--code-highlight-line: oklch(81.778% 0.0784 75.939 / 0.239);--code-selection: #b4c4d852;--code-selection: oklch(81.443% 0.0333 254.025 / 0.322);--code-comment: #feb274;--code-comment: oklch(82.308% 0.1185 59.31);--code-comment-special: #feb274;--code-comment-special: oklch(82.308% 0.1185 59.31);--code-keyword: #9f8153;--code-keyword: oklch(62.083% 0.0727 77.481);--code-keyword-const: #5e8e8e;--code-keyword-const: oklch(61.259% 0.0518 195.882);--code-keyword-decl: #5a8ca6;--code-keyword-decl: oklch(61.474% 0.0666 231.998);--code-keyword-namespace: #9f8152;--code-keyword-namespace: oklch(62.066% 0.0738 77.984);--code-type: #8f865c;--code-type: oklch(61.74% 0.0598 97.524);--code-operator: #81896e;--code-operator: oklch(61.584% 0.0404 121.002);--code-name: #908657;--code-name: oklch(61.738% 0.0662 97.632);--code-name-class: #d1655e;--code-name-class: oklch(63.467% 0.138 25.504);--code-name-constant: #d1655e;--code-name-constant: oklch(63.467% 0.138 25.504);--code-name-decorator: #908657;--code-name-decorator: oklch(61.738% 0.0662 97.632);--code-name-entity: #908657;--code-name-entity: oklch(61.738% 0.0662 97.632);--code-name-exception: #908657;--code-name-exception: oklch(61.738% 0.0662 97.632);--code-name-function: #908657;--code-name-function: oklch(61.738% 0.0662 97.632);--code-name-label: #908657;--code-name-label: oklch(61.738% 0.0662 97.632);--code-name-namespace: #908657;--code-name-namespace: oklch(61.738% 0.0662 97.632);--code-name-property: #d1655e;--code-name-property: oklch(63.467% 0.138 25.504);--code-name-tag: #b37945;--code-name-tag: oklch(62.516% 0.0996 61.24);--code-name-variable: #908657;--code-name-variable: oklch(61.738% 0.0662 97.632);--code-name-attribute: #d1655e;--code-name-attribute: oklch(63.467% 0.138 25.504);--code-name-builtin: #88894e;--code-name-builtin: oklch(61.528% 0.0806 109.397);--code-string: #78879a;--code-string: oklch(61.792% 0.0337 254.41);--code-string-affix: #78879a;--code-string-affix: oklch(61.792% 0.0337 254.41);--code-string-delim: #78879a;--code-string-delim: oklch(61.792% 0.0337 254.41);--code-string-escape: #78879a;--code-string-escape: oklch(61.792% 0.0337 254.41);--code-string-regex: #78879a;--code-string-regex: oklch(61.792% 0.0337 254.41);--code-string-doc: #b4c4d8;--code-string-doc: oklch(81.443% 0.0333 254.025);--code-number: #78879a;--code-number: oklch(61.792% 0.0337 254.41);--code-error: #513a13;--code-error: oklch(36.608% 0.0629 77.485);--code-inserted: #78879a;--code-inserted: oklch(61.792% 0.0337 254.41);--code-deleted: #513a13;--code-deleted: oklch(36.608% 0.0629 77.485);--code-strong: #513a13;--code-strong: oklch(36.608% 0.0629 77.485);--code-emph: #513a13;--code-emph: oklch(36.608% 0.0629 77.485)}:root.dark{--code-bg: #080701;--code-bg: oklch(12.666% 0.0215 103.441);--code-inner-bg: #252f2e;--code-inner-bg: oklch(29.544% 0.0138 188.984);--code-border: #2f4848;--code-border: oklch(38.142% 0.0306 195.942);--code-border-subtle: #2f48488c;--code-border-subtle: oklch(38.142% 0.0306 195.942 / 0.549);--code-fg: #ffe8c9;--code-fg: oklch(94.171% 0.0478 75.325);--code-ln: #ffe8c9;--code-ln: oklch(94.171% 0.0478 75.325);--code-ln-current: #ffe8c9;--code-ln-current: oklch(94.171% 0.0478 75.325);--code-highlight-line: #8b6d413d;--code-highlight-line: oklch(55.418% 0.0716 75.953 / 0.239);--code-selection: #64748552;--code-selection: oklch(55.199% 0.0327 250.007 / 0.322);--code-comment: #a1642d;--code-comment: oklch(55.997% 0.1052 59.806);--code-comment-special: #a1642d;--code-comment-special: oklch(55.997% 0.1052 59.806);--code-keyword: #cdab7a;--code-keyword: oklch(75.993% 0.0761 76.297);--code-keyword-const: #8bbcbc;--code-keyword-const: oklch(76.075% 0.0515 196.12);--code-keyword-decl: #85b7d3;--code-keyword-decl: oklch(75.415% 0.0664 233.327);--code-keyword-namespace: #cdac79;--code-keyword-namespace: oklch(76.174% 0.0775 78.053);--code-type: #bbb184;--code-type: oklch(75.752% 0.0618 97.064);--code-operator: #acb497;--code-operator: oklch(75.567% 0.0412 119.926);--code-name: #bcb17e;--code-name: oklch(75.72% 0.0693 97.432);--code-name-class: #ff928a;--code-name-class: oklch(77.39% 0.1328 25.023);--code-name-constant: #ff928a;--code-name-constant: oklch(77.39% 0.1328 25.023);--code-name-decorator: #bcb17e;--code-name-decorator: oklch(75.72% 0.0693 97.432);--code-name-entity: #bcb17e;--code-name-entity: oklch(75.72% 0.0693 97.432);--code-name-exception: #bcb17e;--code-name-exception: oklch(75.72% 0.0693 97.432);--code-name-function: #bcb17e;--code-name-function: oklch(75.72% 0.0693 97.432);--code-name-label: #bcb17e;--code-name-label: oklch(75.72% 0.0693 97.432);--code-name-namespace: #bcb17e;--code-name-namespace: oklch(75.72% 0.0693 97.432);--code-name-property: #ff928a;--code-name-property: oklch(77.39% 0.1328 25.023);--code-name-tag: #e4a36b;--code-name-tag: oklch(76.581% 0.1058 60.99);--code-name-variable: #bcb17e;--code-name-variable: oklch(75.72% 0.0693 97.432);--code-name-attribute: #ff928a;--code-name-attribute: oklch(77.39% 0.1328 25.023);--code-name-builtin: #b3b475;--code-name-builtin: oklch(75.42% 0.0836 109.023);--code-string: #a3b2c6;--code-string: oklch(75.841% 0.0333 255.617);--code-string-affix: #a3b2c6;--code-string-affix: oklch(75.841% 0.0333 255.617);--code-string-delim: #a3b2c6;--code-string-delim: oklch(75.841% 0.0333 255.617);--code-string-escape: #a3b2c6;--code-string-escape: oklch(75.841% 0.0333 255.617);--code-string-regex: #a3b2c6;--code-string-regex: oklch(75.841% 0.0333 255.617);--code-string-doc: #647485;--code-string-doc: oklch(55.199% 0.0327 250.007);--code-number: #a3b2c6;--code-number: oklch(75.841% 0.0333 255.617);--code-error: #ffe8c9;--code-error: oklch(94.171% 0.0478 75.325);--code-inserted: #a3b2c6;--code-inserted: oklch(75.841% 0.0333 255.617);--code-deleted: #ffe8c9;--code-deleted: oklch(94.171% 0.0478 75.325);--code-strong: #ffe8c9;--code-strong: oklch(94.171% 0.0478 75.325);--code-emph: #ffe8c9;--code-emph: oklch(94.171% 0.0478 75.325)}:root{--font-antique: Superclarendon, "Bookman Old Style", "URW Bookman", "URW Bookman L", "Georgia Pro", Georgia, serif;--font-classical-humanist: Optima, Candara, "Noto Sans", source-sans-pro, sans-serif;--font-didone: Didot, "Bodoni MT", "Noto Serif Display", "URW Palladio L", P052, Sylfaen, serif;--font-geometric-humanist: Avenir, Montserrat, Corbel, "URW Gothic", source-sans-pro, sans-serif;--font-handwritten: "Segoe Print", "Bradley Hand", Chilanka, TSCu_Comic, casual, cursive;--font-humanist: Seravek, "Gill Sans Nova", Ubuntu, Calibri, "DejaVu Sans", source-sans-pro, sans-serif;--font-industrial: Bahnschrift, "DIN Alternate", "Franklin Gothic Medium", "Nimbus Sans Narrow", sans-serif-condensed, sans-serif;--font-mono: "Iosevka X", ui-monospace, "SF Mono", Menlo, monospace;--font-monospace-code: "Dank Mono", "Operator Mono", Inconsolata, "Fira Mono", ui-monospace, "SF Mono", Monaco, "Droid Sans Mono", "Source Code Pro", "Cascadia Code", Menlo, Consolas, "DejaVu Sans Mono", monospace;--font-monospace-slab-serif: "Nimbus Mono PS", "Courier New", monospace;--font-neo-grotesque: Inter, Roboto, "Helvetica Neue", "Arial Nova", "Nimbus Sans", Arial, sans-serif;--font-old-style: "Iowan Old Style", "Palatino Linotype", "URW Palladio L", P052, serif;--font-rounded-sans: ui-rounded, "Hiragino Maru Gothic ProN", Quicksand, Comfortaa, Manjari, "Arial Rounded MT", "Arial Rounded MT Bold", Calibri, source-sans-pro, sans-serif;--font-sans: "Source Sans 3", system-ui, sans-serif;--font-serif: ui-serif, serif;--font-slab-serif: Rockwell, "Rockwell Nova", "Roboto Slab", "DejaVu Serif", "Sitka Small", serif;--font-transitional: Charter, "Bitstream Charter", "Sitka Text", Cambria, serif}:root{--font-weight-thin: 100;--font-weight-extra-light: 200;--font-weight-light: 300;--font-weight-normal: 400;--font-weight-medium: 500;--font-weight-semi-bold: 600;--font-weight-bold: 700;--font-weight-extra-bold: 800;--font-weight-black: 900;--font-weight-extra-black: 950}:root{--viewport-min: 320px;--viewport-max: 1440px;font-size:112.5%}:root{--font-size--2: clamp(0.694444rem, calc(0.667108rem + 0.15377vw), 0.790123rem);--font-size--1: clamp(0.833333rem, calc(0.81746rem + 0.089286vw), 0.888889rem);--font-size-0: 1rem;--font-size-1: clamp(1.125rem, calc(1.103571rem + 0.120536vw), 1.2rem);--font-size-2: clamp(1.265625rem, calc(1.215804rem + 0.280246vw), 1.44rem);--font-size-3: clamp(1.423828rem, calc(1.336922rem + 0.488848vw), 1.728rem);--font-size-4: clamp(1.601807rem, calc(1.467009rem + 0.758239vw), 2.0736rem);--font-size-5: clamp(1.802032rem, calc(1.60595rem + 1.102962vw), 2.48832rem);--font-size-6: clamp(2.027287rem, calc(1.753373rem + 1.540764vw), 2.985984rem);--font-size-7: clamp(2.280697rem, calc(1.908559rem + 2.093277vw), 3.583181rem);--font-size-8: clamp(2.565785rem, calc(2.070347rem + 2.786838vw), 4.299817rem);--font-size-9: clamp(2.886508rem, calc(2.237001rem + 3.653474vw), 5.15978rem);--font-size-10: clamp(3.247321rem, calc(2.406059rem + 4.732096vw), 6.191736rem);--font-size-11: clamp(3.653236rem, calc(2.574137rem + 6.069934vw), 7.430084rem);--font-size-12: clamp(4.109891rem, calc(2.736688rem + 7.724266vw), 8.9161rem)}:root{--font-line-height--3: 1.234804rem;--font-line-height--2: 1.317536rem;--font-line-height--1: 1.405811rem;--font-line-height-0: 1.5rem;--font-line-height-1: 1.6005rem;--font-line-height-2: 1.707733rem;--font-line-height-3: 1.822152rem;--font-line-height-4: 1.944236rem}:root{--font-letter-spacing--2: calc(calc(-1 * max(0, 0.025) * pow(max(1, calc(1.778 + 0.222 * clamp(0, calc((100vw - 320px) / (1440px - 320px)), 1))), 1)) * 1em);--font-letter-spacing--1: calc(calc(-1 * max(0, 0.025)) * 1em);--font-letter-spacing-0: calc(calc(max(0, 0.025) - max(0, 0.025)) * 1em);--font-letter-spacing-1: calc(max(0, 0.025) * 1em);--font-letter-spacing-2: calc(calc(max(0, 0.025) * pow(max(1, calc(1.778 + 0.222 * clamp(0, calc((100vw - 320px) / (1440px - 320px)), 1))), 1)) * 1em);--font-letter-spacing-3: calc(calc(max(0, 0.025) * pow(max(1, calc(1.778 + 0.222 * clamp(0, calc((100vw - 320px) / (1440px - 320px)), 1))), 2)) * 1em)}:root{--size--2: clamp(0.439179rem, calc(0.346404rem + 0.521856vw), 0.763889rem);--size--1: clamp(0.468604rem, calc(0.340586rem + 0.720101vw), 0.916667rem);--size-0: clamp(0.5rem, calc(0.328571rem + 0.964286vw), 1.1rem);--size-1: clamp(0.5335rem, calc(0.308786rem + 1.264018vw), 1.32rem);--size-2: clamp(0.569244rem, calc(0.279314rem + 1.630857vw), 1.584rem);--size-3: clamp(0.607384rem, calc(0.237836rem + 2.078704vw), 1.9008rem);--size-4: clamp(0.648079rem, calc(0.181541rem + 2.624274vw), 2.28096rem);--size-5: clamp(0.6915rem, calc(0.107028rem + 3.287655vw), 2.737152rem);--size-6: clamp(0.73783rem, calc(0.010187rem + 4.092994vw), 3.284582rem);--size-7: clamp(0.787265rem, calc(-0.113945rem + 5.069304vw), 3.941499rem);--size-8: clamp(0.840012rem, calc(-0.271356rem + 6.251443vw), 4.729799rem);--size-9: clamp(0.896293rem, calc(-0.469269rem + 7.681284vw), 5.675758rem);--size-10: clamp(0.956344rem, calc(-0.716389rem + 9.409124vw), 6.81091rem);--size-11: clamp(1.020419rem, calc(-1.023202rem + 11.495367vw), 8.173092rem);--size-12: clamp(1.088787rem, calc(-1.402334rem + 14.012555vw), 9.80771rem)}:root{--aspect-ratio-portrait: 0.75;--aspect-ratio-widescreen: 1.7778;--aspect-ratio-square: 1;--aspect-ratio-landscape: 1.3333;--aspect-ratio-cinematic: 2.35;--aspect-ratio-ultrawide: 3.5556}:root{--zindex-drawer: 700;--zindex-dialog: 800;--zindex-dropdown: 900;--zindex-toast: 950;--zindex-tooltip: 1000;--zindex-important: 18014398509481984}:root{--border-width-0: 0.1rem;--border-width-1: clamp(0.1125rem, calc(0.104243rem + 0.046446vw), 0.1414rem);--border-width-2: clamp(0.126562rem, calc(0.105598rem + 0.117927vw), 0.19994rem);--border-width-3: clamp(0.142383rem, calc(0.102288rem + 0.225533vw), 0.282715rem);--border-width-4: clamp(0.160181rem, calc(0.09173rem + 0.385036vw), 0.399758rem);--border-width-5: clamp(0.180203rem, calc(0.070187rem + 0.618839vw), 0.565258rem)}:root{--border-radius--2: clamp(0.08rem, calc(0.011429rem + 0.385714vw), 0.32rem);--border-radius--1: clamp(0.1rem, calc(0.014286rem + 0.482143vw), 0.4rem);--border-radius-0: clamp(0.125rem, calc(0.017857rem + 0.602679vw), 0.5rem);--border-radius-1: clamp(0.15625rem, calc(0.022321rem + 0.753348vw), 0.625rem);--border-radius-2: clamp(0.195312rem, calc(0.027902rem + 0.941685vw), 0.78125rem);--border-radius-3: clamp(0.244141rem, calc(0.034877rem + 1.177107vw), 0.976562rem);--border-radius-4: clamp(0.305176rem, calc(0.043597rem + 1.471383vw), 1.220703rem);--border-radius-5: clamp(0.38147rem, calc(0.054496rem + 1.839229vw), 1.525879rem);--border-radius-6: clamp(0.476837rem, calc(0.06812rem + 2.299036vw), 1.907349rem);--border-radius-1-2: clamp(0.351562rem, calc(0.050223rem + 1.695033vw), 1.40625rem);--border-radius-2-3: clamp(0.439453rem, calc(0.062779rem + 2.118792vw), 1.757812rem);--border-radius-3-4: clamp(0.549316rem, calc(0.078474rem + 2.64849vw), 2.197266rem);--border-radius-4-5: clamp(0.686646rem, calc(0.098092rem + 3.310612vw), 2.746582rem);--border-radius-5-6: clamp(0.858307rem, calc(0.122615rem + 4.138265vw), 3.433228rem)}:root{--radius-blob-1: 55% 30% 56% 33% / 30% 55% 30% 55%;--radius-blob-2: 34% 30% 60% 60% / 60% 58% 36% 35%;--radius-blob-3: 39% 64% 35% 62% / 61% 32% 62% 33%;--radius-blob-4: 34% 34% 37% 68% / 66% 67% 66% 38%;--radius-blob-5: 69% 36% 39% 38% / 43% 70% 70% 70%;--radius-drawn-1: 0.625rem 8.438rem 0.625rem 8.125rem / 7.813rem 1.563rem 8.75rem 0.625rem;--radius-drawn-2: 9.063rem 2.813rem 2.188rem 9.375rem / 0.625rem 9.063rem 8.438rem 1.25rem;--radius-drawn-3: 11.563rem 2.188rem 10rem 0.625rem / 1.875rem 11.563rem 2.813rem 11.563rem;--radius-drawn-4: 13.125rem 0.625rem 2.5rem 1.25rem / 1.563rem 12.813rem 12.188rem 12.188rem;--radius-drawn-5: 2.188rem 14.375rem 2.188rem 1.25rem / 14.063rem 2.188rem 14.063rem 14.688rem;--radius-drawn-6: 4.688rem 15.938rem 4.063rem 3.125rem / 15.938rem 1.563rem 15.938rem 15.938rem}:root{--anim-duration-base: calc(calc(0.18 + 0.06 * clamp(0, calc((100vw - 320px) / (1440px - 320px)), 1)) * 1s);--anim-scale-base: 1;--anim-distance-0: calc(calc(0.25 + 0.35 * clamp(0, calc((100vw - 320px) / (1440px - 320px)), 1)) * 1rem);--anim-distance-1: calc(calc(0.28125 + 0.46875 * clamp(0, calc((100vw - 320px) / (1440px - 320px)), 1)) * 1rem);--anim-distance-2: calc(calc(0.316406 + 0.621094 * clamp(0, calc((100vw - 320px) / (1440px - 320px)), 1)) * 1rem);--anim-distance-3: calc(calc(0.355957 + 0.815918 * clamp(0, calc((100vw - 320px) / (1440px - 320px)), 1)) * 1rem);--anim-distance-4: calc(calc(0.400452 + 1.064392 * clamp(0, calc((100vw - 320px) / (1440px - 320px)), 1)) * 1rem);--anim-duration--2: calc(calc(0.105403 + 0.033486 * clamp(0, calc((100vw - 320px) / (1440px - 320px)), 1)) * 1s);--anim-duration--1: calc(calc(0.112465 + 0.054202 * clamp(0, calc((100vw - 320px) / (1440px - 320px)), 1)) * 1s);--anim-duration-0: calc(calc(0.12 + 0.08 * clamp(0, calc((100vw - 320px) / (1440px - 320px)), 1)) * 1s);--anim-duration-1: calc(calc(0.12804 + 0.11196 * clamp(0, calc((100vw - 320px) / (1440px - 320px)), 1)) * 1s);--anim-duration-2: calc(calc(0.136619 + 0.151381 * clamp(0, calc((100vw - 320px) / (1440px - 320px)), 1)) * 1s);--anim-duration-3: calc(calc(0.145772 + 0.199828 * clamp(0, calc((100vw - 320px) / (1440px - 320px)), 1)) * 1s);--anim-duration-4: calc(calc(0.155539 + 0.259181 * clamp(0, calc((100vw - 320px) / (1440px - 320px)), 1)) * 1s);--anim-opacity-0: calc(0.2 + 0.4 * clamp(0, calc((100vw - 320px) / (1440px - 320px)), 1));--anim-opacity-1: calc(0.2134 + 0.4268 * clamp(0, calc((100vw - 320px) / (1440px - 320px)), 1));--anim-opacity-2: calc(0.227698 + 0.455396 * clamp(0, calc((100vw - 320px) / (1440px - 320px)), 1));--anim-rotate--1: calc(calc(3.748828 + 2.917838 * clamp(0, calc((100vw - 320px) / (1440px - 320px)), 1)) * 1deg);--anim-rotate-0: calc(calc(4 + 4 * clamp(0, calc((100vw - 320px) / (1440px - 320px)), 1)) * 1deg);--anim-rotate-1: calc(calc(4.268 + 5.332 * clamp(0, calc((100vw - 320px) / (1440px - 320px)), 1)) * 1deg);--anim-rotate-2: calc(calc(4.553956 + 6.966044 * clamp(0, calc((100vw - 320px) / (1440px - 320px)), 1)) * 1deg);--anim-scale--2: calc(0.834439 + 0.061485 * clamp(0, calc((100vw - 320px) / (1440px - 320px)), 1));--anim-scale--1: calc(0.890347 + 0.065604 * clamp(0, calc((100vw - 320px) / (1440px - 320px)), 1));--anim-scale-0: calc(0.95 + 0.07 * clamp(0, calc((100vw - 320px) / (1440px - 320px)), 1));--anim-scale-1: calc(1.01365 + 0.07469 * clamp(0, calc((100vw - 320px) / (1440px - 320px)), 1));--anim-scale-2: calc(1.081565 + 0.079694 * clamp(0, calc((100vw - 320px) / (1440px - 320px)), 1));--anim-distance-none: 0rem;--anim-distance-xs: calc(calc(0.125 + 0.075 * clamp(0, calc((100vw - 320px) / (1440px - 320px)), 1)) * 1rem);--anim-distance-sm: calc(calc(0.25 + 0.1 * clamp(0, calc((100vw - 320px) / (1440px - 320px)), 1)) * 1rem);--anim-distance-md: calc(calc(0.5 + 0.2 * clamp(0, calc((100vw - 320px) / (1440px - 320px)), 1)) * 1rem);--anim-distance-lg: calc(calc(0.75 + 0.25 * clamp(0, calc((100vw - 320px) / (1440px - 320px)), 1)) * 1rem);--anim-distance-xl: calc(calc(1.25 + 0.35 * clamp(0, calc((100vw - 320px) / (1440px - 320px)), 1)) * 1rem);--anim-duration-none: 0s;--anim-duration-instant: 0.01s;--anim-duration-fast: calc(calc(0.12 + 0.06 * clamp(0, calc((100vw - 320px) / (1440px - 320px)), 1)) * 1s);--anim-duration-slow: calc(calc(0.28 + 0.08 * clamp(0, calc((100vw - 320px) / (1440px - 320px)), 1)) * 1s);--anim-ease-standard: cubic-bezier(0.4, 0, 0.2, 1);--anim-ease-emphasized: cubic-bezier(0.2, 0, 0, 1);--anim-ease-entrance: cubic-bezier(0.16, 1, 0.3, 1);--anim-ease-bounce: linear(0, 0.011 2.9%, 0.045 5.8%, 0.1 8.6%, 0.176 11.5%, 0.273 14.4%, 0.391 17.3%, 0.53 20.2%, 0.69 23%, 0.871 25.9%, 1.068 28.8%, 1.156 30.3%, 1.171 30.8%, 1.171 31.6%, 1.163 32.4%, 1.138 33.3%, 1.017 36.7%, 0.977 38.1%, 0.945 39.5%, 0.93 40.9%, 0.928 42.4%, 0.937 43.8%, 0.958 45.2%, 1.075 49.2%, 1.101 50.8%, 1.11 52.1%, 1.105 53.4%, 1.062 56.2%, 1.017 58.1%, 0.991 60.2%, 0.98 62.7%, 0.985 65.2%, 1 68.1%, 1.036 74.2%, 1.046 77.4%, 1.044 80.4%, 1.028 83.5%, 1.001 86.9%, 0.999 99.9%);--anim-ease-shake: linear(0, 0.5 12%, 0.25 22%, 0.75 32%, 0.35 42%, 0.65 52%, 0.43 62%, 0.57 72%, 0.48 82%, 0.52 90%, 1);--anim-ease-heartbeat: linear(0, 0.38 14%, 0.7 28%, 0.46 36%, 0.84 50%, 0.62 60%, 0.9 74%, 1);--anim-ease-pulse: cubic-bezier(0.42, 0, 0.58, 1);--anim-ease-linear: linear;--anim-opacity-transparent: 0;--anim-opacity-muted: calc(0.4 + 0.2 * clamp(0, calc((100vw - 320px) / (1440px - 320px)), 1));--anim-opacity-solid: 1;--anim-rotate-none: 0deg;--anim-rotate-xs: calc(calc(1 + 1 * clamp(0, calc((100vw - 320px) / (1440px - 320px)), 1)) * 1deg);--anim-rotate-sm: calc(calc(3 + 2 * clamp(0, calc((100vw - 320px) / (1440px - 320px)), 1)) * 1deg);--anim-rotate-md: calc(calc(8 + 4 * clamp(0, calc((100vw - 320px) / (1440px - 320px)), 1)) * 1deg);--anim-rotate-lg: calc(calc(15 + 5 * clamp(0, calc((100vw - 320px) / (1440px - 320px)), 1)) * 1deg);--anim-rotate-neg-xs: calc(calc(-2 + 1 * clamp(0, calc((100vw - 320px) / (1440px - 320px)), 1)) * 1deg);--anim-rotate-neg-sm: calc(calc(-5 + 2 * clamp(0, calc((100vw - 320px) / (1440px - 320px)), 1)) * 1deg);--anim-rotate-neg-md: calc(calc(-12 + 4 * clamp(0, calc((100vw - 320px) / (1440px - 320px)), 1)) * 1deg);--anim-scale-down-strong: calc(0.88 + 0.04 * clamp(0, calc((100vw - 320px) / (1440px - 320px)), 1));--anim-scale-down: calc(0.96 + 0.02 * clamp(0, calc((100vw - 320px) / (1440px - 320px)), 1));--anim-scale-up: calc(1.03 + 0.03 * clamp(0, calc((100vw - 320px) / (1440px - 320px)), 1));--anim-scale-up-strong: calc(1.08 + 0.04 * clamp(0, calc((100vw - 320px) / (1440px - 320px)), 1))}.animate{animation-duration:var(--anim-duration-base);animation-delay:var(--anim-duration-none);animation-timing-function:var(--anim-ease-standard);animation-iteration-count:1;animation-fill-mode:both;animation-direction:normal;animation-play-state:running}@media(prefers-reduced-motion:reduce){.animate{animation-duration:var(--anim-duration-instant);animation-delay:var(--anim-duration-none);animation-timing-function:var(--anim-ease-linear);animation-iteration-count:1}}:root{--shadow--4: inset 0 1px 9px 0 rgb(9 19 18 / 0.347), inset 0 -.5px 0 0 #fff, inset 0 .5px 0 0 #0001;--shadow--4: inset 0 1px 9px 0 rgb(9 19 18 / 0.347), inset 0 -.5px 0 0 oklch(100% 0 89.876), inset 0 .5px 0 0 oklch(0% 0 0 / 0.067);--shadow--3: inset 0 1px 8px 0 rgb(9 19 18 / 0.347), inset 0 -.5px 0 0 #fff, inset 0 .5px 0 0 #0001;--shadow--3: inset 0 1px 8px 0 rgb(9 19 18 / 0.347), inset 0 -.5px 0 0 oklch(100% 0 89.876), inset 0 .5px 0 0 oklch(0% 0 0 / 0.067);--shadow--2: inset 0 1px 7px 0 rgb(9 19 18 / 0.347), inset 0 -.5px 0 0 #fff, inset 0 .5px 0 0 #0001;--shadow--2: inset 0 1px 7px 0 rgb(9 19 18 / 0.347), inset 0 -.5px 0 0 oklch(100% 0 89.876), inset 0 .5px 0 0 oklch(0% 0 0 / 0.067);--shadow--1: inset 0 1px 5px 0 rgb(9 19 18 / 0.347), inset 0 -.5px 0 0 #fff, inset 0 .5px 0 0 #0001;--shadow--1: inset 0 1px 5px 0 rgb(9 19 18 / 0.347), inset 0 -.5px 0 0 oklch(100% 0 89.876), inset 0 .5px 0 0 oklch(0% 0 0 / 0.067);--shadow-0: none;--shadow-1: 0 1px 3px -3px rgb(9 19 18 / 0.325);--shadow-2: 0 1px 3px -3px rgb(9 19 18 / 0.293), 0 2px 4px -3px rgb(9 19 18 / 0.321);--shadow-3: 0 1px 3px -3px rgb(9 19 18 / 0.274), 0 1px 3px -3px rgb(9 19 18 / 0.285), 0 2px 4px -3px rgb(9 19 18 / 0.296), 0 3px 5px -3px rgb(9 19 18 / 0.306), 0 7px 8px -3px rgb(9 19 18 / 0.317);--shadow-4: 0 1px 3px -3px rgb(9 19 18 / 0.27), 0 1px 3px -3px rgb(9 19 18 / 0.279), 0 2px 4px -3px rgb(9 19 18 / 0.288), 0 4px 6px -3px rgb(9 19 18 / 0.297), 0 9px 1px -2px rgb(9 19 18 / 0.306), 0 2px 18px -2px rgb(9 19 18 / 0.314);--shadow-5: 0 1px 3px -3px rgb(9 19 18 / 0.268), 0 1px 3px -3px rgb(9 19 18 / 0.277), 0 3px 5px -3px rgb(9 19 18 / 0.286), 0 8px 9px -2px rgb(9 19 18 / 0.294), 0 21px 19px -2px rgb(9 19 18 / 0.303), 0 46px 39px -2px rgb(9 19 18 / 0.312);--shadow-6: 0 1px 3px -3px rgb(9 19 18 / 0.265), 0 1px 3px -3px rgb(9 19 18 / 0.272), 0 3px 5px -3px rgb(9 19 18 / 0.28), 0 8px 9px -2px rgb(9 19 18 / 0.287), 0 21px 19px -2px rgb(9 19 18 / 0.295), 0 47px 4px -2px rgb(9 19 18 / 0.302), 0 93px 77px -1px rgb(9 19 18 / 0.31)}:root.dark{--shadow--4: inset 0 1px 9px 0 rgb(9 19 18 / 0.347), inset 0 -.5px 0 0 #fff, inset 0 .5px 0 0 #0001;--shadow--4: inset 0 1px 9px 0 rgb(9 19 18 / 0.347), inset 0 -.5px 0 0 oklch(100% 0 89.876), inset 0 .5px 0 0 oklch(0% 0 0 / 0.067);--shadow--3: inset 0 1px 8px 0 rgb(9 19 18 / 0.347), inset 0 -.5px 0 0 #fff, inset 0 .5px 0 0 #0001;--shadow--3: inset 0 1px 8px 0 rgb(9 19 18 / 0.347), inset 0 -.5px 0 0 oklch(100% 0 89.876), inset 0 .5px 0 0 oklch(0% 0 0 / 0.067);--shadow--2: inset 0 1px 7px 0 rgb(9 19 18 / 0.347), inset 0 -.5px 0 0 #fff, inset 0 .5px 0 0 #0001;--shadow--2: inset 0 1px 7px 0 rgb(9 19 18 / 0.347), inset 0 -.5px 0 0 oklch(100% 0 89.876), inset 0 .5px 0 0 oklch(0% 0 0 / 0.067);--shadow--1: inset 0 1px 5px 0 rgb(9 19 18 / 0.347), inset 0 -.5px 0 0 #fff, inset 0 .5px 0 0 #0001;--shadow--1: inset 0 1px 5px 0 rgb(9 19 18 / 0.347), inset 0 -.5px 0 0 oklch(100% 0 89.876), inset 0 .5px 0 0 oklch(0% 0 0 / 0.067);--shadow-0: none;--shadow-1: 0 1px 3px -3px rgb(9 19 18 / 0.325);--shadow-2: 0 1px 3px -3px rgb(9 19 18 / 0.293), 0 2px 4px -3px rgb(9 19 18 / 0.321);--shadow-3: 0 1px 3px -3px rgb(9 19 18 / 0.274), 0 1px 3px -3px rgb(9 19 18 / 0.285), 0 2px 4px -3px rgb(9 19 18 / 0.296), 0 3px 5px -3px rgb(9 19 18 / 0.306), 0 7px 8px -3px rgb(9 19 18 / 0.317);--shadow-4: 0 1px 3px -3px rgb(9 19 18 / 0.27), 0 1px 3px -3px rgb(9 19 18 / 0.279), 0 2px 4px -3px rgb(9 19 18 / 0.288), 0 4px 6px -3px rgb(9 19 18 / 0.297), 0 9px 1px -2px rgb(9 19 18 / 0.306), 0 2px 18px -2px rgb(9 19 18 / 0.314);--shadow-5: 0 1px 3px -3px rgb(9 19 18 / 0.268), 0 1px 3px -3px rgb(9 19 18 / 0.277), 0 3px 5px -3px rgb(9 19 18 / 0.286), 0 8px 9px -2px rgb(9 19 18 / 0.294), 0 21px 19px -2px rgb(9 19 18 / 0.303), 0 46px 39px -2px rgb(9 19 18 / 0.312);--shadow-6: 0 1px 3px -3px rgb(9 19 18 / 0.265), 0 1px 3px -3px rgb(9 19 18 / 0.272), 0 3px 5px -3px rgb(9 19 18 / 0.28), 0 8px 9px -2px rgb(9 19 18 / 0.287), 0 21px 19px -2px rgb(9 19 18 / 0.295), 0 47px 4px -2px rgb(9 19 18 / 0.302), 0 93px 77px -1px rgb(9 19 18 / 0.31)}:root{--gradient-brand-surface: linear-gradient(135deg, color-mix(in srgb, var(--primary-6) 100%, var(--secondary-6) 0%) 0%, color-mix(in srgb, var(--primary-6) 88%, var(--secondary-6) 12%) 12%, color-mix(in srgb, var(--primary-6) 75%, var(--secondary-6) 25%) 25%, color-mix(in srgb, var(--primary-6) 62%, var(--secondary-6) 38%) 38%, color-mix(in srgb, var(--primary-6) 50%, var(--secondary-6) 50%) 50%, color-mix(in srgb, var(--primary-6) 38%, var(--secondary-6) 62%) 62%, color-mix(in srgb, var(--primary-6) 25%, var(--secondary-6) 75%) 75%, color-mix(in srgb, var(--primary-6) 12%, var(--secondary-6) 88%) 88%, color-mix(in srgb, var(--primary-6) 0%, var(--secondary-6) 100%) 100%);--gradient-brand-diagonal: linear-gradient(315deg, color-mix(in srgb, var(--primary-6) 100%, var(--secondary-5) 0%) 0%, color-mix(in srgb, var(--primary-6) 88%, var(--secondary-5) 12%) 12%, color-mix(in srgb, var(--primary-6) 75%, var(--secondary-5) 25%) 25%, color-mix(in srgb, var(--primary-6) 62%, var(--secondary-5) 38%) 38%, color-mix(in srgb, var(--primary-6) 50%, var(--secondary-5) 50%) 50%, color-mix(in srgb, var(--primary-6) 38%, var(--secondary-5) 62%) 62%, color-mix(in srgb, var(--primary-6) 25%, var(--secondary-5) 75%) 75%, color-mix(in srgb, var(--primary-6) 12%, var(--secondary-5) 88%) 88%, color-mix(in srgb, var(--primary-6) 0%, var(--secondary-5) 100%) 100%);--gradient-vapor: linear-gradient(135deg, color-mix(in srgb, var(--primary-6) 100%, var(--tertiary-6) 0%) 0%, color-mix(in srgb, var(--primary-6) 88%, var(--tertiary-6) 12%) 6%, color-mix(in srgb, var(--primary-6) 75%, var(--tertiary-6) 25%) 12%, color-mix(in srgb, var(--primary-6) 62%, var(--tertiary-6) 38%) 19%, color-mix(in srgb, var(--primary-6) 50%, var(--tertiary-6) 50%) 25%, color-mix(in srgb, var(--primary-6) 38%, var(--tertiary-6) 62%) 31%, color-mix(in srgb, var(--primary-6) 25%, var(--tertiary-6) 75%) 38%, color-mix(in srgb, var(--primary-6) 12%, var(--tertiary-6) 88%) 44%, color-mix(in srgb, var(--primary-6) 0%, var(--tertiary-6) 100%) 50%, color-mix(in srgb, var(--tertiary-6) 100%, var(--secondary-6) 0%) 50%, color-mix(in srgb, var(--tertiary-6) 88%, var(--secondary-6) 12%) 56%, color-mix(in srgb, var(--tertiary-6) 75%, var(--secondary-6) 25%) 62%, color-mix(in srgb, var(--tertiary-6) 62%, var(--secondary-6) 38%) 69%, color-mix(in srgb, var(--tertiary-6) 50%, var(--secondary-6) 50%) 75%, color-mix(in srgb, var(--tertiary-6) 38%, var(--secondary-6) 62%) 81%, color-mix(in srgb, var(--tertiary-6) 25%, var(--secondary-6) 75%) 88%, color-mix(in srgb, var(--tertiary-6) 12%, var(--secondary-6) 88%) 94%, color-mix(in srgb, var(--tertiary-6) 0%, var(--secondary-6) 100%) 100%);--gradient-spotlight: radial-gradient(circle, color-mix(in srgb, var(--primary-5) 100%, var(--neutral-2) 0%) 0%, color-mix(in srgb, var(--primary-5) 88%, var(--neutral-2) 12%) 12%, color-mix(in srgb, var(--primary-5) 75%, var(--neutral-2) 25%) 25%, color-mix(in srgb, var(--primary-5) 62%, var(--neutral-2) 38%) 38%, color-mix(in srgb, var(--primary-5) 50%, var(--neutral-2) 50%) 50%, color-mix(in srgb, var(--primary-5) 38%, var(--neutral-2) 62%) 62%, color-mix(in srgb, var(--primary-5) 25%, var(--neutral-2) 75%) 75%, color-mix(in srgb, var(--primary-5) 12%, var(--neutral-2) 88%) 88%, color-mix(in srgb, var(--primary-5) 0%, var(--neutral-2) 100%) 100%);--gradient-qualitative-sweep: conic-gradient(from 0deg, color-mix(in srgb, var(--chart-qualitative-1) 50%, var(--chart-qualitative-2) 50%) 0%, color-mix(in srgb, var(--chart-qualitative-1) 50%, var(--chart-qualitative-2) 50%) 9%, color-mix(in srgb, var(--chart-qualitative-2) 50%, var(--chart-qualitative-3) 50%) 9%, color-mix(in srgb, var(--chart-qualitative-2) 50%, var(--chart-qualitative-3) 50%) 18%, color-mix(in srgb, var(--chart-qualitative-3) 50%, var(--chart-qualitative-4) 50%) 18%, color-mix(in srgb, var(--chart-qualitative-3) 50%, var(--chart-qualitative-4) 50%) 27%, color-mix(in srgb, var(--chart-qualitative-4) 50%, var(--chart-qualitative-5) 50%) 27%, color-mix(in srgb, var(--chart-qualitative-4) 50%, var(--chart-qualitative-5) 50%) 36%, color-mix(in srgb, var(--chart-qualitative-5) 50%, var(--chart-qualitative-6) 50%) 36%, color-mix(in srgb, var(--chart-qualitative-5) 50%, var(--chart-qualitative-6) 50%) 45%, color-mix(in srgb, var(--chart-qualitative-6) 50%, var(--chart-qualitative-7) 50%) 45%, color-mix(in srgb, var(--chart-qualitative-6) 50%, var(--chart-qualitative-7) 50%) 54%, color-mix(in srgb, var(--chart-qualitative-7) 50%, var(--chart-qualitative-8) 50%) 54%, color-mix(in srgb, var(--chart-qualitative-7) 50%, var(--chart-qualitative-8) 50%) 63%, color-mix(in srgb, var(--chart-qualitative-8) 50%, var(--chart-qualitative-9) 50%) 63%, color-mix(in srgb, var(--chart-qualitative-8) 50%, var(--chart-qualitative-9) 50%) 72%, color-mix(in srgb, var(--chart-qualitative-9) 50%, var(--chart-qualitative-10) 50%) 72%, color-mix(in srgb, var(--chart-qualitative-9) 50%, var(--chart-qualitative-10) 50%) 81%, color-mix(in srgb, var(--chart-qualitative-10) 50%, var(--chart-qualitative-11) 50%) 81%, color-mix(in srgb, var(--chart-qualitative-10) 50%, var(--chart-qualitative-11) 50%) 90%, color-mix(in srgb, var(--chart-qualitative-11) 50%, var(--chart-qualitative-12) 50%) 90%, color-mix(in srgb, var(--chart-qualitative-11) 50%, var(--chart-qualitative-12) 50%) 100%);--gradient-neutral-soft: linear-gradient(180deg, var(--neutral-variant-5) 0%, var(--neutral-variant-7) 100%);--gradient-striped: repeating-linear-gradient(45deg, color-mix(in srgb, var(--neutral-6) 100%, var(--neutral-6) 0%) 0%, color-mix(in srgb, var(--neutral-6) 88%, var(--neutral-6) 12%) 1%, color-mix(in srgb, var(--neutral-6) 75%, var(--neutral-6) 25%) 2%, color-mix(in srgb, var(--neutral-6) 62%, var(--neutral-6) 38%) 3%, color-mix(in srgb, var(--neutral-6) 50%, var(--neutral-6) 50%) 4%, color-mix(in srgb, var(--neutral-6) 38%, var(--neutral-6) 62%) 5%, color-mix(in srgb, var(--neutral-6) 25%, var(--neutral-6) 75%) 6%, color-mix(in srgb, var(--neutral-6) 12%, var(--neutral-6) 88%) 7%, color-mix(in srgb, var(--neutral-6) 0%, var(--neutral-6) 100%) 8%, color-mix(in srgb, var(--secondary-6) 100%, var(--secondary-6) 0%) 8%, color-mix(in srgb, var(--secondary-6) 88%, var(--secondary-6) 12%) 9%, color-mix(in srgb, var(--secondary-6) 75%, var(--secondary-6) 25%) 10%, color-mix(in srgb, var(--secondary-6) 62%, var(--secondary-6) 38%) 11%, color-mix(in srgb, var(--secondary-6) 50%, var(--secondary-6) 50%) 12%, color-mix(in srgb, var(--secondary-6) 38%, var(--secondary-6) 62%) 13%, color-mix(in srgb, var(--secondary-6) 25%, var(--secondary-6) 75%) 14%, color-mix(in srgb, var(--secondary-6) 12%, var(--secondary-6) 88%) 15%, color-mix(in srgb, var(--secondary-6) 0%, var(--secondary-6) 100%) 16%);--gradient-diverging: linear-gradient(120deg, color-mix(in srgb, var(--secondary-6) 100%, var(--error-6) 0%) 0%, color-mix(in srgb, var(--secondary-6) 88%, var(--error-6) 12%) 12%, color-mix(in srgb, var(--secondary-6) 75%, var(--error-6) 25%) 25%, color-mix(in srgb, var(--secondary-6) 62%, var(--error-6) 38%) 38%, color-mix(in srgb, var(--secondary-6) 50%, var(--error-6) 50%) 50%, color-mix(in srgb, var(--secondary-6) 38%, var(--error-6) 62%) 62%, color-mix(in srgb, var(--secondary-6) 25%, var(--error-6) 75%) 75%, color-mix(in srgb, var(--secondary-6) 12%, var(--error-6) 88%) 88%, color-mix(in srgb, var(--secondary-6) 0%, var(--error-6) 100%) 100%)} diff --git a/www-next-gen/assets/vt-tuner.js b/www-next-gen/assets/vt-tuner.js new file mode 100644 index 0000000..bb21416 --- /dev/null +++ b/www-next-gen/assets/vt-tuner.js @@ -0,0 +1,138 @@ +// View-transition tuner: a small dialog to play with the page-transition dials. +// Settings persist in localStorage and are applied to on every page load +// (this script lives in ), so cross-document navigations use them. +(function () { + var KEY = "vt-tuner"; + var EASES = { + standard: "var(--anim-ease-standard)", + emphasized: "var(--anim-ease-emphasized)", + entrance: "var(--anim-ease-entrance)", + bounce: "var(--anim-ease-bounce)", + linear: "linear", + }; + var STYLES = ["crossfade", "fadein", "slide"]; + var DEFAULTS = { enabled: true, duration: 120, ease: "standard", style: "crossfade", scope: false }; + + function load() { + try { + return Object.assign({}, DEFAULTS, JSON.parse(localStorage.getItem(KEY) || "{}")); + } catch (e) { + return Object.assign({}, DEFAULTS); + } + } + function save() { + localStorage.setItem(KEY, JSON.stringify(state)); + } + // the panel's open/closed state persists too, so it stays open as you navigate + // between pages to compare transitions. + var OPEN_KEY = "vt-tuner-open"; + function setOpen(v) { + try { + if (v) localStorage.setItem(OPEN_KEY, "1"); + else localStorage.removeItem(OPEN_KEY); + } catch (e) {} + } + function isOpen() { + return localStorage.getItem(OPEN_KEY) === "1"; + } + function apply() { + var r = document.documentElement; + r.style.setProperty("--vt-duration", state.duration + "ms"); + r.style.setProperty("--vt-ease", EASES[state.ease] || EASES.standard); + r.classList.toggle("vt-off", !state.enabled); + r.classList.toggle("vt-fadein", state.style === "fadein"); + r.classList.toggle("vt-slide", state.style === "slide"); + r.classList.toggle("vt-scope", !!state.scope); + } + + var state = load(); + apply(); // before paint + + function opts(list) { + return list + .map(function (k) { + return '"; + }) + .join(""); + } + + function build() { + if (document.getElementById("vt-tuner")) return; + var btn = document.getElementById("vt-tuner-btn"); + if (!btn) return; // the nav launcher is server-rendered + + var dlg = document.createElement("dialog"); + dlg.id = "vt-tuner"; + dlg.innerHTML = [ + "

Page transition

", + '
', + '
', + '
", + '
", + '
', + '

Keeps the nav fixed so only the content area transitions, not the whole page.

', + '

Dials apply to real page navigations. Open one to see it:

', + '', + '
', + ].join(""); + + document.body.appendChild(dlg); + + function sync() { + dlg.querySelectorAll("[data-k]").forEach(function (el) { + var k = el.dataset.k; + if (el.type === "checkbox") el.checked = !!state[k]; + else el.value = state[k]; + }); + var out = dlg.querySelector('[data-out="duration"]'); + if (out) out.textContent = state.duration + "ms"; + } + + dlg.addEventListener("input", function (e) { + var el = e.target; + if (!el.dataset || !el.dataset.k) return; + var k = el.dataset.k; + state[k] = el.type === "checkbox" ? el.checked : k === "duration" ? +el.value : el.value; + apply(); + save(); + sync(); + }); + dlg.addEventListener("click", function (e) { + var actEl = e.target.closest ? e.target.closest("[data-act]") : null; + var act = actEl ? actEl.dataset.act : null; + if (act === "close") dlg.close(); + if (act === "reset") { + state = Object.assign({}, DEFAULTS); + apply(); + save(); + sync(); + } + }); + function openPanel() { + sync(); + setOpen(true); + btn.setAttribute("aria-pressed", "true"); + if (!dlg.open) dlg.show(); // non-modal: no backdrop, so the page transition behind stays visible + } + dlg.addEventListener("close", function () { + setOpen(false); + btn.setAttribute("aria-pressed", "false"); + }); + document.addEventListener("keydown", function (e) { + if (e.key === "Escape" && dlg.open) dlg.close(); + }); + // light dismiss: click anywhere outside the panel (but not the launcher) closes it + document.addEventListener("click", function (e) { + if (dlg.open && !dlg.contains(e.target) && !btn.contains(e.target)) dlg.close(); + }); + btn.addEventListener("click", function () { + if (dlg.open) dlg.close(); + else openPanel(); + }); + sync(); + if (isOpen()) openPanel(); + } + + if (document.readyState === "loading") document.addEventListener("DOMContentLoaded", build); + else build(); +})(); diff --git a/www-next-gen/demo-banner.txt b/www-next-gen/demo-banner.txt new file mode 100644 index 0000000..573bc7d --- /dev/null +++ b/www-next-gen/demo-banner.txt @@ -0,0 +1,5 @@ + + __ , + .--()°'.' pid 1516765 · http://127.0.0.1:3001 · 23ms 💜 +'|, . ,' Wed, 17 Jun 2026 23:37:38 +0000 + !_-(_\ nu 0.113.1 \ No newline at end of file diff --git a/www-next-gen/how-tos/render-readme-as-doc-site.md b/www-next-gen/how-tos/render-readme-as-doc-site.md new file mode 100644 index 0000000..fd7be2d --- /dev/null +++ b/www-next-gen/how-tos/render-readme-as-doc-site.md @@ -0,0 +1,76 @@ +# Render a README as a doc site + +Your `README.md` is already the story of your project. http-nu turns it into a +documentation site as rich as [Astro Starlight](https://starlight.astro.build), +with much less fuss. The whole job is two pieces: `.md`, which renders Markdown to +HTML with syntax-highlighted code blocks, and a handful of lines of Nushell to +route it. + +This page is one of those Markdown files, rendered exactly the way it describes. + +## The smallest doc site + +One route, the README rendered to HTML: + +```nushell +const readme = (open --raw README.md | decode utf-8) + +{|req| $readme | .md } +``` + +```bash +http-nu :3001 serve.nu +``` + +Open it and you have a readable page: headings, lists, links, and code blocks +colored by `.md`, which highlights `bash`, `nushell`, `rust`, and more via +TextMate grammars. No build step, no `node_modules`. + +## Split it into pages + +A long README scrolls forever. Break it into navigable pages by heading, and +route each one by its slug: + +```nushell +const readme = (open --raw README.md | decode utf-8) + +# one page per "## " section +let pages = ($readme | split row "\n## " | skip 1 | each {|s| + let title = ($s | lines | first) + { slug: ($title | str downcase | str replace --all " " "-") + body: $"## ($s)" } +}) + +{|req| + let slug = ($req.path | str trim --left --char "/") + let page = ($pages | where slug == $slug | get 0?) + if $page == null { "not found" } else { $page.body | .md } +} +``` + +Add a sidebar that lists the sections and you have navigation. The README stays +the single source of truth; you just slice it. + +> The [Reference](/reference) section of this site is the code above, running: +> the [http-nu README](https://github.com/cablehead/http-nu/blob/main/README.md), +> sliced one page per heading, with a sidebar of slugs and a prev/next pager. +> Click through it, then come back here, the code reads like a description of +> what you just used. + +## Typography + +`.md` gives you semantic HTML, so style raw tags once and every page looks good +with no per-page classes. This site uses stellar tokens plus a small base layer, +but bring whatever CSS you like. + +## Beyond Markdown + +When a page is rendered from data rather than static prose, reach for `.mj` +(minijinja templates). You can poke at both `.md` and `.mj` in the +[Templates theme](/themes/templates). + +## The result + +This site. The [Reference](/reference) is this project's own README, rendered; +the themes wrap interactive toys around it. A README, a few lines of Nushell, and +you are done. diff --git a/www-next-gen/how-tos/reverse-proxy-a-backend.md b/www-next-gen/how-tos/reverse-proxy-a-backend.md new file mode 100644 index 0000000..0dcf101 --- /dev/null +++ b/www-next-gen/how-tos/reverse-proxy-a-backend.md @@ -0,0 +1,57 @@ +# Reverse-proxy a backend + +Put http-nu in front of an existing service to add routing, headers, or path +rewriting without touching it. `.reverse-proxy` forwards the request and returns +the backend's response. + +## The whole thing + +```bash +http-nu :3001 -c '{|req| .reverse-proxy "http://localhost:8080"}' +``` + +Every request is forwarded to the backend and its response comes straight back. +When `.reverse-proxy` is first in the closure, the original request body is +forwarded too. + +## As an API gateway + +Strip a public prefix before forwarding, so `/api/v1/users` reaches the backend +as `/users`: + +```bash +http-nu :3001 -c '{|req| + .reverse-proxy "http://localhost:8080" { + strip_prefix: "/api/v1" + } +}' +``` + +## Add headers, rewrite the query + +The optional config record carries headers, host handling, and query rewrites: + +```nushell +{|req| + .reverse-proxy "http://backend:8080" { + headers: { "X-API-Key": "secret123" } + query: ($req.query | upsert "context-id" "smidgeons" | reject "debug") + } +} +``` + +## Proxy some paths, serve others + +http-nu is a full server, so proxy the API and answer everything else yourself: + +```nushell +{|req| + if ($req.path | str starts-with "/api/") { + .reverse-proxy "http://localhost:8080" { strip_prefix: "/api" } + } else { + "hello from http-nu" + } +} +``` + +See the [Reference](/reference/reverse-proxy) for every option. diff --git a/www-next-gen/how-tos/serve-a-single-page-app.md b/www-next-gen/how-tos/serve-a-single-page-app.md new file mode 100644 index 0000000..e9944e0 --- /dev/null +++ b/www-next-gen/how-tos/serve-a-single-page-app.md @@ -0,0 +1,48 @@ +# Serve a single-page app + +A single-page app ships one `index.html` and lets the client router handle the +rest. The catch with static serving is deep links: a request for `/dashboard` +has no file on disk, so it 404s on refresh. http-nu's `.static` has a +`--fallback` for exactly this. + +## Static files + +Serve a directory, mapping the request path to a file: + +```bash +http-nu :3001 -c '{|req| .static "./dist" $req.path}' +``` + +`.static` infers the content type from the file extension and ignores anything +else the closure returns. + +## The SPA fallback + +Add `--fallback` so any path that does not match a file serves your app shell +instead of returning 404: + +```bash +http-nu :3001 -c '{|req| .static "./dist" $req.path --fallback "index.html"}' +``` + +Now `/`, `/dashboard`, and `/users/42` all return `index.html`, the client +router takes it from there, and real assets like `/app.js` and `/style.css` +still serve directly. + +## Mixing with an API + +Routes compose, so serve the app and an API from one closure: + +```nushell +use http-nu/router * + +{|req| + dispatch $req [ + (route {path-matches: "/api/health"} {|req ctx| {ok: true} }) + (route true {|req ctx| .static "./dist" $req.path --fallback "index.html" }) + ] +} +``` + +API routes win; everything else falls through to the app shell. See the +[Reference](/reference/serving--operations) for the rest of `.static`. diff --git a/www-next-gen/iosevka-x.build-plan.toml b/www-next-gen/iosevka-x.build-plan.toml new file mode 100644 index 0000000..5b40746 --- /dev/null +++ b/www-next-gen/iosevka-x.build-plan.toml @@ -0,0 +1,38 @@ +# Iosevka X - docs/web build: monospace, no ligatures, regular + bold only, +# dotted zero for 0/O clarity, plus a few clean variant touches. +[buildPlans.iosevka-X] +family = "Iosevka X" +spacing = "normal" +serifs = "sans" +noLigation = true + +[buildPlans.iosevka-X.variants.design] +zero = 'dotted' +asterisk = 'penta-low' +tilde = 'low' +underscore = 'low' +brace = 'straight' +dollar = 'open' +four = 'closed-serifless' +eight = 'crossing' + +[buildPlans.iosevka-X.weights.Regular] +shape = 400 +menu = 400 +css = 400 + +[buildPlans.iosevka-X.weights.Bold] +shape = 700 +menu = 700 +css = 700 + +[buildPlans.iosevka-X.slopes.Upright] +angle = 0 +shape = "upright" +menu = "upright" +css = "normal" + +[buildPlans.iosevka-X.widths.Normal] +shape = 500 +menu = 5 +css = "normal" diff --git a/www-next-gen/readme.nu b/www-next-gen/readme.nu new file mode 100644 index 0000000..fc684a7 --- /dev/null +++ b/www-next-gen/readme.nu @@ -0,0 +1,178 @@ +# readme.nu - split one large markdown document (a README) into navigable +# doc pages, driven by nushell's `from md --verbose` AST instead of +# hand-maintained section files. +# +# The AST gives every heading's level and exact source line, so pages are +# sliced out of the raw text by line range: code fences can never be +# mistaken for headings, and the README stays the single source of truth. + +# GitHub-style anchor slug for a heading title. +export def slugify []: string -> string { + $in + | str downcase + | str replace -ra '[^a-z0-9 -]' '' + | str replace --all ' ' '-' +} + +# Collect the plain text of an AST node (headings contain nested link / +# code_inline / text children). +def node-text []: record -> string { + let node = $in + let own = ($node.attrs?.value? | default "") + let kids = ($node.children? | default [] | each { $in | node-text } | str join "") + $own + $kids +} + +# All headings in the document: {level, line, title, slug}. +export def headings []: string -> table { + $in + | from md --verbose + | where type =~ '^h[1-6]$' + | each {|h| + let title = ($h | node-text | str trim) + { + level: $h.attrs.level + line: $h.position.start.line + title: $title + slug: ($title | slugify) + } + } +} + +# Split the document into pages. +# +# $split lists heading slugs that act as groups: their child headings +# become pages of their own (recursively), instead of being folded into +# one page. Everything before the first heading-page (logo, badges, toc) +# is skipped. +# +# Returns: {slug, title, level, start, end, group} +# start/end are 1-based source line ranges, group is the slug of the +# enclosing group heading or null. +export def pages [split: list]: string -> table { + let text = $in + let total = ($text | lines | length) + let hs = ($text | headings) + + let result = ($hs | enumerate | reduce --fold {stack: [], pages: []} {|it, acc| + let h = $it.item + let idx = $it.index + + # pop ancestors at the same or deeper level + let stack = ($acc.stack | where level < $h.level) + let ancestors_are_groups = ($stack | all {|a| $a.is_group }) + let is_group = ($h.slug in $split) + + # section end: line before the next heading at the same or higher level + let next = ($hs | skip ($idx + 1) | where level <= $h.level | first | default null) + let section_end = (if $next == null { $total } else { $next.line - 1 }) + + let pages = (if (not $ancestors_are_groups) { + $acc.pages + } else if $is_group { + # group intro: lines between the group heading and its first child + let child = ($hs | skip ($idx + 1) | where line <= $section_end | first | default null) + let intro_end = (if $child == null { $section_end } else { $child.line - 1 }) + let has_body = ( + $text | lines | slice $h.line..($intro_end - 1) | any {|l| ($l | str trim) != "" } + ) + if $has_body { + $acc.pages | append { + slug: $h.slug, title: $h.title, level: $h.level + start: $h.line, end: $intro_end + group: ($stack | last | get slug? | default null) + } + } else { + $acc.pages + } + } else { + $acc.pages | append { + slug: $h.slug, title: $h.title, level: $h.level + start: $h.line, end: $section_end + group: ($stack | last | get slug? | default null) + } + }) + + { + stack: ($stack | append {level: $h.level, is_group: $is_group, slug: $h.slug}) + pages: $pages + } + }) + + $result.pages +} + +# Map every heading anchor to the page that renders it. +export def anchor-map [split: list]: string -> record { + let text = $in + let pgs = ($text | pages $split) + $text + | headings + | reduce --fold {} {|h, acc| + let owner = ($pgs | where {|p| $h.line >= $p.start and $h.line <= $p.end } | first | default null) + # duplicate heading titles: first occurrence keeps the plain slug, + # matching GitHub's anchor scheme + if $owner == null or ($h.slug in $acc) { $acc } else { $acc | insert $h.slug $owner.slug } + } +} + +# Rewrite the href attributes of already-rendered HTML: +# #anchor -> /base/#anchor (or left alone if the +# anchor is on this same page) +# relative/path -> $repo/relative/path (links into the source tree) +# Absolute http(s):// links and mailto: are left untouched. +def rewrite-links [anchors: record base: string repo: string]: string -> string { + $in | str replace -ra 'href="([^"]*)"' {|href| + let new = if ($href | str starts-with "#") { + let slug = ($href | str substring 1..) + let pg = ($anchors | get -o $slug) + if $pg == null { $href } else { $"($base)/($pg)#($slug)" } + } else if ($href =~ '^[a-z][a-z0-9+.-]*:') or ($href | str starts-with "/") or ($href | str starts-with "//") { + $href + } else { + $"($repo)/($href)" + } + $'href="($new)"' + } +} + +# Render one page to {__html}. Headings are rebased so the page heading +# becomes

, ids are injected for anchors, intra-document links are +# rewritten to their owning page, and relative file links point at $repo. +export def render-page [ + page: record + anchors: record # anchor slug -> page slug + --base: string = "/docs" + --repo: string = "https://github.com/cablehead/http-nu/blob/main" +]: string -> record { + let text = $in + let lines = ($text | lines) + let hs = ($text | headings | where line >= $page.start and line <= $page.end) + let shift = ($page.level - 1) + + let html = ($hs | enumerate | each {|it| + let h = $it.item + let seg_end = ($hs | get -o ($it.index + 1) | get -o line | default ($page.end + 1)) - 1 + let level = ([($h.level - $shift) 6] | math min) + let hashes = (1..$level | each { "#" } | str join "") + let heading_md = ($hashes + " " + ($lines | get ($h.line - 1) | str replace -ra '^#+\s*' '')) + let body_md = ($lines | slice $h.line..($seg_end - 1) | str join "\n") + + # render markdown, then post-process the resulting HTML: give the heading + # its anchor id and wrap its text in a self-link (so the title clicks + # through to its own location), then rewrite links by attribute - which + # also rebases this #slug self-link to its owning page. A heading that + # already holds a link of its own is left alone (no nested anchors). + let plain = (not ($heading_md =~ '\]\(')) + let open = $"" + (if $plain { $"" } else { "" }) + let close = (if $plain { "" } else { "" }) + $"" + + ($heading_md + "\n" + $body_md) + | .md | get __html + | str replace $"" $open + | str replace $"" $close + | rewrite-links $anchors $base $repo + } | str join "\n") + + {__html: $html} +} diff --git a/www-next-gen/stellar.config.json b/www-next-gen/stellar.config.json new file mode 100644 index 0000000..e82f91b --- /dev/null +++ b/www-next-gen/stellar.config.json @@ -0,0 +1,1312 @@ +{ + "$schema": "https://raw.githubusercontent.com/starfederation/stellar/main/internal/config/stellar.config.schema.json", + "version": 1, + "general": { + "viewport": { + "disabled": false, + "min": 320, + "max": 1440, + "baseFont": { + "mode": "multiplier", + "value": 1.125 + } + }, + "size": { + "disabled": false, + "baseMin": 0.5, + "baseMax": 1.1, + "scaleMin": "Minor Second", + "scaleMax": "Minor Third", + "negativeSteps": 2, + "positiveSteps": 12, + "pairs": [] + }, + "zindexes": { + "disabled": false, + "levels": [ + { + "name": "drawer", + "description": "Navigation drawers and shell surfaces.", + "value": 700 + }, + { + "name": "dialog", + "description": "Modal dialogs and confirmation overlays.", + "value": 800 + }, + { + "name": "dropdown", + "description": "Anchored dropdowns and menus.", + "value": 900 + }, + { + "name": "toast", + "description": "Transient toast notifications.", + "value": 950 + }, + { + "name": "tooltip", + "description": "Tooltips and quick helpers.", + "value": 1000 + }, + { + "name": "important", + "description": "Emergency override layer for critical UI.", + "value": 18014398509481984 + } + ] + }, + "aspectRatio": { + "disabled": false, + "pairs": [], + "named": [ + { + "name": "portrait", + "description": "Portrait aspect ratio (3:4)", + "min": 0.75, + "max": 0.75 + }, + { + "name": "widescreen", + "description": "Widescreen aspect ratio (16:9)", + "min": 1.77777777778, + "max": 1.77777777778 + }, + { + "name": "square", + "description": "Square aspect ratio (1:1)", + "min": 1, + "max": 1 + }, + { + "name": "landscape", + "description": "Landscape aspect ratio (4:3)", + "min": 1.333333, + "max": 1.333333 + }, + { + "name": "cinematic", + "description": "Cinematic aspect ratio (21:9)", + "min": 2.35, + "max": 2.35 + }, + { + "name": "ultrawide", + "description": "Ultrawide aspect ratio (32:9)", + "min": 3.5556, + "max": 3.5556 + } + ] + }, + "normalize": { + "disabled": false, + "palette": "neutral", + "reset": "none", + "semantic": "none" + } + }, + "colors": { + "theme": { + "disabled": false, + "imageUrl": "https://i.imgur.com/mLZ5vrL.jpeg", + "theory": "best", + "darkMode": { + "class": "dark" + }, + "colorVision": { + "mode": "none" + }, + "colorIdx": 3, + "easing": "inoutquad", + "toneBiasPercentage": 5, + "apca": { + "onTargetLc": 90, + "dimTargetLc": 30 + } + }, + "named": { + "disabled": false, + "negativeSteps": 2, + "positiveSteps": 2, + "hueStep": 6, + "chromaStep": 1.5, + "toneStep": 4, + "apca": { + "onTargetLc": 90, + "dimTargetLc": 30 + }, + "colors": [ + { + "name": "adobe", + "color": "#fa0f00" + }, + { + "name": "android", + "color": "#34a853" + }, + { + "name": "aws", + "color": "#ff9900" + }, + { + "name": "bluesky", + "color": "#0a7aff" + }, + { + "name": "c", + "color": "#a8b9cc" + }, + { + "name": "clojure", + "color": "#63b132" + }, + { + "name": "cplusplus", + "color": "#00599c" + }, + { + "name": "csharp", + "color": "#512bd4" + }, + { + "name": "css", + "color": "#1572b6" + }, + { + "name": "dart", + "color": "#0175c2" + }, + { + "name": "docker", + "color": "#1d63ed" + }, + { + "name": "elixir", + "color": "#4b275f" + }, + { + "name": "firefox", + "color": "#ff7139" + }, + { + "name": "go", + "color": "#00add8" + }, + { + "name": "html", + "color": "#e34f26" + }, + { + "name": "java", + "color": "#007396" + }, + { + "name": "javascript", + "color": "#f7df1e" + }, + { + "name": "kotlin", + "color": "#7f52ff" + }, + { + "name": "microsoft", + "color": "#00a4ef" + }, + { + "name": "odin", + "color": "#3882d2" + }, + { + "name": "php", + "color": "#777bb4" + }, + { + "name": "python", + "color": "#3776ab" + }, + { + "name": "ruby", + "color": "#cc342d" + }, + { + "name": "rust", + "color": "#dea584" + }, + { + "name": "safari", + "color": "#006cfc" + }, + { + "name": "slack", + "color": "#4a154b" + }, + { + "name": "stripe", + "color": "#635bff" + }, + { + "name": "swift", + "color": "#fa7343" + }, + { + "name": "typescript", + "color": "#3178c6" + }, + { + "name": "ubuntu", + "color": "#e95420" + }, + { + "name": "zig", + "color": "#f7a41d" + }, + { + "name": "ocean", + "color": "#0077b6" + }, + { + "name": "sand", + "color": "#f4d9a0" + }, + { + "name": "orange", + "color": "#e86f3a" + }, + { + "name": "grape", + "color": "#887BB4" + }, + { + "name": "red", + "color": "#e05252" + }, + { + "name": "green", + "color": "#2a9d4a" + }, + { + "name": "purple", + "color": "#8b7ab8" + }, + { + "name": "stream", + "color": "#00d4ff" + }, + { + "name": "navy", + "color": "#0a3d62" + } + ], + "prefix": "named" + }, + "charts": { + "disabled": false, + "qualitativeCount": 12, + "divergingCount": 6, + "toneSteps": 12, + "apca": { + "onTargetLc": 90, + "dimTargetLc": 30 + }, + "qualitativeExport": true, + "divergingExport": [ + true, + true, + true, + true, + true, + true + ] + }, + "gradients": { + "disabled": false, + "items": [ + { + "name": "brand-surface", + "kind": "Linear", + "angle": 135, + "repeating": false, + "stops": [ + { + "palette": "Primary", + "toneStep": 6, + "position": 0, + "interp": "Easing", + "easing": "linear", + "stepCount": 8 + }, + { + "palette": "Secondary", + "toneStep": 6, + "position": 100, + "interp": "Easing", + "easing": "linear", + "stepCount": 8 + } + ] + }, + { + "name": "brand-diagonal", + "kind": "Linear", + "angle": 315, + "repeating": false, + "stops": [ + { + "palette": "Primary", + "toneStep": 6, + "position": 0, + "interp": "Easing", + "easing": "linear", + "stepCount": 8 + }, + { + "palette": "Secondary", + "toneStep": 5, + "position": 100 + } + ] + }, + { + "name": "vapor", + "kind": "Linear", + "angle": 135, + "repeating": false, + "stops": [ + { + "palette": "Primary", + "toneStep": 6, + "position": 0, + "interp": "Easing", + "easing": "linear", + "stepCount": 8 + }, + { + "palette": "Tertiary", + "toneStep": 6, + "position": 50, + "interp": "Easing", + "easing": "linear", + "stepCount": 8 + }, + { + "palette": "Secondary", + "toneStep": 6, + "position": 100 + } + ] + }, + { + "name": "spotlight", + "kind": "Radial", + "angle": 0, + "repeating": false, + "stops": [ + { + "palette": "Primary", + "toneStep": 5, + "position": 0, + "interp": "Easing", + "easing": "linear", + "stepCount": 8 + }, + { + "palette": "Neutral", + "toneStep": 2, + "position": 100 + } + ] + }, + { + "name": "qualitative-sweep", + "kind": "Conic", + "angle": 0, + "repeating": false, + "stops": [ + { + "palette": "Chart Qualitative", + "toneStep": 1, + "position": 0, + "interp": "Steps", + "easing": "linear", + "stepCount": 1 + }, + { + "palette": "Chart Qualitative", + "toneStep": 2, + "position": 9, + "interp": "Steps", + "easing": "linear", + "stepCount": 1 + }, + { + "palette": "Chart Qualitative", + "toneStep": 3, + "position": 18, + "interp": "Steps", + "easing": "linear", + "stepCount": 1 + }, + { + "palette": "Chart Qualitative", + "toneStep": 4, + "position": 27, + "interp": "Steps", + "easing": "linear", + "stepCount": 1 + }, + { + "palette": "Chart Qualitative", + "toneStep": 5, + "position": 36, + "interp": "Steps", + "easing": "linear", + "stepCount": 1 + }, + { + "palette": "Chart Qualitative", + "toneStep": 6, + "position": 45, + "interp": "Steps", + "easing": "linear", + "stepCount": 1 + }, + { + "palette": "Chart Qualitative", + "toneStep": 7, + "position": 54, + "interp": "Steps", + "easing": "linear", + "stepCount": 1 + }, + { + "palette": "Chart Qualitative", + "toneStep": 8, + "position": 63, + "interp": "Steps", + "easing": "linear", + "stepCount": 1 + }, + { + "palette": "Chart Qualitative", + "toneStep": 9, + "position": 72, + "interp": "Steps", + "easing": "linear", + "stepCount": 1 + }, + { + "palette": "Chart Qualitative", + "toneStep": 10, + "position": 81, + "interp": "Steps", + "easing": "linear", + "stepCount": 1 + }, + { + "palette": "Chart Qualitative", + "toneStep": 11, + "position": 90, + "interp": "Steps", + "easing": "linear", + "stepCount": 1 + }, + { + "palette": "Chart Qualitative", + "toneStep": 12, + "position": 100 + } + ] + }, + { + "name": "neutral-soft", + "kind": "Linear", + "angle": 180, + "repeating": false, + "stops": [ + { + "palette": "Neutral Variant", + "toneStep": 5, + "position": 0 + }, + { + "palette": "Neutral Variant", + "toneStep": 7, + "position": 100 + } + ] + }, + { + "name": "striped", + "kind": "Linear", + "angle": 45, + "repeating": true, + "stops": [ + { + "palette": "Neutral", + "toneStep": 6, + "position": 0, + "interp": "Easing", + "easing": "linear", + "stepCount": 8 + }, + { + "palette": "Neutral", + "toneStep": 6, + "position": 8, + "interp": "Easing", + "easing": "linear", + "stepCount": 8 + }, + { + "palette": "Secondary", + "toneStep": 6, + "position": 8, + "interp": "Easing", + "easing": "linear", + "stepCount": 8 + }, + { + "palette": "Secondary", + "toneStep": 6, + "position": 16 + } + ] + }, + { + "name": "diverging", + "kind": "Linear", + "angle": 120, + "repeating": false, + "stops": [ + { + "palette": "Secondary", + "toneStep": 6, + "position": 0, + "interp": "Easing", + "easing": "linear", + "stepCount": 8 + }, + { + "palette": "Error", + "toneStep": 6, + "position": 100 + } + ] + } + ] + }, + "shadows": { + "disabled": false, + "palette": "Neutral", + "baseStep": 1, + "outer": { + "steps": 6, + "pow": 3.965398, + "layer": 4.518945, + "alphaStep": 0.430454, + "alphaLayer": 0.980098, + "alphaMinScale": 0.828863, + "range": { + "yMin": 1.09683, + "yMax": 93.014527, + "blurMin": 3.180445, + "blurMax": 76.603431, + "spreadMin": -2.582253, + "spreadMax": -1.457, + "opacityMin": 0.028506, + "opacityMax": 0, + "strengthMin": 0.309779, + "strengthMax": 0.309779 + } + }, + "inner": { + "steps": 4, + "pow": 0.709677, + "alphaStep": 0.485594, + "range": { + "yMin": 0.367976, + "yMax": 1.41686, + "blurMin": 2.702843, + "blurMax": 9.255569, + "opacityMin": 0.020355, + "opacityMax": 0.020355, + "strengthMin": 0.32654, + "strengthMax": 0.32654 + } + } + }, + "code": { + "disabled": false, + "index": 9, + "invertDarkMode": false, + "apca": { + "onTargetLc": 90, + "dimTargetLc": 30, + "surfaceTargetLc": 30 + } + } + }, + "fonts": { + "families": { + "disabled": false, + "values": [ + { + "name": "antique", + "faces": [ + "Superclarendon", + "Bookman Old Style", + "URW Bookman", + "URW Bookman L", + "Georgia Pro", + "Georgia", + "serif" + ] + }, + { + "name": "classical-humanist", + "faces": [ + "Optima", + "Candara", + "Noto Sans", + "source-sans-pro", + "sans-serif" + ] + }, + { + "name": "didone", + "faces": [ + "Didot", + "Bodoni MT", + "Noto Serif Display", + "URW Palladio L", + "P052", + "Sylfaen", + "serif" + ] + }, + { + "name": "geometric-humanist", + "faces": [ + "Avenir", + "Montserrat", + "Corbel", + "URW Gothic", + "source-sans-pro", + "sans-serif" + ] + }, + { + "name": "handwritten", + "faces": [ + "Segoe Print", + "Bradley Hand", + "Chilanka", + "TSCu_Comic", + "casual", + "cursive" + ] + }, + { + "name": "humanist", + "faces": [ + "Seravek", + "Gill Sans Nova", + "Ubuntu", + "Calibri", + "DejaVu Sans", + "source-sans-pro", + "sans-serif" + ] + }, + { + "name": "industrial", + "faces": [ + "Bahnschrift", + "DIN Alternate", + "Franklin Gothic Medium", + "Nimbus Sans Narrow", + "sans-serif-condensed", + "sans-serif" + ] + }, + { + "name": "mono", + "faces": [ + "Iosevka X", + "ui-monospace", + "SF Mono", + "Menlo", + "monospace" + ] + }, + { + "name": "monospace-code", + "faces": [ + "Dank Mono", + "Operator Mono", + "Inconsolata", + "Fira Mono", + "ui-monospace", + "SF Mono", + "Monaco", + "Droid Sans Mono", + "Source Code Pro", + "Cascadia Code", + "Menlo", + "Consolas", + "DejaVu Sans Mono", + "monospace" + ] + }, + { + "name": "monospace-slab-serif", + "faces": [ + "Nimbus Mono PS", + "Courier New", + "monospace" + ] + }, + { + "name": "neo-grotesque", + "faces": [ + "Inter", + "Roboto", + "Helvetica Neue", + "Arial Nova", + "Nimbus Sans", + "Arial", + "sans-serif" + ] + }, + { + "name": "old-style", + "faces": [ + "Iowan Old Style", + "Palatino Linotype", + "URW Palladio L", + "P052", + "serif" + ] + }, + { + "name": "rounded-sans", + "faces": [ + "ui-rounded", + "Hiragino Maru Gothic ProN", + "Quicksand", + "Comfortaa", + "Manjari", + "Arial Rounded MT", + "Arial Rounded MT Bold", + "Calibri", + "source-sans-pro", + "sans-serif" + ] + }, + { + "name": "sans", + "faces": [ + "Source Sans 3", + "system-ui", + "sans-serif" + ] + }, + { + "name": "serif", + "faces": [ + "ui-serif", + "serif" + ] + }, + { + "name": "slab-serif", + "faces": [ + "Rockwell", + "Rockwell Nova", + "Roboto Slab", + "DejaVu Serif", + "Sitka Small", + "serif" + ] + }, + { + "name": "system-ui", + "faces": [ + "system-ui", + "sans-serif" + ] + }, + { + "name": "transitional", + "faces": [ + "Charter", + "Bitstream Charter", + "Sitka Text", + "Cambria", + "serif" + ] + } + ] + }, + "imports": { + "disabled": false, + "options": { + "preconnect": true + }, + "families": [ + { + "name": "Inter", + "variants": [ + { + "italic": false, + "weight": { + "range": { + "min": 100, + "max": 900 + } + } + } + ] + }, + { + "name": "Inconsolata", + "variants": [ + { + "italic": false, + "weight": { + "set": [ + 400, + 700 + ] + } + } + ] + } + ] + }, + "weights": { + "disabled": false, + "named": [ + { + "name": "thin", + "description": "Thin (Hairline)", + "value": 100 + }, + { + "name": "extra-light", + "description": "Extra Light (Ultra Light)", + "value": 200 + }, + { + "name": "light", + "description": "Light", + "value": 300 + }, + { + "name": "normal", + "description": "Normal (Regular)", + "value": 400 + }, + { + "name": "medium", + "description": "Medium", + "value": 500 + }, + { + "name": "semi-bold", + "description": "Semi Bold (Demi Bold)", + "value": 600 + }, + { + "name": "bold", + "description": "Bold", + "value": 700 + }, + { + "name": "extra-bold", + "description": "Extra Bold (Ultra Bold)", + "value": 800 + }, + { + "name": "black", + "description": "Black (Heavy)", + "value": 900 + }, + { + "name": "extra-black", + "description": "Extra Black (Ultra Black)", + "value": 950 + } + ] + }, + "sizes": { + "disabled": false, + "baseMin": 1, + "baseMax": 1, + "scaleMin": "Major Second", + "scaleMax": "Minor Third", + "negativeSteps": 2, + "positiveSteps": 12, + "pairs": [] + }, + "spacing": { + "disabled": false, + "baseMin": 0.025, + "baseMax": 0.025, + "scaleMin": "Major Sixth", + "scaleMax": "Octave", + "negativeSteps": 2, + "positiveSteps": 3, + "pairs": [] + }, + "lineHeights": { + "disabled": false, + "baseMin": 1.5, + "baseMax": 1.5, + "scaleMin": "Minor Second", + "scaleMax": "Minor Second", + "negativeSteps": 3, + "positiveSteps": 4, + "pairs": [] + } + }, + "borders": { + "widths": { + "disabled": false, + "baseMin": 0.1, + "baseMax": 0.1, + "scaleMin": "Major Second", + "scaleMax": "Augmented Fourth", + "negativeSteps": 0, + "positiveSteps": 5, + "pairs": [] + }, + "radii": { + "disabled": false, + "baseMin": 0.125, + "baseMax": 0.5, + "scaleMin": "Major Third", + "scaleMax": "Major Third", + "negativeSteps": 2, + "positiveSteps": 6, + "pairs": [ + { + "fromStep": 1, + "toStep": 2 + }, + { + "fromStep": 2, + "toStep": 3 + }, + { + "fromStep": 3, + "toStep": 4 + }, + { + "fromStep": 4, + "toStep": 5 + }, + { + "fromStep": 5, + "toStep": 6 + } + ] + }, + "generators": { + "disabled": false, + "drawn": { + "count": 6, + "seed": 42, + "longMin": 7.8125, + "longMax": 15.9375, + "longClampMin": 6.25, + "longClampMax": 15.9375, + "shortMin": 0.625, + "shortMax": 7.1875, + "shortClampMin": 0.625, + "shortClampMax": 7.1875, + "shortMaxWeight": 0.35, + "longJitter": 0.12, + "shortJitter": 0.25, + "roundStep": 0.3125, + "minGap": 0.3125 + }, + "blobs": { + "count": 5, + "seed": 43, + "longMin": 55, + "longMax": 70, + "longClampMin": 30, + "longClampMax": 70, + "shortMin": 30, + "shortMax": 55, + "shortClampMin": 30, + "shortClampMax": 70, + "shortMaxWeight": 0.4, + "longJitter": 0.1, + "shortJitter": 0.18, + "roundStep": 1, + "minGap": 1 + }, + "shouldGenerateDrawn": true, + "shouldGenerateBlobs": true + } + }, + "animations": { + "durations": { + "disabled": false, + "baseMin": 0.12, + "baseMax": 0.2, + "scaleMin": "Minor Second", + "scaleMax": "Minor Third", + "negativeSteps": 2, + "positiveSteps": 4, + "pairs": [], + "named": [ + { + "name": "none", + "min": 0, + "max": 0 + }, + { + "name": "instant", + "min": 0.01, + "max": 0.01 + }, + { + "name": "fast", + "min": 0.12, + "max": 0.18 + }, + { + "name": "base", + "min": 0.18, + "max": 0.24 + }, + { + "name": "slow", + "min": 0.28, + "max": 0.36 + } + ] + }, + "distances": { + "disabled": false, + "baseMin": 0.25, + "baseMax": 0.6, + "scaleMin": "Major Second", + "scaleMax": "Major Third", + "negativeSteps": 0, + "positiveSteps": 4, + "pairs": [], + "named": [ + { + "name": "none", + "min": 0, + "max": 0 + }, + { + "name": "xs", + "min": 0.125, + "max": 0.2 + }, + { + "name": "sm", + "min": 0.25, + "max": 0.35 + }, + { + "name": "md", + "min": 0.5, + "max": 0.7 + }, + { + "name": "lg", + "min": 0.75, + "max": 1 + }, + { + "name": "xl", + "min": 1.25, + "max": 1.6 + } + ] + }, + "scales": { + "disabled": false, + "baseMin": 0.95, + "baseMax": 1.02, + "scaleMin": "Minor Second", + "scaleMax": "Minor Second", + "negativeSteps": 2, + "positiveSteps": 2, + "pairs": [], + "named": [ + { + "name": "down-strong", + "min": 0.88, + "max": 0.92 + }, + { + "name": "down", + "min": 0.96, + "max": 0.98 + }, + { + "name": "base", + "min": 1, + "max": 1 + }, + { + "name": "up", + "min": 1.03, + "max": 1.06 + }, + { + "name": "up-strong", + "min": 1.08, + "max": 1.12 + } + ] + }, + "rotations": { + "disabled": false, + "baseMin": 4, + "baseMax": 8, + "scaleMin": "Minor Second", + "scaleMax": "Minor Third", + "negativeSteps": 1, + "positiveSteps": 2, + "pairs": [], + "named": [ + { + "name": "none", + "min": 0, + "max": 0 + }, + { + "name": "xs", + "min": 1, + "max": 2 + }, + { + "name": "sm", + "min": 3, + "max": 5 + }, + { + "name": "md", + "min": 8, + "max": 12 + }, + { + "name": "lg", + "min": 15, + "max": 20 + }, + { + "name": "neg-xs", + "min": -2, + "max": -1 + }, + { + "name": "neg-sm", + "min": -5, + "max": -3 + }, + { + "name": "neg-md", + "min": -12, + "max": -8 + } + ] + }, + "opacities": { + "disabled": false, + "baseMin": 0.2, + "baseMax": 0.6, + "scaleMin": "Minor Second", + "scaleMax": "Minor Second", + "negativeSteps": 0, + "positiveSteps": 2, + "pairs": [], + "named": [ + { + "name": "transparent", + "min": 0, + "max": 0 + }, + { + "name": "muted", + "min": 0.4, + "max": 0.6 + }, + { + "name": "solid", + "min": 1, + "max": 1 + } + ] + }, + "easings": [ + { + "name": "standard", + "value": "cubic-bezier(0.4, 0, 0.2, 1)" + }, + { + "name": "emphasized", + "value": "cubic-bezier(0.2, 0, 0, 1)" + }, + { + "name": "entrance", + "value": "cubic-bezier(0.16, 1, 0.3, 1)" + }, + { + "name": "bounce", + "value": "linear(0, 0.011 2.9%, 0.045 5.8%, 0.1 8.6%, 0.176 11.5%, 0.273 14.4%, 0.391 17.3%, 0.53 20.2%, 0.69 23%, 0.871 25.9%, 1.068 28.8%, 1.156 30.3%, 1.171 30.8%, 1.171 31.6%, 1.163 32.4%, 1.138 33.3%, 1.017 36.7%, 0.977 38.1%, 0.945 39.5%, 0.93 40.9%, 0.928 42.4%, 0.937 43.8%, 0.958 45.2%, 1.075 49.2%, 1.101 50.8%, 1.11 52.1%, 1.105 53.4%, 1.062 56.2%, 1.017 58.1%, 0.991 60.2%, 0.98 62.7%, 0.985 65.2%, 1 68.1%, 1.036 74.2%, 1.046 77.4%, 1.044 80.4%, 1.028 83.5%, 1.001 86.9%, 0.999 99.9%)" + }, + { + "name": "shake", + "value": "linear(0, 0.5 12%, 0.25 22%, 0.75 32%, 0.35 42%, 0.65 52%, 0.43 62%, 0.57 72%, 0.48 82%, 0.52 90%, 1)" + }, + { + "name": "heartbeat", + "value": "linear(0, 0.38 14%, 0.7 28%, 0.46 36%, 0.84 50%, 0.62 60%, 0.9 74%, 1)" + }, + { + "name": "pulse", + "value": "cubic-bezier(0.42, 0, 0.58, 1)" + }, + { + "name": "linear", + "value": "linear" + } + ] + }, + "settings": { + "general": { + "input": "/root/stellar.config.json", + "force": false, + "verbose": false, + "cpuProfile": "", + "watchMs": 10 + }, + "export": { + "output": "css/stellar.css", + "namespace": ":root", + "overwrite": true, + "includeColorFallback": true, + "includeFontImports": false, + "includeReducedMotion": true, + "fontDir": "./fonts", + "fontFormat": "both", + "bundle": { + "minifyWhitespace": true, + "sourcemap": "none", + "sourcesContent": "include", + "sourceRoot": "", + "lineLimit": 0 + } + } + } +} \ No newline at end of file diff --git a/www-next-gen/tutorials/build-a-live-guestbook.md b/www-next-gen/tutorials/build-a-live-guestbook.md new file mode 100644 index 0000000..829ae15 --- /dev/null +++ b/www-next-gen/tutorials/build-a-live-guestbook.md @@ -0,0 +1,273 @@ +Build a guestbook app with http-nu, step by step. By the end you will have +a server with composable HTML, persistent storage, and real-time updates +powered by Datastar and server-sent events. + +New to http-nu? Start with the [Hello world tutorial](/tutorials/hello-world) to +boot a server and curl it, then come back here to build something real. + +## Step 1: The HTML DSL + +One-liners are fun, but let's build a real page. Create a file called +`serve.nu`: + +```nu +use http-nu/html * + +{|req| + HTML [ + (HEAD [ + (META {charset: "UTF-8"}) + (TITLE "Guestbook") + ]) + (BODY [ + (H1 "Guestbook") + (P "Welcome! Sign the guestbook below.") + (UL [ + (LI [(STRONG "Alice") " -- Hello, world!"]) + (LI [(STRONG "Bob") " -- Great site!"]) + ]) + ]) + ] +} +``` + +Run it: + +```bash +http-nu :3001 serve.nu +``` + +Tags are uppercase Nushell commands: `H1`, `P`, `UL`, `DIV`. The first +argument can be an attribute record: `{class: "intro"}`. Everything after +that is children -- strings, other tags, or lists. Plain strings are +auto-escaped for XSS protection. + +`HTML` prepends ``. `class` accepts a list: +`{class: [bold italic]}`. Boolean attributes work too: `{disabled: true}`. + +## Step 2: Routing + +Every request currently hits the same handler. Let's add proper routing +with the built-in router module. + +```nu +use http-nu/html * +use http-nu/router * + +{|req| + dispatch $req [ + (route {method: "GET" path: "/"} {|req ctx| + HTML [ + (HEAD [(META {charset: "UTF-8"}) (TITLE "Guestbook")]) + (BODY [ + (H1 "Guestbook") + (P "No messages yet.") + (P (A {href: "/about"} "About this guestbook")) + ]) + ] + }) + + (route {method: "GET" path: "/about"} {|req ctx| + HTML [ + (HEAD [(META {charset: "UTF-8"}) (TITLE "About")]) + (BODY [ + (H1 "About") + (P "A guestbook built with http-nu.") + (P (A {href: "/"} "Back")) + ]) + ] + }) + + (route true {|req ctx| + "Not found" | metadata set { merge {'http.response': {status: 404}} } + }) + ] +} +``` + +`dispatch` tests routes in order -- first match wins. Each `route` takes a +test (a record for matching, or `true` for catch-all) and a handler closure. +The handler receives the request and a context record. + +You can match on method, path, or both. For dynamic segments use +`path-matches`: + +```nu +(route {path-matches: "/users/:id"} {|req ctx| + $"User ID: ($ctx.id)" +}) +``` + +## Step 3: The Store + +Time to persist messages. http-nu embeds +[cross.stream](https://cross.stream), an append-only event store. Enable it +with `--store`, and add `-w` for watch mode so the server reloads when you +edit the script: + +```bash +http-nu --store ./store :3001 -w serve.nu +``` + +```nu +use http-nu/html * +use http-nu/router * + +def message-card [msg: record] { + LI [(STRONG $msg.name) $" -- ($msg.message)"] +} + +def page [messages: list] { + HTML [ + (HEAD [(META {charset: "UTF-8"}) (TITLE "Guestbook")]) + (BODY [ + (H1 "Guestbook") + (if ($messages | is-empty) { + P "No messages yet. Be the first!" + } else { + UL { $messages | each {|m| message-card $m } } + }) + ]) + ] +} + +{|req| + dispatch $req [ + (route {method: "GET" path: "/"} {|req ctx| + let messages = try { .cat messages } catch { [] } + | each { $in.meta } + page $messages + }) + + (route {method: "POST" path: "/sign"} {|req ctx| + from json | .append messages --meta $in + "" | metadata set { merge {'http.response': {status: 204}} } + }) + ] +} +``` + +Add some messages: + +```bash +curl -X POST -H "Content-Type: application/json" \ + -d '{"name":"Alice","message":"Hello!"}' localhost:3001/sign + +curl -X POST -H "Content-Type: application/json" \ + -d '{"name":"Bob","message":"Great site!"}' localhost:3001/sign +``` + +Refresh the page -- your messages are there. Restart the server -- still +there. The store persists to `./store` on disk. + +Notice `message-card` and `page`. In http-nu, reusable HTML fragments are +just Nushell `def` commands. Each returns an `{__html: ...}` record that +other tags accept as children without re-escaping. Composition is just +function calls. + +## Step 4: Datastar -- Live Updates + +Now for the payoff. [Datastar](https://data-star.dev) is a lightweight +hypermedia framework that connects your HTML to the server via server-sent +events. http-nu ships with a built-in Datastar SDK and serves the JS bundle +directly. + +```bash +http-nu --datastar --store ./store :3001 -w serve.nu +``` + +```nu +use http-nu/html * +use http-nu/router * +use http-nu/datastar * + +def message-card [msg: record] { + LI [(STRONG $msg.name) $" -- ($msg.message)"] +} + +def page [messages: list] { + HTML [ + (HEAD [ + (META {charset: "UTF-8"}) + (TITLE "Guestbook") + (SCRIPT {type: "module" src: $DATASTAR_JS_PATH}) + ]) + (BODY [ + (H1 "Guestbook") + (UL {id: "messages"} { + $messages | each {|m| message-card $m } + }) + + (H2 "Sign the Guestbook") + (FORM { + "data-signals": "{name: '', message: ''}" + "data-on:submit.prevent": "@post('/sign')" + } [ + (INPUT { + type: "text" + placeholder: "Your name" + "data-bind:name": "" + required: true + }) + (BR) + (TEXTAREA { + placeholder: "Your message" + "data-bind:message": "" + required: true + }) + (BR) + (BUTTON {type: "submit"} "Sign") + ]) + + # Open SSE connection on page load + (DIV {"data-on:load": "@get('/feed')"}) + ]) + ] +} + +{|req| + dispatch $req [ + (route {method: "GET" path: "/"} {|req ctx| + let messages = try { .cat messages } catch { [] } + | each { $in.meta } + page $messages + }) + + (route {method: "POST" path: "/sign"} {|req ctx| + let signals = from datastar-signals $req + .append messages --meta $signals + # Clear the form + {name: "" message: ""} | to datastar-patch-signals | to sse + }) + + (route {method: "GET" path: "/feed"} {|req ctx| + .cat messages --follow --new + | each {|frame| + message-card $frame.meta + | to datastar-patch-elements --selector "#messages" --mode append + } + | to sse + }) + ] +} +``` + +Open `http://localhost:3001` in two browser tabs. Sign the guestbook in +one -- the message appears instantly in both. No page refresh. + +Here is what is happening: + +- **Reactive signals**: `data-signals` declares client state (`name` and + `message`). `data-bind` two-way binds the inputs to those signals. +- **Server actions**: `data-on:submit.prevent` intercepts the form and + sends signals as JSON via `@post('/sign')`. The server stores the message + and responds with `to datastar-patch-signals` to clear the form. +- **Live feed**: `data-on:load` opens an SSE connection to `/feed`. + New messages stream through `to datastar-patch-elements`, which tells + Datastar exactly which DOM element to update and how (`append` mode). +- **Streaming**: `.cat messages --follow --new` is a long-lived stream. + Each new entry flows through `each`, gets wrapped as an SSE event, and + reaches the browser immediately. + +No client-side JavaScript. No virtual DOM. No build step. Just HTML +fragments streamed over SSE. diff --git a/www-next-gen/www.nu b/www-next-gen/www.nu new file mode 100644 index 0000000..b52d40e --- /dev/null +++ b/www-next-gen/www.nu @@ -0,0 +1,1047 @@ +# www.nu - prototype of the http-nu site redone on stellar variables. +# +# - typography and layout come entirely from assets/stellar.css + +# assets/base.css (semantic elements, a few utility classes) +# - the docs section is the project README split into navigable pages +# by readme.nu, so the README stays the single source of truth +# +# Run: http-nu --datastar :3001 www.nu +# (regenerate assets/stellar.css with: stellar gen, stellar.key in place) + +const script_dir = path self | path dirname + +use http-nu/router * +use http-nu/datastar * +use http-nu/html * +use readme.nu * + +# README is read once at handler load. It lives one level up (this dir sits at +# the repo root as www-next-gen/), and stays the single source of truth. +const readme_path = path self | path dirname | path join .. README.md +let readme = (open --raw $readme_path | decode utf-8) + +# Section groupings: these heading slugs hold child pages rather than +# becoming one giant page. +let groups = [] +let pages = ($readme | pages $groups) +let anchors = ($readme | anchor-map $groups) + +# each page's h3 sub-sections, precomputed at load (parsing deep in the DSL tree +# at request time overflows the stack). Map: page slug -> [{slug, title}]. +let all_headings = ($readme | headings) +let page_secs = ($pages | reduce --fold {} {|p, acc| + $acc | upsert $p.slug ($all_headings | where line > $p.start and line <= $p.end and level == ($p.level + 1) | select slug title) +}) + +def icon [name: string] { + {__html: $""} +} + +def page-head [title: string] { + (HEAD + (META {charset: "utf-8"}) + (META {name: "viewport" content: "width=device-width, initial-scale=1"}) + (META {name: "view-transition" content: "same-origin"}) + (TITLE $title) + # social + search metadata (shared by every page via this head) + (META {name: "description" content: "A tiny, Nushell-scriptable HTTP server: hand it a closure and you have a server. Streaming, SSE, templates, and an embedded event store, all in one binary."}) + (META {property: "og:title" content: $title}) + (META {property: "og:type" content: "website"}) + (META {property: "og:site_name" content: "http-nu"}) + (META {property: "og:description" content: "A tiny, Nushell-scriptable HTTP server with streaming, SSE, templates, and an embedded event store."}) + (META {name: "twitter:card" content: "summary"}) + (LINK {rel: "icon" type: "image/png" href: "/assets/ellie.png"}) + # stellar props -> base (raw tags + generic utils/components) -> brand layer + (LINK {rel: "stylesheet" href: "/assets/stellar.css"}) + (LINK {rel: "stylesheet" href: "/assets/base.css"}) + (LINK {rel: "stylesheet" href: "/assets/brand.css"}) + (SCRIPT {src: "https://cdn.jsdelivr.net/npm/iconify-icon@2/dist/iconify-icon.min.js"}) + (SCRIPT {type: "module" src: $DATASTAR_JS_PATH}) + # theme: restore before paint to avoid a flash + (SCRIPT {__html: r#' +(function() { + var saved = localStorage.getItem('theme'); + var dark = saved ? saved === 'dark' : matchMedia('(prefers-color-scheme:dark)').matches; + if (dark) document.documentElement.classList.add('dark'); +})(); +'#}) + (SCRIPT {src: "/assets/vt-tuner.js"}) + ) +} + +def theme-toggle [] { + (BUTTON { + id: "theme-toggle" + title: "Toggle theme" + onclick: "var d=document.documentElement.classList.toggle('dark');localStorage.setItem('theme',d?'dark':'light')" + } (icon "lucide:sun-moon")) +} + +# launcher for the page-transition tuner (wired up by vt-tuner.js) +def vt-toggle [] { + (BUTTON { + id: "vt-tuner-btn" + class: "vt-toggle" + type: "button" + title: "Page transition tuner" + } (icon "ph:toolbox")) +} + +# Shared nav: brand + links + theme toggle, on every page. +def nav-bar [] { + (NAV {class: "nav"} + (DIV {class: "brand"} (A {href: "/"} "http-nu")) + (DIV {class: "links"} + (A {href: "/themes"} "Themes") + (A {href: "/tutorials"} "Tutorials") + (A {href: "/how-tos"} "How-tos") + (A {href: "/examples/"} "Examples") + (A {href: "/reference"} "Reference") + (A {href: "/design"} "Design") + (A {href: "https://github.com/cablehead/http-nu"} "GitHub") + (vt-toggle) + (theme-toggle))) +} + +# Inject copy buttons into rendered code blocks. +def inject-copy-btns []: record -> record { + let btn = '' + {__html: ( + $in.__html + | str replace --all '
' $'
($btn)
'
+    | str replace --all '
' '
' + )} +} + +def copy-script [] { + (SCRIPT {__html: r#' +document.addEventListener('click', function(e) { + var btn = e.target.closest('.copy-btn'); + if (!btn) return; + var code = btn.parentElement.querySelector('pre code'); + if (!code) return; + navigator.clipboard.writeText(code.textContent); + var icon = btn.querySelector('iconify-icon'); + if (icon) { icon.setAttribute('icon','lucide:check'); setTimeout(function(){icon.setAttribute('icon','lucide:copy')},800); } +}); +'#}) +} + +# site footer: shared across every page (added beside copy-script in each body) +def site-footer [] { + (FOOTER {class: "site-footer"} + (DIV {class: "container"} + (P {class: "muted"} + (A {href: "https://github.com/cablehead/http-nu"} "GitHub") " \u{b7} " + (A {href: "https://discord.com/invite/YNbScHBHrh"} "Discord") " \u{b7} " + (A {href: "/tutorials"} "Tutorials") " \u{b7} " + (A {href: "/how-tos"} "How-tos") " \u{b7} " + (A {href: "/examples/"} "Examples") " \u{b7} " + (A {href: "/reference"} "Reference") " \u{b7} " + (A {href: "/design"} "Design") " \u{b7} " + (A {href: "https://www.nushell.sh"} "Nushell") "-scriptable, " (A {href: "https://cross.stream"} "cross.stream") + "-powered, " (A {href: "https://data-star.dev"} "Datastar") "-ready"))) +} + +# --- routes ---------------------------------------------------------- + +# --- splash hero (branded, www palette via stellar named colors) --- + +def svg-top [] { + (SVG {viewBox: "0 0 600 70" xmlns: "http://www.w3.org/2000/svg" class: "curve"} + (PATH {d: "M 20,65 Q 70,45 100,38" stroke: "currentColor" stroke-width: "1" fill: "none"}) + (PATH {id: "curve-top" d: "M 100,38 Q 300,15 500,38" fill: "none"}) + (TEXT {fill: "currentColor" font-family: "Georgia, serif" font-style: "italic" font-size: "28px"} + (TEXTPATH {href: "#curve-top" startOffset: "50%" text-anchor: "middle"} "The surprisingly performant")) + (PATH {d: "M 500,38 Q 530,45 580,65" stroke: "currentColor" stroke-width: "1" fill: "none"})) +} + +def svg-bottom [] { + (SVG {viewBox: "0 0 600 70" xmlns: "http://www.w3.org/2000/svg" class: "curve"} + (PATH {id: "curve-bottom" d: "M 50,15 Q 300,65 550,15" fill: "none"}) + (TEXT {fill: "currentColor" font-family: "Georgia, serif" font-style: "italic" font-size: "28px"} + (TEXTPATH {href: "#curve-bottom" startOffset: "50%" text-anchor: "middle"} "that fits in your back pocket"))) +} + +def wave-divider [] { + (SVG {class: "wave" viewBox: "0 0 1200 150" preserveAspectRatio: "none" xmlns: "http://www.w3.org/2000/svg"} + (PATH {d: "M0,50 Q300,150 600,50 T1200,50 L1200,150 L0,150 Z" fill: "var(--named-green-0)"}) + (PATH {d: "M0,80 Q300,0 600,80 T1200,80 L1200,150 L0,150 Z" fill: "var(--named-orange-0)"})) +} + +def splash-hero [] { + (DIV {class: "splash-hero"} + (nav-bar) + (DIV {class: "taglines"} + (svg-top) + (IMG {src: "/assets/ellie.png" alt: "http-nu mascot"}) + (DIV {class: "badges"} + (SPAN {class: "badge tone-orange"} (A {href: "https://www.nushell.sh"} "Nushell") "-scriptable!") + (SPAN {class: "badge tone-grape"} + (A {href: "https://cross.stream"} "cross." (SPAN {class: "stream"} "stream")) "-powered") + (SPAN {class: "badge tone-red"} (A {href: "https://data-star.dev"} "Datastar") "-ready")) + (SPAN {class: "badge tone-green upper mx-auto"} "HTTP Server") + (svg-bottom)) + (wave-divider)) +} + +# A section heading that links to its own anchor (like ./www), so each +# section is addressable. Extra children (e.g. a gif) sit after the link. +def section-head [title: string ...extra] { + let slug = ($title | str downcase | str replace --all ' ' '-') + (H2 {id: $slug} (A {href: $"#($slug)"} $title) ...$extra) +} + +# "Give it a try": install method tabs (Datastar $tab signal) over a terminal +# that shows the chosen install command, then the hello-world run. +def give-it-a-try [] { + let methods = [ + [brew "Homebrew" "brew install cablehead/tap/http-nu"] + [cargo "Cargo" "cargo install --locked http-nu"] + [eget "eget" "eget cablehead/http-nu"] + [nix "Nix" "nix-shell -p http-nu"] + ] + (DIV {"data-signals:tab": "'brew'"} + (section-head "Give it a try" + (IMG {class: "rocket" alt: "" src: "https://data-star.dev/cdn-cgi/image/format=auto,width=96/static/images/rocket-animated-1d781383a0d7cbb1eb575806abeec107c8a915806fb55ee19e4e33e8632c75e5.gif"})) + (P "http-nu is a single binary. Grab it with your preferred package manager:") + (terminal + ($methods | each {|m| + BUTTON {class: "btn-tab" "data-class:is-active": $"$tab === '($m.0)'" "data-on:click": $"$tab = '($m.0)'"} $m.1 + }) + ($methods | each {|m| + DIV {"data-show": $"$tab === '($m.0)'"} (SPAN {class: "prompt"} "$ ") $m.2 + }))) +} + +# --- topic hubs ------------------------------------------------------ +# A hub weaves one theme together: the reference doc, the worked example +# broken into liftable parts (source shown for each), and how to run it. +# Pilot covers Templates; the shape generalizes to every theme. + +# A code "toy": a highlighted source fragment you can lift out of an example +# and read on its own, with a copy button. (Editable/live is a later step; +# for now the source is shown clearly.) Reuses the docs .code-block chrome. +def code-toy [src: string, lang: string] { + let btn = '' + (DIV {class: "code-block"} {__html: $btn} (PRE (CODE ($src | str trim | .highlight $lang)))) +} + +# A labelled toy: a heading, a one-line gloss, and the source. +def toy [title: string, explain: string, src: string, lang: string] { + (SECTION {class: "toy"} + (H3 $title) + (P {class: "muted"} $explain) + (code-toy $src $lang)) +} + +def templates-files-section [] { + let file_src = r#' +# /file: render page.html from disk. {% extends %} and {% include %} +# resolve from the template dir and subdirs only (no ../, no absolute paths) +"/file" => { {name: "World"} | .mj ($templates_dir | path join page.html) }'# + let files_src = r#' +{% extends "base.html" %} +{% block title %}My Page{% endblock %} +{% block content %} +{% include "nav.html" %} +
Hello {{ name }}
+{% endblock %} + + +{% block title %}Default{% endblock %} +{% block content %}{% endblock %} + + +'# + let topic_src = r#' +# seed templates into the store as topics, once at startup +if $HTTP_NU.store != null { + open (topics/page.html) | .append page.html + open (topics/base.html) | .append base.html + open (topics/nav.html) | .append nav.html +} + +# /topic: same render, but template names resolve as cross.stream topics +"/topic" => { {name: "World"} | .mj --topic "page.html" }'# + (DIV + (H2 "Files and the store") + (P "The snippet above is self-contained. Point " + (CODE ".mj") " at a file and it resolves " + (CODE "{% extends %}") " / " (CODE "{% include %}") + " from disk; point it at the store and the same names resolve as cross.stream topics.") + (toy "From a file" "extends and include resolve from the template's directory and subdirs only." $file_src "nu") + (toy "The files it resolves" "page.html extends base.html and includes nav.html, all from the same directory." $files_src "html") + (toy "From the store" "Seed the templates as topics once, then resolve them by name, with nothing on disk." $topic_src "nu") + (P (A {href: "/themes/templates/reference"} "Full reference: modes, compile / render, highlighting, Markdown ->"))) +} + +def streaming-overview [] { + let chunks_src = r#' +# values from a streaming pipeline flush to the client as they are produced, +# no waiting for the whole response to be ready +generate {|_| + sleep 1sec + {out: $"(date now | to text)\n" next: true} +} true'# + let to_sse_src = r#' +# to sse formats {data? id? event? retry?} records for text/event-stream, +# and sets content-type: text/event-stream for you +{data: 'hello'} | to sse +# data: hello + +{id: 1 event: greet data: 'hi'} | to sse +# id: 1 +# event: greet +# data: hi'# + let page_src = r#' +# the page: a count signal, and buttons that POST to routes. +# data-text binds the signal into the DOM; data-on:click posts. +(BODY {"data-signals": "{count: 0}"} + (P "Count: " (SPAN {"data-text": "$count"} "0")) + (BUTTON {"data-on:click": "@post('./increment')"} "Increment") + (DIV {id: "time"} "--:--:--.---") + (BUTTON {"data-on:click": "@post('./time')"} "Get Time"))'# + let patch_signals_src = r#' +# /increment: read signals, bump count, stream a signal patch back as SSE +(route {method: POST path: "/increment"} {|req ctx| + let signals = from datastar-signals $req + let count = ($signals.count? | default 0) + 1 + {count: $count} | to datastar-patch-signals | to sse +})'# + let patch_elements_src = r#' +# /time: stream an element; Datastar swaps it in by matching id +(route {method: POST path: "/time"} {|req ctx| + let time = date now | format date "%H:%M:%S%.3f" + DIV {id: "time"} $time | to datastar-patch-elements | to sse +})'# + let execute_script_src = r#' +# /hello: stream a script for the client to run +(route {method: POST path: "/hello"} {|req ctx| + "alert('Hello from the server!')" | to datastar-execute-script | to sse +})'# + let run_src = r#' +# serve the example with the embedded Datastar bundle +http-nu --datastar :3001 examples/datastar-sdk/serve.nu + +# click a button: the browser POSTs, the server streams an SSE patch back'# + (ARTICLE + (P "Send chunks as they are produced, and push server-sent events. A streaming " + "pipeline's values flush to the client immediately; " + (CODE "to sse") " formats records for the event stream.") + (DIV {class: "playground" "data-signals": "{count: 0}"} + (P {class: "muted"} "Click a button. The browser POSTs and the server streams an SSE patch back, live:") + (DIV {class: "pg-live"} + (BUTTON {"data-on:click": "@post('/themes/streaming/increment')"} "Increment") + (SPAN "count " (SPAN {class: "pg-val" "data-text": "$count"} "0")) + (BUTTON {"data-on:click": "@post('/themes/streaming/time')"} "Get time") + (SPAN "time " (SPAN {class: "pg-val" id: "stream-time"} "--:--:--")))) + (H2 "Streaming responses") + (toy "Chunks as they are produced" "A streaming pipeline like generate flushes each value to the client as an HTTP chunk, with no buffering." $chunks_src "nu") + (H2 "Server-sent events") + (toy "to sse" "Formats {data? id? event? retry?} records for text/event-stream, and sets the content-type automatically." $to_sse_src "nu") + (H2 "Worked example") + (P "The " + (A {href: "https://github.com/cablehead/http-nu/tree/main/examples/datastar-sdk"} "datastar-sdk example") + ": a button POSTs, the server streams an SSE patch back. Three patch kinds, one per button.") + (toy "The page (client)" "A count signal and buttons that POST to routes; data-text binds the signal into the DOM." $page_src "nu") + (toy "Patch a signal" "Read signals from the request, change one, stream a signal patch back." $patch_signals_src "nu") + (toy "Patch the DOM" "Stream an element and Datastar swaps it in by matching id." $patch_elements_src "nu") + (toy "Run a script" "Stream a script for the client to execute." $execute_script_src "nu") + (H2 "Run it") + (P "Serve the example with the embedded Datastar bundle:") + (code-toy $run_src "bash") + (P (A {href: "/themes/streaming/reference"} "Full reference: streaming responses, to sse, and streaming input ->"))) +} + +# --- theme namespace ------------------------------------------------- +# /themes/ is a theme: an overview that leads with something to poke, +# with a reference facet (a slice of the canonical /reference) one click away. +# The shell + facet nav generalize to every theme. + +def theme-facets [theme: string, current: string] { + let facets = [[slug, name]; [overview, Overview] [reference, Reference]] + (NAV {class: "facets"} + ($facets | each {|f| + let href = (if $f.slug == "overview" { $"/themes/($theme)" } else { $"/themes/($theme)/($f.slug)" }) + (A {href: $href class: (if $f.slug == $current { "active" } else { "" })} $f.name) + })) +} + +# reference sidebar: every section; the current one expands to its sub-sections +def ref-nav [current: string] { + let items = ($pages | each {|p| + let active = ($p.slug == $current) + let sub = (if $active { + let secs = ($page_secs | get $p.slug) + if ($secs | is-empty) { [] } else { + [(UL ($secs | each {|s| LI (A {href: $"/reference/($p.slug)#($s.slug)"} $s.title)}))] + } + } else { [] }) + (LI (A {href: $"/reference/($p.slug)" class: (if $active { "active" } else { "" })} $p.title) ...$sub) + }) + (NAV {class: "docs-side toc"} (UL $items)) +} + +# the first H1 of a Markdown doc, or a fallback +def md-title [fallback: string]: string -> string { + lines | where {|l| ($l | str starts-with "# ")} | get 0? | default $fallback | str replace "# " "" +} + +# Breadcrumb trail: Section / Page, every crumb a link (the last to itself). +# One universal scheme; callers pass [[section href] [page self-href]]. +def crumbs [trail: list] { + let parts = ($trail | enumerate | each {|it| + let link = (A {href: ($it.item | get 1)} ($it.item | get 0)) + if $it.index > 0 { [" / " $link] } else { [$link] } + } | flatten) + (P {class: "muted crumbs"} ...$parts) +} + +# page shell shared by every routed page: head, nav, the given main, then footer. +def page [title: string, main] { + (HTML {lang: "en"} + (page-head $title) + (BODY + (nav-bar) + $main + (site-footer) + (copy-script))) +} + +def theme-shell [theme: string, title: string, current: string, body] { + let trail = [["Themes" "/themes"] [$title $"/themes/($theme)"]] + (page $"($title) - http-nu" + (MAIN {class: "container"} + (crumbs $trail) + (H1 $title) + (theme-facets $theme $current) + $body)) +} + +# The playground render: a JSON context record through an inline template. +# Valid output is injected as HTML (the point); errors show as plain text. +def tpl-render [tpl: string, data: string] { + let ctx = (try { $data | from json } catch { {} }) + let res = (try { {ok: ($ctx | .mj --inline $tpl)} } catch {|e| {err: $e.msg} }) + if ($res.err? != null) { + (DIV {id: "tpl-out" class: "tpl-out is-err"} $res.err) + } else { + (DIV {id: "tpl-out" class: "tpl-out"} {__html: $res.ok}) + } +} + +# Preset snippets the chips load into the playground. +def tpl-preset [name: string] { + match $name { + "loop" => {tpl: "
    {% for i in items %}
  • {{ i }}
  • {% endfor %}
", data: '{"items": ["a", "b", "c"]}'} + "cond" => {tpl: "{% if vip %}VIP {{ name }}{% else %}hi {{ name }}{% endif %}", data: '{"name": "Sam", "vip": true}'} + "filter" => {tpl: "{{ name | upper }} has {{ name | length }} letters", data: '{"name": "ada"}'} + _ => {tpl: "Hello {{ name }}!", data: '{"name": "world"}'} + } +} + +def templates-overview [] { + let def_tpl = "Hello {{ name }}!" + let def_data = '{"name": "world"}' + (ARTICLE + (P "Render a page from data. The same render runs from three sources: an inline " + "snippet, files on disk, or templates kept in the event store. Poke at it:") + (DIV {class: "playground"} + (DIV {class: "pg-inputs"} + (DIV {class: "pg-field"} + (LABEL "template") + (TEXTAREA {"data-bind:tpl": true "data-on:input__debounce.300ms": "@post('/themes/templates/render')" rows: "3" spellcheck: "false"} $def_tpl)) + (DIV {class: "pg-field"} + (LABEL "data (JSON)") + (TEXTAREA {"data-bind:data": true "data-on:input__debounce.300ms": "@post('/themes/templates/render')" rows: "3" spellcheck: "false"} $def_data))) + (DIV {class: "pg-presets"} + (SPAN {class: "muted"} "try:") + (BUTTON {"data-on:click": "@post('/themes/templates/preset/greet')"} "greeting") + (BUTTON {"data-on:click": "@post('/themes/templates/preset/loop')"} "loop") + (BUTTON {"data-on:click": "@post('/themes/templates/preset/cond')"} "conditional") + (BUTTON {"data-on:click": "@post('/themes/templates/preset/filter')"} "filter")) + (DIV {class: "pg-output"} + (LABEL "output") + (tpl-render $def_tpl $def_data))) + (templates-files-section)) +} + +def gb-ensure [] { try { stor create -t guestbook -c {msg: str} } catch {} } +def guestbook-list [] { + gb-ensure + let rows = (stor open | query db "select msg from guestbook order by rowid desc limit 8") + (DIV {id: "guestbook" class: "guestbook"} + (if ($rows | is-empty) { + (P {class: "muted"} "no messages yet") + } else { + (UL ($rows | each {|r| (LI $r.msg)})) + })) +} + +def storage-overview [] { + let stor_src = r#' +# stor is an in-memory SQLite table: no server, no file +stor create -t guestbook -c {msg: str} +"hello" | wrap msg | stor insert -t guestbook +stor open | query db "select * from guestbook"'# + (ARTICLE + (P "Keep state without a database server: an in-memory SQLite via " (CODE "stor") + ", a local pub/sub " (CODE "bus") ", and an embedded cross.stream event store. " + "Here is " (CODE "stor") ", live:") + (DIV {class: "playground" "data-signals": "{msg: ''}"} + (P {class: "muted"} "A shared in-memory table. Sign it; rows last until the server restarts.") + (DIV {class: "pg-live"} + (INPUT {class: "pg-input" "data-bind:msg": true placeholder: "leave a message" spellcheck: "false"}) + (BUTTON {"data-on:click": "@post('/themes/storage/sign')"} "Sign")) + (guestbook-list)) + (H2 "How it works") + (toy "Insert and query" "stor is an in-memory SQLite table you can insert into and query with SQL, no setup." $stor_src "nu") + (P (A {href: "/themes/storage/reference"} "Full reference: stor, the bus, and the event store ->"))) +} + +# styled 404 page, with the 404 status set on the response +def not-found [] { + (page "Not found - http-nu" + (MAIN {class: "container"} + (ARTICLE + (H1 "Not found") + (P {class: "muted"} "That page does not exist.") + (P (A {href: "/"} "Back home ->"))))) + | metadata set { merge {'http.response': {status: 404}} } +} + +def tut-gb-ensure [] { try { stor create -t signatures -c {name: str, msg: str} } catch {} } +def tut-gb-list [] { + tut-gb-ensure + let rows = (stor open | query db "select name, msg from signatures order by rowid desc limit 8") + (DIV {id: "tut-guestbook" class: "guestbook"} + (if ($rows | is-empty) { + (P {class: "muted"} "no signatures yet") + } else { + (UL ($rows | each {|r| (LI (STRONG $r.name) $" -- ($r.msg)")})) + })) +} + +# the hello-world tutorial: a simulated terminal. Enter boots the server (its +# banner), then each curl click appends a response line. +# reusable terminal window chrome (the splash .terminal component): a titlebar +# with the mac dots + a label, and a body. +def terminal [title, body, --action: any = null] { + (DIV {class: "terminal"} + (DIV {class: "terminal-bar"} + (SPAN {class: "terminal-dots"} (SPAN) (SPAN) (SPAN)) + (SPAN {class: "terminal-title"} $title) + (if $action != null { (SPAN {class: "terminal-action"} $action) } else { "" })) + (DIV {class: "terminal-body"} $body)) +} + +# one server + client terminal pair, scripted like a screencast (no real +# server): "Start Server" reveals the captured boot banner and lights the key; +# "Run" reveals the request's log line and the curl response. s_sig / c_sig are +# the Datastar signal names that drive each reveal. +def demo-pair [ + server_cmd: string + client_cmd: string + banner: string + reqlog: string + s_sig: string + c_sig: string + response +] { + let s_show = ("$" + $s_sig) + let c_show = ("$" + $c_sig) + [ + (terminal "server" --action (BUTTON {class: "btn-go" "data-class:is-lit": $s_show "data-on:click": ($s_show + " = true")} "Start Server") + (DIV + (DIV {class: "term-cmd"} (SPAN {class: "prompt"} "$ ") (CODE $server_cmd)) + (DIV {class: "term-out"} + (PRE {"data-show": $s_show} $banner) + (DIV {"data-show": $c_show} $reqlog)))) + (DIV {class: "client-pane" "data-show": $s_show} + (terminal "client" --action (BUTTON {"data-on:click": ($c_show + " = true")} "Run") + (DIV + (DIV {class: "term-cmd"} (SPAN {class: "prompt"} "$ ") (CODE $client_cmd)) + (DIV {class: "term-out" "data-show": $c_show} $response)))) + ] +} + +# the bare interactive widget: a scripted hello-world (string), then the same +# again returning a record (json, curled with -v). Droppable into any page; +# framing prose (intro, next link) is supplied by the caller. +def hello-world-demo [] { + let banner = (open ($script_dir | path join demo-banner.txt)) + (DIV {"data-signals": "{started: false, curled: false, started2: false, curled2: false}"} + (DIV {class: "terminal-row"} + (demo-pair "http-nu :3001 -c '{|req| \"Hello, world!\"}'" "curl localhost:3001" + $banner "23:37:42.108 127.0.0.1 GET / 200 0ms 0ms 13b" "started" "curled" + "Hello, world!")) + (P "The string is returned as " (CODE "text/html") " by default. Return a record " + "and it becomes " (CODE "application/json") " automatically.") + (DIV {class: "terminal-row"} + (demo-pair "http-nu :3003 -c '{|req| {msg: \"Hello, world!\"} }'" "curl -v localhost:3003" + ($banner | str replace --all "3001" "3003") "23:38:55.102 127.0.0.1 GET / 200 0ms 0ms 23b" "started2" "curled2" + (DIV (DIV "< HTTP/1.1 200 OK") (DIV "< content-type: application/json") (DIV "{\"msg\":\"Hello, world!\"}"))))) +} + +def tutorial-hello-world [] { + (DIV + (P "http-nu takes a Nushell closure and serves it over HTTP. The closure " + "receives the request as its argument, and whatever it returns becomes the " + "response. Boot the smallest possible server, then curl it:") + (hello-world-demo) + (P (A {href: "/tutorials/build-a-live-guestbook"} "Next: build a live guestbook ->"))) +} + +# the getting-started tutorial: lead with the finished guestbook (live), then the +# step-by-step build rendered from its Markdown. +def tutorial-getting-started [] { + let md = (open --raw ($script_dir | path join tutorials build-a-live-guestbook.md) | decode utf-8 | .md | inject-copy-btns) + (DIV + (P {class: "muted"} "From hello-world to real-time updates. Here is the finished guestbook, live: sign it, then build it step by step below.") + (DIV {class: "playground" "data-signals": "{name: '', message: ''}"} + (DIV {class: "pg-live"} + (INPUT {class: "pg-input" "data-bind:name": true placeholder: "your name" spellcheck: "false"}) + (INPUT {class: "pg-input" "data-bind:message": true placeholder: "a message" spellcheck: "false"}) + (BUTTON {"data-on:click": "@post('/tutorials/guestbook/sign')"} "Sign")) + (P {class: "muted"} (SMALL "This demo keeps signatures in memory with stor; the tutorial below builds the persistent, cross-tab version with the event store and SSE.")) + (tut-gb-list)) + (H2 "Build it step by step") + $md) +} + +# theme registry: metadata + an overview builder per theme. Drives the /themes +# index and the generic /themes/:slug (+ /reference) routes, so adding a theme is +# one row here plus its overview def, no per-theme route boilerplate. +let themes = [ + [slug, title, ref, icon, blurb, overview]; + [templates, "Templates", "templates--output", "lucide:layout-template", + "Render a page from data: an editable .mj playground, then files and the store.", + {|| templates-overview}] + [streaming, "Streaming & events", "streaming--events", "lucide:zap", + "Stream chunks and push server-sent events, with a live SSE toy.", + {|| streaming-overview}] + [storage, "State & storage", "state--storage", "lucide:database", + "In-memory SQLite, a local bus, and an embedded event store. Sign the live guestbook.", + {|| storage-overview}] +] + +# tutorials registry: step-by-step builds, each with an interactive lead widget. +let tutorials = [ + [slug, title, blurb, builder]; + ["hello-world", "Hello world", + "The smallest http-nu server: a closure that returns a string. Boot it and curl it, right here.", + {|| tutorial-hello-world}] + ["build-a-live-guestbook", "Build a live guestbook", + "From hello-world to real-time updates: the HTML DSL, routing, the store, and Datastar SSE.", + {|| tutorial-getting-started}] +] + +# --- design system viewer -------------------------------------------- +# /design catalogs the site's vocabulary. Each component page shows a live +# example, then breaks the component into its parts; each part lists the +# design tokens behind it (a swatch for colors). + +# one token row: a role label, an optional color swatch, the token name. +def tok [label: string, token: string, --color] { + let bg = (if ($token | str starts-with "--") { $"var\(($token)\)" } else { $token }) + let sw = (if $color { + (SPAN {class: "tok-sw" style: $"background: ($bg)"}) + } else { + (SPAN {class: "tok-sw tok-sw-none"}) + }) + (DIV {class: "tok"} + (SPAN {class: "tok-label"} $label) + $sw + (CODE {class: "tok-name"} $token)) +} + +# one part of a component: name, gloss, an optional focused demo, its tokens. +def design-part [name: string, note: string, demo, tokens: list] { + (SECTION {class: "dz-part"} + (H3 $name) + (P {class: "muted"} $note) + (if $demo != null { (DIV {class: "dz-demo"} $demo) } else { "" }) + (DIV {class: "dz-tokens"} ...$tokens)) +} + +def design-window [] { + let example = (terminal "server" + --action (BUTTON {} "Run") + (DIV + (DIV {class: "term-cmd"} (SPAN {class: "prompt"} "$ ") (CODE "http-nu :3001 -c '{|req| \"hi\"}'")) + (DIV {class: "term-out"} "hi"))) + (DIV + (DIV {class: "dz-example"} $example) + (design-part "Frame" "The window itself: rounded, shadowed, monospace." null [ + (tok "radius" "--border-radius-2") + (tok "shadow" "--shadow-2") + (tok "font" "--font-mono") + (tok "size" "--font-size--1") + ]) + (design-part "Title bar" "Grape chrome strip: dots, title slot, action slot." null [ + (tok "background" "--named-grape-0" --color) + (tok "text" "--named-grape-0-on" --color) + (tok "padding" "0.2rem / --size--1") + (tok "gap" "--size--1") + ]) + (design-part "Traffic lights" "Three mac-style dots, decorative." (SPAN {class: "terminal-dots"} (SPAN) (SPAN) (SPAN)) [ + (tok "size" "0.7rem") + (tok "red" "--named-red-0" --color) + (tok "amber" "#ffbd2e" --color) + (tok "green" "--named-green-0" --color) + ]) + (design-part "Title slot" "Flex slot holding a label (server) or a tab strip (install methods)." null [ + (tok "gap" "--size--1") + ]) + (design-part "Action" "Optional control, pushed to the far edge." null [ + (tok "align" "margin-left: auto") + ]) + (design-part "Body" "The dark content area: code, output, anything." null [ + (tok "background" "rgba(0,0,0,0.25)" --color) + (tok "text" "--surface-on" --color) + (tok "padding" "--size--2") + (tok "line-height" "--code-line-height") + ])) +} + +# one brand row: a live swatch, name, the token to use, and where it shows up. +def pal-row [name: string, value: string, where: string] { + (TR + (TD (DIV {class: "pal-cell" style: $"background: var\(($value)\)"})) + (TD {class: "pal-name"} $name) + (TD (CODE {class: "tok-name"} $value)) + (TD {class: "muted"} $where)) +} + +# one ramp: the 5 shades Stellar generates for a named seed (-2 .. +2). +def ramp [name: string, base: string] { + (TR + (TD {class: "pal-name"} $name) + (TD (DIV {class: "pal-ramp"} + ...([-2 -1 0 1 2] | each {|n| + (DIV {class: "pal-cell" title: $"--($base)-($n)" style: $"background: var\(--($base)-($n)\)"}) + })))) +} + +def design-palette [] { + (DIV + (P {class: "muted"} "Our brand colors, then the full ramp Stellar generates for each from the config. 2048 reads the same stellar.css, so a config change moves both sites.") + (H2 "Brand") + (TABLE {class: "pal-table"} + (THEAD (TR (TH "") (TH "name") (TH "token") (TH "where we use it"))) + (TBODY + (pal-row "red" "--named-red-0" "Datastar badge, terminal dot") + (pal-row "orange" "--named-orange-0" "Nushell badge") + (pal-row "green" "--named-green-0" "HTTP Server badge, Start Server, terminal dot") + (pal-row "grape" "--named-grape-0" "cross.stream badge, terminal bar") + (pal-row "electric blue" "--named-stream-0" "links, the cross.stream wordmark") + (pal-row "ocean" "--named-ocean-0" "page surface (light)") + (pal-row "navy" "--named-navy-0" "page surface (dark)") + (pal-row "sand" "--named-sand-0" "the http-nu wordmark, headers"))) + (H2 "Stellar palette") + (P {class: "muted"} "Each seed expands to a 5-step ramp, plus an -on and -dim per shade, generated from the config.") + (TABLE {class: "pal-table"} + (TBODY + (ramp "red" "named-red") + (ramp "orange" "named-orange") + (ramp "green" "named-green") + (ramp "grape" "named-grape") + (ramp "ocean" "named-ocean") + (ramp "navy" "named-navy") + (ramp "sand" "named-sand") + (ramp "electric blue" "named-stream")))) +} + +def design-button [] { + (DIV + (DIV {class: "dz-example dz-btns"} + (BUTTON "Default") + (BUTTON {class: "btn-go is-lit"} "Go") + (DIV {class: "terminal-bar"} + (BUTTON {class: "btn-tab is-active"} "Active") + (BUTTON {class: "btn-tab"} "Tab") + (BUTTON {class: "btn-tab"} "Tab"))) + (design-part "Base" "Mono, with a currentColor outline that reads on any surface." (BUTTON "Button") [ + (tok "font" "--font-mono") + (tok "size" "--font-size--1") + (tok "radius" "--border-radius-1") + (tok "border" "currentColor 35%") + (tok "padding" "0.1em / 0.6em") + ]) + (design-part "Go variant (.btn-go)" "The lit primary key for a boot action; turns green when .is-lit." (BUTTON {class: "btn-go is-lit"} "Start Server") [ + (tok "lit bg" "--named-green--1" --color) + (tok "lit text" "--named-green--1-on" --color) + (tok "key shadow" "0 2px 0 rgba(0,0,0,.3)") + ]) + (design-part "Tab variant (.btn-tab)" "A segmented selector for a window titlebar; quiet until .is-active." (DIV {class: "terminal-bar"} (BUTTON {class: "btn-tab is-active"} "Active") (BUTTON {class: "btn-tab"} "Tab")) [ + (tok "inactive" "opacity 0.5") + (tok "active" "opacity 1 + bold") + ])) +} + +let design_catalog = [ + [slug, title, blurb, builder]; + ["palette", "Palette", + "Our brand colors and the full Stellar ramp they generate.", + {|| design-palette}] + ["window", "Window", + "A chrome panel for code and output: a title bar over a dark body. The install tabs and the run demos are all this one component.", + {|| design-window}] + ["button", "Button", + "One mono outline button, with go (lit) and tab (segmented) variants.", + {|| design-button}] +] + +def design-nav [current: string] { + (NAV {class: "docs-side toc"} + (UL ($design_catalog | each {|c| + (LI (A {href: $"/design/($c.slug)" class: (if $c.slug == $current { "active" } else { "" })} $c.title)) + }))) +} + +def dz-page [slug: string] { + let entry = ($design_catalog | where slug == $slug | first) + (page $"($entry.title) - Design - http-nu" + (MAIN {class: "container with-sidebar"} + (DIV {class: "docs-menu" "data-signals:nav": "false" "data-class:open": "$nav"} + (BUTTON {class: "docs-toggle" "data-on:click": "$nav = !$nav"} "Components") + (design-nav $slug)) + (ARTICLE + (crumbs [["Design" "/design"] [$entry.title $"/design/($slug)"]]) + (H1 $entry.title) + (P {class: "muted"} $entry.blurb) + (do $entry.builder)))) +} + +# the existing examples hub, mounted as-is at /examples (not yet folded into the +# design system). Same source + mount the old ./www used. +let examples = source ../examples/serve.nu + +{|req| + dispatch $req [ + + # examples hub, served as-is until folded into the design system + (mount "/examples" $examples) + + # landing page + (route {method: GET path: "/"} {|req ctx| + (HTML {lang: "en"} + (page-head "http-nu") + (BODY + (splash-hero) + (MAIN {class: "container"} + (give-it-a-try) + + (P "Then hand it a Nushell closure, and you have a server:") + (DIV {class: "demo-wide"} (hello-world-demo)) + + (section-head "Why http-nu") + (DIV {class: "grid"} + ($themes | each {|t| + (A {class: "card panel" href: $"/themes/($t.slug)"} + (H3 (icon $t.icon) $" ($t.title)") + (P {class: "muted"} $t.blurb)) + })) + ) + (site-footer) + (copy-script) + ) + ) + }) + + # themes index (registry-driven) + (route {method: GET path: "/themes"} {|req ctx| + (page "Themes - http-nu" + (MAIN {class: "container"} + (ARTICLE + (H1 "Themes") + (P {class: "muted"} "Each theme leads with something to poke, then the reference behind it.") + (DIV {class: "grid"} + ($themes | each {|t| + (A {class: "card panel" href: $"/themes/($t.slug)"} + (H3 (icon $t.icon) $" ($t.title)") + (P {class: "muted"} $t.blurb)) + })) + (P (A {href: "/reference"} "Browse the full reference ->"))))) + }) + + # generic theme overview + reference (registry-driven) + (route {path-matches: "/themes/:slug"} {|req ctx| + let t = ($themes | where slug == $ctx.slug | get 0?) + if $t == null { (not-found) } else { + (theme-shell $t.slug $t.title "overview" (do $t.overview)) + } + }) + (route {path-matches: "/themes/:slug/reference"} {|req ctx| + let t = ($themes | where slug == $ctx.slug | get 0?) + if $t == null { (not-found) } else { + let page = ($pages | where slug == $t.ref | first) + let content = ($readme | render-page $page $anchors --base "/reference" | inject-copy-btns) + (theme-shell $t.slug $t.title "reference" (ARTICLE $content)) + } + }) + + # reference index: section cards (the README, section by section) + (route {method: GET path: "/reference"} {|req ctx| + (page "Reference - http-nu" + (MAIN {class: "container"} + (ARTICLE + (H1 "Reference") + (P {class: "muted"} "The project README, section by section. " + (A {href: "/how-tos/render-readme-as-doc-site"} "Rendered as a doc site ->")) + (DIV {class: "grid"} + ($pages | each {|p| + let secs = ($page_secs | get $p.slug) + (A {class: "card panel" href: $"/reference/($p.slug)"} + (H3 $p.title) + (if ($secs | is-empty) { "" } else { (SMALL ($secs | get title | str join " \u{b7} ")) })) + }))))) + }) + + # reference section: one README section with sidebar + pager + (route {path-matches: "/reference/:slug"} {|req ctx| + let idx = ($pages | enumerate | where item.slug == $ctx.slug | get index.0? | default null) + if $idx == null { + (not-found) + } else { + let page = ($pages | get $idx) + let prev = (if $idx > 0 { $pages | get ($idx - 1) } else { null }) + let next = ($pages | get -o ($idx + 1)) + let content = ($readme | render-page $page $anchors --base "/reference" | inject-copy-btns) + (page $"($page.title) - http-nu" + (MAIN {class: "container with-sidebar"} + (DIV {class: "docs-menu" "data-signals:nav": "false" "data-class:open": "$nav"} + (BUTTON {class: "docs-toggle" "data-on:click": "$nav = !$nav"} "Sections") + (ref-nav $page.slug)) + (ARTICLE + (crumbs [["Reference" "/reference"] [$page.title $"/reference/($page.slug)"]]) + $content + (NAV {class: "pager"} + (if ($idx > 0) { + (A {class: "pager-link panel pager-prev" href: $"/reference/($prev.slug)"} + (SPAN {class: "pager-dir"} (icon "lucide:arrow-left") "Previous") + (SPAN {class: "pager-page"} $prev.title)) + } else { "" }) + (if ($next != null) { + (A {class: "pager-link panel pager-next" href: $"/reference/($next.slug)"} + (SPAN {class: "pager-dir"} "Next" (icon "lucide:arrow-right")) + (SPAN {class: "pager-page"} $next.title)) + } else { "" }))))) + } + }) + + # how-tos: collected guides, each a Markdown file in how-tos/ rendered by .md + (route {method: GET path: "/how-tos"} {|req ctx| + let guides = (ls ($script_dir | path join how-tos) | where name =~ '\.md$' | sort-by name | each {|f| + let slug = ($f.name | path basename | str replace --regex '\.md$' "") + let title = (open --raw $f.name | decode utf-8 | md-title $slug) + {slug: $slug, title: $title} + }) + (page "How-tos - http-nu" + (MAIN {class: "container"} + (ARTICLE + (H1 "How-tos") + (P {class: "muted"} "Task guides, each a Markdown file rendered the way it describes.") + (DIV {class: "grid"} + ($guides | each {|g| + (A {class: "card panel" href: $"/how-tos/($g.slug)"} + (H3 (icon "lucide:file-text") $" ($g.title)")) + }))))) + }) + + (route {path-matches: "/how-tos/:slug"} {|req ctx| + let path = ($script_dir | path join how-tos | path join $"($ctx.slug).md") + if ($path | path exists) { + let raw = (open --raw $path | decode utf-8) + let title = ($raw | md-title $ctx.slug) + (page $"($title) - http-nu" + (MAIN {class: "container"} + (ARTICLE + (crumbs [["How-tos" "/how-tos"] [$title $"/how-tos/($ctx.slug)"]]) + ($raw | .md | inject-copy-btns)))) + } else { + (not-found) + } + }) + + # tutorials: step-by-step builds with an interactive lead widget + (route {method: GET path: "/tutorials"} {|req ctx| + (page "Tutorials - http-nu" + (MAIN {class: "container"} + (ARTICLE + (H1 "Tutorials") + (P {class: "muted"} "Step-by-step builds you can poke as you go.") + (DIV {class: "grid"} + ($tutorials | each {|t| + (A {class: "card panel" href: $"/tutorials/($t.slug)"} + (H3 (icon "lucide:graduation-cap") $" ($t.title)") + (P {class: "muted"} $t.blurb)) + }))))) + }) + (route {path-matches: "/tutorials/:slug"} {|req ctx| + let t = ($tutorials | where slug == $ctx.slug | get 0?) + if $t == null { (not-found) } else { + (page $"($t.title) - http-nu" + (MAIN {class: "container"} + (ARTICLE + (crumbs [["Tutorials" "/tutorials"] [$t.title $"/tutorials/($t.slug)"]]) + (H1 $t.title) + (do $t.builder)))) + } + }) + (route {method: POST path: "/tutorials/guestbook/sign"} {|req ctx| + let s = (from datastar-signals $req) + let name = ($s.name? | default "" | str trim) + let message = ($s.message? | default "" | str trim) + tut-gb-ensure + if ($name != "" and $message != "") { {name: $name, msg: $message} | stor insert -t signatures } + [ ({name: "", message: ""} | to datastar-patch-signals) ((tut-gb-list) | to datastar-patch-elements) ] | to sse + }) + # design system viewer: /design redirects to the first component + (route {method: GET path: "/design"} {|req ctx| + let first = ($design_catalog | first | get slug) + "" | metadata set { merge {'http.response': {status: 302 headers: {Location: $"/design/($first)"}}} } + }) + (route {path-matches: "/design/:slug"} {|req ctx| + let entry = ($design_catalog | where slug == $ctx.slug | get 0?) + if $entry == null { (not-found) } else { (dz-page $ctx.slug) } + }) + + # theme interactions: streaming's live SSE buttons + (route {method: POST path: "/themes/streaming/increment"} {|req ctx| + let s = (from datastar-signals $req) + let count = (($s.count? | default 0) + 1) + {count: $count} | to datastar-patch-signals | to sse + }) + (route {method: POST path: "/themes/streaming/time"} {|req ctx| + (DIV {id: "stream-time"} (date now | format date "%H:%M:%S")) | to datastar-patch-elements | to sse + }) + # theme interactions: storage's live stor guestbook + (route {method: POST path: "/themes/storage/sign"} {|req ctx| + let s = (from datastar-signals $req) + let msg = ($s.msg? | default "" | str trim) + gb-ensure + if ($msg != "") { $msg | wrap msg | stor insert -t guestbook } + [ ({msg: ""} | to datastar-patch-signals) ((guestbook-list) | to datastar-patch-elements) ] | to sse + }) + + # theme interactions: templates' live .mj playground + (route {method: POST path: "/themes/templates/render"} {|req ctx| + let s = (from datastar-signals $req) + (tpl-render ($s.tpl? | default "") ($s.data? | default "{}")) | to datastar-patch-elements | to sse + }) + (route {path-matches: "/themes/templates/preset/:name"} {|req ctx| + let p = (tpl-preset $ctx.name) + [ + ({tpl: $p.tpl, data: $p.data} | to datastar-patch-signals) + ((tpl-render $p.tpl $p.data) | to datastar-patch-elements) + ] | to sse + }) + + # static assets (stellar.css, base.css, images) + (route {path-matches: "/assets/:file"} {|req ctx| + .static ($script_dir | path join "assets") $ctx.file + }) + ] +}