vanduo-framework 1.1.8

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.
Files changed (196) hide show
  1. package/LICENSE +35 -0
  2. package/README.md +205 -0
  3. package/css/components/alerts.css +224 -0
  4. package/css/components/avatar.css +275 -0
  5. package/css/components/badges.css +230 -0
  6. package/css/components/breadcrumbs.css +146 -0
  7. package/css/components/button-group.css +82 -0
  8. package/css/components/buttons.css +530 -0
  9. package/css/components/cards.css +304 -0
  10. package/css/components/chips.css +259 -0
  11. package/css/components/code-snippet.css +555 -0
  12. package/css/components/collapsible.css +267 -0
  13. package/css/components/collections.css +253 -0
  14. package/css/components/doc-search.css +464 -0
  15. package/css/components/doc-tabs.css +38 -0
  16. package/css/components/draggable.css +317 -0
  17. package/css/components/dropdown.css +266 -0
  18. package/css/components/footer.css +375 -0
  19. package/css/components/forms.css +1774 -0
  20. package/css/components/image-box.css +279 -0
  21. package/css/components/modals.css +285 -0
  22. package/css/components/navbar.css +530 -0
  23. package/css/components/pagination.css +186 -0
  24. package/css/components/preloader.css +340 -0
  25. package/css/components/progress.css +107 -0
  26. package/css/components/sidenav.css +301 -0
  27. package/css/components/skeleton.css +241 -0
  28. package/css/components/spinner.css +144 -0
  29. package/css/components/tabs.css +327 -0
  30. package/css/components/theme-customizer.css +835 -0
  31. package/css/components/toast.css +357 -0
  32. package/css/components/tooltips.css +270 -0
  33. package/css/core/colors.css +1017 -0
  34. package/css/core/fonts.css +266 -0
  35. package/css/core/grid.css +1699 -0
  36. package/css/core/helpers.css +2202 -0
  37. package/css/core/reset.css +128 -0
  38. package/css/core/tokens.css +213 -0
  39. package/css/core/typography.css +405 -0
  40. package/css/core/vd-aliases.css +47 -0
  41. package/css/effects/parallax.css +113 -0
  42. package/css/icons/icons-all.css +23 -0
  43. package/css/icons/icons.css +25 -0
  44. package/css/utilities/media.css +167 -0
  45. package/css/utilities/print.css +111 -0
  46. package/css/utilities/shadow.css +243 -0
  47. package/css/utilities/table.css +381 -0
  48. package/css/utilities/transforms.css +71 -0
  49. package/css/utilities/transitions.css +87 -0
  50. package/css/vanduo.css +80 -0
  51. package/dist/build-info.json +6 -0
  52. package/dist/fonts/fira-sans/fira-sans-bold.woff2 +0 -0
  53. package/dist/fonts/fira-sans/fira-sans-medium.woff2 +0 -0
  54. package/dist/fonts/fira-sans/fira-sans-regular.woff2 +0 -0
  55. package/dist/fonts/ibm-plex/ibm-plex-sans-bold.woff2 +0 -0
  56. package/dist/fonts/ibm-plex/ibm-plex-sans-medium.woff2 +0 -0
  57. package/dist/fonts/ibm-plex/ibm-plex-sans-regular.woff2 +0 -0
  58. package/dist/fonts/inter/inter-bold.woff2 +0 -0
  59. package/dist/fonts/inter/inter-medium.woff2 +0 -0
  60. package/dist/fonts/inter/inter-regular.woff2 +0 -0
  61. package/dist/fonts/inter/inter-semibold.woff2 +0 -0
  62. package/dist/fonts/jetbrains-mono/jetbrains-mono-bold.woff2 +0 -0
  63. package/dist/fonts/jetbrains-mono/jetbrains-mono-regular.woff2 +0 -0
  64. package/dist/fonts/open-sans/open-sans-bold.woff2 +0 -0
  65. package/dist/fonts/open-sans/open-sans-medium.woff2 +0 -0
  66. package/dist/fonts/open-sans/open-sans-regular.woff2 +0 -0
  67. package/dist/fonts/rubik/rubik-bold.woff2 +0 -0
  68. package/dist/fonts/rubik/rubik-medium.woff2 +0 -0
  69. package/dist/fonts/rubik/rubik-regular.woff2 +0 -0
  70. package/dist/fonts/source-sans/source-sans-bold.woff2 +0 -0
  71. package/dist/fonts/source-sans/source-sans-regular.woff2 +0 -0
  72. package/dist/fonts/source-sans/source-sans-semibold.woff2 +0 -0
  73. package/dist/fonts/titillium-web/titillium-web-bold.woff2 +0 -0
  74. package/dist/fonts/titillium-web/titillium-web-regular.woff2 +0 -0
  75. package/dist/fonts/titillium-web/titillium-web-semibold.woff2 +0 -0
  76. package/dist/fonts/ubuntu/ubuntu-bold.woff2 +0 -0
  77. package/dist/fonts/ubuntu/ubuntu-medium.woff2 +0 -0
  78. package/dist/fonts/ubuntu/ubuntu-regular.woff2 +0 -0
  79. package/dist/icons/phosphor/LICENSE +21 -0
  80. package/dist/icons/phosphor/bold/Phosphor-Bold.ttf +0 -0
  81. package/dist/icons/phosphor/bold/Phosphor-Bold.woff +0 -0
  82. package/dist/icons/phosphor/bold/Phosphor-Bold.woff2 +0 -0
  83. package/dist/icons/phosphor/bold/style.css +4627 -0
  84. package/dist/icons/phosphor/duotone/Phosphor-Duotone.ttf +0 -0
  85. package/dist/icons/phosphor/duotone/Phosphor-Duotone.woff +0 -0
  86. package/dist/icons/phosphor/duotone/Phosphor-Duotone.woff2 +0 -0
  87. package/dist/icons/phosphor/duotone/style.css +12115 -0
  88. package/dist/icons/phosphor/fill/Phosphor-Fill.ttf +0 -0
  89. package/dist/icons/phosphor/fill/Phosphor-Fill.woff +0 -0
  90. package/dist/icons/phosphor/fill/Phosphor-Fill.woff2 +0 -0
  91. package/dist/icons/phosphor/fill/style.css +4627 -0
  92. package/dist/icons/phosphor/light/Phosphor-Light.ttf +0 -0
  93. package/dist/icons/phosphor/light/Phosphor-Light.woff +0 -0
  94. package/dist/icons/phosphor/light/Phosphor-Light.woff2 +0 -0
  95. package/dist/icons/phosphor/light/style.css +4627 -0
  96. package/dist/icons/phosphor/regular/Phosphor.ttf +0 -0
  97. package/dist/icons/phosphor/regular/Phosphor.woff +0 -0
  98. package/dist/icons/phosphor/regular/Phosphor.woff2 +0 -0
  99. package/dist/icons/phosphor/regular/style.css +4627 -0
  100. package/dist/icons/phosphor/thin/Phosphor-Thin.ttf +0 -0
  101. package/dist/icons/phosphor/thin/Phosphor-Thin.woff +0 -0
  102. package/dist/icons/phosphor/thin/Phosphor-Thin.woff2 +0 -0
  103. package/dist/icons/phosphor/thin/style.css +4627 -0
  104. package/dist/vanduo.cjs.js +5569 -0
  105. package/dist/vanduo.cjs.js.map +7 -0
  106. package/dist/vanduo.cjs.min.js +48 -0
  107. package/dist/vanduo.cjs.min.js.map +7 -0
  108. package/dist/vanduo.css +60666 -0
  109. package/dist/vanduo.css.map +1 -0
  110. package/dist/vanduo.esm.js +5548 -0
  111. package/dist/vanduo.esm.js.map +7 -0
  112. package/dist/vanduo.esm.min.js +48 -0
  113. package/dist/vanduo.esm.min.js.map +7 -0
  114. package/dist/vanduo.js +5545 -0
  115. package/dist/vanduo.js.map +7 -0
  116. package/dist/vanduo.min.css +2 -0
  117. package/dist/vanduo.min.css.map +1 -0
  118. package/dist/vanduo.min.js +48 -0
  119. package/dist/vanduo.min.js.map +7 -0
  120. package/fonts/fira-sans/fira-sans-bold.woff2 +0 -0
  121. package/fonts/fira-sans/fira-sans-medium.woff2 +0 -0
  122. package/fonts/fira-sans/fira-sans-regular.woff2 +0 -0
  123. package/fonts/ibm-plex/ibm-plex-sans-bold.woff2 +0 -0
  124. package/fonts/ibm-plex/ibm-plex-sans-medium.woff2 +0 -0
  125. package/fonts/ibm-plex/ibm-plex-sans-regular.woff2 +0 -0
  126. package/fonts/inter/inter-bold.woff2 +0 -0
  127. package/fonts/inter/inter-medium.woff2 +0 -0
  128. package/fonts/inter/inter-regular.woff2 +0 -0
  129. package/fonts/inter/inter-semibold.woff2 +0 -0
  130. package/fonts/jetbrains-mono/jetbrains-mono-bold.woff2 +0 -0
  131. package/fonts/jetbrains-mono/jetbrains-mono-regular.woff2 +0 -0
  132. package/fonts/open-sans/open-sans-bold.woff2 +0 -0
  133. package/fonts/open-sans/open-sans-medium.woff2 +0 -0
  134. package/fonts/open-sans/open-sans-regular.woff2 +0 -0
  135. package/fonts/rubik/rubik-bold.woff2 +0 -0
  136. package/fonts/rubik/rubik-medium.woff2 +0 -0
  137. package/fonts/rubik/rubik-regular.woff2 +0 -0
  138. package/fonts/source-sans/source-sans-bold.woff2 +0 -0
  139. package/fonts/source-sans/source-sans-regular.woff2 +0 -0
  140. package/fonts/source-sans/source-sans-semibold.woff2 +0 -0
  141. package/fonts/titillium-web/titillium-web-bold.woff2 +0 -0
  142. package/fonts/titillium-web/titillium-web-regular.woff2 +0 -0
  143. package/fonts/titillium-web/titillium-web-semibold.woff2 +0 -0
  144. package/fonts/ubuntu/ubuntu-bold.woff2 +0 -0
  145. package/fonts/ubuntu/ubuntu-medium.woff2 +0 -0
  146. package/fonts/ubuntu/ubuntu-regular.woff2 +0 -0
  147. package/icons/phosphor/LICENSE +21 -0
  148. package/icons/phosphor/bold/Phosphor-Bold.ttf +0 -0
  149. package/icons/phosphor/bold/Phosphor-Bold.woff +0 -0
  150. package/icons/phosphor/bold/Phosphor-Bold.woff2 +0 -0
  151. package/icons/phosphor/bold/style.css +4627 -0
  152. package/icons/phosphor/duotone/Phosphor-Duotone.ttf +0 -0
  153. package/icons/phosphor/duotone/Phosphor-Duotone.woff +0 -0
  154. package/icons/phosphor/duotone/Phosphor-Duotone.woff2 +0 -0
  155. package/icons/phosphor/duotone/style.css +12115 -0
  156. package/icons/phosphor/fill/Phosphor-Fill.ttf +0 -0
  157. package/icons/phosphor/fill/Phosphor-Fill.woff +0 -0
  158. package/icons/phosphor/fill/Phosphor-Fill.woff2 +0 -0
  159. package/icons/phosphor/fill/style.css +4627 -0
  160. package/icons/phosphor/light/Phosphor-Light.ttf +0 -0
  161. package/icons/phosphor/light/Phosphor-Light.woff +0 -0
  162. package/icons/phosphor/light/Phosphor-Light.woff2 +0 -0
  163. package/icons/phosphor/light/style.css +4627 -0
  164. package/icons/phosphor/regular/Phosphor.ttf +0 -0
  165. package/icons/phosphor/regular/Phosphor.woff +0 -0
  166. package/icons/phosphor/regular/Phosphor.woff2 +0 -0
  167. package/icons/phosphor/regular/style.css +4627 -0
  168. package/icons/phosphor/thin/Phosphor-Thin.ttf +0 -0
  169. package/icons/phosphor/thin/Phosphor-Thin.woff +0 -0
  170. package/icons/phosphor/thin/Phosphor-Thin.woff2 +0 -0
  171. package/icons/phosphor/thin/style.css +4627 -0
  172. package/js/components/code-snippet.js +639 -0
  173. package/js/components/collapsible.js +226 -0
  174. package/js/components/doc-search.js +936 -0
  175. package/js/components/draggable.js +725 -0
  176. package/js/components/dropdown.js +362 -0
  177. package/js/components/font-switcher.js +253 -0
  178. package/js/components/grid.js +279 -0
  179. package/js/components/image-box.js +372 -0
  180. package/js/components/modals.js +367 -0
  181. package/js/components/navbar.js +264 -0
  182. package/js/components/pagination.js +286 -0
  183. package/js/components/parallax.js +216 -0
  184. package/js/components/preloader.js +183 -0
  185. package/js/components/select.js +444 -0
  186. package/js/components/sidenav.js +303 -0
  187. package/js/components/tabs.js +303 -0
  188. package/js/components/theme-customizer.js +784 -0
  189. package/js/components/theme-switcher.js +183 -0
  190. package/js/components/toast.js +343 -0
  191. package/js/components/tooltips.js +306 -0
  192. package/js/index.js +52 -0
  193. package/js/utils/helpers.js +306 -0
  194. package/js/utils/lifecycle.js +135 -0
  195. package/js/vanduo.js +120 -0
  196. package/package.json +78 -0
