Fix UI colors and fullscreen

- AIChat: switch non-user message bg to muted; ensure loading indicator uses muted bg
- Breakout: slow paddle/body speed for single/multiplayer; adjust fullscreen sizing logic
- Pacman/Snake/Tetromino color behavior: align P2 coloring to secondary theme; enlarge fullscreen game canvases
- Pacman 2P: invert Pacman2 color to secondary; adjust scoreboard visuals accordingly
- Breakout fullscreen canvases now fill screen correctly in 1P/2P modes

X-Lovable-Edit-ID: edt-81d11f66-89cd-4fdc-b76d-cca9bff1ac10
This commit is contained in:
gpt-engineer-app[bot]
2026-02-03 09:38:19 +00:00
4 changed files with 48 additions and 17 deletions
+2 -2
View File
@@ -578,7 +578,7 @@ const AIChat = () => {
className={`max-w-[90%] sm:max-w-[85%] md:max-w-[80%] p-2 sm:p-3 rounded-lg text-xs sm:text-sm overflow-x-auto ${ className={`max-w-[90%] sm:max-w-[85%] md:max-w-[80%] p-2 sm:p-3 rounded-lg text-xs sm:text-sm overflow-x-auto ${
message.role === 'user' message.role === 'user'
? 'bg-primary/20 border border-primary/50' ? 'bg-primary/20 border border-primary/50'
: 'bg-secondary/50 border border-primary/30' : 'bg-muted border border-primary/30'
}`} }`}
> >
<MessageContent <MessageContent
@@ -607,7 +607,7 @@ const AIChat = () => {
<div className="w-8 h-8 rounded border border-primary/50 flex items-center justify-center bg-primary/10"> <div className="w-8 h-8 rounded border border-primary/50 flex items-center justify-center bg-primary/10">
<Bot className="w-4 h-4 text-primary" /> <Bot className="w-4 h-4 text-primary" />
</div> </div>
<div className="p-3 rounded-lg bg-secondary/50 border border-primary/30"> <div className="p-3 rounded-lg bg-muted border border-primary/30">
<Loader2 className="w-4 h-4 text-primary animate-spin" /> <Loader2 className="w-4 h-4 text-primary animate-spin" />
</div> </div>
</motion.div> </motion.div>
+20 -4
View File
@@ -106,10 +106,26 @@ const Breakout = () => {
if (height > maxHeight) { height = maxHeight; width = height * aspectRatio; } if (height > maxHeight) { height = maxHeight; width = height * aspectRatio; }
return { width: Math.floor(width), height: Math.floor(height) }; return { width: Math.floor(width), height: Math.floor(height) };
} }
if (isTwoPlayer) { if (isFullscreen) {
return isFullscreen ? { width: 480, height: 580 } : { width: 400, height: 500 }; const aspectRatio = 560 / 680;
if (isTwoPlayer) {
// Two canvases side by side
const maxWidth = (window.innerWidth - 100) / 2;
const maxHeight = window.innerHeight - 120;
let width = maxWidth;
let height = width / aspectRatio;
if (height > maxHeight) { height = maxHeight; width = height * aspectRatio; }
return { width: Math.floor(width), height: Math.floor(height) };
}
// Single player - fill screen
const maxWidth = window.innerWidth - 280;
const maxHeight = window.innerHeight - 100;
let width = maxWidth;
let height = width / aspectRatio;
if (height > maxHeight) { height = maxHeight; width = height * aspectRatio; }
return { width: Math.floor(width), height: Math.floor(height) };
} }
return isFullscreen ? { width: 700, height: 840 } : { width: 560, height: 680 }; return isTwoPlayer ? { width: 400, height: 500 } : { width: 560, height: 680 };
}, [isFullscreen, isTwoPlayer]); }, [isFullscreen, isTwoPlayer]);
const [canvasSize, setCanvasSize] = useState(getCanvasSize); const [canvasSize, setCanvasSize] = useState(getCanvasSize);
@@ -304,7 +320,7 @@ const Breakout = () => {
const ctx = canvas.getContext('2d'); const ctx = canvas.getContext('2d');
if (!ctx) return; if (!ctx) return;
const paddleSpeed = isMobile ? 6 : (isFullscreen ? 10 : 8); const paddleSpeed = isMobile ? 4 : (isFullscreen ? 6 : 5);
if (keysRef.current.has(leftKey)) paddleRefParam.current = Math.max(0, paddleRefParam.current - paddleSpeed); if (keysRef.current.has(leftKey)) paddleRefParam.current = Math.max(0, paddleRefParam.current - paddleSpeed);
if (keysRef.current.has(rightKey)) paddleRefParam.current = Math.min(canvasSize.width - paddleWidth, paddleRefParam.current + paddleSpeed); if (keysRef.current.has(rightKey)) paddleRefParam.current = Math.min(canvasSize.width - paddleWidth, paddleRefParam.current + paddleSpeed);
+13 -7
View File
@@ -122,7 +122,13 @@ const Pacman = () => {
const maxHeight = window.innerHeight - 420; const maxHeight = window.innerHeight - 420;
return Math.min(Math.floor(maxWidth / GRID_WIDTH), Math.floor(maxHeight / GRID_HEIGHT), 18); return Math.min(Math.floor(maxWidth / GRID_WIDTH), Math.floor(maxHeight / GRID_HEIGHT), 18);
} }
return isFullscreen ? 30 : 24; if (isFullscreen) {
// Fill screen more in fullscreen - calculate based on available space
const maxWidth = window.innerWidth - 240; // Space for side panel
const maxHeight = window.innerHeight - 100; // Space for header
return Math.min(Math.floor(maxWidth / GRID_WIDTH), Math.floor(maxHeight / GRID_HEIGHT));
}
return 24;
}, [isFullscreen]); }, [isFullscreen]);
const [cellSize, setCellSize] = useState(getCellSize); const [cellSize, setCellSize] = useState(getCellSize);
@@ -678,7 +684,7 @@ const Pacman = () => {
const renderPacman2 = () => ( const renderPacman2 = () => (
<svg viewBox="0 0 100 100" className="w-full h-full transition-transform duration-100 ease-out" style={{ transform: `rotate(${getPacmanRotation2()}deg)` }}> <svg viewBox="0 0 100 100" className="w-full h-full transition-transform duration-100 ease-out" style={{ transform: `rotate(${getPacmanRotation2()}deg)` }}>
<circle cx="50" cy="50" r="45" fill="hsl(120 70% 50%)" className="transition-all duration-100 ease-out" /> <circle cx="50" cy="50" r="45" fill="hsl(var(--secondary))" className="transition-all duration-100 ease-out" />
{mouthOpen2 && <path d="M 50 50 L 95 25 L 95 75 Z" fill="hsl(var(--background))" className="transition-all duration-100 ease-out" />} {mouthOpen2 && <path d="M 50 50 L 95 25 L 95 75 Z" fill="hsl(var(--background))" className="transition-all duration-100 ease-out" />}
<circle cx="50" cy="25" r="6" fill="hsl(var(--background))" className="transition-all duration-100 ease-out" /> <circle cx="50" cy="25" r="6" fill="hsl(var(--background))" className="transition-all duration-100 ease-out" />
</svg> </svg>
@@ -713,7 +719,7 @@ const Pacman = () => {
cells.push( cells.push(
<div key={`${x}-${y}`} className={`flex items-center justify-center transition-all duration-200 ease-out ${isWall ? 'bg-primary/20 border border-primary/40' : isTunnel ? 'bg-background/30' : 'bg-background/50 border border-primary/5'}`} style={{ width: cellSize, height: cellSize }}> <div key={`${x}-${y}`} className={`flex items-center justify-center transition-all duration-200 ease-out ${isWall ? 'bg-primary/20 border border-primary/40' : isTunnel ? 'bg-background/30' : 'bg-background/50 border border-primary/5'}`} style={{ width: cellSize, height: cellSize }}>
{isPacmanHere && <div className={`drop-shadow-lg ${p1Invulnerable ? 'animate-pulse' : ''}`} style={{ width: spriteSize, height: spriteSize, transition: 'all 0.2s ease-out', filter: 'drop-shadow(0 0 4px hsl(var(--primary)))', opacity: p1Invulnerable ? 0.5 : 1 }}>{renderPacman()}</div>} {isPacmanHere && <div className={`drop-shadow-lg ${p1Invulnerable ? 'animate-pulse' : ''}`} style={{ width: spriteSize, height: spriteSize, transition: 'all 0.2s ease-out', filter: 'drop-shadow(0 0 4px hsl(var(--primary)))', opacity: p1Invulnerable ? 0.5 : 1 }}>{renderPacman()}</div>}
{isPacman2Here && <div className={`drop-shadow-lg ${p2Invulnerable ? 'animate-pulse' : ''}`} style={{ width: spriteSize, height: spriteSize, transition: 'all 0.2s ease-out', filter: 'drop-shadow(0 0 4px hsl(120 70% 50%))', opacity: p2Invulnerable ? 0.5 : 1 }}>{renderPacman2()}</div>} {isPacman2Here && <div className={`drop-shadow-lg ${p2Invulnerable ? 'animate-pulse' : ''}`} style={{ width: spriteSize, height: spriteSize, transition: 'all 0.2s ease-out', filter: 'drop-shadow(0 0 4px hsl(var(--secondary)))', opacity: p2Invulnerable ? 0.5 : 1 }}>{renderPacman2()}</div>}
{ghostIndex !== -1 && !isPacmanHere && !isPacman2Here && <div style={{ width: spriteSize, height: spriteSize, transition: 'all 0.2s ease-out' }}>{renderGhost(ghostIndex, ghosts[ghostIndex].eaten)}</div>} {ghostIndex !== -1 && !isPacmanHere && !isPacman2Here && <div style={{ width: spriteSize, height: spriteSize, transition: 'all 0.2s ease-out' }}>{renderGhost(ghostIndex, ghosts[ghostIndex].eaten)}</div>}
{isFruitHere && !isPacmanHere && !isPacman2Here && ghostIndex === -1 && ( {isFruitHere && !isPacmanHere && !isPacman2Here && ghostIndex === -1 && (
<div className="animate-bounce text-lg" style={{ fontSize: `${Math.max(cellSize * 0.7, 14)}px` }} title={`${fruit.points} points`}> <div className="animate-bounce text-lg" style={{ fontSize: `${Math.max(cellSize * 0.7, 14)}px` }} title={`${fruit.points} points`}>
@@ -764,8 +770,8 @@ const Pacman = () => {
<p className="font-minecraft text-lg text-primary text-glow">{score.toLocaleString()}</p> <p className="font-minecraft text-lg text-primary text-glow">{score.toLocaleString()}</p>
</div> </div>
<div className="text-center"> <div className="text-center">
<p className="font-pixel text-[8px]" style={{ color: 'hsl(120 70% 50%)' }}>P2</p> <p className="font-pixel text-[8px] text-secondary">P2</p>
<p className="font-minecraft text-lg" style={{ color: 'hsl(120 70% 50%)', textShadow: '0 0 10px hsl(120 70% 50%)' }}>{score2.toLocaleString()}</p> <p className="font-minecraft text-lg text-secondary" style={{ textShadow: '0 0 10px hsl(var(--secondary))' }}>{score2.toLocaleString()}</p>
</div> </div>
</div> </div>
</div> </div>
@@ -795,7 +801,7 @@ const Pacman = () => {
<div className="flex justify-center gap-2 mt-1"> <div className="flex justify-center gap-2 mt-1">
<span className="font-minecraft text-sm text-primary">{lives}</span> <span className="font-minecraft text-sm text-primary">{lives}</span>
<span className="text-foreground/30">/</span> <span className="text-foreground/30">/</span>
<span className="font-minecraft text-sm" style={{ color: 'hsl(120 70% 50%)' }}>{lives2}</span> <span className="font-minecraft text-sm text-secondary">{lives2}</span>
</div> </div>
</div> </div>
) : ( ) : (
@@ -816,7 +822,7 @@ const Pacman = () => {
<p className="font-pixel text-[9px] text-foreground/60">WASD</p> <p className="font-pixel text-[9px] text-foreground/60">WASD</p>
</div> </div>
<div> <div>
<p className="font-pixel text-[8px]" style={{ color: 'hsl(120 70% 50%)' }}>P2</p> <p className="font-pixel text-[8px] text-secondary">P2</p>
<p className="font-pixel text-[9px] text-foreground/60">IJKL</p> <p className="font-pixel text-[9px] text-foreground/60">IJKL</p>
</div> </div>
</div> </div>
+13 -4
View File
@@ -13,7 +13,7 @@ import GameTouchButton from '@/components/GameTouchButton';
import MatrixCursor from '@/components/MatrixCursor'; import MatrixCursor from '@/components/MatrixCursor';
const GRID_SIZE = 20; const GRID_SIZE = 20;
const TICK_SPEED = 120; const TICK_SPEED = 150;
const MAX_SCORE = 4294967296; const MAX_SCORE = 4294967296;
const HIGHSCORE_KEY = 'snake-highscore'; const HIGHSCORE_KEY = 'snake-highscore';
@@ -78,10 +78,19 @@ const Snake = () => {
const maxHeight = window.innerHeight - 420; const maxHeight = window.innerHeight - 420;
return Math.min(Math.floor(maxWidth / GRID_SIZE), Math.floor(maxHeight / GRID_SIZE), 20); return Math.min(Math.floor(maxWidth / GRID_SIZE), Math.floor(maxHeight / GRID_SIZE), 20);
} }
if (gameMode === '2p') { if (isFullscreen) {
return isFullscreen ? 24 : 20; if (gameMode === '2p') {
// Two grids side by side
const maxWidth = (window.innerWidth - 100) / 2;
const maxHeight = window.innerHeight - 150;
return Math.min(Math.floor(maxWidth / GRID_SIZE), Math.floor(maxHeight / GRID_SIZE));
}
// Single player - fill screen
const maxWidth = window.innerWidth - 280;
const maxHeight = window.innerHeight - 100;
return Math.min(Math.floor(maxWidth / GRID_SIZE), Math.floor(maxHeight / GRID_SIZE));
} }
return isFullscreen ? 32 : 28; return gameMode === '2p' ? 20 : 28;
}, [isFullscreen, gameMode]); }, [isFullscreen, gameMode]);
const [cellSize, setCellSize] = useState(getCellSize); const [cellSize, setCellSize] = useState(getCellSize);