perch-design / index.html
hongcoding's picture
feat: Warm Paper 设计系统开源(48 CSS + 令牌 + 实时预览)
f402bb3 verified
Raw History Blame Contribute Delete
9.95 kB
<!doctype html>
<html lang="zh-CN">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>栖木 Perch · Warm Paper 设计系统预览</title>
<style>
/* ── 设计令牌(内联自 tokens/theme.css,保证单文件可预览)──────── */
:root {
--bg: #faf9f5; --bg-elevated: #f0eee6; --bg-sunken: #e8e5db;
--border: #e3dfd3; --text: #1f1e1b; --text-secondary: #6b6862; --text-tertiary: #9c988d;
--accent: #d97757; --accent-soft: #f4e3dc;
--success: #7a9b76; --warning: #c9a227; --danger: #c05b4d;
--font-serif: "Source Serif 4", "Songti SC", Georgia, serif;
--font-sans: "Inter", "PingFang SC", system-ui, -apple-system, sans-serif;
--font-mono: "JetBrains Mono", "SF Mono", Menlo, monospace;
--radius-control: 10px; --radius-card: 14px; --radius-float: 16px; --radius-chip: 8px;
--ease: cubic-bezier(0.2, 0, 0, 1); --dur: 150ms;
--shadow-float: 0 4px 24px rgba(31, 30, 27, 0.08);
--glass: rgba(250, 249, 245, 0.85); --glass-blur: blur(20px);
--scrim: rgba(31, 30, 27, 0.28);
}
[data-theme="dark"] {
--bg: #262521; --bg-elevated: #2f2e29; --bg-sunken: #1e1d1a;
--border: #3b3a34; --text: #edeae0; --text-secondary: #a8a49a; --text-tertiary: #79766d;
--accent: #e08a6e; --accent-soft: #45342d;
--shadow-float: 0 4px 24px rgba(0, 0, 0, 0.35);
--glass: rgba(38, 37, 33, 0.85); --scrim: rgba(0, 0, 0, 0.4);
}
* { box-sizing: border-box; }
html, body { margin: 0; }
body {
background: var(--bg); color: var(--text);
font-family: var(--font-sans); font-size: 14.5px; line-height: 1.65;
-webkit-font-smoothing: antialiased;
transition: background var(--dur) var(--ease), color var(--dur) var(--ease);
}
h1, h2, h3 { font-family: var(--font-serif); font-weight: 600; letter-spacing: -0.01em; margin: 0; }
.wrap { max-width: 880px; margin: 0 auto; padding: 48px 24px 96px; }
.topbar { display: flex; align-items: baseline; justify-content: space-between; gap: 16px; flex-wrap: wrap; margin-bottom: 40px; }
.topbar h1 { font-size: 30px; }
.topbar p { margin: 4px 0 0; color: var(--text-secondary); font-size: 13.5px; }
h2 { font-size: 19px; margin: 44px 0 16px; padding-bottom: 8px; border-bottom: 1px solid var(--border); }
.muted { color: var(--text-secondary); }
.tiny { font-size: 12.5px; color: var(--text-tertiary); }
/* 主题切换按钮 */
.toggle {
border: 1px solid var(--border); background: var(--bg-elevated); color: var(--text);
border-radius: 999px; padding: 7px 16px; font-size: 13px; cursor: pointer;
font-family: var(--font-sans); transition: all var(--dur) var(--ease);
}
.toggle:hover { border-color: var(--accent); }
/* 色板 */
.swatches { display: grid; grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); gap: 12px; }
.swatch { border-radius: var(--radius-card); border: 1px solid var(--border); overflow: hidden; background: var(--bg-elevated); }
.swatch .chip { height: 64px; }
.swatch .meta { padding: 8px 12px; font-size: 12px; }
.swatch .meta b { display: block; font-size: 12.5px; margin-bottom: 2px; }
.swatch .meta code { font-family: var(--font-mono); color: var(--text-secondary); font-size: 11.5px; }
/* 按钮 */
.row { display: flex; gap: 10px; flex-wrap: wrap; align-items: center; }
.btn {
border: 1px solid var(--border); background: var(--bg-elevated); color: var(--text);
border-radius: var(--radius-control); padding: 8px 18px; font-size: 13.5px; cursor: pointer;
font-family: var(--font-sans); transition: all var(--dur) var(--ease);
}
.btn:hover { transform: translateY(-1px); box-shadow: var(--shadow-float); }
.btn.primary { background: var(--accent); border-color: var(--accent); color: #fff; }
.btn.ghost { background: transparent; }
.btn.danger { color: var(--danger); border-color: var(--danger); background: transparent; }
.btn:disabled { opacity: 0.5; cursor: not-allowed; }
/* 卡片 */
.cards { display: grid; grid-template-columns: repeat(auto-fill, minmax(240px, 1fr)); gap: 16px; }
.card {
background: var(--bg-elevated); border: 1px solid var(--border);
border-radius: var(--radius-card); padding: 18px 20px;
transition: all var(--dur) var(--ease);
}
.card:hover { transform: translateY(-2px); box-shadow: var(--shadow-float); }
.card h3 { font-size: 15.5px; margin-bottom: 6px; }
.card p { margin: 0; font-size: 13px; color: var(--text-secondary); }
/* 徽章 */
.badge {
display: inline-flex; align-items: center; gap: 5px;
background: var(--accent-soft); color: var(--accent);
border-radius: var(--radius-chip); padding: 3px 10px; font-size: 12px;
}
/* 输入框 */
.input {
width: 100%; padding: 10px 14px; border-radius: var(--radius-control);
border: 1px solid var(--border); background: var(--bg); color: var(--text);
font-family: var(--font-sans); font-size: 14px; outline: none;
transition: border-color var(--dur) var(--ease);
}
.input:focus { border-color: var(--accent); }
/* 毛玻璃浮窗 */
.glass {
background: var(--glass); backdrop-filter: var(--glass-blur);
-webkit-backdrop-filter: var(--glass-blur);
border: 1px solid var(--border); border-radius: var(--radius-float);
padding: 20px; box-shadow: var(--shadow-float);
}
/* 圆角/阴影演示 */
.radii { display: flex; gap: 12px; flex-wrap: wrap; }
.radii div {
width: 96px; height: 56px; background: var(--bg-elevated);
border: 1px solid var(--border); display: grid; place-items: center;
font-size: 12px; color: var(--text-secondary);
}
footer { margin-top: 64px; padding-top: 20px; border-top: 1px solid var(--border); }
</style>
</head>
<body>
<div class="wrap">
<div class="topbar">
<div>
<h1>Warm Paper</h1>
<p>栖木 Perch 设计系统 · 零依赖预览(点击右上角切换深浅色)</p>
</div>
<button class="toggle" onclick="toggleTheme()">🌗 切换主题</button>
</div>
<h2>① 色板 · 只有一个强调色</h2>
<div class="swatches" id="swatches"></div>
<h2>② 字体 · 衬线标题 + 无衬线正文</h2>
<h1 style="font-size: 28px; margin-bottom: 8px;">这是一段衬线标题</h1>
<p class="muted" style="margin-top: 0;">
正文用无衬线字体(Inter / PingFang SC),行高 1.65。比常规 UI 略大,因为 AI 应用的主要行为是<b>阅读</b>。
</p>
<p class="tiny">代码用等宽字体:<code style="font-family: var(--font-mono);">const perch = await run_hybrid(...)</code></p>
<h2>③ 按钮</h2>
<div class="row">
<button class="btn primary">主要操作</button>
<button class="btn">次要操作</button>
<button class="btn ghost">幽灵按钮</button>
<button class="btn danger">危险操作</button>
<button class="btn" disabled>禁用</button>
</div>
<h2>④ 卡片</h2>
<div class="cards">
<div class="card">
<h3>混合流</h3>
<p>本机先答 0.08s,云端无缝接棒。</p>
</div>
<div class="card">
<h3>GPU 加速</h3>
<p>热驻留让首 token 从 3s 降到 0.08s。</p>
</div>
<div class="card">
<h3>隐私优先</h3>
<p>数据默认全在本地,云端只见密文。</p>
</div>
</div>
<h2>⑤ 徽章与输入</h2>
<div class="row" style="margin-bottom: 12px;">
<span class="badge">⚡ ultrafast</span>
<span class="badge">🟢 就绪</span>
<span class="badge">🔥 狂暴模式</span>
</div>
<input class="input" placeholder="在这里输入点什么…" />
<h2>⑥ 毛玻璃浮窗</h2>
<div class="glass">
<b>85% 暖纸底 + 20px 背景模糊</b>
<p class="muted" style="margin: 6px 0 0; font-size: 13px;">
用于命令面板、侧边抽屉、浮层——既保持可读,又透出底层氛围。
</p>
</div>
<h2>⑦ 圆角层级</h2>
<div class="radii">
<div style="border-radius: var(--radius-chip);">chip 8px</div>
<div style="border-radius: var(--radius-control);">control 10px</div>
<div style="border-radius: var(--radius-card);">card 14px</div>
<div style="border-radius: var(--radius-float);">float 16px</div>
</div>
<footer class="tiny">
MIT License · 来自
<a href="https://huggingface.co/datasets/hongcoding/perch" style="color: var(--accent);">栖木 Perch</a>
· 完整文档见 <code>docs/design-system.md</code>
</footer>
</div>
<script>
// 色板从 CSS 变量实时读取,保证与令牌一致
const TOKENS = [
["--bg", "页面底色", "bg"],
["--bg-elevated", "卡片", "bg-elevated"],
["--bg-sunken", "凹陷", "bg-sunken"],
["--border", "描边", "border"],
["--text", "主文字", "text"],
["--text-secondary", "次要文字", "text-secondary"],
["--text-tertiary", "提示", "text-tertiary"],
["--accent", "强调(唯一)", "accent"],
["--accent-soft", "强调背景", "accent-soft"],
["--success", "成功", "success"],
["--warning", "警告", "warning"],
["--danger", "危险", "danger"],
];
function renderSwatches() {
const root = getComputedStyle(document.documentElement);
const box = document.getElementById("swatches");
box.innerHTML = TOKENS.map(([v, name]) => {
const value = root.getPropertyValue(v).trim();
return `<div class="swatch">
<div class="chip" style="background: ${value};"></div>
<div class="meta"><b>${name}</b><code>${v}<br>${value}</code></div>
</div>`;
}).join("");
}
function currentTheme() {
return document.documentElement.getAttribute("data-theme")
|| (matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light");
}
function toggleTheme() {
const next = currentTheme() === "dark" ? "light" : "dark";
document.documentElement.setAttribute("data-theme", next);
renderSwatches();
}
renderSwatches();
</script>
</body>
</html>