feat: ProCanvas refinements, gamification hooks, a11y updates, index.css polish, gitignore cleanup, and profile asset addition

This commit is contained in:
Satyam
2026-02-25 19:10:48 +05:30
parent 5fef584d7d
commit 3d215f6db3
6 changed files with 605 additions and 191 deletions
+1
View File
@@ -28,4 +28,5 @@ dist-ssr
*.md
!README.md
!data_schema.txt
!docs/**/*.md
.env
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 MiB

+1 -1
View File
@@ -4,7 +4,7 @@ import { useAuth } from './AuthContext';
const GamificationContext = createContext();
const INITIAL_STATE = {
xp: 3550,
xp: 3360,
level: 12,
streak: 4,
todayPoints: 150,
+31 -15
View File
@@ -65,27 +65,43 @@ export const useGamification = (user, role = 'FIELD_AGENT') => {
STAGES = [
{
id: 1,
title: "Stage 1: Groundwork",
requirement: { type: "doorsKnocked", amount: 20 },
reward: { type: "coupon", name: "$25 Uber Eats Card", icon: "uber" },
progress: Math.min((user.doorsKnocked || 0), 20),
goal: 20
title: "Daily Grind",
requirement: { type: "dailyQuests", amount: 5 },
reward: { type: "xp", name: "+500 XP Boost", icon: "zap" },
progress: 5,
goal: 5
},
{
id: 2,
title: "Stage 2: Connections",
requirement: { type: "appointmentsSet", amount: 10 },
reward: { type: "giftcard", name: "$50 Amazon Card", icon: "amazon" },
progress: Math.min((user.appointmentsSet || 0), 10),
goal: 10
title: "Door Warrior",
requirement: { type: "doorsKnocked", amount: 40 },
reward: { type: "coupon", name: "$25 Gift Card", icon: "gift" },
progress: 40,
goal: 40
},
{
id: 3,
title: "Stage 3: Rainmaker",
requirement: { type: "leadsGained", amount: 25 },
reward: { type: "cash", name: "$150 Performance Bonus", icon: "cash" },
progress: Math.min((user.leadsGained || 0), 25),
goal: 25
title: "Lead Machine",
requirement: { type: "leadsGained", amount: 10 },
reward: { type: "cash", name: "$50 Cash Bonus", icon: "cash" },
progress: 4,
goal: 10
},
{
id: 4,
title: "Closer Mode",
requirement: { type: "appointmentsSet", amount: 5 },
reward: { type: "giftcard", name: "$100 Amazon", icon: "amazon" },
progress: 0,
goal: 5
},
{
id: 5,
title: "Legend Run",
requirement: { type: "deals", amount: 3 },
reward: { type: "cash", name: "$250 Bonus", icon: "cash" },
progress: 0,
goal: 3
}
];
}
+2
View File
@@ -5,6 +5,8 @@
@import url('https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500;700&display=swap');
/* Inter: "The Engineer" — body text, UI, navigation (already system-available, loaded for consistency) */
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;900&display=swap');
/* Barlow Condensed: "The Arcade Font" — section headers, action labels, sports display text */
@import url('https://fonts.googleapis.com/css2?family=Barlow+Condensed:ital,wght@0,700;0,900;1,700;1,900&display=swap');
@tailwind base;
@tailwind components;
+570 -175
View File
@@ -1,4 +1,5 @@
import React, { useState, useEffect } from 'react';
import profilePic from '../assets/images/Profile_Pic_1_Agent.png';
import { motion, AnimatePresence } from 'framer-motion';
import { useNavigate } from 'react-router-dom';
import {
@@ -15,12 +16,12 @@ import LevelUpModal from '../components/ProCanvas/gamification/LevelUpModal';
import { SpotlightCard } from '../components/SpotlightCard';
import { AnimatedCounter } from '../components/AnimatedCounter';
// --- NEOMORPHIC STYLING CONSTANTS ---
const NEO_PANEL_CLASS = "bg-zinc-900/40 backdrop-blur-3xl border border-white/5 shadow-[8px_8px_16px_rgba(0,0,0,0.4),-8px_-8px_16px_rgba(255,255,255,0.02)] rounded-2xl relative overflow-hidden transition-all duration-300";
const NEON_GREEN = "text-[#39ff14] drop-shadow-[0_0_8px_rgba(57,255,20,0.4)]";
const NEON_GOLD = "text-[#fda913] drop-shadow-[0_0_8px_rgba(253,169,19,0.4)]";
const NEON_ORANGE = "text-[#ff4500] drop-shadow-[0_0_8px_rgba(255,69,0,0.4)]";
const NEON_BLUE = "text-[#00f0ff] drop-shadow-[0_0_8px_rgba(0,240,255,0.4)]";
// --- ARCADE SPORTS STYLING CONSTANTS ---
const NEO_PANEL_CLASS = "bg-[#0a0d1a]/90 backdrop-blur-xl border-2 border-white/[0.08] shadow-[0_8px_32px_rgba(0,0,0,0.7),inset_0_1px_0_rgba(255,255,255,0.07)] rounded-xl relative overflow-hidden transition-all duration-300";
const NEON_GREEN = "text-[#AAFF00] drop-shadow-[0_0_10px_rgba(170,255,0,0.6)]";
const NEON_GOLD = "text-[#fda913] drop-shadow-[0_0_12px_rgba(253,169,19,0.7)]";
const NEON_ORANGE = "text-[#ff4500] drop-shadow-[0_0_12px_rgba(255,69,0,0.7)]";
const NEON_BLUE = "text-[#00f0ff] drop-shadow-[0_0_12px_rgba(0,240,255,0.7)]";
// -----------------------------------------------------------------
// SHARED UI COMPONENTS
@@ -28,16 +29,21 @@ const NEON_BLUE = "text-[#00f0ff] drop-shadow-[0_0_8px_rgba(0,240,255,0.4)]";
const NeoCard = ({ children, className = "", innerClassName = "", spotlightColor = "rgba(255, 255, 255, 0.05)" }) => (
<SpotlightCard className={`${NEO_PANEL_CLASS} h-full w-full ${className}`} spotlightColor={spotlightColor}>
{/* Arcade corner bracket accents */}
<div className="absolute top-0 left-0 w-6 h-6 border-t-2 border-l-2 border-[#fda913]/50 z-20 pointer-events-none" />
<div className="absolute top-0 right-0 w-6 h-6 border-t-2 border-r-2 border-[#fda913]/50 z-20 pointer-events-none" />
<div className="absolute bottom-0 left-0 w-6 h-6 border-b-2 border-l-2 border-[#fda913]/50 z-20 pointer-events-none" />
<div className="absolute bottom-0 right-0 w-6 h-6 border-b-2 border-r-2 border-[#fda913]/50 z-20 pointer-events-none" />
<div className={`relative z-10 w-full h-full p-4 md:p-6 flex flex-col ${innerClassName}`}>
{children}
</div>
</SpotlightCard>
);
const ProgressBar = ({ progress, goal, colorClass = "bg-[#39ff14]", shadowClass = "shadow-[0_0_10px_#39ff14]" }) => {
const ProgressBar = ({ progress, goal, colorClass = "bg-[#AAFF00]", shadowClass = "shadow-[0_0_10px_#AAFF00]" }) => {
const pct = Math.min((progress / goal) * 100, 100);
return (
<div className="h-2 w-full bg-black/80 rounded-full shadow-[inset_0_2px_4px_rgba(0,0,0,0.6)] border border-white/5 relative overflow-hidden flex-shrink-0">
<div className="h-3 w-full bg-black/80 rounded-full shadow-[inset_0_2px_4px_rgba(0,0,0,0.8)] border border-white/5 relative overflow-hidden flex-shrink-0">
<motion.div
initial={{ width: 0 }}
animate={{ width: `${pct}%` }}
@@ -48,66 +54,289 @@ const ProgressBar = ({ progress, goal, colorClass = "bg-[#39ff14]", shadowClass
);
};
// -----------------------------------------------------------------
// HOOKS
// -----------------------------------------------------------------
const useMonthResetCountdown = () => {
const [timeLeft, setTimeLeft] = useState('');
useEffect(() => {
const calc = () => {
const now = new Date();
const end = new Date(now.getFullYear(), now.getMonth() + 1, 1);
const diff = end.getTime() - now.getTime();
if (diff <= 0) { setTimeLeft('Resetting...'); return; }
const d = Math.floor(diff / 86400000);
const h = Math.floor((diff % 86400000) / 3600000);
const m = Math.floor((diff % 3600000) / 60000);
const s = Math.floor((diff % 60000) / 1000);
setTimeLeft(`${d}d ${String(h).padStart(2, '0')}h ${String(m).padStart(2, '0')}m ${String(s).padStart(2, '0')}s`);
};
calc();
const id = setInterval(calc, 1000);
return () => clearInterval(id);
}, []);
return timeLeft;
};
// -----------------------------------------------------------------
// ROLE-AGNOSTIC WIDGETS
// -----------------------------------------------------------------
const OperatorProfile = ({ gamestate, user, className = "" }) => {
const RANK_TIERS = {
'Rookie': { color: '#CD7F32', glow: 'rgba(205,127,50,0.55)', label: 'BRONZE' },
'Field Runner': { color: '#A8A9AD', glow: 'rgba(168,169,173,0.55)', label: 'SILVER' },
'Roof Warrior': { color: '#fda913', glow: 'rgba(253,169,19,0.65)', label: 'GOLD' },
'Territory Titan': { color: '#A855F7', glow: 'rgba(168,85,247,0.65)', label: 'EPIC' },
'Legendary Closer': { color: '#AAFF00', glow: 'rgba(170,255,0,0.65)', label: 'LEGENDARY' },
};
const RANK_PROGRESSION = ['Rookie', 'Field Runner', 'Roof Warrior', 'Territory Titan', 'Legendary Closer'];
const getNextRankTitle = (currentTitle) => {
const idx = RANK_PROGRESSION.indexOf(currentTitle);
return idx >= 0 && idx < RANK_PROGRESSION.length - 1 ? RANK_PROGRESSION[idx + 1] : 'MAX RANK';
};
const BADGE_CONFIG = {
default: { color: '#00f0ff', glow: 'rgba(0,240,255,0.65)' },
Hunter: { color: '#ff4500', glow: 'rgba(255,69,0,0.65)' },
Storm: { color: '#fda913', glow: 'rgba(253,169,19,0.65)' },
Star: { color: '#FF1493', glow: 'rgba(255,20,147,0.65)' },
Master: { color: '#AAFF00', glow: 'rgba(170,255,0,0.65)' },
};
const OperatorProfile = ({ gamestate, user, badges = [], className = "" }) => {
const tier = RANK_TIERS[gamestate.currentTitle] || RANK_TIERS['Field Runner'];
const initials = user.name.split(' ').map(n => n[0]).join('').slice(0, 2);
const displayBadges = badges.slice(0, 3);
const PIN_TILTS = [-7, 0, 7];
const getBadgeIcon = (badge) => {
if (badge.id?.includes("Hunter")) return Crosshair;
if (badge.id?.includes("Storm")) return Zap;
if (badge.id?.includes("Star")) return Star;
if (badge.id?.includes("Master")) return Map;
return Shield;
};
return (
<NeoCard spotlightColor="rgba(253, 169, 19, 0.1)" className={className} innerClassName="justify-center">
<div className="flex flex-col md:flex-row items-center gap-6 md:gap-8">
<div className="relative w-28 h-28 md:w-32 md:h-32 flex-shrink-0">
<motion.svg
animate={{ rotate: 360 }}
transition={{ duration: 30, repeat: Infinity, ease: "linear" }}
className="absolute inset-0 w-full h-full text-[#fda913]/30"
viewBox="0 0 100 100"
<div className="flex flex-col md:flex-row items-center gap-6 md:gap-10">
{/* ── PLAYER CARD PORTRAIT ── */}
<div className="relative flex-shrink-0">
{/* Trading card frame */}
<div
className="relative overflow-hidden rounded-xl"
style={{
width: '130px',
aspectRatio: '3/4',
border: `3px solid ${tier.color}`,
boxShadow: `0 0 22px ${tier.glow}, 0 0 60px ${tier.glow.replace(/[\d.]+\)$/, '0.18)')}, 0 8px 24px rgba(0,0,0,0.6)`,
}}
>
<circle cx="50" cy="50" r="48" fill="none" stroke="currentColor" strokeWidth="1" strokeDasharray="4 4" />
<circle cx="50" cy="50" r="42" fill="none" stroke="currentColor" strokeWidth="2" strokeDasharray="20 10" />
</motion.svg>
<div className="absolute inset-3 rounded-full bg-zinc-900 border border-white/10 shadow-[inset_4px_4px_10px_rgba(0,0,0,0.8),inset_-4px_-4px_10px_rgba(255,255,255,0.05)] flex items-center justify-center overflow-hidden">
<span className="text-3xl md:text-4xl font-black text-transparent bg-clip-text bg-gradient-to-br from-white to-zinc-500 md:translate-y-0.5">
{user.name.split(' ').map(n => n[0]).join('')}
</span>
{/* Rank tier ribbon at top */}
<div className="absolute top-0 inset-x-0 flex justify-center z-10 pt-1">
<span
className="font-['Barlow_Condensed'] font-bold text-[10px] tracking-[0.2em] uppercase px-3 py-0.5"
style={{
color: tier.color,
background: 'rgba(0,0,0,0.75)',
borderBottom: `1px solid ${tier.color}40`,
}}
>{tier.label}</span>
</div>
{/* Photo or initials */}
{user.profilePhoto ? (
<img
src={user.profilePhoto}
alt={user.name}
className="w-full h-full object-cover object-top"
/>
) : (
<div className="w-full h-full flex items-center justify-center relative" style={{ background: '#070A18' }}>
{/* Diagonal stripe pattern */}
<div
className="absolute inset-0 opacity-[0.12]"
style={{
backgroundImage: `repeating-linear-gradient(45deg, ${tier.color} 0px, ${tier.color} 1.5px, transparent 1.5px, transparent 14px)`,
}}
/>
<span
className="font-['Barlow_Condensed'] font-bold relative z-10 leading-none"
style={{
fontSize: '64px',
color: tier.color,
textShadow: `0 0 30px ${tier.glow}, 0 0 60px ${tier.glow.replace(/[\d.]+\)$/, '0.3)')}`,
}}
>{initials}</span>
</div>
)}
{/* Gloss overlay — top catch light + bottom vignette */}
<div className="absolute inset-0 bg-gradient-to-b from-white/[0.09] via-transparent to-black/50 pointer-events-none" />
</div>
{/* ── BADGE PINS — overlap portrait bottom edge ── */}
{displayBadges.length > 0 && (
<div className="absolute -bottom-4 left-1/2 -translate-x-1/2 flex gap-2 z-20">
{displayBadges.map((badge, i) => {
const BadgeIcon = getBadgeIcon(badge);
const isUnlocked = badge.status === 'unlocked';
return (
<div
key={badge.id || i}
title={badge.title}
className="w-9 h-9 rounded-full flex items-center justify-center flex-shrink-0 cursor-default"
style={{
transform: `rotate(${PIN_TILTS[i]}deg)`,
background: isUnlocked
? `radial-gradient(circle at 35% 30%, #232640, #0a0d1a)`
: 'radial-gradient(circle at 35% 30%, #1a1a1a, #0a0a0a)',
border: `2.5px solid ${isUnlocked ? tier.color : '#333'}`,
boxShadow: isUnlocked
? `0 0 10px ${tier.glow}, inset 0 1px 0 rgba(255,255,255,0.18)`
: 'none',
opacity: isUnlocked ? 1 : 0.35,
filter: isUnlocked ? 'none' : 'grayscale(1)',
}}
>
<BadgeIcon size={14} style={{ color: isUnlocked ? tier.color : '#555' }} />
</div>
);
})}
</div>
)}
</div>
<div className="flex-1 text-center md:text-left z-10 w-full flex flex-col justify-center">
<div className="flex justify-between items-start mb-4 gap-4 md:gap-8 w-full">
<div className="min-w-0 flex-1">
<div className="flex items-center gap-2 mb-1">
<p className="text-[10px] font-mono text-zinc-500 tracking-[0.2em] uppercase truncate">Op ID: {user.empId || 'UNK-99'}</p>
<div className="flex items-center gap-1 bg-orange-500/10 text-orange-500 px-2 py-0.5 rounded-full border border-orange-500/20">
<motion.div animate={{ scale: [1, 1.2, 1] }} transition={{ repeat: Infinity, duration: 2 }}>
<Flame size={12} className="fill-current drop-shadow-[0_0_5px_rgba(249,115,22,0.8)]" />
</motion.div>
<span className="text-[10px] font-bold font-mono">{gamestate.streak} Day Streak</span>
</div>
</div>
<h2 className="text-2xl md:text-3xl font-black text-white tracking-wide truncate">{user.name}</h2>
</div>
<div className="flex flex-col items-end flex-shrink-0">
<span className={`text-xl font-black ${NEON_GOLD}`}>LVL <AnimatedCounter to={gamestate.level} duration={1} /></span>
<span className="text-xs font-mono text-zinc-400 tracking-widest uppercase whitespace-nowrap">{gamestate.currentTitle}</span>
{/* ── INFO COLUMN ── */}
<div className="flex-1 w-full flex flex-col gap-2.5 text-center md:text-left mt-2 md:mt-0">
{/* Row 1: Player ID + Level badge */}
<div className="flex items-center justify-between">
<p className="text-[10px] font-mono text-zinc-500 tracking-[0.2em] uppercase">
Player ID: {user.empId || 'P-99'}
</p>
<span
className="font-['Barlow_Condensed'] font-bold text-xl leading-none"
style={{ color: '#fda913', textShadow: '0 0 14px rgba(253,169,19,0.75)' }}
>
LVL <AnimatedCounter to={gamestate.level} duration={1} />
</span>
</div>
{/* Row 2: Player name */}
<h2
className="font-['Barlow_Condensed'] font-bold italic text-white leading-none truncate"
style={{ fontSize: 'clamp(1.6rem, 3.5vw, 2.4rem)' }}
>
{user.name.toUpperCase()}
</h2>
{/* Row 3: RANK TITLE — primary status identity */}
<div className="flex flex-col gap-0.5 justify-center md:justify-start">
<h3
className="font-['Barlow_Condensed'] font-black uppercase leading-none tracking-tight"
style={{
fontSize: 'clamp(1.35rem, 3vw, 2rem)',
color: tier.color,
textShadow: `0 0 24px ${tier.glow}, 0 0 60px ${tier.glow.replace(/[\d.]+\)$/, '0.28)')}, 0 2px 0 rgba(0,0,0,0.8)`,
WebkitTextStroke: `0.5px ${tier.color}88`,
}}
>
{gamestate.currentTitle}
</h3>
<p
className="font-mono text-[9px] uppercase tracking-[0.28em] leading-none"
style={{ color: tier.color, opacity: 0.55 }}
>
{tier.label} TIER
</p>
</div>
{/* Row 4: Day Streak combo strip */}
<div className="flex items-center gap-3 bg-black/50 border border-[#ff4500]/25 rounded-lg px-4 py-2.5 shadow-[0_0_16px_rgba(255,69,0,0.08),inset_0_1px_0_rgba(255,255,255,0.04)]">
<motion.div
animate={{ scale: [1, 1.18, 1], rotate: [-8, 8, -8] }}
transition={{ repeat: Infinity, duration: 1.6, ease: "easeInOut" }}
className="flex-shrink-0"
>
<Flame
size={36}
className="fill-current"
style={{
color: '#ff4500',
filter: 'drop-shadow(0 0 12px rgba(255,69,0,0.9)) drop-shadow(0 0 24px rgba(255,120,0,0.5))',
}}
/>
</motion.div>
<motion.span
className="font-['Barlow_Condensed'] font-bold text-white leading-none flex-shrink-0"
style={{ fontSize: '3.2rem', textShadow: '0 0 24px rgba(255,69,0,0.7)' }}
initial={{ scale: 0.7 }}
animate={{ scale: 1 }}
transition={{ type: "spring", stiffness: 350, damping: 18 }}
>
<AnimatedCounter to={gamestate.streak} />
</motion.span>
<div className="flex flex-col leading-tight">
<span className="font-['Barlow_Condensed'] font-bold text-sm text-zinc-300 uppercase tracking-wide">Day</span>
<span className="font-['Barlow_Condensed'] font-bold text-sm text-zinc-300 uppercase tracking-wide">Streak</span>
</div>
</div>
<div className="w-full mt-2">
<div className="flex justify-between text-xs font-mono mb-2">
<span className="text-zinc-400">XP <span className="text-white font-bold"><AnimatedCounter to={gamestate.xp} /></span></span>
<span className="text-zinc-500">NEXT <span className="text-zinc-300">{gamestate.nextLevelXP.toLocaleString()}</span></span>
{/* Row 5: XP section — big stats + bar */}
<div className="w-full">
{/* Stat callouts above bar */}
<div className="flex justify-between items-end mb-2">
{/* Total XP — left */}
<div className="flex flex-col gap-0.5">
<span className="font-mono text-[9px] uppercase tracking-widest text-zinc-500">Total XP</span>
<span
className="font-['Barlow_Condensed'] font-black leading-none"
style={{ fontSize: 'clamp(1.4rem, 2.8vw, 1.9rem)', color: '#ffffff', textShadow: '0 0 16px rgba(255,255,255,0.2)' }}
>
<AnimatedCounter to={gamestate.xp} /><span className="text-xs text-zinc-400 ml-1 font-bold">XP</span>
</span>
</div>
{/* XP to go — right */}
<div className="flex flex-col gap-0.5 items-end">
<span className="font-mono text-[9px] uppercase tracking-widest text-zinc-500">To Level Up</span>
<span
className="font-['Barlow_Condensed'] font-black leading-none"
style={{
fontSize: 'clamp(1.4rem, 2.8vw, 1.9rem)',
color: '#ff4500',
textShadow: '0 0 18px rgba(255,69,0,0.7), 0 0 40px rgba(255,69,0,0.25)',
}}
>
{Math.max(0, (gamestate.nextLevelXP || 0) - gamestate.xp).toLocaleString()}<span className="text-xs ml-1 font-bold" style={{ color: '#ff4500' }}>XP</span>
</span>
</div>
</div>
{/* Using context's exact progressPercent for precision inside the progress bar */}
<div className="h-2 w-full bg-black/80 rounded-full shadow-[inset_0_2px_4px_rgba(0,0,0,0.6)] border border-white/5 relative overflow-hidden flex-shrink-0">
{/* Progress bar */}
<div className="h-3.5 w-full bg-black/80 rounded-full shadow-[inset_0_2px_4px_rgba(0,0,0,0.8)] border border-white/5 relative overflow-hidden">
<motion.div
initial={{ width: 0 }}
animate={{ width: `${gamestate.progressPercent}%` }}
transition={{ type: "spring", stiffness: 50, damping: 15 }}
className={`absolute top-0 left-0 h-full rounded-full bg-gradient-to-r from-amber-600 to-[#fda913] shadow-[0_0_15px_#fda913]`}
className="absolute top-0 left-0 h-full rounded-full bg-gradient-to-r from-amber-600 to-[#fda913] shadow-[0_0_20px_#fda913]"
/>
</div>
{/* Below bar: cap XP + next rank */}
<div className="flex justify-between items-center mt-1.5">
<span className="font-mono text-[9px] text-zinc-700">/ {gamestate.nextLevelXP?.toLocaleString()} XP cap</span>
<span
className="font-['Barlow_Condensed'] font-bold text-xs uppercase tracking-wider"
style={{ color: tier.color, opacity: 0.8 }}
>
{getNextRankTitle(gamestate.currentTitle)}
</span>
</div>
</div>
</div>
</div>
</NeoCard>
@@ -147,12 +376,12 @@ const TacticalMissions = ({ role, gamestate, className = "" }) => {
<AnimatedCounter to={current} /> / {target}
</div>
</div>
<div className="h-1.5 w-full bg-black/80 rounded-full shadow-[inset_0_2px_4px_rgba(0,0,0,0.6)] border border-white/5 relative overflow-hidden flex-shrink-0">
<div className="h-2.5 w-full bg-black/80 rounded-full shadow-[inset_0_2px_4px_rgba(0,0,0,0.8)] border border-white/5 relative overflow-hidden flex-shrink-0">
<motion.div
initial={{ width: 0 }}
animate={{ width: `${pct}%` }}
transition={{ duration: 1.5, type: "spring", stiffness: 50, damping: 15 }}
className={`absolute top-0 left-0 h-full rounded-full ${isComplete ? 'bg-[#39ff14] shadow-[0_0_10px_#39ff14]' : `bg-gradient-to-r ${colorClass}`}`}
className={`absolute top-0 left-0 h-full rounded-full ${isComplete ? 'bg-[#AAFF00] shadow-[0_0_10px_#AAFF00]' : `bg-gradient-to-r ${colorClass}`}`}
/>
</div>
</div>
@@ -160,11 +389,11 @@ const TacticalMissions = ({ role, gamestate, className = "" }) => {
};
return (
<NeoCard spotlightColor="rgba(57, 255, 20, 0.1)" className={className} innerClassName="justify-between h-full">
<NeoCard spotlightColor="rgba(170,255,0,0.08)" className={className} innerClassName="justify-between h-full">
<div className="flex flex-col h-full w-full">
<div className="flex items-center gap-3 mb-6 flex-shrink-0">
<Crosshair className={NEON_GREEN} size={24} />
<h3 className="text-sm md:text-base font-mono font-bold text-white tracking-widest uppercase pb-1">Tactical Missions</h3>
<Zap className={NEON_GREEN} size={22} />
<h3 className="text-base md:text-lg font-['Barlow_Condensed'] font-bold text-white tracking-wide uppercase">Daily Missions</h3>
</div>
<div className="flex-1 space-y-6 flex flex-col justify-center min-h-0">
@@ -195,19 +424,19 @@ const TacticalMissions = ({ role, gamestate, className = "" }) => {
{role === 'ADMIN' ? (
<>
<MissionBar current={gamestate.dailyQuests?.knocks?.current || 0} target={gamestate.dailyQuests?.knocks?.target || 1} colorClass="from-blue-600 to-[#00f0ff]" label="Active Recruits" icon={Users} />
<MissionBar current={gamestate.dailyQuests?.leads?.current || 0} target={gamestate.dailyQuests?.leads?.target || 1} colorClass="from-emerald-600 to-[#39ff14]" label="Territories Mapped" icon={Map} />
<MissionBar current={gamestate.dailyQuests?.leads?.current || 0} target={gamestate.dailyQuests?.leads?.target || 1} colorClass="from-emerald-600 to-[#AAFF00]" label="Territories Mapped" icon={Map} />
<MissionBar current={gamestate.dailyQuests?.inspections?.current || 0} target={gamestate.dailyQuests?.inspections?.target || 1} colorClass="from-purple-600 to-pink-500" label="Squad Reviews" icon={ClipboardCheck} />
</>
) : role === 'OWNER' ? (
<>
<MissionBar current={gamestate.dailyQuests?.knocks?.current || 0} target={gamestate.dailyQuests?.knocks?.target || 1} colorClass="from-amber-600 to-[#fda913]" label="Capital Deployed" icon={TrendingUp} />
<MissionBar current={gamestate.dailyQuests?.leads?.current || 0} target={gamestate.dailyQuests?.leads?.target || 1} colorClass="from-emerald-600 to-[#39ff14]" label="New Franchises" icon={Map} />
<MissionBar current={gamestate.dailyQuests?.leads?.current || 0} target={gamestate.dailyQuests?.leads?.target || 1} colorClass="from-emerald-600 to-[#AAFF00]" label="New Franchises" icon={Map} />
<MissionBar current={gamestate.dailyQuests?.inspections?.current || 0} target={gamestate.dailyQuests?.inspections?.target || 1} colorClass="from-purple-600 to-pink-500" label="Exec Meetings" icon={Users} />
</>
) : (
<>
<MissionBar current={gamestate.dailyQuests?.knocks?.current || 0} target={gamestate.dailyQuests?.knocks?.target || 1} colorClass="from-blue-600 to-[#00f0ff]" label="Door Knocks" icon={Home} />
<MissionBar current={gamestate.dailyQuests?.leads?.current || 0} target={gamestate.dailyQuests?.leads?.target || 1} colorClass="from-emerald-600 to-[#39ff14]" label="New Leads" icon={Users} />
<MissionBar current={gamestate.dailyQuests?.leads?.current || 0} target={gamestate.dailyQuests?.leads?.target || 1} colorClass="from-emerald-600 to-[#AAFF00]" label="New Leads" icon={Users} />
<MissionBar current={gamestate.dailyQuests?.inspections?.current || 0} target={gamestate.dailyQuests?.inspections?.target || 1} colorClass="from-purple-600 to-pink-500" label="Inspections" icon={ClipboardCheck} />
</>
)}
@@ -217,13 +446,12 @@ const TacticalMissions = ({ role, gamestate, className = "" }) => {
<button
onClick={() => navigate('/emp/fa/maps')}
className="w-full group mt-6 relative overflow-hidden rounded-xl bg-zinc-900 border border-[#39ff14]/30 p-4 shadow-[4px_4px_15px_rgba(0,0,0,0.5),-4px_-4px_15px_rgba(255,255,255,0.02)] transition-all hover:-translate-y-1 hover:shadow-[#39ff14]/20 flex-shrink-0"
className="w-full group mt-6 relative overflow-hidden rounded-xl bg-gradient-to-r from-[#AAFF00]/15 to-[#AAFF00]/8 border-2 border-[#AAFF00]/50 p-4 shadow-[0_0_20px_rgba(170,255,0,0.12),inset_0_1px_0_rgba(170,255,0,0.2)] transition-all hover:-translate-y-1 hover:shadow-[0_0_30px_rgba(170,255,0,0.28)] hover:border-[#AAFF00]/70 flex-shrink-0"
>
<div className="absolute inset-0 bg-gradient-to-r from-[#39ff14]/10 to-transparent opacity-0 group-hover:opacity-100 transition-opacity" />
<span className={`relative z-10 font-mono font-bold tracking-[0.2em] text-sm md:text-base uppercase flex justify-center w-full ${NEON_GREEN}`}>
[ ENGAGE MAP ]
<div className="absolute inset-0 bg-gradient-to-r from-[#AAFF00]/0 via-[#AAFF00]/12 to-[#AAFF00]/0 -translate-x-full group-hover:translate-x-full transition-transform duration-700" />
<span className={`relative z-10 font-['Barlow_Condensed'] font-bold tracking-[0.25em] text-base md:text-lg uppercase flex justify-center items-center gap-2 w-full ${NEON_GREEN}`}>
HIT THE MAP <span className="text-sm opacity-70"></span>
</span>
<span className="absolute inset-0 border border-[#39ff14] rounded-xl animate-ping opacity-10" />
</button>
</div>
</NeoCard>
@@ -235,86 +463,229 @@ const CheckpointsSystem = ({ stages, role, className = "" }) => {
if (role === 'ADMIN') headerTitle = "Squad Milestone Tracking";
if (role === 'OWNER') headerTitle = "Program Milestones";
const completedCount = stages.filter(s => s.status === 'completed').length;
const fillPct = stages.length > 1 ? (completedCount / (stages.length - 1)) * 100 : 0;
return (
<NeoCard spotlightColor="rgba(0, 240, 255, 0.05)" className={className}>
<div className="flex items-center gap-3 mb-6">
<FastForward className={NEON_BLUE} size={24} />
<h3 className="text-lg font-mono font-bold text-white tracking-widest uppercase">{headerTitle}</h3>
<h3 className="text-xl font-['Barlow_Condensed'] font-bold text-white tracking-wide uppercase">{headerTitle}</h3>
</div>
<div className="grid grid-cols-1 md:grid-cols-3 gap-6 relative w-full flex-1">
{/* Connecting Line for Desktop */}
<div className="hidden md:block absolute top-[28px] left-[15%] right-[15%] h-0.5 bg-zinc-800 rounded z-0" />
{/* Level Map */}
<div className="relative w-full">
{/* Animated connecting path — desktop only */}
<div className="hidden md:block absolute top-[27px] left-[10%] right-[10%] h-1 bg-white/[0.06] rounded-full z-0 overflow-hidden">
<motion.div
className="h-full rounded-full"
initial={{ width: 0 }}
animate={{ width: `${fillPct}%` }}
transition={{ duration: 1.4, ease: 'easeOut', delay: 0.3 }}
style={{
background: 'linear-gradient(90deg, #fda913 0%, #AAFF00 100%)',
boxShadow: '0 0 10px rgba(170,255,0,0.45)',
}}
/>
</div>
{stages.map((stage, idx) => {
const isLocked = stage.status === 'locked';
const isCompleted = stage.status === 'completed';
const isInProgress = stage.status === 'in-progress';
{/* Stage nodes */}
<div className="grid grid-cols-1 md:grid-cols-5 gap-3 relative z-10">
{stages.map((stage, idx) => {
const isLocked = stage.status === 'locked';
const isCompleted = stage.status === 'completed';
const isActive = stage.status === 'in-progress';
const pct = Math.min((stage.progress / stage.goal) * 100, 100);
return (
<div key={stage.id} className="relative z-10 flex flex-col items-center md:items-start text-center md:text-left bg-black/40 md:bg-transparent p-4 md:p-0 rounded-xl md:rounded-none border border-white/5 md:border-none shadow-[inset_0_2px_10px_rgba(0,0,0,0.5)] md:shadow-none h-full">
{/* Icon Node */}
<div className={`w-14 h-14 flex-shrink-0 rounded-xl flex items-center justify-center mb-4 transition-all duration-500 mx-auto md:mx-0
${isCompleted ? 'bg-[#39ff14]/20 text-[#39ff14] border-2 border-[#39ff14] shadow-[0_0_15px_#39ff14]' :
isInProgress ? 'bg-amber-500/20 text-amber-500 border-2 border-amber-500 shadow-[0_0_15px_#fda913] animate-pulse' :
'bg-zinc-900 text-zinc-600 border border-zinc-700'}
`}>
{isCompleted ? <CheckCircle size={24} /> : isLocked ? <Lock size={24} /> : <Target size={24} />}
const nodeStyle = isCompleted
? { background: 'rgba(253,169,19,0.18)', border: '2.5px solid #fda913', boxShadow: '0 0 20px rgba(253,169,19,0.55), 0 0 50px rgba(253,169,19,0.12)' }
: isActive
? { background: 'rgba(170,255,0,0.12)', border: '2.5px solid #AAFF00', boxShadow: '0 0 20px rgba(170,255,0,0.55), 0 0 50px rgba(170,255,0,0.12)' }
: { background: '#0d0e18', border: '1.5px solid #1e1e2c', boxShadow: 'none' };
return (
<div key={stage.id} className="flex flex-col items-center gap-2 text-center">
{/* Circular node */}
<motion.div
className="relative w-14 h-14 rounded-full flex items-center justify-center flex-shrink-0"
style={nodeStyle}
animate={isActive ? { scale: [1, 1.05, 1] } : {}}
transition={isActive ? { repeat: Infinity, duration: 2.2, ease: 'easeInOut' } : {}}
>
{/* Pulse ring for active stage */}
{isActive && (
<motion.div
className="absolute inset-0 rounded-full pointer-events-none"
style={{ border: '2px solid rgba(170,255,0,0.45)' }}
animate={{ scale: [1, 1.65], opacity: [0.7, 0] }}
transition={{ repeat: Infinity, duration: 1.8, ease: 'easeOut' }}
/>
)}
{isCompleted ? (
<CheckCircle size={22} style={{ color: '#fda913', filter: 'drop-shadow(0 0 7px rgba(253,169,19,0.9))' }} />
) : isActive ? (
<span className="font-['Barlow_Condensed'] font-black text-xl leading-none" style={{ color: '#AAFF00', textShadow: '0 0 10px rgba(170,255,0,0.8)' }}>{idx + 1}</span>
) : (
<Lock size={15} className="text-zinc-700" />
)}
</motion.div>
{/* Stage name */}
<p className={`font-['Barlow_Condensed'] font-bold text-xs uppercase tracking-wide leading-tight ${isLocked ? 'text-zinc-700' : 'text-white'}`}>
{stage.title}
</p>
{/* Active: live progress bar */}
{isActive && (
<div className="w-full space-y-0.5">
<div className="flex justify-between text-[9px] font-mono">
<span className="text-zinc-600">{stage.progress}/{stage.goal}</span>
<span style={{ color: '#AAFF00' }}>{Math.round(pct)}%</span>
</div>
<div className="h-1.5 w-full bg-black/60 rounded-full border border-white/5 overflow-hidden">
<motion.div
className="h-full rounded-full"
initial={{ width: 0 }}
animate={{ width: `${pct}%` }}
transition={{ duration: 1.4, ease: 'easeOut' }}
style={{ background: 'linear-gradient(90deg, #fda913, #AAFF00)', boxShadow: '0 0 8px rgba(170,255,0,0.5)' }}
/>
</div>
</div>
)}
{isCompleted && (
<p className="text-[9px] font-mono text-zinc-600">{stage.progress}/{stage.goal} </p>
)}
{/* Reward chip */}
<div
className="mt-auto w-full flex items-center justify-center gap-1.5 px-2 py-1.5 rounded-lg text-[9px] font-mono font-bold"
style={{
background: isCompleted ? 'rgba(253,169,19,0.1)' : isActive ? 'rgba(170,255,0,0.07)' : 'rgba(0,0,0,0.4)',
border: `1px solid ${isCompleted ? 'rgba(253,169,19,0.3)' : isActive ? 'rgba(170,255,0,0.2)' : 'rgba(255,255,255,0.04)'}`,
color: isCompleted ? '#fda913' : isActive ? 'rgba(170,255,0,0.75)' : '#2a2a38',
}}
>
<Award size={10} className="flex-shrink-0" />
<span className="truncate">{isLocked ? '???' : stage.reward.name}</span>
</div>
</div>
<h4 className="font-bold text-white mb-1">{stage.title}</h4>
<p className="text-xs font-mono text-zinc-400 mb-3">{stage.progress} / {stage.goal} {stage.requirement.type.replace(/([A-Z])/g, ' $1').trim()}</p>
{/* Reward Box */}
<div className={`px-3 py-2 mt-auto rounded flex items-center gap-2 text-[10px] font-mono w-full justify-center md:justify-start
${isCompleted ? 'bg-[#39ff14]/10 border border-[#39ff14]/30 text-[#39ff14]' : 'bg-black/50 border border-white/5 text-zinc-500'}
`}>
<Award size={14} className="flex-shrink-0" />
<span className="truncate">{stage.reward.name} {isLocked ? '(LOCKED)' : ''}</span>
</div>
</div>
);
})}
);
})}
</div>
</div>
</NeoCard>
);
};
const DetailedBadges = ({ badges, className = "" }) => {
const getBadgeConfig = (badge) => {
if (badge.id?.includes("Hunter")) return BADGE_CONFIG.Hunter;
if (badge.id?.includes("Storm")) return BADGE_CONFIG.Storm;
if (badge.id?.includes("Star")) return BADGE_CONFIG.Star;
if (badge.id?.includes("Master")) return BADGE_CONFIG.Master;
return BADGE_CONFIG.default;
};
const getBadgeIcon = (badge) => {
if (badge.id?.includes("Hunter")) return Crosshair;
if (badge.id?.includes("Storm")) return Zap;
if (badge.id?.includes("Star")) return Star;
if (badge.id?.includes("Master")) return Map;
return Shield;
};
return (
<NeoCard className={className}>
<div className="flex items-center gap-3 mb-6">
<Medal className={NEON_GOLD} size={24} />
<h3 className="text-lg font-mono font-bold text-white tracking-widest uppercase">Commendations & Badges</h3>
<h3 className="text-xl font-['Barlow_Condensed'] font-bold text-white tracking-wide uppercase">Badges & Achievements</h3>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 xl:grid-cols-3 gap-4">
<div className="grid grid-cols-2 sm:grid-cols-3 xl:grid-cols-5 gap-4">
{badges.map((badge, idx) => {
const isUnlocked = badge.status === 'unlocked';
let Icon = Shield;
if (badge.id.includes("Hunter")) Icon = Crosshair;
if (badge.id.includes("Storm")) Icon = Zap;
if (badge.id.includes("Star")) Icon = Star;
if (badge.id.includes("Master")) Icon = Map;
const config = getBadgeConfig(badge);
const Icon = getBadgeIcon(badge);
return (
<div key={idx} className={`p-4 rounded-xl flex gap-4 transition-all duration-300 h-full
${isUnlocked ? 'bg-zinc-800/50 border border-amber-500/30 shadow-[inset_0_2px_10px_rgba(0,0,0,0.5)]' : 'bg-black/40 border border-white/5 opacity-60 grayscale'}`
}>
<div className={`w-12 h-12 rounded-lg flex-shrink-0 flex items-center justify-center
${isUnlocked ? 'bg-black border border-amber-500/50 shadow-[0_0_10px_#fda913]' : 'bg-zinc-900 border border-zinc-700'}`
}>
<Icon size={20} className={isUnlocked ? NEON_GOLD : 'text-zinc-600'} />
<motion.div
key={idx}
whileHover={isUnlocked ? { scale: 1.05, y: -6 } : { scale: 1.01 }}
transition={{ type: "spring", stiffness: 400, damping: 22 }}
className="relative rounded-xl overflow-hidden flex flex-col cursor-default"
style={{
background: isUnlocked
? `linear-gradient(160deg, #0f1430 0%, #0a0d1a 100%)`
: '#0a0a12',
border: `2px solid ${isUnlocked ? config.color + '55' : '#1e1e2a'}`,
boxShadow: isUnlocked
? `0 0 24px ${config.glow.replace('0.65', '0.1')}, inset 0 1px 0 rgba(255,255,255,0.06)`
: 'none',
opacity: isUnlocked ? 1 : 0.42,
filter: isUnlocked ? 'none' : 'grayscale(0.85)',
}}
>
{/* Top radial glow bleed for unlocked */}
{isUnlocked && (
<div
className="absolute inset-0 pointer-events-none"
style={{
background: `radial-gradient(ellipse at 50% -10%, ${config.glow.replace('0.65', '0.22')} 0%, transparent 65%)`,
}}
/>
)}
{/* Status chip — top right */}
<div className="absolute top-2.5 right-2.5 z-10">
{isUnlocked
? <CheckCircle size={13} style={{ color: config.color, filter: `drop-shadow(0 0 5px ${config.glow})` }} />
: <Lock size={12} className="text-zinc-700" />
}
</div>
<div className="flex flex-col justify-center min-w-0">
<div className="flex items-center gap-2 mb-1">
<h4 className="font-bold text-sm text-white truncate">{badge.title}</h4>
{!isUnlocked && <Lock size={12} className="text-zinc-600 flex-shrink-0" />}
{/* Medallion */}
<div className="flex justify-center pt-6 pb-2 relative z-10">
<div
className="relative w-16 h-16 rounded-full flex items-center justify-center"
style={{
background: isUnlocked
? `radial-gradient(circle at 38% 32%, ${config.color}28 0%, #07091a 65%)`
: 'radial-gradient(circle, #141418, #0a0a0f)',
border: `2.5px solid ${isUnlocked ? config.color + '70' : '#222'}`,
boxShadow: isUnlocked
? `0 0 18px ${config.glow.replace('0.65', '0.45')}, 0 0 50px ${config.glow.replace('0.65', '0.12')}, inset 0 1px 0 rgba(255,255,255,0.14)`
: 'none',
}}
>
{isUnlocked && (
<div className="absolute inset-0 rounded-full bg-gradient-to-b from-white/[0.11] to-transparent pointer-events-none" />
)}
<Icon
size={28}
style={{
color: isUnlocked ? config.color : '#2a2a30',
filter: isUnlocked ? `drop-shadow(0 0 8px ${config.glow})` : 'none',
}}
/>
</div>
<p className="text-[10px] md:text-xs text-zinc-400 font-mono mb-2 leading-tight line-clamp-2">{badge.desc}</p>
<p className="text-[9px] text-[#39ff14] font-mono tracking-widest uppercase opacity-80 truncate">CRITERIA: {badge.criteria}</p>
</div>
</div>
{/* Badge text */}
<div className="px-3 pb-5 pt-1 flex flex-col gap-1 text-center relative z-10">
<h4
className="font-['Barlow_Condensed'] font-bold text-base leading-tight"
style={{
color: isUnlocked ? 'white' : '#3a3a45',
textShadow: isUnlocked ? `0 0 14px ${config.glow.replace('0.65', '0.35')}` : 'none',
}}
>{badge.title}</h4>
<p className="text-[10px] text-zinc-600 font-mono leading-snug line-clamp-2">{badge.desc}</p>
<span
className="text-[9px] font-mono tracking-widest uppercase mt-0.5 line-clamp-1"
style={{ color: isUnlocked ? config.color + 'bb' : '#2a2a30' }}
>{badge.criteria}</span>
</div>
</motion.div>
);
})}
</div>
@@ -324,13 +695,18 @@ const DetailedBadges = ({ badges, className = "" }) => {
const Leaderboard = ({ title = "Live Intel Feed", icon: Icon = Target, className = "" }) => {
const { leaderboard } = useGamification();
const countdown = useMonthResetCountdown();
return (
<NeoCard className={className} innerClassName="flex flex-col h-full">
<div className="flex flex-shrink-0 items-center justify-between mb-6">
<h3 className="text-sm md:text-base font-mono text-zinc-400 font-bold tracking-widest uppercase flex items-center gap-2">
<Icon size={18} className={NEON_BLUE} /> {title}
<div className="flex flex-shrink-0 items-center justify-between mb-5">
<h3 className="text-base md:text-lg font-['Barlow_Condensed'] text-white font-bold tracking-wide uppercase flex items-center gap-2">
<Icon size={28} className={NEON_BLUE} /> {title}
</h3>
<div className="flex flex-col items-end gap-0.5">
<span className="text-[8px] font-mono text-zinc-700 uppercase tracking-widest">Resets in</span>
<span className="text-[11px] font-mono font-bold" style={{ color: '#fda913', textShadow: '0 0 8px rgba(253,169,19,0.45)' }}>{countdown}</span>
</div>
</div>
<div className="flex-1 overflow-hidden relative min-h-[300px]">
@@ -352,9 +728,13 @@ const Leaderboard = ({ title = "Live Intel Feed", icon: Icon = Target, className
className={`group relative flex items-center justify-between p-3 md:p-4 rounded-xl transition-all duration-300
${isUser
? 'bg-blue-600/20 border border-blue-500/50 shadow-[0_0_15px_rgba(59,130,246,0.2)]'
: isTop3
? 'bg-black/40 shadow-[inset_0_2px_10px_rgba(0,0,0,0.5)] border border-white/5'
: 'bg-transparent border border-transparent hover:bg-white/5 border-b-white/5'}`}
: rank === 1
? 'bg-[#fda913]/[0.07] border border-[#fda913]/25'
: rank === 2
? 'bg-[#A8A9AD]/[0.04] border border-[#A8A9AD]/10'
: rank === 3
? 'bg-[#CD7F32]/[0.06] border border-[#CD7F32]/15'
: 'bg-transparent border border-transparent hover:bg-white/5 border-b-white/5'}`}
>
{isUser && (
<motion.div
@@ -364,13 +744,19 @@ const Leaderboard = ({ title = "Live Intel Feed", icon: Icon = Target, className
/>
)}
<div className="flex items-center gap-3 md:gap-4 flex-1 min-w-0 z-10 relative">
<span className={`font-mono font-bold w-6 text-lg md:text-xl flex-shrink-0 ${rank === 1 ? NEON_GOLD : rank === 2 ? 'text-zinc-300' : rank === 3 ? 'text-amber-700' : 'text-zinc-600'}`}>
0{rank}
</span>
<div className="min-w-0">
<span className={`font-bold text-sm md:text-base block truncate ${isUser ? 'text-blue-400' : 'text-white'}`}>{agent.name}</span>
<span className="text-[9px] md:text-[10px] text-zinc-500 uppercase font-mono tracking-widest block truncate">{agent.role}</span>
</div>
{isTop3 ? (
<Trophy
size={rank === 1 ? 24 : rank === 2 ? 20 : 17}
className="flex-shrink-0"
style={{
color: rank === 1 ? '#fda913' : rank === 2 ? '#A8A9AD' : '#CD7F32',
filter: `drop-shadow(0 0 ${rank === 1 ? 8 : 5}px ${rank === 1 ? 'rgba(253,169,19,0.8)' : rank === 2 ? 'rgba(168,169,173,0.6)' : 'rgba(205,127,50,0.6)'})`,
}}
/>
) : (
<span className="font-mono font-bold w-6 text-sm flex-shrink-0 text-zinc-600">{String(rank).padStart(2, '0')}</span>
)}
<span className={`font-bold text-sm md:text-base truncate ${isUser ? 'text-blue-400' : 'text-white'}`}>{agent.name}</span>
</div>
<div className="flex flex-col items-end gap-1 font-mono text-xs md:text-sm flex-shrink-0 z-10 relative">
<span className={`font-bold ${isUser ? 'text-white' : 'text-zinc-300'}`}><AnimatedCounter to={agent.points} /> XP</span>
@@ -395,8 +781,8 @@ const AdminCommandCenter = ({ className = "" }) => {
<div className="flex items-center gap-3 mb-6">
<Activity className={NEON_BLUE} size={28} />
<div>
<h2 className="text-xl md:text-2xl font-black text-white tracking-widest uppercase">Global Control</h2>
<p className="font-mono text-[10px] md:text-xs text-zinc-500 tracking-[0.2em]">Administrative Overseer Panel</p>
<h2 className="text-2xl md:text-3xl font-['Barlow_Condensed'] font-bold text-white tracking-wide uppercase">Command Center</h2>
<p className="font-mono text-[10px] md:text-xs text-zinc-500 tracking-[0.15em]">Season Operations Overview</p>
</div>
</div>
@@ -405,7 +791,7 @@ const AdminCommandCenter = ({ className = "" }) => {
<p className="text-[10px] md:text-xs font-mono text-zinc-400 tracking-widest mb-2">ACTIVE SQUADS</p>
<div className="flex items-end gap-3">
<span className={`text-3xl md:text-4xl font-black ${NEON_BLUE}`}><AnimatedCounter to={12} /></span>
<span className="text-[10px] md:text-sm font-mono text-[#39ff14] pb-1">All Online</span>
<span className="text-[10px] md:text-sm font-mono text-[#AAFF00] pb-1">All Online</span>
</div>
</div>
@@ -413,7 +799,7 @@ const AdminCommandCenter = ({ className = "" }) => {
<p className="text-[10px] md:text-xs font-mono text-zinc-400 tracking-widest mb-2">DOORS KNOCKED TODAY</p>
<div className="flex items-end gap-3">
<span className={`text-3xl md:text-4xl font-black ${NEON_GOLD}`}><AnimatedCounter to={1432} /></span>
<span className="text-[10px] md:text-sm font-mono text-[#39ff14] pb-1">+14% vs Yday</span>
<span className="text-[10px] md:text-sm font-mono text-[#AAFF00] pb-1">+14% vs Yday</span>
</div>
</div>
@@ -438,8 +824,8 @@ const OwnerROIDashboard = ({ className = "" }) => {
<div className="flex items-center gap-3">
<TrendingUp className={NEON_GOLD} size={28} />
<div>
<h2 className="text-xl md:text-2xl font-black text-white tracking-widest uppercase">Executive ROI</h2>
<p className="font-mono text-[10px] md:text-xs text-zinc-500 tracking-[0.2em]">Gamification Investment Returns</p>
<h2 className="text-2xl md:text-3xl font-['Barlow_Condensed'] font-bold text-white tracking-wide uppercase">Executive ROI</h2>
<p className="font-mono text-[10px] md:text-xs text-zinc-500 tracking-[0.15em]">Program Performance</p>
</div>
</div>
<div className="px-4 py-3 bg-black/50 border border-amber-500/20 rounded-lg flex items-center gap-4 shadow-[inset_0_2px_10px_rgba(0,0,0,0.5)] w-full md:w-auto overflow-hidden">
@@ -453,7 +839,7 @@ const OwnerROIDashboard = ({ className = "" }) => {
<div className="w-full">
<div className="flex justify-between text-[10px] md:text-xs font-mono mb-2">
<span className="text-zinc-400">TEAM PARTICIPATION</span>
<span className="text-[#39ff14]"><AnimatedCounter to={92} />%</span>
<span className="text-[#AAFF00]"><AnimatedCounter to={92} />%</span>
</div>
<ProgressBar progress={92} goal={100} />
</div>
@@ -491,10 +877,10 @@ const QuickLogAction = ({ className = "", gridClassName = "grid grid-cols-2 gap-
const { logKnock, logLead, logInspection } = useGamification();
return (
<NeoCard spotlightColor="rgba(57, 255, 20, 0.1)" className={className} innerClassName="flex flex-col h-full">
<NeoCard spotlightColor="rgba(170,255,0,0.08)" className={className} innerClassName="flex flex-col h-full">
<div className="flex items-center gap-3 mb-4 flex-shrink-0">
<Zap className={NEON_GREEN} size={20} />
<h3 className="text-sm md:text-base font-mono font-bold text-white tracking-widest uppercase">Quick Operations Log</h3>
<h3 className="text-base md:text-lg font-['Barlow_Condensed'] font-bold text-white tracking-wide uppercase">Quick Actions</h3>
</div>
<div className={`w-full ${gridClassName}`}>
<button
@@ -503,8 +889,8 @@ const QuickLogAction = ({ className = "", gridClassName = "grid grid-cols-2 gap-
const isHighValue = e.shiftKey || Math.random() > 0.75;
logKnock(isHighValue, { x: e.clientX, y: e.clientY });
}}
className="h-full p-1.5 md:p-2 rounded-xl bg-black/40 border border-white/5 hover:bg-zinc-800 hover:border-[#39ff14]/50 hover:shadow-[0_0_15px_rgba(57,255,20,0.2)] transition-all flex flex-col items-center justify-center gap-1 md:gap-2 group cursor-crosshair min-h-0">
<Map size={18} className="text-zinc-500 group-hover:text-[#39ff14] transition-colors flex-shrink-0" />
className="h-full p-1.5 md:p-2 rounded-xl bg-black/40 border border-white/5 hover:bg-zinc-800 hover:border-[#AAFF00]/50 hover:shadow-[0_0_15px_rgba(170,255,0,0.25)] transition-all flex flex-col items-center justify-center gap-1 md:gap-2 group cursor-crosshair min-h-0">
<Map size={18} className="text-zinc-500 group-hover:text-[#AAFF00] transition-colors flex-shrink-0" />
<span className="text-[9px] md:text-[10px] text-center font-mono text-zinc-400 group-hover:text-white transition-colors truncate w-full px-1">Door Knock</span>
</button>
<button
@@ -531,11 +917,11 @@ const QuickLogAction = ({ className = "", gridClassName = "grid grid-cols-2 gap-
const ScanUpload = ({ className = "" }) => {
return (
<NeoCard className={className} innerClassName="flex flex-col items-center justify-center text-center h-full group cursor-pointer w-full">
<div className="w-16 h-16 rounded-full bg-zinc-900 shadow-[inset_4px_4px_8px_rgba(0,0,0,0.5),inset_-4px_-4px_8px_rgba(255,255,255,0.05)] flex items-center justify-center mb-4 group-hover:shadow-[0_0_15px_#39ff14] transition-all duration-300 transform group-hover:-translate-y-2">
<UploadCloud size={28} className="text-zinc-500 group-hover:text-[#39ff14] transition-colors" />
<div className="w-16 h-16 rounded-full bg-zinc-900 shadow-[inset_4px_4px_8px_rgba(0,0,0,0.5),inset_-4px_-4px_8px_rgba(255,255,255,0.05)] flex items-center justify-center mb-4 group-hover:shadow-[0_0_15px_#AAFF00] transition-all duration-300 transform group-hover:-translate-y-2">
<UploadCloud size={28} className="text-zinc-500 group-hover:text-[#AAFF00] transition-colors" />
</div>
<h4 className="font-mono text-sm tracking-widest text-white mb-2">SCAN UPLOAD</h4>
<p className="text-[10px] text-zinc-500 font-mono mb-0">DRAG RECON INTEL HERE (+500XP)</p>
<p className="text-[10px] text-zinc-500 font-mono mb-0">DROP FILES TO EARN +500 XP</p>
</NeoCard>
);
};
@@ -550,68 +936,77 @@ function ProCanvasContent() {
const role = user?.role || 'FIELD_AGENT';
const mockDbUser = users.find(u => u.email === user?.email) || users.find(u => u.roles?.includes('FIELD_AGENT')) || users.find(u => u.type === 'employee') ||
{
name: user?.name || 'Agent Unknown',
empId: 'OP-00',
xp: 12450,
doorsKnocked: 342,
leadsGained: 45,
appointmentsSet: 18,
streakDays: 5,
achievements: ['Hot Spot Hunter', 'Storm Chaser']
const mockDbUser = {
...(users.find(u => u.email === user?.email) || users.find(u => u.roles?.includes('FIELD_AGENT')) || users.find(u => u.type === 'employee') ||
{
name: user?.name || 'Agent Unknown',
empId: 'OP-00',
xp: 12450,
doorsKnocked: 342,
leadsGained: 45,
appointmentsSet: 18,
streakDays: 5,
achievements: ['Hot Spot Hunter', 'Storm Chaser']
}),
profilePhoto: profilePic,
};
const oldGamestate = useOldGamification(mockDbUser);
const newGamestate = useGamification();
if (!oldGamestate || !newGamestate) return <div className="p-8 text-white font-mono flex items-center justify-center h-screen bg-[#050505]">INITIALIZING HUD...</div>;
if (!oldGamestate || !newGamestate) return <div className="p-8 text-white font-mono flex items-center justify-center h-screen bg-[#060812]">LOADING PLAYER DATA...</div>;
return (
<div className="min-h-full bg-[#09090b] p-4 md:p-8 overflow-y-auto selection:bg-[#fda913] selection:text-black relative">
<div className="min-h-full bg-[#060812] p-4 md:p-8 overflow-y-auto selection:bg-[#fda913] selection:text-black relative">
{/* New Gamification Overlay Components */}
<FloatingXPManager />
<LevelUpModal />
{/* Hex Grid Background & Scanlines */}
{/* Retro Arcade Background */}
<div className="fixed inset-0 pointer-events-none z-0">
<div className="absolute inset-0 opacity-20">
<svg className="w-full h-full" xmlns="http://www.w3.org/2000/svg">
<defs>
<pattern id="hexagons" width="50" height="43.4" patternUnits="userSpaceOnUse" patternTransform="scale(2)">
<path d="M25 0 L50 14.4 L50 43.3 L25 57.7 L0 43.3 L0 14.4 Z" fill="none" stroke="rgba(255,255,255,0.05)" strokeWidth="1" />
</pattern>
</defs>
<rect width="100%" height="100%" fill="url(#hexagons)" />
</svg>
</div>
{/* CSS Scanline Overlay */}
<div className="absolute inset-0 opacity-[0.03] bg-[linear-gradient(to_bottom,transparent_50%,rgba(0,0,0,0.5)_50%)] bg-[length:100%_4px]" />
{/* Halftone dot grid */}
<div className="absolute inset-0" style={{
backgroundImage: 'radial-gradient(circle, rgba(255,255,255,0.055) 1px, transparent 1px)',
backgroundSize: '28px 28px'
}} />
{/* Warm orange-red bloom from bottom — fire/energy */}
<div className="absolute inset-0 bg-[radial-gradient(ellipse_at_50%_105%,rgba(255,75,0,0.07)_0%,transparent_55%)]" />
{/* Cool cyan bloom from top — arena lights */}
<div className="absolute inset-0 bg-[radial-gradient(ellipse_at_50%_-10%,rgba(0,240,255,0.05)_0%,transparent_50%)]" />
{/* Subtle vignette */}
<div className="absolute inset-0 bg-[radial-gradient(ellipse_at_50%_50%,transparent_50%,rgba(0,0,0,0.4)_100%)]" />
</div>
<div className="max-w-7xl mx-auto space-y-6 md:space-y-8 relative z-10 w-full">
<header className="mb-6 md:mb-10 flex flex-col md:flex-row justify-between items-start md:items-end gap-4 border-b border-white/10 pb-6 w-full">
<header className="mb-6 md:mb-10 flex flex-col md:flex-row justify-between items-start md:items-end gap-4 border-b-2 border-white/[0.08] pb-6 w-full">
<div>
<h1 className="text-3xl md:text-4xl font-black text-transparent bg-clip-text bg-gradient-to-r from-white via-zinc-300 to-zinc-600 tracking-tighter drop-shadow-xl">
PRO<span className="text-[#fda913]">CANVAS</span>
<h1 className="text-4xl md:text-5xl font-black text-transparent bg-clip-text bg-gradient-to-r from-white to-zinc-400 tracking-tighter leading-none">
PRO<span className="text-[#fda913] drop-shadow-[0_0_24px_rgba(253,169,19,0.5)]">CANVAS</span>
</h1>
<p className="font-mono text-zinc-500 text-xs md:text-sm tracking-widest uppercase mt-2">
{role === 'FIELD_AGENT' ? 'Tactical HUD Controller' : role === 'ADMIN' ? 'Command Center Dashboard' : 'Executive ROI Overview'}
<p className="font-['Barlow_Condensed'] font-bold text-zinc-400 text-sm md:text-base tracking-[0.15em] uppercase mt-2">
{role === 'FIELD_AGENT' ? 'Player Season Dashboard' : role === 'ADMIN' ? 'Command Center' : 'Executive Overview'}
</p>
</div>
<div className="font-mono text-[9px] md:text-[10px] text-[#39ff14] flex flex-col md:items-end opacity-70 bg-black/40 p-2 md:p-3 rounded border border-[#39ff14]/20 shadow-[0_0_10px_rgba(57,255,20,0.1)] w-full md:w-auto">
<span className="flex justify-between md:justify-end gap-8 w-full">SYS_STATUS: <span>ONLINE</span></span>
<span className="flex justify-between md:justify-end gap-8 w-full">NET_SECURE: <span>TRUE</span></span>
<span className="flex justify-between md:justify-end gap-8 w-full text-zinc-500">AUTH_LVL: <span>{role}</span></span>
{/* Season Badge — sports broadcast style */}
<div className="flex items-center gap-4 bg-[#0a0d1a] border-2 border-[#fda913]/30 rounded-xl px-4 py-3 shadow-[0_0_24px_rgba(253,169,19,0.08),inset_0_1px_0_rgba(255,255,255,0.06)] w-full md:w-auto">
<Trophy size={22} className="text-[#fda913] drop-shadow-[0_0_10px_rgba(253,169,19,0.7)] flex-shrink-0" />
<div className="flex flex-col">
<span className="font-['Barlow_Condensed'] font-bold text-white text-sm md:text-base tracking-wider uppercase leading-tight">Season 2026</span>
<span className="font-mono text-[10px] text-[#AAFF00] drop-shadow-[0_0_6px_rgba(170,255,0,0.5)]"> LIVE · Week 8</span>
</div>
<div className="ml-auto md:ml-6 flex flex-col items-end border-l border-white/10 pl-4">
<span className="font-mono text-[9px] text-zinc-500 uppercase tracking-widest">Status</span>
<span className="font-bold text-xs text-[#AAFF00] drop-shadow-[0_0_6px_rgba(170,255,0,0.5)]">ACTIVE</span>
</div>
</div>
</header>
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6 md:gap-8 w-full items-stretch">
{/* Unified Top Row */}
<OperatorProfile gamestate={newGamestate} user={mockDbUser} className="lg:col-span-2" />
<OperatorProfile gamestate={newGamestate} user={mockDbUser} badges={oldGamestate.badges} className="lg:col-span-2" />
<TacticalMissions role={role} gamestate={newGamestate} className="lg:col-span-1" />
{/* Checkpoints & Stages */}