botWebWars/frontend/src/components/WizardChallengeModal.tsx

225 lines
8.7 KiB
TypeScript
Raw Normal View History

2026-09-09 22:11:54 +00:00
import React, { useEffect, useRef, useState } from 'react';
import type { WizardChallengeResult } from '../types';
interface WizardChallengeModalProps {
challenge: WizardChallengeResult | null;
onClose: () => void;
}
export const WizardChallengeModal: React.FC<WizardChallengeModalProps> = ({
challenge,
onClose,
}) => {
const [countdown, setCountdown] = useState(5);
const [progressPercent, setProgressPercent] = useState(100);
const onCloseRef = useRef(onClose);
useEffect(() => {
onCloseRef.current = onClose;
});
const challengeKey = challenge
? `${challenge.challenger_id}_${challenge.player_bouts_won}_${challenge.wizard_bouts_won}_${challenge.score_change}_${challenge.health_change}_${challenge.wizard_respawn_position?.x}_${challenge.wizard_respawn_position?.y}`
: null;
useEffect(() => {
if (!challengeKey) {
setCountdown(5);
setProgressPercent(100);
return;
}
const DURATION_MS = 5000;
const targetEndTime = Date.now() + DURATION_MS;
setCountdown(5);
setProgressPercent(100);
const timer = window.setInterval(() => {
const now = Date.now();
const remainingMs = targetEndTime - now;
if (remainingMs <= 0) {
window.clearInterval(timer);
setCountdown(0);
setProgressPercent(0);
onCloseRef.current();
return;
}
const seconds = Math.ceil(remainingMs / 1000);
const percent = Math.min(100, Math.max(0, (remainingMs / DURATION_MS) * 100));
setCountdown(seconds);
setProgressPercent(percent);
}, 100);
return () => {
window.clearInterval(timer);
};
}, [challengeKey]);
if (!challenge) return null;
const playerName = challenge.challenger_name;
const wizardName = challenge.wizard_name || 'Grand Wizard';
return (
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/80 backdrop-blur-md p-4 animate-in fade-in duration-200">
<div className="bg-slate-900 border-2 border-purple-500/80 rounded-2xl w-full max-w-xl shadow-2xl p-6 relative overflow-hidden">
{/* Glowing cyber wizard header banner */}
<div className="absolute top-0 left-0 right-0 h-2 bg-gradient-to-r from-purple-500 via-fuchsia-500 to-indigo-500" />
{/* 5-second automatic progress bar */}
<div className="absolute top-2 left-0 right-0 h-1 bg-slate-800">
<div
className="h-full bg-purple-400 transition-all duration-100 ease-linear"
style={{ width: `${progressPercent}%` }}
/>
</div>
<div className="flex items-center justify-between mb-4 pb-2 border-b border-slate-800 mt-1">
<div className="flex items-center gap-2">
<span className="text-2xl">🧙</span>
<div>
<h2 className="text-base font-bold text-slate-100 font-mono tracking-wide flex items-center gap-2">
<span>WIZARD NPC DUEL: 3-BOUT D20 CHALLENGE</span>
</h2>
<p className="text-xs text-slate-400">
<span className="text-sky-300 font-semibold">{playerName}</span> vs{' '}
<span className="text-purple-300 font-semibold">{wizardName}</span>
</p>
</div>
</div>
<button
onClick={() => onCloseRef.current()}
className="text-slate-400 hover:text-slate-200 p-1 text-sm font-mono"
title="Dismiss now"
>
</button>
</div>
{/* 3 Bouts Breakdown */}
<div className="space-y-3 mb-5">
{challenge.bouts.map((bout) => (
<div
key={bout.bout_number}
className={`p-3 rounded-xl border flex items-center justify-between transition-colors ${
bout.winner === 'player'
? 'bg-sky-950/30 border-sky-500/50'
: bout.winner === 'wizard'
? 'bg-purple-950/30 border-purple-500/50'
: 'bg-slate-800/40 border-slate-700'
}`}
>
{/* Bout Index */}
<div className="w-16 font-mono text-xs font-bold text-slate-400">
Bout #{bout.bout_number}
</div>
{/* Player Bout Score */}
<div className="flex-1 text-left px-2">
<div className="text-xs font-bold text-slate-200 truncate">
{playerName}
</div>
<div className="text-[11px] font-mono text-slate-400 flex items-center gap-1">
<span>🎲 D20({bout.player_roll})</span>
<span>×</span>
<span>Str({bout.player_strength})</span>
<span>=</span>
<strong className="text-sky-400 text-xs">{bout.player_score}</strong>
</div>
</div>
{/* VS Divider */}
<div className="px-2 text-slate-600 font-mono text-xs font-black">VS</div>
{/* Wizard Bout Score */}
<div className="flex-1 text-right px-2">
<div className="text-xs font-bold text-purple-300 truncate">
{wizardName}
</div>
<div className="text-[11px] font-mono text-slate-400 flex items-center justify-end gap-1">
<strong className="text-purple-400 text-xs">{bout.wizard_score}</strong>
<span>=</span>
<span>Str({bout.wizard_strength})</span>
<span>×</span>
<span>🎲 D20({bout.wizard_roll})</span>
</div>
</div>
{/* Bout Outcome Badge */}
<div className="w-20 text-right">
<span
className={`text-[10px] font-mono px-2 py-0.5 rounded font-bold uppercase ${
bout.winner === 'player'
? 'bg-sky-900/80 text-sky-200 border border-sky-600'
: bout.winner === 'wizard'
? 'bg-purple-900/80 text-purple-200 border border-purple-600'
: 'bg-slate-700 text-slate-300'
}`}
>
{bout.winner === 'player'
? 'Player'
: bout.winner === 'wizard'
? 'Wizard'
: 'Tie'}
</span>
</div>
</div>
))}
</div>
{/* Overall Match Outcome Banner */}
<div
className={`p-4 rounded-xl border mb-4 text-center ${
challenge.player_won
? 'bg-emerald-950/40 border-emerald-500/60 text-emerald-200'
: 'bg-rose-950/40 border-rose-500/60 text-rose-200'
}`}
>
<div className="text-xs font-mono uppercase tracking-wider mb-1">
Duel Outcome: {challenge.player_bouts_won} - {challenge.wizard_bouts_won}
</div>
<div className="text-base font-extrabold flex items-center justify-center gap-2">
<span>{challenge.player_won ? '✨ CHALLENGE VICTORY ✨' : '💀 CHALLENGE DEFEAT 💀'}</span>
</div>
<div className="mt-2 text-xs font-mono space-y-1">
{challenge.player_won ? (
<div className="text-emerald-300 font-bold">
🎉 +{challenge.score_change} Score Points awarded! (Total: {challenge.new_score})
</div>
) : (
<div className="text-rose-300 font-bold">
{challenge.health_change < 0 ? (
<span>
💔 Lost {Math.abs(challenge.health_change)} Health! (Remaining HP: {challenge.new_health})
</span>
) : (
<span>
📉 Out of HP! Lost {Math.abs(challenge.score_change)} Score points! (Score: {challenge.new_score})
</span>
)}
</div>
)}
<div className="text-purple-300/80 text-[11px] pt-1">
🔮 The Wizard vanished in a puff of smoke and teleported to ({challenge.wizard_respawn_position?.x}, {challenge.wizard_respawn_position?.y}).
</div>
</div>
</div>
{/* Modal Controls / Auto-close countdown */}
<div className="flex items-center justify-between text-xs text-slate-400 font-mono">
<span>Auto-closing in {countdown}s...</span>
<button
onClick={() => onCloseRef.current()}
className="px-4 py-1.5 rounded-lg bg-slate-800 hover:bg-purple-700 text-slate-200 hover:text-white border border-slate-700 transition-colors font-semibold"
>
Acknowledge & Close
</button>
</div>
</div>
</div>
);
};