// PwnHub - Cyberpunk CTF Collaboration Platform let currentChallengeId = null; function showNotification(message, type = 'info') { const notification = document.createElement('div'); notification.className = `notification ${type}`; notification.textContent = message; document.body.appendChild(notification); setTimeout(() => { notification.classList.add('show'); }, 100); setTimeout(() => { notification.classList.remove('show'); setTimeout(() => { document.body.removeChild(notification); }, 300); }, 3000); } function loadChecklistTemplate(category, challengeId) { const params = new URLSearchParams(); params.append('action', 'load_template'); params.append('category', category); params.append('challenge_id', challengeId); fetch('/api/checklist', { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded', }, body: params }) .then(response => response.text()) .then(html => { if (html.includes('success')) { showNotification(`${category} template loaded successfully!`, 'success'); refreshChallengeDetail(challengeId); } else { showNotification('Error loading template', 'error'); } }) .catch(error => { showNotification('Error loading template', 'error'); }); } function addCustomChecklistItem(challengeId) { const addItemDiv = document.getElementById(`add-item-${challengeId}`); if (!addItemDiv) { showNotification('Error: Add item form not found', 'error'); return; } addItemDiv.style.display = 'block'; const inputField = document.getElementById(`new-item-${challengeId}`); if (inputField) { inputField.focus(); } } function saveCustomChecklistItem(challengeId) { const itemText = document.getElementById(`new-item-${challengeId}`).value.trim(); if (!itemText) { showNotification('Please enter an item description', 'error'); return; } const params = new URLSearchParams(); params.append('action', 'add_item'); params.append('challenge_id', challengeId); params.append('item_text', itemText); params.append('assignee', 'Anonymous'); fetch('/api/checklist', { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded', }, body: params }) .then(response => response.text()) .then(html => { if (html.includes('success')) { showNotification('Item added successfully!', 'success'); cancelAddItem(challengeId); refreshChallengeDetail(challengeId); } else { showNotification('Error adding item', 'error'); } }) .catch(error => { showNotification('Error adding item', 'error'); }); } function cancelAddItem(challengeId) { const addItemDiv = document.getElementById(`add-item-${challengeId}`); addItemDiv.style.display = 'none'; document.getElementById(`new-item-${challengeId}`).value = ''; } function submitChecklistItem(event, challengeId) { event.preventDefault(); const form = event.target; const formData = new FormData(form); const params = new URLSearchParams(); for (let [key, value] of formData.entries()) { params.append(key, value); } fetch('/api/checklist', { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded', }, body: params }) .then(response => response.text()) .then(html => { if (html.includes('success')) { showNotification('Item added successfully!', 'success'); cancelAddItem(challengeId); refreshChallengeDetail(challengeId); } else { showNotification('Error adding item', 'error'); } }) .catch(error => { showNotification('Error adding item', 'error'); }); } function updateChecklistStatus(challengeId, itemText, status) { const params = new URLSearchParams(); params.append('action', 'update_status'); params.append('challenge_id', challengeId); params.append('item_text', itemText); params.append('status', status); params.append('assignee', 'Anonymous'); fetch('/api/checklist', { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded', }, body: params }) .then(response => response.text()) .then(html => { if (html.includes('success')) { showNotification(`Marked as ${status}`, 'success'); refreshChallengeDetail(challengeId); } else { showNotification('Error updating status', 'error'); } }) .catch(error => { showNotification('Error updating status', 'error'); }); } function uploadFiles(challengeId, files) { if (!files || files.length === 0) { return; } for (let file of files) { if (file.size > 10 * 1024 * 1024) { showNotification(`File ${file.name} is too large (max 10MB)`, 'error'); continue; } const reader = new FileReader(); reader.onload = function(e) { const params = new URLSearchParams(); params.append('challenge_id', challengeId); params.append('filename', file.name); params.append('content', e.target.result); params.append('uploader', 'Anonymous'); fetch('/api/upload', { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded', }, body: params }) .then(response => response.text()) .then(html => { if (html.includes('success')) { showNotification(`File ${file.name} uploaded successfully!`, 'success'); refreshChallengeDetail(challengeId); } else { showNotification(`Error uploading ${file.name}`, 'error'); } }) .catch(error => { showNotification('Error uploading file', 'error'); }); }; reader.readAsText(file); } } function downloadFile(challengeId, filename) { const link = document.createElement('a'); link.href = `/uploads/${challengeId}/${filename}`; link.download = filename; link.click(); showNotification(`Downloading ${filename}`, 'info'); } function viewFile(challengeId, filename) { let modal = document.getElementById('code-viewer-modal'); if (!modal) { modal = document.createElement('div'); modal.id = 'code-viewer-modal'; modal.className = 'code-modal'; modal.innerHTML = `

`; document.body.appendChild(modal); } const titleElement = document.getElementById('code-modal-title'); titleElement.textContent = filename; titleElement.dataset.filename = filename; const codeText = document.getElementById('code-text'); codeText.textContent = 'Loading...'; modal.style.display = 'flex'; fetch(`/uploads/${challengeId}/${filename}`) .then(response => response.text()) .then(content => { codeText.textContent = content; applySyntaxHighlighting(filename, codeText); }) .catch(error => { codeText.textContent = 'Error loading file content.'; showNotification('Error loading file', 'error'); }); } function closeCodeViewer() { const modal = document.getElementById('code-viewer-modal'); if (modal) { modal.style.display = 'none'; } } function applySyntaxHighlighting(filename, codeElement) { const extension = filename.split('.').pop().toLowerCase(); codeElement.className = ''; switch (extension) { case 'py': codeElement.className = 'language-python'; break; case 'js': codeElement.className = 'language-javascript'; break; case 'sh': codeElement.className = 'language-bash'; break; case 'php': codeElement.className = 'language-php'; break; case 'c': case 'cpp': codeElement.className = 'language-c'; break; case 'java': codeElement.className = 'language-java'; break; case 'rb': codeElement.className = 'language-ruby'; break; case 'go': codeElement.className = 'language-go'; break; case 'md': codeElement.className = 'language-markdown'; break; default: codeElement.className = 'language-text'; } } function refreshChallengeDetail(challengeId) { if (currentChallengeId === challengeId) { loadChallengeDetail(challengeId); } } function loadChallengeDetail(challengeId) { currentChallengeId = challengeId; const challengesGrid = document.querySelector('.challenges-grid'); const contentHeader = document.querySelector('.content-header'); if (challengesGrid) { challengesGrid.style.display = 'none'; } if (contentHeader) { contentHeader.style.display = 'none'; } let challengeDetailContainer = document.getElementById('challenge-detail-container'); if (!challengeDetailContainer) { challengeDetailContainer = document.createElement('div'); challengeDetailContainer.id = 'challenge-detail-container'; challengeDetailContainer.className = 'challenge-detail-container'; document.querySelector('.main-content').appendChild(challengeDetailContainer); } challengeDetailContainer.innerHTML = `

Loading challenge details...

`; challengeDetailContainer.style.display = 'block'; fetch(`/api/challenge-detail?id=${challengeId}`) .then(response => response.text()) .then(html => { challengeDetailContainer.innerHTML = html; const loadNotesWithRetry = (attemptNumber = 1, maxAttempts = 10) => { const notesTextarea = document.getElementById(`team-notes-${challengeId}`); if (notesTextarea) { loadTeamNotesForChallenge(challengeId); } else if (attemptNumber < maxAttempts) { setTimeout(() => { loadNotesWithRetry(attemptNumber + 1, maxAttempts); }, attemptNumber * 100); } }; loadNotesWithRetry(); setTimeout(() => { const items = document.querySelectorAll('.checklist-item, .uploaded-file, .flag-entry'); items.forEach((item, index) => { item.style.animationDelay = `${index * 0.05}s`; item.classList.add('fade-in-up'); }); }, 100); }) .catch(error => { challengeDetailContainer.innerHTML = `

Error Loading Challenge

Failed to load challenge details. Please try again.

`; showNotification('Error loading challenge details', 'error'); }); } function loadTeamNotesForChallenge(challengeId) { const notesTextarea = document.getElementById(`team-notes-${challengeId}`); if (!notesTextarea) { return; } fetch(`/api/team-notes?challenge_id=${challengeId}`) .then(response => response.text()) .then(notes => { if (notes && !notes.includes('error') && notes.trim() !== '') { notesTextarea.value = notes.trim(); showNotification('Team notes loaded', 'success'); } }) .catch(error => { }); } function backToDashboard() { const challengeDetailContainer = document.getElementById('challenge-detail-container'); if (challengeDetailContainer) { challengeDetailContainer.style.display = 'none'; } const challengesGrid = document.querySelector('.challenges-grid'); const contentHeader = document.querySelector('.content-header'); if (challengesGrid) { challengesGrid.style.display = 'grid'; setTimeout(() => { const cards = document.querySelectorAll('.challenge-card'); cards.forEach((card, index) => { card.style.animationDelay = `${index * 0.05}s`; card.classList.add('slide-in-right'); }); }, 100); } if (contentHeader) { contentHeader.style.display = 'flex'; } currentChallengeId = null; showNotification('Returned to dashboard', 'info'); } function initializeEnhancedUI() { const challengeCards = document.querySelectorAll('.challenge-card'); challengeCards.forEach(card => { card.addEventListener('mouseenter', function() { this.style.transform = 'translateY(-5px) scale(1.02)'; }); card.addEventListener('mouseleave', function() { this.style.transform = 'translateY(0) scale(1)'; }); }); const searchInput = document.querySelector('.search-input'); if (searchInput) { searchInput.addEventListener('focus', function() { this.style.boxShadow = '0 0 30px rgba(255, 107, 53, 0.4), inset 0 0 20px rgba(255, 107, 53, 0.1)'; }); searchInput.addEventListener('blur', function() { this.style.boxShadow = ''; }); } const buttons = document.querySelectorAll('.btn'); buttons.forEach(button => { button.addEventListener('click', function() { this.style.transform = 'scale(0.95)'; setTimeout(() => { this.style.transform = ''; }, 150); }); }); initializeTooltips(); } function initializeTooltips() { const tooltipElements = document.querySelectorAll('[title]'); tooltipElements.forEach(element => { const title = element.getAttribute('title'); if (title) { element.removeAttribute('title'); element.classList.add('tooltip'); const tooltipText = document.createElement('span'); tooltipText.className = 'tooltiptext'; tooltipText.textContent = title; element.appendChild(tooltipText); } }); } function updateProgress(challengeId) { const checklistItems = document.querySelectorAll(`[data-challenge-id="${challengeId}"] .checklist-item`); const completedItems = document.querySelectorAll(`[data-challenge-id="${challengeId}"] .checklist-item.status-completed`); if (checklistItems.length > 0) { const progress = (completedItems.length / checklistItems.length) * 100; const progressBar = document.querySelector(`[data-challenge-id="${challengeId}"] .progress-fill`); if (progressBar) { progressBar.style.width = `${progress}%`; } } } function enhanceSearch() { const searchInput = document.querySelector('.search-input'); if (searchInput) { searchInput.addEventListener('input', function() { const query = this.value.toLowerCase(); const challengeCards = document.querySelectorAll('.challenge-card'); challengeCards.forEach(card => { const title = card.querySelector('.challenge-name').textContent.toLowerCase(); const category = card.querySelector('.challenge-category').textContent.toLowerCase(); const tags = Array.from(card.querySelectorAll('.tag')).map(tag => tag.textContent.toLowerCase()); const matches = title.includes(query) || category.includes(query) || tags.some(tag => tag.includes(query)); if (matches || query === '') { card.style.display = 'block'; card.style.animation = 'fadeInUp 0.5s ease'; } else { card.style.display = 'none'; } }); }); } } function initializeKeyboardShortcuts() { document.addEventListener('keydown', function(e) { if (e.key === 'Escape') { if (currentChallengeId) { backToDashboard(); } } if (e.ctrlKey && e.key === 'f') { e.preventDefault(); if (!currentChallengeId) { const searchInput = document.querySelector('.search-input'); if (searchInput) { searchInput.focus(); } } } }); } document.addEventListener('DOMContentLoaded', function() { initializeEnhancedUI(); enhanceSearch(); initializeKeyboardShortcuts(); setTimeout(() => { const elements = document.querySelectorAll('.challenge-card, .team-member, .nav-link'); elements.forEach((element, index) => { element.style.animationDelay = `${index * 0.1}s`; element.classList.add('slide-in-right'); }); }, 500); showNotification('Welcome to PwnHub! CTF collaboration platform loaded.', 'success'); }); window.loadChecklistTemplate = loadChecklistTemplate; window.addCustomChecklistItem = addCustomChecklistItem; window.saveCustomChecklistItem = saveCustomChecklistItem; window.submitChecklistItem = submitChecklistItem; window.cancelAddItem = cancelAddItem; window.updateChecklistStatus = updateChecklistStatus; window.uploadFiles = uploadFiles; window.downloadFile = downloadFile; window.viewFile = viewFile; window.loadChallengeDetail = loadChallengeDetail; window.backToDashboard = backToDashboard; window.saveTeamNotes = saveTeamNotes; window.submitFlag = submitFlag; window.closeCodeViewer = closeCodeViewer; function saveTeamNotes(challengeId) { const notesTextarea = document.getElementById(`team-notes-${challengeId}`); if (!notesTextarea) { return; } const notes = notesTextarea.value.trim(); const params = new URLSearchParams(); params.append('challenge_id', challengeId); params.append('notes', notes); params.append('editor', 'Anonymous'); fetch('/api/team-notes', { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded', }, body: params }) .then(response => response.text()) .then(html => { if (html.includes('success')) { showNotification('Team notes saved successfully!', 'success'); } else { showNotification('Error saving notes', 'error'); } }) .catch(error => { showNotification('Error saving notes', 'error'); }); } function submitFlag(event, challengeId) { event.preventDefault(); const form = event.target; const formData = new FormData(form); const params = new URLSearchParams(); for (let [key, value] of formData.entries()) { params.append(key, value); } fetch('/api/submit-flag', { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded', }, body: params }) .then(response => response.text()) .then(html => { const resultDiv = document.getElementById(`flag-result-${challengeId}`); resultDiv.innerHTML = html; if (html.includes('success')) { showNotification('Flag submitted successfully!', 'success'); form.reset(); setTimeout(() => { refreshChallengeDetail(challengeId); }, 1000); } else { showNotification('Error submitting flag', 'error'); } }) .catch(error => { showNotification('Error submitting flag', 'error'); }); } function openAddChallengeModal() { const modal = document.getElementById('add-challenge-modal'); if (modal) { modal.classList.add('open'); document.body.style.overflow = 'hidden'; } } function closeAddChallengeModal() { const modal = document.getElementById('add-challenge-modal'); if (modal) { modal.classList.remove('open'); document.body.style.overflow = 'auto'; const form = modal.querySelector('form'); if (form) form.reset(); const response = document.getElementById('challenge-response'); if (response) response.innerHTML = ''; } } window.openAddChallengeModal = openAddChallengeModal; window.closeAddChallengeModal = closeAddChallengeModal; function openSettingsModal() { const modal = document.getElementById('settings-modal'); if (modal) { modal.classList.add('open'); document.body.style.overflow = 'hidden'; } } function closeSettingsModal() { const modal = document.getElementById('settings-modal'); if (modal) { modal.classList.remove('open'); document.body.style.overflow = 'auto'; const form = modal.querySelector('form'); if (form) form.reset(); const response = document.getElementById('settings-response'); if (response) response.innerHTML = ''; } } window.openSettingsModal = openSettingsModal; window.closeSettingsModal = closeSettingsModal; document.addEventListener('click', function(event) { const challengeModal = document.getElementById('add-challenge-modal'); const settingsModal = document.getElementById('settings-modal'); if (event.target === challengeModal) { closeAddChallengeModal(); } if (event.target === settingsModal) { closeSettingsModal(); } }); function deleteChallenge(challengeId, event) { event.stopPropagation(); if (confirm('Are you sure you want to delete this challenge? This action cannot be undone.')) { const params = new URLSearchParams(); params.append('challenge_id', challengeId); fetch('/api/delete-challenge', { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded', }, body: params }) .then(response => response.text()) .then(html => { if (html.includes('success')) { showNotification('Challenge deleted successfully!', 'success'); htmx.ajax('GET', '/api/content?tab=challenges', {target: '#main-content'}); } else { showNotification('Error deleting challenge', 'error'); } }) .catch(error => { showNotification('Error deleting challenge', 'error'); }); } } function searchChallenges(query) { const cards = document.querySelectorAll('.challenge-card'); const lowercaseQuery = query.toLowerCase(); cards.forEach(card => { const name = card.querySelector('.challenge-name').textContent.toLowerCase(); const category = card.querySelector('.challenge-category span:last-child').textContent.toLowerCase(); const tags = Array.from(card.querySelectorAll('.tag')).map(tag => tag.textContent.toLowerCase()).join(' '); const matches = name.includes(lowercaseQuery) || category.includes(lowercaseQuery) || tags.includes(lowercaseQuery); if (matches || query === '') { card.style.display = 'block'; card.style.animation = 'fadeInUp 0.3s ease'; } else { card.style.display = 'none'; } }); } window.deleteChallenge = deleteChallenge; window.searchChallenges = searchChallenges;