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) {
.emote-btn:hover { background:rgba(255,255,255,0.1); }
.emote-btn:active { transform:scale(0.85); }
.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; }
@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);} }
.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: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(emoteBar);
......@@ -85,32 +85,29 @@ export function showReceived(container, emote, fromElement) {
const emojiText = emote.emoji || emote;
const el = document.createElement('div');
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);
// Calculate start position (from sender's panel or default top)
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;
let x, y;
if (fromElement) {
const fromRect = fromElement.getBoundingClientRect();
startX = fromRect.left + fromRect.width / 2;
startY = fromRect.top + fromRect.height / 2;
x = fromRect.left + fromRect.width / 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([
{ left: startX + 'px', top: startY + 'px', transform: 'translate(-50%,-50%) scale(0.3)', opacity: 0 },
{ left: startX + 'px', top: startY + 'px', transform: 'translate(-50%,-50%) scale(1.3)', opacity: 1, offset: 0.2 },
{ left: centerX + 'px', top: centerY + 'px', transform: 'translate(-50%,-50%) scale(1.5)', opacity: 1, offset: 0.5 },
{ left: centerX + 'px', top: centerY + 'px', transform: 'translate(-50%,-50%) scale(2)', opacity: 0 }
{ left: x + 'px', top: y + 'px', transform: 'translate(-50%,-50%) scale(0)', opacity: 0 },
{ left: x + 'px', top: y + 'px', transform: 'translate(-50%,-50%) scale(1.15)', opacity: 1, offset: 0.12 },
{ left: x + 'px', top: y + 'px', transform: 'translate(-50%,-50%) scale(1)', opacity: 1, offset: 0.2 },
{ 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,
easing: 'cubic-bezier(0.16, 1, 0.3, 1)',
duration: 3000,
easing: 'ease-out',
fill: 'forwards'
}).onfinish = () => el.remove();
}
......
......@@ -231,21 +231,18 @@ export function mountGame(el, params) {
}
mp.startDisconnectWatch(matchId, 'chess', 60000);
// Synced emotes — animate from OPPONENT bar to center
mp.onEmoteReceived((emote) => {
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);
audio.play('notification');
});
}
// Emote system
const emoteContainer = el.querySelector('#board-container');
emoteSystem.create(emoteContainer, (emote) => {
audio.play('notification');
// Animate from MY player bar to center
const myBar = el.querySelectorAll('.chess-bar')[1]; // player bar is second
const myBar = el.querySelectorAll('.chess-bar')[1];
emoteSystem.showReceived(emoteContainer, emote.emoji, myBar);
// Sync to opponent in live mode
if (gameState.mode === 'live' && matchId) {
......
......@@ -325,13 +325,9 @@ async function botLoop(el) {
const emoteWrap = el.querySelector('#ludo-wrap');
setTimeout(() => {
const botEmotes = ['😂', '💪', '🎉', '😎'];
const emoteSystem = document.querySelector('.emote-received');
if (!emoteSystem) {
const em = document.createElement('div');
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); }
}
const botIdx = game.currentPlayerIndex;
const botPanel = el.querySelector(`#pp-${botIdx}`);
emoteSystem.showReceived(emoteWrap, botEmotes[Math.floor(Math.random() * botEmotes.length)], botPanel);
}, 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