diff --git a/index.html b/index.html
index 4b0fcb1..6f93ab9 100644
--- a/index.html
+++ b/index.html
@@ -7,7 +7,7 @@
兜爸心选 · 孕育陪伴
-
+
@@ -292,13 +292,29 @@
同日三改(用户:「此刻有一点点不够清爽,文字太多了,密密麻麻的」):从 5 行减到 3 行 ——
① 标题行不变;② 「她们在做」两条进度条合成**一句**,只挑你今天还没做的那项(都做了就一句夸奖),去掉进度条;
③ 「今天别忘了」整行删(产检、盲盒在上面工具宫格和盲盒横幅里都有);④ 滚动动态去掉白底卡片,压成一行小字。 -->
-
+
+
此刻
-
+
+
+
+
+
+
+ 同周悄悄话匿名 · 只有同孕期妈妈能看到
+
+
+
+
@@ -1727,6 +1743,27 @@
+
+
+
+ 不显示昵称和头像,只有同孕期的妈妈能看到。她们会给你一个抱抱。
+
+
+ 0/40
+
+ 不知道说什么?点一句
+
+
+
+
🏆
diff --git a/js/app.js b/js/app.js
index ceea833..6ddd0bb 100644
--- a/js/app.js
+++ b/js/app.js
@@ -3,7 +3,7 @@
(function () {
'use strict';
/* 页面版本号(YYMMDD+序号):显示在「关于兜爸心选」页底部;bump 时同步 head 注释与 ?v= */
- var VER = '26100156';
+ var VER = '26100157';
const $ = (s, c) => (c || document).querySelector(s);
const $$ = (s, c) => Array.from((c || document).querySelectorAll(s));
@@ -3296,6 +3296,146 @@
if (k < 1) requestAnimationFrame(step);
})(t0);
}
+ /* ================= 此刻 · 同周悄悄话(2026-10-02) =================
+ 用户原话:「升级成同周妈妈的悄悄话,就是匿名一句心愿墙……不做复杂社区,就是只做一点陪伴」;发言 = 预设句 + 自由输入。
+ GET /api/mp/public/mama-box/whisper?openid=&week= → { show, week, canPost, left, maxLen, presets[], list[] }
+ POST /api/mp/public/mama-box/whisper?openid= {text} → 新的一条(自由输入由服务端过微信内容安全审核,不通就 code≠200)
+ POST /api/mp/public/mama-box/whisper/hug?openid=&id=
+ · 只能「抱抱」,没有评论、昵称、头像 —— 刻意不做成社区。自己说的那句排第一,显示「收到 N 个抱抱」。
+ · 拉不到(show=false / 失败 / 访客没孕周)→ 整块隐藏,此刻卡退回滚动动态。不在前端造任何内容。
+ · 由 pulseCard 的 load() 带着孕周触发(孕周回来 / 变了才重拉),另每 5 分钟静默刷新一次。 */
+ let whLoad = null;
+ (function whisperWall() {
+ const wall = $('#whWall');
+ if (!wall) return;
+ const API = '/api/mp/public/mama-box/whisper';
+ const esc = s => String(s == null ? '' : s).replace(/[&<>"]/g, c => ({ '&': '&', '<': '<', '>': '>', '"': '"' })[c]);
+ const HEART = '';
+ let data = null, wk = null, sig = '', busy = false;
+ const oid = () => hashParam('openid');
+
+ function card(it, i) {
+ const tint = 't' + (i % 4);
+ const meta = '孕 ' + it.week + ' 周 · ' + esc(it.ago);
+ const foot = it.mine
+ ? '' + HEART + (it.hugs ? '收到 ' + it.hugs + ' 个抱抱' : '抱抱正在路上') + ''
+ : '';
+ return '' +
+ (it.mine ? '
我说的' : '') +
+ '
' + esc(it.text) + '
' +
+ '
' + meta + '' + foot + '
';
+ }
+ function render(keepScroll) {
+ const st = $('#whStrip');
+ const x = st.scrollLeft;
+ const list = (data && data.list) || [];
+ st.innerHTML = list.map(card).join('');
+ if (keepScroll) st.scrollLeft = x; else st.scrollLeft = 0;
+ }
+ function show(on) {
+ wall.classList.toggle('hide', !on);
+ const sec = $('#pulse');
+ if (sec) sec.classList.toggle('has-wh', on);
+ }
+ function load(w, silent) {
+ wk = w;
+ if (!w) { data = null; show(false); return; }
+ const o = oid();
+ fetch(API + '?openid=' + encodeURIComponent(o || '') + '&week=' + w)
+ .then(r => (r.ok ? r.json() : null))
+ .then(j => {
+ const r = j && j.code === 200 && j.result;
+ if (!r || !r.show || !(r.list || []).length) { data = null; show(false); return; }
+ data = r;
+ const ns = r.list.map(x => x.id + ':' + x.hugs + (x.hugged ? 'h' : '')).join('|');
+ if (ns !== sig) { sig = ns; render(silent); }
+ show(true);
+ })
+ .catch(() => { if (!silent) show(false); });
+ }
+ whLoad = w => load(w, false);
+ setInterval(() => { if (wk && !document.hidden && !busy) load(wk, true); }, 300000);
+
+ /* 抱抱:先变(心跳一下、数字 +1),失败再退回 */
+ $('#whStrip').addEventListener('click', e => {
+ const b = e.target.closest('[data-hug]');
+ if (!b) return;
+ const o = oid();
+ if (!o) { toast('登录后就能给她抱抱啦'); return; }
+ if (b.classList.contains('on')) { toast('你已经抱过她啦'); return; }
+ const it = ((data && data.list) || []).find(x => x.id === b.dataset.hug);
+ haptic();
+ b.classList.add('on', 'pop');
+ setTimeout(() => b.classList.remove('pop'), 600);
+ const nb = $('b', b);
+ const n0 = it ? it.hugs : 0;
+ if (it) { it.hugs = n0 + 1; it.hugged = true; }
+ nb.textContent = n0 + 1;
+ fetch(API + '/hug?openid=' + encodeURIComponent(o) + '&id=' + encodeURIComponent(b.dataset.hug), { method: 'POST' })
+ .then(r => r.json())
+ .then(j => { if (!j || j.code !== 200) throw j; })
+ .catch(j => {
+ b.classList.remove('on');
+ if (it) { it.hugs = n0; it.hugged = false; }
+ nb.textContent = n0 || '';
+ toast((j && j.message) || '网络不太好,再抱一次试试');
+ });
+ });
+
+ /* 说一句 */
+ const ta = $('#whTa');
+ const cnt = () => { $('#whCnt').textContent = ta.value.length + '/' + ((data && data.maxLen) || 40); };
+ ta.addEventListener('input', () => {
+ cnt();
+ $$('.wh-pre', $('#whPresets')).forEach(c => c.classList.toggle('on', c.textContent === ta.value));
+ });
+ $('#whSay').addEventListener('click', () => {
+ if (!data) return;
+ if (!oid() || !data.canPost) { toast('填好预产期,就能和同周妈妈说悄悄话啦'); return; }
+ if (!data.left) { toast('今天已经说了 3 句啦,明天再来'); return; }
+ ta.value = '';
+ ta.maxLength = data.maxLen || 40;
+ cnt();
+ $('#whPresets').innerHTML = (data.presets || []).map(t => '').join('');
+ $('#whLeft').textContent = '今天还能说 ' + data.left + ' 句';
+ openModal('modalWhisper');
+ });
+ $('#whPresets').addEventListener('click', e => {
+ const c = e.target.closest('.wh-pre');
+ if (!c) return;
+ haptic();
+ ta.value = c.textContent;
+ ta.dispatchEvent(new Event('input'));
+ });
+ $('#whSend').addEventListener('click', () => {
+ const t = ta.value.trim();
+ if (t.length < 2) { toast('写一两个字,或者点一句吧'); return; }
+ if (busy) return;
+ busy = true;
+ const btn = $('#whSend');
+ btn.disabled = true;
+ btn.textContent = '正在发出…';
+ fetch(API + '?openid=' + encodeURIComponent(oid()), {
+ method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ text: t })
+ })
+ .then(r => r.json())
+ .then(j => {
+ if (!j || j.code !== 200 || !j.result) { toast((j && j.message) || '没发出去,再试一次'); return; }
+ const it = j.result;
+ data.left = it.left;
+ delete it.left;
+ data.list = [it].concat(data.list || []);
+ sig = '';
+ render(false);
+ closeModal();
+ toast('发出去啦,同周妈妈会给你抱抱的');
+ })
+ .catch(() => toast('网络不太好,再试一次'))
+ .then(() => { busy = false; btn.disabled = false; btn.textContent = '悄悄发出'; });
+ });
+ })();
+
(function pulseCard() {
const sec = $('#pulse');
if (!sec) return;
@@ -3311,7 +3451,14 @@
};
/* 用户自己今天做没做(足迹 used[].appId 是上游 URL slug)。足迹没回来 / 读不到 → null,不显示「你」那半句 */
const SELF_APP = { kick: 'taidong', weight: 'yunqi-tizhong' };
- const SELF_TXT = { kick: ['你今天还没数', '你也数过了'], weight: ['你今天还没记', '你也记过了'] };
+ /* 2026-10-02 陪伴版:不说「你今天还没数」(像点名),改成宝宝在等你的邀请;做过了就一句肯定。
+ 同周人数那句也不再给百分比(「她们有 18%」是在和你比),只说多少位妈妈今天也在做。 */
+ const nmB = () => state.babyName || '宝宝';
+ const SELF_TXT = {
+ kick: [() => nmB() + '在等你,陪它数一数', () => '你今天也陪' + nmB() + '数过啦'],
+ weight: [() => '也记一下今天的自己', () => '你今天也记过啦']
+ };
+ const CROWD_TXT = { kick: '位妈妈陪宝宝数了胎动', weight: '位妈妈记下了体重' };
function selfDone(k) {
if (_fpMode !== 'rows' && _fpMode !== 'guide') return null;
return ((_fpData && _fpData.used) || []).some(u => u.appId === SELF_APP[k]);
@@ -3343,7 +3490,8 @@
const avs = $('#ppAvs');
if (avs.dataset.seed === String(seed)) return;
avs.dataset.seed = seed;
- avs.innerHTML = [3, 11, 7].map(x => '' + momAva(x + seed) + '').join('');
+ /* 2026-10-02 用户:「头像现在展示 3 个,给我展示 6 到 8 个」→ 7 个 */
+ avs.innerHTML = [3, 11, 7, 19, 2, 14, 23].map(x => '' + momAva(x + seed) + '').join('');
}
function paintPeer(p) {
paintAvs(p.week);
@@ -3351,14 +3499,16 @@
/* 只说一句:优先挑你今天还没做的那项;都做了(或读不到你的足迹)就取第一项 */
const ds = (p.doing || []).filter(d => SELF_TXT[d.key]);
const pick = ds.find(d => selfDone(d.key) === false) || ds[0];
- if (pick) {
- const me = selfDone(pick.key);
- const meH = me == null ? '' : me
- ? '' + SELF_TXT[pick.key][1] + ' ✓'
- : '';
- $('#ppDoing').innerHTML = '她们有 ' + (+pick.pct || 0) + '% ' + esc(pick.label) + '' + meH + '
';
+ const n = pick ? Math.round((+p.count || 0) * (+pick.pct || 0) / 100) : 0;
+ $('#ppCrowd').innerHTML = pick && n >= 3 ? '今天 ' + fmtN(n) + ' ' + CROWD_TXT[pick.key] : '她们和你一样,在等宝宝来';
+ const me = pick ? selfDone(pick.key) : null;
+ if (me != null) {
+ $('#ppDoing').innerHTML = me
+ ? '' + esc(SELF_TXT[pick.key][1]()) + ''
+ : '';
}
- $('#ppDoing').classList.toggle('hide', !pick);
+ $('#ppDoing').classList.toggle('hide', me == null);
}
function paint() {
const r = last;
@@ -3371,6 +3521,8 @@
/* 全站版(访客 / real 模式):只留一行总人数 + 滚动动态 */
paintAvs(0);
$('#ppDoing').classList.add('hide');
+ const td = (r.today || [])[0];
+ $('#ppCrowd').innerHTML = td ? '今天 ' + fmtN(td.value) + ' ' + esc(td.label) : '她们和你一样,在等宝宝来';
const t = r.total;
/* 前缀「已有」省掉:一行放得下「10,037 位妈妈在这里迎接宝宝」,带上就会折出一个孤零零的「宝宝」 */
$('#plTitle').innerHTML = t ? '0 ' + esc(t.suffix) : '孕妈们在做什么';
@@ -3407,6 +3559,7 @@
function load() {
const w = myWeek();
loadedWk = w;
+ if (whLoad) whLoad(w);
return fetch('/api/mp/public/mama-box/pulse' + (w ? '?week=' + w : ''))
.then(r => (r.ok ? r.json() : null))
.then(j => { if (j && j.code === 200 && j.result) { last = j.result; paint(); } })
diff --git a/mama-box.css b/mama-box.css
index 43f6e16..8e6533d 100644
--- a/mama-box.css
+++ b/mama-box.css
@@ -3443,3 +3443,76 @@ a.skip:focus{left:16px;top:16px}
.lc-arrow{flex-basis:26px;width:26px;height:26px}
.lc-arrow svg{width:14px;height:14px}
.lc-env-v{left:9px;top:-10px;width:48px;height:36px;filter:drop-shadow(0 6px 8px rgba(150,30,60,.28))}
+
+/* ================= 此刻 · 陪伴版 + 同周悄悄话(2026-10-02) =================
+ 头像一排 7 个单独成行;「还没数」改成邀请式文字链;悄悄话是一条横滑的小纸条带,四种淡色轮换。 */
+.pulse .pl-title{white-space:normal}
+.pp-crowd{display:flex;align-items:center;gap:9px;margin-top:9px;min-width:0}
+.pp-crowd .pp-avs i{width:24px;height:24px;margin-left:-8px;border:2px solid #fff;box-shadow:0 1px 3px rgba(120,70,50,.12)}
+.pp-crowd .pp-avs i:first-child{margin-left:0}
+.pp-crowd-t{flex:1;min-width:0;font-size:12.5px;color:var(--muted);line-height:1.35}
+.pp-crowd-t b{font-size:14px;font-weight:800;color:var(--ink-2);font-variant-numeric:tabular-nums}
+.pp-doing{margin-top:8px}
+.pp-me{display:inline-flex;align-items:center;gap:5px;padding:0;background:none;border-radius:0;font-size:13px;font-weight:700}
+.pp-me.todo{color:#E5533A;background:none}
+.pp-me.todo i{display:grid;place-items:center;width:17px;height:17px;border-radius:50%;background:#FFE5DC}
+.pp-me.todo i svg{width:11px;height:11px}
+.pp-me.todo:active{opacity:.7;transform:none}
+.pp-me.ok{color:#4E8A43;background:none;font-weight:600}
+.pp-me.ok svg{width:15px;height:15px}
+
+.pulse.has-wh .pl-feed{display:none}
+.wh-wall.hide{display:none}
+.wh-wall{margin-top:13px}
+.wh-head{display:flex;align-items:center;justify-content:space-between;gap:10px}
+.wh-t{font-size:14px;font-weight:800;color:var(--ink);display:flex;align-items:baseline;gap:7px;min-width:0}
+.wh-t em{font-style:normal;font-size:11px;font-weight:500;color:var(--muted);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
+.wh-say{flex:0 0 auto;display:inline-flex;align-items:center;gap:4px;font:inherit;font-size:12.5px;font-weight:700;color:#E5533A;
+ background:none;border:0;padding:4px 0}
+.wh-say::before{content:"";width:14px;height:14px;background:currentColor;
+ -webkit-mask:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath d='M3 12.5l.6-2.6 7-7a1.4 1.4 0 012 2l-7 7z' fill='none' stroke='black' stroke-width='1.6' stroke-linejoin='round'/%3E%3C/svg%3E") center/contain no-repeat;
+ mask:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath d='M3 12.5l.6-2.6 7-7a1.4 1.4 0 012 2l-7 7z' fill='none' stroke='black' stroke-width='1.6' stroke-linejoin='round'/%3E%3C/svg%3E") center/contain no-repeat}
+.wh-say:active{opacity:.6}
+.wh-strip{display:flex;gap:10px;margin:9px -2px 0;padding:4px 2px 6px;overflow-x:auto;scroll-snap-type:x mandatory;
+ -webkit-overflow-scrolling:touch;scrollbar-width:none}
+.wh-strip::-webkit-scrollbar{display:none}
+.wh-card{position:relative;flex:0 0 172px;scroll-snap-align:start;display:flex;flex-direction:column;justify-content:space-between;
+ min-height:104px;padding:12px 12px 10px;border-radius:16px 16px 16px 5px;background:#FFF4EE}
+.wh-card.t0{background:#FFF1EC}
+.wh-card.t1{background:#F3F0FF}
+.wh-card.t2{background:#FFF7E6}
+.wh-card.t3{background:#EEF8F2}
+.wh-card.mine{background:#fff;box-shadow:inset 0 0 0 1.5px #FFC9B8}
+.wh-mine{position:absolute;top:-6px;right:10px;font-size:10px;font-weight:700;color:#fff;background:#F4593C;padding:2px 7px;border-radius:6px}
+.wh-txt{font-size:14px;line-height:1.5;color:var(--ink);font-weight:600;display:-webkit-box;-webkit-line-clamp:3;-webkit-box-orient:vertical;overflow:hidden}
+.wh-txt::before{content:"“";color:#E8A08C;font-weight:800;margin-right:1px}
+.wh-txt::after{content:"”";color:#E8A08C;font-weight:800;margin-left:1px}
+.wh-meta{display:flex;align-items:center;justify-content:space-between;gap:6px;margin-top:9px;font-size:10.5px;color:var(--muted);white-space:nowrap}
+.wh-hug{display:inline-flex;align-items:center;gap:3px;font:inherit;font-size:11px;font-weight:700;color:#C7837A;
+ background:rgba(255,255,255,.8);border:0;border-radius:999px;padding:3px 8px 3px 6px;transition:color .2s,background .2s}
+.wh-hug svg,.wh-got svg{width:13px;height:13px;fill:none;stroke:currentColor;stroke-width:2.2}
+.wh-hug b{font-weight:800;font-variant-numeric:tabular-nums}
+.wh-hug b:empty{display:none}
+.wh-hug.on{color:#fff;background:#F4593C}
+.wh-hug.on svg{fill:currentColor}
+.wh-hug.pop svg{animation:whPop .6s var(--ease)}
+@keyframes whPop{0%{transform:scale(1)}30%{transform:scale(1.5)}60%{transform:scale(.9)}100%{transform:scale(1)}}
+.wh-got{display:inline-flex;align-items:center;gap:3px;color:#E5533A;font-weight:600}
+.wh-got svg{fill:#F4593C;stroke:#F4593C}
+.wh-got b{font-weight:800}
+
+.wh-m-lead{margin-top:8px;font-size:13px;color:var(--muted);line-height:1.55}
+.wh-ta-box{position:relative;margin-top:14px}
+.wh-ta{display:block;width:100%;box-sizing:border-box;resize:none;font:inherit;font-size:15px;line-height:1.55;color:var(--ink);
+ padding:13px 15px 24px;border:1.5px solid #EADFD8;border-radius:16px;background:#fff}
+.wh-ta:focus{outline:none;border-color:var(--brand);box-shadow:0 0 0 3px rgba(244,89,60,.12)}
+.wh-cnt{position:absolute;right:12px;bottom:8px;font-size:11px;color:#C9B7AC;font-variant-numeric:tabular-nums}
+.wh-m-sub{margin-top:14px;font-size:12px;font-weight:700;color:var(--ink-2)}
+.wh-presets{display:flex;flex-wrap:wrap;gap:8px;margin-top:9px}
+.wh-pre{font:inherit;font-size:12.5px;color:var(--ink-2);background:#fff;border:1px solid #EFE4DD;border-radius:12px;padding:7px 11px;text-align:left}
+.wh-pre.on{color:#E5533A;border-color:#FFB7A3;background:#FFF3EE}
+.wh-pre:active{transform:scale(.97)}
+.wh-modal .modal-foot{margin-top:18px}
+.wh-left{margin-top:8px;text-align:center;font-size:11.5px;color:var(--muted)}
+@media (prefers-reduced-motion: reduce){.wh-hug.pop svg{animation:none}}
+.wh-modal .modal-foot{flex-direction:column;align-items:stretch;gap:0}