摇一摇加音效(Web Audio 合成,可关)+ 落定后「就它了 / 重新摇」;百问核心词改上图下字(v26110401)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
deploy
2026-11-04 04:20:17 +00:00
parent 75c418f453
commit 2d779f1b4a
22 changed files with 146 additions and 87 deletions

View File

@@ -314,6 +314,16 @@ button{font:inherit;color:inherit;border:0;background:none;padding:0;cursor:poin
.lko-card .lko-ic{display:grid;place-items:center;width:52px;height:52px;border-radius:16px;background:var(--cb);box-shadow:0 6px 14px -6px var(--cb);position:relative}
.lko-card .lko-ic svg{width:30px;height:30px}
.lko-fx{position:absolute;left:50%;top:50%;pointer-events:none}
.lko-act{display:flex;flex-direction:column;align-items:stretch;gap:10px;width:100%;opacity:0;transform:translateY(10px);pointer-events:none;transition:opacity .25s,transform .3s cubic-bezier(.3,1.4,.5,1)}
.lko.done .lko-act{opacity:1;transform:none;pointer-events:auto}
.lko:not(.hit) .lko-act{display:none}
.lko-go{height:50px;border-radius:99px;background:linear-gradient(120deg,#FFD25A,#FF9A4D);color:#5A2A00;font-size:17px;font-weight:900;letter-spacing:1px;box-shadow:0 10px 22px -10px rgba(255,150,60,.9);animation:lkPulse 1.8s ease-in-out infinite}
.lko-re{display:flex;align-items:center;justify-content:center;gap:6px;height:44px;border-radius:99px;background:rgba(255,255,255,.16);box-shadow:inset 0 0 0 1.5px rgba(255,255,255,.55);color:#fff;font-size:15px;font-weight:800}
.lko-re svg{width:22px;height:22px}
.lko-x,.lko-snd{position:absolute;top:calc(14px + env(safe-area-inset-top));z-index:2;width:36px;height:36px;border-radius:50%;background:rgba(255,255,255,.16);color:#fff;font-size:24px;line-height:36px;text-align:center}
.lko-x{right:14px}.lko-snd{left:14px}
.lko-snd::before{content:"";position:absolute;inset:0;background:center/20px no-repeat url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='white' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M4 9.5h3.5L12 5.5v13l-4.5-4H4z' fill='white'/%3E%3Cpath d='M15.5 9a4.2 4.2 0 010 6M18 6.5a7.8 7.8 0 010 11'/%3E%3C/svg%3E")}
.lko-snd.off::before{background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='white' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M4 9.5h3.5L12 5.5v13l-4.5-4H4z' fill='white'/%3E%3Cpath d='M16 9.5l5 5M21 9.5l-5 5'/%3E%3C/svg%3E")}
.lko-fx i{position:absolute;width:9px;height:12px;border-radius:2px;animation:lkoBoom 1.3s cubic-bezier(.2,.7,.4,1) forwards}
@keyframes lkoBoom{from{transform:translate(0,0) rotate(0) scale(1);opacity:1}to{transform:translate(var(--x),calc(var(--y) + 180px)) rotate(var(--r)) scale(.6);opacity:0}}
@media (prefers-reduced-motion:reduce){.lko-dc span{animation:none}}
@@ -837,11 +847,11 @@ button{font:inherit;color:inherit;border:0;background:none;padding:0;cursor:poin
<div id="sheet" role="dialog" aria-modal="true"><i class="sg"></i><div class="sbd" id="sbd"></div></div>
<div class="toast" id="toast"></div>
<script src="topics.js?v=26110299"></script>
<script src="data.js?v=26110299"></script>
<script src="feed.js?v=26110299"></script>
<script src="ceshi/meta.js?v=26110299"></script>
<script src="../js/share-card.js?v=26110299"></script>
<script src="topics.js?v=26110401"></script>
<script src="data.js?v=26110401"></script>
<script src="feed.js?v=26110401"></script>
<script src="ceshi/meta.js?v=26110401"></script>
<script src="../js/share-card.js?v=26110401"></script>
<script>
(function () {
'use strict';
@@ -907,7 +917,7 @@ button{font:inherit;color:inherit;border:0;background:none;padding:0;cursor:poin
var CMD = null, CMERR = false;
/* 头像编码:数字 = av/NNNN.webp(旧代码头像);'av:c012' = av/c012.webp(卡通/可爱卡通);'/…' = 站内绝对路径(内置头像库 /_bridge/avatars/);其余 = assets/img/mom/xxx.jpg */
var avP = function (a) { return typeof a === 'number' ? 'av/' + ('000' + a).slice(-4) + '.webp' : a.indexOf('av:') === 0 ? 'av/' + a.slice(3) + '.webp' : a.charAt(0) === '/' ? a : '../assets/img/mom/' + a + '.jpg'; };
fetch('cm.json?v=26110299').then(function (r) { return r.json(); }).then(function (j) {
fetch('cm.json?v=26110401').then(function (r) { return r.json(); }).then(function (j) {
CMD = j; if (openId && NBY[openId]) { renderComments(NBY[openId]); syncBar(NBY[openId]); }
}).catch(function () { CMERR = true; if (openId && NBY[openId]) renderComments(NBY[openId]); });
function commentsOf(n) {
@@ -1245,33 +1255,81 @@ button{font:inherit;color:inherit;border:0;background:none;padding:0;cursor:poin
}
var csdBusy = false;
/* 11-04 用户:「点摇一摇,整个页面上浮出一个动态效果,结束后再跳到那个测试」→ 全屏浮层:两颗大骰子翻滚 + 老虎机转测试名(越转越慢)→ 落定弹出那个测试的卡(分组配色 + 专属图标)+ 全屏撒彩纸 → 进入 */
/* 11-04 用户:「摇一摇的动效要有音效」→ Web Audio 现合成(不加音频文件):开摇骰子哗啦声、转名字每格一下「嗒」、落定「叮咚」+ 彩纸「啵」;
右上角喇叭可关,记在 mmbox.gc.snd。用户点按钮时才建 AudioContext(微信 / iOS 要求用户手势后才能出声)。 */
var SND = { ctx: null, on: true };
try { SND.on = localStorage.getItem('mmbox.gc.snd') !== '0'; } catch (e) {}
function sndCtx() {
if (!SND.on) return null;
try { if (!SND.ctx) SND.ctx = new (window.AudioContext || window.webkitAudioContext)(); if (SND.ctx.state === 'suspended') SND.ctx.resume(); } catch (e) { return null; }
return SND.ctx;
}
function tone(f, t0, d, type, vol, f2) {
var c = sndCtx(); if (!c) return;
var o = c.createOscillator(), g = c.createGain(), t = c.currentTime + (t0 || 0);
o.type = type || 'sine'; o.frequency.setValueAtTime(f, t); if (f2) o.frequency.exponentialRampToValueAtTime(f2, t + d);
g.gain.setValueAtTime(0, t); g.gain.linearRampToValueAtTime(vol || .2, t + .008); g.gain.exponentialRampToValueAtTime(.0008, t + d);
o.connect(g); g.connect(c.destination); o.start(t); o.stop(t + d + .02);
}
function noise(t0, d, vol, hp) {
var c = sndCtx(); if (!c) return;
var n = Math.floor(c.sampleRate * d), buf = c.createBuffer(1, n, c.sampleRate), ch = buf.getChannelData(0);
for (var i = 0; i < n; i++) ch[i] = (Math.random() * 2 - 1) * Math.pow(1 - i / n, 3);
var s = c.createBufferSource(), f = c.createBiquadFilter(), g = c.createGain(), t = c.currentTime + (t0 || 0);
s.buffer = buf; f.type = 'bandpass'; f.frequency.value = hp || 2400; f.Q.value = 1.2; g.gain.value = vol || .3;
s.connect(f); f.connect(g); g.connect(c.destination); s.start(t);
}
var SFX = {
rattle: function () { for (var i = 0; i < 9; i++) noise(i * .045 + Math.random() * .02, .05, .35, 1800 + Math.random() * 1600); }, /* 骰子在杯里哗啦 */
tick: function (k) { tone(1250 + (k % 2) * 180, 0, .05, 'square', .05); noise(0, .025, .12, 3500); }, /* 转名字「嗒」 */
land: function () { tone(784, 0, .5, 'triangle', .22); tone(1175, .1, .7, 'triangle', .2); tone(1568, .2, .9, 'sine', .12); noise(.02, .18, .25, 900); }, /* 叮咚 + 啵 */
tap: function () { tone(660, 0, .08, 'sine', .12, 990); }
};
/* 11-04 用户:「摇出来以后两个选择:『就它了』或者重新摇」→ 落定后不再自动跳,给两个按钮;重新摇避开刚摇出的那个 */
function csdRoll(btn) {
if (csdBusy) return; csdBusy = true; haptic(); btn.classList.add('roll');
var dn = csDone(), pool = TM.filter(function (x) { return !dn[x.id]; }); if (!pool.length) pool = TM;
var pk = pool[Math.floor(Math.random() * pool.length)], i = 0, steps = 18;
if (csdBusy) return; csdBusy = true; haptic(); if (btn) btn.classList.add('roll');
var dn = csDone(), last = null, pk = null;
var ov = document.createElement('div'); ov.className = 'lko';
ov.innerHTML = '<div class="lko-bg"></div><div class="lko-in"><div class="lko-dc"><span class="a">' + diceSVG(6) + '</span><span class="b">' + diceSVG(3) + '</span></div>' +
'<p class="lko-tip">手气摇号中…</p><div class="lko-reel"><span id="lkoR">?</span></div><div class="lko-card" id="lkoC"></div></div><div class="lko-fx" id="lkoFx"></div>';
ov.innerHTML = '<div class="lko-bg"></div><button type="button" class="lko-x" aria-label="关闭">×</button><button type="button" class="lko-snd' + (SND.on ? '' : ' off') + '" aria-label="音效开关"></button>' +
'<div class="lko-in"><div class="lko-dc"><span class="a">' + diceSVG(6) + '</span><span class="b">' + diceSVG(3) + '</span></div>' +
'<p class="lko-tip">手气摇号中…</p><div class="lko-reel"><span id="lkoR">?</span></div><div class="lko-card" id="lkoC"></div>' +
'<div class="lko-act"><button type="button" class="lko-go">就它了,去测 ›</button><button type="button" class="lko-re">' + diceSVG(5) + '重新摇</button></div></div><div class="lko-fx" id="lkoFx"></div>';
document.body.appendChild(ov); void ov.offsetWidth; ov.classList.add('on');
var R = ov.querySelector('#lkoR'), dcs = ov.querySelectorAll('.lko-dc span');
setTimeout(function spin() {
var last = i >= steps, t = last ? pk : TM[Math.floor(Math.random() * TM.length)];
R.className = ''; void R.offsetWidth; R.className = last ? 'hit' : 'sp'; R.textContent = CS_SHORT[t.id] || t.title;
[].forEach.call(dcs, function (d) { d.innerHTML = diceSVG(1 + Math.floor(Math.random() * 6)); });
if (i % 3 === 0 && navigator.vibrate) try { navigator.vibrate(8); } catch (e) {}
if (!last) { i++; setTimeout(spin, 45 + i * i * 1.15); return; }
ov.classList.add('hit'); haptic();
ov.querySelector('.lko-tip').textContent = '就它了!';
ov.querySelector('#lkoC').innerHTML = '<div class="ck ' + CS_GPAL[pk.group] + '"><span class="cv">' + (CS_BLOBS[pk.group] || CS_BLOB) + '<i class="ck-wm">' + csTI(pk) + '</i><i class="lko-ic">' + csTI(pk) + '</i>' +
'<b>' + esc(pk.title.replace(/^测一测|/, '')) + '</b><small>' + TG[pk.group] + ' · ' + pk.n + ' 题</small><em>马上开测</em></span></div>';
var fx = ov.querySelector('#lkoFx'), cols = ['#FF8A65', '#9A7FE0', '#3FAE7A', '#4E8DE6', '#FFC94D', '#F0453A', '#FF8FB1'], h = '';
for (var k = 0; k < 46; k++) {
var ag = Math.random() * 6.283, r = 120 + Math.random() * 200;
h += '<i style="--x:' + Math.round(Math.cos(ag) * r) + 'px;--y:' + Math.round(Math.sin(ag) * r - 60) + 'px;--r:' + Math.round(Math.random() * 720) + 'deg;background:' + cols[k % 7] + ';animation-delay:' + Math.round(Math.random() * 120) + 'ms;' + (k % 3 ? '' : 'border-radius:50%;') + '"></i>';
}
fx.innerHTML = h;
setTimeout(function () { location.href = ceshiURL(pk.id, true); }, 1500);
}, 260);
function close() { ov.classList.remove('on'); setTimeout(function () { if (ov.parentNode) ov.parentNode.removeChild(ov); }, 260); csdBusy = false; if (btn) btn.classList.remove('roll', 'hit'); }
function roll() {
var pool = TM.filter(function (x) { return !dn[x.id] && x !== last; }); if (!pool.length) pool = TM.filter(function (x) { return x !== last; });
pk = pool[Math.floor(Math.random() * pool.length)];
var i = 0, steps = 18;
ov.classList.remove('hit', 'done'); ov.querySelector('.lko-tip').textContent = '手气摇号中…'; ov.querySelector('#lkoC').innerHTML = ''; ov.querySelector('#lkoFx').innerHTML = '';
SFX.rattle();
setTimeout(function spin() {
var end = i >= steps, t = end ? pk : TM[Math.floor(Math.random() * TM.length)];
R.className = ''; void R.offsetWidth; R.className = end ? 'hit' : 'sp'; R.textContent = CS_SHORT[t.id] || t.title;
[].forEach.call(dcs, function (d) { d.innerHTML = diceSVG(1 + Math.floor(Math.random() * 6)); });
if (!end) { SFX.tick(i); if (i % 3 === 0 && navigator.vibrate) try { navigator.vibrate(8); } catch (e) {} i++; setTimeout(spin, 45 + i * i * 1.15); return; }
ov.classList.add('hit'); haptic(); SFX.land();
ov.querySelector('.lko-tip').textContent = '摇到了!';
ov.querySelector('#lkoC').innerHTML = '<div class="ck ' + CS_GPAL[pk.group] + '"><span class="cv">' + (CS_BLOBS[pk.group] || CS_BLOB) + '<i class="ck-wm">' + csTI(pk) + '</i><i class="lko-ic">' + csTI(pk) + '</i>' +
'<b>' + esc(pk.title.replace(/^测一测|/, '')) + '</b><small>' + TG[pk.group] + ' · ' + pk.n + ' 题</small></span></div>';
var fx = ov.querySelector('#lkoFx'), cols = ['#FF8A65', '#9A7FE0', '#3FAE7A', '#4E8DE6', '#FFC94D', '#F0453A', '#FF8FB1'], h = '';
for (var k = 0; k < 46; k++) {
var ag = Math.random() * 6.283, r = 120 + Math.random() * 200;
h += '<i style="--x:' + Math.round(Math.cos(ag) * r) + 'px;--y:' + Math.round(Math.sin(ag) * r - 60) + 'px;--r:' + Math.round(Math.random() * 720) + 'deg;background:' + cols[k % 7] + ';animation-delay:' + Math.round(Math.random() * 120) + 'ms;' + (k % 3 ? '' : 'border-radius:50%;') + '"></i>';
}
fx.innerHTML = h;
setTimeout(function () { ov.classList.add('done'); }, 380); /* 卡片弹完再出按钮 */
last = pk;
}, 260);
}
ov.addEventListener('click', function (e) {
if (e.target.closest('.lko-go')) { SFX.tap(); location.href = ceshiURL(pk.id, true); return; }
if (e.target.closest('.lko-re')) { if (!ov.classList.contains('done')) return; haptic(); roll(); return; }
if (e.target.closest('.lko-x')) { close(); return; }
var sb = e.target.closest('.lko-snd');
if (sb) { SND.on = !SND.on; sb.classList.toggle('off', !SND.on); try { localStorage.setItem('mmbox.gc.snd', SND.on ? '1' : '0'); } catch (x) {} if (SND.on) SFX.tap(); }
});
roll();
}
window.addEventListener('pageshow', function () { csdBusy = false; [].forEach.call(document.querySelectorAll('.lko'), function (o) { o.parentNode.removeChild(o); }); var b = document.getElementById('csvGo'); if (b) { b.classList.remove('roll', 'hit'); var bb = b.querySelector('.luck-b'); if (bb) bb.textContent = '摇一摇'; } });
var csvT = 0, csvI = 0, csvRec = [], csvRecT = [];