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 npmTypeScriptMITEarly release
-
-
-

Quick start

-

No repository checkout, external MCP server, or API key is required.

-
-
terminalcommand
$ npx mcp-failure-lab demo
-
demoresult
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 npmTypeScriptMITEarly release +
+
+ +
+

Quick start

+

No repository checkout, external MCP server, or API key is required.

+
+
+
+ terminalcommand +
$ npx mcp-failure-lab demo
+
+
+
+ demoresult +
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.

- - - - - - - -
StageResponsibilityImplemented
LoadValidate a code-first or JSON scenario definition.yes
ExecuteCall a registered tool through an MCP client connection.yes
RecordCapture outcome, duration, result, or execution error.yes
AssertCheck outcome, maximum duration, `isError`, and returned text.yes
ObserveRun a separate post-condition call such as `ping`.yes
ReportRender 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. +

+ + + + + + + + + +
StageResponsibilityImplemented
LoadValidate a code-first or JSON scenario definition.yes
ExecuteCall a registered tool through an MCP client connection.yes
RecordCapture outcome, duration, result, or execution error.yes
AssertCheck outcome, maximum duration, `isError`, and returned text.yes
ObserveRun a separate post-condition call such as `ping`.yes
ReportRender 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 releaseMIT licensedNode.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.jsoninput
{
+
+	
+
+
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 releaseMIT licensedNode.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.jsoninput +
{
   "name": "bounded delay succeeds",
   "call": {
     "tool": "delay",
@@ -26,31 +49,76 @@ import SiteLayout from '../layouts/SiteLayout.astro';
     "outcome": "success",
     "maxDurationMs": 500
   }
-}
-
consoleoutput
$ npm run dev -- run \
+}
+
+
+
+ consoleoutput +
$ 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.

- - - - - - - -
CapabilityBehaviorStatus
delayBounded, cancellation-aware latency from 0 to 30 seconds.available
hangA pending request that ends only on client cancellation.available
disconnectTransport loss while a tool request is in flight.available
pingHealth and post-condition verification through a separate tool path.available
reportingConsole and stable JSON output with CI-oriented exit codes.available
HTTP / JUnitStreamable HTTP transport and JUnit reports.planned
- -
+
+

MCP Failure Lab

+

+ The current release covers a deliberately small set of failure modes and reporting primitives. +

+ + + + + + + + + +
CapabilityBehaviorStatus
delayBounded, cancellation-aware latency from 0 to 30 seconds.available
hangA pending request that ends only on client cancellation.available
disconnectTransport loss while a tool request is in flight.available
pingHealth and post-condition verification through a separate tool path.available
reportingConsole and stable JSON output with CI-oriented exit codes.available
HTTP / JUnitStreamable 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) => )} - -
-
-
- {`UPI -
-
-

₹99 via UPI

-

{upiId}

-

Scan the QR code with your phone to pay.

- -

Confirm the payee and amount in your UPI app before approving. Payments are voluntary and non-refundable.

-
-
- {!isConfigured &&
Preview mode. Set PUBLIC_UPI_ID before publishing to activate payment links and QR codes.
} -
-
+
+ -

Thank you.

If financial support is not a fit, filing a clear issue, improving documentation, or sharing MCP Lab is just as valuable.

Contribute on GitHub
-
+
+
+

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) => ( + + )) + } + +
+
+
+ {`UPI +
+
+

₹99 via UPI

+

{upiId}

+

Scan the QR code with your phone to pay.

+ +

+ Confirm the payee and amount in your UPI app before approving. Payments are voluntary and non-refundable. +

+
+
+ { + !isConfigured && ( +
+ Preview mode. Set PUBLIC_UPI_ID before publishing to activate payment links + and QR codes. +
+ ) + } +
+ - +
+

Thank you.

+ If financial support is not a fit, filing a clear issue, improving documentation, or sharing MCP Lab is just as + valuable. +

Contribute on GitHub +
+ + +
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" }