Skip to content

Commit 5c145cc

Browse files
committed
feat(website): redesign landing/docs with Composio-style visuals
- 字体换为 Inter + JetBrains Mono,替换原 Geist/Instrument Serif 方案 - landing.css/docs.css 配色 token 由暖棕黑 Cursor 风格改为 Composio 风格 - 新增顶部滚动进度条、Hero 均衡器装饰条(轻量视差) - 滚动渐显动画支持 [data-stagger] 容器内子项错峰出现 - 精简 hero 侧边快捷键提示卡与服务商标签页动画等冗余交互
1 parent 4f89c20 commit 5c145cc

5 files changed

Lines changed: 1480 additions & 2017 deletions

File tree

website/src/layouts/DocsLayout.astro

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ const editUrl = `${repo}/blob/main/website/src/content/docs/${activeSlug}.mdx`;
4343
<link rel="icon" type="image/svg+xml" href="/logo.svg" />
4444
<link rel="preconnect" href="https://fonts.googleapis.com" />
4545
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
46-
<link href="https://fonts.googleapis.com/css2?family=Geist:wght@300;400;500;600;700&family=Geist+Mono:wght@400;500;600&display=swap" rel="stylesheet" />
46+
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500;600;700&display=swap" rel="stylesheet" />
4747
<link rel="stylesheet" href="/pagefind/pagefind-ui.css" />
4848
<Analytics />
4949
{/* FOUC 防护 */}

website/src/layouts/LandingLayout.astro

Lines changed: 28 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ const {
2525
<link rel="icon" type="image/svg+xml" href="/logo.svg" />
2626
<link rel="preconnect" href="https://fonts.googleapis.com" />
2727
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
28-
<link href="https://fonts.googleapis.com/css2?family=Instrument+Serif:ital@0;1&family=Inter+Tight:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500;600&display=swap" rel="stylesheet" />
28+
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500;600;700&display=swap" rel="stylesheet" />
2929
<Analytics />
3030
{/* FOUC 防护:在页面渲染前先设好 theme 与 locale */}
3131
<script is:inline>
@@ -50,6 +50,7 @@ const {
5050
</script>
5151
</head>
5252
<body data-locale="en">
53+
<div class="scroll-progress" aria-hidden="true"><div id="scroll-progress"></div></div>
5354
<I18nScript />
5455
<Banner
5556
en="Early development · An independent product built on Warp · Not affiliated with Warp Inc."
@@ -177,35 +178,6 @@ const {
177178
});
178179
});
179180