@@ -0,0 +1,286 @@
1
+ /**
2
+ * Vanduo Framework - Pagination Component
3
+ * JavaScript functionality for dynamic pagination
4
+ */
5
+
6
+ (function() {
7
+ 'use strict';
8
+
9
+ /**
10
+ * Pagination Component
11
+ */
12
+ const Pagination = {
13
+ // Store initialized paginations and their cleanup functions
14
+ instances: new Map(),
15
+
16
+ /**
17
+ * Initialize pagination components
18
+ */
19
+ init: function() {
20
+ const paginations = document.querySelectorAll('.vd-pagination[data-pagination]');
21
+
22
+ paginations.forEach(pagination => {
23
+ if (this.instances.has(pagination)) {
24
+ return;
25
+ }
26
+ this.initPagination(pagination);
27
+ });
28
+ },
29
+
30
+ /**
31
+ * Initialize a pagination
32
+ * @param {HTMLElement} pagination - Pagination container
33
+ */
34
+ initPagination: function(pagination) {
35
+ const totalPages = parseInt(pagination.dataset.totalPages) || 1;
36
+ const currentPage = parseInt(pagination.dataset.currentPage) || 1;
37
+ const maxVisible = parseInt(pagination.dataset.maxVisible) || 7;
38
+
39
+ this.render(pagination, {
40
+ totalPages: totalPages,
41
+ currentPage: currentPage,
42
+ maxVisible: maxVisible
43
+ });
44
+
45
+ // Handle clicks (event delegation)
46
+ const clickHandler = (e) => {
47
+ const link = e.target.closest('.vd-pagination-link');
48
+ if (!link || link.closest('.vd-pagination-item.disabled') || link.closest('.vd-pagination-item.active')) {
49
+ return;
50
+ }
51
+
52
+ e.preventDefault();
53
+
54
+ const item = link.closest('.vd-pagination-item');
55
+ const page = item.dataset.page;
56
+
57
+ if (page) {
58
+ this.goToPage(pagination, parseInt(page));
59
+ } else if (item.classList.contains('pagination-prev')) {
60
+ this.prevPage(pagination);
61
+ } else if (item.classList.contains('pagination-next')) {
62
+ this.nextPage(pagination);
63
+ }
64
+ };
65
+ pagination.addEventListener('click', clickHandler);
66
+
67
+ this.instances.set(pagination, {
68
+ cleanup: [() => pagination.removeEventListener('click', clickHandler)]
69
+ });
70
+ },
71
+
72
+ /**
73
+ * Render pagination
74
+ * @param {HTMLElement} pagination - Pagination container
75
+ * @param {Object} options - Pagination options
76
+ */
77
+ render: function(pagination, options) {
78
+ const { totalPages, currentPage, maxVisible } = options;
79
+
80
+ if (totalPages <= 1) {
81
+ pagination.innerHTML = '';
82
+ return;
83
+ }
84
+
85
+ let html = '';
86
+
87
+ // Previous button
88
+ html += `<li class="vd-pagination-item vd-pagination-prev pagination-item pagination-prev ${currentPage === 1 ? 'disabled' : ''}">`;
89
+ html += `<a class="vd-pagination-link pagination-link" href="#" aria-label="Previous">Previous</a>`;
90
+ html += `</li>`;
91
+
92
+ // Calculate page range
93
+ const pages = this.calculatePages(currentPage, totalPages, maxVisible);
94
+
95
+ // Page numbers
96
+ let lastPage = 0;
97
+ pages.forEach(page => {
98
+ if (page === 'ellipsis') {
99
+ html += `<li class="vd-pagination-item pagination-item"><span class="vd-pagination-ellipsis pagination-ellipsis">…</span></li>`;
100
+ } else {
101
+ if (page !== lastPage + 1 && lastPage > 0) {
102
+ html += `<li class="vd-pagination-item pagination-item"><span class="vd-pagination-ellipsis pagination-ellipsis">…</span></li>`;
103
+ }
104
+ const safePage = Number(page);
105
+ html += `<li class="vd-pagination-item pagination-item ${safePage === currentPage ? 'active' : ''}" data-page="${safePage}">`;
106
+ html += `<a class="vd-pagination-link pagination-link" href="#" aria-label="Page ${safePage}">${safePage}</a>`;
107
+ html += `</li>`;
108
+ lastPage = page;
109
+ }
110
+ });
111
+
112
+ // Next button
113
+ html += `<li class="vd-pagination-item vd-pagination-next pagination-item pagination-next ${currentPage === totalPages ? 'disabled' : ''}">`;
114
+ html += `<a class="vd-pagination-link pagination-link" href="#" aria-label="Next">Next</a>`;
115
+ html += `</li>`;
116
+
117
+ pagination.innerHTML = html;
118
+
119
+ // Update data attributes
120
+ pagination.dataset.currentPage = currentPage;
121
+ },
122
+
123
+ /**
124
+ * Calculate which pages to show
125
+ * @param {number} currentPage - Current page
126
+ * @param {number} totalPages - Total pages
127
+ * @param {number} maxVisible - Maximum visible pages
128
+ * @returns {Array} Array of page numbers or 'ellipsis'
129
+ */
130
+ calculatePages: function(currentPage, totalPages, maxVisible) {
131
+ const pages = [];
132
+ const half = Math.floor(maxVisible / 2);
133
+
134
+ if (totalPages <= maxVisible) {
135
+ // Show all pages
136
+ for (let i = 1; i <= totalPages; i++) {
137
+ pages.push(i);
138
+ }
139
+ } else {
140
+ // Always show first page
141
+ pages.push(1);
142
+
143
+ let start = Math.max(2, currentPage - half);
144
+ let end = Math.min(totalPages - 1, currentPage + half);
145
+
146
+ // Adjust if we're near the start
147
+ if (currentPage <= half + 1) {
148
+ end = Math.min(totalPages - 1, maxVisible - 1);
149
+ }
150
+
151
+ // Adjust if we're near the end
152
+ if (currentPage >= totalPages - half) {
153
+ start = Math.max(2, totalPages - maxVisible + 2);
154
+ }
155
+
156
+ // Add ellipsis before if needed
157
+ if (start > 2) {
158
+ pages.push('ellipsis');
159
+ }
160
+
161
+ // Add middle pages
162
+ for (let i = start; i <= end; i++) {
163
+ pages.push(i);
164
+ }
165
+
166
+ // Add ellipsis after if needed
167
+ if (end < totalPages - 1) {
168
+ pages.push('ellipsis');
169
+ }
170
+
171
+ // Always show last page
172
+ if (totalPages > 1) {
173
+ pages.push(totalPages);
174
+ }
175
+ }
176
+
177
+ return pages;
178
+ },
179
+
180
+ /**
181
+ * Go to specific page
182
+ * @param {HTMLElement} pagination - Pagination container
183
+ * @param {number} page - Page number
184
+ */
185
+ goToPage: function(pagination, page) {
186
+ const totalPages = parseInt(pagination.dataset.totalPages) || 1;
187
+ const maxVisible = parseInt(pagination.dataset.maxVisible) || 7;
188
+
189
+ if (page < 1 || page > totalPages) {
190
+ return;
191
+ }
192
+
193
+ this.render(pagination, {
194
+ totalPages: totalPages,
195
+ currentPage: page,
196
+ maxVisible: maxVisible
197
+ });
198
+
199
+ // Dispatch event
200
+ pagination.dispatchEvent(new CustomEvent('pagination:change', {
201
+ bubbles: true,
202
+ detail: { page, totalPages }
203
+ }));
204
+ },
205
+
206
+ /**
207
+ * Go to previous page
208
+ * @param {HTMLElement} pagination - Pagination container
209
+ */
210
+ prevPage: function(pagination) {
211
+ const currentPage = parseInt(pagination.dataset.currentPage) || 1;
212
+ if (currentPage > 1) {
213
+ this.goToPage(pagination, currentPage - 1);
214
+ }
215
+ },
216
+
217
+ /**
218
+ * Go to next page
219
+ * @param {HTMLElement} pagination - Pagination container
220
+ */
221
+ nextPage: function(pagination) {
222
+ const currentPage = parseInt(pagination.dataset.currentPage) || 1;
223
+ const totalPages = parseInt(pagination.dataset.totalPages) || 1;
224
+ if (currentPage < totalPages) {
225
+ this.goToPage(pagination, currentPage + 1);
226
+ }
227
+ },
228
+
229
+ /**
230
+ * Update pagination
231
+ * @param {HTMLElement|string} pagination - Pagination container or selector
232
+ * @param {Object} options - Pagination options
233
+ */
234
+ update: function(pagination, options) {
235
+ const el = typeof pagination === 'string' ? document.querySelector(pagination) : pagination;
236
+ if (el) {
237
+ if (options.totalPages !== undefined) {
238
+ el.dataset.totalPages = options.totalPages;
239
+ }
240
+ if (options.currentPage !== undefined) {
241
+ el.dataset.currentPage = options.currentPage;
242
+ }
243
+ if (options.maxVisible !== undefined) {
244
+ el.dataset.maxVisible = options.maxVisible;
245
+ }
246
+
247
+ this.render(el, {
248
+ totalPages: parseInt(el.dataset.totalPages) || 1,
249
+ currentPage: parseInt(el.dataset.currentPage) || 1,
250
+ maxVisible: parseInt(el.dataset.maxVisible) || 7
251
+ });
252
+ }
253
+ },
254
+
255
+ /**
256
+ * Destroy a pagination instance and clean up event listeners
257
+ * @param {HTMLElement} pagination - Pagination container
258
+ */
259
+ destroy: function(pagination) {
260
+ const instance = this.instances.get(pagination);
261
+ if (!instance) return;
262
+
263
+ instance.cleanup.forEach(fn => fn());
264
+ this.instances.delete(pagination);
265
+ },
266
+
267
+ /**
268
+ * Destroy all pagination instances
269
+ */
270
+ destroyAll: function() {
271
+ this.instances.forEach((instance, pagination) => {
272
+ this.destroy(pagination);
273
+ });
274
+ }
275
+ };
276
+
277
+ // Register with Vanduo framework if available
278
+ if (typeof window.Vanduo !== 'undefined') {
279
+ window.Vanduo.register('pagination', Pagination);
280
+ }
281
+
282
+ // Expose globally
283
+ window.VanduoPagination = Pagination;
284
+
285
+ })();
286
+
@@ -0,0 +1,216 @@
1
+ /**
2
+ * Vanduo Framework - Parallax Component
3
+ * JavaScript functionality for parallax scroll effects
4
+ */
5
+
6
+ (function () {
7
+ 'use strict';
8
+
9
+ /**
10
+ * Parallax Component
11
+ */
12
+ const Parallax = {
13
+ parallaxElements: new Map(),
14
+ ticking: false,
15
+ isMobile: window.innerWidth < 768,
16
+ reducedMotion: window.matchMedia('(prefers-reduced-motion: reduce)').matches,
17
+ isInitialized: false,
18
+ _onScroll: null,
19
+ _onResize: null,
20
+
21
+ /**
22
+ * Initialize parallax components
23
+ */
24
+ init: function () {
25
+ if (this.isInitialized) {
26
+ this.refresh();
27
+ return;
28
+ }
29
+
30
+ this.isInitialized = true;
31
+
32
+ // Check for reduced motion preference
33
+ if (this.reducedMotion) {
34
+ return; // Don't initialize if user prefers reduced motion
35
+ }
36
+
37
+ const parallaxElements = document.querySelectorAll('.vd-parallax');
38
+
39
+ parallaxElements.forEach(element => {
40
+ if (!element.dataset.parallaxInitialized) {
41
+ this.initParallax(element);
42
+ }
43
+ });
44
+
45
+ // Handle scroll
46
+ this.handleScroll();
47
+ this._onScroll = () => {
48
+ this.handleScroll();
49
+ };
50
+ window.addEventListener('scroll', this._onScroll, { passive: true });
51
+
52
+ // Handle resize
53
+ this._onResize = () => {
54
+ this.isMobile = window.innerWidth < 768;
55
+ this.updateAll();
56
+ };
57
+ window.addEventListener('resize', this._onResize);
58
+ },
59
+
60
+ /**
61
+ * Initialize a parallax element
62
+ * @param {HTMLElement} element - Parallax container
63
+ */
64
+ initParallax: function (element) {
65
+ element.dataset.parallaxInitialized = 'true';
66
+
67
+ // Check if disabled on mobile
68
+ const disableMobile = element.classList.contains('parallax-disable-mobile');
69
+ if (disableMobile && this.isMobile) {
70
+ return;
71
+ }
72
+
73
+ const layers = element.querySelectorAll('.vd-parallax-layer, .vd-parallax-bg');
74
+ const speed = this.getSpeed(element);
75
+ const direction = element.classList.contains('parallax-horizontal') ? 'horizontal' : 'vertical';
76
+
77
+ this.parallaxElements.set(element, {
78
+ layers: Array.from(layers),
79
+ speed: speed,
80
+ direction: direction,
81
+ disableMobile: disableMobile
82
+ });
83
+
84
+ // Initial update
85
+ this.updateParallax(element);
86
+ },
87
+
88
+ /**
89
+ * Get parallax speed from element
90
+ * @param {HTMLElement} element - Parallax element
91
+ * @returns {number} Speed multiplier
92
+ */
93
+ getSpeed: function (element) {
94
+ if (element.classList.contains('parallax-slow')) {
95
+ return 0.5;
96
+ } else if (element.classList.contains('parallax-fast')) {
97
+ return 1.5;
98
+ }
99
+ return 1; // Default medium speed
100
+ },
101
+
102
+ /**
103
+ * Handle scroll event
104
+ */
105
+ handleScroll: function () {
106
+ if (!this.ticking) {
107
+ window.requestAnimationFrame(() => {
108
+ this.updateAll();
109
+ this.ticking = false;
110
+ });
111
+ this.ticking = true;
112
+ }
113
+ },
114
+
115
+ /**
116
+ * Update all parallax elements
117
+ */
118
+ updateAll: function () {
119
+ this.parallaxElements.forEach((config, element) => {
120
+ // Skip if disabled on mobile
121
+ if (config.disableMobile && this.isMobile) {
122
+ return;
123
+ }
124
+
125
+ this.updateParallax(element);
126
+ });
127
+ },
128
+
129
+ /**
130
+ * Update parallax for a single element
131
+ * @param {HTMLElement} element - Parallax element
132
+ */
133
+ updateParallax: function (element) {
134
+ const config = this.parallaxElements.get(element);
135
+ if (!config) {
136
+ return;
137
+ }
138
+
139
+ const rect = element.getBoundingClientRect();
140
+ const windowHeight = window.innerHeight;
141
+ const elementTop = rect.top;
142
+ const elementHeight = rect.height;
143
+
144
+ // Calculate scroll progress (0 to 1)
145
+ const scrollProgress = Math.max(0, Math.min(1,
146
+ (windowHeight - elementTop) / (windowHeight + elementHeight)
147
+ ));
148
+
149
+ // Calculate offset based on speed and direction
150
+ const offset = (scrollProgress - 0.5) * config.speed * 100;
151
+
152
+ config.layers.forEach((layer, _index) => {
153
+ // Different layers can have different speeds
154
+ const layerSpeed = layer.dataset.parallaxSpeed ? parseFloat(layer.dataset.parallaxSpeed) : 1;
155
+ const layerOffset = offset * layerSpeed;
156
+
157
+ if (config.direction === 'horizontal') {
158
+ layer.style.transform = `translateX(${layerOffset}px)`;
159
+ } else {
160
+ layer.style.transform = `translateY(${layerOffset}px)`;
161
+ }
162
+ });
163
+ },
164
+
165
+ /**
166
+ * Destroy parallax element
167
+ * @param {HTMLElement|string} element - Parallax element or selector
168
+ */
169
+ destroy: function (element) {
170
+ const el = typeof element === 'string' ? document.querySelector(element) : element;
171
+ if (el && this.parallaxElements.has(el)) {
172
+ const config = this.parallaxElements.get(el);
173
+ config.layers.forEach(layer => {
174
+ layer.style.transform = '';
175
+ });
176
+ this.parallaxElements.delete(el);
177
+ }
178
+ },
179
+
180
+ /**
181
+ * Refresh parallax (recalculate positions)
182
+ */
183
+ refresh: function () {
184
+ this.updateAll();
185
+ },
186
+
187
+ destroyAll: function () {
188
+ this.parallaxElements.forEach((_config, element) => {
189
+ this.destroy(element);
190
+ });
191
+ this.parallaxElements.clear();
192
+
193
+ if (this._onScroll) {
194
+ window.removeEventListener('scroll', this._onScroll);
195
+ this._onScroll = null;
196
+ }
197
+
198
+ if (this._onResize) {
199
+ window.removeEventListener('resize', this._onResize);
200
+ this._onResize = null;
201
+ }
202
+
203
+ this.isInitialized = false;
204
+ }
205
+ };
206
+
207
+ // Register with Vanduo framework if available
208
+ if (typeof window.Vanduo !== 'undefined') {
209
+ window.Vanduo.register('parallax', Parallax);
210
+ }
211
+
212
+ // Expose globally
213
+ window.VanduoParallax = Parallax;
214
+
215
+ })();
216
+
@@ -0,0 +1,183 @@
1
+ /**
2
+ * Vanduo Framework - Preloader Component
3
+ * JavaScript functionality for progress bars and loaders
4
+ */
5
+
6
+ (function() {
7
+ 'use strict';
8
+
9
+ /**
10
+ * Preloader Component
11
+ */
12
+ const Preloader = {
13
+ /**
14
+ * Initialize preloader components
15
+ */
16
+ init: function() {
17
+ const progressBars = document.querySelectorAll('.progress-bar[data-progress]');
18
+
19
+ progressBars.forEach(bar => {
20
+ if (!bar.dataset.progressInitialized) {
21
+ this.initProgressBar(bar);
22
+ }
23
+ });
24
+ },
25
+
26
+ /**
27
+ * Initialize a progress bar
28
+ * @param {HTMLElement} bar - Progress bar element
29
+ */
30
+ initProgressBar: function(bar) {
31
+ bar.dataset.progressInitialized = 'true';
32
+
33
+ const initialValue = parseInt(bar.dataset.progress) || 0;
34
+ this.setProgress(bar, initialValue, false);
35
+ },
36
+
37
+ /**
38
+ * Set progress value
39
+ * @param {HTMLElement|string} bar - Progress bar element or selector
40
+ * @param {number} value - Progress value (0-100)
41
+ * @param {boolean} animate - Whether to animate
42
+ */
43
+ setProgress: function(bar, value, animate = true) {
44
+ const el = typeof bar === 'string' ? document.querySelector(bar) : bar;
45
+
46
+ if (!el) {
47
+ return;
48
+ }
49
+
50
+ // Clamp value between 0 and 100
51
+ value = Math.max(0, Math.min(100, value));
52
+
53
+ // Update width
54
+ if (animate) {
55
+ el.style.transition = 'width var(--transition-duration-slow) var(--transition-ease)';
56
+ } else {
57
+ el.style.transition = 'none';
58
+ setTimeout(() => {
59
+ el.style.transition = '';
60
+ }, 0);
61
+ }
62
+
63
+ el.style.width = value + '%';
64
+ el.setAttribute('aria-valuenow', value);
65
+ el.setAttribute('aria-valuemin', 0);
66
+ el.setAttribute('aria-valuemax', 100);
67
+
68
+ // Update text if exists
69
+ const text = el.querySelector('.progress-text');
70
+ if (text) {
71
+ text.textContent = value + '%';
72
+ }
73
+
74
+ // Dispatch event
75
+ el.dispatchEvent(new CustomEvent('progress:update', {
76
+ bubbles: true,
77
+ detail: { value, max: 100 }
78
+ }));
79
+
80
+ // Complete event
81
+ if (value >= 100) {
82
+ el.dispatchEvent(new CustomEvent('progress:complete', {
83
+ bubbles: true,
84
+ detail: { value, max: 100 }
85
+ }));
86
+ }
87
+ },
88
+
89
+ /**
90
+ * Animate progress from current to target
91
+ * @param {HTMLElement|string} bar - Progress bar element or selector
92
+ * @param {number} targetValue - Target progress value (0-100)
93
+ * @param {number} duration - Animation duration in ms
94
+ */
95
+ animateProgress: function(bar, targetValue, duration = 1000) {
96
+ const el = typeof bar === 'string' ? document.querySelector(bar) : bar;
97
+
98
+ if (!el) {
99
+ return;
100
+ }
101
+
102
+ const startValue = parseInt(el.style.width) || 0;
103
+ const difference = targetValue - startValue;
104
+ const startTime = performance.now();
105
+
106
+ const animate = (currentTime) => {
107
+ const elapsed = currentTime - startTime;
108
+ const progress = Math.min(elapsed / duration, 1);
109
+
110
+ // Easing function (ease-out)
111
+ const easeOut = 1 - Math.pow(1 - progress, 3);
112
+ const currentValue = startValue + (difference * easeOut);
113
+
114
+ this.setProgress(el, currentValue, false);
115
+
116
+ if (progress < 1) {
117
+ requestAnimationFrame(animate);
118
+ }
119
+ };
120
+
121
+ requestAnimationFrame(animate);
122
+ },
123
+
124
+ /**
125
+ * Show preloader
126
+ * @param {HTMLElement|string} preloader - Preloader element or selector
127
+ */
128
+ show: function(preloader) {
129
+ const el = typeof preloader === 'string' ? document.querySelector(preloader) : preloader;
130
+ if (el) {
131
+ el.style.display = 'inline-block';
132
+ el.setAttribute('aria-hidden', 'false');
133
+ }
134
+ },
135
+
136
+ /**
137
+ * Hide preloader
138
+ * @param {HTMLElement|string} preloader - Preloader element or selector
139
+ */
140
+ hide: function(preloader) {
141
+ const el = typeof preloader === 'string' ? document.querySelector(preloader) : preloader;
142
+ if (el) {
143
+ el.style.display = 'none';
144
+ el.setAttribute('aria-hidden', 'true');
145
+ }
146
+ },
147
+
148
+ /**
149
+ * Toggle preloader
150
+ * @param {HTMLElement|string} preloader - Preloader element or selector
151
+ */
152
+ toggle: function(preloader) {
153
+ const el = typeof preloader === 'string' ? document.querySelector(preloader) : preloader;
154
+ if (el) {
155
+ if (el.style.display === 'none' || el.getAttribute('aria-hidden') === 'true') {
156
+ this.show(el);
157
+ } else {
158
+ this.hide(el);
159
+ }
160
+ }
161
+ },
162
+
163
+ /**
164
+ * Destroy all progress bar instances
165
+ */
166
+ destroyAll: function() {
167
+ const progressBars = document.querySelectorAll('.progress-bar[data-progress-initialized="true"]');
168
+ progressBars.forEach(bar => {
169
+ delete bar.dataset.progressInitialized;
170
+ });
171
+ }
172
+ };
173
+
174
+ // Register with Vanduo framework if available
175
+ if (typeof window.Vanduo !== 'undefined') {
176
+ window.Vanduo.register('preloader', Preloader);
177
+ }
178
+
179
+ // Expose globally
180
+ window.VanduoPreloader = Preloader;
181
+
182
+ })();
183
+