Skip to content

Instantly share code, notes, and snippets.

@mediamagnet
Created March 23, 2026 18:37
Show Gist options
  • Select an option

  • Save mediamagnet/ae38ece8efa9f4245f209a5c1c08a149 to your computer and use it in GitHub Desktop.

Select an option

Save mediamagnet/ae38ece8efa9f4245f209a5c1c08a149 to your computer and use it in GitHub Desktop.
<!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>&#x26B0;&#xFE0F; 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">&#x2212;</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">&#x2212;</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">&#x1F4DC; 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 = '&#x1FA78; 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 = '&#x2728; 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 = '&#x1F480; 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 = '&#x1F4DC; Show Rules';
rulesVisible = false;
} else {
rules.style.display = 'block';
btn.innerHTML = '&#x1F4DC; 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