diff --git a/src/pages/ProCanvas.jsx b/src/pages/ProCanvas.jsx
index 88b9f93..fae1a1e 100644
--- a/src/pages/ProCanvas.jsx
+++ b/src/pages/ProCanvas.jsx
@@ -1,1054 +1,955 @@
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 {
- Flame, Target, Crosshair, Trophy, Shield, Zap, Star, Medal, Map, Activity, Home,
- Users, TrendingUp, CheckCircle, Lock, Award, Calendar, Camera, ChevronRight, Swords,
- X, ClipboardList, Handshake, DoorOpen, UserPlus
-} from 'lucide-react';
import { useAuth } from '../context/AuthContext';
import { useMockStore } from '../data/mockStore';
-import { useGamification as useOldGamification } from '../hooks/useGamification';
import { GamificationProvider, useGamification } from '../context/GamificationContext';
+import { useGamification as useOldGamification } from '../hooks/useGamification';
import FloatingXPManager from '../components/ProCanvas/gamification/FloatingXPManager';
import LevelUpModal from '../components/ProCanvas/gamification/LevelUpModal';
-import { AnimatedCounter } from '../components/AnimatedCounter';
-import { SpotlightCard } from '../components/SpotlightCard';
+import profilePic from '../assets/images/Profile_Pic_1_Agent.png';
+import {
+ Trophy, Flame, Target, Star, Gift, Camera,
+ Map as MapIcon, Users, Crosshair, Award, MapPin,
+ CheckCircle, ChevronRight, X, Home, Shield, Lock,
+ ClipboardList, Handshake, DoorOpen, UserPlus, Zap, Activity, TrendingUp, Hexagon, Calendar
+} from 'lucide-react';
-// ═══════════════════════════════════════════════════════════════════
-// DESIGN TOKENS
-// ═══════════════════════════════════════════════════════════════════
+/*
+ * ==========================================
+ * UTILS & THEME
+ * ==========================================
+ */
const C = {
- gold: '#fda913', neon: '#AAFF00', fire: '#ff4500', blue: '#3b82f6',
- cyan: '#06b6d4', green: '#22c55e', pink: '#ec4899', purple: '#a855f7',
+ gold: '#FFD700',
+ neon: '#AAFF00',
+ blue: '#3B82F6',
+ fire: '#FF4500',
+ cyan: '#00E5FF',
+ purple: '#B388FF',
+ glass: 'rgba(24, 24, 27, 0.65)',
+ border: 'rgba(255, 255, 255, 0.08)'
};
-// ── Pixel-art SVGs ──
-const PixelTrophy = ({ size = 20, color = C.gold, className = '' }) => (
-
-);
-const PixelFlame = ({ size = 20, color = C.fire }) => (
-
-);
-const PixelBolt = ({ size = 18, color = C.neon }) => (
-
-);
-const PixelStar = ({ size = 16, color = C.gold }) => (
-
-);
-
-// ── Corner brackets decoration (like old design) ──
-const CornerBrackets = ({ color = C.gold, thickness = 2 }) => (
- <>
-
-
-
-
- >
-);
-
-// ── Animated floating dots ──
-const FloatingDots = ({ color = C.gold, count = 5 }) => (
-
- {Array.from({ length: count }).map((_, i) => (
-
- ))}
-
-);
-
-// Pulse ring — smoother timing
-const PulseRing = ({ color = C.fire, size = 56 }) => (
-
- {[0, 1, 2].map(i => (
-
- ))}
-
-);
-
-// Grid pattern overlay
-const GridPattern = ({ color = C.gold, opacity = 0.06 }) => (
-
-);
-
-// CRT scanlines
-const ScanlineOverlay = () => (
-
-);
-
-// ═══════════════════════════════════════════════════════════════════
-// GAME CARD — the main card primitive (chunky, with corner brackets)
-// ═══════════════════════════════════════════════════════════════════
-const GameCard = ({ children, className = '', accent = C.gold, scanlines = false, brackets = true, spotlightColor, glow = false, ...props }) => (
-
- {/* Top accent line */}
-
- {/* Bottom accent line */}
-
- {brackets && }
- {scanlines && }
- {children}
-
-);
-
-// ── Section header — big, bold, like the old design ──
-const SectionHeader = ({ icon: Icon, pixelIcon: PIcon, title, color = C.gold, extra = null, size = 'lg' }) => (
-
-
- {PIcon ?
: (
-
-
-
- )}
-
- {title}
-
-
- {extra}
-
-);
-
-// ── Progress bar — thick and glowing ──
-const ProgressBar = ({ progress, goal, color = C.gold, height = 'h-4', showLabel = true, label = '' }) => {
- const pct = Math.min((progress / goal) * 100, 100);
- return (
-
- {showLabel && (
-
- {label &&
{label}}
-
/ {goal}
-
- )}
-
-
- 3 ? 0.9 : 0 }}
- />
-
-
- );
-};
-
-// ── Stagger entrance ──
-const containerV = { hidden: {}, show: { transition: { staggerChildren: 0.08, delayChildren: 0.05 } } };
-const itemV = { hidden: { opacity: 0, y: 28 }, show: { opacity: 1, y: 0, transition: { type: 'spring', stiffness: 240, damping: 22 } } };
-
-// ═══════════════════════════════════════════════════════════════════
-// HOOKS
-// ═══════════════════════════════════════════════════════════════════
-const useMonthResetCountdown = () => {
- const [t, setT] = useState('');
+// CountUp Component for animated numbers (0 to end)
+const CountUp = ({ to, suffix = "", duration = 1500 }) => {
+ const [count, setCount] = useState(0);
useEffect(() => {
- const calc = () => {
- const now = new Date(), end = new Date(now.getFullYear(), now.getMonth() + 1, 1);
- const diff = end - now; if (diff <= 0) { setT('Resetting...'); return; }
- setT(`${Math.floor(diff / 86400000)}d ${String(Math.floor((diff % 86400000) / 3600000)).padStart(2, '0')}h ${String(Math.floor((diff % 3600000) / 60000)).padStart(2, '0')}m`);
+ let start = 0;
+ const end = parseInt(to, 10);
+ if (isNaN(end)) return;
+ let startTime = null;
+ const animate = (timestamp) => {
+ if (!startTime) startTime = timestamp;
+ const progress = timestamp - startTime;
+ const percentage = Math.min(progress / duration, 1);
+ const ease = 1 - Math.pow(1 - percentage, 4);
+ setCount(Math.floor(start + (end - start) * ease));
+ if (percentage < 1) {
+ requestAnimationFrame(animate);
+ } else {
+ setCount(end);
+ }
};
- calc(); const id = setInterval(calc, 60000); return () => clearInterval(id);
- }, []); return t;
+ requestAnimationFrame(animate);
+ }, [to, duration]);
+ return <>{count.toLocaleString()}{suffix}>;
};
-// ═══════════════════════════════════════════════════════════════════
-// CONFIG
-// ═══════════════════════════════════════════════════════════════════
-const RANK_TIERS = {
- 'Rookie': { color: '#CD7F32' }, 'Field Runner': { color: '#A8A9AD' },
- 'Roof Warrior': { color: '#fda913' }, 'Territory Titan': { color: '#A855F7' },
- 'Legendary Closer': { color: '#AAFF00' },
-};
-const RANK_ORDER = ['Rookie', 'Field Runner', 'Roof Warrior', 'Territory Titan', 'Legendary Closer'];
-const nextRank = (c) => { const i = RANK_ORDER.indexOf(c); return i >= 0 && i < RANK_ORDER.length - 1 ? RANK_ORDER[i + 1] : 'MAX RANK'; };
-
-// ═══════════════════════════════════════════════════════════════════
-// 1. OPERATOR PROFILE — big chunky player card
-// ═══════════════════════════════════════════════════════════════════
-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 BADGE_ICONS_MAP = { Hunter: Crosshair, Storm: Zap, Star: Star, Master: Map };
- const BADGE_COLORS_MAP = { Hunter: C.fire, Storm: C.gold, Star: C.pink, Master: C.green };
- const topBadges = badges.filter(b => b.status === 'unlocked').slice(0, 3);
-
- return (
-
-
-
- {/* Card-game style photo frame */}
-
- {/* Outer decorative frame */}
-
- {/* Frame background with gradient border */}
-
-
- {/* Inner card with photo */}
-
- {user.profilePhoto ? (
-

- ) : (
-
- {initials}
-
- )}
- {/* Holographic shimmer overlay */}
-
-
- {/* Corner star accents on frame */}
-
-
-
- {/* Streak badge */}
- {gamestate.streak > 0 && (
-
-
- {gamestate.streak}
-
- )}
-
-
- {/* Name & Rank */}
-
-
{user.name.toUpperCase()}
-
- LVL {gamestate.level} — {gamestate.currentTitle}
-
-
-
- {/* XP Bar */}
-
-
-
→ {nextRank(gamestate.currentTitle)}
-
-
- {/* Quick Stats */}
-
- {[
- { icon: Home, val: gamestate.dailyQuests?.knocks?.current || 18, lbl: 'Knocked', color: C.neon },
- { icon: Users, val: gamestate.dailyQuests?.leads?.current || 6, lbl: 'Leads', color: C.gold },
- { icon: Calendar, val: gamestate.dailyQuests?.inspections?.current || 3, lbl: 'Appts', color: C.cyan },
- ].map(({ icon: I, val, lbl, color }) => (
-
- ))}
-
-
- {/* Top 3 Badges */}
- {topBadges.length > 0 && (
-
-
Top Badges
-
- {topBadges.map((b, i) => {
- const key = Object.keys(BADGE_ICONS_MAP).find(k => b.id?.includes(k));
- const Icon = key ? BADGE_ICONS_MAP[key] : Shield;
- const bColor = key ? BADGE_COLORS_MAP[key] : C.cyan;
- return (
-
-
-
- );
- })}
-
-
- )}
-
-
- );
+const useMonthResetCountdown = () => {
+ const [timeLeft, setTimeLeft] = useState('');
+ useEffect(() => {
+ const updateTimer = () => {
+ const now = new Date();
+ const endOfMonth = new Date(now.getFullYear(), now.getMonth() + 1, 0, 23, 59, 59);
+ const diff = endOfMonth - now;
+ const d = Math.floor(diff / (1000 * 60 * 60 * 24));
+ const h = Math.floor((diff / (1000 * 60 * 60)) % 24);
+ const m = Math.floor((diff / 1000 / 60) % 60);
+ const s = Math.floor((diff / 1000) % 60);
+ setTimeLeft(`${d}d ${h}h ${m}m ${s}s`);
+ };
+ updateTimer();
+ const interval = setInterval(updateTimer, 1000);
+ return () => clearInterval(interval);
+ }, []);
+ return timeLeft;
};
-// ═══════════════════════════════════════════════════════════════════
-// 2. DAILY MISSIONS — like the old design, chunky quest list
-// ═══════════════════════════════════════════════════════════════════
-const DailyMissions = ({ gamestate, role, className = '' }) => {
- const navigate = useNavigate();
- const weeklyKnocks = gamestate.weeklyChallenge?.knocks || { current: 140, target: 150 };
- const dailyQuests = [
- { icon: Home, label: 'Door Knocks', current: gamestate.dailyQuests?.knocks?.current || 18, target: gamestate.dailyQuests?.knocks?.target || 20, color: C.blue },
- { icon: Users, label: 'New Leads', current: gamestate.dailyQuests?.leads?.current || 4, target: gamestate.dailyQuests?.leads?.target || 5, color: C.neon },
- { icon: Calendar, label: 'Inspections', current: gamestate.dailyQuests?.inspections?.current || 0, target: gamestate.dailyQuests?.inspections?.target || 1, color: C.gold },
- ];
-
- return (
-
-
-
-
- {/* Weekly Challenge */}
-
-
-
- Weekly Challenge
-
-
-
-
- {/* Daily Quests */}
-
-
-
- {dailyQuests.map(({ icon: I, label, current, target, color }) => (
-
-
-
-
{label}
-
/ {target}
-
-
-
- ))}
-
-
-
- {/* CTA Button — smooth pulsing glow */}
- navigate('/emp/fa/maps')}
- className="w-full mt-6 py-4 rounded-xl font-['Barlow_Condensed'] font-black text-lg uppercase tracking-wider flex items-center justify-center gap-3 cursor-pointer"
- style={{ background: C.neon, color: '#09090b', border: `2px solid ${C.neon}88` }}>
- Hit The Map
-
-
- );
-};
-
-// ═══════════════════════════════════════════════════════════════════
-// 3. SHOWDOWN & STREAK — side by side stat cards
-// ═══════════════════════════════════════════════════════════════════
-const ShowdownCard = ({ gamestate }) => (
-
- Neighborhood} />
-
-
-
{[1, 2, 3, 4, 5].map(s => )}
-
+250 XP
-
-
-);
-
-const StreakCard = ({ gamestate }) => (
-
-
-
-
-
Current Streak
-
- Days
-
-
Personal Best: 14 Days
-
-
-
{[1, 2, 3, 4, 5].map(s =>
)}
-
+50 XP
-
-
-);
-
-// ═══════════════════════════════════════════════════════════════════
-// 4. REWARDS & CHECKPOINTS — named stages with reward labels
-// ═══════════════════════════════════════════════════════════════════
-const STAGE_NAMES = ['Daily Grind', 'Door Warrior', 'Lead Machine', 'Closer Mode', 'Legend Run'];
-const STAGE_REWARDS = ['+500 XP Boost', '$25 Gift Card', '$50 Cash Bonus', '???', '???'];
-
-const RewardsCheckpoints = ({ stages, className = '' }) => {
- const completedCount = stages.filter(s => s.status === 'completed').length;
- const totalPct = stages.length > 0 ? (completedCount / stages.length) * 100 : 0;
- const activeStage = stages.find(s => s.status === 'in-progress') || stages[completedCount] || stages[0];
-
- return (
-
- {Math.round(totalPct)}%} />
-
- {/* Stage timeline */}
-
- {/* Connecting line through all stages */}
-
-
-
- {stages.map((stage, idx) => {
- const done = stage.status === 'completed', active = stage.status === 'in-progress';
- const stageColor = done ? C.gold : active ? C.neon : 'rgb(63 63 70)';
- const stageName = STAGE_NAMES[idx] || `Stage ${idx + 1}`;
- const stageReward = STAGE_REWARDS[idx] || '???';
-
- return (
-
-
- {done ? : !done && !active ? :
- {idx + 1}}
-
-
{stageName}
- {done &&
- {stage.progress?.current || 0}/{stage.progress?.target || 0} ✓
-
}
-
- );
- })}
-
-
- {/* Reward tiles */}
-
- {stages.map((stage, idx) => {
- const done = stage.status === 'completed', active = stage.status === 'in-progress';
- const stageReward = stage.reward?.name || STAGE_REWARDS[idx] || '???';
- return (
-
- );
- })}
-
-
- );
-};
-
-// ═══════════════════════════════════════════════════════════════════
-// 5. BADGES & ACHIEVEMENTS — tall badge cards with descriptions
-// ═══════════════════════════════════════════════════════════════════
-const BADGE_ICONS = { Hunter: Crosshair, Storm: Zap, Star: Star, Master: Map };
-const BADGE_COLORS = { Hunter: C.fire, Storm: C.gold, Star: C.pink, Master: C.green };
-
-const BadgesAchievements = ({ badges, className = '' }) => (
-
-
-
- {badges.map((badge, idx) => {
- const unlocked = badge.status === 'unlocked';
- const key = Object.keys(BADGE_ICONS).find(k => badge.id?.includes(k)) || null;
- const Icon = key ? BADGE_ICONS[key] : Shield;
- const color = key ? BADGE_COLORS[key] : C.cyan;
- return (
-
- {unlocked && (
-
-
-
- )}
-
- {badge.title}
-
- {unlocked ? (badge.progressText || 'UNLOCKED') : badge.criteria}
-
-
- );
- })}
-
-
-);
-
-// ═══════════════════════════════════════════════════════════════════
-// 6. LEADERBOARD — full-width, chunky rows
-// ═══════════════════════════════════════════════════════════════════
-const Leaderboard = ({ className = '' }) => {
- const { leaderboard } = useGamification();
- const countdown = useMonthResetCountdown();
-
- return (
-
-
-
- Resets in
- {countdown}
- } />
-
-
- {leaderboard.map((agent, i) => {
- const isUser = agent.isUser, rank = i + 1, isTop3 = rank <= 3;
- const medalColor = rank === 1 ? C.gold : rank === 2 ? '#A8A9AD' : rank === 3 ? '#CD7F32' : null;
- return (
-
- {isTop3 && (
-
- )}
-
- {isTop3 ? (
-
- ) :
{rank}.}
-
- {agent.name.split(' ').map(n => n[0]).join('').slice(0, 2)}
-
-
- {agent.name}
-
-
-
-
-
-
- );
- })}
-
-
-
- );
-};
-
-// ═══════════════════════════════════════════════════════════════════
-// 7. ADMIN / OWNER WIDGETS
-// ═══════════════════════════════════════════════════════════════════
-const AdminCommandCenter = ({ className = '' }) => (
-
- Season Ops} />
-
- {[
- { label: 'Active Squads', value: 12, sub: 'All Online', color: C.cyan },
- { label: 'Doors Today', value: 1432, sub: '+14% vs Yesterday', color: C.gold },
- { label: 'Approval Queue', value: 8, sub: 'Needs Review', color: C.fire },
- ].map(({ label, value, sub, color }) => (
-
- ))}
-
-
-);
-
-const OwnerROIDashboard = ({ className = '' }) => (
-
- Cost YTD: $} />
-
- {[
- { label: 'Participation', value: '92%', color: C.green },
- { label: 'Prize Fund', value: '68%', color: C.gold },
- { label: 'Conversion Lift', value: '+24%', color: C.green },
- { label: 'Revenue', value: '$1.2M', color: C.gold },
- ].map(({ label, value, color }) => (
-
- ))}
-
-
-);
-
-// ═══════════════════════════════════════════════════════════════════
-// 8. LOG ACTION CARD + MODAL
-// ═══════════════════════════════════════════════════════════════════
const LOG_ACTIONS = [
- {
- key: 'knock', label: 'Door Knocked', icon: DoorOpen, color: C.cyan,
- fields: [
- { name: 'address', label: 'Address', type: 'text', placeholder: '123 Main St' },
- { name: 'outcome', label: 'Outcome', type: 'select', options: ['Interested', 'Not Home', 'Not Interested', 'Follow Up'] },
- { name: 'notes', label: 'Notes', type: 'textarea', placeholder: 'Any details...' },
- ]
- },
- {
- key: 'lead', label: 'Lead Gained', icon: UserPlus, color: C.neon,
- fields: [
- { name: 'name', label: 'Contact Name', type: 'text', placeholder: 'John Doe' },
- { name: 'phone', label: 'Phone', type: 'text', placeholder: '(555) 123-4567' },
- { name: 'email', label: 'Email', type: 'text', placeholder: 'john@example.com' },
- { name: 'address', label: 'Property Address', type: 'text', placeholder: '456 Oak Ave' },
- { name: 'notes', label: 'Notes', type: 'textarea', placeholder: 'Lead source & details...' },
- ]
- },
- {
- key: 'appt', label: 'Appointment Set', icon: Calendar, color: C.gold,
- fields: [
- { name: 'clientName', label: 'Client Name', type: 'text', placeholder: 'Jane Smith' },
- { name: 'date', label: 'Date & Time', type: 'datetime-local' },
- { name: 'address', label: 'Address', type: 'text', placeholder: '789 Pine Rd' },
- { name: 'type', label: 'Type', type: 'select', options: ['Roof Inspection', 'Estimate', 'Follow Up', 'Other'] },
- { name: 'notes', label: 'Notes', type: 'textarea', placeholder: 'Meeting details...' },
- ]
- },
- {
- key: 'meeting', label: 'Client Meeting', icon: Handshake, color: C.purple,
- fields: [
- { name: 'clientName', label: 'Client Name', type: 'text', placeholder: 'Client name' },
- { name: 'outcome', label: 'Outcome', type: 'select', options: ['Deal Closed', 'Follow Up Needed', 'Proposal Sent', 'No Deal'] },
- { name: 'followUp', label: 'Follow-up Date', type: 'date' },
- { name: 'notes', label: 'Notes', type: 'textarea', placeholder: 'Meeting summary...' },
- ]
- },
+ { key: 'knock', label: 'Door Knocked', icon: DoorOpen, color: C.cyan },
+ { key: 'lead', label: 'Lead Gained', icon: UserPlus, color: C.neon },
+ { key: 'appt', label: 'Appointment', icon: Target, color: C.gold },
+ { key: 'meeting', label: 'Client Meet', icon: Handshake, color: C.purple },
];
-const LogActionCard = ({ className = '', onAction }) => (
-
-
-
- {LOG_ACTIONS.map(({ key, label, icon: I, color }) => (
-
onAction(key)}
- className="rounded-xl p-4 flex flex-col items-center justify-center gap-2.5 cursor-pointer bg-zinc-100 dark:bg-white/[0.03] border border-zinc-200 dark:border-white/[0.06] hover:border-white/15 transition-colors">
-
-
-
- {label}
-
- ))}
+/*
+ * ==========================================
+ * PREMIUM COMPONENTS (SPORTS UI)
+ * ==========================================
+ */
+
+// Hexagonal level badge (Like FIFA overall rating)
+const HexLevelBadge = ({ level, color = C.gold, size = 100, mobileSize = 85 }) => (
+
);
-const LogActionModal = ({ actionKey, onClose }) => {
- const config = LOG_ACTIONS.find(a => a.key === actionKey);
- const [form, setForm] = useState({});
- if (!config) return null;
- const Icon = config.icon;
+// Fut-style Player Card
+const OperatorCard = ({ user, gamestate }) => {
+ return (
+
+ {/* Card Background (Elite Gold Theme) */}
+
+
+ {/* Glossy overlay */}
+
+
+ {/* Dynamic Light rays */}
+
+
+ {/* Inner Content Container */}
+
+
+ {/* Header Row: OVR and 3 Badges */}
+
+
+ {/* Player Cutout / Photo */}
+
+
+
+
+ {/* Name & Title Board */}
+
+
+
+ {user.name}
+
+
+ {gamestate.currentTitle || 'LVL 12 - FIELD RUNNER'}
+
+
+
+
+
+
+ KNK
+
+
+
+ LED
+
+
+
+ APT
+
+
+
+ STR
+
+
+
+
+
+ {/* Bottom Card Flare */}
+
+
+ );
+};
+
+// Sleek Glass Panel (base container for UI)
+const GlassPanel = ({ children, className = '', accentColor = 'transparent', title, icon: Icon, extra }) => (
+
+
+ {/* Subtle accent glow at the top */}
+ {accentColor !== 'transparent' && (
+
+ )}
+
+ {/* Header if provided */}
+ {(title || Icon || extra) && (
+
+
+ {Icon && }
+
+ {title}
+
+
+ {extra &&
{extra}
}
+
+ )}
+
+
+ {children}
+
+
+);
+
+// Progress Bar (Sleek, bright energy bar)
+const EnergyBar = ({ current, max, color = C.neon, label }) => {
+ const pct = Math.min((current / max) * 100, 100);
+ return (
+
+ {label && (
+
+ {label}
+ / {max}
+
+ )}
+
+
+ );
+};
+
+/*
+ * ==========================================
+ * MODALS
+ * ==========================================
+ */
+
+const LogActionModal = ({ actionKey, onClose, gs }) => {
+ const action = LOG_ACTIONS.find(a => a.key === actionKey) || LOG_ACTIONS[0];
+ const Icon = action.icon;
+ const [xpReward, setXpReward] = useState(actionKey === 'knock' ? 10 : actionKey === 'lead' ? 50 : 150);
const handleSubmit = (e) => {
e.preventDefault();
- // In production: dispatch to API
+ const coords = { x: window.innerWidth / 2, y: window.innerHeight / 2 };
+ if (actionKey === 'knock') gs.logKnock(xpReward > 10, coords);
+ else if (actionKey === 'lead' || actionKey === 'meeting') gs.logLead(coords);
+ else if (actionKey === 'appt') gs.logInspection(coords);
onClose();
};
return (
-
-
- e.stopPropagation()}
- className="relative w-full max-w-lg rounded-2xl overflow-hidden bg-zinc-50 dark:bg-zinc-950 border border-zinc-200 dark:border-white/10 shadow-2xl">
- {/* Header */}
-
-
-
-
-
-
{config.label}
+
+
+
+
+
+
+
-
-
-
+
+
Log {action.label}
+ Gain XP instantly
+
+
- {/* Form */}
-