119 lines
3.6 KiB
JavaScript
119 lines
3.6 KiB
JavaScript
import Phaser from '../vendor/phaser.js';
|
|
import { config } from '../config/Config.js';
|
|
import { toColor, toCss } from '../utils/Color.js';
|
|
import { fontStack } from '../utils/Theme.js';
|
|
import { CyberShape } from './CyberShape.js';
|
|
|
|
const FONT_FALLBACK = "'Segoe UI', 'Helvetica Neue', Arial, sans-serif";
|
|
|
|
/**
|
|
* A TOAST — the save pop-up's feedback line ("GAME SAVED · SLOT 03",
|
|
* "SAVE FAILED · STORAGE FULL"), a slim cut-corner strip that slides in
|
|
* above the panel, holds, and dissolves.
|
|
*
|
|
* Kinds (data/save.json → toast): ok (neon) · warn (amber) · error (red) —
|
|
* each with its own color + duration.
|
|
*
|
|
* const toast = new Toast(scene, x, y);
|
|
* toast.show('GAME SAVED · SLOT 03', { kind: 'ok' });
|
|
* toast.update(time); // called by the panel
|
|
*
|
|
* Screen-fixed (scrollFactor 0 on every child — the v4 quirk).
|
|
*/
|
|
export class Toast extends Phaser.GameObjects.Container {
|
|
constructor(scene, x, y) {
|
|
super(scene, x, y);
|
|
this.scene.add.existing(this);
|
|
this.setScrollFactor(0);
|
|
this.setSize(280, 30);
|
|
|
|
const def = config.get('save.toast.ok', {});
|
|
this.accent = toColor(def.color ?? '#00e5ff');
|
|
this.holdMs = def.durationMs ?? 2400;
|
|
|
|
this.panelG = scene.add.graphics().setScrollFactor(0);
|
|
this.add(this.panelG);
|
|
this.text = scene.add
|
|
.text(0, 1, '', {
|
|
fontFamily: fontStack('body', FONT_FALLBACK),
|
|
fontSize: '11px',
|
|
color: toCss(config.get('save.colors.ink', '#eaf6ff')),
|
|
letterSpacing: 2,
|
|
})
|
|
.setOrigin(0.5)
|
|
.setScrollFactor(0);
|
|
this.add(this.text);
|
|
|
|
this.state = 'hidden'; // hidden | in | hold | out
|
|
this.t0 = 0;
|
|
}
|
|
|
|
/**
|
|
* @param {string} msg
|
|
* @param {object} [o] { kind?: 'ok'|'warn'|'error', time? }
|
|
*/
|
|
show(msg, o = {}) {
|
|
const kind = o.kind ?? 'ok';
|
|
const def = config.get(`save.toast.${kind}`, { color: '#00e5ff', durationMs: 2400 }) ?? {};
|
|
this.accent = toColor(def.color ?? '#00e5ff');
|
|
this.holdMs = def.durationMs ?? 2400;
|
|
this.text.setText(String(msg ?? '').toUpperCase());
|
|
// Size the panel to the message (measure, then paint).
|
|
const w = Math.max(120, this.text.width + 34);
|
|
this.setSize(w, 30);
|
|
this.panelG.clear();
|
|
CyberShape.draw(this.panelG, w, 30, {
|
|
notch: 8,
|
|
fill: 0x0a1322,
|
|
fillAlpha: 0.94,
|
|
stroke: this.accent,
|
|
strokeAlpha: 0.65,
|
|
lineWidth: 1.5,
|
|
});
|
|
// Accent tick on the left (the toast's signature).
|
|
this.panelG.fillStyle(this.accent, 0.9);
|
|
this.panelG.fillRect(-w / 2 + 3, -9, 3, 18);
|
|
|
|
const t = o.time ?? this.scene.time.now;
|
|
this.state = 'in';
|
|
this.t0 = t;
|
|
this.setAlpha(0);
|
|
this.setY((this.baseY ?? this.y) + 6);
|
|
}
|
|
|
|
get isOpen() {
|
|
return this.state !== 'hidden';
|
|
}
|
|
|
|
/** Driven by the panel's update(). */
|
|
update(time) {
|
|
if (this.state === 'hidden') return;
|
|
const baseY = this.baseY ?? this.y;
|
|
const p = Phaser.Math.Clamp((time - this.t0) / 170, 0, 1);
|
|
if (this.state === 'in') {
|
|
this.setAlpha(1 - (1 - p) * (1 - p));
|
|
this.setY(baseY + 6 * (1 - p));
|
|
if (p >= 1) {
|
|
this.state = 'hold';
|
|
this.t0 = time;
|
|
}
|
|
} else if (this.state === 'hold') {
|
|
if (time - this.t0 >= this.holdMs) {
|
|
this.state = 'out';
|
|
this.t0 = time;
|
|
}
|
|
} else if (this.state === 'out') {
|
|
const q = Phaser.Math.Clamp((time - this.t0) / 200, 0, 1);
|
|
this.setAlpha(1 - q);
|
|
this.setY(baseY - 4 * q);
|
|
if (q >= 1) this.state = 'hidden';
|
|
}
|
|
}
|
|
|
|
/** Park the toast at a new spot (above the panel). */
|
|
setBaseY(y) {
|
|
this.baseY = y;
|
|
if (this.state === 'hidden') this.setY(y);
|
|
}
|
|
}
|