feat(web): v0.26 refresh — buddy page, changelog, partners, provider … (#2060)
* feat(web): v0.26 refresh — buddy page, changelog, partners, provider catalog - Single-source the site version from the root package.json (never stale again) - New /buddy/ page: all 7 hero sprites rendered as animated SVGs generated from src/buddy/pixelSprites.ts, attack descriptions, commands, hatch lore, plus a dedicated 1200x630 OG image composed from the real sprites - New /changelog/ page: curated release highlights 0.19 -> 0.26 from a typed releases.ts data file - Landing: buddy teaser section, partners strip (GitLawb, Bankr, Atomic Chat, Xiaomi MiMo, Atlas Cloud, AI/ML API, Novita AI) with self-hosted logos, community links, refreshed provider strip, node >= 22 fix - Providers docs rebuilt as grouped catalog (39 providers: subscriptions, gateways, vendors, local, custom) incl. xAI OAuth, AI/ML API, Cloudflare Workers AI, NVIDIA NIM, Kimi K3, GPT-5.6, Opengateway free models - Data refresh vs v0.26.0 source: 16 new slash commands, pdf skill, new CLI flags + 10 subcommands, modelLimits/providerFallbackChain/agentRouting settings, corrected env vars (GEMINI_API_KEY, OPENGATEWAY_API_KEY, ...) - Nav/footer/docs sidebar link the new pages; JSON-LD breadcrumbs on both Co-Authored-By: OpenClaude <openclaude@gitlawb.com> * fix(web): address CodeRabbit review — flag description + dist verification - Correct --disable-slash-commands description: the flag empties the entire slash-command list (REPL.tsx filters all commands), not just skills; the upstream help string "Disable all skills" is the misleading one - Add scripts/verify-dist.ts, wired into `bun run build` (so the existing CI web job runs it): asserts SITE.version matches the root package.json in the rendered pages, nav exposes /buddy/ and /changelog/, every release renders with its GitHub URL, every hero renders with its sprite asset, partner and community links render on the landing page, and the sitemap covers the new routes Co-Authored-By: OpenClaude <openclaude@gitlawb.com> * fix(web): harden verify-dist per review — empty-page guard, rendered-nav check, tests - page() now records a failure for a present-but-empty file, so '' is only ever returned alongside a recorded failure and skipped assertions can no longer mask a blank page - assert the rendered docs sidebar (dist/docs/) links every docsNav route, not just the source data array and the landing nav - extract pure verifyDist(dist) and add 9 fixture-based bun tests covering missing/empty pages, lost sidebar links, missing sprites, stale partner links, missing release URLs, and sitemap regressions; discovered by the root `bun test` run in CI, no workflow changes needed Co-Authored-By: OpenClaude <openclaude@gitlawb.com> * test(web): derive the missing-sprite fixture from heroes data Hard-coding robinhood.svg would make the test throw during fixture mutation if that hero were renamed, instead of exercising verifyDist(). Co-Authored-By: OpenClaude <openclaude@gitlawb.com> --------- Co-authored-by: OpenClaude <openclaude@gitlawb.com>
@@ -5,7 +5,7 @@
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "astro dev",
|
||||
"build": "astro check && astro build",
|
||||
"build": "astro check && astro build && bun scripts/verify-dist.ts",
|
||||
"preview": "astro preview",
|
||||
"typecheck": "astro check"
|
||||
},
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 22 16" shape-rendering="crispEdges" role="img" aria-label="corsair pixel-art buddy">
|
||||
<style>.f0{animation:f0 4s step-end infinite}.f1{animation:f1 4s step-end infinite;opacity:0}
|
||||
@keyframes f0{0%,90%{opacity:1}90.01%,95%{opacity:0}95.01%,100%{opacity:1}}
|
||||
@keyframes f1{0%,90%{opacity:0}90.01%,95%{opacity:1}95.01%,100%{opacity:0}}
|
||||
@media (prefers-reduced-motion: reduce){.f0,.f1,.f2{animation:none}.f0{opacity:1}.f1,.f2{opacity:0}}</style>
|
||||
<g class="f0"><rect x="4" y="0" width="2" height="1" fill="#283c6e"/><rect x="13" y="0" width="2" height="1" fill="#283c6e"/><rect x="5" y="1" width="9" height="1" fill="#283c6e"/><rect x="4" y="2" width="11" height="1" fill="#d6a83c"/><rect x="5" y="3" width="9" height="1" fill="#283c6e"/><rect x="5" y="4" width="8" height="1" fill="#e8be98"/><rect x="4" y="5" width="1" height="1" fill="#e8be98"/><rect x="5" y="5" width="1" height="1" fill="#262220"/><rect x="6" y="5" width="1" height="1" fill="#f0f0f0"/><rect x="7" y="5" width="3" height="1" fill="#e8be98"/><rect x="10" y="5" width="3" height="1" fill="#282624"/><rect x="13" y="5" width="1" height="2" fill="#c6946e"/><rect x="4" y="6" width="9" height="1" fill="#e8be98"/><rect x="5" y="7" width="1" height="1" fill="#c6946e"/><rect x="6" y="7" width="6" height="1" fill="#e8be98"/><rect x="12" y="7" width="1" height="1" fill="#c6946e"/><rect x="4" y="8" width="9" height="1" fill="#cc3333"/><rect x="3" y="9" width="2" height="2" fill="#e8be98"/><rect x="5" y="9" width="1" height="1" fill="#cc3333"/><rect x="6" y="9" width="5" height="1" fill="#ebeef5"/><rect x="11" y="9" width="1" height="1" fill="#cc3333"/><rect x="12" y="9" width="2" height="2" fill="#e8be98"/><rect x="6" y="10" width="1" height="1" fill="#cc3333"/><rect x="7" y="10" width="1" height="1" fill="#d6a83c"/><rect x="8" y="10" width="1" height="1" fill="#cc3333"/><rect x="9" y="10" width="1" height="1" fill="#d6a83c"/><rect x="10" y="10" width="1" height="1" fill="#cc3333"/><rect x="5" y="11" width="7" height="1" fill="#cc3333"/><rect x="5" y="12" width="2" height="2" fill="#283c6e"/><rect x="11" y="12" width="2" height="2" fill="#283c6e"/><rect x="4" y="14" width="3" height="1" fill="#283c6e"/><rect x="11" y="14" width="3" height="1" fill="#283c6e"/></g><g class="f1"><rect x="4" y="0" width="2" height="1" fill="#283c6e"/><rect x="13" y="0" width="2" height="1" fill="#283c6e"/><rect x="5" y="1" width="9" height="1" fill="#283c6e"/><rect x="4" y="2" width="11" height="1" fill="#d6a83c"/><rect x="5" y="3" width="9" height="1" fill="#283c6e"/><rect x="5" y="4" width="8" height="1" fill="#e8be98"/><rect x="4" y="5" width="1" height="1" fill="#e8be98"/><rect x="5" y="5" width="2" height="1" fill="#262220"/><rect x="7" y="5" width="3" height="1" fill="#e8be98"/><rect x="10" y="5" width="3" height="1" fill="#282624"/><rect x="13" y="5" width="1" height="2" fill="#c6946e"/><rect x="4" y="6" width="9" height="1" fill="#e8be98"/><rect x="5" y="7" width="1" height="1" fill="#c6946e"/><rect x="6" y="7" width="6" height="1" fill="#e8be98"/><rect x="12" y="7" width="1" height="1" fill="#c6946e"/><rect x="4" y="8" width="9" height="1" fill="#cc3333"/><rect x="3" y="9" width="2" height="2" fill="#e8be98"/><rect x="5" y="9" width="1" height="1" fill="#cc3333"/><rect x="6" y="9" width="5" height="1" fill="#ebeef5"/><rect x="11" y="9" width="1" height="1" fill="#cc3333"/><rect x="12" y="9" width="2" height="2" fill="#e8be98"/><rect x="6" y="10" width="1" height="1" fill="#cc3333"/><rect x="7" y="10" width="1" height="1" fill="#d6a83c"/><rect x="8" y="10" width="1" height="1" fill="#cc3333"/><rect x="9" y="10" width="1" height="1" fill="#d6a83c"/><rect x="10" y="10" width="1" height="1" fill="#cc3333"/><rect x="5" y="11" width="7" height="1" fill="#cc3333"/><rect x="5" y="12" width="2" height="2" fill="#283c6e"/><rect x="11" y="12" width="2" height="2" fill="#283c6e"/><rect x="4" y="14" width="3" height="1" fill="#283c6e"/><rect x="11" y="14" width="3" height="1" fill="#283c6e"/></g></svg>
|
||||
|
After Width: | Height: | Size: 3.9 KiB |
@@ -0,0 +1,6 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 22 16" shape-rendering="crispEdges" role="img" aria-label="ember pixel-art buddy">
|
||||
<style>.f0{animation:f0 4s step-end infinite}.f1{animation:f1 4s step-end infinite;opacity:0}
|
||||
@keyframes f0{0%,90%{opacity:1}90.01%,95%{opacity:0}95.01%,100%{opacity:1}}
|
||||
@keyframes f1{0%,90%{opacity:0}90.01%,95%{opacity:1}95.01%,100%{opacity:0}}
|
||||
@media (prefers-reduced-motion: reduce){.f0,.f1,.f2{animation:none}.f0{opacity:1}.f1,.f2{opacity:0}}</style>
|
||||
<g class="f0"><rect x="5" y="0" width="1" height="1" fill="#f5dcaa"/><rect x="9" y="0" width="1" height="1" fill="#f5dcaa"/><rect x="4" y="1" width="8" height="1" fill="#be3228"/><rect x="2" y="2" width="3" height="1" fill="#be3228"/><rect x="5" y="2" width="1" height="1" fill="#ffdc50"/><rect x="6" y="2" width="1" height="1" fill="#262220"/><rect x="7" y="2" width="7" height="1" fill="#be3228"/><rect x="2" y="3" width="13" height="1" fill="#be3228"/><rect x="4" y="4" width="9" height="1" fill="#eb5a3c"/><rect x="13" y="4" width="3" height="1" fill="#be3228"/><rect x="4" y="5" width="1" height="1" fill="#be3228"/><rect x="5" y="5" width="6" height="4" fill="#f5dcaa"/><rect x="11" y="5" width="2" height="1" fill="#be3228"/><rect x="14" y="5" width="3" height="1" fill="#be3228"/><rect x="3" y="6" width="2" height="3" fill="#be3228"/><rect x="11" y="6" width="5" height="1" fill="#be3228"/><rect x="16" y="6" width="1" height="1" fill="#eb5a3c"/><rect x="11" y="7" width="2" height="2" fill="#be3228"/><rect x="13" y="7" width="3" height="1" fill="#eb5a3c"/><rect x="3" y="9" width="3" height="1" fill="#be3228"/><rect x="6" y="9" width="4" height="1" fill="#f5dcaa"/><rect x="10" y="9" width="3" height="1" fill="#be3228"/><rect x="4" y="10" width="9" height="1" fill="#be3228"/><rect x="13" y="10" width="1" height="1" fill="#eb5a3c"/><rect x="5" y="11" width="2" height="1" fill="#be3228"/><rect x="9" y="11" width="2" height="1" fill="#be3228"/><rect x="13" y="11" width="2" height="1" fill="#eb5a3c"/><rect x="4" y="12" width="3" height="1" fill="#be3228"/><rect x="9" y="12" width="3" height="1" fill="#be3228"/><rect x="14" y="12" width="2" height="1" fill="#eb5a3c"/></g><g class="f1"><rect x="5" y="0" width="1" height="1" fill="#f5dcaa"/><rect x="9" y="0" width="1" height="1" fill="#f5dcaa"/><rect x="4" y="1" width="8" height="1" fill="#be3228"/><rect x="2" y="2" width="4" height="1" fill="#be3228"/><rect x="6" y="2" width="1" height="1" fill="#262220"/><rect x="7" y="2" width="7" height="1" fill="#be3228"/><rect x="2" y="3" width="13" height="1" fill="#be3228"/><rect x="4" y="4" width="9" height="1" fill="#eb5a3c"/><rect x="13" y="4" width="3" height="1" fill="#be3228"/><rect x="4" y="5" width="1" height="1" fill="#be3228"/><rect x="5" y="5" width="6" height="4" fill="#f5dcaa"/><rect x="11" y="5" width="2" height="1" fill="#be3228"/><rect x="14" y="5" width="3" height="1" fill="#be3228"/><rect x="3" y="6" width="2" height="3" fill="#be3228"/><rect x="11" y="6" width="5" height="1" fill="#be3228"/><rect x="16" y="6" width="1" height="1" fill="#eb5a3c"/><rect x="11" y="7" width="2" height="2" fill="#be3228"/><rect x="13" y="7" width="3" height="1" fill="#eb5a3c"/><rect x="3" y="9" width="3" height="1" fill="#be3228"/><rect x="6" y="9" width="4" height="1" fill="#f5dcaa"/><rect x="10" y="9" width="3" height="1" fill="#be3228"/><rect x="4" y="10" width="9" height="1" fill="#be3228"/><rect x="13" y="10" width="1" height="1" fill="#eb5a3c"/><rect x="5" y="11" width="2" height="1" fill="#be3228"/><rect x="9" y="11" width="2" height="1" fill="#be3228"/><rect x="13" y="11" width="2" height="1" fill="#eb5a3c"/><rect x="4" y="12" width="3" height="1" fill="#be3228"/><rect x="9" y="12" width="3" height="1" fill="#be3228"/><rect x="14" y="12" width="2" height="1" fill="#eb5a3c"/></g></svg>
|
||||
|
After Width: | Height: | Size: 3.7 KiB |
@@ -0,0 +1,6 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 22 16" shape-rendering="crispEdges" role="img" aria-label="kage pixel-art buddy">
|
||||
<style>.f0{animation:f0 4s step-end infinite}.f1{animation:f1 4s step-end infinite;opacity:0}
|
||||
@keyframes f0{0%,90%{opacity:1}90.01%,95%{opacity:0}95.01%,100%{opacity:1}}
|
||||
@keyframes f1{0%,90%{opacity:0}90.01%,95%{opacity:1}95.01%,100%{opacity:0}}
|
||||
@media (prefers-reduced-motion: reduce){.f0,.f1,.f2{animation:none}.f0{opacity:1}.f1,.f2{opacity:0}}</style>
|
||||
<g class="f0"><rect x="6" y="0" width="7" height="1" fill="#464a52"/><rect x="5" y="1" width="9" height="1" fill="#464a52"/><rect x="4" y="2" width="2" height="3" fill="#464a52"/><rect x="6" y="2" width="7" height="1" fill="#32353c"/><rect x="13" y="2" width="2" height="3" fill="#464a52"/><rect x="6" y="3" width="1" height="1" fill="#e8be98"/><rect x="7" y="3" width="1" height="1" fill="#262220"/><rect x="8" y="3" width="1" height="1" fill="#f0f0f0"/><rect x="9" y="3" width="2" height="1" fill="#e8be98"/><rect x="11" y="3" width="1" height="1" fill="#262220"/><rect x="12" y="3" width="1" height="1" fill="#f0f0f0"/><rect x="6" y="4" width="7" height="1" fill="#32353c"/><rect x="5" y="5" width="2" height="1" fill="#464a52"/><rect x="7" y="5" width="5" height="1" fill="#32353c"/><rect x="12" y="5" width="2" height="1" fill="#464a52"/><rect x="4" y="6" width="9" height="1" fill="#cc3333"/><rect x="4" y="7" width="9" height="1" fill="#464a52"/><rect x="13" y="7" width="2" height="1" fill="#cc3333"/><rect x="3" y="8" width="11" height="1" fill="#464a52"/><rect x="14" y="8" width="2" height="1" fill="#cc3333"/><rect x="3" y="9" width="2" height="1" fill="#464a52"/><rect x="6" y="9" width="6" height="1" fill="#464a52"/><rect x="13" y="9" width="2" height="1" fill="#464a52"/><rect x="3" y="10" width="2" height="1" fill="#c6946e"/><rect x="6" y="10" width="6" height="1" fill="#32353c"/><rect x="13" y="10" width="2" height="1" fill="#c6946e"/><rect x="5" y="11" width="7" height="1" fill="#464a52"/><rect x="5" y="12" width="2" height="2" fill="#464a52"/><rect x="11" y="12" width="2" height="2" fill="#464a52"/><rect x="4" y="14" width="3" height="1" fill="#32353c"/><rect x="11" y="14" width="3" height="1" fill="#32353c"/></g><g class="f1"><rect x="6" y="0" width="7" height="1" fill="#464a52"/><rect x="5" y="1" width="9" height="1" fill="#464a52"/><rect x="4" y="2" width="2" height="3" fill="#464a52"/><rect x="6" y="2" width="7" height="1" fill="#32353c"/><rect x="13" y="2" width="2" height="3" fill="#464a52"/><rect x="6" y="3" width="1" height="1" fill="#e8be98"/><rect x="7" y="3" width="2" height="1" fill="#262220"/><rect x="9" y="3" width="2" height="1" fill="#e8be98"/><rect x="11" y="3" width="2" height="1" fill="#262220"/><rect x="6" y="4" width="7" height="1" fill="#32353c"/><rect x="5" y="5" width="2" height="1" fill="#464a52"/><rect x="7" y="5" width="5" height="1" fill="#32353c"/><rect x="12" y="5" width="2" height="1" fill="#464a52"/><rect x="4" y="6" width="9" height="1" fill="#cc3333"/><rect x="4" y="7" width="9" height="1" fill="#464a52"/><rect x="13" y="7" width="2" height="1" fill="#cc3333"/><rect x="3" y="8" width="11" height="1" fill="#464a52"/><rect x="14" y="8" width="2" height="1" fill="#cc3333"/><rect x="3" y="9" width="2" height="1" fill="#464a52"/><rect x="6" y="9" width="6" height="1" fill="#464a52"/><rect x="13" y="9" width="2" height="1" fill="#464a52"/><rect x="3" y="10" width="2" height="1" fill="#c6946e"/><rect x="6" y="10" width="6" height="1" fill="#32353c"/><rect x="13" y="10" width="2" height="1" fill="#c6946e"/><rect x="5" y="11" width="7" height="1" fill="#464a52"/><rect x="5" y="12" width="2" height="2" fill="#464a52"/><rect x="11" y="12" width="2" height="2" fill="#464a52"/><rect x="4" y="14" width="3" height="1" fill="#32353c"/><rect x="11" y="14" width="3" height="1" fill="#32353c"/></g></svg>
|
||||
|
After Width: | Height: | Size: 3.8 KiB |
@@ -0,0 +1,6 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 22 16" shape-rendering="crispEdges" role="img" aria-label="kaio pixel-art buddy">
|
||||
<style>.f0{animation:f0 4s step-end infinite}.f1{animation:f1 4s step-end infinite;opacity:0}
|
||||
@keyframes f0{0%,90%{opacity:1}90.01%,95%{opacity:0}95.01%,100%{opacity:1}}
|
||||
@keyframes f1{0%,90%{opacity:0}90.01%,95%{opacity:1}95.01%,100%{opacity:0}}
|
||||
@media (prefers-reduced-motion: reduce){.f0,.f1,.f2{animation:none}.f0{opacity:1}.f1,.f2{opacity:0}}</style>
|
||||
<g class="f0"><rect x="5" y="0" width="1" height="1" fill="#ffdc50"/><rect x="8" y="0" width="1" height="1" fill="#ffdc50"/><rect x="11" y="0" width="1" height="1" fill="#ffdc50"/><rect x="5" y="1" width="8" height="1" fill="#ffdc50"/><rect x="4" y="2" width="10" height="1" fill="#ffdc50"/><rect x="5" y="3" width="8" height="1" fill="#e8be98"/><rect x="4" y="4" width="1" height="1" fill="#e8be98"/><rect x="5" y="4" width="1" height="1" fill="#262220"/><rect x="6" y="4" width="1" height="1" fill="#f0f0f0"/><rect x="7" y="4" width="3" height="1" fill="#e8be98"/><rect x="10" y="4" width="1" height="1" fill="#262220"/><rect x="11" y="4" width="1" height="1" fill="#f0f0f0"/><rect x="12" y="4" width="1" height="1" fill="#e8be98"/><rect x="13" y="4" width="1" height="2" fill="#c6946e"/><rect x="4" y="5" width="9" height="1" fill="#e8be98"/><rect x="5" y="6" width="1" height="1" fill="#c6946e"/><rect x="6" y="6" width="6" height="1" fill="#e8be98"/><rect x="12" y="6" width="1" height="1" fill="#c6946e"/><rect x="4" y="7" width="10" height="1" fill="#ebeef5"/><rect x="3" y="8" width="12" height="1" fill="#ebeef5"/><rect x="3" y="9" width="2" height="1" fill="#ebeef5"/><rect x="6" y="9" width="6" height="1" fill="#ebeef5"/><rect x="13" y="9" width="2" height="1" fill="#ebeef5"/><rect x="3" y="10" width="2" height="1" fill="#e8be98"/><rect x="6" y="10" width="6" height="1" fill="#4678e6"/><rect x="13" y="10" width="2" height="1" fill="#e8be98"/><rect x="5" y="11" width="8" height="1" fill="#ebeef5"/><rect x="5" y="12" width="2" height="2" fill="#ebeef5"/><rect x="11" y="12" width="2" height="2" fill="#ebeef5"/><rect x="4" y="14" width="3" height="1" fill="#4678e6"/><rect x="11" y="14" width="3" height="1" fill="#4678e6"/></g><g class="f1"><rect x="5" y="0" width="1" height="1" fill="#ffdc50"/><rect x="8" y="0" width="1" height="1" fill="#ffdc50"/><rect x="11" y="0" width="1" height="1" fill="#ffdc50"/><rect x="5" y="1" width="8" height="1" fill="#ffdc50"/><rect x="4" y="2" width="10" height="1" fill="#ffdc50"/><rect x="5" y="3" width="8" height="1" fill="#e8be98"/><rect x="4" y="4" width="1" height="1" fill="#e8be98"/><rect x="5" y="4" width="2" height="1" fill="#262220"/><rect x="7" y="4" width="3" height="1" fill="#e8be98"/><rect x="10" y="4" width="2" height="1" fill="#262220"/><rect x="12" y="4" width="1" height="1" fill="#e8be98"/><rect x="13" y="4" width="1" height="2" fill="#c6946e"/><rect x="4" y="5" width="9" height="1" fill="#e8be98"/><rect x="5" y="6" width="1" height="1" fill="#c6946e"/><rect x="6" y="6" width="6" height="1" fill="#e8be98"/><rect x="12" y="6" width="1" height="1" fill="#c6946e"/><rect x="4" y="7" width="10" height="1" fill="#ebeef5"/><rect x="3" y="8" width="12" height="1" fill="#ebeef5"/><rect x="3" y="9" width="2" height="1" fill="#ebeef5"/><rect x="6" y="9" width="6" height="1" fill="#ebeef5"/><rect x="13" y="9" width="2" height="1" fill="#ebeef5"/><rect x="3" y="10" width="2" height="1" fill="#e8be98"/><rect x="6" y="10" width="6" height="1" fill="#4678e6"/><rect x="13" y="10" width="2" height="1" fill="#e8be98"/><rect x="5" y="11" width="8" height="1" fill="#ebeef5"/><rect x="5" y="12" width="2" height="2" fill="#ebeef5"/><rect x="11" y="12" width="2" height="2" fill="#ebeef5"/><rect x="4" y="14" width="3" height="1" fill="#4678e6"/><rect x="11" y="14" width="3" height="1" fill="#4678e6"/></g></svg>
|
||||
|
After Width: | Height: | Size: 3.8 KiB |
@@ -0,0 +1,6 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 22 16" shape-rendering="crispEdges" role="img" aria-label="merlin pixel-art buddy">
|
||||
<style>.f0{animation:f0 4s step-end infinite}.f1{animation:f1 4s step-end infinite;opacity:0}
|
||||
@keyframes f0{0%,90%{opacity:1}90.01%,95%{opacity:0}95.01%,100%{opacity:1}}
|
||||
@keyframes f1{0%,90%{opacity:0}90.01%,95%{opacity:1}95.01%,100%{opacity:0}}
|
||||
@media (prefers-reduced-motion: reduce){.f0,.f1,.f2{animation:none}.f0{opacity:1}.f1,.f2{opacity:0}}</style>
|
||||
<g class="f0"><rect x="9" y="0" width="2" height="1" fill="#7846be"/><rect x="8" y="1" width="4" height="1" fill="#7846be"/><rect x="7" y="2" width="2" height="1" fill="#7846be"/><rect x="9" y="2" width="1" height="1" fill="#ffdc50"/><rect x="10" y="2" width="3" height="1" fill="#7846be"/><rect x="6" y="3" width="8" height="1" fill="#7846be"/><rect x="4" y="4" width="12" height="1" fill="#7846be"/><rect x="5" y="5" width="2" height="1" fill="#e8be98"/><rect x="7" y="5" width="1" height="1" fill="#262220"/><rect x="8" y="5" width="1" height="1" fill="#f0f0f0"/><rect x="9" y="5" width="2" height="1" fill="#e8be98"/><rect x="11" y="5" width="1" height="1" fill="#262220"/><rect x="12" y="5" width="1" height="1" fill="#f0f0f0"/><rect x="13" y="5" width="1" height="1" fill="#e8be98"/><rect x="4" y="6" width="2" height="1" fill="#ebeef5"/><rect x="6" y="6" width="7" height="1" fill="#e8be98"/><rect x="13" y="6" width="2" height="1" fill="#ebeef5"/><rect x="4" y="7" width="11" height="1" fill="#ebeef5"/><rect x="2" y="8" width="1" height="2" fill="#704a28"/><rect x="5" y="8" width="8" height="1" fill="#7846be"/><rect x="4" y="9" width="2" height="2" fill="#7846be"/><rect x="6" y="9" width="5" height="2" fill="#a573e6"/><rect x="11" y="9" width="3" height="2" fill="#7846be"/><rect x="2" y="10" width="1" height="1" fill="#d6a83c"/><rect x="2" y="11" width="1" height="4" fill="#704a28"/><rect x="5" y="11" width="8" height="3" fill="#7846be"/><rect x="4" y="14" width="10" height="1" fill="#7846be"/></g><g class="f1"><rect x="9" y="0" width="2" height="1" fill="#7846be"/><rect x="8" y="1" width="4" height="1" fill="#7846be"/><rect x="7" y="2" width="2" height="1" fill="#7846be"/><rect x="9" y="2" width="1" height="1" fill="#ffdc50"/><rect x="10" y="2" width="3" height="1" fill="#7846be"/><rect x="6" y="3" width="8" height="1" fill="#7846be"/><rect x="4" y="4" width="12" height="1" fill="#7846be"/><rect x="5" y="5" width="2" height="1" fill="#e8be98"/><rect x="7" y="5" width="2" height="1" fill="#262220"/><rect x="9" y="5" width="2" height="1" fill="#e8be98"/><rect x="11" y="5" width="2" height="1" fill="#262220"/><rect x="13" y="5" width="1" height="1" fill="#e8be98"/><rect x="4" y="6" width="2" height="1" fill="#ebeef5"/><rect x="6" y="6" width="7" height="1" fill="#e8be98"/><rect x="13" y="6" width="2" height="1" fill="#ebeef5"/><rect x="4" y="7" width="11" height="1" fill="#ebeef5"/><rect x="2" y="8" width="1" height="2" fill="#704a28"/><rect x="5" y="8" width="8" height="1" fill="#7846be"/><rect x="4" y="9" width="2" height="2" fill="#7846be"/><rect x="6" y="9" width="5" height="2" fill="#a573e6"/><rect x="11" y="9" width="3" height="2" fill="#7846be"/><rect x="2" y="10" width="1" height="1" fill="#d6a83c"/><rect x="2" y="11" width="1" height="4" fill="#704a28"/><rect x="5" y="11" width="8" height="3" fill="#7846be"/><rect x="4" y="14" width="10" height="1" fill="#7846be"/></g></svg>
|
||||
|
After Width: | Height: | Size: 3.3 KiB |
@@ -0,0 +1,21 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 46 16" shape-rendering="crispEdges" role="img" aria-label="Robin Hood buddy firing an arrow toward the prompt">
|
||||
<style>
|
||||
.i0{animation:i0 8s step-end infinite}
|
||||
.i1{animation:i1 8s step-end infinite;opacity:0}
|
||||
.s0{animation:s0 8s step-end infinite;opacity:0}
|
||||
.s1{animation:s1 8s step-end infinite;opacity:0}
|
||||
.s2{animation:s2 8s step-end infinite;opacity:0}
|
||||
.arrow{animation:fly 8s linear infinite;opacity:0}
|
||||
@keyframes i0{0%,20%{opacity:1}20.01%,24%{opacity:0}24.01%,50%{opacity:1}50.01%,82%{opacity:0}82.01%,100%{opacity:1}}
|
||||
@keyframes i1{0%,20%{opacity:0}20.01%,24%{opacity:1}24.01%,100%{opacity:0}}
|
||||
@keyframes s0{0%,50%{opacity:0}50.01%,58%{opacity:1}58.01%,100%{opacity:0}}
|
||||
@keyframes s1{0%,58%{opacity:0}58.01%,70%{opacity:1}70.01%,100%{opacity:0}}
|
||||
@keyframes s2{0%,70%{opacity:0}70.01%,82%{opacity:1}82.01%,100%{opacity:0}}
|
||||
@keyframes fly{0%,70%{opacity:0;transform:translateX(0)}70.5%{opacity:1}81%{opacity:1;transform:translateX(-30px)}81.5%,100%{opacity:0;transform:translateX(-30px)}}
|
||||
@media (prefers-reduced-motion: reduce){.i0,.i1,.s0,.s1,.s2,.arrow{animation:none}.i0{opacity:1}.i1,.s0,.s1,.s2,.arrow{opacity:0}}</style>
|
||||
<g class="i0" transform="translate(24,0)"><rect x="7" y="0" width="6" height="1" fill="#226633"/><rect x="5" y="1" width="2" height="1" fill="#226633"/><rect x="7" y="1" width="6" height="1" fill="#489e4a"/><rect x="13" y="1" width="2" height="2" fill="#226633"/><rect x="16" y="1" width="2" height="1" fill="#cc3333"/><rect x="4" y="2" width="2" height="1" fill="#226633"/><rect x="6" y="2" width="7" height="1" fill="#489e4a"/><rect x="15" y="2" width="3" height="1" fill="#cc3333"/><rect x="3" y="3" width="13" height="1" fill="#226633"/><rect x="16" y="3" width="1" height="1" fill="#cc3333"/><rect x="5" y="4" width="1" height="1" fill="#7a522e"/><rect x="6" y="4" width="7" height="1" fill="#e8be98"/><rect x="13" y="4" width="1" height="1" fill="#7a522e"/><rect x="4" y="5" width="1" height="1" fill="#e8be98"/><rect x="5" y="5" width="1" height="1" fill="#262220"/><rect x="6" y="5" width="1" height="1" fill="#f0f0f0"/><rect x="7" y="5" width="3" height="1" fill="#e8be98"/><rect x="10" y="5" width="1" height="1" fill="#262220"/><rect x="11" y="5" width="1" height="1" fill="#f0f0f0"/><rect x="12" y="5" width="2" height="1" fill="#e8be98"/><rect x="14" y="5" width="1" height="2" fill="#c6946e"/><rect x="4" y="6" width="10" height="1" fill="#e8be98"/><rect x="5" y="7" width="1" height="1" fill="#c6946e"/><rect x="6" y="7" width="6" height="1" fill="#e8be98"/><rect x="12" y="7" width="1" height="1" fill="#c6946e"/><rect x="2" y="8" width="1" height="2" fill="#704a28"/><rect x="6" y="8" width="2" height="1" fill="#226633"/><rect x="8" y="8" width="2" height="1" fill="#489e4a"/><rect x="10" y="8" width="2" height="1" fill="#226633"/><rect x="12" y="8" width="1" height="1" fill="#489e4a"/><rect x="5" y="9" width="1" height="2" fill="#226633"/><rect x="6" y="9" width="7" height="2" fill="#489e4a"/><rect x="13" y="9" width="1" height="2" fill="#226633"/><rect x="15" y="9" width="2" height="3" fill="#9e7042"/><rect x="2" y="10" width="1" height="1" fill="#f0f0f0"/><rect x="2" y="11" width="1" height="2" fill="#704a28"/><rect x="6" y="11" width="1" height="1" fill="#226633"/><rect x="7" y="11" width="2" height="1" fill="#704a28"/><rect x="9" y="11" width="1" height="1" fill="#d6a83c"/><rect x="10" y="11" width="2" height="1" fill="#704a28"/><rect x="12" y="11" width="1" height="1" fill="#226633"/><rect x="6" y="12" width="2" height="1" fill="#226633"/><rect x="8" y="12" width="3" height="1" fill="#489e4a"/><rect x="11" y="12" width="2" height="1" fill="#226633"/><rect x="7" y="13" width="2" height="2" fill="#226633"/><rect x="10" y="13" width="2" height="2" fill="#226633"/><rect x="6" y="15" width="3" height="1" fill="#704a28"/><rect x="10" y="15" width="3" height="1" fill="#704a28"/></g>
|
||||
<g class="i1" transform="translate(24,0)"><rect x="7" y="0" width="6" height="1" fill="#226633"/><rect x="5" y="1" width="2" height="1" fill="#226633"/><rect x="7" y="1" width="6" height="1" fill="#489e4a"/><rect x="13" y="1" width="2" height="2" fill="#226633"/><rect x="16" y="1" width="2" height="1" fill="#cc3333"/><rect x="4" y="2" width="2" height="1" fill="#226633"/><rect x="6" y="2" width="7" height="1" fill="#489e4a"/><rect x="15" y="2" width="3" height="1" fill="#cc3333"/><rect x="3" y="3" width="13" height="1" fill="#226633"/><rect x="16" y="3" width="1" height="1" fill="#cc3333"/><rect x="5" y="4" width="1" height="1" fill="#7a522e"/><rect x="6" y="4" width="7" height="1" fill="#e8be98"/><rect x="13" y="4" width="1" height="1" fill="#7a522e"/><rect x="4" y="5" width="1" height="1" fill="#e8be98"/><rect x="5" y="5" width="2" height="1" fill="#262220"/><rect x="7" y="5" width="3" height="1" fill="#e8be98"/><rect x="10" y="5" width="2" height="1" fill="#262220"/><rect x="12" y="5" width="2" height="1" fill="#e8be98"/><rect x="14" y="5" width="1" height="2" fill="#c6946e"/><rect x="4" y="6" width="10" height="1" fill="#e8be98"/><rect x="5" y="7" width="1" height="1" fill="#c6946e"/><rect x="6" y="7" width="6" height="1" fill="#e8be98"/><rect x="12" y="7" width="1" height="1" fill="#c6946e"/><rect x="2" y="8" width="1" height="2" fill="#704a28"/><rect x="6" y="8" width="2" height="1" fill="#226633"/><rect x="8" y="8" width="2" height="1" fill="#489e4a"/><rect x="10" y="8" width="2" height="1" fill="#226633"/><rect x="12" y="8" width="1" height="1" fill="#489e4a"/><rect x="5" y="9" width="1" height="2" fill="#226633"/><rect x="6" y="9" width="7" height="2" fill="#489e4a"/><rect x="13" y="9" width="1" height="2" fill="#226633"/><rect x="15" y="9" width="2" height="3" fill="#9e7042"/><rect x="2" y="10" width="1" height="1" fill="#f0f0f0"/><rect x="2" y="11" width="1" height="2" fill="#704a28"/><rect x="6" y="11" width="1" height="1" fill="#226633"/><rect x="7" y="11" width="2" height="1" fill="#704a28"/><rect x="9" y="11" width="1" height="1" fill="#d6a83c"/><rect x="10" y="11" width="2" height="1" fill="#704a28"/><rect x="12" y="11" width="1" height="1" fill="#226633"/><rect x="6" y="12" width="2" height="1" fill="#226633"/><rect x="8" y="12" width="3" height="1" fill="#489e4a"/><rect x="11" y="12" width="2" height="1" fill="#226633"/><rect x="7" y="13" width="2" height="2" fill="#226633"/><rect x="10" y="13" width="2" height="2" fill="#226633"/><rect x="6" y="15" width="3" height="1" fill="#704a28"/><rect x="10" y="15" width="3" height="1" fill="#704a28"/></g>
|
||||
<g class="s0" transform="translate(24,0)"><rect x="7" y="0" width="6" height="1" fill="#226633"/><rect x="5" y="1" width="2" height="1" fill="#226633"/><rect x="7" y="1" width="6" height="1" fill="#489e4a"/><rect x="13" y="1" width="2" height="2" fill="#226633"/><rect x="16" y="1" width="2" height="1" fill="#cc3333"/><rect x="4" y="2" width="2" height="1" fill="#226633"/><rect x="6" y="2" width="7" height="1" fill="#489e4a"/><rect x="15" y="2" width="3" height="1" fill="#cc3333"/><rect x="3" y="3" width="13" height="1" fill="#226633"/><rect x="16" y="3" width="1" height="1" fill="#cc3333"/><rect x="5" y="4" width="1" height="1" fill="#7a522e"/><rect x="6" y="4" width="7" height="1" fill="#e8be98"/><rect x="13" y="4" width="1" height="1" fill="#7a522e"/><rect x="4" y="5" width="1" height="1" fill="#e8be98"/><rect x="5" y="5" width="1" height="1" fill="#262220"/><rect x="6" y="5" width="1" height="1" fill="#f0f0f0"/><rect x="7" y="5" width="3" height="1" fill="#e8be98"/><rect x="10" y="5" width="1" height="1" fill="#262220"/><rect x="11" y="5" width="1" height="1" fill="#f0f0f0"/><rect x="12" y="5" width="2" height="1" fill="#e8be98"/><rect x="14" y="5" width="1" height="2" fill="#c6946e"/><rect x="4" y="6" width="10" height="1" fill="#e8be98"/><rect x="5" y="7" width="1" height="1" fill="#c6946e"/><rect x="6" y="7" width="6" height="1" fill="#e8be98"/><rect x="12" y="7" width="1" height="1" fill="#c6946e"/><rect x="1" y="8" width="1" height="2" fill="#704a28"/><rect x="6" y="8" width="2" height="1" fill="#226633"/><rect x="8" y="8" width="2" height="1" fill="#489e4a"/><rect x="10" y="8" width="2" height="1" fill="#226633"/><rect x="12" y="8" width="1" height="1" fill="#489e4a"/><rect x="3" y="9" width="2" height="2" fill="#e8be98"/><rect x="5" y="9" width="1" height="2" fill="#226633"/><rect x="6" y="9" width="7" height="2" fill="#489e4a"/><rect x="13" y="9" width="1" height="2" fill="#226633"/><rect x="15" y="9" width="2" height="3" fill="#9e7042"/><rect x="1" y="10" width="1" height="1" fill="#f0f0f0"/><rect x="1" y="11" width="1" height="2" fill="#704a28"/><rect x="6" y="11" width="1" height="1" fill="#226633"/><rect x="7" y="11" width="2" height="1" fill="#704a28"/><rect x="9" y="11" width="1" height="1" fill="#d6a83c"/><rect x="10" y="11" width="2" height="1" fill="#704a28"/><rect x="12" y="11" width="1" height="1" fill="#226633"/><rect x="6" y="12" width="2" height="1" fill="#226633"/><rect x="8" y="12" width="3" height="1" fill="#489e4a"/><rect x="11" y="12" width="2" height="1" fill="#226633"/><rect x="7" y="13" width="2" height="2" fill="#226633"/><rect x="10" y="13" width="2" height="2" fill="#226633"/><rect x="6" y="15" width="3" height="1" fill="#704a28"/><rect x="10" y="15" width="3" height="1" fill="#704a28"/></g>
|
||||
<g class="s1" transform="translate(24,0)"><rect x="7" y="0" width="6" height="1" fill="#226633"/><rect x="5" y="1" width="2" height="1" fill="#226633"/><rect x="7" y="1" width="6" height="1" fill="#489e4a"/><rect x="13" y="1" width="2" height="2" fill="#226633"/><rect x="16" y="1" width="2" height="1" fill="#cc3333"/><rect x="4" y="2" width="2" height="1" fill="#226633"/><rect x="6" y="2" width="7" height="1" fill="#489e4a"/><rect x="15" y="2" width="3" height="1" fill="#cc3333"/><rect x="3" y="3" width="13" height="1" fill="#226633"/><rect x="16" y="3" width="1" height="1" fill="#cc3333"/><rect x="5" y="4" width="1" height="1" fill="#7a522e"/><rect x="6" y="4" width="7" height="1" fill="#e8be98"/><rect x="13" y="4" width="1" height="1" fill="#7a522e"/><rect x="4" y="5" width="1" height="1" fill="#e8be98"/><rect x="5" y="5" width="1" height="1" fill="#262220"/><rect x="6" y="5" width="1" height="1" fill="#f0f0f0"/><rect x="7" y="5" width="3" height="1" fill="#e8be98"/><rect x="10" y="5" width="1" height="1" fill="#262220"/><rect x="11" y="5" width="1" height="1" fill="#f0f0f0"/><rect x="12" y="5" width="2" height="1" fill="#e8be98"/><rect x="14" y="5" width="1" height="2" fill="#c6946e"/><rect x="0" y="6" width="1" height="7" fill="#704a28"/><rect x="4" y="6" width="10" height="1" fill="#e8be98"/><rect x="5" y="7" width="1" height="1" fill="#c6946e"/><rect x="6" y="7" width="6" height="1" fill="#e8be98"/><rect x="12" y="7" width="1" height="1" fill="#c6946e"/><rect x="2" y="8" width="1" height="3" fill="#f0f0f0"/><rect x="3" y="8" width="4" height="1" fill="#704a28"/><rect x="7" y="8" width="2" height="1" fill="#e8be98"/><rect x="9" y="8" width="4" height="1" fill="#f0f0f0"/><rect x="13" y="8" width="1" height="1" fill="#c6946e"/><rect x="5" y="9" width="1" height="2" fill="#226633"/><rect x="6" y="9" width="7" height="2" fill="#489e4a"/><rect x="13" y="9" width="1" height="2" fill="#226633"/><rect x="15" y="9" width="2" height="3" fill="#9e7042"/><rect x="6" y="11" width="1" height="1" fill="#226633"/><rect x="7" y="11" width="2" height="1" fill="#704a28"/><rect x="9" y="11" width="1" height="1" fill="#d6a83c"/><rect x="10" y="11" width="2" height="1" fill="#704a28"/><rect x="12" y="11" width="1" height="1" fill="#226633"/><rect x="6" y="12" width="2" height="1" fill="#226633"/><rect x="8" y="12" width="3" height="1" fill="#489e4a"/><rect x="11" y="12" width="2" height="1" fill="#226633"/><rect x="7" y="13" width="2" height="2" fill="#226633"/><rect x="10" y="13" width="2" height="2" fill="#226633"/><rect x="6" y="15" width="3" height="1" fill="#704a28"/><rect x="10" y="15" width="3" height="1" fill="#704a28"/></g>
|
||||
<g class="s2" transform="translate(24,0)"><rect x="7" y="0" width="6" height="1" fill="#226633"/><rect x="5" y="1" width="2" height="1" fill="#226633"/><rect x="7" y="1" width="6" height="1" fill="#489e4a"/><rect x="13" y="1" width="2" height="2" fill="#226633"/><rect x="16" y="1" width="2" height="1" fill="#cc3333"/><rect x="4" y="2" width="2" height="1" fill="#226633"/><rect x="6" y="2" width="7" height="1" fill="#489e4a"/><rect x="15" y="2" width="3" height="1" fill="#cc3333"/><rect x="3" y="3" width="13" height="1" fill="#226633"/><rect x="16" y="3" width="1" height="1" fill="#cc3333"/><rect x="5" y="4" width="1" height="1" fill="#7a522e"/><rect x="6" y="4" width="7" height="1" fill="#e8be98"/><rect x="13" y="4" width="1" height="1" fill="#7a522e"/><rect x="4" y="5" width="1" height="1" fill="#e8be98"/><rect x="5" y="5" width="1" height="1" fill="#262220"/><rect x="6" y="5" width="1" height="1" fill="#f0f0f0"/><rect x="7" y="5" width="3" height="1" fill="#e8be98"/><rect x="10" y="5" width="1" height="1" fill="#262220"/><rect x="11" y="5" width="1" height="1" fill="#f0f0f0"/><rect x="12" y="5" width="2" height="1" fill="#e8be98"/><rect x="14" y="5" width="1" height="2" fill="#c6946e"/><rect x="4" y="6" width="10" height="1" fill="#e8be98"/><rect x="5" y="7" width="1" height="2" fill="#c6946e"/><rect x="6" y="7" width="6" height="1" fill="#e8be98"/><rect x="12" y="7" width="1" height="1" fill="#c6946e"/><rect x="0" y="8" width="1" height="5" fill="#704a28"/><rect x="1" y="8" width="1" height="3" fill="#f0f0f0"/><rect x="3" y="8" width="2" height="1" fill="#e8be98"/><rect x="6" y="8" width="2" height="1" fill="#226633"/><rect x="8" y="8" width="2" height="1" fill="#489e4a"/><rect x="10" y="8" width="2" height="1" fill="#226633"/><rect x="12" y="8" width="1" height="1" fill="#489e4a"/><rect x="5" y="9" width="1" height="2" fill="#226633"/><rect x="6" y="9" width="7" height="2" fill="#489e4a"/><rect x="13" y="9" width="1" height="2" fill="#226633"/><rect x="15" y="9" width="2" height="3" fill="#9e7042"/><rect x="6" y="11" width="1" height="1" fill="#226633"/><rect x="7" y="11" width="2" height="1" fill="#704a28"/><rect x="9" y="11" width="1" height="1" fill="#d6a83c"/><rect x="10" y="11" width="2" height="1" fill="#704a28"/><rect x="12" y="11" width="1" height="1" fill="#226633"/><rect x="6" y="12" width="2" height="1" fill="#226633"/><rect x="8" y="12" width="3" height="1" fill="#489e4a"/><rect x="11" y="12" width="2" height="1" fill="#226633"/><rect x="7" y="13" width="2" height="2" fill="#226633"/><rect x="10" y="13" width="2" height="2" fill="#226633"/><rect x="6" y="15" width="3" height="1" fill="#704a28"/><rect x="10" y="15" width="3" height="1" fill="#704a28"/></g>
|
||||
<g class="arrow"><rect x="22" y="8" width="4" height="1" fill="#9e7042"/><rect x="21" y="8" width="1" height="1" fill="#f0f0f0"/></g></svg>
|
||||
|
After Width: | Height: | Size: 14 KiB |
|
After Width: | Height: | Size: 8.3 KiB |
@@ -0,0 +1,6 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 22 16" shape-rendering="crispEdges" role="img" aria-label="strawhat pixel-art buddy">
|
||||
<style>.f0{animation:f0 4s step-end infinite}.f1{animation:f1 4s step-end infinite;opacity:0}
|
||||
@keyframes f0{0%,90%{opacity:1}90.01%,95%{opacity:0}95.01%,100%{opacity:1}}
|
||||
@keyframes f1{0%,90%{opacity:0}90.01%,95%{opacity:1}95.01%,100%{opacity:0}}
|
||||
@media (prefers-reduced-motion: reduce){.f0,.f1,.f2{animation:none}.f0{opacity:1}.f1,.f2{opacity:0}}</style>
|
||||
<g class="f0"><rect x="6" y="0" width="6" height="1" fill="#e6c85a"/><rect x="4" y="1" width="10" height="1" fill="#e6c85a"/><rect x="3" y="2" width="3" height="1" fill="#e6c85a"/><rect x="6" y="2" width="6" height="1" fill="#cc3333"/><rect x="12" y="2" width="3" height="1" fill="#e6c85a"/><rect x="2" y="3" width="14" height="1" fill="#e6c85a"/><rect x="5" y="4" width="1" height="1" fill="#282624"/><rect x="6" y="4" width="6" height="1" fill="#e8be98"/><rect x="12" y="4" width="1" height="1" fill="#282624"/><rect x="4" y="5" width="1" height="1" fill="#e8be98"/><rect x="5" y="5" width="1" height="1" fill="#262220"/><rect x="6" y="5" width="1" height="1" fill="#f0f0f0"/><rect x="7" y="5" width="3" height="1" fill="#e8be98"/><rect x="10" y="5" width="1" height="1" fill="#262220"/><rect x="11" y="5" width="1" height="1" fill="#f0f0f0"/><rect x="12" y="5" width="1" height="1" fill="#e8be98"/><rect x="13" y="5" width="1" height="2" fill="#c6946e"/><rect x="4" y="6" width="9" height="1" fill="#e8be98"/><rect x="5" y="7" width="1" height="1" fill="#c6946e"/><rect x="6" y="7" width="6" height="1" fill="#e8be98"/><rect x="12" y="7" width="1" height="1" fill="#c6946e"/><rect x="4" y="8" width="9" height="1" fill="#cc3333"/><rect x="3" y="9" width="2" height="2" fill="#e8be98"/><rect x="5" y="9" width="7" height="1" fill="#cc3333"/><rect x="12" y="9" width="2" height="2" fill="#e8be98"/><rect x="6" y="10" width="5" height="1" fill="#cc3333"/><rect x="5" y="11" width="7" height="1" fill="#4678e6"/><rect x="5" y="12" width="2" height="1" fill="#4678e6"/><rect x="11" y="12" width="2" height="1" fill="#4678e6"/><rect x="5" y="13" width="2" height="2" fill="#e8be98"/><rect x="11" y="13" width="2" height="2" fill="#e8be98"/><rect x="4" y="15" width="3" height="1" fill="#704a28"/><rect x="11" y="15" width="3" height="1" fill="#704a28"/></g><g class="f1"><rect x="6" y="0" width="6" height="1" fill="#e6c85a"/><rect x="4" y="1" width="10" height="1" fill="#e6c85a"/><rect x="3" y="2" width="3" height="1" fill="#e6c85a"/><rect x="6" y="2" width="6" height="1" fill="#cc3333"/><rect x="12" y="2" width="3" height="1" fill="#e6c85a"/><rect x="2" y="3" width="14" height="1" fill="#e6c85a"/><rect x="5" y="4" width="1" height="1" fill="#282624"/><rect x="6" y="4" width="6" height="1" fill="#e8be98"/><rect x="12" y="4" width="1" height="1" fill="#282624"/><rect x="4" y="5" width="1" height="1" fill="#e8be98"/><rect x="5" y="5" width="2" height="1" fill="#262220"/><rect x="7" y="5" width="3" height="1" fill="#e8be98"/><rect x="10" y="5" width="2" height="1" fill="#262220"/><rect x="12" y="5" width="1" height="1" fill="#e8be98"/><rect x="13" y="5" width="1" height="2" fill="#c6946e"/><rect x="4" y="6" width="9" height="1" fill="#e8be98"/><rect x="5" y="7" width="1" height="1" fill="#c6946e"/><rect x="6" y="7" width="6" height="1" fill="#e8be98"/><rect x="12" y="7" width="1" height="1" fill="#c6946e"/><rect x="4" y="8" width="9" height="1" fill="#cc3333"/><rect x="3" y="9" width="2" height="2" fill="#e8be98"/><rect x="5" y="9" width="7" height="1" fill="#cc3333"/><rect x="12" y="9" width="2" height="2" fill="#e8be98"/><rect x="6" y="10" width="5" height="1" fill="#cc3333"/><rect x="5" y="11" width="7" height="1" fill="#4678e6"/><rect x="5" y="12" width="2" height="1" fill="#4678e6"/><rect x="11" y="12" width="2" height="1" fill="#4678e6"/><rect x="5" y="13" width="2" height="2" fill="#e8be98"/><rect x="11" y="13" width="2" height="2" fill="#e8be98"/><rect x="4" y="15" width="3" height="1" fill="#704a28"/><rect x="11" y="15" width="3" height="1" fill="#704a28"/></g></svg>
|
||||
|
After Width: | Height: | Size: 4.0 KiB |
|
After Width: | Height: | Size: 40 KiB |
@@ -0,0 +1,98 @@
|
||||
<svg width="160" height="32" viewBox="0 0 160 32" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<mask id="mask0_4703_22805" style="mask-type:alpha" maskUnits="userSpaceOnUse" x="0" y="0" width="36" height="32">
|
||||
<path d="M35.053 14.3536C35.7203 15.5094 35.7203 16.9334 35.053 18.0893L28.1325 30.0759C27.4651 31.2318 26.2319 31.9438 24.8972 31.9438L11.0562 31.9438C9.72158 31.9438 8.48832 31.2318 7.821 30.0759L0.900492 18.0893C0.233169 16.9334 0.233174 15.5094 0.900496 14.3536L7.821 2.36688C8.48832 1.21105 9.72158 0.499022 11.0562 0.499022L24.8972 0.499023C26.2319 0.499023 27.4651 1.21105 28.1325 2.36688L35.053 14.3536Z" fill="#D9D9D9"/>
|
||||
</mask>
|
||||
<g mask="url(#mask0_4703_22805)">
|
||||
<g filter="url(#filter0_f_4703_22805)">
|
||||
<path d="M26.3148 19.7055C40.5006 12.9153 15.0311 -36.3156 19.1559 -20.1938C21.8339 -9.72652 12.4874 -11.2916 13.4349 -5.51437C14.0986 -1.46769 14.2485 1.61091 13.7596 3.97702C13.1705 6.82821 13.0039 11.6582 14.3868 14.2202C16.7751 18.6453 18.4978 23.4472 26.3148 19.7055Z" fill="#D6FFF4"/>
|
||||
</g>
|
||||
<g filter="url(#filter1_f_4703_22805)">
|
||||
<path d="M-6.45699 -6.11653C-3.84677 -5.84225 -0.29764 -4.85464 4.81324 -2.63387C17.0192 2.66984 18.3432 23.8419 10.9243 30.3573C3.78258 36.6293 -45.1307 26.1891 -38.9034 25.6897C-8.63644 23.2621 1.15125 18.1182 -9.31819 6.22888C-11.8405 3.36448 -10.2527 -6.5154 -6.45699 -6.11653Z" fill="#84DBFF"/>
|
||||
</g>
|
||||
<g filter="url(#filter2_f_4703_22805)">
|
||||
<path d="M33.5295 48.8004C46.5191 42.5828 23.1974 -2.49638 26.9743 12.2659C29.4265 21.8504 20.8682 20.4173 21.7358 25.7074C22.2934 29.107 22.4549 31.7605 22.132 33.8486C21.6871 36.7258 21.511 41.697 22.8706 44.2714C24.9313 48.1731 26.6617 52.0878 33.5295 48.8004Z" fill="#B0009A"/>
|
||||
</g>
|
||||
<g filter="url(#filter3_f_4703_22805)">
|
||||
<path d="M37.3555 4.74019C31.7239 -2.82371 8.42239 16.3745 16.6289 12.52C21.9571 10.0175 22.3825 15.5534 25.3958 14.4783C26.0561 14.2427 26.6718 14.0448 27.2475 13.8861C30.1601 13.0831 36.9802 12.2824 38.463 9.65006C39.1115 8.49886 39.063 7.0335 37.3555 4.74019Z" fill="#FF4CE9"/>
|
||||
</g>
|
||||
<g filter="url(#filter4_f_4703_22805)">
|
||||
<path d="M32.0528 23.5665C26.7611 14.5954 -17.548 39.0576 -3.23314 33.9874C6.06088 30.6956 4.25693 37.4278 9.36204 35.981C12.8358 34.9966 15.4868 34.4746 17.515 34.4631C20.0655 34.4487 23.9034 34.2689 26.1017 32.9753C30.2368 30.542 35.1142 28.7565 32.0528 23.5665Z" fill="#1C38FF"/>
|
||||
</g>
|
||||
<path d="M-2.35449 -0.355708C-2.35449 -2.41889 -0.681952 -4.09143 1.38123 -4.09143H32.3959C34.4591 -4.09143 36.1316 -2.41889 36.1316 -0.355707V32.3793C36.1316 34.4425 34.4591 36.115 32.3959 36.115H1.38123C-0.68195 36.115 -2.35449 34.4425 -2.35449 32.3793V-0.355708Z" fill="white"/>
|
||||
</g>
|
||||
<mask id="mask1_4703_22805" style="mask-type:alpha" maskUnits="userSpaceOnUse" x="0" y="0" width="36" height="32">
|
||||
<path d="M35.053 14.3536C35.7203 15.5094 35.7203 16.9334 35.053 18.0893L28.1325 30.0759C27.4651 31.2318 26.2319 31.9438 24.8972 31.9438L11.0562 31.9438C9.72158 31.9438 8.48832 31.2318 7.821 30.0759L0.900492 18.0893C0.233169 16.9334 0.233174 15.5094 0.900496 14.3536L7.821 2.36688C8.48832 1.21105 9.72158 0.499022 11.0562 0.499022L24.8972 0.499023C26.2319 0.499023 27.4651 1.21105 28.1325 2.36688L35.053 14.3536Z" fill="#D9D9D9"/>
|
||||
</mask>
|
||||
<g mask="url(#mask1_4703_22805)">
|
||||
<g filter="url(#filter5_f_4703_22805)">
|
||||
<path d="M26.3147 19.7055C40.5005 12.9152 15.0309 -36.3156 19.1557 -20.1938C21.8338 -9.72655 12.4873 -11.2916 13.4348 -5.51439C14.7775 2.6729 14.0172 6.89746 10.1185 9.27613C16.4624 12.5624 14.966 25.1377 26.3147 19.7055Z" fill="#D6FFF4"/>
|
||||
</g>
|
||||
<g filter="url(#filter6_f_4703_22805)">
|
||||
<path d="M-19.6411 -3.00718C-11.5566 -1.94928 -14.3515 -10.9613 4.81324 -2.63387C17.0192 2.66983 18.3432 23.8419 10.9243 30.3573C3.78258 36.6293 -45.1307 26.1891 -38.9034 25.6897C-1.73287 22.7084 4.55076 15.6303 -19.6411 -3.00718Z" fill="#00AAF0"/>
|
||||
</g>
|
||||
<g filter="url(#filter7_f_4703_22805)">
|
||||
<path d="M33.5299 48.8005C46.5194 42.5828 23.1977 -2.49636 26.9747 12.2659C29.4269 21.8504 20.8686 20.4173 21.7362 25.7074C22.9657 33.2042 22.2695 37.0725 18.6996 39.2506C24.5085 42.2598 23.1383 53.7745 33.5299 48.8005Z" fill="#B0009A"/>
|
||||
</g>
|
||||
<g filter="url(#filter8_f_4703_22805)">
|
||||
<path d="M37.3555 4.74023C31.7238 -2.82367 8.42231 16.3745 16.6289 12.5201C21.957 10.0175 22.3825 15.5534 25.3957 14.4783C29.6661 12.9548 32.068 13.0071 33.8946 15.039C34.8157 11.0781 41.8608 10.7914 37.3555 4.74023Z" fill="#F900DA"/>
|
||||
</g>
|
||||
<g filter="url(#filter9_f_4703_22805)">
|
||||
<path d="M32.0529 23.5666C26.7611 14.5955 -17.5479 39.0576 -3.23308 33.9875C6.06094 30.6957 4.25699 37.4278 9.3621 35.9811C16.5969 33.9308 20.2627 33.8865 22.166 36.2823C25.3388 31.4103 36.2863 30.7435 32.0529 23.5666Z" fill="#1C38FF"/>
|
||||
</g>
|
||||
<rect x="-11.1597" y="-10.0071" width="47.4699" height="48.539" fill="#090F1D"/>
|
||||
</g>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M10.3818 21.4999C10.1215 21.4999 9.85957 21.4169 9.63832 21.2477C9.10474 20.8362 9.00388 20.0699 9.41546 19.5364L14.2844 13.2082C14.4829 12.9495 14.7773 12.782 15.0994 12.7413C15.428 12.699 15.7501 12.7901 16.0055 12.9935L20.5931 16.5968L24.6063 11.4187C25.0195 10.8835 25.7841 10.7843 26.3177 11.2007C26.8513 11.6139 26.9489 12.3802 26.5357 12.9121L21.7692 19.0613C21.5707 19.3184 21.2779 19.4859 20.9558 19.525C20.6305 19.5689 20.3084 19.4745 20.0513 19.2744L15.4671 15.6728L11.3497 21.0232C11.1089 21.3356 10.7478 21.4999 10.3818 21.4999" fill="white"/>
|
||||
<path d="M48.4439 24H44.4963L50.5113 6.57605H55.2586L61.2651 24H57.3175L52.953 10.5577H52.8169L48.4439 24ZM48.1972 17.1512H57.5217V20.0269H48.1972V17.1512ZM67.0462 6.57605V24H63.3623V6.57605H67.0462ZM77.598 5.7593L71.9828 26.6204H68.8605L74.4756 5.7593H77.598ZM79.4122 6.57605H83.9554L88.7538 18.2828H88.958L93.7564 6.57605H98.2995V24H94.7263V12.6591H94.5816L90.0725 23.9149H87.6393L83.1302 12.6166H82.9855V24H79.4122V6.57605ZM101.335 24V6.57605H105.019V20.9627H112.488V24H101.335ZM123.476 24H119.529L125.544 6.57605H130.291L136.297 24H132.35L127.985 10.5577H127.849L123.476 24ZM123.229 17.1512H132.554V20.0269H123.229V17.1512ZM138.395 24V6.57605H145.269C146.59 6.57605 147.716 6.82845 148.646 7.33325C149.577 7.83237 150.286 8.52717 150.773 9.41765C151.267 10.3025 151.514 11.3234 151.514 12.4805C151.514 13.6375 151.264 14.6584 150.765 15.5433C150.266 16.4281 149.543 17.1172 148.595 17.6107C147.654 18.1041 146.514 18.3508 145.175 18.3508H140.794V15.3986H144.58C145.289 15.3986 145.873 15.2767 146.332 15.0328C146.797 14.7832 147.143 14.4401 147.37 14.0034C147.603 13.5609 147.719 13.0533 147.719 12.4805C147.719 11.9019 147.603 11.3971 147.37 10.9661C147.143 10.5293 146.797 10.1919 146.332 9.95364C145.867 9.70975 145.277 9.58781 144.563 9.58781H142.078V24H138.395ZM157.59 6.57605V24H153.906V6.57605H157.59Z" fill="#FFFFFF"/>
|
||||
<defs>
|
||||
<filter id="filter0_f_4703_22805" x="-25.7853" y="-62.5267" width="95.551" height="122.685" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
||||
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
|
||||
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
|
||||
<feGaussianBlur stdDeviation="19.5544" result="effect1_foregroundBlur_4703_22805"/>
|
||||
</filter>
|
||||
<filter id="filter1_f_4703_22805" x="-98.9884" y="-65.667" width="174.021" height="157.563" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
||||
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
|
||||
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
|
||||
<feGaussianBlur stdDeviation="29.7694" result="effect1_foregroundBlur_4703_22805"/>
|
||||
</filter>
|
||||
<filter id="filter2_f_4703_22805" x="-14.1369" y="-26.4972" width="87.4532" height="112.339" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
||||
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
|
||||
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
|
||||
<feGaussianBlur stdDeviation="17.9054" result="effect1_foregroundBlur_4703_22805"/>
|
||||
</filter>
|
||||
<filter id="filter3_f_4703_22805" x="-20.9145" y="-32.853" width="95.5731" height="83.2823" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
||||
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
|
||||
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
|
||||
<feGaussianBlur stdDeviation="17.9054" result="effect1_foregroundBlur_4703_22805"/>
|
||||
</filter>
|
||||
<filter id="filter4_f_4703_22805" x="-41.8776" y="-14.241" width="110.701" height="86.2396" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
||||
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
|
||||
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
|
||||
<feGaussianBlur stdDeviation="17.9054" result="effect1_foregroundBlur_4703_22805"/>
|
||||
</filter>
|
||||
<filter id="filter5_f_4703_22805" x="-28.9904" y="-62.5269" width="98.7561" height="122.685" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
||||
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
|
||||
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
|
||||
<feGaussianBlur stdDeviation="19.5544" result="effect1_foregroundBlur_4703_22805"/>
|
||||
</filter>
|
||||
<filter id="filter6_f_4703_22805" x="-98.9884" y="-65.7386" width="174.021" height="157.634" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
||||
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
|
||||
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
|
||||
<feGaussianBlur stdDeviation="29.7694" result="effect1_foregroundBlur_4703_22805"/>
|
||||
</filter>
|
||||
<filter id="filter7_f_4703_22805" x="-17.1113" y="-26.4972" width="90.4281" height="112.339" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
||||
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
|
||||
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
|
||||
<feGaussianBlur stdDeviation="17.9054" result="effect1_foregroundBlur_4703_22805"/>
|
||||
</filter>
|
||||
<filter id="filter8_f_4703_22805" x="-20.9145" y="-32.853" width="95.5729" height="83.7026" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
||||
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
|
||||
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
|
||||
<feGaussianBlur stdDeviation="17.9054" result="effect1_foregroundBlur_4703_22805"/>
|
||||
</filter>
|
||||
<filter id="filter9_f_4703_22805" x="-41.8774" y="-14.2409" width="110.701" height="86.3339" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
||||
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
|
||||
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
|
||||
<feGaussianBlur stdDeviation="17.9054" result="effect1_foregroundBlur_4703_22805"/>
|
||||
</filter>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 10 KiB |
@@ -0,0 +1,98 @@
|
||||
<svg width="160" height="32" viewBox="0 0 160 32" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<mask id="mask0_4703_22805" style="mask-type:alpha" maskUnits="userSpaceOnUse" x="0" y="0" width="36" height="32">
|
||||
<path d="M35.053 14.3536C35.7203 15.5094 35.7203 16.9334 35.053 18.0893L28.1325 30.0759C27.4651 31.2318 26.2319 31.9438 24.8972 31.9438L11.0562 31.9438C9.72158 31.9438 8.48832 31.2318 7.821 30.0759L0.900492 18.0893C0.233169 16.9334 0.233174 15.5094 0.900496 14.3536L7.821 2.36688C8.48832 1.21105 9.72158 0.499022 11.0562 0.499022L24.8972 0.499023C26.2319 0.499023 27.4651 1.21105 28.1325 2.36688L35.053 14.3536Z" fill="#D9D9D9"/>
|
||||
</mask>
|
||||
<g mask="url(#mask0_4703_22805)">
|
||||
<g filter="url(#filter0_f_4703_22805)">
|
||||
<path d="M26.3148 19.7055C40.5006 12.9153 15.0311 -36.3156 19.1559 -20.1938C21.8339 -9.72652 12.4874 -11.2916 13.4349 -5.51437C14.0986 -1.46769 14.2485 1.61091 13.7596 3.97702C13.1705 6.82821 13.0039 11.6582 14.3868 14.2202C16.7751 18.6453 18.4978 23.4472 26.3148 19.7055Z" fill="#D6FFF4"/>
|
||||
</g>
|
||||
<g filter="url(#filter1_f_4703_22805)">
|
||||
<path d="M-6.45699 -6.11653C-3.84677 -5.84225 -0.29764 -4.85464 4.81324 -2.63387C17.0192 2.66984 18.3432 23.8419 10.9243 30.3573C3.78258 36.6293 -45.1307 26.1891 -38.9034 25.6897C-8.63644 23.2621 1.15125 18.1182 -9.31819 6.22888C-11.8405 3.36448 -10.2527 -6.5154 -6.45699 -6.11653Z" fill="#84DBFF"/>
|
||||
</g>
|
||||
<g filter="url(#filter2_f_4703_22805)">
|
||||
<path d="M33.5295 48.8004C46.5191 42.5828 23.1974 -2.49638 26.9743 12.2659C29.4265 21.8504 20.8682 20.4173 21.7358 25.7074C22.2934 29.107 22.4549 31.7605 22.132 33.8486C21.6871 36.7258 21.511 41.697 22.8706 44.2714C24.9313 48.1731 26.6617 52.0878 33.5295 48.8004Z" fill="#B0009A"/>
|
||||
</g>
|
||||
<g filter="url(#filter3_f_4703_22805)">
|
||||
<path d="M37.3555 4.74019C31.7239 -2.82371 8.42239 16.3745 16.6289 12.52C21.9571 10.0175 22.3825 15.5534 25.3958 14.4783C26.0561 14.2427 26.6718 14.0448 27.2475 13.8861C30.1601 13.0831 36.9802 12.2824 38.463 9.65006C39.1115 8.49886 39.063 7.0335 37.3555 4.74019Z" fill="#FF4CE9"/>
|
||||
</g>
|
||||
<g filter="url(#filter4_f_4703_22805)">
|
||||
<path d="M32.0528 23.5665C26.7611 14.5954 -17.548 39.0576 -3.23314 33.9874C6.06088 30.6956 4.25693 37.4278 9.36204 35.981C12.8358 34.9966 15.4868 34.4746 17.515 34.4631C20.0655 34.4487 23.9034 34.2689 26.1017 32.9753C30.2368 30.542 35.1142 28.7565 32.0528 23.5665Z" fill="#1C38FF"/>
|
||||
</g>
|
||||
<path d="M-2.35449 -0.355708C-2.35449 -2.41889 -0.681952 -4.09143 1.38123 -4.09143H32.3959C34.4591 -4.09143 36.1316 -2.41889 36.1316 -0.355707V32.3793C36.1316 34.4425 34.4591 36.115 32.3959 36.115H1.38123C-0.68195 36.115 -2.35449 34.4425 -2.35449 32.3793V-0.355708Z" fill="white"/>
|
||||
</g>
|
||||
<mask id="mask1_4703_22805" style="mask-type:alpha" maskUnits="userSpaceOnUse" x="0" y="0" width="36" height="32">
|
||||
<path d="M35.053 14.3536C35.7203 15.5094 35.7203 16.9334 35.053 18.0893L28.1325 30.0759C27.4651 31.2318 26.2319 31.9438 24.8972 31.9438L11.0562 31.9438C9.72158 31.9438 8.48832 31.2318 7.821 30.0759L0.900492 18.0893C0.233169 16.9334 0.233174 15.5094 0.900496 14.3536L7.821 2.36688C8.48832 1.21105 9.72158 0.499022 11.0562 0.499022L24.8972 0.499023C26.2319 0.499023 27.4651 1.21105 28.1325 2.36688L35.053 14.3536Z" fill="#D9D9D9"/>
|
||||
</mask>
|
||||
<g mask="url(#mask1_4703_22805)">
|
||||
<g filter="url(#filter5_f_4703_22805)">
|
||||
<path d="M26.3147 19.7055C40.5005 12.9152 15.0309 -36.3156 19.1557 -20.1938C21.8338 -9.72655 12.4873 -11.2916 13.4348 -5.51439C14.7775 2.6729 14.0172 6.89746 10.1185 9.27613C16.4624 12.5624 14.966 25.1377 26.3147 19.7055Z" fill="#D6FFF4"/>
|
||||
</g>
|
||||
<g filter="url(#filter6_f_4703_22805)">
|
||||
<path d="M-19.6411 -3.00718C-11.5566 -1.94928 -14.3515 -10.9613 4.81324 -2.63387C17.0192 2.66983 18.3432 23.8419 10.9243 30.3573C3.78258 36.6293 -45.1307 26.1891 -38.9034 25.6897C-1.73287 22.7084 4.55076 15.6303 -19.6411 -3.00718Z" fill="#00AAF0"/>
|
||||
</g>
|
||||
<g filter="url(#filter7_f_4703_22805)">
|
||||
<path d="M33.5299 48.8005C46.5194 42.5828 23.1977 -2.49636 26.9747 12.2659C29.4269 21.8504 20.8686 20.4173 21.7362 25.7074C22.9657 33.2042 22.2695 37.0725 18.6996 39.2506C24.5085 42.2598 23.1383 53.7745 33.5299 48.8005Z" fill="#B0009A"/>
|
||||
</g>
|
||||
<g filter="url(#filter8_f_4703_22805)">
|
||||
<path d="M37.3555 4.74023C31.7238 -2.82367 8.42231 16.3745 16.6289 12.5201C21.957 10.0175 22.3825 15.5534 25.3957 14.4783C29.6661 12.9548 32.068 13.0071 33.8946 15.039C34.8157 11.0781 41.8608 10.7914 37.3555 4.74023Z" fill="#F900DA"/>
|
||||
</g>
|
||||
<g filter="url(#filter9_f_4703_22805)">
|
||||
<path d="M32.0529 23.5666C26.7611 14.5955 -17.5479 39.0576 -3.23308 33.9875C6.06094 30.6957 4.25699 37.4278 9.3621 35.9811C16.5969 33.9308 20.2627 33.8865 22.166 36.2823C25.3388 31.4103 36.2863 30.7435 32.0529 23.5666Z" fill="#1C38FF"/>
|
||||
</g>
|
||||
<rect x="-11.1597" y="-10.0071" width="47.4699" height="48.539" fill="#090F1D"/>
|
||||
</g>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M10.3818 21.4999C10.1215 21.4999 9.85957 21.4169 9.63832 21.2477C9.10474 20.8362 9.00388 20.0699 9.41546 19.5364L14.2844 13.2082C14.4829 12.9495 14.7773 12.782 15.0994 12.7413C15.428 12.699 15.7501 12.7901 16.0055 12.9935L20.5931 16.5968L24.6063 11.4187C25.0195 10.8835 25.7841 10.7843 26.3177 11.2007C26.8513 11.6139 26.9489 12.3802 26.5357 12.9121L21.7692 19.0613C21.5707 19.3184 21.2779 19.4859 20.9558 19.525C20.6305 19.5689 20.3084 19.4745 20.0513 19.2744L15.4671 15.6728L11.3497 21.0232C11.1089 21.3356 10.7478 21.4999 10.3818 21.4999" fill="white"/>
|
||||
<path d="M48.4439 24H44.4963L50.5113 6.57605H55.2586L61.2651 24H57.3175L52.953 10.5577H52.8169L48.4439 24ZM48.1972 17.1512H57.5217V20.0269H48.1972V17.1512ZM67.0462 6.57605V24H63.3623V6.57605H67.0462ZM77.598 5.7593L71.9828 26.6204H68.8605L74.4756 5.7593H77.598ZM79.4122 6.57605H83.9554L88.7538 18.2828H88.958L93.7564 6.57605H98.2995V24H94.7263V12.6591H94.5816L90.0725 23.9149H87.6393L83.1302 12.6166H82.9855V24H79.4122V6.57605ZM101.335 24V6.57605H105.019V20.9627H112.488V24H101.335ZM123.476 24H119.529L125.544 6.57605H130.291L136.297 24H132.35L127.985 10.5577H127.849L123.476 24ZM123.229 17.1512H132.554V20.0269H123.229V17.1512ZM138.395 24V6.57605H145.269C146.59 6.57605 147.716 6.82845 148.646 7.33325C149.577 7.83237 150.286 8.52717 150.773 9.41765C151.267 10.3025 151.514 11.3234 151.514 12.4805C151.514 13.6375 151.264 14.6584 150.765 15.5433C150.266 16.4281 149.543 17.1172 148.595 17.6107C147.654 18.1041 146.514 18.3508 145.175 18.3508H140.794V15.3986H144.58C145.289 15.3986 145.873 15.2767 146.332 15.0328C146.797 14.7832 147.143 14.4401 147.37 14.0034C147.603 13.5609 147.719 13.0533 147.719 12.4805C147.719 11.9019 147.603 11.3971 147.37 10.9661C147.143 10.5293 146.797 10.1919 146.332 9.95364C145.867 9.70975 145.277 9.58781 144.563 9.58781H142.078V24H138.395ZM157.59 6.57605V24H153.906V6.57605H157.59Z" fill="#090F1D"/>
|
||||
<defs>
|
||||
<filter id="filter0_f_4703_22805" x="-25.7853" y="-62.5267" width="95.551" height="122.685" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
||||
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
|
||||
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
|
||||
<feGaussianBlur stdDeviation="19.5544" result="effect1_foregroundBlur_4703_22805"/>
|
||||
</filter>
|
||||
<filter id="filter1_f_4703_22805" x="-98.9884" y="-65.667" width="174.021" height="157.563" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
||||
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
|
||||
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
|
||||
<feGaussianBlur stdDeviation="29.7694" result="effect1_foregroundBlur_4703_22805"/>
|
||||
</filter>
|
||||
<filter id="filter2_f_4703_22805" x="-14.1369" y="-26.4972" width="87.4532" height="112.339" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
||||
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
|
||||
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
|
||||
<feGaussianBlur stdDeviation="17.9054" result="effect1_foregroundBlur_4703_22805"/>
|
||||
</filter>
|
||||
<filter id="filter3_f_4703_22805" x="-20.9145" y="-32.853" width="95.5731" height="83.2823" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
||||
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
|
||||
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
|
||||
<feGaussianBlur stdDeviation="17.9054" result="effect1_foregroundBlur_4703_22805"/>
|
||||
</filter>
|
||||
<filter id="filter4_f_4703_22805" x="-41.8776" y="-14.241" width="110.701" height="86.2396" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
||||
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
|
||||
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
|
||||
<feGaussianBlur stdDeviation="17.9054" result="effect1_foregroundBlur_4703_22805"/>
|
||||
</filter>
|
||||
<filter id="filter5_f_4703_22805" x="-28.9904" y="-62.5269" width="98.7561" height="122.685" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
||||
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
|
||||
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
|
||||
<feGaussianBlur stdDeviation="19.5544" result="effect1_foregroundBlur_4703_22805"/>
|
||||
</filter>
|
||||
<filter id="filter6_f_4703_22805" x="-98.9884" y="-65.7386" width="174.021" height="157.634" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
||||
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
|
||||
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
|
||||
<feGaussianBlur stdDeviation="29.7694" result="effect1_foregroundBlur_4703_22805"/>
|
||||
</filter>
|
||||
<filter id="filter7_f_4703_22805" x="-17.1113" y="-26.4972" width="90.4281" height="112.339" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
||||
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
|
||||
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
|
||||
<feGaussianBlur stdDeviation="17.9054" result="effect1_foregroundBlur_4703_22805"/>
|
||||
</filter>
|
||||
<filter id="filter8_f_4703_22805" x="-20.9145" y="-32.853" width="95.5729" height="83.7026" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
||||
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
|
||||
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
|
||||
<feGaussianBlur stdDeviation="17.9054" result="effect1_foregroundBlur_4703_22805"/>
|
||||
</filter>
|
||||
<filter id="filter9_f_4703_22805" x="-41.8774" y="-14.2409" width="110.701" height="86.3339" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
||||
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
|
||||
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
|
||||
<feGaussianBlur stdDeviation="17.9054" result="effect1_foregroundBlur_4703_22805"/>
|
||||
</filter>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 10 KiB |
|
After Width: | Height: | Size: 137 KiB |
|
After Width: | Height: | Size: 24 KiB |
@@ -0,0 +1,26 @@
|
||||
<svg width="150" height="150" viewBox="0 0 150 150" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect width="150" height="150" fill="#A78BFA"/>
|
||||
<g clip-path="url(#clip0_21004_3382)">
|
||||
<path d="M134.999 108.125V28.625C134.999 24.9663 132.015 22 128.333 22H21.674C17.9926 22 15.0078 24.9663 15.0078 28.625V121.375C15.0078 125.034 17.9926 128 21.674 128H128.333C132.015 128 134.999 125.034 134.999 121.375V108.125Z" fill="#E9E5D0"/>
|
||||
<path d="M78.3296 31.9375H38.3324C30.9696 31.9375 25 37.8702 25 45.1875V84.9375C25 92.2548 30.9696 98.1875 38.3324 98.1875H78.3296C85.6924 98.1875 91.662 92.2548 91.662 84.9375V45.1875C91.662 37.8702 85.6924 31.9375 78.3296 31.9375Z" fill="#FF613D"/>
|
||||
<path d="M78.3292 71.6875H74.9961V75H78.3292V71.6875Z" fill="#FFE143"/>
|
||||
<path d="M111.662 71.6875H108.329V84.9375H104.992V88.25H108.329V98.1875H111.662V88.25H114.983V84.9375H111.662V71.6875Z" fill="#111827"/>
|
||||
<path d="M124.991 71.6875H121.658V78.3125H118.328V81.625H121.658V98.1875H124.991V81.625H128.321V78.3125H124.991V71.6875Z" fill="#111827"/>
|
||||
<path d="M124.991 31.9375H108.32V35.25H124.991V31.9375Z" fill="#111827"/>
|
||||
<path d="M124.993 45.1875H108.324V48.5H124.993V45.1875Z" fill="#111827"/>
|
||||
<path d="M128.322 38.5625H104.988V41.875H128.322V38.5625Z" fill="#111827"/>
|
||||
<path d="M74.9972 75H71.6641V78.3125H74.9972V75Z" fill="#FFE143"/>
|
||||
<path d="M44.9972 75H41.6641V78.3125H44.9888V81.625H71.662V78.3125H44.9972V75Z" fill="#FFE143"/>
|
||||
<path d="M41.6651 71.6875H38.332V75H41.6651V71.6875Z" fill="#FFE143"/>
|
||||
<path d="M51.6623 51.8125H44.9961V65.0625H51.6623V51.8125Z" fill="#FFE143"/>
|
||||
<path d="M71.6623 51.8125H64.9961V65.0625H71.6623V51.8125Z" fill="#FFE143"/>
|
||||
<path d="M78.3296 31.9375H38.3324C30.9696 31.9375 25 37.8702 25 45.1875V84.9375C25 92.2548 30.9696 98.1875 38.3324 98.1875H78.3296C85.6924 98.1875 91.662 92.2548 91.662 84.9375V45.1875C91.662 37.8702 85.6924 31.9375 78.3296 31.9375ZM88.3289 84.9375C88.3289 90.4164 83.8426 94.875 78.3296 94.875H38.3324C32.8195 94.875 28.3331 90.4164 28.3331 84.9375V45.1875C28.3331 39.7086 32.8195 35.25 38.3324 35.25H78.3296C83.8426 35.25 88.3289 39.7086 88.3289 45.1875V84.9375Z" fill="#111827"/>
|
||||
<path d="M38.3324 111.438H25V114.75H38.3324V111.438Z" fill="#111827"/>
|
||||
<path d="M134.992 108.125V28.625C134.992 24.9663 132.007 22 128.325 22H21.6662C17.9848 22 15 24.9663 15 28.625V121.375C15 125.034 17.9848 128 21.6662 128H128.325C132.007 128 134.992 125.034 134.992 121.375V108.125ZM18.3331 28.625C18.3331 26.7982 19.828 25.3125 21.6662 25.3125H98.3159V104.813H18.3331V28.625ZM131.659 114.75C131.659 116.577 130.164 118.063 128.325 118.063H21.6662C19.828 118.063 18.3331 116.577 18.3331 114.75V108.125H131.659V114.75ZM131.659 104.813H101.656V25.3125H128.325C130.164 25.3125 131.659 26.7982 131.659 28.625V104.813Z" fill="#111827"/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_21004_3382">
|
||||
<rect width="120" height="106" fill="white" transform="translate(15 22)"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.8 KiB |
|
After Width: | Height: | Size: 24 KiB |
|
After Width: | Height: | Size: 8.6 KiB |
|
After Width: | Height: | Size: 5.7 KiB |
@@ -0,0 +1,126 @@
|
||||
import { describe, expect, test } from 'bun:test'
|
||||
import { mkdtempSync, mkdirSync, writeFileSync, rmSync } from 'node:fs'
|
||||
import { tmpdir } from 'node:os'
|
||||
import { join } from 'node:path'
|
||||
|
||||
import { verifyDist } from './verify-dist'
|
||||
import { SITE } from '../src/data/site'
|
||||
import { docsPages } from '../src/data/docsNav'
|
||||
import { releases, releaseUrl } from '../src/data/releases'
|
||||
import { heroes } from '../src/data/buddy'
|
||||
import { partners, community } from '../src/data/partners'
|
||||
|
||||
function writePage(dist: string, route: string, html: string): void {
|
||||
const dir = join(dist, route.replace(/^\//, ''))
|
||||
mkdirSync(dir, { recursive: true })
|
||||
writeFileSync(join(dir, 'index.html'), html)
|
||||
}
|
||||
|
||||
/** Build a minimal dist/ that satisfies every verifyDist assertion. */
|
||||
function writeValidFixture(dist: string): void {
|
||||
const navLinks = ['/buddy/', '/changelog/'].map(h => `<a href="${h}">x</a>`).join('')
|
||||
writePage(
|
||||
dist,
|
||||
'/',
|
||||
`<html>v${SITE.version}${navLinks}${partners
|
||||
.map(p => `<a href="${p.url}"><img src="${p.logo}"></a>`)
|
||||
.join('')}${community.map(c => `<a href="${c.url}">x</a>`).join('')}</html>`,
|
||||
)
|
||||
const sidebar = docsPages.map(p => `<a href="${p.href}">x</a>`).join('')
|
||||
for (const p of docsPages) writePage(dist, p.href, `<html>${sidebar}</html>`)
|
||||
writePage(
|
||||
dist,
|
||||
'/changelog/',
|
||||
`<html>v${SITE.version}${releases
|
||||
.map(r => `<a href="${releaseUrl(r.version)}">v${r.version}</a>`)
|
||||
.join('')}</html>`,
|
||||
)
|
||||
writePage(
|
||||
dist,
|
||||
'/buddy/',
|
||||
`<html>${heroes.map(h => `<img src="/buddy/${h.id}.svg"><p>${h.attack}</p>`).join('')}</html>`,
|
||||
)
|
||||
mkdirSync(join(dist, 'buddy'), { recursive: true })
|
||||
for (const h of heroes) writeFileSync(join(dist, 'buddy', `${h.id}.svg`), '<svg/>')
|
||||
writeFileSync(
|
||||
join(dist, 'sitemap-0.xml'),
|
||||
`<urlset>${['/buddy/', '/changelog/'].map(r => `<loc>${SITE.url}${r}</loc>`).join('')}</urlset>`,
|
||||
)
|
||||
}
|
||||
|
||||
function withFixture(mutate: (dist: string) => void): string[] {
|
||||
const dist = mkdtempSync(join(tmpdir(), 'verify-dist-'))
|
||||
try {
|
||||
writeValidFixture(dist)
|
||||
mutate(dist)
|
||||
return verifyDist(dist)
|
||||
} finally {
|
||||
rmSync(dist, { recursive: true, force: true })
|
||||
}
|
||||
}
|
||||
|
||||
describe('verifyDist', () => {
|
||||
test('passes on a complete fixture', () => {
|
||||
expect(withFixture(() => {})).toEqual([])
|
||||
})
|
||||
|
||||
test('flags a missing page', () => {
|
||||
const failures = withFixture(dist => rmSync(join(dist, 'buddy', 'index.html')))
|
||||
expect(failures).toContain('missing page for route /buddy/')
|
||||
})
|
||||
|
||||
test('flags a present-but-empty page instead of skipping its assertions', () => {
|
||||
const failures = withFixture(dist => writeFileSync(join(dist, 'index.html'), ' \n'))
|
||||
expect(failures).toContain('empty page for route /')
|
||||
// and it must not drown the report in per-needle noise for that page
|
||||
expect(failures.filter(f => f.startsWith('landing '))).toEqual([])
|
||||
})
|
||||
|
||||
test('flags a docs sidebar that lost a navigation link', () => {
|
||||
const failures = withFixture(dist => {
|
||||
const sidebar = docsPages
|
||||
.filter(p => p.href !== '/changelog/')
|
||||
.map(p => `<a href="${p.href}">x</a>`)
|
||||
.join('')
|
||||
writePage(dist, '/docs/', `<html>${sidebar}</html>`)
|
||||
})
|
||||
expect(failures).toContain('docs sidebar link /changelog/: missing "href=\\"/changelog/\\""')
|
||||
})
|
||||
|
||||
test('flags a missing sprite asset', () => {
|
||||
const hero = heroes[0]!
|
||||
const failures = withFixture(dist => rmSync(join(dist, 'buddy', `${hero.id}.svg`)))
|
||||
expect(failures).toContain(`missing sprite asset /buddy/${hero.id}.svg`)
|
||||
})
|
||||
|
||||
test('flags a stale landing page missing a partner link', () => {
|
||||
const failures = withFixture(dist => {
|
||||
const html = `<html>v${SITE.version}<a href="/buddy/">x</a><a href="/changelog/">x</a>${community
|
||||
.map(c => `<a href="${c.url}">x</a>`)
|
||||
.join('')}</html>`
|
||||
writeFileSync(join(dist, 'index.html'), html)
|
||||
})
|
||||
expect(failures.some(f => f.startsWith(`partner link ${partners[0]!.name}`))).toBe(true)
|
||||
})
|
||||
|
||||
test('flags a sitemap missing the new routes', () => {
|
||||
const failures = withFixture(dist =>
|
||||
writeFileSync(join(dist, 'sitemap-0.xml'), `<urlset><loc>${SITE.url}/</loc></urlset>`),
|
||||
)
|
||||
expect(failures.some(f => f.startsWith('sitemap entry /buddy/'))).toBe(true)
|
||||
expect(failures.some(f => f.startsWith('sitemap entry /changelog/'))).toBe(true)
|
||||
})
|
||||
|
||||
test('flags a missing sitemap', () => {
|
||||
const failures = withFixture(dist => rmSync(join(dist, 'sitemap-0.xml')))
|
||||
expect(failures).toContain('missing dist/sitemap-0.xml')
|
||||
})
|
||||
|
||||
test('flags a changelog entry that lost its release URL', () => {
|
||||
const failures = withFixture(dist => {
|
||||
const html = `<html>v${SITE.version}${releases.map(r => `v${r.version}`).join(' ')}</html>`
|
||||
writeFileSync(join(dist, 'changelog', 'index.html'), html)
|
||||
})
|
||||
expect(failures.some(f => f.startsWith('changelog release URL'))).toBe(true)
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,103 @@
|
||||
// Post-build guard, run by `bun run build` after `astro build`.
|
||||
// Asserts that the typed data files actually drive the rendered output:
|
||||
// version propagation, navigation, and the /, /buddy/, /changelog/ routes.
|
||||
// Kept as a pure function so verify-dist.test.ts can exercise it on fixtures.
|
||||
import { readFileSync, existsSync } from 'node:fs'
|
||||
import { join } from 'node:path'
|
||||
|
||||
import { SITE } from '../src/data/site'
|
||||
import { docsPages } from '../src/data/docsNav'
|
||||
import { releases, releaseUrl } from '../src/data/releases'
|
||||
import { heroes } from '../src/data/buddy'
|
||||
import { partners, community } from '../src/data/partners'
|
||||
|
||||
export function verifyDist(dist: string): string[] {
|
||||
const failures: string[] = []
|
||||
|
||||
function page(route: string): string {
|
||||
const file = join(dist, route.replace(/^\//, ''), 'index.html')
|
||||
if (!existsSync(file)) {
|
||||
failures.push(`missing page for route ${route}`)
|
||||
return ''
|
||||
}
|
||||
const html = readFileSync(file, 'utf8')
|
||||
if (html.trim() === '') {
|
||||
// '' is only ever returned alongside a recorded failure, so expect()
|
||||
// skipping falsy html can never silently pass a bad page.
|
||||
failures.push(`empty page for route ${route}`)
|
||||
return ''
|
||||
}
|
||||
return html
|
||||
}
|
||||
|
||||
function expect(html: string, needle: string, why: string): void {
|
||||
if (html !== '' && !html.includes(needle)) failures.push(`${why}: missing ${JSON.stringify(needle)}`)
|
||||
}
|
||||
|
||||
// ── version propagation (site.ts imports the root package.json) ──────────
|
||||
const rootPkg = JSON.parse(
|
||||
readFileSync(join(import.meta.dir, '..', '..', 'package.json'), 'utf8'),
|
||||
) as { version: string }
|
||||
if (SITE.version !== rootPkg.version)
|
||||
failures.push(`SITE.version ${SITE.version} != root package.json ${rootPkg.version}`)
|
||||
|
||||
const index = page('/')
|
||||
expect(index, `v${rootPkg.version}`, 'landing version')
|
||||
|
||||
// ── navigation exposes every docsNav route, in data AND rendered output ──
|
||||
const docsIndex = page('/docs/')
|
||||
for (const p of docsPages) {
|
||||
page(p.href) // records a failure if the route didn't build
|
||||
expect(docsIndex, `href="${p.href}"`, `docs sidebar link ${p.href}`)
|
||||
}
|
||||
for (const href of ['/buddy/', '/changelog/'] as const) {
|
||||
if (!docsPages.some(p => p.href === href)) failures.push(`docsNav missing ${href}`)
|
||||
expect(index, `href="${href}"`, `landing nav link ${href}`)
|
||||
}
|
||||
|
||||
// ── /changelog/: every release renders with its GitHub release URL ───────
|
||||
const changelog = page('/changelog/')
|
||||
for (const r of releases) {
|
||||
expect(changelog, `v${r.version}`, `changelog release ${r.version}`)
|
||||
expect(changelog, releaseUrl(r.version), `changelog release URL ${r.version}`)
|
||||
}
|
||||
expect(changelog, `v${rootPkg.version}`, 'changelog current-version pill')
|
||||
|
||||
// ── /buddy/: every hero renders with its sprite ──────────────────────────
|
||||
const buddy = page('/buddy/')
|
||||
for (const h of heroes) {
|
||||
expect(buddy, `/buddy/${h.id}.svg`, `buddy sprite ${h.id}`)
|
||||
expect(buddy, h.attack, `buddy attack ${h.id}`)
|
||||
if (!existsSync(join(dist, 'buddy', `${h.id}.svg`)))
|
||||
failures.push(`missing sprite asset /buddy/${h.id}.svg`)
|
||||
}
|
||||
|
||||
// ── landing: partner and community links render ──────────────────────────
|
||||
for (const p of partners) {
|
||||
expect(index, `href="${p.url}"`, `partner link ${p.name}`)
|
||||
expect(index, p.logo, `partner logo ${p.name}`)
|
||||
}
|
||||
for (const c of community) expect(index, `href="${c.url}"`, `community link ${c.name}`)
|
||||
|
||||
// ── sitemap covers the new routes ────────────────────────────────────────
|
||||
const sitemapFile = join(dist, 'sitemap-0.xml')
|
||||
if (existsSync(sitemapFile)) {
|
||||
const sitemap = readFileSync(sitemapFile, 'utf8')
|
||||
for (const route of ['/buddy/', '/changelog/'])
|
||||
expect(sitemap, `${SITE.url}${route}`, `sitemap entry ${route}`)
|
||||
} else {
|
||||
failures.push('missing dist/sitemap-0.xml')
|
||||
}
|
||||
|
||||
return [...new Set(failures)]
|
||||
}
|
||||
|
||||
if (import.meta.main) {
|
||||
const failures = verifyDist(join(import.meta.dir, '..', 'dist'))
|
||||
if (failures.length > 0) {
|
||||
console.error(`verify-dist: ${failures.length} failure(s)`)
|
||||
for (const f of failures) console.error(` ✗ ${f}`)
|
||||
process.exit(1)
|
||||
}
|
||||
console.log('verify-dist: ok — version, nav, buddy, changelog, partners all verified')
|
||||
}
|
||||
@@ -24,8 +24,11 @@ const columns = [
|
||||
{
|
||||
title: 'project',
|
||||
links: [
|
||||
{ href: '/changelog/', label: "what's new" },
|
||||
{ href: '/buddy/', label: 'buddy' },
|
||||
{ href: SITE.github, label: 'github' },
|
||||
{ href: SITE.npmUrl, label: 'npm' },
|
||||
{ href: 'https://discord.gg/k68zFR6AcB', label: 'discord' },
|
||||
{ href: `${SITE.github}/blob/main/LICENSE`, label: 'license' },
|
||||
{ href: `${SITE.github}/issues`, label: 'issues' },
|
||||
],
|
||||
|
||||
@@ -3,8 +3,9 @@ import { SITE } from '../data/site'
|
||||
|
||||
const links = [
|
||||
{ href: '/docs/', label: 'docs' },
|
||||
{ href: '/docs/slash-commands/', label: 'commands' },
|
||||
{ href: '/docs/providers/', label: 'providers' },
|
||||
{ href: '/buddy/', label: 'buddy' },
|
||||
{ href: '/changelog/', label: "what's new" },
|
||||
{ href: SITE.github, label: 'github' },
|
||||
{ href: SITE.gitlawb, label: 'gitlawb' },
|
||||
]
|
||||
|
||||
@@ -0,0 +1,100 @@
|
||||
// Hero companion facts, kept in sync with src/buddy/ (types.ts, buddy.tsx,
|
||||
// actionEffects.ts). Sprites in /public/buddy/*.svg are generated from
|
||||
// src/buddy/pixelSprites.ts — regenerate them rather than editing by hand.
|
||||
|
||||
export interface BuddyHero {
|
||||
id: string
|
||||
name: string
|
||||
accent: string
|
||||
/** what happens when you press Enter */
|
||||
attack: string
|
||||
attackDetail: string
|
||||
/** flavor line shown when you /buddy set this form */
|
||||
flavor: string
|
||||
}
|
||||
|
||||
export const heroes: BuddyHero[] = [
|
||||
{
|
||||
id: 'robinhood',
|
||||
name: 'robin hood',
|
||||
accent: '#489e4a',
|
||||
attack: 'arrow shot',
|
||||
attackDetail:
|
||||
'an arrow flies right-to-left across the footer toward your prompt and lands with an impact thunk.',
|
||||
flavor: 'dons the green hood',
|
||||
},
|
||||
{
|
||||
id: 'kaio',
|
||||
name: 'kaio',
|
||||
accent: '#ffdc50',
|
||||
attack: 'energy wave',
|
||||
attackDetail:
|
||||
'charges an orb, then releases a full-width energy beam — white-hot core, blue edge.',
|
||||
flavor: 'powers up — hair blazing gold',
|
||||
},
|
||||
{
|
||||
id: 'strawhat',
|
||||
name: 'strawhat',
|
||||
accent: '#e6c85a',
|
||||
attack: 'stretchy punch',
|
||||
attackDetail:
|
||||
'a rubber arm stretches all the way out, lands the hit mid-flight, and snaps back.',
|
||||
flavor: 'puts on the straw hat with a grin',
|
||||
},
|
||||
{
|
||||
id: 'merlin',
|
||||
name: 'merlin',
|
||||
accent: '#a573e6',
|
||||
attack: 'sparkle stream',
|
||||
attackDetail:
|
||||
'a twinkling comet of sparkles streams from the star-tipped staff and ends in a starburst.',
|
||||
flavor: 'raises the star-tipped staff',
|
||||
},
|
||||
{
|
||||
id: 'kage',
|
||||
name: 'kage',
|
||||
accent: '#c8cdd2',
|
||||
attack: 'shuriken throw',
|
||||
attackDetail:
|
||||
'a steel shuriken spins across the screen, cycling glyphs every 80 milliseconds.',
|
||||
flavor: 'melts into the shadows',
|
||||
},
|
||||
{
|
||||
id: 'ember',
|
||||
name: 'ember',
|
||||
accent: '#eb5a3c',
|
||||
attack: 'dragon fire',
|
||||
attackDetail:
|
||||
'breathes a cone of fire with a real heat gradient — hot core, cooling tail, scorch-out.',
|
||||
flavor: 'puffs a proud little smoke ring',
|
||||
},
|
||||
{
|
||||
id: 'corsair',
|
||||
name: 'corsair',
|
||||
accent: '#d6a83c',
|
||||
attack: 'cannon shot',
|
||||
attackDetail:
|
||||
'fires a cannonball that arcs across the footer trailing smoke.',
|
||||
flavor: 'tips the tricorn',
|
||||
},
|
||||
]
|
||||
|
||||
export const buddyCommands = [
|
||||
{ cmd: '/buddy', desc: 'hatch your companion on first run; pet them after that' },
|
||||
{ cmd: '/buddy status', desc: 'who they are — name, rarity, form, personality' },
|
||||
{ cmd: '/buddy set <hero>', desc: 'choose a form: robinhood, kaio, strawhat, merlin, kage, ember, corsair' },
|
||||
{ cmd: '/buddy set random', desc: 'back to the form you originally rolled' },
|
||||
{ cmd: '/buddy name <name>', desc: 'rename your companion (up to 20 characters)' },
|
||||
{ cmd: '/buddy mute', desc: 'hide the buddy and silence all animations' },
|
||||
{ cmd: '/buddy unmute', desc: 'bring them back' },
|
||||
]
|
||||
|
||||
export const rarities = [
|
||||
{ name: 'common', weight: '60%' },
|
||||
{ name: 'uncommon', weight: '25%' },
|
||||
{ name: 'rare', weight: '10%' },
|
||||
{ name: 'epic', weight: '4%' },
|
||||
{ name: 'legendary', weight: '1%' },
|
||||
]
|
||||
|
||||
export const statNames = ['DEBUGGING', 'PATIENCE', 'CHAOS', 'WISDOM', 'SNARK']
|
||||
@@ -45,6 +45,7 @@ export const flagGroups: FlagGroup[] = [
|
||||
{ flag: '--include-hook-events', description: 'Include hook lifecycle events in the output stream (stream-json only).' },
|
||||
{ flag: '--include-partial-messages', description: 'Include partial message chunks as they arrive (stream-json only).' },
|
||||
{ flag: '--replay-user-messages', description: 'Re-emit user messages from stdin back on stdout for acknowledgment (stream-json in and out).' },
|
||||
{ flag: '--heartbeat', arg: '<duration>', description: 'Emit a headless liveness heartbeat while output is quiet (only with --print; e.g. 30s, 2m).' },
|
||||
],
|
||||
},
|
||||
{
|
||||
@@ -53,7 +54,7 @@ export const flagGroups: FlagGroup[] = [
|
||||
flags: [
|
||||
{ flag: '--model', arg: '<model>', description: "Model for the session: an alias like 'sonnet' or 'opus', or a full model name." },
|
||||
{ flag: '--provider', arg: '<provider>', description: 'AI provider to use (anthropic, openai, gemini, github, bedrock, vertex, ollama, …).' },
|
||||
{ flag: '--effort', arg: '<level>', description: 'Effort level for model usage: low, medium, high, xhigh, or max.' },
|
||||
{ flag: '--effort', arg: '<level>', description: 'Effort level for model usage: low, medium, high, xhigh, max, or ultracode.' },
|
||||
{ flag: '--fallback-model', arg: '<model>', description: 'Automatic fallback model when the default model is overloaded.' },
|
||||
{ flag: '--agent', arg: '<agent>', description: "Agent for the current session. Overrides the 'agent' setting." },
|
||||
{ flag: '--betas', arg: '<betas...>', description: 'Beta headers to include in API requests (API key users only).' },
|
||||
@@ -71,6 +72,7 @@ export const flagGroups: FlagGroup[] = [
|
||||
{ flag: '-n, --name', arg: '<name>', description: 'Set a display name for this session (shown in /resume and the terminal title).' },
|
||||
{ flag: '--no-session-persistence', description: 'Disable session persistence — sessions are not saved to disk and cannot be resumed (--print only).' },
|
||||
{ flag: '-w, --worktree', arg: '[name]', description: 'Run the session in an isolated git worktree, optionally named.' },
|
||||
{ flag: '--tmux', description: 'Create a tmux session for the worktree (requires --worktree). Uses iTerm2 native panes when available; pass --tmux=classic for traditional tmux.' },
|
||||
],
|
||||
},
|
||||
{
|
||||
@@ -111,23 +113,72 @@ export const flagGroups: FlagGroup[] = [
|
||||
{ flag: '--agents', arg: '<json>', description: 'JSON object defining custom agents for the session.' },
|
||||
{ flag: '--plugin-dir', arg: '<path>', description: 'Load plugins from a directory (repeatable).' },
|
||||
{ flag: '--ide', description: 'Automatically connect to your IDE on startup if exactly one valid IDE is available.' },
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 'limits',
|
||||
label: 'limits & budgets',
|
||||
flags: [
|
||||
{ flag: '--max-budget-usd', arg: '<amount>', description: 'Maximum dollar amount to spend on API calls (--print only).' },
|
||||
{ flag: '--chrome', description: 'Enable Claude in Chrome integration.' },
|
||||
{ flag: '--no-chrome', description: 'Disable Claude in Chrome integration.' },
|
||||
{ flag: '--disable-slash-commands', description: 'Disable all slash commands, including skill-backed ones.' },
|
||||
{ flag: '--provider-env-file', arg: '<path>', description: 'Load provider environment variables from a file before validation (repeatable; existing values win).' },
|
||||
{ flag: '--file', arg: '<specs...>', description: 'File resources to download at startup, as file_id:relative_path.' },
|
||||
],
|
||||
},
|
||||
]
|
||||
|
||||
// Note: in -p/--print mode subcommands are skipped entirely.
|
||||
export const subcommands: Subcommand[] = [
|
||||
{
|
||||
name: 'mcp',
|
||||
usage: 'openclaude mcp [add|remove|list|doctor]',
|
||||
description: 'Manage MCP server configuration from the command line.',
|
||||
},
|
||||
{
|
||||
name: 'auth',
|
||||
usage: 'openclaude auth <login|status|logout>',
|
||||
description: 'Manage authentication.',
|
||||
},
|
||||
{
|
||||
name: 'auth xai',
|
||||
usage: 'openclaude auth xai <login|device|logout|status>',
|
||||
description: 'Sign in to xAI (Grok) with browser OAuth, or device code for remote hosts.',
|
||||
},
|
||||
{
|
||||
name: 'skills',
|
||||
usage: 'openclaude skills <list|show|validate|install|remove>',
|
||||
description: 'List, inspect, validate, and manage OpenClaude skills.',
|
||||
},
|
||||
{
|
||||
name: 'plugin',
|
||||
usage: 'openclaude plugin <validate|list|install|uninstall|enable|disable|update|marketplace>',
|
||||
description: 'Manage OpenClaude plugins and marketplaces.',
|
||||
},
|
||||
{
|
||||
name: 'agents',
|
||||
usage: 'openclaude agents',
|
||||
description: 'List configured agents.',
|
||||
},
|
||||
{
|
||||
name: 'doctor',
|
||||
usage: 'openclaude doctor [report]',
|
||||
description: "Check installation health; 'doctor report' prints a redacted diagnostic report for GitHub issues.",
|
||||
},
|
||||
{
|
||||
name: 'update',
|
||||
usage: 'openclaude update',
|
||||
description: 'Check for updates and install if available (alias: upgrade).',
|
||||
},
|
||||
{
|
||||
name: 'install',
|
||||
usage: 'openclaude install [stable|latest|<version>]',
|
||||
description: 'Install the OpenClaude native build.',
|
||||
},
|
||||
{
|
||||
name: 'setup-token',
|
||||
usage: 'openclaude setup-token',
|
||||
description: 'Set up a long-lived authentication token (requires Claude subscription).',
|
||||
},
|
||||
{
|
||||
name: 'aimlapi',
|
||||
usage: 'openclaude aimlapi topup',
|
||||
description: 'Top up an AI/ML API balance and configure the provider.',
|
||||
},
|
||||
{
|
||||
name: 'ssh',
|
||||
usage: 'openclaude ssh <host> [dir]',
|
||||
|
||||
@@ -60,13 +60,16 @@ export const commandCategories: { id: CommandCategory; label: string; blurb: str
|
||||
export const commands: SlashCommand[] = [
|
||||
// ── sessions & conversations ─────────────────────────────────────────
|
||||
{ name: 'clear', description: 'Clear conversation history and free up context', category: 'session' },
|
||||
{ name: 'compact', description: 'Clear conversation history but keep a summary in context', category: 'session', args: '[instructions]' },
|
||||
{ name: 'compact', description: 'Clear conversation history but keep a summary in context', category: 'session', args: '<optional custom summarization instructions>' },
|
||||
{ name: 'resume', description: 'Resume a previous conversation', category: 'session', args: '[conversation id or search term]' },
|
||||
{ name: 'rename', description: 'Rename the current conversation', category: 'session', args: '[name]' },
|
||||
{ name: 'branch', description: 'Create a branch of the current conversation at this point', category: 'session', args: '[name]' },
|
||||
{ name: 'rewind', description: 'Restore the code and/or conversation to a previous point', category: 'session' },
|
||||
{ name: 'export', description: 'Export the current conversation to a file or clipboard', category: 'session', args: '[filename]' },
|
||||
{ name: 'copy', description: "Copy the agent's last response to clipboard (or /copy N for the Nth-latest)", category: 'session', args: '[N]' },
|
||||
{ name: 'copy', description: "Copy the agent's last response to clipboard (or /copy N for the Nth-latest)", category: 'session' },
|
||||
{ name: 'continue', description: 'Continue the current task', category: 'session', args: '[optional instruction]' },
|
||||
{ name: 'replay', description: 'Replay a session showing tool execution timeline', category: 'session', args: '[session id or search term]' },
|
||||
{ name: 'tag', description: 'Toggle a searchable tag on the current session', category: 'session', args: '<tag-name>' },
|
||||
{ name: 'btw', description: 'Ask a quick side question without interrupting the main conversation', category: 'session', args: '<question>' },
|
||||
{ name: 'goal', description: 'Set and manage a session completion goal', category: 'session', args: '[condition|status|pause|resume|clear]' },
|
||||
{ name: 'tasks', description: 'List and manage background tasks', category: 'session' },
|
||||
@@ -77,13 +80,15 @@ export const commands: SlashCommand[] = [
|
||||
|
||||
// ── context & memory ─────────────────────────────────────────────────
|
||||
{ name: 'context', description: 'Show current context usage', category: 'context' },
|
||||
{ name: 'ctx', description: 'Show context window usage and token breakdown', category: 'context' },
|
||||
{ name: 'repomap', description: 'Show or configure the repository structural map (codebase intelligence)', category: 'context' },
|
||||
{ name: 'files', description: 'List all files currently in context', category: 'context' },
|
||||
{ name: 'add-dir', description: 'Add a new working directory', category: 'context', args: '<path>' },
|
||||
{ name: 'init', description: 'Initialize a new project instruction file with codebase documentation', category: 'context' },
|
||||
{ name: 'memory', description: 'Edit persistent memory files', category: 'context' },
|
||||
{ name: 'dream', description: 'Run memory consolidation — synthesize recent sessions into durable memories', category: 'context' },
|
||||
{ name: 'knowledge', description: 'Manage the native Knowledge Graph', category: 'context', args: 'enable <yes|no> | clear | status | list' },
|
||||
{ name: 'wiki', description: 'Initialize and inspect the OpenClaude project wiki', category: 'context', args: '[init|status]' },
|
||||
{ name: 'wiki', description: 'Initialize and inspect the OpenClaude project wiki', category: 'context', args: '[init|status|scan|ingest <path>]' },
|
||||
{ name: 'cost', description: 'Show the total cost and duration of the current session', category: 'context' },
|
||||
{ name: 'request-size', description: 'Show estimated request context load and top contributors', category: 'context' },
|
||||
{ name: 'cache-stats', description: 'Show per-turn and session cache hit/miss stats (works across all providers)', category: 'context' },
|
||||
@@ -91,7 +96,9 @@ export const commands: SlashCommand[] = [
|
||||
// ── models & providers ───────────────────────────────────────────────
|
||||
{ name: 'model', description: 'Set the AI model for the session', category: 'models', args: '[model]' },
|
||||
{ name: 'provider', description: 'Manage API provider profiles', category: 'models' },
|
||||
{ name: 'effort', description: 'Set effort level for model usage', category: 'models', args: '[low|medium|high|max|auto]' },
|
||||
{ name: 'effort', description: 'Set effort level for model usage', category: 'models', args: '[low|medium|high|xhigh|max|ultracode|auto]' },
|
||||
{ name: 'set-context-window', description: 'Set a session-scoped context window override for a model', category: 'models', args: '[model] <tokens>' },
|
||||
{ name: 'clear-context-window', description: 'Clear session-scoped context window overrides', category: 'models', args: '[model]' },
|
||||
{ name: 'smartroute', description: 'Configure smart auto-routing (experimental): route simple turns to your configured simple model', category: 'models', args: '[on|off|simple <key>|strong <key>]' },
|
||||
{ name: 'login', description: 'Sign in with your Anthropic account', category: 'models' },
|
||||
{ name: 'logout', description: 'Sign out from your Anthropic account', category: 'models' },
|
||||
@@ -101,6 +108,9 @@ export const commands: SlashCommand[] = [
|
||||
|
||||
// ── code review & git ────────────────────────────────────────────────
|
||||
{ name: 'diff', description: 'View uncommitted changes and per-turn diffs', category: 'workflow' },
|
||||
{ name: 'bughunter', description: 'Systematic four-phase bug hunt: map → hunt → skeptic pass → fix proposals', category: 'workflow' },
|
||||
{ name: 'bughunter-security', description: 'Security-focused bug hunt: exploit-driven, OWASP-aligned, confidence ≥ 8', category: 'workflow' },
|
||||
{ name: 'bughunter-perf', description: 'Performance-focused bug hunt: hot-path complexity, sync I/O, leaks, N+1 queries', category: 'workflow' },
|
||||
{ name: 'review', description: 'Review a pull request', category: 'workflow' },
|
||||
{ name: 'security-review', description: 'Complete a security review of the pending changes on the current branch', category: 'workflow' },
|
||||
{ name: 'pr-comments', description: 'Get comments from a GitHub pull request', category: 'workflow' },
|
||||
@@ -119,6 +129,7 @@ export const commands: SlashCommand[] = [
|
||||
{ name: 'agents', description: 'Manage agent configurations', category: 'tools' },
|
||||
{ name: 'hooks', description: 'View hook configurations for tool events', category: 'tools' },
|
||||
{ name: 'permissions', description: 'Manage allow & deny tool permission rules', category: 'tools' },
|
||||
{ name: 'chrome', description: 'Claude in Chrome (Beta) settings', category: 'tools' },
|
||||
|
||||
// ── ui & customization ───────────────────────────────────────────────
|
||||
{ name: 'config', description: 'Open the config panel', category: 'customization' },
|
||||
@@ -130,7 +141,8 @@ export const commands: SlashCommand[] = [
|
||||
{ name: 'statusline', description: "Set up OpenClaude's status line UI", category: 'customization' },
|
||||
{ name: 'terminal-setup', description: 'Install the Shift+Enter key binding for newlines', category: 'customization' },
|
||||
{ name: 'commit-message', description: 'Configure commit attribution text', category: 'customization', args: '[status|off|default|set "text"|co-author <name> <email>]' },
|
||||
{ name: 'output-style', description: 'Deprecated: use /config to change output style', category: 'customization' },
|
||||
{ name: 'buddy', description: 'Hatch, pet, and manage your OpenClaude companion', category: 'customization', args: '[status|mute|unmute|set <form>|name <name>|help]' },
|
||||
{ name: 'ads', description: 'Earn opengateway credits from sponsored tips (ads.gitlawb.com)', category: 'customization', args: 'on | off' },
|
||||
{ name: 'stickers', description: 'Order OpenClaude stickers', category: 'customization' },
|
||||
|
||||
// ── help & diagnostics ───────────────────────────────────────────────
|
||||
@@ -142,6 +154,9 @@ export const commands: SlashCommand[] = [
|
||||
{ name: 'insights', description: 'Generate a report analyzing your OpenClaude sessions', category: 'diagnostics' },
|
||||
{ name: 'release-notes', description: 'View release notes', category: 'diagnostics' },
|
||||
{ name: 'feedback', description: 'Submit feedback about OpenClaude', category: 'diagnostics', args: '[report]' },
|
||||
{ name: 'cache-probe', description: 'Send identical requests to test prompt caching (results in debug log)', category: 'diagnostics', args: '[model] [--no-key]' },
|
||||
{ name: 'update', description: 'Update OpenClaude to the latest version', category: 'diagnostics', args: '[latest|stable|<version>] [--force]' },
|
||||
{ name: 'privacy-settings', description: 'View and update your privacy settings', category: 'diagnostics' },
|
||||
]
|
||||
|
||||
export function commandsByCategory(category: CommandCategory): SlashCommand[] {
|
||||
|
||||
@@ -41,17 +41,21 @@ export interface SettingOption {
|
||||
|
||||
export const settingOptions: SettingOption[] = [
|
||||
{ key: 'model', description: "Default model (alias like 'sonnet' or a full model name)." },
|
||||
{ key: 'provider', description: 'Default provider preset for new sessions.' },
|
||||
{ key: 'effort', description: 'Default effort level: low, medium, high, xhigh, or max.' },
|
||||
{ key: 'effortLevel', description: 'Persisted effort level for supported models (low, medium, high, xhigh, max).' },
|
||||
{ key: 'agent', description: 'Default agent for new sessions.' },
|
||||
{ key: 'permissions', description: 'Allow/deny rules for tools, plus the default permission mode.' },
|
||||
{ key: 'env', description: 'Environment variables applied to every session.' },
|
||||
{ key: 'theme', description: 'Terminal color theme.' },
|
||||
{ key: 'verbose', description: 'Verbose output by default.' },
|
||||
{ key: 'allowAutoUpdates', description: 'Enable or disable the auto-updater.' },
|
||||
{ key: 'theme', description: 'Terminal color theme. Lives in the global config (~/.openclaude.json), not settings.json.' },
|
||||
{ key: 'verbose', description: 'Verbose output by default. Lives in the global config (~/.openclaude.json), not settings.json.' },
|
||||
{ key: 'autoUpdates', description: 'Enable or disable the auto-updater. Lives in the global config (~/.openclaude.json), not settings.json.' },
|
||||
{ key: 'hooks', description: 'Shell hooks that run on tool events (PreToolUse, PostToolUse, …).' },
|
||||
{ key: 'subscriptionType', description: "Override the active subscription type from user settings only. Allowed values: free, pro, max, team, enterprise. To prevent spoofing, this override ignores project, repository, local, flag, and policy settings. Setting this to 'free' is authoritative and takes precedence over OAuth or fallback authentication." },
|
||||
{ key: 'smartRouting', description: 'Opt-in smart auto-routing: { enabled, simpleModel, strongModel } route simple turns to the configured simple model. Configure with /smartroute.' },
|
||||
{ key: 'modelLimits', description: 'Per-model overrides for context window and max output tokens, for OpenAI-compatible models missing from the built-in catalog. Example: { "qwen3.6-plus": { "contextWindow": 1048576, "maxOutputTokens": 32768 } }' },
|
||||
{ key: 'providerFallbackChain', description: 'Ordered list of provider profile ids. On a rate-limit or quota error, OpenClaude advances to the next profile and retries the turn.' },
|
||||
{ key: 'agentModels', description: 'Map of route key to provider connection info (base_url, api_key, model) for cross-provider agent routing.' },
|
||||
{ key: 'agentRouting', description: 'Map of agent identifier to a model name from agentModels; use the "default" key as fallback.' },
|
||||
{ key: 'advisorModel', description: 'Advisor model for the server-side advisor tool.' },
|
||||
]
|
||||
|
||||
export interface EnvVar {
|
||||
@@ -62,10 +66,20 @@ export interface EnvVar {
|
||||
export const envVars: EnvVar[] = [
|
||||
{ name: 'ANTHROPIC_API_KEY', description: 'Anthropic API key (also the strict auth path in --bare mode).' },
|
||||
{ name: 'ANTHROPIC_AUTH_TOKEN', description: 'Bearer token alternative to an Anthropic API key.' },
|
||||
{ name: 'ANTHROPIC_FIRST_PARTY_PROXY_HOSTS', description: 'Opt-in comma-separated list of loopback host:port entries that keep subscription (OAuth) auth when ANTHROPIC_BASE_URL points at a local transparent proxy.' },
|
||||
{ name: 'OPENAI_API_KEY', description: 'Key for OpenAI-compatible providers and gateways (incl. Opengateway).' },
|
||||
{ name: 'OPENAI_API_KEYS', description: 'Comma-separated key pool for OpenAI-compatible endpoints; rotates on failure and is checked before OPENAI_API_KEY.' },
|
||||
{ name: 'OPENAI_BASE_URL', description: 'Base URL of an OpenAI-compatible /v1 endpoint (OpenRouter, LM Studio, LiteLLM, …).' },
|
||||
{ name: 'OPENAI_MODEL', description: 'Model name to request from the OpenAI-compatible endpoint.' },
|
||||
{ name: 'GOOGLE_API_KEY', description: 'Google AI / Gemini API key.' },
|
||||
{ name: 'OPENGATEWAY_API_KEY', description: 'Gitlawb Opengateway key (preferred over OPENAI_API_KEY for the gateway).' },
|
||||
{ name: 'OPENGATEWAY_BASE_URL', description: 'Override the Opengateway base URL.' },
|
||||
{ name: 'CLAUDE_CODE_OPENAI_CONTEXT_WINDOWS', description: 'JSON map of model → context window; takes precedence over settings.modelLimits.' },
|
||||
{ name: 'CLAUDE_CODE_OPENAI_MAX_OUTPUT_TOKENS', description: 'JSON map of model → max output tokens; takes precedence over settings.modelLimits.' },
|
||||
{ name: 'GEMINI_API_KEY', description: 'Gemini API key (the preset reads this, not GOOGLE_API_KEY).' },
|
||||
{ name: 'XAI_API_KEY', description: "xAI Grok key (or sign in with 'openclaude auth xai login')." },
|
||||
{ name: 'AIMLAPI_API_KEY', description: 'AI/ML API key.' },
|
||||
{ name: 'CLOUDFLARE_API_TOKEN', description: 'Cloudflare Workers AI token.' },
|
||||
{ name: 'NVIDIA_API_KEY', description: 'NVIDIA NIM key.' },
|
||||
{ name: 'NEARAI_API_KEY', description: 'NEAR AI unified gateway key.' },
|
||||
{ name: 'MIMO_API_KEY', description: 'Xiaomi MiMo API key.' },
|
||||
{ name: 'OPENCODE_API_KEY', description: 'OpenCode Zen / Go gateway key.' },
|
||||
|
||||
@@ -28,6 +28,13 @@ export const docsNav: DocsNavGroup[] = [
|
||||
{ title: 'Skills', href: '/docs/skills/' },
|
||||
],
|
||||
},
|
||||
{
|
||||
group: 'more',
|
||||
items: [
|
||||
{ title: "What's new", href: '/changelog/' },
|
||||
{ title: 'Buddy', href: '/buddy/' },
|
||||
],
|
||||
},
|
||||
]
|
||||
|
||||
export const docsPages: DocsNavItem[] = docsNav.flatMap(g => g.items)
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
// Partner roster — mirrors the Partners section of the repo README.
|
||||
// Logos are self-hosted under /public/partners/.
|
||||
|
||||
export interface Partner {
|
||||
name: string
|
||||
url: string
|
||||
logo: string
|
||||
/** dark-theme variant, when the primary logo doesn't read on dark */
|
||||
logoDark?: string
|
||||
/** rendered logo height in px (logos vary wildly in aspect ratio) */
|
||||
height: number
|
||||
}
|
||||
|
||||
export const partners: Partner[] = [
|
||||
{ name: 'GitLawb', url: 'https://gitlawb.com', logo: '/partners/gitlawb.png', height: 44 },
|
||||
{ name: 'Bankr.bot', url: 'https://bankr.bot', logo: '/partners/bankr.svg', height: 40 },
|
||||
{ name: 'Atomic Chat', url: 'https://atomic.chat/', logo: '/partners/atomic-chat-logo.png', height: 40 },
|
||||
{ name: 'Xiaomi MiMo', url: 'https://mimo.mi.com', logo: '/partners/mimo.svg', height: 30 },
|
||||
{ name: 'Atlas Cloud', url: 'https://www.atlascloud.ai/', logo: '/partners/atlas-cloud.png', height: 36 },
|
||||
{
|
||||
name: 'AI/ML API',
|
||||
url: 'https://aimlapi.com/',
|
||||
logo: '/partners/aimlapi-logo.svg',
|
||||
logoDark: '/partners/aimlapi-logo-dark.svg',
|
||||
height: 30,
|
||||
},
|
||||
{ name: 'Novita AI', url: 'https://novita.ai/', logo: '/partners/novita-logo.png', height: 34 },
|
||||
]
|
||||
|
||||
export const community = [
|
||||
{ name: 'github', url: 'https://github.com/Gitlawb/openclaude', label: 'star the repo' },
|
||||
{ name: 'discord', url: 'https://discord.gg/k68zFR6AcB', label: 'join the discord' },
|
||||
{ name: 'x', url: 'https://x.com/gitlawb', label: 'follow @gitlawb' },
|
||||
{ name: 'discussions', url: 'https://github.com/Gitlawb/openclaude/discussions', label: 'open a discussion' },
|
||||
{ name: 'trendshift', url: 'https://trendshift.io/repositories/25807', label: 'featured on trendshift' },
|
||||
]
|
||||
@@ -1,105 +1,375 @@
|
||||
// Seeded from the Supported Providers table in the repo README.
|
||||
// Mirrors the provider preset manifest in
|
||||
// src/integrations/generated/integrationManifest.generated.ts (v0.26.0)
|
||||
// plus the OAuth/special entries injected by the /provider picker.
|
||||
|
||||
export type ProviderGroup =
|
||||
| 'subscriptions'
|
||||
| 'gateways'
|
||||
| 'vendors'
|
||||
| 'local'
|
||||
| 'custom'
|
||||
|
||||
export interface Provider {
|
||||
id: string
|
||||
name: string
|
||||
group: ProviderGroup
|
||||
setup: string
|
||||
envVars?: string[]
|
||||
notes: string
|
||||
badge?: 'recommended' | 'sponsor'
|
||||
}
|
||||
|
||||
export const providerGroups: { id: ProviderGroup; label: string; blurb: string }[] = [
|
||||
{
|
||||
id: 'subscriptions',
|
||||
label: 'sign-in & subscription plans',
|
||||
blurb: 'oauth sign-ins and coding-plan subscriptions — no raw api key handling.',
|
||||
},
|
||||
{
|
||||
id: 'gateways',
|
||||
label: 'gateways',
|
||||
blurb: 'one key, many models — smart routing and aggregation endpoints.',
|
||||
},
|
||||
{
|
||||
id: 'vendors',
|
||||
label: 'direct vendor apis',
|
||||
blurb: 'first-party api endpoints, each with its own key.',
|
||||
},
|
||||
{
|
||||
id: 'local',
|
||||
label: 'local inference',
|
||||
blurb: 'no api key, no network — your machine, your models.',
|
||||
},
|
||||
{
|
||||
id: 'custom',
|
||||
label: 'cloud routes & custom endpoints',
|
||||
blurb: 'enterprise cloud routes and bring-your-own-endpoint escapes.',
|
||||
},
|
||||
]
|
||||
|
||||
export const providers: Provider[] = [
|
||||
{
|
||||
id: 'openai-compatible',
|
||||
name: 'OpenAI-compatible',
|
||||
setup: '/provider or env vars',
|
||||
envVars: ['OPENAI_API_KEY', 'OPENAI_BASE_URL', 'OPENAI_MODEL'],
|
||||
notes: 'Works with OpenAI, OpenRouter, DeepSeek, Groq, Mistral, LM Studio, and any other compatible /v1 server.',
|
||||
},
|
||||
{
|
||||
id: 'opengateway',
|
||||
name: 'Gitlawb Opengateway',
|
||||
setup: 'startup default, /provider, or env vars',
|
||||
envVars: ['OPENAI_API_KEY', 'OPENAI_MODEL'],
|
||||
notes: 'Smart gateway at https://opengateway.gitlawb.com/v1. Requires an API key from gitlawb.com/opengateway/keys; routes Xiaomi MiMo and GMI Cloud partner models by model name.',
|
||||
},
|
||||
{
|
||||
id: 'gemini',
|
||||
name: 'Google AI / Gemini',
|
||||
setup: '/provider or env vars',
|
||||
envVars: ['GOOGLE_API_KEY'],
|
||||
notes: 'Supports API key, access token, and local ADC auth.',
|
||||
},
|
||||
{
|
||||
id: 'github-models',
|
||||
name: 'GitHub Models',
|
||||
setup: '/onboard-github',
|
||||
envVars: ['GITHUB_TOKEN'],
|
||||
notes: 'Interactive onboarding with saved credentials.',
|
||||
},
|
||||
{
|
||||
id: 'codex-oauth',
|
||||
name: 'Codex OAuth',
|
||||
setup: '/provider',
|
||||
notes: 'Opens ChatGPT sign-in in your browser and stores Codex credentials securely. Can also reuse existing Codex CLI auth or env credentials.',
|
||||
},
|
||||
{
|
||||
id: 'near-ai',
|
||||
name: 'NEAR AI',
|
||||
setup: '/provider or env vars',
|
||||
envVars: ['NEARAI_API_KEY'],
|
||||
notes: 'Unified gateway (Claude, GPT, Gemini, plus TEE open models) at https://cloud-api.near.ai/v1.',
|
||||
},
|
||||
{
|
||||
id: 'ollama',
|
||||
name: 'Ollama',
|
||||
setup: '/provider or env vars',
|
||||
notes: 'Local inference with no API key required.',
|
||||
},
|
||||
{
|
||||
id: 'lm-studio',
|
||||
name: 'LM Studio',
|
||||
setup: '/provider or env vars',
|
||||
envVars: ['OPENAI_BASE_URL'],
|
||||
notes: 'Local OpenAI-compatible server; point the base URL at the LM Studio endpoint.',
|
||||
},
|
||||
{
|
||||
id: 'xiaomi-mimo',
|
||||
name: 'Xiaomi MiMo',
|
||||
setup: '/provider or env vars',
|
||||
envVars: ['MIMO_API_KEY'],
|
||||
notes: 'OpenAI-compatible API at https://mimo.mi.com; defaults to mimo-v2.5-pro.',
|
||||
},
|
||||
{
|
||||
id: 'opencode-zen',
|
||||
name: 'OpenCode Zen / Go',
|
||||
setup: '/provider or env vars',
|
||||
envVars: ['OPENCODE_API_KEY'],
|
||||
notes: 'Pay-as-you-go gateway (Zen) and subscription tier for open models (Go); both share one key via opencode.ai.',
|
||||
},
|
||||
{
|
||||
id: 'atomic-chat',
|
||||
name: 'Atomic Chat',
|
||||
setup: '/provider or env vars',
|
||||
notes: 'Local model provider with auto-detection of loaded models.',
|
||||
},
|
||||
{
|
||||
id: 'hicap',
|
||||
name: 'Hicap',
|
||||
setup: '/provider or OpenAI-compatible env vars',
|
||||
notes: 'api-key auth, discovers models from the unauthenticated /models endpoint, supports Responses mode for gpt- models.',
|
||||
},
|
||||
// ── sign-in & subscription plans ─────────────────────────────────────
|
||||
{
|
||||
id: 'anthropic',
|
||||
name: 'Anthropic',
|
||||
name: 'Anthropic / Claude',
|
||||
group: 'subscriptions',
|
||||
setup: '/login or env vars',
|
||||
envVars: ['ANTHROPIC_API_KEY'],
|
||||
notes: 'Sign in with an Anthropic account or use an API key directly.',
|
||||
},
|
||||
{
|
||||
id: 'codex-oauth',
|
||||
name: 'Codex OAuth (ChatGPT)',
|
||||
group: 'subscriptions',
|
||||
setup: '/provider',
|
||||
notes: 'Opens ChatGPT sign-in in your browser and stores Codex credentials securely — including the GPT-5.6 family (Sol, Terra, Luna) via the Responses API. Manual callback paste supported for SSH sessions.',
|
||||
badge: 'recommended',
|
||||
},
|
||||
{
|
||||
id: 'xai-oauth',
|
||||
name: 'xAI OAuth (Grok)',
|
||||
group: 'subscriptions',
|
||||
setup: '/provider or `openclaude auth xai login`',
|
||||
notes: 'Sign in with your xAI account in the browser; device-code flow available for remote hosts with no localhost callback.',
|
||||
},
|
||||
{
|
||||
id: 'github-models',
|
||||
name: 'GitHub Models / Copilot',
|
||||
group: 'subscriptions',
|
||||
setup: '/onboard-github',
|
||||
envVars: ['GITHUB_TOKEN'],
|
||||
notes: 'Interactive onboarding with saved credentials; Copilot and Copilot Enterprise endpoints supported.',
|
||||
},
|
||||
{
|
||||
id: 'kimi-code',
|
||||
name: 'Moonshot Kimi Code',
|
||||
group: 'subscriptions',
|
||||
setup: '/provider or env vars',
|
||||
envVars: ['KIMI_API_KEY'],
|
||||
notes: 'Kimi Code subscription endpoint, including Kimi K3 in 1M and 256K context variants.',
|
||||
},
|
||||
{
|
||||
id: 'zai',
|
||||
name: 'Z.AI GLM Coding Plan',
|
||||
group: 'subscriptions',
|
||||
setup: '/provider or env vars',
|
||||
notes: 'GLM coding subscription endpoint; defaults to glm-5.2.',
|
||||
},
|
||||
{
|
||||
id: 'dashscope',
|
||||
name: 'Alibaba Coding Plan',
|
||||
group: 'subscriptions',
|
||||
setup: '/provider or env vars',
|
||||
envVars: ['DASHSCOPE_API_KEY'],
|
||||
notes: 'DashScope international and China endpoints; defaults to qwen3.6-plus.',
|
||||
},
|
||||
{
|
||||
id: 'xiaomi-mimo-token',
|
||||
name: 'Xiaomi MiMo Token Plan',
|
||||
group: 'subscriptions',
|
||||
setup: '/provider or env vars',
|
||||
envVars: ['MIMO_API_KEY'],
|
||||
notes: 'MiMo subscription endpoint; defaults to mimo-v2.5-pro.',
|
||||
badge: 'sponsor',
|
||||
},
|
||||
|
||||
// ── gateways ─────────────────────────────────────────────────────────
|
||||
{
|
||||
id: 'opengateway',
|
||||
name: 'Gitlawb Opengateway',
|
||||
group: 'gateways',
|
||||
setup: 'startup default, /provider, or env vars',
|
||||
envVars: ['OPENGATEWAY_API_KEY'],
|
||||
notes: 'Smart gateway at https://opengateway.gitlawb.com/v1 with auto smart-routing, MiMo, MiniMax, Qwen, GLM, Gemini — plus free models (Nemotron 3 Ultra, Ling 3.0 Flash, Tencent HY3). Keys at gitlawb.com/opengateway/keys.',
|
||||
badge: 'recommended',
|
||||
},
|
||||
{
|
||||
id: 'aimlapi',
|
||||
name: 'AI/ML API',
|
||||
group: 'gateways',
|
||||
setup: '/provider or `openclaude aimlapi topup`',
|
||||
envVars: ['AIMLAPI_API_KEY'],
|
||||
notes: '1,000+ models behind one OpenAI-compatible endpoint, with guided top-up and key provisioning from the CLI.',
|
||||
badge: 'recommended',
|
||||
},
|
||||
{
|
||||
id: 'openrouter',
|
||||
name: 'OpenRouter',
|
||||
group: 'gateways',
|
||||
setup: '/provider or env vars',
|
||||
envVars: ['OPENROUTER_API_KEY'],
|
||||
notes: 'OpenAI-compatible aggregation across hundreds of hosted models.',
|
||||
},
|
||||
{
|
||||
id: 'near-ai',
|
||||
name: 'NEAR AI',
|
||||
group: 'gateways',
|
||||
setup: '/provider or env vars',
|
||||
envVars: ['NEARAI_API_KEY'],
|
||||
notes: 'Unified gateway (Claude, GPT, Gemini, plus TEE open models) at https://cloud-api.near.ai/v1.',
|
||||
},
|
||||
{
|
||||
id: 'opencode',
|
||||
name: 'OpenCode Zen / Go',
|
||||
group: 'gateways',
|
||||
setup: '/provider or env vars',
|
||||
envVars: ['OPENCODE_API_KEY'],
|
||||
notes: 'Pay-as-you-go gateway (Zen, 48 models) and a $10/mo subscription for open models (Go); one key for both.',
|
||||
},
|
||||
{
|
||||
id: 'clinepass',
|
||||
name: 'ClinePass',
|
||||
group: 'gateways',
|
||||
setup: '/provider or env vars',
|
||||
envVars: ['CLINE_API_KEY'],
|
||||
notes: 'AI model gateway with usage limits and usage reporting.',
|
||||
},
|
||||
{
|
||||
id: 'bankr',
|
||||
name: 'Bankr',
|
||||
group: 'gateways',
|
||||
setup: '/provider or env vars',
|
||||
envVars: ['BNKR_API_KEY'],
|
||||
notes: 'Bankr LLM gateway (OpenAI-compatible) at https://llm.bankr.bot/v1.',
|
||||
},
|
||||
{
|
||||
id: 'hicap',
|
||||
name: 'Hicap',
|
||||
group: 'gateways',
|
||||
setup: '/provider or env vars',
|
||||
envVars: ['HICAP_API_KEY'],
|
||||
notes: 'Discovers models from the /models endpoint; supports Responses mode for gpt- models. Defaults to claude-opus-4.8.',
|
||||
},
|
||||
{
|
||||
id: 'atlas-cloud',
|
||||
name: 'Atlas Cloud',
|
||||
group: 'gateways',
|
||||
setup: '/provider or env vars',
|
||||
envVars: ['ATLAS_CLOUD_API_KEY'],
|
||||
notes: 'OpenAI-compatible hosted open models with reasoning support at https://api.atlascloud.ai/v1.',
|
||||
},
|
||||
{
|
||||
id: 'cloudflare',
|
||||
name: 'Cloudflare Workers AI',
|
||||
group: 'gateways',
|
||||
setup: '/provider or env vars',
|
||||
envVars: ['CLOUDFLARE_API_TOKEN'],
|
||||
notes: 'Workers AI OpenAI-compatible endpoint, scoped to your Cloudflare account id.',
|
||||
},
|
||||
{
|
||||
id: 'nvidia-nim',
|
||||
name: 'NVIDIA NIM',
|
||||
group: 'gateways',
|
||||
setup: '/provider or env vars',
|
||||
envVars: ['NVIDIA_API_KEY'],
|
||||
notes: 'NVIDIA-hosted inference microservices at https://integrate.api.nvidia.com/v1, with reasoning template support.',
|
||||
},
|
||||
|
||||
// ── direct vendor apis ───────────────────────────────────────────────
|
||||
{
|
||||
id: 'openai',
|
||||
name: 'OpenAI',
|
||||
group: 'vendors',
|
||||
setup: '/provider or env vars',
|
||||
envVars: ['OPENAI_API_KEY'],
|
||||
notes: 'Direct OpenAI API; supports key pools via OPENAI_API_KEYS with rotation on failure.',
|
||||
},
|
||||
{
|
||||
id: 'gemini',
|
||||
name: 'Google Gemini',
|
||||
group: 'vendors',
|
||||
setup: '/provider or env vars',
|
||||
envVars: ['GEMINI_API_KEY'],
|
||||
notes: 'Gemini OpenAI-compatible endpoint; API key, access token, and ADC auth modes.',
|
||||
},
|
||||
{
|
||||
id: 'xai',
|
||||
name: 'xAI (API key)',
|
||||
group: 'vendors',
|
||||
setup: '/provider or env vars',
|
||||
envVars: ['XAI_API_KEY'],
|
||||
notes: 'Grok models at https://api.x.ai/v1; defaults to grok-4.3.',
|
||||
},
|
||||
{
|
||||
id: 'deepseek',
|
||||
name: 'DeepSeek',
|
||||
group: 'vendors',
|
||||
setup: '/provider or env vars',
|
||||
envVars: ['DEEPSEEK_API_KEY'],
|
||||
notes: 'DeepSeek OpenAI-compatible endpoint; defaults to deepseek-v4-pro.',
|
||||
},
|
||||
{
|
||||
id: 'moonshot',
|
||||
name: 'Moonshot AI (API)',
|
||||
group: 'vendors',
|
||||
setup: '/provider or env vars',
|
||||
envVars: ['MOONSHOT_API_KEY'],
|
||||
notes: 'Direct Moonshot API, including Kimi K3.',
|
||||
},
|
||||
{
|
||||
id: 'minimax',
|
||||
name: 'MiniMax',
|
||||
group: 'vendors',
|
||||
setup: '/provider or env vars',
|
||||
envVars: ['MINIMAX_API_KEY'],
|
||||
notes: 'Anthropic-compatible MiniMax endpoint; defaults to MiniMax-M3.',
|
||||
},
|
||||
{
|
||||
id: 'longcat',
|
||||
name: 'LongCat',
|
||||
group: 'vendors',
|
||||
setup: '/provider or env vars',
|
||||
envVars: ['LONGCAT_API_KEY'],
|
||||
notes: "Meituan's LongCat OpenAI-compatible API; defaults to LongCat-2.0.",
|
||||
},
|
||||
{
|
||||
id: 'xiaomi-mimo',
|
||||
name: 'Xiaomi MiMo',
|
||||
group: 'vendors',
|
||||
setup: '/provider or env vars',
|
||||
envVars: ['MIMO_API_KEY'],
|
||||
notes: 'OpenAI-compatible API at https://api.xiaomimimo.com/v1; defaults to mimo-v2.5-pro.',
|
||||
badge: 'sponsor',
|
||||
},
|
||||
{
|
||||
id: 'groq',
|
||||
name: 'Groq',
|
||||
group: 'vendors',
|
||||
setup: '/provider or env vars',
|
||||
envVars: ['GROQ_API_KEY'],
|
||||
notes: 'Ultra-low-latency OpenAI-compatible endpoint.',
|
||||
},
|
||||
{
|
||||
id: 'mistral',
|
||||
name: 'Mistral AI',
|
||||
group: 'vendors',
|
||||
setup: '/provider or env vars',
|
||||
envVars: ['MISTRAL_API_KEY'],
|
||||
notes: 'Mistral OpenAI-compatible endpoint.',
|
||||
},
|
||||
{
|
||||
id: 'fireworks',
|
||||
name: 'Fireworks AI',
|
||||
group: 'vendors',
|
||||
setup: '/provider or env vars',
|
||||
envVars: ['FIREWORKS_API_KEY'],
|
||||
notes: 'Hosted open models, including GLM 5.2.',
|
||||
},
|
||||
{
|
||||
id: 'together',
|
||||
name: 'Together AI',
|
||||
group: 'vendors',
|
||||
setup: '/provider or env vars',
|
||||
envVars: ['TOGETHER_API_KEY'],
|
||||
notes: 'Together chat/completions endpoint.',
|
||||
},
|
||||
{
|
||||
id: 'venice',
|
||||
name: 'Venice',
|
||||
group: 'vendors',
|
||||
setup: '/provider or env vars',
|
||||
envVars: ['VENICE_API_KEY'],
|
||||
notes: 'Privacy-focused OpenAI-compatible endpoint.',
|
||||
},
|
||||
|
||||
// ── local inference ──────────────────────────────────────────────────
|
||||
{
|
||||
id: 'ollama',
|
||||
name: 'Ollama',
|
||||
group: 'local',
|
||||
setup: '/provider or env vars',
|
||||
notes: 'Local inference with no API key required; native context-window handling.',
|
||||
},
|
||||
{
|
||||
id: 'lm-studio',
|
||||
name: 'LM Studio',
|
||||
group: 'local',
|
||||
setup: '/provider or env vars',
|
||||
envVars: ['OPENAI_MODEL'],
|
||||
notes: 'Local OpenAI-compatible server at http://localhost:1234/v1.',
|
||||
},
|
||||
{
|
||||
id: 'atomic-chat',
|
||||
name: 'Atomic Chat',
|
||||
group: 'local',
|
||||
setup: '/provider',
|
||||
notes: 'Local model provider at http://127.0.0.1:1337/v1 with auto-detection of loaded models.',
|
||||
},
|
||||
|
||||
// ── cloud routes & custom endpoints ──────────────────────────────────
|
||||
{
|
||||
id: 'cloud-routes',
|
||||
name: 'Bedrock / Vertex / Foundry',
|
||||
group: 'custom',
|
||||
setup: 'env vars',
|
||||
notes: 'Anthropic-family cloud routes. Vertex is for Claude on Vertex AI, not arbitrary Model Garden models.',
|
||||
},
|
||||
{
|
||||
id: 'azure-openai',
|
||||
name: 'Azure OpenAI',
|
||||
group: 'custom',
|
||||
setup: '/provider or env vars',
|
||||
envVars: ['AZURE_OPENAI_API_KEY'],
|
||||
notes: 'Azure OpenAI endpoint where the model name is your deployment name.',
|
||||
},
|
||||
{
|
||||
id: 'openai-compatible',
|
||||
name: 'Custom (OpenAI-compatible)',
|
||||
group: 'custom',
|
||||
setup: '/provider or env vars',
|
||||
envVars: ['OPENAI_API_KEY', 'OPENAI_BASE_URL', 'OPENAI_MODEL'],
|
||||
notes: 'Any compatible /v1 server — LiteLLM, vLLM, llama.cpp, or your own proxy.',
|
||||
},
|
||||
{
|
||||
id: 'anthropic-compatible',
|
||||
name: 'Custom (Anthropic-compatible)',
|
||||
group: 'custom',
|
||||
setup: '/provider or env vars',
|
||||
envVars: ['ANTHROPIC_AUTH_TOKEN', 'ANTHROPIC_BASE_URL', 'ANTHROPIC_MODEL'],
|
||||
notes: 'Any Anthropic Messages API-compatible provider.',
|
||||
},
|
||||
]
|
||||
|
||||
export function providersInGroup(group: ProviderGroup): Provider[] {
|
||||
return providers.filter(p => p.group === group)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,124 @@
|
||||
// Curated release highlights for the /changelog page. Full notes live on
|
||||
// GitHub releases; this list is the hand-picked "what actually matters"
|
||||
// per minor version. Newest first.
|
||||
|
||||
export interface Release {
|
||||
version: string
|
||||
date: string
|
||||
/** one-line theme of the release */
|
||||
theme: string
|
||||
highlights: string[]
|
||||
}
|
||||
|
||||
export const RELEASES_URL = 'https://github.com/Gitlawb/openclaude/releases'
|
||||
|
||||
export function releaseUrl(version: string): string {
|
||||
return `${RELEASES_URL}/tag/v${version}`
|
||||
}
|
||||
|
||||
export const releases: Release[] = [
|
||||
{
|
||||
version: '0.26.0',
|
||||
date: '2026-07-27',
|
||||
theme: 'polish — steadier long turns, sharper streaming feedback',
|
||||
highlights: [
|
||||
'long-running tools stay active instead of tripping the query guard',
|
||||
'streaming token counts appear immediately, not after the first chunk',
|
||||
'AI/ML API client hardening: passwordless methods and response-shape guards',
|
||||
'windows: tolerate EPERM from mkdir on drive roots',
|
||||
'slash-command arguments insert literally — no more accidental regex references',
|
||||
],
|
||||
},
|
||||
{
|
||||
version: '0.25.0',
|
||||
date: '2026-07-20',
|
||||
theme: 'buddy companions, GPT-5.6, and a leaner install',
|
||||
highlights: [
|
||||
'buddy: pixel-art hero companions with signature Enter animations — /buddy to hatch one',
|
||||
'GPT-5.6 family models on Codex, routed through the OpenAI Responses API',
|
||||
'first-run onboarding experience for third-party providers',
|
||||
'token optimization: universal tool compression, doom-loop detection, configurable compaction',
|
||||
'zero-warning npm install, enforced by a release gate and daily 3-OS checks',
|
||||
'context bar shows live token counts — ctx 74K/200K (37%)',
|
||||
'new providers: LongCat, AI/ML API foundation, Kimi K3 context variants',
|
||||
],
|
||||
},
|
||||
{
|
||||
version: '0.24.0',
|
||||
date: '2026-07-14',
|
||||
theme: 'effort control and per-model tuning',
|
||||
highlights: [
|
||||
'ultrathink keyword detection and the ultracode effort level',
|
||||
'Cloudflare Workers AI provider integration',
|
||||
'per-model context_window and max_output_tokens overrides in settings',
|
||||
'Codex OAuth: manual callback URL paste for SSH and remote sessions',
|
||||
'/doctor gains WebSearch backend diagnostics',
|
||||
'/model surfaces inactive provider profiles',
|
||||
],
|
||||
},
|
||||
{
|
||||
version: '0.23.0',
|
||||
date: '2026-07-07',
|
||||
theme: 'codebase intelligence and skills everywhere',
|
||||
highlights: [
|
||||
'repo map codebase intelligence',
|
||||
'smart auto-routing: per-turn simple-vs-strong model selection',
|
||||
'AI/ML API joins as a first-class provider',
|
||||
'local skill CLI support plus a native-TypeScript PDF generation skill',
|
||||
'ship a zero-warning, minimal install',
|
||||
'openclaude config fully isolated from Claude Code',
|
||||
],
|
||||
},
|
||||
{
|
||||
version: '0.22.0',
|
||||
date: '2026-07-06',
|
||||
theme: 'honest feedback and LSP visibility',
|
||||
highlights: [
|
||||
'LSP diagnostics captured and exposed to the agent',
|
||||
'task reports render as markdown',
|
||||
'honest-feedback UX pass: visible retries, statusline truncation marker, hint grace period',
|
||||
'resume picker groups branched sessions',
|
||||
],
|
||||
},
|
||||
{
|
||||
version: '0.21.0',
|
||||
date: '2026-06-30',
|
||||
theme: 'session branching and effort routing',
|
||||
highlights: [
|
||||
'/branch forks a conversation into a new session',
|
||||
'/set-context-window and /clear-context-window commands',
|
||||
'model-level reasoning effort routing',
|
||||
'per-agent step limits and deterministic session task reports',
|
||||
'Claude Opus 4.8 support; ClinePass gateway provider',
|
||||
'recover GLM/Qwen tool calls emitted as XML text',
|
||||
'perf: minified CLI bundle and lazy provider catalog loading',
|
||||
],
|
||||
},
|
||||
{
|
||||
version: '0.20.0',
|
||||
date: '2026-06-24',
|
||||
theme: 'background sessions and the bughunter goes public',
|
||||
highlights: [
|
||||
'local background sessions — run agents while you keep typing',
|
||||
'/bughunter public, with /bughunter-security and /bughunter-perf variants',
|
||||
'per-agent model assignment from the /agents menu',
|
||||
'/update command with package-manager auto-detection',
|
||||
'auto-detect and persist project conventions to the wiki',
|
||||
'OpenAI-compatible credential pool failover; OPENCLAUDE_CONFIG_DIR override',
|
||||
'GLM 5.2 across Z.AI, Fireworks, Atlas Cloud, and Opengateway',
|
||||
],
|
||||
},
|
||||
{
|
||||
version: '0.19.0',
|
||||
date: '2026-06-16',
|
||||
theme: 'context visibility and new backends',
|
||||
highlights: [
|
||||
'/ctx context-window visualization and token bars in /cost',
|
||||
'NEAR AI provider integration',
|
||||
'native Gemini Vertex client and auth helpers',
|
||||
'compactModel option — use a separate model for compaction',
|
||||
'auto-compact prompt on /resume with a determinate progress bar',
|
||||
'redacted diagnostic issue reports',
|
||||
],
|
||||
},
|
||||
]
|
||||
@@ -1,3 +1,5 @@
|
||||
import pkg from '../../../package.json'
|
||||
|
||||
export const SITE = {
|
||||
url: 'https://openclaude.gitlawb.com',
|
||||
name: 'openclaude',
|
||||
@@ -9,7 +11,7 @@ export const SITE = {
|
||||
github: 'https://github.com/Gitlawb/openclaude',
|
||||
gitlawb: 'https://gitlawb.com',
|
||||
gitlawbRepo: 'https://gitlawb.com/node/repos/z6MkqDnb/openclaude',
|
||||
version: '0.18.0',
|
||||
version: pkg.version,
|
||||
ogDefault: '/og/default.png',
|
||||
ogDocs: '/og/docs.png',
|
||||
} as const
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
// Seeded from src/skills/bundledSkills.ts + src/i18n/languages/en.ts.
|
||||
// Feature-gated and hidden (non-user-invocable) skills are intentionally excluded.
|
||||
|
||||
export interface Skill {
|
||||
name: string
|
||||
@@ -13,12 +14,6 @@ export const skills: Skill[] = [
|
||||
description:
|
||||
'Research and plan a large-scale change, then execute it in parallel across 5–30 isolated worktree agents that each open a PR. Use for sweeping, mechanical changes (migrations, refactors, bulk renames) that decompose into independent units.',
|
||||
},
|
||||
{
|
||||
name: 'loop',
|
||||
invocation: '/loop',
|
||||
description:
|
||||
'Run a prompt on a fixed interval or dynamically reschedule it. Use to poll for status, babysit a workflow, or keep re-running a prompt within the current session.',
|
||||
},
|
||||
{
|
||||
name: 'simplify',
|
||||
invocation: '/simplify',
|
||||
@@ -30,16 +25,16 @@ export const skills: Skill[] = [
|
||||
invocation: '/debug',
|
||||
description: 'Enable debug logging for this session and help diagnose issues.',
|
||||
},
|
||||
{
|
||||
name: 'pdf',
|
||||
invocation: '/pdf',
|
||||
description:
|
||||
'Generate PDF documents from structured content — reports, formatted documents, tables, and more. Native TypeScript implementation, no external tooling.',
|
||||
},
|
||||
{
|
||||
name: 'update-config',
|
||||
invocation: '/update-config',
|
||||
description:
|
||||
'Configure the harness via settings.json: permissions, env vars, hooks, and automated behaviors ("from now on when X…").',
|
||||
},
|
||||
{
|
||||
name: 'keybindings-help',
|
||||
invocation: '/keybindings-help',
|
||||
description:
|
||||
'Customize keyboard shortcuts: rebind keys, add chord bindings, or modify your keybindings file (default: ~/.openclaude/keybindings.json; override via OPENCLAUDE_CONFIG_DIR).',
|
||||
},
|
||||
]
|
||||
|
||||
@@ -0,0 +1,290 @@
|
||||
---
|
||||
import BaseLayout from '../layouts/BaseLayout.astro'
|
||||
import Terminal from '../components/Terminal.astro'
|
||||
import { SITE } from '../data/site'
|
||||
import { heroes, buddyCommands, rarities, statNames } from '../data/buddy'
|
||||
|
||||
const title = 'buddy — pixel-art hero companions for your terminal | openclaude'
|
||||
const description =
|
||||
'Hatch a truecolor pixel-art hero who lives in your openclaude prompt and fires an arrow, energy wave, or fireball every time you press Enter. Seven heroes, rarity rolls, and zero effect on your tokens.'
|
||||
|
||||
const jsonLd = [
|
||||
{
|
||||
'@context': 'https://schema.org',
|
||||
'@type': 'WebPage',
|
||||
name: 'openclaude buddy — pixel-art hero companions',
|
||||
url: `${SITE.url}/buddy/`,
|
||||
description,
|
||||
isPartOf: { '@type': 'WebSite', name: SITE.name, url: SITE.url },
|
||||
},
|
||||
{
|
||||
'@context': 'https://schema.org',
|
||||
'@type': 'BreadcrumbList',
|
||||
itemListElement: [
|
||||
{ '@type': 'ListItem', position: 1, name: 'openclaude', item: SITE.url },
|
||||
{ '@type': 'ListItem', position: 2, name: 'buddy', item: `${SITE.url}/buddy/` },
|
||||
],
|
||||
},
|
||||
]
|
||||
---
|
||||
|
||||
<BaseLayout title={title} description={description} ogImage="/og/buddy.png" jsonLd={jsonLd}>
|
||||
<section class="hero grid-bg" aria-labelledby="buddy-heading">
|
||||
<div class="container hero-inner">
|
||||
<p class="pill"><span class="dot" aria-hidden="true"></span>ships with every install · pure fun · zero tokens</p>
|
||||
<h1 id="buddy-heading" class="text-hero">meet your buddy.</h1>
|
||||
<p class="hero-sub">
|
||||
a truecolor pixel-art hero who stands guard beside your prompt — and fires an
|
||||
arrow, an energy wave, or a fireball every time you press enter. really.
|
||||
</p>
|
||||
<div class="showcase" role="img" aria-label="Robin Hood buddy firing an arrow toward the prompt">
|
||||
<img src="/buddy/robinhood-action.svg" alt="" width="460" height="160" loading="eager" />
|
||||
</div>
|
||||
<p class="hero-foot">type <code>/buddy</code> in any openclaude session to hatch yours.</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="section" aria-labelledby="heroes-heading">
|
||||
<div class="container">
|
||||
<div class="section-head">
|
||||
<p class="eyebrow">// the heroes</p>
|
||||
<h2 id="heroes-heading">seven forms. seven signature attacks.</h2>
|
||||
<p class="lede">
|
||||
every buddy can take any form with <code>/buddy set</code>. each one has its own
|
||||
enter animation, drawn frame-by-frame in the footer while your message goes out.
|
||||
</p>
|
||||
</div>
|
||||
<div class="hero-grid">
|
||||
{heroes.map(h => (
|
||||
<article class="hero-card" style={`--hero-accent: ${h.accent}`}>
|
||||
<div class="sprite">
|
||||
<img src={`/buddy/${h.id}.svg`} alt={`${h.name} pixel-art sprite`} width="110" height="80" loading="lazy" />
|
||||
</div>
|
||||
<h3>{h.name}</h3>
|
||||
<p class="attack"><span class="attack-name">{h.attack}</span> — {h.attackDetail}</p>
|
||||
<p class="flavor">“{h.flavor}.”</p>
|
||||
<code class="set-cmd">/buddy set {h.id}</code>
|
||||
</article>
|
||||
))}
|
||||
<article class="hero-card hero-card-mystery">
|
||||
<div class="sprite mystery" aria-hidden="true">?</div>
|
||||
<h3>your roll</h3>
|
||||
<p class="attack"><span class="attack-name">the rolled form</span> — every buddy hatches as one of the seven, rolled fair from your user id.</p>
|
||||
<p class="flavor">“which one will you get?”</p>
|
||||
<code class="set-cmd">/buddy set random</code>
|
||||
</article>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="section section-tint" aria-labelledby="hatch-heading">
|
||||
<div class="container">
|
||||
<div class="section-head">
|
||||
<p class="eyebrow">// how it works</p>
|
||||
<h2 id="hatch-heading">hatched once, yours forever.</h2>
|
||||
</div>
|
||||
<div class="hatch-grid">
|
||||
<Terminal>
|
||||
<span class="ln"><span class="t-prompt">></span> /buddy</span>
|
||||
<span class="ln"> </span>
|
||||
<span class="ln"><span class="t-ok">●</span> an egg wobbles… and hatches!</span>
|
||||
<span class="ln"> </span>
|
||||
<span class="ln">Novaspark is your <span class="t-warn">Rare</span> robinhood.</span>
|
||||
<span class="ln t-dim">DEBUGGING ▮▮▮▮▮▮▮░░░ · PATIENCE ▮▮▮░░░░░░░ · CHAOS ▮▮▮▮▮▮▮▮▮░</span>
|
||||
<span class="ln t-dim">Submit any message to see the arrow fly.</span>
|
||||
<span class="ln cursor"> </span>
|
||||
</Terminal>
|
||||
<div class="hatch-facts">
|
||||
<ul>
|
||||
<li>
|
||||
<strong>a fair roll.</strong> rarity, stats, and looks are rolled once from your
|
||||
user id — {rarities.map(r => `${r.name} ${r.weight}`).join(', ')}. there's a 1%
|
||||
shiny chance, and no, you can't edit the config file to fake a legendary.
|
||||
</li>
|
||||
<li>
|
||||
<strong>five stats.</strong> every companion rolls {statNames.join(', ').toLowerCase()} —
|
||||
purely cosmetic, deeply important.
|
||||
</li>
|
||||
<li>
|
||||
<strong>a generated name.</strong> names like Bytebean and Novaspark, plus one of
|
||||
five personalities. rename anytime with <code>/buddy name</code>.
|
||||
</li>
|
||||
<li>
|
||||
<strong>zero overhead.</strong> the buddy is pure terminal rendering — it never
|
||||
touches your context window, tokens, or requests.
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="section" aria-labelledby="commands-heading">
|
||||
<div class="container">
|
||||
<div class="section-head">
|
||||
<p class="eyebrow">// commands</p>
|
||||
<h2 id="commands-heading">the whole api is one slash command.</h2>
|
||||
</div>
|
||||
<table class="cmd-table">
|
||||
<thead>
|
||||
<tr><th scope="col">command</th><th scope="col">what it does</th></tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{buddyCommands.map(c => (
|
||||
<tr>
|
||||
<td><code>{c.cmd}</code></td>
|
||||
<td>{c.desc}</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
<p class="text-body-dim table-foot">
|
||||
pixel art needs a truecolor-capable terminal; on low-color terminals the buddy
|
||||
gracefully falls back to line-art sprites. muting hides everything instantly.
|
||||
</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="section section-tint" aria-labelledby="get-heading">
|
||||
<div class="container">
|
||||
<div class="section-head">
|
||||
<p class="eyebrow">// get one</p>
|
||||
<h2 id="get-heading">your prompt is lonely.</h2>
|
||||
<p class="lede">
|
||||
install openclaude, open any repo, and type <code>/buddy</code>.
|
||||
<a href="/docs/installation/"> installation guide →</a>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</BaseLayout>
|
||||
|
||||
<style>
|
||||
.hero {
|
||||
padding: calc(var(--nav-h) + clamp(3.5rem, 10vh, 6rem)) 0 clamp(3rem, 8vh, 5rem);
|
||||
}
|
||||
.hero-inner {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
text-align: center;
|
||||
gap: 1.4rem;
|
||||
}
|
||||
.hero-sub {
|
||||
max-width: 34rem;
|
||||
margin: 0;
|
||||
color: var(--muted);
|
||||
font-size: clamp(0.9rem, 1.4vw, 1rem);
|
||||
line-height: 1.7;
|
||||
}
|
||||
.hero-foot { margin: 0; color: var(--faint); font-size: 0.78rem; }
|
||||
.hero-foot code { color: var(--accent); }
|
||||
.showcase {
|
||||
margin-top: 0.5rem;
|
||||
padding: 1.25rem 1.5rem;
|
||||
border: 1px solid var(--line);
|
||||
background: var(--background);
|
||||
}
|
||||
.showcase img {
|
||||
display: block;
|
||||
width: min(460px, 80vw);
|
||||
height: auto;
|
||||
image-rendering: pixelated;
|
||||
}
|
||||
|
||||
.hero-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(15rem, 1fr));
|
||||
gap: 1px;
|
||||
border: 1px solid var(--line);
|
||||
background: var(--line);
|
||||
}
|
||||
.hero-card {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.6rem;
|
||||
padding: 1.5rem;
|
||||
background: var(--background);
|
||||
}
|
||||
.sprite {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
padding: 0.75rem 0 0.5rem;
|
||||
}
|
||||
.sprite img {
|
||||
width: 110px;
|
||||
height: auto;
|
||||
image-rendering: pixelated;
|
||||
}
|
||||
.sprite.mystery {
|
||||
align-items: center;
|
||||
height: 92px;
|
||||
color: var(--faint);
|
||||
font-size: 3rem;
|
||||
font-weight: 700;
|
||||
}
|
||||
.hero-card-mystery { --hero-accent: var(--faint); }
|
||||
.hero-card-mystery h3 { color: var(--muted); }
|
||||
.hero-card h3 {
|
||||
margin: 0;
|
||||
color: var(--hero-accent);
|
||||
font-size: 0.95rem;
|
||||
font-weight: 600;
|
||||
letter-spacing: 0.04em;
|
||||
}
|
||||
.attack { margin: 0; color: var(--muted); font-size: 0.82rem; line-height: 1.6; }
|
||||
.attack-name { color: var(--ink); font-weight: 600; }
|
||||
.flavor { margin: 0; color: var(--faint); font-size: 0.78rem; font-style: italic; }
|
||||
.set-cmd {
|
||||
margin-top: auto;
|
||||
padding-top: 0.6rem;
|
||||
color: var(--accent);
|
||||
font-size: 0.78rem;
|
||||
}
|
||||
|
||||
.hatch-grid {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr;
|
||||
gap: 2rem;
|
||||
align-items: start;
|
||||
}
|
||||
@media (min-width: 60rem) {
|
||||
.hatch-grid { grid-template-columns: minmax(0, 1.2fr) minmax(0, 1fr); }
|
||||
}
|
||||
.hatch-facts ul { margin: 0; padding: 0; list-style: none; }
|
||||
.hatch-facts li {
|
||||
padding: 0.9rem 0;
|
||||
border-bottom: 1px solid var(--line);
|
||||
color: var(--muted);
|
||||
font-size: 0.85rem;
|
||||
line-height: 1.7;
|
||||
}
|
||||
.hatch-facts li:first-child { padding-top: 0; }
|
||||
.hatch-facts strong { color: var(--ink); font-weight: 600; }
|
||||
.hatch-facts code { color: var(--accent); }
|
||||
|
||||
.cmd-table {
|
||||
width: 100%;
|
||||
border: 1px solid var(--line);
|
||||
border-collapse: collapse;
|
||||
font-size: 0.85rem;
|
||||
}
|
||||
.cmd-table th, .cmd-table td {
|
||||
padding: 0.7rem 1rem;
|
||||
border-bottom: 1px solid var(--line);
|
||||
text-align: left;
|
||||
vertical-align: top;
|
||||
}
|
||||
.cmd-table th {
|
||||
color: var(--faint);
|
||||
font-size: 0.72rem;
|
||||
font-weight: 600;
|
||||
letter-spacing: 0.12em;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
.cmd-table td { color: var(--muted); line-height: 1.6; }
|
||||
.cmd-table td code { color: var(--ink); white-space: nowrap; }
|
||||
.table-foot { margin-top: 1rem; font-size: 0.8rem; }
|
||||
|
||||
.lede a { color: var(--accent); }
|
||||
</style>
|
||||
@@ -0,0 +1,144 @@
|
||||
---
|
||||
import BaseLayout from '../layouts/BaseLayout.astro'
|
||||
import { SITE } from '../data/site'
|
||||
import { releases, releaseUrl, RELEASES_URL } from '../data/releases'
|
||||
|
||||
const title = `what's new in openclaude — release highlights | openclaude`
|
||||
const description = `Curated highlights from every openclaude release: buddy companions, GPT-5.6 support, new providers, background sessions, and more. Currently at v${SITE.version}.`
|
||||
|
||||
const jsonLd = [
|
||||
{
|
||||
'@context': 'https://schema.org',
|
||||
'@type': 'WebPage',
|
||||
name: `what's new in openclaude`,
|
||||
url: `${SITE.url}/changelog/`,
|
||||
description,
|
||||
isPartOf: { '@type': 'WebSite', name: SITE.name, url: SITE.url },
|
||||
},
|
||||
{
|
||||
'@context': 'https://schema.org',
|
||||
'@type': 'BreadcrumbList',
|
||||
itemListElement: [
|
||||
{ '@type': 'ListItem', position: 1, name: 'openclaude', item: SITE.url },
|
||||
{ '@type': 'ListItem', position: 2, name: 'changelog', item: `${SITE.url}/changelog/` },
|
||||
],
|
||||
},
|
||||
]
|
||||
---
|
||||
|
||||
<BaseLayout title={title} description={description} jsonLd={jsonLd}>
|
||||
<section class="hero grid-bg" aria-labelledby="changelog-heading">
|
||||
<div class="container hero-inner">
|
||||
<p class="pill"><span class="dot" aria-hidden="true"></span>currently v{SITE.version}</p>
|
||||
<h1 id="changelog-heading" class="text-hero">what's new.</h1>
|
||||
<p class="hero-sub">
|
||||
the highlights that matter from each release — hand-picked, not a commit dump.
|
||||
full notes live on <a href={RELEASES_URL} rel="noopener">github releases</a>.
|
||||
</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="section" aria-label="release history">
|
||||
<div class="container">
|
||||
<ol class="timeline">
|
||||
{releases.map(r => (
|
||||
<li class="release">
|
||||
<div class="release-meta">
|
||||
<a class="release-ver" href={releaseUrl(r.version)} rel="noopener">v{r.version}</a>
|
||||
<time datetime={r.date}>{r.date}</time>
|
||||
</div>
|
||||
<div class="release-body">
|
||||
<h2>{r.theme}</h2>
|
||||
<ul>
|
||||
{r.highlights.map(h => <li>{h}</li>)}
|
||||
</ul>
|
||||
</div>
|
||||
</li>
|
||||
))}
|
||||
</ol>
|
||||
<p class="text-body-dim older">
|
||||
looking for something older? the complete history is in the
|
||||
<a href={`${SITE.github}/blob/main/CHANGELOG.md`} rel="noopener"> changelog on github</a>.
|
||||
</p>
|
||||
</div>
|
||||
</section>
|
||||
</BaseLayout>
|
||||
|
||||
<style>
|
||||
.hero {
|
||||
padding: calc(var(--nav-h) + clamp(3.5rem, 10vh, 6rem)) 0 clamp(3rem, 8vh, 5rem);
|
||||
}
|
||||
.hero-inner {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
text-align: center;
|
||||
gap: 1.4rem;
|
||||
}
|
||||
.hero-sub {
|
||||
max-width: 34rem;
|
||||
margin: 0;
|
||||
color: var(--muted);
|
||||
font-size: clamp(0.9rem, 1.4vw, 1rem);
|
||||
line-height: 1.7;
|
||||
}
|
||||
.hero-sub a { color: var(--accent); }
|
||||
|
||||
.timeline {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
list-style: none;
|
||||
max-width: 46rem;
|
||||
}
|
||||
.release {
|
||||
display: grid;
|
||||
grid-template-columns: 7.5rem 1fr;
|
||||
gap: 1.5rem;
|
||||
padding: 1.75rem 0;
|
||||
border-bottom: 1px solid var(--line);
|
||||
}
|
||||
.release:first-child { padding-top: 0; }
|
||||
@media (max-width: 40rem) {
|
||||
.release { grid-template-columns: 1fr; gap: 0.5rem; }
|
||||
}
|
||||
.release-meta {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.3rem;
|
||||
}
|
||||
.release-ver {
|
||||
color: var(--accent);
|
||||
font-size: 0.95rem;
|
||||
font-weight: 600;
|
||||
}
|
||||
.release-meta time { color: var(--faint); font-size: 0.72rem; letter-spacing: 0.04em; }
|
||||
.release-body h2 {
|
||||
margin: 0 0 0.75rem;
|
||||
color: var(--ink);
|
||||
font-size: 0.95rem;
|
||||
font-weight: 600;
|
||||
line-height: 1.5;
|
||||
}
|
||||
.release-body ul {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
list-style: none;
|
||||
}
|
||||
.release-body li {
|
||||
position: relative;
|
||||
padding: 0.25rem 0 0.25rem 1.1rem;
|
||||
color: var(--muted);
|
||||
font-size: 0.84rem;
|
||||
line-height: 1.65;
|
||||
}
|
||||
.release-body li::before {
|
||||
content: '●';
|
||||
position: absolute;
|
||||
left: 0;
|
||||
color: var(--accent);
|
||||
font-size: 0.5rem;
|
||||
top: 0.72rem;
|
||||
}
|
||||
.older { margin-top: 2rem; font-size: 0.8rem; }
|
||||
.older a { color: var(--accent); }
|
||||
</style>
|
||||
@@ -1,19 +1,19 @@
|
||||
---
|
||||
import DocsLayout from '../../layouts/DocsLayout.astro'
|
||||
import { providers } from '../../data/providers'
|
||||
import { providerGroups, providersInGroup, providers } from '../../data/providers'
|
||||
|
||||
const toc = [
|
||||
{ id: 'how-it-works', label: 'how provider setup works' },
|
||||
{ id: 'supported-providers', label: 'supported providers' },
|
||||
...providerGroups.map(g => ({ id: g.id, label: g.label })),
|
||||
{ id: 'env-setup', label: 'env-var setup' },
|
||||
]
|
||||
---
|
||||
|
||||
<DocsLayout
|
||||
title="openclaude providers — OpenAI, Gemini, Ollama & 13+ backends"
|
||||
description="Connect openclaude to any model provider: Anthropic, OpenAI-compatible APIs, OpenRouter, Gemini, GitHub Models, Codex OAuth, Ollama, LM Studio, Bedrock, Vertex, and more."
|
||||
title={`openclaude providers — ${providers.length}+ backends: OpenAI, Gemini, Grok, Ollama & more`}
|
||||
description="Connect openclaude to any model provider: Anthropic, OpenAI, Codex OAuth, xAI Grok, Gemini, DeepSeek, Kimi, GLM, Cloudflare Workers AI, NVIDIA NIM, Ollama, LM Studio, Bedrock, Vertex, and any OpenAI-compatible endpoint."
|
||||
heading="model providers"
|
||||
lede="One agent, every backend. Wire a provider once and switch models mid-session."
|
||||
lede={`One agent, every backend — ${providers.length} first-class providers plus any compatible endpoint. Wire one once and switch models mid-session.`}
|
||||
toc={toc}
|
||||
>
|
||||
<h2 id="how-it-works">how provider setup works</h2>
|
||||
@@ -27,33 +27,43 @@ const toc = [
|
||||
<p>
|
||||
Switch models any time with <code>/model</code>, or pass
|
||||
<code>--provider</code> / <code>--model</code> on the command line — see the
|
||||
<a href="/docs/cli-reference/">CLI reference</a>.
|
||||
<a href="/docs/cli-reference/">CLI reference</a>. If a provider rate-limits you,
|
||||
a <code>providerFallbackChain</code> in settings retries the turn on the next
|
||||
profile automatically.
|
||||
</p>
|
||||
|
||||
<h2 id="supported-providers">supported providers</h2>
|
||||
<div class="table-wrap">
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col">provider</th>
|
||||
<th scope="col">setup</th>
|
||||
<th scope="col">notes</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{providers.map(p => (
|
||||
<tr id={p.id}>
|
||||
<td>{p.name}</td>
|
||||
<td>{p.setup}</td>
|
||||
<td>
|
||||
{p.notes}
|
||||
{p.envVars && <span class="env-list"> uses <code>{p.envVars.join(', ')}</code></span>}
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{providerGroups.map(g => (
|
||||
<>
|
||||
<h2 id={g.id}>{g.label}</h2>
|
||||
<p>{g.blurb}</p>
|
||||
<div class="table-wrap">
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col">provider</th>
|
||||
<th scope="col">setup</th>
|
||||
<th scope="col">notes</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{providersInGroup(g.id).map(p => (
|
||||
<tr id={p.id}>
|
||||
<td>
|
||||
{p.name}
|
||||
{p.badge && <span class={`badge badge-${p.badge}`}>{p.badge}</span>}
|
||||
</td>
|
||||
<td>{p.setup}</td>
|
||||
<td>
|
||||
{p.notes}
|
||||
{p.envVars && <span class="env-list"> uses <code>{p.envVars.join(', ')}</code></span>}
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</>
|
||||
))}
|
||||
|
||||
<h2 id="env-setup">env-var setup</h2>
|
||||
<p>The fastest OpenAI-compatible setup (works for OpenRouter, DeepSeek, Groq, LM Studio, LiteLLM, and any /v1 server):</p>
|
||||
@@ -69,3 +79,19 @@ openclaude</code></pre>
|
||||
<a href="/docs/configuration/#environment-variables">configuration page</a>.
|
||||
</p>
|
||||
</DocsLayout>
|
||||
|
||||
<style>
|
||||
.badge {
|
||||
display: inline-block;
|
||||
margin-left: 0.5rem;
|
||||
padding: 0.1rem 0.4rem;
|
||||
border: 1px solid var(--line);
|
||||
font-size: 0.6rem;
|
||||
letter-spacing: 0.08em;
|
||||
text-transform: uppercase;
|
||||
vertical-align: middle;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.badge-recommended { color: var(--green); border-color: var(--green); }
|
||||
.badge-sponsor { color: var(--accent); border-color: var(--accent); }
|
||||
</style>
|
||||
|
||||
@@ -3,6 +3,7 @@ import BaseLayout from '../layouts/BaseLayout.astro'
|
||||
import CopyCommand from '../components/CopyCommand.astro'
|
||||
import Terminal from '../components/Terminal.astro'
|
||||
import { SITE } from '../data/site'
|
||||
import { partners, community } from '../data/partners'
|
||||
|
||||
const features = [
|
||||
{
|
||||
@@ -44,8 +45,9 @@ const features = [
|
||||
]
|
||||
|
||||
const providers = [
|
||||
'OpenAI', 'Gemini', 'Ollama', 'GitHub Models', 'OpenRouter', 'DeepSeek',
|
||||
'Groq', 'Mistral', 'LM Studio', 'NEAR AI', 'Xiaomi MiMo', 'LiteLLM',
|
||||
'OpenAI', 'Gemini', 'Grok', 'Ollama', 'GitHub Models', 'OpenRouter',
|
||||
'DeepSeek', 'Kimi', 'GLM', 'Cloudflare', 'NVIDIA NIM', 'AI/ML API',
|
||||
'Groq', 'Mistral', 'LM Studio', 'Xiaomi MiMo',
|
||||
]
|
||||
|
||||
const jsonLd = [
|
||||
@@ -87,7 +89,7 @@ const jsonLd = [
|
||||
<a class="button button-ghost" href="/docs/quickstart/">read the docs →</a>
|
||||
</div>
|
||||
<p class="hero-foot fade-up fade-up-4">
|
||||
works with openai, gemini, codex, ollama, lm studio, litellm, and 200+ models.
|
||||
works with openai, gemini, codex, grok, ollama, lm studio, and 200+ models.
|
||||
</p>
|
||||
</div>
|
||||
</section>
|
||||
@@ -153,7 +155,56 @@ const jsonLd = [
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="section section-tint" aria-labelledby="install-heading">
|
||||
<section class="section" aria-labelledby="buddy-heading">
|
||||
<div class="container buddy-teaser">
|
||||
<div class="buddy-copy">
|
||||
<p class="eyebrow">// buddy</p>
|
||||
<h2 id="buddy-heading">ship with a hero at your side.</h2>
|
||||
<p class="lede">
|
||||
hatch a pixel-art companion who lives beside your prompt and fires an arrow —
|
||||
or an energy wave, or dragon fire — every time you press enter. seven heroes,
|
||||
rarity rolls, zero tokens.
|
||||
</p>
|
||||
<a class="button button-ghost" href="/buddy/">meet the heroes →</a>
|
||||
</div>
|
||||
<div class="buddy-art">
|
||||
<img src="/buddy/robinhood-action.svg" alt="Robin Hood pixel-art buddy firing an arrow" width="368" height="128" loading="lazy" />
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="section section-tint" aria-labelledby="partners-heading">
|
||||
<div class="container">
|
||||
<div class="section-head">
|
||||
<p class="eyebrow">// partners</p>
|
||||
<h2 id="partners-heading">backed by teams betting on open agents.</h2>
|
||||
</div>
|
||||
<ul class="partner-strip" aria-label="partners">
|
||||
{partners.map(p => (
|
||||
<li>
|
||||
<a href={p.url} rel="noopener sponsored" aria-label={p.name}>
|
||||
{p.logoDark ? (
|
||||
<>
|
||||
<img class="logo-light" src={p.logo} alt={`${p.name} logo`} style={`height:${p.height}px`} loading="lazy" />
|
||||
<img class="logo-dark" src={p.logoDark} alt={`${p.name} logo`} style={`height:${p.height}px`} loading="lazy" />
|
||||
</>
|
||||
) : (
|
||||
<img src={p.logo} alt={`${p.name} logo`} style={`height:${p.height}px`} loading="lazy" />
|
||||
)}
|
||||
<span>{p.name}</span>
|
||||
</a>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
<ul class="community-strip" aria-label="community links">
|
||||
{community.map(c => (
|
||||
<li><a href={c.url} rel="noopener">[{c.label}]</a></li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="section" aria-labelledby="install-heading">
|
||||
<div class="container">
|
||||
<div class="section-head">
|
||||
<p class="eyebrow">// install</p>
|
||||
@@ -166,7 +217,7 @@ const jsonLd = [
|
||||
<span class="step-num">01</span>
|
||||
<div>
|
||||
<strong>install</strong>
|
||||
<p>requires node ≥ 20. <a href="/docs/installation/">platform guides →</a></p>
|
||||
<p>requires node ≥ 22. <a href="/docs/installation/">platform guides →</a></p>
|
||||
</div>
|
||||
</li>
|
||||
<li>
|
||||
@@ -280,4 +331,78 @@ const jsonLd = [
|
||||
}
|
||||
.install-steps p { margin: 0; color: var(--muted); font-size: 0.85rem; line-height: 1.6; }
|
||||
.install-steps a { color: var(--accent); }
|
||||
|
||||
.buddy-teaser {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr;
|
||||
gap: 2rem;
|
||||
align-items: center;
|
||||
}
|
||||
@media (min-width: 56rem) {
|
||||
.buddy-teaser { grid-template-columns: minmax(0, 1.1fr) minmax(0, 1fr); }
|
||||
}
|
||||
.buddy-copy h2 { margin: 0.85rem 0 0; }
|
||||
.buddy-copy .lede { margin: 0.85rem 0 1.25rem; color: var(--muted); }
|
||||
.buddy-art {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
padding: 1.25rem;
|
||||
border: 1px solid var(--line);
|
||||
background: var(--background);
|
||||
}
|
||||
.buddy-art img {
|
||||
width: min(368px, 70vw);
|
||||
height: auto;
|
||||
image-rendering: pixelated;
|
||||
}
|
||||
|
||||
.partner-strip {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 1px;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
list-style: none;
|
||||
border: 1px solid var(--line);
|
||||
background: var(--line);
|
||||
}
|
||||
.partner-strip li { flex: 1 1 9rem; background: var(--background); }
|
||||
.partner-strip a {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 0.6rem;
|
||||
height: 100%;
|
||||
padding: 1.4rem 1rem;
|
||||
text-decoration: none;
|
||||
}
|
||||
.partner-strip img { max-width: 8.5rem; width: auto; object-fit: contain; }
|
||||
.partner-strip span {
|
||||
color: var(--faint);
|
||||
font-size: 0.68rem;
|
||||
letter-spacing: 0.08em;
|
||||
}
|
||||
.partner-strip a:hover span { color: var(--ink); }
|
||||
.logo-dark { display: none; }
|
||||
:global([data-theme='light']) .logo-dark { display: none !important; }
|
||||
:global([data-theme='light']) .logo-light { display: block; }
|
||||
:global(html:not([data-theme='light'])) .logo-dark { display: block; }
|
||||
:global(html:not([data-theme='light'])) .logo-light { display: none; }
|
||||
|
||||
.community-strip {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
justify-content: center;
|
||||
gap: 0.75rem 1.75rem;
|
||||
margin: 1.5rem 0 0;
|
||||
padding: 0;
|
||||
list-style: none;
|
||||
}
|
||||
.community-strip a {
|
||||
color: var(--muted);
|
||||
font-size: 0.78rem;
|
||||
text-decoration: none;
|
||||
}
|
||||
.community-strip a:hover { color: var(--accent); }
|
||||
</style>
|
||||
|
||||