From fed70824e1c5dc410bcf4b00cf1298d51fdf6729 Mon Sep 17 00:00:00 2001 From: JSTAR <78903532+DevJSTAR@users.noreply.github.com> Date: Sun, 10 Nov 2024 22:31:43 +0500 Subject: [PATCH] Add files via upload --- images/favicon.ico | Bin 0 -> 11980 bytes images/icon128.png | Bin 0 -> 2208 bytes images/icon16.png | Bin 0 -> 479 bytes images/icon48.png | Bin 0 -> 978 bytes index.html | 271 +++++++++++++++++ js/main.js | 98 +++++++ js/notifications.js | 131 +++++++++ js/onboarding.js | 73 +++++ js/search.js | 47 +++ js/settings.js | 336 +++++++++++++++++++++ js/shortcuts.js | 255 ++++++++++++++++ js/storage.js | 44 +++ manifest.json | 36 +++ style.css | 699 ++++++++++++++++++++++++++++++++++++++++++++ 14 files changed, 1990 insertions(+) create mode 100644 images/favicon.ico create mode 100644 images/icon128.png create mode 100644 images/icon16.png create mode 100644 images/icon48.png create mode 100644 index.html create mode 100644 js/main.js create mode 100644 js/notifications.js create mode 100644 js/onboarding.js create mode 100644 js/search.js create mode 100644 js/settings.js create mode 100644 js/shortcuts.js create mode 100644 js/storage.js create mode 100644 manifest.json create mode 100644 style.css diff --git a/images/favicon.ico b/images/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..a7a5a021052e7b181a6836ed2b40d2a6c902f57e GIT binary patch literal 11980 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 literal 0 HcmV?d00001 diff --git a/images/icon128.png b/images/icon128.png new file mode 100644 index 0000000000000000000000000000000000000000..dd2c7c8c831c9c49197d4a0eea310973d02fd8cf GIT binary patch literal 2208 zcmdT^`9IWa8~)CiNVK8FmdY*}5lO`)YnGhQf*EE=C%f#%UQ!61(p$1rNOGK_M5aL! z$}$)lOvae8OxBrX-eMrwWqF-W3xyV3A=q*jmb!EY$-9t^zQ-=Gr&U^T!L5BB;V z>_uw#_#1Yhbh|(d>OnE60kOZqXOvbCCY z=QQ`K@xX25_~*JM8J<`q$@Up1v@!z^^*_2!sm`V^&G5#1*Iwn$Tr(wLok#gx zQGOt~3`_mBA`tMzZ|=|iAh2m;oH#ooTCcwd5efL>+Ps;O0Zd@4KSQm|bTWhUesJua| zx=FivaSWx^_wMohqiD(io>~83t~hS$-nAbg=7T>5+1bA1>%W9-mhiguaI6h+k_k}SF+1! z`0=6ZR>OIi34GPU$~<2zmZZ3S&-5fwi0h?^)D5;U*lh6Kqxq+))BZYrPvWPRW_cTI zQM}zqxaH8m$JIOx8Kv1K8BWIvt0#$v|k7uMYX zvY%F8{uOdx%PNh;X)0yJ*bt*m4b#Us3p8%v;Q`QZpxMyyPblqfXN@H&=;NiB^KKr1 zQPB`fB7wQ$_&8U&I98n8mLvfzRuBzyWxC)D_y$G))1$1SSyi>ABH}wG8~L^ZfZGi?OD||Vz^sjCsYr8q|TilLf5Q0JXpCq zchjrQ@1_4x#5nGdm3EZU);f6u`pMJTQ%T_u#XDJ;vu43N^*;r7jXNw4mp;b7HN|yr z-;kg!<5%dymJ`tfA8=U2fdhJH^B+9zxFwbCIm(%GGO?;u@@Lxb-LKeO7Ptnfl4_HI zbV1XM$8=RH^xA5GBNy+g7mw7o#d**7 zT}VIK3~zwWRB)k(5RhQ`Xf$Q$=sTMhi6tsktO}OgjCzv4F6( zmYcp6I0|pg(<~&f5?7B#Z-3xwxu-y+5VMzE+#;Kj{2@ZjE76GRxn%^Zv`Ce_Nv zJujNKEzcMOS`|u2@>gr)8RuG3CJbZle z$iLtbRCFO7K-k#d`x-YF$1C?^xAlfW66Bs_rk#>nTMS>(Pi{|8_4WvnX-A%GI}ziJ ztua0L4qLlpSMcuHu)DX1Jg!}pAF&-{kRbxAhax3<->0wCW>!3#C3<)KtXO3LO zR19NzOYNU(n1zm~A?Rw^_AahZnsOfE{dcBY*0VzHX}_OteHl{i9xb7n4vH<-)JH0dM$UyRK+pJi*-aeTt<2qav$S44>;o_OUX2P?pGg T;dy2S&@Bv}u6{1-oD!M<$wCF# literal 0 HcmV?d00001 diff --git a/images/icon48.png b/images/icon48.png new file mode 100644 index 0000000000000000000000000000000000000000..f2b65455c4efa6022f1ddd474cdeff97ff6e94e0 GIT binary patch literal 978 zcmeAS@N?(olHy`uVBq!ia0vp^1|ZDA3?vioaBc-sjKx9jPK-BC>eK@{oCO|{#S9GG z!XV7ZFl&wkP(f0FPlzi}GEVh)tkS6%5IP;BcsfoE#En%w5vzP^Zq4o~hYp1rq+r9YZ%`=}~d+vh(2`}hCXkH3f4y~!{-pQLs6z{=M@zW$w( zdpAz`%0JJ``&Ty0AKV>gd76U`0qJN5xw#YoP>IM z%0F0mPW@=UiZ9ab*y|m0Sy+^kdcIHG;ng$!<4;DOFZXI3ZXBEA0ssmKaukB_-!&P@`ThG2}ys4h-u=9No+mtg;@^_Ez-|aa2(m>ZglMpgC1|6 z!eiaW(xdfi9cFCRZVQr*ueC7wVernic$Erm03@=^t@wvX2ii{CIcIAUnDuBrHtXFOz0S#gM`TB>Q Z;k8VP)XI}8hQLI@;OXk;vd$@?2>__}ECv7o literal 0 HcmV?d00001 diff --git a/index.html b/index.html new file mode 100644 index 0000000..a388811 --- /dev/null +++ b/index.html @@ -0,0 +1,271 @@ + + + + + + New JSTAR Tab + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + \ No newline at end of file diff --git a/js/main.js b/js/main.js new file mode 100644 index 0000000..65f7023 --- /dev/null +++ b/js/main.js @@ -0,0 +1,98 @@ +// Update greeting based on time of day and user settings +function updateGreeting() { + const greeting = document.getElementById('greeting'); + if (!greeting) return; + + const hour = new Date().getHours(); + const isAnonymous = Storage.get('anonymousMode') || false; + const userName = isAnonymous ? + (Storage.get('anonymousName') || anonymousNames.generate()) : + (Storage.get('userName') || 'Friend'); + + let timeGreeting = 'Hello'; + if (hour >= 5 && hour < 12) timeGreeting = 'Good Morning'; + else if (hour >= 12 && hour < 17) timeGreeting = 'Good Afternoon'; + else if (hour >= 17 && hour < 20) timeGreeting = 'Good Evening'; + else timeGreeting = 'Good Night'; + + greeting.textContent = `${timeGreeting}, ${userName}!`; + greeting.style.opacity = '0'; + setTimeout(() => { + greeting.style.opacity = '1'; + }, 100); +} + +// Set up event listeners for modal interactions +function initModalHandlers() { + const modals = document.querySelectorAll('.modal'); + + modals.forEach(modal => { + modal.addEventListener('click', (e) => { + if (e.target === modal) { + closeModal(modal); + } + }); + + const modalContent = modal.querySelector('.modal-content'); + if (modalContent) { + modalContent.addEventListener('click', (e) => { + e.stopPropagation(); + }); + } + }); +} + +// Open modal with animation +function openModal(modal) { + if (!modal) return; + modal.classList.remove('hidden'); + requestAnimationFrame(() => { + modal.classList.add('active'); + }); +} + +// Close modal with animation +function closeModal(modal) { + if (!modal) return; + modal.classList.remove('active'); + setTimeout(() => { + modal.classList.add('hidden'); + }, 300); +} + +// Initialize application +document.addEventListener('DOMContentLoaded', () => { + // Apply visibility settings + ['greeting', 'search', 'shortcuts', 'addShortcut'].forEach(element => { + const isVisible = Storage.get(`show_${element}`); + if (isVisible === false) { + const elementNode = document.getElementById(element === 'search' ? 'search-container' : element); + if (elementNode) elementNode.style.display = 'none'; + } + }); + + // Start onboarding or show main content + if (!Storage.get('onboardingComplete')) { + onboarding.start(); + } else { + document.getElementById('main-content').classList.remove('hidden'); + } + + // Initialize features + search.init(); + shortcuts.init(); + settings.init(); + initModalHandlers(); + + // Set up greeting + updateGreeting(); + setInterval(updateGreeting, 60000); + + // Settings button handler + const settingsButton = document.getElementById('settings-button'); + const settingsModal = document.getElementById('settings-modal'); + + settingsButton.addEventListener('click', () => { + openModal(settingsModal); + }); +}); \ No newline at end of file diff --git a/js/notifications.js b/js/notifications.js new file mode 100644 index 0000000..0a2b762 --- /dev/null +++ b/js/notifications.js @@ -0,0 +1,131 @@ +// Notification System Class +class NotificationSystem { + constructor() { + this.container = document.getElementById('notification-container'); + this.notifications = new Map(); + } + + // Display a new notification + show(message, type = 'info', duration = 3000) { + const id = Date.now().toString(); + const notification = document.createElement('div'); + notification.className = `notification notification-${type}`; + + // Create notification elements + const icon = this.createIcon(type); + const content = this.createContent(message); + const closeBtn = this.createCloseButton(id); + const progress = this.createProgressBar(type); + + // Assemble notification + notification.appendChild(icon); + notification.appendChild(content); + notification.appendChild(closeBtn); + notification.appendChild(progress); + + this.container.appendChild(notification); + + // Set removal timer + setTimeout(() => this.remove(id), duration); + + // Store notification reference + this.notifications.set(id, { + element: notification, + duration + }); + + this.updateProgress(id); + + return id; + } + + // Remove a notification + remove(id) { + const notification = this.notifications.get(id); + if (notification) { + notification.element.style.animation = 'slideOutRight 0.3s cubic-bezier(0.16, 1, 0.3, 1)'; + setTimeout(() => { + notification.element.remove(); + this.notifications.delete(id); + }, 300); + } + } + + // Update progress bar + updateProgress(id) { + const notification = this.notifications.get(id); + if (notification) { + const progress = notification.element.querySelector('.notification-progress'); + const startTime = Date.now(); + + const update = () => { + const elapsed = Date.now() - startTime; + const percent = 100 - (elapsed / notification.duration * 100); + + if (percent > 0) { + progress.style.width = `${percent}%`; + requestAnimationFrame(update); + } + }; + + requestAnimationFrame(update); + } + } + + // Helper methods for creating notification elements + createIcon(type) { + const icon = document.createElement('i'); + switch(type) { + case 'success': + icon.className = 'fas fa-check-circle'; + icon.style.color = 'var(--success-color, #4caf50)'; + break; + case 'error': + icon.className = 'fas fa-times-circle'; + icon.style.color = 'var(--error-color, #f44336)'; + break; + case 'info': + default: + icon.className = 'fas fa-info-circle'; + icon.style.color = 'var(--info-color, #2196f3)'; + break; + } + return icon; + } + + createContent(message) { + const content = document.createElement('div'); + content.className = 'notification-content'; + content.textContent = message; + return content; + } + + createCloseButton(id) { + const closeBtn = document.createElement('button'); + closeBtn.className = 'notification-close'; + closeBtn.innerHTML = ''; + closeBtn.onclick = () => this.remove(id); + return closeBtn; + } + + createProgressBar(type) { + const progress = document.createElement('div'); + progress.className = 'notification-progress'; + switch(type) { + case 'success': + progress.style.background = 'var(--success-color, #4caf50)'; + break; + case 'error': + progress.style.background = 'var(--error-color, #f44336)'; + break; + case 'info': + default: + progress.style.background = 'var(--info-color, #2196f3)'; + break; + } + return progress; + } +} + +// Initialize the notification system +const notifications = new NotificationSystem(); \ No newline at end of file diff --git a/js/onboarding.js b/js/onboarding.js new file mode 100644 index 0000000..c092e1c --- /dev/null +++ b/js/onboarding.js @@ -0,0 +1,73 @@ +// Onboarding module +const onboarding = { + // Check if onboarding is complete + isComplete: () => { + return Storage.get('onboardingComplete') === true; + }, + + // Start the onboarding process + start: () => { + const modal = document.getElementById('onboarding-modal'); + const mainContent = document.getElementById('main-content'); + + if (!onboarding.isComplete()) { + modal.classList.remove('hidden'); + modal.classList.add('active'); + mainContent.classList.add('hidden'); + + document.getElementById('next-step-btn').addEventListener('click', () => onboarding.nextStep(1)); + document.getElementById('complete-setup-btn').addEventListener('click', onboarding.complete); + + // Set up search engine selection + const engines = document.querySelectorAll('.search-engine-option'); + engines.forEach(engine => { + engine.addEventListener('click', () => { + engines.forEach(e => e.classList.remove('selected')); + engine.classList.add('selected'); + }); + }); + } else { + modal.classList.add('hidden'); + mainContent.classList.remove('hidden'); + } + }, + + // Move to the next step in onboarding + nextStep: (currentStep) => { + const currentStepEl = document.querySelector(`[data-step="${currentStep}"]`); + const nextStepEl = document.querySelector(`[data-step="${currentStep + 1}"]`); + const name = document.getElementById('user-name').value.trim(); + + if (!name) { + notifications.show('Please enter your name', 'error'); + return; + } + + Storage.set('userName', name); + + currentStepEl.classList.add('hidden'); + nextStepEl.classList.remove('hidden'); + nextStepEl.classList.add('visible'); + }, + + // Complete the onboarding process + complete: () => { + const selectedEngine = document.querySelector('.search-engine-option.selected'); + if (!selectedEngine) { + notifications.show('Please select a search engine', 'error'); + return; + } + + const searchEngine = selectedEngine.dataset.engine; + Storage.set('searchEngine', searchEngine); + Storage.set('onboardingComplete', true); + + const modal = document.getElementById('onboarding-modal'); + const mainContent = document.getElementById('main-content'); + + modal.classList.add('hidden'); + mainContent.classList.remove('hidden'); + notifications.show('Welcome to your new tab! 👋', 'success'); + updateGreeting(); + } +}; \ No newline at end of file diff --git a/js/search.js b/js/search.js new file mode 100644 index 0000000..7998479 --- /dev/null +++ b/js/search.js @@ -0,0 +1,47 @@ +const search = { + // Supported search engines and their URLs + engines: { + google: 'https://www.google.com/search?q=', + bing: 'https://www.bing.com/search?q=', + duckduckgo: 'https://duckduckgo.com/?q=', + brave: 'https://search.brave.com/search?q=', + qwant: 'https://www.qwant.com/?q=', + searxng: 'https://searx.org/search?q=' + }, + + // Perform search using selected engine + perform: () => { + const searchBar = document.getElementById('search-bar'); + const query = searchBar.value.trim(); + const engine = Storage.get('searchEngine') || 'google'; + + if (query) { + const searchUrl = search.engines[engine] + encodeURIComponent(query); + window.location.href = searchUrl; + } + }, + + // Initialize search functionality + init: () => { + const searchBar = document.getElementById('search-bar'); + const searchButton = document.getElementById('search-button'); + + searchBar.addEventListener('keypress', (e) => { + if (e.key === 'Enter') { + search.perform(); + } + }); + + searchButton.addEventListener('click', search.perform); + + // Global keyboard shortcut to focus search bar + document.addEventListener('keydown', (e) => { + if (e.key === '/' && + !['INPUT', 'TEXTAREA'].includes(document.activeElement.tagName) && + window.getSelection().toString() === '') { + e.preventDefault(); + searchBar.focus(); + } + }); + } +}; \ No newline at end of file diff --git a/js/settings.js b/js/settings.js new file mode 100644 index 0000000..2f79f4b --- /dev/null +++ b/js/settings.js @@ -0,0 +1,336 @@ +// Anonymous name generator +const anonymousNames = { + adjectives: ['Hidden', 'Secret', 'Mystery', 'Shadow', 'Unknown', 'Silent', 'Stealth', 'Phantom', 'Ghost', 'Anon'], + nouns: [' User', ' Visitor', ' Guest', ' Agent', ' Entity', ' Person', ' Browser', ' Explorer', ' Wanderer', ' Navigator'], + + generate: function() { + const adjective = this.adjectives[Math.floor(Math.random() * this.adjectives.length)]; + const noun = this.nouns[Math.floor(Math.random() * this.nouns.length)]; + return `${adjective}${noun}`; + } +}; + +// Main settings object +const settings = { + // Toggle between light and dark themes + toggleTheme: () => { + const currentTheme = document.body.getAttribute('data-theme'); + const newTheme = currentTheme === 'dark' ? 'light' : 'dark'; + document.body.setAttribute('data-theme', newTheme); + Storage.set('theme', newTheme); + + const themeIcon = document.querySelector('#toggle-theme i'); + themeIcon.className = `fas fa-${newTheme === 'dark' ? 'sun' : 'moon'}`; + }, + + // Toggle anonymous mode + toggleAnonymousMode: () => { + const isAnonymous = Storage.get('anonymousMode') || false; + Storage.set('anonymousMode', !isAnonymous); + + if (!isAnonymous) { + const randomName = anonymousNames.generate(); + Storage.set('anonymousName', randomName); + notifications.show('Anonymous mode enabled!', 'info'); + } else { + Storage.remove('anonymousName'); + notifications.show('Anonymous mode disabled!', 'info'); + } + + shortcuts.render(); + updateGreeting(); + }, + + // Update the search engine + updateSearchEngine: (engine) => { + Storage.set('searchEngine', engine); + notifications.show('Search engine updated successfully!', 'success'); + }, + + // Update visibility of UI elements + updateVisibility: () => { + const elements = { + greeting: { + id: 'greeting', + toggle: 'toggle-greeting', + functions: ['updateGreeting'], + name: 'Greeting' + }, + search: { + id: 'search-container', + toggle: 'toggle-search', + functions: ['search.init', 'search.perform'], + name: 'Search bar' + }, + shortcuts: { + id: 'shortcuts-grid', + toggle: 'toggle-shortcuts', + functions: ['shortcuts.init', 'shortcuts.render'], + name: 'Shortcuts' + }, + addShortcut: { + id: 'add-shortcut', + toggle: 'toggle-add-shortcut', + functions: [], + name: 'Add shortcut button' + } + }; + + Object.entries(elements).forEach(([key, element]) => { + const isVisible = Storage.get(`show_${key}`); + if (isVisible === null) Storage.set(`show_${key}`, true); + + const toggle = document.getElementById(element.toggle); + const elementNode = document.getElementById(element.id); + + if (toggle && elementNode) { + toggle.checked = isVisible !== false; + if (isVisible === false) { + elementNode.style.visibility = 'hidden'; + elementNode.style.opacity = '0'; + elementNode.style.position = 'absolute'; + elementNode.style.pointerEvents = 'none'; + } + + toggle.addEventListener('change', (e) => { + const isChecked = e.target.checked; + Storage.set(`show_${key}`, isChecked); + + if (isChecked) { + elementNode.style.visibility = 'visible'; + elementNode.style.opacity = '1'; + elementNode.style.position = 'relative'; + elementNode.style.pointerEvents = 'auto'; + } else { + elementNode.style.visibility = 'hidden'; + elementNode.style.opacity = '0'; + elementNode.style.position = 'absolute'; + elementNode.style.pointerEvents = 'none'; + } + + if (key === 'shortcuts') { + const addShortcutBtn = document.getElementById('add-shortcut'); + const addShortcutVisible = Storage.get('show_addShortcut') !== false; + + if (addShortcutBtn && !isChecked && !addShortcutVisible) { + addShortcutBtn.style.visibility = 'hidden'; + addShortcutBtn.style.opacity = '0'; + addShortcutBtn.style.position = 'absolute'; + addShortcutBtn.style.pointerEvents = 'none'; + } else if (addShortcutBtn && addShortcutVisible) { + addShortcutBtn.style.visibility = 'visible'; + addShortcutBtn.style.opacity = '1'; + addShortcutBtn.style.position = 'relative'; + addShortcutBtn.style.pointerEvents = 'auto'; + } + } + + notifications.show( + `${element.name} ${isChecked ? 'shown' : 'hidden'}`, + isChecked ? 'success' : 'info' + ); + }); + } + }); + }, + + // Initialize settings + init: () => { + const settingsButton = document.getElementById('settings-button'); + const settingsModal = document.getElementById('settings-modal'); + const closeSettings = document.getElementById('close-settings'); + + settingsButton.addEventListener('click', (e) => { + e.stopPropagation(); + const userName = Storage.get('userName') || ''; + const isAnonymous = Storage.get('anonymousMode') || false; + const currentEngine = Storage.get('searchEngine') || 'google'; + + document.getElementById('settings-name').value = userName; + document.getElementById('toggle-anonymous').checked = isAnonymous; + document.getElementById('search-engine-select').value = currentEngine; + + settingsModal.classList.remove('hidden'); + settingsModal.classList.add('active'); + }); + + closeSettings.addEventListener('click', () => { + settingsModal.classList.remove('active'); + setTimeout(() => { + settingsModal.classList.add('hidden'); + }, 300); + }); + + const themeToggle = document.getElementById('toggle-theme'); + themeToggle.addEventListener('click', settings.toggleTheme); + + const anonymousToggle = document.getElementById('toggle-anonymous'); + anonymousToggle.addEventListener('change', settings.toggleAnonymousMode); + + const searchEngineSelect = document.getElementById('search-engine-select'); + searchEngineSelect.addEventListener('change', (e) => { + settings.updateSearchEngine(e.target.value); + }); + + const nameInput = document.getElementById('settings-name'); + nameInput.addEventListener('change', (e) => { + const newName = e.target.value.trim(); + if (newName) { + Storage.set('userName', newName); + updateGreeting(); + notifications.show('Name updated successfully', 'success'); + } + }); + + const savedTheme = Storage.get('theme') || 'light'; + document.body.setAttribute('data-theme', savedTheme); + const themeIcon = document.querySelector('#toggle-theme i'); + themeIcon.className = `fas fa-${savedTheme === 'dark' ? 'sun' : 'moon'}`; + + settings.initDataManagement(); + settings.updateVisibility(); + } +}; + +// Initialize data management +settings.initDataManagement = () => { + const exportBtn = document.getElementById('export-data'); + const importBtn = document.getElementById('import-data'); + const resetBtn = document.getElementById('reset-data'); + const fileInput = document.getElementById('import-file'); + + // Export Data + exportBtn.addEventListener('click', () => { + try { + const data = { + settings: { + theme: Storage.get('theme'), + userName: Storage.get('userName'), + anonymousMode: Storage.get('anonymousMode'), + anonymousName: Storage.get('anonymousName'), + searchEngine: Storage.get('searchEngine'), + show_greeting: Storage.get('show_greeting'), + show_search: Storage.get('show_search'), + show_shortcuts: Storage.get('show_shortcuts'), + show_addShortcut: Storage.get('show_addShortcut') + }, + shortcuts: Storage.get('shortcuts') || [] + }; + + const blob = new Blob([JSON.stringify(data, null, 2)], { type: 'application/json' }); + const url = URL.createObjectURL(blob); + const a = document.createElement('a'); + a.href = url; + a.download = 'jstar-tab-backup.json'; + document.body.appendChild(a); + a.click(); + document.body.removeChild(a); + URL.revokeObjectURL(url); + + notifications.show('Data exported successfully!', 'success'); + } catch (error) { + notifications.show('Failed to export data', 'error'); + console.error('Export error:', error); + } + }); + + // Import Data + importBtn.addEventListener('click', () => fileInput.click()); + + fileInput.addEventListener('change', (e) => { + const file = e.target.files[0]; + if (!file) return; + + const reader = new FileReader(); + reader.onload = (event) => { + try { + const data = JSON.parse(event.target.result); + + if (!data.shortcuts || !data.settings) { + throw new Error('Invalid backup file format'); + } + + Object.entries(data.settings).forEach(([key, value]) => { + Storage.set(key, value); + }); + + Storage.set('shortcuts', data.shortcuts); + + fileInput.value = ''; + + ['greeting', 'search', 'shortcuts', 'addShortcut'].forEach(element => { + const isVisible = data.settings[`show_${element}`]; + const elementNode = document.getElementById(element === 'search' ? 'search-container' : element); + const toggle = document.getElementById(`toggle-${element}`); + + if (elementNode && toggle) { + toggle.checked = isVisible !== false; + if (isVisible === false) { + elementNode.style.visibility = 'hidden'; + elementNode.style.opacity = '0'; + elementNode.style.position = 'absolute'; + elementNode.style.pointerEvents = 'none'; + } else { + elementNode.style.visibility = 'visible'; + elementNode.style.opacity = '1'; + elementNode.style.position = 'relative'; + elementNode.style.pointerEvents = 'auto'; + } + } + }); + + const shortcutsVisible = data.settings.show_shortcuts !== false; + const addShortcutVisible = data.settings.show_addShortcut !== false; + const addShortcutBtn = document.getElementById('add-shortcut'); + + if (addShortcutBtn) { + if (!shortcutsVisible && !addShortcutVisible) { + addShortcutBtn.style.visibility = 'hidden'; + addShortcutBtn.style.opacity = '0'; + addShortcutBtn.style.position = 'absolute'; + addShortcutBtn.style.pointerEvents = 'none'; + } else if (addShortcutVisible) { + addShortcutBtn.style.visibility = 'visible'; + addShortcutBtn.style.opacity = '1'; + addShortcutBtn.style.position = 'relative'; + addShortcutBtn.style.pointerEvents = 'auto'; + } + } + + shortcuts.render(); + updateGreeting(); + document.body.setAttribute('data-theme', data.settings.theme || 'light'); + + notifications.show('Data imported successfully!', 'success'); + } catch (error) { + notifications.show('Failed to import data: Invalid file format', 'error'); + console.error('Import error:', error); + } + }; + + reader.onerror = () => { + notifications.show('Failed to read file', 'error'); + }; + + reader.readAsText(file); + }); + + // Reset Data + resetBtn.addEventListener('click', () => { + const confirmReset = confirm('Are you sure you want to reset all data? This action cannot be undone.'); + + if (confirmReset) { + try { + Storage.clear(); + closeModal(document.getElementById('settings-modal')); + notifications.show('All data has been reset', 'success'); + setTimeout(() => { + window.location.reload(); + }, 1000); + } catch (error) { + notifications.show('Failed to reset data', 'error'); + console.error('Reset error:', error); + } + } + }); +}; \ No newline at end of file diff --git a/js/shortcuts.js b/js/shortcuts.js new file mode 100644 index 0000000..91005b4 --- /dev/null +++ b/js/shortcuts.js @@ -0,0 +1,255 @@ +const shortcuts = { + MAX_SHORTCUTS: 12, + + // Validate and format URL + validateAndFormatUrl: (url) => { + if (!/^https?:\/\//i.test(url)) { + url = 'https://' + url; + } + + try { + new URL(url); + return url; + } catch (e) { + return false; + } + }, + + // Add new shortcut + add: (url, name) => { + const currentShortcuts = Storage.get('shortcuts') || []; + if (currentShortcuts.length >= shortcuts.MAX_SHORTCUTS) { + notifications.show('Maximum shortcuts limit (12) reached', 'error'); + return; + } + + const formattedUrl = shortcuts.validateAndFormatUrl(url); + if (!formattedUrl) { + notifications.show('Invalid URL format', 'error'); + return; + } + + currentShortcuts.push({ url: formattedUrl, name }); + Storage.set('shortcuts', currentShortcuts); + shortcuts.render(); + }, + + // Remove shortcut + remove: (index) => { + const currentShortcuts = Storage.get('shortcuts') || []; + currentShortcuts.splice(index, 1); + Storage.set('shortcuts', currentShortcuts); + shortcuts.render(); + notifications.show('Shortcut removed!', 'success'); + }, + + // Edit existing shortcut + edit: (index, newUrl, newName) => { + const currentShortcuts = Storage.get('shortcuts') || []; + currentShortcuts[index] = { url: newUrl, name: newName }; + Storage.set('shortcuts', currentShortcuts); + shortcuts.render(); + notifications.show('Shortcut updated!', 'success'); + }, + + // Show context menu for shortcut + showContextMenu: (e, index) => { + e.preventDefault(); + const menu = document.getElementById('context-menu'); + const rect = e.target.getBoundingClientRect(); + + menu.style.top = `${e.clientY}px`; + menu.style.left = `${e.clientX}px`; + menu.classList.remove('hidden'); + menu.dataset.shortcutIndex = index; + + const handleClickOutside = (event) => { + if (!menu.contains(event.target)) { + menu.classList.add('hidden'); + document.removeEventListener('click', handleClickOutside); + } + }; + + setTimeout(() => { + document.addEventListener('click', handleClickOutside); + }, 0); + }, + + // Render shortcuts grid + render: () => { + const grid = document.getElementById('shortcuts-grid'); + const currentShortcuts = Storage.get('shortcuts') || []; + const isAnonymous = Storage.get('anonymousMode') || false; + + grid.innerHTML = ''; + + currentShortcuts.forEach((shortcut, index) => { + const element = document.createElement('div'); + element.className = `shortcut ${isAnonymous ? 'blurred' : ''}`; + + const icon = document.createElement('img'); + icon.src = `https://www.google.com/s2/favicons?domain=${shortcut.url}&sz=64`; + icon.alt = shortcut.name; + + const name = document.createElement('span'); + name.textContent = shortcut.name; + + element.appendChild(icon); + element.appendChild(name); + + element.addEventListener('click', (e) => { + if (e.ctrlKey) { + window.open(shortcut.url, '_blank'); + } else { + window.location.href = shortcut.url; + } + }); + + element.addEventListener('contextmenu', (e) => { + e.preventDefault(); + const menu = document.getElementById('context-menu'); + + menu.style.top = `${e.pageY}px`; + menu.style.left = `${e.pageX}px`; + menu.classList.remove('hidden'); + menu.dataset.shortcutIndex = index; + + const closeMenu = (event) => { + if (!menu.contains(event.target)) { + menu.classList.add('hidden'); + document.removeEventListener('click', closeMenu); + } + }; + + setTimeout(() => { + document.addEventListener('click', closeMenu); + }, 0); + }); + + grid.appendChild(element); + }); + }, + + // Initialize shortcuts functionality + init: () => { + const addShortcutButton = document.getElementById('add-shortcut'); + if (addShortcutButton) { + addShortcutButton.addEventListener('click', (e) => { + e.stopPropagation(); + const currentShortcuts = Storage.get('shortcuts') || []; + + if (currentShortcuts.length >= shortcuts.MAX_SHORTCUTS) { + notifications.show('Maximum shortcuts limit (12) reached!', 'error'); + return; + } + + const modal = document.getElementById('add-shortcut-modal'); + if (modal) { + modal.classList.remove('hidden'); + modal.classList.add('active'); + + const urlInput = document.getElementById('shortcut-url'); + const nameInput = document.getElementById('shortcut-name'); + + const saveShortcutButton = document.getElementById('save-shortcut'); + if (saveShortcutButton) { + saveShortcutButton.onclick = () => { + const url = urlInput.value.trim(); + const name = nameInput.value.trim(); + + if (url && name) { + try { + new URL(url); + shortcuts.add(url, name); + modal.classList.remove('active'); + setTimeout(() => { + modal.classList.add('hidden'); + urlInput.value = ''; + nameInput.value = ''; + }, 300); + notifications.show('Shortcut added successfully!', 'success'); + } catch (e) { + notifications.show('Invalid URL format', 'error'); + } + } + }; + } + + const cancelShortcutButton = document.getElementById('cancel-shortcut'); + if (cancelShortcutButton) { + cancelShortcutButton.onclick = () => { + modal.classList.remove('active'); + setTimeout(() => { + modal.classList.add('hidden'); + urlInput.value = ''; + nameInput.value = ''; + }, 300); + }; + } + } + }); + } + + // Context menu actions + const contextMenu = document.getElementById('context-menu'); + if (contextMenu) { + contextMenu.addEventListener('click', (e) => { + const action = e.target.closest('.context-menu-item')?.dataset.action; + const index = parseInt(contextMenu.dataset.shortcutIndex); + + if (action === 'edit') { + const currentShortcuts = Storage.get('shortcuts') || []; + const shortcut = currentShortcuts[index]; + const modal = document.getElementById('edit-shortcut-modal'); + + if (modal) { + const urlInput = document.getElementById('edit-shortcut-url'); + const nameInput = document.getElementById('edit-shortcut-name'); + + urlInput.value = shortcut.url; + nameInput.value = shortcut.name; + + modal.classList.remove('hidden'); + modal.classList.add('active'); + + const saveButton = document.getElementById('save-edit-shortcut'); + const closeButton = document.getElementById('close-edit-shortcut'); + const cancelButton = document.getElementById('cancel-edit-shortcut'); + + const closeModal = () => { + modal.classList.remove('active'); + setTimeout(() => { + modal.classList.add('hidden'); + }, 300); + }; + + const handleSave = () => { + const newUrl = urlInput.value.trim(); + const newName = nameInput.value.trim(); + + if (newUrl && newName) { + const formattedUrl = shortcuts.validateAndFormatUrl(newUrl); + if (formattedUrl) { + shortcuts.edit(index, formattedUrl, newName); + closeModal(); + } else { + notifications.show('Invalid URL format', 'error'); + } + } + }; + + saveButton.onclick = handleSave; + closeButton.onclick = closeModal; + cancelButton.onclick = closeModal; + } + } else if (action === 'delete') { + shortcuts.remove(index); + } + + contextMenu.classList.add('hidden'); + }); + } + + shortcuts.render(); + } +}; \ No newline at end of file diff --git a/js/storage.js b/js/storage.js new file mode 100644 index 0000000..2f451e8 --- /dev/null +++ b/js/storage.js @@ -0,0 +1,44 @@ +/** + * Storage utility object for managing localStorage operations + * All methods handle JSON parsing/stringifying and error cases + */ +const Storage = { + // Retrieve and parse stored value + get: (key) => { + try { + return JSON.parse(localStorage.getItem(key)); + } catch (e) { + return null; + } + }, + + // Store value as JSON string + set: (key, value) => { + try { + localStorage.setItem(key, JSON.stringify(value)); + return true; + } catch (e) { + return false; + } + }, + + // Delete specific key + remove: (key) => { + try { + localStorage.removeItem(key); + return true; + } catch (e) { + return false; + } + }, + + // Remove all stored data + clear: () => { + try { + localStorage.clear(); + return true; + } catch (e) { + return false; + } + } +}; \ No newline at end of file diff --git a/manifest.json b/manifest.json new file mode 100644 index 0000000..23c719f --- /dev/null +++ b/manifest.json @@ -0,0 +1,36 @@ +{ + "manifest_version": 3, + "name": "JSTAR Tab", + "version": "2.0.0", + "description": "JSTAR Tab is a sleek, customizable new tab extension with personalized greetings, shortcuts, anonymous mode, search engine settings, themes, data management, and more, for an enhanced browsing experience.", + "chrome_url_overrides": { + "newtab": "index.html" + }, + "permissions": [ + "storage", + "favicon" + ], + "icons": { + "16": "images/icon16.png", + "48": "images/icon48.png", + "128": "images/icon128.png" + }, + "action": { + "default_title": "New JSTAR Tab", + "default_icon": { + "16": "images/icon16.png", + "48": "images/icon48.png", + "128": "images/icon128.png" + } + }, + "author": "JSTAR", + "homepage_url": "https://github.com/DevJSTAR/JSTAR-Tab", + "update_url": "https://github.com/DevJSTAR/JSTAR-Tab/releases/latest", + "web_accessible_resources": [{ + "resources": [ + "fonts/*", + "images/*" + ], + "matches": [""] + }] +} \ No newline at end of file diff --git a/style.css b/style.css new file mode 100644 index 0000000..b0c1935 --- /dev/null +++ b/style.css @@ -0,0 +1,699 @@ +/* Root variables for light theme */ +:root { + --primary: #f5f5f5; + --primary-hover: #e0e0e0; + --background: #ffffff; + --surface: #fafafa; + --border: #eaeaea; + --text: #1a1a1a; + --text-secondary: #666666; + --shadow: rgba(0, 0, 0, 0.08); + --modal-backdrop: rgba(0, 0, 0, 0.5); + --scrollbar-thumb: #e0e0e0; + --scrollbar-track: #f5f5f5; + --modal-background: #ffffff; +} + +/* Dark theme variables */ +[data-theme="dark"] { + --primary: #1a1a1a; + --primary-hover: #2a2a2a; + --background: #000000; + --surface: #111111; + --border: #333333; + --text: #ffffff; + --text-secondary: #999999; + --shadow: rgba(0, 0, 0, 0.3); + --modal-backdrop: rgba(255, 255, 255, 0.1); + --scrollbar-thumb: #333333; + --scrollbar-track: #1a1a1a; + --modal-background: #1a1a1a; +} + +/* Dark theme button styles */ +[data-theme="dark"] .btn-primary { + background: var(--primary-hover); + color: var(--text); +} + +[data-theme="dark"] .btn-primary:hover { + background: #3a3a3a; +} + +/* Global styles */ +* { + margin: 0; + padding: 0; + box-sizing: border-box; + font-family: 'Inter', -apple-system, sans-serif; +} + +body { + background: var(--background); + color: var(--text); + min-height: 100vh; +} + +.hidden { + display: none !important; +} + +/* Modal styles */ +.modal { + position: fixed; + inset: 0; + display: flex; + align-items: center; + justify-content: center; + z-index: 1000; + opacity: 0; + visibility: hidden; + transition: all 0.3s ease; + background: var(--modal-backdrop); +} + +.modal-actions { + display: flex; + gap: 1rem; + margin-top: 1rem; +} + +.modal-actions button { + flex: 1; +} + +.modal.active { + opacity: 1; + visibility: visible; +} + +.modal-content { + background: var(--modal-background); + border-radius: 24px; + padding: 2rem; + width: 90%; + max-width: 480px; + transform: translateY(20px); + opacity: 0; + transition: all 0.3s ease; + box-shadow: 0 10px 25px var(--shadow); +} + +/* Modal animations */ +@keyframes slideIn { + from { + opacity: 0; + transform: translateY(20px); + } + to { + opacity: 1; + transform: translateY(0); + } +} + +.modal.active .modal-content { + transform: translateY(0); + opacity: 1; + animation: modalSlideIn 0.3s ease forwards; +} + +@keyframes modalSlideIn { + from { + opacity: 0; + transform: translateY(20px); + } + to { + opacity: 1; + transform: translateY(0); + } +} + +/* Form element styles */ +select { + width: 100%; + padding: 0.75rem 1rem; + border: 2px solid var(--border); + border-radius: 12px; + background: var(--surface); + color: var(--text); + font-size: 1rem; + cursor: pointer; + appearance: none; + background-image: url("data:image/svg+xml;charset=US-ASCII,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%22292.4%22%20height%3D%22292.4%22%3E%3Cpath%20fill%3D%22%23666666%22%20d%3D%22M287%2069.4a17.6%2017.6%200%200%200-13-5.4H18.4c-5%200-9.3%201.8-12.9%205.4A17.6%2017.6%200%200%200%200%2082.2c0%205%201.8%209.3%205.4%2012.9l128%20127.9c3.6%203.6%207.8%205.4%2012.8%205.4s9.2-1.8%2012.8-5.4L287%2095c3.5-3.5%205.4-7.8%205.4-12.8%200-5-1.9-9.2-5.4-12.8z%22%2F%3E%3C%2Fsvg%3E"); + background-repeat: no-repeat; + background-position: right 1rem center; + background-size: 0.65em auto; +} + +select:focus { + outline: none; + border-color: var(--text); +} + +/* Step styles */ +.step h2 { + font-size: 1.75rem; + margin-bottom: 1rem; + font-weight: 700; +} + +.step p { + color: var(--text-secondary); + margin-bottom: 2rem; +} + +.input-group { + margin-bottom: 1.5rem; +} + +input[type="text"] { + width: 100%; + padding: 1rem; + border: 2px solid var(--border); + border-radius: 12px; + background: var(--surface); + color: var(--text); + font-size: 1rem; +} + +input[type="text"]:focus { + outline: none; + border-color: var(--text); +} + +/* Main content styles */ +.center-container { + min-height: 100vh; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + gap: 3rem; + padding: 2rem; + position: relative; +} + +#greeting { + font-size: 2rem; + font-weight: 700; + margin: 0; + opacity: 0; + animation: fadeIn 0.5s ease forwards; + height: 48px; + visibility: hidden; +} + +#greeting:not([style*="visibility"]) { + visibility: visible; +} + +.search-container { + width: 100%; + max-width: 640px; + height: 56px; + visibility: visible; +} + +/* Search engine options */ +.search-engine-options { + display: grid; + grid-template-columns: repeat(3, 1fr); + gap: 1rem; + margin: 2rem 0; +} + +.search-engine-option { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + gap: 0.5rem; + padding: 1.5rem; + border-radius: 16px; + background: var(--surface); + cursor: pointer; + transition: all 0.2s ease; + box-shadow: 0 2px 8px var(--shadow); + border: 2px solid transparent; + width: 100%; + height: 100%; + box-sizing: border-box; +} + +.search-engine-option:hover { + transform: translateY(-2px); + box-shadow: 0 4px 12px var(--shadow); +} + +.search-engine-option.selected { + background: var(--surface); + border: 2px solid var(--border); +} + +.search-engine-option img { + width: 32px; + height: 32px; + border-radius: 4px; +} + +.search-engine-option span { + font-size: 0.875rem; + color: var(--text); + text-align: center; +} + +/* Search bar styles */ +.search-wrapper { + position: relative; +} + +#search-bar { + width: 100%; + padding: 1.25rem 1.5rem; + border: none; + border-radius: 16px; + background: var(--surface); + color: var(--text); + font-size: 1rem; + box-shadow: 0 4px 24px var(--shadow); +} + +.search-icon { + position: absolute; + right: 1.5rem; + top: 50%; + transform: translateY(-50%); + background: none; + border: none; + color: var(--text); + cursor: pointer; + padding: 0.5rem; +} + +/* Shortcuts styles */ +.shortcuts-container { + width: 100%; + max-width: 640px; +} + +#add-shortcut { + width: 40px; + height: 40px; + border-radius: 50%; + padding: 0; + display: flex; + align-items: center; + justify-content: center; + margin: 0 auto; + background: var(--primary); + color: var(--text); + border: none; + cursor: pointer; + transition: background 0.2s ease; +} + +#add-shortcut:hover { + background: var(--primary-hover); +} + +#shortcuts-grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(80px, 1fr)); + gap: 1rem; + margin-bottom: 1.5rem; +} + +.shortcut { + background: var(--surface); + border-radius: 12px; + padding: 1rem; + cursor: pointer; + transition: all 0.2s ease; + display: flex; + flex-direction: column; + align-items: center; + gap: 0.5rem; + box-shadow: 0 2px 10px var(--shadow); +} + +.shortcut:hover { + transform: translateY(-2px); + box-shadow: 0 4px 15px var(--shadow); +} + +.shortcut img { + width: 24px; + height: 24px; + border-radius: 6px; +} + +.shortcut span { + font-size: 0.75rem; + text-align: center; + color: var(--text-secondary); + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + max-width: 100%; +} + +.shortcut.blurred { + filter: blur(4px); + opacity: 0.7; +} + +.shortcut.blurred:hover { + filter: blur(0); + opacity: 1; +} + +/* Settings styles */ +.settings-button { + position: fixed; + bottom: 2rem; + right: 2rem; + width: 48px; + height: 48px; + border-radius: 50%; + background: var(--surface); + border: none; + cursor: pointer; + display: flex; + align-items: center; + justify-content: center; + font-size: 1.25rem; + color: var(--text); + box-shadow: 0 4px 24px var(--shadow); +} + +.settings-button:hover { + transform: translateY(-2px); + box-shadow: 0 8px 32px var(--shadow); +} + +.settings-panel { + max-height: 80vh; + overflow-y: auto; + scrollbar-width: thin; + scrollbar-color: var(--scrollbar-thumb) var(--scrollbar-track); +} + +.settings-panel::-webkit-scrollbar { + width: 8px; +} + +.settings-panel::-webkit-scrollbar-track { + background: var(--scrollbar-track); + border-radius: 0 24px 24px 0; +} + +.settings-panel::-webkit-scrollbar-thumb { + background-color: var(--scrollbar-thumb); + border-radius: 4px; +} + +.modal-header { + display: flex; + align-items: center; + justify-content: space-between; + margin-bottom: 2rem; +} + +.modal-header h2 { + font-size: 1.5rem; + font-weight: 600; +} + +.btn-icon { + background: none; + border: none; + color: var(--text); + cursor: pointer; + padding: 0.5rem; + font-size: 1.25rem; + opacity: 0.6; +} + +.btn-icon:hover { + opacity: 1; +} + +.settings-section { + padding: 1.5rem 0; + border-bottom: 1px solid var(--border); +} + +.settings-section:last-child { + border-bottom: none; +} + +.settings-section h3 { + margin-bottom: 1.5rem; + font-weight: 600; +} + +.setting-item { + display: flex; + align-items: center; + justify-content: space-between; + padding: 0.75rem 0; +} + +.setting-label { + font-weight: 500; +} + +.data-management-buttons { + display: flex; + flex-direction: column; + gap: 1rem; +} + +.btn-danger { + background: #dc3545 !important; + color: white !important; +} + +.btn-danger:hover { + background: #c82333 !important; +} + +/* Toggle switch styles */ +.toggle { + position: relative; + display: inline-block; + width: 50px; + height: 26px; +} + +.toggle input { + opacity: 0; + width: 0; + height: 0; +} + +.toggle-slider { + position: absolute; + cursor: pointer; + top: 0; + left: 0; + right: 0; + bottom: 0; + background-color: var(--border); + border-radius: 34px; +} + +.toggle-slider:before { + position: absolute; + content: ""; + height: 20px; + width: 20px; + left: 3px; + bottom: 3px; + background-color: var(--background); + border-radius: 50%; +} + +input:checked + .toggle-slider { + background-color: var(--text); +} + +input:checked + .toggle-slider:before { + transform: translateX(24px); +} + +/* Button styles */ +.btn-primary { + background: var(--primary); + color: var(--text); + border: none; + padding: 1rem 2rem; + border-radius: 12px; + font-size: 1rem; + font-weight: 500; + cursor: pointer; + transition: all 0.2s ease; + width: 100%; +} + +.btn-primary:hover { + background: var(--primary-hover); +} + +/* Notification styles */ +#notification-container { + position: fixed; + top: 1.5rem; + right: 1.5rem; + z-index: 1000; + display: flex; + flex-direction: column; + gap: 1rem; + pointer-events: none; +} + +.notification { + background: var(--surface); + color: var(--text); + padding: 1.25rem 1.5rem; + border-radius: 16px; + box-shadow: 0 8px 32px rgba(var(--shadow-rgb), 0.1); + display: flex; + align-items: center; + gap: 1.25rem; + pointer-events: auto; + position: relative; + overflow: hidden; + animation: slideInRight 0.4s cubic-bezier(0.16, 1, 0.3, 1), fadeIn 0.4s ease; + max-width: 420px; + border: 1px solid rgba(var(--text-rgb), 0.1); +} + +.notification-content { + flex: 1; + font-size: 0.95rem; + line-height: 1.5; +} + +.notification-close { + background: none; + border: none; + color: var(--text); + cursor: pointer; + padding: 0.5rem; + opacity: 0.6; +} + +.notification-close:hover { + opacity: 1; + background: rgba(var(--text-rgb), 0.1); +} + +.notification-progress { + position: absolute; + bottom: 0; + left: 0; + height: 3px; + background: var(--primary); + opacity: 0.8; + transition: width 0.1s linear; +} + +@keyframes slideInRight { + from { + opacity: 0; + transform: translateX(100%); + } + to { + opacity: 1; + transform: translateX(0); + } +} + +/* About section styles */ +.about-content { + text-align: center; + color: var(--text-secondary); + padding: 1rem 0; +} + +.about-content a { + color: var(--text); + text-decoration: none; +} + +.about-content a:hover { + text-decoration: underline; +} + +.about-content .version { + font-size: 1.1rem; + font-weight: 600; + color: var(--text); + margin-bottom: 0.5rem; +} + +.about-content .description { + margin-bottom: 1rem; +} + +.about-content .features { + font-size: 0.9rem; + margin-bottom: 1rem; +} + +.about-content .copyright { + font-size: 0.8rem; + margin-top: 1rem; +} + +.made-with { + margin-top: 0.5rem; + font-size: 0.875rem; +} + +.made-with i { + color: #ff6b6b; +} + +/* Context menu styles */ +.context-menu { + position: fixed; + background: var(--surface); + border-radius: 8px; + padding: 0.5rem; + box-shadow: 0 2px 10px var(--shadow); + z-index: 1000; +} + +.context-menu.hidden { + display: none; +} + +.context-menu-item { + padding: 0.75rem 1rem; + cursor: pointer; + display: flex; + align-items: center; + gap: 0.5rem; + border-radius: 4px; +} + +.context-menu-item:hover { + background: var(--primary); +} + +.context-menu-item i { + width: 16px; +} + +/* Hidden element styles */ +.search-container.hidden, +#greeting.hidden, +#shortcuts-grid.hidden, +#add-shortcut.hidden { + visibility: hidden !important; + opacity: 0 !important; + position: absolute !important; + pointer-events: none !important; +} \ No newline at end of file