fix(web): unify site layout and surface runtime facts

Use one 76rem container and shared hero/section rhythm across navigation, footer, ticker, feed, install, runtime, and portal pages.

Also make clipboard feedback truthful, repair locale and anchor links, align digest/feed styling, and surface catalog/runtime facts without implying provider readiness.

Signed-off-by: CodeWhale Bot <bot@codewhale.net>
This commit is contained in:
CodeWhale Bot
2026-08-13 20:32:17 -07:00
parent 1c49d737ba
commit 7ad99f400e
16 changed files with 208 additions and 144 deletions

View File

@@ -93,7 +93,7 @@ export default async function CommunityPage({ params }: { params: Promise<{ loca
return (
<>
<section className="community-welcome">
<section className="hero">
<div className="portal-current" aria-hidden="true" />
<div className="portal-container community-welcome-inner">
<div className="eyebrow">{isZh ? "国际开源社区" : "International open-source community"}</div>

View File

@@ -52,7 +52,7 @@ export default async function ConstitutionPage({ params }: { params: Promise<{ l
return (
<>
{/* THE THESIS */}
<section className="mx-auto max-w-[1100px] px-6 pt-12 pb-10">
<section className="site-container section">
<div className="flex items-baseline gap-4 mb-3">
<Seal char="法" />
<div className="eyebrow">{isZh ? "立论" : "The thesis"}</div>
@@ -81,7 +81,7 @@ export default async function ConstitutionPage({ params }: { params: Promise<{ l
</section>
{/* THE THREE LAYERS */}
<section className="mx-auto max-w-[1100px] px-6 py-10 hairline-t">
<section className="site-container py-10 hairline-t">
<div className="flex items-baseline gap-4 mb-6">
<Seal char="序" />
<div className="eyebrow">{isZh ? "位阶,从最稳到最活" : "The rank, most-static first"}</div>
@@ -120,7 +120,7 @@ export default async function ConstitutionPage({ params }: { params: Promise<{ l
</section>
{/* OBSERVABLE IN THE REASONING */}
<section className="mx-auto max-w-[1100px] px-6 py-10 hairline-t">
<section className="site-container py-10 hairline-t">
<div className="flex items-baseline gap-4 mb-3">
<Seal char="证" />
<div className="eyebrow">{isZh ? "在推理里可以被看到" : "Observable in the reasoning"}</div>
@@ -134,7 +134,7 @@ export default async function ConstitutionPage({ params }: { params: Promise<{ l
</section>
{/* WHERE TO GO NEXT */}
<section className="mx-auto max-w-[1100px] px-6 py-8 hairline-t">
<section className="site-container py-8 hairline-t">
<div className="flex flex-wrap items-center gap-3">
<Link
href={p("/install")}

View File

@@ -143,7 +143,7 @@ export default async function ContributePage({ params }: { params: Promise<{ loc
return (
<div className="contribute-page">
<section className="community-welcome">
<section className="hero">
<div className="portal-current" aria-hidden="true" />
<div className="portal-container community-welcome-inner">
<div className="eyebrow">{isZh ? "参与国际开源社区" : "Contribute to an international open-source project"}</div>

View File

@@ -82,10 +82,10 @@ export default async function DigestArchivePage({ params }: { params: Promise<{
if (digests.length === 0) {
return (
<div className="flex flex-col items-center justify-center py-20 text-center max-w-xl mx-auto px-4">
<h1 className="text-3xl font-bold mb-4 tracking-tight">
<h1 className="font-display text-3xl mb-4 tracking-crisp text-ink">
{isZh ? "社区摘要" : "Community Digest"}
</h1>
<p className="text-gray-500 dark:text-gray-400">
<p className="text-ink-mute">
{isZh
? "本周社区动态整理中,请稍后再来!"
: "We are gathering this week's community updates. Check back soon!"}
@@ -96,34 +96,34 @@ export default async function DigestArchivePage({ params }: { params: Promise<{
return (
<div className="max-w-4xl mx-auto py-12 px-6">
<h1 className="text-4xl font-extrabold mb-2 tracking-tight">
<h1 className="font-display text-4xl mb-2 tracking-crisp text-ink">
{isZh ? "每周社区更新" : "Weekly Community Updates"}
</h1>
<p className="text-gray-500 dark:text-gray-400 mb-8">
<p className="text-ink-mute mb-8">
{isZh ? "由 Codewhale 维护者审核的摘要" : "Maintainer-approved summaries from Codewhale"}
</p>
<div className="space-y-12">
{digests.map((digest: WeeklyDigest) => (
<article key={digest.weekId} className="border border-gray-200 dark:border-gray-800 rounded-xl p-6 shadow-sm bg-white dark:bg-black">
<header className="mb-6 border-b border-gray-100 dark:border-gray-900 pb-4">
<span className="text-xs bg-blue-50 text-blue-600 dark:bg-blue-950/50 dark:text-blue-400 px-2.5 py-1 rounded-md font-mono uppercase">
<article key={digest.weekId} className="border hairline rounded-lg p-6 bg-paper-card">
<header className="mb-6 hairline-b pb-4">
<span className="pill">
{digest.weekId}
</span>
<h2 className="text-2xl font-bold mt-3 text-gray-900 dark:text-gray-50">{digest.titleEn}</h2>
<h3 className="text-xl text-gray-500 dark:text-gray-400 mt-1 font-medium">{digest.titleZh}</h3>
<h2 className="font-display text-2xl mt-3 text-ink">{digest.titleEn}</h2>
<h3 className="font-cjk text-lg text-ink-soft mt-1">{digest.titleZh}</h3>
</header>
<div className="mb-6 space-y-4">
<p className="text-gray-700 dark:text-gray-300 leading-relaxed">{digest.summaryEn}</p>
<p className="text-gray-500 dark:text-gray-400 leading-relaxed italic">{digest.summaryZh}</p>
<p className="text-ink-soft leading-relaxed">{digest.summaryEn}</p>
<p className="text-ink-mute leading-relaxed italic">{digest.summaryZh}</p>
</div>
<div className="space-y-6">
{digest.sections.map((section: DigestSection, idx: number) => (
<section key={idx} className="border-l-2 border-gray-200 dark:border-gray-800 pl-4">
<h4 className="font-bold text-lg mb-2 text-gray-900 dark:text-gray-100">{section.heading}</h4>
<ul className="list-disc list-inside space-y-1 text-gray-600 dark:text-gray-400 font-mono text-sm">
<section key={idx} className="border-l-2 border-paper-edge pl-4">
<h4 className="font-display text-lg mb-2 text-ink">{section.heading}</h4>
<ul className="list-disc list-inside space-y-1 text-ink-soft font-mono text-sm">
{section.items.map((item: string, itemIdx: number) => (
<li key={itemIdx}>{item}</li>
))}

View File

@@ -18,7 +18,7 @@ export default async function DocsLayout({
return (
<div className="docs-theme docs-portal min-h-screen">
<section className="docs-portal-hero">
<section className="hero">
<div className="portal-current" aria-hidden="true" />
<div className="portal-container docs-portal-hero-inner">
<div className="portal-mark">

View File

@@ -55,7 +55,7 @@ brew tap Hmbown/deepseek-tui && brew install deepseek-tui
Run <code className="inline">codewhale</code> to start. First run creates <code className="inline">~/.codewhale/</code> automatically. Legacy <code className="inline">~/.deepseek/</code> is still read as a compatibility fallback.
Android arm64 / Termux is preview support: npm works only when the
selected package version has matching Android assets in its GitHub Release.
See the <Link href="/install" className="body-link">full install guide</Link> for China mirrors, Docker, and troubleshooting.
See the <Link href="/en/install" className="body-link">full install guide</Link> for China mirrors, Docker, and troubleshooting.
</p>
</>
),
@@ -119,7 +119,7 @@ codewhale doctor # full connectivity check`}
<ul className="list-disc pl-5 space-y-1 text-sm text-ink-soft mb-3">
<li><strong>DeepSeek</strong> bundled default with a native API route, reasoning streaming, cache metrics, and thinking effort control.</li>
<li><strong>OpenRouter</strong> unified API for DeepSeek models and other open-model routes.</li>
<li><strong>{FACTS.providers.length - 2} more routes</strong> including OpenAI-compatible, Anthropic, Mistral AI, OpenAI Codex, xAI, Moonshot/Kimi, Z.ai, MiniMax, StepFun, Volcengine Ark, Baidu Qianfan, Model Studio, NVIDIA NIM, Fireworks AI, Together AI, DeepInfra, SiliconFlow, Novita AI, Hugging Face, Arcee AI, AtlasCloud, and the keyless local endpoints SGLang, vLLM, and Ollama. <Link href="/models" className="body-link">The full list is generated from the provider registry</Link>.</li>
<li><strong>{FACTS.providers.length - 2} more routes</strong> including OpenAI-compatible, Anthropic, Mistral AI, OpenAI Codex, xAI, Moonshot/Kimi, Z.ai, MiniMax, StepFun, Volcengine Ark, Baidu Qianfan, Model Studio, NVIDIA NIM, Fireworks AI, Together AI, DeepInfra, SiliconFlow, Novita AI, Hugging Face, Arcee AI, AtlasCloud, and the keyless local endpoints SGLang, vLLM, and Ollama. <Link href="/en/models" className="body-link">The full list is generated from the provider registry</Link>.</li>
</ul>
<p>
Set the corresponding env var (e.g. <code className="inline">OPENROUTER_API_KEY</code>) and your provider in <code className="inline">~/.codewhale/config.toml</code>.
@@ -169,13 +169,13 @@ codewhale --provider openrouter --model deepseek/deepseek-v4-pro
sources: ["#574", "#1303", "docs/CONFIGURATION.md"],
},
{
q: "What are Plan, Act, and Operate modes?",
q: "What are Plan, Work, and Operate modes?",
a: (
<>
<ul className="list-disc pl-5 space-y-2 text-sm text-ink-soft">
<li><strong>Plan</strong> — Read-only investigation. Can grep, read files, list directories, fetch URLs. Cannot write or execute shell.</li>
<li><strong>Act</strong> — Normal interactive coding. Tool availability and approval prompts follow the active configuration and permission posture.</li>
<li><strong>Operate</strong> — Direct tools follow the same permission, sandbox, shell, and safety rules as Act. Fleet workers are preferred for independent, parallel, background, or long-running work, but delegation is not mandatory. Workflow is optional for ordered phases and gates.</li>
<li><strong>Work</strong> — Normal interactive coding. Tool availability and approval prompts follow the active configuration and permission posture.</li>
<li><strong>Operate</strong> — Direct tools follow the same permission, sandbox, shell, and safety rules as Work. Fleet workers are preferred for independent, parallel, background, or long-running work, but delegation is not mandatory. Workflow is optional for ordered phases and gates.</li>
</ul>
<p className="mt-2">
When the composer is idle, press <kbd className="font-mono text-xs px-1.5 py-0.5 hairline-t hairline-b hairline-l hairline-r">Tab</kbd> to cycle modes.
@@ -209,7 +209,7 @@ codewhale --provider openrouter --model deepseek/deepseek-v4-pro
<code className="inline">/goal</code> sets a goal for the current TUI session.
App-server clients can also persist a thread-scoped goal through the
<code className="inline">thread/goal/*</code> methods. It does not add another
app mode; the mode switcher remains Plan, Act, and Operate, while permission posture is selected independently.
app mode; the mode switcher remains Plan, Work, and Operate, while permission posture is selected independently.
Track progress in <a href="https://github.com/Hmbown/CodeWhale/issues/891" className="body-link">#891</a>.
</>
),
@@ -252,7 +252,7 @@ codewhale --provider openrouter --model deepseek/deepseek-v4-pro
<>
Codewhale is a bidirectional MCP client and server. Define servers in <code className="inline">~/.codewhale/mcp.json</code>.
Tools appear as <code className="inline">mcp_&lt;server&gt;_&lt;tool&gt;</code>. You can also expose Codewhale as an MCP server with <code className="inline">codewhale mcp</code>.
See the <Link href="/docs#mcp" className="body-link">docs page</Link> for configuration examples.
See the <Link href="/en/docs/mcp" className="body-link">docs page</Link> for configuration examples.
</>
),
sources: ["docs/MCP.md"],
@@ -263,7 +263,7 @@ codewhale --provider openrouter --model deepseek/deepseek-v4-pro
<>
No CLA required. Fork, branch with conventional commits (<code className="inline">feat:</code>, <code className="inline">fix:</code>, etc.), run the local checks, open a PR.
The maintainer reads everything personally. Start with issues labeled <code className="inline">good first issue</code>.
See the <Link href="/contribute" className="body-link">contribute page</Link> and <a href="https://github.com/Hmbown/CodeWhale/blob/main/CONTRIBUTING.md" className="body-link">CONTRIBUTING.md</a>.
See the <Link href="/en/contribute" className="body-link">contribute page</Link> and <a href="https://github.com/Hmbown/CodeWhale/blob/main/CONTRIBUTING.md" className="body-link">CONTRIBUTING.md</a>.
</>
),
sources: ["CONTRIBUTING.md"],
@@ -311,7 +311,7 @@ registry = "sparse+https://mirrors.tuna.tsinghua.edu.cn/crates.io-index/"`}
</p>
<p className="mb-2">
A CNB mirror is maintained for users who cannot reliably reach GitHub
(<Link href="/docs#cnb-mirror" className="body-link">docs/CNB_MIRROR.md</Link>).
(<Link href="/en/install" className="body-link">docs/CNB_MIRROR.md</Link>).
Cargo users can use the TUNA mirror for faster downloads in China.
</p>
<p>
@@ -482,7 +482,7 @@ codewhale doctor # 完整连接检查`}
<ul className="list-disc pl-5 space-y-1 text-sm text-ink-soft mb-3">
<li><strong>DeepSeek</strong> API </li>
<li><strong>OpenRouter</strong> API访 DeepSeek </li>
<li><strong> {FACTS.providers.length - 2} </strong> OpenAI AnthropicMistral AIOpenAI CodexxAIMoonshot/KimiZ.aiMiniMaxStepFunVolcengine ArkModel StudioNVIDIA NIMFireworksTogether AIDeepInfraSiliconFlowNovitaHugging FaceArcee AIAtlasCloud SGLangvLLM Ollama<Link href="/models" className="body-link"></Link></li>
<li><strong> {FACTS.providers.length - 2} </strong> OpenAI AnthropicMistral AIOpenAI CodexxAIMoonshot/KimiZ.aiMiniMaxStepFunVolcengine ArkModel StudioNVIDIA NIMFireworksTogether AIDeepInfraSiliconFlowNovitaHugging FaceArcee AIAtlasCloud SGLangvLLM Ollama<Link href="/zh/models" className="body-link"></Link></li>
</ul>
<p>
<code className="inline">OPENROUTER_API_KEY</code> <code className="inline">~/.codewhale/config.toml</code>
@@ -538,7 +538,7 @@ codewhale --provider openrouter --model deepseek/deepseek-v4-pro
<ul className="list-disc pl-5 space-y-2 text-sm text-ink-soft">
<li><strong>Plan</strong> grep URL Shell</li>
<li><strong>Work</strong> 姿</li>
<li><strong>Operate</strong> Act Shell Fleet worker Workflow</li>
<li><strong>Operate</strong> Work Shell Fleet worker Workflow</li>
</ul>
<p className="mt-2">
<kbd className="font-mono text-xs px-1.5 py-0.5 hairline-t hairline-b hairline-l hairline-r">Tab</kbd>
@@ -571,7 +571,7 @@ codewhale --provider openrouter --model deepseek/deepseek-v4-pro
<>
<code className="inline">/goal</code> TUI <code className="inline">pause</code><code className="inline">resume</code><code className="inline">complete</code><code className="inline">blocked</code> <code className="inline">clear</code>
App-server <code className="inline">thread/goal/*</code> 线 <code className="inline">set</code><code className="inline">get</code> <code className="inline">clear</code>
PlanAct Operate姿
PlanWork Operate姿
<a href="https://github.com/Hmbown/CodeWhale/issues/891" className="body-link">#891</a>
</>
),
@@ -604,7 +604,7 @@ codewhale --provider openrouter --model deepseek/deepseek-v4-pro
<>
Codewhale MCP <code className="inline">~/.codewhale/mcp.json</code>
<code className="inline">mcp_&lt;server&gt;_&lt;tool&gt;</code> <code className="inline">codewhale mcp</code> Codewhale MCP
<Link href="/zh/docs#mcp" className="body-link"></Link>
<Link href="/zh/docs/mcp" className="body-link"></Link>
</>
),
sources: ["docs/MCP.md"],
@@ -662,7 +662,7 @@ registry = "sparse+https://mirrors.tuna.tsinghua.edu.cn/crates.io-index/"`}
</p>
<p className="mb-2">
访 GitHub CNB
<Link href="/docs#cnb-mirror" className="body-link">docs/CNB_MIRROR.md</Link>
<Link href="/zh/install" className="body-link">docs/CNB_MIRROR.md</Link>
Cargo 使 TUNA
</p>
<p>
@@ -746,7 +746,7 @@ export default async function FaqPage({ params }: { params: Promise<{ locale: st
return (
<>
<section className="mx-auto max-w-[1400px] px-6 pt-12 pb-8">
<section className="site-container section">
<div className="flex items-baseline gap-4 mb-3">
<Seal char="问" />
<div className="eyebrow">{isZh ? "常见问题" : "FAQ"}</div>
@@ -765,7 +765,7 @@ export default async function FaqPage({ params }: { params: Promise<{ locale: st
</p>
</section>
<section className="mx-auto max-w-[1400px] px-6 pb-20">
<section className="site-container pb-20">
<FaqSearch items={items} locale={locale} />
<div className="mt-12 text-center">

View File

@@ -35,15 +35,17 @@ export default async function FeedPage({ params }: { params: Promise<{ locale: s
const issues = feed.filter((f) => f.kind === "issue");
const pulls = feed.filter((f) => f.kind === "pull");
const eyebrow = isZh ? "动态" : "Activity";
const emptyLabel = isZh ? "暂无数据 · 动态加载失败" : "No data · feed not loaded";
return (
<>
{isZh ? (
<>
<section className="mx-auto max-w-[1400px] px-6 pt-12 pb-8">
<section className="site-container section">
<div className="flex items-baseline gap-4 mb-3">
<Seal char="动" />
<div className="eyebrow">Section 03 · </div>
<div className="eyebrow">{eyebrow}</div>
</div>
<h1 className="font-display tracking-crisp">
<span className="font-cjk text-indigo text-5xl ml-2">Activity</span>
@@ -55,7 +57,7 @@ export default async function FeedPage({ params }: { params: Promise<{ locale: s
</p>
</section>
<section className="mx-auto max-w-[1400px] px-6 pb-16 grid lg:grid-cols-12 gap-10">
<section className="site-container pb-16 grid lg:grid-cols-12 gap-10">
<div className="lg:col-span-6">
<div className="hairline-t hairline-b hairline-l hairline-r bg-paper">
<div className="bg-indigo text-paper px-4 py-3 flex items-baseline justify-between">
@@ -66,7 +68,7 @@ export default async function FeedPage({ params }: { params: Promise<{ locale: s
{pulls.length > 0 ? (
pulls.map((p) => <FeedCard key={p.url} item={p} />)
) : (
<div className="py-10 text-center text-sm font-mono text-ink-mute"> · feed not loaded</div>
<div className="py-10 text-center text-sm font-mono text-ink-mute">{emptyLabel}</div>
)}
</div>
</div>
@@ -82,7 +84,7 @@ export default async function FeedPage({ params }: { params: Promise<{ locale: s
{issues.length > 0 ? (
issues.map((i) => <FeedCard key={i.url} item={i} />)
) : (
<div className="py-10 text-center text-sm font-mono text-ink-mute"> · feed not loaded</div>
<div className="py-10 text-center text-sm font-mono text-ink-mute">{emptyLabel}</div>
)}
</div>
</div>
@@ -90,7 +92,7 @@ export default async function FeedPage({ params }: { params: Promise<{ locale: s
</section>
<section className="bg-paper-deep hairline-t hairline-b">
<div className="mx-auto max-w-[1400px] px-6 py-10 grid md:grid-cols-3 gap-6 text-center">
<div className="site-container py-10 grid md:grid-cols-3 gap-6 text-center">
<Link href="https://github.com/Hmbown/CodeWhale/issues/new/choose" className="hairline-t hairline-b hairline-l hairline-r bg-paper p-6 hover:bg-indigo hover:text-paper transition-colors">
<div className="font-display text-xl mb-1"></div>
<div className="font-cjk text-sm text-ink-mute">Open an issue</div>
@@ -108,10 +110,10 @@ export default async function FeedPage({ params }: { params: Promise<{ locale: s
</>
) : (
<>
<section className="mx-auto max-w-[1400px] px-6 pt-12 pb-8">
<section className="site-container section">
<div className="flex items-baseline gap-4 mb-3">
<Seal char="动" />
<div className="eyebrow">Section 03 · Activity</div>
<div className="eyebrow">{eyebrow}</div>
</div>
<h1 className="font-display tracking-crisp">
Activity <span className="font-cjk text-indigo text-5xl ml-2"></span>
@@ -123,7 +125,7 @@ export default async function FeedPage({ params }: { params: Promise<{ locale: s
</p>
</section>
<section className="mx-auto max-w-[1400px] px-6 pb-16 grid lg:grid-cols-12 gap-10">
<section className="site-container pb-16 grid lg:grid-cols-12 gap-10">
<div className="lg:col-span-6">
<div className="hairline-t hairline-b hairline-l hairline-r bg-paper">
<div className="bg-indigo text-paper px-4 py-3 flex items-baseline justify-between">
@@ -134,7 +136,7 @@ export default async function FeedPage({ params }: { params: Promise<{ locale: s
{pulls.length > 0 ? (
pulls.map((p) => <FeedCard key={p.url} item={p} />)
) : (
<div className="py-10 text-center text-sm font-mono text-ink-mute"> · feed not loaded</div>
<div className="py-10 text-center text-sm font-mono text-ink-mute">{emptyLabel}</div>
)}
</div>
</div>
@@ -150,7 +152,7 @@ export default async function FeedPage({ params }: { params: Promise<{ locale: s
{issues.length > 0 ? (
issues.map((i) => <FeedCard key={i.url} item={i} />)
) : (
<div className="py-10 text-center text-sm font-mono text-ink-mute"> · feed not loaded</div>
<div className="py-10 text-center text-sm font-mono text-ink-mute">{emptyLabel}</div>
)}
</div>
</div>
@@ -158,7 +160,7 @@ export default async function FeedPage({ params }: { params: Promise<{ locale: s
</section>
<section className="bg-paper-deep hairline-t hairline-b">
<div className="mx-auto max-w-[1400px] px-6 py-10 grid md:grid-cols-3 gap-6 text-center">
<div className="site-container py-10 grid md:grid-cols-3 gap-6 text-center">
<Link href="https://github.com/Hmbown/CodeWhale/issues/new/choose" className="hairline-t hairline-b hairline-l hairline-r bg-paper p-6 hover:bg-indigo hover:text-paper transition-colors">
<div className="font-display text-xl mb-1">Open an issue</div>
<div className="font-cjk text-sm text-ink-mute"></div>

View File

@@ -96,7 +96,7 @@ codewhale doctor`;
return (
<>
{/* ① INSTALL */}
<section className="mx-auto max-w-[1100px] px-6 pt-12 pb-10">
<section className="site-container section">
<div className="flex items-baseline gap-4 mb-3">
<Seal char="装" />
<div className="eyebrow">{isZh ? "01 · 安装" : "01 · Install"}</div>
@@ -140,7 +140,7 @@ codewhale doctor`;
</section>
{/* ② VERIFY */}
<section className="mx-auto max-w-[1100px] px-6 py-10 hairline-t">
<section className="site-container py-10 hairline-t">
<div className="flex items-baseline gap-4 mb-5">
<Seal char="验" />
<div className="eyebrow">{isZh ? "02 · 验证" : "02 · Verify"}</div>
@@ -166,7 +166,7 @@ codewhale doctor`;
</section>
{/* ③ UPDATE */}
<section className="mx-auto max-w-[1100px] px-6 py-10 hairline-t">
<section className="site-container py-10 hairline-t">
<div className="flex items-baseline gap-4 mb-5">
<Seal char="新" />
<div className="eyebrow">{isZh ? "03 · 更新" : "03 · Update"}</div>
@@ -210,7 +210,7 @@ codewhale doctor`;
</section>
{/* ④ FIRST RUN */}
<section className="mx-auto max-w-[1100px] px-6 py-10 hairline-t">
<section className="site-container py-10 hairline-t">
<div className="flex items-baseline gap-4 mb-5">
<Seal char="始" />
<div className="eyebrow">{isZh ? "04 · 首次运行" : "04 · First run"}</div>
@@ -289,7 +289,7 @@ codewhale doctor`;
{/* ⑤ OTHER WAYS TO INSTALL */}
<section id="other-ways" className="bg-paper-deep hairline-t hairline-b">
<div className="mx-auto max-w-[1100px] px-6 py-12">
<div className="site-container py-12">
<div className="flex items-baseline gap-4 mb-5">
<Seal char="备" />
<div className="eyebrow">{isZh ? "05 · 其他安装方式" : "05 · Other ways to install"}</div>
@@ -500,7 +500,7 @@ codewhale doctor`;
</section>
{/* ⑥ WHERE CONFIG LIVES */}
<section className="mx-auto max-w-[1100px] px-6 py-12">
<section className="site-container py-12">
<div className="flex items-baseline gap-4 mb-5">
<Seal char="件" />
<div className="eyebrow">{isZh ? "06 · 配置文件在哪" : "06 · Where config lives"}</div>
@@ -526,7 +526,7 @@ codewhale doctor`;
</section>
{/* ⑦ PROVENANCE */}
<section className="mx-auto max-w-[1100px] px-6 py-12 hairline-t">
<section className="site-container py-12 hairline-t">
<div className="flex items-baseline gap-4 mb-5">
<Seal char="源" />
<div className="eyebrow">{isZh ? "07 · 来源与镜像" : "07 · Provenance & mirrors"}</div>
@@ -603,7 +603,7 @@ codewhale doctor`;
{/* ⑧ NEXT STEPS */}
<section className="bg-paper-deep hairline-t hairline-b">
<div className="mx-auto max-w-[1100px] px-6 py-12">
<div className="site-container py-12">
<div className="flex items-baseline gap-4 mb-5">
<Seal char="续" />
<div className="eyebrow">{isZh ? "08 · 下一步" : "08 · Next steps"}</div>

View File

@@ -62,7 +62,7 @@ export default async function ModelsPage({ params }: { params: Promise<{ locale:
return (
<div className="models-page">
<section className="community-welcome">
<section className="hero">
<div className="portal-current" aria-hidden="true" />
<div className="portal-container community-welcome-inner">
<div className="eyebrow">{isZh ? "模型与提供商" : "Models and providers"}</div>
@@ -96,7 +96,7 @@ export default async function ModelsPage({ params }: { params: Promise<{ locale:
</div>
<div className="portal-topic-list">
{setupPatterns.map((pattern) => (
<Link key={pattern.title} href={p("/docs#providers")}>
<Link key={pattern.title} href={p("/docs/configuration")}>
<strong>{pattern.title}</strong>
<span>{pattern.detail}</span>
<span className="font-mono break-all">{pattern.reference}</span>
@@ -120,12 +120,17 @@ export default async function ModelsPage({ params }: { params: Promise<{ locale:
? "下面的列表由仓库中的提供商注册表生成,并随发布更新。这里列出路由 ID 和常用认证环境变量;传输协议、默认端点、模型解析和完整认证优先级以 docs/PROVIDERS.md 为准。"
: "This list is generated from the provider registry in the repository and updated with releases. It shows route IDs and common authentication environment variables; docs/PROVIDERS.md is the source for wire protocols, default endpoints, model resolution, and full authentication precedence."}
</p>
<ul className="grid gap-x-10 sm:grid-cols-2 hairline-t">
<ul className="grid gap-3 sm:grid-cols-2">
{facts.providers.map((provider) => (
<li key={provider.id} className="py-4 hairline-b min-w-0">
<div className="font-display text-base">{provider.label}</div>
<div className="mt-1 font-mono text-[0.68rem] text-indigo break-all">{provider.id}</div>
<div className="mt-1 font-mono text-[0.64rem] text-ink-mute break-all leading-relaxed">{provider.env}</div>
<li key={provider.id} className="flex items-start gap-3 border hairline rounded-lg bg-paper px-4 py-3 min-w-0">
<span className="mt-1.5 h-2 w-2 shrink-0 rounded-full bg-jade" aria-hidden="true" />
<div className="min-w-0">
<div className="text-sm text-ink font-medium">{provider.label}</div>
<code className="font-mono text-[0.66rem] text-indigo break-all">{provider.id}</code>
<div className="mt-1 font-mono text-[0.62rem] text-ink-mute break-all leading-relaxed">
<code className="inline">{provider.env}</code>
</div>
</div>
</li>
))}
</ul>
@@ -146,6 +151,33 @@ export default async function ModelsPage({ params }: { params: Promise<{ locale:
</p>
</div>
</section>
<section className="portal-section">
<div className="portal-container">
<div className="portal-docs-heading">
<div>
<span>{isZh ? "模型目录" : "Model catalog"}</span>
<h2>{isZh ? "默认模型与 crate 清单" : "Default model & crate inventory"}</h2>
</div>
</div>
<div className="grid gap-8 sm:grid-cols-2">
<div>
<div className="eyebrow mb-1">{isZh ? "默认模型" : "Default model"}</div>
<code className="inline font-mono text-sm break-all">{facts.defaultModel ?? "—"}</code>
</div>
<div>
<div className="eyebrow mb-1">{isZh ? "Crates" : "Crates"}</div>
<ul className="flex flex-wrap gap-1.5">
{facts.crates.map((crate) => (
<li key={crate}>
<code className="inline font-mono text-[0.68rem] break-all">{crate}</code>
</li>
))}
</ul>
</div>
</div>
</div>
</section>
</div>
);
}

View File

@@ -73,7 +73,7 @@ export default async function HomePage({ params }: { params: Promise<{ locale: s
observer does the rest, and reduced motion skips it wholesale. */}
<RevealOnScroll />
{/* HERO — newspaper split: claim + live terminal proof */}
<section className="product-hero paper-hero">
<section className="hero">
<div className="product-container product-hero-grid paper-hero-grid">
<div className="product-hero-copy paper-hero-copy">
<div className="mb-5">

View File

@@ -211,7 +211,7 @@ export default async function RoadmapPage({ params }: { params: Promise<{ locale
return (
<div className="roadmap-page">
<section className="community-welcome">
<section className="hero">
<div className="portal-current" aria-hidden="true" />
<div className="portal-container community-welcome-inner">
<div className="eyebrow">{copy.eyebrow}</div>

View File

@@ -1,4 +1,5 @@
import { Seal } from "@/components/seal";
import { getFacts } from "@/lib/facts";
import { buildPageMetadata } from "@/lib/page-meta";
const REPO_BLOB_BASE = "https://github.com/Hmbown/CodeWhale/blob/main";
@@ -68,14 +69,24 @@ const INTEGRATIONS: Integration[] = [
},
];
function FactRow({ label, children }: { label: string; children: React.ReactNode }) {
return (
<div className="py-3 hairline-b grid gap-1 sm:grid-cols-[10rem_1fr] sm:gap-6 sm:items-baseline">
<div className="eyebrow">{label}</div>
<div className="min-w-0 font-mono text-sm text-ink-soft break-words">{children}</div>
</div>
);
}
export default async function RuntimePage({ params }: { params: Promise<{ locale: string }> }) {
const { locale } = await params;
const isZh = locale === "zh";
const facts = await getFacts();
return (
<>
{/* Hero */}
<section className="mx-auto max-w-[1100px] px-6 pt-12 pb-10">
<section className="site-container section">
<div className="flex items-baseline gap-4 mb-3">
<Seal char="接" />
<div className="eyebrow">{isZh ? "Runtime & 集成" : "Runtime & Integrations"}</div>
@@ -95,8 +106,29 @@ export default async function RuntimePage({ params }: { params: Promise<{ locale
</p>
</section>
{/* Runtime facts */}
<section className="site-container py-8 hairline-t">
<div className="flex items-baseline gap-4 mb-4">
<Seal char="数" />
<div className="eyebrow">{isZh ? "运行时事实" : "Runtime facts"}</div>
</div>
<div className="hairline-t">
<FactRow label={isZh ? "版本" : "Version"}>{facts.version ?? "—"}</FactRow>
<FactRow label={isZh ? "工具数量" : "Tool count"}>{facts.toolCount ?? "—"}</FactRow>
<FactRow label={isZh ? "沙箱后端" : "Sandbox backends"}>
{facts.sandboxBackends.length ? facts.sandboxBackends.join(" · ") : "—"}
</FactRow>
<FactRow label={isZh ? "Crates" : "Crates"}>
{facts.crates.length ? `${facts.crates.length} · ${facts.crates.join(", ")}` : "—"}
</FactRow>
<FactRow label={isZh ? "源码版本" : "Source revision"}>
<code className="inline break-all">{facts.sourceRevision ?? "—"}</code>
</FactRow>
</div>
</section>
{/* Trust boundary */}
<section className="mx-auto max-w-[1100px] px-6 py-8 hairline-t">
<section className="site-container py-8 hairline-t">
<div className="flex items-baseline gap-4 mb-4">
<Seal char="信" />
<div className="eyebrow">{isZh ? "信任边界" : "Trust boundary"}</div>
@@ -138,7 +170,7 @@ export default async function RuntimePage({ params }: { params: Promise<{ locale
</section>
{/* Integration cards */}
<section className="mx-auto max-w-[1100px] px-6 py-10 hairline-t">
<section className="site-container py-10 hairline-t">
<div className="flex items-baseline gap-4 mb-6">
<Seal char="集" />
<h2 className="eyebrow">{isZh ? "集成方式" : "Integration surfaces"}</h2>
@@ -161,7 +193,7 @@ export default async function RuntimePage({ params }: { params: Promise<{ locale
</section>
{/* Read more */}
<section className="mx-auto max-w-[1100px] px-6 py-8 hairline-t">
<section className="site-container py-8 hairline-t">
<p className="text-sm text-ink-soft">
{isZh ? (
<>

View File

@@ -70,6 +70,12 @@
--hairline: rgba(20, 35, 70, 0.14);
--code-bg: #03070d;
--code-fg: #f6f2e8;
/* One site container and one vertical rhythm. Every page gutter aligns
with the nav; every hero and section shares a single padding token. */
--container: min(100% - 2rem, 76rem);
--hero-pad: clamp(3rem, 6vw, 4.75rem);
--section-pad: clamp(2.75rem, 5vw, 4rem);
}
/* ---------- base ---------- */
@@ -201,6 +207,30 @@ html[lang="ko"] h3 {
.hairline-l { border-left: 1px solid var(--hairline); }
.hairline-r { border-right: 1px solid var(--hairline); }
/* The single site container: width + auto margins so every page's gutters
line up with the nav. */
.site-container {
width: var(--container);
margin-inline: auto;
}
/* The single hero band shared by home, docs, and the community portals. */
.hero {
position: relative;
overflow: hidden;
border-bottom: 1px solid var(--hairline);
background:
radial-gradient(circle at 82% 0%, rgba(125, 177, 197, 0.16), transparent 28rem),
linear-gradient(180deg, color-mix(in srgb, var(--paper) 72%, white) 0%, var(--paper-deep) 100%);
color: var(--ink);
padding-block: var(--hero-pad);
}
/* The single section rhythm for per-route hero/section blocks. */
.section {
padding-block: var(--section-pad);
}
.double-rule {
background-image:
linear-gradient(var(--hairline), var(--hairline)),
@@ -366,7 +396,7 @@ mark.search-highlight {
.site-nav-inner {
display: flex;
width: min(100% - 2.5rem, 74rem);
width: var(--container);
min-height: 4.25rem;
align-items: center;
justify-content: space-between;
@@ -394,7 +424,7 @@ mark.search-highlight {
.paper-issue-inner {
display: flex;
width: min(100% - 2.5rem, 74rem);
width: var(--container);
margin-inline: auto;
align-items: center;
justify-content: space-between;
@@ -556,7 +586,7 @@ mark.search-highlight {
.site-footer-main,
.site-footer-meta {
width: min(100% - 2.5rem, 74rem);
width: var(--container);
margin-inline: auto;
}
@@ -645,7 +675,13 @@ mark.search-highlight {
color: var(--ink-mute);
}
.ticker-title { color: var(--ink); }
.ticker-title {
color: var(--ink);
max-width: 24rem;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
/* The contributor's handle is the point of the line, not a footnote. */
.ticker-handle {
@@ -956,9 +992,6 @@ a.body-link:hover { background-size: 100% 6px; color: var(--ink); }
}
@media (max-width: 700px) {
.site-nav-inner,
.site-footer-main,
.site-footer-meta { width: min(100% - 2rem, 74rem); }
.site-wordmark { font-size: 1.05rem; }
}
@@ -975,19 +1008,10 @@ a.body-link:hover { background-size: 100% 6px; color: var(--ink); }
}
.portal-container {
width: min(100% - 2.5rem, 72rem);
width: var(--container);
margin-inline: auto;
}
.portal-hero {
position: relative;
overflow: hidden;
border-bottom: 1px solid var(--hairline);
background:
radial-gradient(circle at 84% 8%, rgba(125, 177, 197, 0.15), transparent 28rem),
linear-gradient(180deg, #ffffff 0%, var(--ocean-mist) 100%);
}
.portal-current {
position: absolute;
inset: 0;
@@ -1434,10 +1458,6 @@ a.body-link:hover { background-size: 100% 6px; color: var(--ink); }
}
@media (max-width: 760px) {
.portal-container {
width: min(100% - 2rem, 72rem);
}
.portal-doc-groups {
grid-template-columns: 1fr;
}
@@ -1507,20 +1527,11 @@ a.body-link:hover { background-size: 100% 6px; color: var(--ink); }
}
.product-container {
width: min(100% - 2.5rem, 76rem);
width: var(--container);
margin-inline: auto;
}
.product-hero,
.paper-hero {
position: relative;
overflow: hidden;
border-bottom: 1px solid var(--hairline);
background:
linear-gradient(180deg, rgba(232, 238, 248, 0.55) 0%, transparent 42%),
var(--paper);
color: var(--ink);
}
/* Product hero background is now the shared `.hero` band defined above. */
.product-hero-brandline {
display: flex;
@@ -1564,7 +1575,6 @@ a.body-link:hover { background-size: 100% 6px; color: var(--ink); }
gap: clamp(1.75rem, 3.5vw, 3.5rem);
align-items: start;
min-height: 0;
padding-block: clamp(2.5rem, 5vw, 4.25rem);
}
.product-hero-copy h1,
@@ -2436,14 +2446,6 @@ html[lang="ko"] .ocean-column h2 {
}
@media (max-width: 760px) {
.product-container {
width: min(100% - 2rem, 76rem);
}
.paper-issue-inner {
width: min(100% - 2rem, 74rem);
}
.paper-wordmark-tag {
max-width: 11rem;
}
@@ -2505,10 +2507,6 @@ html[lang="ko"] .ocean-column h2 {
display: none;
}
.product-hero-grid {
padding-block: 2.75rem 3.5rem;
}
.product-hero-copy h1,
.paper-hero-copy h1 {
max-width: 100%;
@@ -2561,13 +2559,6 @@ html[lang="ko"] .ocean-column h2 {
}
}
@media (min-width: 1536px) {
.site-nav-inner,
.paper-issue-inner {
width: min(100% - 2.5rem, 92rem);
}
}
/* ------------------------------------------------------------------ */
/* Documentation portal */
/* ------------------------------------------------------------------ */
@@ -2576,39 +2567,28 @@ html[lang="ko"] .ocean-column h2 {
background: var(--paper);
}
.docs-portal-hero,
.community-welcome {
position: relative;
overflow: hidden;
border-bottom: 1px solid var(--hairline);
background:
radial-gradient(circle at 82% 0%, rgba(125, 177, 197, 0.16), transparent 28rem),
linear-gradient(180deg, color-mix(in srgb, var(--paper) 72%, white) 0%, var(--paper-deep) 100%);
}
.docs-portal-hero-inner,
.community-welcome-inner {
position: relative;
padding-block: clamp(2.75rem, 5vw, 4rem);
}
.docs-portal-hero .portal-mark {
.hero .portal-mark {
margin-bottom: 1rem;
}
.docs-portal-hero h1,
.community-welcome h1 {
.docs-portal-hero-inner h1,
.community-welcome-inner h1 {
max-width: 50rem;
font-size: clamp(2.2rem, 4.8vw, 3.55rem);
line-height: 1.03;
}
.community-welcome h1 {
.community-welcome-inner h1 {
margin-top: 0.85rem;
}
.docs-portal-hero p,
.community-welcome p {
.docs-portal-hero-inner p,
.community-welcome-inner p {
max-width: 47rem;
margin-top: 1rem;
color: var(--ink-soft);

View File

@@ -11,11 +11,15 @@ interface Props {
export function InstallCodeBlock({ cmd, copyLabel = "Copy", copiedLabel = "Copied ✓" }: Props) {
const [copied, setCopied] = useState(false);
const copy = () => {
if (typeof navigator !== "undefined" && navigator.clipboard) {
navigator.clipboard.writeText(cmd);
const copy = async () => {
if (typeof navigator === "undefined" || !navigator.clipboard) return;
try {
await navigator.clipboard.writeText(cmd);
setCopied(true);
setTimeout(() => setCopied(false), 1400);
} catch {
// Clipboard write failed (permissions, non-secure context, or a focused
// write race) — never show a false "Copied" confirmation.
}
};

View File

@@ -141,9 +141,19 @@ export function Ticker({ items, labels }: { items: FeedItem[]; labels: TickerLab
// flat run of children. The second half is hidden from assistive tech.
const doubled = [...ordered, ...ordered];
// A fixed 80s loop reads broken on /zh: CJK titles are much longer than
// English, so the same duration scrolls 2-4x faster. Derive the duration
// from total glyph count (title + fixed chrome per entry) so reading speed
// stays roughly constant across locales.
const glyphCount = ordered.reduce(
(sum, item) => sum + (item.title?.length ?? 0) + 24,
0,
);
const durationSeconds = Math.max(50, Math.round(glyphCount / 22));
return (
<div className="hairline-t hairline-b bg-paper-deep overflow-hidden">
<div className="mx-auto max-w-[1400px] flex items-stretch">
<div className="site-container flex items-stretch">
<div className="bg-ink text-paper px-4 py-2 flex items-center shrink-0 gap-2">
<span className="w-1.5 h-1.5 bg-indigo rounded-full inline-block animate-pulse" />
<span className="font-cjk text-sm font-semibold tracking-wider">{labels.liveLabel}</span>
@@ -152,7 +162,10 @@ export function Ticker({ items, labels }: { items: FeedItem[]; labels: TickerLab
</span>
</div>
<div className="ticker-viewport" role="group" aria-label={labels.ariaLabel}>
<div className="ticker-track py-2 font-mono text-[0.78rem]">
<div
className="ticker-track py-2 font-mono text-[0.78rem]"
style={{ animationDuration: `${durationSeconds}s` }}
>
{doubled.map((item, i) => (
<TickerEntry
key={`${item.url}-${i}`}

View File

@@ -10,6 +10,7 @@ export default {
paper: "rgb(var(--c-paper) / <alpha-value>)",
"paper-deep": "rgb(var(--c-paper-deep) / <alpha-value>)",
"paper-edge": "rgb(var(--c-paper-edge) / <alpha-value>)",
"paper-card": "var(--paper-card)",
"paper-line": "#1B2230",
"paper-line-soft": "#CBD3DF",
ink: "rgb(var(--c-ink) / <alpha-value>)",