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 ? ( + {user.name} + ) : ( +
+ {/* 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 */} +
+ Total XP + + XP + +
+ {/* 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