aztomiq 1.0.2 → 1.0.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +4 -1
- package/scripts/builds/assets.js +30 -22
- package/scripts/builds/data.js +30 -19
- package/server.js +125 -0
- package/src/assets/css/categories.css +90 -0
- package/src/assets/css/global.css +1815 -0
- package/src/assets/css/home.css +509 -0
- package/src/assets/css/master-layout.css +133 -0
- package/src/assets/css/sidebar-layout.css +264 -0
- package/src/assets/images/logo.svg +29 -0
- package/src/assets/js/global.js +373 -0
- package/src/assets/js/home.js +138 -0
- package/src/assets/js/user-mode.js +54 -0
- package/src/assets/vendor/qrcode/qrcode.min.js +1 -0
- package/src/locales/en/common.yaml +93 -0
- package/src/locales/vi/common.yaml +93 -0
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
/* ============================================
|
|
2
|
+
UNIVERSAL SIDEBAR LAYOUT SYSTEM
|
|
3
|
+
All pages use consistent sidebar + content
|
|
4
|
+
============================================ */
|
|
5
|
+
|
|
6
|
+
/* Page Wrapper */
|
|
7
|
+
.page-wrapper {
|
|
8
|
+
width: 100%;
|
|
9
|
+
padding: 2rem 0;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/* Universal Layout - Sidebar + Content */
|
|
13
|
+
.sidebar-layout {
|
|
14
|
+
display: flex;
|
|
15
|
+
gap: 2rem;
|
|
16
|
+
max-width: 1400px;
|
|
17
|
+
margin: 0 auto;
|
|
18
|
+
padding: 0 1rem;
|
|
19
|
+
min-height: 70vh;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/* Sidebar - Left Column */
|
|
23
|
+
.page-sidebar {
|
|
24
|
+
width: 250px;
|
|
25
|
+
flex-shrink: 0;
|
|
26
|
+
display: flex;
|
|
27
|
+
flex-direction: column;
|
|
28
|
+
gap: 1rem;
|
|
29
|
+
position: sticky;
|
|
30
|
+
top: 80px;
|
|
31
|
+
height: fit-content;
|
|
32
|
+
padding: 1.5rem;
|
|
33
|
+
background: var(--card-bg);
|
|
34
|
+
border: 1px solid var(--border-color);
|
|
35
|
+
border-radius: var(--radius);
|
|
36
|
+
box-shadow: var(--shadow);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/* Sidebar Title */
|
|
40
|
+
.sidebar-title {
|
|
41
|
+
font-size: 0.9rem;
|
|
42
|
+
font-weight: 600;
|
|
43
|
+
color: var(--text-muted);
|
|
44
|
+
text-transform: uppercase;
|
|
45
|
+
letter-spacing: 0.5px;
|
|
46
|
+
margin-bottom: 0.5rem;
|
|
47
|
+
padding-bottom: 0.5rem;
|
|
48
|
+
border-bottom: 1px solid var(--border-color);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/* Sidebar Navigation */
|
|
52
|
+
.sidebar-nav {
|
|
53
|
+
display: flex;
|
|
54
|
+
flex-direction: column;
|
|
55
|
+
gap: 0.5rem;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
.sidebar-nav-item {
|
|
59
|
+
display: flex;
|
|
60
|
+
align-items: center;
|
|
61
|
+
gap: 0.75rem;
|
|
62
|
+
padding: 0.75rem 1rem;
|
|
63
|
+
border: 1px solid var(--border-color);
|
|
64
|
+
background: var(--bg-hover);
|
|
65
|
+
border-radius: 8px;
|
|
66
|
+
cursor: pointer;
|
|
67
|
+
transition: all 0.2s;
|
|
68
|
+
text-decoration: none;
|
|
69
|
+
color: var(--text-color);
|
|
70
|
+
font-size: 0.95rem;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
.sidebar-nav-item:hover {
|
|
74
|
+
border-color: var(--primary-color);
|
|
75
|
+
background: var(--card-bg);
|
|
76
|
+
transform: translateX(5px);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
.sidebar-nav-item.active {
|
|
80
|
+
background: var(--primary-color);
|
|
81
|
+
color: white;
|
|
82
|
+
border-color: var(--primary-color);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
.sidebar-nav-item i {
|
|
86
|
+
font-size: 1.1rem;
|
|
87
|
+
opacity: 0.8;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/* Sidebar Info Box */
|
|
91
|
+
.sidebar-info {
|
|
92
|
+
padding: 1rem;
|
|
93
|
+
background: rgba(var(--primary-rgb), 0.1);
|
|
94
|
+
border: 1px solid rgba(var(--primary-rgb), 0.2);
|
|
95
|
+
border-radius: 8px;
|
|
96
|
+
font-size: 0.85rem;
|
|
97
|
+
color: var(--text-muted);
|
|
98
|
+
line-height: 1.5;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/* Sidebar Quick Actions */
|
|
102
|
+
.sidebar-actions {
|
|
103
|
+
display: flex;
|
|
104
|
+
flex-direction: column;
|
|
105
|
+
gap: 0.5rem;
|
|
106
|
+
margin-top: auto;
|
|
107
|
+
padding-top: 1rem;
|
|
108
|
+
border-top: 1px solid var(--border-color);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
.sidebar-action-btn {
|
|
112
|
+
padding: 0.75rem;
|
|
113
|
+
background: var(--primary-color);
|
|
114
|
+
color: white;
|
|
115
|
+
border: none;
|
|
116
|
+
border-radius: 8px;
|
|
117
|
+
cursor: pointer;
|
|
118
|
+
font-weight: 600;
|
|
119
|
+
transition: all 0.2s;
|
|
120
|
+
text-align: center;
|
|
121
|
+
text-decoration: none;
|
|
122
|
+
display: block;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
.sidebar-action-btn:hover {
|
|
126
|
+
background: var(--primary-hover);
|
|
127
|
+
transform: translateY(-2px);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/* Main Content - Right Column */
|
|
131
|
+
.page-content {
|
|
132
|
+
flex: 1;
|
|
133
|
+
min-width: 0;
|
|
134
|
+
padding: 2rem;
|
|
135
|
+
background: var(--card-bg);
|
|
136
|
+
border: 1px solid var(--border-color);
|
|
137
|
+
border-radius: var(--radius);
|
|
138
|
+
box-shadow: var(--shadow);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/* Content Header */
|
|
142
|
+
.content-header {
|
|
143
|
+
margin-bottom: 2rem;
|
|
144
|
+
padding-bottom: 1.5rem;
|
|
145
|
+
border-bottom: 1px solid var(--border-color);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
.content-header h1 {
|
|
149
|
+
font-size: 2rem;
|
|
150
|
+
margin-bottom: 0.5rem;
|
|
151
|
+
color: var(--text-color);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
.content-header p {
|
|
155
|
+
color: var(--text-muted);
|
|
156
|
+
font-size: 1.1rem;
|
|
157
|
+
line-height: 1.6;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/* Content Sections */
|
|
161
|
+
.content-section {
|
|
162
|
+
margin-bottom: 2rem;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
.content-section:last-child {
|
|
166
|
+
margin-bottom: 0;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
.content-section h2 {
|
|
170
|
+
font-size: 1.5rem;
|
|
171
|
+
margin-bottom: 1rem;
|
|
172
|
+
color: var(--text-color);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
.content-section h3 {
|
|
176
|
+
font-size: 1.2rem;
|
|
177
|
+
margin-bottom: 0.75rem;
|
|
178
|
+
color: var(--text-color);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/* Content Cards */
|
|
182
|
+
.content-card {
|
|
183
|
+
background: var(--bg-hover);
|
|
184
|
+
border: 1px solid var(--border-color);
|
|
185
|
+
border-radius: 8px;
|
|
186
|
+
padding: 1.5rem;
|
|
187
|
+
margin-bottom: 1rem;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
.content-card:last-child {
|
|
191
|
+
margin-bottom: 0;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/* Responsive - Mobile */
|
|
195
|
+
@media (max-width: 1000px) {
|
|
196
|
+
.sidebar-layout {
|
|
197
|
+
flex-direction: column;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
.page-sidebar {
|
|
201
|
+
width: 100%;
|
|
202
|
+
position: static;
|
|
203
|
+
flex-direction: row;
|
|
204
|
+
overflow-x: auto;
|
|
205
|
+
padding: 1rem;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
.sidebar-nav {
|
|
209
|
+
flex-direction: row;
|
|
210
|
+
flex-wrap: nowrap;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
.sidebar-nav-item {
|
|
214
|
+
white-space: nowrap;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
.sidebar-actions {
|
|
218
|
+
margin-top: 0;
|
|
219
|
+
margin-left: auto;
|
|
220
|
+
padding-top: 0;
|
|
221
|
+
padding-left: 1rem;
|
|
222
|
+
border-top: none;
|
|
223
|
+
border-left: 1px solid var(--border-color);
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
.page-content {
|
|
227
|
+
padding: 1.5rem;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
.content-header h1 {
|
|
231
|
+
font-size: 1.5rem;
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
@media (max-width: 768px) {
|
|
236
|
+
.page-wrapper {
|
|
237
|
+
padding: 1rem 0;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
.sidebar-layout {
|
|
241
|
+
padding: 0 0.75rem;
|
|
242
|
+
gap: 1rem;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
.page-sidebar {
|
|
246
|
+
flex-direction: column;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
.sidebar-nav {
|
|
250
|
+
flex-direction: column;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
.sidebar-actions {
|
|
254
|
+
margin-left: 0;
|
|
255
|
+
padding-left: 0;
|
|
256
|
+
border-left: none;
|
|
257
|
+
padding-top: 1rem;
|
|
258
|
+
border-top: 1px solid var(--border-color);
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
.page-content {
|
|
262
|
+
padding: 1rem;
|
|
263
|
+
}
|
|
264
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
<svg
|
|
2
|
+
width="100"
|
|
3
|
+
height="100"
|
|
4
|
+
viewBox="0 0 100 100"
|
|
5
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
6
|
+
>
|
|
7
|
+
<!-- Symmetrical Z with minimal padding -->
|
|
8
|
+
<path
|
|
9
|
+
d="M10 15
|
|
10
|
+
H90
|
|
11
|
+
L10 85
|
|
12
|
+
H90"
|
|
13
|
+
fill="none"
|
|
14
|
+
stroke="#22d3ee"
|
|
15
|
+
stroke-width="14"
|
|
16
|
+
stroke-linecap="round"
|
|
17
|
+
stroke-linejoin="round"
|
|
18
|
+
/>
|
|
19
|
+
|
|
20
|
+
<!-- Hidden A stroke (refined) -->
|
|
21
|
+
<path
|
|
22
|
+
d="M45 50
|
|
23
|
+
L65 88"
|
|
24
|
+
fill="none"
|
|
25
|
+
stroke="#116974"
|
|
26
|
+
stroke-width="14"
|
|
27
|
+
stroke-linecap="round"
|
|
28
|
+
/>
|
|
29
|
+
</svg>
|
|
@@ -0,0 +1,373 @@
|
|
|
1
|
+
document.addEventListener('DOMContentLoaded', () => {
|
|
2
|
+
const toggleBtn = document.getElementById('theme-toggle');
|
|
3
|
+
const htmlEl = document.documentElement;
|
|
4
|
+
|
|
5
|
+
// Update button icon based on current theme
|
|
6
|
+
const updateIcon = (theme) => {
|
|
7
|
+
const iconName = theme === 'dark' ? 'sun' : 'moon';
|
|
8
|
+
toggleBtn.innerHTML = `<i data-lucide="${iconName}"></i>`;
|
|
9
|
+
if (window.lucide) lucide.createIcons();
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
updateIcon(htmlEl.getAttribute('data-theme'));
|
|
13
|
+
|
|
14
|
+
toggleBtn.addEventListener('click', () => {
|
|
15
|
+
const currentTheme = htmlEl.getAttribute('data-theme');
|
|
16
|
+
const newTheme = currentTheme === 'dark' ? 'light' : 'dark';
|
|
17
|
+
|
|
18
|
+
htmlEl.setAttribute('data-theme', newTheme);
|
|
19
|
+
localStorage.setItem('theme', newTheme);
|
|
20
|
+
updateIcon(newTheme);
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
console.log('AZtomiq Global JS Loaded');
|
|
24
|
+
|
|
25
|
+
// Register Service Worker for PWA
|
|
26
|
+
if ('serviceWorker' in navigator) {
|
|
27
|
+
window.addEventListener('load', () => {
|
|
28
|
+
navigator.serviceWorker.register('/sw.js')
|
|
29
|
+
.then(req => console.log('SW Registered!', req.scope))
|
|
30
|
+
.catch(err => console.error('SW Registration Failed', err));
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
// --- Global Navigation & Mega Menu Logic ---
|
|
34
|
+
const navItems = document.querySelectorAll('.nav-item');
|
|
35
|
+
|
|
36
|
+
// 1. Logo/Menu Toggle
|
|
37
|
+
navItems.forEach(item => {
|
|
38
|
+
const toggle = item.querySelector('.dropdown-toggle');
|
|
39
|
+
const menu = item.querySelector('.dropdown-menu');
|
|
40
|
+
|
|
41
|
+
if (toggle && menu) {
|
|
42
|
+
toggle.addEventListener('click', (e) => {
|
|
43
|
+
e.preventDefault();
|
|
44
|
+
e.stopPropagation();
|
|
45
|
+
|
|
46
|
+
// Close other open menus first
|
|
47
|
+
document.querySelectorAll('.dropdown-menu.show').forEach(m => {
|
|
48
|
+
if (m !== menu) m.classList.remove('show');
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
menu.classList.toggle('show');
|
|
52
|
+
toggle.setAttribute('aria-expanded', menu.classList.contains('show'));
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
// 2. Mega Menu Category Toggles (Expand/Collapse)
|
|
58
|
+
document.querySelectorAll('.mega-header').forEach(header => {
|
|
59
|
+
header.addEventListener('click', (e) => {
|
|
60
|
+
e.stopPropagation(); // prevent closing the menu itself
|
|
61
|
+
e.preventDefault();
|
|
62
|
+
const expanded = header.getAttribute('aria-expanded') === 'true';
|
|
63
|
+
header.setAttribute('aria-expanded', !expanded);
|
|
64
|
+
});
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
// 3. Close dropdown when clicking outside
|
|
68
|
+
document.addEventListener('click', (e) => {
|
|
69
|
+
if (!e.target.closest('.nav-item')) {
|
|
70
|
+
document.querySelectorAll('.dropdown-menu.show').forEach(menu => {
|
|
71
|
+
menu.classList.remove('show');
|
|
72
|
+
const toggle = menu.parentElement.querySelector('.dropdown-toggle');
|
|
73
|
+
if (toggle) toggle.setAttribute('aria-expanded', 'false');
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
// --- Search Modal Logic ---
|
|
79
|
+
const searchBtn = document.getElementById('search-btn');
|
|
80
|
+
const searchModal = document.getElementById('search-modal');
|
|
81
|
+
const searchInput = document.getElementById('search-input');
|
|
82
|
+
const searchResults = document.getElementById('search-results');
|
|
83
|
+
const closeSearch = document.getElementById('close-search');
|
|
84
|
+
const searchOverlay = document.querySelector('.search-overlay');
|
|
85
|
+
|
|
86
|
+
let toolsData = [];
|
|
87
|
+
let i18nData = {};
|
|
88
|
+
try {
|
|
89
|
+
const toolsScript = document.getElementById('tools-data');
|
|
90
|
+
if (toolsScript) toolsData = JSON.parse(toolsScript.textContent);
|
|
91
|
+
|
|
92
|
+
const i18nScript = document.getElementById('i18n-data');
|
|
93
|
+
if (i18nScript) i18nData = JSON.parse(i18nScript.textContent);
|
|
94
|
+
} catch (e) { console.error('Error loading search/i18n data', e); }
|
|
95
|
+
|
|
96
|
+
const openSearch = () => {
|
|
97
|
+
searchModal.classList.add('show');
|
|
98
|
+
document.body.style.overflow = 'hidden';
|
|
99
|
+
setTimeout(() => searchInput.focus(), 50);
|
|
100
|
+
renderResults('');
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
const hideSearch = () => {
|
|
104
|
+
searchModal.classList.remove('show');
|
|
105
|
+
document.body.style.overflow = '';
|
|
106
|
+
searchInput.value = '';
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
if (searchBtn) searchBtn.addEventListener('click', openSearch);
|
|
110
|
+
const searchBtnMobile = document.getElementById('search-btn-mobile');
|
|
111
|
+
if (searchBtnMobile) searchBtnMobile.addEventListener('click', openSearch);
|
|
112
|
+
|
|
113
|
+
const headerSearchBox = document.getElementById('header-search-box');
|
|
114
|
+
if (headerSearchBox) {
|
|
115
|
+
headerSearchBox.addEventListener('click', openSearch);
|
|
116
|
+
|
|
117
|
+
// Smart visibility for Homepage Hero Search
|
|
118
|
+
const heroSearch = document.querySelector('.search-box-hero');
|
|
119
|
+
if (heroSearch) {
|
|
120
|
+
// Check initial state
|
|
121
|
+
headerSearchBox.classList.add('search-hidden');
|
|
122
|
+
|
|
123
|
+
window.addEventListener('scroll', () => {
|
|
124
|
+
const rect = heroSearch.getBoundingClientRect();
|
|
125
|
+
// If hero search bottom is above viewport (scrolled past)
|
|
126
|
+
if (rect.bottom < 0) {
|
|
127
|
+
headerSearchBox.classList.remove('search-hidden');
|
|
128
|
+
} else {
|
|
129
|
+
headerSearchBox.classList.add('search-hidden');
|
|
130
|
+
}
|
|
131
|
+
}, { passive: true });
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
if (closeSearch) closeSearch.addEventListener('click', hideSearch);
|
|
136
|
+
if (searchOverlay) searchOverlay.addEventListener('click', hideSearch);
|
|
137
|
+
|
|
138
|
+
// Keyboard Shortcuts
|
|
139
|
+
document.addEventListener('keydown', (e) => {
|
|
140
|
+
if ((e.ctrlKey || e.metaKey) && e.key === 'k') {
|
|
141
|
+
e.preventDefault();
|
|
142
|
+
openSearch();
|
|
143
|
+
}
|
|
144
|
+
if (e.key === 'Escape') hideSearch();
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
// Search Logic
|
|
148
|
+
searchInput.addEventListener('input', (e) => {
|
|
149
|
+
renderResults(e.target.value.trim());
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
function renderResults(query) {
|
|
153
|
+
if (!searchResults) return;
|
|
154
|
+
|
|
155
|
+
const q = query.toLowerCase();
|
|
156
|
+
const filtered = toolsData.filter(tool => {
|
|
157
|
+
if (!q) return true;
|
|
158
|
+
return tool.title.toLowerCase().includes(q) ||
|
|
159
|
+
tool.desc.toLowerCase().includes(q) ||
|
|
160
|
+
tool.id.toLowerCase().includes(q);
|
|
161
|
+
}).slice(0, 10);
|
|
162
|
+
|
|
163
|
+
if (filtered.length === 0) {
|
|
164
|
+
const emptyMsg = i18nData.search_no_results || 'No results for';
|
|
165
|
+
searchResults.innerHTML = `<div class="search-no-results">${emptyMsg} "${query}"</div>`;
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
searchResults.innerHTML = filtered.map((tool, index) => `
|
|
170
|
+
<a href="${tool.link}" class="search-result-item ${index === 0 ? 'selected' : ''}" data-index="${index}">
|
|
171
|
+
<div class="result-icon"><i data-lucide="${tool.icon}"></i></div>
|
|
172
|
+
<div class="result-info">
|
|
173
|
+
<span class="result-title">${tool.title}</span>
|
|
174
|
+
<span class="result-desc">${tool.desc}</span>
|
|
175
|
+
</div>
|
|
176
|
+
<span class="result-cat">${tool.category}</span>
|
|
177
|
+
</a>
|
|
178
|
+
`).join('');
|
|
179
|
+
|
|
180
|
+
if (window.lucide) lucide.createIcons();
|
|
181
|
+
|
|
182
|
+
// Handle Keyboard Navigation within results
|
|
183
|
+
setupResultNavigation();
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
function setupResultNavigation() {
|
|
187
|
+
let selectedIndex = 0;
|
|
188
|
+
const items = searchResults.querySelectorAll('.search-result-item');
|
|
189
|
+
|
|
190
|
+
searchInput.onkeydown = (e) => {
|
|
191
|
+
if (e.key === 'ArrowDown') {
|
|
192
|
+
e.preventDefault();
|
|
193
|
+
items[selectedIndex].classList.remove('selected');
|
|
194
|
+
selectedIndex = (selectedIndex + 1) % items.length;
|
|
195
|
+
items[selectedIndex].classList.add('selected');
|
|
196
|
+
items[selectedIndex].scrollIntoView({ block: 'nearest' });
|
|
197
|
+
} else if (e.key === 'ArrowUp') {
|
|
198
|
+
e.preventDefault();
|
|
199
|
+
items[selectedIndex].classList.remove('selected');
|
|
200
|
+
selectedIndex = (selectedIndex - 1 + items.length) % items.length;
|
|
201
|
+
items[selectedIndex].classList.add('selected');
|
|
202
|
+
items[selectedIndex].scrollIntoView({ block: 'nearest' });
|
|
203
|
+
} else if (e.key === 'Enter') {
|
|
204
|
+
e.preventDefault();
|
|
205
|
+
const selected = searchResults.querySelector('.search-result-item.selected');
|
|
206
|
+
if (selected) selected.click();
|
|
207
|
+
}
|
|
208
|
+
};
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// --- Changelog Modal Logic ---
|
|
212
|
+
const changelogBtn = document.getElementById('open-changelog');
|
|
213
|
+
const changelogModal = document.getElementById('changelog-modal');
|
|
214
|
+
const closeChangelog = document.getElementById('close-changelog');
|
|
215
|
+
const changelogOverlay = document.getElementById('close-changelog-overlay');
|
|
216
|
+
|
|
217
|
+
if (changelogBtn && changelogModal) {
|
|
218
|
+
changelogBtn.addEventListener('click', () => {
|
|
219
|
+
changelogModal.classList.add('show');
|
|
220
|
+
document.body.style.overflow = 'hidden';
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
const hideChangelog = () => {
|
|
224
|
+
changelogModal.classList.remove('show');
|
|
225
|
+
document.body.style.overflow = '';
|
|
226
|
+
};
|
|
227
|
+
|
|
228
|
+
if (closeChangelog) closeChangelog.addEventListener('click', hideChangelog);
|
|
229
|
+
if (changelogOverlay) changelogOverlay.addEventListener('click', hideChangelog);
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
// --- End Search & Changelog Logic ---
|
|
233
|
+
|
|
234
|
+
// Track clicks on tool items (both in Mega Menu and Homepage Grid)
|
|
235
|
+
document.querySelectorAll('.tool-item, .mega-link, .search-result-item').forEach(link => {
|
|
236
|
+
link.addEventListener('click', (e) => {
|
|
237
|
+
const url = link.getAttribute('href');
|
|
238
|
+
trackUsage(url);
|
|
239
|
+
|
|
240
|
+
// Close any open mega-menu if this was a menu click
|
|
241
|
+
if (link.classList.contains('mega-link')) {
|
|
242
|
+
document.querySelectorAll('.dropdown-menu.show').forEach(menu => {
|
|
243
|
+
menu.classList.remove('show');
|
|
244
|
+
const toggle = menu.parentElement.querySelector('.dropdown-toggle');
|
|
245
|
+
if (toggle) toggle.setAttribute('aria-expanded', 'false');
|
|
246
|
+
});
|
|
247
|
+
}
|
|
248
|
+
});
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
function trackUsage(toolUrl) {
|
|
252
|
+
if (!toolUrl) return;
|
|
253
|
+
try {
|
|
254
|
+
const parts = toolUrl.split('/').filter(Boolean);
|
|
255
|
+
const id = parts[parts.length - 1];
|
|
256
|
+
const now = Date.now();
|
|
257
|
+
|
|
258
|
+
// 1. Recently Used
|
|
259
|
+
let recent = JSON.parse(localStorage.getItem('aztomiq_recent') || '{}');
|
|
260
|
+
recent[id] = now;
|
|
261
|
+
localStorage.setItem('aztomiq_recent', JSON.stringify(recent));
|
|
262
|
+
|
|
263
|
+
// 2. Most Used (Personal)
|
|
264
|
+
let usage = JSON.parse(localStorage.getItem('aztomiq_usage') || '{}');
|
|
265
|
+
usage[id] = (usage[id] || 0) + 1;
|
|
266
|
+
localStorage.setItem('aztomiq_usage', JSON.stringify(usage));
|
|
267
|
+
|
|
268
|
+
} catch (e) { console.error('Tracking error', e); }
|
|
269
|
+
}
|
|
270
|
+
// --- Favorite Tools Logic (Star System) ---
|
|
271
|
+
const FAVORITES_KEY = 'aztomiq_favorites';
|
|
272
|
+
let favorites = JSON.parse(localStorage.getItem(FAVORITES_KEY) || '[]');
|
|
273
|
+
|
|
274
|
+
function updateStars() {
|
|
275
|
+
document.querySelectorAll('.star-btn').forEach(btn => {
|
|
276
|
+
const id = btn.getAttribute('data-tool-id');
|
|
277
|
+
const isActive = favorites.includes(id);
|
|
278
|
+
btn.classList.toggle('active', isActive);
|
|
279
|
+
const icon = btn.querySelector('i');
|
|
280
|
+
if (icon && window.lucide) {
|
|
281
|
+
// We just toggle the class, CSS handles the fill/color
|
|
282
|
+
}
|
|
283
|
+
});
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
function toggleFavorite(id) {
|
|
287
|
+
const index = favorites.indexOf(id);
|
|
288
|
+
if (index === -1) {
|
|
289
|
+
favorites.push(id);
|
|
290
|
+
} else {
|
|
291
|
+
favorites.splice(index, 1);
|
|
292
|
+
}
|
|
293
|
+
localStorage.setItem(FAVORITES_KEY, JSON.stringify(favorites));
|
|
294
|
+
updateStars();
|
|
295
|
+
renderFavorites();
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
function renderFavorites() {
|
|
299
|
+
const favoritesSection = document.getElementById('favorites-section');
|
|
300
|
+
const favoritesGrid = document.getElementById('favorites-grid');
|
|
301
|
+
if (!favoritesGrid) return;
|
|
302
|
+
|
|
303
|
+
if (favorites.length === 0) {
|
|
304
|
+
favoritesSection.style.display = 'none';
|
|
305
|
+
return;
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
favoritesSection.style.display = 'block';
|
|
309
|
+
|
|
310
|
+
// Find tool objects from toolsData
|
|
311
|
+
const favoriteTools = favorites.map(id => toolsData.find(t => t.id === id)).filter(Boolean);
|
|
312
|
+
|
|
313
|
+
favoritesGrid.innerHTML = favoriteTools.map(tool => {
|
|
314
|
+
let modeClass = '';
|
|
315
|
+
if (tool.mode === 'standard') modeClass = 'mode-standard-only';
|
|
316
|
+
else if (tool.mode === 'advanced') modeClass = 'mode-advanced-only';
|
|
317
|
+
|
|
318
|
+
return `
|
|
319
|
+
<div class="tool-card-wrapper ${modeClass}">
|
|
320
|
+
<div class="tool-card">
|
|
321
|
+
<button class="star-btn active" data-tool-id="${tool.id}" aria-label="Remove from favorites" title="Remove from favorites">
|
|
322
|
+
<i data-lucide="star" style="width: 14px; height: 14px; fill: currentColor;"></i>
|
|
323
|
+
</button>
|
|
324
|
+
|
|
325
|
+
<div class="tool-badge-row">
|
|
326
|
+
${tool.highlight ? `<span class="tool-badge hot"><i data-lucide="sparkles" style="width: 12px; height: 12px;"></i> HOT</span>` : ''}
|
|
327
|
+
${tool.status === 'not-ready' ? `<span class="tool-badge beta">BETA</span>` : ''}
|
|
328
|
+
</div>
|
|
329
|
+
|
|
330
|
+
<a href="${tool.link}" class="tool-link">
|
|
331
|
+
<div class="tool-icon-wrap">
|
|
332
|
+
<i data-lucide="${tool.icon}" style="width: 32px; height: 32px;"></i>
|
|
333
|
+
</div>
|
|
334
|
+
<h3>${tool.title}</h3>
|
|
335
|
+
<p>${tool.desc}</p>
|
|
336
|
+
</a>
|
|
337
|
+
</div>
|
|
338
|
+
</div>
|
|
339
|
+
`;
|
|
340
|
+
}).join('');
|
|
341
|
+
|
|
342
|
+
if (window.lucide) lucide.createIcons();
|
|
343
|
+
|
|
344
|
+
// Re-attach listeners to new buttons in favorites grid
|
|
345
|
+
favoritesGrid.querySelectorAll('.star-btn').forEach(btn => {
|
|
346
|
+
btn.onclick = (e) => {
|
|
347
|
+
e.preventDefault();
|
|
348
|
+
e.stopPropagation();
|
|
349
|
+
toggleFavorite(btn.getAttribute('data-tool-id'));
|
|
350
|
+
};
|
|
351
|
+
});
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
// Global delegate for stars
|
|
355
|
+
document.addEventListener('click', (e) => {
|
|
356
|
+
const starBtn = e.target.closest('.star-btn');
|
|
357
|
+
if (starBtn && !starBtn.closest('#favorites-grid')) {
|
|
358
|
+
e.preventDefault();
|
|
359
|
+
e.stopPropagation();
|
|
360
|
+
toggleFavorite(starBtn.getAttribute('data-tool-id'));
|
|
361
|
+
}
|
|
362
|
+
});
|
|
363
|
+
|
|
364
|
+
// Initial render
|
|
365
|
+
updateStars();
|
|
366
|
+
renderFavorites();
|
|
367
|
+
|
|
368
|
+
// Final Lucide init (for any dynamically added or static icons missed)
|
|
369
|
+
if (window.lucide) lucide.createIcons();
|
|
370
|
+
|
|
371
|
+
// --- End Favorite Tools Logic ---
|
|
372
|
+
});
|
|
373
|
+
|