From 56dd6d8bf2367e1bda038269d038cc86cd74fff9 Mon Sep 17 00:00:00 2001 From: ZyphrZero <133507172+ZyphrZero@users.noreply.github.com> Date: Fri, 5 Dec 2025 02:55:50 +0800 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8=20feat:=20initialize=20AeroStart=20br?= =?UTF-8?q?owser=20start=20page=20project?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Implement a modern, customizable browser start page with comprehensive features: - Multi-theme support with 8 preset color schemes - Custom wallpaper system supporting images and videos with multiple fit modes - Integrated search functionality with 5 major search engines (Google, Baidu, Bing, DuckDuckGo, Bilibili) - Real-time clock component with 12/24 hour format options - Dynamic background blur effect during search for enhanced focus - Complete i18n system with English and Chinese language support - Responsive design with smooth animations and transitions - Local storage integration for persistent user preferences - Context menu system for quick settings access - Toast notification system for user feedback - Error boundary for robust error handling Tech Stack: - React 19 with TypeScript - Vite 6 for build tooling - Tailwind CSS for styling - Local storage for data persistence Project Structure: - Core components: Clock, SearchBox, SettingsModal, ThemeSettings, WallpaperManager - Utility modules: storage management, search suggestions - Context providers: Toast notifications, i18n - Type definitions and constants configuration ๐Ÿค– Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- .gitignore | 3 + App.tsx | 294 ++++++ README.md | 120 +++ components/Clock.tsx | 78 ++ components/ErrorBoundary.tsx | 96 ++ components/GlobalContextMenu.tsx | 243 +++++ components/Icons.tsx | 361 +++++++ components/SearchBox.tsx | 472 +++++++++ components/SearchEngineManager.tsx | 303 ++++++ components/SettingsModal.tsx | 85 ++ components/ThemeSettings.tsx | 186 ++++ components/WallpaperManager.tsx | 296 ++++++ constants.ts | 88 ++ context/ToastContext.tsx | 79 ++ i18n/I18nContext.tsx | 45 + i18n/README.md | 228 ++++ i18n/index.ts | 4 + i18n/locales/en.ts | 95 ++ i18n/locales/zh.ts | 95 ++ i18n/types.ts | 95 ++ index.html | 39 + index.tsx | 18 + metadata.json | 5 + package.json | 21 + pnpm-lock.yaml | 1574 ++++++++++++++++++++++++++++ tsconfig.json | 26 + types.ts | 40 + utils/storage.ts | 122 +++ utils/suggestions.ts | 126 +++ vite-env.d.ts | 9 + vite.config.ts | 24 + 31 files changed, 5270 insertions(+) create mode 100644 App.tsx create mode 100644 README.md create mode 100644 components/Clock.tsx create mode 100644 components/ErrorBoundary.tsx create mode 100644 components/GlobalContextMenu.tsx create mode 100644 components/Icons.tsx create mode 100644 components/SearchBox.tsx create mode 100644 components/SearchEngineManager.tsx create mode 100644 components/SettingsModal.tsx create mode 100644 components/ThemeSettings.tsx create mode 100644 components/WallpaperManager.tsx create mode 100644 constants.ts create mode 100644 context/ToastContext.tsx create mode 100644 i18n/I18nContext.tsx create mode 100644 i18n/README.md create mode 100644 i18n/index.ts create mode 100644 i18n/locales/en.ts create mode 100644 i18n/locales/zh.ts create mode 100644 i18n/types.ts create mode 100644 index.html create mode 100644 index.tsx create mode 100644 metadata.json create mode 100644 package.json create mode 100644 pnpm-lock.yaml create mode 100644 tsconfig.json create mode 100644 types.ts create mode 100644 utils/storage.ts create mode 100644 utils/suggestions.ts create mode 100644 vite-env.d.ts create mode 100644 vite.config.ts diff --git a/.gitignore b/.gitignore index 1170717..006062e 100644 --- a/.gitignore +++ b/.gitignore @@ -7,6 +7,9 @@ yarn-error.log* lerna-debug.log* .pnpm-debug.log* +.claude/ +CLAUDE.md + # Diagnostic reports (https://nodejs.org/api/report.html) report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json diff --git a/App.tsx b/App.tsx new file mode 100644 index 0000000..702233a --- /dev/null +++ b/App.tsx @@ -0,0 +1,294 @@ + +import React, { useState, useEffect, useMemo, useRef } from 'react'; +import Clock from './components/Clock'; +import SearchBox from './components/SearchBox'; +import SettingsModal from './components/SettingsModal'; +import ErrorBoundary from './components/ErrorBoundary'; +import GlobalContextMenu from './components/GlobalContextMenu'; +import { SettingsIcon } from './components/Icons'; +import { UserSettings, WallpaperFit } from './types'; +import { PRESET_WALLPAPERS, SEARCH_ENGINES, THEMES } from './constants'; +import { loadSettings, saveSettings } from './utils/storage'; +import { I18nProvider } from './i18n'; + +// Default settings - moved outside component to avoid recreation on each render +const DEFAULT_SETTINGS: UserSettings = { + use24HourFormat: true, + showSeconds: true, + backgroundBlur: 8, + searchEngines: [...SEARCH_ENGINES], + selectedEngine: SEARCH_ENGINES[0].name, + themeColor: THEMES[0].hex, + searchOpacity: 0.8, + enableMaskBlur: false, + backgroundUrl: PRESET_WALLPAPERS[0].url, + backgroundType: PRESET_WALLPAPERS[0].type, + wallpaperFit: 'cover', + customWallpapers: [], + enableSearchHistory: true, + searchHistory: [], + language: 'en' +}; + +type ViewMode = 'search' | 'dashboard'; + +const App: React.FC = () => { + // State for settings visibility + const [isSettingsOpen, setIsSettingsOpen] = useState(false); + + // State for view mode (Search Panel vs Dashboard Panel) + const [viewMode, setViewMode] = useState('search'); + + // State for wallpaper loaded (prevent flash) + const [bgLoaded, setBgLoaded] = useState(false); + + // State for search box interaction (controls background blur) + const [isSearchActive, setIsSearchActive] = useState(false); + + // Application Settings - loaded from Local Storage + const [settings, setSettings] = useState(() => loadSettings(DEFAULT_SETTINGS)); + + // Flag to track if this is the initial mount + const isInitialMount = useRef(true); + + // Save settings to Local Storage (skip initial mount) + useEffect(() => { + if (isInitialMount.current) { + isInitialMount.current = false; + return; + } + saveSettings(settings); + }, [settings]); + + // Preload background when URL changes + useEffect(() => { + setBgLoaded(false); + let isMounted = true; + + if (settings.backgroundType === 'image') { + const img = new Image(); + img.src = settings.backgroundUrl; + img.onload = () => { + if (isMounted) { + setBgLoaded(true); + } + }; + // Handle error case to avoid stuck loading state + img.onerror = () => { + if (isMounted) { + setBgLoaded(true); + } + }; + + // Cleanup function + return () => { + isMounted = false; + // Clean up Image object event handlers + img.onload = null; + img.onerror = null; + // Cancel image loading + img.src = ''; + }; + } else { + // For video, we can consider it "loaded" once it starts playing or immediately + // depending on desired UX. Here we'll set it true immediately to show the video element + // which handles its own buffering. + setBgLoaded(true); + } + }, [settings.backgroundUrl, settings.backgroundType]); + + const handleSelectEngine = (name: string) => { + setSettings(prev => ({ ...prev, selectedEngine: name })); + }; + + const handleUpdateHistory = (newHistory: string[]) => { + setSettings(prev => ({ ...prev, searchHistory: newHistory })); + }; + + const getBackgroundStyle = (fit: WallpaperFit): React.CSSProperties => { + const baseStyle = { backgroundImage: `url(${settings.backgroundUrl})` }; + switch (fit) { + case 'contain': + return { ...baseStyle, backgroundSize: 'contain', backgroundPosition: 'center', backgroundRepeat: 'no-repeat' }; + case 'fill': + return { ...baseStyle, backgroundSize: '100% 100%', backgroundPosition: 'center', backgroundRepeat: 'no-repeat' }; + case 'repeat': + return { ...baseStyle, backgroundSize: 'auto', backgroundPosition: 'top left', backgroundRepeat: 'repeat' }; + case 'center': + return { ...baseStyle, backgroundSize: 'auto', backgroundPosition: 'center', backgroundRepeat: 'no-repeat' }; + case 'cover': + default: + return { ...baseStyle, backgroundSize: 'cover', backgroundPosition: 'center', backgroundRepeat: 'no-repeat' }; + } + }; + + const getVideoClass = (fit: WallpaperFit) => { + switch (fit) { + case 'contain': return 'object-contain'; + case 'fill': return 'object-fill'; + case 'center': return 'object-none'; + case 'repeat': return 'object-cover'; // Video tile not supported natively in same way, fallback to cover + case 'cover': + default: return 'object-cover'; + } + }; + + // Handle right-click on the background to switch to Dashboard + const handleBackgroundContextMenu = (e: React.MouseEvent) => { + // If settings modal is open, let standard behavior apply (or it's covered by modal backdrop) + if (isSettingsOpen) return; + + // We only want to capture clicks on the "background" or general containers. + // Specific interactive elements (like SearchBox) should stop propagation. + e.preventDefault(); + if (viewMode === 'search') { + setViewMode('dashboard'); + } + }; + + // Handle left-click on the dashboard to return to Search + const handleDashboardClick = (e: React.MouseEvent) => { + if (viewMode === 'dashboard' && !isSettingsOpen) { + setViewMode('search'); + } + }; + + const handleLanguageChange = (lang: 'en' | 'zh') => { + setSettings(prev => ({ ...prev, language: lang })); + }; + + return ( + + +
+ {/* Context Menu Global Listener */} + + + {/* Background Layer */} +
+ {settings.backgroundType === 'video' ? ( +