diff --git a/README.md b/README.md
index 0adc509..ead1609 100644
--- a/README.md
+++ b/README.md
@@ -4,9 +4,7 @@
PenguinHarness
-With LangChain, you build agents by hand — at 1× speed. With PenguinHarness, agents build agents — at 100×.
-
-A zero-code CLI and Web UI for building agents, connected to 1,000+ models.
+Your automated agent builder lives on your desktop, connected to 1,000+ models.
@@ -36,9 +34,11 @@
## Why PenguinHarness
+> With LangChain, you build agents by hand — at 1× speed. With PenguinHarness, agents build agents — at 100×.
+
Three reasons, in deliberate order — from task quality, to how agents get built, to how they keep improving.
-### 1. 🏆 Comparable quality, one to two orders of magnitude cheaper
+### 1. 🏆 Outstanding results at tens of times less cost
A deliberately minimal toolset over clean low-level interfaces: fewer tool calls, fewer tokens — deeply tuned for open models like DeepSeek. Each harness on the model it is normally paired with, same tasks, head-to-head:
@@ -51,9 +51,9 @@ A deliberately minimal toolset over clean low-level interfaces: fewer tool calls
**Best accuracy on data analysis — at 1/70 of Claude Code's cost.**
-### 2. ⚡ One sentence, and an Agent builds your Agent app
+### 2. ⚡ One sentence, and an agent builds your agent app
-Type one sentence, and an Agent builds the complete Agent application for you — scaffold, code, and run instructions, end to end:
+Type one sentence, and an agent builds the complete agent application for you — scaffold, code, and run instructions, end to end:
```text
Collect the docs from https://github.com/ericbuess/claude-code-docs and build a RAG app that answers Claude Code questions as a configuration expert, citing its sources.
@@ -67,13 +67,13 @@ https://github.com/user-attachments/assets/9b7033e8-f08a-4c3f-bd33-547896664e6e
### 3. 🧬 Self-evolution: it gets stronger with use
-With PenguinHarness Skills, an Agent evaluates and optimizes itself: run the benchmark, find the lost points, ship version N+1 — with a snapshot before every round, and every request observable in the Trace view.
+With PenguinHarness Skills, an agent evaluates and optimizes itself: run the benchmark, find the lost points, ship version N+1 — with a snapshot before every round, and every request observable in the Trace view.
https://github.com/user-attachments/assets/922d13a6-5ffc-4685-9a39-352f02f9afc0
## Built-in Skills
-Four Skill groups ship in the box ([docs](https://penguin.ooo/docs/skills)); Agents can also write and optimize their own:
+Four Skill groups ship in the box ([docs](https://penguin.ooo/docs/skills)); agents can also write and optimize their own:
| Group | Skills |
| -------------------- | --------------------------------------------------------------------------------- |
@@ -110,7 +110,7 @@ Each family's latest generation only — the app's **Models** page lists every b
### 🌐 Web App — for humans
-🚀 Install and launch the full experience (multi-session chat, Agent/skill/model management, usage stats, Trace observability, evaluation center):
+🚀 Install and launch the full experience (multi-session chat, agent/skill/model management, usage stats, Trace observability, evaluation center):
```bash
curl -fsSL https://penguin.ooo/install.sh | sh
diff --git a/README.zh.md b/README.zh.md
index 89ccaf5..31e9172 100644
--- a/README.zh.md
+++ b/README.zh.md
@@ -4,9 +4,7 @@
PenguinHarness
-使用 LangChain,以 1 倍速度人工构建 Agent; 使用 PenguinHarness,以 100 倍速度用 Agent 构建 Agent。
-
-用于构建 Agent 的零代码 CLI 与 Web UI,连接 1,000+ 模型。
+全自动 Agent 构建器,住在你的桌面上,连接 1,000+ 模型。
@@ -36,9 +34,11 @@
## 为什么选择 PenguinHarness
+> 使用 LangChain,以 1 倍速度人工构建 Agent; 使用 PenguinHarness,以 100 倍速度用 Agent 构建 Agent。
+
三个递进的理由——从任务效果,到构建方式,再到进化能力。
-### 1. 🏆 效果同级,成本低一到两个数量级
+### 1. 🏆 以几十分之一的成本,跑出优异的效果
刻意精简的工具集配合干净的底层接口:更少的工具调用、更少的 Token,对 DeepSeek 等开放模型深度适配。各自搭配常用模型、同一批任务,正面对比:
diff --git a/packages/docs/src/components/nav.tsx b/packages/docs/src/components/nav.tsx
index de12832..3adddb8 100644
--- a/packages/docs/src/components/nav.tsx
+++ b/packages/docs/src/components/nav.tsx
@@ -20,7 +20,15 @@ import { GitHubIcon, MenuIcon, XIcon } from "./icons";
import { ThemeToggle } from "./theme-toggle";
import { LangToggle } from "./lang-toggle";
-const SECTION_IDS = ["highlights", "quickstart", "benchmark", "contract", "features"] as const;
+const SECTION_IDS = [
+ "highlights",
+ "quickstart",
+ "cases",
+ "scenarios",
+ "benchmark",
+ "contract",
+ "features",
+] as const;
export function Nav({ menuOpen, onToggleMenu }: { menuOpen: boolean; onToggleMenu: () => void }) {
const pillRef = useRef(null);
@@ -29,6 +37,8 @@ export function Nav({ menuOpen, onToggleMenu }: { menuOpen: boolean; onToggleMen
const sectionLabel: Record<(typeof SECTION_IDS)[number], string> = {
highlights: S.nav.highlights,
quickstart: S.nav.quickstart,
+ cases: S.nav.cases,
+ scenarios: S.nav.scenarios,
benchmark: S.nav.benchmark,
contract: S.nav.contract,
features: S.nav.features,
@@ -102,7 +112,7 @@ export function Nav({ menuOpen, onToggleMenu }: { menuOpen: boolean; onToggleMen
diff --git a/packages/docs/src/lib/strings-en.ts b/packages/docs/src/lib/strings-en.ts
index 84d8e57..966c529 100644
--- a/packages/docs/src/lib/strings-en.ts
+++ b/packages/docs/src/lib/strings-en.ts
@@ -7,6 +7,8 @@ export const en: Strings = {
nav: {
highlights: "Highlights",
quickstart: "Quick start",
+ cases: "Cases",
+ scenarios: "Scenarios",
benchmark: "Benchmark",
contract: "CONTRACT.md",
features: "Features",
diff --git a/packages/docs/src/lib/strings.ts b/packages/docs/src/lib/strings.ts
index ef294a4..67a7d57 100644
--- a/packages/docs/src/lib/strings.ts
+++ b/packages/docs/src/lib/strings.ts
@@ -13,6 +13,8 @@ export const zh = {
// (same items, same order) so the two sites link into each other seamlessly.
highlights: "特色",
quickstart: "快速开始",
+ cases: "案例",
+ scenarios: "应用场景",
benchmark: "评测",
contract: "CONTRACT.md",
features: "功能",
diff --git a/packages/landing/src/assets/case-line-inspection.webp b/packages/landing/src/assets/case-line-inspection.webp
new file mode 100644
index 0000000..672c8d0
Binary files /dev/null and b/packages/landing/src/assets/case-line-inspection.webp differ
diff --git a/packages/landing/src/assets/case-medical-qc.webp b/packages/landing/src/assets/case-medical-qc.webp
new file mode 100644
index 0000000..577711e
Binary files /dev/null and b/packages/landing/src/assets/case-medical-qc.webp differ
diff --git a/packages/landing/src/components/nav.tsx b/packages/landing/src/components/nav.tsx
index 8ba1b57..c858874 100644
--- a/packages/landing/src/components/nav.tsx
+++ b/packages/landing/src/components/nav.tsx
@@ -37,6 +37,8 @@ export function Nav() {
const sectionLabel: Record<(typeof SECTION_IDS)[number], string> = {
highlights: S.nav.highlights,
quickstart: S.nav.quickstart,
+ cases: S.nav.cases,
+ scenarios: S.nav.scenarios,
benchmark: S.nav.benchmark,
contract: S.nav.contract,
features: S.nav.features,
@@ -152,7 +154,7 @@ export function Nav() {
@@ -184,7 +186,7 @@ export function Nav() {
onClick={() => setOpen((v) => !v)}
aria-label={open ? S.nav.closeMenu : S.nav.openMenu}
aria-expanded={open}
- className="inline-flex h-9 w-9 items-center justify-center rounded-lg border border-transparent text-gray-600 transition-colors hover:border-gray-200 hover:bg-gray-50 md:hidden dark:text-gray-400 dark:hover:border-gray-800 dark:hover:bg-gray-900"
+ className="inline-flex h-9 w-9 items-center justify-center rounded-lg border border-transparent text-gray-600 transition-colors hover:border-gray-200 hover:bg-gray-50 xl:hidden dark:text-gray-400 dark:hover:border-gray-800 dark:hover:bg-gray-900"
>
{open ? : }
@@ -193,7 +195,7 @@ export function Nav() {
{open && (
{mobileLinks}
diff --git a/packages/landing/src/components/section.tsx b/packages/landing/src/components/section.tsx
index dbdd0ce..04126e8 100644
--- a/packages/landing/src/components/section.tsx
+++ b/packages/landing/src/components/section.tsx
@@ -13,7 +13,8 @@ export function Section({
id?: string;
eyebrow?: string;
title?: string;
- subtitle?: string;
+ /** A list renders one line per entry (deliberate breaks, e.g. the 1×/100× pair); a string wraps naturally. */
+ subtitle?: string | readonly string[];
children: ReactNode;
className?: string;
}) {
@@ -35,7 +36,13 @@ export function Section({
)}
{subtitle && (
- {subtitle}
+ {typeof subtitle === "string"
+ ? subtitle
+ : subtitle.map((line) => (
+
+ {line}
+
+ ))}
)}
diff --git a/packages/landing/src/lib/nav-state.ts b/packages/landing/src/lib/nav-state.ts
index 5beb17e..9d7d349 100644
--- a/packages/landing/src/lib/nav-state.ts
+++ b/packages/landing/src/lib/nav-state.ts
@@ -5,6 +5,8 @@
export const SECTION_IDS = [
"highlights",
"quickstart",
+ "cases",
+ "scenarios",
"benchmark",
"contract",
"features",
diff --git a/packages/landing/src/lib/strings-en.ts b/packages/landing/src/lib/strings-en.ts
index 67d2ded..05eba2c 100644
--- a/packages/landing/src/lib/strings-en.ts
+++ b/packages/landing/src/lib/strings-en.ts
@@ -20,6 +20,8 @@ export const en: Strings = {
nav: {
highlights: "Highlights",
quickstart: "Quick start",
+ cases: "Cases",
+ scenarios: "Scenarios",
benchmark: "Benchmark",
contract: "CONTRACT.md",
features: "Features",
@@ -45,11 +47,13 @@ export const en: Strings = {
},
hero: {
- badge: "Build AI agents, with an Agent",
- titlePrefix: "Efficient Self-Improving Harness for ",
- titleWords: ["Developers", "Enterprises"],
+ badge: "Agents building agents",
+ // The trailing space lives in the PREFIX (a breakable space before the nowrap
+ // span) — inside titleNoWrap it would glue "Builder" to the unbreakable chunk.
+ titlePrefix: "Your Automated Agent Builder ",
+ titleNoWrap: "Lives on Your ",
+ titleWords: ["Desktop", "Server"],
titleSuffix: "",
- titleSuffixNoWrap: "",
keywords: ["Lightweight", "Efficient", "Open Source"],
ctaPrimary: "Get started",
ctaGithub: "GitHub",
@@ -99,8 +103,10 @@ export const en: Strings = {
compare: {
eyebrow: "vs. LangChain",
title: "The gap between 1× and 100×",
- subtitle:
- "With LangChain, you build agents by hand — at 1× speed. With PenguinHarness, agents build agents — at 100×.",
+ subtitle: [
+ "With LangChain, you build agents by hand — at 1× speed.",
+ "With PenguinHarness, agents build agents — at 100×.",
+ ],
langchain: {
name: "LangChain",
speed: "1×",
@@ -212,6 +218,24 @@ export const en: Strings = {
],
},
+ scenarios: {
+ eyebrow: "Scenarios",
+ title: "From screening centers to factory floors",
+ subtitle: "Real deployments in production — not concept demos.",
+ items: [
+ {
+ title: "Screening-report QC",
+ alt: "A CT scanner room at a health screening center",
+ body: "A health-screening group handed report QC to an agent on a locally deployed Qwen3 14B — data never leaves the facility. A review round that took 30 minutes by hand now clears 30 reports a minute, with findings in line with medical experts and several times the review capacity.",
+ },
+ {
+ title: "Production-line inspection",
+ alt: "An automated semiconductor production line",
+ body: "A manufacturer runs inspection agents across its production lines, watching equipment around the clock and trying automated recovery first when something goes wrong. Downtime is down 65%; output roughly doubled.",
+ },
+ ],
+ },
+
contract: {
eyebrow: "A contract for stable evolution",
title: "CONTRACT.md",
@@ -266,7 +290,7 @@ export const en: Strings = {
benchmark: {
eyebrow: "Benchmark",
- title: "Each with its flagship model — comparable quality, 1-2 orders of magnitude cheaper",
+ title: "Outstanding results at tens of times less cost",
subtitle:
"Every product runs the model it is normally paired with, head-to-head against Claude Code and OpenAI Codex on two suites: comparable accuracy, tens of times the difference in spend.",
higherBetter: "higher is better",
diff --git a/packages/landing/src/lib/strings.ts b/packages/landing/src/lib/strings.ts
index 4094a46..2f6525b 100644
--- a/packages/landing/src/lib/strings.ts
+++ b/packages/landing/src/lib/strings.ts
@@ -21,6 +21,8 @@ export const zh = {
nav: {
highlights: "特色",
quickstart: "快速开始",
+ cases: "案例",
+ scenarios: "应用场景",
benchmark: "评测",
contract: "CONTRACT.md",
features: "功能",
@@ -48,14 +50,15 @@ export const zh = {
hero: {
badge: "让 Agent 为你构建 Agent",
/**
- * Rotating headline: {titlePrefix}{word}{titleSuffix}{titleSuffixNoWrap} — the
- * word cycles through titleWords with a gaussian-blur crossfade; titleSuffixNoWrap
- * renders as an unbreakable span so CJK line-breaking lands on the phrase boundary.
+ * Headline: {titlePrefix}{titleNoWrap}{word}{titleSuffix} , the word
+ * rotating through titleWords (桌面/服务器 — where the builder lives; the README
+ * states Desktop only). The nowrap span pins the wrap point in front of it, so a
+ * break never splits "Agent Builder" / "Lives on Your Desktop" mid-phrase.
*/
- titlePrefix: "专为",
- titleWords: ["开发者", "企业"],
- titleSuffix: "设计的",
- titleSuffixNoWrap: "高效自进化 Harness",
+ titlePrefix: "全自动 Agent 构建器,",
+ titleNoWrap: "住在你的",
+ titleWords: ["桌面", "服务器"],
+ titleSuffix: "上",
keywords: ["轻量", "高效", "开源"],
ctaPrimary: "快速开始",
ctaGithub: "GitHub",
@@ -103,8 +106,10 @@ export const zh = {
compare: {
eyebrow: "对比 LangChain",
title: "1× 与 100× 的差距",
- subtitle:
- "使用 LangChain,以 1 倍速度人工构建 Agent;使用 PenguinHarness,以 100 倍速度用 Agent 构建 Agent。",
+ subtitle: [
+ "使用 LangChain,以 1 倍速度人工构建 Agent;",
+ "使用 PenguinHarness,以 100 倍速度用 Agent 构建 Agent。",
+ ],
langchain: {
name: "LangChain",
speed: "1×",
@@ -208,6 +213,24 @@ export const zh = {
],
},
+ scenarios: {
+ eyebrow: "应用场景",
+ title: "从体检中心到工厂车间",
+ subtitle: "都是已经上线的真实部署,不是概念演示。",
+ items: [
+ {
+ title: "体检报告质控",
+ alt: "体检中心的 CT 检查室",
+ body: "一家体检机构把报告质控交给了 Agent,跑在本地部署的 Qwen3 14B 上,数据不出机房。过去一轮人工复核要 30 分钟,现在一分钟自动过 30 份,核查结论与医学专家基本一致,审核产能提升数倍。",
+ },
+ {
+ title: "产线设备巡检",
+ alt: "自动化半导体生产线",
+ body: "一家制造企业在各条流水线上部署巡检 Agent,全天候盯着设备状态,异常时先尝试自动恢复。停机时间减少 65%,产出提升约 2 倍。",
+ },
+ ],
+ },
+
contract: {
eyebrow: "稳定进化的契约",
title: "CONTRACT.md",
@@ -260,7 +283,7 @@ export const zh = {
benchmark: {
eyebrow: "Benchmark",
- title: "各用各的旗舰模型,效果同级,成本低一到两个数量级",
+ title: "以几十分之一的成本,跑出优异的效果",
subtitle:
"每个产品搭配它常用的模型,与 Claude Code、OpenAI Codex 在两套题库上正面对比:准确率同级,花的钱差出几十倍。",
higherBetter: "越高越好",
diff --git a/packages/landing/src/pages/home.tsx b/packages/landing/src/pages/home.tsx
index 742b770..cf83f8a 100644
--- a/packages/landing/src/pages/home.tsx
+++ b/packages/landing/src/pages/home.tsx
@@ -5,6 +5,7 @@ import { Compare } from "../sections/compare";
import { SelfImprove } from "../sections/self-improve";
import { Quickstart } from "../sections/quickstart";
import { Cases } from "../sections/cases";
+import { Scenarios } from "../sections/scenarios";
import { Benchmark } from "../sections/benchmark";
import { Contract } from "../sections/contract";
import { Features } from "../sections/features";
@@ -22,6 +23,7 @@ export function HomePage() {
+
diff --git a/packages/landing/src/sections/hero.tsx b/packages/landing/src/sections/hero.tsx
index 6ae87fd..9a3803e 100644
--- a/packages/landing/src/sections/hero.tsx
+++ b/packages/landing/src/sections/hero.tsx
@@ -1,8 +1,8 @@
/**
- * Hero: enlarged logo + product name, the slogan inside the pill badge, then a
- * bilingual headline whose rotating word crossfades through a gaussian blur
- * (Developers <-> Enterprises), three keywords, the install one-liner and stats.
- * The rotating word is a stacked inline-grid so line width never jumps.
+ * Hero: enlarged logo + product name, the slogan inside the pill badge, then the
+ * one-line headline whose rotating word crossfades through a gaussian blur
+ * (Desktop <-> Server / 桌面 <-> 服务器), three keywords, the install one-liner
+ * and stats. The rotating word is a stacked inline-grid so line width never jumps.
*/
import { Fragment, useEffect, useState } from "react";
import { Link } from "react-router";
@@ -57,16 +57,19 @@ export function Hero() {
{S.hero.badge}
+ {/* No text-balance: balance may break inside the breakable prefix ("…Agent /
+ Builder…"); greedy wrapping + the nowrap span pins the desktop break to
+ "Your Automated Agent Builder / Lives on Your Desktop". */}
{S.hero.titlePrefix}
-
- {S.hero.titleSuffix}
- {S.hero.titleSuffixNoWrap && (
- {S.hero.titleSuffixNoWrap}
- )}
+
+ {S.hero.titleNoWrap}
+
+ {S.hero.titleSuffix}
+
{
+ if (paused || items.length < 2) return;
+ if (window.matchMedia("(prefers-reduced-motion: reduce)").matches) return;
+ const timer = setInterval(() => setActive((i) => (i + 1) % items.length), ROTATE_MS);
+ return () => clearInterval(timer);
+ }, [paused, items.length]);
+
+ return (
+
+ setPaused(true)}
+ onMouseLeave={() => setPaused(false)}
+ >
+
+ {items.map((c, i) => {
+ const front = i === active;
+ return (
+
setActive(i)}
+ // Cards are 92% wide in a shared cell: the front one holds the left
+ // edge; the covered one shifts its own 8.7% (= the container's 8%)
+ // so exactly that strip peeks past the front card's right edge.
+ className={`col-start-1 row-start-1 w-[92%] overflow-hidden rounded-2xl border bg-white transition-all duration-500 dark:bg-gray-900 ${
+ front
+ ? "z-10 translate-x-0 border-gray-200 opacity-100 dark:border-gray-800"
+ : "z-0 translate-x-[8.7%] scale-[0.96] cursor-pointer border-gray-200/70 opacity-60 dark:border-gray-800/70"
+ }`}
+ >
+
+
+
+ {c.title}
+
+ {c.body}
+
+
+
+
+ );
+ })}
+
+
+ {/* Manual switch doubles as the position indicator */}
+
+ {items.map((c, i) => (
+ setActive(i)}
+ className={`h-1.5 rounded-full transition-all duration-300 ${
+ i === active
+ ? "w-6 bg-gray-800 dark:bg-gray-200"
+ : "w-3 bg-gray-300 hover:bg-gray-400 dark:bg-gray-700 dark:hover:bg-gray-500"
+ }`}
+ />
+ ))}
+
+
+
+ );
+}