diff --git a/.github/dependabot.yml b/.github/dependabot.yml
index 2f8941a..d2f28ab 100644
--- a/.github/dependabot.yml
+++ b/.github/dependabot.yml
@@ -6,11 +6,11 @@ updates:
schedule:
interval: weekly
day: monday
- time: "09:00"
+ time: '09:00'
timezone: Asia/Kolkata
open-pull-requests-limit: 3
commit-message:
- prefix: "chore(deps)"
+ prefix: 'chore(deps)'
groups:
npm-minor-and-patch:
update-types:
@@ -22,8 +22,8 @@ updates:
schedule:
interval: weekly
day: monday
- time: "09:00"
+ time: '09:00'
timezone: Asia/Kolkata
open-pull-requests-limit: 2
commit-message:
- prefix: "chore(deps)"
+ prefix: 'chore(deps)'
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 8a1c71a..9ad52dd 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -14,8 +14,8 @@ concurrency:
cancel-in-progress: true
jobs:
- build:
- name: Build
+ check:
+ name: Check
runs-on: ubuntu-latest
timeout-minutes: 10
env:
@@ -34,5 +34,5 @@ jobs:
- name: Install dependencies
run: npm ci
- - name: Build site
- run: npm run build
+ - name: Check formatting and build
+ run: npm run check
diff --git a/.prettierignore b/.prettierignore
new file mode 100644
index 0000000..46a98bb
--- /dev/null
+++ b/.prettierignore
@@ -0,0 +1,5 @@
+dist/
+.astro/
+node_modules/
+package-lock.json
+public/pagefind/
diff --git a/astro.config.mjs b/astro.config.mjs
index dd94a55..ce5b9bb 100644
--- a/astro.config.mjs
+++ b/astro.config.mjs
@@ -29,7 +29,10 @@ export default defineConfig({
{ tag: 'meta', attrs: { property: 'og:image:width', content: '1731' } },
{ tag: 'meta', attrs: { property: 'og:image:height', content: '909' } },
{ tag: 'meta', attrs: { name: 'twitter:image', content: 'https://mcplab.dev/og.png' } },
- { tag: 'script', content: `const storedTheme = localStorage.getItem('starlight-theme'); if (storedTheme !== 'light' && storedTheme !== 'dark') localStorage.setItem('starlight-theme', matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light'); document.addEventListener('DOMContentLoaded', () => document.querySelector('select[aria-label="Select theme"] option[value="auto"]')?.remove());` },
+ {
+ tag: 'script',
+ content: `const storedTheme = localStorage.getItem('starlight-theme'); if (storedTheme !== 'light' && storedTheme !== 'dark') localStorage.setItem('starlight-theme', matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light'); document.addEventListener('DOMContentLoaded', () => document.querySelector('select[aria-label="Select theme"] option[value="auto"]')?.remove());`,
+ },
],
social: [
{ icon: 'heart', label: 'Support MCP Lab', href: '/support' },
diff --git a/package-lock.json b/package-lock.json
index 3434bf3..d7e92e1 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -15,6 +15,10 @@
"mermaid": "^11.17.0",
"sharp": "^0.35.3"
},
+ "devDependencies": {
+ "prettier": "^3.9.6",
+ "prettier-plugin-astro": "^0.14.1"
+ },
"engines": {
"node": ">=22.19.0"
}
@@ -32,6 +36,13 @@
"url": "https://github.com/sponsors/antfu"
}
},
+ "node_modules/@astrojs/compiler": {
+ "version": "2.13.1",
+ "resolved": "https://registry.npmjs.org/@astrojs/compiler/-/compiler-2.13.1.tgz",
+ "integrity": "sha512-f3FN83d2G/v32ipNClRKgYv30onQlMZX1vCeZMjPsMMPl1mDpmbl0+N5BYo4S/ofzqJyS5hvwacEo0CCVDn/Qg==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/@astrojs/compiler-binding": {
"version": "0.3.2",
"resolved": "https://registry.npmjs.org/@astrojs/compiler-binding/-/compiler-binding-0.3.2.tgz",
@@ -7008,6 +7019,37 @@
"node": ">=4"
}
},
+ "node_modules/prettier": {
+ "version": "3.9.6",
+ "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.9.6.tgz",
+ "integrity": "sha512-OpN0zzVdiaiAhxpuuj5efpIS4sY9j7bY6uR5mnj5yPzGkdkjNKSJeUThPb60Jw29QuAZgA4o+/iB49kFiaBX6g==",
+ "dev": true,
+ "license": "MIT",
+ "bin": {
+ "prettier": "bin/prettier.cjs"
+ },
+ "engines": {
+ "node": ">=14"
+ },
+ "funding": {
+ "url": "https://github.com/prettier/prettier?sponsor=1"
+ }
+ },
+ "node_modules/prettier-plugin-astro": {
+ "version": "0.14.1",
+ "resolved": "https://registry.npmjs.org/prettier-plugin-astro/-/prettier-plugin-astro-0.14.1.tgz",
+ "integrity": "sha512-RiBETaaP9veVstE4vUwSIcdATj6dKmXljouXc/DDNwBSPTp8FRkLGDSGFClKsAFeeg+13SB0Z1JZvbD76bigJw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@astrojs/compiler": "^2.9.1",
+ "prettier": "^3.0.0",
+ "sass-formatter": "^0.7.6"
+ },
+ "engines": {
+ "node": "^14.15.0 || >=16.0.0"
+ }
+ },
"node_modules/prismjs": {
"version": "1.30.0",
"resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.30.0.tgz",
@@ -7483,12 +7525,29 @@
"integrity": "sha512-PdhdWy89SiZogBLaw42zdeqtRJ//zFd2PgQavcICDUgJT5oW10QCRKbJ6bg4r0/UY2M6BWd5tkxuGFRvCkgfHQ==",
"license": "BSD-3-Clause"
},
+ "node_modules/s.color": {
+ "version": "0.0.15",
+ "resolved": "https://registry.npmjs.org/s.color/-/s.color-0.0.15.tgz",
+ "integrity": "sha512-AUNrbEUHeKY8XsYr/DYpl+qk5+aM+DChopnWOPEzn8YKzOhv4l2zH6LzZms3tOZP3wwdOyc0RmTciyi46HLIuA==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/safer-buffer": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
"license": "MIT"
},
+ "node_modules/sass-formatter": {
+ "version": "0.7.9",
+ "resolved": "https://registry.npmjs.org/sass-formatter/-/sass-formatter-0.7.9.tgz",
+ "integrity": "sha512-CWZ8XiSim+fJVG0cFLStwDvft1VI7uvXdCNJYXhDvowiv+DsbD1nXLiQ4zrE5UBvj5DWZJ93cwN0NX5PMsr1Pw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "suf-log": "^2.5.3"
+ }
+ },
"node_modules/satteri": {
"version": "0.9.5",
"resolved": "https://registry.npmjs.org/satteri/-/satteri-0.9.5.tgz",
@@ -7716,6 +7775,16 @@
"integrity": "sha512-5Z9ZpRzfuH6l/UAvCPAPUo3665Nk2wLaZU3x+TLHKVzIz33+sbJqbtrYoC3KD4/uVOr2Zp+L0LySezP9OHV9yA==",
"license": "MIT"
},
+ "node_modules/suf-log": {
+ "version": "2.5.3",
+ "resolved": "https://registry.npmjs.org/suf-log/-/suf-log-2.5.3.tgz",
+ "integrity": "sha512-KvC8OPjzdNOe+xQ4XWJV2whQA0aM1kGVczMQ8+dStAO6KfEB140JEVQ9dE76ONZ0/Ylf67ni4tILPJB41U0eow==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "s.color": "0.0.15"
+ }
+ },
"node_modules/supports-color": {
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
diff --git a/package.json b/package.json
index 3d37d81..c53aaf3 100644
--- a/package.json
+++ b/package.json
@@ -1,24 +1,31 @@
{
- "name": "mcplab.dev",
- "private": true,
- "type": "module",
- "version": "0.1.0",
- "engines": {
- "node": ">=22.19.0"
- },
- "scripts": {
- "dev": "astro dev",
- "start": "astro dev",
- "build": "astro build",
- "preview": "astro preview",
- "astro": "astro"
- },
- "dependencies": {
- "@astrojs/starlight": "^0.41.7",
- "@vercel/analytics": "^2.0.1",
- "astro": "^7.1.6",
- "astro-mermaid": "^2.1.0",
- "mermaid": "^11.17.0",
- "sharp": "^0.35.3"
- }
+ "name": "mcplab.dev",
+ "private": true,
+ "type": "module",
+ "version": "0.1.0",
+ "engines": {
+ "node": ">=22.19.0"
+ },
+ "scripts": {
+ "dev": "astro dev",
+ "start": "astro dev",
+ "build": "astro build",
+ "check": "npm run format:check && npm run build",
+ "format": "prettier --write .",
+ "format:check": "prettier --check .",
+ "preview": "astro preview",
+ "astro": "astro"
+ },
+ "dependencies": {
+ "@astrojs/starlight": "^0.41.7",
+ "@vercel/analytics": "^2.0.1",
+ "astro": "^7.1.6",
+ "astro-mermaid": "^2.1.0",
+ "mermaid": "^11.17.0",
+ "sharp": "^0.35.3"
+ },
+ "devDependencies": {
+ "prettier": "^3.9.6",
+ "prettier-plugin-astro": "^0.14.1"
+ }
}
diff --git a/prettier.config.mjs b/prettier.config.mjs
new file mode 100644
index 0000000..379cfb8
--- /dev/null
+++ b/prettier.config.mjs
@@ -0,0 +1,15 @@
+/** @type {import('prettier').Config} */
+export default {
+ plugins: ['prettier-plugin-astro'],
+ printWidth: 120,
+ singleQuote: true,
+ useTabs: true,
+ overrides: [
+ {
+ files: '*.astro',
+ options: {
+ parser: 'astro',
+ },
+ },
+ ],
+};
diff --git a/src/content/docs/docs.mdx b/src/content/docs/docs.mdx
index cda0aa9..78c5e85 100644
--- a/src/content/docs/docs.mdx
+++ b/src/content/docs/docs.mdx
@@ -17,10 +17,18 @@ hero:
import { Card, CardGrid } from '@astrojs/starlight/components';
- Define a tool call, timeout, and expectations in JSON.
- Use delay, hang, or disconnect to exercise recovery paths.
- Add a sequential observer call such as `ping`.
- Produce console output or stable JSON for CI.
+
+ Define a tool call, timeout, and expectations in JSON.
+
+
+ Use delay, hang, or disconnect to exercise recovery paths.
+
+
+ Add a sequential observer call such as `ping`.
+
+
+ Produce console output or stable JSON for CI.
+
## Protocol compatibility
diff --git a/src/content/docs/docs/architecture.mdx b/src/content/docs/docs/architecture.mdx
index c001dea..4c66675 100644
--- a/src/content/docs/docs/architecture.mdx
+++ b/src/content/docs/docs/architecture.mdx
@@ -20,7 +20,7 @@ graph LR
CLI --> Server
```
-*Figure 1. Current server and stdio transport architecture.*
+_Figure 1. Current server and stdio transport architecture._
## Scenario execution path
@@ -54,7 +54,7 @@ sequenceDiagram
Runner->>Runner: Produce combined scenario recording
```
-*Figure 2. Primary and optional observer calls execute sequentially.*
+_Figure 2. Primary and optional observer calls execute sequentially._
## Responsibilities
@@ -89,7 +89,7 @@ flowchart TD
AssertionFailure --> Final
```
-*Figure 3. Observer verification and failure handling.*
+_Figure 3. Observer verification and failure handling._
:::note[Deliberate boundary]
The current architecture is not a proxy and does not orchestrate external MCP clients. Streamable HTTP and target-client adapters are future work.
diff --git a/src/content/docs/docs/cli.md b/src/content/docs/docs/cli.md
index 819974d..084516e 100644
--- a/src/content/docs/docs/cli.md
+++ b/src/content/docs/docs/cli.md
@@ -34,10 +34,10 @@ Console is the default. Pass `--report json` for machine-readable scenario resul
## Exit codes
-| Code | Meaning |
-| --- | --- |
-| `0` | All expectations passed |
-| `2` | Scenario ran, but one or more assertions failed |
-| `1` | Scenario could not be loaded or executed |
+| Code | Meaning |
+| ---- | ----------------------------------------------- |
+| `0` | All expectations passed |
+| `2` | Scenario ran, but one or more assertions failed |
+| `1` | Scenario could not be loaded or executed |
JSON command error codes are `invalid_arguments`, `scenario_load_failed`, and `scenario_execution_failed`.
diff --git a/src/content/docs/docs/examples.md b/src/content/docs/docs/examples.md
index 8e50225..0384f73 100644
--- a/src/content/docs/docs/examples.md
+++ b/src/content/docs/docs/examples.md
@@ -41,7 +41,7 @@ flowchart TD
Response --> Evaluation[Future AGI Evaluation]
```
-*Figure 1. External validation path for the `hang` fault.*
+_Figure 1. External validation path for the `hang` fault._
### What was validated
diff --git a/src/content/docs/docs/reporting.md b/src/content/docs/docs/reporting.md
index 7154e35..f3db281 100644
--- a/src/content/docs/docs/reporting.md
+++ b/src/content/docs/docs/reporting.md
@@ -7,11 +7,11 @@ The console reporter shows the scenario name, observed outcome, duration, assert
```json
{
- "name": "bounded delay succeeds",
- "outcome": "success",
- "durationMs": 251.25,
- "passed": true,
- "failures": []
+ "name": "bounded delay succeeds",
+ "outcome": "success",
+ "durationMs": 251.25,
+ "passed": true,
+ "failures": []
}
```
diff --git a/src/content/docs/docs/scenarios.md b/src/content/docs/docs/scenarios.md
index e73e321..e799a1c 100644
--- a/src/content/docs/docs/scenarios.md
+++ b/src/content/docs/docs/scenarios.md
@@ -7,14 +7,14 @@ A scenario describes one primary tool call, its deadline, and the expected obser
```json
{
- "name": "bounded delay succeeds",
- "call": { "tool": "delay", "args": { "delayMs": 250 } },
- "timeoutMs": 1000,
- "expect": {
- "outcome": "success",
- "maxDurationMs": 500,
- "result": { "isError": false, "textContains": "\"status\":\"delayed\"" }
- }
+ "name": "bounded delay succeeds",
+ "call": { "tool": "delay", "args": { "delayMs": 250 } },
+ "timeoutMs": 1000,
+ "expect": {
+ "outcome": "success",
+ "maxDurationMs": 500,
+ "result": { "isError": false, "textContains": "\"status\":\"delayed\"" }
+ }
}
```
@@ -26,18 +26,18 @@ Use `observe` to make a separate call after the primary path. The calls are sequ
```json
{
- "name": "server remains responsive after a delay",
- "call": { "tool": "delay", "args": { "delayMs": 250 } },
- "timeoutMs": 1000,
- "expect": { "outcome": "success" },
- "observe": {
- "call": { "tool": "ping", "args": {} },
- "timeoutMs": 1000,
- "expect": {
- "outcome": "success",
- "result": { "isError": false, "textContains": "\"status\":\"ok\"" }
- }
- }
+ "name": "server remains responsive after a delay",
+ "call": { "tool": "delay", "args": { "delayMs": 250 } },
+ "timeoutMs": 1000,
+ "expect": { "outcome": "success" },
+ "observe": {
+ "call": { "tool": "ping", "args": {} },
+ "timeoutMs": 1000,
+ "expect": {
+ "outcome": "success",
+ "result": { "isError": false, "textContains": "\"status\":\"ok\"" }
+ }
+ }
}
```
diff --git a/src/layouts/SiteLayout.astro b/src/layouts/SiteLayout.astro
index 28d69b9..2b00652 100644
--- a/src/layouts/SiteLayout.astro
+++ b/src/layouts/SiteLayout.astro
@@ -6,27 +6,109 @@ const canonical = new URL(Astro.url.pathname, Astro.site ?? Astro.url);
const socialImage = image ? new URL(image, Astro.site ?? Astro.url) : undefined;
const path = Astro.url.pathname;
---
-
{socialImage && } {socialImage && }{title}
-
-
-
-
+
+
+
+ {
+ socialImage &&
+ } {
+ socialImage &&
+ }{title}
+
+
+
+
+
diff --git a/src/pages/failure.astro b/src/pages/failure.astro
index b94ff24..6135e85 100644
--- a/src/pages/failure.astro
+++ b/src/pages/failure.astro
@@ -1,43 +1,117 @@
---
import SiteLayout from '../layouts/SiteLayout.astro';
---
-
-
-
- MCP Lab / MCP Failure Lab
- Deterministic failure scenarios for MCP servers.
- Reproduce delay, hanging requests, cancellation, and transport loss through a real MCP client/server execution path.
-
- Available on npm TypeScript MIT Early release
-
-
- Quick start
- No repository checkout, external MCP server, or API key is required.
-
-
terminal command
$ npx mcp-failure-lab demo
-
demo result
Scenario: Deterministic delay demo
+
+
+
+ MCP Lab / MCP Failure Lab
+ Deterministic failure scenarios for MCP servers.
+
+ Reproduce delay, hanging requests, cancellation, and transport loss through a real MCP client/server execution
+ path.
+
+
+
+ Available on npm TypeScript MIT Early release
+
+
+
+
+ Quick start
+ No repository checkout, external MCP server, or API key is required.
+
+
+
+ terminal command
+
$ npx mcp-failure-lab demo
+
+
+
+ demo result
+
Scenario: Deterministic delay demo
Outcome: success
Duration: ~500 ms
-Assertions: passed
-
-
+Assertions: passed
+
+
+
-
- Execution model
- Each scenario runs against Failure Lab’s built-in server. The primary call and optional observer call are sequential and use the same MCP client connection.
- Stage Responsibility Implemented
- Load Validate a code-first or JSON scenario definition. yes
- Execute Call a registered tool through an MCP client connection. yes
- Record Capture outcome, duration, result, or execution error. yes
- Assert Check outcome, maximum duration, `isError`, and returned text. yes
- Observe Run a separate post-condition call such as `ping`. yes
- Report Render console or JSON output and select an exit code. yes
-
-
+
+ Execution model
+
+ Each scenario runs against Failure Lab’s built-in server. The primary call and optional observer call are
+ sequential and use the same MCP client connection.
+
+
+ Stage Responsibility Implemented
+ Load Validate a code-first or JSON scenario definition. yes
+ Execute Call a registered tool through an MCP client connection. yes
+ Record Capture outcome, duration, result, or execution error. yes
+ Assert Check outcome, maximum duration, `isError`, and returned text. yes
+ Observe Run a separate post-condition call such as `ping`. yes
+ Report Render console or JSON output and select an exit code. yes
+
+
+
- CI behavior Exit code 0 means expectations passed, 2 means assertions failed, and 1 means the scenario could not be loaded or executed.
Operational behavior stdio protocol traffic stays on stdout. Diagnostics use stderr, and SIGINT or SIGTERM triggers graceful shutdown.
+
+
+
CI behavior
+ Exit code 0 means expectations passed, 2 means assertions failed, and 1 means the scenario could not be loaded
+ or executed.
+
+
+
Operational behavior
+ stdio protocol traffic stays on stdout. Diagnostics use stderr, and SIGINT or SIGTERM triggers graceful
+ shutdown.
+
+
+
- Current boundary. External client orchestration, Streamable HTTP, JUnit reports, malformed messages, duplicate responses, and session-loss faults are not implemented.
-
+
+
+ Current boundary. External client orchestration, Streamable HTTP, JUnit reports, malformed messages,
+ duplicate responses, and session-loss faults are not implemented.
+
+
+
diff --git a/src/pages/index.astro b/src/pages/index.astro
index 16ad368..ca2496c 100644
--- a/src/pages/index.astro
+++ b/src/pages/index.astro
@@ -1,21 +1,44 @@
---
import SiteLayout from '../layouts/SiteLayout.astro';
---
-
-
-
- MCP Lab / open-source testing tools
- Test MCP timeout, cancellation, and disconnect handling.
- MCP Lab develops focused tools for reproducing failure conditions in Model Context Protocol systems. The first project, MCP Failure Lab, runs deterministic scenarios against a purpose-built MCP server.
-
- Usable early release MIT licensed Node.js 22.19+ stdio transport
-
-
- A scenario and its result
- The runner makes a real MCP tool call, records the observed outcome and duration, then evaluates the declared expectations.
-
-
delay-success.json input
{
+
+
+
+ MCP Lab / open-source testing tools
+ Test MCP timeout, cancellation, and disconnect handling.
+
+ MCP Lab develops focused tools for reproducing failure conditions in Model Context Protocol systems. The first
+ project, MCP Failure Lab, runs deterministic scenarios against a purpose-built MCP server.
+
+
+
+ Usable early release MIT licensed Node.js 22.19+ stdio transport
+
+
+
+
+ A scenario and its result
+
+ The runner makes a real MCP tool call, records the observed outcome and duration, then evaluates the declared
+ expectations.
+
+
+
+
+ delay-success.json input
+
{
"name" : "bounded delay succeeds" ,
"call" : {
"tool" : "delay" ,
@@ -26,31 +49,76 @@ import SiteLayout from '../layouts/SiteLayout.astro';
"outcome" : "success" ,
"maxDurationMs" : 500
}
-}
-
console output
$ npm run dev -- run \
+}
+
+
+
+ console output
+
$ npm run dev -- run \
examples/scenarios/delay-success.json
Scenario: bounded delay succeeds
Outcome: success
Duration: ~250 ms
-Assertions: passed
-
-
+Assertions: passed
+
+
+
-
- MCP Failure Lab
- The current release covers a deliberately small set of failure modes and reporting primitives.
- Capability Behavior Status
- delay Bounded, cancellation-aware latency from 0 to 30 seconds. available
- hang A pending request that ends only on client cancellation. available
- disconnect Transport loss while a tool request is in flight. available
- ping Health and post-condition verification through a separate tool path. available
- reporting Console and stable JSON output with CI-oriented exit codes. available
- HTTP / JUnit Streamable HTTP transport and JUnit reports. planned
-
-
-
+
+ MCP Failure Lab
+
+ The current release covers a deliberately small set of failure modes and reporting primitives.
+
+
+ Capability Behavior Status
+ delay Bounded, cancellation-aware latency from 0 to 30 seconds. available
+ hang A pending request that ends only on client cancellation. available
+ disconnect Transport loss while a tool request is in flight. available
+ ping Health and post-condition verification through a separate tool path. available
+ reporting Console and stable JSON output with CI-oriented exit codes. available
+ HTTP / JUnit Streamable HTTP transport and JUnit reports. planned
+
+
+
+
- What it is A deterministic test server, scenario runner, assertion model, and CLI for exercising known MCP failure paths.
What it is not It is not currently a general-purpose proxy, protocol compliance suite, or external MCP client orchestrator.
-
+
+
+
What it is
+ A deterministic test server, scenario runner, assertion model, and CLI for exercising known MCP failure paths.
+
+
+
What it is not
+ It is not currently a general-purpose proxy, protocol compliance suite, or external MCP client orchestrator.
+
+
+
+
diff --git a/src/pages/support.astro b/src/pages/support.astro
index a61099d..dc8d55d 100644
--- a/src/pages/support.astro
+++ b/src/pages/support.astro
@@ -5,85 +5,167 @@ const upiId = import.meta.env.PUBLIC_UPI_ID || 'your-upi-id@bank';
const upiName = import.meta.env.PUBLIC_UPI_NAME || 'MCP Lab';
const isConfigured = upiId !== 'your-upi-id@bank';
const amounts = [49, 99];
-const paymentUri = (amount: number) => `upi://pay?${new URLSearchParams({ pa: upiId, pn: upiName, am: String(amount), cu: 'INR', tn: 'Support MCP Lab' })}`;
-const qrUrl = (amount: number) => `https://api.qrserver.com/v1/create-qr-code/?size=320x320&format=png&margin=12&data=${encodeURIComponent(paymentUri(amount))}`;
+const paymentUri = (amount: number) =>
+ `upi://pay?${new URLSearchParams({ pa: upiId, pn: upiName, am: String(amount), cu: 'INR', tn: 'Support MCP Lab' })}`;
+const qrUrl = (amount: number) =>
+ `https://api.qrserver.com/v1/create-qr-code/?size=320x320&format=png&margin=12&data=${encodeURIComponent(paymentUri(amount))}`;
---
-
-
-
- MCP Lab / community supported
- Help keep failure testing open.
- MCP Lab is independent, open-source work. Your support helps fund maintenance, documentation, and the next set of deterministic failure scenarios.
-
-
-
+
+
+
+ MCP Lab / community supported
+ Help keep failure testing open.
+
+ MCP Lab is independent, open-source work. Your support helps fund maintenance, documentation, and the next set
+ of deterministic failure scenarios.
+
+
-
- India / UPI
Buy the project some build time. Choose an amount, then scan the QR code or open your preferred UPI app on mobile.
-
- {amounts.map((amount, index) => ₹{amount} )}
- Custom ₹
-
-
-
-
-
-
-
- {!isConfigured && Preview mode. Set PUBLIC_UPI_ID before publishing to activate payment links and QR codes.
}
-
-
+
+
+
+
India / UPI
Buy the project some build time.
+ Choose an amount, then scan the QR code or open your preferred UPI app on mobile.
+
+
+
+ {
+ amounts.map((amount, index) => (
+
+ ₹{amount}
+
+ ))
+ }
+ Custom ₹
+
+
+
+
+
+
+
+ {
+ !isConfigured && (
+
+ Preview mode. Set PUBLIC_UPI_ID before publishing to activate payment links
+ and QR codes.
+
+ )
+ }
+
+
-
+
+
+
+
diff --git a/src/styles/global.css b/src/styles/global.css
index 4de05fd..bddbda5 100644
--- a/src/styles/global.css
+++ b/src/styles/global.css
@@ -1,50 +1,836 @@
-:root { color-scheme:dark; --bg:#0b0d10; --surface:#111419; --surface-2:#171b21; --surface-3:#1c2128; --line:#272d35; --line-strong:#39414c; --muted:#98a2ad; --text:#eef1f4; --accent:#a8db68; --accent-strong:#b8ed76; --code:#080a0d; --header:rgba(11,13,16,.88); --shadow:0 18px 50px rgba(0,0,0,.22); }
-[data-theme='light'] { color-scheme:light; --bg:#f8f9fa; --surface:#fff; --surface-2:#f1f3f5; --surface-3:#e9ecef; --line:#d9dee4; --line-strong:#b9c1cb; --muted:#5c6672; --text:#15181d; --accent:#477c0d; --accent-strong:#365f09; --code:#101318; --header:rgba(248,249,250,.9); --shadow:0 18px 45px rgba(25,31,38,.08); }
-* { box-sizing:border-box; }
-[hidden] { display:none!important; }
-html { background:var(--bg); scroll-behavior:smooth; }
-body { min-height:100vh; margin:0; background:var(--bg); color:var(--text); font-family:Inter, ui-sans-serif, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; font-size:15px; line-height:1.65; letter-spacing:-.008em; -webkit-font-smoothing:antialiased; text-rendering:optimizeLegibility; }
-::selection { background:var(--accent); color:#10130d; }
-a { color:inherit; }
-a:focus-visible,button:focus-visible { outline:2px solid var(--accent); outline-offset:3px; }
-.shell { width:min(1120px, calc(100% - 48px)); margin-inline:auto; }
-.site-header { position:sticky; top:0; z-index:30; border-bottom:1px solid var(--line); background:var(--header); -webkit-backdrop-filter:blur(16px); backdrop-filter:blur(16px); }
-.nav { min-height:64px; display:flex; align-items:center; justify-content:space-between; gap:24px; }
-.brand { display:inline-flex; align-items:center; gap:10px; color:var(--text); text-decoration:none; font:700 12px/1 ui-monospace,SFMono-Regular,Menlo,monospace; letter-spacing:.13em; }
-.mark { width:17px; height:17px; border:1px solid var(--accent); position:relative; flex:0 0 auto; }
-.mark:after { content:""; position:absolute; width:4px; height:4px; right:3px; bottom:3px; background:var(--accent); }
-.links { display:flex; align-items:center; gap:5px; color:var(--muted); font-size:13px; }
-.nav-link { padding:7px 10px; border-radius:5px; text-decoration:none; transition:color .15s ease,background .15s ease; }
-.nav-link:hover,.nav-link[aria-current='page'] { color:var(--text); background:var(--surface-2); }
-.theme-toggle { position:relative; display:grid; place-items:center; width:34px; height:34px; margin-inline:3px; padding:0; border:0; border-radius:6px; background:transparent; color:var(--muted); cursor:pointer; }
-.theme-toggle:hover { background:var(--surface-2); color:var(--text); }
-.theme-sun,.theme-moon { grid-area:1/1; font-size:17px; line-height:1; transition:opacity .15s ease,transform .2s ease; }
-[data-theme='light'] .theme-sun { opacity:1; transform:rotate(0) scale(1); color:var(--text); } [data-theme='light'] .theme-moon { opacity:0; transform:rotate(-35deg) scale(.65); }
-[data-theme='dark'] .theme-sun { opacity:0; transform:rotate(35deg) scale(.65); } [data-theme='dark'] .theme-moon { opacity:1; transform:rotate(0) scale(1); color:var(--text); }
-.button { min-height:35px; display:inline-flex; align-items:center; justify-content:center; gap:7px; padding:8px 13px; border:1px solid var(--line-strong); border-radius:6px; background:var(--surface); color:var(--text)!important; text-decoration:none; font:600 12px/1.2 ui-monospace,SFMono-Regular,Menlo,monospace; transition:transform .15s ease,border-color .15s ease,background .15s ease; }
-.button:hover { border-color:var(--muted); background:var(--surface-2); transform:translateY(-1px); }.button:active { transform:translateY(0); }.button.primary { background:var(--text); border-color:var(--text); color:var(--bg)!important; }.button.primary:hover { opacity:.9; }
-.hero { max-width:880px; padding:104px 0 82px; }
-.kicker { display:inline-flex; align-items:center; gap:8px; margin-bottom:18px; color:var(--muted); font:12px/1.4 ui-monospace,SFMono-Regular,Menlo,monospace; }
-.kicker:before { content:""; width:7px; height:7px; border-radius:50%; background:var(--accent); box-shadow:0 0 0 4px color-mix(in srgb,var(--accent) 13%,transparent); }
-h1 { max-width:860px; margin:0 0 24px; font-size:clamp(42px,6.1vw,70px); line-height:1.03; letter-spacing:-.052em; font-weight:680; text-wrap:balance; }
-.lede { max-width:720px; margin:0; color:var(--muted); font-size:clamp(17px,2vw,20px); line-height:1.65; letter-spacing:-.015em; text-wrap:pretty; }
-.actions { display:flex; flex-wrap:wrap; gap:10px; margin-top:30px; }
-.meta { display:flex; flex-wrap:wrap; gap:9px 22px; margin-top:32px; color:var(--muted); font:12px/1.5 ui-monospace,SFMono-Regular,Menlo,monospace; }
-.meta span:before { content:""; display:inline-block; width:6px; height:6px; margin-right:8px; border-radius:50%; background:var(--line-strong); }.meta .available:before { background:var(--accent); }
-.section { padding:76px 0; border-top:1px solid var(--line); }
-.section-title { margin:0 0 12px; font-size:clamp(24px,3vw,30px); line-height:1.25; letter-spacing:-.032em; font-weight:650; }.section-intro { max-width:720px; margin:0 0 34px; color:var(--muted); font-size:16px; text-wrap:pretty; }
-.example-grid { display:grid; grid-template-columns:1fr 1fr; border:1px solid var(--line-strong); border-radius:9px; overflow:hidden; background:var(--code); box-shadow:var(--shadow); }
-.code-panel + .code-panel { border-left:1px solid #2a3039; }.code-label { display:flex; justify-content:space-between; padding:10px 15px; border-bottom:1px solid #2a3039; color:#8d98a6; background:#101318; font:11px/1.4 ui-monospace,SFMono-Regular,Menlo,monospace; }
-pre { margin:0; min-height:270px; padding:20px; overflow:auto; color:#dce3eb; font:13px/1.75 ui-monospace,SFMono-Regular,Menlo,monospace; tab-size:2; }.token-key{color:#a8db68}.token-string{color:#9ac7ee}.token-muted{color:#7f8996}.token-pass{color:#a8db68}
-.capabilities { width:100%; border-spacing:0; border:1px solid var(--line); border-radius:8px; overflow:hidden; background:var(--surface); font-size:14px; }
-.capabilities th,.capabilities td { padding:14px 16px; border-bottom:1px solid var(--line); text-align:left; vertical-align:top; }.capabilities th { background:var(--surface-2); color:var(--muted); font:600 11px ui-monospace,SFMono-Regular,Menlo,monospace; text-transform:uppercase; letter-spacing:.055em; }.capabilities td:first-child { width:19%; color:var(--text); font:600 13px ui-monospace,SFMono-Regular,Menlo,monospace; }.capabilities td:nth-child(2) { color:var(--muted); }.capabilities tr:last-child td { border-bottom:0; }.capabilities tbody tr:hover { background:color-mix(in srgb,var(--surface-2) 58%,transparent); }
-.tag { display:inline-flex; align-items:center; gap:6px; white-space:nowrap; color:var(--accent-strong); font:600 11px ui-monospace,SFMono-Regular,Menlo,monospace; }.tag:before { content:""; width:6px; height:6px; border-radius:50%; background:var(--accent); }.tag.planned { color:var(--muted); }.tag.planned:before { background:var(--line-strong); }
-.split { display:grid; grid-template-columns:1fr 1fr; gap:64px; }.split h3 { margin:0 0 12px; font-size:18px; letter-spacing:-.02em; }.split p { margin:0; color:var(--muted); }
-.notice { padding:17px 18px; border:1px solid var(--line); border-left:3px solid var(--accent); border-radius:0 7px 7px 0; background:var(--surface); color:var(--muted); font-size:14px; }.notice strong { color:var(--text); }
-.support-page { padding-bottom:76px; }.support-hero { max-width:850px; padding:96px 0 64px; }.support-hero h1 { margin-bottom:22px; }.support-grid { display:grid; grid-template-columns:minmax(260px,.72fr) minmax(0,1.5fr); gap:18px; align-items:stretch; }.support-card { display:flex; flex-direction:column; justify-content:space-between; gap:36px; padding:30px; border:1px solid var(--line); border-radius:9px; background:var(--surface); }.support-card h2,.support-footnote h2 { margin:4px 0 12px; font-size:24px; line-height:1.25; letter-spacing:-.03em; }.support-card p { margin:0; color:var(--muted); }.eyebrow { color:var(--accent-strong)!important; font:600 11px/1.4 ui-monospace,SFMono-Regular,Menlo,monospace; letter-spacing:.06em; text-transform:uppercase; }.sponsor-card .button { align-self:flex-start; }.upi-card { gap:25px; }.amount-picker { display:grid; grid-template-columns:repeat(3,1fr); gap:8px; }.amount-option { min-height:52px; border:1px solid var(--line-strong); border-radius:6px; background:var(--surface-2); color:var(--muted); cursor:pointer; font:650 15px/1 ui-monospace,SFMono-Regular,Menlo,monospace; }.amount-option:hover { color:var(--text); border-color:var(--muted); }.amount-option.selected { color:var(--bg); border-color:var(--accent); background:var(--accent); }.custom-amount { position:relative; display:grid; grid-template-columns:auto 1fr; grid-template-rows:auto auto; align-content:center; column-gap:6px; min-height:52px; padding:6px 9px; border:1px solid var(--line-strong); border-radius:6px; background:var(--surface-2); color:var(--muted); font:10px/1.2 ui-monospace,SFMono-Regular,Menlo,monospace; }.custom-amount>span:first-child { grid-column:1/-1; }.custom-amount .currency { align-self:center; color:var(--text); font-size:13px; }.custom-amount input { min-width:0; width:100%; padding:0; border:0; outline:0; background:transparent; color:var(--text); font:650 13px/1.2 ui-monospace,SFMono-Regular,Menlo,monospace; }.custom-amount:focus-within { border-color:var(--accent); outline:2px solid color-mix(in srgb,var(--accent) 30%,transparent); }.custom-amount input::-webkit-inner-spin-button { appearance:none; }.upi-payment { display:grid; grid-template-columns:210px 1fr; gap:26px; align-items:center; }.qr-frame { width:210px; height:210px; padding:1px; overflow:hidden; border:1px solid var(--line-strong); border-radius:7px; background:#fff; }.qr-frame img { display:block; width:208px; height:208px; }.payment-amount { color:var(--text)!important; font:650 18px/1.3 ui-monospace,SFMono-Regular,Menlo,monospace; }.upi-id { margin:8px 0 18px!important; font:12px/1.5 ui-monospace,SFMono-Regular,Menlo,monospace; overflow-wrap:anywhere; }.desktop-payment-hint { margin-bottom:14px!important; color:var(--text)!important; font-size:14px; }.pay-button { margin-bottom:14px; }.button.disabled { opacity:.5; pointer-events:none; }.payment-note { max-width:430px; font-size:12px; line-height:1.55; }.notice code { color:var(--text); }.support-footnote { max-width:720px; margin-top:64px; padding-top:60px; border-top:1px solid var(--line); }.support-footnote p { margin:0 0 22px; color:var(--muted); }
-.site-footer { margin-top:24px; border-top:1px solid var(--line); background:var(--surface); }.footer-grid { display:grid; grid-template-columns:1.1fr 2fr; gap:80px; padding:56px 0 44px; }.footer-brand { margin-bottom:15px; }.footer-grid p { max-width:300px; margin:0; color:var(--muted); font-size:13px; }.footer-links { display:grid; grid-template-columns:repeat(3,1fr); gap:32px; }.footer-links div { display:flex; flex-direction:column; align-items:flex-start; gap:8px; }.footer-links strong { margin-bottom:3px; color:var(--text); font-size:12px; }.footer-links a { color:var(--muted); text-decoration:none; font-size:13px; }.footer-links a:hover { color:var(--text); }.footer-bottom { display:flex; justify-content:space-between; padding:18px 0 28px; border-top:1px solid var(--line); color:var(--muted); font-size:12px; }
-@media(max-width:800px){.shell{width:min(100% - 32px,1120px)}.links{gap:2px}.nav-link{padding:7px 8px}.hero{padding:76px 0 58px}.example-grid,.split,.support-grid{grid-template-columns:1fr}.code-panel + .code-panel{border-left:0;border-top:1px solid #2a3039}.footer-grid{grid-template-columns:1fr;gap:36px}.footer-grid p{max-width:440px}.section{padding:58px 0}.support-hero{padding:72px 0 52px}}
-@media(max-width:620px){.nav-link{display:none}.nav-link.support-link{display:inline-flex}.nav-cta{display:none}.nav{min-height:58px}.hero{padding:62px 0 48px}h1{font-size:clamp(38px,11vw,52px)}.capabilities,.capabilities tbody,.capabilities tr,.capabilities td{display:block;width:100%}.capabilities thead{display:none}.capabilities tr{padding:14px 15px;border-bottom:1px solid var(--line)}.capabilities tr:last-child{border-bottom:0}.capabilities td{padding:3px 0!important;border:0!important}.capabilities td:first-child{width:100%;margin-bottom:4px}.capabilities td:last-child{margin-top:8px}.footer-links{grid-template-columns:1fr 1fr}.footer-bottom{gap:12px;flex-direction:column}}
-@media(max-width:420px){.shell{width:calc(100% - 24px)}.footer-links{grid-template-columns:1fr}.button{padding-inline:11px}}
-@media(max-width:520px){.support-card{padding:22px}.upi-payment{grid-template-columns:1fr}.qr-frame{margin-inline:auto}.upi-details{text-align:center}.pay-button{width:100%}}
-@media(prefers-reduced-motion:reduce){html{scroll-behavior:auto}*,*:before,*:after{transition:none!important}}
+:root {
+ color-scheme: dark;
+ --bg: #0b0d10;
+ --surface: #111419;
+ --surface-2: #171b21;
+ --surface-3: #1c2128;
+ --line: #272d35;
+ --line-strong: #39414c;
+ --muted: #98a2ad;
+ --text: #eef1f4;
+ --accent: #a8db68;
+ --accent-strong: #b8ed76;
+ --code: #080a0d;
+ --header: rgba(11, 13, 16, 0.88);
+ --shadow: 0 18px 50px rgba(0, 0, 0, 0.22);
+}
+[data-theme='light'] {
+ color-scheme: light;
+ --bg: #f8f9fa;
+ --surface: #fff;
+ --surface-2: #f1f3f5;
+ --surface-3: #e9ecef;
+ --line: #d9dee4;
+ --line-strong: #b9c1cb;
+ --muted: #5c6672;
+ --text: #15181d;
+ --accent: #477c0d;
+ --accent-strong: #365f09;
+ --code: #101318;
+ --header: rgba(248, 249, 250, 0.9);
+ --shadow: 0 18px 45px rgba(25, 31, 38, 0.08);
+}
+* {
+ box-sizing: border-box;
+}
+[hidden] {
+ display: none !important;
+}
+html {
+ background: var(--bg);
+ scroll-behavior: smooth;
+}
+body {
+ min-height: 100vh;
+ margin: 0;
+ background: var(--bg);
+ color: var(--text);
+ font-family:
+ Inter,
+ ui-sans-serif,
+ -apple-system,
+ BlinkMacSystemFont,
+ 'Segoe UI',
+ sans-serif;
+ font-size: 15px;
+ line-height: 1.65;
+ letter-spacing: -0.008em;
+ -webkit-font-smoothing: antialiased;
+ text-rendering: optimizeLegibility;
+}
+::selection {
+ background: var(--accent);
+ color: #10130d;
+}
+a {
+ color: inherit;
+}
+a:focus-visible,
+button:focus-visible {
+ outline: 2px solid var(--accent);
+ outline-offset: 3px;
+}
+.shell {
+ width: min(1120px, calc(100% - 48px));
+ margin-inline: auto;
+}
+.site-header {
+ position: sticky;
+ top: 0;
+ z-index: 30;
+ border-bottom: 1px solid var(--line);
+ background: var(--header);
+ -webkit-backdrop-filter: blur(16px);
+ backdrop-filter: blur(16px);
+}
+.nav {
+ min-height: 64px;
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ gap: 24px;
+}
+.brand {
+ display: inline-flex;
+ align-items: center;
+ gap: 10px;
+ color: var(--text);
+ text-decoration: none;
+ font:
+ 700 12px/1 ui-monospace,
+ SFMono-Regular,
+ Menlo,
+ monospace;
+ letter-spacing: 0.13em;
+}
+.mark {
+ width: 17px;
+ height: 17px;
+ border: 1px solid var(--accent);
+ position: relative;
+ flex: 0 0 auto;
+}
+.mark:after {
+ content: '';
+ position: absolute;
+ width: 4px;
+ height: 4px;
+ right: 3px;
+ bottom: 3px;
+ background: var(--accent);
+}
+.links {
+ display: flex;
+ align-items: center;
+ gap: 5px;
+ color: var(--muted);
+ font-size: 13px;
+}
+.nav-link {
+ padding: 7px 10px;
+ border-radius: 5px;
+ text-decoration: none;
+ transition:
+ color 0.15s ease,
+ background 0.15s ease;
+}
+.nav-link:hover,
+.nav-link[aria-current='page'] {
+ color: var(--text);
+ background: var(--surface-2);
+}
+.theme-toggle {
+ position: relative;
+ display: grid;
+ place-items: center;
+ width: 34px;
+ height: 34px;
+ margin-inline: 3px;
+ padding: 0;
+ border: 0;
+ border-radius: 6px;
+ background: transparent;
+ color: var(--muted);
+ cursor: pointer;
+}
+.theme-toggle:hover {
+ background: var(--surface-2);
+ color: var(--text);
+}
+.theme-sun,
+.theme-moon {
+ grid-area: 1/1;
+ font-size: 17px;
+ line-height: 1;
+ transition:
+ opacity 0.15s ease,
+ transform 0.2s ease;
+}
+[data-theme='light'] .theme-sun {
+ opacity: 1;
+ transform: rotate(0) scale(1);
+ color: var(--text);
+}
+[data-theme='light'] .theme-moon {
+ opacity: 0;
+ transform: rotate(-35deg) scale(0.65);
+}
+[data-theme='dark'] .theme-sun {
+ opacity: 0;
+ transform: rotate(35deg) scale(0.65);
+}
+[data-theme='dark'] .theme-moon {
+ opacity: 1;
+ transform: rotate(0) scale(1);
+ color: var(--text);
+}
+.button {
+ min-height: 35px;
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ gap: 7px;
+ padding: 8px 13px;
+ border: 1px solid var(--line-strong);
+ border-radius: 6px;
+ background: var(--surface);
+ color: var(--text) !important;
+ text-decoration: none;
+ font:
+ 600 12px/1.2 ui-monospace,
+ SFMono-Regular,
+ Menlo,
+ monospace;
+ transition:
+ transform 0.15s ease,
+ border-color 0.15s ease,
+ background 0.15s ease;
+}
+.button:hover {
+ border-color: var(--muted);
+ background: var(--surface-2);
+ transform: translateY(-1px);
+}
+.button:active {
+ transform: translateY(0);
+}
+.button.primary {
+ background: var(--text);
+ border-color: var(--text);
+ color: var(--bg) !important;
+}
+.button.primary:hover {
+ opacity: 0.9;
+}
+.hero {
+ max-width: 880px;
+ padding: 104px 0 82px;
+}
+.kicker {
+ display: inline-flex;
+ align-items: center;
+ gap: 8px;
+ margin-bottom: 18px;
+ color: var(--muted);
+ font:
+ 12px/1.4 ui-monospace,
+ SFMono-Regular,
+ Menlo,
+ monospace;
+}
+.kicker:before {
+ content: '';
+ width: 7px;
+ height: 7px;
+ border-radius: 50%;
+ background: var(--accent);
+ box-shadow: 0 0 0 4px color-mix(in srgb, var(--accent) 13%, transparent);
+}
+h1 {
+ max-width: 860px;
+ margin: 0 0 24px;
+ font-size: clamp(42px, 6.1vw, 70px);
+ line-height: 1.03;
+ letter-spacing: -0.052em;
+ font-weight: 680;
+ text-wrap: balance;
+}
+.lede {
+ max-width: 720px;
+ margin: 0;
+ color: var(--muted);
+ font-size: clamp(17px, 2vw, 20px);
+ line-height: 1.65;
+ letter-spacing: -0.015em;
+ text-wrap: pretty;
+}
+.actions {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 10px;
+ margin-top: 30px;
+}
+.meta {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 9px 22px;
+ margin-top: 32px;
+ color: var(--muted);
+ font:
+ 12px/1.5 ui-monospace,
+ SFMono-Regular,
+ Menlo,
+ monospace;
+}
+.meta span:before {
+ content: '';
+ display: inline-block;
+ width: 6px;
+ height: 6px;
+ margin-right: 8px;
+ border-radius: 50%;
+ background: var(--line-strong);
+}
+.meta .available:before {
+ background: var(--accent);
+}
+.section {
+ padding: 76px 0;
+ border-top: 1px solid var(--line);
+}
+.section-title {
+ margin: 0 0 12px;
+ font-size: clamp(24px, 3vw, 30px);
+ line-height: 1.25;
+ letter-spacing: -0.032em;
+ font-weight: 650;
+}
+.section-intro {
+ max-width: 720px;
+ margin: 0 0 34px;
+ color: var(--muted);
+ font-size: 16px;
+ text-wrap: pretty;
+}
+.example-grid {
+ display: grid;
+ grid-template-columns: 1fr 1fr;
+ border: 1px solid var(--line-strong);
+ border-radius: 9px;
+ overflow: hidden;
+ background: var(--code);
+ box-shadow: var(--shadow);
+}
+.code-panel + .code-panel {
+ border-left: 1px solid #2a3039;
+}
+.code-label {
+ display: flex;
+ justify-content: space-between;
+ padding: 10px 15px;
+ border-bottom: 1px solid #2a3039;
+ color: #8d98a6;
+ background: #101318;
+ font:
+ 11px/1.4 ui-monospace,
+ SFMono-Regular,
+ Menlo,
+ monospace;
+}
+pre {
+ margin: 0;
+ min-height: 270px;
+ padding: 20px;
+ overflow: auto;
+ color: #dce3eb;
+ font:
+ 13px/1.75 ui-monospace,
+ SFMono-Regular,
+ Menlo,
+ monospace;
+ tab-size: 2;
+}
+.token-key {
+ color: #a8db68;
+}
+.token-string {
+ color: #9ac7ee;
+}
+.token-muted {
+ color: #7f8996;
+}
+.token-pass {
+ color: #a8db68;
+}
+.capabilities {
+ width: 100%;
+ border-spacing: 0;
+ border: 1px solid var(--line);
+ border-radius: 8px;
+ overflow: hidden;
+ background: var(--surface);
+ font-size: 14px;
+}
+.capabilities th,
+.capabilities td {
+ padding: 14px 16px;
+ border-bottom: 1px solid var(--line);
+ text-align: left;
+ vertical-align: top;
+}
+.capabilities th {
+ background: var(--surface-2);
+ color: var(--muted);
+ font:
+ 600 11px ui-monospace,
+ SFMono-Regular,
+ Menlo,
+ monospace;
+ text-transform: uppercase;
+ letter-spacing: 0.055em;
+}
+.capabilities td:first-child {
+ width: 19%;
+ color: var(--text);
+ font:
+ 600 13px ui-monospace,
+ SFMono-Regular,
+ Menlo,
+ monospace;
+}
+.capabilities td:nth-child(2) {
+ color: var(--muted);
+}
+.capabilities tr:last-child td {
+ border-bottom: 0;
+}
+.capabilities tbody tr:hover {
+ background: color-mix(in srgb, var(--surface-2) 58%, transparent);
+}
+.tag {
+ display: inline-flex;
+ align-items: center;
+ gap: 6px;
+ white-space: nowrap;
+ color: var(--accent-strong);
+ font:
+ 600 11px ui-monospace,
+ SFMono-Regular,
+ Menlo,
+ monospace;
+}
+.tag:before {
+ content: '';
+ width: 6px;
+ height: 6px;
+ border-radius: 50%;
+ background: var(--accent);
+}
+.tag.planned {
+ color: var(--muted);
+}
+.tag.planned:before {
+ background: var(--line-strong);
+}
+.split {
+ display: grid;
+ grid-template-columns: 1fr 1fr;
+ gap: 64px;
+}
+.split h3 {
+ margin: 0 0 12px;
+ font-size: 18px;
+ letter-spacing: -0.02em;
+}
+.split p {
+ margin: 0;
+ color: var(--muted);
+}
+.notice {
+ padding: 17px 18px;
+ border: 1px solid var(--line);
+ border-left: 3px solid var(--accent);
+ border-radius: 0 7px 7px 0;
+ background: var(--surface);
+ color: var(--muted);
+ font-size: 14px;
+}
+.notice strong {
+ color: var(--text);
+}
+.support-page {
+ padding-bottom: 76px;
+}
+.support-hero {
+ max-width: 850px;
+ padding: 96px 0 64px;
+}
+.support-hero h1 {
+ margin-bottom: 22px;
+}
+.support-grid {
+ display: grid;
+ grid-template-columns: minmax(260px, 0.72fr) minmax(0, 1.5fr);
+ gap: 18px;
+ align-items: stretch;
+}
+.support-card {
+ display: flex;
+ flex-direction: column;
+ justify-content: space-between;
+ gap: 36px;
+ padding: 30px;
+ border: 1px solid var(--line);
+ border-radius: 9px;
+ background: var(--surface);
+}
+.support-card h2,
+.support-footnote h2 {
+ margin: 4px 0 12px;
+ font-size: 24px;
+ line-height: 1.25;
+ letter-spacing: -0.03em;
+}
+.support-card p {
+ margin: 0;
+ color: var(--muted);
+}
+.eyebrow {
+ color: var(--accent-strong) !important;
+ font:
+ 600 11px/1.4 ui-monospace,
+ SFMono-Regular,
+ Menlo,
+ monospace;
+ letter-spacing: 0.06em;
+ text-transform: uppercase;
+}
+.sponsor-card .button {
+ align-self: flex-start;
+}
+.upi-card {
+ gap: 25px;
+}
+.amount-picker {
+ display: grid;
+ grid-template-columns: repeat(3, 1fr);
+ gap: 8px;
+}
+.amount-option {
+ min-height: 52px;
+ border: 1px solid var(--line-strong);
+ border-radius: 6px;
+ background: var(--surface-2);
+ color: var(--muted);
+ cursor: pointer;
+ font:
+ 650 15px/1 ui-monospace,
+ SFMono-Regular,
+ Menlo,
+ monospace;
+}
+.amount-option:hover {
+ color: var(--text);
+ border-color: var(--muted);
+}
+.amount-option.selected {
+ color: var(--bg);
+ border-color: var(--accent);
+ background: var(--accent);
+}
+.custom-amount {
+ position: relative;
+ display: grid;
+ grid-template-columns: auto 1fr;
+ grid-template-rows: auto auto;
+ align-content: center;
+ column-gap: 6px;
+ min-height: 52px;
+ padding: 6px 9px;
+ border: 1px solid var(--line-strong);
+ border-radius: 6px;
+ background: var(--surface-2);
+ color: var(--muted);
+ font:
+ 10px/1.2 ui-monospace,
+ SFMono-Regular,
+ Menlo,
+ monospace;
+}
+.custom-amount > span:first-child {
+ grid-column: 1/-1;
+}
+.custom-amount .currency {
+ align-self: center;
+ color: var(--text);
+ font-size: 13px;
+}
+.custom-amount input {
+ min-width: 0;
+ width: 100%;
+ padding: 0;
+ border: 0;
+ outline: 0;
+ background: transparent;
+ color: var(--text);
+ font:
+ 650 13px/1.2 ui-monospace,
+ SFMono-Regular,
+ Menlo,
+ monospace;
+}
+.custom-amount:focus-within {
+ border-color: var(--accent);
+ outline: 2px solid color-mix(in srgb, var(--accent) 30%, transparent);
+}
+.custom-amount input::-webkit-inner-spin-button {
+ appearance: none;
+}
+.upi-payment {
+ display: grid;
+ grid-template-columns: 210px 1fr;
+ gap: 26px;
+ align-items: center;
+}
+.qr-frame {
+ width: 210px;
+ height: 210px;
+ padding: 1px;
+ overflow: hidden;
+ border: 1px solid var(--line-strong);
+ border-radius: 7px;
+ background: #fff;
+}
+.qr-frame img {
+ display: block;
+ width: 208px;
+ height: 208px;
+}
+.payment-amount {
+ color: var(--text) !important;
+ font:
+ 650 18px/1.3 ui-monospace,
+ SFMono-Regular,
+ Menlo,
+ monospace;
+}
+.upi-id {
+ margin: 8px 0 18px !important;
+ font:
+ 12px/1.5 ui-monospace,
+ SFMono-Regular,
+ Menlo,
+ monospace;
+ overflow-wrap: anywhere;
+}
+.desktop-payment-hint {
+ margin-bottom: 14px !important;
+ color: var(--text) !important;
+ font-size: 14px;
+}
+.pay-button {
+ margin-bottom: 14px;
+}
+.button.disabled {
+ opacity: 0.5;
+ pointer-events: none;
+}
+.payment-note {
+ max-width: 430px;
+ font-size: 12px;
+ line-height: 1.55;
+}
+.notice code {
+ color: var(--text);
+}
+.support-footnote {
+ max-width: 720px;
+ margin-top: 64px;
+ padding-top: 60px;
+ border-top: 1px solid var(--line);
+}
+.support-footnote p {
+ margin: 0 0 22px;
+ color: var(--muted);
+}
+.site-footer {
+ margin-top: 24px;
+ border-top: 1px solid var(--line);
+ background: var(--surface);
+}
+.footer-grid {
+ display: grid;
+ grid-template-columns: 1.1fr 2fr;
+ gap: 80px;
+ padding: 56px 0 44px;
+}
+.footer-brand {
+ margin-bottom: 15px;
+}
+.footer-grid p {
+ max-width: 300px;
+ margin: 0;
+ color: var(--muted);
+ font-size: 13px;
+}
+.footer-links {
+ display: grid;
+ grid-template-columns: repeat(3, 1fr);
+ gap: 32px;
+}
+.footer-links div {
+ display: flex;
+ flex-direction: column;
+ align-items: flex-start;
+ gap: 8px;
+}
+.footer-links strong {
+ margin-bottom: 3px;
+ color: var(--text);
+ font-size: 12px;
+}
+.footer-links a {
+ color: var(--muted);
+ text-decoration: none;
+ font-size: 13px;
+}
+.footer-links a:hover {
+ color: var(--text);
+}
+.footer-bottom {
+ display: flex;
+ justify-content: space-between;
+ padding: 18px 0 28px;
+ border-top: 1px solid var(--line);
+ color: var(--muted);
+ font-size: 12px;
+}
+@media (max-width: 800px) {
+ .shell {
+ width: min(100% - 32px, 1120px);
+ }
+ .links {
+ gap: 2px;
+ }
+ .nav-link {
+ padding: 7px 8px;
+ }
+ .hero {
+ padding: 76px 0 58px;
+ }
+ .example-grid,
+ .split,
+ .support-grid {
+ grid-template-columns: 1fr;
+ }
+ .code-panel + .code-panel {
+ border-left: 0;
+ border-top: 1px solid #2a3039;
+ }
+ .footer-grid {
+ grid-template-columns: 1fr;
+ gap: 36px;
+ }
+ .footer-grid p {
+ max-width: 440px;
+ }
+ .section {
+ padding: 58px 0;
+ }
+ .support-hero {
+ padding: 72px 0 52px;
+ }
+}
+@media (max-width: 620px) {
+ .nav-link {
+ display: none;
+ }
+ .nav-link.support-link {
+ display: inline-flex;
+ }
+ .nav-cta {
+ display: none;
+ }
+ .nav {
+ min-height: 58px;
+ }
+ .hero {
+ padding: 62px 0 48px;
+ }
+ h1 {
+ font-size: clamp(38px, 11vw, 52px);
+ }
+ .capabilities,
+ .capabilities tbody,
+ .capabilities tr,
+ .capabilities td {
+ display: block;
+ width: 100%;
+ }
+ .capabilities thead {
+ display: none;
+ }
+ .capabilities tr {
+ padding: 14px 15px;
+ border-bottom: 1px solid var(--line);
+ }
+ .capabilities tr:last-child {
+ border-bottom: 0;
+ }
+ .capabilities td {
+ padding: 3px 0 !important;
+ border: 0 !important;
+ }
+ .capabilities td:first-child {
+ width: 100%;
+ margin-bottom: 4px;
+ }
+ .capabilities td:last-child {
+ margin-top: 8px;
+ }
+ .footer-links {
+ grid-template-columns: 1fr 1fr;
+ }
+ .footer-bottom {
+ gap: 12px;
+ flex-direction: column;
+ }
+}
+@media (max-width: 420px) {
+ .shell {
+ width: calc(100% - 24px);
+ }
+ .footer-links {
+ grid-template-columns: 1fr;
+ }
+ .button {
+ padding-inline: 11px;
+ }
+}
+@media (max-width: 520px) {
+ .support-card {
+ padding: 22px;
+ }
+ .upi-payment {
+ grid-template-columns: 1fr;
+ }
+ .qr-frame {
+ margin-inline: auto;
+ }
+ .upi-details {
+ text-align: center;
+ }
+ .pay-button {
+ width: 100%;
+ }
+}
+@media (prefers-reduced-motion: reduce) {
+ html {
+ scroll-behavior: auto;
+ }
+ *,
+ *:before,
+ *:after {
+ transition: none !important;
+ }
+}
diff --git a/src/styles/starlight.css b/src/styles/starlight.css
index e09be92..f2b319b 100644
--- a/src/styles/starlight.css
+++ b/src/styles/starlight.css
@@ -1,14 +1,120 @@
-:root { --sl-color-accent-low:#1b2d12; --sl-color-accent:#b8f36b; --sl-color-accent-high:#e6ffca; --sl-color-white:#f2f4f7; --sl-color-gray-1:#d8dde6; --sl-color-gray-2:#aab2c0; --sl-color-gray-3:#747e8e; --sl-color-gray-4:#454d5a; --sl-color-gray-5:#292e37; --sl-color-gray-6:#171a20; --sl-color-black:#090a0c; --sl-font:Inter,ui-sans-serif,system-ui,sans-serif; --sl-font-mono:ui-monospace,SFMono-Regular,monospace; }
-:root[data-theme='light'] { --sl-color-accent-low:#e5f5d1; --sl-color-accent:#477c0d; --sl-color-accent-high:#1e3b04; --sl-color-white:#111318; --sl-color-gray-1:#2d333b; --sl-color-gray-2:#4e5662; --sl-color-gray-3:#68717e; --sl-color-gray-4:#a9b0ba; --sl-color-gray-5:#d9dde4; --sl-color-gray-6:#eef0f3; --sl-color-black:#ffffff; --sl-color-bg:#f6f7f9; --sl-color-bg-nav:#f6f7f9; --sl-color-bg-sidebar:#ffffff; --sl-color-text:#2d333b; --sl-color-text-accent:#477c0d; }
-html { scroll-behavior:smooth; }
-body { letter-spacing:-.01em; -webkit-font-smoothing:antialiased; text-rendering:optimizeLegibility; }
-::selection { background:var(--sl-color-accent); color:var(--sl-color-black); }
-.header { border-color:var(--sl-color-gray-5)!important; background:color-mix(in srgb,var(--sl-color-black) 88%,transparent)!important; backdrop-filter:blur(16px); }
-.site-title { gap:.65rem; font-family:var(--sl-font-mono); letter-spacing:.12em; font-size:.78rem; }.site-title:before { content:""; width:17px; height:17px; flex:0 0 auto; border:1px solid var(--sl-color-accent); background:linear-gradient(var(--sl-color-accent),var(--sl-color-accent)) right 3px bottom 3px/4px 4px no-repeat; }
-.sidebar-pane { border-color:var(--sl-color-gray-5); }.sidebar-content a[aria-current='page'] { border-radius:5px; font-weight:600; }.right-sidebar { border-color:var(--sl-color-gray-5); }
-.content-panel { border-color:var(--sl-color-gray-5); }.main-pane { --sl-content-width:48rem; }
-.hero h1 { letter-spacing:-.05em; text-wrap:balance; }.hero .tagline { max-width:44rem; color:var(--sl-color-gray-2); }
-.sl-markdown-content h2 { letter-spacing:-.03em; }.sl-markdown-content h3 { letter-spacing:-.02em; }.sl-markdown-content a:not(.sl-link-button) { text-underline-offset:.2em; text-decoration-thickness:1px; }
-.sl-markdown-content table { display:table; width:100%; }.sl-markdown-content th { background:var(--sl-color-gray-6); }.sl-markdown-content th,.sl-markdown-content td { border-color:var(--sl-color-gray-5); }
-code { border-radius:4px; } pre { border:1px solid var(--sl-color-gray-5)!important; border-radius:8px!important; box-shadow:none!important; }
-select[aria-label='Select theme'] { border-radius:6px; }
+:root {
+ --sl-color-accent-low: #1b2d12;
+ --sl-color-accent: #b8f36b;
+ --sl-color-accent-high: #e6ffca;
+ --sl-color-white: #f2f4f7;
+ --sl-color-gray-1: #d8dde6;
+ --sl-color-gray-2: #aab2c0;
+ --sl-color-gray-3: #747e8e;
+ --sl-color-gray-4: #454d5a;
+ --sl-color-gray-5: #292e37;
+ --sl-color-gray-6: #171a20;
+ --sl-color-black: #090a0c;
+ --sl-font: Inter, ui-sans-serif, system-ui, sans-serif;
+ --sl-font-mono: ui-monospace, SFMono-Regular, monospace;
+}
+:root[data-theme='light'] {
+ --sl-color-accent-low: #e5f5d1;
+ --sl-color-accent: #477c0d;
+ --sl-color-accent-high: #1e3b04;
+ --sl-color-white: #111318;
+ --sl-color-gray-1: #2d333b;
+ --sl-color-gray-2: #4e5662;
+ --sl-color-gray-3: #68717e;
+ --sl-color-gray-4: #a9b0ba;
+ --sl-color-gray-5: #d9dde4;
+ --sl-color-gray-6: #eef0f3;
+ --sl-color-black: #ffffff;
+ --sl-color-bg: #f6f7f9;
+ --sl-color-bg-nav: #f6f7f9;
+ --sl-color-bg-sidebar: #ffffff;
+ --sl-color-text: #2d333b;
+ --sl-color-text-accent: #477c0d;
+}
+html {
+ scroll-behavior: smooth;
+}
+body {
+ letter-spacing: -0.01em;
+ -webkit-font-smoothing: antialiased;
+ text-rendering: optimizeLegibility;
+}
+::selection {
+ background: var(--sl-color-accent);
+ color: var(--sl-color-black);
+}
+.header {
+ border-color: var(--sl-color-gray-5) !important;
+ background: color-mix(in srgb, var(--sl-color-black) 88%, transparent) !important;
+ backdrop-filter: blur(16px);
+}
+.site-title {
+ gap: 0.65rem;
+ font-family: var(--sl-font-mono);
+ letter-spacing: 0.12em;
+ font-size: 0.78rem;
+}
+.site-title:before {
+ content: '';
+ width: 17px;
+ height: 17px;
+ flex: 0 0 auto;
+ border: 1px solid var(--sl-color-accent);
+ background: linear-gradient(var(--sl-color-accent), var(--sl-color-accent)) right 3px bottom 3px/4px 4px no-repeat;
+}
+.sidebar-pane {
+ border-color: var(--sl-color-gray-5);
+}
+.sidebar-content a[aria-current='page'] {
+ border-radius: 5px;
+ font-weight: 600;
+}
+.right-sidebar {
+ border-color: var(--sl-color-gray-5);
+}
+.content-panel {
+ border-color: var(--sl-color-gray-5);
+}
+.main-pane {
+ --sl-content-width: 48rem;
+}
+.hero h1 {
+ letter-spacing: -0.05em;
+ text-wrap: balance;
+}
+.hero .tagline {
+ max-width: 44rem;
+ color: var(--sl-color-gray-2);
+}
+.sl-markdown-content h2 {
+ letter-spacing: -0.03em;
+}
+.sl-markdown-content h3 {
+ letter-spacing: -0.02em;
+}
+.sl-markdown-content a:not(.sl-link-button) {
+ text-underline-offset: 0.2em;
+ text-decoration-thickness: 1px;
+}
+.sl-markdown-content table {
+ display: table;
+ width: 100%;
+}
+.sl-markdown-content th {
+ background: var(--sl-color-gray-6);
+}
+.sl-markdown-content th,
+.sl-markdown-content td {
+ border-color: var(--sl-color-gray-5);
+}
+code {
+ border-radius: 4px;
+}
+pre {
+ border: 1px solid var(--sl-color-gray-5) !important;
+ border-radius: 8px !important;
+ box-shadow: none !important;
+}
+select[aria-label='Select theme'] {
+ border-radius: 6px;
+}
diff --git a/tsconfig.json b/tsconfig.json
index 8bf91d3..a9210e6 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -1,5 +1,5 @@
{
- "extends": "astro/tsconfigs/strict",
- "include": [".astro/types.d.ts", "**/*"],
- "exclude": ["dist"]
+ "extends": "astro/tsconfigs/strict",
+ "include": [".astro/types.d.ts", "**/*"],
+ "exclude": ["dist"]
}
diff --git a/vercel.json b/vercel.json
index e37495e..5779fdf 100644
--- a/vercel.json
+++ b/vercel.json
@@ -1,6 +1,6 @@
{
- "$schema": "https://openapi.vercel.sh/vercel.json",
- "framework": "astro",
- "buildCommand": "npm run build",
- "outputDirectory": "dist"
+ "$schema": "https://openapi.vercel.sh/vercel.json",
+ "framework": "astro",
+ "buildCommand": "npm run build",
+ "outputDirectory": "dist"
}