diff --git a/public/src/game.js b/public/src/game.js index d5617be..38fa387 100644 --- a/public/src/game.js +++ b/public/src/game.js @@ -845,18 +845,43 @@ const mapAnimTextByType = { }; /** - * Triggers a spinning emoji animation at the centre of the map zone. + * Spawns a floating emoji that rises one cell height above the clicked cell + * and fades in/out over 1 second. * @param {"reveal-empty"|"reveal-planet"|"capture"} type + * @param {number} x cell grid column + * @param {number} y cell grid row */ -function triggerMapAnimation(type) { - if (!mapAnimEl) return; - mapAnimEl.textContent = mapAnimTextByType[type] || "πŸ”"; - mapAnimEl.classList.remove("mapAnim--active"); - void mapAnimEl.offsetWidth; // force reflow to restart the animation - mapAnimEl.classList.add("mapAnim--active"); - mapAnimEl.addEventListener("animationend", () => { - mapAnimEl.classList.remove("mapAnim--active"); - }, { once: true }); +function triggerMapAnimation(type, x, y) { + const cw = SVG_W / GRID_W; + const ch = SVG_H / GRID_H; + const rect = canvas.getBoundingClientRect(); + + // Top-centre of the cell in screen pixels, relative to the canvas element + const screenX = ((x * cw + cw / 2 - panX) * zoom / SVG_W) * rect.width; + const screenY = ((y * ch - panY) * zoom / SVG_H) * rect.height; + + // One cell height in screen pixels (travel distance) + const cellScreenH = (ch * zoom / SVG_H) * rect.height; + const fontSize = Math.max(8, (cw * zoom / SVG_W) * rect.width); + + const el = document.createElement("div"); + el.className = "mapAnimFloat"; + el.textContent = mapAnimTextByType[type] || "πŸ”"; + el.style.left = `${screenX}px`; + el.style.top = `${screenY}px`; + el.style.fontSize = `${fontSize}px`; + el.style.transform = "translate(-50%, 0)"; + canvas.parentElement.appendChild(el); + + el.animate( + [ + { opacity: 0, transform: "translate(-50%, 0)" }, + { opacity: 1, transform: `translate(-50%, -${cellScreenH * 0.2}px)`, offset: 0.2 }, + { opacity: 1, transform: `translate(-50%, -${cellScreenH * 0.8}px)`, offset: 0.8 }, + { opacity: 0, transform: `translate(-50%, -${cellScreenH}px)` }, + ], + { duration: 1000, easing: "ease-out", fill: "forwards" } + ).onfinish = () => el.remove(); } // ── Cursor ──────────────────────────────────────────────────────────────────── @@ -1062,7 +1087,7 @@ async function onCanvasClick(ev) { } if (!res.ok) throw new Error("reveal"); applyRevealPayload(await res.json()); - triggerMapAnimation(cells.get(key)?.hasPlanet ? "reveal-planet" : "reveal-empty"); + triggerMapAnimation(cells.get(key)?.hasPlanet ? "reveal-planet" : "reveal-empty", cell.x, cell.y); startCooldown(); updateEconomyDisplay(); draw(); @@ -1124,7 +1149,7 @@ async function onCanvasClick(ev) { teamActionsRemaining = data.teamActionsRemaining; updateTeamQuotaDisplay(); } - triggerMapAnimation("capture"); + triggerMapAnimation("capture", cell.x, cell.y); hint.textContent = `🏴 PlanΓ¨te (${cell.x},${cell.y}) capturΓ©e !`; showLocalSelection(cell.x, cell.y); updateEconomyDisplay(); diff --git a/public/style.css b/public/style.css index ab366fb..8e58239 100644 --- a/public/style.css +++ b/public/style.css @@ -1299,27 +1299,11 @@ canvas { /* ── Map action animation ─────────────────────────────────────────────────── */ -@keyframes mapAnimPop { - 0% { transform: translate(-50%, -50%) scale(0.1); opacity: 0; } - 20% { transform: translate(-50%, -50%) scale(1.1); opacity: 1; } - 80% { transform: translate(-50%, -50%) scale(1); opacity: 1; } - 100% { transform: translate(-50%, -50%) scale(1.2); opacity: 0; } -} - -.mapAnim { +.mapAnimFloat { position: absolute; - left: 50%; - top: 50%; - transform: translate(-50%, -50%); - font-size: 88px; line-height: 1; pointer-events: none; z-index: 20; - display: none; user-select: none; -} - -.mapAnim--active { - display: block; - animation: mapAnimPop 0.5s ease-out forwards; + white-space: nowrap; } \ No newline at end of file