180-
// 服务商标签页与动画轨道
181-
document.querySelectorAll<HTMLElement>('.prov-tabs').forEach((tabs) => {
182-
const rail = tabs.querySelector<HTMLElement>('[data-prov-rail]');
183-
const moveRail = (b: HTMLButtonElement) => {
184-
if (!rail) return;
185-
const r = b.getBoundingClientRect();
186-
const t = tabs.getBoundingClientRect();
187-
rail.style.width = `${r.width}px`;
188-
rail.style.transform = `translateX(${r.left - t.left - 4}px)`;
189-
};
190-
const buttons = tabs.querySelectorAll<HTMLButtonElement>('button');
191-
const initial = tabs.querySelector<HTMLButtonElement>('button.active') ?? buttons[0];
192-
if (initial) requestAnimationFrame(() => moveRail(initial));
193-
window.addEventListener('resize', () => {
194-
const a = tabs.querySelector<HTMLButtonElement>('button.active');
195-
if (a) moveRail(a);
196-
});
197-
buttons.forEach((b) => {
198-
b.addEventListener('click', () => {
199-
const name = b.getAttribute('data-pf');
200-
buttons.forEach((x) => x.classList.toggle('active', x === b));
201-
moveRail(b);
202-
document.querySelectorAll<HTMLElement>('[data-pf-pane]').forEach((p) => {
203-
p.hidden = p.getAttribute('data-pf-pane') !== name;
204-
});
205-
});
206-
});
207-
});
208-
209181
// 安装方式标签页
210182
document.querySelectorAll<HTMLElement>('.install-tabs').forEach((tabs) => {
211183
tabs.querySelectorAll<HTMLButtonElement>('button').forEach((b) => {
@@ -235,19 +207,41 @@ const {
235207
});
236208
});
237209

238-
// 滚动渐显
210+
// 滚动渐显(支持 [data-stagger] 容器内子项依次错峰出现)
239211
const io = new IntersectionObserver(
240212
(entries) => {
241213
entries.forEach((e) => {
242-
if (e.isIntersecting) {
243-
e.target.classList.add('in');
244-
io.unobserve(e.target);
214+
if (!e.isIntersecting) return;
215+
const target = e.target as HTMLElement;
216+
if (target.hasAttribute('data-stagger')) {
217+
target.classList.add('in');
218+
const items = Array.from(target.querySelectorAll<HTMLElement>('.stagger-item')).filter((el) => el.offsetParent !== null || el.getClientRects().length > 0);
219+
items.forEach((el, i) => {
220+
el.style.transitionDelay = `${Math.min(i * 70, 420)}ms`;
221+
el.classList.add('in');
222+
});
223+
} else {
224+
target.classList.add('in');
245225
}
226+
io.unobserve(target);
246227
});
247228
},
248229
{ rootMargin: '0px 0px -10% 0px', threshold: 0.05 },
249230
);
250231
document.querySelectorAll('.fade-in').forEach((el) => io.observe(el));
232+
233+
// 顶部滚动进度条
234+
const progress = document.getElementById('scroll-progress');
235+
if (progress) {
236+
const updateProgress = () => {
237+
const h = document.documentElement;
238+
const max = h.scrollHeight - h.clientHeight;
239+
const pct = max > 0 ? (h.scrollTop / max) * 100 : 0;
240+
progress.style.width = `${pct}%`;
241+
};
242+
document.addEventListener('scroll', updateProgress, { passive: true });
243+
updateProgress();
244+
}
251245
})();
252246
</script>
253247
</body>

website/src/pages/index.astro

