Danielchris145 commited on
Commit
bd8840b
·
verified ·
1 Parent(s): ac3e73a

Upload folder using huggingface_hub

Browse files
Files changed (4) hide show
  1. app.py +5 -0
  2. static/assets/css/auth.css +241 -0
  3. static/assets/js/config.js +14 -2
  4. static/index.html +678 -308
app.py CHANGED
@@ -73,6 +73,11 @@ def generate_content_with_groq(prompt):
73
  def index():
74
  return send_file('static/index.html')
75
 
 
 
 
 
 
76
  # API Endpoints
77
  @app.route('/upload_resume', methods=['POST'])
78
  def upload_resume():
 
73
  def index():
74
  return send_file('static/index.html')
75
 
76
+ # Serve static assets
77
+ @app.route('/assets/<path:filename>')
78
+ def serve_assets(filename):
79
+ return send_from_directory('static/assets', filename)
80
+
81
  # API Endpoints
82
  @app.route('/upload_resume', methods=['POST'])
83
  def upload_resume():
static/assets/css/auth.css ADDED
@@ -0,0 +1,241 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /* Authentication Pages - Premium Design */
2
+
3
+ .auth-container {
4
+ position: relative;
5
+ z-index: 1;
6
+ min-height: 100vh;
7
+ display: flex;
8
+ align-items: center;
9
+ justify-content: center;
10
+ padding: var(--spacing-lg);
11
+ gap: var(--spacing-xl);
12
+ }
13
+
14
+ .auth-card {
15
+ width: 100%;
16
+ max-width: 480px;
17
+ background: var(--glass-bg);
18
+ backdrop-filter: blur(20px);
19
+ border: 1px solid var(--glass-border);
20
+ border-radius: var(--radius-xl);
21
+ padding: var(--spacing-xl);
22
+ box-shadow: var(--glass-shadow);
23
+ }
24
+
25
+ .auth-header {
26
+ text-align: center;
27
+ margin-bottom: var(--spacing-xl);
28
+ }
29
+
30
+ .brand-logo {
31
+ display: flex;
32
+ align-items: center;
33
+ justify-content: center;
34
+ gap: var(--spacing-sm);
35
+ margin-bottom: var(--spacing-lg);
36
+ }
37
+
38
+ .auth-title {
39
+ font-size: 32px;
40
+ margin-bottom: var(--spacing-sm);
41
+ }
42
+
43
+ .auth-subtitle {
44
+ color: var(--color-text-secondary);
45
+ font-size: 16px;
46
+ }
47
+
48
+ .auth-form {
49
+ margin-bottom: var(--spacing-lg);
50
+ }
51
+
52
+ .form-group {
53
+ margin-bottom: var(--spacing-md);
54
+ }
55
+
56
+ .form-label {
57
+ display: flex;
58
+ align-items: center;
59
+ gap: var(--spacing-xs);
60
+ font-size: 14px;
61
+ font-weight: 600;
62
+ margin-bottom: var(--spacing-xs);
63
+ color: var(--color-text-secondary);
64
+ }
65
+
66
+ .form-input {
67
+ width: 100%;
68
+ padding: 14px 16px;
69
+ background: var(--color-surface);
70
+ border: 1px solid var(--glass-border);
71
+ border-radius: var(--radius-md);
72
+ color: var(--color-text-primary);
73
+ font-size: 16px;
74
+ transition: all var(--transition-base);
75
+ }
76
+
77
+ .form-input:focus {
78
+ outline: none;
79
+ border-color: var(--color-primary);
80
+ box-shadow: 0 0 0 4px rgba(99, 102, 241, 0.1);
81
+ }
82
+
83
+ .form-hint {
84
+ display: block;
85
+ font-size: 12px;
86
+ color: var(--color-text-tertiary);
87
+ margin-top: 4px;
88
+ }
89
+
90
+ .form-options {
91
+ display: flex;
92
+ justify-content: space-between;
93
+ align-items: center;
94
+ margin-bottom: var(--spacing-md);
95
+ }
96
+
97
+ .checkbox-label {
98
+ display: flex;
99
+ align-items: center;
100
+ gap: var(--spacing-xs);
101
+ font-size: 14px;
102
+ color: var(--color-text-secondary);
103
+ cursor: pointer;
104
+ }
105
+
106
+ .checkbox-label input[type="checkbox"] {
107
+ width: 18px;
108
+ height: 18px;
109
+ cursor: pointer;
110
+ }
111
+
112
+ .link-primary {
113
+ color: var(--color-primary);
114
+ text-decoration: none;
115
+ font-weight: 600;
116
+ transition: color var(--transition-fast);
117
+ }
118
+
119
+ .link-primary:hover {
120
+ color: var(--color-primary-dark);
121
+ }
122
+
123
+ .btn-block {
124
+ width: 100%;
125
+ justify-content: center;
126
+ }
127
+
128
+ .auth-divider {
129
+ position: relative;
130
+ text-align: center;
131
+ margin: var(--spacing-lg) 0;
132
+ }
133
+
134
+ .auth-divider::before {
135
+ content: '';
136
+ position: absolute;
137
+ top: 50%;
138
+ left: 0;
139
+ right: 0;
140
+ height: 1px;
141
+ background: var(--glass-border);
142
+ }
143
+
144
+ .auth-divider span {
145
+ position: relative;
146
+ background: var(--color-bg-primary);
147
+ padding: 0 var(--spacing-md);
148
+ font-size: 14px;
149
+ color: var(--color-text-tertiary);
150
+ }
151
+
152
+ .social-login {
153
+ display: flex;
154
+ gap: var(--spacing-sm);
155
+ margin-bottom: var(--spacing-lg);
156
+ }
157
+
158
+ .btn-social {
159
+ flex: 1;
160
+ padding: 14px;
161
+ background: var(--color-surface);
162
+ border: 1px solid var(--glass-border);
163
+ border-radius: var(--radius-md);
164
+ color: var(--color-text-primary);
165
+ font-size: 20px;
166
+ cursor: pointer;
167
+ transition: all var(--transition-base);
168
+ }
169
+
170
+ .btn-social:hover {
171
+ background: var(--color-surface-hover);
172
+ border-color: var(--color-primary);
173
+ transform: translateY(-2px);
174
+ }
175
+
176
+ .auth-footer {
177
+ text-align: center;
178
+ padding-top: var(--spacing-md);
179
+ border-top: 1px solid var(--glass-border);
180
+ color: var(--color-text-secondary);
181
+ }
182
+
183
+ .auth-features {
184
+ display: flex;
185
+ flex-direction: column;
186
+ gap: var(--spacing-lg);
187
+ max-width: 400px;
188
+ }
189
+
190
+ .feature-item {
191
+ display: flex;
192
+ flex-direction: column;
193
+ gap: var(--spacing-sm);
194
+ }
195
+
196
+ .feature-icon {
197
+ width: 56px;
198
+ height: 56px;
199
+ background: linear-gradient(135deg, var(--color-primary), var(--color-secondary));
200
+ border-radius: var(--radius-lg);
201
+ display: flex;
202
+ align-items: center;
203
+ justify-content: center;
204
+ font-size: 24px;
205
+ box-shadow: 0 8px 24px rgba(99, 102, 241, 0.3);
206
+ }
207
+
208
+ .feature-item h3 {
209
+ font-size: 20px;
210
+ font-weight: 700;
211
+ }
212
+
213
+ .feature-item p {
214
+ color: var(--color-text-secondary);
215
+ font-size: 14px;
216
+ }
217
+
218
+ .alert {
219
+ padding: var(--spacing-md);
220
+ border-radius: var(--radius-md);
221
+ margin-bottom: var(--spacing-md);
222
+ display: flex;
223
+ align-items: center;
224
+ gap: var(--spacing-sm);
225
+ }
226
+
227
+ .alert-error {
228
+ background: rgba(239, 68, 68, 0.1);
229
+ border: 1px solid rgba(239, 68, 68, 0.3);
230
+ color: var(--color-danger);
231
+ }
232
+
233
+ @media (max-width: 1024px) {
234
+ .auth-container {
235
+ flex-direction: column;
236
+ }
237
+
238
+ .auth-features {
239
+ max-width: 100%;
240
+ }
241
+ }
static/assets/js/config.js CHANGED
@@ -1,13 +1,25 @@
1
- // API Configuration for HuggingFace Spaces
 
2
  const API_CONFIG = {
3
- BASE_URL: '', // Same origin
 
 
 
 
 
 
 
4
  TIMEOUT: 30000,
 
 
5
  SESSION_KEY: 'hr_ai_session_id'
6
  };
7
 
 
8
  function getApiUrl(endpoint) {
9
  return `${API_CONFIG.BASE_URL}/${endpoint.replace(/^\//, '')}`;
10
  }
11
 
 
12
  window.API_CONFIG = API_CONFIG;
13
  window.getApiUrl = getApiUrl;
 
1
+ // API Configuration
2
+ // Change this to your production URL when deploying
3
  const API_CONFIG = {
4
+ // Base URL for API calls
5
+ // Development: Python backend runs on port 5000
6
+ // Production: 'https://your-domain.com/api'
7
+ BASE_URL: window.location.hostname === 'localhost'
8
+ ? 'http://localhost:5000'
9
+ : window.location.origin,
10
+
11
+ // Timeout for API requests (in milliseconds)
12
  TIMEOUT: 30000,
13
+
14
+ // Session storage key
15
  SESSION_KEY: 'hr_ai_session_id'
16
  };
17
 
18
+ // Helper function to get API URL
19
  function getApiUrl(endpoint) {
20
  return `${API_CONFIG.BASE_URL}/${endpoint.replace(/^\//, '')}`;
21
  }
22
 
23
+ // Export for use in other modules
24
  window.API_CONFIG = API_CONFIG;
25
  window.getApiUrl = getApiUrl;
static/index.html CHANGED
@@ -9,34 +9,52 @@
9
  <link href="https://fonts.googleapis.com/css2?family=Poppins:wght@300;400;500;600;700;800&family=Inter:wght@300;400;500;600;700&display=swap" rel="stylesheet">
10
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
11
  <link rel="stylesheet" href="assets/css/premium.css">
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
12
  </head>
13
  <body>
14
  <!-- Animated Background -->
15
  <div class="premium-bg">
16
  <div class="gradient-mesh"></div>
17
- <div class="neural-network">
18
- <canvas id="neuralCanvas"></canvas>
19
- </div>
20
  </div>
21
 
22
  <!-- Navigation -->
23
  <nav class="premium-nav">
24
  <div class="nav-container">
25
  <div class="nav-brand">
