From c87d2227d1b4c72c9ed44e058e92a5ecb114379b Mon Sep 17 00:00:00 2001 From: sirpiglr <49359077-sirpiglr@users.noreply.replit.com> Date: Wed, 17 Dec 2025 03:52:14 +0000 Subject: [PATCH] Add a daily tip popup and improve system loading indicators Introduce a daily tips pop-up on startup, refine loading states for API calls, and fix a sound playback initialization bug. Replit-Commit-Author: Agent Replit-Commit-Session-Id: 279f1558-c0e3-40e4-8217-be7e9f4c6eca Replit-Commit-Checkpoint-Type: intermediate_checkpoint Replit-Commit-Event-Id: 041cfe5f-b8c3-4940-b6e3-280aa91a2a9f Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/b984cb14-1d19-4944-922b-bc79e821ed35/279f1558-c0e3-40e4-8217-be7e9f4c6eca/nBprVcr Replit-Helium-Checkpoint-Created: true --- client/src/pages/os.tsx | 198 ++++++++++++++++++++++++++++++++++++---- 1 file changed, 179 insertions(+), 19 deletions(-) diff --git a/client/src/pages/os.tsx b/client/src/pages/os.tsx index fdc64eb..fcdd2ce 100644 --- a/client/src/pages/os.tsx +++ b/client/src/pages/os.tsx @@ -74,6 +74,21 @@ interface ClearanceTheme { 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', 'files', 'calculator', 'settings']; + const CLEARANCE_THEMES: Record = { foundation: { id: 'foundation', @@ -168,6 +183,10 @@ export default function AeThexOS() { return (saved as ClearanceMode) || 'foundation'; }); const [isSwitchingClearance, setIsSwitchingClearance] = useState(false); + const [showDailyTip, setShowDailyTip] = useState(false); + const [dailyTip, setDailyTip] = useState(DAILY_TIPS[0]); + const [mousePosition, setMousePosition] = useState({ x: 0, y: 0 }); + const audioContextRef = useRef(null); const clearanceTheme = CLEARANCE_THEMES[clearanceMode]; const desktopRef = useRef(null); const idleTimer = useRef(null); @@ -175,7 +194,7 @@ export default function AeThexOS() { const { user, isAuthenticated, logout } = useAuth(); const [, setLocation] = useLocation(); - const { data: weatherData } = useQuery({ + 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'); @@ -203,17 +222,13 @@ export default function AeThexOS() { localStorage.setItem('aethex-clearance', clearanceMode); }, [clearanceMode]); - const switchClearance = useCallback(() => { - const newMode: ClearanceMode = clearanceMode === 'foundation' ? 'corp' : 'foundation'; - setIsSwitchingClearance(true); - setShowStartMenu(false); - - setTimeout(() => { - setClearanceMode(newMode); - setIsSwitchingClearance(false); - addToast(`Switched to ${CLEARANCE_THEMES[newMode].name}`, 'success'); - }, 600); - }, [clearanceMode, addToast]); + useEffect(() => { + const handleMouseMove = (e: MouseEvent) => { + setMousePosition({ x: e.clientX, y: e.clientY }); + }; + window.addEventListener('mousemove', handleMouseMove); + return () => window.removeEventListener('mousemove', handleMouseMove); + }, []); useEffect(() => { const bootSequence = async () => { @@ -235,6 +250,10 @@ export default function AeThexOS() { await new Promise(r => setTimeout(r, 500)); setIsBooting(false); + + const randomTip = DAILY_TIPS[Math.floor(Math.random() * DAILY_TIPS.length)]; + setDailyTip(randomTip); + setTimeout(() => setShowDailyTip(true), 1000); }; bootSequence(); @@ -347,6 +366,7 @@ export default function AeThexOS() { { id: "arcade", title: "Arcade", icon: , component: "arcade", defaultWidth: 420, defaultHeight: 520 }, { id: "profiles", title: "Architects", icon: , component: "profiles", defaultWidth: 650, defaultHeight: 550 }, { id: "chat", title: "Comms", icon: , component: "chat", defaultWidth: 400, defaultHeight: 500 }, + { id: "calculator", title: "Calculator", icon: , component: "calculator", defaultWidth: 320, defaultHeight: 450 }, { id: "settings", title: "Settings", icon: , component: "settings", defaultWidth: 550, defaultHeight: 500 }, ]; @@ -365,15 +385,53 @@ export default function AeThexOS() { const apps = clearanceMode === 'foundation' ? foundationApps : corpApps; - const playSound = useCallback((type: 'open' | 'close' | 'minimize' | 'click') => { + const playSound = useCallback((type: 'open' | 'close' | 'minimize' | 'click' | 'notification' | 'switch') => { if (!soundEnabled) return; - // Visual feedback instead of actual sound - const flash = document.createElement('div'); - flash.className = 'fixed inset-0 bg-cyan-400/5 pointer-events-none z-[99999]'; - document.body.appendChild(flash); - setTimeout(() => flash.remove(), 50); + 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 = { + 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) => { playSound('open'); const existingWindow = windows.find(w => w.id === app.id); @@ -633,10 +691,17 @@ export default function AeThexOS() { ); } + const parallaxX = (mousePosition.x / window.innerWidth - 0.5) * 10; + const parallaxY = (mousePosition.y / window.innerHeight - 0.5) * 10; + return (
+ + {showDailyTip && ( + +
+
+ + Daily Tip +
+ +
+
+
+ {dailyTip.title} +
+

+ {dailyTip.tip} +

+
+
+ +
+
+ )} +
+ + {weatherFetching && ( +
+ + Syncing... +
+ )} + {showOnboarding && ( +
+ {PINNED_APPS.map(appId => { + const app = apps.find(a => a.id === appId); + if (!app) return null; + const isOpen = windows.some(w => w.id === appId); + return ( + onAppClick(app)} + whileHover={{ scale: 1.1, y: -2 }} + whileTap={{ scale: 0.95 }} + className="w-9 h-9 rounded-lg flex items-center justify-center relative transition-colors" + style={{ + background: isOpen ? `${clearanceTheme.accent}20` : 'rgba(255,255,255,0.05)', + border: isOpen ? `1px solid ${clearanceTheme.accent}40` : '1px solid transparent' + }} + data-testid={`dock-${appId}`} + > +
+ {app.icon} +
+ {isOpen && ( +
+ )} + + ); + })} +
+ +
+
{windows.map(window => (