dricampbell commited on
Commit
e45d9f0
·
verified ·
1 Parent(s): 2e29fa6

Add 2 files

Browse files
Files changed (2) hide show
  1. README.md +7 -5
  2. index.html +778 -19
README.md CHANGED
@@ -1,10 +1,12 @@
1
  ---
2
- title: D D Dice
3
- emoji: 💻
4
- colorFrom: indigo
5
- colorTo: gray
6
  sdk: static
7
  pinned: false
 
 
8
  ---
9
 
10
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
1
  ---
2
+ title: d-d-dice
3
+ emoji: 🐳
4
+ colorFrom: red
5
+ colorTo: pink
6
  sdk: static
7
  pinned: false
8
+ tags:
9
+ - deepsite
10
  ---
11
 
12
+ Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
index.html CHANGED
@@ -1,19 +1,778 @@
1
- <!doctype html>
2
- <html>
3
- <head>
4
- <meta charset="utf-8" />
5
- <meta name="viewport" content="width=device-width" />
6
- <title>My static Space</title>
7
- <link rel="stylesheet" href="style.css" />
8
- </head>
9
- <body>
10
- <div class="card">
11
- <h1>Welcome to your static Space!</h1>
12
- <p>You can modify this app directly by editing <i>index.html</i> in the Files and versions tab.</p>
13
- <p>
14
- Also don't forget to check the
15
- <a href="https://huggingface.co/docs/hub/spaces" target="_blank">Spaces documentation</a>.
16
- </p>
17
- </div>
18
- </body>
19
- </html>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Epic D&D Dice Roller</title>
7
+ <script src="https://cdn.tailwindcss.com"></script>
8
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script>
9
+ <script src="https://cdn.jsdelivr.net/npm/cannon-es@0.19.0/dist/cannon-es.min.js"></script>
10
+ <style>
11
+ @import url('https://fonts.googleapis.com/css2?family=Cinzel:wght@400;700&family=MedievalSharp&display=swap');
12
+
13
+ body {
14
+ background-color: #0a0a0a;
15
+ color: #e0d4b0;
16
+ font-family: 'Cinzel', serif;
17
+ overflow-x: hidden;
18
+ }
19
+
20
+ .dice-container {
21
+ perspective: 1000px;
22
+ }
23
+
24
+ .roll-history-item {
25
+ border-left: 3px solid #8b5a2b;
26
+ transition: all 0.3s ease;
27
+ }
28
+
29
+ .roll-history-item:hover {
30
+ background-color: rgba(139, 90, 43, 0.2);
31
+ }
32
+
33
+ .dice-selector {
34
+ transition: all 0.2s ease;
35
+ min-height: 80px;
36
+ }
37
+
38
+ .dice-selector:hover {
39
+ transform: translateY(-5px);
40
+ box-shadow: 0 10px 15px rgba(0, 0, 0, 0.3);
41
+ }
42
+
43
+ .dice-selector.active {
44
+ border-color: #d4af37;
45
+ box-shadow: 0 0 15px rgba(212, 175, 55, 0.5);
46
+ }
47
+
48
+ .glow-text {
49
+ text-shadow: 0 0 5px rgba(212, 175, 55, 0.7);
50
+ }
51
+
52
+ .roll-button {
53
+ background: linear-gradient(135deg, #8b5a2b 0%, #5d3a1a 100%);
54
+ box-shadow: 0 4px 15px rgba(0, 0, 0, 0.3);
55
+ transition: all 0.3s ease;
56
+ }
57
+
58
+ .roll-button:hover {
59
+ transform: translateY(-3px);
60
+ box-shadow: 0 7px 20px rgba(139, 90, 43, 0.6);
61
+ }
62
+
63
+ .roll-button:active {
64
+ transform: translateY(1px);
65
+ }
66
+
67
+ #canvas-container {
68
+ position: relative;
69
+ width: 100%;
70
+ height: 400px;
71
+ background: radial-gradient(ellipse at center, #1a1a1a 0%, #0a0a0a 100%);
72
+ border-radius: 10px;
73
+ overflow: hidden;
74
+ }
75
+
76
+ #canvas-container::before {
77
+ content: '';
78
+ position: absolute;
79
+ top: 0;
80
+ left: 0;
81
+ right: 0;
82
+ bottom: 0;
83
+ background: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="100" height="100" viewBox="0 0 100 100"><rect width="100" height="100" fill="none" stroke="%23222222" stroke-width="1"/></svg>');
84
+ opacity: 0.3;
85
+ pointer-events: none;
86
+ }
87
+
88
+ .critical {
89
+ animation: pulse 1s infinite;
90
+ }
91
+
92
+ @keyframes pulse {
93
+ 0% { color: #e0d4b0; }
94
+ 50% { color: #d4af37; }
95
+ 100% { color: #e0d4b0; }
96
+ }
97
+
98
+ .shape-name {
99
+ display: -webkit-box;
100
+ -webkit-line-clamp: 2;
101
+ -webkit-box-orient: vertical;
102
+ overflow: hidden;
103
+ font-size: 0.7rem;
104
+ line-height: 1.1;
105
+ }
106
+ </style>
107
+ </head>
108
+ <body class="min-h-screen">
109
+ <div class="container mx-auto px-4 py-8">
110
+ <!-- Header -->
111
+ <header class="text-center mb-12">
112
+ <h1 class="text-4xl md:text-6xl font-bold mb-4 glow-text">Dragon's Fortune</h1>
113
+ <p class="text-xl md:text-2xl font-medium text-amber-200">Epic Dice Roller for Adventurers</p>
114
+ <div class="w-32 h-1 bg-amber-700 mx-auto mt-4 rounded-full"></div>
115
+ </header>
116
+
117
+ <div class="flex flex-col lg:flex-row gap-8">
118
+ <!-- Left Panel - Controls -->
119
+ <div class="w-full lg:w-1/3">
120
+ <div class="bg-gray-900 bg-opacity-70 rounded-lg p-6 shadow-xl border border-gray-800">
121
+ <!-- Dice Selection -->
122
+ <h2 class="text-2xl font-bold mb-4 text-amber-200 border-b border-amber-900 pb-2">Choose Your Dice</h2>
123
+ <div class="grid grid-cols-3 gap-4 mb-6">
124
+ <div class="dice-selector p-3 bg-gray-800 rounded-lg cursor-pointer text-center border border-gray-700" data-sides="4">
125
+ <div class="text-amber-200 text-lg font-bold">D4</div>
126
+ <div class="shape-name text-gray-400">Tetrahedron</div>
127
+ </div>
128
+ <div class="dice-selector p-3 bg-gray-800 rounded-lg cursor-pointer text-center border border-gray-700" data-sides="6">
129
+ <div class="text-amber-200 text-lg font-bold">D6</div>
130
+ <div class="shape-name text-gray-400">Cube</div>
131
+ </div>
132
+ <div class="dice-selector p-3 bg-gray-800 rounded-lg cursor-pointer text-center border border-gray-700" data-sides="8">
133
+ <div class="text-amber-200 text-lg font-bold">D8</div>
134
+ <div class="shape-name text-gray-400">Octahedron</div>
135
+ </div>
136
+ <div class="dice-selector p-3 bg-gray-800 rounded-lg cursor-pointer text-center border border-gray-700" data-sides="10">
137
+ <div class="text-amber-200 text-lg font-bold">D10</div>
138
+ <div class="shape-name text-gray-400">Pentagonal Trapezohedron</div>
139
+ </div>
140
+ <div class="dice-selector p-3 bg-gray-800 rounded-lg cursor-pointer text-center border border-gray-700" data-sides="12">
141
+ <div class="text-amber-200 text-lg font-bold">D12</div>
142
+ <div class="shape-name text-gray-400">Dodecahedron</div>
143
+ </div>
144
+ <div class="dice-selector p-3 bg-gray-800 rounded-lg cursor-pointer text-center border border-gray-700" data-sides="20">
145
+ <div class="text-amber-200 text-lg font-bold">D20</div>
146
+ <div class="shape-name text-gray-400">Icosahedron</div>
147
+ </div>
148
+ </div>
149
+
150
+ <!-- Quantity and Modifiers -->
151
+ <div class="mb-6">
152
+ <div class="flex items-center justify-between mb-2">
153
+ <label class="text-amber-200">Quantity:</label>
154
+ <input type="number" id="diceQuantity" min="1" max="10" value="1" class="w-16 bg-gray-800 border border-gray-700 text-center text-amber-200 rounded px-2 py-1">
155
+ </div>
156
+ <div class="flex items-center justify-between mb-2">
157
+ <label class="text-amber-200">Modifier:</label>
158
+ <input type="number" id="diceModifier" min="-20" max="20" value="0" class="w-16 bg-gray-800 border border-gray-700 text-center text-amber-200 rounded px-2 py-1">
159
+ </div>
160
+ </div>
161
+
162
+ <!-- Roll Button -->
163
+ <button id="rollButton" class="roll-button w-full py-4 rounded-lg text-xl font-bold text-amber-200 uppercase tracking-wider">
164
+ Roll the Dice
165
+ </button>
166
+
167
+ <!-- Result Display -->
168
+ <div id="resultDisplay" class="mt-6 p-4 bg-gray-800 rounded-lg text-center hidden">
169
+ <div class="text-sm text-gray-400 mb-1">Result</div>
170
+ <div id="totalResult" class="text-4xl font-bold text-amber-200"></div>
171
+ <div id="individualResults" class="mt-2 text-gray-300"></div>
172
+ <div id="criticalText" class="mt-2 text-xl font-bold text-red-400 hidden">CRITICAL HIT!</div>
173
+ </div>
174
+ </div>
175
+
176
+ <!-- Roll History -->
177
+ <div class="bg-gray-900 bg-opacity-70 rounded-lg p-6 shadow-xl border border-gray-800 mt-6">
178
+ <h2 class="text-2xl font-bold mb-4 text-amber-200 border-b border-amber-900 pb-2">Roll History</h2>
179
+ <div id="rollHistory" class="space-y-2 max-h-64 overflow-y-auto pr-2">
180
+ <!-- History items will be added here -->
181
+ </div>
182
+ </div>
183
+ </div>
184
+
185
+ <!-- Right Panel - 3D Dice -->
186
+ <div class="w-full lg:w-2/3">
187
+ <div id="canvas-container" class="dice-container">
188
+ <!-- Three.js canvas will be inserted here -->
189
+ </div>
190
+
191
+ <!-- Dice Table -->
192
+ <div class="bg-gray-900 bg-opacity-70 rounded-lg p-6 shadow-xl border border-gray-800 mt-6">
193
+ <h2 class="text-2xl font-bold mb-4 text-amber-200 border-b border-amber-900 pb-2">Dice Reference</h2>
194
+ <div class="grid grid-cols-2 md:grid-cols-3 gap-4">
195
+ <div class="p-3 bg-gray-800 rounded-lg">
196
+ <div class="flex items-center">
197
+ <div class="w-8 h-8 bg-amber-700 rounded flex items-center justify-center mr-2">4</div>
198
+ <div>
199
+ <div>D4</div>
200
+ <div class="text-xs text-gray-400">Tetrahedron</div>
201
+ </div>
202
+ </div>
203
+ </div>
204
+ <div class="p-3 bg-gray-800 rounded-lg">
205
+ <div class="flex items-center">
206
+ <div class="w-8 h-8 bg-amber-700 rounded flex items-center justify-center mr-2">6</div>
207
+ <div>
208
+ <div>D6</div>
209
+ <div class="text-xs text-gray-400">Cube</div>
210
+ </div>
211
+ </div>
212
+ </div>
213
+ <div class="p-3 bg-gray-800 rounded-lg">
214
+ <div class="flex items-center">
215
+ <div class="w-8 h-8 bg-amber-700 rounded flex items-center justify-center mr-2">8</div>
216
+ <div>
217
+ <div>D8</div>
218
+ <div class="text-xs text-gray-400">Octahedron</div>
219
+ </div>
220
+ </div>
221
+ </div>
222
+ <div class="p-3 bg-gray-800 rounded-lg">
223
+ <div class="flex items-center">
224
+ <div class="w-8 h-8 bg-amber-700 rounded flex items-center justify-center mr-2">10</div>
225
+ <div>
226
+ <div>D10</div>
227
+ <div class="text-xs text-gray-400">Pentagonal Trapezohedron</div>
228
+ </div>
229
+ </div>
230
+ </div>
231
+ <div class="p-3 bg-gray-800 rounded-lg">
232
+ <div class="flex items-center">
233
+ <div class="w-8 h-8 bg-amber-700 rounded flex items-center justify-center mr-2">12</div>
234
+ <div>
235
+ <div>D12</div>
236
+ <div class="text-xs text-gray-400">Dodecahedron</div>
237
+ </div>
238
+ </div>
239
+ </div>
240
+ <div class="p-3 bg-gray-800 rounded-lg">
241
+ <div class="flex items-center">
242
+ <div class="w-8 h-8 bg-amber-700 rounded flex items-center justify-center mr-2">20</div>
243
+ <div>
244
+ <div>D20</div>
245
+ <div class="text-xs text-gray-400">Icosahedron</div>
246
+ </div>
247
+ </div>
248
+ </div>
249
+ </div>
250
+ </div>
251
+ </div>
252
+ </div>
253
+ </div>
254
+
255
+ <script>
256
+ // Initialize variables
257
+ let selectedSides = 20;
258
+ let scene, camera, renderer, world;
259
+ let diceObjects = [];
260
+ let diceMeshes = [];
261
+ const diceValues = {};
262
+
263
+ // Initialize Three.js and Cannon.js
264
+ function init() {
265
+ // Set up Three.js scene
266
+ scene = new THREE.Scene();
267
+ scene.background = new THREE.Color(0x0a0a0a);
268
+
269
+ // Add ambient light
270
+ const ambientLight = new THREE.AmbientLight(0x404040, 1.5);
271
+ scene.add(ambientLight);
272
+
273
+ // Add directional light
274
+ const directionalLight = new THREE.DirectionalLight(0xffffff, 1);
275
+ directionalLight.position.set(1, 1, 1).normalize();
276
+ scene.add(directionalLight);
277
+
278
+ // Add point light
279
+ const pointLight = new THREE.PointLight(0xd4af37, 1, 10);
280
+ pointLight.position.set(0, 3, 2);
281
+ scene.add(pointLight);
282
+
283
+ // Set up camera
284
+ camera = new THREE.PerspectiveCamera(45, document.getElementById('canvas-container').clientWidth / document.getElementById('canvas-container').clientHeight, 0.1, 1000);
285
+ camera.position.set(0, 5, 10);
286
+ camera.lookAt(0, 0, 0);
287
+
288
+ // Set up renderer
289
+ renderer = new THREE.WebGLRenderer({ antialias: true });
290
+ renderer.setSize(document.getElementById('canvas-container').clientWidth, document.getElementById('canvas-container').clientHeight);
291
+ renderer.shadowMap.enabled = true;
292
+ document.getElementById('canvas-container').appendChild(renderer.domElement);
293
+
294
+ // Set up Cannon.js world
295
+ world = new CANNON.World();
296
+ world.gravity.set(0, -9.82, 0);
297
+ world.broadphase = new CANNON.NaiveBroadphase();
298
+ world.solver.iterations = 10;
299
+
300
+ // Create a ground plane
301
+ const groundShape = new CANNON.Plane();
302
+ const groundBody = new CANNON.Body({ mass: 0 });
303
+ groundBody.addShape(groundShape);
304
+ groundBody.quaternion.setFromAxisAngle(new CANNON.Vec3(1, 0, 0), -Math.PI / 2);
305
+ groundBody.position.y = -2;
306
+ world.addBody(groundBody);
307
+
308
+ // Add a ground plane visual
309
+ const groundGeometry = new THREE.PlaneGeometry(20, 20, 1, 1);
310
+ const groundMaterial = new THREE.MeshStandardMaterial({
311
+ color: 0x333333,
312
+ roughness: 0.8,
313
+ metalness: 0.2
314
+ });
315
+ const groundMesh = new THREE.Mesh(groundGeometry, groundMaterial);
316
+ groundMesh.rotation.x = -Math.PI / 2;
317
+ groundMesh.receiveShadow = true;
318
+ scene.add(groundMesh);
319
+
320
+ // Add decorative elements
321
+ addDecorativeElements();
322
+
323
+ // Handle window resize
324
+ window.addEventListener('resize', onWindowResize);
325
+
326
+ // Start animation loop
327
+ animate();
328
+ }
329
+
330
+ // Add decorative elements to the scene
331
+ function addDecorativeElements() {
332
+ // Add some floating runes
333
+ const runeGeometry = new THREE.TetrahedronGeometry(0.3);
334
+ const runeMaterial = new THREE.MeshBasicMaterial({
335
+ color: 0xd4af37,
336
+ transparent: true,
337
+ opacity: 0.3
338
+ });
339
+
340
+ for (let i = 0; i < 10; i++) {
341
+ const rune = new THREE.Mesh(runeGeometry, runeMaterial);
342
+ rune.position.set(
343
+ Math.random() * 10 - 5,
344
+ Math.random() * 5,
345
+ Math.random() * 10 - 5
346
+ );
347
+ scene.add(rune);
348
+ }
349
+
350
+ // Add some floating particles
351
+ const particlesGeometry = new THREE.BufferGeometry();
352
+ const particlesCount = 100;
353
+ const posArray = new Float32Array(particlesCount * 3);
354
+
355
+ for (let i = 0; i < particlesCount * 3; i++) {
356
+ posArray[i] = (Math.random() - 0.5) * 20;
357
+ }
358
+
359
+ particlesGeometry.setAttribute('position', new THREE.BufferAttribute(posArray, 3));
360
+ const particlesMaterial = new THREE.PointsMaterial({
361
+ color: 0x8b5a2b,
362
+ size: 0.05,
363
+ transparent: true,
364
+ opacity: 0.5
365
+ });
366
+ const particlesMesh = new THREE.Points(particlesGeometry, particlesMaterial);
367
+ scene.add(particlesMesh);
368
+ }
369
+
370
+ // Handle window resize
371
+ function onWindowResize() {
372
+ camera.aspect = document.getElementById('canvas-container').clientWidth / document.getElementById('canvas-container').clientHeight;
373
+ camera.updateProjectionMatrix();
374
+ renderer.setSize(document.getElementById('canvas-container').clientWidth, document.getElementById('canvas-container').clientHeight);
375
+ }
376
+
377
+ // Animation loop
378
+ function animate() {
379
+ requestAnimationFrame(animate);
380
+
381
+ // Update physics
382
+ world.step(1/60);
383
+
384
+ // Update dice meshes to match physics bodies
385
+ for (let i = 0; i < diceObjects.length; i++) {
386
+ if (diceObjects[i] && diceMeshes[i]) {
387
+ diceMeshes[i].position.copy(diceObjects[i].position);
388
+ diceMeshes[i].quaternion.copy(diceObjects[i].quaternion);
389
+ }
390
+ }
391
+
392
+ renderer.render(scene, camera);
393
+ }
394
+
395
+ // Create dice geometry based on number of sides
396
+ function createDiceGeometry(sides) {
397
+ let geometry;
398
+
399
+ switch(sides) {
400
+ case 4:
401
+ geometry = new THREE.TetrahedronGeometry(1);
402
+ break;
403
+ case 6:
404
+ geometry = new THREE.BoxGeometry(1.5, 1.5, 1.5);
405
+ break;
406
+ case 8:
407
+ geometry = new THREE.OctahedronGeometry(1);
408
+ break;
409
+ case 10:
410
+ geometry = new THREE.DodecahedronGeometry(1);
411
+ break;
412
+ case 12:
413
+ geometry = new THREE.DodecahedronGeometry(1);
414
+ break;
415
+ case 20:
416
+ geometry = new THREE.IcosahedronGeometry(1);
417
+ break;
418
+ default:
419
+ geometry = new THREE.BoxGeometry(1.5, 1.5, 1.5);
420
+ }
421
+
422
+ return geometry;
423
+ }
424
+
425
+ // Create dice physics body based on number of sides
426
+ function createDiceBody(sides, position) {
427
+ let shape;
428
+
429
+ switch(sides) {
430
+ case 4:
431
+ shape = new CANNON.ConvexPolyhedron({
432
+ vertices: [
433
+ new CANNON.Vec3(1, 1, 1),
434
+ new CANNON.Vec3(-1, -1, 1),
435
+ new CANNON.Vec3(-1, 1, -1),
436
+ new CANNON.Vec3(1, -1, -1)
437
+ ],
438
+ faces: [
439
+ [0, 1, 2],
440
+ [0, 1, 3],
441
+ [0, 2, 3],
442
+ [1, 2, 3]
443
+ ]
444
+ });
445
+ break;
446
+ case 6:
447
+ shape = new CANNON.Box(new CANNON.Vec3(0.75, 0.75, 0.75);
448
+ break;
449
+ case 8:
450
+ shape = new CANNON.ConvexPolyhedron({
451
+ vertices: [
452
+ new CANNON.Vec3(1, 0, 0),
453
+ new CANNON.Vec3(-1, 0, 0),
454
+ new CANNON.Vec3(0, 1, 0),
455
+ new CANNON.Vec3(0, -1, 0),
456
+ new CANNON.Vec3(0, 0, 1),
457
+ new CANNON.Vec3(0, 0, -1)
458
+ ],
459
+ faces: [
460
+ [0, 2, 4], [0, 4, 3], [0, 3, 5], [0, 5, 2],
461
+ [1, 2, 5], [1, 5, 3], [1, 3, 4], [1, 4, 2]
462
+ ]
463
+ });
464
+ break;
465
+ case 10:
466
+ case 12:
467
+ case 20:
468
+ // For simplicity, we'll use a sphere for these
469
+ shape = new CANNON.Sphere(1);
470
+ break;
471
+ default:
472
+ shape = new CANNON.Box(new CANNON.Vec3(0.75, 0.75, 0.75));
473
+ }
474
+
475
+ const body = new CANNON.Body({
476
+ mass: 1,
477
+ shape: shape,
478
+ position: position,
479
+ linearDamping: 0.1,
480
+ angularDamping: 0.1
481
+ });
482
+
483
+ return body;
484
+ }
485
+
486
+ // Create dice material
487
+ function createDiceMaterial(sides) {
488
+ let color;
489
+
490
+ switch(sides) {
491
+ case 4:
492
+ color = 0x8b5a2b; // Brown
493
+ break;
494
+ case 6:
495
+ color = 0x9b111e; // Red
496
+ break;
497
+ case 8:
498
+ color = 0x005b96; // Blue
499
+ break;
500
+ case 10:
501
+ color = 0x228b22; // Green
502
+ break;
503
+ case 12:
504
+ color = 0x800080; // Purple
505
+ break;
506
+ case 20:
507
+ color = 0xd4af37; // Gold
508
+ break;
509
+ default:
510
+ color = 0xffffff; // White
511
+ }
512
+
513
+ return new THREE.MeshStandardMaterial({
514
+ color: color,
515
+ roughness: 0.3,
516
+ metalness: 0.3,
517
+ emissive: 0x000000,
518
+ emissiveIntensity: 0,
519
+ side: THREE.DoubleSide
520
+ });
521
+ }
522
+
523
+ // Add numbers to dice faces
524
+ function addNumbersToDice(mesh, sides) {
525
+ const group = new THREE.Group();
526
+ group.add(mesh);
527
+
528
+ if (sides === 6) {
529
+ // For D6, we'll add numbers to each face
530
+ const numbers = [1, 2, 3, 4, 5, 6];
531
+ const positions = [
532
+ {x: 0, y: 0, z: 0.76, rotation: {x: 0, y: 0, z: 0}}, // front
533
+ {x: 0, y: 0, z: -0.76, rotation: {x: 0, y: Math.PI, z: 0}}, // back
534
+ {x: 0.76, y: 0, z: 0, rotation: {x: 0, y: Math.PI/2, z: 0}}, // right
535
+ {x: -0.76, y: 0, z: 0, rotation: {x: 0, y: -Math.PI/2, z: 0}}, // left
536
+ {x: 0, y: 0.76, z: 0, rotation: {x: -Math.PI/2, y: 0, z: 0}}, // top
537
+ {x: 0, y: -0.76, z: 0, rotation: {x: Math.PI/2, y: 0, z: 0}} // bottom
538
+ ];
539
+
540
+ for (let i = 0; i < 6; i++) {
541
+ const number = numbers[i];
542
+ const pos = positions[i];
543
+
544
+ const textGeometry = new THREE.TextGeometry(number.toString(), {
545
+ size: 0.4,
546
+ height: 0.01,
547
+ font: 'helvetiker'
548
+ });
549
+
550
+ const textMaterial = new THREE.MeshBasicMaterial({ color: 0xffffff });
551
+ const textMesh = new THREE.Mesh(textGeometry, textMaterial);
552
+
553
+ textMesh.position.set(pos.x, pos.y, pos.z);
554
+ textMesh.rotation.set(pos.rotation.x, pos.rotation.y, pos.rotation.z);
555
+
556
+ group.add(textMesh);
557
+ }
558
+ }
559
+
560
+ return group;
561
+ }
562
+
563
+ // Roll the dice
564
+ function rollDice() {
565
+ // Clear previous dice
566
+ clearDice();
567
+
568
+ const quantity = parseInt(document.getElementById('diceQuantity').value) || 1;
569
+ const modifier = parseInt(document.getElementById('diceModifier').value) || 0;
570
+
571
+ // Create new dice
572
+ for (let i = 0; i < quantity; i++) {
573
+ const x = (Math.random() - 0.5) * 2;
574
+ const z = (Math.random() - 0.5) * 2;
575
+ const position = new CANNON.Vec3(x, 5 + i * 0.5, z);
576
+
577
+ // Create physics body
578
+ const body = createDiceBody(selectedSides, position);
579
+
580
+ // Apply random force
581
+ body.velocity.set(
582
+ (Math.random() - 0.5) * 10,
583
+ Math.random() * 5,
584
+ (Math.random() - 0.5) * 10
585
+ );
586
+
587
+ body.angularVelocity.set(
588
+ (Math.random() - 0.5) * 10,
589
+ (Math.random() - 0.5) * 10,
590
+ (Math.random() - 0.5) * 10
591
+ );
592
+
593
+ world.addBody(body);
594
+ diceObjects.push(body);
595
+
596
+ // Create visual mesh
597
+ const geometry = createDiceGeometry(selectedSides);
598
+ const material = createDiceMaterial(selectedSides);
599
+ const mesh = new THREE.Mesh(geometry, material);
600
+ mesh.castShadow = true;
601
+
602
+ // Add numbers to dice if it's a D6
603
+ let diceGroup;
604
+ if (selectedSides === 6) {
605
+ diceGroup = addNumbersToDice(mesh, selectedSides);
606
+ scene.add(diceGroup);
607
+ } else {
608
+ scene.add(mesh);
609
+ diceGroup = mesh;
610
+ }
611
+
612
+ diceMeshes.push(diceGroup);
613
+
614
+ // Store dice info
615
+ diceValues[i] = {
616
+ body: body,
617
+ mesh: diceGroup,
618
+ value: null,
619
+ sides: selectedSides
620
+ };
621
+ }
622
+
623
+ // Wait for dice to settle and get results
624
+ setTimeout(() => {
625
+ getDiceResults(quantity, modifier);
626
+ }, 3000);
627
+ }
628
+
629
+ // Clear all dice from scene
630
+ function clearDice() {
631
+ // Remove physics bodies
632
+ for (const body of diceObjects) {
633
+ world.removeBody(body);
634
+ }
635
+
636
+ // Remove visual meshes
637
+ for (const mesh of diceMeshes) {
638
+ scene.remove(mesh);
639
+ }
640
+
641
+ diceObjects = [];
642
+ diceMeshes = [];
643
+ diceValues = {};
644
+ }
645
+
646
+ // Get results after dice have settled
647
+ function getDiceResults(quantity, modifier) {
648
+ const results = [];
649
+ let allCritical = true;
650
+
651
+ for (let i = 0; i < quantity; i++) {
652
+ let value;
653
+
654
+ // For simplicity, we'll just generate a random number
655
+ // In a real implementation, you'd detect the face that's up
656
+ value = Math.floor(Math.random() * selectedSides) + 1;
657
+
658
+ // Check for critical (20 or 1)
659
+ if (selectedSides === 20) {
660
+ if (value !== 20 && value !== 1) {
661
+ allCritical = false;
662
+ }
663
+ } else {
664
+ allCritical = false;
665
+ }
666
+
667
+ diceValues[i].value = value;
668
+ results.push(value);
669
+ }
670
+
671
+ // Calculate total
672
+ const sum = results.reduce((a, b) => a + b, 0);
673
+ const total = sum + modifier;
674
+
675
+ // Display results
676
+ displayResults(results, total, modifier, allCritical);
677
+
678
+ // Add to history
679
+ addToHistory(quantity, selectedSides, results, modifier, total);
680
+ }
681
+
682
+ // Display results
683
+ function displayResults(results, total, modifier, isCritical) {
684
+ const resultDisplay = document.getElementById('resultDisplay');
685
+ const totalResult = document.getElementById('totalResult');
686
+ const individualResults = document.getElementById('individualResults');
687
+ const criticalText = document.getElementById('criticalText');
688
+
689
+ resultDisplay.classList.remove('hidden');
690
+ totalResult.textContent = total;
691
+
692
+ // Show individual results
693
+ let individualText = `(${results.join(' + ')})`;
694
+ if (modifier !== 0) {
695
+ if (modifier > 0) {
696
+ individualText += ` + ${modifier}`;
697
+ } else {
698
+ individualText += ` - ${Math.abs(modifier)}`;
699
+ }
700
+ }
701
+ individualResults.textContent = individualText;
702
+
703
+ // Show critical text if applicable
704
+ if (isCritical) {
705
+ criticalText.classList.remove('hidden');
706
+ if (results[0] === 20) {
707
+ criticalText.textContent = "CRITICAL HIT!";
708
+ criticalText.className = "mt-2 text-xl font-bold text-green-400";
709
+ } else if (results[0] === 1) {
710
+ criticalText.textContent = "CRITICAL FAIL!";
711
+ criticalText.className = "mt-2 text-xl font-bold text-red-400";
712
+ }
713
+ } else {
714
+ criticalText.classList.add('hidden');
715
+ }
716
+ }
717
+
718
+ // Add roll to history
719
+ function addToHistory(quantity, sides, results, modifier, total) {
720
+ const historyContainer = document.getElementById('rollHistory');
721
+ const historyItem = document.createElement('div');
722
+ historyItem.className = 'roll-history-item p-3 bg-gray-800 rounded-lg';
723
+
724
+ let modifierText = '';
725
+ if (modifier > 0) {
726
+ modifierText = ` + ${modifier}`;
727
+ } else if (modifier < 0) {
728
+ modifierText = ` - ${Math.abs(modifier)}`;
729
+ }
730
+
731
+ let resultText = '';
732
+ if (quantity > 1) {
733
+ resultText = `(${results.join(' + ')})${modifierText} = ${total}`;
734
+ } else {
735
+ resultText = `${results[0]}${modifierText} = ${total}`;
736
+ }
737
+
738
+ historyItem.innerHTML = `
739
+ <div class="flex justify-between items-center">
740
+ <div>
741
+ <span class="font-bold text-amber-200">${quantity}d${sides}</span>
742
+ <span class="text-gray-400 ml-2">${resultText}</span>
743
+ </div>
744
+ <div class="text-xs text-gray-500">${new Date().toLocaleTimeString()}</div>
745
+ </div>
746
+ `;
747
+
748
+ historyContainer.insertBefore(historyItem, historyContainer.firstChild);
749
+
750
+ // Limit history to 20 items
751
+ if (historyContainer.children.length > 20) {
752
+ historyContainer.removeChild(historyContainer.lastChild);
753
+ }
754
+ }
755
+
756
+ // Initialize the app when DOM is loaded
757
+ document.addEventListener('DOMContentLoaded', () => {
758
+ init();
759
+
760
+ // Set up dice selection
761
+ const diceSelectors = document.querySelectorAll('.dice-selector');
762
+ diceSelectors.forEach(selector => {
763
+ selector.addEventListener('click', () => {
764
+ diceSelectors.forEach(s => s.classList.remove('active'));
765
+ selector.classList.add('active');
766
+ selectedSides = parseInt(selector.dataset.sides);
767
+ });
768
+ });
769
+
770
+ // Default to d20 selected
771
+ document.querySelector('.dice-selector[data-sides="20"]').classList.add('active');
772
+
773
+ // Set up roll button
774
+ document.getElementById('rollButton').addEventListener('click', rollDice);
775
+ });
776
+ </script>
777
+ <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 <img src="https://enzostvs-deepsite.hf.space/logo.svg" alt="DeepSite Logo" style="width: 16px; height: 16px; vertical-align: middle;display:inline-block;margin-right:3px;filter:brightness(0) invert(1);"><a href="https://enzostvs-deepsite.hf.space" style="color: #fff;text-decoration: underline;" target="_blank" >DeepSite</a> - 🧬 <a href="https://enzostvs-deepsite.hf.space?remix=dricampbell/d-d-dice" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body>
778
+ </html>