此刻收拢 + 同周悄悄话改为细入口 + 全屏「深夜星河」页 (v26100159)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
137
js/app.js
137
js/app.js
@@ -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 => ({ '&': '&', '<': '<', '>': '>', '"': '"' })[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('网络不太好,再试一次'))
|
||||
|
||||
Reference in New Issue
Block a user