此刻收拢 + 同周悄悄话改为细入口 + 全屏「深夜星河」页 (v26100159)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-02 02:27:55 +00:00
parent 8b749633d6
commit e065a47ccb
3 changed files with 239 additions and 47 deletions

137
js/app.js
View File

@@ -3,7 +3,7 @@
(function () {
'use strict';
/* 页面版本号(YYMMDD+序号):显示在「关于兜爸心选」页底部;bump 时同步 head 注释与 ?v= */
var VER = '26100158';
var VER = '26100159';
const $ = (s, c) => (c || document).querySelector(s);
const $$ = (s, c) => Array.from((c || document).querySelectorAll(s));
@@ -3306,50 +3306,100 @@
· 由 pulseCard 的 load() 带着孕周触发(孕周回来 / 变了才重拉),另每 5 分钟静默刷新一次。 */
let whLoad = null;
(function whisperWall() {
const wall = $('#whWall');
if (!wall) return;
const entry = $('#whWall'), page = $('#whPage');
if (!entry || !page) return;
const API = '/api/mp/public/mama-box/whisper';
const esc = s => String(s == null ? '' : s).replace(/[&<>"]/g, c => ({ '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;' })[c]);
const HEART = '<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M12 20.5s-7.5-4.6-7.5-10.2A4.3 4.3 0 0112 7.6a4.3 4.3 0 017.5 2.7c0 5.6-7.5 10.2-7.5 10.2z"/></svg>';
let data = null, wk = null, sig = '', busy = false;
let data = null, wk = null, sig = '', busy = false, ri = 0, rollT = null;
const oid = () => hashParam('openid');
const list = () => (data && data.list) || [];
function card(it, i) {
const tint = 't' + (i % 4);
const meta = '孕 ' + it.week + ' 周 · ' + esc(it.ago);
const foot = it.mine
? '<span class="wh-got">' + HEART + (it.hugs ? '收到 <b>' + it.hugs + '</b> 个抱抱' : '抱抱正在路上') + '</span>'
: '<button class="wh-hug' + (it.hugged ? ' on' : '') + '" type="button" data-hug="' + esc(it.id) + '" aria-label="给她一个抱抱">' +
HEART + '<span>抱抱</span><b>' + (it.hugs || '') + '</b></button>';
return '<div class="wh-card ' + tint + (it.mine ? ' mine' : '') + '">' +
(it.mine ? '<span class="wh-mine">我说的</span>' : '') +
'<p class="wh-txt">' + esc(it.text) + '</p>' +
'<div class="wh-meta"><span>' + meta + '</span>' + foot + '</div></div>';
/* ---------- 首页入口:一句轮播 ---------- */
function roll() {
const l = list().filter(x => !x.mine);
const el = $('#weRoll');
if (!l.length) { el.textContent = ''; return; }
ri = (ri + 1) % l.length;
el.classList.add('out');
setTimeout(() => { el.textContent = '“' + l[ri].text + '”'; el.classList.remove('out'); }, 260);
}
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 paintEntry() {
const l = list().filter(x => !x.mine);
$('#weRoll').textContent = l.length ? '“' + l[0].text + '”' : '';
ri = 0;
$('#weGo').innerHTML = list().length + ' 句<i aria-hidden="true"><svg viewBox="0 0 16 16"><path d="M6 3.5L10.5 8 6 12.5" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg></i>';
clearInterval(rollT);
if (l.length > 1) rollT = setInterval(() => { if (!document.hidden && !page.classList.contains('show')) roll(); }, 4000);
}
/* ---------- 全屏页 ---------- */
function hugBtn(it) {
if (it.mine) return '<span class="whp-got">' + HEART + (it.hugs ? '收到 <b>' + it.hugs + '</b> 个抱抱' : '抱抱正在路上') + '</span>';
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>';
}
function paintPage() {
const l = list().slice();
$('#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>悄悄话';
$('#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('');
$('#whpLeft').textContent = !data ? '' : !oid() || !data.canPost ? '填好预产期,就能说一句' : data.left ? '今天还能说 ' + data.left + ' 句' : '今天的 3 句说完啦,明天再来';
}
function openPage() {
paintPage();
page.classList.add('show');
page.setAttribute('aria-hidden', 'false');
$('#whpScroll').scrollTop = 0;
haptic();
}
function closePage() {
page.classList.remove('show');
page.setAttribute('aria-hidden', 'true');
}
entry.addEventListener('click', openPage);
$('#whpBack').addEventListener('click', closePage);
function show(on) {
wall.classList.toggle('hide', !on);
entry.classList.toggle('hide', !on);
const sec = $('#pulse');
if (sec) sec.classList.toggle('has-wh', on);
if (!on) closePage();
}
function load(w, silent) {
wk = w;
if (!w) { data = null; show(false); return; }
const o = oid();
fetch(API + '?openid=' + encodeURIComponent(o || '') + '&week=' + w)
fetch(API + '?openid=' + encodeURIComponent(oid() || '') + '&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); }
if (ns !== sig) {
sig = ns;
paintEntry();
/* 页面开着时静默刷新不重画,免得正在看的那句跳走 */
if (!page.classList.contains('show')) paintPage();
}
show(true);
})
.catch(() => { if (!silent) show(false); });
@@ -3357,33 +3407,44 @@
whLoad = w => load(w, false);
setInterval(() => { if (wk && !document.hidden && !busy) load(wk, true); }, 300000);
/* 抱抱:先变(心跳一下、数字 +1),失败再退回 */
$('#whStrip').addEventListener('click', e => {
/* 抱抱:先变(心跳 + 一串小心飘上去),失败再退回 */
function burst(b) {
if (window.matchMedia && matchMedia('(prefers-reduced-motion: reduce)').matches) return;
for (let k = 0; k < 7; k++) {
const h = document.createElement('i');
h.className = 'whp-fly';
h.innerHTML = HEART;
h.style.setProperty('--x', (Math.random() * 60 - 30).toFixed(0) + 'px');
h.style.setProperty('--r', (Math.random() * 50 - 25).toFixed(0) + 'deg');
h.style.animationDelay = (k * 60) + 'ms';
b.appendChild(h);
setTimeout(() => h.remove(), 1300);
}
}
page.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);
const id = b.dataset.hug;
const it = list().find(x => x.id === id);
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' })
$$('[data-hug="' + CSS.escape(id) + '"]', page).forEach(x => { x.classList.add('on', 'pop'); $('b', x).textContent = n0 + 1; setTimeout(() => x.classList.remove('pop'), 600); });
burst(b);
fetch(API + '/hug?openid=' + encodeURIComponent(o) + '&id=' + encodeURIComponent(id), { 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 || '';
$$('[data-hug="' + CSS.escape(id) + '"]', page).forEach(x => { x.classList.remove('on'); $('b', x).textContent = n0 || ''; });
toast((j && j.message) || '网络不太好,再抱一次试试');
});
});
/* 说一句 */
/* 说一句(弹窗盖在全屏页上面) */
const ta = $('#whTa');
const cnt = () => { $('#whCnt').textContent = ta.value.length + '/' + ((data && data.maxLen) || 40); };
ta.addEventListener('input', () => {
@@ -3427,8 +3488,10 @@
delete it.left;
data.list = [it].concat(data.list || []);
sig = '';
render(false);
paintEntry();
paintPage();
closeModal();
$('#whpScroll').scrollTo({ top: $('#whpFlow').offsetTop - 80, behavior: 'smooth' });
toast('发出去啦,同周妈妈会给你抱抱的');
})
.catch(() => toast('网络不太好,再试一次'))