| 1 |
(function () { |
| 2 |
'use strict'; |
| 3 |
|
| 4 |
function parseItems(text, mode) { |
| 5 |
if (mode === 'comma') { |
| 6 |
return text.split(',').map(function(s){ return s.trim(); }).filter(Boolean); |
| 7 |
} |
| 8 |
return text.split('\n').map(function(s){ return s.trim(); }).filter(Boolean); |
| 9 |
} |
| 10 |
|
| 11 |
var _typoKeys = { |
| 12 |
titleTypo: '--bkrpk-tt-', |
| 13 |
subtitleTypo: '--bkrpk-st-', |
| 14 |
winnerTypo: '--bkrpk-wn-' |
| 15 |
}; |
| 16 |
var _tvf; function _getTV() { return _tvf || (_tvf = window.bkbgTypoCssVars); } |
| 17 |
function typoCssVarsForEl(opts, el) { |
| 18 |
var fn = _getTV(); if (!fn) return; |
| 19 |
Object.keys(_typoKeys).forEach(function (k) { |
| 20 |
var vars = fn(opts[k] || {}, _typoKeys[k]); |
| 21 |
Object.keys(vars).forEach(function (p) { el.style.setProperty(p, vars[p]); }); |
| 22 |
}); |
| 23 |
} |
| 24 |
|
| 25 |
function initRandomPicker(app) { |
| 26 |
var opts = {}; |
| 27 |
try { opts = JSON.parse(app.getAttribute('data-opts') || '{}'); } catch(e) {} |
| 28 |
|
| 29 |
var accent = opts.accentColor || '#6c3fb5'; |
| 30 |
var cardBg = opts.cardBg || '#ffffff'; |
| 31 |
var winnerBg = opts.winnerBg || '#6c3fb5'; |
| 32 |
var winnerColor = opts.winnerColor || '#ffffff'; |
| 33 |
var itemBg = opts.itemBg || '#f5f3ff'; |
| 34 |
var itemColor = opts.itemColor || '#374151'; |
| 35 |
var highlightBg = opts.highlightBg || '#ede9fe'; |
| 36 |
var historyBg = opts.historyBg || '#f9fafb'; |
| 37 |
var historyColor = opts.historyColor || '#6b7280'; |
| 38 |
var labelColor = opts.labelColor || '#374151'; |
| 39 |
var titleColor = opts.titleColor || '#1f2937'; |
| 40 |
var subtitleColor= opts.subtitleColor || '#6b7280'; |
| 41 |
var sectionBg = opts.sectionBg || ''; |
| 42 |
var maxWidth = opts.maxWidth || 520; |
| 43 |
var cardRadius = opts.cardRadius || 16; |
| 44 |
var itemRadius = opts.itemRadius || 8; |
| 45 |
|
| 46 |
var padTop = opts.paddingTop || 60; |
| 47 |
var padBot = opts.paddingBottom || 60; |
| 48 |
var showTitle = opts.showTitle !== false; |
| 49 |
var showSub = opts.showSubtitle !== false; |
| 50 |
var showHistory = opts.showHistory !== false; |
| 51 |
var allowRepick = opts.allowRepick !== false; |
| 52 |
var animSteps = opts.animSteps || 12; |
| 53 |
var animSpeed = opts.animSpeedMs || 60; |
| 54 |
var buttonLabel = opts.buttonLabel || 'Pick Random!'; |
| 55 |
var inputMode = opts.inputMode || 'lines'; |
| 56 |
|
| 57 |
var currentItems = parseItems(opts.defaultItems || 'Alice\nBob\nCarol\nDave\nEve\nFrank', inputMode); |
| 58 |
var history = []; |
| 59 |
var winner = null; |
| 60 |
var picking = false; |
| 61 |
|
| 62 |
// Layout |
| 63 |
app.style.fontFamily = '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif'; |
| 64 |
if (sectionBg) app.style.background = sectionBg; |
| 65 |
app.style.paddingTop = padTop + 'px'; |
| 66 |
app.style.paddingBottom = padBot + 'px'; |
| 67 |
typoCssVarsForEl(opts, app); |
| 68 |
|
| 69 |
var card = document.createElement('div'); |
| 70 |
card.className = 'bkbg-rpk-card'; |
| 71 |
card.style.cssText = 'background:' + cardBg + ';border-radius:' + cardRadius + 'px;padding:36px 32px;max-width:' + maxWidth + 'px;margin:0 auto;'; |
| 72 |
app.appendChild(card); |
| 73 |
|
| 74 |
// Header |
| 75 |
if (showTitle && opts.title) { |
| 76 |
var titleEl = document.createElement('div'); |
| 77 |
titleEl.className = 'bkbg-rpk-title'; |
| 78 |
titleEl.style.cssText = 'color:' + titleColor + ';margin-bottom:6px;'; |
| 79 |
titleEl.textContent = opts.title; |
| 80 |
card.appendChild(titleEl); |
| 81 |
} |
| 82 |
if (showSub && opts.subtitle) { |
| 83 |
var subEl = document.createElement('div'); |
| 84 |
subEl.className = 'bkbg-rpk-subtitle'; |
| 85 |
subEl.style.cssText = 'color:' + subtitleColor + ';margin-bottom:20px;'; |
| 86 |
subEl.textContent = opts.subtitle; |
| 87 |
card.appendChild(subEl); |
| 88 |
} |
| 89 |
|
| 90 |
// Textarea |
| 91 |
var textareaWrap = document.createElement('div'); |
| 92 |
textareaWrap.style.marginBottom = '16px'; |
| 93 |
var textareaLbl = document.createElement('label'); |
| 94 |
textareaLbl.style.cssText = 'display:block;font-size:12px;font-weight:600;text-transform:uppercase;letter-spacing:.04em;color:' + labelColor + ';margin-bottom:6px;'; |
| 95 |
textareaLbl.textContent = 'Items (' + currentItems.length + ')'; |
| 96 |
var textarea = document.createElement('textarea'); |
| 97 |
textarea.className = 'bkbg-rpk-textarea'; |
| 98 |
textarea.rows = 6; |
| 99 |
textarea.value = opts.defaultItems || 'Alice\nBob\nCarol\nDave\nEve\nFrank'; |
| 100 |
textarea.style.borderRadius = itemRadius + 'px'; |
| 101 |
textarea.addEventListener('input', function() { |
| 102 |
currentItems = parseItems(textarea.value, inputMode); |
| 103 |
textareaLbl.textContent = 'Items (' + currentItems.length + ')'; |
| 104 |
winner = null; |
| 105 |
winnerBox.style.display = 'none'; |
| 106 |
renderChips(-1, null); |
| 107 |
}); |
| 108 |
textareaWrap.appendChild(textareaLbl); |
| 109 |
textareaWrap.appendChild(textarea); |
| 110 |
card.appendChild(textareaWrap); |
| 111 |
|
| 112 |
// Pick button |
| 113 |
var pickBtn = document.createElement('button'); |
| 114 |
pickBtn.className = 'bkbg-rpk-btn'; |
| 115 |
pickBtn.style.cssText = 'background:' + accent + ';color:' + (opts.buttonColor||'#fff') + ';border-radius:' + itemRadius + 'px;'; |
| 116 |
pickBtn.textContent = buttonLabel; |
| 117 |
pickBtn.addEventListener('click', doPick); |
| 118 |
card.appendChild(pickBtn); |
| 119 |
|
| 120 |
// Chips container |
| 121 |
var chipsWrap = document.createElement('div'); |
| 122 |
chipsWrap.className = 'bkbg-rpk-items'; |
| 123 |
card.appendChild(chipsWrap); |
| 124 |
|
| 125 |
// Winner box |
| 126 |
var winnerBox = document.createElement('div'); |
| 127 |
winnerBox.className = 'bkbg-rpk-winner-box'; |
| 128 |
winnerBox.style.cssText = 'display:none;background:' + winnerBg + ';border-radius:' + itemRadius + 'px;padding:20px;text-align:center;margin-bottom:16px;'; |
| 129 |
var winnerSubLbl = document.createElement('div'); |
| 130 |
winnerSubLbl.style.cssText = 'font-size:13px;font-weight:600;color:' + winnerColor + ';opacity:.8;margin-bottom:6px;'; |
| 131 |
winnerSubLbl.textContent = '🎉 Winner!'; |
| 132 |
var winnerText = document.createElement('div'); |
| 133 |
winnerText.className = 'bkbg-rpk-winner-text'; |
| 134 |
winnerText.style.cssText = 'color:' + winnerColor + ';'; |
| 135 |
winnerBox.appendChild(winnerSubLbl); |
| 136 |
winnerBox.appendChild(winnerText); |
| 137 |
card.appendChild(winnerBox); |
| 138 |
|
| 139 |
// History |
| 140 |
var historySection; |
| 141 |
var historyChips; |
| 142 |
if (showHistory) { |
| 143 |
historySection = document.createElement('div'); |
| 144 |
historySection.className = 'bkbg-rpk-history'; |
| 145 |
historySection.style.cssText = 'display:none;background:' + historyBg + ';border-radius:' + itemRadius + 'px;'; |
| 146 |
var histLbl = document.createElement('div'); |
| 147 |
histLbl.className = 'bkbg-rpk-history-label'; |
| 148 |
histLbl.style.color = labelColor; |
| 149 |
histLbl.textContent = 'Pick History'; |
| 150 |
historyChips = document.createElement('div'); |
| 151 |
historyChips.className = 'bkbg-rpk-history-chips'; |
| 152 |
historySection.appendChild(histLbl); |
| 153 |
historySection.appendChild(historyChips); |
| 154 |
card.appendChild(historySection); |
| 155 |
} |
| 156 |
|
| 157 |
function renderChips(highlightIdx, winnerItem) { |
| 158 |
chipsWrap.innerHTML = ''; |
| 159 |
currentItems.forEach(function(item, idx) { |
| 160 |
var chip = document.createElement('div'); |
| 161 |
chip.className = 'bkbg-rpk-chip'; |
| 162 |
chip.style.borderRadius = itemRadius + 'px'; |
| 163 |
var isWin = winnerItem === item && highlightIdx === idx; |
| 164 |
var isHi = highlightIdx === idx && !winnerItem; |
| 165 |
chip.style.background = isWin ? winnerBg : isHi ? highlightBg : itemBg; |
| 166 |
chip.style.color = isWin ? winnerColor : itemColor; |
| 167 |
chip.style.fontWeight = isWin ? '700' : '500'; |
| 168 |
chip.style.transform = (isWin||isHi) ? 'scale(1.06)' : 'scale(1)'; |
| 169 |
chip.textContent = item; |
| 170 |
chipsWrap.appendChild(chip); |
| 171 |
}); |
| 172 |
} |
| 173 |
|
| 174 |
function doPick() { |
| 175 |
if (picking || currentItems.length === 0) return; |
| 176 |
picking = true; |
| 177 |
winner = null; |
| 178 |
winnerBox.style.display = 'none'; |
| 179 |
pickBtn.disabled = true; |
| 180 |
pickBtn.textContent = 'Picking…'; |
| 181 |
|
| 182 |
var count = 0; |
| 183 |
var totalSteps = animSteps; |
| 184 |
|
| 185 |
function step() { |
| 186 |
var idx = Math.floor(Math.random() * currentItems.length); |
| 187 |
renderChips(idx, null); |
| 188 |
count++; |
| 189 |
if (count < totalSteps) { |
| 190 |
setTimeout(step, animSpeed + count * 8); |
| 191 |
} else { |
| 192 |
var finalIdx = Math.floor(Math.random() * currentItems.length); |
| 193 |
winner = currentItems[finalIdx]; |
| 194 |
renderChips(finalIdx, winner); |
| 195 |
winnerText.textContent = winner; |
| 196 |
winnerBox.style.display = 'block'; |
| 197 |
winnerBox.style.animation = 'none'; |
| 198 |
void winnerBox.offsetWidth; |
| 199 |
winnerBox.style.animation = ''; |
| 200 |
if (showHistory) { |
| 201 |
history.unshift(winner); |
| 202 |
if (history.length > 10) history.pop(); |
| 203 |
historyChips.innerHTML = ''; |
| 204 |
history.forEach(function(h) { |
| 205 |
var chip = document.createElement('span'); |
| 206 |
chip.className = 'bkbg-rpk-history-chip'; |
| 207 |
chip.style.color = historyColor; |
| 208 |
chip.textContent = h; |
| 209 |
historyChips.appendChild(chip); |
| 210 |
}); |
| 211 |
historySection.style.display = 'block'; |
| 212 |
} |
| 213 |
picking = false; |
| 214 |
pickBtn.disabled = false; |
| 215 |
pickBtn.textContent = buttonLabel; |
| 216 |
} |
| 217 |
} |
| 218 |
step(); |
| 219 |
} |
| 220 |
|
| 221 |
renderChips(-1, null); |
| 222 |
} |
| 223 |
|
| 224 |
document.querySelectorAll('.bkbg-rpk-app').forEach(initRandomPicker); |
| 225 |
})(); |
| 226 |
|