feat: improve Banban UI and binding diagnostics

This commit is contained in:
stu2not
2026-09-23 20:19:14 +08:00
parent da3f71a2e3
commit 3fd750989f
34 changed files with 4247 additions and 61 deletions
+23
View File
@@ -0,0 +1,23 @@
# 伴伴小程序当前页面全景
本目录中的 `banban-page-state-overview.png` 根据当前 `banban-mini` 构建生成,包含 11 个页面路由的 26 张真实微信开发者工具截图。
## 内容来源
- 页面主体全部来自微信开发者工具中的实际小程序页面,不使用 HTML 重画小程序界面。
- 游客态直接由当前构建生成。
- 登录态使用本地固定展示数据驱动当前页面组件,不连接生产接口,也不包含真实用户数据。
- `actual-screens/` 保留总览中使用的原始截图。
## 重新渲染
在 macOS 上运行:
```bash
CLANG_MODULE_CACHE_PATH=/tmp/banban-clang-cache clang -fobjc-arc \
-framework AppKit -framework WebKit render-overview.m \
-o /tmp/banban-overview-renderer
/tmp/banban-overview-renderer overview.html banban-page-state-overview.png
```
`overview.html` 和 `overview.css` 只负责截图编排、分组标题和状态说明,不模拟小程序组件。
@@ -0,0 +1,647 @@
@import url("./shared.css");
.system-poster {
min-height: 2520px;
background: #fffdf9;
}
.system-header {
display: grid;
grid-template-columns: 1fr 360px;
gap: 50px;
min-height: 300px;
align-items: center;
padding: 58px 64px 52px;
border-bottom: 1px solid #eee8df;
}
.brand-lockup {
display: flex;
align-items: center;
justify-content: flex-end;
gap: 24px;
}
.brand-mark {
display: flex;
width: 122px;
height: 122px;
align-items: center;
justify-content: center;
border-radius: 28px;
background: linear-gradient(135deg, var(--orange), var(--orange-deep));
box-shadow: 0 18px 36px rgba(255, 140, 66, 0.26);
font-size: 64px;
}
.brand-note {
max-width: 180px;
}
.brand-note strong {
display: block;
margin-bottom: 6px;
font-size: 22px;
}
.brand-note span {
color: var(--muted);
font-size: 14px;
line-height: 1.55;
}
.system-layout {
display: grid;
grid-template-columns: 2fr 1fr;
gap: 0;
}
.system-main {
padding: 44px 42px 52px 54px;
border-right: 1px solid #eee8df;
}
.system-aside {
padding: 44px 54px 52px 42px;
background: #fbfaf7;
}
.spec-section {
padding: 30px 0 34px;
border-bottom: 1px solid #ece8e2;
}
.spec-section:first-child { padding-top: 0; }
.spec-section:last-child { border-bottom: 0; }
.spec-heading {
display: flex;
align-items: baseline;
justify-content: space-between;
margin-bottom: 24px;
}
.spec-heading h2 {
margin: 0;
font-size: 25px;
line-height: 1.3;
}
.spec-heading span {
color: var(--quiet);
font-size: 13px;
}
.button-showcase {
display: grid;
grid-template-columns: 1.45fr 1fr;
gap: 20px 24px;
}
.control-sample {
min-width: 0;
}
.sample-label {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 9px;
color: #4b5563;
font-size: 13px;
}
.sample-label code {
color: var(--quiet);
font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
font-size: 11px;
}
.ds-btn {
display: flex;
width: 100%;
height: 62px;
align-items: center;
justify-content: center;
gap: 10px;
border: 0;
border-radius: 14px;
background: var(--orange);
color: #ffffff;
font-size: 19px;
font-weight: 760;
box-shadow: 0 10px 24px rgba(255, 140, 66, 0.24);
}
.ds-btn.compact {
width: 214px;
height: 54px;
margin: 0 auto;
font-size: 17px;
}
.ds-btn.green { background: #07c160; box-shadow: 0 10px 24px rgba(7, 193, 96, 0.18); }
.ds-btn.dark { background: #1f2937; box-shadow: 0 10px 24px rgba(31, 41, 55, 0.18); }
.ds-btn.pressed { transform: translateY(2px); background: var(--orange-deep); box-shadow: 0 4px 10px rgba(255, 107, 53, 0.22); }
.ds-btn.focused { outline: 3px solid rgba(255, 140, 66, 0.32); outline-offset: 3px; }
.ds-btn.disabled { background: #e5e7eb; color: #a9afb7; box-shadow: none; }
.ds-btn.loading { background: #f2f0ed; color: #929292; box-shadow: none; }
.spinner {
width: 18px;
height: 18px;
border: 2px solid rgba(146, 146, 146, 0.35);
border-top-color: #929292;
border-radius: 50%;
}
.state-strip {
display: grid;
grid-template-columns: repeat(5, 1fr);
gap: 12px;
}
.state-cell .ds-btn {
height: 50px;
font-size: 15px;
border-radius: 12px;
}
.audio-grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 14px;
}
.audio-btn {
display: flex;
height: 58px;
align-items: center;
justify-content: center;
gap: 10px;
border: 1px solid #ffd0b1;
border-radius: 14px;
background: #fffaf6;
color: var(--orange-deep);
font-size: 16px;
font-weight: 730;
}
.audio-btn.playing {
border-color: var(--orange);
background: var(--orange-soft);
}
.audio-icon {
display: flex;
width: 28px;
height: 28px;
align-items: center;
justify-content: center;
border-radius: 50%;
background: var(--orange);
color: #ffffff;
font-size: 12px;
}
.secondary-grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 14px;
}
.secondary-btn {
display: flex;
height: 54px;
align-items: center;
justify-content: center;
border: 1px solid #ffb786;
border-radius: 13px;
background: #ffffff;
color: var(--orange-deep);
font-size: 16px;
font-weight: 700;
}
.secondary-btn.neutral { border-color: #d8dde4; color: #4b5563; }
.secondary-btn.danger { border-color: #e7a4b2; color: var(--red); }
.text-actions {
display: flex;
gap: 48px;
margin-top: 22px;
padding: 0 10px;
}
.text-action {
display: inline-flex;
align-items: center;
gap: 8px;
color: var(--orange-deep);
font-size: 16px;
font-weight: 700;
}
.icon-grid {
display: grid;
grid-template-columns: repeat(8, 1fr);
gap: 12px;
}
.icon-sample {
text-align: center;
}
.round-icon {
display: flex;
width: 54px;
height: 54px;
align-items: center;
justify-content: center;
margin: 0 auto 8px;
border: 1px solid #e5e7eb;
border-radius: 50%;
background: #ffffff;
color: #374151;
font-size: 22px;
box-shadow: 0 4px 12px rgba(31, 41, 55, 0.06);
}
.icon-sample span:last-child {
color: var(--muted);
font-size: 11px;
}
.compound-grid {
display: grid;
grid-template-columns: 1.3fr 1fr;
gap: 26px;
}
.segment {
display: grid;
grid-template-columns: repeat(4, 1fr);
overflow: hidden;
border: 1px solid #e5e7eb;
border-radius: 13px;
background: #ffffff;
}
.segment span {
display: flex;
height: 52px;
align-items: center;
justify-content: center;
border-right: 1px solid #eeeeee;
color: #4b5563;
font-size: 14px;
}
.segment span:last-child { border-right: 0; }
.segment .active { background: var(--orange); color: #ffffff; font-weight: 750; }
.switch-row,
.slider-row {
display: flex;
min-height: 48px;
align-items: center;
justify-content: space-between;
gap: 18px;
color: #4b5563;
font-size: 14px;
}
.switch {
position: relative;
width: 54px;
height: 30px;
border-radius: 999px;
background: var(--orange);
}
.switch::after {
position: absolute;
top: 4px;
right: 4px;
width: 22px;
height: 22px;
border-radius: 50%;
background: #ffffff;
box-shadow: 0 2px 6px rgba(31, 41, 55, 0.18);
content: "";
}
.slider {
position: relative;
flex: 1;
height: 7px;
border-radius: 999px;
background: linear-gradient(90deg, var(--orange) 0 62%, #e5e7eb 62% 100%);
}
.slider::after {
position: absolute;
top: 50%;
left: 62%;
width: 20px;
height: 20px;
transform: translate(-50%, -50%);
border: 3px solid #ffffff;
border-radius: 50%;
background: var(--orange);
box-shadow: 0 2px 8px rgba(31, 41, 55, 0.18);
content: "";
}
.bottom-nav-demo {
display: grid;
grid-template-columns: repeat(4, 1fr);
max-width: 650px;
margin: 0 auto;
padding: 14px 20px 10px;
border: 1px solid #ececec;
border-radius: 18px 18px 8px 8px;
background: #ffffff;
box-shadow: var(--shadow-soft);
}
.feedback-grid {
display: grid;
grid-template-columns: repeat(5, 1fr);
gap: 12px;
}
.status-chip-demo {
display: flex;
min-height: 48px;
align-items: center;
justify-content: center;
gap: 7px;
border-radius: 999px;
background: #f3f4f6;
color: #4b5563;
font-size: 13px;
font-weight: 700;
}
.status-chip-demo::before {
width: 8px;
height: 8px;
border-radius: 50%;
background: currentColor;
content: "";
}
.status-chip-demo.online { background: #e8f8f0; color: #15803d; }
.status-chip-demo.syncing { background: #e8f0fe; color: #2563eb; }
.status-chip-demo.sleeping { background: #f0e6ff; color: #7c3aed; }
.status-chip-demo.low { background: #fff7e8; color: #c2410c; }
.status-chip-demo.alarm { background: #fff1f2; color: #be123c; }
.feedback-row {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 14px;
margin-top: 18px;
}
.feedback-banner {
display: flex;
min-height: 56px;
align-items: center;
gap: 10px;
padding: 0 16px;
border-left: 4px solid #16a34a;
border-radius: 0 10px 10px 0;
background: #f0fdf4;
color: #14532d;
font-size: 14px;
font-weight: 650;
}
.feedback-banner.warning {
border-left-color: var(--orange);
background: #fff7ed;
color: #9a3412;
}
.card-demo-grid {
display: grid;
grid-template-columns: 1.15fr 1fr;
gap: 16px;
}
.status-card-demo {
min-height: 164px;
padding: 22px;
border-radius: 18px;
background: linear-gradient(135deg, var(--orange), var(--orange-deep));
color: #ffffff;
box-shadow: 0 14px 28px rgba(255, 140, 66, 0.22);
}
.status-card-demo .demo-badge {
display: inline-flex;
align-items: center;
gap: 6px;
padding: 6px 10px;
border-radius: 8px;
background: rgba(255, 255, 255, 0.18);
font-size: 11px;
font-weight: 700;
}
.status-card-demo strong {
display: block;
margin-top: 18px;
font-size: 38px;
line-height: 1;
}
.status-card-demo span:last-child {
display: block;
margin-top: 7px;
color: rgba(255, 255, 255, 0.82);
font-size: 12px;
}
.empty-card-demo {
display: flex;
min-height: 164px;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 20px;
border: 1px solid #ececec;
border-radius: 18px;
background: #ffffff;
text-align: center;
box-shadow: var(--shadow-soft);
}
.empty-card-demo b {
margin: 8px 0 4px;
font-size: 15px;
}
.empty-card-demo span {
color: var(--muted);
font-size: 12px;
}
.nav-demo-item {
display: flex;
flex-direction: column;
align-items: center;
gap: 5px;
color: #999999;
font-size: 12px;
}
.nav-demo-item.active { color: var(--orange); font-weight: 760; }
.nav-demo-item img {
width: 30px;
height: 30px;
object-fit: contain;
}
.aside-panel {
padding: 28px 0 32px;
border-bottom: 1px solid #e9e4dc;
}
.aside-panel:first-child { padding-top: 0; }
.aside-panel:last-child { border-bottom: 0; }
.aside-panel h2 {
margin: 0 0 18px;
font-size: 22px;
}
.rule-list {
display: grid;
gap: 13px;
}
.rule {
display: grid;
grid-template-columns: 24px 1fr;
gap: 10px;
align-items: start;
color: #4b5563;
font-size: 14px;
line-height: 1.55;
}
.rule-mark {
display: flex;
width: 20px;
height: 20px;
align-items: center;
justify-content: center;
border-radius: 50%;
background: var(--orange);
color: #ffffff;
font-size: 11px;
font-weight: 800;
}
.token-grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 16px 12px;
}
.color-token {
min-width: 0;
}
.swatch {
width: 52px;
height: 52px;
margin-bottom: 8px;
border: 1px solid rgba(31, 41, 55, 0.06);
border-radius: 50%;
}
.color-token code {
display: block;
color: #4b5563;
font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
font-size: 11px;
}
.color-token span {
color: var(--quiet);
font-size: 11px;
}
.spec-table {
width: 100%;
border-collapse: collapse;
}
.spec-table td {
padding: 11px 0;
border-bottom: 1px solid #ebe7e1;
color: #4b5563;
font-size: 13px;
line-height: 1.45;
vertical-align: top;
}
.spec-table tr:last-child td { border-bottom: 0; }
.spec-table td:first-child { width: 42%; color: var(--quiet); }
.spec-table td:last-child { color: var(--ink); font-weight: 650; }
.type-sample {
margin-bottom: 14px;
}
.type-sample:last-child { margin-bottom: 0; }
.type-sample strong {
display: block;
color: var(--ink);
}
.type-sample span {
color: var(--quiet);
font-size: 11px;
}
.type-display strong { font-size: 32px; }
.type-title strong { font-size: 22px; }
.type-body strong { font-size: 16px; }
.type-caption strong { font-size: 13px; }
.spacing-scale {
display: grid;
gap: 10px;
}
.space-row {
display: grid;
grid-template-columns: 42px 1fr 52px;
gap: 10px;
align-items: center;
color: var(--muted);
font-size: 12px;
}
.space-bar {
height: 10px;
border-radius: 3px;
background: var(--orange-soft);
}
.inventory-note {
padding: 16px;
border-left: 4px solid var(--orange);
border-radius: 0 8px 8px 0;
background: #fff7ed;
color: #9a3412;
font-size: 13px;
line-height: 1.6;
}
@@ -0,0 +1,209 @@
<!doctype html>
<html lang="zh-CN">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=1440, initial-scale=1">
<title>伴伴 · 交互控件现状规范</title>
<link rel="stylesheet" href="./design-system.css">
</head>
<body>
<main class="poster system-poster">
<header class="system-header">
<div>
<div class="poster-kicker">BANBAN MINI PROGRAM · UI INVENTORY</div>
<h1 class="poster-title">伴伴 · 交互控件现状规范</h1>
<p class="poster-subtitle">从现有页面 TSX、SCSS 与 tabBar 配置提取。以真实实现为准,不将参考图的视觉语言替换进产品。</p>
<div style="display:flex;gap:10px;margin-top:24px">
<span class="meta-chip">当前实现</span>
<span class="meta-chip">750rpx 设计稿</span>
<span class="meta-chip">微信小程序</span>
</div>
</div>
<div class="brand-lockup">
<div class="brand-mark">🤖</div>
<div class="brand-note"><strong>让关爱随时随地</strong><span>设备、家庭和陪伴体验中的温暖反馈。</span></div>
</div>
</header>
<div class="system-layout">
<section class="system-main">
<div class="spec-section">
<div class="spec-heading"><h2>1. 主要操作</h2><span>页面主任务 · 每个视图只保留一个视觉主按钮</span></div>
<div class="button-showcase">
<div class="control-sample"><div class="sample-label"><span>橙色主操作</span><code>88–96rpx</code></div><button class="ds-btn">确认并发送绑卡指令</button></div>
<div class="control-sample"><div class="sample-label"><span>紧凑主操作</span><code>88rpx</code></div><button class="ds-btn compact">保存休眠时间</button></div>
<div class="control-sample"><div class="sample-label"><span>微信登录</span><code>#07C160</code></div><button class="ds-btn green">登录后继续</button></div>
<div class="control-sample"><div class="sample-label"><span>设备控制</span><code>#1F2937</code></div><button class="ds-btn compact dark">进入休眠</button></div>
</div>
</div>
<div class="spec-section">
<div class="spec-heading"><h2>2. 状态展示</h2><span>以“保存”为例</span></div>
<div class="state-strip">
<div class="state-cell"><div class="sample-label"><span>默认</span></div><button class="ds-btn">保存</button></div>
<div class="state-cell"><div class="sample-label"><span>按下</span></div><button class="ds-btn pressed">保存</button></div>
<div class="state-cell"><div class="sample-label"><span>聚焦</span></div><button class="ds-btn focused">保存</button></div>
<div class="state-cell"><div class="sample-label"><span>禁用</span></div><button class="ds-btn disabled">保存</button></div>
<div class="state-cell"><div class="sample-label"><span>加载</span></div><button class="ds-btn loading"><i class="spinner"></i>处理中</button></div>
</div>
</div>
<div class="spec-section">
<div class="spec-heading"><h2>3. 音频与留言操作</h2><span>固定高度,避免播放状态改变布局</span></div>
<div class="audio-grid">
<div class="audio-btn"><span class="audio-icon">▶</span>播放留言</div>
<div class="audio-btn"><span class="audio-icon">↻</span>再次播放</div>
<div class="audio-btn playing"><span class="audio-icon">Ⅱ</span>播放中 <span class="audio-wave"><i></i><i></i><i></i><i></i><i></i></span></div>
</div>
</div>
<div class="spec-section">
<div class="spec-heading"><h2>4. 次要、文字与危险操作</h2><span>降低层级,不与主任务争夺注意力</span></div>
<div class="secondary-grid">
<div class="secondary-btn">稍后设置</div>
<div class="secondary-btn neutral">取消</div>
<div class="secondary-btn danger">解除绑定</div>
</div>
<div class="text-actions">
<div class="text-action">查看往期来信 <b>›</b></div>
<div class="text-action">管理设备 <b>›</b></div>
<div class="text-action">暂不登录,先浏览</div>
</div>
</div>
<div class="spec-section">
<div class="spec-heading"><h2>5. 圆形图标按钮</h2><span>72–80rpx · 熟悉符号优先</span></div>
<div class="icon-grid">
<div class="icon-sample"><div class="round-icon">‹</div><span>返回</span></div>
<div class="icon-sample"><div class="round-icon">×</div><span>关闭</span></div>
<div class="icon-sample"><div class="round-icon">•••</div><span>更多</span></div>
<div class="icon-sample"><div class="round-icon">▶</div><span>播放</span></div>
<div class="icon-sample"><div class="round-icon">↻</div><span>刷新</span></div>
<div class="icon-sample"><div class="round-icon">✉</div><span>留言</span></div>
<div class="icon-sample"><div class="round-icon">⌁</div><span>绑定</span></div>
<div class="icon-sample"><div class="round-icon">⚙</div><span>设置</span></div>
</div>
</div>
<div class="spec-section">
<div class="spec-heading"><h2>6. 复合控件</h2><span>分段、开关与音量</span></div>
<div class="compound-grid">
<div>
<div class="sample-label"><span>分段控制</span><code>68rpx</code></div>
<div class="segment"><span class="active">当前位置</span><span>今日轨迹</span><span>最近点位</span><span>全部</span></div>
</div>
<div>
<div class="switch-row"><span>允许家庭通知</span><i class="switch"></i></div>
<div class="slider-row"><span>音量</span><i class="slider"></i><strong>60%</strong></div>
</div>
</div>
</div>
<div class="spec-section">
<div class="spec-heading"><h2>7. 底部导航</h2><span>来自现有 app.config.ts 与 tab-icons</span></div>
<div class="bottom-nav-demo">
<div class="nav-demo-item active"><img src="../../banban-mini/src/assets/tab-icons/device-selected.png" alt=""><span>首页</span></div>
<div class="nav-demo-item"><img src="../../banban-mini/src/assets/tab-icons/chat-icon.png" alt=""><span>玩伴</span></div>
<div class="nav-demo-item"><img src="../../banban-mini/src/assets/tab-icons/location-icon.png" alt=""><span>定位</span></div>
<div class="nav-demo-item"><img src="../../banban-mini/src/assets/tab-icons/manage-icon.png" alt=""><span>管理</span></div>
</div>
</div>
<div class="spec-section">
<div class="spec-heading"><h2>8. 状态标签与反馈</h2><span>设备状态、告警和操作结果</span></div>
<div class="feedback-grid">
<div class="status-chip-demo online">在线</div>
<div class="status-chip-demo syncing">同步中</div>
<div class="status-chip-demo sleeping">休眠中</div>
<div class="status-chip-demo low">低电量</div>
<div class="status-chip-demo alarm">紧急告警</div>
</div>
<div class="feedback-row">
<div class="feedback-banner">✓ 休眠时间已发送</div>
<div class="feedback-banner warning">! 设备暂未确认,请稍后查看</div>
</div>
</div>
<div class="spec-section">
<div class="spec-heading"><h2>9. 状态卡片与空状态</h2><span>信息概览和下一步引导</span></div>
<div class="card-demo-grid">
<div class="status-card-demo"><span class="demo-badge">● 已完成绑定</span><strong>82%</strong><span>小雨的伴伴 · 在线</span></div>
<div class="empty-card-demo"><div style="font-size:32px">🤖</div><b>先绑定一台伴伴设备</b><span>绑定后查看设备状态</span></div>
</div>
</div>
</section>
<aside class="system-aside">
<div class="aside-panel">
<h2>使用原则</h2>
<div class="rule-list">
<div class="rule"><span class="rule-mark">1</span><span>每个视图保持一个明确的主操作,橙色用于产品主任务。</span></div>
<div class="rule"><span class="rule-mark">2</span><span>微信授权沿用微信绿色,不强行改成品牌橙色。</span></div>
<div class="rule"><span class="rule-mark">3</span><span>设备休眠等硬件动作使用深色或语义色,和普通保存区分。</span></div>
<div class="rule"><span class="rule-mark">4</span><span>危险操作采用红色描边,保留确认步骤,降低误触风险。</span></div>
<div class="rule"><span class="rule-mark">5</span><span>加载与禁用状态保持固定尺寸,动态文字不引起页面跳动。</span></div>
</div>
</div>
<div class="aside-panel">
<h2>颜色体系</h2>
<div class="token-grid">
<div class="color-token"><div class="swatch" style="background:#ff8c42"></div><code>#FF8C42</code><span>品牌橙</span></div>
<div class="color-token"><div class="swatch" style="background:#ff6b35"></div><code>#FF6B35</code><span>强调橙</span></div>
<div class="color-token"><div class="swatch" style="background:#1a1a1a"></div><code>#1A1A1A</code><span>主文字</span></div>
<div class="color-token"><div class="swatch" style="background:#f5f7fa"></div><code>#F5F7FA</code><span>页面底色</span></div>
<div class="color-token"><div class="swatch" style="background:#07c160"></div><code>#07C160</code><span>微信绿</span></div>
<div class="color-token"><div class="swatch" style="background:#16a34a"></div><code>#16A34A</code><span>成功绿</span></div>
<div class="color-token"><div class="swatch" style="background:#5b8ff9"></div><code>#5B8FF9</code><span>信息蓝</span></div>
<div class="color-token"><div class="swatch" style="background:#8b5cf6"></div><code>#8B5CF6</code><span>角色紫</span></div>
<div class="color-token"><div class="swatch" style="background:#be123c"></div><code>#BE123C</code><span>告警红</span></div>
</div>
</div>
<div class="aside-panel">
<h2>现有规格</h2>
<table class="spec-table">
<tr><td>主要按钮高度</td><td>88–100rpx</td></tr>
<tr><td>紧凑按钮高度</td><td>60–88rpx</td></tr>
<tr><td>主要按钮圆角</td><td>16–22rpx</td></tr>
<tr><td>页面卡片圆角</td><td>20–28rpx</td></tr>
<tr><td>圆形按钮</td><td>72–80rpx</td></tr>
<tr><td>按钮水平内边距</td><td>24–32rpx</td></tr>
<tr><td>卡片阴影</td><td>0 2rpx 12rpx / 4–8%</td></tr>
<tr><td>控件垂直间距</td><td>16–24rpx</td></tr>
</table>
</div>
<div class="aside-panel">
<h2>字体层级</h2>
<div class="type-sample type-display"><strong>页面标题</strong><span>44–52rpx · 700/800</span></div>
<div class="type-sample type-title"><strong>卡片标题与关键数值</strong><span>30–36rpx · 650/750</span></div>
<div class="type-sample type-body"><strong>正文与按钮文字</strong><span>26–32rpx · 400/700</span></div>
<div class="type-sample type-caption"><strong>辅助信息、时间与状态</strong><span>22–24rpx · 400/600</span></div>
</div>
<div class="aside-panel">
<h2>间距基准</h2>
<div class="spacing-scale">
<div class="space-row"><span>XS</span><div class="space-bar" style="width:20%"></div><strong>8rpx</strong></div>
<div class="space-row"><span>SM</span><div class="space-bar" style="width:35%"></div><strong>16rpx</strong></div>
<div class="space-row"><span>MD</span><div class="space-bar" style="width:50%"></div><strong>24rpx</strong></div>
<div class="space-row"><span>LG</span><div class="space-bar" style="width:68%"></div><strong>32rpx</strong></div>
<div class="space-row"><span>XL</span><div class="space-bar" style="width:100%"></div><strong>48rpx</strong></div>
</div>
</div>
<div class="aside-panel">
<h2>现状说明</h2>
<div class="inventory-note">当前按钮尚未收敛为统一组件:登录、绑定、定位、管理页面存在 88、92、96、100rpx 等多种高度。这张图记录真实实现,不代表已经完成组件统一。</div>
</div>
</aside>
</div>
<footer class="poster-footer">
<span class="footer-brand">伴伴 · 交互控件现状规范</span>
<span>Source: app.config.ts + pages/**/*.scss · 2026.08</span>
</footer>
</main>
</body>
</html>
@@ -0,0 +1,145 @@
@import url("./overview.css");
html,
body.atlas-original-resolution,
.atlas-original-resolution .atlas {
width: 430px;
}
.atlas-original-resolution .atlas-header {
display: block;
min-height: 0;
padding: 34px 24px 30px;
}
.atlas-original-resolution .eyebrow {
margin-bottom: 8px;
font-size: 10px;
}
.atlas-original-resolution h1 {
font-size: 31px;
line-height: 1.2;
}
.atlas-original-resolution .header-copy > p {
margin-top: 14px;
font-size: 13px;
line-height: 1.7;
}
.atlas-original-resolution .resolution-badge {
display: inline-flex;
margin-top: 14px;
padding: 7px 10px;
border: 1px solid #f1c79f;
border-radius: 6px;
background: #fff4e8;
color: #a85d25;
font-size: 12px;
font-weight: 750;
}
.atlas-original-resolution .legend {
gap: 8px 14px;
margin-top: 18px;
font-size: 11px;
}
.atlas-original-resolution .metrics {
margin-top: 24px;
gap: 8px;
}
.atlas-original-resolution .metric {
min-height: 82px;
padding: 14px;
}
.atlas-original-resolution .metric strong {
margin-bottom: 5px;
font-size: 28px;
}
.atlas-original-resolution .metric span {
font-size: 11px;
}
.atlas-original-resolution .flow {
padding: 38px 19px 48px;
}
.atlas-original-resolution .flow-heading {
display: block;
min-height: 0;
margin: 0 2px 24px;
}
.atlas-original-resolution .flow-number {
top: -16px;
font-size: 54px;
}
.atlas-original-resolution .flow-eyebrow {
margin-bottom: 7px;
font-size: 10px;
}
.atlas-original-resolution h2 {
font-size: 26px;
line-height: 1.25;
}
.atlas-original-resolution .flow-heading > p {
max-width: none;
margin-top: 12px;
font-size: 12px;
line-height: 1.65;
text-align: left;
}
.atlas-original-resolution .state-grid {
grid-template-columns: minmax(0, 1fr);
gap: 24px;
}
.atlas-original-resolution .state-card {
box-shadow: 0 7px 18px rgba(70, 52, 37, 0.09);
}
.atlas-original-resolution .state-card img {
image-rendering: auto;
}
.atlas-original-resolution .state-copy {
grid-template-columns: 38px minmax(0, 1fr);
gap: 12px;
min-height: 104px;
padding: 17px;
}
.atlas-original-resolution .state-copy > b {
width: 34px;
height: 34px;
font-size: 12px;
}
.atlas-original-resolution .state-copy h3 {
margin-bottom: 6px;
font-size: 19px;
}
.atlas-original-resolution .state-copy p {
font-size: 14px;
line-height: 1.55;
}
.atlas-original-resolution footer {
min-height: 130px;
flex-direction: column;
align-items: flex-start;
justify-content: center;
gap: 8px;
padding: 24px;
font-size: 10px;
}
@@ -0,0 +1,116 @@
<!doctype html>
<html lang="zh-CN">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>伴伴小程序当前页面全景 · 原始像素版</title>
<link rel="stylesheet" href="overview-original-resolution.css">
</head>
<body class="atlas-original-resolution">
<main class="atlas">
<header class="atlas-header">
<div class="header-copy">
<div class="eyebrow">BANBAN MINI PROGRAM · CURRENT BUILD</div>
<h1>伴伴小程序 / 当前页面全景</h1>
<div class="resolution-badge">单列原始像素版</div>
<p>全部页面画面来自当前 <strong>banban-mini</strong> 构建,并由微信开发者工具直接截图。登录态使用本地固定展示数据,不包含线上用户数据。</p>
<div class="legend">
<span><i class="dot orange"></i>游客与入口</span>
<span><i class="dot green"></i>设备与绑定</span>
<span><i class="dot blue"></i>会话与定位</span>
<span><i class="dot coral"></i>管理与家庭</span>
</div>
</div>
<div class="metrics" aria-label="页面统计">
<div class="metric"><strong>11</strong><span>当前路由</span></div>
<div class="metric"><strong>26</strong><span>真实截图状态</span></div>
<div class="metric"><strong>4</strong><span>核心 Tab</span></div>
<div class="metric wide"><strong>390 px</strong><span>微信模拟器原始截图宽度</span></div>
</div>
</header>
<section class="flow flow-entry">
<div class="flow-heading">
<div>
<span class="flow-number">01</span>
<p class="flow-eyebrow">ENTRY & DEVICE</p>
<h2>进入、登录与设备首页</h2>
</div>
<p>从游客浏览、登录入口到设备绑定后的状态、告警和设备详情。</p>
</div>
<div class="state-grid">
<article class="state-card"><img src="actual-screens/01-device-guest.png" alt="设备首页游客态"><div class="state-copy"><b>01</b><div><h3>首页 · 游客态</h3><p>未登录时的设备入口</p></div></div></article>
<article class="state-card"><img src="actual-screens/02-login.png" alt="登录页"><div class="state-copy"><b>02</b><div><h3>登录 · 默认</h3><p>微信登录与协议确认</p></div></div></article>
<article class="state-card"><img src="actual-screens/08-device-online.png" alt="已绑定设备首页"><div class="state-copy"><b>03</b><div><h3>首页 · 已绑定</h3><p>电量、休眠、音量与告警</p></div></div></article>
<article class="state-card"><img src="actual-screens/09-device-detail.png" alt="设备详情展开"><div class="state-copy"><b>04</b><div><h3>首页 · 详情展开</h3><p>告警位置与设备详细信息</p></div></div></article>
<article class="state-card"><img src="actual-screens/06-notification-invalid.png" alt="通知链接失效"><div class="state-copy"><b>05</b><div><h3>通知 · 链接失效</h3><p>无效通知的回退页面</p></div></div></article>
<article class="state-card"><img src="actual-screens/20-bind-scan.png" alt="设备扫码绑定入口"><div class="state-copy"><b>06</b><div><h3>绑定 · 扫码入口</h3><p>扫码或填写设备信息</p></div></div></article>
</div>
</section>
<section class="flow flow-binding">
<div class="flow-heading">
<div>
<span class="flow-number">02</span>
<p class="flow-eyebrow">BINDING & DEVICE CONTROL</p>
<h2>设备二维码、贴卡与休眠</h2>
</div>
<p>当前绑定页真实展示二维码识别、等待贴卡、追加卡片和休眠配置。</p>
</div>
<div class="state-grid">
<article class="state-card"><img src="actual-screens/21-bind-recognized.png" alt="设备二维码已识别"><div class="state-copy"><b>07</b><div><h3>绑定 · 已识别</h3><p>设备号和序列号已回填</p></div></div></article>
<article class="state-card"><img src="actual-screens/22-bind-waiting-card.png" alt="等待设备贴卡"><div class="state-copy"><b>08</b><div><h3>绑定 · 等待贴卡</h3><p>绑卡指令和倒计时状态</p></div></div></article>
<article class="state-card"><img src="actual-screens/23-bind-add-card.png" alt="添加设备卡片"><div class="state-copy"><b>09</b><div><h3>卡片 · 添加入口</h3><p>为当前设备追加新卡</p></div></div></article>
<article class="state-card"><img src="actual-screens/24-sleep-schedule.png" alt="定时休眠设置"><div class="state-copy"><b>10</b><div><h3>设备 · 定时休眠</h3><p>休眠时段和时区配置</p></div></div></article>
</div>
</section>
<section class="flow flow-social">
<div class="flow-heading">
<div>
<span class="flow-number">03</span>
<p class="flow-eyebrow">MESSAGES & LOCATION</p>
<h2>亲子会话与设备定位</h2>
</div>
<p>覆盖游客预览、会话列表、AI 与家长消息,以及绑定设备后的实时位置。</p>
</div>
<div class="state-grid">
<article class="state-card"><img src="actual-screens/03-chat-guest.png" alt="玩伴游客态"><div class="state-copy"><b>11</b><div><h3>玩伴 · 游客态</h3><p>未登录时的会话能力预览</p></div></div></article>
<article class="state-card"><img src="actual-screens/10-chat-list.png" alt="会话列表"><div class="state-copy"><b>12</b><div><h3>玩伴 · 会话列表</h3><p>AI、家长与宝贝会话</p></div></div></article>
<article class="state-card"><img src="actual-screens/11-chat-ai-detail.png" alt="AI 会话详情"><div class="state-copy"><b>13</b><div><h3>会话 · AI 玩伴</h3><p>宝贝与伴伴的消息记录</p></div></div></article>
<article class="state-card"><img src="actual-screens/12-chat-parent-detail.png" alt="家长会话详情"><div class="state-copy"><b>14</b><div><h3>会话 · 家长沟通</h3><p>文字、语音和留言控制</p></div></div></article>
<article class="state-card"><img src="actual-screens/04-location-guest.png" alt="定位游客态"><div class="state-copy"><b>15</b><div><h3>定位 · 游客态</h3><p>地图与位置能力预览</p></div></div></article>
<article class="state-card"><img src="actual-screens/13-location-current.png" alt="设备当前位置"><div class="state-copy"><b>16</b><div><h3>定位 · 当前位置</h3><p>地图、地址和定位参数</p></div></div></article>
</div>
</section>
<section class="flow flow-manage">
<div class="flow-heading">
<div>
<span class="flow-number">04</span>
<p class="flow-eyebrow">MANAGEMENT & FAMILY</p>
<h2>管理、资料与家庭共享</h2>
</div>
<p>当前管理主页与真实交互弹窗,包含宝贝、卡片、角色、家庭和邀请状态。</p>
</div>
<div class="state-grid">
<article class="state-card"><img src="actual-screens/05-manage-guest.png" alt="管理游客态"><div class="state-copy"><b>17</b><div><h3>管理 · 游客态</h3><p>管理功能的登录前预览</p></div></div></article>
<article class="state-card"><img src="actual-screens/14-manage-overview.png" alt="管理总览"><div class="state-copy"><b>18</b><div><h3>管理 · 账号总览</h3><p>账号、设备和系统版本</p></div></div></article>
<article class="state-card"><img src="actual-screens/15-manage-menu.png" alt="管理功能菜单"><div class="state-copy"><b>19</b><div><h3>管理 · 功能列表</h3><p>卡片、角色、家庭和通知</p></div></div></article>
<article class="state-card"><img src="actual-screens/16-manage-child-modal.png" alt="切换当前宝贝弹窗"><div class="state-copy"><b>20</b><div><h3>宝贝 · 切换资料</h3><p>当前、修改和删除操作</p></div></div></article>
<article class="state-card"><img src="actual-screens/17-manage-cards-modal.png" alt="我的卡片弹窗"><div class="state-copy"><b>21</b><div><h3>卡片 · 卡片列表</h3><p>卡片名称、UUID 和编辑</p></div></div></article>
<article class="state-card"><img src="actual-screens/18-manage-role-modal.png" alt="切换 AI 角色弹窗"><div class="state-copy"><b>22</b><div><h3>角色 · AI 角色</h3><p>当前角色和可选角色</p></div></div></article>
<article class="state-card"><img src="actual-screens/19-manage-family-modal.png" alt="家庭成员弹窗"><div class="state-copy"><b>23</b><div><h3>家庭 · 成员列表</h3><p>主成员、共享成员和邀请</p></div></div></article>
<article class="state-card"><img src="actual-screens/25-family-invite-valid.png" alt="有效家庭邀请"><div class="state-copy"><b>24</b><div><h3>邀请 · 等待加入</h3><p>有效家庭共享邀请</p></div></div></article>
<article class="state-card"><img src="actual-screens/26-family-invite-expired.png" alt="过期家庭邀请"><div class="state-copy"><b>25</b><div><h3>邀请 · 已失效</h3><p>过期邀请的禁用状态</p></div></div></article>
<article class="state-card"><img src="actual-screens/07-family-invite-invalid.png" alt="未登录邀请跳转登录"><div class="state-copy"><b>26</b><div><h3>邀请 · 未登录跳转</h3><p>进入邀请前的登录承接页</p></div></div></article>
</div>
</section>
<footer>
<span>Generated from the current banban-mini build</span>
<span>WeChat DevTools screenshots · local fixtures only · 2026-09-09</span>
</footer>
</main>
</body>
</html>
@@ -0,0 +1,80 @@
@import url("./overview.css");
.atlas-readable .atlas-header {
min-height: 430px;
grid-template-columns: minmax(0, 1fr) 420px;
padding: 68px 76px;
background: #fffdf9;
}
.atlas-readable .readable-badge {
display: inline-flex;
margin-top: 20px;
padding: 8px 12px;
border: 1px solid #f1c79f;
border-radius: 6px;
background: #fff4e8;
color: #a85d25;
font-size: 15px;
font-weight: 750;
}
.atlas-readable .flow {
padding: 64px 72px 78px;
}
.atlas-readable .flow-heading {
min-height: 150px;
margin-bottom: 36px;
}
.atlas-readable .flow-heading > p {
max-width: 510px;
font-size: 17px;
}
.atlas-readable h2 {
font-size: 38px;
}
.atlas-readable .state-grid {
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 34px 30px;
}
.atlas-readable .state-card {
border-color: rgba(57, 40, 28, 0.14);
box-shadow: 0 12px 30px rgba(70, 52, 37, 0.1);
}
.atlas-readable .state-card img {
image-rendering: auto;
}
.atlas-readable .state-copy {
grid-template-columns: 48px minmax(0, 1fr);
gap: 16px;
min-height: 132px;
padding: 22px;
}
.atlas-readable .state-copy > b {
width: 44px;
height: 44px;
font-size: 15px;
}
.atlas-readable .state-copy h3 {
margin-bottom: 8px;
font-size: 24px;
}
.atlas-readable .state-copy p {
font-size: 17px;
line-height: 1.6;
}
.atlas-readable footer {
min-height: 112px;
font-size: 15px;
}
@@ -0,0 +1,116 @@
<!doctype html>
<html lang="zh-CN">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>伴伴小程序当前页面全景 · 清晰阅读版</title>
<link rel="stylesheet" href="overview-readable.css">
</head>
<body class="atlas-readable">
<main class="atlas">
<header class="atlas-header">
<div class="header-copy">
<div class="eyebrow">BANBAN MINI PROGRAM · CURRENT BUILD</div>
<h1>伴伴小程序 / 当前页面全景</h1>
<div class="readable-badge">高清阅读版 · 双列展示</div>
<p>全部页面画面来自当前 <strong>banban-mini</strong> 构建,并由微信开发者工具直接截图。登录态使用本地固定展示数据,不包含线上用户数据。</p>
<div class="legend">
<span><i class="dot orange"></i>游客与入口</span>
<span><i class="dot green"></i>设备与绑定</span>
<span><i class="dot blue"></i>会话与定位</span>
<span><i class="dot coral"></i>管理与家庭</span>
</div>
</div>
<div class="metrics" aria-label="页面统计">
<div class="metric"><strong>11</strong><span>当前路由</span></div>
<div class="metric"><strong>26</strong><span>真实截图状态</span></div>
<div class="metric"><strong>4</strong><span>核心 Tab</span></div>
<div class="metric wide"><strong>390 px</strong><span>微信模拟器原始截图宽度</span></div>
</div>
</header>
<section class="flow flow-entry">
<div class="flow-heading">
<div>
<span class="flow-number">01</span>
<p class="flow-eyebrow">ENTRY & DEVICE</p>
<h2>进入、登录与设备首页</h2>
</div>
<p>从游客浏览、登录入口到设备绑定后的状态、告警和设备详情。</p>
</div>
<div class="state-grid">
<article class="state-card"><img src="actual-screens/01-device-guest.png" alt="设备首页游客态"><div class="state-copy"><b>01</b><div><h3>首页 · 游客态</h3><p>未登录时的设备入口</p></div></div></article>
<article class="state-card"><img src="actual-screens/02-login.png" alt="登录页"><div class="state-copy"><b>02</b><div><h3>登录 · 默认</h3><p>微信登录与协议确认</p></div></div></article>
<article class="state-card"><img src="actual-screens/08-device-online.png" alt="已绑定设备首页"><div class="state-copy"><b>03</b><div><h3>首页 · 已绑定</h3><p>电量、休眠、音量与告警</p></div></div></article>
<article class="state-card"><img src="actual-screens/09-device-detail.png" alt="设备详情展开"><div class="state-copy"><b>04</b><div><h3>首页 · 详情展开</h3><p>告警位置与设备详细信息</p></div></div></article>
<article class="state-card"><img src="actual-screens/06-notification-invalid.png" alt="通知链接失效"><div class="state-copy"><b>05</b><div><h3>通知 · 链接失效</h3><p>无效通知的回退页面</p></div></div></article>
<article class="state-card"><img src="actual-screens/20-bind-scan.png" alt="设备扫码绑定入口"><div class="state-copy"><b>06</b><div><h3>绑定 · 扫码入口</h3><p>扫码或填写设备信息</p></div></div></article>
</div>
</section>
<section class="flow flow-binding">
<div class="flow-heading">
<div>
<span class="flow-number">02</span>
<p class="flow-eyebrow">BINDING & DEVICE CONTROL</p>
<h2>设备二维码、贴卡与休眠</h2>
</div>
<p>当前绑定页真实展示二维码识别、等待贴卡、追加卡片和休眠配置。</p>
</div>
<div class="state-grid">
<article class="state-card"><img src="actual-screens/21-bind-recognized.png" alt="设备二维码已识别"><div class="state-copy"><b>07</b><div><h3>绑定 · 已识别</h3><p>设备号和序列号已回填</p></div></div></article>
<article class="state-card"><img src="actual-screens/22-bind-waiting-card.png" alt="等待设备贴卡"><div class="state-copy"><b>08</b><div><h3>绑定 · 等待贴卡</h3><p>绑卡指令和倒计时状态</p></div></div></article>
<article class="state-card"><img src="actual-screens/23-bind-add-card.png" alt="添加设备卡片"><div class="state-copy"><b>09</b><div><h3>卡片 · 添加入口</h3><p>为当前设备追加新卡</p></div></div></article>
<article class="state-card"><img src="actual-screens/24-sleep-schedule.png" alt="定时休眠设置"><div class="state-copy"><b>10</b><div><h3>设备 · 定时休眠</h3><p>休眠时段和时区配置</p></div></div></article>
</div>
</section>
<section class="flow flow-social">
<div class="flow-heading">
<div>
<span class="flow-number">03</span>
<p class="flow-eyebrow">MESSAGES & LOCATION</p>
<h2>亲子会话与设备定位</h2>
</div>
<p>覆盖游客预览、会话列表、AI 与家长消息,以及绑定设备后的实时位置。</p>
</div>
<div class="state-grid">
<article class="state-card"><img src="actual-screens/03-chat-guest.png" alt="玩伴游客态"><div class="state-copy"><b>11</b><div><h3>玩伴 · 游客态</h3><p>未登录时的会话能力预览</p></div></div></article>
<article class="state-card"><img src="actual-screens/10-chat-list.png" alt="会话列表"><div class="state-copy"><b>12</b><div><h3>玩伴 · 会话列表</h3><p>AI、家长与宝贝会话</p></div></div></article>
<article class="state-card"><img src="actual-screens/11-chat-ai-detail.png" alt="AI 会话详情"><div class="state-copy"><b>13</b><div><h3>会话 · AI 玩伴</h3><p>宝贝与伴伴的消息记录</p></div></div></article>
<article class="state-card"><img src="actual-screens/12-chat-parent-detail.png" alt="家长会话详情"><div class="state-copy"><b>14</b><div><h3>会话 · 家长沟通</h3><p>文字、语音和留言控制</p></div></div></article>
<article class="state-card"><img src="actual-screens/04-location-guest.png" alt="定位游客态"><div class="state-copy"><b>15</b><div><h3>定位 · 游客态</h3><p>地图与位置能力预览</p></div></div></article>
<article class="state-card"><img src="actual-screens/13-location-current.png" alt="设备当前位置"><div class="state-copy"><b>16</b><div><h3>定位 · 当前位置</h3><p>地图、地址和定位参数</p></div></div></article>
</div>
</section>
<section class="flow flow-manage">
<div class="flow-heading">
<div>
<span class="flow-number">04</span>
<p class="flow-eyebrow">MANAGEMENT & FAMILY</p>
<h2>管理、资料与家庭共享</h2>
</div>
<p>当前管理主页与真实交互弹窗,包含宝贝、卡片、角色、家庭和邀请状态。</p>
</div>
<div class="state-grid">
<article class="state-card"><img src="actual-screens/05-manage-guest.png" alt="管理游客态"><div class="state-copy"><b>17</b><div><h3>管理 · 游客态</h3><p>管理功能的登录前预览</p></div></div></article>
<article class="state-card"><img src="actual-screens/14-manage-overview.png" alt="管理总览"><div class="state-copy"><b>18</b><div><h3>管理 · 账号总览</h3><p>账号、设备和系统版本</p></div></div></article>
<article class="state-card"><img src="actual-screens/15-manage-menu.png" alt="管理功能菜单"><div class="state-copy"><b>19</b><div><h3>管理 · 功能列表</h3><p>卡片、角色、家庭和通知</p></div></div></article>
<article class="state-card"><img src="actual-screens/16-manage-child-modal.png" alt="切换当前宝贝弹窗"><div class="state-copy"><b>20</b><div><h3>宝贝 · 切换资料</h3><p>当前、修改和删除操作</p></div></div></article>
<article class="state-card"><img src="actual-screens/17-manage-cards-modal.png" alt="我的卡片弹窗"><div class="state-copy"><b>21</b><div><h3>卡片 · 卡片列表</h3><p>卡片名称、UUID 和编辑</p></div></div></article>
<article class="state-card"><img src="actual-screens/18-manage-role-modal.png" alt="切换 AI 角色弹窗"><div class="state-copy"><b>22</b><div><h3>角色 · AI 角色</h3><p>当前角色和可选角色</p></div></div></article>
<article class="state-card"><img src="actual-screens/19-manage-family-modal.png" alt="家庭成员弹窗"><div class="state-copy"><b>23</b><div><h3>家庭 · 成员列表</h3><p>主成员、共享成员和邀请</p></div></div></article>
<article class="state-card"><img src="actual-screens/25-family-invite-valid.png" alt="有效家庭邀请"><div class="state-copy"><b>24</b><div><h3>邀请 · 等待加入</h3><p>有效家庭共享邀请</p></div></div></article>
<article class="state-card"><img src="actual-screens/26-family-invite-expired.png" alt="过期家庭邀请"><div class="state-copy"><b>25</b><div><h3>邀请 · 已失效</h3><p>过期邀请的禁用状态</p></div></div></article>
<article class="state-card"><img src="actual-screens/07-family-invite-invalid.png" alt="未登录邀请跳转登录"><div class="state-copy"><b>26</b><div><h3>邀请 · 未登录跳转</h3><p>进入邀请前的登录承接页</p></div></div></article>
</div>
</section>
<footer>
<span>Generated from the current banban-mini build</span>
<span>WeChat DevTools screenshots · local fixtures only · 2026-09-09</span>
</footer>
</main>
</body>
</html>
@@ -0,0 +1,84 @@
@import url("./overview.css");
.atlas-v2 {
background: #eceae6;
}
.atlas-v2 .atlas-header {
min-height: 360px;
padding: 58px 66px;
border-bottom: 0;
background: #302b27;
color: #fffdf8;
}
.atlas-v2 .eyebrow {
color: #ffad69;
}
.atlas-v2 .header-copy > p {
color: rgba(255, 253, 248, 0.7);
}
.atlas-v2 .legend {
color: rgba(255, 253, 248, 0.78);
}
.atlas-v2 .metric {
border-color: rgba(255, 255, 255, 0.1);
background: #413a34;
}
.atlas-v2 .metric strong {
color: #fffdf8;
}
.atlas-v2 .metric span {
color: rgba(255, 253, 248, 0.65);
}
.atlas-v2 .flow {
padding: 48px 50px 58px;
}
.atlas-v2 .flow-entry { background: #fff8ed; }
.atlas-v2 .flow-binding { background: #eef7e7; }
.atlas-v2 .flow-social { background: #eaf5f8; }
.atlas-v2 .flow-manage { background: #fff0ed; }
.atlas-v2 .flow-heading {
min-height: 112px;
margin-bottom: 24px;
padding: 0 8px 20px;
border-bottom: 2px solid rgba(57, 40, 28, 0.12);
}
.atlas-v2 .flow-number {
top: -22px;
color: rgba(57, 40, 28, 0.08);
font-size: 78px;
}
.atlas-v2 .state-grid {
gap: 18px;
}
.atlas-v2 .state-card {
border-color: rgba(57, 40, 28, 0.1);
box-shadow: 0 7px 18px rgba(57, 40, 28, 0.09);
}
.atlas-v2 .state-copy {
min-height: 92px;
padding: 14px;
}
.atlas-v2 .state-copy > b {
background: #ff8c42;
color: #2d2118;
}
.atlas-v2 footer {
min-height: 92px;
background: #1f1c19;
}
@@ -0,0 +1,115 @@
<!doctype html>
<html lang="zh-CN">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>伴伴小程序当前页面全景 V2</title>
<link rel="stylesheet" href="overview-v2.css">
</head>
<body class="atlas-v2">
<main class="atlas">
<header class="atlas-header">
<div class="header-copy">
<div class="eyebrow">BANBAN MINI PROGRAM · CURRENT BUILD</div>
<h1>伴伴小程序 / 当前页面全景</h1>
<p>全部页面画面来自当前 <strong>banban-mini</strong> 构建,并由微信开发者工具直接截图。登录态使用本地固定展示数据,不包含线上用户数据。</p>
<div class="legend">
<span><i class="dot orange"></i>游客与入口</span>
<span><i class="dot green"></i>设备与绑定</span>
<span><i class="dot blue"></i>会话与定位</span>
<span><i class="dot coral"></i>管理与家庭</span>
</div>
</div>
<div class="metrics" aria-label="页面统计">
<div class="metric"><strong>11</strong><span>当前路由</span></div>
<div class="metric"><strong>26</strong><span>真实截图状态</span></div>
<div class="metric"><strong>4</strong><span>核心 Tab</span></div>
<div class="metric wide"><strong>390 px</strong><span>微信模拟器原始截图宽度</span></div>
</div>
</header>
<section class="flow flow-entry">
<div class="flow-heading">
<div>
<span class="flow-number">01</span>
<p class="flow-eyebrow">ENTRY & DEVICE</p>
<h2>进入、登录与设备首页</h2>
</div>
<p>从游客浏览、登录入口到设备绑定后的状态、告警和设备详情。</p>
</div>
<div class="state-grid">
<article class="state-card"><img src="actual-screens/01-device-guest.png" alt="设备首页游客态"><div class="state-copy"><b>01</b><div><h3>首页 · 游客态</h3><p>未登录时的设备入口</p></div></div></article>
<article class="state-card"><img src="actual-screens/02-login.png" alt="登录页"><div class="state-copy"><b>02</b><div><h3>登录 · 默认</h3><p>微信登录与协议确认</p></div></div></article>
<article class="state-card"><img src="actual-screens/08-device-online.png" alt="已绑定设备首页"><div class="state-copy"><b>03</b><div><h3>首页 · 已绑定</h3><p>电量、休眠、音量与告警</p></div></div></article>
<article class="state-card"><img src="actual-screens/09-device-detail.png" alt="设备详情展开"><div class="state-copy"><b>04</b><div><h3>首页 · 详情展开</h3><p>告警位置与设备详细信息</p></div></div></article>
<article class="state-card"><img src="actual-screens/06-notification-invalid.png" alt="通知链接失效"><div class="state-copy"><b>05</b><div><h3>通知 · 链接失效</h3><p>无效通知的回退页面</p></div></div></article>
<article class="state-card"><img src="actual-screens/20-bind-scan.png" alt="设备扫码绑定入口"><div class="state-copy"><b>06</b><div><h3>绑定 · 扫码入口</h3><p>扫码或填写设备信息</p></div></div></article>
</div>
</section>
<section class="flow flow-binding">
<div class="flow-heading">
<div>
<span class="flow-number">02</span>
<p class="flow-eyebrow">BINDING & DEVICE CONTROL</p>
<h2>设备二维码、贴卡与休眠</h2>
</div>
<p>当前绑定页真实展示二维码识别、等待贴卡、追加卡片和休眠配置。</p>
</div>
<div class="state-grid">
<article class="state-card"><img src="actual-screens/21-bind-recognized.png" alt="设备二维码已识别"><div class="state-copy"><b>07</b><div><h3>绑定 · 已识别</h3><p>设备号和序列号已回填</p></div></div></article>
<article class="state-card"><img src="actual-screens/22-bind-waiting-card.png" alt="等待设备贴卡"><div class="state-copy"><b>08</b><div><h3>绑定 · 等待贴卡</h3><p>绑卡指令和倒计时状态</p></div></div></article>
<article class="state-card"><img src="actual-screens/23-bind-add-card.png" alt="添加设备卡片"><div class="state-copy"><b>09</b><div><h3>卡片 · 添加入口</h3><p>为当前设备追加新卡</p></div></div></article>
<article class="state-card"><img src="actual-screens/24-sleep-schedule.png" alt="定时休眠设置"><div class="state-copy"><b>10</b><div><h3>设备 · 定时休眠</h3><p>休眠时段和时区配置</p></div></div></article>
</div>
</section>
<section class="flow flow-social">
<div class="flow-heading">
<div>
<span class="flow-number">03</span>
<p class="flow-eyebrow">MESSAGES & LOCATION</p>
<h2>亲子会话与设备定位</h2>
</div>
<p>覆盖游客预览、会话列表、AI 与家长消息,以及绑定设备后的实时位置。</p>
</div>
<div class="state-grid">
<article class="state-card"><img src="actual-screens/03-chat-guest.png" alt="玩伴游客态"><div class="state-copy"><b>11</b><div><h3>玩伴 · 游客态</h3><p>未登录时的会话能力预览</p></div></div></article>
<article class="state-card"><img src="actual-screens/10-chat-list.png" alt="会话列表"><div class="state-copy"><b>12</b><div><h3>玩伴 · 会话列表</h3><p>AI、家长与宝贝会话</p></div></div></article>
<article class="state-card"><img src="actual-screens/11-chat-ai-detail.png" alt="AI 会话详情"><div class="state-copy"><b>13</b><div><h3>会话 · AI 玩伴</h3><p>宝贝与伴伴的消息记录</p></div></div></article>
<article class="state-card"><img src="actual-screens/12-chat-parent-detail.png" alt="家长会话详情"><div class="state-copy"><b>14</b><div><h3>会话 · 家长沟通</h3><p>文字、语音和留言控制</p></div></div></article>
<article class="state-card"><img src="actual-screens/04-location-guest.png" alt="定位游客态"><div class="state-copy"><b>15</b><div><h3>定位 · 游客态</h3><p>地图与位置能力预览</p></div></div></article>
<article class="state-card"><img src="actual-screens/13-location-current.png" alt="设备当前位置"><div class="state-copy"><b>16</b><div><h3>定位 · 当前位置</h3><p>地图、地址和定位参数</p></div></div></article>
</div>
</section>
<section class="flow flow-manage">
<div class="flow-heading">
<div>
<span class="flow-number">04</span>
<p class="flow-eyebrow">MANAGEMENT & FAMILY</p>
<h2>管理、资料与家庭共享</h2>
</div>
<p>当前管理主页与真实交互弹窗,包含宝贝、卡片、角色、家庭和邀请状态。</p>
</div>
<div class="state-grid">
<article class="state-card"><img src="actual-screens/05-manage-guest.png" alt="管理游客态"><div class="state-copy"><b>17</b><div><h3>管理 · 游客态</h3><p>管理功能的登录前预览</p></div></div></article>
<article class="state-card"><img src="actual-screens/14-manage-overview.png" alt="管理总览"><div class="state-copy"><b>18</b><div><h3>管理 · 账号总览</h3><p>账号、设备和系统版本</p></div></div></article>
<article class="state-card"><img src="actual-screens/15-manage-menu.png" alt="管理功能菜单"><div class="state-copy"><b>19</b><div><h3>管理 · 功能列表</h3><p>卡片、角色、家庭和通知</p></div></div></article>
<article class="state-card"><img src="actual-screens/16-manage-child-modal.png" alt="切换当前宝贝弹窗"><div class="state-copy"><b>20</b><div><h3>宝贝 · 切换资料</h3><p>当前、修改和删除操作</p></div></div></article>
<article class="state-card"><img src="actual-screens/17-manage-cards-modal.png" alt="我的卡片弹窗"><div class="state-copy"><b>21</b><div><h3>卡片 · 卡片列表</h3><p>卡片名称、UUID 和编辑</p></div></div></article>
<article class="state-card"><img src="actual-screens/18-manage-role-modal.png" alt="切换 AI 角色弹窗"><div class="state-copy"><b>22</b><div><h3>角色 · AI 角色</h3><p>当前角色和可选角色</p></div></div></article>
<article class="state-card"><img src="actual-screens/19-manage-family-modal.png" alt="家庭成员弹窗"><div class="state-copy"><b>23</b><div><h3>家庭 · 成员列表</h3><p>主成员、共享成员和邀请</p></div></div></article>
<article class="state-card"><img src="actual-screens/25-family-invite-valid.png" alt="有效家庭邀请"><div class="state-copy"><b>24</b><div><h3>邀请 · 等待加入</h3><p>有效家庭共享邀请</p></div></div></article>
<article class="state-card"><img src="actual-screens/26-family-invite-expired.png" alt="过期家庭邀请"><div class="state-copy"><b>25</b><div><h3>邀请 · 已失效</h3><p>过期邀请的禁用状态</p></div></div></article>
<article class="state-card"><img src="actual-screens/07-family-invite-invalid.png" alt="未登录邀请跳转登录"><div class="state-copy"><b>26</b><div><h3>邀请 · 未登录跳转</h3><p>进入邀请前的登录承接页</p></div></div></article>
</div>
</section>
<footer>
<span>Generated from the current banban-mini build</span>
<span>WeChat DevTools screenshots · local fixtures only · 2026-09-09</span>
</footer>
</main>
</body>
</html>
+242
View File
@@ -0,0 +1,242 @@
:root {
color-scheme: light;
--ink: #39281c;
--muted: #77685c;
--line: rgba(73, 54, 38, 0.12);
--orange: #ff8c42;
--green: #b8ec20;
--blue: #77bfea;
--coral: #ef7f72;
}
* { box-sizing: border-box; }
html,
body {
margin: 0;
background: #f3f0ea;
color: var(--ink);
font-family: -apple-system, BlinkMacSystemFont, "PingFang SC", "Microsoft YaHei", sans-serif;
letter-spacing: 0;
}
body { width: 1440px; }
.atlas {
width: 1440px;
overflow: hidden;
background: #f8f6f1;
}
.atlas-header {
display: grid;
min-height: 390px;
grid-template-columns: minmax(0, 1fr) 440px;
gap: 58px;
align-items: center;
padding: 64px 72px 58px;
border-bottom: 1px solid var(--line);
background: #fffdf9;
}
.eyebrow,
.flow-eyebrow {
margin: 0 0 12px;
color: #a36a38;
font-size: 15px;
font-weight: 750;
line-height: 1.4;
text-transform: uppercase;
}
h1 {
max-width: 760px;
margin: 0;
font-size: 54px;
font-weight: 820;
line-height: 1.12;
}
.header-copy > p {
max-width: 790px;
margin: 20px 0 0;
color: var(--muted);
font-size: 18px;
line-height: 1.72;
}
.legend {
display: flex;
flex-wrap: wrap;
gap: 12px 24px;
margin-top: 26px;
color: #695b50;
font-size: 14px;
}
.legend span {
display: inline-flex;
align-items: center;
gap: 8px;
}
.dot {
display: inline-block;
width: 9px;
height: 9px;
border-radius: 50%;
}
.dot.orange { background: var(--orange); }
.dot.green { background: #80b917; }
.dot.blue { background: #4d9dce; }
.dot.coral { background: var(--coral); }
.metrics {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 12px;
}
.metric {
min-height: 124px;
padding: 22px;
border: 1px solid var(--line);
border-radius: 8px;
background: #fff;
}
.metric.wide { grid-column: span 2; }
.metric strong {
display: block;
margin-bottom: 9px;
color: #28211c;
font-size: 40px;
line-height: 1;
}
.metric span {
color: var(--muted);
font-size: 14px;
line-height: 1.4;
}
.flow {
padding: 54px 52px 66px;
border-bottom: 1px solid var(--line);
}
.flow-entry { background: #fff9f1; }
.flow-binding { background: #f3f9ec; }
.flow-social { background: #f0f7fa; }
.flow-manage { background: #fff5f3; }
.flow-heading {
display: flex;
min-height: 130px;
align-items: flex-end;
justify-content: space-between;
gap: 40px;
margin: 0 6px 30px;
}
.flow-heading > div { position: relative; }
.flow-number {
position: absolute;
top: -32px;
left: 0;
color: rgba(57, 40, 28, 0.09);
font-size: 88px;
font-weight: 850;
line-height: 1;
}
.flow-eyebrow { position: relative; }
h2 {
position: relative;
margin: 0;
font-size: 34px;
line-height: 1.2;
}
.flow-heading > p {
max-width: 440px;
margin: 0 0 2px;
color: var(--muted);
font-size: 15px;
line-height: 1.7;
text-align: right;
}
.state-grid {
display: grid;
grid-template-columns: repeat(4, minmax(0, 1fr));
gap: 24px 20px;
align-items: start;
}
.state-card {
min-width: 0;
overflow: hidden;
border: 1px solid var(--line);
border-radius: 8px;
background: rgba(255, 255, 255, 0.96);
box-shadow: 0 10px 24px rgba(70, 52, 37, 0.08);
}
.state-card img {
display: block;
width: 100%;
height: auto;
border-bottom: 1px solid var(--line);
background: #fff9ea;
}
.state-copy {
display: grid;
grid-template-columns: 34px minmax(0, 1fr);
gap: 12px;
min-height: 100px;
padding: 16px;
}
.state-copy > b {
display: flex;
width: 30px;
height: 30px;
align-items: center;
justify-content: center;
border-radius: 50%;
background: #35291f;
color: #fff;
font-size: 11px;
line-height: 1;
}
.state-copy h3 {
margin: 0 0 6px;
font-size: 17px;
line-height: 1.35;
}
.state-copy p {
margin: 0;
color: var(--muted);
font-size: 13px;
line-height: 1.55;
}
footer {
display: flex;
min-height: 100px;
align-items: center;
justify-content: space-between;
gap: 30px;
padding: 0 64px;
background: #312820;
color: rgba(255, 255, 255, 0.72);
font-size: 13px;
}
+115
View File
@@ -0,0 +1,115 @@
<!doctype html>
<html lang="zh-CN">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>伴伴小程序当前页面全景</title>
<link rel="stylesheet" href="overview.css">
</head>
<body>
<main class="atlas">
<header class="atlas-header">
<div class="header-copy">
<div class="eyebrow">BANBAN MINI PROGRAM · CURRENT BUILD</div>
<h1>伴伴小程序 / 当前页面全景</h1>
<p>全部页面画面来自当前 <strong>banban-mini</strong> 构建,并由微信开发者工具直接截图。登录态使用本地固定展示数据,不包含线上用户数据。</p>
<div class="legend">
<span><i class="dot orange"></i>游客与入口</span>
<span><i class="dot green"></i>设备与绑定</span>
<span><i class="dot blue"></i>会话与定位</span>
<span><i class="dot coral"></i>管理与家庭</span>
</div>
</div>
<div class="metrics" aria-label="页面统计">
<div class="metric"><strong>11</strong><span>当前路由</span></div>
<div class="metric"><strong>26</strong><span>真实截图状态</span></div>
<div class="metric"><strong>4</strong><span>核心 Tab</span></div>
<div class="metric wide"><strong>390 px</strong><span>微信模拟器原始截图宽度</span></div>
</div>
</header>
<section class="flow flow-entry">
<div class="flow-heading">
<div>
<span class="flow-number">01</span>
<p class="flow-eyebrow">ENTRY & DEVICE</p>
<h2>进入、登录与设备首页</h2>
</div>
<p>从游客浏览、登录入口到设备绑定后的状态、告警和设备详情。</p>
</div>
<div class="state-grid">
<article class="state-card"><img src="actual-screens/01-device-guest.png" alt="设备首页游客态"><div class="state-copy"><b>01</b><div><h3>首页 · 游客态</h3><p>未登录时的设备入口</p></div></div></article>
<article class="state-card"><img src="actual-screens/02-login.png" alt="登录页"><div class="state-copy"><b>02</b><div><h3>登录 · 默认</h3><p>微信登录与协议确认</p></div></div></article>
<article class="state-card"><img src="actual-screens/08-device-online.png" alt="已绑定设备首页"><div class="state-copy"><b>03</b><div><h3>首页 · 已绑定</h3><p>电量、休眠、音量与告警</p></div></div></article>
<article class="state-card"><img src="actual-screens/09-device-detail.png" alt="设备详情展开"><div class="state-copy"><b>04</b><div><h3>首页 · 详情展开</h3><p>告警位置与设备详细信息</p></div></div></article>
<article class="state-card"><img src="actual-screens/06-notification-invalid.png" alt="通知链接失效"><div class="state-copy"><b>05</b><div><h3>通知 · 链接失效</h3><p>无效通知的回退页面</p></div></div></article>
<article class="state-card"><img src="actual-screens/20-bind-scan.png" alt="设备扫码绑定入口"><div class="state-copy"><b>06</b><div><h3>绑定 · 扫码入口</h3><p>扫码或填写设备信息</p></div></div></article>
</div>
</section>
<section class="flow flow-binding">
<div class="flow-heading">
<div>
<span class="flow-number">02</span>
<p class="flow-eyebrow">BINDING & DEVICE CONTROL</p>
<h2>设备二维码、贴卡与休眠</h2>
</div>
<p>当前绑定页真实展示二维码识别、等待贴卡、追加卡片和休眠配置。</p>
</div>
<div class="state-grid">
<article class="state-card"><img src="actual-screens/21-bind-recognized.png" alt="设备二维码已识别"><div class="state-copy"><b>07</b><div><h3>绑定 · 已识别</h3><p>设备号和序列号已回填</p></div></div></article>
<article class="state-card"><img src="actual-screens/22-bind-waiting-card.png" alt="等待设备贴卡"><div class="state-copy"><b>08</b><div><h3>绑定 · 等待贴卡</h3><p>绑卡指令和倒计时状态</p></div></div></article>
<article class="state-card"><img src="actual-screens/23-bind-add-card.png" alt="添加设备卡片"><div class="state-copy"><b>09</b><div><h3>卡片 · 添加入口</h3><p>为当前设备追加新卡</p></div></div></article>
<article class="state-card"><img src="actual-screens/24-sleep-schedule.png" alt="定时休眠设置"><div class="state-copy"><b>10</b><div><h3>设备 · 定时休眠</h3><p>休眠时段和时区配置</p></div></div></article>
</div>
</section>
<section class="flow flow-social">
<div class="flow-heading">
<div>
<span class="flow-number">03</span>
<p class="flow-eyebrow">MESSAGES & LOCATION</p>
<h2>亲子会话与设备定位</h2>
</div>
<p>覆盖游客预览、会话列表、AI 与家长消息,以及绑定设备后的实时位置。</p>
</div>
<div class="state-grid">
<article class="state-card"><img src="actual-screens/03-chat-guest.png" alt="玩伴游客态"><div class="state-copy"><b>11</b><div><h3>玩伴 · 游客态</h3><p>未登录时的会话能力预览</p></div></div></article>
<article class="state-card"><img src="actual-screens/10-chat-list.png" alt="会话列表"><div class="state-copy"><b>12</b><div><h3>玩伴 · 会话列表</h3><p>AI、家长与宝贝会话</p></div></div></article>
<article class="state-card"><img src="actual-screens/11-chat-ai-detail.png" alt="AI 会话详情"><div class="state-copy"><b>13</b><div><h3>会话 · AI 玩伴</h3><p>宝贝与伴伴的消息记录</p></div></div></article>
<article class="state-card"><img src="actual-screens/12-chat-parent-detail.png" alt="家长会话详情"><div class="state-copy"><b>14</b><div><h3>会话 · 家长沟通</h3><p>文字、语音和留言控制</p></div></div></article>
<article class="state-card"><img src="actual-screens/04-location-guest.png" alt="定位游客态"><div class="state-copy"><b>15</b><div><h3>定位 · 游客态</h3><p>地图与位置能力预览</p></div></div></article>
<article class="state-card"><img src="actual-screens/13-location-current.png" alt="设备当前位置"><div class="state-copy"><b>16</b><div><h3>定位 · 当前位置</h3><p>地图、地址和定位参数</p></div></div></article>
</div>
</section>
<section class="flow flow-manage">
<div class="flow-heading">
<div>
<span class="flow-number">04</span>
<p class="flow-eyebrow">MANAGEMENT & FAMILY</p>
<h2>管理、资料与家庭共享</h2>
</div>
<p>当前管理主页与真实交互弹窗,包含宝贝、卡片、角色、家庭和邀请状态。</p>
</div>
<div class="state-grid">
<article class="state-card"><img src="actual-screens/05-manage-guest.png" alt="管理游客态"><div class="state-copy"><b>17</b><div><h3>管理 · 游客态</h3><p>管理功能的登录前预览</p></div></div></article>
<article class="state-card"><img src="actual-screens/14-manage-overview.png" alt="管理总览"><div class="state-copy"><b>18</b><div><h3>管理 · 账号总览</h3><p>账号、设备和系统版本</p></div></div></article>
<article class="state-card"><img src="actual-screens/15-manage-menu.png" alt="管理功能菜单"><div class="state-copy"><b>19</b><div><h3>管理 · 功能列表</h3><p>卡片、角色、家庭和通知</p></div></div></article>
<article class="state-card"><img src="actual-screens/16-manage-child-modal.png" alt="切换当前宝贝弹窗"><div class="state-copy"><b>20</b><div><h3>宝贝 · 切换资料</h3><p>当前、修改和删除操作</p></div></div></article>
<article class="state-card"><img src="actual-screens/17-manage-cards-modal.png" alt="我的卡片弹窗"><div class="state-copy"><b>21</b><div><h3>卡片 · 卡片列表</h3><p>卡片名称、UUID 和编辑</p></div></div></article>
<article class="state-card"><img src="actual-screens/18-manage-role-modal.png" alt="切换 AI 角色弹窗"><div class="state-copy"><b>22</b><div><h3>角色 · AI 角色</h3><p>当前角色和可选角色</p></div></div></article>
<article class="state-card"><img src="actual-screens/19-manage-family-modal.png" alt="家庭成员弹窗"><div class="state-copy"><b>23</b><div><h3>家庭 · 成员列表</h3><p>主成员、共享成员和邀请</p></div></div></article>
<article class="state-card"><img src="actual-screens/25-family-invite-valid.png" alt="有效家庭邀请"><div class="state-copy"><b>24</b><div><h3>邀请 · 等待加入</h3><p>有效家庭共享邀请</p></div></div></article>
<article class="state-card"><img src="actual-screens/26-family-invite-expired.png" alt="过期家庭邀请"><div class="state-copy"><b>25</b><div><h3>邀请 · 已失效</h3><p>过期邀请的禁用状态</p></div></div></article>
<article class="state-card"><img src="actual-screens/07-family-invite-invalid.png" alt="未登录邀请跳转登录"><div class="state-copy"><b>26</b><div><h3>邀请 · 未登录跳转</h3><p>进入邀请前的登录承接页</p></div></div></article>
</div>
</section>
<footer>
<span>Generated from the current banban-mini build</span>
<span>WeChat DevTools screenshots · local fixtures only · 2026-09-09</span>
</footer>
</main>
</body>
</html>
@@ -0,0 +1,156 @@
#import <Cocoa/Cocoa.h>
#import <CoreImage/CoreImage.h>
#import <WebKit/WebKit.h>
static CGFloat PageWidth = 1440.0;
static CGFloat OutputScale = 1.0;
static CGFloat SharpenIntensity = 0.0;
@interface PageRenderer : NSObject <WKNavigationDelegate>
@property(nonatomic, strong) WKWebView *webView;
@property(nonatomic, strong) NSURL *inputURL;
@property(nonatomic, strong) NSURL *outputURL;
@end
@implementation PageRenderer
- (instancetype)initWithInputURL:(NSURL *)inputURL outputURL:(NSURL *)outputURL {
self = [super init];
if (self) {
_inputURL = inputURL;
_outputURL = outputURL;
_webView = [[WKWebView alloc] initWithFrame:NSMakeRect(0, 0, PageWidth, 900)];
_webView.navigationDelegate = self;
}
return self;
}
- (void)start {
NSURL *repositoryRoot = self.inputURL.URLByDeletingLastPathComponent
.URLByDeletingLastPathComponent.URLByDeletingLastPathComponent;
[self.webView loadFileURL:self.inputURL allowingReadAccessToURL:repositoryRoot];
}
- (void)webView:(WKWebView *)webView didFinishNavigation:(WKNavigation *)navigation {
NSString *heightScript = @"Math.max(document.body.scrollHeight, document.documentElement.scrollHeight)";
[webView evaluateJavaScript:heightScript completionHandler:^(id result, NSError *error) {
if (error || ![result isKindOfClass:NSNumber.class]) {
[self fail:[NSString stringWithFormat:@"Could not read page height: %@", error.localizedDescription ?: @"unknown error"]];
return;
}
CGFloat pageHeight = [(NSNumber *)result doubleValue];
[webView setFrameSize:NSMakeSize(PageWidth, pageHeight)];
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.8 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
WKSnapshotConfiguration *configuration = [[WKSnapshotConfiguration alloc] init];
configuration.rect = CGRectMake(0, 0, PageWidth, pageHeight);
configuration.snapshotWidth = @(PageWidth * OutputScale);
[webView takeSnapshotWithConfiguration:configuration completionHandler:^(NSImage *image, NSError *snapshotError) {
NSBitmapImageRep *bitmap = nil;
if (image && SharpenIntensity > 0.0) {
CIImage *inputImage = [[CIImage alloc] initWithData:image.TIFFRepresentation];
CIFilter *sharpen = [CIFilter filterWithName:@"CIUnsharpMask"];
[sharpen setValue:inputImage forKey:kCIInputImageKey];
[sharpen setValue:@(1.0) forKey:kCIInputRadiusKey];
[sharpen setValue:@(SharpenIntensity) forKey:kCIInputIntensityKey];
CIImage *outputImage = sharpen.outputImage;
CIContext *context = [CIContext contextWithOptions:nil];
CGImageRef outputCGImage = outputImage
? [context createCGImage:outputImage fromRect:inputImage.extent]
: nil;
if (outputCGImage) {
bitmap = [[NSBitmapImageRep alloc] initWithCGImage:outputCGImage];
CGImageRelease(outputCGImage);
}
} else if (image) {
bitmap = [[NSBitmapImageRep alloc] initWithData:image.TIFFRepresentation];
}
NSData *pngData = [bitmap representationUsingType:NSBitmapImageFileTypePNG properties:@{}];
if (snapshotError || !pngData) {
[self fail:[NSString stringWithFormat:@"Could not create snapshot: %@", snapshotError.localizedDescription ?: @"unknown error"]];
return;
}
NSError *writeError = nil;
if (![pngData writeToURL:self.outputURL options:NSDataWritingAtomic error:&writeError]) {
[self fail:[NSString stringWithFormat:@"Could not write PNG: %@", writeError.localizedDescription]];
return;
}
printf("Rendered %dx%d at %.2fx, sharpen %.2f -> %s\n",
(int)PageWidth,
(int)pageHeight,
OutputScale,
SharpenIntensity,
self.outputURL.path.UTF8String);
exit(0);
}];
});
}];
}
- (void)webView:(WKWebView *)webView didFailNavigation:(WKNavigation *)navigation withError:(NSError *)error {
[self fail:[NSString stringWithFormat:@"Navigation failed: %@", error.localizedDescription]];
}
- (void)webView:(WKWebView *)webView didFailProvisionalNavigation:(WKNavigation *)navigation withError:(NSError *)error {
[self fail:[NSString stringWithFormat:@"Page load failed: %@", error.localizedDescription]];
}
- (void)fail:(NSString *)message {
fprintf(stderr, "%s\n", message.UTF8String);
exit(1);
}
@end
int main(int argc, const char *argv[]) {
@autoreleasepool {
if (argc < 3 || argc > 6) {
fprintf(stderr, "Usage: render-overview <input.html> <output.png> [page-width] [output-scale] [sharpen-intensity]\n");
return 2;
}
if (argc >= 4) {
double requestedWidth = strtod(argv[3], NULL);
if (requestedWidth < 320.0 || requestedWidth > 4096.0) {
fprintf(stderr, "Page width must be between 320 and 4096 pixels\n");
return 2;
}
PageWidth = (CGFloat)requestedWidth;
}
if (argc >= 5) {
double requestedScale = strtod(argv[4], NULL);
if (requestedScale < 1.0 || requestedScale > 4.0) {
fprintf(stderr, "Output scale must be between 1 and 4\n");
return 2;
}
OutputScale = (CGFloat)requestedScale;
}
if (argc == 6) {
double requestedSharpness = strtod(argv[5], NULL);
if (requestedSharpness < 0.0 || requestedSharpness > 1.0) {
fprintf(stderr, "Sharpen intensity must be between 0 and 1\n");
return 2;
}
SharpenIntensity = (CGFloat)requestedSharpness;
}
[NSApplication sharedApplication];
[NSApp setActivationPolicy:NSApplicationActivationPolicyProhibited];
NSString *inputPath = [NSString stringWithUTF8String:argv[1]].stringByStandardizingPath;
NSString *outputPath = [NSString stringWithUTF8String:argv[2]].stringByStandardizingPath;
PageRenderer *renderer = [[PageRenderer alloc]
initWithInputURL:[NSURL fileURLWithPath:inputPath]
outputURL:[NSURL fileURLWithPath:outputPath]];
[renderer start];
[NSRunLoop.mainRunLoop run];
}
return 0;
}
+130
View File
@@ -0,0 +1,130 @@
:root {
--orange: #ff8c42;
--orange-deep: #ff6b35;
--orange-soft: #fef3e8;
--ink: #1a1a1a;
--muted: #666666;
--quiet: #999999;
--line: #ece9e5;
--surface: #ffffff;
--canvas: #f5f7fa;
--green: #16a34a;
--green-soft: #e8f8f0;
--blue: #5b8ff9;
--blue-soft: #e8f0fe;
--red: #be123c;
--red-soft: #fff1f2;
--purple: #8b5cf6;
--purple-soft: #f0e6ff;
--yellow: #f59e0b;
--yellow-soft: #fff7e8;
--shadow: 0 10px 30px rgba(31, 41, 55, 0.08);
--shadow-soft: 0 4px 16px rgba(31, 41, 55, 0.06);
}
* {
box-sizing: border-box;
}
html,
body {
margin: 0;
padding: 0;
background: #eceff3;
color: var(--ink);
font-family: -apple-system, BlinkMacSystemFont, "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", sans-serif;
letter-spacing: 0;
}
button,
input {
font: inherit;
letter-spacing: 0;
}
.poster {
width: 1440px;
margin: 0 auto;
overflow: hidden;
background: #fbfaf8;
}
.poster-kicker {
display: inline-flex;
align-items: center;
gap: 10px;
color: var(--orange-deep);
font-size: 16px;
font-weight: 700;
}
.poster-kicker::before {
width: 24px;
height: 4px;
border-radius: 999px;
background: var(--orange);
content: "";
}
.poster-title {
margin: 14px 0 8px;
font-size: 56px;
line-height: 1.12;
font-weight: 800;
}
.poster-subtitle {
margin: 0;
color: var(--muted);
font-size: 22px;
line-height: 1.6;
}
.meta-chip {
display: inline-flex;
min-height: 48px;
align-items: center;
justify-content: center;
padding: 0 18px;
border: 1px solid #ebe7e1;
border-radius: 8px;
background: rgba(255, 255, 255, 0.86);
color: #4b5563;
font-size: 16px;
font-weight: 650;
}
.section-eyebrow {
color: var(--orange-deep);
font-size: 15px;
font-weight: 800;
}
.section-title {
margin: 8px 0 4px;
font-size: 30px;
line-height: 1.25;
font-weight: 800;
}
.section-note {
margin: 0;
color: var(--muted);
font-size: 16px;
line-height: 1.55;
}
.poster-footer {
display: flex;
align-items: center;
justify-content: space-between;
padding: 34px 56px 40px;
border-top: 1px solid #ece7df;
color: #7b7b7b;
font-size: 14px;
}
.footer-brand {
color: var(--ink);
font-weight: 800;
}