feat(首页): 3D 婴儿房入口改版——首页最底的「走廊尽头的门」:门缝里房间随孕周布置、木牌+入住倒计时日历、宝宝气泡轮播、点击推门进入 (v26100111)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-01 07:57:59 +00:00
parent df95d1fae0
commit 3394a1f6b0
4 changed files with 246 additions and 103 deletions

109
js/app.js
View File

@@ -3,7 +3,7 @@
(function () {
'use strict';
/* 页面版本号(YYMMDD+序号):显示在「关于兜爸心选」页底部;bump 时同步 head 注释与 ?v= */
var VER = '26100110';
var VER = '26100111';
const $ = (s, c) => (c || document).querySelector(s);
const $$ = (s, c) => Array.from((c || document).querySelectorAll(s));
@@ -1053,7 +1053,7 @@
function syncPregnancy() {
/* 孕期小知识:只认真实孕周(自己判显隐,见 renderTips) */
renderTips();
nurseryCopy();
nurseryRender();
/* ---- 产后态:宝宝已出生 → 一律不显示孕周数字(2026-09-22) ----
⚠️ 必须放在 pregEmpty 之前:产后返回体的 week 也是 null,
落到空态分支会给**已经生产**的用户弹「完善孕期信息」引导。
@@ -3098,30 +3098,105 @@
}, 2600);
})();
/* ================= 3D 婴儿房 · 入口(2026-10-01)=================
地址由「3D 婴儿房」会话(33d6c837)维护;对方目前不读任何参数,jumpApp 多带的 openid 无害(以后接后端正好用上)。
· 昼夜:北京时间 19:00–6:00 挂 .is-night(和婴儿房里的昼夜灯光呼应);每 10 分钟查一次,跨点时自己换。
· 文案:syncPregnancy() 调 nurseryCopy(),只认真实孕周(同孕期小知识的口径)。 */
/* ================= 3D 婴儿房 · 入口(2026-10-01 二版:走廊尽头的门)=================
设计和取舍写在 index.html 的 #nursery 注释里。地址由「3D 婴儿房」会话(33d6c837)维护,对方目前不读任何参数。
· 布置进度**只按孕周推演**(首页读不到房里真实摆了什么);syncPregnancy() 每次都会调 nurseryRender()。
· 三态:preg(有真实孕周)/ born(产后,全布置好、门敞开)/ empty(没建档,空房间 + 引导填预产期)。 */
const NURSERY_URL = 'https://beian3.douba.top/yef/';
const NURSERY_ID = 'yef-nursery';
function nurseryCopy() {
const sub = $('#nySub');
if (!sub) return;
const left = (state.pregEmpty === false && !state.postpartum) ? 40 - (state.week || 0) : -1;
sub.innerHTML = left > 1 ? '再过 <b>' + left + '</b> 周 TA 就要住进来啦,<br>攒兜豆换小床、墙纸和挂饰'
: left >= 0 ? 'TA 马上就要住进来啦,<br>攒兜豆换小床、墙纸和挂饰'
: '做孕期任务攒兜豆,<br>换小床、墙纸和挂饰';
}
(function nurseryCard() {
const NY_STEPS = [
{ w: 4, k: 'wall', n: '粉墙', t: '粉墙刷好了' },
{ w: 12, k: 'curtain', n: '小窗帘', t: '小窗帘挂上了' },
{ w: 16, k: 'rug', n: '软地毯', t: '软地毯铺好了' },
{ w: 20, k: 'crib', n: '小床', t: '小床搬进来了' },
{ w: 24, k: 'mobile', n: '床头风铃', t: '床头风铃挂上了' },
{ w: 28, k: 'lamp', n: '小夜灯', t: '小夜灯装好了' },
{ w: 32, k: 'bear', n: '玩偶熊', t: '玩偶熊住进来了' },
{ w: 36, k: 'bag', n: '待产包', t: '待产包放在门口了' },
{ w: 39, k: 'stars', n: '星星灯', t: '星星灯亮起来了' }
];
let nyLines = [], nyLineIdx = 0;
function nurseryRender() {
const sec = $('#nursery');
if (!sec) return;
$('#nyCard').addEventListener('click', () => jumpApp(NURSERY_URL, NURSERY_ID));
const nm = state.babyName || '宝宝';
const mode = state.postpartum ? 'born' : (state.pregEmpty === false && state.week) ? 'preg' : 'empty';
const wk = mode === 'born' ? 99 : mode === 'preg' ? state.week : 0;
const done = NY_STEPS.filter(st => wk >= st.w);
const last = done[done.length - 1], next = NY_STEPS[done.length];
sec.classList.toggle('is-born', mode === 'born');
$$('#nursery [data-k]').forEach((g) => {
const k = g.getAttribute('data-k');
g.classList.toggle('on', done.some(st => st.k === k));
g.classList.toggle('new', mode === 'preg' && !!last && last.k === k && done.length > 1);
});
const dots = $('#nyDots');
dots.innerHTML = NY_STEPS.map((st, i) =>
'<i class="' + (i < done.length ? 'on' : '') + (mode === 'preg' && i === done.length - 1 ? ' cur' : '') + '"></i>').join('');
dots.style.setProperty('--p', done.length ? ((done.length - 1) / (NY_STEPS.length - 1) * 100).toFixed(1) + '%' : '0%');
$('#nySign').textContent = nm + '的房间';
/* 入住倒计时:预产期 − 今天(按手机本地日期取整天) */
let days = null;
if (mode === 'preg' && state.due) {
const t = new Date(); t.setHours(0, 0, 0, 0);
days = Math.round((state.due - t) / 864e5);
}
const cal = (top, num, unit, foot) => {
$('#nyCalTop').textContent = top; $('#nyCalNum').textContent = num;
$('#nyCalUnit').textContent = unit; $('#nyCalFoot').textContent = foot;
};
if (mode === 'born') cal('已入住', state.babyAgeDays == null ? '♥' : state.babyAgeDays, state.babyAgeDays == null ? '' : '天', '欢迎回家');
else if (days != null && days > 0) cal('入住倒计时', days, '天', (state.due.getMonth() + 1) + '月' + state.due.getDate() + '日');
else if (days != null) cal('入住倒计时', '0', '天', '随时入住');
else cal('入住日', '?', '', '填预产期');
if (mode === 'born') {
$('#nyTitle').textContent = nm + '已经住进来啦';
$('#nyProg').innerHTML = '房间布置好了 <b>9</b>/9,再给 TA 添点新东西吧';
nyLines = ['这是我的房间!', '妈妈,再给我添件新玩具吧~'];
} else if (mode === 'preg') {
$('#nyTitle').textContent = '第 ' + wk + ' 周 · ' + (last ? last.t : '房间开始布置啦');
$('#nyProg').innerHTML = '已布置 <b>' + done.length + '</b>/9' +
(next ? ' · 下一件 ' + next.n : ' · 全都布置好啦');
nyLines = ['妈妈,我的房间布置好了吗?'];
if (last && done.length > 1) nyLines.push('听说' + last.n + '到了,我能看看吗?');
if (next) nyLines.push('我还想要' + next.n + '~');
if (days > 0) nyLines.push('再过 ' + days + ' 天,我就搬进来啦');
} else {
$('#nyTitle').textContent = nm + '的房间 · 还空着呢';
$('#nyProg').innerHTML = '填好预产期,房间会跟着孕周一件件布置好';
nyLines = ['妈妈,我的房间在哪呀?', '告诉我预产期,我就知道哪天搬进来啦'];
}
nyLineIdx = 0;
$('#nyBubble').textContent = nyLines[0];
}
(function nurseryInit() {
const sec = $('#nursery');
if (!sec) return;
const card = $('#nyCard'), bub = $('#nyBubble');
const still = window.matchMedia && matchMedia('(prefers-reduced-motion: reduce)').matches;
card.addEventListener('click', () => {
if (card.classList.contains('is-opening')) return;
card.classList.add('is-opening');
setTimeout(() => jumpApp(NURSERY_URL, NURSERY_ID), still ? 0 : 700);
});
/* 从婴儿房返回(bfcache 恢复)时把门关回虚掩 */
window.addEventListener('pageshow', () => card.classList.remove('is-opening'));
const tick = () => {
const h = new Date(Date.now() + 8 * 3600e3).getUTCHours(); // 北京时间
sec.classList.toggle('is-night', h >= 19 || h < 6);
};
tick(); setInterval(tick, 600e3);
nurseryCopy();
/* 气泡轮播:一句 4.5 秒,淡出换下一句 */
if (!still) setInterval(() => {
if (nyLines.length < 2 || document.hidden) return;
bub.classList.add('out');
setTimeout(() => {
nyLineIdx = (nyLineIdx + 1) % nyLines.length;
bub.textContent = nyLines[nyLineIdx];
bub.classList.remove('out');
}, 350);
}, 4500);
nurseryRender();
})();
/* ================= 此刻,孕妈们在做什么(2026-10-01,需求文档 v1.0 第二节) =================