mirror of
https://github.com/DevJSTAR/JSTAR-Tab.git
synced 2025-04-18 17:35:26 +00:00
Add files via upload
This commit is contained in:
parent
248e7eb26c
commit
fed70824e1
BIN
images/favicon.ico
Normal file
BIN
images/favicon.ico
Normal file
Binary file not shown.
After Width: | Height: | Size: 12 KiB |
BIN
images/icon128.png
Normal file
BIN
images/icon128.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 2.2 KiB |
BIN
images/icon16.png
Normal file
BIN
images/icon16.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 479 B |
BIN
images/icon48.png
Normal file
BIN
images/icon48.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 978 B |
271
index.html
Normal file
271
index.html
Normal file
|
@ -0,0 +1,271 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>New JSTAR Tab</title>
|
||||||
|
<link rel="icon" href="images/favicon.ico" type="image/x-icon">
|
||||||
|
<link rel="stylesheet" href="style.css">
|
||||||
|
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.6.0/css/all.min.css" rel="stylesheet">
|
||||||
|
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<!-- Onboarding Process -->
|
||||||
|
<div id="onboarding-modal" class="modal hidden">
|
||||||
|
<div class="modal-backdrop"></div>
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="step" data-step="1">
|
||||||
|
<div class="welcome-icon">
|
||||||
|
<i class="fas fa-hand-wave"></i>
|
||||||
|
</div>
|
||||||
|
<h2>Welcome to JSTAR Tab! 👋</h2>
|
||||||
|
<p>Let's personalize your experience</p>
|
||||||
|
<div class="input-group">
|
||||||
|
<input type="text" id="user-name" placeholder="What's your name?">
|
||||||
|
</div>
|
||||||
|
<button id="next-step-btn" class="btn-primary">Continue</button>
|
||||||
|
</div>
|
||||||
|
<div class="step hidden" data-step="2">
|
||||||
|
<h2>Choose Your Search Engine</h2>
|
||||||
|
<div class="search-engine-options">
|
||||||
|
<!-- Search engine options -->
|
||||||
|
<div class="search-engine-option" data-engine="google">
|
||||||
|
<img src="https://www.google.com/favicon.ico" alt="Google">
|
||||||
|
<span>Google</span>
|
||||||
|
</div>
|
||||||
|
<div class="search-engine-option" data-engine="bing">
|
||||||
|
<img src="https://www.bing.com/favicon.ico" alt="Bing">
|
||||||
|
<span>Bing</span>
|
||||||
|
</div>
|
||||||
|
<div class="search-engine-option" data-engine="duckduckgo">
|
||||||
|
<img src="https://duckduckgo.com/favicon.ico" alt="DuckDuckGo">
|
||||||
|
<span>DuckDuckGo</span>
|
||||||
|
</div>
|
||||||
|
<div class="search-engine-option" data-engine="brave">
|
||||||
|
<img src="https://brave.com/static-assets/images/brave-favicon.png" alt="Brave">
|
||||||
|
<span>Brave</span>
|
||||||
|
</div>
|
||||||
|
<div class="search-engine-option" data-engine="qwant">
|
||||||
|
<img src="https://www.qwant.com/favicon.ico" alt="Qwant">
|
||||||
|
<span>Qwant</span>
|
||||||
|
</div>
|
||||||
|
<div class="search-engine-option" data-engine="searxng">
|
||||||
|
<img src="https://www.searx.org/favicon.ico" alt="SearXNG">
|
||||||
|
<span>SearXNG</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<button id="complete-setup-btn" class="btn-primary">Get Started</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Main Application Content -->
|
||||||
|
<div id="main-content" class="hidden">
|
||||||
|
<div class="center-container">
|
||||||
|
<div id="greeting"></div>
|
||||||
|
<div id="search-container" class="search-container">
|
||||||
|
<div class="search-wrapper">
|
||||||
|
<input type="text" id="search-bar" placeholder="Search the web">
|
||||||
|
<button id="search-button" class="search-icon">
|
||||||
|
<i class="fas fa-search"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="shortcuts-container">
|
||||||
|
<div id="shortcuts-grid"></div>
|
||||||
|
<button id="add-shortcut" class="btn-primary" data-tooltip="Maximum shortcuts limit (9) reached">
|
||||||
|
<i class="fas fa-plus"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button id="settings-button" class="settings-button">
|
||||||
|
<i class="fas fa-cog"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Settings Panel -->
|
||||||
|
<div id="settings-modal" class="modal hidden">
|
||||||
|
<div class="modal-content settings-panel">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h2>Settings</h2>
|
||||||
|
<button id="close-settings" class="btn-icon">
|
||||||
|
<i class="fas fa-times"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="settings-sections">
|
||||||
|
<!-- Personalization Settings -->
|
||||||
|
<div class="settings-section">
|
||||||
|
<h3>Personalization</h3>
|
||||||
|
<div class="setting-item">
|
||||||
|
<div class="setting-label">Display Name</div>
|
||||||
|
<input type="text" id="settings-name" placeholder="Enter your name">
|
||||||
|
</div>
|
||||||
|
<div class="setting-item">
|
||||||
|
<div class="setting-label">Anonymous Mode</div>
|
||||||
|
<label class="toggle">
|
||||||
|
<input type="checkbox" id="toggle-anonymous">
|
||||||
|
<span class="toggle-slider"></span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Search Settings -->
|
||||||
|
<div class="settings-section">
|
||||||
|
<h3>Search</h3>
|
||||||
|
<div class="setting-item">
|
||||||
|
<div class="setting-label">Search Engine</div>
|
||||||
|
<select id="search-engine-select">
|
||||||
|
<option value="google">Google</option>
|
||||||
|
<option value="bing">Bing</option>
|
||||||
|
<option value="duckduckgo">DuckDuckGo</option>
|
||||||
|
<option value="brave">Brave</option>
|
||||||
|
<option value="qwant">Qwant</option>
|
||||||
|
<option value="ecosia">Ecosia</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Appearance Settings -->
|
||||||
|
<div class="settings-section">
|
||||||
|
<h3>Appearance</h3>
|
||||||
|
<div class="setting-item">
|
||||||
|
<div class="setting-label">Theme</div>
|
||||||
|
<button id="toggle-theme" class="btn-icon">
|
||||||
|
<i class="fas fa-moon"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="setting-item">
|
||||||
|
<div class="setting-label">Show Greeting</div>
|
||||||
|
<label class="toggle">
|
||||||
|
<input type="checkbox" id="toggle-greeting" checked>
|
||||||
|
<span class="toggle-slider"></span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="setting-item">
|
||||||
|
<div class="setting-label">Show Search Bar</div>
|
||||||
|
<label class="toggle">
|
||||||
|
<input type="checkbox" id="toggle-search" checked>
|
||||||
|
<span class="toggle-slider"></span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="setting-item">
|
||||||
|
<div class="setting-label">Show Shortcuts</div>
|
||||||
|
<label class="toggle">
|
||||||
|
<input type="checkbox" id="toggle-shortcuts" checked>
|
||||||
|
<span class="toggle-slider"></span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="setting-item">
|
||||||
|
<div class="setting-label">Show Add Shortcut Button</div>
|
||||||
|
<label class="toggle">
|
||||||
|
<input type="checkbox" id="toggle-add-shortcut" checked>
|
||||||
|
<span class="toggle-slider"></span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Data Management -->
|
||||||
|
<div class="settings-section">
|
||||||
|
<h3>Data Management</h3>
|
||||||
|
<div class="data-management-buttons">
|
||||||
|
<button id="import-data" class="btn-primary">
|
||||||
|
<i class="fas fa-upload"></i> Import Data
|
||||||
|
</button>
|
||||||
|
<button id="export-data" class="btn-primary">
|
||||||
|
<i class="fas fa-download"></i> Export Data
|
||||||
|
</button>
|
||||||
|
<button id="reset-data" class="btn-primary btn-danger">
|
||||||
|
<i class="fas fa-trash"></i> Reset All Data
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<input type="file" id="import-file" accept=".json" style="display: none;">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- About Section -->
|
||||||
|
<div class="settings-section">
|
||||||
|
<h3>About</h3>
|
||||||
|
<div class="about-content">
|
||||||
|
<p>JSTAR Tab v2.0.0</p>
|
||||||
|
<p>Author: JSTAR</p>
|
||||||
|
<p>Homepage: <a href="https://github.com/DevJSTAR/JSTAR-Tab" target="_blank">GitHub Repository</a></p>
|
||||||
|
<p>Latest Update: <a href="https://github.com/DevJSTAR/JSTAR-Tab/releases/latest" target="_blank">Check for updates</a></p>
|
||||||
|
<p>License: <a href="https://github.com/DevJSTAR/JSTAR-Tab/blob/main/LICENSE" target="_blank">MIT License</a></p>
|
||||||
|
<p class="made-with">Made with <i class="fas fa-heart"></i> by JSTAR</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Add Shortcut Modal -->
|
||||||
|
<div id="add-shortcut-modal" class="modal hidden">
|
||||||
|
<div class="modal-backdrop"></div>
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h2>Add Shortcut</h2>
|
||||||
|
<button id="cancel-shortcut" class="btn-icon">
|
||||||
|
<i class="fas fa-times"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div class="input-group">
|
||||||
|
<input type="text" id="shortcut-name" placeholder="Enter name">
|
||||||
|
</div>
|
||||||
|
<div class="input-group">
|
||||||
|
<input type="text" id="shortcut-url" placeholder="Enter URL (e.g., https://example.com)">
|
||||||
|
</div>
|
||||||
|
<button id="save-shortcut" class="btn-primary">Add Shortcut</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Edit Shortcut Modal -->
|
||||||
|
<div id="edit-shortcut-modal" class="modal hidden">
|
||||||
|
<div class="modal-backdrop"></div>
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h2>Edit Shortcut</h2>
|
||||||
|
<button id="close-edit-shortcut" class="btn-icon">
|
||||||
|
<i class="fas fa-times"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div class="input-group">
|
||||||
|
<input type="text" id="edit-shortcut-name" placeholder="Enter name">
|
||||||
|
</div>
|
||||||
|
<div class="input-group">
|
||||||
|
<input type="text" id="edit-shortcut-url" placeholder="Enter URL">
|
||||||
|
</div>
|
||||||
|
<div class="modal-actions">
|
||||||
|
<button id="save-edit-shortcut" class="btn-primary">Save</button>
|
||||||
|
<button id="cancel-edit-shortcut" class="btn-primary">Cancel</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Notification Container -->
|
||||||
|
<div id="notification-container"></div>
|
||||||
|
|
||||||
|
<!-- Context Menu -->
|
||||||
|
<div id="context-menu" class="context-menu hidden">
|
||||||
|
<div class="context-menu-item" data-action="edit">
|
||||||
|
<i class="fas fa-edit"></i> Edit
|
||||||
|
</div>
|
||||||
|
<div class="context-menu-item" data-action="delete">
|
||||||
|
<i class="fas fa-trash"></i> Delete
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- JavaScript Files -->
|
||||||
|
<script src="js/storage.js"></script>
|
||||||
|
<script src="js/notifications.js"></script>
|
||||||
|
<script src="js/onboarding.js"></script>
|
||||||
|
<script src="js/search.js"></script>
|
||||||
|
<script src="js/shortcuts.js"></script>
|
||||||
|
<script src="js/settings.js"></script>
|
||||||
|
<script src="js/main.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
98
js/main.js
Normal file
98
js/main.js
Normal file
|
@ -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);
|
||||||
|
});
|
||||||
|
});
|
131
js/notifications.js
Normal file
131
js/notifications.js
Normal file
|
@ -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 = '<i class="fas fa-times"></i>';
|
||||||
|
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();
|
73
js/onboarding.js
Normal file
73
js/onboarding.js
Normal file
|
@ -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();
|
||||||
|
}
|
||||||
|
};
|
47
js/search.js
Normal file
47
js/search.js
Normal file
|
@ -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();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
336
js/settings.js
Normal file
336
js/settings.js
Normal file
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
255
js/shortcuts.js
Normal file
255
js/shortcuts.js
Normal file
|
@ -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();
|
||||||
|
}
|
||||||
|
};
|
44
js/storage.js
Normal file
44
js/storage.js
Normal file
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
36
manifest.json
Normal file
36
manifest.json
Normal file
|
@ -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": ["<all_urls>"]
|
||||||
|
}]
|
||||||
|
}
|
699
style.css
Normal file
699
style.css
Normal file
|
@ -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;
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user