| 1 |
(function () { |
| 2 |
'use strict'; |
| 3 |
|
| 4 |
function typoCssVarsForEl(typo, prefix, el) { |
| 5 |
if (!typo) 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 |
var su = typo.sizeUnit || 'px'; |
| 12 |
if (typo.sizeDesktop !== '' && typo.sizeDesktop != null) el.style.setProperty(prefix + 'font-size-d', typo.sizeDesktop + su); |
| 13 |
if (typo.sizeTablet !== '' && typo.sizeTablet != null) el.style.setProperty(prefix + 'font-size-t', typo.sizeTablet + su); |
| 14 |
if (typo.sizeMobile !== '' && typo.sizeMobile != null) el.style.setProperty(prefix + 'font-size-m', typo.sizeMobile + su); |
| 15 |
var lhu = typo.lineHeightUnit || 'px'; |
| 16 |
if (typo.lineHeightDesktop !== '' && typo.lineHeightDesktop != null) el.style.setProperty(prefix + 'line-height-d', typo.lineHeightDesktop + lhu); |
| 17 |
if (typo.lineHeightTablet !== '' && typo.lineHeightTablet != null) el.style.setProperty(prefix + 'line-height-t', typo.lineHeightTablet + lhu); |
| 18 |
if (typo.lineHeightMobile !== '' && typo.lineHeightMobile != null) el.style.setProperty(prefix + 'line-height-m', typo.lineHeightMobile + lhu); |
| 19 |
var lsu = typo.letterSpacingUnit || 'px'; |
| 20 |
if (typo.letterSpacingDesktop !== '' && typo.letterSpacingDesktop != null) { el.style.setProperty(prefix + 'letter-spacing-d', typo.letterSpacingDesktop + lsu); el.style.setProperty(prefix + 'letter-spacing', typo.letterSpacingDesktop + lsu); } |
| 21 |
if (typo.letterSpacingTablet !== '' && typo.letterSpacingTablet != null) el.style.setProperty(prefix + 'letter-spacing-t', typo.letterSpacingTablet + lsu); |
| 22 |
if (typo.letterSpacingMobile !== '' && typo.letterSpacingMobile != null) el.style.setProperty(prefix + 'letter-spacing-m', typo.letterSpacingMobile + lsu); |
| 23 |
var wsu = typo.wordSpacingUnit || 'px'; |
| 24 |
if (typo.wordSpacingDesktop !== '' && typo.wordSpacingDesktop != null) { el.style.setProperty(prefix + 'word-spacing-d', typo.wordSpacingDesktop + wsu); el.style.setProperty(prefix + 'word-spacing', typo.wordSpacingDesktop + wsu); } |
| 25 |
if (typo.wordSpacingTablet !== '' && typo.wordSpacingTablet != null) el.style.setProperty(prefix + 'word-spacing-t', typo.wordSpacingTablet + wsu); |
| 26 |
if (typo.wordSpacingMobile !== '' && typo.wordSpacingMobile != null) el.style.setProperty(prefix + 'word-spacing-m', typo.wordSpacingMobile + wsu); |
| 27 |
} |
| 28 |
|
| 29 |
var COL_LETTERS = ['B','I','N','G','O']; |
| 30 |
var COL_RANGES = [[1,15],[16,30],[31,45],[46,60],[61,75]]; |
| 31 |
var COL_BG = ['#e0f2fe','#fef9c3','#dcfce7','#fce7f3','#ede9fe']; |
| 32 |
|
| 33 |
function generateCard() { |
| 34 |
return COL_LETTERS.map(function (col, ci) { |
| 35 |
var nums = []; |
| 36 |
var lo = COL_RANGES[ci][0], hi = COL_RANGES[ci][1]; |
| 37 |
while (nums.length < 5) { |
| 38 |
var n = Math.floor(Math.random() * (hi - lo + 1)) + lo; |
| 39 |
if (nums.indexOf(n) === -1) nums.push(n); |
| 40 |
} |
| 41 |
return nums; |
| 42 |
}); |
| 43 |
} |
| 44 |
|
| 45 |
function checkBingo(marks) { |
| 46 |
var lines = []; |
| 47 |
/* 5 rows */ |
| 48 |
for (var r = 0; r < 5; r++) { |
| 49 |
lines.push([[0,r],[1,r],[2,r],[3,r],[4,r]]); |
| 50 |
} |
| 51 |
/* 5 cols */ |
| 52 |
for (var c = 0; c < 5; c++) { |
| 53 |
lines.push([[c,0],[c,1],[c,2],[c,3],[c,4]]); |
| 54 |
} |
| 55 |
/* 2 diagonals */ |
| 56 |
lines.push([[0,0],[1,1],[2,2],[3,3],[4,4]]); |
| 57 |
lines.push([[4,0],[3,1],[2,2],[1,3],[0,4]]); |
| 58 |
|
| 59 |
var winLines = lines.filter(function (line) { |
| 60 |
return line.every(function (pos) { return marks[pos[0]][pos[1]]; }); |
| 61 |
}); |
| 62 |
return winLines; |
| 63 |
} |
| 64 |
|
| 65 |
function initBlock(root) { |
| 66 |
var opts; |
| 67 |
try { opts = JSON.parse(root.getAttribute('data-opts')); } catch (e) { return; } |
| 68 |
var a = opts; |
| 69 |
|
| 70 |
var card = generateCard(); |
| 71 |
var marks = COL_LETTERS.map(function () { return [false,false,false,false,false]; }); |
| 72 |
marks[2][2] = true; /* FREE space */ |
| 73 |
|
| 74 |
var calledNumbers = []; |
| 75 |
var allNumbers = []; |
| 76 |
for (var ci = 0; ci < 5; ci++) { |
| 77 |
for (var n = COL_RANGES[ci][0]; n <= COL_RANGES[ci][1]; n++) { |
| 78 |
allNumbers.push({ col: ci, num: n }); |
| 79 |
} |
| 80 |
} |
| 81 |
var callPool = allNumbers.slice(); |
| 82 |
var lastCalled = null; |
| 83 |
var hasBingo = false; |
| 84 |
var autoTimer = null; |
| 85 |
|
| 86 |
root.innerHTML = ''; |
| 87 |
|
| 88 |
typoCssVarsForEl(a.titleTypo, '--bkbg-bng-title-', root); |
| 89 |
typoCssVarsForEl(a.subtitleTypo, '--bkbg-bng-sub-', root); |
| 90 |
|
| 91 |
var wrap = document.createElement('div'); |
| 92 |
wrap.className = 'bkbg-bng-wrap'; |
| 93 |
wrap.style.cssText = 'background:' + a.sectionBg + ';max-width:' + a.contentMaxWidth + 'px;margin:0 auto;'; |
| 94 |
root.appendChild(wrap); |
| 95 |
|
| 96 |
if (a.showTitle) { |
| 97 |
var h = document.createElement('div'); |
| 98 |
h.className = 'bkbg-bng-title'; |
| 99 |
h.style.color = a.titleColor; |
| 100 |
h.textContent = a.title; |
| 101 |
wrap.appendChild(h); |
| 102 |
} |
| 103 |
if (a.showSubtitle) { |
| 104 |
var sub = document.createElement('div'); |
| 105 |
sub.className = 'bkbg-bng-subtitle'; |
| 106 |
sub.style.color = '#6b7280'; |
| 107 |
sub.textContent = a.subtitle; |
| 108 |
wrap.appendChild(sub); |
| 109 |
} |
| 110 |
|
| 111 |
/* Win banner */ |
| 112 |
var winBanner = document.createElement('div'); |
| 113 |
winBanner.className = 'bkbg-bng-win-banner'; |
| 114 |
winBanner.style.background = a.bingoColor; |
| 115 |
winBanner.innerHTML = '<span class="bkbg-bng-win-emoji">🎉</span><div class="bkbg-bng-win-text">BINGO!</div><div class="bkbg-bng-win-sub">You got a bingo! Keep going for more!</div>'; |
| 116 |
wrap.appendChild(winBanner); |
| 117 |
|
| 118 |
/* Caller */ |
| 119 |
var callerEl = null, calledNumEl = null, callBtn = null, resetBtn = null; |
| 120 |
if (a.showCaller) { |
| 121 |
callerEl = document.createElement('div'); |
| 122 |
callerEl.className = 'bkbg-bng-caller'; |
| 123 |
callerEl.style.cssText = 'background:' + a.headerBg + ';color:' + a.headerColor + ';'; |
| 124 |
wrap.appendChild(callerEl); |
| 125 |
|
| 126 |
var callerLabel = document.createElement('div'); |
| 127 |
callerLabel.className = 'bkbg-bng-caller-label'; |
| 128 |
callerLabel.textContent = 'LAST CALLED'; |
| 129 |
callerEl.appendChild(callerLabel); |
| 130 |
|
| 131 |
calledNumEl = document.createElement('div'); |
| 132 |
calledNumEl.className = 'bkbg-bng-called-num'; |
| 133 |
calledNumEl.textContent = '—'; |
| 134 |
callerEl.appendChild(calledNumEl); |
| 135 |
|
| 136 |
var callerBtns = document.createElement('div'); |
| 137 |
callerBtns.className = 'bkbg-bng-caller-btns'; |
| 138 |
callerEl.appendChild(callerBtns); |
| 139 |
|
| 140 |
callBtn = document.createElement('button'); |
| 141 |
callBtn.className = 'bkbg-bng-call-btn'; |
| 142 |
callBtn.style.background = a.accentColor; |
| 143 |
callBtn.textContent = '▶ Call Number'; |
| 144 |
callerBtns.appendChild(callBtn); |
| 145 |
|
| 146 |
if (a.autoCall) { |
| 147 |
var autoBtn = document.createElement('button'); |
| 148 |
autoBtn.className = 'bkbg-bng-call-btn'; |
| 149 |
autoBtn.style.background = '#6366f1'; |
| 150 |
autoBtn.textContent = '⏱ Auto Call'; |
| 151 |
var autoRunning = false; |
| 152 |
autoBtn.addEventListener('click', function () { |
| 153 |
if (autoRunning) { |
| 154 |
clearInterval(autoTimer); |
| 155 |
autoRunning = false; |
| 156 |
autoBtn.textContent = '⏱ Auto Call'; |
| 157 |
callBtn.disabled = false; |
| 158 |
} else { |
| 159 |
autoRunning = true; |
| 160 |
autoBtn.textContent = '⏹ Stop Auto'; |
| 161 |
callBtn.disabled = true; |
| 162 |
autoTimer = setInterval(function () { |
| 163 |
if (!callNumber()) { |
| 164 |
clearInterval(autoTimer); |
| 165 |
autoRunning = false; |
| 166 |
autoBtn.textContent = '⏱ Auto Call'; |
| 167 |
callBtn.disabled = false; |
| 168 |
} |
| 169 |
}, (a.autoCallInterval || 5) * 1000); |
| 170 |
} |
| 171 |
}); |
| 172 |
callerBtns.appendChild(autoBtn); |
| 173 |
} |
| 174 |
} |
| 175 |
|
| 176 |
/* Grid */ |
| 177 |
var gridWrap = document.createElement('div'); |
| 178 |
gridWrap.className = 'bkbg-bng-grid-wrap'; |
| 179 |
gridWrap.style.borderColor = a.headerBg; |
| 180 |
wrap.appendChild(gridWrap); |
| 181 |
|
| 182 |
var headerRow = document.createElement('div'); |
| 183 |
headerRow.className = 'bkbg-bng-header-row'; |
| 184 |
COL_LETTERS.forEach(function (col) { |
| 185 |
var hc = document.createElement('div'); |
| 186 |
hc.className = 'bkbg-bng-header-cell'; |
| 187 |
hc.style.cssText = 'background:' + a.headerBg + ';color:' + a.headerColor + ';'; |
| 188 |
hc.textContent = col; |
| 189 |
headerRow.appendChild(hc); |
| 190 |
}); |
| 191 |
gridWrap.appendChild(headerRow); |
| 192 |
|
| 193 |
var cellGrid = document.createElement('div'); |
| 194 |
cellGrid.className = 'bkbg-bng-cell-grid'; |
| 195 |
gridWrap.appendChild(cellGrid); |
| 196 |
|
| 197 |
var cellEls = []; /* [col][row] */ |
| 198 |
var cellMarkedBg, cellDefaultBg; |
| 199 |
|
| 200 |
for (var c = 0; c < 5; c++) { |
| 201 |
cellEls[c] = []; |
| 202 |
for (var r = 0; r < 5; r++) { |
| 203 |
(function (ci, ri) { |
| 204 |
var isFree = (ci === 2 && ri === 2); |
| 205 |
var cellEl = document.createElement('div'); |
| 206 |
cellEl.className = 'bkbg-bng-cell' + (isFree ? ' bkbg-bng-marked' : ''); |
| 207 |
cellEl.style.cssText = 'border-color:' + a.borderColor + ';background:' + (isFree ? a.freeColor : a.cellBg) + ';color:' + (isFree ? '#fff' : a.cellColor) + ';'; |
| 208 |
|
| 209 |
if (isFree) { |
| 210 |
var fl = document.createElement('span'); |
| 211 |
fl.className = 'bkbg-bng-free-label'; |
| 212 |
fl.textContent = 'FREE'; |
| 213 |
cellEl.appendChild(fl); |
| 214 |
} else { |
| 215 |
cellEl.textContent = card[ci][ri]; |
| 216 |
cellEl.addEventListener('click', function () { |
| 217 |
if (marks[ci][ri]) return; |
| 218 |
marks[ci][ri] = true; |
| 219 |
cellEl.classList.add('bkbg-bng-marked'); |
| 220 |
cellEl.style.cssText = 'border-color:' + a.borderColor + ';background:' + a.markedColor + ';color:#fff;'; |
| 221 |
checkForBingo(); |
| 222 |
}); |
| 223 |
} |
| 224 |
|
| 225 |
cellEls[ci][ri] = cellEl; |
| 226 |
cellGrid.appendChild(cellEl); |
| 227 |
})(c, r); |
| 228 |
} |
| 229 |
} |
| 230 |
|
| 231 |
/* Called list */ |
| 232 |
var calledChipsEl = null; |
| 233 |
if (a.showCalledList) { |
| 234 |
var calledListEl = document.createElement('div'); |
| 235 |
calledListEl.className = 'bkbg-bng-called-list'; |
| 236 |
calledListEl.style.cssText = 'background:#fff;border-color:' + a.borderColor + ';margin-top:14px;'; |
| 237 |
var clTitle = document.createElement('div'); |
| 238 |
clTitle.className = 'bkbg-bng-called-list-title'; |
| 239 |
clTitle.style.color = '#6b7280'; |
| 240 |
clTitle.textContent = 'Called Numbers'; |
| 241 |
calledListEl.appendChild(clTitle); |
| 242 |
calledChipsEl = document.createElement('div'); |
| 243 |
calledChipsEl.className = 'bkbg-bng-called-chips'; |
| 244 |
calledListEl.appendChild(calledChipsEl); |
| 245 |
wrap.appendChild(calledListEl); |
| 246 |
} |
| 247 |
|
| 248 |
/* Actions */ |
| 249 |
var actionsEl = document.createElement('div'); |
| 250 |
actionsEl.className = 'bkbg-bng-actions'; |
| 251 |
actionsEl.style.marginTop = '14px'; |
| 252 |
wrap.appendChild(actionsEl); |
| 253 |
|
| 254 |
var newCardBtn = document.createElement('button'); |
| 255 |
newCardBtn.className = 'bkbg-bng-call-btn bkbg-bng-call-btn-outline'; |
| 256 |
newCardBtn.style.cssText = 'border-color:' + a.headerBg + ';color:' + a.headerBg + ';background:transparent;'; |
| 257 |
newCardBtn.textContent = '🔀 New Card'; |
| 258 |
actionsEl.appendChild(newCardBtn); |
| 259 |
|
| 260 |
var resetGameBtn = document.createElement('button'); |
| 261 |
resetGameBtn.className = 'bkbg-bng-call-btn bkbg-bng-call-btn-outline'; |
| 262 |
resetGameBtn.style.cssText = 'border-color:#6b7280;color:#6b7280;background:transparent;'; |
| 263 |
resetGameBtn.textContent = '↺ Reset Game'; |
| 264 |
actionsEl.appendChild(resetGameBtn); |
| 265 |
|
| 266 |
function addChip(colIdx, num) { |
| 267 |
if (!calledChipsEl) return; |
| 268 |
var chip = document.createElement('div'); |
| 269 |
chip.className = 'bkbg-bng-chip'; |
| 270 |
chip.style.background = a.accentColor; |
| 271 |
chip.textContent = COL_LETTERS[colIdx] + '-' + num; |
| 272 |
calledChipsEl.insertBefore(chip, calledChipsEl.firstChild); |
| 273 |
} |
| 274 |
|
| 275 |
function callNumber() { |
| 276 |
if (!callPool.length) { |
| 277 |
if (callBtn) callBtn.disabled = true; |
| 278 |
return false; |
| 279 |
} |
| 280 |
var idx = Math.floor(Math.random() * callPool.length); |
| 281 |
var called = callPool.splice(idx, 1)[0]; |
| 282 |
calledNumbers.push(called); |
| 283 |
lastCalled = called; |
| 284 |
|
| 285 |
if (calledNumEl) { |
| 286 |
calledNumEl.classList.remove('bkbg-bng-pop'); |
| 287 |
void calledNumEl.offsetWidth; |
| 288 |
calledNumEl.textContent = COL_LETTERS[called.col] + '-' + called.num; |
| 289 |
calledNumEl.classList.add('bkbg-bng-pop'); |
| 290 |
} |
| 291 |
|
| 292 |
addChip(called.col, called.num); |
| 293 |
|
| 294 |
/* Highlight card if number matches */ |
| 295 |
for (var r = 0; r < 5; r++) { |
| 296 |
if (card[called.col][r] === called.num && !marks[called.col][r]) { |
| 297 |
var ce = cellEls[called.col][r]; |
| 298 |
ce.style.outline = '3px solid ' + a.accentColor; |
| 299 |
} |
| 300 |
} |
| 301 |
|
| 302 |
if (!callPool.length && callBtn) callBtn.disabled = true; |
| 303 |
return true; |
| 304 |
} |
| 305 |
|
| 306 |
function checkForBingo() { |
| 307 |
var winLines = checkBingo(marks); |
| 308 |
if (winLines.length && !hasBingo) { |
| 309 |
hasBingo = true; |
| 310 |
winBanner.classList.add('bkbg-bng-show'); |
| 311 |
winLines.forEach(function (line) { |
| 312 |
line.forEach(function (pos) { |
| 313 |
cellEls[pos[0]][pos[1]].classList.add('bkbg-bng-win-cell'); |
| 314 |
cellEls[pos[0]][pos[1]].style.background = a.bingoColor; |
| 315 |
}); |
| 316 |
}); |
| 317 |
} |
| 318 |
} |
| 319 |
|
| 320 |
function resetGame() { |
| 321 |
clearInterval(autoTimer); |
| 322 |
callPool = allNumbers.slice(); |
| 323 |
calledNumbers = []; |
| 324 |
lastCalled = null; |
| 325 |
hasBingo = false; |
| 326 |
winBanner.classList.remove('bkbg-bng-show'); |
| 327 |
if (calledNumEl) calledNumEl.textContent = '—'; |
| 328 |
if (callBtn) callBtn.disabled = false; |
| 329 |
if (calledChipsEl) calledChipsEl.innerHTML = ''; |
| 330 |
marks = COL_LETTERS.map(function () { return [false,false,false,false,false]; }); |
| 331 |
marks[2][2] = true; |
| 332 |
for (var ci = 0; ci < 5; ci++) { |
| 333 |
for (var ri = 0; ri < 5; ri++) { |
| 334 |
var isFree = (ci === 2 && ri === 2); |
| 335 |
var ce = cellEls[ci][ri]; |
| 336 |
ce.classList.remove('bkbg-bng-marked', 'bkbg-bng-win-cell'); |
| 337 |
ce.style.outline = ''; |
| 338 |
if (isFree) { |
| 339 |
ce.classList.add('bkbg-bng-marked'); |
| 340 |
ce.style.cssText = 'border-color:' + a.borderColor + ';background:' + a.freeColor + ';color:#fff;'; |
| 341 |
} else { |
| 342 |
ce.style.cssText = 'border-color:' + a.borderColor + ';background:' + a.cellBg + ';color:' + a.cellColor + ';'; |
| 343 |
} |
| 344 |
} |
| 345 |
} |
| 346 |
} |
| 347 |
|
| 348 |
function newCard() { |
| 349 |
card = generateCard(); |
| 350 |
resetGame(); |
| 351 |
for (var ci = 0; ci < 5; ci++) { |
| 352 |
for (var ri = 0; ri < 5; ri++) { |
| 353 |
if (!(ci === 2 && ri === 2)) { |
| 354 |
cellEls[ci][ri].textContent = card[ci][ri]; |
| 355 |
} |
| 356 |
} |
| 357 |
} |
| 358 |
} |
| 359 |
|
| 360 |
if (callBtn) { callBtn.addEventListener('click', callNumber); } |
| 361 |
resetGameBtn.addEventListener('click', resetGame); |
| 362 |
newCardBtn.addEventListener('click', newCard); |
| 363 |
} |
| 364 |
|
| 365 |
function init() { |
| 366 |
document.querySelectorAll('.bkbg-bng-app').forEach(initBlock); |
| 367 |
} |
| 368 |
|
| 369 |
if (document.readyState === 'loading') { |
| 370 |
document.addEventListener('DOMContentLoaded', init); |
| 371 |
} else { |
| 372 |
init(); |
| 373 |
} |
| 374 |
})(); |
| 375 |
|