+
+
+
+ Würfel
+
+
+ Klicken zum Rollen
+
+
- onRoll?.({ d1: nd1, d2: nd2, special: ns });
- }, 820); // muss zur CSS Animation passen
- };
-
- return (
-
-
-
-
- Würfel
-
-
- Klicken zum Rollen
+ {rolling ? "ROLL…" : "READY"}
- {rolling ? "ROLL…" : "READY"}
+
+
+
+
+
+
+ );
+ };
+
+ const DieShell = ({ children, ringColor = "rgba(255,255,255,0.10)", rolling = false, onClick }) => {
+ return (
+
+ );
+ };
+
+ /* map value->cube rotation so that value face is FRONT
+ Face layout:
+ front=1, back=6, top=2, bottom=5, right=3, left=4
+ */
+ const cubeRotationForD6 = (value) => {
+ switch (value) {
+ case 1: return { rx: "0deg", ry: "0deg" };
+ case 2: return { rx: "-90deg", ry: "0deg" };
+ case 3: return { rx: "0deg", ry: "-90deg" };
+ case 4: return { rx: "0deg", ry: "90deg" };
+ case 5: return { rx: "90deg", ry: "0deg" };
+ case 6: return { rx: "0deg", ry: "180deg" };
+ default: return { rx: "0deg", ry: "0deg" };
+ }
+ };
+
+ const PipFace = ({ value }) => {
+ // pip positions: 0..2 grid
+ const pos = (gx, gy) => ({ left: `${gx * 50}%`, top: `${gy * 50}%` });
+
+ const faces = {
+ 1: [[1, 1]],
+ 2: [[0, 0], [2, 2]],
+ 3: [[0, 0], [1, 1], [2, 2]],
+ 4: [[0, 0], [2, 0], [0, 2], [2, 2]],
+ 5: [[0, 0], [2, 0], [1, 1], [0, 2], [2, 2]],
+ 6: [[0, 0], [0, 1], [0, 2], [2, 0], [2, 1], [2, 2]],
+ };
+
+ const arr = faces[value] || faces[1];
+
+ return (
+
+ {arr.map(([x, y], idx) => (
+
+ ))}
+
+ );
+ };
+
+ const DieD6 = ({ value = 1, rolling = false, onClick }) => {
+ const rot = cubeRotationForD6(value);
+
+ return (
+
+
+
+ {/* faces show correct pips like a real die */}
+
+
+
+
+
+
-
-
-
+ d6
-
-
- );
-};
-
-const DieShell = ({ children, ringColor = "rgba(255,255,255,0.10)", rolling = false, onClick }) => {
- return (
-
- );
-};
-
-const DieD6 = ({ value = 1, rolling = false, onClick }) => {
- const P = ({ x, y }) => (
-
- );
-
- const faces = {
- 1: [{ x: 1, y: 1 }],
- 2: [{ x: 0, y: 0 }, { x: 2, y: 2 }],
- 3: [{ x: 0, y: 0 }, { x: 1, y: 1 }, { x: 2, y: 2 }],
- 4: [{ x: 0, y: 0 }, { x: 2, y: 0 }, { x: 0, y: 2 }, { x: 2, y: 2 }],
- 5: [{ x: 0, y: 0 }, { x: 2, y: 0 }, { x: 1, y: 1 }, { x: 0, y: 2 }, { x: 2, y: 2 }],
- 6: [{ x: 0, y: 0 }, { x: 0, y: 1 }, { x: 0, y: 2 }, { x: 2, y: 0 }, { x: 2, y: 1 }, { x: 2, y: 2 }],
+
+ );
};
- return (
-
-
- {(faces[value] || faces[1]).map((p, idx) => (
-
- ))}
-
+ const HouseDie = ({ face = "gryffindor", rolling = false, onClick }) => {
+ // icon instead of letters
+ const faces = {
+ gryffindor: { icon: "🦁", color: "#ef4444", sub: "Gryff." },
+ slytherin: { icon: "🐍", color: "#22c55e", sub: "Slyth." },
+ ravenclaw: { icon: "🦅", color: "#3b82f6", sub: "Raven." },
+ hufflepuff: { icon: "🦡", color: "#facc15", sub: "Huff." },
+ help: { icon: "🃏", color: "#f2d27a", sub: "Hilf" },
+ dark: { icon: "🌙", color: "rgba(255,255,255,0.85)", sub: "Dark" },
+ };
-
- d6
-
-
- );
-};
+ const order = ["gryffindor", "slytherin", "ravenclaw", "hufflepuff", "help", "dark"];
+ const idx = Math.max(0, order.indexOf(face));
+ const value = idx + 1; // 1..6
+ const rot = cubeRotationForD6(value);
-const HouseDie = ({ face = "gryffindor", rolling = false, onClick }) => {
- const faces = {
- gryffindor: { label: "G", color: "#ef4444", sub: "Gryff." },
- slytherin: { label: "S", color: "#22c55e", sub: "Slyth." },
- ravenclaw: { label: "R", color: "#3b82f6", sub: "Raven." },
- hufflepuff: { label: "H", color: "#facc15", sub: "Huff." },
- help: { label: "?", color: "#f2d27a", sub: "Hilf" },
- dark: { label: "☾", color: "rgba(255,255,255,0.78)", sub: "Dark" },
- };
+ const f = faces[face] || faces.gryffindor;
+ const ring = `${f.color}55`;
- const f = faces[face] || faces.gryffindor;
+ return (
+
+
+
+ {/* order mapped to 6 faces */}
+ {order.map((key, i) => {
+ const item = faces[key];
+ const faceClass =
+ i === 0 ? "front" :
+ i === 1 ? "top" :
+ i === 2 ? "right" :
+ i === 3 ? "left" :
+ i === 4 ? "bottom" :
+ "back";
+
+ return (
+
+ );
+ })}
+
+
- return (
-
-
-
-
- Spezial
-
-
- );
-};
+
+ );
+ };
+
const PlayerIdentityCard = ({
diff --git a/frontend/src/AppLayout.css b/frontend/src/AppLayout.css
index 172d751..5062cfd 100644
--- a/frontend/src/AppLayout.css
+++ b/frontend/src/AppLayout.css
@@ -176,11 +176,7 @@ body {
opacity: 0.98;
}
-/* --- Dice 3D feel --- */
-.diceRow3d {
- perspective: 900px;
-}
-
+/* ===== True 3D Cube Dice ===== */
.die3d {
transform-style: preserve-3d;
will-change: transform;
@@ -190,6 +186,94 @@ body {
transform: translateY(-3px) rotateX(8deg) rotateY(-10deg);
}
+/* Cube container inside the button */
+.dieCubeWrap {
+ width: 44px;
+ height: 44px;
+ position: relative;
+ transform-style: preserve-3d;
+}
+
+/* actual cube */
+.dieCube {
+ width: 44px;
+ height: 44px;
+ position: absolute;
+ inset: 0;
+ transform-style: preserve-3d;
+ transform: rotateX(var(--rx, 0deg)) rotateY(var(--ry, 0deg));
+ transition: transform 260ms cubic-bezier(0.22, 1.0, 0.25, 1);
+}
+
+/* rolling animation (spins, then we snap by updating --rx/--ry) */
+@keyframes cubeRoll {
+ 0% { transform: rotateX(var(--rx, 0deg)) rotateY(var(--ry, 0deg)); }
+ 100% {
+ transform:
+ rotateX(calc(var(--rx, 0deg) + 720deg))
+ rotateY(calc(var(--ry, 0deg) + 540deg));
+ }
+}
+
+.dieCube.rolling {
+ animation: cubeRoll 820ms cubic-bezier(0.18, 0.88, 0.22, 1) both;
+}
+
+/* one face */
+.dieFace {
+ position: absolute;
+ inset: 0;
+ border-radius: 12px;
+ border: 1px solid rgba(255,255,255,0.10);
+ background:
+ radial-gradient(120% 120% at 25% 18%, rgba(255,255,255,0.14), rgba(0,0,0,0.40) 60%, rgba(0,0,0,0.72));
+ box-shadow:
+ inset 0 0 0 1px rgba(255,255,255,0.06),
+ inset 0 12px 20px rgba(255,255,255,0.05);
+ display: grid;
+ place-items: center;
+ backface-visibility: hidden;
+}
+
+/* face positions */
+.dieFace.front { transform: rotateY( 0deg) translateZ(22px); }
+.dieFace.back { transform: rotateY(180deg) translateZ(22px); }
+.dieFace.right { transform: rotateY( 90deg) translateZ(22px); }
+.dieFace.left { transform: rotateY(-90deg) translateZ(22px); }
+.dieFace.top { transform: rotateX( 90deg) translateZ(22px); }
+.dieFace.bottom { transform: rotateX(-90deg) translateZ(22px); }
+
+/* pips */
+.pipGrid {
+ width: 70%;
+ height: 70%;
+ position: relative;
+}
+
+.pip {
+ position: absolute;
+ width: 7px;
+ height: 7px;
+ border-radius: 999px;
+ background: rgba(245,245,245,0.92);
+ box-shadow: 0 3px 10px rgba(0,0,0,0.35), inset 0 1px 2px rgba(0,0,0,0.20);
+}
+
+/* special icon */
+.specialIcon {
+ font-size: 20px;
+ line-height: 1;
+ filter: drop-shadow(0 10px 18px rgba(0,0,0,0.55));
+}
+
+/* optional: subtle face ring tint via CSS var */
+.dieFace {
+ box-shadow:
+ inset 0 0 0 1px rgba(255,255,255,0.06),
+ inset 0 0 0 2px var(--ring, rgba(255,255,255,0.00)),
+ inset 0 12px 20px rgba(255,255,255,0.05);
+}
+
/* Roll animation */
@keyframes dieRoll {
0% {