Add interactive dice panel with custom components
This commit introduces a new DicePanel component, complete with custom dice (DieD6 and HouseDie) for enhanced interactivity. The panel is integrated into the UI with responsive styling, hover effects, and animations, providing a polished and functional user experience. Styles and layout adjustments in `AppLayout.css` support the new feature.
This commit is contained in:
@@ -541,6 +541,261 @@ export default function App() {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// App.jsx (füge diese Komponente irgendwo unter deinen anderen Komponenten ein)
|
||||||
|
// ✅ 3 Würfel: 2x d6 + 1x Spezial (Häuser + Hilfkarte + Dunkles Deck)
|
||||||
|
|
||||||
|
const DicePanel = () => {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
pointerEvents: "auto", // overlay ist sonst pointer-events none im CSS
|
||||||
|
display: "grid",
|
||||||
|
gap: 10,
|
||||||
|
alignContent: "start",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
borderRadius: 18,
|
||||||
|
border: `1px solid ${stylesTokens.panelBorder}`,
|
||||||
|
background: stylesTokens.panelBg,
|
||||||
|
boxShadow: "0 12px 30px rgba(0,0,0,0.35)",
|
||||||
|
backdropFilter: "blur(10px)",
|
||||||
|
padding: 12,
|
||||||
|
overflow: "visible",
|
||||||
|
minWidth: 0,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
fontWeight: 900,
|
||||||
|
color: stylesTokens.textMain,
|
||||||
|
fontSize: 13,
|
||||||
|
letterSpacing: 0.2,
|
||||||
|
lineHeight: 1.15,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Würfel
|
||||||
|
</div>
|
||||||
|
<div style={{ marginTop: 6, color: stylesTokens.textDim, fontSize: 12, opacity: 0.95 }}>
|
||||||
|
2× d6 + 1× Spezial
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
marginTop: 10,
|
||||||
|
display: "grid",
|
||||||
|
gridTemplateColumns: "repeat(3, 1fr)",
|
||||||
|
gap: 10,
|
||||||
|
alignItems: "center",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<DieD6 value={4} />
|
||||||
|
<DieD6 value={2} />
|
||||||
|
<HouseDie face="gryffindor" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const DieShell = ({ children, ringColor = "rgba(255,255,255,0.10)" }) => {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
width: 62,
|
||||||
|
height: 62,
|
||||||
|
borderRadius: 18,
|
||||||
|
border: `1px solid ${stylesTokens.panelBorder}`,
|
||||||
|
background:
|
||||||
|
"radial-gradient(120% 120% at 20% 10%, rgba(255,255,255,0.10), rgba(0,0,0,0.35) 55%, rgba(0,0,0,0.55))",
|
||||||
|
boxShadow: "0 18px 50px rgba(0,0,0,0.55), inset 0 0 0 1px rgba(255,255,255,0.06)",
|
||||||
|
position: "relative",
|
||||||
|
overflow: "hidden",
|
||||||
|
display: "grid",
|
||||||
|
placeItems: "center",
|
||||||
|
cursor: "pointer",
|
||||||
|
transition: "transform 160ms ease, box-shadow 160ms ease",
|
||||||
|
}}
|
||||||
|
onMouseEnter={(e) => {
|
||||||
|
e.currentTarget.style.transform = "translateY(-3px) rotate(-1deg)";
|
||||||
|
e.currentTarget.style.boxShadow =
|
||||||
|
"0 26px 70px rgba(0,0,0,0.65), inset 0 0 0 1px rgba(255,255,255,0.08)";
|
||||||
|
}}
|
||||||
|
onMouseLeave={(e) => {
|
||||||
|
e.currentTarget.style.transform = "translateY(0px) rotate(0deg)";
|
||||||
|
e.currentTarget.style.boxShadow =
|
||||||
|
"0 18px 50px rgba(0,0,0,0.55), inset 0 0 0 1px rgba(255,255,255,0.06)";
|
||||||
|
}}
|
||||||
|
title="Würfel (placeholder)"
|
||||||
|
>
|
||||||
|
{/* subtle inner ring */}
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
position: "absolute",
|
||||||
|
inset: 7,
|
||||||
|
borderRadius: 14,
|
||||||
|
border: `1px solid ${ringColor}`,
|
||||||
|
opacity: 0.85,
|
||||||
|
pointerEvents: "none",
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* gloss */}
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
position: "absolute",
|
||||||
|
inset: 0,
|
||||||
|
background:
|
||||||
|
"linear-gradient(120deg, rgba(255,255,255,0.10) 0%, transparent 38%, transparent 70%, rgba(255,255,255,0.06) 100%)",
|
||||||
|
opacity: 0.75,
|
||||||
|
pointerEvents: "none",
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const DieD6 = ({ value = 1 }) => {
|
||||||
|
// pip layout positions on 3x3 grid
|
||||||
|
const P = ({ x, y }) => (
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
position: "absolute",
|
||||||
|
left: `${x * 50}%`,
|
||||||
|
top: `${y * 50}%`,
|
||||||
|
transform: "translate(-50%, -50%)",
|
||||||
|
width: 7.5,
|
||||||
|
height: 7.5,
|
||||||
|
borderRadius: 999,
|
||||||
|
background: "rgba(245,245,245,0.92)",
|
||||||
|
boxShadow: "0 3px 10px rgba(0,0,0,0.35)",
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
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 (
|
||||||
|
<DieShell ringColor="rgba(242,210,122,0.18)">
|
||||||
|
<div style={{ width: 44, height: 44, position: "relative" }}>
|
||||||
|
{(faces[value] || faces[1]).map((p, idx) => (
|
||||||
|
<P key={idx} x={p.x} y={p.y} />
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* tiny label */}
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
position: "absolute",
|
||||||
|
bottom: 6,
|
||||||
|
right: 8,
|
||||||
|
fontSize: 10,
|
||||||
|
fontWeight: 900,
|
||||||
|
color: "rgba(255,255,255,0.55)",
|
||||||
|
letterSpacing: 0.6,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
d6
|
||||||
|
</div>
|
||||||
|
</DieShell>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const HouseDie = ({ face = "gryffindor" }) => {
|
||||||
|
// 4 Häuser + Hilfkarte + Dunkles Deck
|
||||||
|
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.70)", sub: "Dark" },
|
||||||
|
};
|
||||||
|
|
||||||
|
const f = faces[face] || faces.gryffindor;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<DieShell ringColor={`${f.color}55`}>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
width: 44,
|
||||||
|
height: 44,
|
||||||
|
borderRadius: 14,
|
||||||
|
display: "grid",
|
||||||
|
placeItems: "center",
|
||||||
|
background:
|
||||||
|
"radial-gradient(120% 120% at 30% 20%, rgba(255,255,255,0.10), rgba(0,0,0,0.35) 70%, rgba(0,0,0,0.55))",
|
||||||
|
border: `1px solid rgba(255,255,255,0.08)`,
|
||||||
|
boxShadow: `inset 0 0 0 1px ${f.color}22`,
|
||||||
|
position: "relative",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
fontWeight: 1000,
|
||||||
|
fontSize: 22,
|
||||||
|
color: f.color,
|
||||||
|
textShadow: `0 0 16px ${f.color}55, 0 8px 24px rgba(0,0,0,0.55)`,
|
||||||
|
letterSpacing: 0.5,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{f.label}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
position: "absolute",
|
||||||
|
bottom: 6,
|
||||||
|
left: 10,
|
||||||
|
fontSize: 10,
|
||||||
|
fontWeight: 900,
|
||||||
|
color: "rgba(255,255,255,0.55)",
|
||||||
|
letterSpacing: 0.6,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Spezial
|
||||||
|
</div>
|
||||||
|
</DieShell>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
const PlayerIdentityCard = ({
|
const PlayerIdentityCard = ({
|
||||||
name = "Harry Potter",
|
name = "Harry Potter",
|
||||||
houseLabel = "Gryffindor",
|
houseLabel = "Gryffindor",
|
||||||
@@ -761,7 +1016,7 @@ export default function App() {
|
|||||||
</PlaceholderCard>
|
</PlaceholderCard>
|
||||||
|
|
||||||
<div className="diceOverlay">
|
<div className="diceOverlay">
|
||||||
<PlaceholderCard title="Würfel" variant="compact" />
|
<DicePanel />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -167,6 +167,15 @@ body {
|
|||||||
padding-right: 4px;
|
padding-right: 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.diceOverlay {
|
||||||
|
position: absolute;
|
||||||
|
bottom: 14px;
|
||||||
|
right: 18px;
|
||||||
|
width: 220px;
|
||||||
|
pointer-events: auto; /* ✅ damit Hover/Clicks für Würfel gehen */
|
||||||
|
opacity: 0.98;
|
||||||
|
}
|
||||||
|
|
||||||
/* --- Responsive: shrink gracefully (no scroll outside notes) --- */
|
/* --- Responsive: shrink gracefully (no scroll outside notes) --- */
|
||||||
@media (max-height: 860px) {
|
@media (max-height: 860px) {
|
||||||
.leftPane {
|
.leftPane {
|
||||||
|
|||||||
Reference in New Issue
Block a user