Files
iboard/docs/V0.2/界面设计.html
T
cheney 4a04720a5e feat(V0.2): OTA 版本与判新口径 + Tab 结构调整
1) 版本对齐分支
- package.json / tauri.conf.json / Cargo.toml / Cargo.lock: 0.1.0 -> 0.2.0, OTA 包版本变为 0.2.0-ota
- 新增 scripts/ci/check-version.mjs: 分支 dev-x.y 与 package.json 版本不一致时 CI 直接失败
- docker-build.yml / ota-build.yml: min_binary_version 与前端版本解耦, 固定 0.1.0
  (现有 APK 原生层仍是 0.1.0, 抬高会导致旧 APK 收不到更新)

2) 我的页 OTA「开发版本」开关 (默认关, 持久化 localStorage.iboard_ota_dev_mode)
- 关: 按版本号逐字段比较 (src/utils/version.js), 服务端 version 更高才算新
- 开: 按 build sequence 判新 (hotswap checkUpdate), 每次构建都能推到设备
- Web 端: 开发口径退化为版本不同即算新; 生产口径严格比较大小

3) 打卡 Tab 移到最左并默认选中, 移除特效开关
- BottomNav: 打卡/宏观/监控/我; 删除 home 图标
- App: / 与 404 均重定向到 /checkin
- RingProgress/Celebrate: 删除 effectsEnabled/setEffectsEnabled, 特效恒开
- HabitView: 去掉头部「特效开/关」按钮

4) 取消首页 Tab, 报警/新闻并入宏观
- 新增 pages/macro/AlertsView.jsx、NewsView.jsx; 删除 HomePage.jsx
- MacroPage: 6 个子 Tab (油价/经济数据/策略研究/经济文章/报警/新闻), 报警角标保留
- 新闻详情 /news/:id -> /macro/news/:id (宏观 Tab 保持高亮)

5) 文档: 需求文档补 F-OT 需求点与已决策项; 设计文档新增 §10 变更记录与判新设计;
   界面设计.html 11 屏 (Tab 4 项/去掉特效开关/宏观合并/我的 OTA 开关 关+开 两态)
2026-10-22 17:17:29 +08:00

