diff --git a/.github/workflows/wiki.yml b/.github/workflows/wiki.yml
new file mode 100644
index 00000000..0d32b0b8
--- /dev/null
+++ b/.github/workflows/wiki.yml
@@ -0,0 +1,49 @@
+name: Wiki
+
+on:
+ push:
+ branches: [main]
+ paths:
+ - "wiki/**"
+ - ".github/workflows/wiki.yml"
+ pull_request:
+ branches: [main]
+ paths:
+ - "wiki/**"
+ - ".github/workflows/wiki.yml"
+
+jobs:
+ validate:
+ runs-on: ubuntu-latest
+ timeout-minutes: 5
+
+ steps:
+ - uses: actions/checkout@v4
+
+ - uses: actions/setup-node@v4
+ with:
+ node-version: "20"
+
+ - uses: actions/setup-python@v5
+ with:
+ python-version: "3.11"
+
+ - name: Check JavaScript syntax
+ run: |
+ node --check wiki/main.js
+ node --check wiki/theme.js
+ node --check wiki/docs/content.js
+ node --check wiki/docs/main.js
+
+ - name: Check locale JSON
+ run: python -m json.tool wiki/locales/en.json >/dev/null
+
+ - name: Check static site files
+ run: |
+ test -f wiki/index.html
+ test -f wiki/home/index.html
+ test -f wiki/docs/index.html
+ test -f wiki/_redirects
+ test -f wiki/_headers
+ test -f wiki/assets/icon.png
+ ! grep -R "localhost\\|nanobot" wiki --exclude=README.md
diff --git a/.gitignore b/.gitignore
index 7341d93f..0573517b 100644
--- a/.gitignore
+++ b/.gitignore
@@ -21,6 +21,12 @@ agent/tests/*_result.json
.ruff_cache/
frontend/dist/
frontend/tsconfig.tsbuildinfo
+wiki/node_modules/
+wiki/dist/
+wiki/.astro/
+wiki/.vite/
+wiki/.wrangler/
+wiki/.cache/
.vibe-dev/
.vibe-record/
.cache/
@@ -35,6 +41,8 @@ data/backup/
# Internal docs (plans, specs)
docs/
+!wiki/docs/
+!wiki/docs/**
data/minutes/
data/overnight*.log
data/*.log
diff --git a/wiki/README.md b/wiki/README.md
new file mode 100644
index 00000000..2d16e3ed
--- /dev/null
+++ b/wiki/README.md
@@ -0,0 +1,23 @@
+# Vibe-Trading Wiki
+
+Static source for `https://vibetrading.wiki`.
+
+## Local preview
+
+```bash
+cd wiki
+python3 -m http.server 8088
+```
+
+Open `http://localhost:8088/home/` for the landing page and `http://localhost:8088/docs/` for the docs app.
+
+Direct docs URLs such as `/docs/latest/getting-started/vibe-trading-overview` are handled by Cloudflare Pages via `_redirects`. The simple Python preview server does not apply those rewrite rules, so use `/docs/` as the local entry point.
+
+## Cloudflare Pages
+
+- Project root: `wiki`
+- Build command: leave empty
+- Output directory: `.`
+- Custom domain: `vibetrading.wiki`
+
+The site is intentionally static. No server, database, or build step is required.
diff --git a/wiki/_headers b/wiki/_headers
new file mode 100644
index 00000000..89d13fce
--- /dev/null
+++ b/wiki/_headers
@@ -0,0 +1,14 @@
+/*
+ X-Frame-Options: SAMEORIGIN
+ X-Content-Type-Options: nosniff
+ Referrer-Policy: strict-origin-when-cross-origin
+ Permissions-Policy: camera=(), microphone=(), geolocation=()
+
+/assets/*
+ Cache-Control: public, max-age=31536000, immutable
+
+/*.js
+ Cache-Control: public, max-age=3600
+
+/*.css
+ Cache-Control: public, max-age=3600
diff --git a/wiki/_redirects b/wiki/_redirects
new file mode 100644
index 00000000..69d61bbf
--- /dev/null
+++ b/wiki/_redirects
@@ -0,0 +1,5 @@
+/ /home/ 302
+/docs /docs/latest/getting-started/vibe-trading-overview 302
+/docs/ /docs/latest/getting-started/vibe-trading-overview 302
+/docs/latest/* /docs/index.html 200
+/docs/0.1.7/* /docs/index.html 200
diff --git a/wiki/assets/feature-cross-market-data-backtesting.png b/wiki/assets/feature-cross-market-data-backtesting.png
new file mode 100644
index 00000000..c72dcb20
Binary files /dev/null and b/wiki/assets/feature-cross-market-data-backtesting.png differ
diff --git a/wiki/assets/feature-multi-agent-trading-teams.png b/wiki/assets/feature-multi-agent-trading-teams.png
new file mode 100644
index 00000000..0fb578ad
Binary files /dev/null and b/wiki/assets/feature-multi-agent-trading-teams.png differ
diff --git a/wiki/assets/feature-self-improving-trading-agent.png b/wiki/assets/feature-self-improving-trading-agent.png
new file mode 100644
index 00000000..dae0c115
Binary files /dev/null and b/wiki/assets/feature-self-improving-trading-agent.png differ
diff --git a/wiki/assets/feature-shadow-account.png b/wiki/assets/feature-shadow-account.png
new file mode 100644
index 00000000..077353cb
Binary files /dev/null and b/wiki/assets/feature-shadow-account.png differ
diff --git a/wiki/assets/icon.png b/wiki/assets/icon.png
new file mode 100644
index 00000000..aad9f9a8
Binary files /dev/null and b/wiki/assets/icon.png differ
diff --git a/wiki/docs/content.js b/wiki/docs/content.js
new file mode 100644
index 00000000..473bf58b
--- /dev/null
+++ b/wiki/docs/content.js
@@ -0,0 +1,396 @@
+export const DOCS_DEFAULT_VERSION = "0.1.7";
+export const DOCS_LATEST_ALIAS = "latest";
+export const DOCS_DEFAULT_PAGE = "getting-started/vibe-trading-overview";
+
+export const DOCS_VERSIONS = [
+ { name: "0.1.7", label: "0.1.7 (latest)" }
+];
+
+export const DOCS_STRUCTURE = [
+ {
+ id: "getting-started",
+ label: "Getting started",
+ pages: [
+ {
+ id: "getting-started/vibe-trading-overview",
+ title: "Vibe-Trading overview",
+ description: "What Vibe-Trading is, where it fits, and what boundary it keeps.",
+ lead: "Vibe-Trading is an open-source finance research workspace that turns natural-language prompts into market-data pulls, backtests, reports, and reusable research context.",
+ sections: [
+ {
+ id: "what-it-is",
+ title: "What it is",
+ body: `
+
Vibe-Trading connects an agent loop to finance tools: data loaders, strategy generation, backtest engines, document readers, trade-journal analysis, persistent memory, and multi-agent research teams.
+ The goal is not to replace judgment. The goal is to make every research step runnable, inspectable, and easy to repeat.
+ `
+ },
+ {
+ id: "research-only",
+ title: "Research only",
+ body: `
+ Vibe-Trading does not execute live trades. It is designed for simulation, backtesting, audit trails, and research workflows. Outputs are not investment advice.
+ `
+ },
+ {
+ id: "capabilities",
+ title: "Core capabilities",
+ body: `
+
+ Natural-language CLI and web workflows.
+ Seven backtest engines across equities, crypto, futures, forex, composites, and options portfolios.
+ Market data routing across Tushare, OKX, yfinance, AKShare, CCXT, and Futu.
+ Trade Journal and Shadow Account workflows for behavior diagnostics.
+ Swarm presets for committee-style research reviews.
+ MCP tools for Claude Desktop, OpenClaw, Cursor, and other MCP clients.
+
+ `
+ }
+ ]
+ },
+ {
+ id: "getting-started/quick-start",
+ title: "Quick start",
+ description: "Install Vibe-Trading, initialize configuration, and run the first research task.",
+ lead: "The fastest path is PyPI install, interactive setup, then either CLI research or the local web UI.",
+ sections: [
+ {
+ id: "install",
+ title: "Install",
+ body: `
+ pip install vibe-trading-ai
+vibe-trading init
+vibe-trading
+ `
+ },
+ {
+ id: "first-run",
+ title: "First run",
+ body: `
+ vibe-trading run -p "Backtest a BTC-USDT 20/50 moving-average strategy for 2024, summarize return and drawdown, then export the report"
+ `
+ },
+ {
+ id: "web-ui",
+ title: "Open the web UI",
+ body: `
+ vibe-trading serve --port 8899
+ The local web UI is useful when you want uploaded files, streaming swarm progress, Settings, and generated artifacts in one place.
+ `
+ }
+ ]
+ },
+ {
+ id: "getting-started/configuration",
+ title: "Configuration",
+ description: "Provider, model, market-data, and deployment settings.",
+ lead: "Vibe-Trading keeps secrets and deployment-specific choices in environment variables or local Settings, not in source files.",
+ sections: [
+ {
+ id: "env",
+ title: "Environment file",
+ body: `
+ LANGCHAIN_PROVIDER=deepseek
+LANGCHAIN_MODEL_NAME=deepseek-v4-pro
+TUSHARE_TOKEN=your-token
+TIMEOUT_SECONDS=2400
+ Run vibe-trading init to bootstrap the local configuration interactively.
+ `
+ },
+ {
+ id: "keys",
+ title: "Keys and data sources",
+ body: `
+ Many HK, US, crypto, document, journal, and static analysis workflows work without paid market-data keys. A-share fundamental enrichment and some provider-specific workflows need their matching credentials.
+ For non-local API or web deployments, configure API_AUTH_KEY and send requests with Authorization: Bearer <key>.
+ `
+ },
+ {
+ id: "models",
+ title: "Model choice",
+ body: `
+ Agent quality depends on tool use. Prefer strong tool-calling models for long research runs, swarms, and multi-step backtests. Avoid small distilled models for workflows where fabricated answers are costly.
+ `
+ }
+ ]
+ }
+ ]
+ },
+ {
+ id: "core-concepts",
+ label: "Core concepts",
+ pages: [
+ {
+ id: "core-concepts/research-workflow",
+ title: "Research workflow",
+ description: "How a Vibe-Trading run moves from prompt to evidence.",
+ lead: "A good run routes the request, grounds it in data, executes tools, validates outputs, and leaves artifacts behind.",
+ sections: [
+ {
+ id: "pipeline",
+ title: "Pipeline",
+ body: `
+
+ Plan: choose relevant skills, tools, data sources, and swarm presets.
+ Ground: fetch market bars, documents, URLs, broker journals, or local files at runtime.
+ Execute: run backtests, factor analysis, options checks, exports, or report generation.
+ Validate: attach metrics, benchmark comparison, Monte Carlo, Bootstrap, Walk-Forward, warnings, and run cards when applicable.
+ Deliver: return the answer plus inspectable artifacts.
+
+ `
+ },
+ {
+ id: "artifacts",
+ title: "Artifacts",
+ body: `
+ Backtests and research runs can produce reports, charts, generated strategy files, run metadata, and reusable context. The artifact trail matters because finance research often needs later inspection.
+ `
+ }
+ ]
+ },
+ {
+ id: "core-concepts/backtesting",
+ title: "Backtesting",
+ description: "Market coverage, engines, metrics, and validation tools.",
+ lead: "Vibe-Trading backtests daily and minute strategies across multiple asset classes, then keeps outputs auditable with metrics and run cards.",
+ sections: [
+ {
+ id: "engines",
+ title: "Engines",
+ body: `
+
+ China A-share, global equity, crypto, China futures, global futures, forex, and composite engines.
+ Options portfolio engine for option strategy research.
+ Minute intervals including 1m, 5m, 15m, 30m, 1H, 4H, and 1D where supported by the data source.
+
+ `
+ },
+ {
+ id: "validation",
+ title: "Validation",
+ body: `
+ Research runs can include benchmark comparison, Monte Carlo, Bootstrap confidence intervals, Walk-Forward validation, and run cards. Treat these as evidence helpers, not guarantees.
+ `
+ },
+ {
+ id: "example",
+ title: "Example",
+ body: `
+ vibe-trading run -p "Backtest an equal-weight SPY and BTC-USDT momentum rotation strategy for 2024 with benchmark comparison"
+ `
+ }
+ ]
+ },
+ {
+ id: "core-concepts/swarm-teams",
+ title: "Swarm teams",
+ description: "Preset research teams for committee-style analysis.",
+ lead: "Swarm presets turn a research question into a small DAG of specialist workers, then stream progress and persist the final report.",
+ sections: [
+ {
+ id: "presets",
+ title: "Presets",
+ body: `
+ Vibe-Trading includes 29 presets such as investment committee, quant strategy desk, crypto trading desk, macro rates and FX desk, and risk committee.
+ vibe-trading --swarm-presets
+vibe-trading --swarm-run investment_committee '{"topic":"BTC outlook"}'
+ `
+ },
+ {
+ id: "keys",
+ title: "Model requirements",
+ body: `
+ Most MCP tools work without an LLM key after install. run_swarm needs an LLM provider because it spawns internal worker agents.
+ `
+ }
+ ]
+ }
+ ]
+ },
+ {
+ id: "tools",
+ label: "Tools",
+ pages: [
+ {
+ id: "tools/data-sources",
+ title: "Data sources",
+ description: "How Vibe-Trading routes symbols and market data providers.",
+ lead: "Data routing is provider-aware: mixed symbols can use source=\"auto\" while each market keeps its own data rules.",
+ sections: [
+ {
+ id: "providers",
+ title: "Providers",
+ body: `
+
+ Tushare for China market and fundamental workflows when configured.
+ OKX and CCXT for crypto symbols such as BTC-USDT.
+ yfinance for global equities and common benchmarks.
+ AKShare and Futu for additional China, Hong Kong, and market-specific coverage.
+
+ `
+ },
+ {
+ id: "symbols",
+ title: "Symbol conventions",
+ body: `
+ Crypto pairs use uppercase hyphen format, for example BTC-USDT. Mixed-market research should prefer automatic source routing where possible.
+ `
+ }
+ ]
+ },
+ {
+ id: "tools/shadow-account",
+ title: "Shadow Account",
+ description: "Turn a broker journal into behavior diagnostics and a counterfactual strategy path.",
+ lead: "Shadow Account starts with your real trading records, extracts recurring rules, and compares actual trades with a rule-based shadow strategy.",
+ sections: [
+ {
+ id: "flow",
+ title: "Workflow",
+ body: `
+
+ Read a broker export from supported formats or a generic CSV.
+ Profile holding time, win rate, drawdown, PnL ratio, and behavior signals.
+ Extract recurring if-then strategy rules.
+ Run a shadow backtest and attribute delta-PnL.
+ Render an HTML/PDF audit report.
+
+ `
+ },
+ {
+ id: "example",
+ title: "Example",
+ body: `
+ vibe-trading --upload trades_export.csv
+vibe-trading run -p "Analyze my trading behavior, extract my shadow strategy, and compare it with my actual trades"
+ `
+ }
+ ]
+ },
+ {
+ id: "tools/finance-skills",
+ title: "Finance skills",
+ description: "Reusable finance knowledge modules loaded into research runs.",
+ lead: "Skills keep domain knowledge close to the agent without hardcoding every behavior into the core loop.",
+ sections: [
+ {
+ id: "library",
+ title: "Library",
+ body: `
+ Vibe-Trading bundles specialized skills across data sources, strategy generation, analysis, options, reporting, tools, and risk workflows.
+ Good prompts can ask the agent to load or create skills for repeated research patterns.
+ `
+ },
+ {
+ id: "examples",
+ title: "Examples",
+ body: `
+
+ Dividend analysis and yield-trap checks.
+ A-share pre-ST risk screening.
+ vn.py export and Pine Script export workflows.
+ Factor research, macro analysis, and technical patterns.
+
+ `
+ }
+ ]
+ }
+ ]
+ },
+ {
+ id: "reference",
+ label: "Reference",
+ pages: [
+ {
+ id: "reference/cli",
+ title: "CLI reference",
+ description: "Common commands for local research, web UI, memory, swarms, and files.",
+ lead: "The CLI is the fastest operator surface for repeatable research tasks.",
+ sections: [
+ {
+ id: "commands",
+ title: "Common commands",
+ body: `
+ vibe-trading
+vibe-trading init
+vibe-trading run -p "your research prompt"
+vibe-trading --upload report.pdf
+vibe-trading memory list
+vibe-trading serve --port 8899
+vibe-trading-mcp
+ `
+ },
+ {
+ id: "interactive",
+ title: "Interactive mode",
+ body: `
+ Interactive mode supports slash commands for recent runs, swarm presets, memory, and research navigation.
+ `
+ }
+ ]
+ },
+ {
+ id: "reference/mcp-server",
+ title: "MCP server",
+ description: "Expose Vibe-Trading tools to MCP-compatible clients.",
+ lead: "The MCP server runs as a stdio subprocess and exposes Vibe-Trading tools to agent clients.",
+ sections: [
+ {
+ id: "start",
+ title: "Start",
+ body: `
+ vibe-trading-mcp
+ `
+ },
+ {
+ id: "config",
+ title: "Client config",
+ body: `
+ {
+ "mcpServers": {
+ "vibe-trading": {
+ "command": "vibe-trading-mcp"
+ }
+ }
+}
+ `
+ },
+ {
+ id: "tools",
+ title: "Tool surface",
+ body: `
+ The server exposes tools for skills, market data, backtesting, factor analysis, options, web/document reading, trade journals, Shadow Account, and swarm runs.
+ `
+ }
+ ]
+ },
+ {
+ id: "reference/cloudflare-pages",
+ title: "Cloudflare Pages",
+ description: "Deploy this wiki without running a server.",
+ lead: "The wiki is static. Cloudflare Pages can serve it directly from the repository.",
+ sections: [
+ {
+ id: "settings",
+ title: "Pages settings",
+ body: `
+
+ Project root: wiki
+ Build command: leave empty
+ Output directory: .
+ Custom domain: vibetrading.wiki
+
+ `
+ },
+ {
+ id: "why-static",
+ title: "Why static",
+ body: `
+ Docs, landing copy, redirects, theme state, and client-side search all work as static files. No VPS, database, or server process is required.
+ `
+ }
+ ]
+ }
+ ]
+ }
+];
diff --git a/wiki/docs/index.html b/wiki/docs/index.html
new file mode 100644
index 00000000..d50b0a83
--- /dev/null
+++ b/wiki/docs/index.html
@@ -0,0 +1,84 @@
+
+
+
+
+
+
+ Vibe-Trading Docs
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/wiki/docs/main.js b/wiki/docs/main.js
new file mode 100644
index 00000000..d0005918
--- /dev/null
+++ b/wiki/docs/main.js
@@ -0,0 +1,223 @@
+import { initTheme } from "/theme.js";
+import {
+ DOCS_DEFAULT_PAGE,
+ DOCS_DEFAULT_VERSION,
+ DOCS_LATEST_ALIAS,
+ DOCS_STRUCTURE,
+ DOCS_VERSIONS
+} from "/docs/content.js";
+
+const REPO = "HKUDS/Vibe-Trading";
+const API = `https://api.github.com/repos/${REPO}`;
+const STARS_CACHE_KEY = "vibetrading-github-stars";
+const STARS_TTL_MS = 12 * 60 * 60 * 1000;
+
+const allPages = DOCS_STRUCTURE.flatMap((group) =>
+ group.pages.map((page) => ({ ...page, group: group.label }))
+);
+
+function formatStarCount(n) {
+ if (typeof n !== "number" || !Number.isFinite(n) || n < 0) return "--";
+ if (n < 1000) return String(Math.round(n));
+ if (n < 1_000_000) return `${(n / 1000).toFixed(1)}k`;
+ return `${(n / 1_000_000).toFixed(1)}m`;
+}
+
+function initStars() {
+ const el = document.getElementById("star-count");
+ if (!el) return;
+
+ let cached = null;
+ try {
+ cached = JSON.parse(localStorage.getItem(STARS_CACHE_KEY) || "null");
+ } catch {
+ cached = null;
+ }
+ if (cached && typeof cached.count === "number") el.textContent = formatStarCount(cached.count);
+ if (cached && Date.now() - cached.at < STARS_TTL_MS) return;
+
+ fetch(API, { headers: { Accept: "application/vnd.github+json" } })
+ .then((r) => (r.ok ? r.json() : Promise.reject(new Error(String(r.status)))))
+ .then((data) => {
+ if (typeof data.stargazers_count !== "number") return;
+ try {
+ localStorage.setItem(STARS_CACHE_KEY, JSON.stringify({ count: data.stargazers_count, at: Date.now() }));
+ } catch {
+ /* ignore */
+ }
+ el.textContent = formatStarCount(data.stargazers_count);
+ })
+ .catch(() => {
+ if (!cached) el.textContent = "--";
+ });
+}
+
+function slugify(text) {
+ return String(text)
+ .toLowerCase()
+ .replace(/[^a-z0-9]+/g, "-")
+ .replace(/^-|-$/g, "");
+}
+
+function routeParts() {
+ const normalized = location.pathname.replace(/\/+$/, "");
+ if (!normalized || normalized === "/docs" || normalized === "/docs/index.html") {
+ return { version: DOCS_LATEST_ALIAS, pageId: DOCS_DEFAULT_PAGE };
+ }
+ const match = normalized.match(/^\/docs\/([^/]+)\/(.+)$/);
+ if (!match) return { version: DOCS_LATEST_ALIAS, pageId: DOCS_DEFAULT_PAGE };
+ return { version: match[1], pageId: match[2] || DOCS_DEFAULT_PAGE };
+}
+
+function canonicalPath(pageId, version = DOCS_LATEST_ALIAS) {
+ return `/docs/${version}/${pageId}`;
+}
+
+function resolvePage(pageId) {
+ return allPages.find((page) => page.id === pageId) || allPages.find((page) => page.id === DOCS_DEFAULT_PAGE);
+}
+
+function setMeta(page, version) {
+ document.title = `${page.title} - Vibe-Trading Docs`;
+ const description = page.description || page.lead || "Vibe-Trading documentation.";
+ document.querySelector('meta[name="description"]')?.setAttribute("content", description);
+ document.querySelector('meta[property="og:title"]')?.setAttribute("content", `${page.title} - Vibe-Trading Docs`);
+ document.querySelector('meta[property="og:description"]')?.setAttribute("content", description);
+ const canonical = `https://vibetrading.wiki${canonicalPath(page.id, version)}`;
+ document.querySelector('meta[property="og:url"]')?.setAttribute("content", canonical);
+ document.querySelector("link[rel='canonical']")?.setAttribute("href", canonical);
+}
+
+function navLink(page, currentId) {
+ const active = page.id === currentId ? "is-active" : "";
+ return `
+ ${page.title}
+ ${page.description}
+ `;
+}
+
+function renderNav(currentId, filter = "") {
+ const nav = document.getElementById("docs-nav");
+ if (!nav) return;
+ const q = filter.trim().toLowerCase();
+ const groups = DOCS_STRUCTURE.map((group) => {
+ const pages = group.pages.filter((page) => {
+ if (!q) return true;
+ return `${page.title} ${page.description} ${page.lead}`.toLowerCase().includes(q);
+ });
+ if (!pages.length) return "";
+ return `
+ ${group.label}
+ ${pages.map((page) => navLink(page, currentId)).join("")}
+ `;
+ }).join("");
+ nav.innerHTML = groups || `No pages match that search.
`;
+}
+
+function renderOutline(page) {
+ const outline = document.getElementById("docs-outline");
+ if (!outline) return;
+ outline.innerHTML = page.sections.map((section) =>
+ `${section.title} `
+ ).join("");
+}
+
+function renderArticle(page, version) {
+ const article = document.getElementById("docs-article");
+ if (!article) return;
+
+ const index = allPages.findIndex((candidate) => candidate.id === page.id);
+ const previous = index > 0 ? allPages[index - 1] : null;
+ const next = index < allPages.length - 1 ? allPages[index + 1] : null;
+
+ article.innerHTML = `
+
+ ${page.group}
+ ${page.title}
+ ${page.lead}
+
+ ${page.sections.map((section) => `
+
+ ${section.title}
+ ${section.body}
+
+ `).join("")}
+
+ `;
+}
+
+function renderVersionSelect(version) {
+ const select = document.getElementById("version-select");
+ if (!select) return;
+ select.innerHTML = DOCS_VERSIONS.map((item) =>
+ `${item.label} `
+ ).join("");
+ select.addEventListener("change", () => {
+ const { pageId } = routeParts();
+ navigate(canonicalPath(pageId, DOCS_LATEST_ALIAS));
+ });
+}
+
+function navigate(path) {
+ history.pushState({}, "", path);
+ renderCurrent();
+ window.scrollTo({ top: 0, behavior: "smooth" });
+}
+
+function bindDocLinks(root = document) {
+ root.querySelectorAll("[data-doc-link]").forEach((link) => {
+ link.addEventListener("click", (event) => {
+ event.preventDefault();
+ const pageId = link.getAttribute("data-doc-link");
+ if (!pageId) return;
+ navigate(canonicalPath(pageId));
+ });
+ });
+}
+
+function renderCurrent() {
+ const { version, pageId } = routeParts();
+ const resolvedVersion = version === DOCS_LATEST_ALIAS ? DOCS_LATEST_ALIAS : DOCS_DEFAULT_VERSION;
+ const page = resolvePage(pageId);
+
+ if (location.pathname === "/docs" || location.pathname === "/docs/" || location.pathname === "/docs/index.html") {
+ history.replaceState({}, "", canonicalPath(page.id, DOCS_LATEST_ALIAS));
+ }
+
+ setMeta(page, resolvedVersion);
+ renderNav(page.id, document.getElementById("docs-search")?.value || "");
+ renderArticle(page, resolvedVersion);
+ renderOutline(page);
+ bindDocLinks(document);
+ document.getElementById("docs-article")?.focus({ preventScroll: true });
+}
+
+function initSearch() {
+ const search = document.getElementById("docs-search");
+ if (!search) return;
+ search.addEventListener("input", () => {
+ const { pageId } = routeParts();
+ renderNav(resolvePage(pageId).id, search.value);
+ bindDocLinks(document.getElementById("docs-nav") || document);
+ });
+}
+
+function initHeaderScroll() {
+ const header = document.getElementById("site-header");
+ if (!header) return;
+ const sync = () => header.classList.toggle("is-scrolled", window.scrollY > 10);
+ sync();
+ window.addEventListener("scroll", sync, { passive: true });
+}
+
+window.addEventListener("popstate", renderCurrent);
+
+initTheme();
+initStars();
+renderVersionSelect(DOCS_DEFAULT_VERSION);
+initSearch();
+initHeaderScroll();
+renderCurrent();
diff --git a/wiki/docs/styles.css b/wiki/docs/styles.css
new file mode 100644
index 00000000..0594c4ba
--- /dev/null
+++ b/wiki/docs/styles.css
@@ -0,0 +1,286 @@
+.docs-body {
+ background:
+ linear-gradient(90deg, rgba(11, 143, 112, 0.045) 1px, transparent 1px),
+ linear-gradient(0deg, rgba(35, 102, 168, 0.04) 1px, transparent 1px),
+ var(--bg);
+ background-size: 36px 36px, 36px 36px, auto;
+}
+
+.brand__text em {
+ color: var(--muted);
+ font-family: var(--mono);
+ font-size: 0.72rem;
+ font-style: normal;
+ font-weight: 600;
+ text-transform: uppercase;
+}
+
+.docs-shell {
+ display: grid;
+ grid-template-columns: 280px minmax(0, 1fr) 210px;
+ gap: 32px;
+ width: min(1320px, calc(100% - 32px));
+ margin: 0 auto;
+ padding: 34px 0 80px;
+}
+
+.docs-sidebar,
+.docs-outline {
+ position: sticky;
+ top: 96px;
+ align-self: start;
+ max-height: calc(100vh - 118px);
+ overflow: auto;
+}
+
+.docs-sidebar {
+ border: 1px solid var(--line);
+ border-radius: 8px;
+ background: color-mix(in srgb, var(--surface) 90%, transparent);
+}
+
+.docs-sidebar__tools {
+ display: grid;
+ gap: 12px;
+ border-bottom: 1px solid var(--line);
+ padding: 14px;
+}
+
+.search-box,
+.version-box {
+ display: grid;
+ gap: 6px;
+ color: var(--muted);
+ font-size: 0.78rem;
+ font-weight: 700;
+ text-transform: uppercase;
+}
+
+.search-box input,
+.version-box select {
+ width: 100%;
+ min-height: 40px;
+ border: 1px solid var(--line);
+ border-radius: 6px;
+ background: var(--surface);
+ color: var(--ink);
+ font: 500 0.92rem var(--sans);
+ padding: 0 10px;
+}
+
+#docs-nav {
+ padding: 8px;
+}
+
+#docs-nav section + section {
+ margin-top: 8px;
+}
+
+#docs-nav h2 {
+ margin: 18px 8px 8px;
+ color: var(--accent-2);
+ font-family: var(--mono);
+ font-size: 0.74rem;
+ font-weight: 700;
+ letter-spacing: 0.08em;
+ text-transform: uppercase;
+}
+
+#docs-nav a {
+ display: grid;
+ gap: 4px;
+ border: 1px solid transparent;
+ border-radius: 6px;
+ padding: 10px;
+}
+
+#docs-nav a:hover,
+#docs-nav a.is-active {
+ border-color: var(--line);
+ background: var(--surface-2);
+}
+
+#docs-nav span {
+ font-weight: 700;
+}
+
+#docs-nav small {
+ color: var(--muted);
+ font-size: 0.78rem;
+ line-height: 1.35;
+}
+
+.empty-state {
+ margin: 8px;
+ color: var(--muted);
+ font-size: 0.9rem;
+}
+
+.docs-article {
+ min-width: 0;
+ outline: none;
+}
+
+.doc-hero {
+ border-bottom: 1px solid var(--line);
+ padding: 28px 0 34px;
+}
+
+.doc-hero h1 {
+ max-width: 840px;
+ font-size: clamp(2.6rem, 6vw, 5.6rem);
+}
+
+.doc-hero p:last-child {
+ max-width: 760px;
+ margin: 18px 0 0;
+ color: var(--muted);
+ font-size: 1.15rem;
+}
+
+.doc-section {
+ border-bottom: 1px solid var(--line);
+ padding: 34px 0;
+}
+
+.doc-section h2 {
+ margin-bottom: 14px;
+ font-size: clamp(1.7rem, 3vw, 2.8rem);
+}
+
+.doc-section p,
+.doc-section li {
+ max-width: 780px;
+ color: var(--muted);
+}
+
+.doc-section strong {
+ color: var(--ink);
+}
+
+.doc-section a {
+ color: var(--accent-3);
+ font-weight: 700;
+}
+
+.doc-section ul,
+.doc-section ol {
+ display: grid;
+ gap: 10px;
+ margin: 16px 0 0;
+ padding-left: 22px;
+}
+
+.doc-section pre {
+ max-width: 860px;
+ border: 1px solid var(--line);
+ border-radius: 8px;
+ background: #101312;
+ color: #edf5ef;
+ margin: 18px 0 0;
+}
+
+.doc-section code {
+ font-family: var(--mono);
+ font-size: 0.92em;
+}
+
+.doc-section p code,
+.doc-section li code {
+ border: 1px solid var(--line);
+ border-radius: 4px;
+ background: var(--surface-2);
+ color: var(--ink);
+ padding: 0.12em 0.3em;
+}
+
+.docs-outline {
+ color: var(--muted);
+}
+
+.docs-outline p {
+ margin: 0 0 10px;
+ color: var(--accent-2);
+ font-family: var(--mono);
+ font-size: 0.72rem;
+ font-weight: 700;
+ letter-spacing: 0.08em;
+ text-transform: uppercase;
+}
+
+#docs-outline {
+ display: grid;
+ gap: 8px;
+}
+
+#docs-outline a {
+ border-left: 2px solid var(--line);
+ padding-left: 10px;
+ font-size: 0.88rem;
+}
+
+#docs-outline a:hover {
+ border-color: var(--accent);
+ color: var(--ink);
+}
+
+.doc-footer {
+ display: grid;
+ grid-template-columns: repeat(2, minmax(0, 1fr));
+ gap: 14px;
+ padding-top: 32px;
+}
+
+.doc-footer a {
+ display: grid;
+ gap: 5px;
+ border: 1px solid var(--line);
+ border-radius: 8px;
+ background: var(--surface);
+ padding: 16px;
+}
+
+.doc-footer a:last-child {
+ text-align: right;
+}
+
+.doc-footer span {
+ color: var(--muted);
+ font-family: var(--mono);
+ font-size: 0.78rem;
+ text-transform: uppercase;
+}
+
+.doc-footer strong {
+ font-size: 1rem;
+}
+
+@media (max-width: 1080px) {
+ .docs-shell {
+ grid-template-columns: 260px minmax(0, 1fr);
+ }
+
+ .docs-outline {
+ display: none;
+ }
+}
+
+@media (max-width: 760px) {
+ .docs-shell {
+ grid-template-columns: 1fr;
+ padding-top: 18px;
+ }
+
+ .docs-sidebar {
+ position: static;
+ max-height: none;
+ }
+
+ .doc-footer {
+ grid-template-columns: 1fr;
+ }
+
+ .doc-footer a:last-child {
+ text-align: left;
+ }
+}
diff --git a/wiki/home/index.html b/wiki/home/index.html
new file mode 100644
index 00000000..7a761797
--- /dev/null
+++ b/wiki/home/index.html
@@ -0,0 +1,195 @@
+
+
+
+
+
+
+ Vibe-Trading Wiki - Finance Research Agent
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Open-source finance research agent
+
Turn market questions into runnable research.
+
Vibe-Trading connects natural-language prompts to market data, backtests, swarm analysis, trade-journal diagnostics, and reproducible research artifacts.
+
+
+
+
+
+
+ vibe-trading
+
+
+
$ vibe-trading run -p "Backtest BTC-USDT 20/50 MA for 2024"
+
route: crypto data -> strategy -> backtest -> run card
+
return +18.6% max drawdown -7.4%
+
$ vibe-trading --swarm-run investment_committee
+
workers: macro, quant, risk, catalyst
+
report saved with evidence, metrics, and caveats
+
+
+
+ BTC-USDT SPY 510300.SH USDCNH
+
+
+
+
+
+
+
Install
+
One command to start a research workspace.
+
+
+
+ PyPI
+ uv
+ Source
+
+
pip install vibe-trading-ai
+vibe-trading init
+vibe-trading
+
uv tool install vibe-trading-ai
+vibe-trading init
+vibe-trading serve --port 8899
+
git clone https://github.com/HKUDS/Vibe-Trading
+cd Vibe-Trading
+pip install -e .
+vibe-trading
+
+
+
+
+
+
Research loop
+
Every answer keeps the trail inspectable.
+
+
+
+ 01
+ Route
+ Select skills, data sources, tools, and swarm teams from the request.
+
+
+ 02
+ Ground
+ Fetch market data, documents, URLs, broker journals, or local files at runtime.
+
+
+ 03
+ Test
+ Run backtests, factor analysis, options checks, validation, and report generation.
+
+
+ 04
+ Deliver
+ Return run cards, exports, reports, tool traces, and caveats for the next session.
+
+
+
+
+
+
+
Capabilities
+
Built for finance research, not brokerage execution.
+
+
+
+
+ Agent Harness
+ Persistent memory, session search, editable skills, context compression, and tool-batched research runs.
+
+
+
+ Backtesting
+ Seven engines across equities, crypto, futures, forex, composites, and options portfolios.
+
+
+
+ Swarm Teams
+ Investment committee, quant desk, risk review, macro, crypto, and other preset research teams.
+
+
+
+ Shadow Account
+ Parse broker journals, extract implicit rules, run counterfactual backtests, and render audit reports.
+
+
+
+
+
+
+
Boundary
+
No live trading.
+
+ Vibe-Trading is for research, simulation, and backtesting. It does not execute live trades and does not provide investment advice.
+
+
+
+
+
+
diff --git a/wiki/index.html b/wiki/index.html
new file mode 100644
index 00000000..5135bcd9
--- /dev/null
+++ b/wiki/index.html
@@ -0,0 +1,14 @@
+
+
+
+
+
+
+ Vibe-Trading Wiki
+
+
+
+
+ Open Vibe-Trading Wiki
+
+
diff --git a/wiki/locales/en.json b/wiki/locales/en.json
new file mode 100644
index 00000000..3afa6394
--- /dev/null
+++ b/wiki/locales/en.json
@@ -0,0 +1,10 @@
+{
+ "nav.docs": "Docs",
+ "nav.source": "Source",
+ "nav.pypi": "PyPI",
+ "hero.eyebrow": "Open-source finance research agent",
+ "hero.title": "Turn market questions into runnable research.",
+ "hero.lead": "Vibe-Trading connects natural-language prompts to market data, backtests, swarm analysis, trade-journal diagnostics, and reproducible research artifacts.",
+ "hero.cta_docs": "Read the docs",
+ "hero.cta_source": "View source"
+}
diff --git a/wiki/main.js b/wiki/main.js
new file mode 100644
index 00000000..b895dbec
--- /dev/null
+++ b/wiki/main.js
@@ -0,0 +1,119 @@
+import { initTheme } from "./theme.js";
+
+const REPO = "HKUDS/Vibe-Trading";
+const API = `https://api.github.com/repos/${REPO}`;
+const STARS_CACHE_KEY = "vibetrading-github-stars";
+const STARS_TTL_MS = 12 * 60 * 60 * 1000;
+
+function formatStarCount(n) {
+ if (typeof n !== "number" || !Number.isFinite(n) || n < 0) return "--";
+ if (n < 1000) return String(Math.round(n));
+ if (n < 1_000_000) return `${(n / 1000).toFixed(1)}k`;
+ return `${(n / 1_000_000).toFixed(1)}m`;
+}
+
+function readStarsCache() {
+ try {
+ const raw = localStorage.getItem(STARS_CACHE_KEY);
+ if (!raw) return null;
+ const value = JSON.parse(raw);
+ if (typeof value.count !== "number" || typeof value.at !== "number") return null;
+ return value;
+ } catch {
+ return null;
+ }
+}
+
+function writeStarsCache(count) {
+ try {
+ localStorage.setItem(STARS_CACHE_KEY, JSON.stringify({ count, at: Date.now() }));
+ } catch {
+ /* private mode */
+ }
+}
+
+function initStars() {
+ const el = document.getElementById("star-count");
+ if (!el) return;
+ const cached = readStarsCache();
+ if (cached) el.textContent = formatStarCount(cached.count);
+ if (cached && Date.now() - cached.at < STARS_TTL_MS) return;
+
+ fetch(API, { headers: { Accept: "application/vnd.github+json" } })
+ .then((r) => (r.ok ? r.json() : Promise.reject(new Error(String(r.status)))))
+ .then((data) => {
+ if (typeof data.stargazers_count !== "number") return;
+ writeStarsCache(data.stargazers_count);
+ el.textContent = formatStarCount(data.stargazers_count);
+ })
+ .catch(() => {
+ if (!cached) el.textContent = "--";
+ });
+}
+
+function initInstallTabs() {
+ const tabs = Array.from(document.querySelectorAll("[data-tab]"));
+ if (!tabs.length) return;
+
+ for (const tab of tabs) {
+ tab.addEventListener("click", () => {
+ const key = tab.getAttribute("data-tab");
+ for (const item of tabs) {
+ const selected = item === tab;
+ item.setAttribute("aria-selected", String(selected));
+ const panelId = item.getAttribute("aria-controls");
+ if (panelId) {
+ const panel = document.getElementById(panelId);
+ if (panel) panel.hidden = !selected;
+ }
+ }
+ if (key) {
+ try {
+ localStorage.setItem("vibetrading-install-tab", key);
+ } catch {
+ /* ignore */
+ }
+ }
+ });
+ }
+
+ try {
+ const saved = localStorage.getItem("vibetrading-install-tab");
+ const selected = tabs.find((tab) => tab.getAttribute("data-tab") === saved);
+ if (selected) selected.click();
+ } catch {
+ /* ignore */
+ }
+}
+
+async function applyLocale() {
+ try {
+ const response = await fetch("/locales/en.json");
+ if (!response.ok) return;
+ const messages = await response.json();
+ document.querySelectorAll("[data-i18n]").forEach((el) => {
+ const key = el.getAttribute("data-i18n");
+ if (key && messages[key] != null) el.textContent = messages[key];
+ });
+ document.querySelectorAll("[data-i18n-html]").forEach((el) => {
+ const key = el.getAttribute("data-i18n-html");
+ if (key && messages[key] != null) el.innerHTML = messages[key];
+ });
+ } catch {
+ /* static fallback text is already in the HTML */
+ }
+}
+
+function initHeaderScroll() {
+ const header = document.getElementById("site-header");
+ if (!header) return;
+ const sync = () => header.classList.toggle("is-scrolled", window.scrollY > 10);
+ sync();
+ window.addEventListener("scroll", sync, { passive: true });
+}
+
+initTheme();
+initStars();
+initInstallTabs();
+initHeaderScroll();
+applyLocale();
diff --git a/wiki/styles.css b/wiki/styles.css
new file mode 100644
index 00000000..12bd3cd4
--- /dev/null
+++ b/wiki/styles.css
@@ -0,0 +1,542 @@
+:root {
+ color-scheme: light;
+ --bg: #f7f8f4;
+ --surface: #ffffff;
+ --surface-2: #eff3ea;
+ --ink: #171b1a;
+ --muted: #606962;
+ --line: #d9ded2;
+ --accent: #0b8f70;
+ --accent-2: #d47719;
+ --accent-3: #2366a8;
+ --danger: #b0413e;
+ --shadow: 0 22px 60px rgba(24, 32, 28, 0.12);
+ --mono: "IBM Plex Mono", ui-monospace, SFMono-Regular, Menlo, monospace;
+ --sans: "IBM Plex Sans", system-ui, sans-serif;
+ --serif: "Fraunces", Georgia, serif;
+}
+
+:root[data-theme="dark"] {
+ color-scheme: dark;
+ --bg: #101312;
+ --surface: #171b19;
+ --surface-2: #202821;
+ --ink: #f2f4ec;
+ --muted: #a8b1a9;
+ --line: #313a33;
+ --accent: #35c99b;
+ --accent-2: #f0a04c;
+ --accent-3: #6faee7;
+ --danger: #ff8a7b;
+ --shadow: 0 22px 70px rgba(0, 0, 0, 0.38);
+}
+
+* {
+ box-sizing: border-box;
+}
+
+html {
+ scroll-behavior: smooth;
+}
+
+body {
+ margin: 0;
+ background:
+ linear-gradient(90deg, rgba(11, 143, 112, 0.08) 1px, transparent 1px),
+ linear-gradient(0deg, rgba(35, 102, 168, 0.06) 1px, transparent 1px),
+ var(--bg);
+ background-size: 44px 44px, 44px 44px, auto;
+ color: var(--ink);
+ font-family: var(--sans);
+ font-size: 16px;
+ line-height: 1.55;
+ letter-spacing: 0;
+}
+
+a {
+ color: inherit;
+ text-decoration: none;
+}
+
+img {
+ max-width: 100%;
+}
+
+button {
+ font: inherit;
+}
+
+.site-header {
+ position: sticky;
+ top: 0;
+ z-index: 30;
+ border-bottom: 1px solid transparent;
+ background: color-mix(in srgb, var(--bg) 82%, transparent);
+ backdrop-filter: blur(18px);
+}
+
+.site-header.is-scrolled {
+ border-color: var(--line);
+}
+
+.site-header__inner,
+.site-footer {
+ width: min(1180px, calc(100% - 32px));
+ margin: 0 auto;
+}
+
+.site-header__inner {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ min-height: 72px;
+ gap: 24px;
+}
+
+.brand,
+.site-actions,
+.site-nav {
+ display: flex;
+ align-items: center;
+}
+
+.brand {
+ gap: 12px;
+ min-width: max-content;
+ font-weight: 700;
+}
+
+.brand__mark {
+ width: 38px;
+ height: 38px;
+ border: 1px solid var(--line);
+ border-radius: 6px;
+ background: var(--surface);
+}
+
+.brand__text {
+ font-family: var(--serif);
+ font-size: 1.1rem;
+}
+
+.site-nav {
+ gap: 24px;
+ color: var(--muted);
+ font-size: 0.95rem;
+ font-weight: 600;
+}
+
+.site-nav a:hover {
+ color: var(--ink);
+}
+
+.site-actions {
+ gap: 10px;
+}
+
+.star-pill,
+.icon-button,
+.button,
+.tabs button {
+ border: 1px solid var(--line);
+ border-radius: 6px;
+ background: var(--surface);
+ color: var(--ink);
+}
+
+.star-pill {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ min-height: 38px;
+ padding: 0 12px;
+ font-family: var(--mono);
+ font-size: 0.82rem;
+}
+
+.star-pill strong {
+ color: var(--accent);
+}
+
+.icon-button {
+ display: grid;
+ place-items: center;
+ width: 38px;
+ height: 38px;
+ cursor: pointer;
+}
+
+.theme-icon {
+ width: 16px;
+ height: 16px;
+ border: 2px solid var(--ink);
+ border-radius: 50%;
+ box-shadow: inset -5px -5px 0 var(--ink);
+}
+
+:root[data-theme="dark"] .theme-icon {
+ box-shadow: inset 5px 0 0 var(--ink);
+}
+
+.hero,
+.install-band,
+.workflow,
+.feature-band,
+.safety-band {
+ width: min(1180px, calc(100% - 32px));
+ margin: 0 auto;
+}
+
+.hero {
+ display: grid;
+ grid-template-columns: minmax(0, 1.02fr) minmax(360px, 0.98fr);
+ gap: 48px;
+ align-items: center;
+ min-height: calc(100vh - 72px);
+ padding: 74px 0 54px;
+}
+
+.eyebrow {
+ margin: 0 0 12px;
+ color: var(--accent);
+ font-family: var(--mono);
+ font-size: 0.78rem;
+ font-weight: 600;
+ letter-spacing: 0.08em;
+ text-transform: uppercase;
+}
+
+h1,
+h2,
+h3,
+p {
+ overflow-wrap: anywhere;
+}
+
+h1 {
+ max-width: 820px;
+ margin: 0;
+ font-family: var(--serif);
+ font-size: clamp(3.2rem, 8vw, 7.4rem);
+ line-height: 0.92;
+ letter-spacing: 0;
+}
+
+h2 {
+ margin: 0;
+ font-family: var(--serif);
+ font-size: clamp(2rem, 4vw, 4rem);
+ line-height: 1;
+ letter-spacing: 0;
+}
+
+h3 {
+ margin: 0 0 10px;
+ font-size: 1.1rem;
+ letter-spacing: 0;
+}
+
+.hero__lead {
+ max-width: 670px;
+ margin: 24px 0 0;
+ color: var(--muted);
+ font-size: clamp(1rem, 2.1vw, 1.28rem);
+}
+
+.hero__actions {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 12px;
+ margin-top: 34px;
+}
+
+.button {
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ min-height: 46px;
+ padding: 0 18px;
+ font-weight: 700;
+}
+
+.button--primary {
+ border-color: var(--ink);
+ background: var(--ink);
+ color: var(--bg);
+}
+
+.button--ghost {
+ background: color-mix(in srgb, var(--surface) 78%, transparent);
+}
+
+.hero__visual {
+ display: grid;
+ gap: 16px;
+}
+
+.terminal {
+ overflow: hidden;
+ border: 1px solid var(--line);
+ border-radius: 8px;
+ background: #101312;
+ color: #e7efe8;
+ box-shadow: var(--shadow);
+}
+
+.terminal__bar {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ min-height: 42px;
+ border-bottom: 1px solid #2d3830;
+ padding: 0 14px;
+}
+
+.terminal__bar span {
+ width: 10px;
+ height: 10px;
+ border-radius: 50%;
+ background: #f05d5e;
+}
+
+.terminal__bar span:nth-child(2) {
+ background: #f3b23d;
+}
+
+.terminal__bar span:nth-child(3) {
+ background: #35c99b;
+}
+
+.terminal__bar strong {
+ margin-left: 8px;
+ color: #aeb8af;
+ font-family: var(--mono);
+ font-size: 0.82rem;
+}
+
+.terminal__body {
+ padding: 20px;
+ font-family: var(--mono);
+ font-size: clamp(0.78rem, 1.6vw, 0.95rem);
+}
+
+.terminal__body p {
+ margin: 0 0 14px;
+}
+
+.terminal__body p:last-child {
+ margin-bottom: 0;
+}
+
+.prompt {
+ color: var(--accent-2);
+}
+
+.ok {
+ color: #35c99b;
+}
+
+.warn {
+ color: #f0a04c;
+}
+
+.muted {
+ color: #94a097;
+}
+
+.market-strip {
+ display: grid;
+ grid-template-columns: repeat(4, 1fr);
+ gap: 8px;
+}
+
+.market-strip span {
+ border: 1px solid var(--line);
+ border-radius: 6px;
+ background: var(--surface);
+ padding: 10px 8px;
+ color: var(--muted);
+ font-family: var(--mono);
+ font-size: 0.78rem;
+ text-align: center;
+}
+
+.section-head {
+ display: grid;
+ grid-template-columns: minmax(0, 0.7fr) minmax(0, 1.3fr);
+ gap: 24px;
+ align-items: end;
+ margin-bottom: 24px;
+}
+
+.install-band,
+.workflow,
+.feature-band,
+.safety-band {
+ padding: 68px 0;
+}
+
+.install-panel {
+ border: 1px solid var(--line);
+ border-radius: 8px;
+ background: var(--surface);
+ box-shadow: var(--shadow);
+}
+
+.tabs {
+ display: flex;
+ gap: 8px;
+ border-bottom: 1px solid var(--line);
+ padding: 10px;
+}
+
+.tabs button {
+ min-height: 36px;
+ padding: 0 14px;
+ cursor: pointer;
+ font-family: var(--mono);
+ font-size: 0.82rem;
+}
+
+.tabs button[aria-selected="true"] {
+ border-color: var(--ink);
+ background: var(--ink);
+ color: var(--bg);
+}
+
+pre {
+ margin: 0;
+ overflow-x: auto;
+ padding: 24px;
+ font-family: var(--mono);
+ font-size: clamp(0.86rem, 1.7vw, 1rem);
+}
+
+.step-grid,
+.feature-grid {
+ display: grid;
+ grid-template-columns: repeat(4, minmax(0, 1fr));
+ gap: 14px;
+}
+
+.step-grid article,
+.feature-card {
+ border: 1px solid var(--line);
+ border-radius: 8px;
+ background: color-mix(in srgb, var(--surface) 88%, transparent);
+ padding: 18px;
+}
+
+.step-grid span {
+ display: block;
+ margin-bottom: 26px;
+ color: var(--accent-2);
+ font-family: var(--mono);
+ font-size: 0.82rem;
+ font-weight: 600;
+}
+
+.step-grid p,
+.feature-card p {
+ margin: 0;
+ color: var(--muted);
+}
+
+.feature-card {
+ display: flex;
+ flex-direction: column;
+}
+
+.feature-card img {
+ width: 100%;
+ aspect-ratio: 1 / 0.72;
+ object-fit: cover;
+ border-radius: 6px;
+ margin-bottom: 16px;
+ background: var(--surface-2);
+}
+
+.safety-band {
+ display: grid;
+ grid-template-columns: minmax(220px, 0.8fr) minmax(0, 1.2fr);
+ gap: 24px;
+ align-items: center;
+ border-top: 1px solid var(--line);
+ border-bottom: 1px solid var(--line);
+}
+
+.safety-band p:last-child {
+ margin: 0;
+ color: var(--muted);
+ font-size: 1.08rem;
+}
+
+.site-footer {
+ display: flex;
+ flex-wrap: wrap;
+ align-items: center;
+ gap: 18px;
+ min-height: 92px;
+ color: var(--muted);
+ font-size: 0.92rem;
+}
+
+.site-footer span {
+ margin-right: auto;
+ color: var(--ink);
+ font-weight: 700;
+}
+
+@media (max-width: 920px) {
+ .hero {
+ grid-template-columns: 1fr;
+ min-height: auto;
+ }
+
+ .step-grid,
+ .feature-grid {
+ grid-template-columns: repeat(2, minmax(0, 1fr));
+ }
+}
+
+@media (max-width: 680px) {
+ .site-header__inner {
+ min-height: 66px;
+ gap: 12px;
+ }
+
+ .site-nav {
+ display: none;
+ }
+
+ .brand__text {
+ font-size: 1rem;
+ }
+
+ .star-pill {
+ display: none;
+ }
+
+ .hero {
+ padding-top: 46px;
+ }
+
+ h1 {
+ font-size: clamp(3rem, 16vw, 4.8rem);
+ }
+
+ .section-head,
+ .safety-band {
+ grid-template-columns: 1fr;
+ }
+
+ .step-grid,
+ .feature-grid,
+ .market-strip {
+ grid-template-columns: 1fr;
+ }
+
+ .tabs {
+ overflow-x: auto;
+ }
+}
diff --git a/wiki/theme.js b/wiki/theme.js
new file mode 100644
index 00000000..75ad4246
--- /dev/null
+++ b/wiki/theme.js
@@ -0,0 +1,31 @@
+const STORAGE_KEY = "vibetrading-theme";
+
+function preferredTheme() {
+ try {
+ const stored = localStorage.getItem(STORAGE_KEY);
+ if (stored === "dark" || stored === "light") return stored;
+ } catch {
+ /* ignore */
+ }
+ return window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light";
+}
+
+function applyTheme(theme) {
+ document.documentElement.setAttribute("data-theme", theme);
+}
+
+export function initTheme() {
+ applyTheme(preferredTheme());
+ const button = document.getElementById("theme-toggle");
+ if (!button) return;
+
+ button.addEventListener("click", () => {
+ const next = document.documentElement.getAttribute("data-theme") === "dark" ? "light" : "dark";
+ applyTheme(next);
+ try {
+ localStorage.setItem(STORAGE_KEY, next);
+ } catch {
+ /* ignore */
+ }
+ });
+}