Add static Vibe-Trading wiki

Add a static Cloudflare Pages wiki under wiki/, including the landing page, docs app, redirects/headers, maintainer README, and wiki validation workflow.
This commit is contained in:
Haozhe Wu authored and GitHub committed 2026-05-14 19:31:31 +08:00
1 parent 02a16a6422
commit 7b27f6e3e2
20 files changed
+1999

No files matched your search

+49
View File
@@ -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
+8
View File
@@ -21,6 +21,12 @@ agent/tests/*_result.json
.ruff_cache/ .ruff_cache/
frontend/dist/ frontend/dist/
frontend/tsconfig.tsbuildinfo frontend/tsconfig.tsbuildinfo
wiki/node_modules/
wiki/dist/
wiki/.astro/
wiki/.vite/
wiki/.wrangler/
wiki/.cache/
.vibe-dev/ .vibe-dev/
.vibe-record/ .vibe-record/
.cache/ .cache/
@@ -35,6 +41,8 @@ data/backup/
# Internal docs (plans, specs) # Internal docs (plans, specs)
docs/ docs/
!wiki/docs/
!wiki/docs/**
data/minutes/ data/minutes/
data/overnight*.log data/overnight*.log
data/*.log data/*.log
+23
View File
@@ -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.
+14
View File
@@ -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
+5
View File
@@ -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
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 217 KiB

+396
View File
@@ -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: `
<p>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.</p>
<p>The goal is not to replace judgment. The goal is to make every research step runnable, inspectable, and easy to repeat.</p>
`
},
{
id: "research-only",
title: "Research only",
body: `
<p>Vibe-Trading does not execute live trades. It is designed for simulation, backtesting, audit trails, and research workflows. Outputs are not investment advice.</p>
`
},
{
id: "capabilities",
title: "Core capabilities",
body: `
<ul>
<li>Natural-language CLI and web workflows.</li>
<li>Seven backtest engines across equities, crypto, futures, forex, composites, and options portfolios.</li>
<li>Market data routing across Tushare, OKX, yfinance, AKShare, CCXT, and Futu.</li>
<li>Trade Journal and Shadow Account workflows for behavior diagnostics.</li>
<li>Swarm presets for committee-style research reviews.</li>
<li>MCP tools for Claude Desktop, OpenClaw, Cursor, and other MCP clients.</li>
</ul>
`
}
]
},
{
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: `
<pre><code>pip install vibe-trading-ai
vibe-trading init
vibe-trading</code></pre>
`
},
{
id: "first-run",
title: "First run",
body: `
<pre><code>vibe-trading run -p "Backtest a BTC-USDT 20/50 moving-average strategy for 2024, summarize return and drawdown, then export the report"</code></pre>
`
},
{
id: "web-ui",
title: "Open the web UI",
body: `
<pre><code>vibe-trading serve --port 8899</code></pre>
<p>The local web UI is useful when you want uploaded files, streaming swarm progress, Settings, and generated artifacts in one place.</p>
`
}
]
},
{
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: `
<pre><code>LANGCHAIN_PROVIDER=deepseek
LANGCHAIN_MODEL_NAME=deepseek-v4-pro
TUSHARE_TOKEN=your-token
TIMEOUT_SECONDS=2400</code></pre>
<p>Run <code>vibe-trading init</code> to bootstrap the local configuration interactively.</p>
`
},
{
id: "keys",
title: "Keys and data sources",
body: `
<p>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.</p>
<p>For non-local API or web deployments, configure <code>API_AUTH_KEY</code> and send requests with <code>Authorization: Bearer &lt;key&gt;</code>.</p>
`
},
{
id: "models",
title: "Model choice",
body: `
<p>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.</p>
`
}
]
}
]
},
{
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: `
<ol>
<li><strong>Plan:</strong> choose relevant skills, tools, data sources, and swarm presets.</li>
<li><strong>Ground:</strong> fetch market bars, documents, URLs, broker journals, or local files at runtime.</li>
<li><strong>Execute:</strong> run backtests, factor analysis, options checks, exports, or report generation.</li>
<li><strong>Validate:</strong> attach metrics, benchmark comparison, Monte Carlo, Bootstrap, Walk-Forward, warnings, and run cards when applicable.</li>
<li><strong>Deliver:</strong> return the answer plus inspectable artifacts.</li>
</ol>
`
},
{
id: "artifacts",
title: "Artifacts",
body: `
<p>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.</p>
`
}
]
},
{
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: `
<ul>
<li>China A-share, global equity, crypto, China futures, global futures, forex, and composite engines.</li>
<li>Options portfolio engine for option strategy research.</li>
<li>Minute intervals including 1m, 5m, 15m, 30m, 1H, 4H, and 1D where supported by the data source.</li>
</ul>
`
},
{
id: "validation",
title: "Validation",
body: `
<p>Research runs can include benchmark comparison, Monte Carlo, Bootstrap confidence intervals, Walk-Forward validation, and run cards. Treat these as evidence helpers, not guarantees.</p>
`
},
{
id: "example",
title: "Example",
body: `
<pre><code>vibe-trading run -p "Backtest an equal-weight SPY and BTC-USDT momentum rotation strategy for 2024 with benchmark comparison"</code></pre>
`
}
]
},
{
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: `
<p>Vibe-Trading includes 29 presets such as investment committee, quant strategy desk, crypto trading desk, macro rates and FX desk, and risk committee.</p>
<pre><code>vibe-trading --swarm-presets
vibe-trading --swarm-run investment_committee '{"topic":"BTC outlook"}'</code></pre>
`
},
{
id: "keys",
title: "Model requirements",
body: `
<p>Most MCP tools work without an LLM key after install. <code>run_swarm</code> needs an LLM provider because it spawns internal worker agents.</p>
`
}
]
}
]
},
{
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 <code>source=\"auto\"</code> while each market keeps its own data rules.",
sections: [
{
id: "providers",
title: "Providers",
body: `
<ul>
<li>Tushare for China market and fundamental workflows when configured.</li>
<li>OKX and CCXT for crypto symbols such as <code>BTC-USDT</code>.</li>
<li>yfinance for global equities and common benchmarks.</li>
<li>AKShare and Futu for additional China, Hong Kong, and market-specific coverage.</li>
</ul>
`
},
{
id: "symbols",
title: "Symbol conventions",
body: `
<p>Crypto pairs use uppercase hyphen format, for example <code>BTC-USDT</code>. Mixed-market research should prefer automatic source routing where possible.</p>
`
}
]
},
{
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: `
<ol>
<li>Read a broker export from supported formats or a generic CSV.</li>
<li>Profile holding time, win rate, drawdown, PnL ratio, and behavior signals.</li>
<li>Extract recurring if-then strategy rules.</li>
<li>Run a shadow backtest and attribute delta-PnL.</li>
<li>Render an HTML/PDF audit report.</li>
</ol>
`
},
{
id: "example",
title: "Example",
body: `
<pre><code>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"</code></pre>
`
}
]
},
{
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: `
<p>Vibe-Trading bundles specialized skills across data sources, strategy generation, analysis, options, reporting, tools, and risk workflows.</p>
<p>Good prompts can ask the agent to load or create skills for repeated research patterns.</p>
`
},
{
id: "examples",
title: "Examples",
body: `
<ul>
<li>Dividend analysis and yield-trap checks.</li>
<li>A-share pre-ST risk screening.</li>
<li>vn.py export and Pine Script export workflows.</li>
<li>Factor research, macro analysis, and technical patterns.</li>
</ul>
`
}
]
}
]
},
{
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: `
<pre><code>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</code></pre>
`
},
{
id: "interactive",
title: "Interactive mode",
body: `
<p>Interactive mode supports slash commands for recent runs, swarm presets, memory, and research navigation.</p>
`
}
]
},
{
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: `
<pre><code>vibe-trading-mcp</code></pre>
`
},
{
id: "config",
title: "Client config",
body: `
<pre><code>{
"mcpServers": {
"vibe-trading": {
"command": "vibe-trading-mcp"
}
}
}</code></pre>
`
},
{
id: "tools",
title: "Tool surface",
body: `
<p>The server exposes tools for skills, market data, backtesting, factor analysis, options, web/document reading, trade journals, Shadow Account, and swarm runs.</p>
`
}
]
},
{
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: `
<ul>
<li>Project root: <code>wiki</code></li>
<li>Build command: leave empty</li>
<li>Output directory: <code>.</code></li>
<li>Custom domain: <code>vibetrading.wiki</code></li>
</ul>
`
},
{
id: "why-static",
title: "Why static",
body: `
<p>Docs, landing copy, redirects, theme state, and client-side search all work as static files. No VPS, database, or server process is required.</p>
`
}
]
}
]
}
];
+84
View File
@@ -0,0 +1,84 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<script>
(function () {
try {
var stored = localStorage.getItem("vibetrading-theme");
var resolved = stored === "dark" || stored === "light"
? stored
: window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light";
document.documentElement.setAttribute("data-theme", resolved);
} catch (e) {
document.documentElement.setAttribute("data-theme", "light");
}
})();
</script>
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Vibe-Trading Docs</title>
<meta name="description" content="Documentation for Vibe-Trading, the natural-language finance research agent.">
<meta name="robots" content="index, follow, max-image-preview:large">
<meta property="og:type" content="article">
<meta property="og:site_name" content="Vibe-Trading Docs">
<meta property="og:title" content="Vibe-Trading Docs">
<meta property="og:description" content="Install, configure, and run finance research workflows with Vibe-Trading.">
<meta property="og:image" content="https://vibetrading.wiki/assets/icon.png">
<meta property="og:url" content="https://vibetrading.wiki/docs/latest/getting-started/vibe-trading-overview">
<link rel="canonical" href="https://vibetrading.wiki/docs/latest/getting-started/vibe-trading-overview">
<link rel="icon" type="image/png" href="/assets/icon.png">
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Fraunces:opsz,wght@9..144,600;9..144,750&family=IBM+Plex+Mono:wght@400;500;600&family=IBM+Plex+Sans:wght@400;500;600;700&display=swap" rel="stylesheet">
<link rel="stylesheet" href="/styles.css">
<link rel="stylesheet" href="/docs/styles.css">
<script type="module" src="/docs/main.js"></script>
</head>
<body class="docs-body">
<header class="site-header docs-header" id="site-header">
<div class="site-header__inner">
<a class="brand" href="/home/" aria-label="Vibe-Trading home">
<img class="brand__mark" src="/assets/icon.png" alt="" width="40" height="40">
<span class="brand__text">Vibe-Trading <em>Docs</em></span>
</a>
<nav class="site-nav" aria-label="Primary">
<a href="/home/">Home</a>
<a href="https://github.com/HKUDS/Vibe-Trading">Source</a>
<a href="https://pypi.org/project/vibe-trading-ai/">PyPI</a>
</nav>
<div class="site-actions">
<a class="star-pill" href="https://github.com/HKUDS/Vibe-Trading" aria-label="Vibe-Trading on GitHub">
<span aria-hidden="true">Star</span>
<strong id="star-count">--</strong>
</a>
<button class="icon-button" id="theme-toggle" type="button" aria-label="Toggle color theme">
<span class="theme-icon" aria-hidden="true"></span>
</button>
</div>
</div>
</header>
<main class="docs-shell">
<aside class="docs-sidebar" aria-label="Documentation navigation">
<div class="docs-sidebar__tools">
<label class="search-box">
<span>Search</span>
<input id="docs-search" type="search" placeholder="Search docs">
</label>
<label class="version-box">
<span>Version</span>
<select id="version-select"></select>
</label>
</div>
<nav id="docs-nav"></nav>
</aside>
<article class="docs-article" id="docs-article" tabindex="-1"></article>
<aside class="docs-outline" aria-label="On this page">
<p>On this page</p>
<nav id="docs-outline"></nav>
</aside>
</main>
</body>
</html>
+223
View File
@@ -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 `<a class="${active}" href="${canonicalPath(page.id)}" data-doc-link="${page.id}">
<span>${page.title}</span>
<small>${page.description}</small>
</a>`;
}
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 `<section>
<h2>${group.label}</h2>
${pages.map((page) => navLink(page, currentId)).join("")}
</section>`;
}).join("");
nav.innerHTML = groups || `<p class="empty-state">No pages match that search.</p>`;
}
function renderOutline(page) {
const outline = document.getElementById("docs-outline");
if (!outline) return;
outline.innerHTML = page.sections.map((section) =>
`<a href="#${section.id || slugify(section.title)}">${section.title}</a>`
).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 = `
<header class="doc-hero">
<p class="eyebrow">${page.group}</p>
<h1>${page.title}</h1>
<p>${page.lead}</p>
</header>
${page.sections.map((section) => `
<section id="${section.id || slugify(section.title)}" class="doc-section">
<h2>${section.title}</h2>
${section.body}
</section>
`).join("")}
<footer class="doc-footer">
${previous ? `<a href="${canonicalPath(previous.id, version)}" data-doc-link="${previous.id}"><span>Previous</span><strong>${previous.title}</strong></a>` : "<span></span>"}
${next ? `<a href="${canonicalPath(next.id, version)}" data-doc-link="${next.id}"><span>Next</span><strong>${next.title}</strong></a>` : "<span></span>"}
</footer>
`;
}
function renderVersionSelect(version) {
const select = document.getElementById("version-select");
if (!select) return;
select.innerHTML = DOCS_VERSIONS.map((item) =>
`<option value="${item.name}" ${item.name === DOCS_DEFAULT_VERSION ? "selected" : ""}>${item.label}</option>`
).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();
+286
View File
@@ -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;
}
}
+195
View File
@@ -0,0 +1,195 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<script>
(function () {
try {
var stored = localStorage.getItem("vibetrading-theme");
var resolved = stored === "dark" || stored === "light"
? stored
: window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light";
document.documentElement.setAttribute("data-theme", resolved);
} catch (e) {
document.documentElement.setAttribute("data-theme", "light");
}
})();
</script>
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Vibe-Trading Wiki - Finance Research Agent</title>
<meta name="description" content="Vibe-Trading is a natural-language finance research agent for market data, backtesting, swarm analysis, trade journals, and research artifacts.">
<meta name="robots" content="index, follow, max-image-preview:large">
<meta name="author" content="HKUDS">
<meta property="og:type" content="website">
<meta property="og:site_name" content="Vibe-Trading Wiki">
<meta property="og:title" content="Vibe-Trading Wiki - Finance Research Agent">
<meta property="og:description" content="Natural-language finance research, backtesting, swarm analysis, and Shadow Account workflows. Research only. No live trading.">
<meta property="og:image" content="https://vibetrading.wiki/assets/icon.png">
<meta property="og:url" content="https://vibetrading.wiki/home/">
<meta name="twitter:card" content="summary_large_image">
<meta name="twitter:title" content="Vibe-Trading Wiki">
<meta name="twitter:description" content="Natural-language finance research, backtesting, swarm analysis, and Shadow Account workflows.">
<meta name="twitter:image" content="https://vibetrading.wiki/assets/icon.png">
<link rel="canonical" href="https://vibetrading.wiki/home/">
<link rel="icon" type="image/png" href="/assets/icon.png">
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Fraunces:opsz,wght@9..144,600;9..144,750&family=IBM+Plex+Mono:wght@400;500;600&family=IBM+Plex+Sans:wght@400;500;600;700&display=swap" rel="stylesheet">
<link rel="stylesheet" href="/styles.css">
<script type="module" src="/main.js"></script>
</head>
<body>
<header class="site-header" id="site-header">
<div class="site-header__inner">
<a class="brand" href="/home/" aria-label="Vibe-Trading home">
<img class="brand__mark" src="/assets/icon.png" alt="" width="40" height="40">
<span class="brand__text">Vibe-Trading</span>
</a>
<nav class="site-nav" aria-label="Primary">
<a href="/docs/" data-i18n="nav.docs">Docs</a>
<a href="https://github.com/HKUDS/Vibe-Trading" data-i18n="nav.source">Source</a>
<a href="https://pypi.org/project/vibe-trading-ai/" data-i18n="nav.pypi">PyPI</a>
</nav>
<div class="site-actions">
<a class="star-pill" href="https://github.com/HKUDS/Vibe-Trading" aria-label="Vibe-Trading on GitHub">
<span aria-hidden="true">Star</span>
<strong id="star-count">--</strong>
</a>
<button class="icon-button" id="theme-toggle" type="button" aria-label="Toggle color theme">
<span class="theme-icon" aria-hidden="true"></span>
</button>
</div>
</div>
</header>
<main>
<section class="hero">
<div class="hero__content">
<p class="eyebrow" data-i18n="hero.eyebrow">Open-source finance research agent</p>
<h1 data-i18n-html="hero.title">Turn market questions into runnable research.</h1>
<p class="hero__lead" data-i18n="hero.lead">Vibe-Trading connects natural-language prompts to market data, backtests, swarm analysis, trade-journal diagnostics, and reproducible research artifacts.</p>
<div class="hero__actions">
<a class="button button--primary" href="/docs/" data-i18n="hero.cta_docs">Read the docs</a>
<a class="button button--ghost" href="https://github.com/HKUDS/Vibe-Trading" data-i18n="hero.cta_source">View source</a>
</div>
</div>
<div class="hero__visual" aria-label="Vibe-Trading research terminal preview">
<div class="terminal">
<div class="terminal__bar">
<span></span><span></span><span></span>
<strong>vibe-trading</strong>
</div>
<div class="terminal__body">
<p><span class="prompt">$</span> vibe-trading run -p "Backtest BTC-USDT 20/50 MA for 2024"</p>
<p class="muted">route: crypto data -> strategy -> backtest -> run card</p>
<p><span class="ok">return</span> +18.6% <span class="warn">max drawdown</span> -7.4%</p>
<p><span class="prompt">$</span> vibe-trading --swarm-run investment_committee</p>
<p class="muted">workers: macro, quant, risk, catalyst</p>
<p><span class="ok">report</span> saved with evidence, metrics, and caveats</p>
</div>
</div>
<div class="market-strip" aria-hidden="true">
<span>BTC-USDT</span><span>SPY</span><span>510300.SH</span><span>USDCNH</span>
</div>
</div>
</section>
<section class="install-band" aria-labelledby="install-title">
<div class="section-head">
<p class="eyebrow">Install</p>
<h2 id="install-title">One command to start a research workspace.</h2>
</div>
<div class="install-panel">
<div class="tabs" role="tablist" aria-label="Installation methods">
<button role="tab" aria-selected="true" aria-controls="install-pypi" id="tab-pypi" data-tab="pypi">PyPI</button>
<button role="tab" aria-selected="false" aria-controls="install-uv" id="tab-uv" data-tab="uv">uv</button>
<button role="tab" aria-selected="false" aria-controls="install-source" id="tab-source" data-tab="source">Source</button>
</div>
<pre id="install-pypi" role="tabpanel" aria-labelledby="tab-pypi"><code>pip install vibe-trading-ai
vibe-trading init
vibe-trading</code></pre>
<pre id="install-uv" role="tabpanel" aria-labelledby="tab-uv" hidden><code>uv tool install vibe-trading-ai
vibe-trading init
vibe-trading serve --port 8899</code></pre>
<pre id="install-source" role="tabpanel" aria-labelledby="tab-source" hidden><code>git clone https://github.com/HKUDS/Vibe-Trading
cd Vibe-Trading
pip install -e .
vibe-trading</code></pre>
</div>
</section>
<section class="workflow" aria-labelledby="workflow-title">
<div class="section-head">
<p class="eyebrow">Research loop</p>
<h2 id="workflow-title">Every answer keeps the trail inspectable.</h2>
</div>
<div class="step-grid">
<article>
<span>01</span>
<h3>Route</h3>
<p>Select skills, data sources, tools, and swarm teams from the request.</p>
</article>
<article>
<span>02</span>
<h3>Ground</h3>
<p>Fetch market data, documents, URLs, broker journals, or local files at runtime.</p>
</article>
<article>
<span>03</span>
<h3>Test</h3>
<p>Run backtests, factor analysis, options checks, validation, and report generation.</p>
</article>
<article>
<span>04</span>
<h3>Deliver</h3>
<p>Return run cards, exports, reports, tool traces, and caveats for the next session.</p>
</article>
</div>
</section>
<section class="feature-band" aria-labelledby="features-title">
<div class="section-head">
<p class="eyebrow">Capabilities</p>
<h2 id="features-title">Built for finance research, not brokerage execution.</h2>
</div>
<div class="feature-grid">
<article class="feature-card">
<img src="/assets/feature-self-improving-trading-agent.png" alt="Self-improving trading agent">
<h3>Agent Harness</h3>
<p>Persistent memory, session search, editable skills, context compression, and tool-batched research runs.</p>
</article>
<article class="feature-card">
<img src="/assets/feature-cross-market-data-backtesting.png" alt="Cross-market data and backtesting">
<h3>Backtesting</h3>
<p>Seven engines across equities, crypto, futures, forex, composites, and options portfolios.</p>
</article>
<article class="feature-card">
<img src="/assets/feature-multi-agent-trading-teams.png" alt="Multi-agent trading teams">
<h3>Swarm Teams</h3>
<p>Investment committee, quant desk, risk review, macro, crypto, and other preset research teams.</p>
</article>
<article class="feature-card">
<img src="/assets/feature-shadow-account.png" alt="Shadow Account workflow">
<h3>Shadow Account</h3>
<p>Parse broker journals, extract implicit rules, run counterfactual backtests, and render audit reports.</p>
</article>
</div>
</section>
<section class="safety-band" aria-label="Research only">
<div>
<p class="eyebrow">Boundary</p>
<h2>No live trading.</h2>
</div>
<p>Vibe-Trading is for research, simulation, and backtesting. It does not execute live trades and does not provide investment advice.</p>
</section>
</main>
<footer class="site-footer">
<span>Vibe-Trading Wiki</span>
<a href="/docs/">Docs</a>
<a href="https://github.com/HKUDS/Vibe-Trading">GitHub</a>
<a href="https://pypi.org/project/vibe-trading-ai/">PyPI</a>
</footer>
</body>
</html>
+14
View File
@@ -0,0 +1,14 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta http-equiv="refresh" content="0; url=/home/">
<title>Vibe-Trading Wiki</title>
<link rel="canonical" href="https://vibetrading.wiki/home/">
<script>location.replace("/home/");</script>
</head>
<body>
<p><a href="/home/">Open Vibe-Trading Wiki</a></p>
</body>
</html>
+10
View File
@@ -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"
}
+119
View File
@@ -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();
+542
View File
@@ -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;
}
}
+31
View File
@@ -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 */
}
});
}