Files
mama-box/index.html

1771 lines
127 KiB
HTML
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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,viewport-fit=cover">
<meta name="theme-color" content="#F4593C">
<meta name="apple-mobile-web-app-capable" content="yes">
<title>妈妈盒子 · 孕育陪伴</title>
<!-- V:26090723 缓存版本标记:改 css/js 时同步 bump 该值,并给下方资源引用加 ?v= 以强制 webview 刷新子资源 -->
<link rel="stylesheet" href="mama-box.css?v=26090723">
<script src="https://res.wx.qq.com/open/js/jweixin-1.6.0.js"></script>
</head>
<body>
<a class="skip" href="#screens">跳到内容</a>
<div class="phone" id="phone">
<nav class="tabbar" id="tabbar">
<button class="tab is-active" data-tab="home">
<span class="t-ico ic-home"></span><span class="t-txt">首页</span>
</button>
<button class="tab" data-tab="archive">
<span class="t-ico ic-archive"></span><span class="t-txt">档案</span>
</button>
<button class="tab tab-fab" data-sheet="open">
<span class="fab"><img src="assets/img/69818ea975da.png" alt="" aria-hidden="true"></span><span class="t-txt">记一下</span>
</button>
<button class="tab" data-tab="letter">
<!-- ⚠️ 「宝宝来信」。上一批按需求整条线改口叫过「宝宝消息」,本批用户明确要求
**退回「来信」**,全线七处一起退(底 tab、首页卡、详情页顶栏、珍贵时刻卡
标题 + 出处、搜索占位符、搜索 chip)。
退回之后那条老矛盾会跟着回来:列表本身是聊天气泡(头像 + 气泡 + 时间戳),
tab 上写「来信」、点进去是一屏对话,字面上对不齐。这是用户看过两版之后
**选定**的说法,不是漏改 —— 想再改回「消息」之前先去问,别自己动。
⚠️ 这枚标签**不跟小名走**(syncBabyWords 有意不碰它):底 tab 是全局导航,
跟着改会变成「小汤圆来信」,四个 tab 里只有这一个宽度会变;
而且导航栏本来就该是固定坐标,不随用户数据浮动。 -->
<span class="t-ico ic-letter"></span><span class="t-badge" id="tabBadge"></span><span class="t-txt">宝宝来信</span>
</button>
<button class="tab" data-tab="me">
<span class="t-ico ic-me"></span><span class="t-txt">我的</span>
</button></nav>
<main class="screens" id="screens">
<section class="screen is-active" id="screen-home" data-screen="home">
<div class="home-hero">
<div class="hero-blobs"><span></span><span></span><span></span></div>
<div class="petals" id="petals"></div>
<!-- 宝宝是一张 PNG,嘴没法单独动。改成从嘴边缓缓冒气泡(「咕噜咕噜」),
浮动动画从 img 挪到这层外壳上,泡泡才会跟着一起浮。 -->
<div class="hero-baby" aria-hidden="true">
<img class="hero-art" src="assets/img/b00c707f7b1f.png" alt="">
<span class="hb-bub b1"></span><span class="hb-bub b2"></span><span class="hb-bub b3"></span>
</div>
<!-- 右上角原来有个消息圆钮,点开只弹「暂无新消息」——一个永远没有内容的入口,
还顶着一颗红点天天提示。整个删掉;这一排现在只剩左边的品牌字。
.hero-top 本身留着:它的 height:32 + margin-top:10 撑着下面整列的起点
(现在这一排里除了品牌字还并排放着 slogan,见下面那段注释)。 -->
<!-- slogan 从「品牌字底下另起两行」挪到了「品牌字右边、同一行」。
⚠️ 它现在是 .hero-top 的**孩子**,而 .hero-top 是四个页面共用的
display:flex + justify-content:space-between —— 第二个孩子会被
space-between 甩到最右边去。这里**不动父级的对齐方式**
(动了会连着挪「我的 / 来信 / 档案」三页右上角的按钮组),
改用 .hero-slogan{margin-right:auto} 把富余空间吃掉,让它贴着品牌字排。
这跟 .hero-actions{margin-left:auto} 是同一个路子,全站一致。
⚠️ 一行 10 个字放得下,是**先把气泡挪下去**换来的,不是本来就放得下。
320 宽(本版支持的最窄屏,CSS 里 max-width:359px 那条断点为证)横向账:
左内边距 16 + 品牌字 4×21 + 4×.5 = 86 → 品牌字排到 x=102
slogan 起点 102 + margin-left 9 → x=111
slogan 宽 10×13 + 9×.3 = 132.7 → 排到 x≈243.7
橙区右内边距 15,可用到 x=305 → 还余 61px ✓
真正卡住的是纵向:slogan 的行盒是 y 18..34(13px 字塞在 16px 行高里,
居中于 y10..42 那一排),而气泡原来 top:26 —— 圆顶正好戳在字底下。
量过 hero-art.png 的 alpha 通道:这幅画是**实心整圆、边缘是硬的**,
α≥8 和 α≥220 量出来的左缘只差 1px,没有可以压上去的透明薄边。
所以只能把气泡整体下移到 top:40,给字底留 6px。
气泡一动,橙区那笔竖列账要连着重算 —— 已经算过,见 style.css 的 .home-hero。
改字号 / 加字 / 挪气泡之前,把上面两笔账都重算一遍。 -->
<div class="hero-top">
<div class="hero-brand">妈妈盒子</div>
<p class="hero-slogan">每次记录都是一生记忆</p>
</div>
<div class="hero-main">
<div class="week-ring">
<svg viewBox="0 0 120 120" class="ring-svg">
<circle class="ring-bg" cx="60" cy="60" r="56"/>
<circle class="ring-fg" id="heroRing" cx="60" cy="60" r="56" data-ring="0.6"/>
</svg>
<div class="week-num"><b id="heroWeek">24</b><span>孕周</span></div>
</div>
<div class="week-plus" id="heroPlus">+3天</div>
</div>
<!-- 原来是一行干巴巴的白字浮在橙底上。改成一张毛玻璃小卡:
上行小名,下行天数(数字放大当主角)。卡片是 inline-flex,
宽度由内容撑开,两行排布让它窄到不会伸进右边气泡宝宝底下。 -->
<div class="hero-days">
<div class="hd-card">
<span class="hd-who">
<svg class="hd-heart" viewBox="0 0 24 24" width="12" height="12" aria-hidden="true">
<path d="M12 20.4S3.6 14.9 3.6 9.3a4.6 4.6 0 0 1 8.4-2.6 4.6 4.6 0 0 1 8.4 2.6c0 5.6-8.4 11.1-8.4 11.1z" fill="rgba(255,255,255,.92)"/>
</svg>
<b id="heroWho">小汤圆</b><span class="hd-where">在妈妈肚子里</span>
</span>
<!-- ⚠️ 这句话是**拆成两行**放的,别合成一行。
.hero-days 是 max-width:196px,.hd-tail 又是 white-space:nowrap,
「在妈妈肚子里已经生活了 188 天」整句 ≈ 182px + 卡内边 26px = 208px,
超出 196px 会溢出到插画上。
拆开后:第一行 ♥+小名+「在妈妈肚子里」≈ 163px,
第二行「已经生活了 188 天」≈ 133px,两行都放得下。 -->
<span class="hd-tail">已经生活了 <b id="heroDays">171</b> 天</span>
</div>
</div>
</div>
<!-- 宝宝一有新回信,pushLatestToHome() 会把这张卡换成那条回信,
并加上 .has-new(信封右上角冒一个小红点)。
没有未读、也没有新回信时,同一个函数会给它挂 .hide 整张收起来。 -->
<button class="letter-card ripple" id="homeLetter" data-goto="letter-detail">
<span class="lc-envwrap"><img class="lc-env" src="assets/img/02cf420f0d07.png" alt="" aria-hidden="true"></span>
<div class="lc-body">
<!-- ⚠️ 这句只是**没填小名时的兜底**,屏幕上实际由 pushLatestToHome()
改写成「小汤圆来信」/「小汤圆回信啦」。改这句必须连着改那边,
否则会出现「HTML 里写着一套、屏幕上是另一套」。
⚠️ 上一批这里短暂叫过「宝宝的新消息」,本批随整条线退回「来信」。 -->
<div class="lc-title" id="lcTitle">宝宝来信</div>
<div class="lc-desc" id="lcDesc">妈妈,今天我在你肚子里翻了个身……</div>
<div class="lc-link"><span id="lcLink">读读宝宝的话</span> <i>›</i></div>
</div>
</button>
<!-- id 是给 pushLatestToHome() 用的:来信卡收起来时它挂 .no-letter,
把下面第一块(.tool-dock)的上间距补回来。
── 这一版首页正文换了骨架 ──
上一版是同一套「竖色条标题 + 灰色说明 + 一块内容」原样重复四遍
(工具 / AI 小结 / 脚印 / 指南),四处的字号、间距、列宽全一样,
只有色条颜色不同,往下扫就是「条-块-条-块-条-块-条-块」。
现在那套框架整个拆了:.sec-title / .sec-sub 在首页一处不留,
抬头改为长在各模块**内部**,四个模块收成三块,
靠三种完全不同的容器语言自己分段 ——
.tool-dock 通栏出血的浅米托盘
.today-line 没有容器,只有一条从早贯到晚的竖线
.shelf 横向可滑的书架卡带
页面也因此有了两种宽度节奏:文字型内容仍在 26px 栏内,
托盘和卡带出血到屏幕边缘。 -->
<div class="home-body" id="homeBody">
<!-- 模块一 · 工具托盘。
抬头进了托盘里面:标题独占一行,右端「所有工具」做成白底细边胶囊;
说明单独做成一枚浅色 chip,不再是标题底下一行贴边的灰字。
通往「记一下」弹层的门这一屏仍是两处,是故意的,不是漏删:
这颗胶囊是**导航**位(扫抬头找"看全部"时手先落在这儿),
宫格右下角那张 .mini-card.more 是**内容**位(顺带把第三列剩下的
44px 填平,而且是个够手指点的大靶子)。两个都指向 data-sheet="open",
删掉任意一个另一个都还走得通。 -->
<section class="tool-dock">
<!-- 大字水印。aria-hidden:读屏念到这块该直接进标题,
不该先听见两声「妈妈」。样式和理由都在 css/style.css 的 .wm 上。 -->
<span class="wm" aria-hidden="true">妈妈</span>
<div class="dock-head">
<h2 class="dock-title" id="dockTitle">宝宝的孕育工具</h2>
<button class="dock-all" data-sheet="open">所有工具 ›</button>
</div>
<p class="dock-note"><svg viewBox="0 0 24 24" width="11" height="11" aria-hidden="true"><path d="M11 2.6l1.9 5.2 5.2 1.9-5.2 1.9L11 16.8l-1.9-5.2L3.9 9.7l5.2-1.9L11 2.6z" fill="currentColor"/><path d="M18.4 14.6l.8 2.1 2.2.8-2.2.8-.8 2.1-.8-2.1-2.2-.8 2.2-.8.8-2.1z" fill="currentColor" opacity=".6"/></svg>妈妈每记录一次,宝宝就多一段成长记忆</p>
<!-- 五张工具卡 + 一个出口。宫格本身这一版没动:
不等宽三列 + 竖向堆叠本来就是这一屏最不传统的部分,
而且两列高度是硬凑齐的(见 style.css 里 .mini-card.curve 上面那段),
动它得整列重算,收益也不在这儿。 -->
动它得整列重算,收益也不在这儿。 -->
<div class="bento">
<div class="bento-col">
<button class="mini-card kick ripple" data-mini="kick">
<div class="mc-head">
<div class="mc-title">数胎动</div>
<div class="mc-sub">今天 <b id="kickTodayHome">3</b> 次</div>
</div>
<span class="mc-btn">记录</span>
<img class="mc-art" src="assets/img/7980160b80b0.png" alt="" aria-hidden="true">
</button>
<!-- 第五个工具。原来是「胎心记录」,本批换成「胎儿估重」——
后者是真正部署好的站点(beian1.douba.top),点了能用;
而胎心记录在这张卡上一直只是个弹 toast 的占位入口。
⚠️ 胎心记录**没有被删掉**:它还在「记一下」弹层里(快捷区 q3 +
全部工具宫格),档案页那两条「来自「胎心记录」」的历史记录也照旧。
这里换掉的只是首页宫格的一个坑位。
⚠️ 类名、结构、三个尺寸全部照搬原来那张卡:外层仍是 .mini-card
(.heart 只是改名成 .fweight,两条 CSS 跟着改),内部仍是
「名字 + 状态两行叠着 + 右下角插画」。所以左列 158 + 10 + 64 = 232
这笔账一个数都没动,右列(88 + 10 + 80 + 10 + 44)不用跟着补。
图标换成了内联 SVG 的表盘,理由见 patch 脚本里那段注释。 -->
<button class="mini-card fweight ripple" data-mini="fweight">
<div class="mc-title sm">胎儿估重</div>
<div class="mc-sub">去测算</div>
<img class="mc-art" src="data:image/svg+xml,%3Csvg%20xmlns%3D'http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg'%20viewBox%3D'0%200%2048%2048'%3E%3Cdefs%3E%3ClinearGradient%20id%3D'fwg'%20x1%3D'0'%20y1%3D'0'%20x2%3D'0'%20y2%3D'1'%3E%3Cstop%20offset%3D'0'%20stop-color%3D'%23AEC3EA'%2F%3E%3Cstop%20offset%3D'1'%20stop-color%3D'%238AA3D6'%2F%3E%3C%2FlinearGradient%3E%3C%2Fdefs%3E%3Ccircle%20cx%3D'24'%20cy%3D'24'%20r%3D'18'%20fill%3D'url(%23fwg)'%2F%3E%3Ccircle%20cx%3D'24'%20cy%3D'24'%20r%3D'13.2'%20fill%3D'%23F8FBFF'%2F%3E%3Cpath%20d%3D'M24%2024L31.6%2016.6'%20stroke%3D'%23F2876E'%20stroke-width%3D'3.2'%20stroke-linecap%3D'round'%20fill%3D'none'%2F%3E%3Ccircle%20cx%3D'24'%20cy%3D'24'%20r%3D'2.6'%20fill%3D'%235E78B4'%2F%3E%3C%2Fsvg%3E" alt="" aria-hidden="true">
</button>
</div>
<button class="mini-card weight ripple" data-mini="weight">
<img class="mc-art big" src="assets/img/b2066fd752bf.png" alt="" aria-hidden="true">
<div class="mc-title">孕期体重</div>
<div class="mc-big">58.2 <small>kg</small></div>
</button>
<div class="bento-col">
<button class="mini-card cal ripple" data-mini="cal">
<div class="mc-title sm">孕检日历</div>
<div class="mc-sub" id="calNext">—</div>
<img class="mc-art" src="assets/img/2d23b079caca.png" alt="" aria-hidden="true">
</button>
<!-- 生长曲线原来是 flex:1,把这一列剩下的高度全吃了(约 134px),
而它的内容和上面的孕检日历一模一样多。压到 80px,
省下的 54px 给下面那张「更多工具」。 -->
<button class="mini-card curve ripple" data-mini="curve">
<div class="mc-title sm">生长曲线</div>
<div class="mc-sub" data-wk="cn">—</div>
<img class="mc-art" src="assets/img/72f41378ef8e.png" alt="" aria-hidden="true">
</button>
<!-- 通往「记一下」弹层里那十几个工具。底部那颗 FAB 开的也是它。
故意不做成糖果色卡:它不是第六个工具,是个出口,
灰米底 + 一行小字,和上面五张分得开。 -->
<button class="mini-card more ripple" data-sheet="open">
<span class="mc-more-t">更多工具</span><i class="chev">›</i>
</button>
</div>
</div>
</section>
</section>
<!-- 模块二 · 今天的动线。
上一版这里是两个各自独立的模块:「AI今日小结」一张渐变卡,
下面「今日成长脚印」一张白卡装的时间轴。可 AI 那句话总结的
就是脚印里这四条记录,中间隔着一整套抬头分开摆,两边都在自说自话。
现在合成一条线:起点是「今天」,中间四条记录,
末尾挂 AI 那句话当这一天的落款。
时间轴外面那张白卡也撤了 —— 结构改由 .today-line::before 那条
贯穿到底的竖线撑起来,不再靠一个方框圈出来,
这样它和上面的托盘、下面的卡带才真的是三种容器语言。
⚠️ .steps 仍是整卡可点的 role="button",里面**不能**再出现 <button>。
所以「查看全部 ›」挂在 .tl-head 上、AI 气泡是 .steps 的兄弟节点,
两者都在 .steps 外面。 -->
<section class="today-line">
<div class="tl-head">
<span class="tl-h-when">今天</span>
<span class="tl-h-dot"></span>
<h2 class="tl-h-title" id="tlTitle">宝宝的今日脚印</h2>
<button class="tl-h-all" data-goto="archive">查看全部 ›</button>
</div>
<!-- 这句是首页 slogan 挪走之后接手的那一句(用户点名要放这儿)。
⚠️ 全站现在只有这一处写着「把每次记录变成孩子一生的记忆」,
顶上 slogan 已经换成「每次记录都是一生记忆」了,别再抄回去 ——
两处贴着同一句话,等于把这句说废了。 -->
<p class="tl-h-note">把每次记录变成孩子一生的记忆</p>
<!-- 整张卡都能点。上面那颗「查看全部 ›」正好当成「这张能点」的提示
(整卡可点最大的毛病就是看不出来能点)。 -->
<!-- ⚠️ aria-label 里也有小名,所以它跟 #tlTitle 一起交给 syncBabyWords()
刷。标题跟着小名走、读屏却一直念旧名字,是最难被发现的那种不同步 ——
眼睛看不见它。 -->
<div class="steps ripple" id="tlSteps" data-goto="archive" role="button" tabindex="0"
aria-label="宝宝的今日脚印,查看完整档案">
<div class="step"><span class="st-time">09:10</span><span class="st-dot"></span><span class="st-txt">胎动 <b>12</b> 次</span><span class="st-from">来源 · 数胎动</span></div>
<div class="step"><span class="st-time">13:00</span><span class="st-dot"></span><span class="st-txt">体重 <b>58.2kg</b></span><span class="st-from">来源 · 孕期体重</span></div>
<div class="step"><span class="st-time">16:30</span><span class="st-dot"></span><span class="st-txt">产检提醒</span><span class="st-from">来源 · 孕检日历</span></div>
<div class="step"><span class="st-time">20:15</span><span class="st-dot"></span><span class="st-txt">睡前散步 <b>20</b> 分钟</span><span class="st-from">来源 · 运动记录</span></div>
</div>
<!-- 这一天的收尾节点。三个直接子元素刻意和 .step 一样排
(42px 的左格 + 圆点 + 剩下的),点才落得进同一条竖线里。
模块名「AI今日小结」降级成气泡里的一枚小标签,
说明和「往期小结 ›」并成气泡底下的一行落款 ——
320px 视口下这三段挤在一行是塞不下的,所以标签得进气泡。 -->
<button class="ai-bubble ripple" data-open="detailAI">
<span class="ab-badge">AI</span>
<span class="ab-dot"></span>
<span class="ab-body">
<span class="ab-bubble">
<b class="ab-tag" id="abTag">宝宝今天想说</b>
<!-- 这句是 app.js 里 AI_LOGS[0].txt 的手写副本(这里要 <b> 和 <br> 排版,
没法直接共用那个字符串)。那边改口吻,这里必须跟着改,
否则首页气泡说「小家伙」、点进去小结说「我」,像两个人在写。 -->
<span class="ab-say">妈妈,今天你数到我动了 <b>3</b> 次,<br>我今天精神特别好。</span>
</span>
<!-- 「AI自动汇总你每日记录」按用户要求删了,这行现在只剩右边的入口。
✅ 位置不用额外处理:.ab-foot em 本来就写着 margin-left:auto,
左边那截没了它照样贴右。(原来那条 .ab-foot i 的样式已成
死规则,一并删了。) -->
<span class="ab-foot"><em>往期小结 ›</em></span>
</span>
</button>
</section>
<!-- 模块三 · 书架卡带。
上一版是「一张横排大卡 + 下面两张小卡」的老三段,和上面两个模块
一样都是往下堆。这里改成一条横向可滑的书架:三张等宽等高的**竖版**书
(封面在上、书名在下、免费标在底),右边故意露出下一张的边角,
不用画箭头也知道能往右滑。整页因此多了一段「可以往右读」的节奏 ——
这是最直观的一处排版变化。
资源这类内容对可发现性的要求低(不像工具是每天要点的),
收进横滑里是合适的;工具那一屏就没这么干。
⚠️ data-wk="n" 是从原来那条 h2 里搬过来的。app.js 用
$$('[data-wk]') 全局扫,属性一丢孕周就永远显示「—」。 -->
<section class="shelf">
<div class="shelf-bar">
<h2 class="sh-title">本周孕育指南 · 孕<span data-wk="n">—</span>周</h2>
<button class="sh-all" data-open="detailResList">全部 ›</button>
</div>
<p class="sh-note">适配孕周的专属孕育参考资料</p>
<!-- 三张卡的内部结构是**逐行对齐**的:封面 / 标题 / 简介 / 底行,一个不缺。
上一稿只有第一张有简介、也只有第一张有「立即领取」,另两张少两截,
于是三张卡的标题落在不同高度、底行也不齐 —— 这就是看着乱的原因。
封面给了固定高度(80px + object-fit:contain),底行靠
.sb-row{margin-top:auto} 顶到卡底,中间的简介统一截两行,
三张就自然等高、四段横着都成列了。
简介和「x 万妈妈已领取」都直接取自 js/app.js 里 RES 的同名字段,
和资源详情页显示的是同一份文案,不是这里另编的。
⚠️ 原来第一张那颗「立即领取」删掉了:整张卡本身就是个 <button>,
点哪儿都是进详情页,那颗 span 长得像按钮却并不真的领取,是个假承诺;
而且三张卡并列时三颗橙色大按钮也太吵。领取动作留在详情页底部。
底行读作一句话:「这些人领了 → 一共 12.8 万 → 而且免费」。
所以头像贴着人数(同一件事的两半,中间只留 7px),「免费」用
margin-left:auto 甩到最右,是另起的一件事。
.sb-faces 里那 6 个 <i> 是**装饰**,不是真实用户头像 ——
assets/ 里没有人像图,也不该为这点装饰新增二进制资源,
所以整叠头像是纯 CSS 画的(圆底 + ::before 头 + ::after 肩)。
aria-hidden:读屏念到这行应该只听见「12.8 万妈妈已领取」,
而不是六个没有内容的空节点。 -->
<div class="shelf-rail">
<button class="shelf-book sb-book ripple" data-open="detailRes" data-res="book">
<img class="sb-cover" src="assets/img/9f3fa8edf4aa.png" alt="" aria-hidden="true">
<span class="sb-title">《糖耐检查全攻略》</span>
<span class="sb-desc">孕24-28周必看 · 附检查前饮食清单</span>
<span class="sb-row"><span class="sb-faces" aria-hidden="true"><i></i><i></i><i></i><i></i><i></i><i></i></span><i class="sb-got">12.8 万妈妈已领取</i><span class="pill-free">免费</span></span>
</button>
<button class="shelf-book sb-food ripple" data-open="detailRes" data-res="food">
<img class="sb-cover" src="assets/img/3657b1f76026.png" alt="" aria-hidden="true">
<span class="sb-title">孕期营养全书</span>
<span class="sb-desc">按孕周编排的一日三餐参考</span>
<span class="sb-row"><span class="sb-faces" aria-hidden="true"><i></i><i></i><i></i><i></i><i></i><i></i></span><i class="sb-got">20.4 万妈妈已领取</i><span class="pill-free">免费</span></span>
</button>
<button class="shelf-book sb-music ripple" data-open="detailRes" data-res="music">
<img class="sb-cover" src="assets/img/67405412c52c.png" alt="" aria-hidden="true">
<span class="sb-title">胎教音乐合集</span>
<span class="sb-desc">30 首适合孕中期的舒缓曲目</span>
<span class="sb-row"><span class="sb-faces" aria-hidden="true"><i></i><i></i><i></i><i></i><i></i><i></i></span><i class="sb-got">9.6 万妈妈已领取</i><span class="pill-free">免费</span></span>
</button>
</div>
<!-- ⚠️ 那句话包在 <span id="shelfLeadTxt"> 里,是为了给 syncUnlockCopy()
一个**只含文字**的靶子。旁边「查看全部 ›」是个 <a data-open>,
它的点击监听是 init 时一次性绑上去的;谁要是图省事对整个 <p>
写 innerHTML,链接会连同监听被静默换掉 —— 看着一模一样,就是点不动。
这里写的是「还没解锁」那一版,解锁过的用户由 init() 刷成另一句。 -->
<p class="unlock"><span id="shelfLeadTxt"><b>128</b> 份孕育资料等你来领</span> · <a data-open="detailResList">查看全部 ›</a></p>
</section>
</div>
</section>
<section class="screen" id="screen-archive" data-screen="archive">
<div class="page-hero">
<div class="hero-top">
<div class="hero-actions">
<button class="icon-btn glass" data-search="1" aria-label="搜索"><svg viewBox="0 0 24 24" width="19" height="19"><circle cx="11" cy="11" r="6.5" fill="none" stroke="currentColor" stroke-width="1.9"/><path d="M16 16l4.5 4.5" stroke="currentColor" stroke-width="1.9" stroke-linecap="round"/></svg></button>
<button class="icon-btn glass" data-toast="更多操作" aria-label="更多"><svg viewBox="0 0 24 24" width="19" height="19"><circle cx="5" cy="12" r="1.7"/><circle cx="12" cy="12" r="1.7"/><circle cx="19" cy="12" r="1.7"/></svg></button>
</div>
</div>
<!-- 这一块整体重排过一次,改动的理由都写在下面各处 -->
<div class="hero-info ar-hero">
<!-- 页面名。上一版把它降成了 13px、opacity .78 的一行眉批 ——
当时的理由是「底部 tab 上就写着档案,别拿 27px 再说一遍」,
字号该让给孕周,这点现在还成立。但让过头了:
这行是整个页面唯一出现宝宝小名的地方,糊在橙底上基本读不到。
这一版不靠字号抢回来(25px 仍然归 .ar-week),改成一枚毛玻璃小牌:
半透明白底 + 一圈描边,从橙色里浮出来;小名单独拎出来加粗放大,
「的成长档案」压一档跟在后面 —— 重音落在名字上,而不是「档案」上。
⚠️ 小名现在写进 #arTitleName,不再是 #arTitle 本身。
syncPregnancy() 里那句赋值跟着改了,别再往 #arTitle 上写 textContent,
那会把牌子里的图标和「的成长档案」一起冲掉。 -->
<h1 class="ar-title" id="arTitle"><svg class="ar-t-ico" viewBox="0 0 24 24" width="13" height="13" aria-hidden="true"><path d="M4 4.5A1.5 1.5 0 015.5 3H10a3 3 0 013 3v13a2.5 2.5 0 00-2.5-2.5H4z" fill="currentColor" opacity=".5"/><path d="M20 4.5A1.5 1.5 0 0018.5 3H14a3 3 0 00-3 3v13a2.5 2.5 0 012.5-2.5H20z" fill="currentColor"/></svg><b class="ar-t-name" id="arTitleName">宝宝</b><span class="ar-t-rest">的成长档案</span></h1>
<!-- 这两行 + 左边那个大小图标 + 下面的孕程条,都由 syncPregnancy() 按孕期信息重算。
emoji 用 aria-hidden 挡掉:读屏念成「玉米」会和紧跟着的「像一根玉米了」重复 -->
<div class="ar-lines">
<span class="ar-size" id="arSizeArt" aria-hidden="true">🌽</span>
<div>
<p class="ar-week" id="arLine1">孕 24 周 +3 天</p>
<p class="hero-p sub" id="arLine2">像一根玉米了 · 约 600 克</p>
</div>
</div>
<!-- 孕程原来是右边一个 84px 的圆环,和首页 hero 那个环长得一模一样,
两页来回切像同一个组件贴了两遍;它还横着吃掉近三分之一宽度,
逼得旁边的字只能写得又短又挤,「已陪伴 171 天」当时就只能拿个「·」
硬接在孕周后面,两个不相干的单位读起来像一句话。
换成一条通栏细进度条:高 5px,信息一点没少,左右还各空出一个角,
「孕程」和「已陪伴」这才各站各的位置。 -->
<div class="ar-prog"><i id="arBar"></i></div>
<div class="ar-foot">
<span>孕程 <b id="arPct">60%</b></span>
<span>已陪伴 <b id="arDays">171</b> 天</span>
</div>
</div>
</div>
<div class="page-body">
<!-- ========== 正在自动归档 + 生成档案(合成一张卡) ==========
顺序:标题 → 两个生成入口 → 归档条数统计。
周档案:每周一张长图,保存到相册或分享朋友圈;
完整档案:从记录第一天到今天,排成一本 A4 分页的册子,打印出来能直接装订。
两者都是 canvas 现画出来的,不依赖任何外部库。 -->
<div class="auto-card">
<div class="ac-head">
<span class="ac-h-l"><span class="live-dot"></span> 正在自动归档</span>
<span class="gen-badge" id="genWeekTag">第 24 周</span>
</div>
<!-- 说明文字拆进了下面两个入口各自的副标题:
谁能干什么写在谁身上,比笼统一句更好读。
「可打印」原本在这里的角标和按钮里各出现一次,现在只留按钮那处 -->
<div class="gen-btns">
<button class="gen-btn ripple" data-poster="week">
<svg viewBox="0 0 24 24" width="21" height="21" aria-hidden="true">
<rect x="3.5" y="4.5" width="17" height="16" rx="3" fill="none" stroke="currentColor" stroke-width="1.8"/>
<path d="M3.5 9.5h17M8 2.8v3.4M16 2.8v3.4" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round"/>
<rect x="7" y="12.5" width="5" height="4" rx="1.2" fill="currentColor"/>
</svg>
<span><b>本周档案</b><em>存相册 · 发朋友圈</em></span>
</button>
<button class="gen-btn ripple" data-poster="all">
<svg viewBox="0 0 24 24" width="21" height="21" aria-hidden="true">
<path d="M7 3.5h7.5L19 8v12.5H7z" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linejoin="round"/>
<path d="M14 3.5V8h5" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linejoin="round"/>
<path d="M10 12h6M10 15.5h6M10 19h3.5" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round"/>
</svg>
<span><b>完整档案</b><em>A4 分页 · 可装订</em></span>
</button>
</div>
<div class="ac-chips">
<span class="chip-stat">今日已归档 <b>4</b> 条</span>
<span class="chip-stat">累计 <b>386</b> 条</span>
<span class="chip-stat">来自 <b>9</b> 个孕期工具</span>
</div>
</div>
<div class="segmented" id="archiveSeg">
<span class="seg-thumb" id="segThumb"></span>
<!-- ⚠️ 这三个按钮的顺序必须和下面 .seg-panel 的 DOM 顺序一一对应。
initSegmented() 是按 .is-active 在兄弟节点里的**序号**算 #segThumb 的 x 偏移,
只改按钮不改面板 → 高亮停在「成长数据」、底下却显示时光轴。 -->
<button class="seg-btn is-active" data-seg="data">成长数据</button>
<button class="seg-btn" data-seg="timeline">时光轴</button>
<!-- 收藏数原本在下面那行 sec-title 里,那行整条删了,
数字搬到标签上(和来信页「未读 1」同一种写法),#favCount 仍由 JS 更新 -->
<button class="seg-btn" data-seg="moment">珍贵时刻 <i id="favCount">18</i></button>
</div>
<!-- 成长数据 -->
<div class="seg-panel is-active" data-panel="data">
<div class="stat-row">
<div class="stat-card">
<div class="sc-label">体重</div>
<div class="sc-value">58.2<small>kg</small></div>
<div class="sc-trend up">↑ 7.7kg</div>
<svg class="spark" viewBox="0 0 100 34"><polyline points="2,30 26,24 50,17 74,10 98,4" fill="none" stroke="#F4593C" stroke-width="2.6" stroke-linecap="round"/></svg>
</div>
<div class="stat-card">
<div class="sc-label">日均胎动</div>
<div class="sc-value">31<small>次</small></div>
<div class="sc-trend ok">正常</div>
<svg class="spark" viewBox="0 0 100 34"><polyline points="2,20 20,8 38,24 56,10 74,26 98,14" fill="none" stroke="#7FA86A" stroke-width="2.6" stroke-linecap="round" stroke-linejoin="round"/></svg>
</div>
<div class="stat-card">
<div class="sc-label">估计体重</div>
<div class="sc-value">600<small>g</small></div>
<div class="sc-trend purple">P55</div>
<svg class="spark" viewBox="0 0 100 34"><polyline points="2,29 26,23 50,17 74,11 98,4" fill="none" stroke="#8B6FE0" stroke-width="2.6" stroke-linecap="round"/></svg>
</div>
</div>
<div class="chart-card">
<div class="cc-title">妈妈体重 · 孕12–24周</div>
<svg class="chart" viewBox="0 0 320 150">
<line class="grid" x1="4" y1="20" x2="316" y2="20"/>
<line class="grid" x1="4" y1="66" x2="316" y2="66"/>
<line class="grid" x1="4" y1="112" x2="316" y2="112"/>
<path class="band" d="M8,138 L83,120 L158,100 L234,76 L310,54 L310,14 L234,36 L158,60 L83,80 L8,98 Z"/>
<path class="area" d="M8,124 L83,106 L158,86 L234,62 L310,40 L310,146 L8,146 Z"/>
<polyline class="line" points="8,124 83,106 158,86 234,62 310,40"/>
<circle class="pt" cx="83" cy="106" r="6"/><circle class="pt" cx="158" cy="86" r="6"/><circle class="pt" cx="234" cy="62" r="6"/>
<circle class="pt fill" cx="310" cy="40" r="7"/>
</svg>
<div class="cc-x"><span>12周</span><span>16周</span><span>20周</span><span>24周</span></div>
<div class="cc-legend"><span class="lg lg-line">我的体重</span><span class="lg lg-band">推荐区间</span></div>
</div>
<div class="meas-card">
<div class="meas"><span class="m-name">双顶径</span><div class="m-bar" style="--p:54%"><i></i></div><div class="m-val">6.1<small>cm</small><em>P58</em></div></div>
<div class="meas"><span class="m-name">股骨长</span><div class="m-bar" style="--p:50%"><i></i></div><div class="m-val">4.4<small>cm</small><em>P52</em></div></div>
<div class="meas"><span class="m-name">腹围</span><div class="m-bar" style="--p:60%"><i></i></div><div class="m-val">19.6<small>cm</small><em>P61</em></div></div>
</div>
</div>
<!-- 时光轴 -->
<!-- 这里原来还压着一排分类 chips(全部/胎动/产检/来信/手记)。删了:
上面刚有一排「时光轴 / 成长数据 / 珍贵时刻」的分段,紧接着再来一排样子
几乎一样的标签,两排叠在一起谁管谁根本看不出来。
而且时光轴统共五条,一条一个分类,筛完最多剩两条 —— 为了从五条里挑两条
先读五个标签,不划算。每条底下本来就写着「来自「数胎动」」,
要认哪一类,读那行就够了。 -->
<div class="seg-panel" data-panel="timeline">
<div class="tl-card">
<div class="tl-item" data-cat="胎动">
<div class="tl-date" data-ga="now"></div>
<p class="tl-text">早上 9 点 10 分,他动了 <b>12 次</b>。你说那感觉像有人在里面轻轻敲门。</p>
<span class="tl-from">来自「数胎动」</span>
</div>
<div class="tl-item" data-cat="产检">
<div class="tl-date" data-ga="20"></div>
<p class="tl-text">大排畸那天,你第一次看清了他的侧脸。医生说「一切都好」,你在诊室外站了很久。</p>
<span class="tl-from">来自「孕检日历」+「B超影像」</span>
</div>
<div class="tl-item" data-cat="胎动">
<div class="tl-date" data-ga="18"></div>
<p class="tl-text">傍晚散步时,肚子里忽然有一下轻轻的翻涌 —— <b>第一次胎动</b>。你愣在原地,然后笑了。</p>
<span class="tl-from">来自「胎动记录」</span>
</div>
<div class="tl-item" data-cat="产检">
<div class="tl-date" data-ga="8"></div>
<p class="tl-text">第一次听见心跳,很快,像小火车。你把那 12 秒录了下来。</p>
<span class="tl-from">来自「胎心记录」</span>
</div>
<div class="tl-item" data-cat="手记">
<div class="tl-date" data-ga="1"></div>
<p class="tl-text">那两条线出现的早晨,你把它拍下来,发给了他。你们谁都没说话,笑了很久。</p>
<span class="tl-from">来自「妈妈手记」</span>
</div>
</div>
</div>
<!-- 珍贵时刻 -->
<!-- 同样删掉了一排分类 chips(全部/第一次/影像/声音/手写)。
六个格子的九宫格,本身一眼就扫完了,再给它配五个筛选标签是本末倒置。 -->
<div class="seg-panel" data-panel="moment">
<!-- 珍贵时刻的封面图标:原来是 emoji(🖼️💌👣🎧🤰✍️),
各系统渲染成的样子完全不同(拟物、彩色、还带阴影),
这里统一换成同一套线性扁平 SVG,颜色跟着各自的渐变底走 -->
<div class="moment-grid">
<button class="moment ripple" data-cat="影像 第一次" data-toast="第一张B超 · 孕8周">
<div class="mo-art a1">
<svg viewBox="0 0 48 48" aria-hidden="true">
<rect x="7" y="9" width="34" height="26" rx="5" class="f"/>
<rect x="7" y="9" width="34" height="26" rx="5" class="s"/>
<path d="M16 29a12 12 0 0 1 12-12" class="s"/>
<path d="M16 29a7 7 0 0 1 7-7" class="s"/>
<circle cx="16" cy="29" r="2.4" class="d"/>
<path d="M24 35v4M20 39h8" class="s"/>
</svg>
<i class="star is-on">★</i>
</div>
<div class="mo-title">第一张B超</div><div class="mo-meta" data-ga="8"></div><span class="mo-from">来自「B超影像」</span>
</button>
<!-- ⚠️ `.mo-from` 引的是**底 tab 的名字**,不是随便一句出处。
tab 叫什么,这里就必须写什么 —— 指过去的地方得真的存在。
上一批 tab 改叫「宝宝消息」时这三处跟着改过(标题「第一条消息」、
出处「来自「宝宝消息」」、data-toast),本批 tab 退回「宝宝来信」,
三处一起退。
⚠️ 宽度:来回都是等字数替换(5 字换 5 字、8 字换 8 字),
.mo-title / .mo-from 的排版账不用重算。
⚠️ data-toast 那句是点上去弹的提示,和标题同源,一起改,别只改一处 ——
它藏在属性里,只扫 textContent 的检查看不见它。 -->
<button class="moment ripple" data-cat="第一次" data-toast="第一封来信 · 孕12周">
<div class="mo-art a2">
<svg viewBox="0 0 48 48" aria-hidden="true">
<rect x="6" y="11" width="36" height="26" rx="5" class="f"/>
<rect x="6" y="11" width="36" height="26" rx="5" class="s"/>
<path d="M8.5 14.5l14 11a2.4 2.4 0 0 0 3 0l14-11" class="s"/>
</svg>
<i class="star is-on">★</i>
</div>
<div class="mo-title">第一封来信</div><div class="mo-meta" data-ga="12"></div><span class="mo-from">来自「宝宝来信」</span>
</button>
<button class="moment ripple" data-cat="第一次" data-toast="第一次胎动 · 孕18周">
<div class="mo-art a3">
<svg viewBox="0 0 48 48" aria-hidden="true">
<path d="M27 17c0 6-2.8 8.8-2.8 12.8 0 4-2.4 6.7-5.9 6.7s-6.3-2.7-6.3-6.7c0-5 2.9-7.9 2.9-12.8 0-4.1 2.6-7 5.9-7s6.2 2.9 6.2 7z" class="f"/>
<path d="M27 17c0 6-2.8 8.8-2.8 12.8 0 4-2.4 6.7-5.9 6.7s-6.3-2.7-6.3-6.7c0-5 2.9-7.9 2.9-12.8 0-4.1 2.6-7 5.9-7s6.2 2.9 6.2 7z" class="s"/>
<circle cx="31.5" cy="14.5" r="3" class="d"/>
<circle cx="35.6" cy="20.4" r="2.6" class="d"/>
<circle cx="36.8" cy="27" r="2.2" class="d"/>
</svg>
<i class="star is-on">★</i>
</div>
<div class="mo-title">第一次胎动</div><div class="mo-meta" data-ga="18"></div><span class="mo-from">来自「胎动记录」</span>
</button>
<button class="moment ripple" data-cat="声音" data-toast="正在播放 胎心录音 ♪">
<div class="mo-art a4">
<svg viewBox="0 0 48 48" aria-hidden="true">
<path d="M24 39S8 30.2 8 19.8C8 14.6 12 11.2 16.2 11.2c3.2 0 6 1.8 7.8 4.4 1.8-2.6 4.6-4.4 7.8-4.4 4.2 0 8.2 3.4 8.2 8.6C40 30.2 24 39 24 39z" class="f"/>
<path d="M24 39S8 30.2 8 19.8C8 14.6 12 11.2 16.2 11.2c3.2 0 6 1.8 7.8 4.4 1.8-2.6 4.6-4.4 7.8-4.4 4.2 0 8.2 3.4 8.2 8.6C40 30.2 24 39 24 39z" class="s"/>
<path d="M12.5 22.5h5.2l3-5.4 3.9 10.4 2.8-5h6.1" class="s"/>
</svg>
<i class="star is-on">★</i>
</div>
<div class="mo-title">胎心录音 12秒</div><div class="mo-meta" data-ga="20"></div><span class="mo-from">来自「胎心记录」</span>
</button>
<button class="moment ripple" data-cat="影像 第一次" data-toast="第一张孕肚照 · 孕22周">
<div class="mo-art a5">
<svg viewBox="0 0 48 48" aria-hidden="true">
<path d="M6 17a4 4 0 0 1 4-4h5l2.6-4.2h12.8L33 13h5a4 4 0 0 1 4 4v17a4 4 0 0 1-4 4H10a4 4 0 0 1-4-4z" class="f"/>
<path d="M6 17a4 4 0 0 1 4-4h5l2.6-4.2h12.8L33 13h5a4 4 0 0 1 4 4v17a4 4 0 0 1-4 4H10a4 4 0 0 1-4-4z" class="s"/>
<circle cx="24" cy="25.5" r="7" class="s"/>
<circle cx="24" cy="25.5" r="2.6" class="d"/>
</svg>
<i class="star is-on">★</i>
</div>
<div class="mo-title">第一张孕肚照</div><div class="mo-meta" data-ga="22"></div><span class="mo-from">来自「孕肚照」</span>
</button>
<button class="moment ripple" data-cat="手写" data-toast="给宝宝的回信 · 孕23周">
<div class="mo-art a6">
<svg viewBox="0 0 48 48" aria-hidden="true">
<path d="M31.4 8.6a4 4 0 0 1 5.7 5.7L17.6 33.8l-8.2 2.5 2.5-8.2z" class="f"/>
<path d="M31.4 8.6a4 4 0 0 1 5.7 5.7L17.6 33.8l-8.2 2.5 2.5-8.2z" class="s"/>
<path d="M28.5 11.5l5.7 5.7" class="s"/>
<path d="M14 41h22" class="s"/>
</svg>
<i class="star is-on">★</i>
</div>
<div class="mo-title">给宝宝的回信</div><div class="mo-meta" data-ga="23"></div><span class="mo-from">来自「我的回信」</span>
</button>
</div>
</div>
<button class="album-card ripple" data-toast="纪念册第 2 册编纂中……">
<div class="al-book"><span class="al-leaf">🌱</span><span class="al-tag">孕早期<br>1–13周</span></div>
<div class="al-body">
<div class="al-title">纪念册 <em>交给未来的孩子</em></div>
<p class="al-desc">第 1 册已成册(32 页)· 第 2 册编纂中<br>等他长大,这些会替你把话说完。</p>
<div class="al-bar"><i style="--p:62%"></i></div>
<div class="al-meta">孕中期 62% · 预计孕28周可成册</div>
</div>
</button>
</div>
</section>
<section class="screen" id="screen-letter" data-screen="letter">
<div class="page-hero">
<!-- 这里原来有一整条 .hero-top,里面就一个搜索图标。删掉了:
一共 24 封信,下面那排「全部 / 未读 / 我的回信 / 已收藏」已经够翻,
再放个搜索框只是把首页的样式抄过来,没人会在这儿搜。
搜索本身还在(首页和资源库还挂着 data-search="1"),只是来信不进这个门。 -->
<!-- ===== 信封上盖(本批新增)=====
用户原话:「宝宝来信上面的头部也稍微变换一下,做的稍微有一点设计感。
现在有点太平了。可以把这个页面做成类似一个信封的那种感觉,试试呢?」
整块橘色头部当信封的正面,这一笔画出从两个上角折下来的盖子,
尖端落在下面那排筛选标签的上沿 —— 那条压暗的横条正好像一道封口。
为什么用 SVG,而不是 CSS 三角形或 clip-path:
· 边框三角(border-left/right-width)**不接受百分比**。要么写 vw
—— 桌面预览时 100vw 是整个窗口,比 430px 的 .phone 宽得多,
尖端会跑到屏幕外;要么写死 px —— 写死就贴不住两个上角。
· clip-path 在老 X5 内核上有掉档风险,一旦不认就是**整块实白**
盖住页头,属于「坏掉了还看不出为什么」那一类。
· SVG 是**标记**,smoke 能直接 querySelector 到它并检查坐标;
写成 CSS 的话只能拿正则去比对样式表文本,钉不住形状。
preserveAspectRatio="none" 让 100×100 的 viewBox 非等比拉满盒子,
所以坐标里的 50 就是「横向正中」,不用管屏幕多宽 —— 尺寸交给 CSS。
⚠️ 两个图形上的 fill 都不能省:全局有一条 svg{fill:currentColor},
省掉就会继承成页头的白字色,变成一整块实白糊住标题。 -->
<svg class="env-flap" viewBox="0 0 100 100" preserveAspectRatio="none" aria-hidden="true">
<polygon points="0,0 100,0 50,100" fill="#fff" opacity=".10"/>
<path d="M0 0 50 100 100 0" fill="none" stroke="#fff" stroke-opacity=".26"
stroke-width="1.5" vector-effect="non-scaling-stroke"/>
</svg>
<div class="hero-info">
<div>
<!-- 这两句里的「宝宝」由 syncBabyWords() 换成用户填的小名,
标记里写的只是没填小名时的兜底文案 -->
<h1 class="hero-h1" id="ltHeroH1">宝宝写给你的信</h1>
<!-- 未读数不在这里说了——下面那排标签上就写着「未读 1」,
两者上下紧挨,重复一遍只会让人多读一次 -->
<p class="hero-p">共 <span id="letterTotal">24</span> 封</p>
<p class="hero-p sub" id="ltHeroSub">每周宝宝都会悄悄给你写一封</p>
</div>
<!-- 扁平化信封:纯矢量,与首页来信卡的信封同一套造型语言 -->
<svg class="env-art" viewBox="0 0 64 48" width="72" height="54" aria-hidden="true">
<rect x="1.5" y="1.5" width="61" height="45" rx="8" fill="#fff" opacity=".96"/>
<path d="M1.5 9.5 32 28 62.5 9.5" fill="none" stroke="#F4593C" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M1.5 38.5 22 24M62.5 38.5 42 24" fill="none" stroke="#FBB7A6" stroke-width="3" stroke-linecap="round"/>
<circle cx="52" cy="11" r="7.5" fill="#F4593C"/>
<path d="M52 7.4c1.6-2 4.6-.9 4.6 1.3 0 1.9-2.4 3.6-4.6 5.3-2.2-1.7-4.6-3.4-4.6-5.3 0-2.2 3-3.3 4.6-1.3z" fill="#fff"/>
</svg>
</div>
<!-- 筛选标签收进橘色头部,和标题连成一块。
原先它下面还压着一张「最新那封信」的大卡,而那张卡画的就是
列表第一条——同一封信在一屏里出现两次,删掉了。 -->
<div class="segmented" id="letterSeg">
<span class="seg-thumb" id="letterThumb"></span>
<button class="seg-btn is-active" data-lseg="all">全部</button>
<button class="seg-btn" data-lseg="unread">未读 <i id="unreadNum">1</i></button>
<button class="seg-btn" data-lseg="reply">我的回信</button>
<button class="seg-btn" data-lseg="fav">已收藏</button>
</div>
</div>
<div class="page-body">
<!-- 列表全部由 renderLetters() 生成:每封信是一个 .lthread,
妈妈的回信和宝宝的再回复都作为 .lt-sub 挂在这封信下面,
一眼就能看出「这条回信是回给哪封来信的」 -->
<div class="letter-list" id="letterList"></div>
<div class="empty-tip" id="letterEmpty">这里还没有内容</div>
</div>
</section>
<section class="screen" id="screen-me" data-screen="me">
<div class="me-top">
<div class="hero-top">
<!-- (商城图标已移除:进商城统一走下方「兜爸心选商城」模块 meMallCard)
<button class="icon-btn plain" id="btnMall" aria-label="进商城">
<svg viewBox="0 0 24 24" width="22" height="22" aria-hidden="true">
<path d="M5 8h14l-1.2 11a2 2 0 0 1-2 1.8H8.2a2 2 0 0 1-2-1.8z" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linejoin="round"/>
<path d="M8.6 10V6.8a3.4 3.4 0 0 1 6.8 0V10" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round"/>
</svg>
</button>
-->
<!-- 扁平化齿轮:描边式矢量,替换原本的 ⚙️ emoji -->
<button class="icon-btn plain" data-modal="modalProfile" aria-label="设置">
<svg viewBox="0 0 24 24" width="22" height="22" aria-hidden="true">
<path d="M12 15.2a3.2 3.2 0 1 0 0-6.4 3.2 3.2 0 0 0 0 6.4z" fill="none" stroke="currentColor" stroke-width="1.7"/>
<path d="M19.4 13.5a1.5 1.5 0 0 0 .3 1.65l.06.05a1.8 1.8 0 1 1-2.55 2.55l-.05-.06a1.5 1.5 0 0 0-1.65-.3 1.5 1.5 0 0 0-.91 1.37v.14a1.8 1.8 0 1 1-3.6 0v-.07a1.5 1.5 0 0 0-.98-1.37 1.5 1.5 0 0 0-1.65.3l-.05.06a1.8 1.8 0 1 1-2.55-2.55l.06-.05a1.5 1.5 0 0 0 .3-1.65 1.5 1.5 0 0 0-1.37-.91h-.14a1.8 1.8 0 1 1 0-3.6h.07a1.5 1.5 0 0 0 1.37-.98 1.5 1.5 0 0 0-.3-1.65l-.06-.05A1.8 1.8 0 1 1 8.5 4.01l.05.06a1.5 1.5 0 0 0 1.65.3h.07a1.5 1.5 0 0 0 .91-1.37v-.14a1.8 1.8 0 1 1 3.6 0v.07a1.5 1.5 0 0 0 .91 1.37 1.5 1.5 0 0 0 1.65-.3l.05-.06a1.8 1.8 0 1 1 2.55 2.55l-.06.05a1.5 1.5 0 0 0-.3 1.65v.07a1.5 1.5 0 0 0 1.37.91h.14a1.8 1.8 0 1 1 0 3.6h-.07a1.5 1.5 0 0 0-1.37.91z" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
</button>
</div>
<!-- 整张卡可点:点它和点右上角齿轮都会打开「我的资料」弹窗。
这里保持 div 而不是 button——里面还嵌着头像的编辑角标,
button 套 button 是非法结构,浏览器会把 DOM 拆开 -->
<div class="me-card ripple" data-modal="modalProfile" role="button" tabindex="0">
<div class="me-avatar" id="meAvatar">
<svg viewBox="0 0 40 40" width="34" height="34" aria-hidden="true">
<!-- 卡片改成白底后,写死的 #fff 剪影会直接看不见,改由 .me-avatar 的 color 决定 -->
<circle cx="20" cy="14.5" r="7" fill="currentColor"/>
<path d="M6.5 35c0-7.2 6-11.5 13.5-11.5S33.5 27.8 33.5 35z" fill="currentColor"/>
</svg>
<img id="meAvatarImg" alt="">
<i class="av-edit" aria-hidden="true">
<svg viewBox="0 0 24 24" width="11" height="11">
<path d="M4 20h16" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round"/>
<path d="M15.6 4.4a2.3 2.3 0 0 1 3.3 3.3L9.7 16.9l-4.3 1 1-4.3z" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linejoin="round"/>
</svg>
</i>
</div>
<div class="me-info">
<div class="me-name" id="meName">未登录</div>
<!-- ⚠️ 这里先后住过两样东西,两样都已经删了,别再往回搬:
① 最早是「孕 24 周 +3 天 · 预产期 12月22日」,由 syncPregnancy() 刷。
删它是因为**重复**:往下滚一屏就是孕期卡,孕周、预产期、
剩余天数在那儿写得更全。
② 然后换成了一句话签名 <p class="me-bio" id="meBio">,
本批按用户要求(「我的资料里的一句话签名可以去掉」)连同
弹窗里的 #pfBio 输入框、js 里的 syncBio()、state.userBio 一起删。
⚠️ 必须一起删:只删输入框、留下这行展示,它就成了一句
谁也改不动的死文案 —— 用户会在资料弹窗里翻来翻去找哪儿能改。
⚠️ syncPregnancy() 里那行 $('.me-info p') 早就删掉了(它取的是
**第一个** <p>)。现在第一个 <p> 是下面的「已连续记录」,
谁要是把那行加回来,会把连续记录天数冲掉,而且是静默的。 -->
<p>已连续记录 <b>42</b> 天</p>
</div>
<!-- 退出登录图标按钮:默认隐藏,applyRemoteProfile 确认已登录(有 userName)才显示。
绝对定位在卡片右上角(尖括号左边);stopPropagation 防止触发资料弹窗 -->
<button class="icon-btn plain me-exit" id="meLogoutBtn" aria-label="退出登录" style="display:none">
<svg viewBox="0 0 24 24" width="19" height="19" aria-hidden="true">
<path d="M14 5h5a1.5 1.5 0 0 1 1.5 1.5v11A1.5 1.5 0 0 1 19 19h-5" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round"/>
<path d="M10 12H3m0 0 3.5-3.5M3 12l3.5 3.5" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
</button>
<i class="me-chev" aria-hidden="true">›</i>
</div>
</div>
<div class="page-body no-top">
<!-- 授权完善资料:H5 → navigateTo → 小程序授权页(头像/昵称/手机号) → box_app /auth/profile 落库 -->
<div class="list-card" style="margin-top:12px" id="meAuthBox">
<button class="li ripple" id="meAuthCard">
<span class="li-ico o"><svg viewBox="0 0 24 24" width="19" height="19" aria-hidden="true"><rect x="5" y="10.5" width="14" height="9.5" rx="2.5" fill="none" stroke="currentColor" stroke-width="1.8"/><path d="M8.5 10.5V8a3.5 3.5 0 0 1 7 0v2.5" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round"/><path d="M12 15v2" stroke="currentColor" stroke-width="1.8" stroke-linecap="round"/></svg></span>
<span class="li-t">授权完善资料</span>
<span class="li-v">获取手机号 / 昵称 / 头像</span>
<i class="chev">›</i>
</button>
</div>
<!-- 退出登录已移到个人资料卡右上角的图标按钮(#meLogoutBtn) -->
<!-- ⚠️ 这两处的 data-modal 从 modalPreg 改成了 modalProfile:
「编辑孕期信息」那张弹窗已整个并进「我的资料」(见文件末尾那段说明)。
小名 / 性别 / 预产期现在只有一处能改,改完照样 syncPregnancy() 回刷这张卡。 -->
<!-- ⚠️ 「(必填)」是独立的 <span class="sec-req">,**不能**塞进 .sec-more 那颗按钮里 ——
.sec-more 是「编辑 ›」的可点区域,把「必填」并进去等于扩大了热区,
用户想读一眼提示、结果弹出了编辑窗。它也不能只是写进标题文本里:
要单独一个元素才能上品牌色。
⚠️ 用品牌色不用灰:「必填」是一句**要求**,写成灰小字就退成了附注,没人会去填。
宽度账(.page-body 左右各 18 → 可用 284,.sec-title 是 flex、gap 8):
竖条 4 + 8 + 「孕期信息」4×17.5=70 + 8 + 「(必填)」4×12=48 + 8 + 「编辑 ›」≈34
= 180 ≤ 284 ✓,余 104px,宽裕。 -->
<h2 class="sec-title dark">孕期信息<span class="sec-req">(必填)</span> <button class="sec-more" data-modal="modalProfile">编辑 ›</button></h2>
<button class="preg-card ripple" data-modal="modalProfile">
<div class="pc-row">
<!-- 孕周进度环 + 蜷着的宝宝。
上一版是一个细线条的孕妇侧影:笔画太细、那条「腿」像根多余的杂线,
肚子里那颗浅色小点在 52px 上几乎看不见,凑近才认得出画的是什么。
换掉的理由不只是好看 —— 这张卡讲的就是「走到哪儿了」
(孕 24 周 +3 天 / 还有 110 天 / 一条进度条 / 早中晚期三段),
所以图标本身就画成一圈进度:外圈是整个孕期,实心那段是已经走过的,
中间蜷着的是宝宝。图标和卡片说的是同一件事。
⚠️ 这一圈是**活的**:syncPregnancy() 按真实孕周重写 #pcRing 的
stroke-dasharray,和下面那条 .pc-bar 同源(同一个 pct)。
这里写死的 "45 73" 只是 JS 跑起来之前的占位(≈62%,对应 r=11.6
的周长 72.9),不是设计值 —— 要改进度去改 syncPregnancy,别调这里。
⚠️ 下面这几处颜色跟着 .preg-card 的配色走
(橘 → 雾蓝 → 紫粉 → 现在的深紫渐变 + 白)。
它们是 SVG presentation attribute,不在 style.css 里,
改配色时最容易漏掉的就是这一块 —— 上一版深紫 #7B5BD6 在这儿出现
**三次**(进度环 stroke、宝宝头 fill、身子 stroke),
只改第一处就会得到「紫环配蓝宝宝」。
⚠️ 卡片现在是深底,整个图标就是**一色白**,上面那个「三处」的坑
因此暂时看不出来了 —— 但它随时会回来:下次谁把配色改回浅底,
就得重新给这三处各挑一个深色。smoke 里钉着「图标里所有颜色一致」,
漏改一处就会红。
⚠️ 底环用 stroke-opacity 调暗,**不能**和进度环一样是实白 ——
两圈一样白就等于没有进度环,那一圈永远看着是满的。
用 stroke-opacity 而不是 rgba():presentation attribute 里
rgba 各家浏览器支持不一,stroke-opacity 是 SVG 1.1 的正经属性。 -->
<div class="pc-ico">
<svg viewBox="0 0 32 32" width="27" height="27" aria-hidden="true">
<circle cx="16" cy="16" r="11.6" fill="none" stroke="#FFFFFF" stroke-opacity=".3" stroke-width="3.2"/>
<circle id="pcRing" cx="16" cy="16" r="11.6" fill="none" stroke="#FFFFFF" stroke-width="3.2"
stroke-linecap="round" stroke-dasharray="45 73" transform="rotate(-90 16 16)"/>
<circle cx="17" cy="12.9" r="3.2" fill="#FFFFFF"/>
<path d="M13.8 17.2c.3 3.6 3 5.1 6 4.1" fill="none" stroke="#FFFFFF" stroke-width="3.4" stroke-linecap="round"/>
</svg>
</div>
<div class="pc-main"><div class="pc-title" id="pcTitle">孕 24 周 +3 天</div><div class="pc-sub" id="pcSub">预产期 2024年12月22日 · 还有 110 天</div></div>
<i class="chev">›</i>
</div>
<div class="pc-bar"><i id="pcBar" style="--p:60%"></i></div>
<div class="pc-stage"><span>孕早期</span><span class="cur" id="pcStage">孕中期 · 当前</span><span>孕晚期</span></div>
</button>
<!-- ================= 孕育顾问(企业微信入口) =================
常驻在孕期卡下面:上面那张卡讲「你走到哪儿了」,这张接着说
「路上有人陪你」,是同一段话的下一句;而「设置与服务」那组是
工具箱,把一个真人服务塞进去会被当成一行设置项划过去。
⚠️ 故意**不挂 data-modal="modalWx"**。那条通用通道直接调 openModal,
会绕过 openWx() —— 弹窗的模式类名、引导语、上次残留的红字和
输入内容、还有 wxPending 全都不会被重置。这里必须走 JS 那条路。
⚠️ 这张卡**不随解锁状态隐藏**(资源库顶部那条 banner 会)。
它是「我的」页的常驻入口,藏了会缺一块;代价是已解锁的人
仍会看到福利文案 —— 由弹窗 done 模式的文案把动线接上,
见 app.js openWx() 里那三段。这是有意的,不是漏改。 -->
<h2 class="sec-title dark">专属福利</h2>
<button class="wx-card ripple" id="meWxCard">
<!-- ⚠️ 这一版是**三段竖排**:角标行 / 图文行 / 通栏 CTA。
上一版是四段(角标 / 标题 / 图文 / CTA),本批按用户要求
「商品名放到到手价上边、跟到手价对齐」,把整个标题行搬进了右列
.wx-main —— 商品名和价格从此共用同一条左缘,这就是「对齐」的实现。
同时「每人领取一份放到商品名右边、左边整个位置留给图片」。
卡内净宽 = 320 − 页边 36 − 卡内边 36 = **248px**(下面所有账都对着它)
① 图文行:.wx-fig 76 + gap 12 → 右列 .wx-main = 248 − 76 − 12 = **160px**
② 标题行(住在 160px 的右列里;本批起两件**竖排**,各占一整行):
「可心柔婴儿纸巾」7 字 × 16 = 112 ≤ 160 ✓ 余 48px
「限领一份」 4 字 × 11.5 = 46 ≤ 160 ✓ 余 114px
⚠️ 上一版是横排:105 + gap 5 + 42 = 152 ≤ 160,**只余 8px**,
而且那 8px 是按 15px 字号死算的。微信有一个全局字体大小
滑块,用户一调大,105px 的商品名当场涨到 130+,余量瞬间
蒸发、ellipsis 真的触发 —— 商品名被啃成「可心柔婴儿纸…」。
**这就是用户本批报的那个 bug**(原话「把限领一份儿放到
可心柔婴儿纸巾的后边」,追问后确认他看到的是被切断的商品名)。
⚠️ 竖排把「两件抢同一条 160px」这个根子问题去掉了,不是把
临界点往后推。所以字号也一并回到 16 / 11.5,不再需要压字号。
**别为了省一行高度改回横排。**
③ 右列其余两行(都在 160px 里):
「到手价 ¥0」= 3×11.5 + gap 5 + ¥0 ≈ 26 → 65.5 ✓
副文「加小汤圆的孕育顾问领取」11 × 12.5 = 137.5 ✓ 一行;
小名满 12 字时 20 字 = 250 → 折两行,line-clamp:2 兜住
④ 高度账 —— 这就是「左边整个的位置都留给图片」:
.wx-main 高 = 19.5(head) + 5 + 26(price) + 5 + 18.1(sub 一行) ≈ 73.6
sub 折两行时 ≈ 91.8
所以 .wx-fig **不写死高度**,用 align-self:stretch + min-height:92 ——
图片永远填满左列整个高度:短文案时由 92 撑住卡高,
长小名把右列顶到 91.8 时也不会被超过。
⑤ CTA 通栏 248px。 -->
<!-- 角标行:只有一枚角标,右对齐 = 卡的右上角。
⚠️ 这一行**故意是空的**,只装角标。看着浪费了 18px 高,但角标并回
别处必然挤爆某一行,而挤爆的表现是商品名被省略号啃掉。
⚠️ 角标**仍然是正常流里的一枚标签**,绝不能改回 position:absolute
骑在卡边上:.wx-card 挂着 .ripple,overflow:hidden 会把它裁掉半截。
那正是用户早先说的「限量福利被切了」,已经犯过一次,别再犯。 -->
<span class="wx-top">
<!-- ⚠️ 文案「限量福利」是刻意避开「免费领」的:下面 CTA 已经写了
「立即免费领取」,角标再写一遍「免费领」是同一张卡里重复吆喝。 -->
<span class="wx-badge">限量福利</span>
</span>
<span class="wx-body">
<!-- 左边这块是**商品占位插画**(一包抽纸),不是最终图。
⚠️ 类名是 .wx-fig,**绝不能**叫 .wx-qr —— 那个类是弹窗里的
企业微信二维码,测试里有一条「全页只有一处二维码」的断言,
蹭上去会当场红。
⚠️ 「示例图 · 待替换」这行字画在 svg **内部**,换成真实商品图时
它跟着一起消失,不会留下一行孤零零的说明。
⚠️ svg 的 width/height 从写死的 88 改成了 100%:现在框是
76 宽 × 拉伸高,写死 88 会顶出去。换真图时也照此办理。
⚠️ 里面是**内联 svg**,object-fit 对它**不生效** —— 生效的是
preserveAspectRatio(默认 xMidYMid meet = contain)。
64×64 的方图放进 76×92 的框会上下各留 8px 空条,
露出卡的橙色渐变;.wx-fig 的 background:#FFF1E6 就是来堵这个的
(取的是下面那张 <rect> 自己的颜色,所以看不出接缝)。
别改成 slice(cover)—— 那会把抽纸盒裁掉头尾。 -->
<span class="wx-fig" aria-hidden="true">
<svg viewBox="0 0 64 64" width="100%" height="100%">
<rect x="2" y="2" width="60" height="60" rx="14" fill="#FFF1E6"/>
<!-- 抽纸软包 -->
<path d="M17 22h30v26a3 3 0 0 1-3 3H20a3 3 0 0 1-3-3V22z" fill="#fff" stroke="#E8A98C" stroke-width="1.6" stroke-linejoin="round"/>
<path d="M17 22c0-3.6 3-6.4 7-6.4h16c4 0 7 2.8 7 6.4" fill="#FFE1D2" stroke="#E8A98C" stroke-width="1.6" stroke-linejoin="round"/>
<!-- 抽出来的那一张 -->
<path d="M27.5 22c0-2.6 2-4.6 4.5-4.6s4.5 2 4.5 4.6" fill="none" stroke="#D85F38" stroke-width="1.8" stroke-linecap="round"/>
<path d="M23 33h18M23 39h13" stroke="#F2C7B3" stroke-width="1.8" stroke-linecap="round"/>
<text x="32" y="59.5" text-anchor="middle" font-size="6.4" fill="#B79C90">示例图 · 待替换</text>
</svg>
</span>
<span class="wx-main">
<!-- 标题行:商品名独占一行,「限领一份」排在它**下面**。
⚠️⚠️ 本批(第八批)把这两件从横排改成竖排,方向和上一批**相反**。
上一批是「限领在商品名右侧」,可右列只有 160px,两件抢同一条宽度,
当时那笔账只余 8px —— 于是商品名被省略号啃成「可心柔婴儿纸…」。
用户这次报的就是这个现象(原话「把限领一份儿放到可心柔婴儿纸巾的
后边」,问过之后确认他看到的是被切断的商品名)。
竖排之后标题独占整行 160px,7 字 × 16 = 112,宽度争抢从根上消失。
⚠️ 别为了「省一行高度」改回 flex-direction:row —— 那等于把这个
bug 原样装回来,而且它在短商品名下不复现,测不出来。
⚠️ .wx-head 仍必须是 .wx-main 的**第一件**、排在 .wx-price 前面 ——
用户早先的原话是「商品名放到到手价上边」,两批要求叠加在一起。
⚠️ 别把「限领一份」合回 .wx-title 凑成一个字符串:合成一个字符串
就只剩一个字号,「限领」不再是商品名底下那行灰色小字,
用户要的那个视觉层级整个没了。 -->
<span class="wx-head">
<span class="wx-title" id="meWxTitle">可心柔婴儿纸巾</span>
<!-- ⚠️ 文案「限领一份」和资源库 banner 的 .rlg-lim **必须逐字相同**
(用户早先要求两张卡「一模一样」,smoke 里有一条断言钉着)。
改这句就得同时改 .rlg-lim,只改一边会当场红。
⚠️ 「(每人限领一份)」砍成「限领一份」是**第七批**被 8px 余量逼的
(那时它和商品名横排、抢同一条 160px)。第八批改竖排之后
它独占整行,8 字 × 11.5 = 92 ≤ 160,其实又放得下了。
仍然保持 4 字,理由换成「用户没要求改字」+ 两张卡必须逐字相同。
别拿「现在放得下」当作可以随手改回长文案的依据。 -->
<span class="wx-lim">限领一份</span>
</span>
<!-- 价格行 —— 这一版「电商化」的主角。
⚠️ 这里**只写到手价,不写划掉的原价**。原价是编出来的数字,
而这是一个真实品牌的真实商品:划线价属于价格宣称,
编一个就成了虚假标价。「¥0」不是新承诺,它就是这张卡
从第一版起一直写着的「立即免费领取」换个说法 ——
同一件事用电商的排版语言再说一遍,没有多承诺任何东西。
⚠️ 「免费领」这个商务承诺本身仍未获确认(第四批提出,见 CHANGELOG
的「已知不做」)。要撤这个承诺的话,撤的是整张卡,不是这一行。 -->
<span class="wx-price"><i>到手价</i><b>¥0</b></span>
<!-- 静态兜底写「宝宝的」,不写「小汤圆」:用户还没填小名时,
屏幕上不该出现一个他从没输入过的名字。填了之后
syncBabyWords() 会把这里换掉。
⚠️ 小名的挂点在这一行,不在上面的标题里,也不在下面的 CTA 里。
改 syncBabyWords() 时别找错行。 -->
<span class="wx-sub" id="meWxSub">加宝宝的孕育顾问领取</span>
</span>
</span>
<!-- CTA 现在是**通栏**的(上一版只占右列 159px)。整卡 248px 的实心按钮
是电商卡的标准收尾,也是这张卡唯一的动作。
⚠️ 必须是 <span> 不能是 <button>:外层 .wx-card 本身就是
<button>,按钮套按钮是非法 HTML。 -->
<span class="wx-cta">立即免费领取</span>
</button>
<h2 class="sec-title dark">兜爸心选商城</h2>
<button class="wx-card ripple" id="meMallCard">
<!-- 「甄选好物」角标已按需求去掉(.wx-top 角标行整行移除;孕育顾问那条"限量福利"不受影响) -->
<span class="wx-body">
<!-- 逛商城入口:购物袋图标(同 .wx-fig 76×拉伸高) -->
<span class="wx-fig" aria-hidden="true">
<svg viewBox="0 0 64 64" width="100%" height="100%">
<rect x="2" y="2" width="60" height="60" rx="14" fill="#FFF1E6"/>
<path d="M19 25h26l-2.1 23.2a3.4 3.4 0 0 1-3.4 3H25.5a3.4 3.4 0 0 1-3.4-3L19 25z" fill="#fff" stroke="#E8A98C" stroke-width="1.7" stroke-linejoin="round"/>
<path d="M26 25a6 6 0 0 1 12 0" fill="none" stroke="#D85F38" stroke-width="1.9" stroke-linecap="round"/>
<path d="M27.5 35h9M27.5 41h9" stroke="#F2C7B3" stroke-width="1.8" stroke-linecap="round"/>
</svg>
</span>
<span class="wx-main">
<span class="wx-head">
<span class="wx-title">兜爸心选商城</span>
<span class="wx-lim">母婴 · 好物</span>
</span>
<span class="wx-sub">为宝宝甄选的母婴好物,值得认真挑一挑</span>
</span>
</span>
<span class="wx-cta">进入商城</span>
</button>
<h2 class="sec-title dark">设置与服务</h2>
<div class="list-card">
<!-- 「提醒设置」整项已按需求删除 -->
<button class="li ripple" data-open="detailSetting" data-set="privacy">
<span class="li-ico p"><svg viewBox="0 0 24 24" width="19" height="19" aria-hidden="true"><rect x="4.5" y="10" width="15" height="10.5" rx="3" fill="none" stroke="currentColor" stroke-width="1.8"/><path d="M8.2 10V7.6a3.8 3.8 0 0 1 7.6 0V10" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round"/><circle cx="12" cy="15.2" r="1.5" fill="currentColor"/></svg></span>
<span class="li-t">隐私与权限</span><i class="chev">›</i></button>
<button class="li ripple" data-open="detailSetting" data-set="help">
<span class="li-ico o"><svg viewBox="0 0 24 24" width="19" height="19" aria-hidden="true"><path d="M12 4.2c4.5 0 7.8 2.8 7.8 6.4s-3.3 6.4-7.8 6.4c-.7 0-1.3-.06-1.9-.18l-3.6 2.3a.4.4 0 0 1-.6-.45l.8-2.9c-1.5-1.16-2.3-2.7-2.3-4.4 0-3.6 3.1-6.4 7.6-6.4z" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linejoin="round"/><path d="M9.9 9.4a2.2 2.2 0 0 1 4.2.8c0 1.5-2.1 1.6-2.1 3" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round"/></svg></span>
<span class="li-t">帮助与反馈</span><i class="chev">›</i></button>
<button class="li ripple" data-open="detailSetting" data-set="about">
<span class="li-ico b"><svg viewBox="0 0 24 24" width="19" height="19" aria-hidden="true"><circle cx="12" cy="12" r="8.2" fill="none" stroke="currentColor" stroke-width="1.8"/><path d="M12 11v5.4" fill="none" stroke="currentColor" stroke-width="1.9" stroke-linecap="round"/><circle cx="12" cy="7.9" r="1.15" fill="currentColor"/></svg></span>
<span class="li-t">关于妈妈盒子</span><span class="li-v">v3.2.1</span><i class="chev">›</i></button>
</div>
</div>
</section>
</main>
<!-- ================= 详情页(右滑入) ================= -->
<section class="detail" id="detailLetter">
<div class="detail-bar">
<button class="dt-back" data-detail="close">‹</button>
<!-- 兜底文案:renderLetterDetail() 会把它改写成「小汤圆来信」。
⚠️ 这个写死的值必须和 JS 那句同口径 —— 漏改会在详情页打开的那一帧
闪一下旧文案。上一批这里短暂叫过「宝宝的消息」,本批随整条线退回「来信」。 -->
<span class="dt-title" id="dlBarTitle">宝宝来信</span>
<button class="dt-star" id="dtStar">☆</button>
</div>
<div class="detail-body">
<!-- 极简信纸:整张卡就是一页白信纸,抬头和正文之间不再有色块或撕口分层。
层次全靠字号和留白拉开 —— 上面一行小字交代「什么时候写的」,
中间大标题,底下一道手绘波浪线收住,再往下才是正文。
(原来是暖色抬头块 + 圆邮戳 + 齿孔撕口 + 大引号,元素太多,
每一样都在抢正文的注意力。) -->
<article class="dl-letter">
<header class="dl-head">
<p class="dl-eyebrow">
<span id="dlWeek">孕 24 周 +3 天</span>
<i aria-hidden="true"></i>
<span id="dlDate">2024年9月3日 08:00</span>
</p>
<h1 id="dlTitle">今天我翻了个身</h1>
<!-- 手绘波浪线:一条 path 走三段 S 曲线,比直线多一点手写的松弛感 -->
<svg class="dl-wave" viewBox="0 0 120 9" aria-hidden="true" preserveAspectRatio="none">
<path d="M2 5.6C13 1.9 24 8.4 35 5.1S57 1.6 68 5.4 90 8.6 102 4.6c5-1.7 10-2.2 16-1.4"
fill="none" stroke="#F3A88C" stroke-width="2.3" stroke-linecap="round"/>
</svg>
<!-- 带语音的信,语音条就紧跟在标题下面,不再甩到正文后面 -->
<div id="dlVoice"></div>
</header>
<div class="dl-body">
<div class="dl-text" id="dlText"></div>
</div>
</article>
<!-- 这封信下面的往来:妈妈的回信 + 宝宝的再回复,全部就地展开 -->
<div class="dl-thread" id="dlThread"></div>
<!-- 两颗并排:生成图片、回信。
这里原来还有一颗收藏键,撤了 —— 右上角 #dtStar 已经是这封信的收藏开关,
同一个页面里两个入口管同一件事,状态还得两头同步,没有好处。 -->
<div class="dl-actions">
<!-- ⚠️ 这颗原来是只有图标的 52×52 方钮(一个「卡片飞出去」的转发符号),
没人看得懂那是干什么的 —— 转发这个动作在不同 app 里图标各画各的,
脱离了熟悉的语境就是一个纯抽象图形。改成图标 + 「生成图片」四个字,
说的是这颗键真正做的事(做一张图),不是它背后的技术动作(分享)。
图标同步换成「画框里一张图」,和分享卡面板顶上那枚 .pst-ico 是同一个。 -->
<button class="dl-btn ghost ico-txt has-ico" id="dlCard">
<svg class="btn-ico" viewBox="0 0 24 24" width="17" height="17" aria-hidden="true">
<path d="M4.6 6.4h14.8v11.2H4.6z" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linejoin="round"/>
<path d="M4.6 14.4l3.6-3.1 3.2 2.7 2.7-2.2 4.3 3.7" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linejoin="round"/>
<circle cx="9" cy="9.7" r="1.2" fill="none" stroke="currentColor" stroke-width="1.5"/>
</svg>
<span>生成图片</span>
</button>
<!-- 原来是 ✍️ emoji(各系统都渲染成一只带阴影的实体小手),换成和站内其它
图标同一套的线性扁平笔。文字单独包一层 #dlReplyTxt:syncBabyWords()
要按小名改这行字,继续写 button.textContent 会把 svg 一起抹掉 -->
<button class="dl-btn main has-ico" id="dlReply">
<svg class="btn-ico" viewBox="0 0 24 24" width="17" height="17" aria-hidden="true">
<path d="M16.4 3.8a2.2 2.2 0 0 1 3.1 3.1L9.2 17.2l-4.1 1 1-4.1z" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linejoin="round"/>
<path d="M14.7 5.5l3.1 3.1" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round"/>
</svg>
<span id="dlReplyTxt">给宝宝回信</span>
</button>
</div>
</div>
</section>
<!-- ================= AI 小结 内容页 ================= -->
<section class="detail" id="detailAI">
<div class="detail-bar">
<button class="dt-back" data-detail="close">‹</button>
<span class="dt-title">AI 孕育小结</span>
<button class="dt-plain" data-toast="小结每天 21:00 自动生成">⋯</button>
</div>
<!-- 这一页原来是「大卡 + 三条 li + 一串行」,从上到下全是罗列,没有层次。
重做成三段式:今日卡(深色,一眼看完今天)→ 三张建议卡(分类色 + 一句标题
一句正文)→ 往期时间轴(左边一条线串起来)。每段之间用带序号的小标题隔开。 -->
<div class="detail-body ai-body">
<!-- ① 今日卡:整页唯一的深色块,越往下越浅,视线自然从这里开始 -->
<section class="ai-today">
<span class="ai-glow" aria-hidden="true"></span>
<span class="ai-glow b" aria-hidden="true"></span>
<div class="ai-t-top">
<span class="ai-week" id="aiWeek">孕24周+3</span>
<span class="ai-date" id="aiDate">9月3日 周二</span>
</div>
<!-- 一句话概括今天,字号最大;右上角那枚小圆点是「今日状态」 -->
<span class="ai-mood" id="aiMood">安心</span>
<!-- renderAI() 会用 AI_LOGS[0].txt 覆盖这句,这里写的是渲染前的兜底。
口吻同样是宝宝第一人称,别让它在渲染前闪一句第三人称的旧话 -->
<p class="ai-hero-txt" id="aiTxt">妈妈,今天你数到我动了 3 次。我今天精神特别好,这是踏实的一天。</p>
<div class="ai-stats" id="aiStats"></div>
</section>
<div class="ai-sec">
<span class="ai-sec-n">01</span>
<!-- 原来叫「今天想对你说」。这三张卡的内容是「固定这两个时段陪他待一会儿」
「按平时胃口来就行」这类可执行的动作,是建议不是抒情,标题得对上 -->
<h3>今日小建议</h3>
<i class="ai-sec-line"></i>
</div>
<div class="ai-tips" id="aiTips"></div>
<div class="ai-sec">
<span class="ai-sec-n">02</span>
<h3>往期小结</h3>
<i class="ai-sec-line"></i>
<button class="sec-more" data-toast="已加载全部 30 天小结">近 30 天 ›</button>
</div>
<div class="ai-logs" id="aiLogs"></div>
<p class="unlock">小结由 AI 依据你的记录生成,是陪你一起看记录的伙伴,不代替医生的判断。</p>
</div>
</section>
<!-- ================= 资源库 列表页 ================= -->
<section class="detail" id="detailResList">
<div class="detail-bar">
<button class="dt-back" data-detail="close">‹</button>
<span class="dt-title">孕育资源库</span>
<!-- 原来是 🔍 emoji(各系统渲染成实物放大镜),换成和档案页一致的扁平线性图标 -->
<button class="dt-plain" data-search="1" aria-label="搜索"><svg viewBox="0 0 24 24" width="20" height="20"><circle cx="11" cy="11" r="6.5" fill="none" stroke="currentColor" stroke-width="1.9"/><path d="M16 16l4.5 4.5" stroke="currentColor" stroke-width="1.9" stroke-linecap="round"/></svg></button>
</div>
<div class="detail-body">
<!-- ================= 试用装福利 banner =================
排在最前面(在那句 rl-lead 之前):它是这一页最想被看见的东西。
⚠️ 和服务卡同样的理由,故意**不挂 data-open / data-modal**。
那条通用通道直接调 openModal,会绕过 openWx() —— 模式类名、
引导语、上次残留的红字和输入内容、还有 wxPending 都不会被重置。
⚠️ 里面这张示意图**绝不能带 .wx-qr 类**。冒烟测试有一条钉着
「全页只有一处二维码」,蹭了那个类名会当场变红 —— 而且它也确实
不是二维码。用独立的 .rlg-fig。
⚠️ 已解锁的用户看不到这条 banner,由 syncUnlockCopy() 挂 .hide 完成。
服务卡则**不**跟着藏,理由写在服务卡那段注释里。
换真图:把 .rlg-fig 整个 <svg> 换成一个 img,类名照旧写 rlg-fig,
CSS 一行不用动(.rlg-fig 同时管 svg 和 img)。
⚠️ 这里**不要**写出图片路径的字面量 —— build.js 的 ASSET_RE
连 HTML 注释一起扫,写了就当场报「缺少资源文件」。 -->
<button class="rl-gift ripple" id="rlGift">
<!-- ⚠️ 这条 banner 和「我的」页那张 .wx-card 是**同一件商品的同一张卡**,
所以这一版跟着改成同样的三段竖排:标题行 / 图文行 / 通栏 CTA。
用户上一批的原话是两处「一模一样」;只改一边,同一个商品在两个
页面上又会长成两个样子 —— 上一版商品名写岔(那边「可心柔试用装」、
这边「免费领可心柔试用装」)就是这么来的。
⚠️ 但**比例刻意比那边小一号**(图 68×60 不跟着放大到 88、字号各降一档)。
这不是偷懒:那边是「我的」页上的一张独立卡,四周是空的;
这条 banner 上面是标题栏、下面紧跟着 128 条资源列表,
照搬 88px 的图 + 22px 的价格会把整条撑到 200px 上下,
把真正的资源列表整个顶到首屏外 —— banner 抢了主内容的位置。
⚠️ 宽度账(320 屏):320 − 页边 44(.detail-body 左右各 22)
− 卡内边 28 = 248,和那边一样宽。
本批标题行改成**竖排**(跟 .wx-card 同步),所以「标题和限领抢
同一行」那笔账整个不存在了:
商品名独占一行:「可心柔婴儿纸巾」7 字 × 16 = 112 ≤ 248 ✓ 余 136px
「限领一份」另起一行:4 字 × 11.5 = 46 ✓
⚠️ 这边**本来就排得下**(上一版横排是 7×15 + gap 6 + 4×11.5 = 157
≤ 248,余 91px)。跟着改成竖排纯粹是为了两张卡保持同构 ——
真正被挤爆的是「我的」页那张:它的右列只有 160px。
所以别拿「这边明明放得下」当理由把这边改回横排,
那只会让同一件商品在两个页面上又长成两个样子。 -->
<span class="rlg-head">
<!-- 标题和「我的」页 #meWxTitle 保持**同一个字符串**「可心柔婴儿纸巾」。
测试里有一条拿同一个变量同时比对两处,写岔了会红。
⚠️ 和 .wx-title 一样:标题只放商品名,不许把括号那半句并进来。
.rlg-title 是 nowrap + ellipsis,并进来 16 字 ≈ 264px > 248,
会被省略号从「(」处齐根切掉,而 textContent 照样是全的。 -->
<span class="rlg-title">可心柔婴儿纸巾</span>
<!-- ⚠️ 「限领一份」只是文案,前端**不记状态**:真正的把关在顾问
那边人工做。前端记了也挡不住换设备/清缓存,不假装能管住。
别看到这行就以为漏了个 localStorage。
⚠️ 「(每人限领一份)」砍成「限领一份」是**第七批**的事,
**这边一直排得下** —— 砍它纯粹是为了和「我的」页 .wx-lim
保持逐字相同(用户早先要求两张卡「一模一样」,smoke 有断言钉着)。
当时被宽度逼的是那边:标题行横排、右列 160px、余量只剩 8px。
⚠️ 第八批那边也改成竖排了,8px 那笔账已经不存在,两边**都**放得下。
仍保持 4 字是因为用户没要求改字,不是因为放不下。
这两句必须**一起改**,只改一边会当场红。 -->
<span class="rlg-lim">限领一份</span>
</span>
<span class="rlg-body">
<!-- 「示例图 · 待替换」这几个字写在 svg **内部**:换成真图时它跟着
一起消失,不会剩一句「待替换」孤零零挂在真图旁边。 -->
<svg class="rlg-fig" viewBox="0 0 68 60" role="img" aria-label="示例图,待替换为可心柔婴儿纸巾实物图">
<rect x="6" y="14" width="56" height="34" rx="6" fill="#FFD9C6"/>
<rect x="6" y="14" width="56" height="12" rx="6" fill="#FFC3A8"/>
<path d="M20 26c0-5 4-9 9-9h10c5 0 9 4 9 9" fill="none" stroke="#E8724C" stroke-width="2.4"/>
<path d="M26 33h16M26 39h11" stroke="#E8724C" stroke-width="2.4" stroke-linecap="round"/>
<text x="34" y="57" text-anchor="middle" font-size="7" fill="#B0907F">示例图 · 待替换</text>
</svg>
<span class="rlg-main">
<!-- ⚠️ 角标从卡外边缘(position:absolute;top:-7px)挪进了右列顶部。
原因和「我的」页那张卡一样:.rl-gift 挂着 .ripple,
.ripple 的 overflow:hidden 把负偏移的角标裁掉一半,
「福利」两个字只露下半截。这是本项目里**最早**的那处裁切。
别改回绝对定位。 -->
<span class="rlg-badge">福利</span>
<!-- 价格行,和「我的」页 .wx-price 同一件事、同样只写到手价。
⚠️ 一样**不编划线价**:可心柔是真实品牌,划线价属于价格宣称。
「¥0」只是把这张卡一直写着的「立即免费领取」换个说法。 -->
<span class="rlg-price"><i>到手价</i><b>¥0</b></span>
<!-- ⚠️ 这行原来是写死的静态文本,没有小名。用户要求两处「一模一样」,
所以给了 id 并纳入 syncBabyWords() 一起刷 —— 否则改完小名,
「我的」页说「加小汤圆的孕育顾问领取」、这里说「加顾问领取」,
同一句话又变成两个说法。静态兜底同样写「宝宝的」。 -->
<span class="rlg-sub" id="rlgSub">加宝宝的孕育顾问领取</span>
</span>
</span>
<!-- 实心 CTA,现在是 .rl-gift 的直接子元素、通栏 248px
(上一版收在右列里只有 170px)。
原来这里只有一个 .rlg-chev「›」,尖括号在这一页的语义是
「还有下一层可以翻」,而这条 banner 是要人**领东西**。
⚠️ 「›」已删除:一张卡上同时摆按钮和尖括号 = 两个「点我」信号打架。
⚠️ 必须是 <span>:外层 .rl-gift 本身是 <button>,按钮套按钮非法。 -->
<span class="rlg-cta">立即免费领取</span>
</button>
<!-- 静态值写「还没解锁」那一版,解锁过的用户由 syncUnlockCopy() 改口。
「首次领取需添加顾问微信」这句必须提前说:让人一路读到底、点了
「立即领取」才撞上二维码,是把条件藏到最后一步才亮出来。 -->
<p class="rl-lead" id="rlLead"><b>128</b> 份资源按孕周持续更新 · 首次领取需添加顾问微信</p>
<div class="chips-row" id="resChips">
<button class="chip is-active" data-rescat="all">全部</button>
<button class="chip" data-rescat="产检">产检</button>
<button class="chip" data-rescat="营养">营养</button>
<button class="chip" data-rescat="胎教">胎教</button>
<button class="chip" data-rescat="运动">运动</button>
<button class="chip" data-rescat="心理">心理</button>
<!-- 收藏不是分类而是筛选,所以默认排在最后。但一旦用户有了收藏,
renderResList() 会把它挪到「全部」后面并带上数量 —— 常用的东西
不该让人横向滑到头才找得着。这里写的是「还没收藏过」时的样子 -->
<button class="chip chip-fav" data-rescat="fav">★ 收藏</button>
<!-- 「已领」同理,也是筛选不是分类。领过东西之后它会被挪到「全部」后面
(比收藏还靠前 —— 回来找提取码比回来翻收藏更常发生) -->
<button class="chip chip-got" data-rescat="got">✓ 已领</button>
</div>
<div class="res-list" id="resList"></div>
<p class="unlock" id="resEmptyHint"></p>
</div>
</section>
<!-- ================= 资源 内容页 ================= -->
<section class="detail" id="detailRes">
<div class="detail-bar">
<button class="dt-back" data-detail="close">‹</button>
<span class="dt-title">资源详情</span>
<button class="dt-star" id="resStar">☆</button>
</div>
<div class="detail-body">
<div class="rd-cover" id="rdCover"><img id="rdImg" src="assets/img/9f3fa8edf4aa.png" alt=""></div>
<span class="rd-cat" id="rdCat">产检</span>
<h1 class="rd-title" id="rdTitle">《糖耐检查全攻略》</h1>
<p class="rd-desc" id="rdDesc">孕24-28周必看 · 附检查前饮食清单</p>
<div class="rd-meta" id="rdMeta"></div>
<div class="rd-body" id="rdBody"></div>
<h3 class="d-sec">相关推荐</h3>
<div class="res-list slim" id="rdMore"></div>
</div>
<!-- 其他资源 / 立即领取吸底浮动:移出 .detail-body,怎么滑都跟着。
左边这颗原来是「★ 收藏」,和右上角星星功能完全重复(它就是转发点击给星星)。
收藏保留在星星上,这个位置让给「去看看别的」这个真正缺的出口。
文案用「其他资源」而不是「资源库」:用户此刻就在一份资源里,
「其他」说的是和手上这份的关系,比页面名更能说清点下去会看到什么。 -->
<div class="rd-cta" id="rdCta">
<button class="dl-btn ghost" id="rdLib">其他资源</button>
<button class="dl-btn main" id="rdGet">立即领取</button>
</div>
</section>
<!-- ================= 档案图片 预览页 =================
周档案 / 完整档案共用这一个壳。图是进来时用 canvas 现画的,
画完顺手存成 data URI —— 「保存图片」直接下载它,
「分享朋友圈」把它交给系统分享面板(不支持就提示长按图片),
「打印」走 window.print(),样式表里 @media print 只留这张图。 -->
<section class="detail" id="detailPoster">
<div class="detail-bar">
<button class="dt-back" data-detail="close">‹</button>
<span class="dt-title" id="pstBar">本周档案</span>
</div>
<div class="detail-body">
<!-- 抬头卡:图标 + 档案名 + 一句说明,比孤零零一行灰字有分量 -->
<div class="pst-hero">
<span class="pst-ico" aria-hidden="true">
<svg viewBox="0 0 24 24" width="24" height="24">
<path d="M7 3.5h7.5L19 8v12.5H7z" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linejoin="round"/>
<path d="M14 3.5V8h5" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linejoin="round"/>
<path d="M10 12h6M10 15.5h6M10 19h3.5" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round"/>
</svg>
</span>
<div class="pst-h-txt">
<h2 class="pst-h-title" id="pstName">本周档案</h2>
<p class="pst-lead" id="pstLead">这一周的记录已经排好版了。</p>
</div>
</div>
<!-- 这张图里都装了什么,用几粒胶囊先交代清楚 -->
<div class="pst-chips" id="pstChips"></div>
<!-- 由 JS 往里塞 N 个 .pst-frame(白卡纸 + 一道虚线裁切位,像待打印的样张)。
本周档案是一张长图,完整档案是一页一张 A4 —— 屏幕上就按纸的样子一张张往下排。
画布都按 2 倍图给(750 宽),按 375 显示在手机上不糊。 -->
<div class="pst-pages" id="pstPages"></div>
<p class="pst-tip" id="pstTip">图片已生成。在微信里也可以长按上面这张图,直接保存或转发。</p>
</div>
<div class="rd-cta pst-cta" id="pstCta">
<!-- data-export 是委托用的:来信分享卡那个面板底下也是这三颗,
两边共用 app.js 里同一段保存/分享/打印逻辑。id 还留着,
renderPoster() 要按「本周/完整」换这两颗的主次样式。 -->
<button class="dl-btn ghost" id="pstPrint" data-export="print">打印</button>
<button class="dl-btn ghost" id="pstSave" data-export="save">保存图片</button>
<button class="dl-btn main" id="pstShare" data-export="share">分享朋友圈</button>
</div>
</section>
<!-- ================= 来信分享卡 =================
把单独一封信做成一张能发朋友圈的图。和上面的档案海报是两个壳,不是一个壳两种模式 ——
档案那边的 posterData() 里写死了预产期、体重、睡眠这些东西,
共用一个渲染器就等于把「哪些字段不能上卡」交给一串 if 去把关,迟早漏。
这边的 cardData() 是白名单,从源头上只挑得出小名 / 孕周 / 月日 / 信的正文。
没有「打印」那颗:这张卡是发出去的,不是印出来的。
所以 @media print 那段照旧只认 #detailPoster,不用动。 -->
<section class="detail" id="detailCard">
<div class="detail-bar">
<button class="dt-back" data-detail="close">‹</button>
<span class="dt-title" id="cdBar">分享卡</span>
</div>
<div class="detail-body">
<div class="pst-hero">
<span class="pst-ico" aria-hidden="true">
<svg viewBox="0 0 24 24" width="24" height="24">
<path d="M4.5 6.5h15v13h-15z" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linejoin="round"/>
<path d="M4.5 15.5l4-3.5 3.5 3 3-2.5 4.5 4" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linejoin="round"/>
<circle cx="9.2" cy="10.2" r="1.3" fill="none" stroke="currentColor" stroke-width="1.6"/>
</svg>
</span>
<div class="pst-h-txt">
<h2 class="pst-h-title" id="cdName">这封信的分享卡</h2>
<p class="pst-lead" id="cdLead">生成一张可以直接发出去的图。</p>
</div>
</div>
<!-- 三颗样式胶囊:信纸 / 成长足迹 / 比大小。空的,由 js 里的 CARD_TPL 注册表
建出来 —— 样式表和 DOM 只留一处真相,加第四套只改数组不改这里。
这排下面原来还有一排「说明卡上印了什么」的信息胶囊(小名/孕周/日期/成图),
那排撤了没有恢复:两排长得差不多的标签叠在一起,面板就成了标签墙。
卡上有什么,由 #cdLead 一句话说清楚。 -->
<div class="pst-tpl" id="cdTpl" role="tablist" aria-label="分享卡样式"></div>
<div class="pst-pages" id="cdPages"></div>
<p class="pst-tip" id="cdTip"></p>
</div>
<div class="rd-cta pst-cta">
<!-- 复用 #detailPoster 那三颗按钮背后的同一段逻辑,靠 data-export 委托 -->
<button class="dl-btn ghost" data-export="save">保存图片</button>
<button class="dl-btn main" data-export="share">分享朋友圈</button>
</div>
</section>
<!-- ================= 设置与服务 二级页 =================
隐私与权限 / 帮助与反馈 / 关于妈妈盒子 三项共用这一个壳,
内容由 js 里的 SETTINGS 表渲染进 #stBody,避免写四份几乎一样的结构 -->
<section class="detail" id="detailSetting">
<div class="detail-bar">
<button class="dt-back" data-detail="close">‹</button>
<span class="dt-title" id="stTitle">隐私与权限</span>
<button class="dt-plain" id="stMore" aria-label="更多">⋯</button>
</div>
<div class="detail-body">
<div id="stHero"></div>
<p class="st-lead" id="stLead"></p>
<div id="stBody"></div>
<p class="unlock" id="stFoot"></p>
</div>
</section>
<!-- ================= 记一下 底部弹层 ================= -->
<div class="scrim" id="scrim"></div>
<section class="sheet" id="sheet">
<div class="sheet-grip" id="sheetGrip"><span></span></div>
<div class="sheet-head">
<div>
<h2>记一下</h2>
<p><span id="noteWhen">—</span> · 今天已记 <b id="todayCount">4</b> 条</p>
</div>
<button class="sheet-close" data-sheet="close">✕</button>
</div>
<div class="sheet-scroll">
<div class="apps-head first">今天已使用 <button class="sec-more" data-toast="点一下可直接补记">点一下直接记录 ›</button></div>
<div class="quick-row">
<button class="quick q1 ripple" id="quickKick"><img class="q-ico" src="assets/img/df905dcf3a1c.png" alt="" aria-hidden="true"><span class="q-t">数胎动</span><span class="q-v">+<b id="kickCount">1</b> 次</span></button>
<button class="quick q2 ripple" data-toast="已记录体重 58.2kg"><img class="q-ico" src="assets/img/a9e3b27a4a23.png" alt="" aria-hidden="true"><span class="q-t">孕期体重</span><span class="q-v">58.2kg</span></button>
<button class="quick q3 ripple" data-toast="开始测量胎心……"><img class="q-ico" src="assets/img/30de43bbc9aa.png" alt="" aria-hidden="true"><span class="q-t">胎心记录</span><span class="q-v">待记录</span></button>
<!-- 「今日心情」已按需求删除:记一下里只放工具,不放心情打卡。
删掉后 .quick-row 由 4 列改成 3 列,见 style.css -->
</div>
<div class="apps-head for-all">全部孕期工具 <button class="sec-more" data-toast="共 16 个孕期工具">共 16 个 ›</button></div>
<div class="apps-grid" id="appsGrid">
<button class="app ripple"><img class="a-ico" src="assets/img/505ace532176.png" alt="" aria-hidden="true">数胎动</button>
<button class="app ripple"><img class="a-ico" src="assets/img/8b0f0a53938b.png" alt="" aria-hidden="true">孕期体重</button>
<button class="app ripple"><img class="a-ico" src="assets/img/9a2e7fa472dc.png" alt="" aria-hidden="true">孕检日历</button>
<button class="app ripple"><img class="a-ico" src="assets/img/f989a64fe960.png" alt="" aria-hidden="true">生长曲线</button>
<button class="app ripple"><img class="a-ico" src="assets/img/fd875eb78785.png" alt="" aria-hidden="true">胎心记录</button>
<button class="app ripple"><img class="a-ico" src="assets/img/97c33531806b.png" alt="" aria-hidden="true">孕肚照</button>
<button class="app ripple"><img class="a-ico" src="assets/img/de0bc9e250f2.png" alt="" aria-hidden="true">B超影像</button>
<button class="app ripple"><img class="a-ico" src="assets/img/b855b45df1f3.png" alt="" aria-hidden="true">产检报告</button>
<button class="app ripple"><img class="a-ico" src="assets/img/c2ae6e98283b.png" alt="" aria-hidden="true">血压血糖</button>
<button class="app ripple"><img class="a-ico" src="assets/img/4657f50f42c8.png" alt="" aria-hidden="true">用药记录</button>
<button class="app ripple"><img class="a-ico" src="assets/img/72910df66ee3.png" alt="" aria-hidden="true">运动记录</button>
<button class="app ripple"><img class="a-ico" src="assets/img/dd5639fde61d.png" alt="" aria-hidden="true">饮食记录</button>
<button class="app ripple"><img class="a-ico" src="assets/img/f0d5e062f11a.png" alt="" aria-hidden="true">睡眠记录</button>
<button class="app ripple"><img class="a-ico" src="assets/img/056212807d86.png" alt="" aria-hidden="true">胎教音乐</button>
<button class="app ripple"><img class="a-ico" src="assets/img/7b7d81e02f42.png" alt="" aria-hidden="true">妈妈手记</button>
<button class="app ripple"><img class="a-ico" src="assets/img/f34fb7fdeea5.png" alt="" aria-hidden="true">更多工具</button>
</div>
</div>
</section>
<!-- ================= 搜索层 ================= -->
<section class="search-layer" id="searchLayer">
<div class="sl-bar">
<!-- ⚠️ 占位符里列的是三个可搜的东西,其中一个就是底 tab 那一栏。
tab 叫什么这里就得写什么,否则搜索框在教用户去搜一个界面上不存在的词。
上一批 tab 改叫「消息」时这里跟着改过,本批退回「来信」。等字数替换,宽度不用重算。
⚠️ 这句是 placeholder **属性**,不是文字节点 —— 只扫 textContent 的检查
抓不到它,上一批就是这么漏的。改口时别忘了它。 -->
<div class="sl-input"><span>🔍</span><input id="searchInput" placeholder="搜索档案、来信、记录……"></div>
<button class="sl-cancel" data-search="close">取消</button>
</div>
<div class="sl-body">
<div class="sl-sec">最近搜索</div>
<div class="chips-row wrap">
<button class="chip">第一次胎动</button><button class="chip">大排畸</button><button class="chip">体重</button><button class="chip">胎心</button><button class="chip">宝宝来信</button>
</div>
<div class="sl-sec">大家都在搜</div>
<div class="sl-hot">
<button class="hot"><i>1</i>糖耐检查怎么准备</button>
<button class="hot"><i>2</i>孕<span data-wk="n">—</span>周胎动多少正常</button>
<button class="hot"><i>3</i>孕中期体重增长标准</button>
<button class="hot"><i>4</i>数胎动的正确方法</button>
</div>
</div>
</section>
<!-- 同上:来信页这颗悬浮按钮上的 ✍️ 也换成同一枚扁平笔,两处别一个实体一个线性 -->
<button class="fab-write ripple" id="fabWrite">
<svg class="btn-ico" viewBox="0 0 24 24" width="17" height="17" aria-hidden="true">
<path d="M16.4 3.8a2.2 2.2 0 0 1 3.1 3.1L9.2 17.2l-4.1 1 1-4.1z" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linejoin="round"/>
<path d="M14.7 5.5l3.1 3.1" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round"/>
</svg>
<span>写回信</span>
</button>
<!-- ================= 通用弹窗(回信 / 孕期信息) ================= -->
<div class="modal-scrim" id="modalScrim"></div>
<!-- ================= 往期小结 =================
原来点往期列表是把 AI 小结页顶上的今日卡整个换掉,看完想回到「今天」没有出口。
改成在原页上浮一层:内容只读,看完点底下的叉就走。
叉放在最下面而不是右上角 —— 这一屏是一路往下读完的,
读完手指就停在底部,再让人抬回右上角去关不合理。 -->
<section class="modal" id="modalAiLog" role="dialog" aria-modal="true" aria-labelledby="alogTitle">
<div class="alog-head">
<span class="alog-week" id="alogWeek">孕24周+2</span>
<span class="alog-mood" id="alogMood">安心</span>
</div>
<h3 class="alog-date" id="alogTitle">9月2日 周一</h3>
<p class="alog-txt" id="alogTxt"></p>
<!-- 只留这组数(胎动 / 体重 / 睡眠)—— 这是当天从小程序里真取到的记录。
下面本来还跟着一组 1/2/3 的建议卡(.alog-tips),删了:
那是 AI 当天现写的话,隔了几天再翻出来看已经没有用了,
而且它和上面那句 alogTxt 说的是同一件事,等于把结论又铺开讲三遍。
往期该回答的只有一个问题——那天的数是多少。建议留在「今日小建议」里。 -->
<div class="ai-stats alog-stats" id="alogStats"></div>
<button class="modal-x modal-x-btm" data-modal="close" aria-label="关闭">
<svg viewBox="0 0 24 24" width="16" height="16" aria-hidden="true">
<path d="M6 6l12 12M18 6L6 18" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round"/>
</svg>
</button>
</section>
<section class="modal" id="modalReply" role="dialog" aria-modal="true" aria-labelledby="modalReplyTitle">
<div class="modal-head">
<h3 id="modalReplyTitle">给宝宝回信</h3>
<button class="modal-x" data-modal="close" aria-label="关闭">
<svg viewBox="0 0 24 24" width="15" height="15" aria-hidden="true">
<path d="M6 6l12 12M18 6L6 18" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round"/>
</svg>
</button>
</div>
<!-- 明确标出这封回信是回给哪封来信的,回信也会挂到那封信下面 -->
<p class="modal-lead">回给《<b id="replyOn">今天我翻了个身</b>》 —— 写点什么给 <b id="replyTo">小汤圆</b> 吧。</p>
<textarea class="modal-ta" id="replyText" rows="6" maxlength="500" placeholder="宝宝,妈妈今天……"></textarea>
<div class="modal-tags">
<button class="mtag" data-fill="宝宝,今天妈妈感受到你踢了好多下,是不是在跟妈妈打招呼呀?">今天的胎动</button>
<button class="mtag" data-fill="宝宝,妈妈今天去做了产检,医生说你长得很好,妈妈就放心啦。">产检记录</button>
<button class="mtag" data-fill="宝宝,妈妈想对你说:谢谢你选择妈妈,我们一起慢慢长大。">悄悄话</button>
</div>
<div class="modal-foot">
<span class="modal-count"><b id="replyCount">0</b>/500</span>
<button class="dl-btn ghost" data-modal="close">取消</button>
<button class="dl-btn main" id="replySend">寄给宝宝</button>
</div>
</section>
<!-- ================= 百度网盘领取 =================
资源本体放在网盘上,详情页只负责呈现内容;点「立即领取」才把
链接和提取码给出来。链接与提取码在 js 顶部的 PAN 表里配置
(后台改那一处就行,页面不用动)。 -->
<section class="modal" id="modalPan" role="dialog" aria-modal="true" aria-labelledby="modalPanTitle">
<div class="modal-head">
<h3 id="modalPanTitle">资源已为你准备好</h3>
<button class="modal-x" data-modal="close" aria-label="关闭">
<svg viewBox="0 0 24 24" width="15" height="15" aria-hidden="true">
<path d="M6 6l12 12M18 6L6 18" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round"/>
</svg>
</button>
</div>
<p class="modal-lead">《<b id="panTitle">糖耐检查全攻略</b>》已上传到百度网盘,复制下面的链接到浏览器或网盘 App 打开即可。</p>
<div class="pan-box">
<div class="pan-row">
<span class="pan-k">网盘链接</span>
<!-- readonly 而不是纯文本:iOS 上长按可直接「全选 / 拷贝」 -->
<input class="pan-v" id="panUrl" type="text" readonly value="">
</div>
<div class="pan-row">
<span class="pan-k">提取码</span>
<input class="pan-v code" id="panCode" type="text" readonly value="">
</div>
</div>
<p class="pan-note">链接长期有效。若提示「链接已失效」,可在「帮助与反馈」里联系我们补发。</p>
<div class="modal-foot">
<button class="dl-btn ghost" id="panCopy">复制链接和提取码</button>
<button class="dl-btn main" id="panOpen">打开网盘</button>
</div>
</section>
<!-- ================= 加企业微信顾问 =================
三个入口共用这一个壳,模式由 openWx() 挂类名决定:
gift —— 「我的」页服务卡 + 资源库顶部那条试用装 banner。
**没有兑换码输入框**:这两处主打的是免费领试用装,
顾问会把资料兑换码连同试用装一起发给用户,码在闸门那儿填。
gate —— 点「立即领取」被拦下时。**只有这里有输入框**。
done —— 已解锁的人再从 gift 那两个入口进来。
⚠️ 静态类写 is-gift(原来是 is-card)。默认态因此从「显示输入框」
变成「不显示」—— 万一有人绕过 openWx() 直接 openModal('modalWx'),
他看到的是一张二维码,而不是一个怎么填都不对的码框。
⚠️ data-autofocus="off":这个弹窗的主角是中间那张二维码,用户第一件事
是举起另一台手机去扫它。默认的自动聚焦会把输入法键盘顶起来,
正好盖住二维码下半截 —— 人得先把键盘收掉才能扫。 -->
<section class="modal wx-modal is-gift" id="modalWx" data-autofocus="off"
role="dialog" aria-modal="true" aria-labelledby="wxTitle">
<div class="modal-head">
<!-- 兜底写「宝宝的」;填过小名的由 syncBabyWords() 刷成小名。
⚠️ 这行藏在弹窗里,改完小名不点开就看不见 —— 属于肉眼巡查
永远扫不到的挂点,只能靠 smoke 里那条断言守着。 -->
<h3 id="wxTitle">宝宝的孕育顾问</h3>
<button class="modal-x" data-modal="close" aria-label="关闭">
<svg viewBox="0 0 24 24" width="15" height="15" aria-hidden="true">
<path d="M6 6l12 12M18 6L6 18" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round"/>
</svg>
</button>
</div>
<!-- 静态兜底写 gift 那一版(和上面的静态类 is-gift 配套):
运行时三种模式的文案都由 openWx() 改写,这里只保证「万一没走 openWx」
时读到的是一句说得通的话,而不是一句要人填码、却没有码框的话。 -->
<p class="modal-lead" id="wxLead">扫码添加顾问,说一句「领试用装」就帮你登记,每人限领一份。她也会把资源库的兑换码一并发给你 —— 到资源库点「立即领取」时填进去。</p>
<div class="wx-qr-box">
<!-- ⚠️ 这是**占位**二维码,扫不出任何东西。
换真码的步骤:把真图存成 assets 目录下的 qr-wecom.png,
再把下面整个 <svg> 换成一个 <img class="wx-qr" alt="企业微信顾问二维码">
并让它的 src 指向那张图。CSS 一个字都不用动 —— .wx-qr 同时管 svg 和 img。
(这里为什么不直接把那行标记抄给你:build.js 会扫全文里所有
"assets/文件名" 形式的引用并要求文件真实存在,写在 HTML 注释里
也照样算数。图还没放进去就先把示范代码写在这儿,构建会当场失败,
报「缺少资源文件:qr-wecom.png」。这个检查是对的,别为了少打几个字去动它。)
「示例二维码 · 待替换」那行字是写在 svg **内部**的,跟着一起被换掉。
写在外面的话,换上真码之后页面上会剩一句「待替换」挂在真二维码底下,
而那时候链接已经发出去了。 -->
<svg class="wx-qr is-stub" viewBox="0 0 100 100" role="img" aria-label="示例二维码,待替换为真实企业微信二维码">
<rect width="100" height="100" rx="8" fill="#F3ECE6"/>
<g fill="#C9BCB2">
<rect x="12" y="12" width="22" height="22" rx="4"/><rect x="66" y="12" width="22" height="22" rx="4"/>
<rect x="12" y="66" width="22" height="22" rx="4"/>
<rect x="42" y="14" width="6" height="6"/><rect x="52" y="14" width="6" height="6"/>
<rect x="42" y="24" width="6" height="6"/><rect x="14" y="42" width="6" height="6"/>
<rect x="24" y="42" width="6" height="6"/><rect x="42" y="42" width="6" height="6"/>
<rect x="52" y="52" width="6" height="6"/><rect x="66" y="42" width="6" height="6"/>
<rect x="76" y="52" width="6" height="6"/><rect x="42" y="66" width="6" height="6"/>
<rect x="52" y="76" width="6" height="6"/><rect x="66" y="66" width="6" height="6"/>
<rect x="76" y="76" width="6" height="6"/>
</g>
<rect x="17" y="17" width="12" height="12" rx="2" fill="#F3ECE6"/>
<rect x="71" y="17" width="12" height="12" rx="2" fill="#F3ECE6"/>
<rect x="17" y="71" width="12" height="12" rx="2" fill="#F3ECE6"/>
<rect x="14" y="45" width="72" height="14" rx="4" fill="rgba(243,236,230,.94)"/>
<text x="50" y="54.6" text-anchor="middle" font-size="8.4" font-weight="700" fill="#9A8B80">示例二维码 · 待替换</text>
</svg>
<p class="wx-qr-cap">长按识别,或截图后用微信扫一扫</p>
</div>
<div class="wx-form">
<!-- ⚠️ placeholder 里绝不能写出真码("例如 MAMA2025" 这种)。
这道闸门换的就是「用户去加一次顾问」,把答案印在输入框里,
等于门开着、旁边立个牌子写「请敲门」。 -->
<label class="fld"><span>顾问发给你的兑换码</span>
<input class="wx-code" id="wxCode" type="text" maxlength="16"
autocomplete="off" autocapitalize="characters" spellcheck="false"
placeholder="添加顾问后向她索取"></label>
<!-- 空着也占一行高(CSS 里的 min-height):红字冒出来时弹窗不会往下跳一格,
跳一下的话,用户正要按的「解锁」按钮会在他手指落下前挪走位置 -->
<p class="wx-err" id="wxErr"></p>
</div>
<!-- is-done 和 is-gift **两种**模式下,上面整个 .wx-form 和右边这颗
「解锁全部资料」都会被 CSS 收起来:
done —— 对已经解锁的人再要一次兑换码,是在问一个他已经回答过的问题;
gift —— 那两个入口卖的是免费试用装,摆一个码框等于在福利前面收门票。
剩下的左键文案由 openWx() 按模式改写(done「知道了」/ gate「以后再说」)。
⚠️ gift 模式没有左键:整条 .modal-foot 被 `.is-gift .modal-foot{display:none}`
收起来了(原来那颗「先不领」按用户要求删了)。gift 的退路是右上角那颗
.modal-x —— 删按钮前专门确认过它在,测试里也钉了一条,别把它「优化」掉。
⚠️ 收起来的只是**样式**,#wxOk 的监听还在。所以 app.js 里那个处理器
开头有一句「不是 gate 模式就 return」的 guard —— 别以为 display:none
就等于点不到,jsdom 里 dispatchEvent 照样叫得醒它。 -->
<div class="modal-foot">
<button class="dl-btn ghost" id="wxLater" data-modal="close">以后再说</button>
<button class="dl-btn main" id="wxOk">解锁全部资料</button>
</div>
</section>
<!-- ================= 我的资料(妈妈 + 宝宝,可换头像) =================
⚠️ 这里原来有**两个**弹窗:这张「我的资料」(头像 / 昵称 / 一句话签名)
和它上面一张 #modalPreg「编辑孕期信息」(小名 / 性别 / 预产期 / 孕周)。
本批按用户要求「我的资料里加上宝宝小名、性别、预产期」,
做法是把 #modalPreg **整个并进来**,而不是把三个字段抄一份过来。
为什么不抄一份:抄完这三个字段就有两套输入框写同一份 state。
两套控件迟早会漂 —— 一边加了校验另一边没加、一边保存后调了
syncPregnancy() 另一边忘了调,而且这种不一致在两个弹窗**分别**
打开时都看不出来,只有用户在 A 改完去 B 看才暴露。
合并之后 pgName/pgSex/pgDue/pgWeek/pgDay 这五个 id 一个没改,
原来挂在它们身上的联动(改预产期→算孕周、改孕周→反推预产期)
原样继续生效,只是保存按钮从 #pgSave 并进了 #pfSave。
⚠️ 「孕期信息」那一节的「编辑 ›」和整张孕期卡现在都指向这个弹窗
(data-modal 从 modalPreg 改成了 modalProfile),别再找 modalPreg 了。
⚠️ 高度预算(.modal 是 max-height:92% + overflow-y:auto,撑爆不会破版、
只会要用户滚一下才够得着「保存」,但那已经算体验问题了):
22 内边 + 30 头 + 头像块 121 + 昵称/小名同行 78 + 性别 77
+ 预产期 78 + 孕周行 78 + 提示 43 + 底部按钮 68 + 20 内边 ≈ 615px
设计稿机身 932px × 92% = 857 ✓;最窄的 iPhone SE 667 × 92% = 613 ——
**贴着线**。所以下面做了两件省高度的事,别随手退回去:
① 昵称和宝宝小名并成一行 .fld-row(省 78px);
② 这个弹窗里的大头像从 92px 收到 76px(见 css 的 #modalProfile .av-big)。
再往里加字段之前,先回来把这笔账重算一遍。 -->
<section class="modal" id="modalProfile" role="dialog" aria-modal="true" aria-labelledby="modalProfileTitle">
<div class="modal-head">
<h3 id="modalProfileTitle">我的资料</h3>
<button class="modal-x" data-modal="close" aria-label="关闭">
<svg viewBox="0 0 24 24" width="15" height="15" aria-hidden="true">
<path d="M6 6l12 12M18 6L6 18" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round"/>
</svg>
</button>
</div>
<!-- 头像:点圆形区域直接唤起系统相册;file input 藏起来但不能 display:none,
否则部分安卓 WebView 点不动,所以用 0 尺寸 + opacity 的写法 -->
<div class="av-pick">
<button class="av-big" id="avPick" aria-label="更换头像">
<svg viewBox="0 0 40 40" aria-hidden="true">
<circle cx="20" cy="14.5" r="7" fill="#fff"/>
<path d="M6.5 35c0-7.2 6-11.5 13.5-11.5S33.5 27.8 33.5 35z" fill="#fff"/>
</svg>
<img id="avPreview" alt="">
<span class="av-cam">
<svg viewBox="0 0 24 24" width="15" height="15" aria-hidden="true">
<path d="M3 8.6A2.6 2.6 0 0 1 5.6 6h1.9l1.3-2h6.4L16.5 6h1.9A2.6 2.6 0 0 1 21 8.6v8.8a2.6 2.6 0 0 1-2.6 2.6H5.6A2.6 2.6 0 0 1 3 17.4z" fill="none" stroke="currentColor" stroke-width="1.9" stroke-linejoin="round"/>
<circle cx="12" cy="13" r="3.6" fill="none" stroke="currentColor" stroke-width="1.9"/>
</svg>
</span>
</button>
<input type="file" id="avFile" accept="image/*" class="av-file">
<div class="av-tip">点一下换张照片 · 只存在你自己的手机上</div>
</div>
<!-- ⚠️ 这里原来第二格是「一句话签名」#pfBio,本批按用户要求**删掉了**,别加回来。
连带删的还有「我的」页头像旁那行 #meBio 和 js 里的 syncBio() ——
必须一起走:只删输入框、留着展示行,那行就变成一句谁也改不动的死文案,
比没有更糟(用户会在资料页里翻来翻去找哪儿能改它)。
⚠️ 昵称(妈妈)和宝宝小名并成一行,是上面高度预算里省下的那 78px。
两格都是短文本,半行宽装得下:.fld input 左右内边 15×2 = 30,
半行宽 = (弹窗内容宽 - gap 12) / 2 ≈ (386-12)/2 = 187,
可写 157px ÷ 14px 字 ≈ 11 个汉字,而 maxlength 分别是 16 / 12 ——
填满会在框内横向滚动,不会破版。这是可接受的,别为此把它拆回两行。 -->
<div class="fld-row">
<!-- ⚠️ 不写死演示默认值(原 value="小满妈妈"/"小汤圆"已删):打开弹窗时由
fillProfileForm() 按服务端档案回填,新用户全空 + placeholder 提示 -->
<label class="fld"><span>昵称</span><input id="pfName" type="text" maxlength="16" placeholder="妈妈"></label>
<label class="fld"><span>宝宝小名</span><input id="pgName" type="text" maxlength="12" placeholder="宝宝"></label>
</div>
<div class="fld">
<span>宝宝性别</span>
<!-- 选了性别,宝宝来信/回信会换成对应的口吻与落款 -->
<div class="pick-row" id="pgSex">
<button type="button" class="pick is-active" data-sex="unknown">还不知道</button>
<button type="button" class="pick" data-sex="boy">男孩</button>
<button type="button" class="pick" data-sex="girl">女孩</button>
</div>
</div>
<!-- 预产期排在孕周前面:它是「源」,填了才解锁下面两格。
默认值不写死在标记里 —— 由 JS 按「打开这一刻 + 3 个月」现算 -->
<label class="fld"><span>预产期</span><input id="pgDue" type="date"></label>
<div class="fld-row">
<!-- 孕周/天数由预产期自动算出,打开时按档案回填;新用户留空并锁定 -->
<label class="fld"><span>当前孕周</span><input id="pgWeek" type="number" min="0" max="42" placeholder="—" disabled></label>
<label class="fld"><span>额外天数</span><input id="pgDay" type="number" min="0" max="6" placeholder="—" disabled></label>
</div>
<p class="fld-hint" id="pgHint">填完预产期后自动算出,算得不准可以自己微调。</p>
<div class="modal-foot">
<button class="dl-btn ghost" data-modal="close">取消</button>
<!-- ⚠️ 只剩这一个保存按钮。原 #pgSave 已随 modalPreg 一起删除,
它那套逻辑(校验预产期、夹取孕周天数、写 state、调 syncPregnancy())
整段并进了 js 里的 #pfSave 处理器。 -->
<button class="dl-btn main" id="pfSave">保存</button>
</div>
</section>
<div class="toast" id="toast"></div>
</div>
<!-- 微信 JS-SDK:仅在 web-view 内给 wx.miniProgram 用(「进商城」跳原生页)。
加载失败/离线时 window.wx 为 undefined,goMall() 会降级为 toast 提示,
不影响应用其余功能。 -->
<script src="js/app.js?v=26090723"></script>
<!-- 页面版本号(排查 webview 缓存):底部会显示 v26090723 -->
<div id="ver" style="position:fixed;left:0;right:0;bottom:10px;text-align:center;z-index:9999;pointer-events:none;color:#B3A69C;font-size:10px;letter-spacing:.5px">v26090723</div>
</body>
</html>