// DOM Elements const searchForm = document.getElementById('searchForm'); const searchInput = document.getElementById('searchInput'); const homeBtn = document.getElementById('homeBtn'); // App State let currentEngine = localStorage.getItem('defaultEngine') || 'google'; const APP_VERSION = '2.1.0'; // Search engine configurations const searchEngines = { google: { name: 'Google', url: 'https://www.google.com/search', paramName: 'q', logo: 'https://www.google.com/favicon.ico' }, bing: { name: 'Bing', url: 'https://www.bing.com/search', paramName: 'q', logo: 'https://www.bing.com/favicon.ico' }, baidu: { name: 'Baidu', url: 'https://www.baidu.com/s', paramName: 'wd', logo: 'https://www.baidu.com/favicon.ico' }, duckduckgo: { name: 'DuckDuckGo', url: 'https://duckduckgo.com/', paramName: 'q', logo: 'https://duckduckgo.com/favicon.ico' }, yahoo: { name: 'Yahoo', url: 'https://search.yahoo.com/search', paramName: 'q', logo: 'https://search.yahoo.com/favicon.ico' }, startpage: { name: 'StartPage', url: 'https://www.startpage.com/search', paramName: 'q', logo: 'https://www.startpage.com/favicon.ico' }, ask: { name: 'Ask', url: 'https://www.ask.com/search', paramName: 'q', logo: 'https://www.ask.com/favicon.ico' }, searxng: { name: 'SearXNG', // You can find more from https://searx.space url: 'https://opnxng.com/search', paramName: 'q', logo: 'https://opnxng.com/favicon.ico' }, feloai: { name: 'FeloAI', url: 'https://felo.ai/search', paramName: 'q', logo: 'https://felo.ai/icon.svg' }, yandex: { name: 'Yandex', url: 'https://yandex.eu/search', paramName: 'text', logo: 'https://yandex.eu/favicon.ico' }, metaso: { name: 'Metaso', url: 'https://metaso.cn/', paramName: 'q', logo: 'https://metaso.cn/favicon.ico' } }; const bookmarks = { tech: { name: 'Technical', icon: ` `, items: [ { name: 'ChatGPT', url: 'https://chatgpt.com', img: 'https://openai.com/favicon.ico' }, { name: 'Gemini', url: 'https://gemini.google.com', img: 'https://www.google.com/favicon.ico' }, { name: 'Grok', url: 'https://x.com/i/grok', img: 'https://x.com/favicon.ico' }, { name: 'MSCopilot', url: 'https://copilot.microsoft.com/', img: 'https://copilot.microsoft.com/favicon.ico' }, { name: 'MistralAI', url: 'https://chat.mistral.ai/chat', img: 'https://mistral.ai/favicon.ico' } ] }, tools: { name: 'Common Tools', icon: ` `, items: [ { name: 'Github', url: 'https://github.com', img: 'https://github.com/favicon.ico' }, { name: 'Overleaf', url: 'https://www.overleaf.com', img: 'https://www.grammarly.com/favicon.ico' }, { name: 'Notion', url: 'https://www.notion.so', img: 'https://www.notion.com/front-static/favicon.ico' }, { name: 'Google Drive', url: 'https://drive.google.com', img: 'https://drive.google.com/favicon.ico' }, { name: 'DeepL', url: 'https://www.deepl.com/translator', img: 'https://www.deepl.com/favicon.ico' } ] }, social: { name: 'Social Media', icon: ` `, items: [ { name: 'Twitter', url: 'https://x.com', img: 'https://x.com/favicon.ico' }, { name: 'Instagram', url: 'https://www.instagram.com', img: 'https://www.instagram.com/favicon.ico' }, { name: 'LinkedIn', url: 'https://www.linkedin.com', img: 'https://www.linkedin.com/favicon.ico' }, { name: 'Discord', url: 'https://discord.com/app', img: 'https://discord.com/favicon.ico' }, { name: 'Reddit', url: 'https://www.reddit.com', img: 'https://www.reddit.com/favicon.ico' } ] } }; // Generate engine buttons dynamically function generateEngineButtons() { const engineSelector = document.getElementById('engineSelector'); engineSelector.innerHTML = ''; // Clear existing buttons for (const key in searchEngines) { if (searchEngines.hasOwnProperty(key)) { const engine = searchEngines[key]; const button = document.createElement('button'); button.type = 'button'; button.className = 'engine-button px-4 py-2 flex items-center justify-center rounded-full bg-white border border-gray-300 shadow-sm hover:shadow-md transition-shadow'; button.setAttribute('data-engine', key); button.setAttribute('aria-label', `Select ${engine.name} search engine`); const img = document.createElement('img'); // Prefer using local icons img.src = `./assets/img/${key}.ico`; img.alt = engine.name; img.className = 'engine-logo'; // Set a quick timeout for icon loading (2 seconds) let iconTimeout = setTimeout(() => { if (!img.complete) { img.src = './assets/img/search.svg'; } }, 2000); // If the local icon fails to load, use the online URL. img.onerror = function () { clearTimeout(iconTimeout); this.src = engine.logo; // Set timeout for online icon too iconTimeout = setTimeout(() => { if (!this.complete) { this.src = './assets/img/search.svg'; } }, 2000); // If the online URL also fails to load, use the default search icon. this.onerror = function () { clearTimeout(iconTimeout); this.src = './assets/img/search.svg'; this.onerror = null; // Prevent infinite loop }; }; img.onload = function () { clearTimeout(iconTimeout); }; const span = document.createElement('span'); span.textContent = engine.name; button.appendChild(img); button.appendChild(span); engineSelector.appendChild(button); } } // Attach event listeners after generating buttons attachEngineButtonEvents(); } // Generate bookmark categories and items dynamically function generateBookmarks() { // Get the parent container where all bookmark categories will be added const bookmarksContainer = document.getElementById('bookmarksContainer'); if (!bookmarksContainer) { console.error("Bookmarks container not found"); return; } bookmarksContainer.innerHTML = ''; // Clear existing bookmarks // Generate each category and its bookmarks for (const categoryKey in bookmarks) { if (bookmarks.hasOwnProperty(categoryKey)) { const category = bookmarks[categoryKey]; const savedState = localStorage.getItem(`category_${categoryKey}`); // Create category section const categorySection = document.createElement('div'); categorySection.className = 'mb-4 bookmark-category'; // Create category header const categoryHeader = document.createElement('div'); categoryHeader.className = 'flex items-center cursor-pointer mb-2 category-header'; categoryHeader.setAttribute('data-category', categoryKey); categoryHeader.setAttribute('role', 'button'); categoryHeader.setAttribute('aria-expanded', savedState !== 'closed'); categoryHeader.setAttribute('aria-label', `Toggle ${category.name} bookmarks`); categoryHeader.innerHTML = category.icon; const categoryTitle = document.createElement('h3'); categoryTitle.className = 'font-medium text-gray-800'; categoryTitle.textContent = category.name; categoryHeader.appendChild(categoryTitle); // Create category content container const categoryContent = document.createElement('div'); categoryContent.className = 'grid grid-cols-2 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-6 gap-3 category-content'; categoryContent.id = `${categoryKey}Bookmarks`; // Set initial display state based on localStorage if (savedState === 'closed') { categoryContent.style.display = 'none'; // Ensure the icon is rotated properly const icon = categoryHeader.querySelector('.category-icon'); if (icon) { icon.style.transform = 'rotate(-90deg)'; icon.style.transition = 'transform 0.3s ease'; } } else { categoryContent.style.display = 'grid'; } // Add items to the category category.items.forEach(bookmark => { const bookmarkDiv = document.createElement('div'); bookmarkDiv.className = 'bg-white rounded-lg shadow-sm p-3 card-hover mini-bookmark'; const bookmarkLink = document.createElement('a'); bookmarkLink.href = bookmark.url; bookmarkLink.target = '_blank'; bookmarkLink.className = 'flex flex-col items-center justify-center'; const bookmarkImg = document.createElement('img'); bookmarkImg.src = bookmark.img; bookmarkImg.alt = bookmark.name; bookmarkImg.className = 'w-8 h-8 mb-2'; // Set a quick timeout for bookmark icon loading (2 seconds) let bookmarkTimeout = setTimeout(() => { if (!bookmarkImg.complete) { bookmarkImg.src = './assets/img/web.svg'; } }, 2000); // If the online URL icon fails to load, use the local web.svg as an alternative. bookmarkImg.onerror = function () { clearTimeout(bookmarkTimeout); this.src = './assets/img/web.svg'; this.onerror = null; // Prevent infinite loop }; bookmarkImg.onload = function () { clearTimeout(bookmarkTimeout); }; const bookmarkName = document.createElement('span'); bookmarkName.className = 'text-xs font-medium text-gray-800'; bookmarkName.textContent = bookmark.name; bookmarkLink.appendChild(bookmarkImg); bookmarkLink.appendChild(bookmarkName); bookmarkDiv.appendChild(bookmarkLink); categoryContent.appendChild(bookmarkDiv); }); // Assemble the category section categorySection.appendChild(categoryHeader); categorySection.appendChild(categoryContent); // Add the category section to the bookmarks container bookmarksContainer.appendChild(categorySection); // Add event listener directly to this category header categoryHeader.addEventListener('click', function () { const category = this.getAttribute('data-category'); const content = this.nextElementSibling; const icon = this.querySelector('.category-icon'); const isExpanded = content.style.display !== 'none'; if (isExpanded) { content.style.display = 'none'; if (icon) icon.style.transform = 'rotate(-90deg)'; this.setAttribute('aria-expanded', 'false'); localStorage.setItem(`category_${category}`, 'closed'); } else { content.style.display = 'grid'; if (icon) icon.style.transform = 'rotate(0deg)'; this.setAttribute('aria-expanded', 'true'); localStorage.setItem(`category_${category}`, 'open'); } }); } } } // Attach event listeners to engine buttons function attachEngineButtonEvents() { const engineButtons = document.querySelectorAll('.engine-button'); engineButtons.forEach(button => { button.addEventListener('click', function () { currentEngine = this.getAttribute('data-engine'); localStorage.setItem('defaultEngine', currentEngine); highlightSelectedEngine(); }); }); } // Initialize app function init() { showLoadingState(); generateEngineButtons(); generateBookmarks(); loadDefaultEngine(); highlightSelectedEngine(); setDailyBingBackground(); setupTransparentUI(); displayVersionInfo(); } // Load default engine from local storage function loadDefaultEngine() { const defaultEngine = localStorage.getItem('defaultEngine') || 'google'; currentEngine = defaultEngine; } // Set daily Bing background with CORS proxy fallback function setDailyBingBackground() { // Check if we have a cached image from today const cachedData = localStorage.getItem('bingBackgroundCache'); const today = new Date().toDateString(); if (cachedData) { try { const cache = JSON.parse(cachedData); if (cache.date === today && cache.imageUrl) { setBackgroundImage(cache.imageUrl); return; } } catch (e) { console.error('Error parsing cached background:', e); } } // Set a timeout to ensure loading state doesn't hang forever const fetchTimeout = setTimeout(() => { console.warn('Background fetch timeout, using fallback'); setFallbackBackground(); }, 5000); // 5 second timeout // Try multiple CORS proxies in sequence const corsProxies = [ 'https://api.allorigins.win/raw?url=', 'https://corsproxy.io/?', 'https://cors-anywhere.herokuapp.com/' ]; const bingImageUrl = 'https://www.bing.com/HPImageArchive.aspx?format=js&idx=0&n=1'; // Function to try fetching with different proxies async function tryFetchWithProxies(proxyIndex = 0) { if (proxyIndex >= corsProxies.length) { // All proxies failed, use high-quality fallback images clearTimeout(fetchTimeout); console.warn('All CORS proxies failed, using high-quality fallback'); setHighQualityFallback(); return; } const proxyUrl = corsProxies[proxyIndex] + encodeURIComponent(bingImageUrl); try { const response = await fetch(proxyUrl, { method: 'GET', signal: AbortSignal.timeout(3000) // 3 second timeout per proxy }); if (!response.ok) throw new Error(`HTTP ${response.status}`); const data = await response.json(); if (data && data.images && data.images.length > 0) { clearTimeout(fetchTimeout); const imageData = data.images[0]; const imageUrl = `https://www.bing.com${imageData.url}`; // Cache the image URL localStorage.setItem('bingBackgroundCache', JSON.stringify({ date: today, imageUrl: imageUrl })); setBackgroundImage(imageUrl); } else { throw new Error('Invalid response format'); } } catch (error) { console.warn(`Proxy ${proxyIndex + 1} failed:`, error.message); // Try next proxy tryFetchWithProxies(proxyIndex + 1); } } // Start trying proxies tryFetchWithProxies(); } // Helper function to set background image function setBackgroundImage(imageUrl) { const img = new Image(); img.onload = function () { document.body.style.backgroundImage = `url('${imageUrl}')`; document.body.style.backgroundSize = 'cover'; document.body.style.backgroundPosition = 'center'; document.body.style.backgroundRepeat = 'no-repeat'; document.body.style.backgroundAttachment = 'fixed'; hideLoadingState(); }; img.onerror = function () { console.warn('Image load failed, using fallback'); setHighQualityFallback(); }; img.src = imageUrl; } // Helper function to set high-quality fallback backgrounds function setHighQualityFallback() { // Array of beautiful gradient backgrounds const fallbackBackgrounds = [ 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)', 'linear-gradient(135deg, #f093fb 0%, #f5576c 100%)', 'linear-gradient(135deg, #4facfe 0%, #00f2fe 100%)', 'linear-gradient(135deg, #43e97b 0%, #38f9d7 100%)', 'linear-gradient(135deg, #fa709a 0%, #fee140 100%)', 'linear-gradient(135deg, #30cfd0 0%, #330867 100%)', 'linear-gradient(135deg, #a8edea 0%, #fed6e3 100%)', 'linear-gradient(135deg, #ff9a9e 0%, #fecfef 100%, #fecfef 100%)', 'linear-gradient(135deg, #ffecd2 0%, #fcb69f 100%)', 'linear-gradient(135deg, #ff6e7f 0%, #bfe9ff 100%)' ]; // Get day of year to consistently show same background each day const now = new Date(); const start = new Date(now.getFullYear(), 0, 0); const diff = now - start; const oneDay = 1000 * 60 * 60 * 24; const dayOfYear = Math.floor(diff / oneDay); // Select background based on day of year const selectedBackground = fallbackBackgrounds[dayOfYear % fallbackBackgrounds.length]; document.body.style.background = selectedBackground; document.body.style.backgroundSize = 'cover'; document.body.style.backgroundPosition = 'center'; document.body.style.backgroundRepeat = 'no-repeat'; document.body.style.backgroundAttachment = 'fixed'; hideLoadingState(); } // Helper function to set basic fallback background (last resort) function setFallbackBackground() { setHighQualityFallback(); } // Make UI elements transparent function setupTransparentUI() { // Add glass morphism effect to main containers const mainContainers = document.querySelectorAll('.bg-white'); mainContainers.forEach(container => { container.classList.remove('bg-white'); container.classList.add('glass-morphism'); }); // Add glass morphism to bookmark cards const bookmarkCards = document.querySelectorAll('.card-hover'); bookmarkCards.forEach(card => { card.classList.add('glass-bookmark'); }); // Style the search form for transparency const searchContainer = document.querySelector('#searchForm').parentElement; searchContainer.classList.add('search-container-glass'); // Style search input for transparency const searchInputElem = document.querySelector('#searchInput'); searchInputElem.classList.add('glass-input'); // Apply glass effect to engine buttons const engineButtonElements = document.querySelectorAll('.engine-button'); engineButtonElements.forEach(btn => { btn.classList.add('glass-button'); }); // Add CSS rules for glass morphism const style = document.createElement('style'); style.textContent = ` .glass-morphism { background: rgba(255, 255, 255, 0.2) !important; backdrop-filter: blur(8px) !important; -webkit-backdrop-filter: blur(8px) !important; border: 1px solid rgba(255, 255, 255, 0.3) !important; box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1) !important; } .glass-bookmark { background: rgba(255, 255, 255, 0.25) !important; backdrop-filter: blur(10px) !important; -webkit-backdrop-filter: blur(10px) !important; border: 1px solid rgba(255, 255, 255, 0.3) !important; box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1) !important; transition: all 0.3s ease !important; } .glass-bookmark:hover { background: rgba(255, 255, 255, 0.35) !important; transform: translateY(-5px) !important; box-shadow: 0 15px 35px rgba(0, 0, 0, 0.2) !important; } .search-container-glass { background: rgba(255, 255, 255, 0.15) !important; backdrop-filter: blur(12px) !important; -webkit-backdrop-filter: blur(12px) !important; border-radius: 16px !important; border: 1px solid rgba(255, 255, 255, 0.2) !important; box-shadow: 0 4px 24px rgba(0, 0, 0, 0.1) !important; padding: 24px !important; } .glass-input { background: rgba(255, 255, 255, 0.2) !important; border: 1px solid rgba(255, 255, 255, 0.3) !important; color: rgba(0, 0, 0, 0.8) !important; backdrop-filter: blur(4px) !important; -webkit-backdrop-filter: blur(4px) !important; } .glass-input::placeholder { color: rgba(0, 0, 0, 0.5) !important; } .glass-button { background: rgba(255, 255, 255, 0.25) !important; backdrop-filter: blur(4px) !important; -webkit-backdrop-filter: blur(4px) !important; border: 1px solid rgba(255, 255, 255, 0.3) !important; transition: all 0.3s ease !important; } .glass-button:hover { background: rgba(255, 255, 255, 0.4) !important; transform: translateY(-2px) !important; box-shadow: 0 5px 15px rgba(0, 0, 0, 0.1) !important; } .container { max-width: 900px !important; margin: 0 auto !important; } body { font-family: 'Inter', sans-serif !important; color: white !important; text-shadow: 0 1px 2px rgba(0, 0, 0, 0.2) !important; height: 100vh !important; overflow-x: hidden !important; } h1, h2, h3, h4, h5, h6 { color: white !important; text-shadow: 0 2px 4px rgba(0, 0, 0, 0.3) !important; } a { color: rgba(255, 255, 255, 0.8) !important; transition: color 0.3s ease !important; } a:hover { color: white !important; text-shadow: 0 0 8px rgba(255, 255, 255, 0.5) !important; } .text-gray-600, .text-gray-700, .text-gray-800 { color: rgba(255, 255, 255, 0.9) !important; } .mini-bookmark { background: rgba(255, 255, 255, 0.2) !important; backdrop-filter: blur(10px) !important; -webkit-backdrop-filter: blur(10px) !important; border: 1px solid rgba(255, 255, 255, 0.3) !important; box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1) !important; transition: all 0.3s ease !important; } .mini-bookmark:hover { background: rgba(255, 255, 255, 0.3) !important; transform: translateY(-3px) !important; box-shadow: 0 8px 20px rgba(0, 0, 0, 0.15) !important; } .category-header { padding: 8px 12px; border-radius: 8px; background: rgba(255, 255, 255, 0.15) !important; backdrop-filter: blur(8px) !important; -webkit-backdrop-filter: blur(8px) !important; transition: all 0.3s ease !important; } .category-header:hover { background: rgba(255, 255, 255, 0.25) !important; } .category-content { transition: all 0.3s ease; } `; document.head.appendChild(style); } // Highlight the currently selected search engine function highlightSelectedEngine() { const engineButtons = document.querySelectorAll('.engine-button'); engineButtons.forEach(button => { const engine = button.getAttribute('data-engine'); if (engine === currentEngine) { button.classList.add('ring-2', 'ring-white', 'bg-white/30'); button.setAttribute('aria-pressed', 'true'); } else { button.classList.remove('ring-2', 'ring-white', 'bg-white/30'); button.setAttribute('aria-pressed', 'false'); } }); } // Add loading state functions function showLoadingState() { const loadingDiv = document.createElement('div'); loadingDiv.id = 'backgroundLoader'; loadingDiv.style.cssText = ` position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: linear-gradient(135deg, #1a2a6c, #b21f1f, #fdbb2d); display: flex; align-items: center; justify-content: center; z-index: 9999; transition: opacity 0.5s ease; `; loadingDiv.innerHTML = `

