diff --git a/js/app.js b/js/app.js
index 384964f..23971d8 100644
--- a/js/app.js
+++ b/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 = '
';
- 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
- ? '
' + HEART + (it.hugs ? '收到 ' + it.hugs + ' 个抱抱' : '抱抱正在路上') + ''
- : '
';
- return '
' +
- (it.mine ? '
我说的' : '') +
- '
' + esc(it.text) + '
' +
- '
' + meta + '' + foot + '
';
+ /* ---------- 首页入口:一句轮播 ---------- */
+ 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 + ' 句
';
+ clearInterval(rollT);
+ if (l.length > 1) rollT = setInterval(() => { if (!document.hidden && !page.classList.contains('show')) roll(); }, 4000);
}
+
+ /* ---------- 全屏页 ---------- */
+ function hugBtn(it) {
+ if (it.mine) return '
' + HEART + (it.hugs ? '收到 ' + it.hugs + ' 个抱抱' : '抱抱正在路上') + '';
+ return '
';
+ }
+ 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 '
' +
+ (it.mine ? '
我说的' : '') +
+ '
' + esc(it.text) + '
' +
+ '
孕 ' + it.week + ' 周 · ' + esc(it.ago) + '' + hugBtn(it) + '
';
+ }
+ 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 ? '今晚' : '今天') + ',
' + l.length + ' 句
悄悄话';
+ $('#whpEnd').textContent = night ? '— 都在这里了,晚安 —' : '— 都在这里了 —';
+ /* 主角:抱抱最多的一句(不选自己的) */
+ let top = null;
+ l.forEach(x => { if (!x.mine && (!top || x.hugs > top.hugs)) top = x; });
+ $('#whpHero').innerHTML = top
+ ? '
' + esc(top.text) + '
孕 ' + top.week + ' 周 · ' + esc(top.ago) + ' · 抱抱最多' + hugBtn(top) + '
'
+ : '';
+ $('#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('网络不太好,再试一次'))
diff --git a/mama-box.css b/mama-box.css
index 4a3c606..f4ad095 100644
--- a/mama-box.css
+++ b/mama-box.css
@@ -3552,3 +3552,105 @@ a.skip:focus{left:16px;top:16px}
.bb-shine{display:none}
/* 四版:和下面的盲盒横幅同宽对齐(原来 249px 窄卡偏在右边,说明文字总被截断) */
.letter-card{width:auto;height:auto;min-height:68px;margin-left:26px;margin-right:26px;backdrop-filter:none;-webkit-backdrop-filter:none}
+
+/* ================= 此刻 · 三改 + 同周悄悄话全屏页(2026-10-02)=================
+ 用户:「此刻有一点点乱」→ 头像左、两行字右(人数 / 一句邀请),下面一条细入口;
+ 悄悄话本身挪进 #whPage「深夜星河」全屏页。 */
+.pp-crowd{align-items:center;gap:11px;margin-top:11px}
+.pp-col{flex:1;min-width:0;display:flex;flex-direction:column;gap:3px}
+.pp-col .pp-crowd-t{font-size:12.5px}
+.pp-col .pp-doing{margin:0}
+.pp-col .pp-doing.hide{display:none}
+.pp-col .pp-me{font-size:12.5px}
+.pp-col .pp-me.todo i{width:15px;height:15px}
+.pp-col .pp-me.todo i svg{width:10px;height:10px}
+
+.wh-entry{display:flex;align-items:center;gap:10px;width:100%;margin-top:12px;padding:10px 12px 10px 10px;
+ border-radius:14px;background:linear-gradient(100deg,#2A2550 0%,#3E2F5E 60%,#53365F 100%);color:#fff;text-align:left;
+ box-shadow:0 6px 16px -8px rgba(52,36,92,.55);position:relative;overflow:hidden}
+.wh-entry.hide{display:none}
+.wh-entry::before{content:"";position:absolute;inset:0;pointer-events:none;opacity:.7;
+ background:radial-gradient(1px 1px at 22% 30%,#fff,transparent),radial-gradient(1px 1px at 61% 72%,#fff,transparent),
+ radial-gradient(1.2px 1.2px at 84% 22%,#fff,transparent),radial-gradient(1px 1px at 44% 14%,rgba(255,255,255,.7),transparent)}
+.wh-entry:active{transform:scale(.985)}
+.we-ico{flex:none;display:grid;place-items:center;width:32px;height:32px;border-radius:10px;background:rgba(255,255,255,.12);color:#FFD9A8}
+.we-ico svg{width:18px;height:18px}
+.we-txt{flex:1;min-width:0;display:flex;flex-direction:column;gap:2px}
+.we-txt b{font-size:11px;font-weight:600;letter-spacing:1px;color:rgba(255,255,255,.6)}
+.we-roll{font-size:13.5px;font-weight:600;color:#FFF4E8;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;transition:opacity .26s,transform .26s}
+.we-roll.out{opacity:0;transform:translateY(5px)}
+.we-go{flex:none;display:flex;align-items:center;gap:2px;font-size:12px;color:rgba(255,255,255,.72);font-variant-numeric:tabular-nums}
+.we-go i{display:grid;width:14px;height:14px}
+
+/* ---------- 全屏页 ---------- */
+.wh-page{position:absolute;inset:0;z-index:110;color:#fff;overflow:hidden;
+ background:radial-gradient(120% 60% at 80% 0%,#5A3B72 0%,transparent 60%),radial-gradient(90% 50% at 0% 100%,#4A2C55 0%,transparent 70%),linear-gradient(180deg,#191634 0%,#221C40 45%,#2C1F45 100%);
+ opacity:0;visibility:hidden;transform:scale(1.04);transition:opacity .4s var(--ease),transform .5s var(--ease),visibility .4s}
+.wh-page.show{opacity:1;visibility:visible;transform:none}
+.whp-sky{position:absolute;inset:0;pointer-events:none}
+.whp-sky i.s1,.whp-sky i.s2,.whp-sky i.s3{position:absolute;inset:0;animation:whTw 5s ease-in-out infinite alternate}
+.whp-sky i.s1{background:radial-gradient(1.2px 1.2px at 12% 18%,#fff,transparent),radial-gradient(1px 1px at 35% 8%,#fff,transparent),radial-gradient(1.4px 1.4px at 72% 14%,#fff,transparent),radial-gradient(1px 1px at 90% 40%,#fff,transparent),radial-gradient(1px 1px at 8% 62%,#fff,transparent),radial-gradient(1.2px 1.2px at 55% 88%,#fff,transparent)}
+.whp-sky i.s2{animation-delay:-2s;background:radial-gradient(1px 1px at 25% 34%,#fff,transparent),radial-gradient(1.3px 1.3px at 64% 46%,#fff,transparent),radial-gradient(1px 1px at 84% 70%,#fff,transparent),radial-gradient(1px 1px at 18% 84%,#fff,transparent),radial-gradient(1.5px 1.5px at 46% 24%,#FFE2B8,transparent)}
+.whp-sky i.s3{animation-delay:-3.5s;opacity:.6;background:radial-gradient(1px 1px at 5% 30%,#fff,transparent),radial-gradient(1px 1px at 40% 58%,#fff,transparent),radial-gradient(1px 1px at 77% 30%,#fff,transparent),radial-gradient(1px 1px at 95% 92%,#fff,transparent),radial-gradient(1px 1px at 30% 96%,#fff,transparent)}
+.whp-sky i.moon{position:absolute;top:58px;right:-36px;width:150px;height:150px;border-radius:50%;
+ background:radial-gradient(circle at 35% 35%,#FFE9C9 0%,#F6C893 45%,rgba(246,200,147,0) 72%);opacity:.28;filter:blur(2px)}
+@keyframes whTw{from{opacity:.35}to{opacity:1}}
+.whp-scroll{position:absolute;inset:0;overflow-y:auto;padding:calc(env(safe-area-inset-top) + 10px) 20px 150px;-webkit-overflow-scrolling:touch}
+.whp-scroll::-webkit-scrollbar{display:none}
+.whp-top{display:flex;align-items:center;justify-content:space-between;height:44px}
+.whp-back{display:grid;place-items:center;width:36px;height:36px;border-radius:50%;background:rgba(255,255,255,.1);color:#fff;backdrop-filter:blur(6px)}
+.whp-tag{font-size:11.5px;padding:5px 10px;border-radius:99px;background:rgba(255,255,255,.08);color:rgba(255,255,255,.7);letter-spacing:.5px}
+.whp-title{margin:26px 0 0;font-size:40px;line-height:1.12;font-weight:800;letter-spacing:1px;color:#fff}
+.whp-title b{font-size:56px;font-weight:900;background:linear-gradient(120deg,#FFE3B5,#FF9E8A);-webkit-background-clip:text;background-clip:text;color:transparent;font-variant-numeric:tabular-nums;margin:0 2px}
+.whp-sub{margin:12px 0 0;font-size:13px;line-height:1.7;color:rgba(255,255,255,.55)}
+.whp-hero{position:relative;margin:34px 0 8px;padding:26px 22px 18px;border-radius:24px;
+ background:linear-gradient(150deg,rgba(255,226,190,.16),rgba(255,160,140,.06));border:1px solid rgba(255,226,190,.22);
+ box-shadow:0 20px 50px -20px rgba(255,170,130,.35)}
+.whp-hero:empty{display:none}
+.whp-hero::before{content:"“";position:absolute;top:-30px;left:14px;font-size:96px;line-height:1;font-family:Georgia,serif;color:#FFD9A8;opacity:.9}
+.whp-hq{font-size:25px;line-height:1.45;font-weight:800;color:#FFF4E6;letter-spacing:.5px}
+.whp-flow{display:flex;flex-direction:column;gap:14px;margin-top:22px}
+.whp-b{position:relative;max-width:82%;padding:14px 16px 10px;border-radius:20px;
+ background:rgba(255,255,255,.07);border:1px solid rgba(255,255,255,.1);backdrop-filter:blur(8px);
+ animation:whIn .6s var(--ease) both,whBob 6s ease-in-out infinite;animation-delay:calc(var(--i) * 60ms),var(--d)}
+.whp-b.a{align-self:flex-start;border-bottom-left-radius:6px}
+.whp-b.b{align-self:flex-end;border-bottom-right-radius:6px}
+.whp-b.c{align-self:flex-start;margin-left:9%;border-bottom-left-radius:6px}
+.whp-b p{margin:0;font-size:15px;line-height:1.55;color:rgba(255,255,255,.82);font-weight:500}
+.whp-b.l2{background:rgba(255,255,255,.11);border-color:rgba(255,255,255,.16)}
+.whp-b.l2 p{font-size:16.5px;color:#fff;font-weight:600}
+.whp-b.l3{background:linear-gradient(140deg,rgba(255,180,160,.2),rgba(190,150,255,.14));border-color:rgba(255,200,180,.25);box-shadow:0 10px 30px -14px rgba(255,150,130,.5)}
+.whp-b.l3 p{font-size:18px;color:#fff;font-weight:700}
+.whp-b.mine{align-self:flex-end;max-width:86%;background:linear-gradient(140deg,rgba(255,214,150,.26),rgba(255,170,110,.14));border-color:rgba(255,214,150,.45);box-shadow:0 0 0 4px rgba(255,214,150,.06),0 12px 34px -12px rgba(255,190,120,.55)}
+.whp-b.mine p{color:#FFF1DC;font-weight:700;font-size:16.5px}
+.whp-mine{display:inline-block;margin-bottom:6px;font-size:10.5px;font-weight:700;letter-spacing:1px;padding:2px 8px;border-radius:99px;background:#FFD596;color:#5A3714}
+@keyframes whIn{from{opacity:0;transform:translateY(16px)}}
+@keyframes whBob{0%,100%{translate:0 0}50%{translate:0 -4px}}
+.whp-meta{display:flex;align-items:center;justify-content:space-between;gap:10px;margin-top:10px;font-size:11.5px;color:rgba(255,255,255,.45)}
+.whp-hero .whp-meta{margin-top:18px;font-size:12px}
+.whp-hug{position:relative;display:inline-flex;align-items:center;gap:4px;height:28px;padding:0 11px;border-radius:99px;background:rgba(255,255,255,.1);color:rgba(255,255,255,.85);font-size:12.5px;font-weight:700;font-variant-numeric:tabular-nums}
+.whp-hug svg{width:15px;height:15px;fill:none;stroke:currentColor;stroke-width:2}
+.whp-hug b:empty{display:none}
+.whp-hug.on{background:linear-gradient(120deg,#FF8E7A,#FF6F91);color:#fff}
+.whp-hug.on svg{fill:#fff;stroke:#fff}
+.whp-hug.pop svg{animation:whBeat .5s var(--ease)}
+@keyframes whBeat{30%{transform:scale(1.5)}60%{transform:scale(.9)}}
+.whp-got{display:inline-flex;align-items:center;gap:4px;color:#FFD596;font-weight:600}
+.whp-got svg{width:13px;height:13px;fill:#FFD596}
+.whp-fly{position:absolute;left:50%;bottom:60%;width:14px;height:14px;margin-left:-7px;pointer-events:none;animation:whFly 1.1s ease-out both}
+.whp-fly svg{width:100%;height:100%;fill:#FF8FA3}
+@keyframes whFly{from{opacity:1;transform:translate(0,0) scale(.6)}to{opacity:0;transform:translate(var(--x),-70px) rotate(var(--r)) scale(1.1)}}
+.whp-end{margin:36px 0 0;text-align:center;font-size:11.5px;letter-spacing:2px;color:rgba(255,255,255,.3)}
+.whp-bar{position:absolute;left:0;right:0;bottom:0;padding:30px 24px calc(14px + env(safe-area-inset-bottom));text-align:center;
+ background:linear-gradient(180deg,rgba(28,22,52,0),rgba(28,22,52,.92) 45%)}
+.whp-say{position:relative;width:100%;height:52px;border-radius:99px;font-size:16px;font-weight:800;letter-spacing:2px;color:#3A2312;
+ background:linear-gradient(110deg,#FFE3B5,#FFB48F 55%,#FF9AA6);box-shadow:0 10px 30px -8px rgba(255,160,130,.7),inset 0 1px 0 rgba(255,255,255,.6)}
+.whp-say::after{content:"";position:absolute;inset:-6px;border-radius:inherit;border:1.5px solid rgba(255,200,160,.5);animation:whRing 2.6s ease-out infinite}
+@keyframes whRing{from{opacity:.9;transform:scale(.96)}to{opacity:0;transform:scale(1.08)}}
+.whp-say:active{transform:scale(.98)}
+.whp-left{margin:9px 0 0;font-size:11.5px;color:rgba(255,255,255,.5)}
+@media (prefers-reduced-motion:reduce){
+ .whp-b,.whp-sky i,.whp-say::after{animation:none!important}
+ .wh-page{transition:opacity .2s}
+}
+.whp-say{display:grid;place-items:center;text-align:center;padding:0}