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

BIN
assets/img/nursery-peek.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

View File

@@ -7,7 +7,7 @@
<meta name="mobile-web-app-capable" content="yes"> <meta name="mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-capable" content="yes"> <meta name="apple-mobile-web-app-capable" content="yes">
<title>兜爸心选 · 孕育陪伴</title> <title>兜爸心选 · 孕育陪伴</title>
<!-- V:26100111 缓存版本标记:改 css/js 时同步 bump 该值(与 js/app.js 的 VER 同源),静态资源按 mtime 走 ETag 自动刷新 --> <!-- V:26100112 缓存版本标记:改 css/js 时同步 bump 该值(与 js/app.js 的 VER 同源),静态资源按 mtime 走 ETag 自动刷新 -->
<link rel="stylesheet" href="mama-box.css"> <link rel="stylesheet" href="mama-box.css">
<script src="https://res.wx.qq.com/open/js/jweixin-1.6.0.js"></script> <script src="https://res.wx.qq.com/open/js/jweixin-1.6.0.js"></script>
<!-- 共享助手:授权/跳商城/位置上报(分享卡片同步,见 docs/h5-auth-standard.md 第四节) --> <!-- 共享助手:授权/跳商城/位置上报(分享卡片同步,见 docs/h5-auth-standard.md 第四节) -->
@@ -456,6 +456,24 @@
⚠️ 纸团飞进树洞的动画靠 Element.animate;不支持的老内核直接跳到「已收好」,不影响收存。 --> ⚠️ 纸团飞进树洞的动画靠 Element.animate;不支持的老内核直接跳到「已收好」,不影响收存。 -->
<!-- 「妈妈的树洞」整块已删(2026-10-01 用户要求)。样式与 JS 一并删除,需要恢复看 git 历史 70c9786。 --> <!-- 「妈妈的树洞」整块已删(2026-10-01 用户要求)。样式与 JS 一并删除,需要恢复看 git 历史 70c9786。 -->
<!-- 3D 婴儿房 · 「透过页面看到后面的房间」(2026-10-01 三版)。用户原话:
「把那个房间整体……铺到它的后边……两个模块中间,它不用点击,从外边其实就能看到这个里边是什么,
整个铺到这个背景的后边,有一个引导进入的按钮,我点进去再弹出这个新的页面」。
做法:这一块没有底色,是页面上开的一道「空隙」;房间图层(#nypLayer)由 JS 每次滚动都对齐到 .screen 的可视区,
所以页面往上滚时房间不跟着走,看起来就是铺在整页背后、从缝里露出来(视差)。上下两条内阴影 = 上下模块压在房间上面。
只有「进去布置」按钮能点(整块不点,免得滚动时误触)。
图:assets/img/nursery-peek.jpg 是从 yef/cover-full.png 裁的临时图(390 宽、抹掉了「调试」按钮),
已找「3D 婴儿房」会话(33d6c837)要无 UI 的 1125×1500 日/夜两版高清渲染,到了换掉。
位置:今天的动线 → 这道空隙 → 书架(首页后半段)。北京时间 19:00–6:00 叠一层夜色。JS 在 app.js nurseryRender()。 -->
<section class="ny-peek" id="nursery">
<div class="nyp-layer" id="nypLayer" aria-hidden="true"><img src="assets/img/nursery-peek.jpg" alt="" decoding="async"></div>
<span class="nyp-chip">往里看看 · 3D 婴儿房</span>
<div class="nyp-bar">
<div class="nyp-txt"><b id="nyTitle">宝宝的房间</b><span id="nySub">做孕期任务攒兜豆,一件件布置好</span></div>
<button class="nyp-btn" id="nyBtn" type="button">进去布置 <svg viewBox="0 0 24 24" width="11" height="11" aria-hidden="true"><path d="M8 5l8 7-8 7z" fill="currentColor"/></svg></button>
</div>
</section>
<!-- 模块三 · 书架卡带。 <!-- 模块三 · 书架卡带。
上一版是「一张横排大卡 + 下面两张小卡」的老三段,和上面两个模块 上一版是「一张横排大卡 + 下面两张小卡」的老三段,和上面两个模块
一样都是往下堆。这里改成一条横向可滑的书架:三张等宽等高的**竖版**书 一样都是往下堆。这里改成一条横向可滑的书架:三张等宽等高的**竖版**书
@@ -534,56 +552,6 @@
<p class="unlock"><b>128</b> 份孕育资料等你来领 · <a data-open="detailResList">查看全部 ›</a></p> <p class="unlock"><b>128</b> 份孕育资料等你来领 · <a data-open="detailResList">查看全部 ›</a></p>
</section> </section>
<!-- 3D 婴儿房 · 入口(2026-10-01 二版)。用户原话:「放到首页……不用太靠前……有想法有创意一些,要合理一些,点击之后进入 3D 婴儿房」;
一版(今天的动线后面的小房子卡)被评「创意不够」,用户从 8 个方案里拍了组合 1+2+3+4:
④ 位置:书架之后、首页最底 —— 一路逛下来,最后「回家」看看宝宝的房间,不抢前面每天要用的工具;
② 门:走廊尽头一扇虚掩的拱门,墙上挂木牌「{小名}的房间」,门边一本撕页日历 = 入住倒计时(预产期 − 今天);
① 门缝里的房间随孕周一件件布置好:粉墙 → 窗帘 12 → 地毯 16 → 小床 20 → 风铃 24 → 夜灯 28 → 小熊 32 → 待产包 36 → 星星灯 39,
本周刚到的那件会「蹦」一下;下方 9 颗进度点 + 「下一件」预告;
③ 宝宝开口:门缝里冒出宝宝的对话气泡(「妈妈,我的房间布置好了吗?」等),几句轮播。
原方案是写进「宝宝来信」卡,但那张卡的内容来自 laixin 应用、点了去 laixin,不归首页管,所以改成宝宝在门里说话。
⚠️ 首页读不到婴儿房里真实的布置(对方数据在它自己域名的 localStorage),这里的布置进度**只按孕周推演**,和房里实际摆了什么无关;
对方接后端后可以换成真实进度。
点击:门整扇推开、房间往前一凑,约 0.7 秒后 jumpApp 进 https://beian3.douba.top/yef/ 。北京时间 19:00–6:00 换夜间版。JS 在 app.js nurseryRender()。 -->
<section class="nursery" id="nursery">
<button class="ny" id="nyCard" type="button" aria-label="推门进入 3D 婴儿房">
<span class="ny-head">
<span class="ny-eyebrow">逛完啦,回家看看 · 3D 婴儿房</span>
<span class="ny-title" id="nyTitle">宝宝的房间</span>
</span>
<span class="ny-scene" aria-hidden="true">
<span class="ny-glow"></span>
<span class="ny-way">
<span class="ny-room">
<svg viewBox="0 0 120 168" preserveAspectRatio="xMidYMax slice" aria-hidden="true">
<rect class="r-wall" width="120" height="120" fill="#FCE1E4"/>
<path d="M0 96h120" stroke="#F6C9D0" stroke-width="2"/>
<rect class="r-floor" y="118" width="120" height="50" fill="#F2D3BA"/>
<path d="M0 132h120M0 150h120M30 118v14M80 132v18M50 150v18" stroke="#E7BFA2" stroke-width="1.2"/>
<g class="r-win"><rect x="70" y="16" width="38" height="34" rx="16" fill="#fff"/><rect class="r-sky" x="73" y="19" width="32" height="28" rx="14" fill="#CFE9F2"/><circle class="r-moon" cx="96" cy="28" r="4" fill="#FFE2A8"/><path d="M89 19v28M73 34h32" stroke="#fff" stroke-width="2"/></g>
<g data-k="curtain"><path d="M66 12h10c-2 14 1 30 4 42H66z" fill="#F9C9A8"/><path d="M112 12h-10c2 14-1 30-4 42h14z" fill="#F9C9A8"/><path d="M64 11h50" stroke="#E0A07E" stroke-width="2.4" stroke-linecap="round"/></g>
<g data-k="stars"><path d="M30 6q44 16 90 2" fill="none" stroke="#E7B7A3" stroke-width="1"/><g fill="#FFD27A"><circle cx="40" cy="10" r="2.2"/><circle cx="54" cy="14" r="2.2"/><circle cx="68" cy="15.5" r="2.2"/><circle cx="82" cy="15" r="2.2"/><circle cx="96" cy="13" r="2.2"/><circle cx="110" cy="10" r="2.2"/></g></g>
<g data-k="rug"><ellipse cx="62" cy="148" rx="44" ry="11" fill="#A9DCC7"/><ellipse cx="62" cy="148" rx="34" ry="7" fill="none" stroke="#fff" stroke-width="1.4" stroke-dasharray="3 3"/></g>
<g data-k="mobile"><path d="M64 0v62M50 62h28" stroke="#C9A89A" stroke-width="1.2"/><path d="M50 62v8M64 62v12M78 62v8" stroke="#C9A89A" stroke-width=".8"/><circle cx="50" cy="73" r="3.4" fill="#F18DA2"/><path d="M64 74l2 4 4 .6-3 3 .7 4-3.7-2-3.7 2 .7-4-3-3 4-.6z" fill="#FFD27A"/><path d="M80 70a4 4 0 1 1-4 5 3 3 0 0 0 4-5z" fill="#A9DCC7"/></g>
<g data-k="crib"><rect x="30" y="98" width="68" height="8" rx="3" fill="#fff"/><rect x="32" y="112" width="64" height="12" rx="3" fill="#F7B5C2"/><path d="M38 106v18M46 106v18M54 106v18M62 106v18M70 106v18M78 106v18M86 106v18" stroke="#fff" stroke-width="2.4"/><rect x="28" y="88" width="6" height="52" rx="3" fill="#fff"/><rect x="94" y="88" width="6" height="52" rx="3" fill="#fff"/><rect x="30" y="124" width="68" height="5" rx="2" fill="#fff"/></g>
<g data-k="lamp"><circle class="r-glow" cx="110" cy="96" r="20" fill="#FFE2A8" opacity=".35"/><path d="M110 104v30" stroke="#C9A89A" stroke-width="2"/><ellipse cx="110" cy="135" rx="7" ry="2.4" fill="#C9A89A"/><path d="M102 104l3-12h10l3 12z" fill="#FFD9A8"/></g>
<g data-k="bear"><circle cx="54" cy="140" r="4" fill="#D9A27E"/><circle cx="66" cy="140" r="4" fill="#D9A27E"/><circle cx="60" cy="150" r="9" fill="#D9A27E"/><circle cx="60" cy="143" r="7.5" fill="#E2B08E"/><circle cx="57" cy="142" r="1" fill="#6B5048"/><circle cx="63" cy="142" r="1" fill="#6B5048"/><ellipse cx="60" cy="145.5" rx="2.4" ry="1.8" fill="#F6DCC8"/></g>
<g data-k="bag"><path d="M88 146a6 6 0 0 1 12 0" fill="none" stroke="#E0708A" stroke-width="2"/><rect x="84" y="146" width="20" height="16" rx="4" fill="#F18DA2"/><path d="M84 152h20" stroke="#fff" stroke-width="1.2"/><circle cx="94" cy="156" r="1.6" fill="#fff"/></g>
</svg>
</span>
<span class="ny-door"><i class="ny-knob"></i><i class="ny-heart"></i></span>
</span>
<span class="ny-bubble" id="nyBubble">妈妈,我的房间布置好了吗?</span>
<span class="ny-sign"><b id="nySign">宝宝的房间</b></span>
<span class="ny-cal"><span class="ny-cal-top" id="nyCalTop">入住倒计时</span><span class="ny-cal-num"><b id="nyCalNum">?</b><i id="nyCalUnit">天</i></span><span class="ny-cal-foot" id="nyCalFoot">填预产期</span></span>
</span>
<span class="ny-track" aria-hidden="true"><span class="ny-dots" id="nyDots"></span></span>
<span class="ny-foot">
<span class="ny-prog" id="nyProg">填好预产期,看看哪天搬进来</span>
<span class="ny-cta">推门进去 <svg viewBox="0 0 24 24" width="11" height="11"><path d="M8 5l8 7-8 7z" fill="currentColor"/></svg></span>
</span>
</button>
</section>
</div> </div>
</section> </section>

111
js/app.js
View File

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

View File

@@ -414,7 +414,7 @@ a.skip:focus{left:16px;top:16px}
对齐的是内容,出血的是底色,这两件事要分开。 对齐的是内容,出血的是底色,这两件事要分开。
⚠️ 三块的圆角、底色、左右内边距改成在这里一处定义。以后要调,改这一条, ⚠️ 三块的圆角、底色、左右内边距改成在这里一处定义。以后要调,改这一条,
别再回到各自的规则里单独写,那正是上一稿乱掉的原因。 */ 别再回到各自的规则里单独写,那正是上一稿乱掉的原因。 */
.tool-dock,.game-promo,.preg-tips,.today-line,.shelf,.nursery{ .tool-dock,.game-promo,.preg-tips,.today-line,.shelf{
margin:0 -26px;padding:16px 26px 18px;border-radius:26px; margin:0 -26px;padding:16px 26px 18px;border-radius:26px;
background-color:#fff; background-color:#fff;
/* ⚠️ 这两条是一对,缺一不可,而且必须一起看: /* ⚠️ 这两条是一对,缺一不可,而且必须一起看:
@@ -428,7 +428,7 @@ a.skip:focus{left:16px;top:16px}
/* 块间距。给后两块,第一块的上间距另有规则(来信卡收放要用)。 /* 块间距。给后两块,第一块的上间距另有规则(来信卡收放要用)。
⚠️ 这是**块与块之间**的缝。某一块自己内部觉得挤,要调的是那块的 padding, ⚠️ 这是**块与块之间**的缝。某一块自己内部觉得挤,要调的是那块的 padding,
不是这里 —— 改这里会把三块的关系一起改掉。 */ 不是这里 —— 改这里会把三块的关系一起改掉。 */
.tool-dock,.game-promo,.preg-tips,.today-line,.shelf,.nursery{margin-top:16px} .tool-dock,.game-promo,.preg-tips,.today-line,.shelf{margin-top:16px}
/* ===== 后两块的暖色渐变底(本批新增)===== /* ===== 后两块的暖色渐变底(本批新增)=====
用户原话:「在那个宝宝的今日脚印那块,稍微给背景它加一点渐变」 用户原话:「在那个宝宝的今日脚印那块,稍微给背景它加一点渐变」
@@ -560,106 +560,39 @@ a.skip:focus{left:16px;top:16px}
@keyframes gpTw{0%,100%{opacity:0;transform:scale(.6)}50%{opacity:1;transform:scale(1)}} @keyframes gpTw{0%,100%{opacity:0;transform:scale(.6)}50%{opacity:1;transform:scale(1)}}
@media (prefers-reduced-motion:reduce){.gp-cta,.gp-spark{animation:none}.gp-spark{opacity:.8}} @media (prefers-reduced-motion:reduce){.gp-cta,.gp-spark{animation:none}.gp-spark{opacity:.8}}
/* ============ 3D 婴儿房 · 入口(2026-10-01 二版:走廊尽头的门)============ /* ============ 3D 婴儿房 · 页面上的一道空隙(2026-10-01 三版)============
配色取婴儿房自己的奶油粉:主色 #E0708A、按钮 #F9B4C1→#F18DA2、底 #FFF9F3、蜜桃 #F9C9A8、薄荷 #A9DCC7、字 #6B5048。 这一块**故意没有底色、没有圆角卡片**:它是页面上开的缝,里面是铺在整页背后的房间。
场景坐标都是绝对定位,改尺寸时门(.ny-way)、气泡、木牌、日历四样一起看,别互相压住。 */ .nyp-layer 的 height / transform 由 JS 按 .screen 可视区每帧写入(见 app.js nurseryInit),这里别再给它写 top/transform。
.nursery{padding-top:14px;padding-bottom:16px} ::after = 上下模块压在房间上的阴影,要盖在图层之上、按钮之下。 */
.ny{position:relative;display:block;width:100%;overflow:hidden;border:0;border-radius:22px;padding:14px 16px 12px; .ny-peek{position:relative;margin:16px -26px 0;height:300px;overflow:hidden;background:#FFECD8}
text-align:left;font:inherit;color:#6B5048;background:linear-gradient(180deg,#FFF9F3 0%,#FFEEE9 100%); .nyp-layer{position:absolute;left:0;top:0;width:100%;height:812px;will-change:transform;
box-shadow:0 8px 22px rgba(224,112,138,.15);-webkit-tap-highlight-color:transparent} display:flex;align-items:center;justify-content:center;background:radial-gradient(ellipse at 50% 48%,#FFFDFB 0%,#FFF3E8 40%,#FFECD8 72%)}
.ny-head{display:flex;flex-direction:column;align-items:flex-start} .nyp-layer img{display:block;width:100%;max-width:440px;height:auto;
.ny-eyebrow{font-size:10.5px;font-weight:800;color:#E0708A;background:#fff;border-radius:999px;padding:2px 9px;letter-spacing:.3px} -webkit-mask-image:linear-gradient(180deg,transparent 0,#000 14%,#000 86%,transparent 100%);mask-image:linear-gradient(180deg,transparent 0,#000 14%,#000 86%,transparent 100%)}
.ny-title{margin-top:6px;font-size:17px;font-weight:900;color:#5A3E37;letter-spacing:.3px} .ny-peek::after{content:"";position:absolute;inset:0;pointer-events:none;z-index:1;
.ny-scene{position:relative;display:block;height:192px;margin:6px -16px 0} box-shadow:inset 0 16px 18px -14px rgba(120,70,50,.28),inset 0 -16px 18px -14px rgba(120,70,50,.28)}
.ny-scene::after{content:"";position:absolute;left:0;right:0;bottom:0;height:26px;background:linear-gradient(180deg,#F5DCCB,#F9E6DA)} .nyp-chip{position:absolute;left:26px;top:16px;z-index:2;font-size:11px;font-weight:800;color:#E0708A;letter-spacing:.3px;
/* 门洞:拱形,白色门框;里面是房间,外面盖一扇虚掩的门 */ background:rgba(255,255,255,.86);border-radius:999px;padding:3px 10px;box-shadow:0 2px 8px rgba(224,112,138,.15)}
.ny-way{position:absolute;left:28px;bottom:26px;width:112px;height:158px;border-radius:56px 56px 3px 3px; .nyp-bar{position:absolute;left:18px;right:18px;bottom:16px;z-index:2;display:flex;align-items:center;gap:10px;
box-shadow:0 0 0 6px #fff,0 6px 16px rgba(160,90,90,.18);z-index:1} padding:10px 10px 10px 14px;border-radius:18px;background:rgba(255,255,255,.82);
.ny-room{position:absolute;inset:0;overflow:hidden;border-radius:inherit;background:#FCE1E4} -webkit-backdrop-filter:blur(10px);backdrop-filter:blur(10px);box-shadow:0 6px 18px rgba(160,90,90,.16)}
.ny-room svg{position:absolute;left:0;bottom:0;width:100%;height:100%;transform-origin:50% 70%;transition:transform .7s var(--ease)} .nyp-txt{flex:1;min-width:0;display:flex;flex-direction:column;gap:2px}
.ny-room [data-k]{opacity:0;transform-box:fill-box;transform-origin:50% 100%} .nyp-txt b{font-size:15px;font-weight:900;color:#5A3E37;letter-spacing:.3px}
.ny-room [data-k].on{opacity:1} .nyp-txt span{font-size:11.5px;color:#8A6C63;line-height:1.4}
.ny-room [data-k].new{animation:nyPop 2.6s ease-in-out infinite} .nyp-txt span em{font-style:normal;color:#E0708A;font-weight:900}
@keyframes nyPop{0%,70%,100%{transform:translateY(0) scale(1)}78%{transform:translateY(-4px) scale(1.06)}86%{transform:translateY(0) scale(.98)}} .nyp-btn{flex:0 0 auto;display:inline-flex;align-items:center;gap:3px;border:0;border-radius:999px;padding:8px 14px;font:inherit;
.ny-room [data-k="mobile"].on{transform-origin:50% 0;animation:nySwing 4s ease-in-out infinite} font-size:13px;font-weight:800;color:#fff;background:linear-gradient(135deg,#F9B4C1,#F18DA2);box-shadow:0 4px 10px rgba(224,112,138,.32);
@keyframes nySwing{0%,100%{transform:rotate(-3deg)}50%{transform:rotate(3deg)}} animation:nypNudge 2.8s ease-in-out infinite}
.ny-room .r-moon{opacity:0} .nyp-btn:active{transform:scale(.96)}
.ny-door{position:absolute;inset:0;border-radius:inherit;background:linear-gradient(90deg,#FFF6EF,#FBE6DA); @keyframes nypNudge{0%,80%,100%{transform:translateX(0)}86%{transform:translateX(3px)}92%{transform:translateX(0)}}
transform-origin:0 50%;transform:perspective(380px) rotateY(62deg);transition:transform .7s var(--ease); /* 夜间:房间整体压暗,中间留一团暖灯 */
box-shadow:inset 0 0 0 2px #F3D3C2,6px 0 12px rgba(120,60,60,.18)} .ny-peek.is-night .nyp-layer::after{content:"";position:absolute;inset:0;
.ny-door::before,.ny-door::after{content:"";position:absolute;left:14px;right:14px;border:2px solid #F3D3C2} background:radial-gradient(ellipse at 50% 46%,rgba(255,200,120,.18) 0%,rgba(46,32,70,.32) 45%,rgba(30,22,52,.62) 100%)}
.ny-door::before{top:16px;height:58px;border-radius:40px 40px 6px 6px} .ny-peek.is-night .nyp-bar{background:rgba(58,47,79,.78)}
.ny-door::after{bottom:14px;height:52px;border-radius:6px} .ny-peek.is-night .nyp-txt b{color:#FFF3F5}
.ny-knob{position:absolute;right:11px;top:56%;width:9px;height:9px;border-radius:50%;background:#E3AC72;box-shadow:inset -2px -2px 0 rgba(0,0,0,.08)} .ny-peek.is-night .nyp-txt span{color:#D9C5D0}
.ny-heart{position:absolute;left:50%;top:38px;width:12px;height:12px;margin-left:-6px;background:#F18DA2; .ny-peek.is-night .nyp-txt span em{color:#FFB3C3}
clip-path:path("M6 11L1.2 6.2A3 3 0 0 1 6 2.4 3 3 0 0 1 10.8 6.2Z")} @media (prefers-reduced-motion:reduce){.nyp-btn{animation:none}}
/* 门缝里漏出来的暖光,铺在走廊地上 */
.ny-glow{position:absolute;left:46px;bottom:0;width:150px;height:28px;z-index:0;opacity:.75;transition:opacity .5s;
background:linear-gradient(180deg,rgba(255,214,150,.75),rgba(255,214,150,0));clip-path:polygon(0 0,62% 0,100% 100%,6% 100%)}
.ny-glow{z-index:1}
/* 宝宝从门缝里说话 */
.ny-bubble{position:absolute;left:116px;top:52px;z-index:3;box-sizing:border-box;max-width:104px;padding:6px 10px;border-radius:12px 12px 12px 3px;
background:#fff;color:#6B5048;font-size:11.5px;font-weight:700;line-height:1.45;box-shadow:0 4px 12px rgba(160,90,90,.18);
transition:opacity .35s,transform .35s}
.ny-bubble.out{opacity:0;transform:translateY(4px)}
/* 木牌 */
.ny-sign{position:absolute;right:18px;top:16px;z-index:2;transform-origin:50% -14px;animation:nySwing 5s ease-in-out infinite}
.ny-sign::before{content:"";position:absolute;left:22%;right:22%;top:-13px;height:13px;border:1.5px solid #C99A7A;border-bottom:0;border-radius:50% 50% 0 0}
.ny-sign::after{content:"";position:absolute;left:50%;top:-17px;width:6px;height:6px;margin-left:-3px;border-radius:50%;background:#B9876A}
.ny-sign b{display:block;padding:6px 12px;border-radius:8px;background:linear-gradient(180deg,#EBC09A,#DCA683);color:#fff;
font-size:13px;font-weight:900;letter-spacing:.5px;white-space:nowrap;text-shadow:0 1px 0 rgba(150,90,60,.35);
box-shadow:inset 0 0 0 2px rgba(255,255,255,.35),0 3px 6px rgba(150,90,60,.2)}
/* 撕页日历 = 入住倒计时 */
.ny-cal{position:absolute;right:22px;bottom:34px;z-index:2;width:80px;border-radius:9px;background:#fff;overflow:hidden;
display:flex;flex-direction:column;align-items:center;box-shadow:0 5px 12px rgba(160,90,90,.18);transform:rotate(2deg)}
.ny-cal-top{align-self:stretch;text-align:center;padding:4px 0 3px;font-size:10px;font-weight:800;color:#fff;
background:linear-gradient(135deg,#F9B4C1,#F18DA2);position:relative}
.ny-cal-num{display:flex;align-items:baseline;gap:1px;margin-top:2px;color:#E0708A}
.ny-cal-num b{font-size:28px;font-weight:900;line-height:1.1;font-variant-numeric:tabular-nums}
.ny-cal-num i{font-style:normal;font-size:11px;font-weight:800}
.ny-cal-foot{width:100%;text-align:center;padding:2px 0 5px;font-size:10px;color:#A0857C;border-top:1px dashed #F3D3C2}
/* 进度:9 颗点 */
.ny-track{display:block;margin-top:10px}
.ny-dots{position:relative;display:flex;justify-content:space-between;align-items:center}
.ny-dots::before{content:"";position:absolute;left:4px;right:4px;top:50%;height:2px;margin-top:-1px;background:#F6DCD3}
.ny-dots::after{content:"";position:absolute;left:4px;top:50%;height:2px;margin-top:-1px;width:var(--p,0%);background:#F18DA2;border-radius:2px}
.ny-dots i{position:relative;z-index:1;width:8px;height:8px;border-radius:50%;background:#fff;box-shadow:inset 0 0 0 2px #F6DCD3}
.ny-dots i.on{background:#F18DA2;box-shadow:none}
.ny-dots i.cur{width:12px;height:12px;box-shadow:0 0 0 3px rgba(241,141,162,.28);animation:nyPulse 1.8s ease-in-out infinite}
@keyframes nyPulse{50%{box-shadow:0 0 0 6px rgba(241,141,162,0)}}
.ny-foot{display:flex;align-items:center;gap:8px;margin-top:9px}
.ny-prog{flex:1;min-width:0;font-size:12px;color:#8A6C63;line-height:1.4}
.ny-prog b{color:#E0708A;font-weight:900}
.ny-cta{flex:0 0 auto;display:inline-flex;align-items:center;gap:3px;border-radius:999px;padding:6px 13px;font-size:12.5px;font-weight:800;
color:#fff;background:linear-gradient(135deg,#F9B4C1,#F18DA2);box-shadow:0 4px 10px rgba(224,112,138,.3)}
/* 点击:门推开、房间凑近 */
.ny.is-opening .ny-door{transform:perspective(380px) rotateY(88deg)}
.ny.is-opening .ny-room svg{transform:scale(1.22)}
.ny.is-opening .ny-glow{opacity:1}
.ny.is-opening .ny-bubble{opacity:0}
/* 已出生:门敞开 */
.nursery.is-born .ny-door{transform:perspective(380px) rotateY(80deg)}
/* 夜间(北京时间 19:00–6:00) */
.nursery.is-night .ny{color:#F4E3E7;background:linear-gradient(180deg,#3A2F4F 0%,#4B3756 100%);box-shadow:0 8px 22px rgba(58,47,79,.3)}
.nursery.is-night .ny-scene::after{background:linear-gradient(180deg,#2E2540,#3A2E4A)}
.nursery.is-night .ny-title{color:#FFF3F5}
.nursery.is-night .ny-eyebrow{background:rgba(255,255,255,.14);color:#FFC6D2}
.nursery.is-night .ny-prog{color:#D9C5D0}
.nursery.is-night .ny-prog b{color:#FFB3C3}
.nursery.is-night .ny-way{box-shadow:0 0 0 6px #6A5478,0 0 26px rgba(255,200,120,.45)}
.nursery.is-night .ny-door{background:linear-gradient(90deg,#6A5478,#5A4868);box-shadow:inset 0 0 0 2px #7D6690}
.nursery.is-night .ny-door::before,.nursery.is-night .ny-door::after{border-color:#7D6690}
.nursery.is-night .ny-room .r-wall{fill:#E7C2CF}
.nursery.is-night .ny-room .r-floor{fill:#DDB89E}
.nursery.is-night .ny-room .r-sky{fill:#3E3A66}
.nursery.is-night .ny-room .r-moon{opacity:1}
.nursery.is-night .ny-room .r-glow{opacity:.75}
.nursery.is-night .ny-room svg{filter:sepia(.18) saturate(1.15)}
.nursery.is-night .ny-glow{opacity:1}
.nursery.is-night .ny-dots::before{background:#5E4A70}
.nursery.is-night .ny-dots i{background:#4B3756;box-shadow:inset 0 0 0 2px #6A5478}
.nursery.is-night .ny-dots i.on{background:#F18DA2;box-shadow:none}
@media (prefers-reduced-motion:reduce){.ny-room [data-k],.ny-sign,.ny-dots i.cur{animation:none!important}}
/* ============ 孕期小知识(2026-09-30)============ /* ============ 孕期小知识(2026-09-30)============
外壳是公共白色通栏。里面三层: 外壳是公共白色通栏。里面三层: