UNPKG

@codejoy/random-learner

Version:

A comprehensive interview preparation and learning companion with AI-powered questions, mock interviews, skill assessments, and company-specific question sets for technical job interviews

1,101 lines (943 loc) 38.2 kB
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Skill Assessment - Random Learner</title> <script src="https://cdn.jsdelivr.net/npm/chart.js"></script> <style> * { margin: 0; padding: 0; box-sizing: border-box; } body { font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); min-height: 100vh; padding: 20px; color: #333; } .container { max-width: 1400px; margin: 0 auto; background: white; border-radius: 20px; box-shadow: 0 20px 40px rgba(0,0,0,0.1); overflow: hidden; } .header { background: linear-gradient(135deg, #667eea, #764ba2); color: white; padding: 30px; text-align: center; } .header h1 { font-size: 2.5em; margin-bottom: 10px; } .header p { font-size: 1.1em; opacity: 0.9; } .content { padding: 30px; } /* Loading State */ .loading { text-align: center; padding: 60px; color: #666; } .loading::after { content: ''; width: 30px; height: 30px; border: 3px solid #667eea; border-top: 3px solid transparent; border-radius: 50%; animation: spin 1s linear infinite; margin: 20px auto; display: block; } @keyframes spin { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } } /* Assessment Overview */ .assessment-overview { display: grid; grid-template-columns: 1fr 2fr; gap: 30px; margin-bottom: 40px; } .overall-score { text-align: center; background: linear-gradient(135deg, #f8f9fa, #e9ecef); border-radius: 20px; padding: 40px; position: relative; } .score-circle { width: 200px; height: 200px; border-radius: 50%; background: conic-gradient(#667eea 0deg, #667eea var(--score-angle), #e9ecef var(--score-angle), #e9ecef 360deg); display: flex; align-items: center; justify-content: center; margin: 0 auto 20px; position: relative; } .score-circle::before { content: ''; width: 160px; height: 160px; background: white; border-radius: 50%; position: absolute; } .score-value { font-size: 3em; font-weight: bold; color: #667eea; z-index: 1; } .score-label { font-size: 1.2em; color: #666; margin-bottom: 20px; } .experience-level { background: linear-gradient(135deg, #667eea, #764ba2); color: white; padding: 15px 25px; border-radius: 25px; font-weight: bold; display: inline-block; } .assessment-info { background: #f8f9fa; border-radius: 15px; padding: 30px; } .info-grid { display: grid; grid-template-columns: repeat(2, 1fr); gap: 20px; } .info-item { text-align: center; } .info-value { font-size: 2em; font-weight: bold; color: #667eea; margin-bottom: 5px; } .info-label { color: #666; font-size: 0.9em; } /* Category Scores */ .category-scores { margin-bottom: 40px; } .section-title { font-size: 1.8em; color: #333; margin-bottom: 25px; display: flex; align-items: center; } .section-title::before { content: ''; width: 4px; height: 30px; background: linear-gradient(135deg, #667eea, #764ba2); margin-right: 15px; border-radius: 2px; } .categories-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); gap: 25px; } .category-card { background: #f8f9fa; border-radius: 15px; padding: 25px; border-left: 5px solid #667eea; transition: transform 0.3s ease, box-shadow 0.3s ease; } .category-card:hover { transform: translateY(-5px); box-shadow: 0 10px 30px rgba(0,0,0,0.1); } .category-name { font-size: 1.3em; font-weight: bold; color: #333; margin-bottom: 15px; } .category-score { font-size: 2.5em; font-weight: bold; color: #667eea; margin-bottom: 10px; } .score-bar { height: 8px; background: #e9ecef; border-radius: 4px; overflow: hidden; margin-bottom: 15px; } .score-fill { height: 100%; background: linear-gradient(90deg, #667eea, #764ba2); border-radius: 4px; transition: width 0.8s ease; } .confidence-badge { display: inline-block; padding: 4px 12px; border-radius: 12px; font-size: 0.8em; font-weight: bold; text-transform: uppercase; } .confidence-high { background: #d4edda; color: #155724; } .confidence-medium { background: #fff3cd; color: #856404; } .confidence-low { background: #f8d7da; color: #721c24; } /* Interview Readiness */ .interview-readiness { margin-bottom: 40px; } .readiness-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 30px; } .readiness-chart { background: #f8f9fa; border-radius: 15px; padding: 25px; } .readiness-details { background: #f8f9fa; border-radius: 15px; padding: 25px; } .readiness-item { display: flex; justify-content: space-between; align-items: center; padding: 12px 0; border-bottom: 1px solid #e9ecef; } .readiness-item:last-child { border-bottom: none; } .readiness-score { font-weight: bold; color: #667eea; } /* Strengths and Weaknesses */ .strengths-weaknesses { display: grid; grid-template-columns: 1fr 1fr; gap: 30px; margin-bottom: 40px; } .strengths, .weaknesses { background: #f8f9fa; border-radius: 15px; padding: 25px; } .strengths { border-left: 5px solid #28a745; } .weaknesses { border-left: 5px solid #dc3545; } .strength-item, .weakness-item { padding: 12px 0; border-bottom: 1px solid #e9ecef; display: flex; justify-content: space-between; align-items: center; } .strength-item:last-child, .weakness-item:last-child { border-bottom: none; } .item-name { font-weight: 500; } .item-score { font-weight: bold; } .strength-score { color: #28a745; } .weakness-score { color: #dc3545; } .severity-badge { padding: 2px 8px; border-radius: 10px; font-size: 0.7em; font-weight: bold; text-transform: uppercase; } .severity-critical { background: #f8d7da; color: #721c24; } .severity-high { background: #fff3cd; color: #856404; } .severity-medium { background: #cce5ff; color: #004085; } .severity-low { background: #d4edda; color: #155724; } /* Recommendations */ .recommendations { margin-bottom: 40px; } .recommendation-card { background: #f8f9fa; border-radius: 15px; padding: 25px; margin-bottom: 20px; border-left: 5px solid #667eea; } .recommendation-title { font-size: 1.2em; font-weight: bold; color: #333; margin-bottom: 10px; } .recommendation-description { color: #666; margin-bottom: 15px; line-height: 1.5; } .recommendation-meta { display: flex; justify-content: space-between; align-items: center; font-size: 0.9em; } .priority-badge { padding: 4px 12px; border-radius: 12px; font-weight: bold; text-transform: uppercase; } .priority-high { background: #f8d7da; color: #721c24; } .priority-medium { background: #fff3cd; color: #856404; } .priority-low { background: #d4edda; color: #155724; } .estimated-time { color: #666; } /* Certifications */ .certifications { margin-bottom: 40px; } .certifications-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); gap: 20px; } .certification-card { background: linear-gradient(135deg, #f8f9fa, #e9ecef); border-radius: 15px; padding: 25px; text-align: center; border: 2px solid #e9ecef; transition: all 0.3s ease; } .certification-card.earned { background: linear-gradient(135deg, #d4edda, #c3e6cb); border-color: #28a745; } .certification-badge { font-size: 3em; margin-bottom: 15px; } .certification-name { font-size: 1.1em; font-weight: bold; color: #333; margin-bottom: 10px; } .certification-description { color: #666; font-size: 0.9em; } /* Benchmark Comparison */ .benchmark-comparison { margin-bottom: 40px; } .benchmark-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); gap: 20px; } .benchmark-card { background: #f8f9fa; border-radius: 15px; padding: 25px; border-left: 5px solid #667eea; } .benchmark-level { font-size: 1.2em; font-weight: bold; color: #333; margin-bottom: 15px; } .salary-range { color: #28a745; font-weight: bold; margin-bottom: 10px; } .readiness-percentage { font-size: 1.5em; font-weight: bold; color: #667eea; margin-bottom: 10px; } .gap-info { font-size: 0.9em; color: #666; } /* Actions */ .actions { text-align: center; padding: 30px; background: #f8f9fa; border-radius: 15px; } .action-button { background: linear-gradient(135deg, #667eea, #764ba2); color: white; border: none; padding: 15px 30px; border-radius: 25px; font-size: 1.1em; font-weight: bold; cursor: pointer; margin: 0 10px; transition: all 0.3s ease; } .action-button:hover { transform: translateY(-2px); box-shadow: 0 8px 20px rgba(102, 126, 234, 0.3); } .action-button.secondary { background: #6c757d; } .action-button.danger { background: #dc3545; } /* Responsive Design */ @media (max-width: 768px) { .assessment-overview { grid-template-columns: 1fr; } .readiness-grid { grid-template-columns: 1fr; } .strengths-weaknesses { grid-template-columns: 1fr; } .info-grid { grid-template-columns: 1fr; } .categories-grid { grid-template-columns: 1fr; } .score-circle { width: 150px; height: 150px; } .score-circle::before { width: 120px; height: 120px; } .score-value { font-size: 2.5em; } } /* Chart containers */ .chart-container { position: relative; height: 300px; margin: 20px 0; } /* No data state */ .no-data { text-align: center; padding: 60px; color: #666; } .no-data h3 { margin-bottom: 15px; color: #333; } .no-data p { margin-bottom: 25px; } </style> </head> <body> <div class="container"> <div class="header"> <h1>🎯 Skill Assessment Report</h1> <p>Comprehensive analysis of your programming skills and interview readiness</p> </div> <div class="content"> <!-- Loading State --> <div class="loading" id="loading"> Analyzing your skills and generating comprehensive report... </div> <!-- No Data State --> <div class="no-data" id="no-data" style="display: none;"> <h3>📊 Not Enough Data</h3> <p>We need more learning data to provide a comprehensive skill assessment.</p> <p>Please answer at least 20 questions across different topics to get your first assessment.</p> <button class="action-button" onclick="closeWindow()">Continue Learning</button> </div> <!-- Assessment Content --> <div id="assessment-content" style="display: none;"> <!-- Assessment Overview --> <div class="assessment-overview"> <div class="overall-score"> <div class="score-circle" id="score-circle"> <div class="score-value" id="overall-score">0</div> </div> <div class="score-label">Overall Skill Score</div> <div class="experience-level" id="experience-level">Analyzing...</div> </div> <div class="assessment-info"> <h3 style="margin-bottom: 20px;">Assessment Summary</h3> <div class="info-grid"> <div class="info-item"> <div class="info-value" id="interview-readiness">0%</div> <div class="info-label">Interview Readiness</div> </div> <div class="info-item"> <div class="info-value" id="certifications-earned">0</div> <div class="info-label">Certifications Earned</div> </div> <div class="info-item"> <div class="info-value" id="strengths-count">0</div> <div class="info-label">Key Strengths</div> </div> <div class="info-item"> <div class="info-value" id="improvement-areas">0</div> <div class="info-label">Improvement Areas</div> </div> </div> </div> </div> <!-- Category Scores --> <div class="category-scores"> <h2 class="section-title">📈 Skill Categories</h2> <div class="categories-grid" id="categories-grid"> <!-- Categories will be populated by JavaScript --> </div> </div> <!-- Interview Readiness --> <div class="interview-readiness"> <h2 class="section-title">🎯 Interview Readiness</h2> <div class="readiness-grid"> <div class="readiness-chart"> <h3 style="margin-bottom: 20px;">Company Type Readiness</h3> <div class="chart-container"> <canvas id="readiness-chart"></canvas> </div> </div> <div class="readiness-details"> <h3 style="margin-bottom: 20px;">Role Readiness</h3> <div id="role-readiness"> <!-- Role readiness will be populated by JavaScript --> </div> </div> </div> </div> <!-- Strengths and Weaknesses --> <div class="strengths-weaknesses"> <div class="strengths"> <h3 style="margin-bottom: 20px; color: #28a745;">💪 Key Strengths</h3> <div id="strengths-list"> <!-- Strengths will be populated by JavaScript --> </div> </div> <div class="weaknesses"> <h3 style="margin-bottom: 20px; color: #dc3545;">🎯 Areas for Improvement</h3> <div id="weaknesses-list"> <!-- Weaknesses will be populated by JavaScript --> </div> </div> </div> <!-- Recommendations --> <div class="recommendations"> <h2 class="section-title">💡 Personalized Recommendations</h2> <div id="recommendations-list"> <!-- Recommendations will be populated by JavaScript --> </div> </div> <!-- Certifications --> <div class="certifications"> <h2 class="section-title">🏆 Skill Certifications</h2> <div class="certifications-grid" id="certifications-grid"> <!-- Certifications will be populated by JavaScript --> </div> </div> <!-- Benchmark Comparison --> <div class="benchmark-comparison"> <h2 class="section-title">📊 Industry Benchmark Comparison</h2> <div class="benchmark-grid" id="benchmark-grid"> <!-- Benchmarks will be populated by JavaScript --> </div> </div> <!-- Actions --> <div class="actions"> <button class="action-button" id="new-assessment-btn">🔄 New Assessment</button> <button class="action-button secondary" id="view-history-btn">📈 View History</button> <button class="action-button secondary" id="export-report-btn">📤 Export Report</button> <button class="action-button secondary" onclick="closeWindow()">✖️ Close</button> </div> </div> </div> </div> <script> const { ipcRenderer } = require('electron'); let assessmentData = null; let readinessChart = null; // Initialize the skill assessment interface async function initializeAssessment() { try { console.log('Initializing skill assessment...'); // Conduct new assessment assessmentData = await ipcRenderer.invoke('conduct-skill-assessment'); console.log('Assessment data received:', assessmentData); if (assessmentData.dataQuality === 'low' && assessmentData.overallScore === 0) { showNoDataState(); } else { displayAssessment(assessmentData); showAssessmentContent(); } setupEventListeners(); } catch (error) { console.error('Failed to initialize assessment:', error); showError('Failed to generate skill assessment'); } } // Display the assessment results function displayAssessment(assessment) { // Overall score and experience level document.getElementById('overall-score').textContent = assessment.overallScore; document.getElementById('experience-level').textContent = assessment.experienceLevel?.name || 'Analyzing...'; // Set score circle progress const scoreAngle = (assessment.overallScore / 100) * 360; document.getElementById('score-circle').style.setProperty('--score-angle', `${scoreAngle}deg`); // Assessment summary document.getElementById('interview-readiness').textContent = `${assessment.interviewReadiness?.overall || 0}%`; document.getElementById('certifications-earned').textContent = assessment.certifications?.length || 0; document.getElementById('strengths-count').textContent = assessment.strengths?.length || 0; document.getElementById('improvement-areas').textContent = assessment.weaknesses?.length || 0; // Category scores displayCategoryScores(assessment.categoryScores); // Interview readiness displayInterviewReadiness(assessment.interviewReadiness); // Strengths and weaknesses displayStrengthsAndWeaknesses(assessment.strengths, assessment.weaknesses); // Recommendations displayRecommendations(assessment.recommendations); // Certifications displayCertifications(assessment.certifications); // Benchmark comparison displayBenchmarkComparison(assessment.benchmarkComparison); } // Display category scores function displayCategoryScores(categoryScores) { const container = document.getElementById('categories-grid'); container.innerHTML = ''; const categoryNames = { 'technical-skills': 'Technical Skills', 'problem-solving': 'Problem Solving', 'coding-proficiency': 'Coding Proficiency', 'communication': 'Communication' }; Object.entries(categoryScores).forEach(([category, data]) => { const card = document.createElement('div'); card.className = 'category-card'; card.innerHTML = ` <div class="category-name">${categoryNames[category] || category}</div> <div class="category-score">${Math.round(data.score)}</div> <div class="score-bar"> <div class="score-fill" style="width: ${data.score}%"></div> </div> <div class="confidence-badge confidence-${data.confidence}"> ${data.confidence} confidence </div> `; container.appendChild(card); }); } // Display interview readiness function displayInterviewReadiness(readiness) { if (!readiness) return; // Company readiness chart const ctx = document.getElementById('readiness-chart').getContext('2d'); const companyData = readiness.byCompany || {}; const companyLabels = Object.keys(companyData).map(key => { const names = { 'startup': 'Startup', 'big-tech': 'Big Tech', 'enterprise': 'Enterprise', 'consulting': 'Consulting' }; return names[key] || key; }); const companyScores = Object.values(companyData); readinessChart = new Chart(ctx, { type: 'radar', data: { labels: companyLabels, datasets: [{ label: 'Readiness Score', data: companyScores, backgroundColor: 'rgba(102, 126, 234, 0.2)', borderColor: 'rgba(102, 126, 234, 1)', borderWidth: 2, pointBackgroundColor: 'rgba(102, 126, 234, 1)' }] }, options: { responsive: true, maintainAspectRatio: false, scales: { r: { beginAtZero: true, max: 100, ticks: { stepSize: 20 } } }, plugins: { legend: { display: false } } } }); // Role readiness const roleContainer = document.getElementById('role-readiness'); roleContainer.innerHTML = ''; const roleData = readiness.byRole || {}; const roleNames = { 'frontend': 'Frontend Developer', 'backend': 'Backend Developer', 'fullstack': 'Full-Stack Developer', 'data-science': 'Data Scientist' }; Object.entries(roleData).forEach(([role, score]) => { const item = document.createElement('div'); item.className = 'readiness-item'; item.innerHTML = ` <span>${roleNames[role] || role}</span> <span class="readiness-score">${score}%</span> `; roleContainer.appendChild(item); }); } // Display strengths and weaknesses function displayStrengthsAndWeaknesses(strengths, weaknesses) { // Strengths const strengthsContainer = document.getElementById('strengths-list'); strengthsContainer.innerHTML = ''; if (strengths && strengths.length > 0) { strengths.slice(0, 8).forEach(strength => { const item = document.createElement('div'); item.className = 'strength-item'; item.innerHTML = ` <span class="item-name">${strength.description || strength.name}</span> <span class="strength-score">${Math.round(strength.score)}%</span> `; strengthsContainer.appendChild(item); }); } else { strengthsContainer.innerHTML = '<p style="color: #666; text-align: center;">Keep practicing to identify your strengths!</p>'; } // Weaknesses const weaknessesContainer = document.getElementById('weaknesses-list'); weaknessesContainer.innerHTML = ''; if (weaknesses && weaknesses.length > 0) { weaknesses.slice(0, 8).forEach(weakness => { const item = document.createElement('div'); item.className = 'weakness-item'; item.innerHTML = ` <div> <span class="item-name">${weakness.description || weakness.name}</span> <span class="severity-badge severity-${weakness.severity}">${weakness.severity}</span> </div> <span class="weakness-score">${Math.round(weakness.score)}%</span> `; weaknessesContainer.appendChild(item); }); } else { weaknessesContainer.innerHTML = '<p style="color: #666; text-align: center;">Great! No major weaknesses identified.</p>'; } } // Display recommendations function displayRecommendations(recommendations) { const container = document.getElementById('recommendations-list'); container.innerHTML = ''; if (recommendations && recommendations.length > 0) { recommendations.slice(0, 6).forEach(rec => { const card = document.createElement('div'); card.className = 'recommendation-card'; card.innerHTML = ` <div class="recommendation-title">${rec.title}</div> <div class="recommendation-description">${rec.description}</div> <div class="recommendation-meta"> <span class="priority-badge priority-${rec.priority}">${rec.priority} priority</span> <span class="estimated-time">⏱️ ${rec.estimatedTime}</span> </div> `; container.appendChild(card); }); } else { container.innerHTML = '<p style="color: #666; text-align: center;">You\'re doing great! Keep up the excellent work.</p>'; } } // Display certifications function displayCertifications(certifications) { const container = document.getElementById('certifications-grid'); container.innerHTML = ''; // Get all certification levels const allCertifications = [ { level: 'bronze', name: 'Bronze Certification', badge: '🥉', description: 'Basic programming competency', earned: false }, { level: 'silver', name: 'Silver Certification', badge: '🥈', description: 'Intermediate programming skills', earned: false }, { level: 'gold', name: 'Gold Certification', badge: '🥇', description: 'Advanced programming expertise', earned: false }, { level: 'platinum', name: 'Platinum Certification', badge: '💎', description: 'Expert-level programming mastery', earned: false } ]; // Mark earned certifications if (certifications) { certifications.forEach(cert => { const found = allCertifications.find(c => c.level === cert.level); if (found) { found.earned = true; found.earnedDate = cert.earnedDate; found.score = cert.score; } }); } allCertifications.forEach(cert => { const card = document.createElement('div'); card.className = `certification-card ${cert.earned ? 'earned' : ''}`; card.innerHTML = ` <div class="certification-badge">${cert.badge}</div> <div class="certification-name">${cert.name}</div> <div class="certification-description">${cert.description}</div> ${cert.earned ? `<div style="margin-top: 10px; color: #28a745; font-weight: bold;">✅ Earned!</div>` : ''} `; container.appendChild(card); }); } // Display benchmark comparison function displayBenchmarkComparison(benchmarks) { const container = document.getElementById('benchmark-grid'); container.innerHTML = ''; if (benchmarks) { Object.entries(benchmarks).forEach(([level, data]) => { const card = document.createElement('div'); card.className = 'benchmark-card'; const readinessPercentage = Math.round(data.readiness * 100); const salaryRange = `$${data.salaryRange.min.toLocaleString()} - $${data.salaryRange.max.toLocaleString()}`; card.innerHTML = ` <div class="benchmark-level">${data.name}</div> <div class="salary-range">${salaryRange}</div> <div class="readiness-percentage">${readinessPercentage}% Ready</div> <div class="gap-info"> ${data.categoriesMet}/${data.totalCategories} categories met </div> `; container.appendChild(card); }); } } // Setup event listeners function setupEventListeners() { document.getElementById('new-assessment-btn').addEventListener('click', conductNewAssessment); document.getElementById('view-history-btn').addEventListener('click', viewAssessmentHistory); document.getElementById('export-report-btn').addEventListener('click', exportReport); } // Conduct new assessment async function conductNewAssessment() { try { document.getElementById('loading').style.display = 'block'; document.getElementById('assessment-content').style.display = 'none'; assessmentData = await ipcRenderer.invoke('conduct-skill-assessment'); if (assessmentData.dataQuality === 'low' && assessmentData.overallScore === 0) { showNoDataState(); } else { displayAssessment(assessmentData); showAssessmentContent(); } } catch (error) { console.error('Failed to conduct new assessment:', error); showError('Failed to generate new assessment'); } } // View assessment history async function viewAssessmentHistory() { try { const history = await ipcRenderer.invoke('get-assessment-history'); console.log('Assessment history:', history); // TODO: Implement history view alert('Assessment history feature coming soon!'); } catch (error) { console.error('Failed to get assessment history:', error); } } // Export report async function exportReport() { try { if (!assessmentData) return; const reportData = { timestamp: new Date().toISOString(), assessment: assessmentData }; const blob = new Blob([JSON.stringify(reportData, null, 2)], { type: 'application/json' }); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = `skill-assessment-${new Date().toISOString().split('T')[0]}.json`; a.click(); URL.revokeObjectURL(url); } catch (error) { console.error('Failed to export report:', error); } } // Show states function showAssessmentContent() { document.getElementById('loading').style.display = 'none'; document.getElementById('no-data').style.display = 'none'; document.getElementById('assessment-content').style.display = 'block'; } function showNoDataState() { document.getElementById('loading').style.display = 'none'; document.getElementById('assessment-content').style.display = 'none'; document.getElementById('no-data').style.display = 'block'; } function showError(message) { document.getElementById('loading').innerHTML = ` <h3 style="color: #dc3545;">⚠️ Error</h3> <p>${message}</p> <button class="action-button" onclick="closeWindow()">Close</button> `; } // Close window function closeWindow() { window.close(); } // Initialize when page loads document.addEventListener('DOMContentLoaded', initializeAssessment); </script> </body> </html>