diff --git a/.gitignore b/.gitignore
index 50e8547..a464f83 100644
--- a/.gitignore
+++ b/.gitignore
@@ -28,4 +28,5 @@ dist-ssr
*.md
!README.md
!data_schema.txt
+!docs/**/*.md
.env
diff --git a/src/assets/images/Profile_Pic_1_Agent.png b/src/assets/images/Profile_Pic_1_Agent.png
new file mode 100644
index 0000000..0a064d2
Binary files /dev/null and b/src/assets/images/Profile_Pic_1_Agent.png differ
diff --git a/src/context/GamificationContext.jsx b/src/context/GamificationContext.jsx
index 84337df..0893bcc 100644
--- a/src/context/GamificationContext.jsx
+++ b/src/context/GamificationContext.jsx
@@ -4,7 +4,7 @@ import { useAuth } from './AuthContext';
const GamificationContext = createContext();
const INITIAL_STATE = {
- xp: 3550,
+ xp: 3360,
level: 12,
streak: 4,
todayPoints: 150,
diff --git a/src/hooks/useGamification.js b/src/hooks/useGamification.js
index 4604351..2bd0a05 100644
--- a/src/hooks/useGamification.js
+++ b/src/hooks/useGamification.js
@@ -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
}
];
}
diff --git a/src/index.css b/src/index.css
index 97339d7..dfcbb5a 100644
--- a/src/index.css
+++ b/src/index.css
@@ -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;
diff --git a/src/pages/ProCanvas.jsx b/src/pages/ProCanvas.jsx
index a77e87a..28d19e7 100644
--- a/src/pages/ProCanvas.jsx
+++ b/src/pages/ProCanvas.jsx
@@ -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)" }) => (
+ {/* Arcade corner bracket accents */}
+
+
+
+
{children}
);
-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 (
-
+
{
+ 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 (
-
-
-
+
+ {/* ── PLAYER CARD PORTRAIT ── */}
+
+ {/* Trading card frame */}
+
-
-
-
-
-
- {user.name.split(' ').map(n => n[0]).join('')}
-
+ {/* Rank tier ribbon at top */}
+
+ {tier.label}
+
+
+ {/* Photo or initials */}
+ {user.profilePhoto ? (
+

+ ) : (
+
+ {/* Diagonal stripe pattern */}
+
+
{initials}
+
+ )}
+
+ {/* Gloss overlay — top catch light + bottom vignette */}
+
+
+ {/* ── BADGE PINS — overlap portrait bottom edge ── */}
+ {displayBadges.length > 0 && (
+
+ {displayBadges.map((badge, i) => {
+ const BadgeIcon = getBadgeIcon(badge);
+ const isUnlocked = badge.status === 'unlocked';
+ return (
+
+
+
+ );
+ })}
+
+ )}
-
-
-
-
-
Op ID: {user.empId || 'UNK-99'}
-
-
-
-
- {gamestate.streak} Day Streak
-
-
-
{user.name}
-
-
-
LVL
-
{gamestate.currentTitle}
+ {/* ── INFO COLUMN ── */}
+
+
+ {/* Row 1: Player ID + Level badge */}
+
+
+ Player ID: {user.empId || 'P-99'}
+
+
+ LVL
+
+
+
+ {/* Row 2: Player name */}
+
+ {user.name.toUpperCase()}
+
+
+ {/* Row 3: RANK TITLE — primary status identity */}
+
+
+ {gamestate.currentTitle}
+
+
+ ◈ {tier.label} TIER
+
+
+
+ {/* Row 4: Day Streak combo strip */}
+
+
+
+
+
+
+
+
+ Day
+ Streak
-
-
-
XP
-
NEXT {gamestate.nextLevelXP.toLocaleString()}
+ {/* Row 5: XP section — big stats + bar */}
+
+ {/* Stat callouts above bar */}
+
+ {/* Total XP — left */}
+
+ {/* XP to go — right */}
+
+ To Level Up
+
+ {Math.max(0, (gamestate.nextLevelXP || 0) - gamestate.xp).toLocaleString()}XP
+
+
- {/* Using context's exact progressPercent for precision inside the progress bar */}
-
+
+ {/* Progress bar */}
+
+
+ {/* Below bar: cap XP + next rank */}
+
+ / {gamestate.nextLevelXP?.toLocaleString()} XP cap
+
+ → {getNextRankTitle(gamestate.currentTitle)}
+
+
+
@@ -147,12 +376,12 @@ const TacticalMissions = ({ role, gamestate, className = "" }) => {
/ {target}
-
@@ -160,11 +389,11 @@ const TacticalMissions = ({ role, gamestate, className = "" }) => {
};
return (
-
+
-
-
Tactical Missions
+
+ Daily Missions
@@ -195,19 +424,19 @@ const TacticalMissions = ({ role, gamestate, className = "" }) => {
{role === 'ADMIN' ? (
<>
-
+
>
) : role === 'OWNER' ? (
<>
-
+
>
) : (
<>
-
+
>
)}
@@ -217,13 +446,12 @@ const TacticalMissions = ({ role, gamestate, className = "" }) => {
@@ -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 (
-
{headerTitle}
+ {headerTitle}
-
- {/* Connecting Line for Desktop */}
-
+ {/* Level Map */}
+
+ {/* Animated connecting path — desktop only */}
+
+
+
- {stages.map((stage, idx) => {
- const isLocked = stage.status === 'locked';
- const isCompleted = stage.status === 'completed';
- const isInProgress = stage.status === 'in-progress';
+ {/* Stage nodes */}
+
+ {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 (
-
- {/* Icon Node */}
-
- {isCompleted ?
: isLocked ?
:
}
+ 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 (
+
+ {/* Circular node */}
+
+ {/* Pulse ring for active stage */}
+ {isActive && (
+
+ )}
+ {isCompleted ? (
+
+ ) : isActive ? (
+ {idx + 1}
+ ) : (
+
+ )}
+
+
+ {/* Stage name */}
+
+ {stage.title}
+
+
+ {/* Active: live progress bar */}
+ {isActive && (
+
+
+ {stage.progress}/{stage.goal}
+ {Math.round(pct)}%
+
+
+
+
+
+ )}
+
+ {isCompleted && (
+
{stage.progress}/{stage.goal} ✓
+ )}
+
+ {/* Reward chip */}
+
+
+
{isLocked ? '???' : stage.reward.name}
+
-
-
{stage.title}
-
{stage.progress} / {stage.goal} {stage.requirement.type.replace(/([A-Z])/g, ' $1').trim()}
-
- {/* Reward Box */}
-
-
-
{stage.reward.name} {isLocked ? '(LOCKED)' : ''}
-
-
- );
- })}
+ );
+ })}
+
);
};
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 (
-
Commendations & Badges
+ Badges & Achievements
-
+
{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 (
-
-
-
+
+ {/* Top radial glow bleed for unlocked */}
+ {isUnlocked && (
+
+ )}
+
+ {/* Status chip — top right */}
+
+ {isUnlocked
+ ?
+ :
+ }
-
-
-
{badge.title}
- {!isUnlocked &&
}
+
+ {/* Medallion */}
+
+
+ {isUnlocked && (
+
+ )}
+
-
{badge.desc}
-
CRITERIA: {badge.criteria}
-
+
+ {/* Badge text */}
+
+
{badge.title}
+
{badge.desc}
+
{badge.criteria}
+
+
);
})}
@@ -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 (
-
-
- {title}
+
+
+ {title}
+
+ Resets in
+ {countdown}
+
@@ -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 && (
)}
-
- 0{rank}
-
-
- {agent.name}
- {agent.role}
-
+ {isTop3 ? (
+
+ ) : (
+
{String(rank).padStart(2, '0')}
+ )}
+
{agent.name}
XP
@@ -395,8 +781,8 @@ const AdminCommandCenter = ({ className = "" }) => {
-
Global Control
-
Administrative Overseer Panel
+
Command Center
+
Season Operations Overview
@@ -405,7 +791,7 @@ const AdminCommandCenter = ({ className = "" }) => {
ACTIVE SQUADS
-
All Online
+
All Online
@@ -413,7 +799,7 @@ const AdminCommandCenter = ({ className = "" }) => {
DOORS KNOCKED TODAY
-
+14% vs Yday
+
+14% vs Yday
@@ -438,8 +824,8 @@ const OwnerROIDashboard = ({ className = "" }) => {
-
Executive ROI
-
Gamification Investment Returns
+
Executive ROI
+
Program Performance
@@ -453,7 +839,7 @@ const OwnerROIDashboard = ({ className = "" }) => {
TEAM PARTICIPATION
-
%
+
%
@@ -491,10 +877,10 @@ const QuickLogAction = ({ className = "", gridClassName = "grid grid-cols-2 gap-
const { logKnock, logLead, logInspection } = useGamification();
return (
-
+
-
Quick Operations Log
+ Quick Actions