// DOM Elements
const searchForm = document.getElementById('searchForm');
const searchInput = document.getElementById('searchInput');
const engineSelector = document.getElementById('engineSelector');
const engineButtons = document.querySelectorAll('.engine-button');
const homeBtn = document.getElementById('homeBtn');
const body = document.body;
// App State
let currentEngine = localStorage.getItem('defaultEngine') || 'google';
const APP_VERSION = '2.0.3';
// 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',
url: 'https://search.vsar.site/search',
paramName: 'q',
logo: 'https://search.vsar.site/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 Website',
icon: ``,
items: [
{
name: 'GitHub',
url: 'https://github.com',
img: 'https://github.com/favicon.ico'
},
{
name: 'LinuxDO',
url: 'https://linux.do',
img: 'https://linux.do/uploads/default/optimized/3X/9/d/9dd49731091ce8656e94433a26a3ef36062b3994_2_32x32.png'
},
{
name: '52Pojie',
url: 'https://www.52pojie.cn',
img: 'https://www.52pojie.cn/favicon.ico'
},
{
name: 'Zhihu',
url: 'https://www.zhihu.com',
img: 'https://www.zhihu.com/favicon.ico'
},
{
name: 'V2EX',
url: 'https://v2ex.com',
img: 'https://v2ex.com/favicon.ico'
}
]
},
tools: {
name: 'Common Tools',
icon: ``,
items: [
{
name: 'ChatGPT',
url: 'https://chat.openai.com',
img: 'https://openai.com/favicon.ico'
},
{
name: 'Grammarly',
url: 'https://www.grammarly.com',
img: 'https://static-web.grammarly.com/cms/master/public/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://ssl.gstatic.com/docs/doclist/images/drive_2022q3_32dp.png'
},
{
name: 'DeepL',
url: 'https://www.deepl.com/translator',
img: 'https://www.deepl.com/img/favicon/favicon_96.png'
}
]
},
social: {
name: 'Social Media',
icon: ``,
items: [
{
name: 'Twitter',
url: 'https://x.com',
img: 'https://abs.twimg.com/favicons/twitter.3.ico'
},
{
name: 'Instagram',
url: 'https://www.instagram.com',
img: 'https://static.cdninstagram.com/rsrc.php/y4/r/QaBlI0OZiks.ico'
},
{
name: 'LinkedIn',
url: 'https://www.linkedin.com',
img: 'https://www.linkedin.com/favicon.ico'
},
{
name: 'Discord',
url: 'https://discord.com/app',
img: 'https://cdn.prod.website-files.com/6257adef93867e50d84d30e2/6266bc493fb42d4e27bb8393_847541504914fd33810e70a0ea73177e.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);
const img = document.createElement('img');
// Prefer using local icons
img.src = `./assets/img/${key}.ico`;
img.alt = engine.name;
img.className = 'engine-logo';
// If the local icon fails to load, use the online URL.
img.onerror = function () {
this.src = engine.logo;
// If the online URL also fails to load, use the default search icon.
this.onerror = function() {
this.src = './assets/img/search.svg';
};
};
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.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';
// If the online URL icon fails to load, use the local web.svg as an alternative.
bookmarkImg.onerror = function() {
this.src = './assets/img/web.svg';
};
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');
if (content.style.display === 'none') {
content.style.display = 'grid';
if (icon) icon.style.transform = 'rotate(0deg)';
localStorage.setItem(`category_${category}`, 'open');
} else {
content.style.display = 'none';
if (icon) icon.style.transform = 'rotate(-90deg)';
localStorage.setItem(`category_${category}`, 'closed');
}
});
}
}
}
// 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() {
generateEngineButtons();
generateBookmarks();
loadDefaultEngine();
highlightSelectedEngine();
setDailyBingBackground();
setupTransparentUI();
// setupBookmarkCategories();
// displayVersionInfo();
}
// Load default engine from local storage
function loadDefaultEngine() {
const defaultEngine = localStorage.getItem('defaultEngine') || 'google';
currentEngine = defaultEngine;
}
// Set daily Bing background
function setDailyBingBackground() {
// Fetch Bing daily image
const bingImageUrl = `https://www.bing.com/HPImageArchive.aspx?format=js&idx=0&n=1`;
// Use a proxy to avoid CORS issues
fetch(`https://api.allorigins.win/get?url=${encodeURIComponent(bingImageUrl)}`)
.then(response => response.json())
.then(data => {
const bingData = JSON.parse(data.contents);
if (bingData && bingData.images && bingData.images.length > 0) {
const imageData = bingData.images[0];
const imageUrl = `https://www.bing.com${imageData.url}`;
// Set as background
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';
} else {
// Fallback to a gradient if Bing image can't be fetched
document.body.style.background = 'linear-gradient(135deg, #1a2a6c, #b21f1f, #fdbb2d)';
}
})
.catch(error => {
console.error('Error fetching Bing background:', error);
// Fallback background
document.body.style.background = 'linear-gradient(135deg, #1a2a6c, #b21f1f, #fdbb2d)';
});
}
// 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');
} else {
button.classList.remove('ring-2', 'ring-white', 'bg-white/30');
}
});
}
// Bookmark Category Collapse/Expand Function
function setupBookmarkCategories() {
const categoryHeaders = document.querySelectorAll('.category-header');
categoryHeaders.forEach(header => {
header.addEventListener('click', function () {
const category = this.getAttribute('data-category');
const content = this.nextElementSibling;
const icon = this.querySelector('.category-icon');
if (content.style.display === 'none') {
content.style.display = 'grid';
icon.style.transform = 'rotate(0deg)';
localStorage.setItem(`category_${category}`, 'open');
} else {
content.style.display = 'none';
icon.style.transform = 'rotate(-90deg)';
localStorage.setItem(`category_${category}`, 'closed');
}
});
// Initial state: expanded based on local storage or default settings.
const category = header.getAttribute('data-category');
const savedState = localStorage.getItem(`category_${category}`);
const content = header.nextElementSibling;
const icon = header.querySelector('.category-icon');
if (savedState === 'closed') {
content.style.display = 'none';
icon.style.transform = 'rotate(-90deg)';
} else {
content.style.display = 'grid';
icon.style.transform = 'rotate(0deg)';
}
icon.style.transition = 'transform 0.3s ease';
});
}
// 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();
}
});
// 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();
});