feat(首页): 3D 婴儿房入口三版——今天的动线与书架之间开一道空隙,房间铺在整页背后随滚动视差露出,「进去布置」按钮跳入并带 return_url (v26100112)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-01 08:40:05 +00:00
parent 3394a1f6b0
commit a90f348061
4 changed files with 83 additions and 235 deletions

111
js/app.js
View File

@@ -3,7 +3,7 @@
(function () {
'use strict';
/* 页面版本号(YYMMDD+序号):显示在「关于兜爸心选」页底部;bump 时同步 head 注释与 ?v= */
var VER = '26100111';
var VER = '26100112';
const $ = (s, c) => (c || document).querySelector(s);
const $$ = (s, c) => Array.from((c || document).querySelectorAll(s));
@@ -3098,104 +3098,51 @@
}, 2600);
})();
/* ================= 3D 婴儿房 · 入口(2026-10-01 二版:走廊尽头的门)=================
设计和取舍写在 index.html 的 #nursery 注释里。地址由「3D 婴儿房」会话(33d6c837)维护,对方目前不读任何参数。
· 布置进度**只按孕周推演**(首页读不到房里真实摆了什么);syncPregnancy() 每次都会调 nurseryRender()。
· 三态:preg(有真实孕周)/ born(产后,全布置好、门敞开)/ empty(没建档,空房间 + 引导填预产期)。 */
/* ================= 3D 婴儿房 · 页面上的一道空隙(2026-10-01 三版)=================
设计写在 index.html 的 #nursery 注释里。地址由「3D 婴儿房」会话(33d6c837)维护;
带 return_url(首页净地址,不含 hash),对方返回按钮读到就跳回来。syncPregnancy() 每次都会调 nurseryRender()。 */
const NURSERY_URL = 'https://beian3.douba.top/yef/';
const NURSERY_ID = 'yef-nursery';
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;
if (!$('#nursery')) return;
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 + '的房间';
/* 入住倒计时:预产期 − 今天(按手机本地日期取整天) */
$('#nyTitle').textContent = nm + '的房间';
let days = null;
if (mode === 'preg' && state.due) {
if (!state.postpartum && state.pregEmpty === false && 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];
$('#nySub').innerHTML = state.postpartum ? 'TA 已经住进来啦,再添点新东西吧'
: days > 0 ? '还有 <em>' + days + '</em> 天入住 · 攒兜豆把它布置好'
: days != null ? 'TA 随时会住进来,快去布置好'
: '做孕期任务攒兜豆,一件件布置好';
}
(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'));
$('#nyBtn').addEventListener('click', () =>
jumpApp(NURSERY_URL + '?return_url=' + encodeURIComponent(location.origin + location.pathname), NURSERY_ID));
/* 视差:图层始终对齐 .screen 的可视区 → 页面滚、房间不动,像铺在整页背后 */
const layer = $('#nypLayer'), scr = sec.closest('.screen');
let raf = 0;
const place = () => {
raf = 0;
if (!scr) return;
const s = scr.getBoundingClientRect(), r = sec.getBoundingClientRect();
if (!s.height || !r.height) return;
layer.style.height = s.height + 'px';
layer.style.transform = 'translate3d(0,' + Math.round(s.top - r.top) + 'px,0)';
};
const req = () => { if (!raf) raf = requestAnimationFrame(place); };
if (scr) scr.addEventListener('scroll', req, { passive: true });
window.addEventListener('resize', req);
if ('IntersectionObserver' in window) new IntersectionObserver(req).observe(sec);
place();
const tick = () => {
const h = new Date(Date.now() + 8 * 3600e3).getUTCHours(); // 北京时间
sec.classList.toggle('is-night', h >= 19 || h < 6);
};
tick(); setInterval(tick, 600e3);
/* 气泡轮播:一句 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();
})();