feat(level-select): add hover name tags to cleared nodes

Introduces a subtle fade-in/out name pill for completed level nodes on hover,
improving discoverability of level names without permanently cluttering the UI.
Also adds voice assets for levels 2–10.
This commit is contained in:
Brian Fertig 2026-08-23 12:21:58 -06:00
parent ed81a465ee
commit a12ae73b8a
10 changed files with 32 additions and 2 deletions

BIN
assets/voice/level02.mp3 Normal file

Binary file not shown.

BIN
assets/voice/level03.mp3 Normal file

Binary file not shown.

BIN
assets/voice/level04.mp3 Normal file

Binary file not shown.

BIN
assets/voice/level05.mp3 Normal file

Binary file not shown.

BIN
assets/voice/level06.mp3 Normal file

Binary file not shown.

BIN
assets/voice/level07.mp3 Normal file

Binary file not shown.

BIN
assets/voice/level08.mp3 Normal file

Binary file not shown.

BIN
assets/voice/level09.mp3 Normal file

Binary file not shown.

BIN
assets/voice/level10.mp3 Normal file

Binary file not shown.

View File

@ -199,10 +199,23 @@ export default class LevelSelectScene extends Phaser.Scene {
) )
.on('pointerout', () => this._hideTooltip()); .on('pointerout', () => this._hideTooltip());
} else { } else {
// Hover name tag for CLEARED nodes: a small level-name pill that fades
// in above the badge while the pointer is over it (the 'current'
// node already wears its name pill permanently, so no tag there).
// Created before setInteractive so it sits ahead of the node in the
// display list; depth DEPTH.tooltip (10) puts it over node and tag.
const nameTag = state === 'done' ? createPill(this, p.x, p.y, level.name, PILL_STYLE).setDepth(DEPTH.tooltip).setAlpha(0) : null;
if (nameTag) this._groups.tags.push(nameTag); // rebuilt/destroyed with the campaign group
node node
.setInteractive({ useHandCursor: true, hitArea, hitAreaCallback: hitTest }) .setInteractive({ useHandCursor: true, hitArea, hitAreaCallback: hitTest })
.on('pointerover', () => this._tweenNodeSize(node, nw * NODE_HOVER, nh * NODE_HOVER)) .on('pointerover', () => {
.on('pointerout', () => this._tweenNodeSize(node, nw, nh)) this._tweenNodeSize(node, nw * NODE_HOVER, nh * NODE_HOVER);
if (nameTag) this._showNameTag(nameTag, p.x, p.y - nh * 0.78);
})
.on('pointerout', () => {
this._tweenNodeSize(node, nw, nh);
if (nameTag) this._hideNameTag(nameTag);
})
.on('pointerdown', () => { .on('pointerdown', () => {
this._tweenNodeSize(node, nw * 0.92, nh * 0.92, 70); this._tweenNodeSize(node, nw * 0.92, nh * 0.92, 70);
this.time.delayedCall(80, () => { this.time.delayedCall(80, () => {
@ -376,6 +389,23 @@ export default class LevelSelectScene extends Phaser.Scene {
} }
} }
// Hover name tag for cleared nodes: fade in (a little pop) while hovered,
// fade out when the pointer leaves. Non-blocking - the click's
// pointerdown/scene transition below are never gated on this tween, so an
// overlapping node's pointerover can't interrupt a still-running fade-in.
_showNameTag(tag, x, y) {
tag.setPosition(x, y);
tag.setAlpha(0);
this.tweens.killTweensOf(tag);
this.tweens.add({ targets: tag, alpha: 1, duration: 100, ease: 'Quad.easeOut' });
}
_hideNameTag(tag) {
if (!tag.active) return;
this.tweens.killTweensOf(tag);
this.tweens.add({ targets: tag, alpha: 0, duration: 100, ease: 'Quad.easeIn' });
}
_drawPager() { _drawPager() {
const g = this._pager; const g = this._pager;
g.clear(); g.clear();