Refactor components to use reusable PaperCard.

Introduced a new `PaperCard` component for consistent and reusable card styling across the app, replacing inline card styles. Added subtle animations and paper edge effects to enhance the visual design and provide a polished user experience. Updated existing components to integrate with the new `PaperCard` while maintaining backward compatibility.
This commit is contained in:
2026-02-03 10:54:43 +01:00
parent b442a95c8d
commit 922f2968d7

View File

@@ -19,6 +19,16 @@ function cycleTag(tag) {
return null; return null;
} }
function PaperCard({ children, style }) {
return (
<div style={{ ...styles.paperCard, ...style }}>
<div style={styles.paperEdgeTop} aria-hidden="true" />
{children}
<div style={styles.paperEdgeBottom} aria-hidden="true" />
</div>
);
}
function AdminPanel() { function AdminPanel() {
const [users, setUsers] = useState([]); const [users, setUsers] = useState([]);
@@ -223,7 +233,7 @@ export default function App() {
document.head.appendChild(link); document.head.appendChild(link);
}, []); }, []);
// ✅ Keyframes / Animationen // ✅ Keyframes / Animationen (+ Paper Edge minimal)
useEffect(() => { useEffect(() => {
if (document.getElementById("hp-anim-style")) return; if (document.getElementById("hp-anim-style")) return;
const style = document.createElement("style"); const style = document.createElement("style");
@@ -240,6 +250,13 @@ export default function App() {
70% { opacity: .70; transform: translateY(1px) scale(1.01); filter: blur(17px); } 70% { opacity: .70; transform: translateY(1px) scale(1.01); filter: blur(17px); }
100% { opacity: .65; transform: translateY(0px) scale(1); filter: blur(16px); } 100% { opacity: .65; transform: translateY(0px) scale(1); filter: blur(16px); }
} }
/* Paper edge: super subtle, keeps it "alive" without causing scroll lag */
@keyframes paperEdgeDrift {
0% { opacity: .92; transform: translateX(0px); }
50% { opacity: .98; transform: translateX(0.6px); }
100% { opacity: .92; transform: translateX(0px); }
}
`; `;
document.head.appendChild(style); document.head.appendChild(style);
}, []); }, []);
@@ -282,7 +299,6 @@ export default function App() {
if (root) { if (root) {
root.style.minHeight = "100dvh"; root.style.minHeight = "100dvh";
root.style.background = "transparent"; root.style.background = "transparent";
// extra: verhindert manchmal "white flash" bei overscroll
root.style.overflowX = "hidden"; root.style.overflowX = "hidden";
} }
}, []); }, []);
@@ -397,7 +413,7 @@ export default function App() {
return ( return (
<div style={styles.loginPage}> <div style={styles.loginPage}>
<div style={styles.candleGlowLayer} aria-hidden="true" /> <div style={styles.candleGlowLayer} aria-hidden="true" />
<div style={styles.loginCard}> <PaperCard style={styles.loginCard}>
<div style={styles.loginTitle}>Zauber-Detektiv Notizbogen</div> <div style={styles.loginTitle}>Zauber-Detektiv Notizbogen</div>
<div style={styles.loginSubtitle}> <div style={styles.loginSubtitle}>
@@ -447,7 +463,7 @@ export default function App() {
Deine Notizen bleiben privat jeder Spieler sieht nur seinen eigenen Deine Notizen bleiben privat jeder Spieler sieht nur seinen eigenen
Zettel. Zettel.
</div> </div>
</div> </PaperCard>
</div> </div>
); );
} }
@@ -455,7 +471,11 @@ export default function App() {
// ===== Main App ===== // ===== Main App =====
const sections = sheet const sections = sheet
? [ ? [
{ key: "suspect", title: "VERDÄCHTIGE PERSON", entries: sheet.suspect || [] }, {
key: "suspect",
title: "VERDÄCHTIGE PERSON",
entries: sheet.suspect || [],
},
{ key: "item", title: "GEGENSTAND", entries: sheet.item || [] }, { key: "item", title: "GEGENSTAND", entries: sheet.item || [] },
{ key: "location", title: "ORT", entries: sheet.location || [] }, { key: "location", title: "ORT", entries: sheet.location || [] },
] ]
@@ -514,7 +534,7 @@ export default function App() {
{/* Spiel + Hilfe */} {/* Spiel + Hilfe */}
<div style={{ marginTop: 14 }}> <div style={{ marginTop: 14 }}>
<div style={styles.card}> <PaperCard>
<div style={styles.sectionHeader}>Spiel</div> <div style={styles.sectionHeader}>Spiel</div>
<div <div
style={{ style={{
@@ -545,7 +565,7 @@ export default function App() {
Hilfe Hilfe
</button> </button>
</div> </div>
</div> </PaperCard>
</div> </div>
{/* Hilfe Modal */} {/* Hilfe Modal */}
@@ -633,9 +653,12 @@ export default function App() {
<div style={styles.helpDivider} /> <div style={styles.helpDivider} />
<div style={styles.helpSectionTitle}>2) i / m / s Button (Notiz)</div> <div style={styles.helpSectionTitle}>
2) i / m / s Button (Notiz)
</div>
<div style={styles.helpText}> <div style={styles.helpText}>
Rechts pro Zeile gibt es einen Button, der durch diese Werte rotiert: Rechts pro Zeile gibt es einen Button, der durch diese Werte
rotiert:
</div> </div>
<div style={styles.helpList}> <div style={styles.helpList}>
@@ -679,7 +702,7 @@ export default function App() {
{/* Sheet */} {/* Sheet */}
<div style={{ marginTop: 14, display: "grid", gap: 14 }}> <div style={{ marginTop: 14, display: "grid", gap: 14 }}>
{sections.map((sec) => ( {sections.map((sec) => (
<div key={sec.key} style={styles.card}> <PaperCard key={sec.key}>
<div style={styles.sectionHeader}>{sec.title}</div> <div style={styles.sectionHeader}>{sec.title}</div>
<div style={{ display: "grid" }}> <div style={{ display: "grid" }}>
@@ -689,7 +712,10 @@ export default function App() {
style={{ style={{
...styles.row, ...styles.row,
background: getRowBg(e.status), background: getRowBg(e.status),
animation: pulseId === e.entry_id ? "rowPulse 220ms ease-out" : "none", animation:
pulseId === e.entry_id
? "rowPulse 220ms ease-out"
: "none",
}} }}
> >
<div <div
@@ -724,7 +750,7 @@ export default function App() {
</div> </div>
))} ))}
</div> </div>
</div> </PaperCard>
))} ))}
</div> </div>
@@ -776,6 +802,18 @@ const styles = {
backdropFilter: "blur(4px)", backdropFilter: "blur(4px)",
}, },
// === Paper card base (replaces `card` visually) ===
paperCard: {
position: "relative",
borderRadius: 16,
overflow: "hidden",
border: "1px solid rgba(0,0,0,0.18)",
background: "rgba(255,255,255,0.35)",
boxShadow: "0 10px 24px rgba(0,0,0,0.12)",
transition: "transform 180ms ease, box-shadow 180ms ease",
},
// Fallback (kept, in case you still use it somewhere)
card: { card: {
borderRadius: 16, borderRadius: 16,
overflow: "hidden", overflow: "hidden",
@@ -785,6 +823,82 @@ const styles = {
transition: "transform 180ms ease, box-shadow 180ms ease", transition: "transform 180ms ease, box-shadow 180ms ease",
}, },
// Paper edges (top + bottom) — mask-based, lightweight, no heavy blur/filters
paperEdgeTop: {
position: "absolute",
left: 0,
right: 0,
top: 0,
height: 18,
pointerEvents: "none",
background:
"linear-gradient(180deg, rgba(255,255,255,0.62), rgba(255,255,255,0.0))",
opacity: 0.95,
WebkitMaskImage:
"radial-gradient(10px 8px at 6% 100%, #000 98%, transparent 102%)," +
"radial-gradient(12px 9px at 14% 100%, #000 98%, transparent 102%)," +
"radial-gradient(9px 7px at 24% 100%, #000 98%, transparent 102%)," +
"radial-gradient(13px 10px at 36% 100%, #000 98%, transparent 102%)," +
"radial-gradient(10px 8px at 49% 100%, #000 98%, transparent 102%)," +
"radial-gradient(12px 9px at 62% 100%, #000 98%, transparent 102%)," +
"radial-gradient(9px 7px at 74% 100%, #000 98%, transparent 102%)," +
"radial-gradient(13px 10px at 86% 100%, #000 98%, transparent 102%)," +
"radial-gradient(10px 8px at 94% 100%, #000 98%, transparent 102%)," +
"linear-gradient(#000, #000)",
maskImage:
"radial-gradient(10px 8px at 6% 100%, #000 98%, transparent 102%)," +
"radial-gradient(12px 9px at 14% 100%, #000 98%, transparent 102%)," +
"radial-gradient(9px 7px at 24% 100%, #000 98%, transparent 102%)," +
"radial-gradient(13px 10px at 36% 100%, #000 98%, transparent 102%)," +
"radial-gradient(10px 8px at 49% 100%, #000 98%, transparent 102%)," +
"radial-gradient(12px 9px at 62% 100%, #000 98%, transparent 102%)," +
"radial-gradient(9px 7px at 74% 100%, #000 98%, transparent 102%)," +
"radial-gradient(13px 10px at 86% 100%, #000 98%, transparent 102%)," +
"radial-gradient(10px 8px at 94% 100%, #000 98%, transparent 102%)," +
"linear-gradient(#000, #000)",
filter: "drop-shadow(0 1px 0 rgba(0,0,0,0.14))",
animation: "paperEdgeDrift 8s ease-in-out infinite",
},
paperEdgeBottom: {
position: "absolute",
left: 0,
right: 0,
bottom: 0,
height: 20,
pointerEvents: "none",
background:
"linear-gradient(0deg, rgba(255,255,255,0.62), rgba(255,255,255,0.0))",
opacity: 0.95,
transform: "scaleY(-1)",
WebkitMaskImage:
"radial-gradient(10px 8px at 6% 100%, #000 98%, transparent 102%)," +
"radial-gradient(12px 9px at 14% 100%, #000 98%, transparent 102%)," +
"radial-gradient(9px 7px at 24% 100%, #000 98%, transparent 102%)," +
"radial-gradient(13px 10px at 36% 100%, #000 98%, transparent 102%)," +
"radial-gradient(10px 8px at 49% 100%, #000 98%, transparent 102%)," +
"radial-gradient(12px 9px at 62% 100%, #000 98%, transparent 102%)," +
"radial-gradient(9px 7px at 74% 100%, #000 98%, transparent 102%)," +
"radial-gradient(13px 10px at 86% 100%, #000 98%, transparent 102%)," +
"radial-gradient(10px 8px at 94% 100%, #000 98%, transparent 102%)," +
"linear-gradient(#000, #000)",
maskImage:
"radial-gradient(10px 8px at 6% 100%, #000 98%, transparent 102%)," +
"radial-gradient(12px 9px at 14% 100%, #000 98%, transparent 102%)," +
"radial-gradient(9px 7px at 24% 100%, #000 98%, transparent 102%)," +
"radial-gradient(13px 10px at 36% 100%, #000 98%, transparent 102%)," +
"radial-gradient(10px 8px at 49% 100%, #000 98%, transparent 102%)," +
"radial-gradient(12px 9px at 62% 100%, #000 98%, transparent 102%)," +
"radial-gradient(9px 7px at 74% 100%, #000 98%, transparent 102%)," +
"radial-gradient(13px 10px at 86% 100%, #000 98%, transparent 102%)," +
"radial-gradient(10px 8px at 94% 100%, #000 98%, transparent 102%)," +
"linear-gradient(#000, #000)",
filter: "drop-shadow(0 1px 0 rgba(0,0,0,0.14))",
animation: "paperEdgeDrift 8s ease-in-out infinite",
},
sectionHeader: { sectionHeader: {
padding: "10px 12px", padding: "10px 12px",
fontWeight: 1000, fontWeight: 1000,
@@ -824,7 +938,8 @@ const styles = {
fontWeight: 1000, fontWeight: 1000,
borderRadius: 10, borderRadius: 10,
border: "1px solid rgba(0,0,0,0.25)", border: "1px solid rgba(0,0,0,0.25)",
background: "linear-gradient(180deg, rgba(255,255,255,0.55), rgba(0,0,0,0.06))", background:
"linear-gradient(180deg, rgba(255,255,255,0.55), rgba(0,0,0,0.06))",
cursor: "pointer", cursor: "pointer",
}, },
@@ -832,7 +947,8 @@ const styles = {
padding: "10px 12px", padding: "10px 12px",
borderRadius: 12, borderRadius: 12,
border: "1px solid rgba(0,0,0,0.25)", border: "1px solid rgba(0,0,0,0.25)",
background: "linear-gradient(180deg, rgba(255,255,255,0.75), rgba(0,0,0,0.06))", background:
"linear-gradient(180deg, rgba(255,255,255,0.75), rgba(0,0,0,0.06))",
fontWeight: 1000, fontWeight: 1000,
cursor: "pointer", cursor: "pointer",
whiteSpace: "nowrap", whiteSpace: "nowrap",
@@ -862,7 +978,8 @@ const styles = {
padding: "10px 12px", padding: "10px 12px",
borderRadius: 12, borderRadius: 12,
border: "1px solid rgba(0,0,0,0.25)", border: "1px solid rgba(0,0,0,0.25)",
background: "linear-gradient(180deg, rgba(255,255,255,0.75), rgba(0,0,0,0.05))", background:
"linear-gradient(180deg, rgba(255,255,255,0.75), rgba(0,0,0,0.05))",
fontWeight: 900, fontWeight: 900,
cursor: "pointer", cursor: "pointer",
transition: "transform 140ms ease, box-shadow 140ms ease", transition: "transform 140ms ease, box-shadow 140ms ease",
@@ -907,7 +1024,8 @@ const styles = {
maxWidth: 560, maxWidth: 560,
borderRadius: 18, borderRadius: 18,
border: "1px solid rgba(0,0,0,0.25)", border: "1px solid rgba(0,0,0,0.25)",
background: "linear-gradient(180deg, rgba(255,255,255,0.72), rgba(255,255,255,0.42))", background:
"linear-gradient(180deg, rgba(255,255,255,0.72), rgba(255,255,255,0.42))",
boxShadow: "0 18px 50px rgba(0,0,0,0.35)", boxShadow: "0 18px 50px rgba(0,0,0,0.35)",
padding: 14, padding: 14,
backdropFilter: "blur(6px)", backdropFilter: "blur(6px)",
@@ -924,7 +1042,8 @@ const styles = {
height: 38, height: 38,
borderRadius: 12, borderRadius: 12,
border: "1px solid rgba(0,0,0,0.25)", border: "1px solid rgba(0,0,0,0.25)",
background: "linear-gradient(180deg, rgba(255,255,255,0.85), rgba(0,0,0,0.06))", background:
"linear-gradient(180deg, rgba(255,255,255,0.85), rgba(0,0,0,0.06))",
fontWeight: 1000, fontWeight: 1000,
cursor: "pointer", cursor: "pointer",
lineHeight: "38px", lineHeight: "38px",
@@ -1014,13 +1133,10 @@ const styles = {
width: "100%", width: "100%",
maxWidth: 420, maxWidth: 420,
padding: 26, padding: 26,
borderRadius: 22, borderRadius: 22, // paperCard uses 16; this overrides for login
position: "relative", position: "relative",
zIndex: 2, zIndex: 2,
border: "1px solid rgba(0,0,0,0.25)", // border/background/boxShadow handled by PaperCard base
background: "linear-gradient(180deg, rgba(255,255,255,0.72), rgba(255,255,255,0.40))",
boxShadow: "0 18px 55px rgba(0,0,0,0.35)",
backdropFilter: "blur(8px)",
animation: "popIn 240ms ease-out", animation: "popIn 240ms ease-out",
}, },
loginTitle: { loginTitle: {
@@ -1106,7 +1222,8 @@ const styles = {
borderRadius: "0 12px 12px 0", borderRadius: "0 12px 12px 0",
border: "1px solid rgba(0,0,0,0.25)", border: "1px solid rgba(0,0,0,0.25)",
borderLeft: "none", borderLeft: "none",
background: "linear-gradient(180deg, rgba(255,255,255,0.85), rgba(0,0,0,0.06))", background:
"linear-gradient(180deg, rgba(255,255,255,0.85), rgba(0,0,0,0.06))",
cursor: "pointer", cursor: "pointer",
fontWeight: 900, fontWeight: 900,
padding: 0, padding: 0,