Spaces:
Running
Running
| import React, { useState, useEffect, useRef, useCallback } from 'react'; | |
| // --- Enhanced Icon Components with Modern Styling --- | |
| const IconNova = (props) => ( | |
| <svg {...props} xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"> | |
| <path d="M12 2L2 7l10 5 10-5-10-5z" /> | |
| <path d="M2 17l10 5 10-5" /> | |
| <path d="M2 12l10 5 10-5" /> | |
| </svg> | |
| ); | |
| const IconHome = (props) => ( | |
| <svg {...props} xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"> | |
| <path d="m3 9 9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z" /> | |
| <polyline points="9 22 9 12 15 12 15 22" /> | |
| </svg> | |
| ); | |
| const IconTabs = (props) => ( | |
| <svg {...props} xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"> | |
| <rect width="18" height="18" x="3" y="3" rx="2" /> | |
| <path d="M21 12H3" /> | |
| <path d="M12 3v18" /> | |
| </svg> | |
| ); | |
| const IconSparkles = (props) => ( | |
| <svg {...props} xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"> | |
| <path d="m12 3-1.9 4.8-4.8 1.9 4.8 1.9L12 16l1.9-4.8 4.8-1.9-4.8-1.9L12 3z" /> | |
| <path d="M5 21v-3.8" /> | |
| <path d="M19 21v-3.8" /> | |
| <path d="M3 12H7.8" /> | |
| <path d="M16.2 12H21" /> | |
| </svg> | |
| ); | |
| const IconSettings = (props) => ( | |
| <svg {...props} xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"> | |
| <path d="M12.22 2h-.44a2 2 0 0 0-2 2v.18a2 2 0 0 1-1 1.73l-.43.25a2 2 0 0 1-2 0l-.15-.08a2 2 0 0 0-2.73.73l-.22.38a2 2 0 0 0 .73 2.73l.15.1a2 2 0 0 1 1 1.72v.51a2 2 0 0 1-1 1.74l-.15.09a2 2 0 0 0-.73 2.73l.22.38a2 2 0 0 0 2.73.73l.15-.08a2 2 0 0 1 2 0l.43.25a2 2 0 0 1 1 1.73V20a2 2 0 0 0 2 2h.44a2 2 0 0 0 2-2v-.18a2 2 0 0 1 1-1.73l.43-.25a2 2 0 0 1 2 0l.15.08a2 2 0 0 0 2.73-.73l.22-.38a2 2 0 0 0-.73-2.73l-.15-.1a2 2 0 0 1-1-1.72v-.51a2 2 0 0 1 1-1.74l-.15-.09a2 2 0 0 0 .73 2.73l-.22-.38a2 2 0 0 0-2.73-.73l-.15.08a2 2 0 0 1-2 0l-.43-.25a2 2 0 0 1-1-1.73V4a2 2 0 0 0-2-2z" /> | |
| <circle cx="12" cy="12" r="3" /> | |
| </svg> | |
| ); | |
| const IconPlus = (props) => ( | |
| <svg {...props} xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"> | |
| <path d="M5 12h14" /> | |
| <path d="M12 5v14" /> | |
| </svg> | |
| ); | |
| const IconX = (props) => ( | |
| <svg {...props} xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"> | |
| <path d="M18 6 6 18" /> | |
| <path d="m6 6 12 12" /> | |
| </svg> | |
| ); | |
| const IconSearch = (props) => ( | |
| <svg {...props} xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"> | |
| <circle cx="11" cy="11" r="8" /> | |
| <path d="m21 21-4.3-4.3" /> | |
| </svg> | |
| ); | |
| const IconMic = (props) => ( | |
| <svg {...props} xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"> | |
| <path d="M12 1a3 3 0 0 0-3 3v8a3 3 0 0 0 6 0V4a3 3 0 0 0-3-3z" /> | |
| <path d="M19 10v2a7 7 0 0 1-14 0v-2" /> | |
| <line x1="12" x2="12" y1="19" y2="22" /> | |
| </svg> | |
| ); | |
| const IconSend = (props) => ( | |
| <svg {...props} xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"> | |
| <path d="m22 2-7 20-4-9-9-4Z" /> | |
| <path d="m22 2-11 11" /> | |
| </svg> | |
| ); | |
| const IconGrid = (props) => ( | |
| <svg {...props} xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"> | |
| <rect width="7" height="7" x="3" y="3" rx="1" /> | |
| <rect width="7" height="7" x="14" y="3" rx="1" /> | |
| <rect width="7" height="7" x="14" y="14" rx="1" /> | |
| <rect width="7" height="7" x="3" y="14" rx="1" /> | |
| </svg> | |
| ); | |
| const IconLayers = (props) => ( | |
| <svg {...props} xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"> | |
| <polygon points="12 2 2 7 12 12 22 7 12 2" /> | |
| <polyline points="2 17 12 22 22 17" /> | |
| <polyline points="2 12 12 17 22 12" /> | |
| </svg> | |
| ); | |
| const IconCheckSquare = (props) => ( | |
| <svg {...props} xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"> | |
| <polyline points="9 11 12 14 22 4" /> | |
| <path d="M21 12v7a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11" /> | |
| </svg> | |
| ); | |
| const IconCalendar = (props) => ( | |
| <svg {...props} xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"> | |
| <rect width="18" height="18" x="3" y="4" rx="2" ry="2" /> | |
| <line x1="16" x2="16" y1="2" y2="6" /> | |
| <line x1="8" x2="8" y1="2" y2="6" /> | |
| <line x1="3" x2="21" y1="10" y2="10" /> | |
| </svg> | |
| ); | |
| const IconCloud = (props) => ( | |
| <svg {...props} xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"> | |
| <path d="M18 10h-1.26A8 8 0 1 0 9 20h9a5 5 0 0 0 0-10z" /> | |
| </svg> | |
| ); | |
| const IconSun = (props) => ( | |
| <svg {...props} xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"> | |
| <circle cx="12" cy="12" r="5" /> | |
| <line x1="12" x2="12" y1="1" y2="3" /> | |
| <line x1="12" x2="12" y1="21" y2="23" /> | |
| <line x1="4.22" x2="5.64" y1="4.22" y2="5.64" /> | |
| <line x1="18.36" x2="19.78" y1="18.36" y2="19.78" /> | |
| <line x1="1" x2="3" y1="12" y2="12" /> | |
| <line x1="21" x2="23" y1="12" y2="12" /> | |
| <line x1="4.22" x2="5.64" y1="19.78" y2="18.36" /> | |
| <line x1="18.36" x2="19.78" y1="5.64" y2="4.22" /> | |
| </svg> | |
| ); | |
| const IconMoon = (props) => ( | |
| <svg {...props} xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"> | |
| <path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z" /> | |
| </svg> | |
| ); | |
| const IconCode = (props) => ( | |
| <svg {...props} xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"> | |
| <polyline points="16 18 22 12 16 6" /> | |
| <polyline points="8 6 2 12 8 18" /> | |
| </svg> | |
| ); | |
| const IconBrain = (props) => ( | |
| <svg {...props} xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"> | |
| <path d="M9.5 2A2.5 2.5 0 0 1 12 4.5v15a2.5 2.5 0 0 1-4.96.44 2.5 2.5 0 0 1-2.96-3.08 2.5 2.5 0 0 1-.34-5.99 2.5 2.5 0 0 1 5.76-1.24Z"/> | |
| <path d="M14.5 2A2.5 2.5 0 0 0 12 4.5v15a2.5 2.5 0 0 0 4.96.44 2.5 2.5 0 0 0 2.96-3.08 2.5 2.5 0 0 0 .34-5.99 2.5 2.5 0 0 0-5.76-1.24Z"/> | |
| </svg> | |
| ); | |
| const IconZap = (props) => ( | |
| <svg {...props} xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"> | |
| <polygon points="13 2 3 14 12 14 11 22 21 10 12 10 13 2" /> | |
| </svg> | |
| ); | |
| const IconShield = (props) => ( | |
| <svg {...props} xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"> | |
| <path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"/> | |
| </svg> | |
| ); | |
| const IconDownload = (props) => ( | |
| <svg {...props} xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"> | |
| <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/> | |
| <polyline points="7 10 12 15 17 10"/> | |
| <line x1="12" y1="15" x2="12" y2="3"/> | |
| </svg> | |
| ); | |
| // --- Search Engines Configuration --- | |
| const SEARCH_ENGINES = { | |
| google: { | |
| name: "Google", | |
| url: "https://www.google.com/search?q=", | |
| icon: "🔍", | |
| suggestionUrl: "https://suggestqueries.google.com/complete/search?client=firefox&q=" | |
| }, | |
| bing: { | |
| name: "Bing", | |
| url: "https://www.bing.com/search?q=", | |
| icon: "🔎", | |
| suggestionUrl: "https://api.bing.com/osjson.aspx?query=" | |
| }, | |
| duckduckgo: { | |
| name: "DuckDuckGo", | |
| url: "https://duckduckgo.com/?q=", | |
| icon: "🦆", | |
| suggestionUrl: "https://duckduckgo.com/ac/?q=" | |
| }, | |
| brave: { | |
| name: "Brave", | |
| url: "https://search.brave.com/search?q=", | |
| icon: "🦁", | |
| suggestionUrl: "https://search.brave.com/api/suggest?q=" | |
| }, | |
| youtube: { | |
| name: "YouTube", | |
| url: "https://www.youtube.com/results?search_query=", | |
| icon: "📺", | |
| suggestionUrl: "https://suggestqueries.google.com/complete/search?client=youtube&ds=yt&q=" | |
| } | |
| }; | |
| // --- Enhanced Mock Data --- | |
| const initialTabs = [ | |
| { | |
| id: 1, | |
| title: "NovaBrowse AI Dashboard", | |
| url: "nova://dashboard", | |
| topic: "home", | |
| favicon: "🚀", | |
| lastActive: "now", | |
| isProtected: true | |
| }, | |
| { | |
| id: 2, | |
| title: "Google", | |
| url: "https://www.google.com", | |
| topic: "search", | |
| favicon: "🔍", | |
| lastActive: "5m ago", | |
| searchEngine: "google" | |
| } | |
| ]; | |
| const topicColorsMap = { | |
| home: { border: "border-blue-500", text: "text-blue-400", bg: "bg-blue-500", gradient: "from-blue-500 to-cyan-500" }, | |
| research: { border: "border-purple-500", text: "text-purple-400", bg: "bg-purple-500", gradient: "from-purple-500 to-pink-500" }, | |
| work: { border: "border-green-500", text: "text-green-400", bg: "bg-green-500", gradient: "from-green-500 to-emerald-500" }, | |
| entertainment: { border: "border-red-500", text: "text-red-400", bg: "bg-red-500", gradient: "from-red-500 to-orange-500" }, | |
| search: { border: "border-yellow-500", text: "text-yellow-400", bg: "bg-yellow-500", gradient: "from-yellow-500 to-orange-500" }, | |
| default: { border: "border-gray-500", text: "text-gray-400", bg: "bg-gray-500", gradient: "from-gray-500 to-slate-500" }, | |
| }; | |
| const mockWidgets = [ | |
| { id: 1, icon: <IconCalendar className="w-6 h-6" />, title: "Calendar", content: "3 Meetings Today", metric: "+2 from yesterday", trend: "up" }, | |
| { id: 2, icon: <IconCheckSquare className="w-6 h-6" />, title: "To-Do", content: "Finish browser prototype.", metric: "75% completed", trend: "up" }, | |
| { id: 3, icon: <IconCloud className="w-6 h-6" />, title: "Weather", content: "24°C, Clear", metric: "Perfect for focus", trend: "stable" }, | |
| { id: 4, icon: <IconSparkles className="w-6 h-6" />, title: "AI Tip", content: "Try 'Summarize this' on any article.", metric: "Saves 5min avg", trend: "up" }, | |
| ]; | |
| const aiCommands = [ | |
| { command: "Summarize this page", icon: "📄", description: "Get key points from current tab" }, | |
| { command: "Organize my tabs", icon: "🗂️", description: "AI-powered tab grouping" }, | |
| { command: "Find research papers", icon: "🔍", description: "Academic search optimization" }, | |
| { command: "Translate to Spanish", icon: "🌎", description: "Real-time page translation" }, | |
| ]; | |
| // --- Main Component --- | |
| export default function AdvancedBrowser() { | |
| const [tabs, setTabs] = useState(initialTabs); | |
| const [activeTabId, setActiveTabId] = useState(1); | |
| const [activePage, setActivePage] = useState('dashboard'); | |
| const [isCopilotOpen, setIsCopilotOpen] = useState(false); | |
| const [copilotMessages, setCopilotMessages] = useState([ | |
| { from: "ai", text: "Hello! I am Nova, your AI assistant. How can I help you browse today?" } | |
| ]); | |
| const [copilotInput, setCopilotInput] = useState(""); | |
| const [isAiThinking, setIsAiThinking] = useState(false); | |
| const [searchTerm, setSearchTerm] = useState(""); | |
| const [theme, setTheme] = useState('dark'); | |
| const [isListening, setIsListening] = useState(false); | |
| const [sidebarCollapsed, setSidebarCollapsed] = useState(false); | |
| const [showCommandPalette, setShowCommandPalette] = useState(false); | |
| const [performanceMetrics, setPerformanceMetrics] = useState({ | |
| memory: "1.2GB", cpu: "15%", network: "45ms", tabs: "2 open" | |
| }); | |
| const [currentUrl, setCurrentUrl] = useState("nova://dashboard"); | |
| const [isLoading, setIsLoading] = useState(false); | |
| const [searchSuggestions, setSearchSuggestions] = useState([]); | |
| const [showSuggestions, setShowSuggestions] = useState(false); | |
| const [currentSearchEngine, setCurrentSearchEngine] = useState("google"); | |
| const [downloads, setDownloads] = useState([]); | |
| const [securityStatus, setSecurityStatus] = useState("secure"); | |
| const [bookmarks, setBookmarks] = useState([]); | |
| const [history, setHistory] = useState([]); | |
| const copilotMessagesEndRef = useRef(null); | |
| const speechRecognitionRef = useRef(null); | |
| const searchInputRef = useRef(null); | |
| const iframeRef = useRef(null); | |
| const activeTab = tabs.find(t => t.id === activeTabId); | |
| // Enhanced theme simulation | |
| useEffect(() => { | |
| const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)'); | |
| setTheme(mediaQuery.matches ? 'dark' : 'light'); | |
| const handler = (e) => setTheme(e.matches ? 'dark' : 'light'); | |
| mediaQuery.addEventListener('change', handler); | |
| return () => mediaQuery.removeEventListener('change', handler); | |
| }, []); | |
| // Enhanced scroll effect | |
| useEffect(() => { | |
| copilotMessagesEndRef.current?.scrollIntoView({ behavior: "smooth" }); | |
| }, [copilotMessages]); | |
| // Enhanced speech recognition | |
| useEffect(() => { | |
| const SpeechRecognition = window.SpeechRecognition || window.webkitSpeechRecognition; | |
| if (SpeechRecognition) { | |
| const recognition = new SpeechRecognition(); | |
| recognition.continuous = false; | |
| recognition.interimResults = false; | |
| recognition.lang = 'en-US'; | |
| recognition.onstart = () => setIsListening(true); | |
| recognition.onend = () => setIsListening(false); | |
| recognition.onresult = (event) => { | |
| const transcript = event.results[0][0].transcript; | |
| setSearchTerm(transcript); | |
| handleSearch(transcript); | |
| }; | |
| recognition.onerror = (event) => { | |
| console.error("Speech recognition error:", event.error); | |
| setIsListening(false); | |
| }; | |
| speechRecognitionRef.current = recognition; | |
| } | |
| }, []); | |
| // Command palette shortcut | |
| useEffect(() => { | |
| const handleKeyDown = (e) => { | |
| if ((e.metaKey || e.ctrlKey) && e.key === 'k') { | |
| e.preventDefault(); | |
| setShowCommandPalette(true); | |
| setTimeout(() => searchInputRef.current?.focus(), 100); | |
| } | |
| if (e.key === 'Escape') { | |
| setShowCommandPalette(false); | |
| setShowSuggestions(false); | |
| } | |
| if ((e.metaKey || e.ctrlKey) && e.key === 't') { | |
| e.preventDefault(); | |
| handleNewTab(); | |
| } | |
| if ((e.metaKey || e.ctrlKey) && e.key === 'w') { | |
| e.preventDefault(); | |
| if (tabs.length > 1) { | |
| handleCloseTab(null, activeTabId); | |
| } | |
| } | |
| }; | |
| window.addEventListener('keydown', handleKeyDown); | |
| return () => window.removeEventListener('keydown', handleKeyDown); | |
| }, [tabs, activeTabId]); | |
| // Search suggestions | |
| useEffect(() => { | |
| if (searchTerm.length > 2) { | |
| fetchSearchSuggestions(searchTerm); | |
| } else { | |
| setSearchSuggestions([]); | |
| } | |
| }, [searchTerm]); | |
| const fetchSearchSuggestions = async (query) => { | |
| try { | |
| const engine = SEARCH_ENGINES[currentSearchEngine]; | |
| const response = await fetch(`${engine.suggestionUrl}${encodeURIComponent(query)}`); | |
| const data = await response.json(); | |
| setSearchSuggestions(data[1] || []); | |
| setShowSuggestions(true); | |
| } catch (error) { | |
| console.error("Failed to fetch suggestions:", error); | |
| } | |
| }; | |
| const toggleTheme = () => { | |
| setTheme(t => (t === 'dark' ? 'light' : 'dark')); | |
| }; | |
| const getTopicColor = useCallback((topic, type = 'border') => { | |
| const colors = topicColorsMap[topic] || topicColorsMap.default; | |
| return colors[type]; | |
| }, []); | |
| const handleTabClick = (id) => { | |
| setActiveTabId(id); | |
| const tab = tabs.find(t => t.id === id); | |
| setCurrentUrl(tab.url); | |
| setActivePage(tab.url === "nova://dashboard" ? 'dashboard' : 'web'); | |
| }; | |
| const handleNewTab = (url = "nova://dashboard", title = "New Tab") => { | |
| const newId = Math.max(0, ...tabs.map(t => t.id)) + 1; | |
| const newTab = { | |
| id: newId, | |
| title: title, | |
| url: url, | |
| topic: url === "nova://dashboard" ? "home" : "default", | |
| favicon: "🌐", | |
| lastActive: "now" | |
| }; | |
| setTabs([...tabs, newTab]); | |
| setActiveTabId(newId); | |
| setCurrentUrl(url); | |
| setActivePage(url === "nova://dashboard" ? 'dashboard' : 'web'); | |
| }; | |
| const handleCloseTab = (e, id) => { | |
| e?.stopPropagation(); | |
| if (tabs.length === 1) { | |
| handleNewTab(); | |
| setTabs(tabs.filter(t => t.id !== id)); | |
| return; | |
| } | |
| const newTabs = tabs.filter(t => t.id !== id); | |
| setTabs(newTabs); | |
| if (activeTabId === id) { | |
| const newActiveId = newTabs[newTabs.length - 1].id; | |
| setActiveTabId(newActiveId); | |
| const newActiveTab = newTabs.find(t => t.id === newActiveId); | |
| setCurrentUrl(newActiveTab.url); | |
| setActivePage(newActiveTab.url === "nova://dashboard" ? 'dashboard' : 'web'); | |
| } | |
| }; | |
| const organizeTabsByAi = () => { | |
| const sortedTabs = [...tabs].sort((a, b) => a.topic.localeCompare(b.topic)); | |
| setTabs(sortedTabs); | |
| }; | |
| const handleSearch = (query, engine = currentSearchEngine) => { | |
| if (!query?.trim()) return; | |
| setIsLoading(true); | |
| setSearchTerm(query); | |
| const searchUrl = SEARCH_ENGINES[engine].url + encodeURIComponent(query); | |
| const searchTab = tabs.find(t => t.url.includes(SEARCH_ENGINES[engine].url)); | |
| if (searchTab) { | |
| const updatedTabs = tabs.map(t => | |
| t.id === searchTab.id ? { ...t, url: searchUrl, title: `${SEARCH_ENGINES[engine].name} Search` } : t | |
| ); | |
| setTabs(updatedTabs); | |
| setActiveTabId(searchTab.id); | |
| } else { | |
| handleNewTab(searchUrl, `${SEARCH_ENGINES[engine].name} Search`); | |
| } | |
| setCurrentUrl(searchUrl); | |
| setActivePage('web'); | |
| setShowSuggestions(false); | |
| // Add to history | |
| setHistory(prev => [...prev, { | |
| url: searchUrl, | |
| title: `${query} - ${SEARCH_ENGINES[engine].name} Search`, | |
| timestamp: new Date().toISOString() | |
| }]); | |
| }; | |
| const handleUrlNavigation = (url) => { | |
| if (!url) return; | |
| let finalUrl = url; | |
| if (!url.startsWith('http') && !url.startsWith('nova://')) { | |
| finalUrl = SEARCH_ENGINES[currentSearchEngine].url + encodeURIComponent(url); | |
| } | |
| setIsLoading(true); | |
| setCurrentUrl(finalUrl); | |
| const updatedTabs = tabs.map(t => | |
| t.id === activeTabId ? { ...t, url: finalUrl, title: "Loading..." } : t | |
| ); | |
| setTabs(updatedTabs); | |
| if (finalUrl.startsWith('nova://')) { | |
| setActivePage('dashboard'); | |
| } else { | |
| setActivePage('web'); | |
| } | |
| }; | |
| const handleVoiceSearch = () => { | |
| if (speechRecognitionRef.current && !isListening) { | |
| speechRecognitionRef.current.start(); | |
| } else if (isListening) { | |
| speechRecognitionRef.current.stop(); | |
| } | |
| }; | |
| const handleDownload = (url, filename) => { | |
| const downloadId = Date.now(); | |
| const newDownload = { | |
| id: downloadId, | |
| url, | |
| filename: filename || `download-${downloadId}`, | |
| progress: 0, | |
| status: 'downloading' | |
| }; | |
| setDownloads(prev => [...prev, newDownload]); | |
| // Simulate download progress | |
| const interval = setInterval(() => { | |
| setDownloads(prev => prev.map(d => | |
| d.id === downloadId | |
| ? { ...d, progress: Math.min(d.progress + 10, 100) } | |
| : d | |
| )); | |
| }, 200); | |
| setTimeout(() => { | |
| clearInterval(interval); | |
| setDownloads(prev => prev.map(d => | |
| d.id === downloadId | |
| ? { ...d, progress: 100, status: 'completed' } | |
| : d | |
| )); | |
| }, 2000); | |
| }; | |
| const addBookmark = (url, title) => { | |
| const newBookmark = { | |
| id: Date.now(), | |
| url, | |
| title, | |
| favicon: "🔖", | |
| dateAdded: new Date().toISOString() | |
| }; | |
| setBookmarks(prev => [...prev, newBookmark]); | |
| }; | |
| const removeBookmark = (id) => { | |
| setBookmarks(prev => prev.filter(b => b.id !== id)); | |
| }; | |
| const handleIframeLoad = () => { | |
| setIsLoading(false); | |
| const updatedTabs = tabs.map(t => | |
| t.id === activeTabId ? { ...t, title: iframeRef.current?.contentDocument?.title || t.title } : t | |
| ); | |
| setTabs(updatedTabs); | |
| // Update security status based on URL | |
| const isSecure = currentUrl.startsWith('https://'); | |
| setSecurityStatus(isSecure ? 'secure' : 'insecure'); | |
| }; | |
| const Sidebar = () => ( | |
| <nav className={`flex flex-col h-full py-4 glass-ui transition-all duration-300 ${ | |
| sidebarCollapsed ? 'w-16' : 'w-20' | |
| } ${theme === 'dark' ? 'text-gray-300' : 'text-gray-700'} flex-shrink-0`}> | |
| <div className={`p-2 mb-6 transition-all duration-300 ${ | |
| theme === 'dark' ? 'text-blue-400' : 'text-blue-600' | |
| } hover:opacity-80 hover:scale-110 cursor-pointer`}> | |
| <IconNova className="w-8 h-8" /> | |
| </div> | |
| <div className="flex flex-col items-center space-y-4 flex-1"> | |
| {[ | |
| { icon: <IconHome />, page: 'dashboard', label: 'Home' }, | |
| { icon: <IconGrid />, page: 'tabs', label: 'Tabs' }, | |
| { icon: <IconLayers />, page: 'bookmarks', label: 'Bookmarks' }, | |
| { icon: <IconDownload />, page: 'downloads', label: 'Downloads' }, | |
| { icon: <IconCode />, page: 'developer', label: 'Dev Tools' }, | |
| { icon: <IconBrain />, page: 'ai', label: 'AI Studio' }, | |
| ].map((item) => ( | |
| <button | |
| key={item.page} | |
| onClick={() => { | |
| setActivePage(item.page); | |
| if (item.page === 'dashboard') { | |
| handleNewTab("nova://dashboard", "NovaBrowse Dashboard"); | |
| } | |
| }} | |
| className={`p-3 rounded-xl transition-all duration-300 group relative ${ | |
| activePage === item.page | |
| ? 'bg-gradient-to-br from-blue-500 to-purple-600 text-white shadow-lg' | |
| : 'hover:bg-gray-500/20' | |
| }`} | |
| title={item.label} | |
| > | |
| {React.cloneElement(item.icon, { className: "w-6 h-6" })} | |
| {!sidebarCollapsed && ( | |
| <span className="absolute left-14 bg-gray-900 text-white text-xs px-2 py-1 rounded opacity-0 group-hover:opacity-100 transition-opacity whitespace-nowrap"> | |
| {item.label} | |
| </span> | |
| )} | |
| </button> | |
| ))} | |
| </div> | |
| <div className="space-y-4"> | |
| <button | |
| onClick={toggleTheme} | |
| className="p-3 rounded-xl transition-all duration-300 hover:bg-gray-500/20 group relative" | |
| title="Toggle Theme" | |
| > | |
| {theme === 'dark' ? <IconSun className="w-6 h-6" /> : <IconMoon className="w-6 h-6" />} | |
| {!sidebarCollapsed && ( | |
| <span className="absolute left-14 bg-gray-900 text-white text-xs px-2 py-1 rounded opacity-0 group-hover:opacity-100 transition-opacity"> | |
| Toggle Theme | |
| </span> | |
| )} | |
| </button> | |
| <button | |
| className="p-3 rounded-xl transition-all duration-300 hover:bg-gray-500/20 group relative" | |
| title="Settings" | |
| > | |
| <IconSettings className="w-6 h-6" /> | |
| {!sidebarCollapsed && ( | |
| <span className="absolute left-14 bg-gray-900 text-white text-xs px-2 py-1 rounded opacity-0 group-hover:opacity-100 transition-opacity"> | |
| Settings | |
| </span> | |
| )} | |
| </button> | |
| </div> | |
| </nav> | |
| ); | |
| const TabStrip = () => ( | |
| <div className={`flex items-center w-full h-14 overflow-x-auto glass-ui-top border-b ${ | |
| theme === 'dark' ? 'border-gray-500/20' : 'border-gray-300/30' | |
| }`}> | |
| <div className="flex h-full"> | |
| {tabs.map(tab => ( | |
| <div | |
| key={tab.id} | |
| onClick={() => handleTabClick(tab.id)} | |
| className={`group relative flex items-center justify-between px-4 h-full min-w-[180px] max-w-[240px] cursor-pointer transition-all duration-300 border-r ${ | |
| activeTabId === tab.id | |
| ? 'bg-gradient-to-r from-blue-500/20 to-purple-500/10' | |
| : (theme === 'dark' ? 'bg-gray-800/10 hover:bg-gray-500/20' : 'bg-white/10 hover:bg-gray-200/20') | |
| } ${theme === 'dark' ? 'border-gray-700/30' : 'border-gray-300/30'}`} | |
| > | |
| <div className={`absolute inset-0 rounded-t-lg ${ | |
| activeTabId === tab.id ? getTopicColor(tab.topic, 'border') : 'border-transparent' | |
| } border-t-2`}></div> | |
| <div className="flex items-center space-x-3 flex-1 min-w-0"> | |
| <span className="text-lg">{tab.favicon}</span> | |
| <span className={`text-sm truncate font-medium ${ | |
| theme === 'dark' ? 'text-gray-200' : 'text-gray-800' | |
| }`}>{tab.title}</span> | |
| </div> | |
| <button | |
| onClick={(e) => handleCloseTab(e, tab.id)} | |
| className={`ml-2 p-1 rounded-full opacity-0 group-hover:opacity-100 transition-all ${ | |
| theme === 'dark' ? 'text-gray-400' : 'text-gray-600' | |
| } hover:bg-red-500/50 hover:text-white`} | |
| > | |
| <IconX className="w-3 h-3" /> | |
| </button> | |
| </div> | |
| ))} | |
| </div> | |
| <button | |
| onClick={() => handleNewTab()} | |
| className={`px-4 h-full transition-all duration-300 ${ | |
| theme === 'dark' | |
| ? 'bg-gray-800/20 text-gray-400 hover:bg-blue-500/20 hover:text-white' | |
| : 'bg-gray-100/20 text-gray-600 hover:bg-blue-500/20 hover:text-white' | |
| }`} | |
| > | |
| <IconPlus className="w-5 h-5" /> | |
| </button> | |
| </div> | |
| ); | |
| const AddressBar = () => ( | |
| <div className="flex items-center w-full h-16 p-3 glass-ui-top"> | |
| <div className="flex items-center space-x-2 mr-4"> | |
| <button | |
| onClick={() => activeTabId && handleCloseTab(null, activeTabId)} | |
| className={`p-2 rounded-xl transition-all ${ | |
| theme === 'dark' ? 'hover:bg-gray-700/50 text-gray-400' : 'hover:bg-gray-300/50 text-gray-600' | |
| }`} | |
| title="Close Tab (Ctrl+W)" | |
| > | |
| <IconX className="w-4 h-4" /> | |
| </button> | |
| <button | |
| onClick={() => handleNewTab()} | |
| className={`p-2 rounded-xl transition-all ${ | |
| theme === 'dark' ? 'hover:bg-gray-700/50 text-gray-400' : 'hover:bg-gray-300/50 text-gray-600' | |
| }`} | |
| title="New Tab (Ctrl+T)" | |
| > | |
| <IconPlus className="w-4 h-4" /> | |
| </button> | |
| </div> | |
| <div className={`flex items-center w-full max-w-4xl rounded-2xl glass-ui-inset p-1 ${ | |
| theme === 'dark' ? 'shadow-inner-dark' : 'shadow-inner' | |
| } relative`}> | |
| {/* Search Engine Selector */} | |
| <div className="relative group"> | |
| <button className="flex items-center px-3 py-2 rounded-xl mr-2 transition-all bg-gray-500/20 hover:bg-gray-500/30"> | |
| <span className="text-lg mr-2">{SEARCH_ENGINES[currentSearchEngine].icon}</span> | |
| <span className="text-sm font-medium">{SEARCH_ENGINES[currentSearchEngine].name}</span> | |
| </button> | |
| <div className="absolute top-full left-0 mt-2 w-48 rounded-2xl glass-ui-copilot border border-gray-500/30 shadow-2xl opacity-0 invisible group-hover:opacity-100 group-hover:visible transition-all duration-300 z-50"> | |
| {Object.entries(SEARCH_ENGINES).map(([key, engine]) => ( | |
| <button | |
| key={key} | |
| onClick={() => setCurrentSearchEngine(key)} | |
| className={`w-full text-left p-3 flex items-center space-x-3 transition-all ${ | |
| currentSearchEngine === key | |
| ? 'bg-blue-500/20 text-blue-300' | |
| : 'hover:bg-gray-500/20 text-gray-300' | |
| }`} | |
| > | |
| <span className="text-lg">{engine.icon}</span> | |
| <span>{engine.name}</span> | |
| </button> | |
| ))} | |
| </div> | |
| </div> | |
| <input | |
| ref={searchInputRef} | |
| type="text" | |
| placeholder={`Search with ${SEARCH_ENGINES[currentSearchEngine].name} or enter address...`} | |
| className={`flex-1 py-3 bg-transparent ${ | |
| theme === 'dark' ? 'text-white placeholder-gray-400' : 'text-black placeholder-gray-500' | |
| } focus:outline-none font-body text-lg`} | |
| value={searchTerm} | |
| onChange={(e) => setSearchTerm(e.target.value)} | |
| onKeyDown={(e) => e.key === 'Enter' && handleSearch(searchTerm)} | |
| onFocus={() => searchTerm.length > 2 && setShowSuggestions(true)} | |
| /> | |
| {isLoading && ( | |
| <div className="absolute right-16 top-1/2 transform -translate-y-1/2"> | |
| <div className="w-5 h-5 border-2 border-blue-500 border-t-transparent rounded-full animate-spin"></div> | |
| </div> | |
| )} | |
| <button | |
| onClick={handleVoiceSearch} | |
| className={`p-3 mx-1 rounded-xl transition-all ${ | |
| theme === 'dark' ? 'text-gray-300' : 'text-gray-700' | |
| } hover:bg-blue-500/30 hover:text-white ${isListening ? 'animate-pulse-glow bg-red-500/20' : ''}`} | |
| title="Voice Command" | |
| > | |
| <IconMic className="w-5 h-5" /> | |
| </button> | |
| {/* Security Indicator */} | |
| <div className={`px-3 py-1 rounded-lg mr-2 ${ | |
| securityStatus === 'secure' | |
| ? 'bg-green-500/20 text-green-400' | |
| : 'bg-red-500/20 text-red-400' | |
| }`}> | |
| <IconShield className="w-4 h-4" /> | |
| </div> | |
| </div> | |
| {/* Search Suggestions */} | |
| {showSuggestions && searchSuggestions.length > 0 && ( | |
| <div className="absolute top-full left-1/2 transform -translate-x-1/2 mt-2 w-full max-w-2xl rounded-2xl glass-ui-copilot border border-gray-500/30 shadow-2xl z-40"> | |
| {searchSuggestions.slice(0, 8).map((suggestion, index) => ( | |
| <button | |
| key={index} | |
| onClick={() => { | |
| setSearchTerm(suggestion); | |
| handleSearch(suggestion); | |
| }} | |
| className="w-full text-left p-4 hover:bg-gray-500/20 transition-all border-b border-gray-500/10 last:border-b-0" | |
| > | |
| <div className="flex items-center space-x-3"> | |
| <IconSearch className="w-4 h-4 text-gray-400" /> | |
| <span className="text-gray-200">{suggestion}</span> | |
| </div> | |
| </button> | |
| ))} | |
| </div> | |
| )} | |
| <div className="flex items-center space-x-2 ml-4"> | |
| <button | |
| onClick={() => currentUrl && addBookmark(currentUrl, activeTab?.title)} | |
| className={`p-3 rounded-xl transition-all duration-300 ${ | |
| theme === 'dark' ? 'text-yellow-400 hover:bg-yellow-500/20' : 'text-yellow-600 hover:bg-yellow-500/10' | |
| }`} | |
| title="Bookmark this page" | |
| > | |
| 🔖 | |
| </button> | |
| <button | |
| onClick={() => setIsCopilotOpen(true)} | |
| className="relative p-3 text-blue-300 rounded-xl transition-all duration-300 hover:bg-blue-500/30 hover:text-blue-100 neon-button-glow group" | |
| title="AI Copilot" | |
| > | |
| <IconSparkles className="w-6 h-6" /> | |
| <span className="absolute -top-1 -right-1 w-3 h-3 bg-blue-400 rounded-full animate-pulse"></span> | |
| <span className="absolute -bottom-8 left-1/2 transform -translate-x-1/2 bg-gray-900 text-white text-xs px-2 py-1 rounded opacity-0 group-hover:opacity-100 transition-opacity"> | |
| AI Assistant | |
| </span> | |
| </button> | |
| </div> | |
| </div> | |
| ); | |
| const WebView = () => ( | |
| <div className="w-full h-full relative"> | |
| {isLoading && ( | |
| <div className="absolute top-0 left-0 w-full h-1 bg-gradient-to-r from-blue-500 to-purple-500 z-10"> | |
| <div className="h-full bg-blue-400 animate-pulse" style={{ width: '80%' }}></div> | |
| </div> | |
| )} | |
| {currentUrl.startsWith('nova://') ? ( | |
| <EnhancedDashboard /> | |
| ) : ( | |
| <iframe | |
| ref={iframeRef} | |
| src={currentUrl} | |
| className="w-full h-full border-0" | |
| onLoad={handleIframeLoad} | |
| title="Web Content" | |
| sandbox="allow-same-origin allow-scripts allow-popups allow-forms" | |
| /> | |
| )} | |
| </div> | |
| ); | |
| const EnhancedDashboard = () => ( | |
| <div className="h-full overflow-y-auto"> | |
| <div className="absolute inset-0 -z-10"> | |
| <div className={`absolute inset-0 opacity-20 ${ | |
| theme === 'dark' | |
| ? 'bg-gradient-to-br from-blue-900/30 via-purple-900/20 to-cyan-900/30' | |
| : 'bg-gradient-to-br from-blue-100/50 via-purple-100/30 to-cyan-100/50' | |
| }`}></div> | |
| </div> | |
| <div className="container mx-auto px-8 py-12"> | |
| <div className="text-center mb-16"> | |
| <h1 className="text-7xl font-black bg-gradient-to-r from-blue-400 via-purple-500 to-cyan-400 bg-clip-text text-transparent mb-6 animate-gradient"> | |
| NovaBrowse AI | |
| </h1> | |
| <p className={`text-2xl ${theme === 'dark' ? 'text-gray-300' : 'text-gray-600'} font-light mb-8`}> | |
| The Future of Intelligent Browsing | |
| </p> | |
| <div className="w-full max-w-3xl mx-auto mb-16"> | |
| <div className={`relative rounded-3xl glass-ui p-2 shadow-2xl neon-focus-glow ${ | |
| theme === 'light' ? 'border-gray-300/50' : '' | |
| }`}> | |
| <IconSearch className={`absolute left-6 top-1/2 transform -translate-y-1/2 w-6 h-6 ${ | |
| theme === 'dark' ? 'text-gray-300' : 'text-gray-700' | |
| }`} /> | |
| <input | |
| type="text" | |
| placeholder="Ask Nova anything or search the web..." | |
| className={`w-full p-4 pl-16 text-xl bg-transparent ${ | |
| theme === 'dark' ? 'text-white placeholder-gray-400' : 'text-black placeholder-gray-500' | |
| } focus:outline-none font-body`} | |
| value={searchTerm} | |
| onChange={(e) => setSearchTerm(e.target.value)} | |
| onKeyDown={(e) => e.key === 'Enter' && handleSearch(searchTerm)} | |
| /> | |
| <button | |
| onClick={handleVoiceSearch} | |
| className={`absolute right-4 top-1/2 transform -translate-y-1/2 p-3 rounded-2xl transition-all ${ | |
| theme === 'dark' ? 'text-gray-300' : 'text-gray-700' | |
| } hover:bg-blue-500/30 hover:text-white ${isListening ? 'animate-pulse-glow bg-red-500/20' : ''}`} | |
| > | |
| <IconMic className="w-6 h-6" /> | |
| </button> | |
| </div> | |
| </div> | |
| </div> | |
| <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-8 mb-12"> | |
| {mockWidgets.map((widget, i) => ( | |
| <div | |
| key={widget.id} | |
| className="group p-6 rounded-2xl glass-ui hover-3d-tilt transition-all duration-500 animate-fade-in-up cursor-pointer border border-gray-500/10 hover:border-blue-500/30" | |
| style={{ animationDelay: `${i * 100}ms` }} | |
| > | |
| <div className="flex items-center justify-between mb-4"> | |
| <div className={`p-3 rounded-xl bg-gradient-to-br ${getTopicColor('home', 'gradient')}`}> | |
| {widget.icon} | |
| </div> | |
| <div className={`text-sm px-2 py-1 rounded-lg ${ | |
| widget.trend === 'up' ? 'bg-green-500/20 text-green-400' : | |
| widget.trend === 'down' ? 'bg-red-500/20 text-red-400' : | |
| 'bg-blue-500/20 text-blue-400' | |
| }`}> | |
| {widget.metric} | |
| </div> | |
| </div> | |
| <h3 className={`text-lg font-bold mb-2 ${theme === 'dark' ? 'text-white' : 'text-gray-900'}`}> | |
| {widget.title} | |
| </h3> | |
| <p className={`text-sm ${theme === 'dark' ? 'text-gray-300' : 'text-gray-600'} mb-4`}> | |
| {widget.content} | |
| </p> | |
| <div className="w-full bg-gray-500/20 rounded-full h-1"> | |
| <div className={`h-1 rounded-full bg-gradient-to-r ${getTopicColor('home', 'gradient')} ${ | |
| widget.trend === 'up' ? 'w-3/4' : 'w-1/2' | |
| }`}></div> | |
| </div> | |
| </div> | |
| ))} | |
| </div> | |
| <div className="grid grid-cols-2 md:grid-cols-4 gap-6"> | |
| {aiCommands.slice(0, 4).map((cmd, i) => ( | |
| <button | |
| key={i} | |
| onClick={() => { | |
| setCopilotInput(cmd.command); | |
| setIsCopilotOpen(true); | |
| }} | |
| className={`p-4 rounded-2xl glass-ui transition-all duration-300 hover:scale-105 group ${ | |
| theme === 'dark' ? 'hover:bg-blue-500/20' : 'hover:bg-blue-500/10' | |
| }`} | |
| > | |
| <div className="text-2xl mb-2">{cmd.icon}</div> | |
| <div className={`text-sm font-medium text-left ${ | |
| theme === 'dark' ? 'text-gray-200' : 'text-gray-800' | |
| }`}>{cmd.command}</div> | |
| <div className={`text-xs text-left ${ | |
| theme === 'dark' ? 'text-gray-400' : 'text-gray-600' | |
| }`}>{cmd.description}</div> | |
| </button> | |
| ))} | |
| </div> | |
| {/* Quick Access Sites */} | |
| <div className="mt-16"> | |
| <h2 className={`text-3xl font-bold mb-8 ${theme === 'dark' ? 'text-white' : 'text-gray-900'}`}> | |
| Quick Access | |
| </h2> | |
| <div className="grid grid-cols-2 md:grid-cols-4 lg:grid-cols-6 gap-6"> | |
| {Object.entries(SEARCH_ENGINES).map(([key, engine]) => ( | |
| <button | |
| key={key} | |
| onClick={() => handleNewTab(engine.url, `${engine.name} Home`)} | |
| className="p-6 rounded-2xl glass-ui transition-all duration-300 hover:scale-105 group text-center" | |
| > | |
| <div className="text-3xl mb-3">{engine.icon}</div> | |
| <div className={`text-sm font-medium ${ | |
| theme === 'dark' ? 'text-gray-200' : 'text-gray-800' | |
| }`}>{engine.name}</div> | |
| </button> | |
| ))} | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| ); | |
| const BookmarksView = () => ( | |
| <div className="p-8"> | |
| <div className="flex items-center justify-between mb-8"> | |
| <h1 className="text-5xl font-black bg-gradient-to-r from-yellow-400 to-orange-400 bg-clip-text text-transparent"> | |
| Bookmarks | |
| </h1> | |
| <button | |
| onClick={() => currentUrl && addBookmark(currentUrl, activeTab?.title)} | |
| className="px-6 py-3 rounded-2xl bg-gradient-to-r from-yellow-500 to-orange-500 text-white font-semibold hover:scale-105 transition-transform shadow-lg" | |
| > | |
| + Add Current Page | |
| </button> | |
| </div> | |
| {bookmarks.length === 0 ? ( | |
| <div className="text-center py-16"> | |
| <div className="text-6xl mb-4">🔖</div> | |
| <h3 className={`text-2xl font-bold mb-2 ${theme === 'dark' ? 'text-gray-300' : 'text-gray-700'}`}> | |
| No bookmarks yet | |
| </h3> | |
| <p className={`${theme === 'dark' ? 'text-gray-400' : 'text-gray-600'}`}> | |
| Bookmark your favorite sites for quick access | |
| </p> | |
| </div> | |
| ) : ( | |
| <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-6"> | |
| {bookmarks.map(bookmark => ( | |
| <div | |
| key={bookmark.id} | |
| className="group p-6 rounded-2xl glass-ui h-32 flex flex-col justify-between cursor-pointer transition-all duration-300 hover-3d-tilt border-2 border-transparent hover:border-yellow-500/50" | |
| onClick={() => handleNewTab(bookmark.url, bookmark.title)} | |
| > | |
| <div className="flex items-start justify-between"> | |
| <div className="flex-1 min-w-0"> | |
| <div className="text-2xl mb-2">{bookmark.favicon}</div> | |
| <p className={`font-semibold text-lg truncate ${ | |
| theme === 'dark' ? 'text-white' : 'text-gray-900' | |
| }`}>{bookmark.title}</p> | |
| <p className={`text-sm mt-1 truncate ${ | |
| theme === 'dark' ? 'text-gray-400' : 'text-gray-600' | |
| }`}>{new URL(bookmark.url).hostname}</p> | |
| </div> | |
| <button | |
| onClick={(e) => { | |
| e.stopPropagation(); | |
| removeBookmark(bookmark.id); | |
| }} | |
| className={`p-2 rounded-xl opacity-0 group-hover:opacity-100 transition-all ${ | |
| theme === 'dark' ? 'bg-gray-700 text-gray-400' : 'bg-gray-300 text-gray-600' | |
| } hover:bg-red-500 hover:text-white`} | |
| > | |
| <IconX className="w-4 h-4" /> | |
| </button> | |
| </div> | |
| </div> | |
| ))} | |
| </div> | |
| )} | |
| </div> | |
| ); | |
| const DownloadsView = () => ( | |
| <div className="p-8"> | |
| <h1 className="text-5xl font-black bg-gradient-to-r from-green-400 to-cyan-400 bg-clip-text text-transparent mb-8"> | |
| Downloads | |
| </h1> | |
| {downloads.length === 0 ? ( | |
| <div className="text-center py-16"> | |
| <IconDownload className="w-16 h-16 mx-auto mb-4 text-gray-400" /> | |
| <h3 className={`text-2xl font-bold mb-2 ${theme === 'dark' ? 'text-gray-300' : 'text-gray-700'}`}> | |
| No downloads yet | |
| </h3> | |
| <p className={`${theme === 'dark' ? 'text-gray-400' : 'text-gray-600'}`}> | |
| Your downloaded files will appear here | |
| </p> | |
| </div> | |
| ) : ( | |
| <div className="space-y-4"> | |
| {downloads.map(download => ( | |
| <div key={download.id} className="p-6 rounded-2xl glass-ui"> | |
| <div className="flex items-center justify-between"> | |
| <div className="flex items-center space-x-4"> | |
| <IconDownload className="w-8 h-8 text-green-400" /> | |
| <div> | |
| <h3 className={`font-semibold ${theme === 'dark' ? 'text-white' : 'text-gray-900'}`}> | |
| {download.filename} | |
| </h3> | |
| <p className={`text-sm ${theme === 'dark' ? 'text-gray-400' : 'text-gray-600'}`}> | |
| {download.status === 'completed' ? 'Completed' : 'Downloading...'} | |
| </p> | |
| </div> | |
| </div> | |
| <div className="flex items-center space-x-4"> | |
| <div className="w-32 bg-gray-500/20 rounded-full h-2"> | |
| <div | |
| className="h-2 rounded-full bg-gradient-to-r from-green-500 to-cyan-500 transition-all duration-300" | |
| style={{ width: `${download.progress}%` }} | |
| ></div> | |
| </div> | |
| <span className={`text-sm font-mono ${ | |
| theme === 'dark' ? 'text-gray-400' : 'text-gray-600' | |
| }`}> | |
| {download.progress}% | |
| </span> | |
| </div> | |
| </div> | |
| </div> | |
| ))} | |
| </div> | |
| )} | |
| </div> | |
| ); | |
| const MainContent = () => ( | |
| <div className="flex-1 w-full h-full overflow-y-auto"> | |
| {activePage === 'dashboard' && <EnhancedDashboard />} | |
| {activePage === 'web' && <WebView />} | |
| {activePage === 'tabs' && <EnhancedTabGridView />} | |
| {activePage === 'bookmarks' && <BookmarksView />} | |
| {activePage === 'downloads' && <DownloadsView />} | |
| {activePage === 'developer' && <EnhancedDeveloperView />} | |
| {activePage === 'ai' && <AIStudioView />} | |
| </div> | |
| ); | |
| // ... (Other view components like EnhancedTabGridView, EnhancedDeveloperView, AIStudioView remain similar but updated) | |
| const Copilot = () => ( | |
| <div | |
| className={`fixed top-0 right-0 h-full w-full max-w-md z-50 transform transition-all duration-500 ease-out ${ | |
| isCopilotOpen ? 'translate-x-0' : 'translate-x-full' | |
| }`} | |
| > | |
| <div className="flex flex-col h-full glass-ui-copilot border-l border-blue-500/30 shadow-2xl shadow-black/50 backdrop-blur-xl"> | |
| <div className={`flex items-center justify-between p-6 border-b ${ | |
| theme === 'dark' ? 'border-gray-500/20' : 'border-gray-300/30' | |
| } flex-shrink-0`}> | |
| <div className="flex items-center space-x-3"> | |
| <div className="p-2 rounded-xl bg-gradient-to-br from-blue-500 to-purple-500"> | |
| <IconSparkles className="w-6 h-6 text-white" /> | |
| </div> | |
| <div> | |
| <h2 className={`text-xl font-bold ${theme === 'dark' ? 'text-white' : 'text-gray-900'}`}> | |
| Nova Copilot | |
| </h2> | |
| <p className={`text-sm ${theme === 'dark' ? 'text-blue-300' : 'text-blue-600'}`}> | |
| {isAiThinking ? 'Thinking...' : 'Always learning'} | |
| </p> | |
| </div> | |
| </div> | |
| <button | |
| onClick={() => setIsCopilotOpen(false)} | |
| className={`p-2 rounded-xl transition-all ${ | |
| theme === 'dark' | |
| ? 'text-gray-400 hover:bg-gray-700/50 hover:text-white' | |
| : 'text-gray-600 hover:bg-gray-300/50 hover:text-gray-900' | |
| }`} | |
| > | |
| <IconX className="w-5 h-5" /> | |
| </button> | |
| </div> | |
| <div className="flex-1 p-6 space-y-6 overflow-y-auto"> | |
| {copilotMessages.map((msg, i) => ( | |
| <div key={i} className={`flex ${msg.from === 'user' ? 'justify-end' : 'justify-start'}`}> | |
| <div | |
| className={`max-w-xs px-6 py-4 rounded-3xl transition-all duration-300 ${ | |
| msg.from === 'user' | |
| ? 'bg-gradient-to-br from-blue-500 to-cyan-500 text-white rounded-br-lg shadow-lg' | |
| : (theme === 'dark' | |
| ? 'bg-gray-700/70 text-gray-200 rounded-bl-lg border border-gray-600/50' | |
| : 'bg-gray-200/70 text-gray-800 rounded-bl-lg border border-gray-300/50') | |
| } group hover:scale-105 cursor-pointer`} | |
| > | |
| <div className="flex items-center space-x-2 mb-2"> | |
| {msg.from === 'ai' && ( | |
| <div className="w-6 h-6 rounded-full bg-gradient-to-br from-purple-500 to-pink-500 flex items-center justify-center"> | |
| <IconSparkles className="w-3 h-3 text-white" /> | |
| </div> | |
| )} | |
| <span className={`text-xs font-semibold ${ | |
| msg.from === 'user' ? 'text-blue-100' : (theme === 'dark' ? 'text-purple-300' : 'text-purple-600') | |
| }`}> | |
| {msg.from === 'user' ? 'You' : 'Nova AI'} | |
| </span> | |
| </div> | |
| <p className="text-sm leading-relaxed whitespace-pre-wrap">{msg.text}</p> | |
| </div> | |
| </div> | |
| ))} | |
| {isAiThinking && ( | |
| <div className="flex justify-start"> | |
| <div className={`max-w-xs px-6 py-4 rounded-3xl rounded-bl-lg ${ | |
| theme === 'dark' ? 'bg-gray-700/70' : 'bg-gray-200/70' | |
| } border ${theme === 'dark' ? 'border-gray-600/50' : 'border-gray-300/50'}`}> | |
| <div className="flex items-center space-x-2"> | |
| <div className="flex space-x-1"> | |
| <div className="w-2 h-2 bg-blue-400 rounded-full animate-bounce"></div> | |
| <div className="w-2 h-2 bg-blue-400 rounded-full animate-bounce" style={{ animationDelay: '0.1s' }}></div> | |
| <div className="w-2 h-2 bg-blue-400 rounded-full animate-bounce" style={{ animationDelay: '0.2s' }}></div> | |
| </div> | |
| <span className="text-sm text-blue-400">Nova is thinking...</span> | |
| </div> | |
| </div> | |
| </div> | |
| )} | |
| <div ref={copilotMessagesEndRef} /> | |
| </div> | |
| <form onSubmit={(e) => { | |
| e.preventDefault(); | |
| if (copilotInput.trim()) { | |
| setCopilotMessages(prev => [...prev, { from: 'user', text: copilotInput }]); | |
| setCopilotInput(''); | |
| setIsAiThinking(true); | |
| setTimeout(() => { | |
| setCopilotMessages(prev => [...prev, { | |
| from: 'ai', | |
| text: "I'm an AI assistant in NovaBrowse. In a real implementation, I would connect to services like Gemini API to provide intelligent responses to your queries about browsing, searching, and productivity." | |
| }]); | |
| setIsAiThinking(false); | |
| }, 2000); | |
| } | |
| }} className={`p-6 border-t ${ | |
| theme === 'dark' ? 'border-gray-500/20' : 'border-gray-300/30' | |
| } flex-shrink-0`}> | |
| <div className="flex items-center space-x-3"> | |
| <div className="flex-1 rounded-2xl glass-ui-inset p-1"> | |
| <input | |
| type="text" | |
| value={copilotInput} | |
| onChange={(e) => setCopilotInput(e.target.value)} | |
| placeholder="Ask Nova anything..." | |
| className={`w-full px-4 py-3 bg-transparent ${ | |
| theme === 'dark' ? 'text-white placeholder-gray-400' : 'text-black placeholder-gray-500' | |
| } focus:outline-none text-lg`} | |
| disabled={isAiThinking} | |
| /> | |
| </div> | |
| <button | |
| type="submit" | |
| className={`p-4 rounded-2xl transition-all duration-300 ${ | |
| isAiThinking || !copilotInput.trim() | |
| ? 'bg-gray-500/20 text-gray-500 cursor-not-allowed' | |
| : 'bg-gradient-to-br from-blue-500 to-purple-500 text-white hover:scale-105 shadow-lg' | |
| }`} | |
| disabled={isAiThinking || !copilotInput.trim()} | |
| > | |
| <IconSend className="w-5 h-5" /> | |
| </button> | |
| </div> | |
| </form> | |
| </div> | |
| </div> | |
| ); | |
| return ( | |
| <> | |
| <style>{` | |
| @import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;700&family=Nunito+Sans:wght@400;700&family=Orbitron:wght@500;700;900&family=Poppins:wght@500;700;900&display=swap'); | |
| body { | |
| font-family: 'Inter', sans-serif; | |
| margin: 0; | |
| padding: 0; | |
| overflow: hidden; | |
| } | |
| .font-heading { | |
| font-family: 'Orbitron', sans-serif; | |
| } | |
| .font-body { | |
| font-family: 'Nunito Sans', sans-serif; | |
| } | |
| .font-mono { | |
| font-family: 'Orbitron', sans-serif; | |
| } | |
| /* Enhanced Glassmorphism */ | |
| .glass-ui { | |
| background: ${theme === 'dark' ? 'rgba(30, 30, 40, 0.4)' : 'rgba(255, 255, 255, 0.3)'}; | |
| backdrop-filter: blur(20px) saturate(180%); | |
| -webkit-backdrop-filter: blur(20px) saturate(180%); | |
| border: 1px solid ${theme === 'dark' ? 'rgba(255, 255, 255, 0.125)' : 'rgba(255, 255, 255, 0.3)'}; | |
| box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1); | |
| } | |
| .glass-ui-light { | |
| background: ${theme === 'dark' ? 'rgba(40, 40, 50, 0.3)' : 'rgba(255, 255, 255, 0.4)'}; | |
| backdrop-filter: blur(12px) saturate(160%); | |
| -webkit-backdrop-filter: blur(12px) saturate(160%); | |
| border: 1px solid ${theme === 'dark' ? 'rgba(255, 255, 255, 0.1)' : 'rgba(255, 255, 255, 0.2)'}; | |
| } | |
| .glass-ui-top { | |
| background: ${theme === 'dark' ? 'rgba(23, 23, 33, 0.6)' : 'rgba(240, 240, 255, 0.5)'}; | |
| backdrop-filter: blur(24px) saturate(200%); | |
| -webkit-backdrop-filter: blur(24px) saturate(200%); | |
| } | |
| .glass-ui-copilot { | |
| background: ${theme === 'dark' ? 'rgba(15, 15, 25, 0.85)' : 'rgba(230, 230, 245, 0.9)'}; | |
| backdrop-filter: blur(32px) saturate(200%); | |
| -webkit-backdrop-filter: blur(32px) saturate(200%); | |
| } | |
| .glass-ui-inset { | |
| background: ${theme === 'dark' ? 'rgba(0, 0, 0, 0.4)' : 'rgba(255, 255, 255, 0.4)'}; | |
| box-shadow: inset 0 2px 8px rgba(0,0,0,0.1); | |
| } | |
| /* Enhanced Animations */ | |
| @keyframes fadeInUp { | |
| from { | |
| opacity: 0; | |
| transform: translateY(30px); | |
| } | |
| to { | |
| opacity: 1; | |
| transform: translateY(0); | |
| } | |
| } | |
| .animate-fade-in-up { | |
| animation: fadeInUp 0.6s cubic-bezier(0.25, 0.46, 0.45, 0.94) forwards; | |
| opacity: 0; | |
| } | |
| @keyframes gradient { | |
| 0% { background-position: 0% 50%; } | |
| 50% { background-position: 100% 50%; } | |
| 100% { background-position: 0% 50%; } | |
| } | |
| .animate-gradient { | |
| background-size: 200% 200%; | |
| animation: gradient 6s ease infinite; | |
| } | |
| @keyframes pulseGlow { | |
| 0%, 100% { | |
| box-shadow: 0 0 20px rgba(59, 130, 246, 0.4); | |
| } | |
| 50% { | |
| box-shadow: 0 0 40px rgba(59, 130, 246, 0.8); | |
| } | |
| } | |
| .animate-pulse-glow { | |
| animation: pulseGlow 2s infinite; | |
| } | |
| .hover-3d-tilt { | |
| transform-style: preserve-3d; | |
| transition: transform 0.5s cubic-bezier(0.25, 0.46, 0.45, 0.94); | |
| } | |
| .hover-3d-tilt:hover { | |
| transform: perspective(1000px) rotateX(5deg) rotateY(5deg) scale(1.02); | |
| } | |
| .neon-button-glow { | |
| box-shadow: 0 0 20px rgba(59, 130, 246, 0.6), 0 0 40px rgba(59, 130, 246, 0.3); | |
| } | |
| .neon-focus-glow:focus-within { | |
| box-shadow: 0 0 30px rgba(59, 130, 246, 0.8), 0 0 60px rgba(59, 130, 246, 0.4); | |
| border-color: rgba(59, 130, 246, 0.8); | |
| } | |
| /* Custom scrollbar */ | |
| ::-webkit-scrollbar { | |
| width: 8px; | |
| } | |
| ::-webkit-scrollbar-track { | |
| background: ${theme === 'dark' ? '#1a1a2e' : '#f1f1f1'}; | |
| border-radius: 4px; | |
| } | |
| ::-webkit-scrollbar-thumb { | |
| background: linear-gradient(to bottom, #3b82f6, #8b5cf6); | |
| border-radius: 4px; | |
| } | |
| ::-webkit-scrollbar-thumb:hover { | |
| background: linear-gradient(to bottom, #2563eb, #7c3aed); | |
| } | |
| ::selection { | |
| background: rgba(59, 130, 246, 0.3); | |
| } | |
| `}</style> | |
| <div className={`flex w-full h-screen overflow-hidden transition-colors duration-500 ${ | |
| theme === 'dark' ? 'text-white bg-gray-900' : 'text-gray-900 bg-gray-100' | |
| }`}> | |
| <div className="fixed inset-0 -z-10"> | |
| <div className={`absolute inset-0 transition-all duration-1000 ${ | |
| theme === 'dark' | |
| ? 'bg-gradient-to-br from-gray-900 via-[#0a0a2a] to-[#1a0033]' | |
| : 'bg-gradient-to-br from-blue-50 via-purple-50 to-cyan-50' | |
| }`}></div> | |
| <div className={`absolute inset-0 opacity-30 ${ | |
| theme === 'dark' | |
| ? 'bg-[radial-gradient(ellipse_at_center,_var(--tw-gradient-stops))] from-blue-900/20 via-transparent to-transparent' | |
| : 'bg-[radial-gradient(ellipse_at_center,_var(--tw-gradient-stops))] from-blue-200/30 via-transparent to-transparent' | |
| }`}></div> | |
| </div> | |
| <Sidebar /> | |
| <main className="relative flex flex-col flex-1 h-full overflow-hidden"> | |
| <TabStrip /> | |
| <AddressBar /> | |
| <div className="relative flex-1 w-full h-full overflow-hidden"> | |
| <MainContent /> | |
| </div> | |
| <Copilot /> | |
| </main> | |
| </div> | |
| </> | |
| ); | |
| } |