diff --git a/assets/images/balatro-artwork.png b/assets/images/balatro-artwork.png new file mode 100644 index 0000000..9786091 Binary files /dev/null and b/assets/images/balatro-artwork.png differ diff --git a/assets/images/balatro-artwork.psd b/assets/images/balatro-artwork.psd new file mode 100644 index 0000000..30e70a2 Binary files /dev/null and b/assets/images/balatro-artwork.psd differ diff --git a/assets/images/balatro-packs.png b/assets/images/balatro-packs.png new file mode 100644 index 0000000..8ae40a4 Binary files /dev/null and b/assets/images/balatro-packs.png differ diff --git a/assets/images/balatro-packs.psd b/assets/images/balatro-packs.psd new file mode 100644 index 0000000..b3b4e89 Binary files /dev/null and b/assets/images/balatro-packs.psd differ diff --git a/assets/images/balatro-tarots.png b/assets/images/balatro-tarots.png new file mode 100644 index 0000000..c965f34 Binary files /dev/null and b/assets/images/balatro-tarots.png differ diff --git a/assets/images/balatro-tarots.psd b/assets/images/balatro-tarots.psd new file mode 100644 index 0000000..452395d Binary files /dev/null and b/assets/images/balatro-tarots.psd differ diff --git a/data/balatro-artwork.json b/data/balatro-artwork.json index 6940eed..6b473e6 100644 --- a/data/balatro-artwork.json +++ b/data/balatro-artwork.json @@ -6,13 +6,13 @@ ], "jokerSheet": { "key": "balatro-jokers", - "path": null, + "path": "assets/images/balatro-artwork.png", "frameWidth": 150, "frameHeight": 200 }, "tarotSheet": { "key": "balatro-tarots", - "path": null, + "path": "assets/images/balatro-tarots.png", "frameWidth": 150, "frameHeight": 200 }, @@ -36,7 +36,7 @@ }, "packSheet": { "key": "balatro-packs", - "path": null, + "path": "assets/images/balatro-packs.png", "frameWidth": 180, "frameHeight": 240 }, diff --git a/src/games/balatro/BalatroCrtPipeline.js b/src/games/balatro/BalatroCrtPipeline.js new file mode 100644 index 0000000..9218fbe --- /dev/null +++ b/src/games/balatro/BalatroCrtPipeline.js @@ -0,0 +1,109 @@ +// BalatroCrtPipeline.js +// A subtle full-screen CRT treatment applied as a camera post-FX pipeline +// (like the real game's optional CRT shader): constant faint scanlines and +// grain, plus short "glitch" bursts — horizontal band tearing and RGB +// chromatic aberration — that fire ambiently every so often and can be +// pulsed harder from game events (boss rounds, big hands, game over). +// Canvas renderer (no WebGL) gets a no-op stub; the game is unaffected. + +import * as Phaser from 'phaser'; + +// Tuning knobs. Everything is keyed to "subtle at rest". +const SCANLINE_STRENGTH = 0.12; // max darkening of a scanline trough (0..1) +const SCANLINE_PERIOD = 6.0; // scanline spacing in framebuffer pixels +const GRAIN_STRENGTH = 0.015; // constant animated noise amplitude +const BAND_COUNT = 24.0; // horizontal tear bands during a glitch +const TEAR_STRENGTH = 0.04; // max uv.x displacement of a torn band +const ABERRATION = 0.006; // max R/B channel offset during a glitch +const PULSE_DECAY = 0.92; // per-frame decay of the glitch envelope +const AMBIENT_STRENGTH = 0.35; // envelope kick of an ambient micro-glitch +const AMBIENT_MIN_MS = 8000; // ambient glitch interval range +const AMBIENT_MAX_MS = 15000; + +const FRAG = ` +precision mediump float; + +uniform sampler2D uMainSampler; +uniform float uTime; +uniform float uGlitch; +uniform vec2 uResolution; + +varying vec2 outTexCoord; + +float hash(vec2 p) { + return fract(sin(dot(p, vec2(127.1, 311.7))) * 43758.5453123); +} + +void main(void) { + vec2 uv = outTexCoord; + + // Horizontal band tearing: a minority of bands shear sideways, with the + // pattern re-rolled ~14 times a second so a burst reads as flickery. + float band = floor(uv.y * ${BAND_COUNT.toFixed(1)}); + float h = hash(vec2(band, floor(uTime * 14.0))); + float tear = step(0.72, h) * (h - 0.5) * ${TEAR_STRENGTH.toFixed(3)} * uGlitch; + uv.x = clamp(uv.x + tear, 0.0, 1.0); + + // RGB split, only during a glitch so text stays crisp at rest. + float ca = ${ABERRATION.toFixed(4)} * uGlitch; + vec3 col; + col.r = texture2D(uMainSampler, vec2(clamp(uv.x + ca, 0.0, 1.0), uv.y)).r; + col.g = texture2D(uMainSampler, uv).g; + col.b = texture2D(uMainSampler, vec2(clamp(uv.x - ca, 0.0, 1.0), uv.y)).b; + + // Constant faint scanlines in framebuffer pixels. + float scan = 0.5 + 0.5 * sin(gl_FragCoord.y * 6.28318 / ${SCANLINE_PERIOD.toFixed(1)}); + col *= 1.0 - ${SCANLINE_STRENGTH.toFixed(3)} * scan; + + // Faint animated grain, a touch stronger while glitching. + float grain = hash(gl_FragCoord.xy + fract(uTime) * 61.7) - 0.5; + col += grain * (${GRAIN_STRENGTH.toFixed(3)} + 0.03 * uGlitch); + + gl_FragColor = vec4(col, 1.0); +} +`; + +export class BalatroCrtPipeline extends Phaser.Renderer.WebGL.Pipelines.PostFXPipeline { + constructor(game) { + super({ game, name: 'BalatroCrt', fragShader: FRAG }); + this._pulse = 0; + this._nextAmbient = 0; + } + + onPreRender() { + const t = this.game.loop.time; + if (t > this._nextAmbient) { + // First tick only schedules; later ticks fire a micro-glitch. + if (this._nextAmbient > 0) this._pulse = Math.max(this._pulse, AMBIENT_STRENGTH); + this._nextAmbient = t + AMBIENT_MIN_MS + Math.random() * (AMBIENT_MAX_MS - AMBIENT_MIN_MS); + } + this._pulse *= PULSE_DECAY; + if (this._pulse < 0.005) this._pulse = 0; + + this.set1f('uTime', t / 1000); + this.set1f('uGlitch', this._pulse); + this.set2f('uResolution', this.renderer.width, this.renderer.height); + } + + // Event hook: momentarily intensify the glitch (0..1). + pulse(strength = 0.5) { + this._pulse = Math.min(1, Math.max(this._pulse, strength)); + } +} + +// Attach the CRT post-FX to the scene's main camera. Returns a control +// object mirroring makeSwirlBackground's shape; on Canvas it is a stub. +export function attachCrt(scene) { + if (!scene.renderer || scene.renderer.type !== Phaser.WEBGL) { + return { pulse() {}, destroy() {} }; + } + scene.renderer.pipelines.addPostPipeline('BalatroCrt', BalatroCrtPipeline); + const cam = scene.cameras.main; + cam.setPostPipeline(BalatroCrtPipeline); + const got = cam.getPostPipeline(BalatroCrtPipeline); + const inst = Array.isArray(got) ? got[0] : got; + return { + pulse(strength) { if (inst && inst.pulse) inst.pulse(strength); }, + destroy() { cam.removePostPipeline(BalatroCrtPipeline); }, + }; +} diff --git a/src/games/balatro/BalatroGame.js b/src/games/balatro/BalatroGame.js index b30d00f..0e8b0b5 100644 --- a/src/games/balatro/BalatroGame.js +++ b/src/games/balatro/BalatroGame.js @@ -28,6 +28,7 @@ import { skipPack, useConsumable, consumableDef, isDebuffed, } from './BalatroLogic.js'; import { makeSwirlBackground } from './BalatroSwirlPipeline.js'; +import { attachCrt } from './BalatroCrtPipeline.js'; import { renderTitle, renderDeckSelect, renderBlindSelect, renderShop, renderPackOpen, renderGameOver, renderCashout, @@ -99,6 +100,8 @@ export default class BalatroGame extends Phaser.Scene { this.handLayer = this.add.container(0, 0).setDepth(40); this.fxLayer = this.add.container(0, 0).setDepth(80); this.swirl = makeSwirlBackground(this, this.bgLayer); + this.crt = attachCrt(this); + this.events.once('shutdown', () => this.crt.destroy()); // Fast-forward: tapping during a scoring animation snaps to the end. this.input.on('pointerdown', () => { if (this.animating) this._skipAnim = true; }); @@ -204,6 +207,8 @@ export default class BalatroGame extends Phaser.Scene { this.selected = []; this.pendingConsumable = null; this.pendingJoker = null; + if (v === 'play' && this.run && this.run.blind === 'boss') this.crt.pulse(0.7); + if (v === 'gameover') this.crt.pulse(1.0); this.renderView(); } @@ -279,7 +284,73 @@ export default class BalatroGame extends Phaser.Scene { const map = this.art[mapKey]; if (!sheet || !map || map[id] === undefined) return null; if (!this.textures.exists(sheet.key)) return null; - return { key: sheet.key, frame: map[id] }; + const frame = map[id]; + if (this.frameIsBlank(sheet.key, frame)) return null; + return { key: sheet.key, frame }; + } + + // Sheets are painted incrementally — unpainted frames are left transparent/white. + // Sample a few points per frame and treat it as "not yet drawn" if none hit ink, + // so unfinished sheets fall back to procedural cards instead of showing blanks. + frameIsBlank(key, frame) { + if (!this._blankFrameCache) this._blankFrameCache = {}; + const cacheKey = `${key}:${frame}`; + if (this._blankFrameCache[cacheKey] !== undefined) return this._blankFrameCache[cacheKey]; + const src = this.textures.getFrame(key, frame); + let blank = true; + if (src) { + const steps = 5; + outer: + for (let i = 1; i < steps; i++) { + for (let j = 1; j < steps; j++) { + const x = Math.floor((i / steps) * src.width); + const y = Math.floor((j / steps) * src.height); + const px = this.textures.getPixel(x, y, key, frame); + if (px && px.alpha > 10 && !(px.red > 245 && px.green > 245 && px.blue > 245)) { blank = false; break outer; } + } + } + } + this._blankFrameCache[cacheKey] = blank; + return blank; + } + + // Bakes a rounded-corner copy of a sheet frame into its own canvas texture + // (cached by key:frame:radius) so drop-in art matches the rounded look of + // the procedural cards without needing a live geometry mask on the sprite. + roundedArtKey(key, frame, radius = 10) { + if (!this._roundedArtCache) this._roundedArtCache = {}; + const cacheKey = `${key}:${frame}:${radius}`; + if (this._roundedArtCache[cacheKey]) return this._roundedArtCache[cacheKey]; + const src = this.textures.getFrame(key, frame); + if (!src) return null; + const outKey = `balatro-rounded:${cacheKey}`; + if (!this.textures.exists(outKey)) { + const w = src.cutWidth, h = src.cutHeight; + const r = Math.min(radius, w / 2, h / 2); + const canvasTex = this.textures.createCanvas(outKey, w, h); + const ctx = canvasTex.getContext(); + ctx.beginPath(); + ctx.moveTo(r, 0); + ctx.arcTo(w, 0, w, h, r); + ctx.arcTo(w, h, 0, h, r); + ctx.arcTo(0, h, 0, 0, r); + ctx.arcTo(0, 0, w, 0, r); + ctx.closePath(); + ctx.clip(); + ctx.drawImage(src.source.image, src.cutX, src.cutY, w, h, 0, 0, w, h); + canvasTex.refresh(); + } + this._roundedArtCache[cacheKey] = outKey; + return outKey; + } + + // Draws a drop-in art frame with rounded corners at (0,0) inside a container. + addRoundedArt(container, key, frame, w, h, radius, x = 0, y = 0) { + const roundedKey = this.roundedArtKey(key, frame, radius); + const img = roundedKey ? this.add.image(x, y, roundedKey) : this.add.image(x, y, key, frame); + img.setDisplaySize(w, h); + container.add(img); + return img; } // ── card rendering ──────────────────────────────────────────────────────── @@ -378,8 +449,10 @@ export default class BalatroGame extends Phaser.Scene { const cont = this.add.container(x, y); const af = this.artFrame('jokerSheet', 'jokers', id); if (af) { - const img = this.add.image(0, 0, af.key, af.frame).setDisplaySize(w, h); - cont.add(img); + this.addRoundedArt(cont, af.key, af.frame, w, h, 10); + const rg = this.add.graphics(); + rg.lineStyle(3, C.rarity[def.rarity], 1); rg.strokeRoundedRect(-w / 2, -h / 2, w, h, 10); + cont.add(rg); } else { const g = this.add.graphics(); g.fillStyle(0x241d33, 1); g.fillRoundedRect(-w / 2, -h / 2, w, h, 10); @@ -387,11 +460,11 @@ export default class BalatroGame extends Phaser.Scene { g.lineStyle(2, C.rarity[def.rarity], 1); g.strokeRoundedRect(-w / 2, -h / 2, w, h, 10); cont.add(g); const name = this.add.text(0, -h / 2 + 15, def.name, { - fontFamily: 'm6x11, "Julius Sans One"', fontSize: `${Math.min(17, Math.round(280 / def.name.length))}px`, color: '#141019', fontStyle: 'bold', + fontFamily: 'm6x11, "Julius Sans One"', fontSize: `${Math.min(20, Math.round(320 / def.name.length))}px`, color: '#141019', fontStyle: 'bold', }).setOrigin(0.5); const desc = typeof def.desc === 'function' ? def.desc(opts.inst || { state: def.initState ? def.initState() : {} }) : def.desc; const body = this.add.text(0, 8, desc, { - fontFamily: 'm6x11, "Julius Sans One"', fontSize: '15px', color: C.ink, align: 'center', + fontFamily: 'm6x11, "Julius Sans One"', fontSize: '17px', color: C.ink, align: 'center', wordWrap: { width: w - 16 }, }).setOrigin(0.5); cont.add([name, body]); @@ -403,14 +476,14 @@ export default class BalatroGame extends Phaser.Scene { eg.lineStyle(4, edColor, 0.95); eg.strokeRoundedRect(-w / 2, -h / 2, w, h, 10); cont.add(eg); const tag = this.add.text(0, h / 2 - 16, EDITIONS[edition].name.toUpperCase(), { - fontFamily: 'm6x11, "Julius Sans One"', fontSize: '11px', color: '#ffffff', + fontFamily: 'm6x11, "Julius Sans One"', fontSize: '13px', color: '#ffffff', }).setOrigin(0.5).setAlpha(0.9); cont.add(tag); if (edition === 'negative') cont.setAlpha(0.8); } if (opts.inst && opts.inst.debuffedByBoss) { cont.add(this.add.rectangle(0, 0, w, h, 0x1a1420, 0.6)); - cont.add(this.add.text(0, 0, 'DISABLED', { fontFamily: 'm6x11, "Julius Sans One"', fontSize: '16px', color: '#c86a6a' }).setOrigin(0.5)); + cont.add(this.add.text(0, 0, 'DISABLED', { fontFamily: 'm6x11, "Julius Sans One"', fontSize: '18px', color: '#c86a6a' }).setOrigin(0.5)); } cont.setSize(w, h); return cont; @@ -424,7 +497,7 @@ export default class BalatroGame extends Phaser.Scene { const sheet = { tarot: ['tarotSheet', 'tarots'], planet: ['planetSheet', 'planets'], spectral: ['spectralSheet', 'spectrals'] }[kind]; const af = this.artFrame(sheet[0], sheet[1], id); if (af) { - cont.add(this.add.image(0, 0, af.key, af.frame).setDisplaySize(w, h)); + this.addRoundedArt(cont, af.key, af.frame, w, h, 10); } else { const tint = { tarot: C.tarot, planet: C.planet, spectral: C.spectral }[kind]; const g = this.add.graphics(); @@ -433,16 +506,16 @@ export default class BalatroGame extends Phaser.Scene { g.lineStyle(1, tint, 0.6); g.strokeRoundedRect(-w / 2 + 6, -h / 2 + 6, w - 12, h - 12, 8); cont.add(g); const name = this.add.text(0, -h / 2 + 20, def.name, { - fontFamily: 'm6x11, "Julius Sans One"', fontSize: `${Math.min(16, Math.round(300 / def.name.length))}px`, + fontFamily: 'm6x11, "Julius Sans One"', fontSize: `${Math.min(19, Math.round(340 / def.name.length))}px`, color: `#${tint.toString(16).padStart(6, '0')}`, fontStyle: 'bold', align: 'center', wordWrap: { width: w - 14 }, }).setOrigin(0.5, 0); const descText = kind === 'planet' ? `+1 level: ${HAND_BY_ID[def.hand].name}` : def.desc; const body = this.add.text(0, 16, descText, { - fontFamily: 'm6x11, "Julius Sans One"', fontSize: '14px', color: C.ink, align: 'center', wordWrap: { width: w - 18 }, + fontFamily: 'm6x11, "Julius Sans One"', fontSize: '16px', color: C.ink, align: 'center', wordWrap: { width: w - 18 }, }).setOrigin(0.5); cont.add([name, body]); const glyph = { tarot: '☽', planet: '✶', spectral: '✧' }[kind]; - cont.add(this.add.text(0, h / 2 - 22, glyph, { fontFamily: 'm6x11, Georgia, serif', fontSize: '20px', color: `#${tint.toString(16).padStart(6, '0')}` }).setOrigin(0.5)); + cont.add(this.add.text(0, h / 2 - 22, glyph, { fontFamily: 'm6x11, Georgia, serif', fontSize: '22px', color: `#${tint.toString(16).padStart(6, '0')}` }).setOrigin(0.5)); } cont.setSize(w, h); return cont; @@ -455,19 +528,19 @@ export default class BalatroGame extends Phaser.Scene { showTooltip(anchor, info) { this.hideTooltip(); if (!info || !anchor.active) return; - const W = info.width || 330, PAD = 16; + const W = info.width || 370, PAD = 16; const cont = this.add.container(0, 0); this.fxLayer.add(cont); let y = PAD; const title = this.add.text(PAD, y, info.title, { - fontFamily: 'm6x11, "Julius Sans One"', fontSize: '22px', color: info.titleColor || C.ink, + fontFamily: 'm6x11, "Julius Sans One"', fontSize: '26px', color: info.titleColor || C.ink, fontStyle: 'bold', wordWrap: { width: W - PAD * 2 }, }); cont.add(title); y += title.height + 4; if (info.tag) { const tag = this.add.text(PAD, y, info.tag, { - fontFamily: 'm6x11, "Julius Sans One"', fontSize: '14px', color: info.tagColor || C.muted, + fontFamily: 'm6x11, "Julius Sans One"', fontSize: '16px', color: info.tagColor || C.muted, }); cont.add(tag); y += tag.height + 8; @@ -476,7 +549,7 @@ export default class BalatroGame extends Phaser.Scene { } for (const line of info.lines || []) { const t = this.add.text(PAD, y, line.text, { - fontFamily: 'm6x11, "Julius Sans One"', fontSize: '16px', color: line.color || C.muted, + fontFamily: 'm6x11, "Julius Sans One"', fontSize: '19px', color: line.color || C.muted, wordWrap: { width: W - PAD * 2 }, }); cont.add(t); @@ -769,14 +842,14 @@ export default class BalatroGame extends Phaser.Scene { this.dismissPanel(); const cont = this.add.container(0, 0).setDepth(92); this.fxLayer.add(cont); - const px = 700, py = 280, pw = 460, ph = 190; + const px = 700, py = 280, pw = 460, ph = 210; const g = this.add.graphics(); g.fillStyle(C.panel, 0.97); g.fillRoundedRect(px, py, pw, ph, 12); g.lineStyle(2, C.rarity[def.rarity], 1); g.strokeRoundedRect(px, py, pw, ph, 12); cont.add(g); const desc = typeof def.desc === 'function' ? def.desc(inst) : def.desc; - cont.add(this.add.text(px + 20, py + 14, `${def.name}${inst.edition ? ` (${EDITIONS[inst.edition].name})` : ''}`, { fontFamily: 'm6x11, "Julius Sans One"', fontSize: '24px', color: C.ink })); - cont.add(this.add.text(px + 20, py + 52, desc, { fontFamily: 'm6x11, "Julius Sans One"', fontSize: '17px', color: C.muted, wordWrap: { width: pw - 40 } })); + cont.add(this.add.text(px + 20, py + 14, `${def.name}${inst.edition ? ` (${EDITIONS[inst.edition].name})` : ''}`, { fontFamily: 'm6x11, "Julius Sans One"', fontSize: '28px', color: C.ink })); + cont.add(this.add.text(px + 20, py + 56, desc, { fontFamily: 'm6x11, "Julius Sans One"', fontSize: '20px', color: C.muted, wordWrap: { width: pw - 40 } })); const sell = new Button(this, px + pw - 110, py + ph - 40, `Sell $${inst.sellValue}`, () => { const r = sellJoker(this.run, inst.uid); if (r.ok) { playSound(this, SFX.COINS); this.save(); this.renderView(); } @@ -817,17 +890,17 @@ export default class BalatroGame extends Phaser.Scene { this.dismissPanel(); const cont = this.add.container(0, 0).setDepth(92); this.fxLayer.add(cont); - const px = 1230, py = 260, pw = 470, ph = 210; + const px = 1230, py = 260, pw = 470, ph = 230; const g = this.add.graphics(); g.fillStyle(C.panel, 0.97); g.fillRoundedRect(px, py, pw, ph, 12); const tint = { tarot: C.tarot, planet: C.planet, spectral: C.spectral }[inst.kind]; g.lineStyle(2, tint, 1); g.strokeRoundedRect(px, py, pw, ph, 12); cont.add(g); - cont.add(this.add.text(px + 20, py + 14, def.name, { fontFamily: 'm6x11, "Julius Sans One"', fontSize: '24px', color: C.ink })); + cont.add(this.add.text(px + 20, py + 14, def.name, { fontFamily: 'm6x11, "Julius Sans One"', fontSize: '28px', color: C.ink })); const descText = inst.kind === 'planet' ? `+1 level: ${HAND_BY_ID[def.hand].name}` : def.desc; - cont.add(this.add.text(px + 20, py + 52, descText, { fontFamily: 'm6x11, "Julius Sans One"', fontSize: '17px', color: C.muted, wordWrap: { width: pw - 40 } })); + cont.add(this.add.text(px + 20, py + 56, descText, { fontFamily: 'm6x11, "Julius Sans One"', fontSize: '20px', color: C.muted, wordWrap: { width: pw - 40 } })); const needs = def.targets || 0; - if (needs) cont.add(this.add.text(px + 20, py + ph - 96, `Select up to ${needs} card${needs > 1 ? 's' : ''} in hand first`, { fontFamily: 'm6x11, "Julius Sans One"', fontSize: '15px', color: '#a8d8ff' })); + if (needs) cont.add(this.add.text(px + 20, py + ph - 96, `Select up to ${needs} card${needs > 1 ? 's' : ''} in hand first`, { fontFamily: 'm6x11, "Julius Sans One"', fontSize: '17px', color: '#a8d8ff' })); const use = new Button(this, px + pw - 110, py + ph - 40, 'Use', () => { const r = useConsumable(this.run, inst.uid, this.selected.slice(0, Math.max(needs, 2))); if (!r.ok) { this.toast(r.error); return; } @@ -1170,7 +1243,7 @@ export default class BalatroGame extends Phaser.Scene { case 'xmult': floater(sprite, `×${ev.v}`, '#ffa24f'); pop(sprite, true); playSound(this, SFX.SCIFI_PLINK); - if (ev.v >= 2) this.cameras.main.shake(120, 0.004); + if (ev.v >= 2) { this.cameras.main.shake(120, 0.004); this.crt.pulse(0.5); } break; case 'money': floater(sprite, `+$${ev.v}`, C.money); playSound(this, SFX.COINS); if (this._goldText && this._goldText.active) this._goldText.setText(`$${this.run.gold}`); break; case 'retrigger': if (sprite) this.ringPulse(sprite); break; @@ -1195,6 +1268,7 @@ export default class BalatroGame extends Phaser.Scene { this.fxLayer.add(slam); this.tweens.add({ targets: slam, alpha: 1, scale: 1, duration: 180, ease: 'Back.easeOut' }); this.cameras.main.shake(160, score >= this.run.blindChips ? 0.008 : 0.004); + this.crt.pulse(score >= this.run.blindChips ? 0.8 : 0.4); playSound(this, score >= 10000 ? SFX.SCIFI_EXPLODE : SFX.CASINO_BLACKJACK); this._roundScoreOverride = null; if (this._roundScoreText && this._roundScoreText.active) this._roundScoreText.setText(fmtChips(this.run.roundScore)); diff --git a/src/games/balatro/BalatroViews.js b/src/games/balatro/BalatroViews.js index 19067ac..199aaf0 100644 --- a/src/games/balatro/BalatroViews.js +++ b/src/games/balatro/BalatroViews.js @@ -86,7 +86,7 @@ export function renderDeckSelect(scene) { cont.add(g); // Deck back: art frame if present, else colored card back. const af = scene.artFrame('deckBackSheet', 'deckBacks', deck.id); - if (af) cont.add(scene.add.image(0, -60, af.key, af.frame).setDisplaySize(120, 164)); + if (af) scene.addRoundedArt(cont, af.key, af.frame, 120, 164, 8, 0, -60); else { const bg = scene.add.graphics(); bg.fillStyle(deck.color, 0.95); bg.fillRoundedRect(-60, -142, 120, 164, 8); @@ -94,8 +94,8 @@ export function renderDeckSelect(scene) { bg.lineStyle(1, 0xffffff, 0.25); bg.strokeRoundedRect(-48, -130, 96, 140, 6); cont.add(bg); } - cont.add(scene.add.text(0, 52, deck.name, { fontFamily: 'm6x11, "Julius Sans One"', fontSize: '26px', color: C.ink, fontStyle: 'bold' }).setOrigin(0.5)); - cont.add(scene.add.text(0, 108, deck.desc, { fontFamily: 'm6x11, "Julius Sans One"', fontSize: '17px', color: C.muted, align: 'center', wordWrap: { width: w - 30 } }).setOrigin(0.5)); + cont.add(scene.add.text(0, 52, deck.name, { fontFamily: 'm6x11, "Julius Sans One"', fontSize: '28px', color: C.ink, fontStyle: 'bold' }).setOrigin(0.5)); + cont.add(scene.add.text(0, 108, deck.desc, { fontFamily: 'm6x11, "Julius Sans One"', fontSize: '19px', color: C.muted, align: 'center', wordWrap: { width: w - 30 } }).setOrigin(0.5)); cont.setSize(w, h); scene.add2(cont); scene.addHoverTilt(cont); @@ -240,7 +240,7 @@ export function renderShop(scene) { const def = BOOSTER_BY_ID[slot.id]; const cont = scene.add.container(x, py2); const af = scene.artFrame('packSheet', 'packs', slot.id); - if (af) cont.add(scene.add.image(0, 0, af.key, af.frame).setDisplaySize(180, 240)); + if (af) scene.addRoundedArt(cont, af.key, af.frame, 180, 240, 12); else { const tint = { playing: 0x4f9ad4, tarot: C.tarot, planet: C.planet, joker: 0xd4884f, spectral: C.spectral }[def.what]; const g = scene.add.graphics(); @@ -248,7 +248,7 @@ export function renderShop(scene) { g.lineStyle(3, 0xffffff, 0.5); g.strokeRoundedRect(-90, -120, 180, 240, 12); g.fillStyle(0x141019, 0.35); g.fillRoundedRect(-90, -30, 180, 60, 0); cont.add(g); - cont.add(scene.add.text(0, 0, def.name.replace(' Pack', '\nPack'), { fontFamily: 'm6x11, "Julius Sans One"', fontSize: '22px', color: '#ffffff', align: 'center', fontStyle: 'bold' }).setOrigin(0.5)); + cont.add(scene.add.text(0, 0, def.name.replace(' Pack', '\nPack'), { fontFamily: 'm6x11, "Julius Sans One"', fontSize: '25px', color: '#ffffff', align: 'center', fontStyle: 'bold' }).setOrigin(0.5)); } cont.setSize(180, 240); scene.add2(cont); @@ -277,15 +277,15 @@ export function renderShop(scene) { scene.text(vx, vy - 180, 'Voucher', 20, C.muted, { ox: 0.5 }); const cont = scene.add.container(vx, vy); const af = scene.artFrame('voucherSheet', 'vouchers', v.id); - if (af) cont.add(scene.add.image(0, 0, af.key, af.frame).setDisplaySize(190, 250)); + if (af) scene.addRoundedArt(cont, af.key, af.frame, 190, 250, 10); else { const g = scene.add.graphics(); g.fillStyle(0x143a2a, 0.95); g.fillRoundedRect(-95, -125, 190, 250, 10); g.lineStyle(3, C.voucher, 1); g.strokeRoundedRect(-95, -125, 190, 250, 10); g.lineStyle(1, C.voucher, 0.5); g.strokeRoundedRect(-85, -115, 170, 230, 8); cont.add(g); - cont.add(scene.add.text(0, -80, v.name, { fontFamily: 'm6x11, "Julius Sans One"', fontSize: '22px', color: '#7ce0c0', align: 'center', fontStyle: 'bold', wordWrap: { width: 170 } }).setOrigin(0.5)); - cont.add(scene.add.text(0, 20, v.desc, { fontFamily: 'm6x11, "Julius Sans One"', fontSize: '16px', color: C.ink, align: 'center', wordWrap: { width: 165 } }).setOrigin(0.5)); + cont.add(scene.add.text(0, -80, v.name, { fontFamily: 'm6x11, "Julius Sans One"', fontSize: '25px', color: '#7ce0c0', align: 'center', fontStyle: 'bold', wordWrap: { width: 170 } }).setOrigin(0.5)); + cont.add(scene.add.text(0, 20, v.desc, { fontFamily: 'm6x11, "Julius Sans One"', fontSize: '18px', color: C.ink, align: 'center', wordWrap: { width: 165 } }).setOrigin(0.5)); } cont.setSize(190, 250); scene.add2(cont);