diff --git a/client/src/components/CharacterCard.module.css b/client/src/components/CharacterCard.module.css index e03c7ab..3a3819b 100644 --- a/client/src/components/CharacterCard.module.css +++ b/client/src/components/CharacterCard.module.css @@ -153,3 +153,45 @@ color: var(--text-tertiary); text-align: right; } + +.dying { + border: 2px solid var(--danger) !important; + animation: dyingPulse 1.5s ease-in-out infinite; +} + +@keyframes dyingPulse { + 0%, 100% { box-shadow: 0 0 6px rgba(var(--danger-rgb), 0.4); } + 50% { box-shadow: 0 0 20px rgba(var(--danger-rgb), 0.85); } +} + +.dead { + opacity: 0.45; + filter: grayscale(0.75); +} + +.dyingLabel { + font-size: 0.8rem; + color: var(--danger); + font-weight: 700; + flex-shrink: 0; + white-space: nowrap; +} + +.reviveBtn { + display: block; + width: 100%; + margin-top: 0.5rem; + padding: 0.25rem 0.5rem; + font-size: 0.75rem; + background: transparent; + border: 1px solid var(--gold); + color: var(--gold); + border-radius: 3px; + cursor: pointer; + font-family: "Cinzel", Georgia, serif; + letter-spacing: 0.04em; +} + +.reviveBtn:hover { + background: rgba(var(--gold-rgb), 0.12); +} diff --git a/client/src/components/CharacterCard.tsx b/client/src/components/CharacterCard.tsx index c59f90c..37da6fb 100644 --- a/client/src/components/CharacterCard.tsx +++ b/client/src/components/CharacterCard.tsx @@ -22,6 +22,7 @@ interface CharacterCardProps { onClick: (characterId: number) => void; canEdit?: boolean; focusSpell?: string; + isDM?: boolean; } export default function CharacterCard({ @@ -31,26 +32,42 @@ export default function CharacterCard({ onClick, canEdit = true, focusSpell, + isDM = false, }: CharacterCardProps) { + const dyingCondition = character.conditions?.find((c) => c.name === "Dying"); + const isDying = !!dyingCondition; + const isDead = !!character.is_dead; + + const cardClass = [ + styles.card, + isDying ? styles.dying : "", + isDead ? styles.dead : "", + ] + .filter(Boolean) + .join(" "); + + // When dying/dead the left-color border is replaced by the dying/dead CSS + const cardStyle = isDying || isDead + ? {} + : { borderLeftColor: character.color, borderLeftWidth: "3px" }; + return ( -
onClick(character.id)} - style={{ borderLeftColor: character.color, borderLeftWidth: "3px" }} - > +
onClick(character.id)} style={cardStyle}>
- {character.name} + {isDead ? "\u{1F480} " : ""}{character.name} {character.title ? ` ${character.title}` : ""} Lvl {character.level}
+
{character.ancestry} {character.class}
+ {focusSpell && (
● Focusing: {focusSpell} @@ -61,13 +78,16 @@ export default function CharacterCard({ onHpChange(character.id, hp)} + onChange={isDead ? () => {} : (hp) => onHpChange(character.id, hp)} /> + {isDying && dyingCondition && ( + + {"\u{1F480}"} {dyingCondition.rounds_remaining} + + )}
AC - - {calculateAC(character).effective} - + {calculateAC(character).effective}
+ {isDead && isDM && ( + + )} +
{STATS.map((stat) => { const value = getEffectiveStat(character, stat); diff --git a/client/src/pages/CampaignView.tsx b/client/src/pages/CampaignView.tsx index 167b999..74dfde8 100644 --- a/client/src/pages/CampaignView.tsx +++ b/client/src/pages/CampaignView.tsx @@ -453,6 +453,7 @@ export default function CampaignView() { onClick={setSelectedId} canEdit={role === "dm" || char.user_id === user?.userId} focusSpell={focusSpells.get(char.id)} + isDM={role === "dm"} /> ))}