feat: dying label and Roll Recovery button in InitiativeTracker active phase

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Aaron Wood 2026-04-12 01:39:18 -04:00
parent 52f792d63b
commit bf7db6bd4c
2 changed files with 55 additions and 8 deletions

View file

@ -302,3 +302,27 @@
border-color: rgba(var(--gold-rgb), 0.5);
color: var(--gold);
}
.dyingTag {
font-size: 0.72rem;
color: var(--danger);
font-weight: 600;
margin-left: auto;
white-space: nowrap;
}
.recoveryBtn {
font-size: 0.7rem;
padding: 0.15rem 0.4rem;
background: transparent;
border: 1px solid var(--danger);
color: var(--danger);
border-radius: 3px;
cursor: pointer;
white-space: nowrap;
flex-shrink: 0;
}
.recoveryBtn:hover {
background: rgba(var(--danger-rgb), 0.12);
}

View file

@ -56,6 +56,10 @@ export default function InitiativeTracker({
socket.emit("initiative:end", { campaignId, combatId: combat.id });
}
function emitRecoveryRoll(characterId: number) {
socket.emit("death:recovery-roll", { campaignId, characterId });
}
function emitUpdateEnemyHp(enemyId: string, hp_current: number) {
socket.emit("initiative:update-enemy", {
campaignId,
@ -119,6 +123,7 @@ export default function InitiativeTracker({
onAddEnemy={emitAddEnemy}
onNext={emitNext}
onEnd={emitEnd}
onRecoveryRoll={emitRecoveryRoll}
/>
)}
</div>
@ -232,6 +237,7 @@ interface ActivePhaseProps {
onAddEnemy: () => void;
onNext: () => void;
onEnd: () => void;
onRecoveryRoll: (characterId: number) => void;
}
function ActivePhase({
@ -249,6 +255,7 @@ function ActivePhase({
onAddEnemy,
onNext,
onEnd,
onRecoveryRoll,
}: ActivePhaseProps) {
const partyActive = combat.current_side === "party";
@ -263,14 +270,30 @@ function ActivePhase({
</span>
)}
</div>
{partyChars.map((c) => (
<div key={c.id} className={styles.combatantRow}>
<span className={styles.dot} style={{ background: c.color }} />
<span className={partyActive ? styles.activeName : styles.rollName}>
{c.name}
</span>
</div>
))}
{partyChars.map((c) => {
const dyingCondition = c.conditions?.find((cond) => cond.name === "Dying");
return (
<div key={c.id} className={styles.combatantRow}>
<span className={styles.dot} style={{ background: c.color }} />
<span className={partyActive ? styles.activeName : styles.rollName}>
{c.is_dead ? "\u{1F480} " : ""}{c.name}
</span>
{dyingCondition && (
<span className={styles.dyingTag}>
{"\u{1F480}"} Dying ({dyingCondition.rounds_remaining}r)
</span>
)}
{isDM && dyingCondition && !c.is_dead && (
<button
className={styles.recoveryBtn}
onClick={() => onRecoveryRoll(c.id)}
>
Roll Recovery
</button>
)}
</div>
);
})}
</div>
<div className={`${styles.section} ${!partyActive ? styles.activeSection : ""}`}>