Commit 5893f13a authored by Mahmoud Aglan's avatar Mahmoud Aglan

fix: emotes stay next to sender, animate slower (3s), no center fly

Emotes now pop up next to the player panel that sent them and gently
float up in place before fading. Duration increased from 1.8s to 3s.
Multiplayer sync unchanged — still uses sendEmote/onEmoteReceived.
Bot emotes in ludo also use the new positioned animation.
Co-Authored-By: 's avatarClaude Opus 4.6 <noreply@anthropic.com>
parent 23f9266a
...@@ -44,8 +44,8 @@ export function create(container, onSend) { ...@@ -44,8 +44,8 @@ export function create(container, onSend) {
.emote-btn:hover { background:rgba(255,255,255,0.1); } .emote-btn:hover { background:rgba(255,255,255,0.1); }
.emote-btn:active { transform:scale(0.85); } .emote-btn:active { transform:scale(0.85); }
.emote-btn.cooldown { opacity:0.3;pointer-events:none; } .emote-btn.cooldown { opacity:0.3;pointer-events:none; }
.emote-received { position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);font-size:48px;animation:emoteFloat 2s ease-out forwards;pointer-events:none;z-index:40; } .emote-received { position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);font-size:48px;animation:emoteFloat 3s ease-out forwards;pointer-events:none;z-index:40; }
@keyframes emoteFloat { 0%{opacity:1;transform:translate(-50%,-50%) scale(0.5);} 30%{transform:translate(-50%,-50%) scale(1.2);} 60%{opacity:1;transform:translate(-50%,-80%) scale(1);} 100%{opacity:0;transform:translate(-50%,-120%) scale(0.8);} } @keyframes emoteFloat { 0%{opacity:0;transform:translate(-50%,-50%) scale(0.3);} 15%{opacity:1;transform:translate(-50%,-50%) scale(1.1);} 25%{transform:translate(-50%,-50%) scale(1);} 75%{opacity:1;transform:translate(-50%,-60%) scale(1);} 100%{opacity:0;transform:translate(-50%,-80%) scale(0.9);} }
`; `;
container.appendChild(style); container.appendChild(style);
container.appendChild(emoteBar); container.appendChild(emoteBar);
...@@ -85,32 +85,29 @@ export function showReceived(container, emote, fromElement) { ...@@ -85,32 +85,29 @@ export function showReceived(container, emote, fromElement) {
const emojiText = emote.emoji || emote; const emojiText = emote.emoji || emote;
const el = document.createElement('div'); const el = document.createElement('div');
el.textContent = emojiText; el.textContent = emojiText;
el.style.cssText = 'position:fixed;font-size:40px;z-index:999;pointer-events:none;'; el.style.cssText = 'position:fixed;font-size:36px;z-index:999;pointer-events:none;';
document.body.appendChild(el); document.body.appendChild(el);
// Calculate start position (from sender's panel or default top) let x, y;
const containerRect = container.getBoundingClientRect();
const centerX = containerRect.left + containerRect.width / 2;
const centerY = containerRect.top + containerRect.height / 2;
let startX = centerX;
let startY = containerRect.top + 20;
if (fromElement) { if (fromElement) {
const fromRect = fromElement.getBoundingClientRect(); const fromRect = fromElement.getBoundingClientRect();
startX = fromRect.left + fromRect.width / 2; x = fromRect.left + fromRect.width / 2;
startY = fromRect.top + fromRect.height / 2; y = fromRect.top + fromRect.height / 2;
} else {
const containerRect = container.getBoundingClientRect();
x = containerRect.left + containerRect.width / 2;
y = containerRect.top + 40;
} }
// Animate from sender position → center of board with scale + fade
el.animate([ el.animate([
{ left: startX + 'px', top: startY + 'px', transform: 'translate(-50%,-50%) scale(0.3)', opacity: 0 }, { left: x + 'px', top: y + 'px', transform: 'translate(-50%,-50%) scale(0)', opacity: 0 },
{ left: startX + 'px', top: startY + 'px', transform: 'translate(-50%,-50%) scale(1.3)', opacity: 1, offset: 0.2 }, { left: x + 'px', top: y + 'px', transform: 'translate(-50%,-50%) scale(1.15)', opacity: 1, offset: 0.12 },
{ left: centerX + 'px', top: centerY + 'px', transform: 'translate(-50%,-50%) scale(1.5)', opacity: 1, offset: 0.5 }, { left: x + 'px', top: y + 'px', transform: 'translate(-50%,-50%) scale(1)', opacity: 1, offset: 0.2 },
{ left: centerX + 'px', top: centerY + 'px', transform: 'translate(-50%,-50%) scale(2)', opacity: 0 } { left: x + 'px', top: (y - 20) + 'px', transform: 'translate(-50%,-50%) scale(1)', opacity: 1, offset: 0.75 },
{ left: x + 'px', top: (y - 30) + 'px', transform: 'translate(-50%,-50%) scale(0.85)', opacity: 0 }
], { ], {
duration: 1800, duration: 3000,
easing: 'cubic-bezier(0.16, 1, 0.3, 1)', easing: 'ease-out',
fill: 'forwards' fill: 'forwards'
}).onfinish = () => el.remove(); }).onfinish = () => el.remove();
} }
......
...@@ -231,21 +231,18 @@ export function mountGame(el, params) { ...@@ -231,21 +231,18 @@ export function mountGame(el, params) {
} }
mp.startDisconnectWatch(matchId, 'chess', 60000); mp.startDisconnectWatch(matchId, 'chess', 60000);
// Synced emotes — animate from OPPONENT bar to center
mp.onEmoteReceived((emote) => { mp.onEmoteReceived((emote) => {
const boardContainer = el.querySelector('#board-container'); const boardContainer = el.querySelector('#board-container');
const oppBar = el.querySelector('.chess-bar'); // opponent bar is first chess-bar const oppBar = el.querySelector('.chess-bar');
emoteSystem.showReceived(boardContainer, emote.key === 'gg' ? emoji('handshake', '🤝', 24) : emote.key === 'good_move' ? '👏' : '😮', oppBar); emoteSystem.showReceived(boardContainer, emote.key === 'gg' ? emoji('handshake', '🤝', 24) : emote.key === 'good_move' ? '👏' : '😮', oppBar);
audio.play('notification'); audio.play('notification');
}); });
} }
// Emote system
const emoteContainer = el.querySelector('#board-container'); const emoteContainer = el.querySelector('#board-container');
emoteSystem.create(emoteContainer, (emote) => { emoteSystem.create(emoteContainer, (emote) => {
audio.play('notification'); audio.play('notification');
// Animate from MY player bar to center const myBar = el.querySelectorAll('.chess-bar')[1];
const myBar = el.querySelectorAll('.chess-bar')[1]; // player bar is second
emoteSystem.showReceived(emoteContainer, emote.emoji, myBar); emoteSystem.showReceived(emoteContainer, emote.emoji, myBar);
// Sync to opponent in live mode // Sync to opponent in live mode
if (gameState.mode === 'live' && matchId) { if (gameState.mode === 'live' && matchId) {
......
...@@ -325,13 +325,9 @@ async function botLoop(el) { ...@@ -325,13 +325,9 @@ async function botLoop(el) {
const emoteWrap = el.querySelector('#ludo-wrap'); const emoteWrap = el.querySelector('#ludo-wrap');
setTimeout(() => { setTimeout(() => {
const botEmotes = ['😂', '💪', '🎉', '😎']; const botEmotes = ['😂', '💪', '🎉', '😎'];
const emoteSystem = document.querySelector('.emote-received'); const botIdx = game.currentPlayerIndex;
if (!emoteSystem) { const botPanel = el.querySelector(`#pp-${botIdx}`);
const em = document.createElement('div'); emoteSystem.showReceived(emoteWrap, botEmotes[Math.floor(Math.random() * botEmotes.length)], botPanel);
em.style.cssText = 'position:absolute;top:40%;left:50%;transform:translate(-50%,-50%);font-size:36px;animation:emoteFloat 2s ease-out forwards;pointer-events:none;z-index:40;';
em.textContent = botEmotes[Math.floor(Math.random() * botEmotes.length)];
if (emoteWrap) { emoteWrap.style.position = 'relative'; emoteWrap.appendChild(em); setTimeout(() => em.remove(), 2000); }
}
}, 300); }, 300);
} }
} }
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment