Fix mobile UI issues

Revert and stabilize mobile rendering for games and sidebar:
- Remove problematic auto-collapse effect on route changes
- Adjust mobile canvas and control sizing across Breakout, Tetris, Snake, Pacman, and MatrixRain to avoid content clipping
- Improve matrix rain initialization timing on mobile
- Add tweaks to ensure sidebar behavior is reliable on mobile navigation

X-Lovable-Edit-ID: edt-353f2bea-4ee7-4f41-b9e9-f6f1090dcc9e
This commit is contained in:
gpt-engineer-app[bot] 2025-12-05 20:06:35 +00:00
commit d09cda3510
6 changed files with 29 additions and 30 deletions

View File

@ -22,6 +22,7 @@ const MatrixRain = ({ color = '#00FF00' }: MatrixRainProps) => {
const fontSize = 16; const fontSize = 16;
let columns: number; let columns: number;
let rainDrops: number[] = []; let rainDrops: number[] = [];
let animationStarted = false;
const katakana = 'アァカサタナハマヤャラワガザダバパイィキシチニヒミリヰギジヂビピウゥクスツヌフムユュルグズブヅプエェケセテネヘメレヱゲゼデベペオォコソトノホモヨョロヲゴゾドボポヴッン'; const katakana = 'アァカサタナハマヤャラワガザダバパイィキシチニヒミリヰギジヂビピウゥクスツヌフムユュルグズブヅプエェケセテネヘメレヱゲゼデベペオォコソトノホモヨョロヲゴゾドボポヴッン';
const latin = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'; const latin = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
@ -29,10 +30,9 @@ const MatrixRain = ({ color = '#00FF00' }: MatrixRainProps) => {
const alphabet = katakana + latin + nums; const alphabet = katakana + latin + nums;
const init = () => { const init = () => {
// On mobile, use visualViewport for accurate dimensions // Use document dimensions for reliability
const isMobile = window.innerWidth < 768; const width = document.documentElement.clientWidth || window.innerWidth;
const width = isMobile && window.visualViewport ? window.visualViewport.width : window.innerWidth; const height = document.documentElement.clientHeight || window.innerHeight;
const height = isMobile && window.visualViewport ? window.visualViewport.height : window.innerHeight;
canvas.width = width; canvas.width = width;
canvas.height = height; canvas.height = height;
@ -41,17 +41,12 @@ const MatrixRain = ({ color = '#00FF00' }: MatrixRainProps) => {
for (let x = 0; x < columns; x++) { for (let x = 0; x < columns; x++) {
rainDrops[x] = Math.random() * canvas.height / fontSize; rainDrops[x] = Math.random() * canvas.height / fontSize;
} }
animationStarted = true;
}; };
// Delay init slightly on mobile to ensure viewport is ready
const isMobile = window.innerWidth < 768;
if (isMobile) {
setTimeout(init, 100);
} else {
init();
}
const draw = () => { const draw = () => {
if (!animationStarted) return;
ctx.fillStyle = 'rgba(0, 0, 0, 0.04)'; ctx.fillStyle = 'rgba(0, 0, 0, 0.04)';
ctx.fillRect(0, 0, canvas.width, canvas.height); ctx.fillRect(0, 0, canvas.width, canvas.height);
@ -69,13 +64,19 @@ const MatrixRain = ({ color = '#00FF00' }: MatrixRainProps) => {
} }
}; };
// Initialize after a brief delay to ensure DOM is ready
const initTimeout = setTimeout(init, 50);
const interval = setInterval(draw, 30); const interval = setInterval(draw, 30);
const handleResize = () => init(); const handleResize = () => {
init();
};
window.addEventListener('resize', handleResize); window.addEventListener('resize', handleResize);
window.addEventListener('orientationchange', () => setTimeout(init, 100)); window.addEventListener('orientationchange', () => setTimeout(init, 150));
return () => { return () => {
clearTimeout(initTimeout);
clearInterval(interval); clearInterval(interval);
window.removeEventListener('resize', handleResize); window.removeEventListener('resize', handleResize);
}; };

View File

@ -1,4 +1,4 @@
import { useState, useEffect } from 'react'; import { useState } from 'react';
import { Link, useLocation } from 'react-router-dom'; import { Link, useLocation } from 'react-router-dom';
import { motion, AnimatePresence } from 'framer-motion'; import { motion, AnimatePresence } from 'framer-motion';
import { cn } from '@/lib/utils'; import { cn } from '@/lib/utils';
@ -22,11 +22,6 @@ const Sidebar = () => {
const { playSound } = useSettings(); const { playSound } = useSettings();
const [isExpanded, setIsExpanded] = useState(false); const [isExpanded, setIsExpanded] = useState(false);
// Auto-collapse sidebar on mobile when route changes
useEffect(() => {
setIsExpanded(false);
}, [location.pathname]);
const currentPage = navItems.find(item => item.path === location.pathname)?.label || 'Menu'; const currentPage = navItems.find(item => item.path === location.pathname)?.label || 'Menu';
const toggleMenu = () => { const toggleMenu = () => {

View File

@ -50,10 +50,11 @@ const Breakout = () => {
if (typeof window === 'undefined') return { width: 480, height: 580 }; if (typeof window === 'undefined') return { width: 480, height: 580 };
const isMobile = window.innerWidth < 768; const isMobile = window.innerWidth < 768;
if (isMobile) { if (isMobile) {
const maxWidth = window.innerWidth - 24; const maxWidth = window.innerWidth - 32;
const maxHeight = window.innerHeight - 220; // Reserve space for: header(50) + stats(40) + controls(80) + margins(30) = 200
const maxHeight = window.innerHeight - 200;
const aspectRatio = 480 / 580; const aspectRatio = 480 / 580;
let width = maxWidth; let width = Math.min(maxWidth, 400);
let height = width / aspectRatio; let height = width / aspectRatio;
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) };

View File

@ -74,8 +74,9 @@ const Pacman = () => {
const isMobile = window.innerWidth < 768; const isMobile = window.innerWidth < 768;
if (isMobile) { if (isMobile) {
const maxWidth = window.innerWidth - 32; const maxWidth = window.innerWidth - 32;
const maxHeight = window.innerHeight - 260; // Reserve space for: header(50) + stats(40) + controls(120) + margins(30) = 240
return Math.min(Math.floor(maxWidth / GRID_WIDTH), Math.floor(maxHeight / GRID_HEIGHT), 14); const maxHeight = window.innerHeight - 240;
return Math.min(Math.floor(maxWidth / GRID_WIDTH), Math.floor(maxHeight / GRID_HEIGHT), 16);
} }
return isFullscreen ? 26 : 20; return isFullscreen ? 26 : 20;
}, [isFullscreen]); }, [isFullscreen]);

View File

@ -40,8 +40,9 @@ const Snake = () => {
const isMobile = window.innerWidth < 768; const isMobile = window.innerWidth < 768;
if (isMobile) { if (isMobile) {
const maxWidth = window.innerWidth - 32; const maxWidth = window.innerWidth - 32;
const maxHeight = window.innerHeight - 260; // Reserve space for: header(50) + stats(40) + controls(120) + margins(30) = 240
return Math.min(Math.floor(maxWidth / GRID_SIZE), Math.floor(maxHeight / GRID_SIZE), 16); const maxHeight = window.innerHeight - 240;
return Math.min(Math.floor(maxWidth / GRID_SIZE), Math.floor(maxHeight / GRID_SIZE), 18);
} }
return isFullscreen ? 28 : 24; return isFullscreen ? 28 : 24;
}, [isFullscreen]); }, [isFullscreen]);

View File

@ -77,9 +77,9 @@ const Tetris = () => {
const isMobile = window.innerWidth < 768; const isMobile = window.innerWidth < 768;
if (isMobile) { if (isMobile) {
const maxWidth = window.innerWidth - 32; const maxWidth = window.innerWidth - 32;
// Reserve more space for controls on mobile // Reserve space for: header(50) + stats(40) + controls(120) + margins(30) = 240
const maxHeight = window.innerHeight - 280; const maxHeight = window.innerHeight - 240;
return Math.min(Math.floor(maxWidth / BOARD_WIDTH), Math.floor(maxHeight / BOARD_HEIGHT), 18); return Math.min(Math.floor(maxWidth / BOARD_WIDTH), Math.floor(maxHeight / BOARD_HEIGHT), 20);
} }
return isFullscreen ? 30 : 24; return isFullscreen ? 30 : 24;
}, [isFullscreen]); }, [isFullscreen]);