1 bis 8 Party: mobile Web-App (Regeln, Karten, Spielleiter) + Docker-Deploy
This commit is contained in:
@@ -0,0 +1,155 @@
|
||||
import { useMemo, useState } from 'react'
|
||||
import { RefreshCw, Shuffle } from 'lucide-react'
|
||||
import { KARTEN, KATEGORIEN, kategorieById, type KategorieId, type Spielkarte } from '@/data/game'
|
||||
|
||||
type Filter = 'alle' | KategorieId
|
||||
|
||||
function KartenGesicht({ karte, zeigen }: { karte: Spielkarte | null; zeigen: boolean }) {
|
||||
const kat = karte ? kategorieById(karte.kategorie) : null
|
||||
return (
|
||||
<div className={`perspektive h-72 w-full select-none`}>
|
||||
<div className={`flip-innen ${zeigen && karte ? 'ist-gedreht' : ''}`}>
|
||||
{/* Rückseite = Nachziehstapel */}
|
||||
<div className="flip-seite flex flex-col items-center justify-center gap-3 border-2 border-white/10 bg-gradient-to-br from-[#2a1550] to-[#1b0f38] shadow-[0_16px_48px_rgba(0,0,0,0.5)]">
|
||||
<span className="font-display text-6xl font-bold text-white/10">?</span>
|
||||
<span className="flex items-center gap-2 text-sm font-bold uppercase tracking-[0.2em] text-white/35">
|
||||
<Shuffle size={15} /> Nachziehstapel
|
||||
</span>
|
||||
<span className="text-xs text-white/25">Antippen, um eine Karte zu ziehen</span>
|
||||
</div>
|
||||
{/* Vorderseite = Statement */}
|
||||
{karte && kat && (
|
||||
<div
|
||||
className={`flip-seite flip-rueckseite flex flex-col border-2 p-5 shadow-[0_16px_48px_rgba(0,0,0,0.5)] bg-gradient-to-br ${kat.farbe} border-white/25`}
|
||||
>
|
||||
<div className="flex items-center justify-between">
|
||||
<span className="rounded-full bg-black/25 px-3 py-1 text-[11px] font-extrabold uppercase tracking-widest text-white">
|
||||
{kat.emoji} {kat.titel}
|
||||
</span>
|
||||
<span className="font-display text-4xl font-bold text-white/30">1–8</span>
|
||||
</div>
|
||||
<div className="flex flex-1 items-center justify-center">
|
||||
<p className="text-center font-display text-[22px] font-semibold leading-snug text-white drop-shadow-sm">
|
||||
{karte.text}
|
||||
</p>
|
||||
</div>
|
||||
<p className="text-center text-[11px] font-bold uppercase tracking-[0.2em] text-white/60">
|
||||
Karte {String(karte.id).padStart(2, '0')} / 20
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default function Cards() {
|
||||
const [filter, setFilter] = useState<Filter>('alle')
|
||||
const [gezogen, setGezogen] = useState<Spielkarte | null>(null)
|
||||
const [zaehler, setZaehler] = useState(0) // triggert Re-Flip-Animation
|
||||
|
||||
const stapel = useMemo(
|
||||
() => (filter === 'alle' ? KARTEN : KARTEN.filter((k) => k.kategorie === filter)),
|
||||
[filter]
|
||||
)
|
||||
|
||||
const ziehen = () => {
|
||||
const rest = stapel.filter((k) => k.id !== gezogen?.id)
|
||||
const pool = rest.length > 0 ? rest : stapel
|
||||
setGezogen(pool[Math.floor(Math.random() * pool.length)])
|
||||
setZaehler((z) => z + 1)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="px-5 pt-8">
|
||||
<p className="mb-1 text-xs font-extrabold uppercase tracking-[0.25em] text-violet-300">
|
||||
20 Statements · 5 Kategorien
|
||||
</p>
|
||||
<h2 className="font-display text-3xl font-bold text-white">Die Karten</h2>
|
||||
<p className="mt-2 text-sm leading-relaxed text-white/60">
|
||||
Farblich nach Kategorien sortiert – entscheidet vorab, welche Vibe die
|
||||
Runde haben soll! 🎨
|
||||
</p>
|
||||
|
||||
{/* Kategorie-Filter */}
|
||||
<div className="scrollbar-none -mx-5 mt-5 flex gap-2 overflow-x-auto px-5 pb-1">
|
||||
<FilterChip aktiv={filter === 'alle'} onClick={() => setFilter('alle')} label="✨ Alle" />
|
||||
{KATEGORIEN.map((k) => (
|
||||
<FilterChip
|
||||
key={k.id}
|
||||
aktiv={filter === k.id}
|
||||
onClick={() => setFilter(k.id)}
|
||||
label={`${k.emoji} ${k.titel.split(' ')[0]}`}
|
||||
chipKlasse={k.chip}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Zieh-Stapel */}
|
||||
<div className="mt-5" onClick={ziehen} role="button" aria-label="Karte ziehen">
|
||||
<div key={zaehler} className={gezogen && zaehler > 0 ? 'rein' : ''}>
|
||||
<KartenGesicht karte={gezogen} zeigen={!!gezogen} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button
|
||||
onClick={ziehen}
|
||||
className="glanz mt-4 flex w-full items-center justify-center gap-2 rounded-2xl bg-gradient-to-r from-violet-500 to-fuchsia-500 py-3.5 font-display text-base font-semibold text-white shadow-[0_10px_30px_rgba(139,92,246,0.4)] transition-transform active:scale-[0.97]"
|
||||
>
|
||||
<RefreshCw size={18} /> {gezogen ? 'Nächste Karte ziehen' : 'Karte ziehen'}
|
||||
</button>
|
||||
|
||||
{/* Alle Karten im Überblick */}
|
||||
<h3 className="mt-8 font-display text-xl font-semibold text-white">
|
||||
Alle Karten <span className="text-white/40">({stapel.length})</span>
|
||||
</h3>
|
||||
<div className="mt-3 grid grid-cols-1 gap-3 pb-2">
|
||||
{stapel.map((karte) => {
|
||||
const kat = kategorieById(karte.kategorie)
|
||||
return (
|
||||
<div
|
||||
key={karte.id}
|
||||
className={`glass-card relative overflow-hidden p-4 ring-1 ${kat.ring}`}
|
||||
>
|
||||
<div className={`absolute inset-y-0 left-0 w-1.5 bg-gradient-to-b ${kat.farbe}`} />
|
||||
<div className="flex items-start gap-3 pl-2">
|
||||
<span className="text-xl">{kat.emoji}</span>
|
||||
<div>
|
||||
<p className="text-[15px] font-bold leading-snug text-white/90">{karte.text}</p>
|
||||
<p className={`mt-1.5 text-[11px] font-extrabold uppercase tracking-widest ${kat.textfarbe}`}>
|
||||
{kat.titel}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
function FilterChip({
|
||||
aktiv,
|
||||
onClick,
|
||||
label,
|
||||
chipKlasse,
|
||||
}: {
|
||||
aktiv: boolean
|
||||
onClick: () => void
|
||||
label: string
|
||||
chipKlasse?: string
|
||||
}) {
|
||||
return (
|
||||
<button
|
||||
onClick={onClick}
|
||||
className={`shrink-0 rounded-full border px-3.5 py-1.5 text-xs font-bold transition-all active:scale-95 ${
|
||||
aktiv
|
||||
? 'border-pink-400/60 bg-pink-400/20 text-pink-200 shadow-[0_0_14px_rgba(244,114,182,0.3)]'
|
||||
: `border-white/10 bg-white/[0.05] text-white/55 ${chipKlasse ?? ''}`
|
||||
}`}
|
||||
>
|
||||
{label}
|
||||
</button>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user