From 628e945ea79f1018967059fba52453973422ef51 Mon Sep 17 00:00:00 2001 From: Satyam <95536056+Satyam-Rastogi@users.noreply.github.com> Date: Mon, 2 Mar 2026 20:27:22 +0530 Subject: [PATCH] feat: complete ProCanvas UI revamp with Gamified interactions and Modals --- src/pages/ProCanvas.jsx | 1891 +++++++++++++++++++-------------------- 1 file changed, 896 insertions(+), 995 deletions(-) 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 ? ( - {user.name} - ) : ( -
- {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 }) => ( -
- -

-

{lbl}

-
- ))} -
- - {/* 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 */} -
-
- - 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} /> -
-
- - - - -
-

1ST

-

Leads Today

-
-
-
{[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 ( -
- -

- {stageReward} -

-
- ); - })} -
- - ); -}; - -// ═══════════════════════════════════════════════════════════════════ -// 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 && ( - - - - )} -
- {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 }) => ( -
-

{label}

-

-

{sub}

-
- ))} -
-
-); - -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 }) => ( -
-

{label}

-

{value}

-
- ))} -
-
-); - -// ═══════════════════════════════════════════════════════════════════ -// 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 }) => ( +
+ + + +
+ OVR +
- +
); -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 */} -
- {config.fields.map(field => ( -
- - {field.type === 'select' ? ( - +
+
+ + +
+
+ + +
+
+ + - ) : field.type === 'textarea' ? ( -