Add player rail, dice overlay, and compact card support
This update introduces a player rail to the right of the board, visually representing active and inactive players, and adds a dice overlay beneath the board. Compact variants for placeholder cards were added, with corresponding adjustments to layouts and styles for better responsiveness and a cleaner interface.
This commit is contained in:
@@ -34,7 +34,6 @@ export default function App() {
|
|||||||
const [chipOpen, setChipOpen] = useState(false);
|
const [chipOpen, setChipOpen] = useState(false);
|
||||||
const [chipEntry, setChipEntry] = useState(null);
|
const [chipEntry, setChipEntry] = useState(null);
|
||||||
|
|
||||||
// Live refresh (optional)
|
|
||||||
const aliveRef = useRef(true);
|
const aliveRef = useRef(true);
|
||||||
|
|
||||||
const load = async () => {
|
const load = async () => {
|
||||||
@@ -229,7 +228,7 @@ export default function App() {
|
|||||||
]
|
]
|
||||||
: [];
|
: [];
|
||||||
|
|
||||||
const PlaceholderCard = ({ title, hint }) => (
|
const PlaceholderCard = ({ title, hint, compact = false }) => (
|
||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
borderRadius: 18,
|
borderRadius: 18,
|
||||||
@@ -237,20 +236,23 @@ export default function App() {
|
|||||||
background: stylesTokens.panelBg,
|
background: stylesTokens.panelBg,
|
||||||
boxShadow: "0 12px 30px rgba(0,0,0,0.35)",
|
boxShadow: "0 12px 30px rgba(0,0,0,0.35)",
|
||||||
backdropFilter: "blur(8px)",
|
backdropFilter: "blur(8px)",
|
||||||
padding: 12,
|
padding: compact ? 10 : 12,
|
||||||
overflow: "hidden",
|
overflow: "hidden",
|
||||||
|
minWidth: 0,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div style={{ fontWeight: 900, color: stylesTokens.textMain, fontSize: 13 }}>
|
<div style={{ fontWeight: 900, color: stylesTokens.textMain, fontSize: 13 }}>
|
||||||
{title}
|
{title}
|
||||||
</div>
|
</div>
|
||||||
<div style={{ marginTop: 6, color: stylesTokens.textDim, fontSize: 12, opacity: 0.95 }}>
|
{hint ? (
|
||||||
{hint}
|
<div style={{ marginTop: 6, color: stylesTokens.textDim, fontSize: 12, opacity: 0.95 }}>
|
||||||
</div>
|
{hint}
|
||||||
|
</div>
|
||||||
|
) : null}
|
||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
marginTop: 10,
|
marginTop: compact ? 8 : 10,
|
||||||
height: 64,
|
height: compact ? 46 : 64,
|
||||||
borderRadius: 14,
|
borderRadius: 14,
|
||||||
border: `1px dashed ${stylesTokens.panelBorder}`,
|
border: `1px dashed ${stylesTokens.panelBorder}`,
|
||||||
opacity: 0.8,
|
opacity: 0.8,
|
||||||
@@ -259,79 +261,130 @@ export default function App() {
|
|||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Player rail placeholder (rechts vom Board, vor Notizen)
|
||||||
|
const players = [
|
||||||
|
{ id: "p1", label: "A", active: true },
|
||||||
|
{ id: "p2", label: "B" },
|
||||||
|
{ id: "p3", label: "C" },
|
||||||
|
{ id: "p4", label: "D" },
|
||||||
|
{ id: "p5", label: "E" },
|
||||||
|
];
|
||||||
|
|
||||||
|
const PlayerIcon = ({ label, active }) => (
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
width: active ? 46 : 36,
|
||||||
|
height: active ? 46 : 36,
|
||||||
|
borderRadius: 999,
|
||||||
|
border: `1px solid ${stylesTokens.panelBorder}`,
|
||||||
|
background: stylesTokens.panelBg,
|
||||||
|
boxShadow: active ? "0 16px 40px rgba(0,0,0,0.55)" : "0 10px 28px rgba(0,0,0,0.35)",
|
||||||
|
display: "grid",
|
||||||
|
placeItems: "center",
|
||||||
|
color: active ? stylesTokens.textGold : stylesTokens.textMain,
|
||||||
|
fontWeight: 900,
|
||||||
|
fontSize: active ? 14 : 12,
|
||||||
|
transition: "transform 120ms ease, width 120ms ease, height 120ms ease",
|
||||||
|
}}
|
||||||
|
title={active ? "Aktiver Spieler" : "Spieler"}
|
||||||
|
>
|
||||||
|
{label}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div style={styles.page}>
|
<div style={styles.page}>
|
||||||
<div style={styles.bgFixed} aria-hidden="true">
|
<div style={styles.bgFixed} aria-hidden="true">
|
||||||
<div style={styles.bgMap} />
|
<div style={styles.bgMap} />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Layout: Links Game/Board (fix), Rechts Notizen (scroll) */}
|
|
||||||
<div className="appRoot">
|
<div className="appRoot">
|
||||||
{/* LEFT: Game Area */}
|
{/* LEFT: Game Area */}
|
||||||
<section className="leftPane">
|
<section className="leftPane">
|
||||||
{/* Top bar placeholders (User + Settings) */}
|
{/* Top: User + Settings adjacent */}
|
||||||
<div className="topBarRow">
|
<div className="topBarRow">
|
||||||
<PlaceholderCard title="User Dropdown" hint="(placeholder)" />
|
<PlaceholderCard title="User Dropdown" hint="(placeholder)" compact />
|
||||||
<PlaceholderCard title="Einstellungen" hint="(placeholder)" />
|
<PlaceholderCard title="Einstellungen" hint="(placeholder)" compact />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Center Board */}
|
{/* Main: Tools | Board | Player Rail */}
|
||||||
<div
|
<div className="mainRow">
|
||||||
className="boardWrap"
|
{/* Left of board: Hilfskarten + Dunkles Deck side-by-side */}
|
||||||
style={{
|
<div className="leftTools">
|
||||||
border: `1px solid ${stylesTokens.panelBorder}`,
|
<div className="leftToolsRow">
|
||||||
background: stylesTokens.panelBg,
|
<PlaceholderCard title="Hilfskarten" hint="(placeholder)" />
|
||||||
boxShadow: "0 20px 70px rgba(0,0,0,0.45)",
|
<PlaceholderCard title="Dunkles Deck" hint="(placeholder)" />
|
||||||
backdropFilter: "blur(10px)",
|
|
||||||
padding: 12,
|
|
||||||
position: "relative",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
style={{
|
|
||||||
position: "absolute",
|
|
||||||
inset: 0,
|
|
||||||
background: `linear-gradient(90deg, transparent, ${stylesTokens.goldLine}, transparent)`,
|
|
||||||
opacity: 0.25,
|
|
||||||
pointerEvents: "none",
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<div style={{ position: "relative", height: "100%", display: "flex", flexDirection: "column" }}>
|
|
||||||
<div style={{ fontWeight: 900, color: stylesTokens.textMain, fontSize: 14 }}>
|
|
||||||
3D Board / Game View
|
|
||||||
</div>
|
|
||||||
<div style={{ marginTop: 6, color: stylesTokens.textDim, fontSize: 12 }}>
|
|
||||||
Platzhalter – hier kommt später das Board + Figuren rein.
|
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Board: big */}
|
||||||
|
<div
|
||||||
|
className="boardWrap"
|
||||||
|
style={{
|
||||||
|
border: `1px solid ${stylesTokens.panelBorder}`,
|
||||||
|
background: stylesTokens.panelBg,
|
||||||
|
boxShadow: "0 20px 70px rgba(0,0,0,0.45)",
|
||||||
|
backdropFilter: "blur(10px)",
|
||||||
|
padding: 12,
|
||||||
|
position: "relative",
|
||||||
|
}}
|
||||||
|
>
|
||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
marginTop: 10,
|
position: "absolute",
|
||||||
flex: 1,
|
inset: 0,
|
||||||
borderRadius: 18,
|
background: `linear-gradient(90deg, transparent, ${stylesTokens.goldLine}, transparent)`,
|
||||||
border: `1px dashed ${stylesTokens.panelBorder}`,
|
opacity: 0.22,
|
||||||
opacity: 0.85,
|
pointerEvents: "none",
|
||||||
minHeight: 0,
|
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<div style={{ position: "relative", height: "100%", display: "flex", flexDirection: "column" }}>
|
||||||
|
<div style={{ fontWeight: 900, color: stylesTokens.textMain, fontSize: 14 }}>
|
||||||
|
3D Board / Game View
|
||||||
|
</div>
|
||||||
|
<div style={{ marginTop: 6, color: stylesTokens.textDim, fontSize: 12 }}>
|
||||||
|
Platzhalter – hier kommt später das Board + Figuren rein.
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
marginTop: 10,
|
||||||
|
flex: 1,
|
||||||
|
borderRadius: 18,
|
||||||
|
border: `1px dashed ${stylesTokens.panelBorder}`,
|
||||||
|
opacity: 0.85,
|
||||||
|
minHeight: 0,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Dice: under the board slightly right (overlay) */}
|
||||||
|
<div className="diceOverlay">
|
||||||
|
<PlaceholderCard title="Würfel" hint="(placeholder)" compact />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Right of board: player rail, directly before notes */}
|
||||||
|
<div className="playerRail">
|
||||||
|
<div className="playerRailTitle">Spieler</div>
|
||||||
|
<div className="playerRailList">
|
||||||
|
{players.map((p) => (
|
||||||
|
<PlayerIcon key={p.id} label={p.label} active={!!p.active} />
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Bottom HUD */}
|
{/* Bottom: Player HUD (left user card, mid secret, right points) */}
|
||||||
<div className="bottomHud">
|
<div className="playerHud">
|
||||||
|
<PlaceholderCard title="Spielerkarte (User)" hint="(placeholder)" />
|
||||||
<PlaceholderCard title="Meine Geheimkarten" hint="(placeholder)" />
|
<PlaceholderCard title="Meine Geheimkarten" hint="(placeholder)" />
|
||||||
<PlaceholderCard title="Würfel + Hogwarts Points" hint="(placeholder)" />
|
<PlaceholderCard title="Hogwarts Points" hint="(placeholder)" />
|
||||||
<PlaceholderCard title="Spielerkarte / Turn" hint="(placeholder)" />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Extra: Hilfskarten / Deck */}
|
|
||||||
<div className="extraRow">
|
|
||||||
<PlaceholderCard title="Dunkles Deck" hint="(placeholder)" />
|
|
||||||
<PlaceholderCard title="Hilfskarten" hint="(placeholder)" />
|
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
{/* RIGHT: Notes Panel */}
|
{/* RIGHT: Notes Panel (scroll only here) */}
|
||||||
<aside
|
<aside
|
||||||
className="notesPane"
|
className="notesPane"
|
||||||
style={{
|
style={{
|
||||||
|
|||||||
@@ -1,60 +1,129 @@
|
|||||||
/* Desktop-only layout: left fixed (no scroll), right notes scroll only */
|
/* Desktop-only layout:
|
||||||
|
- Left: fixed, no scroll
|
||||||
|
- Right: notes scroll only
|
||||||
|
*/
|
||||||
|
|
||||||
html, body, #root {
|
html, body, #root {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
body {
|
body {
|
||||||
overflow: hidden; /* no page scroll, only notes panel scrolls */
|
overflow: hidden; /* prevent global scroll */
|
||||||
}
|
}
|
||||||
|
|
||||||
.appRoot {
|
.appRoot {
|
||||||
height: 100vh;
|
height: 100vh;
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: minmax(720px, 1fr) clamp(380px, 32vw, 520px);
|
grid-template-columns: minmax(860px, 1fr) clamp(380px, 32vw, 520px);
|
||||||
gap: 14px;
|
gap: 14px;
|
||||||
padding: 14px;
|
padding: 14px;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* LEFT COLUMN */
|
||||||
.leftPane {
|
.leftPane {
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-rows: 72px minmax(340px, 1fr) 160px 110px; /* top / board / hud / extra */
|
grid-template-rows: 72px minmax(340px, 1fr) 160px; /* top | main | player HUD */
|
||||||
gap: 14px;
|
gap: 14px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.topBarRow {
|
.topBarRow {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: 1fr 1fr;
|
grid-template-columns: 1fr 1fr; /* User | Settings adjacent */
|
||||||
gap: 14px;
|
gap: 14px;
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* MIDDLE: Tools | Board | Player rail */
|
||||||
|
.mainRow {
|
||||||
|
min-height: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: minmax(260px, 360px) minmax(520px, 1fr) 92px;
|
||||||
|
gap: 14px;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Left of board: two cards next to each other */
|
||||||
|
.leftTools {
|
||||||
|
min-width: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
display: grid;
|
||||||
|
align-content: start;
|
||||||
|
}
|
||||||
|
|
||||||
|
.leftToolsRow {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1fr 1fr; /* Hilfskarten | Dunkles Deck */
|
||||||
|
gap: 14px;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Board */
|
||||||
.boardWrap {
|
.boardWrap {
|
||||||
min-height: 0;
|
min-height: 0;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
border-radius: 22px;
|
border-radius: 22px;
|
||||||
|
min-width: 0;
|
||||||
|
position: relative;
|
||||||
}
|
}
|
||||||
|
|
||||||
.bottomHud {
|
/* Dice overlay: under board slightly right */
|
||||||
|
.diceOverlay {
|
||||||
|
position: absolute;
|
||||||
|
bottom: 14px;
|
||||||
|
right: 18px; /* "leicht rechts" */
|
||||||
|
width: 220px;
|
||||||
|
pointer-events: none; /* placeholder only */
|
||||||
|
opacity: 0.95;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Player rail: right of board, before notes */
|
||||||
|
.playerRail {
|
||||||
|
min-height: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
border-radius: 22px;
|
||||||
|
border: 1px solid rgba(255,255,255,0.08);
|
||||||
|
background: rgba(0,0,0,0.18);
|
||||||
|
backdrop-filter: blur(10px);
|
||||||
|
box-shadow: 0 16px 50px rgba(0,0,0,0.35);
|
||||||
|
padding: 10px 8px;
|
||||||
|
display: grid;
|
||||||
|
grid-template-rows: auto 1fr;
|
||||||
|
justify-items: center;
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.playerRailTitle {
|
||||||
|
font-weight: 900;
|
||||||
|
font-size: 12px;
|
||||||
|
opacity: 0.9;
|
||||||
|
}
|
||||||
|
|
||||||
|
.playerRailList {
|
||||||
min-height: 0;
|
min-height: 0;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: 1.15fr 0.9fr 1.15fr;
|
gap: 10px;
|
||||||
gap: 14px;
|
align-content: start;
|
||||||
|
justify-items: center;
|
||||||
|
padding-top: 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.extraRow {
|
/* Bottom: Player HUD (User | Secret | Points) */
|
||||||
|
.playerHud {
|
||||||
min-height: 0;
|
min-height: 0;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: 1fr 1fr;
|
grid-template-columns: 1.1fr 1.4fr 1.1fr;
|
||||||
gap: 14px;
|
gap: 14px;
|
||||||
|
min-width: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* RIGHT COLUMN */
|
||||||
.notesPane {
|
.notesPane {
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
@@ -69,21 +138,32 @@ body {
|
|||||||
padding-right: 4px;
|
padding-right: 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Make it shrink gracefully on smaller viewports */
|
/* graceful shrink */
|
||||||
@media (max-height: 860px) {
|
@media (max-height: 860px) {
|
||||||
.leftPane {
|
.leftPane {
|
||||||
grid-template-rows: 64px minmax(280px, 1fr) 140px 96px;
|
grid-template-rows: 64px minmax(280px, 1fr) 140px;
|
||||||
|
}
|
||||||
|
.diceOverlay {
|
||||||
|
bottom: 10px;
|
||||||
|
right: 12px;
|
||||||
|
width: 200px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width: 1280px) {
|
@media (max-width: 1400px) {
|
||||||
.appRoot {
|
.appRoot {
|
||||||
grid-template-columns: minmax(620px, 1fr) clamp(340px, 34vw, 480px);
|
grid-template-columns: minmax(760px, 1fr) clamp(360px, 34vw, 480px);
|
||||||
|
}
|
||||||
|
.mainRow {
|
||||||
|
grid-template-columns: minmax(240px, 320px) minmax(480px, 1fr) 88px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width: 1120px) {
|
@media (max-width: 1220px) {
|
||||||
.appRoot {
|
.appRoot {
|
||||||
grid-template-columns: 1fr clamp(320px, 36vw, 440px);
|
grid-template-columns: 1fr clamp(340px, 36vw, 460px);
|
||||||
|
}
|
||||||
|
.mainRow {
|
||||||
|
grid-template-columns: minmax(220px, 300px) minmax(420px, 1fr) 84px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user