summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authort <t@tjp.lol>2026-07-11 14:06:25 -0600
committert <t@tjp.lol>2026-07-11 19:59:46 -0600
commit9cd3f87dd63bff27acbe3966e90f3b689e898b93 (patch)
tree0bf4481d129d8624237bf9c5e5ca2a0a932f7daf
parentd6c7333d37638447a2a41c762574de8851665934 (diff)
Import Pantograph website project
Add the standalone Pantograph documentation site, including its assets, shared styling, and bundled design-system artifacts.
-rw-r--r--pantograph/assets/logo-mark-mono.svg10
-rw-r--r--pantograph/assets/logo-mark.svg15
-rw-r--r--pantograph/cli.html32
-rw-r--r--pantograph/configuration.html55
-rw-r--r--pantograph/ds/pantograph-design-system-56a1c74e-41c1-486b-ba3c-4eabdfacef31/_adherence.oxlintrc.json425
-rw-r--r--pantograph/ds/pantograph-design-system-56a1c74e-41c1-486b-ba3c-4eabdfacef31/_ds_bundle.js1878
-rw-r--r--pantograph/ds/pantograph-design-system-56a1c74e-41c1-486b-ba3c-4eabdfacef31/_ds_manifest.json1
-rw-r--r--pantograph/ds/pantograph-design-system-56a1c74e-41c1-486b-ba3c-4eabdfacef31/readme.md144
-rw-r--r--pantograph/ds/pantograph-design-system-56a1c74e-41c1-486b-ba3c-4eabdfacef31/styles.css13
-rw-r--r--pantograph/ds/pantograph-design-system-56a1c74e-41c1-486b-ba3c-4eabdfacef31/tokens/base.css113
-rw-r--r--pantograph/ds/pantograph-design-system-56a1c74e-41c1-486b-ba3c-4eabdfacef31/tokens/colors.css87
-rw-r--r--pantograph/ds/pantograph-design-system-56a1c74e-41c1-486b-ba3c-4eabdfacef31/tokens/fonts.css19
-rw-r--r--pantograph/ds/pantograph-design-system-56a1c74e-41c1-486b-ba3c-4eabdfacef31/tokens/spacing.css72
-rw-r--r--pantograph/ds/pantograph-design-system-56a1c74e-41c1-486b-ba3c-4eabdfacef31/tokens/typography.css66
-rw-r--r--pantograph/index.html37
-rw-r--r--pantograph/keybindings.html31
-rw-r--r--pantograph/lua.html74
-rw-r--r--pantograph/shared/components.css160
-rw-r--r--pantograph/shared/docs.css202
-rw-r--r--pantograph/shared/docs.js85
20 files changed, 3519 insertions, 0 deletions
diff --git a/pantograph/assets/logo-mark-mono.svg b/pantograph/assets/logo-mark-mono.svg
new file mode 100644
index 0000000..ed9b65d
--- /dev/null
+++ b/pantograph/assets/logo-mark-mono.svg
@@ -0,0 +1,10 @@
+<svg width="48" height="48" viewBox="0 0 48 48" fill="none" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="Pantograph">
+ <g stroke="currentColor" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round">
+ <path d="M8 40 L28 34 L40 10 L20 16 Z" fill="none"></path>
+ <path d="M8 40 L40 10" stroke-dasharray="0.1 4.2" opacity="0.55"></path>
+ </g>
+ <circle cx="8" cy="40" r="3.2" fill="currentColor"></circle>
+ <line x1="3.5" y1="44" x2="12.5" y2="44" stroke="currentColor" stroke-width="2.2" stroke-linecap="round"></line>
+ <circle cx="20" cy="16" r="2.4" fill="none" stroke="currentColor" stroke-width="2.2"></circle>
+ <circle cx="40" cy="10" r="3.6" fill="currentColor"></circle>
+</svg> \ No newline at end of file
diff --git a/pantograph/assets/logo-mark.svg b/pantograph/assets/logo-mark.svg
new file mode 100644
index 0000000..f577b74
--- /dev/null
+++ b/pantograph/assets/logo-mark.svg
@@ -0,0 +1,15 @@
+<svg width="48" height="48" viewBox="0 0 48 48" fill="none" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="Pantograph">
+
+ <g stroke="currentColor" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round">
+ <path d="M8 40 L28 34 L40 10 L20 16 Z" fill="none"></path>
+
+ <path d="M8 40 L40 10" stroke-dasharray="0.1 4.2" opacity="0.55"></path>
+ </g>
+
+ <circle cx="8" cy="40" r="3.2" fill="currentColor"></circle>
+ <line x1="3.5" y1="44" x2="12.5" y2="44" stroke="currentColor" stroke-width="2.2" stroke-linecap="round"></line>
+
+ <circle cx="20" cy="16" r="2.4" fill="none" stroke="currentColor" stroke-width="2.2"></circle>
+
+ <circle cx="40" cy="10" r="3.6" fill="#B23A2E"></circle>
+</svg> \ No newline at end of file
diff --git a/pantograph/cli.html b/pantograph/cli.html
new file mode 100644
index 0000000..88bd226
--- /dev/null
+++ b/pantograph/cli.html
@@ -0,0 +1,32 @@
+<!doctype html>
+<html lang="en">
+<head>
+<meta charset="utf-8">
+<meta name="viewport" content="width=device-width, initial-scale=1">
+<title>panto — Command line</title>
+<link rel="stylesheet" href="ds/pantograph-design-system-56a1c74e-41c1-486b-ba3c-4eabdfacef31/styles.css">
+<link rel="stylesheet" href="shared/components.css">
+<link rel="stylesheet" href="shared/docs.css">
+</head>
+<body>
+<div class="pd-app">
+<header class="pd-header">
+<div class="pd-header__left">
+<button class="pd-menu" type="button" aria-label="Menu" data-menu-toggle><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" width="1em" height="1em"><path d="M4 12h16"/><path d="M4 6h16"/><path d="M4 18h16"/></svg></button>
+<a class="pd-brand" href="index.html"><img src="assets/logo-mark.svg" width="28" height="28" alt=""><span class="pd-brand__word">panto</span><span class="pd-brand__div">/</span><span class="pd-brand__sub">docs</span></a>
+<span class="pg-badge pg-badge--outline">CLI</span>
+</div>
+<nav class="pd-nav"><a href="index.html" class="pd-nav__link">Getting started</a><a href="cli.html" class="pd-nav__link pd-nav__link--active">Command line</a><a href="configuration.html" class="pd-nav__link">Configuration</a><a href="keybindings.html" class="pd-nav__link">Key bindings</a><a href="lua.html" class="pd-nav__link">Lua extensions</a></nav>
+<div class="pd-header__right"><a class="pd-ghlink" href="https://code.tjp.lol/pantograph.git/" target="_blank" rel="noreferrer"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" width="1em" height="1em"><path d="M15 22v-4a4.8 4.8 0 0 0-1-3.5c3 0 6-2 6-5.5.08-1.25-.27-2.48-1-3.5.28-1.15.28-2.35 0-3.5 0 0-1 0-3 1.5-2.64-.5-5.36-.5-8 0C6 .5 5 .5 5 .5c-.3 1.15-.3 2.35 0 3.5A5.4 5.4 0 0 0 4 7.5c0 3.5 3 5.5 6 5.5-.39.49-.68 1.05-.85 1.65-.17.6-.22 1.23-.15 1.85v4"/><path d="M9 18c-4.51 2-5-2-7-2"/></svg> Source</a></div>
+</header>
+<div class="pd-shell">
+<aside class="pd-sidebar"><div class="pd-sidebar__scroll"><div class="pd-navsec"><p class="pd-navsec__title"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" width="1em" height="1em"><path d="m16.24 7.76-1.804 5.411a2 2 0 0 1-1.265 1.265L7.76 16.24l1.804-5.411a2 2 0 0 1 1.265-1.265z"/><circle cx="12" cy="12" r="10"/></svg>Getting started</p><ul class="pd-navsec__list"><li><a href="index.html#overview" class="pd-navsec__link">Overview</a></li><li><a href="index.html#install" class="pd-navsec__link">Install</a></li><li><a href="index.html#first-run" class="pd-navsec__link">First run</a></li><li><a href="index.html#authenticate" class="pd-navsec__link">Authenticate</a></li></ul></div><div class="pd-navsec pd-navsec--current"><p class="pd-navsec__title"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" width="1em" height="1em"><path d="m4 17 6-6-6-6"/><path d="M12 19h8"/></svg>Command line</p><ul class="pd-navsec__list"><li><a href="cli.html#starting" class="pd-navsec__link pd-navsec__link--active">Starting a session</a></li><li><a href="cli.html#sessions" class="pd-navsec__link"><code>panto sessions</code></a></li><li><a href="cli.html#models" class="pd-navsec__link"><code>panto models</code></a></li><li><a href="cli.html#auth" class="pd-navsec__link"><code>panto auth</code></a></li><li><a href="cli.html#bootstrap" class="pd-navsec__link"><code>panto bootstrap</code></a></li><li><a href="cli.html#lua" class="pd-navsec__link"><code>panto lua</code></a></li><li><a href="cli.html#slash-commands" class="pd-navsec__link">Slash commands</a></li><li><a href="cli.html#environment" class="pd-navsec__link">Environment</a></li></ul></div><div class="pd-navsec"><p class="pd-navsec__title"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" width="1em" height="1em"><path d="M3 5h11"/><path d="M19 5h2"/><path d="M3 12h6"/><path d="M14 12h7"/><path d="M3 19h10"/><path d="M18 19h3"/><circle cx="16" cy="5" r="2"/><circle cx="11" cy="12" r="2"/><circle cx="15" cy="19" r="2"/></svg>Configuration</p><ul class="pd-navsec__list"><li><a href="configuration.html#files" class="pd-navsec__link">Files &amp; layers</a></li><li><a href="configuration.html#providers" class="pd-navsec__link"><code>[providers]</code></a></li><li><a href="configuration.html#auth" class="pd-navsec__link"><code>[auth]</code></a></li><li><a href="configuration.html#defaults" class="pd-navsec__link"><code>[defaults]</code></a></li><li><a href="configuration.html#policies" class="pd-navsec__link">Extensions policy</a></li><li><a href="configuration.html#compaction" class="pd-navsec__link"><code>[compaction]</code></a></li><li><a href="configuration.html#models" class="pd-navsec__link"><code>models.toml</code></a></li><li><a href="configuration.html#system-prompt" class="pd-navsec__link">System prompt</a></li></ul></div><div class="pd-navsec"><p class="pd-navsec__title"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" width="1em" height="1em"><rect x="2" y="4" width="20" height="16" rx="2"/><path d="M6 8h.001"/><path d="M10 8h.001"/><path d="M14 8h.001"/><path d="M18 8h.001"/><path d="M8 12h.001"/><path d="M12 12h.001"/><path d="M16 12h.001"/><path d="M7 16h10"/></svg>Key bindings</p><ul class="pd-navsec__list"><li><a href="keybindings.html#editing" class="pd-navsec__link">Editing &amp; cursor</a></li><li><a href="keybindings.html#submit" class="pd-navsec__link">Submit &amp; newline</a></li><li><a href="keybindings.html#control" class="pd-navsec__link">Selectors &amp; control</a></li><li><a href="keybindings.html#terminal" class="pd-navsec__link">Terminal protocol</a></li></ul></div><div class="pd-navsec"><p class="pd-navsec__title"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" width="1em" height="1em"><path d="M2.97 12.92A2 2 0 0 0 2 14.63v3.24a2 2 0 0 0 .97 1.71l3 1.8a2 2 0 0 0 2.06 0L12 19v-5.5l-5-3-4.03 2.42Z"/><path d="m7 16.5-4.74-2.85"/><path d="m7 16.5 5-3"/><path d="M7 16.5v5.17"/><path d="M12 13.5V19l3.97 2.38a2 2 0 0 0 2.06 0l3-1.8a2 2 0 0 0 .97-1.71v-3.24a2 2 0 0 0-.97-1.71L17 10.5l-5 3Z"/><path d="m17 16.5-5-3"/><path d="m17 16.5 4.74-2.85"/><path d="M17 16.5v5.17"/><path d="M7.97 4.42A2 2 0 0 0 7 6.13v4.37l5 3 5-3V6.13a2 2 0 0 0-.97-1.71l-3-1.8a2 2 0 0 0-2.06 0l-3 1.8Z"/><path d="M12 8 7.26 5.15"/><path d="m12 8 4.74-2.85"/><path d="M12 13.5V8"/></svg>Lua extensions</p><ul class="pd-navsec__list"><li><a href="lua.html#overview" class="pd-navsec__link">Overview</a></li><li><a href="lua.html#loading" class="pd-navsec__link">Loading extensions</a></li><li><a href="lua.html#tools" class="pd-navsec__link"><code>register_tool</code></a></li><li><a href="lua.html#commands" class="pd-navsec__link"><code>register_command</code></a></li><li><a href="lua.html#events" class="pd-navsec__link">Events: on &amp; emit</a></li><li><a href="lua.html#components" class="pd-navsec__link">Lua components</a></li><li><a href="lua.html#runtime" class="pd-navsec__link">Runtime &amp; luarocks</a></li></ul></div></div></aside>
+<div class="pd-scrim"></div>
+<main class="pd-main"><article class="pd-doc" data-screen-label="Command line"><p class="panto-eyebrow pd-eyebrow">Reference</p><h1 class="pd-doc__h1">Command line</h1><p class="pd-lead"><code>panto</code> dispatches on its first argument. Some names are true subcommands (<code>sessions</code>, <code>models</code>, <code>auth</code>, <code>bootstrap</code>, <code>update</code>, <code>lua</code>, <code>help</code>); the flag forms start the agent itself, either interactively or in one-shot print mode.</p><div class="pd-table-wrap"><table class="pd-table"><thead><tr><th>Command</th><th>Does</th></tr></thead><tbody><tr><td class="mono">panto</td><td>Start a new interactive conversation in the current directory.</td></tr><tr><td class="mono">panto --resume [&lt;id&gt;]</td><td>Resume the most recent session, or a specific session by id prefix.</td></tr><tr><td class="mono">panto -c</td><td>Alias for bare <code>--resume</code>.</td></tr><tr><td class="mono">panto -p [prompt]</td><td>Run one non-interactive turn, print assistant text, exit 0/1.</td></tr><tr><td class="mono">panto sessions</td><td>List saved sessions for this directory.</td></tr><tr><td class="mono">panto models sync</td><td>Fetch models.dev and rebuild the base <code>models.toml</code>.</td></tr><tr><td class="mono">panto auth …</td><td>Show auth status, log in, or log out.</td></tr><tr><td class="mono">panto bootstrap [--force]</td><td>Prepare the bundled Lua / luarocks runtime and exit.</td></tr><tr><td class="mono">panto update</td><td>Install or refresh every rock listed in <code>extensions.rocks</code>.</td></tr><tr><td class="mono">panto lua [args…]</td><td>Run the embedded Lua interpreter.</td></tr><tr><td class="mono">panto --version</td><td>Print the panto version.</td></tr></tbody></table></div><h2 id="starting" class="pd-h2">Starting a session</h2><div class="pd-cmd"><span class="pd-cmd__sig">panto</span><span class="pd-cmd__note">Start a fresh interactive conversation.</span></div><div class="pd-cmd"><span class="pd-cmd__sig">panto --resume <span class="opt">[&lt;id&gt;]</span></span><span class="pd-cmd__note">Resume the latest or a specific saved session.</span></div><div class="pd-cmd"><span class="pd-cmd__sig">panto -p, --print <span class="opt">[&lt;prompt&gt;]</span></span><span class="pd-cmd__note">Run one turn without the TUI.</span></div><p class="pd-body">Interactive mode requires a tty on stdin and stdout. Print mode does not: the prompt can come from the flag argument or from piped stdin, and the command prints only the assistant text before exiting.</p><div class="pd-opts"><div class="pd-opt"><div class="pd-opt__key"><span class="pd-opt__name">-c, --continue</span></div><div class="pd-opt__desc">Alias for bare <code>--resume</code>.</div></div><div class="pd-opt"><div class="pd-opt__key"><span class="pd-opt__name">-m, --model &lt;provider:alias&gt;</span></div><div class="pd-opt__desc">Override the model for this run. A bare alias also works when it is unique across providers.</div></div><div class="pd-opt"><div class="pd-opt__key"><span class="pd-opt__name">--effort &lt;level&gt;</span></div><div class="pd-opt__desc">Override the reasoning level for this run. This beats both <code>models.toml</code> knobs and <code>[defaults] reasoning</code>.</div></div><div class="pd-opt"><div class="pd-opt__key"><span class="pd-opt__name">--no-extensions</span></div><div class="pd-opt__desc">Skip the Lua runtime entirely. The core still starts, but because the shipped tools are extensions the agent runs with no tools.</div></div></div><div class="pg-callout pg-callout--warn"><span class="pg-callout__icon"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3Z"/><path d="M12 9v4"/><path d="M12 17h.01"/></svg></span><div class="pg-callout__body"><p class="pg-callout__title">Unknown arguments are errors now</p>Agent-mode flag parsing is strict. An unrecognized argument prints an error and exits instead of being ignored.</div></div><h2 id="sessions" class="pd-h2">panto sessions</h2><div class="pd-cmd"><span class="pd-cmd__sig">panto sessions</span><span class="pd-cmd__note">List sessions for the current directory.</span></div><p class="pd-body">Sessions are stored per working directory. The listing is newest-modified first and prints the shortest unambiguous id prefix, the modified timestamp, message count, model, and last user message:</p><div class="pg-term"><div class="pg-term__bar"><span class="pg-term__dot" style="background:var(--red)"></span><span class="pg-term__dot" style="background:var(--brass)"></span><span class="pg-term__dot" style="background:var(--green)"></span><span class="pg-term__title">panto · sessions</span></div><div class="pg-term__body"><div class="pg-term__line pg-term__line--cmd"><span class="pg-term__prompt">$ </span>panto sessions</div><div class="pg-term__line pg-term__line--out">ID MODIFIED MSGS MODEL LAST MESSAGE</div><div class="pg-term__line pg-term__line--out">0197c2a4 2026-07-01 14:32 18 sonnet add json mode to stats</div><div class="pg-term__line pg-term__line--out">0197bfe1 2026-06-30 09:11 6 gpt-5 clean up the parser errors</div><div class="pg-term__line pg-term__line--out">~/.local/share/panto/sessions/--Users-travis-Code-ledger--</div></div></div><p class="pd-body">If there are no sessions yet, panto prints <code>no sessions for &lt;cwd&gt;</code> and then the directory it would use.</p><h2 id="models" class="pd-h2">panto models</h2><div class="pd-cmd"><span class="pd-cmd__sig">panto models sync</span><span class="pd-cmd__note">Fetch models.dev and rebuild the base <code>models.toml</code>.</span></div><p class="pd-body">The sync command looks at the providers that resolved from your layered config, maps each one to a models.dev catalog entry, optionally intersects that with the provider's own live <code>/models</code> listing, and writes the result to the base-layer <code>models.toml</code> at <code>$XDG_DATA_HOME/panto/models.toml</code> (or <code>~/.local/share/panto/models.toml</code>).</p><div class="pg-term"><div class="pg-term__bar"><span class="pg-term__dot" style="background:var(--red)"></span><span class="pg-term__dot" style="background:var(--brass)"></span><span class="pg-term__dot" style="background:var(--green)"></span><span class="pg-term__title">panto · models sync</span></div><div class="pg-term__body"><div class="pg-term__line pg-term__line--cmd"><span class="pg-term__prompt">$ </span>panto models sync</div><div class="pg-term__line pg-term__line--out">synced 42 model(s) across 3 provider(s) to ~/.local/share/panto/models.toml</div></div></div><p class="pd-body">Your user and project <code>models.toml</code> layers still sit on top and win.</p><h2 id="auth" class="pd-h2">panto auth</h2><p class="pd-body">With no action, <code>panto auth</code> defaults to <code>status</code>.</p><div class="pd-opts"><div class="pd-opt"><div class="pd-opt__key"><span class="pd-opt__name">panto auth status</span></div><div class="pd-opt__desc">Show every configured auth session. API-key sessions report <em>resolved</em> or <em>unresolved (key/env missing)</em>; OAuth sessions report whether they are logged in and when the current access token expires.</div></div><div class="pd-opt"><div class="pd-opt__key"><span class="pd-opt__name">panto auth login &lt;name&gt;</span></div><div class="pd-opt__desc">Run the OAuth device flow for the named session and persist the resulting token set.</div></div><div class="pd-opt"><div class="pd-opt__key"><span class="pd-opt__name">panto auth logout &lt;name&gt;</span></div><div class="pd-opt__desc">Delete the stored token set for the named session.</div></div></div><h2 id="bootstrap" class="pd-h2">panto bootstrap</h2><div class="pd-cmd"><span class="pd-cmd__sig">panto bootstrap <span class="opt">[--force]</span></span><span class="pd-cmd__note">Run the runtime bootstrap pipeline, then exit.</span></div><p class="pd-body">Bootstrap stages the base agent tree, creates the generated base config as needed, and prepares the versioned luarocks tree under the data home. The normal agent startup path runs the same bootstrap automatically.</p><div class="pd-cmd"><span class="pd-cmd__sig">panto update</span><span class="pd-cmd__note">Install or refresh the rocks listed in <code>extensions.rocks</code>.</span></div><p class="pd-body"><code>panto update</code> is the explicit “go hit luarocks again” command. Startup only installs missing rocks; changing a version pin or forcing a re-resolve is what <code>update</code> is for.</p><h2 id="lua" class="pd-h2">panto lua</h2><div class="pd-cmd"><span class="pd-cmd__sig">panto lua <span class="opt">[args…]</span></span><span class="pd-cmd__note">Run the embedded Lua interpreter with panto's runtime wired in.</span></div><p class="pd-body">This is the same embedded Lua build panto uses internally, with package paths and luarocks configured the same way a session sees them. It is handy both for experiments and for driving luarocks manually:</p><div class="pg-code"><div class="pg-code__bar"><span class="pg-code__name">shell</span><span class="pg-code__lang">bash</span><button class="pg-code__copy" type="button" aria-label="Copy code"><svg class="pg-code__copyglyph" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="9" y="9" width="11" height="11" rx="2"/><path d="M5 15V5a2 2 0 0 1 2-2h10"/></svg><svg class="pg-code__check" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><path d="m20 6-11 11-5-5"/></svg><span class="pg-code__label">Copy</span></button></div><div class="pg-code__scroll"><pre><code>panto lua my-script.lua
+panto lua -e 'arg[0]="luarocks"; require("luarocks.cmd").run_command("install","lpeg")'</code></pre></div></div><h2 id="slash-commands" class="pd-h2">Slash commands</h2><p class="pd-body">Lines beginning with <code>/</code> are handled locally inside the TUI and never sent to the model.</p><div class="pd-opts"><div class="pd-opt"><div class="pd-opt__key"><span class="pd-opt__name">Built in</span></div><div class="pd-opt__desc"><code>/help</code>, <code>/quit</code>, <code>/model</code>, <code>/reasoning</code>, <code>/new</code>, <code>/resume</code>, <code>/status</code>, and <code>/compact</code>.</div></div><div class="pd-opt"><div class="pd-opt__key"><span class="pd-opt__name">Extension-defined</span></div><div class="pd-opt__desc">Lua extensions can register more slash commands with <code>panto.ext.register_command</code>.</div></div></div><h2 id="environment" class="pd-h2">Environment</h2><div class="pd-table-wrap"><table class="pd-table"><thead><tr><th>Variable</th><th>Effect</th></tr></thead><tbody><tr><td class="mono">ANTHROPIC_API_KEY</td><td>Consumed by the default Anthropic provider.</td></tr><tr><td class="mono">OPENAI_API_KEY</td><td>Consumed by the default OpenAI provider.</td></tr><tr><td class="mono">PANTO_DEBUG</td><td>When non-zero, route <code>std.log</code> output to <code>&lt;data home&gt;/debug/&lt;session&gt;.log</code> instead of the terminal.</td></tr><tr><td class="mono">PANTO_SESSION_DIR</td><td>Override the base sessions directory. panto still appends one encoded subdirectory per working directory.</td></tr><tr><td class="mono">XDG_CONFIG_HOME</td><td>Locates the user config and user extensions directories.</td></tr><tr><td class="mono">XDG_DATA_HOME</td><td>Locates the data home: base config, base models, sessions, auth cache, agent tree, and rocks.</td></tr></tbody></table></div><nav class="pd-prevnext"><a class="pd-pn pd-pn--prev" href="index.html"><span class="pd-pn__dir">← Previous</span><span class="pd-pn__title">Getting started</span></a><a class="pd-pn pd-pn--next" href="configuration.html"><span class="pd-pn__dir">Next →</span><span class="pd-pn__title">Configuration</span></a></nav></article></main>
+<aside class="pd-toc"><p class="pd-toc__title">On this page</p><ul class="pd-toc__list"><li><a href="#starting" class="pd-toc__link pd-toc__link--active">Starting a session</a></li><li><a href="#sessions" class="pd-toc__link">panto sessions</a></li><li><a href="#models" class="pd-toc__link">panto models</a></li><li><a href="#auth" class="pd-toc__link">panto auth</a></li><li><a href="#bootstrap" class="pd-toc__link">panto bootstrap</a></li><li><a href="#lua" class="pd-toc__link">panto lua</a></li><li><a href="#slash-commands" class="pd-toc__link">Slash commands</a></li><li><a href="#environment" class="pd-toc__link">Environment</a></li></ul></aside>
+</div>
+</div>
+<script src="shared/docs.js"></script>
+</body>
+</html>
diff --git a/pantograph/configuration.html b/pantograph/configuration.html
new file mode 100644
index 0000000..27f631a
--- /dev/null
+++ b/pantograph/configuration.html
@@ -0,0 +1,55 @@
+<!doctype html>
+<html lang="en">
+<head>
+<meta charset="utf-8">
+<meta name="viewport" content="width=device-width, initial-scale=1">
+<title>panto — Configuration</title>
+<link rel="stylesheet" href="ds/pantograph-design-system-56a1c74e-41c1-486b-ba3c-4eabdfacef31/styles.css">
+<link rel="stylesheet" href="shared/components.css">
+<link rel="stylesheet" href="shared/docs.css">
+</head>
+<body>
+<div class="pd-app">
+<header class="pd-header">
+<div class="pd-header__left">
+<button class="pd-menu" type="button" aria-label="Menu" data-menu-toggle><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" width="1em" height="1em"><path d="M4 12h16"/><path d="M4 6h16"/><path d="M4 18h16"/></svg></button>
+<a class="pd-brand" href="index.html"><img src="assets/logo-mark.svg" width="28" height="28" alt=""><span class="pd-brand__word">panto</span><span class="pd-brand__div">/</span><span class="pd-brand__sub">docs</span></a>
+<span class="pg-badge pg-badge--outline">CLI</span>
+</div>
+<nav class="pd-nav"><a href="index.html" class="pd-nav__link">Getting started</a><a href="cli.html" class="pd-nav__link">Command line</a><a href="configuration.html" class="pd-nav__link pd-nav__link--active">Configuration</a><a href="keybindings.html" class="pd-nav__link">Key bindings</a><a href="lua.html" class="pd-nav__link">Lua extensions</a></nav>
+<div class="pd-header__right"><a class="pd-ghlink" href="https://code.tjp.lol/pantograph.git/" target="_blank" rel="noreferrer"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" width="1em" height="1em"><path d="M15 22v-4a4.8 4.8 0 0 0-1-3.5c3 0 6-2 6-5.5.08-1.25-.27-2.48-1-3.5.28-1.15.28-2.35 0-3.5 0 0-1 0-3 1.5-2.64-.5-5.36-.5-8 0C6 .5 5 .5 5 .5c-.3 1.15-.3 2.35 0 3.5A5.4 5.4 0 0 0 4 7.5c0 3.5 3 5.5 6 5.5-.39.49-.68 1.05-.85 1.65-.17.6-.22 1.23-.15 1.85v4"/><path d="M9 18c-4.51 2-5-2-7-2"/></svg> Source</a></div>
+</header>
+<div class="pd-shell">
+<aside class="pd-sidebar"><div class="pd-sidebar__scroll"><div class="pd-navsec"><p class="pd-navsec__title"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" width="1em" height="1em"><path d="m16.24 7.76-1.804 5.411a2 2 0 0 1-1.265 1.265L7.76 16.24l1.804-5.411a2 2 0 0 1 1.265-1.265z"/><circle cx="12" cy="12" r="10"/></svg>Getting started</p><ul class="pd-navsec__list"><li><a href="index.html#overview" class="pd-navsec__link">Overview</a></li><li><a href="index.html#install" class="pd-navsec__link">Install</a></li><li><a href="index.html#first-run" class="pd-navsec__link">First run</a></li><li><a href="index.html#authenticate" class="pd-navsec__link">Authenticate</a></li></ul></div><div class="pd-navsec"><p class="pd-navsec__title"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" width="1em" height="1em"><path d="m4 17 6-6-6-6"/><path d="M12 19h8"/></svg>Command line</p><ul class="pd-navsec__list"><li><a href="cli.html#starting" class="pd-navsec__link">Starting a session</a></li><li><a href="cli.html#sessions" class="pd-navsec__link"><code>panto sessions</code></a></li><li><a href="cli.html#models" class="pd-navsec__link"><code>panto models</code></a></li><li><a href="cli.html#auth" class="pd-navsec__link"><code>panto auth</code></a></li><li><a href="cli.html#bootstrap" class="pd-navsec__link"><code>panto bootstrap</code></a></li><li><a href="cli.html#lua" class="pd-navsec__link"><code>panto lua</code></a></li><li><a href="cli.html#slash-commands" class="pd-navsec__link">Slash commands</a></li><li><a href="cli.html#environment" class="pd-navsec__link">Environment</a></li></ul></div><div class="pd-navsec pd-navsec--current"><p class="pd-navsec__title"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" width="1em" height="1em"><path d="M3 5h11"/><path d="M19 5h2"/><path d="M3 12h6"/><path d="M14 12h7"/><path d="M3 19h10"/><path d="M18 19h3"/><circle cx="16" cy="5" r="2"/><circle cx="11" cy="12" r="2"/><circle cx="15" cy="19" r="2"/></svg>Configuration</p><ul class="pd-navsec__list"><li><a href="configuration.html#files" class="pd-navsec__link pd-navsec__link--active">Files &amp; layers</a></li><li><a href="configuration.html#providers" class="pd-navsec__link"><code>[providers]</code></a></li><li><a href="configuration.html#auth" class="pd-navsec__link"><code>[auth]</code></a></li><li><a href="configuration.html#defaults" class="pd-navsec__link"><code>[defaults]</code></a></li><li><a href="configuration.html#policies" class="pd-navsec__link">Extensions policy</a></li><li><a href="configuration.html#compaction" class="pd-navsec__link"><code>[compaction]</code></a></li><li><a href="configuration.html#models" class="pd-navsec__link"><code>models.toml</code></a></li><li><a href="configuration.html#system-prompt" class="pd-navsec__link">System prompt</a></li></ul></div><div class="pd-navsec"><p class="pd-navsec__title"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" width="1em" height="1em"><rect x="2" y="4" width="20" height="16" rx="2"/><path d="M6 8h.001"/><path d="M10 8h.001"/><path d="M14 8h.001"/><path d="M18 8h.001"/><path d="M8 12h.001"/><path d="M12 12h.001"/><path d="M16 12h.001"/><path d="M7 16h10"/></svg>Key bindings</p><ul class="pd-navsec__list"><li><a href="keybindings.html#editing" class="pd-navsec__link">Editing &amp; cursor</a></li><li><a href="keybindings.html#submit" class="pd-navsec__link">Submit &amp; newline</a></li><li><a href="keybindings.html#control" class="pd-navsec__link">Selectors &amp; control</a></li><li><a href="keybindings.html#terminal" class="pd-navsec__link">Terminal protocol</a></li></ul></div><div class="pd-navsec"><p class="pd-navsec__title"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" width="1em" height="1em"><path d="M2.97 12.92A2 2 0 0 0 2 14.63v3.24a2 2 0 0 0 .97 1.71l3 1.8a2 2 0 0 0 2.06 0L12 19v-5.5l-5-3-4.03 2.42Z"/><path d="m7 16.5-4.74-2.85"/><path d="m7 16.5 5-3"/><path d="M7 16.5v5.17"/><path d="M12 13.5V19l3.97 2.38a2 2 0 0 0 2.06 0l3-1.8a2 2 0 0 0 .97-1.71v-3.24a2 2 0 0 0-.97-1.71L17 10.5l-5 3Z"/><path d="m17 16.5-5-3"/><path d="m17 16.5 4.74-2.85"/><path d="M17 16.5v5.17"/><path d="M7.97 4.42A2 2 0 0 0 7 6.13v4.37l5 3 5-3V6.13a2 2 0 0 0-.97-1.71l-3-1.8a2 2 0 0 0-2.06 0l-3 1.8Z"/><path d="M12 8 7.26 5.15"/><path d="m12 8 4.74-2.85"/><path d="M12 13.5V8"/></svg>Lua extensions</p><ul class="pd-navsec__list"><li><a href="lua.html#overview" class="pd-navsec__link">Overview</a></li><li><a href="lua.html#loading" class="pd-navsec__link">Loading extensions</a></li><li><a href="lua.html#tools" class="pd-navsec__link"><code>register_tool</code></a></li><li><a href="lua.html#commands" class="pd-navsec__link"><code>register_command</code></a></li><li><a href="lua.html#events" class="pd-navsec__link">Events: on &amp; emit</a></li><li><a href="lua.html#components" class="pd-navsec__link">Lua components</a></li><li><a href="lua.html#runtime" class="pd-navsec__link">Runtime &amp; luarocks</a></li></ul></div></div></aside>
+<div class="pd-scrim"></div>
+<main class="pd-main"><article class="pd-doc" data-screen-label="Configuration"><p class="panto-eyebrow pd-eyebrow">Reference</p><h1 class="pd-doc__h1">Configuration</h1><div class="pd-doc__meta"><span class="pg-badge pg-badge--neutral">TOML</span></div><p class="pd-lead">panto reads layered <code>config.toml</code> files for providers, auth, defaults, TUI settings, and extension policy. Model aliases and pricing live beside them in a separate layered <code>models.toml</code>.</p><h2 id="files" class="pd-h2">Files &amp; layers</h2><p class="pd-body">Four <code>config.toml</code> layers are merged, lowest precedence first. Missing files are skipped.</p><div class="pd-table-wrap"><table class="pd-table"><thead><tr><th>Layer</th><th>Path</th><th>Role</th></tr></thead><tbody><tr><td class="mono">base</td><td class="mono">$XDG_DATA_HOME/panto/config.toml · else ~/.local/share/panto/config.toml</td><td>Auto-generated defaults.</td></tr><tr><td class="mono">user</td><td class="mono">$XDG_CONFIG_HOME/panto/config.toml · else ~/.config/panto/config.toml</td><td>Machine-wide personal settings.</td></tr><tr><td class="mono">project</td><td class="mono">./.panto/config.toml</td><td>Checked-in project defaults.</td></tr><tr><td class="mono">local</td><td class="mono">./.panto/config.local.toml</td><td>Personal per-project overrides.</td></tr></tbody></table></div><p class="pd-body">Merge rule of thumb: tables merge recursively; scalars and arrays from a higher layer replace lower-layer values. One important exception is <code>[extensions]</code>: its allow/deny rules and source lists accumulate across layers so policy and extra extension sources can be refined instead of erased.</p><div class="pg-code"><div class="pg-code__bar"><span class="pg-code__name">config.toml</span><span class="pg-code__lang">toml</span><button class="pg-code__copy" type="button" aria-label="Copy code"><svg class="pg-code__copyglyph" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="9" y="9" width="11" height="11" rx="2"/><path d="M5 15V5a2 2 0 0 1 2-2h10"/></svg><svg class="pg-code__check" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><path d="m20 6-11 11-5-5"/></svg><span class="pg-code__label">Copy</span></button></div><div class="pg-code__scroll"><pre><code>[defaults]
+model = "anthropic:sonnet"
+reasoning = "high"
+
+[tui]
+editor = "code -w"
+tools_collapsed = false
+
+[providers.anthropic]
+style = "anthropic_messages"
+base_url = "https://api.anthropic.com"
+auth = "anthropic_api"
+
+[auth.anthropic_api]
+key = "${env:ANTHROPIC_API_KEY}"</code></pre></div></div><h2 id="providers" class="pd-h2">[providers.&lt;name&gt;]</h2><p class="pd-body">A provider describes transport only: API shape, base URL, auth session, and a few request-level knobs. The provider name is the left half of a <code>provider:alias</code> model reference.</p><div class="pd-opts"><div class="pd-opt"><div class="pd-opt__key"><span class="pd-opt__name">style</span><span class="pd-opt__type">string</span><span class="pg-badge pg-badge--danger">required</span></div><div class="pd-opt__desc">One of <code>anthropic_messages</code>, <code>openai_chat</code>, or <code>openai_responses</code>.</div></div><div class="pd-opt"><div class="pd-opt__key"><span class="pd-opt__name">dialect</span><span class="pd-opt__type">string</span></div><div class="pd-opt__desc">For <code>openai_responses</code>, the only current dialect is <code>"codex"</code>.</div></div><div class="pd-opt"><div class="pd-opt__key"><span class="pd-opt__name">base_url</span><span class="pd-opt__type">string</span><span class="pg-badge pg-badge--danger">required</span></div><div class="pd-opt__desc">API base URL.</div></div><div class="pd-opt"><div class="pd-opt__key"><span class="pd-opt__name">auth</span><span class="pd-opt__type">string</span><span class="pg-badge pg-badge--danger">required</span></div><div class="pd-opt__desc">Names the <a href="#auth"><code>[auth.&lt;name&gt;]</code></a> session used for this provider.</div></div><div class="pd-opt"><div class="pd-opt__key"><span class="pd-opt__name">prompt_cache</span><span class="pd-opt__type">bool</span></div><div class="pd-opt__desc">Anthropic only. Controls the advancing <code>cache_control</code> breakpoint.</div></div><div class="pd-opt"><div class="pd-opt__key"><span class="pd-opt__name">extra_headers</span><span class="pd-opt__type">table</span></div><div class="pd-opt__desc">Static request headers merged onto every request.</div></div><div class="pd-opt"><div class="pd-opt__key"><span class="pd-opt__name">model_catalog_name</span><span class="pd-opt__type">string</span></div><div class="pd-opt__desc">Optional models.dev provider key for <code>panto models sync</code>.</div></div></div><div class="pg-code"><div class="pg-code__bar"><span class="pg-code__name">config.toml</span><span class="pg-code__lang">toml</span><button class="pg-code__copy" type="button" aria-label="Copy code"><svg class="pg-code__copyglyph" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="9" y="9" width="11" height="11" rx="2"/><path d="M5 15V5a2 2 0 0 1 2-2h10"/></svg><svg class="pg-code__check" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><path d="m20 6-11 11-5-5"/></svg><span class="pg-code__label">Copy</span></button></div><div class="pg-code__scroll"><pre><code>[providers.copilot]
+style = "openai_chat"
+base_url = "https://api.individual.githubcopilot.com"
+auth = "github_copilot"
+
+[providers.copilot.extra_headers]
+Copilot-Integration-Id = "vscode-chat"
+X-Initiator = "user"</code></pre></div></div><h2 id="auth" class="pd-h2">[auth.&lt;name&gt;]</h2><p class="pd-body">Auth sessions are named, reusable credential sources. If <code>type</code> is omitted, panto infers it from the fields present: <code>key</code> implies <code>api_key</code>; <code>client_id</code> implies <code>oauth_device</code>.</p><h3 id="auth-subst" class="pd-h3">Substitution</h3><p class="pd-body">Every auth value supports <code>${...}</code> substitution. <code>${env:VAR}</code> reads the environment; <code>${name}</code> reads another key in the same auth block. Unresolved substitutions become the empty string.</p><h3 id="auth-apikey" class="pd-h3">API-key sessions</h3><div class="pd-opts"><div class="pd-opt"><div class="pd-opt__key"><span class="pd-opt__name">key</span><span class="pd-opt__type">string</span><span class="pg-badge pg-badge--danger">required</span></div><div class="pd-opt__desc">Usually <code>"${env:VAR}"</code>. Resolved eagerly at load time.</div></div></div><h3 id="auth-oauth" class="pd-h3">OAuth device sessions</h3><div class="pd-opts"><div class="pd-opt"><div class="pd-opt__key"><span class="pd-opt__name">client_id / device_code_url / token_url</span><span class="pd-opt__type">string</span><span class="pg-badge pg-badge--danger">required</span></div><div class="pd-opt__desc">Core device-flow endpoints.</div></div><div class="pd-opt"><div class="pd-opt__key"><span class="pd-opt__name">dialect</span><span class="pd-opt__type">token | codex</span></div><div class="pd-opt__desc"><code>codex</code> additionally requires <code>device_poll_url</code>.</div></div><div class="pd-opt"><div class="pd-opt__key"><span class="pd-opt__name">scope / verification_url / redirect_uri</span><span class="pd-opt__type">string</span></div><div class="pd-opt__desc">Optional flow details.</div></div><div class="pd-opt"><div class="pd-opt__key"><span class="pd-opt__name">exchange_…</span><span class="pd-opt__type">string</span></div><div class="pd-opt__desc">Optional secondary token exchange after device login.</div></div></div><h2 id="defaults" class="pd-h2">[defaults]</h2><div class="pd-opts"><div class="pd-opt"><div class="pd-opt__key"><span class="pd-opt__name">model</span><span class="pd-opt__type">string</span></div><div class="pd-opt__desc">Default model as <code>"&lt;provider&gt;:&lt;alias&gt;"</code>.</div></div><div class="pd-opt"><div class="pd-opt__key"><span class="pd-opt__name">reasoning</span><span class="pd-opt__type">string</span></div><div class="pd-opt__desc">Session-default reasoning label — the same names the <span class="pd-kbd">Ctrl</span>+<span class="pd-kbd">R</span> picker shows.</div></div></div><p class="pd-body">Model selection falls through in this order: <code>--model</code>, then <code>[defaults] model</code>, then the single-provider/single-alias convenience case. Reasoning falls through similarly: <code>--effort</code> beats a per-alias setting in <code>models.toml</code>, which beats <code>[defaults] reasoning</code>.</p><h3 class="pd-h3">[tui]</h3><div class="pd-opts"><div class="pd-opt"><div class="pd-opt__key"><span class="pd-opt__name">editor</span><span class="pd-opt__type">string</span></div><div class="pd-opt__desc">Overrides <code>$VISUAL</code> / <code>$EDITOR</code> for <span class="pd-kbd">Ctrl</span>+<span class="pd-kbd">G</span>.</div></div><div class="pd-opt"><div class="pd-opt__key"><span class="pd-opt__name">tools_collapsed</span><span class="pd-opt__type">bool</span></div><div class="pd-opt__desc">Initial state of the global tool-output collapse toggle.</div></div></div><h2 id="policies" class="pd-h2">Extensions policy &amp; sources</h2><p class="pd-body"><code>[extensions]</code> governs every Lua-authored capability, including the shipped standard tools. The names you match are extension entry names such as <code>std.read</code>, <code>std.write</code>, <code>std.edit</code>, and <code>std.shell</code>.</p><div class="pd-opts"><div class="pd-opt"><div class="pd-opt__key"><span class="pd-opt__name">allow / deny</span><span class="pd-opt__type">string[]</span></div><div class="pd-opt__desc">Glob rules over entry names. The default is allow; later, more specific rules win, and <code>deny</code> wins an exact tie.</div></div><div class="pd-opt"><div class="pd-opt__key"><span class="pd-opt__name">paths</span><span class="pd-opt__type">string[]</span></div><div class="pd-opt__desc">Extra directories to scan for extension sources.</div></div><div class="pd-opt"><div class="pd-opt__key"><span class="pd-opt__name">rocks</span><span class="pd-opt__type">string[]</span></div><div class="pd-opt__desc">Installed rock specs to load as extension sources. <code>panto update</code> installs or refreshes them.</div></div></div><div class="pg-code"><div class="pg-code__bar"><span class="pg-code__name">config.toml</span><span class="pg-code__lang">toml</span><button class="pg-code__copy" type="button" aria-label="Copy code"><svg class="pg-code__copyglyph" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="9" y="9" width="11" height="11" rx="2"/><path d="M5 15V5a2 2 0 0 1 2-2h10"/></svg><svg class="pg-code__check" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><path d="m20 6-11 11-5-5"/></svg><span class="pg-code__label">Copy</span></button></div><div class="pg-code__scroll"><pre><code>[extensions]
+deny = ["std.shell"]
+paths = ["./dev/panto-ext"]
+rocks = ["panto-agent 1.4.2-1"]</code></pre></div></div><h2 id="compaction" class="pd-h2">[compaction]</h2><div class="pd-opts"><div class="pd-opt"><div class="pd-opt__key"><span class="pd-opt__name">keep_verbatim</span><span class="pd-opt__type">int</span></div><div class="pd-opt__desc">Token budget for the recent tail kept verbatim during compaction.</div></div><div class="pd-opt"><div class="pd-opt__key"><span class="pd-opt__name">model</span><span class="pd-opt__type">string</span></div><div class="pd-opt__desc">Optional <code>provider:alias</code> override for the compaction model.</div></div></div><h2 id="models" class="pd-h2">models.toml</h2><p class="pd-body"><code>models.toml</code> is layered just like <code>config.toml</code>: base at <code>$XDG_DATA_HOME/panto/models.toml</code>, then user, project, and <code>./.panto/models.local.toml</code>. Each entry is keyed <code>[&lt;provider&gt;.&lt;alias&gt;]</code>. If an alias is missing entirely, panto simply uses the alias itself as the wire model name.</p><h3 id="models-common" class="pd-h3">Common keys</h3><div class="pd-opts"><div class="pd-opt"><div class="pd-opt__key"><span class="pd-opt__name">model</span><span class="pd-opt__type">string</span></div><div class="pd-opt__desc">Wire model id sent to the provider API. Defaults to the alias.</div></div><div class="pd-opt"><div class="pd-opt__key"><span class="pd-opt__name">context_window / max_tokens</span><span class="pd-opt__type">int</span></div><div class="pd-opt__desc">Context metadata for the UI and a per-request output cap.</div></div><div class="pd-opt"><div class="pd-opt__key"><span class="pd-opt__name">input / output / cache_read / cache_write</span><span class="pd-opt__type">float</span></div><div class="pd-opt__desc">Pricing in USD per million tokens.</div></div></div><h3 id="models-openai" class="pd-h3">OpenAI-style knobs</h3><div class="pd-opts"><div class="pd-opt"><div class="pd-opt__key"><span class="pd-opt__name">reasoning</span><span class="pd-opt__type">string</span></div><div class="pd-opt__desc">Applies to <code>openai_chat</code> and <code>openai_responses</code>: <code>default</code>, <code>off</code>, <code>minimal</code>, <code>low</code>, <code>medium</code>, or <code>high</code>.</div></div></div><h3 id="models-anthropic" class="pd-h3">Anthropic-style knobs</h3><div class="pd-opts"><div class="pd-opt"><div class="pd-opt__key"><span class="pd-opt__name">thinking</span><span class="pd-opt__type">string</span></div><div class="pd-opt__desc"><code>disabled</code>, <code>enabled</code>, or <code>adaptive</code>.</div></div><div class="pd-opt"><div class="pd-opt__key"><span class="pd-opt__name">effort</span><span class="pd-opt__type">string</span></div><div class="pd-opt__desc"><code>low</code>, <code>medium</code>, <code>high</code>, <code>xhigh</code>, or <code>max</code>.</div></div><div class="pd-opt"><div class="pd-opt__key"><span class="pd-opt__name">thinking_budget_tokens / thinking_interleaved / api_version</span><span class="pd-opt__type">int | bool | string</span></div><div class="pd-opt__desc">Manual-thinking budget, interleaved-thinking beta header, and Anthropic API version.</div></div></div><h2 id="system-prompt" class="pd-h2">System prompt</h2><p class="pd-body">Prompt files are layered beside the extension tree: base under <code>$XDG_DATA_HOME/panto/agent/</code>, then user, then project. The conventional files are <code>SYSTEM.md</code>, <code>APPEND_SYSTEM.md</code>, and <code>COMPACTION.md</code>.</p><nav class="pd-prevnext"><a class="pd-pn pd-pn--prev" href="cli.html"><span class="pd-pn__dir">← Previous</span><span class="pd-pn__title">Command line</span></a><a class="pd-pn pd-pn--next" href="keybindings.html"><span class="pd-pn__dir">Next →</span><span class="pd-pn__title">Key bindings</span></a></nav></article></main>
+<aside class="pd-toc"><p class="pd-toc__title">On this page</p><ul class="pd-toc__list"><li><a href="#files" class="pd-toc__link pd-toc__link--active">Files &amp; layers</a></li><li><a href="#providers" class="pd-toc__link">[providers]</a></li><li><a href="#auth" class="pd-toc__link">[auth]</a></li><li><a href="#defaults" class="pd-toc__link">[defaults]</a></li><li><a href="#policies" class="pd-toc__link">Extensions policy</a></li><li><a href="#compaction" class="pd-toc__link">[compaction]</a></li><li><a href="#models" class="pd-toc__link">models.toml</a></li><li><a href="#system-prompt" class="pd-toc__link">System prompt</a></li></ul></aside>
+</div>
+</div>
+<script src="shared/docs.js"></script>
+</body>
+</html>
diff --git a/pantograph/ds/pantograph-design-system-56a1c74e-41c1-486b-ba3c-4eabdfacef31/_adherence.oxlintrc.json b/pantograph/ds/pantograph-design-system-56a1c74e-41c1-486b-ba3c-4eabdfacef31/_adherence.oxlintrc.json
new file mode 100644
index 0000000..d745f07
--- /dev/null
+++ b/pantograph/ds/pantograph-design-system-56a1c74e-41c1-486b-ba3c-4eabdfacef31/_adherence.oxlintrc.json
@@ -0,0 +1,425 @@
+{
+ "plugins": [
+ "react",
+ "import"
+ ],
+ "rules": {
+ "react/forbid-elements": [
+ "warn",
+ {
+ "forbid": []
+ }
+ ],
+ "no-restricted-imports": [
+ "warn",
+ {
+ "patterns": [
+ {
+ "group": [
+ "components/code/**",
+ "components/core/**",
+ "components/forms/**",
+ "components/navigation/**",
+ "ui_kits/docs/**",
+ "ui_kits/home/**"
+ ],
+ "message": "Import design-system components from 'index.js', not component internals."
+ }
+ ]
+ }
+ ],
+ "no-restricted-syntax": [
+ "warn",
+ {
+ "selector": "Literal[value=/#[0-9a-fA-F]{3,8}\\b/]",
+ "message": "Raw hex color — use a design-system color token via var()."
+ },
+ {
+ "selector": "Literal[value=/\\b\\d+px\\b/]",
+ "message": "Raw px value — use a design-system spacing token via var()."
+ },
+ {
+ "selector": "Literal[value=/font-family\\s*:\\s*(?!['\\\"]?(?:IBM Plex Mono|Saira Condensed))/i]",
+ "message": "Font not provided by the design system. Available: IBM Plex Mono, Saira Condensed."
+ },
+ {
+ "selector": "JSXOpeningElement[name.name='Badge'] > JSXAttribute > JSXIdentifier[name!=/^(?:variant|dot|children|key|ref|className|style|children)$/]",
+ "message": "<Badge> doesn't accept that prop. Declared props: variant, dot, children."
+ },
+ {
+ "selector": "JSXOpeningElement[name.name='Badge'] > JSXAttribute[name.name='variant'] > Literal[value!=/^(?:neutral|danger|success|ink|outline)$/]",
+ "message": "<Badge> variant must be one of 'neutral' | 'danger' | 'success' | 'ink' | 'outline'."
+ },
+ {
+ "selector": "JSXOpeningElement[name.name='Button'] > JSXAttribute > JSXIdentifier[name!=/^(?:variant|size|block|icon|iconRight|as|children|key|ref|className|style|children)$/]",
+ "message": "<Button> doesn't accept that prop. Declared props: variant, size, block, icon, iconRight, as, children."
+ },
+ {
+ "selector": "JSXOpeningElement[name.name='Button'] > JSXAttribute[name.name='variant'] > Literal[value!=/^(?:primary|accent|secondary|ghost|danger)$/]",
+ "message": "<Button> variant must be one of 'primary' | 'accent' | 'secondary' | 'ghost' | 'danger'."
+ },
+ {
+ "selector": "JSXOpeningElement[name.name='Button'] > JSXAttribute[name.name='size'] > Literal[value!=/^(?:sm|md|lg)$/]",
+ "message": "<Button> size must be one of 'sm' | 'md' | 'lg'."
+ },
+ {
+ "selector": "JSXOpeningElement[name.name='Button'] > JSXAttribute[name.name='as'] > Literal[value!=/^(?:button|a)$/]",
+ "message": "<Button> as must be one of 'button' | 'a'."
+ },
+ {
+ "selector": "JSXOpeningElement[name.name='Callout'] > JSXAttribute > JSXIdentifier[name!=/^(?:variant|title|icon|children|key|ref|className|style|children)$/]",
+ "message": "<Callout> doesn't accept that prop. Declared props: variant, title, icon, children."
+ },
+ {
+ "selector": "JSXOpeningElement[name.name='Callout'] > JSXAttribute[name.name='variant'] > Literal[value!=/^(?:note|tip|warn|danger)$/]",
+ "message": "<Callout> variant must be one of 'note' | 'tip' | 'warn' | 'danger'."
+ },
+ {
+ "selector": "JSXOpeningElement[name.name='Card'] > JSXAttribute > JSXIdentifier[name!=/^(?:variant|ruled|interactive|title|actions|footer|children|key|ref|className|style|children)$/]",
+ "message": "<Card> doesn't accept that prop. Declared props: variant, ruled, interactive, title, actions, footer, children."
+ },
+ {
+ "selector": "JSXOpeningElement[name.name='Card'] > JSXAttribute[name.name='variant'] > Literal[value!=/^(?:default|flat|sunken|accent)$/]",
+ "message": "<Card> variant must be one of 'default' | 'flat' | 'sunken' | 'accent'."
+ },
+ {
+ "selector": "JSXOpeningElement[name.name='CodeBlock'] > JSXAttribute > JSXIdentifier[name!=/^(?:code|language|filename|showBar|children|key|ref|className|style|children)$/]",
+ "message": "<CodeBlock> doesn't accept that prop. Declared props: code, language, filename, showBar, children."
+ },
+ {
+ "selector": "JSXOpeningElement[name.name='IconButton'] > JSXAttribute > JSXIdentifier[name!=/^(?:size|bordered|active|label|children|key|ref|className|style|children)$/]",
+ "message": "<IconButton> doesn't accept that prop. Declared props: size, bordered, active, label, children."
+ },
+ {
+ "selector": "JSXOpeningElement[name.name='IconButton'] > JSXAttribute[name.name='size'] > Literal[value!=/^(?:sm|md|lg)$/]",
+ "message": "<IconButton> size must be one of 'sm' | 'md' | 'lg'."
+ },
+ {
+ "selector": "JSXOpeningElement[name.name='Input'] > JSXAttribute > JSXIdentifier[name!=/^(?:label|hint|error|icon|mono|required|key|ref|className|style|children)$/]",
+ "message": "<Input> doesn't accept that prop. Declared props: label, hint, error, icon, mono, required."
+ },
+ {
+ "selector": "JSXOpeningElement[name.name='SelectOption'] > JSXAttribute > JSXIdentifier[name!=/^(?:value|label|key|ref|className|style|children)$/]",
+ "message": "<SelectOption> doesn't accept that prop. Declared props: value, label."
+ },
+ {
+ "selector": "JSXOpeningElement[name.name='Switch'] > JSXAttribute > JSXIdentifier[name!=/^(?:checked|defaultChecked|label|key|ref|className|style|children)$/]",
+ "message": "<Switch> doesn't accept that prop. Declared props: checked, defaultChecked, label."
+ },
+ {
+ "selector": "JSXOpeningElement[name.name='TabItem'] > JSXAttribute > JSXIdentifier[name!=/^(?:id|label|icon|key|ref|className|style|children)$/]",
+ "message": "<TabItem> doesn't accept that prop. Declared props: id, label, icon."
+ },
+ {
+ "selector": "JSXOpeningElement[name.name='Tag'] > JSXAttribute > JSXIdentifier[name!=/^(?:dot|onRemove|interactive|children|key|ref|className|style|children)$/]",
+ "message": "<Tag> doesn't accept that prop. Declared props: dot, onRemove, interactive, children."
+ },
+ {
+ "selector": "JSXOpeningElement[name.name='TerminalLine'] > JSXAttribute > JSXIdentifier[name!=/^(?:type|text|shell|key|ref|className|style|children)$/]",
+ "message": "<TerminalLine> doesn't accept that prop. Declared props: type, text, shell."
+ },
+ {
+ "selector": "JSXOpeningElement[name.name='TerminalLine'] > JSXAttribute[name.name='type'] > Literal[value!=/^(?:cmd|out|ok|err|com)$/]",
+ "message": "<TerminalLine> type must be one of 'cmd' | 'out' | 'ok' | 'err' | 'com'."
+ },
+ {
+ "selector": "JSXOpeningElement[name.name='TerminalLine'] > JSXAttribute[name.name='shell'] > Literal[value!=/^(?:shell|panto)$/]",
+ "message": "<TerminalLine> shell must be one of 'shell' | 'panto'."
+ }
+ ]
+ },
+ "overrides": [
+ {
+ "files": [
+ "**/index.js"
+ ],
+ "rules": {
+ "no-restricted-imports": "off"
+ }
+ }
+ ],
+ "x-omelette": {
+ "components": {
+ "Badge": {
+ "replaces": []
+ },
+ "Button": {
+ "replaces": []
+ },
+ "Callout": {
+ "replaces": []
+ },
+ "Card": {
+ "replaces": []
+ },
+ "CodeBlock": {
+ "replaces": []
+ },
+ "IconButton": {
+ "replaces": []
+ },
+ "Input": {
+ "replaces": []
+ },
+ "SelectOption": {
+ "replaces": []
+ },
+ "Switch": {
+ "replaces": []
+ },
+ "TabItem": {
+ "replaces": []
+ },
+ "Tag": {
+ "replaces": []
+ },
+ "TerminalLine": {
+ "replaces": []
+ }
+ },
+ "tokens": [
+ "--accent",
+ "--accent-deep",
+ "--border-default",
+ "--border-hairline",
+ "--border-medium",
+ "--border-strong",
+ "--border-thick",
+ "--border-thin",
+ "--brass",
+ "--container-prose",
+ "--container-wide",
+ "--dur-base",
+ "--dur-fast",
+ "--dur-slow",
+ "--ease-out",
+ "--ease-slide",
+ "--focus-ring",
+ "--font-display",
+ "--font-mono",
+ "--font-primary",
+ "--font-sans",
+ "--font-serif",
+ "--green",
+ "--green-deep",
+ "--green-soft",
+ "--green-wash",
+ "--ink-0",
+ "--ink-1",
+ "--ink-2",
+ "--ink-3",
+ "--ink-4",
+ "--leading-mono",
+ "--leading-normal",
+ "--leading-relaxed",
+ "--leading-snug",
+ "--leading-tight",
+ "--link",
+ "--link-hover",
+ "--paper-0",
+ "--paper-1",
+ "--paper-2",
+ "--paper-3",
+ "--paper-4",
+ "--radius-lg",
+ "--radius-md",
+ "--radius-none",
+ "--radius-pill",
+ "--radius-sm",
+ "--radius-xs",
+ "--red",
+ "--red-deep",
+ "--red-soft",
+ "--red-wash",
+ "--role-body",
+ "--role-code",
+ "--role-display",
+ "--role-h1",
+ "--role-h2",
+ "--role-h3",
+ "--role-label",
+ "--role-prose",
+ "--rule-tick",
+ "--shadow-inset",
+ "--shadow-lg",
+ "--shadow-md",
+ "--shadow-sm",
+ "--shadow-xs",
+ "--sidebar-width",
+ "--space-0",
+ "--space-1",
+ "--space-2",
+ "--space-3",
+ "--space-4",
+ "--space-5",
+ "--space-6",
+ "--space-7",
+ "--space-8",
+ "--space-9",
+ "--status-danger",
+ "--status-danger-bg",
+ "--status-info",
+ "--status-info-bg",
+ "--status-success",
+ "--status-success-bg",
+ "--surface-card",
+ "--surface-code",
+ "--surface-inverse",
+ "--surface-page",
+ "--surface-sunken",
+ "--text-2xl",
+ "--text-2xs",
+ "--text-3xl",
+ "--text-4xl",
+ "--text-5xl",
+ "--text-base",
+ "--text-body",
+ "--text-faint",
+ "--text-lg",
+ "--text-md",
+ "--text-muted",
+ "--text-on-accent",
+ "--text-on-ink",
+ "--text-placeholder",
+ "--text-sm",
+ "--text-strong",
+ "--text-xl",
+ "--text-xs",
+ "--tick-gradient",
+ "--tick-h-major",
+ "--tick-h-minor",
+ "--tick-pitch",
+ "--tracking-label",
+ "--tracking-normal",
+ "--tracking-tight",
+ "--weight-bold",
+ "--weight-medium",
+ "--weight-regular",
+ "--weight-semibold"
+ ],
+ "tokenKinds": {
+ "--paper-0": "color",
+ "--paper-1": "color",
+ "--paper-2": "color",
+ "--paper-3": "color",
+ "--paper-4": "color",
+ "--ink-0": "color",
+ "--ink-1": "color",
+ "--ink-2": "color",
+ "--ink-3": "color",
+ "--ink-4": "color",
+ "--red-deep": "color",
+ "--red": "color",
+ "--red-soft": "color",
+ "--red-wash": "color",
+ "--green-deep": "color",
+ "--green": "color",
+ "--green-soft": "color",
+ "--green-wash": "color",
+ "--brass": "color",
+ "--surface-page": "color",
+ "--surface-card": "color",
+ "--surface-sunken": "color",
+ "--surface-code": "color",
+ "--surface-inverse": "color",
+ "--text-strong": "font",
+ "--text-body": "font",
+ "--text-muted": "font",
+ "--text-faint": "font",
+ "--text-placeholder": "font",
+ "--text-on-ink": "font",
+ "--text-on-accent": "font",
+ "--border-hairline": "color",
+ "--border-default": "color",
+ "--border-strong": "color",
+ "--rule-tick": "color",
+ "--accent": "color",
+ "--accent-deep": "color",
+ "--link": "color",
+ "--link-hover": "color",
+ "--status-danger": "color",
+ "--status-danger-bg": "color",
+ "--status-success": "color",
+ "--status-success-bg": "color",
+ "--status-info": "color",
+ "--status-info-bg": "color",
+ "--focus-ring": "color",
+ "--font-primary": "font",
+ "--font-mono": "font",
+ "--font-display": "font",
+ "--font-sans": "font",
+ "--font-serif": "font",
+ "--text-2xs": "font",
+ "--text-xs": "font",
+ "--text-sm": "font",
+ "--text-base": "font",
+ "--text-md": "font",
+ "--text-lg": "font",
+ "--text-xl": "font",
+ "--text-2xl": "font",
+ "--text-3xl": "font",
+ "--text-4xl": "font",
+ "--text-5xl": "font",
+ "--weight-regular": "font",
+ "--weight-medium": "font",
+ "--weight-semibold": "font",
+ "--weight-bold": "font",
+ "--leading-tight": "font",
+ "--leading-snug": "font",
+ "--leading-normal": "font",
+ "--leading-relaxed": "font",
+ "--leading-mono": "font",
+ "--tracking-label": "font",
+ "--tracking-tight": "font",
+ "--tracking-normal": "font",
+ "--role-display": "color",
+ "--role-h1": "color",
+ "--role-h2": "color",
+ "--role-h3": "color",
+ "--role-body": "color",
+ "--role-prose": "color",
+ "--role-code": "color",
+ "--role-label": "color",
+ "--space-0": "spacing",
+ "--space-1": "spacing",
+ "--space-2": "spacing",
+ "--space-3": "spacing",
+ "--space-4": "spacing",
+ "--space-5": "spacing",
+ "--space-6": "spacing",
+ "--space-7": "spacing",
+ "--space-8": "spacing",
+ "--space-9": "spacing",
+ "--radius-none": "radius",
+ "--radius-xs": "radius",
+ "--radius-sm": "radius",
+ "--radius-md": "radius",
+ "--radius-lg": "radius",
+ "--radius-pill": "radius",
+ "--border-thin": "spacing",
+ "--border-medium": "spacing",
+ "--border-thick": "spacing",
+ "--shadow-xs": "shadow",
+ "--shadow-sm": "shadow",
+ "--shadow-md": "shadow",
+ "--shadow-lg": "shadow",
+ "--shadow-inset": "shadow",
+ "--tick-pitch": "spacing",
+ "--tick-h-major": "spacing",
+ "--tick-h-minor": "color",
+ "--tick-gradient": "color",
+ "--ease-slide": "other",
+ "--ease-out": "other",
+ "--dur-fast": "other",
+ "--dur-base": "other",
+ "--dur-slow": "other",
+ "--container-prose": "spacing",
+ "--container-wide": "spacing",
+ "--sidebar-width": "spacing"
+ },
+ "fontFamilies": [
+ "IBM Plex Mono",
+ "Saira Condensed"
+ ]
+ }
+} \ No newline at end of file
diff --git a/pantograph/ds/pantograph-design-system-56a1c74e-41c1-486b-ba3c-4eabdfacef31/_ds_bundle.js b/pantograph/ds/pantograph-design-system-56a1c74e-41c1-486b-ba3c-4eabdfacef31/_ds_bundle.js
new file mode 100644
index 0000000..f584806
--- /dev/null
+++ b/pantograph/ds/pantograph-design-system-56a1c74e-41c1-486b-ba3c-4eabdfacef31/_ds_bundle.js
@@ -0,0 +1,1878 @@
+/* @ds-bundle: {"format":3,"namespace":"PantographDesignSystem_56a1c7","components":[{"name":"Callout","sourcePath":"components/code/Callout.jsx"},{"name":"CodeBlock","sourcePath":"components/code/CodeBlock.jsx"},{"name":"Terminal","sourcePath":"components/code/Terminal.jsx"},{"name":"Badge","sourcePath":"components/core/Badge.jsx"},{"name":"Button","sourcePath":"components/core/Button.jsx"},{"name":"Card","sourcePath":"components/core/Card.jsx"},{"name":"IconButton","sourcePath":"components/core/IconButton.jsx"},{"name":"Tag","sourcePath":"components/core/Tag.jsx"},{"name":"Input","sourcePath":"components/forms/Input.jsx"},{"name":"Select","sourcePath":"components/forms/Select.jsx"},{"name":"Switch","sourcePath":"components/forms/Switch.jsx"},{"name":"Tabs","sourcePath":"components/navigation/Tabs.jsx"}],"sourceHashes":{"components/code/Callout.jsx":"dc74992ca3a8","components/code/CodeBlock.jsx":"75f95cdb07d4","components/code/Terminal.jsx":"32207bf1f8d8","components/core/Badge.jsx":"c04491952135","components/core/Button.jsx":"c43631b0d758","components/core/Card.jsx":"b4474a239d87","components/core/IconButton.jsx":"bd55860ce51f","components/core/Tag.jsx":"c8a2e8432e9a","components/forms/Input.jsx":"4c58262a357e","components/forms/Select.jsx":"a2a13ccf0cb7","components/forms/Switch.jsx":"2737a3f2e747","components/navigation/Tabs.jsx":"d3f184460fa6","ui_kits/docs/App.jsx":"7a59cd523900","ui_kits/docs/DocsHeader.jsx":"2eb476264c51","ui_kits/docs/DocsSidebar.jsx":"a091bd208645","ui_kits/docs/ReferencePage.jsx":"d0855f29aced","ui_kits/home/Features.jsx":"48ac29e811d8","ui_kits/home/Hero.jsx":"2dc3333c9b3b","ui_kits/home/HomeApp.jsx":"f2a18138face","ui_kits/home/HomeHeader.jsx":"dfc98c37e66f","ui_kits/home/InstallSection.jsx":"aec2cdd7dae8"},"inlinedExternals":[],"unexposedExports":[]} */
+
+(() => {
+
+const __ds_ns = (window.PantographDesignSystem_56a1c7 = window.PantographDesignSystem_56a1c7 || {});
+
+const __ds_scope = {};
+
+(__ds_ns.__errors = __ds_ns.__errors || []);
+
+// components/code/Callout.jsx
+try { (() => {
+function _extends() { return _extends = Object.assign ? Object.assign.bind() : function (n) { for (var e = 1; e < arguments.length; e++) { var t = arguments[e]; for (var r in t) ({}).hasOwnProperty.call(t, r) && (n[r] = t[r]); } return n; }, _extends.apply(null, arguments); }
+const STYLE_ID = 'pg-callout-css';
+function ensureStyles() {
+ if (typeof document === 'undefined' || document.getElementById(STYLE_ID)) return;
+ const el = document.createElement('style');
+ el.id = STYLE_ID;
+ el.textContent = `
+ .pg-callout {
+ display: flex; gap: 12px;
+ padding: 13px 15px;
+ background: var(--paper-0);
+ border: var(--border-thin) solid var(--border-hairline);
+ border-left: var(--border-thick) solid var(--ink-3);
+ border-radius: var(--radius-sm);
+ }
+ .pg-callout__icon { flex: none; width: 18px; height: 18px; margin-top: 1px; color: var(--ink-3); }
+ .pg-callout__icon svg { width: 100%; height: 100%; }
+ .pg-callout__body { font: var(--weight-regular) var(--text-sm)/1.55 var(--font-sans); color: var(--ink-1); }
+ .pg-callout__title {
+ font: var(--weight-semibold) var(--text-2xs)/1 var(--font-display);
+ letter-spacing: var(--tracking-label); text-transform: uppercase;
+ margin: 0 0 5px; color: var(--ink-2);
+ }
+ .pg-callout__body :last-child { margin-bottom: 0; }
+ .pg-callout--note { border-left-color: var(--ink-3); }
+ .pg-callout--tip { border-left-color: var(--green); background: var(--green-wash); }
+ .pg-callout--tip .pg-callout__icon, .pg-callout--tip .pg-callout__title { color: var(--green-deep); }
+ .pg-callout--warn { border-left-color: var(--brass); background: #F0E7D0; }
+ .pg-callout--warn .pg-callout__icon, .pg-callout--warn .pg-callout__title { color: #7A5E20; }
+ .pg-callout--danger { border-left-color: var(--red); background: var(--red-wash); }
+ .pg-callout--danger .pg-callout__icon, .pg-callout--danger .pg-callout__title { color: var(--red-deep); }
+ `;
+ document.head.appendChild(el);
+}
+const ICONS = {
+ note: /*#__PURE__*/React.createElement("svg", {
+ viewBox: "0 0 24 24",
+ fill: "none",
+ stroke: "currentColor",
+ strokeWidth: "2",
+ strokeLinecap: "round",
+ strokeLinejoin: "round"
+ }, /*#__PURE__*/React.createElement("circle", {
+ cx: "12",
+ cy: "12",
+ r: "10"
+ }), /*#__PURE__*/React.createElement("path", {
+ d: "M12 16v-4"
+ }), /*#__PURE__*/React.createElement("path", {
+ d: "M12 8h.01"
+ })),
+ tip: /*#__PURE__*/React.createElement("svg", {
+ viewBox: "0 0 24 24",
+ fill: "none",
+ stroke: "currentColor",
+ strokeWidth: "2",
+ strokeLinecap: "round",
+ strokeLinejoin: "round"
+ }, /*#__PURE__*/React.createElement("path", {
+ d: "M9 18h6"
+ }), /*#__PURE__*/React.createElement("path", {
+ d: "M10 22h4"
+ }), /*#__PURE__*/React.createElement("path", {
+ d: "M15.09 14c.18-.98.65-1.74 1.41-2.5A4.65 4.65 0 0 0 18 8 6 6 0 0 0 6 8c0 1 .23 2.23 1.5 3.5A4.61 4.61 0 0 1 8.91 14"
+ })),
+ warn: /*#__PURE__*/React.createElement("svg", {
+ viewBox: "0 0 24 24",
+ fill: "none",
+ stroke: "currentColor",
+ strokeWidth: "2",
+ strokeLinecap: "round",
+ strokeLinejoin: "round"
+ }, /*#__PURE__*/React.createElement("path", {
+ d: "m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3Z"
+ }), /*#__PURE__*/React.createElement("path", {
+ d: "M12 9v4"
+ }), /*#__PURE__*/React.createElement("path", {
+ d: "M12 17h.01"
+ })),
+ danger: /*#__PURE__*/React.createElement("svg", {
+ viewBox: "0 0 24 24",
+ fill: "none",
+ stroke: "currentColor",
+ strokeWidth: "2",
+ strokeLinecap: "round",
+ strokeLinejoin: "round"
+ }, /*#__PURE__*/React.createElement("circle", {
+ cx: "12",
+ cy: "12",
+ r: "10"
+ }), /*#__PURE__*/React.createElement("path", {
+ d: "m15 9-6 6"
+ }), /*#__PURE__*/React.createElement("path", {
+ d: "m9 9 6 6"
+ }))
+};
+const DEFAULT_TITLES = {
+ note: 'Note',
+ tip: 'Tip',
+ warn: 'Warning',
+ danger: 'Danger'
+};
+
+/**
+ * Callout — a documentation admonition (note / tip / warn / danger).
+ */
+function Callout({
+ variant = 'note',
+ title,
+ icon,
+ className = '',
+ children,
+ ...rest
+}) {
+ ensureStyles();
+ const heading = title === undefined ? DEFAULT_TITLES[variant] : title;
+ return /*#__PURE__*/React.createElement("div", _extends({
+ className: ['pg-callout', `pg-callout--${variant}`, className].filter(Boolean).join(' ')
+ }, rest), /*#__PURE__*/React.createElement("span", {
+ className: "pg-callout__icon"
+ }, icon || ICONS[variant]), /*#__PURE__*/React.createElement("div", {
+ className: "pg-callout__body"
+ }, heading ? /*#__PURE__*/React.createElement("p", {
+ className: "pg-callout__title"
+ }, heading) : null, children));
+}
+Object.assign(__ds_scope, { Callout });
+})(); } catch (e) { __ds_ns.__errors.push({ path: "components/code/Callout.jsx", error: String((e && e.message) || e) }); }
+
+// components/code/CodeBlock.jsx
+try { (() => {
+function _extends() { return _extends = Object.assign ? Object.assign.bind() : function (n) { for (var e = 1; e < arguments.length; e++) { var t = arguments[e]; for (var r in t) ({}).hasOwnProperty.call(t, r) && (n[r] = t[r]); } return n; }, _extends.apply(null, arguments); }
+const STYLE_ID = 'pg-codeblock-css';
+function ensureStyles() {
+ if (typeof document === 'undefined' || document.getElementById(STYLE_ID)) return;
+ const el = document.createElement('style');
+ el.id = STYLE_ID;
+ el.textContent = `
+ .pg-code {
+ background: var(--surface-code);
+ border: var(--border-thin) solid var(--border-default);
+ border-radius: var(--radius-md);
+ overflow: hidden;
+ font-family: var(--font-mono);
+ }
+ .pg-code__bar {
+ display: flex; align-items: center; gap: 10px;
+ padding: 8px 12px; background: var(--paper-2);
+ border-bottom: var(--border-thin) solid var(--border-hairline);
+ }
+ .pg-code__name { font: var(--weight-medium) var(--text-xs)/1 var(--font-mono); color: var(--ink-2); }
+ .pg-code__lang {
+ font: var(--weight-semibold) var(--text-2xs)/1 var(--font-display);
+ letter-spacing: var(--tracking-label); text-transform: uppercase; color: var(--ink-3);
+ margin-left: auto; padding-right: 4px;
+ }
+ .pg-code__copy {
+ display: inline-flex; align-items: center; gap: 5px;
+ font: var(--weight-medium) var(--text-2xs)/1 var(--font-display);
+ letter-spacing: 0.1em; text-transform: uppercase;
+ color: var(--ink-3); background: none; border: 0; cursor: pointer;
+ padding: 4px 6px; border-radius: var(--radius-xs);
+ transition: color var(--dur-fast) var(--ease-out), background var(--dur-fast) var(--ease-out);
+ }
+ .pg-code__copy:hover { color: var(--ink-0); background: var(--paper-1); }
+ .pg-code__copy--done { color: var(--green-deep); }
+ .pg-code__copy svg { width: 13px; height: 13px; }
+ .pg-code__scroll { overflow-x: auto; padding: 14px 16px; }
+ .pg-code pre { font-size: var(--text-sm); line-height: var(--leading-mono); color: var(--ink-1); margin: 0; }
+ .pg-code--nobar { }
+ /* token colors for hand-marked spans */
+ .pg-code .tok-kw { color: var(--green-deep); }
+ .pg-code .tok-str { color: var(--red-deep); }
+ .pg-code .tok-com { color: var(--ink-4); font-style: italic; }
+ .pg-code .tok-fn { color: var(--ink-0); font-weight: 500; }
+ .pg-code .tok-num { color: var(--brass); }
+ `;
+ document.head.appendChild(el);
+}
+const CopyGlyph = () => /*#__PURE__*/React.createElement("svg", {
+ viewBox: "0 0 24 24",
+ fill: "none",
+ stroke: "currentColor",
+ strokeWidth: "2",
+ strokeLinecap: "round",
+ strokeLinejoin: "round"
+}, /*#__PURE__*/React.createElement("rect", {
+ x: "9",
+ y: "9",
+ width: "11",
+ height: "11",
+ rx: "2"
+}), /*#__PURE__*/React.createElement("path", {
+ d: "M5 15V5a2 2 0 0 1 2-2h10"
+}));
+const CheckGlyph = () => /*#__PURE__*/React.createElement("svg", {
+ viewBox: "0 0 24 24",
+ fill: "none",
+ stroke: "currentColor",
+ strokeWidth: "2.5",
+ strokeLinecap: "round",
+ strokeLinejoin: "round"
+}, /*#__PURE__*/React.createElement("path", {
+ d: "m20 6-11 11-5-5"
+}));
+
+/**
+ * CodeBlock — a code surface with an optional title bar, language label,
+ * and copy button. Pass a raw `code` string or pre-marked JSX children.
+ */
+function CodeBlock({
+ code = null,
+ language = null,
+ filename = null,
+ showBar = true,
+ className = '',
+ children,
+ ...rest
+}) {
+ ensureStyles();
+ const [copied, setCopied] = React.useState(false);
+ const preRef = React.useRef(null);
+ const copy = () => {
+ const text = code != null ? code : preRef.current ? preRef.current.innerText : '';
+ if (navigator.clipboard) navigator.clipboard.writeText(text);
+ setCopied(true);
+ setTimeout(() => setCopied(false), 1400);
+ };
+ return /*#__PURE__*/React.createElement("div", _extends({
+ className: ['pg-code', className].filter(Boolean).join(' ')
+ }, rest), showBar ? /*#__PURE__*/React.createElement("div", {
+ className: "pg-code__bar"
+ }, filename ? /*#__PURE__*/React.createElement("span", {
+ className: "pg-code__name"
+ }, filename) : null, language ? /*#__PURE__*/React.createElement("span", {
+ className: "pg-code__lang"
+ }, language) : null, /*#__PURE__*/React.createElement("button", {
+ className: ['pg-code__copy', copied ? 'pg-code__copy--done' : ''].join(' '),
+ onClick: copy
+ }, copied ? /*#__PURE__*/React.createElement(CheckGlyph, null) : /*#__PURE__*/React.createElement(CopyGlyph, null), copied ? 'Copied' : 'Copy')) : null, /*#__PURE__*/React.createElement("div", {
+ className: "pg-code__scroll"
+ }, /*#__PURE__*/React.createElement("pre", {
+ ref: preRef
+ }, /*#__PURE__*/React.createElement("code", null, children != null ? children : code))));
+}
+Object.assign(__ds_scope, { CodeBlock });
+})(); } catch (e) { __ds_ns.__errors.push({ path: "components/code/CodeBlock.jsx", error: String((e && e.message) || e) }); }
+
+// components/code/Terminal.jsx
+try { (() => {
+function _extends() { return _extends = Object.assign ? Object.assign.bind() : function (n) { for (var e = 1; e < arguments.length; e++) { var t = arguments[e]; for (var r in t) ({}).hasOwnProperty.call(t, r) && (n[r] = t[r]); } return n; }, _extends.apply(null, arguments); }
+const STYLE_ID = 'pg-terminal-css';
+function ensureStyles() {
+ if (typeof document === 'undefined' || document.getElementById(STYLE_ID)) return;
+ const el = document.createElement('style');
+ el.id = STYLE_ID;
+ el.textContent = `
+ .pg-term {
+ background: var(--ink-1);
+ border: var(--border-thin) solid var(--ink-0);
+ border-radius: var(--radius-md);
+ overflow: hidden;
+ box-shadow: var(--shadow-md);
+ font-family: var(--font-mono);
+ }
+ .pg-term__bar {
+ display: flex; align-items: center; gap: 8px;
+ padding: 9px 12px; background: #232B3B;
+ border-bottom: var(--border-thin) solid #2E3749;
+ }
+ .pg-term__dot { width: 9px; height: 9px; border-radius: var(--radius-pill); }
+ .pg-term__title {
+ margin-left: 6px;
+ font: var(--weight-semibold) var(--text-2xs)/1 var(--font-display);
+ letter-spacing: var(--tracking-label); text-transform: uppercase; color: var(--paper-4);
+ }
+ .pg-term__body { padding: 14px 16px; overflow-x: auto; font-size: var(--text-sm); line-height: 1.65; }
+ .pg-term__line { white-space: pre; color: var(--paper-2); }
+ .pg-term__line .pg-term__prompt { color: var(--green-soft); user-select: none; }
+ .pg-term__line .pg-term__panto { color: var(--red-soft); user-select: none; }
+ .pg-term__line--cmd { color: var(--paper-0); }
+ .pg-term__line--out { color: var(--paper-4); }
+ .pg-term__line--ok { color: var(--green-soft); }
+ .pg-term__line--err { color: var(--red-soft); }
+ .pg-term__line--com { color: #6E7689; font-style: italic; }
+ .pg-term__cursor {
+ display: inline-block; width: 8px; height: 1.05em; vertical-align: text-bottom;
+ background: var(--red); margin-left: 2px;
+ animation: pg-term-blink 1.1s steps(1) infinite;
+ }
+ @keyframes pg-term-blink { 50% { opacity: 0; } }
+ @media (prefers-reduced-motion: reduce) { .pg-term__cursor { animation: none; } }
+ `;
+ document.head.appendChild(el);
+}
+
+/**
+ * Terminal — a panto CLI window. Render with a `lines` array, or pass
+ * children for full control. Line types: cmd, out, ok, err, com.
+ */
+function Terminal({
+ title = 'panto',
+ lines = null,
+ cursor = false,
+ className = '',
+ children,
+ ...rest
+}) {
+ ensureStyles();
+ return /*#__PURE__*/React.createElement("div", _extends({
+ className: ['pg-term', className].filter(Boolean).join(' ')
+ }, rest), /*#__PURE__*/React.createElement("div", {
+ className: "pg-term__bar"
+ }, /*#__PURE__*/React.createElement("span", {
+ className: "pg-term__dot",
+ style: {
+ background: 'var(--red)'
+ }
+ }), /*#__PURE__*/React.createElement("span", {
+ className: "pg-term__dot",
+ style: {
+ background: 'var(--brass)'
+ }
+ }), /*#__PURE__*/React.createElement("span", {
+ className: "pg-term__dot",
+ style: {
+ background: 'var(--green)'
+ }
+ }), /*#__PURE__*/React.createElement("span", {
+ className: "pg-term__title"
+ }, title)), /*#__PURE__*/React.createElement("div", {
+ className: "pg-term__body"
+ }, lines ? lines.map((l, i) => {
+ const type = l.type || 'out';
+ if (type === 'cmd') {
+ return /*#__PURE__*/React.createElement("div", {
+ key: i,
+ className: "pg-term__line pg-term__line--cmd"
+ }, /*#__PURE__*/React.createElement("span", {
+ className: l.shell === 'panto' ? 'pg-term__panto' : 'pg-term__prompt'
+ }, l.shell === 'panto' ? 'panto ▸ ' : '$ '), l.text);
+ }
+ return /*#__PURE__*/React.createElement("div", {
+ key: i,
+ className: `pg-term__line pg-term__line--${type}`
+ }, l.text);
+ }) : children, cursor ? /*#__PURE__*/React.createElement("span", {
+ className: "pg-term__cursor"
+ }) : null));
+}
+Object.assign(__ds_scope, { Terminal });
+})(); } catch (e) { __ds_ns.__errors.push({ path: "components/code/Terminal.jsx", error: String((e && e.message) || e) }); }
+
+// components/core/Badge.jsx
+try { (() => {
+function _extends() { return _extends = Object.assign ? Object.assign.bind() : function (n) { for (var e = 1; e < arguments.length; e++) { var t = arguments[e]; for (var r in t) ({}).hasOwnProperty.call(t, r) && (n[r] = t[r]); } return n; }, _extends.apply(null, arguments); }
+const STYLE_ID = 'pg-badge-css';
+function ensureStyles() {
+ if (typeof document === 'undefined' || document.getElementById(STYLE_ID)) return;
+ const el = document.createElement('style');
+ el.id = STYLE_ID;
+ el.textContent = `
+ .pg-badge {
+ display: inline-flex; align-items: center; gap: 5px;
+ font-family: var(--font-display); font-weight: var(--weight-semibold);
+ font-size: var(--text-2xs); letter-spacing: var(--tracking-label);
+ text-transform: uppercase; line-height: 1; white-space: nowrap;
+ padding: 4px 7px; border-radius: var(--radius-xs);
+ border: var(--border-thin) solid transparent;
+ }
+ .pg-badge__dot { width: 6px; height: 6px; border-radius: var(--radius-pill); background: currentColor; }
+ .pg-badge--neutral { color: var(--ink-2); background: var(--paper-2); border-color: var(--paper-3); }
+ .pg-badge--danger { color: var(--red-deep); background: var(--red-wash); border-color: var(--red-soft); }
+ .pg-badge--success { color: var(--green-deep); background: var(--green-wash); border-color: var(--green-soft); }
+ .pg-badge--ink { color: var(--paper-0); background: var(--ink-1); border-color: var(--ink-1); }
+ .pg-badge--outline { color: var(--ink-2); background: transparent; border-color: var(--border-default); }
+ `;
+ document.head.appendChild(el);
+}
+
+/**
+ * Badge — a small engraved-label status marker (Stable, Beta, Deprecated, …).
+ */
+function Badge({
+ variant = 'neutral',
+ dot = false,
+ className = '',
+ children,
+ ...rest
+}) {
+ ensureStyles();
+ const cls = ['pg-badge', `pg-badge--${variant}`, className].filter(Boolean).join(' ');
+ return /*#__PURE__*/React.createElement("span", _extends({
+ className: cls
+ }, rest), dot ? /*#__PURE__*/React.createElement("span", {
+ className: "pg-badge__dot"
+ }) : null, children);
+}
+Object.assign(__ds_scope, { Badge });
+})(); } catch (e) { __ds_ns.__errors.push({ path: "components/core/Badge.jsx", error: String((e && e.message) || e) }); }
+
+// components/core/Button.jsx
+try { (() => {
+function _extends() { return _extends = Object.assign ? Object.assign.bind() : function (n) { for (var e = 1; e < arguments.length; e++) { var t = arguments[e]; for (var r in t) ({}).hasOwnProperty.call(t, r) && (n[r] = t[r]); } return n; }, _extends.apply(null, arguments); }
+/* Injects the Button stylesheet once. Components in this system are
+ self-contained: they reference design tokens via CSS custom
+ properties and ship their own scoped CSS. */
+const STYLE_ID = 'pg-button-css';
+function ensureStyles() {
+ if (typeof document === 'undefined' || document.getElementById(STYLE_ID)) return;
+ const el = document.createElement('style');
+ el.id = STYLE_ID;
+ el.textContent = `
+ .pg-btn {
+ --_bg: var(--ink-1); --_fg: var(--paper-0); --_bd: var(--ink-1);
+ display: inline-flex; align-items: center; justify-content: center;
+ gap: var(--space-2); box-sizing: border-box;
+ font-family: var(--font-sans); font-weight: var(--weight-semibold);
+ line-height: 1; letter-spacing: 0.01em; white-space: nowrap;
+ background: var(--_bg); color: var(--_fg);
+ border: var(--border-medium) solid var(--_bd);
+ border-radius: var(--radius-sm);
+ cursor: pointer; text-decoration: none; user-select: none;
+ transition: background var(--dur-fast) var(--ease-out),
+ border-color var(--dur-fast) var(--ease-out),
+ transform var(--dur-fast) var(--ease-out),
+ box-shadow var(--dur-fast) var(--ease-out);
+ box-shadow: var(--shadow-xs);
+ }
+ .pg-btn:hover { background: var(--_bgh, var(--_bg)); border-color: var(--_bdh, var(--_bd)); }
+ .pg-btn:active { transform: translateY(1px); box-shadow: none; }
+ .pg-btn:focus-visible { outline: var(--border-thick) solid var(--focus-ring); outline-offset: 2px; }
+ .pg-btn[disabled] { cursor: not-allowed; opacity: 0.45; box-shadow: none; transform: none; }
+
+ .pg-btn--sm { font-size: var(--text-xs); padding: 6px 12px; }
+ .pg-btn--md { font-size: var(--text-sm); padding: 9px 16px; }
+ .pg-btn--lg { font-size: var(--text-base); padding: 12px 22px; }
+ .pg-btn--block { display: flex; width: 100%; }
+
+ .pg-btn--primary { --_bg: var(--ink-1); --_fg: var(--paper-0); --_bd: var(--ink-1); --_bgh: var(--ink-0); --_bdh: var(--ink-0); }
+ .pg-btn--accent { --_bg: var(--red); --_fg: var(--paper-0); --_bd: var(--red); --_bgh: var(--red-deep); --_bdh: var(--red-deep); }
+ .pg-btn--secondary { --_bg: var(--paper-0); --_fg: var(--ink-1); --_bd: var(--border-default); --_bgh: var(--paper-2); --_bdh: var(--ink-3); }
+ .pg-btn--ghost { --_bg: transparent; --_fg: var(--ink-1); --_bd: transparent; --_bgh: var(--paper-2); --_bdh: transparent; box-shadow: none; }
+ .pg-btn--danger { --_bg: transparent; --_fg: var(--red-deep); --_bd: var(--red); --_bgh: var(--red-wash); --_bdh: var(--red-deep); box-shadow: none; }
+ .pg-btn__icon { display: inline-flex; width: 1.05em; height: 1.05em; }
+ .pg-btn__icon svg { width: 100%; height: 100%; }
+ `;
+ document.head.appendChild(el);
+}
+
+/**
+ * Button — the primary action control. Machined edges, instrument palette.
+ */
+function Button({
+ variant = 'primary',
+ size = 'md',
+ block = false,
+ icon = null,
+ iconRight = null,
+ disabled = false,
+ as = 'button',
+ className = '',
+ children,
+ ...rest
+}) {
+ ensureStyles();
+ const Tag = as;
+ const cls = ['pg-btn', `pg-btn--${variant}`, `pg-btn--${size}`, block ? 'pg-btn--block' : '', className].filter(Boolean).join(' ');
+ return /*#__PURE__*/React.createElement(Tag, _extends({
+ className: cls,
+ disabled: Tag === 'button' ? disabled : undefined
+ }, rest), icon ? /*#__PURE__*/React.createElement("span", {
+ className: "pg-btn__icon"
+ }, icon) : null, children, iconRight ? /*#__PURE__*/React.createElement("span", {
+ className: "pg-btn__icon"
+ }, iconRight) : null);
+}
+Object.assign(__ds_scope, { Button });
+})(); } catch (e) { __ds_ns.__errors.push({ path: "components/core/Button.jsx", error: String((e && e.message) || e) }); }
+
+// components/core/Card.jsx
+try { (() => {
+function _extends() { return _extends = Object.assign ? Object.assign.bind() : function (n) { for (var e = 1; e < arguments.length; e++) { var t = arguments[e]; for (var r in t) ({}).hasOwnProperty.call(t, r) && (n[r] = t[r]); } return n; }, _extends.apply(null, arguments); }
+const STYLE_ID = 'pg-card-css';
+function ensureStyles() {
+ if (typeof document === 'undefined' || document.getElementById(STYLE_ID)) return;
+ const el = document.createElement('style');
+ el.id = STYLE_ID;
+ el.textContent = `
+ .pg-card {
+ display: block; box-sizing: border-box;
+ background: var(--surface-card);
+ border: var(--border-thin) solid var(--border-hairline);
+ border-radius: var(--radius-md);
+ box-shadow: var(--shadow-sm);
+ overflow: hidden;
+ }
+ .pg-card--flat { box-shadow: none; }
+ .pg-card--sunken { background: var(--surface-sunken); box-shadow: var(--shadow-inset); border-color: var(--paper-3); }
+ .pg-card--interactive { cursor: pointer; transition: box-shadow var(--dur-base) var(--ease-out), transform var(--dur-base) var(--ease-out), border-color var(--dur-base) var(--ease-out); }
+ .pg-card--interactive:hover { box-shadow: var(--shadow-md); border-color: var(--border-default); transform: translateY(-1px); }
+ /* top engraved tick rule, like a scale edge */
+ .pg-card--ruled { border-top: var(--border-thick) solid var(--ink-1); }
+ .pg-card--ruled.pg-card--accent { border-top-color: var(--red); }
+
+ .pg-card__header {
+ display: flex; align-items: center; gap: var(--space-3);
+ padding: var(--space-4) var(--space-5);
+ border-bottom: var(--border-thin) solid var(--border-hairline);
+ }
+ .pg-card__title { font: var(--weight-semibold) var(--text-lg)/1.2 var(--font-sans); color: var(--text-strong); margin: 0; }
+ .pg-card__body { padding: var(--space-5); }
+ .pg-card__footer {
+ padding: var(--space-4) var(--space-5);
+ border-top: var(--border-thin) solid var(--border-hairline);
+ background: var(--paper-1);
+ display: flex; align-items: center; gap: var(--space-3);
+ }
+ `;
+ document.head.appendChild(el);
+}
+
+/**
+ * Card — the primary surface container. Header / body / footer are optional.
+ */
+function Card({
+ variant = 'default',
+ ruled = false,
+ interactive = false,
+ title = null,
+ actions = null,
+ footer = null,
+ className = '',
+ children,
+ ...rest
+}) {
+ ensureStyles();
+ const cls = ['pg-card', variant !== 'default' ? `pg-card--${variant}` : '', ruled ? 'pg-card--ruled' : '', interactive ? 'pg-card--interactive' : '', className].filter(Boolean).join(' ');
+ const hasHeader = title != null || actions != null;
+ return /*#__PURE__*/React.createElement("div", _extends({
+ className: cls
+ }, rest), hasHeader ? /*#__PURE__*/React.createElement("div", {
+ className: "pg-card__header"
+ }, title ? /*#__PURE__*/React.createElement("h3", {
+ className: "pg-card__title"
+ }, title) : null, actions ? /*#__PURE__*/React.createElement("div", {
+ style: {
+ marginLeft: 'auto',
+ display: 'flex',
+ gap: 'var(--space-2)'
+ }
+ }, actions) : null) : null, /*#__PURE__*/React.createElement("div", {
+ className: "pg-card__body"
+ }, children), footer ? /*#__PURE__*/React.createElement("div", {
+ className: "pg-card__footer"
+ }, footer) : null);
+}
+Object.assign(__ds_scope, { Card });
+})(); } catch (e) { __ds_ns.__errors.push({ path: "components/core/Card.jsx", error: String((e && e.message) || e) }); }
+
+// components/core/IconButton.jsx
+try { (() => {
+function _extends() { return _extends = Object.assign ? Object.assign.bind() : function (n) { for (var e = 1; e < arguments.length; e++) { var t = arguments[e]; for (var r in t) ({}).hasOwnProperty.call(t, r) && (n[r] = t[r]); } return n; }, _extends.apply(null, arguments); }
+const STYLE_ID = 'pg-iconbutton-css';
+function ensureStyles() {
+ if (typeof document === 'undefined' || document.getElementById(STYLE_ID)) return;
+ const el = document.createElement('style');
+ el.id = STYLE_ID;
+ el.textContent = `
+ .pg-iconbtn {
+ display: inline-flex; align-items: center; justify-content: center;
+ box-sizing: border-box; padding: 0; cursor: pointer;
+ color: var(--ink-2); background: transparent;
+ border: var(--border-thin) solid transparent;
+ border-radius: var(--radius-sm);
+ transition: background var(--dur-fast) var(--ease-out),
+ color var(--dur-fast) var(--ease-out),
+ border-color var(--dur-fast) var(--ease-out),
+ transform var(--dur-fast) var(--ease-out);
+ }
+ .pg-iconbtn:hover { background: var(--paper-2); color: var(--ink-0); }
+ .pg-iconbtn:active { transform: translateY(1px); }
+ .pg-iconbtn:focus-visible { outline: var(--border-thick) solid var(--focus-ring); outline-offset: 2px; }
+ .pg-iconbtn[disabled] { cursor: not-allowed; opacity: 0.4; }
+ .pg-iconbtn--bordered { border-color: var(--border-default); background: var(--paper-0); }
+ .pg-iconbtn--bordered:hover { border-color: var(--ink-3); }
+ .pg-iconbtn--active { background: var(--paper-2); color: var(--red-deep); border-color: var(--border-default); }
+ .pg-iconbtn--sm { width: 28px; height: 28px; }
+ .pg-iconbtn--md { width: 36px; height: 36px; }
+ .pg-iconbtn--lg { width: 44px; height: 44px; }
+ .pg-iconbtn svg { width: 1.15em; height: 1.15em; display: block; }
+ .pg-iconbtn--sm svg { font-size: 15px; }
+ .pg-iconbtn--md svg { font-size: 18px; }
+ .pg-iconbtn--lg svg { font-size: 21px; }
+ `;
+ document.head.appendChild(el);
+}
+
+/**
+ * IconButton — a square, icon-only control for toolbars and dense UI.
+ */
+function IconButton({
+ size = 'md',
+ bordered = false,
+ active = false,
+ label,
+ disabled = false,
+ className = '',
+ children,
+ ...rest
+}) {
+ ensureStyles();
+ const cls = ['pg-iconbtn', `pg-iconbtn--${size}`, bordered ? 'pg-iconbtn--bordered' : '', active ? 'pg-iconbtn--active' : '', className].filter(Boolean).join(' ');
+ return /*#__PURE__*/React.createElement("button", _extends({
+ type: "button",
+ className: cls,
+ "aria-label": label,
+ title: label,
+ disabled: disabled
+ }, rest), children);
+}
+Object.assign(__ds_scope, { IconButton });
+})(); } catch (e) { __ds_ns.__errors.push({ path: "components/core/IconButton.jsx", error: String((e && e.message) || e) }); }
+
+// components/core/Tag.jsx
+try { (() => {
+function _extends() { return _extends = Object.assign ? Object.assign.bind() : function (n) { for (var e = 1; e < arguments.length; e++) { var t = arguments[e]; for (var r in t) ({}).hasOwnProperty.call(t, r) && (n[r] = t[r]); } return n; }, _extends.apply(null, arguments); }
+const STYLE_ID = 'pg-tag-css';
+function ensureStyles() {
+ if (typeof document === 'undefined' || document.getElementById(STYLE_ID)) return;
+ const el = document.createElement('style');
+ el.id = STYLE_ID;
+ el.textContent = `
+ .pg-tag {
+ display: inline-flex; align-items: center; gap: 6px;
+ font-family: var(--font-mono); font-weight: var(--weight-medium);
+ font-size: var(--text-xs); line-height: 1; white-space: nowrap;
+ color: var(--ink-1); background: var(--paper-0);
+ padding: 5px 9px; border-radius: var(--radius-pill);
+ border: var(--border-thin) solid var(--border-default);
+ }
+ .pg-tag__dot { width: 7px; height: 7px; border-radius: var(--radius-pill); flex: none; background: var(--ink-3); }
+ .pg-tag--interactive { cursor: pointer; transition: border-color var(--dur-fast) var(--ease-out), background var(--dur-fast) var(--ease-out); }
+ .pg-tag--interactive:hover { border-color: var(--ink-3); background: var(--paper-2); }
+ .pg-tag__remove {
+ display: inline-flex; align-items: center; justify-content: center;
+ width: 14px; height: 14px; margin-right: -2px; padding: 0;
+ border: 0; background: none; cursor: pointer; color: var(--ink-3);
+ font-family: var(--font-sans); font-size: 14px; line-height: 1; border-radius: var(--radius-pill);
+ }
+ .pg-tag__remove:hover { color: var(--red); }
+ `;
+ document.head.appendChild(el);
+}
+
+/**
+ * Tag — a monospace chip for languages, keywords, and filters.
+ */
+function Tag({
+ dot = null,
+ onRemove = null,
+ interactive = false,
+ className = '',
+ children,
+ ...rest
+}) {
+ ensureStyles();
+ const cls = ['pg-tag', interactive || rest.onClick ? 'pg-tag--interactive' : '', className].filter(Boolean).join(' ');
+ return /*#__PURE__*/React.createElement("span", _extends({
+ className: cls
+ }, rest), dot ? /*#__PURE__*/React.createElement("span", {
+ className: "pg-tag__dot",
+ style: {
+ background: dot === true ? undefined : dot
+ }
+ }) : null, children, onRemove ? /*#__PURE__*/React.createElement("button", {
+ type: "button",
+ className: "pg-tag__remove",
+ "aria-label": "Remove",
+ onClick: e => {
+ e.stopPropagation();
+ onRemove(e);
+ }
+ }, "\xD7") : null);
+}
+Object.assign(__ds_scope, { Tag });
+})(); } catch (e) { __ds_ns.__errors.push({ path: "components/core/Tag.jsx", error: String((e && e.message) || e) }); }
+
+// components/forms/Input.jsx
+try { (() => {
+function _extends() { return _extends = Object.assign ? Object.assign.bind() : function (n) { for (var e = 1; e < arguments.length; e++) { var t = arguments[e]; for (var r in t) ({}).hasOwnProperty.call(t, r) && (n[r] = t[r]); } return n; }, _extends.apply(null, arguments); }
+const STYLE_ID = 'pg-input-css';
+function ensureStyles() {
+ if (typeof document === 'undefined' || document.getElementById(STYLE_ID)) return;
+ const el = document.createElement('style');
+ el.id = STYLE_ID;
+ el.textContent = `
+ .pg-field { display: flex; flex-direction: column; gap: 6px; }
+ .pg-field__label {
+ font: var(--weight-semibold) var(--text-xs)/1 var(--font-display);
+ letter-spacing: var(--tracking-label); text-transform: uppercase; color: var(--ink-2);
+ }
+ .pg-field__req { color: var(--red); margin-left: 3px; }
+ .pg-input-wrap { position: relative; display: flex; align-items: center; }
+ .pg-input-wrap__icon {
+ position: absolute; left: 11px; display: inline-flex; color: var(--ink-3); pointer-events: none;
+ }
+ .pg-input-wrap__icon svg { width: 16px; height: 16px; }
+ .pg-input {
+ width: 100%; box-sizing: border-box;
+ font: var(--weight-regular) var(--text-sm)/1.4 var(--font-sans);
+ color: var(--ink-1); background: var(--paper-0);
+ border: var(--border-thin) solid var(--border-default);
+ border-radius: var(--radius-xs);
+ padding: 9px 12px;
+ transition: border-color var(--dur-fast) var(--ease-out), box-shadow var(--dur-fast) var(--ease-out);
+ }
+ .pg-input--mono { font-family: var(--font-mono); }
+ .pg-input--has-icon { padding-left: 34px; }
+ .pg-input::placeholder { color: var(--text-placeholder); }
+ .pg-input:hover { border-color: var(--ink-4); }
+ .pg-input:focus { outline: none; border-color: var(--green); box-shadow: 0 0 0 3px var(--green-wash); }
+ .pg-input--invalid { border-color: var(--red); }
+ .pg-input--invalid:focus { border-color: var(--red); box-shadow: 0 0 0 3px var(--red-wash); }
+ .pg-input:disabled { background: var(--paper-2); color: var(--ink-3); cursor: not-allowed; }
+ .pg-field__hint { font: var(--weight-regular) var(--text-xs)/1.4 var(--font-sans); color: var(--ink-3); }
+ .pg-field__hint--error { color: var(--red-deep); }
+ `;
+ document.head.appendChild(el);
+}
+
+/**
+ * Input — single-line text field with optional label, icon, and validation.
+ */
+function Input({
+ label = null,
+ hint = null,
+ error = null,
+ icon = null,
+ mono = false,
+ required = false,
+ id,
+ className = '',
+ ...rest
+}) {
+ ensureStyles();
+ const inputId = id || (label ? `pg-in-${Math.random().toString(36).slice(2, 8)}` : undefined);
+ const cls = ['pg-input', mono ? 'pg-input--mono' : '', icon ? 'pg-input--has-icon' : '', error ? 'pg-input--invalid' : '', className].filter(Boolean).join(' ');
+ return /*#__PURE__*/React.createElement("div", {
+ className: "pg-field"
+ }, label ? /*#__PURE__*/React.createElement("label", {
+ className: "pg-field__label",
+ htmlFor: inputId
+ }, label, required ? /*#__PURE__*/React.createElement("span", {
+ className: "pg-field__req"
+ }, "*") : null) : null, /*#__PURE__*/React.createElement("div", {
+ className: "pg-input-wrap"
+ }, icon ? /*#__PURE__*/React.createElement("span", {
+ className: "pg-input-wrap__icon"
+ }, icon) : null, /*#__PURE__*/React.createElement("input", _extends({
+ id: inputId,
+ className: cls,
+ "aria-invalid": !!error
+ }, rest))), error ? /*#__PURE__*/React.createElement("span", {
+ className: "pg-field__hint pg-field__hint--error"
+ }, error) : hint ? /*#__PURE__*/React.createElement("span", {
+ className: "pg-field__hint"
+ }, hint) : null);
+}
+Object.assign(__ds_scope, { Input });
+})(); } catch (e) { __ds_ns.__errors.push({ path: "components/forms/Input.jsx", error: String((e && e.message) || e) }); }
+
+// components/forms/Select.jsx
+try { (() => {
+function _extends() { return _extends = Object.assign ? Object.assign.bind() : function (n) { for (var e = 1; e < arguments.length; e++) { var t = arguments[e]; for (var r in t) ({}).hasOwnProperty.call(t, r) && (n[r] = t[r]); } return n; }, _extends.apply(null, arguments); }
+const STYLE_ID = 'pg-select-css';
+function ensureStyles() {
+ if (typeof document === 'undefined' || document.getElementById(STYLE_ID)) return;
+ const el = document.createElement('style');
+ el.id = STYLE_ID;
+ el.textContent = `
+ .pg-select-field { display: flex; flex-direction: column; gap: 6px; }
+ .pg-select-field__label {
+ font: var(--weight-semibold) var(--text-xs)/1 var(--font-display);
+ letter-spacing: var(--tracking-label); text-transform: uppercase; color: var(--ink-2);
+ }
+ .pg-select-wrap { position: relative; display: flex; align-items: center; }
+ .pg-select {
+ width: 100%; box-sizing: border-box; appearance: none;
+ font: var(--weight-regular) var(--text-sm)/1.4 var(--font-sans);
+ color: var(--ink-1); background: var(--paper-0);
+ border: var(--border-thin) solid var(--border-default);
+ border-radius: var(--radius-xs);
+ padding: 9px 34px 9px 12px; cursor: pointer;
+ transition: border-color var(--dur-fast) var(--ease-out), box-shadow var(--dur-fast) var(--ease-out);
+ }
+ .pg-select:hover { border-color: var(--ink-4); }
+ .pg-select:focus { outline: none; border-color: var(--green); box-shadow: 0 0 0 3px var(--green-wash); }
+ .pg-select:disabled { background: var(--paper-2); color: var(--ink-3); cursor: not-allowed; }
+ .pg-select-wrap__chevron {
+ position: absolute; right: 11px; pointer-events: none; color: var(--ink-3);
+ width: 16px; height: 16px;
+ }
+ `;
+ document.head.appendChild(el);
+}
+
+/**
+ * Select — native dropdown styled to the system, with a custom chevron.
+ */
+function Select({
+ label = null,
+ options = [],
+ placeholder = null,
+ id,
+ className = '',
+ children,
+ ...rest
+}) {
+ ensureStyles();
+ const selId = id || (label ? `pg-sel-${Math.random().toString(36).slice(2, 8)}` : undefined);
+ return /*#__PURE__*/React.createElement("div", {
+ className: "pg-select-field"
+ }, label ? /*#__PURE__*/React.createElement("label", {
+ className: "pg-select-field__label",
+ htmlFor: selId
+ }, label) : null, /*#__PURE__*/React.createElement("div", {
+ className: "pg-select-wrap"
+ }, /*#__PURE__*/React.createElement("select", _extends({
+ id: selId,
+ className: ['pg-select', className].filter(Boolean).join(' ')
+ }, rest), placeholder ? /*#__PURE__*/React.createElement("option", {
+ value: "",
+ disabled: true
+ }, placeholder) : null, children, options.map(o => {
+ const value = typeof o === 'string' ? o : o.value;
+ const optLabel = typeof o === 'string' ? o : o.label;
+ return /*#__PURE__*/React.createElement("option", {
+ key: value,
+ value: value
+ }, optLabel);
+ })), /*#__PURE__*/React.createElement("svg", {
+ className: "pg-select-wrap__chevron",
+ viewBox: "0 0 24 24",
+ fill: "none",
+ stroke: "currentColor",
+ strokeWidth: "2",
+ strokeLinecap: "round",
+ strokeLinejoin: "round"
+ }, /*#__PURE__*/React.createElement("path", {
+ d: "m6 9 6 6 6-6"
+ }))));
+}
+Object.assign(__ds_scope, { Select });
+})(); } catch (e) { __ds_ns.__errors.push({ path: "components/forms/Select.jsx", error: String((e && e.message) || e) }); }
+
+// components/forms/Switch.jsx
+try { (() => {
+function _extends() { return _extends = Object.assign ? Object.assign.bind() : function (n) { for (var e = 1; e < arguments.length; e++) { var t = arguments[e]; for (var r in t) ({}).hasOwnProperty.call(t, r) && (n[r] = t[r]); } return n; }, _extends.apply(null, arguments); }
+const STYLE_ID = 'pg-switch-css';
+function ensureStyles() {
+ if (typeof document === 'undefined' || document.getElementById(STYLE_ID)) return;
+ const el = document.createElement('style');
+ el.id = STYLE_ID;
+ el.textContent = `
+ .pg-switch { display: inline-flex; align-items: center; gap: 10px; cursor: pointer; user-select: none; }
+ .pg-switch input { position: absolute; opacity: 0; width: 0; height: 0; }
+ .pg-switch__track {
+ position: relative; flex: none; width: 38px; height: 22px;
+ background: var(--paper-3); border: var(--border-thin) solid var(--border-default);
+ border-radius: var(--radius-pill);
+ transition: background var(--dur-base) var(--ease-slide), border-color var(--dur-base) var(--ease-slide);
+ }
+ .pg-switch__thumb {
+ position: absolute; top: 2px; left: 2px; width: 16px; height: 16px;
+ background: var(--paper-0); border-radius: var(--radius-pill);
+ box-shadow: var(--shadow-sm);
+ transition: transform var(--dur-base) var(--ease-slide);
+ }
+ .pg-switch input:checked + .pg-switch__track { background: var(--green); border-color: var(--green-deep); }
+ .pg-switch input:checked + .pg-switch__track .pg-switch__thumb { transform: translateX(16px); }
+ .pg-switch input:focus-visible + .pg-switch__track { outline: var(--border-thick) solid var(--focus-ring); outline-offset: 2px; }
+ .pg-switch input:disabled + .pg-switch__track { opacity: 0.45; cursor: not-allowed; }
+ .pg-switch__label { font: var(--weight-regular) var(--text-sm)/1.3 var(--font-sans); color: var(--ink-1); }
+ `;
+ document.head.appendChild(el);
+}
+
+/**
+ * Switch — a toggle. The "on" state slides to instrument green.
+ */
+function Switch({
+ checked,
+ defaultChecked,
+ onChange,
+ disabled = false,
+ label = null,
+ id,
+ className = '',
+ ...rest
+}) {
+ ensureStyles();
+ const swId = id || `pg-sw-${Math.random().toString(36).slice(2, 8)}`;
+ return /*#__PURE__*/React.createElement("label", {
+ className: ['pg-switch', className].filter(Boolean).join(' '),
+ htmlFor: swId
+ }, /*#__PURE__*/React.createElement("input", _extends({
+ id: swId,
+ type: "checkbox",
+ checked: checked,
+ defaultChecked: defaultChecked,
+ onChange: onChange,
+ disabled: disabled
+ }, rest)), /*#__PURE__*/React.createElement("span", {
+ className: "pg-switch__track"
+ }, /*#__PURE__*/React.createElement("span", {
+ className: "pg-switch__thumb"
+ })), label ? /*#__PURE__*/React.createElement("span", {
+ className: "pg-switch__label"
+ }, label) : null);
+}
+Object.assign(__ds_scope, { Switch });
+})(); } catch (e) { __ds_ns.__errors.push({ path: "components/forms/Switch.jsx", error: String((e && e.message) || e) }); }
+
+// components/navigation/Tabs.jsx
+try { (() => {
+function _extends() { return _extends = Object.assign ? Object.assign.bind() : function (n) { for (var e = 1; e < arguments.length; e++) { var t = arguments[e]; for (var r in t) ({}).hasOwnProperty.call(t, r) && (n[r] = t[r]); } return n; }, _extends.apply(null, arguments); }
+const STYLE_ID = 'pg-tabs-css';
+function ensureStyles() {
+ if (typeof document === 'undefined' || document.getElementById(STYLE_ID)) return;
+ const el = document.createElement('style');
+ el.id = STYLE_ID;
+ el.textContent = `
+ .pg-tabs { display: flex; align-items: stretch; gap: 2px; border-bottom: var(--border-thin) solid var(--border-hairline); }
+ .pg-tabs--pill { border-bottom: 0; gap: 4px; background: var(--paper-2); padding: 3px; border-radius: var(--radius-sm); width: max-content; }
+ .pg-tab {
+ display: inline-flex; align-items: center; gap: 7px;
+ font: var(--weight-medium) var(--text-sm)/1 var(--font-sans);
+ color: var(--ink-3); background: none; border: 0; cursor: pointer;
+ padding: 10px 14px; position: relative; white-space: nowrap;
+ transition: color var(--dur-fast) var(--ease-out);
+ }
+ .pg-tab:hover { color: var(--ink-1); }
+ .pg-tab__icon { display: inline-flex; width: 15px; height: 15px; }
+ .pg-tab__icon svg { width: 100%; height: 100%; }
+ /* underline indicator — the engraved cursor line */
+ .pg-tabs:not(.pg-tabs--pill) .pg-tab::after {
+ content: ''; position: absolute; left: 8px; right: 8px; bottom: -1px; height: 2px;
+ background: var(--red); transform: scaleX(0); transform-origin: center;
+ transition: transform var(--dur-base) var(--ease-slide);
+ }
+ .pg-tabs:not(.pg-tabs--pill) .pg-tab[aria-selected="true"] { color: var(--ink-0); font-weight: var(--weight-semibold); }
+ .pg-tabs:not(.pg-tabs--pill) .pg-tab[aria-selected="true"]::after { transform: scaleX(1); }
+ .pg-tab:focus-visible { outline: var(--border-thick) solid var(--focus-ring); outline-offset: -2px; border-radius: var(--radius-xs); }
+ /* pill variant */
+ .pg-tabs--pill .pg-tab { border-radius: var(--radius-xs); padding: 7px 13px; font-family: var(--font-mono); font-size: var(--text-xs); }
+ .pg-tabs--pill .pg-tab[aria-selected="true"] { background: var(--paper-0); color: var(--ink-0); box-shadow: var(--shadow-xs); }
+ `;
+ document.head.appendChild(el);
+}
+
+/**
+ * Tabs — segmented navigation. Default variant draws the engraved red cursor
+ * underline; `pill` variant is a compact monospace switch (e.g. language picker).
+ */
+function Tabs({
+ tabs = [],
+ value,
+ onChange,
+ variant = 'underline',
+ className = '',
+ ...rest
+}) {
+ ensureStyles();
+ const cls = ['pg-tabs', variant === 'pill' ? 'pg-tabs--pill' : '', className].filter(Boolean).join(' ');
+ return /*#__PURE__*/React.createElement("div", _extends({
+ className: cls,
+ role: "tablist"
+ }, rest), tabs.map(t => {
+ const id = typeof t === 'string' ? t : t.id;
+ const label = typeof t === 'string' ? t : t.label;
+ const icon = typeof t === 'string' ? null : t.icon;
+ const selected = id === value;
+ return /*#__PURE__*/React.createElement("button", {
+ key: id,
+ role: "tab",
+ "aria-selected": selected,
+ className: "pg-tab",
+ onClick: () => onChange && onChange(id)
+ }, icon ? /*#__PURE__*/React.createElement("span", {
+ className: "pg-tab__icon"
+ }, icon) : null, label);
+ }));
+}
+Object.assign(__ds_scope, { Tabs });
+})(); } catch (e) { __ds_ns.__errors.push({ path: "components/navigation/Tabs.jsx", error: String((e && e.message) || e) }); }
+
+// ui_kits/docs/App.jsx
+try { (() => {
+// App — composes the docs shell: header, sidebar, content, on-this-page TOC.
+(function () {
+ const React = window.React;
+ const TOC = [{
+ id: 'example',
+ label: 'Example'
+ }, {
+ id: 'parameters',
+ label: 'Parameters'
+ }, {
+ id: 'returns',
+ label: 'Returns'
+ }];
+ function App() {
+ const [active, setActive] = React.useState('complete()');
+ const [menuOpen, setMenuOpen] = React.useState(false);
+ return /*#__PURE__*/React.createElement("div", {
+ className: "dk-app"
+ }, /*#__PURE__*/React.createElement(window.DocsHeader, {
+ onMenu: () => setMenuOpen(v => !v)
+ }), /*#__PURE__*/React.createElement("div", {
+ className: "dk-shell"
+ }, /*#__PURE__*/React.createElement(window.DocsSidebar, {
+ active: active,
+ onSelect: it => {
+ setActive(it);
+ setMenuOpen(false);
+ },
+ open: menuOpen
+ }), menuOpen ? /*#__PURE__*/React.createElement("div", {
+ className: "dk-scrim",
+ onClick: () => setMenuOpen(false)
+ }) : null, /*#__PURE__*/React.createElement("main", {
+ className: "dk-main"
+ }, /*#__PURE__*/React.createElement(window.ReferencePage, null)), /*#__PURE__*/React.createElement("aside", {
+ className: "dk-toc"
+ }, /*#__PURE__*/React.createElement("p", {
+ className: "dk-toc__title"
+ }, "On this page"), /*#__PURE__*/React.createElement("ul", {
+ className: "dk-toc__list"
+ }, TOC.map(t => /*#__PURE__*/React.createElement("li", {
+ key: t.id
+ }, /*#__PURE__*/React.createElement("a", {
+ href: '#' + t.id,
+ className: "dk-toc__link"
+ }, t.label)))), /*#__PURE__*/React.createElement("div", {
+ className: "panto-tick-rule",
+ style: {
+ margin: 'var(--space-5) 0 var(--space-4)'
+ }
+ }), /*#__PURE__*/React.createElement("a", {
+ className: "dk-edit",
+ href: "#"
+ }, "Edit this page \u2192"))));
+ }
+ window.DocsApp = App;
+})();
+})(); } catch (e) { __ds_ns.__errors.push({ path: "ui_kits/docs/App.jsx", error: String((e && e.message) || e) }); }
+
+// ui_kits/docs/DocsHeader.jsx
+try { (() => {
+// Lucide-derived inline icons (ISC) + DocsHeader. Icons live here (a JSX file)
+// so the design-system compiler treats them as kit-local, not a bundle module.
+(function () {
+ const React = window.React;
+ const S = (paths, extra = {}) => props => React.createElement('svg', Object.assign({
+ viewBox: '0 0 24 24',
+ fill: 'none',
+ stroke: 'currentColor',
+ strokeWidth: 2,
+ strokeLinecap: 'round',
+ strokeLinejoin: 'round',
+ width: '1em',
+ height: '1em'
+ }, extra, props), paths.map((d, i) => React.createElement('path', {
+ key: i,
+ d
+ })));
+ const M = (children, extra = {}) => props => React.createElement('svg', Object.assign({
+ viewBox: '0 0 24 24',
+ fill: 'none',
+ stroke: 'currentColor',
+ strokeWidth: 2,
+ strokeLinecap: 'round',
+ strokeLinejoin: 'round',
+ width: '1em',
+ height: '1em'
+ }, extra, props), children(React));
+ window.Icons = {
+ Search: M(R => [R.createElement('circle', {
+ key: 0,
+ cx: 11,
+ cy: 11,
+ r: 8
+ }), R.createElement('path', {
+ key: 1,
+ d: 'm21 21-4.3-4.3'
+ })]),
+ ChevronRight: S(['m9 18 6-6-6-6']),
+ Hash: S(['M4 9h16', 'M4 15h16', 'M10 3 8 21', 'M16 3l-2 18']),
+ Book: S(['M4 19.5v-15A2.5 2.5 0 0 1 6.5 2H20v20H6.5a2.5 2.5 0 0 1 0-5H20']),
+ Terminal: S(['m4 17 6-6-6-6', 'M12 19h8']),
+ Github: M(R => [R.createElement('path', {
+ key: 0,
+ d: 'M15 22v-4a4.8 4.8 0 0 0-1-3.5c3 0 6-2 6-5.5.08-1.25-.27-2.48-1-3.5.28-1.15.28-2.35 0-3.5 0 0-1 0-3 1.5-2.64-.5-5.36-.5-8 0C6 .5 5 .5 5 .5c-.3 1.15-.3 2.35 0 3.5A5.4 5.4 0 0 0 4 7.5c0 3.5 3 5.5 6 5.5-.39.49-.68 1.05-.85 1.65-.17.6-.22 1.23-.15 1.85v4'
+ }), R.createElement('path', {
+ key: 1,
+ d: 'M9 18c-4.51 2-5-2-7-2'
+ })]),
+ Zap: S(['M4 14a1 1 0 0 1-.78-1.63l9.9-10.2a.5.5 0 0 1 .86.46l-1.92 6.02A1 1 0 0 0 13 10h7a1 1 0 0 1 .78 1.63l-9.9 10.2a.5.5 0 0 1-.86-.46l1.92-6.02A1 1 0 0 0 11 14z']),
+ Boxes: S(['M2.97 12.92A2 2 0 0 0 2 14.63v3.24a2 2 0 0 0 .97 1.71l3 1.8a2 2 0 0 0 2.06 0L12 19v-5.5l-5-3-4.03 2.42Z', 'm7 16.5-4.74-2.85', 'm7 16.5 5-3', 'M7 16.5v5.17', 'M12 13.5V19l3.97 2.38a2 2 0 0 0 2.06 0l3-1.8a2 2 0 0 0 .97-1.71v-3.24a2 2 0 0 0-.97-1.71L17 10.5l-5 3Z', 'm17 16.5-5-3', 'm17 16.5 4.74-2.85', 'M17 16.5v5.17', 'M7.97 4.42A2 2 0 0 0 7 6.13v4.37l5 3 5-3V6.13a2 2 0 0 0-.97-1.71l-3-1.8a2 2 0 0 0-2.06 0l-3 1.8Z', 'M12 8 7.26 5.15', 'm12 8 4.74-2.85', 'M12 13.5V8']),
+ Layers: S(['m12.83 2.18a2 2 0 0 0-1.66 0L2.6 6.08a1 1 0 0 0 0 1.83l8.58 3.91a2 2 0 0 0 1.66 0l8.58-3.9a1 1 0 0 0 0-1.83Z', 'M2 12a1 1 0 0 0 .58.91l8.6 3.91a2 2 0 0 0 1.65 0l8.58-3.9A1 1 0 0 0 22 12', 'M2 17a1 1 0 0 0 .58.91l8.6 3.91a2 2 0 0 0 1.65 0l8.58-3.9A1 1 0 0 0 22 17']),
+ Menu: S(['M4 12h16', 'M4 6h16', 'M4 18h16']),
+ Check: S(['M20 6 9 17l-5-5']),
+ Compass: M(R => [R.createElement('path', {
+ key: 0,
+ d: 'm16.24 7.76-1.804 5.411a2 2 0 0 1-1.265 1.265L7.76 16.24l1.804-5.411a2 2 0 0 1 1.265-1.265z'
+ }), R.createElement('circle', {
+ key: 1,
+ cx: 12,
+ cy: 12,
+ r: 10
+ })])
+ };
+})();
+
+// DocsHeader — top bar: logo lockup, primary nav, search, github.
+(function () {
+ const React = window.React;
+ const {
+ Badge,
+ IconButton
+ } = window.PantographDesignSystem_56a1c7;
+ const I = window.Icons;
+ function DocsHeader({
+ onMenu
+ }) {
+ return /*#__PURE__*/React.createElement("header", {
+ className: "dk-header"
+ }, /*#__PURE__*/React.createElement("div", {
+ className: "dk-header__left"
+ }, /*#__PURE__*/React.createElement("button", {
+ className: "dk-menu",
+ "aria-label": "Menu",
+ onClick: onMenu
+ }, /*#__PURE__*/React.createElement(I.Menu, null)), /*#__PURE__*/React.createElement("a", {
+ className: "dk-brand",
+ href: "#"
+ }, /*#__PURE__*/React.createElement("img", {
+ src: "../../assets/logo-mark.svg",
+ width: "28",
+ height: "28",
+ alt: ""
+ }), /*#__PURE__*/React.createElement("span", {
+ className: "dk-brand__word"
+ }, "pantograph"), /*#__PURE__*/React.createElement("span", {
+ className: "dk-brand__div"
+ }, "/"), /*#__PURE__*/React.createElement("span", {
+ className: "dk-brand__sub"
+ }, "libpanto")), /*#__PURE__*/React.createElement(Badge, {
+ variant: "outline"
+ }, "v2.4")), /*#__PURE__*/React.createElement("nav", {
+ className: "dk-nav"
+ }, /*#__PURE__*/React.createElement("a", {
+ href: "#",
+ className: "dk-nav__link"
+ }, "Guides"), /*#__PURE__*/React.createElement("a", {
+ href: "#",
+ className: "dk-nav__link dk-nav__link--active"
+ }, "Reference"), /*#__PURE__*/React.createElement("a", {
+ href: "#",
+ className: "dk-nav__link"
+ }, "CLI"), /*#__PURE__*/React.createElement("a", {
+ href: "#",
+ className: "dk-nav__link"
+ }, "Changelog")), /*#__PURE__*/React.createElement("div", {
+ className: "dk-header__right"
+ }, /*#__PURE__*/React.createElement("label", {
+ className: "dk-search"
+ }, /*#__PURE__*/React.createElement("span", {
+ className: "dk-search__icon"
+ }, /*#__PURE__*/React.createElement(I.Search, null)), /*#__PURE__*/React.createElement("input", {
+ className: "dk-search__input",
+ placeholder: "Search the docs"
+ }), /*#__PURE__*/React.createElement("kbd", {
+ className: "dk-search__kbd"
+ }, "/")), /*#__PURE__*/React.createElement(IconButton, {
+ label: "GitHub",
+ bordered: true
+ }, /*#__PURE__*/React.createElement(I.Github, null))));
+ }
+ window.DocsHeader = DocsHeader;
+})();
+})(); } catch (e) { __ds_ns.__errors.push({ path: "ui_kits/docs/DocsHeader.jsx", error: String((e && e.message) || e) }); }
+
+// ui_kits/docs/DocsSidebar.jsx
+try { (() => {
+// DocsSidebar — grouped navigation tree.
+(function () {
+ const React = window.React;
+ const I = window.Icons;
+ const NAV = [{
+ group: 'Getting Started',
+ items: ['Installation', 'Quickstart', 'Authentication']
+ }, {
+ group: 'Guides',
+ items: ['Streaming', 'Tool use', 'Structured output', 'Retries & timeouts']
+ }, {
+ group: 'Reference',
+ items: ['Client', 'complete()', 'respond()', 'Response', 'Errors']
+ }, {
+ group: 'CLI · panto',
+ items: ['panto run', 'panto chat', 'Configuration']
+ }];
+ function DocsSidebar({
+ active,
+ onSelect,
+ open
+ }) {
+ return /*#__PURE__*/React.createElement("aside", {
+ className: 'dk-sidebar' + (open ? ' dk-sidebar--open' : '')
+ }, /*#__PURE__*/React.createElement("div", {
+ className: "dk-sidebar__scroll"
+ }, NAV.map(sec => /*#__PURE__*/React.createElement("div", {
+ className: "dk-navsec",
+ key: sec.group
+ }, /*#__PURE__*/React.createElement("p", {
+ className: "dk-navsec__title"
+ }, sec.group), /*#__PURE__*/React.createElement("ul", {
+ className: "dk-navsec__list"
+ }, sec.items.map(it => /*#__PURE__*/React.createElement("li", {
+ key: it
+ }, /*#__PURE__*/React.createElement("a", {
+ href: "#",
+ className: 'dk-navsec__link' + (it === active ? ' dk-navsec__link--active' : ''),
+ onClick: e => {
+ e.preventDefault();
+ onSelect(it);
+ }
+ }, it))))))));
+ }
+ window.DocsSidebar = DocsSidebar;
+})();
+})(); } catch (e) { __ds_ns.__errors.push({ path: "ui_kits/docs/DocsSidebar.jsx", error: String((e && e.message) || e) }); }
+
+// ui_kits/docs/ReferencePage.jsx
+try { (() => {
+// ReferencePage — the main documentation view for client.complete().
+(function () {
+ const React = window.React;
+ const {
+ Badge,
+ Tag,
+ Tabs,
+ CodeBlock,
+ Callout,
+ Card
+ } = window.PantographDesignSystem_56a1c7;
+ const I = window.Icons;
+ const SAMPLES = {
+ python: /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement("span", {
+ className: "tok-kw"
+ }, "import"), " panto", "\n\n", "client = panto.", /*#__PURE__*/React.createElement("span", {
+ className: "tok-fn"
+ }, "Client"), "()", "\n", "resp = client.", /*#__PURE__*/React.createElement("span", {
+ className: "tok-fn"
+ }, "complete"), "(", "\n", " ", "prompt=", /*#__PURE__*/React.createElement("span", {
+ className: "tok-str"
+ }, "\"explain this stack trace\""), ",", "\n", " ", "model=", /*#__PURE__*/React.createElement("span", {
+ className: "tok-str"
+ }, "\"claude-sonnet\""), ",", "\n", " ", "max_tokens=", /*#__PURE__*/React.createElement("span", {
+ className: "tok-num"
+ }, "1024"), ",", "\n", ")", "\n", /*#__PURE__*/React.createElement("span", {
+ className: "tok-kw"
+ }, "print"), "(resp.text)"),
+ rust: /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement("span", {
+ className: "tok-kw"
+ }, "use"), " panto::Client;", "\n\n", /*#__PURE__*/React.createElement("span", {
+ className: "tok-kw"
+ }, "let"), " client = Client::", /*#__PURE__*/React.createElement("span", {
+ className: "tok-fn"
+ }, "new"), "()?;", "\n", /*#__PURE__*/React.createElement("span", {
+ className: "tok-kw"
+ }, "let"), " resp = client", "\n", " ", ".", /*#__PURE__*/React.createElement("span", {
+ className: "tok-fn"
+ }, "complete"), "(", /*#__PURE__*/React.createElement("span", {
+ className: "tok-str"
+ }, "\"explain this stack trace\""), ")", "\n", " ", ".", /*#__PURE__*/React.createElement("span", {
+ className: "tok-fn"
+ }, "model"), "(", /*#__PURE__*/React.createElement("span", {
+ className: "tok-str"
+ }, "\"claude-sonnet\""), ")", "\n", " ", ".", /*#__PURE__*/React.createElement("span", {
+ className: "tok-fn"
+ }, "max_tokens"), "(", /*#__PURE__*/React.createElement("span", {
+ className: "tok-num"
+ }, "1024"), ")", "\n", " ", ".", /*#__PURE__*/React.createElement("span", {
+ className: "tok-fn"
+ }, "send"), "().", /*#__PURE__*/React.createElement("span", {
+ className: "tok-kw"
+ }, "await"), "?;", "\n", /*#__PURE__*/React.createElement("span", {
+ className: "tok-fn"
+ }, "println!"), "(", /*#__PURE__*/React.createElement("span", {
+ className: "tok-str"
+ }, "\"", `{}`, "\""), ", resp.text);"),
+ typescript: /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement("span", {
+ className: "tok-kw"
+ }, "import"), " ", "{ Client }", " ", /*#__PURE__*/React.createElement("span", {
+ className: "tok-kw"
+ }, "from"), " ", /*#__PURE__*/React.createElement("span", {
+ className: "tok-str"
+ }, "\"panto\""), ";", "\n\n", /*#__PURE__*/React.createElement("span", {
+ className: "tok-kw"
+ }, "const"), " client = ", /*#__PURE__*/React.createElement("span", {
+ className: "tok-kw"
+ }, "new"), " ", /*#__PURE__*/React.createElement("span", {
+ className: "tok-fn"
+ }, "Client"), "();", "\n", /*#__PURE__*/React.createElement("span", {
+ className: "tok-kw"
+ }, "const"), " resp = ", /*#__PURE__*/React.createElement("span", {
+ className: "tok-kw"
+ }, "await"), " client.", /*#__PURE__*/React.createElement("span", {
+ className: "tok-fn"
+ }, "complete"), "(", "{", "\n", " ", "prompt: ", /*#__PURE__*/React.createElement("span", {
+ className: "tok-str"
+ }, "\"explain this stack trace\""), ",", "\n", " ", "model: ", /*#__PURE__*/React.createElement("span", {
+ className: "tok-str"
+ }, "\"claude-sonnet\""), ",", "\n", " ", "maxTokens: ", /*#__PURE__*/React.createElement("span", {
+ className: "tok-num"
+ }, "1024"), ",", "\n", "});", "\n", "console.", /*#__PURE__*/React.createElement("span", {
+ className: "tok-fn"
+ }, "log"), "(resp.text);")
+ };
+ const FILE = {
+ python: 'main.py',
+ rust: 'src/main.rs',
+ typescript: 'index.ts'
+ };
+ const PARAMS = [['prompt', 'string', 'required', 'The input message sent to the model.'], ['model', 'string', '"claude-sonnet"', 'Provider-agnostic model alias. Resolved per provider.'], ['max_tokens', 'int', '1024', 'Upper bound on generated tokens.'], ['stream', 'bool', 'false', 'Return an async iterator of deltas instead of a full Response.'], ['tools', 'Tool[]', '[]', 'Tool definitions the model may call.']];
+ function ReferencePage() {
+ const [lang, setLang] = React.useState('python');
+ return /*#__PURE__*/React.createElement("article", {
+ className: "dk-doc"
+ }, /*#__PURE__*/React.createElement("div", {
+ className: "dk-crumbs"
+ }, /*#__PURE__*/React.createElement("span", null, "Reference"), /*#__PURE__*/React.createElement(I.ChevronRight, null), /*#__PURE__*/React.createElement("span", {
+ className: "dk-crumbs__here"
+ }, "complete()")), /*#__PURE__*/React.createElement("p", {
+ className: "panto-eyebrow"
+ }, "SDK Reference"), /*#__PURE__*/React.createElement("h1", {
+ className: "dk-doc__h1"
+ }, "client.complete()"), /*#__PURE__*/React.createElement("div", {
+ className: "dk-doc__meta"
+ }, /*#__PURE__*/React.createElement(Badge, {
+ variant: "success",
+ dot: true
+ }, "Stable"), /*#__PURE__*/React.createElement(Badge, {
+ variant: "neutral"
+ }, "since v2.0"), /*#__PURE__*/React.createElement(Tag, {
+ dot: "#3572A5"
+ }, "python"), /*#__PURE__*/React.createElement(Tag, {
+ dot: "#DEA584"
+ }, "rust"), /*#__PURE__*/React.createElement(Tag, {
+ dot: "#3178C6"
+ }, "typescript")), /*#__PURE__*/React.createElement("p", {
+ className: "dk-lead"
+ }, "Send a single prompt and receive one complete response. The same call signature is reproduced across every binding \u2014 write it once, scale it to any language."), /*#__PURE__*/React.createElement("h2", {
+ className: "dk-h2",
+ id: "example"
+ }, "Example"), /*#__PURE__*/React.createElement("div", {
+ className: "dk-langbar"
+ }, /*#__PURE__*/React.createElement(Tabs, {
+ variant: "pill",
+ value: lang,
+ onChange: setLang,
+ tabs: ['python', 'rust', 'typescript']
+ })), /*#__PURE__*/React.createElement(CodeBlock, {
+ filename: FILE[lang],
+ language: lang
+ }, SAMPLES[lang]), /*#__PURE__*/React.createElement(Callout, {
+ variant: "tip"
+ }, "Set ", /*#__PURE__*/React.createElement("code", null, "PANTO_KEY"), " in your environment once. Every binding \u2014 and the ", /*#__PURE__*/React.createElement("code", null, "panto"), " CLI \u2014 reads it automatically."), /*#__PURE__*/React.createElement("h2", {
+ className: "dk-h2",
+ id: "parameters"
+ }, "Parameters"), /*#__PURE__*/React.createElement("div", {
+ className: "dk-table-wrap"
+ }, /*#__PURE__*/React.createElement("table", {
+ className: "dk-table"
+ }, /*#__PURE__*/React.createElement("thead", null, /*#__PURE__*/React.createElement("tr", null, /*#__PURE__*/React.createElement("th", null, "Name"), /*#__PURE__*/React.createElement("th", null, "Type"), /*#__PURE__*/React.createElement("th", null, "Default"), /*#__PURE__*/React.createElement("th", null, "Description"))), /*#__PURE__*/React.createElement("tbody", null, PARAMS.map(p => /*#__PURE__*/React.createElement("tr", {
+ key: p[0]
+ }, /*#__PURE__*/React.createElement("td", null, /*#__PURE__*/React.createElement("code", null, p[0])), /*#__PURE__*/React.createElement("td", null, /*#__PURE__*/React.createElement("span", {
+ className: "dk-type"
+ }, p[1])), /*#__PURE__*/React.createElement("td", null, p[2] === 'required' ? /*#__PURE__*/React.createElement(Badge, {
+ variant: "danger"
+ }, "required") : /*#__PURE__*/React.createElement("code", {
+ className: "dk-default"
+ }, p[2])), /*#__PURE__*/React.createElement("td", null, p[3])))))), /*#__PURE__*/React.createElement("h2", {
+ className: "dk-h2",
+ id: "returns"
+ }, "Returns"), /*#__PURE__*/React.createElement("p", {
+ className: "dk-body"
+ }, "A ", /*#__PURE__*/React.createElement("a", {
+ href: "#"
+ }, "Response"), " object with ", /*#__PURE__*/React.createElement("code", null, ".text"), ", ", /*#__PURE__*/React.createElement("code", null, ".usage"), ", and ", /*#__PURE__*/React.createElement("code", null, ".stop_reason"), ". When ", /*#__PURE__*/React.createElement("code", null, "stream=true"), ", returns an async iterator yielding ", /*#__PURE__*/React.createElement("code", null, "Delta"), " objects instead."), /*#__PURE__*/React.createElement(Callout, {
+ variant: "danger",
+ title: "Deprecated argument"
+ }, "The ", /*#__PURE__*/React.createElement("code", null, "temperature"), " float is removed in v3 \u2014 pass a", /*#__PURE__*/React.createElement("code", null, "Sampling"), " config instead. See the migration guide."));
+ }
+ window.ReferencePage = ReferencePage;
+})();
+})(); } catch (e) { __ds_ns.__errors.push({ path: "ui_kits/docs/ReferencePage.jsx", error: String((e && e.message) || e) }); }
+
+// ui_kits/home/Features.jsx
+try { (() => {
+// Features — a grid of capability cards.
+(function () {
+ const React = window.React;
+ const {
+ Card,
+ Badge
+ } = window.PantographDesignSystem_56a1c7;
+ const I = window.Icons;
+ const FEATURES = [{
+ icon: /*#__PURE__*/React.createElement(I.Boxes, null),
+ title: 'Provider-agnostic',
+ body: 'One client targets OpenAI, Anthropic, or a local model. Swap providers with a string — no rewrites.'
+ }, {
+ icon: /*#__PURE__*/React.createElement(I.Zap, null),
+ title: 'Streaming & tools',
+ body: 'First-class async iterators for token streams and a typed tool-use loop across every binding.'
+ }, {
+ icon: /*#__PURE__*/React.createElement(I.Layers, null),
+ title: 'Typed responses',
+ body: 'complete() returns a structured Response — text, usage, stop reason — not a raw string.'
+ }, {
+ icon: /*#__PURE__*/React.createElement(I.Terminal, null),
+ title: 'The panto agent',
+ body: 'A terminal coding agent built on libpanto — a minimal system prompt and tool set you can swap out entirely. Written in Zig, extended in Lua.'
+ }];
+ function Features() {
+ return /*#__PURE__*/React.createElement("section", {
+ className: "hm-features"
+ }, /*#__PURE__*/React.createElement("div", {
+ className: "hm-section-head"
+ }, /*#__PURE__*/React.createElement("p", {
+ className: "panto-eyebrow"
+ }, "Why pantograph"), /*#__PURE__*/React.createElement("h2", {
+ className: "hm-h2"
+ }, "A small instrument, precisely built.")), /*#__PURE__*/React.createElement("div", {
+ className: "hm-feature-grid"
+ }, FEATURES.map(f => /*#__PURE__*/React.createElement(Card, {
+ key: f.title,
+ className: "hm-feature"
+ }, /*#__PURE__*/React.createElement("span", {
+ className: "hm-feature__icon"
+ }, f.icon), /*#__PURE__*/React.createElement("h3", {
+ className: "hm-feature__title"
+ }, f.title), /*#__PURE__*/React.createElement("p", {
+ className: "hm-feature__body"
+ }, f.body)))));
+ }
+ window.Features = Features;
+})();
+})(); } catch (e) { __ds_ns.__errors.push({ path: "ui_kits/home/Features.jsx", error: String((e && e.message) || e) }); }
+
+// ui_kits/home/Hero.jsx
+try { (() => {
+// Hero — headline, install command, CTAs, and a live-looking terminal.
+(function () {
+ const React = window.React;
+ const {
+ Button,
+ Badge,
+ Terminal
+ } = window.PantographDesignSystem_56a1c7;
+ const I = window.Icons;
+ function CopyCommand({
+ text
+ }) {
+ const [done, setDone] = React.useState(false);
+ return /*#__PURE__*/React.createElement("button", {
+ className: "hm-cmd",
+ onClick: () => {
+ if (navigator.clipboard) navigator.clipboard.writeText(text);
+ setDone(true);
+ setTimeout(() => setDone(false), 1400);
+ }
+ }, /*#__PURE__*/React.createElement("span", {
+ className: "hm-cmd__prompt"
+ }, "$"), /*#__PURE__*/React.createElement("span", {
+ className: "hm-cmd__text"
+ }, text), /*#__PURE__*/React.createElement("span", {
+ className: 'hm-cmd__copy' + (done ? ' hm-cmd__copy--done' : '')
+ }, done ? /*#__PURE__*/React.createElement(I.Check, null) : /*#__PURE__*/React.createElement(I.Compass, null)));
+ }
+ function Hero() {
+ return /*#__PURE__*/React.createElement("section", {
+ className: "hm-hero"
+ }, /*#__PURE__*/React.createElement("div", {
+ className: "hm-hero__copy"
+ }, /*#__PURE__*/React.createElement("p", {
+ className: "panto-eyebrow"
+ }, "Open source \xB7 MIT \xB7 v2.4"), /*#__PURE__*/React.createElement("h1", {
+ className: "hm-hero__h1"
+ }, "Scale one prompt", /*#__PURE__*/React.createElement("br", null), "to every provider."), /*#__PURE__*/React.createElement("p", {
+ className: "hm-hero__lead"
+ }, /*#__PURE__*/React.createElement("strong", null, "libpanto"), " is a single, small set of motions \u2014 faithfully reproduced across OpenAI, Anthropic, and your local models. ", /*#__PURE__*/React.createElement("strong", null, "panto"), " ", "is the terminal coding agent built on top."), /*#__PURE__*/React.createElement(CopyCommand, {
+ text: "pip install panto"
+ }), /*#__PURE__*/React.createElement("div", {
+ className: "hm-hero__cta"
+ }, /*#__PURE__*/React.createElement(Button, {
+ variant: "accent",
+ size: "lg",
+ icon: /*#__PURE__*/React.createElement(I.Terminal, null)
+ }, "Get started"), /*#__PURE__*/React.createElement(Button, {
+ variant: "secondary",
+ size: "lg",
+ icon: /*#__PURE__*/React.createElement(I.Github, null)
+ }, "Star on GitHub")), /*#__PURE__*/React.createElement("div", {
+ className: "hm-hero__langs"
+ }, /*#__PURE__*/React.createElement("span", {
+ className: "hm-hero__langlabel"
+ }, "Bindings"), /*#__PURE__*/React.createElement("span", {
+ className: "hm-langdot",
+ style: {
+ background: '#3572A5'
+ }
+ }), "python", /*#__PURE__*/React.createElement("span", {
+ className: "hm-langdot",
+ style: {
+ background: '#DEA584'
+ }
+ }), "rust", /*#__PURE__*/React.createElement("span", {
+ className: "hm-langdot",
+ style: {
+ background: '#3178C6'
+ }
+ }), "typescript", /*#__PURE__*/React.createElement("span", {
+ className: "hm-hero__more"
+ }, "+ 1 in beta"))), /*#__PURE__*/React.createElement("div", {
+ className: "hm-hero__demo"
+ }, /*#__PURE__*/React.createElement(Terminal, {
+ title: "panto \xB7 run",
+ cursor: true,
+ lines: [{
+ type: 'cmd',
+ shell: 'shell',
+ text: 'panto run "add a --json flag to the export command"'
+ }, {
+ type: 'com',
+ text: '• indexed 41 files · 3 relevant'
+ }, {
+ type: 'cmd',
+ shell: 'panto',
+ text: "I'll add a --json flag and route output through the serializer."
+ }, {
+ type: 'out',
+ text: ' cli/export.py +18 −2'
+ }, {
+ type: 'out',
+ text: ' cli/__init__.py +3'
+ }, {
+ type: 'ok',
+ text: '✓ applied · 2 files changed in 0.3s'
+ }]
+ })));
+ }
+ window.Hero = Hero;
+})();
+})(); } catch (e) { __ds_ns.__errors.push({ path: "ui_kits/home/Hero.jsx", error: String((e && e.message) || e) }); }
+
+// ui_kits/home/HomeApp.jsx
+try { (() => {
+// HomeApp — composes the marketing page + footer.
+(function () {
+ const React = window.React;
+ function Footer() {
+ const COLS = [{
+ h: 'SDK',
+ items: ['Installation', 'Reference', 'Streaming', 'Tool use']
+ }, {
+ h: 'panto CLI',
+ items: ['Quickstart', 'Commands', 'Configuration']
+ }, {
+ h: 'Project',
+ items: ['GitHub', 'Changelog', 'Roadmap', 'License (MIT)']
+ }];
+ return /*#__PURE__*/React.createElement("footer", {
+ className: "hm-footer"
+ }, /*#__PURE__*/React.createElement("div", {
+ className: "hm-footer__inner"
+ }, /*#__PURE__*/React.createElement("div", {
+ className: "hm-footer__brand"
+ }, /*#__PURE__*/React.createElement("div", {
+ className: "hm-brand"
+ }, /*#__PURE__*/React.createElement("img", {
+ src: "../../assets/logo-mark.svg",
+ width: "28",
+ height: "28",
+ alt: ""
+ }), /*#__PURE__*/React.createElement("span", {
+ className: "hm-brand__word"
+ }, "pantograph")), /*#__PURE__*/React.createElement("p", {
+ className: "hm-footer__tag"
+ }, "Scale one prompt to every provider.")), COLS.map(c => /*#__PURE__*/React.createElement("div", {
+ className: "hm-footer__col",
+ key: c.h
+ }, /*#__PURE__*/React.createElement("p", {
+ className: "hm-footer__h"
+ }, c.h), c.items.map(it => /*#__PURE__*/React.createElement("a", {
+ key: it,
+ href: "#",
+ className: "hm-footer__link"
+ }, it))))), /*#__PURE__*/React.createElement("div", {
+ className: "hm-footer__bar"
+ }, /*#__PURE__*/React.createElement("span", null, "\xA9 2026 the pantograph authors"), /*#__PURE__*/React.createElement("span", {
+ className: "hm-footer__mono"
+ }, "built with libpanto")));
+ }
+ function HomeApp() {
+ return /*#__PURE__*/React.createElement("div", {
+ className: "hm-app"
+ }, /*#__PURE__*/React.createElement(window.HomeHeader, null), /*#__PURE__*/React.createElement("main", null, /*#__PURE__*/React.createElement(window.Hero, null), /*#__PURE__*/React.createElement("div", {
+ className: "hm-ruler",
+ "aria-hidden": "true"
+ }), /*#__PURE__*/React.createElement(window.InstallSection, null), /*#__PURE__*/React.createElement(window.Features, null)), /*#__PURE__*/React.createElement(Footer, null));
+ }
+ window.HomeApp = HomeApp;
+})();
+})(); } catch (e) { __ds_ns.__errors.push({ path: "ui_kits/home/HomeApp.jsx", error: String((e && e.message) || e) }); }
+
+// ui_kits/home/HomeHeader.jsx
+try { (() => {
+// Lucide-derived inline icons (ISC) + HomeHeader. Icons live here (a JSX file)
+// so the design-system compiler treats them as kit-local, not a bundle module.
+(function () {
+ const React = window.React;
+ const S = (paths, extra = {}) => props => React.createElement('svg', Object.assign({
+ viewBox: '0 0 24 24',
+ fill: 'none',
+ stroke: 'currentColor',
+ strokeWidth: 2,
+ strokeLinecap: 'round',
+ strokeLinejoin: 'round',
+ width: '1em',
+ height: '1em'
+ }, extra, props), paths.map((d, i) => React.createElement('path', {
+ key: i,
+ d
+ })));
+ const M = (children, extra = {}) => props => React.createElement('svg', Object.assign({
+ viewBox: '0 0 24 24',
+ fill: 'none',
+ stroke: 'currentColor',
+ strokeWidth: 2,
+ strokeLinecap: 'round',
+ strokeLinejoin: 'round',
+ width: '1em',
+ height: '1em'
+ }, extra, props), children(React));
+ window.Icons = {
+ ChevronRight: S(['m9 18 6-6-6-6']),
+ Book: S(['M4 19.5v-15A2.5 2.5 0 0 1 6.5 2H20v20H6.5a2.5 2.5 0 0 1 0-5H20']),
+ Terminal: S(['m4 17 6-6-6-6', 'M12 19h8']),
+ Github: M(R => [R.createElement('path', {
+ key: 0,
+ d: 'M15 22v-4a4.8 4.8 0 0 0-1-3.5c3 0 6-2 6-5.5.08-1.25-.27-2.48-1-3.5.28-1.15.28-2.35 0-3.5 0 0-1 0-3 1.5-2.64-.5-5.36-.5-8 0C6 .5 5 .5 5 .5c-.3 1.15-.3 2.35 0 3.5A5.4 5.4 0 0 0 4 7.5c0 3.5 3 5.5 6 5.5-.39.49-.68 1.05-.85 1.65-.17.6-.22 1.23-.15 1.85v4'
+ }), R.createElement('path', {
+ key: 1,
+ d: 'M9 18c-4.51 2-5-2-7-2'
+ })]),
+ Zap: S(['M4 14a1 1 0 0 1-.78-1.63l9.9-10.2a.5.5 0 0 1 .86.46l-1.92 6.02A1 1 0 0 0 13 10h7a1 1 0 0 1 .78 1.63l-9.9 10.2a.5.5 0 0 1-.86-.46l1.92-6.02A1 1 0 0 0 11 14z']),
+ Boxes: S(['M2.97 12.92A2 2 0 0 0 2 14.63v3.24a2 2 0 0 0 .97 1.71l3 1.8a2 2 0 0 0 2.06 0L12 19v-5.5l-5-3-4.03 2.42Z', 'm7 16.5-4.74-2.85', 'm7 16.5 5-3', 'M7 16.5v5.17', 'M12 13.5V19l3.97 2.38a2 2 0 0 0 2.06 0l3-1.8a2 2 0 0 0 .97-1.71v-3.24a2 2 0 0 0-.97-1.71L17 10.5l-5 3Z', 'm17 16.5-5-3', 'm17 16.5 4.74-2.85', 'M17 16.5v5.17', 'M7.97 4.42A2 2 0 0 0 7 6.13v4.37l5 3 5-3V6.13a2 2 0 0 0-.97-1.71l-3-1.8a2 2 0 0 0-2.06 0l-3 1.8Z', 'M12 8 7.26 5.15', 'm12 8 4.74-2.85', 'M12 13.5V8']),
+ Layers: S(['m12.83 2.18a2 2 0 0 0-1.66 0L2.6 6.08a1 1 0 0 0 0 1.83l8.58 3.91a2 2 0 0 0 1.66 0l8.58-3.9a1 1 0 0 0 0-1.83Z', 'M2 12a1 1 0 0 0 .58.91l8.6 3.91a2 2 0 0 0 1.65 0l8.58-3.9A1 1 0 0 0 22 12', 'M2 17a1 1 0 0 0 .58.91l8.6 3.91a2 2 0 0 0 1.65 0l8.58-3.9A1 1 0 0 0 22 17']),
+ Check: S(['M20 6 9 17l-5-5']),
+ Compass: M(R => [R.createElement('path', {
+ key: 0,
+ d: 'm16.24 7.76-1.804 5.411a2 2 0 0 1-1.265 1.265L7.76 16.24l1.804-5.411a2 2 0 0 1 1.265-1.265z'
+ }), R.createElement('circle', {
+ key: 1,
+ cx: 12,
+ cy: 12,
+ r: 10
+ })])
+ };
+})();
+
+// HomeHeader — marketing top bar.
+(function () {
+ const React = window.React;
+ const {
+ Button
+ } = window.PantographDesignSystem_56a1c7;
+ const I = window.Icons;
+ function HomeHeader() {
+ return /*#__PURE__*/React.createElement("header", {
+ className: "hm-header"
+ }, /*#__PURE__*/React.createElement("a", {
+ className: "hm-brand",
+ href: "#"
+ }, /*#__PURE__*/React.createElement("img", {
+ src: "../../assets/logo-mark.svg",
+ width: "30",
+ height: "30",
+ alt: ""
+ }), /*#__PURE__*/React.createElement("span", {
+ className: "hm-brand__word"
+ }, "pantograph")), /*#__PURE__*/React.createElement("nav", {
+ className: "hm-nav"
+ }, /*#__PURE__*/React.createElement("a", {
+ href: "#",
+ className: "hm-nav__link"
+ }, "Docs"), /*#__PURE__*/React.createElement("a", {
+ href: "#",
+ className: "hm-nav__link"
+ }, "SDK"), /*#__PURE__*/React.createElement("a", {
+ href: "#",
+ className: "hm-nav__link"
+ }, "CLI"), /*#__PURE__*/React.createElement("a", {
+ href: "#",
+ className: "hm-nav__link"
+ }, "Blog")), /*#__PURE__*/React.createElement("div", {
+ className: "hm-header__right"
+ }, /*#__PURE__*/React.createElement("a", {
+ href: "#",
+ className: "hm-stars"
+ }, /*#__PURE__*/React.createElement(I.Github, null), " 14.2k"), /*#__PURE__*/React.createElement(Button, {
+ variant: "primary",
+ size: "sm",
+ icon: /*#__PURE__*/React.createElement(I.Book, null)
+ }, "Read the docs")));
+ }
+ window.HomeHeader = HomeHeader;
+})();
+})(); } catch (e) { __ds_ns.__errors.push({ path: "ui_kits/home/HomeHeader.jsx", error: String((e && e.message) || e) }); }
+
+// ui_kits/home/InstallSection.jsx
+try { (() => {
+// InstallSection — "same call, every language" with a language switcher.
+(function () {
+ const React = window.React;
+ const {
+ Tabs,
+ CodeBlock
+ } = window.PantographDesignSystem_56a1c7;
+ const INSTALL = {
+ python: 'pip install panto',
+ rust: 'cargo add panto',
+ typescript: 'npm install panto'
+ };
+ const FILE = {
+ python: 'main.py',
+ rust: 'src/main.rs',
+ typescript: 'index.ts'
+ };
+ const CODE = {
+ python: /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement("span", {
+ className: "tok-kw"
+ }, "import"), " panto", "\n\n", "resp = panto.", /*#__PURE__*/React.createElement("span", {
+ className: "tok-fn"
+ }, "Client"), "().", /*#__PURE__*/React.createElement("span", {
+ className: "tok-fn"
+ }, "complete"), "(", /*#__PURE__*/React.createElement("span", {
+ className: "tok-str"
+ }, "\"name three uses for a pantograph\""), ")", "\n", /*#__PURE__*/React.createElement("span", {
+ className: "tok-kw"
+ }, "print"), "(resp.text)"),
+ rust: /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement("span", {
+ className: "tok-kw"
+ }, "use"), " panto::Client;", "\n\n", /*#__PURE__*/React.createElement("span", {
+ className: "tok-kw"
+ }, "let"), " resp = Client::", /*#__PURE__*/React.createElement("span", {
+ className: "tok-fn"
+ }, "new"), "()?", "\n", " ", ".", /*#__PURE__*/React.createElement("span", {
+ className: "tok-fn"
+ }, "complete"), "(", /*#__PURE__*/React.createElement("span", {
+ className: "tok-str"
+ }, "\"name three uses for a pantograph\""), ")", "\n", " ", ".", /*#__PURE__*/React.createElement("span", {
+ className: "tok-fn"
+ }, "send"), "().", /*#__PURE__*/React.createElement("span", {
+ className: "tok-kw"
+ }, "await"), "?;", "\n", /*#__PURE__*/React.createElement("span", {
+ className: "tok-fn"
+ }, "println!"), "(", /*#__PURE__*/React.createElement("span", {
+ className: "tok-str"
+ }, "\"", `{}`, "\""), ", resp.text);"),
+ typescript: /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement("span", {
+ className: "tok-kw"
+ }, "import"), " ", "{ Client }", " ", /*#__PURE__*/React.createElement("span", {
+ className: "tok-kw"
+ }, "from"), " ", /*#__PURE__*/React.createElement("span", {
+ className: "tok-str"
+ }, "\"panto\""), ";", "\n\n", /*#__PURE__*/React.createElement("span", {
+ className: "tok-kw"
+ }, "const"), " resp = ", /*#__PURE__*/React.createElement("span", {
+ className: "tok-kw"
+ }, "await"), " ", /*#__PURE__*/React.createElement("span", {
+ className: "tok-kw"
+ }, "new"), " ", /*#__PURE__*/React.createElement("span", {
+ className: "tok-fn"
+ }, "Client"), "()", "\n", " ", ".", /*#__PURE__*/React.createElement("span", {
+ className: "tok-fn"
+ }, "complete"), "(", /*#__PURE__*/React.createElement("span", {
+ className: "tok-str"
+ }, "\"name three uses for a pantograph\""), ");", "\n", "console.", /*#__PURE__*/React.createElement("span", {
+ className: "tok-fn"
+ }, "log"), "(resp.text);")
+ };
+ function InstallSection() {
+ const [lang, setLang] = React.useState('python');
+ return /*#__PURE__*/React.createElement("section", {
+ className: "hm-install"
+ }, /*#__PURE__*/React.createElement("div", {
+ className: "hm-section-head"
+ }, /*#__PURE__*/React.createElement("p", {
+ className: "panto-eyebrow"
+ }, "The SDK"), /*#__PURE__*/React.createElement("h2", {
+ className: "hm-h2"
+ }, "Same call, every language."), /*#__PURE__*/React.createElement("p", {
+ className: "hm-section-sub"
+ }, "The signature doesn't change when the language does. Learn it once.")), /*#__PURE__*/React.createElement("div", {
+ className: "hm-install__panel"
+ }, /*#__PURE__*/React.createElement("div", {
+ className: "hm-install__tabs"
+ }, /*#__PURE__*/React.createElement(Tabs, {
+ variant: "pill",
+ value: lang,
+ onChange: setLang,
+ tabs: ['python', 'rust', 'typescript']
+ })), /*#__PURE__*/React.createElement(CodeBlock, {
+ filename: "install",
+ language: "shell",
+ showBar: true,
+ code: INSTALL[lang]
+ }), /*#__PURE__*/React.createElement(CodeBlock, {
+ filename: FILE[lang],
+ language: lang
+ }, CODE[lang])));
+ }
+ window.InstallSection = InstallSection;
+})();
+})(); } catch (e) { __ds_ns.__errors.push({ path: "ui_kits/home/InstallSection.jsx", error: String((e && e.message) || e) }); }
+
+__ds_ns.Callout = __ds_scope.Callout;
+
+__ds_ns.CodeBlock = __ds_scope.CodeBlock;
+
+__ds_ns.Terminal = __ds_scope.Terminal;
+
+__ds_ns.Badge = __ds_scope.Badge;
+
+__ds_ns.Button = __ds_scope.Button;
+
+__ds_ns.Card = __ds_scope.Card;
+
+__ds_ns.IconButton = __ds_scope.IconButton;
+
+__ds_ns.Tag = __ds_scope.Tag;
+
+__ds_ns.Input = __ds_scope.Input;
+
+__ds_ns.Select = __ds_scope.Select;
+
+__ds_ns.Switch = __ds_scope.Switch;
+
+__ds_ns.Tabs = __ds_scope.Tabs;
+
+})();
diff --git a/pantograph/ds/pantograph-design-system-56a1c74e-41c1-486b-ba3c-4eabdfacef31/_ds_manifest.json b/pantograph/ds/pantograph-design-system-56a1c74e-41c1-486b-ba3c-4eabdfacef31/_ds_manifest.json
new file mode 100644
index 0000000..df50d41
--- /dev/null
+++ b/pantograph/ds/pantograph-design-system-56a1c74e-41c1-486b-ba3c-4eabdfacef31/_ds_manifest.json
@@ -0,0 +1 @@
+{"namespace":"PantographDesignSystem_56a1c7","components":[{"name":"Callout","sourcePath":"components/code/Callout.jsx"},{"name":"CodeBlock","sourcePath":"components/code/CodeBlock.jsx"},{"name":"Terminal","sourcePath":"components/code/Terminal.jsx"},{"name":"Badge","sourcePath":"components/core/Badge.jsx"},{"name":"Button","sourcePath":"components/core/Button.jsx"},{"name":"Card","sourcePath":"components/core/Card.jsx"},{"name":"IconButton","sourcePath":"components/core/IconButton.jsx"},{"name":"Tag","sourcePath":"components/core/Tag.jsx"},{"name":"Input","sourcePath":"components/forms/Input.jsx"},{"name":"Select","sourcePath":"components/forms/Select.jsx"},{"name":"Switch","sourcePath":"components/forms/Switch.jsx"},{"name":"Tabs","sourcePath":"components/navigation/Tabs.jsx"}],"startingPoints":[{"name":"Button","path":"components/core/Button.jsx","previewPath":"components/core/core.card.html","kind":"component","section":"Core","subtitle":"Buttons in every variant & size","viewport":"700x220"},{"name":"Card","path":"components/core/Card.jsx","previewPath":"components/core/core.card.html","kind":"component","section":"Core","subtitle":"Surface container with header/body/footer","viewport":"700x320"},{"name":"docs","path":"ui_kits/docs/index.html","previewPath":"ui_kits/docs/index.html","kind":"screen","section":"Docs","subtitle":"libpanto SDK documentation site","viewport":"1280x800"},{"name":"home","path":"ui_kits/home/index.html","previewPath":"ui_kits/home/index.html","kind":"screen","section":"Marketing","subtitle":"pantograph project homepage","viewport":"1280x860"}],"cards":[{"path":"guidelines/brand-logo-variants.card.html","group":"Brand","viewport":"700x160","subtitle":"On paper, on ink, and the monochrome mark. Keep one mark of clearspace.","name":"Logo — variants & placement"},{"path":"guidelines/brand-logo.card.html","group":"Brand","viewport":"700x200","subtitle":"A parallelogram linkage: anchored fulcrum, scaling axis, red pen tip.","name":"Logo — the pantograph mark"},{"path":"guidelines/brand-voice.card.html","group":"Brand","viewport":"700x210","subtitle":"Light, straightforward, fast, minimal. Everything you specify, nothing you don't.","name":"Voice & tone"},{"path":"guidelines/color-accents.card.html","group":"Colors","viewport":"700x190","subtitle":"The only two accents. Used like a draftsman: sparingly, for meaning.","name":"Instrument accents — red & green"},{"path":"guidelines/color-ink.card.html","group":"Colors","viewport":"700x150","subtitle":"Text ramp. The darkest is a blue-black, never pure black.","name":"Ink — deep blue-black marks"},{"path":"guidelines/color-paper.card.html","group":"Colors","viewport":"700x150","subtitle":"Faint warm tint, close to white. No pure #FFF, no yellow cast.","name":"Paper — near-white surfaces"},{"path":"guidelines/color-status.card.html","group":"Colors","viewport":"700x150","subtitle":"Semantic aliases — reference these, not the raw ramp.","name":"Status & surfaces"},{"path":"components/code/code.card.html","group":"Components","viewport":"700x520","subtitle":"The documentation & CLI surfaces.","name":"Code — CodeBlock, Terminal, Callout"},{"path":"components/core/core.card.html","group":"Components","viewport":"700x430","subtitle":"The instrument-palette primitives.","name":"Core — Button, IconButton, Badge, Tag, Card"},{"path":"components/forms/forms.card.html","group":"Components","viewport":"700x340","subtitle":"Green focus ring, red invalid state, engraved labels.","name":"Forms — Input, Select, Switch"},{"path":"components/navigation/navigation.card.html","group":"Components","viewport":"700x200","subtitle":"Engraved red cursor underline, plus a compact pill switch.","name":"Navigation — Tabs"},{"path":"ui_kits/docs/index.html","group":"libpanto Docs","viewport":"1280x800","subtitle":"Header, nav tree, language-switching reference page, TOC.","name":"SDK reference site"},{"path":"ui_kits/home/index.html","group":"panto Home","viewport":"1280x860","subtitle":"Hero with live terminal, language-switching install, feature grid.","name":"Project homepage"},{"path":"guidelines/space-borders.card.html","group":"Spacing","viewport":"700x190","subtitle":"Hairlines, and the engraved tick rule — the standard section separator.","name":"Borders, ticks & rules"},{"path":"guidelines/space-radius.card.html","group":"Spacing","viewport":"700x140","subtitle":"Barely rounded. The instrument body has sharp, machined corners.","name":"Radius — machined edges"},{"path":"guidelines/space-scale.card.html","group":"Spacing","viewport":"700x150","subtitle":"4px base unit. Rigid and gridded, like engraved ticks.","name":"Spacing scale"},{"path":"guidelines/space-shadow.card.html","group":"Spacing","viewport":"700x160","subtitle":"Warm, low, ink-tinted. Like an object resting on a drafting table.","name":"Shadows — ink on paper"},{"path":"guidelines/type-display.card.html","group":"Type","viewport":"700x160","subtitle":"The primary face at display scale. Tall, narrow, engraved character.","name":"Display — Saira Condensed"},{"path":"guidelines/type-eyebrow.card.html","group":"Type","viewport":"700x130","subtitle":"Saira Condensed, caps, +0.14em tracking. The slide-rule scale label.","name":"Engraved label / eyebrow"},{"path":"guidelines/type-mono.card.html","group":"Type","viewport":"700x170","subtitle":"Code, terminals, the panto CLI. The machine voice.","name":"Mono — IBM Plex Mono"},{"path":"guidelines/type-prose.card.html","group":"Type","viewport":"700x300","subtitle":"The primary face carries running body copy too. A full paragraph, set for reading.","name":"Prose — Saira Condensed"},{"path":"guidelines/type-sans.card.html","group":"Type","viewport":"700x190","subtitle":"The primary face also runs the interface: headings, buttons, labels, table data, short copy.","name":"UI & body — Saira Condensed"},{"path":"guidelines/type-scale.card.html","group":"Type","viewport":"700x230","subtitle":"1.250 major-third ramp, rooted at 16px.","name":"Type scale"}],"templates":[],"globalCssPaths":["tokens/fonts.css","tokens/colors.css","tokens/typography.css","tokens/spacing.css","tokens/base.css","styles.css"],"tokens":[{"name":"--paper-0","value":"#FCFAF5","kind":"color","definedIn":"tokens/colors.css"},{"name":"--paper-1","value":"#F8F4EB","kind":"color","definedIn":"tokens/colors.css"},{"name":"--paper-2","value":"#EFE9DC","kind":"color","definedIn":"tokens/colors.css"},{"name":"--paper-3","value":"#DFD7C6","kind":"color","definedIn":"tokens/colors.css"},{"name":"--paper-4","value":"#C7BCA4","kind":"color","definedIn":"tokens/colors.css"},{"name":"--ink-0","value":"#161B27","kind":"color","definedIn":"tokens/colors.css"},{"name":"--ink-1","value":"#1E2533","kind":"color","definedIn":"tokens/colors.css"},{"name":"--ink-2","value":"#3B4456","kind":"color","definedIn":"tokens/colors.css"},{"name":"--ink-3","value":"#6E6B5C","kind":"color","definedIn":"tokens/colors.css"},{"name":"--ink-4","value":"#8C8470","kind":"color","definedIn":"tokens/colors.css"},{"name":"--red-deep","value":"#8F2B22","kind":"color","definedIn":"tokens/colors.css"},{"name":"--red","value":"#B23A2E","kind":"color","definedIn":"tokens/colors.css"},{"name":"--red-soft","value":"#E7C3BB","kind":"color","definedIn":"tokens/colors.css"},{"name":"--red-wash","value":"#EBD9D0","kind":"color","definedIn":"tokens/colors.css"},{"name":"--green-deep","value":"#245138","kind":"color","definedIn":"tokens/colors.css"},{"name":"--green","value":"#2F6B4A","kind":"color","definedIn":"tokens/colors.css"},{"name":"--green-soft","value":"#BCD2BF","kind":"color","definedIn":"tokens/colors.css"},{"name":"--green-wash","value":"#D6DECB","kind":"color","definedIn":"tokens/colors.css"},{"name":"--brass","value":"#9A7B3F","kind":"color","definedIn":"tokens/colors.css"},{"name":"--surface-page","value":"var(--paper-1)","kind":"color","definedIn":"tokens/colors.css"},{"name":"--surface-card","value":"var(--paper-0)","kind":"color","definedIn":"tokens/colors.css"},{"name":"--surface-sunken","value":"var(--paper-2)","kind":"color","definedIn":"tokens/colors.css"},{"name":"--surface-code","value":"#F4EEE1","kind":"color","definedIn":"tokens/colors.css"},{"name":"--surface-inverse","value":"var(--ink-1)","kind":"color","definedIn":"tokens/colors.css"},{"name":"--text-strong","value":"var(--ink-0)","kind":"font","definedIn":"tokens/colors.css"},{"name":"--text-body","value":"var(--ink-1)","kind":"font","definedIn":"tokens/colors.css"},{"name":"--text-muted","value":"var(--ink-2)","kind":"font","definedIn":"tokens/colors.css"},{"name":"--text-faint","value":"var(--ink-3)","kind":"font","definedIn":"tokens/colors.css"},{"name":"--text-placeholder","value":"var(--ink-4)","kind":"font","definedIn":"tokens/colors.css"},{"name":"--text-on-ink","value":"var(--paper-0)","kind":"font","definedIn":"tokens/colors.css"},{"name":"--text-on-accent","value":"var(--paper-0)","kind":"font","definedIn":"tokens/colors.css"},{"name":"--border-hairline","value":"var(--paper-3)","kind":"color","definedIn":"tokens/colors.css"},{"name":"--border-default","value":"var(--paper-4)","kind":"color","definedIn":"tokens/colors.css"},{"name":"--border-strong","value":"var(--ink-3)","kind":"color","definedIn":"tokens/colors.css"},{"name":"--rule-tick","value":"var(--paper-4)","kind":"color","definedIn":"tokens/colors.css"},{"name":"--accent","value":"var(--red)","kind":"color","definedIn":"tokens/colors.css"},{"name":"--accent-deep","value":"var(--red-deep)","kind":"color","definedIn":"tokens/colors.css"},{"name":"--link","value":"var(--red-deep)","kind":"color","definedIn":"tokens/colors.css"},{"name":"--link-hover","value":"var(--red)","kind":"color","definedIn":"tokens/colors.css"},{"name":"--status-danger","value":"var(--red)","kind":"color","definedIn":"tokens/colors.css"},{"name":"--status-danger-bg","value":"var(--red-wash)","kind":"color","definedIn":"tokens/colors.css"},{"name":"--status-success","value":"var(--green)","kind":"color","definedIn":"tokens/colors.css"},{"name":"--status-success-bg","value":"var(--green-wash)","kind":"color","definedIn":"tokens/colors.css"},{"name":"--status-info","value":"var(--ink-2)","kind":"color","definedIn":"tokens/colors.css"},{"name":"--status-info-bg","value":"var(--paper-2)","kind":"color","definedIn":"tokens/colors.css"},{"name":"--focus-ring","value":"var(--green)","kind":"color","definedIn":"tokens/colors.css"},{"name":"--font-primary","value":"'Saira Condensed', 'Arial Narrow', sans-serif","kind":"font","definedIn":"tokens/typography.css"},{"name":"--font-mono","value":"'IBM Plex Mono', ui-monospace, 'SF Mono', Menlo, monospace","kind":"font","definedIn":"tokens/typography.css"},{"name":"--font-display","value":"var(--font-primary)","kind":"font","definedIn":"tokens/typography.css"},{"name":"--font-sans","value":"var(--font-primary)","kind":"font","definedIn":"tokens/typography.css"},{"name":"--font-serif","value":"var(--font-primary)","kind":"font","definedIn":"tokens/typography.css"},{"name":"--text-2xs","value":"0.6875rem","kind":"font","definedIn":"tokens/typography.css"},{"name":"--text-xs","value":"0.75rem","kind":"font","definedIn":"tokens/typography.css"},{"name":"--text-sm","value":"0.875rem","kind":"font","definedIn":"tokens/typography.css"},{"name":"--text-base","value":"1rem","kind":"font","definedIn":"tokens/typography.css"},{"name":"--text-md","value":"1.125rem","kind":"font","definedIn":"tokens/typography.css"},{"name":"--text-lg","value":"1.375rem","kind":"font","definedIn":"tokens/typography.css"},{"name":"--text-xl","value":"1.75rem","kind":"font","definedIn":"tokens/typography.css"},{"name":"--text-2xl","value":"2.25rem","kind":"font","definedIn":"tokens/typography.css"},{"name":"--text-3xl","value":"3rem","kind":"font","definedIn":"tokens/typography.css"},{"name":"--text-4xl","value":"4rem","kind":"font","definedIn":"tokens/typography.css"},{"name":"--text-5xl","value":"5.5rem","kind":"font","definedIn":"tokens/typography.css"},{"name":"--weight-regular","value":"400","kind":"font","definedIn":"tokens/typography.css"},{"name":"--weight-medium","value":"500","kind":"font","definedIn":"tokens/typography.css"},{"name":"--weight-semibold","value":"600","kind":"font","definedIn":"tokens/typography.css"},{"name":"--weight-bold","value":"700","kind":"font","definedIn":"tokens/typography.css"},{"name":"--leading-tight","value":"1.05","kind":"font","definedIn":"tokens/typography.css"},{"name":"--leading-snug","value":"1.25","kind":"font","definedIn":"tokens/typography.css"},{"name":"--leading-normal","value":"1.5","kind":"font","definedIn":"tokens/typography.css"},{"name":"--leading-relaxed","value":"1.7","kind":"font","definedIn":"tokens/typography.css"},{"name":"--leading-mono","value":"1.6","kind":"font","definedIn":"tokens/typography.css"},{"name":"--tracking-label","value":"0.14em","kind":"font","definedIn":"tokens/typography.css"},{"name":"--tracking-tight","value":"-0.01em","kind":"font","definedIn":"tokens/typography.css"},{"name":"--tracking-normal","value":"0","kind":"font","definedIn":"tokens/typography.css"},{"name":"--role-display","value":"var(--weight-bold) var(--text-4xl)/var(--leading-tight) var(--font-display)","kind":"color","definedIn":"tokens/typography.css"},{"name":"--role-h1","value":"var(--weight-bold) var(--text-3xl)/var(--leading-snug) var(--font-display)","kind":"color","definedIn":"tokens/typography.css"},{"name":"--role-h2","value":"var(--weight-semibold) var(--text-2xl)/var(--leading-snug) var(--font-sans)","kind":"color","definedIn":"tokens/typography.css"},{"name":"--role-h3","value":"var(--weight-semibold) var(--text-xl)/var(--leading-snug) var(--font-sans)","kind":"color","definedIn":"tokens/typography.css"},{"name":"--role-body","value":"var(--weight-regular) var(--text-base)/var(--leading-normal) var(--font-sans)","kind":"color","definedIn":"tokens/typography.css"},{"name":"--role-prose","value":"var(--weight-regular) var(--text-md)/var(--leading-relaxed) var(--font-primary)","kind":"color","definedIn":"tokens/typography.css"},{"name":"--role-code","value":"var(--weight-regular) var(--text-sm)/var(--leading-mono) var(--font-mono)","kind":"color","definedIn":"tokens/typography.css"},{"name":"--role-label","value":"var(--weight-semibold) var(--text-xs)/1 var(--font-display)","kind":"color","definedIn":"tokens/typography.css"},{"name":"--space-0","value":"0","kind":"spacing","definedIn":"tokens/spacing.css"},{"name":"--space-1","value":"0.25rem","kind":"spacing","definedIn":"tokens/spacing.css"},{"name":"--space-2","value":"0.5rem","kind":"spacing","definedIn":"tokens/spacing.css"},{"name":"--space-3","value":"0.75rem","kind":"spacing","definedIn":"tokens/spacing.css"},{"name":"--space-4","value":"1rem","kind":"spacing","definedIn":"tokens/spacing.css"},{"name":"--space-5","value":"1.5rem","kind":"spacing","definedIn":"tokens/spacing.css"},{"name":"--space-6","value":"2rem","kind":"spacing","definedIn":"tokens/spacing.css"},{"name":"--space-7","value":"3rem","kind":"spacing","definedIn":"tokens/spacing.css"},{"name":"--space-8","value":"4rem","kind":"spacing","definedIn":"tokens/spacing.css"},{"name":"--space-9","value":"6rem","kind":"spacing","definedIn":"tokens/spacing.css"},{"name":"--radius-none","value":"0","kind":"radius","definedIn":"tokens/spacing.css"},{"name":"--radius-xs","value":"2px","kind":"radius","definedIn":"tokens/spacing.css"},{"name":"--radius-sm","value":"3px","kind":"radius","definedIn":"tokens/spacing.css"},{"name":"--radius-md","value":"5px","kind":"radius","definedIn":"tokens/spacing.css"},{"name":"--radius-lg","value":"8px","kind":"radius","definedIn":"tokens/spacing.css"},{"name":"--radius-pill","value":"999px","kind":"radius","definedIn":"tokens/spacing.css"},{"name":"--border-thin","value":"1px","kind":"spacing","definedIn":"tokens/spacing.css"},{"name":"--border-medium","value":"1.5px","kind":"spacing","definedIn":"tokens/spacing.css"},{"name":"--border-thick","value":"2px","kind":"spacing","definedIn":"tokens/spacing.css"},{"name":"--shadow-xs","value":"0 1px 0 rgba(30, 37, 51, 0.06)","kind":"shadow","definedIn":"tokens/spacing.css"},{"name":"--shadow-sm","value":"0 1px 2px rgba(30, 37, 51, 0.10)","kind":"shadow","definedIn":"tokens/spacing.css"},{"name":"--shadow-md","value":"0 2px 6px rgba(30, 37, 51, 0.12), 0 1px 0 rgba(30, 37, 51, 0.04)","kind":"shadow","definedIn":"tokens/spacing.css"},{"name":"--shadow-lg","value":"0 8px 24px rgba(30, 37, 51, 0.16)","kind":"shadow","definedIn":"tokens/spacing.css"},{"name":"--shadow-inset","value":"inset 0 1px 2px rgba(30, 37, 51, 0.10)","kind":"shadow","definedIn":"tokens/spacing.css"},{"name":"--tick-pitch","value":"8px","kind":"spacing","definedIn":"tokens/spacing.css"},{"name":"--tick-h-major","value":"14px","kind":"spacing","definedIn":"tokens/spacing.css"},{"name":"--tick-h-minor","value":"calc(var(--tick-h-major) / 2)","kind":"color","definedIn":"tokens/spacing.css"},{"name":"--tick-gradient","value":"repeating-linear-gradient(90deg, var(--rule-tick) 0 1px, transparent 1px var(--tick-pitch))\n 0 50% / 100% var(--tick-h-minor) repeat-x,\n repeating-linear-gradient(90deg, var(--rule-tick) 0 1px, transparent 1px calc(var(--tick-pitch) * 5))\n 0 50% / 100% var(--tick-h-major) repeat-x","kind":"color","definedIn":"tokens/spacing.css"},{"name":"--ease-slide","value":"cubic-bezier(0.3, 0.0, 0.2, 1)","kind":"other","definedIn":"tokens/spacing.css","annotation":"other"},{"name":"--ease-out","value":"cubic-bezier(0.2, 0.0, 0.0, 1)","kind":"other","definedIn":"tokens/spacing.css","annotation":"other"},{"name":"--dur-fast","value":"120ms","kind":"other","definedIn":"tokens/spacing.css","annotation":"other"},{"name":"--dur-base","value":"200ms","kind":"other","definedIn":"tokens/spacing.css","annotation":"other"},{"name":"--dur-slow","value":"320ms","kind":"other","definedIn":"tokens/spacing.css","annotation":"other"},{"name":"--container-prose","value":"720px","kind":"spacing","definedIn":"tokens/spacing.css"},{"name":"--container-wide","value":"1140px","kind":"spacing","definedIn":"tokens/spacing.css"},{"name":"--sidebar-width","value":"264px","kind":"spacing","definedIn":"tokens/spacing.css"}],"themes":[],"fonts":[],"brandFonts":[{"family":"Saira Condensed","status":"ok","tokens":["--font-primary"],"path":"tokens/typography.css"},{"family":"IBM Plex Mono","status":"ok","tokens":["--font-mono"],"path":"tokens/typography.css"}],"source":"spa"} \ No newline at end of file
diff --git a/pantograph/ds/pantograph-design-system-56a1c74e-41c1-486b-ba3c-4eabdfacef31/readme.md b/pantograph/ds/pantograph-design-system-56a1c74e-41c1-486b-ba3c-4eabdfacef31/readme.md
new file mode 100644
index 0000000..8ea0b0e
--- /dev/null
+++ b/pantograph/ds/pantograph-design-system-56a1c74e-41c1-486b-ba3c-4eabdfacef31/readme.md
@@ -0,0 +1,144 @@
+# Pantograph Design System
+
+The design system for **pantograph** — an open-source project comprising:
+
+- **libpanto** — an LLM provider SDK, imported as `panto` in each language. It gives one provider-agnostic call signature, faithfully reproduced across its (currently three, "and counting") language bindings: **Python, Rust, TypeScript**.
+- **panto** — a terminal coding agent built on top of libpanto. A minimal system prompt and a small, swappable tool set. libpanto and the panto CLI are written in **Zig** (native binaries — maximally fast and resource-efficient); the CLI embeds a **Lua** interpreter for extensions (about as fast as a dynamic scripting language gets).
+
+This system dresses the project's **web resources**: the libpanto SDK reference docs and the panto CLI docs, plus the marketing homepage.
+
+> **The metaphor.** A *pantograph* is an old drafting instrument: a linkage of hinged arms that reproduces a small drawing at a larger scale. One small motion in, one faithful enlargement out. That is exactly what libpanto does for language models — write the motion once, scale it to any provider.
+
+> **The reference object.** The visual language is taken from a **1970s Post Versalog slide rule**: an eggshell, bamboo-bodied precision instrument printed in black, red, and green. So: **no pure white and no pure black** anywhere — warm eggshell "paper" surfaces, deep blue-black "ink" marks, and two engraved accents (instrument red, instrument green).
+
+---
+
+## Sources
+
+This is a **greenfield** system — no codebase, Figma file, or prior brand was supplied. Everything here is derived from the written brief (the pantograph concept + the Post Versalog slide rule as a physical reference). If a repository or design file exists, link it here so future work can cross-reference:
+
+- Repository: _(none provided — add the GitHub URL here)_
+- Design file: _(none provided)_
+- Reference object: Post Versalog slide rule (c. 1970s), eggshell over bamboo, black/red/green markings.
+
+---
+
+## CONTENT FUNDAMENTALS
+
+How pantograph writes. The voice is that of a **well-made instrument manual**: plainspoken, precise, and quietly confident. It never oversells. The throughline is **minimalism** — pantograph is light, straightforward, and fast. *Everything you specify, nothing you don't.*
+
+- **Tone:** Calm, technical, exact. We describe what the tool *does*, in the order it does it. The pantograph metaphor is used sparingly and only when it clarifies ("write the motion once, scale it to any provider") — never as decoration.
+- **Lead with minimalism, not magic.** The product is small on purpose: a minimal system prompt, a small tool set, swap any of it out. It is fast and lean because it is written in Zig (native binaries) and extended in Lua. Say *what isn't there* as a feature.
+- **Don't claim gates it doesn't have.** panto has **no permission prompts** — that would be more, not less. So avoid "waits for yes / waits for approval." And avoid "install with one line" as a headline; the story is restraint, not convenience tricks.
+- **Person:** Address the reader as **you**; the project refers to itself by name (**panto**, **libpanto**, **pantograph**) or as **we** in prose. Avoid "I".
+- **Casing:** The product names are **always lowercase** in body and wordmark: `pantograph`, `panto`, `libpanto`. Sentence case for headings and UI ("Same call, every language"). Engraved labels/eyebrows are **UPPERCASE** with wide tracking (the slide-rule scale-label voice): `SDK REFERENCE`, `GETTING STARTED`.
+- **Commands read like commands.** CLI examples use the imperative, lowercase, quoted: `panto run "add rate limiting to the login route"`.
+- **Numbers & specifics over adjectives.** "Returns a typed Response — text, usage, stop reason" beats "powerful, flexible responses". Cite versions (`since v2.0`), diffs (`+24 −3`), counts ("3 and counting").
+- **No hype, no emoji.** Never "🚀 unlock the power of next-gen AI". Emoji are **not** used in the brand. (See `guidelines/brand-voice.card.html` for the say/don't-say pairs.)
+- **Status is stated, not implied:** `Stable`, `Beta`, `Deprecated`, `since v2.0`, `removed in v3`.
+
+**Specimen copy**
+- Headline: *"Scale one prompt to every provider."*
+- Lead: *"libpanto is a single, small set of motions — faithfully reproduced across OpenAI, Anthropic, and your local models."*
+- Positioning line: *"Everything you specify, nothing you don't."*
+- Callout: *"A minimal prompt and tool set — swap any piece for your own. Written in Zig, extended in Lua."*
+
+---
+
+## VISUAL FOUNDATIONS
+
+The whole system is an eggshell instrument printed in ink. Restraint is the point.
+
+### Color
+- **Two ramps, two accents, nothing else.** Warm **paper** (eggshell, `#F6F0E2 → #BFAE8A`) for surfaces; deep blue-black **ink** (`#161B27 → #8C8470`) for marks. The darkest ink is a *blue*-black, never `#000`; the lightest paper is eggshell, never `#fff`.
+- **Instrument red** (`#B23A2E`) = the cursor line, danger, and the single primary accent. **Instrument green** (`#2F6B4A`) = success, the "folded" scales, focus rings. Used like a draftsman: *sparingly, for meaning, never as fields.* A page should read black-on-eggshell with red/green appearing only at points of emphasis.
+- **Brass** (`#9A7B3F`) is a rare hardware accent (the cursor slide, warning admonitions).
+- Tokens in `tokens/colors.css`; always reference the **semantic aliases** (`--surface-card`, `--text-body`, `--accent`, `--status-*`) rather than the raw ramp.
+
+### Type
+Two voices — minimal, like the product (see `tokens/typography.css`):
+- **Saira Condensed** — **THE primary face**, used everywhere: display headlines, headings, UI, buttons, tables, body, long-form prose, eyebrows, and scale numerals. Tall and narrow with engraved-instrument character; it carries running body copy as well as display. Set in caps with `0.14em` tracking for labels. The `--font-display`, `--font-sans`, and `--font-serif` tokens all alias the primary face, so role tokens and existing components keep working.
+- **IBM Plex Mono** — code, terminals, the CLI, tags, tabular numerals — the machine voice.
+- Scale is a 1.250 major third rooted at 16px (`--text-xs … --text-5xl`).
+
+### Backgrounds & texture
+- Flat eggshell paper — **no photographic backgrounds, no gradients as fields, no glassmorphism.** The one recurring graphic device is the **engraved tick rule** (`--tick-gradient`, `.panto-tick-rule`) — the **standard separator between sections**: short ticks every `--tick-pitch` (8px) with **every 5th tick at double height**, all centered on a common axis. The homepage hero also carries a slide-rule **ruler strip**: a literal printed scale.
+- The terminal is the **only** dark surface in the system — the one place ink becomes a background.
+
+### Shape, borders, radius
+- **Machined, minimal corners.** `--radius-xs:2px` (inputs), `sm:3px` (buttons/badges), `md:5px` (cards), `lg:8px` (dialogs). `pill` is reserved for tags and the switch, used deliberately.
+- **Hairline rules** (`--border-hairline`) divide; **engraved top rules** (a thick ink or red bar on a `ruled` Card) mark importance. Borders are warm paper/ink tones, never cool gray.
+
+### Shadow
+- Warm, low, **ink-tinted** (never neutral or blue): `--shadow-xs … --shadow-lg`, plus `--shadow-inset` for sunken fields. Like an object resting on a drafting table. Cards sit on a shallow `sm`; they lift to `md` on hover.
+
+### Cards
+- Raised eggshell (`--surface-card`, lighter than the page), hairline border, `--radius-md`, shallow shadow. `sunken` variant inverts to an inset secondary surface. `ruled` adds the engraved top scale-rule (ink, or red for `accent`).
+
+### Motion
+- **Mechanical, precise, no bounce** — like a sliding cursor. `--ease-slide` / `--ease-out`; durations `120 / 200 / 320ms`. Tabs slide their red underline; switches glide; buttons press *down 1px* with the shadow collapsing. The terminal cursor blinks (respecting `prefers-reduced-motion`). No floaty fades, no spring overshoot.
+
+### States
+- **Hover:** darker fill (primary → `ink-0`), or a paper-2 wash for low-emphasis controls; links shift red and underline.
+- **Press:** `translateY(1px)` with shadow removed.
+- **Focus:** a 2px **instrument-green** outline (`--focus-ring`) at 2px offset, or a green ring + tinted glow on inputs.
+- **Invalid:** instrument red border + red wash glow.
+- **Disabled:** ~45% opacity, no shadow, `not-allowed`.
+
+### Layout
+- Reading column `--container-prose: 720px`; app/marketing `--container-wide: 1140px`; docs sidebar `264px`. Sticky header + sticky sidebar/TOC; content stays in a measured column. Generous vertical rhythm on the 4px grid.
+
+### Transparency & blur
+- Used **once**: the sticky docs header is `color-mix` paper at ~88% with an 8px backdrop blur, so content scrolls under it. Otherwise surfaces are opaque.
+
+---
+
+## ICONOGRAPHY
+
+- **System set: [Lucide](https://lucide.dev)** — clean line icons, 2px stroke, 24px grid, rounded caps and joins. Their precision and uniform weight suit a drafting instrument.
+ - **Substitution flag:** because this is greenfield with no supplied icon assets, Lucide is a *chosen* set, not a recreation of an existing one. The UI kits inline a curated subset of Lucide-derived paths in `Icons.jsx` (Lucide is ISC-licensed). If the project later ships its own icons, replace `Icons.jsx` and document here.
+- **Icons inherit `currentColor`** and ride the ink ramp; they pick up red/green only inside an accented context (feature tiles, active states).
+- **The logo mark is the one bespoke glyph** (`assets/logo-mark.svg`): a pantograph parallelogram linkage — anchored fulcrum, dashed scaling axis, **red pen tip**. `logo-mark-mono.svg` is the single-color variant. Arms inherit `currentColor`.
+- **No emoji.** No decorative illustration. No multi-color icons. Status uses the engraved `Badge`, not an icon.
+
+---
+
+## INDEX — what's in this system
+
+**Foundations / global CSS**
+- `styles.css` — entry point (consumers link this one file); `@import`s only.
+- `tokens/fonts.css` — webfonts (Google Fonts; see substitution note below).
+- `tokens/colors.css` · `tokens/typography.css` · `tokens/spacing.css` — the token ramps + semantic aliases.
+- `tokens/base.css` — base element styling + helpers (`.panto-prose`, `.panto-eyebrow`, `.panto-tick-rule`).
+
+**Specimen cards** (Design System tab) — in `guidelines/`
+- Colors: paper, ink, accents, status & surfaces.
+- Type: display, sans, prose, mono, scale, eyebrow.
+- Spacing: scale, radius, shadows, borders & ticks.
+- Brand: logo, logo variants, voice & tone.
+
+**Components** — in `components/` (React, reference tokens via CSS vars)
+- `core/` — **Button, IconButton, Badge, Tag, Card**
+- `forms/` — **Input, Select, Switch**
+- `navigation/` — **Tabs** (underline + pill)
+- `code/` — **CodeBlock, Terminal, Callout**
+
+Each directory has `<Name>.jsx` + `<Name>.d.ts` + `<Name>.prompt.md` and one `@dsCard` showcase HTML.
+
+**UI kits** — in `ui_kits/`
+- `docs/` — the **libpanto SDK reference site** (header, nav tree, language-switching reference page, TOC).
+- `home/` — the **pantograph project homepage** (hero + live terminal, install switcher, feature grid, footer).
+
+**Brand assets** — in `assets/`
+- `logo-mark.svg` (red pen tip) · `logo-mark-mono.svg` (single color).
+
+**Other**
+- `SKILL.md` — makes this folder usable as a downloadable Agent Skill.
+
+---
+
+## Font note — self-host when ready
+
+Both typefaces load from **Google Fonts** via `@import` in `tokens/fonts.css` (so the compiler reports **0 `@font-face` rules** — fonts come from Google's network CSS, not self-hosted binaries). The system deliberately runs on just two: **Saira Condensed** (the single primary face) and **IBM Plex Mono**.
+
+If you want self-hosted fonts (offline, no Google dependency) or have specific licensed faces in mind, send the font files and I'll wire real `@font-face` rules. The mono could become JetBrains Mono or Berkeley Mono; if Saira Condensed ever feels too narrow for long-form docs, a slightly wider engineering gothic is the natural fallback — but the brief is one primary face, used everywhere.
diff --git a/pantograph/ds/pantograph-design-system-56a1c74e-41c1-486b-ba3c-4eabdfacef31/styles.css b/pantograph/ds/pantograph-design-system-56a1c74e-41c1-486b-ba3c-4eabdfacef31/styles.css
new file mode 100644
index 0000000..c6b7224
--- /dev/null
+++ b/pantograph/ds/pantograph-design-system-56a1c74e-41c1-486b-ba3c-4eabdfacef31/styles.css
@@ -0,0 +1,13 @@
+/* ============================================================
+ Pantograph Design System — global entry point
+ ------------------------------------------------------------
+ Consumers link THIS one file. It is a manifest of @imports
+ only — no rules live here. Everything reachable from here is
+ shipped to consuming projects.
+ ============================================================ */
+
+@import url('tokens/fonts.css');
+@import url('tokens/colors.css');
+@import url('tokens/typography.css');
+@import url('tokens/spacing.css');
+@import url('tokens/base.css');
diff --git a/pantograph/ds/pantograph-design-system-56a1c74e-41c1-486b-ba3c-4eabdfacef31/tokens/base.css b/pantograph/ds/pantograph-design-system-56a1c74e-41c1-486b-ba3c-4eabdfacef31/tokens/base.css
new file mode 100644
index 0000000..6b8272a
--- /dev/null
+++ b/pantograph/ds/pantograph-design-system-56a1c74e-41c1-486b-ba3c-4eabdfacef31/tokens/base.css
@@ -0,0 +1,113 @@
+/* ============================================================
+ Pantograph — base element styling
+ ------------------------------------------------------------
+ A light, opinionated reset that binds the tokens to real
+ elements. Optional for consumers, but every specimen card,
+ component card, and UI kit in this system assumes it.
+ ============================================================ */
+
+*, *::before, *::after { box-sizing: border-box; }
+
+html { -webkit-text-size-adjust: 100%; }
+
+body {
+ margin: 0;
+ background-color: var(--surface-page);
+ color: var(--text-body);
+ font: var(--role-body);
+ letter-spacing: var(--tracking-normal);
+ -webkit-font-smoothing: antialiased;
+ text-rendering: optimizeLegibility;
+}
+
+/* Headings default to the engraved display voice */
+h1, h2, h3, h4, h5, h6 {
+ margin: 0 0 var(--space-3);
+ color: var(--text-strong);
+ font-family: var(--font-sans);
+ font-weight: var(--weight-semibold);
+ line-height: var(--leading-snug);
+ letter-spacing: var(--tracking-tight);
+ text-wrap: balance;
+}
+h1 { font: var(--role-h1); letter-spacing: var(--tracking-tight); }
+h2 { font: var(--role-h2); }
+h3 { font: var(--role-h3); }
+
+p { margin: 0 0 var(--space-4); text-wrap: pretty; }
+
+a {
+ color: var(--link);
+ text-decoration: none;
+ text-underline-offset: 0.18em;
+ text-decoration-thickness: 1px;
+ transition: color var(--dur-fast) var(--ease-out);
+}
+a:hover { color: var(--link-hover); text-decoration: underline; }
+
+strong, b { font-weight: var(--weight-semibold); color: var(--text-strong); }
+
+code, kbd, samp, pre {
+ font-family: var(--font-mono);
+ font-size: 0.92em;
+}
+
+code {
+ background: var(--surface-code);
+ border: var(--border-thin) solid var(--border-hairline);
+ border-radius: var(--radius-xs);
+ padding: 0.08em 0.36em;
+ color: var(--ink-1);
+}
+
+pre {
+ font-size: var(--text-sm);
+ line-height: var(--leading-mono);
+ margin: 0;
+}
+pre code { background: none; border: none; padding: 0; }
+
+hr {
+ border: 0;
+ height: 1px;
+ background: var(--border-hairline);
+ margin: var(--space-6) 0;
+}
+
+/* The engraved-tick rule: THE standard section separator.
+ Short ticks with every 5th at double height (see
+ --tick-gradient in tokens/spacing.css). Height = the tall
+ tick; short ticks center on the same axis. */
+.panto-tick-rule {
+ border: 0;
+ height: var(--tick-h-major);
+ background: var(--tick-gradient);
+ opacity: 0.85;
+ margin: var(--space-6) 0;
+}
+
+::selection { background: var(--green-soft); color: var(--ink-0); }
+
+:focus-visible {
+ outline: var(--border-thick) solid var(--focus-ring);
+ outline-offset: 2px;
+}
+
+/* Long-form documentation prose — the primary face, set larger
+ and looser for comfortable reading */
+.panto-prose {
+ font: var(--role-prose);
+ color: var(--text-body);
+ max-width: var(--container-prose);
+}
+.panto-prose p { margin-bottom: var(--space-5); }
+
+/* Engraved eyebrow / kicker label */
+.panto-eyebrow {
+ font-family: var(--font-display);
+ font-weight: var(--weight-semibold);
+ font-size: var(--text-xs);
+ letter-spacing: var(--tracking-label);
+ text-transform: uppercase;
+ color: var(--text-faint);
+}
diff --git a/pantograph/ds/pantograph-design-system-56a1c74e-41c1-486b-ba3c-4eabdfacef31/tokens/colors.css b/pantograph/ds/pantograph-design-system-56a1c74e-41c1-486b-ba3c-4eabdfacef31/tokens/colors.css
new file mode 100644
index 0000000..7fef466
--- /dev/null
+++ b/pantograph/ds/pantograph-design-system-56a1c74e-41c1-486b-ba3c-4eabdfacef31/tokens/colors.css
@@ -0,0 +1,87 @@
+/* ============================================================
+ Pantograph — Color tokens
+ ------------------------------------------------------------
+ Inspired by a 1970s Post Versalog slide rule: an eggshell
+ bamboo body printed in black, red, and green. There is NO
+ pure white and NO pure black anywhere in the system.
+
+ Base ramp = warm "paper" (eggshell) surfaces and "ink"
+ (deep blue-black) marks. Two engraved accents: instrument
+ red and instrument green. Use accents like a draftsman —
+ sparingly, for emphasis and meaning, never as fields.
+ ============================================================ */
+
+:root {
+ /* --- Paper (near-white surfaces, faint warm tint, lightest → deepest) --- */
+ --paper-0: #FCFAF5; /* raised cards, popovers, code surfaces */
+ --paper-1: #F8F4EB; /* default page background */
+ --paper-2: #EFE9DC; /* sunken / secondary fields, table stripes */
+ --paper-3: #DFD7C6; /* hairline rules, dividers, engraved ticks */
+ --paper-4: #C7BCA4; /* strong borders, disabled fills */
+
+ /* --- Ink (deep blue-black marks, darkest → lightest) --- */
+ --ink-0: #161B27; /* near-black blue ink: max-contrast headings */
+ --ink-1: #1E2533; /* primary body text, the default mark color */
+ --ink-2: #3B4456; /* secondary text, captions */
+ --ink-3: #6E6B5C; /* muted / warm-gray metadata */
+ --ink-4: #8C8470; /* faint hints, placeholder text */
+
+ /* --- Instrument Red (cursor line, warnings, the C/D scale) --- */
+ --red-deep: #8F2B22;
+ --red: #B23A2E; /* the canonical slide-rule red */
+ --red-soft: #E7C3BB; /* tinted backgrounds */
+ --red-wash: #EBD9D0; /* faintest red wash on paper */
+
+ /* --- Instrument Green (folded scales, success, positive) --- */
+ --green-deep: #245138;
+ --green: #2F6B4A; /* the canonical slide-rule green */
+ --green-soft: #BCD2BF; /* tinted backgrounds */
+ --green-wash: #D6DECB; /* faintest green wash on paper */
+
+ /* --- Brass (the cursor / hardware accent, used very rarely) --- */
+ --brass: #9A7B3F;
+
+ /* ============================================================
+ Semantic aliases — reference THESE in components, not the
+ raw ramp above.
+ ============================================================ */
+
+ /* Surfaces */
+ --surface-page: var(--paper-1);
+ --surface-card: var(--paper-0);
+ --surface-sunken: var(--paper-2);
+ --surface-code: #F4EEE1; /* code blocks: a hair warmer than card */
+ --surface-inverse: var(--ink-1);
+
+ /* Text */
+ --text-strong: var(--ink-0);
+ --text-body: var(--ink-1);
+ --text-muted: var(--ink-2);
+ --text-faint: var(--ink-3);
+ --text-placeholder: var(--ink-4);
+ --text-on-ink: var(--paper-0);
+ --text-on-accent: var(--paper-0);
+
+ /* Lines */
+ --border-hairline: var(--paper-3);
+ --border-default: var(--paper-4);
+ --border-strong: var(--ink-3);
+ --rule-tick: var(--paper-4);
+
+ /* Accents */
+ --accent: var(--red); /* primary brand accent */
+ --accent-deep: var(--red-deep);
+ --link: var(--red-deep);
+ --link-hover: var(--red);
+
+ /* Status */
+ --status-danger: var(--red);
+ --status-danger-bg: var(--red-wash);
+ --status-success: var(--green);
+ --status-success-bg:var(--green-wash);
+ --status-info: var(--ink-2);
+ --status-info-bg: var(--paper-2);
+
+ /* Focus ring */
+ --focus-ring: var(--green);
+}
diff --git a/pantograph/ds/pantograph-design-system-56a1c74e-41c1-486b-ba3c-4eabdfacef31/tokens/fonts.css b/pantograph/ds/pantograph-design-system-56a1c74e-41c1-486b-ba3c-4eabdfacef31/tokens/fonts.css
new file mode 100644
index 0000000..637c1a1
--- /dev/null
+++ b/pantograph/ds/pantograph-design-system-56a1c74e-41c1-486b-ba3c-4eabdfacef31/tokens/fonts.css
@@ -0,0 +1,19 @@
+/* ============================================================
+ Pantograph — Webfonts
+ ------------------------------------------------------------
+ Two families, loaded from Google Fonts. The system runs on a
+ single primary face plus a monospace — minimal by design, the
+ way the product is. See readme.md → VISUAL FOUNDATIONS.
+
+ Saira Condensed → THE primary face. Display, headings, UI,
+ body, labels, prose. Tall, narrow, and
+ precise — engraved-instrument character.
+ IBM Plex Mono → code, terminals, the panto CLI, tabular
+ numerals — the machine voice.
+
+ Saira Condensed loads 400–700 (plus 400/500 italic) so it can
+ carry running body copy, not just display. @import MUST be the
+ first statement, before any other rule.
+ ============================================================ */
+
+@import url('https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:wght@400;500;600&family=Saira+Condensed:ital,wght@0,400;0,500;0,600;0,700;1,400;1,500&display=swap');
diff --git a/pantograph/ds/pantograph-design-system-56a1c74e-41c1-486b-ba3c-4eabdfacef31/tokens/spacing.css b/pantograph/ds/pantograph-design-system-56a1c74e-41c1-486b-ba3c-4eabdfacef31/tokens/spacing.css
new file mode 100644
index 0000000..b06cdc8
--- /dev/null
+++ b/pantograph/ds/pantograph-design-system-56a1c74e-41c1-486b-ba3c-4eabdfacef31/tokens/spacing.css
@@ -0,0 +1,72 @@
+/* ============================================================
+ Pantograph — Spacing, radius, borders, shadow, motion
+ ------------------------------------------------------------
+ A slide rule is a precision instrument: spacing is rigid and
+ gridded (4px base unit), corners are barely rounded (the body
+ has sharp machined edges), and shadows are shallow and warm,
+ like an object resting on a drafting table — never glassy
+ blue drop-shadows.
+ ============================================================ */
+
+:root {
+ /* Spacing scale (4px base) */
+ --space-0: 0;
+ --space-1: 0.25rem; /* 4px */
+ --space-2: 0.5rem; /* 8px */
+ --space-3: 0.75rem; /* 12px */
+ --space-4: 1rem; /* 16px */
+ --space-5: 1.5rem; /* 24px */
+ --space-6: 2rem; /* 32px */
+ --space-7: 3rem; /* 48px */
+ --space-8: 4rem; /* 64px */
+ --space-9: 6rem; /* 96px */
+
+ /* Radius — machined, minimal. Nothing pill-shaped except by intent. */
+ --radius-none: 0;
+ --radius-xs: 2px; /* inputs, code chips */
+ --radius-sm: 3px; /* buttons, badges */
+ --radius-md: 5px; /* cards, panels */
+ --radius-lg: 8px; /* large surfaces, dialogs */
+ --radius-pill: 999px; /* tags, switches — use deliberately */
+
+ /* Border widths */
+ --border-thin: 1px;
+ --border-medium: 1.5px;
+ --border-thick: 2px;
+
+ /* Shadows — warm, low, like ink/paper on a table. Tinted with
+ ink, never neutral gray, never blue. Keep them shallow. */
+ --shadow-xs: 0 1px 0 rgba(30, 37, 51, 0.06);
+ --shadow-sm: 0 1px 2px rgba(30, 37, 51, 0.10);
+ --shadow-md: 0 2px 6px rgba(30, 37, 51, 0.12), 0 1px 0 rgba(30, 37, 51, 0.04);
+ --shadow-lg: 0 8px 24px rgba(30, 37, 51, 0.16);
+ --shadow-inset: inset 0 1px 2px rgba(30, 37, 51, 0.10);
+
+ /* Engraved-tick separator: THE standard section break.
+ A ruled scale — short ticks every --tick-pitch, with every
+ 5th tick at DOUBLE height, all centered on a common axis.
+ The short ticks sit 20% shorter than a plain rule would, so
+ the tall ones stand out without making the whole rule tall.
+ Drive size with --tick-h-major (= the rule's height); the
+ short tick is exactly half of it. */
+ --tick-pitch: 8px; /* spacing between ticks */
+ --tick-h-major: 14px; /* tall (every-5th) tick = rule height */
+ --tick-h-minor: calc(var(--tick-h-major) / 2); /* short tick — half the tall one */
+ --tick-gradient:
+ repeating-linear-gradient(90deg, var(--rule-tick) 0 1px, transparent 1px var(--tick-pitch))
+ 0 50% / 100% var(--tick-h-minor) repeat-x,
+ repeating-linear-gradient(90deg, var(--rule-tick) 0 1px, transparent 1px calc(var(--tick-pitch) * 5))
+ 0 50% / 100% var(--tick-h-major) repeat-x;
+
+ /* Motion — mechanical, precise, no bounce. Like a sliding cursor. */
+ --ease-slide: cubic-bezier(0.3, 0.0, 0.2, 1); /* @kind other */
+ --ease-out: cubic-bezier(0.2, 0.0, 0.0, 1); /* @kind other */
+ --dur-fast: 120ms; /* @kind other */
+ --dur-base: 200ms; /* @kind other */
+ --dur-slow: 320ms; /* @kind other */
+
+ /* Layout */
+ --container-prose: 720px; /* documentation reading column */
+ --container-wide: 1140px; /* app / marketing max width */
+ --sidebar-width: 264px;
+}
diff --git a/pantograph/ds/pantograph-design-system-56a1c74e-41c1-486b-ba3c-4eabdfacef31/tokens/typography.css b/pantograph/ds/pantograph-design-system-56a1c74e-41c1-486b-ba3c-4eabdfacef31/tokens/typography.css
new file mode 100644
index 0000000..be943df
--- /dev/null
+++ b/pantograph/ds/pantograph-design-system-56a1c74e-41c1-486b-ba3c-4eabdfacef31/tokens/typography.css
@@ -0,0 +1,66 @@
+/* ============================================================
+ Pantograph — Typography tokens
+ ------------------------------------------------------------
+ Two voices. Minimal, like the product:
+ • Saira Condensed — THE primary face. Display, headings,
+ UI, body, prose, eyebrows, scale numerals. Tall and
+ narrow; reads as engraved instrument lettering.
+ • IBM Plex Mono — code, terminals, the panto CLI, tabular
+ numerals, anything "machine".
+
+ --font-sans / --font-serif / --font-display all resolve to
+ Saira Condensed, so role tokens and existing components keep
+ working while the system runs on a single primary face.
+ ============================================================ */
+
+:root {
+ /* Families — one primary face, one mono */
+ --font-primary: 'Saira Condensed', 'Arial Narrow', sans-serif;
+ --font-mono: 'IBM Plex Mono', ui-monospace, 'SF Mono', Menlo, monospace;
+
+ /* Aliases — all point at the primary face (legacy role names) */
+ --font-display: var(--font-primary);
+ --font-sans: var(--font-primary);
+ --font-serif: var(--font-primary);
+
+ /* Type scale (1.250 major-third, rooted at 16px) */
+ --text-2xs: 0.6875rem; /* 11px — tick labels, legal */
+ --text-xs: 0.75rem; /* 12px — captions, eyebrows */
+ --text-sm: 0.875rem; /* 14px — secondary UI text */
+ --text-base: 1rem; /* 16px — body */
+ --text-md: 1.125rem; /* 18px — lead paragraph */
+ --text-lg: 1.375rem; /* 22px — h4 / card titles */
+ --text-xl: 1.75rem; /* 28px — h3 */
+ --text-2xl: 2.25rem; /* 36px — h2 */
+ --text-3xl: 3rem; /* 48px — h1 */
+ --text-4xl: 4rem; /* 64px — display */
+ --text-5xl: 5.5rem; /* 88px — hero display */
+
+ /* Weights */
+ --weight-regular: 400;
+ --weight-medium: 500;
+ --weight-semibold: 600;
+ --weight-bold: 700;
+
+ /* Line heights */
+ --leading-tight: 1.05; /* display */
+ --leading-snug: 1.25; /* headings */
+ --leading-normal: 1.5; /* UI / sans body */
+ --leading-relaxed: 1.7; /* serif prose */
+ --leading-mono: 1.6; /* code */
+
+ /* Letter spacing */
+ --tracking-label: 0.14em; /* ALL-CAPS engraved labels */
+ --tracking-tight: -0.01em; /* large display */
+ --tracking-normal: 0;
+
+ /* ---- Semantic roles ---- */
+ --role-display: var(--weight-bold) var(--text-4xl)/var(--leading-tight) var(--font-display);
+ --role-h1: var(--weight-bold) var(--text-3xl)/var(--leading-snug) var(--font-display);
+ --role-h2: var(--weight-semibold) var(--text-2xl)/var(--leading-snug) var(--font-sans);
+ --role-h3: var(--weight-semibold) var(--text-xl)/var(--leading-snug) var(--font-sans);
+ --role-body: var(--weight-regular) var(--text-base)/var(--leading-normal) var(--font-sans);
+ --role-prose: var(--weight-regular) var(--text-md)/var(--leading-relaxed) var(--font-primary);
+ --role-code: var(--weight-regular) var(--text-sm)/var(--leading-mono) var(--font-mono);
+ --role-label: var(--weight-semibold) var(--text-xs)/1 var(--font-display);
+}
diff --git a/pantograph/index.html b/pantograph/index.html
new file mode 100644
index 0000000..8d37dba
--- /dev/null
+++ b/pantograph/index.html
@@ -0,0 +1,37 @@
+<!doctype html>
+<html lang="en">
+<head>
+<meta charset="utf-8">
+<meta name="viewport" content="width=device-width, initial-scale=1">
+<title>panto — Getting started</title>
+<link rel="stylesheet" href="ds/pantograph-design-system-56a1c74e-41c1-486b-ba3c-4eabdfacef31/styles.css">
+<link rel="stylesheet" href="shared/components.css">
+<link rel="stylesheet" href="shared/docs.css">
+</head>
+<body>
+<div class="pd-app">
+<header class="pd-header">
+<div class="pd-header__left">
+<button class="pd-menu" type="button" aria-label="Menu" data-menu-toggle><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" width="1em" height="1em"><path d="M4 12h16"/><path d="M4 6h16"/><path d="M4 18h16"/></svg></button>
+<a class="pd-brand" href="index.html"><img src="assets/logo-mark.svg" width="28" height="28" alt=""><span class="pd-brand__word">panto</span><span class="pd-brand__div">/</span><span class="pd-brand__sub">docs</span></a>
+<span class="pg-badge pg-badge--outline">CLI</span>
+</div>
+<nav class="pd-nav"><a href="index.html" class="pd-nav__link pd-nav__link--active">Getting started</a><a href="cli.html" class="pd-nav__link">Command line</a><a href="configuration.html" class="pd-nav__link">Configuration</a><a href="keybindings.html" class="pd-nav__link">Key bindings</a><a href="lua.html" class="pd-nav__link">Lua extensions</a></nav>
+<div class="pd-header__right"><a class="pd-ghlink" href="https://code.tjp.lol/pantograph.git/" target="_blank" rel="noreferrer"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" width="1em" height="1em"><path d="M15 22v-4a4.8 4.8 0 0 0-1-3.5c3 0 6-2 6-5.5.08-1.25-.27-2.48-1-3.5.28-1.15.28-2.35 0-3.5 0 0-1 0-3 1.5-2.64-.5-5.36-.5-8 0C6 .5 5 .5 5 .5c-.3 1.15-.3 2.35 0 3.5A5.4 5.4 0 0 0 4 7.5c0 3.5 3 5.5 6 5.5-.39.49-.68 1.05-.85 1.65-.17.6-.22 1.23-.15 1.85v4"/><path d="M9 18c-4.51 2-5-2-7-2"/></svg> Source</a></div>
+</header>
+<div class="pd-shell">
+<aside class="pd-sidebar"><div class="pd-sidebar__scroll"><div class="pd-navsec pd-navsec--current"><p class="pd-navsec__title"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" width="1em" height="1em"><path d="m16.24 7.76-1.804 5.411a2 2 0 0 1-1.265 1.265L7.76 16.24l1.804-5.411a2 2 0 0 1 1.265-1.265z"/><circle cx="12" cy="12" r="10"/></svg>Getting started</p><ul class="pd-navsec__list"><li><a href="index.html#overview" class="pd-navsec__link pd-navsec__link--active">Overview</a></li><li><a href="index.html#install" class="pd-navsec__link">Install</a></li><li><a href="index.html#first-run" class="pd-navsec__link">First run</a></li><li><a href="index.html#authenticate" class="pd-navsec__link">Authenticate</a></li></ul></div><div class="pd-navsec"><p class="pd-navsec__title"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" width="1em" height="1em"><path d="m4 17 6-6-6-6"/><path d="M12 19h8"/></svg>Command line</p><ul class="pd-navsec__list"><li><a href="cli.html#starting" class="pd-navsec__link">Starting a session</a></li><li><a href="cli.html#sessions" class="pd-navsec__link"><code>panto sessions</code></a></li><li><a href="cli.html#models" class="pd-navsec__link"><code>panto models</code></a></li><li><a href="cli.html#auth" class="pd-navsec__link"><code>panto auth</code></a></li><li><a href="cli.html#bootstrap" class="pd-navsec__link"><code>panto bootstrap</code></a></li><li><a href="cli.html#lua" class="pd-navsec__link"><code>panto lua</code></a></li><li><a href="cli.html#slash-commands" class="pd-navsec__link">Slash commands</a></li><li><a href="cli.html#environment" class="pd-navsec__link">Environment</a></li></ul></div><div class="pd-navsec"><p class="pd-navsec__title"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" width="1em" height="1em"><path d="M3 5h11"/><path d="M19 5h2"/><path d="M3 12h6"/><path d="M14 12h7"/><path d="M3 19h10"/><path d="M18 19h3"/><circle cx="16" cy="5" r="2"/><circle cx="11" cy="12" r="2"/><circle cx="15" cy="19" r="2"/></svg>Configuration</p><ul class="pd-navsec__list"><li><a href="configuration.html#files" class="pd-navsec__link">Files &amp; layers</a></li><li><a href="configuration.html#providers" class="pd-navsec__link"><code>[providers]</code></a></li><li><a href="configuration.html#auth" class="pd-navsec__link"><code>[auth]</code></a></li><li><a href="configuration.html#defaults" class="pd-navsec__link"><code>[defaults]</code></a></li><li><a href="configuration.html#policies" class="pd-navsec__link">Extensions policy</a></li><li><a href="configuration.html#compaction" class="pd-navsec__link"><code>[compaction]</code></a></li><li><a href="configuration.html#models" class="pd-navsec__link"><code>models.toml</code></a></li><li><a href="configuration.html#system-prompt" class="pd-navsec__link">System prompt</a></li></ul></div><div class="pd-navsec"><p class="pd-navsec__title"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" width="1em" height="1em"><rect x="2" y="4" width="20" height="16" rx="2"/><path d="M6 8h.001"/><path d="M10 8h.001"/><path d="M14 8h.001"/><path d="M18 8h.001"/><path d="M8 12h.001"/><path d="M12 12h.001"/><path d="M16 12h.001"/><path d="M7 16h10"/></svg>Key bindings</p><ul class="pd-navsec__list"><li><a href="keybindings.html#editing" class="pd-navsec__link">Editing &amp; cursor</a></li><li><a href="keybindings.html#submit" class="pd-navsec__link">Submit &amp; newline</a></li><li><a href="keybindings.html#control" class="pd-navsec__link">Selectors &amp; control</a></li><li><a href="keybindings.html#terminal" class="pd-navsec__link">Terminal protocol</a></li></ul></div><div class="pd-navsec"><p class="pd-navsec__title"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" width="1em" height="1em"><path d="M2.97 12.92A2 2 0 0 0 2 14.63v3.24a2 2 0 0 0 .97 1.71l3 1.8a2 2 0 0 0 2.06 0L12 19v-5.5l-5-3-4.03 2.42Z"/><path d="m7 16.5-4.74-2.85"/><path d="m7 16.5 5-3"/><path d="M7 16.5v5.17"/><path d="M12 13.5V19l3.97 2.38a2 2 0 0 0 2.06 0l3-1.8a2 2 0 0 0 .97-1.71v-3.24a2 2 0 0 0-.97-1.71L17 10.5l-5 3Z"/><path d="m17 16.5-5-3"/><path d="m17 16.5 4.74-2.85"/><path d="M17 16.5v5.17"/><path d="M7.97 4.42A2 2 0 0 0 7 6.13v4.37l5 3 5-3V6.13a2 2 0 0 0-.97-1.71l-3-1.8a2 2 0 0 0-2.06 0l-3 1.8Z"/><path d="M12 8 7.26 5.15"/><path d="m12 8 4.74-2.85"/><path d="M12 13.5V8"/></svg>Lua extensions</p><ul class="pd-navsec__list"><li><a href="lua.html#overview" class="pd-navsec__link">Overview</a></li><li><a href="lua.html#loading" class="pd-navsec__link">Loading extensions</a></li><li><a href="lua.html#tools" class="pd-navsec__link"><code>register_tool</code></a></li><li><a href="lua.html#commands" class="pd-navsec__link"><code>register_command</code></a></li><li><a href="lua.html#events" class="pd-navsec__link">Events: on &amp; emit</a></li><li><a href="lua.html#components" class="pd-navsec__link">Lua components</a></li><li><a href="lua.html#runtime" class="pd-navsec__link">Runtime &amp; luarocks</a></li></ul></div></div></aside>
+<div class="pd-scrim"></div>
+<main class="pd-main"><article class="pd-doc" data-screen-label="panto"><p class="panto-eyebrow pd-eyebrow">Getting started</p><h1 class="pd-doc__h1">panto</h1><div class="pd-doc__meta"><span class="pg-badge pg-badge--neutral">terminal agent</span><span class="pg-tag"><span class="pg-tag__dot" style="background:var(--red)"></span>zig</span><span class="pg-tag"><span class="pg-tag__dot" style="background:var(--green)"></span>lua</span></div><p class="pd-lead">panto is a terminal coding agent with a small Zig core and a Lua extension surface. Run it interactively in a project directory, or use <code>-p/--print</code> for one-shot non-interactive turns. These pages cover the current CLI, configuration layers, key bindings, and extension API.</p><h2 id="overview" class="pd-h2">Overview</h2><p class="pd-body">The default entry point is just <code>panto</code>: it opens the TUI in the current directory, selects a model from your layered config, and lets the agent work through tools. The shipped file tools are themselves Lua extensions, so the same loader that powers your own add-ons also powers <code>std.read</code>, <code>std.write</code>, <code>std.edit</code>, and <code>std.shell</code>.</p><p class="pd-body">The first argument also dispatches subcommands. <code>panto sessions</code>, <code>panto auth</code>, <code>panto models sync</code>, <code>panto bootstrap</code>, <code>panto update</code>, and <code>panto lua</code> all short-circuit before the chat loop; <code>panto -p</code> runs a single non-interactive turn and exits.</p><div class="panto-tick-rule pd-rule"></div><h2 id="install" class="pd-h2">Install</h2><p class="pd-body">panto builds from source with <a href="https://ziglang.org" target="_blank" rel="noreferrer">Zig</a>. Clone the repository and build the binary:</p><div class="pg-code"><div class="pg-code__bar"><span class="pg-code__name">shell</span><span class="pg-code__lang">bash</span><button class="pg-code__copy" type="button" aria-label="Copy code"><svg class="pg-code__copyglyph" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="9" y="9" width="11" height="11" rx="2"/><path d="M5 15V5a2 2 0 0 1 2-2h10"/></svg><svg class="pg-code__check" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><path d="m20 6-11 11-5-5"/></svg><span class="pg-code__label">Copy</span></button></div><div class="pg-code__scroll"><pre><code>git clone https://code.tjp.lol/pantograph.git
+cd pantograph
+zig build # native binary at zig-out/bin/panto</code></pre></div></div><p class="pd-body">The first real run bootstraps panto's bundled Lua runtime automatically, so most users can go straight to <code>panto</code>. The explicit bootstrap command is still useful for CI, fresh-machine setup, or forcing the rocks tree to exist before you start a session:</p><div class="pg-code"><div class="pg-code__bar"><span class="pg-code__name">shell</span><span class="pg-code__lang">bash</span><button class="pg-code__copy" type="button" aria-label="Copy code"><svg class="pg-code__copyglyph" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="9" y="9" width="11" height="11" rx="2"/><path d="M5 15V5a2 2 0 0 1 2-2h10"/></svg><svg class="pg-code__check" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><path d="m20 6-11 11-5-5"/></svg><span class="pg-code__label">Copy</span></button></div><div class="pg-code__scroll"><pre><code>panto bootstrap
+# or: panto bootstrap --force</code></pre></div></div><div class="pg-callout pg-callout--tip"><span class="pg-callout__icon"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M9 18h6"/><path d="M10 22h4"/><path d="M15.09 14c.18-.98.65-1.74 1.41-2.5A4.65 4.65 0 0 0 18 8 6 6 0 0 0 6 8c0 1 .23 2.23 1.5 3.5A4.61 4.61 0 0 1 8.91 14"/></svg></span><div class="pg-callout__body"><p class="pg-callout__title">Where things live</p>The data home is <code>$XDG_DATA_HOME/panto</code> (or <code>~/.local/share/panto</code> when <code>XDG_DATA_HOME</code> is unset). User config lives under <code>$XDG_CONFIG_HOME/panto</code> (or <code>~/.config/panto</code>). Project-local overrides live in <code>./.panto/</code>.</div></div><h2 id="first-run" class="pd-h2">First run</h2><p class="pd-body">Start an interactive session with no arguments:</p><div class="pg-code"><div class="pg-code__bar"><span class="pg-code__name">shell</span><span class="pg-code__lang">bash</span><button class="pg-code__copy" type="button" aria-label="Copy code"><svg class="pg-code__copyglyph" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="9" y="9" width="11" height="11" rx="2"/><path d="M5 15V5a2 2 0 0 1 2-2h10"/></svg><svg class="pg-code__check" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><path d="m20 6-11 11-5-5"/></svg><span class="pg-code__label">Copy</span></button></div><div class="pg-code__scroll"><pre><code>cd ~/code/ledger
+panto</code></pre></div></div><p class="pd-body">Useful first-day flags:</p><div class="pd-opts"><div class="pd-opt"><div class="pd-opt__key"><span class="pd-opt__name">panto --resume</span></div><div class="pd-opt__desc">Reattach the most recent conversation in this directory. <code>--resume &lt;id&gt;</code> resumes a specific session by id prefix; <code>-c</code> / <code>--continue</code> is an alias for bare <code>--resume</code>.</div></div><div class="pd-opt"><div class="pd-opt__key"><span class="pd-opt__name">panto -p &quot;prompt&quot;</span></div><div class="pd-opt__desc">Run one turn non-interactively, print only the assistant text, and exit. The prompt may also come from piped stdin.</div></div><div class="pd-opt"><div class="pd-opt__key"><span class="pd-opt__name">panto -m openai:gpt --effort high</span></div><div class="pd-opt__desc">Override the model and reasoning level for just this run.</div></div><div class="pd-opt"><div class="pd-opt__key"><span class="pd-opt__name">panto --no-extensions</span></div><div class="pd-opt__desc">Skip the Lua runtime entirely. This disables shipped tools too, but it is a useful escape hatch when an extension breaks startup.</div></div></div><p class="pd-body">The TUI requires an interactive terminal. If you only want a single answer in a script or CI job, use <code>-p</code> instead of trying to pipe the full-screen UI.</p><h2 id="authenticate" class="pd-h2">Authenticate</h2><p class="pd-body">Providers draw credentials from named auth sessions in <code>config.toml</code>. API-key sessions usually resolve from the environment:</p><div class="pg-code"><div class="pg-code__bar"><span class="pg-code__name">shell</span><span class="pg-code__lang">bash</span><button class="pg-code__copy" type="button" aria-label="Copy code"><svg class="pg-code__copyglyph" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="9" y="9" width="11" height="11" rx="2"/><path d="M5 15V5a2 2 0 0 1 2-2h10"/></svg><svg class="pg-code__check" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><path d="m20 6-11 11-5-5"/></svg><span class="pg-code__label">Copy</span></button></div><div class="pg-code__scroll"><pre><code>export ANTHROPIC_API_KEY=sk-ant-…
+panto auth status</code></pre></div></div><p class="pd-body">OAuth device sessions are logged in explicitly once, then cached on disk:</p><div class="pg-code"><div class="pg-code__bar"><span class="pg-code__name">shell</span><span class="pg-code__lang">bash</span><button class="pg-code__copy" type="button" aria-label="Copy code"><svg class="pg-code__copyglyph" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="9" y="9" width="11" height="11" rx="2"/><path d="M5 15V5a2 2 0 0 1 2-2h10"/></svg><svg class="pg-code__check" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><path d="m20 6-11 11-5-5"/></svg><span class="pg-code__label">Copy</span></button></div><div class="pg-code__scroll"><pre><code>panto auth login github_copilot
+panto auth status</code></pre></div></div><div class="pg-callout pg-callout--note"><span class="pg-callout__icon"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/><path d="M12 16v-4"/><path d="M12 8h.01"/></svg></span><div class="pg-callout__body"><p class="pg-callout__title">If a provider seems missing</p>An <code>api_key</code> auth session that resolves to the empty string leaves its provider unavailable, so exporting the missing environment variable is often enough to make it appear. OAuth providers stay configured and resolve at use time.</div></div><nav class="pd-prevnext"><span style="flex:1"></span><a class="pd-pn pd-pn--next" href="cli.html"><span class="pd-pn__dir">Next →</span><span class="pd-pn__title">Command line</span></a></nav></article></main>
+<aside class="pd-toc"><p class="pd-toc__title">On this page</p><ul class="pd-toc__list"><li><a href="#overview" class="pd-toc__link pd-toc__link--active">Overview</a></li><li><a href="#install" class="pd-toc__link">Install</a></li><li><a href="#first-run" class="pd-toc__link">First run</a></li><li><a href="#authenticate" class="pd-toc__link">Authenticate</a></li></ul></aside>
+</div>
+</div>
+<script src="shared/docs.js"></script>
+</body>
+</html>
diff --git a/pantograph/keybindings.html b/pantograph/keybindings.html
new file mode 100644
index 0000000..3d82675
--- /dev/null
+++ b/pantograph/keybindings.html
@@ -0,0 +1,31 @@
+<!doctype html>
+<html lang="en">
+<head>
+<meta charset="utf-8">
+<meta name="viewport" content="width=device-width, initial-scale=1">
+<title>panto — Key bindings</title>
+<link rel="stylesheet" href="ds/pantograph-design-system-56a1c74e-41c1-486b-ba3c-4eabdfacef31/styles.css">
+<link rel="stylesheet" href="shared/components.css">
+<link rel="stylesheet" href="shared/docs.css">
+</head>
+<body>
+<div class="pd-app">
+<header class="pd-header">
+<div class="pd-header__left">
+<button class="pd-menu" type="button" aria-label="Menu" data-menu-toggle><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" width="1em" height="1em"><path d="M4 12h16"/><path d="M4 6h16"/><path d="M4 18h16"/></svg></button>
+<a class="pd-brand" href="index.html"><img src="assets/logo-mark.svg" width="28" height="28" alt=""><span class="pd-brand__word">panto</span><span class="pd-brand__div">/</span><span class="pd-brand__sub">docs</span></a>
+<span class="pg-badge pg-badge--outline">CLI</span>
+</div>
+<nav class="pd-nav"><a href="index.html" class="pd-nav__link">Getting started</a><a href="cli.html" class="pd-nav__link">Command line</a><a href="configuration.html" class="pd-nav__link">Configuration</a><a href="keybindings.html" class="pd-nav__link pd-nav__link--active">Key bindings</a><a href="lua.html" class="pd-nav__link">Lua extensions</a></nav>
+<div class="pd-header__right"><a class="pd-ghlink" href="https://code.tjp.lol/pantograph.git/" target="_blank" rel="noreferrer"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" width="1em" height="1em"><path d="M15 22v-4a4.8 4.8 0 0 0-1-3.5c3 0 6-2 6-5.5.08-1.25-.27-2.48-1-3.5.28-1.15.28-2.35 0-3.5 0 0-1 0-3 1.5-2.64-.5-5.36-.5-8 0C6 .5 5 .5 5 .5c-.3 1.15-.3 2.35 0 3.5A5.4 5.4 0 0 0 4 7.5c0 3.5 3 5.5 6 5.5-.39.49-.68 1.05-.85 1.65-.17.6-.22 1.23-.15 1.85v4"/><path d="M9 18c-4.51 2-5-2-7-2"/></svg> Source</a></div>
+</header>
+<div class="pd-shell">
+<aside class="pd-sidebar"><div class="pd-sidebar__scroll"><div class="pd-navsec"><p class="pd-navsec__title"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" width="1em" height="1em"><path d="m16.24 7.76-1.804 5.411a2 2 0 0 1-1.265 1.265L7.76 16.24l1.804-5.411a2 2 0 0 1 1.265-1.265z"/><circle cx="12" cy="12" r="10"/></svg>Getting started</p><ul class="pd-navsec__list"><li><a href="index.html#overview" class="pd-navsec__link">Overview</a></li><li><a href="index.html#install" class="pd-navsec__link">Install</a></li><li><a href="index.html#first-run" class="pd-navsec__link">First run</a></li><li><a href="index.html#authenticate" class="pd-navsec__link">Authenticate</a></li></ul></div><div class="pd-navsec"><p class="pd-navsec__title"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" width="1em" height="1em"><path d="m4 17 6-6-6-6"/><path d="M12 19h8"/></svg>Command line</p><ul class="pd-navsec__list"><li><a href="cli.html#starting" class="pd-navsec__link">Starting a session</a></li><li><a href="cli.html#sessions" class="pd-navsec__link"><code>panto sessions</code></a></li><li><a href="cli.html#models" class="pd-navsec__link"><code>panto models</code></a></li><li><a href="cli.html#auth" class="pd-navsec__link"><code>panto auth</code></a></li><li><a href="cli.html#bootstrap" class="pd-navsec__link"><code>panto bootstrap</code></a></li><li><a href="cli.html#lua" class="pd-navsec__link"><code>panto lua</code></a></li><li><a href="cli.html#slash-commands" class="pd-navsec__link">Slash commands</a></li><li><a href="cli.html#environment" class="pd-navsec__link">Environment</a></li></ul></div><div class="pd-navsec"><p class="pd-navsec__title"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" width="1em" height="1em"><path d="M3 5h11"/><path d="M19 5h2"/><path d="M3 12h6"/><path d="M14 12h7"/><path d="M3 19h10"/><path d="M18 19h3"/><circle cx="16" cy="5" r="2"/><circle cx="11" cy="12" r="2"/><circle cx="15" cy="19" r="2"/></svg>Configuration</p><ul class="pd-navsec__list"><li><a href="configuration.html#files" class="pd-navsec__link">Files &amp; layers</a></li><li><a href="configuration.html#providers" class="pd-navsec__link"><code>[providers]</code></a></li><li><a href="configuration.html#auth" class="pd-navsec__link"><code>[auth]</code></a></li><li><a href="configuration.html#defaults" class="pd-navsec__link"><code>[defaults]</code></a></li><li><a href="configuration.html#policies" class="pd-navsec__link">Extensions policy</a></li><li><a href="configuration.html#compaction" class="pd-navsec__link"><code>[compaction]</code></a></li><li><a href="configuration.html#models" class="pd-navsec__link"><code>models.toml</code></a></li><li><a href="configuration.html#system-prompt" class="pd-navsec__link">System prompt</a></li></ul></div><div class="pd-navsec pd-navsec--current"><p class="pd-navsec__title"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" width="1em" height="1em"><rect x="2" y="4" width="20" height="16" rx="2"/><path d="M6 8h.001"/><path d="M10 8h.001"/><path d="M14 8h.001"/><path d="M18 8h.001"/><path d="M8 12h.001"/><path d="M12 12h.001"/><path d="M16 12h.001"/><path d="M7 16h10"/></svg>Key bindings</p><ul class="pd-navsec__list"><li><a href="keybindings.html#editing" class="pd-navsec__link pd-navsec__link--active">Editing &amp; cursor</a></li><li><a href="keybindings.html#submit" class="pd-navsec__link">Submit &amp; newline</a></li><li><a href="keybindings.html#control" class="pd-navsec__link">Selectors &amp; control</a></li><li><a href="keybindings.html#terminal" class="pd-navsec__link">Terminal protocol</a></li></ul></div><div class="pd-navsec"><p class="pd-navsec__title"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" width="1em" height="1em"><path d="M2.97 12.92A2 2 0 0 0 2 14.63v3.24a2 2 0 0 0 .97 1.71l3 1.8a2 2 0 0 0 2.06 0L12 19v-5.5l-5-3-4.03 2.42Z"/><path d="m7 16.5-4.74-2.85"/><path d="m7 16.5 5-3"/><path d="M7 16.5v5.17"/><path d="M12 13.5V19l3.97 2.38a2 2 0 0 0 2.06 0l3-1.8a2 2 0 0 0 .97-1.71v-3.24a2 2 0 0 0-.97-1.71L17 10.5l-5 3Z"/><path d="m17 16.5-5-3"/><path d="m17 16.5 4.74-2.85"/><path d="M17 16.5v5.17"/><path d="M7.97 4.42A2 2 0 0 0 7 6.13v4.37l5 3 5-3V6.13a2 2 0 0 0-.97-1.71l-3-1.8a2 2 0 0 0-2.06 0l-3 1.8Z"/><path d="M12 8 7.26 5.15"/><path d="m12 8 4.74-2.85"/><path d="M12 13.5V8"/></svg>Lua extensions</p><ul class="pd-navsec__list"><li><a href="lua.html#overview" class="pd-navsec__link">Overview</a></li><li><a href="lua.html#loading" class="pd-navsec__link">Loading extensions</a></li><li><a href="lua.html#tools" class="pd-navsec__link"><code>register_tool</code></a></li><li><a href="lua.html#commands" class="pd-navsec__link"><code>register_command</code></a></li><li><a href="lua.html#events" class="pd-navsec__link">Events: on &amp; emit</a></li><li><a href="lua.html#components" class="pd-navsec__link">Lua components</a></li><li><a href="lua.html#runtime" class="pd-navsec__link">Runtime &amp; luarocks</a></li></ul></div></div></aside>
+<div class="pd-scrim"></div>
+<main class="pd-main"><article class="pd-doc" data-screen-label="Key bindings"><p class="panto-eyebrow pd-eyebrow">Reference</p><h1 class="pd-doc__h1">Key bindings</h1><div class="pd-doc__meta"><span class="pg-badge pg-badge--neutral">TUI input</span></div><p class="pd-lead">The session UI reads raw keystrokes and negotiates richer terminal keyboard protocols at startup. These are the bindings the current TUI recognizes.</p><h2 id="editing" class="pd-h2">Editing &amp; cursor</h2><p class="pd-body">The input box is multi-line and accepts full UTF-8 text. Pasted text arrives as one literal block via bracketed paste.</p><div class="pd-table-wrap"><table class="pd-table pd-keytable"><thead><tr><th style="width:34%">Key</th><th>Action</th></tr></thead><tbody><tr><td>any printable key</td><td>Insert text at the cursor.</td></tr><tr><td><span class="pd-kbd">←</span> / <span class="pd-kbd">→</span></td><td>Move one character.</td></tr><tr><td><span class="pd-kbd">Alt</span>+<span class="pd-kbd">B</span> / <span class="pd-kbd">Alt</span>+<span class="pd-kbd">F</span><br><span class="pd-kbd">Ctrl</span>+<span class="pd-kbd">←</span> / <span class="pd-kbd">Ctrl</span>+<span class="pd-kbd">→</span></td><td>Move by word. Terminals vary in which encoding they send; panto accepts the common ones.</td></tr><tr><td><span class="pd-kbd">Ctrl</span>+<span class="pd-kbd">A</span> / <span class="pd-kbd">Ctrl</span>+<span class="pd-kbd">E</span></td><td>Jump to start / end of the current line.</td></tr><tr><td><span class="pd-kbd">Backspace</span> / <span class="pd-kbd">Delete</span></td><td>Delete backward / forward.</td></tr><tr><td><span class="pd-kbd">Ctrl</span>+<span class="pd-kbd">U</span> / <span class="pd-kbd">Ctrl</span>+<span class="pd-kbd">W</span></td><td>Delete to line start / delete previous word.</td></tr><tr><td>paste</td><td>Insert the pasted bytes literally; pasted newlines do not auto-submit.</td></tr></tbody></table></div><h2 id="submit" class="pd-h2">Submit &amp; newline</h2><div class="pd-table-wrap"><table class="pd-table pd-keytable"><thead><tr><th style="width:34%">Key</th><th>Action</th></tr></thead><tbody><tr><td><span class="pd-kbd">Enter</span></td><td>Submit the current input.</td></tr><tr><td><span class="pd-kbd">Shift</span>+<span class="pd-kbd">Enter</span></td><td>Insert a newline when the terminal can report it distinctly.</td></tr></tbody></table></div><p class="pd-body">Whether <span class="pd-kbd">Shift</span>+<span class="pd-kbd">Enter</span> works depends on keyboard-protocol support. panto negotiates Kitty first, then falls back to xterm <code>modifyOtherKeys</code>.</p><h2 id="control" class="pd-h2">Selectors &amp; control</h2><div class="pd-table-wrap"><table class="pd-table pd-keytable"><thead><tr><th style="width:34%">Key</th><th>Action</th></tr></thead><tbody><tr><td><span class="pd-kbd">Ctrl</span>+<span class="pd-kbd">T</span></td><td>Open the model selector.</td></tr><tr><td><span class="pd-kbd">Ctrl</span>+<span class="pd-kbd">R</span></td><td>Open the reasoning selector.</td></tr><tr><td><span class="pd-kbd">Ctrl</span>+<span class="pd-kbd">O</span></td><td>Collapse / expand all tool output.</td></tr><tr><td><span class="pd-kbd">Ctrl</span>+<span class="pd-kbd">G</span></td><td>Edit the current draft in <code>$EDITOR</code> (or <code>[tui] editor</code>).</td></tr><tr><td><span class="pd-kbd">Esc</span></td><td>Interrupt the running turn, close an open selector, or clear the input when idle.</td></tr><tr><td><span class="pd-kbd">Ctrl</span>+<span class="pd-kbd">C</span> / <span class="pd-kbd">Ctrl</span>+<span class="pd-kbd">D</span></td><td>Exit cleanly.</td></tr></tbody></table></div><p class="pd-body">Selectors are filterable: type to narrow the list, move with <span class="pd-kbd">↑</span>/<span class="pd-kbd">↓</span> or <span class="pd-kbd">Ctrl</span>+<span class="pd-kbd">N</span>/<span class="pd-kbd">Ctrl</span>+<span class="pd-kbd">P</span>, and accept with <span class="pd-kbd">Enter</span> (or <span class="pd-kbd">Tab</span> for completion-style pickers).</p><div class="pg-callout pg-callout--note"><span class="pg-callout__icon"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/><path d="M12 16v-4"/><path d="M12 8h.01"/></svg></span><div class="pg-callout__body"><p class="pg-callout__title">Selectors are session-local</p>Choosing a model or reasoning level changes the live session only. Nothing is written back to <code>config.toml</code> or <code>models.toml</code>.</div></div><h2 id="terminal" class="pd-h2">Terminal protocol</h2><p class="pd-body">panto enables bracketed paste and probes for keyboard protocol support at startup.</p><h3 id="terminal-shiftenter" class="pd-h3">Shift + Enter</h3><p class="pd-body">With the legacy terminal protocol, <span class="pd-kbd">Enter</span> and <span class="pd-kbd">Shift</span>+<span class="pd-kbd">Enter</span> are the same byte. panto pushes the Kitty keyboard protocol and queries the terminal; if that is unavailable it falls back to xterm <code>modifyOtherKeys</code>.</p><div class="pd-table-wrap"><table class="pd-table"><thead><tr><th>Terminal</th><th>Shift + Enter</th></tr></thead><tbody><tr><td>Kitty, Ghostty, foot</td><td>Supported via Kitty keyboard protocol.</td></tr><tr><td>xterm, tmux</td><td>Supported via <code>modifyOtherKeys</code> fallback.</td></tr><tr><td>Terminal.app and similar</td><td>Indistinguishable from Enter, so it submits.</td></tr></tbody></table></div><h3 id="terminal-paste" class="pd-h3">Bracketed paste</h3><p class="pd-body">Bracketed paste means multi-line pastes are inserted literally instead of being reinterpreted as a sequence of keypresses.</p><nav class="pd-prevnext"><a class="pd-pn pd-pn--prev" href="configuration.html"><span class="pd-pn__dir">← Previous</span><span class="pd-pn__title">Configuration</span></a><a class="pd-pn pd-pn--next" href="lua.html"><span class="pd-pn__dir">Next →</span><span class="pd-pn__title">Lua extensions</span></a></nav></article></main>
+<aside class="pd-toc"><p class="pd-toc__title">On this page</p><ul class="pd-toc__list"><li><a href="#editing" class="pd-toc__link pd-toc__link--active">Editing &amp; cursor</a></li><li><a href="#submit" class="pd-toc__link">Submit &amp; newline</a></li><li><a href="#control" class="pd-toc__link">Selectors &amp; control</a></li><li><a href="#terminal" class="pd-toc__link">Terminal protocol</a></li></ul></aside>
+</div>
+</div>
+<script src="shared/docs.js"></script>
+</body>
+</html>
diff --git a/pantograph/lua.html b/pantograph/lua.html
new file mode 100644
index 0000000..ace492f
--- /dev/null
+++ b/pantograph/lua.html
@@ -0,0 +1,74 @@
+<!doctype html>
+<html lang="en">
+<head>
+<meta charset="utf-8">
+<meta name="viewport" content="width=device-width, initial-scale=1">
+<title>panto — Lua extensions</title>
+<link rel="stylesheet" href="ds/pantograph-design-system-56a1c74e-41c1-486b-ba3c-4eabdfacef31/styles.css">
+<link rel="stylesheet" href="shared/components.css">
+<link rel="stylesheet" href="shared/docs.css">
+</head>
+<body>
+<div class="pd-app">
+<header class="pd-header">
+<div class="pd-header__left">
+<button class="pd-menu" type="button" aria-label="Menu" data-menu-toggle><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" width="1em" height="1em"><path d="M4 12h16"/><path d="M4 6h16"/><path d="M4 18h16"/></svg></button>
+<a class="pd-brand" href="index.html"><img src="assets/logo-mark.svg" width="28" height="28" alt=""><span class="pd-brand__word">panto</span><span class="pd-brand__div">/</span><span class="pd-brand__sub">docs</span></a>
+<span class="pg-badge pg-badge--outline">CLI</span>
+</div>
+<nav class="pd-nav"><a href="index.html" class="pd-nav__link">Getting started</a><a href="cli.html" class="pd-nav__link">Command line</a><a href="configuration.html" class="pd-nav__link">Configuration</a><a href="keybindings.html" class="pd-nav__link">Key bindings</a><a href="lua.html" class="pd-nav__link pd-nav__link--active">Lua extensions</a></nav>
+<div class="pd-header__right"><a class="pd-ghlink" href="https://code.tjp.lol/pantograph.git/" target="_blank" rel="noreferrer"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" width="1em" height="1em"><path d="M15 22v-4a4.8 4.8 0 0 0-1-3.5c3 0 6-2 6-5.5.08-1.25-.27-2.48-1-3.5.28-1.15.28-2.35 0-3.5 0 0-1 0-3 1.5-2.64-.5-5.36-.5-8 0C6 .5 5 .5 5 .5c-.3 1.15-.3 2.35 0 3.5A5.4 5.4 0 0 0 4 7.5c0 3.5 3 5.5 6 5.5-.39.49-.68 1.05-.85 1.65-.17.6-.22 1.23-.15 1.85v4"/><path d="M9 18c-4.51 2-5-2-7-2"/></svg> Source</a></div>
+</header>
+<div class="pd-shell">
+<aside class="pd-sidebar"><div class="pd-sidebar__scroll"><div class="pd-navsec"><p class="pd-navsec__title"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" width="1em" height="1em"><path d="m16.24 7.76-1.804 5.411a2 2 0 0 1-1.265 1.265L7.76 16.24l1.804-5.411a2 2 0 0 1 1.265-1.265z"/><circle cx="12" cy="12" r="10"/></svg>Getting started</p><ul class="pd-navsec__list"><li><a href="index.html#overview" class="pd-navsec__link">Overview</a></li><li><a href="index.html#install" class="pd-navsec__link">Install</a></li><li><a href="index.html#first-run" class="pd-navsec__link">First run</a></li><li><a href="index.html#authenticate" class="pd-navsec__link">Authenticate</a></li></ul></div><div class="pd-navsec"><p class="pd-navsec__title"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" width="1em" height="1em"><path d="m4 17 6-6-6-6"/><path d="M12 19h8"/></svg>Command line</p><ul class="pd-navsec__list"><li><a href="cli.html#starting" class="pd-navsec__link">Starting a session</a></li><li><a href="cli.html#sessions" class="pd-navsec__link"><code>panto sessions</code></a></li><li><a href="cli.html#models" class="pd-navsec__link"><code>panto models</code></a></li><li><a href="cli.html#auth" class="pd-navsec__link"><code>panto auth</code></a></li><li><a href="cli.html#bootstrap" class="pd-navsec__link"><code>panto bootstrap</code></a></li><li><a href="cli.html#lua" class="pd-navsec__link"><code>panto lua</code></a></li><li><a href="cli.html#slash-commands" class="pd-navsec__link">Slash commands</a></li><li><a href="cli.html#environment" class="pd-navsec__link">Environment</a></li></ul></div><div class="pd-navsec"><p class="pd-navsec__title"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" width="1em" height="1em"><path d="M3 5h11"/><path d="M19 5h2"/><path d="M3 12h6"/><path d="M14 12h7"/><path d="M3 19h10"/><path d="M18 19h3"/><circle cx="16" cy="5" r="2"/><circle cx="11" cy="12" r="2"/><circle cx="15" cy="19" r="2"/></svg>Configuration</p><ul class="pd-navsec__list"><li><a href="configuration.html#files" class="pd-navsec__link">Files &amp; layers</a></li><li><a href="configuration.html#providers" class="pd-navsec__link"><code>[providers]</code></a></li><li><a href="configuration.html#auth" class="pd-navsec__link"><code>[auth]</code></a></li><li><a href="configuration.html#defaults" class="pd-navsec__link"><code>[defaults]</code></a></li><li><a href="configuration.html#policies" class="pd-navsec__link">Extensions policy</a></li><li><a href="configuration.html#compaction" class="pd-navsec__link"><code>[compaction]</code></a></li><li><a href="configuration.html#models" class="pd-navsec__link"><code>models.toml</code></a></li><li><a href="configuration.html#system-prompt" class="pd-navsec__link">System prompt</a></li></ul></div><div class="pd-navsec"><p class="pd-navsec__title"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" width="1em" height="1em"><rect x="2" y="4" width="20" height="16" rx="2"/><path d="M6 8h.001"/><path d="M10 8h.001"/><path d="M14 8h.001"/><path d="M18 8h.001"/><path d="M8 12h.001"/><path d="M12 12h.001"/><path d="M16 12h.001"/><path d="M7 16h10"/></svg>Key bindings</p><ul class="pd-navsec__list"><li><a href="keybindings.html#editing" class="pd-navsec__link">Editing &amp; cursor</a></li><li><a href="keybindings.html#submit" class="pd-navsec__link">Submit &amp; newline</a></li><li><a href="keybindings.html#control" class="pd-navsec__link">Selectors &amp; control</a></li><li><a href="keybindings.html#terminal" class="pd-navsec__link">Terminal protocol</a></li></ul></div><div class="pd-navsec pd-navsec--current"><p class="pd-navsec__title"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" width="1em" height="1em"><path d="M2.97 12.92A2 2 0 0 0 2 14.63v3.24a2 2 0 0 0 .97 1.71l3 1.8a2 2 0 0 0 2.06 0L12 19v-5.5l-5-3-4.03 2.42Z"/><path d="m7 16.5-4.74-2.85"/><path d="m7 16.5 5-3"/><path d="M7 16.5v5.17"/><path d="M12 13.5V19l3.97 2.38a2 2 0 0 0 2.06 0l3-1.8a2 2 0 0 0 .97-1.71v-3.24a2 2 0 0 0-.97-1.71L17 10.5l-5 3Z"/><path d="m17 16.5-5-3"/><path d="m17 16.5 4.74-2.85"/><path d="M17 16.5v5.17"/><path d="M7.97 4.42A2 2 0 0 0 7 6.13v4.37l5 3 5-3V6.13a2 2 0 0 0-.97-1.71l-3-1.8a2 2 0 0 0-2.06 0l-3 1.8Z"/><path d="M12 8 7.26 5.15"/><path d="m12 8 4.74-2.85"/><path d="M12 13.5V8"/></svg>Lua extensions</p><ul class="pd-navsec__list"><li><a href="lua.html#overview" class="pd-navsec__link pd-navsec__link--active">Overview</a></li><li><a href="lua.html#loading" class="pd-navsec__link">Loading extensions</a></li><li><a href="lua.html#tools" class="pd-navsec__link"><code>register_tool</code></a></li><li><a href="lua.html#commands" class="pd-navsec__link"><code>register_command</code></a></li><li><a href="lua.html#events" class="pd-navsec__link">Events: on &amp; emit</a></li><li><a href="lua.html#components" class="pd-navsec__link">Lua components</a></li><li><a href="lua.html#runtime" class="pd-navsec__link">Runtime &amp; luarocks</a></li></ul></div></div></aside>
+<div class="pd-scrim"></div>
+<main class="pd-main"><article class="pd-doc" data-screen-label="Lua extensions"><p class="panto-eyebrow pd-eyebrow">Reference</p><h1 class="pd-doc__h1">Lua extensions</h1><div class="pd-doc__meta"><span class="pg-badge pg-badge--neutral">require(&quot;panto&quot;)</span></div><p class="pd-lead">panto's standard tools are Lua extensions, and your own extensions use the same loader. A source file returns one entry, one tool declaration, or a list of them; activation then registers tools, slash commands, and UI-event handlers into the live session.</p><h2 id="overview" class="pd-h2">Overview</h2><p class="pd-body">Extension source files are evaluated first and activated later. A file must return one of these shapes:</p><div class="pd-opts"><div class="pd-opt"><div class="pd-opt__key"><span class="pd-opt__name">{ name, activate }</span></div><div class="pd-opt__desc">A named entry whose <code>activate()</code> function performs registration when the entry survives policy and shadowing.</div></div><div class="pd-opt"><div class="pd-opt__key"><span class="pd-opt__name">{ name, description, schema, handler }</span></div><div class="pd-opt__desc">Sugar form for a single tool entry. Activation registers it as a tool automatically.</div></div><div class="pd-opt"><div class="pd-opt__key"><span class="pd-opt__name">{ ... , ... }</span></div><div class="pd-opt__desc">A list of entries or tool declarations returned from one file.</div></div></div><p class="pd-body">Inside <code>activate()</code>, use <code>local panto = require(&quot;panto&quot;)</code> and the <code>panto.ext</code> table:</p><div class="pd-opts"><div class="pd-opt"><div class="pd-opt__key"><span class="pd-opt__name">register_tool</span></div><div class="pd-opt__desc">Register a model-facing tool.</div></div><div class="pd-opt"><div class="pd-opt__key"><span class="pd-opt__name">register_command</span></div><div class="pd-opt__desc">Register a slash command.</div></div><div class="pd-opt"><div class="pd-opt__key"><span class="pd-opt__name">on / emit</span></div><div class="pd-opt__desc">Subscribe to transcript/UI lifecycle events or fire custom ones.</div></div></div><h2 id="loading" class="pd-h2">Loading extensions</h2><p class="pd-body">panto scans both <code>extensions/</code> and <code>tools/</code> directories at each layer:</p><div class="pd-table-wrap"><table class="pd-table"><thead><tr><th>Layer</th><th>Location</th></tr></thead><tbody><tr><td class="mono">base</td><td class="mono">$XDG_DATA_HOME/panto/agent/{extensions,tools}</td></tr><tr><td class="mono">user</td><td class="mono">$XDG_CONFIG_HOME/panto/{extensions,tools} · else ~/.config/panto/{extensions,tools}</td></tr><tr><td class="mono">project</td><td class="mono">./.panto/{extensions,tools}</td></tr></tbody></table></div><p class="pd-body">Additional sources can come from <code>[extensions] paths</code> and <code>[extensions] rocks</code>. Cross-layer precedence is project &gt; user &gt; base. Within one layer, canonical directories beat extra paths, which beat rocks. Identity is the returned entry's <code>name</code>, not the filename.</p><div class="pg-callout pg-callout--warn"><span class="pg-callout__icon"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3Z"/><path d="M12 9v4"/><path d="M12 17h.01"/></svg></span><div class="pg-callout__body"><p class="pg-callout__title">Eval is side-effect free</p>Every candidate file is evaluated before policy and shadowing are resolved, including entries that will later be denied. Put side effects in <code>activate()</code>, not at top level.</div></div><h2 id="tools" class="pd-h2">register_tool</h2><p class="pd-body">The simplest way to define a tool is to return the sugar form directly:</p><div class="pg-code"><div class="pg-code__bar"><span class="pg-code__name">.panto/tools/echo.lua</span><span class="pg-code__lang">lua</span><button class="pg-code__copy" type="button" aria-label="Copy code"><svg class="pg-code__copyglyph" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="9" y="9" width="11" height="11" rx="2"/><path d="M5 15V5a2 2 0 0 1 2-2h10"/></svg><svg class="pg-code__check" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><path d="m20 6-11 11-5-5"/></svg><span class="pg-code__label">Copy</span></button></div><div class="pg-code__scroll"><pre><code>return {
+ name = "echo",
+ description = "Echo back the given message.",
+ schema = {
+ type = "object",
+ properties = {
+ message = { type = "string" },
+ },
+ required = { "message" },
+ },
+ handler = function(input)
+ return "echo: " .. input.message
+ end,
+}</code></pre></div></div><p class="pd-body">Use <code>register_tool</code> inside <code>activate()</code> when you want one entry to register multiple tools or commands together.</p><h2 id="commands" class="pd-h2">register_command</h2><p class="pd-body">Slash commands are typically registered from an explicit entry:</p><div class="pg-code"><div class="pg-code__bar"><span class="pg-code__name">.panto/extensions/greet.lua</span><span class="pg-code__lang">lua</span><button class="pg-code__copy" type="button" aria-label="Copy code"><svg class="pg-code__copyglyph" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="9" y="9" width="11" height="11" rx="2"/><path d="M5 15V5a2 2 0 0 1 2-2h10"/></svg><svg class="pg-code__check" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><path d="m20 6-11 11-5-5"/></svg><span class="pg-code__label">Copy</span></button></div><div class="pg-code__scroll"><pre><code>return {
+ name = "greet",
+ activate = function()
+ local panto = require("panto")
+ panto.ext.register_command {
+ name = "greet",
+ description = "Print a greeting.",
+ handler = function(args)
+ local who = args ~= "" and args or "world"
+ io.write("\n[greet] hello, " .. who .. "!\n")
+ end,
+ }
+ end,
+}</code></pre></div></div><h2 id="events" class="pd-h2">Events: on &amp; emit</h2><p class="pd-body"><code>panto.ext.on(name, handler)</code> subscribes to transcript/UI lifecycle events. The handler receives an <code>event</code> userdata with snake-case methods <code>get_component()</code> and <code>set_component()</code>, plus payload fields such as <code>tool_name</code>, <code>delta</code>, <code>text</code>, <code>id</code>, <code>input</code>, <code>output</code>, <code>cwd</code>, and <code>model</code>.</p><div class="pd-table-wrap"><table class="pd-table"><thead><tr><th>Event family</th><th>Names</th></tr></thead><tbody><tr><td class="mono">tool</td><td class="mono">tool, tool_details, tool_delta, tool_call_complete, tool_result, tool_collapse</td></tr><tr><td class="mono">thinking</td><td class="mono">thinking, thinking_delta, thinking_complete</td></tr><tr><td class="mono">assistant text</td><td class="mono">assistant_text, assistant_text_delta, assistant_text_complete</td></tr><tr><td class="mono">single-shot</td><td class="mono">user_message, session_start, compaction</td></tr><tr><td class="mono">custom</td><td class="mono">whatever you emit via <code>panto.ext.emit</code></td></tr></tbody></table></div><p class="pd-body">A few payload fields are writable during dispatch: <code>user_message.text</code>, <code>tool_call_complete.input</code> (assign a Lua table; panto serializes it to JSON), and <code>tool_result.output</code>.</p><div class="pg-code"><div class="pg-code__bar"><span class="pg-code__name">.panto/extensions/skill-badge.lua</span><span class="pg-code__lang">lua</span><button class="pg-code__copy" type="button" aria-label="Copy code"><svg class="pg-code__copyglyph" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="9" y="9" width="11" height="11" rx="2"/><path d="M5 15V5a2 2 0 0 1 2-2h10"/></svg><svg class="pg-code__check" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><path d="m20 6-11 11-5-5"/></svg><span class="pg-code__label">Copy</span></button></div><div class="pg-code__scroll"><pre><code>return {
+ name = "skill-badge",
+ activate = function()
+ local panto = require("panto")
+ panto.ext.on("tool_details", function(e)
+ if e.tool_name ~= "skill" then return end
+ local inner = e:get_component()
+ e:set_component({
+ render = function(self, width)
+ local lines = inner:render(width)
+ lines[#lines + 1] = "▸ running skill"
+ return lines
+ end,
+ })
+ end)
+ end,
+}</code></pre></div></div><h2 id="components" class="pd-h2">Lua components</h2><p class="pd-body">A Lua component is a table passed to <code>set_component()</code>. The required method is <code>render(self, width)</code>, which returns an array of strings. <code>handleInput(self, data)</code> is optional. Differential repaint is cache-derived automatically; you do not need to implement <code>firstLineChanged</code> yourself.</p><h2 id="runtime" class="pd-h2">Runtime &amp; luarocks</h2><p class="pd-body">panto embeds a pinned Lua and luarocks runtime and ships <code>luv</code> as its core async dependency. Extra rocks may be loaded as extension sources via <code>[extensions] rocks</code> and installed with <code>panto update</code>, or added manually with <code>panto lua</code>.</p><div class="pg-code"><div class="pg-code__bar"><span class="pg-code__name">shell</span><span class="pg-code__lang">bash</span><button class="pg-code__copy" type="button" aria-label="Copy code"><svg class="pg-code__copyglyph" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="9" y="9" width="11" height="11" rx="2"/><path d="M5 15V5a2 2 0 0 1 2-2h10"/></svg><svg class="pg-code__check" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><path d="m20 6-11 11-5-5"/></svg><span class="pg-code__label">Copy</span></button></div><div class="pg-code__scroll"><pre><code>panto update
+panto lua -e 'arg[0]="luarocks"; require("luarocks.cmd").run_command("install","lpeg")'</code></pre></div></div><nav class="pd-prevnext"><a class="pd-pn pd-pn--prev" href="keybindings.html"><span class="pd-pn__dir">← Previous</span><span class="pd-pn__title">Key bindings</span></a><span style="flex:1"></span></nav></article></main>
+<aside class="pd-toc"><p class="pd-toc__title">On this page</p><ul class="pd-toc__list"><li><a href="#overview" class="pd-toc__link pd-toc__link--active">Overview</a></li><li><a href="#loading" class="pd-toc__link">Loading extensions</a></li><li><a href="#tools" class="pd-toc__link">register_tool</a></li><li><a href="#commands" class="pd-toc__link">register_command</a></li><li><a href="#events" class="pd-toc__link">Events: on &amp; emit</a></li><li><a href="#components" class="pd-toc__link">Lua components</a></li><li><a href="#runtime" class="pd-toc__link">Runtime &amp; luarocks</a></li></ul></aside>
+</div>
+</div>
+<script src="shared/docs.js"></script>
+</body>
+</html>
diff --git a/pantograph/shared/components.css b/pantograph/shared/components.css
new file mode 100644
index 0000000..2b50d99
--- /dev/null
+++ b/pantograph/shared/components.css
@@ -0,0 +1,160 @@
+/* ============================================================
+ panto docs — static component styles.
+ Extracted from the Pantograph design system's React components
+ (Badge, Tag, Terminal, CodeBlock, Callout) so the docs render
+ identically as plain HTML, with no JavaScript and no React.
+ These mirror the styles those components inject at runtime.
+ ============================================================ */
+
+/* ---- Badge ----------------------------------------------------------- */
+.pg-badge {
+ display: inline-flex; align-items: center; gap: 5px;
+ font-family: var(--font-display); font-weight: var(--weight-semibold);
+ font-size: var(--text-2xs); letter-spacing: var(--tracking-label);
+ text-transform: uppercase; line-height: 1; white-space: nowrap;
+ padding: 4px 7px; border-radius: var(--radius-xs);
+ border: var(--border-thin) solid transparent;
+}
+.pg-badge__dot { width: 6px; height: 6px; border-radius: var(--radius-pill); background: currentColor; }
+.pg-badge--neutral { color: var(--ink-2); background: var(--paper-2); border-color: var(--paper-3); }
+.pg-badge--danger { color: var(--red-deep); background: var(--red-wash); border-color: var(--red-soft); }
+.pg-badge--success { color: var(--green-deep); background: var(--green-wash); border-color: var(--green-soft); }
+.pg-badge--ink { color: var(--paper-0); background: var(--ink-1); border-color: var(--ink-1); }
+.pg-badge--outline { color: var(--ink-2); background: transparent; border-color: var(--border-default); }
+
+/* ---- Tag ------------------------------------------------------------- */
+.pg-tag {
+ display: inline-flex; align-items: center; gap: 6px;
+ font-family: var(--font-mono); font-weight: var(--weight-medium);
+ font-size: var(--text-xs); line-height: 1; white-space: nowrap;
+ color: var(--ink-1); background: var(--paper-0);
+ padding: 5px 9px; border-radius: var(--radius-pill);
+ border: var(--border-thin) solid var(--border-default);
+}
+.pg-tag__dot { width: 7px; height: 7px; border-radius: var(--radius-pill); flex: none; background: var(--ink-3); }
+
+/* ---- Callout (admonition) -------------------------------------------- */
+.pg-callout {
+ display: flex; gap: 12px;
+ padding: 13px 15px;
+ background: var(--paper-0);
+ border: var(--border-thin) solid var(--border-hairline);
+ border-left: var(--border-thick) solid var(--ink-3);
+ border-radius: var(--radius-sm);
+}
+.pg-callout__icon { flex: none; width: 18px; height: 18px; margin-top: 1px; color: var(--ink-3); }
+.pg-callout__icon svg { width: 100%; height: 100%; }
+.pg-callout__body { font: var(--weight-regular) var(--text-sm)/1.55 var(--font-sans); color: var(--ink-1); }
+.pg-callout__title {
+ font: var(--weight-semibold) var(--text-2xs)/1 var(--font-display);
+ letter-spacing: var(--tracking-label); text-transform: uppercase;
+ margin: 0 0 5px; color: var(--ink-2);
+}
+.pg-callout__body p { margin: 0 0 var(--space-2); }
+.pg-callout__body :last-child { margin-bottom: 0; }
+.pg-callout--note { border-left-color: var(--ink-3); }
+.pg-callout--tip { border-left-color: var(--green); background: var(--green-wash); }
+.pg-callout--tip .pg-callout__icon, .pg-callout--tip .pg-callout__title { color: var(--green-deep); }
+.pg-callout--warn { border-left-color: var(--brass); background: #F0E7D0; }
+.pg-callout--warn .pg-callout__icon, .pg-callout--warn .pg-callout__title { color: #7A5E20; }
+.pg-callout--danger { border-left-color: var(--red); background: var(--red-wash); }
+.pg-callout--danger .pg-callout__icon, .pg-callout--danger .pg-callout__title { color: var(--red-deep); }
+
+/* ---- CodeBlock ------------------------------------------------------- */
+.pg-code {
+ background: var(--surface-code);
+ border: var(--border-thin) solid var(--border-default);
+ border-radius: var(--radius-md);
+ overflow: hidden;
+ font-family: var(--font-mono);
+}
+.pg-code__bar {
+ display: flex; align-items: center; gap: 10px;
+ padding: 8px 12px; background: var(--paper-2);
+ border-bottom: var(--border-thin) solid var(--border-hairline);
+}
+.pg-code__name { font: var(--weight-medium) var(--text-xs)/1 var(--font-mono); color: var(--ink-2); }
+.pg-code__lang {
+ font: var(--weight-semibold) var(--text-2xs)/1 var(--font-display);
+ letter-spacing: var(--tracking-label); text-transform: uppercase; color: var(--ink-3);
+ margin-left: auto; padding-right: 4px;
+}
+.pg-code__copy {
+ display: inline-flex; align-items: center; gap: 5px;
+ font: var(--weight-medium) var(--text-2xs)/1 var(--font-display);
+ letter-spacing: 0.1em; text-transform: uppercase;
+ color: var(--ink-3); background: none; border: 0; cursor: pointer;
+ padding: 4px 6px; border-radius: var(--radius-xs);
+ transition: color var(--dur-fast) var(--ease-out), background var(--dur-fast) var(--ease-out);
+}
+.pg-code__copy:hover { color: var(--ink-0); background: var(--paper-1); }
+.pg-code__copy--done { color: var(--green-deep); }
+.pg-code__copy svg { width: 13px; height: 13px; }
+.pg-code__copy .pg-code__check { display: none; }
+.pg-code__copy--done .pg-code__check { display: inline; }
+.pg-code__copy--done .pg-code__copyglyph { display: none; }
+.pg-code__scroll { overflow-x: auto; padding: 14px 16px; }
+.pg-code pre { font-size: var(--text-sm); line-height: var(--leading-mono); color: var(--ink-1); margin: 0; }
+.pg-code code { font-family: inherit; background: none; border: 0; padding: 0; color: inherit; }
+/* token colors for hand-marked spans */
+.pg-code .tok-kw { color: var(--green-deep); }
+.pg-code .tok-str { color: var(--red-deep); }
+.pg-code .tok-com { color: var(--ink-4); font-style: italic; }
+.pg-code .tok-fn { color: var(--ink-0); font-weight: 500; }
+.pg-code .tok-num { color: var(--brass); }
+
+/* ---- Terminal -------------------------------------------------------- */
+.pg-term {
+ background: var(--ink-1);
+ border: var(--border-thin) solid var(--ink-0);
+ border-radius: var(--radius-md);
+ overflow: hidden;
+ box-shadow: var(--shadow-md);
+ font-family: var(--font-mono);
+}
+.pg-term__bar {
+ display: flex; align-items: center; gap: 8px;
+ padding: 9px 12px; background: #232B3B;
+ border-bottom: var(--border-thin) solid #2E3749;
+}
+.pg-term__dot { width: 9px; height: 9px; border-radius: var(--radius-pill); }
+.pg-term__title {
+ margin-left: 6px;
+ font: var(--weight-semibold) var(--text-2xs)/1 var(--font-display);
+ letter-spacing: var(--tracking-label); text-transform: uppercase; color: var(--paper-4);
+}
+.pg-term__body { padding: 14px 16px; overflow-x: auto; font-size: var(--text-sm); line-height: 1.65; }
+.pg-term__line { white-space: pre; color: var(--paper-2); }
+.pg-term__line .pg-term__prompt { color: var(--green-soft); user-select: none; }
+.pg-term__line .pg-term__panto { color: var(--red-soft); user-select: none; }
+.pg-term__line--cmd { color: var(--paper-0); }
+.pg-term__line--out { color: var(--paper-4); }
+.pg-term__line--ok { color: var(--green-soft); }
+.pg-term__line--err { color: var(--red-soft); }
+.pg-term__line--com { color: #6E7689; font-style: italic; }
+.pg-term__cursor {
+ display: inline-block; width: 8px; height: 1.05em; vertical-align: text-bottom;
+ background: var(--red); margin-left: 2px;
+ animation: pg-term-blink 1.1s steps(1) infinite;
+}
+@keyframes pg-term-blink { 50% { opacity: 0; } }
+@media (prefers-reduced-motion: reduce) { .pg-term__cursor { animation: none; } }
+/* panto TUI mock: welcome banner, user block, tool boxes, footer, input frame */
+.pg-term__line--accent { color: var(--red-soft); }
+.pg-term__line--dim { color: #6E7689; }
+.pg-term__user {
+ white-space: pre-wrap; color: var(--paper-0);
+ background: rgba(178,58,46,0.16); padding: 5px 10px; margin: 3px 0;
+}
+.pg-term__tool { margin: 3px 0; }
+.pg-term__tool-head { white-space: pre; color: var(--paper-0); padding-left: 1ch; }
+.pg-term__tool-body {
+ white-space: pre; color: var(--paper-4);
+ padding: 3px 0 0 calc(1ch + 8px); margin-left: 1ch;
+ border-left: 2px solid #2E3749;
+}
+.pg-term__add { color: var(--green-soft); }
+.pg-term__del { color: var(--red-soft); }
+.pg-term .pg-term__rule { border: 0; border-top: 1px solid #2E3749; margin: 7px 0; }
+.pg-term code { background: none; border: 0; padding: 0; color: var(--green-soft); font: inherit; }
+.pg-term__user code { color: inherit; }
diff --git a/pantograph/shared/docs.css b/pantograph/shared/docs.css
new file mode 100644
index 0000000..bfa1e44
--- /dev/null
+++ b/pantograph/shared/docs.css
@@ -0,0 +1,202 @@
+/* ============================================================
+ panto docs — shared documentation chrome.
+ Built on the Pantograph design system tokens. Class prefix: pd-
+ ============================================================ */
+
+body { margin: 0; background: var(--surface-page); }
+html { scroll-behavior: smooth; }
+@media (prefers-reduced-motion: reduce) { html { scroll-behavior: auto; } }
+
+.pd-app { min-height: 100vh; }
+
+/* ---- Header ---------------------------------------------------------- */
+.pd-header {
+ position: sticky; top: 0; z-index: 30;
+ display: flex; align-items: center; gap: var(--space-5);
+ height: 60px; padding: 0 var(--space-6);
+ background: color-mix(in srgb, var(--paper-1) 88%, transparent);
+ backdrop-filter: blur(8px);
+ border-bottom: var(--border-thin) solid var(--border-hairline);
+}
+.pd-header__left { display: flex; align-items: center; gap: var(--space-3); }
+.pd-menu { display: none; background: none; border: 0; cursor: pointer; color: var(--ink-2); font-size: 20px; padding: 4px; }
+.pd-brand { display: flex; align-items: center; gap: 9px; text-decoration: none; }
+.pd-brand:hover { text-decoration: none; }
+.pd-brand__word { font: 700 20px var(--font-display); letter-spacing: -0.01em; color: var(--ink-0); }
+.pd-brand__div { color: var(--paper-4); font: 400 18px var(--font-display); }
+.pd-brand__sub { font: 600 18px var(--font-display); color: var(--ink-3); }
+.pd-nav { display: flex; gap: 2px; margin-left: var(--space-4); }
+.pd-nav__link {
+ font: 500 var(--text-sm)/1 var(--font-sans); color: var(--ink-3);
+ padding: 8px 11px; border-radius: var(--radius-xs); text-decoration: none;
+ transition: color var(--dur-fast), background var(--dur-fast);
+}
+.pd-nav__link:hover { color: var(--ink-1); background: var(--paper-2); text-decoration: none; }
+.pd-nav__link--active { color: var(--ink-0); font-weight: 600; }
+.pd-header__right { display: flex; align-items: center; gap: var(--space-3); margin-left: auto; }
+.pd-ghlink { display: inline-flex; align-items: center; gap: 7px; font: 500 var(--text-sm) var(--font-mono); color: var(--ink-2); text-decoration: none; }
+.pd-ghlink:hover { color: var(--ink-0); text-decoration: none; }
+.pd-ghlink svg { font-size: 16px; }
+
+/* ---- Shell grid ------------------------------------------------------ */
+.pd-shell {
+ display: grid;
+ grid-template-columns: var(--sidebar-width) minmax(0, 1fr) 220px;
+ max-width: 1340px; margin: 0 auto;
+ align-items: start;
+}
+.pd-sidebar {
+ position: sticky; top: 60px; align-self: start;
+ height: calc(100vh - 60px); overflow: hidden;
+ border-right: var(--border-thin) solid var(--border-hairline);
+}
+.pd-sidebar__scroll { height: 100%; overflow-y: auto; padding: var(--space-5) var(--space-4) var(--space-8); }
+.pd-navsec { margin-bottom: var(--space-5); }
+.pd-navsec__title {
+ display: flex; align-items: center; gap: 7px;
+ font: 600 var(--text-2xs) var(--font-display); letter-spacing: var(--tracking-label);
+ text-transform: uppercase; color: var(--ink-4); margin: 0 0 var(--space-2); padding-left: 10px;
+}
+.pd-navsec__title svg { font-size: 13px; }
+.pd-navsec--current .pd-navsec__title { color: var(--red-deep); }
+.pd-navsec__list { list-style: none; margin: 0; padding: 0; }
+.pd-navsec__link {
+ display: block; font: 400 var(--text-sm)/1.2 var(--font-sans); color: var(--ink-2);
+ text-decoration: none; padding: 7px 10px; border-radius: var(--radius-xs);
+ border-left: 2px solid transparent; transition: color var(--dur-fast), background var(--dur-fast), border-color var(--dur-fast);
+}
+.pd-navsec__link:hover { color: var(--ink-0); background: var(--paper-2); text-decoration: none; }
+.pd-navsec__link--active {
+ color: var(--red-deep); font-weight: 600; background: var(--paper-0);
+ border-left-color: var(--red);
+}
+.pd-navsec__link code { background: none; border: 0; padding: 0; color: inherit; font-size: 0.92em; }
+
+/* ---- Main ------------------------------------------------------------ */
+.pd-main { padding: var(--space-6) var(--space-7) var(--space-9); min-width: 0; }
+.pd-doc { max-width: 800px; }
+.pd-eyebrow { display: block; margin-bottom: var(--space-2); }
+.pd-doc__h1 { font: 700 var(--text-3xl) var(--font-display); color: var(--ink-0); letter-spacing: -0.01em; margin: 0 0 var(--space-4); }
+.pd-doc__meta { display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-2); margin-bottom: var(--space-5); }
+.pd-lead { font: 400 var(--text-md)/1.6 var(--font-serif); color: var(--ink-1); margin: 0 0 var(--space-6); max-width: 66ch; }
+
+.pd-h2 {
+ font: 600 var(--text-xl) var(--font-sans); color: var(--ink-0);
+ margin: var(--space-8) 0 var(--space-4); padding-bottom: var(--space-2);
+ border-bottom: var(--border-thin) solid var(--border-hairline);
+ scroll-margin-top: 76px;
+}
+.pd-h3 { font: 600 var(--text-lg) var(--font-sans); color: var(--ink-0); margin: var(--space-6) 0 var(--space-3); scroll-margin-top: 76px; }
+.pd-body { font: 400 var(--text-base)/1.65 var(--font-sans); color: var(--ink-1); max-width: 66ch; }
+.pd-body + .pd-body { margin-top: var(--space-4); }
+.pd-doc a { text-decoration-thickness: 1px; }
+.pd-doc > .pg-callout, .pd-doc .pd-callout { margin: var(--space-5) 0; }
+.pd-doc .pg-code, .pd-doc .pg-term { margin: var(--space-4) 0; max-width: 800px; }
+.pd-section { margin-bottom: var(--space-2); }
+
+/* ---- Command signature header --------------------------------------- */
+.pd-cmd {
+ display: flex; flex-wrap: wrap; align-items: baseline; gap: var(--space-3);
+ margin: var(--space-6) 0 var(--space-2); scroll-margin-top: 76px;
+}
+.pd-cmd__sig {
+ font: 600 var(--text-md) var(--font-mono); color: var(--ink-0);
+ background: var(--surface-code); border: var(--border-thin) solid var(--border-default);
+ border-radius: var(--radius-sm); padding: 5px 10px;
+}
+.pd-cmd__sig .opt { color: var(--ink-3); }
+.pd-cmd__note { font: 400 var(--text-sm) var(--font-sans); color: var(--ink-3); }
+
+/* ---- Option / parameter list ---------------------------------------- */
+.pd-opts { display: flex; flex-direction: column; gap: 0; margin: var(--space-4) 0; border-top: var(--border-thin) solid var(--border-hairline); }
+.pd-opt {
+ display: grid; grid-template-columns: minmax(160px, 240px) 1fr; gap: var(--space-4);
+ padding: var(--space-4) var(--space-2); border-bottom: var(--border-thin) solid var(--border-hairline);
+}
+.pd-opt__key { display: flex; flex-direction: column; gap: 6px; align-items: flex-start; }
+.pd-opt__name { font: 500 var(--text-sm) var(--font-mono); color: var(--ink-0); }
+.pd-opt__type { font: 400 var(--text-xs) var(--font-mono); color: var(--green-deep); }
+.pd-opt__desc { font: 400 var(--text-sm)/1.6 var(--font-sans); color: var(--ink-1); }
+.pd-opt__desc code { font-size: 0.9em; }
+.pd-opt__desc p { margin: 0 0 var(--space-2); }
+.pd-opt__desc p:last-child { margin: 0; }
+@media (max-width: 640px) { .pd-opt { grid-template-columns: 1fr; gap: var(--space-2); } }
+
+/* ---- Table ----------------------------------------------------------- */
+.pd-table-wrap { border: var(--border-thin) solid var(--border-hairline); border-radius: var(--radius-md); overflow: hidden; margin: var(--space-4) 0; }
+.pd-table { width: 100%; border-collapse: collapse; font: 400 var(--text-sm) var(--font-sans); }
+.pd-table th {
+ text-align: left; font: 600 var(--text-2xs) var(--font-display); letter-spacing: var(--tracking-label);
+ text-transform: uppercase; color: var(--ink-3); background: var(--paper-2);
+ padding: 10px 14px; border-bottom: var(--border-thin) solid var(--border-hairline);
+}
+.pd-table td { padding: 11px 14px; border-bottom: var(--border-thin) solid var(--paper-2); color: var(--ink-1); vertical-align: top; }
+.pd-table tr:last-child td { border-bottom: 0; }
+.pd-table tr:nth-child(even) td { background: color-mix(in srgb, var(--paper-2) 40%, transparent); }
+.pd-table td code { font-size: 0.9em; }
+.pd-table .mono { font-family: var(--font-mono); }
+.pd-table .t-type { font: 400 var(--text-xs) var(--font-mono); color: var(--green-deep); }
+.pd-table .t-def { font: 400 var(--text-xs) var(--font-mono); color: var(--ink-3); }
+
+/* ---- Keycaps --------------------------------------------------------- */
+.pd-kbd {
+ display: inline-flex; align-items: center; gap: 4px;
+ font: 600 var(--text-xs) var(--font-mono); color: var(--ink-1);
+ background: var(--paper-0); border: var(--border-thin) solid var(--paper-4);
+ border-bottom-width: 2px; border-radius: var(--radius-sm);
+ padding: 3px 7px; white-space: nowrap;
+}
+.pd-keytable .pd-kbd { font-size: var(--text-sm); }
+
+/* ---- Inline tick rule ------------------------------------------------ */
+.pd-rule { margin: var(--space-7) 0; }
+
+/* ---- TOC ------------------------------------------------------------- */
+.pd-toc { position: sticky; top: 60px; padding: var(--space-6) var(--space-4); align-self: start; max-height: calc(100vh - 60px); overflow-y: auto; }
+.pd-toc__title { font: 600 var(--text-2xs) var(--font-display); letter-spacing: var(--tracking-label); text-transform: uppercase; color: var(--ink-4); margin: 0 0 var(--space-3); }
+.pd-toc__list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 2px; }
+.pd-toc__link { display: block; font: 400 var(--text-sm)/1.3 var(--font-sans); color: var(--ink-3); text-decoration: none; padding: 5px 10px; border-left: 2px solid var(--paper-3); transition: color var(--dur-fast), border-color var(--dur-fast); }
+.pd-toc__link:hover { color: var(--ink-0); border-left-color: var(--ink-3); text-decoration: none; }
+.pd-toc__link--active { color: var(--red-deep); border-left-color: var(--red); font-weight: 600; }
+
+/* ---- Prev / next footer --------------------------------------------- */
+.pd-prevnext {
+ display: flex; gap: var(--space-4); margin-top: var(--space-8);
+ padding-top: var(--space-5); border-top: var(--border-thin) solid var(--border-hairline);
+}
+.pd-pn {
+ flex: 1; display: flex; flex-direction: column; gap: 4px; text-decoration: none;
+ padding: var(--space-4); border: var(--border-thin) solid var(--border-hairline);
+ border-radius: var(--radius-md); background: var(--surface-card); box-shadow: var(--shadow-xs);
+ transition: box-shadow var(--dur-fast), border-color var(--dur-fast);
+}
+.pd-pn:hover { box-shadow: var(--shadow-sm); border-color: var(--border-default); text-decoration: none; }
+.pd-pn--next { text-align: right; }
+.pd-pn__dir { font: 600 var(--text-2xs) var(--font-display); letter-spacing: var(--tracking-label); text-transform: uppercase; color: var(--ink-4); }
+.pd-pn__title { font: 600 var(--text-base) var(--font-sans); color: var(--ink-0); }
+
+/* ---- Footnote / source line ----------------------------------------- */
+.pd-srcnote { font: 400 var(--text-xs)/1.6 var(--font-sans); color: var(--ink-3); margin-top: var(--space-6); }
+.pd-srcnote code { font-size: 0.92em; }
+
+.pd-scrim { display: none; position: fixed; inset: 60px 0 0; z-index: 35; background: rgba(22,27,39,0.28); }
+
+/* ---- Responsive ------------------------------------------------------ */
+@media (max-width: 1120px) {
+ .pd-shell { grid-template-columns: var(--sidebar-width) minmax(0, 1fr); }
+ .pd-toc { display: none; }
+}
+@media (max-width: 860px) {
+ .pd-shell { grid-template-columns: minmax(0, 1fr); }
+ .pd-nav { display: none; }
+ .pd-menu { display: inline-flex; }
+ .pd-sidebar {
+ position: fixed; top: 60px; left: 0; z-index: 40; width: var(--sidebar-width);
+ background: var(--paper-1); transform: translateX(-105%);
+ transition: transform var(--dur-base) var(--ease-slide);
+ }
+ .pd-sidebar--open { transform: translateX(0); box-shadow: var(--shadow-lg); }
+ .pd-sidebar--open ~ .pd-scrim { display: block; }
+ .pd-main { padding: var(--space-5) var(--space-5) var(--space-8); }
+ .pd-prevnext { flex-direction: column; }
+}
diff --git a/pantograph/shared/docs.js b/pantograph/shared/docs.js
new file mode 100644
index 0000000..7e07310
--- /dev/null
+++ b/pantograph/shared/docs.js
@@ -0,0 +1,85 @@
+/* panto docs — progressive enhancement only.
+ The site is fully functional with this file absent or JS disabled:
+ every nav item is a real link, all content is in the HTML. This adds
+ the mobile sidebar toggle, code copy buttons, and TOC scroll-spy. */
+(function () {
+ 'use strict';
+
+ /* ---- Mobile sidebar toggle ---------------------------------------- */
+ var menuBtn = document.querySelector('[data-menu-toggle]');
+ var sidebar = document.querySelector('.pd-sidebar');
+ var scrim = document.querySelector('.pd-scrim');
+ function closeMenu() {
+ if (sidebar) sidebar.classList.remove('pd-sidebar--open');
+ }
+ if (menuBtn && sidebar) {
+ menuBtn.addEventListener('click', function () {
+ sidebar.classList.toggle('pd-sidebar--open');
+ });
+ }
+ if (scrim) scrim.addEventListener('click', closeMenu);
+ if (sidebar) {
+ sidebar.addEventListener('click', function (e) {
+ if (e.target.closest('a')) closeMenu();
+ });
+ }
+
+ /* ---- Code copy buttons -------------------------------------------- */
+ document.querySelectorAll('.pg-code__copy').forEach(function (btn) {
+ btn.addEventListener('click', function () {
+ var block = btn.closest('.pg-code');
+ var pre = block && block.querySelector('pre');
+ if (!pre) return;
+ var text = pre.innerText;
+ var done = function () {
+ btn.classList.add('pg-code__copy--done');
+ var label = btn.querySelector('.pg-code__label');
+ if (label) label.textContent = 'Copied';
+ setTimeout(function () {
+ btn.classList.remove('pg-code__copy--done');
+ if (label) label.textContent = 'Copy';
+ }, 1400);
+ };
+ if (navigator.clipboard) navigator.clipboard.writeText(text).then(done, done);
+ else done();
+ });
+ });
+
+ /* ---- Scroll-spy: highlight the active section in TOC + sidebar ---- */
+ var tocLinks = Array.prototype.slice.call(document.querySelectorAll('.pd-toc__link'));
+ var sideLinks = Array.prototype.slice.call(
+ document.querySelectorAll('.pd-navsec--current .pd-navsec__link')
+ );
+ if (!tocLinks.length || !('IntersectionObserver' in window)) return;
+
+ var ids = tocLinks.map(function (a) {
+ var href = a.getAttribute('href') || '';
+ return href.charAt(0) === '#' ? href.slice(1) : href.split('#')[1];
+ }).filter(Boolean);
+
+ function setActive(id) {
+ tocLinks.forEach(function (a) {
+ var h = a.getAttribute('href') || '';
+ a.classList.toggle('pd-toc__link--active', h === '#' + id);
+ });
+ sideLinks.forEach(function (a) {
+ var h = a.getAttribute('href') || '';
+ a.classList.toggle('pd-navsec__link--active', h.split('#')[1] === id);
+ });
+ }
+
+ var visible = new Set();
+ var obs = new IntersectionObserver(function (entries) {
+ entries.forEach(function (e) {
+ if (e.isIntersecting) visible.add(e.target.id);
+ else visible.delete(e.target.id);
+ });
+ var first = ids.find(function (id) { return visible.has(id); });
+ if (first) setActive(first);
+ }, { rootMargin: '-72px 0px -68% 0px', threshold: 0 });
+
+ ids.forEach(function (id) {
+ var el = document.getElementById(id);
+ if (el) obs.observe(el);
+ });
+})();