Compare commits
No commits in common. "7cc5e4010ed72d48fb4fba28b99421a4aae5e933" and "3e6b86b79d9409eec45384a9537f1bcc289d7065" have entirely different histories.
7cc5e4010e
...
3e6b86b79d
22
README.md
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
Welcome to **JSTAR Tab**, the ultimate customizable new tab extension for your browser! Whether you're looking for a sleek, modern design or powerful personalization options, JSTAR Tab has you covered. 🚀
|
Welcome to **JSTAR Tab**, the ultimate customizable new tab extension for your browser! Whether you're looking for a sleek, modern design or powerful personalization options, JSTAR Tab has you covered. 🚀
|
||||||
|
|
||||||
Transform your browsing experience with custom greetings, themes, fonts, shortcuts, and more. 🎉
|
Transform your browsing experience with custom greetings, themes, shortcuts, and more. 🎉
|
||||||
|
|
||||||
## ✨ Features
|
## ✨ Features
|
||||||
|
|
||||||
|
@ -20,10 +20,8 @@ Transform your browsing experience with custom greetings, themes, fonts, shortcu
|
||||||
- "{greeting}, {name}! Today is {date}"
|
- "{greeting}, {name}! Today is {date}"
|
||||||
- "Happy {day}, {name}!"
|
- "Happy {day}, {name}!"
|
||||||
|
|
||||||
- **Font Selection**: Choose from multiple fonts (Inter, Poppins, Roboto, Montserrat, Quicksand, and Comic Sans) to personalize your experience.
|
- **Customizable Themes**: Switch between light and dark modes to suit your mood. 🌗
|
||||||
- **Custom Background Images**: Upload and set your favorite images as your new tab background.
|
- **Shortcut Management**: Add, edit, and remove shortcuts to your favorite websites.
|
||||||
- **Customizable Themes**: Switch between light and dark modes to suit your mood.
|
|
||||||
- **Shortcut Management**: Add, edit, and remove shortcuts to your favorite websites with an improved grid layout system.
|
|
||||||
- **Search Engine Selection**: Choose your preferred search engine for quick and efficient browsing.
|
- **Search Engine Selection**: Choose your preferred search engine for quick and efficient browsing.
|
||||||
- **Keyboard Shortcuts**: Set up custom keybinds for various actions:
|
- **Keyboard Shortcuts**: Set up custom keybinds for various actions:
|
||||||
- Open settings
|
- Open settings
|
||||||
|
@ -31,6 +29,7 @@ Transform your browsing experience with custom greetings, themes, fonts, shortcu
|
||||||
- Toggle anonymous mode
|
- Toggle anonymous mode
|
||||||
- Change themes
|
- Change themes
|
||||||
- Redirect to a specific URL (with a notification and "Redirecting to..." message)
|
- Redirect to a specific URL (with a notification and "Redirecting to..." message)
|
||||||
|
|
||||||
- **Data Backup and Restore**: Export and import your settings and shortcuts effortlessly.
|
- **Data Backup and Restore**: Export and import your settings and shortcuts effortlessly.
|
||||||
|
|
||||||
## 🌐 Getting Started
|
## 🌐 Getting Started
|
||||||
|
@ -55,18 +54,21 @@ Transform your browsing experience with custom greetings, themes, fonts, shortcu
|
||||||
### **Greeting Formats**
|
### **Greeting Formats**
|
||||||
Personalize your greeting with dynamic tags listed above. Example: "Good {greeting}, {name}! It's {time} on {day}!"
|
Personalize your greeting with dynamic tags listed above. Example: "Good {greeting}, {name}! It's {time} on {day}!"
|
||||||
|
|
||||||
### **Themes & Fonts**
|
### **Themes**
|
||||||
- Toggle between light and dark themes from the settings panel.
|
Toggle between light and dark themes from the settings panel.
|
||||||
- Select from various font options to match your style.
|
|
||||||
|
|
||||||
### **Shortcuts & Backgrounds**
|
### **Shortcuts**
|
||||||
|
Manage your favorite sites effortlessly:
|
||||||
- Add shortcuts with the "+" button.
|
- Add shortcuts with the "+" button.
|
||||||
- Edit or delete shortcuts by right-clicking on them.
|
- Edit or delete shortcuts by right-clicking on them.
|
||||||
- Customize your background by uploading your favorite images.
|
|
||||||
|
|
||||||
### **Keyboard Shortcuts**
|
### **Keyboard Shortcuts**
|
||||||
Set up custom keybinds for quick actions like opening settings or switching themes.
|
Set up custom keybinds for quick actions like opening settings or switching themes.
|
||||||
|
|
||||||
|
## ❤️ Acknowledgments
|
||||||
|
|
||||||
|
Special thanks to **[Equa](https://github.com/EquaTechnologies)** for contributing amazing hover animations for shortcuts, settings, and the "Add Shortcut" button. Your work is truly appreciated! 🌟
|
||||||
|
|
||||||
## 📄 License
|
## 📄 License
|
||||||
|
|
||||||
This project is licensed under the [MIT License](https://github.com/DevJSTAR/JSTAR-Tab/blob/main/LICENSE).
|
This project is licensed under the [MIT License](https://github.com/DevJSTAR/JSTAR-Tab/blob/main/LICENSE).
|
||||||
|
|
|
@ -1,419 +0,0 @@
|
||||||
.container-ob {
|
|
||||||
position: fixed;
|
|
||||||
inset: 0;
|
|
||||||
z-index: 10000;
|
|
||||||
background-color: var(--background);
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
.container-ob::before {
|
|
||||||
content: '';
|
|
||||||
display: block;
|
|
||||||
position: absolute;
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
z-index: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.step-ob {
|
|
||||||
display: none;
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
text-align: center;
|
|
||||||
max-width: 800px;
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
padding: 2rem;
|
|
||||||
position: relative;
|
|
||||||
opacity: 0;
|
|
||||||
transform: translateY(20px);
|
|
||||||
transition: opacity 0.5s ease, transform 0.5s ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
.step-ob.active-ob {
|
|
||||||
display: flex;
|
|
||||||
opacity: 1;
|
|
||||||
transform: translateY(0);
|
|
||||||
z-index: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.title-ob {
|
|
||||||
font-size: 2.5rem;
|
|
||||||
font-weight: 700;
|
|
||||||
color: var(--text);
|
|
||||||
margin-bottom: 1rem;
|
|
||||||
transform-origin: center;
|
|
||||||
animation: titlePop-ob 0.6s cubic-bezier(0.34, 1.56, 0.64, 1) forwards;
|
|
||||||
}
|
|
||||||
|
|
||||||
.subtitle-ob {
|
|
||||||
font-size: 1.25rem;
|
|
||||||
color: var(--text-secondary);
|
|
||||||
margin-bottom: 2.5rem;
|
|
||||||
max-width: 600px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.options-grid-ob {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
|
|
||||||
gap: 1.5rem;
|
|
||||||
width: 100%;
|
|
||||||
margin-bottom: 2rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.step-ob[data-step="3"] .options-grid-ob {
|
|
||||||
grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
|
|
||||||
max-width: 900px;
|
|
||||||
margin-left: auto;
|
|
||||||
margin-right: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.step-ob[data-step="5"] .options-grid-ob {
|
|
||||||
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
|
||||||
max-width: 900px;
|
|
||||||
margin-left: auto;
|
|
||||||
margin-right: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.option-card-ob {
|
|
||||||
background-color: var(--surface);
|
|
||||||
border: 2px solid var(--border);
|
|
||||||
border-radius: 16px;
|
|
||||||
padding: 1.5rem;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: center;
|
|
||||||
cursor: pointer;
|
|
||||||
transition: all 0.3s cubic-bezier(0.34, 1.56, 0.64, 1);
|
|
||||||
position: relative;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
.option-card-ob:hover {
|
|
||||||
transform: translateY(-5px);
|
|
||||||
box-shadow: 0 10px 20px var(--shadow);
|
|
||||||
border-color: var(--text);
|
|
||||||
}
|
|
||||||
|
|
||||||
.option-card-ob.selected-ob {
|
|
||||||
border-color: var(--text);
|
|
||||||
background-color: var(--surface);
|
|
||||||
transform: scale(1.02);
|
|
||||||
}
|
|
||||||
|
|
||||||
.option-card-ob svg {
|
|
||||||
width: 48px;
|
|
||||||
height: 48px;
|
|
||||||
color: var(--text);
|
|
||||||
margin-bottom: 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.option-card-ob h3 {
|
|
||||||
font-size: 1.25rem;
|
|
||||||
font-weight: 600;
|
|
||||||
color: var(--text);
|
|
||||||
margin-bottom: 0.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.option-card-ob p {
|
|
||||||
font-size: 0.875rem;
|
|
||||||
color: var(--text-secondary);
|
|
||||||
}
|
|
||||||
|
|
||||||
.option-card-ob[data-engine] img {
|
|
||||||
width: 32px;
|
|
||||||
height: 32px;
|
|
||||||
border-radius: 50%;
|
|
||||||
object-fit: cover;
|
|
||||||
margin-bottom: 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.nav-ob {
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
|
||||||
width: 100%;
|
|
||||||
max-width: 800px;
|
|
||||||
padding: 0 1rem;
|
|
||||||
margin-top: auto;
|
|
||||||
margin-bottom: 40px;
|
|
||||||
position: relative;
|
|
||||||
z-index: 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
.button-ob {
|
|
||||||
background-color: var(--surface);
|
|
||||||
border: 2px solid var(--border);
|
|
||||||
border-radius: 12px;
|
|
||||||
padding: 1rem 2rem;
|
|
||||||
font-size: 1rem;
|
|
||||||
font-weight: 500;
|
|
||||||
cursor: pointer;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 0.5rem;
|
|
||||||
color: var(--text);
|
|
||||||
transition: all 0.3s cubic-bezier(0.34, 1.56, 0.64, 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
.button-ob:hover {
|
|
||||||
background-color: var(--primary);
|
|
||||||
border-color: var(--text);
|
|
||||||
transform: translateY(-2px);
|
|
||||||
}
|
|
||||||
|
|
||||||
.button-ob svg {
|
|
||||||
width: 20px;
|
|
||||||
height: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.button-ob.primary-ob {
|
|
||||||
background-color: var(--text);
|
|
||||||
color: var(--background);
|
|
||||||
border-color: var(--text);
|
|
||||||
}
|
|
||||||
|
|
||||||
.button-ob.primary-ob:hover {
|
|
||||||
transform: translateY(-2px);
|
|
||||||
box-shadow: 0 4px 12px var(--shadow);
|
|
||||||
}
|
|
||||||
|
|
||||||
.button-ob.disabled-ob,
|
|
||||||
.button-ob:disabled {
|
|
||||||
opacity: 0.5;
|
|
||||||
cursor: not-allowed;
|
|
||||||
pointer-events: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.button-ob.disabled-ob:hover,
|
|
||||||
.button-ob:disabled:hover {
|
|
||||||
transform: none;
|
|
||||||
box-shadow: none;
|
|
||||||
background-color: var(--surface);
|
|
||||||
border-color: var(--border);
|
|
||||||
}
|
|
||||||
|
|
||||||
.progress-dots-ob {
|
|
||||||
display: flex;
|
|
||||||
gap: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.dot-ob {
|
|
||||||
width: 10px;
|
|
||||||
height: 10px;
|
|
||||||
border-radius: 50%;
|
|
||||||
background-color: var(--border);
|
|
||||||
transition: all 0.3s cubic-bezier(0.34, 1.56, 0.64, 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
.dot-ob.active-ob {
|
|
||||||
background-color: var(--text);
|
|
||||||
transform: scale(1.2);
|
|
||||||
}
|
|
||||||
|
|
||||||
.theme-preview-ob {
|
|
||||||
width: 100%;
|
|
||||||
border-radius: 16px;
|
|
||||||
padding: 1.5rem;
|
|
||||||
margin-bottom: 1rem;
|
|
||||||
transition: all 0.3s cubic-bezier(0.34, 1.56, 0.64, 1);
|
|
||||||
border: 2px solid var(--border);
|
|
||||||
}
|
|
||||||
|
|
||||||
.theme-preview-ob.light-ob {
|
|
||||||
background-color: white;
|
|
||||||
color: #1a1a1a;
|
|
||||||
}
|
|
||||||
|
|
||||||
.theme-preview-ob.dark-ob {
|
|
||||||
background-color: #1a1a1a;
|
|
||||||
color: white;
|
|
||||||
}
|
|
||||||
|
|
||||||
.theme-preview-ob.light-ob .preview-search-ob {
|
|
||||||
background-color: #f0f0f0;
|
|
||||||
border-color: #dddddd;
|
|
||||||
}
|
|
||||||
|
|
||||||
.theme-preview-content-ob {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: center;
|
|
||||||
gap: 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.preview-search-ob {
|
|
||||||
width: 100%;
|
|
||||||
height: 50px;
|
|
||||||
border-radius: 25px;
|
|
||||||
background-color: var(--surface);
|
|
||||||
border: 2px solid var(--border);
|
|
||||||
}
|
|
||||||
|
|
||||||
.preview-shortcuts-ob {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: repeat(3, 1fr);
|
|
||||||
gap: 0.5rem;
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.preview-shortcut-ob {
|
|
||||||
width: 100%;
|
|
||||||
aspect-ratio: 1/1;
|
|
||||||
border-radius: 12px;
|
|
||||||
background-color: rgba(0, 0, 0, 0.1);
|
|
||||||
}
|
|
||||||
|
|
||||||
.dark-ob .preview-shortcut-ob {
|
|
||||||
background-color: rgba(255, 255, 255, 0.1);
|
|
||||||
}
|
|
||||||
|
|
||||||
.font-preview-ob {
|
|
||||||
font-size: 3rem;
|
|
||||||
margin-bottom: 1rem;
|
|
||||||
line-height: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.name-input-container-ob {
|
|
||||||
width: 100%;
|
|
||||||
max-width: 500px;
|
|
||||||
margin: 2rem 0;
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
|
|
||||||
.name-input-ob {
|
|
||||||
width: 100%;
|
|
||||||
border: none !important;
|
|
||||||
background: transparent !important;
|
|
||||||
font-size: 4rem !important;
|
|
||||||
font-weight: 600 !important;
|
|
||||||
color: var(--text) !important;
|
|
||||||
text-align: left !important;
|
|
||||||
border-radius: 0 !important;
|
|
||||||
padding: 0.5rem 0 !important;
|
|
||||||
border-bottom: 2px solid #a3a3a3 !important;
|
|
||||||
transition: all 0.3s ease !important;
|
|
||||||
outline: none !important;
|
|
||||||
-webkit-appearance: none !important;
|
|
||||||
-moz-appearance: none !important;
|
|
||||||
appearance: none !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.name-input-ob:focus {
|
|
||||||
border-color: var(--text);
|
|
||||||
}
|
|
||||||
|
|
||||||
.name-input-ob::placeholder {
|
|
||||||
color: var(--text-secondary);
|
|
||||||
opacity: 0.5;
|
|
||||||
}
|
|
||||||
|
|
||||||
.name-input-ob:-webkit-autofill,
|
|
||||||
.name-input-ob:-webkit-autofill:hover,
|
|
||||||
.name-input-ob:-webkit-autofill:focus {
|
|
||||||
-webkit-text-fill-color: var(--text) !important;
|
|
||||||
-webkit-box-shadow: 0 0 0px 1000px transparent inset !important;
|
|
||||||
transition: background-color 5000s ease-in-out 0s !important;
|
|
||||||
background-clip: content-box !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes titlePop-ob {
|
|
||||||
0% { transform: scale(0.8); opacity: 0; }
|
|
||||||
50% { transform: scale(1.05); }
|
|
||||||
100% { transform: scale(1); opacity: 1; }
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes slideInUp-ob {
|
|
||||||
0% { transform: translateY(40px); opacity: 0; }
|
|
||||||
100% { transform: translateY(0); opacity: 1; }
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes fadeIn-ob {
|
|
||||||
0% { opacity: 0; }
|
|
||||||
100% { opacity: 1; }
|
|
||||||
}
|
|
||||||
|
|
||||||
.search-engine-preview-ob {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
gap: 0.5rem;
|
|
||||||
margin-top: 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.search-engine-preview-ob img {
|
|
||||||
width: 24px;
|
|
||||||
height: 24px;
|
|
||||||
border-radius: 50%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.animate-in-ob {
|
|
||||||
animation: slideInUp-ob 0.5s cubic-bezier(0.34, 1.56, 0.64, 1) forwards;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (max-width: 768px) {
|
|
||||||
.options-grid-ob {
|
|
||||||
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
|
||||||
gap: 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.title-ob {
|
|
||||||
font-size: 2rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.subtitle-ob {
|
|
||||||
font-size: 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.name-input-ob {
|
|
||||||
font-size: 1.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.nav-ob {
|
|
||||||
padding: 0.5rem;
|
|
||||||
margin-bottom: 30px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (max-width: 576px) {
|
|
||||||
.step-ob {
|
|
||||||
padding: 1.5rem 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.options-grid-ob,
|
|
||||||
.step-ob[data-step="3"] .options-grid-ob,
|
|
||||||
.step-ob[data-step="5"] .options-grid-ob {
|
|
||||||
grid-template-columns: 1fr;
|
|
||||||
gap: 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.option-card-ob {
|
|
||||||
padding: 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.title-ob {
|
|
||||||
font-size: 1.75rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.button-ob {
|
|
||||||
padding: 0.75rem 1.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.font-preview-ob {
|
|
||||||
font-size: 2.5rem;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (min-width: 577px) and (max-width: 991px) {
|
|
||||||
.step-ob[data-step="3"] .options-grid-ob,
|
|
||||||
.step-ob[data-step="5"] .options-grid-ob {
|
|
||||||
grid-template-columns: repeat(2, 1fr);
|
|
||||||
}
|
|
||||||
}
|
|
2240
css/style.css
Before Width: | Height: | Size: 339 KiB |
Before Width: | Height: | Size: 648 KiB |
Before Width: | Height: | Size: 1.7 MiB |
Before Width: | Height: | Size: 235 KiB |
BIN
images/favicon.ico
Normal file
After Width: | Height: | Size: 12 KiB |
Before Width: | Height: | Size: 29 KiB |
Before Width: | Height: | Size: 2.9 KiB After Width: | Height: | Size: 2.2 KiB |
Before Width: | Height: | Size: 584 B After Width: | Height: | Size: 479 B |
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 978 B |
807
index.html
|
@ -4,180 +4,73 @@
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
<title>New JSTAR Tab</title>
|
<title>New JSTAR Tab</title>
|
||||||
<link rel="icon" href="/images/favicon.png">
|
<link rel="icon" href="images/favicon.ico" type="image/x-icon">
|
||||||
<link rel="stylesheet" href="css/style.css">
|
<link rel="stylesheet" href="style.css">
|
||||||
<link rel="stylesheet" href="css/onboarding.css">
|
|
||||||
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.6.0/css/all.min.css" rel="stylesheet">
|
<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&family=Poppins:wght@400;500;600&family=Roboto:wght@400;500;700&family=Montserrat:wght@400;500;600&family=Quicksand:wght@400;500;600&display=swap" rel="stylesheet">
|
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<!-- Onboarding Process -->
|
<!-- Onboarding Process -->
|
||||||
<div id="onboarding-container" class="container-ob hidden">
|
<div id="onboarding-modal" class="modal hidden onboarding-modal">
|
||||||
<!-- Step 1: Welcome -->
|
<div class="modal-backdrop"></div>
|
||||||
<div class="step-ob" data-step="1">
|
<div class="modal-content">
|
||||||
<h1 class="title-ob">Welcome to JSTAR Tab</h1>
|
<div class="step" data-step="1">
|
||||||
<p class="subtitle-ob">Let's personalize your new tab experience together. We'll help you set up everything just the way you like it.</p>
|
<div class="welcome-icon">
|
||||||
|
<i class="fas fa-hand-wave"></i>
|
||||||
<div class="options-grid-ob">
|
|
||||||
<div class="option-card-ob" data-action="start-fresh">
|
|
||||||
<svg><use href="#icon-sparkle"/></svg>
|
|
||||||
<h3>Start Fresh</h3>
|
|
||||||
<p>Begin with a clean slate and customize everything from scratch</p>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="option-card-ob" data-action="import-data">
|
<h2>Welcome to JSTAR Tab! 👋</h2>
|
||||||
<svg><use href="#icon-import"/></svg>
|
<p>Let's get you started</p>
|
||||||
<h3>Import Settings</h3>
|
<div class="import-options">
|
||||||
<p>Already have a JSTAR Tab setup? Import your existing settings</p>
|
<button id="start-fresh-btn" class="btn-primary">Start Fresh</button>
|
||||||
|
<div class="or-divider">
|
||||||
|
<span>OR</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<button id="import-data-btn" class="btn-primary">
|
||||||
</div>
|
<i class="fas fa-upload"></i> Import Data
|
||||||
|
|
||||||
<!-- Step 2: Theme Selection -->
|
|
||||||
<div class="step-ob" data-step="2">
|
|
||||||
<h1 class="title-ob">Choose Your Theme</h1>
|
|
||||||
<p class="subtitle-ob">Pick a theme that's easy on your eyes. You can always change this later.</p>
|
|
||||||
|
|
||||||
<div class="options-grid-ob">
|
|
||||||
<div class="option-card-ob" data-theme="light">
|
|
||||||
<div class="theme-preview-ob light-ob">
|
|
||||||
<div class="theme-preview-content-ob">
|
|
||||||
<div class="preview-search-ob"></div>
|
|
||||||
<div class="preview-shortcuts-ob">
|
|
||||||
<div class="preview-shortcut-ob"></div>
|
|
||||||
<div class="preview-shortcut-ob"></div>
|
|
||||||
<div class="preview-shortcut-ob"></div>
|
|
||||||
<div class="preview-shortcut-ob"></div>
|
|
||||||
<div class="preview-shortcut-ob"></div>
|
|
||||||
<div class="preview-shortcut-ob"></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<h3>Light Theme</h3>
|
|
||||||
<p>Clean and bright interface</p>
|
|
||||||
</div>
|
|
||||||
<div class="option-card-ob" data-theme="dark">
|
|
||||||
<div class="theme-preview-ob dark-ob">
|
|
||||||
<div class="theme-preview-content-ob">
|
|
||||||
<div class="preview-search-ob"></div>
|
|
||||||
<div class="preview-shortcuts-ob">
|
|
||||||
<div class="preview-shortcut-ob"></div>
|
|
||||||
<div class="preview-shortcut-ob"></div>
|
|
||||||
<div class="preview-shortcut-ob"></div>
|
|
||||||
<div class="preview-shortcut-ob"></div>
|
|
||||||
<div class="preview-shortcut-ob"></div>
|
|
||||||
<div class="preview-shortcut-ob"></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<h3>Dark Theme</h3>
|
|
||||||
<p>Easy on the eyes at night</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Step 3: Font Selection -->
|
|
||||||
<div class="step-ob" data-step="3">
|
|
||||||
<h1 class="title-ob">Pick Your Font</h1>
|
|
||||||
<p class="subtitle-ob">Choose a font that makes reading comfortable for you.</p>
|
|
||||||
|
|
||||||
<div class="options-grid-ob">
|
|
||||||
<div class="option-card-ob" data-font="Inter">
|
|
||||||
<div class="font-preview-ob" style="font-family: Inter">Aa</div>
|
|
||||||
<h3 style="font-family: Inter">Inter</h3>
|
|
||||||
<p style="font-family: Inter">Modern and clean</p>
|
|
||||||
</div>
|
|
||||||
<div class="option-card-ob" data-font="Poppins">
|
|
||||||
<div class="font-preview-ob" style="font-family: Poppins">Aa</div>
|
|
||||||
<h3 style="font-family: Poppins">Poppins</h3>
|
|
||||||
<p style="font-family: Poppins">Geometric and friendly</p>
|
|
||||||
</div>
|
|
||||||
<div class="option-card-ob" data-font="Roboto">
|
|
||||||
<div class="font-preview-ob" style="font-family: Roboto">Aa</div>
|
|
||||||
<h3 style="font-family: Roboto">Roboto</h3>
|
|
||||||
<p style="font-family: Roboto">Clear and readable</p>
|
|
||||||
</div>
|
|
||||||
<div class="option-card-ob" data-font="Montserrat">
|
|
||||||
<div class="font-preview-ob" style="font-family: Montserrat">Aa</div>
|
|
||||||
<h3 style="font-family: Montserrat">Montserrat</h3>
|
|
||||||
<p style="font-family: Montserrat">Elegant and stylish</p>
|
|
||||||
</div>
|
|
||||||
<div class="option-card-ob" data-font="Quicksand">
|
|
||||||
<div class="font-preview-ob" style="font-family: Quicksand">Aa</div>
|
|
||||||
<h3 style="font-family: Quicksand">Quicksand</h3>
|
|
||||||
<p style="font-family: Quicksand">Rounded and soft</p>
|
|
||||||
</div>
|
|
||||||
<div class="option-card-ob" data-font="Comic Sans MS">
|
|
||||||
<div class="font-preview-ob" style="font-family: Comic Sans MS">Aa</div>
|
|
||||||
<h3 style="font-family: Comic Sans MS">Comic Sans</h3>
|
|
||||||
<p style="font-family: Comic Sans MS">Fun and playful</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Step 4: Name Input -->
|
|
||||||
<div class="step-ob" data-step="4">
|
|
||||||
<h1 class="title-ob">Make It Personal</h1>
|
|
||||||
<p class="subtitle-ob">Tell us your name and we'll greet you every time you open a new tab.</p>
|
|
||||||
|
|
||||||
<div class="name-input-container-ob">
|
|
||||||
<input type="text" id="user-name" class="name-input-ob" placeholder="Name" autocomplete="off">
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Step 5: Search Engine Selection -->
|
|
||||||
<div class="step-ob" data-step="5">
|
|
||||||
<h1 class="title-ob">Choose Your Search Engine</h1>
|
|
||||||
<p class="subtitle-ob">Select your preferred way to search the web.</p>
|
|
||||||
|
|
||||||
<div class="options-grid-ob">
|
|
||||||
<div class="option-card-ob" data-engine="google">
|
|
||||||
<img src="https://www.google.com/favicon.ico" alt="Google">
|
|
||||||
<h3>Google</h3>
|
|
||||||
<p>The world's most popular search engine</p>
|
|
||||||
</div>
|
|
||||||
<div class="option-card-ob" data-engine="bing">
|
|
||||||
<img src="https://www.bing.com/favicon.ico" alt="Bing">
|
|
||||||
<h3>Bing</h3>
|
|
||||||
<p>Microsoft's intelligent search</p>
|
|
||||||
</div>
|
|
||||||
<div class="option-card-ob" data-engine="duckduckgo">
|
|
||||||
<img src="https://duckduckgo.com/favicon.ico" alt="DuckDuckGo">
|
|
||||||
<h3>DuckDuckGo</h3>
|
|
||||||
<p>Privacy-focused search</p>
|
|
||||||
</div>
|
|
||||||
<div class="option-card-ob" data-engine="brave">
|
|
||||||
<img src="https://brave.com/static-assets/images/brave-favicon.png" alt="Brave">
|
|
||||||
<h3>Brave</h3>
|
|
||||||
<p>Independent and secure</p>
|
|
||||||
</div>
|
|
||||||
<div class="option-card-ob" data-engine="qwant">
|
|
||||||
<img src="https://www.qwant.com/favicon.ico" alt="Qwant">
|
|
||||||
<h3>Qwant</h3>
|
|
||||||
<p>European privacy-first search</p>
|
|
||||||
</div>
|
|
||||||
<div class="option-card-ob" data-engine="searxng">
|
|
||||||
<img src="https://www.searx.org/favicon.ico" alt="SearXNG">
|
|
||||||
<h3>SearXNG</h3>
|
|
||||||
<p>Meta search engine</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Navigation -->
|
|
||||||
<div class="nav-ob">
|
|
||||||
<button id="prev-step" class="button-ob">
|
|
||||||
<svg><use href="#icon-arrow-left"/></svg>
|
|
||||||
Back
|
|
||||||
</button>
|
|
||||||
<div class="progress-dots-ob"></div>
|
|
||||||
<button id="next-step" class="button-ob primary-ob">
|
|
||||||
Next
|
|
||||||
<svg><use href="#icon-arrow-right"/></svg>
|
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<input type="file" id="onboarding-import" accept=".json" style="display: none;">
|
<input type="file" id="onboarding-import" accept=".json" style="display: none;">
|
||||||
</div>
|
</div>
|
||||||
|
<div class="step hidden" data-step="2">
|
||||||
|
<h2>What's your name?</h2>
|
||||||
|
<div class="input-group">
|
||||||
|
<input type="text" id="user-name" placeholder="Enter your name">
|
||||||
|
</div>
|
||||||
|
<button id="next-step-btn" class="btn-primary">Continue</button>
|
||||||
|
</div>
|
||||||
|
<div class="step hidden" data-step="3">
|
||||||
|
<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 -->
|
<!-- Main Application Content -->
|
||||||
<div id="main-content" class="hidden">
|
<div id="main-content" class="hidden">
|
||||||
|
@ -187,87 +80,43 @@
|
||||||
<div class="search-wrapper">
|
<div class="search-wrapper">
|
||||||
<input type="text" id="search-bar" placeholder="Search the web">
|
<input type="text" id="search-bar" placeholder="Search the web">
|
||||||
<button id="search-button" class="search-icon">
|
<button id="search-button" class="search-icon">
|
||||||
<svg><use href="#icon-search"/></svg>
|
<i class="fas fa-search"></i>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="shortcuts-container">
|
<div class="shortcuts-container">
|
||||||
<div id="shortcuts-grid"></div>
|
<div id="shortcuts-grid"></div>
|
||||||
<button id="add-shortcut" class="btn-primary">
|
<button id="add-shortcut" class="btn-primary">
|
||||||
<svg><use href="#icon-plus"/></svg>
|
<i class="fas fa-plus"></i>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<button id="settings-button" class="settings-button">
|
<button id="settings-button" class="settings-button">
|
||||||
<svg><use href="#icon-settings"/></svg>
|
<i class="fas fa-cog"></i>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Settings Page -->
|
<!-- Settings Panel -->
|
||||||
<div id="settings-page" class="settings-page hidden">
|
<div id="settings-modal" class="modal hidden">
|
||||||
<div class="settings-container">
|
<div class="modal-content settings-panel">
|
||||||
<div class="settings-sidebar">
|
<div class="modal-header">
|
||||||
<div class="settings-sidebar-header">
|
|
||||||
<button id="back-to-home" class="btn-icon">
|
|
||||||
<svg><use href="#icon-arrow-left"/></svg>
|
|
||||||
</button>
|
|
||||||
<h2>Settings</h2>
|
<h2>Settings</h2>
|
||||||
|
<button id="close-settings" class="btn-icon">
|
||||||
|
<i class="fas fa-times"></i>
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<nav class="settings-nav">
|
|
||||||
<a href="#personalization" class="settings-nav-item active" data-section="personalization">
|
<div class="settings-sections">
|
||||||
<svg><use href="#icon-user"/></svg>
|
<!-- Personalization Settings -->
|
||||||
<span>Personalization</span>
|
<div class="settings-section">
|
||||||
</a>
|
|
||||||
<a href="#appearance" class="settings-nav-item" data-section="appearance">
|
|
||||||
<svg><use href="#icon-sparkle"/></svg>
|
|
||||||
<span>Appearance</span>
|
|
||||||
</a>
|
|
||||||
<a href="#search" class="settings-nav-item" data-section="search">
|
|
||||||
<svg><use href="#icon-search"/></svg>
|
|
||||||
<span>Search</span>
|
|
||||||
</a>
|
|
||||||
<a href="#shortcuts" class="settings-nav-item" data-section="shortcuts">
|
|
||||||
<svg><use href="#icon-shortcuts"/></svg>
|
|
||||||
<span>Shortcuts</span>
|
|
||||||
</a>
|
|
||||||
<a href="#backgrounds" class="settings-nav-item" data-section="backgrounds">
|
|
||||||
<svg><use href="#icon-image"/></svg>
|
|
||||||
<span>Backgrounds</span>
|
|
||||||
</a>
|
|
||||||
<a href="#data" class="settings-nav-item" data-section="data">
|
|
||||||
<svg><use href="#icon-database"/></svg>
|
|
||||||
<span>Data Management</span>
|
|
||||||
</a>
|
|
||||||
<a href="#about" class="settings-nav-item" data-section="about">
|
|
||||||
<svg><use href="#icon-info"/></svg>
|
|
||||||
<span>About</span>
|
|
||||||
</a>
|
|
||||||
</nav>
|
|
||||||
</div>
|
|
||||||
<div class="settings-content">
|
|
||||||
<div id="personalization" class="settings-section active">
|
|
||||||
<h3>Personalization</h3>
|
<h3>Personalization</h3>
|
||||||
<div class="settings-card">
|
|
||||||
<div class="settings-card-header">
|
|
||||||
<div class="settings-card-icon">
|
|
||||||
<svg><use href="#icon-user"/></svg>
|
|
||||||
</div>
|
|
||||||
<div class="settings-card-title">
|
|
||||||
<h4>Profile Settings</h4>
|
|
||||||
<p>Customize how you appear and interact with JSTAR Tab</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="setting-item">
|
<div class="setting-item">
|
||||||
<div class="setting-label">Display Name</div>
|
<div class="setting-label">Display Name</div>
|
||||||
<div class="setting-description">This name will be used in your greeting message</div>
|
|
||||||
<input type="text" id="settings-name" placeholder="Enter your name">
|
<input type="text" id="settings-name" placeholder="Enter your name">
|
||||||
</div>
|
</div>
|
||||||
<div class="setting-item horizontal">
|
<div class="setting-item horizontal">
|
||||||
<div>
|
|
||||||
<div class="setting-label">Anonymous Mode</div>
|
<div class="setting-label">Anonymous Mode</div>
|
||||||
<div class="setting-description">Hide your real name and use a randomly generated one</div>
|
|
||||||
</div>
|
|
||||||
<label class="toggle">
|
<label class="toggle">
|
||||||
<input type="checkbox" id="toggle-anonymous">
|
<input type="checkbox" id="toggle-anonymous">
|
||||||
<span class="toggle-slider"></span>
|
<span class="toggle-slider"></span>
|
||||||
|
@ -275,279 +124,74 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="settings-card">
|
<!-- Search Settings -->
|
||||||
<div class="settings-card-header">
|
<div class="settings-section">
|
||||||
<div class="settings-card-icon">
|
<h3>Search</h3>
|
||||||
<svg><use href="#icon-message"/></svg>
|
|
||||||
</div>
|
|
||||||
<div class="settings-card-title">
|
|
||||||
<h4>Greeting Customization</h4>
|
|
||||||
<p>Personalize your welcome message</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="setting-item">
|
<div class="setting-item">
|
||||||
<div class="setting-label">Custom Greeting</div>
|
<div class="setting-label">Search Engine</div>
|
||||||
<div class="setting-description">Create your own greeting format using the available variables</div>
|
<select id="search-engine-select">
|
||||||
<input type="text" id="custom-greeting" placeholder="Enter custom greeting">
|
<option value="google">Google</option>
|
||||||
<span class="format-hint">Available variables: {name}, {greeting}, {time}, {date}, {day}, {month}, {year}</span>
|
<option value="bing">Bing</option>
|
||||||
</div>
|
<option value="duckduckgo">DuckDuckGo</option>
|
||||||
</div>
|
<option value="brave">Brave</option>
|
||||||
</div>
|
<option value="qwant">Qwant</option>
|
||||||
|
<option value="searxng">SearXNG</option>
|
||||||
<div id="appearance" class="settings-section">
|
|
||||||
<h3>Appearance</h3>
|
|
||||||
<div class="settings-card">
|
|
||||||
<div class="settings-card-header">
|
|
||||||
<div class="settings-card-icon">
|
|
||||||
<svg><use href="#icon-sparkle"/></svg>
|
|
||||||
</div>
|
|
||||||
<div class="settings-card-title">
|
|
||||||
<h4>Theme Settings</h4>
|
|
||||||
<p>Customize the look and feel of JSTAR Tab</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="setting-item horizontal">
|
|
||||||
<div>
|
|
||||||
<div class="setting-label">Theme</div>
|
|
||||||
<div class="setting-description">Switch between light and dark mode</div>
|
|
||||||
</div>
|
|
||||||
<button id="toggle-theme" class="btn-icon">
|
|
||||||
<svg><use href="#icon-light-mode"/></svg>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<!-- Icon style setting used to be here (will be added in full release) -->
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="settings-card">
|
|
||||||
<div class="settings-card-header">
|
|
||||||
<div class="settings-card-icon">
|
|
||||||
<svg><use href="#icon-text"/></svg>
|
|
||||||
</div>
|
|
||||||
<div class="settings-card-title">
|
|
||||||
<h4>Typography</h4>
|
|
||||||
<p>Customize fonts and text sizes</p>
|
|
||||||
</div>
|
|
||||||
<button id="reset-font-size" class="btn-reset">Reset to Default</button>
|
|
||||||
</div>
|
|
||||||
<div class="setting-item">
|
|
||||||
<div class="setting-label">Font Family</div>
|
|
||||||
<div class="setting-description">Choose your preferred font for the interface</div>
|
|
||||||
<div class="custom-select">
|
|
||||||
<select id="font-family-select">
|
|
||||||
<option value="Inter" style="font-family: Inter">Inter</option>
|
|
||||||
<option value="Poppins" style="font-family: Poppins">Poppins</option>
|
|
||||||
<option value="Roboto" style="font-family: Roboto">Roboto</option>
|
|
||||||
<option value="Montserrat" style="font-family: Montserrat">Montserrat</option>
|
|
||||||
<option value="Quicksand" style="font-family: Quicksand">Quicksand</option>
|
|
||||||
<option value="Comic Sans MS" style="font-family: 'Comic Sans MS', cursive">Comic Sans MS</option>
|
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="setting-item">
|
|
||||||
<div class="setting-label">Font Size</div>
|
<!-- Appearance Settings -->
|
||||||
<div class="setting-description">Adjust the size of text throughout the interface</div>
|
<div class="settings-section">
|
||||||
<div class="font-size-control">
|
<h3>Appearance</h3>
|
||||||
<div class="slider-container">
|
<div class="setting-item horizontal">
|
||||||
<input type="range" id="font-size-slider" min="8" max="36" step="1">
|
<div class="setting-label">Theme</div>
|
||||||
</div>
|
<button id="toggle-theme" class="btn-icon">
|
||||||
<div class="font-size-input">
|
<i class="fas fa-moon"></i>
|
||||||
<input type="number" id="font-size-number" min="8" max="36">
|
</button>
|
||||||
<span>px</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="settings-card">
|
|
||||||
<div class="settings-card-header">
|
|
||||||
<div class="settings-card-icon">
|
|
||||||
<svg><use href="#icon-layout"/></svg>
|
|
||||||
</div>
|
|
||||||
<div class="settings-card-title">
|
|
||||||
<h4>Layout Settings</h4>
|
|
||||||
<p>Configure which elements are visible on your new tab page</p>
|
|
||||||
</div>
|
|
||||||
<button class="btn-reset" id="reset-layout">Reset to Default</button>
|
|
||||||
</div>
|
|
||||||
<div class="setting-item horizontal">
|
<div class="setting-item horizontal">
|
||||||
<div>
|
|
||||||
<div class="setting-label">Show Greeting</div>
|
<div class="setting-label">Show Greeting</div>
|
||||||
<div class="setting-description">Display the welcome message</div>
|
|
||||||
</div>
|
|
||||||
<label class="toggle">
|
<label class="toggle">
|
||||||
<input type="checkbox" id="toggle-greeting" checked>
|
<input type="checkbox" id="toggle-greeting" checked>
|
||||||
<span class="toggle-slider"></span>
|
<span class="toggle-slider"></span>
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
<div class="setting-item horizontal">
|
|
||||||
<div>
|
<div class="setting-item">
|
||||||
<div class="setting-label">Show Search Bar</div>
|
<div class="setting-label">Custom Greeting</div>
|
||||||
<div class="setting-description">Display the search bar for quick web searches</div>
|
<input type="text" id="custom-greeting" placeholder="Enter custom greeting">
|
||||||
|
<span class="format-hint">{name}, {greeting}, {time}, {date}, {day}, {month}, {year}</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="setting-item horizontal">
|
||||||
|
<div class="setting-label">Show Search Bar</div>
|
||||||
<label class="toggle">
|
<label class="toggle">
|
||||||
<input type="checkbox" id="toggle-search" checked>
|
<input type="checkbox" id="toggle-search" checked>
|
||||||
<span class="toggle-slider"></span>
|
<span class="toggle-slider"></span>
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="setting-item horizontal">
|
<div class="setting-item horizontal">
|
||||||
<div>
|
|
||||||
<div class="setting-label">Show Shortcuts</div>
|
<div class="setting-label">Show Shortcuts</div>
|
||||||
<div class="setting-description">Display your favorite website shortcuts</div>
|
|
||||||
</div>
|
|
||||||
<label class="toggle">
|
<label class="toggle">
|
||||||
<input type="checkbox" id="toggle-shortcuts" checked>
|
<input type="checkbox" id="toggle-shortcuts" checked>
|
||||||
<span class="toggle-slider"></span>
|
<span class="toggle-slider"></span>
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="setting-item horizontal">
|
<div class="setting-item horizontal">
|
||||||
<div>
|
|
||||||
<div class="setting-label">Show Add Shortcut Button</div>
|
<div class="setting-label">Show Add Shortcut Button</div>
|
||||||
<div class="setting-description">Display the button to add new shortcuts</div>
|
|
||||||
</div>
|
|
||||||
<label class="toggle">
|
<label class="toggle">
|
||||||
<input type="checkbox" id="toggle-add-shortcut" checked>
|
<input type="checkbox" id="toggle-add-shortcut" checked>
|
||||||
<span class="toggle-slider"></span>
|
<span class="toggle-slider"></span>
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
<div class="setting-item">
|
|
||||||
<div class="setting-label">Grid Layout</div>
|
|
||||||
<div class="setting-description">Choose how your content is organized</div>
|
|
||||||
<div class="custom-select">
|
|
||||||
<select id="grid-layout-type">
|
|
||||||
<option value="default">Default Grid</option>
|
|
||||||
<option value="compact">Compact Grid</option>
|
|
||||||
<option value="comfortable">Comfortable Grid</option>
|
|
||||||
<option value="list">List View</option>
|
|
||||||
<option value="custom">Custom Grid</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div id="custom-grid-settings" class="setting-item hidden">
|
|
||||||
<div class="setting-label">Custom Grid Settings</div>
|
|
||||||
<div class="setting-description">Customize your grid layout with specific dimensions</div>
|
|
||||||
<div class="custom-grid-controls">
|
|
||||||
<div class="grid-control">
|
|
||||||
<label for="grid-columns">Columns</label>
|
|
||||||
<input type="number" id="grid-columns" min="1" max="12" value="5">
|
|
||||||
</div>
|
|
||||||
<div class="grid-control">
|
|
||||||
<label for="grid-gap">Spacing</label>
|
|
||||||
<input type="number" id="grid-gap" min="0" max="50" value="16">
|
|
||||||
</div>
|
|
||||||
<div class="grid-control">
|
|
||||||
<label for="grid-size">Item Size</label>
|
|
||||||
<input type="number" id="grid-size" min="40" max="200" value="80">
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="setting-item horizontal">
|
|
||||||
<div>
|
|
||||||
<div class="setting-label">Resizable Items</div>
|
|
||||||
<div class="setting-description">Allow items to be resized within the grid</div>
|
|
||||||
</div>
|
|
||||||
<label class="toggle">
|
|
||||||
<input type="checkbox" id="toggle-resizable">
|
|
||||||
<span class="toggle-slider"></span>
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="settings-card">
|
<!-- Keyboard Shortcuts Settings -->
|
||||||
<div class="settings-card-header">
|
<div class="settings-section">
|
||||||
<div class="settings-card-icon">
|
|
||||||
<svg><use href="#icon-bell"/></svg>
|
|
||||||
</div>
|
|
||||||
<div class="settings-card-title">
|
|
||||||
<h4>Notifications</h4>
|
|
||||||
<p>Configure update notifications and alerts</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="setting-item horizontal">
|
|
||||||
<div>
|
|
||||||
<div class="setting-label">Update Alerts</div>
|
|
||||||
<div class="setting-description">Get notified when new updates are available</div>
|
|
||||||
</div>
|
|
||||||
<label class="toggle">
|
|
||||||
<input type="checkbox" id="toggle-update-alerts" checked>
|
|
||||||
<span class="toggle-slider"></span>
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="settings-card">
|
|
||||||
<div class="settings-card-header">
|
|
||||||
<div class="settings-card-icon">
|
|
||||||
<svg><use href="#icon-light-mode"/></svg>
|
|
||||||
</div>
|
|
||||||
<div class="settings-card-title">
|
|
||||||
<h4>Light Mode Colors</h4>
|
|
||||||
<p>Customize the color scheme for light mode</p>
|
|
||||||
</div>
|
|
||||||
<button id="reset-light-colors" class="btn-reset">Reset to Default</button>
|
|
||||||
</div>
|
|
||||||
<div id="light-mode-colors">
|
|
||||||
<!-- Color settings will be dynamically added here -->
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="settings-card">
|
|
||||||
<div class="settings-card-header">
|
|
||||||
<div class="settings-card-icon">
|
|
||||||
<svg><use href="#icon-dark-mode"/></svg>
|
|
||||||
</div>
|
|
||||||
<div class="settings-card-title">
|
|
||||||
<h4>Dark Mode Colors</h4>
|
|
||||||
<p>Customize the color scheme for dark mode</p>
|
|
||||||
</div>
|
|
||||||
<button id="reset-dark-colors" class="btn-reset">Reset to Default</button>
|
|
||||||
</div>
|
|
||||||
<div id="dark-mode-colors">
|
|
||||||
<!-- Color settings will be dynamically added here -->
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div id="search" class="settings-section">
|
|
||||||
<h3>Search</h3>
|
|
||||||
<div class="settings-card">
|
|
||||||
<div class="settings-card-header">
|
|
||||||
<div class="settings-card-icon">
|
|
||||||
<svg><use href="#icon-search"/></svg>
|
|
||||||
</div>
|
|
||||||
<div class="settings-card-title">
|
|
||||||
<h4>Search Engine</h4>
|
|
||||||
<p>Choose your preferred search engine for the search bar</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="setting-item">
|
|
||||||
<div class="setting-label">Default Search Engine</div>
|
|
||||||
<div class="setting-description">Select which search engine to use when searching from the new tab page</div>
|
|
||||||
<div class="custom-select">
|
|
||||||
<select id="search-engine-select">
|
|
||||||
<option value="google" class="search-engine-google">Google</option>
|
|
||||||
<option value="bing" class="search-engine-bing">Bing</option>
|
|
||||||
<option value="duckduckgo" class="search-engine-duckduckgo">DuckDuckGo</option>
|
|
||||||
<option value="brave" class="search-engine-brave">Brave</option>
|
|
||||||
<option value="qwant" class="search-engine-qwant">Qwant</option>
|
|
||||||
<option value="searxng" class="search-engine-searxng">SearXNG</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div id="shortcuts" class="settings-section">
|
|
||||||
<h3>Keyboard Shortcuts</h3>
|
<h3>Keyboard Shortcuts</h3>
|
||||||
<div class="settings-card">
|
|
||||||
<div class="settings-card-header">
|
|
||||||
<div class="settings-card-icon">
|
|
||||||
<svg><use href="#icon-shortcuts"/></svg>
|
|
||||||
</div>
|
|
||||||
<div class="settings-card-title">
|
|
||||||
<h4>Shortcut Settings</h4>
|
|
||||||
<p>Manage your keyboard shortcuts for quick access</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="setting-item">
|
<div class="setting-item">
|
||||||
<div class="setting-label">Settings Menu</div>
|
<div class="setting-label">Settings Menu</div>
|
||||||
<div class="keybind-container">
|
<div class="keybind-container">
|
||||||
|
@ -585,79 +229,33 @@
|
||||||
<input type="text" id="keybind-url" placeholder="Enter URL">
|
<input type="text" id="keybind-url" placeholder="Enter URL">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
<div id="backgrounds" class="settings-section">
|
<!-- Data Management -->
|
||||||
<h3>Backgrounds</h3>
|
<div class="settings-section">
|
||||||
<div class="settings-card">
|
|
||||||
<div class="settings-card-header">
|
|
||||||
<div class="settings-card-icon">
|
|
||||||
<svg><use href="#icon-image"/></svg>
|
|
||||||
</div>
|
|
||||||
<div class="settings-card-title">
|
|
||||||
<h4>Custom Background</h4>
|
|
||||||
<p>Personalize your new tab page with custom backgrounds</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="background-preview-grid" id="background-preview-grid">
|
|
||||||
<div class="background-preview empty" id="default-background">
|
|
||||||
<svg><use href="#icon-close"/></svg>
|
|
||||||
</div>
|
|
||||||
<!-- Previews will be added here dynamically -->
|
|
||||||
</div>
|
|
||||||
<label for="background-upload" class="file-input-label">Upload New Background</label>
|
|
||||||
<input type="file" id="background-upload" accept="image/*" class="file-input">
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div id="data" class="settings-section">
|
|
||||||
<h3>Data Management</h3>
|
<h3>Data Management</h3>
|
||||||
<div class="settings-card">
|
|
||||||
<div class="settings-card-header">
|
|
||||||
<div class="settings-card-icon">
|
|
||||||
<svg><use href="#icon-database"/></svg>
|
|
||||||
</div>
|
|
||||||
<div class="settings-card-title">
|
|
||||||
<h4>Backup & Restore</h4>
|
|
||||||
<p>Manage your JSTAR Tab data and settings</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="setting-item">
|
|
||||||
<div class="setting-description">Export your settings, shortcuts, and customizations to a file, or restore them from a previous backup.</div>
|
|
||||||
<div class="data-management-buttons">
|
<div class="data-management-buttons">
|
||||||
<button id="import-data" class="btn-primary">
|
<button id="import-data" class="btn-primary">
|
||||||
<svg><use href="#icon-import"/></svg> Import Data
|
<i class="fas fa-upload"></i> Import Data
|
||||||
</button>
|
</button>
|
||||||
<button id="export-data" class="btn-primary">
|
<button id="export-data" class="btn-primary">
|
||||||
<svg><use href="#icon-export"/></svg> Export Data
|
<i class="fas fa-download"></i> Export Data
|
||||||
</button>
|
</button>
|
||||||
<button id="reset-data" class="btn-primary btn-danger">
|
<button id="reset-data" class="btn-primary btn-danger">
|
||||||
<svg><use href="#icon-delete"/></svg> Reset All Data
|
<i class="fas fa-trash"></i> Reset All Data
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<input type="file" id="import-file" accept=".json" style="display: none;">
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id="about" class="settings-section">
|
<!-- About Section -->
|
||||||
|
<div class="settings-section">
|
||||||
<h3>About</h3>
|
<h3>About</h3>
|
||||||
<div class="settings-card">
|
|
||||||
<div class="settings-card-header">
|
|
||||||
<div class="settings-card-icon">
|
|
||||||
<svg><use href="#icon-info"/></svg>
|
|
||||||
</div>
|
|
||||||
<div class="settings-card-title">
|
|
||||||
<h4>JSTAR Tab</h4>
|
|
||||||
<p>Everyone deserves a beautiful browsing experience, a customizable new tab page that defines you.</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="about-content">
|
<div class="about-content">
|
||||||
<p id="extension-version">Version <span id="version-icon" class="version-icon"></span></p>
|
<p>JSTAR Tab v2.6.2</p>
|
||||||
<p>Homepage: <a href="https://github.com/DevJSTAR/JSTAR-Tab" target="_blank">GitHub Repository</a></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>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>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 <a href="https://junaid.xyz" target="_blank">Junaid</a></p>
|
<p class="made-with">Made with <i class="fas fa-heart"></i> by <a href="https://linktr.ee/jstarsdev" target="_blank">JSTAR</a></p>
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -671,7 +269,7 @@
|
||||||
<div class="modal-header">
|
<div class="modal-header">
|
||||||
<h2>Add Shortcut</h2>
|
<h2>Add Shortcut</h2>
|
||||||
<button id="cancel-shortcut" class="btn-icon">
|
<button id="cancel-shortcut" class="btn-icon">
|
||||||
<svg><use href="#icon-close"/></svg>
|
<i class="fas fa-times"></i>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="input-group">
|
<div class="input-group">
|
||||||
|
@ -691,7 +289,7 @@
|
||||||
<div class="modal-header">
|
<div class="modal-header">
|
||||||
<h2>Edit Shortcut</h2>
|
<h2>Edit Shortcut</h2>
|
||||||
<button id="close-edit-shortcut" class="btn-icon">
|
<button id="close-edit-shortcut" class="btn-icon">
|
||||||
<svg><use href="#icon-close"/></svg>
|
<i class="fas fa-times"></i>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="input-group">
|
<div class="input-group">
|
||||||
|
@ -713,187 +311,24 @@
|
||||||
<!-- Context Menu -->
|
<!-- Context Menu -->
|
||||||
<div id="context-menu" class="context-menu hidden">
|
<div id="context-menu" class="context-menu hidden">
|
||||||
<div class="context-menu-item" data-action="open-new-tab">
|
<div class="context-menu-item" data-action="open-new-tab">
|
||||||
<svg><use href="#icon-external-link"/></svg> Open in New Tab
|
<i class="fas fa-external-link-alt"></i> Open in New Tab
|
||||||
</div>
|
</div>
|
||||||
<div class="context-menu-item" data-action="edit">
|
<div class="context-menu-item" data-action="edit">
|
||||||
<svg><use href="#icon-edit"/></svg> Edit
|
<i class="fas fa-edit"></i> Edit
|
||||||
</div>
|
</div>
|
||||||
<div class="context-menu-item" data-action="delete">
|
<div class="context-menu-item" data-action="delete">
|
||||||
<svg><use href="#icon-delete"/></svg> Delete
|
<i class="fas fa-trash"></i> Delete
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- SVG Definitions (Linear Icons) -->
|
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" style="display:none;">
|
|
||||||
<!-- Arrow Left Icon -->
|
|
||||||
<symbol id="icon-arrow-left" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
||||||
<line x1="19" y1="12" x2="5" y2="12"/>
|
|
||||||
<polyline points="12 19 5 12 12 5"/>
|
|
||||||
</symbol>
|
|
||||||
|
|
||||||
<!-- Arrow Right Icon -->
|
|
||||||
<symbol id="icon-arrow-right" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
||||||
<line x1="5" y1="12" x2="19" y2="12"/>
|
|
||||||
<polyline points="12 5 19 12 12 19"/>
|
|
||||||
</symbol>
|
|
||||||
|
|
||||||
<!-- Image/Background Icon -->
|
|
||||||
<symbol id="icon-image" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
||||||
<rect x="3" y="3" width="18" height="18" rx="2" ry="2"/>
|
|
||||||
<circle cx="8.5" cy="8.5" r="1.5"/>
|
|
||||||
<polyline points="21 15 16 10 5 21"/>
|
|
||||||
</symbol>
|
|
||||||
|
|
||||||
<!-- Database Icon -->
|
|
||||||
<symbol id="icon-database" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
||||||
<ellipse cx="12" cy="5" rx="9" ry="3"/>
|
|
||||||
<path d="M21 12c0 1.66-4 3-9 3s-9-1.34-9-3"/>
|
|
||||||
<path d="M3 5v14c0 1.66 4 3 9 3s9-1.34 9-3V5"/>
|
|
||||||
</symbol>
|
|
||||||
|
|
||||||
<!-- Chevron Down Icon -->
|
|
||||||
<symbol id="icon-chevron-down" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
||||||
<polyline points="6 9 12 15 18 9"/>
|
|
||||||
</symbol>
|
|
||||||
|
|
||||||
<!-- External Link Icon -->
|
|
||||||
<symbol id="icon-external-link" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
||||||
<path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6"/>
|
|
||||||
<polyline points="15 3 21 3 21 9"/>
|
|
||||||
<line x1="10" y1="14" x2="21" y2="3"/>
|
|
||||||
</symbol>
|
|
||||||
|
|
||||||
<!-- Edit Icon -->
|
|
||||||
<symbol id="icon-edit" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
||||||
<path d="M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7"/>
|
|
||||||
<path d="M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z"/>
|
|
||||||
</symbol>
|
|
||||||
|
|
||||||
<!-- Delete Icon -->
|
|
||||||
<symbol id="icon-delete" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
||||||
<polyline points="3 6 5 6 21 6"/>
|
|
||||||
<path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"/>
|
|
||||||
<line x1="10" y1="11" x2="10" y2="17"/>
|
|
||||||
<line x1="14" y1="11" x2="14" y2="17"/>
|
|
||||||
</symbol>
|
|
||||||
|
|
||||||
<!-- Search Icon -->
|
|
||||||
<symbol id="icon-search" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
||||||
<circle cx="11" cy="11" r="8"/>
|
|
||||||
<line x1="21" y1="21" x2="16.65" y2="16.65"/>
|
|
||||||
</symbol>
|
|
||||||
|
|
||||||
<!-- Plus Icon -->
|
|
||||||
<symbol id="icon-plus" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
||||||
<line x1="12" y1="5" x2="12" y2="19"/>
|
|
||||||
<line x1="5" y1="12" x2="19" y2="12"/>
|
|
||||||
</symbol>
|
|
||||||
|
|
||||||
<!-- Text Icon-->
|
|
||||||
<symbol id="icon-text" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
||||||
<path d="M4 7V4h16v3"/>
|
|
||||||
<path d="M9 20h6"/>
|
|
||||||
<path d="M12 4v16"/>
|
|
||||||
</symbol>
|
|
||||||
|
|
||||||
<!-- User Icon -->
|
|
||||||
<symbol id="icon-user" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
||||||
<path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2"/>
|
|
||||||
<circle cx="12" cy="7" r="4"/>
|
|
||||||
</symbol>
|
|
||||||
|
|
||||||
<!-- Sparkle Icon -->
|
|
||||||
<symbol id="icon-sparkle" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
||||||
<path d="M12 3l1.88 5.79a2 2 0 0 0 1.33 1.33L21 12l-5.79 1.88a2 2 0 0 0-1.33 1.33L12 21l-1.88-5.79a2 2 0 0 0-1.33-1.33L3 12l5.79-1.88a2 2 0 0 0 1.33-1.33L12 3z"/>
|
|
||||||
</symbol>
|
|
||||||
|
|
||||||
<!-- Settings Icon -->
|
|
||||||
<symbol id="icon-settings" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
||||||
<circle cx="12" cy="12" r="3"/>
|
|
||||||
<path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1 0 2.83 2 2 0 0 1-2.83 0l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-2 2 2 2 0 0 1-2-2v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83 0 2 2 0 0 1 0-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1-2-2 2 2 0 0 1 2-2h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 0-2.83 2 2 0 0 1 2.83 0l.06.06a1.65 1.65 0 0 0 1.82.33H9a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 2-2 2 2 0 0 1 2 2v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 0 2 2 0 0 1 0 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 2 2 2 2 0 0 1-2 2h-.09a1.65 1.65 0 0 0-1.51 1z"/>
|
|
||||||
</symbol>
|
|
||||||
|
|
||||||
<!-- Import Icon -->
|
|
||||||
<symbol id="icon-import" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
||||||
<path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/>
|
|
||||||
<polyline points="7 10 12 15 17 10"/>
|
|
||||||
<line x1="12" y1="15" x2="12" y2="3"/>
|
|
||||||
</symbol>
|
|
||||||
|
|
||||||
<!-- Export Icon -->
|
|
||||||
<symbol id="icon-export" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
||||||
<path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/>
|
|
||||||
<polyline points="7 10 12 5 17 10"/>
|
|
||||||
<line x1="12" y1="5" x2="12" y2="15"/>
|
|
||||||
</symbol>
|
|
||||||
|
|
||||||
<!-- Light Mode (Sun) Icon -->
|
|
||||||
<symbol id="icon-light-mode" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
||||||
<circle cx="12" cy="12" r="5"/>
|
|
||||||
<line x1="12" y1="1" x2="12" y2="3"/>
|
|
||||||
<line x1="12" y1="21" x2="12" y2="23"/>
|
|
||||||
<line x1="4.22" y1="4.22" x2="5.64" y2="5.64"/>
|
|
||||||
<line x1="18.36" y1="18.36" x2="19.78" y2="19.78"/>
|
|
||||||
<line x1="1" y1="12" x2="3" y2="12"/>
|
|
||||||
<line x1="21" y1="12" x2="23" y2="12"/>
|
|
||||||
<line x1="4.22" y1="19.78" x2="5.64" y2="18.36"/>
|
|
||||||
<line x1="18.36" y1="5.64" x2="19.78" y2="4.22"/>
|
|
||||||
</symbol>
|
|
||||||
|
|
||||||
<!-- Dark Mode (Moon) Icon -->
|
|
||||||
<symbol id="icon-dark-mode" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
||||||
<path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"/>
|
|
||||||
</symbol>
|
|
||||||
|
|
||||||
<!-- Close/X Icon -->
|
|
||||||
<symbol id="icon-close" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
||||||
<line x1="18" y1="6" x2="6" y2="18"/>
|
|
||||||
<line x1="6" y1="6" x2="18" y2="18"/>
|
|
||||||
</symbol>
|
|
||||||
|
|
||||||
<!-- Message Icon -->
|
|
||||||
<symbol id="icon-message" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
||||||
<path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"/>
|
|
||||||
</symbol>
|
|
||||||
|
|
||||||
<!-- Layout Icon -->
|
|
||||||
<symbol id="icon-layout" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
||||||
<rect x="3" y="3" width="18" height="18" rx="2" ry="2"/>
|
|
||||||
<line x1="3" y1="9" x2="21" y2="9"/>
|
|
||||||
<line x1="9" y1="21" x2="9" y2="9"/>
|
|
||||||
</symbol>
|
|
||||||
|
|
||||||
<!-- Bell Icon -->
|
|
||||||
<symbol id="icon-bell" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
||||||
<path d="M18 8A6 6 0 0 0 6 8c0 7-3 9-3 9h18s-3-2-3-9"/>
|
|
||||||
<path d="M13.73 21a2 2 0 0 1-3.46 0"/>
|
|
||||||
</symbol>
|
|
||||||
|
|
||||||
<!-- Info Icon -->
|
|
||||||
<symbol id="icon-info" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
||||||
<circle cx="12" cy="12" r="10"/>
|
|
||||||
<line x1="12" y1="16" x2="12" y2="12"/>
|
|
||||||
<line x1="12" y1="8" x2="12.01" y2="8"/>
|
|
||||||
</symbol>
|
|
||||||
|
|
||||||
<!-- Shortcuts Icon -->
|
|
||||||
<symbol id="icon-shortcuts" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
||||||
<path d="M18 3a3 3 0 0 0-3 3v12a3 3 0 0 0 3 3 3 3 0 0 0 3-3 3 3 0 0 0-3-3H6a3 3 0 0 0-3 3 3 3 0 0 0 3 3 3 3 0 0 0 3-3V6a3 3 0 0 0-3-3 3 3 0 0 0-3 3 3 3 0 0 0 3 3h12a3 3 0 0 0 3-3 3 3 0 0 0-3-3z"/>
|
|
||||||
</symbol>
|
|
||||||
</svg>
|
|
||||||
|
|
||||||
<!-- JavaScript Files -->
|
<!-- JavaScript Files -->
|
||||||
<script src="js/storage.js"></script>
|
<script src="js/storage.js"></script>
|
||||||
<script src="js/notifications.js"></script>
|
<script src="js/notifications.js"></script>
|
||||||
<script src="js/version.js"></script>
|
<script src="js/onboarding.js"></script>
|
||||||
<script src="js/backgrounds.js"></script>
|
<script src="js/search.js"></script>
|
||||||
<script src="js/keybinds.js"></script>
|
|
||||||
<script src="js/shortcuts.js"></script>
|
<script src="js/shortcuts.js"></script>
|
||||||
<script src="js/settings.js"></script>
|
<script src="js/settings.js"></script>
|
||||||
<script src="js/grid-layout.js"></script>
|
|
||||||
<script src="js/onboarding.js"></script>
|
|
||||||
<script src="js/cache-handler.js"></script>
|
|
||||||
<script src="js/search.js"></script>
|
|
||||||
<script src="js/main.js"></script>
|
<script src="js/main.js"></script>
|
||||||
|
<script src="js/keybinds.js"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
|
@ -1,159 +0,0 @@
|
||||||
document.addEventListener('DOMContentLoaded', () => {
|
|
||||||
const MAX_IMAGE_SIZE_MB = 1.5;
|
|
||||||
const MAX_USER_BACKGROUNDS = 10;
|
|
||||||
|
|
||||||
const backgroundUpload = document.getElementById('background-upload');
|
|
||||||
const backgroundPreviewGrid = document.getElementById('background-preview-grid');
|
|
||||||
const resetBackground = document.getElementById('default-background');
|
|
||||||
|
|
||||||
resetBackground.style.order = '-1';
|
|
||||||
backgroundPreviewGrid.prepend(resetBackground);
|
|
||||||
|
|
||||||
loadBackgrounds();
|
|
||||||
setSavedBackground();
|
|
||||||
|
|
||||||
backgroundUpload.addEventListener('change', (event) => {
|
|
||||||
const file = event.target.files[0];
|
|
||||||
if (!file) return;
|
|
||||||
|
|
||||||
const maxSizeBytes = MAX_IMAGE_SIZE_MB * 1024 * 1024;
|
|
||||||
if (file.size > maxSizeBytes) {
|
|
||||||
notifications.show(`Image exceeds size limit (max ${MAX_IMAGE_SIZE_MB} MB)`, 'error');
|
|
||||||
event.target.value = '';
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const existingBackgrounds = JSON.parse(Storage.get('backgrounds') || '[]');
|
|
||||||
if (existingBackgrounds.length >= MAX_USER_BACKGROUNDS) {
|
|
||||||
notifications.show(`Maximum custom backgrounds limit reached!`, 'error');
|
|
||||||
event.target.value = '';
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const reader = new FileReader();
|
|
||||||
reader.onload = function (e) {
|
|
||||||
const imageUrl = e.target.result;
|
|
||||||
addBackgroundPreview(imageUrl, false);
|
|
||||||
saveBackground(imageUrl);
|
|
||||||
setCustomBackground(imageUrl);
|
|
||||||
notifications.show('Background uploaded successfully!', 'success');
|
|
||||||
};
|
|
||||||
reader.readAsDataURL(file);
|
|
||||||
});
|
|
||||||
|
|
||||||
resetBackground.addEventListener('click', () => {
|
|
||||||
const currentBackground = Storage.get('customBackground');
|
|
||||||
if (!currentBackground) return;
|
|
||||||
|
|
||||||
document.body.style.backgroundImage = '';
|
|
||||||
Storage.remove('customBackground');
|
|
||||||
removeAllSelection();
|
|
||||||
notifications.show('Background reset to default!', 'success');
|
|
||||||
});
|
|
||||||
|
|
||||||
function loadBackgrounds() {
|
|
||||||
try {
|
|
||||||
const backgrounds = JSON.parse(Storage.get('backgrounds') || '[]');
|
|
||||||
if (!Array.isArray(backgrounds)) {
|
|
||||||
throw new Error('Invalid backgrounds format');
|
|
||||||
}
|
|
||||||
backgrounds.forEach((bg) => {
|
|
||||||
if (typeof bg === 'string' &&
|
|
||||||
(bg.startsWith('data:image/') || bg.startsWith('images/backgrounds/'))) {
|
|
||||||
addBackgroundPreview(bg, false);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} catch (e) {
|
|
||||||
console.error('Failed to load backgrounds:', e);
|
|
||||||
Storage.set('backgrounds', '[]');
|
|
||||||
notifications.show('Corrupted backgrounds data - resetting', 'error');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function setSavedBackground() {
|
|
||||||
const customBackground = Storage.get('customBackground');
|
|
||||||
if (customBackground) {
|
|
||||||
setCustomBackground(customBackground, true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function addBackgroundPreview(imageUrl, isPredefined) {
|
|
||||||
const preview = document.createElement('div');
|
|
||||||
preview.className = 'background-preview' + (isPredefined ? ' predefined' : ' custom');
|
|
||||||
preview.style.backgroundImage = `url(${imageUrl})`;
|
|
||||||
preview.dataset.url = imageUrl;
|
|
||||||
|
|
||||||
if (Storage.get('customBackground') === imageUrl) {
|
|
||||||
preview.classList.add('selected');
|
|
||||||
}
|
|
||||||
|
|
||||||
preview.addEventListener('click', () => {
|
|
||||||
if (Storage.get('customBackground') === imageUrl) return;
|
|
||||||
setCustomBackground(imageUrl);
|
|
||||||
removeAllSelection();
|
|
||||||
preview.classList.add('selected');
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!isPredefined) {
|
|
||||||
const removeIcon = document.createElement('span');
|
|
||||||
removeIcon.className = 'remove-icon';
|
|
||||||
removeIcon.innerHTML = '<i class="fas fa-times"></i>';
|
|
||||||
removeIcon.addEventListener('click', (e) => {
|
|
||||||
e.stopPropagation();
|
|
||||||
const wasSelected = preview.classList.contains('selected');
|
|
||||||
|
|
||||||
if (wasSelected) {
|
|
||||||
document.body.style.backgroundImage = '';
|
|
||||||
Storage.remove('customBackground');
|
|
||||||
}
|
|
||||||
|
|
||||||
removeBackground(imageUrl);
|
|
||||||
preview.remove();
|
|
||||||
notifications.show('Background removed!', 'success');
|
|
||||||
});
|
|
||||||
preview.appendChild(removeIcon);
|
|
||||||
}
|
|
||||||
|
|
||||||
const insertPosition = isPredefined ? 1 : backgroundPreviewGrid.children.length;
|
|
||||||
backgroundPreviewGrid.insertBefore(preview, backgroundPreviewGrid.children[insertPosition]);
|
|
||||||
}
|
|
||||||
|
|
||||||
function removeAllSelection() {
|
|
||||||
document.querySelectorAll('.background-preview').forEach(preview => {
|
|
||||||
preview.classList.remove('selected');
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function removeBackground(imageUrl) {
|
|
||||||
const backgrounds = JSON.parse(Storage.get('backgrounds') || '[]');
|
|
||||||
Storage.set('backgrounds', JSON.stringify(backgrounds.filter(bg => bg !== imageUrl)));
|
|
||||||
}
|
|
||||||
|
|
||||||
function saveBackground(imageUrl) {
|
|
||||||
const backgrounds = JSON.parse(Storage.get('backgrounds') || '[]');
|
|
||||||
if (!backgrounds.includes(imageUrl)) {
|
|
||||||
backgrounds.push(imageUrl);
|
|
||||||
Storage.set('backgrounds', JSON.stringify(backgrounds));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function setCustomBackground(imageUrl, initialLoad = false) {
|
|
||||||
document.body.style.backgroundImage = `url(${imageUrl})`;
|
|
||||||
Storage.set('customBackground', imageUrl);
|
|
||||||
|
|
||||||
if (!initialLoad) {
|
|
||||||
removeAllSelection();
|
|
||||||
const targetPreview = document.querySelector(`.background-preview[data-url="${imageUrl}"]`);
|
|
||||||
if (targetPreview) targetPreview.classList.add('selected');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const predefinedImages = [
|
|
||||||
'images/backgrounds/cherry.png',
|
|
||||||
'images/backgrounds/mommies.png',
|
|
||||||
'images/backgrounds/peachs-castle.png',
|
|
||||||
'images/backgrounds/windows-xp.jpg',
|
|
||||||
];
|
|
||||||
|
|
||||||
predefinedImages.forEach(image => addBackgroundPreview(image, true));
|
|
||||||
});
|
|
|
@ -1,86 +0,0 @@
|
||||||
const CacheUpdater = {
|
|
||||||
update: () => {
|
|
||||||
const shortcuts = Storage.get('shortcuts') || [];
|
|
||||||
const faviconUrls = shortcuts.map(shortcut =>
|
|
||||||
`https://www.google.com/s2/favicons?domain=${encodeURIComponent(new URL(shortcut.url).hostname)}&sz=64`
|
|
||||||
);
|
|
||||||
|
|
||||||
if (navigator.serviceWorker?.controller && faviconUrls.length > 0) {
|
|
||||||
navigator.serviceWorker.controller.postMessage({
|
|
||||||
action: 'updateFavicons',
|
|
||||||
urls: faviconUrls
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const cacheHandler = {
|
|
||||||
SHORTCUT_CACHE_DURATION: 7 * 24 * 60 * 60 * 1000,
|
|
||||||
|
|
||||||
init() {
|
|
||||||
this.cleanExpiredCache();
|
|
||||||
},
|
|
||||||
|
|
||||||
cleanExpiredCache() {
|
|
||||||
const cache = this.getAllCache();
|
|
||||||
const now = Date.now();
|
|
||||||
|
|
||||||
Object.entries(cache).forEach(([key, value]) => {
|
|
||||||
if (key.startsWith('shortcut_') && value.expiry && value.expiry < now) {
|
|
||||||
this.removeFromCache(key);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
addToCache(key, value, isSearchEngine = false) {
|
|
||||||
const cacheItem = {
|
|
||||||
value,
|
|
||||||
timestamp: Date.now(),
|
|
||||||
expiry: isSearchEngine ? null : Date.now() + this.SHORTCUT_CACHE_DURATION
|
|
||||||
};
|
|
||||||
|
|
||||||
localStorage.setItem(key, JSON.stringify(cacheItem));
|
|
||||||
},
|
|
||||||
|
|
||||||
getFromCache(key) {
|
|
||||||
const item = localStorage.getItem(key);
|
|
||||||
if (!item) return null;
|
|
||||||
|
|
||||||
try {
|
|
||||||
const cacheItem = JSON.parse(item);
|
|
||||||
|
|
||||||
if (cacheItem.expiry && cacheItem.expiry < Date.now()) {
|
|
||||||
this.removeFromCache(key);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return cacheItem.value;
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Cache read error:', error);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
removeFromCache(key) {
|
|
||||||
localStorage.removeItem(key);
|
|
||||||
},
|
|
||||||
|
|
||||||
getAllCache() {
|
|
||||||
const cache = {};
|
|
||||||
for (let i = 0; i < localStorage.length; i++) {
|
|
||||||
const key = localStorage.key(i);
|
|
||||||
if (key.startsWith('shortcut_') || key.startsWith('search_engine_')) {
|
|
||||||
try {
|
|
||||||
cache[key] = JSON.parse(localStorage.getItem(key));
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Cache read error:', error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return cache;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
document.addEventListener('DOMContentLoaded', () => {
|
|
||||||
cacheHandler.init();
|
|
||||||
});
|
|
|
@ -1,337 +0,0 @@
|
||||||
const GridLayout = {
|
|
||||||
defaults: {
|
|
||||||
type: 'default',
|
|
||||||
columns: 6,
|
|
||||||
gap: 16,
|
|
||||||
size: 80,
|
|
||||||
resizable: false
|
|
||||||
},
|
|
||||||
|
|
||||||
layouts: {
|
|
||||||
default: {
|
|
||||||
columns: 6,
|
|
||||||
gap: 16,
|
|
||||||
size: 80
|
|
||||||
},
|
|
||||||
compact: {
|
|
||||||
columns: 5,
|
|
||||||
gap: 8,
|
|
||||||
size: 70
|
|
||||||
},
|
|
||||||
comfortable: {
|
|
||||||
columns: 3,
|
|
||||||
gap: 24,
|
|
||||||
size: 100
|
|
||||||
},
|
|
||||||
list: {
|
|
||||||
columns: 1,
|
|
||||||
gap: 12,
|
|
||||||
size: 60
|
|
||||||
},
|
|
||||||
custom: {}
|
|
||||||
},
|
|
||||||
|
|
||||||
init: function() {
|
|
||||||
const settings = this.getSettings();
|
|
||||||
this.applyLayout(settings);
|
|
||||||
this.setupEventListeners();
|
|
||||||
this.toggleCustomSettings(settings.type === 'custom');
|
|
||||||
this.updateUI(settings);
|
|
||||||
},
|
|
||||||
|
|
||||||
getSettings: function() {
|
|
||||||
const storedSettings = Storage.get('gridLayout');
|
|
||||||
return { ...this.defaults, ...storedSettings };
|
|
||||||
},
|
|
||||||
|
|
||||||
saveSettings: function(settings) {
|
|
||||||
Storage.set('gridLayout', settings);
|
|
||||||
},
|
|
||||||
|
|
||||||
updateUI: function(settings) {
|
|
||||||
const layoutTypeSelect = document.getElementById('grid-layout-type');
|
|
||||||
if (layoutTypeSelect) {
|
|
||||||
layoutTypeSelect.value = settings.type;
|
|
||||||
const event = new Event('change');
|
|
||||||
layoutTypeSelect.dispatchEvent(event);
|
|
||||||
|
|
||||||
const customSelectDiv = layoutTypeSelect.closest('.custom-select');
|
|
||||||
if (customSelectDiv) {
|
|
||||||
const selectSelected = customSelectDiv.querySelector('.select-selected');
|
|
||||||
if (selectSelected) {
|
|
||||||
const selectedOption = layoutTypeSelect.options[layoutTypeSelect.selectedIndex];
|
|
||||||
selectSelected.textContent = selectedOption.textContent;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const columnsInput = document.getElementById('grid-columns');
|
|
||||||
const gapInput = document.getElementById('grid-gap');
|
|
||||||
const sizeInput = document.getElementById('grid-size');
|
|
||||||
|
|
||||||
if (columnsInput) columnsInput.value = settings.columns;
|
|
||||||
if (gapInput) gapInput.value = settings.gap;
|
|
||||||
if (sizeInput) {
|
|
||||||
sizeInput.value = settings.size;
|
|
||||||
sizeInput.disabled = !settings.resizable;
|
|
||||||
if (!settings.resizable) {
|
|
||||||
sizeInput.setAttribute('title', 'Enable \'Resizable Items\' to customize item size');
|
|
||||||
sizeInput.style.cursor = 'not-allowed';
|
|
||||||
} else {
|
|
||||||
sizeInput.removeAttribute('title');
|
|
||||||
sizeInput.style.cursor = 'auto';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const resizableToggle = document.getElementById('toggle-resizable');
|
|
||||||
if (resizableToggle) resizableToggle.checked = settings.resizable;
|
|
||||||
|
|
||||||
this.toggleCustomSettings(settings.type === 'custom');
|
|
||||||
},
|
|
||||||
|
|
||||||
applyLayout: function(settings) {
|
|
||||||
const grid = document.getElementById('shortcuts-grid');
|
|
||||||
if (!grid) return;
|
|
||||||
|
|
||||||
grid.classList.remove('grid-default', 'grid-compact', 'grid-comfortable', 'grid-list', 'grid-custom');
|
|
||||||
grid.classList.add(`grid-${settings.type}`);
|
|
||||||
|
|
||||||
if (settings.type === 'custom') {
|
|
||||||
grid.style.gridTemplateColumns = `repeat(${settings.columns}, minmax(${settings.size}px, 1fr))`;
|
|
||||||
grid.style.gap = `${settings.gap}px`;
|
|
||||||
} else {
|
|
||||||
const layoutConfig = this.layouts[settings.type];
|
|
||||||
if (layoutConfig) {
|
|
||||||
grid.style.gridTemplateColumns = `repeat(${layoutConfig.columns}, minmax(${layoutConfig.size}px, 1fr))`;
|
|
||||||
grid.style.gap = `${layoutConfig.gap}px`;
|
|
||||||
|
|
||||||
if (settings.type === 'list') {
|
|
||||||
grid.style.gridTemplateColumns = '1fr';
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
grid.style.gridTemplateColumns = '';
|
|
||||||
grid.style.gap = '';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
this.applyResizable(settings.resizable);
|
|
||||||
},
|
|
||||||
|
|
||||||
setupEventListeners: function() {
|
|
||||||
const layoutTypeSelect = document.getElementById('grid-layout-type');
|
|
||||||
if (layoutTypeSelect) {
|
|
||||||
layoutTypeSelect.addEventListener('change', () => {
|
|
||||||
const settings = this.getSettings();
|
|
||||||
settings.type = layoutTypeSelect.value;
|
|
||||||
this.saveSettings(settings);
|
|
||||||
this.applyLayout(settings);
|
|
||||||
this.toggleCustomSettings(settings.type === 'custom');
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const columnsInput = document.getElementById('grid-columns');
|
|
||||||
const gapInput = document.getElementById('grid-gap');
|
|
||||||
const sizeInput = document.getElementById('grid-size');
|
|
||||||
|
|
||||||
const validateInputValue = (input) => {
|
|
||||||
const min = parseInt(input.getAttribute('min'), 10);
|
|
||||||
const max = parseInt(input.getAttribute('max'), 10);
|
|
||||||
let value = parseInt(input.value, 10);
|
|
||||||
|
|
||||||
if (isNaN(value)) {
|
|
||||||
value = parseInt(input.defaultValue, 10);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (value < min) value = min;
|
|
||||||
if (value > max) value = max;
|
|
||||||
|
|
||||||
input.value = value;
|
|
||||||
return value;
|
|
||||||
};
|
|
||||||
|
|
||||||
[columnsInput, gapInput, sizeInput].forEach(input => {
|
|
||||||
if (input) {
|
|
||||||
input.addEventListener('input', () => {
|
|
||||||
validateInputValue(input);
|
|
||||||
});
|
|
||||||
|
|
||||||
input.addEventListener('change', () => {
|
|
||||||
const value = validateInputValue(input);
|
|
||||||
const settings = this.getSettings();
|
|
||||||
settings[input.id.split('-')[1]] = value;
|
|
||||||
this.saveSettings(settings);
|
|
||||||
this.applyLayout(settings);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
const resizableToggle = document.getElementById('toggle-resizable');
|
|
||||||
if (resizableToggle) {
|
|
||||||
resizableToggle.addEventListener('change', () => {
|
|
||||||
const settings = this.getSettings();
|
|
||||||
settings.resizable = resizableToggle.checked;
|
|
||||||
this.saveSettings(settings);
|
|
||||||
this.applyLayout(settings);
|
|
||||||
this.toggleItemSizeInput(settings.resizable);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const resetButton = document.getElementById('reset-layout');
|
|
||||||
if (resetButton) {
|
|
||||||
resetButton.addEventListener('click', () => {
|
|
||||||
this.resetToDefaults();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
toggleItemSizeInput: function(enabled) {
|
|
||||||
const sizeInput = document.getElementById('grid-size');
|
|
||||||
if (sizeInput) {
|
|
||||||
sizeInput.disabled = !enabled;
|
|
||||||
if (enabled) {
|
|
||||||
sizeInput.removeAttribute('title');
|
|
||||||
sizeInput.style.cursor = 'auto';
|
|
||||||
} else {
|
|
||||||
sizeInput.setAttribute('title', 'Enable \'Resizable Items\' to customize item size');
|
|
||||||
sizeInput.style.cursor = 'not-allowed';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
toggleCustomSettings: function(show) {
|
|
||||||
const customSettings = document.getElementById('custom-grid-settings');
|
|
||||||
if (customSettings) {
|
|
||||||
if (show) {
|
|
||||||
customSettings.classList.remove('hidden');
|
|
||||||
} else {
|
|
||||||
customSettings.classList.add('hidden');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
applyResizable: function(resizable) {
|
|
||||||
const shortcuts = document.querySelectorAll('.shortcut');
|
|
||||||
|
|
||||||
shortcuts.forEach(shortcut => {
|
|
||||||
const existingHandle = shortcut.querySelector('.resize-handle');
|
|
||||||
if (existingHandle) {
|
|
||||||
existingHandle.remove();
|
|
||||||
}
|
|
||||||
|
|
||||||
shortcut.classList.remove('resizable');
|
|
||||||
|
|
||||||
if (resizable) {
|
|
||||||
shortcut.classList.add('resizable');
|
|
||||||
|
|
||||||
const resizeHandle = document.createElement('div');
|
|
||||||
resizeHandle.className = 'resize-handle';
|
|
||||||
shortcut.appendChild(resizeHandle);
|
|
||||||
|
|
||||||
this.setupResizeEvents(shortcut, resizeHandle);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
setupResizeEvents: function(shortcut, handle) {
|
|
||||||
let startX, startY, startWidth, startHeight;
|
|
||||||
|
|
||||||
const startResize = (e) => {
|
|
||||||
e.preventDefault();
|
|
||||||
shortcut.classList.add('resizing');
|
|
||||||
|
|
||||||
startX = e.clientX;
|
|
||||||
startY = e.clientY;
|
|
||||||
startWidth = shortcut.offsetWidth;
|
|
||||||
startHeight = shortcut.offsetHeight;
|
|
||||||
|
|
||||||
document.addEventListener('mousemove', resize);
|
|
||||||
document.addEventListener('mouseup', stopResize);
|
|
||||||
};
|
|
||||||
|
|
||||||
const resize = (e) => {
|
|
||||||
const newWidth = startWidth + (e.clientX - startX);
|
|
||||||
const newHeight = startHeight + (e.clientY - startY);
|
|
||||||
|
|
||||||
shortcut.style.width = `${Math.max(80, newWidth)}px`;
|
|
||||||
shortcut.style.height = `${Math.max(80, newHeight)}px`;
|
|
||||||
};
|
|
||||||
|
|
||||||
const stopResize = () => {
|
|
||||||
shortcut.classList.remove('resizing');
|
|
||||||
document.removeEventListener('mousemove', resize);
|
|
||||||
document.removeEventListener('mouseup', stopResize);
|
|
||||||
};
|
|
||||||
|
|
||||||
handle.addEventListener('mousedown', startResize);
|
|
||||||
},
|
|
||||||
|
|
||||||
resetToDefaults: function() {
|
|
||||||
this.saveSettings(this.defaults);
|
|
||||||
this.updateUI(this.defaults);
|
|
||||||
this.applyLayout(this.defaults);
|
|
||||||
this.toggleCustomSettings(false);
|
|
||||||
|
|
||||||
const visibilitySettings = {
|
|
||||||
showGreeting: true,
|
|
||||||
showSearch: true,
|
|
||||||
showShortcuts: true,
|
|
||||||
showAddButton: true,
|
|
||||||
showGrid: true
|
|
||||||
};
|
|
||||||
|
|
||||||
Storage.set('visibility', visibilitySettings);
|
|
||||||
Storage.set('show_greeting', true);
|
|
||||||
Storage.set('show_search', true);
|
|
||||||
Storage.set('show_shortcuts', true);
|
|
||||||
Storage.set('show_addShortcut', true);
|
|
||||||
|
|
||||||
const greetingToggle = document.getElementById('toggle-greeting');
|
|
||||||
const searchToggle = document.getElementById('toggle-search');
|
|
||||||
const shortcutsToggle = document.getElementById('toggle-shortcuts');
|
|
||||||
const addButtonToggle = document.getElementById('toggle-add-shortcut');
|
|
||||||
|
|
||||||
if (greetingToggle) greetingToggle.checked = true;
|
|
||||||
if (searchToggle) searchToggle.checked = true;
|
|
||||||
if (shortcutsToggle) shortcutsToggle.checked = true;
|
|
||||||
if (addButtonToggle) addButtonToggle.checked = true;
|
|
||||||
|
|
||||||
const greeting = document.getElementById('greeting');
|
|
||||||
const search = document.getElementById('search-container');
|
|
||||||
const shortcuts = document.getElementById('shortcuts-grid');
|
|
||||||
const addButton = document.getElementById('add-shortcut');
|
|
||||||
|
|
||||||
const showElement = (element) => {
|
|
||||||
if (element) {
|
|
||||||
element.style.visibility = 'visible';
|
|
||||||
element.style.opacity = '1';
|
|
||||||
element.style.position = 'relative';
|
|
||||||
element.style.pointerEvents = 'auto';
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
showElement(greeting);
|
|
||||||
showElement(search);
|
|
||||||
showElement(shortcuts);
|
|
||||||
showElement(addButton);
|
|
||||||
},
|
|
||||||
|
|
||||||
reset: function() {
|
|
||||||
this.resetToDefaults();
|
|
||||||
|
|
||||||
notifications.show('Layout settings reset to defaults.', 'success');
|
|
||||||
|
|
||||||
return this.defaults;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
document.addEventListener('DOMContentLoaded', () => {
|
|
||||||
GridLayout.init();
|
|
||||||
});
|
|
||||||
|
|
||||||
window.addEventListener('load', () => {
|
|
||||||
setTimeout(() => {
|
|
||||||
if (!window.gridLayoutInitialized) {
|
|
||||||
GridLayout.init();
|
|
||||||
}
|
|
||||||
}, 500);
|
|
||||||
});
|
|
|
@ -1,8 +1,8 @@
|
||||||
|
// List of keys that cannot be used as keybinds
|
||||||
const FORBIDDEN_KEYS = [
|
const FORBIDDEN_KEYS = [
|
||||||
'Tab', 'CapsLock', 'Meta', 'ContextMenu',
|
'Tab', 'CapsLock', 'Meta', 'ContextMenu',
|
||||||
'F1', 'F2', 'F3', 'F4', 'F5', 'F6', 'F7', 'F8', 'F9', 'F10', 'F11', 'F12',
|
'F1', 'F2', 'F3', 'F4', 'F5', 'F6', 'F7', 'F8', 'F9', 'F10', 'F11', 'F12',
|
||||||
'Home', 'End', 'PageUp', 'PageDown', 'Insert', 'Delete', 'ScrollLock', 'Pause', 'NumLock',
|
'Home', 'End', 'PageUp', 'PageDown', 'Insert', 'Delete', 'ScrollLock', 'Pause', 'NumLock'
|
||||||
'/'
|
|
||||||
];
|
];
|
||||||
|
|
||||||
const keybinds = {
|
const keybinds = {
|
||||||
|
@ -11,6 +11,7 @@ const keybinds = {
|
||||||
init() {
|
init() {
|
||||||
this.bindings = Storage.get('keybinds') || {};
|
this.bindings = Storage.get('keybinds') || {};
|
||||||
|
|
||||||
|
// URL keybind handling
|
||||||
const urlInput = document.getElementById('keybind-url');
|
const urlInput = document.getElementById('keybind-url');
|
||||||
const urlComboInput = document.getElementById('keybind-url-combo');
|
const urlComboInput = document.getElementById('keybind-url-combo');
|
||||||
|
|
||||||
|
@ -66,6 +67,7 @@ const keybinds = {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Keybind input handling
|
||||||
const keybindInputs = document.querySelectorAll('[id^="keybind-"]');
|
const keybindInputs = document.querySelectorAll('[id^="keybind-"]');
|
||||||
keybindInputs.forEach(input => {
|
keybindInputs.forEach(input => {
|
||||||
if (input.id === 'keybind-url') {
|
if (input.id === 'keybind-url') {
|
||||||
|
@ -166,6 +168,7 @@ const keybinds = {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Clear keybind button handling
|
||||||
document.querySelectorAll('.clear-keybind').forEach(button => {
|
document.querySelectorAll('.clear-keybind').forEach(button => {
|
||||||
button.addEventListener('click', () => {
|
button.addEventListener('click', () => {
|
||||||
const action = button.dataset.for;
|
const action = button.dataset.for;
|
||||||
|
@ -183,6 +186,7 @@ const keybinds = {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Global keybind listener
|
||||||
document.addEventListener('keydown', (e) => {
|
document.addEventListener('keydown', (e) => {
|
||||||
if (e.target.tagName === 'INPUT') return;
|
if (e.target.tagName === 'INPUT') return;
|
||||||
|
|
||||||
|
@ -202,29 +206,23 @@ const keybinds = {
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// Execute the action associated with a keybind
|
||||||
executeAction(action, binding) {
|
executeAction(action, binding) {
|
||||||
const settingsPage = document.getElementById('settings-page');
|
|
||||||
if (settingsPage.classList.contains('active')) {
|
|
||||||
settingsPage.classList.remove('active');
|
|
||||||
setTimeout(() => {
|
|
||||||
settingsPage.classList.add('hidden');
|
|
||||||
}, 300);
|
|
||||||
}
|
|
||||||
|
|
||||||
const activeModal = document.querySelector('.modal.active');
|
const activeModal = document.querySelector('.modal.active');
|
||||||
|
if (activeModal) {
|
||||||
|
closeModal(activeModal);
|
||||||
|
}
|
||||||
|
|
||||||
switch (action) {
|
switch (action) {
|
||||||
case 'settings':
|
case 'settings':
|
||||||
if (settingsPage.classList.contains('hidden')) {
|
const settingsModal = document.getElementById('settings-modal');
|
||||||
notifications.show('Opening settings.', 'info');
|
if (settingsModal === activeModal) {
|
||||||
settings.updateSettingsUI();
|
|
||||||
settingsPage.classList.remove('hidden');
|
|
||||||
setTimeout(() => {
|
|
||||||
settingsPage.classList.add('active');
|
|
||||||
}, 10);
|
|
||||||
} else {
|
|
||||||
notifications.show('Settings closed.', 'info');
|
notifications.show('Settings closed.', 'info');
|
||||||
settings.updateSettingsUI();
|
settings.updateSettingsUI();
|
||||||
|
} else {
|
||||||
|
notifications.show('Opening settings...', 'info');
|
||||||
|
settings.updateSettingsUI();
|
||||||
|
openModal(settingsModal);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case 'add-shortcut':
|
case 'add-shortcut':
|
||||||
|
@ -236,9 +234,9 @@ const keybinds = {
|
||||||
|
|
||||||
const shortcutModal = document.getElementById('add-shortcut-modal');
|
const shortcutModal = document.getElementById('add-shortcut-modal');
|
||||||
if (shortcutModal === activeModal) {
|
if (shortcutModal === activeModal) {
|
||||||
notifications.show('Add shortcut menu closed.', 'info');
|
notifications.show('Add shortcut closed.', 'info');
|
||||||
} else {
|
} else {
|
||||||
notifications.show('Opening add shortcut menu.', 'info');
|
notifications.show('Opening add shortcut...', 'info');
|
||||||
openModal(shortcutModal);
|
openModal(shortcutModal);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
29
js/main.js
|
@ -1,7 +1,4 @@
|
||||||
if ('serviceWorker' in navigator) {
|
// Greeting functionality
|
||||||
navigator.serviceWorker.register('/sw.js');
|
|
||||||
}
|
|
||||||
|
|
||||||
async function updateGreeting() {
|
async function updateGreeting() {
|
||||||
const greeting = document.getElementById('greeting');
|
const greeting = document.getElementById('greeting');
|
||||||
if (!greeting) return;
|
if (!greeting) return;
|
||||||
|
@ -38,6 +35,7 @@ async function updateGreeting() {
|
||||||
}, 100);
|
}, 100);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Modal handling
|
||||||
function initModalHandlers() {
|
function initModalHandlers() {
|
||||||
const modals = document.querySelectorAll('.modal');
|
const modals = document.querySelectorAll('.modal');
|
||||||
|
|
||||||
|
@ -68,12 +66,6 @@ function initModalHandlers() {
|
||||||
|
|
||||||
function openModal(modal) {
|
function openModal(modal) {
|
||||||
if (!modal) return;
|
if (!modal) return;
|
||||||
|
|
||||||
const contextMenu = document.querySelector('.context-menu');
|
|
||||||
if (contextMenu) {
|
|
||||||
contextMenu.classList.add('hidden');
|
|
||||||
}
|
|
||||||
|
|
||||||
modal.classList.remove('hidden');
|
modal.classList.remove('hidden');
|
||||||
requestAnimationFrame(() => {
|
requestAnimationFrame(() => {
|
||||||
modal.classList.add('active');
|
modal.classList.add('active');
|
||||||
|
@ -88,25 +80,15 @@ function closeModal(modal) {
|
||||||
}, 300);
|
}, 300);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Application initialization
|
||||||
document.addEventListener('DOMContentLoaded', () => {
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
if (typeof settings !== 'undefined' && typeof settings.updateVisibility === 'function') {
|
|
||||||
settings.updateVisibility();
|
|
||||||
} else {
|
|
||||||
['greeting', 'search', 'shortcuts', 'addShortcut'].forEach(element => {
|
['greeting', 'search', 'shortcuts', 'addShortcut'].forEach(element => {
|
||||||
const isVisible = Storage.get(`show_${element}`);
|
const isVisible = Storage.get(`show_${element}`);
|
||||||
if (isVisible === false) {
|
if (isVisible === false) {
|
||||||
const elementNode = document.getElementById(element === 'search' ? 'search-container' :
|
const elementNode = document.getElementById(element === 'search' ? 'search-container' : element);
|
||||||
element === 'addShortcut' ? 'add-shortcut' : element);
|
if (elementNode) elementNode.style.display = 'none';
|
||||||
|
|
||||||
if (elementNode) {
|
|
||||||
elementNode.style.visibility = 'hidden';
|
|
||||||
elementNode.style.opacity = '0';
|
|
||||||
elementNode.style.position = 'absolute';
|
|
||||||
elementNode.style.pointerEvents = 'none';
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
|
||||||
|
|
||||||
if (!Storage.get('onboardingComplete')) {
|
if (!Storage.get('onboardingComplete')) {
|
||||||
onboarding.start();
|
onboarding.start();
|
||||||
|
@ -132,6 +114,7 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||||
keybinds.init();
|
keybinds.init();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Global keydown event handler
|
||||||
document.addEventListener('keydown', (e) => {
|
document.addEventListener('keydown', (e) => {
|
||||||
if (e.key === 'Enter') {
|
if (e.key === 'Enter') {
|
||||||
const activeModal = document.querySelector('.modal.active');
|
const activeModal = document.querySelector('.modal.active');
|
||||||
|
|
|
@ -1,19 +1,23 @@
|
||||||
|
// Notification System Class
|
||||||
class NotificationSystem {
|
class NotificationSystem {
|
||||||
constructor() {
|
constructor() {
|
||||||
this.container = document.getElementById('notification-container');
|
this.container = document.getElementById('notification-container');
|
||||||
this.notifications = new Map();
|
this.notifications = new Map();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Display a new notification
|
||||||
show(message, type = 'info', duration = 3000) {
|
show(message, type = 'info', duration = 3000) {
|
||||||
const id = Date.now().toString();
|
const id = Date.now().toString();
|
||||||
const notification = document.createElement('div');
|
const notification = document.createElement('div');
|
||||||
notification.className = `notification notification-${type}`;
|
notification.className = `notification notification-${type}`;
|
||||||
|
|
||||||
|
// Create notification elements
|
||||||
const icon = this.createIcon(type);
|
const icon = this.createIcon(type);
|
||||||
const content = this.createContent(message);
|
const content = this.createContent(message);
|
||||||
const closeBtn = this.createCloseButton(id);
|
const closeBtn = this.createCloseButton(id);
|
||||||
const progress = this.createProgressBar(type);
|
const progress = this.createProgressBar(type);
|
||||||
|
|
||||||
|
// Assemble notification
|
||||||
notification.appendChild(icon);
|
notification.appendChild(icon);
|
||||||
notification.appendChild(content);
|
notification.appendChild(content);
|
||||||
notification.appendChild(closeBtn);
|
notification.appendChild(closeBtn);
|
||||||
|
@ -21,8 +25,10 @@ class NotificationSystem {
|
||||||
|
|
||||||
this.container.appendChild(notification);
|
this.container.appendChild(notification);
|
||||||
|
|
||||||
|
// Set removal timer
|
||||||
setTimeout(() => this.remove(id), duration);
|
setTimeout(() => this.remove(id), duration);
|
||||||
|
|
||||||
|
// Store notification reference
|
||||||
this.notifications.set(id, {
|
this.notifications.set(id, {
|
||||||
element: notification,
|
element: notification,
|
||||||
duration
|
duration
|
||||||
|
@ -33,6 +39,7 @@ class NotificationSystem {
|
||||||
return id;
|
return id;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Remove a notification
|
||||||
remove(id) {
|
remove(id) {
|
||||||
const notification = this.notifications.get(id);
|
const notification = this.notifications.get(id);
|
||||||
if (notification) {
|
if (notification) {
|
||||||
|
@ -44,6 +51,7 @@ class NotificationSystem {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Update progress bar
|
||||||
updateProgress(id) {
|
updateProgress(id) {
|
||||||
const notification = this.notifications.get(id);
|
const notification = this.notifications.get(id);
|
||||||
if (notification) {
|
if (notification) {
|
||||||
|
@ -64,6 +72,7 @@ class NotificationSystem {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Helper methods for creating notification elements
|
||||||
createIcon(type) {
|
createIcon(type) {
|
||||||
const icon = document.createElement('i');
|
const icon = document.createElement('i');
|
||||||
switch(type) {
|
switch(type) {
|
||||||
|
@ -87,7 +96,7 @@ class NotificationSystem {
|
||||||
createContent(message) {
|
createContent(message) {
|
||||||
const content = document.createElement('div');
|
const content = document.createElement('div');
|
||||||
content.className = 'notification-content';
|
content.className = 'notification-content';
|
||||||
content.innerHTML = message;
|
content.textContent = message;
|
||||||
return content;
|
return content;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -118,4 +127,5 @@ class NotificationSystem {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Initialize the notification system
|
||||||
const notifications = new NotificationSystem();
|
const notifications = new NotificationSystem();
|
381
js/onboarding.js
|
@ -1,64 +1,31 @@
|
||||||
|
// Onboarding module
|
||||||
const onboarding = {
|
const onboarding = {
|
||||||
currentStep: 1,
|
// Core functions
|
||||||
totalSteps: 5,
|
|
||||||
settings: {},
|
|
||||||
lastNotification: 0,
|
|
||||||
isCompleting: false,
|
|
||||||
notificationShown: false,
|
|
||||||
|
|
||||||
showNotification(message, type = 'info') {
|
|
||||||
const now = Date.now();
|
|
||||||
if (now - this.lastNotification >= 500) {
|
|
||||||
this.lastNotification = now;
|
|
||||||
notifications.show(message, type);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
isComplete: () => {
|
isComplete: () => {
|
||||||
return Storage.get('onboardingComplete') === true;
|
return Storage.get('onboardingComplete') === true;
|
||||||
},
|
},
|
||||||
|
|
||||||
start: () => {
|
start: () => {
|
||||||
const onboardingContainer = document.getElementById('onboarding-container');
|
const modal = document.getElementById('onboarding-modal');
|
||||||
const mainContent = document.getElementById('main-content');
|
const mainContent = document.getElementById('main-content');
|
||||||
|
const importDataBtn = document.getElementById('import-data-btn');
|
||||||
|
const startFreshBtn = document.getElementById('start-fresh-btn');
|
||||||
const fileInput = document.getElementById('onboarding-import');
|
const fileInput = document.getElementById('onboarding-import');
|
||||||
|
|
||||||
document.getElementById('notification-container').style.zIndex = "20000";
|
|
||||||
|
|
||||||
if (!onboarding.isComplete()) {
|
if (!onboarding.isComplete()) {
|
||||||
document.body.style.overflow = 'hidden';
|
modal.classList.remove('hidden');
|
||||||
onboardingContainer.classList.remove('hidden');
|
modal.classList.add('active');
|
||||||
|
mainContent.classList.add('hidden');
|
||||||
|
|
||||||
onboarding.initProgressDots();
|
startFreshBtn.addEventListener('click', () => {
|
||||||
onboarding.setupEventListeners();
|
document.querySelector('[data-step="1"]').classList.add('hidden');
|
||||||
|
document.querySelector('[data-step="2"]').classList.remove('hidden');
|
||||||
const theme = Storage.get('theme') || 'light';
|
document.getElementById('next-step-btn').addEventListener('click', () => onboarding.nextStep(2));
|
||||||
document.body.setAttribute('data-theme', theme);
|
|
||||||
|
|
||||||
document.querySelectorAll('.step-ob').forEach(step => {
|
|
||||||
if (step.dataset.step !== "1") {
|
|
||||||
step.classList.remove('active-ob');
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const firstStep = document.querySelector('.step-ob[data-step="1"]');
|
importDataBtn.addEventListener('click', () => fileInput.click());
|
||||||
firstStep.classList.add('active-ob');
|
|
||||||
|
|
||||||
document.getElementById('prev-step').style.visibility = 'hidden';
|
|
||||||
|
|
||||||
const nextButton = document.getElementById('next-step');
|
|
||||||
nextButton.innerHTML = 'Next <svg><use href="#icon-arrow-right"/></svg>';
|
|
||||||
|
|
||||||
if (onboarding.currentStep > 1) {
|
|
||||||
nextButton.disabled = true;
|
|
||||||
nextButton.classList.add('disabled-ob');
|
|
||||||
}
|
|
||||||
|
|
||||||
mainContent.classList.add('hidden');
|
|
||||||
} else {
|
|
||||||
mainContent.classList.remove('hidden');
|
|
||||||
}
|
|
||||||
|
|
||||||
|
// Data import handling
|
||||||
fileInput.addEventListener('change', async (e) => {
|
fileInput.addEventListener('change', async (e) => {
|
||||||
if (e.target.files.length > 0) {
|
if (e.target.files.length > 0) {
|
||||||
try {
|
try {
|
||||||
|
@ -66,8 +33,10 @@ const onboarding = {
|
||||||
const text = await file.text();
|
const text = await file.text();
|
||||||
const data = JSON.parse(text);
|
const data = JSON.parse(text);
|
||||||
|
|
||||||
if (!data.settings || !data.shortcuts || !Array.isArray(data.shortcuts)) {
|
if (!data.settings || typeof data.settings !== 'object' ||
|
||||||
|
!Array.isArray(data.shortcuts)) {
|
||||||
throw new Error('Invalid data structure');
|
throw new Error('Invalid data structure');
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Object.entries(data.settings).forEach(([key, value]) => {
|
Object.entries(data.settings).forEach(([key, value]) => {
|
||||||
|
@ -80,248 +49,88 @@ const onboarding = {
|
||||||
Storage.set('keybinds', data.keybinds);
|
Storage.set('keybinds', data.keybinds);
|
||||||
}
|
}
|
||||||
|
|
||||||
Storage.set('onboardingComplete', true);
|
// Initialize components
|
||||||
|
|
||||||
localStorage.setItem('showWelcomeAfterImport', 'true');
|
|
||||||
|
|
||||||
window.location.reload();
|
|
||||||
} catch (error) {
|
|
||||||
onboarding.showNotification('Failed to import data: Invalid file format!', 'error');
|
|
||||||
fileInput.value = '';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
setupEventListeners: () => {
|
|
||||||
document.querySelectorAll('.option-card-ob').forEach(card => {
|
|
||||||
card.addEventListener('click', () => {
|
|
||||||
const step = card.closest('.step-ob');
|
|
||||||
const stepNumber = parseInt(step.dataset.step);
|
|
||||||
const cards = step.querySelectorAll('.option-card-ob');
|
|
||||||
const nextButton = document.getElementById('next-step');
|
|
||||||
|
|
||||||
if (card.dataset.action === 'import-data') {
|
|
||||||
if (!card.classList.contains('selected-ob')) {
|
|
||||||
document.getElementById('onboarding-import').click();
|
|
||||||
}
|
|
||||||
cards.forEach(c => c.classList.remove('selected-ob'));
|
|
||||||
card.classList.add('selected-ob');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
cards.forEach(c => c.classList.remove('selected-ob'));
|
|
||||||
card.classList.add('selected-ob');
|
|
||||||
|
|
||||||
card.style.transform = 'scale(1.05)';
|
|
||||||
setTimeout(() => {
|
|
||||||
card.style.transform = 'scale(1.02)';
|
|
||||||
}, 150);
|
|
||||||
|
|
||||||
nextButton.disabled = false;
|
|
||||||
nextButton.classList.remove('disabled-ob');
|
|
||||||
|
|
||||||
if (card.dataset.theme) {
|
|
||||||
onboarding.settings.theme = card.dataset.theme;
|
|
||||||
document.body.setAttribute('data-theme', card.dataset.theme);
|
|
||||||
} else if (card.dataset.font) {
|
|
||||||
onboarding.settings.fontFamily = card.dataset.font;
|
|
||||||
document.documentElement.style.setProperty('--font-family', card.dataset.font);
|
|
||||||
} else if (card.dataset.engine) {
|
|
||||||
onboarding.settings.searchEngine = card.dataset.engine;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
const nameInput = document.getElementById('user-name');
|
|
||||||
const nextButton = document.getElementById('next-step');
|
|
||||||
|
|
||||||
nameInput.addEventListener('input', (e) => {
|
|
||||||
const name = e.target.value.trim();
|
|
||||||
if (name) {
|
|
||||||
onboarding.settings.userName = name;
|
|
||||||
|
|
||||||
if (onboarding.currentStep === 4) {
|
|
||||||
nextButton.disabled = false;
|
|
||||||
nextButton.classList.remove('disabled-ob');
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (onboarding.currentStep === 4) {
|
|
||||||
nextButton.disabled = true;
|
|
||||||
nextButton.classList.add('disabled-ob');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
document.getElementById('prev-step').addEventListener('click', () => {
|
|
||||||
if (onboarding.currentStep > 1) {
|
|
||||||
onboarding.navigateToStep(onboarding.currentStep - 1);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
document.getElementById('next-step').addEventListener('click', () => {
|
|
||||||
let canProceed = true;
|
|
||||||
|
|
||||||
if (onboarding.currentStep === 2 && !onboarding.settings.theme) {
|
|
||||||
onboarding.showNotification('Please select a theme!', 'error');
|
|
||||||
canProceed = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
else if (onboarding.currentStep === 3 && !onboarding.settings.fontFamily) {
|
|
||||||
onboarding.showNotification('Please select a font!', 'error');
|
|
||||||
canProceed = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
else if (onboarding.currentStep === 4) {
|
|
||||||
const name = document.getElementById('user-name').value.trim();
|
|
||||||
|
|
||||||
if (!name) {
|
|
||||||
onboarding.showNotification('Please enter your name!', 'error');
|
|
||||||
canProceed = false;
|
|
||||||
} else {
|
|
||||||
onboarding.settings.userName = name;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
else if (onboarding.currentStep === 5 && !onboarding.settings.searchEngine) {
|
|
||||||
onboarding.showNotification('Please select a search engine!', 'error');
|
|
||||||
canProceed = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (canProceed) {
|
|
||||||
if (onboarding.currentStep < onboarding.totalSteps) {
|
|
||||||
onboarding.navigateToStep(onboarding.currentStep + 1);
|
|
||||||
} else {
|
|
||||||
onboarding.isCompleting = true;
|
|
||||||
localStorage.setItem('showWelcomeAfterImport', 'true');
|
|
||||||
onboarding.complete();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
document.querySelectorAll('.step-ob').forEach(step => {
|
|
||||||
if (step.dataset.step !== "1" && step.dataset.step !== "4") {
|
|
||||||
const firstOption = step.querySelector('.option-card-ob');
|
|
||||||
if (firstOption) {
|
|
||||||
setTimeout(() => {
|
|
||||||
firstOption.click();
|
|
||||||
}, 100);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
navigateToStep: (step) => {
|
|
||||||
const prevButton = document.getElementById('prev-step');
|
|
||||||
const nextButton = document.getElementById('next-step');
|
|
||||||
|
|
||||||
const currentStepEl = document.querySelector(`.step-ob[data-step="${onboarding.currentStep}"]`);
|
|
||||||
if (currentStepEl) {
|
|
||||||
currentStepEl.classList.remove('active-ob');
|
|
||||||
}
|
|
||||||
|
|
||||||
setTimeout(() => {
|
|
||||||
const targetStepEl = document.querySelector(`.step-ob[data-step="${step}"]`);
|
|
||||||
if (targetStepEl) {
|
|
||||||
targetStepEl.classList.add('active-ob');
|
|
||||||
}
|
|
||||||
|
|
||||||
onboarding.currentStep = step;
|
|
||||||
|
|
||||||
prevButton.style.visibility = step === 1 ? 'hidden' : 'visible';
|
|
||||||
|
|
||||||
if (step === onboarding.totalSteps) {
|
|
||||||
nextButton.innerHTML = 'Get Started <svg><use href="#icon-sparkle"/></svg>';
|
|
||||||
} else {
|
|
||||||
nextButton.innerHTML = 'Next <svg><use href="#icon-arrow-right"/></svg>';
|
|
||||||
}
|
|
||||||
|
|
||||||
if ((step === 2 && !onboarding.settings.theme) ||
|
|
||||||
(step === 3 && !onboarding.settings.fontFamily) ||
|
|
||||||
(step === 5 && !onboarding.settings.searchEngine)) {
|
|
||||||
nextButton.disabled = true;
|
|
||||||
nextButton.classList.add('disabled-ob');
|
|
||||||
} else if (step === 4) {
|
|
||||||
const name = document.getElementById('user-name').value.trim();
|
|
||||||
if (!name) {
|
|
||||||
nextButton.disabled = true;
|
|
||||||
nextButton.classList.add('disabled-ob');
|
|
||||||
} else {
|
|
||||||
nextButton.disabled = false;
|
|
||||||
nextButton.classList.remove('disabled-ob');
|
|
||||||
}
|
|
||||||
} else if (step === 1) {
|
|
||||||
nextButton.disabled = false;
|
|
||||||
nextButton.classList.remove('disabled-ob');
|
|
||||||
}
|
|
||||||
|
|
||||||
onboarding.updateProgressDots();
|
|
||||||
}, 100);
|
|
||||||
},
|
|
||||||
|
|
||||||
initProgressDots: () => {
|
|
||||||
const container = document.querySelector('.progress-dots-ob');
|
|
||||||
container.innerHTML = '';
|
|
||||||
|
|
||||||
for (let i = 0; i < onboarding.totalSteps; i++) {
|
|
||||||
const dot = document.createElement('div');
|
|
||||||
dot.className = 'dot-ob' + (i === 0 ? ' active-ob' : '');
|
|
||||||
container.appendChild(dot);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
updateProgressDots: () => {
|
|
||||||
const dots = document.querySelectorAll('.dot-ob');
|
|
||||||
dots.forEach((dot, index) => {
|
|
||||||
dot.classList.toggle('active-ob', index + 1 === onboarding.currentStep);
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
complete: () => {
|
|
||||||
const onboardingContainer = document.getElementById('onboarding-container');
|
|
||||||
const mainContent = document.getElementById('main-content');
|
|
||||||
|
|
||||||
if (!onboarding.settings.theme) onboarding.settings.theme = 'light';
|
|
||||||
if (!onboarding.settings.fontFamily) onboarding.settings.fontFamily = 'Inter';
|
|
||||||
if (!onboarding.settings.searchEngine) onboarding.settings.searchEngine = 'google';
|
|
||||||
if (!onboarding.settings.userName) onboarding.settings.userName = 'User';
|
|
||||||
|
|
||||||
document.body.setAttribute('data-theme', onboarding.settings.theme);
|
|
||||||
document.documentElement.style.setProperty('--font-family', onboarding.settings.fontFamily);
|
|
||||||
|
|
||||||
Object.entries(onboarding.settings).forEach(([key, value]) => {
|
|
||||||
Storage.set(key, value);
|
|
||||||
});
|
|
||||||
|
|
||||||
Storage.set('onboardingComplete', true);
|
|
||||||
|
|
||||||
setTimeout(() => {
|
|
||||||
onboardingContainer.classList.add('hidden');
|
|
||||||
mainContent.classList.remove('hidden');
|
|
||||||
document.body.style.overflow = '';
|
|
||||||
|
|
||||||
search.init();
|
search.init();
|
||||||
shortcuts.init();
|
shortcuts.init();
|
||||||
settings.init();
|
settings.init();
|
||||||
updateGreeting();
|
updateGreeting();
|
||||||
|
document.body.setAttribute('data-theme', data.settings.theme || 'light');
|
||||||
|
|
||||||
setTimeout(() => {
|
// Update UI visibility
|
||||||
if (!onboarding.notificationShown) {
|
['greeting', 'search', 'shortcuts', 'addShortcut'].forEach(element => {
|
||||||
onboarding.notificationShown = true;
|
const isVisible = data.settings[`show_${element}`];
|
||||||
onboarding.showNotification('Welcome to your new JSTAR Tab! 🎉', 'success');
|
const elementNode = document.getElementById(element === 'search' ? 'search-container' : element);
|
||||||
}
|
if (elementNode) {
|
||||||
}, 100);
|
elementNode.style.display = isVisible === false ? 'none' : 'block';
|
||||||
}, 500);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
document.addEventListener('DOMContentLoaded', () => {
|
|
||||||
onboarding.start();
|
|
||||||
|
|
||||||
if (onboarding.isComplete() && localStorage.getItem('showWelcomeAfterImport') === 'true') {
|
|
||||||
localStorage.removeItem('showWelcomeAfterImport');
|
|
||||||
setTimeout(() => {
|
|
||||||
notifications.show('Welcome to your new JSTAR Tab! 🎉', 'success');
|
|
||||||
}, 500);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
Storage.set('onboardingComplete', true);
|
||||||
|
modal.classList.add('hidden');
|
||||||
|
mainContent.classList.remove('hidden');
|
||||||
|
|
||||||
|
const userName = Storage.get('userName') || 'Guest';
|
||||||
|
notifications.show(`Welcome back, ${userName}! 👋`, 'success');
|
||||||
|
} catch (error) {
|
||||||
|
notifications.show('Failed to import data: Invalid file format!', 'error');
|
||||||
|
fileInput.value = '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 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');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
document.getElementById('complete-setup-btn').addEventListener('click', onboarding.complete);
|
||||||
|
} else {
|
||||||
|
modal.classList.add('hidden');
|
||||||
|
mainContent.classList.remove('hidden');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// Onboarding step navigation
|
||||||
|
nextStep: (currentStep) => {
|
||||||
|
const currentStepEl = document.querySelector(`[data-step="${currentStep}"]`);
|
||||||
|
const nextStepEl = document.querySelector(`[data-step="${currentStep + 1}"]`);
|
||||||
|
|
||||||
|
if (currentStep === 2) {
|
||||||
|
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');
|
||||||
|
},
|
||||||
|
|
||||||
|
// Finalize onboarding
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
};
|
71
js/search.js
|
@ -1,37 +1,27 @@
|
||||||
const search = {
|
const search = {
|
||||||
|
// Supported search engines and their URLs
|
||||||
engines: {
|
engines: {
|
||||||
google: {
|
google: 'https://www.google.com/search?q=',
|
||||||
url: 'https://www.google.com/search?q=',
|
bing: 'https://www.bing.com/search?q=',
|
||||||
icon: 'https://www.google.com/s2/favicons?domain=google.com&sz=32',
|
duckduckgo: 'https://duckduckgo.com/?q=',
|
||||||
name: 'Google'
|
brave: 'https://search.brave.com/search?q=',
|
||||||
|
qwant: 'https://www.qwant.com/?q=',
|
||||||
|
searxng: 'https://searx.org/search?q='
|
||||||
},
|
},
|
||||||
bing: {
|
|
||||||
url: 'https://www.bing.com/search?q=',
|
// Perform search using selected engine
|
||||||
icon: 'https://www.google.com/s2/favicons?domain=bing.com&sz=32',
|
perform: () => {
|
||||||
name: 'Bing'
|
const searchBar = document.getElementById('search-bar');
|
||||||
},
|
const query = searchBar.value.trim();
|
||||||
duckduckgo: {
|
const engine = Storage.get('searchEngine') || 'google';
|
||||||
url: 'https://duckduckgo.com/?q=',
|
|
||||||
icon: 'https://www.google.com/s2/favicons?domain=duckduckgo.com&sz=32',
|
if (query) {
|
||||||
name: 'DuckDuckGo'
|
const searchUrl = search.engines[engine] + encodeURIComponent(query);
|
||||||
},
|
window.location.href = searchUrl;
|
||||||
brave: {
|
|
||||||
url: 'https://search.brave.com/search?q=',
|
|
||||||
icon: 'https://www.google.com/s2/favicons?domain=brave.com&sz=32',
|
|
||||||
name: 'Brave'
|
|
||||||
},
|
|
||||||
qwant: {
|
|
||||||
url: 'https://www.qwant.com/?q=',
|
|
||||||
icon: 'https://www.google.com/s2/favicons?domain=qwant.com&sz=32',
|
|
||||||
name: 'Qwant'
|
|
||||||
},
|
|
||||||
searxng: {
|
|
||||||
url: 'https://searx.be/search?q=',
|
|
||||||
icon: 'https://www.google.com/s2/favicons?domain=searx.be&sz=32',
|
|
||||||
name: 'SearXNG'
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// Initialize search functionality
|
||||||
init: () => {
|
init: () => {
|
||||||
const searchBar = document.getElementById('search-bar');
|
const searchBar = document.getElementById('search-bar');
|
||||||
const searchButton = document.getElementById('search-button');
|
const searchButton = document.getElementById('search-button');
|
||||||
|
@ -44,6 +34,7 @@ const search = {
|
||||||
|
|
||||||
searchButton.addEventListener('click', search.perform);
|
searchButton.addEventListener('click', search.perform);
|
||||||
|
|
||||||
|
// Global keyboard shortcut to focus search bar
|
||||||
document.addEventListener('keydown', (e) => {
|
document.addEventListener('keydown', (e) => {
|
||||||
if (e.key === '/' &&
|
if (e.key === '/' &&
|
||||||
!['INPUT', 'TEXTAREA'].includes(document.activeElement.tagName) &&
|
!['INPUT', 'TEXTAREA'].includes(document.activeElement.tagName) &&
|
||||||
|
@ -52,29 +43,5 @@ const search = {
|
||||||
searchBar.focus();
|
searchBar.focus();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const searchEngine = Storage.get('searchEngine') || 'google';
|
|
||||||
search.updateSearchEngineIcon(searchEngine);
|
|
||||||
},
|
|
||||||
|
|
||||||
updateSearchEngineIcon(engine) {
|
|
||||||
const searchIcon = document.querySelector('#search-container .search-icon img');
|
|
||||||
if (!searchIcon) return;
|
|
||||||
searchIcon.src = this.engines[engine].icon;
|
|
||||||
},
|
|
||||||
|
|
||||||
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].url + encodeURIComponent(query);
|
|
||||||
window.location.href = searchUrl;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
document.addEventListener('DOMContentLoaded', () => {
|
|
||||||
search.init();
|
|
||||||
});
|
|
834
js/settings.js
|
@ -1,51 +1,4 @@
|
||||||
const defaultSettings = {
|
// Anonymous name generator
|
||||||
theme: 'light',
|
|
||||||
userName: '',
|
|
||||||
anonymousMode: false,
|
|
||||||
searchEngine: 'Google',
|
|
||||||
updateAlerts: true,
|
|
||||||
show_greeting: true,
|
|
||||||
show_search: true,
|
|
||||||
show_shortcuts: true,
|
|
||||||
show_addShortcut: true,
|
|
||||||
fontFamily: 'Inter',
|
|
||||||
fontSize: '16',
|
|
||||||
lightModeColors: {
|
|
||||||
'--primary': '#f5f5f5',
|
|
||||||
'--primary-hover': '#e0e0e0',
|
|
||||||
'--background': '#ffffff',
|
|
||||||
'--surface': '#fafafa',
|
|
||||||
'--border': '#eaeaea',
|
|
||||||
'--text': '#1a1a1a',
|
|
||||||
'--text-secondary': '#666666',
|
|
||||||
'--shadow': 'hsla(0, 0.00%, 0.00%, 0.08)',
|
|
||||||
'--modal-backdrop': 'rgba(0, 0, 0, 0.5)',
|
|
||||||
'--scrollbar-thumb': '#e0e0e0',
|
|
||||||
'--scrollbar-track': '#f5f5f5',
|
|
||||||
'--modal-background': '#ffffff',
|
|
||||||
'--toggle-bg': '#e0e0e0',
|
|
||||||
'--toggle-bg-active': 'var(--text)',
|
|
||||||
'--toggle-knob': 'var(--background)'
|
|
||||||
},
|
|
||||||
darkModeColors: {
|
|
||||||
'--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(0, 0, 0, 0.75)',
|
|
||||||
'--scrollbar-thumb': '#333333',
|
|
||||||
'--scrollbar-track': '#1a1a1a',
|
|
||||||
'--modal-background': '#1a1a1a',
|
|
||||||
'--toggle-bg': '#333333',
|
|
||||||
'--toggle-bg-active': 'var(--text)',
|
|
||||||
'--toggle-knob': 'var(--background)'
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const anonymousNames = {
|
const anonymousNames = {
|
||||||
adjectives: ['Hidden', 'Secret', 'Mystery', 'Shadow', 'Unknown', 'Silent', 'Stealth', 'Phantom', 'Ghost', 'Anon'],
|
adjectives: ['Hidden', 'Secret', 'Mystery', 'Shadow', 'Unknown', 'Silent', 'Stealth', 'Phantom', 'Ghost', 'Anon'],
|
||||||
nouns: [' User', ' Visitor', ' Guest', ' Agent', ' Entity', ' Person', ' Browser', ' Explorer', ' Wanderer', ' Navigator'],
|
nouns: [' User', ' Visitor', ' Guest', ' Agent', ' Entity', ' Person', ' Browser', ' Explorer', ' Wanderer', ' Navigator'],
|
||||||
|
@ -65,22 +18,7 @@ const getTimeBasedGreeting = () => {
|
||||||
return 'Good Night';
|
return 'Good Night';
|
||||||
};
|
};
|
||||||
|
|
||||||
const updateAlerts = Storage.get('updateAlerts');
|
// Main settings object
|
||||||
|
|
||||||
document.getElementById('toggle-update-alerts').addEventListener('change', function() {
|
|
||||||
Storage.set('updateAlerts', this.checked);
|
|
||||||
|
|
||||||
notifications.show(
|
|
||||||
this.checked ? 'Update alerts enabled!' : 'Update alerts disabled!',
|
|
||||||
this.checked ? 'success' : 'success'
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
document.addEventListener('DOMContentLoaded', () => {
|
|
||||||
document.getElementById('toggle-update-alerts').checked =
|
|
||||||
updateAlerts !== false;
|
|
||||||
});
|
|
||||||
|
|
||||||
const settings = {
|
const settings = {
|
||||||
GREETING_MAX_LENGTH: 60,
|
GREETING_MAX_LENGTH: 60,
|
||||||
|
|
||||||
|
@ -90,14 +28,10 @@ const settings = {
|
||||||
document.body.setAttribute('data-theme', newTheme);
|
document.body.setAttribute('data-theme', newTheme);
|
||||||
Storage.set('theme', newTheme);
|
Storage.set('theme', newTheme);
|
||||||
|
|
||||||
const themeIcon = document.querySelector('#toggle-theme svg use');
|
const themeIcon = document.querySelector('#toggle-theme i');
|
||||||
const lightModeIcon = newTheme === 'dark' ? 'dark-mode' : 'light-mode';
|
themeIcon.className = `fas fa-${newTheme === 'dark' ? 'sun' : 'moon'}`;
|
||||||
const darkModeIcon = newTheme === 'dark' ? 'light-mode' : 'dark-mode';
|
|
||||||
|
|
||||||
themeIcon.setAttribute('href', `#icon-${newTheme === 'dark' ? darkModeIcon : lightModeIcon}`);
|
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
||||||
toggleAnonymousMode: () => {
|
toggleAnonymousMode: () => {
|
||||||
const isAnonymous = Storage.get('anonymousMode') || false;
|
const isAnonymous = Storage.get('anonymousMode') || false;
|
||||||
Storage.set('anonymousMode', !isAnonymous);
|
Storage.set('anonymousMode', !isAnonymous);
|
||||||
|
@ -120,6 +54,7 @@ const settings = {
|
||||||
notifications.show('Search engine updated successfully!', 'success');
|
notifications.show('Search engine updated successfully!', 'success');
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// Update visibility of UI elements
|
||||||
updateVisibility: () => {
|
updateVisibility: () => {
|
||||||
const elements = {
|
const elements = {
|
||||||
greeting: {
|
greeting: {
|
||||||
|
@ -157,7 +92,6 @@ const settings = {
|
||||||
|
|
||||||
if (toggle && elementNode) {
|
if (toggle && elementNode) {
|
||||||
toggle.checked = isVisible !== false;
|
toggle.checked = isVisible !== false;
|
||||||
|
|
||||||
if (isVisible === false) {
|
if (isVisible === false) {
|
||||||
elementNode.style.visibility = 'hidden';
|
elementNode.style.visibility = 'hidden';
|
||||||
elementNode.style.opacity = '0';
|
elementNode.style.opacity = '0';
|
||||||
|
@ -170,9 +104,6 @@ const settings = {
|
||||||
elementNode.style.pointerEvents = 'auto';
|
elementNode.style.pointerEvents = 'auto';
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!toggle.hasAttribute('data-visibility-initialized')) {
|
|
||||||
toggle.setAttribute('data-visibility-initialized', 'true');
|
|
||||||
|
|
||||||
toggle.addEventListener('change', (e) => {
|
toggle.addEventListener('change', (e) => {
|
||||||
const isChecked = e.target.checked;
|
const isChecked = e.target.checked;
|
||||||
Storage.set(`show_${key}`, isChecked);
|
Storage.set(`show_${key}`, isChecked);
|
||||||
|
@ -212,47 +143,29 @@ const settings = {
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// Initialize settings
|
||||||
init: () => {
|
init: () => {
|
||||||
const settingsButton = document.getElementById('settings-button');
|
const settingsButton = document.getElementById('settings-button');
|
||||||
const settingsPage = document.getElementById('settings-page');
|
const settingsModal = document.getElementById('settings-modal');
|
||||||
const backToHome = document.getElementById('back-to-home');
|
const closeSettings = document.getElementById('close-settings');
|
||||||
|
|
||||||
settingsButton.addEventListener('click', (e) => {
|
settingsButton.addEventListener('click', (e) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
settings.updateSettingsUI();
|
settings.updateSettingsUI();
|
||||||
settingsPage.classList.remove('hidden');
|
settingsModal.classList.remove('hidden');
|
||||||
setTimeout(() => {
|
settingsModal.classList.add('active');
|
||||||
settingsPage.classList.add('active');
|
|
||||||
}, 10);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
backToHome.addEventListener('click', () => {
|
closeSettings.addEventListener('click', () => {
|
||||||
settingsPage.classList.remove('active');
|
settingsModal.classList.remove('active');
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
settingsPage.classList.add('hidden');
|
settingsModal.classList.add('hidden');
|
||||||
}, 300);
|
}, 300);
|
||||||
});
|
});
|
||||||
|
|
||||||
const navItems = document.querySelectorAll('.settings-nav-item');
|
|
||||||
navItems.forEach(item => {
|
|
||||||
item.addEventListener('click', (e) => {
|
|
||||||
e.preventDefault();
|
|
||||||
const section = item.getAttribute('data-section');
|
|
||||||
|
|
||||||
navItems.forEach(navItem => navItem.classList.remove('active'));
|
|
||||||
item.classList.add('active');
|
|
||||||
|
|
||||||
document.querySelectorAll('.settings-section').forEach(section => {
|
|
||||||
section.classList.remove('active');
|
|
||||||
});
|
|
||||||
document.getElementById(section).classList.add('active');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
const themeToggle = document.getElementById('toggle-theme');
|
const themeToggle = document.getElementById('toggle-theme');
|
||||||
themeToggle.addEventListener('click', settings.toggleTheme);
|
themeToggle.addEventListener('click', settings.toggleTheme);
|
||||||
|
|
||||||
|
@ -274,53 +187,10 @@ const settings = {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const fontFamilySelect = document.getElementById('font-family-select');
|
|
||||||
const fontSizeSlider = document.getElementById('font-size-slider');
|
|
||||||
const fontSizeNumber = document.getElementById('font-size-number');
|
|
||||||
const resetFontSize = document.getElementById('reset-font-size');
|
|
||||||
const resetLightColors = document.getElementById('reset-light-colors');
|
|
||||||
const resetDarkColors = document.getElementById('reset-dark-colors');
|
|
||||||
|
|
||||||
fontFamilySelect.value = Storage.get('fontFamily') || defaultSettings.fontFamily;
|
|
||||||
const currentFontSize = Storage.get('fontSize') || defaultSettings.fontSize;
|
|
||||||
fontSizeSlider.value = currentFontSize;
|
|
||||||
fontSizeNumber.value = currentFontSize;
|
|
||||||
|
|
||||||
fontFamilySelect.addEventListener('change', (e) => {
|
|
||||||
Storage.set('fontFamily', e.target.value);
|
|
||||||
settings.updateTypography();
|
|
||||||
notifications.show('Font updated successfully!', 'success');
|
|
||||||
});
|
|
||||||
|
|
||||||
const updateFontSize = (value) => {
|
|
||||||
if (value >= 8 && value <= 36) {
|
|
||||||
fontSizeSlider.value = value;
|
|
||||||
fontSizeNumber.value = value;
|
|
||||||
Storage.set('fontSize', value);
|
|
||||||
settings.updateTypography();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
fontSizeSlider.addEventListener('input', (e) => updateFontSize(e.target.value));
|
|
||||||
fontSizeNumber.addEventListener('change', (e) => updateFontSize(e.target.value));
|
|
||||||
|
|
||||||
resetFontSize.addEventListener('click', settings.resetTypography);
|
|
||||||
resetLightColors.addEventListener('click', () => settings.resetColors('light'));
|
|
||||||
resetDarkColors.addEventListener('click', () => settings.resetColors('dark'));
|
|
||||||
|
|
||||||
settings.initColorSettings();
|
|
||||||
|
|
||||||
settings.updateTypography();
|
|
||||||
settings.updateColors();
|
|
||||||
|
|
||||||
const savedTheme = Storage.get('theme') || 'light';
|
const savedTheme = Storage.get('theme') || 'light';
|
||||||
document.body.setAttribute('data-theme', savedTheme);
|
document.body.setAttribute('data-theme', savedTheme);
|
||||||
|
const themeIcon = document.querySelector('#toggle-theme i');
|
||||||
const themeIcon = document.querySelector('#toggle-theme svg use');
|
themeIcon.className = `fas fa-${savedTheme === 'dark' ? 'sun' : 'moon'}`;
|
||||||
const lightModeIcon = 'light-mode';
|
|
||||||
const darkModeIcon = 'dark-mode';
|
|
||||||
|
|
||||||
themeIcon.setAttribute('href', `#icon-${savedTheme === 'dark' ? darkModeIcon : lightModeIcon}`);
|
|
||||||
|
|
||||||
settings.initDataManagement();
|
settings.initDataManagement();
|
||||||
settings.updateVisibility();
|
settings.updateVisibility();
|
||||||
|
@ -357,12 +227,6 @@ const settings = {
|
||||||
}
|
}
|
||||||
updateGreeting();
|
updateGreeting();
|
||||||
});
|
});
|
||||||
|
|
||||||
document.addEventListener('keydown', (e) => {
|
|
||||||
if (e.key === 'Escape' && settingsPage.classList.contains('active')) {
|
|
||||||
backToHome.click();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
},
|
},
|
||||||
|
|
||||||
updateSettingsUI: () => {
|
updateSettingsUI: () => {
|
||||||
|
@ -373,7 +237,6 @@ const settings = {
|
||||||
document.getElementById('settings-name').value = userName;
|
document.getElementById('settings-name').value = userName;
|
||||||
document.getElementById('toggle-anonymous').checked = isAnonymous;
|
document.getElementById('toggle-anonymous').checked = isAnonymous;
|
||||||
document.getElementById('search-engine-select').value = currentEngine;
|
document.getElementById('search-engine-select').value = currentEngine;
|
||||||
document.getElementById('toggle-update-alerts').checked = updateAlerts;
|
|
||||||
|
|
||||||
['greeting', 'search', 'shortcuts', 'addShortcut'].forEach(element => {
|
['greeting', 'search', 'shortcuts', 'addShortcut'].forEach(element => {
|
||||||
const isVisible = Storage.get(`show_${element}`);
|
const isVisible = Storage.get(`show_${element}`);
|
||||||
|
@ -385,13 +248,6 @@ const settings = {
|
||||||
|
|
||||||
const customGreeting = Storage.get('customGreeting') || '';
|
const customGreeting = Storage.get('customGreeting') || '';
|
||||||
document.getElementById('custom-greeting').value = customGreeting;
|
document.getElementById('custom-greeting').value = customGreeting;
|
||||||
|
|
||||||
const fontFamily = Storage.get('fontFamily') || defaultSettings.fontFamily;
|
|
||||||
const fontSize = Storage.get('fontSize') || defaultSettings.fontSize;
|
|
||||||
|
|
||||||
document.getElementById('font-family-select').value = fontFamily;
|
|
||||||
document.getElementById('font-size-slider').value = fontSize;
|
|
||||||
document.getElementById('font-size-number').value = fontSize;
|
|
||||||
},
|
},
|
||||||
|
|
||||||
formatGreeting: (format) => {
|
formatGreeting: (format) => {
|
||||||
|
@ -448,73 +304,28 @@ const settings = {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// Data management functions
|
||||||
exportData: () => {
|
exportData: () => {
|
||||||
try {
|
|
||||||
const data = {
|
const data = {
|
||||||
settings: {
|
settings: {
|
||||||
theme: Storage.get('theme') || defaultSettings.theme,
|
theme: Storage.get('theme'),
|
||||||
userName: Storage.get('userName') || '',
|
userName: Storage.get('userName'),
|
||||||
anonymousMode: Storage.get('anonymousMode') || false,
|
anonymousMode: Storage.get('anonymousMode'),
|
||||||
anonymousName: Storage.get('anonymousName') || '',
|
anonymousName: Storage.get('anonymousName'),
|
||||||
searchEngine: Storage.get('searchEngine') || 'Google',
|
searchEngine: Storage.get('searchEngine'),
|
||||||
customGreeting: Storage.get('customGreeting') || '',
|
customGreeting: Storage.get('customGreeting'),
|
||||||
updateAlerts: Storage.get('updateAlerts') !== false,
|
show_greeting: Storage.get('show_greeting'),
|
||||||
|
show_search: Storage.get('show_search'),
|
||||||
fontFamily: Storage.get('fontFamily') || defaultSettings.fontFamily,
|
show_shortcuts: Storage.get('show_shortcuts'),
|
||||||
fontSize: Storage.get('fontSize') || defaultSettings.fontSize,
|
show_addShortcut: Storage.get('show_addShortcut')
|
||||||
|
|
||||||
show_greeting: Storage.get('show_greeting') !== false,
|
|
||||||
show_search: Storage.get('show_search') !== false,
|
|
||||||
show_shortcuts: Storage.get('show_shortcuts') !== false,
|
|
||||||
show_addShortcut: Storage.get('show_addShortcut') !== false,
|
|
||||||
|
|
||||||
gridLayout: Storage.get('gridLayout') ?
|
|
||||||
(typeof Storage.get('gridLayout') === 'string' ?
|
|
||||||
JSON.parse(Storage.get('gridLayout')) :
|
|
||||||
Storage.get('gridLayout')) :
|
|
||||||
{
|
|
||||||
type: 'default',
|
|
||||||
columns: 6,
|
|
||||||
gap: 16,
|
|
||||||
size: 80,
|
|
||||||
resizable: false
|
|
||||||
},
|
|
||||||
|
|
||||||
backgrounds: typeof Storage.get('backgrounds') === 'string' ?
|
|
||||||
JSON.parse(Storage.get('backgrounds') || '[]') :
|
|
||||||
[],
|
|
||||||
customBackground: Storage.get('customBackground') || null,
|
|
||||||
|
|
||||||
lightModeColors: typeof Storage.get('lightModeColors') === 'string' ?
|
|
||||||
JSON.parse(Storage.get('lightModeColors')) :
|
|
||||||
defaultSettings.lightModeColors,
|
|
||||||
darkModeColors: typeof Storage.get('darkModeColors') === 'string' ?
|
|
||||||
JSON.parse(Storage.get('darkModeColors')) :
|
|
||||||
defaultSettings.darkModeColors
|
|
||||||
},
|
},
|
||||||
shortcuts: Storage.get('shortcuts') || [],
|
shortcuts: Storage.get('shortcuts') || [],
|
||||||
keybinds: typeof Storage.get('keybinds') === 'string' ?
|
keybinds: Storage.get('keybinds') || {}
|
||||||
JSON.parse(Storage.get('keybinds') || '{}') :
|
|
||||||
(Storage.get('keybinds') || {})
|
|
||||||
};
|
};
|
||||||
|
|
||||||
if (data.settings.backgrounds && Array.isArray(data.settings.backgrounds)) {
|
if (!data.settings || !data.shortcuts || !data.keybinds) {
|
||||||
data.settings.backgrounds = data.settings.backgrounds.filter(bg => {
|
notifications.show('Failed to export data: Invalid data structure.', 'error');
|
||||||
return typeof bg === 'string' &&
|
return;
|
||||||
(bg.startsWith('data:image/') || bg.startsWith('images/backgrounds/'));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (data.settings.customBackground &&
|
|
||||||
data.settings.backgrounds &&
|
|
||||||
!data.settings.backgrounds.includes(data.settings.customBackground)) {
|
|
||||||
data.settings.customBackground = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!data.settings || typeof data.settings !== 'object' ||
|
|
||||||
!Array.isArray(data.shortcuts) ||
|
|
||||||
!data.keybinds || typeof data.keybinds !== 'object') {
|
|
||||||
throw new Error('Invalid data structure');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const blob = new Blob([JSON.stringify(data, null, 2)], { type: 'application/json' });
|
const blob = new Blob([JSON.stringify(data, null, 2)], { type: 'application/json' });
|
||||||
|
@ -522,19 +333,16 @@ const settings = {
|
||||||
const a = document.createElement('a');
|
const a = document.createElement('a');
|
||||||
a.href = url;
|
a.href = url;
|
||||||
a.download = 'jstar-tab-backup.json';
|
a.download = 'jstar-tab-backup.json';
|
||||||
|
document.body.appendChild(a);
|
||||||
a.click();
|
a.click();
|
||||||
|
document.body.removeChild(a);
|
||||||
URL.revokeObjectURL(url);
|
URL.revokeObjectURL(url);
|
||||||
|
|
||||||
notifications.show('Data exported successfully!', 'success');
|
|
||||||
} catch (error) {
|
|
||||||
notifications.show(`Failed to export data: ${error.message}`, 'error');
|
|
||||||
console.error('Export error:', error);
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
|
|
||||||
importData: async (file) => {
|
importData: async (file) => {
|
||||||
try {
|
|
||||||
const text = await file.text();
|
const text = await file.text();
|
||||||
|
|
||||||
|
try {
|
||||||
const data = JSON.parse(text);
|
const data = JSON.parse(text);
|
||||||
|
|
||||||
if (!data.settings || typeof data.settings !== 'object' ||
|
if (!data.settings || typeof data.settings !== 'object' ||
|
||||||
|
@ -543,110 +351,8 @@ const settings = {
|
||||||
throw new Error('Invalid data structure');
|
throw new Error('Invalid data structure');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (data.settings.backgrounds) {
|
|
||||||
if (!Array.isArray(data.settings.backgrounds)) {
|
|
||||||
throw new Error('Invalid backgrounds format');
|
|
||||||
}
|
|
||||||
|
|
||||||
const validBackgrounds = data.settings.backgrounds.filter(bg => {
|
|
||||||
return typeof bg === 'string' &&
|
|
||||||
(bg.startsWith('data:image/') || bg.startsWith('images/backgrounds/'));
|
|
||||||
});
|
|
||||||
|
|
||||||
if (validBackgrounds.length !== data.settings.backgrounds.length) {
|
|
||||||
throw new Error('Invalid background images detected');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (data.settings.customBackground &&
|
|
||||||
!validBackgrounds.includes(data.settings.customBackground)) {
|
|
||||||
delete data.settings.customBackground;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (data.settings.gridLayout) {
|
|
||||||
if (typeof data.settings.gridLayout !== 'object') {
|
|
||||||
data.settings.gridLayout = {
|
|
||||||
type: 'default',
|
|
||||||
columns: 6,
|
|
||||||
gap: 16,
|
|
||||||
size: 80,
|
|
||||||
resizable: false
|
|
||||||
};
|
|
||||||
} else {
|
|
||||||
const defaultLayout = {
|
|
||||||
type: 'default',
|
|
||||||
columns: 6,
|
|
||||||
gap: 16,
|
|
||||||
size: 80,
|
|
||||||
resizable: false
|
|
||||||
};
|
|
||||||
|
|
||||||
if (!data.settings.gridLayout.type ||
|
|
||||||
!['default', 'compact', 'comfortable', 'list', 'custom'].includes(data.settings.gridLayout.type)) {
|
|
||||||
data.settings.gridLayout.type = defaultLayout.type;
|
|
||||||
}
|
|
||||||
|
|
||||||
const columns = parseInt(data.settings.gridLayout.columns);
|
|
||||||
if (isNaN(columns) || columns < 1 || columns > 12) {
|
|
||||||
data.settings.gridLayout.columns = defaultLayout.columns;
|
|
||||||
}
|
|
||||||
|
|
||||||
const gap = parseInt(data.settings.gridLayout.gap);
|
|
||||||
if (isNaN(gap) || gap < 0 || gap > 50) {
|
|
||||||
data.settings.gridLayout.gap = defaultLayout.gap;
|
|
||||||
}
|
|
||||||
|
|
||||||
const size = parseInt(data.settings.gridLayout.size);
|
|
||||||
if (isNaN(size) || size < 40 || size > 200) {
|
|
||||||
data.settings.gridLayout.size = defaultLayout.size;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (typeof data.settings.gridLayout.resizable !== 'boolean') {
|
|
||||||
data.settings.gridLayout.resizable = defaultLayout.resizable;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const validateThemeColors = (colorObj, defaultColors) => {
|
|
||||||
if (!colorObj || typeof colorObj !== 'object') {
|
|
||||||
return defaultColors;
|
|
||||||
}
|
|
||||||
|
|
||||||
const validatedColors = {...defaultColors};
|
|
||||||
|
|
||||||
Object.keys(defaultColors).forEach(key => {
|
|
||||||
if (colorObj[key] && typeof colorObj[key] === 'string') {
|
|
||||||
validatedColors[key] = colorObj[key];
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return validatedColors;
|
|
||||||
};
|
|
||||||
|
|
||||||
if (data.settings.lightModeColors) {
|
|
||||||
data.settings.lightModeColors = validateThemeColors(
|
|
||||||
data.settings.lightModeColors,
|
|
||||||
defaultSettings.lightModeColors
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (data.settings.darkModeColors) {
|
|
||||||
data.settings.darkModeColors = validateThemeColors(
|
|
||||||
data.settings.darkModeColors,
|
|
||||||
defaultSettings.darkModeColors
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Object.entries(data.settings).forEach(([key, value]) => {
|
Object.entries(data.settings).forEach(([key, value]) => {
|
||||||
if (key === 'backgrounds') {
|
|
||||||
Storage.set(key, JSON.stringify(value));
|
|
||||||
} else if (key === 'gridLayout') {
|
|
||||||
Storage.set(key, JSON.stringify(value));
|
|
||||||
} else if (key === 'lightModeColors' || key === 'darkModeColors') {
|
|
||||||
Storage.set(key, JSON.stringify(value));
|
|
||||||
} else {
|
|
||||||
Storage.set(key, value);
|
Storage.set(key, value);
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
Storage.set('shortcuts', data.shortcuts);
|
Storage.set('shortcuts', data.shortcuts);
|
||||||
|
@ -664,42 +370,7 @@ const settings = {
|
||||||
settings.updateSettingsUI();
|
settings.updateSettingsUI();
|
||||||
settings.updateVisibility();
|
settings.updateVisibility();
|
||||||
shortcuts.render();
|
shortcuts.render();
|
||||||
|
|
||||||
if (typeof GridLayout !== 'undefined' && GridLayout.init) {
|
|
||||||
setTimeout(() => {
|
|
||||||
GridLayout.init();
|
|
||||||
}, 100);
|
|
||||||
}
|
|
||||||
|
|
||||||
document.body.setAttribute('data-theme', data.settings.theme || 'light');
|
document.body.setAttribute('data-theme', data.settings.theme || 'light');
|
||||||
settings.updateColors();
|
|
||||||
|
|
||||||
if (data.settings.lightModeColors || data.settings.darkModeColors) {
|
|
||||||
settings.initColorSettings();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (data.settings.fontFamily) {
|
|
||||||
document.documentElement.style.setProperty('--font-family', data.settings.fontFamily);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (data.settings.fontSize) {
|
|
||||||
document.documentElement.style.setProperty('--font-size', data.settings.fontSize + 'px');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Array.isArray(data.settings.backgrounds)) {
|
|
||||||
data.settings.backgrounds.forEach(bg => {
|
|
||||||
if (bg.startsWith('data:image/')) {
|
|
||||||
addBackgroundPreview(bg, false);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (data.settings.customBackground) {
|
|
||||||
setCustomBackground(data.settings.customBackground, true);
|
|
||||||
} else {
|
|
||||||
document.body.style.backgroundImage = '';
|
|
||||||
Storage.remove('customBackground');
|
|
||||||
}
|
|
||||||
|
|
||||||
notifications.show('Data imported successfully!', 'success');
|
notifications.show('Data imported successfully!', 'success');
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
@ -709,6 +380,17 @@ const settings = {
|
||||||
},
|
},
|
||||||
|
|
||||||
resetData: () => {
|
resetData: () => {
|
||||||
|
const defaultSettings = {
|
||||||
|
theme: 'light',
|
||||||
|
userName: '',
|
||||||
|
anonymousMode: false,
|
||||||
|
searchEngine: 'Google',
|
||||||
|
show_greeting: true,
|
||||||
|
show_search: true,
|
||||||
|
show_shortcuts: true,
|
||||||
|
show_addShortcut: true
|
||||||
|
};
|
||||||
|
|
||||||
Object.entries(defaultSettings).forEach(([key, value]) => {
|
Object.entries(defaultSettings).forEach(([key, value]) => {
|
||||||
Storage.set(key, value);
|
Storage.set(key, value);
|
||||||
});
|
});
|
||||||
|
@ -718,186 +400,59 @@ const settings = {
|
||||||
Storage.remove('anonymousName');
|
Storage.remove('anonymousName');
|
||||||
Storage.remove('customGreeting');
|
Storage.remove('customGreeting');
|
||||||
|
|
||||||
if (typeof GridLayout !== 'undefined') {
|
|
||||||
if (GridLayout.reset) {
|
|
||||||
GridLayout.reset();
|
|
||||||
} else if (GridLayout.defaults) {
|
|
||||||
Storage.set('gridLayout', GridLayout.defaults);
|
|
||||||
if (GridLayout.init) {
|
|
||||||
setTimeout(() => {
|
|
||||||
GridLayout.init();
|
|
||||||
}, 100);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
Storage.remove('gridLayout');
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
Storage.remove('gridLayout');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (typeof GridLayout === 'undefined' || !GridLayout.reset) {
|
|
||||||
const defaultVisibility = {
|
|
||||||
showGreeting: true,
|
|
||||||
showSearch: true,
|
|
||||||
showShortcuts: true,
|
|
||||||
showAddButton: true,
|
|
||||||
showGrid: true
|
|
||||||
};
|
|
||||||
Storage.set('visibility', defaultVisibility);
|
|
||||||
}
|
|
||||||
|
|
||||||
settings.updateSettingsUI();
|
settings.updateSettingsUI();
|
||||||
settings.updateVisibility();
|
settings.updateVisibility();
|
||||||
settings.updateTypography();
|
|
||||||
shortcuts.render();
|
shortcuts.render();
|
||||||
document.body.setAttribute('data-theme', 'light');
|
document.body.setAttribute('data-theme', 'light');
|
||||||
|
|
||||||
notifications.show('All data has been reset!', 'success');
|
notifications.show('All data has been reset!', 'success');
|
||||||
},
|
|
||||||
|
|
||||||
updateTypography: () => {
|
|
||||||
const fontFamily = Storage.get('fontFamily') || defaultSettings.fontFamily;
|
|
||||||
const fontSize = Storage.get('fontSize') || defaultSettings.fontSize;
|
|
||||||
|
|
||||||
document.documentElement.style.setProperty('--font-family', fontFamily);
|
|
||||||
document.documentElement.style.setProperty('--font-size-base', `${fontSize}px`);
|
|
||||||
},
|
|
||||||
|
|
||||||
updateColors: () => {
|
|
||||||
const theme = document.body.getAttribute('data-theme');
|
|
||||||
let colors;
|
|
||||||
|
|
||||||
if (theme === 'dark') {
|
|
||||||
const darkColors = Storage.get('darkModeColors');
|
|
||||||
colors = typeof darkColors === 'string' ?
|
|
||||||
JSON.parse(darkColors) :
|
|
||||||
(darkColors || defaultSettings.darkModeColors);
|
|
||||||
} else {
|
|
||||||
const lightColors = Storage.get('lightModeColors');
|
|
||||||
colors = typeof lightColors === 'string' ?
|
|
||||||
JSON.parse(lightColors) :
|
|
||||||
(lightColors || defaultSettings.lightModeColors);
|
|
||||||
}
|
|
||||||
|
|
||||||
Object.entries(colors).forEach(([variable, value]) => {
|
|
||||||
document.documentElement.style.setProperty(variable, value);
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
resetTypography: () => {
|
|
||||||
Storage.set('fontFamily', defaultSettings.fontFamily);
|
|
||||||
Storage.set('fontSize', defaultSettings.fontSize);
|
|
||||||
settings.updateTypography();
|
|
||||||
settings.updateSettingsUI();
|
|
||||||
notifications.show('Typography reset to default!', 'success');
|
|
||||||
},
|
|
||||||
|
|
||||||
resetColors: (theme) => {
|
|
||||||
if (theme === 'light') {
|
|
||||||
Storage.set('lightModeColors', JSON.stringify(defaultSettings.lightModeColors));
|
|
||||||
} else {
|
|
||||||
Storage.set('darkModeColors', JSON.stringify(defaultSettings.darkModeColors));
|
|
||||||
}
|
|
||||||
settings.updateColors();
|
|
||||||
settings.initColorSettings();
|
|
||||||
notifications.show(`${theme === 'light' ? 'Light' : 'Dark'} mode colors reset to default!`, 'success');
|
|
||||||
},
|
|
||||||
|
|
||||||
initColorSettings: () => {
|
|
||||||
const colorVariables = {
|
|
||||||
'Primary Color': '--primary',
|
|
||||||
'Primary Hover': '--primary-hover',
|
|
||||||
'Background': '--background',
|
|
||||||
'Surface': '--surface',
|
|
||||||
'Border': '--border',
|
|
||||||
'Text': '--text',
|
|
||||||
'Secondary Text': '--text-secondary',
|
|
||||||
'Shadow': '--shadow',
|
|
||||||
'Modal Backdrop': '--modal-backdrop',
|
|
||||||
'Scrollbar Thumb': '--scrollbar-thumb',
|
|
||||||
'Scrollbar Track': '--scrollbar-track',
|
|
||||||
'Modal Background': '--modal-background',
|
|
||||||
'Toggle Background': '--toggle-bg',
|
|
||||||
'Toggle Active': '--toggle-bg-active',
|
|
||||||
'Toggle Knob': '--toggle-knob'
|
|
||||||
};
|
|
||||||
|
|
||||||
const createColorInputs = (containerId, theme) => {
|
|
||||||
const container = document.getElementById(containerId);
|
|
||||||
container.innerHTML = '';
|
|
||||||
|
|
||||||
let colors;
|
|
||||||
if (theme === 'dark') {
|
|
||||||
const darkColors = Storage.get('darkModeColors');
|
|
||||||
colors = typeof darkColors === 'string' ?
|
|
||||||
JSON.parse(darkColors) :
|
|
||||||
(darkColors || defaultSettings.darkModeColors);
|
|
||||||
} else {
|
|
||||||
const lightColors = Storage.get('lightModeColors');
|
|
||||||
colors = typeof lightColors === 'string' ?
|
|
||||||
JSON.parse(lightColors) :
|
|
||||||
(lightColors || defaultSettings.lightModeColors);
|
|
||||||
}
|
|
||||||
|
|
||||||
Object.entries(colorVariables).forEach(([label, variable]) => {
|
|
||||||
const div = document.createElement('div');
|
|
||||||
div.className = 'color-setting';
|
|
||||||
div.innerHTML = `
|
|
||||||
<div class="color-setting-label">${label}</div>
|
|
||||||
<input type="color" value="${colors[variable]}" data-variable="${variable}">
|
|
||||||
`;
|
|
||||||
|
|
||||||
const input = div.querySelector('input');
|
|
||||||
input.addEventListener('change', (e) => {
|
|
||||||
let updatedColors;
|
|
||||||
if (theme === 'dark') {
|
|
||||||
const darkColors = Storage.get('darkModeColors');
|
|
||||||
updatedColors = typeof darkColors === 'string' ?
|
|
||||||
JSON.parse(darkColors) :
|
|
||||||
(darkColors || {...defaultSettings.darkModeColors});
|
|
||||||
} else {
|
|
||||||
const lightColors = Storage.get('lightModeColors');
|
|
||||||
updatedColors = typeof lightColors === 'string' ?
|
|
||||||
JSON.parse(lightColors) :
|
|
||||||
(lightColors || {...defaultSettings.lightModeColors});
|
|
||||||
}
|
|
||||||
|
|
||||||
updatedColors[variable] = e.target.value;
|
|
||||||
Storage.set(theme === 'dark' ? 'darkModeColors' : 'lightModeColors', JSON.stringify(updatedColors));
|
|
||||||
settings.updateColors();
|
|
||||||
});
|
|
||||||
|
|
||||||
container.appendChild(div);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
createColorInputs('light-mode-colors', 'light');
|
|
||||||
createColorInputs('dark-mode-colors', 'dark');
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Initialize data management
|
||||||
settings.initDataManagement = () => {
|
settings.initDataManagement = () => {
|
||||||
const importBtn = document.getElementById('import-data');
|
|
||||||
const exportBtn = document.getElementById('export-data');
|
const exportBtn = document.getElementById('export-data');
|
||||||
|
const importBtn = document.getElementById('import-data');
|
||||||
const resetBtn = document.getElementById('reset-data');
|
const resetBtn = document.getElementById('reset-data');
|
||||||
|
const fileInput = document.getElementById('import-file');
|
||||||
const fileInput = document.createElement('input');
|
|
||||||
fileInput.type = 'file';
|
|
||||||
fileInput.id = 'import-file';
|
|
||||||
fileInput.accept = '.json';
|
|
||||||
fileInput.style.display = 'none';
|
|
||||||
document.body.appendChild(fileInput);
|
|
||||||
|
|
||||||
if (!importBtn || !exportBtn || !resetBtn) {
|
|
||||||
console.warn('Data management buttons not found');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
exportBtn.replaceWith(exportBtn.cloneNode(true));
|
exportBtn.replaceWith(exportBtn.cloneNode(true));
|
||||||
const newExportBtn = document.getElementById('export-data');
|
const newExportBtn = document.getElementById('export-data');
|
||||||
|
|
||||||
newExportBtn.addEventListener('click', () => {
|
newExportBtn.addEventListener('click', () => {
|
||||||
settings.exportData();
|
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'),
|
||||||
|
customGreeting: Storage.get('customGreeting')
|
||||||
|
},
|
||||||
|
shortcuts: Storage.get('shortcuts') || [],
|
||||||
|
keybinds: Storage.get('keybinds') || {}
|
||||||
|
};
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
importBtn.addEventListener('click', () => fileInput.click());
|
importBtn.addEventListener('click', () => fileInput.click());
|
||||||
|
@ -906,8 +461,119 @@ settings.initDataManagement = () => {
|
||||||
const file = e.target.files[0];
|
const file = e.target.files[0];
|
||||||
if (!file) return;
|
if (!file) return;
|
||||||
|
|
||||||
settings.importData(file);
|
const reader = new FileReader();
|
||||||
|
reader.onload = (event) => {
|
||||||
|
try {
|
||||||
|
const data = JSON.parse(event.target.result);
|
||||||
|
|
||||||
|
if (!data.shortcuts || !data.settings || !data.keybinds) {
|
||||||
|
throw new Error('Invalid backup file format');
|
||||||
|
}
|
||||||
|
|
||||||
|
Object.entries(data.settings).forEach(([key, value]) => {
|
||||||
|
Storage.set(key, value);
|
||||||
|
});
|
||||||
|
|
||||||
|
if (data.keybinds) {
|
||||||
|
const validatedKeybinds = {};
|
||||||
|
Object.entries(data.keybinds).forEach(([action, binding]) => {
|
||||||
|
if (binding && typeof binding === 'object' &&
|
||||||
|
typeof binding.keys === 'string' &&
|
||||||
|
(!binding.url || typeof binding.url === 'string')) {
|
||||||
|
validatedKeybinds[action] = binding;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
Storage.set('keybinds', validatedKeybinds);
|
||||||
|
keybinds.init();
|
||||||
|
}
|
||||||
|
|
||||||
|
Storage.set('shortcuts', data.shortcuts);
|
||||||
|
|
||||||
fileInput.value = '';
|
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();
|
||||||
|
search.init();
|
||||||
|
document.body.setAttribute('data-theme', data.settings.theme || 'light');
|
||||||
|
settings.updateSettingsUI();
|
||||||
|
settings.updateVisibility();
|
||||||
|
|
||||||
|
const greetingElement = document.getElementById('greeting');
|
||||||
|
if (greetingElement) {
|
||||||
|
const userName = data.settings.anonymousMode ? data.settings.anonymousName : data.settings.userName;
|
||||||
|
const timeBasedGreeting = getTimeBasedGreeting();
|
||||||
|
greetingElement.textContent = `${timeBasedGreeting}, ${userName || 'Guest'}!`;
|
||||||
|
}
|
||||||
|
|
||||||
|
const searchInput = document.getElementById('search-input');
|
||||||
|
if (searchInput) {
|
||||||
|
searchInput.placeholder = `Search ${data.settings.searchEngine || 'Google'}...`;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (addShortcutBtn) {
|
||||||
|
addShortcutBtn.style.display = data.settings.show_addShortcut !== false ? 'block' : 'none';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data.settings.customGreeting) {
|
||||||
|
Storage.set('customGreeting', data.settings.customGreeting);
|
||||||
|
const testFormat = settings.formatGreeting(data.settings.customGreeting);
|
||||||
|
if (!testFormat) {
|
||||||
|
notifications.show('Invalid custom greeting format in imported data', 'error');
|
||||||
|
Storage.remove('customGreeting');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
});
|
});
|
||||||
|
|
||||||
resetBtn.addEventListener('click', () => {
|
resetBtn.addEventListener('click', () => {
|
||||||
|
@ -929,117 +595,3 @@ settings.initDataManagement = () => {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
function initCustomSelects() {
|
|
||||||
const customSelects = document.querySelectorAll(".custom-select");
|
|
||||||
|
|
||||||
customSelects.forEach(select => {
|
|
||||||
const nativeSelect = select.querySelector("select");
|
|
||||||
if (!nativeSelect) return;
|
|
||||||
|
|
||||||
const selectedDiv = document.createElement("div");
|
|
||||||
selectedDiv.className = "select-selected";
|
|
||||||
|
|
||||||
if (nativeSelect.id === 'search-engine-select') {
|
|
||||||
nativeSelect.value = Storage.get('searchEngine') || 'google';
|
|
||||||
} else if (nativeSelect.id === 'font-family-select') {
|
|
||||||
nativeSelect.value = Storage.get('fontFamily') || 'Inter';
|
|
||||||
selectedDiv.style.fontFamily = nativeSelect.value;
|
|
||||||
} else if (nativeSelect.id === 'grid-layout-type') {
|
|
||||||
const savedGridType = Storage.get('gridLayoutType');
|
|
||||||
if (savedGridType) {
|
|
||||||
nativeSelect.value = savedGridType;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const initialOption = nativeSelect.options[nativeSelect.selectedIndex];
|
|
||||||
selectedDiv.innerHTML = `
|
|
||||||
<span class="${initialOption?.className || ''}">
|
|
||||||
${initialOption.innerHTML}
|
|
||||||
</span>
|
|
||||||
`;
|
|
||||||
|
|
||||||
const itemsDiv = document.createElement("div");
|
|
||||||
itemsDiv.className = "select-items select-hide";
|
|
||||||
|
|
||||||
for (let i = 0; i < nativeSelect.options.length; i++) {
|
|
||||||
const optionDiv = document.createElement("div");
|
|
||||||
const option = nativeSelect.options[i];
|
|
||||||
|
|
||||||
optionDiv.innerHTML = option.innerHTML;
|
|
||||||
optionDiv.className = option.className || '';
|
|
||||||
optionDiv.setAttribute('data-value', option.value);
|
|
||||||
|
|
||||||
if (nativeSelect.id === 'font-family-select') {
|
|
||||||
optionDiv.style.fontFamily = option.value;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (option.value === nativeSelect.value) {
|
|
||||||
optionDiv.classList.add('same-as-selected');
|
|
||||||
}
|
|
||||||
|
|
||||||
optionDiv.addEventListener("click", function() {
|
|
||||||
nativeSelect.selectedIndex = i;
|
|
||||||
nativeSelect.dispatchEvent(new Event('change'));
|
|
||||||
|
|
||||||
selectedDiv.innerHTML = `
|
|
||||||
<span class="${this.className.replace('same-as-selected', '').trim()}">
|
|
||||||
${this.innerHTML}
|
|
||||||
</span>
|
|
||||||
`;
|
|
||||||
|
|
||||||
if (nativeSelect.id === 'font-family-select') {
|
|
||||||
selectedDiv.style.fontFamily = nativeSelect.options[i].value;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (nativeSelect.id === 'grid-layout-type') {
|
|
||||||
Storage.set('gridLayoutType', nativeSelect.value);
|
|
||||||
}
|
|
||||||
|
|
||||||
const sameAsSelected = itemsDiv.querySelector('.same-as-selected');
|
|
||||||
if (sameAsSelected) {
|
|
||||||
sameAsSelected.classList.remove('same-as-selected');
|
|
||||||
}
|
|
||||||
this.classList.add('same-as-selected');
|
|
||||||
|
|
||||||
itemsDiv.classList.add('select-hide');
|
|
||||||
selectedDiv.classList.remove('select-arrow-active');
|
|
||||||
});
|
|
||||||
|
|
||||||
itemsDiv.appendChild(optionDiv);
|
|
||||||
}
|
|
||||||
|
|
||||||
selectedDiv.addEventListener("click", function(e) {
|
|
||||||
e.stopPropagation();
|
|
||||||
closeAllSelects(this);
|
|
||||||
this.nextSibling.classList.toggle("select-hide");
|
|
||||||
this.classList.toggle("select-arrow-active");
|
|
||||||
});
|
|
||||||
|
|
||||||
select.appendChild(selectedDiv);
|
|
||||||
select.appendChild(itemsDiv);
|
|
||||||
});
|
|
||||||
|
|
||||||
function closeAllSelects(element) {
|
|
||||||
const selectItems = document.getElementsByClassName("select-items");
|
|
||||||
const selectSelected = document.getElementsByClassName("select-selected");
|
|
||||||
|
|
||||||
for (let i = 0; i < selectSelected.length; i++) {
|
|
||||||
if (element !== selectSelected[i]) {
|
|
||||||
selectSelected[i].classList.remove("select-arrow-active");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (let i = 0; i < selectItems.length; i++) {
|
|
||||||
if (element !== selectItems[i].previousSibling) {
|
|
||||||
selectItems[i].classList.add("select-hide");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
document.addEventListener("click", closeAllSelects);
|
|
||||||
}
|
|
||||||
|
|
||||||
document.addEventListener("DOMContentLoaded", function() {
|
|
||||||
initCustomSelects();
|
|
||||||
});
|
|
|
@ -1,6 +1,7 @@
|
||||||
const shortcuts = {
|
const shortcuts = {
|
||||||
MAX_SHORTCUTS: 12,
|
MAX_SHORTCUTS: 12,
|
||||||
|
|
||||||
|
// URL Validation
|
||||||
validateAndFormatUrl: (url) => {
|
validateAndFormatUrl: (url) => {
|
||||||
if (!/^https?:\/\//i.test(url)) {
|
if (!/^https?:\/\//i.test(url)) {
|
||||||
url = 'https://' + url;
|
url = 'https://' + url;
|
||||||
|
@ -14,6 +15,7 @@ const shortcuts = {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// Shortcut Management
|
||||||
add: (url, name) => {
|
add: (url, name) => {
|
||||||
const currentShortcuts = Storage.get('shortcuts') || [];
|
const currentShortcuts = Storage.get('shortcuts') || [];
|
||||||
if (currentShortcuts.length >= shortcuts.MAX_SHORTCUTS) {
|
if (currentShortcuts.length >= shortcuts.MAX_SHORTCUTS) {
|
||||||
|
@ -30,7 +32,6 @@ const shortcuts = {
|
||||||
currentShortcuts.push({ url: formattedUrl, name });
|
currentShortcuts.push({ url: formattedUrl, name });
|
||||||
Storage.set('shortcuts', currentShortcuts);
|
Storage.set('shortcuts', currentShortcuts);
|
||||||
shortcuts.render();
|
shortcuts.render();
|
||||||
CacheUpdater.update();
|
|
||||||
},
|
},
|
||||||
|
|
||||||
remove: (index) => {
|
remove: (index) => {
|
||||||
|
@ -39,7 +40,6 @@ const shortcuts = {
|
||||||
Storage.set('shortcuts', currentShortcuts);
|
Storage.set('shortcuts', currentShortcuts);
|
||||||
shortcuts.render();
|
shortcuts.render();
|
||||||
notifications.show('Shortcut removed!', 'success');
|
notifications.show('Shortcut removed!', 'success');
|
||||||
CacheUpdater.update();
|
|
||||||
},
|
},
|
||||||
|
|
||||||
edit: (index, newUrl, newName) => {
|
edit: (index, newUrl, newName) => {
|
||||||
|
@ -48,9 +48,9 @@ const shortcuts = {
|
||||||
Storage.set('shortcuts', currentShortcuts);
|
Storage.set('shortcuts', currentShortcuts);
|
||||||
shortcuts.render();
|
shortcuts.render();
|
||||||
notifications.show('Shortcut updated!', 'success');
|
notifications.show('Shortcut updated!', 'success');
|
||||||
CacheUpdater.update();
|
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// UI Interactions
|
||||||
showContextMenu: (e, index) => {
|
showContextMenu: (e, index) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
const menu = document.getElementById('context-menu');
|
const menu = document.getElementById('context-menu');
|
||||||
|
@ -73,6 +73,7 @@ const shortcuts = {
|
||||||
}, 0);
|
}, 0);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// Rendering
|
||||||
render: () => {
|
render: () => {
|
||||||
const grid = document.getElementById('shortcuts-grid');
|
const grid = document.getElementById('shortcuts-grid');
|
||||||
const currentShortcuts = Storage.get('shortcuts') || [];
|
const currentShortcuts = Storage.get('shortcuts') || [];
|
||||||
|
@ -84,12 +85,9 @@ const shortcuts = {
|
||||||
const element = document.createElement('div');
|
const element = document.createElement('div');
|
||||||
element.className = `shortcut ${isAnonymous ? 'blurred' : ''}`;
|
element.className = `shortcut ${isAnonymous ? 'blurred' : ''}`;
|
||||||
|
|
||||||
element.dataset.index = index;
|
|
||||||
|
|
||||||
const icon = document.createElement('img');
|
const icon = document.createElement('img');
|
||||||
icon.src = `https://www.google.com/s2/favicons?domain=${shortcut.url}&sz=64`;
|
icon.src = `https://www.google.com/s2/favicons?domain=${shortcut.url}&sz=64`;
|
||||||
icon.alt = shortcut.name;
|
icon.alt = shortcut.name;
|
||||||
icon.draggable = false;
|
|
||||||
|
|
||||||
const name = document.createElement('span');
|
const name = document.createElement('span');
|
||||||
name.textContent = shortcut.name;
|
name.textContent = shortcut.name;
|
||||||
|
@ -98,13 +96,11 @@ const shortcuts = {
|
||||||
element.appendChild(name);
|
element.appendChild(name);
|
||||||
|
|
||||||
element.addEventListener('click', (e) => {
|
element.addEventListener('click', (e) => {
|
||||||
if (!grid.classList.contains('grid-draggable') || !e.target.closest('.shortcut').classList.contains('drag-active')) {
|
|
||||||
if (e.ctrlKey) {
|
if (e.ctrlKey) {
|
||||||
window.open(shortcut.url, '_blank');
|
window.open(shortcut.url, '_blank');
|
||||||
} else {
|
} else {
|
||||||
window.location.href = shortcut.url;
|
window.location.href = shortcut.url;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
element.addEventListener('contextmenu', (e) => {
|
element.addEventListener('contextmenu', (e) => {
|
||||||
|
@ -132,6 +128,7 @@ const shortcuts = {
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// Initialization
|
||||||
init: () => {
|
init: () => {
|
||||||
const addShortcutButton = document.getElementById('add-shortcut');
|
const addShortcutButton = document.getElementById('add-shortcut');
|
||||||
const modal = document.getElementById('add-shortcut-modal');
|
const modal = document.getElementById('add-shortcut-modal');
|
||||||
|
@ -204,6 +201,7 @@ const shortcuts = {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Context menu actions
|
||||||
const contextMenu = document.getElementById('context-menu');
|
const contextMenu = document.getElementById('context-menu');
|
||||||
if (contextMenu) {
|
if (contextMenu) {
|
||||||
contextMenu.addEventListener('click', (e) => {
|
contextMenu.addEventListener('click', (e) => {
|
||||||
|
@ -261,6 +259,7 @@ const shortcuts = {
|
||||||
const currentShortcuts = Storage.get('shortcuts') || [];
|
const currentShortcuts = Storage.get('shortcuts') || [];
|
||||||
const shortcut = currentShortcuts[index];
|
const shortcut = currentShortcuts[index];
|
||||||
|
|
||||||
|
// Open the URL of the shortcut in a new tab
|
||||||
if (shortcut && shortcut.url) {
|
if (shortcut && shortcut.url) {
|
||||||
window.open(shortcut.url, '_blank');
|
window.open(shortcut.url, '_blank');
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,9 @@
|
||||||
|
/**
|
||||||
|
* Storage utility object for managing localStorage operations
|
||||||
|
* All methods handle JSON parsing/stringifying and error cases
|
||||||
|
*/
|
||||||
const Storage = {
|
const Storage = {
|
||||||
|
// Retrieve and parse stored value
|
||||||
get: (key) => {
|
get: (key) => {
|
||||||
try {
|
try {
|
||||||
return JSON.parse(localStorage.getItem(key));
|
return JSON.parse(localStorage.getItem(key));
|
||||||
|
@ -7,6 +12,7 @@ const Storage = {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// Store value as JSON string
|
||||||
set: (key, value) => {
|
set: (key, value) => {
|
||||||
try {
|
try {
|
||||||
localStorage.setItem(key, JSON.stringify(value));
|
localStorage.setItem(key, JSON.stringify(value));
|
||||||
|
@ -16,6 +22,7 @@ const Storage = {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// Delete specific key
|
||||||
remove: (key) => {
|
remove: (key) => {
|
||||||
try {
|
try {
|
||||||
localStorage.removeItem(key);
|
localStorage.removeItem(key);
|
||||||
|
@ -25,6 +32,7 @@ const Storage = {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// Remove all stored data
|
||||||
clear: () => {
|
clear: () => {
|
||||||
try {
|
try {
|
||||||
localStorage.clear();
|
localStorage.clear();
|
||||||
|
|
|
@ -1,93 +0,0 @@
|
||||||
const versionUrl = 'https://www.junaid.xyz/projects/jstar-tab/version.txt';
|
|
||||||
const manifestVersion = chrome.runtime.getManifest().version;
|
|
||||||
|
|
||||||
function compareVersions(version1, version2) {
|
|
||||||
const v1 = version1.split('.').map(Number);
|
|
||||||
const v2 = version2.split('.').map(Number);
|
|
||||||
|
|
||||||
for (let i = 0; i < Math.max(v1.length, v2.length); i++) {
|
|
||||||
const diff = (v1[i] || 0) - (v2[i] || 0);
|
|
||||||
if (diff !== 0) return diff;
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
async function checkForUpdate() {
|
|
||||||
try {
|
|
||||||
const response = await fetch(versionUrl, { cache: 'no-store' });
|
|
||||||
const latestVersion = await response.text();
|
|
||||||
handleVersionComparison(latestVersion);
|
|
||||||
} catch (error) {
|
|
||||||
const cachedResponse = await caches.match(versionUrl);
|
|
||||||
if (cachedResponse) {
|
|
||||||
const cachedVersion = await cachedResponse.text();
|
|
||||||
handleVersionComparison(cachedVersion, true);
|
|
||||||
} else {
|
|
||||||
updateVersionIcon(null);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function handleVersionComparison(latestVersion, isCached = false) {
|
|
||||||
latestVersion = latestVersion.trim();
|
|
||||||
const comparison = compareVersions(latestVersion, manifestVersion);
|
|
||||||
|
|
||||||
updateVersionIcon(comparison, latestVersion);
|
|
||||||
|
|
||||||
if (comparison > 0) {
|
|
||||||
const alertMessage = `New version ${latestVersion} available! ` +
|
|
||||||
`<a href="https://github.com/DevJSTAR/JSTAR-Tab/releases/${latestVersion}" ` +
|
|
||||||
`target="_blank" style="color: #2196F3;">Update now</a>`;
|
|
||||||
|
|
||||||
if (isCached) {
|
|
||||||
notifications.show(`${alertMessage} (Showing cached version)`, 'info', 8000);
|
|
||||||
} else {
|
|
||||||
notifications.show(alertMessage, 'info');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function updateVersionIcon(versionComparison, latestVersion) {
|
|
||||||
const versionIcon = document.getElementById('version-icon');
|
|
||||||
if (!versionIcon) return;
|
|
||||||
|
|
||||||
versionIcon.className = 'version-icon fas';
|
|
||||||
versionIcon.style.color = '';
|
|
||||||
versionIcon.removeAttribute('title');
|
|
||||||
|
|
||||||
latestVersion = latestVersion.trim();
|
|
||||||
|
|
||||||
if (versionComparison === 0) {
|
|
||||||
versionIcon.classList.add('fa-check-circle');
|
|
||||||
versionIcon.style.color = '#4caf50';
|
|
||||||
versionIcon.title = 'You’re up to date! Enjoy the latest features.';
|
|
||||||
} else if (versionComparison > 0) {
|
|
||||||
versionIcon.classList.add('fa-exclamation-circle');
|
|
||||||
versionIcon.style.color = '#ff9800';
|
|
||||||
versionIcon.title = `A newer version (${latestVersion}) is available! Don’t miss out on the new goodies.`;
|
|
||||||
} else if (versionComparison < 0) {
|
|
||||||
versionIcon.classList.add('fa-question-circle');
|
|
||||||
versionIcon.style.color = '#2196f3';
|
|
||||||
versionIcon.title = 'Whoa! You’re ahead of the curve. Are you from the future?';
|
|
||||||
} else {
|
|
||||||
versionIcon.classList.add('fa-times-circle');
|
|
||||||
versionIcon.style.color = '#f44336';
|
|
||||||
versionIcon.title = 'Unable to check the version. Is the internet sleeping?';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function showUpdateNotification(latestVersion) {
|
|
||||||
const message = `Version ${latestVersion} is available! <a href="https://github.com/DevJSTAR/JSTAR-Tab/releases/${latestVersion}" target="_blank">Update now</a>!`;
|
|
||||||
notifications.show(message, 'info');
|
|
||||||
}
|
|
||||||
|
|
||||||
checkForUpdate();
|
|
||||||
|
|
||||||
document.addEventListener('DOMContentLoaded', () => {
|
|
||||||
const version = chrome.runtime.getManifest().version;
|
|
||||||
const versionElement = document.getElementById('extension-version');
|
|
||||||
if (versionElement) {
|
|
||||||
versionElement.innerHTML = `JSTAR Tab v<a href="https://github.com/DevJSTAR/JSTAR-Tab/releases/${version}" target="_blank" style="color: inherit;">${version}</a> <span id="version-icon" class="version-icon"></span>`;
|
|
||||||
}
|
|
||||||
});
|
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"manifest_version": 3,
|
"manifest_version": 3,
|
||||||
"name": "JSTAR Tab",
|
"name": "JSTAR Tab",
|
||||||
"version": "3.0.0",
|
"version": "2.6.2",
|
||||||
"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.",
|
"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": {
|
"chrome_url_overrides": {
|
||||||
"newtab": "index.html"
|
"newtab": "index.html"
|
||||||
|
|
999
style.css
Normal file
|
@ -0,0 +1,999 @@
|
||||||
|
/* 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(0, 0, 0, 0.75);
|
||||||
|
--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: #dddddd;
|
||||||
|
border: 2px solid #dddddd;
|
||||||
|
}
|
||||||
|
|
||||||
|
.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;
|
||||||
|
transition: transform 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
#add-shortcut:hover {
|
||||||
|
background: var(--primary-hover);
|
||||||
|
transform: scale(1.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
#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: scale(1.079);
|
||||||
|
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);
|
||||||
|
transition: transform 0.4s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.settings-button:hover {
|
||||||
|
transform: rotate(180deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.settings-button:active {
|
||||||
|
transform: rotate(360deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.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: 0rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.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;
|
||||||
|
padding-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.settings-section h3 {
|
||||||
|
margin-bottom: 1.5rem;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.setting-item {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
padding: 0.75rem 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.setting-item.horizontal {
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
|
||||||
|
.setting-item .setting-label {
|
||||||
|
font-weight: 500;
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.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%;
|
||||||
|
transition: transform 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
input:checked + .toggle-slider {
|
||||||
|
background-color: var(--text);
|
||||||
|
}
|
||||||
|
|
||||||
|
input:checked + .toggle-slider:before {
|
||||||
|
transform: translateX(24px);
|
||||||
|
}
|
||||||
|
|
||||||
|
#keybind-url-combo {
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
#keybind-url {
|
||||||
|
margin-top: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.keybind-container {
|
||||||
|
display: flex;
|
||||||
|
gap: 0.5rem;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.clear-keybind {
|
||||||
|
padding: 0.25rem 0.5rem;
|
||||||
|
background: var(--accent);
|
||||||
|
border: none;
|
||||||
|
border-radius: 4px;
|
||||||
|
color: var(--text);
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 1.2rem;
|
||||||
|
line-height: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.clear-keybind:hover {
|
||||||
|
opacity: 0.8;
|
||||||
|
}
|
||||||
|
|
||||||
|
.format-hint {
|
||||||
|
display: block;
|
||||||
|
font-size: 0.75rem;
|
||||||
|
color: var(--text-secondary);
|
||||||
|
opacity: 0.7;
|
||||||
|
margin-top: 0.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Mobile-first responsive styles */
|
||||||
|
@media screen and (max-width: 768px) {
|
||||||
|
.center-container {
|
||||||
|
padding: 1rem;
|
||||||
|
gap: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
#greeting {
|
||||||
|
font-size: 1.5rem;
|
||||||
|
height: 36px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-container {
|
||||||
|
max-width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
#search-bar {
|
||||||
|
padding: 1rem;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-engine-options {
|
||||||
|
grid-template-columns: repeat(2, 1fr);
|
||||||
|
gap: 0.75rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-engine-option {
|
||||||
|
padding: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-engine-option img {
|
||||||
|
width: 24px;
|
||||||
|
height: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#shortcuts-grid {
|
||||||
|
grid-template-columns: repeat(auto-fill, minmax(80px, 1fr));
|
||||||
|
gap: 0.75rem;
|
||||||
|
padding: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.shortcut {
|
||||||
|
padding: 0.75rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.shortcut img {
|
||||||
|
width: 24px;
|
||||||
|
height: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.shortcut span {
|
||||||
|
font-size: 0.75rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-content {
|
||||||
|
width: 95%;
|
||||||
|
padding: 1.5rem;
|
||||||
|
border-radius: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.step h2 {
|
||||||
|
font-size: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.setting-item {
|
||||||
|
padding: 0.5rem 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.setting-label {
|
||||||
|
font-size: 0.9rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type="text"], select {
|
||||||
|
padding: 0.75rem;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.notification {
|
||||||
|
width: 90%;
|
||||||
|
max-width: none;
|
||||||
|
margin: 0.5rem;
|
||||||
|
padding: 0.75rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Styles for very small screens */
|
||||||
|
@media screen and (max-width: 480px) {
|
||||||
|
#greeting {
|
||||||
|
font-size: 1.25rem;
|
||||||
|
height: 32px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-engine-options {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
gap: 0.5rem;
|
||||||
|
padding: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
#shortcuts-grid {
|
||||||
|
grid-template-columns: repeat(auto-fill, minmax(70px, 1fr));
|
||||||
|
gap: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-content {
|
||||||
|
padding: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-actions {
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-actions button {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.format-hint {
|
||||||
|
font-size: 0.7rem;
|
||||||
|
line-height: 1.2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Landscape orientation adjustments */
|
||||||
|
@media screen and (max-height: 480px) and (orientation: landscape) {
|
||||||
|
.center-container {
|
||||||
|
padding: 0.5rem;
|
||||||
|
gap: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
#greeting {
|
||||||
|
font-size: 1.25rem;
|
||||||
|
height: 32px;
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-content {
|
||||||
|
height: 90vh;
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-engine-options {
|
||||||
|
grid-template-columns: repeat(3, 1fr);
|
||||||
|
gap: 0.5rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Mobile-specific styles for onboarding */
|
||||||
|
@media screen and (max-width: 480px) {
|
||||||
|
.search-engine-options {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(2, 1fr);
|
||||||
|
gap: 0.75rem;
|
||||||
|
padding: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-engine-option {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
text-align: center;
|
||||||
|
padding: 1rem;
|
||||||
|
background: var(--surface);
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
border-radius: 12px;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
aspect-ratio: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-engine-option img {
|
||||||
|
width: 24px;
|
||||||
|
height: 24px;
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-engine-option span {
|
||||||
|
font-size: 0.85rem;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-engine-option.selected {
|
||||||
|
background: #eeeeee;
|
||||||
|
border: 2px solid var(--border);
|
||||||
|
}
|
||||||
|
|
||||||
|
.onboarding-modal .modal-content {
|
||||||
|
width: 90%;
|
||||||
|
max-width: none;
|
||||||
|
padding: 1.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.step h2 {
|
||||||
|
font-size: 1.35rem;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
#complete-setup-btn {
|
||||||
|
width: 100%;
|
||||||
|
margin-top: 1rem;
|
||||||
|
padding: 0.875rem;
|
||||||
|
font-size: 0.95rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.import-options {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
gap: 1rem;
|
||||||
|
width: 100%;
|
||||||
|
max-width: 300px;
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.or-divider {
|
||||||
|
position: relative;
|
||||||
|
width: 100%;
|
||||||
|
text-align: center;
|
||||||
|
margin: 0.5rem 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.or-divider::before,
|
||||||
|
.or-divider::after {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
width: calc(50% - 24px);
|
||||||
|
height: 1px;
|
||||||
|
background-color: var(--border);
|
||||||
|
}
|
||||||
|
|
||||||
|
.or-divider::before {
|
||||||
|
left: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.or-divider::after {
|
||||||
|
right: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.or-divider span {
|
||||||
|
background-color: var(--background);
|
||||||
|
padding: 0 8px;
|
||||||
|
color: var(--text);
|
||||||
|
font-size: 0.9rem;
|
||||||
|
}
|
60
sw.js
|
@ -1,60 +0,0 @@
|
||||||
const CACHE_PREFIX = 'jstartab-cache';
|
|
||||||
const VERSION_URL = 'https://www.junaid.xyz/projects/jstar-tab/version.txt';
|
|
||||||
const STATIC_CACHE = 'jstartab-static';
|
|
||||||
|
|
||||||
self.addEventListener('install', (event) => {
|
|
||||||
self.skipWaiting();
|
|
||||||
event.waitUntil(
|
|
||||||
caches.open(STATIC_CACHE).then(cache => cache.add(VERSION_URL))
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
self.addEventListener('activate', (event) => {
|
|
||||||
event.waitUntil(clients.claim());
|
|
||||||
});
|
|
||||||
|
|
||||||
self.addEventListener('message', async (event) => {
|
|
||||||
if (event.data.action === 'updateFavicons') {
|
|
||||||
const faviconUrls = event.data.urls;
|
|
||||||
const currentDate = new Date();
|
|
||||||
const cacheName = `${CACHE_PREFIX}-${currentDate.toISOString().split('T')[0]}`;
|
|
||||||
|
|
||||||
const cache = await caches.open(cacheName);
|
|
||||||
await cache.addAll(faviconUrls);
|
|
||||||
|
|
||||||
const keys = await caches.keys();
|
|
||||||
keys.forEach((key) => {
|
|
||||||
if (key.startsWith(CACHE_PREFIX) && key !== cacheName) {
|
|
||||||
caches.delete(key);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
self.addEventListener('fetch', (event) => {
|
|
||||||
const request = event.request;
|
|
||||||
|
|
||||||
if (event.request.url.startsWith('https://www.google.com/s2/favicons')) {
|
|
||||||
event.respondWith(
|
|
||||||
caches.match(event.request).then((response) => {
|
|
||||||
return response || fetch(event.request).then((fetchResponse) => {
|
|
||||||
const cacheCopy = fetchResponse.clone();
|
|
||||||
caches.open(`${CACHE_PREFIX}-${new Date().toISOString().split('T')[0]}`)
|
|
||||||
.then((cache) => cache.put(event.request, cacheCopy));
|
|
||||||
return fetchResponse;
|
|
||||||
});
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (request.url === VERSION_URL) {
|
|
||||||
event.respondWith(
|
|
||||||
caches.open(STATIC_CACHE).then(cache =>
|
|
||||||
fetch(request).then(networkResponse => {
|
|
||||||
cache.put(request, networkResponse.clone());
|
|
||||||
return networkResponse;
|
|
||||||
}).catch(() => cache.match(request))
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
});
|
|