From 61a3402bb9da2cdb6a4f019a87e6db49dde70335 Mon Sep 17 00:00:00 2001 From: jintekc Date: Fri, 17 Jul 2026 13:08:08 -0400 Subject: [PATCH] migrate site to Astro Starlight: islands for demos, astro-mermaid, Pagefind search --- .gitignore | 2 +- CLAUDE.md | 49 ++++---- astro.config.mjs | 55 ++++++++ docs/.vitepress/config.ts | 84 ------------- docs/.vitepress/shims.d.ts | 18 --- docs/.vitepress/theme/components/Mermaid.vue | 118 ------------------ docs/.vitepress/theme/custom.css | 49 -------- docs/.vitepress/theme/index.ts | 25 ---- docs/.vitepress/tsconfig.json | 25 ---- docs/index.md | 35 ------ package.json | 28 +++-- .../diagrams/okrs/o1-kr1.mmd | 0 .../diagrams/okrs/o1-kr2.1.mmd | 0 .../diagrams/okrs/o1-kr2.2.mmd | 0 .../diagrams/okrs/o1-kr2.3.mmd | 0 .../diagrams/okrs/o1-kr3.1.mmd | 0 .../diagrams/okrs/o1-kr3.2.mmd | 0 rpm/btcr2-dev.spec | 10 +- src/content.config.ts | 7 ++ docs/demo.md => src/content/docs/demo.mdx | 38 +++--- {docs => src/content/docs}/diagrams.md | 5 +- {docs => src/content/docs}/impls.md | 4 +- {docs => src/content/docs}/impls/java.md | 4 +- {docs => src/content/docs}/impls/py.md | 4 +- {docs => src/content/docs}/impls/rs.md | 4 +- .../ts.md => src/content/docs/impls/ts.mdx | 39 ++++-- src/content/docs/index.mdx | 47 +++++++ {docs => src/content/docs}/parity.md | 4 +- {docs => src/content/docs}/spec.md | 4 +- {docs => src}/examples/ts/create-external.ts | 0 {docs => src}/examples/ts/create-key.ts | 0 {docs => src}/examples/ts/deactivate.ts | 3 +- {docs => src}/examples/ts/resolve.ts | 0 {docs => src}/examples/ts/update.ts | 3 +- src/shims.d.ts | 5 + src/styles/custom.css | 40 ++++++ .../theme/components/CopyButton.vue | 0 .../theme/components/DemoCard.vue | 6 +- .../theme/composables/useCopyToClipboard.ts | 0 .../theme/composables/useDidBtcr2.ts | 0 .../.vitepress => src}/theme/demos/Create.vue | 0 .../theme/demos/Resolve.vue | 0 .../.vitepress => src}/theme/demos/Update.vue | 0 .../theme/demos/demo-fields.css | 0 {docs/.vitepress => src}/theme/demos/hex.ts | 0 tsconfig.json | 5 + 46 files changed, 278 insertions(+), 442 deletions(-) create mode 100644 astro.config.mjs delete mode 100644 docs/.vitepress/config.ts delete mode 100644 docs/.vitepress/shims.d.ts delete mode 100644 docs/.vitepress/theme/components/Mermaid.vue delete mode 100644 docs/.vitepress/theme/custom.css delete mode 100644 docs/.vitepress/theme/index.ts delete mode 100644 docs/.vitepress/tsconfig.json delete mode 100644 docs/index.md rename {docs/public => public}/diagrams/okrs/o1-kr1.mmd (100%) rename {docs/public => public}/diagrams/okrs/o1-kr2.1.mmd (100%) rename {docs/public => public}/diagrams/okrs/o1-kr2.2.mmd (100%) rename {docs/public => public}/diagrams/okrs/o1-kr2.3.mmd (100%) rename {docs/public => public}/diagrams/okrs/o1-kr3.1.mmd (100%) rename {docs/public => public}/diagrams/okrs/o1-kr3.2.mmd (100%) create mode 100644 src/content.config.ts rename docs/demo.md => src/content/docs/demo.mdx (56%) rename {docs => src/content/docs}/diagrams.md (99%) rename {docs => src/content/docs}/impls.md (95%) rename {docs => src/content/docs}/impls/java.md (98%) rename {docs => src/content/docs}/impls/py.md (97%) rename {docs => src/content/docs}/impls/rs.md (97%) rename docs/impls/ts.md => src/content/docs/impls/ts.mdx (73%) create mode 100644 src/content/docs/index.mdx rename {docs => src/content/docs}/parity.md (98%) rename {docs => src/content/docs}/spec.md (87%) rename {docs => src}/examples/ts/create-external.ts (100%) rename {docs => src}/examples/ts/create-key.ts (100%) rename {docs => src}/examples/ts/deactivate.ts (85%) rename {docs => src}/examples/ts/resolve.ts (100%) rename {docs => src}/examples/ts/update.ts (88%) create mode 100644 src/shims.d.ts create mode 100644 src/styles/custom.css rename {docs/.vitepress => src}/theme/components/CopyButton.vue (100%) rename {docs/.vitepress => src}/theme/components/DemoCard.vue (98%) rename {docs/.vitepress => src}/theme/composables/useCopyToClipboard.ts (100%) rename {docs/.vitepress => src}/theme/composables/useDidBtcr2.ts (100%) rename {docs/.vitepress => src}/theme/demos/Create.vue (100%) rename {docs/.vitepress => src}/theme/demos/Resolve.vue (100%) rename {docs/.vitepress => src}/theme/demos/Update.vue (100%) rename {docs/.vitepress => src}/theme/demos/demo-fields.css (100%) rename {docs/.vitepress => src}/theme/demos/hex.ts (100%) create mode 100644 tsconfig.json diff --git a/.gitignore b/.gitignore index 30f42d3..85e6c9e 100644 --- a/.gitignore +++ b/.gitignore @@ -50,5 +50,5 @@ release/ dist/ cache/ -### Astro (branch residue) ### +### Astro ### .astro/ diff --git a/CLAUDE.md b/CLAUDE.md index 7b75408..53a65f7 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -4,54 +4,49 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co ## Project -VitePress documentation site for the **did:btcr2** DID Method (Bitcoin Reference 2.0), a censorship-resistant DID method anchored to the Bitcoin blockchain. Deployed to `btcr2.dev`. +Astro Starlight documentation site for the **did:btcr2** DID Method (Bitcoin Reference 2.0), a censorship-resistant DID method anchored to the Bitcoin blockchain. Deployed to `btcr2.dev`. ## Commands Package manager: **pnpm 10.16.1** (declared in `packageManager`). Use `pnpm`, not `npm` or `yarn`. -- `pnpm dev`: VitePress dev server (`vitepress dev docs`) -- `pnpm build`: Production build (`vitepress build docs`, output goes to `docs/.vitepress/dist`) -- `pnpm typecheck`: `vue-tsc --noEmit` against `docs/.vitepress/tsconfig.json` -- `pnpm preview`: Serve build on port 8080 -- `pnpm serve`: `vitepress serve docs` -- `pnpm clean`: Wipe `node_modules`, lockfile, and VitePress cache/dist +- `pnpm dev`: Astro dev server +- `pnpm build`: Production build (output goes to `dist/`) +- `pnpm typecheck`: `astro check` (also validates the TS example snippets in `src/examples/`) +- `pnpm preview`: Serve the build locally +- `pnpm clean`: Wipe `node_modules`, lockfile, `.astro`, and `dist` -There is no test or lint script. Run `pnpm typecheck && pnpm build` before committing theme/demo changes. +Run `pnpm typecheck && pnpm build` before committing. No lockfile is committed (`.gitignore`d by design), so installs float on latest matching versions. ## Architecture -### Content lives in `docs/` -- Markdown pages: `docs/index.md` (home), `docs/spec.md`, `docs/demo.md`, `docs/diagrams.md`, `docs/impls.md`, `docs/parity.md`, plus per-language pages under `docs/impls/{java,py,rs,ts}.md`. -- TypeScript code snippets embedded in `docs/impls/ts.md` via `<<< @/examples/ts/*.ts` live in `docs/examples/ts/`. -- VitePress nav/sidebar/theme config: `docs/.vitepress/config.ts`. +### Content lives in `src/content/docs/` +- Pages: `index.mdx` (splash home), `spec.md`, `demo.mdx`, `diagrams.md`, `parity.md`, `impls.md`, and `impls/{java,py,rs}.md` + `impls/ts.mdx`. +- Starlight requires a `title` in every page's frontmatter; do not add an H1 in the body. +- `.mdx` pages import components explicitly; `.md` pages are plain markdown. MDX does NOT support `` autolinks; use `[text](url)`. +- TS example snippets live in `src/examples/ts/` and are embedded in `impls/ts.mdx` via `?raw` imports + Starlight's `` component. They are typechecked by `astro check`, so they must be self-contained. +- Nav/sidebar/theme config: `astro.config.mjs` (Starlight `sidebar`, `social`, `customCss`). -### Custom theme + Vue demo components -`docs/.vitepress/theme/index.ts` extends the default VitePress theme and globally registers: -- ``, ``, `` (also ``) from `theme/demos/`, used inline in `demo.md`. -- `` (shared demo shell) and `` from `theme/components/`. +### Interactive demos (Vue islands) +Vue 3 demo components live in `src/theme/` (`components/`, `demos/`, `composables/`) and are mounted in `demo.mdx` as islands with `client:only="vue"`; they never render during SSR. `composables/useDidBtcr2.ts` dynamically imports `@did-btcr2/api`, `@did-btcr2/keypair`, and `@did-btcr2/common` once per page and exposes `createApiForNetwork()`. The packages are pure JS (no WASM). Keep new `@did-btcr2/*` usage behind the composable. -`theme/composables/useDidBtcr2.ts` dynamically imports `@did-btcr2/api`, `@did-btcr2/keypair`, and `@did-btcr2/common` once per page and exposes `createApiForNetwork()`. The dynamic import keeps these packages out of VitePress SSR; the demos are strictly client-side. The packages are pure JS (no WASM), so no WASM/top-level-await Vite plugins are needed. Keep new `@did-btcr2/*` usage behind the composable. +The components still use `--vp-c-*` CSS variables from their VitePress origin; `src/styles/custom.css` aliases those to Starlight's `--sl-color-*` palette. Don't remove the alias block. ### Mermaid diagrams -A markdown-it fence override in `config.ts` turns ```` ```mermaid ```` blocks into ``; `Mermaid.vue` renders client-side in `onMounted` (theme-aware, re-renders on dark-mode toggle). There is no vitepress mermaid plugin; diagram sources live inline in the markdown pages. +```` ```mermaid ```` fences render client-side via the `astro-mermaid` integration (registered BEFORE `starlight` in `astro.config.mjs`; order matters). Theme switching is automatic. Diagram sources live inline in the markdown pages; standalone OKR sources are kept in `public/diagrams/okrs/`. ### Bitcoin REST endpoints (same-origin /mempool proxy is REQUIRED) -mempool.space networks (`bitcoin`, `testnet3`, `testnet4`, `signet`) are routed through the site's **same-origin `/mempool` path** via `createApiForNetwork()` in `useDidBtcr2.ts`: -- Dev: the Vite `server.proxy` block in `config.ts`. +mempool.space networks (`bitcoin`, `testnet3`, `testnet4`, `signet`) are routed through the site's **same-origin `/mempool` path** via `createApiForNetwork()` in `src/theme/composables/useDidBtcr2.ts`: +- Dev: the `vite.server.proxy` block in `astro.config.mjs`. - Prod: the VM's nginx `location /mempool/ { proxy_pass https://mempool.space/; }` block (added via helpdesk issue #25; not in this repo). Direct browser calls to mempool.space FAIL: `@did-btcr2/bitcoin`'s REST client sends `Content-Type: application/json` on GETs, making them non-simple requests, and mempool.space's OPTIONS handler 404s the resulting preflight. Do not "simplify" this back to direct calls unless the upstream client stops sending that header. `mutinynet.com` handles preflight correctly and stays direct; `regtest` uses the library's localhost default. There is no `fetch` monkey-patching and no env-var config; the `@did-btcr2` packages take explicit config objects only (`createApi({ btc: { network, rest, rpc, executor } })`). ### Deployment -btcr2.dev is served from a company VM with **no automation**. Release flow: bump `Version:` in `rpm/btcr2-dev.spec` (+ changelog) and `package.json`, push to the GitLab upstream (`gl1.dcdpr.com:website/btcr2-dev.git`), tag `vX.Y.Z`, then file an issue on the internal helpdesk GitLab; third-party IT clones the GitLab repo at the tag, builds an RPM (`rpmbuild -ta`, spec runs `npm install && npm run build` and installs `docs/.vitepress/dist/*` to `/var/www/btcr2-dev`), and installs it. nginx serves the site and must keep the `/mempool` proxy block. The GitHub Actions workflow in `.github/workflows/ci.yml` only verifies typecheck+build (weekly cron catches upstream `@did-btcr2` breakage, since no lockfile is committed); it does not deploy. - -### Vite resolve settings -`config.ts` sets `resolve.conditions: ['browser']` (so the `@did-btcr2/*` prebuilt browser bundles are picked up during SSR/build) and `resolve.dedupe: ['vue']`. Keep these when adding packages with Node-vs-browser conditional exports. +btcr2.dev is served from a company VM with **no automation**. Release flow: bump `Version:` in `rpm/btcr2-dev.spec` (+ changelog) and `package.json`, push to the GitLab upstream (`gl1.dcdpr.com:website/btcr2-dev.git`), tag `vX.Y.Z`, then file an issue on the internal helpdesk GitLab; third-party IT clones the GitLab repo at the tag, builds an RPM (`rpmbuild -ta`, spec runs `npm install && npm run build` and installs `dist/*` to `/var/www/btcr2-dev`), and installs it. nginx serves the site and must keep the `/mempool` proxy block. The GitHub Actions workflow in `.github/workflows/ci.yml` only verifies typecheck+build (weekly cron catches upstream `@did-btcr2` breakage, since no lockfile is committed); it does not deploy. ## Conventions - License: **MPL-2.0**. -- Markdown pages can embed the registered Vue components directly as tags; no per-page imports needed. -- Dependency versions track the `did-btcr2-js` monorepo (`@did-btcr2/api` / `keypair` / `common`); when bumping, re-run the demos against a test network. The api facade is pre-1.0 and moves fast. -- The spec itself is **not** in this repo. `docs/spec.md` only links to `https://dcdpr.github.io/did-btcr2` (source: `github.com/dcdpr/did-btcr2`). Don't try to edit spec content here. +- Dependency versions track the `did-btcr2-js` monorepo (`@did-btcr2/api` / `keypair` / `common`); the api facade is pre-1.0 and moves fast. When bumping, re-run the demos against a test network. +- The spec itself is **not** in this repo. `src/content/docs/spec.md` only links to `https://dcdpr.github.io/did-btcr2`. Don't try to edit spec content here. diff --git a/astro.config.mjs b/astro.config.mjs new file mode 100644 index 0000000..0d62b5a --- /dev/null +++ b/astro.config.mjs @@ -0,0 +1,55 @@ +import { defineConfig } from 'astro/config'; +import starlight from '@astrojs/starlight'; +import vue from '@astrojs/vue'; +import mermaid from 'astro-mermaid'; + +export default defineConfig({ + site: 'https://btcr2.dev', + vite: { + server: { + // Dev-server twin of the production nginx `location /mempool/` block + // (see rpm/ and CLAUDE.md). Same-origin proxying is REQUIRED for + // mempool.space: the @did-btcr2/bitcoin REST client sends + // `Content-Type: application/json` on GETs, which triggers a CORS + // preflight that mempool.space's OPTIONS handler rejects (404). + proxy: { + '/mempool': { + target: 'https://mempool.space', + changeOrigin: true, + rewrite: (path) => path.replace(/^\/mempool/, ''), + }, + }, + }, + }, + integrations: [ + // astro-mermaid must come before starlight so its remark plugin sees the + // ```mermaid fences first. + mermaid({ autoTheme: true }), + starlight({ + title: 'did:btcr2', + description: + 'A censorship-resistant DID Method using the Bitcoin blockchain as a Verifiable Data Registry to announce changes to the DID document.', + social: [ + { icon: 'github', label: 'GitHub', href: 'https://github.com/dcdpr/did-btcr2' }, + ], + sidebar: [ + { label: 'Specification', link: '/spec/' }, + { label: 'Diagrams', link: '/diagrams/' }, + { label: 'Demo', link: '/demo/' }, + { + label: 'Implementations', + items: [ + { label: 'Overview', link: '/impls/' }, + { label: 'Java', link: '/impls/java/' }, + { label: 'Python', link: '/impls/py/' }, + { label: 'Rust', link: '/impls/rs/' }, + { label: 'TypeScript', link: '/impls/ts/' }, + ], + }, + { label: 'Cross-impl Parity', link: '/parity/' }, + ], + customCss: ['./src/styles/custom.css'], + }), + vue(), + ], +}); diff --git a/docs/.vitepress/config.ts b/docs/.vitepress/config.ts deleted file mode 100644 index 9e00c5a..0000000 --- a/docs/.vitepress/config.ts +++ /dev/null @@ -1,84 +0,0 @@ -import { defineConfig } from 'vitepress' - -// Encode mermaid source so it can ride inside an attribute without breaking -// HTML parsing. We base64-encode (using a UTF-8-safe path) to sidestep quote / -// angle-bracket / newline collisions that the previous vitepress-plugin-mermaid -// SSR step paved over for us. -function encodeForAttr(source: string): string { - return Buffer.from(source, 'utf-8').toString('base64') -} - -export default defineConfig({ - title: 'did:btcr2', - description: 'A censorship-resistant DID Method using the Bitcoin blockchain as a Verifiable Data Registry to announce changes to the DID document.', - cleanUrls: true, - base: '/', - markdown: { - config(md) { - // Replace the default fenced-code renderer so ```mermaid blocks emit a - // component instead of a
. Falls through to the
-      // original renderer for every other language.
-      const defaultFence = md.renderer.rules.fence!
-      md.renderer.rules.fence = (tokens, idx, options, env, self) => {
-        const token = tokens[idx]
-        if (token.info.trim() === 'mermaid') {
-          return ``
-        }
-        return defaultFence(tokens, idx, options, env, self)
-      }
-    },
-  },
-  vite: {
-    server: {
-      // Dev-server twin of the production nginx `location /mempool/` block
-      // (see rpm/ and CLAUDE.md). Same-origin proxying is REQUIRED for
-      // mempool.space: the @did-btcr2/bitcoin REST client sends
-      // `Content-Type: application/json` on GETs, which triggers a CORS
-      // preflight that mempool.space's OPTIONS handler rejects (404).
-      proxy: {
-        '/mempool': {
-          target: 'https://mempool.space',
-          changeOrigin: true,
-          rewrite: (path) => path.replace(/^\/mempool/, ''),
-        },
-      },
-    },
-    resolve: {
-      conditions: ['browser'],
-      dedupe: ['vue'],
-    },
-  },
-  themeConfig: {
-    outline: { level: 'deep' },
-    externalLinkIcon: true,
-    search: { provider: 'local' },
-    nav: [
-      { text: 'Home', link: '/' },
-      { text: 'Specification', link: '/spec' },
-      { text: 'Diagrams', link: '/diagrams' },
-      { text: 'Demo', link: '/demo' },
-      { text: 'Implementations', link: '/impls' },
-      { text: 'Parity', link: '/parity' },
-    ],
-    footer: {
-      copyright: 'Copyright © 2025 Digital Contract Design',
-    },
-    sidebar: [
-      { text: 'Specification', link: '/spec' },
-      { text: 'Diagrams', link: '/diagrams' },
-      { text: 'Demo', link: '/demo' },
-      {
-        text: 'Implementations',
-        link: '/impls',
-        items: [
-          { text: 'Java', link: '/impls/java' },
-          { text: 'Python', link: '/impls/py' },
-          { text: 'Rust', link: '/impls/rs' },
-          { text: 'TypeScript', link: '/impls/ts' },
-        ],
-      },
-      { text: 'Cross-impl Parity', link: '/parity' },
-    ],
-    socialLinks: [{ icon: 'github', link: 'https://github.com/dcdpr/did-btcr2' }],
-  },
-})
diff --git a/docs/.vitepress/shims.d.ts b/docs/.vitepress/shims.d.ts
deleted file mode 100644
index d64d592..0000000
--- a/docs/.vitepress/shims.d.ts
+++ /dev/null
@@ -1,18 +0,0 @@
-declare module '*.css';
-declare module '*.vue' {
-  import type { DefineComponent } from 'vue';
-  const component: DefineComponent, Record, unknown>;
-  export default component;
-}
-
-interface ImportMetaEnv {
-  readonly DEV: boolean;
-  readonly PROD: boolean;
-  readonly MODE: string;
-  readonly BASE_URL: string;
-  readonly SSR: boolean;
-}
-
-interface ImportMeta {
-  readonly env: ImportMetaEnv;
-}
diff --git a/docs/.vitepress/theme/components/Mermaid.vue b/docs/.vitepress/theme/components/Mermaid.vue
deleted file mode 100644
index bbe62a8..0000000
--- a/docs/.vitepress/theme/components/Mermaid.vue
+++ /dev/null
@@ -1,118 +0,0 @@
-
-
-
-
-
diff --git a/docs/.vitepress/theme/custom.css b/docs/.vitepress/theme/custom.css
deleted file mode 100644
index 7553e76..0000000
--- a/docs/.vitepress/theme/custom.css
+++ /dev/null
@@ -1,49 +0,0 @@
-html {
-  color-scheme: light;
-}
-
-html[data-theme="dark"] {
-  color-scheme: dark;
-}
-
-select,
-input,
-textarea,
-button {
-  color-scheme: inherit;
-}
-
-select {
-  background-color: var(--vp-c-bg);
-  color: var(--vp-c-text-1);
-  border: 1px solid var(--vp-c-divider);
-  border-radius: 6px;
-  padding: 8px 10px;
-}
-
-option {
-  background-color: var(--vp-c-bg);
-  color: var(--vp-c-text-1);
-}
-
-:root {
-  --vp-c-brand-1: #38cee0;
-  --vp-c-brand-2: #123456;
-  --vp-c-alt: #32363f;
-  --vp-button-brand-bg: #123456;
-  --vp-button-alt-bg: #0d688a;
-}
-
-.heading .text {
-  max-width: 800px !important;
-}
-
-.vp-doc p {
-  line-height: 25px;
-}
-
-/* Mermaid: let mermaid pick its own theme; just constrain its SVG size. */
-.mermaid svg {
-  max-width: 100%;
-  height: auto;
-}
diff --git a/docs/.vitepress/theme/index.ts b/docs/.vitepress/theme/index.ts
deleted file mode 100644
index e927bf2..0000000
--- a/docs/.vitepress/theme/index.ts
+++ /dev/null
@@ -1,25 +0,0 @@
-import DefaultTheme from 'vitepress/theme';
-import type { Theme } from 'vitepress';
-import DemoCard from './components/DemoCard.vue';
-import Mermaid from './components/Mermaid.vue';
-import DemoCreate from './demos/Create.vue';
-import DemoResolve from './demos/Resolve.vue';
-import DemoUpdate from './demos/Update.vue';
-import './custom.css';
-
-// Bitcoin REST endpoints are called directly (mempool.space and
-// mutinynet.com both send Access-Control-Allow-Origin: *); see
-// composables/useDidBtcr2.ts. No proxies, no fetch patching.
-
-const theme: Theme = {
-  extends: DefaultTheme,
-  enhanceApp({ app }) {
-    app.component('DemoCard', DemoCard);
-    app.component('Mermaid', Mermaid);
-    app.component('DemoCreate', DemoCreate);
-    app.component('DemoResolve', DemoResolve);
-    app.component('DemoUpdate', DemoUpdate);
-  },
-};
-
-export default theme;
diff --git a/docs/.vitepress/tsconfig.json b/docs/.vitepress/tsconfig.json
deleted file mode 100644
index a587fd8..0000000
--- a/docs/.vitepress/tsconfig.json
+++ /dev/null
@@ -1,25 +0,0 @@
-{
-  "compilerOptions": {
-    "target": "ES2022",
-    "module": "ESNext",
-    "moduleResolution": "Bundler",
-    "lib": ["ES2022", "DOM", "DOM.Iterable"],
-    "types": ["node"],
-    "strict": true,
-    "noImplicitAny": true,
-    "noUnusedLocals": false,
-    "esModuleInterop": true,
-    "resolveJsonModule": true,
-    "isolatedModules": true,
-    "skipLibCheck": true,
-    "allowImportingTsExtensions": false,
-    "verbatimModuleSyntax": false,
-    "jsx": "preserve"
-  },
-  "include": [
-    "config.ts",
-    "shims.d.ts",
-    "theme/**/*.ts",
-    "theme/**/*.vue"
-  ]
-}
diff --git a/docs/index.md b/docs/index.md
deleted file mode 100644
index 5bed9b1..0000000
--- a/docs/index.md
+++ /dev/null
@@ -1,35 +0,0 @@
----
-layout: home
-hero:
-  name: did:btcr2
-  text: Bitcoin Reference 2.0 
-  tagline: >
-    A censorship-resistant DID Method using the Bitcoin blockchain as a Verifiable Data Registry to announce changes to the DID document.
-
-  actions:
-    - theme: brand
-      text: Getting Started
-      link: /impls
-    - theme: alt
-      text: Demo
-      link: /demo
-features:
-  - icon: 🛡️
-    title: Censorship Resistant
-    details: Anchored directly to the Bitcoin blockchain, did:btcr2 ensures DIDs cannot be blocked, erased, or overwritten, protecting identity operations from central or state-level interference.
-  - icon: 🕵️‍♂️
-    title: Private
-    details: Avoids reliance on public registries by enabling the secure sidecar delivery of DID Documents—transferred directly from controller to relying party—preserving privacy by default.
-  - icon: 🔏
-    title: Non-Repudiation with Time Anchoring
-    details: Every update is immutably timestamped using Bitcoin, preventing late publishing and enabling a cryptographic chain-of-custody suitable for high-stakes contracts.
-  - icon: 🔄
-    title: Pairwise Non-Correlation
-    details: Supports private, purpose-specific identifiers for each relationship or task, significantly reducing the risk of tracking or correlation across digital interactions.
-  - icon: ⚡
-    title: Efficient & Scalable Updates
-    details: DID creation is possible entirely off-chain at zero cost, while updates can be aggregated, making long-term maintenance on Bitcoin practical and economical.
-  - icon: 🔐
-    title: Closed-Loop Resolution
-    details: DID identifiers remain stable over time, even with frequent updates, making did:btcr2 suitable for lasting relationships, recurring interactions, and durable digital identities.
----
diff --git a/package.json b/package.json
index 50119b4..faf61fa 100644
--- a/package.json
+++ b/package.json
@@ -1,31 +1,33 @@
 {
   "name": "btcr2-dev",
-  "version": "1.1.0",
+  "version": "2.0.0",
   "description": "",
   "type": "module",
   "scripts": {
-    "dev": "vitepress dev docs",
-    "build": "vitepress build docs",
-    "serve": "vitepress serve docs",
-    "preview": "vitepress preview docs --port 8080",
-    "typecheck": "vue-tsc --noEmit -p docs/.vitepress/tsconfig.json",
-    "clean": "rimraf node_modules pnpm-lock.yaml docs/.vitepress/cache docs/.vitepress/dist"
+    "dev": "astro dev",
+    "build": "astro build",
+    "preview": "astro preview",
+    "typecheck": "astro check",
+    "clean": "rimraf node_modules pnpm-lock.yaml .astro dist"
   },
   "keywords": [],
   "license": "MPL-2.0",
   "packageManager": "pnpm@10.16.1",
   "devDependencies": {
-    "@types/node": "^24.5.0",
-    "mermaid": "^11.11.0",
+    "@astrojs/check": "^0.9.9",
     "rimraf": "^6.0.1",
-    "vitepress": "^1.6.4",
-    "vue": "^3.5.21",
-    "vue-tsc": "^2.1.10"
+    "typescript": "^5.9.0"
   },
   "dependencies": {
+    "@astrojs/starlight": "^0.41.3",
+    "@astrojs/vue": "^7.0.1",
     "@did-btcr2/api": "^0.17.0",
     "@did-btcr2/common": "^9.1.0",
     "@did-btcr2/keypair": "^0.13.1",
-    "highlight.js": "^11.11.1"
+    "astro": "^7.1.1",
+    "astro-mermaid": "^2.1.0",
+    "highlight.js": "^11.11.1",
+    "mermaid": "^11.11.0",
+    "vue": "^3.5.21"
   }
 }
