Spaces:
Sleeping
Sleeping
| import { useEffect, useState } from 'react'; | |
| import { supabase } from './supabaseClient'; | |
| import Auth from './components/Auth'; | |
| import ReviewQueue from './components/ReviewQueue'; | |
| import JourneyPath from './components/JourneyPath'; | |
| import WindowShell from './components/WindowShell'; | |
| import TerminalEmulator from './components/TerminalEmulator'; | |
| import PythonChallenge from './components/PythonChallenge'; | |
| import MySQLPlayground from './components/MySQLPlayground'; | |
| import FileExplorer from './components/FileExplorer'; | |
| import Settings from './components/Settings'; | |
| interface WindowState { | |
| isOpen: boolean; | |
| isMinimized: boolean; | |
| } | |
| export default function App() { | |
| const [user, setUser] = useState<any>(null); | |
| const [overrideProblemId, setOverrideProblemId] = useState<string | null>(null); | |
| // Window states | |
| const [windows, setWindows] = useState<Record<string, WindowState>>({ | |
| queue: { isOpen: true, isMinimized: false }, | |
| journey: { isOpen: false, isMinimized: false }, | |
| terminal: { isOpen: false, isMinimized: false }, | |
| computer: { isOpen: false, isMinimized: false }, | |
| sync: { isOpen: false, isMinimized: false }, | |
| python: { isOpen: false, isMinimized: false }, | |
| mysql: { isOpen: false, isMinimized: false }, | |
| settings: { isOpen: false, isMinimized: false }, | |
| }); | |
| const [activeWindow, setActiveWindow] = useState<string>('queue'); | |
| const [isMobile, setIsMobile] = useState(false); | |
| // Start Menu and System info | |
| const [isStartOpen, setIsStartOpen] = useState(false); | |
| const [streak, setStreak] = useState<any>(null); | |
| const [time, setTime] = useState(''); | |
| // Sync state | |
| const [syncing, setSyncing] = useState(false); | |
| const [syncResult, setSyncResult] = useState<string | null>(null); | |
| // Wallpaper state | |
| const [wallpaperUrl, setWallpaperUrl] = useState<string>(() => { | |
| return localStorage.getItem('desktopWallpaperUrl') || 'https://lh3.googleusercontent.com/aida-public/AB6AXuAMJHVhhK8_G7F8sbhn8F7w4AZWXb1O_-HLKvrZ_fJlbUVMcVuxxEOO_LlOh8qIwtAn2KvzvCwzmtNLLEZ5uYkCEsx8ZWXJR609qgSMRSX8LBBeskk4VzVXnyzsgIKCedeV2PvGxJlUNnledcWKCXqG9egQi8dgTcA7C2z82QyM73KZ4s7ZRzZHNupuQt1ocfcMl9E_x1PWFRridl751LIpyGZgednS5CmVw2rZvFc_tbp2QTxgVHJ_59myEBmy6aajbO06AhkKmCvI'; | |
| }); | |
| // Context menu state | |
| const [contextMenu, setContextMenu] = useState<{ visible: boolean; x: number; y: number }>({ | |
| visible: false, | |
| x: 0, | |
| y: 0 | |
| }); | |
| // Fullscreen state | |
| const [isFullscreen, setIsFullscreen] = useState(false); | |
| // Fetch streak info | |
| const fetchStreak = async () => { | |
| try { | |
| const { data: { session } } = await supabase.auth.getSession(); | |
| if (!session) return; | |
| const res = await fetch(`${import.meta.env.VITE_API_BASE_URL}/api/streak`, { | |
| headers: { 'Authorization': `Bearer ${session.access_token}` } | |
| }); | |
| if (res.ok) { | |
| const data = await res.json(); | |
| setStreak(data); | |
| } | |
| } catch (err) { | |
| console.error('Error fetching streak:', err); | |
| } | |
| }; | |
| useEffect(() => { | |
| // Check initial session | |
| supabase.auth.getSession().then(({ data: { session } }) => { | |
| setUser(session?.user ?? null); | |
| }); | |
| // Listen for auth state changes | |
| const { data: { subscription } } = supabase.auth.onAuthStateChange((_event, session) => { | |
| setUser(session?.user ?? null); | |
| }); | |
| // Clock Updater | |
| const updateTime = () => { | |
| const date = new Date(); | |
| let hours = date.getHours(); | |
| const minutes = date.getMinutes(); | |
| const ampm = hours >= 12 ? 'PM' : 'AM'; | |
| hours = hours % 12; | |
| hours = hours ? hours : 12; // 0 is 12 | |
| const minStr = minutes < 10 ? '0' + minutes : minutes; | |
| setTime(`${hours}:${minStr} ${ampm}`); | |
| }; | |
| updateTime(); | |
| const interval = setInterval(updateTime, 30000); | |
| return () => { | |
| subscription.unsubscribe(); | |
| clearInterval(interval); | |
| }; | |
| }, []); | |
| useEffect(() => { | |
| const checkMobile = () => { | |
| setIsMobile(window.innerWidth < 768); | |
| }; | |
| checkMobile(); | |
| window.addEventListener('resize', checkMobile); | |
| return () => { | |
| window.removeEventListener('resize', checkMobile); | |
| }; | |
| }, []); | |
| useEffect(() => { | |
| if (user) { | |
| fetchStreak(); | |
| const savedWallpaper = localStorage.getItem('desktopWallpaperUrl'); | |
| if (savedWallpaper) { | |
| setWallpaperUrl(savedWallpaper); | |
| } | |
| } | |
| }, [user]); | |
| // Fullscreen change listener | |
| useEffect(() => { | |
| const handleFullscreenChange = () => { | |
| setIsFullscreen(!!document.fullscreenElement); | |
| }; | |
| document.addEventListener('fullscreenchange', handleFullscreenChange); | |
| return () => { | |
| document.removeEventListener('fullscreenchange', handleFullscreenChange); | |
| }; | |
| }, []); | |
| // Zoom prevention listener | |
| useEffect(() => { | |
| const handleWheel = (e: WheelEvent) => { | |
| if (e.ctrlKey) { | |
| e.preventDefault(); | |
| } | |
| }; | |
| const handleKeydown = (e: KeyboardEvent) => { | |
| if (e.ctrlKey && ( | |
| e.key === '=' || | |
| e.key === '-' || | |
| e.key === '0' || | |
| e.key === '+' || | |
| e.code === 'NumpadAdd' || | |
| e.code === 'NumpadSubtract' | |
| )) { | |
| e.preventDefault(); | |
| } | |
| }; | |
| const handleTouchmove = (e: TouchEvent) => { | |
| if (e.touches.length > 1) { | |
| e.preventDefault(); | |
| } | |
| }; | |
| document.addEventListener('wheel', handleWheel, { passive: false }); | |
| document.addEventListener('keydown', handleKeydown); | |
| document.addEventListener('touchmove', handleTouchmove, { passive: false }); | |
| return () => { | |
| document.removeEventListener('wheel', handleWheel); | |
| document.removeEventListener('keydown', handleKeydown); | |
| document.removeEventListener('touchmove', handleTouchmove); | |
| }; | |
| }, []); | |
| const handleContextMenu = (e: React.MouseEvent) => { | |
| const target = e.target as HTMLElement; | |
| // Prevent menu if clicking interactive windows/buttons/inputs | |
| if ( | |
| target.closest('.window-shadow') || | |
| target.closest('button') || | |
| target.closest('input') || | |
| target.closest('textarea') || | |
| target.closest('details') || | |
| target.closest('#start-menu') | |
| ) { | |
| return; | |
| } | |
| e.preventDefault(); | |
| setContextMenu({ | |
| visible: true, | |
| x: e.clientX, | |
| y: e.clientY | |
| }); | |
| }; | |
| const closeContextMenu = () => { | |
| if (contextMenu.visible) { | |
| setContextMenu(prev => ({ ...prev, visible: false })); | |
| } | |
| }; | |
| const toggleFullscreen = () => { | |
| closeContextMenu(); | |
| if (!document.fullscreenElement) { | |
| document.documentElement.requestFullscreen().catch((err) => { | |
| console.error(`Error enabling fullscreen: ${err.message}`); | |
| }); | |
| } else { | |
| document.exitFullscreen(); | |
| } | |
| }; | |
| const compressAndSaveWallpaper = (file: File) => { | |
| const reader = new FileReader(); | |
| reader.onload = (event) => { | |
| const img = new Image(); | |
| img.onload = () => { | |
| const canvas = document.createElement('canvas'); | |
| let width = img.width; | |
| let height = img.height; | |
| const MAX_WIDTH = 1920; | |
| const MAX_HEIGHT = 1080; | |
| if (width > MAX_WIDTH || height > MAX_HEIGHT) { | |
| if (width / height > MAX_WIDTH / MAX_HEIGHT) { | |
| height = Math.round((height * MAX_WIDTH) / width); | |
| width = MAX_WIDTH; | |
| } else { | |
| width = Math.round((width * MAX_HEIGHT) / height); | |
| height = MAX_HEIGHT; | |
| } | |
| } | |
| canvas.width = width; | |
| canvas.height = height; | |
| const ctx = canvas.getContext('2d'); | |
| if (ctx) { | |
| ctx.drawImage(img, 0, 0, width, height); | |
| try { | |
| const dataUrl = canvas.toDataURL('image/jpeg', 0.7); | |
| setWallpaperUrl(dataUrl); | |
| localStorage.setItem('desktopWallpaperUrl', dataUrl); | |
| localStorage.setItem('customWallpaperUrl', dataUrl); | |
| localStorage.setItem('customWallpaperName', file.name); | |
| } catch (err) { | |
| console.error('Failed to save compressed image to localStorage:', err); | |
| // Fallback: set it as state but warn | |
| const rawDataUrl = event.target?.result as string; | |
| setWallpaperUrl(rawDataUrl); | |
| } | |
| } else { | |
| const rawDataUrl = event.target?.result as string; | |
| setWallpaperUrl(rawDataUrl); | |
| try { | |
| localStorage.setItem('desktopWallpaperUrl', rawDataUrl); | |
| localStorage.setItem('customWallpaperUrl', rawDataUrl); | |
| localStorage.setItem('customWallpaperName', file.name); | |
| } catch (err) { | |
| console.error('Failed to save raw image to localStorage:', err); | |
| } | |
| } | |
| }; | |
| img.src = event.target?.result as string; | |
| }; | |
| reader.readAsDataURL(file); | |
| }; | |
| const handleWallpaperChange = (e: React.ChangeEvent<HTMLInputElement>) => { | |
| closeContextMenu(); | |
| const file = e.target.files?.[0]; | |
| if (file) { | |
| compressAndSaveWallpaper(file); | |
| } | |
| }; | |
| const openWindow = (id: string) => { | |
| setWindows(prev => ({ | |
| ...prev, | |
| [id]: { isOpen: true, isMinimized: false } | |
| })); | |
| setActiveWindow(id); | |
| setIsStartOpen(false); | |
| }; | |
| const closeWindow = (id: string) => { | |
| setWindows(prev => ({ | |
| ...prev, | |
| [id]: { ...prev[id], isOpen: false } | |
| })); | |
| }; | |
| const minimizeWindow = (id: string) => { | |
| setWindows(prev => ({ | |
| ...prev, | |
| [id]: { ...prev[id], isMinimized: true } | |
| })); | |
| }; | |
| const toggleWindowMinimize = (id: string) => { | |
| setWindows(prev => { | |
| const win = prev[id]; | |
| if (win.isMinimized || !win.isOpen) { | |
| setActiveWindow(id); | |
| return { | |
| ...prev, | |
| [id]: { isOpen: true, isMinimized: false } | |
| }; | |
| } else if (activeWindow === id) { | |
| return { | |
| ...prev, | |
| [id]: { ...win, isMinimized: true } | |
| }; | |
| } else { | |
| setActiveWindow(id); | |
| return prev; | |
| } | |
| }); | |
| }; | |
| const handleSelectProblemForReview = (problemId: string) => { | |
| setOverrideProblemId(problemId); | |
| openWindow('queue'); | |
| }; | |
| const triggerSync = async () => { | |
| openWindow('sync'); | |
| setSyncing(true); | |
| setSyncResult(null); | |
| try { | |
| const { data: { session } } = await supabase.auth.getSession(); | |
| if (!session) return; | |
| const res = await fetch(`${import.meta.env.VITE_API_BASE_URL}/api/sync`, { | |
| method: 'POST', | |
| headers: { 'Authorization': `Bearer ${session.access_token}` } | |
| }); | |
| if (res.ok) { | |
| const data = await res.json(); | |
| setSyncResult(data.message || 'Synchronization successfully completed!'); | |
| fetchStreak(); | |
| } else { | |
| setSyncResult('Sync operation failed at server host.'); | |
| } | |
| } catch { | |
| setSyncResult('Cloud synchronization server unreachable.'); | |
| } finally { | |
| setSyncing(false); | |
| } | |
| }; | |
| const handleSignOut = async () => { | |
| setIsStartOpen(false); | |
| await supabase.auth.signOut(); | |
| }; | |
| if (!user) { | |
| return <Auth />; | |
| } | |
| const username = user.email ? user.email.split('@')[0] : 'Player_One'; | |
| const isAnyWindowOpen = Object.values(windows).some(w => w.isOpen && !w.isMinimized); | |
| return ( | |
| <div | |
| onContextMenu={handleContextMenu} | |
| onClick={closeContextMenu} | |
| className="w-screen h-screen m-0 p-0 relative font-sans text-ink-black overflow-hidden select-none" | |
| > | |
| {/* Background Wallpaper image */} | |
| <div | |
| className="absolute inset-0 w-full h-full bg-cover bg-center z-[-1] saturate-150 contrast-125 transition-all duration-300" | |
| style={{ backgroundImage: `url('${wallpaperUrl}')` }} | |
| /> | |
| {/* Desktop Icons column */} | |
| {!isMobile && ( | |
| <nav className="fixed left-0 top-12 h-[calc(100vh-80px)] w-[140px] flex flex-col items-center py-gutter-md space-y-6 z-10 overflow-y-auto custom-scrollbar select-none"> | |
| {/* Daily Queue Shortcut */} | |
| <button | |
| onClick={() => toggleWindowMinimize('queue')} | |
| className="flex flex-col items-center justify-center text-ink-black gap-2.5 p-2 w-full group icon-hover-shift cursor-pointer outline-none" | |
| > | |
| <div className="w-16 h-16 bg-paper-white border-[3px] border-ink-black flex items-center justify-center gadget-shadow group-hover:bg-secondary-container transition-colors shadow-[inset_2px_2px_0px_rgba(255,255,255,0.8)]"> | |
| <span className="material-symbols-outlined text-4xl" style={{ fontVariationSettings: "'FILL' 1" }}> | |
| view_list | |
| </span> | |
| </div> | |
| <span className="font-icon-label text-[9px] bg-ink-black text-white px-2 py-1 border-[3px] border-ink-black text-center leading-tight shadow-[2px_2px_0px_0px_#fde047] uppercase"> | |
| DAILY<br/>QUEUE | |
| </span> | |
| </button> | |
| {/* Journey Path Shortcut */} | |
| <button | |
| onClick={() => toggleWindowMinimize('journey')} | |
| className="flex flex-col items-center justify-center text-ink-black gap-2.5 p-2 w-full group icon-hover-shift cursor-pointer outline-none" | |
| > | |
| <div className="w-16 h-16 bg-paper-white border-[3px] border-ink-black flex items-center justify-center gadget-shadow group-hover:bg-secondary-container transition-colors shadow-[inset_2px_2px_0px_rgba(255,255,255,0.8)]"> | |
| <span className="material-symbols-outlined text-4xl" style={{ fontVariationSettings: "'FILL' 1" }}> | |
| route | |
| </span> | |
| </div> | |
| <span className="font-icon-label text-[9px] bg-ink-black text-white px-2 py-1 border-[3px] border-ink-black text-center leading-tight shadow-[2px_2px_0px_0px_#fde047] uppercase"> | |
| JOURNEY<br/>PATH | |
| </span> | |
| </button> | |
| {/* Drive Sync Shortcut */} | |
| <button | |
| onClick={triggerSync} | |
| className="flex flex-col items-center justify-center text-ink-black gap-2.5 p-2 w-full group icon-hover-shift cursor-pointer outline-none" | |
| > | |
| <div className="w-16 h-16 bg-paper-white border-[3px] border-ink-black flex items-center justify-center gadget-shadow group-hover:bg-secondary-container transition-colors shadow-[inset_2px_2px_0px_rgba(255,255,255,0.8)]"> | |
| <span className="material-symbols-outlined text-4xl" style={{ fontVariationSettings: "'FILL' 1" }}> | |
| cloud_sync | |
| </span> | |
| </div> | |
| <span className="font-icon-label text-[9px] bg-ink-black text-white px-2 py-1 border-[3px] border-ink-black text-center leading-tight shadow-[2px_2px_0px_0px_#fde047] uppercase"> | |
| DRIVE<br/>SYNC | |
| </span> | |
| </button> | |
| {/* My Computer Shortcut */} | |
| <button | |
| onClick={() => toggleWindowMinimize('computer')} | |
| className="flex flex-col items-center justify-center text-ink-black gap-2.5 p-2 w-full group icon-hover-shift cursor-pointer outline-none" | |
| > | |
| <div className="w-16 h-16 bg-paper-white border-[3px] border-ink-black flex items-center justify-center gadget-shadow group-hover:bg-secondary-container transition-colors shadow-[inset_2px_2px_0px_rgba(255,255,255,0.8)] p-1 overflow-hidden"> | |
| <img | |
| alt="My Computer" | |
| className="w-full h-full object-cover" | |
| src="https://lh3.googleusercontent.com/aida-public/AB6AXuD2tUGA2d7QiNyajDAWYK183zhNTgtAZpOXK3E9wRvHGs-t6ykWt9uPScYe_fziWzQI0pREcY_ThI341WvGMusyYmnAkagfuwx6wubIs1ES68DO8CCNAlcHcb2zOUU4MJeYuhWDy1uYRqyGYjIaDUqfgNWk2vm4WzwRqoorn2dxtZ6QdJhEKbXjG8fG9chkkvr68qJf0fLUL6bIqBXZg2FJ30S7zS3oZ4IZsug-wLyRYuJ4Gu_86snNUo1whNrBdZm5OMREfc9sLbwJ" | |
| /> | |
| </div> | |
| <span className="font-icon-label text-[9px] bg-ink-black text-white px-2 py-1 border-[3px] border-ink-black text-center leading-tight shadow-[2px_2px_0px_0px_#fde047] uppercase"> | |
| MY<br/>COMP | |
| </span> | |
| </button> | |
| {/* Terminal Shortcut */} | |
| <button | |
| onClick={() => toggleWindowMinimize('terminal')} | |
| className="flex flex-col items-center justify-center text-ink-black gap-2.5 p-2 w-full group icon-hover-shift cursor-pointer outline-none" | |
| > | |
| <div className="w-16 h-16 bg-paper-white border-[3px] border-ink-black flex items-center justify-center gadget-shadow group-hover:bg-secondary-container transition-colors shadow-[inset_2px_2px_0px_rgba(255,255,255,0.8)] p-1 overflow-hidden"> | |
| <img | |
| alt="Terminal" | |
| className="w-full h-full object-cover" | |
| src="https://lh3.googleusercontent.com/aida-public/AB6AXuA2AvcQ2oKWP8AJ8yR5nF82LLDEH1FKySUK-npnqEjvJ6Cs9xp8joCwH7x43oTCAC5xhO2rQdYzka3B6u72FNbdxMnsv3WJLye3bKochu8AnnRlS3szfYSaj724sYsiZo4n8G-oWqvl7C0rOGaDaU2nRH1xXmW2PBi6L2NryTsUaTCVZQKTKBBNNEOig5kh7B52e-iIuj2PTzaSjF9ebp6gMuCnBtJT8Sxo5Qsmvfm-t2U5r_qQpRiYj--Ut41T7NTh-tHgs4S4kBvz" | |
| /> | |
| </div> | |
| <span className="font-icon-label text-[9px] bg-ink-black text-white px-2 py-1 border-[3px] border-ink-black text-center leading-tight shadow-[2px_2px_0px_0px_#fde047] uppercase"> | |
| TERMINAL | |
| </span> | |
| </button> | |
| {localStorage.getItem('algospaced_sandbox_mode') === 'true' && ( | |
| <button | |
| onClick={() => toggleWindowMinimize('settings')} | |
| className="flex flex-col items-center justify-center text-ink-black gap-2.5 p-2 w-full group icon-hover-shift cursor-pointer outline-none animate-fade-in" | |
| > | |
| <div className="w-16 h-16 bg-paper-white border-[3px] border-ink-black flex items-center justify-center gadget-shadow group-hover:bg-[#10b981] group-hover:text-white transition-all shadow-[inset_2px_2px_0px_rgba(255,255,255,0.8)]"> | |
| <span className="material-symbols-outlined text-4xl" style={{ fontVariationSettings: "'FILL' 1" }}> | |
| key | |
| </span> | |
| </div> | |
| <span className="font-icon-label text-[9px] bg-ink-black text-white px-2 py-1 border-[3px] border-ink-black text-center leading-tight shadow-[2px_2px_0px_0px_#10b981] uppercase"> | |
| API<br/>KEYS | |
| </span> | |
| </button> | |
| )} | |
| </nav> | |
| )} | |
| {/* System Gadget Widget (Top Right) */} | |
| {!isMobile && ( | |
| <div className="absolute top-desktop-margin right-desktop-margin w-64 bg-paper-white border-[3px] border-ink-black gadget-shadow z-20 overflow-hidden flex flex-col shadow-[inset_2px_2px_0px_rgba(255,255,255,0.8)] select-none"> | |
| <div className="bg-[#0ea5e9] border-b-[3px] border-ink-black px-3 py-1 flex justify-between items-center text-white"> | |
| <span className="font-window-title text-xs font-bold tracking-wide">Profile.sys</span> | |
| </div> | |
| <div className="p-3 flex items-center gap-3 bg-[#fdf8e1]"> | |
| <div className="w-12 h-12 border-[3px] border-ink-black overflow-hidden bg-primary-fixed-dim shadow-[2px_2px_0_0_#1E293B] shrink-0"> | |
| <img | |
| className="w-full h-full object-cover" | |
| src="https://lh3.googleusercontent.com/aida-public/AB6AXuBl1MnAmh_W_EWibibrGw1F1YSenuKlaIn3JymUnBKn1nPLjAaOBjoLzdT5krzVDmihFhXOk-DEtNTdM2Js1_Ttr--5WZohhyqD1QfpS6j_yU1_-4wweOmgWq2HLUYNMf0mzQDNLNu5KbACEJKL5sm1YaTLPePlO687X3VHYsUhWTpUQeBwFjushaBpyW8tEa2dJj5ZSRWq0qa5GhtE0zAAW741e2SIRtFOp7CvBYEEetymSiucK513lli1TUwgWIgXvlfxZoGbH00q" | |
| /> | |
| </div> | |
| <div className="overflow-hidden"> | |
| <div className="font-headline-md text-sm font-bold text-ink-black truncate">{username}</div> | |
| <div className="flex items-center gap-1 text-red-500 mt-1 select-none font-bold"> | |
| <span className="material-symbols-outlined text-sm" style={{ fontVariationSettings: "'FILL' 1" }}> | |
| local_fire_department | |
| </span> | |
| <span className="font-arcade text-[7px]">{streak?.current_streak ?? 0} DAY STREAK</span> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| )} | |
| {/* Windows Phone Metro Start Screen (Mobile Home View) */} | |
| {isMobile && !isAnyWindowOpen && ( | |
| <div className="w-full h-[calc(100vh-56px)] overflow-y-auto custom-scrollbar p-6 pb-24 flex flex-col gap-6 z-10 relative"> | |
| {/* Metro Header */} | |
| <div className="flex flex-col select-none border-b-[3px] border-dashed border-ink-black pb-4"> | |
| <span className="text-[9px] font-arcade text-trash-gray font-bold uppercase tracking-wider">AlgoSpaced OS v1.0</span> | |
| <h1 className="text-4xl font-extrabold font-headline-md text-ink-black uppercase tracking-tight mt-1">start</h1> | |
| </div> | |
| {/* Tiles Grid */} | |
| <div className="grid grid-cols-2 gap-4"> | |
| {/* Profile Tile (Wide - 2 cols) */} | |
| <div className="col-span-2 bg-[#fdf8e1] border-[3px] border-ink-black p-4 flex items-center gap-4 gadget-shadow shadow-[inset_2px_2px_0px_rgba(255,255,255,0.8)]"> | |
| <div className="w-12 h-12 border-[3px] border-ink-black overflow-hidden bg-primary-fixed-dim shadow-[2px_2px_0_0_#1E293B] shrink-0"> | |
| <img | |
| className="w-full h-full object-cover" | |
| src="https://lh3.googleusercontent.com/aida-public/AB6AXuBl1MnAmh_W_EWibibrGw1F1YSenuKlaIn3JymUnBKn1nPLjAaOBjoLzdT5krzVDmihFhXOk-DEtNTdM2Js1_Ttr--5WZohhyqD1QfpS6j_yU1_-4wweOmgWq2HLUYNMf0mzQDNLNu5KbACEJKL5sm1YaTLPePlO687X3VHYsUhWTpUQeBwFjushaBpyW8tEa2dJj5ZSRWq0qa5GhtE0zAAW741e2SIRtFOp7CvBYEEetymSiucK513lli1TUwgWIgXvlfxZoGbH00q" | |
| /> | |
| </div> | |
| <div className="overflow-hidden"> | |
| <div className="font-headline-md text-sm font-bold text-ink-black truncate">{username}</div> | |
| <div className="flex items-center gap-1 text-red-500 mt-1 select-none font-bold"> | |
| <span className="material-symbols-outlined text-sm" style={{ fontVariationSettings: "'FILL' 1" }}> | |
| local_fire_department | |
| </span> | |
| <span className="font-arcade text-[7px]">{streak?.current_streak ?? 0} DAY STREAK</span> | |
| </div> | |
| </div> | |
| </div> | |
| {/* Daily Queue Tile */} | |
| <button | |
| onClick={() => toggleWindowMinimize('queue')} | |
| className="bg-grass-green text-ink-black border-[3px] border-ink-black p-4 flex flex-col justify-between aspect-square gadget-shadow active:translate-y-1 active:translate-x-1 active:shadow-none select-none text-left cursor-pointer transition-all" | |
| > | |
| <span className="material-symbols-outlined text-3xl align-top" style={{ fontVariationSettings: "'FILL' 1" }}> | |
| view_list | |
| </span> | |
| <span className="font-arcade text-[8px] font-bold tracking-tight uppercase leading-tight"> | |
| DAILY<br/>QUEUE | |
| </span> | |
| </button> | |
| {/* Journey Path Tile */} | |
| <button | |
| onClick={() => toggleWindowMinimize('journey')} | |
| className="bg-[#0ea5e9] text-white border-[3px] border-ink-black p-4 flex flex-col justify-between aspect-square gadget-shadow active:translate-y-1 active:translate-x-1 active:shadow-none select-none text-left cursor-pointer transition-all" | |
| > | |
| <span className="material-symbols-outlined text-3xl align-top" style={{ fontVariationSettings: "'FILL' 1" }}> | |
| route | |
| </span> | |
| <span className="font-arcade text-[8px] font-bold tracking-tight uppercase leading-tight"> | |
| JOURNEY<br/>PATH | |
| </span> | |
| </button> | |
| {/* My Computer Tile */} | |
| <button | |
| onClick={() => toggleWindowMinimize('computer')} | |
| className="bg-paper-white text-ink-black border-[3px] border-ink-black p-4 flex flex-col justify-between aspect-square gadget-shadow active:translate-y-1 active:translate-x-1 active:shadow-none select-none text-left cursor-pointer transition-all" | |
| > | |
| <img | |
| alt="My Computer" | |
| className="w-10 h-10 object-cover border-2 border-ink-black bg-white" | |
| src="https://lh3.googleusercontent.com/aida-public/AB6AXuD2tUGA2d7QiNyajDAWYK183zhNTgtAZpOXK3E9wRvHGs-t6ykWt9uPScYe_fziWzQI0pREcY_ThI341WvGMusyYmnAkagfuwx6wubIs1ES68DO8CCNAlcHcb2zOUU4MJeYuhWDy1uYRqyGYjIaDUqfgNWk2vm4WzwRqoorn2dxtZ6QdJhEKbXjG8fG9chkkvr68qJf0fLUL6bIqBXZg2FJ30S7zS3oZ4IZsug-wLyRYuJ4Gu_86snNUo1whNrBdZm5OMREfc9sLbwJ" | |
| /> | |
| <span className="font-arcade text-[8px] font-bold tracking-tight uppercase leading-tight"> | |
| MY COMP | |
| </span> | |
| </button> | |
| {/* Terminal Tile */} | |
| <button | |
| onClick={() => toggleWindowMinimize('terminal')} | |
| className="bg-[#1e293b] text-[#39ff14] border-[3px] border-ink-black p-4 flex flex-col justify-between aspect-square gadget-shadow active:translate-y-1 active:translate-x-1 active:shadow-none select-none text-left cursor-pointer transition-all" | |
| > | |
| <img | |
| alt="Terminal" | |
| className="w-10 h-10 object-cover border-2 border-ink-black bg-white" | |
| src="https://lh3.googleusercontent.com/aida-public/AB6AXuA2AvcQ2oKWP8AJ8yR5nF82LLDEH1FKySUK-npnqEjvJ6Cs9xp8joCwH7x43oTCAC5xhO2rQdYzka3B6u72FNbdxMnsv3WJLye3bKochu8AnnRlS3szfYSaj724sYsiZo4n8G-oWqvl7C0rOGaDaU2nRH1xXmW2PBi6L2NryTsUaTCVZQKTKBBNNEOig5kh7B52e-iIuj2PTzaSjF9ebp6gMuCnBtJT8Sxo5Qsmvfm-t2U5r_qQpRiYj--Ut41T7NTh-tHgs4S4kBvz" | |
| /> | |
| <span className="font-arcade text-[8px] text-[#39ff14] font-bold tracking-tight uppercase leading-tight"> | |
| TERMINAL | |
| </span> | |
| </button> | |
| {/* Python Challenge Tile */} | |
| <button | |
| onClick={() => toggleWindowMinimize('python')} | |
| className="bg-[#9d4edd] text-white border-[3px] border-ink-black p-4 flex flex-col justify-between aspect-square gadget-shadow active:translate-y-1 active:translate-x-1 active:shadow-none select-none text-left cursor-pointer transition-all" | |
| > | |
| <span className="material-symbols-outlined text-3xl align-top" style={{ fontVariationSettings: "'FILL' 1" }}> | |
| sports_esports | |
| </span> | |
| <span className="font-arcade text-[8px] font-bold tracking-tight uppercase leading-tight"> | |
| PYTHON<br/>PLAY | |
| </span> | |
| </button> | |
| {/* MySQL Playground Tile */} | |
| <button | |
| onClick={() => toggleWindowMinimize('mysql')} | |
| className="bg-[#ffcc00] text-ink-black border-[3px] border-ink-black p-4 flex flex-col justify-between aspect-square gadget-shadow active:translate-y-1 active:translate-x-1 active:shadow-none select-none text-left cursor-pointer transition-all" | |
| > | |
| <span className="material-symbols-outlined text-3xl align-top" style={{ fontVariationSettings: "'FILL' 1" }}> | |
| database | |
| </span> | |
| <span className="font-arcade text-[8px] font-bold tracking-tight uppercase leading-tight"> | |
| MYSQL<br/>PLAY | |
| </span> | |
| </button> | |
| {localStorage.getItem('algospaced_sandbox_mode') === 'true' && ( | |
| <button | |
| onClick={() => toggleWindowMinimize('settings')} | |
| className="bg-[#10b981] text-white border-[3px] border-ink-black p-4 flex flex-col justify-between aspect-square gadget-shadow active:translate-y-1 active:translate-x-1 active:shadow-none select-none text-left cursor-pointer transition-all" | |
| > | |
| <span className="material-symbols-outlined text-3xl align-top" style={{ fontVariationSettings: "'FILL' 1" }}> | |
| key | |
| </span> | |
| <span className="font-arcade text-[8px] font-bold tracking-tight uppercase leading-tight"> | |
| API<br/>KEYS | |
| </span> | |
| </button> | |
| )} | |
| {/* Drive Sync Tile (Wide - 2 cols) */} | |
| <button | |
| onClick={triggerSync} | |
| className="col-span-2 bg-[#ffe24c] hover:bg-yellow-300 text-ink-black border-[3px] border-ink-black p-4 flex items-center justify-between gadget-shadow active:translate-y-1 active:translate-x-1 active:shadow-none select-none text-left cursor-pointer transition-all" | |
| > | |
| <div className="flex items-center gap-3"> | |
| <span className="material-symbols-outlined text-3xl" style={{ fontVariationSettings: "'FILL' 1" }}> | |
| cloud_sync | |
| </span> | |
| <span className="font-arcade text-[8px] font-bold tracking-tight uppercase"> | |
| DRIVE NOTES SYNC | |
| </span> | |
| </div> | |
| <span className="material-symbols-outlined text-lg">chevron_right</span> | |
| </button> | |
| </div> | |
| </div> | |
| )} | |
| {/* Floating Windows Shell Containers */} | |
| {/* 1. Daily Review Queue Window */} | |
| <WindowShell | |
| id="queue" | |
| title="Daily Queue.exe" | |
| icon="terminal" | |
| isOpen={windows.queue.isOpen} | |
| isMinimized={windows.queue.isMinimized} | |
| onClose={() => closeWindow('queue')} | |
| onMinimize={() => minimizeWindow('queue')} | |
| activeWindow={activeWindow} | |
| setActiveWindow={setActiveWindow} | |
| defaultWidth="750px" | |
| defaultHeight="600px" | |
| isMobile={isMobile} | |
| > | |
| <ReviewQueue | |
| overrideProblemId={overrideProblemId} | |
| onClearOverride={() => setOverrideProblemId(null)} | |
| /> | |
| </WindowShell> | |
| {/* 2. Journey Path Map Window */} | |
| <WindowShell | |
| id="journey" | |
| title="Journey_Path.exe" | |
| icon="route" | |
| isOpen={windows.journey.isOpen} | |
| isMinimized={windows.journey.isMinimized} | |
| onClose={() => closeWindow('journey')} | |
| onMinimize={() => minimizeWindow('journey')} | |
| activeWindow={activeWindow} | |
| setActiveWindow={setActiveWindow} | |
| defaultWidth="700px" | |
| defaultHeight="560px" | |
| isMobile={isMobile} | |
| > | |
| <JourneyPath | |
| onSelectProblemForReview={handleSelectProblemForReview} | |
| /> | |
| </WindowShell> | |
| {/* 3. Command Line Terminal Window */} | |
| <WindowShell | |
| id="terminal" | |
| title="Arcade Terminal.com" | |
| icon="terminal" | |
| isOpen={windows.terminal.isOpen} | |
| isMinimized={windows.terminal.isMinimized} | |
| onClose={() => closeWindow('terminal')} | |
| onMinimize={() => minimizeWindow('terminal')} | |
| activeWindow={activeWindow} | |
| setActiveWindow={setActiveWindow} | |
| defaultWidth="640px" | |
| defaultHeight="450px" | |
| titleBarColor="#1e293b" | |
| isMobile={isMobile} | |
| > | |
| <TerminalEmulator | |
| onOpenWindow={openWindow} | |
| onClose={() => closeWindow('terminal')} | |
| /> | |
| </WindowShell> | |
| {/* 4. Google Drive Sync Dialog Window */} | |
| <WindowShell | |
| id="sync" | |
| title="Drive_Sync.sys" | |
| icon="cloud_sync" | |
| isOpen={windows.sync.isOpen} | |
| isMinimized={windows.sync.isMinimized} | |
| onClose={() => closeWindow('sync')} | |
| onMinimize={() => minimizeWindow('sync')} | |
| activeWindow={activeWindow} | |
| setActiveWindow={setActiveWindow} | |
| defaultWidth="480px" | |
| defaultHeight="320px" | |
| titleBarColor="#855400" | |
| isMobile={isMobile} | |
| > | |
| <div className="w-full h-full bg-[#fdf8e1] p-6 flex flex-col justify-between"> | |
| <div className="space-y-4"> | |
| <div className="flex items-center gap-3 border-b-2 border-dashed border-ink-black pb-3"> | |
| <span className="material-symbols-outlined text-4xl text-[#855400] animate-bounce">cloud_sync</span> | |
| <div> | |
| <h4 className="font-arcade text-[10px] font-bold text-ink-black">DRIVE NOTES SYNCHRONIZATION</h4> | |
| <span className="text-[8px] font-arcade text-trash-gray font-bold uppercase mt-1 block">Importing LeetCode sheets</span> | |
| </div> | |
| </div> | |
| {syncing ? ( | |
| <div className="space-y-3 py-2"> | |
| <span className="text-[9px] font-arcade text-ink-black font-bold block animate-pulse">SYNC PROCESS RUNNING...</span> | |
| <div className="w-full h-6 bg-white border-[3px] border-ink-black relative overflow-hidden select-none"> | |
| <div className="absolute inset-y-0 left-0 bg-[#ffe24c] border-r-2 border-ink-black w-[75%] animate-pulse" /> | |
| </div> | |
| <p className="text-[9px] font-sans text-trash-gray leading-normal font-semibold"> | |
| Connecting to Supabase partition, requesting Google Drive manifest, indexing note items... | |
| </p> | |
| </div> | |
| ) : ( | |
| <div className="p-3 bg-white border-[3px] border-ink-black text-xs font-semibold text-ink-black leading-relaxed shadow-[2px_2px_0px_0px_#1E293B]"> | |
| {syncResult || 'Idle. Ready to synchronize Leitner box data logs from Sheets drive storage.'} | |
| </div> | |
| )} | |
| </div> | |
| <div className="flex gap-3 justify-end pt-4 border-t border-slate-200"> | |
| <button | |
| onClick={() => closeWindow('sync')} | |
| className="px-4 py-2 bg-white hover:bg-slate-100 text-ink-black border-[3px] border-ink-black font-bold text-xs shadow-[2px_2px_0px_0px_#1E293B] active:translate-y-0.5 active:shadow-none cursor-pointer" | |
| > | |
| CLOSE | |
| </button> | |
| <button | |
| onClick={triggerSync} | |
| disabled={syncing} | |
| className="px-4 py-2 bg-[#ffcc00] hover:bg-yellow-300 text-ink-black border-[3px] border-ink-black font-bold text-xs shadow-[2px_2px_0px_0px_#1E293B] active:translate-y-0.5 active:shadow-none disabled:opacity-50 cursor-pointer" | |
| > | |
| {syncing ? 'SYNCING...' : 'SYNC NOW'} | |
| </button> | |
| </div> | |
| </div> | |
| </WindowShell> | |
| {/* 5. My Computer Window (Interactive File Explorer) */} | |
| <WindowShell | |
| id="computer" | |
| title="File Explorer" | |
| icon="desktop_windows" | |
| isOpen={windows.computer.isOpen} | |
| isMinimized={windows.computer.isMinimized} | |
| onClose={() => closeWindow('computer')} | |
| onMinimize={() => minimizeWindow('computer')} | |
| activeWindow={activeWindow} | |
| setActiveWindow={setActiveWindow} | |
| defaultWidth="640px" | |
| defaultHeight="480px" | |
| titleBarColor="#006686" | |
| isMobile={isMobile} | |
| > | |
| <FileExplorer onSetWallpaper={setWallpaperUrl} activeWallpaperUrl={wallpaperUrl} /> | |
| </WindowShell> | |
| {/* 6. Daily Python Challenge Window */} | |
| <WindowShell | |
| id="python" | |
| title="Python Challenge" | |
| icon="sports_esports" | |
| isOpen={windows.python.isOpen} | |
| isMinimized={windows.python.isMinimized} | |
| onClose={() => closeWindow('python')} | |
| onMinimize={() => minimizeWindow('python')} | |
| activeWindow={activeWindow} | |
| setActiveWindow={setActiveWindow} | |
| defaultWidth="620px" | |
| defaultHeight="580px" | |
| titleBarColor="#9d4edd" | |
| isMobile={isMobile} | |
| > | |
| <PythonChallenge /> | |
| </WindowShell> | |
| {/* 7. MySQL Playground Window */} | |
| <WindowShell | |
| id="mysql" | |
| title="MySQL Playground" | |
| icon="database" | |
| isOpen={windows.mysql.isOpen} | |
| isMinimized={windows.mysql.isMinimized} | |
| onClose={() => closeWindow('mysql')} | |
| onMinimize={() => minimizeWindow('mysql')} | |
| activeWindow={activeWindow} | |
| setActiveWindow={setActiveWindow} | |
| defaultWidth="660px" | |
| defaultHeight="580px" | |
| titleBarColor="#0ea5e9" | |
| isMobile={isMobile} | |
| > | |
| <MySQLPlayground /> | |
| </WindowShell> | |
| {/* 8. API Control Panel Window */} | |
| <WindowShell | |
| id="settings" | |
| title="Control Panel" | |
| icon="key" | |
| isOpen={windows.settings?.isOpen} | |
| isMinimized={windows.settings?.isMinimized} | |
| onClose={() => closeWindow('settings')} | |
| onMinimize={() => minimizeWindow('settings')} | |
| activeWindow={activeWindow} | |
| setActiveWindow={setActiveWindow} | |
| defaultWidth="500px" | |
| defaultHeight="450px" | |
| titleBarColor="#10b981" | |
| isMobile={isMobile} | |
| > | |
| <Settings /> | |
| </WindowShell> | |
| {/* Start Menu Popup */} | |
| {isStartOpen && ( | |
| <div | |
| onClick={(e) => e.stopPropagation()} | |
| className="absolute bottom-14 left-0 w-64 bg-white border-[3px] border-ink-black shadow-[6px_6px_0px_0px_#1E293B] flex flex-col p-4 z-[60] ml-2 mb-2 select-none" | |
| > | |
| <div className="flex flex-col gap-2.5 font-window-title text-base font-bold"> | |
| <button | |
| onClick={() => openWindow('queue')} | |
| className="w-full text-left hover:bg-secondary-container px-3 py-1.5 border-2 border-transparent hover:border-ink-black transition-colors cursor-pointer flex items-center gap-2" | |
| > | |
| <span className="material-symbols-outlined text-lg" style={{ fontVariationSettings: "'FILL' 1" }}>view_list</span> | |
| Daily Queue | |
| </button> | |
| <button | |
| onClick={() => openWindow('journey')} | |
| className="w-full text-left hover:bg-secondary-container px-3 py-1.5 border-2 border-transparent hover:border-ink-black transition-colors cursor-pointer flex items-center gap-2" | |
| > | |
| <span className="material-symbols-outlined text-lg" style={{ fontVariationSettings: "'FILL' 1" }}>route</span> | |
| Journey Path | |
| </button> | |
| <button | |
| onClick={triggerSync} | |
| className="w-full text-left hover:bg-secondary-container px-3 py-1.5 border-2 border-transparent hover:border-ink-black transition-colors cursor-pointer flex items-center gap-2" | |
| > | |
| <span className="material-symbols-outlined text-lg" style={{ fontVariationSettings: "'FILL' 1" }}>cloud_sync</span> | |
| Drive Sync | |
| </button> | |
| <button | |
| onClick={() => openWindow('terminal')} | |
| className="w-full text-left hover:bg-secondary-container px-3 py-1.5 border-2 border-transparent hover:border-ink-black transition-colors cursor-pointer flex items-center gap-2" | |
| > | |
| <span className="material-symbols-outlined text-lg" style={{ fontVariationSettings: "'FILL' 1" }}>terminal</span> | |
| Terminal | |
| </button> | |
| <button | |
| onClick={() => openWindow('computer')} | |
| className="w-full text-left hover:bg-secondary-container px-3 py-1.5 border-2 border-transparent hover:border-ink-black transition-colors cursor-pointer flex items-center gap-2" | |
| > | |
| <span className="material-symbols-outlined text-lg" style={{ fontVariationSettings: "'FILL' 1" }}>desktop_windows</span> | |
| My Computer | |
| </button> | |
| <button | |
| onClick={() => openWindow('python')} | |
| className="w-full text-left hover:bg-secondary-container px-3 py-1.5 border-2 border-transparent hover:border-ink-black transition-colors cursor-pointer flex items-center gap-2" | |
| > | |
| <span className="material-symbols-outlined text-lg" style={{ fontVariationSettings: "'FILL' 1" }}>sports_esports</span> | |
| Python Challenge | |
| </button> | |
| <button | |
| onClick={() => openWindow('mysql')} | |
| className="w-full text-left hover:bg-secondary-container px-3 py-1.5 border-2 border-transparent hover:border-ink-black transition-colors cursor-pointer flex items-center gap-2" | |
| > | |
| <span className="material-symbols-outlined text-lg" style={{ fontVariationSettings: "'FILL' 1" }}>database</span> | |
| MySQL Playground | |
| </button> | |
| {localStorage.getItem('algospaced_sandbox_mode') === 'true' && ( | |
| <button | |
| onClick={() => openWindow('settings')} | |
| className="w-full text-left hover:bg-[#10b981]/25 px-3 py-1.5 border-2 border-transparent hover:border-[#10b981] transition-colors cursor-pointer flex items-center gap-2" | |
| > | |
| <span className="material-symbols-outlined text-lg text-[#10b981]" style={{ fontVariationSettings: "'FILL' 1" }}>key</span> | |
| Gemini API Config | |
| </button> | |
| )} | |
| </div> | |
| <div className="mt-4 flex justify-end border-t-[3px] border-ink-black pt-4 select-none"> | |
| <button | |
| onClick={handleSignOut} | |
| className="bg-highlight-pink text-white font-window-title px-4 py-2.5 border-[3px] border-ink-black shadow-[3px_3px_0px_0px_#1E293B] active:translate-y-0.5 active:translate-x-0.5 active:shadow-none hover:bg-rose-600 transition-all cursor-pointer font-bold uppercase text-xs" | |
| > | |
| Shut Down | |
| </button> | |
| </div> | |
| </div> | |
| )} | |
| {/* Taskbar Bottom (Taskbar fixed layout) */} | |
| <div | |
| onClick={() => setIsStartOpen(false)} | |
| className="fixed bottom-0 left-0 w-full z-50 flex items-center justify-between h-14 border-t-[4px] border-ink-black bg-[#0ea5e9] px-0 select-none shadow-[0_-4px_0_0_rgba(30,41,59,0.1)]" | |
| > | |
| <div className="flex items-center h-full overflow-x-auto custom-scrollbar"> | |
| {/* Start button */} | |
| <button | |
| onClick={(e) => { | |
| e.stopPropagation(); | |
| setIsStartOpen(!isStartOpen); | |
| }} | |
| className={`h-full ${isMobile ? 'px-3' : 'px-6'} flex items-center gap-2 border-r-[4px] border-ink-black bg-[#39ff14] text-ink-black hover:bg-[#28e007] active:bg-[#1fb304] transition-colors shadow-[inset_-3px_-3px_0px_rgba(0,0,0,0.2),inset_3px_3px_0px_rgba(255,255,255,0.5)] font-bold cursor-pointer`} | |
| > | |
| <span className="material-symbols-outlined text-2xl font-bold" style={{ fontVariationSettings: "'FILL' 1" }}> | |
| sports_esports | |
| </span> | |
| <span className={`font-display-lg ${isMobile ? 'text-sm' : 'text-lg'} italic tracking-wide`}>Start</span> | |
| </button> | |
| {/* Windows tabs in taskbar */} | |
| {/* Daily Queue Tab */} | |
| {windows.queue.isOpen && ( | |
| <button | |
| onClick={() => toggleWindowMinimize('queue')} | |
| className={`h-full ${isMobile ? 'px-2 text-[10px] gap-1' : 'px-4 text-xs gap-2'} border-r-[4px] border-ink-black font-bold font-window-title cursor-pointer transition-all shadow-[inset_0px_3px_0px_rgba(255,255,255,0.8)] ${ | |
| activeWindow === 'queue' && !windows.queue.isMinimized | |
| ? 'bg-paper-white text-ink-black font-extrabold translate-y-[2px]' | |
| : 'bg-surface-variant text-trash-gray hover:bg-white/40' | |
| }`} | |
| > | |
| <span className="material-symbols-outlined text-sm font-bold">view_list</span> | |
| <span>{isMobile ? 'Queue' : 'Daily Queue'}</span> | |
| </button> | |
| )} | |
| {/* Journey Path Tab */} | |
| {windows.journey.isOpen && ( | |
| <button | |
| onClick={() => toggleWindowMinimize('journey')} | |
| className={`h-full ${isMobile ? 'px-2 text-[10px] gap-1' : 'px-4 text-xs gap-2'} border-r-[4px] border-ink-black font-bold font-window-title cursor-pointer transition-all shadow-[inset_0px_3px_0px_rgba(255,255,255,0.8)] ${ | |
| activeWindow === 'journey' && !windows.journey.isMinimized | |
| ? 'bg-paper-white text-ink-black font-extrabold translate-y-[2px]' | |
| : 'bg-surface-variant text-trash-gray hover:bg-white/40' | |
| }`} | |
| > | |
| <span className="material-symbols-outlined text-sm font-bold">route</span> | |
| <span>{isMobile ? 'Journey' : 'Journey Path'}</span> | |
| </button> | |
| )} | |
| {/* Terminal Tab */} | |
| {windows.terminal.isOpen && ( | |
| <button | |
| onClick={() => toggleWindowMinimize('terminal')} | |
| className={`h-full ${isMobile ? 'px-2 text-[10px] gap-1' : 'px-4 text-xs gap-2'} border-r-[4px] border-ink-black font-bold font-window-title cursor-pointer transition-all shadow-[inset_0px_3px_0px_rgba(255,255,255,0.8)] ${ | |
| activeWindow === 'terminal' && !windows.terminal.isMinimized | |
| ? 'bg-paper-white text-ink-black font-extrabold translate-y-[2px]' | |
| : 'bg-surface-variant text-trash-gray hover:bg-white/40' | |
| }`} | |
| > | |
| <span className="material-symbols-outlined text-sm font-bold">terminal</span> | |
| <span>{isMobile ? 'Terminal' : 'Terminal'}</span> | |
| </button> | |
| )} | |
| {/* Google Drive Sync Tab */} | |
| {windows.sync.isOpen && ( | |
| <button | |
| onClick={() => toggleWindowMinimize('sync')} | |
| className={`h-full ${isMobile ? 'px-2 text-[10px] gap-1' : 'px-4 text-xs gap-2'} border-r-[4px] border-ink-black font-bold font-window-title cursor-pointer transition-all shadow-[inset_0px_3px_0px_rgba(255,255,255,0.8)] ${ | |
| activeWindow === 'sync' && !windows.sync.isMinimized | |
| ? 'bg-paper-white text-ink-black font-extrabold translate-y-[2px]' | |
| : 'bg-surface-variant text-trash-gray hover:bg-white/40' | |
| }`} | |
| > | |
| <span className="material-symbols-outlined text-sm font-bold">cloud_sync</span> | |
| <span>{isMobile ? 'Sync' : 'Drive Sync'}</span> | |
| </button> | |
| )} | |
| {/* My Computer Tab */} | |
| {windows.computer.isOpen && ( | |
| <button | |
| onClick={() => toggleWindowMinimize('computer')} | |
| className={`h-full ${isMobile ? 'px-2 text-[10px] gap-1' : 'px-4 text-xs gap-2'} border-r-[4px] border-ink-black font-bold font-window-title cursor-pointer transition-all shadow-[inset_0px_3px_0px_rgba(255,255,255,0.8)] ${ | |
| activeWindow === 'computer' && !windows.computer.isMinimized | |
| ? 'bg-paper-white text-ink-black font-extrabold translate-y-[2px]' | |
| : 'bg-surface-variant text-trash-gray hover:bg-white/40' | |
| }`} | |
| > | |
| <span className="material-symbols-outlined text-sm font-bold">desktop_windows</span> | |
| <span>{isMobile ? 'Comp' : 'My Computer'}</span> | |
| </button> | |
| )} | |
| {/* Python Challenge Tab */} | |
| {windows.python.isOpen && ( | |
| <button | |
| onClick={() => toggleWindowMinimize('python')} | |
| className={`h-full ${isMobile ? 'px-2 text-[10px] gap-1' : 'px-4 text-xs gap-2'} border-r-[4px] border-ink-black font-bold font-window-title cursor-pointer transition-all shadow-[inset_0px_3px_0px_rgba(255,255,255,0.8)] ${ | |
| activeWindow === 'python' && !windows.python.isMinimized | |
| ? 'bg-paper-white text-ink-black font-extrabold translate-y-[2px]' | |
| : 'bg-surface-variant text-trash-gray hover:bg-white/40' | |
| }`} | |
| > | |
| <span className="material-symbols-outlined text-sm font-bold">sports_esports</span> | |
| <span>{isMobile ? 'Python' : 'Python Challenge'}</span> | |
| </button> | |
| )} | |
| {/* MySQL Playground Tab */} | |
| {windows.mysql.isOpen && ( | |
| <button | |
| onClick={() => toggleWindowMinimize('mysql')} | |
| className={`h-full ${isMobile ? 'px-2 text-[10px] gap-1' : 'px-4 text-xs gap-2'} border-r-[4px] border-ink-black font-bold font-window-title cursor-pointer transition-all shadow-[inset_0px_3px_0px_rgba(255,255,255,0.8)] ${ | |
| activeWindow === 'mysql' && !windows.mysql.isMinimized | |
| ? 'bg-paper-white text-ink-black font-extrabold translate-y-[2px]' | |
| : 'bg-surface-variant text-trash-gray hover:bg-white/40' | |
| }`} | |
| > | |
| <span className="material-symbols-outlined text-sm font-bold">database</span> | |
| <span>{isMobile ? 'SQL' : 'MySQL Playground'}</span> | |
| </button> | |
| )} | |
| {/* API Settings Tab */} | |
| {windows.settings?.isOpen && ( | |
| <button | |
| onClick={() => toggleWindowMinimize('settings')} | |
| className={`h-full ${isMobile ? 'px-2 text-[10px] gap-1' : 'px-4 text-xs gap-2'} border-r-[4px] border-ink-black font-bold font-window-title cursor-pointer transition-all shadow-[inset_0px_3px_0px_rgba(255,255,255,0.8)] ${ | |
| activeWindow === 'settings' && !windows.settings.isMinimized | |
| ? 'bg-paper-white text-[#10b981] font-extrabold translate-y-[2px]' | |
| : 'bg-surface-variant text-trash-gray hover:bg-white/40' | |
| }`} | |
| > | |
| <span className="material-symbols-outlined text-sm font-bold text-[#10b981]">key</span> | |
| <span>{isMobile ? 'API' : 'API Config'}</span> | |
| </button> | |
| )} | |
| </div> | |
| {/* System tray (wifi, clock, volume) */} | |
| <div className="flex items-center h-full border-l-[4px] border-ink-black bg-[#0284c7] text-white px-4 gap-4 shadow-[inset_3px_3px_0px_rgba(0,0,0,0.2)]"> | |
| <span className="material-symbols-outlined cursor-pointer select-none text-lg">volume_up</span> | |
| <span className="material-symbols-outlined cursor-pointer select-none text-lg">network_wifi</span> | |
| <div className="font-arcade text-[9px] font-bold flex flex-col items-center leading-none justify-center select-none pt-0.5"> | |
| <span>{time}</span> | |
| </div> | |
| </div> | |
| </div> | |
| {/* Context Menu (Right Click Options) */} | |
| {contextMenu.visible && ( | |
| <div | |
| style={{ top: contextMenu.y, left: contextMenu.x }} | |
| className="absolute bg-paper-white border-[3px] border-ink-black shadow-[4px_4px_0px_0px_#1E293B] z-[999] py-1 w-48 font-headline-md text-xs font-bold text-ink-black select-none animate-fade-in" | |
| > | |
| <button | |
| onClick={toggleFullscreen} | |
| className="w-full text-left px-4 py-2.5 hover:bg-secondary-container border-b-[2px] border-dashed border-ink-black flex items-center gap-2 cursor-pointer font-bold select-none outline-none" | |
| > | |
| <span className="material-symbols-outlined text-sm">fullscreen</span> | |
| {isFullscreen ? 'Exit Full Screen' : 'Go Full Screen'} | |
| </button> | |
| <button | |
| onClick={() => { | |
| closeContextMenu(); | |
| document.getElementById('wallpaper-input')?.click(); | |
| }} | |
| className="w-full text-left px-4 py-2.5 hover:bg-secondary-container flex items-center gap-2 cursor-pointer font-bold select-none outline-none" | |
| > | |
| <span className="material-symbols-outlined text-sm">image</span> | |
| Change Wallpaper | |
| </button> | |
| </div> | |
| )} | |
| {/* Hidden Wallpaper File Input Selector */} | |
| <input | |
| type="file" | |
| id="wallpaper-input" | |
| accept="image/*" | |
| onChange={handleWallpaperChange} | |
| className="hidden" | |
| /> | |
| </div> | |
| ); | |
| } | |