Drew4564's picture
make me a full working mobile-first html css javascript project for a school anti-cheating exam app called "Nova ExamGuard". it must be a single-page web app (spa style) where all buttons and links work — no dead buttons, no reloads, everything switches screens using javascript. everything must work just by opening index.html (no backend). include all pages inside one continuous html file, with css and js either inline or in the same file. design it for phones (max-width 420px) with clean modern ui, rounded corners, and bottom navigation.
98d40b7 verified
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
<title>Nova ExamGuard</title>
<link href="https://fonts.googleapis.com/css2?family=Poppins:wght@300;400;500;600&display=swap" rel="stylesheet">
<script src="https://cdn.tailwindcss.com"></script>
<script src="https://cdn.jsdelivr.net/npm/feather-icons/dist/feather.min.js"></script>
<style>
:root {
--primary: #4361ee;
--secondary: #3a0ca3;
--danger: #ef233c;
--success: #2ec4b6;
}
* {
-webkit-tap-highlight-color: transparent;
-webkit-user-select: none;
user-select: none;
}
body {
font-family: 'Poppins', sans-serif;
background-color: #f8f9fa;
color: #333;
margin: 0;
padding: 0;
min-height: 100vh;
overflow-x: hidden;
}
.page {
display: none;
padding: 20px;
padding-bottom: 80px;
min-height: calc(100vh - 80px);
}
.page.active {
display: block;
animation: fadeIn 0.3s ease;
}
@keyframes fadeIn {
from { opacity: 0; transform: translateY(10px); }
to { opacity: 1; transform: translateY(0); }
}
.nav-tab.active {
color: var(--primary);
}
.nav-tab.active svg {
stroke: var(--primary);
}
.exam-card {
transition: transform 0.2s, box-shadow 0.2s;
}
.exam-card:hover {
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(0,0,0,0.1);
}
.webcam-container {
background-color: #e9ecef;
border-radius: 10px;
overflow: hidden;
}
.status-badge {
font-size: 12px;
padding: 4px 8px;
border-radius: 20px;
}
.modal {
display: none;
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0,0,0,0.5);
z-index: 100;
justify-content: center;
align-items: center;
}
.modal-content {
background-color: white;
padding: 20px;
border-radius: 10px;
max-width: 90%;
width: 100%;
max-width: 350px;
animation: modalFadeIn 0.3s ease;
}
@keyframes modalFadeIn {
from { opacity: 0; transform: translateY(-20px); }
to { opacity: 1; transform: translateY(0); }
}
.question-option {
transition: background-color 0.2s;
}
.question-option:hover {
background-color: #f1f3f5;
}
.question-option.selected {
background-color: #e6f7ff;
border-color: var(--primary);
}
.suspicious-count {
background-color: var(--danger);
color: white;
width: 20px;
height: 20px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
font-size: 12px;
position: absolute;
top: -5px;
right: -5px;
}
</style>
</head>
<body class="relative">
<!-- Login Page -->
<div id="login-page" class="page active flex flex-col justify-center items-center p-6">
<div class="w-full max-w-xs">
<div class="text-center mb-8">
<h1 class="text-3xl font-bold text-gray-800 mb-2">Nova ExamGuard</h1>
<p class="text-gray-600">Secure exam proctoring system</p>
</div>
<div class="bg-white rounded-xl shadow-md p-6 mb-4">
<div class="mb-6">
<label for="student-id" class="block text-sm font-medium text-gray-700 mb-1">Student ID</label>
<input type="text" id="student-id" class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 outline-none transition">
</div>
<div class="mb-6">
<label for="password" class="block text-sm font-medium text-gray-700 mb-1">Password</label>
<input type="password" id="password" class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 outline-none transition">
</div>
<button id="login-btn" class="w-full bg-blue-600 text-white py-2 px-4 rounded-lg font-medium hover:bg-blue-700 transition duration-200 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-opacity-50">
Login
</button>
<div class="text-center mt-4">
<a href="#" id="forgot-password-link" class="text-sm text-blue-600 hover:underline">Forgot password?</a>
</div>
</div>
</div>
</div>
<!-- Forgot Password Modal -->
<div id="forgot-password-modal" class="modal">
<div class="modal-content">
<div class="flex justify-between items-center mb-4">
<h3 class="text-lg font-medium">Reset Password</h3>
<button id="close-modal" class="text-gray-500 hover:text-gray-700">
<i data-feather="x"></i>
</button>
</div>
<div class="mb-4">
<label for="reset-email" class="block text-sm font-medium text-gray-700 mb-1">Email Address</label>
<input type="email" id="reset-email" class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 outline-none transition">
</div>
<button id="send-reset-link" class="w-full bg-blue-600 text-white py-2 px-4 rounded-lg font-medium hover:bg-blue-700 transition duration-200 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-opacity-50 mb-4">
Send Reset Link
</button>
<div id="reset-success" class="hidden text-center text-green-600 text-sm mb-4">
Reset link has been sent to your email (simulation only)
</div>
</div>
</div>
<!-- Main App (hidden until login) -->
<div id="app-container" class="hidden">
<!-- Dashboard Page -->
<div id="dashboard-page" class="page">
<div class="mb-6">
<div class="flex items-center mb-4">
<div class="w-16 h-16 rounded-full bg-gray-200 overflow-hidden mr-4">
<img src="http://static.photos/people/200x200/1" alt="Student" class="w-full h-full object-cover">
</div>
<div>
<h1 class="text-xl font-semibold" id="student-name">John Doe</h1>
<span id="status-badge" class="status-badge bg-blue-100 text-blue-800 inline-block">No active exam</span>
</div>
</div>
<div class="bg-white rounded-xl shadow-md p-6">
<h2 class="text-lg font-medium mb-4">Upcoming Exams</h2>
<div class="space-y-3">
<div class="flex justify-between items-center p-3 bg-gray-50 rounded-lg">
<div>
<h3 class="font-medium">Math 101</h3>
<p class="text-sm text-gray-600">Tomorrow, 10:00 AM</p>
</div>
<span class="text-xs bg-blue-100 text-blue-800 px-2 py-1 rounded">Upcoming</span>
</div>
<div class="flex justify-between items-center p-3 bg-gray-50 rounded-lg">
<div>
<h3 class="font-medium">Science 201</h3>
<p class="text-sm text-gray-600">Next week, 2:00 PM</p>
</div>
<span class="text-xs bg-gray-100 text-gray-800 px-2 py-1 rounded">Scheduled</span>
</div>
</div>
</div>
</div>
</div>
<!-- Exams Page -->
<div id="exams-page" class="page">
<h1 class="text-xl font-semibold mb-6">Available Exams</h1>
<div class="space-y-4" id="exams-list">
<!-- Exams will be populated via JS -->
</div>
</div>
<!-- Take Exam Page -->
<div id="take-exam-page" class="page">
<div class="flex justify-between items-center mb-4">
<h1 id="exam-title" class="text-xl font-semibold">Exam Title</h1>
<div id="exam-timer" class="bg-gray-800 text-white px-3 py-1 rounded-lg text-sm">00:30:00</div>
</div>
<div class="webcam-container mb-4 relative">
<video id="webcam" autoplay muted playsinline class="w-full h-auto"></video>
<div class="absolute bottom-2 left-2 bg-black bg-opacity-50 text-white text-xs px-2 py-1 rounded">Live Proctoring</div>
</div>
<div class="bg-white rounded-xl shadow-md p-6 mb-4">
<div id="exam-questions">
<!-- Questions will be populated via JS -->
</div>
<div class="mt-6">
<button id="submit-exam-btn" class="w-full bg-blue-600 text-white py-2 px-4 rounded-lg font-medium hover:bg-blue-700 transition duration-200 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-opacity-50">
Submit Exam
</button>
</div>
</div>
<div id="warning-banner" class="hidden bg-red-100 border-l-4 border-red-500 text-red-700 p-4 mb-4 rounded-lg">
<p>Warning: Suspicious activity detected (tab change/fullscreen exit). This incident has been recorded.</p>
</div>
<div id="fullscreen-warning" class="hidden bg-yellow-100 border-l-4 border-yellow-500 text-yellow-700 p-4 mb-4 rounded-lg">
<p>Please return to fullscreen mode to continue your exam.</p>
</div>
</div>
<!-- Pending Page -->
<div id="pending-page" class="page">
<h1 class="text-xl font-semibold mb-6">Submitted Exams</h1>
<div class="space-y-4" id="pending-exams-list">
<!-- Submitted exams will be populated via JS -->
</div>
</div>
<!-- Profile Page -->
<div id="profile-page" class="page">
<div class="flex justify-center mb-6">
<div class="w-24 h-24 rounded-full bg-gray-200 overflow-hidden">
<img src="http://static.photos/people/200x200/1" alt="Profile" class="w-full h-full object-cover">
</div>
</div>
<div class="bg-white rounded-xl shadow-md p-6 mb-4">
<div class="space-y-4">
<div>
<label class="block text-sm font-medium text-gray-500">Full Name</label>
<p id="profile-name" class="text-gray-800 font-medium">John Doe</p>
</div>
<div>
<label class="block text-sm font-medium text-gray-500">Student ID</label>
<p id="profile-id" class="text-gray-800 font-medium">S123456</p>
</div>
<div>
<label class="block text-sm font-medium text-gray-500">Email</label>
<p id="profile-email" class="text-gray-800 font-medium">john.doe@university.edu</p>
</div>
</div>
<button id="edit-profile-btn" class="w-full mt-6 bg-gray-100 text-gray-800 py-2 px-4 rounded-lg font-medium hover:bg-gray-200 transition duration-200 focus:outline-none focus:ring-2 focus:ring-gray-500 focus:ring-opacity-50">
Edit Profile
</button>
<button id="logout-btn" class="w-full mt-4 bg-red-100 text-red-600 py-2 px-4 rounded-lg font-medium hover:bg-red-200 transition duration-200 focus:outline-none focus:ring-2 focus:ring-red-500 focus:ring-opacity-50">
Logout
</button>
</div>
</div>
<!-- Bottom Navigation -->
<nav class="fixed bottom-0 left-0 right-0 bg-white border-t border-gray-200 flex justify-around py-2 px-4">
<button class="nav-tab active flex flex-col items-center py-2 px-4 text-xs" data-page="dashboard-page">
<i data-feather="home" class="mb-1"></i>
<span>Home</span>
</button>
<button class="nav-tab flex flex-col items-center py-2 px-4 text-xs" data-page="exams-page">
<i data-feather="book" class="mb-1"></i>
<span>Exams</span>
</button>
<button class="nav-tab flex flex-col items-center py-2 px-4 text-xs" data-page="take-exam-page">
<i data-feather="edit-3" class="mb-1"></i>
<span>Take Exam</span>
</button>
<button class="nav-tab flex flex-col items-center py-2 px-4 text-xs" data-page="pending-page">
<i data-feather="clock" class="mb-1"></i>
<span>Pending</span>
</button>
<button class="nav-tab flex flex-col items-center py-2 px-4 text-xs" data-page="profile-page">
<i data-feather="user" class="mb-1"></i>
<span>Profile</span>
</button>
</nav>
</div>
<script>
// App State
const state = {
currentUser: null,
currentExam: null,
examTimer: null,
suspiciousActions: 0,
webcamInterval: null,
snapshots: [],
examInProgress: false,
isFullscreen: false
};
// Sample Exams Data
const exams = [
{
id: 'exam-1',
title: 'Mathematics Final Exam',
subject: 'Math 101',
date: '2023-06-15',
time: '10:00 AM',
duration: 30, // minutes
questions: [
{
id: 1,
type: 'mcq',
question: 'What is the derivative of x²?',
options: ['x', '2x', 'x²', '2x²'],
correctAnswer: 1
},
{
id: 2,
type: 'mcq',
question: 'What is the value of π (pi) rounded to two decimal places?',
options: ['3.14', '3.16', '3.12', '3.18'],
correctAnswer: 0
},
{
id: 3,
type: 'short',
question: 'Explain the Pythagorean theorem.',
correctAnswer: 'In a right-angled triangle, the square of the hypotenuse is equal to the sum of the squares of the other two sides.'
},
{
id: 4,
type: 'mcq',
question: 'What is the solution to the equation 2x + 5 = 15?',
options: ['x = 5', 'x = 10', 'x = 7.5', 'x = 3'],
correctAnswer: 0
},
{
id: 5,
type: 'short',
question: 'What is the quadratic formula?',
correctAnswer: 'x = [-b ± √(b² - 4ac)] / 2a'
}
]
},
{
id: 'exam-2',
title: 'Science Midterm Exam',
subject: 'Science 201',
date: '2023-06-20',
time: '02:00 PM',
duration: 45,
questions: [
{
id: 1,
type: 'mcq',
question: 'What is the chemical symbol for gold?',
options: ['Go', 'Gd', 'Au', 'Ag'],
correctAnswer: 2
},
{
id: 2,
type: 'mcq',
question: 'Which planet is known as the Red Planet?',
options: ['Venus', 'Mars', 'Jupiter', 'Saturn'],
correctAnswer: 1
},
{
id: 3,
type: 'short',
question: 'What is Newton\'s First Law of Motion?',
correctAnswer: 'An object in motion stays in motion unless acted upon by an external force.'
}
]
}
];
// DOM Elements
const loginPage = document.getElementById('login-page');
const appContainer = document.getElementById('app-container');
const studentIdInput = document.getElementById('student-id');
const passwordInput = document.getElementById('password');
const loginBtn = document.getElementById('login-btn');
const forgotPasswordLink = document.getElementById('forgot-password-link');
const forgotPasswordModal = document.getElementById('forgot-password-modal');
const closeModalBtn = document.getElementById('close-modal');
const sendResetLinkBtn = document.getElementById('send-reset-link');
const resetSuccess = document.getElementById('reset-success');
const resetEmailInput = document.getElementById('reset-email');
const studentNameElement = document.getElementById('student-name');
const statusBadge = document.getElementById('status-badge');
const examsList = document.getElementById('exams-list');
const examTitle = document.getElementById('exam-title');
const examTimer = document.getElementById('exam-timer');
const examQuestions = document.getElementById('exam-questions');
const submitExamBtn = document.getElementById('submit-exam-btn');
const warningBanner = document.getElementById('warning-banner');
const fullscreenWarning = document.getElementById('fullscreen-warning');
const pendingExamsList = document.getElementById('pending-exams-list');
const profileName = document.getElementById('profile-name');
const profileId = document.getElementById('profile-id');
const profileEmail = document.getElementById('profile-email');
const editProfileBtn = document.getElementById('edit-profile-btn');
const logoutBtn = document.getElementById('logout-btn');
const webcamElement = document.getElementById('webcam');
const navTabs = document.querySelectorAll('.nav-tab');
const pages = document.querySelectorAll('.page');
// Initialize Feather Icons
document.addEventListener('DOMContentLoaded', function() {
feather.replace();
// Check if user is already logged in (from localStorage)
const savedUser = localStorage.getItem('examGuardUser');
if (savedUser) {
state.currentUser = JSON.parse(savedUser);
login();
}
// Load pending exams from localStorage
loadPendingExams();
});
// Event Listeners
loginBtn.addEventListener('click', handleLogin);
forgotPasswordLink.addEventListener('click', showForgotPasswordModal);
closeModalBtn.addEventListener('click', closeForgotPasswordModal);
sendResetLinkBtn.addEventListener('click', sendResetLink);
submitExamBtn.addEventListener('click', submitExam);
editProfileBtn.addEventListener('click', editProfile);
logoutBtn.addEventListener('click', logout);
// Navigation tabs
navTabs.forEach(tab => {
tab.addEventListener('click', function() {
const pageId = this.getAttribute('data-page');
showPage(pageId);
// Update active tab
navTabs.forEach(t => t.classList.remove('active'));
this.classList.add('active');
});
});
// Visibility change detection (anti-cheat)
document.addEventListener('visibilitychange', function() {
if (state.examInProgress && document.visibilityState === 'hidden') {
handleSuspiciousActivity('Tab change detected');
}
});
// Fullscreen change detection
document.addEventListener('fullscreenchange', function() {
state.isFullscreen = !!document.fullscreenElement;
if (state.examInProgress && !state.isFullscreen) {
handleSuspiciousActivity('Fullscreen exit detected');
fullscreenWarning.classList.remove('hidden');
} else {
fullscreenWarning.classList.add('hidden');
}
});
// Right click prevention
document.addEventListener('contextmenu', function(e) {
if (state.examInProgress) {
e.preventDefault();
handleSuspiciousActivity('Right click attempted');
}
});
// Text selection prevention
document.addEventListener('selectstart', function(e) {
if (state.examInProgress) {
e.preventDefault();
handleSuspiciousActivity('Text selection attempted');
}
});
// Copy/paste prevention
document.addEventListener('copy', function(e) {
if (state.examInProgress) {
e.preventDefault();
handleSuspiciousActivity('Copy attempted');
}
});
document.addEventListener('paste', function(e) {
if (state.examInProgress) {
e.preventDefault();
handleSuspiciousActivity('Paste attempted');
}
});
// Functions
function handleLogin() {
const studentId = studentIdInput.value.trim();
const password = passwordInput.value.trim();
if (!studentId || !password) {
alert('Please enter both student ID and password');
return;
}
// Mock login - accept any credentials
state.currentUser = {
id: studentId,
name: studentId === 'admin' ? 'Admin User' : `Student ${studentId}`,
email: `${studentId}@university.edu`
};
// Save to localStorage
localStorage.setItem('examGuardUser', JSON.stringify(state.currentUser));
login();
}
function login() {
// Hide login page, show app
loginPage.classList.remove('active');
appContainer.classList.remove('hidden');
// Update user info
studentNameElement.textContent = state.currentUser.name;
profileName.textContent = state.currentUser.name;
profileId.textContent = state.currentUser.id;
profileEmail.textContent = state.currentUser.email;
// Show dashboard by default
showPage('dashboard-page');
// Populate exams list
renderExamsList();
}
function logout() {
// Clear user session
state.currentUser = null;
localStorage.removeItem('examGuardUser');
// Reset form
studentIdInput.value = '';
passwordInput.value = '';
// Hide app, show login page
appContainer.classList.add('hidden');
loginPage.classList.add('active');
// Reset active tab
navTabs.forEach(tab => tab.classList.remove('active'));
navTabs[0].classList.add('active');
// Stop any ongoing exam
stopExam();
}
function showForgotPasswordModal() {
forgotPasswordModal.style.display = 'flex';
}
function closeForgotPasswordModal() {
forgotPasswordModal.style.display = 'none';
resetSuccess.classList.add('hidden');
}
function sendResetLink() {
const email = resetEmailInput.value.trim();
if (!email) {
alert('Please enter your email address');
return;
}
// Simulate sending reset link
resetSuccess.classList.remove('hidden');
}
function showPage(pageId) {
pages.forEach(page => page.classList.remove('active'));
document.getElementById(pageId).classList.add('active');
// Special handling for certain pages
if (pageId === 'exams-page') {
renderExamsList();
} else if (pageId === 'pending-page') {
loadPendingExams();
}
}
function renderExamsList() {
examsList.innerHTML = '';
exams.forEach(exam => {
const examCard = document.createElement('div');
examCard.className = 'exam-card bg-white rounded-xl shadow-md overflow-hidden';
const isPending = checkIfExamPending(exam.id);
examCard.innerHTML = `
<div class="p-6">
<div class="flex justify-between items-start mb-2">
<h2 class="text-lg font-semibold">${exam.subject}</h2>
${isPending ? '<span class="text-xs bg-green-100 text-green-800 px-2 py-1 rounded">Submitted</span>' : ''}
</div>
<p class="text-sm text-gray-600 mb-4">${exam.date}${exam.time}</p>
<button data-exam-id="${exam.id}" class="take-exam-btn w-full bg-blue-600 text-white py-2 px-4 rounded-lg font-medium hover:bg-blue-700 transition duration-200 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-opacity-50 ${isPending ? 'opacity-50 cursor-not-allowed' : ''}" ${isPending ? 'disabled' : ''}>
${isPending ? 'Already Submitted' : 'Take Exam'}
</button>
</div>
`;
examsList.appendChild(examCard);
});
// Add event listeners to take exam buttons
document.querySelectorAll('.take-exam-btn').forEach(btn => {
btn.addEventListener('click', function() {
if (this.disabled) return;
const examId = this.getAttribute('data-exam-id');
startExam(examId);
});
});
}
function checkIfExamPending(examId) {
const pendingExams = JSON.parse(localStorage.getItem('pendingExams') || '[]');
return pendingExams.some(exam => exam.examId === examId);
}
function startExam(examId) {
// Find the exam
const exam = exams.find(e => e.id === examId);
if (!exam) return;
state.currentExam = exam;
state.examInProgress = true;
state.suspiciousActions = 0;
state.snapshots = [];
// Update UI
examTitle.textContent = exam.title;
updateExamTimer(exam.duration * 60); // Convert minutes to seconds
// Render questions
renderExamQuestions(exam.questions);
// Start webcam
startWebcam();
// Start snapshot interval
state.webcamInterval = setInterval(takeSnapshot, 30000); // Every 30 seconds
// Request fullscreen
requestFullscreen();
// Show take exam page
showPage('take-exam-page');
// Start timer
let timeLeft = exam.duration * 60; // in seconds
state.examTimer = setInterval(() => {
timeLeft--;
updateExamTimer(timeLeft);
if (timeLeft <= 0) {
submitExam();
}
}, 1000);
// Update status badge
statusBadge.textContent = 'Exam in progress';
statusBadge.className = 'status-badge bg-green-100 text-green-800 inline-block';
}
function stopExam() {
if (state.examTimer) {
clearInterval(state.examTimer);
state.examTimer = null;
}
if (state.webcamInterval) {
clearInterval(state.webcamInterval);
state.webcamInterval = null;
}
// Stop webcam
stopWebcam();
state.examInProgress = false;
state.currentExam = null;
// Reset status badge
statusBadge.textContent = 'No active exam';
statusBadge.className = 'status-badge bg-blue-100 text-blue-800 inline-block';
// Hide warnings
warningBanner.classList.add('hidden');
fullscreenWarning.classList.add('hidden');
}
function updateExamTimer(seconds) {
const mins = Math.floor(seconds / 60);
const secs = seconds % 60;
examTimer.textContent = `${mins.toString().padStart(2, '0')}:${secs.toString().padStart(2, '0')}`;
// Change color when time is running out
if (seconds <= 300) { // 5 minutes
examTimer.className = 'bg-red-600 text-white px-3 py-1 rounded-lg text-sm';
} else {
examTimer.className = 'bg-gray-800 text-white px-3 py-1 rounded-lg text-sm';
}
}
function renderExamQuestions(questions) {
examQuestions.innerHTML = '';
questions.forEach((q, index) => {
const questionElement = document.createElement('div');
questionElement.className = 'mb-6';
if (q.type === 'mcq') {
questionElement.innerHTML = `
<h3 class="font-medium mb-2">${index + 1}. ${q.question}</h3>
<div class="space-y-2">
${q.options.map((option, i) => `
<div class="question-option p-3 border border-gray-300 rounded-lg cursor-pointer" data-question-id="${q.id}" data-option-id="${i}">
${option}
</div>
`).join('')}
</div>
`;
} else {
questionElement.innerHTML = `
<h3 class="font-medium mb-2">${index + 1}. ${q.question}</h3>
<textarea class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 outline-none transition" data-question-id="${q.id}" rows="3" placeholder="Type your answer here..."></textarea>
`;
}
examQuestions.appendChild(questionElement);
});
// Add event listeners to MCQ options
document.querySelectorAll('.question-option').forEach(option => {
option.addEventListener('click', function() {
// Deselect all options for this question first
const questionId = this.getAttribute('data-question-id');
document.querySelectorAll(`.question-option[data-question-id="${questionId}"]`).forEach(el => {
el.classList.remove('selected');
});
// Select clicked option
this.classList.add('selected');
});
});
}
function startWebcam() {
navigator.mediaDevices.getUserMedia({ video: true })
.then(stream => {
webcamElement.srcObject = stream;
})
.catch(err => {
console.error('Error accessing webcam:', err);
});
}
function stopWebcam() {
if (webcamElement.srcObject) {
webcamElement.srcObject.getTracks().forEach(track => track.stop());
webcamElement.srcObject = null;
}
}
function takeSnapshot() {
// In a real app, this would capture and upload the webcam frame
// For this demo, we'll just store a fake snapshot
state.snapshots.push({
timestamp: new Date().toISOString(),
suspicious: state.suspiciousActions > 0
});
console.log('Snapshot taken at', new Date().toISOString());
}
function requestFullscreen() {
if (!document.fullscreenElement) {
document.documentElement.requestFullscreen().catch(err => {
console.error('Error attempting to enable fullscreen:', err);
});
}
}
function handleSuspiciousActivity(reason) {
console.log('Suspicious activity:', reason);
state.suspiciousActions++;
// Show warning banner
warningBanner.classList.remove('hidden');
// Hide after 5 seconds
setTimeout(() => {
warningBanner.classList.add('hidden');
}, 5000);
}
function submitExam() {
if (!state.currentExam) return;
// Collect answers
const answers = [];
// Multiple choice answers
document.querySelectorAll('.question-option.selected').forEach(option => {
answers.push({
questionId: parseInt(option.getAttribute('data-question-id')),
answer: option.getAttribute('data-option-id')
});
});
// Short answer responses
document.querySelectorAll('textarea').forEach(textarea => {
const answer = textarea.value.trim();
if (answer) {
answers.push({
questionId: parseInt(textarea.getAttribute('data-question-id')),
answer: answer
});
}
});
// Create exam result
const examResult = {
examId: state.currentExam.id,
title: state.currentExam.title,
subject: state.currentExam.subject,
date: new Date().toISOString(),
status: 'pending',
suspiciousCount: state.suspiciousActions,
snapshots: state.snapshots,
answers: answers
};
// Save to localStorage
const pendingExams = JSON.parse(localStorage.getItem('pendingExams') || '[]');
pendingExams.push(examResult);
localStorage.setItem('pendingExams', JSON.stringify(pendingExams));
// Stop exam
stopExam();
// Show pending page
showPage('pending-page');
}
function loadPendingExams() {
const pendingExams = JSON.parse(localStorage.getItem('pendingExams') || '[]');
pendingExamsList.innerHTML = '';
if (pendingExams.length === 0) {
pendingExamsList.innerHTML = '<p class="text-gray-500 text-center py-4">No submitted exams yet</p>';
return;
}
pendingExams.forEach((exam, index) => {
const examElement = document.createElement('div');
examElement.className = 'bg-white rounded-xl shadow-md overflow-hidden relative';
// Format date
const examDate = new Date(exam.date);
const formattedDate = examDate.toLocaleDateString('en-US', {
month: 'short',
day: 'numeric',
year: 'numeric'
});
examElement.innerHTML = `
<div class="p-6">
<h2 class="text-lg font-semibold mb-1">${exam.subject}</h2>
<p class="text-sm text-gray-600 mb-2">Submitted on ${formattedDate}</p>
<p class="text-sm mb-2">Status: <span class="font-medium">${exam.status === 'pending' ? 'Pending Review' : exam.status}</span></p>
${exam.suspiciousCount > 0 ? `
<div class="flex items-center text-sm mb-2">
<span class="text-red-600">${exam.suspiciousCount} suspicious actions detected</span>
${exam.suspiciousCount > 3 ? '<i data-feather="alert-triangle" class="ml-1 text-red-600 w-4 h-4"></i>' : ''}
</div>
` : '<p class="text-sm text-green-600 mb-2">No suspicious activity detected</p>'}
<div class="flex gap-2 overflow-x-auto py-2">
${exam.snapshots.map((snapshot, i) => `
<div class="relative">
<div class="w-16 h-16 bg-gray-200 rounded-lg flex items-center justify-center text-xs text-gray-500">
Snapshot ${i + 1}
</div>
${snapshot.suspicious ? '<div class="suspicious-count">!</div>' : ''}
</div>
`).join('')}
</div>
</div>
`;
pendingExamsList.appendChild(examElement);
});
feather.replace();
}
function editProfile() {
const newName = prompt('Enter your new name:', state.currentUser.name);
if (newName && newName.trim() !== '') {
state.currentUser.name = newName.trim();
studentNameElement.textContent = state.currentUser.name;
profileName.textContent = state.currentUser.name;
localStorage.setItem('examGuardUser', JSON.stringify(state.currentUser));
}
}
</script>
<script src="https://huggingface.co/deepsite/deepsite-badge.js"></script>
</body>
</html>