diff --git a/src/components/PlayerTab.jsx b/src/components/PlayerTab.jsx index 93ae9c8..1142997 100755 --- a/src/components/PlayerTab.jsx +++ b/src/components/PlayerTab.jsx @@ -2,6 +2,7 @@ import { useState, useEffect, useMemo, useCallback } from 'react'; import axios from 'axios'; import logger, { debug, info, warn, logApiCall, logApiError, logUserAction } from '../utils/logger'; import { Tooltip } from './DeathwatchRoller'; +import { XPBar, XPSummary } from './XPBar'; const STORAGE_SHOP_AUTHED = 'dw:shop:authedPlayer'; const STORAGE_SHOP_PLAYERS = 'dw:shop:players:v1'; @@ -936,6 +937,82 @@ function PlayerTab({ )} + {/* Experience Points Display - Minecraft-style XP Bar */} +
+
Experience Points (XP)
+ + {/* XP Bar */} +
+ +
+ 1 Characteristic = 500 XP | 1 Skill Rank = 100 XP | 1 Talent = 50 XP +
+
+ + {/* XP Summary */} +
+ +
+ + {/* GM Controls */} + {isGMLoggedIn() && ( +
+
GM Controls
+
+
+ Set Total XP: + +
+
+ Set Spent XP: + +
+
+ + + +
+
+
+ )} +
+ {/* Characteristics */}
Characteristics
@@ -1168,10 +1245,20 @@ function PlayerTab({
setRenown(e.target.value)} /> - - setXp(parseInt(e.target.value||'0'))} /> - - setXpSpent(parseInt(e.target.value||'0'))} /> + +
+ +
+
+
+ + setXp(parseInt(e.target.value||'0'))} /> +
+
+ + setXpSpent(parseInt(e.target.value||'0'))} /> +
+
diff --git a/src/components/XPBar.jsx b/src/components/XPBar.jsx new file mode 100644 index 0000000..91b28eb --- /dev/null +++ b/src/components/XPBar.jsx @@ -0,0 +1,95 @@ +/** + * XP Bar Component - Minecraft-style progress bar for experience points + * Shows progress toward next "level" with customizable thresholds + */ + +export function XPBar({ + currentXP = 0, + xpSpent = 0, + thresholdXP = 500, + showLabel = true, + compact = false +}) { + // Available XP is current minus spent + const availableXP = Math.max(0, currentXP - xpSpent); + + // Calculate progress toward next threshold + // Each "level" is thresholdXP points + const currentLevel = Math.floor(currentXP / thresholdXP); + const nextLevelThreshold = (currentLevel + 1) * thresholdXP; + const xpIntoLevel = currentXP % thresholdXP; + const progress = (xpIntoLevel / thresholdXP) * 100; + + const barHeight = compact ? 'h-2' : 'h-4'; + const textSize = compact ? 'text-xs' : 'text-sm'; + + return ( +
+ {/* XP Bar */} +
+ {/* Green fill */} +
+ {/* Optional inner glow effect */} + {progress > 10 && ( +
+ )} +
+ + {/* Label overlay */} + {showLabel && ( +
+ + {currentXP}/{nextLevelThreshold} XP + {availableXP > 0 && ({availableXP} avail)} + +
+ )} +
+ + {/* Debug info - only show if spent XP is tracked */} + {xpSpent > 0 && !compact && ( +
+ Lvl {currentLevel} + Spent: {xpSpent} +
+ )} +
+ ); +} + +/** + * XP Summary Card - Shows overall player XP status + */ +export function XPSummary({ currentXP = 0, xpSpent = 0, thresholdXP = 500 }) { + const availableXP = Math.max(0, currentXP - xpSpent); + const currentLevel = Math.floor(currentXP / thresholdXP); + + return ( +
+
+
+
Total XP
+
{currentXP}
+
+
+
Available XP
+
{availableXP}
+
+
+
Current Level
+
Level {currentLevel}
+
+
+ {xpSpent > 0 && ( +
+ Spent: {xpSpent} XP | Remaining: {availableXP} XP +
+ )} +
+ ); +} + +export default XPBar;