3434 < button class ="secondary-btn " onclick ="toggleHighContrast() " style ="padding: 5px 10px; font-size: 10px; "> 🌓 High Contrast</ button >
3535 < button id ="replayTourBtn " class ="secondary-btn " onclick ="replayTour() " title ="Replay the onboarding tour "> 🧭 Tour</ button >
3636 < button class ="secondary-btn " onclick ="showHowToPlay() " title ="How to play CyberArena " style ="padding: 5px 10px; font-size: 10px; "> 🎮 Guide</ button >
37+ < button class ="secondary-btn " onclick ="window.location.href='profile.html' " title ="Your Profile " style ="padding: 5px 10px; font-size: 10px; "> 👤 Profile</ button >
3738 </ div >
3839 < button class ="logout-btn " onclick ="logout() "> Logout</ button >
3940 </ div >
4041
4142 < h1 > 🛡️ CyberArena</ h1 >
4243 < p > Select Your Mission</ p >
43-
44- <!-- How to Play Modal -->
4544 < div id ="howToPlayModal " class ="modal " style ="display:none; ">
4645 < div class ="modal-content " style ="max-width:600px; padding:30px; ">
4746 < h2 style ="color:var(--neon-cyan); margin-top:0; "> 🎮 How to Play CyberArena</ h2 >
@@ -122,94 +121,14 @@ <h3 class="section-title">🛠️ Tools & Knowledge</h3>
122121 </ div >
123122 </ div >
124123
125- < hr />
126-
127- < div class ="user-stats ">
128- < h2 > 📊 Your Profile</ h2 >
129- < div class ="profile-layout ">
130- < div class ="xp-container ">
131- < div id ="profileSkeleton ">
132- < div class ="skeleton " style ="width:60%; "> </ div >
133- < div class ="skeleton " style ="width:100%; height:12px; "> </ div >
134- < div class ="skeleton " style ="width:40%; "> </ div >
135- < div class ="skeleton " style ="width:50%; "> </ div >
136- </ div >
137- < div id ="profileData " style ="display:none; ">
138- < p id ="rankLabel " class ="rank-label "> Rank: Novice</ p >
139- < div class ="progress-bar ">
140- < div id ="userXPFill " style ="width: 0%; "> </ div >
141- </ div >
142- < p id ="userXP "> XP: Loading...</ p >
143- < p id ="missionsCompleted "> Missions Completed: Loading...</ p >
144- < button id ="shareScoreBtn " class ="secondary-btn " onclick ="shareScore() " style ="margin-top:10px; padding:8px 16px; font-size:11px; display:none; "> 📤 Share Your Score</ button >
145-
146- <!-- Mission Completion Tracker -->
147- < div id ="missionTracker " style ="margin-top:20px; display:none; ">
148- < h4 style ="font-size:12px; color:var(--neon-cyan); margin-bottom:8px; "> ✅ Completed Missions</ h4 >
149- < div id ="missionCheckList " style ="font-size:11px; line-height:1.8; color:#9ca3af; "> </ div >
150- </ div >
151- </ div >
152- </ div >
153-
154- < div class ="achievement-gallery ">
155- < h3 > 🏅 Achievements</ h3 >
156- < div id ="badgeList " class ="badge-list ">
157- <!-- Badges will be injected here -->
158- </ div >
159- </ div >
160- </ div >
161- </ div >
162-
163- < div class ="leaderboard ">
164- < h2 > 🏆 Global Leaderboard</ h2 >
165- < div id ="leaderboardSkeleton ">
166- < div class ="skeleton " style ="width:80%; "> </ div >
167- < div class ="skeleton " style ="width:65%; "> </ div >
168- < div class ="skeleton " style ="width:72%; "> </ div >
169- < div class ="skeleton " style ="width:55%; "> </ div >
170- < div class ="skeleton " style ="width:68%; "> </ div >
171- </ div >
172- < ul id ="leaderboardList " style ="display:none; ">
173- < li > Loading...</ li >
174- </ ul >
175- </ div >
176124 </ div >
177125
178126 < script type ="module ">
179127 import { auth , db } from "./js/firebase.js" ;
180128 import { onAuthStateChanged } from "https://www.gstatic.com/firebasejs/10.12.2/firebase-auth.js" ;
181129 import { getUserData } from "./js/xp.js" ;
182- import { collection , query , orderBy , limit , getDocs } from "https://www.gstatic.com/firebasejs/10.12.2/firebase-firestore.js" ;
183130 import { initTour } from "./js/tour.js" ;
184- import { applyLockStates , getNewlyUnlocked , showUnlockToast , UNLOCKS , getUnlockXP } from "./js/unlocks.js" ;
185-
186- async function loadLeaderboard ( ) {
187- try {
188- const list = document . getElementById ( "leaderboardList" ) ;
189- const q = query ( collection ( db , "users" ) , orderBy ( "xp" , "desc" ) , limit ( 5 ) ) ;
190- const querySnapshot = await getDocs ( q ) ;
191-
192- list . innerHTML = "" ;
193- if ( querySnapshot . empty ) {
194- list . innerHTML = "<li>No data available</li>" ;
195- } else {
196- querySnapshot . forEach ( ( doc ) => {
197- const data = doc . data ( ) ;
198- const li = document . createElement ( "li" ) ;
199- const name = data . email ? data . email . split ( '@' ) [ 0 ] : "Anonymous" ;
200- li . innerHTML = `<span>${ name } </span> <span>${ data . xp || 0 } XP</span>` ;
201- list . appendChild ( li ) ;
202- } ) ;
203- }
204- document . getElementById ( "leaderboardSkeleton" ) . style . display = "none" ;
205- list . style . display = "block" ;
206- } catch ( error ) {
207- console . error ( "Error loading leaderboard:" , error ) ;
208- document . getElementById ( "leaderboardSkeleton" ) . style . display = "none" ;
209- document . getElementById ( "leaderboardList" ) . innerHTML = "<li>Failed to load leaderboard</li>" ;
210- document . getElementById ( "leaderboardList" ) . style . display = "block" ;
211- }
212- }
131+ import { applyLockStates , getNewlyUnlocked , showUnlockToast } from "./js/unlocks.js" ;
213132
214133 function startActivityTicker ( ) {
215134 const activities = [
@@ -240,130 +159,24 @@ <h2>🏆 Global Leaderboard</h2>
240159 const userData = await getUserData ( ) ;
241160 if ( userData ) {
242161 const xp = userData . xp || 0 ;
243- document . getElementById ( "userXP" ) . innerText = `XP: ${ xp } ` ;
244- document . getElementById ( "missionsCompleted" ) . innerText = `Missions Completed: ${ userData . missionsCompleted || 0 } ` ;
245-
246- // Rank Calculation
247- let rank = "Novice" ;
248- let nextXP = 100 ;
249- let currentLevelXP = 0 ;
250-
251- if ( xp >= 500 ) {
252- rank = "Elite Guardian" ;
253- nextXP = 1000 ;
254- currentLevelXP = 500 ;
255- } else if ( xp >= 100 ) {
256- rank = "Specialist" ;
257- nextXP = 500 ;
258- currentLevelXP = 100 ;
259- }
260-
261- const progress = ( ( xp - currentLevelXP ) / ( nextXP - currentLevelXP ) ) * 100 ;
262- document . getElementById ( "rankLabel" ) . innerText = `Rank: ${ rank } ` ;
263- document . getElementById ( "userXPFill" ) . style . width = `${ Math . min ( progress , 100 ) } %` ;
264-
265- // Apply lock/unlock states to mission buttons
266162 applyLockStates ( xp ) ;
267-
268- // Show unlock toasts for anything newly unlocked this session
269163 const prevXP = parseInt ( localStorage . getItem ( 'prevXP' ) || '0' ) ;
270164 if ( prevXP < xp ) {
271165 getNewlyUnlocked ( prevXP , xp ) . forEach ( ( u , i ) => {
272166 setTimeout ( ( ) => showUnlockToast ( u ) , i * 1000 ) ;
273167 } ) ;
274168 }
275169 localStorage . setItem ( 'prevXP' , xp ) ;
276-
277- // Show profile data, hide skeleton
278- document . getElementById ( "profileSkeleton" ) . style . display = "none" ;
279- document . getElementById ( "profileData" ) . style . display = "block" ;
280- document . getElementById ( "shareScoreBtn" ) . style . display = "block" ;
281-
282- // Mission Completion Tracker
283- const completedMissions = userData . completedMissions || [ ] ;
284- const allMissions = [
285- { id : 'phishing' , name : '🕵️ Phishing Detective' } ,
286- { id : 'social' , name : '🎭 Social Engineering' } ,
287- { id : 'smishing' , name : '📱 Smishing Simulator' } ,
288- { id : 'ai' , name : '🤖 AI Crime Lab' } ,
289- { id : 'malware' , name : '🧩 Malware Escape' } ,
290- { id : 'password' , name : '🛡️ Password Lab' } ,
291- { id : 'darkweb' , name : '🌐 Dark Web Market' } ,
292- { id : 'incident' , name : '🚨 Incident Response' } ,
293- { id : 'creator' , name : '🧪 Mission Creator' } ,
294- { id : 'wiki' , name : '📖 Cyber-Wiki' }
295- ] ;
296-
297- const missionCheckList = document . getElementById ( 'missionCheckList' ) ;
298- missionCheckList . innerHTML = allMissions . map ( m => {
299- const completed = completedMissions . includes ( m . id ) ;
300- return `<div>${ completed ? '✅' : '⬜' } ${ m . name } </div>` ;
301- } ) . join ( '' ) ;
302- document . getElementById ( 'missionTracker' ) . style . display = 'block' ;
303-
304- // Unlocks Panel
305- const unlockPanel = document . createElement ( 'div' ) ;
306- unlockPanel . className = 'unlocks-panel' ;
307- unlockPanel . innerHTML = `<h4 style="font-size:12px;color:var(--neon-cyan);margin-bottom:8px;">🔓 Unlocks</h4>` +
308- UNLOCKS . filter ( u => u . type === 'mission' || u . type === 'feature' ) . map ( u => {
309- const done = xp >= u . xpRequired ;
310- const pct = Math . min ( 100 , Math . round ( ( xp / u . xpRequired ) * 100 ) ) ;
311- return `<div class="unlock-row">
312- <span class="unlock-icon">${ done ? '✅' : '🔒' } </span>
313- <span class="unlock-label">${ u . label } </span>
314- ${ done
315- ? `<span class="unlock-status-done">Unlocked</span>`
316- : `<div class="unlock-bar"><div class="unlock-bar-fill" style="width:${ pct } %"></div></div>
317- <span class="unlock-status-locked">${ u . xpRequired } XP</span>`
318- }
319- </div>` ;
320- } ) . join ( '' ) ;
321- document . getElementById ( 'profileData' ) . appendChild ( unlockPanel ) ;
322-
323- // Achievements Display
324- const badgeList = document . getElementById ( "badgeList" ) ;
325- badgeList . innerHTML = "" ;
326-
327- const possibleBadges = [
328- { id : "rookie" , name : "Rookie" , icon : "🔰" , threshold : 0 } ,
329- { id : "detective" , name : "Detective" , icon : "🔍" , threshold : 50 } ,
330- { id : "shield" , name : "Shield" , icon : "🛡️" , threshold : 200 } ,
331- { id : "elite" , name : "Elite" , icon : "👑" , threshold : 500 }
332- ] ;
333-
334- possibleBadges . forEach ( badge => {
335- if ( xp >= badge . threshold ) {
336- const span = document . createElement ( "span" ) ;
337- span . className = "badge" ;
338- span . title = badge . name ;
339- span . innerText = badge . icon ;
340- badgeList . appendChild ( span ) ;
341- }
342- } ) ;
343170 }
344- await loadLeaderboard ( ) ;
345171 startActivityTicker ( ) ;
346-
347- // Show How to Play modal on first login
348172 if ( ! localStorage . getItem ( 'cyberarena_guide_seen' ) ) {
349173 setTimeout ( ( ) => {
350174 document . getElementById ( 'howToPlayModal' ) . style . display = 'flex' ;
351175 } , 800 ) ;
352176 }
353-
354- // Start tour after everything is loaded
355177 initTour ( ) ;
356178 } catch ( error ) {
357179 console . error ( "Error loading user data:" , error ) ;
358- // Graceful offline / network failure fallback
359- document . getElementById ( "profileSkeleton" ) . style . display = "none" ;
360- document . getElementById ( "profileData" ) . style . display = "block" ;
361- document . getElementById ( "userXP" ) . innerText = "XP: unavailable (offline)" ;
362- document . getElementById ( "missionsCompleted" ) . innerText = "" ;
363- document . getElementById ( "rankLabel" ) . innerText = "Rank: --" ;
364- document . getElementById ( "leaderboardSkeleton" ) . style . display = "none" ;
365- document . getElementById ( "leaderboardList" ) . innerHTML = "<li style='color:#666;'>Leaderboard unavailable — check your connection.</li>" ;
366- document . getElementById ( "leaderboardList" ) . style . display = "block" ;
367180 startActivityTicker ( ) ;
368181 }
369182 }
@@ -381,28 +194,9 @@ <h2>🏆 Global Leaderboard</h2>
381194 localStorage . setItem ( 'cyberarena_guide_seen' , '1' ) ;
382195 }
383196
384- // Close modal on backdrop click
385197 document . getElementById ( 'howToPlayModal' ) . addEventListener ( 'click' , ( e ) => {
386198 if ( e . target === document . getElementById ( 'howToPlayModal' ) ) closeHowToPlay ( ) ;
387199 } ) ;
388-
389- function shareScore ( ) {
390- const xp = document . getElementById ( 'userXP' ) . innerText ;
391- const rank = document . getElementById ( 'rankLabel' ) . innerText ;
392- const missions = document . getElementById ( 'missionsCompleted' ) . innerText ;
393- const text = `🛡️ CyberArena Training Report\n${ rank } \n${ xp } \n${ missions } \n\nI'm training to become an Elite Guardian in cybersecurity! Try CyberArena 🔗` ;
394-
395- if ( navigator . share ) {
396- navigator . share ( { title : 'CyberArena Score' , text } ) ;
397- } else {
398- navigator . clipboard . writeText ( text ) . then ( ( ) => {
399- const btn = document . getElementById ( 'shareScoreBtn' ) ;
400- const orig = btn . innerText ;
401- btn . innerText = '✅ Copied!' ;
402- setTimeout ( ( ) => btn . innerText = orig , 2000 ) ;
403- } ) ;
404- }
405- }
406200 </ script >
407201 </ body >
408202</ html >
0 commit comments