diff --git a/.dockerignore b/.dockerignore
new file mode 100644
index 0000000..4411221
--- /dev/null
+++ b/.dockerignore
@@ -0,0 +1,13 @@
+node_modules
+**/node_modules
+**/dist
+**/storybook-static
+**/.storybook-static
+.git
+docs
+.tmp
+.bun-cache
+.pw-browsers
+.pw-libs
+.shots
+*.log
diff --git a/.forgejo/workflows/ci.yml b/.forgejo/workflows/ci.yml
new file mode 100644
index 0000000..42044cc
--- /dev/null
+++ b/.forgejo/workflows/ci.yml
@@ -0,0 +1,100 @@
+name: ci
+
+on:
+ push:
+ tags:
+ - 'v*'
+ pull_request:
+
+jobs:
+ build:
+ runs-on: docker
+ container: oven/bun:1
+ steps:
+ - name: Install git
+ run: apt-get update && apt-get install -y git nodejs
+
+ - name: Checkout
+ uses: actions/checkout@v4
+
+ - name: Install dependencies
+ run: bun install --frozen-lockfile
+
+ - name: Audit dependencies
+ run: bun audit
+
+ - name: Run unit tests
+ run: bun run test:unit run
+
+ - name: Type check
+ run: bun run type-check -- --force
+
+ - name: Build
+ run: bun run build-only
+
+ - name: Upload dist
+ uses: actions/upload-artifact@v4
+ with:
+ name: dist
+ path: packages/library/dist
+
+ publish:
+ needs: build
+ if: startsWith(github.ref, 'refs/tags/v')
+ runs-on: docker
+ container: oven/bun:1
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v4
+
+ - name: Install dependencies
+ run: bun install --frozen-lockfile
+
+ - name: Download dist
+ uses: actions/download-artifact@v4
+ with:
+ name: dist
+ path: packages/library/dist
+
+ - name: Set package version
+ run: bun pm version "${GITHUB_REF_NAME#v}" --no-git-tag-version
+ working-directory: packages/library
+
+ - name: Configure registry auth
+ run: echo "//git.mcpeakdev.com/api/packages/mcpeakdev/npm/:_authToken=${FORGEJO_TOKEN}" > .npmrc
+ working-directory: packages/library
+ env:
+ FORGEJO_TOKEN: ${{ secrets.FORGEJO_TOKEN }}
+
+ - name: Publish package
+ run: bun publish --registry https://git.mcpeakdev.com/api/packages/mcpeakdev/npm/
+ working-directory: packages/library
+
+ publish-docs:
+ needs: build
+ if: startsWith(github.ref, 'refs/tags/v')
+ runs-on: docker
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v4
+
+ - name: Log in to registry
+ uses: docker/login-action@v3
+ with:
+ registry: git.mcpeakdev.com
+ username: ${{ github.actor }}
+ password: ${{ secrets.FORGEJO_TOKEN }}
+
+ - name: Extract image tag
+ id: meta
+ run: echo "tag=${GITHUB_REF_NAME#v}" >> "$GITHUB_OUTPUT"
+
+ - name: Build and push docs image
+ uses: docker/build-push-action@v6
+ with:
+ context: .
+ file: packages/playground/Dockerfile
+ push: true
+ tags: |
+ git.mcpeakdev.com/mcpeakdev/nychthemeron-docs:${{ steps.meta.outputs.tag }}
+ git.mcpeakdev.com/mcpeakdev/nychthemeron-docs:latest
diff --git a/docs/superpowers/plans/2026-06-11-docs-site-and-docker.md b/docs/superpowers/plans/2026-06-11-docs-site-and-docker.md
new file mode 100644
index 0000000..b5a1360
--- /dev/null
+++ b/docs/superpowers/plans/2026-06-11-docs-site-and-docker.md
@@ -0,0 +1,222 @@
+# Docs Site Docker Image + Install Page Implementation Plan
+
+> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
+
+**Goal:** Add an Installation page to the Storybook playground, package the playground's static build as a `nychthemeron-docs` Docker image served by nginx, and wire up CI to build/push that image on version tags.
+
+**Architecture:** A new MDX docs page (`packages/playground/stories/Install.mdx`) documents installing `@nychthemeron/library` via npm/pnpm/yarn/bun from the private Forgejo npm registry. A new multi-stage `packages/playground/Dockerfile` builds the library and Storybook static output with `oven/bun:1`, then copies `storybook-static` into `nginx:alpine`. A root `.dockerignore` keeps the build context lean. A new `publish-docs` job in `.forgejo/workflows/ci.yml`, parallel to `publish`, builds and pushes the image on `v*` tags.
+
+**Tech Stack:** Storybook 10 (`@storybook/addon-docs`), Vue 3, Bun, Docker (multi-stage, nginx:alpine), Forgejo Actions.
+
+---
+
+### Task 1: Add Installation MDX page
+
+**Files:**
+- Create: `packages/playground/stories/Install.mdx`
+
+- [ ] **Step 1: Create the MDX file**
+
+```mdx
+import { Meta } from '@storybook/addon-docs/blocks'
+
+
+
+# Installation
+
+`@nychthemeron/library` is published to a private npm registry hosted on
+Forgejo. Configure your package manager for the `@nychthemeron` scope, then
+install the package.
+
+## Configure the registry
+
+Add the following to your project's `.npmrc`:
+
+```
+@nychthemeron:registry=https://git.mcpeakdev.com/api/packages/mcpeakdev/npm/
+```
+
+## npm
+
+```bash
+npm install @nychthemeron/library
+```
+
+## pnpm
+
+```bash
+pnpm add @nychthemeron/library
+```
+
+## yarn
+
+```bash
+yarn add @nychthemeron/library
+```
+
+## bun
+
+```bash
+bun add @nychthemeron/library
+```
+```
+
+- [ ] **Step 2: Build Storybook and verify the page is included**
+
+Run: `cd packages/playground && bun run build`
+
+Expected: Build succeeds with output ending in something like
+`Output directory: storybook-static`. Then run:
+
+`grep -o '"title":"Get Started/Installation"' packages/playground/storybook-static/index.json`
+
+Expected: prints `"title":"Get Started/Installation"`.
+
+- [ ] **Step 3: Commit**
+
+```bash
+git add packages/playground/stories/Install.mdx
+git commit -m "Add installation docs page to playground"
+```
+
+---
+
+### Task 2: Add root `.dockerignore`
+
+**Files:**
+- Create: `.dockerignore`
+
+- [ ] **Step 1: Create `.dockerignore`**
+
+```
+node_modules
+**/node_modules
+**/dist
+**/storybook-static
+**/.storybook-static
+.git
+docs
+.tmp
+.bun-cache
+.pw-browsers
+.pw-libs
+.shots
+*.log
+```
+
+- [ ] **Step 2: Commit**
+
+```bash
+git add .dockerignore
+git commit -m "Add .dockerignore for docs Docker build"
+```
+
+---
+
+### Task 3: Add Dockerfile for the docs site
+
+**Files:**
+- Create: `packages/playground/Dockerfile`
+
+- [ ] **Step 1: Create the Dockerfile**
+
+```dockerfile
+# syntax=docker/dockerfile:1
+
+FROM oven/bun:1 AS build
+WORKDIR /app
+COPY . .
+RUN bun install --frozen-lockfile
+RUN cd packages/library && bun run build
+RUN cd packages/playground && bun run build
+
+FROM nginx:alpine AS runtime
+COPY --from=build /app/packages/playground/storybook-static /usr/share/nginx/html
+EXPOSE 80
+```
+
+- [ ] **Step 2: Build and run the image locally**
+
+Run (from repo root):
+
+```bash
+docker build -t nychthemeron-docs:test -f packages/playground/Dockerfile .
+docker run -d --rm --name nychthemeron-docs-test -p 8080:80 nychthemeron-docs:test
+sleep 2
+curl -sf http://localhost:8080/ | grep -i storybook
+docker stop nychthemeron-docs-test
+```
+
+Expected: `docker build` completes successfully, the container starts, and
+the `curl` output contains a reference to `storybook` (from
+`storybook-static/index.html`).
+
+> If the Docker CLI/daemon isn't available in the current environment (e.g.
+> sandboxed dev container), skip running this step here and ask the user to
+> run it locally before merging.
+
+- [ ] **Step 3: Commit**
+
+```bash
+git add packages/playground/Dockerfile
+git commit -m "Add Dockerfile for nychthemeron-docs static site"
+```
+
+---
+
+### Task 4: Add `publish-docs` CI job
+
+**Files:**
+- Modify: `.forgejo/workflows/ci.yml`
+
+- [ ] **Step 1: Add the `publish-docs` job**
+
+Add this job after the existing `publish` job (same indentation level,
+i.e. as a sibling under `jobs:`):
+
+```yaml
+ publish-docs:
+ needs: build
+ if: startsWith(github.ref, 'refs/tags/v')
+ runs-on: docker
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v4
+
+ - name: Log in to registry
+ uses: docker/login-action@v3
+ with:
+ registry: git.mcpeakdev.com
+ username: ${{ github.actor }}
+ password: ${{ secrets.FORGEJO_TOKEN }}
+
+ - name: Extract image tag
+ id: meta
+ run: echo "tag=${GITHUB_REF_NAME#v}" >> "$GITHUB_OUTPUT"
+
+ - name: Build and push docs image
+ uses: docker/build-push-action@v6
+ with:
+ context: .
+ file: packages/playground/Dockerfile
+ push: true
+ tags: |
+ git.mcpeakdev.com/mcpeakdev/nychthemeron-docs:${{ steps.meta.outputs.tag }}
+ git.mcpeakdev.com/mcpeakdev/nychthemeron-docs:latest
+```
+
+- [ ] **Step 2: Verify YAML structure**
+
+Run: `bun run --eval "console.log('ok')"` is not a YAML check — instead,
+visually confirm in the file that `publish-docs:` is indented exactly like
+`publish:` (4 spaces under `jobs:`), and that all of its child keys
+(`needs`, `if`, `runs-on`, `steps`, and each `- name:` step) match the
+indentation pattern used by the `publish` job. Confirm there are exactly
+three top-level jobs: `build`, `publish`, `publish-docs`.
+
+- [ ] **Step 3: Commit**
+
+```bash
+git add .forgejo/workflows/ci.yml
+git commit -m "Add publish-docs job to build and push nychthemeron-docs image"
+```
diff --git a/docs/superpowers/specs/2026-06-11-docs-site-and-docker-design.md b/docs/superpowers/specs/2026-06-11-docs-site-and-docker-design.md
new file mode 100644
index 0000000..47fadbc
--- /dev/null
+++ b/docs/superpowers/specs/2026-06-11-docs-site-and-docker-design.md
@@ -0,0 +1,55 @@
+# Docs site (playground) Docker image + install page
+
+## Goal
+
+Publish the Storybook playground as a static "docs" site, package it as a
+Docker image (`nychthemeron-docs`), and add an Installation page documenting
+how to install `@nychthemeron/library` from the private Forgejo npm registry
+via npm, pnpm, yarn, and bun.
+
+## Install page
+
+- New file: `packages/playground/stories/Install.mdx`
+- Storybook addon-docs MDX page (no story export), `Meta` title
+ `Get Started/Installation` so it sorts near the top of the sidebar.
+- Content:
+ - Heading + short description of the library.
+ - Registry config snippet (`.npmrc`) showing how to point the scoped
+ `@nychthemeron` registry at `https://git.mcpeakdev.com/api/packages/mcpeakdev/npm/`.
+ - Four code blocks showing install commands for `@nychthemeron/library`:
+ npm, pnpm, yarn, bun.
+
+## Docker image (`nychthemeron-docs`)
+
+- New file: `packages/playground/Dockerfile`, multi-stage build:
+ - **Stage 1 (`oven/bun:1`)**: copy the workspace, `bun install --frozen-lockfile`,
+ build `@nychthemeron/library` (`cd packages/library && bun run build`),
+ then build Storybook (`cd packages/playground && bun run build` →
+ `packages/playground/storybook-static`).
+ - **Stage 2 (`nginx:alpine`)**: copy `storybook-static` into
+ `/usr/share/nginx/html`, default nginx config, `EXPOSE 80`.
+- Build context is the repo root (so the workspace lockfile and all
+ packages are available to stage 1).
+
+## CI changes
+
+In `.forgejo/workflows/ci.yml`, add a new `publish-docs` job:
+
+- Parallel to the existing `publish` job (both depend on `build`).
+- Gated on `startsWith(github.ref, 'refs/tags/v')`.
+- Steps:
+ 1. Checkout
+ 2. Log in to `git.mcpeakdev.com` using `docker/login-action` with
+ `secrets.FORGEJO_TOKEN` (same credential as the library publish job).
+ 3. Extract version from tag (`${GITHUB_REF_NAME#v}`).
+ 4. `docker/build-push-action` with context `.`, dockerfile
+ `packages/playground/Dockerfile`, push `true`, tags:
+ - `git.mcpeakdev.com/mcpeakdev/nychthemeron-docs:`
+ - `git.mcpeakdev.com/mcpeakdev/nychthemeron-docs:latest`
+
+## Out of scope
+
+- No changes to the existing `build` or `publish` (npm) jobs beyond what's
+ already in place.
+- No SPA routing / custom nginx config — Storybook's static output is
+ served as-is.
diff --git a/packages/playground/.storybook/main.ts b/packages/playground/.storybook/main.ts
index 25482a1..a5cb9fe 100644
--- a/packages/playground/.storybook/main.ts
+++ b/packages/playground/.storybook/main.ts
@@ -3,7 +3,7 @@ import { dirname } from 'node:path'
import type { StorybookConfig } from '@storybook/vue3-vite'
const config: StorybookConfig = {
- stories: ['../stories/**/*.stories.ts'],
+ stories: ['../stories/**/*.stories.ts', '../stories/**/*.mdx'],
addons: [getAbsolutePath('@storybook/addon-links'), getAbsolutePath('@storybook/addon-docs')],
framework: {
name: getAbsolutePath('@storybook/vue3-vite'),
diff --git a/packages/playground/Dockerfile b/packages/playground/Dockerfile
new file mode 100644
index 0000000..01ad6bd
--- /dev/null
+++ b/packages/playground/Dockerfile
@@ -0,0 +1,12 @@
+# syntax=docker/dockerfile:1
+
+FROM oven/bun:1 AS build
+WORKDIR /app
+COPY . .
+RUN bun install --frozen-lockfile
+RUN cd packages/library && bun run build
+RUN cd packages/playground && bun run build
+
+FROM nginx:alpine AS runtime
+COPY --from=build /app/packages/playground/storybook-static /usr/share/nginx/html
+EXPOSE 80
diff --git a/packages/playground/stories/Install.mdx b/packages/playground/stories/Install.mdx
new file mode 100644
index 0000000..dcdd3cd
--- /dev/null
+++ b/packages/playground/stories/Install.mdx
@@ -0,0 +1,41 @@
+import { Meta } from '@storybook/addon-docs/blocks'
+
+
+
+# Installation
+
+`@nychthemeron/library` is published to a private npm registry hosted on
+Forgejo. Configure your package manager for the `@nychthemeron` scope, then
+install the package.
+
+## Configure the registry
+
+Add the following to your project's `.npmrc`:
+
+```
+@nychthemeron:registry=https://git.mcpeakdev.com/api/packages/mcpeakdev/npm/
+```
+
+## npm
+
+```bash
+npm install @nychthemeron/library
+```
+
+## pnpm
+
+```bash
+pnpm add @nychthemeron/library
+```
+
+## yarn
+
+```bash
+yarn add @nychthemeron/library
+```
+
+## bun
+
+```bash
+bun add @nychthemeron/library
+```