<!DOCTYPE html>
<html lang="bn">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>ডোপামিন - Dashboard</title>
<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" />
<link href="https://cdn.jsdelivr.net/npm/toastify-js/src/toastify.min.css" rel="stylesheet">
<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;
--primary-color: #6b48ff;
--hover-color: #5a3de6;
--secondary-text-color: #666;
--glow-color: rgba(107, 72, 255, 0.3);
--subject-card-bg: #e3efff;
--settings-card-bg: #fff5e6;
--custom-exam-bg: #e8f0ff;
}
[data-theme="dark"] {
--bg-color: #1a1a1a;
--text-color: #f0f0f5;
--card-bg: #2d2d2d;
--shadow: 0 2px 10px rgba(255, 255, 255, 0.1);
--border-color: #444;
--secondary-text-color: #c0c0d0;
--glow-color: rgba(139, 109, 255, 0.3);
--subject-card-bg: #2a2f50;
--settings-card-bg: #4a2f20;
--custom-exam-bg: #252540;
}
* {
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 {
display: flex;
flex-direction: column;
min-height: 100vh;
background: var(--bg-color);
padding: 0.5rem;
}
.navbar {
background: var(--card-bg);
padding: 1rem;
border-radius: 15px;
box-shadow: var(--shadow);
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 1rem;
}
.nav-left {
display: flex;
align-items: center;
gap: 1rem;
}
.nav-left img {
width: 40px;
height: 40px;
border-radius: 50%;
}
.website-name {
font-size: 1.5rem;
color: var(--text-color);
font-weight: 700;
}
.nav-right {
display: flex;
align-items: center;
gap: 1rem;
}
.login-btn {
background: var(--primary-color);
color: white;
padding: 0.5rem 1rem;
border-radius: 8px;
text-decoration: none;
font-weight: 500;
transition: background 0.3s ease;
}
.login-btn:hover {
background: var(--hover-color);
}
.theme-toggle {
background: none;
border: none;
color: var(--primary-color);
cursor: pointer;
font-size: 1.5rem;
padding: 0.5rem;
transition: transform 0.3s;
}
.theme-toggle:hover {
transform: scale(1.1);
}
.menu-container {
position: relative;
}
.menu-btn {
background: none;
border: none;
color: var(--primary-color);
cursor: pointer;
font-size: 1.5rem;
padding: 0.5rem;
transition: transform 0.3s;
}
.menu-btn:hover {
transform: rotate(90deg);
}
.menu-dropdown {
position: absolute;
right: 0;
top: 3rem;
background: var(--card-bg);
border-radius: 10px;
box-shadow: var(--shadow);
display: none;
min-width: 200px;
z-index: 1000;
}
.menu-dropdown.show {
display: block;
animation: fadeIn 0.3s ease-out;
}
@keyframes fadeIn {
from { opacity: 0; transform: translateY(-10px); }
to { opacity: 1; transform: translateY(0); }
}
.user-info {
text-align: center;
padding: 1rem;
border-bottom: 1px solid var(--border-color);
}
.user-avatar {
width: 60px;
height: 60px;
border-radius: 50%;
margin: 0 auto;
cursor: pointer;
overflow: hidden;
border: 2px solid var(--primary-color);
}
.user-avatar img {
width: 100%;
height: 100%;
object-fit: cover;
}
.user-name {
margin-top: 0.5rem;
font-size: 1rem;
font-weight: 500;
color: var(--text-color);
cursor: pointer;
}
.menu-item {
padding: 1rem 1.5rem;
color: var(--text-color);
text-decoration: none;
display: flex;
align-items: center;
gap: 1rem;
transition: all 0.3s;
}
.menu-item:hover {
background: var(--border-color);
padding-left: 2rem;
}
.greeting-section {
padding: 2rem 1rem;
text-align: center;
background: var(--card-bg);
border-radius: 15px;
box-shadow: var(--shadow);
margin-bottom: 1rem;
}
.greeting {
font-size: 1.5rem;
color: var(--text-color);
margin-bottom: 0.5rem;
}
.sub-greeting {
font-size: 1rem;
color: var(--secondary-text-color);
}
.cards-container {
flex: 1;
padding: 1rem;
display: grid;
grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
gap: 1rem;
}
.card {
background: var(--card-bg);
border-radius: 15px;
padding: 1rem;
text-align: center;
box-shadow: var(--shadow);
cursor: pointer;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
min-height: 120px;
transition: transform 0.3s;
position: relative;
}
.card:hover {
transform: translateY(-5px);
}
.card-icon {
font-size: 2rem;
color: var(--primary-color);
margin-bottom: 0.5rem;
}
.card-title {
color: var(--text-color);
font-size: 0.9rem;
font-weight: 500;
}
.card.glowing {
box-shadow: 0 0 8px var(--glow-color);
animation: pulseGlow 2s ease-in-out infinite;
}
@keyframes pulseGlow {
0% { box-shadow: 0 0 8px var(--glow-color); }
50% { box-shadow: 0 0 12px var(--glow-color); }
100% { box-shadow: 0 0 8px var(--glow-color); }
}
.custom-exam-section {
padding: 1rem;
max-width: 1200px;
margin: 0 auto;
width: 100%;
background: var(--custom-exam-bg);
border-radius: 15px;
position: relative;
min-height: 100vh;
}
.custom-exam-headline {
font-size: 1.5rem;
color: var(--primary-color);
text-align: center;
margin-bottom: 1rem;
padding: 0.5rem;
border-bottom: 2px solid var(--border-color);
position: sticky;
top: 0;
background: var(--custom-exam-bg);
z-index: 99;
}
.form-group {
margin-bottom: 1rem;
}
.custom-exam-card {
border-radius: 15px;
padding: 1.5rem;
margin-bottom: 1.5rem;
box-shadow: var(--shadow);
height: 100%;
}
.subject-card {
background: var(--subject-card-bg);
border-left: 5px solid var(--primary-color);
}
.settings-card {
background: var(--settings-card-bg);
border-left: 5px solid #ff9800;
}
@media (min-width: 768px) {
.settings-card {
position: sticky;
top: 1rem;
align-self: flex-start;
}
}
@media (max-width: 767px) {
.settings-card {
position: static;
}
}
.custom-exam-card-title {
font-size: 1.5rem;
color: var(--primary-color);
margin-bottom: 1rem;
padding-bottom: 0.5rem;
border-bottom: 2px solid var(--border-color);
display: inline-block;
}
.settings-card .custom-exam-card-title {
color: #ff9800;
}
.form-label {
display: block;
margin-bottom: 0.5rem;
font-weight: 600;
color: var(--text-color);
}
.form-control {
background: var(--card-bg);
border: 1px solid var(--border-color);
border-radius: 5px;
padding: 0.5rem;
color: var(--text-color);
width: 100%;
}
.form-control:focus {
border-color: var(--primary-color);
box-shadow: 0 0 5px rgba(107, 72, 255, 0.3);
outline: none;
}
.select-wrapper {
position: relative;
}
.select-wrapper::after {
content: "▼";
font-size: 0.8rem;
position: absolute;
right: 10px;
top: 50%;
transform: translateY(-50%);
pointer-events: none;
color: var(--secondary-text-color);
}
select.form-control {
appearance: none;
padding-right: 30px;
}
.custom-time-input {
display: none;
margin-top: 0.5rem;
}
.summary-section {
background: rgba(107, 72, 255, 0.05);
border-radius: 10px;
padding: 1rem;
margin: 1.5rem 0;
}
[data-theme="dark"] .summary-section {
background: rgba(139, 109, 255, 0.1);
}
.summary-title {
font-weight: 600;
margin-bottom: 0.5rem;
color: var(--primary-color);
}
.summary-item {
display: flex;
justify-content: space-between;
margin-bottom: 0.3rem;
}
.summary-label {
color: var(--secondary-text-color);
}
.summary-value {
font-weight: 600;
color: var(--text-color);
}
.btn-primary {
background-color: var(--primary-color);
border: none;
color: #fff;
padding: 0.75rem 1rem;
border-radius: 5px;
cursor: pointer;
transition: background-color 0.3s;
width: 100%;
font-size: 1rem;
font-weight: 600;
}
.btn-primary:hover {
background-color: var(--hover-color);
}
.btn-primary:disabled {
background-color: #a79bff;
cursor: not-allowed;
}
.subject-box {
border: 1px solid var(--border-color);
border-radius: 10px;
margin-bottom: 0.5rem;
background: var(--card-bg);
box-shadow: var(--shadow);
}
.subject-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 0.8rem;
cursor: pointer;
}
.subject-header:hover {
background: var(--border-color);
}
.subject-header .form-check-input {
margin-right: 0.5rem;
width: 20px;
height: 20px;
border: 2px solid var(--border-color);
cursor: pointer;
}
.subject-header .form-check-input:checked {
background-color: var(--primary-color);
border-color: var(--primary-color);
}
.subject-name {
flex-grow: 1;
font-size: 1rem;
font-weight: 700;
}
.question-count {
color: #fff;
background: var(--hover-color);
padding: 0.2rem 0.5rem;
border-radius: 12px;
font-size: 0.9rem;
}
[data-theme="dark"] .question-count {
background: #7b5eff;
}
.arrow {
display: none;
cursor: pointer;
color: var(--primary-color);
font-size: 1.2rem;
transition: transform 0.3s;
}
.arrow.active {
transform: rotate(180deg);
}
.chapter-list {
max-height: 0;
overflow: hidden;
padding: 0 0 0 2rem;
transition: max-height 0.5s ease-in-out;
}
.chapter-list.show {
max-height: 500px;
padding: 0.5rem 0 0.5rem 2rem;
}
.chapter-box {
display: flex;
align-items: center;
padding: 0.5rem 0;
justify-content: space-between;
}
.chapter-box .form-check-input {
margin-right: 0.5rem;
width: 18px;
height: 18px;
border: 2px solid var(--border-color);
cursor: pointer;
}
.chapter-box .form-check-input:checked {
background-color: var(--primary-color);
border-color: var(--primary-color);
}
.chapter-name {
flex-grow: 1;
font-size: 0.9rem;
}
.chapter-question-count {
color: #fff;
background: #e68a00;
padding: 0.2rem 0.5rem;
border-radius: 12px;
font-size: 0.9rem;
}
[data-theme="dark"] .chapter-question-count {
background: #ffaa33;
}
.loading-spinner {
display: none;
text-align: center;
padding: 2rem;
color: var(--primary-color);
font-size: 1.2rem;
animation: spin 1s linear infinite;
}
.loading-spinner::before {
content: "\f110";
font-family: "Font Awesome 6 Free";
font-weight: 900;
margin-right: 0.5rem;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
.row {
display: flex;
flex-wrap: wrap;
margin: 0 -0.5rem;
}
.col-md-6 {
padding: 0 0.5rem;
flex: 0 0 100%;
max-width: 100%;
}
@media (min-width: 768px) {
.col-md-6 {
flex: 0 0 50%;
max-width: 50%;
}
}
.modal {
display: none;
position: fixed;
z-index: 1001;
left: 0;
top: 0;
width: 100%;
height: 100%;
background-color: rgba(0,0,0,0.7);
}
.modal-content {
background-color: var(--card-bg);
margin: 5% auto;
padding: 20px;
border-radius: 15px;
width: 90%;
max-width: 600px;
box-shadow: var(--shadow);
position: relative;
}
.close-modal {
position: absolute;
top: 10px;
right: 15px;
font-size: 28px;
font-weight: bold;
color: var(--text-color);
cursor: pointer;
}
.tab-btn {
background: var(--primary-color);
color: white;
border: none;
padding: 10px 15px;
margin: 0 5px;
border-radius: 5px;
cursor: pointer;
font-size: 0.9rem;
}
.tab-btn.active {
background: var(--hover-color);
}
.tab-content {
display: none;
padding: 20px 0;
}
.tab-content.active {
display: block;
}
#friendList {
margin-top: 15px;
}
.friend-item {
display: flex;
align-items: center;
padding: 10px;
border-bottom: 1px solid var(--border-color);
cursor: pointer;
}
.friend-item:hover {
background-color: rgba(107, 72, 255, 0.1);
}
.friend-avatar {
width: 40px;
height: 40px;
border-radius: 50%;
margin-right: 10px;
}
#battleScreen {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: var(--bg-color);
z-index: 1000;
padding: 20px;
display: none;
}
.player-info {
display: flex;
justify-content: space-around;
align-items: center;
margin-bottom: 30px;
}
.player {
display: flex;
flex-direction: column;
align-items: center;
flex: 1;
}
.player .avatar {
width: 80px;
height: 80px;
border-radius: 50%;
border: 3px solid var(--primary-color);
object-fit: cover;
}
.player .name {
margin-top: 5px;
font-weight: 600;
}
.player .score {
font-size: 1.2rem;
font-weight: bold;
color: var(--primary-color);
margin-top: 5px;
}
.vs {
font-size: 24px;
font-weight: bold;
color: var(--primary-color);
margin: 0 20px;
}
.question-container {
background: var(--card-bg);
padding: 20px;
border-radius: 15px;
max-width: 800px;
margin: 0 auto;
box-shadow: var(--shadow);
}
.timer {
font-size: 24px;
text-align: center;
color: var(--primary-color);
margin-bottom: 20px;
}
.question-text {
font-size: 1.1rem;
margin-bottom: 20px;
line-height: 1.5;
}
.options {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 15px;
}
.option {
padding: 15px;
background: var(--subject-card-bg);
border: none;
border-radius: 10px;
cursor: pointer;
transition: all 0.3s;
font-size: 0.9rem;
text-align: left;
}
.option:hover {
background: var(--primary-color);
color: white;
}
.option.correct {
background: #4CAF50;
color: white;
}
.option.incorrect {
background: #F44336;
color: white;
}
.battle-result {
text-align: center;
margin-top: 30px;
}
.battle-result h2 {
font-size: 2rem;
color: var(--primary-color);
margin-bottom: 10px;
}
.battle-stats {
display: flex;
justify-content: space-around;
margin-top: 20px;
}
.stat-item {
text-align: center;
}
.stat-value {
font-size: 1.5rem;
font-weight: bold;
color: var(--primary-color);
}
.stat-label {
font-size: 0.9rem;
color: var(--secondary-text-color);
}
#matchmakingStatus {
text-align: center;
margin-top: 15px;
font-style: italic;
color: var(--secondary-text-color);
}
.history-item {
padding: 10px;
border-bottom: 1px solid var(--border-color);
display: flex;
justify-content: space-between;
}
.history-item.win {
background-color: rgba(76, 175, 80, 0.1);
}
.history-item.lose {
background-color: rgba(244, 67, 54, 0.1);
}
.history-opponent {
display: flex;
align-items: center;
}
.history-avatar {
width: 30px;
height: 30px;
border-radius: 50%;
margin-right: 10px;
}
@media (max-width: 767px) {
.cards-container {
grid-template-columns: repeat(2, 1fr);
padding: 0.5rem;
gap: 0.8rem;
}
.card {
padding: 0.8rem;
min-height: 100px;
}
.card-icon {
font-size: 1.5rem;
}
.card-title {
font-size: 0.8rem;
}
.navbar {
padding: 0.8rem;
}
.nav-left img {
width: 30px;
height: 30px;
}
.website-name {
font-size: 1.2rem;
}
.theme-toggle, .menu-btn, .login-btn {
font-size: 1.2rem;
padding: 0.3rem;
}
.greeting-section {
padding: 1.5rem 0.8rem;
}
.greeting {
font-size: 1.2rem;
}
.sub-greeting {
font-size: 0.9rem;
}
.custom-exam-headline {
font-size: 1.2rem;
}
.custom-exam-card-title {
font-size: 1.2rem;
}
.subject-box, .chapter-box {
font-size: 0.85rem;
}
.subject-name, .chapter-name {
font-size: 0.85rem;
}
.question-count, .chapter-question-count {
font-size: 0.75rem;
}
.form-label {
font-size: 0.9rem;
}
.form-control {
font-size: 0.85rem;
}
.summary-title, .summary-label, .summary-value {
font-size: 0.85rem;
}
.btn-primary {
font-size: 0.9rem;
}
.custom-exam-card {
padding: 1rem;
}
.player .avatar {
width: 60px;
height: 60px;
}
.vs {
font-size: 18px;
margin: 0 10px;
}
.options {
grid-template-columns: 1fr;
}
.question-text {
font-size: 1rem;
}
}
</style>
</head>
<body>
<div id="content">
<nav class="navbar">
<div class="nav-left">
<img src="logo.png" alt="ডোপামিন Logo" onerror="this.src='https://via.placeholder.com/40';">
<h1 class="website-name">ডোপামিন</h1>
</div>
<div class="nav-right" id="nav-right">
<button class="theme-toggle" onclick="toggleTheme()" aria-label="Toggle theme">
<i class="fas fa-moon" id="themeIcon"></i>
</button>
<div class="menu-container" id="menu-container">
<button class="menu-btn" onclick="toggleMenu(event)">
<i class="fas fa-bars"></i>
</button>
<div class="menu-dropdown" id="menuDropdown">
<div class="user-info" onclick="requireLogin('https://dopamine-edu.github.io/profile.html')">
<div class="user-avatar">
<img id="userAvatar" src="https://cdn-icons-png.flaticon.com/512/4333/4333609.png" alt="User Avatar" onerror="this.src='https://via.placeholder.com/60';">
</div>
<div class="user-name" id="userFullName">ব্যবহারকারী</div>
</div>
<a href="https://dopamine-edu.github.io/courses.html" class="menu-item" onclick="return requireLogin('https://dopamine-edu.github.io/courses.html')">
<i class="fas fa-book"></i>
কোর্স
</a>
<a href="#" class="menu-item" onclick="return requireLogin('#')">
<i class="fab fa-facebook"></i>
ফেসবুক গ্রুপ
</a>
<a href="#" class="menu-item" onclick="return requireLogin('#')">
<i class="fab fa-telegram"></i>
টেলিগ্রাম গ্রুপ
</a>
<a href="#" class="menu-item" onclick="logout()">
<i class="fas fa-sign-out-alt"></i>
লগআউট
</a>
</div>
</div>
<a href="https://dopamine-edu.github.io/auth.html" class="login-btn" id="login-btn" style="display: none;">লগইন করুন</a>
</div>
</nav>
<div class="greeting-section">
<h1 class="greeting">হ্যালো, <span id="lastName">ব্যবহারকারী</span>!</h1>
<p class="sub-greeting">কেমন চলছে আপনার পড়াশোনা?</p>
</div>
<div class="cards-container">
<div class="card" onclick="requireLogin('https://dopamine-edu.github.io/studyplanner.html')">
<i class="fas fa-tasks card-icon"></i>
<h3 class="card-title">স্টাডি প্ল্যানার</h3>
</div>
<div class="card glowing" onclick="requireLogin('https://dopamine-edu.github.io/eid-salami-exam.html')">
<i class="fas fa-graduation-cap card-icon"></i>
<h3 class="card-title">ঈদ সালামী এক্সাম</h3>
</div>
<div class="card" onclick="requireLogin('https://dopamine-edu.github.io/courses.html')">
<i class="fas fa-book card-icon"></i>
<h3 class="card-title">কোর্স</h3>
</div>
<div class="card glowing" onclick="scrollToCustomExam()">
<i class="fas fa-cogs card-icon"></i>
<h3 class="card-title">কাস্টম পরীক্ষা</h3>
</div>
<div class="card" onclick="window.location.href='https://t.me/PDF_paradise'">
<i class="fas fa-file-pdf card-icon"></i>
<h3 class="card-title">পিডিএফ</h3>
</div>
<div class="card glowing" onclick="openBattleModal()">
<i class="fas fa-gamepad card-icon"></i>
<h3 class="card-title">কুইজ ব্যাটল</h3>
</div>
<div class="card" onclick="requireLogin('#')">
<i class="fas fa-chart-line card-icon"></i>
<h3 class="card-title">পারফরম্যান্স</h3>
</div>
</div>
<div class="custom-exam-section" id="custom-exam-section">
<h2 class="custom-exam-headline">কাস্টম পরীক্ষা তৈরি করুন</h2>
<div class="container">
<div id="loading-spinner" class="loading-spinner">পরীক্ষা তৈরি হচ্ছে...</div>
<form id="customExamForm">
<div class="row">
<div class="col-md-6 form-group">
<div class="custom-exam-card subject-card">
<h2 class="custom-exam-card-title">বিষয় নির্বাচন</h2>
<div id="subjectSelection"></div>
</div>
</div>
<div class="col-md-6 form-group">
<div class="custom-exam-card settings-card">
<h2 class="custom-exam-card-title">পরীক্ষা সেটিংস</h2>
<div class="settings-content">
<div class="form-group">
<label for="numQuestions" class="form-label">প্রশ্নের সংখ্যা</label>
<input type="number" id="numQuestions" class="form-control" min="1" required placeholder="প্রশ্ন সংখ্যা লিখুন" />
</div>
<div class="form-group">
<label for="timeSelect" class="form-label">সময় (মিনিট)</label>
<div class="select-wrapper">
<select id="timeSelect" class="form-control">
<option value="10">১০ মিনিট</option>
<option value="20">২০ মিনিট</option>
<option value="30">৩০ মিনিট</option>
<option value="60">৬০ মিনিট</option>
<option value="custom">কাস্টম সময়</option>
</select>
</div>
<div id="customTimeInput" class="custom-time-input">
<input type="number" id="customTime" class="form-control" min="1" placeholder="কাস্টম মিনিট লিখুন" />
</div>
</div>
<div class="form-group">
<label for="negativeMarking" class="form-label">নেগেটিভ মার্কিং (প্রতি ভুল উত্তরে)</label>
<input type="number" id="negativeMarking" class="form-control" min="0" step="0.01" value="0" placeholder="০" />
</div>
<div class="summary-section">
<div class="summary-title">সেটিংস সামারি</div>
<div class="summary-item">
<span class="summary-label">মোট প্রশ্ন:</span>
<span id="summaryQuestions" class="summary-value">০</span>
</div>
<div class="summary-item">
<span class="summary-label">সময়:</span>
<span id="summaryTime" class="summary-value">০ মিনিট</span>
</div>
<div class="summary-item">
<span class="summary-label">নেগেটিভ মার্কিং:</span>
<span id="summaryNegative" class="summary-value">০</span>
</div>
</div>
<button type="submit" class="btn-primary">পরীক্ষা শুরু</button>
</div>
</div>
</div>
</div>
</form>
</div>
</div>
</div>
<div id="battleModal" class="modal">
<div class="modal-content">
<span class="close-modal" onclick="closeBattleModal()">×</span>
<h2>কুইজ ব্যাটল</h2>
<div id="battleTabs">
<button class="tab-btn active" onclick="openBattleTab('quickBattle')">দ্রুত খেলুন</button>
<button class="tab-btn" onclick="openBattleTab('challengeFriend')">বন্ধুকে চ্যালেঞ্জ</button>
<button class="tab-btn" onclick="openBattleTab('battleHistory')">ইতিহাস</button>
</div>
<div id="quickBattle" class="tab-content active">
<p>একটি দ্রুত ম্যাচ খেলুন</p>
<button class="btn-primary" onclick="findOpponent()">খেলোয়াড় খুঁজুন</button>
<div id="matchmakingStatus"></div>
</div>
<div id="challengeFriend" class="tab-content">
<input type="text" id="friendSearch" class="form-control" placeholder="বন্ধুর নাম লিখুন" oninput="searchFriends()">
<div id="friendList"></div>
</div>
<div id="battleHistory" class="tab-content">
<div id="historyList"></div>
</div>
</div>
</div>
<div id="battleScreen">
<div class="player-info">
<div class="player" id="player1">
<img src="" class="avatar" id="player1Avatar">
<span class="name" id="player1Name"></span>
<span class="score" id="player1Score">0</span>
</div>
<div class="vs">VS</div>
<div class="player" id="player2">
<img src="" class="avatar" id="player2Avatar">
<span class="name" id="player2Name"></span>
<span class="score" id="player2Score">0</span>
</div>
</div>
<div class="question-container">
<div class="timer" id="questionTimer">30</div>
<div class="question-text" id="battleQuestionText"></div>
<div class="options" id="battleOptions">
<button class="option" onclick="submitAnswer(0)"></button>
<button class="option" onclick="submitAnswer(1)"></button>
<button class="option" onclick="submitAnswer(2)"></button>
<button class="option" onclick="submitAnswer(3)"></button>
</div>
</div>
<div class="battle-result" id="battleResult" style="display: none;">
<h2 id="resultTitle">আপনি জিতেছেন!</h2>
<div class="battle-stats">
<div class="stat-item">
<div class="stat-value" id="finalPlayer1Score">0</div>
<div class="stat-label">আপনার স্কোর</div>
</div>
<div class="stat-item">
<div class="stat-value" id="finalPlayer2Score">0</div>
<div class="stat-label">প্রতিপক্ষের স্কোর</div>
</div>
<div class="stat-item">
<div class="stat-value" id="correctAnswers">0</div>
<div class="stat-label">সঠিক উত্তর</div>
</div>
</div>
<button class="btn-primary" onclick="closeBattleScreen()" style="margin-top: 20px;">ফিরে যান</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 src="https://cdn.jsdelivr.net/npm/toastify-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();
const CACHE_KEY = 'userProfileData';
const CACHE_EXPIRATION = 24 * 60 * 60 * 1000;
const QUESTION_COUNT_CACHE_KEY = 'questionCounts';
const QUESTION_COUNT_CACHE_EXPIRATION = 60 * 60 * 1000;
let isDark = localStorage.getItem('theme') === 'dark';
let isAuthenticated = false;
let currentBattle = null;
let battleTimer = null;
let timeLeft = 30;
let matchmakingListener = null;
let battleListener = null;
let battleListener2 = null;
let selectedQuestions = [];
let currentQuestionIndex = 0;
let playerAnswers = [];
let opponentAnswers = [];
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');
}
function toggleMenu(event) {
event.stopPropagation();
if (isAuthenticated) {
document.getElementById('menuDropdown').classList.toggle('show');
} else {
Toastify({ text: "এই ফিচারটি ব্যবহার করতে লগইন করুন!", duration: 3000, style: { background: "#dc3545" } }).showToast();
window.location.href = 'https://dopamine-edu.github.io/auth.html';
}
}
function requireLogin(url) {
if (!isAuthenticated) {
Toastify({ text: "এই ফিচারটি ব্যবহার করতে লগইন করুন!", duration: 3000, style: { background: "#dc3545" } }).showToast();
window.location.href = 'https://dopamine-edu.github.io/auth.html';
return false;
}
window.location.href = url;
return true;
}
function getCachedUserData() {
const cached = localStorage.getItem(CACHE_KEY);
if (cached) {
try {
const cacheData = JSON.parse(cached);
if (Date.now() - cacheData.timestamp < CACHE_EXPIRATION) {
return cacheData.data;
} else {
localStorage.removeItem(CACHE_KEY);
}
} catch (e) {
console.error('Error parsing cached user data:', e);
localStorage.removeItem(CACHE_KEY);
}
}
return null;
}
function setCachedUserData(userData) {
const cacheData = {
timestamp: Date.now(),
data: userData
};
try {
localStorage.setItem(CACHE_KEY, JSON.stringify(cacheData));
} catch (e) {
console.error('Error setting cache:', e);
}
}
auth.onAuthStateChanged(async user => {
const loginBtn = document.getElementById('login-btn');
const menuContainer = document.getElementById('menu-container');
const userFullName = document.getElementById('userFullName');
const lastName = document.getElementById('lastName');
if (user) {
isAuthenticated = true;
loginBtn.style.display = 'none';
menuContainer.style.display = 'block';
let userData = getCachedUserData();
if (userData) {
populateUserData(userData);
} else {
try {
const userDoc = await db.collection('users').doc(user.uid).get();
if (userDoc.exists) {
userData = userDoc.data();
userData.email = user.email;
setCachedUserData(userData);
populateUserData(userData);
} else {
userFullName.textContent = 'ব্যবহারকারী';
lastName.textContent = 'ব্যবহারকারী';
}
} catch (error) {
console.error('Error loading user data:', error);
userFullName.textContent = 'ব্যবহারকারী';
lastName.textContent = 'ব্যবহারকারী';
}
}
db.collection('battle_requests')
.where('toUser', '==', user.uid)
.where('status', '==', 'pending')
.onSnapshot(snapshot => {
snapshot.docChanges().forEach(change => {
if (change.type === 'added') {
showChallengeNotification({ ...change.doc.data(), requestId: change.doc.id });
}
});
});
setupBattleListener(user.uid);
} else {
isAuthenticated = false;
loginBtn.style.display = 'inline-block';
menuContainer.style.display = 'none';
userFullName.textContent = 'ব্যবহারকারী';
lastName.textContent = 'ব্যবহারকারী';
if (battleListener) {
battleListener();
battleListener = null;
}
if (battleListener2) {
battleListener2();
battleListener2 = null;
}
if (matchmakingListener) {
matchmakingListener();
matchmakingListener = null;
}
}
});
function populateUserData(userData) {
const fullName = userData.fullName || 'ব্যবহারকারী';
document.getElementById('userFullName').textContent = fullName;
document.getElementById('lastName').textContent = fullName.split(' ').pop();
if (userData.avatar) {
document.getElementById('userAvatar').src = userData.avatar;
}
}
function logout() {
if (isAuthenticated) {
auth.signOut().then(() => {
localStorage.removeItem(CACHE_KEY);
localStorage.removeItem('theme');
window.location.href = 'https://dopamine-edu.github.io/auth.html';
}).catch(error => {
Toastify({ text: "লগআউটে ত্রুটি: " + error.message, duration: 3000, style: { background: "#dc3545" } }).showToast();
});
}
}
function scrollToCustomExam() {
if (!isAuthenticated) {
Toastify({ text: "কাস্টম পরীক্ষা তৈরি করতে লগইন করুন!", duration: 3000, style: { background: "#dc3545" } }).showToast();
window.location.href = 'https://dopamine-edu.github.io/auth.html';
return;
}
document.getElementById('custom-exam-section').scrollIntoView({ behavior: 'smooth' });
}
document.addEventListener('click', (e) => {
if (!e.target.closest('.menu-container')) {
document.getElementById('menuDropdown').classList.remove('show');
}
});
const subjects = {
"Botany": [
"Chapter 01: কোষ ও এর গঠন",
"Chapter 02: কোষ বিভাজন",
"Chapter 03: কোষ রসায়ন",
"Chapter 04: অণুজীব",
"Chapter 05: শৈবাল ও ছত্রাক",
"Chapter 06: ব্রায়োফাইটা ও টেরেডোফাইটা",
"Chapter 07: নগ্নবীজী ও আবৃতবীজী উদ্ভিদ",
"Chapter 08: টিস্যু ও টিস্যুতন্ত্র",
"Chapter 09: উদ্ভিদ শারীরতত্ত্ব",
"Chapter 10: উদ্ভিদ প্রজনন",
"Chapter 11: জীবপ্রযুক্তি"
],
"Zoology": [
"Chapter 01: প্রাণীর বিভিন্নতা ও শ্রেণিবিন্যাস",
"Chapter 02: প্রাণীর পরিচিতি",
"Chapter 03: পরিপাক ও শোষণ",
"Chapter 04: রক্ত সঞ্চালন",
"Chapter 05: শ্বাসক্রিয়া ও শ্বসন",
"Chapter 06: বর্জ্য ও নিষ্কাশন",
"Chapter 07: চলন ও অঙ্গচালনা",
"Chapter 08: সমন্বয় ও নিয়ন্ত্রণ",
"Chapter 09: মানব জীবনের ধারাবাহিকতা",
"Chapter 10: মানবদেহের প্রতিরক্ষা",
"Chapter 11: জীনতত্ত্ব ও বিবর্তন"
],
"Physics 1st": [
"Chapter 01: ভৌত জগৎ ও পরিমাপ",
"Chapter 02: ভেক্টর",
"Chapter 04: নিউটোনিয়ান বলবিদ্যা",
"Chapter 05: কাজ, শক্তি ও ক্ষমতা",
"Chapter 06: মহাকর্ষ ও অভিকর্ষ",
"Chapter 07: পদার্থের গাঠনিক ধর্ম",
"Chapter 08: পর্যাবৃত্ত গতি",
"Chapter 10: আদর্শ গ্যাস ও গ্যাসের গতিতত্ত্ব"
],
"Physics 2nd": [
"Chapter 01: তাপগতিবিদ্যা",
"Chapter 02: স্থির তড়িৎ",
"Chapter 03: চলতড়িৎ",
"Chapter 07: ভৌত আলোকবিজ্ঞান",
"Chapter 08: আধুনিক পদার্থবিজ্ঞানের সূচনা",
"Chapter 09: পরমাণুর মডেল এবং নিউক্লিয়ার পদার্থবিজ্ঞান",
"Chapter 10: সেমিকন্ডাক্টর ও ইলেকট্রনিক্স"
],
"Chemistry 1st": [
"Chapter 01: ল্যাবরেটরির নিরাপদ ব্যবহার",
"Chapter 02: গুণগত রসায়ন",
"Chapter 03: মৌলের পর্যায়বৃত্ত ধর্ম",
"Chapter 04: রাসায়নিক পরিবর্তন",
"Chapter 05: কর্মমুখী রসায়ন"
],
"Chemistry 2nd": [
"Chapter 01: পরিবেশ রসায়ন",
"Chapter 02: জৈব রসায়ন",
"Chapter 03: পরিমাণগত রসায়ন",
"Chapter 04: তড়িৎ রসায়ন",
"Chapter 05: অর্থনৈতিক রসায়ন"
],
"Higher Math 1st": [
"Chapter 01: ম্যাট্রিক্স ও নির্ণায়ক",
"Chapter 03: সরলরেখা",
"Chapter 04: বৃত্ত",
"Chapter 07: সংযুক্ত কোণের ত্রিকোণমিতিক অনুপাত",
"Chapter 09: অন্তরীকরণ",
"Chapter 10: যোগজীকরণ"
],
"Higher Math 2nd": [
"Chapter 03: জটিল সংখ্যা",
"Chapter 04: বহুপদী ও বহুপদী সমীকরণ",
"Chapter 06: কণিক",
"Chapter 07: বিপরীত ত্রিকোণমিতিক ফাংশন ও ত্রিকোণমিতিক সমীকরণ",
"Chapter 08: স্থিতিবিদ্যা",
"Chapter 09: সমতলে বস্তুকণার গতি"
],
"English": [],
"Bangla": []
};
document.getElementById('timeSelect').addEventListener('change', function() {
const customTimeInput = document.getElementById('customTimeInput');
if (this.value === 'custom') {
customTimeInput.style.display = 'block';
} else {
customTimeInput.style.display = 'none';
updateSummary();
}
});
document.getElementById('numQuestions').addEventListener('input', updateSummary);
document.getElementById('timeSelect').addEventListener('change', updateSummary);
document.getElementById('customTime').addEventListener('input', updateSummary);
document.getElementById('negativeMarking').addEventListener('input', updateSummary);
function updateSummary() {
const numQuestions = document.getElementById('numQuestions').value || '0';
const timeSelect = document.getElementById('timeSelect');
const customTime = document.getElementById('customTime').value;
const negativeMarking = document.getElementById('negativeMarking').value || '0';
document.getElementById('summaryQuestions').textContent = numQuestions;
let timeDisplay;
if (timeSelect.value === 'custom' && customTime) {
timeDisplay = `${customTime} মিনিট`;
} else if (timeSelect.value !== 'custom') {
timeDisplay = `${timeSelect.options[timeSelect.selectedIndex].text}`;
} else {
timeDisplay = '০ মিনিট';
}
document.getElementById('summaryTime').textContent = timeDisplay;
document.getElementById('summaryNegative').textContent = negativeMarking;
}
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`;
try {
const response = await fetch(filePath);
if (!response.ok) {
throw new Error(`HTTP error! Status: ${response.status}`);
}
const data = await response.json();
return data;
} catch (error) {
console.error(`Error loading ${filePath}:`, error);
Toastify({ text: `${subject} - ${chapter} প্রশ্ন লোড করতে ব্যর্থ`, duration: 3000, style: { background: "#dc3545" } }).showToast();
return [];
}
}
async function fetchQuestionCounts() {
const cachedCounts = localStorage.getItem(QUESTION_COUNT_CACHE_KEY);
if (cachedCounts) {
try {
const { data, timestamp } = JSON.parse(cachedCounts);
if (Date.now() - timestamp < QUESTION_COUNT_CACHE_EXPIRATION) {
return data;
}
} catch (e) {
console.error('Error parsing cached question counts:', e);
localStorage.removeItem(QUESTION_COUNT_CACHE_KEY);
}
}
const questionCounts = {};
const chapterQuestionCounts = {};
for (const subject in subjects) {
if (subjects[subject].length === 0) continue;
let subjectCount = 0;
const chapters = subjects[subject];
const chapterCounts = [];
for (const chapter of chapters) {
const questions = await loadQuestionsFromJSON(subject, chapter);
const count = questions.length;
subjectCount += count;
chapterCounts.push(count);
}
questionCounts[subject] = subjectCount;
chapterQuestionCounts[subject] = chapterCounts;
}
try {
localStorage.setItem(QUESTION_COUNT_CACHE_KEY, JSON.stringify({
data: { questionCounts, chapterQuestionCounts },
timestamp: Date.now()
}));
} catch (e) {
console.error('Error caching question counts:', e);
}
return { questionCounts, chapterQuestionCounts };
}
function createSubjectBox(subject, questionCount, chapterCounts) {
const subjectBox = document.createElement('div');
subjectBox.className = 'subject-box';
subjectBox.innerHTML = `
<div class="subject-header">
<input type="checkbox" class="form-check-input" onchange="toggleSubject(this)">
<span class="subject-name">${subject}</span>
<span class="question-count">${questionCount} প্রশ্ন</span>
<i class="fas fa-chevron-down arrow"></i>
</div>
`;
const chapterList = document.createElement('div');
chapterList.className = 'chapter-list';
subjects[subject].forEach((chapter, index) => {
const displayChapterName = chapter.replace(/^Chapter \d+:\s*/, '');
const chapterBox = document.createElement('div');
chapterBox.className = 'chapter-box';
chapterBox.dataset.originalChapter = chapter;
chapterBox.dataset.subject = subject;
chapterBox.innerHTML = `
<input type="checkbox" class="form-check-input" onchange="toggleChapter(this)">
<span class="chapter-name">${displayChapterName}</span>
<span class="chapter-question-count">${chapterCounts[index]} প্রশ্ন</span>
`;
chapterList.appendChild(chapterBox);
});
subjectBox.appendChild(chapterList);
subjectBox.querySelector('.subject-header').addEventListener('click', function(e) {
if (e.target.type !== 'checkbox') {
toggleChapters(this);
}
});
return subjectBox;
}
function toggleSubject(checkbox) {
const subjectBox = checkbox.parentElement.parentElement;
const chapterList = subjectBox.querySelector('.chapter-list');
const arrow = subjectBox.querySelector('.arrow');
const chapterCheckboxes = chapterList.querySelectorAll('.form-check-input');
arrow.style.display = checkbox.checked ? 'block' : 'none';
chapterCheckboxes.forEach(chapterCheckbox => {
chapterCheckbox.checked = checkbox.checked;
});
updateSummary();
}
function toggleChapters(header) {
const chapterList = header.parentElement.querySelector('.chapter-list');
const arrow = header.querySelector('.arrow');
chapterList.classList.toggle('show');
arrow.classList.toggle('active');
}
function toggleChapter(checkbox) {
updateSummary();
}
async function populateSubjects() {
try {
const { questionCounts, chapterQuestionCounts } = await fetchQuestionCounts();
const subjectSelection = document.getElementById('subjectSelection');
subjectSelection.innerHTML = '';
for (const subject in subjects) {
if (subjects[subject].length > 0) {
const subjectBox = createSubjectBox(subject, questionCounts[subject], chapterQuestionCounts[subject]);
subjectSelection.appendChild(subjectBox);
}
}
} catch (error) {
console.error("Error populating subjects:", error);
Toastify({ text: "বিষয়গুলো লোড করতে ত্রুটি: " + error.message, duration: 3000, style: { background: "#dc3545" } }).showToast();
}
}
document.getElementById("customExamForm").addEventListener("submit", async (e) => {
e.preventDefault();
if (!isAuthenticated) {
Toastify({ text: "কাস্টম পরীক্ষা তৈরি করতে লগইন করুন!", duration: 3000, style: { background: "#dc3545" } }).showToast();
window.location.href = 'https://dopamine-edu.github.io/auth.html';
return;
}
const submitButton = e.target.querySelector('.btn-primary');
submitButton.disabled = true;
submitButton.textContent = 'তৈরি হচ্ছে...';
const loadingDiv = document.getElementById("loading-spinner");
loadingDiv.style.display = "block";
const numQuestions = parseInt(document.getElementById("numQuestions").value);
const timeSelect = document.getElementById("timeSelect");
const customTime = parseInt(document.getElementById("customTime").value);
const negativeMarking = parseFloat(document.getElementById("negativeMarking").value);
const selectedChapters = document.querySelectorAll('.chapter-box .form-check-input:checked');
if (selectedChapters.length === 0) {
Toastify({ text: "অন্তত একটি অধ্যায় নির্বাচন করুন!", duration: 3000, style: { background: "#dc3545" } }).showToast();
loadingDiv.style.display = "none";
submitButton.disabled = false;
submitButton.textContent = 'পরীক্ষা শুরু';
return;
}
if (!numQuestions || numQuestions <= 0) {
Toastify({ text: "প্রশ্নের সংখ্যা ১ বা তার বেশি হতে হবে।", duration: 3000, style: { background: "#dc3545" } }).showToast();
loadingDiv.style.display = "none";
submitButton.disabled = false;
submitButton.textContent = 'পরীক্ষা শুরু';
return;
}
if (timeSelect.value === 'custom' && (!customTime || customTime <= 0)) {
Toastify({ text: "কাস্টম সময় ১ মিনিট বা তার বেশি হতে হবে।", duration: 3000, style: { background: "#dc3545" } }).showToast();
loadingDiv.style.display = "none";
submitButton.disabled = false;
submitButton.textContent = 'পরীক্ষা শুরু';
return;
}
if (isNaN(negativeMarking) || negativeMarking < 0) {
Toastify({ text: "নেগেটিভ মার্কিং ০ বা তার বেশি হতে হবে।", duration: 3000, style: { background: "#dc3545" } }).showToast();
loadingDiv.style.display = "none";
submitButton.disabled = false;
submitButton.textContent = 'পরীক্ষা শুরু';
return;
}
try {
const selectedChaptersData = [];
selectedChapters.forEach(checkbox => {
const chapterBox = checkbox.parentElement;
selectedChaptersData.push({ subject: chapterBox.dataset.subject, chapter: chapterBox.dataset.originalChapter });
});
const allQuestions = [];
for (const { subject, chapter } of selectedChaptersData) {
const chapterQuestions = await loadQuestionsFromJSON(subject, chapter);
chapterQuestions.forEach((q, index) => {
allQuestions.push({ id: index, subject, chapter });
});
}
if (allQuestions.length === 0) {
throw new Error("নির্বাচিত অধ্যায়গুলোর জন্য কোনো প্রশ্ন পাওয়া যায়নি।");
}
if (allQuestions.length < numQuestions) {
Toastify({ text: `মাত্র ${allQuestions.length}টি প্রশ্ন পাওয়া গেছে, সবগুলো ব্যবহার করা হচ্ছে।`, duration: 3000, style: { background: "#ff9800" } }).showToast();
}
const selectedQuestions = allQuestions.sort(() => 0.5 - Math.random()).slice(0, Math.min(numQuestions, allQuestions.length));
const subjectsList = [...new Set(selectedChaptersData.map(item => item.subject))];
const chaptersList = selectedChaptersData.map(item => item.chapter);
const time = timeSelect.value === 'custom' ? customTime : parseInt(timeSelect.value);
const examDocRef = await db.collection("exams").add({
title: `কাস্টম পরীক্ষা - ${chaptersList.map(ch => ch.replace(/^Chapter \d+:\s*/, '')).join(", ")}`,
duration: time,
marksPerQuestion: 1,
negativeMark: negativeMarking,
lastDate: null,
questions: selectedQuestions,
subjects: subjectsList,
chapters: chaptersList,
createdAt: firebase.firestore.FieldValue.serverTimestamp()
});
loadingDiv.style.display = "none";
submitButton.disabled = false;
submitButton.textContent = 'পরীক্ষা শুরু';
Toastify({ text: "কাস্টম পরীক্ষা সফলভাবে তৈরি হয়েছে!", duration: 3000, style: { background: "#6b48ff" } }).showToast();
window.location.href = `quiz.html?examId=${examDocRef.id}`;
} catch (error) {
loadingDiv.style.display = "none";
submitButton.disabled = false;
submitButton.textContent = 'পরীক্ষা শুরু';
console.error("কাস্টম পরীক্ষা তৈরিতে ত্রুটি:", error);
Toastify({ text: "পরীক্ষা তৈরিতে ত্রুটি: " + error.message, duration: 3000, style: { background: "#dc3545" } }).showToast();
}
});
function openBattleModal() {
if (!isAuthenticated) {
Toastify({ text: "কুইজ ব্যাটল খেলতে লগইন করুন!", duration: 3000, style: { background: "#dc3545" } }).showToast();
window.location.href = 'https://dopamine-edu.github.io/auth.html';
return;
}
document.getElementById('battleModal').style.display = 'block';
loadFriendList();
loadBattleHistory();
const user = auth.currentUser;
if (user) {
setupBattleListener(user.uid);
}
}
function closeBattleModal() {
document.getElementById('battleModal').style.display = 'none';
if (matchmakingListener) {
matchmakingListener();
matchmakingListener = null;
}
if (battleListener) {
battleListener();
battleListener = null;
}
if (battleListener2) {
battleListener2();
battleListener2 = null;
}
}
function openBattleTab(tabId) {
document.querySelectorAll('.tab-content').forEach(tab => {
tab.classList.remove('active');
});
document.querySelectorAll('.tab-btn').forEach(btn => {
btn.classList.remove('active');
});
document.getElementById(tabId).classList.add('active');
document.querySelector(`[onclick="openBattleTab('${tabId}')"]`).classList.add('active');
}
async function loadFriendList() {
const user = auth.currentUser;
if (!user) return;
try {
const snapshot = await db.collection('users')
.where('uid', '!=', user.uid)
.limit(20)
.get();
const friendList = document.getElementById('friendList');
friendList.innerHTML = '';
if (snapshot.empty) {
friendList.innerHTML = '<p>কোনো বন্ধু পাওয়া যায়নি</p>';
return;
}
snapshot.forEach(doc => {
const friend = doc.data();
const friendItem = document.createElement('div');
friendItem.className = 'friend-item';
friendItem.innerHTML = `
<img src="${friend.avatar || 'https://cdn-icons-png.flaticon.com/512/4333/4333609.png'}"
class="friend-avatar"
onerror="this.src='https://via.placeholder.com/40'">
<span>${friend.fullName || 'ব্যবহারকারী'}</span>
`;
friendItem.onclick = () => sendChallenge(friend.uid);
friendList.appendChild(friendItem);
});
} catch (error) {
console.error("Error loading friends:", error);
Toastify({ text: "বন্ধু লোড করতে ত্রুটি: " + error.message, duration: 3000, style: { background: "#dc3545" } }).showToast();
}
}
async function loadBattleHistory() {
const user = auth.currentUser;
if (!user) return;
try {
const snapshot1 = await db.collection('battles')
.where('status', '==', 'completed')
.where('player1', '==', user.uid)
.orderBy('endTime', 'desc')
.limit(10)
.get();
const snapshot2 = await db.collection('battles')
.where('status', '==', 'completed')
.where('player2', '==', user.uid)
.orderBy('endTime', 'desc')
.limit(10)
.get();
const historyList = document.getElementById('historyList');
historyList.innerHTML = '';
const battles = [];
snapshot1.forEach(doc => battles.push({ id: doc.id, ...doc.data() }));
snapshot2.forEach(doc => battles.push({ id: doc.id, ...doc.data() }));
battles.sort((a, b) => (b.endTime?.toMillis() || 0) - (a.endTime?.toMillis() || 0));
const limitedBattles = battles.slice(0, 10);
if (limitedBattles.length === 0) {
historyList.innerHTML = '<p>কোনো ইতিহাস পাওয়া যায়নি</p>';
return;
}
for (const battle of limitedBattles) {
const opponentId = battle.player1 === user.uid ? battle.player2 : battle.player1;
const opponentDoc = await db.collection('users').doc(opponentId).get();
const opponent = opponentDoc.data() || { fullName: 'ব্যবহারকারী', avatar: 'https://cdn-icons-png.flaticon.com/512/4333/4333609.png' };
const isWinner = (battle.player1 === user.uid && battle.scores.player1 > battle.scores.player2) ||
(battle.player2 === user.uid && battle.scores.player2 > battle.scores.player1);
const historyItem = document.createElement('div');
historyItem.className = `history-item ${isWinner ? 'win' : 'lose'}`;
historyItem.innerHTML = `
<div class="history-opponent">
<img src="${opponent.avatar || 'https://cdn-icons-png.flaticon.com/512/4333/4333609.png'}"
class="history-avatar"
onerror="this.src='https://via.placeholder.com/30'">
<span>${opponent.fullName}</span>
</div>
<div>
<span>${battle.scores.player1}</span> -
<span>${battle.scores.player2}</span>
</div>
`;
historyList.appendChild(historyItem);
}
} catch (error) {
console.error("Error loading battle history:", error);
Toastify({ text: "ইতিহাস লোড করতে ত্রুটি: " + error.message, duration: 3000, style: { background: "#dc3545" } }).showToast();
}
}
function searchFriends() {
const searchTerm = document.getElementById('friendSearch').value.toLowerCase();
const friends = document.querySelectorAll('.friend-item');
friends.forEach(friend => {
const name = friend.textContent.toLowerCase();
friend.style.display = name.includes(searchTerm) ? 'flex' : 'none';
});
}
function sendChallenge(friendId) {
const user = auth.currentUser;
if (!user) return;
db.collection('battle_requests').add({
fromUser: user.uid,
toUser: friendId,
status: 'pending',
createdAt: firebase.firestore.FieldValue.serverTimestamp()
}).then(() => {
Toastify({ text: "চ্যালেঞ্জ পাঠানো হয়েছে!", duration: 3000, style: { background: "#6b48ff" } }).showToast();
}).catch(error => {
Toastify({ text: "চ্যালেঞ্জ পাঠাতে ত্রুটি: " + error.message, duration: 3000, style: { background: "#dc3545" } }).showToast();
console.error("Error sending challenge:", error);
}
}
function showChallengeNotification(request) {
Toastify({
text: `আপনি ${request.fromUser} থেকে একটি ব্যাটল চ্যালেঞ্জ পেয়েছেন!`,
duration: 5000,
style: { background: "#6b48ff" },
close: true,
onClick: () => {
db.collection('battle_requests').doc(request.requestId).update({
status: 'accepted'
}).then(() => startBattle(request.fromUser, auth.currentUser.uid));
}
}).showToast();
}
async function findOpponent() {
const user = auth.currentUser;
if (!user) return;
const statusDiv = document.getElementById('matchmakingStatus');
statusDiv.textContent = 'প্রতিপক্ষ খুঁজছি...';
try {
const existingRequest = await db.collection('battle_requests')
.where('status', '==', 'pending')
.where('toUser', '==', null)
.where('fromUser', '!=', user.uid)
.limit(1)
.get();
if (!existingRequest.empty) {
const request = existingRequest.docs[0];
await db.collection('battle_requests').doc(request.id).update({
toUser: user.uid,
status: 'accepted'
});
startBattle(request.data().fromUser, user.uid);
return;
}
const requestRef = await db.collection('battle_requests').add({
fromUser: user.uid,
toUser: null,
status: 'pending',
createdAt: firebase.firestore.FieldValue.serverTimestamp()
});
matchmakingListener = db.collection('battle_requests')
.doc(requestRef.id)
.onSnapshot(doc => {
if (doc.exists && doc.data().status === 'accepted') {
statusDiv.textContent = 'প্রতিপক্ষ পাওয়া গেছে!';
startBattle(user.uid, doc.data().toUser);
}
});
} catch (error) {
statusDiv.textContent = 'প্রতিপক্ষ খুঁজতে ত্রুটি';
Toastify({ text: "প্রতিপক্ষ খুঁজতে ত্রুটি: " + error.message, duration: 3000, style: { background: "#dc3545" } }).showToast();
console.error("Error finding opponent:", error);
}
}
async function startBattle(player1Id, player2Id) {
try {
const subjectsToInclude = ['Botany', 'Zoology', 'Physics 1st', 'Physics 2nd', 'Chemistry 1st', 'Chemistry 2nd'];
const allQuestions = [];
for (const subject of subjectsToInclude) {
for (const chapter of subjects[subject]) {
const questions = await loadQuestionsFromJSON(subject, chapter);
questions.forEach((q, index) => {
allQuestions.push({ id: index, subject, chapter });
});
}
}
selectedQuestions = allQuestions.sort(() => 0.5 - Math.random()).slice(0, 10);
const battleRef = await db.collection('battles').add({
player1: player1Id,
player2: player2Id,
status: 'active',
questions: selectedQuestions,
scores: { player1: 0, player2: 0 },
answers: { player1: [], player2: [] },
currentQuestion: 0,
startTime: firebase.firestore.FieldValue.serverTimestamp()
});
currentBattle = battleRef.id;
document.getElementById('battleModal').style.display = 'none';
document.getElementById('battleScreen').style.display = 'block';
loadBattleQuestion();
} catch (error) {
Toastify({ text: "ব্যাটল শুরু করতে ত্রুটি: " + error.message, duration: 3000, style: { background: "#dc3545" } }).showToast();
console.error("Error starting battle:", error);
}
}
async function loadBattleQuestion() {
if (!currentBattle) return;
try {
const battleDoc = await db.collection('battles').doc(currentBattle).get();
if (!battleDoc.exists) {
endBattle();
return;
}
const battleData = battleDoc.data();
currentQuestionIndex = battleData.currentQuestion || 0;
if (currentQuestionIndex >= selectedQuestions.length) {
endBattle();
return;
}
const questionData = selectedQuestions[currentQuestionIndex];
const questions = await loadQuestionsFromJSON(questionData.subject, questionData.chapter);
const question = questions[questionData.id];
document.getElementById('battleQuestionText').textContent = question.question;
const options = document.querySelectorAll('#battleOptions .option');
question.options.forEach((option, index) => {
options[index].textContent = option;
options[index].classList.remove('correct', 'incorrect');
options[index].disabled = false;
});
timeLeft = 30;
document.getElementById('questionTimer').textContent = timeLeft;
if (battleTimer) clearInterval(battleTimer);
battleTimer = setInterval(() => {
timeLeft--;
document.getElementById('questionTimer').textContent = timeLeft;
if (timeLeft <= 0) {
submitAnswer(null);
}
}, 1000);
const user = auth.currentUser;
if (user) {
const playerField = battleData.player1 === user.uid ? 'player1' : 'player2';
playerAnswers = battleData.answers[playerField] || [];
opponentAnswers = battleData.answers[playerField === 'player1' ? 'player2' : 'player1'] || [];
}
} catch (error) {
Toastify({ text: "প্রশ্ন লোড করতে ত্রুটি: " + error.message, duration: 3000, style: { background: "#dc3545" } }).showToast();
console.error("Error loading battle question:", error);
}
}
async function submitAnswer(optionIndex) {
if (!currentBattle || optionIndex === null) {
clearInterval(battleTimer);
currentQuestionIndex++;
try {
await db.collection('battles').doc(currentBattle).update({
currentQuestion: currentQuestionIndex
});
loadBattleQuestion();
} catch (error) {
Toastify({ text: "উত্তর জমা দিতে ত্রুটি: " + error.message, duration: 3000, style: { background: "#dc3545" } }).showToast();
console.error("Error submitting answer:", error);
}
return;
}
try {
const battleDoc = await db.collection('battles').doc(currentBattle).get();
const battleData = battleDoc.data();
const questionData = selectedQuestions[currentQuestionIndex];
const questions = await loadQuestionsFromJSON(questionData.subject, questionData.chapter);
const question = questions[questionData.id];
const isCorrect = optionIndex === question.correctOption;
const user = auth.currentUser;
const playerField = battleData.player1 === user.uid ? 'player1' : 'player2';
playerAnswers.push({ questionIndex: currentQuestionIndex, answer: optionIndex, correct: isCorrect });
const updateData = {
[`answers.${playerField}`]: playerAnswers
};
if (isCorrect) {
updateData[`scores.${playerField}`] = firebase.firestore.FieldValue.increment(1);
}
const options = document.querySelectorAll('#battleOptions .option');
options.forEach((option, idx) => {
option.disabled = true;
if (idx === question.correctOption) {
option.classList.add('correct');
} else if (idx === optionIndex) {
option.classList.add('incorrect');
}
});
clearInterval(battleTimer);
setTimeout(async () => {
currentQuestionIndex++;
await db.collection('battles').doc(currentBattle).update({
currentQuestion: currentQuestionIndex,
...updateData
});
loadBattleQuestion();
}, 2000);
} catch (error) {
Toastify({ text: "উত্তর জমা দিতে ত্রুটি: " + error.message, duration: 3000, style: { background: "#dc3545" } }).showToast();
console.error("Error submitting answer:", error);
}
}
async function endBattle() {
clearInterval(battleTimer);
if (!currentBattle) return;
try {
const battleDoc = await db.collection('battles').doc(currentBattle).get();
const battleData = battleDoc.data();
const user = auth.currentUser;
const playerField = battleData.player1 === user.uid ? 'player1' : 'player2';
const opponentField = playerField === 'player1' ? 'player2' : 'player1';
const playerScore = battleData.scores[playerField] || 0;
const opponentScore = battleData.scores[opponentField] || 0;
const correctCount = playerAnswers.filter(a => a.correct).length;
document.getElementById('finalPlayer1Score').textContent = battleData.scores.player1 || 0;
document.getElementById('finalPlayer2Score').textContent = battleData.scores.player2 || 0;
document.getElementById('correctAnswers').textContent = correctCount;
document.getElementById('resultTitle').textContent = playerScore > opponentScore ? 'আপনি জিতেছেন!' : playerScore < opponentScore ? 'আপনি হেরেছেন!' : 'ড্র!';
document.getElementById('battleResult').style.display = 'block';
await db.collection('battles').doc(currentBattle).update({
status: 'completed',
endTime: firebase.firestore.FieldValue.serverTimestamp()
});
currentBattle = null;
selectedQuestions = [];
currentQuestionIndex = 0;
playerAnswers = [];
opponentAnswers = [];
} catch (error) {
Toastify({ text: "ব্যাটল শেষ করতে ত্রুটি: " + error.message, duration: 3000, style: { background: "#dc3545" } }).showToast();
console.error("Error ending battle:", error);
}
}
function closeBattleScreen() {
document.getElementById('battleScreen').style.display = 'none';
document.getElementById('battleResult').style.display = 'none';
if (battleListener) {
battleListener();
battleListener = null;
}
if (battleListener2) {
battleListener2();
battleListener2 = null;
}
if (matchmakingListener) {
matchmakingListener();
matchmakingListener = null;
}
openBattleModal();
}
async function setupBattleListener(userId) {
try {
battleListener = db.collection('battles')
.where('player1', '==', userId)
.where('status', '==', 'active')
.onSnapshot(async snapshot => {
snapshot.docChanges().forEach(async change => {
if (change.type === 'added' || change.type === 'modified') {
const battleData = change.doc.data();
currentBattle = change.doc.id;
selectedQuestions = battleData.questions;
await updatePlayerInfo(battleData);
loadBattleQuestion();
}
});
}, error => {
console.error("Error in battle listener (player1):", error);
Toastify({ text: "ব্যাটল তথ্য লোডে ত্রুটি: " + error.message, duration: 3000, style: { background: "#dc3545" } }).showToast();
});
battleListener2 = db.collection('battles')
.where('player2', '==', userId)
.where('status', '==', 'active')
.onSnapshot(async snapshot => {
snapshot.docChanges().forEach(async change => {
if (change.type === 'added' || change.type === 'modified') {
const battleData = change.doc.data();
currentBattle = change.doc.id;
selectedQuestions = battleData.questions;
await updatePlayerInfo(battleData);
loadBattleQuestion();
}
});
}, error => {
console.error("Error in battle listener (player2):", error);
Toastify({ text: "ব্যাটল তথ্য লোডে ত্রুটি: " + error.message, duration: 3000, style: { background: "#dc3545" } }).showToast();
});
} catch (error) {
console.error("Error setting up battle listener:", error);
Toastify({ text: "ব্যাটল লিসেনার সেটআপে ত্রুটি: " + error.message, duration: 3000, style: { background: "#dc3545" } }).showToast();
}
}
async function updatePlayerInfo(battleData) {
try {
const player1Doc = await db.collection('users').doc(battleData.player1).get();
const player2Doc = await db.collection('users').doc(battleData.player2).get();
document.getElementById('player1Name').textContent = player1Doc.data()?.fullName || 'Player 1';
document.getElementById('player1Avatar').src = player1Doc.data()?.avatar || 'https://cdn-icons-png.flaticon.com/512/4333/4333609.png';
document.getElementById('player1Score').textContent = battleData.scores.player1 || 0;
document.getElementById('player2Name').textContent = player2Doc.data()?.fullName || 'Player 2';
document.getElementById('player2Avatar').src = player2Doc.data()?.avatar || 'https://cdn-icons-png.flaticon.com/512/4333/4333609.png';
document.getElementById('player2Score').textContent = battleData.scores.player2 || 0;
} catch (error) {
console.error("Error updating player info:", error);
Toastify({ text: "প্লেয়ার তথ্য লোডে ত্রুটি: " + error.message, duration: 3000, style: { background: "#dc3545" } }).showToast();
}
}
populateSubjects();
</script>
</body>
</html>