Spaces:
Running
Running
can you make the exam page have like this border red because in this app we will have eye tracker use and that red border is the edge and make tghis on exam pc layout
Browse files- README.md +8 -5
- app.js +493 -0
- index.html +206 -19
- styles.css +70 -0
README.md
CHANGED
|
@@ -1,10 +1,13 @@
|
|
| 1 |
---
|
| 2 |
-
title:
|
| 3 |
-
|
| 4 |
-
|
| 5 |
-
|
| 6 |
sdk: static
|
| 7 |
pinned: false
|
|
|
|
|
|
|
| 8 |
---
|
| 9 |
|
| 10 |
-
|
|
|
|
|
|
| 1 |
---
|
| 2 |
+
title: ExamProctor EyeTrack πποΈ
|
| 3 |
+
colorFrom: green
|
| 4 |
+
colorTo: purple
|
| 5 |
+
emoji: π³
|
| 6 |
sdk: static
|
| 7 |
pinned: false
|
| 8 |
+
tags:
|
| 9 |
+
- deepsite-v3
|
| 10 |
---
|
| 11 |
|
| 12 |
+
# Welcome to your new DeepSite project!
|
| 13 |
+
This project was created with [DeepSite](https://huggingface.co/deepsite).
|
app.js
ADDED
|
@@ -0,0 +1,493 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
// App State
|
| 2 |
+
const state = {
|
| 3 |
+
isLoggedIn: false,
|
| 4 |
+
currentStudent: null,
|
| 5 |
+
exams: [
|
| 6 |
+
{
|
| 7 |
+
id: 1,
|
| 8 |
+
subject: "Mathematics",
|
| 9 |
+
date: "2023-06-15",
|
| 10 |
+
duration: 30, // minutes
|
| 11 |
+
questions: [
|
| 12 |
+
{
|
| 13 |
+
id: 1,
|
| 14 |
+
type: "multiple",
|
| 15 |
+
text: "What is the derivative of xΒ²?",
|
| 16 |
+
options: ["x", "2x", "xΒ²", "2xΒ²"],
|
| 17 |
+
answer: 1
|
| 18 |
+
},
|
| 19 |
+
{
|
| 20 |
+
id: 2,
|
| 21 |
+
type: "multiple",
|
| 22 |
+
text: "What is the value of Ο (pi) to two decimal places?",
|
| 23 |
+
options: ["3.14", "3.16", "3.12", "3.18"],
|
| 24 |
+
answer: 0
|
| 25 |
+
},
|
| 26 |
+
{
|
| 27 |
+
id: 3,
|
| 28 |
+
type: "short",
|
| 29 |
+
text: "Solve for x: 2x + 5 = 15",
|
| 30 |
+
answer: "5"
|
| 31 |
+
},
|
| 32 |
+
{
|
| 33 |
+
id: 4,
|
| 34 |
+
type: "multiple",
|
| 35 |
+
text: "Which of these is a prime number?",
|
| 36 |
+
options: ["4", "9", "11", "15"],
|
| 37 |
+
answer: 2
|
| 38 |
+
},
|
| 39 |
+
{
|
| 40 |
+
id: 5,
|
| 41 |
+
type: "short",
|
| 42 |
+
text: "What is the area of a circle with radius 3? (Use Ο β 3.14)",
|
| 43 |
+
answer: "28.26"
|
| 44 |
+
}
|
| 45 |
+
]
|
| 46 |
+
},
|
| 47 |
+
{
|
| 48 |
+
id: 2,
|
| 49 |
+
subject: "Physics",
|
| 50 |
+
date: "2023-06-20",
|
| 51 |
+
duration: 45,
|
| 52 |
+
questions: [
|
| 53 |
+
{
|
| 54 |
+
id: 1,
|
| 55 |
+
type: "multiple",
|
| 56 |
+
text: "What is the SI unit of force?",
|
| 57 |
+
options: ["Joule", "Watt", "Newton", "Pascal"],
|
| 58 |
+
answer: 2
|
| 59 |
+
},
|
| 60 |
+
{
|
| 61 |
+
id: 2,
|
| 62 |
+
type: "short",
|
| 63 |
+
text: "What is the acceleration due to gravity on Earth (in m/sΒ²)?",
|
| 64 |
+
answer: "9.8"
|
| 65 |
+
}
|
| 66 |
+
]
|
| 67 |
+
}
|
| 68 |
+
],
|
| 69 |
+
currentExam: null,
|
| 70 |
+
examStartTime: null,
|
| 71 |
+
examTimer: null,
|
| 72 |
+
suspiciousActivities: 0,
|
| 73 |
+
submissions: []
|
| 74 |
+
};
|
| 75 |
+
|
| 76 |
+
// DOM Elements
|
| 77 |
+
const loginScreen = document.getElementById('login-screen');
|
| 78 |
+
const appScreens = document.getElementById('app-screens');
|
| 79 |
+
const loginForm = document.getElementById('login-form');
|
| 80 |
+
const forgotPasswordBtn = document.getElementById('forgot-password');
|
| 81 |
+
const forgotModal = document.getElementById('forgot-modal');
|
| 82 |
+
const resetConfirmation = document.getElementById('reset-confirmation');
|
| 83 |
+
const studentName = document.getElementById('student-name');
|
| 84 |
+
const studentAvatar = document.getElementById('student-avatar');
|
| 85 |
+
const statusBadge = document.getElementById('status-badge');
|
| 86 |
+
const examList = document.getElementById('exam-list');
|
| 87 |
+
const examScreen = document.getElementById('exam-screen');
|
| 88 |
+
const examQuestions = document.getElementById('exam-questions');
|
| 89 |
+
const examTitle = document.getElementById('exam-title');
|
| 90 |
+
const examTimer = document.getElementById('exam-timer');
|
| 91 |
+
const examProgress = document.getElementById('exam-progress');
|
| 92 |
+
const submitExamBtn = document.getElementById('submit-exam');
|
| 93 |
+
const submissionList = document.getElementById('submission-list');
|
| 94 |
+
const logoutBtn = document.getElementById('logout-btn');
|
| 95 |
+
const navButtons = document.querySelectorAll('.nav-btn');
|
| 96 |
+
|
| 97 |
+
// Initialize the app
|
| 98 |
+
function init() {
|
| 99 |
+
checkAuth();
|
| 100 |
+
setupEventListeners();
|
| 101 |
+
renderExamList();
|
| 102 |
+
loadSubmissions();
|
| 103 |
+
}
|
| 104 |
+
|
| 105 |
+
// Check if user is logged in
|
| 106 |
+
function checkAuth() {
|
| 107 |
+
const student = localStorage.getItem('examGuardStudent');
|
| 108 |
+
if (student) {
|
| 109 |
+
state.isLoggedIn = true;
|
| 110 |
+
state.currentStudent = JSON.parse(student);
|
| 111 |
+
showApp();
|
| 112 |
+
} else {
|
| 113 |
+
showLogin();
|
| 114 |
+
}
|
| 115 |
+
}
|
| 116 |
+
|
| 117 |
+
// Show login screen
|
| 118 |
+
function showLogin() {
|
| 119 |
+
loginScreen.classList.remove('hidden');
|
| 120 |
+
appScreens.classList.add('hidden');
|
| 121 |
+
state.isLoggedIn = false;
|
| 122 |
+
}
|
| 123 |
+
|
| 124 |
+
// Show main app
|
| 125 |
+
function showApp() {
|
| 126 |
+
loginScreen.classList.add('hidden');
|
| 127 |
+
appScreens.classList.remove('hidden');
|
| 128 |
+
|
| 129 |
+
// Update student info
|
| 130 |
+
studentName.textContent = state.currentStudent?.name || 'Student';
|
| 131 |
+
studentAvatar.src = `https://static.photos/people/120x120/${state.currentStudent?.id || 1}`;
|
| 132 |
+
|
| 133 |
+
// Show dashboard by default
|
| 134 |
+
showScreen('dashboard');
|
| 135 |
+
updateStatusBadge();
|
| 136 |
+
}
|
| 137 |
+
|
| 138 |
+
// Show a specific screen
|
| 139 |
+
function showScreen(screenId) {
|
| 140 |
+
// Hide all screens
|
| 141 |
+
document.querySelectorAll('[id$="-screen"]').forEach(screen => {
|
| 142 |
+
screen.classList.add('hidden');
|
| 143 |
+
});
|
| 144 |
+
|
| 145 |
+
// Show requested screen
|
| 146 |
+
document.getElementById(`${screenId}-screen`)?.classList.remove('hidden');
|
| 147 |
+
|
| 148 |
+
// Update active nav button
|
| 149 |
+
navButtons.forEach(btn => {
|
| 150 |
+
if (btn.dataset.screen === screenId) {
|
| 151 |
+
btn.classList.add('text-indigo-600');
|
| 152 |
+
btn.classList.remove('text-gray-500');
|
| 153 |
+
} else {
|
| 154 |
+
btn.classList.remove('text-indigo-600');
|
| 155 |
+
btn.classList.add('text-gray-500');
|
| 156 |
+
}
|
| 157 |
+
});
|
| 158 |
+
}
|
| 159 |
+
|
| 160 |
+
// Update status badge
|
| 161 |
+
function updateStatusBadge() {
|
| 162 |
+
const pendingExams = state.exams.filter(exam =>
|
| 163 |
+
!state.submissions.some(sub => sub.examId === exam.id)
|
| 164 |
+
);
|
| 165 |
+
|
| 166 |
+
if (pendingExams.length > 0) {
|
| 167 |
+
statusBadge.textContent = `${pendingExams.length} pending exam${pendingExams.length > 1 ? 's' : ''}`;
|
| 168 |
+
statusBadge.className = 'text-xs px-2 py-1 bg-amber-100 text-amber-800 rounded-full';
|
| 169 |
+
} else {
|
| 170 |
+
statusBadge.textContent = 'No active exams';
|
| 171 |
+
statusBadge.className = 'text-xs px-2 py-1 bg-emerald-100 text-emerald-800 rounded-full';
|
| 172 |
+
}
|
| 173 |
+
}
|
| 174 |
+
|
| 175 |
+
// Render exam list
|
| 176 |
+
function renderExamList() {
|
| 177 |
+
examList.innerHTML = '';
|
| 178 |
+
|
| 179 |
+
state.exams.forEach(exam => {
|
| 180 |
+
const isSubmitted = state.submissions.some(sub => sub.examId === exam.id);
|
| 181 |
+
|
| 182 |
+
const examCard = document.createElement('div');
|
| 183 |
+
examCard.className = 'bg-white rounded-xl shadow-sm p-4';
|
| 184 |
+
examCard.innerHTML = `
|
| 185 |
+
<div class="flex justify-between items-start">
|
| 186 |
+
<div>
|
| 187 |
+
<h3 class="font-medium text-gray-800">${exam.subject}</h3>
|
| 188 |
+
<p class="text-sm text-gray-500">${new Date(exam.date).toLocaleDateString()} β’ ${exam.duration} min</p>
|
| 189 |
+
</div>
|
| 190 |
+
${isSubmitted ?
|
| 191 |
+
'<span class="text-xs px-2 py-1 bg-green-100 text-green-800 rounded-full">Submitted</span>' :
|
| 192 |
+
`<button data-exam-id="${exam.id}" class="take-exam-btn text-sm bg-indigo-600 text-white px-3 py-1 rounded-lg hover:bg-indigo-700">
|
| 193 |
+
Take Exam
|
| 194 |
+
</button>`
|
| 195 |
+
}
|
| 196 |
+
</div>
|
| 197 |
+
`;
|
| 198 |
+
|
| 199 |
+
examList.appendChild(examCard);
|
| 200 |
+
});
|
| 201 |
+
|
| 202 |
+
// Add event listeners to take exam buttons
|
| 203 |
+
document.querySelectorAll('.take-exam-btn').forEach(btn => {
|
| 204 |
+
btn.addEventListener('click', () => {
|
| 205 |
+
const examId = parseInt(btn.dataset.examId);
|
| 206 |
+
startExam(examId);
|
| 207 |
+
});
|
| 208 |
+
});
|
| 209 |
+
}
|
| 210 |
+
|
| 211 |
+
// Start an exam
|
| 212 |
+
function startExam(examId) {
|
| 213 |
+
const exam = state.exams.find(e => e.id === examId);
|
| 214 |
+
if (!exam) return;
|
| 215 |
+
|
| 216 |
+
state.currentExam = exam;
|
| 217 |
+
state.examStartTime = new Date();
|
| 218 |
+
state.suspiciousActivities = 0;
|
| 219 |
+
|
| 220 |
+
// Setup exam UI
|
| 221 |
+
examTitle.textContent = exam.subject;
|
| 222 |
+
examQuestions.innerHTML = '';
|
| 223 |
+
|
| 224 |
+
// Render questions
|
| 225 |
+
exam.questions.forEach((q, index) => {
|
| 226 |
+
const questionCard = document.createElement('div');
|
| 227 |
+
questionCard.className = 'question-card bg-white rounded-xl shadow-sm p-4';
|
| 228 |
+
|
| 229 |
+
if (q.type === 'multiple') {
|
| 230 |
+
questionCard.innerHTML = `
|
| 231 |
+
<h4 class="font-medium mb-3">${index + 1}. ${q.text}</h4>
|
| 232 |
+
<div class="space-y-2">
|
| 233 |
+
${q.options.map((opt, i) => `
|
| 234 |
+
<label class="flex items-center space-x-3 cursor-pointer">
|
| 235 |
+
<input type="radio" name="q${q.id}" value="${i}" class="w-4 h-4 text-indigo-600">
|
| 236 |
+
<span>${opt}</span>
|
| 237 |
+
</label>
|
| 238 |
+
`).join('')}
|
| 239 |
+
</div>
|
| 240 |
+
`;
|
| 241 |
+
} else {
|
| 242 |
+
questionCard.innerHTML = `
|
| 243 |
+
<h4 class="font-medium mb-3">${index + 1}. ${q.text}</h4>
|
| 244 |
+
<input type="text" name="q${q.id}"
|
| 245 |
+
class="w-full px-4 py-2 border border-gray-300 rounded-lg">
|
| 246 |
+
`;
|
| 247 |
+
}
|
| 248 |
+
|
| 249 |
+
examQuestions.appendChild(questionCard);
|
| 250 |
+
});
|
| 251 |
+
|
| 252 |
+
// Start timer
|
| 253 |
+
const durationInSeconds = exam.duration * 60;
|
| 254 |
+
let remainingTime = durationInSeconds;
|
| 255 |
+
|
| 256 |
+
updateTimerDisplay(remainingTime);
|
| 257 |
+
|
| 258 |
+
state.examTimer = setInterval(() => {
|
| 259 |
+
remainingTime--;
|
| 260 |
+
updateTimerDisplay(remainingTime);
|
| 261 |
+
|
| 262 |
+
if (remainingTime <= 0) {
|
| 263 |
+
clearInterval(state.examTimer);
|
| 264 |
+
submitExam();
|
| 265 |
+
}
|
| 266 |
+
}, 1000);
|
| 267 |
+
|
| 268 |
+
// Setup anti-cheat
|
| 269 |
+
setupAntiCheat();
|
| 270 |
+
|
| 271 |
+
// Show exam screen
|
| 272 |
+
showScreen('exam');
|
| 273 |
+
}
|
| 274 |
+
|
| 275 |
+
// Update timer display
|
| 276 |
+
function updateTimerDisplay(seconds) {
|
| 277 |
+
const mins = Math.floor(seconds / 60);
|
| 278 |
+
const secs = seconds % 60;
|
| 279 |
+
examTimer.textContent = `${mins}:${secs < 10 ? '0' + secs : secs}`;
|
| 280 |
+
|
| 281 |
+
if (seconds < 60) {
|
| 282 |
+
examTimer.classList.add('timer-urgent');
|
| 283 |
+
} else {
|
| 284 |
+
examTimer.classList.remove('timer-urgent');
|
| 285 |
+
}
|
| 286 |
+
|
| 287 |
+
// Update progress bar
|
| 288 |
+
const examDuration = state.currentExam.duration * 60;
|
| 289 |
+
const percent = (seconds / examDuration) * 100;
|
| 290 |
+
examProgress.style.width = `${percent}%`;
|
| 291 |
+
}
|
| 292 |
+
// Setup anti-cheat measures
|
| 293 |
+
function setupAntiCheat() {
|
| 294 |
+
// Add exam layout class
|
| 295 |
+
document.getElementById('app').classList.add('exam-layout');
|
| 296 |
+
// Detect tab switching
|
| 297 |
+
document.addEventListener('visibilitychange', () => {
|
| 298 |
+
if (document.visibilityState !== 'visible') {
|
| 299 |
+
handleSuspiciousActivity('Tab switched');
|
| 300 |
+
}
|
| 301 |
+
});
|
| 302 |
+
|
| 303 |
+
// Disable right click
|
| 304 |
+
document.addEventListener('contextmenu', (e) => {
|
| 305 |
+
e.preventDefault();
|
| 306 |
+
handleSuspiciousActivity('Right click attempt');
|
| 307 |
+
});
|
| 308 |
+
|
| 309 |
+
// Disable text selection
|
| 310 |
+
document.addEventListener('selectstart', (e) => {
|
| 311 |
+
e.preventDefault();
|
| 312 |
+
handleSuspiciousActivity('Text selection attempt');
|
| 313 |
+
});
|
| 314 |
+
|
| 315 |
+
// Disable copy/paste
|
| 316 |
+
document.addEventListener('copy', (e) => {
|
| 317 |
+
e.preventDefault();
|
| 318 |
+
handleSuspiciousActivity('Copy attempt');
|
| 319 |
+
});
|
| 320 |
+
|
| 321 |
+
document.addEventListener('paste', (e) => {
|
| 322 |
+
e.preventDefault();
|
| 323 |
+
handleSuspiciousActivity('Paste attempt');
|
| 324 |
+
});
|
| 325 |
+
|
| 326 |
+
// Try to access camera
|
| 327 |
+
if (navigator.mediaDevices && navigator.mediaDevices.getUserMedia) {
|
| 328 |
+
navigator.mediaDevices.getUserMedia({ video: true })
|
| 329 |
+
.then(stream => {
|
| 330 |
+
// Camera access granted
|
| 331 |
+
console.log('Camera access granted for monitoring');
|
| 332 |
+
})
|
| 333 |
+
.catch(err => {
|
| 334 |
+
console.log('Camera access denied or not available');
|
| 335 |
+
});
|
| 336 |
+
}
|
| 337 |
+
}
|
| 338 |
+
|
| 339 |
+
// Handle suspicious activity
|
| 340 |
+
function handleSuspiciousActivity(reason) {
|
| 341 |
+
state.suspiciousActivities++;
|
| 342 |
+
|
| 343 |
+
// Show warning
|
| 344 |
+
const warning = document.createElement('div');
|
| 345 |
+
warning.className = 'warning-flash fixed top-4 left-1/2 transform -translate-x-1/2 bg-white shadow-md rounded-lg px-4 py-2 z-50';
|
| 346 |
+
warning.textContent = `β οΈ Suspicious activity detected: ${reason}`;
|
| 347 |
+
document.body.appendChild(warning);
|
| 348 |
+
|
| 349 |
+
setTimeout(() => {
|
| 350 |
+
warning.remove();
|
| 351 |
+
}, 3000);
|
| 352 |
+
}
|
| 353 |
+
// Submit exam
|
| 354 |
+
function submitExam() {
|
| 355 |
+
// Remove exam layout class
|
| 356 |
+
document.getElementById('app').classList.remove('exam-layout');
|
| 357 |
+
if (!state.currentExam) return;
|
| 358 |
+
|
| 359 |
+
clearInterval(state.examTimer);
|
| 360 |
+
|
| 361 |
+
// Create submission
|
| 362 |
+
const submission = {
|
| 363 |
+
examId: state.currentExam.id,
|
| 364 |
+
subject: state.currentExam.subject,
|
| 365 |
+
submittedAt: new Date().toISOString(),
|
| 366 |
+
suspiciousCount: state.suspiciousActivities
|
| 367 |
+
};
|
| 368 |
+
|
| 369 |
+
// Save to state and localStorage
|
| 370 |
+
state.submissions.push(submission);
|
| 371 |
+
localStorage.setItem('examGuardSubmissions', JSON.stringify(state.submissions));
|
| 372 |
+
|
| 373 |
+
// Update UI
|
| 374 |
+
updateStatusBadge();
|
| 375 |
+
renderExamList();
|
| 376 |
+
loadSubmissions();
|
| 377 |
+
|
| 378 |
+
// Show pending screen
|
| 379 |
+
showScreen('pending');
|
| 380 |
+
}
|
| 381 |
+
|
| 382 |
+
// Load submissions from localStorage
|
| 383 |
+
function loadSubmissions() {
|
| 384 |
+
const savedSubmissions = localStorage.getItem('examGuardSubmissions');
|
| 385 |
+
if (savedSubmissions) {
|
| 386 |
+
state.submissions = JSON.parse(savedSubmissions);
|
| 387 |
+
}
|
| 388 |
+
|
| 389 |
+
renderSubmissions();
|
| 390 |
+
}
|
| 391 |
+
|
| 392 |
+
// Render submissions list
|
| 393 |
+
function renderSubmissions() {
|
| 394 |
+
submissionList.innerHTML = '';
|
| 395 |
+
|
| 396 |
+
if (state.submissions.length === 0) {
|
| 397 |
+
submissionList.innerHTML = '<p class="text-gray-500 text-center py-4">No submissions yet</p>';
|
| 398 |
+
return;
|
| 399 |
+
}
|
| 400 |
+
|
| 401 |
+
state.submissions.forEach(sub => {
|
| 402 |
+
const subCard = document.createElement('div');
|
| 403 |
+
subCard.className = 'bg-white rounded-xl shadow-sm p-4';
|
| 404 |
+
subCard.innerHTML = `
|
| 405 |
+
<div class="flex justify-between items-start">
|
| 406 |
+
<div>
|
| 407 |
+
<h3 class="font-medium text-gray-800">${sub.subject}</h3>
|
| 408 |
+
<p class="text-sm text-gray-500">Submitted: ${new Date(sub.submittedAt).toLocaleString()}</p>
|
| 409 |
+
</div>
|
| 410 |
+
<div class="text-right">
|
| 411 |
+
<span class="block text-xs px-2 py-1 bg-blue-100 text-blue-800 rounded-full mb-1">Pending Review</span>
|
| 412 |
+
${sub.suspiciousCount > 0 ?
|
| 413 |
+
`<span class="text-xs px-2 py-1 bg-red-100 text-red-800 rounded-full">
|
| 414 |
+
${sub.suspiciousCount} suspicious event${sub.suspiciousCount > 1 ? 's' : ''}
|
| 415 |
+
</span>` :
|
| 416 |
+
`<span class="text-xs px-2 py-1 bg-green-100 text-green-800 rounded-full">
|
| 417 |
+
No issues detected
|
| 418 |
+
</span>`
|
| 419 |
+
}
|
| 420 |
+
</div>
|
| 421 |
+
</div>
|
| 422 |
+
`;
|
| 423 |
+
|
| 424 |
+
submissionList.appendChild(subCard);
|
| 425 |
+
});
|
| 426 |
+
}
|
| 427 |
+
|
| 428 |
+
// Setup event listeners
|
| 429 |
+
function setupEventListeners() {
|
| 430 |
+
// Login form
|
| 431 |
+
loginForm.addEventListener('submit', (e) => {
|
| 432 |
+
e.preventDefault();
|
| 433 |
+
|
| 434 |
+
const studentId = document.getElementById('student-id').value;
|
| 435 |
+
const password = document.getElementById('password').value;
|
| 436 |
+
|
| 437 |
+
// Mock authentication
|
| 438 |
+
state.currentStudent = {
|
| 439 |
+
id: studentId,
|
| 440 |
+
name: `Student ${studentId}`,
|
| 441 |
+
email: `student${studentId}@example.com`
|
| 442 |
+
};
|
| 443 |
+
|
| 444 |
+
// Save to localStorage
|
| 445 |
+
localStorage.setItem('examGuardStudent', JSON.stringify(state.currentStudent));
|
| 446 |
+
|
| 447 |
+
// Show app
|
| 448 |
+
showApp();
|
| 449 |
+
});
|
| 450 |
+
|
| 451 |
+
// Forgot password
|
| 452 |
+
forgotPasswordBtn.addEventListener('click', () => {
|
| 453 |
+
forgotModal.classList.remove('hidden');
|
| 454 |
+
});
|
| 455 |
+
|
| 456 |
+
// Cancel reset
|
| 457 |
+
document.getElementById('cancel-reset').addEventListener('click', () => {
|
| 458 |
+
forgotModal.classList.add('hidden');
|
| 459 |
+
});
|
| 460 |
+
|
| 461 |
+
// Submit reset
|
| 462 |
+
document.getElementById('submit-reset').addEventListener('click', () => {
|
| 463 |
+
const email = document.getElementById('reset-email').value;
|
| 464 |
+
if (email) {
|
| 465 |
+
forgotModal.classList.add('hidden');
|
| 466 |
+
resetConfirmation.classList.remove('hidden');
|
| 467 |
+
}
|
| 468 |
+
});
|
| 469 |
+
|
| 470 |
+
// Close confirmation
|
| 471 |
+
document.getElementById('close-confirmation').addEventListener('click', () => {
|
| 472 |
+
resetConfirmation.classList.add('hidden');
|
| 473 |
+
});
|
| 474 |
+
|
| 475 |
+
// Navigation buttons
|
| 476 |
+
navButtons.forEach(btn => {
|
| 477 |
+
btn.addEventListener('click', () => {
|
| 478 |
+
showScreen(btn.dataset.screen);
|
| 479 |
+
});
|
| 480 |
+
});
|
| 481 |
+
|
| 482 |
+
// Submit exam button
|
| 483 |
+
submitExamBtn.addEventListener('click', submitExam);
|
| 484 |
+
|
| 485 |
+
// Logout button
|
| 486 |
+
logoutBtn.addEventListener('click', () => {
|
| 487 |
+
localStorage.removeItem('examGuardStudent');
|
| 488 |
+
showLogin();
|
| 489 |
+
});
|
| 490 |
+
}
|
| 491 |
+
|
| 492 |
+
// Initialize the app when DOM is loaded
|
| 493 |
+
document.addEventListener('DOMContentLoaded', init);
|
index.html
CHANGED
|
@@ -1,19 +1,206 @@
|
|
| 1 |
-
<!
|
| 2 |
-
<html>
|
| 3 |
-
|
| 4 |
-
|
| 5 |
-
|
| 6 |
-
|
| 7 |
-
|
| 8 |
-
|
| 9 |
-
|
| 10 |
-
|
| 11 |
-
|
| 12 |
-
|
| 13 |
-
|
| 14 |
-
|
| 15 |
-
|
| 16 |
-
|
| 17 |
-
|
| 18 |
-
|
| 19 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<!DOCTYPE html>
|
| 2 |
+
<html lang="en">
|
| 3 |
+
<head>
|
| 4 |
+
<meta charset="UTF-8">
|
| 5 |
+
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
|
| 6 |
+
<title>Nova ExamGuard</title>
|
| 7 |
+
<link rel="stylesheet" href="styles.css">
|
| 8 |
+
<script src="https://cdn.tailwindcss.com"></script>
|
| 9 |
+
<script src="https://cdn.jsdelivr.net/npm/feather-icons/dist/feather.min.js"></script>
|
| 10 |
+
<script src="https://unpkg.com/feather-icons"></script>
|
| 11 |
+
<style>
|
| 12 |
+
:root {
|
| 13 |
+
--primary: #4f46e5;
|
| 14 |
+
--secondary: #10b981;
|
| 15 |
+
}
|
| 16 |
+
</style>
|
| 17 |
+
</head>
|
| 18 |
+
<body class="bg-gray-50 min-h-screen font-sans">
|
| 19 |
+
<div id="app" class="max-w-md mx-auto relative overflow-hidden exam-layout">
|
| 20 |
+
<!-- Login Screen -->
|
| 21 |
+
<section id="login-screen" class="p-6 h-screen flex flex-col justify-center">
|
| 22 |
+
<div class="bg-white rounded-xl shadow-md p-6">
|
| 23 |
+
<div class="text-center mb-8">
|
| 24 |
+
<h1 class="text-2xl font-bold text-gray-800 mb-2">Nova ExamGuard</h1>
|
| 25 |
+
<p class="text-gray-600">Secure exam platform</p>
|
| 26 |
+
</div>
|
| 27 |
+
|
| 28 |
+
<form id="login-form" class="space-y-4">
|
| 29 |
+
<div>
|
| 30 |
+
<label for="student-id" class="block text-sm font-medium text-gray-700 mb-1">Student ID</label>
|
| 31 |
+
<input type="text" id="student-id" required
|
| 32 |
+
class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500">
|
| 33 |
+
</div>
|
| 34 |
+
|
| 35 |
+
<div>
|
| 36 |
+
<label for="password" class="block text-sm font-medium text-gray-700 mb-1">Password</label>
|
| 37 |
+
<input type="password" id="password" required
|
| 38 |
+
class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500">
|
| 39 |
+
</div>
|
| 40 |
+
|
| 41 |
+
<button type="submit" class="w-full bg-indigo-600 text-white py-2 px-4 rounded-lg hover:bg-indigo-700 transition">
|
| 42 |
+
Login
|
| 43 |
+
</button>
|
| 44 |
+
|
| 45 |
+
<div class="text-center">
|
| 46 |
+
<button type="button" id="forgot-password" class="text-sm text-indigo-600 hover:text-indigo-800">
|
| 47 |
+
Forgot Password?
|
| 48 |
+
</button>
|
| 49 |
+
</div>
|
| 50 |
+
</form>
|
| 51 |
+
</div>
|
| 52 |
+
</section>
|
| 53 |
+
|
| 54 |
+
<!-- Forgot Password Modal -->
|
| 55 |
+
<div id="forgot-modal" class="hidden fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center p-4 z-50">
|
| 56 |
+
<div class="bg-white rounded-xl p-6 w-full max-w-sm">
|
| 57 |
+
<h3 class="text-lg font-medium mb-4">Reset Password</h3>
|
| 58 |
+
<input type="email" id="reset-email" placeholder="Enter your email"
|
| 59 |
+
class="w-full px-4 py-2 border border-gray-300 rounded-lg mb-4">
|
| 60 |
+
<div class="flex justify-end space-x-3">
|
| 61 |
+
<button id="cancel-reset" class="px-4 py-2 text-gray-600 rounded-lg">Cancel</button>
|
| 62 |
+
<button id="submit-reset" class="px-4 py-2 bg-indigo-600 text-white rounded-lg">Submit</button>
|
| 63 |
+
</div>
|
| 64 |
+
</div>
|
| 65 |
+
</div>
|
| 66 |
+
|
| 67 |
+
<!-- Reset Confirmation -->
|
| 68 |
+
<div id="reset-confirmation" class="hidden fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center p-4 z-50">
|
| 69 |
+
<div class="bg-white rounded-xl p-6 w-full max-w-sm">
|
| 70 |
+
<h3 class="text-lg font-medium mb-4">Reset Link Sent</h3>
|
| 71 |
+
<p class="text-gray-600 mb-4">Password reset link has been sent to your email (simulation only).</p>
|
| 72 |
+
<button id="close-confirmation" class="w-full px-4 py-2 bg-indigo-600 text-white rounded-lg">OK</button>
|
| 73 |
+
</div>
|
| 74 |
+
</div>
|
| 75 |
+
|
| 76 |
+
<!-- Main App Screens (hidden initially) -->
|
| 77 |
+
<div id="app-screens" class="hidden h-screen flex flex-col">
|
| 78 |
+
<!-- Header -->
|
| 79 |
+
<header class="bg-white shadow-sm p-4">
|
| 80 |
+
<div class="flex items-center justify-between">
|
| 81 |
+
<div class="flex items-center space-x-3">
|
| 82 |
+
<img id="student-avatar" src="https://static.photos/people/120x120/1" alt="Student" class="w-10 h-10 rounded-full">
|
| 83 |
+
<div>
|
| 84 |
+
<h2 id="student-name" class="font-medium text-gray-800">Student</h2>
|
| 85 |
+
<span id="status-badge" class="text-xs px-2 py-1 bg-emerald-100 text-emerald-800 rounded-full">No active exam</span>
|
| 86 |
+
</div>
|
| 87 |
+
</div>
|
| 88 |
+
</div>
|
| 89 |
+
</header>
|
| 90 |
+
|
| 91 |
+
<!-- Main Content -->
|
| 92 |
+
<main id="main-content" class="flex-1 overflow-y-auto p-4 bg-gray-50">
|
| 93 |
+
<!-- Dashboard Screen -->
|
| 94 |
+
<section id="dashboard-screen" class="space-y-6">
|
| 95 |
+
<div class="bg-white rounded-xl shadow-sm p-6">
|
| 96 |
+
<h3 class="text-lg font-medium mb-4">Upcoming Exams</h3>
|
| 97 |
+
<div class="space-y-4">
|
| 98 |
+
<div class="border border-gray-200 rounded-lg p-4">
|
| 99 |
+
<h4 class="font-medium">Mathematics Final</h4>
|
| 100 |
+
<p class="text-sm text-gray-500">Tomorrow at 9:00 AM</p>
|
| 101 |
+
</div>
|
| 102 |
+
</div>
|
| 103 |
+
</div>
|
| 104 |
+
</section>
|
| 105 |
+
|
| 106 |
+
<!-- Exams Screen -->
|
| 107 |
+
<section id="exams-screen" class="hidden space-y-4">
|
| 108 |
+
<h2 class="text-xl font-bold text-gray-800 mb-4">Available Exams</h2>
|
| 109 |
+
<div id="exam-list" class="space-y-3"></div>
|
| 110 |
+
</section>
|
| 111 |
+
|
| 112 |
+
<!-- Take Exam Screen -->
|
| 113 |
+
<section id="exam-screen" class="hidden">
|
| 114 |
+
<div class="bg-white rounded-xl shadow-sm p-4 mb-4">
|
| 115 |
+
<div class="flex justify-between items-center mb-2">
|
| 116 |
+
<h3 id="exam-title" class="text-lg font-medium">Mathematics Final</h3>
|
| 117 |
+
<span id="exam-timer" class="bg-red-100 text-red-800 px-3 py-1 rounded-full text-sm">30:00</span>
|
| 118 |
+
</div>
|
| 119 |
+
|
| 120 |
+
<div class="relative h-2 bg-gray-200 rounded-full mb-4">
|
| 121 |
+
<div id="exam-progress" class="absolute top-0 left-0 h-full bg-indigo-600 rounded-full" style="width: 100%"></div>
|
| 122 |
+
</div>
|
| 123 |
+
</div>
|
| 124 |
+
|
| 125 |
+
<div id="exam-questions" class="space-y-6"></div>
|
| 126 |
+
|
| 127 |
+
<div class="fixed bottom-4 left-0 right-0 px-4">
|
| 128 |
+
<button id="submit-exam" class="w-full bg-indigo-600 text-white py-3 px-4 rounded-lg shadow-md hover:bg-indigo-700">
|
| 129 |
+
Submit Exam
|
| 130 |
+
</button>
|
| 131 |
+
</div>
|
| 132 |
+
</section>
|
| 133 |
+
|
| 134 |
+
<!-- Pending Screen -->
|
| 135 |
+
<section id="pending-screen" class="hidden">
|
| 136 |
+
<h2 class="text-xl font-bold text-gray-800 mb-4">Pending Submissions</h2>
|
| 137 |
+
<div id="submission-list" class="space-y-3"></div>
|
| 138 |
+
</section>
|
| 139 |
+
|
| 140 |
+
<!-- Profile Screen -->
|
| 141 |
+
<section id="profile-screen" class="hidden space-y-6">
|
| 142 |
+
<div class="bg-white rounded-xl shadow-sm p-6">
|
| 143 |
+
<div class="flex flex-col items-center mb-6">
|
| 144 |
+
<img src="https://static.photos/people/120x120/1" alt="Student" class="w-20 h-20 rounded-full mb-3">
|
| 145 |
+
<h3 id="profile-name" class="text-lg font-medium">John Doe</h3>
|
| 146 |
+
<p id="profile-id" class="text-sm text-gray-500">ID: 12345</p>
|
| 147 |
+
</div>
|
| 148 |
+
|
| 149 |
+
<div class="space-y-4">
|
| 150 |
+
<div>
|
| 151 |
+
<label class="block text-sm font-medium text-gray-700 mb-1">Email</label>
|
| 152 |
+
<input type="email" id="profile-email" value="student@example.com" class="w-full px-4 py-2 border border-gray-300 rounded-lg">
|
| 153 |
+
</div>
|
| 154 |
+
|
| 155 |
+
<div>
|
| 156 |
+
<label class="block text-sm font-medium text-gray-700 mb-1">Change Password</label>
|
| 157 |
+
<input type="password" placeholder="New password" class="w-full px-4 py-2 border border-gray-300 rounded-lg">
|
| 158 |
+
</div>
|
| 159 |
+
|
| 160 |
+
<button class="w-full bg-indigo-600 text-white py-2 px-4 rounded-lg hover:bg-indigo-700">
|
| 161 |
+
Save Changes
|
| 162 |
+
</button>
|
| 163 |
+
</div>
|
| 164 |
+
</div>
|
| 165 |
+
|
| 166 |
+
<button id="logout-btn" class="w-full bg-red-600 text-white py-2 px-4 rounded-lg hover:bg-red-700">
|
| 167 |
+
Logout
|
| 168 |
+
</button>
|
| 169 |
+
</section>
|
| 170 |
+
</main>
|
| 171 |
+
|
| 172 |
+
<!-- Bottom Navigation -->
|
| 173 |
+
<nav class="bg-white border-t border-gray-200 p-2">
|
| 174 |
+
<div class="flex justify-around">
|
| 175 |
+
<button data-screen="dashboard" class="nav-btn flex flex-col items-center text-indigo-600 p-2">
|
| 176 |
+
<i data-feather="home" class="w-5 h-5"></i>
|
| 177 |
+
<span class="text-xs mt-1">Home</span>
|
| 178 |
+
</button>
|
| 179 |
+
<button data-screen="exams" class="nav-btn flex flex-col items-center text-gray-500 p-2">
|
| 180 |
+
<i data-feather="book" class="w-5 h-5"></i>
|
| 181 |
+
<span class="text-xs mt-1">Exams</span>
|
| 182 |
+
</button>
|
| 183 |
+
<button data-screen="take-exam" class="nav-btn flex flex-col items-center text-gray-500 p-2">
|
| 184 |
+
<i data-feather="edit-3" class="w-5 h-5"></i>
|
| 185 |
+
<span class="text-xs mt-1">Take Exam</span>
|
| 186 |
+
</button>
|
| 187 |
+
<button data-screen="pending" class="nav-btn flex flex-col items-center text-gray-500 p-2">
|
| 188 |
+
<i data-feather="clock" class="w-5 h-5"></i>
|
| 189 |
+
<span class="text-xs mt-1">Pending</span>
|
| 190 |
+
</button>
|
| 191 |
+
<button data-screen="profile" class="nav-btn flex flex-col items-center text-gray-500 p-2">
|
| 192 |
+
<i data-feather="user" class="w-5 h-5"></i>
|
| 193 |
+
<span class="text-xs mt-1">Profile</span>
|
| 194 |
+
</button>
|
| 195 |
+
</div>
|
| 196 |
+
</nav>
|
| 197 |
+
</div>
|
| 198 |
+
</div>
|
| 199 |
+
|
| 200 |
+
<script src="app.js"></script>
|
| 201 |
+
<script>
|
| 202 |
+
feather.replace();
|
| 203 |
+
</script>
|
| 204 |
+
<script src="https://huggingface.co/deepsite/deepsite-badge.js"></script>
|
| 205 |
+
</body>
|
| 206 |
+
</html>
|
styles.css
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
/* Base Styles */
|
| 2 |
+
body {
|
| 3 |
+
font-family: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif;
|
| 4 |
+
-webkit-tap-highlight-color: transparent;
|
| 5 |
+
}
|
| 6 |
+
|
| 7 |
+
/* Custom scrollbar */
|
| 8 |
+
::-webkit-scrollbar {
|
| 9 |
+
width: 6px;
|
| 10 |
+
height: 6px;
|
| 11 |
+
}
|
| 12 |
+
::-webkit-scrollbar-track {
|
| 13 |
+
background: #f1f1f1;
|
| 14 |
+
}
|
| 15 |
+
::-webkit-scrollbar-thumb {
|
| 16 |
+
background: #c1c1c1;
|
| 17 |
+
border-radius: 3px;
|
| 18 |
+
}
|
| 19 |
+
::-webkit-scrollbar-thumb:hover {
|
| 20 |
+
background: #a1a1a1;
|
| 21 |
+
}
|
| 22 |
+
|
| 23 |
+
/* Animation classes */
|
| 24 |
+
.fade-in {
|
| 25 |
+
animation: fadeIn 0.3s ease-in-out;
|
| 26 |
+
}
|
| 27 |
+
|
| 28 |
+
@keyframes fadeIn {
|
| 29 |
+
from { opacity: 0; }
|
| 30 |
+
to { opacity: 1; }
|
| 31 |
+
}
|
| 32 |
+
|
| 33 |
+
.slide-up {
|
| 34 |
+
animation: slideUp 0.3s ease-out;
|
| 35 |
+
}
|
| 36 |
+
|
| 37 |
+
@keyframes slideUp {
|
| 38 |
+
from { transform: translateY(20px); opacity: 0; }
|
| 39 |
+
to { transform: translateY(0); opacity: 1; }
|
| 40 |
+
}
|
| 41 |
+
|
| 42 |
+
/* Question styles */
|
| 43 |
+
.question-card {
|
| 44 |
+
transition: all 0.2s ease;
|
| 45 |
+
}
|
| 46 |
+
|
| 47 |
+
.question-card:hover {
|
| 48 |
+
transform: translateY(-2px);
|
| 49 |
+
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
|
| 50 |
+
}
|
| 51 |
+
|
| 52 |
+
/* Anti-cheat warnings */
|
| 53 |
+
.warning-flash {
|
| 54 |
+
animation: warningFlash 0.5s ease 3;
|
| 55 |
+
}
|
| 56 |
+
|
| 57 |
+
@keyframes warningFlash {
|
| 58 |
+
0%, 100% { background-color: transparent; }
|
| 59 |
+
50% { background-color: rgba(239, 68, 68, 0.2); }
|
| 60 |
+
}
|
| 61 |
+
|
| 62 |
+
/* Exam timer urgent state */
|
| 63 |
+
.timer-urgent {
|
| 64 |
+
animation: pulse 1s infinite;
|
| 65 |
+
}
|
| 66 |
+
|
| 67 |
+
@keyframes pulse {
|
| 68 |
+
0%, 100% { opacity: 1; }
|
| 69 |
+
50% { opacity: 0.7; }
|
| 70 |
+
}
|