Created
March 23, 2026 18:37
-
-
Save mediamagnet/ae38ece8efa9f4245f209a5c1c08a149 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <!DOCTYPE html> | |
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no"> | |
| <title>V5 Dice Roller</title> | |
| <style> | |
| * { | |
| box-sizing: border-box; | |
| -webkit-tap-highlight-color: transparent; | |
| } | |
| body { | |
| font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; | |
| background: linear-gradient(135deg, #1a1a2e 0%, #16213e 50%, #0f0f23 100%); | |
| color: #e0e0e0; | |
| min-height: 100vh; | |
| margin: 0; | |
| padding: 20px; | |
| } | |
| .container { | |
| max-width: 400px; | |
| margin: 0 auto; | |
| } | |
| h1 { | |
| text-align: center; | |
| color: #c41e3a; | |
| text-shadow: 0 0 10px rgba(196, 30, 58, 0.5); | |
| font-size: 1.8rem; | |
| margin-bottom: 5px; | |
| } | |
| .subtitle { | |
| text-align: center; | |
| color: #888; | |
| font-size: 0.9rem; | |
| margin-bottom: 25px; | |
| } | |
| .input-group { | |
| background: rgba(255, 255, 255, 0.05); | |
| border-radius: 15px; | |
| padding: 20px; | |
| margin-bottom: 15px; | |
| } | |
| .input-row { | |
| display: flex; | |
| align-items: center; | |
| justify-content: space-between; | |
| margin-bottom: 15px; | |
| } | |
| .input-row:last-child { | |
| margin-bottom: 0; | |
| } | |
| .input-label { | |
| font-size: 1.1rem; | |
| flex: 1; | |
| } | |
| .input-label.hunger { | |
| color: #c41e3a; | |
| } | |
| .input-controls { | |
| display: flex; | |
| align-items: center; | |
| gap: 10px; | |
| } | |
| .input-controls button { | |
| width: 44px; | |
| height: 44px; | |
| border-radius: 50%; | |
| border: none; | |
| background: rgba(255, 255, 255, 0.1); | |
| color: #fff; | |
| font-size: 1.5rem; | |
| cursor: pointer; | |
| transition: background 0.2s; | |
| } | |
| .input-controls button:active { | |
| background: rgba(255, 255, 255, 0.2); | |
| } | |
| .input-controls .value { | |
| font-size: 1.5rem; | |
| width: 40px; | |
| text-align: center; | |
| font-weight: bold; | |
| } | |
| .roll-btn { | |
| width: 100%; | |
| padding: 18px; | |
| font-size: 1.4rem; | |
| font-weight: bold; | |
| background: linear-gradient(135deg, #c41e3a 0%, #8b0000 100%); | |
| color: white; | |
| border: none; | |
| border-radius: 15px; | |
| cursor: pointer; | |
| text-transform: uppercase; | |
| letter-spacing: 2px; | |
| transition: transform 0.1s, box-shadow 0.2s; | |
| box-shadow: 0 4px 15px rgba(196, 30, 58, 0.4); | |
| } | |
| .roll-btn:active { | |
| transform: scale(0.98); | |
| } | |
| .results { | |
| margin-top: 25px; | |
| display: none; | |
| } | |
| .dice-display { | |
| display: flex; | |
| flex-wrap: wrap; | |
| gap: 10px; | |
| justify-content: center; | |
| margin-bottom: 20px; | |
| } | |
| .die { | |
| width: 50px; | |
| height: 50px; | |
| border-radius: 10px; | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| font-size: 1.4rem; | |
| font-weight: bold; | |
| background: linear-gradient(135deg, #2a2a4a 0%, #1a1a3a 100%); | |
| border: 2px solid #444; | |
| position: relative; | |
| } | |
| .die.hunger { | |
| background: linear-gradient(135deg, #4a1a1a 0%, #3a0a0a 100%); | |
| border-color: #c41e3a; | |
| } | |
| .die.success { | |
| border-color: #4CAF50; | |
| box-shadow: 0 0 10px rgba(76, 175, 80, 0.5); | |
| } | |
| .die.hunger.success { | |
| border-color: #c41e3a; | |
| box-shadow: 0 0 10px rgba(196, 30, 58, 0.7); | |
| } | |
| .die.crit { | |
| border-color: #FFD700; | |
| box-shadow: 0 0 15px rgba(255, 215, 0, 0.7); | |
| } | |
| .die.hunger.crit { | |
| box-shadow: 0 0 15px rgba(255, 215, 0, 0.5), 0 0 25px rgba(196, 30, 58, 0.7); | |
| } | |
| .die.skull::after { | |
| content: '\1F480'; | |
| position: absolute; | |
| font-size: 0.6rem; | |
| bottom: -2px; | |
| right: -2px; | |
| } | |
| .result-box { | |
| background: rgba(255, 255, 255, 0.05); | |
| border-radius: 15px; | |
| padding: 20px; | |
| text-align: center; | |
| } | |
| .success-count { | |
| font-size: 3rem; | |
| font-weight: bold; | |
| color: #4CAF50; | |
| } | |
| .success-label { | |
| font-size: 1.2rem; | |
| color: #888; | |
| margin-bottom: 15px; | |
| } | |
| .special-result { | |
| padding: 12px 20px; | |
| border-radius: 10px; | |
| margin-top: 15px; | |
| font-weight: bold; | |
| font-size: 1.1rem; | |
| } | |
| .critical { | |
| background: linear-gradient(135deg, rgba(255, 215, 0, 0.2) 0%, rgba(255, 215, 0, 0.1) 100%); | |
| border: 2px solid #FFD700; | |
| color: #FFD700; | |
| } | |
| .messy-crit { | |
| background: linear-gradient(135deg, rgba(255, 215, 0, 0.2) 0%, rgba(196, 30, 58, 0.2) 100%); | |
| border: 2px solid #FF6B6B; | |
| color: #FF6B6B; | |
| } | |
| .bestial-failure { | |
| background: linear-gradient(135deg, rgba(196, 30, 58, 0.3) 0%, rgba(139, 0, 0, 0.3) 100%); | |
| border: 2px solid #c41e3a; | |
| color: #FF4444; | |
| } | |
| .total-failure { | |
| background: rgba(100, 100, 100, 0.2); | |
| border: 2px solid #666; | |
| color: #999; | |
| } | |
| .rules-toggle { | |
| text-align: center; | |
| margin-top: 25px; | |
| } | |
| .rules-toggle button { | |
| background: none; | |
| border: 1px solid #444; | |
| color: #888; | |
| padding: 10px 20px; | |
| border-radius: 20px; | |
| font-size: 0.9rem; | |
| cursor: pointer; | |
| } | |
| .rules { | |
| display: none; | |
| background: rgba(0, 0, 0, 0.3); | |
| border-radius: 15px; | |
| padding: 20px; | |
| margin-top: 15px; | |
| font-size: 0.85rem; | |
| line-height: 1.6; | |
| } | |
| .rules h3 { | |
| color: #c41e3a; | |
| margin-top: 15px; | |
| margin-bottom: 8px; | |
| } | |
| .rules h3:first-child { | |
| margin-top: 0; | |
| } | |
| .rules ul { | |
| margin: 0; | |
| padding-left: 20px; | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <div class="container"> | |
| <h1>⚰️ V5 Dice Roller</h1> | |
| <p class="subtitle">Vampire: The Masquerade 5th Edition</p> | |
| <div class="input-group"> | |
| <div class="input-row"> | |
| <span class="input-label">Dice Pool</span> | |
| <div class="input-controls"> | |
| <button type="button" id="pool-down">−</button> | |
| <span class="value" id="pool-value">5</span> | |
| <button type="button" id="pool-up">+</button> | |
| </div> | |
| </div> | |
| <div class="input-row"> | |
| <span class="input-label hunger">Hunger</span> | |
| <div class="input-controls"> | |
| <button type="button" id="hunger-down">−</button> | |
| <span class="value" id="hunger-value">1</span> | |
| <button type="button" id="hunger-up">+</button> | |
| </div> | |
| </div> | |
| </div> | |
| <button type="button" class="roll-btn" id="roll-btn">Roll the Dice</button> | |
| <div class="results" id="results"> | |
| <div class="dice-display" id="dice-display"></div> | |
| <div class="result-box"> | |
| <div class="success-count" id="success-count">0</div> | |
| <div class="success-label">Successes</div> | |
| <div id="special-results"></div> | |
| </div> | |
| </div> | |
| <div class="rules-toggle"> | |
| <button type="button" id="rules-btn">📜 Show Rules</button> | |
| </div> | |
| <div class="rules" id="rules"> | |
| <h3>Basic Rules</h3> | |
| <ul> | |
| <li><strong>Success:</strong> Each die showing 6+ is one success</li> | |
| <li><strong>Critical:</strong> Each pair of 10s gives +2 bonus successes (4 total for the pair)</li> | |
| </ul> | |
| <h3>Hunger</h3> | |
| <ul> | |
| <li>Hunger dice replace regular dice (not added)</li> | |
| <li>They follow the same success rules</li> | |
| </ul> | |
| <h3>Messy Critical</h3> | |
| <ul> | |
| <li>If you score a critical AND at least one 10 is on a hunger die</li> | |
| <li>You succeed spectacularly, but the Beast influences how</li> | |
| </ul> | |
| <h3>Bestial Failure</h3> | |
| <ul> | |
| <li>If you fail the roll AND any hunger die shows a 1</li> | |
| <li>The Beast takes over momentarily - compulsion triggered</li> | |
| </ul> | |
| </div> | |
| </div> | |
| <script> | |
| var poolValue = 5; | |
| var hungerValue = 1; | |
| var rulesVisible = false; | |
| function adjustValue(type, delta) { | |
| if (type === 'pool') { | |
| poolValue = Math.max(1, Math.min(20, poolValue + delta)); | |
| document.getElementById('pool-value').textContent = poolValue; | |
| } else { | |
| hungerValue = Math.max(0, Math.min(5, hungerValue + delta)); | |
| document.getElementById('hunger-value').textContent = hungerValue; | |
| } | |
| } | |
| function rollDice() { | |
| var actualHunger = Math.min(hungerValue, poolValue); | |
| var regularDice = poolValue - actualHunger; | |
| var regularResults = []; | |
| var hungerResults = []; | |
| for (var i = 0; i < regularDice; i++) { | |
| regularResults.push(Math.floor(Math.random() * 10) + 1); | |
| } | |
| for (var i = 0; i < actualHunger; i++) { | |
| hungerResults.push(Math.floor(Math.random() * 10) + 1); | |
| } | |
| var successes = 0; | |
| var regularTens = 0; | |
| var hungerTens = 0; | |
| var hungerOnes = 0; | |
| for (var i = 0; i < regularResults.length; i++) { | |
| if (regularResults[i] >= 6) successes++; | |
| if (regularResults[i] === 10) regularTens++; | |
| } | |
| for (var i = 0; i < hungerResults.length; i++) { | |
| if (hungerResults[i] >= 6) successes++; | |
| if (hungerResults[i] === 10) hungerTens++; | |
| if (hungerResults[i] === 1) hungerOnes++; | |
| } | |
| var totalTens = regularTens + hungerTens; | |
| var critPairs = Math.floor(totalTens / 2); | |
| var critBonus = critPairs * 2; | |
| successes += critBonus; | |
| var hasCrit = critPairs > 0; | |
| var messyCrit = hasCrit && hungerTens > 0; | |
| var bestialFailure = successes === 0 && hungerOnes > 0; | |
| var tensInCrits = critPairs * 2; | |
| var regularTensInCrit = Math.min(regularTens, tensInCrits); | |
| var hungerTensInCrit = tensInCrits - regularTensInCrit; | |
| var diceDisplay = document.getElementById('dice-display'); | |
| diceDisplay.innerHTML = ''; | |
| var regularTenCount = 0; | |
| for (var i = 0; i < regularResults.length; i++) { | |
| var r = regularResults[i]; | |
| var die = document.createElement('div'); | |
| die.className = 'die'; | |
| if (r >= 6) die.className += ' success'; | |
| if (r === 10) { | |
| regularTenCount++; | |
| if (regularTenCount <= regularTensInCrit) { | |
| die.className += ' crit'; | |
| } | |
| } | |
| die.textContent = r; | |
| diceDisplay.appendChild(die); | |
| } | |
| var hungerTenCount = 0; | |
| for (var i = 0; i < hungerResults.length; i++) { | |
| var r = hungerResults[i]; | |
| var die = document.createElement('div'); | |
| die.className = 'die hunger'; | |
| if (r >= 6) die.className += ' success'; | |
| if (r === 10) { | |
| hungerTenCount++; | |
| if (hungerTenCount <= hungerTensInCrit) { | |
| die.className += ' crit'; | |
| } | |
| } | |
| if (r === 1) die.className += ' skull'; | |
| die.textContent = r; | |
| diceDisplay.appendChild(die); | |
| } | |
| document.getElementById('success-count').textContent = successes; | |
| var specialResults = document.getElementById('special-results'); | |
| specialResults.innerHTML = ''; | |
| if (messyCrit) { | |
| var div = document.createElement('div'); | |
| div.className = 'special-result messy-crit'; | |
| div.innerHTML = '🩸 MESSY CRITICAL<br><small>The Beast colors your success</small>'; | |
| specialResults.appendChild(div); | |
| } else if (hasCrit) { | |
| var div = document.createElement('div'); | |
| div.className = 'special-result critical'; | |
| div.innerHTML = '✨ CRITICAL SUCCESS<br><small>+' + critBonus + ' bonus successes</small>'; | |
| specialResults.appendChild(div); | |
| } | |
| if (bestialFailure) { | |
| var div = document.createElement('div'); | |
| div.className = 'special-result bestial-failure'; | |
| div.innerHTML = '💀 BESTIAL FAILURE<br><small>The Beast demands a Compulsion</small>'; | |
| specialResults.appendChild(div); | |
| } else if (successes === 0) { | |
| var div = document.createElement('div'); | |
| div.className = 'special-result total-failure'; | |
| div.textContent = 'Total Failure'; | |
| specialResults.appendChild(div); | |
| } | |
| document.getElementById('results').style.display = 'block'; | |
| } | |
| function toggleRules() { | |
| var rules = document.getElementById('rules'); | |
| var btn = document.getElementById('rules-btn'); | |
| if (rulesVisible) { | |
| rules.style.display = 'none'; | |
| btn.innerHTML = '📜 Show Rules'; | |
| rulesVisible = false; | |
| } else { | |
| rules.style.display = 'block'; | |
| btn.innerHTML = '📜 Hide Rules'; | |
| rulesVisible = true; | |
| } | |
| } | |
| document.getElementById('pool-down').addEventListener('click', function() { adjustValue('pool', -1); }); | |
| document.getElementById('pool-up').addEventListener('click', function() { adjustValue('pool', 1); }); | |
| document.getElementById('hunger-down').addEventListener('click', function() { adjustValue('hunger', -1); }); | |
| document.getElementById('hunger-up').addEventListener('click', function() { adjustValue('hunger', 1); }); | |
| document.getElementById('roll-btn').addEventListener('click', rollDice); | |
| document.getElementById('rules-btn').addEventListener('click', toggleRules); | |
| </script> | |
| </body> | |
| </html> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment