|
35 | 35 | <button id="replayTourBtn" class="secondary-btn" onclick="replayTour()" title="Replay the onboarding tour">🧭 Tour</button> |
36 | 36 | <button class="secondary-btn" onclick="showHowToPlay()" title="How to play CyberArena" style="padding: 5px 10px; font-size: 10px;">🎮 Guide</button> |
37 | 37 | <button class="secondary-btn" onclick="window.location.href='profile.html'" title="Your Profile" style="padding: 5px 10px; font-size: 10px;">👤 Profile</button> |
| 38 | + <div style="position:relative;display:inline-block;"> |
| 39 | + <button id="bellBtn" class="secondary-btn" onclick="toggleBell()" title="Notifications" style="padding:5px 10px;font-size:14px;">🔔</button> |
| 40 | + <span id="bellBadge" style="display:none;position:absolute;top:-4px;right:-4px;background:#ff003c;color:#fff;font-size:9px;font-family:var(--font-main);border-radius:50%;width:16px;height:16px;display:flex;align-items:center;justify-content:center;pointer-events:none;">0</span> |
| 41 | + </div> |
38 | 42 | </div> |
39 | 43 | <button class="logout-btn" onclick="logout()">Logout</button> |
40 | 44 | </div> |
@@ -76,6 +80,15 @@ <h3 style="color:var(--neon-magenta); font-size:14px; margin-top:20px;">💡 Pro |
76 | 80 | </div> |
77 | 81 | </div> |
78 | 82 |
|
| 83 | + <!-- Notification dropdown --> |
| 84 | + <div id="bellPanel" style="display:none;position:fixed;top:95px;right:15px;z-index:9998;background:rgba(0,0,0,0.95);border:1px solid var(--neon-green);border-radius:4px;min-width:260px;max-width:320px;font-size:12px;box-shadow:0 0 20px rgba(57,255,20,0.15);"> |
| 85 | + <div style="display:flex;justify-content:space-between;align-items:center;padding:10px 14px;border-bottom:1px solid #1a1a1a;"> |
| 86 | + <span style="color:var(--neon-green);font-family:var(--font-cyber);font-size:11px;letter-spacing:1px;">🔔 UNLOCKS</span> |
| 87 | + <button onclick="clearBell()" style="font-size:9px;padding:2px 8px;clip-path:none;color:#555;border-color:#333;">Mark all read</button> |
| 88 | + </div> |
| 89 | + <div id="bellList" style="max-height:300px;overflow-y:auto;padding:8px 0;"></div> |
| 90 | + </div> |
| 91 | + |
79 | 92 | <div class="mission-section" id="tour-social"> |
80 | 93 | <h3 class="section-title">🎭 Social Engineering</h3> |
81 | 94 | <div class="missions"> |
@@ -128,7 +141,62 @@ <h3 class="section-title">🛠️ Tools & Knowledge</h3> |
128 | 141 | import { onAuthStateChanged } from "https://www.gstatic.com/firebasejs/10.12.2/firebase-auth.js"; |
129 | 142 | import { getUserData } from "./js/xp.js"; |
130 | 143 | import { initTour } from "./js/tour.js"; |
131 | | - import { applyLockStates, getNewlyUnlocked, showUnlockToast } from "./js/unlocks.js"; |
| 144 | + import { applyLockStates, getNewlyUnlocked, addUnlockNotification, getUnreadCount, markAllRead, getNotifications } from "./js/unlocks.js"; |
| 145 | + |
| 146 | + function updateBell() { |
| 147 | + const count = getUnreadCount(); |
| 148 | + const badge = document.getElementById('bellBadge'); |
| 149 | + if (count > 0) { |
| 150 | + badge.style.display = 'flex'; |
| 151 | + badge.textContent = count; |
| 152 | + } else { |
| 153 | + badge.style.display = 'none'; |
| 154 | + } |
| 155 | + } |
| 156 | + |
| 157 | + window.toggleBell = function() { |
| 158 | + const panel = document.getElementById('bellPanel'); |
| 159 | + const open = panel.style.display === 'block'; |
| 160 | + if (!open) { |
| 161 | + renderBellList(); |
| 162 | + panel.style.display = 'block'; |
| 163 | + } else { |
| 164 | + panel.style.display = 'none'; |
| 165 | + } |
| 166 | + }; |
| 167 | + |
| 168 | + function renderBellList() { |
| 169 | + const list = document.getElementById('bellList'); |
| 170 | + const notifications = getNotifications(); |
| 171 | + if (!notifications.length) { |
| 172 | + list.innerHTML = '<div style="padding:12px 14px;color:#555;">No unlocks yet. Keep earning XP!</div>'; |
| 173 | + return; |
| 174 | + } |
| 175 | + list.innerHTML = notifications.slice().reverse().map(n => ` |
| 176 | + <div style="padding:10px 14px;border-bottom:1px solid #111;display:flex;gap:10px;align-items:flex-start;opacity:${n.read ? '0.5' : '1'};"> |
| 177 | + <span style="font-size:16px;">${n.read ? '✅' : '🔓'}</span> |
| 178 | + <div> |
| 179 | + <div style="color:var(--neon-green);font-family:var(--font-cyber);font-size:11px;">${n.label}</div> |
| 180 | + <div style="color:#888;font-size:10px;margin-top:2px;">${n.description.replace('Reach','Reached')}</div> |
| 181 | + </div> |
| 182 | + </div> |
| 183 | + `).join(''); |
| 184 | + } |
| 185 | + |
| 186 | + window.clearBell = function() { |
| 187 | + markAllRead(); |
| 188 | + updateBell(); |
| 189 | + renderBellList(); |
| 190 | + }; |
| 191 | + |
| 192 | + // Close bell panel when clicking outside |
| 193 | + document.addEventListener('click', (e) => { |
| 194 | + const panel = document.getElementById('bellPanel'); |
| 195 | + const btn = document.getElementById('bellBtn'); |
| 196 | + if (panel && !panel.contains(e.target) && e.target !== btn) { |
| 197 | + panel.style.display = 'none'; |
| 198 | + } |
| 199 | + }); |
132 | 200 |
|
133 | 201 | function startActivityTicker() { |
134 | 202 | const activities = [ |
@@ -160,13 +228,14 @@ <h3 class="section-title">🛠️ Tools & Knowledge</h3> |
160 | 228 | if (userData) { |
161 | 229 | const xp = userData.xp || 0; |
162 | 230 | applyLockStates(xp); |
163 | | - const prevXP = parseInt(localStorage.getItem('prevXP') || '0'); |
164 | | - if (prevXP < xp) { |
165 | | - getNewlyUnlocked(prevXP, xp).forEach((u, i) => { |
166 | | - setTimeout(() => showUnlockToast(u), i * 1000); |
167 | | - }); |
| 231 | + // Store new unlocks into notification bell (never show popup) |
| 232 | + const seenUnlocks = JSON.parse(localStorage.getItem('seenUnlocks') || '[]'); |
| 233 | + const newItems = getNewlyUnlocked(0, xp).filter(u => !seenUnlocks.includes(u.id)); |
| 234 | + newItems.forEach(u => addUnlockNotification(u)); |
| 235 | + if (newItems.length) { |
| 236 | + localStorage.setItem('seenUnlocks', JSON.stringify([...seenUnlocks, ...newItems.map(u => u.id)])); |
168 | 237 | } |
169 | | - localStorage.setItem('prevXP', xp); |
| 238 | + updateBell(); |
170 | 239 | } |
171 | 240 | startActivityTicker(); |
172 | 241 | if (!localStorage.getItem('cyberarena_guide_seen')) { |
|
0 commit comments