Add existing to tracked
This commit is contained in:
@@ -0,0 +1,232 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Side Cards — Craps Throw Lab</title>
|
||||
<style>
|
||||
* { box-sizing: border-box; margin: 0; padding: 0; }
|
||||
|
||||
body {
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 20px;
|
||||
padding: 24px;
|
||||
color: #fff;
|
||||
background: radial-gradient(ellipse at center, #0b6b3a 0%, #074d28 100%);
|
||||
font-family: Georgia, "Times New Roman", serif;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 26px;
|
||||
letter-spacing: 1px;
|
||||
text-shadow: 0 2px 6px rgba(0,0,0,.5);
|
||||
}
|
||||
|
||||
.dice-stage {
|
||||
position: relative;
|
||||
width: min(700px, calc(100vw - 24px));
|
||||
aspect-ratio: 14 / 9;
|
||||
}
|
||||
|
||||
#dice-canvas {
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.loading {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
display: grid;
|
||||
place-items: center;
|
||||
color: rgba(255,255,255,.65);
|
||||
font-size: 11px;
|
||||
letter-spacing: 2px;
|
||||
text-transform: uppercase;
|
||||
pointer-events: none;
|
||||
transition: opacity .18s;
|
||||
}
|
||||
|
||||
.dice-stage.ready .loading { opacity: 0; }
|
||||
|
||||
.readout {
|
||||
min-height: 62px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.total { font-size: 34px; letter-spacing: 2px; }
|
||||
.detail { font-size: 13px; letter-spacing: 3px; text-transform: uppercase; opacity: .7; }
|
||||
.tally { font-size: 11px; letter-spacing: 1.5px; opacity: .55; font-variant-numeric: tabular-nums; }
|
||||
.controls { display: flex; gap: 12px; }
|
||||
|
||||
.force-control {
|
||||
width: min(420px, calc(100vw - 48px));
|
||||
display: grid;
|
||||
gap: 7px;
|
||||
}
|
||||
|
||||
.force-heading,
|
||||
.force-scale {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: baseline;
|
||||
}
|
||||
|
||||
.force-heading {
|
||||
font-size: 12px;
|
||||
letter-spacing: 2px;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
#force-label { letter-spacing: 1px; opacity: .72; }
|
||||
|
||||
#force {
|
||||
width: 100%;
|
||||
accent-color: #f4f0e7;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
#force[disabled] { cursor: default; opacity: .55; }
|
||||
|
||||
.force-scale {
|
||||
font-size: 9px;
|
||||
letter-spacing: 1.5px;
|
||||
text-transform: uppercase;
|
||||
opacity: .48;
|
||||
}
|
||||
|
||||
.controls button {
|
||||
padding: 11px 24px;
|
||||
border: 1px solid rgba(255,255,255,.35);
|
||||
border-radius: 8px;
|
||||
color: #fff;
|
||||
background: rgba(255,255,255,.12);
|
||||
font: inherit;
|
||||
font-size: 14px;
|
||||
letter-spacing: 1px;
|
||||
cursor: pointer;
|
||||
transition: background .15s, transform .1s;
|
||||
}
|
||||
|
||||
.controls button:hover { background: rgba(255,255,255,.22); }
|
||||
.controls button:active { transform: translateY(1px); }
|
||||
.controls button[disabled] { opacity: .45; cursor: default; transform: none; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Craps Throw Lab</h1>
|
||||
|
||||
<div class="dice-stage" id="stage">
|
||||
<canvas id="dice-canvas" aria-label="Two red dice in a craps-style tray"></canvas>
|
||||
<div class="loading" id="loading">loading dice</div>
|
||||
</div>
|
||||
|
||||
<div class="force-control">
|
||||
<div class="force-heading">
|
||||
<label for="force">Throw force</label>
|
||||
<output id="force-label" for="force">Standard</output>
|
||||
</div>
|
||||
<input id="force" type="range" min="0" max="100" value="50" step="1">
|
||||
<div class="force-scale" aria-hidden="true"><span>Gentle</span><span>Standard</span><span>Hard</span></div>
|
||||
</div>
|
||||
|
||||
<div class="readout">
|
||||
<div class="total" id="total">—</div>
|
||||
<div class="detail" id="detail">ready</div>
|
||||
<div class="tally" id="tally"></div>
|
||||
</div>
|
||||
|
||||
<div class="controls">
|
||||
<button id="throw" disabled>Throw Dice</button>
|
||||
<button id="reset">Reset Tally</button>
|
||||
</div>
|
||||
|
||||
<script src="dice-renderer.bundle.js"></script>
|
||||
<script>
|
||||
const stage = document.getElementById("stage");
|
||||
const canvas = document.getElementById("dice-canvas");
|
||||
const total = document.getElementById("total");
|
||||
const detail = document.getElementById("detail");
|
||||
const tallyText = document.getElementById("tally");
|
||||
const throwButton = document.getElementById("throw");
|
||||
const forceSlider = document.getElementById("force");
|
||||
const forceLabel = document.getElementById("force-label");
|
||||
const tally = new Array(7).fill(0);
|
||||
let rolls = 0;
|
||||
let tray;
|
||||
|
||||
function forceName(value) {
|
||||
if (value < 34) return "Gentle";
|
||||
if (value > 72) return "Hard";
|
||||
return "Standard";
|
||||
}
|
||||
|
||||
function updateForce() {
|
||||
forceLabel.textContent = forceName(Number(forceSlider.value));
|
||||
if (tray && !tray.isBusy()) tray.setThrowForce(forceSlider.value);
|
||||
}
|
||||
|
||||
function updateTally(values) {
|
||||
values.forEach((value) => tally[value]++);
|
||||
rolls += values.length;
|
||||
const parts = [];
|
||||
for (let face = 1; face <= 6; face++) {
|
||||
parts.push(`${face}: ${(100 * tally[face] / rolls).toFixed(1)}%`);
|
||||
}
|
||||
tallyText.textContent = `${rolls} faces — ${parts.join(" ")}`;
|
||||
}
|
||||
|
||||
async function throwDice() {
|
||||
if (!tray || tray.isBusy()) return;
|
||||
throwButton.disabled = true;
|
||||
forceSlider.disabled = true;
|
||||
detail.textContent = "rolling";
|
||||
total.textContent = "··";
|
||||
const values = await tray.roll();
|
||||
if (values) {
|
||||
total.textContent = values[0] + values[1];
|
||||
detail.textContent = `${values[0]} + ${values[1]}`;
|
||||
updateTally(values);
|
||||
}
|
||||
throwButton.disabled = false;
|
||||
forceSlider.disabled = false;
|
||||
}
|
||||
|
||||
Dice3D.mount(canvas, 2, { scene: "craps" }).then((mounted) => {
|
||||
tray = mounted;
|
||||
updateForce();
|
||||
stage.classList.add("ready");
|
||||
throwButton.disabled = false;
|
||||
if (new URLSearchParams(location.search).get("autoroll") === "1") {
|
||||
setTimeout(throwDice, 100);
|
||||
}
|
||||
}).catch((error) => {
|
||||
console.error(error);
|
||||
document.getElementById("loading").textContent = "3D dice unavailable";
|
||||
detail.textContent = "renderer error";
|
||||
});
|
||||
|
||||
throwButton.addEventListener("click", throwDice);
|
||||
forceSlider.addEventListener("input", updateForce);
|
||||
document.getElementById("reset").addEventListener("click", () => {
|
||||
tally.fill(0);
|
||||
rolls = 0;
|
||||
tallyText.textContent = "";
|
||||
});
|
||||
window.addEventListener("keydown", (event) => {
|
||||
if (event.code === "Space" || event.code === "Enter") {
|
||||
event.preventDefault();
|
||||
throwDice();
|
||||
}
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user