Browse Source

Add combat animations and fix music overlap

- Add monster attack animation (rubber band snap towards player)
- Fix animation direction (now snaps left towards player)
- Add orange highlight on attacking monster
- Fix animation persistence through DOM rebuilds
- Prevent login music playing over game music on refresh
- Check for existing auth before starting login music

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
master
HikeMap User 1 month ago
parent
commit
455cb8af69
  1. 65
      index.html
  2. 2
      service-worker.js

65
index.html

@ -2574,6 +2574,32 @@
pointer-events: none; pointer-events: none;
text-decoration: line-through; text-decoration: line-through;
} }
.monster-entry.attacking {
border-color: #ff6b35;
box-shadow: 0 0 15px rgba(255, 107, 53, 0.6);
background: rgba(255, 107, 53, 0.15);
}
/* Rubber band attack animation for monster icon */
@keyframes monsterAttack {
0% {
transform: translateX(0);
}
20% {
transform: translateX(20px) scale(0.9);
}
50% {
transform: translateX(-30px) scale(1.15);
}
70% {
transform: translateX(-5px) scale(1.05);
}
100% {
transform: translateX(0) scale(1);
}
}
.monster-entry.attacking .monster-entry-icon {
animation: monsterAttack 0.5s ease-out;
}
.monster-entry-header { .monster-entry-header {
display: flex; display: flex;
align-items: center; align-items: center;
@ -10842,7 +10868,8 @@
let loginMusicStarted = false; let loginMusicStarted = false;
function startLoginMusic() { function startLoginMusic() {
if (!loginMusicStarted && !gameMusic.muted) {
const alreadyLoggedIn = localStorage.getItem('accessToken') || sessionStorage.getItem('guestMode');
if (!loginMusicStarted && !gameMusic.muted && !alreadyLoggedIn) {
loginMusicStarted = true; loginMusicStarted = true;
playMusic('login'); playMusic('login');
} }
@ -11016,8 +11043,10 @@
// Try to autostart login music immediately (works if user has interacted before) // Try to autostart login music immediately (works if user has interacted before)
// Falls back to first interaction if autoplay is blocked // Falls back to first interaction if autoplay is blocked
// Skip if user is already logged in (will go straight to game music)
setTimeout(() => { setTimeout(() => {
if (!loginMusicStarted && !gameMusic.muted) {
const alreadyLoggedIn = localStorage.getItem('accessToken') || sessionStorage.getItem('guestMode');
if (!loginMusicStarted && !gameMusic.muted && !alreadyLoggedIn) {
const loginAudio = gameMusic.login; const loginAudio = gameMusic.login;
loginAudio.play().then(() => { loginAudio.play().then(() => {
loginMusicStarted = true; loginMusicStarted = true;
@ -13526,6 +13555,31 @@
} }
} }
// Scroll to the attacking monster and trigger rubber band animation
function animateMonsterAttack(monsterIndex) {
try {
const container = document.getElementById('monsterList');
if (!container) return;
const entries = container.querySelectorAll('.monster-entry');
const entry = entries[monsterIndex];
if (!entry) return;
// Scroll the monster into view smoothly
entry.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
// Force restart the rubber band animation on the icon
const icon = entry.querySelector('.monster-entry-icon');
if (icon) {
icon.style.animation = 'none';
icon.offsetHeight; // Trigger reflow
icon.style.animation = 'monsterAttack 0.5s ease-out';
}
} catch (e) {
console.error('Animation error:', e);
}
}
// Render the monster list in combat UI // Render the monster list in combat UI
function renderMonsterList() { function renderMonsterList() {
const container = document.getElementById('monsterList'); const container = document.getElementById('monsterList');
@ -13546,6 +13600,10 @@
if (combatState.targetingMode && monster.hp > 0) { if (combatState.targetingMode && monster.hp > 0) {
entry.classList.add('targeting-selectable'); entry.classList.add('targeting-selectable');
} }
// Highlight the currently attacking monster
if (combatState.turn === 'monster' && index === combatState.currentMonsterTurn && monster.hp > 0) {
entry.classList.add('attacking');
}
const hpPct = Math.max(0, (monster.hp / monster.maxHp) * 100); const hpPct = Math.max(0, (monster.hp / monster.maxHp) * 100);
const mpPct = Math.max(0, (monster.mp / monster.maxMp) * 100); const mpPct = Math.max(0, (monster.mp / monster.maxMp) * 100);
@ -14201,6 +14259,9 @@
combatState.currentMonsterTurn = monsterIndex; combatState.currentMonsterTurn = monsterIndex;
updateCombatUI(); updateCombatUI();
// Scroll to and animate the attacking monster (after DOM update)
setTimeout(() => animateMonsterAttack(monsterIndex), 50);
// Decrement monster buff durations at start of its turn // Decrement monster buff durations at start of its turn
if (monster.buffs) { if (monster.buffs) {
if (monster.buffs.defense && monster.buffs.defense.turnsLeft > 0) { if (monster.buffs.defense && monster.buffs.defense.turnsLeft > 0) {

2
service-worker.js

@ -1,6 +1,6 @@
// HikeMap Service Worker // HikeMap Service Worker
// Increment version to force cache refresh // Increment version to force cache refresh
const CACHE_NAME = 'hikemap-v1.0.8';
const CACHE_NAME = 'hikemap-v1.1.0';
const urlsToCache = [ const urlsToCache = [
'/', '/',
'/index.html', '/index.html',

Loading…
Cancel
Save