maverick-wave 3.0.0
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/.claude/commands/mw.md +580 -0
- package/.claude/settings.local.json +11 -0
- package/.idea/codeStyles/Project.xml +59 -0
- package/.idea/codeStyles/codeStyleConfig.xml +5 -0
- package/.idea/maverick-wave.iml +12 -0
- package/.idea/modules.xml +8 -0
- package/.idea/prettier.xml +6 -0
- package/.idea/vcs.xml +6 -0
- package/.prettierignore +6 -0
- package/.prettierrc.json +7 -0
- package/CHANGELOG.md +420 -0
- package/CLAUDE.md +53 -0
- package/LICENSE +21 -0
- package/README.md +191 -0
- package/gulpfile.js +158 -0
- package/index.html +472 -0
- package/maverick-wave.min.css +1 -0
- package/maverick-wave.min.js +1 -0
- package/package.json +52 -0
- package/release.sh +17 -0
- package/src/assets/favicon/android-chrome-192x192.png +0 -0
- package/src/assets/favicon/android-chrome-512x512.png +0 -0
- package/src/assets/favicon/apple-touch-icon.png +0 -0
- package/src/assets/favicon/favicon-16x16.png +0 -0
- package/src/assets/favicon/favicon-32x32.png +0 -0
- package/src/assets/favicon/site.webmanifest +1 -0
- package/src/assets/favicon.ico +0 -0
- package/src/assets/header-logo.svg +24 -0
- package/src/js/main.js +587 -0
- package/src/partials/accordions-container.html +163 -0
- package/src/partials/alerts-container.html +120 -0
- package/src/partials/avatars-container.html +84 -0
- package/src/partials/blog-posts-container.html +157 -0
- package/src/partials/breadcrumbs-container.html +126 -0
- package/src/partials/button-bar-container.html +168 -0
- package/src/partials/buttons-container.html +133 -0
- package/src/partials/cards-container.html +508 -0
- package/src/partials/code-container.html +194 -0
- package/src/partials/colors-container.html +125 -0
- package/src/partials/coming-soon-container.html +6 -0
- package/src/partials/content-slider-container.html +47 -0
- package/src/partials/divider-container.html +52 -0
- package/src/partials/documentation-container.html +161 -0
- package/src/partials/empty-state-container.html +74 -0
- package/src/partials/footer-container.html +113 -0
- package/src/partials/form-container.html +257 -0
- package/src/partials/form-elements-container.html +595 -0
- package/src/partials/form-field-container.html +141 -0
- package/src/partials/gallery-container.html +49 -0
- package/src/partials/get-started-container.html +294 -0
- package/src/partials/grid-container.html +171 -0
- package/src/partials/header-container.html +49 -0
- package/src/partials/header-utilities-container.html +80 -0
- package/src/partials/home-container.html +48 -0
- package/src/partials/html-lists-container.html +58 -0
- package/src/partials/info-container.html +87 -0
- package/src/partials/input-group-container.html +285 -0
- package/src/partials/item-lists-container.html +413 -0
- package/src/partials/login-container.html +61 -0
- package/src/partials/meta-info-container.html +16 -0
- package/src/partials/modals-container.html +136 -0
- package/src/partials/pagination-container.html +27 -0
- package/src/partials/panels-container.html +196 -0
- package/src/partials/progress-container.html +226 -0
- package/src/partials/ratings-container.html +104 -0
- package/src/partials/skeleton-container.html +82 -0
- package/src/partials/spinners-container.html +92 -0
- package/src/partials/stepper-container.html +107 -0
- package/src/partials/tables-container.html +120 -0
- package/src/partials/tabs-container.html +263 -0
- package/src/partials/tags-container.html +168 -0
- package/src/partials/techstack-bucket-container.html +174 -0
- package/src/partials/tiles-container.html +245 -0
- package/src/partials/timelines-container.html +196 -0
- package/src/partials/typography-container.html +66 -0
- package/src/partials/utilities-container.html +85 -0
- package/src/scss/abstracts/_functions.scss +37 -0
- package/src/scss/abstracts/_index.scss +3 -0
- package/src/scss/abstracts/_mixins.scss +45 -0
- package/src/scss/abstracts/_variables.scss +222 -0
- package/src/scss/base/_base.scss +165 -0
- package/src/scss/base/_index.scss +3 -0
- package/src/scss/base/_reset.scss +75 -0
- package/src/scss/base/_typography.scss +230 -0
- package/src/scss/components/_accordions.scss +91 -0
- package/src/scss/components/_alerts.scss +105 -0
- package/src/scss/components/_avatars.scss +123 -0
- package/src/scss/components/_blog-post.scss +268 -0
- package/src/scss/components/_breadcrumbs.scss +108 -0
- package/src/scss/components/_button-bar.scss +139 -0
- package/src/scss/components/_buttons.scss +164 -0
- package/src/scss/components/_cards.scss +316 -0
- package/src/scss/components/_code.scss +252 -0
- package/src/scss/components/_coming-soon.scss +92 -0
- package/src/scss/components/_content-slider.scss +119 -0
- package/src/scss/components/_divider.scss +70 -0
- package/src/scss/components/_empty-state.scss +73 -0
- package/src/scss/components/_gallery.scss +158 -0
- package/src/scss/components/_index.scss +33 -0
- package/src/scss/components/_info.scss +143 -0
- package/src/scss/components/_lists.scss +653 -0
- package/src/scss/components/_localhost-indicator.scss +29 -0
- package/src/scss/components/_meta-info.scss +34 -0
- package/src/scss/components/_modals.scss +118 -0
- package/src/scss/components/_pagination.scss +99 -0
- package/src/scss/components/_panels.scss +112 -0
- package/src/scss/components/_progress.scss +107 -0
- package/src/scss/components/_ratings.scss +47 -0
- package/src/scss/components/_skeleton.scss +90 -0
- package/src/scss/components/_spinners.scss +208 -0
- package/src/scss/components/_stepper.scss +145 -0
- package/src/scss/components/_tables.scss +127 -0
- package/src/scss/components/_tabs.scss +146 -0
- package/src/scss/components/_tags.scss +93 -0
- package/src/scss/components/_techstack-bucket.scss +145 -0
- package/src/scss/components/_theme-toggle.scss +50 -0
- package/src/scss/components/_tiles.scss +217 -0
- package/src/scss/components/_timelines.scss +339 -0
- package/src/scss/form-elements/_checkbox.scss +174 -0
- package/src/scss/form-elements/_form.scss +122 -0
- package/src/scss/form-elements/_index.scss +10 -0
- package/src/scss/form-elements/_input-group.scss +103 -0
- package/src/scss/form-elements/_input.scss +72 -0
- package/src/scss/form-elements/_login.scss +62 -0
- package/src/scss/form-elements/_radio.scss +144 -0
- package/src/scss/form-elements/_select.scss +56 -0
- package/src/scss/form-elements/_slider.scss +174 -0
- package/src/scss/form-elements/_textarea.scss +74 -0
- package/src/scss/form-elements/_toggle.scss +174 -0
- package/src/scss/layout/_footer.scss +214 -0
- package/src/scss/layout/_grid.scss +287 -0
- package/src/scss/layout/_header.scss +441 -0
- package/src/scss/layout/_home.scss +40 -0
- package/src/scss/layout/_index.scss +6 -0
- package/src/scss/layout/_main.scss +116 -0
- package/src/scss/layout/_section.scss +89 -0
- package/src/scss/main.scss +5 -0
- package/src/scss/utilities/_display.scss +35 -0
- package/src/scss/utilities/_flex.scss +66 -0
- package/src/scss/utilities/_index.scss +3 -0
- package/src/scss/utilities/_spacing.scss +78 -0
package/src/js/main.js
ADDED
|
@@ -0,0 +1,587 @@
|
|
|
1
|
+
(function () {
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
// Initialize all components when DOM is ready
|
|
5
|
+
document.addEventListener('DOMContentLoaded', function () {
|
|
6
|
+
// Initialize all components
|
|
7
|
+
initGalleries();
|
|
8
|
+
initThemeToggle();
|
|
9
|
+
initColorSwatches();
|
|
10
|
+
initAccordions();
|
|
11
|
+
initMobileNav();
|
|
12
|
+
initProgressBars();
|
|
13
|
+
initSmoothScrolling();
|
|
14
|
+
initScrollSpy();
|
|
15
|
+
initTabs();
|
|
16
|
+
initAlerts();
|
|
17
|
+
initLocalhostIndicator();
|
|
18
|
+
initFormSliders();
|
|
19
|
+
initModals();
|
|
20
|
+
initHeaderLoginButton();
|
|
21
|
+
initImageSliders();
|
|
22
|
+
initCheckboxLists();
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
// ===== Checkbox Lists =====
|
|
26
|
+
function initCheckboxLists() {
|
|
27
|
+
const checkboxLists = document.querySelectorAll(
|
|
28
|
+
'.mw-item-list-checkbox, .mw-item-list-checkbox-scroll'
|
|
29
|
+
);
|
|
30
|
+
|
|
31
|
+
checkboxLists.forEach((list) => {
|
|
32
|
+
const listItems = list.querySelectorAll('li');
|
|
33
|
+
|
|
34
|
+
listItems.forEach((item) => {
|
|
35
|
+
const checkbox = item.querySelector('input[type="checkbox"]');
|
|
36
|
+
const label = item.querySelector('.mw-checkbox');
|
|
37
|
+
|
|
38
|
+
if (!checkbox || !label) return;
|
|
39
|
+
|
|
40
|
+
// Set initial state based on checkbox checked property
|
|
41
|
+
if (checkbox.checked) {
|
|
42
|
+
item.classList.add('mw-selected');
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Add click handler to the entire list item
|
|
46
|
+
item.addEventListener('click', function (e) {
|
|
47
|
+
if (
|
|
48
|
+
e.target === checkbox ||
|
|
49
|
+
e.target.closest('.mw-checkbox') === label
|
|
50
|
+
) {
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
toggleCheckbox(this);
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
// Add change handler to the checkbox itself
|
|
57
|
+
checkbox.addEventListener('change', function () {
|
|
58
|
+
const listItem = this.closest('li');
|
|
59
|
+
if (this.checked) {
|
|
60
|
+
listItem.classList.add('mw-selected');
|
|
61
|
+
} else {
|
|
62
|
+
listItem.classList.remove('mw-selected');
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// Dispatch custom event
|
|
66
|
+
listItem.dispatchEvent(
|
|
67
|
+
new CustomEvent('checkboxToggle', {
|
|
68
|
+
detail: { checked: this.checked, item: listItem },
|
|
69
|
+
})
|
|
70
|
+
);
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
// Add label click handler
|
|
74
|
+
label.addEventListener('click', function (e) {
|
|
75
|
+
setTimeout(() => {
|
|
76
|
+
const listItem = this.closest('li');
|
|
77
|
+
const checkbox = this.querySelector('input[type="checkbox"]');
|
|
78
|
+
|
|
79
|
+
if (checkbox.checked) {
|
|
80
|
+
listItem.classList.add('mw-selected');
|
|
81
|
+
} else {
|
|
82
|
+
listItem.classList.remove('mw-selected');
|
|
83
|
+
}
|
|
84
|
+
}, 0);
|
|
85
|
+
});
|
|
86
|
+
});
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
// Listen for custom events (optional - for debugging or external handling)
|
|
90
|
+
document.addEventListener('checkboxToggle', function (event) {
|
|
91
|
+
console.log('Checkbox toggled:', event.detail.checked, event.detail.item);
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// Global function for manual checkbox toggling (for onclick attributes)
|
|
96
|
+
window.toggleCheckbox = function (listItem) {
|
|
97
|
+
const checkbox = listItem.querySelector('input[type="checkbox"]');
|
|
98
|
+
if (!checkbox) return;
|
|
99
|
+
|
|
100
|
+
const isChecked = checkbox.checked;
|
|
101
|
+
|
|
102
|
+
// Toggle checkbox
|
|
103
|
+
checkbox.checked = !isChecked;
|
|
104
|
+
|
|
105
|
+
// Add/remove selected class for visual feedback
|
|
106
|
+
if (checkbox.checked) {
|
|
107
|
+
listItem.classList.add('mw-selected');
|
|
108
|
+
} else {
|
|
109
|
+
listItem.classList.remove('mw-selected');
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// Dispatch custom event for external handling
|
|
113
|
+
listItem.dispatchEvent(
|
|
114
|
+
new CustomEvent('checkboxToggle', {
|
|
115
|
+
detail: { checked: checkbox.checked, item: listItem },
|
|
116
|
+
})
|
|
117
|
+
);
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
// ===== Gallery Component =====
|
|
121
|
+
function initGalleries() {
|
|
122
|
+
const track = document.querySelector('.mw-gallery-track');
|
|
123
|
+
|
|
124
|
+
if (track) {
|
|
125
|
+
const dots = document.querySelector('.mw-gallery-dots');
|
|
126
|
+
const slides = track.children;
|
|
127
|
+
const descBox = document.querySelector('.mw-gallery-desc');
|
|
128
|
+
const prevBtn = document.querySelector('.mw-gallery-navi-prev');
|
|
129
|
+
const nextBtn = document.querySelector('.mw-gallery-navi-next');
|
|
130
|
+
const sliderContainer = document.querySelector('.mw-gallery');
|
|
131
|
+
|
|
132
|
+
let current = 0;
|
|
133
|
+
|
|
134
|
+
function updateSliderPosition() {
|
|
135
|
+
const slideWidth = sliderContainer.offsetWidth;
|
|
136
|
+
track.style.transform = `translateX(-${current * slideWidth}px)`;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function goToSlide(index) {
|
|
140
|
+
const total = slides.length;
|
|
141
|
+
current = (index + total) % total;
|
|
142
|
+
updateSliderPosition();
|
|
143
|
+
|
|
144
|
+
document.querySelectorAll('.mw-gallery-dot').forEach((dot, i) => {
|
|
145
|
+
dot.classList.toggle('mw-active', i === current);
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
descBox.textContent = slides[current].dataset.desc;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
function initDots() {
|
|
152
|
+
for (let i = 0; i < slides.length; i++) {
|
|
153
|
+
const dot = document.createElement('span');
|
|
154
|
+
dot.className = 'mw-gallery-dot' + (i === 0 ? ' mw-active' : '');
|
|
155
|
+
dot.addEventListener('click', () => goToSlide(i));
|
|
156
|
+
dots.appendChild(dot);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
prevBtn.addEventListener('click', () => goToSlide(current - 1));
|
|
161
|
+
nextBtn.addEventListener('click', () => goToSlide(current + 1));
|
|
162
|
+
|
|
163
|
+
window.addEventListener('resize', updateSliderPosition);
|
|
164
|
+
|
|
165
|
+
// Optional: Swipe support
|
|
166
|
+
let startX = 0;
|
|
167
|
+
track.addEventListener(
|
|
168
|
+
'touchstart',
|
|
169
|
+
(e) => (startX = e.touches[0].clientX)
|
|
170
|
+
);
|
|
171
|
+
track.addEventListener('touchend', (e) => {
|
|
172
|
+
const delta = e.changedTouches[0].clientX - startX;
|
|
173
|
+
if (delta > 50) goToSlide(current - 1);
|
|
174
|
+
if (delta < -50) goToSlide(current + 1);
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
initDots();
|
|
178
|
+
updateSliderPosition();
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// ===== Theme Toggle =====
|
|
183
|
+
function initThemeToggle() {
|
|
184
|
+
const themeToggle = document.querySelector('.mw-theme-toggle');
|
|
185
|
+
if (!themeToggle) return;
|
|
186
|
+
|
|
187
|
+
const body = document.body;
|
|
188
|
+
const icon = themeToggle.querySelector('.mw-theme-toggle-slider i');
|
|
189
|
+
|
|
190
|
+
const computedStyle = getComputedStyle(document.documentElement);
|
|
191
|
+
const themeMode =
|
|
192
|
+
computedStyle.getPropertyValue('--mw-internal-theme-mode').trim() ||
|
|
193
|
+
'switchable';
|
|
194
|
+
|
|
195
|
+
// mode dark or light
|
|
196
|
+
if (themeMode !== 'switchable') {
|
|
197
|
+
console.log(`Theme mode fixed to: ${themeMode}. Disabling toggle.`);
|
|
198
|
+
if (icon) {
|
|
199
|
+
icon.className = themeMode === 'light' ? 'fas fa-sun' : 'fas fa-moon';
|
|
200
|
+
}
|
|
201
|
+
// Adjust toggle 'active' state if needed (assuming 'active' shows sun)
|
|
202
|
+
if (themeMode === 'light') {
|
|
203
|
+
themeToggle.classList.add('active');
|
|
204
|
+
} else {
|
|
205
|
+
themeToggle.classList.remove('active');
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
// Disable existing toggle visually and functionally
|
|
209
|
+
themeToggle.style.opacity = '0.4';
|
|
210
|
+
themeToggle.style.pointerEvents = 'none';
|
|
211
|
+
themeToggle.style.cursor = 'default';
|
|
212
|
+
themeToggle.setAttribute('aria-disabled', 'true');
|
|
213
|
+
|
|
214
|
+
// Clean up potentially conflicting localStorage
|
|
215
|
+
localStorage.removeItem('mw-theme');
|
|
216
|
+
|
|
217
|
+
// Ensure body class is correct for fixed mode (remove light if fixed dark)
|
|
218
|
+
if (themeMode === 'dark') {
|
|
219
|
+
body.classList.remove('mw-theme-light');
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
return;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
// mode switchable - only runs if themeMode === 'switchable'
|
|
226
|
+
let isLight = localStorage.getItem('mw-theme') === 'light';
|
|
227
|
+
|
|
228
|
+
// Function to apply theme styles and icon
|
|
229
|
+
const applyTheme = (lightMode) => {
|
|
230
|
+
// Use toggle's second argument for cleaner class switching
|
|
231
|
+
body.classList.toggle('mw-theme-light', lightMode);
|
|
232
|
+
themeToggle.classList.toggle('active', lightMode);
|
|
233
|
+
if (icon) {
|
|
234
|
+
icon.className = lightMode ? 'fas fa-sun' : 'fas fa-moon';
|
|
235
|
+
}
|
|
236
|
+
};
|
|
237
|
+
|
|
238
|
+
// Set initial theme based on isLight
|
|
239
|
+
applyTheme(isLight);
|
|
240
|
+
|
|
241
|
+
// Add click listener for switching
|
|
242
|
+
themeToggle.addEventListener('click', () => {
|
|
243
|
+
isLight = !isLight;
|
|
244
|
+
applyTheme(isLight);
|
|
245
|
+
localStorage.setItem('mw-theme', isLight ? 'light' : 'dark'); // Save
|
|
246
|
+
|
|
247
|
+
// update color swatches
|
|
248
|
+
setTimeout(updateColorSwatchHexValues, 400);
|
|
249
|
+
});
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
// ===== Color Swatches =====
|
|
253
|
+
function initColorSwatches() {
|
|
254
|
+
updateColorSwatchHexValues();
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
function updateColorSwatchHexValues() {
|
|
258
|
+
const colorSwatches = document.querySelectorAll('.color-swatch');
|
|
259
|
+
colorSwatches.forEach((swatch) => {
|
|
260
|
+
const bgColor = window.getComputedStyle(swatch).backgroundColor;
|
|
261
|
+
const hex = rgbToHex(bgColor);
|
|
262
|
+
const hexTextElement =
|
|
263
|
+
swatch.parentElement.querySelector('.mw-text-muted');
|
|
264
|
+
if (hexTextElement) {
|
|
265
|
+
hexTextElement.textContent = hex;
|
|
266
|
+
}
|
|
267
|
+
});
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
function rgbToHex(rgb) {
|
|
271
|
+
// Check if the color is in RGB/RGBA format
|
|
272
|
+
if (!rgb || rgb === 'transparent') return '#000000';
|
|
273
|
+
|
|
274
|
+
let rgbArray;
|
|
275
|
+
if (rgb.startsWith('rgba')) {
|
|
276
|
+
rgbArray = rgb.match(/rgba?\((\d+),\s*(\d+),\s*(\d+)(?:,\s*[\d.]+)?\)/);
|
|
277
|
+
} else {
|
|
278
|
+
rgbArray = rgb.match(/rgb?\((\d+),\s*(\d+),\s*(\d+)\)/);
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
if (!rgbArray) return rgb;
|
|
282
|
+
|
|
283
|
+
const r = parseInt(rgbArray[1], 10).toString(16).padStart(2, '0');
|
|
284
|
+
const g = parseInt(rgbArray[2], 10).toString(16).padStart(2, '0');
|
|
285
|
+
const b = parseInt(rgbArray[3], 10).toString(16).padStart(2, '0');
|
|
286
|
+
|
|
287
|
+
return `#${r}${g}${b}`.toUpperCase();
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
// ===== Accordions =====
|
|
291
|
+
function initAccordions() {
|
|
292
|
+
const accordionHeaders = document.querySelectorAll('.mw-accordion-header');
|
|
293
|
+
accordionHeaders.forEach((header) => {
|
|
294
|
+
header.addEventListener('click', function () {
|
|
295
|
+
this.classList.toggle('active');
|
|
296
|
+
const content = this.nextElementSibling;
|
|
297
|
+
if (content) content.classList.toggle('active');
|
|
298
|
+
});
|
|
299
|
+
});
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
// ===== Mobile Navigation =====
|
|
303
|
+
function initMobileNav() {
|
|
304
|
+
const menuBtn = document.querySelector('.mw-menu-btn');
|
|
305
|
+
const navbar = document.querySelector('.mw-navbar');
|
|
306
|
+
|
|
307
|
+
if (!menuBtn || !navbar) return;
|
|
308
|
+
|
|
309
|
+
function toggleMenu(e) {
|
|
310
|
+
e.preventDefault();
|
|
311
|
+
e.stopPropagation();
|
|
312
|
+
menuBtn.classList.toggle('open');
|
|
313
|
+
navbar.classList.toggle('open');
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
// Add multiple event listeners for better iOS compatibility
|
|
317
|
+
menuBtn.addEventListener('click', toggleMenu);
|
|
318
|
+
menuBtn.addEventListener('touchstart', toggleMenu, { passive: false });
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
// ===== Progress Bars =====
|
|
322
|
+
function initProgressBars() {
|
|
323
|
+
const fills = document.querySelectorAll('.mw-progress-fill');
|
|
324
|
+
if (!fills.length) return;
|
|
325
|
+
|
|
326
|
+
const obs = new IntersectionObserver(
|
|
327
|
+
(entries, observer) => {
|
|
328
|
+
entries.forEach((entry) => {
|
|
329
|
+
if (!entry.isIntersecting) return;
|
|
330
|
+
const bar = entry.target;
|
|
331
|
+
const pct = bar.dataset.value || 0;
|
|
332
|
+
// trigger the CSS transition
|
|
333
|
+
bar.style.width = pct + '%';
|
|
334
|
+
// stop observing this one
|
|
335
|
+
observer.unobserve(bar);
|
|
336
|
+
});
|
|
337
|
+
},
|
|
338
|
+
{
|
|
339
|
+
root: null,
|
|
340
|
+
threshold: 0.2,
|
|
341
|
+
}
|
|
342
|
+
);
|
|
343
|
+
|
|
344
|
+
fills.forEach((bar) => obs.observe(bar));
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
// ===== Smooth Scrolling =====
|
|
348
|
+
function initSmoothScrolling() {
|
|
349
|
+
document.querySelectorAll('a[href^="#"]').forEach((anchor) => {
|
|
350
|
+
anchor.addEventListener('click', function (e) {
|
|
351
|
+
const targetId = this.getAttribute('href');
|
|
352
|
+
if (targetId === '#') return;
|
|
353
|
+
|
|
354
|
+
const targetElement = document.querySelector(targetId);
|
|
355
|
+
if (!targetElement) return;
|
|
356
|
+
|
|
357
|
+
const menuBtn = document.querySelector('.mw-menu-btn');
|
|
358
|
+
const nav = document.querySelector('.mw-navbar');
|
|
359
|
+
if (menuBtn && nav) {
|
|
360
|
+
menuBtn.classList.remove('open');
|
|
361
|
+
nav.classList.remove('open');
|
|
362
|
+
}
|
|
363
|
+
});
|
|
364
|
+
});
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
// ===== Scroll Spy =====
|
|
368
|
+
function initScrollSpy() {
|
|
369
|
+
const sections = document.querySelectorAll('section[id]');
|
|
370
|
+
const navLinks = document.querySelectorAll('.mw-navbar-link');
|
|
371
|
+
|
|
372
|
+
if (sections.length === 0 || navLinks.length === 0) return;
|
|
373
|
+
|
|
374
|
+
window.addEventListener(
|
|
375
|
+
'scroll',
|
|
376
|
+
debounce(function () {
|
|
377
|
+
let current = '';
|
|
378
|
+
|
|
379
|
+
sections.forEach((section) => {
|
|
380
|
+
const sectionTop = section.offsetTop - 100;
|
|
381
|
+
const sectionHeight = section.clientHeight;
|
|
382
|
+
|
|
383
|
+
if (scrollY >= sectionTop && scrollY < sectionTop + sectionHeight) {
|
|
384
|
+
current = section.getAttribute('id');
|
|
385
|
+
}
|
|
386
|
+
});
|
|
387
|
+
|
|
388
|
+
navLinks.forEach((link) => {
|
|
389
|
+
link.classList.remove('active');
|
|
390
|
+
|
|
391
|
+
const href = link.getAttribute('href');
|
|
392
|
+
if (href) {
|
|
393
|
+
const hashIndex = href.indexOf('#');
|
|
394
|
+
if (hashIndex !== -1) {
|
|
395
|
+
const linkTarget = href.substring(hashIndex + 1);
|
|
396
|
+
if (linkTarget === current) {
|
|
397
|
+
link.classList.add('active');
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
});
|
|
402
|
+
}, 100)
|
|
403
|
+
);
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
// ===== Tabs =====
|
|
407
|
+
function initTabs() {
|
|
408
|
+
const tabNavItems = document.querySelectorAll('.mw-tabs-nav-item');
|
|
409
|
+
|
|
410
|
+
tabNavItems.forEach((item) => {
|
|
411
|
+
item.addEventListener('click', function () {
|
|
412
|
+
const tabsContainer = this.closest('.mw-tabs');
|
|
413
|
+
if (!tabsContainer) return;
|
|
414
|
+
|
|
415
|
+
tabsContainer
|
|
416
|
+
.querySelectorAll('.mw-tabs-nav-item')
|
|
417
|
+
.forEach((navItem) => {
|
|
418
|
+
navItem.classList.remove('active');
|
|
419
|
+
});
|
|
420
|
+
|
|
421
|
+
this.classList.add('active');
|
|
422
|
+
|
|
423
|
+
const tabId = this.getAttribute('data-tab');
|
|
424
|
+
if (!tabId) return;
|
|
425
|
+
|
|
426
|
+
tabsContainer.querySelectorAll('.mw-tabs-panel').forEach((panel) => {
|
|
427
|
+
panel.classList.remove('active');
|
|
428
|
+
});
|
|
429
|
+
|
|
430
|
+
const targetPanel = document.getElementById(tabId);
|
|
431
|
+
if (targetPanel) targetPanel.classList.add('active');
|
|
432
|
+
});
|
|
433
|
+
});
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
// ===== Alerts =====
|
|
437
|
+
function initAlerts() {
|
|
438
|
+
const alertCloseButtons = document.querySelectorAll('.mw-alert-close');
|
|
439
|
+
|
|
440
|
+
alertCloseButtons.forEach((button) => {
|
|
441
|
+
button.addEventListener('click', function () {
|
|
442
|
+
const alert = this.closest('.mw-alert');
|
|
443
|
+
if (!alert) return;
|
|
444
|
+
|
|
445
|
+
alert.classList.add('mw-alert-closing');
|
|
446
|
+
setTimeout(() => {
|
|
447
|
+
alert.classList.add('mw-alert-closed');
|
|
448
|
+
}, 300);
|
|
449
|
+
});
|
|
450
|
+
});
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
// ===== Utility Functions =====
|
|
454
|
+
function debounce(func, wait) {
|
|
455
|
+
let timeout;
|
|
456
|
+
return function () {
|
|
457
|
+
const context = this;
|
|
458
|
+
const args = arguments;
|
|
459
|
+
clearTimeout(timeout);
|
|
460
|
+
timeout = setTimeout(() => func.apply(context, args), wait);
|
|
461
|
+
};
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
// ===== Utility Functions =====
|
|
465
|
+
function initLocalhostIndicator() {
|
|
466
|
+
const activated = document.querySelector(
|
|
467
|
+
'.mw-localhost-indicator-activated'
|
|
468
|
+
);
|
|
469
|
+
const header = document.querySelector('.mw-header');
|
|
470
|
+
|
|
471
|
+
if (activated) {
|
|
472
|
+
const isLocalhost =
|
|
473
|
+
window.location.hostname === 'localhost' ||
|
|
474
|
+
window.location.hostname === '127.0.0.1' ||
|
|
475
|
+
window.location.hostname.includes('192.168.');
|
|
476
|
+
|
|
477
|
+
if (isLocalhost) {
|
|
478
|
+
const indicator = document.createElement('div');
|
|
479
|
+
indicator.className = 'mw-localhost-indicator-pulse';
|
|
480
|
+
header.prepend(indicator);
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
// ===== Form Sliders =====
|
|
486
|
+
function initFormSliders() {
|
|
487
|
+
document.querySelectorAll('.mw-slider-container').forEach((wrapper) => {
|
|
488
|
+
const slider = wrapper.querySelector('.mw-slider');
|
|
489
|
+
const badge = wrapper.querySelector('.mw-slider-value');
|
|
490
|
+
if (!slider || !badge) return;
|
|
491
|
+
|
|
492
|
+
const update = () => {
|
|
493
|
+
const val = Number(slider.value);
|
|
494
|
+
const max = Number(slider.max) || 100;
|
|
495
|
+
const pct = Math.round((val / max) * 100);
|
|
496
|
+
|
|
497
|
+
// update track‐fill
|
|
498
|
+
slider.style.setProperty('--value', pct + '%');
|
|
499
|
+
|
|
500
|
+
// decide what to show in the badge
|
|
501
|
+
if (badge.classList.contains('mw-slider-numeric')) {
|
|
502
|
+
badge.setAttribute('data-value', val);
|
|
503
|
+
} else {
|
|
504
|
+
badge.setAttribute('data-value', pct);
|
|
505
|
+
}
|
|
506
|
+
};
|
|
507
|
+
|
|
508
|
+
slider.addEventListener('input', update);
|
|
509
|
+
|
|
510
|
+
update();
|
|
511
|
+
});
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
// ===== Modals =====
|
|
515
|
+
function initModals() {
|
|
516
|
+
document.querySelectorAll('.mw-modal-close').forEach((button) => {
|
|
517
|
+
button.addEventListener('click', function () {
|
|
518
|
+
const modal = this.closest('.mw-modal-overlay');
|
|
519
|
+
if (modal) {
|
|
520
|
+
modal.classList.remove('mw-modal-open');
|
|
521
|
+
}
|
|
522
|
+
});
|
|
523
|
+
});
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
// ===== Login Button =====
|
|
527
|
+
function initHeaderLoginButton() {
|
|
528
|
+
const loginButton = document.getElementById('login-button');
|
|
529
|
+
|
|
530
|
+
if (loginButton) {
|
|
531
|
+
loginButton.addEventListener('click', function () {
|
|
532
|
+
const icon = this.querySelector('i');
|
|
533
|
+
|
|
534
|
+
// Toggle between fa-user-lock and fa-user-tag
|
|
535
|
+
if (icon.classList.contains('fa-lock')) {
|
|
536
|
+
icon.classList.remove('fa-lock');
|
|
537
|
+
icon.classList.add('fa-lock-open');
|
|
538
|
+
} else {
|
|
539
|
+
icon.classList.remove('fa-lock-open');
|
|
540
|
+
icon.classList.add('fa-lock');
|
|
541
|
+
}
|
|
542
|
+
});
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
// ===== Image Sliders =====
|
|
547
|
+
function initImageSliders() {
|
|
548
|
+
const sliders = document.querySelectorAll('.mw-image-slider');
|
|
549
|
+
|
|
550
|
+
sliders.forEach((slider) => {
|
|
551
|
+
const overlayImages = slider.querySelectorAll(
|
|
552
|
+
'.mw-image-slider-overlay-image'
|
|
553
|
+
);
|
|
554
|
+
const buttons = slider.querySelectorAll('button[data-index]');
|
|
555
|
+
|
|
556
|
+
if (!overlayImages.length || !buttons.length) return;
|
|
557
|
+
|
|
558
|
+
let current = 0;
|
|
559
|
+
|
|
560
|
+
function updateSliderView() {
|
|
561
|
+
overlayImages.forEach((img) => {
|
|
562
|
+
const imgIndex = parseInt(img.dataset.index);
|
|
563
|
+
img.classList.toggle('active', imgIndex === current);
|
|
564
|
+
});
|
|
565
|
+
|
|
566
|
+
buttons.forEach((btn) => {
|
|
567
|
+
const btnIndex = parseInt(btn.dataset.index);
|
|
568
|
+
btn.classList.toggle('active', btnIndex === current);
|
|
569
|
+
});
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
function goToSlide(index) {
|
|
573
|
+
current = index;
|
|
574
|
+
updateSliderView();
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
buttons.forEach((button) => {
|
|
578
|
+
button.addEventListener('click', () => {
|
|
579
|
+
const index = parseInt(button.dataset.index);
|
|
580
|
+
goToSlide(index);
|
|
581
|
+
});
|
|
582
|
+
});
|
|
583
|
+
|
|
584
|
+
updateSliderView();
|
|
585
|
+
});
|
|
586
|
+
}
|
|
587
|
+
})();
|