Taller canvas + real background design for all 4 game templates

Phone screenshots showed the canvas was short and squat (960x600, 8:5)
leaving most of the screen blank, with Clicker's hero list and Quiz's
answer options too cramped to tap comfortably. Also the dark-mode canvas
background was the same near-black as the page, so games had no visible
frame and read as flat/undesigned.

Bumps canvas to 960x720 (4:3) and uses the extra room to enlarge hero
rows, quiz options/lifelines, and maze/snake grid cells. Adds a themed
gradient frame around the canvas in play-session.tsx and replaces each
scene's flat background fill with a subtle top-to-bottom gradient.
This commit is contained in:
ogrechko committed 2026-08-01 12:01:59 +00:00
1 parent 13bea64bb6
commit 65044fa828
6 files changed
+42 -26

No files matched your search

+2 -2
View File
@@ -51,7 +51,7 @@ export const GameCanvas = forwardRef<
mode: PhaserLib.Scale.FIT, mode: PhaserLib.Scale.FIT,
autoCenter: PhaserLib.Scale.CENTER_BOTH, autoCenter: PhaserLib.Scale.CENTER_BOTH,
width: 960, width: 960,
height: 600, height: 720,
}, },
scene: [SceneClass], scene: [SceneClass],
}); });
@@ -75,5 +75,5 @@ export const GameCanvas = forwardRef<
// eslint-disable-next-line react-hooks/exhaustive-deps // eslint-disable-next-line react-hooks/exhaustive-deps
}, [templateType]); }, [templateType]);
return <div ref={containerRef} className="aspect-[8/5] w-full overflow-hidden rounded-lg bg-black" />; return <div ref={containerRef} className="aspect-[4/3] w-full overflow-hidden rounded-lg bg-black" />;
}); });
+1 -1
View File
@@ -101,7 +101,7 @@ export function PlaySession({
return ( return (
<div className="mx-auto w-full max-w-4xl flex-1 px-1 py-4 sm:px-4 sm:py-6"> <div className="mx-auto w-full max-w-4xl flex-1 px-1 py-4 sm:px-4 sm:py-6">
<div className="relative"> <div className="relative rounded-xl border border-border bg-gradient-to-b from-accent-soft/40 to-surface p-2 shadow-lg sm:p-3">
<GameCanvas <GameCanvas
ref={gameCanvasRef} ref={gameCanvasRef}
key={attempt} key={attempt}
+8 -4
View File
@@ -30,7 +30,7 @@ const MONSTER_Y = 155;
const HP_BAR_Y = 258; const HP_BAR_Y = 258;
const HP_BAR_WIDTH = 500; const HP_BAR_WIDTH = 500;
const HEROES_START_Y = 308; const HEROES_START_Y = 308;
const HERO_ROW_GAP = 28; const HERO_ROW_GAP = 38;
export class ClickerScene extends Phaser.Scene { export class ClickerScene extends Phaser.Scene {
private definition!: ClickerDefinition; private definition!: ClickerDefinition;
@@ -80,7 +80,11 @@ export class ClickerScene extends Phaser.Scene {
this.heroLevels = this.definition.heroes.map((_, i) => initialState?.heroLevels[i] ?? 0); this.heroLevels = this.definition.heroes.map((_, i) => initialState?.heroLevels[i] ?? 0);
this.finished = false; this.finished = false;
this.cameras.main.setBackgroundColor(COLORS.bg); this.add
.graphics()
.fillGradientStyle(0x23264a, 0x23264a, COLORS.bg, COLORS.bg, 1)
.fillRect(0, 0, 960, 720)
.setDepth(-10);
this.levelText = this.add.text(20, 15, "", { fontFamily: "sans-serif", fontSize: "20px", color: "#a3a8c6" }); this.levelText = this.add.text(20, 15, "", { fontFamily: "sans-serif", fontSize: "20px", color: "#a3a8c6" });
this.goldText = this.add this.goldText = this.add
@@ -117,11 +121,11 @@ export class ClickerScene extends Phaser.Scene {
this.definition.heroes.forEach((_, index) => { this.definition.heroes.forEach((_, index) => {
const y = HEROES_START_Y + index * HERO_ROW_GAP; const y = HEROES_START_Y + index * HERO_ROW_GAP;
const bg = this.add.rectangle(480, y, 780, 26, COLORS.heroRow).setStrokeStyle(1, 0x4f46e5).setInteractive({ const bg = this.add.rectangle(480, y, 780, 34, COLORS.heroRow).setStrokeStyle(1, 0x4f46e5).setInteractive({
useHandCursor: true, useHandCursor: true,
}); });
const label = this.add const label = this.add
.text(480, y, "", { fontFamily: "sans-serif", fontSize: "17px", color: "#eef0fb" }) .text(480, y, "", { fontFamily: "sans-serif", fontSize: "20px", color: "#eef0fb" })
.setOrigin(0.5); .setOrigin(0.5);
bg.on("pointerdown", () => this.buyHero(index)); bg.on("pointerdown", () => this.buyHero(index));
this.heroRows.push({ bg, label }); this.heroRows.push({ bg, label });
+6 -2
View File
@@ -102,7 +102,11 @@ export class MazeScene extends Phaser.Scene {
this.finished = false; this.finished = false;
this.desiredDir = null; this.desiredDir = null;
this.currentDir = null; this.currentDir = null;
this.cameras.main.setBackgroundColor(COLORS.bg); this.add
.graphics()
.fillGradientStyle(0x14162a, 0x14162a, COLORS.bg, COLORS.bg, 1)
.fillRect(0, 0, 960, 720)
.setDepth(-10);
const initialState = this.registry.get("initialState") as MazeProgress | undefined; const initialState = this.registry.get("initialState") as MazeProgress | undefined;
this.collectedKeys = new Set(initialState?.collectedKeys ?? []); this.collectedKeys = new Set(initialState?.collectedKeys ?? []);
@@ -112,7 +116,7 @@ export class MazeScene extends Phaser.Scene {
this.grid = this.definition.grid; this.grid = this.definition.grid;
this.rows = this.grid.length; this.rows = this.grid.length;
this.cols = this.grid[0].length; this.cols = this.grid[0].length;
this.cellSize = Math.max(10, Math.min(34, Math.floor(900 / this.cols), Math.floor(420 / this.rows))); this.cellSize = Math.max(10, Math.min(40, Math.floor(900 / this.cols), Math.floor(550 / this.rows)));
this.offsetX = 480 - (this.cols * this.cellSize) / 2; this.offsetX = 480 - (this.cols * this.cellSize) / 2;
this.offsetY = 130; this.offsetY = 130;
+19 -15
View File
@@ -84,7 +84,11 @@ export class QuizScene extends Phaser.Scene {
this.usedQuestionKeys = new Set(); this.usedQuestionKeys = new Set();
this.finished = false; this.finished = false;
this.cameras.main.setBackgroundColor(COLORS.bg); this.add
.graphics()
.fillGradientStyle(0x23264a, 0x23264a, COLORS.bg, COLORS.bg, 1)
.fillRect(0, 0, 960, 720)
.setDepth(-10);
this.levelText = this.add.text(30, 18, "", { fontFamily: "sans-serif", fontSize: "22px", color: "#a3a8c6" }); this.levelText = this.add.text(30, 18, "", { fontFamily: "sans-serif", fontSize: "22px", color: "#a3a8c6" });
this.prizeText = this.add this.prizeText = this.add
@@ -113,16 +117,16 @@ export class QuizScene extends Phaser.Scene {
this.walkAwayButton.on("pointerdown", () => this.walkAway()); this.walkAwayButton.on("pointerdown", () => this.walkAway());
const lifelineDefs: { kind: LifelineKind; x: number; label: string }[] = [ const lifelineDefs: { kind: LifelineKind; x: number; label: string }[] = [
{ kind: "fiftyFifty", x: 330, label: "50:50" }, { kind: "fiftyFifty", x: 300, label: "50:50" },
{ kind: "audience", x: 480, label: "Зал" }, { kind: "audience", x: 480, label: "Зал" },
{ kind: "phone", x: 630, label: "Звонок" }, { kind: "phone", x: 660, label: "Звонок" },
]; ];
lifelineDefs.forEach(({ kind, x, label }) => { lifelineDefs.forEach(({ kind, x, label }) => {
const bg = this.add const bg = this.add
.rectangle(x, 425, 130, 36, COLORS.lifeline) .rectangle(x, 520, 150, 46, COLORS.lifeline)
.setStrokeStyle(1, 0x4f46e5) .setStrokeStyle(1, 0x4f46e5)
.setInteractive({ useHandCursor: true }); .setInteractive({ useHandCursor: true });
this.add.text(x, 425, label, { fontFamily: "sans-serif", fontSize: "19px", color: "#eef0fb" }).setOrigin(0.5); this.add.text(x, 520, label, { fontFamily: "sans-serif", fontSize: "21px", color: "#eef0fb" }).setOrigin(0.5);
bg.on("pointerdown", () => this.useLifeline(kind)); bg.on("pointerdown", () => this.useLifeline(kind));
this.lifelineButtons.set(kind, bg); this.lifelineButtons.set(kind, bg);
}); });
@@ -170,20 +174,20 @@ export class QuizScene extends Phaser.Scene {
private renderOptions() { private renderOptions() {
this.clearOptions(); this.clearOptions();
const startY = 175; const startY = 200;
const gap = 58; const gap = 76;
this.shuffledOptions.forEach((option, i) => { this.shuffledOptions.forEach((option, i) => {
const y = startY + i * gap; const y = startY + i * gap;
const hidden = this.hiddenIndices.has(i); const hidden = this.hiddenIndices.has(i);
const bg = this.add.rectangle(480, y, 780, 48, hidden ? COLORS.optionHidden : COLORS.option).setStrokeStyle( const bg = this.add.rectangle(480, y, 780, 64, hidden ? COLORS.optionHidden : COLORS.option).setStrokeStyle(
1, 1,
hidden ? 0x292d4a : 0x4f46e5, hidden ? 0x292d4a : 0x4f46e5,
); );
const label = this.add const label = this.add
.text(140, y, hidden ? "" : `${OPTION_LETTERS[i]}: ${option}`, { .text(140, y, hidden ? "" : `${OPTION_LETTERS[i]}: ${option}`, {
fontFamily: "sans-serif", fontFamily: "sans-serif",
fontSize: "22px", fontSize: "24px",
color: "#eef0fb", color: "#eef0fb",
}) })
.setOrigin(0, 0.5); .setOrigin(0, 0.5);
@@ -283,10 +287,10 @@ export class QuizScene extends Phaser.Scene {
private showAudienceOverlay(shares: Map<number, number>) { private showAudienceOverlay(shares: Map<number, number>) {
this.clearOverlay(); this.clearOverlay();
const bg = this.add.rectangle(480, 505, 780, 70, COLORS.overlayBg).setStrokeStyle(1, 0x292d4a); const bg = this.add.rectangle(480, 615, 780, 90, COLORS.overlayBg).setStrokeStyle(1, 0x292d4a);
this.overlayObjects.push(bg); this.overlayObjects.push(bg);
const title = this.add const title = this.add
.text(480, 478, "Помощь зала", { fontFamily: "sans-serif", fontSize: "17px", color: "#a3a8c6" }) .text(480, 585, "Помощь зала", { fontFamily: "sans-serif", fontSize: "17px", color: "#a3a8c6" })
.setOrigin(0.5); .setOrigin(0.5);
this.overlayObjects.push(title); this.overlayObjects.push(title);
@@ -296,7 +300,7 @@ export class QuizScene extends Phaser.Scene {
entries.forEach(([optIndex, pct], i) => { entries.forEach(([optIndex, pct], i) => {
const x = 480 - barAreaWidth / 2 + i * (barAreaWidth / entries.length) + barWidth / 2 + 8; const x = 480 - barAreaWidth / 2 + i * (barAreaWidth / entries.length) + barWidth / 2 + 8;
const label = this.add const label = this.add
.text(x, 495, `${OPTION_LETTERS[optIndex]}: ${Math.round(pct * 100)}%`, { .text(x, 605, `${OPTION_LETTERS[optIndex]}: ${Math.round(pct * 100)}%`, {
fontFamily: "sans-serif", fontFamily: "sans-serif",
fontSize: "18px", fontSize: "18px",
color: "#eef0fb", color: "#eef0fb",
@@ -304,7 +308,7 @@ export class QuizScene extends Phaser.Scene {
.setOrigin(0.5); .setOrigin(0.5);
this.overlayObjects.push(label); this.overlayObjects.push(label);
const bar = this.add const bar = this.add
.rectangle(x, 522, Math.max(6, barWidth * pct), 10, 0x818cf8) .rectangle(x, 635, Math.max(6, barWidth * pct), 10, 0x818cf8)
.setOrigin(0.5); .setOrigin(0.5);
this.overlayObjects.push(bar); this.overlayObjects.push(bar);
}); });
@@ -312,10 +316,10 @@ export class QuizScene extends Phaser.Scene {
private showPhoneOverlay(suggestedIndex: number) { private showPhoneOverlay(suggestedIndex: number) {
this.clearOverlay(); this.clearOverlay();
const bg = this.add.rectangle(480, 500, 780, 60, COLORS.overlayBg).setStrokeStyle(1, 0x292d4a); const bg = this.add.rectangle(480, 615, 780, 70, COLORS.overlayBg).setStrokeStyle(1, 0x292d4a);
this.overlayObjects.push(bg); this.overlayObjects.push(bg);
const text = this.add const text = this.add
.text(480, 500, `Друг на телефоне: «Я почти уверен, это ${OPTION_LETTERS[suggestedIndex]}»`, { .text(480, 615, `Друг на телефоне: «Я почти уверен, это ${OPTION_LETTERS[suggestedIndex]}»`, {
fontFamily: "sans-serif", fontFamily: "sans-serif",
fontSize: "19px", fontSize: "19px",
color: "#eef0fb", color: "#eef0fb",
+6 -2
View File
@@ -54,11 +54,15 @@ export class SnakeScene extends Phaser.Scene {
this.onProgress = this.registry.get("onProgress"); this.onProgress = this.registry.get("onProgress");
this.finished = false; this.finished = false;
this.cameras.main.setBackgroundColor(COLORS.bg); this.add
.graphics()
.fillGradientStyle(0x14162a, 0x14162a, COLORS.bg, COLORS.bg, 1)
.fillRect(0, 0, 960, 720)
.setDepth(-10);
this.width = this.definition.width; this.width = this.definition.width;
this.height = this.definition.height; this.height = this.definition.height;
this.cellSize = Math.max(8, Math.min(30, Math.floor(920 / this.width), Math.floor(460 / this.height))); this.cellSize = Math.max(8, Math.min(36, Math.floor(920 / this.width), Math.floor(580 / this.height)));
this.offsetX = 480 - (this.width * this.cellSize) / 2; this.offsetX = 480 - (this.width * this.cellSize) / 2;
this.offsetY = 110; this.offsetY = 110;