Spaces:
Running
Running
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>Minimalist 3D Dice Roller</title> | |
| <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css"> | |
| <style> | |
| :root { | |
| --primary-color: #2c3e50; | |
| --secondary-color: #e74c3c; | |
| --background-color: #f5f6fa; | |
| --dice-color: #ffffff; | |
| --shadow-color: rgba(0, 0, 0, 0.2); | |
| } | |
| * { | |
| margin: 0; | |
| padding: 0; | |
| box-sizing: border-box; | |
| font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; | |
| } | |
| body { | |
| background-color: var(--background-color); | |
| display: flex; | |
| justify-content: center; | |
| align-items: center; | |
| min-height: 100vh; | |
| color: var(--primary-color); | |
| overflow-x: hidden; | |
| } | |
| .container { | |
| text-align: center; | |
| max-width: 800px; | |
| padding: 2rem; | |
| } | |
| h1 { | |
| font-weight: 300; | |
| margin-bottom: 2rem; | |
| font-size: 2.5rem; | |
| letter-spacing: 1px; | |
| } | |
| .controls { | |
| display: flex; | |
| justify-content: center; | |
| align-items: center; | |
| margin-bottom: 2rem; | |
| gap: 1rem; | |
| flex-wrap: wrap; | |
| } | |
| .dice-count { | |
| display: flex; | |
| align-items: center; | |
| gap: 0.5rem; | |
| } | |
| label { | |
| font-size: 1.1rem; | |
| } | |
| input[type="number"] { | |
| width: 60px; | |
| padding: 0.5rem; | |
| border: 1px solid #ddd; | |
| border-radius: 4px; | |
| text-align: center; | |
| font-size: 1rem; | |
| outline: none; | |
| transition: border 0.3s; | |
| } | |
| input[type="number"]:focus { | |
| border-color: var(--secondary-color); | |
| } | |
| input[type="number"]::-webkit-inner-spin-button, | |
| input[type="number"]::-webkit-outer-spin-button { | |
| -webkit-appearance: none; | |
| margin: 0; | |
| } | |
| .btn { | |
| background-color: var(--secondary-color); | |
| color: white; | |
| border: none; | |
| padding: 0.75rem 1.5rem; | |
| border-radius: 4px; | |
| cursor: pointer; | |
| font-size: 1rem; | |
| transition: all 0.3s; | |
| display: flex; | |
| align-items: center; | |
| gap: 0.5rem; | |
| } | |
| .btn:hover { | |
| background-color: #c0392b; | |
| transform: translateY(-2px); | |
| box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1); | |
| } | |
| .btn:active { | |
| transform: translateY(0); | |
| box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); | |
| } | |
| .dice-container { | |
| display: flex; | |
| justify-content: center; | |
| flex-wrap: wrap; | |
| gap: 2rem; | |
| margin: 2rem 0; | |
| min-height: 200px; | |
| perspective: 1000px; | |
| } | |
| .dice { | |
| width: 80px; | |
| height: 80px; | |
| position: relative; | |
| transform-style: preserve-3d; | |
| transition: transform 1s ease-in-out; | |
| cursor: pointer; | |
| } | |
| @media (max-width: 768px) { | |
| .dice { | |
| width: 60px; | |
| height: 60px; | |
| } | |
| } | |
| .dice-face { | |
| position: absolute; | |
| width: 100%; | |
| height: 100%; | |
| background-color: var(--dice-color); | |
| border-radius: 8px; | |
| display: flex; | |
| justify-content: center; | |
| align-items: center; | |
| box-shadow: inset 0 0 10px var(--shadow-color); | |
| border: 1px solid #eee; | |
| } | |
| .dot { | |
| width: 12px; | |
| height: 12px; | |
| background-color: var(--primary-color); | |
| border-radius: 50%; | |
| position: absolute; | |
| } | |
| @media (max-width: 768px) { | |
| .dot { | |
| width: 10px; | |
| height: 10px; | |
| } | |
| } | |
| /* Dice face positions */ | |
| .front { transform: translateZ(40px); } | |
| .back { transform: rotateY(180deg) translateZ(40px); } | |
| .right { transform: rotateY(90deg) translateZ(40px); } | |
| .left { transform: rotateY(-90deg) translateZ(40px); } | |
| .top { transform: rotateX(90deg) translateZ(40px); } | |
| .bottom { transform: rotateX(-90deg) translateZ(40px); } | |
| /* Media query for smaller dice on mobile */ | |
| @media (max-width: 768px) { | |
| .front { transform: translateZ(30px); } | |
| .back { transform: rotateY(180deg) translateZ(30px); } | |
| .right { transform: rotateY(90deg) translateZ(30px); } | |
| .left { transform: rotateY(-90deg) translateZ(30px); } | |
| .top { transform: rotateX(90deg) translateZ(30px); } | |
| .bottom { transform: rotateX(-90deg) translateZ(30px); } | |
| } | |
| /* Dot positions for each face */ | |
| /* All faces use these positions and visibility is controlled by JavaScript */ | |
| .dot-center { transform: translate(0, 0); } | |
| .dot-top-left { | |
| top: 20%; | |
| left: 20%; | |
| } | |
| .dot-top-right { | |
| top: 20%; | |
| right: 20%; | |
| } | |
| .dot-middle-left { | |
| left: 20%; | |
| } | |
| .dot-middle-right { | |
| right: 20%; | |
| } | |
| .dot-bottom-left { | |
| bottom: 20%; | |
| left: 20%; | |
| } | |
| .dot-bottom-right { | |
| bottom: 20%; | |
| right: 20%; | |
| } | |
| /* Dice roll animations */ | |
| @keyframes roll-twist { | |
| 0% { transform: rotateX(0) rotateY(0) rotateZ(0); } | |
| 20% { transform: rotateX(180deg) rotateY(360deg) rotateZ(45deg); } | |
| 40% { transform: rotateX(360deg) rotateY(180deg) rotateZ(90deg); } | |
| 60% { transform: rotateX(540deg) rotateY(360deg) rotateZ(135deg); } | |
| 80% { transform: rotateX(720deg) rotateY(540deg) rotateZ(180deg); } | |
| 100% { transform: rotateX(900deg) rotateY(720deg) rotateZ(225deg); } | |
| } | |
| @keyframes roll-flip { | |
| 0% { transform: rotateX(0) rotateY(0); } | |
| 30% { transform: rotateX(360deg) rotateY(180deg); } | |
| 70% { transform: rotateX(720deg) rotateY(540deg); } | |
| 100% { transform: rotateX(1080deg) rotateY(720deg); } | |
| } | |
| @keyframes roll-spiral { | |
| 0% { transform: rotateX(0) rotateY(0) rotateZ(0) translateY(0); } | |
| 20% { transform: rotateX(180deg) rotateY(90deg) rotateZ(90deg) translateY(-50px); } | |
| 40% { transform: rotateX(360deg) rotateY(180deg) rotateZ(180deg) translateY(0); } | |
| 60% { transform: rotateX(540deg) rotateY(270deg) rotateZ(270deg) translateY(30px); } | |
| 80% { transform: rotateX(720deg) rotateY(360deg) rotateZ(360deg) translateY(-20px); } | |
| 100% { transform: rotateX(900deg) rotateY(450deg) rotateZ(450deg) translateY(0); } | |
| } | |
| @keyframes roll-bounce { | |
| 0% { transform: rotateX(0) rotateY(0) translateY(0); } | |
| 10% { transform: rotateX(180deg) rotateY(90deg) translateY(-40px); } | |
| 25% { transform: rotateX(360deg) rotateY(180deg) translateY(0); } | |
| 35% { transform: rotateX(540deg) rotateY(270deg) translateY(-30px); } | |
| 45% { transform: rotateX(720deg) rotateY(360deg) translateY(0); } | |
| 55% { transform: rotateX(900deg) rotateY(450deg) translateY(-15px); } | |
| 65% { transform: rotateX(1080deg) rotateY(540deg) translateY(0); } | |
| 75% { transform: rotateX(1260deg) rotateY(630deg) translateY(-5px); } | |
| 85% { transform: rotateX(1440deg) rotateY(720deg) translateY(0); } | |
| 100% { transform: rotateX(1620deg) rotateY(810deg) translateY(0); } | |
| } | |
| .rolling-twist { | |
| animation: roll-twist 1.5s cubic-bezier(0.17, 0.67, 0.83, 0.67) forwards; | |
| } | |
| .rolling-flip { | |
| animation: roll-flip 1.5s cubic-bezier(0.17, 0.67, 0.83, 0.67) forwards; | |
| } | |
| .rolling-spiral { | |
| animation: roll-spiral 1.5s cubic-bezier(0.17, 0.67, 0.83, 0.67) forwards; | |
| } | |
| .rolling-bounce { | |
| animation: roll-bounce 2s cubic-bezier(0.17, 0.67, 0.83, 0.67) forwards; | |
| } | |
| .total-display { | |
| margin-top: 2rem; | |
| font-size: 1.5rem; | |
| opacity: 0; | |
| transition: opacity 0.5s; | |
| } | |
| .show-total { | |
| opacity: 1; | |
| } | |
| footer { | |
| margin-top: 3rem; | |
| font-size: 0.9rem; | |
| color: #7f8c8d; | |
| } | |
| /* Responsive adjustments */ | |
| @media (max-width: 600px) { | |
| h1 { | |
| font-size: 2rem; | |
| } | |
| .controls { | |
| flex-direction: column; | |
| } | |
| .dice-count { | |
| margin-bottom: 1rem; | |
| } | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <div class="container"> | |
| <h1>Minimalist Dice Roller</h1> | |
| <div class="controls"> | |
| <div class="dice-count"> | |
| <label for="dice-number">Number of dice:</label> | |
| <input type="number" id="dice-number" min="1" max="10" value="3"> | |
| </div> | |
| <button id="roll-btn" class="btn"> | |
| <i class="fas fa-dice"></i> Roll Dice | |
| </button> | |
| </div> | |
| <div id="dice-container" class="dice-container"></div> | |
| <div id="total-display" class="total-display"></div> | |
| <footer> | |
| Click any die to roll it individually | |
| </footer> | |
| </div> | |
| <script> | |
| document.addEventListener('DOMContentLoaded', function() { | |
| const diceContainer = document.getElementById('dice-container'); | |
| const rollBtn = document.getElementById('roll-btn'); | |
| const diceNumberInput = document.getElementById('dice-number'); | |
| const totalDisplay = document.getElementById('total-display'); | |
| let diceCount = 3; | |
| let diceElements = []; | |
| // Possible roll animations | |
| const rollAnimations = [ | |
| 'rolling-twist', | |
| 'rolling-flip', | |
| 'rolling-spiral', | |
| 'rolling-bounce' | |
| ]; | |
| // Create initial dice | |
| initializeDice(); | |
| // Update dice count when input changes | |
| diceNumberInput.addEventListener('change', function() { | |
| const value = parseInt(this.value); | |
| if (isNaN(value) || value < 1) { | |
| this.value = 1; | |
| diceCount = 1; | |
| } else if (value > 10) { | |
| this.value = 10; | |
| diceCount = 10; | |
| } else { | |
| diceCount = value; | |
| } | |
| initializeDice(); | |
| }); | |
| // Roll all dice | |
| rollBtn.addEventListener('click', function() { | |
| rollAllDice(); | |
| }); | |
| function initializeDice() { | |
| // Clear existing dice | |
| diceContainer.innerHTML = ''; | |
| diceElements = []; | |
| // Create new dice | |
| for (let i = 0; i < diceCount; i++) { | |
| const dice = createDiceElement(); | |
| diceContainer.appendChild(dice); | |
| diceElements.push(dice); | |
| } | |
| } | |
| function createDiceElement() { | |
| const dice = document.createElement('div'); | |
| dice.className = 'dice'; | |
| // Create all 6 faces of the dice with ALL possible dots | |
| const faces = [ | |
| { class: 'front', value: 1 }, | |
| { class: 'back', value: 2 }, | |
| { class: 'right', value: 3 }, | |
| { class: 'left', value: 4 }, | |
| { class: 'top', value: 5 }, | |
| { class: 'bottom', value: 6 } | |
| ]; | |
| faces.forEach((face) => { | |
| const faceElement = document.createElement('div'); | |
| faceElement.className = `dice-face ${face.class}`; | |
| // Create all possible dots (they will be shown/hidden based on value) | |
| const dotPositions = [ | |
| 'dot-center', | |
| 'dot-top-left', | |
| 'dot-top-right', | |
| 'dot-middle-left', | |
| 'dot-middle-right', | |
| 'dot-bottom-left', | |
| 'dot-bottom-right' | |
| ]; | |
| // Create all dots (visibility will be controlled by JS) | |
| dotPositions.forEach(pos => { | |
| const dot = document.createElement('div'); | |
| dot.className = `dot ${pos}`; | |
| faceElement.appendChild(dot); | |
| }); | |
| dice.appendChild(faceElement); | |
| }); | |
| // Show random face initially | |
| rollSingleDice(dice, false); | |
| // Add click event to roll single die | |
| dice.addEventListener('click', function() { | |
| rollSingleDice(this, true); | |
| updateTotal(); | |
| }); | |
| return dice; | |
| } | |
| function rollSingleDice(diceElement, withAnimation = true) { | |
| // Remove any existing animation classes | |
| rollAnimations.forEach(anim => { | |
| diceElement.classList.remove(anim); | |
| }); | |
| if (withAnimation) { | |
| // Randomly select an animation | |
| const randomAnim = rollAnimations[Math.floor(Math.random() * rollAnimations.length)]; | |
| diceElement.classList.add(randomAnim); | |
| // Remove animation class after animation completes | |
| setTimeout(() => { | |
| diceElement.className = 'dice'; // Reset all classes | |
| showRandomFace(diceElement); | |
| }, 2000); | |
| } else { | |
| showRandomFace(diceElement); | |
| } | |
| } | |
| function showRandomFace(diceElement) { | |
| // Generate random number between 1 and 6 | |
| const randomValue = Math.floor(Math.random() * 6) + 1; | |
| // Calculate rotation to show the correct face | |
| const rotations = [ | |
| { x: 0, y: 0 }, // 1 (front) | |
| { x: 0, y: 180 }, // 2 (back) | |
| { x: 0, y: 90 }, // 3 (right) | |
| { x: 0, y: 270 }, // 4 (left) | |
| { x: 270, y: 0 }, // 5 (top) | |
| { x: 90, y: 0 } // 6 (bottom) | |
| ]; | |
| const rotation = rotations[randomValue - 1]; | |
| diceElement.style.transform = `rotateX(${rotation.x}deg) rotateY(${rotation.y}deg)`; | |
| diceElement.setAttribute('data-value', randomValue); | |
| // Update dots visibility for all faces based on their values | |
| const faces = diceElement.querySelectorAll('.dice-face'); | |
| faces.forEach((face, idx) => { | |
| const value = idx + 1; // faces are in order 1-6 | |
| const dots = face.querySelectorAll('.dot'); | |
| // Hide all dots first | |
| dots.forEach(dot => dot.style.display = 'none'); | |
| // Show dots based on face value | |
| switch(value) { | |
| case 1: // Center | |
| dots[0].style.display = 'block'; | |
| break; | |
| case 2: // Top-left, Bottom-right | |
| dots[1].style.display = 'block'; | |
| dots[6].style.display = 'block'; | |
| break; | |
| case 3: // Center + case 2 | |
| dots[0].style.display = 'block'; | |
| dots[1].style.display = 'block'; | |
| dots[6].style.display = 'block'; | |
| break; | |
| case 4: // All corners | |
| dots[1].style.display = 'block'; | |
| dots[2].style.display = 'block'; | |
| dots[5].style.display = 'block'; | |
| dots[6].style.display = 'block'; | |
| break; | |
| case 5: // Center + case 4 | |
| dots[0].style.display = 'block'; | |
| dots[1].style.display = 'block'; | |
| dots[2].style.display = 'block'; | |
| dots[5].style.display = 'block'; | |
| dots[6].style.display = 'block'; | |
| break; | |
| case 6: // All corners + middle | |
| dots[1].style.display = 'block'; | |
| dots[2].style.display = 'block'; | |
| dots[3].style.display = 'block'; | |
| dots[4].style.display = 'block'; | |
| dots[5].style.display = 'block'; | |
| dots[6].style.display = 'block'; | |
| break; | |
| } | |
| }); | |
| } | |
| function rollAllDice() { | |
| // Hide total display during roll | |
| totalDisplay.classList.remove('show-total'); | |
| // Roll each die with a slight delay between them | |
| diceElements.forEach((dice, index) => { | |
| setTimeout(() => { | |
| rollSingleDice(dice, true); | |
| }, index * 150); | |
| }); | |
| // Show total after all dice finish rolling | |
| setTimeout(() => { | |
| updateTotal(); | |
| totalDisplay.classList.add('show-total'); | |
| }, 2500); | |
| } | |
| function updateTotal() { | |
| let total = 0; | |
| let allRolled = true; | |
| diceElements.forEach(dice => { | |
| const value = dice.getAttribute('data-value'); | |
| if (value) { | |
| total += parseInt(value); | |
| } else { | |
| allRolled = false; | |
| } | |
| }); | |
| if (allRolled) { | |
| totalDisplay.textContent = `Total: ${total}`; | |
| } else { | |
| totalDisplay.textContent = ''; | |
| } | |
| } | |
| }); | |
| </script> | |
| <p style="border-radius: 8px; text-align: center; font-size: 12px; color: #fff; margin-top: 16px;position: fixed; left: 8px; bottom: 8px; z-index: 10; background: rgba(0, 0, 0, 0.8); padding: 4px 8px;">Made with <a href="https://enzostvs-deepsite.hf.space" style="color: #fff;" target="_blank" >DeepSite</a> <img src="https://enzostvs-deepsite.hf.space/logo.svg" alt="DeepSite Logo" style="width: 16px; height: 16px; vertical-align: middle;"></p></body> | |
| </html> |