HTML
<div class="die-stage">
<div class="die-wrap">
<div class="die-cube" id="die-cube">
<div class="die-pip" data-pos="tl"></div>
<div class="die-pip" data-pos="tc"></div>
<div class="die-pip" data-pos="tr"></div>
<div class="die-pip" data-pos="ml"></div>
<div class="die-pip" data-pos="mc"></div>
<div class="die-pip" data-pos="mr"></div>
<div class="die-pip" data-pos="bl"></div>
<div class="die-pip" data-pos="bc"></div>
<div class="die-pip" data-pos="br"></div>
</div>
</div>
<div class="die-face-num" id="die-face-num">—</div>
<div class="die-caption">click the die to roll</div>
</div> CSS
.die-stage {
background: #fff;
padding: 60px 48px;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 24px;
min-height: 360px;
}
.die-wrap { perspective: 600px; }
.die-cube {
width: 110px;
height: 110px;
background: #fff;
border: 2.5px solid #1a1612;
border-radius: 18px;
display: grid;
grid-template-areas:
"tl tc tr"
"ml mc mr"
"bl bc br";
grid-template-columns: 1fr 1fr 1fr;
grid-template-rows: 1fr 1fr 1fr;
padding: 14px;
cursor: pointer;
box-shadow:
4px 4px 0 #1a1612,
5px 5px 8px rgba(0,0,0,0.12);
transition: transform 0.15s, box-shadow 0.15s;
position: relative;
overflow: hidden;
}
.die-cube:active {
transform: translate(3px, 3px);
box-shadow: 1px 1px 0 #1a1612, 2px 2px 4px rgba(0,0,0,0.1);
}
.die-cube.is-rolling {
animation: die-roll 0.5s cubic-bezier(0.36, 0.07, 0.19, 0.97);
}
@keyframes die-roll {
0% { transform: rotate(0deg) scale(1); }
20% { transform: rotate(-8deg) scale(0.95); }
50% { transform: rotate(12deg) scale(0.92); }
75% { transform: rotate(-5deg) scale(0.97); }
100% { transform: rotate(0deg) scale(1); }
}
@media (prefers-reduced-motion: reduce) {
.die-cube.is-rolling { animation: none; }
}
.die-pip {
display: flex;
align-items: center;
justify-content: center;
}
.die-pip::after {
content: '';
width: 14px;
height: 14px;
border-radius: 50%;
background: transparent;
transition: background 0.2s;
}
.die-pip.is-on::after { background: #1a1612; }
.die-pip[data-pos="tl"] { grid-area: tl; }
.die-pip[data-pos="tc"] { grid-area: tc; }
.die-pip[data-pos="tr"] { grid-area: tr; }
.die-pip[data-pos="ml"] { grid-area: ml; }
.die-pip[data-pos="mc"] { grid-area: mc; }
.die-pip[data-pos="mr"] { grid-area: mr; }
.die-pip[data-pos="bl"] { grid-area: bl; }
.die-pip[data-pos="bc"] { grid-area: bc; }
.die-pip[data-pos="br"] { grid-area: br; }
.die-caption {
font-family: system-ui, "Bricolage Grotesque", sans-serif;
font-size: 11px;
letter-spacing: 0.2em;
text-transform: uppercase;
color: #aaa;
text-align: center;
}
.die-face-num {
font-family: Georgia, "Fraunces", serif;
font-size: 48px;
font-style: italic;
font-weight: 300;
color: #1a1612;
line-height: 1;
text-align: center;
} JS
// Click the die to roll. Pip positions per face are encoded as
// arrays of grid-area names; the .is-on class lights up matching pips.
var dieFaces = {
1: ['mc'],
2: ['tr', 'bl'],
3: ['tr', 'mc', 'bl'],
4: ['tl', 'tr', 'bl', 'br'],
5: ['tl', 'tr', 'mc', 'bl', 'br'],
6: ['tl', 'tr', 'ml', 'mr', 'bl', 'br']
};
var dieCube = document.getElementById('die-cube');
var dieFaceNum = document.getElementById('die-face-num');
var dieRolling = false;
function dieSetFace(n) {
var pips = dieFaces[n];
dieCube.querySelectorAll('.die-pip').forEach(function (p) {
p.classList.toggle('is-on', pips.indexOf(p.dataset.pos) !== -1);
});
dieFaceNum.textContent = n;
}
if (dieCube) {
dieCube.addEventListener('click', function () {
if (dieRolling) return;
dieRolling = true;
dieCube.classList.add('is-rolling');
setTimeout(function () {
var n = Math.floor(Math.random() * 6) + 1;
dieSetFace(n);
dieCube.classList.remove('is-rolling');
dieRolling = false;
}, 280);
});
}