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,012 lines (870 loc) 34.7 kB
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Random Learning Question</title> <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%); color: #333; min-height: 100vh; display: flex; align-items: flex-start; justify-content: center; padding: 20px 0; margin: 0; overflow-y: auto; } .container { background: white; border-radius: 15px; padding: 30px; box-shadow: 0 10px 30px rgba(0,0,0,0.2); max-width: min(800px, 90vw); width: 90%; min-height: fit-content; max-height: calc(100vh - 40px); overflow-y: auto; animation: slideIn 0.3s ease-out; position: relative; /* Enable absolute positioning for child elements */ margin: auto 0; } /* Responsive design for larger screens */ @media (min-width: 768px) { .container { width: 80%; max-width: 900px; } } @media (min-width: 1200px) { .container { width: 70%; max-width: 1000px; } } @keyframes slideIn { from { opacity: 0; transform: translateY(-20px); } to { opacity: 1; transform: translateY(0); } } .header { text-align: center; margin-bottom: 25px; position: relative; } .header h1 { color: #667eea; font-size: 1.5em; margin-bottom: 5px; display: flex; align-items: center; justify-content: center; } .header h1 img { width: 32px; height: 32px; margin-right: 10px; border-radius: 6px; background: transparent; /* Remove box-shadow to avoid background appearance */ } .header p { color: #666; font-size: 0.9em; } .question { background: #f8f9ff; border-left: 4px solid #667eea; padding: 20px; margin-bottom: 25px; border-radius: 8px; font-size: 1.1em; line-height: 1.4; position: relative; } .question-source-tag { position: absolute; top: 50%; right: 15px; transform: translateY(-50%); padding: 4px 8px; border-radius: 12px; font-size: 0.75em; font-weight: bold; text-transform: uppercase; letter-spacing: 0.5px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); z-index: 10; cursor: help; transition: transform 0.2s ease; } .question-source-tag:hover { transform: translateY(-50%) scale(1.05); } .tag-ai { background: linear-gradient(135deg, #667eea, #764ba2); color: white; } .tag-gemini { background: linear-gradient(135deg, #4285f4, #34a853); color: white; } .tag-openai { background: linear-gradient(135deg, #10a37f, #1a7f64); color: white; } .tag-static { background: linear-gradient(135deg, #718096, #4a5568); color: white; } .tag-static-fallback { background: linear-gradient(135deg, #ed8936, #dd6b20); color: white; } .topic-icon { position: absolute; top: 50%; left: 15px; transform: translateY(-50%); width: 32px; height: 32px; border-radius: 8px; display: flex; align-items: center; justify-content: center; font-size: 16px; font-weight: bold; box-shadow: 0 2px 4px rgba(0,0,0,0.1); z-index: 9; cursor: help; transition: transform 0.2s ease; } .topic-icon:hover { transform: translateY(-50%) scale(1.1); } /* Programming Language Icons */ .icon-python { background: linear-gradient(135deg, #3776ab, #ffd43b); color: white; } .icon-java { background: linear-gradient(135deg, #f89820, #ed8b00); color: white; } .icon-javascript { background: linear-gradient(135deg, #f7df1e, #f0db4f); color: #323330; } .icon-cpp { background: linear-gradient(135deg, #00599c, #004482); color: white; } .icon-csharp { background: linear-gradient(135deg, #239120, #68217a); color: white; } .icon-php { background: linear-gradient(135deg, #777bb4, #4f5b93); color: white; } .icon-ruby { background: linear-gradient(135deg, #cc342d, #a91e1e); color: white; } .icon-go { background: linear-gradient(135deg, #00add8, #007d9c); color: white; } .icon-rust { background: linear-gradient(135deg, #ce422b, #a33319); color: white; } .icon-swift { background: linear-gradient(135deg, #fa7343, #ff8c00); color: white; } .icon-kotlin { background: linear-gradient(135deg, #7f52ff, #0095d5); color: white; } /* Topic Category Icons */ .icon-database { background: linear-gradient(135deg, #336791, #2d5aa0); color: white; } .icon-web { background: linear-gradient(135deg, #61dafb, #21759b); color: white; } .icon-mobile { background: linear-gradient(135deg, #a4c639, #8bc34a); color: white; } .icon-algorithms { background: linear-gradient(135deg, #ff6b6b, #ee5a52); color: white; } .icon-security { background: linear-gradient(135deg, #ffa726, #ff9800); color: white; } .icon-devops { background: linear-gradient(135deg, #4caf50, #388e3c); color: white; } .icon-ai { background: linear-gradient(135deg, #9c27b0, #673ab7); color: white; } .icon-general { background: linear-gradient(135deg, #607d8b, #455a64); color: white; } /* Normal question padding - icons are now in header area */ .question { padding: 20px; /* Normal padding since icons are in header */ } /* Larger text for bigger screens */ @media (min-width: 768px) { .question { font-size: 1.2em; padding: 25px; line-height: 1.5; } .topic-icon { width: 36px; height: 36px; font-size: 18px; left: 20px; } .question-source-tag { right: 20px; } .header h1 { font-size: 1.8em; } .header h1 img { width: 36px; height: 36px; background: transparent; } .option { padding: 15px 20px; font-size: 1.1em; } .btn { padding: 15px 30px; font-size: 1.1em; } } .options { list-style: none; margin-bottom: 25px; } .option { margin-bottom: 12px; cursor: pointer; padding: 12px 15px; border: 2px solid #e0e0e0; border-radius: 8px; transition: all 0.2s ease; background: white; } .option:hover { border-color: #667eea; background: #f8f9ff; transform: translateX(5px); } .option.selected { border-color: #667eea; background: #667eea; color: white; } .option-letter { font-weight: bold; margin-right: 10px; color: #667eea; } .option.selected .option-letter { color: white; } .buttons { display: flex; gap: 10px; justify-content: center; } .btn { padding: 12px 25px; border: none; border-radius: 8px; cursor: pointer; font-size: 1em; font-weight: 500; transition: all 0.2s ease; text-transform: uppercase; letter-spacing: 0.5px; } .btn-primary { background: #667eea; color: white; } .btn-primary:hover:not(:disabled) { background: #5a67d8; transform: translateY(-2px); } .btn-secondary { background: #e2e8f0; color: #4a5568; } .btn-secondary:hover { background: #cbd5e0; } .btn:disabled { opacity: 0.6; cursor: not-allowed; } .feedback { margin-top: 20px; padding: 20px; border-radius: 8px; display: none; animation: fadeIn 0.3s ease-out; } @keyframes fadeIn { from { opacity: 0; } to { opacity: 1; } } .feedback.correct { background: #c6f6d5; border-left: 4px solid #38a169; color: #22543d; } .feedback.incorrect { background: #fed7d7; border-left: 4px solid #e53e3e; color: #742a2a; } .feedback-title { font-weight: bold; margin-bottom: 10px; font-size: 1.1em; } .feedback-explanation { line-height: 1.5; margin-top: 10px; font-style: italic; } .loading { text-align: center; color: #666; } .loading::after { content: ''; animation: dots 1.5s infinite; } @keyframes dots { 0%, 20% { content: '.'; } 40% { content: '..'; } 60%, 100% { content: '...'; } } /* Confirmation Dialog Styles */ .confirmation-overlay { position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0, 0, 0, 0.7); display: flex; align-items: center; justify-content: center; z-index: 1000; animation: fadeIn 0.3s ease-out; } .confirmation-dialog { background: white; border-radius: 15px; padding: 30px; box-shadow: 0 15px 35px rgba(0,0,0,0.3); max-width: 400px; width: 90%; text-align: center; animation: slideIn 0.3s ease-out; } .confirmation-title { font-size: 1.3em; font-weight: bold; color: #667eea; margin-bottom: 15px; } .confirmation-message { color: #333; margin-bottom: 25px; line-height: 1.5; } .confirmation-buttons { display: flex; gap: 10px; justify-content: center; margin-bottom: 20px; } .countdown { font-size: 0.9em; color: #666; font-style: italic; } .countdown #countdown-timer { font-weight: bold; color: #e53e3e; } /* Extension Notification Styles */ #extension-notification { position: fixed; top: 20px; right: 20px; z-index: 1001; animation: slideInRight 0.3s ease-out; } .extension-notice { background: #c6f6d5; border: 2px solid #38a169; color: #22543d; padding: 15px 20px; border-radius: 8px; font-weight: 500; box-shadow: 0 4px 12px rgba(0,0,0,0.1); } @keyframes slideInRight { from { opacity: 0; transform: translateX(100px); } to { opacity: 1; transform: translateX(0); } } /* Media queries for small screens and windows */ @media (max-width: 480px) { .container { padding: 15px; margin: 5px auto; max-height: calc(100vh - 10px); } body { padding: 5px 0; } } @media (max-height: 600px) { .container { max-height: calc(100vh - 20px); overflow-y: auto; } body { align-items: flex-start; padding: 10px 0; } .header { margin-bottom: 15px; } } </style> </head> <body> <div class="container"> <div class="header"> <h1><img src="icons/app-icon.png" alt="Random Learner">Learning Time!</h1> <p>Test your knowledge with this random question</p> <div id="question-meta" style="font-size: 0.8em; color: #666; margin-top: 10px;"></div> </div> <div id="loading" class="loading">Loading question</div> <div id="quiz-content" style="display: none;"> <div id="question" class="question"></div> <ul id="options" class="options"></ul> <div class="buttons"> <button id="submit-btn" class="btn btn-primary" disabled>Submit Answer</button> <button id="next-btn" class="btn btn-primary" style="display: none;">Next Question</button> <button id="skip-btn" class="btn btn-secondary">Skip Question</button> </div> <div id="feedback" class="feedback"></div> </div> </div> <script> const { ipcRenderer } = require('electron'); let currentQuestion = null; let selectedOption = null; // DOM elements const loadingEl = document.getElementById('loading'); const quizContentEl = document.getElementById('quiz-content'); const questionEl = document.getElementById('question'); const questionMetaEl = document.getElementById('question-meta'); const optionsEl = document.getElementById('options'); const submitBtn = document.getElementById('submit-btn'); const nextBtn = document.getElementById('next-btn'); const skipBtn = document.getElementById('skip-btn'); const feedbackEl = document.getElementById('feedback'); // Load question when window opens async function loadQuestion() { try { currentQuestion = await ipcRenderer.invoke('get-question'); displayQuestion(); } catch (error) { console.error('Failed to load question:', error); questionEl.textContent = 'Failed to load question. Please try again.'; } } function displayQuestion() { loadingEl.style.display = 'none'; quizContentEl.style.display = 'block'; // Set question text only questionEl.innerHTML = currentQuestion.question; // Add icons to the header area const header = document.querySelector('.header'); // Remove any existing icons const existingTopicIcon = header.querySelector('.topic-icon'); const existingSourceTag = header.querySelector('.question-source-tag'); if (existingTopicIcon) existingTopicIcon.remove(); if (existingSourceTag) existingSourceTag.remove(); // Add new icons to header if (currentQuestion.topic) { header.insertAdjacentHTML('beforeend', getTopicIcon(currentQuestion.topic)); } header.insertAdjacentHTML('beforeend', getSourceTag(currentQuestion.source, currentQuestion.aiEnabled)); // Show topic and level if available if (currentQuestion.topic || currentQuestion.level) { let metaText = ''; if (currentQuestion.topic && currentQuestion.topic !== 'general') { metaText += `Topic: ${currentQuestion.topic.charAt(0).toUpperCase() + currentQuestion.topic.slice(1)}`; } if (currentQuestion.level && currentQuestion.level !== 'mixed') { if (metaText) metaText += ' • '; metaText += `Level: ${currentQuestion.level.charAt(0).toUpperCase() + currentQuestion.level.slice(1)}`; } questionMetaEl.textContent = metaText; } optionsEl.innerHTML = ''; currentQuestion.options.forEach((option, index) => { const li = document.createElement('li'); li.className = 'option'; li.innerHTML = `<span class="option-letter">${String.fromCharCode(65 + index)})</span>${option}`; li.addEventListener('click', () => selectOption(index, li)); optionsEl.appendChild(li); }); } function getTopicIcon(topic) { if (!topic) return ''; let iconClass = ''; let iconText = ''; // Normalize topic for matching const normalizedTopic = topic.toLowerCase().trim(); // Programming Languages if (normalizedTopic.includes('python')) { iconClass = 'icon-python'; iconText = 'Py'; } else if (normalizedTopic.includes('java') && !normalizedTopic.includes('javascript')) { iconClass = 'icon-java'; iconText = 'J'; } else if (normalizedTopic.includes('javascript') || normalizedTopic.includes('js')) { iconClass = 'icon-javascript'; iconText = 'JS'; } else if (normalizedTopic.includes('c++') || normalizedTopic.includes('cpp')) { iconClass = 'icon-cpp'; iconText = 'C++'; } else if (normalizedTopic.includes('c#') || normalizedTopic.includes('csharp')) { iconClass = 'icon-csharp'; iconText = 'C#'; } else if (normalizedTopic.includes('php')) { iconClass = 'icon-php'; iconText = 'PHP'; } else if (normalizedTopic.includes('ruby')) { iconClass = 'icon-ruby'; iconText = 'Rb'; } else if (normalizedTopic.includes('go') || normalizedTopic.includes('golang')) { iconClass = 'icon-go'; iconText = 'Go'; } else if (normalizedTopic.includes('rust')) { iconClass = 'icon-rust'; iconText = 'Rs'; } else if (normalizedTopic.includes('swift')) { iconClass = 'icon-swift'; iconText = 'Sw'; } else if (normalizedTopic.includes('kotlin')) { iconClass = 'icon-kotlin'; iconText = 'Kt'; } // Topic Categories else if (normalizedTopic.includes('database') || normalizedTopic.includes('sql') || normalizedTopic.includes('mysql') || normalizedTopic.includes('postgresql')) { iconClass = 'icon-database'; iconText = 'DB'; } else if (normalizedTopic.includes('web') || normalizedTopic.includes('html') || normalizedTopic.includes('css') || normalizedTopic.includes('react') || normalizedTopic.includes('vue') || normalizedTopic.includes('angular')) { iconClass = 'icon-web'; iconText = 'Web'; } else if (normalizedTopic.includes('mobile') || normalizedTopic.includes('android') || normalizedTopic.includes('ios') || normalizedTopic.includes('flutter')) { iconClass = 'icon-mobile'; iconText = '📱'; } else if (normalizedTopic.includes('algorithm') || normalizedTopic.includes('data structure') || normalizedTopic.includes('sorting') || normalizedTopic.includes('search')) { iconClass = 'icon-algorithms'; iconText = 'Alg'; } else if (normalizedTopic.includes('security') || normalizedTopic.includes('encryption') || normalizedTopic.includes('auth')) { iconClass = 'icon-security'; iconText = '🔒'; } else if (normalizedTopic.includes('devops') || normalizedTopic.includes('docker') || normalizedTopic.includes('kubernetes') || normalizedTopic.includes('ci/cd')) { iconClass = 'icon-devops'; iconText = 'Ops'; } else if (normalizedTopic.includes('ai') || normalizedTopic.includes('machine learning') || normalizedTopic.includes('ml') || normalizedTopic.includes('neural')) { iconClass = 'icon-ai'; iconText = 'AI'; } else if (normalizedTopic.includes('programming') || normalizedTopic.includes('coding') || normalizedTopic.includes('development')) { iconClass = 'icon-general'; iconText = '💻'; } else { // Default general programming icon iconClass = 'icon-general'; iconText = '📝'; } const topicTooltip = `Topic: ${topic.charAt(0).toUpperCase() + topic.slice(1)} - This question focuses on ${topic} concepts`; return `<div class="topic-icon ${iconClass}" title="${topicTooltip}">${iconText}</div>`; } function getSourceTag(source, aiEnabled = false) { let tagClass = ''; let tagText = ''; let icon = ''; let tooltip = ''; switch (source) { case 'openai': tagClass = 'tag-openai'; tagText = 'OpenAI'; icon = '🤖'; tooltip = 'This question was generated by OpenAI GPT - AI-powered with comprehensive explanations'; break; case 'gemini': tagClass = 'tag-gemini'; tagText = 'Gemini'; icon = '✨'; tooltip = 'This question was generated by Google Gemini AI - Advanced AI with detailed explanations'; break; case 'static-fallback': tagClass = 'tag-static-fallback'; tagText = 'Static (AI Fallback)'; icon = '⚠️'; tooltip = 'AI was enabled but unavailable - Using static question as fallback'; break; case 'static': default: tagClass = 'tag-static'; tagText = 'Static'; icon = '📚'; tooltip = 'This is a pre-written static question from the built-in question bank'; break; } return `<div class="question-source-tag ${tagClass}" title="${tooltip}">${icon} ${tagText}</div>`; } function selectOption(index, element) { // Remove previous selection document.querySelectorAll('.option').forEach(opt => opt.classList.remove('selected')); // Select current option element.classList.add('selected'); selectedOption = index; submitBtn.disabled = false; } async function submitAnswer() { if (selectedOption === null) return; submitBtn.disabled = true; skipBtn.disabled = true; try { const result = await ipcRenderer.invoke('submit-answer', currentQuestion.id, selectedOption); showFeedback(result); } catch (error) { console.error('Failed to submit answer:', error); } } function showFeedback(result) { feedbackEl.style.display = 'block'; feedbackEl.className = `feedback ${result.correct ? 'correct' : 'incorrect'}`; const title = result.correct ? '🎉 Correct!' : '❌ Incorrect'; const answerInfo = result.correct ? `Great job! You selected: ${result.userAnswer}` : `You selected: ${result.userAnswer}<br>Correct answer: ${result.correctAnswer}`; let enhancedIndicator = ''; if (result.enhanced) { enhancedIndicator = '<div style="font-size: 0.8em; color: #666; margin-top: 5px;">🤖 Enhanced AI Feedback</div>'; } feedbackEl.innerHTML = ` <div class="feedback-title">${title}</div> <div>${answerInfo}</div> <div class="feedback-explanation">${result.explanation}</div> ${enhancedIndicator} `; // Show next question button and hide submit button submitBtn.style.display = 'none'; nextBtn.style.display = 'inline-block'; nextBtn.disabled = false; // Update skip button to close skipBtn.textContent = 'Close'; skipBtn.disabled = false; skipBtn.onclick = () => ipcRenderer.invoke('close-question'); // Auto-close with confirmation after 8 seconds (increased time for better UX) setTimeout(() => { showCloseConfirmation(); }, 8000); } function skipQuestion() { ipcRenderer.invoke('close-question'); } async function nextQuestion() { // Reset UI state resetQuestionUI(); // Show loading state loadingEl.style.display = 'block'; quizContentEl.style.display = 'none'; try { // Load next question currentQuestion = await ipcRenderer.invoke('next-question'); displayQuestion(); } catch (error) { console.error('Failed to load next question:', error); questionEl.textContent = 'Failed to load next question. Please try again.'; loadingEl.style.display = 'none'; quizContentEl.style.display = 'block'; } } function resetQuestionUI() { // Reset form state selectedOption = null; feedbackEl.style.display = 'none'; feedbackEl.className = 'feedback'; // Reset buttons submitBtn.style.display = 'inline-block'; submitBtn.disabled = true; nextBtn.style.display = 'none'; nextBtn.disabled = true; skipBtn.textContent = 'Skip Question'; skipBtn.disabled = false; skipBtn.onclick = skipQuestion; // Clear selections document.querySelectorAll('.option').forEach(opt => opt.classList.remove('selected')); } let extendedTimer = null; function showCloseConfirmation() { // Create confirmation dialog const confirmDialog = document.createElement('div'); confirmDialog.id = 'close-confirmation'; confirmDialog.innerHTML = ` <div class="confirmation-overlay"> <div class="confirmation-dialog"> <div class="confirmation-title">🎯 Continue Learning?</div> <div class="confirmation-message"> Great job on that question!<br> What would you like to do next? </div> <div class="confirmation-buttons"> <button id="next-question-btn" class="btn btn-primary">Next Question</button> <button id="extend-btn" class="btn btn-secondary">Keep Open (5 min)</button> <button id="close-now-btn" class="btn btn-secondary">Close Now</button> </div> <div id="countdown" class="countdown">Auto-closing in <span id="countdown-timer">15</span> seconds...</div> </div> </div> `; document.body.appendChild(confirmDialog); // Start countdown let countdown = 15; const countdownEl = document.getElementById('countdown-timer'); const countdownInterval = setInterval(() => { countdown--; countdownEl.textContent = countdown; if (countdown <= 0) { clearInterval(countdownInterval); closeWindow(); } }, 1000); // Event listeners document.getElementById('next-question-btn').addEventListener('click', () => { clearInterval(countdownInterval); document.body.removeChild(confirmDialog); nextQuestion(); }); document.getElementById('extend-btn').addEventListener('click', () => { clearInterval(countdownInterval); document.body.removeChild(confirmDialog); extendWindow(); }); document.getElementById('close-now-btn').addEventListener('click', () => { clearInterval(countdownInterval); closeWindow(); }); } function extendWindow() { // Clear any existing extended timer if (extendedTimer) { clearTimeout(extendedTimer); } // Show extension notification const notification = document.createElement('div'); notification.id = 'extension-notification'; notification.innerHTML = ` <div class="extension-notice"> ✅ Window extended for 5 more minutes </div> `; document.body.appendChild(notification); // Remove notification after 3 seconds setTimeout(() => { if (document.getElementById('extension-notification')) { document.body.removeChild(notification); } }, 3000); // Set new timer for 5 minutes extendedTimer = setTimeout(() => { showCloseConfirmation(); }, 300000); // 5 minutes } function closeWindow() { ipcRenderer.invoke('close-question'); } // Event listeners submitBtn.addEventListener('click', submitAnswer); nextBtn.addEventListener('click', nextQuestion); skipBtn.addEventListener('click', skipQuestion); // Load question on page load loadQuestion(); // Handle keyboard shortcuts document.addEventListener('keydown', (event) => { if (event.key >= '1' && event.key <= '4') { const index = parseInt(event.key) - 1; const options = document.querySelectorAll('.option'); if (options[index]) { selectOption(index, options[index]); } } else if (event.key === 'Enter') { if (nextBtn.style.display !== 'none' && !nextBtn.disabled) { // If next button is visible, load next question nextQuestion(); } else if (selectedOption !== null && !submitBtn.disabled) { // If answer is selected and submit is enabled, submit answer submitAnswer(); } } else if (event.key === 'Escape') { skipQuestion(); } else if (event.key === 'n' || event.key === 'N') { // 'N' key for next question (when available) if (nextBtn.style.display !== 'none' && !nextBtn.disabled) { nextQuestion(); } } }); </script> </body> </html>