feat(首页): 孕期盲盒横幅 + 盲盒页(开盒动画/翻卡/卡册/概率公示)+「此刻,孕妈们在做什么」卡 (v26100101)

- 横幅:顶部大图下方,按 /blindbox 状态切换文案(可开 / 补开 / 下周倒计时 / 填预产期引导),礼盒晃动 + 扫光
- 盲盒页:开盒舞台(光芒旋转、礼盒悬浮,开启时越晃越烈→盖子飞起→白光+彩纸→3D 翻卡),闪卡镭射光;
  卡册按孕早/中/晚期分组,补发标「补」,错过的周可补开;规则与概率说明由接口下发
- 此刻卡:大数字进入视口时数上去、每分钟平滑刷新;今日三项可点进工具;匿名动态每 3 秒上翻
- 新增 14 张周卡插画(其余孕周先用本周水果占位)
- AGENTS.md §4.12 登记模拟数据例外(决策人/日期/期限 2026-12-31)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-01 01:44:44 +00:00
parent 4c550882fe
commit c414a7ff8f
18 changed files with 800 additions and 2 deletions

View File

@@ -80,6 +80,12 @@ nginx:`root /opt/app` + `Cache-Control: no-cache` + `etag on` —— 缓存靠
⚠️ 卡是固定高度 bento,只有 kick / weight 有位置放第二行(`.mc-extra`);给 fweight/cal/curve 加第二行前 ⚠️ 卡是固定高度 bento,只有 kick / weight 有位置放第二行(`.mc-extra`);给 fweight/cal/curve 加第二行前
先重排那笔高度账(`158+10+64=232`,见 `mama-box.css`)。 先重排那笔高度账(`158+10+64=232`,见 `mama-box.css`)。
12. **⚠️ 第 2 条「不造假数据」的唯一登记例外:首页「此刻,孕妈们在做什么」卡(2026-10-01 起)**
- 内容:该卡的总人数 / 今日三项 / 匿名动态在**模拟阶段**为 box_app 按规则生成的模拟数字(`GET /pulse`,确定性哈希、不落库、不进任何报表)。
- 决策人:产品负责人(用户本人),2026-10-01 拍板「前期先用模拟数据,要显示人多;正式页先用模拟,真实数据跟上后替换」;已书面告知虚假宣传风险(《反不正当竞争法》第 8 条)。
- 期限:**最晚 2026-12-31** 复核(≤ 3 个月);真实数据接通后在后台把 `MamaBoxConfig.pulseMode` 改成 `real`(或 `off`),无需发版。
- 边界:**只限这张卡**。孕期盲盒的数字(已收集、本周开盒人数)一律真实;任何别处都不得引用这里的模拟数字,前端也不得在接口失败时自造数字兜底。
## 5. 踩坑 ## 5. 踩坑
- **不要给别的应用追加 `#openid`**:部分应用自带 hash 路由(如 `/baobao-yingji`),追加 hash 会被当非法路由 → 页面未找到。身份一律走 query(`withIdentity()` 已封装)。 - **不要给别的应用追加 `#openid`**:部分应用自带 hash 路由(如 `/baobao-yingji`),追加 hash 会被当非法路由 → 页面未找到。身份一律走 query(`withIdentity()` 已封装)。

BIN
assets/img/0fa71babe9a2.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

BIN
assets/img/2314f334e4d0.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

BIN
assets/img/2a88a5a7c1c3.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

BIN
assets/img/5ae2e7f617e2.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

BIN
assets/img/6a17a98c52b8.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

BIN
assets/img/6bdabd9eecc5.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

BIN
assets/img/83f0ab30cf36.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

BIN
assets/img/affcfc61f21f.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

BIN
assets/img/b743ed8646d5.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

BIN
assets/img/bddd81b7315d.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

BIN
assets/img/c6e9c5612d8e.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

BIN
assets/img/cceeb3b3f4ef.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

BIN
assets/img/d41305de1db9.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

BIN
assets/img/fe00c2bddf9b.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 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:26093025 缓存版本标记:改 css/js 时同步 bump 该值(与 js/app.js 的 VER 同源),静态资源按 mtime 走 ETag 自动刷新 --> <!-- V:26100101 缓存版本标记:改 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 第四节) -->
@@ -182,6 +182,19 @@
宫格右下角那张 .mini-card.more 是**内容**位(顺带把第三列剩下的 宫格右下角那张 .mini-card.more 是**内容**位(顺带把第三列剩下的
44px 填平,而且是个够手指点的大靶子)。两个都指向 data-sheet="open", 44px 填平,而且是个够手指点的大靶子)。两个都指向 data-sheet="open",
删掉任意一个另一个都还走得通。 --> 删掉任意一个另一个都还走得通。 -->
<!-- 孕期盲盒 · 首页细横幅(2026-10-01,需求文档 v1.0 三-7)。
放在顶部大图正下方(需求:顶部大图 → 盲盒横幅 → 孕育工具 → 此刻 → 消消乐)。
文案/状态全由 app.js renderBanner() 按 /blindbox 返回写入;访客态(无 openid)、孕 4 周前、接口失败 → 保持 .hide。
⚠️ 它是 #homeBody 的第一个孩子,吃的是 .home-body>:first-child 那条上间距;
隐藏时由 CSS「.bb-banner.hide + .tool-dock」把间距还给工具托盘。 -->
<button class="bb-banner hide" id="bbBanner" type="button">
<span class="bb-gift" id="bbGift" aria-hidden="true"></span>
<span class="bb-badge hide" id="bbBadge" aria-hidden="true"></span>
<span class="bb-txt"><b class="bb-t" id="bbTitle"></b><span class="bb-s" id="bbSub"></span></span>
<span class="bb-cta" id="bbCta"></span>
<i class="bb-shine" aria-hidden="true"></i>
</button>
<section class="tool-dock"> <section class="tool-dock">
<!-- 大字水印。aria-hidden:读屏念到这块该直接进标题, <!-- 大字水印。aria-hidden:读屏念到这块该直接进标题,
不该先听见两声「妈妈」。样式和理由都在 css/style.css 的 .wm 上。 --> 不该先听见两声「妈妈」。样式和理由都在 css/style.css 的 .wm 上。 -->
@@ -274,6 +287,24 @@
</div> </div>
</section> </section>
<!-- 此刻,孕妈们在做什么(2026-10-01,需求文档 v1.0 第二节)。纯展示,所有人看到同一份。
数据 GET /api/mp/public/mama-box/pulse(box_app 按后台 pulseMode 给模拟 / 真实 / 关闭),渲染在 app.js pulseCard()。
⚠️ 模拟数据是产品负责人 2026-10-01 拍板的例外(AGENTS.md §4.12),只限这张卡。
show=false 或接口失败 → 整块保持 .hide。 -->
<section class="pulse hide" id="pulse" aria-label="此刻,孕妈们在做什么">
<span class="pl-orb o1" aria-hidden="true"></span><span class="pl-orb o2" aria-hidden="true"></span>
<div class="pl-head">
<span class="pl-live"><i></i>此刻</span>
<h2 class="pl-title">孕妈们在做什么</h2>
</div>
<div class="pl-total hide" id="plTotal">
<span class="pl-avs" aria-hidden="true"><i></i><i></i><i></i><i></i></span>
<span class="pl-tt"><span id="plPre"></span><b id="plNum">0</b><span id="plSuf"></span></span>
</div>
<div class="pl-today hide" id="plToday"></div>
<div class="pl-feed hide" id="plFeed"><div class="pl-track" id="plTrack"></div></div>
</section>
<!-- 宝宝消消乐 · 推广入口(2026-09-30)。用户原话:「做了一个消消乐小游戏(待产清单主题), <!-- 宝宝消消乐 · 推广入口(2026-09-30)。用户原话:「做了一个消消乐小游戏(待产清单主题),
放到首页展示并推广,宝妈们业余时间玩,名字叫宝宝消消乐」。 放到首页展示并推广,宝妈们业余时间玩,名字叫宝宝消消乐」。
放在工具托盘正下方:首屏滑一下就能看到。右侧 3×3 小棋盘是纯装饰动画 放在工具托盘正下方:首屏滑一下就能看到。右侧 3×3 小棋盘是纯装饰动画
@@ -1412,6 +1443,64 @@
</div> </div>
</section> </section>
<!-- ================= 孕期盲盒(2026-10-01,需求文档 v1.0 第三节) =================
上半是「开盒舞台」,下半是卡册(按孕早/中/晚期分三组)+ 规则与概率说明(合规:公示每档概率)。
.bx-reveal 是开盒后的翻卡层,盖在本页之上。全部渲染在 app.js 的 blindBox 段。 -->
<section class="detail bx" id="detailBox">
<div class="detail-bar">
<button class="dt-back" data-detail="close">‹</button>
<span class="dt-title">孕期盲盒</span>
<button class="dt-plain bx-rule-btn" id="bxRuleBtn" type="button">规则</button>
</div>
<div class="detail-body bx-body">
<div class="bx-stage" id="bxStage">
<span class="bx-rays" aria-hidden="true"></span>
<span class="bx-sp s1" aria-hidden="true">✦</span><span class="bx-sp s2" aria-hidden="true">✦</span><span class="bx-sp s3" aria-hidden="true">✦</span><span class="bx-sp s4" aria-hidden="true">✦</span>
<div class="bx-week" id="bxWeek"></div>
<div class="bx-hint" id="bxHint"></div>
<button class="bx-box" id="bxBox" type="button" aria-label="开启盲盒"></button>
<button class="bx-mini hide" id="bxMini" type="button" aria-label="查看这张宝宝卡"></button>
<span class="bx-shadow" aria-hidden="true"></span>
<button class="bx-open" id="bxOpen" type="button"></button>
<div class="bx-misc" id="bxMisc"></div>
</div>
<div class="bx-pend hide" id="bxPend">
<div class="bx-pend-t">还有 <b id="bxPendN"></b> 个盲盒等你打开 <em>错过的周会一直保留到孕 40 周</em></div>
<div class="bx-chips" id="bxChips"></div>
</div>
<section class="bx-album">
<div class="bx-ah">
<h3>我的宝宝卡册</h3>
<span class="bx-cnt"><b id="bxGot">0</b>/<span id="bxAll">37</span></span>
</div>
<div class="bx-prog"><i id="bxBar"></i></div>
<p class="bx-anote" id="bxAnote"></p>
<div id="bxGroups"></div>
</section>
<details class="bx-rules" id="bxRules">
<summary>规则与概率说明</summary>
<div class="bx-rules-b" id="bxRulesBody"></div>
</details>
</div>
<div class="bx-reveal" id="bxReveal" aria-hidden="true">
<span class="bx-glow" aria-hidden="true"></span>
<span class="bx-flash" aria-hidden="true"></span>
<div class="bx-confetti" id="bxConfetti" aria-hidden="true"></div>
<div class="bx-rv-in">
<div class="bx-tag" id="bxTag"></div>
<div class="bx-persp">
<div class="bx-card3d" id="bxCard3d">
<div class="bx-face bx-back" id="bxBack"></div>
<div class="bx-face bx-front" id="bxFront"></div>
</div>
</div>
<p class="bx-dev" id="bxDev"></p>
<div class="bx-reward" id="bxReward"></div>
<button class="bx-keep" id="bxKeep" type="button"></button>
</div>
</div>
</section>
<!-- ================= 记一下 底部弹层 ================= --> <!-- ================= 记一下 底部弹层 ================= -->
<div class="scrim" id="scrim"></div> <div class="scrim" id="scrim"></div>
<section class="sheet" id="sheet"> <section class="sheet" id="sheet">

