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

Upload folder using huggingface_hub

Browse files
Dockerfile CHANGED
@@ -12,7 +12,8 @@ COPY requirements.txt .
12
  RUN pip install --no-cache-dir -r requirements.txt
13
 
14
  # Copy application files
15
- COPY . .
 
16
 
17
  # Expose port 7860 (Hugging Face default)
18
  EXPOSE 7860
 
12
  RUN pip install --no-cache-dir -r requirements.txt
13
 
14
  # Copy application files
15
+ COPY app.py .
16
+ COPY static/ static/
17
 
18
  # Expose port 7860 (Hugging Face default)
19
  EXPOSE 7860
README.md CHANGED
@@ -1,41 +1,40 @@
1
  ---
2
- title: HR-AI Interview Simulation
3
  emoji: 🤖
4
  colorFrom: indigo
5
  colorTo: purple
6
  sdk: docker
7
  app_file: app.py
8
- pinned: false
9
  license: mit
10
  ---
11
 
12
- # 🤖 HR-AI Interview Simulation Platform
13
 
14
- AI-Powered Technical Interview System using Groq LLaMA 3.3 70B
15
 
16
- ## Features
17
- - 📄 Resume Analysis (PDF upload)
18
- - 🎯 Dynamic Question Generation (10 questions)
19
- - ✅ Real-time Answer Evaluation with scoring
20
- - 📊 Comprehensive Assessment Reports
 
21
 
22
- ## How to Use
23
- 1. **Upload Resume** - Upload your PDF resume
24
- 2. **Review Profile** - AI extracts your information
25
- 3. **Start Interview** - Answer 10 AI-generated questions
26
- 4. **Get Assessment** - Receive detailed evaluation report
 
27
 
28
- ## Technology Stack
29
  - **Backend**: Python Flask
30
  - **AI**: Groq LLaMA 3.3 70B
31
- - **Frontend**: HTML5, CSS3, JavaScript
32
 
33
- ## Setup (for cloning)
34
- Add your `GROQ_API_KEY` as a secret in Space settings:
35
- 1. Go to Settings → Repository secrets
36
- 2. Add `GROQ_API_KEY` with your Groq API key
37
-
38
- Get your API key at: https://console.groq.com
39
 
40
  ---
41
- Made with ❤️ by Chris Daniel
 
1
  ---
2
+ title: HR-AI Interview Simulation V2
3
  emoji: 🤖
4
  colorFrom: indigo
5
  colorTo: purple
6
  sdk: docker
7
  app_file: app.py
8
+ pinned: true
9
  license: mit
10
  ---
11
 
12
+ # 🤖 HR-AI Interview Simulation V2 - Premium Edition
13
 
14
+ **Next-Generation AI-Powered Technical Interview Platform**
15
 
16
+ ## Features
17
+ - 📄 **Resume Analysis** - AI-powered PDF parsing
18
+ - 🎯 **Dynamic Questions** - 15 role-specific questions
19
+ - ✅ **Real-time Evaluation** - Instant scoring & feedback
20
+ - 📊 **Assessment Reports** - Comprehensive insights
21
+ - 🎨 **Premium UI** - Modern glass-morphism design
22
 
23
+ ## 🚀 How to Use
24
+ 1. Upload your PDF resume
25
+ 2. Review AI-extracted profile
26
+ 3. Enter target position
27
+ 4. Answer interview questions
28
+ 5. Get detailed assessment
29
 
30
+ ## 🛠 Tech Stack
31
  - **Backend**: Python Flask
32
  - **AI**: Groq LLaMA 3.3 70B
33
+ - **Frontend**: Premium HTML5/CSS3/JS
34
 
35
+ ## ⚙️ Setup
36
+ Add `GROQ_API_KEY` secret in Space settings.
37
+ Get key: https://console.groq.com
 
 
 
38
 
39
  ---
40
+ **HR-AI Simulation V2** by Chris Daniel
app.py CHANGED
@@ -1,10 +1,10 @@
1
  """
2
- HR-AI Interview Simulation Platform - Full Flask Application
3
- Deployed on Hugging Face Spaces with Docker
4
  """
5
 
6
  import os
7
- from flask import Flask, request, jsonify, render_template_string, send_from_directory
8
  from flask_cors import CORS
9
  from groq import Groq
10
  import json
@@ -12,21 +12,18 @@ import uuid
12
  import re
13
  from datetime import datetime
14
  from PyPDF2 import PdfReader
15
- import io
16
 
17
  # Configuration
18
  API_KEY = os.getenv('GROQ_API_KEY', '')
19
  GROQ_MODEL = 'llama-3.3-70b-versatile'
20
 
21
- app = Flask(__name__)
22
  CORS(app)
23
 
24
- # Initialize Groq client
25
  client = None
26
  if API_KEY:
27
  client = Groq(api_key=API_KEY)
28
 
29
- # In-memory sessions
30
  sessions = {}
31
 
32
  def get_or_create_session(session_id):
@@ -41,56 +38,41 @@ def get_or_create_session(session_id):
41
  return sessions[session_id]
42
 
43
  def extract_text_from_pdf(pdf_file):
44
- text = ""
45
  try:
46
  reader = PdfReader(pdf_file)
47
- for page in reader.pages:
48
- text += page.extract_text() or ""
49
- return text
50
- except Exception as e:
51
- print(f"PDF Error: {e}")
52
  return ""
53
 
54
-
55
  def extract_json_from_response(text):
56
- match = re.search(r'```(?:json)?\s*([\s\S]*?)\s*```|({\s*".*?"[\s\S]*})|(\[\s*[\s\S]*\])', text, re.DOTALL)
57
  if match:
58
- json_str = match.group(1) or match.group(2) or match.group(3)
59
- if json_str:
60
- try:
61
- return json.loads(json_str)
62
- except:
63
- pass
64
- if '{' in text and '}' in text:
65
- try:
66
- return json.loads(text[text.find('{'):text.rfind('}')+1])
67
- except:
68
- pass
69
  return None
70
 
 
71
  def generate_content_with_groq(prompt):
72
- if not client:
73
- return None
74
  try:
75
- response = client.chat.completions.create(
76
- messages=[
77
- {"role": "system", "content": "Return only valid JSON as requested."},
78
- {"role": "user", "content": prompt}
79
- ],
80
- model=GROQ_MODEL,
81
- temperature=0.7,
82
- max_tokens=4096
83
  )
84
- content = response.choices[0].message.content
85
- if content:
86
- data = extract_json_from_response(content)
87
- if data:
88
- return json.dumps(data)
89
- return None
90
  except Exception as e:
91
  print(f"Groq Error: {e}")
92
  return None
93
 
 
 
 
 
 
94
  # API Endpoints
95
  @app.route('/upload_resume', methods=['POST'])
96
  def upload_resume():
@@ -98,28 +80,23 @@ def upload_resume():
98
  session = get_or_create_session(session_id)
99
 
100
  if 'resume' not in request.files:
101
- return jsonify({'error': 'No resume file provided'}), 400
102
 
103
  file = request.files['resume']
104
- if file.filename == '':
105
- return jsonify({'error': 'No selected file'}), 400
106
-
107
- resume_content = extract_text_from_pdf(file)
108
- if not resume_content.strip():
109
- return jsonify({'error': 'Could not extract text from PDF'}), 400
110
 
111
- prompt = f"""Analyze this resume and extract: name, email, experience, key_skills (array), inferred_position.
112
- Return JSON: {{"name":"","email":"","experience":"","key_skills":[],"inferred_position":""}}
113
- Resume: {resume_content[:8000]}"""
114
 
115
- ai_response = generate_content_with_groq(prompt)
116
- if ai_response:
117
- profile = json.loads(ai_response)
118
- if not isinstance(profile.get('key_skills'), list):
119
- profile['key_skills'] = []
120
  session['candidate_profile'] = profile
121
- return jsonify({'message': 'Resume processed', 'candidate_profile': profile, 'session_id': session_id}), 200
122
- return jsonify({'error': 'AI failed to parse resume'}), 500
123
 
124
  @app.route('/setup_interview', methods=['POST'])
125
  def setup_interview():
@@ -136,21 +113,22 @@ def setup_interview():
136
  return jsonify({'error': 'Position and profile required'}), 400
137
 
138
  skills = ", ".join(profile.get('key_skills', []))
139
- prompt = f"""Generate 10 interview questions for {profile.get('name','Candidate')} applying for '{position}'.
140
- Experience: {profile.get('experience','N/A')}. Skills: {skills}.
141
- Generate: 6 Technical, 2 Soft Skills, 2 Communication questions.
142
- Return: {{"questions":[{{"id":"q1","question":"...","tags":["technical"]}}]}}"""
143
-
144
- ai_response = generate_content_with_groq(prompt)
145
- if ai_response:
146
- result = json.loads(ai_response)
147
- questions = result.get('questions', [])
148
- session['interview_questions'] = questions
 
 
149
  session['interview_responses'] = []
150
  session['interview_start_time'] = datetime.now().isoformat()
151
- return jsonify({'message': 'Questions generated', 'questions': questions, 'is_coding_role': False}), 200
152
- return jsonify({'error': 'Failed to generate questions'}), 500
153
-
154
 
155
  @app.route('/submit_answer', methods=['POST'])
156
  def submit_answer():
@@ -160,35 +138,25 @@ def submit_answer():
160
 
161
  session = sessions[session_id]
162
  data = request.get_json()
163
- question_id = data.get('question_id')
164
- response_text = data.get('response_text', '')
165
  duration = data.get('duration', '00:00')
166
 
167
- question_obj = next((q for q in session['interview_questions'] if q['id'] == question_id), None)
168
- if not question_obj:
169
- return jsonify({'error': 'Question not found'}), 404
170
 
171
- prompt = f"""Evaluate this interview response strictly:
172
- Question: {question_obj['question']}
173
- Answer: {response_text}
174
- Return: {{"technicalScore":85,"communicationScore":90,"relevanceScore":88,"feedback":"..."}}"""
175
 
176
- ai_response = generate_content_with_groq(prompt)
177
- if ai_response:
178
- evaluation = json.loads(ai_response)
179
- score = (evaluation.get('technicalScore',0) + evaluation.get('communicationScore',0) + evaluation.get('relevanceScore',0)) / 3
180
- evaluation['score'] = round(score)
181
-
182
- session['interview_responses'].append({
183
- 'question_id': question_id,
184
- 'question': question_obj['question'],
185
- 'tags': question_obj.get('tags', []),
186
- 'response': response_text,
187
- 'duration': duration,
188
- 'evaluation': evaluation
189
- })
190
- return jsonify({'message': 'Answer evaluated', 'evaluation': evaluation}), 200
191
- return jsonify({'error': 'Evaluation failed'}), 500
192
 
193
  @app.route('/get_assessment', methods=['GET'])
194
  def get_assessment():
@@ -198,360 +166,28 @@ def get_assessment():
198
 
199
  session = sessions[session_id]
200
  if not session.get('interview_responses'):
201
- return jsonify({'error': 'No responses to assess'}), 400
202
 
203
  profile = session['candidate_profile']
204
  responses = session['interview_responses']
 
205
 
206
- summary = "\n".join([f"Q: {r['question'][:80]}... Score: {r['evaluation']['score']}%" for r in responses[:5]])
207
- avg_score = sum(r['evaluation']['score'] for r in responses) / len(responses)
208
-
209
- prompt = f"""Generate assessment for {profile.get('name','Candidate')}.
210
- Average Score: {avg_score:.1f}%. Questions: {len(responses)}.
211
- Summary: {summary}
212
- Return: {{"overallScore":85,"recommendation":"Recommended","keyStrengths":["..."],"areasForImprovement":["..."],"detailedScores":{{"technicalSkills":85,"communication":80,"softSkills":78}}}}"""
213
 
214
- ai_response = generate_content_with_groq(prompt)
215
- if ai_response:
216
- assessment = json.loads(ai_response)
217
- assessment['detailedQuestionAnalysis'] = [{
218
- 'question': r['question'],
219
- 'score': r['evaluation']['score'],
220
- 'technicalScore': r['evaluation'].get('technicalScore', 0),
221
- 'communicationScore': r['evaluation'].get('communicationScore', 0),
222
- 'relevanceScore': r['evaluation'].get('relevanceScore', 0)
223
- } for r in responses]
224
- return jsonify({'message': 'Assessment generated', 'assessment': assessment}), 200
225
-
226
- # Fallback
227
- return jsonify({'assessment': {
228
- 'overallScore': round(avg_score),
229
- 'recommendation': 'Recommended' if avg_score >= 70 else 'Needs Improvement',
230
- 'keyStrengths': ['Completed interview'],
231
- 'areasForImprovement': ['Review feedback'],
232
- 'detailedScores': {'technicalSkills': round(avg_score), 'communication': round(avg_score), 'softSkills': round(avg_score)}
233
- }}), 200
234
 
235
  @app.route('/log_security', methods=['POST'])
236
  def log_security():
237
  return jsonify({'message': 'Logged'}), 200
238
 
239
-
240
- # Main UI Route
241
- @app.route('/')
242
- def index():
243
- return render_template_string(HTML_TEMPLATE)
244
-
245
- HTML_TEMPLATE = '''
246
- <!DOCTYPE html>
247
- <html lang="en">
248
- <head>
249
- <meta charset="UTF-8">
250
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
251
- <title>HR-AI Interview Platform</title>
252
- <link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap" rel="stylesheet">
253
- <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
254
- <style>
255
- * { margin: 0; padding: 0; box-sizing: border-box; }
256
- body { font-family: 'Inter', sans-serif; background: linear-gradient(135deg, #1a1a2e 0%, #16213e 50%, #0f3460 100%); min-height: 100vh; color: #fff; }
257
- .container { max-width: 900px; margin: 0 auto; padding: 20px; }
258
- .header { text-align: center; padding: 40px 0; }
259
- .header h1 { font-size: 2.5rem; background: linear-gradient(90deg, #667eea, #764ba2); -webkit-background-clip: text; -webkit-text-fill-color: transparent; }
260
- .header p { color: #a0aec0; margin-top: 10px; }
261
- .card { background: rgba(255,255,255,0.05); backdrop-filter: blur(10px); border-radius: 20px; padding: 30px; margin: 20px 0; border: 1px solid rgba(255,255,255,0.1); }
262
- .card h2 { color: #667eea; margin-bottom: 20px; display: flex; align-items: center; gap: 10px; }
263
- .upload-area { border: 2px dashed rgba(102,126,234,0.5); border-radius: 15px; padding: 40px; text-align: center; cursor: pointer; transition: all 0.3s; }
264
- .upload-area:hover { border-color: #667eea; background: rgba(102,126,234,0.1); }
265
- .upload-area i { font-size: 3rem; color: #667eea; margin-bottom: 15px; }
266
- input[type="file"] { display: none; }
267
- input[type="text"], textarea { width: 100%; padding: 15px; border-radius: 10px; border: 1px solid rgba(255,255,255,0.2); background: rgba(255,255,255,0.05); color: #fff; font-size: 1rem; margin: 10px 0; }
268
- textarea { min-height: 150px; resize: vertical; }
269
- .btn { padding: 15px 30px; border-radius: 10px; border: none; font-size: 1rem; font-weight: 600; cursor: pointer; transition: all 0.3s; display: inline-flex; align-items: center; gap: 10px; }
270
- .btn-primary { background: linear-gradient(90deg, #667eea, #764ba2); color: #fff; }
271
- .btn-primary:hover { transform: translateY(-2px); box-shadow: 0 10px 30px rgba(102,126,234,0.4); }
272
- .btn-secondary { background: rgba(255,255,255,0.1); color: #fff; }
273
- .hidden { display: none !important; }
274
- .profile-info { display: grid; grid-template-columns: repeat(2, 1fr); gap: 15px; }
275
- .profile-item { background: rgba(255,255,255,0.05); padding: 15px; border-radius: 10px; }
276
- .profile-item label { color: #a0aec0; font-size: 0.85rem; }
277
- .profile-item span { display: block; font-weight: 600; margin-top: 5px; }
278
- .skills-tags { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 10px; }
279
- .skill-tag { background: rgba(102,126,234,0.3); padding: 5px 12px; border-radius: 20px; font-size: 0.85rem; }
280
- .question-box { background: rgba(102,126,234,0.1); padding: 25px; border-radius: 15px; margin: 20px 0; border-left: 4px solid #667eea; }
281
- .question-tags { display: flex; gap: 8px; margin-bottom: 10px; }
282
- .tag { background: rgba(118,75,162,0.3); padding: 4px 10px; border-radius: 15px; font-size: 0.75rem; }
283
- .progress-bar { height: 8px; background: rgba(255,255,255,0.1); border-radius: 10px; overflow: hidden; margin: 20px 0; }
284
- .progress-fill { height: 100%; background: linear-gradient(90deg, #667eea, #764ba2); transition: width 0.3s; }
285
- .score-display { text-align: center; padding: 30px; }
286
- .score-circle { width: 150px; height: 150px; border-radius: 50%; background: conic-gradient(#667eea var(--score), rgba(255,255,255,0.1) 0); display: flex; align-items: center; justify-content: center; margin: 0 auto 20px; }
287
- .score-inner { width: 120px; height: 120px; border-radius: 50%; background: #1a1a2e; display: flex; flex-direction: column; align-items: center; justify-content: center; }
288
- .score-value { font-size: 2.5rem; font-weight: 700; }
289
- .score-label { color: #a0aec0; font-size: 0.9rem; }
290
- .recommendation { padding: 15px 25px; border-radius: 10px; display: inline-block; font-weight: 600; }
291
- .rec-high { background: rgba(72,187,120,0.2); color: #48bb78; }
292
- .rec-good { background: rgba(102,126,234,0.2); color: #667eea; }
293
- .rec-low { background: rgba(237,137,54,0.2); color: #ed8936; }
294
- .feedback-box { background: rgba(72,187,120,0.1); padding: 20px; border-radius: 10px; margin: 15px 0; border-left: 4px solid #48bb78; }
295
- .loading { display: none; position: fixed; top: 0; left: 0; right: 0; bottom: 0; background: rgba(0,0,0,0.8); z-index: 1000; align-items: center; justify-content: center; flex-direction: column; }
296
- .loading.show { display: flex; }
297
- .spinner { width: 50px; height: 50px; border: 4px solid rgba(255,255,255,0.1); border-top-color: #667eea; border-radius: 50%; animation: spin 1s linear infinite; }
298
- @keyframes spin { to { transform: rotate(360deg); } }
299
- .timer { font-size: 1.5rem; font-weight: 600; color: #667eea; }
300
- .actions { display: flex; gap: 15px; margin-top: 20px; flex-wrap: wrap; }
301
- .breakdown { margin-top: 20px; }
302
- .breakdown-item { display: flex; justify-content: space-between; padding: 10px 0; border-bottom: 1px solid rgba(255,255,255,0.1); }
303
- </style>
304
- </head>
305
- <body>
306
- <div class="container">
307
- <div class="header">
308
- <h1><i class="fas fa-robot"></i> HR-AI Interview Platform</h1>
309
- <p>AI-Powered Technical Interview Simulation</p>
310
- </div>
311
-
312
- <!-- Step 1: Upload Resume -->
313
- <div class="card" id="uploadSection">
314
- <h2><i class="fas fa-file-upload"></i> Step 1: Upload Resume</h2>
315
- <div class="upload-area" onclick="document.getElementById('resumeInput').click()">
316
- <i class="fas fa-cloud-upload-alt"></i>
317
- <h3>Drop your resume here</h3>
318
- <p>or click to browse (PDF only)</p>
319
- </div>
320
- <input type="file" id="resumeInput" accept=".pdf" onchange="uploadResume(this.files[0])">
321
- </div>
322
-
323
- <!-- Step 2: Profile & Setup -->
324
- <div class="card hidden" id="setupSection">
325
- <h2><i class="fas fa-user-check"></i> Step 2: Candidate Profile</h2>
326
- <div class="profile-info" id="profileDisplay"></div>
327
- <div style="margin-top: 20px;">
328
- <label>Position/Role:</label>
329
- <input type="text" id="positionInput" placeholder="e.g., Senior Software Engineer">
330
- </div>
331
- <div class="actions">
332
- <button class="btn btn-primary" onclick="startInterview()"><i class="fas fa-play"></i> Start Interview</button>
333
- </div>
334
- </div>
335
-
336
- <!-- Step 3: Interview -->
337
- <div class="card hidden" id="interviewSection">
338
- <h2><i class="fas fa-comments"></i> Step 3: Interview</h2>
339
- <div class="progress-bar"><div class="progress-fill" id="progressFill" style="width: 0%"></div></div>
340
- <p id="progressText">Question 1 of 10</p>
341
- <div class="question-box" id="questionBox"></div>
342
- <textarea id="answerInput" placeholder="Type your answer here..."></textarea>
343
- <div class="feedback-box hidden" id="feedbackBox"></div>
344
- <div class="actions">
345
- <button class="btn btn-primary" onclick="submitAnswer()"><i class="fas fa-paper-plane"></i> Submit Answer</button>
346
- </div>
347
- </div>
348
-
349
- <!-- Step 4: Assessment -->
350
- <div class="card hidden" id="assessmentSection">
351
- <h2><i class="fas fa-chart-pie"></i> Assessment Report</h2>
352
- <div class="score-display" id="scoreDisplay"></div>
353
- <div class="breakdown" id="breakdownDisplay"></div>
354
- <div class="actions">
355
- <button class="btn btn-secondary" onclick="location.reload()"><i class="fas fa-redo"></i> New Interview</button>
356
- </div>
357
- </div>
358
- </div>
359
-
360
- <!-- Loading Overlay -->
361
- <div class="loading" id="loading">
362
- <div class="spinner"></div>
363
- <p style="margin-top: 20px;" id="loadingText">Processing...</p>
364
- </div>
365
-
366
- <script>
367
- let sessionId = 'session_' + Date.now();
368
- let questions = [];
369
- let currentQuestion = 0;
370
- let responses = [];
371
-
372
- function showLoading(text) {
373
- document.getElementById('loadingText').textContent = text || 'Processing...';
374
- document.getElementById('loading').classList.add('show');
375
- }
376
- function hideLoading() { document.getElementById('loading').classList.remove('show'); }
377
-
378
- async function uploadResume(file) {
379
- if (!file) return;
380
- showLoading('Analyzing resume with AI...');
381
-
382
- const formData = new FormData();
383
- formData.append('resume', file);
384
-
385
- try {
386
- const resp = await fetch('/upload_resume', {
387
- method: 'POST',
388
- headers: { 'X-User-Session-Id': sessionId },
389
- body: formData
390
- });
391
- const data = await resp.json();
392
- hideLoading();
393
-
394
- if (data.candidate_profile) {
395
- const p = data.candidate_profile;
396
- document.getElementById('profileDisplay').innerHTML = `
397
- <div class="profile-item"><label>Name</label><span>${p.name || 'N/A'}</span></div>
398
- <div class="profile-item"><label>Email</label><span>${p.email || 'N/A'}</span></div>
399
- <div class="profile-item"><label>Experience</label><span>${p.experience || 'N/A'}</span></div>
400
- <div class="profile-item"><label>Suggested Role</label><span>${p.inferred_position || 'N/A'}</span></div>
401
- <div class="profile-item" style="grid-column: span 2;"><label>Skills</label>
402
- <div class="skills-tags">${(p.key_skills || []).map(s => `<span class="skill-tag">${s}</span>`).join('')}</div>
403
- </div>
404
- `;
405
- document.getElementById('positionInput').value = p.inferred_position || '';
406
- document.getElementById('uploadSection').classList.add('hidden');
407
- document.getElementById('setupSection').classList.remove('hidden');
408
- } else {
409
- alert('Error: ' + (data.error || 'Failed to analyze resume'));
410
- }
411
- } catch (e) {
412
- hideLoading();
413
- alert('Error: ' + e.message);
414
- }
415
- }
416
-
417
- async function startInterview() {
418
- const position = document.getElementById('positionInput').value;
419
- if (!position) { alert('Please enter a position'); return; }
420
-
421
- showLoading('Generating interview questions...');
422
- try {
423
- const resp = await fetch('/setup_interview', {
424
- method: 'POST',
425
- headers: { 'Content-Type': 'application/json', 'X-User-Session-Id': sessionId },
426
- body: JSON.stringify({ position_role: position })
427
- });
428
- const data = await resp.json();
429
- hideLoading();
430
-
431
- if (data.questions) {
432
- questions = data.questions;
433
- currentQuestion = 0;
434
- responses = [];
435
- document.getElementById('setupSection').classList.add('hidden');
436
- document.getElementById('interviewSection').classList.remove('hidden');
437
- showQuestion();
438
- } else {
439
- alert('Error: ' + (data.error || 'Failed to generate questions'));
440
- }
441
- } catch (e) {
442
- hideLoading();
443
- alert('Error: ' + e.message);
444
- }
445
- }
446
-
447
- function showQuestion() {
448
- const q = questions[currentQuestion];
449
- const progress = ((currentQuestion + 1) / questions.length) * 100;
450
- document.getElementById('progressFill').style.width = progress + '%';
451
- document.getElementById('progressText').textContent = `Question ${currentQuestion + 1} of ${questions.length}`;
452
- document.getElementById('questionBox').innerHTML = `
453
- <div class="question-tags">${(q.tags || []).map(t => `<span class="tag">${t}</span>`).join('')}</div>
454
- <p style="font-size: 1.1rem; line-height: 1.6;">${q.question}</p>
455
- `;
456
- document.getElementById('answerInput').value = '';
457
- document.getElementById('feedbackBox').classList.add('hidden');
458
- }
459
-
460
- async function submitAnswer() {
461
- const answer = document.getElementById('answerInput').value.trim();
462
- if (!answer) { alert('Please provide an answer'); return; }
463
-
464
- showLoading('Evaluating your answer...');
465
- try {
466
- const resp = await fetch('/submit_answer', {
467
- method: 'POST',
468
- headers: { 'Content-Type': 'application/json', 'X-User-Session-Id': sessionId },
469
- body: JSON.stringify({
470
- question_id: questions[currentQuestion].id,
471
- response_text: answer,
472
- duration: '02:00'
473
- })
474
- });
475
- const data = await resp.json();
476
- hideLoading();
477
-
478
- if (data.evaluation) {
479
- const e = data.evaluation;
480
- responses.push({ question: questions[currentQuestion].question, evaluation: e });
481
-
482
- document.getElementById('feedbackBox').innerHTML = `
483
- <strong>Score: ${e.score}/100</strong><br>
484
- Technical: ${e.technicalScore}% | Communication: ${e.communicationScore}% | Relevance: ${e.relevanceScore}%<br>
485
- <em>${e.feedback}</em>
486
- `;
487
- document.getElementById('feedbackBox').classList.remove('hidden');
488
-
489
- currentQuestion++;
490
- if (currentQuestion < questions.length) {
491
- setTimeout(showQuestion, 2000);
492
- } else {
493
- setTimeout(showAssessment, 2000);
494
- }
495
- }
496
- } catch (e) {
497
- hideLoading();
498
- alert('Error: ' + e.message);
499
- }
500
- }
501
-
502
- async function showAssessment() {
503
- showLoading('Generating assessment report...');
504
- try {
505
- const resp = await fetch('/get_assessment', {
506
- headers: { 'X-User-Session-Id': sessionId }
507
- });
508
- const data = await resp.json();
509
- hideLoading();
510
-
511
- if (data.assessment) {
512
- const a = data.assessment;
513
- const score = a.overallScore || 0;
514
- const recClass = score >= 85 ? 'rec-high' : score >= 70 ? 'rec-good' : 'rec-low';
515
-
516
- document.getElementById('scoreDisplay').innerHTML = `
517
- <div class="score-circle" style="--score: ${score * 3.6}deg">
518
- <div class="score-inner">
519
- <span class="score-value">${score}</span>
520
- <span class="score-label">Overall</span>
521
- </div>
522
- </div>
523
- <div class="recommendation ${recClass}">${a.recommendation || 'N/A'}</div>
524
- `;
525
-
526
- let breakdown = '<h3>Detailed Scores</h3>';
527
- if (a.detailedScores) {
528
- breakdown += `
529
- <div class="breakdown-item"><span>Technical Skills</span><span>${a.detailedScores.technicalSkills}%</span></div>
530
- <div class="breakdown-item"><span>Communication</span><span>${a.detailedScores.communication}%</span></div>
531
- <div class="breakdown-item"><span>Soft Skills</span><span>${a.detailedScores.softSkills}%</span></div>
532
- `;
533
- }
534
- if (a.keyStrengths) {
535
- breakdown += '<h3 style="margin-top:20px">Key Strengths</h3><ul>' + a.keyStrengths.map(s => `<li>${s}</li>`).join('') + '</ul>';
536
- }
537
- if (a.areasForImprovement) {
538
- breakdown += '<h3 style="margin-top:20px">Areas for Improvement</h3><ul>' + a.areasForImprovement.map(s => `<li>${s}</li>`).join('') + '</ul>';
539
- }
540
- document.getElementById('breakdownDisplay').innerHTML = breakdown;
541
-
542
- document.getElementById('interviewSection').classList.add('hidden');
543
- document.getElementById('assessmentSection').classList.remove('hidden');
544
- }
545
- } catch (e) {
546
- hideLoading();
547
- alert('Error: ' + e.message);
548
- }
549
- }
550
- </script>
551
- </body>
552
- </html>
553
- '''
554
-
555
  if __name__ == '__main__':