26
- <div class="brand-icon">
27
- <i class="fas fa-brain"></i>
28
- </div>
29
  <span class="brand-text">IntervuAI<span class="brand-pro">Pro</span></span>
30
  </div>
31
  <div class="nav-menu">
32
- <a href="#" class="nav-link active">
33
- <i class="fas fa-home"></i>
34
- <span>Dashboard</span>
35
- </a>
36
  <div class="nav-user">
37
- <div class="user-avatar">
38
- <i class="fas fa-user"></i>
39
- </div>
40
  <div class="user-info">
41
  <span class="user-name">Guest User</span>
42
  <span class="user-role">Premium Access</span>
@@ -49,43 +67,20 @@
49
  <!-- Main Content -->
50
  <main class="premium-main">
51
  <!-- Hero Section -->
52
- <section class="hero-section">
53
  <div class="hero-content">
54
- <div class="hero-badge">
55
- <i class="fas fa-sparkles"></i>
56
- <span>Powered by Advanced AI</span>
57
- </div>
58
- <h1 class="hero-title">
59
- Next-Generation
60
- <span class="gradient-text">AI Interview</span>
61
- Platform
62
- </h1>
63
- <p class="hero-subtitle">
64
- Experience intelligent candidate evaluation with real-time AI analysis,
65
- automated scoring, and comprehensive insights.
66
- </p>
67
  <div class="hero-actions">
68
- <button class="btn-primary" onclick="scrollToUpload()">
69
- <i class="fas fa-rocket"></i>
70
- <span>Start Interview</span>
71
- <div class="btn-glow"></div>
72
- </button>
73
  </div>
74
  <div class="hero-stats">
75
- <div class="stat-item">
76
- <div class="stat-value">10K+</div>
77
- <div class="stat-label">Interviews</div>
78
- </div>
79
  <div class="stat-divider"></div>
80
- <div class="stat-item">
81
- <div class="stat-value">98%</div>
82
- <div class="stat-label">Accuracy</div>
83
- </div>
84
  <div class="stat-divider"></div>
85
- <div class="stat-item">
86
- <div class="stat-value">AI</div>
87
- <div class="stat-label">Powered</div>
88
- </div>
89
  </div>
90
  </div>
91
  </section>
@@ -94,36 +89,27 @@
94
  <section class="upload-section" id="uploadSection">
95
  <div class="glass-card large">
96
  <div class="card-header">
97
- <div class="header-icon">
98
- <i class="fas fa-cloud-upload-alt"></i>
99
- </div>
100
  <div class="header-content">
101
  <h2 class="card-title">Upload Resume</h2>
102
  <p class="card-subtitle">AI-powered resume analysis in seconds</p>
103
  </div>
104
  </div>
105
-
106
  <div class="upload-area" id="uploadArea">
107
  <input type="file" id="resumeInput" accept=".pdf" hidden>
108
- <div class="upload-icon">
109
- <i class="fas fa-file-upload"></i>
110
- </div>
111
  <h3 class="upload-title">Drop your resume here</h3>
112
  <p class="upload-text">or click to browse</p>
113
- <div class="upload-formats">
114
- <span class="format-badge">PDF</span>
115
- </div>
116
  </div>
117
  </div>
118
  </section>
119
 
120
- <!-- Setup Section (Hidden) -->
121
  <section class="upload-section hidden" id="setupSection">
122
  <div class="glass-card large">
123
  <div class="card-header">
124
- <div class="header-icon">
125
- <i class="fas fa-user-check"></i>
126
- </div>
127
  <div class="header-content">
128
  <h2 class="card-title">Candidate Profile</h2>
129
  <p class="card-subtitle">Review and configure interview</p>
@@ -135,19 +121,13 @@
135
  <input type="text" id="positionInput" class="form-input" placeholder="e.g., Senior Software Engineer">
136
  </div>
137
  <div class="interview-actions" style="margin-top: 2rem;">
138
- <button class="btn-secondary" onclick="location.reload()">
139
- <i class="fas fa-arrow-left"></i> Back
140
- </button>
141
- <button class="btn-primary" onclick="startInterview()">
142
- <span>Start Interview</span>
143
- <i class="fas fa-arrow-right"></i>
144
- <div class="btn-glow"></div>
145
- </button>
146
  </div>
147
  </div>
148
  </section>
149
 
150
- <!-- Interview Section (Hidden) -->
151
  <section class="interview-section hidden" id="interviewSection">
152
  <div class="glass-card">
153
  <div class="interview-header">
@@ -160,42 +140,28 @@
160
  <span class="progress-number" id="progressNumber">1/10</span>
161
  </div>
162
  </div>
163
- <div class="timer">
164
- <i class="fas fa-clock"></i>
165
- <span id="timer">00:00</span>
166
- </div>
167
  </div>
168
-
169
  <div class="question-container">
170
  <div class="question-tags" id="questionTags"></div>
171
  <h3 class="question-text" id="questionText">Loading question...</h3>
172
  </div>
173
-
174
  <div class="answer-container">
175
  <textarea id="answerText" class="answer-input" placeholder="Type your response here..." rows="8"></textarea>
176
  </div>
177
-
178
  <div class="feedback-box hidden" id="feedbackBox"></div>
179
-
180
  <div class="interview-actions">
181
- <button class="btn-secondary" onclick="location.reload()">
182
- <i class="fas fa-home"></i> Home
183
- </button>
184
- <button class="btn-primary" onclick="submitAnswer()">
185
- <span>Submit Answer</span>
186
- <i class="fas fa-paper-plane"></i>
187
- <div class="btn-glow"></div>
188
- </button>
189
  </div>
190
  </div>
191
  </section>
192
 
193
- <!-- Results Section (Hidden) -->
194
  <section class="results-section hidden" id="resultsSection">
195
  <div class="results-header">
196
  <h2 class="section-title">Interview Assessment</h2>
197
  </div>
198
-
199
  <div class="results-grid">
200
  <div class="glass-card">
201
  <div class="score-display">
@@ -209,29 +175,42 @@
209
  <span class="score-label">Overall</span>
210
  </div>
211
  </div>
212
- <div class="recommendation" id="recommendation">
213
- <i class="fas fa-hourglass-half"></i>
214
- <span>Loading...</span>
215
- </div>
216
  </div>
217
  </div>
218
-
219
  <div class="glass-card">
220
  <h3 class="card-title">Detailed Scores</h3>
221
- <div class="score-breakdown" id="scoreBreakdown"></div>
 
 
 
 
 
 
 
 
 
 
 
 
 
222
  </div>
223
  </div>
224
-
225
  <div class="glass-card" style="margin-top: 2rem;">
226
  <h3 class="card-title">Key Insights</h3>
227
- <div class="insights-grid" id="insightsGrid"></div>
 
 
 
 
 
 
 
 
 
228
  </div>
229
-
230
- <div class="interview-actions" style="margin-top: 2rem;">
231
- <button class="btn-primary" onclick="location.reload()">
232
- <i class="fas fa-redo"></i> Start New Interview
233
- <div class="btn-glow"></div>
234
- </button>
235
  </div>
236
  </section>
237
  </main>
@@ -244,238 +223,629 @@
244
  </div>
245
  </div>
246
 
247
- <script src="assets/js/config.js"></script>
248
- <script src="assets/js/neural-bg.js"></script>
 
 
 
249
  <script>
250
- // Session management
251
- let sessionId = 'session_' + Date.now();
252
- let questions = [];
253
- let currentQuestion = 0;
254
- let timerInterval = null;
255
- let seconds = 0;
256
-
257
- // Utility functions
258
- function showLoading(text) {
259
- document.getElementById('loadingText').textContent = text || 'Processing...';
260
- document.getElementById('loadingOverlay').classList.remove('hidden');
261
  }
262
- function hideLoading() {
263
- document.getElementById('loadingOverlay').classList.add('hidden');
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
264
  }
265
- function scrollToUpload() {
266
- document.getElementById('uploadSection').scrollIntoView({ behavior: 'smooth' });
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
267
  }
268
- function getSessionId() { return sessionId; }
 
 
 
269
 
270
- // Upload area events
271
- document.addEventListener('DOMContentLoaded', () => {
272
- const uploadArea = document.getElementById('uploadArea');
273
- const resumeInput = document.getElementById('resumeInput');
274
-
275
- uploadArea.addEventListener('click', () => resumeInput.click());
276
- uploadArea.addEventListener('dragover', (e) => {
277
- e.preventDefault();
278
- uploadArea.style.borderColor = 'var(--color-primary)';
279
- });
280
- uploadArea.addEventListener('dragleave', () => {
281
- uploadArea.style.borderColor = '';
282
- });
283
- uploadArea.addEventListener('drop', (e) => {
284
- e.preventDefault();
285
- uploadArea.style.borderColor = '';
286
- if (e.dataTransfer.files.length > 0) handleFileUpload(e.dataTransfer.files[0]);
287
- });
288
- resumeInput.addEventListener('change', (e) => {
289
- if (e.target.files.length > 0) handleFileUpload(e.target.files[0]);
290
  });
291
- });
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
292
 
293
- // Handle file upload
294
- async function handleFileUpload(file) {
295
- showLoading('Analyzing resume with AI...');
296
- const formData = new FormData();
297
- formData.append('resume', file);
298
-
 
 
 
299
  try {
300
- const resp = await fetch('/upload_resume', {
301
- method: 'POST',
302
- headers: { 'X-User-Session-Id': sessionId },
303
- body: formData
304
- });
305
- const data = await resp.json();
306
- hideLoading();
307
-
308
- if (data.candidate_profile) {
309
- const p = data.candidate_profile;
310
- document.getElementById('profileDisplay').innerHTML = `
311
- <div class="profile-item"><span class="profile-label">Name:</span><span class="profile-value">${p.name || 'N/A'}</span></div>
312
- <div class="profile-item"><span class="profile-label">Email:</span><span class="profile-value">${p.email || 'N/A'}</span></div>
313
- <div class="profile-item"><span class="profile-label">Experience:</span><span class="profile-value">${p.experience || 'N/A'}</span></div>
314
- <div class="profile-item"><span class="profile-label">Skills:</span><div class="skills-tags">${(p.key_skills||[]).map(s=>`<span class="skill-tag">${s}</span>`).join('')}</div></div>
315
- `;
316
- document.getElementById('positionInput').value = p.inferred_position || '';
317
- document.getElementById('uploadSection').classList.add('hidden');
318
- document.getElementById('setupSection').classList.remove('hidden');
319
- } else {
320
- alert('Error: ' + (data.error || 'Failed'));
321
- }
322
- } catch (e) {
323
- hideLoading();
324
- alert('Error: ' + e.message);
325
  }
326
  }