1083 lines
76 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>iBoard V0.2 界面设计 · 打卡 / 日历 / 清单</title>
<style>
:root{
--bg:#f0f2f5; --page:#ffffff; --card:#ffffff;
--text:#333333; --muted:#666666; --weak:#999999; --line:#eeeeee;
--primary:#1677ff; --success:#00b578; --warn:#ffc53d; --over:#a855f7;
--danger:#ff3141; --orange:#ff8f1f; --teal:#14b8a6; --pink:#f43f9e;
--nav-h:45px; --tab-h:72px;
}
*{box-sizing:border-box;margin:0;padding:0}
body{
background:var(--bg); color:#1f2328; font-size:14px; line-height:1.6;
font-family:-apple-system,BlinkMacSystemFont,'Segoe UI','PingFang SC','Hiragino Sans GB','Microsoft YaHei',Roboto,sans-serif;
-webkit-font-smoothing:antialiased;
}
.wrap{max-width:1400px;margin:0 auto;padding:36px 24px 72px}
.doc-head{border-bottom:1px solid #e3e6ea;padding-bottom:20px;margin-bottom:28px}
.doc-head h1{font-size:26px;font-weight:700;letter-spacing:.5px}
.doc-head p{color:var(--muted);font-size:13px;margin-top:6px}
.meta{display:flex;flex-wrap:wrap;gap:8px;margin-top:14px}
.meta span{font-size:12px;background:#fff;border:1px solid #e3e6ea;border-radius:999px;padding:3px 12px;color:var(--muted)}
h2{font-size:19px;font-weight:700;margin:40px 0 6px;display:flex;align-items:center;gap:10px}
h2::before{content:"";width:4px;height:17px;background:var(--primary);border-radius:2px}
h2 .sub{font-size:12px;font-weight:400;color:var(--weak)}
h3{font-size:15px;font-weight:700;margin:22px 0 10px;color:#24292f}
p.desc{color:var(--muted);font-size:13px;margin-bottom:16px}
/* ---------- 设计规范 ---------- */
.tokens{display:grid;grid-template-columns:repeat(auto-fit,minmax(300px,1fr));gap:18px}
.panel{background:#fff;border:1px solid #e6e8eb;border-radius:12px;padding:16px 18px}
.panel h4{font-size:13px;color:var(--muted);font-weight:600;margin-bottom:12px;letter-spacing:.4px}
.swatches{display:flex;flex-wrap:wrap;gap:10px}
.sw{width:88px;text-align:center}
.sw i{display:block;height:44px;border-radius:8px;border:1px solid rgba(0,0,0,.06)}
.sw b{display:block;font-size:11px;font-weight:600;margin-top:5px}
.sw em{display:block;font-size:10px;font-style:normal;color:var(--weak)}
.kv{display:grid;grid-template-columns:96px 1fr;gap:6px 12px;font-size:12px}
.kv span{color:var(--weak)}
.kv b{font-weight:600}
.type-row{display:flex;align-items:baseline;gap:12px;padding:6px 0;border-bottom:1px dashed var(--line);font-size:12px}
.type-row:last-child{border-bottom:none}
.type-row em{font-style:normal;color:var(--weak);width:132px;flex-shrink:0}
.type-row span{color:#444}
/* ---------- 手机 ---------- */
.stage{display:grid;grid-template-columns:repeat(auto-fit,minmax(420px,1fr));gap:30px 26px;justify-items:center}
.shot{width:420px}
.phone{
width:420px;height:820px;background:var(--page);position:relative;overflow:hidden;
border:1px solid #d8dde3;border-radius:22px;box-shadow:0 10px 24px rgba(15,23,42,.08);
display:flex;flex-direction:column;
}
.phone::after{content:"";position:absolute;top:8px;left:50%;transform:translateX(-50%);width:118px;height:20px;background:#111827;border-radius:12px;opacity:.9}
.navbar{height:var(--nav-h);margin-top:28px;flex-shrink:0;display:flex;align-items:center;justify-content:center;
font-size:17px;font-weight:600;border-bottom:1px solid var(--line);background:#fff}
.body{flex:1;min-height:0;overflow:hidden;background:var(--bg);padding:0 0 8px}
.tabbar{height:var(--tab-h);flex-shrink:0;background:#fff;border-top:1px solid var(--line);
display:flex;align-items:flex-start;padding-top:7px}
.tab{flex:1;display:flex;flex-direction:column;align-items:center;gap:3px;color:var(--weak);font-size:11px}
.tab.on{color:var(--primary)}
.tab svg{width:23px;height:23px}
.caption{margin-top:10px;background:#fff;border:1px solid #e6e8eb;border-radius:10px;padding:10px 14px}
.caption b{font-size:13px}
.caption p{font-size:12px;color:var(--muted);margin-top:3px}
.tag{display:inline-block;font-size:11px;color:var(--primary);background:#f0f5ff;border-radius:4px;padding:1px 6px;margin-left:6px}
/* ---------- 打卡视图 ---------- */
.habit-head{display:flex;justify-content:space-between;align-items:flex-end;padding:12px 14px 8px}
.habit-date{font-size:18px;font-weight:700}
.habit-summary{font-size:12px;color:var(--muted);margin-top:2px}
.habit-summary strong{color:var(--primary);font-size:14px}
.badge{font-size:11px;color:#ad6800;background:#fff7e6;border-radius:4px;padding:1px 5px;margin-left:6px}
.mini-btn{font-size:11px;border:1px solid var(--primary);color:var(--primary);border-radius:999px;padding:3px 10px;background:#fff}
.mini-btn.solid{background:var(--primary);color:#fff;border-color:var(--primary)}
.mini-btn.grey{border-color:#dcdcdc;color:var(--muted)}
.head-right{display:flex;gap:6px}
.habit-list{padding:0 12px;display:flex;flex-direction:column;gap:10px}
.habit-card{position:relative;background:#fff;border-radius:10px;padding:12px;box-shadow:0 1px 2px rgba(0,0,0,.04);
display:grid;grid-template-columns:auto 1fr auto;grid-template-areas:"ring main actions" "foot foot foot";gap:8px 12px}
.habit-card.done{background:#f7fbf8}
.ring{grid-area:ring;width:58px;height:58px;position:relative;display:flex;align-items:center;justify-content:center}
.ring svg{display:block}
.ring-center{position:absolute;inset:0;display:flex;flex-direction:column;align-items:center;justify-content:center;font-size:16px;font-weight:700}
.ring-center em{font-style:normal;font-size:10px;color:var(--muted);font-weight:400}
.ring-over{position:absolute;top:-4px;right:-10px;font-size:10px;color:#fff;background:var(--orange);border-radius:999px;padding:0 5px}
.habit-main{grid-area:main;min-width:0}
.habit-name{font-size:15px;font-weight:600}
.habit-meta{font-size:12px;color:var(--muted);margin-top:3px}
.streak{margin-left:8px;color:var(--orange);background:rgba(255,143,31,.12);border-radius:4px;padding:1px 6px}
.habit-range{font-size:11px;color:var(--muted);margin-top:3px}
.over-txt{margin-left:8px;color:var(--orange)}
.habit-actions{grid-area:actions;display:flex;align-items:center}
.habit-foot{grid-area:foot;display:flex;gap:14px;padding-top:8px;border-top:1px dashed var(--line)}
.link{font-size:12px;color:var(--primary)}
.link.danger{color:var(--danger)}
/* ---------- 日历视图 ---------- */
.cal-month-bar{display:flex;align-items:center;gap:10px;padding:12px 14px 6px}
.cal-month{font-size:16px;font-weight:700}
.cal-arrow{font-size:20px;color:var(--muted);padding:0 4px;line-height:1}
.cal-today{margin-left:auto;font-size:12px;color:var(--primary)}
.seg{display:flex;margin:4px 14px 10px;background:#f2f3f5;border-radius:8px;padding:3px}
.seg div{flex:1;text-align:center;font-size:13px;padding:5px 0;border-radius:6px;color:var(--muted)}
.seg div.on{background:#fff;color:var(--text);font-weight:600;box-shadow:0 1px 2px rgba(0,0,0,.06)}
.cal-grid{margin:0 12px;background:#fff;border-radius:10px;padding:8px 6px 10px;box-shadow:0 1px 2px rgba(0,0,0,.04)}
.grid7{display:grid;grid-template-columns:repeat(7,1fr)}
.wd span{text-align:center;font-size:11px;color:var(--muted);padding:4px 0}
.day{display:flex;flex-direction:column;align-items:center;justify-content:flex-start;padding:5px 0;border-radius:8px}
.day .num{font-size:13px}
.day.out .num{color:#c8c8c8}
.day.future .num{color:#b0b0b0}
.day.today .num{color:var(--primary);font-weight:700}
.day.sel{background:var(--primary)}
.day.sel .num{color:#fff;font-weight:600}
.dot{width:5px;height:5px;border-radius:50%;margin-top:3px}
.dot.partial{background:var(--warn)} .dot.full{background:var(--success)} .dot.over{background:var(--over)}
.day.sel .dot{background:#fff}
.cal-rate{font-size:12px;color:var(--muted);padding:8px 14px}
.cal-rate strong{color:var(--primary)}
.cal-detail-title{font-size:14px;font-weight:600;padding:6px 14px 8px}
.cal-item{display:flex;align-items:center;gap:8px;background:#fff;border-radius:8px;padding:10px 12px;margin:0 12px 8px;box-shadow:0 1px 2px rgba(0,0,0,.04)}
.cal-item.done{background:#f7fbf8}
.cdot{width:8px;height:8px;border-radius:50%;flex-shrink:0}
.cname{font-size:14px;font-weight:600}
.cmeta{font-size:11px;color:var(--muted);flex:1;text-align:right}
.cplan{font-size:12px;color:var(--muted)}
/* ---------- 清单视图 ---------- */
.list-head{display:flex;align-items:center;gap:8px;padding:12px 14px 10px}
.list-head .seg{flex:1;margin:0}
.checklist-card{background:#fff;border-radius:10px;padding:12px;margin:0 12px 10px;box-shadow:0 1px 2px rgba(0,0,0,.04)}
.checklist-card.done{background:#f7fbf8}
.cl-title-row{display:flex;align-items:center;gap:8px}
.cl-title{font-size:15px;font-weight:600}
.cl-scene{font-size:11px;color:var(--primary);background:#f0f5ff;border-radius:4px;padding:1px 6px}
.progress-row{display:flex;align-items:center;gap:8px;margin-top:8px}
.bar{flex:1;height:6px;border-radius:3px;background:#f0f0f0;overflow:hidden}
.bar i{display:block;height:100%;border-radius:3px;background:var(--success)}
.count{font-size:12px;color:var(--muted)}
.arrow{color:var(--muted);transform:rotate(90deg)}
.cl-body{margin-top:10px;padding-top:8px;border-top:1px dashed var(--line)}
.cl-item{display:flex;align-items:center;gap:8px;padding:7px 0}
.box{width:20px;height:20px;border-radius:50%;border:1.5px solid #d0d0d0;display:flex;align-items:center;justify-content:center;flex-shrink:0}
.cl-item.done .box{background:var(--success);border-color:var(--success)}
.box svg{opacity:0} .cl-item.done .box svg{opacity:1}
.cl-item-title{flex:1;font-size:14px}
.cl-item.done .cl-item-title{color:#a0a0a0;text-decoration:line-through}
.cl-del{color:#ccc;font-size:18px;line-height:1;padding:0 4px}
.cl-add{display:flex;gap:8px;margin-top:8px}
.cl-add .inp{flex:1;border:1px solid var(--line);border-radius:6px;padding:6px 8px;font-size:13px;color:var(--weak);background:#fff}
.cl-actions{display:flex;flex-wrap:wrap;gap:12px;margin-top:10px}
/* ---------- 弹窗 ---------- */
.mask{position:absolute;inset:28px 0 0 0;background:rgba(0,0,0,.45);z-index:9}
.sheet{position:absolute;left:0;right:0;bottom:0;background:#fff;border-radius:12px 12px 0 0;padding:16px 16px 12px;z-index:10;max-height:560px;overflow:hidden}
.sheet-title{font-size:16px;font-weight:700;margin-bottom:12px}
.field{margin-bottom:13px}
.field label{display:block;font-size:12px;color:var(--muted);margin-bottom:6px}
.inp{border:1px solid var(--line);border-radius:8px;padding:8px 10px;font-size:14px;background:#fff;color:var(--text)}
.inp.ph{color:#b8bcc4}
.chips{display:flex;gap:8px}
.chip{flex:1;text-align:center;font-size:13px;padding:6px 0;border-radius:8px;background:#f5f6f8;color:var(--muted)}
.chip.on{background:var(--primary);color:#fff;font-weight:600}
.stepper{display:inline-flex;align-items:center;gap:10px}
.stepper span{width:28px;height:28px;border-radius:6px;background:#f5f6f8;display:flex;align-items:center;justify-content:center;color:var(--muted)}
.stepper span.act{background:var(--primary);color:#fff}
.stepper b{font-size:16px;min-width:24px;text-align:center}
.colors{display:flex;gap:10px;flex-wrap:wrap}
.cd{width:26px;height:26px;border-radius:50%;border:2px solid transparent}
.cd.on{border-color:rgba(0,0,0,.25);transform:scale(1.12)}
.btn-primary{display:block;text-align:center;background:var(--primary);color:#fff;border-radius:8px;padding:10px 0;font-size:15px;font-weight:600;margin-top:18px}
.btn-ghost{display:block;text-align:center;border:1px solid var(--line);border-radius:8px;padding:9px 0;font-size:15px;color:var(--muted);margin-top:8px;background:#fff}
/* ---------- 特效 ---------- */
.fx{position:absolute;inset:0;pointer-events:none}
.fx .halo{position:absolute;left:50%;top:50%;width:88px;height:88px;margin:-44px 0 0 -44px;border-radius:50%;border:2px solid var(--success);opacity:.55}
.fx .halo2{width:108px;height:108px;margin:-54px 0 0 -54px;opacity:.25}
.fx i{position:absolute;left:50%;top:50%;width:6px;height:6px;border-radius:50%;}
/* ---------- 空态 ---------- */
.empty{text-align:center;padding:70px 20px;color:var(--weak)}
.empty .ico{width:64px;height:64px;margin:0 auto 12px;border-radius:50%;background:#f2f3f5;display:flex;align-items:center;justify-content:center;color:#c4c8ce;font-size:30px}
.empty p{font-size:13px;margin-bottom:12px}
/* ---------- 表格 ---------- */
table{width:100%;border-collapse:collapse;background:#fff;border:1px solid #e6e8eb;border-radius:10px;overflow:hidden;font-size:13px}
th,td{border-bottom:1px solid var(--line);padding:9px 12px;text-align:left;vertical-align:middle}
th{background:#fafbfc;font-weight:600;color:#444;white-space:nowrap}
tr:last-child td{border-bottom:none}
td code{background:#f4f6f8;border-radius:4px;padding:1px 5px;font-size:12px}
.legend{display:flex;flex-wrap:wrap;gap:18px;font-size:12px;color:var(--muted);margin:10px 0 0}
.legend span{display:flex;align-items:center;gap:6px}
.note{font-size:12px;color:var(--weak);margin-top:8px}
.flow{background:#fff;border:1px solid #e6e8eb;border-radius:12px;padding:16px 18px;font-size:13px;color:#333;white-space:pre;overflow-x:auto;line-height:1.7}
/* ---------- 宏观子 Tab(6 项,可横向滚动) ---------- */
.mtabs{display:flex;gap:18px;padding:10px 14px 8px;overflow-x:auto;background:#fff;border-bottom:1px solid var(--line);font-size:13px;color:var(--muted);white-space:nowrap}
.mtabs::-webkit-scrollbar{display:none}
.mtabs .on{color:var(--primary);font-weight:600;position:relative}
.mtabs .on::after{content:"";position:absolute;left:50%;transform:translateX(-50%);bottom:-9px;width:22px;height:3px;border-radius:2px;background:var(--primary)}
.mtabs .badge-dot{display:inline-block;min-width:15px;height:15px;line-height:15px;padding:0 4px;margin-left:3px;
background:var(--danger);color:#fff;font-size:10px;border-radius:8px;text-align:center;vertical-align:1px}
/* ---------- 资讯卡片(报警 / 新闻) ---------- */
.feed{padding:10px 14px 14px;display:flex;flex-direction:column;gap:8px}
.feed-card{background:#fff;border:1px solid #eef0f2;border-radius:10px;padding:10px 12px;box-shadow:0 1px 2px rgba(0,0,0,.04)}
.feed-title{font-size:14px;font-weight:600;line-height:1.45;color:var(--text)}
.feed-sub{font-size:12px;color:var(--muted);margin-top:4px;line-height:1.5}
.feed-meta{display:flex;gap:10px;font-size:11px;color:var(--weak);margin-top:6px}
.feed-meta .rk{color:var(--primary)}
.alert-name{color:var(--danger);font-weight:600}
/* ---------- 我的页 ---------- */
.me-head{display:flex;flex-direction:column;align-items:center;gap:4px;padding:18px 0 16px;background:#fff}
.me-avatar{width:60px;height:60px;border-radius:50%;background:#e8effd;color:var(--primary);
display:flex;align-items:center;justify-content:center;font-size:22px;font-weight:600}
.me-name{font-size:16px;font-weight:600}
.me-mail{font-size:12px;color:var(--weak)}
.me-group{margin:10px 12px 0;background:#fff;border-radius:10px;overflow:hidden;border:1px solid #eef0f2}
.me-group .g-head{font-size:12px;color:var(--weak);padding:9px 14px 6px}
.me-row{display:flex;align-items:center;justify-content:space-between;gap:12px;padding:11px 14px;border-top:1px solid var(--line);font-size:14px}
.me-group .g-head + .me-row{border-top:none}
.me-row .val{font-size:12px;color:var(--weak)}
.me-row .hint{font-size:11px;color:var(--weak);margin-top:2px;line-height:1.45}
.sw{width:42px;height:25px;border-radius:999px;background:#e5e7eb;position:relative;flex-shrink:0;transition:background .2s}
.sw::after{content:"";position:absolute;top:2px;left:2px;width:21px;height:21px;border-radius:50%;background:#fff;box-shadow:0 1px 3px rgba(0,0,0,.2)}
.sw.on{background:var(--success)}
.sw.on::after{left:19px}
.me-actions{display:flex;gap:8px;padding:12px 14px;border-top:1px solid var(--line)}
.me-actions .mini-btn{flex:1;text-align:center;padding:7px 0;font-size:13px}
.me-actions .mini-btn.ghost{border-color:#e5e7eb;color:var(--muted)}
</style>
</head>
<body>
<div class="wrap">
<div class="doc-head">
<h1>iBoard V0.2 · 界面设计</h1>
<p>打卡 / 日历 / 清单 —— 移动端静态高保真原型(设计基线 420 × 820,适配 375~430px)</p>
<div class="meta">
<span>版本 V0.2(0.2.0)</span><span>分支 dev-0.2</span><span>antd-mobile 风格</span>
<span>底部 4 Tab(打卡默认)</span><span>时区 Asia/Shanghai</span><span>文档日期 2026-10-22</span>
</div>
</div>
<!-- ============ 1 设计规范 ============ -->
<h2>1 设计规范<span class="sub">Design Tokens</span></h2>
<p class="desc">沿用项目既有 <code>:root</code> 变量(<code>src/styles/globals.css</code>),本模块不引入新主题。</p>
<div class="tokens">
<div class="panel">
<h4>语义色</h4>
<div class="swatches">
<div class="sw"><i style="background:#1677ff"></i><b>#1677ff</b><em>主色 / 今日 / 链接</em></div>
<div class="sw"><i style="background:#00b578"></i><b>#00b578</b><em>完成 / 进度条</em></div>
<div class="sw"><i style="background:#ffc53d"></i><b>#ffc53d</b><em>部分完成 / 粒子</em></div>
<div class="sw"><i style="background:#a855f7"></i><b>#a855f7</b><em>超额完成</em></div>
<div class="sw"><i style="background:#ff8f1f"></i><b>#ff8f1f</b><em>连续天数 / 超额徽标</em></div>
<div class="sw"><i style="background:#ff3141"></i><b>#ff3141</b><em>危险操作</em></div>
<div class="sw"><i style="background:#f5f5f5"></i><b>#f5f5f5</b><em>页面底色</em></div>
<div class="sw"><i style="background:#f7fbf8"></i><b>#f7fbf8</b><em>已完成卡片底</em></div>
</div>
<div class="legend" style="margin-top:14px">
<span><i class="dot full"></i>日历·全部完成</span>
<span><i class="dot partial"></i>日历·部分完成</span>
<span><i class="dot over"></i>日历·超额</span>
</div>
</div>
<div class="panel">
<h4>布局与圆角</h4>
<div class="kv">
<span>视口</span><b>420 × 820(375~430 自适应)</b>
<span>NavBar 高</span><b>45px</b>
<span>TabBar 高</span><b>72px(含安全区)</b>
<span>页面内边距</span><b>左右 12px</b>
<span>卡片圆角</span><b>10px</b>
<span>卡片间距</span><b>10px 纵向</b>
<span>卡片阴影</span><b>0 1px 2px rgba(0,0,0,.04)</b>
<span>环形尺寸</span><b>58 × 58,环宽 6px</b>
<span>最小点按区</span><b>≥ 40 × 40</b>
</div>
</div>
<div class="panel">
<h4>字号层级</h4>
<div class="type-row"><em>18 / 700</em><span style="font-size:18px;font-weight:700">日期标题「10月22日」</span></div>
<div class="type-row"><em>17 / 600</em><span style="font-size:17px;font-weight:600">NavBar 标题「打卡」</span></div>
<div class="type-row"><em>16 / 700</em><span style="font-size:16px;font-weight:700">环形中心数字 / 弹窗标题</span></div>
<div class="type-row"><em>15 / 600</em><span style="font-size:15px;font-weight:600">打卡项名 / 清单标题</span></div>
<div class="type-row"><em>13 / 400</em><span style="font-size:13px">正文 / 日历日期数字</span></div>
<div class="type-row"><em>12 / 400</em><span style="font-size:12px;color:#666">次要信息 / 操作链接</span></div>
<div class="type-row"><em>11 / 400</em><span style="font-size:11px;color:#999">备注 / 周期范围 / 徽标</span></div>
</div>
</div>
<!-- ============ 2 主界面 ============ -->
<h2>2 主界面<span class="sub">三个子视图(SwipeTabs 左右滑动切换)</span></h2>
<div class="stage">
<!-- ---------- 2.1 打卡视图 ---------- -->
<div class="shot">
<div class="phone">
<div class="navbar">打卡</div>
<div class="body">
<div class="habit-head">
<div>
<div class="habit-date">10月22日 周四</div>
<div class="habit-summary">已完成 <strong>2</strong> / 4</div>
</div>
<div class="head-right">
<div class="mini-btn solid">+ 新建</div>
</div>
</div>
<div class="habit-list">
<!-- 未完成:每天 2 次,0/2 -->
<div class="habit-card">
<div class="ring">
<svg width="58" height="58" viewBox="0 0 58 58">
<circle cx="29" cy="29" r="26" fill="none" stroke="#ececf0" stroke-width="6"/>
<circle cx="29" cy="29" r="26" fill="none" stroke="#1677ff" stroke-width="6" stroke-linecap="round"
stroke-dasharray="163.4" stroke-dashoffset="163.4" transform="rotate(-90 29 29)"/>
</svg>
<div class="ring-center">0<em>/2</em></div>
</div>
<div class="habit-main">
<div class="habit-name">喝水</div>
<div class="habit-meta">每天 2 次<span class="streak">连续 12</span></div>
<div class="habit-range">今日</div>
</div>
<div class="habit-actions"><div class="mini-btn solid">打卡</div></div>
<div class="habit-foot"><span class="link">编辑</span><span class="link danger">归档</span></div>
</div>
<!-- 未完成:每周 3 次,1/3 = 33.3% -->
<div class="habit-card">
<div class="ring">
<svg width="58" height="58" viewBox="0 0 58 58">
<circle cx="29" cy="29" r="26" fill="none" stroke="#ececf0" stroke-width="6"/>
<circle cx="29" cy="29" r="26" fill="none" stroke="#ff8f1f" stroke-width="6" stroke-linecap="round"
stroke-dasharray="163.4" stroke-dashoffset="108.9" transform="rotate(-90 29 29)"/>
</svg>
<div class="ring-center">1<em>/3</em></div>
</div>
<div class="habit-main">
<div class="habit-name">健身</div>
<div class="habit-meta">每周 3 次<span class="streak">连续 5</span></div>
<div class="habit-range">10-19 ~ 10-25</div>
</div>
<div class="habit-actions"><div class="mini-btn solid">打卡</div></div>
<div class="habit-foot"><span class="link">撤销</span><span class="link">编辑</span><span class="link danger">归档</span></div>
</div>
<!-- 已完成:每月 12 次,12/12 闭合 -->
<div class="habit-card done">
<div class="ring">
<svg width="58" height="58" viewBox="0 0 58 58">
<circle cx="29" cy="29" r="26" fill="none" stroke="#ececf0" stroke-width="6"/>
<circle cx="29" cy="29" r="26" fill="none" stroke="#00b578" stroke-width="6" stroke-linecap="round"
stroke-dasharray="163.4" stroke-dashoffset="0" transform="rotate(-90 29 29)"/>
</svg>
<div class="ring-center">
<svg width="20" height="20" viewBox="0 0 24 24" fill="none">
<path d="M4 12.5l5 5L20 6.5" stroke="#00b578" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
</div>
</div>
<div class="habit-main">
<div class="habit-name">读书</div>
<div class="habit-meta">每月 12 次<span class="streak">连续 3</span></div>
<div class="habit-range">10-01 ~ 10-31</div>
</div>
<div class="habit-actions"><div class="mini-btn grey">再打一次</div></div>
<div class="habit-foot"><span class="link">撤销</span><span class="link">编辑</span><span class="link danger">归档</span></div>
</div>
<!-- 已完成 + 超额:每天 1 次,打了 2 次 -->
<div class="habit-card done">
<div class="ring">
<svg width="58" height="58" viewBox="0 0 58 58">
<circle cx="29" cy="29" r="26" fill="none" stroke="#ececf0" stroke-width="6"/>
<circle cx="29" cy="29" r="26" fill="none" stroke="#a855f7" stroke-width="6" stroke-linecap="round"
stroke-dasharray="163.4" stroke-dashoffset="0" transform="rotate(-90 29 29)"/>
</svg>
<div class="ring-center">
<svg width="20" height="20" viewBox="0 0 24 24" fill="none">
<path d="M4 12.5l5 5L20 6.5" stroke="#a855f7" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
</div>
<span class="ring-over">+1</span>
</div>
<div class="habit-main">
<div class="habit-name">吃药</div>
<div class="habit-meta">每天 1 次<span class="streak">连续 30</span></div>
<div class="habit-range">今日<span class="over-txt">超额 1</span></div>
</div>
<div class="habit-actions"><div class="mini-btn grey">再打一次</div></div>
<div class="habit-foot"><span class="link">撤销</span><span class="link">编辑</span><span class="link danger">归档</span></div>
</div>
</div>
</div>
<div class="tabbar">
<div class="tab">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8"><circle cx="12" cy="12" r="8.5"/><path d="M8 12.4l2.6 2.6L16.5 9.5" stroke-linecap="round" stroke-linejoin="round"/></svg>打卡</div>
<div class="tab">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8"><path d="M4 19V9m5 10V5m5 14v-7m5 7V8"/></svg>宏观</div>
<div class="tab">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8"><rect x="3" y="4" width="18" height="7" rx="2"/><rect x="3" y="13" width="18" height="7" rx="2"/><circle cx="7" cy="7.5" r="1"/><circle cx="7" cy="16.5" r="1"/></svg>监控</div>
<div class="tab on">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8"><circle cx="12" cy="8" r="3.6"/><path d="M4.5 20c1.4-3.6 4.1-5.4 7.5-5.4S18.1 16.4 19.5 20"/></svg>我</div>
</div>
</div>
<div class="caption">
<b>2.1 打卡视图 · 今天<span class="tag">默认进入</span></b>
<p>头部日期 + 完成数;未完成卡片排在前面。环形中心未达标显示 <code>done/target</code>,达标显示对勾(每天 1 次打完即刻闭合);超额在右上角显示 <code>+n</code>。点环或「打卡」按钮均可打卡,卡片底部提供撤销 / 编辑 / 归档。</p>
</div>
</div>
<!-- ---------- 2.2 日历视图 ---------- -->
<div class="shot">
<div class="phone">
<div class="navbar">打卡</div>
<div class="body">
<div class="seg" style="width:calc(100% - 28px);margin-left:14px">
<div>打卡</div><div class="on">日历</div><div>清单</div>
</div>
<div class="cal-month-bar">
<span class="cal-arrow">‹</span><span class="cal-month">2026年10月</span><span class="cal-arrow">›</span>
<span class="cal-today">回到今天</span>
</div>
<div class="seg">
<div>昨天</div><div class="on">今天</div><div>明天</div>
</div>
<div class="cal-grid">
<div class="grid7 wd"><span>一</span><span>二</span><span>三</span><span>四</span><span>五</span><span>六</span><span>日</span></div>
<div class="grid7">
<div class="day out"><span class="num">28</span></div>
<div class="day out"><span class="num">29</span></div>
<div class="day out"><span class="num">30</span></div>
<div class="day"><span class="num">1</span><i class="dot full"></i></div>
<div class="day"><span class="num">2</span><i class="dot full"></i></div>
<div class="day"><span class="num">3</span><i class="dot partial"></i></div>
<div class="day"><span class="num">4</span><i class="dot full"></i></div>
<div class="day"><span class="num">5</span></div>
<div class="day"><span class="num">6</span><i class="dot full"></i></div>
<div class="day"><span class="num">7</span><i class="dot over"></i></div>
<div class="day"><span class="num">8</span><i class="dot full"></i></div>
<div class="day"><span class="num">9</span><i class="dot partial"></i></div>
<div class="day"><span class="num">10</span><i class="dot full"></i></div>
<div class="day"><span class="num">11</span><i class="dot full"></i></div>
<div class="day"><span class="num">12</span></div>
<div class="day"><span class="num">13</span></div>
<div class="day"><span class="num">14</span><i class="dot full"></i></div>
<div class="day"><span class="num">15</span><i class="dot full"></i></div>
<div class="day"><span class="num">16</span><i class="dot partial"></i></div>
<div class="day"><span class="num">17</span><i class="dot full"></i></div>
<div class="day"><span class="num">18</span><i class="dot full"></i></div>
<div class="day"><span class="num">19</span><i class="dot full"></i></div>
<div class="day"><span class="num">20</span><i class="dot full"></i></div>
<div class="day"><span class="num">21</span><i class="dot full"></i></div>
<div class="day today sel"><span class="num">22</span><i class="dot partial"></i></div>
<div class="day future"><span class="num">23</span></div>
<div class="day future"><span class="num">24</span></div>
<div class="day future"><span class="num">25</span></div>
<div class="day future"><span class="num">26</span></div>
<div class="day future"><span class="num">27</span></div>
<div class="day future"><span class="num">28</span></div>
<div class="day future"><span class="num">29</span></div>
<div class="day future"><span class="num">30</span></div>
<div class="day future"><span class="num">31</span></div>
<div class="day out"><span class="num">1</span></div>
</div>
</div>
<div class="cal-rate">本月完成率 <strong>82%</strong></div>
<div class="cal-detail-title">今天 · 2026-10-22</div>
<div class="cal-item done">
<i class="cdot" style="background:#1677ff"></i><span class="cname">喝水</span>
<span class="cmeta">每天 2 次 · 当日 2/2</span>
<span class="mini-btn grey">已达标</span>
</div>
<div class="cal-item">
<i class="cdot" style="background:#ff8f1f"></i><span class="cname">健身</span>
<span class="cmeta">每周 3 次 · 当日 0/1</span>
<span class="mini-btn solid">打卡</span>
</div>
<div class="cal-item done">
<i class="cdot" style="background:#00b578"></i><span class="cname">读书</span>
<span class="cmeta">每月 12 次 · 当日 1/1</span>
<span class="mini-btn grey">已达标</span>
</div>
<div class="cal-item done">
<i class="cdot" style="background:#a855f7"></i><span class="cname">吃药</span>
<span class="cmeta">每天 1 次 · 当日 2/1</span>
<span class="mini-btn grey">已达标</span>
</div>
</div>
<div class="tabbar">
<div class="tab">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8"><circle cx="12" cy="12" r="8.5"/><path d="M8 12.4l2.6 2.6L16.5 9.5" stroke-linecap="round" stroke-linejoin="round"/></svg>打卡</div>
<div class="tab">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8"><path d="M4 19V9m5 10V5m5 14v-7m5 7V8"/></svg>宏观</div>
<div class="tab">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8"><rect x="3" y="4" width="18" height="7" rx="2"/><rect x="3" y="13" width="18" height="7" rx="2"/><circle cx="7" cy="7.5" r="1"/><circle cx="7" cy="16.5" r="1"/></svg>监控</div>
<div class="tab on">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8"><circle cx="12" cy="8" r="3.6"/><path d="M4.5 20c1.4-3.6 4.1-5.4 7.5-5.4S18.1 16.4 19.5 20"/></svg>我</div>
</div>
</div>
<div class="caption">
<b>2.2 日历视图 · 今天选中</b>
<p>周一起始月历 + 「昨天 / 今天 / 明天」快捷分段;日期下方状态点:绿=全部完成、黄=部分完成、紫=超额、无点=未完成或未来。今天用主色数字并选中高亮,未来日期数字弱化。下方为该日明细,可直接打卡;底部展示本月完成率。</p>
</div>
</div>
<!-- ---------- 2.3 清单视图 ---------- -->
<div class="shot">
<div class="phone">
<div class="navbar">打卡</div>
<div class="body">
<div class="seg" style="width:calc(100% - 28px);margin-left:14px">
<div>打卡</div><div>日历</div><div class="on">清单</div>
</div>
<div class="list-head">
<div class="seg"><div class="on">进行中</div><div>已归档</div></div>
<div class="mini-btn solid">+ 新建</div>
</div>
<div class="checklist-card">
<div class="cl-title-row"><span class="cl-title">出差 · 杭州</span><span class="cl-scene">出差</span></div>
<div class="progress-row">
<div class="bar"><i style="width:66.7%"></i></div><span class="count">8/12</span><span class="arrow">›</span>
</div>
<div class="cl-body">
<div class="cl-item done"><span class="box"><svg viewBox="0 0 24 24" width="14" height="14" fill="none"><path d="M4 12.5l5 5L20 6.5" stroke="#fff" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"/></svg></span><span class="cl-item-title">身份证 / 证件</span><span class="cl-del">×</span></div>
<div class="cl-item done"><span class="box"><svg viewBox="0 0 24 24" width="14" height="14" fill="none"><path d="M4 12.5l5 5L20 6.5" stroke="#fff" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"/></svg></span><span class="cl-item-title">手机充电器 + 充电宝</span><span class="cl-del">×</span></div>
<div class="cl-item done"><span class="box"><svg viewBox="0 0 24 24" width="14" height="14" fill="none"><path d="M4 12.5l5 5L20 6.5" stroke="#fff" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"/></svg></span><span class="cl-item-title">笔记本电脑 + 电源</span><span class="cl-del">×</span></div>
<div class="cl-item done"><span class="box"><svg viewBox="0 0 24 24" width="14" height="14" fill="none"><path d="M4 12.5l5 5L20 6.5" stroke="#fff" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"/></svg></span><span class="cl-item-title">耳机</span><span class="cl-del">×</span></div>
<div class="cl-item done"><span class="box"><svg viewBox="0 0 24 24" width="14" height="14" fill="none"><path d="M4 12.5l5 5L20 6.5" stroke="#fff" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"/></svg></span><span class="cl-item-title">换洗衣物</span><span class="cl-del">×</span></div>
<div class="cl-item done"><span class="box"><svg viewBox="0 0 24 24" width="14" height="14" fill="none"><path d="M4 12.5l5 5L20 6.5" stroke="#fff" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"/></svg></span><span class="cl-item-title">洗漱包</span><span class="cl-del">×</span></div>
<div class="cl-item done"><span class="box"><svg viewBox="0 0 24 24" width="14" height="14" fill="none"><path d="M4 12.5l5 5L20 6.5" stroke="#fff" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"/></svg></span><span class="cl-item-title">常用药</span><span class="cl-del">×</span></div>
<div class="cl-item done"><span class="box"><svg viewBox="0 0 24 24" width="14" height="14" fill="none"><path d="M4 12.5l5 5L20 6.5" stroke="#fff" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"/></svg></span><span class="cl-item-title">车票 / 机票 / 酒店确认</span><span class="cl-del">×</span></div>
<div class="cl-item"><span class="box"><svg viewBox="0 0 24 24" width="14" height="14" fill="none"><path d="M4 12.5l5 5L20 6.5" stroke="#fff" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"/></svg></span><span class="cl-item-title">雨伞</span><span class="cl-del">×</span></div>
<div class="cl-item"><span class="box"><svg viewBox="0 0 24 24" width="14" height="14" fill="none"><path d="M4 12.5l5 5L20 6.5" stroke="#fff" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"/></svg></span><span class="cl-item-title">工牌 / 名片</span><span class="cl-del">×</span></div>
<div class="cl-item"><span class="box"><svg viewBox="0 0 24 24" width="14" height="14" fill="none"><path d="M4 12.5l5 5L20 6.5" stroke="#fff" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"/></svg></span><span class="cl-item-title">报销单据</span><span class="cl-del">×</span></div>
<div class="cl-item"><span class="box"><svg viewBox="0 0 24 24" width="14" height="14" fill="none"><path d="M4 12.5l5 5L20 6.5" stroke="#fff" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"/></svg></span><span class="cl-item-title">现金零钱</span><span class="cl-del">×</span></div>
<div class="cl-add"><div class="inp ph">添加检查项</div><div class="mini-btn solid">添加</div></div>
<div class="cl-actions">
<span class="link">全选</span><span class="link">全不选</span><span class="link">复制</span>
<span class="link">存为模板</span><span class="link">归档</span><span class="link danger">删除</span>
</div>
</div>
</div>
<div class="checklist-card">
<div class="cl-title-row"><span class="cl-title">回老家 · 国庆</span><span class="cl-scene">回老家</span></div>
<div class="progress-row">
<div class="bar"><i style="width:0%"></i></div><span class="count">0/12</span><span class="arrow" style="transform:none">›</span>
</div>
</div>
</div>
<div class="tabbar">
<div class="tab">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8"><circle cx="12" cy="12" r="8.5"/><path d="M8 12.4l2.6 2.6L16.5 9.5" stroke-linecap="round" stroke-linejoin="round"/></svg>打卡</div>
<div class="tab">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8"><path d="M4 19V9m5 10V5m5 14v-7m5 7V8"/></svg>宏观</div>
<div class="tab">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8"><rect x="3" y="4" width="18" height="7" rx="2"/><rect x="3" y="13" width="18" height="7" rx="2"/><circle cx="7" cy="7.5" r="1"/><circle cx="7" cy="16.5" r="1"/></svg>监控</div>
<div class="tab on">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8"><circle cx="12" cy="8" r="3.6"/><path d="M4.5 20c1.4-3.6 4.1-5.4 7.5-5.4S18.1 16.4 19.5 20"/></svg>我</div>
</div>
</div>
<div class="caption">
<b>2.3 清单视图 · 展开态</b>
<p>卡片头部常显标题、场景标签、进度条与 <code>8/12</code>;点击展开逐条勾选(勾选后绿色圆底、文字加删除线并弱化)。底部可继续添加检查项,并提供全选 / 全不选 / 复制 / 存为模板 / 归档 / 删除。收起态只保留头部一行。</p>
</div>
</div>
<!-- ---------- 2.4 新建打卡项弹窗 ---------- -->
<div class="shot">
<div class="phone">
<div class="navbar">打卡</div>
<div class="body">
<div class="habit-head">
<div>
<div class="habit-date">10月22日 周四</div>
<div class="habit-summary">已完成 <strong>2</strong> / 4</div>
</div>
<div class="head-right"><div class="mini-btn solid">+ 新建</div></div>
</div>
<div class="habit-list">
<div class="habit-card">
<div class="ring">
<svg width="58" height="58" viewBox="0 0 58 58">
<circle cx="29" cy="29" r="26" fill="none" stroke="#ececf0" stroke-width="6"/>
<circle cx="29" cy="29" r="26" fill="none" stroke="#1677ff" stroke-width="6" stroke-linecap="round" stroke-dasharray="163.4" stroke-dashoffset="163.4" transform="rotate(-90 29 29)"/>
</svg><div class="ring-center">0<em>/2</em></div>
</div>
<div class="habit-main"><div class="habit-name">喝水</div><div class="habit-meta">每天 2 次</div><div class="habit-range">今日</div></div>
<div class="habit-actions"><div class="mini-btn solid">打卡</div></div>
<div class="habit-foot"><span class="link">编辑</span><span class="link danger">归档</span></div>
</div>
</div>
</div>
<div class="mask"></div>
<div class="sheet">
<div class="sheet-title">新建打卡项</div>
<div class="field"><label>名称</label><div class="inp ph">例如:喝水 / 健身 / 读书</div></div>
<div class="field"><label>周期</label>
<div class="chips"><div class="chip on">每天</div><div class="chip">每周</div><div class="chip">每月</div><div class="chip">每年</div></div>
</div>
<div class="field"><label>目标次数(周期内)</label>
<div class="stepper"><span>−</span><b>1</b><span class="act">+</span></div>
</div>
<div class="field"><label>颜色</label>
<div class="colors">
<span class="cd on" style="background:#1677ff"></span><span class="cd" style="background:#00b578"></span>
<span class="cd" style="background:#ff8f1f"></span><span class="cd" style="background:#ff3141"></span>
<span class="cd" style="background:#a855f7"></span><span class="cd" style="background:#14b8a6"></span>
<span class="cd" style="background:#f43f9e"></span>
</div>
</div>
<div class="field"><label>备注(可选)</label><div class="inp ph">补充说明</div></div>
<div class="btn-primary">保存</div>
<div class="btn-ghost">取消</div>
</div>
<div class="tabbar">
<div class="tab">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8"><circle cx="12" cy="12" r="8.5"/><path d="M8 12.4l2.6 2.6L16.5 9.5" stroke-linecap="round" stroke-linejoin="round"/></svg>打卡</div>
<div class="tab">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8"><path d="M4 19V9m5 10V5m5 14v-7m5 7V8"/></svg>宏观</div>
<div class="tab">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8"><rect x="3" y="4" width="18" height="7" rx="2"/><rect x="3" y="13" width="18" height="7" rx="2"/><circle cx="7" cy="7.5" r="1"/><circle cx="7" cy="16.5" r="1"/></svg>监控</div>
<div class="tab on">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8"><circle cx="12" cy="8" r="3.6"/><path d="M4.5 20c1.4-3.6 4.1-5.4 7.5-5.4S18.1 16.4 19.5 20"/></svg>我</div>
</div>
</div>
<div class="caption">
<b>2.4 新建 / 编辑打卡项(底部弹层)</b>
<p>名称 ≤20 字;周期四选一(每天 / 每周 / 每月 / 每年);目标次数用 Stepper,最小 1;颜色 7 色预设;备注可选。编辑态标题变为「编辑打卡项」并回填当前值。</p>
</div>
</div>
<!-- ---------- 2.5 完成特效 ---------- -->
<div class="shot">
<div class="phone">
<div class="navbar">打卡</div>
<div class="body">
<div class="habit-head">
<div><div class="habit-date">10月22日 周四</div><div class="habit-summary">已完成 <strong>3</strong> / 4</div></div>
<div class="head-right"><div class="mini-btn solid">+ 新建</div></div>
</div>
<div class="habit-list">
<div class="habit-card done" style="overflow:visible">
<div class="ring">
<!-- 特效层:光环 + 14 颗粒子(静态呈现 t=250ms 时的形态) -->
<div class="fx">
<div class="halo"></div><div class="halo halo2"></div>
<i style="background:#ffc53d;transform:translate(-50%,-50%) rotate(0deg) translateY(-34px)"></i>
<i style="background:#00b578;transform:translate(-50%,-50%) rotate(26deg) translateY(-40px)"></i>
<i style="background:#00b578;transform:translate(-50%,-50%) rotate(52deg) translateY(-34px)"></i>
<i style="background:#ffc53d;transform:translate(-50%,-50%) rotate(78deg) translateY(-46px)"></i>
<i style="background:#00b578;transform:translate(-50%,-50%) rotate(104deg) translateY(-34px)"></i>
<i style="background:#00b578;transform:translate(-50%,-50%) rotate(130deg) translateY(-40px)"></i>
<i style="background:#ffc53d;transform:translate(-50%,-50%) rotate(156deg) translateY(-34px)"></i>
<i style="background:#00b578;transform:translate(-50%,-50%) rotate(182deg) translateY(-46px)"></i>
<i style="background:#00b578;transform:translate(-50%,-50%) rotate(208deg) translateY(-34px)"></i>
<i style="background:#ffc53d;transform:translate(-50%,-50%) rotate(234deg) translateY(-40px)"></i>
<i style="background:#00b578;transform:translate(-50%,-50%) rotate(260deg) translateY(-34px)"></i>
<i style="background:#00b578;transform:translate(-50%,-50%) rotate(286deg) translateY(-46px)"></i>
<i style="background:#ffc53d;transform:translate(-50%,-50%) rotate(312deg) translateY(-34px)"></i>
<i style="background:#00b578;transform:translate(-50%,-50%) rotate(338deg) translateY(-40px)"></i>
</div>
<svg width="58" height="58" viewBox="0 0 58 58">
<circle cx="29" cy="29" r="26" fill="none" stroke="#ececf0" stroke-width="6"/>
<circle cx="29" cy="29" r="26" fill="none" stroke="#00b578" stroke-width="6" stroke-linecap="round" stroke-dasharray="163.4" stroke-dashoffset="0" transform="rotate(-90 29 29)"/>
</svg>
<div class="ring-center">
<svg width="20" height="20" viewBox="0 0 24 24" fill="none"><path d="M4 12.5l5 5L20 6.5" stroke="#00b578" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"/></svg>
</div>
</div>
<div class="habit-main"><div class="habit-name">吃药</div><div class="habit-meta">每天 1 次<span class="streak">连续 30</span></div><div class="habit-range">今日</div></div>
<div class="habit-actions"><div class="mini-btn grey">再打一次</div></div>
<div class="habit-foot"><span class="link">撤销</span><span class="link">编辑</span><span class="link danger">归档</span></div>
</div>
<div class="habit-card">
<div class="ring">
<svg width="58" height="58" viewBox="0 0 58 58">
<circle cx="29" cy="29" r="26" fill="none" stroke="#ececf0" stroke-width="6"/>
<circle cx="29" cy="29" r="26" fill="none" stroke="#ff8f1f" stroke-width="6" stroke-linecap="round" stroke-dasharray="163.4" stroke-dashoffset="108.9" transform="rotate(-90 29 29)"/>
</svg><div class="ring-center">1<em>/3</em></div>
</div>
<div class="habit-main"><div class="habit-name">健身</div><div class="habit-meta">每周 3 次</div><div class="habit-range">10-19 ~ 10-25</div></div>
<div class="habit-actions"><div class="mini-btn solid">打卡</div></div>
<div class="habit-foot"><span class="link">撤销</span><span class="link">编辑</span><span class="link danger">归档</span></div>
</div>
</div>
</div>
<div class="tabbar">
<div class="tab">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8"><circle cx="12" cy="12" r="8.5"/><path d="M8 12.4l2.6 2.6L16.5 9.5" stroke-linecap="round" stroke-linejoin="round"/></svg>打卡</div>
<div class="tab">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8"><path d="M4 19V9m5 10V5m5 14v-7m5 7V8"/></svg>宏观</div>
<div class="tab">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8"><rect x="3" y="4" width="18" height="7" rx="2"/><rect x="3" y="13" width="18" height="7" rx="2"/><circle cx="7" cy="7.5" r="1"/><circle cx="7" cy="16.5" r="1"/></svg>监控</div>
<div class="tab on">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8"><circle cx="12" cy="8" r="3.6"/><path d="M4.5 20c1.4-3.6 4.1-5.4 7.5-5.4S18.1 16.4 19.5 20"/></svg>我</div>
</div>
</div>
<div class="caption">
<b>2.5 达标瞬间 · 完成特效</b>
<p>环形闭合 + 对勾缩放入场(0.32s);同时 14 颗粒子沿圆周扩散(26~58px,交替主色与金色)+ 主色光环脉冲(0.7s),总时长 ≤900ms,<code>pointer-events:none</code> 不阻塞后续操作;并触发 18ms 轻震动。<b>特效恒开</b>(V0.2 设计变更:头部不再有开关)。</p>
</div>
</div>
<!-- ---------- 2.6 日历·明天(计划) ---------- -->
<div class="shot">
<div class="phone">
<div class="navbar">打卡</div>
<div class="body">
<div class="seg" style="width:calc(100% - 28px);margin-left:14px"><div>打卡</div><div class="on">日历</div><div>清单</div></div>
<div class="cal-month-bar"><span class="cal-arrow">‹</span><span class="cal-month">2026年10月</span><span class="cal-arrow">›</span><span class="cal-today">回到今天</span></div>
<div class="seg"><div>昨天</div><div>今天</div><div class="on">明天</div></div>
<div class="cal-grid">
<div class="grid7 wd"><span>一</span><span>二</span><span>三</span><span>四</span><span>五</span><span>六</span><span>日</span></div>
<div class="grid7">
<div class="day out"><span class="num">28</span></div><div class="day out"><span class="num">29</span></div><div class="day out"><span class="num">30</span></div>
<div class="day"><span class="num">1</span><i class="dot full"></i></div>
<div class="day"><span class="num">2</span><i class="dot full"></i></div>
<div class="day"><span class="num">3</span><i class="dot partial"></i></div>
<div class="day"><span class="num">4</span><i class="dot full"></i></div>
<div class="day"><span class="num">5</span></div>
<div class="day"><span class="num">6</span><i class="dot full"></i></div>
<div class="day"><span class="num">7</span><i class="dot over"></i></div>
<div class="day"><span class="num">8</span><i class="dot full"></i></div>
<div class="day"><span class="num">9</span><i class="dot partial"></i></div>
<div class="day"><span class="num">10</span><i class="dot full"></i></div>
<div class="day"><span class="num">11</span><i class="dot full"></i></div>
<div class="day"><span class="num">12</span></div><div class="day"><span class="num">13</span></div>
<div class="day"><span class="num">14</span><i class="dot full"></i></div>
<div class="day"><span class="num">15</span><i class="dot full"></i></div>
<div class="day"><span class="num">16</span><i class="dot partial"></i></div>
<div class="day"><span class="num">17</span><i class="dot full"></i></div>
<div class="day"><span class="num">18</span><i class="dot full"></i></div>
<div class="day"><span class="num">19</span><i class="dot full"></i></div>
<div class="day"><span class="num">20</span><i class="dot full"></i></div>
<div class="day today"><span class="num">21</span><i class="dot full"></i></div>
<div class="day"><span class="num">22</span><i class="dot partial"></i></div>
<div class="day future sel"><span class="num">23</span></div>
<div class="day future"><span class="num">24</span></div>
<div class="day future"><span class="num">25</span></div>
<div class="day future"><span class="num">26</span></div><div class="day future"><span class="num">27</span></div>
<div class="day future"><span class="num">28</span></div><div class="day future"><span class="num">29</span></div>
<div class="day future"><span class="num">30</span></div><div class="day future"><span class="num">31</span></div>
<div class="day out"><span class="num">1</span></div>
</div>
</div>
<div class="cal-rate">本月完成率 <strong>82%</strong></div>
<div class="cal-detail-title">明天 · 2026-10-23<span class="badge">计划(不可打卡)</span></div>
<div class="cal-item"><i class="cdot" style="background:#1677ff"></i><span class="cname">喝水</span><span class="cmeta">每天 2 次 · 当日 0/2</span><span class="cplan">待办</span></div>
<div class="cal-item"><i class="cdot" style="background:#ff8f1f"></i><span class="cname">健身</span><span class="cmeta">每周 3 次 · 当日 0/1</span><span class="cplan">待办</span></div>
<div class="cal-item"><i class="cdot" style="background:#00b578"></i><span class="cname">读书</span><span class="cmeta">每月 12 次 · 当日 0/1</span><span class="cplan">待办</span></div>
<div class="cal-item"><i class="cdot" style="background:#a855f7"></i><span class="cname">吃药</span><span class="cmeta">每天 1 次 · 当日 0/1</span><span class="cplan">待办</span></div>
</div>
<div class="tabbar">
<div class="tab">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8"><circle cx="12" cy="12" r="8.5"/><path d="M8 12.4l2.6 2.6L16.5 9.5" stroke-linecap="round" stroke-linejoin="round"/></svg>打卡</div>
<div class="tab">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8"><path d="M4 19V9m5 10V5m5 14v-7m5 7V8"/></svg>宏观</div>
<div class="tab">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8"><rect x="3" y="4" width="18" height="7" rx="2"/><rect x="3" y="13" width="18" height="7" rx="2"/><circle cx="7" cy="7.5" r="1"/><circle cx="7" cy="16.5" r="1"/></svg>监控</div>
<div class="tab on">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8"><circle cx="12" cy="8" r="3.6"/><path d="M4.5 20c1.4-3.6 4.1-5.4 7.5-5.4S18.1 16.4 19.5 20"/></svg>我</div>
</div>
</div>
<div class="caption">
<b>2.6 日历 · 明天(计划,只读)</b>
<p>未来日期不允许打卡:标题右侧显示「计划(不可打卡)」徽标,条目右侧只显示「待办」文案而不渲染按钮,月历上未来日期数字弱化且不打点。昨天视图则相反——可补打最近 7 天内的漏打卡。</p>
</div>
</div>
<!-- ---------- 2.7 清单完成 + 空态 ---------- -->
<div class="shot">
<div class="phone">
<div class="navbar">打卡</div>
<div class="body">
<div class="seg" style="width:calc(100% - 28px);margin-left:14px"><div>打卡</div><div>日历</div><div class="on">清单</div></div>
<div class="list-head">
<div class="seg"><div class="on">进行中</div><div>已归档</div></div>
<div class="mini-btn solid">+ 新建</div>
</div>
<div class="checklist-card done" style="position:relative;overflow:visible">
<div class="cl-title-row"><span class="cl-title">出差 · 杭州</span><span class="cl-scene">出差</span></div>
<div class="progress-row"><div class="bar"><i style="width:100%"></i></div><span class="count">12/12</span><span class="arrow">›</span></div>
<div class="fx" style="left:50%;top:34px;right:auto;bottom:auto;width:0;height:0">
<div class="halo" style="border-color:#00b578"></div>
<i style="background:#ffc53d;transform:translate(-50%,-50%) rotate(10deg) translateY(-46px)"></i>
<i style="background:#00b578;transform:translate(-50%,-50%) rotate(46deg) translateY(-56px)"></i>
<i style="background:#00b578;transform:translate(-50%,-50%) rotate(92deg) translateY(-46px)"></i>
<i style="background:#ffc53d;transform:translate(-50%,-50%) rotate(140deg) translateY(-62px)"></i>
<i style="background:#00b578;transform:translate(-50%,-50%) rotate(190deg) translateY(-46px)"></i>
<i style="background:#00b578;transform:translate(-50%,-50%) rotate(240deg) translateY(-56px)"></i>
<i style="background:#ffc53d;transform:translate(-50%,-50%) rotate(300deg) translateY(-46px)"></i>
<i style="background:#00b578;transform:translate(-50%,-50%) rotate(340deg) translateY(-62px)"></i>
</div>
</div>
<div class="checklist-card">
<div class="cl-title-row"><span class="cl-title">回老家 · 国庆</span><span class="cl-scene">回老家</span></div>
<div class="progress-row"><div class="bar"><i style="width:0%"></i></div><span class="count">0/12</span><span class="arrow" style="transform:none">›</span></div>
</div>
</div>
<div class="tabbar">
<div class="tab">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8"><circle cx="12" cy="12" r="8.5"/><path d="M8 12.4l2.6 2.6L16.5 9.5" stroke-linecap="round" stroke-linejoin="round"/></svg>打卡</div>
<div class="tab">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8"><path d="M4 19V9m5 10V5m5 14v-7m5 7V8"/></svg>宏观</div>
<div class="tab">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8"><rect x="3" y="4" width="18" height="7" rx="2"/><rect x="3" y="13" width="18" height="7" rx="2"/><circle cx="7" cy="7.5" r="1"/><circle cx="7" cy="16.5" r="1"/></svg>监控</div>
<div class="tab on">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8"><circle cx="12" cy="8" r="3.6"/><path d="M4.5 20c1.4-3.6 4.1-5.4 7.5-5.4S18.1 16.4 19.5 20"/></svg>我</div>
</div>
</div>
<div class="caption">
<b>2.7 清单全部完成(12/12)</b>
<p>进度条 100%、计数变为 <code>12/12</code>、卡片底色转 <code>#f7fbf8</code>,并播放与打卡一致的完成特效。清单完成后用户通常选择「归档」或「复制」复用。</p>
</div>
</div>
<!-- ---------- 2.8 空态与归档 ---------- -->
<div class="shot">
<div class="phone">
<div class="navbar">打卡</div>
<div class="body">
<div class="habit-head">
<div><div class="habit-date">10月22日 周四</div><div class="habit-summary">已完成 <strong>0</strong> / 0</div></div>
<div class="head-right"><div class="mini-btn solid">+ 新建</div></div>
</div>
<div class="empty">
<div class="ico">○</div>
<p>还没有打卡项</p>
<div style="display:inline-block" class="mini-btn solid">创建第一个打卡项</div>
</div>
</div>
<div class="tabbar">
<div class="tab">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8"><circle cx="12" cy="12" r="8.5"/><path d="M8 12.4l2.6 2.6L16.5 9.5" stroke-linecap="round" stroke-linejoin="round"/></svg>打卡</div>
<div class="tab">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8"><path d="M4 19V9m5 10V5m5 14v-7m5 7V8"/></svg>宏观</div>
<div class="tab">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8"><rect x="3" y="4" width="18" height="7" rx="2"/><rect x="3" y="13" width="18" height="7" rx="2"/><circle cx="7" cy="7.5" r="1"/><circle cx="7" cy="16.5" r="1"/></svg>监控</div>
<div class="tab on">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8"><circle cx="12" cy="8" r="3.6"/><path d="M4.5 20c1.4-3.6 4.1-5.4 7.5-5.4S18.1 16.4 19.5 20"/></svg>我</div>
</div>
</div>
<div class="caption">
<b>2.8 空态</b>
<p>三个视图均具备加载中 / 空态 / 错误(带重试)三态。空态给出明确引导按钮。<b>V0.2 设计变更</b>:打卡页头部不再有「特效开 / 特效关」开关,完成特效与触感反馈<b>恒开</b>,没有任何关闭入口。</p>
</div>
</div>
<!-- ---------- 2.9 宏观(原首页报警/新闻并入) ---------- -->
<div class="shot">
<div class="phone">
<div class="navbar">宏观</div>
<div class="body">
<div class="mtabs">
<div>油价</div><div>经济数据</div><div>策略研究</div><div>经济文章</div>
<div>报警<span class="badge-dot">3</span></div><div class="on">新闻</div>
</div>
<div class="feed">
<div class="feed-card">
<div class="feed-title">央行开展 5000 亿元 MLF 操作,中标利率持平</div>
<div class="feed-sub">本月 MLF 净投放 2000 亿元,市场流动性保持合理充裕…</div>
<div class="feed-meta"><span>妙想财经</span><span>2026-10-22</span><span class="rk">#1</span></div>
</div>
<div class="feed-card">
<div class="feed-title">9 月社零同比增长 3.2%,服务消费延续回暖</div>
<div class="feed-sub">餐饮收入增速回升至 5.1%,可选消费分化明显…</div>
<div class="feed-meta"><span>妙想财经</span><span>2026-10-21</span><span class="rk">#2</span></div>
</div>
<div class="feed-card">
<div class="feed-title">美联储会议纪要偏鹰,美元指数小幅走强</div>
<div class="feed-sub">点阵图显示年内或仍有一次降息空间,但分歧加大…</div>
<div class="feed-meta"><span>妙想财经</span><span>2026-10-21</span><span class="rk">#3</span></div>
</div>
<div class="feed-card">
<div class="feed-title">OPEC+ 维持减产节奏,国际油价日内涨逾 1%</div>
<div class="feed-sub">布伦特原油重回 78 美元上方,化工链成本端承压…</div>
<div class="feed-meta"><span>妙想财经</span><span>2026-10-20</span><span class="rk">#4</span></div>
</div>
</div>
</div>
<div class="tabbar">
<div class="tab">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8"><circle cx="12" cy="12" r="8.5"/><path d="M8 12.4l2.6 2.6L16.5 9.5" stroke-linecap="round" stroke-linejoin="round"/></svg>打卡</div>
<div class="tab on">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8"><path d="M4 19V9m5 10V5m5 14v-7m5 7V8"/></svg>宏观</div>
<div class="tab">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8"><rect x="3" y="4" width="18" height="7" rx="2"/><rect x="3" y="13" width="18" height="7" rx="2"/><circle cx="7" cy="7.5" r="1"/><circle cx="7" cy="16.5" r="1"/></svg>监控</div>
<div class="tab">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8"><circle cx="12" cy="8" r="3.6"/><path d="M4.5 20c1.4-3.6 4.1-5.4 7.5-5.4S18.1 16.4 19.5 20"/></svg>我</div>
</div>
</div>
<div class="caption">
<b>2.9 宏观 · 原「首页」报警/新闻并入<span class="tag">设计变更</span></b>
<p>「首页」Tab 取消,报警与新闻成为宏观的第 5、6 个子 Tab(<code>/macro/alerts</code>、<code>/macro/news</code>),共 6 个子 Tab 可横向滚动,当前项底部有 3px 主色指示条;报警数量以红色角标挂在子 Tab 上。新闻详情改为 <code>/macro/news/:id</code>,底部「宏观」保持高亮。</p>
</div>
</div>
<!-- ---------- 2.10 我的 · OTA(开发版本 关 = 默认) ---------- -->
<div class="shot">
<div class="phone">
<div class="navbar">我</div>
<div class="body">
<div class="me-head">
<div class="me-avatar">C</div>
<div class="me-name">Cheney</div>
<div class="me-mail">qian.huang@sunyard.com</div>
</div>
<div class="me-group">
<div class="g-head">前端升级 (OTA)</div>
<div class="me-row"><span>当前版本</span><span class="val">v0.2.0 · 20261022.1530</span></div>
<div class="me-row"><span>运行资源</span><span class="val">内置</span></div>
<div class="me-row">
<div>
<div>开发版本</div>
<div class="hint">按版本号判新:仅当服务端版本号更高时才提示更新</div>
</div>
<div class="sw"></div>
</div>
<div class="me-actions">
<div class="mini-btn solid">检查更新</div>
<div class="mini-btn ghost">立即升级</div>
</div>
</div>
</div>
<div class="tabbar">
<div class="tab">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8"><circle cx="12" cy="12" r="8.5"/><path d="M8 12.4l2.6 2.6L16.5 9.5" stroke-linecap="round" stroke-linejoin="round"/></svg>打卡</div>
<div class="tab">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8"><path d="M4 19V9m5 10V5m5 14v-7m5 7V8"/></svg>宏观</div>
<div class="tab">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8"><rect x="3" y="4" width="18" height="7" rx="2"/><rect x="3" y="13" width="18" height="7" rx="2"/><circle cx="7" cy="7.5" r="1"/><circle cx="7" cy="16.5" r="1"/></svg>监控</div>
<div class="tab on">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8"><circle cx="12" cy="8" r="3.6"/><path d="M4.5 20c1.4-3.6 4.1-5.4 7.5-5.4S18.1 16.4 19.5 20"/></svg>我</div>
</div>
</div>
<div class="caption">
<b>2.10 我的 · OTA「开发版本」关闭(默认)<span class="tag">新功能</span></b>
<p>开关默认关闭 = <b>按版本号判新</b>:取服务端 <code>latest.json</code> 的 <code>version</code>(如 <code>0.2.0-ota</code>),与当前运行版本按数字字段逐段比较,<b>更高</b>才提示更新。用于生产,避免旧分支被重建(sequence 变大但版本更小)把设备拉回旧版。开关值保存在设备端 <code>localStorage.iboard_ota_dev_mode</code>。</p>
</div>
</div>
<!-- ---------- 2.11 我的 · OTA(开发版本 开) ---------- -->
<div class="shot">
<div class="phone">
<div class="navbar">我</div>
<div class="body">
<div class="me-head">
<div class="me-avatar">C</div>
<div class="me-name">Cheney</div>
<div class="me-mail">qian.huang@sunyard.com</div>
</div>
<div class="me-group">
<div class="g-head">前端升级 (OTA)</div>
<div class="me-row"><span>当前版本</span><span class="val">v0.2.0 · 20261022.1530</span></div>
<div class="me-row"><span>运行资源</span><span class="val">内置</span></div>
<div class="me-row">
<div>
<div>开发版本</div>
<div class="hint">按 build sequence 判新:每次构建都算新版本,便于开发联调</div>
</div>
<div class="sw on"></div>
</div>
<div class="me-row"><span>新版本</span><span class="val">v0.2.0-ota (288 KB)</span></div>
<div class="me-actions">
<div class="mini-btn solid">立即升级</div>
<div class="mini-btn ghost">回滚</div>
</div>
</div>
</div>
<div class="tabbar">
<div class="tab">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8"><circle cx="12" cy="12" r="8.5"/><path d="M8 12.4l2.6 2.6L16.5 9.5" stroke-linecap="round" stroke-linejoin="round"/></svg>打卡</div>
<div class="tab">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8"><path d="M4 19V9m5 10V5m5 14v-7m5 7V8"/></svg>宏观</div>
<div class="tab">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8"><rect x="3" y="4" width="18" height="7" rx="2"/><rect x="3" y="13" width="18" height="7" rx="2"/><circle cx="7" cy="7.5" r="1"/><circle cx="7" cy="16.5" r="1"/></svg>监控</div>
<div class="tab on">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8"><circle cx="12" cy="8" r="3.6"/><path d="M4.5 20c1.4-3.6 4.1-5.4 7.5-5.4S18.1 16.4 19.5 20"/></svg>我</div>
</div>
</div>
<div class="caption">
<b>2.11 我的 · OTA「开发版本」打开</b>
<p>开关打开 = <b>按 build sequence 判新</b>:走 hotswap 插件的 <code>checkUpdate()</code>,只要服务端 <code>sequence</code> 比本地大就算新版本——每次 CI 构建 sequence 都会变(默认取时间戳),所以任何一次提交都能推到设备上,适合开发联调。切换开关会清空上一次的检查结果,避免上一种口径的结论误导。</p>
</div>
</div>
</div>
<!-- ============ 3 组件状态 ============ -->
<h2>3 组件状态定义<span class="sub">Component States</span></h2>
<h3>3.1 环形进度(尺寸 58,环宽 6,r=26,周长 163.4)</h3>
<table>
<tr><th>状态</th><th>条件</th><th>环形</th><th>中心</th><th>卡片底</th><th>按钮</th></tr>
<tr><td>未开始</td><td><code>done = 0</code></td><td>仅灰底环</td><td><code>0/2</code></td><td>白</td><td>打卡(主色)</td></tr>
<tr><td>进行中</td><td><code>0 &lt; done &lt; target</code></td><td>按 <code>done/target</code> 填充(45°…330°)</td><td><code>1/3</code></td><td>白</td><td>打卡(主色)</td></tr>
<tr><td>已达标</td><td><code>done = target</code></td><td>闭合整圈</td><td>对勾(主色)</td><td><code>#f7fbf8</code></td><td>再打一次(灰)</td></tr>
<tr><td>超额</td><td><code>done &gt; target</code></td><td>闭合 + 右上角 <code>+n</code> 橙标</td><td>对勾</td><td><code>#f7fbf8</code></td><td>再打一次(灰)</td></tr>
<tr><td>每日 1 次达标</td><td><code>target = 1</code> 且已打卡</td><td><b>直接整圈闭合</b>,不做渐增生长期</td><td>对勾</td><td><code>#f7fbf8</code></td><td>再打一次</td></tr>
</table>
<p class="note">环形进度 = 周期完成度(<code>done / n</code>);日历日标记 = 当日份额(<code>当日次数 / 日均期望</code>),两者口径不同,互不影响。</p>
<h3>3.2 日历日期</h3>
<table>
<tr><th>状态</th><th>判定</th><th>视觉</th></tr>
<tr><td>全部完成 <code>full</code></td><td>所有打卡项当日次数 ≥ 日均期望,且无超额</td><td>绿色圆点</td></tr>
<tr><td>超额 <code>over</code></td><td>全部达标且存在任一打卡项当日次数 &gt; 期望</td><td>紫色圆点</td></tr>
<tr><td>部分完成 <code>partial</code></td><td>有完成但未全部完成</td><td>黄色圆点</td></tr>
<tr><td>无 <code>empty</code></td><td>无记录 / 当天无打卡项 / 未来日期</td><td>不显示圆点</td></tr>
<tr><td>今天</td><td><code>date = today</code></td><td>数字主色加粗</td></tr>
<tr><td>选中</td><td>用户点击</td><td>主色实心底,数字与圆点转白</td></tr>
<tr><td>非本月 / 未来</td><td>月份不符 / <code>date &gt; today</code></td><td>数字弱化(#c8c8c8 / #b0b0b0)</td></tr>
</table>
<h3>3.3 清单检查项</h3>
<table>
<tr><th>状态</th><th>勾选框</th><th>文字</th><th>触发</th></tr>
<tr><td>未完成</td><td>1.5px 灰边圆圈,对勾透明</td><td>#333</td><td>点击圆圈或文字切换</td></tr>
<tr><td>已完成</td><td>绿底绿边,白色对勾显现(0.2s)</td><td>#a0a0a0 + 删除线</td><td>同上,写 <code>doneAt</code></td></tr>
<tr><td>整单完成</td><td>—</td><td>进度条 100%、卡片转绿</td><td>最后一项勾选时播放特效</td></tr>
</table>
<!-- ============ 4 交互与动效 ============ -->
<h2>4 交互与动效<span class="sub">Motion</span></h2>
<table>
<tr><th>动效</th><th>时长</th><th>曲线 / 实现</th><th>说明</th></tr>
<tr><td>环形增长</td><td>450ms</td><td><code>cubic-bezier(.4,0,.2,1)</code>,<code>stroke-dashoffset</code> 过渡</td><td>12 点方向顺时针增长</td></tr>
<tr><td>对勾入场</td><td>320ms</td><td><code>scale(.4) → 1</code> + 透明度</td><td>达标瞬间出现</td></tr>
<tr><td>完成粒子</td><td>≤900ms</td><td>14 颗粒子按角度 <code>--a</code>、距离 <code>--d</code> 扩散,交错 40ms 延迟</td><td>不阻塞点击</td></tr>
<tr><td>光环脉冲</td><td>700ms</td><td>主色圆环放大淡出</td><td>与粒子同时开始</td></tr>
<tr><td>触感反馈</td><td>18ms</td><td><code>navigator.vibrate(18)</code></td><td>WebView 不支持则静默跳过</td></tr>
<tr><td>进度条填充</td><td>400ms</td><td><code>width</code> 过渡</td><td>清单勾选后更新</td></tr>
<tr><td>视图切换</td><td>—</td><td>SwipeTabs 跟手横滑</td><td>与既有模块一致</td></tr>
</table>
<h3>4.1 打卡时序</h3>
<div class="flow">用户点环 / 点「打卡」
└─ 前端乐观更新:done+1,重算 percent / completed / over,环形立即增长
└─ POST /api/checkin/habits/:id/check
├─ 200 → 用服务端 item 覆盖本地;若 completed → 触发特效 + 震动
├─ 400 → 回滚 + Toast(未来日期 / 超出 7 天补打窗口)
├─ 409 → 视为已打卡,刷新一次(幂等)
└─ 网络失败 → 回滚 + Toast「打卡失败」</div>
<!-- ============ 5 适配与可达性 ============ -->
<h2>5 适配与可达性</h2>
<table>
<tr><th>项</th><th>约定</th></tr>
<tr><td>视口</td><td>设计稿 420×820;375~430px 内元素不换行、不裁切;底部 TabBar 4 项每项 ≥ 44px 可点宽度</td></tr>
<tr><td>安全区</td><td>TabBar 使用 antd-mobile <code>safeArea</code>,页面底部留出 <code>tabbar-h + safe-bottom</code></td></tr>
<tr><td>动效</td><td><b>特效恒开</b>(V0.2 设计变更,无开关):单次 ≤900ms、<code>pointer-events:none</code>,不阻塞连续打卡;不再响应 <code>prefers-reduced-motion</code></td></tr>
<tr><td>大字号</td><td>卡片采用 grid 自适应列宽,文字超长换行不截断;名称限制 20 字、检查项 40 字</td></tr>
<tr><td>深色模式</td><td>V0.2 沿用现有浅色主题,暂不适配</td></tr>
</table>
<p class="note" style="margin-top:26px">本文档为静态设计稿,不含脚本逻辑;实现对应 <code>src/pages/checkin/*.jsx</code>、<code>src/components/RingProgress.jsx</code> 与样式段(<code>src/styles/globals.css</code>)。接口与数据口径见《设计文档.md》与《数据库设计文档.md》。</p>
</div>
</body>
</html>