385
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 = '26093025'; var VER = '26100101';
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));
@@ -2735,6 +2735,7 @@
applyRemoteFootprint(); applyRemoteFootprint();
applyRemoteCardsFetch(); applyRemoteCardsFetch();
syncLaixinReminder(); syncLaixinReminder();
loadBlindbox(); // 孕期盲盒:要等 ② 的预产期落地(服务端按同一份上游视图算孕周)
}); });
} }
/* ---------- 远程配置(box_app 后台下发):已下线 ---------- /* ---------- 远程配置(box_app 后台下发):已下线 ----------
@@ -3086,6 +3087,388 @@
}, 2600); }, 2600);
})(); })();
/* ================= 此刻,孕妈们在做什么(2026-10-01,需求文档 v1.0 第二节) =================
GET /api/mp/public/mama-box/pulse(免登录、所有人同一份)→ { show, total, today[], feed[] }。
· 数据是模拟还是真实由 box_app 后台 pulseMode 决定,前端不区分、不兜底造数:show=false / 接口失败 → 整卡隐藏。
⚠️ 模拟阶段是 AGENTS.md §4.12 登记的例外,只限这张卡;别把这里的「失败兜底」改成前端自造数字。
· 动效:进入视口时大数字从 0 数上去;之后每 60 秒刷新一次,数字平滑涨到新值;动态每 3 秒往上翻一条。
· 今日三项可点:kick / weight 复用首页工具卡的跳转(MINI_APP_MAP),game 去消消乐。 */
const PULSE_ICO = {
kick: '<svg viewBox="0 0 24 24"><ellipse cx="10" cy="15" rx="5" ry="6.5" fill="currentColor"/><circle cx="16.5" cy="6" r="2" fill="currentColor"/><circle cx="12.5" cy="4.6" r="1.6" fill="currentColor"/><circle cx="19.4" cy="9.6" r="1.6" fill="currentColor"/></svg>',
weight: '<svg viewBox="0 0 24 24"><rect x="3" y="4" width="18" height="16" rx="5" fill="none" stroke="currentColor" stroke-width="2.2"/><path d="M8 10a4 4 0 018 0z" fill="currentColor"/><path d="M12 10l1.6-2.4" stroke="currentColor" stroke-width="1.6" stroke-linecap="round"/></svg>',
cal: '<svg viewBox="0 0 24 24"><rect x="3.5" y="5" width="17" height="15" rx="3.5" fill="none" stroke="currentColor" stroke-width="2.2"/><path d="M3.5 10h17M8 3v4M16 3v4" stroke="currentColor" stroke-width="2.2" stroke-linecap="round"/></svg>',
game: '<svg viewBox="0 0 24 24"><rect x="3" y="3" width="8" height="8" rx="2.5" fill="currentColor"/><rect x="13" y="3" width="8" height="8" rx="2.5" fill="currentColor" opacity=".6"/><rect x="3" y="13" width="8" height="8" rx="2.5" fill="currentColor" opacity=".6"/><rect x="13" y="13" width="8" height="8" rx="2.5" fill="currentColor"/></svg>',
box: '<svg viewBox="0 0 24 24"><rect x="4" y="11" width="16" height="10" rx="2" fill="currentColor"/><rect x="2.5" y="7.5" width="19" height="4.5" rx="1.6" fill="currentColor"/><path d="M12 7.5V21" stroke="#fff" stroke-width="2"/><path d="M12 7.5C10 3.5 6 4 7.5 6.6 8.3 7.8 10.4 7.6 12 7.5zM12 7.5c2-4 6-3.5 4.5-.9-.8 1.2-2.9 1-4.5.9z" fill="currentColor"/></svg>'
};
const fmtN = n => String(Math.round(n)).replace(/\B(?=(\d{3})+(?!\d))/g, ',');
/* 数字补间:el.dataset.v 记当前值,从它平滑走到 to;首次(无 dataset.v)从 0 起 */
function tweenNum(el, to, dur) {
if (!el) return;
const from = el.dataset.v == null ? 0 : +el.dataset.v;
el.dataset.v = to;
if (from === to || (window.matchMedia && matchMedia('(prefers-reduced-motion: reduce)').matches)) { el.textContent = fmtN(to); return; }
const t0 = performance.now(), d = dur || 1400;
(function step(t) {
const k = Math.min(1, (t - t0) / d), e = 1 - Math.pow(1 - k, 4);
el.textContent = fmtN(from + (to - from) * e);
if (k < 1) requestAnimationFrame(step);
})(t0);
}
(function pulseCard() {
const sec = $('#pulse');
if (!sec) return;
let seen = false, last = null, feed = [], fi = 0, ticking = null;
const TODAY_GO = {
kick: () => { const b = $('.mini-card[data-mini="kick"]'); if (b) b.click(); },
weight: () => { const b = $('.mini-card[data-mini="weight"]'); if (b) b.click(); },
game: () => { if (GAME_URL) jumpApp(GAME_URL, GAME_ID); }
};
function rowHTML(f) {
/* 「孕 29 周的妈妈 数完胎动,宝宝动了 13 次」→ 上行「孕 29 周的妈妈 · 刚刚」,下行动作;数字标色 */
const t = String(f.text || ''), i = t.indexOf('的妈妈 ');
const who = i > 0 ? t.slice(0, i + 3) : '一位妈妈', what = i > 0 ? t.slice(i + 4) : t;
const esc = s => s.replace(/[&<>"]/g, c => ({ '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;' })[c]);
return '<div class="pl-row a-' + (PULSE_ICO[f.act] ? f.act : 'box') + '"><span class="pr-ico">' + (PULSE_ICO[f.act] || PULSE_ICO.box) +
'</span><span class="pr-t"><span class="pr-who">' + esc(who) + ' · ' + esc(f.ago || '刚刚') + '</span><span class="pr-do">' +
esc(what).replace(/(\d+)/g, '<em>$1</em>') + '</span></span></div>';
}
function tick() {
const tr = $('#plTrack');
if (!tr || feed.length < 2 || document.hidden) return;
fi = (fi + 1) % feed.length;
tr.insertAdjacentHTML('beforeend', rowHTML(feed[fi]));
tr.classList.add('is-move');
tr.style.transform = 'translateY(-46px)';
setTimeout(() => {
tr.classList.remove('is-move');
if (tr.firstElementChild) tr.removeChild(tr.firstElementChild);
tr.style.transform = '';
}, 600);
}
function paint() {
const r = last;
if (!r || !r.show) { sec.classList.add('hide'); return; }
sec.classList.remove('hide');
const tot = $('#plTotal');
tot.classList.toggle('hide', !r.total);
if (r.total) {
$('#plPre').textContent = r.total.prefix || '';
$('#plSuf').textContent = r.total.suffix || '';
if (seen) tweenNum($('#plNum'), r.total.value, 1800);
}
const td = $('#plToday'), items = r.today || [];
td.classList.toggle('hide', !items.length);
const keys = items.map(x => x.key).join(',');
if (td.dataset.keys !== keys) {
td.dataset.keys = keys;
td.innerHTML = items.map(x => '<button class="pl-stat k-' + x.key + '" type="button" data-k="' + x.key + '"><span class="ps-ico">' +
(PULSE_ICO[x.key] || PULSE_ICO.box) + '</span><b>0</b><span>' + String(x.label || '').replace(/[<>&]/g, '') + '</span><i class="go">›</i></button>').join('');
}
if (seen) items.forEach(x => tweenNum(td.querySelector('[data-k="' + x.key + '"] b'), x.value, 1600));
feed = r.feed || [];
const fe = $('#plFeed');
fe.classList.toggle('hide', !feed.length);
if (feed.length) {
fi = 0;
$('#plTrack').innerHTML = rowHTML(feed[0]);
if (!ticking) ticking = setInterval(tick, 3000);
}
}
$('#plToday').addEventListener('click', e => {
const b = e.target.closest('.pl-stat');
if (b && TODAY_GO[b.dataset.k]) { haptic(); TODAY_GO[b.dataset.k](); }
});
function load() {
return fetch('/api/mp/public/mama-box/pulse')
.then(r => (r.ok ? r.json() : null))
.then(j => { if (j && j.code === 200 && j.result) { last = j.result; paint(); } })
.catch(() => { /* 静默:保持现状(首次失败即一直隐藏) */ });
}
/* 进入视口才开始数数,否则用户滑到这里时数字早就数完了 */
function onSeen() {
if (seen) return;
seen = true;
paint();
}
if ('IntersectionObserver' in window) {
const io = new IntersectionObserver(es => { if (es.some(e => e.isIntersecting)) { io.disconnect(); onSeen(); } }, { threshold: .25 });
io.observe(sec);
} else seen = true;
load();
setInterval(() => { if (!document.hidden) load(); }, 60000);
})();
/* ================= 孕期盲盒(2026-10-01,需求文档 v1.0 第三节) =================
接口(box_app,openid 身份):
GET /api/mp/public/mama-box/blindbox?openid= → 横幅 + 本页的全部状态(含卡册、概率表)
POST /api/mp/public/mama-box/blindbox/open?openid=&week= → 开盒(服务端抽卡/抽奖励,原子去重)
· 抽什么完全由服务端决定,前端只播动画;概率说明用接口下发的 probs,和服务端永远一致(合规:公示概率)。
· 访客态 / 孕 4 周前 / 接口失败 → 横幅保持隐藏。未填预产期 → 横幅改成「填预产期领盲盒」引导。
· 周卡插画在 assets/img(文件名 = md5 前 12 位),CARD_IMG 是孕周 → 文件的表;没有图的孕周用「本周水果」占位画。 */
const CARD_IMG = {
4: 'b743ed8646d5', 5: 'bddd81b7315d', 6: 'affcfc61f21f', 7: 'fe00c2bddf9b', 8: '2a88a5a7c1c3', 9: '6a17a98c52b8',
10: 'c6e9c5612d8e', 11: '5ae2e7f617e2', 12: '0fa71babe9a2', 13: '6bdabd9eecc5', 15: 'd41305de1db9', 16: 'cceeb3b3f4ef',
24: '83f0ab30cf36', 32: '2314f334e4d0'
};
const BX_GROUPS = [
{ name: '孕早期', from: 4, to: 13, c: '#9ED8C0' },
{ name: '孕中期', from: 14, to: 27, c: '#FFB08F' },
{ name: '孕晚期', from: 28, to: 40, c: '#C9B6FF' }
];
let BB = null, bbSel = null, bbBusy = false, bbNew = null, giftSeq = 0;
function giftSVG() {
const g = 'gf' + (++giftSeq);
return '<svg class="gift" viewBox="0 0 120 120" aria-hidden="true"><defs>' +
'<linearGradient id="' + g + 'b" x1="0" y1="0" x2="1" y2="1"><stop offset="0" stop-color="#FF9677"/><stop offset="1" stop-color="#EE5A43"/></linearGradient>' +
'<linearGradient id="' + g + 'l" x1="0" y1="0" x2="0" y2="1"><stop offset="0" stop-color="#FFB49A"/><stop offset="1" stop-color="#FF8064"/></linearGradient>' +
'<linearGradient id="' + g + 'r" x1="0" y1="0" x2="0" y2="1"><stop offset="0" stop-color="#FFF6D6"/><stop offset="1" stop-color="#FFD36E"/></linearGradient></defs>' +
'<g class="g-body"><rect x="20" y="54" width="80" height="56" rx="11" fill="url(#' + g + 'b)"/>' +
'<rect x="20" y="54" width="80" height="9" fill="#9B2B1E" opacity=".12"/>' +
'<rect x="53" y="54" width="14" height="56" fill="url(#' + g + 'r)"/>' +
'<circle cx="34" cy="80" r="3.2" fill="#fff" opacity=".55"/><circle cx="86" cy="94" r="2.4" fill="#fff" opacity=".45"/>' +
'<path d="M84 68l1.6 3.8 3.8 1.6-3.8 1.6L84 78.8l-1.6-3.8-3.8-1.6 3.8-1.6z" fill="#fff" opacity=".8"/></g>' +
'<g class="g-lid"><path d="M60 38C51 20 32 17 33 29c1 9 15 10 27 9z" fill="url(#' + g + 'r)" stroke="#EFAE3C" stroke-width="2"/>' +
'<path d="M60 38c9-18 28-21 27-9-1 9-15 10-27 9z" fill="url(#' + g + 'r)" stroke="#EFAE3C" stroke-width="2"/>' +
'<rect x="14" y="36" width="92" height="22" rx="8" fill="url(#' + g + 'l)"/>' +
'<rect x="14" y="36" width="92" height="7" rx="4" fill="#fff" opacity=".2"/>' +
'<rect x="52" y="36" width="16" height="22" fill="url(#' + g + 'r)"/>' +
'<circle cx="60" cy="37.5" r="6.5" fill="#FFD36E" stroke="#EFAE3C" stroke-width="2"/></g></svg>';
}
const bbEsc = s => String(s == null ? '' : s).replace(/[&<>"]/g, c => ({ '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;' })[c]);
function cardImgHTML(w) {
const h = CARD_IMG[w];
return h ? '<img src="assets/img/' + h + '.jpg" alt="孕 ' + w + ' 周宝宝卡" decoding="async">'
: '<span class="cf-fb" aria-hidden="true">' + babySize(w).emoji + '</span>';
}
/* 卡正面。card = { week, v, size:{name,txt}, say } */
function cardFrontHTML(c) {
const sz = c.size || babySize(c.week);
return '<div class="cf v-' + (c.v === 'flash' ? 'flash' : 'normal') + '">' +
'<span class="cf-tag">✦ 闪卡</span><span class="cf-no">No.' + String(c.week).padStart(2, '0') + '</span>' +
'<div class="cf-img">' + cardImgHTML(c.week) + '</div>' +
'<div class="cf-row"><span class="cf-wk">孕<b>' + c.week + '</b>周</span><span class="cf-size">像<b>' + bbEsc(sz.name) + '</b> · ' + bbEsc(sz.txt) + '</span></div>' +
'<p class="cf-say">' + (c.say ? '“' + bbEsc(c.say) + '”' : '') + '</p>' +
'<i class="cf-holo"></i></div>';
}
function bbApi(path, method) {
const oid = hashParam('openid');
return fetch('/api/mp/public/mama-box/' + path + (path.indexOf('?') >= 0 ? '&' : '?') + 'openid=' + encodeURIComponent(oid), { method: method || 'GET' })
.then(r => r.json().catch(() => null));
}
function loadBlindbox() {
if (!hashParam('openid')) { $('#bbBanner').classList.add('hide'); return Promise.resolve(); }
return bbApi('blindbox').then(j => {
if (!j || j.code !== 200 || !j.result) return;
BB = j.result;
renderBanner();
if (BB.backfilled > 0) setTimeout(() => toast('错过的 ' + BB.backfilled + ' 张宝宝卡已补给你,去卡册看看吧'), 1800);
if ($('#detailBox').classList.contains('show')) renderBox();
}).catch(() => { /* 静默:横幅保持现状 */ });
}
function renderBanner() {
const ban = $('#bbBanner');
const set = (t, s, cta, ready, calm) => {
$('#bbTitle').textContent = t;
$('#bbSub').innerHTML = s;
$('#bbCta').textContent = cta;
ban.classList.toggle('is-ready', !!ready);
ban.classList.toggle('is-calm', !!calm);
ban.classList.remove('hide');
};
if (!BB || !BB.enabled || BB.notYet) { ban.classList.add('hide'); return; }
if (!$('#bbGift').firstChild) $('#bbGift').innerHTML = giftSVG();
const badge = $('#bbBadge');
badge.classList.add('hide');
if (BB.needProfile) { set('每周一个宝宝盲盒', '填好预产期就能领 · 完全免费', '去填写', true); return; }
const got = '已收集 <b>' + BB.collected + '</b>/' + BB.totalWeeks;
const op = BB.openable || [];
if (op.length > 1) { badge.textContent = op.length; badge.classList.remove('hide'); }
if (BB.ended) set('我的孕期卡册', got + ' 张宝宝卡,永久保留', '去看看', false, true);
else if (!BB.currentOpened && op.indexOf(BB.week) >= 0) set('孕 ' + BB.week + ' 周的盲盒到了', got + ' · 免费开启', '去开启', true);
else if (op.length) set('还有 ' + op.length + ' 个盲盒没开', '错过的周也能补开 · ' + got, '去补开', true);
else set(BB.nextIn ? '下一个盲盒 ' + BB.nextIn + ' 天后到' : '本周盲盒已开启', got + (BB.flash ? ' · 闪卡 <b>' + BB.flash + '</b> 张' : ''), '看卡册', false, true);
}
$('#bbBanner').addEventListener('click', () => {
if (!BB) return;
if (BB.needProfile) { openModal('modalProfile'); return; }
bbSel = null;
renderBox();
openPanel('detailBox');
});
function renderBox() {
if (!BB || BB.needProfile) return;
const op = BB.openable || [];
if (op.indexOf(bbSel) < 0) bbSel = op.indexOf(BB.week) >= 0 ? BB.week : (op[0] || null);
const st = $('#bxStage'), box = $('#bxBox'), mini = $('#bxMini'), btn = $('#bxOpen');
st.classList.remove('is-shaking', 'is-popping');
btn.disabled = false;
if (bbSel) {
st.classList.remove('is-done');
$('#bxWeek').textContent = '孕 ' + bbSel + ' 周 · 宝宝盲盒';
$('#bxHint').textContent = bbSel === BB.week ? '宝宝给你准备了这周的小惊喜' : '这是你错过的一周,现在补开';
box.innerHTML = giftSVG();
box.classList.remove('hide'); mini.classList.add('hide');
btn.textContent = '开启盲盒'; btn.classList.remove('hide');
} else {
st.classList.add('is-done');
box.classList.add('hide');
const cc = BB.currentCard;
$('#bxWeek').textContent = BB.ended ? '孕期卡册' : '孕 ' + BB.week + ' 周 · 已开启';
$('#bxHint').textContent = BB.ended ? (BB.postpartum ? '宝宝出生啦,卡册会一直留着' : '孕期盲盒已经全部开完') :
(BB.nextIn ? '下一个盲盒 ' + BB.nextIn + ' 天后到' : '本周的盲盒已经开过啦');
if (cc) { mini.innerHTML = cardFrontHTML(cc); mini.classList.remove('hide'); mini.onclick = () => showReveal(cc, null, false); }
else { mini.classList.add('hide'); box.innerHTML = giftSVG(); box.classList.remove('hide'); }
btn.classList.toggle('hide', !cc);
btn.textContent = '看看这张卡';
}
$('#bxMisc').textContent = (BB.weekOpeners >= 50) ? '本周已有 ' + fmtN(BB.weekOpeners) + ' 位妈妈开了盲盒' : '每周一个 · 完全免费';
/* 待补开 */
const pend = $('#bxPend');
pend.classList.toggle('hide', op.length < 2);
if (op.length >= 2) {
$('#bxPendN').textContent = op.length;
$('#bxChips').innerHTML = op.map(w => '<button class="bx-chip' + (w === bbSel ? ' on' : '') + '" data-w="' + w + '" type="button">孕 ' + w + ' 周</button>').join('');
}
/* 卡册 */
$('#bxGot').textContent = BB.collected;
$('#bxAll').textContent = BB.totalWeeks;
requestAnimationFrame(() => { $('#bxBar').style.width = Math.round(BB.collected / BB.totalWeeks * 100) + '%'; });
const nBack = (BB.album || []).filter(a => a.src === 'backfill').length;
$('#bxAnote').textContent = (BB.flash ? '其中闪卡 ' + BB.flash + ' 张。' : '') + (nBack ? '标「补」的是来之前的孕周,已自动补发普通款。' : '');
const byW = {};
(BB.album || []).forEach(a => { byW[a.week] = a; });
$('#bxGroups').innerHTML = BX_GROUPS.map(g => {
let n = 0, cells = '';
for (let w = g.from; w <= g.to; w++) {
const a = byW[w] || {};
if (a.got) {
n++;
cells += '<button class="bx-t got' + (a.v === 'flash' ? ' flash' : '') + (bbNew === w ? ' is-new' : '') + '" data-w="' + w + '" data-v="' + (a.v || 'normal') + '" type="button">' +
(CARD_IMG[w] ? '<img src="assets/img/' + CARD_IMG[w] + '.jpg" alt="" loading="lazy">' : '<span class="fb">' + babySize(w).emoji + '</span>') +
(a.src === 'backfill' ? '<span class="bf">补</span>' : '') + '<span class="wk">' + w + ' 周</span></button>';
} else if (op.indexOf(w) >= 0) {
cells += '<button class="bx-t can" data-op="' + w + '" type="button">' + giftSVG() + '<span class="wk">' + w + ' 周</span></button>';
} else {
cells += '<div class="bx-t lock">?<span class="wk">' + w + ' 周</span></div>';
}
}
const all = g.to - g.from + 1;
return '<div class="bx-grp"><div class="bx-gh' + (n === all ? ' full' : '') + '" style="--c:' + g.c + '"><i></i>' + g.name + ' · ' + g.from + '–' + g.to + ' 周<span>' +
(n === all ? '已集齐 ✓' : n + '/' + all) + '</span></div><div class="bx-grid">' + cells + '</div></div>';
}).join('');
bbNew = null;
/* 规则与概率(接口下发) */
const pb = BB.probs || {};
const bar = x => '<div class="bx-pr"><span>' + bbEsc(x.name) + '</span><i style="--w:' + x.p + '%"></i><b>' + x.p + '%</b></div>';
$('#bxRulesBody').innerHTML = '<h4>宝宝卡(每盒必出 1 张)</h4>' + (pb.card || []).map(bar).join('') +
'<h4>随机奖励(每盒 1 份)</h4>' + (pb.reward || []).map(bar).join('') +
'<h4>规则</h4><ul>' + (pb.notes || []).map(t => '<li>' + bbEsc(t) + '</li>').join('') + '</ul>' +
'<p class="free">孕期盲盒完全免费,不出售、不需要任何付费。</p>';
}
$('#bxChips').addEventListener('click', e => {
const b = e.target.closest('.bx-chip');
if (!b || bbBusy) return;
bbSel = +b.dataset.w; haptic(); renderBox();
$('#detailBox .detail-body').scrollTo({ top: 0, behavior: 'smooth' });
});
$('#bxGroups').addEventListener('click', e => {
const t = e.target.closest('.bx-t');
if (!t || bbBusy) return;
if (t.dataset.op) { bbSel = +t.dataset.op; renderBox(); $('#detailBox .detail-body').scrollTo({ top: 0, behavior: 'smooth' }); return; }
const w = +t.dataset.w;
if (!w) return;
/* 卡册里点开一张:大小来自本地 SIZE 表,发育说明取孕期小知识的「宝宝这周」 */
const c = { week: w, v: t.dataset.v, size: babySize(w), say: '' };
showReveal(c, null, false);
loadTips().then(() => { const x = TIPS && TIPS[w]; if (x && x.baby && $('#bxReveal').classList.contains('show')) $('#bxDev').textContent = x.baby; });
});
$('#bxRuleBtn').addEventListener('click', () => {
const d = $('#bxRules'); d.open = true;
d.scrollIntoView({ behavior: 'smooth', block: 'start' });
});
$('#bxBox').addEventListener('click', () => openBox());
$('#bxOpen').addEventListener('click', () => {
if (bbSel) openBox();
else if (BB && BB.currentCard) showReveal(BB.currentCard, null, false);
});
function openBox() {
if (bbBusy || !bbSel) return;
bbBusy = true; haptic();
const st = $('#bxStage'), btn = $('#bxOpen'), w = bbSel, t0 = Date.now();
st.classList.add('is-shaking');
btn.disabled = true; btn.textContent = '正在打开…';
bbApi('blindbox/open?week=' + w, 'POST').catch(() => null).then(j => {
/* 至少晃 1.3 秒:接口太快时动画一闪而过,没有「拆」的感觉 */
setTimeout(() => {
st.classList.remove('is-shaking');
if (!j || j.code !== 200 || !j.result) {
bbBusy = false; btn.disabled = false; btn.textContent = '开启盲盒';
toast((j && j.message) || '网络开小差了,再试一次');
if (j && j.code === 400) loadBlindbox();
return;
}
st.classList.add('is-popping');
if (navigator.vibrate) navigator.vibrate([12, 40, 18]);
const r = j.result;
bbNew = r.card.week;
setTimeout(() => { showReveal(r.card, r.reward, true); bbBusy = false; }, 420);
loadBlindbox(); // 后台刷新横幅与卡册(翻卡层盖着,用户看不到重绘)
}, Math.max(0, 1300 - (Date.now() - t0)));
});
}
function burst(flash) {
const box = $('#bxConfetti');
const C = flash ? ['#FFD66E', '#FF9AD5', '#9ADFFF', '#fff', '#C9B6FF'] : ['#FFD66E', '#FF8A66', '#FFB3C1', '#fff', '#9ED8C0'];
let h = '';
for (let i = 0; i < (flash ? 44 : 30); i++) {
const a = Math.random() * Math.PI * 2, d = 90 + Math.random() * 170;
h += '<i class="' + (i % 3 ? '' : 'r') + '" style="--c:' + C[i % C.length] + ';--x:' + Math.round(Math.cos(a) * d) + 'px;--y:' + Math.round(Math.sin(a) * d + 60) +
'px;--r:' + Math.round(Math.random() * 720 - 360) + 'deg;--d:' + (Math.random() * .15).toFixed(2) + 's"></i>';
}
box.innerHTML = h;
setTimeout(() => { box.innerHTML = ''; }, 1800);
}
/* 翻卡层。fresh=true:刚开出来(彩纸 + 翻面 + 奖励);false:从卡册/舞台查看(直接正面) */
function showReveal(card, reward, fresh) {
const rv = $('#bxReveal'), c3 = $('#bxCard3d'), flash = card.v === 'flash';
$('#bxFront').innerHTML = cardFrontHTML(card);
if (!$('#bxBack').firstChild) $('#bxBack').innerHTML = giftSVG() + '<b>兜爸心选 · 孕期盲盒</b>';
$('#bxTag').textContent = fresh ? (flash ? '✦ 哇!开出了闪卡 ✦' : '新卡 · 孕 ' + card.week + ' 周') : (flash ? '✦ 闪卡 · 孕 ' + card.week + ' 周' : '孕 ' + card.week + ' 周');
$('#bxDev').textContent = card.dev || '';
let rw = '';
if (reward) {
const tj = reward.type === 'taijiao';
rw = '<div class="bx-rw"><div class="bx-rw-h"><i>' + (tj ? '🎵' : '💌') + '</i>' + (tj ? '胎教时间 · ' + bbEsc(reward.kind || '') : '宝宝对你说') +
'<em>随机奖励</em></div>' + (reward.title ? '<div class="bx-rw-t">' + bbEsc(reward.title) + '</div>' : '') +
'<p class="bx-rw-p">' + bbEsc(reward.text) + '</p></div>';
}
$('#bxReward').innerHTML = rw;
$('#bxKeep').textContent = fresh ? '收进卡册' : '好的';
rv.classList.remove('is-done', 'is-burst');
rv.classList.toggle('is-flash', flash);
c3.classList.remove('is-flip', 'no-anim');
void c3.offsetWidth;
rv.classList.add('show');
rv.setAttribute('aria-hidden', 'false');
rv.scrollTop = 0;
if (fresh) {
rv.classList.add('is-burst');
burst(flash);
setTimeout(() => c3.classList.add('is-flip'), 260);
setTimeout(() => rv.classList.add('is-done'), 1100);
} else {
c3.classList.add('no-anim', 'is-flip');
rv.classList.add('is-done');
}
}
function hideReveal() {
const rv = $('#bxReveal');
rv.classList.remove('show', 'is-done', 'is-burst');
rv.setAttribute('aria-hidden', 'true');
}
$('#bxKeep').addEventListener('click', () => { hideReveal(); renderBox(); });
$('#detailBox .dt-back').addEventListener('click', hideReveal);
/* ================= 孕期小知识(2026-09-30) ================= /* ================= 孕期小知识(2026-09-30) =================
内容 assets/data/preg-tips.json:[{week, baby, mom, eat, care, days:[{t,d}×7]}],孕 4–40 周。 内容 assets/data/preg-tips.json:[{week, baby, mom, eat, care, days:[{t,d}×7]}],孕 4–40 周。
第几天 = state.day(0–6),和首页「孕 N 周 D 天」同一口径。 第几天 = state.day(0–6),和首页「孕 N 周 D 天」同一口径。

View File

@@ -2371,3 +2371,323 @@ a.skip:focus{left:16px;top:16px}
font-size:12px;padding:6px 12px;border-radius:9px; font-size:12px;padding:6px 12px;border-radius:9px;
} }
.idn-retry:active{transform:scale(.95)} .idn-retry:active{transform:scale(.95)}
/* =====================================================================
孕期盲盒 · 首页细横幅(2026-10-01,需求文档 v1.0 三-7)
一条奶油粉→薰衣草的渐变细卡:左边会晃的礼盒,中间两行字,右边胶囊。
⚠️ 它是 #homeBody 第一个孩子;隐藏时把首块上间距还给工具托盘(下面两条)。
动效:礼盒每 3 秒晃一下(只在「有盒子可开」时)+ 一道斜光每 5 秒扫过;减少动态效果时全关。
===================================================================== */
.bb-banner{position:relative;display:flex;align-items:center;gap:11px;width:100%;min-height:66px;
padding:9px 12px 9px 9px;border:1px solid rgba(255,255,255,.9);border-radius:22px;overflow:hidden;
text-align:left;font:inherit;color:#5A2E22;
background:linear-gradient(105deg,#FFF0E2 0%,#FFE3E6 55%,#F1E5FF 100%);
box-shadow:0 10px 24px rgba(226,120,108,.16)}
.bb-banner.hide{display:none}
.home-body>.bb-banner.hide+.tool-dock{margin-top:21px}
.home-body.no-letter>.bb-banner.hide+.tool-dock{margin-top:30px}
.bb-banner:active{transform:scale(.985)}
.bb-gift{position:relative;flex:0 0 auto;width:50px;height:50px;border-radius:16px;display:grid;place-items:center;
background:radial-gradient(circle at 50% 60%,#fff 0,rgba(255,255,255,.55) 55%,rgba(255,255,255,0) 72%)}
.bb-gift svg{width:44px;height:44px;overflow:visible}
.bb-banner.is-ready .bb-gift svg{animation:bbWiggle 3s ease-in-out infinite;transform-origin:50% 90%}
@keyframes bbWiggle{0%,62%,100%{transform:rotate(0) scale(1)}66%{transform:rotate(-10deg) scale(1.06)}
71%{transform:rotate(9deg) scale(1.08)}76%{transform:rotate(-6deg)}81%{transform:rotate(4deg)}86%{transform:rotate(0)}}
.bb-badge{position:absolute;left:44px;top:6px;min-width:18px;height:18px;padding:0 5px;border-radius:9px;
font-size:11px;font-weight:800;line-height:18px;text-align:center;color:#fff;
background:linear-gradient(135deg,#FF5B6E,#F4593C);box-shadow:0 0 0 2px #fff;animation:bbBadge 1.6s ease-in-out infinite}
.bb-badge.hide{display:none}
@keyframes bbBadge{0%,100%{transform:scale(1)}50%{transform:scale(1.15)}}
.bb-txt{flex:1;min-width:0;display:flex;flex-direction:column;gap:3px}
.bb-t{font-size:15.5px;font-weight:800;letter-spacing:-.2px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.bb-s{font-size:11.5px;color:#9A6F62;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.bb-s b{color:var(--brand);font-weight:800}
.bb-cta{flex:0 0 auto;border-radius:999px;padding:7px 13px;font-size:12.5px;font-weight:800;color:#fff;
background:linear-gradient(135deg,#FF7A5C,#F4593C);box-shadow:0 6px 14px rgba(244,89,60,.3)}
.bb-banner.is-calm .bb-cta{color:var(--brand);background:#fff;box-shadow:0 3px 10px rgba(226,120,108,.16)}
.bb-shine{position:absolute;top:-20%;left:-40%;width:28%;height:140%;pointer-events:none;
background:linear-gradient(90deg,rgba(255,255,255,0),rgba(255,255,255,.75),rgba(255,255,255,0));
transform:skewX(-20deg);animation:bbShine 5s ease-in-out infinite}
@keyframes bbShine{0%,70%{left:-40%}100%{left:130%}}
/* 礼盒 SVG(横幅和舞台共用一份画法,见 app.js giftSVG):盖子单独一组,开盒时飞起来 */
.gift .g-lid{transform-box:fill-box;transform-origin:50% 100%}
.gift .g-body{transform-box:fill-box;transform-origin:50% 100%}
/* =====================================================================
此刻,孕妈们在做什么(2026-10-01,需求文档 v1.0 第二节)
外壳是首页公共白色通栏。里面三层:
① 头像叠 + 一个会滚动累加的大数字(进入视口时从 0 数上去,之后每分钟刷新平滑涨)
② 今日三项:三张浅色小卡,点击进入对应工具
③ 匿名动态:一行,每 3 秒往上翻一条
背景两团慢慢漂的柔光 + 左上「此刻」红点呼吸,表达「正在发生」。
===================================================================== */
.pulse{margin:16px -26px 0;padding:16px 26px 18px;border-radius:26px;position:relative;z-index:0;overflow:hidden;
background:linear-gradient(160deg,#fff 0%,#fff 45%,#FFF6F0 100%)}
.pulse.hide{display:none}
.pl-orb{position:absolute;z-index:-1;border-radius:50%;filter:blur(18px);opacity:.55;pointer-events:none}
.pl-orb.o1{width:150px;height:150px;right:-40px;top:-50px;background:#FFD9CB;animation:plDrift 9s ease-in-out infinite}
.pl-orb.o2{width:120px;height:120px;left:-30px;bottom:-50px;background:#F1E2FF;animation:plDrift 11s ease-in-out infinite reverse}
@keyframes plDrift{0%,100%{transform:translate(0,0)}50%{transform:translate(-18px,14px)}}
.pl-head{display:flex;align-items:center;gap:8px}
.pl-live{display:inline-flex;align-items:center;gap:5px;font-size:11.5px;font-weight:800;color:#fff;
background:linear-gradient(135deg,#FF6B57,#F4593C);border-radius:999px;padding:3px 9px 3px 7px}
.pl-live i{position:relative;width:6px;height:6px;border-radius:50%;background:#fff}
.pl-live i::after{content:"";position:absolute;inset:-3px;border-radius:50%;border:1.5px solid #fff;animation:plPing 1.6s ease-out infinite}
@keyframes plPing{from{transform:scale(.6);opacity:1}to{transform:scale(1.9);opacity:0}}
.pl-title{font-size:16.5px;font-weight:800;letter-spacing:-.2px}
.pl-total{display:flex;align-items:center;gap:10px;margin-top:14px}
.pl-total.hide,.pl-today.hide,.pl-feed.hide{display:none}
.pl-avs{display:flex;flex:0 0 auto}
.pl-avs i{width:28px;height:28px;border-radius:50%;border:2px solid #fff;margin-left:-9px;box-shadow:0 2px 6px rgba(200,120,100,.18);
background:#FFC9B6 url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 28 28'%3E%3Ccircle cx='14' cy='11' r='5' fill='%23fff'/%3E%3Cpath d='M5 26c1-6 5-9 9-9s8 3 9 9z' fill='%23fff'/%3E%3C/svg%3E") center/100% no-repeat}
.pl-avs i:first-child{margin-left:0}
.pl-avs i:nth-child(2){background-color:#C9B6FF}
.pl-avs i:nth-child(3){background-color:#FFD98A}
.pl-avs i:nth-child(4){background-color:#9ED8C0}
.pl-tt{display:flex;flex-wrap:wrap;align-items:baseline;font-size:13px;color:var(--ink-2);line-height:1.3}
#plSuf{flex-basis:100%;margin-top:1px;font-size:12.5px}
.pl-tt b{display:inline-block;margin:0 3px;font-size:24px;font-weight:900;letter-spacing:-.5px;color:var(--brand);
font-variant-numeric:tabular-nums;vertical-align:-2px}
.pl-today{display:grid;grid-template-columns:repeat(3,1fr);gap:8px;margin-top:14px}
.pl-stat{position:relative;display:flex;flex-direction:column;align-items:flex-start;gap:2px;border:0;border-radius:16px;
padding:10px 10px 9px;text-align:left;font:inherit;overflow:hidden;transition:transform .16s}
.pl-stat:active{transform:scale(.96)}
.pl-stat .ps-ico{width:22px;height:22px;border-radius:8px;display:grid;place-items:center;color:#fff;margin-bottom:4px}
.pl-stat .ps-ico svg{width:14px;height:14px}
.pl-stat b{font-size:18px;font-weight:900;letter-spacing:-.4px;font-variant-numeric:tabular-nums}
.pl-stat span{font-size:11px;line-height:1.35;color:var(--ink-2)}
.pl-stat i.go{position:absolute;right:8px;top:10px;font-style:normal;font-size:13px;opacity:.45}
.pl-stat.k-kick{background:#FFF0EA}.pl-stat.k-kick b{color:#E5583C}.pl-stat.k-kick .ps-ico{background:#F4795C}
.pl-stat.k-weight{background:#EEF6EC}.pl-stat.k-weight b{color:#4E8A43}.pl-stat.k-weight .ps-ico{background:#6EAE5E}
.pl-stat.k-game{background:#FFF0F5}.pl-stat.k-game b{color:#D9487A}.pl-stat.k-game .ps-ico{background:#F07AA0}
.pl-feed{position:relative;margin-top:12px;height:46px;overflow:hidden;border-radius:14px;background:#FBF6F2;
-webkit-mask-image:linear-gradient(180deg,transparent 0,#000 18%,#000 82%,transparent 100%);
mask-image:linear-gradient(180deg,transparent 0,#000 18%,#000 82%,transparent 100%)}
.pl-track{will-change:transform}
.pl-track.is-move{transition:transform .55s var(--ease)}
.pl-row{display:flex;align-items:center;gap:9px;height:46px;padding:0 12px}
.pl-row .pr-ico{flex:0 0 auto;width:26px;height:26px;border-radius:50%;display:grid;place-items:center;color:#fff}
.pl-row .pr-ico svg{width:14px;height:14px}
.pl-row .pr-t{flex:1;min-width:0;display:flex;flex-direction:column;gap:1px}
.pl-row .pr-who{font-size:10.5px;color:var(--muted)}
.pl-row .pr-do{font-size:12.5px;font-weight:700;color:var(--ink);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.pl-row .pr-do em{font-style:normal;color:var(--brand)}
.a-kick .pr-ico{background:#F4795C}.a-weight .pr-ico{background:#6EAE5E}.a-cal .pr-ico{background:#5B8FD6}
.a-game .pr-ico{background:#F07AA0}.a-box .pr-ico{background:linear-gradient(135deg,#FFB547,#FF7A5C)}
/* =====================================================================
孕期盲盒 · 详情页(2026-10-01)
① 舞台:珊瑚→玫瑰渐变 + 缓慢旋转的光芒 + 闪烁星星,礼盒悬浮呼吸;
点「开启」→ 礼盒越晃越厉害(等接口,最少 1.3 秒)→ 盖子飞起、白光一闪 → 翻卡层。
② 翻卡层:暗色幕 + 彩纸迸开 + 卡片从背面 3D 翻到正面;闪卡多一层流动的镭射光。
③ 卡册:按孕早/中/晚期三组,已集的显示插画,可开的在发光,没到的是问号。
===================================================================== */
.bx .detail-body{padding:16px 16px 40px}
.bx-rule-btn{width:auto;font-size:13px;font-weight:700;color:var(--brand)}
.bx-stage{position:relative;z-index:0;overflow:hidden;border-radius:28px;padding:22px 18px 20px;text-align:center;color:#fff;
background:linear-gradient(165deg,#FFB08F 0%,#FF8B76 42%,#E9688C 100%);box-shadow:0 16px 36px rgba(233,104,120,.3)}
.bx-rays{position:absolute;z-index:-1;left:50%;top:46%;width:520px;height:520px;margin:-260px 0 0 -260px;
background:repeating-conic-gradient(rgba(255,255,255,.16) 0 9deg,rgba(255,255,255,0) 9deg 22.5deg);
-webkit-mask-image:radial-gradient(circle,#000 0,rgba(0,0,0,.6) 30%,transparent 62%);
mask-image:radial-gradient(circle,#000 0,rgba(0,0,0,.6) 30%,transparent 62%);
animation:bxSpin 24s linear infinite}
@keyframes bxSpin{to{transform:rotate(360deg)}}
.bx-sp{position:absolute;z-index:-1;color:#FFF3C4;animation:gpTw 2.2s ease-in-out infinite;opacity:0}
.bx-sp.s1{left:16%;top:30%;font-size:14px}.bx-sp.s2{right:14%;top:24%;font-size:11px;animation-delay:.5s}
.bx-sp.s3{left:22%;top:62%;font-size:9px;animation-delay:1.1s}.bx-sp.s4{right:20%;top:58%;font-size:13px;animation-delay:1.6s}
.bx-week{font-size:13px;font-weight:800;letter-spacing:1px;display:inline-block;padding:4px 12px;border-radius:999px;
background:rgba(255,255,255,.22);backdrop-filter:blur(4px)}
.bx-hint{margin-top:8px;font-size:18px;font-weight:900;letter-spacing:.2px;text-shadow:0 2px 0 rgba(190,70,80,.25)}
.bx-box{display:block;margin:6px auto 0;width:168px;height:168px;border:0;background:none;padding:0;
animation:bxFloat 3.2s ease-in-out infinite}
.bx-box.hide,.bx-mini.hide,.bx-open.hide{display:none}
.bx-box svg{width:100%;height:100%;overflow:visible;filter:drop-shadow(0 12px 14px rgba(160,50,60,.28))}
@keyframes bxFloat{0%,100%{transform:translateY(0)}50%{transform:translateY(-8px)}}
.bx-shadow{display:block;width:96px;height:12px;margin:-8px auto 0;border-radius:50%;background:rgba(120,30,40,.18);
animation:bxShadow 3.2s ease-in-out infinite}
@keyframes bxShadow{0%,100%{transform:scale(1);opacity:1}50%{transform:scale(.82);opacity:.7}}
.bx-stage.is-done .bx-shadow{display:none}
.bx-stage.is-shaking .bx-box{animation:bxShake .32s ease-in-out infinite}
.bx-stage.is-shaking .bx-box svg{animation:bxSwell 1.3s ease-in forwards}
@keyframes bxShake{0%,100%{transform:rotate(0)}25%{transform:rotate(-7deg) translateY(-2px)}75%{transform:rotate(7deg) translateY(-2px)}}
@keyframes bxSwell{to{transform:scale(1.12)}}
.bx-stage.is-popping .g-lid{animation:bxLid .5s cubic-bezier(.2,.8,.3,1) forwards}
.bx-stage.is-popping .g-body{animation:bxBody .5s ease-out forwards}
@keyframes bxLid{to{transform:translate(18px,-90px) rotate(28deg);opacity:0}}
@keyframes bxBody{40%{transform:scaleY(.86) scaleX(1.08)}100%{transform:scale(.7);opacity:0}}
.bx-open{position:relative;margin-top:14px;min-width:178px;border-radius:999px;padding:13px 26px;font-size:16px;font-weight:900;
letter-spacing:1px;color:#E5503C;background:#fff;box-shadow:0 10px 22px rgba(170,50,50,.25),0 0 0 0 rgba(255,255,255,.6);
animation:bxCta 2s ease-out infinite}
.bx-open:active{transform:scale(.96)}
.bx-open[disabled]{opacity:.85;animation:none}
@keyframes bxCta{0%{box-shadow:0 10px 22px rgba(170,50,50,.25),0 0 0 0 rgba(255,255,255,.55)}
80%,100%{box-shadow:0 10px 22px rgba(170,50,50,.25),0 0 0 16px rgba(255,255,255,0)}}
.bx-stage.is-done .bx-open{animation:none;background:rgba(255,255,255,.95)}
.bx-misc{margin-top:12px;font-size:12px;color:rgba(255,255,255,.9)}
.bx-misc:empty{display:none}
.bx-mini{display:block;margin:12px auto 4px;width:150px;border:0;background:none;padding:0;transform:rotate(-4deg);
animation:bxFloat 3.6s ease-in-out infinite}
.bx-mini .cf{transform-origin:top left;width:248px;transform:scale(.605);margin-bottom:-130px;height:330px}
.bx-pend{margin-top:14px;padding:13px 14px;border-radius:20px;background:#fff;box-shadow:var(--sh)}
.bx-pend.hide{display:none}
.bx-pend-t{font-size:14px;font-weight:800}
.bx-pend-t b{color:var(--brand)}
.bx-pend-t em{display:block;margin-top:2px;font-style:normal;font-size:11.5px;font-weight:500;color:var(--muted)}
.bx-chips{display:flex;flex-wrap:wrap;gap:7px;margin-top:10px}
.bx-chip{border-radius:999px;padding:6px 12px;font-size:12.5px;font-weight:700;color:#C4553E;background:#FFF0EA;
border:1.5px solid transparent;transition:all .2s var(--ease)}
.bx-chip.on{color:#fff;background:linear-gradient(135deg,#FF7A5C,#F4593C);box-shadow:0 4px 10px rgba(244,89,60,.28)}
.bx-album{margin-top:16px;padding:16px 14px 6px;border-radius:24px;background:#fff;box-shadow:var(--sh)}
.bx-ah{display:flex;align-items:baseline}
.bx-ah h3{font-size:16.5px;font-weight:800}
.bx-cnt{margin-left:auto;font-size:13px;color:var(--muted)}
.bx-cnt b{font-size:20px;font-weight:900;color:var(--brand)}
.bx-prog{margin-top:9px;height:8px;border-radius:4px;background:#F5ECE6;overflow:hidden}
.bx-prog i{display:block;height:100%;width:0;border-radius:4px;background:linear-gradient(90deg,#FFB08F,#F4593C,#E9688C);
transition:width 1s var(--ease)}
.bx-anote{margin-top:7px;font-size:11.5px;color:var(--muted)}
.bx-anote:empty{display:none}
.bx-grp{margin-top:14px}
.bx-gh{display:flex;align-items:center;gap:6px;font-size:13px;font-weight:800;color:var(--ink);margin-bottom:8px}
.bx-gh i{width:6px;height:6px;border-radius:50%;background:var(--c,#F4593C)}
.bx-gh span{margin-left:auto;font-size:11.5px;font-weight:600;color:var(--muted)}
.bx-gh.full span{color:var(--brand)}
.bx-grid{display:grid;grid-template-columns:repeat(5,1fr);gap:7px}
.bx-t{position:relative;aspect-ratio:3/4;border-radius:11px;overflow:hidden;border:0;padding:0;font:inherit;
background:#F7F0EB;display:grid;place-items:center}
.bx-t .wk{position:absolute;left:0;right:0;bottom:0;text-align:center;padding:10px 0 3px;font-size:10.5px;font-weight:800;color:#fff;
background:linear-gradient(180deg,rgba(0,0,0,0),rgba(90,40,30,.55))}
.bx-t img{width:100%;height:100%;object-fit:cover}
.bx-t .fb{font-size:26px}
.bx-t.got{box-shadow:0 3px 8px rgba(190,120,96,.18)}
.bx-t.lock{border:1.5px dashed #EADBD2;color:#C9B4A8;font-size:18px;font-weight:900}
.bx-t.lock .wk{color:#B9A296;background:none}
.bx-t.can{background:linear-gradient(160deg,#FFE1D4,#FFC2B6);border:0;animation:bxCan 1.8s ease-in-out infinite}
.bx-t.can svg{width:30px;height:30px;overflow:visible}
.bx-t.can .wk{color:#C4553E;background:none}
@keyframes bxCan{0%,100%{box-shadow:0 0 0 0 rgba(244,89,60,.0)}50%{box-shadow:0 0 0 3px rgba(244,89,60,.35)}}
.bx-t.flash::after{content:"";position:absolute;inset:0;border-radius:inherit;pointer-events:none;
box-shadow:inset 0 0 0 2px #FFD66E;
background:linear-gradient(115deg,transparent 25%,rgba(255,255,255,.65) 45%,rgba(255,200,240,.4) 52%,transparent 70%) 0 0/250% 250%;
mix-blend-mode:screen;animation:bxHolo 2.6s linear infinite}
.bx-t .bf{position:absolute;right:3px;top:3px;font-size:9px;font-weight:800;color:#fff;background:rgba(0,0,0,.25);border-radius:5px;padding:0 3px}
.bx-t.is-new{animation:bxNewT 1s cubic-bezier(.3,1.6,.5,1)}
@keyframes bxNewT{0%{transform:scale(.4);opacity:0}60%{transform:scale(1.15)}100%{transform:none;opacity:1}}
.bx-rules{margin-top:16px;border-radius:20px;background:#fff;box-shadow:var(--sh);overflow:hidden}
.bx-rules summary{list-style:none;display:flex;align-items:center;padding:14px 16px;font-size:14px;font-weight:800}
.bx-rules summary::-webkit-details-marker{display:none}
.bx-rules summary::after{content:"›";margin-left:auto;color:var(--muted);font-size:18px;transition:transform .25s}
.bx-rules[open] summary::after{transform:rotate(90deg)}
.bx-rules-b{padding:0 16px 16px;font-size:12.5px;color:var(--ink-2);line-height:1.7}
.bx-rules-b h4{margin:10px 0 6px;font-size:12.5px;font-weight:800;color:var(--ink)}
.bx-pr{display:flex;align-items:center;gap:8px;margin:5px 0}
.bx-pr span{flex:0 0 auto;width:118px;font-size:12px}
.bx-pr i{flex:1;height:6px;border-radius:3px;background:#F5ECE6;position:relative;overflow:hidden}
.bx-pr i::after{content:"";position:absolute;left:0;top:0;bottom:0;width:var(--w);border-radius:3px;background:linear-gradient(90deg,#FFB08F,#F4593C)}
.bx-pr b{flex:0 0 auto;width:34px;text-align:right;font-size:12px;color:var(--brand)}
.bx-rules-b ul{margin:4px 0 0;padding-left:16px}
.bx-rules-b .free{margin-top:10px;padding:8px 10px;border-radius:10px;background:#FFF5EF;color:#B5543F;font-weight:700;font-size:12px}
/* ---------- 翻卡层 ---------- */
.bx-reveal{position:absolute;inset:0;z-index:6;display:flex;align-items:flex-start;justify-content:center;overflow-y:auto;
background:rgba(48,22,26,.82);-webkit-backdrop-filter:blur(6px);backdrop-filter:blur(6px);
opacity:0;pointer-events:none;transition:opacity .35s}
.bx-reveal.show{opacity:1;pointer-events:auto}
.bx-glow{position:absolute;left:50%;top:260px;width:460px;height:460px;margin:-230px 0 0 -230px;pointer-events:none;
background:repeating-conic-gradient(rgba(255,214,150,.18) 0 8deg,rgba(255,214,150,0) 8deg 20deg);
-webkit-mask-image:radial-gradient(circle,#000 0,transparent 65%);mask-image:radial-gradient(circle,#000 0,transparent 65%);
animation:bxSpin 30s linear infinite;opacity:0;transition:opacity .6s .4s}
.bx-reveal.show .bx-glow{opacity:1}
.bx-reveal.is-flash .bx-glow{background:repeating-conic-gradient(rgba(255,150,220,.2) 0 8deg,rgba(150,220,255,.12) 8deg 14deg,rgba(255,240,150,0) 14deg 20deg)}
.bx-flash{position:absolute;left:50%;top:260px;width:120px;height:120px;margin:-60px 0 0 -60px;border-radius:50%;
background:radial-gradient(circle,#fff 0,rgba(255,255,255,.8) 40%,rgba(255,255,255,0) 70%);opacity:0;pointer-events:none}
.bx-reveal.is-burst .bx-flash{animation:bxFlash .8s ease-out}
@keyframes bxFlash{0%{transform:scale(.2);opacity:1}100%{transform:scale(7);opacity:0}}
.bx-confetti{position:absolute;left:50%;top:260px;width:0;height:0;pointer-events:none}
.bx-confetti i{position:absolute;left:0;top:0;width:8px;height:12px;border-radius:2px;background:var(--c);opacity:0;
animation:bxBurst 1.4s cubic-bezier(.15,.7,.3,1) var(--d) forwards}
.bx-confetti i.r{border-radius:50%;width:8px;height:8px}
@keyframes bxBurst{0%{transform:translate(0,0) rotate(0) scale(.6);opacity:1}
70%{opacity:1}100%{transform:translate(var(--x),var(--y)) rotate(var(--r)) scale(1);opacity:0}}
.bx-rv-in{position:relative;display:flex;flex-direction:column;align-items:center;width:100%;padding:calc(env(safe-area-inset-top) + 64px) 24px 40px}
.bx-tag{font-size:14px;font-weight:900;letter-spacing:1px;color:#FFE8B8;opacity:0;transform:translateY(-8px);transition:all .4s .5s}
.bx-reveal.show .bx-tag{opacity:1;transform:none}
.bx-reveal.is-flash .bx-tag{background:linear-gradient(90deg,#FFD66E,#FF9AD5,#9ADFFF,#FFD66E) 0 0/300% 100%;
-webkit-background-clip:text;background-clip:text;color:transparent;font-size:16px;animation:bxHolo 2.4s linear infinite}
.bx-persp{margin-top:14px;perspective:1100px}
.bx-card3d{position:relative;width:248px;height:330px;transform-style:preserve-3d;
transform:rotateY(180deg) scale(.35) translateY(90px);transition:transform 1.05s cubic-bezier(.2,.9,.25,1.12)}
.bx-card3d.is-flip{transform:rotateY(0) scale(1)}
.bx-card3d.no-anim{transition:none}
.bx-face{position:absolute;inset:0;-webkit-backface-visibility:hidden;backface-visibility:hidden;border-radius:20px}
.bx-back{transform:rotateY(180deg);overflow:hidden;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:8px;
background:radial-gradient(circle at 50% 42%,#FFB08F 0,#F4593C 60%,#D9466E 100%);border:4px solid #FFE3D6;color:#fff}
.bx-back::before{content:"";position:absolute;inset:10px;border-radius:14px;border:1.5px dashed rgba(255,255,255,.5)}
.bx-back::after{content:"";position:absolute;inset:0;opacity:.25;
background:radial-gradient(circle,#fff 1.4px,transparent 1.6px) 0 0/18px 18px}
.bx-back svg{width:96px;height:96px;overflow:visible}
.bx-back b{position:relative;z-index:1;font-size:13px;letter-spacing:3px}
.bx-dev{max-width:290px;margin-top:16px;font-size:13px;line-height:1.7;color:rgba(255,255,255,.88);text-align:center;
opacity:0;transform:translateY(10px);transition:all .45s}
.bx-dev:empty{display:none}
.bx-reveal.is-done .bx-dev{opacity:1;transform:none;transition-delay:.15s}
.bx-reward{width:100%;max-width:300px;margin-top:14px;opacity:0;transform:translateY(24px) scale(.96);transition:all .5s var(--ease)}
.bx-reward:empty{display:none}
.bx-reveal.is-done .bx-reward{opacity:1;transform:none;transition-delay:.3s}
.bx-rw{position:relative;border-radius:20px;padding:14px 16px 15px;background:#FFFBF6;color:var(--ink);text-align:left;
box-shadow:0 14px 30px rgba(0,0,0,.25)}
.bx-rw::before{content:"";position:absolute;left:18px;top:-7px;width:14px;height:14px;transform:rotate(45deg);background:#FFFBF6}
.bx-rw-h{display:flex;align-items:center;gap:8px;font-size:12px;font-weight:800;color:#C4553E}
.bx-rw-h i{width:26px;height:26px;border-radius:9px;display:grid;place-items:center;font-style:normal;font-size:15px;
background:linear-gradient(135deg,#FFE0D2,#FFC8BA)}
.bx-rw-h em{margin-left:auto;font-style:normal;font-size:10.5px;font-weight:700;color:#B8A094;background:#F6EEE8;border-radius:999px;padding:2px 8px}
.bx-rw-t{margin-top:9px;font-size:15px;font-weight:800}
.bx-rw-p{margin-top:5px;font-size:13.5px;line-height:1.75;color:#5E4A42;font-family:var(--font-kid)}
.bx-keep{margin-top:20px;min-width:200px;border-radius:999px;padding:13px 28px;font-size:15.5px;font-weight:900;color:#fff;
background:linear-gradient(135deg,#FF7A5C,#F4593C);box-shadow:0 10px 24px rgba(244,89,60,.4);
opacity:0;transform:translateY(16px);transition:all .45s}
.bx-reveal.is-done .bx-keep{opacity:1;transform:none;transition-delay:.55s}
.bx-keep:active{transform:scale(.96)}
/* ---------- 宝宝卡正面 ---------- */
.cf{position:relative;width:248px;height:330px;border-radius:20px;padding:10px;overflow:hidden;background:#FFFDF9;
box-shadow:0 18px 40px rgba(0,0,0,.28);text-align:left;color:var(--ink)}
.cf-img{position:relative;width:228px;height:200px;border-radius:14px;overflow:hidden;background:#FFF4EC;display:grid;place-items:center}
.cf-img img{width:100%;height:100%;object-fit:cover}
.cf-fb{display:grid;place-items:center;width:100%;height:100%;font-size:86px;
background:radial-gradient(circle at 50% 45%,#fff 0,#FFEADF 55%,#FFD9CB 100%)}
.cf-row{display:flex;align-items:baseline;margin-top:10px;padding:0 4px}
.cf-wk{font-size:13px;font-weight:800;color:var(--muted)}
.cf-wk b{font-size:24px;font-weight:900;color:var(--brand);margin:0 2px;letter-spacing:-.5px}
.cf-size{margin-left:auto;font-size:12px;color:var(--ink-2)}
.cf-size b{color:var(--ink);font-weight:800}
.cf-say{margin-top:6px;padding:8px 10px;border-radius:12px;background:#FFF3EC;font-size:13px;line-height:1.55;color:#7A4A3B;
font-family:var(--font-kid);display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden}
.cf-say:empty{display:none}
.cf-no{position:absolute;right:16px;top:16px;font-size:10px;font-weight:800;letter-spacing:.5px;color:#fff;
background:rgba(90,46,34,.35);border-radius:999px;padding:2px 8px;backdrop-filter:blur(2px)}
.cf-tag{display:none}
.cf.v-flash{background:linear-gradient(135deg,#FFE7A3,#FFC6E5 35%,#C6E8FF 65%,#FFE7A3);padding:12px}
.cf.v-flash .cf-img{width:224px;height:196px}
.cf.v-flash .cf-row,.cf.v-flash .cf-say{position:relative}
.cf.v-flash .cf-say{background:rgba(255,255,255,.75)}
.cf.v-flash .cf-tag{display:block;position:absolute;left:16px;top:16px;z-index:2;font-size:11px;font-weight:900;color:#8A5A00;
background:linear-gradient(90deg,#FFE58A,#FFF6D0,#FFD66E);border-radius:999px;padding:2px 9px;box-shadow:0 2px 6px rgba(160,110,0,.3)}
.cf-holo{position:absolute;inset:0;pointer-events:none;z-index:1;border-radius:inherit;display:none;
background:linear-gradient(115deg,transparent 20%,rgba(255,255,255,.7) 38%,rgba(255,170,230,.45) 46%,rgba(150,225,255,.45) 54%,transparent 72%) 0 0/250% 250%;
mix-blend-mode:soft-light;animation:bxHolo 3s linear infinite}
.cf.v-flash .cf-holo{display:block}
@keyframes bxHolo{from{background-position:120% 0}to{background-position:-120% 0}}
@media (prefers-reduced-motion:reduce){
.bb-banner .bb-gift svg,.bb-badge,.bb-shine,.pl-orb,.pl-live i::after,.bx-rays,.bx-sp,.bx-box,.bx-shadow,.bx-open,.bx-mini,
.bx-t.can,.bx-t.flash::after,.bx-glow,.cf-holo{animation:none!important}
.bb-shine{display:none}
.bx-card3d{transition-duration:.2s}
}
.bx-t .fb{display:grid;place-items:center;width:100%;height:100%;background:radial-gradient(circle at 50% 40%,#fff 0,#FFE6DA 70%)}
.bx-keep,.bx-open{text-align:center}