计算器改分步模式(wizard.js,12 个含产假计算器模式)、测试卡图文分开、百宝箱火苗去圈、关注前3条头像各自2~6秒换(v26110419)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
deploy
2026-11-04 08:44:16 +00:00
parent e57bc9f4a5
commit a6e4a73d21
28 changed files with 342 additions and 90 deletions

View File

@@ -57,7 +57,7 @@
'.tc-more{display:block;width:100%;margin-top:6px;height:40px;border:0;border-radius:99px;background:#F7F8FA;color:#4E5969;font-size:13px;font-weight:600}';
var SLUG = (location.pathname.match(/\/topic\/([^\/]+)\//) || [])[1] || '';
/* 11-04 微信转发图:每个专题一张 assets/share/t/<slug>.jpg(核心词大字 + logo),见 js/share-card.js */
if (SLUG) (function () { var s = document.createElement('script'); s.src = '../../js/share-card.js?v=26110418'; s.onload = function () { window.mbShare('../../assets/share/t/' + SLUG + '.jpg'); }; document.head.appendChild(s); })();
if (SLUG) (function () { var s = document.createElement('script'); s.src = '../../js/share-card.js?v=26110419'; s.onload = function () { window.mbShare('../../assets/share/t/' + SLUG + '.jpg'); }; document.head.appendChild(s); })();
var SK = 'mmbox.tp.v1', UK = (function () { var m = ((location.hash || '') + '&' + (location.search || '')).match(/[#?&]openid=([^&]*)/); return m ? decodeURIComponent(m[1]) : '_guest'; })();
function loadMe() { try { var a = JSON.parse(localStorage.getItem(SK) || '{}') || {}; var m = a[UK] || {}; m.cm = m.cm || {}; m.lk = m.lk || {}; return { a: a, m: m }; } catch (e) { return { a: {}, m: { cm: {}, lk: {} } }; } }
function saveMe(st) { try { st.a[UK] = st.m; localStorage.setItem(SK, JSON.stringify(st.a)); } catch (e) {} }
@@ -70,7 +70,7 @@
function getJSON(u) { return fetch(u, { cache: 'no-cache' }).then(function (r) { if (!r.ok) throw r.status; return r.json(); }); }
function related(box) {
getJSON('../_shared/idx.json?v=26110418').then(function (idx) {
getJSON('../_shared/idx.json?v=26110419').then(function (idx) {
var me = null; idx.forEach(function (x) { if (x[0] === SLUG) me = x; });
var tag = me ? me[2] : '', h = 0; for (var i = 0; i < SLUG.length; i++) h = (h * 31 + SLUG.charCodeAt(i)) >>> 0;
var rest = idx.filter(function (x) { return x[0] !== SLUG && (window.__mbUgc || ['ceshi', 'duiyou'].indexOf(x[0]) < 0); }); /* UGC 关着不推两个测试 */
@@ -177,13 +177,13 @@
}
fetch(API + '/comments?kind=topic&target=' + encodeURIComponent(SLUG) + (OID ? '&openid=' + encodeURIComponent(OID) : ''))
.then(function (r) { return r.json(); }).then(function (j) { if (j && j.code === 200) { RC = j.result; paint(); } }).catch(function () {});
getJSON('../_shared/cm/' + SLUG + '.json?v=26110418').then(function (d) { L = d.c || []; paint(); }).catch(function () { paint(); });
getJSON('../_shared/cm/' + SLUG + '.json?v=26110419').then(function (d) { L = d.c || []; paint(); }).catch(function () { paint(); });
}
function ugcReady() {
if (window.__mbUgcReady) return window.__mbUgcReady;
return new Promise(function (res) {
var u = document.createElement('script'); u.src = '../../js/ugc.js?v=26110418';
var u = document.createElement('script'); u.src = '../../js/ugc.js?v=26110419';
u.onload = function () { (window.__mbUgcReady || Promise.resolve(false)).then(res); };
u.onerror = function () { res(false); };
document.head.appendChild(u);

46
topic/_shared/wizard.css Normal file
View File

@@ -0,0 +1,46 @@
/* 计算器分步模式(配合 wizard.js,11-04)。用页面的 --c 主题色;--h 标题色、--m 弱文字色。 */
.wz .wz-off,.wz-hold,html.wz-q #tx-rel,html.wz-q #tx-cm,html.wz-q .tc-in{display:none!important}
.wz:not(.wz-res) .res,.wz:not(.wz-res) .wz-again,.wz.wz-res .wz-top,.wz.wz-res .wz-bot{display:none!important}
.wz{min-height:300px}
.wz-top{display:flex;align-items:center;justify-content:space-between;gap:10px;margin-bottom:4px}
.wz-n{font-size:12.5px;font-weight:800;color:var(--c);letter-spacing:.5px}
.wz-dots{display:flex;gap:5px}
.wz-dots i{width:7px;height:7px;border-radius:99px;background:#ECE6E8;transition:width .25s,background .25s}
.wz-dots i.d{background:var(--c);opacity:.45}
.wz-dots i.c{width:20px;background:var(--c)}
/* 题目:标签放大成问题 */
.wz .wz-in{animation:wzIn .32s ease both}
.wz .fld.wz-in{margin-top:10px}
.wz .fld.wz-in>.lb,.wz .fld.wz-in>label.lb,.wz .fld.wz-in>span.lb{display:block;margin-bottom:12px;font-size:20px;font-weight:900;line-height:1.4;color:var(--h)}
.wz .fld.wz-in>.lb small,.wz .fld.wz-in>.lb small{display:block;margin-top:3px;font-size:13px;font-weight:500;color:var(--m)}
.wz .fld.two.wz-in .lb{font-size:16px;font-weight:800;color:var(--h)}
.wz .tabs.wz-in{margin-top:10px}
.wz .tabs.wz-in::before{content:attr(data-wzq);display:block;margin-bottom:12px;font-size:20px;font-weight:900;line-height:1.4;color:var(--h)}
.calc.wz .tabs.wz-in{display:flex;flex-direction:column;gap:10px;padding:0;border-radius:0;background:none}
.calc.wz .tabs.wz-in button{flex:none;height:54px;border-radius:14px;border:1.5px solid #EDE7EA;background:#fff;box-shadow:none;font-size:16px;font-weight:700;color:var(--h)}
.calc.wz .tabs.wz-in button.on{border-color:var(--c);background:var(--cbg);color:var(--c)}
.wz .fld.wz-in .seg button{min-height:46px;font-size:15px}
.wz .fld.wz-in .in,.wz .fld.wz-in .inp{min-height:50px;font-size:17px}
/* 底部按钮 */
.wz-bot{margin-top:22px}
.wz-err{min-height:18px;margin-bottom:6px;font-size:13px;font-weight:700;color:#E5484D;text-align:center}
.wz-err:empty{min-height:0;margin:0}
.wz-act{display:flex;gap:10px}
.wz-prev{flex:0 0 auto;height:48px;padding:0 16px;border-radius:99px;background:#F5F2F3;color:var(--m);font-size:14.5px;font-weight:700}
.wz-next{flex:1;height:48px;border-radius:99px;background:var(--c);color:#fff;font-size:16px;font-weight:800;box-shadow:0 8px 18px -8px var(--c)}
.wz-next.go{background:linear-gradient(135deg,var(--c2,var(--c)),var(--c))}
.wz-prev[hidden]{display:none}
.wz-tip{margin-top:8px;font-size:12px;color:var(--m);text-align:center}
.wz-tip:empty{display:none}
.wz-shake .wz-in{animation:wzShake .35s}
/* 结果屏 */
.wz.wz-res{min-height:0}
.wz-pop{animation:wzPop .5s cubic-bezier(.3,1.4,.5,1) both}
.wz-again{display:flex;flex-direction:column;align-items:center;gap:4px;margin-top:14px}
.wz-re{height:42px;padding:0 22px;border-radius:99px;border:1.5px solid var(--c);color:var(--c);font-size:14.5px;font-weight:800;background:#fff}
.wz-re{display:inline-flex;align-items:center;gap:5px}.wz-re svg{width:16px;height:16px;fill:none;stroke:currentColor;stroke-width:2;stroke-linecap:round;stroke-linejoin:round}
.wz-again small{font-size:12px;color:var(--m)}
@keyframes wzIn{from{opacity:0;transform:translateX(18px)}to{opacity:1;transform:none}}
@keyframes wzPop{from{opacity:0;transform:scale(.92) translateY(10px)}to{opacity:1;transform:none}}
@keyframes wzShake{0%,100%{transform:none}25%{transform:translateX(-6px)}75%{transform:translateX(6px)}}
@media (prefers-reduced-motion:reduce){.wz .wz-in,.wz-pop,.wz-shake .wz-in{animation:none}}

144
topic/_shared/wizard.js Normal file
View File

@@ -0,0 +1,144 @@
/* 计算器分步模式(11-04 用户:「其他计算器都按生娃计算器那种模式:点进去一步一步填,最后出答案」)
用法:页面原来的计算逻辑不动(照样实时往结果框里写),本脚本只管「显示哪一块」:
CKW.init({ root: '.calc', res: '#res', after: '#scroll > section.calc ~ *' })
· root 里每个 .fld 是一题;.tabs(算法切换)单独一题,题目取 data-q;包在 div 里的 .fld 会被拆开,带 .hide 的跳过;
.calc 直属的 .hint 跟着上一题。
· 必填:有 input/select/.seg 的题要先填;标签里写了「选填」或 .fld 带 data-opt 的不拦。
· 只有一组 .seg / .tabs 的题,点完自动下一题(同生娃计算器)。
· 最后一题按钮是「看结果」:结果框里还是 .empty(没填全 / 数不对)就不放行,把那句提示显示出来。
· 结果屏:只出结果框 + after 选中的内容(详细说明等)+「重新算」(回第 1 题,填过的都留着)。
· 记住停在哪一屏(AGENTS 规则:测试 / 计算器再进来回到最后停的屏),键 mmbox.wz.v1,按页面路径分。 */
(function (g) {
'use strict';
var KEY = 'mmbox.wz.v1';
function load() { try { return JSON.parse(localStorage.getItem(KEY) || '{}') || {}; } catch (e) { return {}; } }
function save(v) { try { var m = load(); m[location.pathname] = v; localStorage.setItem(KEY, JSON.stringify(m)); } catch (e) {} }
function hidden(el) { return el.classList.contains('hide') || el.hidden; }
function txt(el) { return el ? el.textContent.replace(/\s+/g, ' ').trim() : ''; }
function init(o) {
var root = typeof o.root === 'string' ? document.querySelector(o.root) : o.root;
var res = typeof o.res === 'string' ? document.querySelector(o.res) : o.res;
if (!root || !res || root.__wz) return;
root.__wz = 1;
var after = o.after ? [].slice.call(document.querySelectorAll(o.after)).filter(function (e) { return !/toast|mask/.test(e.className + ' ' + e.id); }) : [];
var sc = document.getElementById('scroll') || document.scrollingElement;
root.classList.add('wz');
/* 顶部进度 + 底部按钮 */
var top = document.createElement('div'); top.className = 'wz-top';
top.innerHTML = '<span class="wz-n"></span><span class="wz-dots"></span>';
var bot = document.createElement('div'); bot.className = 'wz-bot';
bot.innerHTML = '<p class="wz-err" role="alert"></p><div class="wz-act"><button type="button" class="wz-prev">‹ 上一步</button><button type="button" class="wz-next"></button></div><p class="wz-tip"></p>';
var again = document.createElement('div'); again.className = 'wz-again';
again.innerHTML = '<button type="button" class="wz-re"><svg viewBox="0 0 24 24" aria-hidden="true"><path d="M20 11a8 8 0 0 0-14.3-4.9L4 8M4 4v4h4M4 13a8 8 0 0 0 14.3 4.9L20 16M20 20v-4h-4"/></svg>重新算</button><small>填过的都还在,改哪项点哪项</small>';
root.insertBefore(top, root.firstChild);
root.appendChild(bot);
res.parentNode.insertBefore(again, res.nextSibling);
var nEl = top.querySelector('.wz-n'), dEl = top.querySelector('.wz-dots'), err = bot.querySelector('.wz-err'),
prev = bot.querySelector('.wz-prev'), next = bot.querySelector('.wz-next'), tip = bot.querySelector('.wz-tip');
/* 题目单元:每次现算(页面切 tabs 会显示 / 隐藏不同的 .fld) */
function units() {
var out = [];
[].forEach.call(root.children, function (c) {
if (c === top || c === bot || c === res || c === again || hidden(c)) return;
if (c.classList.contains('fld') || c.classList.contains('tabs')) out.push([c]);
else if (c.querySelector('.fld')) [].forEach.call(c.querySelectorAll('.fld'), function (f) { if (!hidden(f)) out.push([f]); });
else if (out.length) out[out.length - 1].push(c); /* 跟在题后的 .hint / 「再加一个孩子」等 */
else out.push([c]);
});
return out;
}
/* 能被藏的块:root 的直属子元素(除进度 / 按钮 / 结果框)+ 壳里的 .fld;题里面的东西不碰 */
function all() {
var out = [];
[].forEach.call(root.children, function (c) {
if (c === top || c === bot || c === res || c === again) return;
out.push(c);
if (!c.classList.contains('fld') && !c.classList.contains('tabs')) [].push.apply(out, c.querySelectorAll('.fld'));
});
return out;
}
function single(u) { /* 只有一组按钮可选 → 选完自动下一题 */
var el = u[0];
if (el.classList.contains('tabs')) return true;
return el.querySelectorAll('.seg').length === 1 && !el.querySelector('input,select,.stp');
}
function missing(u) {
var el = u[0];
if (el.hasAttribute('data-opt') || /选填/.test(txt(el.querySelector('.lb')))) return '';
var bad = '';
[].forEach.call(el.querySelectorAll('input,select'), function (i) {
if (bad || i.type === 'hidden' || i.disabled) return;
var lb = i.closest('div') && i.closest('div').querySelector('.lb');
if (lb && /选填/.test(txt(lb))) return;
if (!String(i.value).trim()) bad = '先填一下「' + (txt(lb || el.querySelector('.lb')).replace(/选填.*$/, '') || '这一项') + '」';
});
if (!bad) [].forEach.call(el.querySelectorAll('.seg'), function (s) { if (!bad && !s.querySelector('.on')) bad = '先选一下' + (txt(el.querySelector('.lb')) ? '「' + txt(el.querySelector('.lb')).replace(/\s.*$/, '') + '」' : ''); });
return bad;
}
var i = 0, mode = 'q';
function show() {
var us = units();
if (i >= us.length) i = us.length - 1;
if (i < 0) i = 0;
all().forEach(function (e) { e.classList.add('wz-off'); });
[].forEach.call(root.querySelectorAll('.wz-in'), function (e) { e.classList.remove('wz-in'); });
root.classList.toggle('wz-res', mode === 'r');
document.documentElement.classList.toggle('wz-q', mode !== 'r'); /* 答题时页面底部后加的相关推荐 / 评论区也先不出 */
after.forEach(function (e) { e.classList.toggle('wz-hold', mode !== 'r'); });
err.textContent = '';
if (mode === 'r') {
res.classList.remove('wz-pop'); void res.offsetWidth; res.classList.add('wz-pop');
save('r'); sc.scrollTop = 0; return;
}
var u = us[i];
u.forEach(function (e) {
e.classList.remove('wz-off'); e.classList.add('wz-in');
if (e.parentNode !== root) e.parentNode.classList.remove('wz-off'); /* 包在 #fLmp 这类壳里的题 */
});
var q = u[0].classList.contains('tabs') ? (u[0].getAttribute('data-q') || '先选一种算法') : '';
u[0].setAttribute('data-wzq', q);
nEl.textContent = '第 ' + (i + 1) + ' / ' + us.length + ' 步';
dEl.innerHTML = us.map(function (_, k) { return '<i' + (k < i ? ' class="d"' : k === i ? ' class="c"' : '') + '></i>'; }).join('');
prev.hidden = !i;
var last = i === us.length - 1;
next.textContent = last ? '看结果 ›' : '下一步 ›';
next.classList.toggle('go', last);
tip.textContent = single(u) && !last ? '选完自动下一步' : '';
save(i); sc.scrollTop = 0;
}
function go(d) {
var us = units();
if (d > 0) {
var m = missing(us[i]);
if (m) { err.textContent = m; root.classList.remove('wz-shake'); void root.offsetWidth; root.classList.add('wz-shake'); return; }
if (i === us.length - 1) {
var e = res.querySelector('.empty');
if (e) { err.textContent = txt(e); return; }
mode = 'r'; return show();
}
}
i += d; show();
}
prev.addEventListener('click', function () { go(-1); });
next.addEventListener('click', function () { go(1); });
again.querySelector('.wz-re').addEventListener('click', function () { mode = 'q'; i = 0; show(); });
/* 单选题点完自动下一步(等页面自己的点击处理先跑完) */
root.addEventListener('click', function (e) {
if (mode !== 'q') return;
var b = e.target.closest('.seg button,.tabs button'); if (!b) return;
var us = units(), u = us[i];
if (!u || u.indexOf(b.closest('.fld,.tabs')) < 0 || !single(u) || i === us.length - 1) return;
setTimeout(function () { if (mode === 'q' && units()[i] && units()[i][0] === u[0]) go(1); }, 320);
});
var sv = load()[location.pathname];
if (sv === 'r' && !res.querySelector('.empty')) mode = 'r';
else if (typeof sv === 'number') i = sv;
show();
}
g.CKW = { init: init };
})(window);