// Mythic Oracle — Dice: pool builder, custom roll, percentile, ability score // Pure frontend — no backend calls. const dicePool = { 4: 0, 6: 0, 8: 0, 10: 0, 12: 0, 20: 0, 2: 0 }; const POOL_MAX = 25; const DIE_ORDER = [4, 6, 8, 10, 12, 20, 2]; function addDie(sides) { const total = DIE_ORDER.reduce((s, d) => s + dicePool[d], 0); if (total >= POOL_MAX) return; dicePool[sides]++; updatePoolDisplay(); } function clearPool() { DIE_ORDER.forEach((d) => { dicePool[d] = 0; }); updatePoolDisplay(); const box = document.getElementById('poolResult'); box.className = 'dice-result-box'; box.innerHTML = 'Build a pool and roll.'; } function updatePoolDisplay() { const total = DIE_ORDER.reduce((s, d) => s + dicePool[d], 0); const display = document.getElementById('poolDisplay'); const capMsg = document.getElementById('poolCapMsg'); const parts = DIE_ORDER.filter((d) => dicePool[d] > 0).map((d) => `${dicePool[d]}d${d}`); if (parts.length === 0) { display.innerHTML = 'No dice in pool — click above to add.'; } else { display.textContent = parts.join(' + '); } const capped = total >= POOL_MAX; capMsg.style.display = capped ? '' : 'none'; document.querySelectorAll('#diceGrid .die-btn').forEach((btn) => { btn.disabled = capped; }); } function rollPool() { const total = DIE_ORDER.reduce((s, d) => s + dicePool[d], 0); const box = document.getElementById('poolResult'); if (total === 0) { box.className = 'dice-result-box animate'; box.innerHTML = 'Add dice to the pool first.'; void box.offsetWidth; return; } const results = {}; let grandTotal = 0; DIE_ORDER.forEach((sides) => { if (dicePool[sides] > 0) { results[sides] = []; for (let i = 0; i < dicePool[sides]; i++) { const roll = Math.floor(Math.random() * sides) + 1; results[sides].push(roll); grandTotal += roll; } } }); const activeDice = DIE_ORDER.filter((s) => results[s]); const groupedHTML = activeDice .map((sides) => { const rolls = results[sides]; const sum = rolls.reduce((a, b) => a + b, 0); const rollStr = rolls.length > 1 ? `${rolls.join(' + ')} = ${sum}` : `${sum}`; return `
d${sides}: ${rollStr}
`; }) .join(''); box.className = 'dice-result-box animate'; box.innerHTML = `
${grandTotal}
${groupedHTML}
`; void box.offsetWidth; } function validateCustomRoll() { const qty = document.getElementById('customQty').value; const sides = document.getElementById('customSides').value; const btn = document.getElementById('customRollBtn'); const isPosInt = (v) => v !== '' && Number.isInteger(Number(v)) && Number(v) > 0; const valid = isPosInt(qty) && Number(qty) <= 25 && isPosInt(sides); btn.disabled = !valid; } function rollCustom() { const qty = Number(document.getElementById('customQty').value); const sides = Number(document.getElementById('customSides').value); const box = document.getElementById('customResult'); const rolls = []; let total = 0; for (let i = 0; i < qty; i++) { const roll = Math.floor(Math.random() * sides) + 1; rolls.push(roll); total += roll; } box.className = 'dice-result-box animate'; box.innerHTML = `
${total}
${qty}d${sides}  [${rolls.join(', ')}]
`; void box.offsetWidth; } function clearCustomRoll() { const box = document.getElementById('customResult'); box.className = 'dice-result-box'; box.innerHTML = 'Enter a quantity and die size.'; } let percentileTens = null; function updatePercentileButtons() { document.getElementById('percentileTensBtn').disabled = percentileTens !== null; document.getElementById('percentileOnesBtn').disabled = percentileTens === null; } function rollPercentileTens() { percentileTens = Math.floor(Math.random() * 10) * 10; // 0, 10, 20 ... 90 updatePercentileButtons(); const box = document.getElementById('percentileResult'); box.className = 'dice-result-box animate pending'; box.innerHTML = `
${percentileTens}
Tens rolled — now roll ones.
`; void box.offsetWidth; } function rollPercentileOnes() { if (percentileTens === null) return; const ones = Math.floor(Math.random() * 10); // 0-9 const total = percentileTens + ones === 0 ? 100 : percentileTens + ones; const box = document.getElementById('percentileResult'); box.className = 'dice-result-box animate'; box.innerHTML = `
${total}
Percentile  [tens: ${percentileTens}, ones: ${ones}]
`; void box.offsetWidth; percentileTens = null; updatePercentileButtons(); } function clearPercentile() { percentileTens = null; updatePercentileButtons(); const box = document.getElementById('percentileResult'); box.className = 'dice-result-box'; box.innerHTML = 'Roll tens to begin.'; } function quickRoll(sides, qty, mod, label, boxId) { const rolls = []; for (let i = 0; i < qty; i++) { rolls.push(Math.floor(Math.random() * sides) + 1); } let total = rolls.reduce((a, b) => a + b, 0) + mod; if (label.includes('drop lowest')) { const sorted = [...rolls].sort((a, b) => a - b); sorted.shift(); total = sorted.reduce((a, b) => a + b, 0); } const box = document.getElementById(boxId); box.classList.add('animate'); box.innerHTML = `
${total}
${label}  [${rolls.join(', ')}]
`; void box.offsetWidth; } function rollAbilityArray() { const lines = []; for (let i = 0; i < 6; i++) { const rolls = []; for (let j = 0; j < 4; j++) { rolls.push(Math.floor(Math.random() * 6) + 1); } const sorted = [...rolls].sort((a, b) => a - b); sorted.shift(); const total = sorted.reduce((a, b) => a + b, 0); lines.push(`${rolls.join(', ')}  →  ${total}`); } const box = document.getElementById('abilityResult'); box.className = 'dice-result-box animate'; box.innerHTML = `
${lines.join('
')}
`; void box.offsetWidth; } function init() { document.querySelectorAll('#diceGrid .die-btn').forEach((btn) => { btn.addEventListener('click', () => addDie(Number(btn.dataset.sides))); }); document.getElementById('poolRollBtn').addEventListener('click', rollPool); document.getElementById('poolClearBtn').addEventListener('click', clearPool); document.getElementById('customQty').addEventListener('input', validateCustomRoll); document.getElementById('customSides').addEventListener('input', validateCustomRoll); document.getElementById('customRollBtn').addEventListener('click', rollCustom); document.getElementById('customClearBtn').addEventListener('click', clearCustomRoll); document.getElementById('percentileTensBtn').addEventListener('click', rollPercentileTens); document.getElementById('percentileOnesBtn').addEventListener('click', rollPercentileOnes); document.getElementById('percentileClearBtn').addEventListener('click', clearPercentile); updatePercentileButtons(); document.getElementById('ability4d6Btn').addEventListener('click', () => { quickRoll(6, 4, -4, '4d6 drop lowest (approx)', 'abilityResult'); }); document.getElementById('abilityArrayBtn').addEventListener('click', rollAbilityArray); } document.addEventListener('DOMContentLoaded', init);