2026-09-05 21:10:18 +00:00
|
|
|
|
import React from 'react';
|
2026-09-05 21:20:39 +00:00
|
|
|
|
import type { BoardState, Player } from '../types';
|
2026-09-09 20:41:06 +00:00
|
|
|
|
import { PixelAvatar, getPlayerPieceType } from '../utils/pixelAvatars';
|
2026-09-05 21:10:18 +00:00
|
|
|
|
|
|
|
|
|
|
interface PlayerListProps {
|
2026-09-05 21:20:39 +00:00
|
|
|
|
boardState: BoardState;
|
2026-09-05 21:10:18 +00:00
|
|
|
|
selectedPlayer: Player | null;
|
|
|
|
|
|
onSelectPlayer: (player: Player) => void;
|
|
|
|
|
|
onRemovePlayer: (id: string) => void;
|
2026-09-05 21:55:07 +00:00
|
|
|
|
onOpenPartyModal?: () => void;
|
|
|
|
|
|
onDefeatParty?: (partyId: string) => void;
|
2026-09-05 22:09:18 +00:00
|
|
|
|
onFightBattle?: (challengerId: string, defenderId: string) => void;
|
2026-09-09 22:11:54 +00:00
|
|
|
|
onChallengeWizard?: (playerId: string) => void;
|
2026-09-05 21:10:18 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export const PlayerList: React.FC<PlayerListProps> = ({
|
2026-09-05 21:20:39 +00:00
|
|
|
|
boardState,
|
2026-09-05 21:10:18 +00:00
|
|
|
|
selectedPlayer,
|
|
|
|
|
|
onSelectPlayer,
|
|
|
|
|
|
onRemovePlayer,
|
2026-09-05 21:55:07 +00:00
|
|
|
|
onOpenPartyModal,
|
|
|
|
|
|
onDefeatParty,
|
2026-09-09 22:11:54 +00:00
|
|
|
|
onChallengeWizard,
|
2026-09-05 21:10:18 +00:00
|
|
|
|
}) => {
|
2026-09-05 21:55:07 +00:00
|
|
|
|
const { players, parties = [], turn } = boardState;
|
2026-09-05 21:20:39 +00:00
|
|
|
|
const currentTurnId = turn.current_player_id;
|
|
|
|
|
|
|
2026-09-05 21:55:07 +00:00
|
|
|
|
const partyMap = new Map<string, string>();
|
|
|
|
|
|
parties.forEach((p) => {
|
|
|
|
|
|
p.member_ids.forEach((mid) => partyMap.set(mid, p.name));
|
|
|
|
|
|
});
|
|
|
|
|
|
|
2026-09-09 22:11:54 +00:00
|
|
|
|
const isStarted = Boolean(turn.game_started);
|
|
|
|
|
|
const liveSelected = selectedPlayer
|
|
|
|
|
|
? players.find((p) => p.id === selectedPlayer.id) || selectedPlayer
|
|
|
|
|
|
: null;
|
|
|
|
|
|
const activePlayer = players.find((p) => p.id === currentTurnId) || null;
|
|
|
|
|
|
const candidate = liveSelected || activePlayer;
|
|
|
|
|
|
const candidateAdjacent = Boolean(
|
|
|
|
|
|
candidate &&
|
|
|
|
|
|
boardState.wizard &&
|
|
|
|
|
|
Math.max(
|
|
|
|
|
|
Math.abs(candidate.x - boardState.wizard.x),
|
|
|
|
|
|
Math.abs(candidate.y - boardState.wizard.y)
|
|
|
|
|
|
) <= 1
|
|
|
|
|
|
);
|
|
|
|
|
|
const candidateIsTurn = Boolean(candidate && isStarted && candidate.id === currentTurnId);
|
|
|
|
|
|
const candidateCanChallenge = Boolean(
|
|
|
|
|
|
candidateAdjacent &&
|
|
|
|
|
|
candidateIsTurn &&
|
|
|
|
|
|
(!candidate?.party_id || candidate?.is_party_leader)
|
|
|
|
|
|
);
|
|
|
|
|
|
|
2026-09-05 21:10:18 +00:00
|
|
|
|
return (
|
|
|
|
|
|
<div className="flex flex-col h-full bg-slate-900 border-l border-slate-800 w-80">
|
|
|
|
|
|
{/* Header */}
|
|
|
|
|
|
<div className="p-4 border-b border-slate-800 flex items-center justify-between">
|
|
|
|
|
|
<div className="flex items-center gap-2">
|
|
|
|
|
|
<span className="w-2.5 h-2.5 rounded-full bg-sky-400" />
|
2026-09-05 21:55:07 +00:00
|
|
|
|
<h3 className="font-semibold text-slate-100 text-sm">Active Bots & Parties</h3>
|
2026-09-05 21:10:18 +00:00
|
|
|
|
</div>
|
|
|
|
|
|
<span className="text-xs font-mono px-2 py-0.5 rounded-full bg-slate-800 text-slate-300 border border-slate-700">
|
2026-09-05 21:55:07 +00:00
|
|
|
|
{players.length} Bots
|
2026-09-05 21:10:18 +00:00
|
|
|
|
</span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
2026-09-05 21:55:07 +00:00
|
|
|
|
{/* Turn & Round Banner */}
|
2026-09-05 21:20:39 +00:00
|
|
|
|
{players.length > 0 && (
|
|
|
|
|
|
<div className="px-4 py-2.5 bg-slate-950/70 border-b border-slate-800/80 flex items-center justify-between text-xs font-mono">
|
|
|
|
|
|
<span className="text-slate-400">
|
|
|
|
|
|
Round <strong className="text-sky-400">{turn.round_number}</strong>
|
|
|
|
|
|
</span>
|
|
|
|
|
|
<span className="text-slate-400">
|
|
|
|
|
|
Turn <strong className="text-amber-400">#{turn.turn_number}</strong>
|
|
|
|
|
|
</span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
)}
|
|
|
|
|
|
|
2026-09-05 21:55:07 +00:00
|
|
|
|
{/* Parties Section */}
|
|
|
|
|
|
{parties.length > 0 && (
|
|
|
|
|
|
<div className="p-3 border-b border-slate-800 bg-slate-950/40">
|
|
|
|
|
|
<div className="text-[11px] font-mono text-slate-400 uppercase tracking-wider mb-2 flex items-center justify-between">
|
|
|
|
|
|
<span>🛡️ Active Parties ({parties.length})</span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div className="space-y-1.5">
|
|
|
|
|
|
{parties.map((party) => (
|
|
|
|
|
|
<div
|
|
|
|
|
|
key={party.id}
|
|
|
|
|
|
className="bg-slate-900/90 border border-sky-500/40 rounded-lg p-2 flex items-center justify-between text-xs"
|
|
|
|
|
|
>
|
|
|
|
|
|
<div className="min-w-0 flex-1">
|
|
|
|
|
|
<div className="font-bold text-sky-300 truncate flex items-center gap-1">
|
|
|
|
|
|
<span>👑</span> {party.name}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div className="text-[10px] text-slate-400 font-mono">
|
|
|
|
|
|
Leader: <strong className="text-amber-300">{party.leader_name}</strong> •{' '}
|
2026-09-05 22:09:18 +00:00
|
|
|
|
{party.member_ids.length} bots • <span className="text-amber-400 font-bold">⚡{party.total_strength}</span>
|
2026-09-05 21:55:07 +00:00
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
{onDefeatParty && (
|
|
|
|
|
|
<button
|
|
|
|
|
|
onClick={() => onDefeatParty(party.id)}
|
|
|
|
|
|
className="ml-2 bg-rose-950/80 hover:bg-rose-900 text-rose-300 border border-rose-700/60 px-2 py-1 rounded text-[10px] font-mono transition-all"
|
2026-09-05 22:09:18 +00:00
|
|
|
|
title="Defeat party: leader killed (-1 pt), leader respawns, remainder assigns new leader"
|
2026-09-05 21:55:07 +00:00
|
|
|
|
>
|
|
|
|
|
|
💥 Defeat
|
|
|
|
|
|
</button>
|
|
|
|
|
|
)}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
))}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
)}
|
|
|
|
|
|
|
2026-09-09 22:11:54 +00:00
|
|
|
|
{/* Wizard NPC Section */}
|
|
|
|
|
|
{boardState.wizard && (
|
|
|
|
|
|
<div className="p-3 border-b border-slate-800 bg-purple-950/20">
|
|
|
|
|
|
<div className="text-[11px] font-mono text-purple-300 uppercase tracking-wider mb-1.5 flex items-center justify-between">
|
|
|
|
|
|
<span>🧙 Wandering Wizard (NPC)</span>
|
|
|
|
|
|
<span className="text-[9px] bg-purple-900/60 px-1.5 py-0.5 rounded text-purple-300 border border-purple-700 font-mono">
|
|
|
|
|
|
CHALLENGE NPC
|
|
|
|
|
|
</span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div className="bg-slate-900/90 border border-purple-500/40 rounded-lg p-2 flex items-center justify-between text-xs">
|
|
|
|
|
|
<div className="min-w-0 flex-1">
|
|
|
|
|
|
<div className="font-bold text-purple-300 flex items-center gap-1.5">
|
|
|
|
|
|
<span>🧙♂️</span> {boardState.wizard.name}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div className="text-[10px] text-slate-400 font-mono mt-0.5">
|
|
|
|
|
|
pos: <span className="text-emerald-400">({boardState.wizard.x}, {boardState.wizard.y})</span> • Str: <span className="text-amber-400 font-bold">⚡{boardState.wizard.strength}</span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
{candidateAdjacent && onChallengeWizard ? (
|
|
|
|
|
|
<button
|
|
|
|
|
|
onClick={() => {
|
|
|
|
|
|
if (!candidate) return;
|
|
|
|
|
|
if (!isStarted) {
|
|
|
|
|
|
alert("Game has not started yet. Click 'Start Game' in header first.");
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (!candidateIsTurn) {
|
|
|
|
|
|
alert(`It is not ${candidate.name}'s turn! Wait for their turn to challenge.`);
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (candidate.party_id && !candidate.is_party_leader) {
|
|
|
|
|
|
alert("Only party leader can challenge the wizard.");
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
onChallengeWizard(candidate.id);
|
|
|
|
|
|
}}
|
|
|
|
|
|
disabled={!candidateCanChallenge}
|
|
|
|
|
|
className={`ml-2 px-2.5 py-1 rounded text-xs font-mono font-bold flex items-center gap-1 transition-all ${
|
|
|
|
|
|
candidateCanChallenge
|
|
|
|
|
|
? 'bg-purple-600 hover:bg-purple-500 text-white shadow-md shadow-purple-900/60 animate-pulse active:scale-95 cursor-pointer'
|
|
|
|
|
|
: 'bg-purple-950/60 text-purple-400/60 border border-purple-800/40 cursor-not-allowed text-[10px]'
|
|
|
|
|
|
}`}
|
|
|
|
|
|
title={
|
|
|
|
|
|
!candidateIsTurn
|
|
|
|
|
|
? `Wait for ${candidate?.name || 'bot'}'s turn`
|
|
|
|
|
|
: candidate?.party_id && !candidate.is_party_leader
|
|
|
|
|
|
? 'Only party leader can challenge'
|
|
|
|
|
|
: `Challenge Wizard with ${candidate?.name}!`
|
|
|
|
|
|
}
|
|
|
|
|
|
>
|
|
|
|
|
|
<span>⚔️</span>
|
|
|
|
|
|
<span>{candidateCanChallenge ? 'Challenge' : 'Wait Turn'}</span>
|
|
|
|
|
|
</button>
|
|
|
|
|
|
) : (
|
|
|
|
|
|
<div className="text-[10px] font-mono text-purple-400 bg-purple-950/80 border border-purple-800 px-2 py-1 rounded text-center">
|
|
|
|
|
|
D20 Duel
|
|
|
|
|
|
</div>
|
|
|
|
|
|
)}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
)}
|
|
|
|
|
|
|
2026-09-05 21:55:07 +00:00
|
|
|
|
{/* Action to form party */}
|
|
|
|
|
|
{players.length >= 2 && onOpenPartyModal && (
|
|
|
|
|
|
<div className="p-2 border-b border-slate-800 bg-slate-950/30">
|
|
|
|
|
|
<button
|
|
|
|
|
|
onClick={onOpenPartyModal}
|
|
|
|
|
|
className="w-full bg-slate-800 hover:bg-sky-700 text-slate-200 hover:text-white py-1.5 px-3 rounded-lg text-xs font-mono border border-slate-700 transition-colors flex items-center justify-center gap-1.5"
|
|
|
|
|
|
>
|
|
|
|
|
|
<span>🤝</span> Form / Join Party
|
|
|
|
|
|
</button>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
)}
|
|
|
|
|
|
|
2026-09-05 21:10:18 +00:00
|
|
|
|
{/* Players List */}
|
|
|
|
|
|
<div className="flex-1 overflow-y-auto p-3 space-y-2">
|
|
|
|
|
|
{players.length === 0 ? (
|
|
|
|
|
|
<div className="text-center py-12 px-4 text-slate-500 text-xs">
|
|
|
|
|
|
<div className="text-2xl mb-2">🤖</div>
|
|
|
|
|
|
No players registered yet.
|
|
|
|
|
|
<br />
|
2026-09-05 21:20:39 +00:00
|
|
|
|
Register a player or spawn a random bot to start taking turns!
|
2026-09-05 21:10:18 +00:00
|
|
|
|
</div>
|
|
|
|
|
|
) : (
|
|
|
|
|
|
players.map((player) => {
|
|
|
|
|
|
const isSelected = selectedPlayer?.id === player.id;
|
2026-09-05 21:20:39 +00:00
|
|
|
|
const isCurrentTurn = currentTurnId === player.id;
|
2026-09-05 21:55:07 +00:00
|
|
|
|
const isLeader = player.is_party_leader;
|
|
|
|
|
|
const partyName = partyMap.get(player.id);
|
2026-09-05 21:20:39 +00:00
|
|
|
|
|
2026-09-05 21:10:18 +00:00
|
|
|
|
return (
|
|
|
|
|
|
<div
|
|
|
|
|
|
key={player.id}
|
|
|
|
|
|
onClick={() => onSelectPlayer(player)}
|
|
|
|
|
|
className={`group flex items-center justify-between p-2.5 rounded-xl border transition-all cursor-pointer ${
|
2026-09-05 21:20:39 +00:00
|
|
|
|
isCurrentTurn
|
|
|
|
|
|
? 'bg-amber-950/25 border-amber-500/70 shadow-md shadow-amber-500/10'
|
|
|
|
|
|
: isSelected
|
2026-09-05 21:10:18 +00:00
|
|
|
|
? 'bg-sky-950/40 border-sky-500 shadow-md shadow-sky-500/10'
|
|
|
|
|
|
: 'bg-slate-950/60 border-slate-800 hover:border-slate-700 hover:bg-slate-800/40'
|
|
|
|
|
|
}`}
|
|
|
|
|
|
>
|
|
|
|
|
|
<div className="flex items-center gap-3 min-w-0">
|
2026-09-09 20:41:06 +00:00
|
|
|
|
<div className="relative flex-shrink-0">
|
2026-09-05 21:20:39 +00:00
|
|
|
|
<div
|
2026-09-09 20:41:06 +00:00
|
|
|
|
className="w-9 h-9 rounded-lg flex items-center justify-center bg-slate-950/80 border p-0.5 shadow transition-transform"
|
2026-09-05 21:20:39 +00:00
|
|
|
|
style={{
|
2026-09-09 20:41:06 +00:00
|
|
|
|
borderColor: isLeader
|
|
|
|
|
|
? '#fbbf24'
|
|
|
|
|
|
: isCurrentTurn
|
|
|
|
|
|
? '#f59e0b'
|
|
|
|
|
|
: `${player.color}66`,
|
2026-09-05 21:55:07 +00:00
|
|
|
|
boxShadow: isLeader
|
2026-09-09 20:41:06 +00:00
|
|
|
|
? '0 0 12px #fbbf2455'
|
2026-09-05 21:55:07 +00:00
|
|
|
|
: isCurrentTurn
|
2026-09-09 20:41:06 +00:00
|
|
|
|
? '0 0 12px #f59e0b55'
|
|
|
|
|
|
: `0 0 8px ${player.color}33`,
|
2026-09-05 21:20:39 +00:00
|
|
|
|
}}
|
|
|
|
|
|
>
|
2026-09-09 20:41:06 +00:00
|
|
|
|
<PixelAvatar
|
|
|
|
|
|
pieceType={getPlayerPieceType(player)}
|
|
|
|
|
|
color={player.color}
|
|
|
|
|
|
isLeader={isLeader}
|
|
|
|
|
|
size={32}
|
|
|
|
|
|
/>
|
2026-09-05 21:20:39 +00:00
|
|
|
|
</div>
|
2026-09-05 21:55:07 +00:00
|
|
|
|
{isLeader && (
|
|
|
|
|
|
<span className="absolute -top-2 -right-1 text-[12px] leading-none">
|
2026-09-05 21:20:39 +00:00
|
|
|
|
👑
|
|
|
|
|
|
</span>
|
|
|
|
|
|
)}
|
2026-09-05 21:10:18 +00:00
|
|
|
|
</div>
|
2026-09-05 21:20:39 +00:00
|
|
|
|
|
2026-09-05 21:10:18 +00:00
|
|
|
|
<div className="min-w-0">
|
2026-09-05 21:20:39 +00:00
|
|
|
|
<div className="flex items-center gap-1.5">
|
|
|
|
|
|
<span className="text-xs font-semibold text-slate-200 truncate">
|
|
|
|
|
|
{player.name}
|
|
|
|
|
|
</span>
|
2026-09-05 21:55:07 +00:00
|
|
|
|
<span
|
|
|
|
|
|
className={`text-[10px] font-mono px-1.5 py-0.2 rounded ${
|
|
|
|
|
|
player.score < 0
|
|
|
|
|
|
? 'bg-rose-950 text-rose-300 border border-rose-800'
|
|
|
|
|
|
: player.score > 0
|
|
|
|
|
|
? 'bg-emerald-950 text-emerald-300 border border-emerald-800'
|
|
|
|
|
|
: 'bg-slate-800 text-slate-400'
|
|
|
|
|
|
}`}
|
|
|
|
|
|
>
|
|
|
|
|
|
{player.score >= 0 ? `+${player.score}` : player.score} pts
|
|
|
|
|
|
</span>
|
2026-09-05 21:10:18 +00:00
|
|
|
|
</div>
|
|
|
|
|
|
<div className="text-[11px] font-mono text-slate-400">
|
2026-09-09 22:11:54 +00:00
|
|
|
|
pos: <span className="text-emerald-400">({player.x}, {player.y})</span> • Str: <span className="text-amber-400">⚡{player.strength || 1}</span> • HP: <span className="text-rose-400 font-bold">❤️{player.health ?? 10}</span>
|
2026-09-05 21:55:07 +00:00
|
|
|
|
{partyName && (
|
|
|
|
|
|
<span className="ml-1 text-sky-400 truncate font-sans">
|
|
|
|
|
|
• {isLeader ? 'Leader' : 'Squad'}
|
2026-09-05 21:20:39 +00:00
|
|
|
|
</span>
|
|
|
|
|
|
)}
|
2026-09-05 21:10:18 +00:00
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
2026-09-09 22:11:54 +00:00
|
|
|
|
<div className="flex items-center gap-1.5 opacity-70 group-hover:opacity-100 transition-opacity">
|
|
|
|
|
|
{boardState.wizard &&
|
|
|
|
|
|
Math.max(
|
|
|
|
|
|
Math.abs(player.x - boardState.wizard.x),
|
|
|
|
|
|
Math.abs(player.y - boardState.wizard.y)
|
|
|
|
|
|
) <= 1 &&
|
|
|
|
|
|
onChallengeWizard && (
|
|
|
|
|
|
<button
|
|
|
|
|
|
onClick={(e) => {
|
|
|
|
|
|
e.stopPropagation();
|
|
|
|
|
|
if (!isStarted) {
|
|
|
|
|
|
alert("Game has not started yet. Click 'Start Game' in header first.");
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (currentTurnId !== player.id) {
|
|
|
|
|
|
alert(`It is not ${player.name}'s turn! Wait for their turn to challenge.`);
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (player.party_id && !player.is_party_leader) {
|
|
|
|
|
|
alert("Only party leader can challenge the wizard.");
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
onChallengeWizard(player.id);
|
|
|
|
|
|
}}
|
|
|
|
|
|
disabled={
|
|
|
|
|
|
!isStarted ||
|
|
|
|
|
|
currentTurnId !== player.id ||
|
|
|
|
|
|
Boolean(player.party_id && !player.is_party_leader)
|
|
|
|
|
|
}
|
|
|
|
|
|
className={`px-2 py-0.5 rounded text-[10px] font-mono font-bold flex items-center gap-1 transition-all ${
|
|
|
|
|
|
isStarted &&
|
|
|
|
|
|
currentTurnId === player.id &&
|
|
|
|
|
|
(!player.party_id || player.is_party_leader)
|
|
|
|
|
|
? 'bg-purple-600 hover:bg-purple-500 text-white shadow-md shadow-purple-900/60 animate-pulse active:scale-95 cursor-pointer'
|
|
|
|
|
|
: 'bg-purple-950/50 text-purple-400/50 border border-purple-800/40 cursor-not-allowed'
|
|
|
|
|
|
}`}
|
|
|
|
|
|
title={
|
|
|
|
|
|
currentTurnId === player.id
|
|
|
|
|
|
? `Challenge Wizard with ${player.name}!`
|
|
|
|
|
|
: `Wait for ${player.name}'s turn to challenge`
|
|
|
|
|
|
}
|
|
|
|
|
|
>
|
|
|
|
|
|
<span>🧙</span>
|
|
|
|
|
|
<span>Duel</span>
|
|
|
|
|
|
</button>
|
|
|
|
|
|
)}
|
2026-09-05 21:10:18 +00:00
|
|
|
|
<button
|
|
|
|
|
|
onClick={(e) => {
|
|
|
|
|
|
e.stopPropagation();
|
|
|
|
|
|
onRemovePlayer(player.id);
|
|
|
|
|
|
}}
|
|
|
|
|
|
title="Remove Bot"
|
|
|
|
|
|
className="p-1 rounded text-slate-500 hover:text-red-400 hover:bg-red-950/40 transition-colors text-xs"
|
|
|
|
|
|
>
|
|
|
|
|
|
✕
|
|
|
|
|
|
</button>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
);
|
|
|
|
|
|
})
|
|
|
|
|
|
)}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
{/* Footer Info */}
|
|
|
|
|
|
<div className="p-3 border-t border-slate-800 bg-slate-950/40 text-[11px] font-mono text-slate-400 flex justify-between items-center">
|
|
|
|
|
|
<span>Coordinate range:</span>
|
|
|
|
|
|
<span className="text-sky-400 font-semibold">[0,0] → [64,64]</span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
);
|
|
|
|
|
|
};
|