feat: overhaul visual theme to vintage gold and update UI components
- Update color palette to warm vintage tones (gold, cream, dark brown) in config.js - Apply 'Righteous' and 'Julius Sans One' fonts across all game scenes and UI components - Redesign Parchisi game logic: switch to counter-clockwise movement, update entry/home positions, and add bonus chip indicator - Improve Parchisi UI: reposition dice and buttons, add turn indicator movement with callbacks, and fix AI turn timing - Enhance LandingScene with animated logo, avatar support, and improved layout - Update Button component with rounded corners, shadow effects, and hover states - Add background image and main title asset loading in PreloadScene - Extend auth service to include displayName and avatarPath in session user data - Update CSS to match new theme and load custom fonts
This commit is contained in:
parent
d206cf6e5b
commit
56f1cdd752
|
|
@ -2,18 +2,22 @@ export const GAME_WIDTH = 1920;
|
||||||
export const GAME_HEIGHT = 1080;
|
export const GAME_HEIGHT = 1080;
|
||||||
|
|
||||||
export const COLORS = {
|
export const COLORS = {
|
||||||
bg: 0x0a0e14,
|
bg: 0x0f0d0a,
|
||||||
bgHex: '#0a0e14',
|
bgHex: '#0f0d0a',
|
||||||
panel: 0x111923,
|
panel: 0x1e1a12,
|
||||||
panelHex: '#111923',
|
panelHex: '#1e1a12',
|
||||||
accent: 0x5aa9e6,
|
accent: 0xc8a84b,
|
||||||
accentHex: '#5aa9e6',
|
accentHex: '#c8a84b',
|
||||||
text: 0xe6edf3,
|
text: 0xf2ead8,
|
||||||
textHex: '#e6edf3',
|
textHex: '#f2ead8',
|
||||||
muted: 0x8a94a6,
|
muted: 0x9e9080,
|
||||||
mutedHex: '#8a94a6',
|
mutedHex: '#9e9080',
|
||||||
danger: 0xe06c75,
|
danger: 0xe06c75,
|
||||||
dangerHex: '#e06c75',
|
dangerHex: '#e06c75',
|
||||||
|
gold: 0xd4a017,
|
||||||
|
goldHex: '#d4a017',
|
||||||
|
textDark: 0x1a1208,
|
||||||
|
textDarkHex: '#1a1208',
|
||||||
};
|
};
|
||||||
|
|
||||||
export const API_BASE = '/api';
|
export const API_BASE = '/api';
|
||||||
|
|
|
||||||
|
|
@ -151,7 +151,7 @@ export default class BackgammonGame extends Phaser.Scene {
|
||||||
const isBottom = idx < 12;
|
const isBottom = idx < 12;
|
||||||
const ly = isBottom ? FY + FH + 14 : FY - 14;
|
const ly = isBottom ? FY + FH + 14 : FY - 14;
|
||||||
this.add.text(cx, ly, label, {
|
this.add.text(cx, ly, label, {
|
||||||
fontFamily: 'system-ui, sans-serif',
|
fontFamily: '"Julius Sans One"',
|
||||||
fontSize: '18px',
|
fontSize: '18px',
|
||||||
color: COLORS.mutedHex,
|
color: COLORS.mutedHex,
|
||||||
}).setOrigin(0.5).setDepth(DEPTH.board);
|
}).setOrigin(0.5).setDepth(DEPTH.board);
|
||||||
|
|
@ -181,20 +181,20 @@ export default class BackgammonGame extends Phaser.Scene {
|
||||||
|
|
||||||
// Labels
|
// Labels
|
||||||
this.add.text(BEAR_X + BEAR_W / 2, FY + 18, 'OFF', {
|
this.add.text(BEAR_X + BEAR_W / 2, FY + 18, 'OFF', {
|
||||||
fontFamily: 'system-ui, sans-serif', fontSize: '16px', color: COLORS.mutedHex,
|
fontFamily: '"Julius Sans One"', fontSize: '16px', color: COLORS.mutedHex,
|
||||||
}).setOrigin(0.5).setDepth(DEPTH.board);
|
}).setOrigin(0.5).setDepth(DEPTH.board);
|
||||||
|
|
||||||
this.add.text(BEAR_X + BEAR_W / 2, FY + FH - 18, 'OFF', {
|
this.add.text(BEAR_X + BEAR_W / 2, FY + FH - 18, 'OFF', {
|
||||||
fontFamily: 'system-ui, sans-serif', fontSize: '16px', color: COLORS.mutedHex,
|
fontFamily: '"Julius Sans One"', fontSize: '16px', color: COLORS.mutedHex,
|
||||||
}).setOrigin(0.5).setDepth(DEPTH.board);
|
}).setOrigin(0.5).setDepth(DEPTH.board);
|
||||||
|
|
||||||
// Pip count labels
|
// Pip count labels
|
||||||
this.pipBlackText = this.add.text(BEAR_X + BEAR_W / 2, FY + FH / 4, '0', {
|
this.pipBlackText = this.add.text(BEAR_X + BEAR_W / 2, FY + FH / 4, '0', {
|
||||||
fontFamily: 'system-ui, sans-serif', fontSize: '28px', color: '#3a3a4e',
|
fontFamily: '"Julius Sans One"', fontSize: '28px', color: '#3a3a4e',
|
||||||
}).setOrigin(0.5).setDepth(DEPTH.ui);
|
}).setOrigin(0.5).setDepth(DEPTH.ui);
|
||||||
|
|
||||||
this.pipWhiteText = this.add.text(BEAR_X + BEAR_W / 2, FY + 3 * FH / 4, '0', {
|
this.pipWhiteText = this.add.text(BEAR_X + BEAR_W / 2, FY + 3 * FH / 4, '0', {
|
||||||
fontFamily: 'system-ui, sans-serif', fontSize: '28px', color: '#d4c5a0',
|
fontFamily: '"Julius Sans One"', fontSize: '28px', color: '#d4c5a0',
|
||||||
}).setOrigin(0.5).setDepth(DEPTH.ui);
|
}).setOrigin(0.5).setDepth(DEPTH.ui);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -246,13 +246,13 @@ export default class BackgammonGame extends Phaser.Scene {
|
||||||
|
|
||||||
// Status message (also serves as turn label at bottom)
|
// Status message (also serves as turn label at bottom)
|
||||||
this.turnText = this.add.text(cx, BY + BH + 18, '', {
|
this.turnText = this.add.text(cx, BY + BH + 18, '', {
|
||||||
fontFamily: 'system-ui, sans-serif',
|
fontFamily: '"Julius Sans One"',
|
||||||
fontSize: '24px',
|
fontSize: '24px',
|
||||||
color: COLORS.mutedHex,
|
color: COLORS.mutedHex,
|
||||||
}).setOrigin(0.5).setDepth(DEPTH.ui);
|
}).setOrigin(0.5).setDepth(DEPTH.ui);
|
||||||
|
|
||||||
this.statusText = this.add.text(cx, BY + BH + 46, '', {
|
this.statusText = this.add.text(cx, BY + BH + 46, '', {
|
||||||
fontFamily: 'system-ui, sans-serif',
|
fontFamily: '"Julius Sans One"',
|
||||||
fontSize: '22px',
|
fontSize: '22px',
|
||||||
color: COLORS.mutedHex,
|
color: COLORS.mutedHex,
|
||||||
}).setOrigin(0.5).setDepth(DEPTH.ui);
|
}).setOrigin(0.5).setDepth(DEPTH.ui);
|
||||||
|
|
@ -289,7 +289,7 @@ export default class BackgammonGame extends Phaser.Scene {
|
||||||
this.add.circle(avatarX, oppAY, r + 5, C.barWood).setDepth(depth);
|
this.add.circle(avatarX, oppAY, r + 5, C.barWood).setDepth(depth);
|
||||||
this.opponentPortrait = createOpponentPortrait(this, opp, avatarX, oppAY, r, depth + 1);
|
this.opponentPortrait = createOpponentPortrait(this, opp, avatarX, oppAY, r, depth + 1);
|
||||||
this.add.text(avatarX, oppAY + r + 14, opp?.name ?? 'CPU', {
|
this.add.text(avatarX, oppAY + r + 14, opp?.name ?? 'CPU', {
|
||||||
fontFamily: 'system-ui, sans-serif', fontSize: '18px',
|
fontFamily: '"Julius Sans One"', fontSize: '18px',
|
||||||
color: COLORS.textHex, wordWrap: { width: 240 }, align: 'center',
|
color: COLORS.textHex, wordWrap: { width: 240 }, align: 'center',
|
||||||
}).setOrigin(0.5, 0).setDepth(depth + 2);
|
}).setOrigin(0.5, 0).setDepth(depth + 2);
|
||||||
|
|
||||||
|
|
@ -298,7 +298,7 @@ export default class BackgammonGame extends Phaser.Scene {
|
||||||
this.add.circle(avatarX, plrAY, r + 5, COLORS.accent, 0.5).setDepth(depth);
|
this.add.circle(avatarX, plrAY, r + 5, COLORS.accent, 0.5).setDepth(depth);
|
||||||
createPlayerPortrait(this, avatarX, plrAY, r, depth + 1, 'Backgammon');
|
createPlayerPortrait(this, avatarX, plrAY, r, depth + 1, 'Backgammon');
|
||||||
this.add.text(avatarX, plrAY - r - 14, auth.user?.username ?? 'You', {
|
this.add.text(avatarX, plrAY - r - 14, auth.user?.username ?? 'You', {
|
||||||
fontFamily: 'system-ui, sans-serif', fontSize: '18px',
|
fontFamily: '"Julius Sans One"', fontSize: '18px',
|
||||||
color: COLORS.textHex, wordWrap: { width: 240 }, align: 'center',
|
color: COLORS.textHex, wordWrap: { width: 240 }, align: 'center',
|
||||||
}).setOrigin(0.5, 1).setDepth(depth + 2);
|
}).setOrigin(0.5, 1).setDepth(depth + 2);
|
||||||
}
|
}
|
||||||
|
|
@ -352,7 +352,7 @@ export default class BackgammonGame extends Phaser.Scene {
|
||||||
if (pt.count > 5) {
|
if (pt.count > 5) {
|
||||||
const pos = this.checkerScreenPos(idx, 4);
|
const pos = this.checkerScreenPos(idx, 4);
|
||||||
this.add.text(pos.x, pos.y, String(pt.count), {
|
this.add.text(pos.x, pos.y, String(pt.count), {
|
||||||
fontFamily: 'system-ui, sans-serif', fontSize: '22px',
|
fontFamily: '"Julius Sans One"', fontSize: '22px',
|
||||||
color: pt.color === 'white' ? '#2c1a0e' : '#f0e8d0',
|
color: pt.color === 'white' ? '#2c1a0e' : '#f0e8d0',
|
||||||
}).setOrigin(0.5).setDepth(DEPTH.checker + 1);
|
}).setOrigin(0.5).setDepth(DEPTH.checker + 1);
|
||||||
}
|
}
|
||||||
|
|
@ -377,7 +377,7 @@ export default class BackgammonGame extends Phaser.Scene {
|
||||||
if (count > 4) {
|
if (count > 4) {
|
||||||
const dy = color === 'white' ? 3 * (CR * 2 + 2) : -(3 * (CR * 2 + 2));
|
const dy = color === 'white' ? 3 * (CR * 2 + 2) : -(3 * (CR * 2 + 2));
|
||||||
this.add.text(barCX, baseY + dy, `×${count}`, {
|
this.add.text(barCX, baseY + dy, `×${count}`, {
|
||||||
fontFamily: 'system-ui, sans-serif', fontSize: '20px',
|
fontFamily: '"Julius Sans One"', fontSize: '20px',
|
||||||
color: color === 'white' ? COLORS.textHex : COLORS.mutedHex,
|
color: color === 'white' ? COLORS.textHex : COLORS.mutedHex,
|
||||||
}).setOrigin(0.5).setDepth(DEPTH.checker + 1);
|
}).setOrigin(0.5).setDepth(DEPTH.checker + 1);
|
||||||
}
|
}
|
||||||
|
|
@ -817,7 +817,7 @@ export default class BackgammonGame extends Phaser.Scene {
|
||||||
showTurnBanner(text) {
|
showTurnBanner(text) {
|
||||||
const cx = BX + BW / 2;
|
const cx = BX + BW / 2;
|
||||||
const banner = this.add.text(cx, BY - 80, text, {
|
const banner = this.add.text(cx, BY - 80, text, {
|
||||||
fontFamily: 'system-ui, sans-serif',
|
fontFamily: 'Righteous',
|
||||||
fontSize: '36px',
|
fontSize: '36px',
|
||||||
color: COLORS.textHex,
|
color: COLORS.textHex,
|
||||||
backgroundColor: '#111923ee',
|
backgroundColor: '#111923ee',
|
||||||
|
|
@ -888,7 +888,7 @@ export default class BackgammonGame extends Phaser.Scene {
|
||||||
.setStrokeStyle(3, COLORS.accent)
|
.setStrokeStyle(3, COLORS.accent)
|
||||||
.setDepth(DEPTH.banner);
|
.setDepth(DEPTH.banner);
|
||||||
const txt = this.add.text(BX + BW / 2, BY + BH / 2 - 40, msg, {
|
const txt = this.add.text(BX + BW / 2, BY + BH / 2 - 40, msg, {
|
||||||
fontFamily: 'system-ui, sans-serif',
|
fontFamily: '"Julius Sans One"',
|
||||||
fontSize: '32px',
|
fontSize: '32px',
|
||||||
color: isHuman ? '#ffd700' : COLORS.textHex,
|
color: isHuman ? '#ffd700' : COLORS.textHex,
|
||||||
align: 'center',
|
align: 'center',
|
||||||
|
|
|
||||||
|
|
@ -116,15 +116,15 @@ export default class BlackjackGame extends Phaser.Scene {
|
||||||
// ── Dealer area ───────────────────────────────────────────────────────────
|
// ── Dealer area ───────────────────────────────────────────────────────────
|
||||||
buildDealerArea() {
|
buildDealerArea() {
|
||||||
this.add.text(CX, 60, 'Blackjack', {
|
this.add.text(CX, 60, 'Blackjack', {
|
||||||
fontFamily: 'system-ui, sans-serif', fontSize: '52px', color: COLORS.textHex,
|
fontFamily: 'Righteous', fontSize: '52px', color: COLORS.textHex,
|
||||||
}).setOrigin(0.5).setDepth(D.ui);
|
}).setOrigin(0.5).setDepth(D.ui);
|
||||||
|
|
||||||
this.add.text(CX, 110, 'Dealer stands on all 17s · Blackjack pays 3:2', {
|
this.add.text(CX, 110, 'Dealer stands on all 17s · Blackjack pays 3:2', {
|
||||||
fontFamily: 'system-ui, sans-serif', fontSize: '18px', color: COLORS.mutedHex,
|
fontFamily: '"Julius Sans One"', fontSize: '18px', color: COLORS.mutedHex,
|
||||||
}).setOrigin(0.5).setDepth(D.ui);
|
}).setOrigin(0.5).setDepth(D.ui);
|
||||||
|
|
||||||
this.dealerScoreTxt = this.add.text(CX, DEALER_Y - CARD_H / 2 - 22, '', {
|
this.dealerScoreTxt = this.add.text(CX, DEALER_Y - CARD_H / 2 - 22, '', {
|
||||||
fontFamily: 'system-ui, sans-serif', fontSize: '22px', color: COLORS.textHex,
|
fontFamily: '"Julius Sans One"', fontSize: '22px', color: COLORS.textHex,
|
||||||
}).setOrigin(0.5).setDepth(D.ui);
|
}).setOrigin(0.5).setDepth(D.ui);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -159,11 +159,11 @@ export default class BlackjackGame extends Phaser.Scene {
|
||||||
}
|
}
|
||||||
|
|
||||||
this.nameTxts[seat] = this.add.text(nameX, nameY, player.name, {
|
this.nameTxts[seat] = this.add.text(nameX, nameY, player.name, {
|
||||||
fontFamily: 'system-ui, sans-serif', fontSize: '20px', color: COLORS.textHex,
|
fontFamily: '"Julius Sans One"', fontSize: '20px', color: COLORS.textHex,
|
||||||
}).setOrigin(labelAnchorX, 0.5).setDepth(D.ui);
|
}).setOrigin(labelAnchorX, 0.5).setDepth(D.ui);
|
||||||
|
|
||||||
this.chipTxts[seat] = this.add.text(chipX, chipY, '', {
|
this.chipTxts[seat] = this.add.text(chipX, chipY, '', {
|
||||||
fontFamily: 'system-ui, sans-serif', fontSize: '18px', color: COLORS.mutedHex,
|
fontFamily: '"Julius Sans One"', fontSize: '18px', color: COLORS.mutedHex,
|
||||||
}).setOrigin(labelAnchorX, 0.5).setDepth(D.ui);
|
}).setOrigin(labelAnchorX, 0.5).setDepth(D.ui);
|
||||||
|
|
||||||
// Semi-transparent backing behind both text lines, depth below portrait images/videos
|
// Semi-transparent backing behind both text lines, depth below portrait images/videos
|
||||||
|
|
@ -180,7 +180,7 @@ export default class BlackjackGame extends Phaser.Scene {
|
||||||
}
|
}
|
||||||
|
|
||||||
this.scoreTxts[seat] = this.add.text(pos.x, pos.y - CARD_H / 2 - 11, '', {
|
this.scoreTxts[seat] = this.add.text(pos.x, pos.y - CARD_H / 2 - 11, '', {
|
||||||
fontFamily: 'system-ui, sans-serif', fontSize: '20px', color: COLORS.textHex,
|
fontFamily: '"Julius Sans One"', fontSize: '20px', color: COLORS.textHex,
|
||||||
}).setOrigin(0.5).setDepth(D.ui);
|
}).setOrigin(0.5).setDepth(D.ui);
|
||||||
|
|
||||||
// Portraits
|
// Portraits
|
||||||
|
|
@ -215,7 +215,7 @@ export default class BlackjackGame extends Phaser.Scene {
|
||||||
container.on('pointerout', () => container.setAlpha(1));
|
container.on('pointerout', () => container.setAlpha(1));
|
||||||
|
|
||||||
const t = this.add.text(bx, y, `$${amt}`, {
|
const t = this.add.text(bx, y, `$${amt}`, {
|
||||||
fontFamily: 'system-ui, sans-serif', fontSize: '13px',
|
fontFamily: '"Julius Sans One"', fontSize: '13px',
|
||||||
color: CHIP_TEXT_COLORS[amt], fontStyle: 'bold',
|
color: CHIP_TEXT_COLORS[amt], fontStyle: 'bold',
|
||||||
}).setOrigin(0.5).setDepth(D.ui + 2);
|
}).setOrigin(0.5).setDepth(D.ui + 2);
|
||||||
|
|
||||||
|
|
@ -224,11 +224,11 @@ export default class BlackjackGame extends Phaser.Scene {
|
||||||
});
|
});
|
||||||
|
|
||||||
this.betDisplayText = this.add.text(cx + 170, y, 'Bet: $0', {
|
this.betDisplayText = this.add.text(cx + 170, y, 'Bet: $0', {
|
||||||
fontFamily: 'system-ui, sans-serif', fontSize: '22px', color: COLORS.textHex,
|
fontFamily: '"Julius Sans One"', fontSize: '22px', color: COLORS.textHex,
|
||||||
}).setOrigin(0, 0.5).setDepth(D.ui + 1);
|
}).setOrigin(0, 0.5).setDepth(D.ui + 1);
|
||||||
|
|
||||||
this.balanceText = this.add.text(cx + 170, y - 32, '', {
|
this.balanceText = this.add.text(cx + 170, y - 32, '', {
|
||||||
fontFamily: 'system-ui, sans-serif', fontSize: '18px', color: COLORS.mutedHex,
|
fontFamily: '"Julius Sans One"', fontSize: '18px', color: COLORS.mutedHex,
|
||||||
}).setOrigin(0, 0.5).setDepth(D.ui + 1);
|
}).setOrigin(0, 0.5).setDepth(D.ui + 1);
|
||||||
|
|
||||||
const clearBtn = new Button(this, cx + 360, y, 'Clear', () => this.onClearBet(), {
|
const clearBtn = new Button(this, cx + 360, y, 'Clear', () => this.onClearBet(), {
|
||||||
|
|
@ -296,7 +296,7 @@ export default class BlackjackGame extends Phaser.Scene {
|
||||||
|
|
||||||
// Create prompt text
|
// Create prompt text
|
||||||
const text = this.add.text(cx + 100, y, 'Choose an amount to bet and click Deal to begin', {
|
const text = this.add.text(cx + 100, y, 'Choose an amount to bet and click Deal to begin', {
|
||||||
fontFamily: 'system-ui, sans-serif',
|
fontFamily: '"Julius Sans One"',
|
||||||
fontSize: '18px',
|
fontSize: '18px',
|
||||||
color: '#ffffff',
|
color: '#ffffff',
|
||||||
align: 'center',
|
align: 'center',
|
||||||
|
|
@ -512,7 +512,7 @@ export default class BlackjackGame extends Phaser.Scene {
|
||||||
circle.lineStyle(2, 0xf0e8d0, 0.8);
|
circle.lineStyle(2, 0xf0e8d0, 0.8);
|
||||||
circle.strokeCircle(0, 0, 28);
|
circle.strokeCircle(0, 0, 28);
|
||||||
const txt = this.add.text(0, 0, `$${p.bet}`, {
|
const txt = this.add.text(0, 0, `$${p.bet}`, {
|
||||||
fontFamily: 'system-ui, sans-serif', fontSize: '14px', color: '#f0e8d0',
|
fontFamily: '"Julius Sans One"', fontSize: '14px', color: '#f0e8d0',
|
||||||
}).setOrigin(0.5);
|
}).setOrigin(0.5);
|
||||||
cont.add([circle, txt]);
|
cont.add([circle, txt]);
|
||||||
this.betGraphics[seat] = cont;
|
this.betGraphics[seat] = cont;
|
||||||
|
|
@ -575,7 +575,7 @@ export default class BlackjackGame extends Phaser.Scene {
|
||||||
container.add(g);
|
container.add(g);
|
||||||
const color = card.isRed ? '#c0392b' : '#1a1a2e';
|
const color = card.isRed ? '#c0392b' : '#1a1a2e';
|
||||||
const style = (sz, bold = false) => ({
|
const style = (sz, bold = false) => ({
|
||||||
fontFamily: 'system-ui, sans-serif', fontSize: `${sz}px`, color,
|
fontFamily: '"Julius Sans One"', fontSize: `${sz}px`, color,
|
||||||
...(bold ? { fontStyle: 'bold' } : {}),
|
...(bold ? { fontStyle: 'bold' } : {}),
|
||||||
});
|
});
|
||||||
container.add(this.add.text(x + 7, y + 5, card.label, style(17, true)));
|
container.add(this.add.text(x + 7, y + 5, card.label, style(17, true)));
|
||||||
|
|
@ -811,7 +811,7 @@ export default class BlackjackGame extends Phaser.Scene {
|
||||||
|
|
||||||
const textY = pos.y - CARD_H / 2 - 60;
|
const textY = pos.y - CARD_H / 2 - 60;
|
||||||
const badge = this.add.text(pos.x, textY, LABELS[result] ?? result, {
|
const badge = this.add.text(pos.x, textY, LABELS[result] ?? result, {
|
||||||
fontFamily: 'system-ui, sans-serif',
|
fontFamily: '"Julius Sans One"',
|
||||||
fontSize: SIZES[result] ?? '48px',
|
fontSize: SIZES[result] ?? '48px',
|
||||||
color: COLORS[result] ?? '#ffffff',
|
color: COLORS[result] ?? '#ffffff',
|
||||||
fontStyle: 'bold',
|
fontStyle: 'bold',
|
||||||
|
|
@ -953,7 +953,7 @@ export default class BlackjackGame extends Phaser.Scene {
|
||||||
const modal = this.add.container(CX, GAME_HEIGHT / 2).setDepth(D.modal);
|
const modal = this.add.container(CX, GAME_HEIGHT / 2).setDepth(D.modal);
|
||||||
const bg = this.add.rectangle(0, 0, 560, 200, COLORS.panel).setStrokeStyle(2, COLORS.accent);
|
const bg = this.add.rectangle(0, 0, 560, 200, COLORS.panel).setStrokeStyle(2, COLORS.accent);
|
||||||
const txt = this.add.text(0, -55, 'Dealer shows Ace\nTake insurance? (pays 2:1)', {
|
const txt = this.add.text(0, -55, 'Dealer shows Ace\nTake insurance? (pays 2:1)', {
|
||||||
fontFamily: 'system-ui, sans-serif', fontSize: '24px', color: COLORS.textHex, align: 'center',
|
fontFamily: '"Julius Sans One"', fontSize: '24px', color: COLORS.textHex, align: 'center',
|
||||||
}).setOrigin(0.5);
|
}).setOrigin(0.5);
|
||||||
modal.add([bg, txt]);
|
modal.add([bg, txt]);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -150,7 +150,7 @@ export default class HoldemGame extends Phaser.Scene {
|
||||||
|
|
||||||
buildPotDisplay() {
|
buildPotDisplay() {
|
||||||
this.potText = this.add.text(CX, CY - CARD_H / 2 - 36, 'Pot: $0', {
|
this.potText = this.add.text(CX, CY - CARD_H / 2 - 36, 'Pot: $0', {
|
||||||
fontFamily: 'system-ui, sans-serif',
|
fontFamily: '"Julius Sans One"',
|
||||||
fontSize: '26px',
|
fontSize: '26px',
|
||||||
color: '#f0e8d0',
|
color: '#f0e8d0',
|
||||||
}).setOrigin(0.5).setDepth(D.ui);
|
}).setOrigin(0.5).setDepth(D.ui);
|
||||||
|
|
@ -158,13 +158,13 @@ export default class HoldemGame extends Phaser.Scene {
|
||||||
|
|
||||||
buildBlindDisplay() {
|
buildBlindDisplay() {
|
||||||
this.blindText = this.add.text(24, 24, 'Blinds $5/$10', {
|
this.blindText = this.add.text(24, 24, 'Blinds $5/$10', {
|
||||||
fontFamily: 'system-ui, sans-serif',
|
fontFamily: '"Julius Sans One"',
|
||||||
fontSize: '20px',
|
fontSize: '20px',
|
||||||
color: COLORS.mutedHex,
|
color: COLORS.mutedHex,
|
||||||
}).setOrigin(0, 0).setDepth(D.ui);
|
}).setOrigin(0, 0).setDepth(D.ui);
|
||||||
|
|
||||||
this.timerText = this.add.text(24, 50, '', {
|
this.timerText = this.add.text(24, 50, '', {
|
||||||
fontFamily: 'system-ui, sans-serif',
|
fontFamily: '"Julius Sans One"',
|
||||||
fontSize: '16px',
|
fontSize: '16px',
|
||||||
color: COLORS.mutedHex,
|
color: COLORS.mutedHex,
|
||||||
}).setOrigin(0, 0).setDepth(D.ui);
|
}).setOrigin(0, 0).setDepth(D.ui);
|
||||||
|
|
@ -181,7 +181,7 @@ export default class HoldemGame extends Phaser.Scene {
|
||||||
// Name label
|
// Name label
|
||||||
const nameY = isHuman ? -CARD_H / 2 - 48 : CARD_H / 2 + 28;
|
const nameY = isHuman ? -CARD_H / 2 - 48 : CARD_H / 2 + 28;
|
||||||
const name = this.add.text(0, nameY, '—', {
|
const name = this.add.text(0, nameY, '—', {
|
||||||
fontFamily: 'system-ui, sans-serif',
|
fontFamily: '"Julius Sans One"',
|
||||||
fontSize: '18px',
|
fontSize: '18px',
|
||||||
color: COLORS.textHex,
|
color: COLORS.textHex,
|
||||||
}).setOrigin(0.5);
|
}).setOrigin(0.5);
|
||||||
|
|
@ -189,7 +189,7 @@ export default class HoldemGame extends Phaser.Scene {
|
||||||
// Chip count
|
// Chip count
|
||||||
const chipY = isHuman ? -CARD_H / 2 - 24 : CARD_H / 2 + 52;
|
const chipY = isHuman ? -CARD_H / 2 - 24 : CARD_H / 2 + 52;
|
||||||
const chipTxt = this.add.text(0, chipY, '$0', {
|
const chipTxt = this.add.text(0, chipY, '$0', {
|
||||||
fontFamily: 'system-ui, sans-serif',
|
fontFamily: '"Julius Sans One"',
|
||||||
fontSize: '20px',
|
fontSize: '20px',
|
||||||
color: '#f0e8d0',
|
color: '#f0e8d0',
|
||||||
}).setOrigin(0.5);
|
}).setOrigin(0.5);
|
||||||
|
|
@ -197,7 +197,7 @@ export default class HoldemGame extends Phaser.Scene {
|
||||||
// Bet display (shown at table edge between seat and center)
|
// Bet display (shown at table edge between seat and center)
|
||||||
const betY = isHuman ? -CARD_H / 2 - 80 : (seat === 2 ? CARD_H / 2 + 80 : (y > CY ? -CARD_H / 2 - 80 : CARD_H / 2 + 80));
|
const betY = isHuman ? -CARD_H / 2 - 80 : (seat === 2 ? CARD_H / 2 + 80 : (y > CY ? -CARD_H / 2 - 80 : CARD_H / 2 + 80));
|
||||||
const betTxt = this.add.text(0, betY, '', {
|
const betTxt = this.add.text(0, betY, '', {
|
||||||
fontFamily: 'system-ui, sans-serif',
|
fontFamily: '"Julius Sans One"',
|
||||||
fontSize: '18px',
|
fontSize: '18px',
|
||||||
color: '#ffd700',
|
color: '#ffd700',
|
||||||
}).setOrigin(0.5);
|
}).setOrigin(0.5);
|
||||||
|
|
@ -205,7 +205,7 @@ export default class HoldemGame extends Phaser.Scene {
|
||||||
// Dealer chip
|
// Dealer chip
|
||||||
const dealerChip = this.add.circle(CARD_W / 2 + 16, -CARD_H / 2 + 16, 14, 0xf0e8d0).setVisible(false);
|
const dealerChip = this.add.circle(CARD_W / 2 + 16, -CARD_H / 2 + 16, 14, 0xf0e8d0).setVisible(false);
|
||||||
const dealerTxt = this.add.text(CARD_W / 2 + 16, -CARD_H / 2 + 16, 'D', {
|
const dealerTxt = this.add.text(CARD_W / 2 + 16, -CARD_H / 2 + 16, 'D', {
|
||||||
fontFamily: 'system-ui, sans-serif', fontSize: '14px', color: '#1a1a1a',
|
fontFamily: '"Julius Sans One"', fontSize: '14px', color: '#1a1a1a',
|
||||||
}).setOrigin(0.5).setVisible(false);
|
}).setOrigin(0.5).setVisible(false);
|
||||||
|
|
||||||
// Card containers — each holds graphics + text children
|
// Card containers — each holds graphics + text children
|
||||||
|
|
@ -271,7 +271,7 @@ export default class HoldemGame extends Phaser.Scene {
|
||||||
const bg = this.add.rectangle(0, 0, 560, 68, 0x111923, 0.95)
|
const bg = this.add.rectangle(0, 0, 560, 68, 0x111923, 0.95)
|
||||||
.setStrokeStyle(1, COLORS.accent);
|
.setStrokeStyle(1, COLORS.accent);
|
||||||
this.raiseAmtText = this.add.text(0, -10, '$0', {
|
this.raiseAmtText = this.add.text(0, -10, '$0', {
|
||||||
fontFamily: 'system-ui, sans-serif', fontSize: '22px', color: '#f0e8d0',
|
fontFamily: '"Julius Sans One"', fontSize: '22px', color: '#f0e8d0',
|
||||||
}).setOrigin(0.5);
|
}).setOrigin(0.5);
|
||||||
|
|
||||||
const minus = new Button(this, -200, 0, '−', () => this.adjustRaise(-this.raiseStep()), { width: 50, height: 40, fontSize: 22 });
|
const minus = new Button(this, -200, 0, '−', () => this.adjustRaise(-this.raiseStep()), { width: 50, height: 40, fontSize: 22 });
|
||||||
|
|
@ -365,22 +365,22 @@ export default class HoldemGame extends Phaser.Scene {
|
||||||
.setStrokeStyle(2, COLORS.accent).setDepth(D.modal);
|
.setStrokeStyle(2, COLORS.accent).setDepth(D.modal);
|
||||||
|
|
||||||
const title = this.add.text(CX, CY - 120, "Texas Hold 'Em", {
|
const title = this.add.text(CX, CY - 120, "Texas Hold 'Em", {
|
||||||
fontFamily: 'system-ui, sans-serif', fontSize: '36px', color: COLORS.textHex,
|
fontFamily: 'Righteous', fontSize: '36px', color: COLORS.textHex,
|
||||||
}).setOrigin(0.5).setDepth(D.modal);
|
}).setOrigin(0.5).setDepth(D.modal);
|
||||||
|
|
||||||
const balanceTxt = this.add.text(CX, CY - 60, `Your balance: $${this.globalChips.toLocaleString()}`, {
|
const balanceTxt = this.add.text(CX, CY - 60, `Your balance: $${this.globalChips.toLocaleString()}`, {
|
||||||
fontFamily: 'system-ui, sans-serif', fontSize: '22px', color: COLORS.mutedHex,
|
fontFamily: '"Julius Sans One"', fontSize: '22px', color: COLORS.mutedHex,
|
||||||
}).setOrigin(0.5).setDepth(D.modal);
|
}).setOrigin(0.5).setDepth(D.modal);
|
||||||
|
|
||||||
const buyInTxt = this.add.text(CX, CY - 20, `Buy-in: $${this.buyIn}`, {
|
const buyInTxt = this.add.text(CX, CY - 20, `Buy-in: $${this.buyIn}`, {
|
||||||
fontFamily: 'system-ui, sans-serif', fontSize: '28px', color: '#ffd700',
|
fontFamily: '"Julius Sans One"', fontSize: '28px', color: '#ffd700',
|
||||||
}).setOrigin(0.5).setDepth(D.modal);
|
}).setOrigin(0.5).setDepth(D.modal);
|
||||||
|
|
||||||
const modalItems = [overlay, panel, title, balanceTxt, buyInTxt];
|
const modalItems = [overlay, panel, title, balanceTxt, buyInTxt];
|
||||||
|
|
||||||
if (this.globalChips <= 0) {
|
if (this.globalChips <= 0) {
|
||||||
this.add.text(CX, CY + 40, 'You have no chips! Return to the lobby.', {
|
this.add.text(CX, CY + 40, 'You have no chips! Return to the lobby.', {
|
||||||
fontFamily: 'system-ui, sans-serif', fontSize: '20px', color: COLORS.dangerHex,
|
fontFamily: '"Julius Sans One"', fontSize: '20px', color: COLORS.dangerHex,
|
||||||
}).setOrigin(0.5).setDepth(D.modal);
|
}).setOrigin(0.5).setDepth(D.modal);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -742,7 +742,7 @@ export default class HoldemGame extends Phaser.Scene {
|
||||||
}
|
}
|
||||||
|
|
||||||
const label = this.add.text(0, 22, `$${p.bet}`, {
|
const label = this.add.text(0, 22, `$${p.bet}`, {
|
||||||
fontFamily: 'system-ui, sans-serif',
|
fontFamily: '"Julius Sans One"',
|
||||||
fontSize: '15px',
|
fontSize: '15px',
|
||||||
color: '#f0e8d0',
|
color: '#f0e8d0',
|
||||||
stroke: '#000000',
|
stroke: '#000000',
|
||||||
|
|
@ -951,7 +951,7 @@ export default class HoldemGame extends Phaser.Scene {
|
||||||
const color = card.isRed ? '#c0392b' : '#1a1a2e';
|
const color = card.isRed ? '#c0392b' : '#1a1a2e';
|
||||||
const sym = card.suitSymbol;
|
const sym = card.suitSymbol;
|
||||||
const style = (size, bold = false) => ({
|
const style = (size, bold = false) => ({
|
||||||
fontFamily: 'system-ui, sans-serif',
|
fontFamily: '"Julius Sans One"',
|
||||||
fontSize: `${size}px`,
|
fontSize: `${size}px`,
|
||||||
color,
|
color,
|
||||||
...(bold ? { fontStyle: 'bold' } : {}),
|
...(bold ? { fontStyle: 'bold' } : {}),
|
||||||
|
|
@ -995,10 +995,10 @@ export default class HoldemGame extends Phaser.Scene {
|
||||||
const isHuman = seat === 0;
|
const isHuman = seat === 0;
|
||||||
const badgeY = isHuman ? pos.y - CARD_H / 2 - 110 : pos.y + CARD_H / 2 + 70;
|
const badgeY = isHuman ? pos.y - CARD_H / 2 - 110 : pos.y + CARD_H / 2 + 70;
|
||||||
const labels = { fold: 'FOLD', check: 'CHECK', call: 'CALL', raise: `RAISE $${action.amount ?? ''}`, allin: 'ALL IN' };
|
const labels = { fold: 'FOLD', check: 'CHECK', call: 'CALL', raise: `RAISE $${action.amount ?? ''}`, allin: 'ALL IN' };
|
||||||
const colors = { fold: '#e06c75', check: '#8a94a6', call: '#5aa9e6', raise: '#ffd700', allin: '#ff6b35' };
|
const colors = { fold: '#e06c75', check: '#9e9080', call: COLORS.accentHex, raise: '#ffd700', allin: '#ff6b35' };
|
||||||
|
|
||||||
const badge = this.add.text(pos.x, badgeY, labels[action.type] ?? action.type.toUpperCase(), {
|
const badge = this.add.text(pos.x, badgeY, labels[action.type] ?? action.type.toUpperCase(), {
|
||||||
fontFamily: 'system-ui, sans-serif',
|
fontFamily: '"Julius Sans One"',
|
||||||
fontSize: '22px',
|
fontSize: '22px',
|
||||||
color: colors[action.type] ?? COLORS.textHex,
|
color: colors[action.type] ?? COLORS.textHex,
|
||||||
backgroundColor: '#111923cc',
|
backgroundColor: '#111923cc',
|
||||||
|
|
@ -1043,7 +1043,7 @@ export default class HoldemGame extends Phaser.Scene {
|
||||||
|
|
||||||
// Title
|
// Title
|
||||||
const title = this.add.text(CX, 38, 'Hand Summary', {
|
const title = this.add.text(CX, 38, 'Hand Summary', {
|
||||||
fontFamily: 'system-ui, sans-serif', fontSize: '26px', color: COLORS.mutedHex,
|
fontFamily: '"Julius Sans One"', fontSize: '26px', color: COLORS.mutedHex,
|
||||||
}).setOrigin(0.5).setDepth(D.modal + 1).setAlpha(0);
|
}).setOrigin(0.5).setDepth(D.modal + 1).setAlpha(0);
|
||||||
S.push(title);
|
S.push(title);
|
||||||
this.tweens.add({ targets: title, alpha: 1, duration: 300, delay: 200 });
|
this.tweens.add({ targets: title, alpha: 1, duration: 300, delay: 200 });
|
||||||
|
|
@ -1143,7 +1143,7 @@ export default class HoldemGame extends Phaser.Scene {
|
||||||
} else {
|
} else {
|
||||||
const initial = (auth.user?.username ?? 'You').charAt(0).toUpperCase();
|
const initial = (auth.user?.username ?? 'You').charAt(0).toUpperCase();
|
||||||
const initTxt = this.add.text(portX, rowY, initial, {
|
const initTxt = this.add.text(portX, rowY, initial, {
|
||||||
fontFamily: 'system-ui, sans-serif', fontSize: '46px', color: COLORS.accentHex,
|
fontFamily: 'Righteous', fontSize: '46px', color: COLORS.accentHex,
|
||||||
}).setOrigin(0.5).setDepth(D.modal + 2).setAlpha(0);
|
}).setOrigin(0.5).setDepth(D.modal + 2).setAlpha(0);
|
||||||
S.push(initTxt);
|
S.push(initTxt);
|
||||||
this.tweens.add({ targets: initTxt, alpha: 1, duration: 350, delay });
|
this.tweens.add({ targets: initTxt, alpha: 1, duration: 350, delay });
|
||||||
|
|
@ -1152,7 +1152,7 @@ export default class HoldemGame extends Phaser.Scene {
|
||||||
// Player name
|
// Player name
|
||||||
const nameX = 250;
|
const nameX = 250;
|
||||||
const nameTxt = this.add.text(nameX, rowY - 28, player.name ?? '—', {
|
const nameTxt = this.add.text(nameX, rowY - 28, player.name ?? '—', {
|
||||||
fontFamily: 'system-ui, sans-serif', fontSize: '22px', color: COLORS.textHex,
|
fontFamily: '"Julius Sans One"', fontSize: '22px', color: COLORS.textHex,
|
||||||
}).setOrigin(0, 0.5).setDepth(D.modal + 2).setAlpha(0);
|
}).setOrigin(0, 0.5).setDepth(D.modal + 2).setAlpha(0);
|
||||||
S.push(nameTxt);
|
S.push(nameTxt);
|
||||||
this.tweens.add({ targets: nameTxt, alpha: 1, duration: 350, delay });
|
this.tweens.add({ targets: nameTxt, alpha: 1, duration: 350, delay });
|
||||||
|
|
@ -1172,7 +1172,7 @@ export default class HoldemGame extends Phaser.Scene {
|
||||||
}
|
}
|
||||||
if (statusStr) {
|
if (statusStr) {
|
||||||
const statusTxt = this.add.text(nameX, rowY + 4, statusStr, {
|
const statusTxt = this.add.text(nameX, rowY + 4, statusStr, {
|
||||||
fontFamily: 'system-ui, sans-serif', fontSize: '18px', color: statusColor,
|
fontFamily: '"Julius Sans One"', fontSize: '18px', color: statusColor,
|
||||||
}).setOrigin(0, 0.5).setDepth(D.modal + 2).setAlpha(0);
|
}).setOrigin(0, 0.5).setDepth(D.modal + 2).setAlpha(0);
|
||||||
S.push(statusTxt);
|
S.push(statusTxt);
|
||||||
this.tweens.add({ targets: statusTxt, alpha: 1, duration: 350, delay });
|
this.tweens.add({ targets: statusTxt, alpha: 1, duration: 350, delay });
|
||||||
|
|
@ -1202,7 +1202,7 @@ export default class HoldemGame extends Phaser.Scene {
|
||||||
const won = winnings.get(player.seat) ?? 0;
|
const won = winnings.get(player.seat) ?? 0;
|
||||||
if (won > 0) {
|
if (won > 0) {
|
||||||
const wonTxt = this.add.text(790, rowY, `+$${won}`, {
|
const wonTxt = this.add.text(790, rowY, `+$${won}`, {
|
||||||
fontFamily: 'system-ui, sans-serif', fontSize: '28px',
|
fontFamily: '"Julius Sans One"', fontSize: '28px',
|
||||||
color: '#ffd700', fontStyle: 'bold',
|
color: '#ffd700', fontStyle: 'bold',
|
||||||
}).setOrigin(0, 0.5).setDepth(D.modal + 2).setAlpha(0);
|
}).setOrigin(0, 0.5).setDepth(D.modal + 2).setAlpha(0);
|
||||||
S.push(wonTxt);
|
S.push(wonTxt);
|
||||||
|
|
@ -1212,7 +1212,7 @@ export default class HoldemGame extends Phaser.Scene {
|
||||||
// Chip count
|
// Chip count
|
||||||
const chipStr = player.eliminated ? 'Eliminated' : `$${player.chips} chips`;
|
const chipStr = player.eliminated ? 'Eliminated' : `$${player.chips} chips`;
|
||||||
const chipTxt = this.add.text(1050, rowY, chipStr, {
|
const chipTxt = this.add.text(1050, rowY, chipStr, {
|
||||||
fontFamily: 'system-ui, sans-serif', fontSize: '18px', color: COLORS.mutedHex,
|
fontFamily: '"Julius Sans One"', fontSize: '18px', color: COLORS.mutedHex,
|
||||||
}).setOrigin(0, 0.5).setDepth(D.modal + 2).setAlpha(0);
|
}).setOrigin(0, 0.5).setDepth(D.modal + 2).setAlpha(0);
|
||||||
S.push(chipTxt);
|
S.push(chipTxt);
|
||||||
this.tweens.add({ targets: chipTxt, alpha: 1, duration: 350, delay });
|
this.tweens.add({ targets: chipTxt, alpha: 1, duration: 350, delay });
|
||||||
|
|
@ -1246,19 +1246,19 @@ export default class HoldemGame extends Phaser.Scene {
|
||||||
: '🎉 You win!';
|
: '🎉 You win!';
|
||||||
|
|
||||||
this.add.text(CX, CY - 140, headline, {
|
this.add.text(CX, CY - 140, headline, {
|
||||||
fontFamily: 'system-ui, sans-serif', fontSize: '40px',
|
fontFamily: 'Righteous', fontSize: '40px',
|
||||||
color: won ? '#ffd700' : COLORS.textHex,
|
color: won ? '#ffd700' : COLORS.textHex,
|
||||||
}).setOrigin(0.5).setDepth(D.modal);
|
}).setOrigin(0.5).setDepth(D.modal);
|
||||||
|
|
||||||
this.add.text(CX, CY - 70, won
|
this.add.text(CX, CY - 70, won
|
||||||
? `+$${net} profit`
|
? `+$${net} profit`
|
||||||
: net === 0 ? 'Broke even' : `-$${Math.abs(net)} loss`, {
|
: net === 0 ? 'Broke even' : `-$${Math.abs(net)} loss`, {
|
||||||
fontFamily: 'system-ui, sans-serif', fontSize: '28px',
|
fontFamily: '"Julius Sans One"', fontSize: '28px',
|
||||||
color: won ? '#5aa9e6' : COLORS.dangerHex,
|
color: won ? COLORS.accentHex : COLORS.dangerHex,
|
||||||
}).setOrigin(0.5).setDepth(D.modal);
|
}).setOrigin(0.5).setDepth(D.modal);
|
||||||
|
|
||||||
this.add.text(CX, CY - 20, `Your balance: $${this.globalChips.toLocaleString()}`, {
|
this.add.text(CX, CY - 20, `Your balance: $${this.globalChips.toLocaleString()}`, {
|
||||||
fontFamily: 'system-ui, sans-serif', fontSize: '22px', color: COLORS.mutedHex,
|
fontFamily: '"Julius Sans One"', fontSize: '22px', color: COLORS.mutedHex,
|
||||||
}).setOrigin(0.5).setDepth(D.modal);
|
}).setOrigin(0.5).setDepth(D.modal);
|
||||||
|
|
||||||
new Button(this, CX - 110, CY + 100, 'Play Again', () => {
|
new Button(this, CX - 110, CY + 100, 'Play Again', () => {
|
||||||
|
|
|
||||||
|
|
@ -52,19 +52,19 @@ function trackXY(idx) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function homeXY(color, idx) {
|
function homeXY(color, idx) {
|
||||||
if (color === 'red') return { col: 9, row: 17 - idx };
|
if (color === 'red') return { col: 1 + idx, row: 9 }; // west spoke
|
||||||
if (color === 'blue') return { col: 17 - idx, row: 9 };
|
if (color === 'blue') return { col: 9, row: 17 - idx }; // south spoke
|
||||||
if (color === 'yellow') return { col: 9, row: 1 + idx };
|
if (color === 'yellow') return { col: 17 - idx, row: 9 }; // east spoke
|
||||||
if (color === 'green') return { col: 1 + idx, row: 9 };
|
if (color === 'green') return { col: 9, row: 1 + idx }; // north spoke
|
||||||
throw new Error(`bad color ${color}`);
|
throw new Error(`bad color ${color}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Final "home" cell at the center boundary per color.
|
// Final "home" cell at the center boundary per color.
|
||||||
function homeFinalXY(color) {
|
function homeFinalXY(color) {
|
||||||
if (color === 'red') return { col: 9, row: 10 };
|
if (color === 'red') return { col: 8, row: 9 }; // west spoke
|
||||||
if (color === 'blue') return { col: 10, row: 9 };
|
if (color === 'blue') return { col: 9, row: 10 }; // south spoke
|
||||||
if (color === 'yellow') return { col: 9, row: 8 };
|
if (color === 'yellow') return { col: 10, row: 9 }; // east spoke
|
||||||
if (color === 'green') return { col: 8, row: 9 };
|
if (color === 'green') return { col: 9, row: 8 }; // north spoke
|
||||||
}
|
}
|
||||||
|
|
||||||
function cellWorld(col, row) {
|
function cellWorld(col, row) {
|
||||||
|
|
@ -146,6 +146,9 @@ export default class ParchisiGame extends Phaser.Scene {
|
||||||
this.turnIndicator = null;
|
this.turnIndicator = null;
|
||||||
this.turnIndicatorGfx = null;
|
this.turnIndicatorGfx = null;
|
||||||
this.turnIndicatorPulseTween = null;
|
this.turnIndicatorPulseTween = null;
|
||||||
|
this.turnIndicatorMoveTween = null;
|
||||||
|
this.bonusChip20 = null;
|
||||||
|
this._bonusChip20Visible = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
create() {
|
create() {
|
||||||
|
|
@ -153,6 +156,7 @@ export default class ParchisiGame extends Phaser.Scene {
|
||||||
this.buildBoard();
|
this.buildBoard();
|
||||||
this.buildDice();
|
this.buildDice();
|
||||||
this.buildUI();
|
this.buildUI();
|
||||||
|
this.buildBonusChip();
|
||||||
this.buildPlayerCards();
|
this.buildPlayerCards();
|
||||||
this.buildTurnIndicator();
|
this.buildTurnIndicator();
|
||||||
this.buildPawns();
|
this.buildPawns();
|
||||||
|
|
@ -229,6 +233,7 @@ export default class ParchisiGame extends Phaser.Scene {
|
||||||
if (isSafeTrack(idx) && !entryColor) {
|
if (isSafeTrack(idx) && !entryColor) {
|
||||||
this.drawStar(g, wp.x, wp.y, 5, 10, 5, 0x2b5d80, 0.7);
|
this.drawStar(g, wp.x, wp.y, 5, 10, 5, 0x2b5d80, 0.7);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
drawHomeColumn(color) {
|
drawHomeColumn(color) {
|
||||||
|
|
@ -263,7 +268,7 @@ export default class ParchisiGame extends Phaser.Scene {
|
||||||
g.lineStyle(2, TRACK_STROKE, 0.9);
|
g.lineStyle(2, TRACK_STROKE, 0.9);
|
||||||
g.strokeCircle(cx, cy, r);
|
g.strokeCircle(cx, cy, r);
|
||||||
this.add.text(cx, cy, 'HOME', {
|
this.add.text(cx, cy, 'HOME', {
|
||||||
fontFamily: 'system-ui, sans-serif',
|
fontFamily: '"Julius Sans One"',
|
||||||
fontSize: '16px',
|
fontSize: '16px',
|
||||||
color: '#3a2010',
|
color: '#3a2010',
|
||||||
fontStyle: 'bold',
|
fontStyle: 'bold',
|
||||||
|
|
@ -287,7 +292,7 @@ export default class ParchisiGame extends Phaser.Scene {
|
||||||
// ── Dice ──────────────────────────────────────────────────────────────────
|
// ── Dice ──────────────────────────────────────────────────────────────────
|
||||||
buildDice() {
|
buildDice() {
|
||||||
const baseX = ORIGIN_X - 80;
|
const baseX = ORIGIN_X - 80;
|
||||||
const baseY = GAME_HEIGHT / 2 - 150;
|
const baseY = GAME_HEIGHT / 2 - 80;
|
||||||
for (let i = 0; i < 2; i++) {
|
for (let i = 0; i < 2; i++) {
|
||||||
const g = this.add.graphics();
|
const g = this.add.graphics();
|
||||||
const container = this.add.container(baseX, baseY + i * 80).setDepth(DEPTH.dice);
|
const container = this.add.container(baseX, baseY + i * 80).setDepth(DEPTH.dice);
|
||||||
|
|
@ -344,6 +349,7 @@ export default class ParchisiGame extends Phaser.Scene {
|
||||||
}
|
}
|
||||||
|
|
||||||
updateDiceDisplay() {
|
updateDiceDisplay() {
|
||||||
|
this.updateBonusChip();
|
||||||
if (!this.gs.dice) {
|
if (!this.gs.dice) {
|
||||||
this.diceContainers.forEach((c) => c.setAlpha(0.25));
|
this.diceContainers.forEach((c) => c.setAlpha(0.25));
|
||||||
return;
|
return;
|
||||||
|
|
@ -364,27 +370,66 @@ export default class ParchisiGame extends Phaser.Scene {
|
||||||
// ── UI / Portraits ────────────────────────────────────────────────────────
|
// ── UI / Portraits ────────────────────────────────────────────────────────
|
||||||
buildUI() {
|
buildUI() {
|
||||||
const xLeft = ORIGIN_X - 80;
|
const xLeft = ORIGIN_X - 80;
|
||||||
const yRoll = GAME_HEIGHT / 2 + 10;
|
const yRoll = GAME_HEIGHT / 2 + 80;
|
||||||
this.rollBtn = new Button(this, xLeft, yRoll, 'Roll', () => this.onRollClick(), {
|
this.rollBtn = new Button(this, xLeft, yRoll, 'Roll', () => this.onRollClick(), {
|
||||||
width: 110, height: 44, fontSize: 22,
|
width: 110, height: 44, fontSize: 22,
|
||||||
});
|
});
|
||||||
this.rollBtn.setDepth(DEPTH.ui);
|
this.rollBtn.setDepth(DEPTH.ui);
|
||||||
|
|
||||||
new Button(this, xLeft, yRoll + 70, 'New', () => this.initGame(), {
|
new Button(this, 80, GAME_HEIGHT - 70, 'New', () => this.initGame(), {
|
||||||
variant: 'ghost', width: 110, height: 40, fontSize: 18,
|
variant: 'ghost', width: 110, height: 40, fontSize: 18,
|
||||||
}).setDepth(DEPTH.ui);
|
}).setDepth(DEPTH.ui);
|
||||||
|
|
||||||
new Button(this, xLeft, yRoll + 120, 'Leave', () => this.scene.start('GameMenu'), {
|
new Button(this, 80, GAME_HEIGHT - 25, 'Leave', () => this.scene.start('GameMenu'), {
|
||||||
variant: 'ghost', width: 110, height: 40, fontSize: 18,
|
variant: 'ghost', width: 110, height: 40, fontSize: 18,
|
||||||
}).setDepth(DEPTH.ui);
|
}).setDepth(DEPTH.ui);
|
||||||
|
|
||||||
this.statusText = this.add.text(GAME_WIDTH / 2, GAME_HEIGHT - 30, '', {
|
this.statusText = this.add.text(GAME_WIDTH / 2, GAME_HEIGHT - 30, '', {
|
||||||
fontFamily: 'system-ui, sans-serif',
|
fontFamily: '"Julius Sans One"',
|
||||||
fontSize: '22px',
|
fontSize: '22px',
|
||||||
color: UI.textHex,
|
color: UI.textHex,
|
||||||
}).setOrigin(0.5).setDepth(DEPTH.ui);
|
}).setOrigin(0.5).setDepth(DEPTH.ui);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
buildBonusChip() {
|
||||||
|
const x = ORIGIN_X - 155;
|
||||||
|
const y = GAME_HEIGHT / 2 - 40;
|
||||||
|
const R = 26;
|
||||||
|
const gfx = this.add.graphics();
|
||||||
|
gfx.fillStyle(0xffffff, 1);
|
||||||
|
gfx.fillCircle(0, 0, R);
|
||||||
|
gfx.lineStyle(3, 0x222222, 1);
|
||||||
|
gfx.strokeCircle(0, 0, R);
|
||||||
|
const label = this.add.text(0, 0, '20', {
|
||||||
|
fontFamily: '"Julius Sans One"',
|
||||||
|
fontSize: '20px',
|
||||||
|
fontStyle: 'bold',
|
||||||
|
color: '#111111',
|
||||||
|
}).setOrigin(0.5);
|
||||||
|
this.bonusChip20 = this.add.container(x, y, [gfx, label])
|
||||||
|
.setDepth(DEPTH.ui + 3)
|
||||||
|
.setAlpha(0)
|
||||||
|
.setScale(0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
updateBonusChip() {
|
||||||
|
const should = !!(this.gs?.movesLeft?.includes(20));
|
||||||
|
if (should === this._bonusChip20Visible) return;
|
||||||
|
this._bonusChip20Visible = should;
|
||||||
|
this.tweens.killTweensOf(this.bonusChip20);
|
||||||
|
if (should) {
|
||||||
|
this.tweens.add({
|
||||||
|
targets: this.bonusChip20, alpha: 1, scale: 1,
|
||||||
|
duration: 250, ease: 'Back.easeOut',
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
this.tweens.add({
|
||||||
|
targets: this.bonusChip20, alpha: 0, scale: 0.3,
|
||||||
|
duration: 180, ease: 'Cubic.easeIn',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
buildPlayerCards() {
|
buildPlayerCards() {
|
||||||
const portraitR = 56;
|
const portraitR = 56;
|
||||||
// Left nests (red, green): col0=0, so left edge is ORIGIN_X
|
// Left nests (red, green): col0=0, so left edge is ORIGIN_X
|
||||||
|
|
@ -397,7 +442,7 @@ export default class ParchisiGame extends Phaser.Scene {
|
||||||
this.add.circle(xLeft, plY, portraitR + 5, COLOR_HEX.red.fill, 0.6).setDepth(DEPTH.ui);
|
this.add.circle(xLeft, plY, portraitR + 5, COLOR_HEX.red.fill, 0.6).setDepth(DEPTH.ui);
|
||||||
createPlayerPortrait(this, xLeft, plY, portraitR, DEPTH.ui + 1, 'ParchisiGame');
|
createPlayerPortrait(this, xLeft, plY, portraitR, DEPTH.ui + 1, 'ParchisiGame');
|
||||||
this.add.text(xLeft, plY + portraitR + 14, auth.user?.username ?? 'You', {
|
this.add.text(xLeft, plY + portraitR + 14, auth.user?.username ?? 'You', {
|
||||||
fontFamily: 'system-ui, sans-serif', fontSize: '16px', color: UI.textHex,
|
fontFamily: '"Julius Sans One"', fontSize: '16px', color: UI.textHex,
|
||||||
}).setOrigin(0.5, 0).setDepth(DEPTH.ui + 2);
|
}).setOrigin(0.5, 0).setDepth(DEPTH.ui + 2);
|
||||||
|
|
||||||
// AI opponents: blue (right/bottom), yellow (right/top), green (left/top)
|
// AI opponents: blue (right/bottom), yellow (right/top), green (left/top)
|
||||||
|
|
@ -410,7 +455,7 @@ export default class ParchisiGame extends Phaser.Scene {
|
||||||
this.add.circle(x, y, portraitR + 5, COLOR_HEX[color].fill, 0.6).setDepth(DEPTH.ui);
|
this.add.circle(x, y, portraitR + 5, COLOR_HEX[color].fill, 0.6).setDepth(DEPTH.ui);
|
||||||
this.opponentPortraits[color] = createOpponentPortrait(this, opp, x, y, portraitR, DEPTH.ui + 1);
|
this.opponentPortraits[color] = createOpponentPortrait(this, opp, x, y, portraitR, DEPTH.ui + 1);
|
||||||
this.add.text(x, y + portraitR + 12, opp.name ?? color, {
|
this.add.text(x, y + portraitR + 12, opp.name ?? color, {
|
||||||
fontFamily: 'system-ui, sans-serif', fontSize: '16px', color: UI.textHex,
|
fontFamily: '"Julius Sans One"', fontSize: '16px', color: UI.textHex,
|
||||||
}).setOrigin(0.5, 0).setDepth(DEPTH.ui + 2);
|
}).setOrigin(0.5, 0).setDepth(DEPTH.ui + 2);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
@ -481,29 +526,37 @@ export default class ParchisiGame extends Phaser.Scene {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
moveTurnIndicator(color, immediately = false) {
|
moveTurnIndicator(color, immediately = false, onArrive = null) {
|
||||||
const { x, y, side } = this._indicatorPos(color);
|
const { x, y, side } = this._indicatorPos(color);
|
||||||
|
// Stop both running tweens before starting new ones
|
||||||
if (this.turnIndicatorPulseTween) {
|
if (this.turnIndicatorPulseTween) {
|
||||||
this.turnIndicatorPulseTween.stop();
|
this.turnIndicatorPulseTween.stop();
|
||||||
this.turnIndicatorPulseTween = null;
|
this.turnIndicatorPulseTween = null;
|
||||||
}
|
}
|
||||||
|
if (this.turnIndicatorMoveTween) {
|
||||||
|
this.turnIndicatorMoveTween.stop();
|
||||||
|
this.turnIndicatorMoveTween = null;
|
||||||
|
}
|
||||||
this.turnIndicator.setScale(1);
|
this.turnIndicator.setScale(1);
|
||||||
|
|
||||||
if (immediately || this.turnIndicator.alpha === 0) {
|
if (immediately || this.turnIndicator.alpha === 0) {
|
||||||
this._drawTurnTriangle(side);
|
this._drawTurnTriangle(side);
|
||||||
this.turnIndicator.setPosition(x, y).setAlpha(1);
|
this.turnIndicator.setPosition(x, y).setAlpha(1);
|
||||||
this._startIndicatorPulse();
|
this._startIndicatorPulse();
|
||||||
|
onArrive?.();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.tweens.add({
|
this.turnIndicatorMoveTween = this.tweens.add({
|
||||||
targets: this.turnIndicator,
|
targets: this.turnIndicator,
|
||||||
x, y,
|
x, y,
|
||||||
duration: 550,
|
duration: 400,
|
||||||
ease: 'Cubic.easeInOut',
|
ease: 'Cubic.easeInOut',
|
||||||
onComplete: () => {
|
onComplete: () => {
|
||||||
|
this.turnIndicatorMoveTween = null;
|
||||||
this._drawTurnTriangle(side);
|
this._drawTurnTriangle(side);
|
||||||
this._startIndicatorPulse();
|
this._startIndicatorPulse();
|
||||||
|
onArrive?.();
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
@ -749,13 +802,16 @@ export default class ParchisiGame extends Phaser.Scene {
|
||||||
|
|
||||||
afterTurn() {
|
afterTurn() {
|
||||||
this.updateButtons();
|
this.updateButtons();
|
||||||
this.moveTurnIndicator(this.gs.currentPlayer);
|
|
||||||
if (this.gs.currentPlayer === 'red') {
|
if (this.gs.currentPlayer === 'red') {
|
||||||
|
this.moveTurnIndicator('red');
|
||||||
this.setStatus('Your turn — roll the dice');
|
this.setStatus('Your turn — roll the dice');
|
||||||
} else {
|
} else {
|
||||||
const opp = this.opponents[['blue', 'yellow', 'green'].indexOf(this.gs.currentPlayer)];
|
const opp = this.opponents[['blue', 'yellow', 'green'].indexOf(this.gs.currentPlayer)];
|
||||||
this.setStatus(`${opp?.name ?? this.gs.currentPlayer}'s turn`);
|
this.setStatus(`${opp?.name ?? this.gs.currentPlayer}'s turn`);
|
||||||
this.time.delayedCall(700, () => this.runAITurn());
|
// Start AI dice roll only after the indicator has finished travelling
|
||||||
|
this.moveTurnIndicator(this.gs.currentPlayer, false, () => {
|
||||||
|
this.time.delayedCall(250, () => this.runAITurn());
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -815,11 +871,6 @@ export default class ParchisiGame extends Phaser.Scene {
|
||||||
if (i >= moves.length || this.gs.phase === 'game_over') {
|
if (i >= moves.length || this.gs.phase === 'game_over') {
|
||||||
this.animating = false;
|
this.animating = false;
|
||||||
if (this.gs.phase === 'game_over') { this.onGameOver(); return; }
|
if (this.gs.phase === 'game_over') { this.onGameOver(); return; }
|
||||||
// If AI got another roll (doubles), continue
|
|
||||||
if (this.gs.phase === 'roll' && this.gs.currentPlayer !== 'red') {
|
|
||||||
this.time.delayedCall(700, () => this.runAITurn());
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.afterTurn();
|
this.afterTurn();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -935,7 +986,7 @@ export default class ParchisiGame extends Phaser.Scene {
|
||||||
? '🎉 You Win!\nAll four pawns home!'
|
? '🎉 You Win!\nAll four pawns home!'
|
||||||
: `${oppName} wins this round.\nBetter luck next game!`;
|
: `${oppName} wins this round.\nBetter luck next game!`;
|
||||||
const txt = this.add.text(GAME_WIDTH / 2, GAME_HEIGHT / 2 - 50, msg, {
|
const txt = this.add.text(GAME_WIDTH / 2, GAME_HEIGHT / 2 - 50, msg, {
|
||||||
fontFamily: 'system-ui, sans-serif',
|
fontFamily: '"Julius Sans One"',
|
||||||
fontSize: '32px',
|
fontSize: '32px',
|
||||||
color: isHuman ? '#ffd700' : UI.textHex,
|
color: isHuman ? '#ffd700' : UI.textHex,
|
||||||
align: 'center',
|
align: 'center',
|
||||||
|
|
|
||||||
|
|
@ -18,20 +18,20 @@
|
||||||
|
|
||||||
export const COLORS = ['red', 'blue', 'yellow', 'green'];
|
export const COLORS = ['red', 'blue', 'yellow', 'green'];
|
||||||
|
|
||||||
export const ENTRY = { red: 0, blue: 17, yellow: 34, green: 51 };
|
export const ENTRY = { red: 11, blue: 62, yellow: 45, green: 28 };
|
||||||
// Home-entry = the LAST outer-track square a pawn occupies before turning
|
// Home-entry = the LAST outer-track square a pawn occupies before turning
|
||||||
// into its home column. = (entry + 67) mod 68 = (entry - 1 + 68) mod 68.
|
// into its home column. Movement is CCW (track index decrements).
|
||||||
export const HOME_ENTRY = { red: 67, blue: 16, yellow: 33, green: 50 };
|
export const HOME_ENTRY = { red: 16, blue: 67, yellow: 50, green: 33 };
|
||||||
|
|
||||||
export const TRACK_LEN = 68;
|
export const TRACK_LEN = 68;
|
||||||
export const HOME_COL_LEN = 7;
|
export const HOME_COL_LEN = 7;
|
||||||
export const PAWNS_PER_PLAYER = 4;
|
export const PAWNS_PER_PLAYER = 4;
|
||||||
|
|
||||||
const SAFE_SET = new Set([
|
const SAFE_SET = new Set([
|
||||||
0, 7, 12,
|
4, 11, 16,
|
||||||
17, 24, 29,
|
21, 28, 33,
|
||||||
34, 41, 46,
|
38, 45, 50,
|
||||||
51, 58, 63,
|
55, 62, 67,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
export function isSafeTrack(idx) {
|
export function isSafeTrack(idx) {
|
||||||
|
|
@ -139,7 +139,7 @@ export function pawnDistanceToHome(pawn, color) {
|
||||||
if (pawn.track !== undefined) {
|
if (pawn.track !== undefined) {
|
||||||
// Distance from track t to home-entry, then +7 (cols) + 1 (home circle) = +8.
|
// Distance from track t to home-entry, then +7 (cols) + 1 (home circle) = +8.
|
||||||
const homeEntry = HOME_ENTRY[color];
|
const homeEntry = HOME_ENTRY[color];
|
||||||
const d = (homeEntry - pawn.track + TRACK_LEN) % TRACK_LEN;
|
const d = (pawn.track - homeEntry + TRACK_LEN) % TRACK_LEN;
|
||||||
return d + 8;
|
return d + 8;
|
||||||
}
|
}
|
||||||
return 0;
|
return 0;
|
||||||
|
|
@ -221,7 +221,7 @@ function projectPath(from, color, steps) {
|
||||||
pos = { home: 0 };
|
pos = { home: 0 };
|
||||||
path.push(pos);
|
path.push(pos);
|
||||||
} else {
|
} else {
|
||||||
const nextIdx = (pos.track + 1) % TRACK_LEN;
|
const nextIdx = (pos.track - 1 + TRACK_LEN) % TRACK_LEN;
|
||||||
pos = { track: nextIdx };
|
pos = { track: nextIdx };
|
||||||
path.push(pos);
|
path.push(pos);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -10,11 +10,14 @@ export default class GameMenuScene extends Phaser.Scene {
|
||||||
async create() {
|
async create() {
|
||||||
const cx = GAME_WIDTH / 2;
|
const cx = GAME_WIDTH / 2;
|
||||||
|
|
||||||
this.add.text(cx, 120, 'Choose a game', {
|
this.add.image(cx, GAME_HEIGHT / 2, 'bg-menu').setDisplaySize(GAME_WIDTH, GAME_HEIGHT);
|
||||||
fontFamily: 'system-ui, sans-serif',
|
|
||||||
|
const titleText = this.add.text(cx, 120, 'Choose a game', {
|
||||||
|
fontFamily: 'Righteous',
|
||||||
fontSize: '64px',
|
fontSize: '64px',
|
||||||
color: COLORS.textHex,
|
color: COLORS.textHex,
|
||||||
}).setOrigin(0.5);
|
}).setOrigin(0.5).setDepth(1);
|
||||||
|
this.add.rectangle(cx, 120, titleText.width + 64, titleText.height + 28, 0x000000, 0.7);
|
||||||
|
|
||||||
const loadingText = this.add.text(cx, 220, 'Loading game list…', {
|
const loadingText = this.add.text(cx, 220, 'Loading game list…', {
|
||||||
fontSize: '24px', color: COLORS.mutedHex,
|
fontSize: '24px', color: COLORS.mutedHex,
|
||||||
|
|
@ -40,17 +43,19 @@ export default class GameMenuScene extends Phaser.Scene {
|
||||||
}
|
}
|
||||||
|
|
||||||
renderColumn(title, games, x, y) {
|
renderColumn(title, games, x, y) {
|
||||||
|
const panelTop = y - 44;
|
||||||
|
const panelBot = games.length > 0 ? y + 80 + (games.length - 1) * 90 + 56 : y + 56;
|
||||||
|
const panelH = panelBot - panelTop;
|
||||||
|
this.add.rectangle(x, panelTop + panelH / 2, 420, panelH, 0x000000, 0.7);
|
||||||
|
|
||||||
this.add.text(x, y, title, {
|
this.add.text(x, y, title, {
|
||||||
fontFamily: 'system-ui, sans-serif',
|
fontFamily: 'Righteous',
|
||||||
fontSize: '40px',
|
fontSize: '40px',
|
||||||
color: COLORS.accentHex,
|
color: COLORS.accentHex,
|
||||||
}).setOrigin(0.5);
|
}).setOrigin(0.5);
|
||||||
|
|
||||||
games.forEach((game, i) => {
|
games.forEach((game, i) => {
|
||||||
const btn = new Button(this, x, y + 80 + i * 90, game.name, () => this.openGame(game), {
|
new Button(this, x, y + 80 + i * 90, game.name, () => this.openGame(game), { width: 360 });
|
||||||
width: 360,
|
|
||||||
});
|
|
||||||
void btn;
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -31,7 +31,7 @@ export default class GameRoomScene extends Phaser.Scene {
|
||||||
const cx = GAME_WIDTH / 2;
|
const cx = GAME_WIDTH / 2;
|
||||||
|
|
||||||
this.add.text(cx, 80, `${this.game.name}`, {
|
this.add.text(cx, 80, `${this.game.name}`, {
|
||||||
fontFamily: 'system-ui, sans-serif',
|
fontFamily: 'Righteous',
|
||||||
fontSize: '52px',
|
fontSize: '52px',
|
||||||
color: COLORS.textHex,
|
color: COLORS.textHex,
|
||||||
}).setOrigin(0.5);
|
}).setOrigin(0.5);
|
||||||
|
|
@ -44,7 +44,7 @@ export default class GameRoomScene extends Phaser.Scene {
|
||||||
// Placeholder table felt
|
// Placeholder table felt
|
||||||
this.add.rectangle(cx, GAME_HEIGHT / 2 + 40, 1400, 700, 0x14532d).setStrokeStyle(4, COLORS.accent);
|
this.add.rectangle(cx, GAME_HEIGHT / 2 + 40, 1400, 700, 0x14532d).setStrokeStyle(4, COLORS.accent);
|
||||||
this.add.text(cx, GAME_HEIGHT / 2 + 40, `${this.game.name} board placeholder\n(game logic plugs in here)`, {
|
this.add.text(cx, GAME_HEIGHT / 2 + 40, `${this.game.name} board placeholder\n(game logic plugs in here)`, {
|
||||||
fontFamily: 'system-ui, sans-serif',
|
fontFamily: '"Julius Sans One"',
|
||||||
fontSize: '28px',
|
fontSize: '28px',
|
||||||
color: COLORS.mutedHex,
|
color: COLORS.mutedHex,
|
||||||
align: 'center',
|
align: 'center',
|
||||||
|
|
|
||||||
|
|
@ -9,19 +9,29 @@ export default class LandingScene extends Phaser.Scene {
|
||||||
create() {
|
create() {
|
||||||
const cx = GAME_WIDTH / 2;
|
const cx = GAME_WIDTH / 2;
|
||||||
|
|
||||||
this.add.rectangle(cx, GAME_HEIGHT / 2, GAME_WIDTH, GAME_HEIGHT, COLORS.bg);
|
this.add.image(cx, GAME_HEIGHT / 2, 'bg-menu').setDisplaySize(GAME_WIDTH, GAME_HEIGHT);
|
||||||
|
|
||||||
this.add.text(cx, 220, 'Fertig Classic Games', {
|
const logo = this.add.image(cx, 290, 'main-title').setOrigin(0.5, 0.5).setAlpha(0);
|
||||||
fontFamily: 'Georgia, "Times New Roman", serif',
|
logo.postFX.addShadow(3, 6, 0.005, 2, 0x000000, 10, 0.75);
|
||||||
fontSize: '96px',
|
|
||||||
color: COLORS.textHex,
|
|
||||||
}).setOrigin(0.5);
|
|
||||||
|
|
||||||
this.add.text(cx, 320, 'Cards, dice, and classic tables.', {
|
this.tweens.add({
|
||||||
fontFamily: 'system-ui, sans-serif',
|
targets: logo,
|
||||||
fontSize: '32px',
|
alpha: 1,
|
||||||
color: COLORS.mutedHex,
|
y: 260,
|
||||||
}).setOrigin(0.5);
|
duration: 900,
|
||||||
|
ease: 'Power2',
|
||||||
|
onComplete: () => {
|
||||||
|
this.tweens.add({
|
||||||
|
targets: logo,
|
||||||
|
scaleX: 1.025,
|
||||||
|
scaleY: 1.025,
|
||||||
|
duration: 2200,
|
||||||
|
yoyo: true,
|
||||||
|
repeat: -1,
|
||||||
|
ease: 'Sine.easeInOut',
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
this.renderButtons();
|
this.renderButtons();
|
||||||
|
|
||||||
|
|
@ -37,29 +47,66 @@ export default class LandingScene extends Phaser.Scene {
|
||||||
const user = auth.user;
|
const user = auth.user;
|
||||||
|
|
||||||
if (user) {
|
if (user) {
|
||||||
this.add.text(cx, 480, `Welcome back, ${user.username}`, {
|
const avatarR = 28;
|
||||||
fontFamily: 'system-ui, sans-serif',
|
const avatarGap = 18;
|
||||||
|
const pad = { x: 32, y: 14 };
|
||||||
|
const y = 630;
|
||||||
|
|
||||||
|
const welcomeText = this.add.text(0, y, `Welcome back, ${user.displayName ?? user.username}`, {
|
||||||
|
fontFamily: 'Righteous',
|
||||||
fontSize: '36px',
|
fontSize: '36px',
|
||||||
color: COLORS.accentHex,
|
color: COLORS.accentHex,
|
||||||
}).setOrigin(0.5);
|
}).setOrigin(0.5).setDepth(1);
|
||||||
|
|
||||||
|
const hasAvatar = !!user.avatarPath;
|
||||||
|
const totalW = hasAvatar ? avatarR * 2 + avatarGap + welcomeText.width : welcomeText.width;
|
||||||
|
const groupLeft = cx - totalW / 2;
|
||||||
|
|
||||||
|
welcomeText.setX(hasAvatar ? groupLeft + avatarR * 2 + avatarGap + welcomeText.width / 2 : cx);
|
||||||
|
|
||||||
|
this.add.rectangle(cx, y, totalW + pad.x * 2, welcomeText.height + pad.y * 2, 0x000000, 0.45);
|
||||||
|
|
||||||
|
if (hasAvatar) {
|
||||||
|
const avatarCx = groupLeft + avatarR;
|
||||||
|
const key = `landing-avatar-${user.id}`;
|
||||||
|
(async () => {
|
||||||
|
try {
|
||||||
|
if (!this.textures.exists(key)) {
|
||||||
|
await new Promise((resolve) => {
|
||||||
|
this.load.image(key, user.avatarPath);
|
||||||
|
this.load.once('complete', resolve);
|
||||||
|
this.load.start();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (!this.scene.isActive('Landing')) return;
|
||||||
|
const maskG = this.make.graphics({ x: 0, y: 0, add: false });
|
||||||
|
maskG.fillStyle(0xffffff);
|
||||||
|
maskG.fillCircle(avatarCx, y, avatarR);
|
||||||
|
this.add.image(avatarCx, y, key)
|
||||||
|
.setDisplaySize(avatarR * 2, avatarR * 2)
|
||||||
|
.setMask(maskG.createGeometryMask())
|
||||||
|
.setDepth(1);
|
||||||
|
} catch { /* no avatar shown */ }
|
||||||
|
})();
|
||||||
|
}
|
||||||
|
|
||||||
if (!user.emailVerified) {
|
if (!user.emailVerified) {
|
||||||
this.add.text(cx, 540, 'Email not yet verified — check the server console for the link in dev.', {
|
this.add.text(cx, 690, 'Email not yet verified — check the server console for the link in dev.', {
|
||||||
fontFamily: 'system-ui, sans-serif',
|
fontFamily: '"Julius Sans One"',
|
||||||
fontSize: '22px',
|
fontSize: '22px',
|
||||||
color: COLORS.dangerHex,
|
color: COLORS.dangerHex,
|
||||||
}).setOrigin(0.5);
|
}).setOrigin(0.5);
|
||||||
}
|
}
|
||||||
|
|
||||||
new Button(this, cx, 660, 'Play', () => this.scene.start('GameMenu'));
|
new Button(this, cx, 810, 'Play', () => this.scene.start('GameMenu'));
|
||||||
new Button(this, cx, 740, 'Profile', () => this.scene.start('Profile'));
|
new Button(this, cx, 890, 'Profile', () => this.scene.start('Profile'));
|
||||||
new Button(this, cx, 820, 'Sign out', async () => {
|
new Button(this, cx, 970, 'Sign out', async () => {
|
||||||
await auth.logout();
|
await auth.logout();
|
||||||
}, { variant: 'ghost' });
|
}, { variant: 'ghost' });
|
||||||
} else {
|
} else {
|
||||||
new Button(this, cx, 540, 'Sign in', () => this.scene.start('Login'));
|
new Button(this, cx, 690, 'Sign in', () => this.scene.start('Login'));
|
||||||
new Button(this, cx, 620, 'Create account', () => this.scene.start('Register'));
|
new Button(this, cx, 770, 'Create account', () => this.scene.start('Register'));
|
||||||
new Button(this, cx, 700, 'Continue as guest', () => this.scene.start('GameMenu'), { variant: 'ghost' });
|
new Button(this, cx, 850, 'Continue as guest', () => this.scene.start('GameMenu'), { variant: 'ghost' });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -13,13 +13,13 @@ export default class LobbyScene extends Phaser.Scene {
|
||||||
const cx = GAME_WIDTH / 2;
|
const cx = GAME_WIDTH / 2;
|
||||||
|
|
||||||
this.add.text(cx, 100, `${this.game.name} — Lobby`, {
|
this.add.text(cx, 100, `${this.game.name} — Lobby`, {
|
||||||
fontFamily: 'system-ui, sans-serif',
|
fontFamily: 'Righteous',
|
||||||
fontSize: '52px',
|
fontSize: '52px',
|
||||||
color: COLORS.textHex,
|
color: COLORS.textHex,
|
||||||
}).setOrigin(0.5);
|
}).setOrigin(0.5);
|
||||||
|
|
||||||
this.listText = this.add.text(cx, 220, 'Connecting…', {
|
this.listText = this.add.text(cx, 220, 'Connecting…', {
|
||||||
fontFamily: 'system-ui, sans-serif',
|
fontFamily: '"Julius Sans One"',
|
||||||
fontSize: '24px',
|
fontSize: '24px',
|
||||||
color: COLORS.mutedHex,
|
color: COLORS.mutedHex,
|
||||||
}).setOrigin(0.5);
|
}).setOrigin(0.5);
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,7 @@ export default class LoginScene extends Phaser.Scene {
|
||||||
const cx = GAME_WIDTH / 2;
|
const cx = GAME_WIDTH / 2;
|
||||||
|
|
||||||
this.add.text(cx, 200, 'Sign in', {
|
this.add.text(cx, 200, 'Sign in', {
|
||||||
fontFamily: 'system-ui, sans-serif',
|
fontFamily: 'Righteous',
|
||||||
fontSize: '64px',
|
fontSize: '64px',
|
||||||
color: COLORS.textHex,
|
color: COLORS.textHex,
|
||||||
}).setOrigin(0.5);
|
}).setOrigin(0.5);
|
||||||
|
|
|
||||||
|
|
@ -35,13 +35,13 @@ export default class OpponentSelectScene extends Phaser.Scene {
|
||||||
const cx = GAME_WIDTH / 2;
|
const cx = GAME_WIDTH / 2;
|
||||||
|
|
||||||
this.add.text(cx, 60, this.gameDef.name, {
|
this.add.text(cx, 60, this.gameDef.name, {
|
||||||
fontFamily: 'system-ui, sans-serif',
|
fontFamily: 'Righteous',
|
||||||
fontSize: '52px',
|
fontSize: '52px',
|
||||||
color: COLORS.textHex,
|
color: COLORS.textHex,
|
||||||
}).setOrigin(0.5);
|
}).setOrigin(0.5);
|
||||||
|
|
||||||
this.add.text(cx, 122, 'Choose your opponent', {
|
this.add.text(cx, 122, 'Choose your opponent', {
|
||||||
fontFamily: 'system-ui, sans-serif',
|
fontFamily: 'Righteous',
|
||||||
fontSize: '36px',
|
fontSize: '36px',
|
||||||
color: COLORS.mutedHex,
|
color: COLORS.mutedHex,
|
||||||
}).setOrigin(0.5);
|
}).setOrigin(0.5);
|
||||||
|
|
@ -185,7 +185,7 @@ export default class OpponentSelectScene extends Phaser.Scene {
|
||||||
ctx.fillStyle = COLORS.panelHex;
|
ctx.fillStyle = COLORS.panelHex;
|
||||||
ctx.fillRect(0, 0, portraitSize, portraitSize);
|
ctx.fillRect(0, 0, portraitSize, portraitSize);
|
||||||
ctx.fillStyle = COLORS.accentHex;
|
ctx.fillStyle = COLORS.accentHex;
|
||||||
ctx.font = `bold ${Math.round(r * 0.8)}px system-ui,sans-serif`;
|
ctx.font = `bold ${Math.round(r * 0.8)}px Righteous,sans-serif`;
|
||||||
ctx.textAlign = 'center';
|
ctx.textAlign = 'center';
|
||||||
ctx.textBaseline = 'middle';
|
ctx.textBaseline = 'middle';
|
||||||
ctx.fillText((opp.name ?? '?').charAt(0).toUpperCase(), r, r);
|
ctx.fillText((opp.name ?? '?').charAt(0).toUpperCase(), r, r);
|
||||||
|
|
@ -215,7 +215,7 @@ export default class OpponentSelectScene extends Phaser.Scene {
|
||||||
const name = document.createElement('div');
|
const name = document.createElement('div');
|
||||||
name.textContent = opp.name ?? '';
|
name.textContent = opp.name ?? '';
|
||||||
name.style.cssText = [
|
name.style.cssText = [
|
||||||
'font-family:system-ui,sans-serif',
|
'font-family:"Julius Sans One"',
|
||||||
'font-size:22px',
|
'font-size:22px',
|
||||||
'font-weight:600',
|
'font-weight:600',
|
||||||
`color:${COLORS.textHex}`,
|
`color:${COLORS.textHex}`,
|
||||||
|
|
@ -227,7 +227,7 @@ export default class OpponentSelectScene extends Phaser.Scene {
|
||||||
const bio = document.createElement('div');
|
const bio = document.createElement('div');
|
||||||
bio.textContent = opp.bio ?? '';
|
bio.textContent = opp.bio ?? '';
|
||||||
bio.style.cssText = [
|
bio.style.cssText = [
|
||||||
'font-family:system-ui,sans-serif',
|
'font-family:"Julius Sans One"',
|
||||||
'font-size:15px',
|
'font-size:15px',
|
||||||
`color:${COLORS.mutedHex}`,
|
`color:${COLORS.mutedHex}`,
|
||||||
'line-height:1.4',
|
'line-height:1.4',
|
||||||
|
|
@ -294,7 +294,7 @@ export default class OpponentSelectScene extends Phaser.Scene {
|
||||||
buildOptionSection(label, labelY, items, selectedProp, tilesProp, onSelect, tileW = TILE_W, tileH = TILE_H, tileGap = TILE_GAP) {
|
buildOptionSection(label, labelY, items, selectedProp, tilesProp, onSelect, tileW = TILE_W, tileH = TILE_H, tileGap = TILE_GAP) {
|
||||||
const cx = GAME_WIDTH / 2;
|
const cx = GAME_WIDTH / 2;
|
||||||
this.add.text(cx, labelY, label, {
|
this.add.text(cx, labelY, label, {
|
||||||
fontFamily: 'system-ui, sans-serif',
|
fontFamily: '"Julius Sans One"',
|
||||||
fontSize: '24px',
|
fontSize: '24px',
|
||||||
color: COLORS.mutedHex,
|
color: COLORS.mutedHex,
|
||||||
}).setOrigin(0.5);
|
}).setOrigin(0.5);
|
||||||
|
|
@ -333,7 +333,7 @@ export default class OpponentSelectScene extends Phaser.Scene {
|
||||||
}
|
}
|
||||||
|
|
||||||
const nameText = this.add.text(0, tileH / 2 - 11, item.name, {
|
const nameText = this.add.text(0, tileH / 2 - 11, item.name, {
|
||||||
fontFamily: 'system-ui, sans-serif',
|
fontFamily: '"Julius Sans One"',
|
||||||
fontSize: '13px',
|
fontSize: '13px',
|
||||||
color: COLORS.textHex,
|
color: COLORS.textHex,
|
||||||
}).setOrigin(0.5);
|
}).setOrigin(0.5);
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,7 @@ export default class PreloadScene extends Phaser.Scene {
|
||||||
const bar = this.add.rectangle(w / 2 - barWidth / 2, h / 2, 0, 20, COLORS.accent)
|
const bar = this.add.rectangle(w / 2 - barWidth / 2, h / 2, 0, 20, COLORS.accent)
|
||||||
.setOrigin(0, 0.5);
|
.setOrigin(0, 0.5);
|
||||||
this.add.text(w / 2, h / 2 - 60, 'Loading…', {
|
this.add.text(w / 2, h / 2 - 60, 'Loading…', {
|
||||||
fontFamily: 'system-ui, sans-serif',
|
fontFamily: '"Julius Sans One"',
|
||||||
fontSize: '32px',
|
fontSize: '32px',
|
||||||
color: COLORS.textHex,
|
color: COLORS.textHex,
|
||||||
}).setOrigin(0.5);
|
}).setOrigin(0.5);
|
||||||
|
|
@ -30,6 +30,8 @@ export default class PreloadScene extends Phaser.Scene {
|
||||||
frameWidth: 320,
|
frameWidth: 320,
|
||||||
frameHeight: 420,
|
frameHeight: 420,
|
||||||
});
|
});
|
||||||
|
this.load.image('bg-menu', '/assets/images/background-menu.png');
|
||||||
|
this.load.image('main-title', '/assets/images/main-title.png');
|
||||||
this.load.json('playfields', '/data/playfields.json');
|
this.load.json('playfields', '/data/playfields.json');
|
||||||
this.load.json('card-backs', '/data/card-backs.json');
|
this.load.json('card-backs', '/data/card-backs.json');
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -18,13 +18,13 @@ export default class ProfileScene extends Phaser.Scene {
|
||||||
const cx = GAME_WIDTH / 2;
|
const cx = GAME_WIDTH / 2;
|
||||||
|
|
||||||
this.add.text(cx, 140, 'Profile', {
|
this.add.text(cx, 140, 'Profile', {
|
||||||
fontFamily: 'system-ui, sans-serif',
|
fontFamily: 'Righteous',
|
||||||
fontSize: '64px',
|
fontSize: '64px',
|
||||||
color: COLORS.textHex,
|
color: COLORS.textHex,
|
||||||
}).setOrigin(0.5);
|
}).setOrigin(0.5);
|
||||||
|
|
||||||
this.statusText = this.add.text(cx, 220, 'Loading profile…', {
|
this.statusText = this.add.text(cx, 220, 'Loading profile…', {
|
||||||
fontFamily: 'system-ui, sans-serif',
|
fontFamily: '"Julius Sans One"',
|
||||||
fontSize: '24px',
|
fontSize: '24px',
|
||||||
color: COLORS.mutedHex,
|
color: COLORS.mutedHex,
|
||||||
}).setOrigin(0.5);
|
}).setOrigin(0.5);
|
||||||
|
|
@ -45,7 +45,7 @@ export default class ProfileScene extends Phaser.Scene {
|
||||||
const avatarBg = this.add.circle(avatarX, avatarY, 96, COLORS.panel).setStrokeStyle(3, COLORS.accent);
|
const avatarBg = this.add.circle(avatarX, avatarY, 96, COLORS.panel).setStrokeStyle(3, COLORS.accent);
|
||||||
const initial = (profile.displayName ?? profile.username ?? '?').charAt(0).toUpperCase();
|
const initial = (profile.displayName ?? profile.username ?? '?').charAt(0).toUpperCase();
|
||||||
this.add.text(avatarX, avatarY, initial, {
|
this.add.text(avatarX, avatarY, initial, {
|
||||||
fontFamily: 'system-ui, sans-serif',
|
fontFamily: 'Righteous',
|
||||||
fontSize: '72px',
|
fontSize: '72px',
|
||||||
color: COLORS.accentHex,
|
color: COLORS.accentHex,
|
||||||
}).setOrigin(0.5);
|
}).setOrigin(0.5);
|
||||||
|
|
@ -61,13 +61,13 @@ export default class ProfileScene extends Phaser.Scene {
|
||||||
}
|
}
|
||||||
|
|
||||||
this.add.text(cx - 320, 300, profile.username, {
|
this.add.text(cx - 320, 300, profile.username, {
|
||||||
fontFamily: 'system-ui, sans-serif',
|
fontFamily: 'Righteous',
|
||||||
fontSize: '40px',
|
fontSize: '40px',
|
||||||
color: COLORS.textHex,
|
color: COLORS.textHex,
|
||||||
}).setOrigin(0, 0.5);
|
}).setOrigin(0, 0.5);
|
||||||
|
|
||||||
this.add.text(cx - 320, 350, profile.email, {
|
this.add.text(cx - 320, 350, profile.email, {
|
||||||
fontFamily: 'system-ui, sans-serif',
|
fontFamily: '"Julius Sans One"',
|
||||||
fontSize: '24px',
|
fontSize: '24px',
|
||||||
color: COLORS.mutedHex,
|
color: COLORS.mutedHex,
|
||||||
}).setOrigin(0, 0.5);
|
}).setOrigin(0, 0.5);
|
||||||
|
|
@ -82,7 +82,7 @@ export default class ProfileScene extends Phaser.Scene {
|
||||||
const chipsY = 460;
|
const chipsY = 460;
|
||||||
this.add.text(cx - 320, chipsY, 'Chip balance', { fontSize: '22px', color: COLORS.mutedHex }).setOrigin(0, 0.5);
|
this.add.text(cx - 320, chipsY, 'Chip balance', { fontSize: '22px', color: COLORS.mutedHex }).setOrigin(0, 0.5);
|
||||||
const chipsValueText = this.add.text(cx - 40, chipsY, `$${profile.chips.toLocaleString()}`, {
|
const chipsValueText = this.add.text(cx - 40, chipsY, `$${profile.chips.toLocaleString()}`, {
|
||||||
fontFamily: 'system-ui, sans-serif',
|
fontFamily: '"Julius Sans One"',
|
||||||
fontSize: '22px',
|
fontSize: '22px',
|
||||||
color: COLORS.textHex,
|
color: COLORS.textHex,
|
||||||
}).setOrigin(0, 0.5);
|
}).setOrigin(0, 0.5);
|
||||||
|
|
@ -91,7 +91,7 @@ export default class ProfileScene extends Phaser.Scene {
|
||||||
let resetBtn = null;
|
let resetBtn = null;
|
||||||
if (profile.chips < 100) {
|
if (profile.chips < 100) {
|
||||||
this.add.text(cx - 320, chipsY + 44, 'You are running low on chips.', {
|
this.add.text(cx - 320, chipsY + 44, 'You are running low on chips.', {
|
||||||
fontFamily: 'system-ui, sans-serif',
|
fontFamily: '"Julius Sans One"',
|
||||||
fontSize: '18px',
|
fontSize: '18px',
|
||||||
color: COLORS.dangerHex,
|
color: COLORS.dangerHex,
|
||||||
}).setOrigin(0, 0.5);
|
}).setOrigin(0, 0.5);
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,7 @@ export default class RegisterScene extends Phaser.Scene {
|
||||||
const cx = GAME_WIDTH / 2;
|
const cx = GAME_WIDTH / 2;
|
||||||
|
|
||||||
this.add.text(cx, 180, 'Create account', {
|
this.add.text(cx, 180, 'Create account', {
|
||||||
fontFamily: 'system-ui, sans-serif',
|
fontFamily: 'Righteous',
|
||||||
fontSize: '64px',
|
fontSize: '64px',
|
||||||
color: COLORS.textHex,
|
color: COLORS.textHex,
|
||||||
}).setOrigin(0.5);
|
}).setOrigin(0.5);
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,7 @@ export default class VerifyScene extends Phaser.Scene {
|
||||||
const cx = GAME_WIDTH / 2;
|
const cx = GAME_WIDTH / 2;
|
||||||
|
|
||||||
this.add.text(cx, 220, 'Verify your email', {
|
this.add.text(cx, 220, 'Verify your email', {
|
||||||
fontFamily: 'system-ui, sans-serif',
|
fontFamily: 'Righteous',
|
||||||
fontSize: '64px',
|
fontSize: '64px',
|
||||||
color: COLORS.textHex,
|
color: COLORS.textHex,
|
||||||
}).setOrigin(0.5);
|
}).setOrigin(0.5);
|
||||||
|
|
@ -24,7 +24,7 @@ export default class VerifyScene extends Phaser.Scene {
|
||||||
: 'SMTP is not configured. Use the dev link below to verify your email.';
|
: 'SMTP is not configured. Use the dev link below to verify your email.';
|
||||||
|
|
||||||
this.add.text(cx, 360, body, {
|
this.add.text(cx, 360, body, {
|
||||||
fontFamily: 'system-ui, sans-serif',
|
fontFamily: '"Julius Sans One"',
|
||||||
fontSize: '26px',
|
fontSize: '26px',
|
||||||
color: COLORS.mutedHex,
|
color: COLORS.mutedHex,
|
||||||
wordWrap: { width: 1200 },
|
wordWrap: { width: 1200 },
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,8 @@
|
||||||
import * as Phaser from 'phaser';
|
import * as Phaser from 'phaser';
|
||||||
import { COLORS } from '../config.js';
|
import { COLORS } from '../config.js';
|
||||||
|
|
||||||
|
const RADIUS = 8;
|
||||||
|
|
||||||
export class Button extends Phaser.GameObjects.Container {
|
export class Button extends Phaser.GameObjects.Container {
|
||||||
constructor(scene, x, y, label, onClick, options = {}) {
|
constructor(scene, x, y, label, onClick, options = {}) {
|
||||||
super(scene, x, y);
|
super(scene, x, y);
|
||||||
|
|
@ -8,49 +10,74 @@ export class Button extends Phaser.GameObjects.Container {
|
||||||
width = 280,
|
width = 280,
|
||||||
height = 64,
|
height = 64,
|
||||||
bg = COLORS.panel,
|
bg = COLORS.panel,
|
||||||
bgHover = COLORS.accent,
|
bgHover = COLORS.gold,
|
||||||
textColor = COLORS.textHex,
|
textColor = COLORS.textHex,
|
||||||
|
textHoverColor = COLORS.textDarkHex,
|
||||||
fontSize = 28,
|
fontSize = 28,
|
||||||
variant = 'solid',
|
variant = 'solid',
|
||||||
} = options;
|
} = options;
|
||||||
|
|
||||||
this.options = { width, height, bg, bgHover, textColor, fontSize, variant };
|
this.options = { width, height, bg, bgHover, textColor, textHoverColor, fontSize, variant };
|
||||||
|
|
||||||
this.bgRect = scene.add.rectangle(0, 0, width, height, bg, variant === 'ghost' ? 0 : 1);
|
const isGhost = variant === 'ghost';
|
||||||
this.bgRect.setStrokeStyle(2, COLORS.accent, 1);
|
const hw = width / 2;
|
||||||
|
const hh = height / 2;
|
||||||
|
|
||||||
|
this.bgRect = scene.add.graphics();
|
||||||
|
this.bgRect.postFX.addShadow(0, 3, 0.004, 1.5, 0x000000, 8, 0.65);
|
||||||
|
|
||||||
|
const drawBg = (fillColor, fillAlpha) => {
|
||||||
|
this.bgRect.clear();
|
||||||
|
if (fillAlpha > 0) {
|
||||||
|
this.bgRect.fillStyle(fillColor, fillAlpha);
|
||||||
|
this.bgRect.fillRoundedRect(-hw, -hh, width, height, RADIUS);
|
||||||
|
}
|
||||||
|
this.bgRect.lineStyle(2, COLORS.accent, 1);
|
||||||
|
this.bgRect.strokeRoundedRect(-hw, -hh, width, height, RADIUS);
|
||||||
|
};
|
||||||
|
|
||||||
|
drawBg(bg, isGhost ? 0.35 : 1);
|
||||||
|
|
||||||
this.text = scene.add.text(0, 0, label, {
|
this.text = scene.add.text(0, 0, label, {
|
||||||
fontFamily: 'system-ui, sans-serif',
|
fontFamily: '"Julius Sans One"',
|
||||||
fontSize: `${fontSize}px`,
|
fontSize: `${fontSize}px`,
|
||||||
color: textColor,
|
color: textColor,
|
||||||
}).setOrigin(0.5);
|
}).setOrigin(0.5);
|
||||||
|
|
||||||
this.add([this.bgRect, this.text]);
|
this.add([this.bgRect, this.text]);
|
||||||
|
|
||||||
|
const bgHitArea = new Phaser.Geom.Rectangle(-hw, -hh, width, height);
|
||||||
|
const textHitArea = new Phaser.Geom.Rectangle(0, 0, width, height);
|
||||||
|
const hitCb = Phaser.Geom.Rectangle.Contains;
|
||||||
this.setSize(width, height);
|
this.setSize(width, height);
|
||||||
this.setInteractive({ useHandCursor: true, hitArea: new Phaser.Geom.Rectangle(0, 0, width, height), hitAreaCallback: Phaser.Geom.Rectangle.Contains });
|
this.setInteractive({ useHandCursor: true, hitArea: bgHitArea, hitAreaCallback: hitCb });
|
||||||
this.bgRect.setInteractive({ hitArea: new Phaser.Geom.Rectangle(0, 0, width, height), hitAreaCallback: Phaser.Geom.Rectangle.Contains });
|
this.bgRect.setInteractive({ hitArea: bgHitArea, hitAreaCallback: hitCb });
|
||||||
this.text.setInteractive({ hitArea: new Phaser.Geom.Rectangle(0, 0, width, height), hitAreaCallback: Phaser.Geom.Rectangle.Contains });
|
this.text.setInteractive({ hitArea: textHitArea, hitAreaCallback: hitCb });
|
||||||
this.on('pointerover', () => this.bgRect.setFillStyle(bgHover, 1));
|
|
||||||
this.on('pointerout', () => this.bgRect.setFillStyle(bg, variant === 'ghost' ? 0 : 1));
|
|
||||||
this.on('pointerdown', () => this.bgRect.setScale(0.97));
|
|
||||||
this.on('pointerup', () => this.bgRect.setScale(1));
|
|
||||||
this.on('pointerupoutside', () => this.bgRect.setScale(1));
|
|
||||||
if (onClick) this.on('pointerup', onClick);
|
|
||||||
|
|
||||||
this.bgRect.on('pointerover', () => this.bgRect.setFillStyle(bgHover, 1));
|
const onOver = () => {
|
||||||
this.bgRect.on('pointerout', () => this.bgRect.setFillStyle(bg, variant === 'ghost' ? 0 : 1));
|
if (isGhost) {
|
||||||
this.bgRect.on('pointerdown', () => this.bgRect.setScale(0.97));
|
drawBg(bgHover, 0.18);
|
||||||
this.bgRect.on('pointerup', () => this.bgRect.setScale(1));
|
this.text.setColor(COLORS.goldHex);
|
||||||
this.bgRect.on('pointerupoutside', () => this.bgRect.setScale(1));
|
} else {
|
||||||
if (onClick) this.bgRect.on('pointerup', onClick);
|
drawBg(bgHover, 1);
|
||||||
|
this.text.setColor(textHoverColor);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const onOut = () => {
|
||||||
|
drawBg(bg, isGhost ? 0.35 : 1);
|
||||||
|
this.text.setColor(textColor);
|
||||||
|
};
|
||||||
|
const onDown = () => this.bgRect.setScale(0.97);
|
||||||
|
const onUp = () => this.bgRect.setScale(1);
|
||||||
|
|
||||||
this.text.on('pointerover', () => this.bgRect.setFillStyle(bgHover, 1));
|
for (const target of [this, this.bgRect, this.text]) {
|
||||||
this.text.on('pointerout', () => this.bgRect.setFillStyle(bg, variant === 'ghost' ? 0 : 1));
|
target.on('pointerover', onOver);
|
||||||
this.text.on('pointerdown', () => this.bgRect.setScale(0.97));
|
target.on('pointerout', onOut);
|
||||||
this.text.on('pointerup', () => this.bgRect.setScale(1));
|
target.on('pointerdown', onDown);
|
||||||
this.text.on('pointerupoutside', () => this.bgRect.setScale(1));
|
target.on('pointerup', onUp);
|
||||||
if (onClick) this.text.on('pointerup', onClick);
|
target.on('pointerupoutside', onUp);
|
||||||
|
if (onClick) target.on('pointerup', onClick);
|
||||||
|
}
|
||||||
|
|
||||||
scene.add.existing(this);
|
scene.add.existing(this);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,7 @@ export class Modal extends Phaser.GameObjects.Container {
|
||||||
GAME_WIDTH / 2, GAME_HEIGHT / 2, 720, 280, COLORS.panel, 1,
|
GAME_WIDTH / 2, GAME_HEIGHT / 2, 720, 280, COLORS.panel, 1,
|
||||||
).setStrokeStyle(2, COLORS.accent);
|
).setStrokeStyle(2, COLORS.accent);
|
||||||
const text = scene.add.text(GAME_WIDTH / 2, GAME_HEIGHT / 2 - 30, message, {
|
const text = scene.add.text(GAME_WIDTH / 2, GAME_HEIGHT / 2 - 30, message, {
|
||||||
fontFamily: 'system-ui, sans-serif',
|
fontFamily: '"Julius Sans One"',
|
||||||
fontSize: '28px',
|
fontSize: '28px',
|
||||||
color: options.color ?? COLORS.textHex,
|
color: options.color ?? COLORS.textHex,
|
||||||
wordWrap: { width: 660 },
|
wordWrap: { width: 660 },
|
||||||
|
|
|
||||||
|
|
@ -105,7 +105,7 @@ export function createPlayerPortrait(scene, worldX, worldY, radius, depth, scene
|
||||||
|
|
||||||
const initial = (auth.user?.username ?? 'You').charAt(0).toUpperCase();
|
const initial = (auth.user?.username ?? 'You').charAt(0).toUpperCase();
|
||||||
const placeholder = scene.add.text(worldX, worldY, initial, {
|
const placeholder = scene.add.text(worldX, worldY, initial, {
|
||||||
fontFamily: 'system-ui, sans-serif',
|
fontFamily: '"Julius Sans One"',
|
||||||
fontSize: `${Math.round(radius * 0.9)}px`,
|
fontSize: `${Math.round(radius * 0.9)}px`,
|
||||||
color: COLORS.accentHex,
|
color: COLORS.accentHex,
|
||||||
}).setOrigin(0.5).setDepth(depth + 1);
|
}).setOrigin(0.5).setDepth(depth + 1);
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,24 @@
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Righteous';
|
||||||
|
src: url('/assets/fonts/Righteous-Regular.ttf') format('truetype');
|
||||||
|
font-weight: normal;
|
||||||
|
font-style: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Julius Sans One';
|
||||||
|
src: url('/assets/fonts/JuliusSansOne-Regular.ttf') format('truetype');
|
||||||
|
font-weight: normal;
|
||||||
|
font-style: normal;
|
||||||
|
}
|
||||||
|
|
||||||
html, body {
|
html, body {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
background: #0a0e14;
|
background: #0f0d0a;
|
||||||
color: #e6edf3;
|
color: #f2ead8;
|
||||||
font-family: system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
|
font-family: 'Julius Sans One', system-ui, sans-serif;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -49,8 +63,8 @@ html, body {
|
||||||
|
|
||||||
#dom-layer input:focus,
|
#dom-layer input:focus,
|
||||||
#dom-layer textarea:focus {
|
#dom-layer textarea:focus {
|
||||||
border-color: #5aa9e6;
|
border-color: #c8a84b;
|
||||||
box-shadow: 0 0 0 2px rgba(90, 169, 230, 0.3);
|
box-shadow: 0 0 0 2px rgba(200, 168, 75, 0.3);
|
||||||
}
|
}
|
||||||
|
|
||||||
#dom-layer button {
|
#dom-layer button {
|
||||||
|
|
|
||||||
|
|
@ -91,8 +91,11 @@ export function findSessionUser(sessionId) {
|
||||||
if (!sessionId) return null;
|
if (!sessionId) return null;
|
||||||
const row = db
|
const row = db
|
||||||
.prepare(
|
.prepare(
|
||||||
`SELECT u.id, u.email, u.username, u.email_verified, s.expires_at
|
`SELECT u.id, u.email, u.username, u.email_verified, s.expires_at,
|
||||||
FROM sessions s JOIN users u ON u.id = s.user_id
|
p.display_name, p.avatar_path
|
||||||
|
FROM sessions s
|
||||||
|
JOIN users u ON u.id = s.user_id
|
||||||
|
LEFT JOIN profiles p ON p.user_id = u.id
|
||||||
WHERE s.id = ?`,
|
WHERE s.id = ?`,
|
||||||
)
|
)
|
||||||
.get(sessionId);
|
.get(sessionId);
|
||||||
|
|
@ -106,6 +109,8 @@ export function findSessionUser(sessionId) {
|
||||||
email: row.email,
|
email: row.email,
|
||||||
username: row.username,
|
username: row.username,
|
||||||
emailVerified: !!row.email_verified,
|
emailVerified: !!row.email_verified,
|
||||||
|
displayName: row.display_name || null,
|
||||||
|
avatarPath: row.avatar_path || null,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue