You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
134 lines
3.5 KiB
TypeScript
134 lines
3.5 KiB
TypeScript
import { useEffect, useState, type CSSProperties } from 'react';
|
|
import { REVIVE_TIMER_SECONDS } from '../shared/constants';
|
|
import { useT, t } from '../i18n';
|
|
|
|
interface DeathScreenProps {
|
|
visible: boolean;
|
|
onRevive: () => void;
|
|
/** Free revives left for non-subscribers; omit when subscribed / unlimited. */
|
|
revivesRemaining?: number;
|
|
}
|
|
|
|
/** Full-screen dimming; `pointerEvents: 'none'` so HUD controls (e.g. hero sheet) stay clickable. */
|
|
const overlayStyle: CSSProperties = {
|
|
position: 'absolute',
|
|
top: 0,
|
|
left: 0,
|
|
width: '100%',
|
|
height: '100%',
|
|
backgroundColor: 'rgba(0, 0, 0, 0.7)',
|
|
display: 'flex',
|
|
flexDirection: 'column',
|
|
alignItems: 'center',
|
|
justifyContent: 'center',
|
|
zIndex: 100,
|
|
transition: 'opacity 0.5s ease',
|
|
pointerEvents: 'none',
|
|
};
|
|
|
|
/** Death UI captures taps; overlay around it passes clicks through to the game HUD. */
|
|
const deathPanelStyle: CSSProperties = {
|
|
display: 'flex',
|
|
flexDirection: 'column',
|
|
alignItems: 'center',
|
|
gap: 24,
|
|
pointerEvents: 'auto',
|
|
};
|
|
|
|
const titleStyle: CSSProperties = {
|
|
fontSize: 36,
|
|
fontWeight: 900,
|
|
color: '#cc3333',
|
|
textShadow: '0 0 20px rgba(204, 51, 51, 0.5)',
|
|
letterSpacing: 4,
|
|
};
|
|
|
|
const timerStyle: CSSProperties = {
|
|
fontSize: 48,
|
|
fontWeight: 700,
|
|
color: '#ffffff',
|
|
fontVariantNumeric: 'tabular-nums',
|
|
};
|
|
|
|
const buttonStyle: CSSProperties = {
|
|
padding: '14px 40px',
|
|
fontSize: 18,
|
|
fontWeight: 700,
|
|
color: '#fff',
|
|
backgroundColor: '#cc3333',
|
|
border: 'none',
|
|
borderRadius: 8,
|
|
cursor: 'pointer',
|
|
transition: 'background-color 0.2s',
|
|
};
|
|
|
|
export function DeathScreen({ visible, onRevive, revivesRemaining }: DeathScreenProps) {
|
|
const tr = useT();
|
|
const [timer, setTimer] = useState(REVIVE_TIMER_SECONDS);
|
|
const canRevive = revivesRemaining === undefined || revivesRemaining > 0;
|
|
|
|
// Countdown timer
|
|
useEffect(() => {
|
|
if (!visible) {
|
|
setTimer(REVIVE_TIMER_SECONDS);
|
|
return;
|
|
}
|
|
|
|
const interval = setInterval(() => {
|
|
setTimer((t) => {
|
|
if (t <= 1) {
|
|
clearInterval(interval);
|
|
return 0;
|
|
}
|
|
return t - 1;
|
|
});
|
|
}, 1000);
|
|
|
|
return () => clearInterval(interval);
|
|
}, [visible]);
|
|
|
|
// Auto-revive when timer hits 0 (only if server still allows free revives)
|
|
useEffect(() => {
|
|
if (visible && timer === 0 && canRevive) {
|
|
onRevive();
|
|
}
|
|
}, [visible, timer, onRevive, canRevive]);
|
|
|
|
if (!visible) return null;
|
|
|
|
return (
|
|
<div style={overlayStyle}>
|
|
<div style={deathPanelStyle}>
|
|
<div style={titleStyle}>{tr.youDied}</div>
|
|
<div style={timerStyle}>{canRevive ? timer : '—'}</div>
|
|
{revivesRemaining !== undefined && (
|
|
<div style={{ color: '#aaa', fontSize: 14 }}>
|
|
{t(tr.freeRevivesLeft, { count: Math.max(0, revivesRemaining) })}
|
|
</div>
|
|
)}
|
|
<button
|
|
style={{
|
|
...buttonStyle,
|
|
opacity: canRevive ? 1 : 0.45,
|
|
cursor: canRevive ? 'pointer' : 'not-allowed',
|
|
}}
|
|
disabled={!canRevive}
|
|
onClick={onRevive}
|
|
onMouseEnter={(e) => {
|
|
if (!canRevive) return;
|
|
e.currentTarget.style.backgroundColor = '#ee4444';
|
|
}}
|
|
onMouseLeave={(e) => {
|
|
e.currentTarget.style.backgroundColor = '#cc3333';
|
|
}}
|
|
>
|
|
{tr.reviveNow}
|
|
</button>
|
|
<div style={{ color: '#888', fontSize: 13 }}>
|
|
{canRevive ? t(tr.autoReviveIn, { timer }) : tr.noFreeRevives}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|