diff --git a/docs/public/diagrams/okrs/o1-kr1.mmd b/public/diagrams/okrs/o1-kr1.mmd
similarity index 100%
rename from docs/public/diagrams/okrs/o1-kr1.mmd
rename to public/diagrams/okrs/o1-kr1.mmd
diff --git a/docs/public/diagrams/okrs/o1-kr2.1.mmd b/public/diagrams/okrs/o1-kr2.1.mmd
similarity index 100%
rename from docs/public/diagrams/okrs/o1-kr2.1.mmd
rename to public/diagrams/okrs/o1-kr2.1.mmd
diff --git a/docs/public/diagrams/okrs/o1-kr2.2.mmd b/public/diagrams/okrs/o1-kr2.2.mmd
similarity index 100%
rename from docs/public/diagrams/okrs/o1-kr2.2.mmd
rename to public/diagrams/okrs/o1-kr2.2.mmd
diff --git a/docs/public/diagrams/okrs/o1-kr2.3.mmd b/public/diagrams/okrs/o1-kr2.3.mmd
similarity index 100%
rename from docs/public/diagrams/okrs/o1-kr2.3.mmd
rename to public/diagrams/okrs/o1-kr2.3.mmd
diff --git a/docs/public/diagrams/okrs/o1-kr3.1.mmd b/public/diagrams/okrs/o1-kr3.1.mmd
similarity index 100%
rename from docs/public/diagrams/okrs/o1-kr3.1.mmd
rename to public/diagrams/okrs/o1-kr3.1.mmd
diff --git a/docs/public/diagrams/okrs/o1-kr3.2.mmd b/public/diagrams/okrs/o1-kr3.2.mmd
similarity index 100%
rename from docs/public/diagrams/okrs/o1-kr3.2.mmd
rename to public/diagrams/okrs/o1-kr3.2.mmd
diff --git a/rpm/btcr2-dev.spec b/rpm/btcr2-dev.spec
index 1c8ec98..19cce88 100644
--- a/rpm/btcr2-dev.spec
+++ b/rpm/btcr2-dev.spec
@@ -1,5 +1,5 @@
 Name:           btcr2-dev
