PluginProbe
Blockenberg — 600+ Advanced Gutenberg Blocks & AI Agent for WordPress Block Editor / 2.0.7
Blockenberg — 600+ Advanced Gutenberg Blocks & AI Agent for WordPress Block Editor v2.0.7
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 / spin-wheel / frontend.js

frontend.js in Blockenberg — 600+ Advanced Gutenberg Blocks & AI Agent for WordPress Block Editor 2.0.7, at blocks/spin-wheel/frontend.js

299 lines 13.4 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 var _typoKeys = [
5 ['family','font-family'],['weight','font-weight'],['style','font-style'],
6 ['decoration','text-decoration'],['transform','text-transform'],
7 ['sizeDesktop','font-size-d'],['sizeTablet','font-size-t'],['sizeMobile','font-size-m'],
8 ['lineHeightDesktop','line-height-d'],['lineHeightTablet','line-height-t'],['lineHeightMobile','line-height-m'],
9 ['letterSpacingDesktop','letter-spacing-d'],['letterSpacingTablet','letter-spacing-t'],['letterSpacingMobile','letter-spacing-m'],
10 ['wordSpacingDesktop','word-spacing-d'],['wordSpacingTablet','word-spacing-t'],['wordSpacingMobile','word-spacing-m']
11 ];
12 function typoCssVarsForEl(el, obj, prefix) {
13 if (!obj || typeof obj !== 'object') return;
14 _typoKeys.forEach(function (pair) {
15 var val = obj[pair[0]];
16 if (val !== undefined && val !== '') el.style.setProperty(prefix + pair[1], String(val));
17 });
18 if (obj.sizeUnit && obj.sizeUnit !== 'px') {
19 ['sizeDesktop','sizeTablet','sizeMobile'].forEach(function (k, i) {
20 var v = obj[k]; if (v !== undefined && v !== '') el.style.setProperty(prefix + ['font-size-d','font-size-t','font-size-m'][i], v + obj.sizeUnit);
21 });
22 }
23 }
24
25 function parseItems(text) {
26 return String(text || '').split('\n').map(function(s){ return s.trim(); }).filter(Boolean);
27 }
28
29 // Easing: ease-out cubic
30 function easeOut(t) {
31 return 1 - Math.pow(1 - t, 3);
32 }
33
34 function drawWheelFrame(ctx, items, colors, textColor, centerColor, size, rotation) {
35 var cx = size / 2, cy = size / 2, rad = size / 2 - 4;
36 ctx.clearRect(0, 0, size, size);
37 if (!items.length) return;
38 var slice = (2 * Math.PI) / items.length;
39 items.forEach(function(item, i) {
40 var start = rotation + i * slice;
41 var end = start + slice;
42 var color = colors[i % colors.length];
43 // Segment
44 ctx.beginPath();
45 ctx.moveTo(cx, cy);
46 ctx.arc(cx, cy, rad, start, end);
47 ctx.closePath();
48 ctx.fillStyle = color;
49 ctx.fill();
50 ctx.strokeStyle = 'rgba(255,255,255,0.3)';
51 ctx.lineWidth = 2;
52 ctx.stroke();
53 // Label
54 ctx.save();
55 ctx.translate(cx, cy);
56 ctx.rotate(start + slice / 2);
57 ctx.textAlign = 'right';
58 ctx.fillStyle = textColor || '#ffffff';
59 ctx.font = 'bold ' + Math.min(15, Math.max(9, Math.floor(rad * 0.13))) + 'px -apple-system,sans-serif';
60 var label = item.length > 14 ? item.slice(0,13) + '' : item;
61 ctx.fillText(label, rad - 12, 5);
62 ctx.restore();
63 });
64 // Center circle
65 ctx.beginPath();
66 ctx.arc(cx, cy, rad * 0.12, 0, 2 * Math.PI);
67 ctx.fillStyle = centerColor || '#ffffff';
68 ctx.fill();
69 ctx.strokeStyle = 'rgba(0,0,0,0.1)';
70 ctx.lineWidth = 2;
71 ctx.stroke();
72 }
73
74 function initSpinWheel(app) {
75 var opts = {};
76 try { opts = JSON.parse(app.getAttribute('data-opts') || '{}'); } catch(e) {}
77
78 var cardBg = opts.cardBg || '#ffffff';
79 var winnerBg = opts.winnerBg || '#6c3fb5';
80 var winnerColor = opts.winnerColor || '#ffffff';
81 var historyBg = opts.historyBg || '#f9fafb';
82 var historyColor= opts.historyColor|| '#6b7280';
83 var btnBg = opts.btnBg || '#6c3fb5';
84 var btnColor = opts.btnColor || '#ffffff';
85 var labelColor = opts.labelColor || '#374151';
86 var titleColor = opts.titleColor || '#1f2937';
87 var subtitleColor = opts.subtitleColor || '#6b7280';
88 var sectionBg = opts.sectionBg || '';
89 var pointerColor= opts.pointerColor|| '#1f2937';
90 var textColor = opts.textColor || '#ffffff';
91 var centerColor = opts.centerColor || '#ffffff';
92 var maxWidth = opts.maxWidth || 520;
93 var cardRadius = opts.cardRadius || 20;
94 var btnRadius = opts.btnRadius || 100;
95 var titleSize = opts.titleSize || 28;
96 var winnerSize = opts.winnerSize || 28;
97 var wheelSize = Math.min(opts.wheelSize || 340, 340);
98 var padTop = opts.paddingTop || 60;
99 var padBot = opts.paddingBottom || 60;
100 var showTitle = opts.showTitle !== false;
101 var showSub = opts.showSubtitle !== false;
102 var showWinner = opts.showWinnerBox !== false;
103 var showHistory = opts.showHistory !== false;
104 var spinDuration= (opts.spinDuration || 4) * 1000;
105 var buttonLabel = opts.buttonLabel || 'Spin!';
106 var colorList = (opts.colors || '#6c3fb5,#10b981,#f59e0b,#3b82f6,#ef4444,#8b5cf6,#06b6d4,#f97316').split(',').map(function(c){ return c.trim(); });
107
108 var items = parseItems(opts.defaultItems || 'Alice\nBob\nCarol\nDave\nEve\nFrank\nGrace\nHenry');
109 var history = [];
110 var spinning = false;
111 var currentRotation = 0;
112
113 // Layout
114 app.style.fontFamily = '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif';
115 if (sectionBg) app.style.background = sectionBg;
116 app.style.paddingTop = padTop + 'px';
117 app.style.paddingBottom = padBot + 'px';
118
119 typoCssVarsForEl(app, opts.titleTypo, '--bksw-tt-');
120 typoCssVarsForEl(app, opts.subtitleTypo, '--bksw-st-');
121 typoCssVarsForEl(app, opts.winnerTypo, '--bksw-wn-');
122
123 var card = document.createElement('div');
124 card.className = 'bkbg-sw-card';
125 card.style.cssText = 'background:' + cardBg + ';border-radius:' + cardRadius + 'px;padding:36px 28px;max-width:' + maxWidth + 'px;margin:0 auto;';
126 app.appendChild(card);
127
128 // Title
129 if (showTitle && opts.title) {
130 var titleEl = document.createElement('div');
131 titleEl.className = 'bkbg-sw-title';
132 titleEl.style.color = titleColor;
133 titleEl.style.marginBottom = '6px';
134 titleEl.textContent = opts.title;
135 card.appendChild(titleEl);
136 }
137 if (showSub && opts.subtitle) {
138 var subEl = document.createElement('div');
139 subEl.className = 'bkbg-sw-subtitle';
140 subEl.style.color = subtitleColor;
141 subEl.style.marginBottom = '20px';
142 subEl.textContent = opts.subtitle;
143 card.appendChild(subEl);
144 }
145
146 // Textarea
147 var taWrap = document.createElement('div');
148 taWrap.style.cssText = 'margin-bottom:18px;text-align:left;';
149 var taLbl = document.createElement('label');
150 taLbl.className = 'bkbg-sw-items-label';
151 taLbl.style.color = labelColor;
152 taLbl.textContent = 'Items (' + items.length + ')';
153 var textarea = document.createElement('textarea');
154 textarea.className = 'bkbg-sw-textarea';
155 textarea.rows = 5;
156 textarea.value = opts.defaultItems || '';
157 textarea.style.borderRadius = '8px';
158 textarea.addEventListener('input', function() {
159 items = parseItems(textarea.value);
160 taLbl.textContent = 'Items (' + items.length + ')';
161 currentRotation = 0;
162 drawWheelFrame(ctx, items, colorList, textColor, centerColor, wheelSize, 0);
163 });
164 taWrap.appendChild(taLbl);
165 taWrap.appendChild(textarea);
166 card.appendChild(taWrap);
167
168 // Wheel wrap
169 var wheelWrap = document.createElement('div');
170 wheelWrap.className = 'bkbg-sw-wheel-wrap';
171 card.appendChild(wheelWrap);
172
173 // Pointer
174 var pointer = document.createElement('div');
175 pointer.className = 'bkbg-sw-pointer';
176 pointer.style.cssText = 'position:absolute;top:-10px;left:50%;transform:translateX(-50%);width:0;height:0;border-left:12px solid transparent;border-right:12px solid transparent;border-top:22px solid ' + pointerColor + ';z-index:10;';
177 wheelWrap.appendChild(pointer);
178
179 // Canvas
180 var canvas = document.createElement('canvas');
181 canvas.className = 'bkbg-sw-canvas';
182 canvas.width = wheelSize;
183 canvas.height = wheelSize;
184 wheelWrap.appendChild(canvas);
185 var ctx = canvas.getContext('2d');
186 drawWheelFrame(ctx, items, colorList, textColor, centerColor, wheelSize, 0);
187
188 // Spin button
189 var spinBtn = document.createElement('button');
190 spinBtn.className = 'bkbg-sw-spin-btn';
191 spinBtn.style.cssText = 'background:' + btnBg + ';color:' + btnColor + ';border-radius:' + btnRadius + 'px;';
192 spinBtn.textContent = buttonLabel;
193 card.appendChild(spinBtn);
194
195 // Winner box
196 var winnerBox;
197 if (showWinner) {
198 winnerBox = document.createElement('div');
199 winnerBox.style.display = 'none';
200 winnerBox.style.marginBottom = '14px';
201 var wInner = document.createElement('div');
202 wInner.className = 'bkbg-sw-winner-box';
203 wInner.style.cssText = 'background:' + winnerBg + ';color:' + winnerColor + ';border-radius:12px;padding:16px 28px;display:inline-block;';
204 var wSub = document.createElement('div');
205 wSub.className = 'bkbg-sw-winner-sub';
206 wSub.textContent = '🎉 Winner!';
207 var wText = document.createElement('div');
208 wText.className = 'bkbg-sw-winner-text';
209 wInner.appendChild(wSub);
210 wInner.appendChild(wText);
211 winnerBox.appendChild(wInner);
212 card.appendChild(winnerBox);
213 }
214
215 // History
216 var histSection, histChips;
217 if (showHistory) {
218 histSection = document.createElement('div');
219 histSection.className = 'bkbg-sw-history';
220 histSection.style.cssText = 'display:none;background:' + historyBg + ';';
221 var histLbl = document.createElement('div');
222 histLbl.className = 'bkbg-sw-history-label';
223 histLbl.style.color = labelColor;
224 histLbl.textContent = 'Spin History';
225 histChips = document.createElement('div');
226 histChips.className = 'bkbg-sw-history-chips';
227 histSection.appendChild(histLbl);
228 histSection.appendChild(histChips);
229 card.appendChild(histSection);
230 }
231
232 function spin() {
233 if (spinning || items.length === 0) return;
234 spinning = true;
235 spinBtn.disabled = true;
236 if (winnerBox) winnerBox.style.display = 'none';
237
238 // Choose winner index, calculate target rotation
239 var winnerIdx = Math.floor(Math.random() * items.length);
240 var slice = (2 * Math.PI) / items.length;
241 // We want winnerIdx to land at top (which is -Math.PI/2 after rotation)
242 // Pointer at top = rotation offset where winnerIdx center aligns at top
243 // Target angle for item i center = i * slice + slice/2
244 // We want that angle + rotation = -PI/2 → rotation = -PI/2 - (i*slice + slice/2)
245 var targetNormalized = -Math.PI / 2 - (winnerIdx * slice + slice / 2);
246 // Add extra full spins for drama
247 var extraSpins = 5 + Math.floor(Math.random() * 3);
248 var endRotation = targetNormalized + extraSpins * 2 * Math.PI;
249 // Normalize to start
250 var startRotation = currentRotation;
251 var startTime = null;
252
253 function frame(timestamp) {
254 if (!startTime) startTime = timestamp;
255 var elapsed = timestamp - startTime;
256 var progress = Math.min(elapsed / spinDuration, 1);
257 var eased = easeOut(progress);
258 var rotation = startRotation + (endRotation - startRotation) * eased;
259 currentRotation = rotation;
260 drawWheelFrame(ctx, items, colorList, textColor, centerColor, wheelSize, rotation);
261 if (progress < 1) {
262 requestAnimationFrame(frame);
263 } else {
264 spinning = false;
265 spinBtn.disabled = false;
266 // Show winner
267 var winner = items[winnerIdx];
268 if (showWinner && winnerBox) {
269 winnerBox.style.display = 'block';
270 wText.textContent = winner;
271 // Re-trigger animation
272 wInner.style.animation = 'none';
273 void wInner.offsetWidth;
274 wInner.style.animation = 'bkbg-sw-pop .35s ease';
275 }
276 if (showHistory) {
277 history.unshift(winner);
278 if (history.length > 10) history.pop();
279 histChips.innerHTML = '';
280 history.forEach(function(h) {
281 var chip = document.createElement('span');
282 chip.className = 'bkbg-sw-history-chip';
283 chip.style.color = historyColor;
284 chip.textContent = h;
285 histChips.appendChild(chip);
286 });
287 histSection.style.display = 'block';
288 }
289 }
290 }
291 requestAnimationFrame(frame);
292 }
293
294 spinBtn.addEventListener('click', spin);
295 }
296
297 document.querySelectorAll('.bkbg-sw-app').forEach(initSpinWheel);
298 })();
299