this is quiz.html
<!DOCTYPE html>
<html lang="bn">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>ডোপামিন - কুইজ</title>
<!-- Bootstrap CSS -->
<link href="https://cdn.jsdelivr.net/npm/
[email protected]/dist/css/bootstrap.min.css" rel="stylesheet">
<!-- Font Awesome CDN -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css" integrity="sha512-iecdLmaskl7CVkqkXNQ/ZH/XLlvWZOJyj7Yy7tcenmpD1ypASozpmT/E0iPtmFIB46ZmdtAc9eNBvH0H/ZpiBw==" crossorigin="anonymous" referrerpolicy="no-referrer" />
<!-- Toastify CSS -->
<link href="https://cdn.jsdelivr.net/npm/toastify-js/src/toastify.min.css" rel="stylesheet">
<!-- Chart.js CDN -->
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<!-- Chart.js Datalabels Plugin -->
<script src="https://cdn.jsdelivr.net/npm/chartjs-plugin-datalabels@2"></script>
<style>
@import url('https://fonts.googleapis.com/css?family=Hind+Siliguri');
:root {
--bg-color: #f5f5f5;
--text-color: #333;
--card-bg: #fff;
--shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
--border-color: #ddd;
--secondary-text-color: #666;
--correct-bg: #d4edda;
--correct-border: #28a745;
--incorrect-bg: #f8d7da;
--incorrect-border: #dc3545;
--purple-box: #9810fa;
--blue-box: #2b7fff;
--green-box: #00c950;
--orange-btn: #F97316;
--light-purple: #c084fc;
--answered-border: #00c950;
}
[data-theme="dark"] {
--bg-color: #1a1a1a;
--text-color: #e0e0e0;
--card-bg: #2d2d2d;
--shadow: 0 2px 10px rgba(255, 255, 255, 0.1);
--border-color: #444;
--secondary-text-color: #b0b0b0;
--correct-bg: #2e7d32;
--correct-border: #4caf50;
--incorrect-bg: #d32f2f;
--incorrect-border: #f44336;
--purple-box: #7C3AED;
--blue-box: #2563EB;
--green-box: #16A34A;
--orange-btn: #EA580C;
--light-purple: #A855F7;
--answered-border: #16A34A;
}
* {
margin: 0;
padding: 0;
box-sizing: border-box;
font-family: 'Hind Siliguri', sans-serif;
transition: background-color 0.3s, color 0.3s, border-color 0.3s;
}
body {
background: var(--bg-color);
color: var(--text-color);
min-height: 100vh;
display: flex;
flex-direction: column;
}
.navbar {
padding: 0.5rem 1rem;
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 1rem;
}
.nav-left {
display: flex;
flex-direction: column;
gap: 0.5rem;
}
.exam-title {
font-size: 1.5rem;
font-weight: bold;
color: var(--purple-box);
margin-bottom: 0.5rem;
}
.subject-box {
background: var(--purple-box);
color: white;
padding: 0.3rem 0.8rem;
border-radius: 8px;
font-size: 0.85rem;
}
.questions-box {
background: var(--blue-box);
color: white;
padding: 0.3rem 0.8rem;
border-radius: 8px;
font-size: 0.85rem;
}
.timer-box {
background: var(--green-box);
color: white;
padding: 0.3rem 0.8rem;
border-radius: 8px;
font-size: 0.85rem;
}
.nav-right {
display: flex;
align-items: center;
gap: 1rem;
}
.theme-toggle {
background: none;
border: none;
color: var(--text-color);
cursor: pointer;
font-size: 1.2rem;
padding: 0.5rem;
transition: transform 0.3s;
}
.theme-toggle:hover { transform: scale(1.1); }
.sticky-bar {
position: sticky;
top: 0;
background: var(--card-bg);
padding: 0.5rem 1rem;
box-shadow: var(--shadow);
display: flex;
justify-content: space-between;
align-items: center;
z-index: 99;
margin-bottom: 1rem;
}
.sticky-bar .answered-count {
font-size: 0.9rem;
font-weight: bold;
background: var(--blue-box);
color: white;
padding: 0.3rem 0.8rem;
border-radius: 8px;
}
.sticky-bar .timer {
font-size: 0.9rem;
font-weight: bold;
display: flex;
align-items: center;
gap: 0.3rem;
}
.sticky-bar .action-buttons {
display: flex;
gap: 0.5rem;
}
.btn-next {
background: none;
border: 1px solid var(--border-color);
padding: 0.3rem 0.8rem;
border-radius: 8px;
cursor: pointer;
font-size: 0.9rem;
transition: background-color 0.3s;
}
.btn-next:hover {
background-color: var(--purple-box);
color: white;
}
.container {
flex: 1;
padding: 1rem;
max-width: 1200px;
margin: 0 auto;
}
.header { margin-bottom: 20px; }
.progress-container {
margin-bottom: 20px;
display: flex;
align-items: center;
gap: 1rem;
}
.progress {
height: 8px;
background-color: var(--border-color);
border-radius: 8px;
overflow: hidden;
flex-grow: 1;
}
.progress-bar {
background-color: var(--purple-box);
height: 100%;
transition: width 0.3s ease-in-out;
}
.progress-text {
font-size: 0.85rem;
color: var(--secondary-text-color);
}
#scoreDisplay {
font-size: 1.5rem;
font-weight: bold;
margin-bottom: 10px;
display: none;
color: var(--text-color);
}
.question-card {
background: var(--card-bg);
padding: 15px;
border-radius: 8px;
box-shadow: var(--shadow);
margin-bottom: 20px;
transition: border 0.3s;
}
.question-card.answered {
border: 2px solid var(--answered-border);
}
.question-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 1rem;
}
.question-number {
background: var(--light-purple);
color: white;
width: 30px;
height: 30px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
\n font-size: 0.9rem;
font-weight: bold;
}
.chapter-box {
background: var(--blue-box);
color: white;
padding: 0.5rem 1rem;
border-radius: 8px;
font-size: 0.75rem;
}
.question-text {
font-size: 1rem;
font-weight: 500;
margin-bottom: 1rem;
}
.option-label {
display: flex;
align-items: center;
padding: 10px;
border: 1px solid var(--border-color);
border-radius: 8px;
margin-bottom: 10px;
cursor: pointer;
font-size: 0.9rem;
transition: background 0.3s;
}
.option-label:hover { background: var(--border-color); }
.option-prefix {
width: 30px;
height: 30px;
border-radius: 50%;
border: 1px solid var(--border-color);
display: flex;
align-items: center;
justify-content: center;
margin-right: 10px;
font-size: 0.9rem;
}
input[type="radio"] {
display: none;
}
input[type="radio"]:checked + .option-prefix {
background: var(--purple-box);
color: white;
border-color: var(--purple-box);
}
input[type="radio"]:disabled + .option-prefix {
cursor: not-allowed;
opacity: 0.6;
}
.correct { background-color: var(--correct-bg); border: 2px solid var(--correct-border); }
.incorrect { background-color: var(--incorrect-bg); border: 2px solid var(--incorrect-border); }
.solution-explanation {
margin-top: 10px;
padding: 10px;
background: var(--border-color);
border-radius: 8px;
border-left: 4px solid var(--purple-box);
font-size: 0.9rem;
min-height: 50px;
}
.solution-explanation.image-only {
height: 200px;
padding: 0;
}
.question-image,
.option-image,
.explanation-image {
height: auto;
border-radius: 8px;
box-shadow: var(--shadow);
}
.question-image {
width: 100%;
max-width: none;
margin: 5px 0;
}
.option-image {
height: 100%;
width: auto;
max-width: 120px;
margin-left: 5px;
object-fit: contain;
}
.explanation-image {
width: 100%;
height: 100%;
max-width: none;
max-height: none;
object-fit: contain;
margin: 0;
}
.stats-container {
margin-top: 10px;
padding: 15px;
background: var(--card-bg);
border-radius: 8px;
box-shadow: var(--shadow);
display: none;
}
.chart-container {
margin-top: 20px;
max-width: 400px;
margin-left: auto;
margin-right: auto;
display: none;
padding: 1.5rem;
background: var(--card-bg);
border-radius: 8px;
box-shadow: var(--shadow);
}
.btn-primary {
background-color: var(--orange-btn);
border-color: var(--orange-btn);
color: #fff;
padding: 0.5rem 1rem;
border-radius: 8px;
transition: background-color 0.3s;
}
.btn-primary:hover {
background-color: #EA580C;
border-color: #EA580C;
}
.btn-leaderboard {
background-color: var(--purple-box);
border-color: var(--purple-box);
color: #fff;
padding: 0.5rem 1rem;
border-radius: 8px;
transition: background-color 0.3s;
}
.btn-leaderboard:hover {
background-color: #7C3AED;
border-color: #7C3AED;
}
.loading-spinner {
display: none;
text-align: center;
padding: 20px;
color: var(--purple-box);
}
.expired-message {
text-align: center;
color: #dc3545;
font-size: 1.5rem;
padding: 20px;
display: none;
}
.leaderboard-container {
margin-top: 20px;
padding: 15px;
background: var(--card-bg);
border-radius: 8px;
box-shadow: var(--shadow);
display: none;
}
.leaderboard-table {
width: 100%;
border-collapse: collapse;
}
.leaderboard-table th,
.leaderboard-table td {
padding: 10px;
text-align: left;
border-bottom: 1px solid var(--border-color);
}
.leaderboard-table th {
font-weight: bold;
color: var(--purple-box);
}
.leaderboard-table tr:hover {
background-color: var(--border-color);
}
.question-nav {
position: fixed;
bottom: 10px;
right: 10px;
max-height: 70vh;
overflow-y: auto;
background: var(--card-bg);
padding: 10px;
border-radius: 8px;
box-shadow: var(--shadow);
z-index: 100;
display: flex;
flex-direction: column;
gap: 5px;
}
.nav-item {
background: var(--border-color);
color: var(--text-color);
border: none;
padding: 5px 10px;
border-radius: 5px;
cursor: pointer;
transition: background 0.3s;
font-size: 0.9rem;
}
.nav-item.answered {
background: var(--answered-border);
color: white;
}
.nav-item.correct {
background: var(--correct-border);
color: white;
}
.nav-item.incorrect {
background: var(--incorrect-border);
color: white;
}
.nav-item.skipped {
background: var(--secondary-text-color);
color: white;
}
@media (max-width: 768px) {
.navbar { padding: 0.5rem; }
.exam-title { font-size: 1.5rem; }
.subject-box, .questions-box, .timer-box { font-size: 0.85rem; padding: 0.3rem 0.8rem; }
.theme-toggle { font-size: 1.2rem; padding: 0.5rem; }
.sticky-bar { padding: 0.5rem; }
.sticky-bar .answered-count, .sticky-bar .timer { font-size: 0.9rem; }
.btn-next { font-size: 0.9rem; padding: 0.3rem 0.8rem; }
.question-text { font-size: 1rem; }
.option-label { font-size: 0.9rem; padding: 10px; }
.solution-explanation { font-size: 0.9rem; padding: 10px; }
.chart-container { max-width: 300px; }
.option-image { max-width: 100px; }
.question-nav {
bottom: 10px;
right: 10px;
left: 10px;
max-height: 100px;
overflow-x: auto;
white-space: nowrap;
flex-direction: row;
}
.nav-item {
display: inline-block;
margin: 0 5px;
}
}
@media (min-width: 769px) {
.navbar { padding: 1rem; }
.exam-title { font-size: 2rem; }
.subject-box, .questions-box, .timer-box { font-size: 1rem; padding: 0.5rem 1rem; }
.theme-toggle { font-size: 1.5rem; padding: 0.5rem; }
.sticky-bar { padding: 0.8rem 1rem; }
.sticky-bar .answered-count, .sticky-bar .timer { font-size: 1.1rem; }
.btn-next { font-size: 1rem; padding: 0.5rem 1rem; }
.question-text { font-size: 1.2rem; }
.option-label { font-size: 1rem; padding: 12px; }
.solution-explanation { font-size: 1rem; padding: 12px; }
.question-number { width: 40px; height: 40px; font-size: 1rem; }
.option-prefix { width: 40px; height: 40px; font-size: 1rem; }
}
</style>
<script src="https://www.gstatic.com/firebasejs/9.22.0/firebase-app-compat.js"></script>
<script src="https://www.gstatic.com/firebasejs/9.22.0/firebase-auth-compat.js"></script>
<script src="https://www.gstatic.com/firebasejs/9.22.0/firebase-firestore-compat.js"></script>
<script src="https://cdn.jsdelivr.net/npm/toastify-js"></script>
</head>
<body>
<div class="container">
<nav class="navbar">
<div class="nav-left">
<div class="exam-title" id="examNameDisplay">ডোপামিন এমসিকিউ</div>
<div class="d-flex flex-wrap gap-2">
<span class="subject-box" id="subjectDisplay">লোড হচ্ছে...</span>
<span class="questions-box" id="questionsCount">০ প্রশ্ন</span>
<span class="timer-box" id="timer">০০ মিনিট</span>
</div>
</div>
<div class="nav-right">
<button class="theme-toggle" onclick="toggleTheme()">
<i class="fas fa-moon" id="themeIcon"></i>
</button>
</div>
</nav>
<div class="sticky-bar" id="stickyBar">
<span class="answered-count" id="answeredCount">০/০</span>
<span class="timer" id="stickyTimer"><i class="fas fa-clock"></i> ৫৯:০০ বাকি</span>
<div class="action-buttons">
<button class="btn-next" id="submitBtnSticky">জমা দিন</button>
</div>
</div>
<div id="questionNav" class="question-nav"></div>
<div class="header">
<div class="progress-container">
<span class="progress-text">অগ্রগতি</span>
<div class="progress">
<div id="progressBar" class="progress-bar" role="progressbar" style="width: 0%;" aria-valuenow="0" aria-valuemin="0" aria-valuemax="100"></div>
</div>
<span class="progress-text" id="progressPercentage">০%</span>
</div>
<div id="scoreDisplay"></div>
<div id="statsContainer" class="stats-container"></div>
<div class="chart-container" id="chartContainer">
<canvas id="resultChart"></canvas>
</div>
</div>
<div id="loading" class="loading-spinner">পরীক্ষা লোড হচ্ছে...</div>
<div id="expiredMessage" class="expired-message"></div>
<form id="examForm"></form>
<div id="backToDashboardContainer" class="text-center mt-3" style="display: none;">
<button type="button" id="backToDashboardBtn" class="btn btn-primary">ড্যাশবোর্ডে ফিরে যান</button>
<button type="button" id="leaderboardBtn" class="btn btn-leaderboard ms-2">লিডারবোর্ড দেখুন</button>
</div>
<div id="leaderboardContainer" class="leaderboard-container">
<h3 class="text-center mb-3">লিডারবোর্ড</h3>
<table class="leaderboard-table">
<thead>
<tr>
<th>র্যাঙ্ক</th>
<th>নাম</th>
<th>স্কোর</th>
<th>সঠিক</th>
<th>ভুল</th>
<th>এড়িয়ে যাওয়া</th>
</tr>
</thead>
<tbody id="leaderboardBody"></tbody>
</table>
</div>
</div>
<script>
const firebaseConfig = {
apiKey: "AIzaSyBXXaWWoFqn6MpH6IWSm6CGaqUJzAmzbzA",
authDomain: "dopamine-quiz.firebaseapp.com",
projectId: "dopamine-quiz",
storageBucket: "dopamine-quiz.appspot.com",
messagingSenderId: "822531459966",
appId: "1:822531459966:web:8e7d2385090e997eb1c12f"
};
firebase.initializeApp(firebaseConfig);
const auth = firebase.auth();
const db = firebase.firestore();
auth.onAuthStateChanged((user) => {
if (!user) window.location.href = "auth.html";
});
let questions = [];
let examData = {};
let userAnswers = {};
let timerInterval;
let submitted = false;
let currentQuestionIndex = 0;
const urlParams = new URLSearchParams(window.location.search);
const examId = urlParams.get('examId');
const answersKey = "examAnswers_" + examId;
const startTimeKey = "examStartTime_" + examId;
const submittedFlagKey = "examSubmitted_" + examId;
let visibility = { correct: true, incorrect: true, skipped: true };
let chartInstance = null;
function sanitizeFileName(name) {
return name.replace(/\s+/g, '-').replace(/[^a-zA-Z0-9\u0980-\u09FF-]/g, '');
}
async function loadQuestionsFromJSON(subject, chapter) {
const sanitizedSubject = sanitizeFileName(subject);
const sanitizedChapter = sanitizeFileName(chapter.replace(/^Chapter \d+:\s*/, ''));
const filePath = `./questions/subjects/${sanitizedSubject}/${sanitizedChapter}.json`;
console.log(`Attempting to load: ${filePath}`);
try {
const response = await fetch(filePath);
if (!response.ok) {
console.error(`Fetch failed for ${filePath}: ${response.status} - ${response.statusText}`);
throw new Error(`HTTP error! Status: ${response.status}`);
}
const data = await response.json();
data.forEach((q, idx) => {
if (!q.options || q.options.length === 0 || !q.correctOption) {
console.warn(`Invalid question at index ${idx} in ${filePath}: Missing options or correctOption`);
throw new Error(`Invalid question at index ${idx}`);
}
if (q.optionImages && q.optionImages.length !== q.options.length) {
console.warn(`optionImages length mismatch for question ${idx} in ${filePath}`);
q.optionImages = q.options.map(() => null);
}
if (!q.question && !q.questionImage) {
console.warn(`Question at index ${idx} in ${filePath} has no text or image`);
q.question = `প্রশ্ন ${idx + 1}`;
}
});
console.log(`Loaded ${data.length} questions from ${filePath}`);
return data;
} catch (error) {
console.error(`Error loading ${filePath}:`, error);
Toastify({ text: `${subject} - ${chapter} প্রশ্ন লোড করতে ব্যর্থ: ${error.message}`, duration: 3000, style: { background: "#dc3545" } }).showToast();
return [];
}
}
async function loadExam() {
const loadingDiv = document.getElementById("loading");
loadingDiv.style.display = "block";
try {
if (!examId) throw new Error("Exam ID not found in URL");
const examDoc = await db.collection("exams").doc(examId).get();
if (!examDoc.exists) throw new Error("Exam not found");
examData = examDoc.data();
examData.id = examDoc.id;
const attemptSnapshot = await db.collection("attempts")
.where("userId", "==", auth.currentUser.uid)
.where("examId", "==", examId)
.get();
if (!attemptSnapshot.empty) {
const resultDoc = attemptSnapshot.docs[0].data();
await loadQuestionsForReview();
displayReviewMode(questions, resultDoc);
return;
}
if (isExamExpired(examData.lastDate)) {
document.getElementById("expiredMessage").style.display = "block";
document.getElementById("expiredMessage").textContent = "এই পরীক্ষার সময় শেষ হয়ে গেছে। আপনি অংশ নিতে পারবেন না।";
document.getElementById("examForm").style.display = "none";
document.getElementById("stickyBar").style.display = "none";
loadingDiv.style.display = "none";
return;
}
questions = [];
for (const q of examData.questions) {
const chapterQuestions = await loadQuestionsFromJSON(q.subject, q.chapter);
if (chapterQuestions[q.id]) {
questions.push({
...chapterQuestions[q.id],
id: `${q.subject}-${q.chapter}-${q.id}`,
subject: q.subject,
chapter: q.chapter.replace(/^Chapter \d+:\s*/, '')
});
}
}
if (questions.length === 0) throw new Error("No questions found for this exam");
userAnswers = JSON.parse(localStorage.getItem(answersKey)) || {};
const subjects = [...new Set(questions.map(q => q.subject))].join(", ");
document.getElementById("subjectDisplay").textContent = subjects || "অজানা বিষয়";
document.getElementById("questionsCount").textContent = `${questions.length} প্রশ্ন`;
document.getElementById("timer").textContent = `${examData.duration} মিনিট`;
document.getElementById("answeredCount").textContent = `০/${questions.length}`;
updateProgress();
renderQuiz(questions);
} catch (error) {
console.error("Error loading exam:", error);
Toastify({
text: "পরীক্ষা লোড করতে ত্রুটি: " + error.message,
duration: 3000,
style: { background: "#dc3545" }
}).showToast();
setTimeout(() => {
window.location.href = "https://dopamine-edu.github.io/dashboard.html";
}, 3000);
} finally {
loadingDiv.style.display = "none";
}
}
async function loadQuestionsForReview() {
questions = [];
const examDoc = await db.collection("exams").doc(examId).get();
examData = examDoc.data();
examData.id = examDoc.id;
for (const q of examData.questions) {
const chapterQuestions = await loadQuestionsFromJSON(q.subject, q.chapter);
if (chapterQuestions[q.id]) {
questions.push({
...chapterQuestions[q.id],
id: `${q.subject}-${q.chapter}-${q.id}`,
subject: q.subject,
chapter: q.chapter.replace(/^Chapter \d+:\s*/, '')
});
}
}
}
function isExamExpired(lastDate) {
if (!lastDate) return false;
const examDeadline = new Date(lastDate);
return new Date() > examDeadline;
}
function updateProgress() {
const totalQuestions = questions.length;
const answeredQuestions = Object.keys(userAnswers).filter(key => !key.includes("_locked")).length;
const progressPercentage = (answeredQuestions / totalQuestions) * 100;
document.getElementById("progressBar").style.width = `${progressPercentage}%`;
document.getElementById("progressBar").setAttribute("aria-valuenow", progressPercentage);
document.getElementById("progressPercentage").textContent = `${Math.round(progressPercentage)}%`;
document.getElementById("answeredCount").textContent = `${answeredQuestions}/${totalQuestions}`;
}
function renderQuiz(questions, isReview = false, previousAnswers = {}) {
const examForm = document.getElementById("examForm");
examForm.innerHTML = "";
questions.forEach((q, idx) => {
const questionDiv = document.createElement("div");
questionDiv.className = "question-card";
questionDiv.dataset.answer = q.correctOption;
questionDiv.dataset.index = idx;
if (userAnswers["q" + idx] && userAnswers["q" + idx + "_locked"]) {
questionDiv.classList.add("answered");
}
const questionHeader = `
<div class="question-header">
<div class="d-flex align-items-center gap-2">
<span class="question-number">${idx + 1}</span>
<span class="chapter-box">${q.chapter}</span>
</div>
</div>
`;
let questionHTML = `
${questionHeader}
<p class="question-text">${q.question || ''}</p>
`;
if (q.questionImage) {
questionHTML += `<img src="${q.questionImage}" class="question-image" alt="Question ${idx + 1} diagram" loading="lazy" onerror="this.style.display='none'">`;
}
questionDiv.innerHTML = questionHTML;
q.options.forEach((opt, optIdx) => {
const label = document.createElement("label");
label.className = "option-label";
const radioInput = document.createElement("input");
radioInput.type = "radio";
radioInput.name = "q" + idx;
radioInput.value = String(optIdx + 1);
radioInput.setAttribute("aria-label", `Option ${optIdx + 1} for question ${idx + 1}`);
if (isReview) {
radioInput.disabled = true;
if (previousAnswers["q" + idx] === radioInput.value) {
radioInput.checked = true;
if (radioInput.value === q.correctOption) {
label.classList.add("correct");
} else {
label.classList.add("incorrect");
}
} else if (radioInput.value === q.correctOption) {
label.classList.add("correct");
}
} else {
if (userAnswers["q" + idx] && userAnswers["q" + idx + "_locked"]) {
if (userAnswers["q" + idx] === radioInput.value) {
radioInput.checked = true;
}
radioInput.disabled = true;
}
radioInput.addEventListener("change", () => {
if (!userAnswers["q" + idx + "_locked"]) {
userAnswers["q" + idx] = radioInput.value;
userAnswers["q" + idx + "_locked"] = true;
const radios = document.getElementsByName("q" + idx);
Array.from(radios).forEach(radio => radio.disabled = true);
radioInput.checked = true;
localStorage.setItem(answersKey, JSON.stringify(userAnswers));
updateProgress();
const questionCard = radioInput.closest(".question-card");
questionCard.classList.add("answered");
const index = questionCard.dataset.index;
const navItem = document.querySelector(`.nav-item[data-index="${index}"]`);
if (navItem) navItem.classList.add("answered");
}
});
}
const optionPrefix = document.createElement("span");
optionPrefix.className = "option-prefix";
optionPrefix.textContent = String.fromCharCode(2453 + optIdx);
label.appendChild(radioInput);
label.appendChild(optionPrefix);
if (opt?.trim()) {
label.appendChild(document.createTextNode(" " + opt));
}
if (q.optionImages && q.optionImages[optIdx]) {
const optionImg = document.createElement("img");
optionImg.src = q.optionImages[optIdx];
optionImg.className = "option-image";
optionImg.alt = `Option ${optIdx + 1} diagram`;
optionImg.loading = "lazy";
optionImg.onerror = () => (optionImg.style.display = "none");
label.appendChild(optionImg);
}
questionDiv.appendChild(label);
});
if (isReview) {
const explanationDiv = document.createElement("div");
explanationDiv.className = "solution-explanation";
const hasExplanationText = q.explanation?.trim();
const hasExplanationImage = q.explanationImage;
if (hasExplanationText) {
explanationDiv.innerHTML = `<strong>ব্যাখ্যা:</strong> ${q.explanation}`;
} else if (hasExplanationImage) {
explanationDiv.classList.add("image-only");
explanationDiv.innerHTML = "";
} else {
explanationDiv.innerHTML = `<strong>ব্যাখ্যা:</strong> ব্যাখ্যা উপলব্ধ নয়`;
}
if (hasExplanationImage) {
const explanationImg = document.createElement("img");
explanationImg.src = q.explanationImage;
explanationImg.className = "explanation-image";
explanationImg.alt = `Explanation diagram for question ${idx + 1}`;
explanationImg.loading = "lazy";
explanationImg.onerror = () => (explanationImg.style.display = "none");
explanationDiv.appendChild(explanationImg);
}
questionDiv.appendChild(explanationDiv);
}
examForm.appendChild(questionDiv);
});
renderNavigation();
if (!isReview && !timerInterval) startTimer();
}
function renderNavigation() {
const navDiv = document.getElementById("questionNav");
navDiv.innerHTML = "";
questions.forEach((q, idx) => {
const navItem = document.createElement("button");
navItem.textContent = idx + 1;
navItem.className = "nav-item";
navItem.dataset.index = idx;
if (userAnswers["q" + idx] && userAnswers["q" + idx + "_locked"]) {
navItem.classList.add("answered");
}
navItem.addEventListener("click", () => {
document.querySelector(`.question-card[data-index="${idx}"]`).scrollIntoView({ behavior: "smooth", block: "center" });
});
navDiv.appendChild(navItem);
});
}
function startTimer() {
const timerElement = document.getElementById("timer");
const stickyTimerElement = document.getElementById("stickyTimer");
let storedStart = parseInt(localStorage.getItem(startTimeKey)) || Date.now();
localStorage.setItem(startTimeKey, storedStart);
const examDurationMs = examData.duration * 60 * 1000;
function updateTimer() {
const elapsed = Date.now() - storedStart;
let remainingMs = examDurationMs - elapsed;
if (remainingMs <= 0) {
clearInterval(timerInterval);
if (!submitted) submitQuiz();
} else {
const minutes = Math.floor(remainingMs / 60000);
const seconds = Math.floor((remainingMs % 60000) / 1000);
timerElement.textContent = `${minutes} মিনিট`;
stickyTimerElement.innerHTML = `<i class="fas fa-clock"></i> ${minutes}:${seconds.toString().padStart(2, "0")} বাকি`;
}
}
updateTimer();
timerInterval = setInterval(updateTimer, 1000);
}
document.getElementById("submitBtnSticky").addEventListener("click", () => {
if (confirm("আপনি কি আপনার উত্তর জমা দিতে নিশ্চিত?")) submitQuiz();
});
function createGradient(ctx, colorStart, colorEnd) {
const gradient = ctx.createLinearGradient(0, 0, 0, 400);
gradient.addColorStop(0, colorStart);
gradient.addColorStop(1, colorEnd);
return gradient;
}
function updateVisibility() {
const types = ['correct', 'incorrect', 'skipped'];
types.forEach(type => {
const elements = document.querySelectorAll(`.question-card[data-type="${type}"], .nav-item.${type}`);
elements.forEach(el => {
el.style.display = visibility[type] ? '' : 'none';
});
});
updateChart();
}
function updateChart() {
if (chartInstance) {
const data = chartInstance.data.datasets[0].data;
const newData = [
visibility.correct ? data[0] : 0,
visibility.incorrect ? data[1] : 0,
visibility.skipped ? data[2] : 0
];
chartInstance.data.datasets[0].data = newData;
chartInstance.update();
}
}
function createPieChart(correct, incorrect, skipped) {
const ctx = document.getElementById('resultChart').getContext('2d');
const total = correct + incorrect + skipped;
const gradientCorrect = createGradient(ctx, "#34C759", "#28A745");
const gradientIncorrect = createGradient(ctx, "#FF3B30", "#D32F2F");
const gradientSkipped = createGradient(ctx, "#FFD700", "#FFA500");
chartInstance = new Chart(ctx, {
type: 'pie',
data: {
labels: ['সঠিক উত্তর', 'ভুল উত্তর', 'এড়িয়ে যাওয়া'],
datasets: [{
data: [correct, incorrect, skipped],
backgroundColor: [gradientCorrect, gradientIncorrect, gradientSkipped],
borderColor: '#fff',
borderWidth: 2,
shadowOffsetX: 3,
shadowOffsetY: 3,
shadowBlur: 10,
shadowColor: 'rgba(0, 0, 0, 0.2)'
}]
},
options: {
responsive: true,
maintainAspectRatio: true,
animation: {
animateScale: true,
animateRotate: true,
duration: 1500,
easing: 'easeOutQuart'
},
plugins: {
legend: {
position: 'bottom',
labels: {
font: { size: 14, family: "'Hind Siliguri', sans-serif" },
padding: 20,
usePointStyle: true,
pointStyle: 'circle',
color: getComputedStyle(document.documentElement).getPropertyValue('--text-color').trim()
},
onClick: (e, legendItem, legend) => {
const index = legendItem.index;
const type = ['correct', 'incorrect', 'skipped'][index];
visibility[type] = !visibility[type];
legendItem.text = visibility[type]
? ['সঠিক উত্তর', 'ভুল উত্তর', 'এড়িয়ে যাওয়া'][index]
: `<s>${['সঠিক উত্তর', 'ভুল উত্তর', 'এড়িয়ে যাওয়া'][index]}</s>`;
updateVisibility();
}
},
tooltip: {
enabled: true,
backgroundColor: 'rgba(0, 0, 0, 0.8)',
titleFont: { size: 14 },
bodyFont: { size: 12 },
padding: 10,
cornerRadius: 8
},
datalabels: {
formatter: (value, context) => {
const percentage = ((value / total) * 100).toFixed(1);
return `${percentage}%`;
},
color: '#fff',
font: { size: 14, weight: 'bold' },
textShadow: '0 0 5px rgba(0, 0, 0, 0.5)'
}
},
layout: { padding: 20 }
},
plugins: [ChartDataLabels]
});
document.getElementById("chartContainer").style.display = "block";
}
async function showLeaderboard() {
const leaderboardContainer = document.getElementById("leaderboardContainer");
const leaderboardBody = document.getElementById("leaderboardBody");
leaderboardBody.innerHTML = '<tr><td colspan="6" class="text-center">লোড হচ্ছে...</td></tr>';
try {
const attemptsSnapshot = await db.collection("attempts")
.where("examId", "==", examId)
.orderBy("score", "desc")
.orderBy("timestamp", "asc")
.limit(10)
.get();
if (attemptsSnapshot.empty) {
leaderboardBody.innerHTML = '<tr><td colspan="6" class="text-center">কোনো ফলাফল পাওয়া যায়নি।</td></tr>';
leaderboardContainer.style.display = "block";
return;
}
const userIds = [...new Set(attemptsSnapshot.docs.map(doc => doc.data().userId))];
const usersSnapshot = await db.collection("users")
.where(firebase.firestore.FieldPath.documentId(), "in", userIds)
.get();
const userMap = {};
usersSnapshot.forEach(doc => {
userMap[doc.id] = doc.data().fullName || "অজানা ব্যবহারকারী";
});
leaderboardBody.innerHTML = "";
attemptsSnapshot.forEach((doc, index) => {
const data = doc.data();
const row = document.createElement("tr");
row.innerHTML = `
<td>${index + 1}</td>
<td>${userMap[data.userId] || "অজানা ব্যবহারকারী"}</td>
<td>${data.score}</td>
<td>${data.correctAnswers}</td>
<td>${data.incorrectAnswers}</td>
<td>${data.skippedAnswers}</td>
`;
leaderboardBody.appendChild(row);
});
leaderboardContainer.style.display = "block";
leaderboardContainer.scrollIntoView({ behavior: "smooth" });
} catch (error) {
console.error("Error loading leaderboard:", error);
Toastify({
text: "লিডারবোর্ড লোড করতে ত্রুটি: " + error.message,
duration: 3000,
style: { background: "#dc3545" }
}).showToast();
leaderboardBody.innerHTML = '<tr><td colspan="6" class="text-center">লিডারবোর্ড লোড করতে ব্যর্থ।</td></tr>';
leaderboardContainer.style.display = "block";
}
}
async function submitQuiz() {
if (submitted) return;
submitted = true;
clearInterval(timerInterval);
let score = 0;
let correctAnswers = 0;
let incorrectAnswers = 0;
let skippedAnswers = 0;
const questionDivs = document.querySelectorAll(".question-card");
const totalQuestions = questionDivs.length;
questionDivs.forEach((qDiv, idx) => {
const correctAnswer = qDiv.dataset.answer;
const selectedOption = qDiv.querySelector("input[type='radio']:checked");
if (selectedOption) {
if (selectedOption.value === correctAnswer) {
score += examData.marksPerQuestion;
correctAnswers++;
selectedOption.parentElement.classList.add("correct");
qDiv.dataset.type = 'correct';
document.querySelector(`.nav-item[data-index="${idx}"]`).classList.add('correct');
} else {
incorrectAnswers++;
score -= examData.negativeMark || 0;
selectedOption.parentElement.classList.add("incorrect");
qDiv.dataset.type = 'incorrect';
document.querySelector(`.nav-item[data-index="${idx}"]`).classList.add('incorrect');
}
} else {
skippedAnswers++;
qDiv.dataset.type = 'skipped';
document.querySelector(`.nav-item[data-index="${idx}"]`).classList.add('skipped');
}
qDiv.querySelectorAll(".option-label").forEach(label => {
if (label.querySelector("input").value === correctAnswer) {
label.classList.add("correct");
}
});
const explanation = questions[idx].explanation;
const explanationImage = questions[idx].explanationImage;
const explanationDiv = document.createElement("div");
explanationDiv.className = "solution-explanation";
const hasExplanationText = explanation?.trim();
const hasExplanationImage = explanationImage;
if (hasExplanationText) {
explanationDiv.innerHTML = `<strong>ব্যাখ্যা:</strong> ${explanation}`;
} else if (hasExplanationImage) {
explanationDiv.classList.add("image-only");
explanationDiv.innerHTML = "";
} else {
explanationDiv.innerHTML = `<strong>ব্যাখ্যা:</strong> ব্যাখ্যা উপলব্ধ নয়`;
}
if (hasExplanationImage) {
const explanationImg = document.createElement("img");
explanationImg.src = explanationImage;
explanationImg.className = "explanation-image";
explanationImg.alt = `Explanation diagram for question ${idx + 1}`;
explanationImg.loading = "lazy";
explanationImg.onerror = () => (explanationImg.style.display = "none");
explanationDiv.appendChild(explanationImg);
}
qDiv.appendChild(explanationDiv);
});
document.getElementById("scoreDisplay").style.display = "block";
document.getElementById("scoreDisplay").innerHTML = `আপনার স্কোর: ${score} / ${totalQuestions * examData.marksPerQuestion}`;
document.getElementById("statsContainer").style.display = "block";
document.getElementById("statsContainer").innerHTML = `
<p class="text-success fw-bold">সঠিক উত্তর: ${correctAnswers}</p>
<p class="text-danger fw-bold">ভুল উত্তর: ${incorrectAnswers}</p>
<p class="text-secondary fw-bold">এড়িয়ে যাওয়া: ${skippedAnswers}</p>
`;
createPieChart(correctAnswers, incorrectAnswers, skippedAnswers);
document.getElementById("stickyBar").style.display = "none";
document.getElementById("backToDashboardContainer").style.display = "block";
localStorage.removeItem(startTimeKey);
localStorage.removeItem(answersKey);
localStorage.setItem(submittedFlagKey, "true");
try {
await db.collection("attempts").add({
userId: auth.currentUser.uid,
examId: examData.id,
score: score,
correctAnswers: correctAnswers,
incorrectAnswers: incorrectAnswers,
skippedAnswers: skippedAnswers,
totalQuestions: questions.length,
answers: userAnswers,
timestamp: firebase.firestore.FieldValue.serverTimestamp()
});
Toastify({
text: `পরীক্ষা সম্পন্ন! আপনার স্কোর: ${score}`,
duration: 5000,
style: { background: "#9810fa" }
}).showToast();
} catch (error) {
console.error("Error submitting quiz:", error);
Toastify({
text: "পরীক্ষা জমা দিতে ত্রুটি: " + error.message,
duration: 3000,
style: { background: "#dc3545" }
}).showToast();
}
window.scrollTo(0, 0);
}
function displayReviewMode(questions, resultData) {
document.getElementById("stickyBar").style.display = "none";
document.getElementById("questionNav").style.display = "none";
renderQuiz(questions, true, resultData.answers || {});
let correctAnswers = 0;
let incorrectAnswers = 0;
let skippedAnswers = 0;
questions.forEach((q, idx) => {
const questionDiv = document.querySelector(`.question-card[data-index="${idx}"]`);
const userAnswer = resultData.answers["q" + idx];
if (userAnswer) {
if (userAnswer === q.correctOption) {
questionDiv.dataset.type = 'correct';
correctAnswers++;
} else {
questionDiv.dataset.type = 'incorrect';
incorrectAnswers++;
}
} else {
questionDiv.dataset.type = 'skipped';
skippedAnswers++;
}
});
document.getElementById("scoreDisplay").style.display = "block";
document.getElementById("scoreDisplay").innerHTML = `আপনার স্কোর: ${resultData.score} / ${questions.length * examData.marksPerQuestion}`;
document.getElementById("statsContainer").style.display = "block";
document.getElementById("statsContainer").innerHTML = `
<p class="text-success fw-bold">সঠিক উত্তর: ${correctAnswers}</p>
<p class="text-danger fw-bold">ভুল উত্তর: ${incorrectAnswers}</p>
<p class="text-secondary fw-bold">এড়িয়ে যাওয়া: ${skippedAnswers}</p>
`;
createPieChart(correctAnswers, incorrectAnswers, skippedAnswers);
document.getElementById("backToDashboardContainer").style.display = "block";
}
document.getElementById("backToDashboardBtn").addEventListener("click", () => {
window.location.href = "https://dopamine-edu.github.io/dashboard.html";
});
document.getElementById("leaderboardBtn").addEventListener("click", showLeaderboard);
let isDark = localStorage.getItem('theme') === 'dark';
if (isDark) {
document.body.setAttribute('data-theme', 'dark');
document.getElementById('themeIcon').classList.replace('fa-moon', 'fa-sun');
}
function toggleTheme() {
isDark = !isDark;
document.body.setAttribute('data-theme', isDark ? 'dark' : 'light');
document.getElementById('themeIcon').classList.toggle('fa-moon');
document.getElementById('themeIcon').classList.toggle('fa-sun');
localStorage.setItem('theme', isDark ? 'dark' : 'light');
}
document.addEventListener("DOMContentLoaded", loadExam);
</script>
</body>
</html>
and this is auth.html
<!DOCTYPE html>
<html lang="bn">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>ডোপামিন - Auth</title>
<!-- Font Awesome CDN with integrity attribute -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css" integrity="sha512-iecdLmaskl7CVkqkXNQ/ZH/XLlvWZOJyj7Yy7tcenmpD1ypASozpmT/E0iPtmFIB46ZmdtAc9eNBvH0H/ZpiBw==" crossorigin="anonymous" referrerpolicy="no-referrer" />
<style>
@import url('https://fonts.googleapis.com/css?family=Hind+Siliguri');
* {
margin: 0;
padding: 0;
box-sizing: border-box;
font-family: 'Hind Siliguri', sans-serif;
}
body {
display: flex;
justify-content: center;
align-items: center;
min-height: 100vh;
background: #f5f5f5;
padding: 0.5rem;
}
.auth-wrapper {
width: 100%;
max-width: 400px;
text-align: center;
}
.logo-container {
margin-bottom: 1rem;
}
.logo-container img {
width: 60px;
height: 60px;
border-radius: 50%;
margin-bottom: 0.3rem;
}
.logo-container h1 {
font-size: 1.8rem;
color: #333;
font-weight: 700;
}
.auth-options {
display: flex;
justify-content: space-between;
background: #f0f0f0;
border-radius: 25px;
padding: 0.3rem;
margin-bottom: 0.5rem;
}
.auth-option {
flex: 1;
padding: 0.5rem 1rem;
border-radius: 20px;
cursor: pointer;
font-weight: 500;
color: #666;
transition: all 0.3s ease;
font-size: 1rem;
background: transparent;
border: none;
outline: none;
}
.auth-option.active {
background: #fff;
color: #6b48ff;
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1);
}
.auth-container {
background: #fff;
padding: 1rem;
border-radius: 15px;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
display: none;
}
.form-group {
position: relative;
margin-bottom: 0.8rem;
}
input {
width: 100%;
padding: 0.8rem 2.5rem 0.8rem 2.5rem;
border: 1px solid #ddd;
border-radius: 8px;
font-size: 1rem;
color: #333;
background: #f9f9f9;
}
input:focus {
border-color: #6b48ff;
outline: none;
background: #fff;
}
input::placeholder {
color: #999;
}
.form-group i.icon-left {
position: absolute;
left: 10px;
top: 50%;
transform: translateY(-50%);
color: #999;
font-size: 1rem;
}
.password-toggle {
position: absolute;
right: 10px;
top: 50%;
transform: translateY(-50%);
color: #999;
cursor: pointer;
font-size: 1rem;
width: 1rem;
height: 1rem;
display: flex;
align-items: center;
justify-content: center;
}
button {
width: 100%;
padding: 0.8rem;
background: #6b48ff;
color: white;
border: none;
border-radius: 8px;
cursor: pointer;
font-size: 1rem;
font-weight: 500;
transition: background 0.3s ease;
position: relative;
overflow: hidden;
}
button:hover {
background: #5a3de6;
}
button:focus,
a:focus {
outline: none;
box-shadow: none;
}
button.loading::after {
content: '';
position: absolute;
width: 20px;
height: 20px;
border: 2px solid #fff;
border-top: 2px solid transparent;
border-radius: 50%;
animation: spin 0.8s linear infinite;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
button.loading {
pointer-events: none;
color: transparent;
}
@keyframes spin {
0% { transform: translate(-50%, -50%) rotate(0deg); }
100% { transform: translate(-50%, -50%) rotate(360deg); }
}
.google-btn {
background: #fff;
color: #4285F4;
border: 1px solid #ddd;
margin-top: 0.8rem;
display: flex;
align-items: center;
justify-content: center;
gap: 0.5rem;
font-weight: 500;
}
.google-btn i {
font-size: 1rem;
}
.google-btn:hover {
background: #f0f0f0;
}
.divider {
position: relative;
text-align: center;
margin: 0.8rem 0;
color: #666;
font-size: 0.9rem;
}
.divider span {
background: #fff;
padding: 0 10px;
position: relative;
z-index: 1;
}
.divider::before,
.divider::after {
content: '';
position: absolute;
top: 50%;
width: 40%;
height: 1px;
background: #ddd;
}
.divider::before { left: 0; }
.divider::after { right: 0; }
.links {
text-align: center;
margin-top: 0.8rem;
color: #666;
font-size: 0.9rem;
}
a {
color: #6b48ff;
text-decoration: none;
font-weight: 500;
margin: 0 0.5rem;
}
a:hover { text-decoration: underline; }
/* Error Pop-up Styles */
.error-modal {
display: none;
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.5);
justify-content: center;
align-items: center;
z-index: 1000;
}
.error-modal-content {
background: #fff;
padding: 1.5rem;
border-radius: 10px;
text-align: center;
max-width: 300px;
width: 90%;
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.2);
}
.error-modal-content p {
color: #e53e3e;
font-size: 1rem;
margin-bottom: 1rem;
}
.error-modal-content button {
background: #6b48ff;
color: white;
padding: 0.5rem 1rem;
border: none;
border-radius: 5px;
cursor: pointer;
font-size: 0.9rem;
}
.error-modal-content button:hover {
background: #5a3de6;
}
/* Custom Radio Button Styles for Department and HSC Batch */
.options-group {
margin-bottom: 0.8rem;
}
.options-group label {
display: block;
font-size: 1rem;
color: #666;
margin-bottom: 0.5rem;
position: relative;
padding-left: 2rem;
}
.options-group label i {
position: absolute;
left: 0;
top: 50%;
transform: translateY(-50%);
color: #999;
font-size: 1rem;
}
.options-list {
display: flex;
flex-wrap: wrap;
gap: 0.5rem;
}
.options-list input[type="radio"] {
display: none;
}
.options-list label {
flex: 1;
padding: 0.8rem;
border: 1px solid #ddd;
border-radius: 8px;
background: #f9f9f9;
text-align: center;
cursor: pointer;
font-size: 1rem;
color: #333;
transition: all 0.3s ease;
}
.options-list input[type="radio"]:checked + label {
background: #6b48ff;
color: white;
border-color: #6b48ff;
}
.options-list label:hover {
background: #f0f0f0;
}
@media (max-width: 576px) {
.auth-wrapper { max-width: 100%; padding: 0 10px; }
.logo-container img { width: 50px; height: 50px; }
.logo-container h1 { font-size: 1.5rem; }
.auth-options { padding: 0.2rem; margin-bottom: 0.3rem; }
.auth-option { font-size: 0.9rem; padding: 0.4rem 0.8rem; }
.auth-container { padding: 0.8rem; }
input { font-size: 0.9rem; padding: 0.7rem 2.2rem 0.7rem 2.2rem; }
button { font-size: 0.9rem; padding: 0.7rem; }
.form-group i, .password-toggle { font-size: 0.9rem; }
.form-group { margin-bottom: 0.6rem; }
.google-btn { margin-top: 0.6rem; }
.divider { margin: 0.6rem 0; }
.links { margin-top: 0.6rem; }
.error-modal-content { padding: 1rem; }
.error-modal-content p { font-size: 0.9rem; }
.error-modal-content button { font-size: 0.8rem; padding: 0.4rem 0.8rem; }
.options-group label { font-size: 0.9rem; }
.options-list label { font-size: 0.9rem; padding: 0.7rem; }
}
</style>
</head>
<body>
<div class="auth-wrapper">
<div class="logo-container">
<img src="logo.png" alt="ডোপামিন Logo">
<h1>ডোপামিন</h1>
</div>
<div class="auth-options">
<button class="auth-option active" onclick="showLogin()">লগইন</button>
<button class="auth-option" onclick="showSignup()">রেজিষ্ট্রেশন</button>
</div>
<!-- Login Form -->
<div class="auth-container" id="login-container" style="display: block;">
<form id="login-form">
<div class="form-group">
<i class="fas fa-envelope icon-left"></i>
<input type="email" id="login-email" placeholder="ইমেইল" required>
</div>
<div class="form-group">
<i class="fas fa-lock icon-left"></i>
<i class="fas fa-eye password-toggle" onclick="togglePassword('login-password')"></i>
<input type="password" id="login-password" placeholder="পাসওয়ার্ড" required>
</div>
<button type="submit">লগইন করুন</button>
</form>
<div class="divider"><span>অথবা</span></div>
<button type="button" class="google-btn" onclick="signInWithGoogle()">
<i class="fab fa-google"></i> গুগল সাইন ইন করুন
</button>
<div class="links">
<a onclick="showForgotPassword()">পাসওয়ার্ড ভুলে গেছেন?</a>
<a id="resend-verification" style="display: none;" onclick="resendVerification()">পুনরায় ইমেইল পাঠান</a>
</div>
</div>
<!-- Signup Form -->
<div class="auth-container" id="signup-container">
<form id="signup-form">
<div class="form-group">
<i class="fas fa-envelope icon-left"></i>
<input type="email" id="signup-email" placeholder="ইমেইল" required>
</div>
<div class="form-group">
<i class="fas fa-lock icon-left"></i>
<i class="fas fa-eye password-toggle" onclick="togglePassword('signup-password')"></i>
<input type="password" id="signup-password" placeholder="পাসওয়ার্ড" required>
</div>
<div class="form-group">
<i class="fas fa-lock icon-left"></i>
<i class="fas fa-eye password-toggle" onclick="togglePassword('confirm-password')"></i>
<input type="password" id="confirm-password" placeholder="কনফার্ম পাসওয়ার্ড" required>
</div>
<button type="submit">রেজিষ্ট্রেশন করুন</button>
</form>
<div class="divider"><span>অথবা</span></div>
<button type="button" class="google-btn" onclick="signInWithGoogle()">
<i class="fab fa-google"></i> গুগল সাইন আপ করুন
</button>
</div>
<!-- Profile Completion Form -->
<div class="auth-container" id="profile-container">
<p style="text-align: center; color: #666; margin-bottom: 0.8rem;">অনুগ্রহ করে আপনার তথ্য দিন</p>
<form id="profile-form">
<div class="form-group">
<i class="fas fa-user icon-left"></i>
<input type="text" id="full-name" placeholder="নাম" required>
</div>
<div class="options-group">
<label><i class="fas fa-book"></i> বিভাগ</label>
<div class="options-list">
<input type="radio" id="dept-science" name="department" value="বিজ্ঞান" required>
<label for="dept-science">বিজ্ঞান</label>
<input type="radio" id="dept-humanities" name="department" value="মানবিক">
<label for="dept-humanities">মানবিক</label>
<input type="radio" id="dept-business" name="department" value="ব্যবসা">
<label for="dept-business">ব্যবসা</label>
</div>
</div>
<div class="options-group">
<label><i class="fas fa-calendar-alt"></i> HSC ব্যাচ</label>
<div class="options-list">
<input type="radio" id="hsc-2024" name="hsc-year" value="2024" required>
<label for="hsc-2024">2024</label>
<input type="radio" id="hsc-2025" name="hsc-year" value="2025">
<label for="hsc-2025">2025</label>
<input type="radio" id="hsc-2026" name="hsc-year" value="2026">
<label for="hsc-2026">2026</label>
</div>
</div>
<div class="form-group">
<i class="fas fa-phone icon-left"></i>
<input type="tel" id="phone" placeholder="মোবাইল নং" required>
</div>
<button type="submit">সম্পন্ন করুন</button>
</form>
</div>
</div>
<!-- Error Pop-up Modal -->
<div class="error-modal" id="error-modal">
<div class="error-modal-content">
<p id="error-message"></p>
<button onclick="closeErrorModal()">ঠিক আছে</button>
</div>
</div>
<script src="https://www.gstatic.com/firebasejs/9.6.10/firebase-app-compat.js"></script>
<script src="https://www.gstatic.com/firebasejs/9.6.10/firebase-auth-compat.js"></script>
<script src="https://www.gstatic.com/firebasejs/9.6.10/firebase-firestore-compat.js"></script>
<script>
const firebaseConfig = {
apiKey: "AIzaSyBXXaWWoFqn6MpH6IWSm6CGaqUJzAmzbzA",
authDomain: "dopamine-quiz.firebaseapp.com",
projectId: "dopamine-quiz",
storageBucket: "dopamine-quiz.appspot.com",
messagingSenderId: "822531459966",
appId: "1:822531459966:web:8e7d2385090e997eb1c12f"
};
const app = firebase.initializeApp(firebaseConfig);
const auth = firebase.auth();
const db = firebase.firestore();
function showLogin() {
document.getElementById('login-container').style.display = 'block';
document.getElementById('signup-container').style.display = 'none';
document.getElementById('profile-container').style.display = 'none';
document.querySelectorAll('.auth-option').forEach(option => option.classList.remove('active'));
document.querySelectorAll('.auth-option')[0].classList.add('active');
}
function showSignup() {
document.getElementById('login-container').style.display = 'none';
document.getElementById('signup-container').style.display = 'block';
document.getElementById('profile-container').style.display = 'none';
document.querySelectorAll('.auth-option').forEach(option => option.classList.remove('active'));
document.querySelectorAll('.auth-option')[1].classList.add('active');
}
function showProfile() {
document.getElementById('login-container').style.display = 'none';
document.getElementById('signup-container').style.display = 'none';
document.getElementById('profile-container').style.display = 'block';
}
function togglePassword(inputId) {
const input = document.getElementById(inputId);
const icon = input.parentElement.querySelector('.password-toggle');
if (input.type === 'password') {
input.type = 'text';
icon.classList.remove('fa-eye');
icon.classList.add('fa-eye-slash');
} else {
input.type = 'password';
icon.classList.remove('fa-eye-slash');
icon.classList.add('fa-eye');
}
}
document.getElementById('signup-form').addEventListener('submit', async (e) => {
e.preventDefault();
const button = e.target.querySelector('button');
button.classList.add('loading');
const email = document.getElementById('signup-email').value;
const password = document.getElementById('signup-password').value;
const confirmPassword = document.getElementById('confirm-password').value;
if (password !== confirmPassword) {
showError('Passwords do not match', 'পাসওয়ার্ড সঠিক নয়');
button.classList.remove('loading');
return;
}
let createdUser;
try {
const userCredential = await auth.createUserWithEmailAndPassword(email, password);
createdUser = userCredential.user;
await db.collection('users').doc(createdUser.uid).set({
email: email,
status: 'unverified',
profileComplete: false,
createdAt: firebase.firestore.FieldValue.serverTimestamp()
});
await createdUser.sendEmailVerification();
alert('Verification email sent. Please check your inbox.');
showLogin();
} catch (error) {
if (error.code === 'auth/email-already-in-use') {
showError('Email already exists', 'এই ইমেইলটি ইতোমধ্যে ব্যবহৃত হয়েছে');
} else if (error.code === 'auth/invalid-email') {
showError('Email Incorrect', 'ইমেইলটি সঠিক নয়');
} else {
showError(error.message, error.message);
}
} finally {
button.classList.remove('loading');
}
});
document.getElementById('login-form').addEventListener('submit', async (e) => {
e.preventDefault();
const button = e.target.querySelector('button');
button.classList.add('loading');
const email = document.getElementById('login-email').value;
const password = document.getElementById('login-password').value;
try {
const userCredential = await auth.signInWithEmailAndPassword(email, password);
const user = userCredential.user;
if (user.emailVerified) {
const doc = await db.collection('users').doc(user.uid).get();
if (doc.exists && doc.data().profileComplete) {
window.location.href = '/dashboard.html';
} else {
showProfile();
}
} else {
await auth.signOut();
showError('Please verify your email first', 'Please verify your email first');
document.getElementById('resend-verification').style.display = 'inline';
}
} catch (error) {
if (error.code === 'auth/invalid-login-credentials' ||
error.code === 'auth/wrong-password' ||
error.code === 'auth/invalid-email' ||
error.code === 'auth/user-not-found') {
showError('Invalid credentials', 'ইমেইল অথবা পাসওয়ার্ডটি সঠিক নয়');
} else {
showError(error.message, error.message);
}
} finally {
button.classList.remove('loading');
}
});
function signInWithGoogle() {
const provider = new firebase.auth.GoogleAuthProvider();
auth.signInWithPopup(provider)
.then((result) => {
const user = result.user;
db.collection('users').doc(user.uid).get()
.then((doc) => {
if (!doc.exists) {
return db.collection('users').doc(user.uid).set({
fullName: user.displayName || '',
email: user.email,
status: 'active',
profileComplete: false,
createdAt: firebase.firestore.FieldValue.serverTimestamp()
});
}
})
.then(() => {
return db.collection('users').doc(user.uid).get();
})
.then((doc) => {
if (doc.data().profileComplete) {
window.location.href = '/dashboard.html';
} else {
showProfile();
}
})
.catch(error => {
showError(error.message, error.message);
});
})
.catch(error => {
showError(error.message, error.message);
});
}
document.getElementById('profile-form').addEventListener('submit', async (e) => {
e.preventDefault();
const button = e.target.querySelector('button');
button.classList.add('loading');
const fullName = document.getElementById('full-name').value;
const department = document.querySelector('input[name="department"]:checked')?.value;
const hscYear = document.querySelector('input[name="hsc-year"]:checked')?.value;
const phone = document.getElementById('phone').value;
if (!department || !hscYear) {
showError('Please select all required fields', 'অনুগ্রহ করে সব প্রয়োজনীয় তথ্য নির্বাচন করুন');
button.classList.remove('loading');
return;
}
const user = auth.currentUser;
if (user) {
try {
await db.collection('users').doc(user.uid).update({
fullName: fullName,
department: department,
hscYear: hscYear,
phone: phone,
profileComplete: true
});
window.location.href = '/dashboard.html';
} catch (error) {
showError(error.message, error.message);
} finally {
button.classList.remove('loading');
}
} else {
showError('User not authenticated', 'User not authenticated');
button.classList.remove('loading');
}
});
function resendVerification() {
const email = document.getElementById('login-email').value;
const password = prompt('Please enter your password to resend verification email:');
if (!email || !password) return;
auth.signInWithEmailAndPassword(email, password)
.then((userCredential) => {
return userCredential.user.sendEmailVerification();
})
.then(() => {
alert('Verification email resent. Please check your inbox.');
auth.signOut();
})
.catch(error => {
showError(error.message, error.message);
});
}
function showForgotPassword() {
const email = prompt('Please enter your email address:');
if (email) {
auth.sendPasswordResetEmail(email)
.then(() => alert('Password reset email sent!'))
.catch(error => showError(error.message, error.message));
}
}
function showError(englishMessage, banglaMessage) {
const errorModal = document.getElementById('error-modal');
const errorMessage = document.getElementById('error-message');
errorMessage.textContent = banglaMessage;
errorModal.style.display = 'flex';
}
function closeErrorModal() {
const errorModal = document.getElementById('error-modal');
errorModal.style.display = 'none';
}
</script>
</body>
</html>
modify the quiz page like this:
when a non authorized user enters the quiz page a auth interface should open for authorize. once logged in or registered then he will proceed to the link he previously entered.
give me the full quiz.html dding this feature