-Version:        1.1.0
+Version:        2.0.0
 Release:        1%{?dist}
 Summary:        Static website
 
@@ -13,7 +13,7 @@ Requires:       nginx
 BuildArch:      noarch
 
 %description
-DCD's static website built with the 'vitepress' framework.
+DCD's static website built with the Astro 'starlight' framework.
 This package includes the built static files and nginx configuration.
 
 %prep
@@ -28,7 +28,7 @@ npm run build
 mkdir -p %{buildroot}/var/www/%{name}
 
 # install static files
-cp -r docs/.vitepress/dist/* %{buildroot}/var/www/%{name}/
+cp -r dist/* %{buildroot}/var/www/%{name}/
 
 %files
 %defattr(-,root,root,-)
@@ -54,6 +54,10 @@ if [ $1 -ge 1 ]; then
 fi
 
 %changelog
+* Fri Jul 17 2026 jintekc  - 2.0.0-1
+- Migrate the site to Astro Starlight (build output moves from
+  docs/.vitepress/dist to dist).
+
 * Fri Jul 17 2026 jintekc  - 1.1.0-1
 - Fix Resolve demo: route mempool.space through the site's same-origin
   /mempool nginx proxy via api config (no more global fetch patching).
diff --git a/src/content.config.ts b/src/content.config.ts
new file mode 100644
index 0000000..6a7b7a0
--- /dev/null
+++ b/src/content.config.ts
@@ -0,0 +1,7 @@
+import { defineCollection } from 'astro:content';
+import { docsLoader } from '@astrojs/starlight/loaders';
+import { docsSchema } from '@astrojs/starlight/schema';
+
+export const collections = {
+  docs: defineCollection({ loader: docsLoader(), schema: docsSchema() }),
+};
diff --git a/docs/demo.md b/src/content/docs/demo.mdx
similarity index 56%
rename from docs/demo.md
rename to src/content/docs/demo.mdx
index 94e6185..ac1ae8d 100644
--- a/docs/demo.md
+++ b/src/content/docs/demo.mdx
@@ -1,33 +1,41 @@
-# Demo
+---
+title: Demo
+---
+
+import DemoCreate from '../../theme/demos/Create.vue';
+import DemoResolve from '../../theme/demos/Resolve.vue';
+import DemoUpdate from '../../theme/demos/Update.vue';
 
 The DID Method specification covers four CRUD operations. The widgets on this page
 exercise the **TypeScript** reference implementation (`@did-btcr2/api`, backed by
 `@did-btcr2/method`, `@did-btcr2/keypair`, and `@did-btcr2/common`) directly in
 your browser via dynamic imports.
 
-* [Create](#create) — produce a new `did:btcr2` identifier from a public key or an intermediate DID document.
-* [Resolve](#resolve) — resolve an identifier using Bitcoin beacon signals and optional sidecar data.
-* [Update](#update) — apply a JSON Patch to the DID document and announce it on-chain.
-* [Deactivate](#deactivate) — special-case Update that adds `{"deactivated": true}` to the DID document.
+* [Create](#create): produce a new `did:btcr2` identifier from a public key or an intermediate DID document.
+* [Resolve](#resolve): resolve an identifier using Bitcoin beacon signals and optional sidecar data.
+* [Update](#update): apply a JSON Patch to the DID document and announce it on-chain.
+* [Deactivate](#deactivate): special-case Update that adds `{"deactivated": true}` to the DID document.
 
-> **Note** — these demos run client-side against the live Bitcoin network you select.
-> Use a test network (`regtest`, `signet`, `mutinynet`, `testnet3`, `testnet4`) for anything
-> that broadcasts a transaction, and never paste a real signing key.
+:::note
+These demos run client-side against the live Bitcoin network you select.
+Use a test network (`regtest`, `signet`, `mutinynet`, `testnet3`, `testnet4`) for anything
+that broadcasts a transaction, and never paste a real signing key.
+:::
 
 ## Create
 
-Creating a `did:btcr2` identifier is fully off-chain — no network round-trip is
+Creating a `did:btcr2` identifier is fully off-chain; no network round-trip is
 needed. The Create operation accepts either:
 
-* **`KEY` (deterministic)** — a compressed secp256k1 public key (33 bytes, SEC-encoded).
-* **`EXTERNAL`** — an [intermediate DID document](https://dcdpr.github.io/did-btcr2/#def-intermediate-did-document)
+* **`KEY` (deterministic)**: a compressed secp256k1 public key (33 bytes, SEC-encoded).
+* **`EXTERNAL`**: an [intermediate DID document](https://dcdpr.github.io/did-btcr2/#def-intermediate-did-document)
   with every identifier replaced by the placeholder
   `did:btcr2:xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx`.
   The identifier encodes the SHA-256 hash of the canonicalized document.
 
 Supported networks: `bitcoin`, `testnet3`, `testnet4`, `signet`, `mutinynet`, `regtest`.
 
-
+
 
 ## Resolve
 
@@ -37,7 +45,7 @@ the configured network so beacon signals can be fetched automatically. For
 `did:btcr2:x1…` identifiers you may also provide sidecar data containing the
 initial document, signed updates, CAS announcements and/or SMT proofs.
 
-
+
 
 ## Update
 
@@ -47,7 +55,7 @@ documents. `api.updateDid(...)` resolves the current state (unless you provide
 with the verification method and signing key you supply, and broadcasts via the
 chosen beacon. The response includes the signed update and the signal `txid`.
 
-
+
 
 ## Deactivate
 
@@ -55,4 +63,4 @@ Deactivation is an Update with the well-known patch
 `[{ "op": "add", "path": "/deactivated", "value": true }]`. The demo pre-fills
 that patch for you.
 
-
+
diff --git a/docs/diagrams.md b/src/content/docs/diagrams.md
similarity index 99%
rename from docs/diagrams.md
rename to src/content/docs/diagrams.md
index a836793..f76e7e6 100644
--- a/docs/diagrams.md
+++ b/src/content/docs/diagrams.md
@@ -1,10 +1,7 @@
 ---
-mermaidTheme: forest
-title: DID BTCR2 Diagrams
+title: Diagrams
 ---
 
-# Diagrams
-
 Below are UML diagrams of the various objects and algorithms in the specification.
 
 ## Architecture
diff --git a/docs/impls.md b/src/content/docs/impls.md
similarity index 95%
rename from docs/impls.md
rename to src/content/docs/impls.md
index 32fbfda..09aa9da 100644
--- a/docs/impls.md
+++ b/src/content/docs/impls.md
@@ -1,4 +1,6 @@
-# Implementations
+---
+title: Implementations
+---
 
 `did:btcr2` is implemented across four languages today. The TypeScript reference
 implementation is the most complete; Java is delivered as Universal
diff --git a/docs/impls/java.md b/src/content/docs/impls/java.md
similarity index 98%
rename from docs/impls/java.md
rename to src/content/docs/impls/java.md
index 89075d1..8e4eab3 100644
--- a/docs/impls/java.md
+++ b/src/content/docs/impls/java.md
@@ -1,4 +1,6 @@
-# Java
+---
+title: Java
+---
 
 The Java integration is delivered via the [DIF Universal Resolver](https://dev.uniresolver.io/)
 and [Universal Registrar](https://uniregistrar.io/) drivers maintained by Danube Tech.
diff --git a/docs/impls/py.md b/src/content/docs/impls/py.md
similarity index 97%
rename from docs/impls/py.md
rename to src/content/docs/impls/py.md
index 4448b57..def7f45 100644
--- a/docs/impls/py.md
+++ b/src/content/docs/impls/py.md
@@ -1,4 +1,6 @@
-# Python
+---
+title: Python
+---
 
 The Python reference implementation lives at
 [`dcdpr/did-btcr2-py`](https://github.com/dcdpr/did-btcr2-py).
diff --git a/docs/impls/rs.md b/src/content/docs/impls/rs.md
similarity index 97%
rename from docs/impls/rs.md
rename to src/content/docs/impls/rs.md
index 9028b0a..aa6b5fa 100644
--- a/docs/impls/rs.md
+++ b/src/content/docs/impls/rs.md
@@ -1,4 +1,6 @@
-# Rust
+---
+title: Rust
+---
 
 The Rust reference implementation lives at
 [`dcdpr/did-btcr2-rs`](https://github.com/dcdpr/did-btcr2-rs).
diff --git a/docs/impls/ts.md b/src/content/docs/impls/ts.mdx
similarity index 73%
rename from docs/impls/ts.md
rename to src/content/docs/impls/ts.mdx
index 024c8dd..8bcf59e 100644
--- a/docs/impls/ts.md
+++ b/src/content/docs/impls/ts.mdx
@@ -1,4 +1,13 @@
-# TypeScript
+---
+title: TypeScript
+---
+
+import { Tabs, TabItem, Code } from '@astrojs/starlight/components';
+import createKey from '../../../examples/ts/create-key.ts?raw';
+import createExternal from '../../../examples/ts/create-external.ts?raw';
+import resolveExample from '../../../examples/ts/resolve.ts?raw';
+import updateExample from '../../../examples/ts/update.ts?raw';
+import deactivateExample from '../../../examples/ts/deactivate.ts?raw';
 
 The TypeScript reference implementation lives in the
 [`did-btcr2-js` monorepo](https://github.com/dcdpr/did-btcr2-js). The packages
@@ -13,7 +22,9 @@ most relevant to consumers:
 | [`@did-btcr2/aggregation`](https://www.npmjs.com/package/@did-btcr2/aggregation) | Aggregated-beacon protocol with Nostr and HTTP/REST transports. |
 | [`@did-btcr2/cli`](https://www.npmjs.com/package/@did-btcr2/cli) | Command-line interface. |
 
-> **Status**: all packages are pre-1.0. APIs may change before stabilization.
+:::note[Status]
+All packages are pre-1.0. APIs may change before stabilization.
+:::
 
 ## Install
 
@@ -23,9 +34,7 @@ pnpm add @did-btcr2/api @did-btcr2/keypair @did-btcr2/common
 
 The packages are pure JavaScript (no WASM) and target both Node.js ≥ 22 and
 modern browsers. Each ships a prebuilt browser bundle selected via the
-`browser` condition in `exports`; in Vite set
-`resolve.conditions: ['browser']` if your build resolves with Node conditions
-(e.g. VitePress SSR).
+`browser` condition in `exports`.
 
 ## Quickstart
 
@@ -53,10 +62,14 @@ api.dispose();
 * **`external`**: encode the SHA-256 hash of the canonicalized intermediate
   DID document (`canonicalHashBytes` from `@did-btcr2/common`).
 
-::: code-group
-<<< @/examples/ts/create-key.ts [Deterministic (k1)]
-<<< @/examples/ts/create-external.ts [External (x1)]
-:::
+
+  
+    
+  
+  
+    
+  
+
 
 ## Resolve
 
@@ -64,7 +77,7 @@ Resolution drives the [`Resolver`](https://dcdpr.github.io/did-btcr2/operations/
 state machine. `api.resolveDid()` injects the configured Bitcoin connection so
 beacon signals are fetched for you.
 
-<<< @/examples/ts/resolve.ts
+
 
 ## Update
 
@@ -74,19 +87,19 @@ signer); the library signs the update with the verification method you
 nominate and broadcasts it through the chosen beacon. The result contains the
 signed update, the signal `txid`, and any per-beacon sidecar artifacts.
 
-<<< @/examples/ts/update.ts
+
 
 ## Deactivate
 
 Deactivation is an Update with the well-known patch
 `[{ op: 'add', path: '/deactivated', value: true }]`.
 
-<<< @/examples/ts/deactivate.ts
+
 
 ## Contributing
 
 To report bugs or request features, open an issue at
-.
+[github.com/dcdpr/did-btcr2-js/issues](https://github.com/dcdpr/did-btcr2-js/issues).
 
 Local development:
 
diff --git a/src/content/docs/index.mdx b/src/content/docs/index.mdx
new file mode 100644
index 0000000..56f4a92
--- /dev/null
+++ b/src/content/docs/index.mdx
@@ -0,0 +1,47 @@
+---
+title: did:btcr2
+description: A censorship-resistant DID Method using the Bitcoin blockchain as a Verifiable Data Registry to announce changes to the DID document.
+template: splash
+hero:
+  title: did:btcr2
+  tagline: 'Bitcoin Reference 2.0: a censorship-resistant DID Method using the Bitcoin blockchain as a Verifiable Data Registry to announce changes to the DID document.'
+  actions:
+    - text: Getting Started
+      link: /impls/
+      variant: primary
+    - text: Demo
+      link: /demo/
+      variant: minimal
+---
+
+import { Card, CardGrid } from '@astrojs/starlight/components';
+
+
+  
+    Anchored directly to the Bitcoin blockchain, did:btcr2 ensures DIDs cannot be blocked,
+    erased, or overwritten, protecting identity operations from central or state-level
+    interference.
+  
+  
+    Avoids reliance on public registries by enabling the secure sidecar delivery of DID
+    Documents (transferred directly from controller to relying party), preserving privacy
+    by default.
+  
+  
+    Every update is immutably timestamped using Bitcoin, preventing late publishing and
+    enabling a cryptographic chain-of-custody suitable for high-stakes contracts.
+  
+  
+    Supports private, purpose-specific identifiers for each relationship or task,
+    significantly reducing the risk of tracking or correlation across digital interactions.
+  
+  
+    DID creation is possible entirely off-chain at zero cost, while updates can be
+    aggregated, making long-term maintenance on Bitcoin practical and economical.
+  
+  
+    DID identifiers remain stable over time, even with frequent updates, making did:btcr2
+    suitable for lasting relationships, recurring interactions, and durable digital
+    identities.
+  
+
diff --git a/docs/parity.md b/src/content/docs/parity.md
similarity index 98%
rename from docs/parity.md
rename to src/content/docs/parity.md
index 468c05e..81b7603 100644
--- a/docs/parity.md
+++ b/src/content/docs/parity.md
@@ -1,4 +1,6 @@
-# Cross-impl Parity Matrix
+---
+title: Cross-impl Parity Matrix
+---
 
 A quick snapshot of which `did:btcr2` features each implementation supports
 today. Use it to pick an implementation that fits your stack.
diff --git a/docs/spec.md b/src/content/docs/spec.md
similarity index 87%
rename from docs/spec.md
rename to src/content/docs/spec.md
index 3c35df9..e00e938 100644
--- a/docs/spec.md
+++ b/src/content/docs/spec.md
@@ -1,4 +1,6 @@
-# Specification
+---
+title: Specification
+---
 
 To read the did:btcr2 DID Method specification, visit https://dcdpr.github.io/did-btcr2. 
 
diff --git a/docs/examples/ts/create-external.ts b/src/examples/ts/create-external.ts
similarity index 100%
rename from docs/examples/ts/create-external.ts
rename to src/examples/ts/create-external.ts
diff --git a/docs/examples/ts/create-key.ts b/src/examples/ts/create-key.ts
similarity index 100%
rename from docs/examples/ts/create-key.ts
rename to src/examples/ts/create-key.ts
diff --git a/docs/examples/ts/deactivate.ts b/src/examples/ts/deactivate.ts
similarity index 85%
rename from docs/examples/ts/deactivate.ts
rename to src/examples/ts/deactivate.ts
index 0658aae..54ecb58 100644
--- a/docs/examples/ts/deactivate.ts
+++ b/src/examples/ts/deactivate.ts
@@ -8,7 +8,8 @@ const api = createApi({ btc: { network: 'regtest' } });
 const did =
   'did:btcr2:k1qgpgwtp2dpe3thqny6jngl5eg6p4wghd04yj70jcp8qe4nh75hd4dhc8f08q4';
 
-const signer = new LocalSigner(secretKeyBytes); // 32-byte secp256k1 secret key
+const secretKeyBytes = new Uint8Array(32); // your 32-byte secp256k1 secret key
+const signer = new LocalSigner(secretKeyBytes);
 
 const result = await api.updateDid({
   did,
diff --git a/docs/examples/ts/resolve.ts b/src/examples/ts/resolve.ts
similarity index 100%
rename from docs/examples/ts/resolve.ts
rename to src/examples/ts/resolve.ts
diff --git a/docs/examples/ts/update.ts b/src/examples/ts/update.ts
similarity index 88%
rename from docs/examples/ts/update.ts
rename to src/examples/ts/update.ts
index afb89c2..92dfa62 100644
--- a/docs/examples/ts/update.ts
+++ b/src/examples/ts/update.ts
@@ -8,7 +8,8 @@ const api = createApi({ btc: { network: 'regtest' } });
 const did =
   'did:btcr2:k1qgpgwtp2dpe3thqny6jngl5eg6p4wghd04yj70jcp8qe4nh75hd4dhc8f08q4';
 
-const signer = new LocalSigner(secretKeyBytes); // 32-byte secp256k1 secret key
+const secretKeyBytes = new Uint8Array(32); // your 32-byte secp256k1 secret key
+const signer = new LocalSigner(secretKeyBytes);
 
 const result = await api.updateDid({
   did,
diff --git a/src/shims.d.ts b/src/shims.d.ts
new file mode 100644
index 0000000..40556b9
--- /dev/null
+++ b/src/shims.d.ts
@@ -0,0 +1,5 @@
+declare module '*.vue' {
+  import type { DefineComponent } from 'vue';
+  const component: DefineComponent, Record, unknown>;
+  export default component;
+}
diff --git a/src/styles/custom.css b/src/styles/custom.css
new file mode 100644
index 0000000..f73fdf5
--- /dev/null
+++ b/src/styles/custom.css
@@ -0,0 +1,40 @@
+/* Brand accent (btcr2 cyan) for Starlight's theme. */
+:root {
+  --sl-color-accent-low: #0d3a40;
+  --sl-color-accent: #38cee0;
+  --sl-color-accent-high: #a6e8f0;
+}
+:root[data-theme='light'] {
+  --sl-color-accent-low: #c9f0f5;
+  --sl-color-accent: #0d688a;
+  --sl-color-accent-high: #123456;
+}
+
+/* Aliases so the demo components (written against VitePress theme variables)
+   keep working unmodified under Starlight. */
+:root {
+  --vp-c-brand-1: var(--sl-color-accent);
+  --vp-c-text-1: var(--sl-color-text);
+  --vp-c-text-2: var(--sl-color-gray-3);
+  --vp-c-divider: var(--sl-color-hairline);
+  --vp-c-bg: var(--sl-color-bg);
+  --vp-c-alt: var(--sl-color-gray-5);
+}
+
+select,
+input,
+textarea,
+button {
+  color-scheme: inherit;
+}
+
+option {
+  background-color: var(--sl-color-bg);
+  color: var(--sl-color-text);
+}
+
+/* Mermaid: let mermaid pick its own theme; just constrain its SVG size. */
+.mermaid svg {
+  max-width: 100%;
+  height: auto;
+}
diff --git a/docs/.vitepress/theme/components/CopyButton.vue b/src/theme/components/CopyButton.vue
similarity index 100%
rename from docs/.vitepress/theme/components/CopyButton.vue
rename to src/theme/components/CopyButton.vue
diff --git a/docs/.vitepress/theme/components/DemoCard.vue b/src/theme/components/DemoCard.vue
similarity index 98%
rename from docs/.vitepress/theme/components/DemoCard.vue
rename to src/theme/components/DemoCard.vue
index a551770..c0e1dc9 100644
--- a/docs/.vitepress/theme/components/DemoCard.vue
+++ b/src/theme/components/DemoCard.vue
@@ -61,8 +61,7 @@ function replacer(_key: string, value: unknown): unknown {