PluginProbe
Blockenberg — 600+ Advanced Gutenberg Blocks & AI Agent for WordPress Block Editor / 2.0.6
Blockenberg — 600+ Advanced Gutenberg Blocks & AI Agent for WordPress Block Editor v2.0.6
2.0.13 2.0.12 2.0.11 2.0.10 2.0.9 trunk 2.0.2 2.0.3 2.0.4 2.0.5 2.0.6 2.0.7 2.0.8
blockenberg / blocks / stats-counter / frontend.js

frontend.js in Blockenberg — 600+ Advanced Gutenberg Blocks & AI Agent for WordPress Block Editor 2.0.6, at blocks/stats-counter/frontend.js

280 lines 10.6 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 (function () {
2 'use strict';
3
4 function clampInt(value, min, max) {
5 var n = parseInt(value, 10);
6 if (isNaN(n)) n = 0;
7 return Math.max(min, Math.min(max, n));
8 }
9
10 function sanitizeColor(input, fallback) {
11 var v = String(input || '').trim();
12 if (/^#([0-9a-f]{3}|[0-9a-f]{6}|[0-9a-f]{8})$/i.test(v)) {
13 return v;
14 }
15 return fallback;
16 }
17
18 function formatNumber(num, separator) {
19 if (!separator) return String(num);
20 return String(num).replace(/\B(?=(\d{3})+(?!\d))/g, separator);
21 }
22
23 function easeOutQuart(t) {
24 return 1 - Math.pow(1 - t, 4);
25 }
26
27 function animateCounter(el, targetNum, duration, separator, prefix, suffix) {
28 var startTime = null;
29 var startNum = 0;
30
31 function update(timestamp) {
32 if (!startTime) startTime = timestamp;
33 var elapsed = timestamp - startTime;
34 var progress = Math.min(elapsed / duration, 1);
35 var easedProgress = easeOutQuart(progress);
36 var currentNum = Math.floor(startNum + (targetNum - startNum) * easedProgress);
37
38 el.textContent = prefix + formatNumber(currentNum, separator) + suffix;
39
40 if (progress < 1) {
41 requestAnimationFrame(update);
42 } else {
43 el.textContent = prefix + formatNumber(targetNum, separator) + suffix;
44 }
45 }
46
47 requestAnimationFrame(update);
48 }
49
50 function clearEl(el) {
51 while (el && el.firstChild) {
52 el.removeChild(el.firstChild);
53 }
54 }
55
56 function initStatsCounter(wrap) {
57 var itemsData = wrap.getAttribute('data-items');
58 var animate = wrap.getAttribute('data-animate') === '1';
59 var duration = clampInt(wrap.getAttribute('data-duration') || 2000, 100, 10000);
60 var separator = wrap.getAttribute('data-separator') || ',';
61 var showIcon = wrap.getAttribute('data-show-icon') === '1';
62 var iconStyle = wrap.getAttribute('data-icon-style') || 'default';
63 var iconPosition = wrap.getAttribute('data-icon-position') || 'top';
64 var showLabel = wrap.getAttribute('data-show-label') === '1';
65 var labelPosition = wrap.getAttribute('data-label-position') || 'below';
66 var cardStyle = wrap.getAttribute('data-card-style') === '1';
67 var cardShadow = wrap.getAttribute('data-card-shadow') === '1';
68 var useItemColors = wrap.getAttribute('data-use-item-colors') === '1';
69
70 if (!itemsData) return;
71
72 // Parse items
73 var items = itemsData.split(';;').map(function (item) {
74 var parts = item.split('|');
75 return {
76 number: clampInt(parts[0], 0, 999999999),
77 prefix: parts[1] || '',
78 suffix: parts[2] || '',
79 label: parts[3] || '',
80 icon: parts[4] || '',
81 color: sanitizeColor(parts[5], '#3b82f6'),
82 numberColor: parts[6] || '',
83 labelColor: parts[7] || ''
84 };
85 });
86
87 var list = wrap.querySelector('.bkbg-sc-list');
88 if (!list) return;
89
90 clearEl(list);
91
92 var numberEls = [];
93
94 items.forEach(function (item, index) {
95 var itemEl = document.createElement('div');
96 itemEl.className = 'bkbg-sc-item';
97 if (cardStyle) itemEl.className += ' bkbg-sc-item--card';
98 if (cardShadow) itemEl.className += ' bkbg-sc-item--shadow';
99
100 var innerEl = document.createElement('div');
101 innerEl.className = 'bkbg-sc-item-inner';
102
103 if (iconPosition === 'left') {
104 innerEl.className += ' bkbg-sc-item-inner--left';
105 } else if (iconPosition === 'right') {
106 innerEl.className += ' bkbg-sc-item-inner--right';
107 } else {
108 innerEl.className += ' bkbg-sc-item-inner--top';
109 }
110
111 // Icon
112 if (showIcon && item.icon) {
113 var iconWrap = document.createElement('div');
114 iconWrap.className = 'bkbg-sc-icon';
115
116 // Check if custom image icon (JSON format or direct URL)
117 var isCustomIcon = false;
118 var customIconUrl = '';
119 var dashiconName = item.icon;
120
121 if (item.icon.indexOf('{') === 0) {
122 // JSON format: {"type":"custom","value":"url"}
123 try {
124 var iconData = JSON.parse(item.icon);
125 if (iconData.type === 'custom' && iconData.value) {
126 isCustomIcon = true;
127 customIconUrl = iconData.value;
128 } else if (iconData.type === 'dashicon' && iconData.value) {
129 dashiconName = iconData.value;
130 }
131 } catch (e) {
132 // Not valid JSON, use as dashicon name
133 }
134 }
135
136 var iconInner;
137 if (isCustomIcon) {
138 iconInner = document.createElement('img');
139 iconInner.src = customIconUrl;
140 iconInner.alt = '';
141 iconInner.className = 'bkbg-sc-icon-img';
142 } else {
143 iconInner = document.createElement('span');
144 iconInner.className = 'dashicons dashicons-' + dashiconName;
145 }
146
147 if (iconStyle === 'filled') {
148 iconWrap.className += ' bkbg-sc-icon--filled';
149 iconWrap.style.backgroundColor = useItemColors ? item.color : '';
150 if (!isCustomIcon) iconInner.style.color = '#fff';
151 } else if (iconStyle === 'outlined') {
152 iconWrap.className += ' bkbg-sc-icon--outlined';
153 iconWrap.style.borderColor = useItemColors ? item.color : '';
154 if (!isCustomIcon) iconInner.style.color = useItemColors ? item.color : '';
155 } else {
156 if (!isCustomIcon) iconInner.style.color = useItemColors ? item.color : '';
157 }
158
159 iconWrap.appendChild(iconInner);
160
161 if (iconPosition !== 'right') {
162 innerEl.appendChild(iconWrap);
163 }
164 }
165
166 // Content
167 var contentEl = document.createElement('div');
168 contentEl.className = 'bkbg-sc-content';
169
170 // Label above
171 if (showLabel && labelPosition === 'above' && item.label) {
172 var labelEl = document.createElement('span');
173 labelEl.className = 'bkbg-sc-label';
174 if (item.labelColor) labelEl.style.color = item.labelColor;
175 labelEl.textContent = item.label;
176 contentEl.appendChild(labelEl);
177 }
178
179 // Number
180 var numberEl = document.createElement('span');
181 numberEl.className = 'bkbg-sc-number';
182 if (item.numberColor) numberEl.style.color = item.numberColor;
183 if (animate) {
184 numberEl.textContent = item.prefix + '0' + item.suffix;
185 } else {
186 numberEl.textContent = item.prefix + formatNumber(item.number, separator) + item.suffix;
187 }
188 contentEl.appendChild(numberEl);
189
190 numberEls.push({
191 el: numberEl,
192 number: item.number,
193 prefix: item.prefix,
194 suffix: item.suffix
195 });
196
197 // Label below
198 if (showLabel && labelPosition === 'below' && item.label) {
199 var labelEl2 = document.createElement('span');
200 labelEl2.className = 'bkbg-sc-label';
201 if (item.labelColor) labelEl2.style.color = item.labelColor;
202 labelEl2.textContent = item.label;
203 contentEl.appendChild(labelEl2);
204 }
205
206 innerEl.appendChild(contentEl);
207
208 // Icon on right
209 if (showIcon && item.icon && iconPosition === 'right') {
210 var iconWrap2 = document.createElement('div');
211 iconWrap2.className = 'bkbg-sc-icon';
212
213 var iconInner2;
214 if (isCustomIcon) {
215 iconInner2 = document.createElement('img');
216 iconInner2.src = customIconUrl;
217 iconInner2.alt = '';
218 iconInner2.className = 'bkbg-sc-icon-img';
219 } else {
220 iconInner2 = document.createElement('span');
221 iconInner2.className = 'dashicons dashicons-' + dashiconName;
222 }
223
224 if (iconStyle === 'filled') {
225 iconWrap2.className += ' bkbg-sc-icon--filled';
226 iconWrap2.style.backgroundColor = useItemColors ? item.color : '';
227 if (!isCustomIcon) iconInner2.style.color = '#fff';
228 } else if (iconStyle === 'outlined') {
229 iconWrap2.className += ' bkbg-sc-icon--outlined';
230 iconWrap2.style.borderColor = useItemColors ? item.color : '';
231 if (!isCustomIcon) iconInner2.style.color = useItemColors ? item.color : '';
232 } else {
233 if (!isCustomIcon) iconInner2.style.color = useItemColors ? item.color : '';
234 }
235
236 iconWrap2.appendChild(iconInner2);
237 innerEl.appendChild(iconWrap2);
238 }
239
240 itemEl.appendChild(innerEl);
241 list.appendChild(itemEl);
242 });
243
244 // Animate if needed
245 if (animate) {
246 var observed = false;
247 var observer = new IntersectionObserver(function (entries) {
248 entries.forEach(function (entry) {
249 if (entry.isIntersecting && !observed) {
250 observed = true;
251 wrap.classList.add('bkbg-sc-animated');
252
253 numberEls.forEach(function (data) {
254 animateCounter(data.el, data.number, duration, separator, data.prefix, data.suffix);
255 });
256
257 observer.unobserve(wrap);
258 }
259 });
260 }, { threshold: 0.2 });
261
262 observer.observe(wrap);
263 } else {
264 wrap.classList.add('bkbg-sc-animated');
265 }
266 }
267
268 function init() {
269 var statsCounters = document.querySelectorAll('.bkbg-sc-wrap[data-items]');
270 statsCounters.forEach(initStatsCounter);
271 }
272
273 // Initialize
274 if (document.readyState === 'loading') {
275 document.addEventListener('DOMContentLoaded', init);
276 } else {
277 init();
278 }
279 })();
280