Lines changed: 108 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -85,24 +85,49 @@ const providerChips = ['302.AI (97)', 'Abacus (65)', 'abliteration.ai (1)', 'AIH
8585
8686
// Logos(信赖墙)— 我们没有真实合作伙伴,改成"原生支持的协议名"
8787
const protocols = ['OpenAI', 'Anthropic', 'Gemini', 'Ollama', 'DeepSeek', 'OpenAI-Response'];
88+
89+
// Hero 装饰性均衡器条 — 纯视觉,数值确定性生成(避免 hydration 不一致)
90+
const eqColors = ['var(--grad-1)', 'var(--grad-2)', 'var(--grad-3)', 'var(--grad-4)'];
91+
function makeEqBars(count: number, seed: number) {
92+
return Array.from({ length: count }, (_, i) => {
93+
const h = 22 + Math.round(Math.abs(Math.sin(i * 0.7 + seed)) * 70);
94+
const d = (i * 0.09 + seed * 0.13) % 1.6;
95+
const c = eqColors[(i + seed) % eqColors.length];
96+
return { h, d: d.toFixed(2), c };
97+
});
98+
}
99+
const eqLeft = makeEqBars(16, 0);
100+
const eqRight = makeEqBars(16, 2);
88101
---
89102
<LandingLayout>
90103
<!-- ============== HERO ============== -->
91104
<section class="hero">
105+
<div class="hero-eq" aria-hidden="true">
106+
<div class="col left">
107+
{eqLeft.map((b) => (
108+
<span class="bar" style={`--bar-h:${b.h}%; --bar-d:${b.d}s; --bar-c:${b.c};`}></span>
109+
))}
110+
</div>
111+
<div class="col right">
112+
{eqRight.map((b) => (
113+
<span class="bar" style={`--bar-h:${b.h}%; --bar-d:${b.d}s; --bar-c:${b.c};`}></span>
114+
))}
115+
</div>
116+
</div>
92117
<div class="wrap hero-grid">
93-
<div class="fade-in">
94-
<span class="hero-badge"><span class="pulse-dot"></span>
118+
<div class="fade-in" data-stagger>
119+
<span class="hero-badge stagger-item"><span class="pulse-dot"></span>
95120
<span data-lang="en">{t.hero.badge.en}</span><span data-lang="zh">{t.hero.badge.zh}</span><span data-lang="ja">{t.hero.badge.ja}</span>
96121
</span>
97-
<h1>
122+
<h1 class="stagger-item">
98123
<span data-lang="en">{t.hero.h1a.en}<br/><span class="em">{t.hero.h1b.en}</span></span>
99124
<span data-lang="zh">{t.hero.h1a.zh}<br/><span class="em">{t.hero.h1b.zh}</span></span>
100125
<span data-lang="ja">{t.hero.h1a.ja}<br/><span class="em">{t.hero.h1b.ja}</span></span>
101126
</h1>
102-
<p class="lede" data-lang="en">{t.hero.lede.en}</p>
103-
<p class="lede" data-lang="zh">{t.hero.lede.zh}</p>
104-
<p class="lede" data-lang="ja">{t.hero.lede.ja}</p>
105-
<div class="hero-cta">
127+
<p class="lede stagger-item" data-lang="en">{t.hero.lede.en}</p>
128+
<p class="lede stagger-item" data-lang="zh">{t.hero.lede.zh}</p>
129+
<p class="lede stagger-item" data-lang="ja">{t.hero.lede.ja}</p>
130+
<div class="hero-cta stagger-item">
106131
<a class="btn" href="#install">
107132
<span data-lang="en">{t.hero.primary.en}</span><span data-lang="zh">{t.hero.primary.zh}</span><span data-lang="ja">{t.hero.primary.ja}</span>
108133
<span class="arr">→</span>
@@ -147,33 +172,7 @@ const protocols = ['OpenAI', 'Anthropic', 'Gemini', 'Ollama', 'DeepSeek', 'OpenA
147172
</div>
148173
</div>
149174
<div class="app-body">
150-
<!-- 左侧:快捷键提示卡 + 状态栏 -->
151-
<div class="hero-side">
152-
<div class="hero-hint">
153-
<div class="hh-title">
154-
<svg class="ico" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.6"><path d="M3 11l4-4 4 4M3 7l4-4 4 4"/></svg>
155-
<span data-lang="en">New terminal session</span><span data-lang="zh">新建终端会话</span><span data-lang="ja">新しいセッション</span>
156-
</div>
157-
<div class="hh-row">
158-
<span class="kbds"><span class="kbd">ctrl</span><span class="kbd">shift</span><span class="kbd">↩</span></span>
159-
<span data-lang="en">New Agent chat</span><span data-lang="zh">新 Agent 对话</span><span data-lang="ja">新しい Agent 会話</span>
160-
</div>
161-
<div class="hh-row">
162-
<span class="kbds"><span class="kbd">↑</span></span>
163-
<span data-lang="en">History</span><span data-lang="zh">历史记录</span><span data-lang="ja">履歴</span>
164-
</div>
165-
<div class="hh-row">
166-
<span class="kbds"><span class="kbd">⌘</span><span class="kbd">K</span></span>
167-
<span data-lang="en">Command palette</span><span data-lang="zh">命令面板</span><span data-lang="ja">コマンドパレット</span>
168-
</div>
169-
</div>
170-
<div class="hero-status">
171-
<span class="ok">anthropic / claude-4-7</span>
172-
<span>local · no telemetry</span>
173-
</div>
174-
</div>
175-
176-
<!-- 右侧:终端输出 + 可执行输入 -->
175+
<!-- 单栏 Block 时间线:终端输出 + AI 对话 + 可交互输入 -->
177176
<div class="hero-main">
178177
<div class="term-stage"></div>
179178
<div class="term-input live">
@@ -216,19 +215,19 @@ const protocols = ['OpenAI', 'Anthropic', 'Gemini', 'Ollama', 'DeepSeek', 'OpenA
216215
</section>
217216

218217
<!-- ============== LOGOS / PROTOCOLS ============== -->
219-
<div class="logos fade-in">
220-
<p class="logos-title">
218+
<div class="logos fade-in" data-stagger>
219+
<p class="logos-title stagger-item">
221220
<span data-lang="en">{t.logos.title.en}</span><span data-lang="zh">{t.logos.title.zh}</span><span data-lang="ja">{t.logos.title.ja}</span>
222221
</p>
223222
<div class="logos-track">
224-
{protocols.map((p) => <span class="item">{p}</span>)}
223+
{protocols.map((p) => <span class="item stagger-item">{p}</span>)}
225224
</div>
226225
</div>
227226

228227
<!-- ============== FEAT 1: 终端 ============== -->
229228
<section class="section" id="how">
230229
<div class="wrap">
231-
<article class="feat-card fade-in">
230+
<article class="feat-card fade-in slide-l">
232231
<div class="feat-text">
233232
<div class="feat-eyebrow">
234233
<span data-lang="en">{t.feat1.eyebrow.en}</span><span data-lang="zh">{t.feat1.eyebrow.zh}</span><span data-lang="ja">{t.feat1.eyebrow.ja}</span>
@@ -281,7 +280,7 @@ const protocols = ['OpenAI', 'Anthropic', 'Gemini', 'Ollama', 'DeepSeek', 'OpenA
281280
<!-- ============== FEAT 2: 服务商(reverse 布局) ============== -->
282281
<section class="section" id="adapt">
283282
<div class="wrap">
284-
<article class="feat-card reverse fade-in">
283+
<article class="feat-card reverse fade-in slide-r">
285284
<div class="feat-text">
286285
<div class="feat-eyebrow">
287286
<span data-lang="en">{t.feat2.eyebrow.en}</span><span data-lang="zh">{t.feat2.eyebrow.zh}</span><span data-lang="ja">{t.feat2.eyebrow.ja}</span>
@@ -391,7 +390,7 @@ const protocols = ['OpenAI', 'Anthropic', 'Gemini', 'Ollama', 'DeepSeek', 'OpenA
391390
<!-- ============== FEAT 3: 本地化 ============== -->
392391
<section class="section" id="privacy">
393392
<div class="wrap">
394-
<article class="feat-card fade-in">
393+
<article class="feat-card fade-in slide-l">
395394
<div class="feat-text">
396395
<div class="feat-eyebrow">
397396
<span data-lang="en">{t.feat3.eyebrow.en}</span><span data-lang="zh">{t.feat3.eyebrow.zh}</span><span data-lang="ja">{t.feat3.eyebrow.ja}</span>
@@ -459,7 +458,7 @@ const protocols = ['OpenAI', 'Anthropic', 'Gemini', 'Ollama', 'DeepSeek', 'OpenA
459458
</section>
460459

461460
<!-- ============== QUOTE(单条) ============== -->
462-
<section class="quote fade-in">
461+
<section class="quote fade-in scale-in">
463462
<div class="wrap">
464463
<blockquote data-lang="en">{t.quote.text.en}</blockquote>
465464
<blockquote data-lang="zh">{t.quote.text.zh}</blockquote>
@@ -566,24 +565,47 @@ git clone https://github.com/zerx-lab/zap.git zap
566565

567566
// 初始动画行(页面刚加载时) — 按当前 locale 渲染
568567
// 命令本身保留英文(真实 shell 也是英文),只本地化人类语言部分
568+
// 叙事:构建失败 → AI 用 block 上下文(cwd/exit code)诊断并给出修复建议
569+
// 呼应 feat1 文案 "Every Block — cwd, env, exit code — feeds an AI conversation"
569570
const initLinesByLocale: Record<'en' | 'zh' | 'ja', string[]> = {
570571
en: [
571-
`<span class="p">$</span> npm run build`,
572-
`<span class="dim">› vite v5.4 · production</span>`,
573-
`<span class="dim">› 248 modules transformed</span>`,
574-
`<span class="ok">✓ built in 1.24s</span>`,
572+
`<span class="p">$</span> cargo build --release`,
573+
`<span class="dim"> Compiling zap v0.1.0</span>`,
574+
`<span class="err">error[E0433]: failed to resolve: use of undeclared crate \`openssl\`</span>`,
575+
`<span class="err"> --&gt; src/tls.rs:4:5</span>`,
576+
`<div class="stage-turn">
577+
<div class="st-chip">block · cwd ~/zap · exit 101</div>
578+
<div class="st-prompt">▌ Why did this fail?</div>
579+
<div class="st-think">Reading block context <span class="dots"><span></span><span></span><span></span></span></div>
580+
<div class="st-answer"><code>openssl</code> isn't in Cargo.toml. Run <code>cargo add openssl --features vendored</code>, then rebuild.</div>
581+
<div class="st-meta"><span class="ok">anthropic / claude-opus-4-7</span><span>local · no telemetry</span></div>
582+
</div>`,
575583
],
576584
zh: [
577-
`<span class="p">$</span> npm run build`,
578-
`<span class="dim">› vite v5.4 · 生产构建</span>`,
579-
`<span class="dim">› 已转换 248 个模块</span>`,
580-
`<span class="ok">✓ 1.24s 内构建完成</span>`,
585+
`<span class="p">$</span> cargo build --release`,
586+
`<span class="dim"> 正在编译 zap v0.1.0</span>`,
587+
`<span class="err">error[E0433]: 无法解析:未声明的 crate \`openssl\`</span>`,
588+
`<span class="err"> --&gt; src/tls.rs:4:5</span>`,
589+
`<div class="stage-turn">
590+
<div class="st-chip">block · cwd ~/zap · exit 101</div>
591+
<div class="st-prompt">▌ 为什么会失败?</div>
592+
<div class="st-think">正在读取 block 上下文 <span class="dots"><span></span><span></span><span></span></span></div>
593+
<div class="st-answer">Cargo.toml 里没有 <code>openssl</code>。运行 <code>cargo add openssl --features vendored</code> 后重新构建即可。</div>
594+
<div class="st-meta"><span class="ok">anthropic / claude-opus-4-7</span><span>本地 · 无遥测</span></div>
595+
</div>`,
581596
],
582597
ja: [
583-
`<span class="p">$</span> npm run build`,
584-
`<span class="dim">› vite v5.4 · 本番ビルド</span>`,
585-
`<span class="dim">› 248 モジュールを変換</span>`,
586-
`<span class="ok">✓ 1.24 秒でビルド完了</span>`,
598+
`<span class="p">$</span> cargo build --release`,
599+
`<span class="dim"> zap v0.1.0 をコンパイル中</span>`,
600+
`<span class="err">error[E0433]: 解決できません: 宣言されていないクレート \`openssl\`</span>`,
601+
`<span class="err"> --&gt; src/tls.rs:4:5</span>`,
602+
`<div class="stage-turn">
603+
<div class="st-chip">block · cwd ~/zap · exit 101</div>
604+
<div class="st-prompt">▌ なぜ失敗したの?</div>
605+
<div class="st-think">block コンテキストを読み取り中 <span class="dots"><span></span><span></span><span></span></span></div>
606+
<div class="st-answer">Cargo.toml に <code>openssl</code> がありません。<code>cargo add openssl --features vendored</code> を実行して再ビルドしてください。</div>
607+
<div class="st-meta"><span class="ok">anthropic / claude-opus-4-7</span><span>ローカル · テレメトリなし</span></div>
608+
</div>`,
587609
],
588610
};
589611
function renderInitLines() {
@@ -871,3 +893,36 @@ git clone https://github.com/zerx-lab/zap.git zap
871893
localeObserver.observe(document.body, { attributes: true, attributeFilter: ['data-locale'] });
872894
})();
873895
</script>
896+
897+
<script>
898+
// Hero 装饰性均衡器条 — 轻量视差(仅位移,GPU 合成,不触发 layout)
899+
(function () {
900+
const eq = document.querySelector<HTMLElement>('.hero-eq');
901+
if (!eq) return;
902+
const reduceMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
903+
if (reduceMotion) return;
904+
const cols = Array.from(eq.querySelectorAll<HTMLElement>('.col'));
905+
let ticking = false;
906+
function apply() {
907+
const y = window.scrollY;
908+
const heroH = (eq.closest('.hero') as HTMLElement | null)?.offsetHeight || 800;
909+
const progress = Math.min(y / heroH, 1);
910+
cols.forEach((col, i) => {
911+
const dir = i % 2 === 0 ? 1 : -1;
912+
col.style.transform = `translateY(${progress * 40 * dir}px)`;
913+
});
914+
ticking = false;
915+
}
916+
document.addEventListener(
917+
'scroll',
918+
() => {
919+
if (!ticking) {
920+
requestAnimationFrame(apply);
921+
ticking = true;
922+
}
923+
},
924+
{ passive: true },
925+
);
926+
apply();
927+
})();
928+
</script>

0 commit comments

Comments
 (0)