同孕周悄悄话全屏页三改:星图 + 聚焦卡 + 清单;运动助手待办文案 (v26100165)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-02 06:14:34 +00:00
parent c4c4d8ceac
commit acc8301b20
3 changed files with 138 additions and 22 deletions

103
js/app.js
View File

@@ -3,7 +3,7 @@
(function () {
'use strict';
/* 页面版本号(YYMMDD+序号):显示在「关于兜爸心选」页底部;bump 时同步 head 注释与 ?v= */
var VER = '26100164';
var VER = '26100165';
const $ = (s, c) => (c || document).querySelector(s);
const $$ = (s, c) => Array.from((c || document).querySelectorAll(s));
@@ -3339,39 +3339,101 @@
return '<button class="whp-hug' + (it.hugged ? ' on' : '') + '" type="button" data-hug="' + esc(it.id) + '" aria-label="给她一个抱抱">' +
HEART + '<b>' + (it.hugs || '') + '</b></button>';
}
function bubble(it, i) {
/* 抱抱越多越亮越大:三档 */
const lv = it.mine ? 'mine' : it.hugs >= 25 ? 'l3' : it.hugs >= 10 ? 'l2' : 'l1';
const side = ['a', 'b', 'c'][i % 3];
return '<div class="whp-b ' + lv + ' ' + side + '" style="--d:' + ((i * 0.7) % 4).toFixed(1) + 's;--i:' + i + '">' +
(it.mine ? '<span class="whp-mine">我说的</span>' : '') +
'<p>' + esc(it.text) + '</p>' +
'<div class="whp-meta"><span>孕 ' + it.week + ' 周 · ' + esc(it.ago) + '</span>' + hugBtn(it) + '</div></div>';
/* 星图 + 聚焦卡 + 清单(三改)。星星位置按 id 哈希定在网格里再抖一抖,同一句每次出现在同一处 */
let fi = 0, focT = null, focHold = false;
const hsh = s => { let h = 7; for (const c of String(s)) h = (h * 31 + c.charCodeAt(0)) >>> 0; return h; };
function paintSky(l) {
const n = l.length, cols = n > 12 ? 5 : 4, rows = Math.max(2, Math.ceil(n / cols));
const pts = l.map((it, i) => {
const h = hsh(it.id), c = i % cols, r = Math.floor(i / cols);
/* 蛇形排列,连线不来回横穿 */
const cc = r % 2 ? cols - 1 - c : c;
const x = ((cc + .5 + ((h % 100) / 100 - .5) * .62) / cols) * 100;
const y = ((r + .5 + (((h >> 8) % 100) / 100 - .5) * .55) / rows) * 100;
const sz = it.mine ? 13 : Math.round(5 + Math.min(it.hugs || 0, 40) / 40 * 11);
return { x, y, sz, it, i };
});
$('#whpSky').innerHTML =
'<svg class="whs-lines" viewBox="0 0 100 100" preserveAspectRatio="none" aria-hidden="true"><polyline points="' +
pts.map(p => p.x.toFixed(1) + ',' + p.y.toFixed(1)).join(' ') + '"/></svg>' +
pts.map(p => '<button type="button" class="whs-star' + (p.it.mine ? ' mine' : '') + (p.i === fi ? ' on' : '') +
'" data-fi="' + p.i + '" style="left:' + p.x.toFixed(1) + '%;top:' + p.y.toFixed(1) + '%;--s:' + p.sz + 'px;--tw:' + ((hsh(p.it.id) % 30) / 10).toFixed(1) + 's" aria-label="' +
esc(p.it.text) + '"><i></i></button>').join('');
}
function paintFocus(anim) {
const l = list(), it = l[fi];
const box = $('#whpFocus');
if (!it) { box.innerHTML = ''; return; }
box.innerHTML =
'<div class="whf-top"><span class="whf-no">' + (it.mine ? '<em>我说的</em>' : '') + 'No.' + (fi + 1) + ' / ' + l.length + '</span>' +
'<span class="whf-nav"><button type="button" data-fn="-1" aria-label="上一句">‹</button><button type="button" data-fn="1" aria-label="下一句">›</button></span></div>' +
'<p class="whf-q' + (anim ? ' in' : '') + '">' + esc(it.text) + '</p>' +
'<div class="whp-meta"><span>孕 ' + it.week + ' 周 · ' + esc(it.ago) + '</span>' + hugBtn(it) + '</div>';
$$('.whs-star', $('#whpSky')).forEach(s => s.classList.toggle('on', +s.dataset.fi === fi));
}
function focusTo(i, user) {
const n = list().length;
if (!n) return;
fi = (i + n) % n;
if (user) focHold = true; // 用户自己点过就不再自动轮播,免得正要抱抱时换走
paintFocus(true);
}
function paintList(l) {
$('#whpCount').textContent = l.length + ' 句 · 最新在上';
$('#whpList').innerHTML = l.map((it, i) => {
const lv = it.mine ? 'mine' : it.hugs >= 25 ? 'l3' : it.hugs >= 10 ? 'l2' : 'l1';
return '<div class="whl ' + lv + '"><button type="button" class="whl-t" data-fi="' + i + '">' +
(it.mine ? '<em>我说的</em>' : '') + esc(it.text) + '</button>' +
'<div class="whp-meta"><span>孕 ' + it.week + ' 周 · ' + esc(it.ago) + '</span>' + hugBtn(it) + '</div></div>';
}).join('');
}
function paintPage() {
const l = list().slice();
const l = list();
$('#whpTag').textContent = data ? '孕 ' + data.week + ' 周 · 前后一周的妈妈' : '';
/* 北京时间 18 点后 / 5 点前叫「今晚」,其余「今天」 */
const bh = (new Date().getUTCHours() + 8) % 24, night = bh >= 18 || bh < 5;
$('#whpTitle').innerHTML = (night ? '今晚' : '今天') + ',<b>' + l.length + '</b> 句<br>悄悄话';
$('#whpTitle').innerHTML = (night ? '今晚' : '今天') + ',<b>' + l.length + '</b> 句悄悄话';
$('#whpEnd').textContent = night ? '— 都在这里了,晚安 —' : '— 都在这里了 —';
/* 主角:抱抱最多的一句(不选自己的) */
let top = null;
l.forEach(x => { if (!x.mine && (!top || x.hugs > top.hugs)) top = x; });
$('#whpHero').innerHTML = top
? '<div class="whp-hq">' + esc(top.text) + '</div><div class="whp-meta"><span>孕 ' + top.week + ' 周 · ' + esc(top.ago) + ' · 抱抱最多</span>' + hugBtn(top) + '</div>'
: '';
$('#whpFlow').innerHTML = l.filter(x => x !== top).map(bubble).join('');
/* 第一次聚焦:抱抱最多的那句(不选自己的) */
fi = 0;
l.forEach((x, i) => { if (!x.mine && x.hugs > (l[fi].mine ? -1 : l[fi].hugs)) fi = i; });
paintSky(l);
paintFocus(false);
paintList(l);
$('#whpLeft').textContent = !data ? '' : !oid() || !data.canPost ? '填好预产期,就能说一句' : data.left ? '今天还能说 ' + data.left + ' 句' : '今天的 3 句说完啦,明天再来';
}
page.addEventListener('click', e => {
const st = e.target.closest('[data-fi]');
if (st) {
haptic();
focusTo(+st.dataset.fi, true);
if (st.classList.contains('whl-t')) $('#whpScroll').scrollTo({ top: $('#whpSky').offsetTop - 70, behavior: 'smooth' });
return;
}
const nb = e.target.closest('[data-fn]');
if (nb) { haptic(); focusTo(fi + +nb.dataset.fn, true); }
});
/* 聚焦卡左右滑切换 */
let tx = null;
$('#whpFocus').addEventListener('touchstart', e => { tx = e.touches[0].clientX; }, { passive: true });
$('#whpFocus').addEventListener('touchend', e => {
if (tx == null) return;
const dx = e.changedTouches[0].clientX - tx; tx = null;
if (Math.abs(dx) > 40) focusTo(fi + (dx < 0 ? 1 : -1), true);
});
function openPage() {
paintPage();
page.classList.add('show');
page.setAttribute('aria-hidden', 'false');
$('#whpScroll').scrollTop = 0;
haptic();
focHold = false;
clearInterval(focT);
if (!(window.matchMedia && matchMedia('(prefers-reduced-motion: reduce)').matches))
focT = setInterval(() => { if (!focHold && !document.hidden && list().length > 1) focusTo(fi + 1); }, 6000);
}
function closePage() {
clearInterval(focT);
page.classList.remove('show');
page.setAttribute('aria-hidden', 'true');
}
@@ -3427,6 +3489,7 @@
const o = oid();
if (!o) { toast('登录后就能给她抱抱啦'); return; }
if (b.classList.contains('on')) { toast('你已经抱过她啦'); return; }
focHold = true;
const id = b.dataset.hug;
const it = list().find(x => x.id === id);
haptic();
@@ -3491,7 +3554,8 @@
paintEntry();
paintPage();
closeModal();
$('#whpScroll').scrollTo({ top: $('#whpFlow').offsetTop - 80, behavior: 'smooth' });
fi = 0; focHold = true; paintFocus(true);
$('#whpScroll').scrollTo({ top: $('#whpSky').offsetTop - 70, behavior: 'smooth' });
toast('发出去啦,同孕周妈妈会给你抱抱的');
})
.catch(() => toast('网络不太好,再试一次'))
@@ -4311,6 +4375,7 @@
[/估重/, '算一算宝宝有多重', '拿最近一次 B 超数据就能估'],
[/起名/, '给宝宝想一个名字', '挑几个喜欢的字,和家人一起选'],
[/档案/, '翻翻孕期档案', '把这周的变化记进档案里'],
[/运动/, '做 10 分钟孕期运动', '散散步、拉伸一下,身体会轻松些'],
];
function todoLine(name) {
for (const [re, t, d] of FP_TODO) if (re.test(name)) return [t, d];