327
-
328
- // Start interview
329
- async function startInterview() {
330
- const position = document.getElementById('positionInput').value;
331
- if (!position) { alert('Enter a position'); return; }
332
-
333
- showLoading('Generating interview questions...');
 
 
 
 
 
 
 
 
 
 
 
334
  try {
335
- const resp = await fetch('/setup_interview', {
336
- method: 'POST',
337
- headers: { 'Content-Type': 'application/json', 'X-User-Session-Id': sessionId },
338
- body: JSON.stringify({ position_role: position })
339
- });
340
- const data = await resp.json();
341
- hideLoading();
342
-
343
- if (data.questions) {
344
- questions = data.questions;
345
- currentQuestion = 0;
346
- document.getElementById('setupSection').classList.add('hidden');
347
- document.getElementById('interviewSection').classList.remove('hidden');
348
- showQuestion();
349
- startTimer();
350
- } else {
351
- alert('Error: ' + (data.error || 'Failed'));
352
- }
353
- } catch (e) {
354
- hideLoading();
355
- alert('Error: ' + e.message);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
356
  }
 
 
357
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
358
 
359
- // Show question
360
- function showQuestion() {
361
- const q = questions[currentQuestion];
362
- const progress = ((currentQuestion + 1) / questions.length) * 100;
363
-
364
- document.getElementById('progressNumber').textContent = `${currentQuestion + 1}/${questions.length}`;
365
- document.getElementById('questionTags').innerHTML = (q.tags||[]).map(t=>`<span class="tag">${t}</span>`).join('');
366
- document.getElementById('questionText').textContent = q.question;
367
- document.getElementById('answerText').value = '';
368
- document.getElementById('feedbackBox').classList.add('hidden');
369
-
370
- // Update progress ring
371
- const ring = document.getElementById('progressRing');
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
372
  const circumference = 2 * Math.PI * 45;
373
- ring.style.strokeDasharray = `${circumference} ${circumference}`;
374
- ring.style.strokeDashoffset = circumference - (progress / 100) * circumference;
375
  }
 
 
376
 
377
- // Timer
378
- function startTimer() {
379
- seconds = 0;
380
- timerInterval = setInterval(() => {
381
- seconds++;
382
- const m = Math.floor(seconds / 60).toString().padStart(2, '0');
383
- const s = (seconds % 60).toString().padStart(2, '0');
384
- document.getElementById('timer').textContent = `${m}:${s}`;
385
- }, 1000);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
386
  }
 
387
 
388
- // Submit answer
389
- async function submitAnswer() {
390
- const answer = document.getElementById('answerText').value.trim();
391
- if (!answer) { alert('Provide an answer'); return; }
392
-
393
- showLoading('Evaluating your answer...');
394
- try {
395
- const resp = await fetch('/submit_answer', {
396
- method: 'POST',
397
- headers: { 'Content-Type': 'application/json', 'X-User-Session-Id': sessionId },
398
- body: JSON.stringify({
399
- question_id: questions[currentQuestion].id,
400
- response_text: answer,
401
- duration: document.getElementById('timer').textContent
402
- })
403
- });
404
- const data = await resp.json();
405
- hideLoading();
406
-
407
- if (data.evaluation) {
408
- const e = data.evaluation;
409
- document.getElementById('feedbackBox').innerHTML = `
410
- <strong>Score: ${e.score}/100</strong><br>
411
- Technical: ${e.technicalScore}% | Communication: ${e.communicationScore}% | Relevance: ${e.relevanceScore}%<br>
412
- <em>${e.feedback}</em>
413
- `;
414
- document.getElementById('feedbackBox').classList.remove('hidden');
415
-
416
- currentQuestion++;
417
- if (currentQuestion < questions.length) {
418
- setTimeout(showQuestion, 2000);
419
- } else {
420
- clearInterval(timerInterval);
421
- setTimeout(showAssessment, 2000);
422
- }
423
- }
424
- } catch (e) {
425
- hideLoading();
426
- alert('Error: ' + e.message);
427
- }
428
  }
 
429
 
430
- // Show assessment
431
- async function showAssessment() {
432
- showLoading('Generating assessment report...');
433
- try {
434
- const resp = await fetch('/get_assessment', { headers: { 'X-User-Session-Id': sessionId } });
435
- const data = await resp.json();
436
- hideLoading();
437
-
438
- if (data.assessment) {
439
- const a = data.assessment;
440
- const score = a.overallScore || 0;
441
-
442
- document.getElementById('overallScore').textContent = score;
443
-
444
- // Update score ring
445
- const ring = document.getElementById('scoreRing');
446
- const circumference = 2 * Math.PI * 90;
447
- ring.style.strokeDasharray = `${circumference} ${circumference}`;
448
- ring.style.strokeDashoffset = circumference - (score / 100) * circumference;
449
-
450
- // Recommendation
451
- const recClass = score >= 85 ? 'recommended' : score >= 70 ? 'consider' : 'not-recommended';
452
- document.getElementById('recommendation').innerHTML = `<i class="fas fa-${score >= 70 ? 'check-circle' : 'exclamation-circle'}"></i><span>${a.recommendation || 'N/A'}</span>`;
453
- document.getElementById('recommendation').className = 'recommendation ' + recClass;
454
-
455
- // Detailed scores
456
- const ds = a.detailedScores || {};
457
- document.getElementById('scoreBreakdown').innerHTML = `
458
- <div class="score-item"><div class="score-info"><span class="score-name">Technical Skills</span><span class="score-percent">${ds.technicalSkills || 0}%</span></div><div class="score-bar"><div class="score-bar-fill" style="width: ${ds.technicalSkills || 0}%"></div></div></div>
459
- <div class="score-item"><div class="score-info"><span class="score-name">Communication</span><span class="score-percent">${ds.communication || 0}%</span></div><div class="score-bar"><div class="score-bar-fill" style="width: ${ds.communication || 0}%"></div></div></div>
460
- <div class="score-item"><div class="score-info"><span class="score-name">Soft Skills</span><span class="score-percent">${ds.softSkills || 0}%</span></div><div class="score-bar"><div class="score-bar-fill" style="width: ${ds.softSkills || 0}%"></div></div></div>
461
- `;
462
-
463
- // Insights
464
- const strengths = (a.keyStrengths || []).map(s => `<div class="insight-item strength"><i class="fas fa-check"></i>${s}</div>`).join('');
465
- const improvements = (a.areasForImprovement || []).map(s => `<div class="insight-item improvement"><i class="fas fa-arrow-up"></i>${s}</div>`).join('');
466
- document.getElementById('insightsGrid').innerHTML = `
467
- <div class="insights-column"><h4>Key Strengths</h4>${strengths}</div>
468
- <div class="insights-column"><h4>Areas for Improvement</h4>${improvements}</div>
469
- `;
470
-
471
- document.getElementById('interviewSection').classList.add('hidden');
472
- document.getElementById('resultsSection').classList.remove('hidden');
473
- }
474
- } catch (e) {
475
- hideLoading();
476
- alert('Error: ' + e.message);
 
477
  }
478
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
479
  </script>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
480
  </body>
481
  </html>
 
9
  <link href="https://fonts.googleapis.com/css2?family=Poppins:wght@300;400;500;600;700;800&family=Inter:wght@300;400;500;600;700&display=swap" rel="stylesheet">
10
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
11
  <link rel="stylesheet" href="assets/css/premium.css">
12
+ <style>
13
+ .feedback-box { background: rgba(16, 185, 129, 0.1); padding: 20px; border-radius: 10px; margin: 15px 0; border-left: 4px solid var(--color-success); }
14
+ .feedback-box.hidden { display: none; }
15
+ .profile-display { display: grid; gap: 1rem; padding: 1.5rem; background: var(--color-surface); border-radius: var(--radius-lg); border: 1px solid var(--glass-border); }
16
+ .profile-item { display: flex; gap: 1rem; align-items: start; }
17
+ .profile-label { font-weight: 600; color: var(--color-text-secondary); min-width: 120px; }
18
+ .profile-value { color: var(--color-text-primary); }
19
+ .skills-tags { display: flex; flex-wrap: wrap; gap: 0.5rem; }
20
+ .skill-tag { padding: 4px 12px; background: rgba(99, 102, 241, 0.1); border: 1px solid rgba(99, 102, 241, 0.3); border-radius: 20px; font-size: 14px; color: var(--color-primary); }
21
+ .form-input { width: 100%; padding: 14px 16px; background: var(--color-surface); border: 1px solid var(--glass-border); border-radius: var(--radius-md); color: var(--color-text-primary); font-size: 16px; }
22
+ .form-input:focus { outline: none; border-color: var(--color-primary); }
23
+ .form-label { display: flex; align-items: center; gap: 8px; font-size: 14px; font-weight: 600; margin-bottom: 8px; color: var(--color-text-secondary); }
24
+ .insights-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 2rem; }
25
+ .insights-column h4 { margin-bottom: 1rem; color: var(--color-text-secondary); }
26
+ .insight-item { padding: 12px; margin: 8px 0; border-radius: 8px; display: flex; align-items: center; gap: 10px; }
27
+ .insight-item.strength { background: rgba(16, 185, 129, 0.1); border-left: 3px solid var(--color-success); }
28
+ .insight-item.improvement { background: rgba(239, 68, 68, 0.1); border-left: 3px solid var(--color-danger); }
29
+ .recommendation.recommended { border-color: var(--color-success); color: var(--color-success); background: rgba(16, 185, 129, 0.1); }
30
+ .recommendation.consider { border-color: var(--color-warning); color: var(--color-warning); background: rgba(245, 158, 11, 0.1); }
31
+ .recommendation.not-recommended { border-color: var(--color-danger); color: var(--color-danger); background: rgba(239, 68, 68, 0.1); }
32
+ .score-item { margin: 1rem 0; }
33
+ .score-label-row { display: flex; justify-content: space-between; margin-bottom: 0.5rem; }
34
+ .score-name { color: var(--color-text-secondary); }
35
+ .score-percent { color: var(--color-primary); font-weight: 600; }
36
+ .score-bar { height: 8px; background: var(--color-surface); border-radius: 4px; overflow: hidden; }
37
+ .score-bar-fill { height: 100%; background: linear-gradient(90deg, var(--color-primary), var(--color-secondary)); border-radius: 4px; transition: width 1s ease; }
38
+ </style>
39
  </head>
40
  <body>
41
  <!-- Animated Background -->
42
  <div class="premium-bg">
43
  <div class="gradient-mesh"></div>
44
+ <div class="neural-network"><canvas id="neuralCanvas"></canvas></div>
 
 
45
  </div>
46
 
47
  <!-- Navigation -->
48
  <nav class="premium-nav">
49
  <div class="nav-container">
50
  <div class="nav-brand">
51
+ <div class="brand-icon"><i class="fas fa-brain"></i></div>
 
 
52
  <span class="brand-text">IntervuAI<span class="brand-pro">Pro</span></span>
53
  </div>
54
  <div class="nav-menu">
55
+ <a href="#" class="nav-link active"><i class="fas fa-home"></i><span>Dashboard</span></a>
 
 
 
56
  <div class="nav-user">
57
+ <div class="user-avatar"><i class="fas fa-user"></i></div>
 
 
58
  <div class="user-info">
59
  <span class="user-name">Guest User</span>
60
  <span class="user-role">Premium Access</span>
 
67
  <!-- Main Content -->
68
  <main class="premium-main">
69
  <!-- Hero Section -->
70
+ <section class="hero-section" id="heroSection">
71
  <div class="hero-content">
72
+ <div class="hero-badge"><i class="fas fa-sparkles"></i><span>Powered by Advanced AI</span></div>
73
+ <h1 class="hero-title">Next-Generation<br><span class="gradient-text">AI Interview</span><br>Platform</h1>
74
+ <p class="hero-subtitle">Experience intelligent candidate evaluation with real-time AI analysis, automated scoring, and comprehensive insights.</p>
 
 
 
 
 
 
 
 
 
 
75
  <div class="hero-actions">
76
+ <button class="btn-primary" onclick="scrollToUpload()"><i class="fas fa-rocket"></i><span>Start Interview</span><div class="btn-glow"></div></button>
 
 
 
 
77
  </div>
78
  <div class="hero-stats">
79
+ <div class="stat-item"><div class="stat-value">10K+</div><div class="stat-label">Interviews</div></div>
 
 
 
80
  <div class="stat-divider"></div>
81
+ <div class="stat-item"><div class="stat-value">98%</div><div class="stat-label">Accuracy</div></div>
 
 
 
82
  <div class="stat-divider"></div>
83
+ <div class="stat-item"><div class="stat-value">AI</div><div class="stat-label">Powered</div></div>
 
 
 
84
  </div>
85
  </div>
86
  </section>
 
89
  <section class="upload-section" id="uploadSection">
90
  <div class="glass-card large">
91
  <div class="card-header">
92
+ <div class="header-icon"><i class="fas fa-cloud-upload-alt"></i></div>
 
 
93
  <div class="header-content">
94
  <h2 class="card-title">Upload Resume</h2>
95
  <p class="card-subtitle">AI-powered resume analysis in seconds</p>
96
  </div>
97
  </div>
 
98
  <div class="upload-area" id="uploadArea">
99
  <input type="file" id="resumeInput" accept=".pdf" hidden>
100
+ <div class="upload-icon"><i class="fas fa-file-upload"></i></div>
 
 
101
  <h3 class="upload-title">Drop your resume here</h3>
102
  <p class="upload-text">or click to browse</p>
103
+ <div class="upload-formats"><span class="format-badge">PDF</span></div>
 
 
104
  </div>
105
  </div>
106
  </section>
107
 
108
+ <!-- Setup Section -->
109
  <section class="upload-section hidden" id="setupSection">
110
  <div class="glass-card large">
111
  <div class="card-header">
112
+ <div class="header-icon"><i class="fas fa-user-check"></i></div>
 
 
113
  <div class="header-content">
114
  <h2 class="card-title">Candidate Profile</h2>
115
  <p class="card-subtitle">Review and configure interview</p>
 
121
  <input type="text" id="positionInput" class="form-input" placeholder="e.g., Senior Software Engineer">
122
  </div>
123
  <div class="interview-actions" style="margin-top: 2rem;">
124
+ <button class="btn-secondary" onclick="location.reload()"><i class="fas fa-arrow-left"></i> Back</button>
125
+ <button class="btn-primary" onclick="startInterview()"><span>Start Interview</span><i class="fas fa-arrow-right"></i><div class="btn-glow"></div></button>
 
 
 
 
 
 
126
  </div>
127
  </div>
128
  </section>
129
 
130
+ <!-- Interview Section -->
131
  <section class="interview-section hidden" id="interviewSection">
132
  <div class="glass-card">
133
  <div class="interview-header">
 
140
  <span class="progress-number" id="progressNumber">1/10</span>
141
  </div>
142
  </div>
143
+ <div class="timer"><i class="fas fa-clock"></i><span id="timer">00:00</span></div>
 
 
 
144
  </div>
 
145
  <div class="question-container">
146
  <div class="question-tags" id="questionTags"></div>
147
  <h3 class="question-text" id="questionText">Loading question...</h3>
148
  </div>
 
149
  <div class="answer-container">
150
  <textarea id="answerText" class="answer-input" placeholder="Type your response here..." rows="8"></textarea>
151
  </div>
 
152
  <div class="feedback-box hidden" id="feedbackBox"></div>
 
153
  <div class="interview-actions">
154
+ <button class="btn-secondary" onclick="location.reload()"><i class="fas fa-home"></i> Home</button>
155
+ <button class="btn-primary" onclick="submitAnswer()"><span>Submit Answer</span><i class="fas fa-paper-plane"></i><div class="btn-glow"></div></button>
 
 
 
 
 
 
156
  </div>
157
  </div>
158
  </section>
159
 
160
+ <!-- Results Section -->
161
  <section class="results-section hidden" id="resultsSection">
162
  <div class="results-header">
163
  <h2 class="section-title">Interview Assessment</h2>
164
  </div>
 
165
  <div class="results-grid">
166
  <div class="glass-card">
167
  <div class="score-display">
 
175
  <span class="score-label">Overall</span>
176
  </div>
177
  </div>
178
+ <div class="recommendation" id="recommendation"><i class="fas fa-hourglass-half"></i><span>Loading...</span></div>
 
 
 
179
  </div>
180
  </div>
 
181
  <div class="glass-card">
182
  <h3 class="card-title">Detailed Scores</h3>
183
+ <div class="score-breakdown" id="scoreBreakdown">
184
+ <div class="score-item">
185
+ <div class="score-label-row"><span class="score-name">Technical Skills</span><span class="score-percent">0%</span></div>
186
+ <div class="score-bar"><div class="score-bar-fill" style="width: 0%"></div></div>
187
+ </div>
188
+ <div class="score-item">
189
+ <div class="score-label-row"><span class="score-name">Communication</span><span class="score-percent">0%</span></div>
190
+ <div class="score-bar"><div class="score-bar-fill" style="width: 0%"></div></div>
191
+ </div>
192
+ <div class="score-item">
193
+ <div class="score-label-row"><span class="score-name">Soft Skills</span><span class="score-percent">0%</span></div>
194
+ <div class="score-bar"><div class="score-bar-fill" style="width: 0%"></div></div>
195
+ </div>
196
+ </div>
197
  </div>
198
  </div>
 
199
  <div class="glass-card" style="margin-top: 2rem;">
200
  <h3 class="card-title">Key Insights</h3>
201
+ <div class="insights-grid" id="insightsGrid">
202
+ <div class="insights-column">
203
+ <h4><i class="fas fa-check-circle" style="color: var(--color-success);"></i> Strengths</h4>
204
+ <ul id="strengthsList" style="list-style: none; padding: 0;"></ul>
205
+ </div>
206
+ <div class="insights-column">
207
+ <h4><i class="fas fa-exclamation-circle" style="color: var(--color-danger);"></i> Areas for Improvement</h4>
208
+ <ul id="improvementsList" style="list-style: none; padding: 0;"></ul>
209
+ </div>
210
+ </div>
211
  </div>
212
+ <div class="interview-actions" style="margin-top: 2rem; justify-content: center;">
213
+ <button class="btn-primary" onclick="location.reload()"><i class="fas fa-redo"></i> Start New Interview<div class="btn-glow"></div></button>
 
 
 
 
214
  </div>
215
  </section>
216
  </main>
 
223
  </div>
224
  </div>
225
 
226
+ <!-- TensorFlow and BlazeFace for face detection -->
227
+ <script src="https://cdn.jsdelivr.net/npm/@tensorflow/tfjs@3.18.0/dist/tf.min.js"></script>
228
+ <script src="https://cdn.jsdelivr.net/npm/@tensorflow-models/blazeface@0.0.7/dist/blazeface.min.js"></script>
229
+
230
+ <!-- Config -->
231
  <script>
232
+ const API_CONFIG = { BASE_URL: window.location.origin, TIMEOUT: 30000 };
233
+ function getApiUrl(endpoint) { return `${API_CONFIG.BASE_URL}/${endpoint.replace(/^\//, '')}`; }
234
+ function getSessionId() {
235
+ let sid = sessionStorage.getItem('sessionId');
236
+ if (!sid) { sid = 'session_' + Date.now() + '_' + Math.random().toString(36).substring(2, 11); sessionStorage.setItem('sessionId', sid); }
237
+ return sid;
 
 
 
 
 
238
  }
239
+ window.API_CONFIG = API_CONFIG;
240
+ window.getApiUrl = getApiUrl;
241
+ window.getSessionId = getSessionId;
242
+ </script>
243
+
244
+ <!-- Neural Network Animation -->
245
+ <script>
246
+ (function() {
247
+ const canvas = document.getElementById('neuralCanvas');
248
+ if (!canvas) return;
249
+ const ctx = canvas.getContext('2d');
250
+ let nodes = [], connections = [];
251
+ function resize() { canvas.width = window.innerWidth; canvas.height = window.innerHeight; init(); }
252
+ function init() {
253
+ nodes = []; connections = [];
254
+ const nodeCount = Math.floor((canvas.width * canvas.height) / 25000);
255
+ for (let i = 0; i < nodeCount; i++) {
256
+ nodes.push({ x: Math.random() * canvas.width, y: Math.random() * canvas.height, vx: (Math.random() - 0.5) * 0.5, vy: (Math.random() - 0.5) * 0.5, radius: Math.random() * 2 + 1 });
257
+ }
258
  }
259
+ function animate() {
260
+ ctx.clearRect(0, 0, canvas.width, canvas.height);
261
+ nodes.forEach(node => {
262
+ node.x += node.vx; node.y += node.vy;
263
+ if (node.x < 0 || node.x > canvas.width) node.vx *= -1;
264
+ if (node.y < 0 || node.y > canvas.height) node.vy *= -1;
265
+ ctx.beginPath(); ctx.arc(node.x, node.y, node.radius, 0, Math.PI * 2);
266
+ ctx.fillStyle = 'rgba(99, 102, 241, 0.5)'; ctx.fill();
267
+ });
268
+ for (let i = 0; i < nodes.length; i++) {
269
+ for (let j = i + 1; j < nodes.length; j++) {
270
+ const dx = nodes[i].x - nodes[j].x, dy = nodes[i].y - nodes[j].y;
271
+ const dist = Math.sqrt(dx * dx + dy * dy);
272
+ if (dist < 150) {
273
+ ctx.beginPath(); ctx.moveTo(nodes[i].x, nodes[i].y); ctx.lineTo(nodes[j].x, nodes[j].y);
274
+ ctx.strokeStyle = `rgba(99, 102, 241, ${0.2 * (1 - dist / 150)})`; ctx.stroke();
275
+ }
276
+ }
277
+ }
278
+ requestAnimationFrame(animate);
279
  }
280
+ window.addEventListener('resize', resize);
281
+ resize(); animate();
282
+ })();
283
+ </script>
284
 
285
+ <!-- Exam Security System -->
286
+ <script>
287
+ class ExamSecurity {
288
+ constructor() {
289
+ this.tabSwitchCount = 0; this.maxTabSwitches = 2; this.isExamMode = false;
290
+ this.questionTimeLimit = 180; this.questionTimer = null; this.currentQuestionStartTime = null;
291
+ this.init();
292
+ }
293
+ init() {
294
+ document.addEventListener('contextmenu', (e) => { if (this.isExamMode) { e.preventDefault(); this.showWarning('Right-click disabled'); } });
295
+ document.addEventListener('copy', (e) => { if (this.isExamMode) { e.preventDefault(); this.showWarning('Copy disabled'); } });
296
+ document.addEventListener('paste', (e) => { if (this.isExamMode) { e.preventDefault(); this.showWarning('Paste disabled'); } });
297
+ document.addEventListener('keydown', (e) => {
298
+ if (this.isExamMode) {
299
+ if (e.key === 'F12' || (e.ctrlKey && e.shiftKey && (e.key === 'I' || e.key === 'J')) || (e.ctrlKey && e.key === 'u')) { e.preventDefault(); return false; }
300
+ if (e.ctrlKey && (e.key === 'c' || e.key === 'v' || e.key === 'x')) { e.preventDefault(); this.showWarning('Copy/Paste disabled'); return false; }
301
+ }
 
 
 
302
  });
303
+ document.addEventListener('visibilitychange', () => { if (this.isExamMode && document.hidden) { const ae = document.activeElement; if (ae && ae.tagName === 'IFRAME') return; this.handleTabSwitch(); } });
304
+ window.addEventListener('blur', () => { if (this.isExamMode) { const ae = document.activeElement; if (ae && ae.tagName === 'IFRAME') return; setTimeout(() => { if (!document.hasFocus()) this.handleTabSwitch(); }, 100); } });
305
+ document.addEventListener('fullscreenchange', () => { if (this.isExamMode && !document.fullscreenElement) { this.showWarning('Please stay in fullscreen'); this.enterFullscreen(); } });
306
+ }
307
+ handleTabSwitch() {
308
+ this.tabSwitchCount++;
309
+ this.logSecurityEvent('tab_switch', { count: this.tabSwitchCount });
310
+ if (this.tabSwitchCount >= this.maxTabSwitches) { this.terminateExam(); }
311
+ else { this.showCriticalWarning(`TAB SWITCH DETECTED!\n\nYou have ${this.maxTabSwitches - this.tabSwitchCount} warning(s) remaining.`); }
312
+ }
313
+ terminateExam() {
314
+ this.isExamMode = false;
315
+ this.logSecurityEvent('exam_terminated', { reason: 'exceeded_tab_switches' });
316
+ const overlay = document.createElement('div');
317
+ overlay.className = 'critical-warning-overlay show';
318
+ overlay.style.zIndex = '99999';
319
+ overlay.innerHTML = `<div class="critical-warning-box"><div class="warning-icon"><i class="fas fa-ban"></i></div><h3>Interview Terminated</h3><p>You exceeded the maximum tab switches (2).</p><button class="btn-primary" onclick="location.reload()"><span>Go Home</span></button></div>`;
320
+ document.body.appendChild(overlay);
321
+ }
322
+ async startExamMode() {
323
+ if (!this.checkBrowser()) { this.showBrowserError(); return false; }
324
+ this.tabSwitchCount = 0;
325
+ this.enterFullscreen();
326
+ this.showSecurityNotice();
327
+ this.logSecurityEvent('exam_started', { browser: this.getBrowserInfo() });
328
+ return true;
329
+ }
330
+ activateExamMode() { this.isExamMode = true; }
331
+ checkBrowser() { const ua = navigator.userAgent.toLowerCase(); return ua.includes('chrome') || ua.includes('edg') || ua.includes('brave') || ua.includes('opr') || ua.includes('opera'); }
332
+ getBrowserInfo() { const ua = navigator.userAgent.toLowerCase(); if (ua.includes('edg')) return 'Edge'; if (ua.includes('brave')) return 'Brave'; if (ua.includes('opr') || ua.includes('opera')) return 'Opera'; if (ua.includes('chrome')) return 'Chrome'; return 'Unknown'; }
333
+ async detectExtensions() { return false; }
334
+ showBrowserError() {
335
+ const overlay = document.createElement('div');
336
+ overlay.className = 'critical-warning-overlay show';
337
+ overlay.innerHTML = `<div class="critical-warning-box"><div class="warning-icon"><i class="fas fa-exclamation-circle"></i></div><h3>Unsupported Browser</h3><p>Please use Chrome, Edge, Brave, or Opera.</p><button class="btn-primary" onclick="location.reload()"><span>Go Back</span></button></div>`;
338
+ document.body.appendChild(overlay);
339
+ }
340
+ endExamMode() { this.isExamMode = false; this.exitFullscreen(); if (this.questionTimer) clearInterval(this.questionTimer); }
341
+ enterFullscreen() { const e = document.documentElement; if (e.requestFullscreen) e.requestFullscreen().catch(() => {}); }
342
+ exitFullscreen() { if (document.exitFullscreen) document.exitFullscreen(); }
343
+ startQuestionTimer(onTimeout, customTimeLimit = null) {
344
+ this.currentQuestionStartTime = Date.now();
345
+ let remaining = customTimeLimit || this.questionTimeLimit;
346
+ const update = () => {
347
+ const m = Math.floor(remaining / 60), s = remaining % 60;
348
+ const el = document.getElementById('timer');
349
+ if (el) { el.textContent = `${m.toString().padStart(2,'0')}:${s.toString().padStart(2,'0')}`; el.style.color = remaining < 30 ? 'var(--color-danger)' : 'var(--color-primary)'; }
350
+ };
351
+ update();
352
+ if (this.questionTimer) clearInterval(this.questionTimer);
353
+ this.questionTimer = setInterval(() => {
354
+ remaining--;
355
+ update();
356
+ if (remaining === 30) this.showWarning('30 seconds remaining!');
357
+ if (remaining <= 0) { clearInterval(this.questionTimer); this.showWarning('Time up! Moving to next...'); setTimeout(() => { if (onTimeout) onTimeout(); }, 1500); }
358
+ }, 1000);
359
+ }
360
+ stopQuestionTimer() { if (this.questionTimer) clearInterval(this.questionTimer); }
361
+ getQuestionDuration() {
362
+ if (this.currentQuestionStartTime) { const d = Math.floor((Date.now() - this.currentQuestionStartTime) / 1000); return `${Math.floor(d/60).toString().padStart(2,'0')}:${(d%60).toString().padStart(2,'0')}`; }
363
+ return '00:00';
364
+ }
365
+ showSecurityNotice() {
366
+ const notice = document.createElement('div');
367
+ notice.className = 'security-notice';
368
+ notice.innerHTML = `<div class="security-notice-content"><div class="security-icon"><i class="fas fa-shield-alt"></i></div><h3>Interview Security Active</h3><ul class="security-rules"><li><i class="fas fa-check"></i> Fullscreen mode enabled</li><li><i class="fas fa-check"></i> Tab switching limited (2 warnings)</li><li><i class="fas fa-check"></i> Copy/paste disabled</li><li><i class="fas fa-check"></i> 3 minutes per question</li></ul><button class="btn-primary" onclick="this.parentElement.parentElement.remove()"><span>I Understand</span></button></div>`;
369
+ document.body.appendChild(notice);
370
+ }
371
+ showWarning(msg) {
372
+ const w = document.createElement('div');
373
+ w.className = 'security-warning';
374
+ w.innerHTML = `<i class="fas fa-exclamation-triangle"></i><span>${msg}</span>`;
375
+ document.body.appendChild(w);
376
+ setTimeout(() => w.classList.add('show'), 100);
377
+ setTimeout(() => { w.classList.remove('show'); setTimeout(() => w.remove(), 300); }, 3000);
378
+ }
379
+ showCriticalWarning(msg) {
380
+ const overlay = document.createElement('div');
381
+ overlay.className = 'critical-warning-overlay';
382
+ overlay.innerHTML = `<div class="critical-warning-box"><div class="warning-icon"><i class="fas fa-exclamation-triangle"></i></div><h3>Security Warning</h3><p>${msg.replace(/\n/g, '<br>')}</p><button class="btn-primary" onclick="this.parentElement.parentElement.remove()"><span>I Understand</span></button></div>`;
383
+ document.body.appendChild(overlay);
384
+ setTimeout(() => overlay.classList.add('show'), 100);
385
+ }
386
+ logSecurityEvent(type, data) { fetch(getApiUrl('log_security'), { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-User-Session-Id': getSessionId() }, body: JSON.stringify({ event_type: type, data: data }) }).catch(() => {}); }
387
+ }
388
+ window.examSecurity = new ExamSecurity();
389
+ </script>
390
 
391
+ <!-- Proctoring System -->
392
+ <script>
393
+ class ProctoringSystem {
394
+ constructor() {
395
+ this.isActive = false; this.stream = null; this.videoElement = null;
396
+ this.lookAwayCount = 0; this.maxLookAwayWarnings = 2; this.faceDetectionModel = null;
397
+ this.faceDetectionInterval = null; this.noFaceDetectedTime = 0; this.multipleFacesCount = 0;
398
+ }
399
+ async startProctoring() {
400
  try {
401
+ console.log('Loading face detection...');
402
+ this.faceDetectionModel = await blazeface.load();
403
+ console.log('Face detection loaded');
404
+ this.stream = await navigator.mediaDevices.getUserMedia({ video: { width: { ideal: 640 }, height: { ideal: 480 }, facingMode: 'user' }, audio: { echoCancellation: false, noiseSuppression: false } });
405
+ this.createVideoElement();
406
+ this.videoElement.srcObject = this.stream;
407
+ await this.videoElement.play();
408
+ this.isActive = true;
409
+ this.startFaceDetection();
410
+ return true;
411
+ } catch (error) {
412
+ console.error('Proctoring error:', error);
413
+ this.showProctoringError(error);
414
+ return false;
 
 
 
 
 
 
 
 
 
 
 
415
  }
416
  }
417
+ createVideoElement() {
418
+ const container = document.createElement('div');
419
+ container.id = 'proctoringVideo';
420
+ container.style.cssText = 'position:fixed;bottom:20px;right:20px;width:200px;height:150px;border:2px solid var(--color-primary);border-radius:var(--radius-lg);overflow:hidden;z-index:9998;box-shadow:0 4px 12px rgba(0,0,0,0.3);';
421
+ this.videoElement = document.createElement('video');
422
+ this.videoElement.style.cssText = 'width:100%;height:100%;object-fit:cover;transform:scaleX(-1);';
423
+ this.videoElement.autoplay = true; this.videoElement.muted = true;
424
+ const label = document.createElement('div');
425
+ label.style.cssText = 'position:absolute;top:5px;left:5px;background:rgba(0,0,0,0.7);color:white;padding:4px 8px;border-radius:4px;font-size:12px;display:flex;align-items:center;gap:5px;';
426
+ label.innerHTML = '<i class="fas fa-video" style="color:var(--color-success);"></i><span>Proctoring Active</span>';
427
+ container.appendChild(this.videoElement);
428
+ container.appendChild(label);
429
+ document.body.appendChild(container);
430
+ }
431
+ startFaceDetection() {
432
+ this.faceDetectionInterval = setInterval(() => { if (this.isActive) this.detectFace(); }, 1000);
433
+ }
434
+ async detectFace() {
435
  try {
436
+ if (!this.faceDetectionModel || !this.videoElement) return;
437
+ const predictions = await this.faceDetectionModel.estimateFaces(this.videoElement, false);
438
+ const count = predictions.length;
439
+ this.updateFaceIndicator(count);
440
+ if (count === 0) { this.noFaceDetectedTime += 1000; if (this.noFaceDetectedTime >= 3000) { this.handleNoFace(); this.noFaceDetectedTime = 0; } }
441
+ else if (count === 1) { this.noFaceDetectedTime = 0; }
442
+ else if (count > 1) { this.handleMultipleFaces(count); }
443
+ } catch (e) { console.error('Face detection error:', e); }
444
+ }
445
+ handleNoFace() {
446
+ this.lookAwayCount++;
447
+ if (this.lookAwayCount >= this.maxLookAwayWarnings) { this.terminateExam('no_face'); }
448
+ else { this.showWarning(`NO FACE DETECTED!\n\nYou have ${this.maxLookAwayWarnings - this.lookAwayCount} warning(s) remaining.`); }
449
+ }
450
+ handleMultipleFaces(count) {
451
+ this.multipleFacesCount++;
452
+ if (this.multipleFacesCount >= 2) { this.terminateExam('multiple_faces'); }
453
+ else { this.showWarning(`MULTIPLE PEOPLE DETECTED!\n\n${count} faces in frame. Only candidate should be visible.`); }
454
+ }
455
+ terminateExam(reason) {
456
+ this.isActive = false;
457
+ const overlay = document.createElement('div');
458
+ overlay.className = 'critical-warning-overlay show';
459
+ overlay.style.zIndex = '99999';
460
+ overlay.innerHTML = `<div class="critical-warning-box"><div class="warning-icon"><i class="fas fa-ban"></i></div><h3>Interview Terminated</h3><p>${reason === 'multiple_faces' ? 'Multiple people detected.' : 'Face not detected for too long.'}</p><button class="btn-primary" onclick="location.reload()"><span>Go Home</span></button></div>`;
461
+ document.body.appendChild(overlay);
462
+ this.stopProctoring();
463
+ }
464
+ updateFaceIndicator(count) {
465
+ const container = document.getElementById('proctoringVideo');
466
+ if (!container) return;
467
+ let indicator = document.getElementById('faceCountIndicator');
468
+ if (!indicator) {
469
+ indicator = document.createElement('div');
470
+ indicator.id = 'faceCountIndicator';
471
+ indicator.style.cssText = 'position:absolute;top:5px;right:5px;background:rgba(0,0,0,0.7);color:white;padding:4px 8px;border-radius:4px;font-size:12px;font-weight:bold;';
472
+ container.appendChild(indicator);
473
+ }
474
+ if (count === 0) { indicator.style.background = 'rgba(239,68,68,0.9)'; indicator.innerHTML = '<i class="fas fa-user-slash"></i> No Face'; }
475
+ else if (count === 1) { indicator.style.background = 'rgba(16,185,129,0.9)'; indicator.innerHTML = '<i class="fas fa-user-check"></i> 1 Face'; }
476
+ else { indicator.style.background = 'rgba(239,68,68,0.9)'; indicator.innerHTML = `<i class="fas fa-users"></i> ${count} Faces`; }
477
+ }
478
+ stopProctoring() {
479
+ this.isActive = false;
480
+ if (this.faceDetectionInterval) clearInterval(this.faceDetectionInterval);
481
+ if (this.stream) this.stream.getTracks().forEach(t => t.stop());
482
+ const container = document.getElementById('proctoringVideo');
483
+ if (container) container.remove();
484
+ }
485
+ showWarning(msg) {
486
+ const w = document.createElement('div');
487
+ w.className = 'critical-warning-overlay show';
488
+ w.style.zIndex = '99999';
489
+ w.innerHTML = `<div class="critical-warning-box"><div class="warning-icon"><i class="fas fa-exclamation-triangle"></i></div><h3>Proctoring Warning</h3><p>${msg.replace(/\n/g, '<br>')}</p><button class="btn-primary" onclick="this.parentElement.parentElement.remove()"><span>I Understand</span></button></div>`;
490
+ document.body.appendChild(w);
491
+ }
492
+ showProctoringError(error) {
493
+ const msg = error.name === 'NotAllowedError' ? 'Camera access required. Please allow and refresh.' : 'Failed to access camera. Check device settings.';
494
+ const overlay = document.createElement('div');
495
+ overlay.className = 'critical-warning-overlay show';
496
+ overlay.style.zIndex = '99999';
497
+ overlay.innerHTML = `<div class="critical-warning-box"><div class="warning-icon"><i class="fas fa-video-slash"></i></div><h3>Camera Access Required</h3><p>${msg}</p><div style="margin-top:2rem;display:flex;gap:1rem;justify-content:center;"><button class="btn-secondary" onclick="location.reload()"><span>Cancel</span></button><button class="btn-primary" onclick="location.reload()"><span>Retry</span></button></div></div>`;
498
+ document.body.appendChild(overlay);
499
+ }
500
+ }
501
+ window.proctoringSystem = new ProctoringSystem();
502
+ </script>
503
+
504
+ <!-- Main Application -->
505
+ <script>
506
+ function scrollToUpload() { document.getElementById('uploadSection')?.scrollIntoView({ behavior: 'smooth', block: 'center' }); }
507
+ function showLoading(msg = 'Processing...') { const o = document.getElementById('loadingOverlay'); if (o) { o.querySelector('.loading-text').textContent = msg; o.classList.remove('hidden'); } }
508
+ function hideLoading() { document.getElementById('loadingOverlay')?.classList.add('hidden'); }
509
+ function showAlert(msg, type = 'info') {
510
+ const a = document.createElement('div');
511
+ a.className = `alert-toast alert-${type}`;
512
+ a.innerHTML = `<i class="fas fa-${type === 'success' ? 'check-circle' : type === 'error' ? 'exclamation-circle' : 'info-circle'}"></i><span>${msg}</span>`;
513
+ document.body.appendChild(a);
514
+ setTimeout(() => a.classList.add('show'), 100);
515
+ setTimeout(() => { a.classList.remove('show'); setTimeout(() => a.remove(), 300); }, 3000);
516
+ }
517
+
518
+ document.addEventListener('DOMContentLoaded', () => {
519
+ const uploadArea = document.getElementById('uploadArea');
520
+ const resumeInput = document.getElementById('resumeInput');
521
+ if (uploadArea && resumeInput) {
522
+ uploadArea.addEventListener('click', () => resumeInput.click());
523
+ uploadArea.addEventListener('dragover', (e) => { e.preventDefault(); uploadArea.style.borderColor = 'var(--color-primary)'; });
524
+ uploadArea.addEventListener('dragleave', () => { uploadArea.style.borderColor = ''; });
525
+ uploadArea.addEventListener('drop', (e) => { e.preventDefault(); uploadArea.style.borderColor = ''; if (e.dataTransfer.files.length > 0) handleFileUpload(e.dataTransfer.files[0]); });
526
+ resumeInput.addEventListener('change', (e) => { if (e.target.files.length > 0) handleFileUpload(e.target.files[0]); });
527
+ }
528
+ });
529
+
530
+ async function handleFileUpload(file) {
531
+ showLoading('Analyzing resume with AI...');
532
+ const formData = new FormData();
533
+ formData.append('resume', file);
534
+ try {
535
+ const response = await fetch(getApiUrl('upload_resume'), { method: 'POST', body: formData, headers: { 'X-User-Session-Id': getSessionId() } });
536
+ if (!response.ok) throw new Error('Upload failed');
537
+ const result = await response.json();
538
+ hideLoading();
539
+ if (result.candidate_profile) {
540
+ sessionStorage.setItem('candidateProfile', JSON.stringify(result.candidate_profile));
541
+ showAlert('Resume analyzed successfully!', 'success');
542
+ setTimeout(() => showInterviewSetup(result.candidate_profile), 1000);
543
+ } else { showAlert('Failed to analyze resume.', 'error'); }
544
+ } catch (error) { hideLoading(); showAlert('Failed to upload: ' + error.message, 'error'); }
545
+ }
546
+
547
+ function showInterviewSetup(profile) {
548
+ document.getElementById('uploadSection')?.classList.add('hidden');
549
+ const setupSection = document.getElementById('setupSection');
550
+ if (setupSection) {
551
+ setupSection.classList.remove('hidden');
552
+ const profileDisplay = document.getElementById('profileDisplay');
553
+ if (profileDisplay) {
554
+ profileDisplay.innerHTML = `
555
+ <div class="profile-item"><span class="profile-label">Name:</span><span class="profile-value">${profile.name || 'N/A'}</span></div>
556
+ <div class="profile-item"><span class="profile-label">Email:</span><span class="profile-value">${profile.email || 'N/A'}</span></div>
557
+ <div class="profile-item"><span class="profile-label">Experience:</span><span class="profile-value">${profile.experience || 'N/A'}</span></div>
558
+ <div class="profile-item"><span class="profile-label">Skills:</span><div class="skills-tags">${(profile.key_skills || []).map(s => `<span class="skill-tag">${s}</span>`).join('')}</div></div>
559
+ `;
560
  }
561
+ const posInput = document.getElementById('positionInput');
562
+ if (posInput && profile.inferred_position) posInput.value = profile.inferred_position;
563
  }
564
+ }
565
+
566
+ async function startInterview() {
567
+ const position = document.getElementById('positionInput')?.value;
568
+ if (!position) { showAlert('Please enter a position/role', 'error'); return; }
569
+ showPreExamChecklist(position);
570
+ }
571
+
572
+ async function showPreExamChecklist(position) {
573
+ const overlay = document.createElement('div');
574
+ overlay.className = 'critical-warning-overlay show';
575
+ overlay.id = 'preExamChecklist';
576
+ overlay.style.zIndex = '99999';
577
+ overlay.innerHTML = `
578
+ <div class="critical-warning-box" style="max-width:600px;">
579
+ <div class="warning-icon" style="background:rgba(99,102,241,0.2);"><i class="fas fa-clipboard-check" style="color:var(--color-primary);"></i></div>
580
+ <h3>Pre-Interview Checklist</h3>
581
+ <p style="margin-bottom:1.5rem;">Please ensure all requirements are met:</p>
582
+ <div class="checklist-items">
583
+ <div class="checklist-item" id="check-browser"><i class="fas fa-spinner fa-spin"></i><span>Checking browser...</span></div>
584
+ <div class="checklist-item" id="check-camera"><i class="fas fa-spinner fa-spin"></i><span>Requesting camera...</span></div>
585
+ <div class="checklist-item" id="check-microphone"><i class="fas fa-spinner fa-spin"></i><span>Requesting microphone...</span></div>
586
+ <div class="checklist-item" id="check-questions"><i class="fas fa-spinner fa-spin"></i><span>Generating questions...</span></div>
587
+ </div>
588
+ <div id="checklistActions" style="margin-top:2rem;display:none;"><button class="btn-primary" onclick="beginInterview()" style="width:100%;"><i class="fas fa-play"></i><span>Start Interview</span><div class="btn-glow"></div></button></div>
589
+ <div id="checklistError" style="margin-top:2rem;display:none;"><button class="btn-secondary" onclick="document.getElementById('preExamChecklist').remove()" style="width:100%;"><i class="fas fa-times"></i><span>Cancel</span></button></div>
590
+ </div>
591
+ `;
592
+ document.body.appendChild(overlay);
593
+ await runPreExamChecks(position);
594
+ }
595
 
596
+ async function runPreExamChecks(position) {
597
+ let allPassed = true;
598
+ // Browser check
599
+ await checkItem('check-browser', async () => { if (!window.examSecurity.checkBrowser()) throw new Error('Unsupported browser'); return 'Browser compatible'; });
600
+ // Camera check
601
+ const cameraOk = await checkItem('check-camera', async () => {
602
+ const stream = await navigator.mediaDevices.getUserMedia({ video: true });
603
+ stream.getTracks().forEach(t => t.stop());
604
+ return 'Camera access granted';
605
+ });
606
+ if (!cameraOk) allPassed = false;
607
+ // Microphone check
608
+ const micOk = await checkItem('check-microphone', async () => {
609
+ const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
610
+ stream.getTracks().forEach(t => t.stop());
611
+ return 'Microphone access granted';
612
+ });
613
+ if (!micOk) allPassed = false;
614
+ // Questions check
615
+ const questionsOk = await checkItem('check-questions', async () => {
616
+ const response = await fetch(getApiUrl('setup_interview'), { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-User-Session-Id': getSessionId() }, body: JSON.stringify({ position_role: position }) });
617
+ const result = await response.json();
618
+ if (!result.questions) throw new Error('Failed to generate questions');
619
+ sessionStorage.setItem('questions', JSON.stringify(result.questions));
620
+ sessionStorage.setItem('currentQuestion', '0');
621
+ sessionStorage.setItem('responses', JSON.stringify([]));
622
+ sessionStorage.setItem('isCodingRole', result.is_coding_role || false);
623
+ return `${result.questions.length} questions generated`;
624
+ });
625
+ if (!questionsOk) allPassed = false;
626
+ if (allPassed) document.getElementById('checklistActions').style.display = 'block';
627
+ else document.getElementById('checklistError').style.display = 'block';
628
+ }
629
+
630
+ async function checkItem(itemId, checkFn) {
631
+ const item = document.getElementById(itemId);
632
+ if (!item) return false;
633
+ try {
634
+ const msg = await checkFn();
635
+ item.innerHTML = `<i class="fas fa-check-circle"></i><span>${msg}</span>`;
636
+ item.classList.add('success');
637
+ return true;
638
+ } catch (error) {
639
+ item.innerHTML = `<i class="fas fa-times-circle"></i><span>${error.message}</span>`;
640
+ item.classList.add('error');
641
+ return false;
642
+ }
643
+ }
644
+
645
+ async function beginInterview() {
646
+ document.getElementById('preExamChecklist')?.remove();
647
+ showLoading('Initializing secure exam environment...');
648
+ try {
649
+ const securityStarted = await window.examSecurity.startExamMode();
650
+ if (!securityStarted) { hideLoading(); return; }
651
+ const proctoringStarted = await window.proctoringSystem.startProctoring();
652
+ if (!proctoringStarted) { hideLoading(); return; }
653
+ window.examSecurity.activateExamMode();
654
+ hideLoading();
655
+ hideAllUIForExam();
656
+ document.getElementById('setupSection')?.classList.add('hidden');
657
+ document.getElementById('interviewSection')?.classList.remove('hidden');
658
+ loadQuestion(0);
659
+ } catch (error) { hideLoading(); showAlert('Error starting interview: ' + error.message, 'error'); }
660
+ }
661
+
662
+ function hideAllUIForExam() {
663
+ const nav = document.querySelector('.premium-nav'); if (nav) nav.style.display = 'none';
664
+ const hero = document.querySelector('.hero-section'); if (hero) hero.style.display = 'none';
665
+ const bg = document.querySelector('.premium-bg'); if (bg) bg.style.opacity = '0.3';
666
+ document.body.classList.add('exam-mode');
667
+ }
668
+
669
+ function showAllUIAfterExam() {
670
+ const nav = document.querySelector('.premium-nav'); if (nav) nav.style.display = 'block';
671
+ const hero = document.querySelector('.hero-section'); if (hero) hero.style.display = 'flex';
672
+ const bg = document.querySelector('.premium-bg'); if (bg) bg.style.opacity = '1';
673
+ document.body.classList.remove('exam-mode');
674
+ }
675
+
676
+ function loadQuestion(index) {
677
+ const questions = JSON.parse(sessionStorage.getItem('questions') || '[]');
678
+ const question = questions[index];
679
+ if (!question) return;
680
+ const answerTextarea = document.getElementById('answerText');
681
+ if (answerTextarea) answerTextarea.value = '';
682
+ document.getElementById('questionText').textContent = question.question;
683
+ const progressNumber = document.querySelector('.progress-number');
684
+ if (progressNumber) progressNumber.textContent = `${index + 1}/${questions.length}`;
685
+ const progress = ((index + 1) / questions.length) * 100;
686
+ const progressRing = document.querySelector('.progress-ring');
687
+ if (progressRing) {
688
  const circumference = 2 * Math.PI * 45;
689
+ progressRing.style.strokeDasharray = `${circumference} ${circumference}`;
690
+ progressRing.style.strokeDashoffset = circumference - (progress / 100) * circumference;
691
  }
692
+ window.examSecurity.startQuestionTimer(() => autoSubmitAndNext(), 180);
693
+ }
694
 
695
+ async function submitAnswer() {
696
+ const currentIndex = parseInt(sessionStorage.getItem('currentQuestion') || '0');
697
+ const questions = JSON.parse(sessionStorage.getItem('questions') || '[]');
698
+ const question = questions[currentIndex];
699
+ const answerTextarea = document.getElementById('answerText');
700
+ const answer = answerTextarea?.value.trim();
701
+ if (!answer) { showAlert('Please provide an answer', 'error'); return; }
702
+ showLoading('Evaluating your answer...');
703
+ try {
704
+ const response = await fetch(getApiUrl('submit_answer'), {
705
+ method: 'POST',
706
+ headers: { 'Content-Type': 'application/json', 'X-User-Session-Id': getSessionId() },
707
+ body: JSON.stringify({ question_id: question.id || `q${currentIndex + 1}`, response_text: answer, duration: window.examSecurity.getQuestionDuration() })
708
+ });
709
+ if (!response.ok) { const err = await response.json(); throw new Error(err.error || 'Failed to submit'); }
710
+ const result = await response.json();
711
+ hideLoading();
712
+ const responses = JSON.parse(sessionStorage.getItem('responses') || '[]');
713
+ responses.push({ question: question.question, answer: answer, evaluation: result.evaluation, time_taken: window.examSecurity.getQuestionDuration() });
714
+ sessionStorage.setItem('responses', JSON.stringify(responses));
715
+ window.examSecurity.stopQuestionTimer();
716
+ if (currentIndex < questions.length - 1) { sessionStorage.setItem('currentQuestion', (currentIndex + 1).toString()); loadQuestion(currentIndex + 1); }
717
+ else { completeInterview(); }
718
+ } catch (error) {
719
+ hideLoading();
720
+ showAlert('Error: ' + error.message, 'error');
721
+ const currentIndex = parseInt(sessionStorage.getItem('currentQuestion') || '0');
722
+ const questions = JSON.parse(sessionStorage.getItem('questions') || '[]');
723
+ if (currentIndex < questions.length - 1) { setTimeout(() => { sessionStorage.setItem('currentQuestion', (currentIndex + 1).toString()); loadQuestion(currentIndex + 1); }, 2000); }
724
  }
725
+ }
726
 
727
+ async function autoSubmitAndNext() {
728
+ const answerTextarea = document.getElementById('answerText');
729
+ const answer = answerTextarea?.value.trim() || '[No answer - Time expired]';
730
+ const currentIndex = parseInt(sessionStorage.getItem('currentQuestion') || '0');
731
+ const questions = JSON.parse(sessionStorage.getItem('questions') || '[]');
732
+ const question = questions[currentIndex];
733
+ try {
734
+ const response = await fetch(getApiUrl('submit_answer'), {
735
+ method: 'POST',
736
+ headers: { 'Content-Type': 'application/json', 'X-User-Session-Id': getSessionId() },
737
+ body: JSON.stringify({ question_id: question.id || `q${currentIndex + 1}`, response_text: answer, duration: '03:00', auto_submitted: true })
738
+ });
739
+ const result = await response.json();
740
+ const responses = JSON.parse(sessionStorage.getItem('responses') || '[]');
741
+ responses.push({ question: question.question, answer: answer, evaluation: result.evaluation, time_taken: '03:00', auto_submitted: true });
742
+ sessionStorage.setItem('responses', JSON.stringify(responses));
743
+ if (currentIndex < questions.length - 1) { sessionStorage.setItem('currentQuestion', (currentIndex + 1).toString()); loadQuestion(currentIndex + 1); }
744
+ else { completeInterview(); }
745
+ } catch (error) {
746
+ if (currentIndex < questions.length - 1) { sessionStorage.setItem('currentQuestion', (currentIndex + 1).toString()); loadQuestion(currentIndex + 1); }
747
+ else { completeInterview(); }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
748
  }
749
+ }
750
 
751
+ async function completeInterview() {
752
+ showLoading('Generating your assessment report...');
753
+ window.examSecurity.endExamMode();
754
+ window.proctoringSystem.stopProctoring();
755
+ showAllUIAfterExam();
756
+ try {
757
+ const response = await fetch(getApiUrl('get_assessment'), { method: 'GET', headers: { 'X-User-Session-Id': getSessionId() } });
758
+ if (!response.ok) { const err = await response.json(); throw new Error(err.error || 'Failed to generate assessment'); }
759
+ const result = await response.json();
760
+ hideLoading();
761
+ if (result.assessment) { sessionStorage.setItem('assessment', JSON.stringify(result.assessment)); showResults(); }
762
+ else { showAlert('Failed to generate assessment', 'error'); }
763
+ } catch (error) {
764
+ hideLoading();
765
+ showAlert('Error: ' + error.message, 'error');
766
+ setTimeout(() => showResults(), 2000);
767
+ }
768
+ }
769
+
770
+ function showResults() {
771
+ document.getElementById('interviewSection')?.classList.add('hidden');
772
+ document.getElementById('resultsSection')?.classList.remove('hidden');
773
+ const assessment = JSON.parse(sessionStorage.getItem('assessment') || '{}');
774
+ if (!assessment || typeof assessment.overallScore === 'undefined') { showAlert('No assessment data', 'error'); return; }
775
+ const overallScoreEl = document.getElementById('overallScore');
776
+ if (overallScoreEl) overallScoreEl.textContent = assessment.overallScore;
777
+ const recommendationEl = document.getElementById('recommendation');
778
+ if (recommendationEl && assessment.recommendation) {
779
+ const rec = assessment.recommendation;
780
+ let icon = 'fa-check-circle', color = 'var(--color-success)';
781
+ if (rec.toLowerCase().includes('not recommended')) { icon = 'fa-times-circle'; color = 'var(--color-danger)'; }
782
+ else if (rec.toLowerCase().includes('consider')) { icon = 'fa-exclamation-circle'; color = 'var(--color-warning)'; }
783
+ recommendationEl.innerHTML = `<i class="fas ${icon}"></i><span>${rec}</span>`;
784
+ recommendationEl.style.borderColor = color; recommendationEl.style.color = color; recommendationEl.style.background = color + '20';
785
+ }
786
+ if (assessment.detailedScores) {
787
+ const scores = assessment.detailedScores;
788
+ const scoreItems = document.querySelectorAll('.score-item');
789
+ if (scoreItems.length >= 3) {
790
+ const techScore = scores.technicalSkills ?? 0;
791
+ scoreItems[0].querySelector('.score-percent').textContent = techScore + '%';
792
+ scoreItems[0].querySelector('.score-bar-fill').style.width = techScore + '%';
793
+ const commScore = scores.communication ?? 0;
794
+ scoreItems[1].querySelector('.score-percent').textContent = commScore + '%';
795
+ scoreItems[1].querySelector('.score-bar-fill').style.width = commScore + '%';
796
+ const softScore = scores.softSkills ?? 0;
797
+ scoreItems[2].querySelector('.score-percent').textContent = softScore + '%';
798
+ scoreItems[2].querySelector('.score-bar-fill').style.width = softScore + '%';
799
  }
800
  }
801
+ const scoreRing = document.querySelector('.results-section .score-ring');
802
+ if (scoreRing) {
803
+ const score = assessment.overallScore ?? 0;
804
+ const circumference = 2 * Math.PI * 90;
805
+ scoreRing.style.strokeDasharray = `${circumference} ${circumference}`;
806
+ scoreRing.style.strokeDashoffset = circumference - (score / 100) * circumference;
807
+ }
808
+ const strengthsList = document.getElementById('strengthsList');
809
+ if (strengthsList && assessment.keyStrengths && assessment.keyStrengths.length > 0) {
810
+ strengthsList.innerHTML = assessment.keyStrengths.map(s => `<li style="padding:0.75rem;background:rgba(16,185,129,0.1);border-left:3px solid var(--color-success);margin:0.5rem 0;border-radius:4px;"><i class="fas fa-check-circle" style="color:var(--color-success);margin-right:0.5rem;"></i>${s}</li>`).join('');
811
+ }
812
+ const improvementsList = document.getElementById('improvementsList');
813
+ if (improvementsList && assessment.areasForImprovement && assessment.areasForImprovement.length > 0) {
814
+ improvementsList.innerHTML = assessment.areasForImprovement.map(a => `<li style="padding:0.75rem;background:rgba(239,68,68,0.1);border-left:3px solid var(--color-danger);margin:0.5rem 0;border-radius:4px;"><i class="fas fa-exclamation-circle" style="color:var(--color-danger);margin-right:0.5rem;"></i>${a}</li>`).join('');
815
+ }
816
+ }
817
  </script>
818
+
819
+ <!-- Additional Styles -->
820
+ <style>
821
+ .alert-toast { position: fixed; top: 100px; right: 20px; padding: 16px 24px; background: var(--glass-bg); backdrop-filter: blur(20px); border: 1px solid var(--glass-border); border-radius: var(--radius-lg); display: flex; align-items: center; gap: 12px; z-index: 10000; transform: translateX(400px); transition: transform 0.3s ease; box-shadow: var(--glass-shadow); }
822
+ .alert-toast.show { transform: translateX(0); }
823
+ .alert-toast.alert-success { border-color: var(--color-success); color: var(--color-success); }
824
+ .alert-toast.alert-error { border-color: var(--color-danger); color: var(--color-danger); }
825
+ .alert-toast.alert-info { border-color: var(--color-primary); color: var(--color-primary); }
826
+ .checklist-items { display: flex; flex-direction: column; gap: 1rem; margin: 1rem 0; }
827
+ .checklist-item { display: flex; align-items: center; gap: 1rem; padding: 1rem; background: var(--color-surface); border-radius: var(--radius-md); border-left: 3px solid var(--color-text-tertiary); }
828
+ .checklist-item.success { border-left-color: var(--color-success); }
829
+ .checklist-item.error { border-left-color: var(--color-danger); }
830
+ .checklist-item i { font-size: 20px; min-width: 20px; }
831
+ .checklist-item.success i { color: var(--color-success); }
832
+ .checklist-item.error i { color: var(--color-danger); }
833
+ .security-notice { position: fixed; top: 0; left: 0; right: 0; bottom: 0; background: rgba(0, 0, 0, 0.95); backdrop-filter: blur(10px); display: flex; align-items: center; justify-content: center; z-index: 99999; }
834
+ .security-notice-content { background: var(--glass-bg); border: 1px solid var(--glass-border); border-radius: var(--radius-xl); padding: var(--spacing-xl); max-width: 500px; text-align: center; }
835
+ .security-icon { width: 80px; height: 80px; background: linear-gradient(135deg, var(--color-primary), var(--color-secondary)); border-radius: 50%; display: flex; align-items: center; justify-content: center; font-size: 36px; margin: 0 auto var(--spacing-lg); }
836
+ .security-rules { list-style: none; text-align: left; margin: var(--spacing-lg) 0; padding: 0; }
837
+ .security-rules li { display: flex; align-items: center; gap: var(--spacing-sm); padding: var(--spacing-sm) 0; color: var(--color-text-secondary); }
838
+ .security-rules i { color: var(--color-success); }
839
+ .security-warning { position: fixed; top: 100px; right: -400px; padding: var(--spacing-md) var(--spacing-lg); background: rgba(239, 68, 68, 0.95); border: 2px solid var(--color-danger); border-radius: var(--radius-lg); color: white; font-weight: 600; display: flex; align-items: center; gap: var(--spacing-sm); z-index: 99999; transition: right 0.3s ease; box-shadow: 0 8px 32px rgba(239, 68, 68, 0.5); }
840
+ .security-warning.show { right: 20px; }
841
+ .critical-warning-overlay { position: fixed; top: 0; left: 0; right: 0; bottom: 0; background: rgba(0, 0, 0, 0.95); backdrop-filter: blur(10px); display: flex; align-items: center; justify-content: center; z-index: 99999; opacity: 0; transition: opacity 0.3s ease; }
842
+ .critical-warning-overlay.show { opacity: 1; }
843
+ .critical-warning-box { background: var(--glass-bg); border: 2px solid var(--color-danger); border-radius: var(--radius-xl); padding: var(--spacing-xl); max-width: 500px; text-align: center; animation: shake 0.5s; }
844
+ @keyframes shake { 0%, 100% { transform: translateX(0); } 25% { transform: translateX(-10px); } 75% { transform: translateX(10px); } }
845
+ .warning-icon { width: 100px; height: 100px; background: rgba(239, 68, 68, 0.2); border-radius: 50%; display: flex; align-items: center; justify-content: center; font-size: 48px; color: var(--color-danger); margin: 0 auto var(--spacing-lg); animation: pulse 1s infinite; }
846
+ @keyframes pulse { 0%, 100% { transform: scale(1); } 50% { transform: scale(1.1); } }
847
+ .critical-warning-box h3 { font-size: 28px; color: var(--color-danger); margin-bottom: var(--spacing-md); }
848
+ .critical-warning-box p { font-size: 16px; color: var(--color-text-secondary); line-height: 1.8; margin-bottom: var(--spacing-lg); }
849
+ </style>
850
  </body>
851
  </html>