diff --git a/assets/game-api.js b/assets/game-api.js index 58e8669..267fdec 100644 --- a/assets/game-api.js +++ b/assets/game-api.js @@ -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 || ''; diff --git a/world.html b/world.html index 9fd00e5..4f03027 100644 --- a/world.html +++ b/world.html @@ -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 = '
' + '
🌟
' + '
' + + '
' + '' + '
' + (t.icon || '🌟') + '
' + '
' + (QLABEL[t.quality] || '普通') + '
' + @@ -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 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;