天赋抽取 #8
+7
-5
@@ -17,14 +17,16 @@ const _isDevPort = (port) =>
|
||||
(port === 80 && (location.port === '' || location.port === '80'));
|
||||
|
||||
const API_CONFIG = {
|
||||
// 本地开发一律跨域直连 Odoo 8069(python http.server 无代理能力);
|
||||
// 仅 8080 dev-server(自带反向代理到 Odoo)才走同源空 base。
|
||||
base: _isLocalDev()
|
||||
? ((_isDevPort(8080) || _isDevPort(8888)) ? '' : `http://${location.hostname}:8080`)
|
||||
: '',
|
||||
db: _isLocalDev() ? 'study' : 'game',
|
||||
? (_isDevPort(8080) ? '' : `http://${location.hostname}:8069`)
|
||||
: '',
|
||||
db: _isLocalDev() ? 'game_a' : 'game',
|
||||
};
|
||||
|
||||
// 世界(yt_world)所在库:本地 Odoo 钉 yt_game;线上游戏库名为 game。按域名自适应,git 同步不冲突。
|
||||
const WORLD_DB = _isLocalDev() ? 'yt_game' : 'game';
|
||||
// 世界(yt_world)所在库:与 API_CONFIG.db 保持一致(本地均为 game_a),避免跨库导致 auth 失败。
|
||||
const WORLD_DB = API_CONFIG.db;
|
||||
|
||||
// Odoo 后端基础 URL(世界 API 专用,兼容本地跨域 / 生产同域)
|
||||
const WORLD_BASE = API_CONFIG.base || '';
|
||||
|
||||
+92
-5
@@ -779,7 +779,7 @@ body { background: #0a0e17; overflow: hidden; font-family: 'Microsoft YaHei', sa
|
||||
.tp-empty{text-align:center;padding:30px 10px;color:#7b8ea8;font-size:14px;line-height:1.7;}
|
||||
.tp-empty a{color:#8b5cf6;}
|
||||
/* ===== 新手抽天赋引导 ===== */
|
||||
#talentDraw{position:fixed;inset:0;z-index:1300;display:flex;align-items:flex-end;justify-content:center;background:rgba(4,7,15,.72);backdrop-filter:blur(7px);opacity:0;visibility:hidden;transition:opacity .3s,visibility .3s;padding:28px 18px 40px;}
|
||||
#talentDraw{position:fixed;inset:0;z-index:1300;display:flex;align-items:center;justify-content:center;background:rgba(4,7,15,.72);backdrop-filter:blur(7px);opacity:0;visibility:hidden;transition:opacity .3s,visibility .3s;padding:28px 18px;}
|
||||
#talentDraw.open{opacity:1;visibility:visible;}
|
||||
#talentDraw[hidden]{display:none;}
|
||||
.td-wrap{width:760px;max-width:100%;display:flex;flex-direction:column;align-items:center;gap:18px;}
|
||||
@@ -792,9 +792,14 @@ body { background: #0a0e17; overflow: hidden; font-family: 'Microsoft YaHei', sa
|
||||
.td-bubble b{color:#c4b5fd;}
|
||||
.td-cards{display:flex;gap:14px;flex-wrap:wrap;justify-content:center;min-height:168px;}
|
||||
.td-card{width:120px;height:168px;perspective:900px;cursor:pointer;position:relative;}
|
||||
.td-card.dealt{animation:td-deal .42s ease both;}
|
||||
@keyframes td-deal{from{opacity:0;transform:translateY(26px) rotate(-6deg);}to{opacity:1;transform:none;}}
|
||||
.td-inner{position:relative;width:100%;height:100%;transform-style:preserve-3d;transition:transform .55s cubic-bezier(.2,.8,.3,1);}
|
||||
.td-card.dealt{animation:td-deal .62s cubic-bezier(.22,1.4,.36,1) both;}
|
||||
@keyframes td-deal{
|
||||
0%{opacity:0;transform:translateY(-160px) rotateZ(var(--rz,-6deg)) rotateY(-55deg) scale(.62);}
|
||||
55%{opacity:1;transform:translateY(10px) rotateZ(calc(var(--rz,0deg)*-.35)) rotateY(8deg) scale(1.05);}
|
||||
75%{transform:translateY(-4px) rotateY(-3deg) scale(.98);}
|
||||
100%{transform:none;}
|
||||
}
|
||||
.td-inner{position:relative;width:100%;height:100%;transform-style:preserve-3d;transition:transform 1.0s cubic-bezier(.22,.85,.28,1);}
|
||||
.td-card.flipped .td-inner{transform:rotateY(180deg);}
|
||||
.td-face{position:absolute;inset:0;backface-visibility:hidden;-webkit-backface-visibility:hidden;border-radius:14px;display:flex;flex-direction:column;align-items:center;justify-content:center;text-align:center;padding:10px;box-shadow:0 6px 18px rgba(0,0,0,.45);}
|
||||
.td-back{background:linear-gradient(160deg,#3b2a6b,#1c1340);border:1px solid rgba(167,139,250,.5);color:#c4b5fd;font-size:40px;}
|
||||
@@ -807,6 +812,21 @@ body { background: #0a0e17; overflow: hidden; font-family: 'Microsoft YaHei', sa
|
||||
.td-front.got{box-shadow:0 0 0 2px rgba(70,224,122,.7),0 6px 18px rgba(70,224,122,.35);}
|
||||
.td-front .td-got{position:absolute;top:6px;right:8px;font-size:10px;color:#46e07a;font-weight:700;}
|
||||
.td-card.done{cursor:default;}
|
||||
/* 抽卡揭晓特效:光晕扩散 + 斜向扫光 + 正面弹跳 + 星星迸发(翻面后接续播放) */
|
||||
.td-front .td-glow{position:absolute;inset:0;border-radius:14px;pointer-events:none;opacity:0;
|
||||
background:radial-gradient(circle at 50% 42%,rgba(167,139,250,.9),rgba(74,175,255,.3) 45%,transparent 70%);}
|
||||
.td-card.revealing .td-glow{animation:td-glow 1s ease-out .5s forwards;}
|
||||
@keyframes td-glow{0%{opacity:0;transform:scale(.4);}35%{opacity:1;}100%{opacity:0;transform:scale(1.3);}}
|
||||
.td-front .td-shine{position:absolute;top:0;left:-60%;width:60%;height:100%;pointer-events:none;opacity:0;
|
||||
background:linear-gradient(105deg,transparent,rgba(255,255,255,.6),transparent);transform:skewX(-18deg);}
|
||||
.td-card.revealing .td-shine{animation:td-shine 1s ease-in-out .55s forwards;}
|
||||
@keyframes td-shine{0%{left:-60%;opacity:0;}45%{opacity:.9;}100%{left:120%;opacity:0;}}
|
||||
.td-card.revealing .td-front{animation:td-pop 1s cubic-bezier(.18,1.5,.4,1) .5s both;}
|
||||
@keyframes td-pop{0%{transform:scale(.82) rotateY(180deg);filter:brightness(1.7);}60%{transform:scale(1.06) rotateY(180deg);}100%{transform:scale(1) rotateY(180deg);filter:brightness(1);}}
|
||||
.td-spark{position:absolute;left:50%;top:46%;width:6px;height:6px;border-radius:50%;pointer-events:none;z-index:5;opacity:0;
|
||||
background:#fff;box-shadow:0 0 8px 2px rgba(167,139,250,.9);}
|
||||
.td-card.revealing .td-spark{animation:td-spark .9s ease-out .5s forwards;}
|
||||
@keyframes td-spark{0%{opacity:1;transform:translate(-50%,-50%) scale(1);}100%{opacity:0;transform:translate(calc(-50% + var(--dx)),calc(-50% + var(--dy))) scale(.2);}}
|
||||
.td-actions{display:flex;gap:12px;align-items:center;flex-wrap:wrap;justify-content:center;}
|
||||
.td-btn{padding:10px 22px;border:none;border-radius:10px;background:linear-gradient(135deg,#8b5cf6,#4aafff);color:#fff;font-size:15px;font-weight:700;cursor:pointer;transition:.18s;box-shadow:0 6px 18px rgba(74,175,255,.35);}
|
||||
.td-btn:hover{filter:brightness(1.08);}
|
||||
@@ -7062,11 +7082,14 @@ if (typeof fitWorld === 'function') fitWorld();
|
||||
tdDeck.forEach((t, i) => {
|
||||
const card = document.createElement('div');
|
||||
card.className = 'td-card dealt';
|
||||
card.style.animationDelay = (i * 0.08) + 's';
|
||||
card.style.animationDelay = (i * 0.12) + 's';
|
||||
card.style.setProperty('--rz', (Math.random()*18 - 9).toFixed(1) + 'deg');
|
||||
setTimeout(() => playTalentSfx('deal'), i * 120);
|
||||
card.innerHTML =
|
||||
'<div class="td-inner">' +
|
||||
'<div class="td-face td-back">🌟</div>' +
|
||||
'<div class="td-face td-front">' +
|
||||
'<div class="td-glow"></div><div class="td-shine"></div>' +
|
||||
'<div class="td-got" hidden>已获得</div>' +
|
||||
'<div class="td-ico">' + (t.icon || '🌟') + '</div>' +
|
||||
'<div class="td-q ' + tdQualityClass(t.quality) + '">' + (QLABEL[t.quality] || '普通') + '</div>' +
|
||||
@@ -7083,6 +7106,66 @@ if (typeof fitWorld === 'function') fitWorld();
|
||||
document.getElementById('tdFinish').hidden = false;
|
||||
tdUpdateCount();
|
||||
}
|
||||
// 抽卡音效:Web Audio 合成,不依赖外部文件;尊重「音效」开关
|
||||
let _sfxCtx = null;
|
||||
function _getSfxCtx(){
|
||||
try{
|
||||
if(!_sfxCtx){ const AC = window.AudioContext || window.webkitAudioContext; _sfxCtx = new AC(); }
|
||||
if(_sfxCtx.state === 'suspended') _sfxCtx.resume();
|
||||
return _sfxCtx;
|
||||
}catch(e){ return null; }
|
||||
}
|
||||
function playTalentSfx(type){
|
||||
const tg = document.getElementById('sfxToggle');
|
||||
if(tg && !tg.classList.contains('active')) return; // 音效关闭则不发声
|
||||
const ctx = _getSfxCtx(); if(!ctx) return;
|
||||
const now = ctx.currentTime;
|
||||
if(type === 'whoosh'){
|
||||
const o = ctx.createOscillator(), g = ctx.createGain();
|
||||
o.type = 'sine';
|
||||
o.frequency.setValueAtTime(640, now);
|
||||
o.frequency.exponentialRampToValueAtTime(120, now + 0.4);
|
||||
g.gain.setValueAtTime(0.0001, now);
|
||||
g.gain.exponentialRampToValueAtTime(0.22, now + 0.05);
|
||||
g.gain.exponentialRampToValueAtTime(0.0001, now + 0.45);
|
||||
o.connect(g).connect(ctx.destination); o.start(now); o.stop(now + 0.47);
|
||||
} else if(type === 'chime'){
|
||||
[523.25, 659.25, 783.99].forEach((f, i) => {
|
||||
const t = now + i * 0.09;
|
||||
const o = ctx.createOscillator(), g = ctx.createGain();
|
||||
o.type = 'triangle'; o.frequency.setValueAtTime(f, t);
|
||||
g.gain.setValueAtTime(0.0001, t);
|
||||
g.gain.exponentialRampToValueAtTime(0.26, t + 0.02);
|
||||
g.gain.exponentialRampToValueAtTime(0.0001, t + 0.5);
|
||||
o.connect(g).connect(ctx.destination); o.start(t); o.stop(t + 0.55);
|
||||
});
|
||||
} else if(type === 'deal'){
|
||||
// 蜘蛛纸牌式发牌/落牌声:短促纸张“啪”声
|
||||
const len = Math.floor(ctx.sampleRate * 0.10);
|
||||
const buf = ctx.createBuffer(1, len, ctx.sampleRate);
|
||||
const data = buf.getChannelData(0);
|
||||
for(let i=0;i<len;i++) data[i] = (Math.random()*2-1) * Math.pow(1 - i/len, 2.2);
|
||||
const n = ctx.createBufferSource(); n.buffer = buf;
|
||||
const hp = ctx.createBiquadFilter(); hp.type = 'highpass'; hp.frequency.value = 900;
|
||||
const g = ctx.createGain();
|
||||
g.gain.setValueAtTime(0.16, now);
|
||||
g.gain.exponentialRampToValueAtTime(0.0001, now + 0.10);
|
||||
n.connect(hp).connect(g).connect(ctx.destination); n.start(now); n.stop(now + 0.12);
|
||||
}
|
||||
}
|
||||
function tdSpawnSparks(card){
|
||||
for(let i = 0; i < 9; i++){
|
||||
const s = document.createElement('div');
|
||||
s.className = 'td-spark';
|
||||
const ang = (Math.PI * 2) * (i / 9) + Math.random() * 0.4;
|
||||
const dist = 46 + Math.random() * 32;
|
||||
s.style.setProperty('--dx', (Math.cos(ang) * dist).toFixed(1) + 'px');
|
||||
s.style.setProperty('--dy', (Math.sin(ang) * dist).toFixed(1) + 'px');
|
||||
s.style.background = (i % 2) ? '#c4b5fd' : '#ffffff';
|
||||
card.appendChild(s);
|
||||
}
|
||||
setTimeout(() => card.querySelectorAll('.td-spark').forEach(s => s.remove()), 1500);
|
||||
}
|
||||
async function tdFlip(card, t){
|
||||
if(card.classList.contains('flipped') || card.classList.contains('done')) return;
|
||||
if(tdFlipped >= TD_MAX){
|
||||
@@ -7090,12 +7173,16 @@ if (typeof fitWorld === 'function') fitWorld();
|
||||
return;
|
||||
}
|
||||
card.classList.add('flipped');
|
||||
playTalentSfx('whoosh'); // 翻牌起手音
|
||||
tdFlipped++;
|
||||
tdNames.push(t.name);
|
||||
tdUpdateCount();
|
||||
const res = await API.talentDrawActivate(t.code);
|
||||
const front = card.querySelector('.td-front');
|
||||
if(res && res.ok){
|
||||
card.classList.add('revealing'); // 触发光晕/扫光/星星特效
|
||||
tdSpawnSparks(card);
|
||||
setTimeout(() => playTalentSfx('chime'), 480); // 翻到正面时响起获得音
|
||||
card.classList.add('done');
|
||||
const got = card.querySelector('.td-got');
|
||||
if(got) got.hidden = false;
|
||||
|
||||
Reference in New Issue
Block a user