{"id":416,"date":"2026-09-18T04:56:22","date_gmt":"2026-09-18T04:56:22","guid":{"rendered":"https:\/\/breadman.men\/?page_id=416"},"modified":"2026-09-18T04:57:55","modified_gmt":"2026-09-18T04:57:55","slug":"dice-roller","status":"publish","type":"page","link":"https:\/\/breadman.men\/index.php\/dice-roller\/","title":{"rendered":"ye olde dice roller"},"content":{"rendered":"    <script src=\"https:\/\/cdn.jsdelivr.net\/npm\/canvas-confetti@1.9.3\/dist\/confetti.browser.min.js\"><\/script>\n\n    <style>\n        .dice-app-container {\n            max-width: 700px;\n            margin: 40px auto;\n            background: #181818;\n            border: 1px solid #282828;\n            border-radius: 12px;\n            padding: 30px;\n            color: #eee;\n            box-shadow: 0 10px 30px rgba(0,0,0,0.5);\n            font-family: inherit;\n            box-sizing: border-box;\n        }\n        \n        .dice-header {\n            text-align: center;\n            margin-bottom: 25px;\n        }\n        .dice-header h2 {\n            margin: 0 0 10px 0;\n            color: #fff;\n        }\n\n        \/* Controls: Adding Dice *\/\n        .dice-controls {\n            display: flex;\n            flex-wrap: wrap;\n            gap: 10px;\n            justify-content: center;\n            margin-bottom: 25px;\n            background: #222;\n            padding: 15px;\n            border-radius: 8px;\n            border: 1px solid #333;\n        }\n        .add-dice-btn {\n            background: #333;\n            color: #ffb400;\n            border: 1px solid #444;\n            border-radius: 6px;\n            padding: 8px 16px;\n            font-weight: bold;\n            font-size: 1rem;\n            cursor: pointer;\n            transition: all 0.2s ease;\n        }\n        .add-dice-btn:hover {\n            background: #ffb400;\n            color: #111;\n        }\n\n        \/* Modifier & Action Row *\/\n        .action-row {\n            display: flex;\n            gap: 15px;\n            align-items: center;\n            justify-content: space-between;\n            margin-bottom: 25px;\n        }\n        .modifier-box {\n            display: flex;\n            align-items: center;\n            gap: 10px;\n            background: #222;\n            padding: 8px 15px;\n            border-radius: 8px;\n            border: 1px solid #333;\n        }\n        .modifier-box input {\n            width: 60px;\n            background: #181818;\n            border: 1px solid #444;\n            color: #fff;\n            padding: 6px;\n            border-radius: 4px;\n            text-align: center;\n            font-size: 1.1rem;\n            font-weight: bold;\n        }\n        .modifier-box input:focus {\n            outline: none;\n            border-color: #ffb400;\n        }\n        .roll-btn {\n            background: #ffb400;\n            color: #111;\n            border: none;\n            border-radius: 8px;\n            padding: 12px 30px;\n            font-size: 1.2rem;\n            font-weight: bold;\n            cursor: pointer;\n            flex: 1;\n            text-transform: uppercase;\n            letter-spacing: 1px;\n            transition: transform 0.1s ease, background 0.2s ease;\n        }\n        .roll-btn:hover { background: #e5a200; transform: translateY(-2px); }\n        .roll-btn:active { transform: translateY(0); }\n        \n        .clear-btn {\n            background: transparent;\n            color: #888;\n            border: 1px solid #444;\n            border-radius: 8px;\n            padding: 12px 20px;\n            cursor: pointer;\n            transition: all 0.2s;\n        }\n        .clear-btn:hover { color: #dc3545; border-color: #dc3545; }\n\n        \/* The Tray *\/\n        .dice-tray {\n            min-height: 150px;\n            background: #111;\n            border: 2px dashed #333;\n            border-radius: 12px;\n            padding: 20px;\n            display: flex;\n            flex-wrap: wrap;\n            gap: 20px;\n            justify-content: center;\n            align-items: center;\n            margin-bottom: 25px;\n        }\n        .empty-tray-msg {\n            color: #666;\n            font-style: italic;\n        }\n\n        \/* Results Area *\/\n        .results-box {\n            text-align: center;\n            background: rgba(255, 180, 0, 0.1);\n            border: 1px solid #ffb400;\n            padding: 20px;\n            border-radius: 8px;\n            display: none;\n        }\n        .results-box h3 { margin: 0 0 5px 0; color: #ffb400; font-size: 1.2rem; }\n        .results-box .total-score { font-size: 3rem; font-weight: bold; color: #fff; margin: 0; line-height: 1; }\n        .results-box .breakdown { color: #aaa; font-size: 0.9rem; margin-top: 10px; }\n\n        \/* CSS Polyhedral Dice Shapes *\/\n        .die {\n            width: 80px;\n            height: 80px;\n            background: #2a2a2a;\n            position: relative;\n            display: flex;\n            justify-content: center;\n            align-items: center;\n            font-size: 1.8rem;\n            font-weight: bold;\n            color: #fff;\n            box-shadow: inset 0 0 15px rgba(0,0,0,0.8);\n            transition: transform 0.2s ease;\n            cursor: pointer;\n        }\n        .die:hover { transform: scale(1.05); }\n        \n        \/* Shape Masks *\/\n        .die-d4 { clip-path: polygon(50% 0%, 0% 100%, 100% 100%); align-items: flex-end; padding-bottom: 12px; }\n        .die-d6 { border-radius: 12px; }\n        .die-d8 { clip-path: polygon(50% 0%, 100% 50%, 50% 100%, 0% 50%); }\n        .die-d10 { clip-path: polygon(50% 0%, 100% 40%, 50% 100%, 0% 40%); }\n        .die-d12 { clip-path: polygon(50% 0%, 100% 38%, 81% 100%, 19% 100%, 0% 38%); }\n        .die-d20 { clip-path: polygon(50% 0%, 95% 25%, 95% 75%, 50% 100%, 5% 75%, 5% 25%); }\n        .die-d100 { border-radius: 50%; }\n\n        .die-label {\n            position: absolute;\n            bottom: -22px;\n            font-size: 0.75rem;\n            color: #888;\n            text-transform: uppercase;\n            letter-spacing: 1px;\n            width: 100%;\n            text-align: center;\n        }\n\n        .die-container {\n            position: relative;\n            display: flex;\n            flex-direction: column;\n            align-items: center;\n            margin-bottom: 15px;\n        }\n\n        \/* Animations *\/\n        .rolling {\n            animation: shake 0.4s infinite alternate ease-in-out;\n            color: #ffb400; \n        }\n        .rolled-nat-20 {\n            background: #ffb400 !important;\n            color: #111 !important;\n            box-shadow: 0 0 20px rgba(255, 180, 0, 0.6) !important;\n        }\n        .rolled-nat-1 {\n            background: #dc3545 !important;\n            color: #fff !important;\n        }\n\n        @keyframes shake {\n            0% { transform: rotate(0deg) translate(0px, 0px); }\n            25% { transform: rotate(-15deg) translate(-3px, -3px); }\n            50% { transform: rotate(15deg) translate(3px, 3px); }\n            75% { transform: rotate(-10deg) translate(-2px, 2px); }\n            100% { transform: rotate(10deg) translate(2px, -2px); }\n        }\n        \n        @keyframes popIn {\n            0% { transform: scale(0.8); opacity: 0; }\n            100% { transform: scale(1); opacity: 1; }\n        }\n    <\/style>\n\n    <div class=\"dice-app-container\" id=\"dnd-dice-app\">\n        <div class=\"dice-header\">\n            <h2>Digital Dice Roller<\/h2>\n            <p style=\"color:#aaa; margin:0; font-size:0.9rem;\">Click dice to add them to your pool, then click Roll.<\/p>\n        <\/div>\n\n        <div class=\"dice-controls\">\n            <button class=\"add-dice-btn\" onclick=\"addDie(4)\">+ d4<\/button>\n            <button class=\"add-dice-btn\" onclick=\"addDie(6)\">+ d6<\/button>\n            <button class=\"add-dice-btn\" onclick=\"addDie(8)\">+ d8<\/button>\n            <button class=\"add-dice-btn\" onclick=\"addDie(10)\">+ d10<\/button>\n            <button class=\"add-dice-btn\" onclick=\"addDie(12)\">+ d12<\/button>\n            <button class=\"add-dice-btn\" style=\"border-color: #ffb400;\" onclick=\"addDie(20)\">+ d20<\/button>\n            <button class=\"add-dice-btn\" onclick=\"addDie(100)\">+ d100<\/button>\n        <\/div>\n\n        <div class=\"action-row\">\n            <div class=\"modifier-box\">\n                <label style=\"font-weight:bold; color:#bbb;\">Modifier<\/label>\n                <!-- Changed to text input with auto-formatting events -->\n                <input type=\"text\" id=\"dice-modifier\" value=\"+0\" onblur=\"formatModifier()\" onfocus=\"this.select()\" onkeydown=\"if(event.key==='Enter') executeRoll();\" \/>\n            <\/div>\n            <button class=\"roll-btn\" id=\"execute-roll-btn\" onclick=\"executeRoll()\">Roll Dice<\/button>\n            <button class=\"clear-btn\" onclick=\"clearTray()\">Clear Tray<\/button>\n        <\/div>\n\n        <div class=\"dice-tray\" id=\"dice-tray\">\n            <span class=\"empty-tray-msg\" id=\"empty-msg\">Your dice pool is empty.<\/span>\n        <\/div>\n\n        <div class=\"results-box\" id=\"results-box\">\n            <h3>Total Result<\/h3>\n            <div class=\"total-score\" id=\"total-score\">0<\/div>\n            <div class=\"breakdown\" id=\"breakdown-text\"><\/div>\n        <\/div>\n    <\/div>\n\n    <script>\n        let dicePool = [];\n        let isRolling = false;\n\n        \/\/ Auto-formats the modifier input to always show a + or -\n        function formatModifier() {\n            const input = document.getElementById('dice-modifier');\n            \/\/ Extract numbers and negative signs securely\n            let val = parseInt(input.value.replace(\/[^0-9\\-+]\/g, ''), 10);\n            if (isNaN(val)) val = 0;\n            \n            \/\/ Reapply formatting\n            input.value = (val >= 0 ? '+' : '') + val;\n        }\n\n        function addDie(sides) {\n            if (isRolling) return;\n            document.getElementById('empty-msg').style.display = 'none';\n            document.getElementById('results-box').style.display = 'none';\n\n            const dieId = 'die-' + Date.now() + '-' + Math.floor(Math.random() * 1000);\n            dicePool.push({ id: dieId, sides: sides, result: sides });\n\n            renderTray();\n        }\n\n        function removeDie(idToRemove) {\n            if (isRolling) return;\n            dicePool = dicePool.filter(d => d.id !== idToRemove);\n            document.getElementById('results-box').style.display = 'none';\n            renderTray();\n        }\n\n        function clearTray() {\n            if (isRolling) return;\n            dicePool = [];\n            document.getElementById('results-box').style.display = 'none';\n            renderTray();\n        }\n\n        function renderTray() {\n            const tray = document.getElementById('dice-tray');\n            const emptyMsg = document.getElementById('empty-msg');\n            \n            const existingDice = tray.querySelectorAll('.die-container');\n            existingDice.forEach(e => e.remove());\n\n            if (dicePool.length === 0) {\n                emptyMsg.style.display = 'block';\n                return;\n            } else {\n                emptyMsg.style.display = 'none';\n            }\n\n            dicePool.forEach(die => {\n                const container = document.createElement('div');\n                container.className = 'die-container';\n                container.style.animation = 'popIn 0.2s ease';\n                \n                const dieVisual = document.createElement('div');\n                dieVisual.className = `die die-d${die.sides}`;\n                dieVisual.id = die.id;\n                dieVisual.innerText = die.sides;\n                dieVisual.onclick = () => removeDie(die.id);\n                dieVisual.title = \"Click to remove\";\n\n                const label = document.createElement('div');\n                label.className = 'die-label';\n                label.innerText = `d${die.sides}`;\n\n                container.appendChild(dieVisual);\n                container.appendChild(label);\n                tray.appendChild(container);\n            });\n        }\n\n        function executeRoll() {\n            \/\/ Force the format immediately in case they clicked Roll while typing\n            formatModifier();\n\n            if (dicePool.length === 0 || isRolling) return;\n            isRolling = true;\n\n            const rollBtn = document.getElementById('execute-roll-btn');\n            rollBtn.innerText = 'Rolling...';\n            rollBtn.style.opacity = '0.5';\n            document.getElementById('results-box').style.display = 'none';\n\n            const scrambleInterval = setInterval(() => {\n                dicePool.forEach(die => {\n                    const el = document.getElementById(die.id);\n                    if (el) {\n                        el.classList.add('rolling');\n                        el.classList.remove('rolled-nat-20', 'rolled-nat-1');\n                        el.innerText = Math.floor(Math.random() * die.sides) + 1;\n                    }\n                });\n            }, 60);\n\n            setTimeout(() => {\n                clearInterval(scrambleInterval);\n                \n                let sum = 0;\n                let breakdownArr = [];\n                let nat20Fired = false;\n\n                dicePool.forEach(die => {\n                    const finalResult = Math.floor(Math.random() * die.sides) + 1;\n                    die.result = finalResult;\n                    sum += finalResult;\n                    \n                    const el = document.getElementById(die.id);\n                    if (el) {\n                        el.classList.remove('rolling');\n                        el.innerText = finalResult;\n                        \n                        if (die.sides === 20 && finalResult === 20) {\n                            el.classList.add('rolled-nat-20');\n                            if (!nat20Fired) {\n                                triggerNat20Confetti();\n                                nat20Fired = true;\n                            }\n                        } else if (die.sides === 20 && finalResult === 1) {\n                            el.classList.add('rolled-nat-1');\n                        }\n                    }\n                    breakdownArr.push(`d${die.sides}[${finalResult}]`);\n                });\n\n                \/\/ Retrieve formatted modifier securely\n                const modInput = document.getElementById('dice-modifier').value;\n                let modifier = parseInt(modInput.replace(\/[^0-9\\-+]\/g, ''), 10);\n                if (isNaN(modifier)) modifier = 0;\n                \n                const grandTotal = sum + modifier;\n\n                let modStr = '';\n                if (modifier > 0) modStr = ` + ${modifier}`;\n                else if (modifier < 0) modStr = ` - ${Math.abs(modifier)}`;\n\n                document.getElementById('total-score').innerText = grandTotal;\n                document.getElementById('breakdown-text').innerText = `Rolls: ${breakdownArr.join(' + ')}${modStr}`;\n                \n                const resultsBox = document.getElementById('results-box');\n                resultsBox.style.display = 'block';\n                resultsBox.style.animation = 'popIn 0.3s ease';\n\n                rollBtn.innerText = 'Roll Dice';\n                rollBtn.style.opacity = '1';\n                isRolling = false;\n\n            }, 1200);\n        }\n\n        function triggerNat20Confetti() {\n            if (typeof confetti !== 'function') return;\n            const duration = 2000;\n            const end = Date.now() + duration;\n\n            (function frame() {\n                confetti({\n                    particleCount: 5,\n                    angle: 60,\n                    spread: 55,\n                    origin: { x: 0 },\n                    colors: ['#ffb400', '#ffd700']\n                });\n                confetti({\n                    particleCount: 5,\n                    angle: 120,\n                    spread: 55,\n                    origin: { x: 1 },\n                    colors: ['#ffb400', '#ffd700']\n                });\n\n                if (Date.now() < end) {\n                    requestAnimationFrame(frame);\n                }\n            }());\n        }\n    <\/script>\n    \n","protected":false},"excerpt":{"rendered":"","protected":false},"author":1,"featured_media":0,"parent":0,"menu_order":0,"comment_status":"closed","ping_status":"closed","template":"","meta":{"_acf_changed":false,"footnotes":""},"class_list":["post-416","page","type-page","status-publish","hentry"],"acf":[],"_links":{"self":[{"href":"https:\/\/breadman.men\/index.php\/wp-json\/wp\/v2\/pages\/416","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/breadman.men\/index.php\/wp-json\/wp\/v2\/pages"}],"about":[{"href":"https:\/\/breadman.men\/index.php\/wp-json\/wp\/v2\/types\/page"}],"author":[{"embeddable":true,"href":"https:\/\/breadman.men\/index.php\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/breadman.men\/index.php\/wp-json\/wp\/v2\/comments?post=416"}],"version-history":[{"count":1,"href":"https:\/\/breadman.men\/index.php\/wp-json\/wp\/v2\/pages\/416\/revisions"}],"predecessor-version":[{"id":417,"href":"https:\/\/breadman.men\/index.php\/wp-json\/wp\/v2\/pages\/416\/revisions\/417"}],"wp:attachment":[{"href":"https:\/\/breadman.men\/index.php\/wp-json\/wp\/v2\/media?parent=416"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}