mirror of
https://github.com/AeThex-Corporation/AeThex-OS.git
synced 2026-04-24 00:47:20 +00:00
1573 lines
68 KiB
TypeScript
1573 lines
68 KiB
TypeScript
import { useState, useRef, useCallback, useEffect } from "react";
|
|
import { useQuery } from "@tanstack/react-query";
|
|
import { motion, AnimatePresence, useMotionValue, useTransform, PanInfo } from "framer-motion";
|
|
import { useLocation } from "wouter";
|
|
import { useAuth } from "@/lib/auth";
|
|
import { useWebSocket } from "@/hooks/use-websocket";
|
|
import { getIcon } from "@/lib/iconMap";
|
|
import { usePlatformLayout, PlatformSwitch } from "@/hooks/use-platform-layout";
|
|
import { useHaptics } from "@/hooks/use-haptics";
|
|
import { useMobileNative } from "@/hooks/use-mobile-native";
|
|
import { useNativeFeatures } from "@/hooks/use-native-features";
|
|
import { useBiometricAuth } from "@/hooks/use-biometric-auth";
|
|
import { StatusBar, Style } from '@capacitor/status-bar';
|
|
import { MobileQuickActions } from "@/components/MobileQuickActions";
|
|
import { Minesweeper } from "@/components/games/Minesweeper";
|
|
import { CookieClicker } from "@/components/games/CookieClicker";
|
|
import AethexStudio from "@/components/AethexStudio";
|
|
import AethexAppStore from "@/components/AethexAppStore";
|
|
import { BootSequence } from "@/os/boot/BootSequence";
|
|
import { Taskbar } from "@/os/core/Taskbar";
|
|
import { TerminalApp } from "@/os/apps/TerminalApp";
|
|
import { PassportApp } from "@/os/apps/PassportApp";
|
|
import { SettingsApp, WALLPAPERS, ACCENT_COLORS, ThemeSettings, DesktopLayout } from "@/os/apps/SettingsApp";
|
|
import { FilesApp } from "@/os/apps/FilesApp";
|
|
import { AchievementsApp } from "@/os/apps/AchievementsApp";
|
|
import { OpportunitiesApp } from "@/os/apps/OpportunitiesApp";
|
|
import { EventsApp } from "@/os/apps/EventsApp";
|
|
import { ChatApp } from "@/os/apps/ChatApp";
|
|
import { ManifestoApp } from "@/os/apps/ManifestoApp";
|
|
import { MusicApp } from "@/os/apps/MusicApp";
|
|
import { PitchApp } from "@/os/apps/PitchApp";
|
|
import { NetworkMapApp } from "@/os/apps/NetworkMapApp";
|
|
import { MetricsDashboardApp } from "@/os/apps/MetricsDashboardApp";
|
|
import { CodeEditorApp } from "@/os/apps/CodeEditorApp";
|
|
import { NewsFeedApp } from "@/os/apps/NewsFeedApp";
|
|
import { ArcadeApp } from "@/os/apps/ArcadeApp";
|
|
import { ProfilesApp } from "@/os/apps/ProfilesApp";
|
|
import { NetworkNeighborhoodApp } from "@/os/apps/NetworkNeighborhoodApp";
|
|
import { FoundryApp } from "@/os/apps/FoundryApp";
|
|
import { DevToolsApp } from "@/os/apps/DevToolsApp";
|
|
import { IntelApp } from "@/os/apps/IntelApp";
|
|
import { DrivesApp } from "@/os/apps/DrivesApp";
|
|
import { MissionApp } from "@/os/apps/MissionApp";
|
|
import { LeaderboardApp } from "@/os/apps/LeaderboardApp";
|
|
import { CalculatorApp } from "@/os/apps/CalculatorApp";
|
|
import { NotesApp } from "@/os/apps/NotesApp";
|
|
import { SystemMonitorApp } from "@/os/apps/SystemMonitorApp";
|
|
import { WebcamApp } from "@/os/apps/WebcamApp";
|
|
import {
|
|
ProjectsAppWrapper,
|
|
MessagingAppWrapper,
|
|
MarketplaceAppWrapper,
|
|
FileManagerAppWrapper,
|
|
CodeGalleryAppWrapper,
|
|
NotificationsAppWrapper,
|
|
AnalyticsAppWrapper
|
|
} from "@/os/apps/IframeWrappers";
|
|
import { DesktopWidgets, DraggableWidget, WidgetPosition, WidgetPositions, getDefaultWidgetPositions } from "@/os/components/Widgets";
|
|
import { DesktopIcon, ContextMenuComponent, MenuItem } from "@/os/components/Desktop";
|
|
import { Window } from "@/os/components/Window";
|
|
import { Skeleton, LoadingSkeleton, ParticleField } from "@/os/components/UI";
|
|
import { SpotlightSearch, ToastContainer, OnboardingTour } from "@/os/components/Overlays";
|
|
import { OSProviders, useWindows, useDesktop, useSettings, useNotifications } from "@/os/contexts";
|
|
import {
|
|
Terminal, FileText, IdCard, Music, Settings, Globe,
|
|
X, Minus, Square, Maximize2, Volume2, Wifi, Battery,
|
|
ChevronUp, FolderOpen, Award, MessageCircle, Send,
|
|
ExternalLink, User, LogOut, BarChart3, Loader2, Layers,
|
|
Presentation, Bell, Image, Monitor, Play, Pause, ChevronRight,
|
|
Network, Activity, Code2, Radio, Newspaper, Gamepad2,
|
|
Users, Trophy, Calculator, StickyNote, Cpu, Camera,
|
|
Eye, Shield, Zap, Skull, Lock, Unlock, Server, Database,
|
|
TrendingUp, ArrowUp, ArrowDown, Hash, Key, HardDrive, FolderSearch,
|
|
AlertTriangle, Briefcase, CalendarDays, FolderGit2, MessageSquare,
|
|
ShoppingCart, Folder, Code, Home, Flag, Cookie, ChevronLeft,
|
|
MoreVertical, Search, Mic, ArrowLeft, RefreshCw, Star, Clock, MapPin,
|
|
Store, Rocket
|
|
} from "lucide-react";
|
|
|
|
interface WindowState {
|
|
id: string;
|
|
title: string;
|
|
icon: React.ReactNode;
|
|
component: string;
|
|
x: number;
|
|
y: number;
|
|
width: number;
|
|
height: number;
|
|
minimized: boolean;
|
|
maximized: boolean;
|
|
zIndex: number;
|
|
accentColor?: string;
|
|
desktopId: number;
|
|
iframeUrl?: string;
|
|
}
|
|
|
|
interface Toast {
|
|
id: string;
|
|
message: string;
|
|
type: 'info' | 'success' | 'warning' | 'error';
|
|
}
|
|
|
|
type ClearanceMode = 'foundation' | 'corp';
|
|
|
|
interface ClearanceTheme {
|
|
id: ClearanceMode;
|
|
name: string;
|
|
title: string;
|
|
subtitle: string;
|
|
primary: string;
|
|
secondary: string;
|
|
accent: string;
|
|
accentSecondary: string;
|
|
wallpaper: string;
|
|
borderStyle: string;
|
|
fontStyle: string;
|
|
}
|
|
|
|
const DAILY_TIPS = [
|
|
{ title: "Quick Launch", tip: "Press Ctrl+Space to open Spotlight search and quickly find apps." },
|
|
{ title: "Virtual Desktops", tip: "Use the numbered buttons (1-4) in the taskbar to switch between virtual desktops." },
|
|
{ title: "Window Management", tip: "Double-click a window title bar to maximize/restore it." },
|
|
{ title: "Keyboard Shortcuts", tip: "Ctrl+T opens Terminal, Ctrl+S opens Settings, Ctrl+F opens Files." },
|
|
{ title: "Theme Switching", tip: "Click the Start menu and use 'Switch Clearance' to change between Foundation and Corp modes." },
|
|
{ title: "Sound Settings", tip: "Toggle system sounds in Settings to enable audio feedback for actions." },
|
|
{ title: "Dock Apps", tip: "Your most-used apps are pinned to the quick-launch dock for easy access." },
|
|
{ title: "Right-Click Menu", tip: "Right-click on the desktop to access quick options like refresh and settings." },
|
|
{ title: "Calculator", tip: "Need quick math? Open Calculator from the app menu or dock." },
|
|
{ title: "Notifications", tip: "Click the bell icon in the taskbar to view system notifications." },
|
|
];
|
|
|
|
const PINNED_APPS = ['terminal', 'networkneighborhood', 'calculator', 'settings'];
|
|
|
|
const CLEARANCE_THEMES: Record<ClearanceMode, ClearanceTheme> = {
|
|
foundation: {
|
|
id: 'foundation',
|
|
name: 'The Foundation',
|
|
title: 'FOUNDATION',
|
|
subtitle: 'The Architect\'s Domain',
|
|
primary: '#DC2626',
|
|
secondary: '#D4AF37',
|
|
accent: '#DC2626',
|
|
accentSecondary: '#D4AF37',
|
|
wallpaper: 'radial-gradient(ellipse at 30% 20%, #4a1515 0%, #1a0505 40%, #0a0202 100%)',
|
|
borderStyle: 'border-yellow-600/40',
|
|
fontStyle: 'font-mono',
|
|
},
|
|
corp: {
|
|
id: 'corp',
|
|
name: 'The Corp',
|
|
title: 'CORPORATION',
|
|
subtitle: 'Executive Operations',
|
|
primary: '#0F172A',
|
|
secondary: '#C0C0C0',
|
|
accent: '#3B82F6',
|
|
accentSecondary: '#C0C0C0',
|
|
wallpaper: 'radial-gradient(ellipse at 70% 80%, #1e3a5f 0%, #0f172a 40%, #050a14 100%)',
|
|
borderStyle: 'border-slate-400/30',
|
|
fontStyle: 'font-sans',
|
|
},
|
|
};
|
|
|
|
interface DesktopApp {
|
|
id: string;
|
|
title: string;
|
|
icon: React.ReactNode;
|
|
component: string;
|
|
defaultWidth: number;
|
|
defaultHeight: number;
|
|
}
|
|
|
|
interface ContextMenuState {
|
|
x: number;
|
|
y: number;
|
|
type: 'desktop' | 'icon';
|
|
appId?: string;
|
|
}
|
|
|
|
const WALLPAPERS = [
|
|
];
|
|
|
|
const KONAMI_CODE = ['ArrowUp', 'ArrowUp', 'ArrowDown', 'ArrowDown', 'ArrowLeft', 'ArrowRight', 'ArrowLeft', 'ArrowRight', 'b', 'a'];
|
|
|
|
function AeThexOSInner() {
|
|
const layout = usePlatformLayout();
|
|
const { impact, notification } = useHaptics();
|
|
const { keyboardVisible, deviceInfo } = useMobileNative('dark');
|
|
const native = useNativeFeatures();
|
|
const biometric = useBiometricAuth();
|
|
|
|
// Context hooks
|
|
const windowContext = useWindows();
|
|
const {
|
|
currentDesktop,
|
|
desktopIcons,
|
|
showStartMenu,
|
|
contextMenu,
|
|
mousePosition,
|
|
setCurrentDesktop,
|
|
setDesktopIcons,
|
|
setShowStartMenu,
|
|
toggleStartMenu,
|
|
setContextMenu,
|
|
setMousePosition,
|
|
getDesktopWindowCount
|
|
} = useDesktop();
|
|
const {
|
|
theme,
|
|
wallpaper,
|
|
soundEnabled,
|
|
clearanceMode,
|
|
clearanceTheme,
|
|
isSwitchingClearance,
|
|
savedLayouts,
|
|
setTheme,
|
|
setWallpaper,
|
|
setSoundEnabled,
|
|
setClearanceMode,
|
|
setIsSwitchingClearance,
|
|
setSavedLayouts
|
|
} = useSettings();
|
|
const {
|
|
toasts,
|
|
notifications,
|
|
showNotifications,
|
|
addToast,
|
|
addNotification,
|
|
clearNotification,
|
|
clearAllNotifications,
|
|
setShowNotifications,
|
|
toggleNotifications
|
|
} = useNotifications();
|
|
|
|
// Local state (not moved to context)
|
|
const [isBooting, setIsBooting] = useState(!layout.isMobile);
|
|
const [bootProgress, setBootProgress] = useState(0);
|
|
const [bootStep, setBootStep] = useState('');
|
|
const [time, setTime] = useState(new Date());
|
|
const [showScreensaver, setShowScreensaver] = useState(false);
|
|
const [secretsUnlocked, setSecretsUnlocked] = useState(false);
|
|
const [konamiProgress, setKonamiProgress] = useState<string[]>([]);
|
|
const [showSpotlight, setShowSpotlight] = useState(false);
|
|
const [spotlightQuery, setSpotlightQuery] = useState('');
|
|
const [showOnboarding, setShowOnboarding] = useState(false);
|
|
const [onboardingStep, setOnboardingStep] = useState(0);
|
|
const [showDailyTip, setShowDailyTip] = useState(false);
|
|
const [dailyTip, setDailyTip] = useState(DAILY_TIPS[0]);
|
|
const audioContextRef = useRef<AudioContext | null>(null);
|
|
const desktopRef = useRef<HTMLDivElement>(null);
|
|
const idleTimer = useRef<NodeJS.Timeout | null>(null);
|
|
const spotlightRef = useRef<HTMLInputElement>(null);
|
|
|
|
// Motion values for mobile gestures (must be declared before any conditional returns)
|
|
const dragX = useMotionValue(0);
|
|
const dragOpacity = useTransform(dragX, [-200, 0, 200], [0.5, 1, 0.5]);
|
|
|
|
const { user, isAuthenticated, logout } = useAuth();
|
|
const [, setLocation] = useLocation();
|
|
const [activeTrayPanel, setActiveTrayPanel] = useState<'wifi' | 'volume' | 'battery' | 'notifications' | 'upgrade' | null>(null);
|
|
const [volume, setVolume] = useState(75);
|
|
const [isMuted, setIsMuted] = useState(false);
|
|
|
|
// WebSocket connection for real-time updates
|
|
const {
|
|
connected: wsConnected,
|
|
metrics: wsMetrics,
|
|
alerts: wsAlerts,
|
|
achievements: wsAchievements,
|
|
notifications: wsNotifications
|
|
} = useWebSocket();
|
|
const [batteryInfo, setBatteryInfo] = useState<{ level: number; charging: boolean } | null>(null);
|
|
|
|
useEffect(() => {
|
|
let battery: any = null;
|
|
let levelChangeHandler: (() => void) | null = null;
|
|
let chargingChangeHandler: (() => void) | null = null;
|
|
|
|
if ('getBattery' in navigator) {
|
|
(navigator as any).getBattery().then((bat: any) => {
|
|
battery = bat;
|
|
setBatteryInfo({ level: Math.round(battery.level * 100), charging: battery.charging });
|
|
|
|
levelChangeHandler = () => {
|
|
setBatteryInfo(prev => prev ? { ...prev, level: Math.round(battery.level * 100) } : null);
|
|
};
|
|
chargingChangeHandler = () => {
|
|
setBatteryInfo(prev => prev ? { ...prev, charging: battery.charging } : null);
|
|
};
|
|
|
|
battery.addEventListener('levelchange', levelChangeHandler);
|
|
battery.addEventListener('chargingchange', chargingChangeHandler);
|
|
});
|
|
}
|
|
|
|
// Cleanup: remove battery event listeners to prevent memory leak
|
|
return () => {
|
|
if (battery) {
|
|
if (levelChangeHandler) battery.removeEventListener('levelchange', levelChangeHandler);
|
|
if (chargingChangeHandler) battery.removeEventListener('chargingchange', chargingChangeHandler);
|
|
}
|
|
};
|
|
}, []);
|
|
|
|
const { data: weatherData, isFetching: weatherFetching } = useQuery({
|
|
queryKey: ['weather'],
|
|
queryFn: async () => {
|
|
const res = await fetch('https://api.open-meteo.com/v1/forecast?latitude=40.7128&longitude=-74.0060¤t_weather=true&temperature_unit=fahrenheit');
|
|
return res.json();
|
|
},
|
|
refetchInterval: 600000,
|
|
staleTime: 300000,
|
|
});
|
|
|
|
useEffect(() => {
|
|
const handleMouseMove = (e: MouseEvent) => {
|
|
setMousePosition({ x: e.clientX, y: e.clientY });
|
|
};
|
|
window.addEventListener('mousemove', handleMouseMove);
|
|
return () => window.removeEventListener('mousemove', handleMouseMove);
|
|
}, []);
|
|
|
|
const [showLoginPrompt, setShowLoginPrompt] = useState(false);
|
|
const [isDesktopLocked, setIsDesktopLocked] = useState(true);
|
|
const [detectedIdentity, setDetectedIdentity] = useState<{ username?: string; passportId?: string } | null>(null);
|
|
const [threatLevel, setThreatLevel] = useState<'scanning' | 'low' | 'medium' | 'high'>('scanning');
|
|
const [bootLogs, setBootLogs] = useState<string[]>([]);
|
|
|
|
useEffect(() => {
|
|
const bootSequence = async () => {
|
|
const addLog = (text: string) => setBootLogs(prev => [...prev.slice(-8), text]);
|
|
|
|
// Phase 1: Hardware initialization
|
|
const phase1 = [
|
|
{ text: 'POST: Power-On Self Test...', progress: 3 },
|
|
{ text: 'CPU: AMD Ryzen 9 7950X3D @ 4.2GHz... OK', progress: 5 },
|
|
{ text: 'RAM: 64GB DDR5-6000 ECC... OK', progress: 8 },
|
|
{ text: 'GPU: Quantum Accelerator v2.1... OK', progress: 10 },
|
|
{ text: 'NVME: AeThex Vault 2TB... OK', progress: 12 },
|
|
];
|
|
|
|
for (const step of phase1) {
|
|
setBootStep(step.text);
|
|
addLog(step.text);
|
|
setBootProgress(step.progress);
|
|
await new Promise(r => setTimeout(r, 150));
|
|
}
|
|
|
|
// Phase 2: Kernel & filesystem
|
|
const phase2 = [
|
|
{ text: 'Loading AeThex Kernel v4.2.1...', progress: 18 },
|
|
{ text: 'Initializing virtual memory manager...', progress: 22 },
|
|
{ text: 'Mounting encrypted file systems...', progress: 26 },
|
|
{ text: 'Loading device drivers...', progress: 30 },
|
|
];
|
|
|
|
for (const step of phase2) {
|
|
setBootStep(step.text);
|
|
addLog(step.text);
|
|
setBootProgress(step.progress);
|
|
await new Promise(r => setTimeout(r, 200));
|
|
}
|
|
|
|
// Phase 3: Passport Identity Detection
|
|
setBootStep('INITIATING AETHEX PASSPORT SUBSYSTEM...');
|
|
addLog('▸ PASSPORT: Initializing identity subsystem...');
|
|
setBootProgress(35);
|
|
await new Promise(r => setTimeout(r, 300));
|
|
|
|
// Check for existing session/identity
|
|
let foundIdentity = false;
|
|
try {
|
|
const sessionRes = await fetch('/api/auth/session', { credentials: 'include' });
|
|
const sessionData = await sessionRes.json();
|
|
if (sessionData?.authenticated && sessionData?.user) {
|
|
foundIdentity = true;
|
|
setDetectedIdentity({
|
|
username: sessionData.user.username,
|
|
passportId: sessionData.user.id?.slice(0, 8).toUpperCase()
|
|
});
|
|
addLog(`▸ PASSPORT: Identity token detected`);
|
|
setBootStep('PASSPORT: IDENTITY TOKEN DETECTED');
|
|
setBootProgress(40);
|
|
await new Promise(r => setTimeout(r, 300));
|
|
|
|
addLog(`▸ PASSPORT: Verifying credentials for ${sessionData.user.username}...`);
|
|
setBootStep(`Verifying credentials for ${sessionData.user.username}...`);
|
|
setBootProgress(45);
|
|
await new Promise(r => setTimeout(r, 400));
|
|
|
|
addLog(`▸ PASSPORT: Welcome back, ARCHITECT ${sessionData.user.username.toUpperCase()}`);
|
|
setBootStep(`WELCOME BACK, ARCHITECT ${sessionData.user.username.toUpperCase()}`);
|
|
setBootProgress(50);
|
|
await new Promise(r => setTimeout(r, 500));
|
|
}
|
|
} catch (err) {
|
|
// Session fetch failed, continue with guest mode
|
|
if (import.meta.env.DEV) console.debug('[Boot] Session check failed:', err);
|
|
}
|
|
|
|
if (!foundIdentity) {
|
|
addLog('▸ PASSPORT: No active identity token found');
|
|
setBootStep('PASSPORT: NO ACTIVE IDENTITY TOKEN');
|
|
setBootProgress(42);
|
|
await new Promise(r => setTimeout(r, 300));
|
|
|
|
addLog('▸ PASSPORT: Guest access mode available');
|
|
setBootStep('Guest access mode available');
|
|
setBootProgress(48);
|
|
await new Promise(r => setTimeout(r, 300));
|
|
}
|
|
|
|
// Phase 4: Aegis Security Layer
|
|
addLog('▸ AEGIS: Initializing security layer...');
|
|
setBootStep('AEGIS: INITIALIZING SECURITY LAYER...');
|
|
setBootProgress(55);
|
|
await new Promise(r => setTimeout(r, 300));
|
|
|
|
addLog('▸ AEGIS: Loading threat detection modules...');
|
|
setBootStep('Loading threat detection modules...');
|
|
setBootProgress(60);
|
|
await new Promise(r => setTimeout(r, 250));
|
|
|
|
addLog('▸ AEGIS: Scanning network perimeter...');
|
|
setBootStep('AEGIS: SCANNING NETWORK PERIMETER...');
|
|
setBootProgress(65);
|
|
setThreatLevel('scanning');
|
|
await new Promise(r => setTimeout(r, 600));
|
|
|
|
// Simulate threat assessment result
|
|
const threatResult = Math.random();
|
|
if (threatResult < 0.7) {
|
|
setThreatLevel('low');
|
|
addLog('▸ AEGIS: Threat level LOW - All systems nominal');
|
|
setBootStep('THREAT LEVEL: LOW - ALL SYSTEMS NOMINAL');
|
|
} else if (threatResult < 0.95) {
|
|
setThreatLevel('medium');
|
|
addLog('▸ AEGIS: Threat level MEDIUM - Enhanced monitoring active');
|
|
setBootStep('THREAT LEVEL: MEDIUM - MONITORING ACTIVE');
|
|
} else {
|
|
setThreatLevel('high');
|
|
addLog('▸ AEGIS: Threat level ELEVATED - Defensive protocols engaged');
|
|
setBootStep('THREAT LEVEL: ELEVATED - PROTOCOLS ENGAGED');
|
|
}
|
|
|
|
// Schedule upgrade alert in system tray once per session
|
|
try {
|
|
const shown = localStorage.getItem('aethex-upgrade-alert-shown');
|
|
if (!shown) {
|
|
setTimeout(() => {
|
|
try {
|
|
if (!localStorage.getItem('aethex-upgrade-alert-shown')) {
|
|
setActiveTrayPanel('upgrade');
|
|
addToast('⚠️ Architect Access Available — Use tray to upgrade', 'info');
|
|
localStorage.setItem('aethex-upgrade-alert-shown', 'true');
|
|
}
|
|
} catch (err) {
|
|
if (import.meta.env.DEV) console.debug('[Boot] localStorage access failed:', err);
|
|
}
|
|
}, 30000);
|
|
}
|
|
} catch (err) {
|
|
if (import.meta.env.DEV) console.debug('[Boot] Upgrade check failed:', err);
|
|
}
|
|
setBootProgress(75);
|
|
await new Promise(r => setTimeout(r, 400));
|
|
|
|
// Phase 5: Network & Final
|
|
addLog('▸ NEXUS: Connecting to AeThex network...');
|
|
setBootStep('Connecting to Nexus network...');
|
|
setBootProgress(82);
|
|
await new Promise(r => setTimeout(r, 300));
|
|
|
|
addLog('▸ NEXUS: Syncing with distributed nodes...');
|
|
setBootStep('Syncing with distributed nodes...');
|
|
setBootProgress(88);
|
|
await new Promise(r => setTimeout(r, 250));
|
|
|
|
addLog('▸ NEXUS: Connection established - 42 peers online');
|
|
setBootStep('NEXUS: 42 PEERS ONLINE');
|
|
setBootProgress(94);
|
|
await new Promise(r => setTimeout(r, 200));
|
|
|
|
addLog('▸ SYSTEM: AeThex OS ready');
|
|
setBootStep('AETHEX OS READY');
|
|
setBootProgress(100);
|
|
await new Promise(r => setTimeout(r, 500));
|
|
|
|
setShowLoginPrompt(true);
|
|
};
|
|
|
|
bootSequence();
|
|
}, []);
|
|
|
|
useEffect(() => {
|
|
const timer = setInterval(() => setTime(new Date()), 1000);
|
|
return () => clearInterval(timer);
|
|
}, []);
|
|
|
|
useEffect(() => {
|
|
const fetchNotifications = async () => {
|
|
try {
|
|
const res = await fetch('/api/os/notifications');
|
|
const data = await res.json();
|
|
if (Array.isArray(data)) {
|
|
setNotifications(data.map((n: any) => n.message));
|
|
}
|
|
} catch (err) {
|
|
// Notifications fetch failed, not critical
|
|
if (import.meta.env.DEV) console.debug('[OS] Notifications fetch failed:', err);
|
|
}
|
|
};
|
|
fetchNotifications();
|
|
const interval = setInterval(fetchNotifications, 60000);
|
|
return () => clearInterval(interval);
|
|
}, []);
|
|
|
|
useEffect(() => {
|
|
const resetIdle = () => {
|
|
if (showScreensaver) setShowScreensaver(false);
|
|
if (idleTimer.current) clearTimeout(idleTimer.current);
|
|
idleTimer.current = setTimeout(() => setShowScreensaver(true), 5 * 60 * 1000);
|
|
};
|
|
|
|
window.addEventListener('mousemove', resetIdle);
|
|
window.addEventListener('keydown', resetIdle);
|
|
resetIdle();
|
|
|
|
return () => {
|
|
window.removeEventListener('mousemove', resetIdle);
|
|
window.removeEventListener('keydown', resetIdle);
|
|
if (idleTimer.current) clearTimeout(idleTimer.current);
|
|
};
|
|
}, [showScreensaver]);
|
|
|
|
useEffect(() => {
|
|
const handleKonami = (e: KeyboardEvent) => {
|
|
setKonamiProgress(prev => {
|
|
const newProgress = [...prev, e.key].slice(-10);
|
|
if (newProgress.length === 10 && newProgress.every((k, i) => k === KONAMI_CODE[i])) {
|
|
setSecretsUnlocked(true);
|
|
setNotifications(prev => ['🎮 SECRETS UNLOCKED! Check Settings for new wallpapers.', ...prev]);
|
|
return [];
|
|
}
|
|
return newProgress;
|
|
});
|
|
};
|
|
const handleTerminalUnlock = () => {
|
|
setSecretsUnlocked(true);
|
|
setNotifications(prev => ['🔓 Terminal unlock activated! Check Settings.', ...prev]);
|
|
};
|
|
window.addEventListener('keydown', handleKonami);
|
|
window.addEventListener('aethex-unlock-secrets', handleTerminalUnlock);
|
|
return () => {
|
|
window.removeEventListener('keydown', handleKonami);
|
|
window.removeEventListener('aethex-unlock-secrets', handleTerminalUnlock);
|
|
};
|
|
}, []);
|
|
|
|
useEffect(() => {
|
|
if (showSpotlight && spotlightRef.current) {
|
|
spotlightRef.current.focus();
|
|
}
|
|
}, [showSpotlight]);
|
|
|
|
useEffect(() => {
|
|
const saved = localStorage.getItem('aethex-window-positions');
|
|
if (saved) {
|
|
try {
|
|
const positions = JSON.parse(saved);
|
|
setWindows(prev => prev.map(w => {
|
|
const savedPos = positions[w.id];
|
|
return savedPos ? { ...w, ...savedPos } : w;
|
|
}));
|
|
} catch (err) {
|
|
// Corrupted localStorage data, ignore and use defaults
|
|
if (import.meta.env.DEV) console.debug('[OS] Failed to restore window positions:', err);
|
|
}
|
|
}
|
|
const hasVisited = localStorage.getItem('aethex-visited');
|
|
if (!hasVisited) {
|
|
setShowOnboarding(true);
|
|
localStorage.setItem('aethex-visited', 'true');
|
|
}
|
|
}, []);
|
|
|
|
useEffect(() => {
|
|
if (windows.length > 0) {
|
|
const positions: Record<string, { x: number; y: number; width: number; height: number }> = {};
|
|
windows.forEach(w => {
|
|
positions[w.id] = { x: w.x, y: w.y, width: w.width, height: w.height };
|
|
});
|
|
localStorage.setItem('aethex-window-positions', JSON.stringify(positions));
|
|
}
|
|
}, [windows]);
|
|
|
|
const foundationApps: DesktopApp[] = [
|
|
{ id: "networkneighborhood", title: "Network Neighborhood", icon: <Network className="w-8 h-8" />, component: "networkneighborhood", defaultWidth: 500, defaultHeight: 450 },
|
|
{ id: "mission", title: "README.TXT", icon: <FileText className="w-8 h-8" />, component: "mission", defaultWidth: 500, defaultHeight: 500 },
|
|
{ id: "passport", title: "Passport", icon: <Key className="w-8 h-8" />, component: "passport", defaultWidth: 650, defaultHeight: 500 },
|
|
{ id: "achievements", title: "Achievements", icon: <Trophy className="w-8 h-8" />, component: "achievements", defaultWidth: 800, defaultHeight: 600 },
|
|
{ id: "projects", title: "Projects", icon: <FolderGit2 className="w-8 h-8" />, component: "projects", defaultWidth: 900, defaultHeight: 650 },
|
|
{ id: "opportunities", title: "Opportunities", icon: <Briefcase className="w-8 h-8" />, component: "opportunities", defaultWidth: 850, defaultHeight: 650 },
|
|
{ id: "events", title: "Events", icon: <CalendarDays className="w-8 h-8" />, component: "events", defaultWidth: 900, defaultHeight: 650 },
|
|
{ id: "messaging", title: "Messages", icon: <MessageSquare className="w-8 h-8" />, component: "messaging", defaultWidth: 850, defaultHeight: 600 },
|
|
{ id: "marketplace", title: "Marketplace", icon: <ShoppingCart className="w-8 h-8" />, component: "marketplace", defaultWidth: 900, defaultHeight: 650 },
|
|
{ id: "foundry", title: "FOUNDRY.EXE", icon: <Award className="w-8 h-8" />, component: "foundry", defaultWidth: 450, defaultHeight: 500 },
|
|
{ id: "intel", title: "INTEL", icon: <FolderSearch className="w-8 h-8" />, component: "intel", defaultWidth: 550, defaultHeight: 450 },
|
|
{ id: "filemanager", title: "File Manager", icon: <Folder className="w-8 h-8" />, component: "filemanager", defaultWidth: 800, defaultHeight: 600 },
|
|
{ id: "codegallery", title: "Code Gallery", icon: <Code className="w-8 h-8" />, component: "codegallery", defaultWidth: 900, defaultHeight: 650 },
|
|
{ id: "drives", title: "My Computer", icon: <HardDrive className="w-8 h-8" />, component: "drives", defaultWidth: 450, defaultHeight: 400 },
|
|
{ id: "chat", title: "AeThex AI", icon: <MessageCircle className="w-8 h-8" />, component: "chat", defaultWidth: 400, defaultHeight: 500 },
|
|
{ id: "terminal", title: "Terminal", icon: <Terminal className="w-8 h-8" />, component: "terminal", defaultWidth: 750, defaultHeight: 500 },
|
|
{ id: "notifications", title: "Notifications", icon: <Bell className="w-8 h-8" />, component: "notifications", defaultWidth: 700, defaultHeight: 600 },
|
|
{ id: "analytics", title: "Analytics", icon: <BarChart3 className="w-8 h-8" />, component: "analytics", defaultWidth: 1000, defaultHeight: 700 },
|
|
{ id: "metrics", title: "System Status", icon: <Activity className="w-8 h-8" />, component: "metrics", defaultWidth: 750, defaultHeight: 550 },
|
|
{ id: "devtools", title: "Dev Tools", icon: <Code2 className="w-8 h-8" />, component: "devtools", defaultWidth: 450, defaultHeight: 400 },
|
|
{ id: "music", title: "Radio AeThex", icon: <Radio className="w-8 h-8" />, component: "music", defaultWidth: 400, defaultHeight: 350 },
|
|
{ id: "codeeditor", title: "The Lab", icon: <Code2 className="w-8 h-8" />, component: "codeeditor", defaultWidth: 700, defaultHeight: 500 },
|
|
{ id: "arcade", title: "Snake", icon: <Gamepad2 className="w-8 h-8" />, component: "arcade", defaultWidth: 420, defaultHeight: 520 },
|
|
{ id: "minesweeper", title: "Minesweeper", icon: <Flag className="w-8 h-8" />, component: "minesweeper", defaultWidth: 400, defaultHeight: 500 },
|
|
{ id: "cookieclicker", title: "Cookie Clicker", icon: <Cookie className="w-8 h-8" />, component: "cookieclicker", defaultWidth: 420, defaultHeight: 600 },
|
|
{ id: "aethexstudio", title: "AeThex Studio", icon: <Rocket className="w-8 h-8" />, component: "aethexstudio", defaultWidth: 1200, defaultHeight: 700 },
|
|
{ id: "aethexappstore", title: "App Store", icon: <Store className="w-8 h-8" />, component: "aethexappstore", defaultWidth: 1000, defaultHeight: 650 },
|
|
{ id: "calculator", title: "Calculator", icon: <Calculator className="w-8 h-8" />, component: "calculator", defaultWidth: 320, defaultHeight: 450 },
|
|
{ id: "settings", title: "Settings", icon: <Settings className="w-8 h-8" />, component: "settings", defaultWidth: 550, defaultHeight: 500 },
|
|
];
|
|
|
|
const corpApps: DesktopApp[] = [
|
|
{ id: "networkneighborhood", title: "Network Neighborhood", icon: <Network className="w-8 h-8" />, component: "networkneighborhood", defaultWidth: 500, defaultHeight: 450 },
|
|
{ id: "mission", title: "README.TXT", icon: <FileText className="w-8 h-8" />, component: "mission", defaultWidth: 500, defaultHeight: 500 },
|
|
{ id: "passport", title: "Passport", icon: <Key className="w-8 h-8" />, component: "passport", defaultWidth: 650, defaultHeight: 500 },
|
|
{ id: "achievements", title: "Achievements", icon: <Trophy className="w-8 h-8" />, component: "achievements", defaultWidth: 800, defaultHeight: 600 },
|
|
{ id: "projects", title: "Projects", icon: <FolderGit2 className="w-8 h-8" />, component: "projects", defaultWidth: 900, defaultHeight: 650 },
|
|
{ id: "opportunities", title: "Opportunities", icon: <Briefcase className="w-8 h-8" />, component: "opportunities", defaultWidth: 850, defaultHeight: 650 },
|
|
{ id: "events", title: "Events", icon: <CalendarDays className="w-8 h-8" />, component: "events", defaultWidth: 900, defaultHeight: 650 },
|
|
{ id: "messaging", title: "Messages", icon: <MessageSquare className="w-8 h-8" />, component: "messaging", defaultWidth: 850, defaultHeight: 600 },
|
|
{ id: "marketplace", title: "Marketplace", icon: <ShoppingCart className="w-8 h-8" />, component: "marketplace", defaultWidth: 900, defaultHeight: 650 },
|
|
{ id: "foundry", title: "FOUNDRY.EXE", icon: <Award className="w-8 h-8" />, component: "foundry", defaultWidth: 450, defaultHeight: 500 },
|
|
{ id: "intel", title: "INTEL", icon: <FolderSearch className="w-8 h-8" />, component: "intel", defaultWidth: 550, defaultHeight: 450 },
|
|
{ id: "filemanager", title: "File Manager", icon: <Folder className="w-8 h-8" />, component: "filemanager", defaultWidth: 800, defaultHeight: 600 },
|
|
{ id: "codegallery", title: "Code Gallery", icon: <Code className="w-8 h-8" />, component: "codegallery", defaultWidth: 900, defaultHeight: 650 },
|
|
{ id: "drives", title: "My Computer", icon: <HardDrive className="w-8 h-8" />, component: "drives", defaultWidth: 450, defaultHeight: 400 },
|
|
{ id: "devtools", title: "Dev Tools", icon: <Code2 className="w-8 h-8" />, component: "devtools", defaultWidth: 450, defaultHeight: 400 },
|
|
{ id: "notifications", title: "Notifications", icon: <Bell className="w-8 h-8" />, component: "notifications", defaultWidth: 700, defaultHeight: 600 },
|
|
{ id: "analytics", title: "Analytics", icon: <BarChart3 className="w-8 h-8" />, component: "analytics", defaultWidth: 1000, defaultHeight: 700 },
|
|
{ id: "metrics", title: "System Status", icon: <Activity className="w-8 h-8" />, component: "metrics", defaultWidth: 750, defaultHeight: 550 },
|
|
{ id: "network", title: "Global Ops", icon: <Globe className="w-8 h-8" />, component: "network", defaultWidth: 700, defaultHeight: 550 },
|
|
{ id: "files", title: "Asset Library", icon: <Database className="w-8 h-8" />, component: "files", defaultWidth: 700, defaultHeight: 500 },
|
|
{ id: "pitch", title: "Contracts", icon: <FileText className="w-8 h-8" />, component: "pitch", defaultWidth: 500, defaultHeight: 400 },
|
|
{ id: "sysmonitor", title: "Infrastructure", icon: <Server className="w-8 h-8" />, component: "sysmonitor", defaultWidth: 450, defaultHeight: 400 },
|
|
{ id: "leaderboard", title: "Performance", icon: <BarChart3 className="w-8 h-8" />, component: "leaderboard", defaultWidth: 500, defaultHeight: 550 },
|
|
{ id: "aethexstudio", title: "AeThex Studio", icon: <Rocket className="w-8 h-8" />, component: "aethexstudio", defaultWidth: 1200, defaultHeight: 700 },
|
|
{ id: "aethexappstore", title: "App Store", icon: <Store className="w-8 h-8" />, component: "aethexappstore", defaultWidth: 1000, defaultHeight: 650 },
|
|
{ id: "calculator", title: "Calculator", icon: <Calculator className="w-8 h-8" />, component: "calculator", defaultWidth: 320, defaultHeight: 450 },
|
|
{ id: "settings", title: "Settings", icon: <Settings className="w-8 h-8" />, component: "settings", defaultWidth: 550, defaultHeight: 500 },
|
|
];
|
|
|
|
const apps = clearanceMode === 'foundation' ? foundationApps : corpApps;
|
|
|
|
// Handle WebSocket notifications
|
|
useEffect(() => {
|
|
if (wsNotifications && wsNotifications.length > 0) {
|
|
wsNotifications.forEach((notification: any) => {
|
|
if (notification.message) {
|
|
addToast(notification.message, notification.type || 'info');
|
|
}
|
|
});
|
|
}
|
|
}, [wsNotifications]);
|
|
|
|
// Handle WebSocket alerts for admins
|
|
useEffect(() => {
|
|
if (user?.isAdmin && wsAlerts && wsAlerts.length > 0) {
|
|
const newAlertMessages = wsAlerts.map((alert: any) =>
|
|
`[AEGIS] ${alert.severity?.toUpperCase()}: ${alert.message}`
|
|
);
|
|
setNotifications(prev => [...new Set([...newAlertMessages, ...prev])].slice(0, 10));
|
|
}
|
|
}, [wsAlerts, user?.isAdmin]);
|
|
|
|
// Show WebSocket connection status
|
|
useEffect(() => {
|
|
if (wsConnected && !isBooting) {
|
|
addToast('Real-time connection established', 'success');
|
|
}
|
|
}, [wsConnected, isBooting]);
|
|
|
|
const playSound = useCallback((type: 'open' | 'close' | 'minimize' | 'click' | 'notification' | 'switch') => {
|
|
if (!soundEnabled) return;
|
|
try {
|
|
if (!audioContextRef.current) {
|
|
audioContextRef.current = new AudioContext();
|
|
}
|
|
const ctx = audioContextRef.current;
|
|
const oscillator = ctx.createOscillator();
|
|
const gainNode = ctx.createGain();
|
|
oscillator.connect(gainNode);
|
|
gainNode.connect(ctx.destination);
|
|
|
|
const sounds: Record<string, { freq: number; duration: number; type: OscillatorType }> = {
|
|
open: { freq: 523, duration: 0.1, type: 'sine' },
|
|
close: { freq: 392, duration: 0.1, type: 'sine' },
|
|
minimize: { freq: 330, duration: 0.08, type: 'sine' },
|
|
click: { freq: 800, duration: 0.03, type: 'square' },
|
|
notification: { freq: 880, duration: 0.15, type: 'sine' },
|
|
switch: { freq: 440, duration: 0.2, type: 'sawtooth' },
|
|
};
|
|
|
|
const sound = sounds[type] || sounds.click;
|
|
oscillator.type = sound.type;
|
|
oscillator.frequency.setValueAtTime(sound.freq, ctx.currentTime);
|
|
gainNode.gain.setValueAtTime(0.1, ctx.currentTime);
|
|
gainNode.gain.exponentialRampToValueAtTime(0.01, ctx.currentTime + sound.duration);
|
|
|
|
oscillator.start(ctx.currentTime);
|
|
oscillator.stop(ctx.currentTime + sound.duration);
|
|
} catch (e) {
|
|
console.log('Audio not available');
|
|
}
|
|
}, [soundEnabled]);
|
|
|
|
const switchClearance = useCallback(() => {
|
|
const newMode: ClearanceMode = clearanceMode === 'foundation' ? 'corp' : 'foundation';
|
|
setIsSwitchingClearance(true);
|
|
setShowStartMenu(false);
|
|
playSound('switch');
|
|
|
|
setTimeout(() => {
|
|
setClearanceMode(newMode);
|
|
setIsSwitchingClearance(false);
|
|
addToast(`Switched to ${CLEARANCE_THEMES[newMode].name}`, 'success');
|
|
}, 600);
|
|
}, [clearanceMode, addToast, playSound]);
|
|
|
|
const openApp = useCallback((app: DesktopApp) => {
|
|
const appToOpen = (isDesktopLocked && app.id !== 'passport')
|
|
? apps.find(a => a.id === 'passport') || app
|
|
: app;
|
|
|
|
playSound('open');
|
|
const existingWindow = windowContext.windows.find(w => w.id === appToOpen.id);
|
|
if (existingWindow) {
|
|
windowContext.focusWindow(appToOpen.id);
|
|
return;
|
|
}
|
|
|
|
const offsetX = (windowContext.windows.length % 5) * 40 + 100;
|
|
const offsetY = (windowContext.windows.length % 5) * 40 + 50;
|
|
|
|
const newWindow = {
|
|
id: appToOpen.id,
|
|
title: appToOpen.title,
|
|
icon: appToOpen.icon,
|
|
component: appToOpen.component,
|
|
x: offsetX,
|
|
y: offsetY,
|
|
width: appToOpen.defaultWidth,
|
|
height: appToOpen.defaultHeight,
|
|
minimized: false,
|
|
maximized: false,
|
|
desktopId: currentDesktop
|
|
};
|
|
|
|
windowContext.openWindow(newWindow);
|
|
setShowStartMenu(false);
|
|
}, [windowContext, playSound, currentDesktop, isDesktopLocked, apps]);
|
|
|
|
const closeWindow = useCallback((id: string) => {
|
|
playSound('close');
|
|
windowContext.closeWindow(id);
|
|
}, [windowContext, playSound]);
|
|
|
|
const minimizeWindow = useCallback((id: string) => {
|
|
playSound('minimize');
|
|
windowContext.minimizeWindow(id);
|
|
}, [windowContext, playSound]);
|
|
|
|
const toggleMaximize = useCallback((id: string) => {
|
|
windowContext.maximizeWindow(id);
|
|
}, [windowContext]);
|
|
|
|
const focusWindow = useCallback((id: string) => {
|
|
windowContext.focusWindow(id);
|
|
}, [windowContext]);
|
|
|
|
const handleDesktopClick = (e: React.MouseEvent) => {
|
|
setShowStartMenu(false);
|
|
setContextMenu(null);
|
|
};
|
|
|
|
const handleDesktopContextMenu = (e: React.MouseEvent) => {
|
|
e.preventDefault();
|
|
setContextMenu({ x: e.clientX, y: e.clientY, type: 'desktop' });
|
|
};
|
|
|
|
const handleIconContextMenu = (e: React.MouseEvent, appId: string) => {
|
|
e.preventDefault();
|
|
e.stopPropagation();
|
|
setContextMenu({ x: e.clientX, y: e.clientY, type: 'icon', appId });
|
|
};
|
|
|
|
const handleWindowSnap = useCallback((id: string, x: number, y: number, width: number, height: number) => {
|
|
const screenWidth = window.innerWidth;
|
|
const screenHeight = window.innerHeight - 48;
|
|
|
|
if (x <= 10 || x + width >= screenWidth - 10 || y <= 10) {
|
|
return windowContext.snapWindow(id, x, y);
|
|
}
|
|
return false;
|
|
}, [windowContext]);
|
|
|
|
const handleLogout = async () => {
|
|
await logout();
|
|
setLocation("/");
|
|
};
|
|
|
|
useEffect(() => {
|
|
const handleGlobalKeys = (e: KeyboardEvent) => {
|
|
if ((e.metaKey || e.ctrlKey) && e.key === ' ') {
|
|
e.preventDefault();
|
|
setShowSpotlight(prev => !prev);
|
|
setSpotlightQuery('');
|
|
}
|
|
if (e.key === 'Escape') {
|
|
setShowSpotlight(false);
|
|
setShowStartMenu(false);
|
|
setContextMenu(null);
|
|
}
|
|
if ((e.metaKey || e.ctrlKey) && !e.shiftKey) {
|
|
const shortcuts: Record<string, string> = { 't': 'terminal', 'n': 'notes', 'e': 'codeeditor', 'p': 'passport', 'm': 'metrics' };
|
|
if (shortcuts[e.key]) {
|
|
e.preventDefault();
|
|
const app = apps.find(a => a.id === shortcuts[e.key]);
|
|
if (app) openApp(app);
|
|
}
|
|
}
|
|
if ((e.metaKey || e.ctrlKey) && e.key >= '1' && e.key <= '4') {
|
|
e.preventDefault();
|
|
setCurrentDesktop(parseInt(e.key) - 1);
|
|
addToast(`Switched to Desktop ${e.key}`, 'info');
|
|
}
|
|
};
|
|
window.addEventListener('keydown', handleGlobalKeys);
|
|
return () => window.removeEventListener('keydown', handleGlobalKeys);
|
|
}, [apps, openApp, addToast]);
|
|
|
|
const renderAppContent = (component: string) => {
|
|
switch (component) {
|
|
case 'terminal': return <TerminalApp />;
|
|
case 'passport': return <PassportApp onLoginSuccess={unlockDesktop} isDesktopLocked={isDesktopLocked} />;
|
|
case 'files': return <FilesApp />;
|
|
case 'network': return <NetworkMapApp />;
|
|
case 'metrics': return <MetricsDashboardApp />;
|
|
case 'codeeditor': return <CodeEditorApp />;
|
|
case 'newsfeed': return <NewsFeedApp />;
|
|
case 'arcade': return <ArcadeApp />;
|
|
case 'minesweeper': return <Minesweeper />;
|
|
case 'cookieclicker': return <CookieClicker />;
|
|
case 'profiles': return <ProfilesApp />;
|
|
case 'leaderboard': return <LeaderboardApp />;
|
|
case 'calculator': return <CalculatorApp />;
|
|
case 'notes': return <NotesApp />;
|
|
case 'sysmonitor': return <SystemMonitorApp />;
|
|
case 'webcam': return <WebcamApp />;
|
|
case 'achievements': return <AchievementsApp />;
|
|
case 'projects': return <ProjectsAppWrapper />;
|
|
case 'opportunities': return <OpportunitiesApp />;
|
|
case 'events': return <EventsApp />;
|
|
case 'messaging': return <MessagingAppWrapper />;
|
|
case 'marketplace': return <MarketplaceAppWrapper />;
|
|
case 'chat': return <ChatApp />;
|
|
case 'music': return <MusicApp />;
|
|
case 'pitch': return <PitchApp onNavigate={() => setLocation('/pitch')} />;
|
|
case 'networkneighborhood': return <NetworkNeighborhoodApp openIframeWindow={openIframeWindow} />;
|
|
case 'foundry': return <FoundryApp openIframeWindow={openIframeWindow} />;
|
|
case 'devtools': return <DevToolsApp openIframeWindow={openIframeWindow} />;
|
|
case 'mission': return <MissionApp />;
|
|
case 'intel': return <IntelApp />;
|
|
case 'filemanager': return <FileManagerAppWrapper />;
|
|
case 'codegallery': return <CodeGalleryAppWrapper />;
|
|
case 'notifications': return <NotificationsAppWrapper />;
|
|
case 'analytics': return <AnalyticsAppWrapper />;
|
|
case 'drives': return <DrivesApp openIframeWindow={openIframeWindow} />;
|
|
case 'aethexstudio': return <AethexStudio />;
|
|
case 'aethexappstore': return <AethexAppStore />;
|
|
case 'iframe': return null;
|
|
case 'settings': return <SettingsApp
|
|
wallpaper={wallpaper}
|
|
setWallpaper={setWallpaper}
|
|
soundEnabled={soundEnabled}
|
|
setSoundEnabled={setSoundEnabled}
|
|
secretsUnlocked={secretsUnlocked}
|
|
theme={theme}
|
|
setTheme={setTheme}
|
|
savedLayouts={savedLayouts}
|
|
onSaveLayout={(name) => {
|
|
const layout: DesktopLayout = {
|
|
name,
|
|
windows: windows.map(w => ({ appId: w.component, x: w.x, y: w.y, width: w.width, height: w.height })),
|
|
desktop: currentDesktop,
|
|
};
|
|
setSavedLayouts(prev => [...prev.filter(l => l.name !== name), layout]);
|
|
addToast(`Layout "${name}" saved`, 'success');
|
|
}}
|
|
onLoadLayout={(layout) => {
|
|
setWindows([]);
|
|
setTimeout(() => {
|
|
layout.windows.forEach((w, i) => {
|
|
const app = apps.find(a => a.component === w.appId);
|
|
if (app) {
|
|
const windowId = `${app.id}-${Date.now()}-${i}`;
|
|
setWindows(prev => [...prev, {
|
|
id: windowId,
|
|
title: app.title,
|
|
icon: app.icon,
|
|
component: app.component,
|
|
x: w.x,
|
|
y: w.y,
|
|
width: w.width,
|
|
height: w.height,
|
|
minimized: false,
|
|
maximized: false,
|
|
zIndex: i + 1,
|
|
desktopId: layout.desktop
|
|
}]);
|
|
}
|
|
});
|
|
setCurrentDesktop(layout.desktop);
|
|
addToast(`Layout "${layout.name}" loaded`, 'success');
|
|
}, 100);
|
|
}}
|
|
onDeleteLayout={(name) => {
|
|
setSavedLayouts(prev => prev.filter(l => l.name !== name));
|
|
addToast(`Layout "${name}" deleted`, 'info');
|
|
}}
|
|
/>;
|
|
default: return null;
|
|
}
|
|
};
|
|
|
|
const handleGuestContinue = () => {
|
|
setShowLoginPrompt(false);
|
|
setIsBooting(false);
|
|
setIsDesktopLocked(false);
|
|
const randomTip = DAILY_TIPS[Math.floor(Math.random() * DAILY_TIPS.length)];
|
|
setDailyTip(randomTip);
|
|
setTimeout(() => setShowDailyTip(true), 1000);
|
|
};
|
|
|
|
const handleLoginFromBoot = () => {
|
|
setShowLoginPrompt(false);
|
|
setIsBooting(false);
|
|
// Keep desktop locked until login succeeds
|
|
const randomTip = DAILY_TIPS[Math.floor(Math.random() * DAILY_TIPS.length)];
|
|
setDailyTip(randomTip);
|
|
setTimeout(() => {
|
|
setShowDailyTip(true);
|
|
const passportApp = apps.find(a => a.id === 'passport');
|
|
if (passportApp) openApp(passportApp);
|
|
}, 500);
|
|
};
|
|
|
|
const unlockDesktop = () => {
|
|
setIsDesktopLocked(false);
|
|
};
|
|
|
|
const openIframeWindow = (url: string, title: string) => {
|
|
// Most external sites block iframe embedding - open in new tab instead
|
|
window.open(url, '_blank', 'noopener,noreferrer');
|
|
};
|
|
|
|
if (isBooting) {
|
|
return (
|
|
<BootSequence
|
|
onBootComplete={() => setIsBooting(false)}
|
|
onLoginClick={() => setLocation('/login')}
|
|
onGuestContinue={() => setIsBooting(false)}
|
|
/>
|
|
);
|
|
}
|
|
|
|
if (showScreensaver) {
|
|
return (
|
|
<div
|
|
className="h-screen w-screen bg-black flex items-center justify-center cursor-pointer"
|
|
onClick={() => setShowScreensaver(false)}
|
|
>
|
|
<motion.div
|
|
animate={{
|
|
x: [0, 100, -100, 50, -50, 0],
|
|
y: [0, -50, 50, -25, 25, 0],
|
|
}}
|
|
transition={{ duration: 20, repeat: Infinity, ease: "linear" }}
|
|
className="text-center"
|
|
>
|
|
<div className="text-6xl font-display font-bold bg-gradient-to-r from-cyan-400 via-purple-500 to-pink-500 text-transparent bg-clip-text">
|
|
AeThex
|
|
</div>
|
|
<div className="text-white/30 text-sm mt-2 font-mono">Click to wake</div>
|
|
</motion.div>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
const parallaxX = (mousePosition.x / window.innerWidth - 0.5) * 10;
|
|
const parallaxY = (mousePosition.y / window.innerHeight - 0.5) * 10;
|
|
|
|
// Native Android App Layout
|
|
if (layout.isMobile) {
|
|
const activeWindows = windows.filter(w => !w.minimized);
|
|
const currentWindow = activeWindows[activeWindows.length - 1];
|
|
|
|
// Hide system navigation bar on mount (Android only)
|
|
useEffect(() => {
|
|
const setupStatusBar = async () => {
|
|
try {
|
|
// Hide the status bar for full immersion
|
|
await StatusBar.hide();
|
|
|
|
// Set navigation bar to transparent and hide it
|
|
if ((window as any).NavigationBar) {
|
|
await (window as any).NavigationBar.backgroundColorByHexString('#00000000', false);
|
|
await (window as any).NavigationBar.hide();
|
|
}
|
|
|
|
// Enable edge-to-edge mode
|
|
document.documentElement.style.setProperty('--safe-area-inset-bottom', 'env(safe-area-inset-bottom)');
|
|
} catch (error) {
|
|
console.log('StatusBar not available on this platform');
|
|
}
|
|
};
|
|
|
|
setupStatusBar();
|
|
|
|
return () => {
|
|
// Show status bar when leaving
|
|
StatusBar.show().catch(() => {});
|
|
};
|
|
}, []);
|
|
|
|
return (
|
|
<div className="h-screen w-screen bg-black overflow-hidden flex flex-col">
|
|
<style>{`
|
|
@keyframes scan {
|
|
0% { transform: translateY(-100%); }
|
|
100% { transform: translateY(100%); }
|
|
}
|
|
@keyframes pulse-border {
|
|
0%, 100% { opacity: 0.3; }
|
|
50% { opacity: 0.8; }
|
|
}
|
|
`}</style>
|
|
|
|
{/* Ingress Status Bar - Minimal */}
|
|
<div className="relative h-8 bg-black/90 border-b border-emerald-500/50 shrink-0">
|
|
<div className="absolute inset-0 bg-gradient-to-r from-transparent via-emerald-500/5 to-transparent"></div>
|
|
<div className="relative flex items-center justify-between px-4 h-full">
|
|
<div className="flex items-center gap-3">
|
|
<Activity className="w-3.5 h-3.5 text-emerald-400" />
|
|
<Wifi className="w-3.5 h-3.5 text-cyan-400" />
|
|
<div className="flex items-center gap-0.5">
|
|
{[...Array(4)].map((_, i) => (
|
|
<div key={i} className="w-0.5 h-1.5 bg-emerald-400 rounded-full" style={{ height: `${(i + 1) * 2}px` }} />
|
|
))}
|
|
</div>
|
|
</div>
|
|
<div className="flex items-center gap-4 text-cyan-400 text-xs font-mono font-bold tracking-wider">
|
|
<span>{batteryInfo?.level || 100}%</span>
|
|
<Battery className="w-4 h-4 text-green-400" />
|
|
<span className="font-mono text-cyan-400">{time.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })}</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Main Content */}
|
|
<div className="flex-1 overflow-hidden relative bg-black">
|
|
<AnimatePresence mode="wait">
|
|
{currentWindow ? (
|
|
// Fullscreen App View with 3D Card Flip
|
|
<motion.div
|
|
key={currentWindow.id}
|
|
initial={{ rotateY: 90, opacity: 0 }}
|
|
animate={{ rotateY: 0, opacity: 1 }}
|
|
exit={{ rotateY: -90, opacity: 0 }}
|
|
transition={{ duration: 0.4, type: "spring" }}
|
|
style={{ transformStyle: "preserve-3d" }}
|
|
className="h-full w-full flex flex-col relative"
|
|
>
|
|
{/* Ingress Style - Minimal App Bar */}
|
|
<div className="relative h-12 bg-black/95 border-b-2 border-emerald-500/50 shrink-0">
|
|
<div className="absolute inset-0" style={{ animation: 'pulse-border 2s ease-in-out infinite' }}>
|
|
<div className="absolute inset-x-0 bottom-0 h-[2px] bg-gradient-to-r from-transparent via-cyan-500 to-transparent"></div>
|
|
</div>
|
|
<div className="relative flex items-center px-3 h-full">
|
|
<button
|
|
onClick={() => {
|
|
impact('light');
|
|
closeWindow(currentWindow.id);
|
|
}}
|
|
className="w-10 h-10 flex items-center justify-center border border-emerald-500/50 active:bg-emerald-500/20"
|
|
style={{ clipPath: 'polygon(30% 0%, 70% 0%, 100% 30%, 100% 70%, 70% 100%, 30% 100%, 0% 70%, 0% 30%)' }}
|
|
>
|
|
<ChevronLeft className="w-5 h-5 text-emerald-400" />
|
|
</button>
|
|
<div className="flex-1 px-4">
|
|
<h1 className="text-cyan-400 font-mono font-bold text-lg uppercase tracking-widest">
|
|
{currentWindow.title}
|
|
</h1>
|
|
</div>
|
|
<button
|
|
className="w-10 h-10 flex items-center justify-center border border-cyan-500/50 active:bg-cyan-500/20"
|
|
style={{ clipPath: 'polygon(30% 0%, 70% 0%, 100% 30%, 100% 70%, 70% 100%, 30% 100%, 0% 70%, 0% 30%)' }}
|
|
>
|
|
<MoreVertical className="w-5 h-5 text-cyan-400" />
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
{/* App Content */}
|
|
<div className="flex-1 overflow-auto relative bg-black">
|
|
{renderAppContent(currentWindow.component)}
|
|
</div>
|
|
</motion.div>
|
|
) : (
|
|
// ULTRA FUTURISTIC LAUNCHER
|
|
<motion.div
|
|
key="launcher"
|
|
initial={{ opacity: 0, scale: 0.9 }}
|
|
animate={{ opacity: 1, scale: 1 }}
|
|
exit={{ opacity: 0, scale: 0.9 }}
|
|
className="h-full flex flex-col relative"
|
|
>
|
|
{/* Ingress Style Search Bar */}
|
|
<div className="px-4 pt-6 pb-4">
|
|
<div className="relative bg-black/80 border border-emerald-500/50 p-3">
|
|
<div className="absolute inset-0 border border-cyan-500/30" style={{ clipPath: 'polygon(0 0, calc(100% - 12px) 0, 100% 12px, 100% 100%, 12px 100%, 0 calc(100% - 12px))' }}></div>
|
|
<div className="relative flex items-center gap-3">
|
|
<Search className="w-5 h-5 text-emerald-400" />
|
|
<input
|
|
type="text"
|
|
placeholder="SCANNER SEARCH..."
|
|
className="flex-1 bg-transparent text-emerald-400 placeholder:text-emerald-400/40 outline-none text-sm font-mono uppercase tracking-wide"
|
|
onFocus={() => impact('light')}
|
|
/>
|
|
<Mic className="w-5 h-5 text-cyan-400" />
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{/* App Grid - Hexagonal */}
|
|
<div className="flex-1 overflow-auto px-4 pb-24">
|
|
<div className="mb-6">
|
|
<div className="flex items-center gap-2 mb-3 px-2">
|
|
<div className="w-2 h-2 bg-emerald-400"></div>
|
|
<h2 className="text-emerald-400 text-xs uppercase tracking-widest font-mono font-bold">
|
|
Quick Access
|
|
</h2>
|
|
<div className="flex-1 h-[1px] bg-emerald-500/30"></div>
|
|
</div>
|
|
<div className="grid grid-cols-4 gap-3">
|
|
{apps.slice(0, 8).map((app) => (
|
|
<button
|
|
key={app.id}
|
|
onClick={() => {
|
|
impact('medium');
|
|
openApp(app);
|
|
}}
|
|
className="flex flex-col items-center gap-2 p-2 active:bg-emerald-500/10"
|
|
>
|
|
<div
|
|
className="relative w-16 h-16 bg-black border-2 border-emerald-500/50 flex items-center justify-center active:border-cyan-400"
|
|
style={{ clipPath: 'polygon(30% 0%, 70% 0%, 100% 30%, 100% 70%, 70% 100%, 30% 100%, 0% 70%, 0% 30%)' }}
|
|
>
|
|
<div className="text-emerald-400 scale-75">{app.icon}</div>
|
|
<div className="absolute inset-0 border border-cyan-500/20" style={{ clipPath: 'polygon(30% 0%, 70% 0%, 100% 30%, 100% 70%, 70% 100%, 30% 100%, 0% 70%, 0% 30%)' }}></div>
|
|
</div>
|
|
<span className="text-cyan-400 text-[9px] font-mono text-center line-clamp-2 leading-tight uppercase">
|
|
{app.title}
|
|
</span>
|
|
</button>
|
|
))}
|
|
</div>
|
|
</div>
|
|
|
|
{/* All Apps - Minimal List */}
|
|
<div>
|
|
<div className="flex items-center gap-2 mb-3 px-2">
|
|
<div className="w-2 h-2 bg-cyan-400"></div>
|
|
<h2 className="text-cyan-400 text-xs uppercase tracking-widest font-mono font-bold">
|
|
All Systems
|
|
</h2>
|
|
<div className="flex-1 h-[1px] bg-cyan-500/30"></div>
|
|
</div>
|
|
<div className="space-y-2">
|
|
{apps.slice(8).map((app) => (
|
|
<button
|
|
key={app.id}
|
|
onClick={() => {
|
|
impact('medium');
|
|
openApp(app);
|
|
}}
|
|
className="relative w-full flex items-center gap-3 p-3 border border-emerald-500/30 active:bg-emerald-500/10 active:border-cyan-500"
|
|
>
|
|
<div className="w-10 h-10 bg-black border border-emerald-500/50 flex items-center justify-center shrink-0">
|
|
<div className="text-emerald-400 scale-75">{app.icon}</div>
|
|
</div>
|
|
<span className="text-cyan-400 font-mono text-sm text-left flex-1 uppercase tracking-wide">{app.title}</span>
|
|
<ChevronRight className="w-4 h-4 text-emerald-400" />
|
|
</button>
|
|
))}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</motion.div>
|
|
)}
|
|
</AnimatePresence>
|
|
</div>
|
|
|
|
{/* INGRESS STYLE NAVIGATION BAR - Lightweight */}
|
|
<div
|
|
className="relative bg-black/95 border-t-2 border-emerald-500/50 shrink-0 z-50"
|
|
style={{
|
|
paddingTop: '0.75rem',
|
|
paddingBottom: 'calc(0.75rem + env(safe-area-inset-bottom))',
|
|
}}
|
|
>
|
|
<div className="absolute inset-x-0 top-0 h-[2px] bg-gradient-to-r from-transparent via-cyan-500 to-transparent" style={{ animation: 'pulse-border 2s ease-in-out infinite' }}></div>
|
|
<div className="relative flex items-center justify-around px-6">
|
|
<button
|
|
onClick={() => {
|
|
impact('medium');
|
|
windows.forEach(w => closeWindow(w.id));
|
|
}}
|
|
className="relative w-14 h-14 bg-black border-2 border-emerald-500/70 flex items-center justify-center active:bg-emerald-500/20 active:border-cyan-400"
|
|
style={{ clipPath: 'polygon(30% 0%, 70% 0%, 100% 30%, 100% 70%, 70% 100%, 30% 100%, 0% 70%, 0% 30%)' }}
|
|
>
|
|
<Home className="w-6 h-6 text-emerald-400" />
|
|
</button>
|
|
|
|
<button
|
|
onClick={() => {
|
|
impact('medium');
|
|
const minimized = windows.filter(w => w.minimized);
|
|
if (minimized.length > 0) {
|
|
setWindows(prev => prev.map(w =>
|
|
w.id === minimized[0].id ? { ...w, minimized: false } : w
|
|
));
|
|
}
|
|
}}
|
|
className="relative w-14 h-14 bg-black border-2 border-cyan-500/70 flex items-center justify-center active:bg-cyan-500/20 active:border-emerald-400"
|
|
style={{ clipPath: 'polygon(30% 0%, 70% 0%, 100% 30%, 100% 70%, 70% 100%, 30% 100%, 0% 70%, 0% 30%)' }}
|
|
>
|
|
<Square className="w-6 h-6 text-cyan-400" />
|
|
{windows.filter(w => w.minimized).length > 0 && (
|
|
<span className="absolute -top-1 -right-1 w-5 h-5 bg-emerald-500 text-black text-xs flex items-center justify-center font-bold">
|
|
{windows.filter(w => w.minimized).length}
|
|
</span>
|
|
)}
|
|
</button>
|
|
|
|
<button
|
|
onClick={() => {
|
|
impact('medium');
|
|
if (currentWindow) {
|
|
closeWindow(currentWindow.id);
|
|
}
|
|
}}
|
|
className="relative w-14 h-14 bg-black border-2 border-emerald-500/70 flex items-center justify-center active:bg-emerald-500/20 active:border-cyan-400"
|
|
style={{ clipPath: 'polygon(30% 0%, 70% 0%, 100% 30%, 100% 70%, 70% 100%, 30% 100%, 0% 70%, 0% 30%)' }}
|
|
>
|
|
<ArrowLeft className="w-6 h-6 text-emerald-400" />
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Floating Action Button with Orbital Menu */}
|
|
<MobileQuickActions />
|
|
</div>
|
|
);
|
|
}
|
|
|
|
console.log('🖥️ [OS] Rendering DESKTOP layout (isMobile=false)');
|
|
|
|
// Desktop/Web layout
|
|
return (
|
|
<div
|
|
className="h-screen w-screen overflow-hidden select-none relative transition-all duration-700"
|
|
style={{
|
|
background: clearanceTheme.wallpaper,
|
|
backgroundPosition: `${50 + parallaxX}% ${50 + parallaxY}%`,
|
|
backgroundSize: '110% 110%'
|
|
}}
|
|
>
|
|
<div
|
|
ref={desktopRef}
|
|
className="h-[calc(100vh-48px)] w-full relative"
|
|
onClick={handleDesktopClick}
|
|
onContextMenu={handleDesktopContextMenu}
|
|
>
|
|
<div className="absolute inset-0 bg-[linear-gradient(rgba(0,255,170,0.02)_1px,transparent_1px),linear-gradient(90deg,rgba(0,255,170,0.02)_1px,transparent_1px)] bg-[size:50px_50px] pointer-events-none" />
|
|
|
|
<DesktopWidgets time={time} weather={weatherData} notifications={notifications} />
|
|
|
|
<div className="absolute top-4 left-4 grid grid-cols-2 gap-2 w-48">
|
|
{apps.slice(0, 9).map((app) => (
|
|
<DesktopIcon
|
|
key={app.id}
|
|
icon={app.icon}
|
|
label={app.title}
|
|
onClick={() => openApp(app)}
|
|
onContextMenu={(e) => handleIconContextMenu(e, app.id)}
|
|
/>
|
|
))}
|
|
</div>
|
|
|
|
{isDesktopLocked && windows.length === 0 && (
|
|
<motion.div
|
|
initial={{ opacity: 0 }}
|
|
animate={{ opacity: 1 }}
|
|
className="absolute inset-0 bg-black/60 backdrop-blur-sm flex items-center justify-center z-50"
|
|
>
|
|
<div className="text-center">
|
|
<div className="w-20 h-20 mx-auto rounded-full bg-gradient-to-br from-cyan-500 to-purple-600 flex items-center justify-center mb-4">
|
|
<Lock className="w-10 h-10 text-white" />
|
|
</div>
|
|
<h2 className="text-xl font-display text-white uppercase tracking-wider mb-2">Desktop Locked</h2>
|
|
<p className="text-white/60 text-sm font-mono mb-6">Sign in with your Passport to continue</p>
|
|
<button
|
|
onClick={() => {
|
|
const passportApp = apps.find(a => a.id === 'passport');
|
|
if (passportApp) openApp(passportApp);
|
|
}}
|
|
className="px-6 py-3 bg-cyan-500 hover:bg-cyan-400 text-black font-mono font-bold uppercase tracking-wider transition-colors"
|
|
data-testid="unlock-desktop-btn"
|
|
>
|
|
Open Passport
|
|
</button>
|
|
</div>
|
|
</motion.div>
|
|
)}
|
|
|
|
<AnimatePresence>
|
|
{windows.filter(w => !w.minimized && w.desktopId === currentDesktop).map((window) => (
|
|
<Window
|
|
key={window.id}
|
|
window={window}
|
|
isActive={activeWindowId === window.id}
|
|
onClose={() => closeWindow(window.id)}
|
|
onMinimize={() => minimizeWindow(window.id)}
|
|
onMaximize={() => toggleMaximize(window.id)}
|
|
onFocus={() => focusWindow(window.id)}
|
|
onMove={(x, y) => setWindows(prev => prev.map(w => w.id === window.id ? { ...w, x, y } : w))}
|
|
onResize={(width, height) => setWindows(prev => prev.map(w => w.id === window.id ? { ...w, width, height } : w))}
|
|
onSnap={(x, y) => handleWindowSnap(window.id, x, y, window.width, window.height)}
|
|
content={window.component === 'iframe' && window.iframeUrl ? (
|
|
<iframe
|
|
src={window.iframeUrl}
|
|
className="w-full h-full border-0"
|
|
title={window.title}
|
|
sandbox="allow-scripts allow-same-origin allow-forms allow-popups"
|
|
/>
|
|
) : renderAppContent(window.component)}
|
|
/>
|
|
))}
|
|
</AnimatePresence>
|
|
|
|
<AnimatePresence>
|
|
{contextMenu && (
|
|
<ContextMenuComponent
|
|
menu={contextMenu}
|
|
apps={apps}
|
|
onClose={() => setContextMenu(null)}
|
|
onOpenApp={openApp}
|
|
onRefresh={() => window.location.reload()}
|
|
onChangeWallpaper={() => {
|
|
const idx = WALLPAPERS.findIndex(w => w.id === wallpaper.id);
|
|
setWallpaper(WALLPAPERS[(idx + 1) % WALLPAPERS.length]);
|
|
setContextMenu(null);
|
|
}}
|
|
/>
|
|
)}
|
|
</AnimatePresence>
|
|
</div>
|
|
|
|
<Taskbar
|
|
windows={windows.filter(w => w.desktopId === currentDesktop)}
|
|
activeWindowId={activeWindowId}
|
|
apps={apps}
|
|
time={time}
|
|
showStartMenu={showStartMenu}
|
|
user={user}
|
|
isAuthenticated={isAuthenticated}
|
|
notifications={notifications}
|
|
showNotifications={showNotifications}
|
|
onToggleStartMenu={() => { setShowStartMenu(!showStartMenu); setActiveTrayPanel(null); }}
|
|
onToggleNotifications={() => setShowNotifications(!showNotifications)}
|
|
onWindowClick={(id) => {
|
|
const window = windows.find(w => w.id === id);
|
|
if (window?.minimized) {
|
|
setWindows(prev => prev.map(w => w.id === id ? { ...w, minimized: false, zIndex: maxZIndex + 1 } : w));
|
|
setMaxZIndex(prev => prev + 1);
|
|
}
|
|
focusWindow(id);
|
|
}}
|
|
onAppClick={openApp}
|
|
onLogout={handleLogout}
|
|
onNavigate={setLocation}
|
|
currentDesktop={currentDesktop}
|
|
onDesktopChange={(d) => {
|
|
setCurrentDesktop(d);
|
|
setActiveTrayPanel(null);
|
|
}}
|
|
clearanceTheme={clearanceTheme}
|
|
onSwitchClearance={switchClearance}
|
|
activeTrayPanel={activeTrayPanel}
|
|
onTrayPanelToggle={(panel) => setActiveTrayPanel(activeTrayPanel === panel ? null : panel)}
|
|
volume={volume}
|
|
onVolumeChange={setVolume}
|
|
isMuted={isMuted}
|
|
onMuteToggle={() => setIsMuted(!isMuted)}
|
|
batteryInfo={batteryInfo}
|
|
onClearNotification={(idx) => setNotifications(prev => prev.filter((_, i) => i !== idx))}
|
|
onClearAllNotifications={() => setNotifications([])}
|
|
desktopWindowCounts={[0, 1, 2, 3].map(d => windows.filter(w => w.desktopId === d).length)}
|
|
openIframeWindow={openIframeWindow}
|
|
/>
|
|
|
|
<AnimatePresence>
|
|
{isSwitchingClearance && (
|
|
<motion.div
|
|
initial={{ opacity: 0 }}
|
|
animate={{ opacity: 1 }}
|
|
exit={{ opacity: 0 }}
|
|
transition={{ duration: 0.3 }}
|
|
className="fixed inset-0 z-[99999] flex items-center justify-center"
|
|
style={{ background: clearanceMode === 'foundation' ? '#0F172A' : '#1a0505' }}
|
|
>
|
|
<motion.div
|
|
initial={{ scale: 0.8, opacity: 0 }}
|
|
animate={{ scale: 1, opacity: 1 }}
|
|
exit={{ scale: 1.2, opacity: 0 }}
|
|
transition={{ duration: 0.5 }}
|
|
className="text-center"
|
|
>
|
|
<motion.div
|
|
animate={{ rotate: 360 }}
|
|
transition={{ duration: 1, repeat: Infinity, ease: "linear" }}
|
|
className="w-20 h-20 mx-auto mb-6 rounded-full flex items-center justify-center"
|
|
style={{
|
|
border: `3px solid ${clearanceMode === 'foundation' ? '#3B82F6' : '#D4AF37'}`,
|
|
borderTopColor: 'transparent'
|
|
}}
|
|
/>
|
|
<motion.div
|
|
initial={{ opacity: 0, y: 10 }}
|
|
animate={{ opacity: 1, y: 0 }}
|
|
transition={{ delay: 0.2 }}
|
|
className="font-display text-2xl uppercase tracking-[0.3em]"
|
|
style={{ color: clearanceMode === 'foundation' ? '#C0C0C0' : '#D4AF37' }}
|
|
>
|
|
{clearanceMode === 'foundation' ? 'Entering Corp' : 'Entering Foundation'}
|
|
</motion.div>
|
|
<motion.div
|
|
initial={{ width: 0 }}
|
|
animate={{ width: '100%' }}
|
|
transition={{ duration: 0.8, delay: 0.3 }}
|
|
className="h-0.5 mt-4 mx-auto max-w-[200px]"
|
|
style={{ background: `linear-gradient(90deg, transparent, ${clearanceMode === 'foundation' ? '#3B82F6' : '#DC2626'}, transparent)` }}
|
|
/>
|
|
</motion.div>
|
|
</motion.div>
|
|
)}
|
|
</AnimatePresence>
|
|
|
|
<ParticleField />
|
|
|
|
<AnimatePresence>
|
|
{showSpotlight && (
|
|
<SpotlightSearch
|
|
query={spotlightQuery}
|
|
setQuery={setSpotlightQuery}
|
|
apps={apps}
|
|
onSelectApp={(app) => { openApp(app); setShowSpotlight(false); }}
|
|
onClose={() => setShowSpotlight(false)}
|
|
inputRef={spotlightRef}
|
|
/>
|
|
)}
|
|
</AnimatePresence>
|
|
|
|
<ToastContainer toasts={toasts} />
|
|
|
|
<AnimatePresence>
|
|
{showDailyTip && (
|
|
<motion.div
|
|
initial={{ opacity: 0, y: 50 }}
|
|
animate={{ opacity: 1, y: 0 }}
|
|
exit={{ opacity: 0, y: 50 }}
|
|
className="fixed bottom-20 right-4 z-[9998] w-80 rounded-xl overflow-hidden shadow-2xl"
|
|
style={{
|
|
background: clearanceTheme.id === 'foundation' ? 'rgba(26, 5, 5, 0.95)' : 'rgba(15, 23, 42, 0.95)',
|
|
border: `1px solid ${clearanceTheme.accent}40`
|
|
}}
|
|
>
|
|
<div
|
|
className="px-4 py-3 flex items-center justify-between"
|
|
style={{ borderBottom: `1px solid ${clearanceTheme.accent}30` }}
|
|
>
|
|
<div className="flex items-center gap-2">
|
|
<Zap className="w-4 h-4" style={{ color: clearanceTheme.accent }} />
|
|
<span className="text-sm font-semibold text-white">Daily Tip</span>
|
|
</div>
|
|
<button
|
|
onClick={() => setShowDailyTip(false)}
|
|
className="text-white/40 hover:text-white transition-colors"
|
|
data-testid="close-daily-tip"
|
|
>
|
|
<X className="w-4 h-4" />
|
|
</button>
|
|
</div>
|
|
<div className="p-4">
|
|
<div className="text-xs uppercase tracking-wider mb-2" style={{ color: clearanceTheme.accent }}>
|
|
{dailyTip.title}
|
|
</div>
|
|
<p className="text-sm text-white/80 leading-relaxed">
|
|
{dailyTip.tip}
|
|
</p>
|
|
</div>
|
|
<div className="px-4 pb-4">
|
|
<button
|
|
onClick={() => setShowDailyTip(false)}
|
|
className="w-full py-2 rounded-lg text-sm font-medium transition-all hover:scale-[1.02]"
|
|
style={{
|
|
background: `${clearanceTheme.accent}20`,
|
|
color: clearanceTheme.accent,
|
|
border: `1px solid ${clearanceTheme.accent}40`
|
|
}}
|
|
data-testid="dismiss-daily-tip"
|
|
>
|
|
Got it!
|
|
</button>
|
|
</div>
|
|
</motion.div>
|
|
)}
|
|
</AnimatePresence>
|
|
|
|
{weatherFetching && (
|
|
<div className="fixed top-4 right-4 z-[9999] flex items-center gap-2 px-3 py-2 rounded-lg bg-black/50 backdrop-blur-sm" data-testid="loading-indicator">
|
|
<Loader2 className="w-4 h-4 animate-spin" style={{ color: clearanceTheme.accent }} />
|
|
<span className="text-xs text-white/60">Syncing...</span>
|
|
</div>
|
|
)}
|
|
|
|
<AnimatePresence>
|
|
{showOnboarding && (
|
|
<OnboardingTour
|
|
step={onboardingStep}
|
|
onNext={() => setOnboardingStep(s => s + 1)}
|
|
onClose={() => setShowOnboarding(false)}
|
|
/>
|
|
)}
|
|
</AnimatePresence>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
export default function AeThexOS() {
|
|
return (
|
|
<OSProviders>
|
|
<AeThexOSInner />
|
|
</OSProviders>
|
|
);
|
|
}
|
|
|