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 / flashcard / frontend.js

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

340 lines 13.3 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 typoCssVarsForEl(typo, prefix, el) {
5 if (!typo || typeof typo !== 'object') return;
6 if (typo.family) el.style.setProperty(prefix + 'font-family', "'" + typo.family + "', sans-serif");
7 if (typo.weight) el.style.setProperty(prefix + 'font-weight', typo.weight);
8 if (typo.transform) el.style.setProperty(prefix + 'text-transform', typo.transform);
9 if (typo.style) el.style.setProperty(prefix + 'font-style', typo.style);
10 if (typo.decoration) el.style.setProperty(prefix + 'text-decoration', typo.decoration);
11 ['size', 'lineHeight', 'letterSpacing', 'wordSpacing'].forEach(function (prop) {
12 var unit = typo[prop + 'Unit'] || 'px';
13 var d = typo[prop + 'Desktop'], t = typo[prop + 'Tablet'], m = typo[prop + 'Mobile'];
14 var css = { size: 'font-size', lineHeight: 'line-height', letterSpacing: 'letter-spacing', wordSpacing: 'word-spacing' }[prop];
15 if (d !== undefined && d !== '') el.style.setProperty(prefix + css + '-d', d + unit);
16 if (t !== undefined && t !== '') el.style.setProperty(prefix + css + '-t', t + unit);
17 if (m !== undefined && m !== '') el.style.setProperty(prefix + css + '-m', m + unit);
18 });
19 }
20
21 function shuffle(arr) {
22 var a = arr.slice();
23 for (var i = a.length - 1; i > 0; i--) {
24 var j = Math.floor(Math.random() * (i + 1));
25 var t = a[i]; a[i] = a[j]; a[j] = t;
26 }
27 return a;
28 }
29
30 function luminance(hex) {
31 var c = (hex || '#000').replace('#', '');
32 return 0.299 * parseInt(c.slice(0, 2), 16) + 0.587 * parseInt(c.slice(2, 4), 16) + 0.114 * parseInt(c.slice(4, 6), 16);
33 }
34
35 function initBlock(root) {
36 var opts;
37 try { opts = JSON.parse(root.getAttribute('data-opts')); } catch (e) { return; }
38
39 var a = opts;
40 var rawCards;
41 try { rawCards = JSON.parse(a.cards); } catch (e) { rawCards = []; }
42
43 var deck = rawCards.map(function (c) { return { id: c.id, front: c.front, back: c.back, known: false }; });
44 var currentIndex = 0;
45 var isFlipped = false;
46
47 root.innerHTML = '';
48
49 var wrap = document.createElement('div');
50 wrap.className = 'bkbg-fc-wrap';
51 wrap.style.cssText = 'background:' + a.sectionBg + ';max-width:' + a.contentMaxWidth + 'px;margin:0 auto;';
52 wrap.setAttribute('tabindex', '-1');
53 root.appendChild(wrap);
54 typoCssVarsForEl(a.typoTitle, '--bkbg-flash-tt-', wrap);
55 typoCssVarsForEl(a.typoSubtitle, '--bkbg-flash-ts-', wrap);
56 typoCssVarsForEl(a.typoCard, '--bkbg-flash-tc-', wrap);
57
58 if (a.showTitle) {
59 var h = document.createElement('div');
60 h.className = 'bkbg-fc-title';
61 h.style.color = a.titleColor;
62 h.textContent = a.title;
63 wrap.appendChild(h);
64 }
65
66 if (a.showSubtitle) {
67 var s = document.createElement('div');
68 s.className = 'bkbg-fc-subtitle';
69 s.style.color = a.subtitleColor;
70 s.textContent = a.subtitle;
71 wrap.appendChild(s);
72 }
73
74 var progressBarWrap = null;
75 var progressBar = null;
76 var progressLabel = null;
77
78 if (a.showProgress) {
79 var progressRow = document.createElement('div');
80 progressRow.className = 'bkbg-fc-progress-row';
81 var pLabel = document.createElement('span');
82 pLabel.style.color = a.labelColor;
83 pLabel.textContent = 'Progress';
84 progressLabel = document.createElement('span');
85 progressLabel.style.color = a.labelColor;
86 progressRow.appendChild(pLabel);
87 progressRow.appendChild(progressLabel);
88 wrap.appendChild(progressRow);
89 progressBarWrap = document.createElement('div');
90 progressBarWrap.className = 'bkbg-fc-progress-bar-wrap';
91 progressBar = document.createElement('div');
92 progressBar.className = 'bkbg-fc-progress-bar';
93 progressBar.style.background = a.knownColor;
94 progressBarWrap.appendChild(progressBar);
95 wrap.appendChild(progressBarWrap);
96 }
97
98 // Card scene
99 var scene = document.createElement('div');
100 scene.className = 'bkbg-fc-scene';
101 scene.style.height = a.cardHeight + 'px';
102 wrap.appendChild(scene);
103
104 var card = document.createElement('div');
105 card.className = 'bkbg-fc-card';
106 card.style.height = a.cardHeight + 'px';
107 scene.appendChild(card);
108
109 var front = document.createElement('div');
110 front.className = 'bkbg-fc-face bkbg-fc-front';
111 front.style.cssText = 'background:' + a.cardFrontBg + ';color:' + a.labelColor + ';';
112 card.appendChild(front);
113
114 var back = document.createElement('div');
115 back.className = 'bkbg-fc-face bkbg-fc-back';
116 back.style.cssText = 'background:' + a.cardBackBg + ';color:' + a.cardBackColor + ';';
117 card.appendChild(back);
118
119 var flipHint = document.createElement('div');
120 flipHint.className = 'bkbg-fc-flip-hint';
121 flipHint.style.color = a.labelColor;
122 flipHint.textContent = 'Click to flip';
123 front.appendChild(flipHint);
124
125 card.addEventListener('click', function () {
126 isFlipped = !isFlipped;
127 card.classList.toggle('bkbg-fc-flipped', isFlipped);
128 });
129
130 // Navigation
131 var nav = document.createElement('div');
132 nav.className = 'bkbg-fc-nav';
133 wrap.appendChild(nav);
134
135 var prevBtn = document.createElement('button');
136 prevBtn.className = 'bkbg-fc-nav-btn';
137 prevBtn.textContent = '';
138 prevBtn.setAttribute('aria-label', 'Previous card');
139 nav.appendChild(prevBtn);
140
141 if (a.showKnownBtn) {
142 var knownBtn = document.createElement('button');
143 knownBtn.className = 'bkbg-fc-known-btn';
144 knownBtn.style.background = a.knownColor;
145 knownBtn.textContent = '✓ Know it';
146 knownBtn.addEventListener('click', function () {
147 deck[currentIndex].known = true;
148 render();
149 if (currentIndex < deck.length - 1) {
150 currentIndex++;
151 isFlipped = false;
152 card.classList.remove('bkbg-fc-flipped');
153 render();
154 } else {
155 showSummary();
156 }
157 });
158 nav.appendChild(knownBtn);
159 }
160
161 var nextBtn = document.createElement('button');
162 nextBtn.className = 'bkbg-fc-nav-btn';
163 nextBtn.textContent = '';
164 nextBtn.setAttribute('aria-label', 'Next card');
165 nav.appendChild(nextBtn);
166
167 var counterRow = document.createElement('div');
168 counterRow.className = 'bkbg-fc-counter-row';
169 counterRow.style.color = a.labelColor;
170 wrap.appendChild(counterRow);
171
172 var counter = document.createElement('span');
173 counter.className = 'bkbg-fc-counter';
174 counterRow.appendChild(counter);
175
176 if (a.showShuffleBtn) {
177 var shuffleBtn = document.createElement('button');
178 shuffleBtn.className = 'bkbg-fc-action-btn';
179 shuffleBtn.style.cssText = 'border:1px solid ' + a.accentColor + ';color:' + a.accentColor + ';';
180 shuffleBtn.textContent = '⇌ Shuffle';
181 shuffleBtn.addEventListener('click', function () {
182 deck = shuffle(deck);
183 currentIndex = 0;
184 isFlipped = false;
185 card.classList.remove('bkbg-fc-flipped');
186 render();
187 });
188 counterRow.appendChild(shuffleBtn);
189 }
190
191 var resetBtn = document.createElement('button');
192 resetBtn.className = 'bkbg-fc-action-btn';
193 resetBtn.style.cssText = 'border:1px solid #e5e7eb;color:' + a.subtitleColor + ';';
194 resetBtn.textContent = '↺ Reset';
195 resetBtn.addEventListener('click', function () {
196 deck.forEach(function (c) { c.known = false; });
197 currentIndex = 0;
198 isFlipped = false;
199 card.classList.remove('bkbg-fc-flipped');
200 scene.style.display = '';
201 nav.style.display = '';
202 counterRow.style.display = '';
203 if (skHint) skHint.style.display = '';
204 if (summaryEl) summaryEl.remove(), summaryEl = null;
205 render();
206 });
207 counterRow.appendChild(resetBtn);
208
209 var skHint = null;
210 if (a.showKeyboardHint) {
211 skHint = document.createElement('div');
212 skHint.className = 'bkbg-fc-keyboard-hint';
213 skHint.style.color = a.subtitleColor;
214 skHint.textContent = '← → Arrow keys to navigate • Space to flip';
215 wrap.appendChild(skHint);
216 }
217
218 prevBtn.addEventListener('click', function () {
219 if (currentIndex > 0) {
220 currentIndex--;
221 isFlipped = false;
222 card.classList.remove('bkbg-fc-flipped');
223 render();
224 }
225 });
226
227 nextBtn.addEventListener('click', function () {
228 if (currentIndex < deck.length - 1) {
229 currentIndex++;
230 isFlipped = false;
231 card.classList.remove('bkbg-fc-flipped');
232 render();
233 }
234 });
235
236 var summaryEl = null;
237
238 function showSummary() {
239 scene.style.display = 'none';
240 nav.style.display = 'none';
241 counterRow.style.display = 'none';
242 if (skHint) skHint.style.display = 'none';
243
244 summaryEl = document.createElement('div');
245 summaryEl.className = 'bkbg-fc-summary';
246 summaryEl.style.color = a.labelColor;
247
248 var knownCount = deck.filter(function (c) { return c.known; }).length;
249 var icon = knownCount === deck.length ? '🎉' : '📚';
250 summaryEl.innerHTML = '<div class="bkbg-fc-summary-icon">' + icon + '</div>' +
251 '<h3 style="color:' + a.titleColor + '">Deck Complete!</h3>' +
252 '<p>' + knownCount + ' of ' + deck.length + ' cards marked as known</p>';
253
254 var rsBtn = document.createElement('button');
255 rsBtn.className = 'bkbg-fc-restart-btn';
256 rsBtn.style.background = a.accentColor;
257 rsBtn.textContent = '↺ Study Again';
258 rsBtn.addEventListener('click', function () {
259 deck.forEach(function (c) { c.known = false; });
260 currentIndex = 0;
261 isFlipped = false;
262 card.classList.remove('bkbg-fc-flipped');
263 scene.style.display = '';
264 nav.style.display = '';
265 counterRow.style.display = '';
266 if (skHint) skHint.style.display = '';
267 summaryEl.remove();
268 summaryEl = null;
269 render();
270 });
271 summaryEl.appendChild(rsBtn);
272 wrap.appendChild(summaryEl);
273 }
274
275 function render() {
276 var c = deck[currentIndex];
277 if (!c) return;
278
279 // known badge
280 front.querySelectorAll('.bkbg-fc-known-badge').forEach(function (el) { el.remove(); });
281 if (c.known) {
282 var badge = document.createElement('div');
283 badge.className = 'bkbg-fc-known-badge';
284 badge.style.cssText = 'background:' + a.knownColor + ';color:#fff;';
285 badge.textContent = '✓ Known';
286 front.prepend(badge);
287 }
288
289 // front text
290 var frontText = front.querySelector('.bkbg-fc-text');
291 if (!frontText) {
292 frontText = document.createElement('div');
293 frontText.className = 'bkbg-fc-text';
294 front.insertBefore(frontText, flipHint);
295 }
296 frontText.textContent = c.front;
297
298 // back text
299 back.textContent = c.back;
300
301 // counter
302 counter.textContent = (currentIndex + 1) + ' / ' + deck.length;
303
304 // nav buttons
305 prevBtn.disabled = currentIndex === 0;
306 nextBtn.disabled = currentIndex === deck.length - 1;
307
308 // progress
309 if (progressBar && progressLabel) {
310 var knownCount = deck.filter(function (d) { return d.known; }).length;
311 progressBar.style.width = (knownCount / deck.length * 100) + '%';
312 progressLabel.textContent = knownCount + ' / ' + deck.length + ' known';
313 }
314 }
315
316 // Keyboard nav
317 document.addEventListener('keydown', function (e) {
318 if (!root.closest(':focus-within') && document.activeElement !== document.body) {
319 var rect = root.getBoundingClientRect();
320 if (rect.top > window.innerHeight || rect.bottom < 0) return;
321 }
322 if (e.key === 'ArrowLeft') { prevBtn.click(); }
323 if (e.key === 'ArrowRight') { nextBtn.click(); }
324 if (e.key === ' ') { e.preventDefault(); card.click(); }
325 });
326
327 render();
328 }
329
330 function init() {
331 document.querySelectorAll('.bkbg-fc-app').forEach(initBlock);
332 }
333
334 if (document.readyState === 'loading') {
335 document.addEventListener('DOMContentLoaded', init);
336 } else {
337 init();
338 }
339 })();
340