feat: enhance Bejeweled sound effects and Yatzi dice animations

- Replace generic sci-fi sounds with gem-themed audio (match, chain, drop, big drop)
- Add context-aware match sounds based on special gems (hyper, flame) and match size
- Improve Yatzi dice roll with bezier curve trajectories, rotation, and bounce
- Restructure dice rendering to use containers for consistent transforms
- Add new gem sound assets and SFX constants
This commit is contained in:
Brian Fertig 2026-06-20 14:33:52 -06:00
parent 5632dbae9b
commit e4c12e91bd
9 changed files with 100 additions and 40 deletions

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -79,6 +79,16 @@
"file": "track16.mp3", "file": "track16.mp3",
"artist": "Surf Sundown", "artist": "Surf Sundown",
"title": "Khruangbin" "title": "Khruangbin"
},
{
"file": "track17.mp3",
"artist": "All Night Long",
"title": "Becky Hill"
},
{
"file": "track18.mp3",
"artist": "How You Feel It",
"title": "Mutemath"
} }
] ]
} }

View File

@ -493,10 +493,10 @@ export default class BlockFighterGame extends Phaser.Scene {
let locked = false; let locked = false;
let events = null; let events = null;
switch (action) { switch (action) {
case 'left': if (moveLeft(m, idx) && idx === 0) playSound(this, SFX.PIECE_CLICK); break; case 'left': if (moveLeft(m, idx) && idx === 0) playSound(this, SFX.UI_PICK); break;
case 'right': if (moveRight(m, idx) && idx === 0) playSound(this, SFX.PIECE_CLICK); break; case 'right': if (moveRight(m, idx) && idx === 0) playSound(this, SFX.UI_PICK); break;
case 'rotateCW': if (rotateCW(m, idx) && idx === 0) playSound(this, SFX.PIECE_CLICK); break; case 'rotateCW': if (rotateCW(m, idx) && idx === 0) playSound(this, SFX.UI_PICK); break;
case 'rotateCCW': if (rotateCCW(m, idx) && idx === 0) playSound(this, SFX.PIECE_CLICK); break; case 'rotateCCW': if (rotateCCW(m, idx) && idx === 0) playSound(this, SFX.UI_PICK); break;
case 'softDrop': { case 'softDrop': {
const r = stepDown(m, idx); const r = stepDown(m, idx);
locked = r.locked; events = r.events; locked = r.locked; events = r.events;
@ -525,7 +525,7 @@ export default class BlockFighterGame extends Phaser.Scene {
} }
onLocked(idx, events) { onLocked(idx, events) {
playSound(this, SFX.CARD_PLACE); playSound(this, SFX.UI_PLACE);
this.updatePieceSprites(idx); // hides the piece (now null) this.updatePieceSprites(idx); // hides the piece (now null)
this.enqueue(idx, events); this.enqueue(idx, events);
} }
@ -542,6 +542,7 @@ export default class BlockFighterGame extends Phaser.Scene {
case 'spawn': case 'spawn':
this.updatePieceSprites(idx); this.updatePieceSprites(idx);
this.updatePreviews(); this.updatePreviews();
if (idx === 0) playSound(this, SFX.UI_CHIME);
break; break;
case 'place': case 'place':
case 'settle': case 'settle':
@ -550,12 +551,12 @@ export default class BlockFighterGame extends Phaser.Scene {
break; break;
case 'garbage': case 'garbage':
this.renderBoard(idx, e); this.renderBoard(idx, e);
if (e.cells?.length) playSound(this, SFX.DICE_ROLL); if (e.cells?.length) playSound(this, SFX.GEM_CHAIN);
break; break;
case 'clear': case 'clear':
case 'diamond': { case 'diamond': {
this.renderBoard(idx, e); this.renderBoard(idx, e);
playSound(this, SFX.MASTERMIND_MATCH ?? SFX.CARD_SHOW); playSound(this, SFX.GEM_MATCH_4);
for (const cell of e.cells ?? []) { for (const cell of e.cells ?? []) {
if (cell.r < HIDDEN_ROWS) continue; if (cell.r < HIDDEN_ROWS) continue;
const fx = this.add.rectangle( const fx = this.add.rectangle(

View File

@ -126,7 +126,7 @@ export default class CrapsGame extends Phaser.Scene {
buildTitle() { buildTitle() {
this.add.text(CX, 52, 'Craps', { this.add.text(CX, 52, 'Craps', {
fontFamily: 'JqkasWild', fontSize: '50px', color: COLORS.textHex, fontFamily: 'Caesar', fontSize: '50px', color: COLORS.textHex,
}).setOrigin(0.5).setDepth(D.ui); }).setOrigin(0.5).setDepth(D.ui);
this.statusText = this.add.text(CX, 96, '', { this.statusText = this.add.text(CX, 96, '', {
@ -209,7 +209,7 @@ export default class CrapsGame extends Phaser.Scene {
_renderNumberBox(z) { _renderNumberBox(z) {
this.add.text(z.center.x, z.center.y - 16, z.label, { this.add.text(z.center.x, z.center.y - 16, z.label, {
fontFamily: 'JqkasWild', fontSize: '38px', color: COLORS.textHex, fontFamily: 'Caesar', fontSize: '38px', color: COLORS.textHex,
}).setOrigin(0.5).setDepth(D.zoneLabel); }).setOrigin(0.5).setDepth(D.zoneLabel);
this.add.text(z.center.x, z.center.y + 24, z.payout, { this.add.text(z.center.x, z.center.y + 24, z.payout, {
fontFamily: '"Julius Sans One"', fontSize: '11px', color: COLORS.accentHex, fontFamily: '"Julius Sans One"', fontSize: '11px', color: COLORS.accentHex,
@ -218,14 +218,14 @@ export default class CrapsGame extends Phaser.Scene {
_renderLineZone(z, text, fontSize) { _renderLineZone(z, text, fontSize) {
this.add.text(z.center.x, z.center.y, text, { this.add.text(z.center.x, z.center.y, text, {
fontFamily: '"Julius Sans One"', fontSize: `${fontSize}px`, color: COLORS.textHex, fontFamily: 'Caesar', fontSize: `${fontSize}px`, color: COLORS.textHex,
letterSpacing: 14, letterSpacing: 14,
}).setOrigin(0.5).setDepth(D.zoneLabel); }).setOrigin(0.5).setDepth(D.zoneLabel);
} }
_renderDontZone(z, fontSize, ls) { _renderDontZone(z, fontSize, ls) {
this.add.text(z.center.x, z.center.y - 11, "DON'T PASS", { this.add.text(z.center.x, z.center.y - 11, "DON'T PASS", {
fontFamily: '"Julius Sans One"', fontSize: `${fontSize}px`, color: COLORS.textHex, fontFamily: 'Caesar', fontSize: `${fontSize}px`, color: COLORS.textHex,
letterSpacing: ls, letterSpacing: ls,
}).setOrigin(0.5).setDepth(D.zoneLabel); }).setOrigin(0.5).setDepth(D.zoneLabel);
this.add.text(z.center.x, z.center.y + 17, z.barText, { this.add.text(z.center.x, z.center.y + 17, z.barText, {
@ -235,10 +235,10 @@ export default class CrapsGame extends Phaser.Scene {
_renderDontComeZone(z) { _renderDontComeZone(z) {
this.add.text(z.center.x, z.center.y - 44, "DON'T", { this.add.text(z.center.x, z.center.y - 44, "DON'T", {
fontFamily: '"Julius Sans One"', fontSize: '18px', color: COLORS.textHex, fontFamily: 'Caesar', fontSize: '18px', color: COLORS.textHex,
}).setOrigin(0.5).setDepth(D.zoneLabel); }).setOrigin(0.5).setDepth(D.zoneLabel);
this.add.text(z.center.x, z.center.y, 'COME', { this.add.text(z.center.x, z.center.y, 'COME', {
fontFamily: '"Julius Sans One"', fontSize: '18px', color: COLORS.textHex, fontFamily: 'Caesar', fontSize: '18px', color: COLORS.textHex,
letterSpacing: 8, letterSpacing: 8,
}).setOrigin(0.5).setDepth(D.zoneLabel); }).setOrigin(0.5).setDepth(D.zoneLabel);
this.add.text(z.center.x, z.center.y + 44, z.barText, { this.add.text(z.center.x, z.center.y + 44, z.barText, {
@ -249,7 +249,7 @@ export default class CrapsGame extends Phaser.Scene {
_renderFieldZone(z) { _renderFieldZone(z) {
// "FIELD" label left-anchored // "FIELD" label left-anchored
this.add.text(z.rect.x + 55, z.center.y, 'FIELD', { this.add.text(z.rect.x + 55, z.center.y, 'FIELD', {
fontFamily: '"Julius Sans One"', fontSize: '20px', color: COLORS.textHex, fontFamily: 'Caesar', fontSize: '20px', color: COLORS.textHex,
letterSpacing: 6, letterSpacing: 6,
}).setOrigin(0.5).setDepth(D.zoneLabel); }).setOrigin(0.5).setDepth(D.zoneLabel);
@ -271,13 +271,13 @@ export default class CrapsGame extends Phaser.Scene {
cg.lineStyle(2, COLORS.gold, 1); cg.lineStyle(2, COLORS.gold, 1);
cg.strokeCircle(nx, ny, 22); cg.strokeCircle(nx, ny, 22);
this.add.text(nx, ny, String(n), { this.add.text(nx, ny, String(n), {
fontFamily: 'JqkasWild', fontSize: n === 12 ? '22px' : '22px', fontFamily: 'Caesar', fontSize: n === 12 ? '22px' : '22px',
color: COLORS.textDarkHex, color: COLORS.textDarkHex,
}).setOrigin(0.5).setDepth(D.zoneLabel); }).setOrigin(0.5).setDepth(D.zoneLabel);
} else { } else {
const fs = (n === 10 || n === 11) ? '24px' : '28px'; const fs = (n === 10 || n === 11) ? '24px' : '28px';
this.add.text(nx, ny, String(n), { this.add.text(nx, ny, String(n), {
fontFamily: 'JqkasWild', fontSize: fs, color: COLORS.textHex, fontFamily: 'Caesar', fontSize: fs, color: COLORS.textHex,
}).setOrigin(0.5).setDepth(D.zoneLabel); }).setOrigin(0.5).setDepth(D.zoneLabel);
} }
}); });
@ -327,7 +327,7 @@ export default class CrapsGame extends Phaser.Scene {
buildTooltip() { buildTooltip() {
const bg = this.add.graphics(); const bg = this.add.graphics();
const title = this.add.text(0, 0, '', { const title = this.add.text(0, 0, '', {
fontFamily: 'JqkasWild', fontSize: '30px', color: COLORS.goldHex, fontFamily: 'Caesar', fontSize: '30px', color: COLORS.goldHex,
}); });
const desc = this.add.text(0, 0, '', { const desc = this.add.text(0, 0, '', {
fontFamily: '"Julius Sans One"', fontSize: '22px', color: COLORS.textHex, fontFamily: '"Julius Sans One"', fontSize: '22px', color: COLORS.textHex,
@ -389,7 +389,7 @@ export default class CrapsGame extends Phaser.Scene {
this.puck.add(g); this.puck.add(g);
this.puckGfx = g; this.puckGfx = g;
this.puckText = this.add.text(0, 0, 'OFF', { this.puckText = this.add.text(0, 0, 'OFF', {
fontFamily: 'JqkasWild', fontSize: '18px', color: '#1a1208', fontFamily: 'Caesar', fontSize: '18px', color: '#1a1208',
}).setOrigin(0.5); }).setOrigin(0.5);
this.puck.add(this.puckText); this.puck.add(this.puckText);
this.drawPuck(false); this.drawPuck(false);

View File

@ -32,6 +32,7 @@ const CARD_H = 118;
const CARD_R = 8; const CARD_R = 8;
const FAN_Y = 26; // vertical fan offset for work piles const FAN_Y = 26; // vertical fan offset for work piles
const WASTE_FAN = 30; // horizontal fan for the waste's visible cards const WASTE_FAN = 30; // horizontal fan for the waste's visible cards
const NERTS_FAN = 8; // horizontal fan offset for the nerts pile cards
const D = { const D = {
felt: -1, pile: 5, card: 10, drag: 40, ui: 30, fly: 50, panel: 28, modal: 80, felt: -1, pile: 5, card: 10, drag: 40, ui: 30, fly: 50, panel: 28, modal: 80,
@ -50,7 +51,7 @@ const FOUND_ROW_Y = [286, 286 + CARD_H + 24];
// x values are recomputed in buildFoundations() based on player count; NERTS x mirrors STOCK x // x values are recomputed in buildFoundations() based on player count; NERTS x mirrors STOCK x
let NERTS_POS = { x: 340, y: 640 - CARD_H - 16 }; let NERTS_POS = { x: 340, y: 640 - CARD_H - 16 };
let WORK_TOP_Y = 540; let WORK_TOP_Y = 540;
const WORK_X = [540, 720, 900, 1080]; const WORK_X = [800, 990, 1180, 1370];
let STOCK_POS = { x: 1470, y: 640 }; let STOCK_POS = { x: 1470, y: 640 };
let WASTE_POS = { x: 1600, y: 640 }; let WASTE_POS = { x: 1600, y: 640 };
const LOCAL_PORTRAIT = { x: 130, y: 820, r: 58 }; const LOCAL_PORTRAIT = { x: 130, y: 820, r: 58 };
@ -83,6 +84,7 @@ export default class NertsGame extends Phaser.Scene {
this.foundationCardObjs = []; // foundation top-card sprites this.foundationCardObjs = []; // foundation top-card sprites
this.foundationPos = []; // idx → {x,y} this.foundationPos = []; // idx → {x,y}
this.foundationSlotRects = []; this.foundationSlotRects = [];
this.foundationHints = []; // idx → { bg, label } shown when slot is empty
this.oppPanelPos = []; // seat → {x,y} (portrait pos, for fly origin) this.oppPanelPos = []; // seat → {x,y} (portrait pos, for fly origin)
this.oppDynamic = []; // seat → { nertsText, scoreText } this.oppDynamic = []; // seat → { nertsText, scoreText }
this.opponentPortraits = []; this.opponentPortraits = [];
@ -98,6 +100,7 @@ export default class NertsGame extends Phaser.Scene {
this.potentialDrag = null; this.potentialDrag = null;
this.dragState = null; this.dragState = null;
this.dropHighlight = null; this.dropHighlight = null;
this.foundationPulseObjs = [];
this.roundEnding = false; this.roundEnding = false;
this.panelObjs = []; this.panelObjs = [];
} }
@ -137,15 +140,15 @@ export default class NertsGame extends Phaser.Scene {
const total = 4 * this.playerCount; const total = 4 * this.playerCount;
const countInRow0 = Math.min(FOUND_PER_ROW, total); const countInRow0 = Math.min(FOUND_PER_ROW, total);
const rowW = countInRow0 * CARD_W + (countInRow0 - 1) * FOUND_GAP_X; const rowW = countInRow0 * CARD_W + (countInRow0 - 1) * FOUND_GAP_X;
const PAD = 14; // Nerts + stock + waste live on the left; work piles are on the right.
STOCK_POS.x = CX + rowW / 2 + PAD + CARD_W / 2 - 150; NERTS_POS.x = 600;
NERTS_POS.x = STOCK_POS.x; STOCK_POS.x = 495;
WASTE_POS.x = STOCK_POS.x + CARD_W + FOUND_GAP_X; WASTE_POS.x = 605;
const yOffset = this.opponents.length >= 2 ? 100 : 0; const yOffset = this.opponents.length >= 2 ? 100 : 0;
WORK_TOP_Y = 540 + yOffset; WORK_TOP_Y = 540 + yOffset;
STOCK_POS.y = 640 + yOffset; STOCK_POS.y = 715 + yOffset;
WASTE_POS.y = 640 + yOffset; WASTE_POS.y = 715 + yOffset;
NERTS_POS.y = 640 - CARD_H - 16 + yOffset; NERTS_POS.y = 640 - CARD_H - 16 + yOffset;
for (let idx = 0; idx < total; idx++) { for (let idx = 0; idx < total; idx++) {
@ -160,6 +163,17 @@ export default class NertsGame extends Phaser.Scene {
const r = this.add.rectangle(x, y, CARD_W + 4, CARD_H + 4, 0x000000, 0.22) const r = this.add.rectangle(x, y, CARD_W + 4, CARD_H + 4, 0x000000, 0.22)
.setStrokeStyle(2, COLORS.muted, 0.5).setDepth(D.pile); .setStrokeStyle(2, COLORS.muted, 0.5).setDepth(D.pile);
this.foundationSlotRects[idx] = r; this.foundationSlotRects[idx] = r;
const HINT_SUITS = ['♠', '♥', '♦', '♣'];
const HINT_COLORS = ['#aab0cc', '#cc8888', '#cc8888', '#aab0cc'];
const suit = idx % 4;
const hintBg = this.add.graphics().setDepth(D.card - 1);
hintBg.fillStyle(0xffffff, 0.45);
hintBg.fillRoundedRect(x - CARD_W / 2, y - CARD_H / 2, CARD_W, CARD_H, CARD_R);
const hintLabel = this.add.text(x, y, HINT_SUITS[suit], {
fontFamily: 'Arial', fontSize: '52px', color: HINT_COLORS[suit],
}).setOrigin(0.5).setAlpha(0.95).setDepth(D.card - 1);
this.foundationHints[idx] = { bg: hintBg, label: hintLabel };
} }
this.add.text(CX, FOUND_ROW_Y[0] - CARD_H / 2 - 26, 'FOUNDATIONS — play Aces here, build up by suit', { this.add.text(CX, FOUND_ROW_Y[0] - CARD_H / 2 - 26, 'FOUNDATIONS — play Aces here, build up by suit', {
fontFamily: '"Julius Sans One"', fontSize: '18px', color: COLORS.mutedHex, fontFamily: '"Julius Sans One"', fontSize: '18px', color: COLORS.mutedHex,
@ -458,17 +472,19 @@ export default class NertsGame extends Phaser.Scene {
this.clearLocalCards(); this.clearLocalCards();
const p = this.gs.players[0]; const p = this.gs.players[0];
// Nerts pile: a back beneath (if >1) and the face-up top. // Nerts pile: fan left-to-right so card count is visible; top card face-up and draggable.
if (p.nerts.length > 1) { p.nerts.forEach((card, i) => {
this.localExtraObjs.push( const x = NERTS_POS.x - (p.nerts.length - 1 - i) * NERTS_FAN;
this.makeCardSprite({ id: 'nerts-back' }, NERTS_POS.x, NERTS_POS.y, { faceUp: false, store: false }) const isTop = i === p.nerts.length - 1;
); if (isTop) {
} const c = this.makeCardSprite(card, x, NERTS_POS.y, { faceUp: true });
const nt = p.nerts[p.nerts.length - 1]; this.makeDraggable(c, { kind: 'nerts' });
if (nt) { } else {
const c = this.makeCardSprite(nt, NERTS_POS.x, NERTS_POS.y, { faceUp: true }); this.localExtraObjs.push(
this.makeDraggable(c, { kind: 'nerts' }); this.makeCardSprite(card, x, NERTS_POS.y, { faceUp: false, store: false })
} );
}
});
this.localNertsText.setText(`${p.nerts.length} left`); this.localNertsText.setText(`${p.nerts.length} left`);
// Work piles: fan downward; cards starting a valid run are draggable. // Work piles: fan downward; cards starting a valid run are draggable.
@ -512,7 +528,10 @@ export default class NertsGame extends Phaser.Scene {
const slot = this.gs.foundations[idx]; const slot = this.gs.foundations[idx];
const complete = slot && slot.cards.length > 0 && slot.cards[slot.cards.length - 1].rank === 'K'; const complete = slot && slot.cards.length > 0 && slot.cards[slot.cards.length - 1].rank === 'K';
this.foundationSlotRects[idx]?.setFillStyle(complete ? 0x111111 : 0x000000, complete ? 0.5 : 0.22); this.foundationSlotRects[idx]?.setFillStyle(complete ? 0x111111 : 0x000000, complete ? 0.5 : 0.22);
if (!slot || slot.cards.length === 0) continue; const empty = !slot || slot.cards.length === 0;
this.foundationHints[idx]?.bg.setVisible(empty);
this.foundationHints[idx]?.label.setVisible(empty);
if (empty) continue;
const top = slot.cards[slot.cards.length - 1]; const top = slot.cards[slot.cards.length - 1];
const pos = this.foundationPos[idx]; const pos = this.foundationPos[idx];
const c = this.makeCardSprite(top, pos.x, pos.y, { const c = this.makeCardSprite(top, pos.x, pos.y, {
@ -601,6 +620,10 @@ export default class NertsGame extends Phaser.Scene {
g.strokeRoundedRect(x + 6, y + 6, CARD_W - 12, CARD_H - 12, CARD_R - 2); g.strokeRoundedRect(x + 6, y + 6, CARD_W - 12, CARD_H - 12, CARD_R - 2);
container.add(g); container.add(g);
} }
const edge = this.add.graphics();
edge.lineStyle(1.5, 0xffffff, 0.28);
edge.strokeRoundedRect(x, y, CARD_W, CARD_H, CARD_R);
container.add(edge);
return; return;
} }
@ -690,6 +713,25 @@ export default class NertsGame extends Phaser.Scene {
return []; return [];
} }
startFoundationPulse(card) {
if (!card || card.rank !== 'A') return;
for (let f = 0; f < this.foundationPos.length; f++) {
if (!canPlayOnFoundation(this.gs, card, f)) continue;
const { x, y } = this.foundationPos[f];
const rect = this.add.rectangle(x, y, CARD_W + 18, CARD_H + 18, 0xffd700, 0.55)
.setStrokeStyle(3, 0xffd700, 1).setDepth(D.card - 1).setAlpha(0.55);
const tween = this.tweens.add({
targets: rect, alpha: 0.15, duration: 520, ease: 'Sine.easeInOut', yoyo: true, repeat: -1,
});
this.foundationPulseObjs.push({ rect, tween });
}
}
stopFoundationPulse() {
for (const { rect, tween } of this.foundationPulseObjs) { tween.stop(); rect.destroy(); }
this.foundationPulseObjs = [];
}
setupDragHandlers() { setupDragHandlers() {
this.input.on('pointermove', (pointer) => { this.input.on('pointermove', (pointer) => {
if (!pointer.isDown) return; if (!pointer.isDown) return;
@ -719,6 +761,10 @@ export default class NertsGame extends Phaser.Scene {
this.tweens.add({ targets: obj, scaleX: 1.06, scaleY: 1.06, duration: 90 }); this.tweens.add({ targets: obj, scaleX: 1.06, scaleY: 1.06, duration: 90 });
}); });
this.dragState = pd; this.dragState = pd;
const dragCard = pd.descriptor.kind === 'nerts' ? nertsTop(this.gs, 0)
: pd.descriptor.kind === 'waste' ? wasteTop(this.gs, 0)
: workTop(this.gs, 0, pd.descriptor.idx);
this.startFoundationPulse(dragCard);
} }
updateDrag(pointer) { updateDrag(pointer) {
@ -759,6 +805,7 @@ export default class NertsGame extends Phaser.Scene {
const ds = this.dragState; const ds = this.dragState;
this.dragState = null; this.dragState = null;
if (this.dropHighlight) { this.dropHighlight.destroy(); this.dropHighlight = null; } if (this.dropHighlight) { this.dropHighlight.destroy(); this.dropHighlight = null; }
this.stopFoundationPulse();
const primary = ds.sprites[0].obj; const primary = ds.sprites[0].obj;
const target = this.getDropTargetAt(primary.x, primary.y); const target = this.getDropTargetAt(primary.x, primary.y);

View File

@ -24,6 +24,8 @@
import { Deck } from '../cards/Deck.js'; import { Deck } from '../cards/Deck.js';
export const NERTS_PILE_SIZE = 13; export const NERTS_PILE_SIZE = 13;
// Suit required to start each foundation slot (cycles every 4 slots).
export const FOUNDATION_SUITS = ['s', 'h', 'd', 'c'];
export const WORK_PILE_COUNT = 4; export const WORK_PILE_COUNT = 4;
export const STOCK_FLIP = 3; export const STOCK_FLIP = 3;
export const DEFAULT_TARGET_SCORE = 100; export const DEFAULT_TARGET_SCORE = 100;
@ -152,7 +154,7 @@ export function foundationTopRank(slot) {
export function canPlayOnFoundation(state, card, fIdx) { export function canPlayOnFoundation(state, card, fIdx) {
if (!card) return false; if (!card) return false;
const slot = state.foundations[fIdx]; const slot = state.foundations[fIdx];
if (!slot) return rv(card) === 1; // empty slot needs an Ace if (!slot) return rv(card) === 1 && card.suit === FOUNDATION_SUITS[fIdx % 4]; // Ace of required suit
if (card.suit !== slot.suit) return false; if (card.suit !== slot.suit) return false;
const top = foundationTopRank(slot); const top = foundationTopRank(slot);
if (top >= 13) return false; // completed if (top >= 13) return false; // completed

View File

@ -20,8 +20,8 @@
} }
@font-face { @font-face {
font-family: 'JqkasWild'; font-family: 'Caesar';
src: url('/assets/fonts/JqkasWild.ttf') format('truetype'); src: url('/assets/fonts/Caesar.ttf') format('truetype');
font-weight: normal; font-weight: normal;
font-style: normal; font-style: normal;
} }