556
  port = int(os.environ.get('PORT', 7860))
557
  app.run(host='0.0.0.0', port=port, debug=False)
 
1
  """
2
+ HR-AI Interview Simulation V2 - Full Premium UI
3
+ Complete Flask Application with Premium Frontend
4
  """
5
 
6
  import os
7
+ from flask import Flask, request, jsonify, send_from_directory, send_file
8
  from flask_cors import CORS
9
  from groq import Groq
10
  import json
 
12
  import re
13
  from datetime import datetime
14
  from PyPDF2 import PdfReader
 
15
 
16
  # Configuration
17
  API_KEY = os.getenv('GROQ_API_KEY', '')
18
  GROQ_MODEL = 'llama-3.3-70b-versatile'
19
 
20
+ app = Flask(__name__, static_folder='static', static_url_path='')
21
  CORS(app)
22
 
 
23
  client = None
24
  if API_KEY:
25
  client = Groq(api_key=API_KEY)
26
 
 
27
  sessions = {}
28
 
29
  def get_or_create_session(session_id):
 
38
  return sessions[session_id]
39
 
40
  def extract_text_from_pdf(pdf_file):
 
41
  try:
42
  reader = PdfReader(pdf_file)
43
+ return "".join(p.extract_text() or "" for p in reader.pages)
44
+ except:
 
 
 
45
  return ""
46
 
 
47
  def extract_json_from_response(text):
48
+ match = re.search(r'```(?:json)?\s*([\s\S]*?)\s*```', text)
49
  if match:
50
+ try: return json.loads(match.group(1))
51
+ except: pass
52
+ if '{' in text:
53
+ try: return json.loads(text[text.find('{'):text.rfind('}')+1])
54
+ except: pass
 
 
 
 
 
 
55
  return None
56
 
57
+
58
  def generate_content_with_groq(prompt):
59
+ if not client: return None
 
60
  try:
61
+ resp = client.chat.completions.create(
62
+ messages=[{"role": "system", "content": "Return only valid JSON."}, {"role": "user", "content": prompt}],
63
+ model=GROQ_MODEL, temperature=0.7, max_tokens=4096
 
 
 
 
 
64
  )
65
+ data = extract_json_from_response(resp.choices[0].message.content)
66
+ return json.dumps(data) if data else None
 
 
 
 
67
  except Exception as e:
68
  print(f"Groq Error: {e}")
69
  return None
70
 
71
+ # Serve main page
72
+ @app.route('/')
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():
 
80
  session = get_or_create_session(session_id)
81
 
82
  if 'resume' not in request.files:
83
+ return jsonify({'error': 'No resume file'}), 400
84
 
85
  file = request.files['resume']
86
+ text = extract_text_from_pdf(file)
87
+ if not text.strip():
88
+ return jsonify({'error': 'Cannot extract text from PDF'}), 400
 
 
 
89
 
90
+ prompt = f'''Analyze resume: {{"name":"","email":"","experience":"","key_skills":[],"inferred_position":""}}
91
+ Resume: {text[:8000]}'''
 
92
 
93
+ resp = generate_content_with_groq(prompt)
94
+ if resp:
95
+ profile = json.loads(resp)
96
+ if not isinstance(profile.get('key_skills'), list): profile['key_skills'] = []
 
97
  session['candidate_profile'] = profile
98
+ return jsonify({'message': 'Success', 'candidate_profile': profile, 'session_id': session_id}), 200
99
+ return jsonify({'error': 'AI failed'}), 500
100
 
101
  @app.route('/setup_interview', methods=['POST'])
102
  def setup_interview():
 
113
  return jsonify({'error': 'Position and profile required'}), 400
114
 
115
  skills = ", ".join(profile.get('key_skills', []))
116
+ is_coding = any(k in position.lower() for k in ['developer','engineer','programmer','software'])
117
+
118
+ coding_text = "- 2 Coding questions (simple problems)" if is_coding else ""
119
+ prompt = f'''Generate interview questions for {profile.get('name','Candidate')} for "{position}".
120
+ Skills: {skills}. Experience: {profile.get('experience','N/A')}.
121
+ Generate: 10 Technical, 3 Soft Skills, 2 Communication {coding_text}
122
+ Return: {{"questions":[{{"id":"q1","question":"...","tags":["technical"]}}]}}'''
123
+
124
+ resp = generate_content_with_groq(prompt)
125
+ if resp:
126
+ result = json.loads(resp)
127
+ session['interview_questions'] = result.get('questions', [])
128
  session['interview_responses'] = []
129
  session['interview_start_time'] = datetime.now().isoformat()
130
+ return jsonify({'message': 'Generated', 'questions': result.get('questions', []), 'is_coding_role': is_coding}), 200
131
+ return jsonify({'error': 'Failed'}), 500
 
132
 
133
  @app.route('/submit_answer', methods=['POST'])
134
  def submit_answer():
 
138
 
139
  session = sessions[session_id]
140
  data = request.get_json()
141
+ qid = data.get('question_id')
142
+ answer = data.get('response_text', '')
143
  duration = data.get('duration', '00:00')
144
 
145
+ q = next((x for x in session['interview_questions'] if x['id'] == qid), None)
146
+ if not q: return jsonify({'error': 'Question not found'}), 404
 
147
 
148
+ prompt = f'''Evaluate strictly:
149
+ Q: {q['question']}
150
+ A: {answer}
151
+ Return: {{"technicalScore":85,"communicationScore":90,"relevanceScore":88,"feedback":"..."}}'''
152
 
153
+ resp = generate_content_with_groq(prompt)
154
+ if resp:
155
+ ev = json.loads(resp)
156
+ ev['score'] = round((ev.get('technicalScore',0)+ev.get('communicationScore',0)+ev.get('relevanceScore',0))/3)
157
+ session['interview_responses'].append({'question_id':qid,'question':q['question'],'tags':q.get('tags',[]),'response':answer,'duration':duration,'evaluation':ev})
158
+ return jsonify({'message': 'Evaluated', 'evaluation': ev}), 200
159
+ return jsonify({'error': 'Failed'}), 500
 
 
 
 
 
 
 
 
 
160
 
161
  @app.route('/get_assessment', methods=['GET'])
162
  def get_assessment():
 
166
 
167
  session = sessions[session_id]
168
  if not session.get('interview_responses'):
169
+ return jsonify({'error': 'No responses'}), 400
170
 
171
  profile = session['candidate_profile']
172
  responses = session['interview_responses']
173
+ avg = sum(r['evaluation']['score'] for r in responses) / len(responses)
174
 
175
+ summary = "\n".join([f"Q: {r['question'][:60]}... Score: {r['evaluation']['score']}%" for r in responses[:5]])
176
+ prompt = f'''Assessment for {profile.get('name','Candidate')}. Avg: {avg:.1f}%. Questions: {len(responses)}.
177
+ {summary}
178
+ Return: {{"overallScore":85,"recommendation":"Recommended","keyStrengths":["..."],"areasForImprovement":["..."],"detailedScores":{{"technicalSkills":85,"communication":80,"softSkills":78}}}}'''
 
 
 
179
 
180
+ resp = generate_content_with_groq(prompt)
181
+ if resp:
182
+ a = json.loads(resp)
183
+ a['detailedQuestionAnalysis'] = [{'question':r['question'],'score':r['evaluation']['score'],'technicalScore':r['evaluation'].get('technicalScore',0),'communicationScore':r['evaluation'].get('communicationScore',0),'relevanceScore':r['evaluation'].get('relevanceScore',0)} for r in responses]
184
+ return jsonify({'message': 'Generated', 'assessment': a}), 200
185
+ return jsonify({'assessment': {'overallScore': round(avg), 'recommendation': 'Recommended' if avg >= 70 else 'Needs Improvement', 'keyStrengths': [], 'areasForImprovement': [], 'detailedScores': {'technicalSkills': round(avg), 'communication': round(avg), 'softSkills': round(avg)}}}), 200
 
 
 
 
 
 
 
 
 
 
 
 
 
 
186
 
187
  @app.route('/log_security', methods=['POST'])
188
  def log_security():
189
  return jsonify({'message': 'Logged'}), 200
190
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
191
  if __name__ == '__main__':
192
  port = int(os.environ.get('PORT', 7860))
193
  app.run(host='0.0.0.0', port=port, debug=False)
