diff --git a/public/src/config.js b/public/src/config.js index 8fb67d9..547f461 100644 --- a/public/src/config.js +++ b/public/src/config.js @@ -2,18 +2,22 @@ export const GAME_WIDTH = 1920; export const GAME_HEIGHT = 1080; export const COLORS = { - bg: 0x0a0e14, - bgHex: '#0a0e14', - panel: 0x111923, - panelHex: '#111923', - accent: 0x5aa9e6, - accentHex: '#5aa9e6', - text: 0xe6edf3, - textHex: '#e6edf3', - muted: 0x8a94a6, - mutedHex: '#8a94a6', - danger: 0xe06c75, - dangerHex: '#e06c75', + bg: 0x0f0d0a, + bgHex: '#0f0d0a', + panel: 0x1e1a12, + panelHex: '#1e1a12', + accent: 0xc8a84b, + accentHex: '#c8a84b', + text: 0xf2ead8, + textHex: '#f2ead8', + muted: 0x9e9080, + mutedHex: '#9e9080', + danger: 0xe06c75, + dangerHex: '#e06c75', + gold: 0xd4a017, + goldHex: '#d4a017', + textDark: 0x1a1208, + textDarkHex: '#1a1208', }; export const API_BASE = '/api'; diff --git a/public/src/games/backgammon/BackgammonGame.js b/public/src/games/backgammon/BackgammonGame.js index f2cd92b..5e7307a 100644 --- a/public/src/games/backgammon/BackgammonGame.js +++ b/public/src/games/backgammon/BackgammonGame.js @@ -151,7 +151,7 @@ export default class BackgammonGame extends Phaser.Scene { const isBottom = idx < 12; const ly = isBottom ? FY + FH + 14 : FY - 14; this.add.text(cx, ly, label, { - fontFamily: 'system-ui, sans-serif', + fontFamily: '"Julius Sans One"', fontSize: '18px', color: COLORS.mutedHex, }).setOrigin(0.5).setDepth(DEPTH.board); @@ -181,20 +181,20 @@ export default class BackgammonGame extends Phaser.Scene { // Labels 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); 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); // Pip count labels 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); 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); } @@ -246,13 +246,13 @@ export default class BackgammonGame extends Phaser.Scene { // Status message (also serves as turn label at bottom) this.turnText = this.add.text(cx, BY + BH + 18, '', { - fontFamily: 'system-ui, sans-serif', + fontFamily: '"Julius Sans One"', fontSize: '24px', color: COLORS.mutedHex, }).setOrigin(0.5).setDepth(DEPTH.ui); this.statusText = this.add.text(cx, BY + BH + 46, '', { - fontFamily: 'system-ui, sans-serif', + fontFamily: '"Julius Sans One"', fontSize: '22px', color: COLORS.mutedHex, }).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.opponentPortrait = createOpponentPortrait(this, opp, avatarX, oppAY, r, depth + 1); 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', }).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); createPlayerPortrait(this, avatarX, plrAY, r, depth + 1, 'Backgammon'); 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', }).setOrigin(0.5, 1).setDepth(depth + 2); } @@ -352,7 +352,7 @@ export default class BackgammonGame extends Phaser.Scene { if (pt.count > 5) { const pos = this.checkerScreenPos(idx, 4); 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', }).setOrigin(0.5).setDepth(DEPTH.checker + 1); } @@ -377,7 +377,7 @@ export default class BackgammonGame extends Phaser.Scene { if (count > 4) { const dy = color === 'white' ? 3 * (CR * 2 + 2) : -(3 * (CR * 2 + 2)); 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, }).setOrigin(0.5).setDepth(DEPTH.checker + 1); } @@ -817,7 +817,7 @@ export default class BackgammonGame extends Phaser.Scene { showTurnBanner(text) { const cx = BX + BW / 2; const banner = this.add.text(cx, BY - 80, text, { - fontFamily: 'system-ui, sans-serif', + fontFamily: 'Righteous', fontSize: '36px', color: COLORS.textHex, backgroundColor: '#111923ee', @@ -888,7 +888,7 @@ export default class BackgammonGame extends Phaser.Scene { .setStrokeStyle(3, COLORS.accent) .setDepth(DEPTH.banner); const txt = this.add.text(BX + BW / 2, BY + BH / 2 - 40, msg, { - fontFamily: 'system-ui, sans-serif', + fontFamily: '"Julius Sans One"', fontSize: '32px', color: isHuman ? '#ffd700' : COLORS.textHex, align: 'center', diff --git a/public/src/games/blackjack/BlackjackGame.js b/public/src/games/blackjack/BlackjackGame.js index a62fe34..a78ef07 100644 --- a/public/src/games/blackjack/BlackjackGame.js +++ b/public/src/games/blackjack/BlackjackGame.js @@ -116,15 +116,15 @@ export default class BlackjackGame extends Phaser.Scene { // ── Dealer area ─────────────────────────────────────────────────────────── buildDealerArea() { 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); 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); 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); } @@ -159,11 +159,11 @@ export default class BlackjackGame extends Phaser.Scene { } 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); 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); // 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, '', { - 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); // Portraits @@ -215,7 +215,7 @@ export default class BlackjackGame extends Phaser.Scene { container.on('pointerout', () => container.setAlpha(1)); 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', }).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', { - 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); 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); 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 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', color: '#ffffff', align: 'center', @@ -512,7 +512,7 @@ export default class BlackjackGame extends Phaser.Scene { circle.lineStyle(2, 0xf0e8d0, 0.8); circle.strokeCircle(0, 0, 28); 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); cont.add([circle, txt]); this.betGraphics[seat] = cont; @@ -575,7 +575,7 @@ export default class BlackjackGame extends Phaser.Scene { container.add(g); const color = card.isRed ? '#c0392b' : '#1a1a2e'; 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' } : {}), }); 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 badge = this.add.text(pos.x, textY, LABELS[result] ?? result, { - fontFamily: 'system-ui, sans-serif', + fontFamily: '"Julius Sans One"', fontSize: SIZES[result] ?? '48px', color: COLORS[result] ?? '#ffffff', 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 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)', { - 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); modal.add([bg, txt]); diff --git a/public/src/games/holdem/HoldemGame.js b/public/src/games/holdem/HoldemGame.js index da80f6a..c0e3660 100644 --- a/public/src/games/holdem/HoldemGame.js +++ b/public/src/games/holdem/HoldemGame.js @@ -150,7 +150,7 @@ export default class HoldemGame extends Phaser.Scene { buildPotDisplay() { this.potText = this.add.text(CX, CY - CARD_H / 2 - 36, 'Pot: $0', { - fontFamily: 'system-ui, sans-serif', + fontFamily: '"Julius Sans One"', fontSize: '26px', color: '#f0e8d0', }).setOrigin(0.5).setDepth(D.ui); @@ -158,13 +158,13 @@ export default class HoldemGame extends Phaser.Scene { buildBlindDisplay() { this.blindText = this.add.text(24, 24, 'Blinds $5/$10', { - fontFamily: 'system-ui, sans-serif', + fontFamily: '"Julius Sans One"', fontSize: '20px', color: COLORS.mutedHex, }).setOrigin(0, 0).setDepth(D.ui); this.timerText = this.add.text(24, 50, '', { - fontFamily: 'system-ui, sans-serif', + fontFamily: '"Julius Sans One"', fontSize: '16px', color: COLORS.mutedHex, }).setOrigin(0, 0).setDepth(D.ui); @@ -181,7 +181,7 @@ export default class HoldemGame extends Phaser.Scene { // Name label const nameY = isHuman ? -CARD_H / 2 - 48 : CARD_H / 2 + 28; const name = this.add.text(0, nameY, '—', { - fontFamily: 'system-ui, sans-serif', + fontFamily: '"Julius Sans One"', fontSize: '18px', color: COLORS.textHex, }).setOrigin(0.5); @@ -189,7 +189,7 @@ export default class HoldemGame extends Phaser.Scene { // Chip count const chipY = isHuman ? -CARD_H / 2 - 24 : CARD_H / 2 + 52; const chipTxt = this.add.text(0, chipY, '$0', { - fontFamily: 'system-ui, sans-serif', + fontFamily: '"Julius Sans One"', fontSize: '20px', color: '#f0e8d0', }).setOrigin(0.5); @@ -197,7 +197,7 @@ export default class HoldemGame extends Phaser.Scene { // 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 betTxt = this.add.text(0, betY, '', { - fontFamily: 'system-ui, sans-serif', + fontFamily: '"Julius Sans One"', fontSize: '18px', color: '#ffd700', }).setOrigin(0.5); @@ -205,7 +205,7 @@ export default class HoldemGame extends Phaser.Scene { // Dealer chip 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', { - fontFamily: 'system-ui, sans-serif', fontSize: '14px', color: '#1a1a1a', + fontFamily: '"Julius Sans One"', fontSize: '14px', color: '#1a1a1a', }).setOrigin(0.5).setVisible(false); // 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) .setStrokeStyle(1, COLORS.accent); 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); 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); 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); 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); 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); const modalItems = [overlay, panel, title, balanceTxt, buyInTxt]; if (this.globalChips <= 0) { 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); return; } @@ -742,7 +742,7 @@ export default class HoldemGame extends Phaser.Scene { } const label = this.add.text(0, 22, `$${p.bet}`, { - fontFamily: 'system-ui, sans-serif', + fontFamily: '"Julius Sans One"', fontSize: '15px', color: '#f0e8d0', stroke: '#000000', @@ -951,7 +951,7 @@ export default class HoldemGame extends Phaser.Scene { const color = card.isRed ? '#c0392b' : '#1a1a2e'; const sym = card.suitSymbol; const style = (size, bold = false) => ({ - fontFamily: 'system-ui, sans-serif', + fontFamily: '"Julius Sans One"', fontSize: `${size}px`, color, ...(bold ? { fontStyle: 'bold' } : {}), @@ -995,10 +995,10 @@ export default class HoldemGame extends Phaser.Scene { const isHuman = seat === 0; 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 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(), { - fontFamily: 'system-ui, sans-serif', + fontFamily: '"Julius Sans One"', fontSize: '22px', color: colors[action.type] ?? COLORS.textHex, backgroundColor: '#111923cc', @@ -1043,7 +1043,7 @@ export default class HoldemGame extends Phaser.Scene { // Title 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); S.push(title); this.tweens.add({ targets: title, alpha: 1, duration: 300, delay: 200 }); @@ -1143,7 +1143,7 @@ export default class HoldemGame extends Phaser.Scene { } else { const initial = (auth.user?.username ?? 'You').charAt(0).toUpperCase(); 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); S.push(initTxt); this.tweens.add({ targets: initTxt, alpha: 1, duration: 350, delay }); @@ -1152,7 +1152,7 @@ export default class HoldemGame extends Phaser.Scene { // Player name const nameX = 250; 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); S.push(nameTxt); this.tweens.add({ targets: nameTxt, alpha: 1, duration: 350, delay }); @@ -1172,7 +1172,7 @@ export default class HoldemGame extends Phaser.Scene { } if (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); S.push(statusTxt); 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; if (won > 0) { 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', }).setOrigin(0, 0.5).setDepth(D.modal + 2).setAlpha(0); S.push(wonTxt); @@ -1212,7 +1212,7 @@ export default class HoldemGame extends Phaser.Scene { // Chip count const chipStr = player.eliminated ? 'Eliminated' : `$${player.chips} chips`; 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); S.push(chipTxt); this.tweens.add({ targets: chipTxt, alpha: 1, duration: 350, delay }); @@ -1246,19 +1246,19 @@ export default class HoldemGame extends Phaser.Scene { : '🎉 You win!'; this.add.text(CX, CY - 140, headline, { - fontFamily: 'system-ui, sans-serif', fontSize: '40px', + fontFamily: 'Righteous', fontSize: '40px', color: won ? '#ffd700' : COLORS.textHex, }).setOrigin(0.5).setDepth(D.modal); this.add.text(CX, CY - 70, won ? `+$${net} profit` : net === 0 ? 'Broke even' : `-$${Math.abs(net)} loss`, { - fontFamily: 'system-ui, sans-serif', fontSize: '28px', - color: won ? '#5aa9e6' : COLORS.dangerHex, + fontFamily: '"Julius Sans One"', fontSize: '28px', + color: won ? COLORS.accentHex : COLORS.dangerHex, }).setOrigin(0.5).setDepth(D.modal); 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); new Button(this, CX - 110, CY + 100, 'Play Again', () => { diff --git a/public/src/games/parchisi/ParchisiGame.js b/public/src/games/parchisi/ParchisiGame.js index 0c13195..99ab079 100644 --- a/public/src/games/parchisi/ParchisiGame.js +++ b/public/src/games/parchisi/ParchisiGame.js @@ -52,19 +52,19 @@ function trackXY(idx) { } function homeXY(color, idx) { - if (color === 'red') return { col: 9, row: 17 - idx }; - if (color === 'blue') return { col: 17 - idx, row: 9 }; - if (color === 'yellow') return { col: 9, row: 1 + idx }; - if (color === 'green') return { col: 1 + idx, row: 9 }; + if (color === 'red') return { col: 1 + idx, row: 9 }; // west spoke + if (color === 'blue') return { col: 9, row: 17 - idx }; // south spoke + if (color === 'yellow') return { col: 17 - idx, row: 9 }; // east spoke + if (color === 'green') return { col: 9, row: 1 + idx }; // north spoke throw new Error(`bad color ${color}`); } // Final "home" cell at the center boundary per color. function homeFinalXY(color) { - if (color === 'red') return { col: 9, row: 10 }; - if (color === 'blue') return { col: 10, row: 9 }; - if (color === 'yellow') return { col: 9, row: 8 }; - if (color === 'green') return { col: 8, row: 9 }; + if (color === 'red') return { col: 8, row: 9 }; // west spoke + if (color === 'blue') return { col: 9, row: 10 }; // south spoke + if (color === 'yellow') return { col: 10, row: 9 }; // east spoke + if (color === 'green') return { col: 9, row: 8 }; // north spoke } function cellWorld(col, row) { @@ -146,6 +146,9 @@ export default class ParchisiGame extends Phaser.Scene { this.turnIndicator = null; this.turnIndicatorGfx = null; this.turnIndicatorPulseTween = null; + this.turnIndicatorMoveTween = null; + this.bonusChip20 = null; + this._bonusChip20Visible = false; } create() { @@ -153,6 +156,7 @@ export default class ParchisiGame extends Phaser.Scene { this.buildBoard(); this.buildDice(); this.buildUI(); + this.buildBonusChip(); this.buildPlayerCards(); this.buildTurnIndicator(); this.buildPawns(); @@ -229,6 +233,7 @@ export default class ParchisiGame extends Phaser.Scene { if (isSafeTrack(idx) && !entryColor) { this.drawStar(g, wp.x, wp.y, 5, 10, 5, 0x2b5d80, 0.7); } + } drawHomeColumn(color) { @@ -263,7 +268,7 @@ export default class ParchisiGame extends Phaser.Scene { g.lineStyle(2, TRACK_STROKE, 0.9); g.strokeCircle(cx, cy, r); this.add.text(cx, cy, 'HOME', { - fontFamily: 'system-ui, sans-serif', + fontFamily: '"Julius Sans One"', fontSize: '16px', color: '#3a2010', fontStyle: 'bold', @@ -287,7 +292,7 @@ export default class ParchisiGame extends Phaser.Scene { // ── Dice ────────────────────────────────────────────────────────────────── buildDice() { const baseX = ORIGIN_X - 80; - const baseY = GAME_HEIGHT / 2 - 150; + const baseY = GAME_HEIGHT / 2 - 80; for (let i = 0; i < 2; i++) { const g = this.add.graphics(); const container = this.add.container(baseX, baseY + i * 80).setDepth(DEPTH.dice); @@ -344,6 +349,7 @@ export default class ParchisiGame extends Phaser.Scene { } updateDiceDisplay() { + this.updateBonusChip(); if (!this.gs.dice) { this.diceContainers.forEach((c) => c.setAlpha(0.25)); return; @@ -364,27 +370,66 @@ export default class ParchisiGame extends Phaser.Scene { // ── UI / Portraits ──────────────────────────────────────────────────────── buildUI() { 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(), { width: 110, height: 44, fontSize: 22, }); 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, }).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, }).setDepth(DEPTH.ui); this.statusText = this.add.text(GAME_WIDTH / 2, GAME_HEIGHT - 30, '', { - fontFamily: 'system-ui, sans-serif', + fontFamily: '"Julius Sans One"', fontSize: '22px', color: UI.textHex, }).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() { const portraitR = 56; // 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); createPlayerPortrait(this, xLeft, plY, portraitR, DEPTH.ui + 1, 'ParchisiGame'); 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); // 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.opponentPortraits[color] = createOpponentPortrait(this, opp, x, y, portraitR, DEPTH.ui + 1); 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); }); } @@ -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); + // Stop both running tweens before starting new ones if (this.turnIndicatorPulseTween) { this.turnIndicatorPulseTween.stop(); this.turnIndicatorPulseTween = null; } + if (this.turnIndicatorMoveTween) { + this.turnIndicatorMoveTween.stop(); + this.turnIndicatorMoveTween = null; + } this.turnIndicator.setScale(1); if (immediately || this.turnIndicator.alpha === 0) { this._drawTurnTriangle(side); this.turnIndicator.setPosition(x, y).setAlpha(1); this._startIndicatorPulse(); + onArrive?.(); return; } - this.tweens.add({ + this.turnIndicatorMoveTween = this.tweens.add({ targets: this.turnIndicator, x, y, - duration: 550, + duration: 400, ease: 'Cubic.easeInOut', onComplete: () => { + this.turnIndicatorMoveTween = null; this._drawTurnTriangle(side); this._startIndicatorPulse(); + onArrive?.(); }, }); } @@ -749,13 +802,16 @@ export default class ParchisiGame extends Phaser.Scene { afterTurn() { this.updateButtons(); - this.moveTurnIndicator(this.gs.currentPlayer); if (this.gs.currentPlayer === 'red') { + this.moveTurnIndicator('red'); this.setStatus('Your turn — roll the dice'); } else { const opp = this.opponents[['blue', 'yellow', 'green'].indexOf(this.gs.currentPlayer)]; 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') { this.animating = false; 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(); return; } @@ -935,7 +986,7 @@ export default class ParchisiGame extends Phaser.Scene { ? '🎉 You Win!\nAll four pawns home!' : `${oppName} wins this round.\nBetter luck next game!`; const txt = this.add.text(GAME_WIDTH / 2, GAME_HEIGHT / 2 - 50, msg, { - fontFamily: 'system-ui, sans-serif', + fontFamily: '"Julius Sans One"', fontSize: '32px', color: isHuman ? '#ffd700' : UI.textHex, align: 'center', diff --git a/public/src/games/parchisi/ParchisiLogic.js b/public/src/games/parchisi/ParchisiLogic.js index ec52768..14ec534 100644 --- a/public/src/games/parchisi/ParchisiLogic.js +++ b/public/src/games/parchisi/ParchisiLogic.js @@ -18,20 +18,20 @@ 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 -// into its home column. = (entry + 67) mod 68 = (entry - 1 + 68) mod 68. -export const HOME_ENTRY = { red: 67, blue: 16, yellow: 33, green: 50 }; +// into its home column. Movement is CCW (track index decrements). +export const HOME_ENTRY = { red: 16, blue: 67, yellow: 50, green: 33 }; export const TRACK_LEN = 68; export const HOME_COL_LEN = 7; export const PAWNS_PER_PLAYER = 4; const SAFE_SET = new Set([ - 0, 7, 12, - 17, 24, 29, - 34, 41, 46, - 51, 58, 63, + 4, 11, 16, + 21, 28, 33, + 38, 45, 50, + 55, 62, 67, ]); export function isSafeTrack(idx) { @@ -139,7 +139,7 @@ export function pawnDistanceToHome(pawn, color) { if (pawn.track !== undefined) { // Distance from track t to home-entry, then +7 (cols) + 1 (home circle) = +8. 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 0; @@ -221,7 +221,7 @@ function projectPath(from, color, steps) { pos = { home: 0 }; path.push(pos); } else { - const nextIdx = (pos.track + 1) % TRACK_LEN; + const nextIdx = (pos.track - 1 + TRACK_LEN) % TRACK_LEN; pos = { track: nextIdx }; path.push(pos); } diff --git a/public/src/scenes/GameMenuScene.js b/public/src/scenes/GameMenuScene.js index 684e38b..44f3ea5 100644 --- a/public/src/scenes/GameMenuScene.js +++ b/public/src/scenes/GameMenuScene.js @@ -10,11 +10,14 @@ export default class GameMenuScene extends Phaser.Scene { async create() { const cx = GAME_WIDTH / 2; - this.add.text(cx, 120, 'Choose a game', { - fontFamily: 'system-ui, sans-serif', + this.add.image(cx, GAME_HEIGHT / 2, 'bg-menu').setDisplaySize(GAME_WIDTH, GAME_HEIGHT); + + const titleText = this.add.text(cx, 120, 'Choose a game', { + fontFamily: 'Righteous', fontSize: '64px', 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…', { fontSize: '24px', color: COLORS.mutedHex, @@ -40,17 +43,19 @@ export default class GameMenuScene extends Phaser.Scene { } 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, { - fontFamily: 'system-ui, sans-serif', + fontFamily: 'Righteous', fontSize: '40px', color: COLORS.accentHex, }).setOrigin(0.5); games.forEach((game, i) => { - const btn = new Button(this, x, y + 80 + i * 90, game.name, () => this.openGame(game), { - width: 360, - }); - void btn; + new Button(this, x, y + 80 + i * 90, game.name, () => this.openGame(game), { width: 360 }); }); } diff --git a/public/src/scenes/GameRoomScene.js b/public/src/scenes/GameRoomScene.js index 364e421..70645cc 100644 --- a/public/src/scenes/GameRoomScene.js +++ b/public/src/scenes/GameRoomScene.js @@ -31,7 +31,7 @@ export default class GameRoomScene extends Phaser.Scene { const cx = GAME_WIDTH / 2; this.add.text(cx, 80, `${this.game.name}`, { - fontFamily: 'system-ui, sans-serif', + fontFamily: 'Righteous', fontSize: '52px', color: COLORS.textHex, }).setOrigin(0.5); @@ -44,7 +44,7 @@ export default class GameRoomScene extends Phaser.Scene { // Placeholder table felt 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)`, { - fontFamily: 'system-ui, sans-serif', + fontFamily: '"Julius Sans One"', fontSize: '28px', color: COLORS.mutedHex, align: 'center', diff --git a/public/src/scenes/LandingScene.js b/public/src/scenes/LandingScene.js index 3367062..d2a3abb 100644 --- a/public/src/scenes/LandingScene.js +++ b/public/src/scenes/LandingScene.js @@ -9,19 +9,29 @@ export default class LandingScene extends Phaser.Scene { create() { 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', { - fontFamily: 'Georgia, "Times New Roman", serif', - fontSize: '96px', - color: COLORS.textHex, - }).setOrigin(0.5); + const logo = this.add.image(cx, 290, 'main-title').setOrigin(0.5, 0.5).setAlpha(0); + logo.postFX.addShadow(3, 6, 0.005, 2, 0x000000, 10, 0.75); - this.add.text(cx, 320, 'Cards, dice, and classic tables.', { - fontFamily: 'system-ui, sans-serif', - fontSize: '32px', - color: COLORS.mutedHex, - }).setOrigin(0.5); + this.tweens.add({ + targets: logo, + alpha: 1, + y: 260, + 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(); @@ -37,29 +47,66 @@ export default class LandingScene extends Phaser.Scene { const user = auth.user; if (user) { - this.add.text(cx, 480, `Welcome back, ${user.username}`, { - fontFamily: 'system-ui, sans-serif', + const avatarR = 28; + 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', 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) { - this.add.text(cx, 540, 'Email not yet verified — check the server console for the link in dev.', { - fontFamily: 'system-ui, sans-serif', + this.add.text(cx, 690, 'Email not yet verified — check the server console for the link in dev.', { + fontFamily: '"Julius Sans One"', fontSize: '22px', color: COLORS.dangerHex, }).setOrigin(0.5); } - new Button(this, cx, 660, 'Play', () => this.scene.start('GameMenu')); - new Button(this, cx, 740, 'Profile', () => this.scene.start('Profile')); - new Button(this, cx, 820, 'Sign out', async () => { + new Button(this, cx, 810, 'Play', () => this.scene.start('GameMenu')); + new Button(this, cx, 890, 'Profile', () => this.scene.start('Profile')); + new Button(this, cx, 970, 'Sign out', async () => { await auth.logout(); }, { variant: 'ghost' }); } else { - new Button(this, cx, 540, 'Sign in', () => this.scene.start('Login')); - new Button(this, cx, 620, 'Create account', () => this.scene.start('Register')); - new Button(this, cx, 700, 'Continue as guest', () => this.scene.start('GameMenu'), { variant: 'ghost' }); + new Button(this, cx, 690, 'Sign in', () => this.scene.start('Login')); + new Button(this, cx, 770, 'Create account', () => this.scene.start('Register')); + new Button(this, cx, 850, 'Continue as guest', () => this.scene.start('GameMenu'), { variant: 'ghost' }); } } } diff --git a/public/src/scenes/LobbyScene.js b/public/src/scenes/LobbyScene.js index 8427509..7ca222e 100644 --- a/public/src/scenes/LobbyScene.js +++ b/public/src/scenes/LobbyScene.js @@ -13,13 +13,13 @@ export default class LobbyScene extends Phaser.Scene { const cx = GAME_WIDTH / 2; this.add.text(cx, 100, `${this.game.name} — Lobby`, { - fontFamily: 'system-ui, sans-serif', + fontFamily: 'Righteous', fontSize: '52px', color: COLORS.textHex, }).setOrigin(0.5); this.listText = this.add.text(cx, 220, 'Connecting…', { - fontFamily: 'system-ui, sans-serif', + fontFamily: '"Julius Sans One"', fontSize: '24px', color: COLORS.mutedHex, }).setOrigin(0.5); diff --git a/public/src/scenes/LoginScene.js b/public/src/scenes/LoginScene.js index 72c1842..e10b358 100644 --- a/public/src/scenes/LoginScene.js +++ b/public/src/scenes/LoginScene.js @@ -12,7 +12,7 @@ export default class LoginScene extends Phaser.Scene { const cx = GAME_WIDTH / 2; this.add.text(cx, 200, 'Sign in', { - fontFamily: 'system-ui, sans-serif', + fontFamily: 'Righteous', fontSize: '64px', color: COLORS.textHex, }).setOrigin(0.5); diff --git a/public/src/scenes/OpponentSelectScene.js b/public/src/scenes/OpponentSelectScene.js index dc4aeb0..c43f19c 100644 --- a/public/src/scenes/OpponentSelectScene.js +++ b/public/src/scenes/OpponentSelectScene.js @@ -35,13 +35,13 @@ export default class OpponentSelectScene extends Phaser.Scene { const cx = GAME_WIDTH / 2; this.add.text(cx, 60, this.gameDef.name, { - fontFamily: 'system-ui, sans-serif', + fontFamily: 'Righteous', fontSize: '52px', color: COLORS.textHex, }).setOrigin(0.5); this.add.text(cx, 122, 'Choose your opponent', { - fontFamily: 'system-ui, sans-serif', + fontFamily: 'Righteous', fontSize: '36px', color: COLORS.mutedHex, }).setOrigin(0.5); @@ -185,7 +185,7 @@ export default class OpponentSelectScene extends Phaser.Scene { ctx.fillStyle = COLORS.panelHex; ctx.fillRect(0, 0, portraitSize, portraitSize); 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.textBaseline = 'middle'; 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'); name.textContent = opp.name ?? ''; name.style.cssText = [ - 'font-family:system-ui,sans-serif', + 'font-family:"Julius Sans One"', 'font-size:22px', 'font-weight:600', `color:${COLORS.textHex}`, @@ -227,7 +227,7 @@ export default class OpponentSelectScene extends Phaser.Scene { const bio = document.createElement('div'); bio.textContent = opp.bio ?? ''; bio.style.cssText = [ - 'font-family:system-ui,sans-serif', + 'font-family:"Julius Sans One"', 'font-size:15px', `color:${COLORS.mutedHex}`, '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) { const cx = GAME_WIDTH / 2; this.add.text(cx, labelY, label, { - fontFamily: 'system-ui, sans-serif', + fontFamily: '"Julius Sans One"', fontSize: '24px', color: COLORS.mutedHex, }).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, { - fontFamily: 'system-ui, sans-serif', + fontFamily: '"Julius Sans One"', fontSize: '13px', color: COLORS.textHex, }).setOrigin(0.5); diff --git a/public/src/scenes/PreloadScene.js b/public/src/scenes/PreloadScene.js index f88afbc..385daec 100644 --- a/public/src/scenes/PreloadScene.js +++ b/public/src/scenes/PreloadScene.js @@ -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) .setOrigin(0, 0.5); this.add.text(w / 2, h / 2 - 60, 'Loading…', { - fontFamily: 'system-ui, sans-serif', + fontFamily: '"Julius Sans One"', fontSize: '32px', color: COLORS.textHex, }).setOrigin(0.5); @@ -30,6 +30,8 @@ export default class PreloadScene extends Phaser.Scene { frameWidth: 320, 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('card-backs', '/data/card-backs.json'); } diff --git a/public/src/scenes/ProfileScene.js b/public/src/scenes/ProfileScene.js index 86fb52a..2046852 100644 --- a/public/src/scenes/ProfileScene.js +++ b/public/src/scenes/ProfileScene.js @@ -18,13 +18,13 @@ export default class ProfileScene extends Phaser.Scene { const cx = GAME_WIDTH / 2; this.add.text(cx, 140, 'Profile', { - fontFamily: 'system-ui, sans-serif', + fontFamily: 'Righteous', fontSize: '64px', color: COLORS.textHex, }).setOrigin(0.5); this.statusText = this.add.text(cx, 220, 'Loading profile…', { - fontFamily: 'system-ui, sans-serif', + fontFamily: '"Julius Sans One"', fontSize: '24px', color: COLORS.mutedHex, }).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 initial = (profile.displayName ?? profile.username ?? '?').charAt(0).toUpperCase(); this.add.text(avatarX, avatarY, initial, { - fontFamily: 'system-ui, sans-serif', + fontFamily: 'Righteous', fontSize: '72px', color: COLORS.accentHex, }).setOrigin(0.5); @@ -61,13 +61,13 @@ export default class ProfileScene extends Phaser.Scene { } this.add.text(cx - 320, 300, profile.username, { - fontFamily: 'system-ui, sans-serif', + fontFamily: 'Righteous', fontSize: '40px', color: COLORS.textHex, }).setOrigin(0, 0.5); this.add.text(cx - 320, 350, profile.email, { - fontFamily: 'system-ui, sans-serif', + fontFamily: '"Julius Sans One"', fontSize: '24px', color: COLORS.mutedHex, }).setOrigin(0, 0.5); @@ -82,7 +82,7 @@ export default class ProfileScene extends Phaser.Scene { const chipsY = 460; 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()}`, { - fontFamily: 'system-ui, sans-serif', + fontFamily: '"Julius Sans One"', fontSize: '22px', color: COLORS.textHex, }).setOrigin(0, 0.5); @@ -91,7 +91,7 @@ export default class ProfileScene extends Phaser.Scene { let resetBtn = null; if (profile.chips < 100) { this.add.text(cx - 320, chipsY + 44, 'You are running low on chips.', { - fontFamily: 'system-ui, sans-serif', + fontFamily: '"Julius Sans One"', fontSize: '18px', color: COLORS.dangerHex, }).setOrigin(0, 0.5); diff --git a/public/src/scenes/RegisterScene.js b/public/src/scenes/RegisterScene.js index acaa020..c2ddcbc 100644 --- a/public/src/scenes/RegisterScene.js +++ b/public/src/scenes/RegisterScene.js @@ -12,7 +12,7 @@ export default class RegisterScene extends Phaser.Scene { const cx = GAME_WIDTH / 2; this.add.text(cx, 180, 'Create account', { - fontFamily: 'system-ui, sans-serif', + fontFamily: 'Righteous', fontSize: '64px', color: COLORS.textHex, }).setOrigin(0.5); diff --git a/public/src/scenes/VerifyScene.js b/public/src/scenes/VerifyScene.js index 47a39ba..558c26c 100644 --- a/public/src/scenes/VerifyScene.js +++ b/public/src/scenes/VerifyScene.js @@ -14,7 +14,7 @@ export default class VerifyScene extends Phaser.Scene { const cx = GAME_WIDTH / 2; this.add.text(cx, 220, 'Verify your email', { - fontFamily: 'system-ui, sans-serif', + fontFamily: 'Righteous', fontSize: '64px', color: COLORS.textHex, }).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.'; this.add.text(cx, 360, body, { - fontFamily: 'system-ui, sans-serif', + fontFamily: '"Julius Sans One"', fontSize: '26px', color: COLORS.mutedHex, wordWrap: { width: 1200 }, diff --git a/public/src/ui/Button.js b/public/src/ui/Button.js index 07df3cb..c940497 100644 --- a/public/src/ui/Button.js +++ b/public/src/ui/Button.js @@ -1,56 +1,83 @@ import * as Phaser from 'phaser'; import { COLORS } from '../config.js'; +const RADIUS = 8; + export class Button extends Phaser.GameObjects.Container { constructor(scene, x, y, label, onClick, options = {}) { super(scene, x, y); const { - width = 280, - height = 64, - bg = COLORS.panel, - bgHover = COLORS.accent, - textColor = COLORS.textHex, - fontSize = 28, - variant = 'solid', + width = 280, + height = 64, + bg = COLORS.panel, + bgHover = COLORS.gold, + textColor = COLORS.textHex, + textHoverColor = COLORS.textDarkHex, + fontSize = 28, + variant = 'solid', } = 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); - this.bgRect.setStrokeStyle(2, COLORS.accent, 1); + const isGhost = variant === 'ghost'; + 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, { - fontFamily: 'system-ui, sans-serif', + fontFamily: '"Julius Sans One"', fontSize: `${fontSize}px`, color: textColor, }).setOrigin(0.5); 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.setInteractive({ useHandCursor: true, hitArea: new Phaser.Geom.Rectangle(0, 0, width, height), hitAreaCallback: Phaser.Geom.Rectangle.Contains }); - this.bgRect.setInteractive({ hitArea: new Phaser.Geom.Rectangle(0, 0, width, height), hitAreaCallback: Phaser.Geom.Rectangle.Contains }); - this.text.setInteractive({ hitArea: new Phaser.Geom.Rectangle(0, 0, width, height), hitAreaCallback: Phaser.Geom.Rectangle.Contains }); - 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.setInteractive({ useHandCursor: true, hitArea: bgHitArea, hitAreaCallback: hitCb }); + this.bgRect.setInteractive({ hitArea: bgHitArea, hitAreaCallback: hitCb }); + this.text.setInteractive({ hitArea: textHitArea, hitAreaCallback: hitCb }); - this.bgRect.on('pointerover', () => this.bgRect.setFillStyle(bgHover, 1)); - this.bgRect.on('pointerout', () => this.bgRect.setFillStyle(bg, variant === 'ghost' ? 0 : 1)); - this.bgRect.on('pointerdown', () => this.bgRect.setScale(0.97)); - this.bgRect.on('pointerup', () => this.bgRect.setScale(1)); - this.bgRect.on('pointerupoutside', () => this.bgRect.setScale(1)); - if (onClick) this.bgRect.on('pointerup', onClick); + const onOver = () => { + if (isGhost) { + drawBg(bgHover, 0.18); + this.text.setColor(COLORS.goldHex); + } else { + 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)); - this.text.on('pointerout', () => this.bgRect.setFillStyle(bg, variant === 'ghost' ? 0 : 1)); - this.text.on('pointerdown', () => this.bgRect.setScale(0.97)); - this.text.on('pointerup', () => this.bgRect.setScale(1)); - this.text.on('pointerupoutside', () => this.bgRect.setScale(1)); - if (onClick) this.text.on('pointerup', onClick); + for (const target of [this, this.bgRect, this.text]) { + target.on('pointerover', onOver); + target.on('pointerout', onOut); + target.on('pointerdown', onDown); + target.on('pointerup', onUp); + target.on('pointerupoutside', onUp); + if (onClick) target.on('pointerup', onClick); + } scene.add.existing(this); } diff --git a/public/src/ui/Modal.js b/public/src/ui/Modal.js index 152e746..0b39345 100644 --- a/public/src/ui/Modal.js +++ b/public/src/ui/Modal.js @@ -11,7 +11,7 @@ export class Modal extends Phaser.GameObjects.Container { GAME_WIDTH / 2, GAME_HEIGHT / 2, 720, 280, COLORS.panel, 1, ).setStrokeStyle(2, COLORS.accent); const text = scene.add.text(GAME_WIDTH / 2, GAME_HEIGHT / 2 - 30, message, { - fontFamily: 'system-ui, sans-serif', + fontFamily: '"Julius Sans One"', fontSize: '28px', color: options.color ?? COLORS.textHex, wordWrap: { width: 660 }, diff --git a/public/src/ui/Portrait.js b/public/src/ui/Portrait.js index 51a71f8..a603870 100644 --- a/public/src/ui/Portrait.js +++ b/public/src/ui/Portrait.js @@ -105,7 +105,7 @@ export function createPlayerPortrait(scene, worldX, worldY, radius, depth, scene const initial = (auth.user?.username ?? 'You').charAt(0).toUpperCase(); const placeholder = scene.add.text(worldX, worldY, initial, { - fontFamily: 'system-ui, sans-serif', + fontFamily: '"Julius Sans One"', fontSize: `${Math.round(radius * 0.9)}px`, color: COLORS.accentHex, }).setOrigin(0.5).setDepth(depth + 1); diff --git a/public/styles.css b/public/styles.css index 8f570f0..79caa61 100644 --- a/public/styles.css +++ b/public/styles.css @@ -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 { margin: 0; padding: 0; height: 100%; - background: #0a0e14; - color: #e6edf3; - font-family: system-ui, -apple-system, "Segoe UI", Roboto, sans-serif; + background: #0f0d0a; + color: #f2ead8; + font-family: 'Julius Sans One', system-ui, sans-serif; overflow: hidden; } @@ -49,8 +63,8 @@ html, body { #dom-layer input:focus, #dom-layer textarea:focus { - border-color: #5aa9e6; - box-shadow: 0 0 0 2px rgba(90, 169, 230, 0.3); + border-color: #c8a84b; + box-shadow: 0 0 0 2px rgba(200, 168, 75, 0.3); } #dom-layer button { diff --git a/server/auth/service.js b/server/auth/service.js index bb4fdd4..0f8b7f6 100644 --- a/server/auth/service.js +++ b/server/auth/service.js @@ -91,8 +91,11 @@ export function findSessionUser(sessionId) { if (!sessionId) return null; const row = db .prepare( - `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 + `SELECT u.id, u.email, u.username, u.email_verified, s.expires_at, + 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 = ?`, ) .get(sessionId); @@ -106,6 +109,8 @@ export function findSessionUser(sessionId) { email: row.email, username: row.username, emailVerified: !!row.email_verified, + displayName: row.display_name || null, + avatarPath: row.avatar_path || null, }; }