diff --git a/html/js/core/game.js b/html/js/core/game.js
index 07d6e7d..a7ffaaa 100644
--- a/html/js/core/game.js
+++ b/html/js/core/game.js
@@ -450,19 +450,10 @@ var Game = {
window.FX.sparkle(pp.x, pp.y, '#fbbf24');
}
- // Monster proximity: throttled red edge pulse, stronger the closer it is.
- var m = GameState._monster;
- if (m && typeof m.x === 'number' && typeof m.y === 'number' && window.FX.flash) {
- var md = this._fxDist(player.x, player.y, m.x, m.y);
- if (md <= 3) {
- var now = Date.now();
- if (!this._fxLastMonsterFlash || (now - this._fxLastMonsterFlash) > 550) {
- this._fxLastMonsterFlash = now;
- var alpha = (md <= 1) ? 0.22 : (md <= 2 ? 0.14 : 0.09);
- window.FX.flash('rgba(255,60,60,1)', alpha, 260);
- }
- }
- }
+ // NOTE: removed the monster-proximity red screen flash — it fired on nearly every step
+ // (monster usually within 3 tiles) and read as a constant annoying pulse. The monster is
+ // already visible on-screen, so proximity needs no full-screen flash. Reserve FX.flash for
+ // genuine one-shot events (win/lose in handleGameOver).
},
// Attach + start the FX loop for a fresh game.
diff --git a/html/js/network/socketHandlers.js b/html/js/network/socketHandlers.js
index c7dc0be..9df9868 100644
--- a/html/js/network/socketHandlers.js
+++ b/html/js/network/socketHandlers.js
@@ -139,8 +139,11 @@ const SocketHandlers = {
var lvl = (data.depth && data.maxDepth) ? ('Level ' + data.depth + ' of ' + data.maxDepth + ' — ') : '';
$m.append($('
')
.text('⬇️ Stairs down! ' + lvl + 'keep going — escape before the block!'));
+ var lvlTitle = (data.depth && data.maxDepth) ? ('⬇ LEVEL ' + data.depth + ' / ' + data.maxDepth) : '⬇ DESCEND';
+ SocketHandlers.showEventBanner(lvlTitle, 'Deeper in — escape before the block!', '#ffcc00');
} else if (data.event === 'treasure_found') {
$m.append($('').text('💰 Treasure secured!'));
+ SocketHandlers.showEventBanner('💰 TREASURE SECURED', 'Now get to the stairs and escape!', '#fbbf24');
} else return;
if (typeof UI !== 'undefined' && UI.scrollChat) UI.scrollChat();
});
@@ -333,6 +336,42 @@ const SocketHandlers = {
UI.scrollChat();
},
+ // A big, one-shot on-screen banner over the game view for headline in-run events (descend /
+ // treasure). Pops in, holds, fades — pointer-events:none so it never eats input, and auto-removes.
+ showEventBanner: function(title, sub, color) {
+ color = color || '#ffcc00';
+ var host = document.getElementById('game-display') || document.body;
+ if (!document.getElementById('evt-banner-style')) {
+ var st = document.createElement('style');
+ st.id = 'evt-banner-style';
+ st.textContent =
+ '@keyframes evtBannerIn{0%{opacity:0;transform:translate(-50%,-50%) scale(.6)}' +
+ '12%{opacity:1;transform:translate(-50%,-50%) scale(1.12)}' +
+ '22%{transform:translate(-50%,-50%) scale(1)}' +
+ '76%{opacity:1;transform:translate(-50%,-50%) scale(1)}' +
+ '100%{opacity:0;transform:translate(-50%,-50%) scale(1.05)}}';
+ document.head.appendChild(st);
+ }
+ // Drop any banner still on screen so they never stack.
+ var prev = document.getElementById('evt-banner');
+ if (prev && prev.parentNode) prev.parentNode.removeChild(prev);
+
+ var b = document.createElement('div');
+ b.id = 'evt-banner';
+ var positioned = host.id === 'game-display'; // #game-display is position:relative
+ b.style.cssText = (positioned ? 'position:absolute;' : 'position:fixed;') +
+ 'left:50%;top:34%;z-index:30;pointer-events:none;text-align:center;' +
+ 'font-family:ui-monospace,monospace;animation:evtBannerIn 2600ms ease-out forwards;';
+ b.innerHTML =
+ '' + title + '
' +
+ (sub ? '' + sub + '
' : '');
+ host.appendChild(b);
+ setTimeout(function () { if (b.parentNode) b.parentNode.removeChild(b); }, 2650);
+ // A little extra pop: a soft colored flash if the FX overlay is up.
+ try { if (window.FX && window.FX.flash) window.FX.flash(color, 0.16, 320); } catch (e) {}
+ },
+
onStatusUpdate: function(data) {
const statusClass = data.type === 'error' ? 'error' : 'status';
const statusColor = data.type === 'error' ? '#f00' :