Compare commits

...

4 Commits

Author SHA1 Message Date
karlcarlo
9968b7d961 正式版暂不挂首页操作引导(coach.js 留在仓库不引用),小知识手绘图标随本版上正式 v26101207
Co-Authored-By: AI 助手
2026-10-12 12:35:41 +00:00
karlcarlo
5e4ca782b3 手绘图标库引用升到 v26101202(32 周大白菜、28 周大茄子换新图)v26101206
Co-Authored-By: AI 助手
2026-10-12 09:39:34 +00:00
karlcarlo
6808975750 今日孕期小知识 emoji 换成手绘图标库(库没加载退回 emoji)v26101205
Co-Authored-By: AI 助手
2026-10-12 09:36:45 +00:00
karlcarlo
62b36aab8d 首页新增操作引导(孕周/孕育工具/此刻/记一下/我的,只走一次)v26101204
Co-Authored-By: AI 助手
2026-10-12 06:44:46 +00:00
4 changed files with 184 additions and 5 deletions

View File

@@ -7,7 +7,7 @@
<meta name="mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-capable" content="yes">
<title>兜爸心选 · 孕育陪伴</title>
<!-- V:26101203 缓存版本标记:改 css/js 时同步 bump 该值(与 js/app.js 的 VER 同源),静态资源按 mtime 走 ETag 自动刷新 -->
<!-- V:26101207 缓存版本标记:改 css/js 时同步 bump 该值(与 js/app.js 的 VER 同源),静态资源按 mtime 走 ETag 自动刷新 -->
<link rel="stylesheet" href="mama-box.css">
<link rel="stylesheet" href="night.css">
<!-- 夜晚模式:首屏绘制前就按上次的选择加 class,避免先白后黑闪一下(开关在首页右上角 #nightBtn,逻辑见 app.js) -->
@@ -2040,6 +2040,7 @@
<!-- 统一头像面板(预设妈妈/宝宝头像 + 从相册上传 + 用微信头像),孕期档案点头像用 -->
<script src="/_bridge/avatar-picker.js?v=26101105"></script>
<script src="/_bridge/hand-icons.js?v=26101202"></script>
<script src="js/zodiac-art.js"></script>
<script src="js/app.js"></script>
<!-- 2026-10-03 首页彩蛋:宝宝在肚皮底下动(独立文件,删掉这一行即整体下线;开关在「隐私权限」) -->

View File

@@ -3,7 +3,7 @@
(function () {
'use strict';
/* 页面版本号(YYMMDD+序号):显示在「关于兜爸心选」页底部;bump 时同步 head 注释与 ?v= */
var VER = '26101203';
var VER = '26101207';
const $ = (s, c) => (c || document).querySelector(s);
const $$ = (s, c) => Array.from((c || document).querySelectorAll(s));
@@ -4972,6 +4972,24 @@
}
return tipsLoading;
}
/* 手绘图标:el 里放一张 <img>(装饰,aria-hidden);没有库 / 没有这个 id / 图片加载失败 → 写回 emoji */
function setHandIco(el, id, emoji) {
if (!el) return;
if (!id || !window.handIcons || !handIcons.has(id)) { el.textContent = emoji; return; }
const cur = el.querySelector('img');
if (cur && cur.dataset.hi === id) return;
const im = handIcons.img(id);
im.removeAttribute('width'); im.removeAttribute('height'); // 尺寸跟着各处的 font-size(css 里 1em 起算)
im.dataset.hi = id;
im.onerror = function () { el.textContent = emoji; };
el.textContent = '';
el.appendChild(im);
}
/* 小知识四个固定入口(首页那排 + 详情页四张卡):妈妈 / 吃 / 留心固定图标;「宝宝这周」跟孕周走,在 renderTips 里写 */
$$('[data-pt="mom"] > i, [data-pt="mom"] > .pt-ico').forEach(el => setHandIco(el, 'mom', '🤰'));
$$('[data-pt="eat"] > i, [data-pt="eat"] > .pt-ico').forEach(el => setHandIco(el, 'eat', '🥗'));
$$('[data-pt="care"] > i, [data-pt="care"] > .pt-ico').forEach(el => setHandIco(el, 'care', '📋'));
function renderTips() {
const sec = $('#pregTips');
if (!sec) return;
@@ -4984,14 +5002,17 @@
const size = babySize(w);
$('#ptWk').textContent = '孕 ' + state.week + ' 周';
$('#ptWhen').textContent = '本周第 ' + (today + 1) + ' 天';
$('#ptFloat').textContent = size.emoji;
$('#ptBabyIco').textContent = size.emoji;
/* 2026-10-12 用户:小知识里的 emoji 换成兜爸手绘图标库(/_bridge/hand-icons.js,小程序后台 375797cf 维护);
图标按孕周从 handIcons.week() 取,SIZE 表只管文字;库没加载 / 图片挂了 → 退回原来的 emoji,位置不空 */
const hw = window.handIcons && handIcons.week(w);
setHandIco($('#ptFloat'), hw && hw.id, size.emoji);
setHandIco($('#ptBabyIco'), hw && hw.id, size.emoji);
/* 四张卡全文只在详情页 #detailTips;首页那排入口只有图标 + 标题 */
$('#ptBaby').textContent = '像' + size.name + '那么大。' + (wk.baby || '');
$('#ptMom').textContent = wk.mom || '';
$('#ptEat').textContent = wk.eat || '';
$('#ptCare').textContent = wk.care || '';
$('#ptChipBaby').textContent = size.emoji;
setHandIco($('#ptChipBaby'), hw && hw.id, size.emoji);
$('#ptDtTitle').textContent = '孕 ' + state.week + ' 周 · 这一周';
/* 一周 7 个圆点:以前可回看,今天实心,以后锁着 */
const box = $('#ptDays');

151
js/coach.js Normal file
View File

@@ -0,0 +1,151 @@
/* ================= 首页操作引导(2026-10-12) =================
用户:「给首页加上引导操作步骤」。第一次进首页时,一步一步高亮讲 5 个地方:
① 头图孕周 ② 孕育工具 ③ 此刻 ④「记一下」圆钮 ⑤「我的」
· 只走一次:看完或点「跳过」都记 localStorage 'mmbox.coach.v1',以后不再出(同 onboarding-once-rule)。
· 调试 / 给人看:URL 带 ?coach=1 每次都出。
· 不抢别的弹层:授权、欢迎礼、孕期档案、记一下面板、详情页等开着时不计时,
全部关掉、首页安静 1.5 秒后才开始;引导进行中别的弹层冒出来 → 暂时收起,等它关了从当前步接着讲。
· 某一步的目标不在页面上(如「此刻」接口没数据整块隐藏)→ 自动跳过这一步,不留空框。
· 遮罩挂在 #phone 里(跟页面同一个坐标系),z-index 高于所有弹层;滚动交给首页 .screen,引导时锁住。 */
(function () {
'use strict';
const KEY = 'mmbox.coach.v1';
const FORCE = /[?&]coach=1\b/.test(location.search);
try { if (!FORCE && localStorage.getItem(KEY)) return; } catch (e) { return; }
const phone = document.getElementById('phone');
const scr = document.getElementById('screen-home');
if (!phone || !scr) return;
/* 样式随脚本走(不动 mama-box.css)。z-index 140:高于 modal(125) / pk-sheet(131)。 */
const st = document.createElement('style');
st.textContent =
'.mb-coach{position:absolute;inset:0;z-index:140;opacity:0;visibility:hidden;transition:opacity .25s,visibility .25s;-webkit-tap-highlight-color:transparent}' +
'.mb-coach.show{opacity:1;visibility:visible}' +
'.mbc-hole{position:absolute;border-radius:18px;box-shadow:0 0 0 2px rgba(255,255,255,.9),0 0 0 9999px rgba(40,22,16,.62);transition:left .3s,top .3s,width .3s,height .3s;pointer-events:none}' +
'.mb-coach.is-moving .mbc-hole,.mb-coach.is-moving .mbc-card{transition:none}' +
'.mbc-card{position:absolute;left:20px;right:20px;background:#fff;border-radius:18px;padding:16px 18px 12px;box-shadow:0 12px 32px rgba(0,0,0,.18);transition:top .3s}' +
'.mbc-n{display:block;font-size:12px;color:#B08A7C;margin-bottom:4px}' +
'.mbc-t{display:block;font-size:17px;color:var(--ink,#2B2320);margin-bottom:6px}' +
'.mbc-d{margin:0;font-size:14px;line-height:1.6;color:#6B5A53}' +
'.mbc-act{display:flex;justify-content:flex-end;gap:8px;margin-top:12px}' +
'.mbc-act button{min-height:40px;min-width:64px;padding:0 16px;border:0;border-radius:20px;font-size:14px;font-weight:600}' +
'.mbc-skip{background:none;color:#9C8A83}' +
'.mbc-next{background:var(--brand,#F4593C);color:#fff}' +
'.screen.mbc-lock{overflow:hidden!important}' +
'@media (prefers-reduced-motion:reduce){.mbc-hole,.mbc-card,.mb-coach{transition:none}}';
document.head.appendChild(st);
const STEPS = [
{ sel: '.hero-pane', t: '这是你的孕周', d: '孕几周、离见面还有几天、宝宝现在有多大,每天打开都会更新。' },
{ sel: '.tool-dock', t: '常用孕育工具', d: '数胎动、孕检日历、体重这些常用工具都在这里,点卡片直接进。' },
{ sel: '#pulse', t: '此刻', d: '看看和你同一周的妈妈们今天都在做什么。' },
{ sel: '.tab-fab', t: '记一下', d: '想记点什么、找更多工具,点中间这颗按钮。' },
{ sel: '.tabbar [data-tab="me"]', t: '我的', d: '孕期档案、头像、兜豆和兑换记录都在这里。' }
];
const visible = el => {
if (!el) return false;
const r = el.getBoundingClientRect();
if (!r.width || !r.height) return false;
const cs = getComputedStyle(el);
return cs.display !== 'none' && cs.visibility !== 'hidden' && !el.classList.contains('hide');
};
// 有别的弹层 / 不在首页 / 页面在后台 → 先别讲
function busy() {
if (document.hidden || !scr.classList.contains('is-active')) return true;
if (document.querySelector('.modal.show,.sheet.show,.detail.show,.pk-sheet.show,.search-layer.show,.bx-reveal.show')) return true;
const wh = document.getElementById('whPage');
if (wh && wh.getAttribute('aria-hidden') === 'false') return true;
return false;
}
let root, hole, card, idx = -1, on = false, timer = 0;
function build() {
root = document.createElement('div');
root.className = 'mb-coach';
root.setAttribute('role', 'dialog');
root.setAttribute('aria-modal', 'true');
root.setAttribute('aria-label', '首页使用引导');
root.innerHTML = '<i class="mbc-hole" aria-hidden="true"></i>' +
'<div class="mbc-card"><span class="mbc-n"></span><b class="mbc-t"></b><p class="mbc-d"></p>' +
'<div class="mbc-act"><button type="button" class="mbc-skip">跳过</button><button type="button" class="mbc-next">下一步</button></div></div>';
phone.appendChild(root);
hole = root.querySelector('.mbc-hole');
card = root.querySelector('.mbc-card');
root.querySelector('.mbc-skip').addEventListener('click', e => { e.stopPropagation(); done(); });
root.querySelector('.mbc-next').addEventListener('click', e => { e.stopPropagation(); go(idx + 1); });
// 点遮罩任意处也算下一步(大靶子);引导时不让页面跟着滑
root.addEventListener('click', () => go(idx + 1));
root.addEventListener('touchmove', e => { if (e.cancelable) e.preventDefault(); }, { passive: false });
root.addEventListener('wheel', e => e.preventDefault(), { passive: false });
}
function place() {
const s = STEPS[idx], el = s && document.querySelector(s.sel);
if (!el || !visible(el)) return false;
const p = phone.getBoundingClientRect(), r = el.getBoundingClientRect(), pad = 6;
const x = r.left - p.left - pad, y = r.top - p.top - pad, w = r.width + pad * 2, h = r.height + pad * 2;
Object.assign(hole.style, { left: x + 'px', top: y + 'px', width: w + 'px', height: h + 'px' });
hole.style.borderRadius = getComputedStyle(el).borderRadius === '0px' ? '16px' : 'calc(' + getComputedStyle(el).borderRadius + ' + 6px)';
const live = STEPS.filter(t => visible(document.querySelector(t.sel)));
root.querySelector('.mbc-n').textContent = (live.indexOf(s) + 1) + ' / ' + live.length;
root.querySelector('.mbc-t').textContent = s.t;
root.querySelector('.mbc-d').textContent = s.d;
root.querySelector('.mbc-next').textContent = live.indexOf(s) === live.length - 1 ? '知道了' : '下一步';
// 卡片放在目标下方,放不下就放上方
const ch = card.offsetHeight || 140, below = y + h + 12, ph = p.height;
card.style.top = (below + ch < ph - 16 ? below : Math.max(16, y - ch - 12)) + 'px';
return true;
}
function go(i) {
while (i < STEPS.length) {
const el = document.querySelector(STEPS[i].sel);
if (visible(el)) break;
i++;
}
if (i >= STEPS.length) return done();
idx = i;
const el = document.querySelector(STEPS[i].sel);
// 首页里的目标先滚到视口中间(底栏不在 .screen 里,不用滚)
if (scr.contains(el)) {
const sr = scr.getBoundingClientRect(), r = el.getBoundingClientRect();
const want = scr.scrollTop + (r.top - sr.top) - Math.max(80, (sr.height - r.height) / 2 - 60);
scr.scrollTop = Math.max(0, want);
}
root.classList.add('is-moving');
requestAnimationFrame(() => requestAnimationFrame(() => { place(); root.classList.remove('is-moving'); }));
}
function show() {
if (!root) build();
on = true;
root.classList.add('show');
scr.classList.add('mbc-lock');
go(idx < 0 ? 0 : idx);
}
function hideTmp() { if (root) root.classList.remove('show'); scr.classList.remove('mbc-lock'); on = false; }
function done() {
try { localStorage.setItem(KEY, String(Date.now())); } catch (e) { /* 存不了就只是下次还会出 */ }
clearInterval(timer);
if (root) { root.classList.remove('show'); setTimeout(() => root.remove(), 300); }
scr.classList.remove('mbc-lock');
idx = STEPS.length;
}
// 每 0.5 秒看一眼:首页安静满 1.5 秒才开讲;讲到一半有弹层冒出来就先收起
let quiet = 0;
function tick() {
if (idx >= STEPS.length) return;
if (busy()) { quiet = 0; if (on) hideTmp(); return; }
if (on) return;
if ((quiet += 500) >= 1500) show();
}
// 等孕周数据回来(头图有真实数字)再开始;最多等 4 秒
let started = false;
const start = () => { if (started) return; started = true; timer = setInterval(tick, 500); };
addEventListener('mb-preg', () => setTimeout(start, 600));
setTimeout(start, 4000);
addEventListener('resize', () => { if (on) place(); });
})();

View File

@@ -4856,3 +4856,9 @@ a.skip:focus{left:16px;top:16px}
box-shadow:0 0 0 1.5px #E7DCD5,0 3px 10px rgba(90,50,35,.12)}
.avp-x:active{transform:scale(.92)}
/* 小知识手绘图标(2026-10-12,js/app.js setHandIco):img 尺寸按各处原来的 emoji 字号放大一点,手绘线条在 20px 以下看不清 */
.pt-float img,.pt-chip i img,.pt-ico img{display:block;width:1em;height:1em;object-fit:contain;pointer-events:none}
.pt-float img{width:1.4em;height:1.4em}
.pt-chip i img{width:1.5em;height:1.5em}
.pt-ico img{width:1.3em;height:1.3em}