Why ga #9
+5
-7
@@ -17,16 +17,14 @@ 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) ? '' : `http://${location.hostname}:8069`)
|
||||
: '',
|
||||
db: _isLocalDev() ? 'game_a' : 'game',
|
||||
? ((_isDevPort(8080) || _isDevPort(8888)) ? '' : `http://${location.hostname}:8080`)
|
||||
: '',
|
||||
db: _isLocalDev() ? 'study' : 'game',
|
||||
};
|
||||
|
||||
// 世界(yt_world)所在库:与 API_CONFIG.db 保持一致(本地均为 game_a),避免跨库导致 auth 失败。
|
||||
const WORLD_DB = API_CONFIG.db;
|
||||
// 世界(yt_world)所在库:本地 Odoo 钉 yt_game;线上游戏库名为 game。按域名自适应,git 同步不冲突。
|
||||
const WORLD_DB = _isLocalDev() ? 'yt_game' : 'game';
|
||||
|
||||
// Odoo 后端基础 URL(世界 API 专用,兼容本地跨域 / 生产同域)
|
||||
const WORLD_BASE = API_CONFIG.base || '';
|
||||
|
||||
+67
-36
@@ -812,15 +812,10 @@ 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-front .td-ring{position:absolute;left:50%;top:50%;width:64%;height:64%;margin:-32% 0 0 -32%;border-radius:50%;pointer-events:none;opacity:0;border:2px solid rgba(167,139,250,.85);box-shadow:0 0 14px rgba(167,139,250,.45);}
|
||||
.td-card.revealing .td-ring{animation:td-ring .9s ease-out .5s forwards;}
|
||||
@keyframes td-ring{0%{opacity:.9;transform:scale(.25);}70%{opacity:.5;}100%{opacity:0;transform:scale(2.5);}}
|
||||
.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;
|
||||
@@ -833,14 +828,16 @@ body { background: #0a0e17; overflow: hidden; font-family: 'Microsoft YaHei', sa
|
||||
.td-btn.ghost{background:rgba(255,255,255,.06);border:1px solid rgba(255,255,255,.16);box-shadow:none;color:#cdd8e6;}
|
||||
.td-count{color:#9fb3cc;font-size:13px;}
|
||||
.td-q-common{color:#9ca3af;} .td-q-uncommon{color:#5fd17a;} .td-q-rare{color:#4aafff;} .td-q-epic{color:#c084fc;} .td-q-legendary{color:#f59e0b;} .td-q-mythic{color:#fb7185;}
|
||||
/* 抽卡完成动画:未翻牌碎裂 / 已翻牌飞向天赋按钮 */
|
||||
.td-card.shatter{pointer-events:none;}
|
||||
.td-card.shatter .td-inner{opacity:0;transform:scale(.92);transition:opacity .22s,transform .22s;}
|
||||
.shard{position:absolute;width:16px;height:16px;background:linear-gradient(135deg,#a78bfa,#4c1d95);border:1px solid rgba(196,181,253,.35);border-radius:3px;pointer-events:none;opacity:0;left:50%;top:50%;margin:-8px 0 0 -8px;box-shadow:0 0 8px rgba(139,92,246,.5);}
|
||||
.td-card.shatter .shard{animation:shard-burst .7s cubic-bezier(.2,.7,.2,1) forwards;}
|
||||
@keyframes shard-burst{0%{opacity:1;transform:translate3d(0,0,0) rotate(0deg) scale(1);}100%{opacity:0;transform:translate3d(var(--tx),var(--ty),0) rotate(var(--rot)) scale(.15);}}
|
||||
.td-fly-clone{position:fixed;z-index:9999;pointer-events:none;border-radius:14px;overflow:hidden;box-shadow:0 0 28px rgba(139,92,246,.75);transition:transform .75s cubic-bezier(.2,.8,.2,1),opacity .75s;}
|
||||
/* 抽卡完成动画:未翻牌光尘消散 / 已翻牌飞向天赋按钮 */
|
||||
.td-card.dissolve{pointer-events:none;}
|
||||
.td-card.dissolve .td-inner{opacity:0;transform:scale(.82) translateY(-14px);filter:blur(3px);transition:opacity .5s ease,transform .5s ease,filter .5s ease;}
|
||||
.dust{position:absolute;width:7px;height:7px;border-radius:50%;pointer-events:none;opacity:0;left:50%;top:50%;margin:-3.5px 0 0 -3.5px;background:radial-gradient(circle,#c4b5fd,#7c3aed);box-shadow:0 0 6px rgba(167,139,250,.7);}
|
||||
.td-card.dissolve .dust{animation:tdust .8s ease-out forwards;}
|
||||
@keyframes tdust{0%{opacity:1;transform:translate(0,0) scale(1);}100%{opacity:0;transform:translate(var(--dx),var(--dy)) scale(.2);}}
|
||||
.td-fly-clone{position:fixed;z-index:9999;pointer-events:none;border-radius:14px;overflow:hidden;box-shadow:0 0 28px rgba(139,92,246,.75);transition:transform 1.05s cubic-bezier(.2,.8,.2,1),opacity 1.05s;}
|
||||
.td-fly-clone .td-inner{transition:none !important;}
|
||||
#talentBtn.talent-pulse{animation:tbtn-pulse .55s ease-out;}
|
||||
@keyframes tbtn-pulse{0%{box-shadow:0 0 0 0 rgba(139,92,246,.7);transform:scale(1);}50%{transform:scale(1.18);}100%{box-shadow:0 0 0 18px rgba(139,92,246,0);transform:scale(1);}}
|
||||
</style>
|
||||
<link rel="stylesheet" href="assets/story-player.css" />
|
||||
</head>
|
||||
@@ -7089,7 +7086,7 @@ if (typeof fitWorld === 'function') fitWorld();
|
||||
'<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-ring"></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>' +
|
||||
@@ -7151,6 +7148,24 @@ if (typeof fitWorld === 'function') fitWorld();
|
||||
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);
|
||||
} else if(type === 'vanish'){
|
||||
// 未选中牌消散:低沉下滑“嗖”声
|
||||
const o = ctx.createOscillator(), g = ctx.createGain();
|
||||
o.type = 'sine'; o.frequency.setValueAtTime(420, now);
|
||||
o.frequency.exponentialRampToValueAtTime(90, now + 0.35);
|
||||
g.gain.setValueAtTime(0.0001, now);
|
||||
g.gain.exponentialRampToValueAtTime(0.18, now + 0.04);
|
||||
g.gain.exponentialRampToValueAtTime(0.0001, now + 0.38);
|
||||
o.connect(g).connect(ctx.destination); o.start(now); o.stop(now + 0.4);
|
||||
} else if(type === 'collect'){
|
||||
// 已翻牌收进天赋:上行短“叮”声
|
||||
const o = ctx.createOscillator(), g = ctx.createGain();
|
||||
o.type = 'triangle'; o.frequency.setValueAtTime(880, now);
|
||||
o.frequency.exponentialRampToValueAtTime(1318.5, now + 0.12);
|
||||
g.gain.setValueAtTime(0.0001, now);
|
||||
g.gain.exponentialRampToValueAtTime(0.2, now + 0.02);
|
||||
g.gain.exponentialRampToValueAtTime(0.0001, now + 0.3);
|
||||
o.connect(g).connect(ctx.destination); o.start(now); o.stop(now + 0.32);
|
||||
}
|
||||
}
|
||||
function tdSpawnSparks(card){
|
||||
@@ -7173,14 +7188,14 @@ if (typeof fitWorld === 'function') fitWorld();
|
||||
return;
|
||||
}
|
||||
card.classList.add('flipped');
|
||||
playTalentSfx('whoosh'); // 翻牌起手音
|
||||
playTalentSfx('deal'); // 翻牌起手音(蜘蛛纸牌纸张啪声)
|
||||
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'); // 触发光晕/扫光/星星特效
|
||||
card.classList.add('revealing'); // 触发命运涟漪环/星星特效
|
||||
tdSpawnSparks(card);
|
||||
setTimeout(() => playTalentSfx('chime'), 480); // 翻到正面时响起获得音
|
||||
card.classList.add('done');
|
||||
@@ -7210,7 +7225,12 @@ if (typeof fitWorld === 'function') fitWorld();
|
||||
const flipped = cards.filter(c => c.classList.contains('flipped'));
|
||||
const unflipped = cards.filter(c => !c.classList.contains('flipped'));
|
||||
const target = document.getElementById('talentBtn');
|
||||
const targetRect = target ? target.getBoundingClientRect() : null;
|
||||
let targetRect = target ? target.getBoundingClientRect() : null;
|
||||
if(!targetRect || targetRect.width === 0){ // 按钮隐藏时回退到顶栏右侧
|
||||
const tb = document.getElementById('topBar');
|
||||
const r = tb ? tb.getBoundingClientRect() : { right: window.innerWidth, top: 8 };
|
||||
targetRect = { left: r.right - 56, top: (tb ? r.top : 8) + 6, width: 50, height: 34 };
|
||||
}
|
||||
|
||||
let completed = 0;
|
||||
const total = flipped.length + (unflipped.length ? 1 : 0);
|
||||
@@ -7233,45 +7253,56 @@ if (typeof fitWorld === 'function') fitWorld();
|
||||
const rect = card.getBoundingClientRect();
|
||||
const clone = card.cloneNode(true);
|
||||
clone.classList.add('td-fly-clone');
|
||||
clone.classList.remove('dealt','revealing'); // 去掉发牌/揭晓动画类,否则其 animation 会覆盖飞行 transform
|
||||
clone.style.left = rect.left + 'px';
|
||||
clone.style.top = rect.top + 'px';
|
||||
clone.style.width = rect.width + 'px';
|
||||
clone.style.height = rect.height + 'px';
|
||||
clone.style.opacity = '1';
|
||||
clone.style.transform = 'translate3d(0,0,0) rotate(0deg)';
|
||||
document.body.appendChild(clone);
|
||||
void clone.offsetWidth; // 强制 reflow,保证起始 transform 已渲染
|
||||
card.style.opacity = '0';
|
||||
setTimeout(()=>{
|
||||
playTalentSfx('deal');
|
||||
if(targetRect){
|
||||
const tx = targetRect.left + targetRect.width/2 - (rect.left + rect.width/2);
|
||||
const ty = targetRect.top + targetRect.height/2 - (rect.top + rect.height/2);
|
||||
const scale = Math.max(0.18, Math.min(0.32, targetRect.width / rect.width));
|
||||
clone.style.transform = 'translate3d(' + tx + 'px,' + ty + 'px,0) scale(' + scale + ')';
|
||||
const scale = Math.max(0.16, Math.min(0.28, targetRect.width / rect.width));
|
||||
const rot = -360 - (i % 2 === 0 ? 180 : 0); // 每张牌朝按钮方向旋转飞入
|
||||
clone.style.transform = 'translate3d(' + tx + 'px,' + ty + 'px,0) scale(' + scale + ') rotate(' + rot + 'deg)';
|
||||
// 到达时给天赋按钮一个脉冲反馈
|
||||
setTimeout(()=>{
|
||||
const btn = document.getElementById('talentBtn');
|
||||
if(btn){ btn.classList.remove('talent-pulse'); void btn.offsetWidth; btn.classList.add('talent-pulse'); }
|
||||
}, 980);
|
||||
}
|
||||
clone.style.opacity = '0';
|
||||
}, 60 + i*90);
|
||||
setTimeout(()=>{ clone.remove(); checkDone(); }, 820);
|
||||
}, 80 + i*120);
|
||||
setTimeout(()=>{ clone.remove(); checkDone(); }, 1150);
|
||||
});
|
||||
|
||||
// 未翻开的牌:延迟碎裂
|
||||
// 未翻开的牌:延迟光尘消散 + 消散音效
|
||||
if(unflipped.length){
|
||||
setTimeout(()=>{
|
||||
unflipped.forEach(card => { createShatter(card); card.style.opacity = '0'; });
|
||||
setTimeout(checkDone, 720);
|
||||
playTalentSfx('vanish');
|
||||
unflipped.forEach(card => { createDissolve(card); card.style.opacity = '0'; });
|
||||
setTimeout(checkDone, 820);
|
||||
}, flipped.length ? 520 : 0);
|
||||
}
|
||||
}
|
||||
function createShatter(card){
|
||||
const N = 10;
|
||||
function createDissolve(card){
|
||||
const N = 14;
|
||||
for(let i=0;i<N;i++){
|
||||
const s = document.createElement('div'); s.className = 'shard';
|
||||
const angle = (Math.PI*2*i/N) + (Math.random()*0.7-0.35);
|
||||
const dist = 55 + Math.random()*90;
|
||||
s.style.setProperty('--tx', (Math.cos(angle)*dist).toFixed(1) + 'px');
|
||||
s.style.setProperty('--ty', (Math.sin(angle)*dist).toFixed(1) + 'px');
|
||||
s.style.setProperty('--rot', (Math.random()*360-180).toFixed(0) + 'deg');
|
||||
const s = document.createElement('div'); s.className = 'dust';
|
||||
const ang = Math.random() * Math.PI * 2;
|
||||
const dist = 30 + Math.random() * 70;
|
||||
const dy = -(20 + Math.random() * 60); // 偏向上飘散
|
||||
s.style.setProperty('--dx', (Math.cos(ang) * dist).toFixed(1) + 'px');
|
||||
s.style.setProperty('--dy', (Math.sin(ang) * dist + dy).toFixed(1) + 'px');
|
||||
card.appendChild(s);
|
||||
}
|
||||
card.classList.add('shatter');
|
||||
card.classList.add('dissolve');
|
||||
}
|
||||
(function bindTalentDraw(){
|
||||
const ov = document.getElementById('talentDraw');
|
||||
|
||||
Reference in New Issue
Block a user