
Skill Site Generator
- 3 installs
- 4 repo stars
- Updated April 10, 2026
- eze-is/skill-site-generator
Helps with ai & agent building tasks.
About
skill-site-generator is a Claude Code skill for ai & agent building. It helps solo builders move faster with AI-assisted development.
- skill-site-generator
- AI & Agent Building
- AI-coding skill
Skill Site Generator by the numbers
- 3 all-time installs (skills.sh)
- Ranked #13,657 of 16,546 AI & Agent Building skills by installs in the Skillselion catalog
- Data as of Jul 13, 2026 (Skillselion catalog sync)
npx skills add https://github.com/eze-is/skill-site-generator --skill skill-site-generatorAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 3 |
|---|---|
| repo stars | ★ 4 |
| Last updated | April 10, 2026 |
| Repository | eze-is/skill-site-generator ↗ |
What it does
Helps with ai & agent building tasks.
Files
skill-site-generator
为 SKILL.md 格式的 Agent Skill 生成 landing page 并部署到 GitHub Pages。
内容生成哲学
网站的目标读者是第一次看到这个 skill 的开发者。他不知道这个 skill 存在,不知道它解决什么问题,不知道该不该装。
内容的组织逻辑是:问题 → 方案 → 效果 → 安装,不是功能罗列。先让读者认出自己的痛点,再展示解决方案。
每个 skill 的复杂度不同。区块数量和深度应该匹配 skill 本身的复杂度,不是所有 skill 都套满所有区块。
执行流程
1. 确定目标与前置检查
用户给出 GitHub 仓库地址、本地 SKILL.md 路径、或 skill 名称。需要拿到:SKILL.md 全文 和 GitHub owner/repo。
前置条件:skill 必须有公开的 GitHub 仓库。 官网的存在意义是让别人发现并安装这个 skill——没有公开仓库,安装命令无处指向,官网就没有意义。
如果 skill 还没有仓库,先帮用户创建并确保兼容 npx skills 生态:
1. 确认 skill 目录中有 SKILL.md(必需)和相关文件 2. 确认 SKILL.md 在仓库根目录或可被 npx skills 发现的子目录中(npx skills 会扫描仓库中所有 SKILL.md) 3. gh repo create {skill-name} --public --source=. --push 4. 确认仓库可访问后继续
创建完成后,用户的 skill 即可通过 npx skills add {owner}/{skill-name} 安装,且会自动出现在 skills.sh 目录中。
2. 读取与理解
ReadSKILL.md 全文gh api repos/{owner}/{repo}拿 stars、forks- 通读 SKILL.md,理解这个 skill 做什么、怎么工作、解决什么问题
3. 生成网站
加载 references/content-guide.md 获取内容思考框架。
加载 templates/reference.html 作为质量标准和结构参考——这是一个完整的、效果好的 skill 官网实例(web-access)。以它为范本:
- 保持相同的 HTML 结构和 CSS class 名
- 保持相同的视觉质量和文案水平
- 根据目标 skill 的实际情况增删区块
直接 Write 生成 index.html,引用外部 style.css 和 script.js:
style.css:从templates/style.css复制,不修改script.js:基于templates/script-base.js,替换其中的i18n对象和demos数组为目标 skill 的内容
i18n 机制:所有文案元素加 data-i18n 属性,script.js 中的 i18n 对象包含 en 和 zh 两套翻译。
4. 生成辅助资源
node "${CLAUDE_SKILL_DIR}/scripts/generate-assets.mjs" \
--name <skill名称> --repo <owner/repo> --color <品牌色> --out <输出目录> \
--subtitle "一句话描述" --author "作者名" --stars "数量" --forks "数量"脚本生成:favicon SVG/PNG、OG 社交分享图、robots.txt、sitemap.xml。--subtitle、--author、--stars、--forks 可选,用于丰富 OG 图内容。
5. 预览
在浏览器中打开生成的网站,截图展示给用户确认。可用 web-access 的 CDP 能力预览,或提示用户自行打开 file:// 路径。
6. 部署
node "${CLAUDE_SKILL_DIR}/scripts/deploy.mjs" --repo <owner/repo> --dir <输出目录>通过 gh CLI 推送到 gh-pages 分支,开启 GitHub Pages。自定义域名:用户指定时生成 CNAME 文件一并推送。
输出目录结构
<output>/
index.html ← AI 直接生成
style.css ← 从模板复制(固定)
script.js ← AI 基于模板生成(填入 i18n + demos)
favicon.svg ← 脚本生成
favicon-*.png ← 脚本生成(sharp 可用时)
og.png ← 脚本生成
robots.txt ← 脚本生成
sitemap.xml ← 脚本生成References
| 文件 | 何时加载 |
|---|---|
references/content-guide.md | Step 3 开始前,获取内容思考框架 |
templates/reference.html | Step 3 开始前,作为 HTML 结构和文案质量参考 |
templates/style.css | Step 3 复制到输出目录 |
templates/script-base.js | Step 3 作为 JS 基础,填入动态数据 |
skill-site-generator
为任意 SKILL.md 格式的 Agent Skill 生成 landing page 并部署到 GitHub Pages。
给你的 skill 做一个官网,只需一句话。
[官网](https://eze-is.github.io/skill-site-generator/) — 这个页面本身就是用 skill-site-generator 生成的。
效果示例
- skill-site-generator 官网(本 skill 自己的官网,由自己生成)
- web-access.eze.is(web-access skill 的官网)
安装
npx skills add eze-is/skill-site-generator使用
安装后,直接告诉你的 Agent:
给 web-access skill 做一个官网帮我给 https://github.com/xxx/my-skill 生成一个 landing page 并部署Agent 会自动完成:读取 SKILL.md → 理解 skill 定位 → 生成网站 → 预览 → 部署到 GitHub Pages。
工作原理
| 步骤 | 做什么 | 谁做 |
|---|---|---|
| 1. 前置检查 | 确认 skill 有公开 GitHub 仓库 | AI |
| 2. 读取理解 | 通读 SKILL.md,拿 stars/forks | AI |
| 3. 生成网站 | 写 HTML + 填充 i18n 双语 + 终端演示动画 | AI |
| 4. 生成资源 | favicon、OG 图、SEO 文件 | 脚本 |
| 5. 预览 | 浏览器打开,截图确认 | AI |
| 6. 部署 | 推送 gh-pages,开启 GitHub Pages | 脚本 |
设计理念
- AI 做 AI 擅长的:理解内容、写文案、生成 HTML
- 脚本做脚本擅长的:图片处理、git 部署
- 不过度工程化:没有 JSON 中间层、没有模板引擎,AI 直接对着一个好的参考范本写 HTML
网站内容的组织逻辑是 问题 → 方案 → 效果 → 安装,不是功能罗列。AI 会根据 skill 复杂度自动决定保留哪些区块。
生成的网站包含
- 中英双语切换(自动检测浏览器语言)
- Hero 区 + 终端演示动画(双语)
- Problem 对比区(Without / With)
- 架构图(可选,适用于有独立 runtime 的 skill)
- 能力卡片(4-6 张)
- API 端点展示(可选)
- 安装步骤
- FAQ
- 完整 SEO(Open Graph、Twitter Card、JSON-LD、sitemap)
- Favicon + 社交分享图
前置要求
ghCLI 已登录(用于部署到 GitHub Pages)- skill 必须有公开的 GitHub 仓库(没有的话 Agent 会帮你创建)
目录结构
skill-site-generator/
├── SKILL.md # 流程定义 + 内容生成哲学
├── references/
│ └── content-guide.md # AI 内容生成思考框架
├── templates/
│ ├── reference.html # 质量标准(web-access 官网实例)
│ ├── style.css # 固定样式系统
│ └── script-base.js # i18n + 动画逻辑
└── scripts/
├── generate-assets.mjs # favicon / OG 图 / SEO 生成
└── deploy.mjs # GitHub Pages 部署License
MIT · 作者:一泽 Eze
内容生成思考框架
读完目标 SKILL.md 后,按以下框架生成网站内容。不是每个区块都必须有——根据 skill 复杂度决定保留哪些。
输出格式为 JSON,字段见 template-spec.md。
先判断 skill 类型
| 类型 | 特征 | 建议区块 |
|---|---|---|
| 轻量型 | 纯 prompt 增强,无独立 runtime | Hero + Problem + Capabilities + Install + FAQ |
| 中等型 | 有脚本/工具,但架构简单 | Hero + Problem + How + Capabilities + Install + FAQ |
| 重型 | 有独立 runtime、API、多组件协作 | 全部区块 |
各区块思考标准
Hero
从 SKILL.md 的 description 出发,提炼一句话让从没听过这个 skill 的人立刻知道它是干什么的。
- 标题:skill 名称
- 副标题:一句话,动词开头,说清楚这个 skill 给 agent 带来什么能力。不用"强大的"、"全面的"等虚词
- 描述:2-3 句补充说明,具体到技术手段或使用场景
Problem(为什么需要它)
这个区块的目标是让读者在 3 秒内认出自己的痛点。
思考方式:没有这个 skill 时,用户/agent 在相关场景下会遇到什么具体问题? 不是抽象的"不够好",而是具体的"做不到 X"、"每次都要 Y"。
输出为 Without / With 对比,各 3-6 条。每条是一个可感知的场景,不是功能描述的反面。
How it Works(架构/原理)
只在 skill 有独立 runtime 或多组件协作时才需要这个区块。 如果 skill 只是一个 SKILL.md 文件,没有脚本、没有服务,跳过。
简化为 2-4 个节点的流程:每个节点用名词,标注连接方式。下方提炼 2-3 个核心动词概括 skill 的操作模式。
Capabilities(能力卡片)
从 SKILL.md 中提取 skill 能做的所有事情,归类为 4-6 张卡片。
每张卡片:
- 标题:2-3 个词的能力名
- 描述:1-2 句话说清楚做什么、怎么做
- 示例:一个用户可能说出的自然语言 prompt
卡片之间不应重叠。如果 skill 能力单一(比如只做一件事),减少到 2-3 张,拆分使用场景而非硬凑功能。
API / Commands(可选)
如果 skill 暴露了 API 端点或命令列表,以卡片网格展示。每张卡片:方法/命令名 + 一句话描述。
只展示用户直接接触的接口,不展示内部实现细节。skill 没有外部 API 则跳过。
Install(安装)
固定结构,3 步: 1. npx skills add {owner}/{repo} 2. 前置配置(从 SKILL.md 中提取,如果有) 3. 开始使用
如果 skill 没有前置配置,第 2 步改为"无需额外配置"。
FAQ
站在第一次看到这个 skill 的开发者角度:他会担心什么?会困惑什么?
5-6 条,应覆盖:
- 安全性/副作用(会不会搞坏我的环境)
- 兼容性(支持哪些 agent)
- 局限性(什么做不到)
- 与类似工具的区别(如果有)
不写显而易见的问题("怎么安装"不是 FAQ,上面已经写了)。
终端演示动画(Hero 区)
设计 3 组场景,循环播放。prompt 和 output 都必须提供中英双语,终端演示会随页面语言切换。
每组数据结构:
{
prompt: { en: 'English prompt', zh: '中文提示词' },
url: 'example.com/page',
output: [
{ text: { en: 'English output...', zh: '中文输出...' }, cls: 't-dim' },
// cls: 't-dim'(灰) / 't-info'(白) / 't-accent'(青) / 't-success'(绿)
]
}- 覆盖 skill 的不同能力面
- 输出行要有层次:灰色(状态)→ 白色(信息)→ 彩色(关键结果)→ 绿色(完成)
i18n
所有文案(包括终端演示)都需要中英双语。翻译时保留技术术语不翻(如 SKILL.md、CDP、GitHub Pages)。
视觉元素
Favicon
基于 skill 的核心概念设计一个 32x32 的 SVG 图标。用简单几何形状,不要复杂插画。颜色使用品牌色(默认 #e84393,可由用户指定)。
OG 图(1200x630)
模板布局固定:
- 左上:
AGENT SKILL标签 - 左侧大字:skill 名称
- 左侧中:副标题 + 描述
- 左下:stats + install 命令
- 右侧:终端窗口装饰(模拟演示)
- 右下:作者信息
#!/usr/bin/env node
/**
* deploy.mjs — 将生成的网站部署到 GitHub Pages
*
* 用法:node deploy.mjs --repo owner/repo --dir ./output [--domain custom.domain.com]
*/
import { execSync } from 'child_process';
import { existsSync, writeFileSync, mkdtempSync, cpSync } from 'fs';
import { join } from 'path';
import { tmpdir } from 'os';
import { parseArgs } from 'util';
const { values } = parseArgs({
options: {
repo: { type: 'string' },
dir: { type: 'string' },
domain: { type: 'string' },
},
});
const { repo, dir, domain } = values;
if (!repo || !dir) {
console.error('用法: node deploy.mjs --repo owner/repo --dir ./output [--domain example.com]');
process.exit(1);
}
if (!existsSync(dir)) {
console.error(`输出目录不存在: ${dir}`);
process.exit(1);
}
const [owner, repoName] = repo.split('/');
if (!owner || !repoName) {
console.error('repo 格式应为 owner/repo');
process.exit(1);
}
function run(cmd, opts = {}) {
try {
return execSync(cmd, { encoding: 'utf-8', stdio: opts.silent ? 'pipe' : 'inherit', ...opts }).trim();
} catch (e) {
if (opts.silent) return '';
throw e;
}
}
// 1. 检查 gh CLI 登录态
console.log('检查 gh CLI 登录态...');
const ghStatus = run('gh auth status 2>&1', { silent: true });
if (ghStatus.includes('not logged')) {
console.error('gh CLI 未登录,请先运行: gh auth login');
process.exit(1);
}
console.log('✓ gh CLI 已登录');
// 2. 如果有自定义域名,生成 CNAME 文件
if (domain) {
writeFileSync(join(dir, 'CNAME'), domain);
console.log(`✓ 生成 CNAME: ${domain}`);
}
// 3. 创建临时工作目录,初始化 git 并推送
const workDir = mkdtempSync(join(tmpdir(), 'skill-site-deploy-'));
console.log(`准备部署文件...`);
// 初始化 git
run(`git init`, { cwd: workDir, silent: true });
run(`git checkout -b gh-pages`, { cwd: workDir, silent: true });
// 复制网站文件
cpSync(dir, workDir, { recursive: true });
// 添加 .nojekyll 文件(禁用 Jekyll 处理)
writeFileSync(join(workDir, '.nojekyll'), '');
// 提交
run(`git add -A`, { cwd: workDir, silent: true });
run(`git commit -m "Deploy skill site"`, { cwd: workDir, silent: true });
// 设置远程并推送
const remoteUrl = `https://github.com/${repo}.git`;
run(`git remote add origin ${remoteUrl}`, { cwd: workDir, silent: true });
console.log(`推送到 ${repo} gh-pages 分支...`);
try {
run(`git push origin gh-pages --force`, { cwd: workDir });
} catch (e) {
console.error(`推送失败。请确认你对 ${repo} 有写权限。`);
process.exit(1);
}
console.log('✓ 推送完成');
// 4. 开启 GitHub Pages
console.log('配置 GitHub Pages...');
try {
// 先尝试获取当前 Pages 状态
const pagesStatus = run(`gh api repos/${repo}/pages 2>&1`, { silent: true });
if (pagesStatus.includes('"source"')) {
// Pages 已存在,更新配置
run(`gh api repos/${repo}/pages -X PUT -f "build_type=legacy" -f "source[branch]=gh-pages" -f "source[path]=/" 2>&1`, { silent: true });
}
} catch {
// Pages 不存在,创建
try {
run(`gh api repos/${repo}/pages -X POST -f "build_type=legacy" -f "source[branch]=gh-pages" -f "source[path]=/" 2>&1`, { silent: true });
} catch {
// 可能已存在,忽略
}
}
console.log('✓ GitHub Pages 已配置');
// 5. 输出结果
const siteUrl = domain
? `https://${domain}`
: `https://${owner}.github.io/${repoName}/`;
console.log('');
console.log('========================================');
console.log(` 网站已部署!`);
console.log(` ${siteUrl}`);
console.log('========================================');
if (domain) {
console.log('');
console.log(`请在 DNS 服务商添加 CNAME 记录:`);
console.log(` ${domain} → ${owner}.github.io`);
}
// 清理临时目录
try {
run(`rm -rf "${workDir}"`, { silent: true });
} catch {}
#!/usr/bin/env node
/**
* generate-assets.mjs — 生成 favicon、OG 图、SEO 文件
*
* 用法:node generate-assets.mjs --name <skill名称> --repo <owner/repo> --color <品牌色> --out <输出目录>
*
* AI 负责生成 index.html、style.css、script.js
* 本脚本只生成 AI 不擅长的:图片资源 + SEO 文件
*/
import { writeFileSync, existsSync, mkdirSync } from 'fs';
import { join, resolve } from 'path';
import { parseArgs } from 'util';
const { values } = parseArgs({
options: {
name: { type: 'string' },
repo: { type: 'string' },
color: { type: 'string', default: '#e84393' },
out: { type: 'string' },
domain: { type: 'string' }, // 可选自定义域名
subtitle: { type: 'string' }, // 可选,用于 OG 图
author: { type: 'string' }, // 可选,用于 OG 图
stars: { type: 'string' }, // 可选,用于 OG 图
forks: { type: 'string' }, // 可选,用于 OG 图
},
});
const { name, repo, color, out, domain, subtitle, author, stars, forks } = values;
if (!name || !out) {
console.error('用法: node generate-assets.mjs --name <name> --repo <owner/repo> --color "#e84393" --out ./output');
process.exit(1);
}
const outDir = resolve(out);
if (!existsSync(outDir)) mkdirSync(outDir, { recursive: true });
const initial = name.charAt(0).toUpperCase();
const [owner, repoName] = (repo || '').split('/');
const siteUrl = domain ? `https://${domain}` : (owner ? `https://${owner}.github.io/${repoName}/` : '');
// ---- Favicon SVG ----
const faviconSvg = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32">
<rect width="32" height="32" rx="6" fill="${color}"/>
<text x="16" y="22" font-family="system-ui,sans-serif" font-size="18" font-weight="700" fill="white" text-anchor="middle">${initial}</text>
</svg>`;
writeFileSync(join(outDir, 'favicon.svg'), faviconSvg);
console.log(' ✓ favicon.svg');
// ---- Favicon PNG(sharp 可用时)----
let sharpAvailable = false;
try {
const sharp = (await import('sharp')).default;
sharpAvailable = true;
const svgBuf = Buffer.from(faviconSvg);
await Promise.all([
sharp(svgBuf).resize(32, 32).png().toFile(join(outDir, 'favicon-32x32.png')),
sharp(svgBuf).resize(16, 16).png().toFile(join(outDir, 'favicon-16x16.png')),
sharp(svgBuf).resize(180, 180).png().toFile(join(outDir, 'apple-touch-icon.png')),
]);
console.log(' ✓ favicon PNG (32/16/180)');
} catch {
console.log(' ⚠ sharp 不可用,跳过 PNG favicon');
}
// ---- OG 图 (1200x630) ----
const ogSubtitle = subtitle || `Agent Skill for ${name}`;
const ogAuthor = author || '';
const ogStars = stars || '';
const ogForks = forks || '';
const installCmd = repo ? `npx skills add ${repo}` : '';
const ogSvg = `<svg width="1200" height="630" xmlns="http://www.w3.org/2000/svg">
<defs>
<linearGradient id="bg" x1="0%" y1="0%" x2="100%" y2="100%">
<stop offset="0%" style="stop-color:#fafafa"/>
<stop offset="100%" style="stop-color:#f0f0ee"/>
</linearGradient>
</defs>
<rect width="1200" height="630" fill="url(#bg)"/>
<text x="80" y="100" font-family="monospace" font-size="16" font-weight="600" fill="${color}" letter-spacing="2">AGENT SKILL</text>
<text x="80" y="190" font-family="monospace" font-size="72" font-weight="700" fill="#1a1a1a" letter-spacing="-2">${name}</text>
<text x="80" y="260" font-family="sans-serif" font-size="28" fill="#4a4a4a">${ogSubtitle}</text>
${ogStars ? `<text x="80" y="340" font-family="monospace" font-size="18" font-weight="600" fill="#1a1a1a">${ogStars} stars</text>` : ''}
${ogForks ? `<text x="260" y="340" font-family="monospace" font-size="18" font-weight="600" fill="#1a1a1a">${ogForks} forks</text>` : ''}
${installCmd ? `
<rect x="80" y="420" width="460" height="44" rx="8" fill="#1a1a1a"/>
<text x="100" y="448" font-family="monospace" font-size="16" fill="#888">$</text>
<text x="120" y="448" font-family="monospace" font-size="16" fill="#e0e0e0">${installCmd}</text>` : ''}
<rect x="750" y="120" width="370" height="280" rx="12" fill="#1a1a1a"/>
<rect x="750" y="120" width="370" height="36" rx="12" fill="#222"/>
<rect x="750" y="144" width="370" height="2" fill="#333"/>
<circle cx="770" cy="138" r="5" fill="#ff5f57"/>
<circle cx="786" cy="138" r="5" fill="#febc2e"/>
<circle cx="802" cy="138" r="5" fill="#28c840"/>
<text x="770" y="185" font-family="monospace" font-size="14" fill="${color}">></text>
<text x="790" y="185" font-family="monospace" font-size="13" fill="#ccc">Using ${name}...</text>
<text x="770" y="215" font-family="monospace" font-size="12" fill="#666">Processing...</text>
<text x="770" y="245" font-family="monospace" font-size="12" fill="#4ade80">✓ Done</text>
${ogAuthor ? `<text x="1000" y="580" font-family="sans-serif" font-size="16" fill="#8a8a8a">by ${ogAuthor}</text>` : ''}
${siteUrl ? `<text x="80" y="580" font-family="sans-serif" font-size="16" fill="#8a8a8a">${siteUrl.replace('https://', '')}</text>` : ''}
</svg>`;
writeFileSync(join(outDir, 'og.svg'), ogSvg);
if (sharpAvailable) {
try {
const sharp = (await import('sharp')).default;
await sharp(Buffer.from(ogSvg)).png().toFile(join(outDir, 'og.png'));
console.log(' ✓ og.png');
} catch {
writeFileSync(join(outDir, 'og.png'), ogSvg);
console.log(' ✓ og.svg (PNG 转换失败,保留 SVG)');
}
} else {
writeFileSync(join(outDir, 'og.png'), ogSvg);
console.log(' ✓ og.svg (sharp 不可用)');
}
// ---- robots.txt ----
writeFileSync(join(outDir, 'robots.txt'), `User-agent: *\nAllow: /\n${siteUrl ? `\nSitemap: ${siteUrl}sitemap.xml` : ''}\n`);
console.log(' ✓ robots.txt');
// ---- sitemap.xml ----
if (siteUrl) {
const today = new Date().toISOString().split('T')[0];
writeFileSync(join(outDir, 'sitemap.xml'), `<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
<url>
<loc>${siteUrl}</loc>
<lastmod>${today}</lastmod>
<changefreq>weekly</changefreq>
<priority>1.0</priority>
</url>
</urlset>\n`);
console.log(' ✓ sitemap.xml');
}
console.log(`\n✅ 资源生成完成 → ${outDir}`);
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>web-access — Real Browser Automation for AI Agents</title>
<meta name="description" content="Give your AI agent eyes and hands on the web. Connect to your daily Chrome with full login state. Search, browse, click, extract — your agent operates the web like you do. 4000+ stars on GitHub.">
<meta name="keywords" content="web-access, agent skill, AI agent, browser automation, CDP, Chrome DevTools Protocol, Claude Code, Cursor, SKILL.md, web scraping, authenticated browsing">
<meta name="author" content="一泽Eze">
<link rel="canonical" href="https://web-access.eze.is/">
<!-- Favicon -->
<link rel="icon" type="image/svg+xml" href="/favicon.svg">
<link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png">
<link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png">
<link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png">
<meta name="theme-color" content="#fafafa">
<!-- Open Graph -->
<meta property="og:type" content="website">
<meta property="og:title" content="web-access — Real Browser Automation for AI Agents">
<meta property="og:description" content="Give your AI agent eyes and hands on the web. Connect to your daily Chrome with full login state, automate browsing, extraction, and interaction.">
<meta property="og:url" content="https://web-access.eze.is/">
<meta property="og:site_name" content="web-access">
<meta property="og:image" content="https://web-access.eze.is/og.png">
<meta property="og:image:width" content="1200">
<meta property="og:image:height" content="630">
<!-- Twitter Card -->
<meta name="twitter:card" content="summary_large_image">
<meta name="twitter:title" content="web-access — Real Browser Automation for AI Agents">
<meta name="twitter:description" content="Give your AI agent eyes and hands on the web. Connect to your daily Chrome with full login state.">
<meta name="twitter:image" content="https://web-access.eze.is/og.png">
<meta name="twitter:creator" content="@eze_is_1">
<!-- Structured Data -->
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "SoftwareApplication",
"name": "web-access",
"description": "Agent Skill for real browser automation. Give your AI agent eyes and hands on the web via Chrome DevTools Protocol.",
"url": "https://web-access.eze.is/",
"applicationCategory": "DeveloperApplication",
"operatingSystem": "macOS, Linux, Windows",
"author": {
"@type": "Person",
"name": "一泽Eze",
"url": "https://x.com/eze_is_1"
},
"codeRepository": "https://github.com/eze-is/web-access",
"license": "https://opensource.org/licenses/MIT",
"offers": {
"@type": "Offer",
"price": "0",
"priceCurrency": "USD"
},
"aggregateRating": {
"@type": "AggregateRating",
"ratingValue": "4.9",
"ratingCount": "4000",
"bestRating": "5"
}
}
</script>
<!-- Breadcrumb structured data -->
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "BreadcrumbList",
"itemListElement": [
{ "@type": "ListItem", "position": 1, "name": "Home", "item": "https://web-access.eze.is/" },
{ "@type": "ListItem", "position": 2, "name": "Capabilities", "item": "https://web-access.eze.is/#capabilities" },
{ "@type": "ListItem", "position": 3, "name": "Install", "item": "https://web-access.eze.is/#install" },
{ "@type": "ListItem", "position": 4, "name": "FAQ", "item": "https://web-access.eze.is/#faq" }
]
}
</script>
<!-- FAQ structured data for Google rich results -->
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "FAQPage",
"mainEntity": [
{
"@type": "Question",
"name": "Is web-access safe? Can the agent mess up my browser?",
"acceptedAnswer": {
"@type": "Answer",
"text": "All operations happen in new background tabs created by the agent. Your existing tabs are never touched. When the task is done, the agent closes its own tabs."
}
},
{
"@type": "Question",
"name": "Does web-access work with private or internal websites?",
"acceptedAnswer": {
"@type": "Answer",
"text": "Yes. Since it uses your real Chrome with all your login sessions, it can access any site you're logged into — including corporate intranets, admin dashboards, and internal tools."
}
},
{
"@type": "Question",
"name": "Will websites detect and block web-access?",
"acceptedAnswer": {
"@type": "Answer",
"text": "The skill operates through your real browser, not a headless automation tool. Most websites can't distinguish it from normal browsing."
}
},
{
"@type": "Question",
"name": "Can multiple agents use the browser simultaneously?",
"acceptedAnswer": {
"@type": "Answer",
"text": "Yes. Each sub-agent creates its own tabs and operates independently. They share one Chrome instance and one proxy, using different tab IDs — no conflicts."
}
},
{
"@type": "Question",
"name": "Does web-access work outside Claude Code?",
"acceptedAnswer": {
"@type": "Answer",
"text": "web-access follows the SKILL.md standard. It works with any agent harness that supports skills — Claude Code, Cursor, Gemini CLI, Codex CLI, and others."
}
}
]
}
</script>
<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=Inter:wght@400;500;600&family=JetBrains+Mono:wght@400;500;600&display=swap" rel="stylesheet">
<link rel="stylesheet" href="style.css">
</head>
<body>
<!-- Nav -->
<nav class="nav" id="nav">
<div class="nav-inner">
<a href="#" class="nav-logo">web-access</a>
<div class="nav-links">
<a href="#problem" data-i18n="nav.why">Why</a>
<a href="#architecture" data-i18n="nav.how">How</a>
<a href="#capabilities" data-i18n="nav.capabilities">Capabilities</a>
<a href="#install" data-i18n="nav.install">Install</a>
<a href="#faq">FAQ</a>
<button class="lang-toggle" id="lang-toggle" aria-label="Switch language">EN / 中</button>
<a href="https://github.com/eze-is/web-access" target="_blank" class="nav-github">
<svg width="20" height="20" viewBox="0 0 24 24" fill="currentColor"><path d="M12 0c-6.626 0-12 5.373-12 12 0 5.302 3.438 9.8 8.207 11.387.599.111.793-.261.793-.577v-2.234c-3.338.726-4.033-1.416-4.033-1.416-.546-1.387-1.333-1.756-1.333-1.756-1.089-.745.083-.729.083-.729 1.205.084 1.839 1.237 1.839 1.237 1.07 1.834 2.807 1.304 3.492.997.107-.775.418-1.305.762-1.604-2.665-.305-5.467-1.334-5.467-5.931 0-1.311.469-2.381 1.236-3.221-.124-.303-.535-1.524.117-3.176 0 0 1.008-.322 3.301 1.23.957-.266 1.983-.399 3.003-.404 1.02.005 2.047.138 3.006.404 2.291-1.552 3.297-1.23 3.297-1.23.653 1.653.242 2.874.118 3.176.77.84 1.235 1.911 1.235 3.221 0 4.609-2.807 5.624-5.479 5.921.43.372.823 1.102.823 2.222v3.293c0 .319.192.694.801.576 4.765-1.589 8.199-6.086 8.199-11.386 0-6.627-5.373-12-12-12z"/></svg>
<span class="star-count">4.1k</span>
</a>
</div>
</div>
</nav>
<!-- Hero -->
<section class="hero" id="hero">
<div class="hero-content">
<div class="hero-badge" data-i18n="hero.badge">Agent Skill</div>
<h1 class="hero-title">web-access</h1>
<p class="hero-subtitle" data-i18n="hero.subtitle">Give your AI agent eyes and hands on the web.</p>
<p class="hero-desc" data-i18n="hero.desc">Connect to your daily browser with full login state. Search, browse, click, extract — your agent operates the web like you do.</p>
<div class="hero-actions">
<div class="hero-install">
<span class="install-prompt">$</span>
<code>npx skills add eze-is/web-access</code>
<button class="copy-btn" data-copy="npx skills add eze-is/web-access" aria-label="Copy">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="9" y="9" width="13" height="13" rx="2"/><path d="M5 15H4a2 2 0 01-2-2V4a2 2 0 012-2h9a2 2 0 012 2v1"/></svg>
</button>
</div>
<a href="#capabilities" class="hero-cta" data-i18n="hero.cta">See what it can do</a>
</div>
<div class="hero-stats">
<div class="stat"><strong>4,000+</strong> <span data-i18n="hero.stars">stars</span></div>
<div class="stat-sep"></div>
<div class="stat"><strong>290+</strong> forks</div>
<div class="stat-sep"></div>
<div class="stat"><strong>395+</strong> <span data-i18n="hero.installs">installs via npx</span></div>
</div>
</div>
<div class="hero-visual">
<div class="terminal-window">
<div class="terminal-header">
<div class="terminal-dots">
<span class="dot red"></span>
<span class="dot yellow"></span>
<span class="dot green"></span>
</div>
<span class="terminal-title">Agent Terminal</span>
</div>
<div class="terminal-body" id="terminal-demo">
<div class="terminal-line">
<span class="t-prompt">></span>
<span class="t-text" id="demo-prompt"></span>
<span class="t-cursor">|</span>
</div>
<div class="terminal-output" id="demo-output"></div>
</div>
</div>
<div class="browser-window">
<div class="browser-header">
<div class="terminal-dots">
<span class="dot red"></span>
<span class="dot yellow"></span>
<span class="dot green"></span>
</div>
<div class="browser-url">
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"/></svg>
<span id="browser-url-text">your-chrome.local</span>
</div>
</div>
<div class="browser-body" id="browser-demo">
<div class="browser-placeholder">
<div class="browser-skeleton">
<div class="skel-nav"></div>
<div class="skel-hero-block"></div>
<div class="skel-lines">
<div class="skel-line w80"></div>
<div class="skel-line w60"></div>
<div class="skel-line w70"></div>
</div>
</div>
</div>
</div>
</div>
</div>
</section>
<!-- Logos -->
<section class="logos-section">
<p class="logos-label" data-i18n="logos.label">Works with</p>
<div class="logos-row">
<div class="logo-item">Claude Code</div>
<div class="logo-item">Cursor</div>
<div class="logo-item">Gemini CLI</div>
<div class="logo-item">Codex CLI</div>
<div class="logo-item">Kilo Code</div>
<div class="logo-item">Any SKILL.md agent</div>
</div>
</section>
<!-- Problem -->
<section class="section problem-section" id="problem">
<div class="section-inner">
<div class="section-label" data-i18n="problem.label">The Problem</div>
<h2 class="section-title" data-i18n="problem.title">Your agent is blind to the real web.</h2>
<p class="section-desc" data-i18n="problem.desc">Built-in web tools return summaries, not source. They can't log in, can't interact, can't see what you see.</p>
<div class="comparison-grid">
<div class="comparison-card without">
<div class="comparison-header">
<span class="comparison-icon">✕</span>
<span data-i18n="problem.without">Without web-access</span>
</div>
<ul class="comparison-list">
<li data-i18n="problem.w1">Search returns snippets, not real pages</li>
<li data-i18n="problem.w2">No login state — paywalls, auth walls everywhere</li>
<li data-i18n="problem.w3">Dynamic content invisible (SPA, lazy-load)</li>
<li data-i18n="problem.w4">Anti-scraping platforms completely blocked</li>
<li data-i18n="problem.w5">Can't click, scroll, fill forms, or navigate</li>
<li data-i18n="problem.w6">One tool at a time, no parallel research</li>
</ul>
</div>
<div class="comparison-card with">
<div class="comparison-header">
<span class="comparison-icon">✓</span>
<span data-i18n="problem.with">With web-access</span>
</div>
<ul class="comparison-list">
<li data-i18n="problem.a1">Full page content from the real browser DOM</li>
<li data-i18n="problem.a2">Your Chrome's login sessions, cookies, everything</li>
<li data-i18n="problem.a3">JavaScript-rendered content fully accessible</li>
<li data-i18n="problem.a4">Xiaohongshu, WeChat, Zhihu — all work</li>
<li data-i18n="problem.a5">Click, scroll, type, upload files, take screenshots</li>
<li data-i18n="problem.a6">Parallel sub-agents, each with their own tabs</li>
</ul>
</div>
</div>
</div>
</section>
<!-- Architecture -->
<section class="section arch-section" id="architecture">
<div class="section-inner">
<div class="section-label" data-i18n="arch.label">How It Works</div>
<h2 class="section-title" data-i18n="arch.title">Three layers. Zero new browsers.</h2>
<p class="section-desc" data-i18n="arch.desc">web-access connects directly to your daily Chrome via CDP. No Puppeteer, no headless browser, no separate profile. Your agent sees exactly what you see.</p>
<div class="arch-diagram">
<div class="arch-node agent-node">
<div class="arch-icon">
<svg width="28" height="28" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M12 2a4 4 0 014 4v1a4 4 0 01-8 0V6a4 4 0 014-4z"/><path d="M20 21v-2a4 4 0 00-4-4H8a4 4 0 00-4 4v2"/><circle cx="12" cy="10" r="1" fill="currentColor"/></svg>
</div>
<div class="arch-label" data-i18n="arch.agent">AI Agent</div>
<div class="arch-sub" data-i18n="arch.agentSub">Claude Code, Cursor, etc.</div>
</div>
<div class="arch-connector">
<div class="arch-line"></div>
<div class="arch-flow-label">HTTP API</div>
</div>
<div class="arch-node proxy-node">
<div class="arch-icon">
<svg width="28" height="28" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><rect x="2" y="3" width="20" height="18" rx="3"/><path d="M7 8h4M7 12h8M7 16h6"/></svg>
</div>
<div class="arch-label" data-i18n="arch.proxy">CDP Proxy</div>
<div class="arch-sub" data-i18n="arch.proxySub">Lightweight local bridge</div>
</div>
<div class="arch-connector">
<div class="arch-line"></div>
<div class="arch-flow-label">WebSocket</div>
</div>
<div class="arch-node chrome-node">
<div class="arch-icon">
<svg width="28" height="28" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><circle cx="12" cy="12" r="10"/><circle cx="12" cy="12" r="4"/><path d="M21.17 8H12M12 12l-5.07 8.79M3.95 6.06L9.05 16"/></svg>
</div>
<div class="arch-label" data-i18n="arch.chrome">Your Chrome</div>
<div class="arch-sub" data-i18n="arch.chromeSub">Login state, cookies, extensions</div>
</div>
</div>
<div class="arch-features">
<div class="arch-feature">
<div class="feature-icon">
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z"/><circle cx="12" cy="12" r="3"/></svg>
</div>
<h3 data-i18n="arch.see">See</h3>
<p data-i18n="arch.seeDesc">Query the DOM, extract text, discover links and structure</p>
</div>
<div class="arch-feature">
<div class="feature-icon">
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M18 8h1a4 4 0 010 8h-1M2 8h16v9a4 4 0 01-4 4H6a4 4 0 01-4-4V8z"/><line x1="6" y1="1" x2="6" y2="4"/><line x1="10" y1="1" x2="10" y2="4"/><line x1="14" y1="1" x2="14" y2="4"/></svg>
</div>
<h3 data-i18n="arch.act">Act</h3>
<p data-i18n="arch.actDesc">Click buttons, scroll pages, fill forms, upload files</p>
</div>
<div class="arch-feature">
<div class="feature-icon">
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M14 2H6a2 2 0 00-2 2v16a2 2 0 002 2h12a2 2 0 002-2V8z"/><polyline points="14 2 14 8 20 8"/><line x1="16" y1="13" x2="8" y2="13"/><line x1="16" y1="17" x2="8" y2="17"/></svg>
</div>
<h3 data-i18n="arch.read">Read</h3>
<p data-i18n="arch.readDesc">Extract content, capture screenshots, analyze video frames</p>
</div>
</div>
</div>
</section>
<!-- Capabilities -->
<section class="section cap-section" id="capabilities">
<div class="section-inner">
<div class="section-label" data-i18n="cap.label">Capabilities</div>
<h2 class="section-title" data-i18n="cap.title">Everything your agent needs on the web.</h2>
<div class="cap-grid">
<div class="cap-card" data-cap="research">
<div class="cap-icon-wrap">
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><circle cx="11" cy="11" r="8"/><path d="M21 21l-4.35-4.35"/></svg>
</div>
<h3 data-i18n="cap.research">Deep Research</h3>
<p data-i18n="cap.researchDesc">Multi-layer search: engine results → primary sources → full page extraction. Verify facts against official documentation, not second-hand reports.</p>
<div class="cap-example">
<code>> "Research the latest Claude model pricing and compare with GPT-4o"</code>
</div>
</div>
<div class="cap-card" data-cap="auth">
<div class="cap-icon-wrap">
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><rect x="3" y="11" width="18" height="11" rx="2"/><path d="M7 11V7a5 5 0 0110 0v4"/><circle cx="12" cy="16" r="1"/></svg>
</div>
<h3 data-i18n="cap.auth">Authenticated Access</h3>
<p data-i18n="cap.authDesc">Operates inside your logged-in Chrome. Access dashboards, admin panels, internal tools — anything you can see, your agent can see.</p>
<div class="cap-example">
<code>> "Check my Vercel deployment status and summarize recent errors"</code>
</div>
</div>
<div class="cap-card" data-cap="extract">
<div class="cap-icon-wrap">
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M21 15v4a2 2 0 01-2 2H5a2 2 0 01-2-2v-4"/><polyline points="7 10 12 15 17 10"/><line x1="12" y1="15" x2="12" y2="3"/></svg>
</div>
<h3 data-i18n="cap.extract">Anti-Scraping Bypass</h3>
<p data-i18n="cap.extractDesc">Xiaohongshu, WeChat Official Accounts, Zhihu, Weibo — platforms that block static fetching work naturally through the real browser.</p>
<div class="cap-example">
<code>> "Grab the top 10 posts about AI agents on Xiaohongshu"</code>
</div>
</div>
<div class="cap-card" data-cap="interact">
<div class="cap-icon-wrap">
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M18 3a3 3 0 00-3 3v12a3 3 0 003 3 3 3 0 003-3 3 3 0 00-3-3H6a3 3 0 00-3 3 3 3 0 003 3 3 3 0 003-3V6a3 3 0 00-3-3 3 3 0 00-3 3 3 3 0 003 3h12a3 3 0 003-3 3 3 0 00-3-3z"/></svg>
</div>
<h3 data-i18n="cap.interact">Full Interaction</h3>
<p data-i18n="cap.interactDesc">Click, scroll, type, upload files, handle popups. Navigate multi-step flows like a human — form submissions, checkout processes, configuration wizards.</p>
<div class="cap-example">
<code>> "Fill out the application form on this page with my info"</code>
</div>
</div>
<div class="cap-card" data-cap="video">
<div class="cap-icon-wrap">
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><polygon points="23 7 16 12 23 17 23 7"/><rect x="1" y="5" width="15" height="14" rx="2"/></svg>
</div>
<h3 data-i18n="cap.video">Video Analysis</h3>
<p data-i18n="cap.videoDesc">Seek to any timestamp, capture frames, analyze video content visually. Extract information from tutorials, presentations, and recordings.</p>
<div class="cap-example">
<code>> "Watch this demo video and summarize the key features shown"</code>
</div>
</div>
<div class="cap-card" data-cap="parallel">
<div class="cap-icon-wrap">
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><rect x="2" y="3" width="7" height="7" rx="1"/><rect x="15" y="3" width="7" height="7" rx="1"/><rect x="2" y="14" width="7" height="7" rx="1"/><rect x="15" y="14" width="7" height="7" rx="1"/></svg>
</div>
<h3 data-i18n="cap.parallel">Parallel Sub-Agents</h3>
<p data-i18n="cap.parallelDesc">Spawn multiple agents, each with their own browser tabs. Research N topics simultaneously — total time equals the slowest single task.</p>
<div class="cap-example">
<code>> "Compare these 5 competitor products — pricing, features, reviews"</code>
</div>
</div>
</div>
</div>
</section>
<!-- API Preview -->
<section class="section api-section" id="api">
<div class="section-inner">
<div class="section-label" data-i18n="api.label">Under the Hood</div>
<h2 class="section-title" data-i18n="api.title">Simple HTTP API. Powerful primitives.</h2>
<p class="section-desc" data-i18n="api.desc">The CDP Proxy exposes a clean REST API. Your agent calls curl — no SDK, no dependency, no complexity.</p>
<div class="api-grid">
<div class="api-card">
<div class="api-method">GET</div>
<code class="api-endpoint">/new?url=...</code>
<span class="api-desc" data-i18n="api.new">Open a new background tab</span>
</div>
<div class="api-card">
<div class="api-method">POST</div>
<code class="api-endpoint">/eval</code>
<span class="api-desc" data-i18n="api.eval">Execute JavaScript in page context</span>
</div>
<div class="api-card">
<div class="api-method">POST</div>
<code class="api-endpoint">/click</code>
<span class="api-desc" data-i18n="api.click">Click an element by CSS selector</span>
</div>
<div class="api-card">
<div class="api-method">GET</div>
<code class="api-endpoint">/screenshot</code>
<span class="api-desc" data-i18n="api.screenshot">Capture page or video frame</span>
</div>
<div class="api-card">
<div class="api-method">GET</div>
<code class="api-endpoint">/scroll</code>
<span class="api-desc" data-i18n="api.scroll">Scroll to position or direction</span>
</div>
<div class="api-card">
<div class="api-method">GET</div>
<code class="api-endpoint">/navigate</code>
<span class="api-desc" data-i18n="api.navigate">Navigate to a new URL</span>
</div>
<div class="api-card">
<div class="api-method">POST</div>
<code class="api-endpoint">/setFiles</code>
<span class="api-desc" data-i18n="api.setFiles">Upload local files to file inputs</span>
</div>
<div class="api-card">
<div class="api-method">GET</div>
<code class="api-endpoint">/close</code>
<span class="api-desc" data-i18n="api.close">Close a tab when done</span>
</div>
</div>
</div>
</section>
<!-- Install -->
<section class="section install-section" id="install">
<div class="section-inner">
<div class="section-label" data-i18n="install.label">Get Started</div>
<h2 class="section-title" data-i18n="install.title">Two minutes to full web access.</h2>
<div class="install-steps">
<div class="install-step">
<div class="step-num">1</div>
<div class="step-content">
<h3 data-i18n="install.step1">Install the skill</h3>
<div class="code-block">
<code>npx skills add eze-is/web-access</code>
<button class="copy-btn" data-copy="npx skills add eze-is/web-access" aria-label="Copy">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="9" y="9" width="13" height="13" rx="2"/><path d="M5 15H4a2 2 0 01-2-2V4a2 2 0 012-2h9a2 2 0 012 2v1"/></svg>
</button>
</div>
</div>
</div>
<div class="install-step">
<div class="step-num">2</div>
<div class="step-content">
<h3 data-i18n="install.step2">Enable Chrome debugging</h3>
<p data-i18n="install.step2desc" data-i18n-html="true">Open <code>chrome://inspect/#remote-debugging</code> in Chrome, check <strong>"Allow remote debugging"</strong>, restart Chrome.</p>
</div>
</div>
<div class="install-step">
<div class="step-num">3</div>
<div class="step-content">
<h3 data-i18n="install.step3">Start using it</h3>
<p data-i18n="install.step3desc">Ask your agent to browse, search, or interact with any website. The skill activates automatically.</p>
</div>
</div>
</div>
<div class="install-req">
<h4 data-i18n="install.req">Requirements</h4>
<div class="req-items">
<span class="req-item">Node.js 22+</span>
<span class="req-item">Google Chrome</span>
<span class="req-item">Any SKILL.md-compatible agent</span>
</div>
</div>
</div>
</section>
<!-- FAQ -->
<section class="section faq-section" id="faq">
<div class="section-inner">
<div class="section-label">FAQ</div>
<h2 class="section-title" data-i18n="faq.title">Frequently Asked Questions</h2>
<div class="faq-list">
<details class="faq-item">
<summary data-i18n="faq.q1">Is this safe? Can the agent mess up my browser?</summary>
<p data-i18n="faq.a1" data-i18n-html="true">All operations happen in <strong>new background tabs</strong> created by the agent. Your existing tabs are never touched. When the task is done, the agent closes its own tabs.</p>
</details>
<details class="faq-item">
<summary data-i18n="faq.q2">Does this work with private/internal websites?</summary>
<p data-i18n="faq.a2">Yes. Since it uses your real Chrome with all your login sessions, it can access any site you're logged into — including corporate intranets, admin dashboards, and internal tools.</p>
</details>
<details class="faq-item">
<summary data-i18n="faq.q3">Will websites detect and block this?</summary>
<p data-i18n="faq.a3">The skill operates through your real browser, not a headless automation tool. Most websites can't distinguish it from normal browsing. However, extremely aggressive anti-bot systems may still flag automated patterns.</p>
</details>
<details class="faq-item">
<summary data-i18n="faq.q4">Can multiple agents use the browser simultaneously?</summary>
<p data-i18n="faq.a4">Yes. Each sub-agent creates its own tabs and operates independently. They share one Chrome instance and one proxy, using different tab IDs — no conflicts.</p>
</details>
<details class="faq-item">
<summary data-i18n="faq.q5">Does it work outside Claude Code?</summary>
<p data-i18n="faq.a5">web-access follows the SKILL.md standard. It works with any agent harness that supports skills — Claude Code, Cursor, Gemini CLI, Codex CLI, and others.</p>
</details>
<details class="faq-item">
<summary data-i18n="faq.q6">What about headless/server environments?</summary>
<p data-i18n="faq.a6">web-access is designed for local development where you have a running Chrome instance. For server environments, you'd need a headed Chrome session accessible via CDP.</p>
</details>
</div>
</div>
</section>
<!-- Footer -->
<footer class="footer">
<div class="footer-inner">
<div class="footer-left">
<span class="footer-logo">web-access</span>
<span class="footer-author">by <a href="https://x.com/eze_is_1" target="_blank">一泽Eze</a></span>
</div>
<div class="footer-right">
<a href="https://github.com/eze-is/web-access" target="_blank">GitHub</a>
<a href="https://github.com/eze-is/web-access/blob/main/LICENSE" target="_blank">MIT License</a>
</div>
</div>
</footer>
<noscript>
<style>.t-cursor { display: none; } .terminal-output { display: none; }</style>
</noscript>
<script src="script.js"></script>
</body>
</html>
// ============================================
// web-access — Landing Page Scripts
// ============================================
// ---- i18n System ----
const i18n = {
en: {
'nav.why': 'Why',
'nav.how': 'How',
'nav.capabilities': 'Capabilities',
'nav.install': 'Install',
'hero.badge': 'Agent Skill',
'hero.subtitle': 'Give your AI agent eyes and hands on the web.',
'hero.desc': 'Connect to your daily browser with full login state. Search, browse, click, extract — your agent operates the web like you do.',
'hero.cta': 'See what it can do',
'hero.stars': 'stars',
'hero.installs': 'installs via npx',
'logos.label': 'Works with',
'problem.label': 'The Problem',
'problem.title': 'Your agent is blind to the real web.',
'problem.desc': "Built-in web tools return summaries, not source. They can't log in, can't interact, can't see what you see.",
'problem.without': 'Without web-access',
'problem.with': 'With web-access',
'problem.w1': 'Search returns snippets, not real pages',
'problem.w2': 'No login state — paywalls, auth walls everywhere',
'problem.w3': 'Dynamic content invisible (SPA, lazy-load)',
'problem.w4': 'Anti-scraping platforms completely blocked',
'problem.w5': "Can't click, scroll, fill forms, or navigate",
'problem.w6': 'One tool at a time, no parallel research',
'problem.a1': 'Full page content from the real browser DOM',
'problem.a2': "Your Chrome's login sessions, cookies, everything",
'problem.a3': 'JavaScript-rendered content fully accessible',
'problem.a4': 'Xiaohongshu, WeChat, Zhihu — all work',
'problem.a5': 'Click, scroll, type, upload files, take screenshots',
'problem.a6': 'Parallel sub-agents, each with their own tabs',
'arch.label': 'How It Works',
'arch.title': 'Three layers. Zero new browsers.',
'arch.desc': 'web-access connects directly to your daily Chrome via CDP. No Puppeteer, no headless browser, no separate profile. Your agent sees exactly what you see.',
'arch.agent': 'AI Agent',
'arch.agentSub': 'Claude Code, Cursor, etc.',
'arch.proxy': 'CDP Proxy',
'arch.proxySub': 'Lightweight local bridge',
'arch.chrome': 'Your Chrome',
'arch.chromeSub': 'Login state, cookies, extensions',
'arch.see': 'See',
'arch.seeDesc': 'Query the DOM, extract text, discover links and structure',
'arch.act': 'Act',
'arch.actDesc': 'Click buttons, scroll pages, fill forms, upload files',
'arch.read': 'Read',
'arch.readDesc': 'Extract content, capture screenshots, analyze video frames',
'cap.label': 'Capabilities',
'cap.title': 'Everything your agent needs on the web.',
'cap.research': 'Deep Research',
'cap.researchDesc': 'Multi-layer search: engine results → primary sources → full page extraction. Verify facts against official documentation, not second-hand reports.',
'cap.auth': 'Authenticated Access',
'cap.authDesc': 'Operates inside your logged-in Chrome. Access dashboards, admin panels, internal tools — anything you can see, your agent can see.',
'cap.extract': 'Anti-Scraping Bypass',
'cap.extractDesc': 'Xiaohongshu, WeChat Official Accounts, Zhihu, Weibo — platforms that block static fetching work naturally through the real browser.',
'cap.interact': 'Full Interaction',
'cap.interactDesc': 'Click, scroll, type, upload files, handle popups. Navigate multi-step flows like a human — form submissions, checkout processes, configuration wizards.',
'cap.video': 'Video Analysis',
'cap.videoDesc': 'Seek to any timestamp, capture frames, analyze video content visually. Extract information from tutorials, presentations, and recordings.',
'cap.parallel': 'Parallel Sub-Agents',
'cap.parallelDesc': 'Spawn multiple agents, each with their own browser tabs. Research N topics simultaneously — total time equals the slowest single task.',
'api.label': 'Under the Hood',
'api.title': 'Simple HTTP API. Powerful primitives.',
'api.desc': 'The CDP Proxy exposes a clean REST API. Your agent calls curl — no SDK, no dependency, no complexity.',
'api.new': 'Open a new background tab',
'api.eval': 'Execute JavaScript in page context',
'api.click': 'Click an element by CSS selector',
'api.screenshot': 'Capture page or video frame',
'api.scroll': 'Scroll to position or direction',
'api.navigate': 'Navigate to a new URL',
'api.setFiles': 'Upload local files to file inputs',
'api.close': 'Close a tab when done',
'install.label': 'Get Started',
'install.title': 'Two minutes to full web access.',
'install.step1': 'Install the skill',
'install.step2': 'Enable Chrome debugging',
'install.step2desc': 'Open <code>chrome://inspect/#remote-debugging</code> in Chrome, check <strong>"Allow remote debugging"</strong>, restart Chrome.',
'install.step3': 'Start using it',
'install.step3desc': 'Ask your agent to browse, search, or interact with any website. The skill activates automatically.',
'install.req': 'Requirements',
'faq.title': 'Frequently Asked Questions',
'faq.q1': 'Is this safe? Can the agent mess up my browser?',
'faq.a1': 'All operations happen in <strong>new background tabs</strong> created by the agent. Your existing tabs are never touched. When the task is done, the agent closes its own tabs.',
'faq.q2': 'Does this work with private/internal websites?',
'faq.a2': "Yes. Since it uses your real Chrome with all your login sessions, it can access any site you're logged into — including corporate intranets, admin dashboards, and internal tools.",
'faq.q3': 'Will websites detect and block this?',
'faq.a3': "The skill operates through your real browser, not a headless automation tool. Most websites can't distinguish it from normal browsing. However, extremely aggressive anti-bot systems may still flag automated patterns.",
'faq.q4': 'Can multiple agents use the browser simultaneously?',
'faq.a4': 'Yes. Each sub-agent creates its own tabs and operates independently. They share one Chrome instance and one proxy, using different tab IDs — no conflicts.',
'faq.q5': 'Does it work outside Claude Code?',
'faq.a5': 'web-access follows the SKILL.md standard. It works with any agent harness that supports skills — Claude Code, Cursor, Gemini CLI, Codex CLI, and others.',
'faq.q6': 'What about headless/server environments?',
'faq.a6': "web-access is designed for local development where you have a running Chrome instance. For server environments, you'd need a headed Chrome session accessible via CDP.",
},
zh: {
'nav.why': '为什么',
'nav.how': '原理',
'nav.capabilities': '能力',
'nav.install': '安装',
'hero.badge': 'Agent Skill',
'hero.subtitle': '让你的 AI Agent 能看、能操作、能上网。',
'hero.desc': '直连你日常使用的 Chrome 浏览器,天然携带登录态。搜索、浏览、点击、提取 — Agent 像你一样操作网页。',
'hero.cta': '看看它能做什么',
'hero.stars': '星标',
'hero.installs': '次 npx 安装',
'logos.label': '兼容以下 Agent',
'problem.label': '痛点',
'problem.title': '你的 Agent 是个「网络盲」。',
'problem.desc': '内置联网工具只能拿到摘要,不是一手信息。没有登录态,无法交互,看不到你看到的内容。',
'problem.without': '没有 web-access',
'problem.with': '有了 web-access',
'problem.w1': '搜索只返回摘要片段,不是真实页面',
'problem.w2': '没有登录态 — 到处是付费墙、登录墙',
'problem.w3': '动态内容不可见(SPA、懒加载)',
'problem.w4': '反爬平台完全无法访问',
'problem.w5': '不能点击、滚动、填表、导航',
'problem.w6': '一次只能用一个工具,无法并行调研',
'problem.a1': '从真实浏览器 DOM 获取完整页面内容',
'problem.a2': '直接使用你 Chrome 的登录状态、Cookie',
'problem.a3': 'JavaScript 动态渲染的内容完全可访问',
'problem.a4': '小红书、微信公众号、知乎 — 全部支持',
'problem.a5': '点击、滚动、输入、上传文件、截图',
'problem.a6': '并行子 Agent,各自独立操作浏览器标签页',
'arch.label': '工作原理',
'arch.title': '三层架构,零额外浏览器。',
'arch.desc': 'web-access 通过 CDP 协议直连你的日常 Chrome。不用 Puppeteer,不用无头浏览器,不用单独的配置文件。Agent 看到的就是你看到的。',
'arch.agent': 'AI Agent',
'arch.agentSub': 'Claude Code、Cursor 等',
'arch.proxy': 'CDP Proxy',
'arch.proxySub': '轻量级本地代理',
'arch.chrome': '你的 Chrome',
'arch.chromeSub': '登录态、Cookie、扩展',
'arch.see': '看',
'arch.seeDesc': '查询 DOM、提取文本、发现链接和页面结构',
'arch.act': '做',
'arch.actDesc': '点击按钮、滚动页面、填写表单、上传文件',
'arch.read': '读',
'arch.readDesc': '提取内容、截图捕获、分析视频帧',
'cap.label': '核心能力',
'cap.title': 'Agent 上网所需的一切。',
'cap.research': '深度调研',
'cap.researchDesc': '多层搜索:搜索引擎结果 → 一手来源 → 全页提取。基于官方文档验证事实,而非二手报道。',
'cap.auth': '登录态访问',
'cap.authDesc': '在你已登录的 Chrome 中操作。后台面板、管理系统、内部工具 — 你能看到的,Agent 都能看到。',
'cap.extract': '突破反爬限制',
'cap.extractDesc': '小红书、微信公众号、知乎、微博 — 这些封锁静态抓取的平台,通过真实浏览器自然访问。',
'cap.interact': '完整交互',
'cap.interactDesc': '点击、滚动、输入、上传文件、处理弹窗。像人一样完成多步流程 — 表单提交、结账流程、配置向导。',
'cap.video': '视频分析',
'cap.videoDesc': '跳转到任意时间点、截取帧画面、视觉分析视频内容。从教程、演示和录屏中提取信息。',
'cap.parallel': '并行子 Agent',
'cap.parallelDesc': '生成多个 Agent,各自拥有独立的浏览器标签页。同时调研 N 个主题 — 总耗时等于最慢的单个任务。',
'api.label': '底层实现',
'api.title': '简洁的 HTTP API,强大的原语。',
'api.desc': 'CDP Proxy 暴露一套简洁的 REST API。Agent 用 curl 调用 — 无 SDK,无依赖,无复杂度。',
'api.new': '打开新的后台标签页',
'api.eval': '在页面上下文中执行 JavaScript',
'api.click': '通过 CSS 选择器点击元素',
'api.screenshot': '截取页面或视频帧',
'api.scroll': '滚动到指定位置或方向',
'api.navigate': '导航到新 URL',
'api.setFiles': '上传本地文件到文件输入框',
'api.close': '关闭标签页',
'install.label': '开始使用',
'install.title': '两分钟,完整联网能力。',
'install.step1': '安装 Skill',
'install.step2': '开启 Chrome 调试',
'install.step2desc': '在 Chrome 地址栏打开 <code>chrome://inspect/#remote-debugging</code>,勾选 <strong>「允许远程调试」</strong>,重启 Chrome。',
'install.step3': '开始使用',
'install.step3desc': '让你的 Agent 去浏览、搜索或操作任何网站。Skill 会自动激活。',
'install.req': '环境要求',
'faq.title': '常见问题',
'faq.q1': '安全吗?Agent 会搞乱我的浏览器吗?',
'faq.a1': '所有操作都在 Agent 自己创建的<strong>新后台标签页</strong>中进行。你现有的标签页不会被触碰。任务完成后,Agent 会关闭自己创建的标签页。',
'faq.q2': '支持内网/私有网站吗?',
'faq.a2': '支持。因为使用的是你真实 Chrome 的所有登录状态,它可以访问你已登录的任何网站 — 包括企业内网、管理后台和内部工具。',
'faq.q3': '网站会检测并封锁吗?',
'faq.a3': '这个 Skill 通过你的真实浏览器操作,而不是无头自动化工具。大多数网站无法区分它和正常浏览。但极其激进的反机器人系统仍可能标记自动化模式。',
'faq.q4': '多个 Agent 能同时使用浏览器吗?',
'faq.a4': '可以。每个子 Agent 创建自己的标签页,独立操作。它们共享一个 Chrome 实例和一个 Proxy,通过不同的 tab ID 操作不同的标签页 — 没有冲突。',
'faq.q5': '只支持 Claude Code 吗?',
'faq.a5': 'web-access 遵循 SKILL.md 标准。它兼容所有支持 skills 的 Agent 工具 — Claude Code、Cursor、Gemini CLI、Codex CLI 等。',
'faq.q6': '支持无头/服务器环境吗?',
'faq.a6': 'web-access 设计用于有 Chrome 运行的本地开发环境。服务器环境需要一个可通过 CDP 访问的有界面的 Chrome 会话。',
}
};
let currentLang = 'en';
function setLang(lang) {
currentLang = lang;
const data = i18n[lang];
document.querySelectorAll('[data-i18n]').forEach(el => {
const key = el.dataset.i18n;
if (!data[key]) return;
// 对于包含 HTML 的内容(install step2desc, FAQ answers 等),使用 innerHTML
if (el.dataset.i18nHtml === 'true' || key.startsWith('faq.a') || key === 'install.step2desc') {
el.innerHTML = data[key];
} else {
el.textContent = data[key];
}
});
// 更新按钮文字
const btn = document.getElementById('lang-toggle');
btn.textContent = lang === 'en' ? 'EN / 中' : '中 / EN';
// 持久化
localStorage.setItem('wa-lang', lang);
}
// ---- Copy to Clipboard ----
document.querySelectorAll('.copy-btn').forEach(btn => {
btn.addEventListener('click', () => {
const text = btn.dataset.copy;
navigator.clipboard.writeText(text).then(() => {
btn.classList.add('copied');
const icon = btn.innerHTML;
btn.innerHTML = '<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="20 6 9 17 4 12"/></svg>';
setTimeout(() => {
btn.classList.remove('copied');
btn.innerHTML = icon;
}, 2000);
});
});
});
// ---- Scroll Reveal ----
const observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
entry.target.classList.add('visible');
}
});
}, { threshold: 0.1, rootMargin: '0px 0px -50px 0px' });
document.querySelectorAll('.section-inner, .logos-section').forEach(el => {
observer.observe(el);
});
// ---- Terminal Demo Animation ----
// demos 支持双语:prompt 和 output.text 可以是 {en, zh} 对象或纯字符串
const demos = [
{
prompt: { en: 'Help me research the latest AI agent frameworks', zh: '帮我调研最新的 AI Agent 框架' },
url: 'google.com/search?q=ai+agent+frameworks',
output: [
{ text: { en: 'Opening new tab...', zh: '打开新标签页...' }, cls: 't-dim' },
{ text: { en: 'Searching Google for "AI agent frameworks 2026"', zh: '在 Google 搜索「AI agent frameworks 2026」' }, cls: 't-info' },
{ text: { en: 'Found 12 results. Extracting top 5...', zh: '找到 12 条结果,提取前 5 条...' }, cls: 't-info' },
{ text: '', cls: '' },
{ text: { en: 'Opening primary sources in parallel (3 tabs)...', zh: '并行打开 3 个一手来源...' }, cls: 't-accent' },
{ text: { en: 'Extracting content from official docs...', zh: '从官方文档提取内容...' }, cls: 't-info' },
{ text: { en: '✓ Research complete — 3 sources verified', zh: '✓ 调研完成 — 3 个来源已验证' }, cls: 't-success' },
]
},
{
prompt: { en: 'Check my GitHub notifications and summarize', zh: '看看我的 GitHub 通知,给我总结一下' },
url: 'github.com/notifications',
output: [
{ text: { en: 'Connecting to your Chrome...', zh: '连接你的 Chrome...' }, cls: 't-dim' },
{ text: { en: 'Navigating to GitHub notifications (logged in)', zh: '打开 GitHub 通知页(已登录)' }, cls: 't-info' },
{ text: { en: 'Found 8 unread notifications', zh: '发现 8 条未读通知' }, cls: 't-info' },
{ text: '', cls: '' },
{ text: { en: '3 PR reviews requested', zh: '3 个 PR 等待 review' }, cls: 't-accent' },
{ text: { en: '2 issue mentions, 3 release updates', zh: '2 条 issue 提及,3 个版本更新' }, cls: 't-accent' },
{ text: { en: '✓ Summary ready', zh: '✓ 总结完成' }, cls: 't-success' },
]
},
{
prompt: { en: 'Grab the top trending posts from Xiaohongshu about AI', zh: '去小红书搜一下 AI 相关的热门帖子' },
url: 'xiaohongshu.com/search_result/?keyword=AI',
output: [
{ text: { en: 'CDP mode — anti-scraping platform detected', zh: 'CDP 模式 — 检测到反爬平台' }, cls: 't-dim' },
{ text: { en: 'Opening Xiaohongshu in real browser...', zh: '在真实浏览器中打开小红书...' }, cls: 't-info' },
{ text: { en: 'Scrolling feed to load content...', zh: '滚动信息流加载内容...' }, cls: 't-info' },
{ text: { en: 'Extracting 10 posts with engagement data...', zh: '提取 10 条帖子及互动数据...' }, cls: 't-info' },
{ text: '', cls: '' },
{ text: { en: 'Downloading cover images...', zh: '下载封面图...' }, cls: 't-accent' },
{ text: { en: '✓ 10 posts extracted with full metadata', zh: '✓ 10 条帖子提取完成' }, cls: 't-success' },
]
}
];
let currentDemo = 0;
function typeText(element, text, speed = 35) {
return new Promise(resolve => {
let i = 0;
element.textContent = '';
if (!text) { resolve(); return; }
const timer = setInterval(() => {
element.textContent += text[i];
i++;
if (i >= text.length) {
clearInterval(timer);
resolve();
}
}, speed);
});
}
function showOutputLines(container, lines, delay = 400) {
return new Promise(resolve => {
container.innerHTML = '';
let i = 0;
const timer = setInterval(() => {
if (i >= lines.length) {
clearInterval(timer);
resolve();
return;
}
const line = lines[i];
const div = document.createElement('div');
div.className = line.cls || '';
div.textContent = line.text;
div.style.opacity = '0';
div.style.transform = 'translateY(4px)';
container.appendChild(div);
requestAnimationFrame(() => {
div.style.transition = 'opacity 0.3s, transform 0.3s';
div.style.opacity = '1';
div.style.transform = 'translateY(0)';
});
i++;
}, delay);
});
}
// 从双语对象或纯字符串中取当前语言的文本
function t(val) {
if (!val) return '';
if (typeof val === 'string') return val;
return val[currentLang] || val.en || '';
}
async function runDemo() {
const promptEl = document.getElementById('demo-prompt');
const outputEl = document.getElementById('demo-output');
const urlEl = document.getElementById('browser-url-text');
const cursorEl = document.querySelector('.t-cursor');
const browserBody = document.getElementById('browser-demo');
while (true) {
const demo = demos[currentDemo];
outputEl.innerHTML = '';
cursorEl.style.display = 'inline';
await typeText(promptEl, t(demo.prompt), 30);
await sleep(500);
cursorEl.style.display = 'none';
urlEl.textContent = demo.url;
browserBody.innerHTML = `
<div class="browser-loading">
<div class="browser-skeleton">
<div class="skel-nav" style="animation: pulse 1.5s ease-in-out infinite"></div>
<div class="skel-hero-block" style="animation: pulse 1.5s ease-in-out infinite 0.2s"></div>
<div class="skel-lines">
<div class="skel-line w80" style="animation: pulse 1.5s ease-in-out infinite 0.3s"></div>
<div class="skel-line w60" style="animation: pulse 1.5s ease-in-out infinite 0.4s"></div>
<div class="skel-line w70" style="animation: pulse 1.5s ease-in-out infinite 0.5s"></div>
</div>
</div>
</div>
`;
// 把双语 output 转为当前语言的纯文本
const localizedOutput = demo.output.map(line => ({
text: t(line.text),
cls: line.cls
}));
await showOutputLines(outputEl, localizedOutput, 500);
await sleep(4000);
currentDemo = (currentDemo + 1) % demos.length;
}
}
function sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
// 添加 pulse 动画
const style = document.createElement('style');
style.textContent = `
@keyframes pulse {
0%, 100% { opacity: 0.3; }
50% { opacity: 0.6; }
}
`;
document.head.appendChild(style);
// 页面加载后启动
document.addEventListener('DOMContentLoaded', () => {
// 语言切换
const langBtn = document.getElementById('lang-toggle');
langBtn.addEventListener('click', () => {
setLang(currentLang === 'en' ? 'zh' : 'en');
});
// 读取持久化语言设置
const saved = localStorage.getItem('wa-lang');
if (saved && saved !== 'en') {
setLang(saved);
}
// 延迟启动 demo 动画
setTimeout(runDemo, 1000);
});
// ---- Nav Scroll Effect ----
let lastScroll = 0;
window.addEventListener('scroll', () => {
const nav = document.getElementById('nav');
const scrollY = window.scrollY;
if (scrollY > 100) {
nav.style.borderBottomColor = 'var(--border)';
} else {
nav.style.borderBottomColor = 'var(--border-subtle)';
}
lastScroll = scrollY;
}, { passive: true });
/* ============================================
web-access — Landing Page Styles
============================================ */
:root {
/* Colors — Light theme inspired by impeccable.style */
--bg: #fafafa;
--bg-elevated: #ffffff;
--bg-card: #f5f5f4;
--bg-card-hover: #eeeeec;
--border: #e5e5e3;
--border-subtle: #ededeb;
--text: #1a1a1a;
--text-secondary: #4a4a4a;
--text-tertiary: #8a8a8a;
--accent: #1a1a1a;
--accent-dim: rgba(26, 26, 26, 0.06);
--accent-glow: rgba(26, 26, 26, 0.03);
--green: #16a34a;
--green-dim: rgba(22, 163, 74, 0.08);
--red: #dc2626;
--red-dim: rgba(220, 38, 38, 0.08);
--yellow: #ca8a04;
--pink: #e84393;
/* Typography */
--font-sans: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif;
--font-mono: 'JetBrains Mono', 'SF Mono', 'Fira Code', monospace;
/* Spacing */
--section-padding: clamp(4rem, 8vw, 7rem) clamp(1.5rem, 5vw, 4rem);
--content-max: 1120px;
--radius: 12px;
--radius-sm: 8px;
--radius-xs: 6px;
}
/* ---- Reset ---- */
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
html {
scroll-behavior: smooth;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
body {
font-family: var(--font-sans);
background: var(--bg);
color: var(--text);
line-height: 1.6;
overflow-x: hidden;
}
a { color: var(--accent); text-decoration: none; transition: opacity 0.2s; }
a:hover { opacity: 0.8; }
code { font-family: var(--font-mono); }
/* ---- Nav ---- */
.nav {
position: fixed;
top: 0;
left: 0;
right: 0;
z-index: 100;
background: rgba(250, 250, 250, 0.85);
backdrop-filter: blur(20px) saturate(1.2);
-webkit-backdrop-filter: blur(20px) saturate(1.2);
border-bottom: 1px solid var(--border-subtle);
}
.nav-inner {
max-width: var(--content-max);
margin: 0 auto;
padding: 0 clamp(1.5rem, 5vw, 4rem);
height: 56px;
display: flex;
align-items: center;
justify-content: space-between;
}
.nav-logo {
font-family: var(--font-mono);
font-weight: 600;
font-size: 0.95rem;
color: var(--text);
letter-spacing: -0.02em;
}
.nav-links {
display: flex;
align-items: center;
gap: 1.75rem;
}
.nav-links a {
font-size: 0.825rem;
color: var(--text-tertiary);
transition: color 0.2s;
font-weight: 500;
}
.nav-links a:hover { color: var(--text); opacity: 1; }
.nav-github {
display: flex;
align-items: center;
gap: 6px;
padding: 5px 12px;
background: var(--bg-card);
border: 1px solid var(--border);
border-radius: 999px;
font-size: 0.8rem;
color: var(--text-secondary) !important;
transition: border-color 0.2s, background 0.2s;
}
.nav-github:hover {
border-color: var(--text-tertiary);
background: var(--bg-card-hover);
}
.star-count { font-family: var(--font-mono); font-weight: 500; }
.lang-toggle {
font-family: var(--font-mono);
font-size: 0.75rem;
font-weight: 500;
color: var(--text-tertiary);
background: var(--bg-card);
border: 1px solid var(--border);
border-radius: 999px;
padding: 4px 12px;
cursor: pointer;
transition: border-color 0.2s, color 0.2s;
white-space: nowrap;
}
.lang-toggle:hover {
border-color: var(--text-tertiary);
color: var(--text);
}
/* ---- Hero ---- */
.hero {
max-width: var(--content-max);
margin: 0 auto;
padding: 8rem clamp(1.5rem, 5vw, 4rem) 3rem;
display: grid;
grid-template-columns: 1fr 1fr;
gap: 4rem;
align-items: center;
min-height: 90vh;
}
.hero-badge {
display: inline-block;
font-family: var(--font-mono);
font-size: 0.7rem;
font-weight: 600;
letter-spacing: 0.08em;
text-transform: uppercase;
color: var(--pink);
background: rgba(232, 67, 147, 0.06);
padding: 4px 12px;
border-radius: 999px;
border: 1px solid rgba(232, 67, 147, 0.15);
margin-bottom: 1.25rem;
}
.hero-title {
font-family: var(--font-mono);
font-size: clamp(2.5rem, 5vw, 3.5rem);
font-weight: 600;
letter-spacing: -0.04em;
line-height: 1.1;
margin-bottom: 0.75rem;
color: var(--text);
}
.hero-subtitle {
font-size: clamp(1.1rem, 2vw, 1.35rem);
color: var(--text-secondary);
margin-bottom: 0.75rem;
font-weight: 500;
}
.hero-desc {
color: var(--text-tertiary);
font-size: 0.95rem;
max-width: 440px;
margin-bottom: 2rem;
line-height: 1.7;
}
.hero-actions {
display: flex;
align-items: center;
gap: 1rem;
margin-bottom: 2rem;
flex-wrap: wrap;
}
.hero-install {
display: flex;
align-items: center;
gap: 8px;
background: var(--bg-elevated);
border: 1px solid var(--border);
border-radius: var(--radius-sm);
padding: 10px 16px;
font-family: var(--font-mono);
font-size: 0.85rem;
transition: border-color 0.2s;
}
.hero-install:hover { border-color: var(--text-tertiary); }
.install-prompt { color: var(--text-tertiary); font-weight: 600; }
.hero-install code { color: var(--text); }
.copy-btn {
display: flex;
align-items: center;
justify-content: center;
background: none;
border: none;
color: var(--text-tertiary);
cursor: pointer;
padding: 4px;
border-radius: 4px;
transition: color 0.2s, background 0.2s;
}
.copy-btn:hover { color: var(--text); background: var(--bg-card); }
.copy-btn.copied { color: var(--green); }
.hero-cta {
display: inline-flex;
align-items: center;
font-size: 0.85rem;
font-weight: 500;
color: var(--text-tertiary) !important;
transition: color 0.2s;
}
.hero-cta:hover { color: var(--text) !important; opacity: 1; }
.hero-cta::after {
content: '\2192';
margin-left: 6px;
transition: transform 0.2s;
}
.hero-cta:hover::after { transform: translateX(3px); }
.hero-stats {
display: flex;
align-items: center;
gap: 1rem;
}
.stat {
font-size: 0.8rem;
color: var(--text-tertiary);
}
.stat strong {
color: var(--text-secondary);
font-family: var(--font-mono);
font-weight: 600;
}
.stat-sep {
width: 3px;
height: 3px;
border-radius: 50%;
background: var(--text-tertiary);
opacity: 0.5;
}
/* Hero Visual */
.hero-visual {
position: relative;
}
.terminal-window, .browser-window {
border: 1px solid var(--border);
border-radius: var(--radius);
overflow: hidden;
background: #1a1a1a;
color: #e0e0e0;
box-shadow: 0 8px 40px rgba(0,0,0,0.08), 0 2px 8px rgba(0,0,0,0.04);
}
.terminal-window {
position: relative;
z-index: 2;
}
.browser-window {
position: absolute;
top: 40px;
right: -30px;
width: 75%;
z-index: 1;
opacity: 0.7;
transform: perspective(800px) rotateY(-5deg);
transition: opacity 0.4s, transform 0.4s;
}
.browser-window:hover {
opacity: 0.9;
transform: perspective(800px) rotateY(-2deg);
}
.terminal-header, .browser-header {
display: flex;
align-items: center;
padding: 10px 14px;
background: #222;
border-bottom: 1px solid #333;
gap: 10px;
}
.terminal-dots {
display: flex;
gap: 6px;
}
.dot {
width: 10px;
height: 10px;
border-radius: 50%;
}
.dot.red { background: #ff5f57; }
.dot.yellow { background: #febc2e; }
.dot.green { background: #28c840; }
.terminal-title {
font-family: var(--font-mono);
font-size: 0.7rem;
color: #888;
margin-left: auto;
margin-right: auto;
}
.terminal-body {
padding: 1.25rem;
font-family: var(--font-mono);
font-size: 0.8rem;
min-height: 200px;
}
.terminal-line {
display: flex;
align-items: center;
gap: 8px;
}
.t-prompt { color: #22d3ee; font-weight: 600; }
.t-text { color: #ccc; }
.t-cursor {
color: #22d3ee;
animation: blink 1s step-end infinite;
font-weight: 300;
}
@keyframes blink {
0%, 100% { opacity: 1; }
50% { opacity: 0; }
}
.terminal-output {
margin-top: 0.75rem;
color: #999;
line-height: 1.8;
font-size: 0.75rem;
}
.terminal-output .t-success { color: #4ade80; }
.terminal-output .t-info { color: #ccc; }
.terminal-output .t-accent { color: #22d3ee; }
.terminal-output .t-dim { color: #666; }
.browser-url {
display: flex;
align-items: center;
gap: 6px;
flex: 1;
background: #111;
padding: 4px 10px;
border-radius: 6px;
font-family: var(--font-mono);
font-size: 0.7rem;
color: #888;
}
.browser-body {
padding: 1.25rem;
min-height: 140px;
}
.browser-skeleton {
opacity: 0.5;
}
.skel-nav {
height: 8px;
width: 60%;
background: #333;
border-radius: 4px;
margin-bottom: 16px;
}
.skel-hero-block {
height: 40px;
width: 80%;
background: #333;
border-radius: 6px;
margin-bottom: 14px;
}
.skel-lines {
display: flex;
flex-direction: column;
gap: 8px;
}
.skel-line {
height: 6px;
background: #333;
border-radius: 3px;
}
.skel-line.w80 { width: 80%; }
.skel-line.w60 { width: 60%; }
.skel-line.w70 { width: 70%; }
/* ---- Logos ---- */
.logos-section {
max-width: var(--content-max);
margin: 0 auto;
padding: 0 clamp(1.5rem, 5vw, 4rem) 3rem;
text-align: center;
}
.logos-label {
font-size: 0.7rem;
text-transform: uppercase;
letter-spacing: 0.12em;
color: var(--text-tertiary);
margin-bottom: 1.25rem;
font-weight: 500;
}
.logos-row {
display: flex;
justify-content: center;
flex-wrap: wrap;
gap: 0.5rem;
}
.logo-item {
font-family: var(--font-mono);
font-size: 0.75rem;
color: var(--text-tertiary);
padding: 6px 16px;
border: 1px solid var(--border-subtle);
border-radius: 999px;
transition: border-color 0.2s, color 0.2s;
}
.logo-item:hover {
border-color: var(--border);
color: var(--text-secondary);
}
/* ---- Sections ---- */
.section {
max-width: var(--content-max);
margin: 0 auto;
}
.section-inner {
padding: var(--section-padding);
border-top: 1px solid var(--border-subtle);
}
.section-label {
font-family: var(--font-mono);
font-size: 0.7rem;
font-weight: 600;
letter-spacing: 0.1em;
text-transform: uppercase;
color: var(--pink);
margin-bottom: 1rem;
}
.section-title {
font-size: clamp(1.5rem, 3vw, 2.25rem);
font-weight: 600;
letter-spacing: -0.03em;
line-height: 1.2;
margin-bottom: 0.75rem;
}
.section-desc {
color: var(--text-tertiary);
font-size: 0.95rem;
max-width: 560px;
line-height: 1.7;
margin-bottom: 2.5rem;
}
/* ---- Problem Section ---- */
.comparison-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 1.25rem;
}
.comparison-card {
padding: 1.75rem;
border-radius: var(--radius);
border: 1px solid var(--border);
background: var(--bg-elevated);
}
.comparison-header {
display: flex;
align-items: center;
gap: 8px;
font-weight: 600;
font-size: 0.9rem;
margin-bottom: 1.25rem;
padding-bottom: 1rem;
border-bottom: 1px solid var(--border-subtle);
}
.without .comparison-icon {
color: var(--red);
font-size: 0.85rem;
width: 22px;
height: 22px;
display: inline-flex;
align-items: center;
justify-content: center;
background: var(--red-dim);
border-radius: 50%;
}
.with .comparison-icon {
color: var(--green);
font-size: 0.85rem;
width: 22px;
height: 22px;
display: inline-flex;
align-items: center;
justify-content: center;
background: var(--green-dim);
border-radius: 50%;
}
.with {
border-color: rgba(22, 163, 74, 0.25);
background: linear-gradient(135deg, var(--bg-elevated), rgba(22, 163, 74, 0.04));
}
.comparison-list {
list-style: none;
display: flex;
flex-direction: column;
gap: 0.6rem;
}
.comparison-list li {
font-size: 0.85rem;
color: var(--text-secondary);
padding-left: 1.25rem;
position: relative;
line-height: 1.5;
}
.without .comparison-list li::before {
content: '\2013';
position: absolute;
left: 0;
color: var(--text-tertiary);
}
.with .comparison-list li::before {
content: '\2713';
position: absolute;
left: 0;
color: var(--green);
font-size: 0.8rem;
}
/* ---- Architecture Section ---- */
.arch-diagram {
display: flex;
align-items: center;
justify-content: center;
gap: 0;
margin-bottom: 3rem;
padding: 2.5rem 1.5rem;
background: var(--bg-elevated);
border: 1px solid var(--border);
border-radius: var(--radius);
}
.arch-node {
text-align: center;
padding: 1.5rem 2rem;
position: relative;
}
.arch-icon {
width: 56px;
height: 56px;
display: flex;
align-items: center;
justify-content: center;
margin: 0 auto 0.75rem;
border-radius: 14px;
border: 1px solid var(--border);
background: var(--bg-card);
color: var(--text-secondary);
transition: border-color 0.3s, color 0.3s;
}
.arch-node:hover .arch-icon {
border-color: var(--accent);
color: var(--accent);
}
.arch-label {
font-weight: 600;
font-size: 0.9rem;
margin-bottom: 0.25rem;
}
.arch-sub {
font-size: 0.75rem;
color: var(--text-tertiary);
}
.arch-connector {
display: flex;
flex-direction: column;
align-items: center;
gap: 6px;
min-width: 80px;
}
.arch-line {
width: 60px;
height: 1px;
background: linear-gradient(90deg, transparent, var(--accent), transparent);
position: relative;
}
.arch-line::after {
content: '';
position: absolute;
right: -3px;
top: -3px;
width: 7px;
height: 7px;
border-top: 1px solid var(--accent);
border-right: 1px solid var(--accent);
transform: rotate(45deg);
}
.arch-flow-label {
font-family: var(--font-mono);
font-size: 0.65rem;
color: var(--text-tertiary);
text-transform: uppercase;
letter-spacing: 0.05em;
}
.arch-features {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 1.25rem;
}
.arch-feature {
padding: 1.5rem;
background: var(--bg-elevated);
border: 1px solid var(--border);
border-radius: var(--radius);
transition: border-color 0.2s;
}
.arch-feature:hover { border-color: var(--text-tertiary); }
.feature-icon {
color: var(--pink);
margin-bottom: 0.75rem;
}
.arch-feature h3 {
font-size: 0.95rem;
margin-bottom: 0.4rem;
}
.arch-feature p {
font-size: 0.825rem;
color: var(--text-tertiary);
line-height: 1.6;
}
/* ---- Capabilities Section ---- */
.cap-grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 1.25rem;
}
.cap-card {
padding: 1.75rem;
background: var(--bg-elevated);
border: 1px solid var(--border);
border-radius: var(--radius);
transition: border-color 0.3s, transform 0.3s;
display: flex;
flex-direction: column;
}
.cap-card:hover {
border-color: var(--text-tertiary);
transform: translateY(-2px);
}
.cap-icon-wrap {
width: 44px;
height: 44px;
display: flex;
align-items: center;
justify-content: center;
background: var(--bg-card);
border-radius: 10px;
color: var(--text);
margin-bottom: 1rem;
}
.cap-card h3 {
font-size: 0.95rem;
margin-bottom: 0.5rem;
}
.cap-card > p {
font-size: 0.825rem;
color: var(--text-tertiary);
line-height: 1.65;
flex: 1;
}
.cap-example {
margin-top: 1rem;
padding-top: 1rem;
border-top: 1px solid var(--border-subtle);
}
.cap-example code {
font-size: 0.72rem;
color: var(--text-tertiary);
line-height: 1.5;
display: block;
opacity: 0.7;
}
/* ---- API Section ---- */
.api-grid {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 0.75rem;
}
.api-card {
padding: 1rem 1.25rem;
background: var(--bg-elevated);
border: 1px solid var(--border);
border-radius: var(--radius-sm);
transition: border-color 0.2s;
display: flex;
flex-direction: column;
gap: 0.4rem;
}
.api-card:hover { border-color: var(--text-tertiary); }
.api-method {
font-family: var(--font-mono);
font-size: 0.6rem;
font-weight: 600;
letter-spacing: 0.05em;
color: var(--pink);
opacity: 0.7;
}
.api-endpoint {
font-size: 0.82rem;
color: var(--text);
font-weight: 500;
}
.api-desc {
font-size: 0.72rem;
color: var(--text-tertiary);
line-height: 1.4;
}
/* ---- Install Section ---- */
.install-steps {
display: flex;
flex-direction: column;
gap: 1.5rem;
margin-bottom: 2.5rem;
}
.install-step {
display: flex;
gap: 1.25rem;
align-items: flex-start;
}
.step-num {
width: 36px;
height: 36px;
display: flex;
align-items: center;
justify-content: center;
background: var(--bg-card);
color: var(--text);
font-family: var(--font-mono);
font-weight: 600;
font-size: 0.85rem;
border-radius: 10px;
flex-shrink: 0;
border: 1px solid var(--border);
}
.step-content h3 {
font-size: 0.95rem;
margin-bottom: 0.5rem;
}
.step-content p {
font-size: 0.85rem;
color: var(--text-tertiary);
line-height: 1.6;
}
.step-content p code {
font-size: 0.8rem;
color: var(--text);
background: var(--bg-card);
padding: 1px 6px;
border-radius: 4px;
}
.step-content p strong {
color: var(--text-secondary);
}
.code-block {
display: inline-flex;
align-items: center;
gap: 10px;
background: var(--bg-card);
border: 1px solid var(--border);
padding: 8px 14px;
border-radius: var(--radius-xs);
font-family: var(--font-mono);
font-size: 0.85rem;
color: var(--text-secondary);
}
.install-req {
padding: 1.25rem 1.5rem;
background: var(--bg-elevated);
border: 1px solid var(--border);
border-radius: var(--radius-sm);
}
.install-req h4 {
font-size: 0.8rem;
color: var(--text-tertiary);
text-transform: uppercase;
letter-spacing: 0.08em;
margin-bottom: 0.75rem;
}
.req-items {
display: flex;
gap: 0.75rem;
flex-wrap: wrap;
}
.req-item {
font-family: var(--font-mono);
font-size: 0.78rem;
color: var(--text-secondary);
padding: 4px 12px;
background: var(--bg-card);
border-radius: 999px;
border: 1px solid var(--border-subtle);
}
/* ---- FAQ Section ---- */
.faq-list {
display: flex;
flex-direction: column;
gap: 0;
}
.faq-item {
border-bottom: 1px solid var(--border-subtle);
}
.faq-item:first-child { border-top: 1px solid var(--border-subtle); }
.faq-item summary {
padding: 1.25rem 0;
font-size: 0.9rem;
font-weight: 500;
cursor: pointer;
list-style: none;
display: flex;
align-items: center;
justify-content: space-between;
color: var(--text-secondary);
transition: color 0.2s;
}
.faq-item summary:hover { color: var(--text); }
.faq-item summary::-webkit-details-marker { display: none; }
.faq-item summary::after {
content: '+';
font-family: var(--font-mono);
font-size: 1.1rem;
color: var(--text-tertiary);
transition: transform 0.3s;
flex-shrink: 0;
margin-left: 1rem;
}
.faq-item[open] summary::after {
transform: rotate(45deg);
}
.faq-item p {
padding: 0 0 1.25rem;
font-size: 0.85rem;
color: var(--text-tertiary);
line-height: 1.7;
max-width: 640px;
}
.faq-item p strong { color: var(--text-secondary); }
/* ---- Footer ---- */
.footer {
border-top: 1px solid var(--border-subtle);
margin-top: 2rem;
}
.footer-inner {
max-width: var(--content-max);
margin: 0 auto;
padding: 2rem clamp(1.5rem, 5vw, 4rem);
display: flex;
align-items: center;
justify-content: space-between;
}
.footer-left {
display: flex;
align-items: center;
gap: 1rem;
}
.footer-logo {
font-family: var(--font-mono);
font-weight: 600;
font-size: 0.85rem;
color: var(--text-secondary);
}
.footer-author {
font-size: 0.8rem;
color: var(--text-tertiary);
}
.footer-right {
display: flex;
gap: 1.5rem;
}
.footer-right a {
font-size: 0.8rem;
color: var(--text-tertiary);
transition: color 0.2s;
}
.footer-right a:hover { color: var(--text); opacity: 1; }
/* ---- Responsive ---- */
@media (max-width: 900px) {
.hero {
grid-template-columns: 1fr;
min-height: auto;
padding-top: 6rem;
gap: 3rem;
}
.hero-visual {
max-width: 480px;
}
.browser-window {
position: relative;
top: -20px;
right: 0;
width: 100%;
margin-left: 10%;
transform: none;
}
.browser-window:hover { transform: none; }
.comparison-grid { grid-template-columns: 1fr; }
.cap-grid { grid-template-columns: 1fr 1fr; }
.api-grid { grid-template-columns: repeat(2, 1fr); }
.arch-diagram {
flex-direction: column;
gap: 0;
}
.arch-connector {
transform: rotate(90deg);
min-width: auto;
min-height: 60px;
}
}
@media (max-width: 600px) {
.nav-links a:not(.nav-github) { display: none; }
.cap-grid { grid-template-columns: 1fr; }
.api-grid { grid-template-columns: 1fr; }
.arch-features { grid-template-columns: 1fr; }
.hero-stats { flex-wrap: wrap; gap: 0.5rem; }
.footer-inner { flex-direction: column; gap: 1rem; text-align: center; }
}
/* ---- Animations ---- */
@keyframes fadeInUp {
from { opacity: 0; transform: translateY(20px); }
to { opacity: 1; transform: translateY(0); }
}
.hero-content > * {
animation: fadeInUp 0.6s ease-out both;
}
.hero-content > :nth-child(1) { animation-delay: 0.1s; }
.hero-content > :nth-child(2) { animation-delay: 0.15s; }
.hero-content > :nth-child(3) { animation-delay: 0.2s; }
.hero-content > :nth-child(4) { animation-delay: 0.25s; }
.hero-content > :nth-child(5) { animation-delay: 0.3s; }
.hero-content > :nth-child(6) { animation-delay: 0.35s; }
.hero-visual {
animation: fadeInUp 0.8s ease-out 0.4s both;
}
/* Scroll animations */
.section-inner,
.logos-section {
opacity: 0;
transform: translateY(30px);
transition: opacity 0.6s ease-out, transform 0.6s ease-out;
}
.section-inner.visible,
.logos-section.visible {
opacity: 1;
transform: translateY(0);
}
/* Glow effect behind hero terminal */
.hero-visual::before {
content: '';
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: 400px;
height: 400px;
background: radial-gradient(circle, var(--accent-glow) 0%, transparent 70%);
pointer-events: none;
z-index: 0;
}