diff --git a/.claude/worktrees/relaxed-mcclintock b/.claude/worktrees/relaxed-mcclintock deleted file mode 160000 index 2ebe237..0000000 --- a/.claude/worktrees/relaxed-mcclintock +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 2ebe237aab189a1827a8263b166707f45d4bd965 diff --git a/.gitignore b/.gitignore index b22f8a0..0680ab2 100644 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,9 @@ dist/ # generated types .astro/ +# Claude Code agent worktrees / local config +.claude/ + # dependencies node_modules/ diff --git a/astro.config.mjs b/astro.config.mjs index a183d63..8b6c608 100644 --- a/astro.config.mjs +++ b/astro.config.mjs @@ -1,11 +1,27 @@ import { defineConfig } from 'astro/config'; import starlight from '@astrojs/starlight'; +import sitemap from '@astrojs/sitemap'; export default defineConfig({ output:'static', site: "https://docs.consensus.canister.software", base: "/", integrations: [ + sitemap({ + changefreq: 'weekly', + priority: 0.7, + serialize(item) { + // Landing page: top-priority entry point + if (item.url === 'https://docs.consensus.canister.software/') { + item.priority = 1.0; + } + // Node-operator + quickstart funnels: slight boost + else if (/\/(guides\/node|quickstart)\//.test(item.url)) { + item.priority = 0.9; + } + return item; + }, + }), starlight({ title: 'Consensus Docs', @@ -41,10 +57,6 @@ export default defineConfig({ label: 'CLI', autogenerate: { directory: 'cli' }, }, - { - label: 'x402 Proxy', - autogenerate: {directory:'x402proxy'}, - }, { label: 'Facilitator', autogenerate: {directory:'facilitator'}, diff --git a/opentui-drafts/tunnel-setup.html b/opentui-drafts/tunnel-setup.html new file mode 100644 index 0000000..1c7eb32 --- /dev/null +++ b/opentui-drafts/tunnel-setup.html @@ -0,0 +1,741 @@ + + + + + Consensus · Tunnel Setup (opentui-faithful) + + + + + + + + +
+ +
+
+
+
consensus — ~/code/consensus — 168×42
+
$ consensus tunnel
+
+ +
+ + +
+
+ CONSENSUS + your private network, on demand +
+
+ connected + acct bob + bal $24.18 + v 2.4.1 +
+
+ + +
+
+
+ + Tunnels/New tunnel +
+
pick a local process or LAN device — we'll mint the public URL
+
+
+ Protocol +
+ + +
+
+
+ + +
+ + +
+
Local processes
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
#PortProcess
1:3000node · my-api
2:5173vite
3:8000python -m http
:5432postgres · system
+
R rescan local · 4 ports
+
+ + +
+
LAN devices
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
#IPHostPorts
4192.168.1.1gateway:80, :443
5192.168.1.66esp32-sensor:80
6192.168.1.42pi-camera:8554
7192.168.1.55homelab-ha:8123
+
+ 3 devices hidden + A show all + · + L rescan LAN +
+
+ +
+ + +
+
Target
+
+
+
+
+ Target + localhost +
+
IP address or hostname
+
+
+
+ Port + 3000 + optional — leave blank to use default +
+
+
✕ placeholder
+
+ +
+ Will create + https://amber-otter.consensus.canister.software + + + localhost:3000 + · + http · region auto · free tier + +
+ + or press +
+
+
+
+ + +
+
+ Bookmarks + M save current · O load +
+
+
+ 1 + my-api + http + localhost:3000 + 2h ago +
+
+ 2 + homelab-ha + http + 192.168.1.55:8123 + 1d ago +
+
+ 3 + db-tunnel + tcp + 192.168.1.42:22 + 3d ago +
+
+
+ + +
+ 1-7select + Rrescan local + Lrescan LAN + Ashow all + Mbookmark + ↑↓navigate + start + Bback + + tunnel setup +
+ +
+
+ +
+ screen tunnel setup (pick a target) + grid 168×42 cells · jetbrains mono + palette rosé pine + toggle use the Tweaks button to flip protocol / theme / tier +
+ +
+ + + + + + + +
+ + + + + diff --git a/package-lock.json b/package-lock.json index 1d8328f..ee56f7a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,6 +8,7 @@ "name": "magical-meteor", "version": "0.0.1", "dependencies": { + "@astrojs/sitemap": "3.6.1", "@astrojs/starlight": "^0.37.2", "astro": "^5.6.1", "sharp": "^0.34.2" diff --git a/package.json b/package.json index b05a790..a4c7f1c 100644 --- a/package.json +++ b/package.json @@ -10,8 +10,9 @@ "astro": "astro" }, "dependencies": { + "@astrojs/sitemap": "3.6.1", "@astrojs/starlight": "^0.37.2", "astro": "^5.6.1", "sharp": "^0.34.2" } -} \ No newline at end of file +} diff --git a/public/JavaScript-logo.png b/public/JavaScript-logo.png new file mode 100644 index 0000000..0da1dcd Binary files /dev/null and b/public/JavaScript-logo.png differ diff --git a/public/chains/ethereum.png b/public/chains/ethereum.png new file mode 100644 index 0000000..19f0aa0 Binary files /dev/null and b/public/chains/ethereum.png differ diff --git a/public/chains/icp.png b/public/chains/icp.png new file mode 100644 index 0000000..ba5346a Binary files /dev/null and b/public/chains/icp.png differ diff --git a/public/chains/solana.svg b/public/chains/solana.svg new file mode 100644 index 0000000..14c18b3 --- /dev/null +++ b/public/chains/solana.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/public/fonts/OFL-JetBrainsMono.txt b/public/fonts/OFL-JetBrainsMono.txt new file mode 100644 index 0000000..3f34847 --- /dev/null +++ b/public/fonts/OFL-JetBrainsMono.txt @@ -0,0 +1,93 @@ +Copyright 2020 The JetBrains Mono Project Authors (https://github.com/JetBrains/JetBrainsMono) + +This Font Software is licensed under the SIL Open Font License, Version 1.1. +This license is copied below, and is also available with a FAQ at: +https://openfontlicense.org + + +----------------------------------------------------------- +SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 +----------------------------------------------------------- + +PREAMBLE +The goals of the Open Font License (OFL) are to stimulate worldwide +development of collaborative font projects, to support the font creation +efforts of academic and linguistic communities, and to provide a free and +open framework in which fonts may be shared and improved in partnership +with others. + +The OFL allows the licensed fonts to be used, studied, modified and +redistributed freely as long as they are not sold by themselves. The +fonts, including any derivative works, can be bundled, embedded, +redistributed and/or sold with any software provided that any reserved +names are not used by derivative works. The fonts and derivatives, +however, cannot be released under any other type of license. The +requirement for fonts to remain under this license does not apply +to any document created using the fonts or their derivatives. + +DEFINITIONS +"Font Software" refers to the set of files released by the Copyright +Holder(s) under this license and clearly marked as such. This may +include source files, build scripts and documentation. + +"Reserved Font Name" refers to any names specified as such after the +copyright statement(s). + +"Original Version" refers to the collection of Font Software components as +distributed by the Copyright Holder(s). + +"Modified Version" refers to any derivative made by adding to, deleting, +or substituting -- in part or in whole -- any of the components of the +Original Version, by changing formats or by porting the Font Software to a +new environment. + +"Author" refers to any designer, engineer, programmer, technical +writer or other person who contributed to the Font Software. + +PERMISSION & CONDITIONS +Permission is hereby granted, free of charge, to any person obtaining +a copy of the Font Software, to use, study, copy, merge, embed, modify, +redistribute, and sell modified and unmodified copies of the Font +Software, subject to the following conditions: + +1) Neither the Font Software nor any of its individual components, +in Original or Modified Versions, may be sold by itself. + +2) Original or Modified Versions of the Font Software may be bundled, +redistributed and/or sold with any software, provided that each copy +contains the above copyright notice and this license. These can be +included either as stand-alone text files, human-readable headers or +in the appropriate machine-readable metadata fields within text or +binary files as long as those fields can be easily viewed by the user. + +3) No Modified Version of the Font Software may use the Reserved Font +Name(s) unless explicit written permission is granted by the corresponding +Copyright Holder. This restriction only applies to the primary font name as +presented to the users. + +4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font +Software shall not be used to promote, endorse or advertise any +Modified Version, except to acknowledge the contribution(s) of the +Copyright Holder(s) and the Author(s) or with their explicit written +permission. + +5) The Font Software, modified or unmodified, in part or in whole, +must be distributed entirely under this license, and must not be +distributed under any other license. The requirement for fonts to +remain under this license does not apply to any document created +using the Font Software. + +TERMINATION +This license becomes null and void if any of the above conditions are +not met. + +DISCLAIMER +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT +OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE +COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL +DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM +OTHER DEALINGS IN THE FONT SOFTWARE. diff --git a/public/fonts/OFL-SpaceGrotesk.txt b/public/fonts/OFL-SpaceGrotesk.txt new file mode 100644 index 0000000..014b75d --- /dev/null +++ b/public/fonts/OFL-SpaceGrotesk.txt @@ -0,0 +1,93 @@ +Copyright 2020 The Space Grotesk Project Authors (https://github.com/floriankarsten/space-grotesk) + +This Font Software is licensed under the SIL Open Font License, Version 1.1. +This license is copied below, and is also available with a FAQ at: +https://openfontlicense.org + + +----------------------------------------------------------- +SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 +----------------------------------------------------------- + +PREAMBLE +The goals of the Open Font License (OFL) are to stimulate worldwide +development of collaborative font projects, to support the font creation +efforts of academic and linguistic communities, and to provide a free and +open framework in which fonts may be shared and improved in partnership +with others. + +The OFL allows the licensed fonts to be used, studied, modified and +redistributed freely as long as they are not sold by themselves. The +fonts, including any derivative works, can be bundled, embedded, +redistributed and/or sold with any software provided that any reserved +names are not used by derivative works. The fonts and derivatives, +however, cannot be released under any other type of license. The +requirement for fonts to remain under this license does not apply +to any document created using the fonts or their derivatives. + +DEFINITIONS +"Font Software" refers to the set of files released by the Copyright +Holder(s) under this license and clearly marked as such. This may +include source files, build scripts and documentation. + +"Reserved Font Name" refers to any names specified as such after the +copyright statement(s). + +"Original Version" refers to the collection of Font Software components as +distributed by the Copyright Holder(s). + +"Modified Version" refers to any derivative made by adding to, deleting, +or substituting -- in part or in whole -- any of the components of the +Original Version, by changing formats or by porting the Font Software to a +new environment. + +"Author" refers to any designer, engineer, programmer, technical +writer or other person who contributed to the Font Software. + +PERMISSION & CONDITIONS +Permission is hereby granted, free of charge, to any person obtaining +a copy of the Font Software, to use, study, copy, merge, embed, modify, +redistribute, and sell modified and unmodified copies of the Font +Software, subject to the following conditions: + +1) Neither the Font Software nor any of its individual components, +in Original or Modified Versions, may be sold by itself. + +2) Original or Modified Versions of the Font Software may be bundled, +redistributed and/or sold with any software, provided that each copy +contains the above copyright notice and this license. These can be +included either as stand-alone text files, human-readable headers or +in the appropriate machine-readable metadata fields within text or +binary files as long as those fields can be easily viewed by the user. + +3) No Modified Version of the Font Software may use the Reserved Font +Name(s) unless explicit written permission is granted by the corresponding +Copyright Holder. This restriction only applies to the primary font name as +presented to the users. + +4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font +Software shall not be used to promote, endorse or advertise any +Modified Version, except to acknowledge the contribution(s) of the +Copyright Holder(s) and the Author(s) or with their explicit written +permission. + +5) The Font Software, modified or unmodified, in part or in whole, +must be distributed entirely under this license, and must not be +distributed under any other license. The requirement for fonts to +remain under this license does not apply to any document created +using the Font Software. + +TERMINATION +This license becomes null and void if any of the above conditions are +not met. + +DISCLAIMER +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT +OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE +COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL +DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM +OTHER DEALINGS IN THE FONT SOFTWARE. diff --git a/public/fonts/jetbrains-mono-variable.woff2 b/public/fonts/jetbrains-mono-variable.woff2 new file mode 100644 index 0000000..2c68c08 Binary files /dev/null and b/public/fonts/jetbrains-mono-variable.woff2 differ diff --git a/public/fonts/space-grotesk-variable.woff2 b/public/fonts/space-grotesk-variable.woff2 new file mode 100644 index 0000000..ef68853 Binary files /dev/null and b/public/fonts/space-grotesk-variable.woff2 differ diff --git a/public/logo-light.svg b/public/logo-light.svg new file mode 100644 index 0000000..838623f --- /dev/null +++ b/public/logo-light.svg @@ -0,0 +1,73 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/public/opentui/nodes.html b/public/opentui/nodes.html new file mode 100644 index 0000000..90c0930 --- /dev/null +++ b/public/opentui/nodes.html @@ -0,0 +1,483 @@ + + + + + + Consensus · Nodes (opentui-faithful) + + + + + + +
+ +
+
+
+
consensus — ~/code/consensus — 168×46
+
$ consensus nodes
+
+ +
+ + +
+
+ CONSENSUS + your private network, on demand +
+
+ connected + acct bob + bal $24.18 + v 2.4.1 +
+
+ + +
+
+
+ + Nodes +
+
lease a node to pin all tunnel, proxy & socket traffic to it
+
+
148 nodes · 5 regions · refreshed 12s ago
+
+ + +
+ + No lease active — traffic uses automatic node selection +
+ + +
+ + + + + + + sort: score ↓ +
+ + +
+
Available nodes
+ + + + + + + + + + + + + + + +
NodeDomainRegionIPScoreLatencyLoadCaps
+
+ ↑↓ navigate + lease selected + · + showing 8 of 148 · pin all proxy/tunnel/ws traffic to this node +
+
+ + +
+ ↑↓navigate + lease selected + Drelease + /filter + Rrefresh + Bback + + node browser +
+ +
+ + +
+ +
+ +
+ +
+ screen nodes (browse & lease) + grid 168×46 cells · jetbrains mono + palette rosé pine + toggle click a row then ↵ / Lease · use Tweaks for theme / tier +
+ +
+ + + + + + diff --git a/public/opentui/proxy.html b/public/opentui/proxy.html new file mode 100644 index 0000000..33553a2 --- /dev/null +++ b/public/opentui/proxy.html @@ -0,0 +1,489 @@ + + + + + + Consensus · Proxy Live (opentui-faithful) + + + + + + +
+ +
+
+
+
consensus — ~/code/consensus — 168×46
+
$ consensus proxy forward
+
+ +
+ + +
+
+ CONSENSUS + your private network, on demand +
+
+ live + acct bob + tier free + v 2.4.1 +
+
+ + +
+
RUNNING :8080
+
+ app :3000consensus·preload +
+
uptime 00:42:18
+
+ + +
+
+ Requests + 1,204 + ~26 / min +
+
+ Last status + 200 + success +
+
+ Cache hits + n/a + reverse only +
+ + +
+ Success rate + 98% + last 40 checks +
+
+ + +
+ + +
+
+
Latency · last 40
+
+
+ current38 ms + avg52 ms + p95180 ms + samples40 +
+
+ +
+
Throughput
+
+
+ ↑ Sent + 8.42 MB + 112.4 KB/s +
+
+ ↓ Received + 41.7 MB + 486.1 KB/s +
+
+
+ +
+
Recent checks
+
+
39 ok · 1 failed · last 40 health probes
+
+
+ + +
+
+
App control
+
+ App status + running pid 4821 +
+
+ Last probe + reachable · 200 /healthz · 31 ms · 2s ago +
+
+ Launch cmd + bun --preload .consensus-preload.ts ~/code/my-api/server.ts +
+
+ Fetch mode + preload → globalThis.fetch → consensus +
+
+ Preload + .consensus-preload.ts · auto restart enabled +
+
+ Routing + inclusive · /api, /v2 · subroutes on +
+
+ Node + us-west-1 · auto · 38 ms +
+
+ + +
+
+
+ +
+ + +
+ Llaunch + Ttest + ↑↓scroll + Sstop proxy + Bback + + forward proxy · live +
+ +
+
+ +
+ screen proxy live (running dashboard) + grid 168×46 cells · jetbrains mono + palette rosé pine + toggle use Tweaks to flip type / theme / tier +
+ +
+ + + + + + diff --git a/public/opentui/tunnel.html b/public/opentui/tunnel.html new file mode 100644 index 0000000..f401dd7 --- /dev/null +++ b/public/opentui/tunnel.html @@ -0,0 +1,672 @@ + + + + + + Consensus · Active Tunnel (opentui-faithful) + + + + + + +
+ +
+
+
+
consensus — ~/code/consensus — 168×42
+
$ consensus tunnel http localhost:3000
+
+ +
+ + +
+
+ CONSENSUS + your private network, on demand +
+
+ live + acct bob + bal $24.18 + v 2.4.1 +
+
+ + +
+
CONNECTED
+
+ https://amber-otter.consensus.canister.software + +
+
uptime 01:24:07
+
+ + +
+
+ Forwarding to + localhost:3000 + http · node · my-api +
+
+ Requests + 3,418 + ~14 / min +
+
+ Active + 2streams + live connections +
+
+ ↑ Sent + 8.42MB + ↓ 41.7 MB recv +
+
+ Error rate + 1.2% + last 100 reqs +
+
+ + +
+
Activity
+
+ streaming + + 20 most recent · newest on top +
+ + + + + + + + + + +
TimeMethodPathStatusLatencySize
+
+ + +
+
latency 38ms
+
avg 52ms
+
p95 180ms
+
last 40 + +
+
+
region sfo · auto
+
+ + +
+ Cclear log + Ffilter + Ppause stream + ↑↓scroll + inspect + Qstop tunnel + + active tunnel +
+ +
+
+ +
+ screen active tunnel (live traffic) + grid 168×42 cells · jetbrains mono + palette rosé pine + toggle use Tweaks to flip theme / protocol / live stream +
+ +
+ + + + + + diff --git a/public/opentui/websocket.html b/public/opentui/websocket.html new file mode 100644 index 0000000..27e0950 --- /dev/null +++ b/public/opentui/websocket.html @@ -0,0 +1,378 @@ + + + + + + Consensus · WebSocket Live (opentui-faithful) + + + + + + +
+ +
+
+
+
consensus — ~/code/consensus — 168×42
+
$ consensus ws · hybrid 60min 500MB
+
+ +
+ + +
+
+ CONSENSUS + your private network, on demand +
+
+ live + acct bob + bal $24.18 + v 2.4.1 +
+
+ + +
+
CONNECTED
+
HYBRID · 60 min · 500 MB · node sfo-3
+
expires 59:41 remaining
+
+ + +
+
Messages
+ + + + + + +
TimeDirMessageBytes
+
waiting for messages…
+
+ + + + + +
+
data + + 3.1 KB/ 500 MB +
+
rtt
+
avg
+
p95
+
msgs0
+
+
uptime00:00:18
+
+ + +
+ Ssend + Cclear + Qclose session + + websocket · live +
+ +
+
+ +
+ screen websocket live (metered socket) + grid 168×42 cells · jetbrains mono + palette rosé pine + try press S to compose · type · ↵ to send (server echoes back) +
+ +
+ + + + + + diff --git a/public/robots.txt b/public/robots.txt new file mode 100644 index 0000000..210df35 --- /dev/null +++ b/public/robots.txt @@ -0,0 +1,6 @@ +# robots.txt — https://docs.consensus.canister.software +# Full access for all crawlers, including search engines and AI answer/training bots. +User-agent: * +Allow: / + +Sitemap: https://docs.consensus.canister.software/sitemap-index.xml diff --git a/public/seo/consensus-node-provider-card.png b/public/seo/consensus-node-provider-card.png new file mode 100644 index 0000000..16961c3 Binary files /dev/null and b/public/seo/consensus-node-provider-card.png differ diff --git a/public/seo/consensus-protocol-seo-card.png b/public/seo/consensus-protocol-seo-card.png new file mode 100644 index 0000000..ba5dc66 Binary files /dev/null and b/public/seo/consensus-protocol-seo-card.png differ diff --git a/public/typescript_logo.webp b/public/typescript_logo.webp new file mode 100644 index 0000000..d90a4c9 Binary files /dev/null and b/public/typescript_logo.webp differ diff --git a/src/components/Head.astro b/src/components/Head.astro index 33e2753..a7bd84a 100644 --- a/src/components/Head.astro +++ b/src/components/Head.astro @@ -31,6 +31,10 @@ const hasTwitterCard = hasMeta('name', 'twitter:card'); {!hasTwitterImage && } {!hasTwitterCard && } + + + + diff --git a/src/content/docs/index.mdx b/src/content/docs/index.mdx deleted file mode 100644 index 05a3ef5..0000000 --- a/src/content/docs/index.mdx +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: Consensus Protocol -description: Get started building with Consensus. -template: splash -hero: - tagline: A decentralised x402 HTTPS proxy and websocket service network. - image: - dark: ../../assets/logo-light.svg - light: ../../assets/logo-dark.svg - actions: - - text: Learn more - link: /protocol/info/ - icon: right-arrow - - text: Read the Whitepaper - link: "#whitepaper" - variant: minimal - attrs: - class: wp-cta - aria-haspopup: dialog ---- - -import WhitepaperModal from "../../components/WhitepaperModal.astro"; - - diff --git a/src/content/docs/protocol/api.md b/src/content/docs/protocol/api.md index 567f4d8..b379730 100644 --- a/src/content/docs/protocol/api.md +++ b/src/content/docs/protocol/api.md @@ -154,7 +154,7 @@ Routes an outbound HTTP request through the Consensus network. Identical request | `x-node-domain` | `string` | Route through a specific node domain | | `x-node-exclude` | `string` | Exclude a specific node domain from selection | | `x-idempotency-key` | `string` | Manual deduplication key. Auto-generated if omitted | -| `x-api-key` | `string` | Scopes the deduplication key — requests with different keys never share cache entries | +| `x-api-key` | `string` | **Deprecated and inert.** Stripped before the upstream call; it no longer scopes the deduplication key or carries any identity. | **Payment** diff --git a/src/content/docs/protocol/architecture.md b/src/content/docs/protocol/architecture.md index 2f7c0fd..12071d8 100644 --- a/src/content/docs/protocol/architecture.md +++ b/src/content/docs/protocol/architecture.md @@ -45,7 +45,7 @@ Supporting decisions: - **Request:** `POST /proxy` with `{ target_url | target_ref, method, headers, body }`. - **Response:** `{ status, statusText, data }` by default, or the full `{ status, statusText, headers, data, meta }` (`ProxyResponse`) when the request carries `x-verbose: true`. -- **Client-controlled headers** that influence routing/caching, all stripped before the upstream call: `x-cache-ttl`, `x-verbose`, `x-api-key`, `x-idempotency-key`, `x-node-region`, `x-node-domain`, `x-node-exclude`. +- **Client-controlled headers** that influence routing/caching, all stripped before the upstream call: `x-cache-ttl`, `x-verbose`, `x-idempotency-key`, `x-node-region`, `x-node-domain`, `x-node-exclude`. The deprecated `x-api-key` is also stripped, but carries no identity or deduplication behaviour. ### Node control tunnel diff --git a/src/content/docs/protocol/concepts.md b/src/content/docs/protocol/concepts.md index 8fe6921..2108aab 100644 --- a/src/content/docs/protocol/concepts.md +++ b/src/content/docs/protocol/concepts.md @@ -80,7 +80,7 @@ registerExactIcpScheme(client, { signer }) The deduplication key is the core fingerprint that identifies a unique request. It is a SHA-256 hash of the canonicalized request, computed before any payment is checked. -The key is derived from five inputs: +The key is derived from four inputs: | Input | How it's canonicalized | |---|---| @@ -88,13 +88,16 @@ The key is derived from five inputs: | **URL** | Lowercased scheme and host, default ports removed, query params sorted alphabetically, fragment stripped | | **Semantic headers** | Only `accept` and `content-type` are included, lowercased and trimmed | | **Body** | SHA-256 hash of the body — JSON objects are deep-sorted before hashing for stability | -| **Scope** | SHA-256 of the `x-api-key` header value, or `"global"` if absent | -Two requests produce the same deduplication key if and only if all five inputs are equivalent after canonicalization. This means: +Two requests produce the same deduplication key if and only if all four inputs are equivalent after canonicalization. This means: - `https://api.example.com/prices?a=1&b=2` and `https://api.example.com/prices?b=2&a=1` produce the **same key** - The same JSON body with different key ordering produces the **same key** -- The same request with different `x-api-key` values produces **different keys** — they never share a cache entry +- Two callers issuing the same request produce the **same key** and share the cache entry — the scope is always global + +:::note[No caller scoping] +The deduplication scope is global. There is no per-caller namespace: the same canonical request is the same key no matter who sends it, which is what makes deduplication pay off across users. Earlier versions scoped the key by `x-api-key`; that behaviour has been removed, and the header is now stripped without effect. Do not send anything caller-specific in `accept` or `content-type`, the only headers that enter the key. +::: The key is computed twice per request: once before the payment check (to serve cache hits for free) and once after payment to store the response. diff --git a/src/content/docs/quickstart/usage.mdx b/src/content/docs/quickstart/usage.mdx index e41a416..b01b3c8 100644 --- a/src/content/docs/quickstart/usage.mdx +++ b/src/content/docs/quickstart/usage.mdx @@ -30,8 +30,8 @@ npm install @canister-software/consensus-cli | Option | Type | Default | Description | |---|---|---|---| -| `mode` | `"inclusive" \| "exclusive"` | `"inclusive"` | `inclusive` proxies all routes **except** those in `routes`. `exclusive` proxies **only** the listed routes. | -| `routes` | `string[]` | `[]` | Route paths to include or exclude depending on `mode`. | +| `mode` | `"only" \| "except"` | `"except"` | `only` proxies **only** the listed `routes` (allowlist). `except` proxies everything **but** the listed `routes` (denylist). Required whenever `routes` is non-empty. | +| `routes` | `string[]` | `[]` | Path rules selected by `mode`. Query params are ignored; matching is on path only. | | `matchSubroutes` | `boolean` | `false` | When `true`, a route match also applies to all sub-paths beneath it. | | `strategy` | `"auto" \| "manual"` | `"auto"` | `auto` intercepts `fetch()` calls transparently. `manual` exposes `req.consensus.fetch()` for explicit control. | | `cache_ttl` | `number` | — | TTL in seconds for node-level response caching. | @@ -46,6 +46,29 @@ npm install @canister-software/consensus-cli Spend tracking uses the fixed server price of **$0.0001** per paid `/proxy` request. Cached responses are not charged. ::: +### Route Filtering + +`mode` decides how `routes` is read. The two are exact inverses: + +```ts +ProxyClient(fetchWithPayment, { mode: 'only', routes: ['/api'] }) // proxy only /api +ProxyClient(fetchWithPayment, { mode: 'except', routes: ['/health'] }) // proxy everything but /health +ProxyClient(fetchWithPayment, {}) // proxy everything +``` + +`mode` is **required whenever `routes` is non-empty**. `{ routes: ['/api'] }` reads equally well as "proxy `/api`" and "don't proxy `/api`", and picking one silently would mean billing you for the opposite of what you meant. Misconfigurations are rejected when the client is constructed, rather than quietly changing what you pay for: + +| Config | Result | +|---|---| +| `{ routes: ['/api'] }` | throws — say which `mode` you meant | +| `{ mode: 'exclusve', routes: ['/api'] }` | throws — unrecognized `mode`, no silent fallback | +| `{ mode: 'only', routes: [] }` | throws — an empty allowlist would disable proxying entirely | +| `{ mode: 'except', routes: [] }` | fine — proxies everything (the default) | + +:::caution[Renamed in 0.2.0] +`mode` was `"inclusive" | "exclusive"`. Those names described the proxy's breadth while sitting next to `routes`, so they read as the inverse of what they did — under `"inclusive"`, listing a route *excluded* it from proxying. `"exclusive"` is now `"only"` and `"inclusive"` is now `"except"`. The old names still work as deprecated aliases with identical behaviour. +::: + ### Auto Strategy In `auto` mode, `ProxyClient` intercepts the global `fetch()` within the request context — your route handlers require no changes. @@ -59,7 +82,7 @@ const app = express() // Proxy only /price — all other routes use direct fetch app.use( ProxyClient(fetchWithPayment, { - mode: 'exclusive', + mode: 'only', routes: ['/price'], matchSubroutes: false, strategy: 'auto', @@ -108,13 +131,62 @@ const response = await req.consensus.fetch( ) ``` +### Batch Requests + +`batch()` runs many proxy requests as one group, either concurrently or strictly one at a time: + +```ts +const results = await proxy.batch( + [ + 'https://api.example.com/users/1', + 'https://api.example.com/users/2', + { target_url: 'https://api.example.com/events', method: 'POST', body: { kind: 'sync' } }, + ], + { mode: 'parallel', concurrency: 4 } +) +``` + +| Option | Default | Description | +|---|---|---| +| `mode` | `"parallel"` | `"parallel"` dispatches concurrently; `"sequential"` runs strictly one at a time, in input order. | +| `concurrency` | `8` | Max requests in flight in `"parallel"` mode. Ignored by `"sequential"`. | +| `signal` | — | An `AbortSignal` that stops dispatching further items. | + +Items accept a bare URL string or a full `request()` payload. Any per-request option (`profile`, `cache_ttl`, `verbose`, `node_region`, `node_domain`, `node_exclude`, `direct`) can be set for the whole batch and overridden on a single item through its `options` key. + +**Every item settles.** `batch()` never rejects because one request failed — results come back in input order as a discriminated union, so a partial failure is a normal, inspectable outcome: + +```ts +for (const result of results) { + if (result.ok) console.log(result.index, result.value.status, result.value.data) + else console.error(result.index, result.error.status, result.error.message) +} + +const succeeded = results.filter((r) => r.ok).map((r) => r.value) +``` + +It *does* reject before dispatching anything if the input itself is malformed — a missing `target_url`, an unrecognized `mode`, a non-positive `concurrency`. That is a programming error, and finding it halfway through a paid batch would be an expensive way to learn about a typo. + +#### Choosing a mode + +Each item is an independent paid `/proxy` request, so caching, profiles, node routing, and the spend limit all behave exactly as they do for a single `request()`. The one behavioural difference between the modes is the budget: + +- **`"sequential"`** — the spend guard sees every response before the next request goes out, so `limit_usd` is enforced **exactly**. Also the right choice for rate-limited upstreams. +- **`"parallel"`** — faster, but up to `concurrency` requests are already in flight when the limit is reached, so spend can overshoot `limit_usd` by up to `concurrency - 1` requests. Keep `concurrency` low when running close to a hard cap. + +Items that arrive after the limit is reached stand down to a direct `fetch`, exactly as a single request would, and are reported as successes carrying `meta.bypassed === true`. + +:::note[Client-side fan-out] +Batching currently fans out over the existing `POST /proxy` endpoint — one payment per item. The API is shaped so a future server-side `POST /proxy/batch`, with one payment and one routing pass for the whole group, can back it without a breaking change. +::: + ### Framework-Agnostic Usage Use `runWithPath()` to scope interception in any server framework and `createFetch()` for explicit route-scoped fetch — no Express required: ```ts const proxy = ProxyClient(fetchWithPayment, { - mode: 'exclusive', + mode: 'only', routes: ['/api'], limit_usd: 1.25, }) diff --git a/src/content/docs/x402proxy/networks.md b/src/content/docs/x402proxy/networks.md deleted file mode 100644 index 3efbf00..0000000 --- a/src/content/docs/x402proxy/networks.md +++ /dev/null @@ -1,14 +0,0 @@ ---- -title: Supported Tokens & Networks -description: Define supported tokens and networks ---- - - \ No newline at end of file diff --git a/src/content/docs/x402proxy/x402.md b/src/content/docs/x402proxy/x402.md deleted file mode 100644 index ab4796d..0000000 --- a/src/content/docs/x402proxy/x402.md +++ /dev/null @@ -1,112 +0,0 @@ ---- -title: What is x402? -description: The HTTP payment protocol that powers Consensus — how 402 challenges work, what clients and servers exchange, and how the flow is implemented -sidebar: - order: 1 ---- - -x402 is an open payment protocol built on top of HTTP. It gives any HTTP endpoint the ability to require a micropayment before responding, using the standard `402 Payment Required` status code as a machine-readable payment challenge. - -The protocol is network-agnostic — the same flow works over ICP, EVM, and Solana. Consensus implements x402 on both sides: the server issues challenges and the client handles them transparently. - ---- - -## The Payment Flow - -Every x402 interaction follows four steps: - -``` -Client Resource Server Facilitator - │ │ │ - ├─── GET /api/data ────────────►│ │ - │ │ │ - │◄── 402 Payment Required ──────┤ │ - │ X-Payment-Required: {...} │ │ - │ │ │ - │ (client signs payment) │ │ - │ │ │ - ├─── GET /api/data ────────────►│ │ - │ X-Payment: {...} │ │ - │ ├─── verify payment ────────►│ - │ │◄── verified ───────────────┤ - │◄── 200 OK ────────────────────┤ │ -``` - -**Step 1 — Initial request** -The client sends a normal HTTP request. No special headers are required. - -**Step 2 — Payment challenge** -The server responds `402 Payment Required` with a `X-Payment-Required` header containing a JSON object that describes what payment is needed: - -```json -{ - "scheme": "exact", - "network": "icp:1:xafvr-biaaa-aaaai-aql5q-cai", - "maxAmountRequired": "100000", - "payTo": "", - "description": "Premium data endpoint", - "mimeType": "application/json" -} -``` - -**Step 3 — Client pays** -The client reads the challenge, signs and submits a payment on the specified network, then retries the original request with the signed proof in the `X-Payment` header. - -**Step 4 — Verification and response** -The server forwards the payment proof to the facilitator. The facilitator verifies on-chain settlement and returns confirmation. The server then processes the request and returns the response. - -From the client's perspective, steps 2–4 are invisible. `wrapFetchWithPayment` intercepts the `402`, handles the payment, and returns the final `200` response. - ---- - -## The `X-Payment-Required` Header - -When the server issues a `402`, the `X-Payment-Required` header contains a JSON object with the following fields: - -| Field | Type | Description | -|---|---|---| -| `scheme` | `string` | Payment scheme — always `"exact"` for Consensus | -| `network` | `string` | Network identifier — see [Supported Networks](/x402proxy/networks/) | -| `maxAmountRequired` | `string` | Maximum amount the server will accept as payment | -| `payTo` | `string` | Address or principal that receives the payment | -| `description` | `string` | Human-readable description of what is being paid for | -| `mimeType` | `string` | MIME type of the response once payment is accepted | - -A single route can advertise multiple accepted networks simultaneously. The client picks one. - ---- - -## The `X-Payment` Header - -After paying, the client attaches proof in the `X-Payment` header. The exact structure is scheme- and network-specific, but always includes: - -| Field | Type | Description | -|---|---|---| -| `scheme` | `string` | Must match the scheme from the challenge | -| `network` | `string` | Must match the network from the challenge | -| `payload` | `string` | Signed payment proof — format is network-specific | - -The server does not validate this itself — it forwards it to the facilitator, which does the chain-level verification. - ---- - -## The `exact` Scheme - -Consensus uses the `exact` scheme exclusively. Under this scheme: - -- The client pays **exactly** the amount specified in `maxAmountRequired` -- No range, no estimation, no partial payment -- Payment goes directly to `payTo` on-chain -- The facilitator confirms that the exact transfer occurred before the server responds - -This makes pricing deterministic and auditable — both the client and server know the cost before any payment is made. - ---- - -## Implementation - -For complete setup guides on both sides of the payment flow: - -- **[Facilitator — Resource Server](/facilitator/resource-server/)** — protect Express routes with `paymentMiddleware`, connect to the facilitator, accept multiple networks -- **[Facilitator — Client](/facilitator/client/)** — build a client with `wrapFetchWithPayment` across ICP, EVM, and Solana -- **[Supported Networks](/facilitator/networks/)** — every network and token the facilitator accepts diff --git a/src/pages/index.astro b/src/pages/index.astro new file mode 100644 index 0000000..5aa2fe4 --- /dev/null +++ b/src/pages/index.astro @@ -0,0 +1,538 @@ + + + + + + Consensus | Tunnels, proxies, and stable IPs + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ + +
+ + +
+
+ +
+
Proxies & tunnels on-demand
+

+ Tunnels, proxies, and stable IPs. + No accounts. No API keys. +

+

Expose local HTTP/TCP services, route traffic via forward/reverse proxies, and instantly get stable IPs for whitelisted gateways.

+
+ $ + npm i @canister-software/consensus-cli + +
+
+
+ + +
+
The CLI & TUI
+

The whole network, from the terminal.

+

A CLI and a full-screen TUI, built with OpenTUI. Four primitives, one binary: tunnels, deduplicated proxies, leased IPs, and metered sockets.

+ + +
+
+ 01 +

+ TUNNELS: Expose local services natively via the CLI +

+
+
+ +
+
+
+
$ consensus tunnel http localhost:3000
+
+ Tunnel online
+  https://amber-otter.consensus.canister.software  localhost:3000
+  
+
+
+
One command to Live
+

Put a local port or device on a public HTTPS URL in seconds. No signup, monitor every request in real time.

+
+
+
+ + +
+
+ 02 +

+ PROXIES: Wrap fetch(). Ship secured. +

+
+
+ +
+
+
+
import { ProxyClient } from '@canister-software/consensus-cli'
+
+// intercepts outbound HTTP transparently
+const fetch = ProxyClient.wrap(globalThis.fetch)
+
+const res = await fetch('https://external.com', {
+  region:   'us-east',
+  caching:  true,
+  spendCap: '0.05',   // stands down to direct fetch when hit
+})
+
+
+
Drop-in SDK
+

Wrap fetch() once and every outbound call routes through Consensus: protected, deduplicated, cached, and metered per request. Existing route handlers stay untouched.

+
+
+
+ + +
+
+ 03 +

+ STABLE IPs: Verify and pin traffic to a static IP +

+
+
+ +
+
+
+
$ consensus ip list --region us-east
+NODE ID   DOMAIN                                     REGION   SCORE
+7f3a91    cobalt-heron.consensus.canister.software   us-east  94
+
+$ consensus ip lease cobalt-heron.consensus.canister.software
+ Leased: cobalt-heron.consensus.canister.software (traffic pinned)
+
+
+
Pin the IP
+

Lease a verified, performant node and pin every tunnel, proxy, and socket to one static IP. Whitelist once, reconnect automatically, stop fighting dynamic firewalls.

+
+
+
+ + +
+
+ 04 +

+ WEBSOCKETS: Prepaid, metered sessions for stateless apps +

+
+
+ +
+
+
+
import { SocketClient } from '@canister-software/consensus-sdk'
+
+const client = SocketClient(fetchWithPayment)
+
+// prepay a bounded, metered session
+const auth = await client.requestToken({
+  model: 'hybrid', minutes: 60, mb: 500,
+})
+
+const session = await client.connect(auth)
+session.on('message', (m) => console.log('recv', m))
+
+
+
Prepaid & metered
+

Prepay time, data, or both. Each persistent session is metered by the protocol and closed cleanly once the budget runs out. No accounts, no state.

+
+
+
+
+ + +
+
+
+

Pay on any of three networks.

+ one facilitator · the exact scheme +
+
+
+ Ethereum +
+ Ethereum + Mainnet +
+
+
+ Solana +
+ Solana + Mainnet +
+
+
+ Internet Computer +
+ Internet Computer + Mainnet +
+
+
+
+
+ + +
+
+
+
+

Pure application-level routing with a Cache.

+
+

Wire native proxy handlers directly into your JavaScript/TypeScript processes using clean dependency injection, and manage parameters locally via secure profiles.

+
+ +
+ +
+
+ Integration + 01 +
+
Drop-in SDK, or dependency injection
+

Two ways to wire into a codebase. Import the SDK and call ProxyClient or SocketClient as middleware directly in application code, or hand the protocol native handlers through dependency injection. Both paths run at the application level and never decrypt HTTPS, so existing request logic and TLS stay exactly as written.

+
+ +
+
+ Configuration + 02 +
+
No accounts. Local profiles.
+

Nothing to sign up for and no keys to rotate. Configuration lives in local profiles on the machine. Create named profiles, customize defaults and spend limits per project, and switch between them instantly, with nothing held on a remote server.

+
+ +
+
+ Security + 03 +
+
Vetted nodes, encrypted channels
+

Every node earns its place. Before serving any traffic a node clears a performance benchmark, and a heartbeat every five minutes keeps only healthy, responsive nodes in rotation. Traffic between the runtime and each node travels over encrypted channels, so data in transit stays protected across every hop.

+
+ +
+ +
+ Built for JavaScript & TypeScript runtimes + + JavaScript + TypeScript + +
+
+
+ + +
+ +
+
+
Built for speed · shipped via the CLI
+

One command. You're on the network.

+ +
+ $ + npm i -g @canister-software/consensus-cli + +
+ +

Looking to host infrastructure? Join the network as a node operator →

+
+
+ + + + +
+ + + + + diff --git a/src/pages/join.astro b/src/pages/join.astro new file mode 100644 index 0000000..db6d815 --- /dev/null +++ b/src/pages/join.astro @@ -0,0 +1,234 @@ + + + + + + Run a Consensus node | Earn from the network + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+ + +
+
+ +
+
+ Network live · Beta +
+

I am. You are.
We are Consensus.

+

Ready to power the network?

+

Run a node. Carry traffic. Earn directly. Serve tunnels, proxy requests, and metered WebSocket sessions and earn from network usage.

+ +
+
+ + +
+
Four ways a node earns
+

Every request you serve pays you.

+
+
+
EARN 01
+

Serve tunnels

+

Forward HTTP/TCP traffic for tunnels opened through your node and earn for the bytes you carry.

+
+
+
EARN 02
+

Serve the proxy

+

Execute deduplicated HTTP for the network and collect the per-request fee on every call you handle.

+
+
+
EARN 03
+

Lease your IP

+

Offer a verified, long-lived IP into the shared pool and earn when apps route through it for whitelisting.

+
+
+
EARN 04
+

Serve WebSockets

+

Host prepaid, metered sessions. Clients pay up front; you earn for the time and data you serve.

+
+
+
+ + +
+
+
The bar to join
+

Every node is vetted, to join and to stay.

+

Consensus enforces quality rather than assuming it. A machine clears a baseline of cores, memory, and storage, then passes a live stress test to be admitted. The vetting never stops: the network tracks availability and performance, and re-tests nodes during their downtime.

+ + +
Baseline machine
+
+ Memory8 GB + Cores4 + Storage50 GB SSD + Bandwidth100 Mbps + Public IPv41 static + Open port1 inbound + Uptime24 / 7 + RuntimeBun +
+ + +
The admission stress test
+
+
+
5000 hash/s
+
Encryption speed
+
How fast the machine encrypts and signs the requests it serves.
+
+
+
60s @ 16 KB
+
Sustained speed
+
Holds a steady rate through a full minute of load, without throttling.
+
+
+
p99 < 10 ms
+
Responsiveness
+
Picks up new work without stalls, even on a busy host.
+
+
+
< 2%
+
Packet loss
+
Keeps connections stable for live, metered sessions.
+
+
+
Admission is not the finish line: a heartbeat every 5 minutes proves availability, and the network re-tests nodes during their downtime.
+
+
+ + +
+
+
From install to earning
+

Three steps to a live node.

+ +
+
+
01
+
+
Clone and install
+
Pull the node runtime and install its dependencies with Bun.
+
+
+ $ git clone https://github.com/Demali-876/consensus-node.git +$ cd consensus-node +$ bun install +
+
+
+
02
+
+
Run guided setup
+
One command installs the approved release, runs the live benchmark, verifies email, collects payout addresses, and registers. A domain is issued on pass.
+
Prefer a browser? bun run setup:wizard runs the same flow on a local page.
+
+
+ $ bun run setup +
+
+
+
03
+
+
Keep it running
+
Start the always-on control tunnel under pm2 and save it, so the node survives reboots and stays in the active set.
+
+
+ $ pm2 startOrReload ~/.consensus/node-runtime/current/ecosystem.config.cjs --only consensus-node-control --update-env +$ pm2 save +
+
+
+ + +
+
+ + + + +
+ + diff --git a/src/styles/custom.css b/src/styles/custom.css index d4506a2..1804338 100644 --- a/src/styles/custom.css +++ b/src/styles/custom.css @@ -1,3 +1,52 @@ +/* ============================================================ + Consensus docs theme: matches the landing page. + Fonts: Space Grotesk (UI/headings) + JetBrains Mono (code/labels), + self-hosted as variable woff2 from public/fonts/. + ============================================================ */ + +/* ---- Self-hosted variable fonts ---- */ +@font-face { + font-family: 'Space Grotesk'; + font-style: normal; + font-weight: 300 700; + font-display: swap; + src: url('/fonts/space-grotesk-variable.woff2') format('woff2'); +} +@font-face { + font-family: 'JetBrains Mono'; + font-style: normal; + font-weight: 100 800; + font-display: swap; + src: url('/fonts/jetbrains-mono-variable.woff2') format('woff2'); +} + +/* ---- Type system ---- */ +:root { + --sl-font: 'Space Grotesk', system-ui, -apple-system, sans-serif; + --sl-font-mono: 'JetBrains Mono', ui-monospace, 'SFMono-Regular', monospace; +} + +/* Headings a touch tighter, like the landing */ +.sl-markdown-content h1, +.sl-markdown-content h2, +.sl-markdown-content h3, +.content-panel h1, +h1, h2, h3 { + letter-spacing: -0.02em; + font-weight: 600; +} + +/* Mono for eyebrows / sidebar group labels for the protocol-grade feel */ +.sidebar-content summary, +.sidebar-content .large, +nav.sidebar .top-level > li > a[aria-current] { + font-family: var(--sl-font-mono); + font-size: 0.72rem; + letter-spacing: 0.08em; + text-transform: uppercase; +} + +/* ---- Accent + surface tokens (monochrome) ---- */ :root { --sl-color-accent: #fff; --sl-color-accent-high: #fff; @@ -17,10 +66,56 @@ :root[data-theme='dark'] { --sl-color-black: #000; --sl-color-bg: #000; - --sl-color-bg-nav: #000; + --sl-color-bg-nav: rgba(0, 0, 0, 0.55); --sl-color-bg-sidebar: #000; + /* hairline borders to echo the landing */ + --sl-color-hairline-shade: rgba(255, 255, 255, 0.07); + --sl-color-hairline: rgba(255, 255, 255, 0.07); + --sl-color-gray-5: #0a0a0b; + --sl-color-gray-6: #060607; +} + +/* Blurred sticky header, like the landing nav */ +:root[data-theme='dark'] .header { + backdrop-filter: blur(14px); + -webkit-backdrop-filter: blur(14px); + border-bottom: 1px solid rgba(255, 255, 255, 0.07); } +/* Code blocks: bordered panel on near-black, matching the landing snippets */ +:root[data-theme='dark'] .sl-markdown-content pre { + background: #060607 !important; + border: 1px solid rgba(255, 255, 255, 0.1); + border-radius: 12px; +} +.sl-markdown-content code { + font-family: var(--sl-font-mono); +} + +/* Inline code chips */ +:root[data-theme='dark'] .sl-markdown-content :not(pre) > code { + background: rgba(255, 255, 255, 0.06); + border: 1px solid rgba(255, 255, 255, 0.08); + border-radius: 6px; +} + +/* Subtle link underline in prose */ +.sl-markdown-content a { + text-underline-offset: 0.2em; +} + +/* Cards / asides pick up the hairline look in dark mode */ +:root[data-theme='dark'] .card, +:root[data-theme='dark'] starlight-aside { + border-color: rgba(255, 255, 255, 0.1); + background: rgba(255, 255, 255, 0.018); + border-radius: 14px; +} + +/* ============================================================ + Existing rules (diagrams, lightbox, whitepaper CTA) + ============================================================ */ + .consensus-diagram { width: 100%; display: none;