From 2cf2dcd7d8bd6a2eb7919b391ea850b67c3e43ac Mon Sep 17 00:00:00 2001 From: "gpt-engineer-app[bot]" <159125892+gpt-engineer-app[bot]@users.noreply.github.com> Date: Sat, 10 Jan 2026 23:30:14 +0000 Subject: [PATCH] Changes --- src/components/FullscreenPortal.tsx | 21 ++++ src/components/MainLayout.tsx | 6 +- src/pages/AIChat.tsx | 81 ++++++++---- src/pages/Tetris.tsx | 188 ++-------------------------- 4 files changed, 88 insertions(+), 208 deletions(-) create mode 100644 src/components/FullscreenPortal.tsx diff --git a/src/components/FullscreenPortal.tsx b/src/components/FullscreenPortal.tsx new file mode 100644 index 0000000..84c06b4 --- /dev/null +++ b/src/components/FullscreenPortal.tsx @@ -0,0 +1,21 @@ +import { ReactNode, useEffect, useState } from 'react'; +import { createPortal } from 'react-dom'; + +interface FullscreenPortalProps { + children: ReactNode; +} + +/** + * Portals children to document.body to escape any transformed ancestors. + * This is important because CSS transforms on parents can break `position: fixed`. + */ +export const FullscreenPortal = ({ children }: FullscreenPortalProps) => { + const [mounted, setMounted] = useState(false); + + useEffect(() => { + setMounted(true); + }, []); + + if (!mounted || typeof document === 'undefined') return null; + return createPortal(children, document.body); +}; diff --git a/src/components/MainLayout.tsx b/src/components/MainLayout.tsx index 1bcf2ab..30e2b4e 100644 --- a/src/components/MainLayout.tsx +++ b/src/components/MainLayout.tsx @@ -14,9 +14,9 @@ const MainLayout = () => { return ( {!isMobileGamePage && ( diff --git a/src/pages/AIChat.tsx b/src/pages/AIChat.tsx index cae7195..11bfb6c 100644 --- a/src/pages/AIChat.tsx +++ b/src/pages/AIChat.tsx @@ -9,6 +9,7 @@ import { useAchievements, incrementAiMessageCount } from '@/contexts/Achievement import { useToast } from '@/hooks/use-toast'; import GlitchText from '@/components/GlitchText'; import MessageContent from '@/components/MessageContent'; +import { FullscreenPortal } from '@/components/FullscreenPortal'; import { AI_PROVIDERS, AIProvider, getProvider, CUSTOM_API_STORAGE_KEY } from '@/lib/aiProviders'; import { DropdownMenu, @@ -372,16 +373,14 @@ const AIChat = () => { sendMessage(); } }; - - - return ( + const content = ( @@ -408,14 +407,31 @@ const AIChat = () => { <>
Memory: - { - const totalChars = messages.filter(m => m.role !== 'system').reduce((acc, m) => acc + m.content.length, 0); - const percent = (totalChars / 5000) * 100; - return percent > 80 ? 'text-red-400' : percent > 50 ? 'text-yellow-400' : 'text-green-400'; - })() - }`}> - {Math.min(100, Math.round((messages.filter(m => m.role !== 'system').reduce((acc, m) => acc + m.content.length, 0) / 5000) * 100))}% + { + const totalChars = messages + .filter(m => m.role !== 'system') + .reduce((acc, m) => acc + m.content.length, 0); + const percent = (totalChars / 5000) * 100; + return percent > 80 + ? 'text-red-400' + : percent > 50 + ? 'text-yellow-400' + : 'text-green-400'; + })() + }`} + > + {Math.min( + 100, + Math.round( + (messages + .filter(m => m.role !== 'system') + .reduce((acc, m) => acc + m.content.length, 0) / + 5000) * + 100, + ), + )}%
- ) : ( - - )} + {/* ... keep existing code (rest of sidebar / stats / UI) */} )} ) : ( <> - {/* P1 Board */} -
-
-
- P1 (WASD/Q) -
-
{renderBoard2P(player1, 'border-primary/20')}
-
-

SCORE

{player1.score}

-

LINES

{player1.lines}

-
- {player1.gameOver && GAME OVER} -
- - {/* P1 Next Piece */} -
-

Next

-
-
- {player1.nextPiece.shape.map((row, y) => ( -
- {row.map((val, x) => ( -
- ))} -
- ))} -
-
-
- - {/* Center controls */} -
-
-

CONTROLS

-

P1: WASD Q

-

P2: ↑↓←→ /

-

P: Pause

-
- {!gameStarted || gameOver ? ( - - ) : ( - - )} -
- - {/* P2 Board */} -
-
-
- P2 (↑↓←→/) -
-
{renderBoard2P(player2, 'border-purple-500/20')}
-
-

SCORE

{player2.score}

-

LINES

{player2.lines}

-
- {player2.gameOver && GAME OVER} -
- - {/* P2 Next Piece */} -
-

Next

-
-
- {player2.nextPiece.shape.map((row, y) => ( -
- {row.map((val, x) => ( -
- ))} -
- ))} -
-
-
- - )} - - {isMobile && gameMode === '1p' && ( - <> - - -
-
-

SCORE

{score.toLocaleString()}

-

HIGH

{highScore.toLocaleString()}

-

LINES

{lines}

-
- - {gameStarted && !gameOver && !gameComplete ? ( -
-
- -
- - DROP - - - -
-
- ) : ( - - )} -
- + {/* ... keep existing code (2P layout) */} )}
- {/* Overlays */} - {!isMobile && (gameOver || isPaused || gameComplete) && gameStarted && ( -
-
-

- {gameMode === '2p' && gameOver ? winner : gameComplete ? 'GAME COMPLETE!' : gameOver ? 'GAME OVER' : 'PAUSED'} -

- {gameMode === '1p' && (gameOver || gameComplete) && ( - <> -

Final Score: {score.toLocaleString()}

-

Lines: {lines}

- - )} - {gameMode === '2p' && gameOver && ( -
-
-

P1 Score

-

{player1.score}

-
-
-

P2 Score

-

{player2.score}

-
-
- )} - -
-
- )} + {/* ... keep existing code (mobile controls + overlays) */} ); + + return isFullscreen ? {content} : content; }; export default Tetris; \ No newline at end of file