static/assets/css/premium.css ADDED
@@ -0,0 +1,1376 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /* Premium AI Platform - Luxury Design System */
2
+
3
+ :root {
4
+ /* Premium Color Palette */
5
+ --color-primary: #6366f1;
6
+ --color-primary-dark: #4f46e5;
7
+ --color-secondary: #8b5cf6;
8
+ --color-accent: #06b6d4;
9
+ --color-success: #10b981;
10
+ --color-warning: #f59e0b;
11
+ --color-danger: #ef4444;
12
+
13
+ /* Dark Theme */
14
+ --color-bg-primary: #0a0a0f;
15
+ --color-bg-secondary: #13131a;
16
+ --color-bg-tertiary: #1a1a24;
17
+ --color-surface: rgba(255, 255, 255, 0.03);
18
+ --color-surface-hover: rgba(255, 255, 255, 0.06);
19
+
20
+ /* Text Colors */
21
+ --color-text-primary: #ffffff;
22
+ --color-text-secondary: #a1a1aa;
23
+ --color-text-tertiary: #71717a;
24
+
25
+ /* Glass Effect */
26
+ --glass-bg: rgba(255, 255, 255, 0.05);
27
+ --glass-border: rgba(255, 255, 255, 0.1);
28
+ --glass-shadow: 0 8px 32px 0 rgba(0, 0, 0, 0.37);
29
+
30
+ /* Spacing */
31
+ --spacing-xs: 0.5rem;
32
+ --spacing-sm: 1rem;
33
+ --spacing-md: 1.5rem;
34
+ --spacing-lg: 2rem;
35
+ --spacing-xl: 3rem;
36
+
37
+ /* Border Radius */
38
+ --radius-sm: 0.5rem;
39
+ --radius-md: 1rem;
40
+ --radius-lg: 1.5rem;
41
+ --radius-xl: 2rem;
42
+
43
+ /* Transitions */
44
+ --transition-fast: 0.2s cubic-bezier(0.4, 0, 0.2, 1);
45
+ --transition-base: 0.3s cubic-bezier(0.4, 0, 0.2, 1);
46
+ --transition-slow: 0.5s cubic-bezier(0.4, 0, 0.2, 1);
47
+ }
48
+
49
+ * {
50
+ margin: 0;
51
+ padding: 0;
52
+ box-sizing: border-box;
53
+ }
54
+
55
+ body {
56
+ font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
57
+ background: var(--color-bg-primary);
58
+ color: var(--color-text-primary);
59
+ line-height: 1.6;
60
+ overflow-x: hidden;
61
+ -webkit-font-smoothing: antialiased;
62
+ -moz-osx-font-smoothing: grayscale;
63
+ }
64
+
65
+ h1, h2, h3, h4, h5, h6 {
66
+ font-family: 'Poppins', sans-serif;
67
+ font-weight: 700;
68
+ line-height: 1.2;
69
+ }
70
+
71
+ /* Premium Background */
72
+ .premium-bg {
73
+ position: fixed;
74
+ top: 0;
75
+ left: 0;
76
+ width: 100%;
77
+ height: 100%;
78
+ z-index: 0;
79
+ overflow: hidden;
80
+ }
81
+
82
+ .gradient-mesh {
83
+ position: absolute;
84
+ width: 100%;
85
+ height: 100%;
86
+ background:
87
+ radial-gradient(circle at 20% 50%, rgba(99, 102, 241, 0.15) 0%, transparent 50%),
88
+ radial-gradient(circle at 80% 80%, rgba(139, 92, 246, 0.15) 0%, transparent 50%),
89
+ radial-gradient(circle at 40% 20%, rgba(6, 182, 212, 0.1) 0%, transparent 50%);
90
+ animation: meshMove 20s ease-in-out infinite;
91
+ }
92
+
93
+ @keyframes meshMove {
94
+ 0%, 100% { transform: translate(0, 0) scale(1); }
95
+ 50% { transform: translate(50px, 30px) scale(1.1); }
96
+ }
97
+
98
+ .neural-network {
99
+ position: absolute;
100
+ width: 100%;
101
+ height: 100%;
102
+ opacity: 0.3;
103
+ }
104
+
105
+ #neuralCanvas {
106
+ width: 100%;
107
+ height: 100%;
108
+ }
109
+
110
+ /* Navigation */
111
+ .premium-nav {
112
+ position: fixed;
113
+ top: 0;
114
+ left: 0;
115
+ right: 0;
116
+ z-index: 1000;
117
+ background: var(--glass-bg);
118
+ backdrop-filter: blur(20px);
119
+ -webkit-backdrop-filter: blur(20px);
120
+ border-bottom: 1px solid var(--glass-border);
121
+ }
122
+
123
+ .nav-container {
124
+ max-width: 1400px;
125
+ margin: 0 auto;
126
+ padding: var(--spacing-md) var(--spacing-lg);
127
+ display: flex;
128
+ justify-content: space-between;
129
+ align-items: center;
130
+ }
131
+
132
+ .nav-brand {
133
+ display: flex;
134
+ align-items: center;
135
+ gap: var(--spacing-sm);
136
+ }
137
+
138
+ .brand-icon {
139
+ width: 48px;
140
+ height: 48px;
141
+ background: linear-gradient(135deg, var(--color-primary), var(--color-secondary));
142
+ border-radius: var(--radius-md);
143
+ display: flex;
144
+ align-items: center;
145
+ justify-content: center;
146
+ font-size: 24px;
147
+ box-shadow: 0 8px 24px rgba(99, 102, 241, 0.3);
148
+ }
149
+
150
+ .brand-text {
151
+ font-size: 24px;
152
+ font-weight: 800;
153
+ letter-spacing: -0.5px;
154
+ }
155
+
156
+ .brand-pro {
157
+ background: linear-gradient(135deg, var(--color-primary), var(--color-accent));
158
+ -webkit-background-clip: text;
159
+ -webkit-text-fill-color: transparent;
160
+ background-clip: text;
161
+ margin-left: 4px;
162
+ }
163
+
164
+ .nav-menu {
165
+ display: flex;
166
+ align-items: center;
167
+ gap: var(--spacing-md);
168
+ }
169
+
170
+ .nav-link {
171
+ display: flex;
172
+ align-items: center;
173
+ gap: var(--spacing-xs);
174
+ padding: var(--spacing-sm) var(--spacing-md);
175
+ border-radius: var(--radius-md);
176
+ color: var(--color-text-secondary);
177
+ text-decoration: none;
178
+ transition: all var(--transition-base);
179
+ font-weight: 500;
180
+ }
181
+
182
+ .nav-link:hover {
183
+ color: var(--color-text-primary);
184
+ background: var(--color-surface-hover);
185
+ }
186
+
187
+ .nav-link.active {
188
+ color: var(--color-primary);
189
+ background: rgba(99, 102, 241, 0.1);
190
+ }
191
+
192
+ .nav-user {
193
+ display: flex;
194
+ align-items: center;
195
+ gap: var(--spacing-sm);
196
+ padding: var(--spacing-xs) var(--spacing-sm);
197
+ background: var(--color-surface);
198
+ border-radius: var(--radius-lg);
199
+ border: 1px solid var(--glass-border);
200
+ }
201
+
202
+ .user-avatar {
203
+ width: 40px;
204
+ height: 40px;
205
+ border-radius: 50%;
206
+ background: linear-gradient(135deg, var(--color-primary), var(--color-secondary));
207
+ display: flex;
208
+ align-items: center;
209
+ justify-content: center;
210
+ }
211
+
212
+ .user-info {
213
+ display: flex;
214
+ flex-direction: column;
215
+ }
216
+
217
+ .user-name {
218
+ font-size: 14px;
219
+ font-weight: 600;
220
+ }
221
+
222
+ .user-role {
223
+ font-size: 12px;
224
+ color: var(--color-text-tertiary);
225
+ }
226
+
227
+ .logout-btn {
228
+ padding: var(--spacing-xs);
229
+ color: var(--color-text-secondary);
230
+ transition: color var(--transition-fast);
231
+ cursor: pointer;
232
+ text-decoration: none;
233
+ }
234
+
235
+ .logout-btn:hover {
236
+ color: var(--color-danger);
237
+ }
238
+
239
+ /* Main Content */
240
+ .premium-main {
241
+ position: relative;
242
+ z-index: 1;
243
+ padding-top: 100px;
244
+ max-width: 1400px;
245
+ margin: 0 auto;
246
+ padding-left: var(--spacing-lg);
247
+ padding-right: var(--spacing-lg);
248
+ }
249
+
250
+ /* Hero Section */
251
+ .hero-section {
252
+ min-height: 80vh;
253
+ display: flex;
254
+ align-items: center;
255
+ justify-content: center;
256
+ text-align: center;
257
+ padding: var(--spacing-xl) 0;
258
+ }
259
+
260
+ .hero-content {
261
+ max-width: 900px;
262
+ }
263
+
264
+ .hero-badge {
265
+ display: inline-flex;
266
+ align-items: center;
267
+ gap: var(--spacing-xs);
268
+ padding: var(--spacing-xs) var(--spacing-md);
269
+ background: var(--glass-bg);
270
+ border: 1px solid var(--glass-border);
271
+ border-radius: var(--radius-xl);
272
+ font-size: 14px;
273
+ font-weight: 500;
274
+ margin-bottom: var(--spacing-lg);
275
+ backdrop-filter: blur(10px);
276
+ }
277
+
278
+ .hero-badge i {
279
+ color: var(--color-accent);
280
+ }
281
+
282
+ .hero-title {
283
+ font-size: clamp(48px, 8vw, 72px);
284
+ font-weight: 800;
285
+ margin-bottom: var(--spacing-md);
286
+ letter-spacing: -2px;
287
+ }
288
+
289
+ .gradient-text {
290
+ background: linear-gradient(135deg, var(--color-primary), var(--color-accent));
291
+ -webkit-background-clip: text;
292
+ -webkit-text-fill-color: transparent;
293
+ background-clip: text;
294
+ }
295
+
296
+ .hero-subtitle {
297
+ font-size: 20px;
298
+ color: var(--color-text-secondary);
299
+ margin-bottom: var(--spacing-xl);
300
+ line-height: 1.8;
301
+ }
302
+
303
+ .hero-actions {
304
+ display: flex;
305
+ gap: var(--spacing-md);
306
+ justify-content: center;
307
+ margin-bottom: var(--spacing-xl);
308
+ }
309
+
310
+ /* Buttons */
311
+ .btn-primary, .btn-secondary {
312
+ position: relative;
313
+ display: inline-flex;
314
+ align-items: center;
315
+ gap: var(--spacing-sm);
316
+ padding: 16px 32px;
317
+ border-radius: var(--radius-lg);
318
+ font-size: 16px;
319
+ font-weight: 600;
320
+ border: none;
321
+ cursor: pointer;
322
+ transition: all var(--transition-base);
323
+ overflow: hidden;
324
+ }
325
+
326
+ .btn-primary {
327
+ background: linear-gradient(135deg, var(--color-primary), var(--color-secondary));
328
+ color: white;
329
+ box-shadow: 0 8px 24px rgba(99, 102, 241, 0.4);
330
+ }
331
+
332
+ .btn-primary:hover {
333
+ transform: translateY(-2px);
334
+ box-shadow: 0 12px 32px rgba(99, 102, 241, 0.5);
335
+ }
336
+
337
+ .btn-glow {
338
+ position: absolute;
339
+ top: 0;
340
+ left: -100%;
341
+ width: 100%;
342
+ height: 100%;
343
+ background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.3), transparent);
344
+ transition: left 0.5s;
345
+ }
346
+
347
+ .btn-primary:hover .btn-glow {
348
+ left: 100%;
349
+ }
350
+
351
+ .btn-secondary {
352
+ background: var(--color-surface);
353
+ color: var(--color-text-primary);
354
+ border: 1px solid var(--glass-border);
355
+ }
356
+
357
+ .btn-secondary:hover {
358
+ background: var(--color-surface-hover);
359
+ border-color: var(--color-primary);
360
+ }
361
+
362
+ /* Hero Stats */
363
+ .hero-stats {
364
+ display: flex;
365
+ justify-content: center;
366
+ align-items: center;
367
+ gap: var(--spacing-lg);
368
+ padding: var(--spacing-lg);
369
+ background: var(--glass-bg);
370
+ border: 1px solid var(--glass-border);
371
+ border-radius: var(--radius-xl);
372
+ backdrop-filter: blur(10px);
373
+ max-width: 600px;
374
+ margin: 0 auto;
375
+ }
376
+
377
+ .stat-item {
378
+ text-align: center;
379
+ }
380
+
381
+ .stat-value {
382
+ font-size: 32px;
383
+ font-weight: 800;
384
+ background: linear-gradient(135deg, var(--color-primary), var(--color-accent));
385
+ -webkit-background-clip: text;
386
+ -webkit-text-fill-color: transparent;
387
+ background-clip: text;
388
+ }
389
+
390
+ .stat-label {
391
+ font-size: 14px;
392
+ color: var(--color-text-tertiary);
393
+ margin-top: 4px;
394
+ }
395
+
396
+ .stat-divider {
397
+ width: 1px;
398
+ height: 40px;
399
+ background: var(--glass-border);
400
+ }
401
+
402
+ /* Glass Card */
403
+ .glass-card {
404
+ background: var(--glass-bg);
405
+ backdrop-filter: blur(20px);
406
+ -webkit-backdrop-filter: blur(20px);
407
+ border: 1px solid var(--glass-border);
408
+ border-radius: var(--radius-xl);
409
+ padding: var(--spacing-xl);
410
+ box-shadow: var(--glass-shadow);
411
+ transition: all var(--transition-base);
412
+ }
413
+
414
+ .glass-card:hover {
415
+ border-color: rgba(99, 102, 241, 0.3);
416
+ box-shadow: 0 12px 48px rgba(0, 0, 0, 0.5);
417
+ }
418
+
419
+ .glass-card.large {
420
+ padding: var(--spacing-xl) var(--spacing-xl);
421
+ }
422
+
423
+ /* Upload Section */
424
+ .upload-section {
425
+ margin: var(--spacing-xl) 0;
426
+ }
427
+
428
+ .card-header {
429
+ display: flex;
430
+ align-items: center;
431
+ gap: var(--spacing-md);
432
+ margin-bottom: var(--spacing-xl);
433
+ }
434
+
435
+ .header-icon {
436
+ width: 64px;
437
+ height: 64px;
438
+ background: linear-gradient(135deg, var(--color-primary), var(--color-secondary));
439
+ border-radius: var(--radius-lg);
440
+ display: flex;
441
+ align-items: center;
442
+ justify-content: center;
443
+ font-size: 28px;
444
+ box-shadow: 0 8px 24px rgba(99, 102, 241, 0.3);
445
+ }
446
+
447
+ .card-title {
448
+ font-size: 28px;
449
+ font-weight: 700;
450
+ margin-bottom: 4px;
451
+ }
452
+
453
+ .card-subtitle {
454
+ font-size: 16px;
455
+ color: var(--color-text-secondary);
456
+ }
457
+
458
+ .upload-area {
459
+ border: 2px dashed var(--glass-border);
460
+ border-radius: var(--radius-lg);
461
+ padding: var(--spacing-xl);
462
+ text-align: center;
463
+ cursor: pointer;
464
+ transition: all var(--transition-base);
465
+ }
466
+
467
+ .upload-area:hover {
468
+ border-color: var(--color-primary);
469
+ background: rgba(99, 102, 241, 0.05);
470
+ }
471
+
472
+ .upload-icon {
473
+ font-size: 64px;
474
+ color: var(--color-primary);
475
+ margin-bottom: var(--spacing-md);
476
+ }
477
+
478
+ .upload-title {
479
+ font-size: 24px;
480
+ margin-bottom: var(--spacing-sm);
481
+ }
482
+
483
+ .upload-text {
484
+ color: var(--color-text-secondary);
485
+ margin-bottom: var(--spacing-md);
486
+ }
487
+
488
+ .upload-formats {
489
+ display: flex;
490
+ gap: var(--spacing-sm);
491
+ justify-content: center;
492
+ }
493
+
494
+ .format-badge {
495
+ padding: 6px 16px;
496
+ background: var(--color-surface);
497
+ border: 1px solid var(--glass-border);
498
+ border-radius: var(--radius-sm);
499
+ font-size: 12px;
500
+ font-weight: 600;
501
+ color: var(--color-primary);
502
+ }
503
+
504
+ /* Loading Overlay */
505
+ .loading-overlay {
506
+ position: fixed;
507
+ top: 0;
508
+ left: 0;
509
+ width: 100%;
510
+ height: 100%;
511
+ background: rgba(10, 10, 15, 0.95);
512
+ backdrop-filter: blur(10px);
513
+ display: flex;
514
+ align-items: center;
515
+ justify-content: center;
516
+ z-index: 9999;
517
+ }
518
+
519
+ .loading-overlay.hidden {
520
+ display: none;
521
+ }
522
+
523
+ .loading-content {
524
+ text-align: center;
525
+ }
526
+
527
+ .loading-spinner {
528
+ width: 80px;
529
+ height: 80px;
530
+ border: 4px solid var(--color-surface);
531
+ border-top-color: var(--color-primary);
532
+ border-radius: 50%;
533
+ animation: spin 1s linear infinite;
534
+ margin: 0 auto var(--spacing-md);
535
+ }
536
+
537
+ @keyframes spin {
538
+ to { transform: rotate(360deg); }
539
+ }
540
+
541
+ .loading-text {
542
+ font-size: 18px;
543
+ color: var(--color-text-secondary);
544
+ }
545
+
546
+ /* Interview Section */
547
+ .interview-section {
548
+ margin: var(--spacing-xl) 0;
549
+ }
550
+
551
+ .interview-header {
552
+ display: flex;
553
+ justify-content: space-between;
554
+ align-items: center;
555
+ margin-bottom: var(--spacing-xl);
556
+ }
557
+
558
+ .progress-indicator {
559
+ position: relative;
560
+ }
561
+
562
+ .progress-circle {
563
+ position: relative;
564
+ width: 100px;
565
+ height: 100px;
566
+ }
567
+
568
+ .progress-circle svg {
569
+ transform: rotate(-90deg);
570
+ }
571
+
572
+ .progress-circle circle {
573
+ fill: none;
574
+ stroke: var(--color-surface);
575
+ stroke-width: 8;
576
+ }
577
+
578
+ .progress-circle .progress-ring {
579
+ stroke: var(--color-primary);
580
+ stroke-linecap: round;
581
+ transition: stroke-dashoffset 0.5s ease;
582
+ }
583
+
584
+ .progress-number {
585
+ position: absolute;
586
+ top: 50%;
587
+ left: 50%;
588
+ transform: translate(-50%, -50%);
589
+ font-size: 20px;
590
+ font-weight: 700;
591
+ color: var(--color-primary);
592
+ }
593
+
594
+ .timer {
595
+ display: flex;
596
+ align-items: center;
597
+ gap: var(--spacing-sm);
598
+ padding: var(--spacing-sm) var(--spacing-md);
599
+ background: var(--color-surface);
600
+ border-radius: var(--radius-lg);
601
+ font-size: 18px;
602
+ font-weight: 600;
603
+ }
604
+
605
+ .question-container {
606
+ margin-bottom: var(--spacing-xl);
607
+ }
608
+
609
+ .question-tags {
610
+ display: flex;
611
+ gap: var(--spacing-sm);
612
+ margin-bottom: var(--spacing-md);
613
+ }
614
+
615
+ .tag {
616
+ padding: 6px 16px;
617
+ background: rgba(99, 102, 241, 0.1);
618
+ border: 1px solid rgba(99, 102, 241, 0.3);
619
+ border-radius: var(--radius-sm);
620
+ font-size: 14px;
621
+ font-weight: 600;
622
+ color: var(--color-primary);
623
+ }
624
+
625
+ .question-text {
626
+ font-size: 24px;
627
+ line-height: 1.6;
628
+ color: var(--color-text-primary);
629
+ }
630
+
631
+ .answer-container {
632
+ margin-bottom: var(--spacing-xl);
633
+ }
634
+
635
+ .answer-input {
636
+ width: 100%;
637
+ padding: var(--spacing-md);
638
+ background: var(--color-surface);
639
+ border: 1px solid var(--glass-border);
640
+ border-radius: var(--radius-lg);
641
+ color: var(--color-text-primary);
642
+ font-size: 16px;
643
+ font-family: inherit;
644
+ resize: vertical;
645
+ min-height: 200px;
646
+ transition: all var(--transition-base);
647
+ }
648
+
649
+ .answer-input:focus {
650
+ outline: none;
651
+ border-color: var(--color-primary);
652
+ box-shadow: 0 0 0 4px rgba(99, 102, 241, 0.1);
653
+ }
654
+
655
+ .ai-warning {
656
+ margin-top: var(--spacing-md);
657
+ padding: var(--spacing-md);
658
+ background: rgba(239, 68, 68, 0.1);
659
+ border: 1px solid rgba(239, 68, 68, 0.3);
660
+ border-radius: var(--radius-md);
661
+ color: var(--color-danger);
662
+ display: flex;
663
+ align-items: center;
664
+ gap: var(--spacing-sm);
665
+ }
666
+
667
+ .interview-actions {
668
+ display: flex;
669
+ justify-content: space-between;
670
+ gap: var(--spacing-md);
671
+ }
672
+
673
+ /* Results Section */
674
+ .results-section {
675
+ margin: var(--spacing-xl) 0;
676
+ }
677
+
678
+ .results-header {
679
+ display: flex;
680
+ justify-content: space-between;
681
+ align-items: center;
682
+ margin-bottom: var(--spacing-xl);
683
+ }
684
+
685
+ .section-title {
686
+ font-size: 32px;
687
+ font-weight: 700;
688
+ }
689
+
690
+ .results-grid {
691
+ display: grid;
692
+ grid-template-columns: repeat(auto-fit, minmax(400px, 1fr));
693
+ gap: var(--spacing-lg);
694
+ }
695
+
696
+ .score-display {
697
+ text-align: center;
698
+ }
699
+
700
+ .score-circle {
701
+ position: relative;
702
+ width: 200px;
703
+ height: 200px;
704
+ margin: 0 auto var(--spacing-lg);
705
+ }
706
+
707
+ .score-circle svg {
708
+ transform: rotate(-90deg);
709
+ }
710
+
711
+ .score-circle circle {
712
+ fill: none;
713
+ stroke: var(--color-surface);
714
+ stroke-width: 12;
715
+ }
716
+
717
+ .score-circle .score-ring {
718
+ stroke: url(#scoreGradient);
719
+ stroke-linecap: round;
720
+ }
721
+
722
+ .score-content {
723
+ position: absolute;
724
+ top: 50%;
725
+ left: 50%;
726
+ transform: translate(-50%, -50%);
727
+ text-align: center;
728
+ }
729
+
730
+ .score-value {
731
+ display: block;
732
+ font-size: 48px;
733
+ font-weight: 800;
734
+ background: linear-gradient(135deg, var(--color-primary), var(--color-accent));
735
+ -webkit-background-clip: text;
736
+ -webkit-text-fill-color: transparent;
737
+ background-clip: text;
738
+ }
739
+
740
+ .score-label {
741
+ display: block;
742
+ font-size: 16px;
743
+ color: var(--color-text-secondary);
744
+ margin-top: 4px;
745
+ }
746
+
747
+ .recommendation {
748
+ display: flex;
749
+ align-items: center;
750
+ justify-content: center;
751
+ gap: var(--spacing-sm);
752
+ padding: var(--spacing-md);
753
+ background: rgba(16, 185, 129, 0.1);
754
+ border: 1px solid rgba(16, 185, 129, 0.3);
755
+ border-radius: var(--radius-lg);
756
+ color: var(--color-success);
757
+ font-weight: 600;
758
+ }
759
+
760
+ .score-breakdown {
761
+ display: flex;
762
+ flex-direction: column;
763
+ gap: var(--spacing-md);
764
+ }
765
+
766
+ .score-item {
767
+ display: flex;
768
+ flex-direction: column;
769
+ gap: var(--spacing-xs);
770
+ }
771
+
772
+ .score-info {
773
+ display: flex;
774
+ justify-content: space-between;
775
+ align-items: center;
776
+ }
777
+
778
+ .score-name {
779
+ font-weight: 600;
780
+ color: var(--color-text-secondary);
781
+ }
782
+
783
+ .score-percent {
784
+ font-weight: 700;
785
+ color: var(--color-primary);
786
+ }
787
+
788
+ .score-bar {
789
+ height: 8px;
790
+ background: var(--color-surface);
791
+ border-radius: 10px;
792
+ overflow: hidden;
793
+ }
794
+
795
+ .score-bar-fill {
796
+ height: 100%;
797
+ background: linear-gradient(90deg, var(--color-primary), var(--color-accent));
798
+ border-radius: 10px;
799
+ transition: width 1s ease;
800
+ }
801
+
802
+ /* Utility Classes */
803
+ .hidden {
804
+ display: none !important;
805
+ }
806
+
807
+ /* Responsive */
808
+ @media (max-width: 768px) {
809
+ .premium-main {
810
+ padding-left: var(--spacing-md);
811
+ padding-right: var(--spacing-md);
812
+ }
813
+
814
+ .hero-title {
815
+ font-size: 36px;
816
+ }
817
+
818
+ .hero-actions {
819
+ flex-direction: column;
820
+ }
821
+
822
+ .hero-stats {
823
+ flex-direction: column;
824
+ gap: var(--spacing-md);
825
+ }
826
+
827
+ .stat-divider {
828
+ width: 100%;
829
+ height: 1px;
830
+ }
831
+
832
+ .nav-menu {
833
+ gap: var(--spacing-sm);
834
+ }
835
+
836
+ .nav-link span {
837
+ display: none;
838
+ }
839
+ }
840
+
841
+ /* Exam Mode Styles */
842
+ body.exam-mode {
843
+ overflow: hidden;
844
+ }
845
+
846
+ body.exam-mode .interview-section {
847
+ position: fixed;
848
+ top: 0;
849
+ left: 0;
850
+ right: 0;
851
+ bottom: 0;
852
+ width: 100vw;
853
+ height: 100vh;
854
+ z-index: 9999;
855
+ background: var(--color-bg-primary);
856
+ display: flex;
857
+ align-items: center;
858
+ justify-content: center;
859
+ padding: 2rem;
860
+ margin: 0;
861
+ }
862
+
863
+ body.exam-mode .interview-section .glass-card {
864
+ max-width: 900px;
865
+ width: 100%;
866
+ height: auto;
867
+ max-height: 90vh;
868
+ overflow-y: auto;
869
+ }
870
+
871
+ body.exam-mode .interview-section:not(.hidden) {
872
+ display: flex !important;
873
+ }
874
+
875
+ /* Hide elements during exam */
876
+ body.exam-mode .premium-nav,
877
+ body.exam-mode .hero-section,
878
+ body.exam-mode .upload-section,
879
+ body.exam-mode .results-section {
880
+ display: none !important;
881
+ }
882
+
883
+ /* Interview section full screen layout */
884
+ .interview-section {
885
+ min-height: 100vh;
886
+ display: flex;
887
+ align-items: center;
888
+ justify-content: center;
889
+ padding: var(--spacing-xl);
890
+ }
891
+
892
+ .interview-section.hidden {
893
+ display: none;
894
+ }
895
+
896
+ .interview-section .glass-card {
897
+ width: 100%;
898
+ max-width: 900px;
899
+ }
900
+
901
+ .interview-header {
902
+ display: flex;
903
+ justify-content: space-between;
904
+ align-items: center;
905
+ margin-bottom: var(--spacing-lg);
906
+ padding-bottom: var(--spacing-md);
907
+ border-bottom: 1px solid var(--glass-border);
908
+ }
909
+
910
+ .progress-indicator {
911
+ display: flex;
912
+ align-items: center;
913
+ gap: var(--spacing-md);
914
+ }
915
+
916
+ .progress-circle {
917
+ position: relative;
918
+ width: 80px;
919
+ height: 80px;
920
+ }
921
+
922
+ .progress-circle svg {
923
+ width: 100%;
924
+ height: 100%;
925
+ transform: rotate(-90deg);
926
+ }
927
+
928
+ .progress-circle circle {
929
+ fill: none;
930
+ stroke-width: 8;
931
+ }
932
+
933
+ .progress-circle circle:first-child {
934
+ stroke: var(--color-surface);
935
+ }
936
+
937
+ .progress-circle circle.progress-ring {
938
+ stroke: var(--color-primary);
939
+ stroke-linecap: round;
940
+ transition: stroke-dashoffset 0.5s ease;
941
+ }
942
+
943
+ .progress-number {
944
+ position: absolute;
945
+ top: 50%;
946
+ left: 50%;
947
+ transform: translate(-50%, -50%);
948
+ font-size: 18px;
949
+ font-weight: 700;
950
+ color: var(--color-text-primary);
951
+ }
952
+
953
+ .timer {
954
+ display: flex;
955
+ align-items: center;
956
+ gap: var(--spacing-sm);
957
+ padding: var(--spacing-sm) var(--spacing-md);
958
+ background: var(--glass-bg);
959
+ border: 1px solid var(--glass-border);
960
+ border-radius: var(--radius-lg);
961
+ font-size: 24px;
962
+ font-weight: 700;
963
+ color: var(--color-primary);
964
+ }
965
+
966
+ .timer i {
967
+ font-size: 20px;
968
+ }
969
+
970
+ .question-container {
971
+ margin-bottom: var(--spacing-xl);
972
+ }
973
+
974
+ .question-tags {
975
+ display: flex;
976
+ gap: var(--spacing-sm);
977
+ margin-bottom: var(--spacing-md);
978
+ }
979
+
980
+ .question-tags .tag {
981
+ padding: 6px 16px;
982
+ background: rgba(99, 102, 241, 0.1);
983
+ border: 1px solid rgba(99, 102, 241, 0.3);
984
+ border-radius: 20px;
985
+ font-size: 14px;
986
+ font-weight: 500;
987
+ color: var(--color-primary);
988
+ }
989
+
990
+ .question-text {
991
+ font-size: 24px;
992
+ font-weight: 600;
993
+ line-height: 1.6;
994
+ color: var(--color-text-primary);
995
+ margin: 0;
996
+ }
997
+
998
+ .answer-container {
999
+ margin-bottom: var(--spacing-xl);
1000
+ }
1001
+
1002
+ .answer-input {
1003
+ width: 100%;
1004
+ min-height: 200px;
1005
+ padding: var(--spacing-md);
1006
+ background: var(--color-surface);
1007
+ border: 1px solid var(--glass-border);
1008
+ border-radius: var(--radius-lg);
1009
+ color: var(--color-text-primary);
1010
+ font-size: 16px;
1011
+ font-family: 'Inter', sans-serif;
1012
+ line-height: 1.6;
1013
+ resize: vertical;
1014
+ transition: all var(--transition-base);
1015
+ }
1016
+
1017
+ .answer-input:focus {
1018
+ outline: none;
1019
+ border-color: var(--color-primary);
1020
+ box-shadow: 0 0 0 4px rgba(99, 102, 241, 0.1);
1021
+ }
1022
+
1023
+ .answer-input::placeholder {
1024
+ color: var(--color-text-tertiary);
1025
+ }
1026
+
1027
+ .ai-warning {
1028
+ display: flex;
1029
+ align-items: center;
1030
+ gap: var(--spacing-sm);
1031
+ padding: var(--spacing-sm) var(--spacing-md);
1032
+ background: rgba(239, 68, 68, 0.1);
1033
+ border: 1px solid rgba(239, 68, 68, 0.3);
1034
+ border-radius: var(--radius-md);
1035
+ color: var(--color-danger);
1036
+ font-size: 14px;
1037
+ font-weight: 500;
1038
+ margin-top: var(--spacing-sm);
1039
+ }
1040
+
1041
+ .ai-warning.hidden {
1042
+ display: none;
1043
+ }
1044
+
1045
+ .interview-actions {
1046
+ display: flex;
1047
+ justify-content: space-between;
1048
+ gap: var(--spacing-md);
1049
+ }
1050
+
1051
+ .interview-actions button {
1052
+ flex: 1;
1053
+ }
1054
+
1055
+ /* Exam mode - center interview card */
1056
+ body.exam-mode .interview-section .glass-card {
1057
+ animation: slideInUp 0.5s ease;
1058
+ }
1059
+
1060
+ @keyframes slideInUp {
1061
+ from {
1062
+ opacity: 0;
1063
+ transform: translateY(30px);
1064
+ }
1065
+ to {
1066
+ opacity: 1;
1067
+ transform: translateY(0);
1068
+ }
1069
+ }
1070
+
1071
+ /* Results section styles */
1072
+ .results-section {
1073
+ padding: var(--spacing-xl);
1074
+ min-height: 100vh;
1075
+ }
1076
+
1077
+ .results-section.hidden {
1078
+ display: none;
1079
+ }
1080
+
1081
+ .results-header {
1082
+ display: flex;
1083
+ justify-content: space-between;
1084
+ align-items: center;
1085
+ margin-bottom: var(--spacing-xl);
1086
+ }
1087
+
1088
+ .section-title {
1089
+ font-size: 32px;
1090
+ font-weight: 700;
1091
+ color: var(--color-text-primary);
1092
+ }
1093
+
1094
+ .results-grid {
1095
+ display: grid;
1096
+ grid-template-columns: repeat(auto-fit, minmax(400px, 1fr));
1097
+ gap: var(--spacing-lg);
1098
+ }
1099
+
1100
+ .score-display {
1101
+ text-align: center;
1102
+ padding: var(--spacing-lg);
1103
+ }
1104
+
1105
+ .score-circle {
1106
+ position: relative;
1107
+ width: 200px;
1108
+ height: 200px;
1109
+ margin: 0 auto var(--spacing-lg);
1110
+ }
1111
+
1112
+ .score-circle svg {
1113
+ width: 100%;
1114
+ height: 100%;
1115
+ transform: rotate(-90deg);
1116
+ }
1117
+
1118
+ .score-circle circle {
1119
+ fill: none;
1120
+ stroke-width: 12;
1121
+ }
1122
+
1123
+ .score-circle circle:first-child {
1124
+ stroke: var(--color-surface);
1125
+ }
1126
+
1127
+ .score-circle circle.score-ring {
1128
+ stroke: var(--color-primary);
1129
+ stroke-linecap: round;
1130
+ }
1131
+
1132
+ .score-content {
1133
+ position: absolute;
1134
+ top: 50%;
1135
+ left: 50%;
1136
+ transform: translate(-50%, -50%);
1137
+ text-align: center;
1138
+ }
1139
+
1140
+ .score-value {
1141
+ display: block;
1142
+ font-size: 48px;
1143
+ font-weight: 800;
1144
+ color: var(--color-text-primary);
1145
+ }
1146
+
1147
+ .score-label {
1148
+ display: block;
1149
+ font-size: 16px;
1150
+ color: var(--color-text-secondary);
1151
+ margin-top: var(--spacing-xs);
1152
+ }
1153
+
1154
+ .recommendation {
1155
+ display: flex;
1156
+ align-items: center;
1157
+ justify-content: center;
1158
+ gap: var(--spacing-sm);
1159
+ padding: var(--spacing-sm) var(--spacing-md);
1160
+ background: rgba(16, 185, 129, 0.1);
1161
+ border: 1px solid rgba(16, 185, 129, 0.3);
1162
+ border-radius: var(--radius-lg);
1163
+ color: var(--color-success);
1164
+ font-weight: 600;
1165
+ }
1166
+
1167
+ .score-breakdown {
1168
+ display: flex;
1169
+ flex-direction: column;
1170
+ gap: var(--spacing-md);
1171
+ }
1172
+
1173
+ .score-item {
1174
+ display: flex;
1175
+ flex-direction: column;
1176
+ gap: var(--spacing-xs);
1177
+ }
1178
+
1179
+ .score-info {
1180
+ display: flex;
1181
+ justify-content: space-between;
1182
+ align-items: center;
1183
+ }
1184
+
1185
+ .score-name {
1186
+ font-weight: 600;
1187
+ color: var(--color-text-primary);
1188
+ }
1189
+
1190
+ .score-percent {
1191
+ font-weight: 700;
1192
+ color: var(--color-primary);
1193
+ }
1194
+
1195
+ .score-bar {
1196
+ height: 8px;
1197
+ background: var(--color-surface);
1198
+ border-radius: 4px;
1199
+ overflow: hidden;
1200
+ }
1201
+
1202
+ .score-bar-fill {
1203
+ height: 100%;
1204
+ background: linear-gradient(90deg, var(--color-primary), var(--color-secondary));
1205
+ border-radius: 4px;
1206
+ transition: width 1s ease;
1207
+ }
1208
+
1209
+ /* Coding Challenge Styles */
1210
+ .coding-challenge-notice {
1211
+ display: flex;
1212
+ align-items: center;
1213
+ gap: var(--spacing-sm);
1214
+ padding: var(--spacing-md);
1215
+ background: rgba(99, 102, 241, 0.1);
1216
+ border: 1px solid rgba(99, 102, 241, 0.3);
1217
+ border-radius: var(--radius-md);
1218
+ color: var(--color-primary);
1219
+ font-weight: 600;
1220
+ margin-bottom: var(--spacing-md);
1221
+ }
1222
+
1223
+ .coding-challenge-notice i {
1224
+ font-size: 20px;
1225
+ }
1226
+
1227
+ .coding-language-selector {
1228
+ display: flex;
1229
+ align-items: center;
1230
+ gap: var(--spacing-md);
1231
+ padding: var(--spacing-md);
1232
+ background: var(--color-surface);
1233
+ border: 1px solid var(--glass-border);
1234
+ border-radius: var(--radius-md);
1235
+ margin-bottom: var(--spacing-md);
1236
+ }
1237
+
1238
+ .coding-language-selector label {
1239
+ display: flex;
1240
+ align-items: center;
1241
+ gap: var(--spacing-sm);
1242
+ font-weight: 600;
1243
+ color: var(--color-text-secondary);
1244
+ margin: 0;
1245
+ }
1246
+
1247
+ .language-dropdown {
1248
+ padding: 10px 16px;
1249
+ background: var(--color-bg-secondary);
1250
+ border: 1px solid var(--glass-border);
1251
+ border-radius: var(--radius-md);
1252
+ color: var(--color-text-primary);
1253
+ font-size: 16px;
1254
+ font-weight: 500;
1255
+ cursor: pointer;
1256
+ transition: all var(--transition-base);
1257
+ min-width: 200px;
1258
+ }
1259
+
1260
+ .language-dropdown:hover {
1261
+ border-color: var(--color-primary);
1262
+ }
1263
+
1264
+ .language-dropdown:focus {
1265
+ outline: none;
1266
+ border-color: var(--color-primary);
1267
+ box-shadow: 0 0 0 4px rgba(99, 102, 241, 0.1);
1268
+ }
1269
+
1270
+ #codingEditor {
1271
+ background: var(--color-surface);
1272
+ border: 1px solid var(--glass-border);
1273
+ border-radius: var(--radius-lg);
1274
+ min-height: 700px;
1275
+ font-size: 16px;
1276
+ }
1277
+
1278
+ /* Ensure iframe content is properly sized */
1279
+ #codingEditor iframe {
1280
+ width: 100%;
1281
+ height: 100%;
1282
+ }
1283
+
1284
+ /* Make interview card bigger for coding questions */
1285
+ body.exam-mode .interview-section .glass-card.coding-active {
1286
+ max-width: 1200px;
1287
+ max-height: 98vh;
1288
+ }
1289
+
1290
+ /* Exam Mode - Hide UI Elements */
1291
+ body.exam-mode .premium-nav,
1292
+ body.exam-mode .hero-section,
1293
+ body.exam-mode .upload-section,
1294
+ body.exam-mode .results-section {
1295
+ display: none !important;
1296
+ }
1297
+
1298
+ body.exam-mode {
1299
+ overflow: hidden;
1300
+ }
1301
+
1302
+ body.exam-mode .interview-section {
1303
+ position: fixed;
1304
+ top: 0;
1305
+ left: 0;
1306
+ right: 0;
1307
+ bottom: 0;
1308
+ width: 100vw;
1309
+ height: 100vh;
1310
+ z-index: 9999;
1311
+ background: var(--color-bg-primary);
1312
+ display: flex;
1313
+ align-items: center;
1314
+ justify-content: center;
1315
+ padding: 2rem;
1316
+ margin: 0;
1317
+ overflow-y: auto;
1318
+ }
1319
+
1320
+ body.exam-mode .interview-section .glass-card {
1321
+ max-width: 1000px;
1322
+ width: 100%;
1323
+ height: auto;
1324
+ max-height: 95vh;
1325
+ overflow-y: auto;
1326
+ }
1327
+
1328
+ body.exam-mode .interview-section:not(.hidden) {
1329
+ display: flex !important;
1330
+ }
1331
+
1332
+ /* Fullscreen coding editor for coding questions */
1333
+ body.exam-mode .interview-section.coding-mode {
1334
+ padding: 0 !important;
1335
+ align-items: stretch !important;
1336
+ }
1337
+
1338
+ body.exam-mode .interview-section.coding-mode .glass-card {
1339
+ max-width: 100vw !important;
1340
+ width: 100vw !important;
1341
+ max-height: 100vh !important;
1342
+ height: 100vh !important;
1343
+ margin: 0 !important;
1344
+ padding: 1.5rem !important;
1345
+ border-radius: 0 !important;
1346
+ display: flex;
1347
+ flex-direction: column;
1348
+ overflow: hidden;
1349
+ }
1350
+
1351
+ body.exam-mode .interview-section.coding-mode .interview-header {
1352
+ flex-shrink: 0;
1353
+ }
1354
+
1355
+ body.exam-mode .interview-section.coding-mode .question-container {
1356
+ flex-shrink: 0;
1357
+ }
1358
+
1359
+ body.exam-mode .interview-section.coding-mode .answer-container {
1360
+ flex: 1;
1361
+ display: flex;
1362
+ flex-direction: column;
1363
+ min-height: 0;
1364
+ overflow: hidden;
1365
+ }
1366
+
1367
+ body.exam-mode .interview-section.coding-mode #codingEditor {
1368
+ flex: 1;
1369
+ min-height: 0 !important;
1370
+ height: 100% !important;
1371
+ }
1372
+
1373
+ body.exam-mode .interview-section.coding-mode .interview-actions {
1374
+ flex-shrink: 0;
1375
+ margin-top: 1rem;
1376
+ }
static/assets/js/config.js ADDED
@@ -0,0 +1,13 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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;
static/assets/js/exam-security.js ADDED
@@ -0,0 +1,754 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * Exam Security System
3
+ * - Tab switch detection (max 2 switches)
4
+ * - Right-click disabled
5
+ * - Copy/paste disabled
6
+ * - Auto fullscreen
7
+ * - Time limit per question (3 minutes)
8
+ * - No going back to previous questions
9
+ * - Browser restriction (Chrome-like only)
10
+ * - Extension detection (block AI extensions)
11
+ */
12
+
13
+ class ExamSecurity {
14
+ constructor() {
15
+ this.tabSwitchCount = 0;
16
+ this.maxTabSwitches = 2;
17
+ this.isExamMode = false;
18
+ this.questionTimeLimit = 180; // 3 minutes in seconds
19
+ this.questionTimer = null;
20
+ this.currentQuestionStartTime = null;
21
+ this.allowedBrowsers = ['chrome', 'edge', 'brave', 'opera'];
22
+ this.blockedExtensions = [
23
+ 'chatgpt', 'copilot', 'grammarly', 'quillbot',
24
+ 'jasper', 'writesonic', 'claude', 'bard'
25
+ ];
26
+
27
+ this.init();
28
+ }
29
+
30
+ init() {
31
+ // Disable right-click
32
+ document.addEventListener('contextmenu', (e) => {
33
+ if (this.isExamMode) {
34
+ e.preventDefault();
35
+ this.showWarning('Right-click is disabled during the interview');
36
+ return false;
37
+ }
38
+ });
39
+
40
+ // Disable copy/cut/paste completely
41
+ document.addEventListener('copy', (e) => {
42
+ if (this.isExamMode) {
43
+ e.preventDefault();
44
+ this.showWarning('Copy is disabled during the interview');
45
+ }
46
+ });
47
+
48
+ document.addEventListener('cut', (e) => {
49
+ if (this.isExamMode) {
50
+ e.preventDefault();
51
+ this.showWarning('Cut is disabled during the interview');
52
+ }
53
+ });
54
+
55
+ document.addEventListener('paste', (e) => {
56
+ if (this.isExamMode) {
57
+ e.preventDefault();
58
+ this.showWarning('Paste is disabled during the interview');
59
+ }
60
+ });
61
+
62
+ // Disable keyboard shortcuts
63
+ document.addEventListener('keydown', (e) => {
64
+ if (this.isExamMode) {
65
+ // Disable F12, Ctrl+Shift+I, Ctrl+Shift+J, Ctrl+U
66
+ if (e.key === 'F12' ||
67
+ (e.ctrlKey && e.shiftKey && (e.key === 'I' || e.key === 'J')) ||
68
+ (e.ctrlKey && e.key === 'u')) {
69
+ e.preventDefault();
70
+ this.showWarning('Developer tools are disabled during the interview');
71
+ return false;
72
+ }
73
+
74
+ // Disable Ctrl+C, Ctrl+V, Ctrl+X everywhere (no copy/paste allowed)
75
+ if (e.ctrlKey && (e.key === 'c' || e.key === 'v' || e.key === 'x')) {
76
+ e.preventDefault();
77
+ this.showWarning('Copy/Paste is disabled during the interview');
78
+ return false;
79
+ }
80
+ }
81
+ });
82
+
83
+ // Tab switch detection
84
+ document.addEventListener('visibilitychange', () => {
85
+ if (this.isExamMode && document.hidden) {
86
+ // Don't count if user is interacting with iframe (coding editor)
87
+ const activeElement = document.activeElement;
88
+ if (activeElement && activeElement.tagName === 'IFRAME') {
89
+ console.log('Iframe interaction detected, not counting as tab switch');
90
+ return;
91
+ }
92
+ this.handleTabSwitch();
93
+ }
94
+ });
95
+
96
+ // Window blur detection (switching to another window)
97
+ window.addEventListener('blur', () => {
98
+ if (this.isExamMode) {
99
+ // Don't count if user is interacting with iframe (coding editor)
100
+ const activeElement = document.activeElement;
101
+ if (activeElement && activeElement.tagName === 'IFRAME') {
102
+ console.log('Iframe interaction detected, not counting as tab switch');
103
+ return;
104
+ }
105
+
106
+ // Add a small delay to check if it's really a tab switch
107
+ setTimeout(() => {
108
+ if (!document.hasFocus()) {
109
+ this.handleTabSwitch();
110
+ }
111
+ }, 100);
112
+ }
113
+ });
114
+
115
+ // Prevent exit fullscreen
116
+ document.addEventListener('fullscreenchange', () => {
117
+ if (this.isExamMode && !document.fullscreenElement) {
118
+ this.showWarning('Please stay in fullscreen mode during the interview');
119
+ this.enterFullscreen();
120
+ }
121
+ });
122
+ }
123
+
124
+ handleTabSwitch() {
125
+ this.tabSwitchCount++;
126
+
127
+ console.log(`Tab switch detected: ${this.tabSwitchCount}/${this.maxTabSwitches}`);
128
+
129
+ // Log to backend
130
+ this.logSecurityEvent('tab_switch', {
131
+ count: this.tabSwitchCount,
132
+ timestamp: new Date().toISOString()
133
+ });
134
+
135
+ if (this.tabSwitchCount >= this.maxTabSwitches) {
136
+ this.terminateExam();
137
+ } else {
138
+ const remaining = this.maxTabSwitches - this.tabSwitchCount;
139
+ this.showCriticalWarning(
140
+ `⚠️ TAB SWITCH DETECTED!\n\nYou have ${remaining} warning(s) remaining.\n\nSwitching tabs again will terminate your interview.`
141
+ );
142
+ }
143
+ }
144
+
145
+ terminateExam() {
146
+ this.isExamMode = false;
147
+
148
+ // Log termination
149
+ this.logSecurityEvent('exam_terminated', {
150
+ reason: 'exceeded_tab_switches',
151
+ timestamp: new Date().toISOString()
152
+ });
153
+
154
+ // Show termination message
155
+ const overlay = document.createElement('div');
156
+ overlay.className = 'critical-warning-overlay show';
157
+ overlay.style.zIndex = '99999';
158
+ overlay.innerHTML = `
159
+ <div class="critical-warning-box">
160
+ <div class="warning-icon">
161
+ <i class="fas fa-ban"></i>
162
+ </div>
163
+ <h3>Interview Terminated</h3>
164
+ <p>You have exceeded the maximum number of tab switches (2).</p>
165
+ <p style="margin-top: 1rem;">Your interview has been automatically submitted with current progress.</p>
166
+ <div style="margin-top: 2rem; display: flex; gap: 1rem; justify-content: center;">
167
+ <button class="btn-primary" onclick="window.location.href='index.php'">
168
+ <span>Go to Home</span>
169
+ </button>
170
+ </div>
171
+ </div>
172
+ `;
173
+ document.body.appendChild(overlay);
174
+
175
+ // Auto-submit interview
176
+ this.autoSubmitInterview();
177
+ }
178
+
179
+ async startExamMode() {
180
+ // Check browser compatibility
181
+ if (!this.checkBrowser()) {
182
+ this.showBrowserError();
183
+ return false;
184
+ }
185
+
186
+ // Check for AI extensions
187
+ const hasBlockedExtensions = await this.detectExtensions();
188
+ if (hasBlockedExtensions) {
189
+ this.showExtensionError();
190
+ return false;
191
+ }
192
+
193
+ // Don't activate exam mode yet - wait for proctoring to be set up
194
+ this.tabSwitchCount = 0;
195
+
196
+ // Enter fullscreen
197
+ this.enterFullscreen();
198
+
199
+ // Show security notice
200
+ this.showSecurityNotice();
201
+
202
+ // Log exam start
203
+ this.logSecurityEvent('exam_started', {
204
+ timestamp: new Date().toISOString(),
205
+ browser: this.getBrowserInfo(),
206
+ userAgent: navigator.userAgent
207
+ });
208
+
209
+ return true;
210
+ }
211
+
212
+ activateExamMode() {
213
+ // Called after proctoring is set up
214
+ this.isExamMode = true;
215
+ }
216
+
217
+ checkBrowser() {
218
+ const userAgent = navigator.userAgent.toLowerCase();
219
+ const isChrome = userAgent.includes('chrome') && !userAgent.includes('edg');
220
+ const isEdge = userAgent.includes('edg');
221
+ const isBrave = userAgent.includes('brave');
222
+ const isOpera = userAgent.includes('opr') || userAgent.includes('opera');
223
+
224
+ return isChrome || isEdge || isBrave || isOpera;
225
+ }
226
+
227
+ getBrowserInfo() {
228
+ const userAgent = navigator.userAgent.toLowerCase();
229
+ if (userAgent.includes('edg')) return 'Microsoft Edge';
230
+ if (userAgent.includes('brave')) return 'Brave';
231
+ if (userAgent.includes('opr') || userAgent.includes('opera')) return 'Opera';
232
+ if (userAgent.includes('chrome')) return 'Google Chrome';
233
+ return 'Unknown';
234
+ }
235
+
236
+ async detectExtensions() {
237
+ // More specific AI extension detection to avoid false positives
238
+ const extensionTests = [
239
+ // ChatGPT specific elements
240
+ { id: 'chatgpt', test: () => document.querySelector('[data-testid*="chatgpt"]') || document.querySelector('[class*="chatgpt-"]') },
241
+ // Grammarly specific
242
+ { id: 'grammarly', test: () => document.querySelector('grammarly-extension') || document.querySelector('grammarly-desktop-integration') },
243
+ // Copilot specific
244
+ { id: 'copilot', test: () => document.querySelector('[class*="copilot"]') || document.querySelector('[id*="copilot"]') }
245
+ ];
246
+
247
+ for (const test of extensionTests) {
248
+ try {
249
+ if (test.test()) {
250
+ console.warn(`Detected AI extension: ${test.id}`);
251
+ return true;
252
+ }
253
+ } catch (e) {
254
+ // Extension detection blocked, continue
255
+ }
256
+ }
257
+
258
+ // Don't check for generic extensions - too many false positives
259
+ return false;
260
+ }
261
+
262
+ showBrowserError() {
263
+ const overlay = document.createElement('div');
264
+ overlay.className = 'critical-warning-overlay show';
265
+ overlay.innerHTML = `
266
+ <div class="critical-warning-box">
267
+ <div class="warning-icon">
268
+ <i class="fas fa-exclamation-circle"></i>
269
+ </div>
270
+ <h3>Unsupported Browser</h3>
271
+ <p>This interview can only be taken on the following browsers:</p>
272
+ <ul class="browser-list">
273
+ <li><i class="fab fa-chrome"></i> Google Chrome</li>
274
+ <li><i class="fab fa-edge"></i> Microsoft Edge</li>
275
+ <li><i class="fab fa-brave"></i> Brave Browser</li>
276
+ <li><i class="fab fa-opera"></i> Opera</li>
277
+ </ul>
278
+ <p style="margin-top: 1rem; color: var(--color-danger);">
279
+ <strong>Current Browser:</strong> ${this.getBrowserInfo()}
280
+ </p>
281
+ <button class="btn-primary" onclick="window.location.href='index.php'">
282
+ <span>Go Back</span>
283
+ </button>
284
+ </div>
285
+ `;
286
+ document.body.appendChild(overlay);
287
+ }
288
+
289
+ showExtensionError() {
290
+ const overlay = document.createElement('div');
291
+ overlay.className = 'critical-warning-overlay show';
292
+ overlay.innerHTML = `
293
+ <div class="critical-warning-box">
294
+ <div class="warning-icon">
295
+ <i class="fas fa-puzzle-piece"></i>
296
+ </div>
297
+ <h3>Extensions Detected</h3>
298
+ <p>AI-powered browser extensions must be disabled before starting the interview.</p>
299
+ <div class="extension-warning">
300
+ <p><strong>Commonly blocked extensions:</strong></p>
301
+ <ul class="extension-list">
302
+ <li>ChatGPT / AI Assistants</li>
303
+ <li>GitHub Copilot</li>
304
+ <li>Grammarly</li>
305
+ <li>QuillBot</li>
306
+ <li>Any AI writing tools</li>
307
+ </ul>
308
+ </div>
309
+ <p style="margin-top: 1rem;">
310
+ <strong>How to disable extensions:</strong><br>
311
+ 1. Click the puzzle icon in your browser toolbar<br>
312
+ 2. Disable all AI-related extensions<br>
313
+ 3. Refresh this page and try again
314
+ </p>
315
+ <button class="btn-primary" onclick="window.location.reload()">
316
+ <span>Refresh Page</span>
317
+ </button>
318
+ </div>
319
+ `;
320
+ document.body.appendChild(overlay);
321
+ }
322
+
323
+ endExamMode() {
324
+ this.isExamMode = false;
325
+ this.exitFullscreen();
326
+
327
+ if (this.questionTimer) {
328
+ clearInterval(this.questionTimer);
329
+ }
330
+ }
331
+
332
+ enterFullscreen() {
333
+ const elem = document.documentElement;
334
+ if (elem.requestFullscreen) {
335
+ elem.requestFullscreen().catch(err => {
336
+ console.log('Fullscreen error:', err);
337
+ });
338
+ } else if (elem.webkitRequestFullscreen) {
339
+ elem.webkitRequestFullscreen();
340
+ } else if (elem.msRequestFullscreen) {
341
+ elem.msRequestFullscreen();
342
+ }
343
+ }
344
+
345
+ exitFullscreen() {
346
+ if (document.exitFullscreen) {
347
+ document.exitFullscreen();
348
+ } else if (document.webkitExitFullscreen) {
349
+ document.webkitExitFullscreen();
350
+ } else if (document.msExitFullscreen) {
351
+ document.msExitFullscreen();
352
+ }
353
+ }
354
+
355
+ startQuestionTimer(onTimeout, customTimeLimit = null) {
356
+ this.currentQuestionStartTime = Date.now();
357
+ // Use custom time limit if provided, otherwise use default
358
+ let remainingTime = customTimeLimit || this.questionTimeLimit;
359
+
360
+ // Update timer display
361
+ const updateTimerDisplay = () => {
362
+ const minutes = Math.floor(remainingTime / 60);
363
+ const seconds = remainingTime % 60;
364
+ const timerElement = document.getElementById('timer');
365
+ if (timerElement) {
366
+ timerElement.textContent = `${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}`;
367
+
368
+ // Change color when time is running out (for coding: < 2 min, regular: < 30 sec)
369
+ const warningThreshold = customTimeLimit ? 120 : 30;
370
+ const dangerThreshold = customTimeLimit ? 60 : 30;
371
+
372
+ if (remainingTime < dangerThreshold) {
373
+ timerElement.style.color = 'var(--color-danger)';
374
+ } else if (remainingTime < warningThreshold) {
375
+ timerElement.style.color = 'var(--color-warning)';
376
+ } else {
377
+ timerElement.style.color = 'var(--color-primary)';
378
+ }
379
+ }
380
+ };
381
+
382
+ updateTimerDisplay();
383
+
384
+ if (this.questionTimer) {
385
+ clearInterval(this.questionTimer);
386
+ }
387
+
388
+ this.questionTimer = setInterval(() => {
389
+ remainingTime--;
390
+ updateTimerDisplay();
391
+
392
+ // Warning based on question type
393
+ if (customTimeLimit) {
394
+ // Coding question - warn at 2 minutes
395
+ if (remainingTime === 120) {
396
+ this.showWarning('⏰ 2 minutes remaining!');
397
+ }
398
+ } else {
399
+ // Regular question - warn at 30 seconds
400
+ if (remainingTime === 30) {
401
+ this.showWarning('⏰ 30 seconds remaining!');
402
+ }
403
+ }
404
+
405
+ // Time's up
406
+ if (remainingTime <= 0) {
407
+ clearInterval(this.questionTimer);
408
+ this.showWarning('⏰ Time is up! Moving to next question...');
409
+
410
+ setTimeout(() => {
411
+ if (onTimeout) onTimeout();
412
+ }, 1500);
413
+ }
414
+ }, 1000);
415
+ }
416
+
417
+ stopQuestionTimer() {
418
+ if (this.questionTimer) {
419
+ clearInterval(this.questionTimer);
420
+ }
421
+ }
422
+
423
+ getQuestionDuration() {
424
+ if (this.currentQuestionStartTime) {
425
+ const duration = Math.floor((Date.now() - this.currentQuestionStartTime) / 1000);
426
+ const minutes = Math.floor(duration / 60);
427
+ const seconds = duration % 60;
428
+ return `${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}`;
429
+ }
430
+ return '00:00';
431
+ }
432
+
433
+ showSecurityNotice() {
434
+ const notice = document.createElement('div');
435
+ notice.className = 'security-notice';
436
+ notice.innerHTML = `
437
+ <div class="security-notice-content">
438
+ <div class="security-icon">
439
+ <i class="fas fa-shield-alt"></i>
440
+ </div>
441
+ <h3>Interview Security Active</h3>
442
+ <div class="security-info">
443
+ <p><strong>Browser:</strong> ${this.getBrowserInfo()}</p>
444
+ <p><strong>Mode:</strong> Secure Exam Mode</p>
445
+ </div>
446
+ <ul class="security-rules">
447
+ <li><i class="fas fa-check"></i> Fullscreen mode enabled</li>
448
+ <li><i class="fas fa-check"></i> Tab switching limited (2 warnings max)</li>
449
+ <li><i class="fas fa-check"></i> Right-click disabled</li>
450
+ <li><i class="fas fa-check"></i> Copy/paste disabled</li>
451
+ <li><i class="fas fa-check"></i> 3 minutes per question (auto-submit)</li>
452
+ <li><i class="fas fa-check"></i> No going back to previous questions</li>
453
+ <li><i class="fas fa-check"></i> AI extensions blocked</li>
454
+ <li><i class="fas fa-check"></i> Developer tools disabled</li>
455
+ </ul>
456
+ <div class="security-warning-box">
457
+ <i class="fas fa-exclamation-triangle"></i>
458
+ <p><strong>Warning:</strong> Violating any security rule will result in automatic interview termination.</p>
459
+ </div>
460
+ <button class="btn-primary" onclick="this.parentElement.parentElement.remove()">
461
+ <span>I Understand & Accept</span>
462
+ <i class="fas fa-check"></i>
463
+ </button>
464
+ </div>
465
+ `;
466
+
467
+ document.body.appendChild(notice);
468
+ }
469
+
470
+ showWarning(message) {
471
+ const warning = document.createElement('div');
472
+ warning.className = 'security-warning';
473
+ warning.innerHTML = `
474
+ <i class="fas fa-exclamation-triangle"></i>
475
+ <span>${message}</span>
476
+ `;
477
+
478
+ document.body.appendChild(warning);
479
+
480
+ setTimeout(() => warning.classList.add('show'), 100);
481
+
482
+ setTimeout(() => {
483
+ warning.classList.remove('show');
484
+ setTimeout(() => warning.remove(), 300);
485
+ }, 3000);
486
+ }
487
+
488
+ showCriticalWarning(message) {
489
+ const overlay = document.createElement('div');
490
+ overlay.className = 'critical-warning-overlay';
491
+ overlay.innerHTML = `
492
+ <div class="critical-warning-box">
493
+ <div class="warning-icon">
494
+ <i class="fas fa-exclamation-triangle"></i>
495
+ </div>
496
+ <h3>Security Warning</h3>
497
+ <p>${message.replace(/\n/g, '<br>')}</p>
498
+ <button class="btn-primary" onclick="this.parentElement.parentElement.remove()">
499
+ <span>I Understand</span>
500
+ </button>
501
+ </div>
502
+ `;
503
+
504
+ document.body.appendChild(overlay);
505
+ setTimeout(() => overlay.classList.add('show'), 100);
506
+ }
507
+
508
+ logSecurityEvent(eventType, data) {
509
+ // Log to backend
510
+ fetch(getApiUrl('log_security'), {
511
+ method: 'POST',
512
+ headers: {
513
+ 'Content-Type': 'application/json',
514
+ 'X-User-Session-Id': getSessionId()
515
+ },
516
+ body: JSON.stringify({
517
+ event_type: eventType,
518
+ data: data,
519
+ user_agent: navigator.userAgent,
520
+ timestamp: new Date().toISOString()
521
+ })
522
+ }).catch(err => console.error('Failed to log security event:', err));
523
+ }
524
+
525
+ autoSubmitInterview() {
526
+ // Auto-submit current answers
527
+ console.log('Auto-submitting interview due to security violation');
528
+
529
+ // Call assessment endpoint
530
+ fetch(getApiUrl('get_assessment'), {
531
+ method: 'GET',
532
+ headers: {
533
+ 'X-User-Session-Id': getSessionId()
534
+ }
535
+ }).catch(err => console.error('Auto-submit failed:', err));
536
+ }
537
+ }
538
+
539
+ // Initialize security system
540
+ const examSecurity = new ExamSecurity();
541
+
542
+ // Add security styles
543
+ const securityStyles = document.createElement('style');
544
+ securityStyles.textContent = `
545
+ .security-notice {
546
+ position: fixed;
547
+ top: 0;
548
+ left: 0;
549
+ right: 0;
550
+ bottom: 0;
551
+ background: rgba(0, 0, 0, 0.95);
552
+ backdrop-filter: blur(10px);
553
+ display: flex;
554
+ align-items: center;
555
+ justify-content: center;
556
+ z-index: 99999;
557
+ }
558
+
559
+ .security-notice-content {
560
+ background: var(--glass-bg);
561
+ border: 1px solid var(--glass-border);
562
+ border-radius: var(--radius-xl);
563
+ padding: var(--spacing-xl);
564
+ max-width: 500px;
565
+ text-align: center;
566
+ }
567
+
568
+ .security-icon {
569
+ width: 80px;
570
+ height: 80px;
571
+ background: linear-gradient(135deg, var(--color-primary), var(--color-secondary));
572
+ border-radius: 50%;
573
+ display: flex;
574
+ align-items: center;
575
+ justify-content: center;
576
+ font-size: 36px;
577
+ margin: 0 auto var(--spacing-lg);
578
+ }
579
+
580
+ .security-rules {
581
+ list-style: none;
582
+ text-align: left;
583
+ margin: var(--spacing-lg) 0;
584
+ }
585
+
586
+ .security-rules li {
587
+ display: flex;
588
+ align-items: center;
589
+ gap: var(--spacing-sm);
590
+ padding: var(--spacing-sm) 0;
591
+ color: var(--color-text-secondary);
592
+ }
593
+
594
+ .security-rules i {
595
+ color: var(--color-success);
596
+ }
597
+
598
+ .security-info {
599
+ background: rgba(99, 102, 241, 0.1);
600
+ border: 1px solid rgba(99, 102, 241, 0.3);
601
+ border-radius: var(--radius-md);
602
+ padding: var(--spacing-md);
603
+ margin: var(--spacing-md) 0;
604
+ text-align: left;
605
+ }
606
+
607
+ .security-info p {
608
+ margin: var(--spacing-xs) 0;
609
+ color: var(--color-text-secondary);
610
+ }
611
+
612
+ .security-warning-box {
613
+ background: rgba(239, 68, 68, 0.1);
614
+ border: 1px solid rgba(239, 68, 68, 0.3);
615
+ border-radius: var(--radius-md);
616
+ padding: var(--spacing-md);
617
+ margin: var(--spacing-lg) 0;
618
+ display: flex;
619
+ align-items: start;
620
+ gap: var(--spacing-sm);
621
+ text-align: left;
622
+ }
623
+
624
+ .security-warning-box i {
625
+ color: var(--color-danger);
626
+ font-size: 20px;
627
+ margin-top: 2px;
628
+ }
629
+
630
+ .security-warning-box p {
631
+ margin: 0;
632
+ color: var(--color-text-secondary);
633
+ font-size: 14px;
634
+ }
635
+
636
+ .browser-list, .extension-list {
637
+ list-style: none;
638
+ padding: 0;
639
+ margin: var(--spacing-md) 0;
640
+ }
641
+
642
+ .browser-list li, .extension-list li {
643
+ padding: var(--spacing-sm);
644
+ background: rgba(99, 102, 241, 0.1);
645
+ border-radius: var(--radius-sm);
646
+ margin: var(--spacing-xs) 0;
647
+ display: flex;
648
+ align-items: center;
649
+ gap: var(--spacing-sm);
650
+ }
651
+
652
+ .extension-warning {
653
+ background: rgba(239, 68, 68, 0.1);
654
+ border: 1px solid rgba(239, 68, 68, 0.3);
655
+ border-radius: var(--radius-md);
656
+ padding: var(--spacing-md);
657
+ margin: var(--spacing-md) 0;
658
+ text-align: left;
659
+ }
660
+
661
+ .security-warning {
662
+ position: fixed;
663
+ top: 100px;
664
+ right: -400px;
665
+ padding: var(--spacing-md) var(--spacing-lg);
666
+ background: rgba(239, 68, 68, 0.95);
667
+ border: 2px solid var(--color-danger);
668
+ border-radius: var(--radius-lg);
669
+ color: white;
670
+ font-weight: 600;
671
+ display: flex;
672
+ align-items: center;
673
+ gap: var(--spacing-sm);
674
+ z-index: 99999;
675
+ transition: right 0.3s ease;
676
+ box-shadow: 0 8px 32px rgba(239, 68, 68, 0.5);
677
+ }
678
+
679
+ .security-warning.show {
680
+ right: 20px;
681
+ }
682
+
683
+ .critical-warning-overlay {
684
+ position: fixed;
685
+ top: 0;
686
+ left: 0;
687
+ right: 0;
688
+ bottom: 0;
689
+ background: rgba(0, 0, 0, 0.95);
690
+ backdrop-filter: blur(10px);
691
+ display: flex;
692
+ align-items: center;
693
+ justify-content: center;
694
+ z-index: 99999;
695
+ opacity: 0;
696
+ transition: opacity 0.3s ease;
697
+ }
698
+
699
+ .critical-warning-overlay.show {
700
+ opacity: 1;
701
+ }
702
+
703
+ .critical-warning-box {
704
+ background: var(--glass-bg);
705
+ border: 2px solid var(--color-danger);
706
+ border-radius: var(--radius-xl);
707
+ padding: var(--spacing-xl);
708
+ max-width: 500px;
709
+ text-align: center;
710
+ animation: shake 0.5s;
711
+ }
712
+
713
+ @keyframes shake {
714
+ 0%, 100% { transform: translateX(0); }
715
+ 25% { transform: translateX(-10px); }
716
+ 75% { transform: translateX(10px); }
717
+ }
718
+
719
+ .warning-icon {
720
+ width: 100px;
721
+ height: 100px;
722
+ background: rgba(239, 68, 68, 0.2);
723
+ border-radius: 50%;
724
+ display: flex;
725
+ align-items: center;
726
+ justify-content: center;
727
+ font-size: 48px;
728
+ color: var(--color-danger);
729
+ margin: 0 auto var(--spacing-lg);
730
+ animation: pulse 1s infinite;
731
+ }
732
+
733
+ @keyframes pulse {
734
+ 0%, 100% { transform: scale(1); }
735
+ 50% { transform: scale(1.1); }
736
+ }
737
+
738
+ .critical-warning-box h3 {
739
+ font-size: 28px;
740
+ color: var(--color-danger);
741
+ margin-bottom: var(--spacing-md);
742
+ }
743
+
744
+ .critical-warning-box p {
745
+ font-size: 16px;
746
+ color: var(--color-text-secondary);
747
+ line-height: 1.8;
748
+ margin-bottom: var(--spacing-lg);
749
+ }
750
+ `;
751
+ document.head.appendChild(securityStyles);
752
+
753
+ // Export for global use
754
+ window.examSecurity = examSecurity;
static/assets/js/neural-bg.js ADDED
@@ -0,0 +1,90 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // Neural Network Background Animation
2
+
3
+ class NeuralNetwork {
4
+ constructor(canvas) {
5
+ this.canvas = canvas;
6
+ this.ctx = canvas.getContext('2d');
7
+ this.nodes = [];
8
+ this.connections = [];
9
+ this.nodeCount = 50;
10
+
11
+ this.resize();
12
+ this.init();
13
+ this.animate();
14
+
15
+ window.addEventListener('resize', () => this.resize());
16
+ }
17
+
18
+ resize() {
19
+ this.canvas.width = window.innerWidth;
20
+ this.canvas.height = window.innerHeight;
21
+ }
22
+
23
+ init() {
24
+ this.nodes = [];
25
+ for (let i = 0; i < this.nodeCount; i++) {
26
+ this.nodes.push({
27
+ x: Math.random() * this.canvas.width,
28
+ y: Math.random() * this.canvas.height,
29
+ vx: (Math.random() - 0.5) * 0.5,
30
+ vy: (Math.random() - 0.5) * 0.5,
31
+ radius: Math.random() * 2 + 1
32
+ });
33
+ }
34
+ }
35
+
36
+ update() {
37
+ this.nodes.forEach(node => {
38
+ node.x += node.vx;
39
+ node.y += node.vy;
40
+
41
+ if (node.x < 0 || node.x > this.canvas.width) node.vx *= -1;
42
+ if (node.y < 0 || node.y > this.canvas.height) node.vy *= -1;
43
+ });
44
+ }
45
+
46
+ draw() {
47
+ this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
48
+
49
+ // Draw connections
50
+ this.nodes.forEach((node, i) => {
51
+ this.nodes.slice(i + 1).forEach(otherNode => {
52
+ const dx = node.x - otherNode.x;
53
+ const dy = node.y - otherNode.y;
54
+ const distance = Math.sqrt(dx * dx + dy * dy);
55
+
56
+ if (distance < 150) {
57
+ const opacity = (1 - distance / 150) * 0.3;
58
+ this.ctx.strokeStyle = `rgba(99, 102, 241, ${opacity})`;
59
+ this.ctx.lineWidth = 1;
60
+ this.ctx.beginPath();
61
+ this.ctx.moveTo(node.x, node.y);
62
+ this.ctx.lineTo(otherNode.x, otherNode.y);
63
+ this.ctx.stroke();
64
+ }
65
+ });
66
+ });
67
+
68
+ // Draw nodes
69
+ this.nodes.forEach(node => {
70
+ this.ctx.fillStyle = 'rgba(99, 102, 241, 0.6)';
71
+ this.ctx.beginPath();
72
+ this.ctx.arc(node.x, node.y, node.radius, 0, Math.PI * 2);
73
+ this.ctx.fill();
74
+ });
75
+ }
76
+
77
+ animate() {
78
+ this.update();
79
+ this.draw();
80
+ requestAnimationFrame(() => this.animate());
81
+ }
82
+ }
83
+
84
+ // Initialize when DOM is ready
85
+ document.addEventListener('DOMContentLoaded', () => {
86
+ const canvas = document.getElementById('neuralCanvas');
87
+ if (canvas) {
88
+ new NeuralNetwork(canvas);
89
+ }
90
+ });
static/assets/js/premium-app.js ADDED
@@ -0,0 +1,1450 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // Premium IntervuAI Application
2
+
3
+ // Scroll to upload section
4
+ function scrollToUpload() {
5
+ const uploadSection = document.getElementById('uploadSection');
6
+ if (uploadSection) {
7
+ uploadSection.scrollIntoView({ behavior: 'smooth', block: 'center' });
8
+ }
9
+ }
10
+
11
+ // Upload area functionality
12
+ document.addEventListener('DOMContentLoaded', () => {
13
+ const uploadArea = document.getElementById('uploadArea');
14
+ const resumeInput = document.getElementById('resumeInput');
15
+
16
+ if (uploadArea && resumeInput) {
17
+ // Click to upload
18
+ uploadArea.addEventListener('click', () => {
19
+ resumeInput.click();
20
+ });
21
+
22
+ // Drag and drop
23
+ uploadArea.addEventListener('dragover', (e) => {
24
+ e.preventDefault();
25
+ uploadArea.style.borderColor = 'var(--color-primary)';
26
+ uploadArea.style.background = 'rgba(99, 102, 241, 0.05)';
27
+ });
28
+
29
+ uploadArea.addEventListener('dragleave', () => {
30
+ uploadArea.style.borderColor = '';
31
+ uploadArea.style.background = '';
32
+ });
33
+
34
+ uploadArea.addEventListener('drop', (e) => {
35
+ e.preventDefault();
36
+ uploadArea.style.borderColor = '';
37
+ uploadArea.style.background = '';
38
+
39
+ const files = e.dataTransfer.files;
40
+ if (files.length > 0) {
41
+ handleFileUpload(files[0]);
42
+ }
43
+ });
44
+
45
+ // File input change
46
+ resumeInput.addEventListener('change', (e) => {
47
+ if (e.target.files.length > 0) {
48
+ handleFileUpload(e.target.files[0]);
49
+ }
50
+ });
51
+ }
52
+ });
53
+
54
+ // Handle file upload
55
+ async function handleFileUpload(file) {
56
+ console.log('Uploading file:', file.name);
57
+
58
+ // Show loading
59
+ showLoading('Analyzing resume with AI...');
60
+
61
+ const formData = new FormData();
62
+ formData.append('resume', file);
63
+
64
+ try {
65
+ // Call Python backend via PHP proxy
66
+ const response = await fetch(getApiUrl('upload_resume'), {
67
+ method: 'POST',
68
+ body: formData,
69
+ headers: {
70
+ 'X-User-Session-Id': getSessionId()
71
+ }
72
+ });
73
+
74
+ if (!response.ok) {
75
+ throw new Error('Upload failed');
76
+ }
77
+
78
+ const result = await response.json();
79
+ console.log('Upload result:', result);
80
+
81
+ hideLoading();
82
+
83
+ if (result.candidate_profile) {
84
+ // Store profile
85
+ sessionStorage.setItem('candidateProfile', JSON.stringify(result.candidate_profile));
86
+
87
+ // Show success message
88
+ showAlert('Resume analyzed successfully!', 'success');
89
+
90
+ // Show interview setup
91
+ setTimeout(() => {
92
+ showInterviewSetup(result.candidate_profile);
93
+ }, 1000);
94
+ } else {
95
+ showAlert('Failed to analyze resume. Please try again.', 'error');
96
+ }
97
+
98
+ } catch (error) {
99
+ console.error('Upload error:', error);
100
+ hideLoading();
101
+ showAlert('Failed to upload resume: ' + error.message, 'error');
102
+ }
103
+ }
104
+
105
+ // Show interview setup
106
+ function showInterviewSetup(profile) {
107
+ const uploadSection = document.getElementById('uploadSection');
108
+
109
+ if (uploadSection) uploadSection.classList.add('hidden');
110
+
111
+ // Create setup UI
112
+ const setupHTML = `
113
+ <div class="glass-card large">
114
+ <div class="card-header">
115
+ <div class="header-icon">
116
+ <i class="fas fa-user-check"></i>
117
+ </div>
118
+ <div class="header-content">
119
+ <h2 class="card-title">Interview Setup</h2>
120
+ <p class="card-subtitle">Review candidate profile and configure interview</p>
121
+ </div>
122
+ </div>
123
+
124
+ <div class="profile-display">
125
+ <div class="profile-item">
126
+ <span class="profile-label">Name:</span>
127
+ <span class="profile-value">${profile.name || 'N/A'}</span>
128
+ </div>
129
+ <div class="profile-item">
130
+ <span class="profile-label">Email:</span>
131
+ <span class="profile-value">${profile.email || 'N/A'}</span>
132
+ </div>
133
+ <div class="profile-item">
134
+ <span class="profile-label">Experience:</span>
135
+ <span class="profile-value">${profile.experience || 'N/A'}</span>
136
+ </div>
137
+ <div class="profile-item">
138
+ <span class="profile-label">Skills:</span>
139
+ <div class="skills-tags">
140
+ ${(profile.key_skills || []).map(skill => `<span class="skill-tag">${skill}</span>`).join('')}
141
+ </div>
142
+ </div>
143
+ </div>
144
+
145
+ <div class="form-group" style="margin-top: 2rem;">
146
+ <label class="form-label">
147
+ <i class="fas fa-briefcase"></i>
148
+ <span>Position/Role</span>
149
+ </label>
150
+ <input
151
+ type="text"
152
+ id="positionInput"
153
+ class="form-input"
154
+ placeholder="e.g., Senior Software Engineer"
155
+ value="${profile.inferred_position || ''}"
156
+ >
157
+ </div>
158
+
159
+ <div class="interview-actions" style="margin-top: 2rem;">
160
+ <button class="btn-secondary" onclick="location.reload()">
161
+ <i class="fas fa-arrow-left"></i>
162
+ <span>Back</span>
163
+ </button>
164
+ <button class="btn-primary" onclick="startInterview()">
165
+ <span>Start Interview</span>
166
+ <i class="fas fa-arrow-right"></i>
167
+ <div class="btn-glow"></div>
168
+ </button>
169
+ </div>
170
+ </div>
171
+ `;
172
+
173
+ const container = document.createElement('section');
174
+ container.className = 'upload-section';
175
+ container.id = 'setupSection';
176
+ container.innerHTML = setupHTML;
177
+
178
+ uploadSection.parentNode.insertBefore(container, uploadSection);
179
+ }
180
+
181
+ // Start interview
182
+ async function startInterview() {
183
+ const position = document.getElementById('positionInput')?.value;
184
+
185
+ if (!position) {
186
+ showAlert('Please enter a position/role', 'error');
187
+ return;
188
+ }
189
+
190
+ // Show pre-exam checklist
191
+ showPreExamChecklist(position);
192
+ }
193
+
194
+ // Show pre-exam checklist and requirements
195
+ async function showPreExamChecklist(position) {
196
+ const overlay = document.createElement('div');
197
+ overlay.className = 'critical-warning-overlay show';
198
+ overlay.id = 'preExamChecklist';
199
+ overlay.style.zIndex = '99999';
200
+ overlay.innerHTML = `
201
+ <div class="critical-warning-box" style="max-width: 600px;">
202
+ <div class="warning-icon" style="background: rgba(99, 102, 241, 0.2);">
203
+ <i class="fas fa-clipboard-check" style="color: var(--color-primary);"></i>
204
+ </div>
205
+ <h3>Pre-Interview Checklist</h3>
206
+ <p style="margin-bottom: 1.5rem;">Please ensure all requirements are met before starting:</p>
207
+
208
+ <div class="checklist-items">
209
+ <div class="checklist-item" id="check-browser">
210
+ <i class="fas fa-spinner fa-spin"></i>
211
+ <span>Checking browser compatibility...</span>
212
+ </div>
213
+ <div class="checklist-item" id="check-extensions">
214
+ <i class="fas fa-spinner fa-spin"></i>
215
+ <span>Checking for blocked extensions...</span>
216
+ </div>
217
+ <div class="checklist-item" id="check-camera">
218
+ <i class="fas fa-spinner fa-spin"></i>
219
+ <span>Requesting camera access...</span>
220
+ </div>
221
+ <div class="checklist-item" id="check-microphone">
222
+ <i class="fas fa-spinner fa-spin"></i>
223
+ <span>Requesting microphone access...</span>
224
+ </div>
225
+ <div class="checklist-item" id="check-questions">
226
+ <i class="fas fa-spinner fa-spin"></i>
227
+ <span>Generating interview questions...</span>
228
+ </div>
229
+ </div>
230
+
231
+ <div id="checklistActions" style="margin-top: 2rem; display: none;">
232
+ <button class="btn-primary" onclick="beginInterview()" style="width: 100%;">
233
+ <i class="fas fa-play"></i>
234
+ <span>Start Interview</span>
235
+ <div class="btn-glow"></div>
236
+ </button>
237
+ </div>
238
+
239
+ <div id="checklistError" style="margin-top: 2rem; display: none; display: flex; gap: 1rem;">
240
+ <button class="btn-secondary" onclick="recheckRequirements()" style="flex: 1;">
241
+ <i class="fas fa-redo"></i>
242
+ <span>Recheck</span>
243
+ </button>
244
+ <button class="btn-secondary" onclick="document.getElementById('preExamChecklist').remove()" style="flex: 1;">
245
+ <i class="fas fa-times"></i>
246
+ <span>Cancel</span>
247
+ </button>
248
+ </div>
249
+ </div>
250
+ `;
251
+ document.body.appendChild(overlay);
252
+
253
+ // Add checklist styles
254
+ const style = document.createElement('style');
255
+ style.textContent = `
256
+ .checklist-items {
257
+ display: flex;
258
+ flex-direction: column;
259
+ gap: 1rem;
260
+ margin: 1rem 0;
261
+ }
262
+ .checklist-item {
263
+ display: flex;
264
+ align-items: center;
265
+ gap: 1rem;
266
+ padding: 1rem;
267
+ background: var(--color-surface);
268
+ border-radius: var(--radius-md);
269
+ border-left: 3px solid var(--color-text-tertiary);
270
+ }
271
+ .checklist-item.success {
272
+ border-left-color: var(--color-success);
273
+ }
274
+ .checklist-item.error {
275
+ border-left-color: var(--color-danger);
276
+ }
277
+ .checklist-item i {
278
+ font-size: 20px;
279
+ min-width: 20px;
280
+ }
281
+ .checklist-item.success i {
282
+ color: var(--color-success);
283
+ }
284
+ .checklist-item.error i {
285
+ color: var(--color-danger);
286
+ }
287
+ `;
288
+ document.head.appendChild(style);
289
+
290
+ // Run checks
291
+ await runPreExamChecks(position);
292
+ }
293
+
294
+ // Recheck all requirements
295
+ async function recheckRequirements() {
296
+ // Hide action buttons
297
+ document.getElementById('checklistActions').style.display = 'none';
298
+ document.getElementById('checklistError').style.display = 'none';
299
+
300
+ // Reset all checklist items to loading state
301
+ const items = ['check-browser', 'check-extensions', 'check-camera', 'check-microphone', 'check-questions'];
302
+ items.forEach(itemId => {
303
+ const item = document.getElementById(itemId);
304
+ if (item) {
305
+ item.className = 'checklist-item';
306
+ item.innerHTML = `
307
+ <i class="fas fa-spinner fa-spin"></i>
308
+ <span>Checking...</span>
309
+ `;
310
+ }
311
+ });
312
+
313
+ // Get position from session or input
314
+ const position = document.getElementById('positionInput')?.value || 'Software Engineer';
315
+
316
+ // Re-run all checks
317
+ await runPreExamChecks(position);
318
+ }
319
+
320
+ // Run all pre-exam checks
321
+ async function runPreExamChecks(position) {
322
+ let allPassed = true;
323
+
324
+ // 1. Check browser
325
+ await checkItem('check-browser', async () => {
326
+ const isCompatible = window.examSecurity.checkBrowser();
327
+ if (!isCompatible) {
328
+ throw new Error('Unsupported browser. Please use Chrome, Edge, Brave, or Opera.');
329
+ }
330
+ return 'Browser compatible';
331
+ });
332
+
333
+ // 2. Check extensions
334
+ const extensionsOk = await checkItem('check-extensions', async () => {
335
+ const hasBlocked = await window.examSecurity.detectExtensions();
336
+ if (hasBlocked) {
337
+ throw new Error('AI extensions detected. Please disable them and refresh.');
338
+ }
339
+ return 'No blocked extensions found';
340
+ });
341
+
342
+ if (!extensionsOk) allPassed = false;
343
+
344
+ // 3. Check camera
345
+ const cameraOk = await checkItem('check-camera', async () => {
346
+ try {
347
+ const stream = await navigator.mediaDevices.getUserMedia({ video: true });
348
+ stream.getTracks().forEach(track => track.stop());
349
+ return 'Camera access granted';
350
+ } catch (error) {
351
+ throw new Error('Camera access denied. Please allow camera access.');
352
+ }
353
+ });
354
+
355
+ if (!cameraOk) allPassed = false;
356
+
357
+ // 4. Check microphone
358
+ const micOk = await checkItem('check-microphone', async () => {
359
+ try {
360
+ const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
361
+ stream.getTracks().forEach(track => track.stop());
362
+ return 'Microphone access granted';
363
+ } catch (error) {
364
+ throw new Error('Microphone access denied. Please allow microphone access.');
365
+ }
366
+ });
367
+
368
+ if (!micOk) allPassed = false;
369
+
370
+ // 5. Generate questions
371
+ const questionsOk = await checkItem('check-questions', async () => {
372
+ const response = await fetch(getApiUrl('setup_interview'), {
373
+ method: 'POST',
374
+ headers: {
375
+ 'Content-Type': 'application/json',
376
+ 'X-User-Session-Id': getSessionId()
377
+ },
378
+ body: JSON.stringify({ position_role: position })
379
+ });
380
+
381
+ const result = await response.json();
382
+
383
+ if (!result.questions) {
384
+ throw new Error('Failed to generate questions');
385
+ }
386
+
387
+ sessionStorage.setItem('questions', JSON.stringify(result.questions));
388
+ sessionStorage.setItem('currentQuestion', '0');
389
+ sessionStorage.setItem('responses', JSON.stringify([]));
390
+ sessionStorage.setItem('isCodingRole', result.is_coding_role || false);
391
+
392
+ return `${result.questions.length} questions generated`;
393
+ });
394
+
395
+ if (!questionsOk) allPassed = false;
396
+
397
+ // Show appropriate action
398
+ if (allPassed) {
399
+ document.getElementById('checklistActions').style.display = 'block';
400
+ } else {
401
+ document.getElementById('checklistError').style.display = 'block';
402
+ }
403
+ }
404
+
405
+ // Check individual item
406
+ async function checkItem(itemId, checkFunction) {
407
+ const item = document.getElementById(itemId);
408
+ if (!item) return false;
409
+
410
+ try {
411
+ const message = await checkFunction();
412
+ item.innerHTML = `
413
+ <i class="fas fa-check-circle"></i>
414
+ <span>${message}</span>
415
+ `;
416
+ item.classList.add('success');
417
+ return true;
418
+ } catch (error) {
419
+ item.innerHTML = `
420
+ <i class="fas fa-times-circle"></i>
421
+ <span>${error.message}</span>
422
+ `;
423
+ item.classList.add('error');
424
+ return false;
425
+ }
426
+ }
427
+
428
+ // Begin interview after all checks pass
429
+ async function beginInterview() {
430
+ // Remove checklist
431
+ document.getElementById('preExamChecklist')?.remove();
432
+
433
+ showLoading('Initializing secure exam environment...');
434
+
435
+ try {
436
+ // Start exam security mode
437
+ const securityStarted = await window.examSecurity.startExamMode();
438
+ if (!securityStarted) {
439
+ hideLoading();
440
+ return;
441
+ }
442
+
443
+ // Start proctoring (camera + audio monitoring)
444
+ const proctoringStarted = await window.proctoringSystem.startProctoring();
445
+ if (!proctoringStarted) {
446
+ hideLoading();
447
+ return;
448
+ }
449
+
450
+ // Activate exam mode
451
+ window.examSecurity.activateExamMode();
452
+
453
+ hideLoading();
454
+
455
+ // Hide all UI elements except interview section
456
+ hideAllUIForExam();
457
+
458
+ // Hide setup, show interview
459
+ document.getElementById('setupSection')?.classList.add('hidden');
460
+ document.getElementById('interviewSection')?.classList.remove('hidden');
461
+
462
+ loadQuestion(0);
463
+
464
+ } catch (error) {
465
+ hideLoading();
466
+ showAlert('Error starting interview: ' + error.message, 'error');
467
+ }
468
+ }
469
+
470
+ // Load question
471
+ function loadQuestion(index) {
472
+ const questions = JSON.parse(sessionStorage.getItem('questions') || '[]');
473
+ const question = questions[index];
474
+ const isCodingRole = sessionStorage.getItem('isCodingRole') === 'true';
475
+
476
+ if (!question) return;
477
+
478
+ // Clear previous answer
479
+ const answerTextarea = document.getElementById('answerText');
480
+ if (answerTextarea) {
481
+ answerTextarea.value = '';
482
+ }
483
+
484
+ // Check if this is a coding question
485
+ const isCodingQuestion = question.tags && (
486
+ question.tags.includes('coding') ||
487
+ question.tags.includes('programming') ||
488
+ question.id.includes('code')
489
+ );
490
+
491
+ // Show/hide coding editor based on question type
492
+ const answerContainer = document.querySelector('.answer-container');
493
+ if (answerContainer) {
494
+ if (isCodingRole && isCodingQuestion) {
495
+ // Show coding editor with language selector
496
+ answerContainer.innerHTML = `
497
+ <div class="coding-challenge-notice">
498
+ <i class="fas fa-code"></i>
499
+ <span>Coding Challenge - Write your solution below</span>
500
+ </div>
501
+ <div class="coding-language-selector">
502
+ <label for="languageSelect">
503
+ <i class="fas fa-laptop-code"></i>
504
+ <span>Select Language:</span>
505
+ </label>
506
+ <select id="languageSelect" class="language-dropdown" onchange="changeLanguage(this.value)">
507
+ <option value="python">Python</option>
508
+ <option value="javascript">JavaScript</option>
509
+ <option value="java">Java</option>
510
+ <option value="cpp">C++</option>
511
+ <option value="csharp">C#</option>
512
+ <option value="php">PHP</option>
513
+ <option value="ruby">Ruby</option>
514
+ <option value="go">Go</option>
515
+ <option value="rust">Rust</option>
516
+ <option value="typescript">TypeScript</option>
517
+ <option value="kotlin">Kotlin</option>
518
+ <option value="swift">Swift</option>
519
+ </select>
520
+ </div>
521
+ <iframe
522
+ id="codingEditor"
523
+ src="https://onecompiler.com/embed/python?hideNew=true&hideNewFileOption=true&hideLanguageSelection=true&theme=dark&hideStdin=true&hideResult=false&fontSize=16"
524
+ style="width: 100%; height: 100%; min-height: 700px; border: 1px solid var(--glass-border); border-radius: var(--radius-lg); margin-top: 1rem;"
525
+ frameborder="0"
526
+ ></iframe>
527
+ <div class="code-submission-notice" style="margin-top: 1rem; padding: 1rem; background: rgba(239, 68, 68, 0.1); border: 1px solid rgba(239, 68, 68, 0.3); border-radius: var(--radius-md);">
528
+ <i class="fas fa-exclamation-triangle" style="color: var(--color-danger);"></i>
529
+ <strong style="color: var(--color-danger);">Important:</strong> Copy your final working code from the editor above and paste it in the box below for evaluation.
530
+ </div>
531
+ <textarea
532
+ id="codeSubmission"
533
+ class="answer-input"
534
+ placeholder="Paste your final code here for evaluation..."
535
+ rows="6"
536
+ style="margin-top: 1rem; font-family: 'Courier New', monospace; font-size: 14px;"
537
+ ></textarea>
538
+ <textarea
539
+ id="answerText"
540
+ class="answer-input"
541
+ placeholder="Explain your approach and solution here..."
542
+ rows="4"
543
+ style="margin-top: 1rem;"
544
+ ></textarea>
545
+ `;
546
+ } else {
547
+ // Show regular textarea
548
+ answerContainer.innerHTML = `
549
+ <textarea
550
+ id="answerText"
551
+ class="answer-input"
552
+ placeholder="Type your response here..."
553
+ rows="8"
554
+ ></textarea>
555
+ <div class="ai-warning hidden" id="aiWarning">
556
+ <i class="fas fa-exclamation-triangle"></i>
557
+ <span>AI-generated content detected</span>
558
+ </div>
559
+ `;
560
+ }
561
+ }
562
+
563
+ document.getElementById('questionText').textContent = question.question;
564
+
565
+ // Add coding-mode class to interview section if it's a coding question
566
+ const interviewSection = document.getElementById('interviewSection');
567
+ if (interviewSection) {
568
+ if (isCodingRole && isCodingQuestion) {
569
+ interviewSection.classList.add('coding-mode');
570
+ } else {
571
+ interviewSection.classList.remove('coding-mode');
572
+ }
573
+ }
574
+
575
+ // Update progress
576
+ const progressNumber = document.querySelector('.progress-number');
577
+ if (progressNumber) {
578
+ progressNumber.textContent = `${index + 1}/${questions.length}`;
579
+ }
580
+
581
+ // Update progress ring
582
+ const progress = ((index + 1) / questions.length) * 100;
583
+ const progressRing = document.querySelector('.progress-ring');
584
+ if (progressRing) {
585
+ const circumference = 2 * Math.PI * 45;
586
+ const offset = circumference - (progress / 100) * circumference;
587
+ progressRing.style.strokeDasharray = `${circumference} ${circumference}`;
588
+ progressRing.style.strokeDashoffset = offset;
589
+ }
590
+
591
+ // Hide previous button (can't go back)
592
+ const prevButton = document.querySelector('[onclick="previousQuestion()"]');
593
+ if (prevButton) {
594
+ prevButton.style.display = 'none';
595
+ }
596
+
597
+ // Start question timer with auto-submit
598
+ // Give more time for coding questions (20 minutes vs 3 minutes)
599
+ if (isCodingRole && isCodingQuestion) {
600
+ window.examSecurity.startQuestionTimer(() => {
601
+ autoSubmitAndNext();
602
+ }, 1200); // 20 minutes for coding questions
603
+ } else {
604
+ window.examSecurity.startQuestionTimer(() => {
605
+ autoSubmitAndNext();
606
+ }, 180); // 3 minutes for regular questions
607
+ }
608
+ }
609
+
610
+ // Submit answer and move to next question
611
+ async function submitAnswer() {
612
+ const currentIndex = parseInt(sessionStorage.getItem('currentQuestion') || '0');
613
+ const questions = JSON.parse(sessionStorage.getItem('questions') || '[]');
614
+ const question = questions[currentIndex];
615
+
616
+ // Check if this is a coding question
617
+ const isCodingQuestion = question.tags && (
618
+ question.tags.includes('coding') ||
619
+ question.tags.includes('programming')
620
+ );
621
+
622
+ const answerTextarea = document.getElementById('answerText');
623
+ const answer = answerTextarea?.value.trim();
624
+
625
+ // For coding questions, require code submission
626
+ let codeSubmission = '';
627
+ if (isCodingQuestion) {
628
+ const codeTextarea = document.getElementById('codeSubmission');
629
+ codeSubmission = codeTextarea?.value.trim() || '';
630
+
631
+ if (!codeSubmission) {
632
+ showAlert('Please paste your code in the code submission box', 'error');
633
+ return;
634
+ }
635
+ }
636
+
637
+ if (!answer && !isCodingQuestion) {
638
+ showAlert('Please provide an answer before continuing', 'error');
639
+ return;
640
+ }
641
+
642
+ showLoading('Evaluating your answer...');
643
+
644
+ try {
645
+ const response = await fetch(getApiUrl('submit_answer'), {
646
+ method: 'POST',
647
+ headers: {
648
+ 'Content-Type': 'application/json',
649
+ 'X-User-Session-Id': getSessionId()
650
+ },
651
+ body: JSON.stringify({
652
+ question_id: question.id || `q${currentIndex + 1}`,
653
+ response_text: answer,
654
+ code_submission: codeSubmission,
655
+ is_coding_question: isCodingQuestion,
656
+ duration: window.examSecurity.getQuestionDuration()
657
+ })
658
+ });
659
+
660
+ if (!response.ok) {
661
+ const errorData = await response.json();
662
+ throw new Error(errorData.error || 'Failed to submit answer');
663
+ }
664
+
665
+ const result = await response.json();
666
+ hideLoading();
667
+
668
+ // Store response
669
+ const responses = JSON.parse(sessionStorage.getItem('responses') || '[]');
670
+ responses.push({
671
+ question: question.question,
672
+ answer: answer,
673
+ evaluation: result.evaluation,
674
+ time_taken: window.examSecurity.getQuestionDuration()
675
+ });
676
+ sessionStorage.setItem('responses', JSON.stringify(responses));
677
+
678
+ // Stop timer
679
+ window.examSecurity.stopQuestionTimer();
680
+
681
+ // Move to next question
682
+ if (currentIndex < questions.length - 1) {
683
+ sessionStorage.setItem('currentQuestion', (currentIndex + 1).toString());
684
+ loadQuestion(currentIndex + 1);
685
+ } else {
686
+ // Interview complete
687
+ completeInterview();
688
+ }
689
+
690
+ } catch (error) {
691
+ hideLoading();
692
+ console.error('Submit answer error:', error);
693
+ showAlert('Error submitting answer: ' + error.message, 'error');
694
+
695
+ // Still move to next question to avoid getting stuck
696
+ const currentIndex = parseInt(sessionStorage.getItem('currentQuestion') || '0');
697
+ const questions = JSON.parse(sessionStorage.getItem('questions') || '[]');
698
+ if (currentIndex < questions.length - 1) {
699
+ setTimeout(() => {
700
+ sessionStorage.setItem('currentQuestion', (currentIndex + 1).toString());
701
+ loadQuestion(currentIndex + 1);
702
+ }, 2000);
703
+ }
704
+ }
705
+ }
706
+
707
+ // Auto-submit when time runs out
708
+ async function autoSubmitAndNext() {
709
+ const answerTextarea = document.getElementById('answerText');
710
+ const answer = answerTextarea?.value.trim() || '[No answer provided - Time expired]';
711
+
712
+ const currentIndex = parseInt(sessionStorage.getItem('currentQuestion') || '0');
713
+ const questions = JSON.parse(sessionStorage.getItem('questions') || '[]');
714
+ const question = questions[currentIndex];
715
+
716
+ try {
717
+ const response = await fetch(getApiUrl('submit_answer'), {
718
+ method: 'POST',
719
+ headers: {
720
+ 'Content-Type': 'application/json',
721
+ 'X-User-Session-Id': getSessionId()
722
+ },
723
+ body: JSON.stringify({
724
+ question_id: question.id || `q${currentIndex + 1}`,
725
+ response_text: answer,
726
+ duration: '03:00',
727
+ auto_submitted: true
728
+ })
729
+ });
730
+
731
+ const result = await response.json();
732
+
733
+ // Store response
734
+ const responses = JSON.parse(sessionStorage.getItem('responses') || '[]');
735
+ responses.push({
736
+ question: question.question,
737
+ answer: answer,
738
+ evaluation: result.evaluation,
739
+ time_taken: '03:00',
740
+ auto_submitted: true
741
+ });
742
+ sessionStorage.setItem('responses', JSON.stringify(responses));
743
+
744
+ // Move to next question
745
+ if (currentIndex < questions.length - 1) {
746
+ sessionStorage.setItem('currentQuestion', (currentIndex + 1).toString());
747
+ loadQuestion(currentIndex + 1);
748
+ } else {
749
+ // Interview complete
750
+ completeInterview();
751
+ }
752
+
753
+ } catch (error) {
754
+ console.error('Auto-submit error:', error);
755
+ // Still move to next question even if submit fails
756
+ if (currentIndex < questions.length - 1) {
757
+ sessionStorage.setItem('currentQuestion', (currentIndex + 1).toString());
758
+ loadQuestion(currentIndex + 1);
759
+ } else {
760
+ completeInterview();
761
+ }
762
+ }
763
+ }
764
+
765
+ // Previous question (disabled - can't go back)
766
+ function previousQuestion() {
767
+ showAlert('You cannot go back to previous questions', 'error');
768
+ }
769
+
770
+ // Change coding language
771
+ function changeLanguage(language) {
772
+ const iframe = document.getElementById('codingEditor');
773
+ if (iframe) {
774
+ iframe.src = `https://onecompiler.com/embed/${language}?hideNew=true&hideNewFileOption=true&hideLanguageSelection=true&theme=dark&hideStdin=true&hideResult=false&fontSize=16`;
775
+ }
776
+ }
777
+
778
+ // Hide all UI elements during exam
779
+ function hideAllUIForExam() {
780
+ // Hide navigation
781
+ const nav = document.querySelector('.premium-nav');
782
+ if (nav) nav.style.display = 'none';
783
+
784
+ // Hide hero section
785
+ const hero = document.querySelector('.hero-section');
786
+ if (hero) hero.style.display = 'none';
787
+
788
+ // Hide background animations (dim them)
789
+ const bg = document.querySelector('.premium-bg');
790
+ if (bg) bg.style.opacity = '0.3';
791
+
792
+ // Add exam mode class to body
793
+ document.body.classList.add('exam-mode');
794
+ }
795
+
796
+ // Show all UI elements after exam
797
+ function showAllUIAfterExam() {
798
+ // Show navigation
799
+ const nav = document.querySelector('.premium-nav');
800
+ if (nav) nav.style.display = 'block';
801
+
802
+ // Show hero section
803
+ const hero = document.querySelector('.hero-section');
804
+ if (hero) hero.style.display = 'flex';
805
+
806
+ // Show background animations
807
+ const bg = document.querySelector('.premium-bg');
808
+ if (bg) bg.style.opacity = '1';
809
+
810
+ // Remove exam mode class from body
811
+ document.body.classList.remove('exam-mode');
812
+ }
813
+
814
+ // Complete interview
815
+ async function completeInterview() {
816
+ showLoading('Generating your assessment report...');
817
+
818
+ // End exam mode
819
+ window.examSecurity.endExamMode();
820
+
821
+ // Stop proctoring
822
+ window.proctoringSystem.stopProctoring();
823
+
824
+ // Restore UI elements
825
+ showAllUIAfterExam();
826
+
827
+ try {
828
+ const response = await fetch(getApiUrl('get_assessment'), {
829
+ method: 'GET',
830
+ headers: {
831
+ 'X-User-Session-Id': getSessionId()
832
+ }
833
+ });
834
+
835
+ if (!response.ok) {
836
+ const errorData = await response.json();
837
+ throw new Error(errorData.error || 'Failed to generate assessment');
838
+ }
839
+
840
+ const result = await response.json();
841
+ hideLoading();
842
+
843
+ if (result.assessment) {
844
+ sessionStorage.setItem('assessment', JSON.stringify(result.assessment));
845
+ showResults();
846
+ } else {
847
+ showAlert('Failed to generate assessment', 'error');
848
+ }
849
+
850
+ } catch (error) {
851
+ hideLoading();
852
+ console.error('Assessment error:', error);
853
+ showAlert('Error generating assessment: ' + error.message, 'error');
854
+
855
+ // Show results anyway with partial data
856
+ setTimeout(() => {
857
+ showResults();
858
+ }, 2000);
859
+ }
860
+ }
861
+
862
+ // Hide all UI elements during exam
863
+ function hideAllUIForExam() {
864
+ // Hide navigation
865
+ const nav = document.querySelector('.premium-nav');
866
+ if (nav) nav.style.display = 'none';
867
+
868
+ // Hide hero section
869
+ const hero = document.querySelector('.hero-section');
870
+ if (hero) hero.style.display = 'none';
871
+
872
+ // Hide background animations
873
+ const bg = document.querySelector('.premium-bg');
874
+ if (bg) bg.style.opacity = '0.3';
875
+
876
+ // Add exam mode class to body
877
+ document.body.classList.add('exam-mode');
878
+ }
879
+
880
+ // Show all UI elements after exam
881
+ function showAllUIAfterExam() {
882
+ // Show navigation
883
+ const nav = document.querySelector('.premium-nav');
884
+ if (nav) nav.style.display = 'block';
885
+
886
+ // Show hero section
887
+ const hero = document.querySelector('.hero-section');
888
+ if (hero) hero.style.display = 'flex';
889
+
890
+ // Show background animations
891
+ const bg = document.querySelector('.premium-bg');
892
+ if (bg) bg.style.opacity = '1';
893
+
894
+ // Remove exam mode class from body
895
+ document.body.classList.remove('exam-mode');
896
+ }
897
+
898
+ // Show results
899
+ function showResults() {
900
+ // Check if we're on the results page or main page
901
+ const isResultsPage = window.location.pathname.includes('results.php');
902
+
903
+ if (!isResultsPage) {
904
+ // Redirect to results page
905
+ window.location.href = 'results.php';
906
+ return;
907
+ }
908
+
909
+ document.getElementById('interviewSection')?.classList.add('hidden');
910
+ document.getElementById('resultsSection')?.classList.remove('hidden');
911
+
912
+ // Display assessment
913
+ const assessment = JSON.parse(sessionStorage.getItem('assessment') || '{}');
914
+ const responses = JSON.parse(sessionStorage.getItem('responses') || '[]');
915
+
916
+ console.log('Assessment:', assessment);
917
+ console.log('Responses:', responses);
918
+
919
+ // Check if we have assessment data
920
+ if (!assessment || typeof assessment.overallScore === 'undefined') {
921
+ showAlert('No assessment data available', 'error');
922
+ return;
923
+ }
924
+
925
+ // Update overall score (handle 0 as valid score)
926
+ const overallScoreEl = document.getElementById('overallScore');
927
+ if (overallScoreEl) {
928
+ overallScoreEl.textContent = assessment.overallScore;
929
+ }
930
+
931
+ // Update recommendation
932
+ const recommendationEl = document.getElementById('recommendation');
933
+ if (recommendationEl && assessment.recommendation) {
934
+ const rec = assessment.recommendation;
935
+ let icon = 'fa-check-circle';
936
+ let color = 'var(--color-success)';
937
+
938
+ if (rec.toLowerCase().includes('not recommended')) {
939
+ icon = 'fa-times-circle';
940
+ color = 'var(--color-danger)';
941
+ } else if (rec.toLowerCase().includes('consider')) {
942
+ icon = 'fa-exclamation-circle';
943
+ color = 'var(--color-warning)';
944
+ }
945
+
946
+ recommendationEl.innerHTML = `
947
+ <i class="fas ${icon}"></i>
948
+ <span>${rec}</span>
949
+ `;
950
+ recommendationEl.style.borderColor = color;
951
+ recommendationEl.style.color = color;
952
+ recommendationEl.style.background = color + '20';
953
+ }
954
+
955
+ // Update detailed scores
956
+ if (assessment.detailedScores) {
957
+ const scores = assessment.detailedScores;
958
+ const scoreItems = document.querySelectorAll('.score-item');
959
+
960
+ if (scoreItems.length >= 3) {
961
+ // Technical Skills
962
+ const techScore = scores.technicalSkills ?? 0;
963
+ scoreItems[0].querySelector('.score-percent').textContent = techScore + '%';
964
+ scoreItems[0].querySelector('.score-bar-fill').style.width = techScore + '%';
965
+
966
+ // Communication
967
+ const commScore = scores.communication ?? 0;
968
+ scoreItems[1].querySelector('.score-percent').textContent = commScore + '%';
969
+ scoreItems[1].querySelector('.score-bar-fill').style.width = commScore + '%';
970
+
971
+ // Soft Skills
972
+ const softScore = scores.softSkills ?? 0;
973
+ scoreItems[2].querySelector('.score-percent').textContent = softScore + '%';
974
+ scoreItems[2].querySelector('.score-bar-fill').style.width = softScore + '%';
975
+ }
976
+ }
977
+
978
+ // Update progress ring
979
+ const scoreRing = document.querySelector('.results-section .score-ring');
980
+ if (scoreRing) {
981
+ const score = assessment.overallScore ?? 0;
982
+ const circumference = 2 * Math.PI * 90;
983
+ const progress = (score / 100) * circumference;
984
+ scoreRing.style.strokeDasharray = `${circumference} ${circumference}`;
985
+ scoreRing.style.strokeDashoffset = circumference - progress;
986
+ }
987
+
988
+ // Update strengths list
989
+ const strengthsList = document.getElementById('strengthsList');
990
+ if (strengthsList && assessment.keyStrengths && assessment.keyStrengths.length > 0) {
991
+ strengthsList.innerHTML = assessment.keyStrengths.map(strength =>
992
+ `<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;">
993
+ <i class="fas fa-check-circle" style="color: var(--color-success); margin-right: 0.5rem;"></i>
994
+ ${strength}
995
+ </li>`
996
+ ).join('');
997
+ }
998
+
999
+ // Update improvements list
1000
+ const improvementsList = document.getElementById('improvementsList');
1001
+ if (improvementsList && assessment.areasForImprovement && assessment.areasForImprovement.length > 0) {
1002
+ improvementsList.innerHTML = assessment.areasForImprovement.map(area =>
1003
+ `<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;">
1004
+ <i class="fas fa-exclamation-circle" style="color: var(--color-danger); margin-right: 0.5rem;"></i>
1005
+ ${area}
1006
+ </li>`
1007
+ ).join('');
1008
+ }
1009
+ }
1010
+
1011
+ // Generate HTML content for report
1012
+ function generateReportHTML() {
1013
+ const assessment = JSON.parse(sessionStorage.getItem('assessment') || '{}');
1014
+ const profile = JSON.parse(sessionStorage.getItem('candidateProfile') || '{}');
1015
+ const responses = JSON.parse(sessionStorage.getItem('responses') || '[]');
1016
+
1017
+ // Check if assessment exists (allow 0 as valid score)
1018
+ if (!assessment || typeof assessment.overallScore === 'undefined') {
1019
+ return null;
1020
+ }
1021
+
1022
+ // Create HTML content
1023
+ let htmlContent = `
1024
+ <!DOCTYPE html>
1025
+ <html>
1026
+ <head>
1027
+ <meta charset="UTF-8">
1028
+ <title>Interview Assessment Report</title>
1029
+ <style>
1030
+ body { font-family: Arial, sans-serif; padding: 40px; color: #333; }
1031
+ .header { text-align: center; margin-bottom: 40px; border-bottom: 3px solid #6366f1; padding-bottom: 20px; }
1032
+ .header h1 { color: #6366f1; margin: 0; }
1033
+ .header p { color: #666; margin: 5px 0; }
1034
+ .section { margin: 30px 0; }
1035
+ .section h2 { color: #6366f1; border-bottom: 2px solid #e5e7eb; padding-bottom: 10px; }
1036
+ .profile-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 15px; margin: 20px 0; }
1037
+ .profile-item { padding: 10px; background: #f9fafb; border-radius: 5px; }
1038
+ .profile-item strong { color: #6366f1; }
1039
+ .score-box { text-align: center; padding: 30px; background: linear-gradient(135deg, #6366f1, #8b5cf6); color: white; border-radius: 10px; margin: 20px 0; }
1040
+ .score-box .score { font-size: 72px; font-weight: bold; }
1041
+ .score-box .label { font-size: 18px; opacity: 0.9; }
1042
+ .scores-grid { display: grid; grid-template-columns: 1fr 1fr 1fr; gap: 20px; margin: 20px 0; }
1043
+ .score-card { padding: 20px; background: #f9fafb; border-radius: 8px; text-align: center; }
1044
+ .score-card .value { font-size: 36px; font-weight: bold; color: #6366f1; }
1045
+ .score-card .name { color: #666; margin-top: 10px; }
1046
+ .recommendation { padding: 20px; background: #10b981; color: white; border-radius: 8px; text-align: center; font-size: 20px; font-weight: bold; margin: 20px 0; }
1047
+ .recommendation.not-recommended { background: #ef4444; }
1048
+ .recommendation.consider { background: #f59e0b; }
1049
+ .list-section ul { list-style: none; padding: 0; }
1050
+ .list-section li { padding: 10px; margin: 5px 0; background: #f9fafb; border-left: 4px solid #6366f1; }
1051
+ .question-analysis { margin: 20px 0; padding: 15px; background: #f9fafb; border-radius: 8px; }
1052
+ .question-analysis h4 { color: #6366f1; margin: 0 0 10px 0; }
1053
+ .question-analysis .answer { color: #666; font-style: italic; margin: 10px 0; }
1054
+ .question-analysis .scores { display: flex; gap: 15px; margin-top: 10px; }
1055
+ .question-analysis .scores span { padding: 5px 10px; background: white; border-radius: 5px; font-size: 14px; }
1056
+ .footer { margin-top: 50px; text-align: center; color: #999; font-size: 12px; border-top: 1px solid #e5e7eb; padding-top: 20px; }
1057
+ </style>
1058
+ </head>
1059
+ <body>
1060
+ <div class="header">
1061
+ <h1>🧠 IntervuAI Pro</h1>
1062
+ <p>AI-Powered Interview Assessment Report</p>
1063
+ <p>Generated on ${new Date().toLocaleDateString()} at ${new Date().toLocaleTimeString()}</p>
1064
+ </div>
1065
+
1066
+ <div class="section">
1067
+ <h2>Candidate Profile</h2>
1068
+ <div class="profile-grid">
1069
+ <div class="profile-item"><strong>Name:</strong> ${profile.name || 'N/A'}</div>
1070
+ <div class="profile-item"><strong>Email:</strong> ${profile.email || 'N/A'}</div>
1071
+ <div class="profile-item"><strong>Position:</strong> ${profile.position || profile.inferred_position || 'N/A'}</div>
1072
+ <div class="profile-item"><strong>Experience:</strong> ${profile.experience || 'N/A'}</div>
1073
+ </div>
1074
+ <div class="profile-item" style="grid-column: 1 / -1;">
1075
+ <strong>Skills:</strong> ${(profile.key_skills || []).join(', ') || 'N/A'}
1076
+ </div>
1077
+ </div>
1078
+
1079
+ <div class="score-box">
1080
+ <div class="score">${assessment.overallScore || 0}</div>
1081
+ <div class="label">Overall Score</div>
1082
+ </div>
1083
+
1084
+ <div class="recommendation ${getRecommendationClass(assessment.recommendation)}">
1085
+ ${assessment.recommendation || 'No Recommendation'}
1086
+ </div>
1087
+
1088
+ <div class="section">
1089
+ <h2>Detailed Scores</h2>
1090
+ <div class="scores-grid">
1091
+ <div class="score-card">
1092
+ <div class="value">${assessment.detailedScores?.technicalSkills || 0}%</div>
1093
+ <div class="name">Technical Skills</div>
1094
+ </div>
1095
+ <div class="score-card">
1096
+ <div class="value">${assessment.detailedScores?.communication || 0}%</div>
1097
+ <div class="name">Communication</div>
1098
+ </div>
1099
+ <div class="score-card">
1100
+ <div class="value">${assessment.detailedScores?.softSkills || 0}%</div>
1101
+ <div class="name">Soft Skills</div>
1102
+ </div>
1103
+ </div>
1104
+ </div>
1105
+
1106
+ <div class="section list-section">
1107
+ <h2>Key Strengths</h2>
1108
+ <ul>
1109
+ ${(assessment.keyStrengths || ['No strengths identified']).map(s => `<li>${s}</li>`).join('')}
1110
+ </ul>
1111
+ </div>
1112
+
1113
+ <div class="section list-section">
1114
+ <h2>Areas for Improvement</h2>
1115
+ <ul>
1116
+ ${(assessment.areasForImprovement || ['No areas identified']).map(a => `<li>${a}</li>`).join('')}
1117
+ </ul>
1118
+ </div>
1119
+
1120
+ <div class="section">
1121
+ <h2>Question-by-Question Analysis</h2>
1122
+ ${responses.map((r, i) => `
1123
+ <div class="question-analysis">
1124
+ <h4>Question ${i + 1}: ${r.question}</h4>
1125
+ <div class="answer"><strong>Answer:</strong> ${r.answer.substring(0, 200)}${r.answer.length > 200 ? '...' : ''}</div>
1126
+ <div class="scores">
1127
+ <span>Technical: ${r.evaluation?.technicalScore || 0}%</span>
1128
+ <span>Communication: ${r.evaluation?.communicationScore || 0}%</span>
1129
+ <span>Relevance: ${r.evaluation?.relevanceScore || 0}%</span>
1130
+ <span>Overall: ${r.evaluation?.score || 0}%</span>
1131
+ </div>
1132
+ <p><strong>Feedback:</strong> ${r.evaluation?.feedback || 'No feedback'}</p>
1133
+ </div>
1134
+ `).join('')}
1135
+ </div>
1136
+
1137
+ <div class="footer">
1138
+ <p>This report was generated by IntervuAI Pro - AI-Powered Interview Platform</p>
1139
+ <p>Interview Duration: ${assessment.interviewDuration || 'N/A'}</p>
1140
+ </div>
1141
+ </body>
1142
+ </html>
1143
+ `;
1144
+
1145
+ return { htmlContent, profile, assessment };
1146
+ }
1147
+
1148
+ // Download report as HTML
1149
+ function downloadReportHTML() {
1150
+ const reportData = generateReportHTML();
1151
+
1152
+ if (!reportData) {
1153
+ showAlert('No assessment data available. Please complete the interview first.', 'error');
1154
+ return;
1155
+ }
1156
+
1157
+ const { htmlContent, profile } = reportData;
1158
+
1159
+ // Create a blob and download
1160
+ const blob = new Blob([htmlContent], { type: 'text/html' });
1161
+ const url = URL.createObjectURL(blob);
1162
+ const a = document.createElement('a');
1163
+ a.href = url;
1164
+ a.download = `Interview_Assessment_${profile.name || 'Candidate'}_${new Date().toISOString().split('T')[0]}.html`;
1165
+ document.body.appendChild(a);
1166
+ a.click();
1167
+ document.body.removeChild(a);
1168
+ URL.revokeObjectURL(url);
1169
+
1170
+ showAlert('HTML report downloaded successfully!', 'success');
1171
+ }
1172
+
1173
+ // Download report as PDF
1174
+ async function downloadReportPDF() {
1175
+ const reportData = generateReportHTML();
1176
+
1177
+ if (!reportData) {
1178
+ showAlert('No assessment data available. Please complete the interview first.', 'error');
1179
+ return;
1180
+ }
1181
+
1182
+ const { htmlContent, profile, assessment } = reportData;
1183
+
1184
+ showLoading('Generating PDF report...');
1185
+
1186
+ try {
1187
+ // Create a temporary container
1188
+ const container = document.createElement('div');
1189
+ container.style.position = 'absolute';
1190
+ container.style.left = '-9999px';
1191
+ container.style.width = '800px';
1192
+ container.innerHTML = htmlContent;
1193
+ document.body.appendChild(container);
1194
+
1195
+ // Wait for content to render
1196
+ await new Promise(resolve => setTimeout(resolve, 100));
1197
+
1198
+ // Use html2canvas to capture the content
1199
+ const canvas = await html2canvas(container, {
1200
+ scale: 2,
1201
+ useCORS: true,
1202
+ logging: false,
1203
+ backgroundColor: '#ffffff'
1204
+ });
1205
+
1206
+ // Remove temporary container
1207
+ document.body.removeChild(container);
1208
+
1209
+ // Create PDF using jsPDF
1210
+ const { jsPDF } = window.jspdf;
1211
+ const pdf = new jsPDF({
1212
+ orientation: 'portrait',
1213
+ unit: 'mm',
1214
+ format: 'a4'
1215
+ });
1216
+
1217
+ const imgWidth = 210; // A4 width in mm
1218
+ const pageHeight = 297; // A4 height in mm
1219
+ const imgHeight = (canvas.height * imgWidth) / canvas.width;
1220
+ let heightLeft = imgHeight;
1221
+ let position = 0;
1222
+
1223
+ const imgData = canvas.toDataURL('image/png');
1224
+
1225
+ // Add first page
1226
+ pdf.addImage(imgData, 'PNG', 0, position, imgWidth, imgHeight);
1227
+ heightLeft -= pageHeight;
1228
+
1229
+ // Add additional pages if needed
1230
+ while (heightLeft > 0) {
1231
+ position = heightLeft - imgHeight;
1232
+ pdf.addPage();
1233
+ pdf.addImage(imgData, 'PNG', 0, position, imgWidth, imgHeight);
1234
+ heightLeft -= pageHeight;
1235
+ }
1236
+
1237
+ // Save PDF
1238
+ pdf.save(`Interview_Assessment_${profile.name || 'Candidate'}_${new Date().toISOString().split('T')[0]}.pdf`);
1239
+
1240
+ hideLoading();
1241
+ showAlert('PDF report downloaded successfully!', 'success');
1242
+
1243
+ } catch (error) {
1244
+ hideLoading();
1245
+ console.error('PDF generation error:', error);
1246
+ showAlert('Failed to generate PDF. Try downloading HTML instead.', 'error');
1247
+ }
1248
+ }
1249
+
1250
+ function getRecommendationClass(recommendation) {
1251
+ if (!recommendation) return '';
1252
+ const rec = recommendation.toLowerCase();
1253
+ if (rec.includes('not recommended')) return 'not-recommended';
1254
+ if (rec.includes('consider')) return 'consider';
1255
+ return '';
1256
+ }
1257
+
1258
+ // Utility functions
1259
+ function getSessionId() {
1260
+ let sessionId = sessionStorage.getItem('sessionId');
1261
+ if (!sessionId) {
1262
+ sessionId = 'session_' + Date.now() + '_' + Math.random().toString(36).substring(2, 11);
1263
+ sessionStorage.setItem('sessionId', sessionId);
1264
+ }
1265
+ return sessionId;
1266
+ }
1267
+
1268
+ function showLoading(message = 'Processing...') {
1269
+ const overlay = document.getElementById('loadingOverlay');
1270
+ if (overlay) {
1271
+ overlay.querySelector('.loading-text').textContent = message;
1272
+ overlay.classList.remove('hidden');
1273
+ }
1274
+ }
1275
+
1276
+ function hideLoading() {
1277
+ const overlay = document.getElementById('loadingOverlay');
1278
+ if (overlay) {
1279
+ overlay.classList.add('hidden');
1280
+ }
1281
+ }
1282
+
1283
+ function showAlert(message, type = 'info') {
1284
+ // Create alert element
1285
+ const alert = document.createElement('div');
1286
+ alert.className = `alert-toast alert-${type}`;
1287
+ alert.innerHTML = `
1288
+ <i class="fas fa-${type === 'success' ? 'check-circle' : type === 'error' ? 'exclamation-circle' : 'info-circle'}"></i>
1289
+ <span>${message}</span>
1290
+ `;
1291
+
1292
+ document.body.appendChild(alert);
1293
+
1294
+ // Animate in
1295
+ setTimeout(() => alert.classList.add('show'), 100);
1296
+
1297
+ // Remove after 3 seconds
1298
+ setTimeout(() => {
1299
+ alert.classList.remove('show');
1300
+ setTimeout(() => alert.remove(), 300);
1301
+ }, 3000);
1302
+ }
1303
+
1304
+ // Add toast styles
1305
+ const style = document.createElement('style');
1306
+ style.textContent = `
1307
+ .alert-toast {
1308
+ position: fixed;
1309
+ top: 100px;
1310
+ right: 20px;
1311
+ padding: 16px 24px;
1312
+ background: var(--glass-bg);
1313
+ backdrop-filter: blur(20px);
1314
+ border: 1px solid var(--glass-border);
1315
+ border-radius: var(--radius-lg);
1316
+ display: flex;
1317
+ align-items: center;
1318
+ gap: 12px;
1319
+ z-index: 10000;
1320
+ transform: translateX(400px);
1321
+ transition: transform 0.3s ease;
1322
+ box-shadow: var(--glass-shadow);
1323
+ }
1324
+
1325
+ .alert-toast.show {
1326
+ transform: translateX(0);
1327
+ }
1328
+
1329
+ .alert-toast.alert-success {
1330
+ border-color: var(--color-success);
1331
+ color: var(--color-success);
1332
+ }
1333
+
1334
+ .alert-toast.alert-error {
1335
+ border-color: var(--color-danger);
1336
+ color: var(--color-danger);
1337
+ }
1338
+
1339
+ .alert-toast.alert-info {
1340
+ border-color: var(--color-primary);
1341
+ color: var(--color-primary);
1342
+ }
1343
+
1344
+ .profile-display {
1345
+ display: grid;
1346
+ gap: 1rem;
1347
+ padding: 1.5rem;
1348
+ background: var(--color-surface);
1349
+ border-radius: var(--radius-lg);
1350
+ border: 1px solid var(--glass-border);
1351
+ }
1352
+
1353
+ .profile-item {
1354
+ display: flex;
1355
+ gap: 1rem;
1356
+ align-items: start;
1357
+ }
1358
+
1359
+ .profile-label {
1360
+ font-weight: 600;
1361
+ color: var(--color-text-secondary);
1362
+ min-width: 120px;
1363
+ }
1364
+
1365
+ .profile-value {
1366
+ color: var(--color-text-primary);
1367
+ }
1368
+
1369
+ .skills-tags {
1370
+ display: flex;
1371
+ flex-wrap: wrap;
1372
+ gap: 0.5rem;
1373
+ }
1374
+
1375
+ .skill-tag {
1376
+ padding: 4px 12px;
1377
+ background: rgba(99, 102, 241, 0.1);
1378
+ border: 1px solid rgba(99, 102, 241, 0.3);
1379
+ border-radius: 20px;
1380
+ font-size: 14px;
1381
+ color: var(--color-primary);
1382
+ }
1383
+
1384
+ .form-input {
1385
+ width: 100%;
1386
+ padding: 14px 16px;
1387
+ background: var(--color-surface);
1388
+ border: 1px solid var(--glass-border);
1389
+ border-radius: var(--radius-md);
1390
+ color: var(--color-text-primary);
1391
+ font-size: 16px;
1392
+ transition: all var(--transition-base);
1393
+ }
1394
+
1395
+ .form-input:focus {
1396
+ outline: none;
1397
+ border-color: var(--color-primary);
1398
+ box-shadow: 0 0 0 4px rgba(99, 102, 241, 0.1);
1399
+ }
1400
+
1401
+ .form-label {
1402
+ display: flex;
1403
+ align-items: center;
1404
+ gap: 8px;
1405
+ font-size: 14px;
1406
+ font-weight: 600;
1407
+ margin-bottom: 8px;
1408
+ color: var(--color-text-secondary);
1409
+ }
1410
+ `;
1411
+ document.head.appendChild(style);
1412
+
1413
+
1414
+ // Reset interview and go back to home
1415
+ function resetInterview() {
1416
+ // During exam mode, show warning about termination
1417
+ if (document.body.classList.contains('exam-mode')) {
1418
+ if (confirm('⚠️ WARNING: Exiting will terminate your interview!\n\nYour progress will be lost and the interview will be marked as incomplete.\n\nAre you sure you want to exit?')) {
1419
+ // End exam mode
1420
+ window.examSecurity.endExamMode();
1421
+
1422
+ // Log termination
1423
+ window.examSecurity.logSecurityEvent('exam_abandoned', {
1424
+ reason: 'user_exit',
1425
+ timestamp: new Date().toISOString()
1426
+ });
1427
+
1428
+ sessionStorage.clear();
1429
+ location.reload();
1430
+ }
1431
+ } else {
1432
+ if (confirm('Are you sure you want to go back to home? All progress will be lost.')) {
1433
+ sessionStorage.clear();
1434
+ location.reload();
1435
+ }
1436
+ }
1437
+ }
1438
+
1439
+ // Update next button to submit answer
1440
+ document.addEventListener('DOMContentLoaded', () => {
1441
+ // Replace next button click handler
1442
+ const nextButton = document.querySelector('[onclick="nextQuestion()"]');
1443
+ if (nextButton) {
1444
+ nextButton.setAttribute('onclick', 'submitAnswer()');
1445
+ const buttonText = nextButton.querySelector('span');
1446
+ if (buttonText) {
1447
+ buttonText.textContent = 'Submit & Next';
1448
+ }
1449
+ }
1450
+ });
static/assets/js/proctoring.js ADDED
@@ -0,0 +1,605 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * Proctoring System - Camera and Audio Monitoring
3
+ * - Face detection
4
+ * - Looking away detection
5
+ * - Audio monitoring
6
+ * - Warning system (2 warnings max)
7
+ */
8
+
9
+ class ProctoringSystem {
10
+ constructor() {
11
+ this.isActive = false;
12
+ this.stream = null;
13
+ this.videoElement = null;
14
+ this.lookAwayCount = 0;
15
+ this.maxLookAwayWarnings = 2;
16
+ this.lookAwayTimer = null;
17
+ this.lookAwayThreshold = 3000; // 3 seconds
18
+ this.faceDetectionInterval = null;
19
+ this.lastFaceDetected = Date.now();
20
+ this.noFaceDetectedTime = 0;
21
+ this.multipleFacesCount = 0;
22
+
23
+ // BlazeFace model
24
+ this.faceDetectionModel = null;
25
+
26
+ // Audio monitoring
27
+ this.audioContext = null;
28
+ this.analyser = null;
29
+ this.audioViolationCount = 0;
30
+ this.maxAudioViolations = 2;
31
+ this.audioThreshold = 30; // Lower threshold for better detection (0-100)
32
+ this.suspiciousAudioDuration = 0;
33
+ this.audioCheckInterval = null;
34
+ }
35
+
36
+ async startProctoring() {
37
+ try {
38
+ // Load face detection model
39
+ console.log('Loading face detection model...');
40
+ this.faceDetectionModel = await blazeface.load();
41
+ console.log('Face detection model loaded');
42
+
43
+ // Request camera and microphone access
44
+ this.stream = await navigator.mediaDevices.getUserMedia({
45
+ video: {
46
+ width: { ideal: 640 },
47
+ height: { ideal: 480 },
48
+ facingMode: 'user'
49
+ },
50
+ audio: {
51
+ echoCancellation: false,
52
+ noiseSuppression: false,
53
+ autoGainControl: false
54
+ }
55
+ });
56
+
57
+ // Create video element
58
+ this.createVideoElement();
59
+ this.videoElement.srcObject = this.stream;
60
+ await this.videoElement.play();
61
+
62
+ this.isActive = true;
63
+
64
+ // Start face detection
65
+ this.startFaceDetection();
66
+
67
+ // Start audio monitoring
68
+ this.startAudioMonitoring();
69
+
70
+ // Log proctoring start
71
+ this.logProctoringEvent('proctoring_started', {
72
+ timestamp: new Date().toISOString()
73
+ });
74
+
75
+ return true;
76
+ } catch (error) {
77
+ console.error('Proctoring error:', error);
78
+ this.showProctoringError(error);
79
+ return false;
80
+ }
81
+ }
82
+
83
+ createVideoElement() {
84
+ // Create video preview
85
+ const videoContainer = document.createElement('div');
86
+ videoContainer.id = 'proctoringVideo';
87
+ videoContainer.style.cssText = `
88
+ position: fixed;
89
+ bottom: 20px;
90
+ right: 20px;
91
+ width: 200px;
92
+ height: 150px;
93
+ border: 2px solid var(--color-primary);
94
+ border-radius: var(--radius-lg);
95
+ overflow: hidden;
96
+ z-index: 9998;
97
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
98
+ `;
99
+
100
+ this.videoElement = document.createElement('video');
101
+ this.videoElement.style.cssText = `
102
+ width: 100%;
103
+ height: 100%;
104
+ object-fit: cover;
105
+ transform: scaleX(-1);
106
+ `;
107
+ this.videoElement.autoplay = true;
108
+ this.videoElement.muted = true;
109
+
110
+ const label = document.createElement('div');
111
+ label.style.cssText = `
112
+ position: absolute;
113
+ top: 5px;
114
+ left: 5px;
115
+ background: rgba(0, 0, 0, 0.7);
116
+ color: white;
117
+ padding: 4px 8px;
118
+ border-radius: 4px;
119
+ font-size: 12px;
120
+ display: flex;
121
+ align-items: center;
122
+ gap: 5px;
123
+ `;
124
+ label.innerHTML = `
125
+ <i class="fas fa-video" style="color: var(--color-success);"></i>
126
+ <span>Proctoring Active</span>
127
+ `;
128
+
129
+ videoContainer.appendChild(this.videoElement);
130
+ videoContainer.appendChild(label);
131
+ document.body.appendChild(videoContainer);
132
+ }
133
+
134
+ startFaceDetection() {
135
+ // Use BlazeFace for accurate face detection
136
+ this.faceDetectionInterval = setInterval(() => {
137
+ if (!this.isActive) return;
138
+
139
+ this.detectFace();
140
+ }, 1000); // Check every second
141
+ }
142
+
143
+ async detectFace() {
144
+ try {
145
+ if (!this.faceDetectionModel || !this.videoElement) return;
146
+
147
+ // Detect faces using BlazeFace
148
+ const predictions = await this.faceDetectionModel.estimateFaces(this.videoElement, false);
149
+
150
+ const faceCount = predictions.length;
151
+
152
+ // Update face count indicator
153
+ this.updateFaceCountIndicator(faceCount);
154
+
155
+ if (faceCount === 0) {
156
+ // No face detected
157
+ this.noFaceDetectedTime += 1000;
158
+
159
+ if (this.noFaceDetectedTime >= this.lookAwayThreshold) {
160
+ this.handleNoFaceDetected();
161
+ this.noFaceDetectedTime = 0;
162
+ }
163
+ } else if (faceCount === 1) {
164
+ // Exactly one face - good!
165
+ this.noFaceDetectedTime = 0;
166
+ this.lastFaceDetected = Date.now();
167
+ } else if (faceCount > 1) {
168
+ // Multiple faces detected
169
+ this.handleMultipleFaces(faceCount);
170
+ }
171
+
172
+ } catch (error) {
173
+ console.error('Face detection error:', error);
174
+ }
175
+ }
176
+
177
+ handleNoFaceDetected() {
178
+ this.lookAwayCount++;
179
+
180
+ this.logProctoringEvent('no_face_detected', {
181
+ count: this.lookAwayCount,
182
+ timestamp: new Date().toISOString()
183
+ });
184
+
185
+ if (this.lookAwayCount >= this.maxLookAwayWarnings) {
186
+ this.terminateExam();
187
+ } else {
188
+ const remaining = this.maxLookAwayWarnings - this.lookAwayCount;
189
+ this.showWarning(
190
+ `⚠️ NO FACE DETECTED!\n\nYou have ${remaining} warning(s) remaining.\n\nPlease look at the camera and stay in frame.`
191
+ );
192
+ }
193
+ }
194
+
195
+ handleMultipleFaces(count) {
196
+ this.multipleFacesCount++;
197
+
198
+ this.logProctoringEvent('multiple_faces_detected', {
199
+ face_count: count,
200
+ violation_count: this.multipleFacesCount,
201
+ timestamp: new Date().toISOString()
202
+ });
203
+
204
+ if (this.multipleFacesCount >= 2) {
205
+ this.terminateExamMultipleFaces(count);
206
+ } else {
207
+ this.showWarning(
208
+ `⚠️ MULTIPLE PEOPLE DETECTED!\n\n${count} faces detected in frame.\n\nOnly the candidate should be visible.\n\nThis is your only warning.`
209
+ );
210
+ }
211
+ }
212
+
213
+ terminateExamMultipleFaces(count) {
214
+ this.isActive = false;
215
+
216
+ this.logProctoringEvent('exam_terminated', {
217
+ reason: 'multiple_faces_detected',
218
+ face_count: count,
219
+ timestamp: new Date().toISOString()
220
+ });
221
+
222
+ const overlay = document.createElement('div');
223
+ overlay.className = 'critical-warning-overlay show';
224
+ overlay.style.zIndex = '99999';
225
+ overlay.innerHTML = `
226
+ <div class="critical-warning-box">
227
+ <div class="warning-icon">
228
+ <i class="fas fa-ban"></i>
229
+ </div>
230
+ <h3>Interview Terminated</h3>
231
+ <p>Multiple people detected in the camera frame.</p>
232
+ <p style="margin-top: 1rem;">Only the candidate should be visible during the interview.</p>
233
+ <p style="margin-top: 1rem;">Your interview has been automatically submitted with current progress.</p>
234
+ <div style="margin-top: 2rem; display: flex; gap: 1rem; justify-content: center;">
235
+ <button class="btn-primary" onclick="window.location.href='index.php'">
236
+ <span>Go to Home</span>
237
+ </button>
238
+ </div>
239
+ </div>
240
+ `;
241
+ document.body.appendChild(overlay);
242
+
243
+ this.stopProctoring();
244
+ }
245
+
246
+ updateFaceCountIndicator(count) {
247
+ const videoContainer = document.getElementById('proctoringVideo');
248
+ if (!videoContainer) return;
249
+
250
+ let indicator = document.getElementById('faceCountIndicator');
251
+ if (!indicator) {
252
+ indicator = document.createElement('div');
253
+ indicator.id = 'faceCountIndicator';
254
+ indicator.style.cssText = `
255
+ position: absolute;
256
+ top: 5px;
257
+ right: 5px;
258
+ background: rgba(0, 0, 0, 0.7);
259
+ color: white;
260
+ padding: 4px 8px;
261
+ border-radius: 4px;
262
+ font-size: 12px;
263
+ font-weight: bold;
264
+ `;
265
+ videoContainer.appendChild(indicator);
266
+ }
267
+
268
+ if (count === 0) {
269
+ indicator.style.background = 'rgba(239, 68, 68, 0.9)';
270
+ indicator.innerHTML = '<i class="fas fa-user-slash"></i> No Face';
271
+ } else if (count === 1) {
272
+ indicator.style.background = 'rgba(16, 185, 129, 0.9)';
273
+ indicator.innerHTML = '<i class="fas fa-user-check"></i> 1 Face';
274
+ } else {
275
+ indicator.style.background = 'rgba(239, 68, 68, 0.9)';
276
+ indicator.innerHTML = `<i class="fas fa-users"></i> ${count} Faces`;
277
+ }
278
+ }
279
+
280
+ handleLookingAway() {
281
+ const timeSinceLastFace = Date.now() - this.lastFaceDetected;
282
+
283
+ if (timeSinceLastFace > this.lookAwayThreshold && !this.lookAwayTimer) {
284
+ this.lookAwayTimer = setTimeout(() => {
285
+ this.lookAwayCount++;
286
+
287
+ this.logProctoringEvent('looking_away', {
288
+ count: this.lookAwayCount,
289
+ timestamp: new Date().toISOString()
290
+ });
291
+
292
+ if (this.lookAwayCount >= this.maxLookAwayWarnings) {
293
+ this.terminateExam();
294
+ } else {
295
+ const remaining = this.maxLookAwayWarnings - this.lookAwayCount;
296
+ this.showWarning(
297
+ `⚠️ FACE NOT DETECTED!\n\nYou have ${remaining} warning(s) remaining.\n\nPlease look at the camera and stay focused on the screen.`
298
+ );
299
+ }
300
+
301
+ this.lookAwayTimer = null;
302
+ }, 1000);
303
+ }
304
+ }
305
+
306
+ clearLookAwayTimer() {
307
+ if (this.lookAwayTimer) {
308
+ clearTimeout(this.lookAwayTimer);
309
+ this.lookAwayTimer = null;
310
+ }
311
+ }
312
+
313
+ terminateExam() {
314
+ this.isActive = false;
315
+
316
+ this.logProctoringEvent('exam_terminated', {
317
+ reason: 'looking_away_exceeded',
318
+ timestamp: new Date().toISOString()
319
+ });
320
+
321
+ const overlay = document.createElement('div');
322
+ overlay.className = 'critical-warning-overlay show';
323
+ overlay.style.zIndex = '99999';
324
+ overlay.innerHTML = `
325
+ <div class="critical-warning-box">
326
+ <div class="warning-icon">
327
+ <i class="fas fa-ban"></i>
328
+ </div>
329
+ <h3>Interview Terminated</h3>
330
+ <p>You have been looking away from the screen for too long.</p>
331
+ <p style="margin-top: 1rem;">Your interview has been automatically submitted with current progress.</p>
332
+ <div style="margin-top: 2rem; display: flex; gap: 1rem; justify-content: center;">
333
+ <button class="btn-primary" onclick="window.location.href='index.php'">
334
+ <span>Go to Home</span>
335
+ </button>
336
+ </div>
337
+ </div>
338
+ `;
339
+ document.body.appendChild(overlay);
340
+
341
+ this.stopProctoring();
342
+ }
343
+
344
+ startAudioMonitoring() {
345
+ try {
346
+ // Create audio context
347
+ this.audioContext = new (window.AudioContext || window.webkitAudioContext)();
348
+ this.analyser = this.audioContext.createAnalyser();
349
+ this.analyser.fftSize = 256;
350
+
351
+ // Connect microphone to analyser
352
+ const source = this.audioContext.createMediaStreamSource(this.stream);
353
+ source.connect(this.analyser);
354
+
355
+ // Start monitoring
356
+ this.audioCheckInterval = setInterval(() => {
357
+ this.checkAudioLevel();
358
+ }, 500); // Check every 500ms
359
+
360
+ // Add audio indicator to video preview
361
+ this.addAudioIndicator();
362
+
363
+ } catch (error) {
364
+ console.error('Audio monitoring error:', error);
365
+ }
366
+ }
367
+
368
+ checkAudioLevel() {
369
+ if (!this.isActive || !this.analyser) return;
370
+
371
+ const dataArray = new Uint8Array(this.analyser.frequencyBinCount);
372
+ this.analyser.getByteFrequencyData(dataArray);
373
+
374
+ // Calculate average volume
375
+ let sum = 0;
376
+ for (let i = 0; i < dataArray.length; i++) {
377
+ sum += dataArray[i];
378
+ }
379
+ const average = sum / dataArray.length;
380
+ const volumePercent = (average / 255) * 100;
381
+
382
+ // Update audio indicator
383
+ this.updateAudioIndicator(volumePercent);
384
+
385
+ // Check if volume exceeds threshold (someone speaking)
386
+ if (volumePercent > this.audioThreshold) {
387
+ this.suspiciousAudioDuration += 500; // Add 500ms
388
+
389
+ // If speaking for more than 2 seconds (more sensitive)
390
+ if (this.suspiciousAudioDuration >= 2000) {
391
+ this.handleAudioViolation();
392
+ this.suspiciousAudioDuration = 0;
393
+ }
394
+ } else {
395
+ // Reset if quiet
396
+ if (this.suspiciousAudioDuration > 0) {
397
+ this.suspiciousAudioDuration = Math.max(0, this.suspiciousAudioDuration - 500);
398
+ }
399
+ }
400
+ }
401
+
402
+ handleAudioViolation() {
403
+ this.audioViolationCount++;
404
+
405
+ this.logProctoringEvent('audio_violation', {
406
+ count: this.audioViolationCount,
407
+ timestamp: new Date().toISOString()
408
+ });
409
+
410
+ if (this.audioViolationCount >= this.maxAudioViolations) {
411
+ this.terminateExamAudio();
412
+ } else {
413
+ const remaining = this.maxAudioViolations - this.audioViolationCount;
414
+ this.showWarning(
415
+ `⚠️ SUSPICIOUS AUDIO DETECTED!\n\nVoice or conversation detected in the background.\n\nYou have ${remaining} warning(s) remaining.\n\nPlease ensure you are alone and silent during the interview.`
416
+ );
417
+ }
418
+ }
419
+
420
+ terminateExamAudio() {
421
+ this.isActive = false;
422
+
423
+ this.logProctoringEvent('exam_terminated', {
424
+ reason: 'audio_violations_exceeded',
425
+ timestamp: new Date().toISOString()
426
+ });
427
+
428
+ const overlay = document.createElement('div');
429
+ overlay.className = 'critical-warning-overlay show';
430
+ overlay.style.zIndex = '99999';
431
+ overlay.innerHTML = `
432
+ <div class="critical-warning-box">
433
+ <div class="warning-icon">
434
+ <i class="fas fa-ban"></i>
435
+ </div>
436
+ <h3>Interview Terminated</h3>
437
+ <p>Multiple audio violations detected.</p>
438
+ <p style="margin-top: 1rem;">Voice or conversation was detected in the background, which is not allowed during the interview.</p>
439
+ <p style="margin-top: 1rem;">Your interview has been automatically submitted with current progress.</p>
440
+ <div style="margin-top: 2rem; display: flex; gap: 1rem; justify-content: center;">
441
+ <button class="btn-primary" onclick="window.location.href='index.php'">
442
+ <span>Go to Home</span>
443
+ </button>
444
+ </div>
445
+ </div>
446
+ `;
447
+ document.body.appendChild(overlay);
448
+
449
+ this.stopProctoring();
450
+ }
451
+
452
+ addAudioIndicator() {
453
+ const videoContainer = document.getElementById('proctoringVideo');
454
+ if (!videoContainer) return;
455
+
456
+ const audioIndicator = document.createElement('div');
457
+ audioIndicator.id = 'audioIndicator';
458
+ audioIndicator.style.cssText = `
459
+ position: absolute;
460
+ bottom: 5px;
461
+ left: 5px;
462
+ right: 5px;
463
+ height: 20px;
464
+ background: rgba(0, 0, 0, 0.7);
465
+ border-radius: 4px;
466
+ overflow: hidden;
467
+ `;
468
+
469
+ const audioBar = document.createElement('div');
470
+ audioBar.id = 'audioBar';
471
+ audioBar.style.cssText = `
472
+ height: 100%;
473
+ width: 0%;
474
+ background: linear-gradient(90deg, var(--color-success), var(--color-warning), var(--color-danger));
475
+ transition: width 0.1s ease;
476
+ `;
477
+
478
+ const audioLabel = document.createElement('div');
479
+ audioLabel.style.cssText = `
480
+ position: absolute;
481
+ top: 50%;
482
+ left: 50%;
483
+ transform: translate(-50%, -50%);
484
+ color: white;
485
+ font-size: 10px;
486
+ font-weight: bold;
487
+ pointer-events: none;
488
+ `;
489
+ audioLabel.innerHTML = '<i class="fas fa-microphone"></i> Audio';
490
+
491
+ audioIndicator.appendChild(audioBar);
492
+ audioIndicator.appendChild(audioLabel);
493
+ videoContainer.appendChild(audioIndicator);
494
+ }
495
+
496
+ updateAudioIndicator(volumePercent) {
497
+ const audioBar = document.getElementById('audioBar');
498
+ if (audioBar) {
499
+ audioBar.style.width = `${Math.min(volumePercent, 100)}%`;
500
+
501
+ // Change color based on volume
502
+ if (volumePercent > this.audioThreshold) {
503
+ audioBar.style.background = 'var(--color-danger)';
504
+ } else {
505
+ audioBar.style.background = 'linear-gradient(90deg, var(--color-success), var(--color-warning), var(--color-danger))';
506
+ }
507
+ }
508
+ }
509
+
510
+ stopProctoring() {
511
+ this.isActive = false;
512
+
513
+ if (this.faceDetectionInterval) {
514
+ clearInterval(this.faceDetectionInterval);
515
+ }
516
+
517
+ if (this.audioCheckInterval) {
518
+ clearInterval(this.audioCheckInterval);
519
+ }
520
+
521
+ if (this.audioContext) {
522
+ this.audioContext.close();
523
+ }
524
+
525
+ if (this.stream) {
526
+ this.stream.getTracks().forEach(track => track.stop());
527
+ }
528
+
529
+ const videoContainer = document.getElementById('proctoringVideo');
530
+ if (videoContainer) {
531
+ videoContainer.remove();
532
+ }
533
+ }
534
+
535
+ showWarning(message) {
536
+ const warning = document.createElement('div');
537
+ warning.className = 'critical-warning-overlay show';
538
+ warning.style.zIndex = '99999';
539
+ warning.innerHTML = `
540
+ <div class="critical-warning-box">
541
+ <div class="warning-icon">
542
+ <i class="fas fa-exclamation-triangle"></i>
543
+ </div>
544
+ <h3>Proctoring Warning</h3>
545
+ <p>${message.replace(/\n/g, '<br>')}</p>
546
+ <button class="btn-primary" onclick="this.parentElement.parentElement.remove()">
547
+ <span>I Understand</span>
548
+ </button>
549
+ </div>
550
+ `;
551
+ document.body.appendChild(warning);
552
+ }
553
+
554
+ showProctoringError(error) {
555
+ const errorMessage = error.name === 'NotAllowedError'
556
+ ? 'Camera and microphone access is required for this interview. Please allow access and refresh the page.'
557
+ : 'Failed to access camera/microphone. Please check your device settings.';
558
+
559
+ const overlay = document.createElement('div');
560
+ overlay.className = 'critical-warning-overlay show';
561
+ overlay.style.zIndex = '99999';
562
+ overlay.innerHTML = `
563
+ <div class="critical-warning-box">
564
+ <div class="warning-icon">
565
+ <i class="fas fa-video-slash"></i>
566
+ </div>
567
+ <h3>Camera Access Required</h3>
568
+ <p>${errorMessage}</p>
569
+ <div style="margin-top: 2rem; display: flex; gap: 1rem; justify-content: center;">
570
+ <button class="btn-secondary" onclick="window.location.href='index.php'">
571
+ <span>Cancel</span>
572
+ </button>
573
+ <button class="btn-primary" onclick="window.location.reload()">
574
+ <span>Retry</span>
575
+ </button>
576
+ </div>
577
+ </div>
578
+ `;
579
+ document.body.appendChild(overlay);
580
+ }
581
+
582
+ logProctoringEvent(eventType, data) {
583
+ console.log(`PROCTORING EVENT: ${eventType}`, data);
584
+
585
+ // Send to backend
586
+ fetch(getApiUrl('log_security'), {
587
+ method: 'POST',
588
+ headers: {
589
+ 'Content-Type': 'application/json',
590
+ 'X-User-Session-Id': getSessionId()
591
+ },
592
+ body: JSON.stringify({
593
+ event_type: eventType,
594
+ data: data,
595
+ timestamp: new Date().toISOString()
596
+ })
597
+ }).catch(err => console.error('Failed to log proctoring event:', err));
598
+ }
599
+ }
600
+
601
+ // Initialize proctoring system
602
+ const proctoringSystem = new ProctoringSystem();
603
+
604
+ // Export for global use
605
+ window.proctoringSystem = proctoringSystem;
static/index.html ADDED
@@ -0,0 +1,481 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>IntervuAI Pro - Next-Gen AI Interview Platform</title>
7
+ <link rel="preconnect" href="https://fonts.googleapis.com">
8
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
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>
43
+ </div>
44
+ </div>
45
+ </div>
46
+ </div>
47
+ </nav>
48
+
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>
92
+
93
+ <!-- Upload Section -->
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>
130
+ </div>
131
+ </div>
132
+ <div class="profile-display" id="profileDisplay"></div>
133
+ <div class="form-group" style="margin-top: 2rem;">
134
+ <label class="form-label"><i class="fas fa-briefcase"></i> Position/Role</label>
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">
154
+ <div class="progress-indicator">
155
+ <div class="progress-circle">
156
+ <svg viewBox="0 0 100 100">
157
+ <circle cx="50" cy="50" r="45"></circle>
158
+ <circle cx="50" cy="50" r="45" class="progress-ring" id="progressRing"></circle>
159
+ </svg>
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">
202
+ <div class="score-circle">
203
+ <svg viewBox="0 0 200 200">
204
+ <circle cx="100" cy="100" r="90"></circle>
205
+ <circle cx="100" cy="100" r="90" class="score-ring" id="scoreRing"></circle>
206
+ </svg>
207
+ <div class="score-content">
208
+ <span class="score-value" id="overallScore">--</span>
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>
238
+
239
+ <!-- Loading Overlay -->
240
+ <div class="loading-overlay hidden" id="loadingOverlay">
241
+ <div class="loading-content">
242
+ <div class="loading-spinner"></div>
243
+ <p class="loading-text" id="loadingText">Processing with AI...</p>
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>