Loading...

`; document.body.appendChild(loadingDiv); } function hideLoadingState() { const loader = document.getElementById('backgroundLoader'); if (loader) { loader.style.opacity = '0'; setTimeout(() => { loader.remove(); }, 500); } } // Add a function to display the version number function displayVersionInfo() { const footer = document.createElement('div'); footer.className = 'fixed bottom-2 right-2 flex items-center bg-black/20 backdrop-filter backdrop-blur-sm px-3 py-1 rounded-full hover:bg-black/30 transition-all'; const versionSpan = document.createElement('span'); versionSpan.className = 'text-xs text-white/70 font-medium'; versionSpan.textContent = `v${APP_VERSION}`; versionSpan.style.textShadow = '0 1px 2px rgba(0,0,0,0.3)'; const githubLink = document.createElement('a'); githubLink.href = 'https://github.com/vsar/SimpleSearch'; githubLink.target = '_blank'; githubLink.className = 'ml-2 text-white/80 hover:text-white transition-colors'; githubLink.title = 'View on GitHub'; githubLink.style.textShadow = '0 1px 2px rgba(0,0,0,0.3)'; const githubIcon = document.createElement('span'); githubIcon.innerHTML = ` `; githubLink.appendChild(githubIcon); footer.appendChild(versionSpan); footer.appendChild(githubLink); document.body.appendChild(footer); } // Handle search form submission searchForm.addEventListener('submit', function (e) { e.preventDefault(); const query = searchInput.value.trim(); if (!query) return; const engine = searchEngines[currentEngine]; const url = new URL(engine.url); url.searchParams.append(engine.paramName, query); window.open(url.toString(), '_blank'); }); // Home button functionality if (homeBtn) { homeBtn.addEventListener('click', function () { window.location.reload(); }); } // Add keyboard shortcuts for enhanced UX document.addEventListener('keydown', function (e) { // Alt+S to focus search input if (e.altKey && e.key === 's') { e.preventDefault(); searchInput.focus(); } // Number keys (1-9) to select search engines if (e.altKey && e.key >= '1' && e.key <= '9') { e.preventDefault(); const engineIndex = parseInt(e.key) - 1; const engineButtons = document.querySelectorAll('.engine-button'); if (engineButtons[engineIndex]) { engineButtons[engineIndex].click(); engineButtons[engineIndex].focus(); } } // Escape to clear search input if (e.key === 'Escape' && document.activeElement === searchInput) { searchInput.value = ''; } }); // Prevent form resubmission on page refresh if (window.history.replaceState) { window.history.replaceState(null, null, window.location.href); } // Initialize the application when DOM is fully loaded document.addEventListener('DOMContentLoaded', function () { init(); });