sameernotes commited on
Commit
dd72ec3
·
verified ·
1 Parent(s): 5ef6fb6

Upload 14 files

Browse files
admin.html ADDED
@@ -0,0 +1,613 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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>Hindi OCR App - Admin Panel</title>
7
+ <style>
8
+ /* --- REUSABLE STYLES (from translation.html) --- */
9
+ :root {
10
+ --primary-color: #4a90e2; /* Consistent primary color */
11
+ --secondary-color: #f8f9fa;
12
+ --text-color: #2c3e50;
13
+ --border-radius: 12px;
14
+ --transition: all 0.3s ease;
15
+ }
16
+
17
+ * {
18
+ margin: 0;
19
+ padding: 0;
20
+ box-sizing: border-box;
21
+ }
22
+
23
+ body {
24
+ font-family: 'Segoe UI', system-ui, -apple-system, sans-serif; /* Consistent font */
25
+ line-height: 1.6;
26
+ color: var(--text-color);
27
+ background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%); /* Consistent background */
28
+ min-height: 100vh;
29
+ padding: 2rem;
30
+ }
31
+
32
+ .container {
33
+ max-width: 1000px;
34
+ margin: 0 auto;
35
+ background: white;
36
+ padding: 2rem;
37
+ border-radius: var(--border-radius);
38
+ box-shadow: 0 10px 30px rgba(0, 0, 0, 0.1);
39
+ }
40
+
41
+ h1 {
42
+ color: var(--primary-color);
43
+ text-align: center;
44
+ margin-bottom: 2rem;
45
+ font-size: 2.5rem;
46
+ font-weight: 700;
47
+ }
48
+
49
+ h2 {
50
+ color: var(--text-color);
51
+ margin-bottom: 1.5rem;
52
+ font-size: 1.8rem;
53
+ }
54
+
55
+ label {
56
+ display: block;
57
+ margin-bottom: 0.5rem;
58
+ font-weight: 600;
59
+ color: var(--text-color);
60
+ }
61
+
62
+ input[type="text"], input[type="password"], input[type="email"] {
63
+ width: 100%;
64
+ padding: 1rem;
65
+ margin-bottom: 1.5rem;
66
+ border: 2px solid #e1e8ed;
67
+ border-radius: var(--border-radius);
68
+ font-size: 1rem;
69
+ transition: var(--transition);
70
+ background: white;
71
+ }
72
+
73
+
74
+ input[type="text"]:focus, input[type="password"]:focus, input[type="email"]:focus {
75
+ outline: none;
76
+ border-color: var(--primary-color);
77
+ box-shadow: 0 0 0 3px rgba(74, 144, 226, 0.1);
78
+ }
79
+
80
+ button {
81
+ background: var(--primary-color);
82
+ color: white;
83
+ padding: 1rem 2rem;
84
+ border: none;
85
+ border-radius: var(--border-radius);
86
+ font-size: 1.1rem;
87
+ cursor: pointer;
88
+ transition: var(--transition);
89
+ width: 100%;
90
+ margin-top: 1rem;
91
+ }
92
+
93
+ button:hover {
94
+ transform: translateY(-2px);
95
+ box-shadow: 0 5px 15px rgba(74, 144, 226, 0.3);
96
+ }
97
+ /* --- ERROR MESSAGE --- */
98
+ .error-message {
99
+ background: #fee2e2;
100
+ color: #dc2626;
101
+ padding: 1rem;
102
+ border-radius: var(--border-radius);
103
+ margin-top: 1rem;
104
+ display: none; /* Hidden by default */
105
+ }
106
+ /* --- BUTTONS --- */
107
+ .button-secondary {
108
+ background-color: var(--secondary-color);
109
+ color: var(--primary-color);
110
+ border: 2px solid var(--primary-color);
111
+ width: auto;
112
+ }
113
+ .button-secondary:hover {
114
+ background-color: rgba(74, 144, 226, 0.1);
115
+ box-shadow: 0 5px 15px rgba(74, 144, 226, 0.3);
116
+ }
117
+ /* --- NAVIGATION BAR --- */
118
+ nav {
119
+ display: flex;
120
+ flex-wrap: wrap; /* Allow wrapping on smaller screens */
121
+ gap: 1rem;
122
+ justify-content: center; /* Center items */
123
+ margin-bottom: 2rem;
124
+ border-bottom: 2px solid var(--secondary-color);
125
+ padding-bottom: 1rem;
126
+ }
127
+ nav a {
128
+ color: var(--primary-color);
129
+ text-decoration: none;
130
+ padding: 0.5rem 1rem;
131
+ border-radius: var(--border-radius);
132
+ transition: var(--transition);
133
+ }
134
+ nav a:hover {
135
+ background-color: rgba(74, 144, 226, 0.1);
136
+ }
137
+ nav a.active {
138
+ background-color: var(--primary-color);
139
+ color: white;
140
+ }
141
+ .subtitle {
142
+ text-align: center;
143
+ color: #64748b;
144
+ margin-bottom: 1rem;
145
+ }
146
+
147
+ .attribution {
148
+ text-align: center;
149
+ color: #64748b;
150
+ font-size: 0.875rem;
151
+ margin-bottom: 2rem;
152
+ }
153
+ /* --- LOGOUT BUTTON --- */
154
+ .logout-button {
155
+ position: absolute;
156
+ top: 1rem;
157
+ right: 1rem;
158
+ width: auto; /* Let the button size itself based on content */
159
+ }
160
+
161
+ .logout-button svg {
162
+ margin-right: 0.5rem;
163
+ }
164
+ /* --- ADMIN TABLE STYLES --- */
165
+ .admin-table {
166
+ width: 100%;
167
+ border-collapse: collapse;
168
+ margin-top: 1rem; /* Reduced margin */
169
+ }
170
+ .admin-table th, .admin-table td {
171
+ border: 1px solid #e1e8ed; /* Lighter border */
172
+ padding: 0.75rem; /* Reduced padding */
173
+ text-align: left;
174
+ font-size: 0.9rem; /* Smaller font size */
175
+ }
176
+ .admin-table th {
177
+ background-color: var(--secondary-color);
178
+ font-weight: 600; /* Use font-weight from variables */
179
+ color: var(--text-color);
180
+ }
181
+ /* --- ADMIN BUTTONS (Pagination) --- */
182
+ .admin-buttons {
183
+ display: flex;
184
+ gap: 0.5rem; /* Reduced gap */
185
+ justify-content: center;
186
+ margin-top: 1rem; /* Reduced margin */
187
+ }
188
+
189
+ .admin-buttons button {
190
+ width: auto; /* Let buttons size to content */
191
+ padding: 0.5rem 1rem; /* Smaller padding */
192
+ font-size: 0.9rem;
193
+ margin-top: 0; /* Remove top margin from generic button style */
194
+ }
195
+ /* --- RESPONSIVE --- */
196
+ @media (max-width: 768px) {
197
+ body {
198
+ padding: 1rem;
199
+ }
200
+
201
+ .container {
202
+ padding: 1rem;
203
+ }
204
+ nav {
205
+ justify-content: space-between; /* Adjust as needed */
206
+ }
207
+
208
+ nav a {
209
+ padding: 0.5rem; /* Smaller padding on mobile */
210
+ }
211
+ .admin-table th,
212
+ .admin-table td {
213
+ padding: 0.5rem; /*Even smaller padding on mobile*/
214
+ font-size: 0.8rem;
215
+ }
216
+
217
+ .admin-buttons button {
218
+ padding: 0.4rem 0.8rem; /*Smaller padding on mobile*/
219
+ font-size: 0.8rem;
220
+ }
221
+ }
222
+ .credits {
223
+ text-align: center;
224
+ margin-top: 2rem;
225
+ color: var(--text-color);
226
+ font-size: 0.875rem;
227
+ }
228
+ .feedback-form {
229
+ padding: 2rem;
230
+ background: var(--secondary-color);
231
+ border-radius: var(--border-radius);
232
+ margin-bottom: 2rem;
233
+ }
234
+
235
+ </style>
236
+ </head>
237
+ <body>
238
+ <div class="container" id="admin-app-container" style="display: none;">
239
+ <header>
240
+ <nav>
241
+ <a href="home.html">Home</a>
242
+ <a href="translation.html" target="_blank">translation</a> <!-- Open in new tab -->
243
+ <a href="gender_predictor.html" target="_blank">Gender predictor</a> <!-- Open in new tab -->
244
+ <a href="index.html">OCR App</a>
245
+ <a href="features.html" target="_blank">Key Features</a>
246
+ <a href="feedback.html" target="_blank">Feedback</a>
247
+ <a href="contact.html" target="_blank">Contact Us</a>
248
+ <a href="admin.html" class="active">Admin Panel</a>
249
+ </nav>
250
+ <button id="adminLogoutButton" class="logout-button button-secondary">
251
+ Logout
252
+ </button>
253
+ <h1>Hindi OCR Admin Panel</h1>
254
+ <p class="subtitle">Manage users and feedback for the Hindi OCR Application</p>
255
+ <p class="attribution">Powered by Sakshi's Hindi OCR Engine</p>
256
+
257
+ </header>
258
+
259
+ <div class="main-content">
260
+ <div class="container">
261
+ <h2> User Management</h2>
262
+ <div class="error-message" id="userErrorMessage"></div>
263
+ <div class = "feedback-form">
264
+ <table class="admin-table" id="userTable">
265
+ <thead>
266
+ <tr>
267
+ <th>ID</th>
268
+ <th>Username</th>
269
+ <th>Email</th>
270
+ <th>Is Active</th>
271
+ <th>Is Admin</th>
272
+ <th>Actions</th>
273
+ </tr>
274
+ </thead>
275
+ <tbody id="userTableBody">
276
+ <tr><td colspan="6">Loading users...</td></tr>
277
+ </tbody>
278
+ </table>
279
+ <div class="admin-buttons" id="userPagination">
280
+ <button id="prevUsers" disabled>< Previous</button>
281
+ <button id="nextUsers">Next ></button>
282
+ </div>
283
+ </div>
284
+ </div>
285
+
286
+ <div class="container">
287
+ <h2>Feedback Management</h2>
288
+ <div class="error-message" id="feedbackErrorMessage"></div>
289
+ <div class = "feedback-form">
290
+ <table class="admin-table" id="feedbackTable">
291
+ <thead>
292
+ <tr>
293
+ <th>ID</th>
294
+ <th>Username</th>
295
+ <th>Comment</th>
296
+ <th>Created At</th>
297
+ </tr>
298
+ </thead>
299
+ <tbody id="feedbackTableBody">
300
+ <tr><td colspan="4">Loading feedback...</td></tr>
301
+ </tbody>
302
+ </table>
303
+ <div class="admin-buttons" id="feedbackPagination">
304
+ <button id="prevFeedback" disabled>< Previous</button>
305
+ <button id="nextFeedback">Next ></button>
306
+ </div>
307
+ </div>
308
+ </div>
309
+ </div>
310
+
311
+ <div class="credits">
312
+ <p>Powered by <strong>D SAKSHI</strong> (MCA Final Year BIT Durg, Chhattisgarh) | © SlimShadow Org. All Rights Reserved.</p>
313
+ </div>
314
+ </div>
315
+
316
+ <div id="login-container" style="display:flex; justify-content: center; align-items: center;">
317
+ <div class="container" id="admin-login-card">
318
+ <h2>Admin Login</h2>
319
+ <p>Login to access the Admin Panel.</p>
320
+ <div class="error-message" id="adminLoginErrorMessage"></div>
321
+ <label for="adminUsername">Username</label>
322
+ <input type="text" id="adminUsername" placeholder="Username" value="admin">
323
+ <label for="adminPassword">Password</label>
324
+ <input type="password" id="adminPassword" placeholder="Password" value="adminpassword">
325
+ <button id="adminLoginButton">Login</button>
326
+ </div>
327
+ </div>
328
+
329
+ <script>
330
+ const API_BASE_URL = 'https://sameernotes-ocr.hf.space';
331
+ let adminAccessToken = null;
332
+ const adminAppContainer = document.getElementById('admin-app-container');
333
+ const loginContainer = document.getElementById('login-container');
334
+ const adminLoginCard = document.getElementById('admin-login-card');
335
+
336
+ // Admin Login Elements
337
+ const adminUsernameInput = document.getElementById('adminUsername');
338
+ const adminPasswordInput = document.getElementById('adminPassword');
339
+ const adminLoginButton = document.getElementById('adminLoginButton');
340
+ const adminLoginErrorMessage = document.getElementById('adminLoginErrorMessage');
341
+ const adminLogoutButton = document.getElementById('adminLogoutButton');
342
+
343
+ // User Table Elements
344
+ const userTableBody = document.getElementById('userTableBody');
345
+ const userErrorMessage = document.getElementById('userErrorMessage');
346
+ const prevUsersButton = document.getElementById('prevUsers');
347
+ const nextUsersButton = document.getElementById('nextUsers');
348
+ let currentUserPage = 0;
349
+ const usersPerPage = 5; // Adjust as needed
350
+
351
+ // Feedback Table Elements
352
+ const feedbackTableBody = document.getElementById('feedbackTableBody');
353
+ const feedbackErrorMessage = document.getElementById('feedbackErrorMessage');
354
+ const prevFeedbackButton = document.getElementById('prevFeedback');
355
+ const nextFeedbackButton = document.getElementById('nextFeedback');
356
+ let currentFeedbackPage = 0;
357
+ const feedbackPerPage = 5; // Adjust as needed
358
+
359
+
360
+ adminLoginButton.addEventListener('click', async () => {
361
+ const username = adminUsernameInput.value;
362
+ const password = adminPasswordInput.value;
363
+
364
+ if (!username || !password) {
365
+ showAdminLoginError("Please fill in all fields.");
366
+ return;
367
+ }
368
+
369
+ const formData = new URLSearchParams();
370
+ formData.append('username', username);
371
+ formData.append('password', password);
372
+
373
+ try {
374
+ const response = await fetch(`${API_BASE_URL}/token`, {
375
+ method: 'POST',
376
+ headers: {
377
+ 'Content-Type': 'application/x-www-form-urlencoded'
378
+ },
379
+ body: formData.toString()
380
+ });
381
+
382
+ if (!response.ok) {
383
+ const errorData = await response.json();
384
+ showAdminLoginError(errorData.detail || "Login failed");
385
+ return;
386
+ }
387
+
388
+ const data = await response.json();
389
+ adminAccessToken = data.access_token;
390
+ loginContainer.style.display = 'none';
391
+ adminAppContainer.style.display = 'block';
392
+ adminLoginErrorMessage.style.display = 'none';
393
+ loadUsers(); // Load users table after login
394
+ loadFeedback(); // Load feedback table after login
395
+
396
+
397
+ } catch (error) {
398
+ showAdminLoginError( "Error during login. Please try again.");
399
+ console.error("Admin Login error:", error);
400
+ }
401
+ });
402
+
403
+ adminLogoutButton.addEventListener('click', () => {
404
+ adminAccessToken = null;
405
+ adminAppContainer.style.display = 'none';
406
+ loginContainer.style.display = 'flex';
407
+ // Redirect to index.html
408
+ window.location.href = "index.html";
409
+ });
410
+
411
+
412
+ async function loadUsers() {
413
+ userTableBody.innerHTML = '<tr><td colspan="6">Loading users...</td></tr>';
414
+ userErrorMessage.style.display = 'none';
415
+
416
+ try {
417
+ const response = await fetch(`${API_BASE_URL}/admin/users/?skip=${currentUserPage * usersPerPage}&limit=${usersPerPage}`, {
418
+ headers: {
419
+ 'Authorization': `Bearer ${adminAccessToken}`
420
+ }
421
+ });
422
+
423
+ if (!response.ok) {
424
+ if (response.status === 403) {
425
+ showUserError( "Unauthorized: Admin access required.");
426
+ } else {
427
+ showUserError( `Failed to load users. Status: ${response.status}`);
428
+ }
429
+ userTableBody.innerHTML = '<tr><td colspan="6">Error loading users.</td></tr>';
430
+ return;
431
+ }
432
+
433
+ const users = await response.json();
434
+ if (users.length === 0 && currentUserPage > 0) {
435
+ currentUserPage--; // Adjust page if no users on current page and not on first page
436
+ await loadUsers(); // Reload users with adjusted page
437
+ return;
438
+ }
439
+ populateUserTable(users);
440
+
441
+
442
+ } catch (error) {
443
+ showUserError("Error loading users. Please check console.");
444
+ userTableBody.innerHTML = '<tr><td colspan="6">Error loading users.</td></tr>';
445
+ console.error("Error fetching users:", error);
446
+ }
447
+ }
448
+
449
+ function populateUserTable(users) {
450
+ userTableBody.innerHTML = '';
451
+ if (users.length === 0) {
452
+ userTableBody.innerHTML = '<tr><td colspan="6">No users found.</td></tr>';
453
+ return;
454
+ }
455
+
456
+ users.forEach(user => {
457
+ const row = userTableBody.insertRow();
458
+ row.insertCell(0).textContent = user.id;
459
+ row.insertCell(1).textContent = user.username;
460
+ row.insertCell(2).textContent = user.email;
461
+ row.insertCell(3).textContent = user.is_active ? 'Yes' : 'No';
462
+ row.insertCell(4).textContent = user.is_admin ? 'Yes' : 'No';
463
+ const actionsCell = row.insertCell(5);
464
+ actionsCell.innerHTML = `<button class="button-secondary" onclick="deleteUser(${user.id})">Delete</button>`;
465
+ });
466
+
467
+ // Update pagination button states
468
+ prevUsersButton.disabled = currentUserPage === 0;
469
+ nextUsersButton.disabled = users.length < usersPerPage; // Disable if fewer users than per page, assuming last page
470
+ }
471
+
472
+ prevUsersButton.addEventListener('click', async () => {
473
+ if (currentUserPage > 0) {
474
+ currentUserPage--;
475
+ await loadUsers();
476
+ }
477
+ });
478
+
479
+ nextUsersButton.addEventListener('click', async () => {
480
+ currentUserPage++;
481
+ await loadUsers();
482
+ });
483
+
484
+
485
+ async function deleteUser(userId) {
486
+ if (confirm(`Are you sure you want to delete user ID ${userId}?`)) {
487
+ try {
488
+ const response = await fetch(`${API_BASE_URL}/admin/users/${userId}`, {
489
+ method: 'DELETE',
490
+ headers: {
491
+ 'Authorization': `Bearer ${adminAccessToken}`
492
+ }
493
+ });
494
+
495
+ if (!response.ok) {
496
+ const errorData = await response.json();
497
+ showUserError( errorData.detail || `Failed to delete user. Status: ${response.status}`);
498
+ return;
499
+ }
500
+
501
+ userErrorMessage.style.display = 'none';
502
+ alert(`User ID ${userId} deleted successfully.`);
503
+ loadUsers(); // Reload user list
504
+ } catch (error) {
505
+ showUserError("Error deleting user. Please check console.");
506
+ console.error("Error deleting user:", error);
507
+ }
508
+ }
509
+ }
510
+
511
+
512
+ async function loadFeedback() {
513
+ feedbackTableBody.innerHTML = '<tr><td colspan="4">Loading feedback...</td></tr>';
514
+ feedbackErrorMessage.style.display = 'none';
515
+
516
+ try {
517
+ const response = await fetch(`${API_BASE_URL}/admin/feedback/?skip=${currentFeedbackPage * feedbackPerPage}&limit=${feedbackPerPage}`, {
518
+ headers: {
519
+ 'Authorization': `Bearer ${adminAccessToken}`
520
+ }
521
+ });
522
+
523
+ if (!response.ok) {
524
+ if (response.status === 403) {
525
+ showFeedbackError( "Unauthorized access.");
526
+ } else {
527
+ showFeedbackError( `Failed to load feedback. Status: ${response.status}`);
528
+ }
529
+ feedbackTableBody.innerHTML = '<tr><td colspan="4">Error loading feedback.</td></tr>';
530
+ return;
531
+ }
532
+
533
+ const feedbackData = await response.json();
534
+ if (feedbackData.length === 0 && currentFeedbackPage > 0) {
535
+ currentFeedbackPage--;
536
+ await loadFeedback();
537
+ return;
538
+ }
539
+ populateFeedbackTable(feedbackData);
540
+
541
+
542
+ } catch (error) {
543
+ showFeedbackError("Error loading feedback. Please check console.");
544
+ feedbackTableBody.innerHTML = '<tr><td colspan="4">Error loading feedback.</td></tr>';
545
+ console.error("Error fetching feedback:", error);
546
+ }
547
+ }
548
+
549
+ function populateFeedbackTable(feedbackData) {
550
+ feedbackTableBody.innerHTML = '';
551
+ if (feedbackData.length === 0) {
552
+ feedbackTableBody.innerHTML = '<tr><td colspan="4">No feedback found.</td></tr>';
553
+ return;
554
+ }
555
+
556
+ feedbackData.forEach(feedback => {
557
+ const row = feedbackTableBody.insertRow();
558
+ row.insertCell(0).textContent = feedback.id;
559
+ row.insertCell(1).textContent = feedback.username;
560
+ row.insertCell(2).textContent = feedback.comment;
561
+ row.insertCell(3).textContent = new Date(feedback.created_at).toLocaleString();
562
+ });
563
+
564
+ prevFeedbackButton.disabled = currentFeedbackPage === 0;
565
+ nextFeedbackButton.disabled = feedbackData.length < feedbackPerPage;
566
+ }
567
+
568
+
569
+ prevFeedbackButton.addEventListener('click', async () => {
570
+ if (currentFeedbackPage > 0) {
571
+ currentFeedbackPage--;
572
+ await loadFeedback();
573
+ }
574
+ });
575
+
576
+ nextFeedbackButton.addEventListener('click', async () => {
577
+ currentFeedbackPage++;
578
+ await loadFeedback();
579
+ });
580
+
581
+ function showAdminLoginError(message) {
582
+ adminLoginErrorMessage.textContent = message;
583
+ adminLoginErrorMessage.style.display = 'block';
584
+ setTimeout(() => {
585
+ adminLoginErrorMessage.style.display = 'none';
586
+ }, 5000); // Hide after 5 seconds
587
+
588
+ }
589
+ function showUserError(message) {
590
+ userErrorMessage.textContent = message;
591
+ userErrorMessage.style.display = 'block';
592
+ setTimeout(() => {
593
+ userErrorMessage.style.display = 'none';
594
+ }, 5000);
595
+ }
596
+
597
+ function showFeedbackError(message) {
598
+ feedbackErrorMessage.textContent = message;
599
+ feedbackErrorMessage.style.display = 'block';
600
+ setTimeout(() => {
601
+ feedbackErrorMessage.style.display = 'none';
602
+ }, 5000);
603
+ }
604
+
605
+ // --- Initial Check for Admin Token ---
606
+ // For simplicity, always start at admin login for this example
607
+ loginContainer.style.display = 'flex';
608
+ adminAppContainer.style.display = 'none';
609
+
610
+
611
+ </script>
612
+ </body>
613
+ </html>
contact.html ADDED
@@ -0,0 +1,271 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <!-- Start with lang="en" and no 'dark' class initially -->
3
+ <html lang="en" class="">
4
+
5
+ <head>
6
+ <meta charset="UTF-8">
7
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
8
+ <title>Vision and Discern - Feedback</title> <!-- Updated Title -->
9
+
10
+ <!-- Tailwind CSS via CDN -->
11
+ <script src="https://cdn.tailwindcss.com/3.4.1"></script>
12
+ <script>
13
+ tailwind.config = {
14
+ darkMode: 'class', // Enable class-based dark mode
15
+ theme: {
16
+ extend: {
17
+ colors: {
18
+ primary: '#4a90e2', // Match main app's primary color
19
+ secondary: '#f0f8ff',
20
+ },
21
+ borderRadius: {
22
+ 'none': '0px', 'sm': '4px', DEFAULT: '8px', 'md': '12px',
23
+ 'lg': '16px', 'xl': '20px', '2xl': '24px', '3xl': '32px',
24
+ 'full': '9999px',
25
+ 'button': '4px' // Match main app's button radius
26
+ }
27
+ }
28
+ }
29
+ }
30
+ </script>
31
+ <!-- Fonts -->
32
+ <link rel="preconnect" href="https://fonts.googleapis.com">
33
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
34
+ <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
35
+ <!-- Icons (Remixicon) -->
36
+ <link href="https://cdn.jsdelivr.net/npm/remixicon@4.2.0/fonts/remixicon.css" rel="stylesheet">
37
+
38
+ <!-- Custom Styles (Mainly for the switch) -->
39
+ <style>
40
+ body { font-family: 'Inter', sans-serif; }
41
+ /* Custom Switch Styles - Copied from main app */
42
+ .custom-switch { position: relative; display: inline-block; width: 50px; height: 24px; }
43
+ .custom-switch-input { opacity: 0; width: 0; height: 0; }
44
+ .custom-switch-slider { position: absolute; cursor: pointer; top: 0; left: 0; right: 0; bottom: 0; background-color: #ccc; transition: .4s; border-radius: 34px; }
45
+ .custom-switch-slider:before { position: absolute; content: ""; height: 18px; width: 18px; left: 3px; bottom: 3px; background-color: white; transition: .4s; border-radius: 50%; }
46
+ .custom-switch-input:checked + .custom-switch-slider { background-color: #4a90e2; } /* Your primary color */
47
+ .custom-switch-input:checked + .custom-switch-slider:before { transform: translateX(26px); }
48
+
49
+ /* Base dark mode styles - Copied from main app */
50
+ html.dark body { background-color: #111827; color: #d1d5db; }
51
+ html.dark header, html.dark footer { background-color: #1f2937; }
52
+ html.dark .card { background-color: #1f2937; border-color: #374151; }
53
+ html.dark h1, html.dark h2, html.dark h3, html.dark p, html.dark span, html.dark li, html.dark label, html.dark small, html.dark .subtitle, html.dark .info-text, html.dark .credits p, html.dark .attribution { color: #d1d5db; }
54
+ html.dark .text-gray-600 { color: #9ca3af; }
55
+ html.dark .text-gray-700 { color: #9ca3af; }
56
+ html.dark .text-gray-500 { color: #6b7280; }
57
+ /* Input/Textarea dark styles */
58
+ html.dark input[type="text"],
59
+ html.dark input[type="email"],
60
+ html.dark textarea {
61
+ background-color: #374151; /* gray-700 */
62
+ border-color: #4b5563; /* gray-600 */
63
+ color: #d1d5db; /* gray-300 */
64
+ }
65
+ html.dark input::placeholder,
66
+ html.dark textarea::placeholder {
67
+ color: #9ca3af; /* gray-400 */
68
+ }
69
+ html.dark .button-secondary { background-color: #4b5563; color: #d1d5db; }
70
+ html.dark .button-secondary:hover { background-color: #374151; }
71
+ /* Success message dark */
72
+ html.dark #feedbackSuccessMessage {
73
+ background-color: #065f46; /* emerald-800 */
74
+ color: #a7f3d0; /* emerald-200 */
75
+ border-color: #047857; /* emerald-700 */
76
+ }
77
+ </style>
78
+ </head>
79
+
80
+ <body class="bg-gray-100 dark:bg-gray-900 min-h-screen flex flex-col text-gray-900 dark:text-gray-200">
81
+
82
+ <!-- Main Application Container -->
83
+ <div id="app-container" class="flex-grow flex flex-col">
84
+
85
+ <!-- Header -->
86
+ <header class="bg-white dark:bg-gray-800 shadow-sm sticky top-0 z-50">
87
+ <div class="container mx-auto px-4 py-3 flex items-center justify-between">
88
+ <!-- Left Side: Logo & Nav -->
89
+ <div class="flex items-center">
90
+ <a href="home.html" class="text-xl font-bold text-primary dark:text-blue-400 mr-6">Vision & Discern</a>
91
+ <nav class="hidden md:flex space-x-6">
92
+ <a href="home.html" class="text-gray-600 dark:text-gray-300 hover:text-primary dark:hover:text-blue-400">Home</a>
93
+ <a href="index.html" class="text-gray-600 dark:text-gray-300 hover:text-primary dark:hover:text-blue-400">OCR</a>
94
+ <a href="features.html" class="text-gray-600 dark:text-gray-300 hover:text-primary dark:hover:text-blue-400">Features</a>
95
+ <a href="feedback.html" class="text-primary dark:text-blue-400 font-medium">Feedback</a>
96
+ <a href="contact.html" class="text-gray-600 dark:text-gray-300 hover:text-primary dark:hover:text-blue-400">Contact Us</a>
97
+ </nav>
98
+ </div>
99
+
100
+ <!-- Right Side: Switches & Logout -->
101
+ <div class="flex items-center space-x-4">
102
+ <!-- Theme Switch -->
103
+ <div class="items-center space-x-2 hidden md:flex">
104
+ <span class="text-sm text-gray-600 dark:text-gray-400"><i class="ri-sun-line"></i></span>
105
+ <label class="custom-switch">
106
+ <input type="checkbox" id="themeToggle" class="custom-switch-input">
107
+ <span class="custom-switch-slider"></span>
108
+ </label>
109
+ <span class="text-sm text-gray-600 dark:text-gray-400"><i class="ri-moon-line"></i></span>
110
+ </div>
111
+ <!-- Logout Button -->
112
+ <button id="logoutButton" class="button-secondary bg-red-600 hover:bg-red-700 dark:bg-red-700 dark:hover:bg-red-800 text-white px-3 py-1.5 rounded-button text-sm inline-flex items-center gap-1">
113
+ <i class="ri-logout-box-r-line"></i>
114
+ Logout
115
+ </button>
116
+ <!-- Mobile Menu Button (Placeholder) -->
117
+ <button class="md:hidden w-10 h-10 flex items-center justify-center" aria-label="Toggle Menu">
118
+ <i class="ri-menu-line text-gray-600 dark:text-gray-300 text-xl"></i>
119
+ </button>
120
+ </div>
121
+ </div>
122
+ </header>
123
+
124
+ <!-- Main Content Area -->
125
+ <main class="flex-grow container mx-auto px-4 py-8">
126
+
127
+ <!-- Page Header Section -->
128
+ <section class="mb-10 text-center border-b border-gray-200 dark:border-gray-700 pb-6">
129
+ <h1 class="text-3xl md:text-4xl font-bold text-gray-900 dark:text-white mb-2">Feedback</h1>
130
+ <p class="subtitle text-lg text-gray-700 dark:text-gray-300 mb-1">Help us improve by sharing your thoughts</p>
131
+ </section>
132
+
133
+ <!-- Feedback Form Section -->
134
+ <div class="card bg-white dark:bg-gray-800 p-6 md:p-8 rounded-lg shadow-lg border border-gray-100 dark:border-gray-700 max-w-2xl mx-auto">
135
+ <h2 class="text-2xl font-semibold text-gray-800 dark:text-white mb-6 flex items-center gap-3 border-b border-gray-200 dark:border-gray-600 pb-3">
136
+ <i class="ri-mail-send-line text-primary dark:text-blue-400 text-3xl"></i>
137
+ <span>Submit Your Feedback</span>
138
+ </h2>
139
+
140
+ <!-- Success Message Placeholder -->
141
+ <div id="feedbackSuccessMessage" class="hidden bg-emerald-100 border border-emerald-300 text-emerald-800 dark:bg-emerald-900 dark:bg-opacity-60 dark:border-emerald-700 dark:text-emerald-200 px-4 py-3 rounded-md text-sm mb-6" role="alert">
142
+ <p><strong class="font-medium">Thank you!</strong> Your feedback has been submitted successfully.</p>
143
+ </div>
144
+
145
+ <form id="feedbackForm" class="space-y-5">
146
+ <div>
147
+ <label for="name" class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">Name</label>
148
+ <input type="text" id="name" name="name" required
149
+ class="w-full px-4 py-2 border border-gray-300 rounded-button focus:outline-none focus:ring-2 focus:ring-primary/50 dark:bg-gray-700 dark:border-gray-600 dark:text-gray-200"
150
+ placeholder="Your Name">
151
+ </div>
152
+
153
+ <div>
154
+ <label for="email" class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">Email Address</label>
155
+ <input type="email" id="email" name="email" required
156
+ class="w-full px-4 py-2 border border-gray-300 rounded-button focus:outline-none focus:ring-2 focus:ring-primary/50 dark:bg-gray-700 dark:border-gray-600 dark:text-gray-200"
157
+ placeholder="you@example.com">
158
+ </div>
159
+
160
+ <div>
161
+ <label for="feedback" class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">Feedback / Comments</label>
162
+ <textarea id="feedback" name="feedback" rows="5" required
163
+ class="w-full px-4 py-2 border border-gray-300 rounded-button focus:outline-none focus:ring-2 focus:ring-primary/50 dark:bg-gray-700 dark:border-gray-600 dark:text-gray-200"
164
+ placeholder="Please share your experience, suggestions, or any issues you encountered..."></textarea>
165
+ </div>
166
+
167
+ <div class="pt-2">
168
+ <button type="submit"
169
+ class="w-full sm:w-auto bg-primary text-white px-6 py-2.5 rounded-button hover:bg-blue-700 transition-colors font-medium inline-flex items-center justify-center gap-2 disabled:opacity-50">
170
+ <i class="ri-send-plane-2-line"></i> Send Feedback
171
+ </button>
172
+ </div>
173
+ </form>
174
+ </div> <!-- End Feedback Card -->
175
+
176
+ </main>
177
+
178
+ <!-- Footer -->
179
+ <footer class="bg-gray-800 text-gray-400 py-8 mt-12">
180
+ <div class="container mx-auto px-4 text-center">
181
+ <div class="credits text-sm mb-4">
182
+ <p>Powered by <strong>D SAKSHI</strong> (MCA Final Year BIT Durg, Chhattisgarh) | © SlimShadow Org. All Rights Reserved.</p>
183
+ </div>
184
+ <div class="flex justify-center space-x-4">
185
+ <a href="#" class="hover:text-white" title="GitHub (Placeholder)"><i class="ri-github-fill"></i></a>
186
+ <a href="#" class="hover:text-white" title="LinkedIn (Placeholder)"><i class="ri-linkedin-box-fill"></i></a>
187
+ </div>
188
+ </div>
189
+ </footer>
190
+
191
+ </div> <!-- End #app-container -->
192
+
193
+ <script>
194
+ // --- THEME TOGGLE LOGIC ---
195
+ const themeToggle = document.getElementById('themeToggle');
196
+ const htmlElement = document.documentElement;
197
+
198
+ function applyTheme(isDark) {
199
+ if (isDark) {
200
+ htmlElement.classList.add('dark');
201
+ if (themeToggle) themeToggle.checked = true;
202
+ } else {
203
+ htmlElement.classList.remove('dark');
204
+ if (themeToggle) themeToggle.checked = false;
205
+ }
206
+ }
207
+ // Check localStorage on load
208
+ const prefersDark = localStorage.getItem('theme') === 'dark' ||
209
+ (localStorage.getItem('theme') === null && window.matchMedia('(prefers-color-scheme: dark)').matches);
210
+ applyTheme(prefersDark);
211
+
212
+ // Add listener to toggle button
213
+ if (themeToggle) {
214
+ themeToggle.addEventListener('change', (event) => {
215
+ const isDark = event.target.checked;
216
+ applyTheme(isDark);
217
+ localStorage.setItem('theme', isDark ? 'dark' : 'light');
218
+ });
219
+ }
220
+
221
+ // --- Logout Button Logic ---
222
+ const logoutButton = document.getElementById("logoutButton");
223
+ if (logoutButton) {
224
+ logoutButton.addEventListener("click", function() {
225
+ console.log("Logout clicked, redirecting...");
226
+ window.location.href = "index.html"; // Redirect to main app/login
227
+ });
228
+ } else {
229
+ console.warn("Logout button not found.");
230
+ }
231
+
232
+ // --- Feedback Form Logic ---
233
+ const feedbackForm = document.getElementById('feedbackForm');
234
+ const successMessage = document.getElementById('feedbackSuccessMessage');
235
+
236
+ if (feedbackForm) {
237
+ feedbackForm.addEventListener('submit', function(event) {
238
+ event.preventDefault(); // Prevent default form submission
239
+
240
+ // Basic validation (Tailwind uses :required for browser validation)
241
+ // You could add more JS validation here if needed
242
+
243
+ // Simulate submission (Replace with actual fetch/API call)
244
+ console.log('Feedback form submitted (simulation)');
245
+ const formData = new FormData(feedbackForm);
246
+ for (let [key, value] of formData.entries()) {
247
+ console.log(`${key}: ${value}`);
248
+ }
249
+
250
+ // Clear the form
251
+ feedbackForm.reset();
252
+
253
+ // Show success message
254
+ if (successMessage) {
255
+ successMessage.classList.remove('hidden');
256
+ // Optional: Hide the message after a few seconds
257
+ setTimeout(() => {
258
+ successMessage.classList.add('hidden');
259
+ }, 5000); // Hide after 5 seconds
260
+ }
261
+
262
+ alert('Thank you for your feedback! (Simulation)'); // Simple alert for now
263
+ });
264
+ } else {
265
+ console.warn("Feedback form not found.");
266
+ }
267
+
268
+ </script>
269
+
270
+ </body>
271
+ </html>
features.html ADDED
@@ -0,0 +1,247 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <!-- Start with lang="en" and no 'dark' class initially -->
3
+ <html lang="en" class="">
4
+
5
+ <head>
6
+ <meta charset="UTF-8">
7
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
8
+ <title>Vision and Discern - Key Features</title> <!-- Updated Title -->
9
+
10
+ <!-- Tailwind CSS via CDN -->
11
+ <script src="https://cdn.tailwindcss.com/3.4.1"></script>
12
+ <script>
13
+ tailwind.config = {
14
+ darkMode: 'class', // Enable class-based dark mode
15
+ theme: {
16
+ extend: {
17
+ colors: {
18
+ primary: '#4a90e2', // Match main app's primary color
19
+ secondary: '#f0f8ff', // Light blueish background for reference
20
+ },
21
+ borderRadius: {
22
+ 'none': '0px', 'sm': '4px', DEFAULT: '8px', 'md': '12px',
23
+ 'lg': '16px', 'xl': '20px', '2xl': '24px', '3xl': '32px',
24
+ 'full': '9999px',
25
+ 'button': '4px' // Match main app's button radius
26
+ }
27
+ }
28
+ }
29
+ }
30
+ </script>
31
+ <!-- Fonts -->
32
+ <link rel="preconnect" href="https://fonts.googleapis.com">
33
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
34
+ <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
35
+ <!-- Icons (Remixicon) -->
36
+ <link href="https://cdn.jsdelivr.net/npm/remixicon@4.2.0/fonts/remixicon.css" rel="stylesheet">
37
+
38
+ <!-- Custom Styles (Mainly for the switch) -->
39
+ <style>
40
+ body { font-family: 'Inter', sans-serif; }
41
+ /* Custom Switch Styles - Copied from main app */
42
+ .custom-switch { position: relative; display: inline-block; width: 50px; height: 24px; }
43
+ .custom-switch-input { opacity: 0; width: 0; height: 0; }
44
+ .custom-switch-slider { position: absolute; cursor: pointer; top: 0; left: 0; right: 0; bottom: 0; background-color: #ccc; transition: .4s; border-radius: 34px; }
45
+ .custom-switch-slider:before { position: absolute; content: ""; height: 18px; width: 18px; left: 3px; bottom: 3px; background-color: white; transition: .4s; border-radius: 50%; }
46
+ .custom-switch-input:checked + .custom-switch-slider { background-color: #4a90e2; } /* Your primary color */
47
+ .custom-switch-input:checked + .custom-switch-slider:before { transform: translateX(26px); }
48
+
49
+ /* Base dark mode styles - Copied from main app */
50
+ html.dark body { background-color: #111827; color: #d1d5db; }
51
+ html.dark header, html.dark footer { background-color: #1f2937; }
52
+ html.dark .card { background-color: #1f2937; border-color: #374151; }
53
+ html.dark h1, html.dark h2, html.dark h3, html.dark p, html.dark span, html.dark li, html.dark label, html.dark small, html.dark .subtitle, html.dark .info-text, html.dark .credits p, html.dark .attribution { color: #d1d5db; }
54
+ html.dark .text-gray-600 { color: #9ca3af; }
55
+ html.dark .text-gray-700 { color: #9ca3af; }
56
+ html.dark .text-gray-500 { color: #6b7280; }
57
+ html.dark .button-secondary { background-color: #4b5563; color: #d1d5db; }
58
+ html.dark .button-secondary:hover { background-color: #374151; }
59
+ html.dark .features-list li strong { color: #e5e7eb; } /* Slightly lighter strong tag in dark mode */
60
+ </style>
61
+ </head>
62
+
63
+ <body class="bg-gray-100 dark:bg-gray-900 min-h-screen flex flex-col text-gray-900 dark:text-gray-200">
64
+
65
+ <!-- Main Application Container -->
66
+ <div id="app-container" class="flex-grow flex flex-col">
67
+
68
+ <!-- Header -->
69
+ <header class="bg-white dark:bg-gray-800 shadow-sm sticky top-0 z-50">
70
+ <div class="container mx-auto px-4 py-3 flex items-center justify-between">
71
+ <!-- Left Side: Logo & Nav -->
72
+ <div class="flex items-center">
73
+ <a href="home.html" class="text-xl font-bold text-primary dark:text-blue-400 mr-6">Vision & Discern</a>
74
+ <nav class="hidden md:flex space-x-6">
75
+ <a href="home.html" class="text-gray-600 dark:text-gray-300 hover:text-primary dark:hover:text-blue-400">Home</a>
76
+ <a href="index.html" class="text-gray-600 dark:text-gray-300 hover:text-primary dark:hover:text-blue-400">OCR</a>
77
+ <!-- Links to other tools (optional, consider if they are separate apps or tabs) -->
78
+ <!-- <a href="translation.html" target="_blank" class="text-gray-600 dark:text-gray-300 hover:text-primary dark:hover:text-blue-400">Translation</a> -->
79
+ <!-- <a href="gender_predictor.html" target="_blank" class="text-gray-600 dark:text-gray-300 hover:text-primary dark:hover:text-blue-400">Gender</a> -->
80
+ <a href="features.html" class="text-primary dark:text-blue-400 font-medium">Features</a>
81
+ <a href="feedback.html" class="text-gray-600 dark:text-gray-300 hover:text-primary dark:hover:text-blue-400">Feedback</a>
82
+ <a href="contact.html" class="text-gray-600 dark:text-gray-300 hover:text-primary dark:hover:text-blue-400">Contact Us</a>
83
+ <!-- <a href="admin.html" target="_blank" class="text-gray-600 dark:text-gray-300 hover:text-primary dark:hover:text-blue-400">Admin</a> -->
84
+ </nav>
85
+ </div>
86
+
87
+ <!-- Right Side: Switches & Logout -->
88
+ <div class="flex items-center space-x-4">
89
+ <!-- Theme Switch -->
90
+ <div class="items-center space-x-2 hidden md:flex">
91
+ <span class="text-sm text-gray-600 dark:text-gray-400"><i class="ri-sun-line"></i></span>
92
+ <label class="custom-switch">
93
+ <input type="checkbox" id="themeToggle" class="custom-switch-input">
94
+ <span class="custom-switch-slider"></span>
95
+ </label>
96
+ <span class="text-sm text-gray-600 dark:text-gray-400"><i class="ri-moon-line"></i></span>
97
+ </div>
98
+ <!-- Logout Button -->
99
+ <button id="logoutButton" class="button-secondary bg-red-600 hover:bg-red-700 dark:bg-red-700 dark:hover:bg-red-800 text-white px-3 py-1.5 rounded-button text-sm inline-flex items-center gap-1">
100
+ <i class="ri-logout-box-r-line"></i>
101
+ Logout
102
+ </button>
103
+ <!-- Mobile Menu Button (Placeholder) -->
104
+ <button class="md:hidden w-10 h-10 flex items-center justify-center" aria-label="Toggle Menu">
105
+ <i class="ri-menu-line text-gray-600 dark:text-gray-300 text-xl"></i>
106
+ </button>
107
+ </div>
108
+ </div>
109
+ </header>
110
+
111
+ <!-- Main Content Area -->
112
+ <main class="flex-grow container mx-auto px-4 py-8">
113
+
114
+ <!-- Page Header Section -->
115
+ <section class="mb-10 text-center border-b border-gray-200 dark:border-gray-700 pb-6">
116
+ <h1 class="text-3xl md:text-4xl font-bold text-gray-900 dark:text-white mb-2">Application Features</h1>
117
+ <p class="subtitle text-lg text-gray-700 dark:text-gray-300 mb-1">Explore the capabilities of our AI-powered tools</p>
118
+ <p class="attribution text-sm text-gray-500 dark:text-gray-400">Powered by Sakshi's Engines</p>
119
+ </section>
120
+
121
+ <!-- Features Section -->
122
+ <div class="card bg-white dark:bg-gray-800 p-6 md:p-8 rounded-lg shadow-lg border border-gray-100 dark:border-gray-700 max-w-3xl mx-auto">
123
+ <h2 class="text-2xl font-semibold text-gray-800 dark:text-white mb-6 flex items-center gap-3 border-b border-gray-200 dark:border-gray-600 pb-3">
124
+ <i class="ri-star-line text-primary dark:text-blue-400 text-3xl"></i>
125
+ <span>Key Features</span>
126
+ </h2>
127
+
128
+ <!-- Using Tailwind for the list -->
129
+ <ul class="features-list list-none space-y-4 text-gray-700 dark:text-gray-300">
130
+ <li class="flex items-start gap-3">
131
+ <i class="ri-text-scan-line text-lg mt-1 text-primary dark:text-blue-400"></i>
132
+ <div>
133
+ <strong class="font-semibold text-gray-800 dark:text-gray-100">Accurate Hindi OCR:</strong>
134
+ <span>Extracts Hindi text from images with high precision using advanced deep learning models.</span>
135
+ </div>
136
+ </li>
137
+ <li class="flex items-start gap-3">
138
+ <i class="ri-focus-3-line text-lg mt-1 text-primary dark:text-blue-400"></i>
139
+ <div>
140
+ <strong class="font-semibold text-gray-800 dark:text-gray-100">Word Detection:</strong>
141
+ <span>Identifies and visually highlights individual word boundaries within the uploaded image.</span>
142
+ </div>
143
+ </li>
144
+ <li class="flex items-start gap-3">
145
+ <i class="ri-markup-line text-lg mt-1 text-primary dark:text-blue-400"></i>
146
+ <div>
147
+ <strong class="font-semibold text-gray-800 dark:text-gray-100">Text Prediction:</strong>
148
+ <span>Provides the most likely textual representation of the detected handwritten words.</span>
149
+ </div>
150
+ </li>
151
+ <li class="flex items-start gap-3">
152
+ <i class="ri-translate-2 text-lg mt-1 text-primary dark:text-blue-400"></i>
153
+ <div>
154
+ <strong class="font-semibold text-gray-800 dark:text-gray-100">Multi-Language Translation:</strong>
155
+ <span>Translates the extracted text (or any input text) between numerous languages.</span>
156
+ </div>
157
+ </li>
158
+ <li class="flex items-start gap-3">
159
+ <i class="ri-men-line text-lg mt-1 text-primary dark:text-blue-400"></i> <i class="ri-women-line text-lg mt-1 text-primary dark:text-blue-400 -ml-2"></i>
160
+ <div class="ml-1">
161
+ <strong class="font-semibold text-gray-800 dark:text-gray-100">Gender Prediction:</strong>
162
+ <span>Predicts the likely gender associated with given names based on statistical models.</span>
163
+ </div>
164
+ </li>
165
+ <li class="flex items-start gap-3">
166
+ <i class="ri-drag-move-2-line text-lg mt-1 text-primary dark:text-blue-400"></i>
167
+ <div>
168
+ <strong class="font-semibold text-gray-800 dark:text-gray-100">User-Friendly Interface:</strong>
169
+ <span>Simple, intuitive, and responsive design built with Tailwind CSS for ease of use across devices. Drag & drop supported for uploads.</span>
170
+ </div>
171
+ </li>
172
+ <li class="flex items-start gap-3">
173
+ <i class="ri-lock-password-line text-lg mt-1 text-primary dark:text-blue-400"></i>
174
+ <div>
175
+ <strong class="font-semibold text-gray-800 dark:text-gray-100">Secure Authentication:</strong>
176
+ <span>Protects access with JWT-based login and signup functionality, ensuring only registered users can use the core features.</span>
177
+ </div>
178
+ </li>
179
+ <li class="flex items-start gap-3">
180
+ <i class="ri-moon-foggy-line text-lg mt-1 text-primary dark:text-blue-400"></i>
181
+ <div>
182
+ <strong class="font-semibold text-gray-800 dark:text-gray-100">Dark Mode Support:</strong>
183
+ <span>Includes a theme toggle for comfortable viewing in different lighting conditions.</span>
184
+ </div>
185
+ </li>
186
+ </ul>
187
+ </div> <!-- End Features Card -->
188
+
189
+ </main>
190
+
191
+ <!-- Footer -->
192
+ <footer class="bg-gray-800 text-gray-400 py-8 mt-12">
193
+ <div class="container mx-auto px-4 text-center">
194
+ <div class="credits text-sm mb-4">
195
+ <p>Powered by <strong>D SAKSHI</strong> (MCA Final Year BIT Durg, Chhattisgarh) | © SlimShadow Org. All Rights Reserved.</p>
196
+ </div>
197
+ <div class="flex justify-center space-x-4">
198
+ <a href="#" class="hover:text-white" title="GitHub (Placeholder)"><i class="ri-github-fill"></i></a>
199
+ <a href="#" class="hover:text-white" title="LinkedIn (Placeholder)"><i class="ri-linkedin-box-fill"></i></a>
200
+ </div>
201
+ </div>
202
+ </footer>
203
+
204
+ </div> <!-- End #app-container -->
205
+
206
+ <script>
207
+ // --- THEME TOGGLE LOGIC ---
208
+ const themeToggle = document.getElementById('themeToggle');
209
+ const htmlElement = document.documentElement;
210
+
211
+ function applyTheme(isDark) {
212
+ if (isDark) {
213
+ htmlElement.classList.add('dark');
214
+ if (themeToggle) themeToggle.checked = true;
215
+ } else {
216
+ htmlElement.classList.remove('dark');
217
+ if (themeToggle) themeToggle.checked = false;
218
+ }
219
+ }
220
+ // Check localStorage on load
221
+ const prefersDark = localStorage.getItem('theme') === 'dark' ||
222
+ (localStorage.getItem('theme') === null && window.matchMedia('(prefers-color-scheme: dark)').matches);
223
+ applyTheme(prefersDark);
224
+
225
+ // Add listener to toggle button
226
+ if (themeToggle) {
227
+ themeToggle.addEventListener('change', (event) => {
228
+ const isDark = event.target.checked;
229
+ applyTheme(isDark);
230
+ localStorage.setItem('theme', isDark ? 'dark' : 'light');
231
+ });
232
+ }
233
+
234
+ // --- Logout Button Logic ---
235
+ const logoutButton = document.getElementById("logoutButton");
236
+ if (logoutButton) {
237
+ logoutButton.addEventListener("click", function() {
238
+ console.log("Logout clicked, redirecting...");
239
+ window.location.href = "index.html"; // Redirect to main app/login
240
+ });
241
+ } else {
242
+ console.warn("Logout button not found.");
243
+ }
244
+ </script>
245
+
246
+ </body>
247
+ </html>
feedback.html ADDED
@@ -0,0 +1,303 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <!-- Start with lang="en" and no 'dark' class initially -->
3
+ <html lang="en" class="">
4
+
5
+ <head>
6
+ <meta charset="UTF-8">
7
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
8
+ <title>Vision and Discern - Feedback</title> <!-- Updated Title -->
9
+
10
+ <!-- Tailwind CSS via CDN -->
11
+ <script src="https://cdn.tailwindcss.com/3.4.1"></script>
12
+ <script>
13
+ tailwind.config = {
14
+ darkMode: 'class', // Enable class-based dark mode
15
+ theme: {
16
+ extend: {
17
+ colors: {
18
+ primary: '#4a90e2', // Match main app's primary color
19
+ secondary: '#f8f9fa', // Using light gray for backgrounds now
20
+ success: '#10b981', // emerald-500
21
+ danger: '#ef4444', // red-500
22
+ },
23
+ borderRadius: {
24
+ 'none': '0px', 'sm': '4px', DEFAULT: '8px', 'md': '12px',
25
+ 'lg': '16px', 'xl': '20px', '2xl': '24px', '3xl': '32px',
26
+ 'full': '9999px',
27
+ 'button': '8px' // Using slightly rounder buttons like NIC project
28
+ // 'button': '4px' // Or keep the previous style
29
+ }
30
+ }
31
+ }
32
+ }
33
+ </script>
34
+ <!-- Fonts -->
35
+ <link rel="preconnect" href="https://fonts.googleapis.com">
36
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
37
+ <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
38
+ <!-- Icons (Remixicon) -->
39
+ <link href="https://cdn.jsdelivr.net/npm/remixicon@4.2.0/fonts/remixicon.css" rel="stylesheet">
40
+
41
+ <!-- Custom Styles (Mainly for the switch) -->
42
+ <style>
43
+ body { font-family: 'Inter', sans-serif; }
44
+ /* Custom Switch Styles - Copied from main app */
45
+ .custom-switch { position: relative; display: inline-block; width: 50px; height: 24px; }
46
+ .custom-switch-input { opacity: 0; width: 0; height: 0; }
47
+ .custom-switch-slider { position: absolute; cursor: pointer; top: 0; left: 0; right: 0; bottom: 0; background-color: #ccc; transition: .4s; border-radius: 34px; }
48
+ .custom-switch-slider:before { position: absolute; content: ""; height: 18px; width: 18px; left: 3px; bottom: 3px; background-color: white; transition: .4s; border-radius: 50%; }
49
+ .custom-switch-input:checked + .custom-switch-slider { background-color: #4a90e2; } /* Your primary color */
50
+ .custom-switch-input:checked + .custom-switch-slider:before { transform: translateX(26px); }
51
+
52
+ /* Base dark mode styles - Copied from main app */
53
+ html.dark body { background-color: #111827; color: #d1d5db; }
54
+ html.dark header, html.dark footer { background-color: #1f2937; }
55
+ html.dark .card { background-color: #1f2937; border-color: #374151; }
56
+ html.dark h1, html.dark h2, html.dark h3, html.dark p, html.dark span, html.dark li, html.dark label, html.dark small, html.dark .subtitle, html.dark .info-text, html.dark .credits p, html.dark .attribution { color: #d1d5db; }
57
+ html.dark .text-gray-600 { color: #9ca3af; }
58
+ html.dark .text-gray-700 { color: #9ca3af; }
59
+ html.dark .text-gray-500 { color: #6b7280; }
60
+ /* Input/Textarea dark styles */
61
+ html.dark input[type="text"],
62
+ html.dark input[type="email"],
63
+ html.dark textarea {
64
+ background-color: #374151; /* gray-700 */
65
+ border-color: #4b5563; /* gray-600 */
66
+ color: #d1d5db; /* gray-300 */
67
+ }
68
+ html.dark input::placeholder,
69
+ html.dark textarea::placeholder {
70
+ color: #9ca3af; /* gray-400 */
71
+ }
72
+ html.dark .button-secondary { background-color: #4b5563; color: #d1d5db; }
73
+ html.dark .button-secondary:hover { background-color: #374151; }
74
+ /* Feedback Message Dark Mode */
75
+ html.dark .feedback-message-success { background-color: #065f46; color: #a7f3d0; } /* emerald */
76
+ html.dark .feedback-message-error { background-color: #991b1b; color: #fecaca; } /* red */
77
+
78
+ </style>
79
+ </head>
80
+
81
+ <body class="bg-gray-50 dark:bg-gray-900 min-h-screen flex flex-col text-gray-900 dark:text-gray-200">
82
+
83
+ <!-- Main Application Container -->
84
+ <div id="app-container" class="flex-grow flex flex-col">
85
+
86
+ <!-- Header -->
87
+ <header class="bg-white dark:bg-gray-800 shadow-sm sticky top-0 z-50">
88
+ <div class="container mx-auto px-4 py-3 flex items-center justify-between">
89
+ <!-- Left Side: Logo & Nav -->
90
+ <div class="flex items-center">
91
+ <a href="home.html" class="text-xl font-bold text-primary dark:text-blue-400 mr-6">Vision & Discern</a>
92
+ <nav class="hidden md:flex space-x-6">
93
+ <a href="home.html" class="text-gray-600 dark:text-gray-300 hover:text-primary dark:hover:text-blue-400">Home</a>
94
+ <a href="index.html" class="text-gray-600 dark:text-gray-300 hover:text-primary dark:hover:text-blue-400">OCR</a>
95
+ <!-- Optional Links, remove if not separate pages -->
96
+ <!-- <a href="translation.html" target="_blank" class="text-gray-600 dark:text-gray-300 hover:text-primary dark:hover:text-blue-400">Translation</a> -->
97
+ <!-- <a href="gender_predictor.html" target="_blank" class="text-gray-600 dark:text-gray-300 hover:text-primary dark:hover:text-blue-400">Gender</a> -->
98
+ <a href="features.html" class="text-gray-600 dark:text-gray-300 hover:text-primary dark:hover:text-blue-400">Features</a>
99
+ <a href="feedback.html" class="text-primary dark:text-blue-400 font-medium">Feedback</a>
100
+ <a href="contact.html" class="text-gray-600 dark:text-gray-300 hover:text-primary dark:hover:text-blue-400">Contact Us</a>
101
+ <!-- <a href="admin.html" target="_blank" class="text-gray-600 dark:text-gray-300 hover:text-primary dark:hover:text-blue-400">Admin</a> -->
102
+ </nav>
103
+ </div>
104
+
105
+ <!-- Right Side: Switches & Logout -->
106
+ <div class="flex items-center space-x-4">
107
+ <!-- Theme Switch -->
108
+ <div class="items-center space-x-2 hidden md:flex">
109
+ <span class="text-sm text-gray-600 dark:text-gray-400"><i class="ri-sun-line"></i></span>
110
+ <label class="custom-switch">
111
+ <input type="checkbox" id="themeToggle" class="custom-switch-input">
112
+ <span class="custom-switch-slider"></span>
113
+ </label>
114
+ <span class="text-sm text-gray-600 dark:text-gray-400"><i class="ri-moon-line"></i></span>
115
+ </div>
116
+ <!-- Logout Button -->
117
+ <button id="logoutButton" class="button-secondary bg-red-600 hover:bg-red-700 dark:bg-red-700 dark:hover:bg-red-800 text-white px-3 py-1.5 rounded-button text-sm inline-flex items-center gap-1">
118
+ <i class="ri-logout-box-r-line"></i>
119
+ Logout
120
+ </button>
121
+ <!-- Mobile Menu Button (Placeholder) -->
122
+ <button class="md:hidden w-10 h-10 flex items-center justify-center" aria-label="Toggle Menu">
123
+ <i class="ri-menu-line text-gray-600 dark:text-gray-300 text-xl"></i>
124
+ </button>
125
+ </div>
126
+ </div>
127
+ </header>
128
+
129
+ <!-- Main Content Area -->
130
+ <main class="flex-grow container mx-auto px-4 py-8">
131
+
132
+ <!-- Page Header Section -->
133
+ <section class="mb-10 text-center border-b border-gray-200 dark:border-gray-700 pb-6">
134
+ <h1 class="text-3xl md:text-4xl font-bold text-gray-900 dark:text-white mb-2">Feedback</h1>
135
+ <p class="subtitle text-lg text-gray-700 dark:text-gray-300 mb-1">Share your feedback to help us improve</p>
136
+ <p class="attribution text-sm text-gray-500 dark:text-gray-400">Powered by Sakshi's Hindi OCR Engine</p>
137
+ </section>
138
+
139
+ <!-- Feedback Form Section -->
140
+ <div class="card bg-white dark:bg-gray-800 p-6 md:p-8 rounded-lg shadow-lg border border-gray-100 dark:border-gray-700 max-w-xl mx-auto" id="feedback">
141
+ <h2 class="text-2xl font-semibold text-gray-800 dark:text-white mb-4 flex items-center gap-3">
142
+ <i class="ri-chat-voice-line text-primary dark:text-blue-400"></i>
143
+ <span>Your Feedback</span>
144
+ </h2>
145
+ <!-- Removed feedback-form wrapper div, styling applied to card -->
146
+ <p class="text-gray-600 dark:text-gray-400 mb-6">We appreciate your input! Please let us know your thoughts, suggestions, or any issues you encountered with the application.</p>
147
+
148
+ <!-- Using a standard form element -->
149
+ <form id="feedbackFormActual" class="space-y-5">
150
+ <div>
151
+ <label for="feedbackText" class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1 sr-only">Feedback</label> <!-- Screen reader only label -->
152
+ <textarea id="feedbackText" name="comment" required rows="6"
153
+ class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-primary/50 dark:bg-gray-700 dark:border-gray-600 dark:text-gray-200"
154
+ placeholder="Enter your feedback here..."></textarea>
155
+ </div>
156
+ <!-- Feedback Message Placeholder - Styled with Tailwind -->
157
+ <p id="feedbackMessage" class="info-text text-sm font-medium p-3 rounded-md hidden"></p>
158
+
159
+ <div class="pt-1">
160
+ <button id="submitFeedback" type="submit"
161
+ class="w-full bg-primary text-white px-6 py-2.5 rounded-button hover:bg-blue-700 transition-colors font-medium inline-flex items-center justify-center gap-2 disabled:opacity-50">
162
+ <i class="ri-send-plane-2-line"></i> Submit Feedback
163
+ </button>
164
+ </div>
165
+ </form>
166
+ </div> <!-- End Feedback Card -->
167
+
168
+ </main>
169
+
170
+ <!-- Footer -->
171
+ <footer class="bg-gray-800 text-gray-400 py-8 mt-12">
172
+ <div class="container mx-auto px-4 text-center">
173
+ <div class="credits text-sm mb-4">
174
+ <p>Powered by <strong>D SAKSHI</strong> (MCA Final Year BIT Durg, Chhattisgarh) | © SlimShadow Org. All Rights Reserved.</p>
175
+ </div>
176
+ <div class="flex justify-center space-x-4">
177
+ <a href="#" class="hover:text-white" title="GitHub (Placeholder)"><i class="ri-github-fill"></i></a>
178
+ <a href="#" class="hover:text-white" title="LinkedIn (Placeholder)"><i class="ri-linkedin-box-fill"></i></a>
179
+ </div>
180
+ </div>
181
+ </footer>
182
+
183
+ </div> <!-- End #app-container -->
184
+
185
+ <script>
186
+ // --- API Base URL ---
187
+ const API_BASE_URL = 'https://sameernotes-ocr.hf.space';
188
+
189
+ // --- DOM Elements ---
190
+ const themeToggle = document.getElementById('themeToggle');
191
+ const htmlElement = document.documentElement;
192
+ const logoutButton = document.getElementById('logoutButton');
193
+ const feedbackForm = document.getElementById('feedbackFormActual'); // Get the form element
194
+ const submitFeedbackButton = document.getElementById('submitFeedback');
195
+ const feedbackText = document.getElementById('feedbackText');
196
+ const feedbackMessage = document.getElementById('feedbackMessage');
197
+
198
+ // --- THEME TOGGLE LOGIC ---
199
+ function applyTheme(isDark) {
200
+ if (isDark) {
201
+ htmlElement.classList.add('dark');
202
+ if (themeToggle) themeToggle.checked = true;
203
+ } else {
204
+ htmlElement.classList.remove('dark');
205
+ if (themeToggle) themeToggle.checked = false;
206
+ }
207
+ }
208
+ const prefersDark = localStorage.getItem('theme') === 'dark' || (localStorage.getItem('theme') === null && window.matchMedia('(prefers-color-scheme: dark)').matches);
209
+ applyTheme(prefersDark);
210
+ if (themeToggle) {
211
+ themeToggle.addEventListener('change', (event) => {
212
+ const isDark = event.target.checked;
213
+ applyTheme(isDark);
214
+ localStorage.setItem('theme', isDark ? 'dark' : 'light');
215
+ });
216
+ }
217
+
218
+ // --- Logout Button Logic ---
219
+ if (logoutButton) {
220
+ logoutButton.addEventListener("click", function() {
221
+ console.log("Logout clicked, redirecting...");
222
+ window.location.href = "index.html"; // Redirect to main app/login
223
+ });
224
+ }
225
+
226
+ // --- Feedback Submission Logic ---
227
+ function showFeedbackMessage(message, type = 'info') { // type can be 'info', 'success', 'error'
228
+ if (!feedbackMessage) return;
229
+
230
+ feedbackMessage.textContent = message;
231
+ // Remove previous color classes
232
+ feedbackMessage.classList.remove('hidden', 'feedback-message-success', 'bg-emerald-100', 'text-emerald-800', 'feedback-message-error', 'bg-red-100', 'text-red-800', 'dark:bg-emerald-900', 'dark:text-emerald-200', 'dark:bg-red-900', 'dark:text-red-200');
233
+
234
+ if (type === 'success') {
235
+ feedbackMessage.classList.add('feedback-message-success', 'bg-emerald-100', 'text-emerald-800', 'dark:bg-emerald-900', 'dark:text-emerald-200');
236
+ } else if (type === 'error') {
237
+ feedbackMessage.classList.add('feedback-message-error', 'bg-red-100', 'text-red-800', 'dark:bg-red-900', 'dark:text-red-200');
238
+ } else {
239
+ // Default info style (optional)
240
+ feedbackMessage.classList.add('text-gray-600', 'dark:text-gray-400');
241
+ }
242
+
243
+ feedbackMessage.classList.remove('hidden'); // Make it visible
244
+
245
+ // Auto-hide after 5 seconds
246
+ setTimeout(() => {
247
+ feedbackMessage.classList.add('hidden');
248
+ feedbackMessage.textContent = '';
249
+ // Clean up color classes after hiding
250
+ feedbackMessage.classList.remove('feedback-message-success', 'bg-emerald-100', 'text-emerald-800', 'feedback-message-error', 'bg-red-100', 'text-red-800', 'dark:bg-emerald-900', 'dark:text-emerald-200', 'dark:bg-red-900', 'dark:text-red-200', 'text-gray-600', 'dark:text-gray-400');
251
+ }, 5000);
252
+ }
253
+
254
+ if (feedbackForm) {
255
+ feedbackForm.addEventListener('submit', async (event) => { // Use the form's submit event
256
+ event.preventDefault(); // Prevent default form submission
257
+ submitFeedbackButton.disabled = true; // Disable button during submission
258
+
259
+ const comment = feedbackText.value.trim();
260
+
261
+ if (!comment) {
262
+ showFeedbackMessage('Please enter your feedback.', 'error');
263
+ submitFeedbackButton.disabled = false; // Re-enable button
264
+ return;
265
+ }
266
+
267
+ try {
268
+ // Assuming the API accepts anonymous feedback or you handle authentication differently
269
+ const response = await fetch(`${API_BASE_URL}/feedback/`, {
270
+ method: 'POST',
271
+ headers: {
272
+ 'Content-Type': 'application/json',
273
+ // If authentication is sometimes required, add Authorization header conditionally
274
+ // 'Authorization': `Bearer ${accessToken}` // Example if token needed
275
+ },
276
+ body: JSON.stringify({
277
+ username: "Anonymous", // Or get username if available
278
+ comment: comment
279
+ })
280
+ });
281
+
282
+ if (response.ok) {
283
+ showFeedbackMessage('Feedback submitted successfully! Thank you.', 'success');
284
+ feedbackText.value = ''; // Clear input
285
+ } else {
286
+ const errorData = await response.json().catch(() => ({ detail: 'Failed to submit feedback.' }));
287
+ showFeedbackMessage(`Error: ${errorData.detail || 'Failed to submit feedback.'}`, 'error');
288
+ }
289
+ } catch (error) {
290
+ showFeedbackMessage("Network error submitting feedback. Please try again.", 'error');
291
+ console.error("Feedback submission error:", error);
292
+ } finally {
293
+ submitFeedbackButton.disabled = false; // Re-enable button
294
+ }
295
+ });
296
+ } else {
297
+ console.warn("Feedback form element not found");
298
+ }
299
+
300
+ </script>
301
+
302
+ </body>
303
+ </html>
gender_predictor.html ADDED
@@ -0,0 +1,448 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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>Name Gender Predictor</title>
7
+ <style>
8
+ /* --- REUSABLE STYLES (from translation.html) --- */
9
+ :root {
10
+ --primary-color: #4a90e2; /* Consistent primary color */
11
+ --secondary-color: #f8f9fa;
12
+ --text-color: #2c3e50;
13
+ --border-radius: 12px;
14
+ --transition: all 0.3s ease;
15
+ }
16
+
17
+ * {
18
+ margin: 0;
19
+ padding: 0;
20
+ box-sizing: border-box;
21
+ }
22
+
23
+ body {
24
+ font-family: 'Segoe UI', system-ui, -apple-system, sans-serif; /* Consistent font */
25
+ line-height: 1.6;
26
+ color: var(--text-color);
27
+ background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%); /* Consistent background */
28
+ min-height: 100vh;
29
+ padding: 2rem;
30
+ }
31
+
32
+ .container {
33
+ max-width: 1000px;
34
+ margin: 0 auto;
35
+ background: white;
36
+ padding: 2rem;
37
+ border-radius: var(--border-radius);
38
+ box-shadow: 0 10px 30px rgba(0, 0, 0, 0.1);
39
+ }
40
+
41
+ h1 {
42
+ color: var(--primary-color);
43
+ text-align: center;
44
+ margin-bottom: 2rem;
45
+ font-size: 2.5rem;
46
+ font-weight: 700;
47
+ }
48
+
49
+ h2 {
50
+ color: var(--text-color);
51
+ margin-bottom: 1.5rem;
52
+ font-size: 1.8rem;
53
+ }
54
+
55
+ /* --- TAB STYLES --- */
56
+ .tab {
57
+ display: flex;
58
+ gap: 1rem;
59
+ margin-bottom: 2rem;
60
+ border: none;
61
+ background: none;
62
+ }
63
+
64
+ .tab button {
65
+ flex: 1;
66
+ padding: 1rem;
67
+ font-size: 1.1rem;
68
+ border: 2px solid var(--primary-color);
69
+ background: transparent;
70
+ color: var(--primary-color);
71
+ border-radius: var(--border-radius);
72
+ cursor: pointer;
73
+ transition: var(--transition);
74
+ }
75
+
76
+ .tab button:hover {
77
+ background: rgba(74, 144, 226, 0.1);
78
+ }
79
+
80
+ .tab button.active {
81
+ background: var(--primary-color);
82
+ color: white;
83
+ }
84
+
85
+ .tabcontent {
86
+ display: none; /* Initially hidden */
87
+ padding: 2rem;
88
+ background: var(--secondary-color);
89
+ border-radius: var(--border-radius);
90
+ margin-bottom: 2rem;
91
+ }
92
+ /* --- END TAB STYLES --- */
93
+
94
+ label {
95
+ display: block;
96
+ margin-bottom: 0.5rem;
97
+ font-weight: 600;
98
+ color: var(--text-color);
99
+ }
100
+
101
+ textarea, input[type="text"] {
102
+ width: 100%;
103
+ padding: 1rem;
104
+ margin-bottom: 1.5rem;
105
+ border: 2px solid #e1e8ed;
106
+ border-radius: var(--border-radius);
107
+ font-size: 1rem;
108
+ transition: var(--transition);
109
+ background: white;
110
+ }
111
+
112
+ textarea {
113
+ min-height: 150px;
114
+ resize: vertical;
115
+ }
116
+
117
+ textarea:focus, input[type="text"]:focus {
118
+ outline: none;
119
+ border-color: var(--primary-color);
120
+ box-shadow: 0 0 0 3px rgba(74, 144, 226, 0.1);
121
+ }
122
+
123
+ button {
124
+ background: var(--primary-color);
125
+ color: white;
126
+ padding: 1rem 2rem;
127
+ border: none;
128
+ border-radius: var(--border-radius);
129
+ font-size: 1.1rem;
130
+ cursor: pointer;
131
+ transition: var(--transition);
132
+ width: 100%;
133
+ margin-top: 1rem;
134
+ }
135
+
136
+ button:hover {
137
+ transform: translateY(-2px);
138
+ box-shadow: 0 5px 15px rgba(74, 144, 226, 0.3);
139
+ }
140
+
141
+ .loading-indicator {
142
+ display: none;
143
+ text-align: center;
144
+ color: var(--primary-color);
145
+ margin: 1rem 0;
146
+ font-weight: 600;
147
+ }
148
+
149
+ /* --- RESULT STYLES (from translation.html)--- */
150
+ #results {
151
+ background: white;
152
+ padding: 1.5rem;
153
+ border-radius: var(--border-radius);
154
+ margin-top: 2rem;
155
+ box-shadow: 0 5px 15px rgba(0, 0, 0, 0.05);
156
+ }
157
+
158
+ .result-header {
159
+ color: var(--primary-color);
160
+ margin-bottom: 1rem;
161
+ font-size: 1.3rem;
162
+ }
163
+
164
+ .result-item {
165
+ margin-bottom: 1rem;
166
+ padding-bottom: 1rem;
167
+ border-bottom: 1px solid #e1e8ed;
168
+ }
169
+
170
+ .result-item:last-child {
171
+ border-bottom: none;
172
+ margin-bottom: 0;
173
+ padding-bottom: 0;
174
+ }
175
+ .result-label {
176
+ font-weight: 600;
177
+ margin-bottom: 0.5rem;
178
+ }
179
+
180
+ /* --- ERROR MESSAGE --- */
181
+ #error-message {
182
+ background: #fee2e2;
183
+ color: #dc2626;
184
+ padding: 1rem;
185
+ border-radius: var(--border-radius);
186
+ margin-top: 1rem;
187
+ display: none;
188
+ }
189
+
190
+ /* --- RESPONSIVE --- */
191
+ @media (max-width: 768px) {
192
+ body {
193
+ padding: 1rem;
194
+ }
195
+
196
+ .container {
197
+ padding: 1rem;
198
+ }
199
+
200
+ .tab button {
201
+ padding: 0.8rem;
202
+ font-size: 1rem;
203
+ }
204
+
205
+ .tabcontent {
206
+ padding: 1rem;
207
+ }
208
+ }
209
+ .credits {
210
+ text-align: center;
211
+ margin-top: 2rem;
212
+ color: var(--text-color);
213
+ font-size: 0.875rem;
214
+ }
215
+
216
+ </style>
217
+ </head>
218
+ <body>
219
+ <div class="container">
220
+ <h1>Name Gender Predictor</h1>
221
+
222
+ <div class="tab">
223
+ <button class="tablinks active" onclick="openTab(event, 'single')">Single Name</button>
224
+ <button class="tablinks" onclick="openTab(event, 'batch')">Batch Names</button>
225
+ </div>
226
+
227
+ <!-- Single Name Prediction Tab -->
228
+ <div id="single" class="tabcontent" style="display: block;">
229
+ <h2>Single Name Prediction</h2>
230
+ <label for="singleName">Enter a single name:</label>
231
+ <input type="text" id="singleName" name="singleName" placeholder="e.g., Sakshi">
232
+ <button id="predictSingleButton">Predict Gender</button>
233
+
234
+ <div class="loading-indicator" id="singleLoading">
235
+ Analyzing name...
236
+ </div>
237
+
238
+ <div id="singleResult" class = "results">
239
+ <h3 class="result-header">Prediction Results</h3>
240
+ </div>
241
+ </div>
242
+
243
+ <!-- Batch Name Prediction Tab -->
244
+ <div id="batch" class="tabcontent">
245
+ <h2>Batch Name Prediction</h2>
246
+ <label for="batchNames">Enter names (comma-separated):</label>
247
+ <textarea id="batchNames" name="batchNames" rows="4" placeholder="e.g., Sakshi, Sameer, Shweta"></textarea>
248
+ <button id="predictBatchButton">Predict Multiple Names</button>
249
+
250
+ <div class="loading-indicator" id="batchLoading">
251
+ Analyzing names...
252
+ </div>
253
+
254
+ <div id="batchResults" class = "results">
255
+ <h3 class="result-header">Prediction Results</h3>
256
+ </div>
257
+ </div>
258
+ <div id="error-message"></div>
259
+ <div class="credits">
260
+ <p>Powered by <strong>D SAKSHI</strong> (MCA Final Year BIT Durg, Chhattisgarh)</p>
261
+ <p>© SlimShadow Org. All Rights Reserved.</p>
262
+ </div>
263
+ </div>
264
+
265
+ <script>
266
+ const API_BASE_URL = "https://sameernotes-gender-prediction-space.hf.space";
267
+
268
+ // --- ELEMENTS ---
269
+ const singleNameInput = document.getElementById('singleName');
270
+ const predictSingleButton = document.getElementById('predictSingleButton');
271
+ const singleResultDiv = document.getElementById('singleResult');
272
+ const batchNamesInput = document.getElementById('batchNames');
273
+ const predictBatchButton = document.getElementById('predictBatchButton');
274
+ const batchResultsDiv = document.getElementById('batchResults');
275
+ const errorMessageDiv = document.getElementById('error-message');
276
+ const singleLoadingDiv = document.getElementById('singleLoading'); // Separate loading indicators
277
+ const batchLoadingDiv = document.getElementById('batchLoading');
278
+
279
+ // --- TAB FUNCTIONALITY ---
280
+ function openTab(evt, tabName) {
281
+ const tabcontent = document.getElementsByClassName("tabcontent");
282
+ for (let i = 0; i < tabcontent.length; i++) {
283
+ tabcontent[i].style.display = "none";
284
+ }
285
+
286
+ const tablinks = document.getElementsByClassName("tablinks");
287
+ for (let i = 0; i < tablinks.length; i++) {
288
+ tablinks[i].className = tablinks[i].className.replace(" active", "");
289
+ }
290
+
291
+ document.getElementById(tabName).style.display = "block";
292
+ evt.currentTarget.className += " active";
293
+
294
+ // Hide results on tab switch
295
+ singleResultDiv.innerHTML = '';
296
+ batchResultsDiv.innerHTML = '';
297
+
298
+ }
299
+
300
+
301
+ // --- SINGLE PREDICTION ---
302
+ predictSingleButton.addEventListener('click', async () => {
303
+ const name = singleNameInput.value.trim();
304
+
305
+ if (!name) {
306
+ showError("Please enter a name.");
307
+ return;
308
+ }
309
+
310
+ hideError();
311
+ showLoading(singleLoadingDiv); // Show single loading
312
+ singleResultDiv.innerHTML = ''; // Clear previous results
313
+
314
+ try {
315
+ const response = await fetch(`${API_BASE_URL}/predict_single?name=${encodeURIComponent(name)}&threshold=0.5`);
316
+
317
+ if (!response.ok) {
318
+ const errorData = await response.json();
319
+ showError(`Error: ${errorData.detail || 'An unexpected error occurred.'}`);
320
+ return;
321
+ }
322
+
323
+ const data = await response.json();
324
+ displaySingleResult(data);
325
+ } catch (error) {
326
+ showError("An error occurred during prediction. Please try again.");
327
+ console.error("Prediction error:", error);
328
+ } finally {
329
+ hideLoading(singleLoadingDiv); // Hide single loading
330
+ }
331
+ });
332
+
333
+
334
+
335
+ // --- BATCH PREDICTION ---
336
+ predictBatchButton.addEventListener('click', async () => {
337
+ const namesString = batchNamesInput.value.trim();
338
+ const names = namesString.split(',').map(name => name.trim()).filter(name => name !== "");
339
+
340
+ if (names.length === 0) {
341
+ showError("Please enter at least one valid name.");
342
+ return;
343
+ }
344
+
345
+ hideError();
346
+ showLoading(batchLoadingDiv); // Show batch loading
347
+ batchResultsDiv.innerHTML = ''; // Clear previous results.
348
+
349
+ try {
350
+ const response = await fetch(`${API_BASE_URL}/predict`, {
351
+ method: 'POST',
352
+ headers: {
353
+ 'Content-Type': 'application/json'
354
+ },
355
+ body: JSON.stringify({ names: names, threshold: 0.5 })
356
+ });
357
+
358
+ if (!response.ok) {
359
+ const errorData = await response.json();
360
+ showError(`Error: ${errorData.detail || 'An unexpected error occurred.'}`);
361
+ return;
362
+ }
363
+
364
+ const data = await response.json();
365
+ displayBatchResults(data);
366
+ } catch (error) {
367
+ showError("An error occurred during prediction. Please try again.");
368
+ console.error("Prediction error:", error);
369
+ } finally {
370
+ hideLoading(batchLoadingDiv); // Hide batch loading
371
+ }
372
+ });
373
+
374
+ // --- DISPLAY RESULTS (REFACTORED) ---
375
+ function displaySingleResult(data) {
376
+ const resultHTML = `
377
+ <div class="result-item">
378
+ <div class="result-label">Name:</div>
379
+ <div>${data.name}</div>
380
+ </div>
381
+ <div class="result-item">
382
+ <div class="result-label">Predicted Gender:</div>
383
+ <div>${data.predicted_gender}</div>
384
+ </div>
385
+ <div class="result-item">
386
+ <div class="result-label">Male Probability:</div>
387
+ <div> ${data.male_probability.toFixed(2)}</div>
388
+ </div>
389
+ <div class="result-item">
390
+ <div class="result-label">Confidence:</div>
391
+ <div>${data.confidence.toFixed(2)}</div>
392
+ </div>
393
+ `;
394
+ singleResultDiv.innerHTML = resultHTML;
395
+ }
396
+
397
+
398
+ function displayBatchResults(data) {
399
+ let resultsHTML = '';
400
+ for (const prediction of data.predictions) {
401
+ resultsHTML += `
402
+ <div class="result-item">
403
+ <div class="result-label">Name:</div>
404
+ <div>${prediction.name}</div>
405
+ </div>
406
+ <div class="result-item">
407
+ <div class="result-label">Predicted Gender:</div>
408
+ <div>${prediction.predicted_gender}</div>
409
+ </div>
410
+ <div class="result-item">
411
+ <div class="result-label">Male Probability:</div>
412
+ <div> ${prediction.male_probability.toFixed(2)}</div>
413
+ </div>
414
+ <div class="result-item">
415
+ <div class="result-label">Confidence:</div>
416
+ <div>${prediction.confidence.toFixed(2)}</div>
417
+ </div>
418
+ `;
419
+ }
420
+ batchResultsDiv.innerHTML = resultsHTML;
421
+ }
422
+
423
+
424
+
425
+ // --- UTILITY FUNCTIONS ---
426
+
427
+ function showError(message) {
428
+ errorMessageDiv.textContent = message;
429
+ errorMessageDiv.style.display = 'block';
430
+ setTimeout(() => {
431
+ errorMessageDiv.style.display = 'none';
432
+ }, 5000);
433
+ }
434
+
435
+ function hideError() {
436
+ errorMessageDiv.style.display = 'none';
437
+ }
438
+
439
+ function showLoading(loadingIndicator) {
440
+ loadingIndicator.style.display = 'block';
441
+ }
442
+
443
+ function hideLoading(loadingIndicator) {
444
+ loadingIndicator.style.display = 'none';
445
+ }
446
+ </script>
447
+ </body>
448
+ </html>
home.html ADDED
@@ -0,0 +1,247 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <!-- Start with lang="en" and no 'dark' class initially -->
3
+ <html lang="en" class="">
4
+
5
+ <head>
6
+ <meta charset="UTF-8">
7
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
8
+ <title>Vision and Discern - Home</title> <!-- Updated Title -->
9
+
10
+ <!-- Tailwind CSS via CDN -->
11
+ <script src="https://cdn.tailwindcss.com/3.4.1"></script>
12
+ <script>
13
+ tailwind.config = {
14
+ darkMode: 'class', // Enable class-based dark mode
15
+ theme: {
16
+ extend: {
17
+ colors: {
18
+ primary: '#4a90e2', // Match main app's primary color
19
+ secondary: '#f0f8ff',
20
+ },
21
+ borderRadius: {
22
+ 'none': '0px', 'sm': '4px', DEFAULT: '8px', 'md': '12px',
23
+ 'lg': '16px', 'xl': '20px', '2xl': '24px', '3xl': '32px',
24
+ 'full': '9999px',
25
+ 'button': '4px' // Match main app's button radius
26
+ }
27
+ }
28
+ }
29
+ }
30
+ </script>
31
+ <!-- Fonts -->
32
+ <link rel="preconnect" href="https://fonts.googleapis.com">
33
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
34
+ <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
35
+ <!-- Icons (Remixicon) -->
36
+ <link href="https://cdn.jsdelivr.net/npm/remixicon@4.2.0/fonts/remixicon.css" rel="stylesheet">
37
+
38
+ <!-- Custom Styles (Mainly for the switch) -->
39
+ <style>
40
+ body { font-family: 'Inter', sans-serif; }
41
+ /* Custom Switch Styles - Copied from main app */
42
+ .custom-switch { position: relative; display: inline-block; width: 50px; height: 24px; }
43
+ .custom-switch-input { opacity: 0; width: 0; height: 0; }
44
+ .custom-switch-slider { position: absolute; cursor: pointer; top: 0; left: 0; right: 0; bottom: 0; background-color: #ccc; transition: .4s; border-radius: 34px; }
45
+ .custom-switch-slider:before { position: absolute; content: ""; height: 18px; width: 18px; left: 3px; bottom: 3px; background-color: white; transition: .4s; border-radius: 50%; }
46
+ .custom-switch-input:checked + .custom-switch-slider { background-color: #4a90e2; } /* Your primary color */
47
+ .custom-switch-input:checked + .custom-switch-slider:before { transform: translateX(26px); }
48
+
49
+ /* Base dark mode styles - Copied from main app */
50
+ html.dark body { background-color: #111827; color: #d1d5db; }
51
+ html.dark header, html.dark footer { background-color: #1f2937; }
52
+ html.dark .card { background-color: #1f2937; border-color: #374151; }
53
+ html.dark h1, html.dark h2, html.dark h3, html.dark p, html.dark span, html.dark li, html.dark label, html.dark small, html.dark .subtitle, html.dark .info-text, html.dark .credits p, html.dark .attribution { color: #d1d5db; }
54
+ html.dark .text-gray-600 { color: #9ca3af; }
55
+ html.dark .text-gray-700 { color: #9ca3af; }
56
+ html.dark .text-gray-500 { color: #6b7280; }
57
+ html.dark .button-secondary { background-color: #4b5563; color: #d1d5db; }
58
+ html.dark .button-secondary:hover { background-color: #374151; }
59
+ </style>
60
+ </head>
61
+
62
+ <body class="bg-gray-100 dark:bg-gray-900 min-h-screen flex flex-col text-gray-900 dark:text-gray-200">
63
+
64
+ <!-- Main Application Container -->
65
+ <div id="app-container" class="flex-grow flex flex-col">
66
+
67
+ <!-- Header -->
68
+ <header class="bg-white dark:bg-gray-800 shadow-sm sticky top-0 z-50">
69
+ <div class="container mx-auto px-4 py-3 flex items-center justify-between">
70
+ <!-- Left Side: Logo & Nav -->
71
+ <div class="flex items-center">
72
+ <a href="home.html" class="text-xl font-bold text-primary dark:text-blue-400 mr-6">Vision & Discern</a>
73
+ <nav class="hidden md:flex space-x-6">
74
+ <!-- Set 'active' class for the current page -->
75
+ <a href="home.html" class="text-primary dark:text-blue-400 font-medium">Home</a>
76
+ <a href="index.html" class="text-gray-600 dark:text-gray-300 hover:text-primary dark:hover:text-blue-400">OCR</a>
77
+ <a href="features.html" class="text-gray-600 dark:text-gray-300 hover:text-primary dark:hover:text-blue-400">Features</a>
78
+ <a href="feedback.html" class="text-gray-600 dark:text-gray-300 hover:text-primary dark:hover:text-blue-400">Feedback</a>
79
+ <a href="contact.html" class="text-gray-600 dark:text-gray-300 hover:text-primary dark:hover:text-blue-400">Contact Us</a>
80
+ </nav>
81
+ </div>
82
+
83
+ <!-- Right Side: Switches & Logout -->
84
+ <div class="flex items-center space-x-4">
85
+ <!-- Theme Switch -->
86
+ <div class="items-center space-x-2 hidden md:flex">
87
+ <span class="text-sm text-gray-600 dark:text-gray-400"><i class="ri-sun-line"></i></span>
88
+ <label class="custom-switch">
89
+ <input type="checkbox" id="themeToggle" class="custom-switch-input">
90
+ <span class="custom-switch-slider"></span>
91
+ </label>
92
+ <span class="text-sm text-gray-600 dark:text-gray-400"><i class="ri-moon-line"></i></span>
93
+ </div>
94
+ <!-- Logout Button -->
95
+ <button id="logoutButton" class="button-secondary bg-red-600 hover:bg-red-700 dark:bg-red-700 dark:hover:bg-red-800 text-white px-3 py-1.5 rounded-button text-sm inline-flex items-center gap-1">
96
+ <i class="ri-logout-box-r-line"></i>
97
+ Logout
98
+ </button>
99
+ <!-- Mobile Menu Button (Placeholder) -->
100
+ <button class="md:hidden w-10 h-10 flex items-center justify-center" aria-label="Toggle Menu">
101
+ <i class="ri-menu-line text-gray-600 dark:text-gray-300 text-xl"></i>
102
+ </button>
103
+ </div>
104
+ </div>
105
+ </header>
106
+
107
+ <!-- Main Content Area -->
108
+ <main class="flex-grow container mx-auto px-4 py-8">
109
+
110
+ <!-- Page Header Section -->
111
+ <section class="mb-10 text-center border-b border-gray-200 dark:border-gray-700 pb-6">
112
+ <h1 class="text-3xl md:text-4xl font-bold text-gray-900 dark:text-white mb-2">Hindi OCR Application</h1>
113
+ <p class="subtitle text-lg text-gray-700 dark:text-gray-300 mb-1">Convert Images to Hindi Text with Ease</p>
114
+ <p class="attribution text-sm text-gray-500 dark:text-gray-400">Powered by Sakshi's Hindi OCR Engine</p>
115
+ </section>
116
+
117
+ <!-- Main Content Grid -->
118
+ <div class="grid grid-cols-1 md:grid-cols-2 gap-8">
119
+
120
+ <!-- Welcome Section -->
121
+ <div class="card bg-white dark:bg-gray-800 p-6 rounded-lg shadow-md border border-gray-100 dark:border-gray-700">
122
+ <h2 class="text-2xl font-semibold text-gray-800 dark:text-white mb-4 flex items-center gap-2">
123
+ <i class="ri-sparkling-2-line text-primary dark:text-blue-400"></i>
124
+ Welcome!
125
+ </h2>
126
+ <p class="text-gray-700 dark:text-gray-300 mb-4">
127
+ Welcome to the Hindi OCR application. This tool allows you to easily extract text from images containing Hindi script.
128
+ </p>
129
+ <p class="text-gray-700 dark:text-gray-300">
130
+ Navigate to the <a href="index.html" class="text-primary dark:text-blue-400 hover:underline font-medium">OCR</a> page to start converting your images.
131
+ </p>
132
+ <div class="mt-6 flex justify-center">
133
+ <img src="placeholder.svg" alt="Illustration showing OCR process" class="h-40 w-auto text-gray-400 dark:text-gray-600 border border-dashed rounded p-4" />
134
+ <!-- Replace placeholder.svg with a relevant image or SVG if available -->
135
+ </div>
136
+ </div>
137
+
138
+ <!-- How to Use Section -->
139
+ <div class="card bg-white dark:bg-gray-800 p-6 rounded-lg shadow-md border border-gray-100 dark:border-gray-700">
140
+ <h2 class="text-2xl font-semibold text-gray-800 dark:text-white mb-4 flex items-center gap-2">
141
+ <i class="ri-question-line text-primary dark:text-blue-400"></i>
142
+ How to Use
143
+ </h2>
144
+ <ol class="list-decimal list-inside space-y-3 text-gray-700 dark:text-gray-300">
145
+ <li class="flex items-start gap-2">
146
+ <i class="ri-user-add-line mt-1 text-primary dark:text-blue-400"></i>
147
+ <span>Register or Login with your credentials.</span>
148
+ </li>
149
+ <li class="flex items-start gap-2">
150
+ <i class="ri-computer-line mt-1 text-primary dark:text-blue-400"></i>
151
+ <span>Navigate to the <a href="index.html" class="text-primary dark:text-blue-400 hover:underline font-medium">OCR tab</a>.</span>
152
+ </li>
153
+ <li class="flex items-start gap-2">
154
+ <i class="ri-upload-2-line mt-1 text-primary dark:text-blue-400"></i>
155
+ <span>Click the upload area or drag & drop an image containing Hindi text. You can also select a sample image.</span>
156
+ </li>
157
+ <li class="flex items-start gap-2">
158
+ <i class="ri-camera-lens-line mt-1 text-primary dark:text-blue-400"></i>
159
+ <span>Press the <strong>Process Image</strong> button.</span>
160
+ </li>
161
+ <li class="flex items-start gap-2">
162
+ <i class="ri-timer-line mt-1 text-primary dark:text-blue-400"></i>
163
+ <span>Wait briefly for the AI to analyze and extract the text.</span>
164
+ </li>
165
+ <li class="flex items-start gap-2">
166
+ <i class="ri-file-copy-line mt-1 text-primary dark:text-blue-400"></i>
167
+ <span>View and copy the extracted text from the results area.</span>
168
+ </li>
169
+ <li class="flex items-start gap-2">
170
+ <i class="ri-translate-2 mt-1 text-primary dark:text-blue-400"></i>
171
+ <span>Optionally, use the <strong>Translation</strong> tab to translate the extracted text.</span>
172
+ </li>
173
+ <li class="flex items-start gap-2">
174
+ <i class="ri-user-search-line mt-1 text-primary dark:text-blue-400"></i>
175
+ <span>Explore the <strong>Gender Prediction</strong> tab for name analysis.</span>
176
+ </li>
177
+ <li class="flex items-start gap-2">
178
+ <i class="ri-feedback-line mt-1 text-primary dark:text-blue-400"></i>
179
+ <span>Use the <a href="feedback.html" class="text-primary dark:text-blue-400 hover:underline font-medium">Feedback</a> link to share your thoughts.</span>
180
+ </li>
181
+ </ol>
182
+ </div>
183
+
184
+ </div> <!-- End Grid -->
185
+
186
+ </main>
187
+
188
+ <!-- Footer -->
189
+ <footer class="bg-gray-800 text-gray-400 py-8 mt-12">
190
+ <div class="container mx-auto px-4 text-center">
191
+ <div class="credits text-sm mb-4">
192
+ <p>Powered by <strong>D SAKSHI</strong> (MCA Final Year BIT Durg, Chhattisgarh) | © SlimShadow Org. All Rights Reserved.</p>
193
+ </div>
194
+ <div class="flex justify-center space-x-4">
195
+ <a href="#" class="hover:text-white" title="GitHub (Placeholder)"><i class="ri-github-fill"></i></a>
196
+ <a href="#" class="hover:text-white" title="LinkedIn (Placeholder)"><i class="ri-linkedin-box-fill"></i></a>
197
+ </div>
198
+ </div>
199
+ </footer>
200
+
201
+ </div> <!-- End #app-container -->
202
+
203
+ <script>
204
+ // --- THEME TOGGLE LOGIC ---
205
+ const themeToggle = document.getElementById('themeToggle');
206
+ const htmlElement = document.documentElement;
207
+
208
+ function applyTheme(isDark) {
209
+ if (isDark) {
210
+ htmlElement.classList.add('dark');
211
+ if (themeToggle) themeToggle.checked = true;
212
+ } else {
213
+ htmlElement.classList.remove('dark');
214
+ if (themeToggle) themeToggle.checked = false;
215
+ }
216
+ }
217
+ // Check localStorage on load
218
+ const prefersDark = localStorage.getItem('theme') === 'dark' ||
219
+ (localStorage.getItem('theme') === null && window.matchMedia('(prefers-color-scheme: dark)').matches);
220
+ applyTheme(prefersDark);
221
+
222
+ // Add listener to toggle button
223
+ if (themeToggle) {
224
+ themeToggle.addEventListener('change', (event) => {
225
+ const isDark = event.target.checked;
226
+ applyTheme(isDark);
227
+ localStorage.setItem('theme', isDark ? 'dark' : 'light');
228
+ });
229
+ }
230
+
231
+ // --- Logout Button Logic ---
232
+ const logoutButton = document.getElementById("logoutButton");
233
+ if (logoutButton) {
234
+ logoutButton.addEventListener("click", function() {
235
+ // TODO: Add actual logout logic here if needed (e.g., clear tokens)
236
+ console.log("Logout clicked, redirecting...");
237
+ // Redirect to index.html (which should show the login screen if not authenticated)
238
+ window.location.href = "index.html";
239
+ });
240
+ } else {
241
+ console.warn("Logout button not found.");
242
+ }
243
+
244
+ </script>
245
+
246
+ </body>
247
+ </html>
images/example-1.jpg ADDED
images//340/244/205/340/244/202/340/244/244/340/244/260/340/245/215/340/244/260/340/244/276/340/244/267/340/245/215/340/244/237/340/245/215/340/244/260/340/245/200/340/244/257.png ADDED
images//340/244/205/340/244/227/340/245/215/340/244/260/340/244/270/340/244/260.png ADDED
images//340/244/205/340/244/247/340/245/215/340/244/257/340/244/257/340/244/250.png ADDED
images//340/244/205/340/244/250/340/245/201/340/244/270/340/244/202/340/244/247/340/244/276/340/244/250.png ADDED
index.html CHANGED
@@ -1,19 +1,1114 @@
1
- <!doctype html>
2
- <html>
3
- <head>
4
- <meta charset="utf-8" />
5
- <meta name="viewport" content="width=device-width" />
6
- <title>My static Space</title>
7
- <link rel="stylesheet" href="style.css" />
8
- </head>
9
- <body>
10
- <div class="card">
11
- <h1>Welcome to your static Space!</h1>
12
- <p>You can modify this app directly by editing <i>index.html</i> in the Files and versions tab.</p>
13
- <p>
14
- Also don't forget to check the
15
- <a href="https://huggingface.co/docs/hub/spaces" target="_blank">Spaces documentation</a>.
16
- </p>
17
- </div>
18
- </body>
19
- </html>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <!-- Start with lang="en" and no 'dark' class initially -->
3
+ <html lang="en" class="">
4
+
5
+ <head>
6
+ <meta charset="UTF-8">
7
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
8
+ <title>Vision and Discern - AI Text Analysis</title>
9
+
10
+ <!-- Tailwind CSS via CDN -->
11
+ <script src="https://cdn.tailwindcss.com/3.4.1"></script>
12
+ <script>
13
+ tailwind.config = {
14
+ darkMode: 'class', // Enable class-based dark mode
15
+ theme: {
16
+ extend: {
17
+ colors: {
18
+ primary: '#4a90e2', // Adjusted primary to match old button color
19
+ // primary: '#1a73e8', // Or use the NIC project's primary
20
+ secondary: '#f0f8ff', // Light blueish background
21
+ // Define dark mode colors or rely on Tailwind's defaults
22
+ },
23
+ borderRadius: {
24
+ 'none': '0px', 'sm': '4px', DEFAULT: '8px', 'md': '12px',
25
+ 'lg': '16px', 'xl': '20px', '2xl': '24px', '3xl': '32px',
26
+ 'full': '9999px',
27
+ 'button': '4px' // Match old button radius
28
+ // 'button': '8px' // Or use the NIC project's button radius
29
+ }
30
+ }
31
+ }
32
+ }
33
+ </script>
34
+ <!-- Fonts -->
35
+ <link rel="preconnect" href="https://fonts.googleapis.com">
36
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
37
+ <!-- Using Inter like the NIC project, add Noto Sans Devanagari if needed for specific Hindi text -->
38
+ <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=Noto+Sans+Devanagari:wght@400;700&display=swap" rel="stylesheet">
39
+ <!-- Icons (Remixicon like the NIC project) -->
40
+ <link href="https://cdn.jsdelivr.net/npm/remixicon@4.2.0/fonts/remixicon.css" rel="stylesheet">
41
+
42
+ <!-- Custom Styles (Mainly for the switch, keep this) -->
43
+ <style>
44
+ body { font-family: 'Inter', sans-serif; }
45
+ .hindi-font { font-family: 'Noto Sans Devanagari', sans-serif; } /* Add if displaying Hindi */
46
+
47
+ /* Custom Switch Styles - Keep As Is from NIC Project */
48
+ .custom-switch { position: relative; display: inline-block; width: 50px; height: 24px; }
49
+ .custom-switch-input { opacity: 0; width: 0; height: 0; }
50
+ .custom-switch-slider { position: absolute; cursor: pointer; top: 0; left: 0; right: 0; bottom: 0; background-color: #ccc; transition: .4s; border-radius: 34px; }
51
+ .custom-switch-slider:before { position: absolute; content: ""; height: 18px; width: 18px; left: 3px; bottom: 3px; background-color: white; transition: .4s; border-radius: 50%; }
52
+ /* Use the primary color from Tailwind config if possible, or hardcode */
53
+ .custom-switch-input:checked + .custom-switch-slider { background-color: #4a90e2; } /* Your primary */
54
+ /* .custom-switch-input:checked + .custom-switch-slider { background-color: #1a73e8; } /* NIC primary */
55
+ .custom-switch-input:checked + .custom-switch-slider:before { transform: translateX(26px); }
56
+
57
+ /* Base dark mode styles adapted from NIC project */
58
+ html.dark body {
59
+ background-color: #111827; /* Tailwind gray-900 */
60
+ color: #d1d5db; /* Tailwind gray-300 */
61
+ }
62
+ html.dark header, html.dark footer {
63
+ background-color: #1f2937; /* Tailwind gray-800 */
64
+ }
65
+ html.dark .tab-container {
66
+ border-bottom-color: #4b5563; /* gray-600 */
67
+ }
68
+ html.dark .tab-button {
69
+ color: #9ca3af; /* gray-400 */
70
+ }
71
+ html.dark .tab-button.active {
72
+ color: #60a5fa; /* blue-400 */
73
+ border-bottom-color: #60a5fa; /* blue-400 */
74
+ }
75
+ html.dark .card,
76
+ html.dark .auth-card,
77
+ html.dark .results {
78
+ background-color: #1f2937; /* gray-800 */
79
+ border-color: #374151; /* gray-700 */
80
+ }
81
+ /* Adjust general text elements */
82
+ html.dark h1, html.dark h2, html.dark h3, html.dark p, html.dark span, html.dark li, html.dark label, html.dark small, html.dark .subtitle, html.dark .info-text, html.dark .credits p {
83
+ color: #d1d5db; /* gray-300 */
84
+ }
85
+ /* Adjust specific text colors */
86
+ html.dark .text-gray-600 { color: #9ca3af; /* gray-400 */ }
87
+ html.dark .text-gray-500 { color: #6b7280; /* gray-500 */ }
88
+ html.dark .text-gray-700 { color: #9ca3af; /* gray-400 */ }
89
+
90
+ /* Input/Textarea dark styles */
91
+ html.dark input[type="text"],
92
+ html.dark input[type="password"],
93
+ html.dark input[type="email"],
94
+ html.dark textarea {
95
+ background-color: #374151; /* gray-700 */
96
+ border-color: #4b5563; /* gray-600 */
97
+ color: #d1d5db; /* gray-300 */
98
+ }
99
+ html.dark input::placeholder,
100
+ html.dark textarea::placeholder {
101
+ color: #9ca3af; /* gray-400 */
102
+ }
103
+
104
+ /* Button dark styles */
105
+ html.dark .button-secondary {
106
+ background-color: #4b5563; /* gray-600 */
107
+ color: #d1d5db; /* gray-300 */
108
+ }
109
+ html.dark .button-secondary:hover {
110
+ background-color: #374151; /* gray-700 */
111
+ }
112
+
113
+ /* Error message dark styles */
114
+ html.dark .error-message {
115
+ background-color: #450a0a; /* red-950 */
116
+ color: #fecaca; /* red-200 */
117
+ border-color: #7f1d1d; /* red-800 */
118
+ }
119
+
120
+ /* Upload area dark */
121
+ html.dark .upload-area {
122
+ border-color: #4b5563; /* gray-600 */
123
+ color: #9ca3af; /* gray-400 */
124
+ }
125
+ html.dark .upload-area.dragover {
126
+ border-color: #60a5fa; /* blue-400 */
127
+ background-color: #1e3a8a; /* blue-900 */
128
+ }
129
+ /* Image borders dark */
130
+ html.dark #imagePreview,
131
+ html.dark .sample-image,
132
+ html.dark .result-image {
133
+ border-color: #4b5563; /* gray-600 */
134
+ }
135
+ /* Results dark */
136
+ html.dark .ocr-result {
137
+ background-color: #374151; /* gray-700 */
138
+ border-color: #4b5563; /* gray-600 */
139
+ }
140
+ html.dark .result-content {
141
+ background-color: #1f2937; /* gray-800 */
142
+ border-color: #4b5563; /* gray-600 */
143
+ }
144
+ html.dark .result-item {
145
+ border-bottom-color: #4b5563; /* gray-600 */
146
+ }
147
+
148
+
149
+ </style>
150
+ </head>
151
+
152
+ <!-- Add dark mode background/text for body base -->
153
+ <body class="bg-gray-100 dark:bg-gray-900 min-h-screen flex flex-col text-gray-900 dark:text-gray-200">
154
+
155
+ <!-- Login Section - Styled with Tailwind -->
156
+ <div id="login-container" class="min-h-screen flex items-center justify-center p-4">
157
+ <!-- Login Card -->
158
+ <div class="auth-card bg-white dark:bg-gray-800 p-6 md:p-8 rounded-lg shadow-lg text-center w-full max-w-sm border dark:border-gray-700">
159
+ <h1 class="text-2xl font-bold mb-2 text-primary dark:text-blue-400">Vision and Discern</h1>
160
+ <h2 class="text-xl font-semibold mb-3">Welcome!</h2>
161
+ <p class="text-gray-600 dark:text-gray-400 mb-6 text-sm">Login to access the applications.</p>
162
+ <div class="error-message bg-red-100 border border-red-300 text-red-800 dark:bg-red-900 dark:bg-opacity-50 dark:border-red-600 dark:text-red-200 px-4 py-2 rounded-md text-sm mb-4 hidden" id="loginErrorMessage"></div>
163
+ <input type="text" id="username" placeholder="Username" class="w-full px-4 py-2 mb-4 border border-gray-300 rounded-button focus:outline-none focus:ring-2 focus:ring-primary/50 dark:bg-gray-700 dark:border-gray-600 dark:text-gray-200">
164
+ <input type="password" id="password" placeholder="Password" class="w-full px-4 py-2 mb-4 border border-gray-300 rounded-button focus:outline-none focus:ring-2 focus:ring-primary/50 dark:bg-gray-700 dark:border-gray-600 dark:text-gray-200">
165
+ <button id="loginButton" class="w-full bg-primary text-white px-4 py-2 rounded-button hover:bg-blue-700 transition-colors font-medium disabled:opacity-50">Login</button>
166
+ <div class="mt-4 text-sm text-gray-600 dark:text-gray-400">
167
+ Don't have an account? <a href="#" id="signup-link" class="text-primary dark:text-blue-400 hover:underline">Sign up</a>
168
+ </div>
169
+ </div>
170
+
171
+ <!-- Signup Card -->
172
+ <div class="auth-card bg-white dark:bg-gray-800 p-6 md:p-8 rounded-lg shadow-lg text-center w-full max-w-sm border dark:border-gray-700 hidden" id="signup-card">
173
+ <h1 class="text-2xl font-bold mb-2 text-primary dark:text-blue-400">Vision and Discern</h1>
174
+ <h2 class="text-xl font-semibold mb-3">Create Account</h2>
175
+ <p class="text-gray-600 dark:text-gray-400 mb-6 text-sm">Sign up to start using applications.</p>
176
+ <div class="error-message bg-red-100 border border-red-300 text-red-800 dark:bg-red-900 dark:bg-opacity-50 dark:border-red-600 dark:text-red-200 px-4 py-2 rounded-md text-sm mb-4 hidden" id="signupErrorMessage"></div>
177
+ <input type="text" id="signupUsername" placeholder="Username" class="w-full px-4 py-2 mb-4 border border-gray-300 rounded-button focus:outline-none focus:ring-2 focus:ring-primary/50 dark:bg-gray-700 dark:border-gray-600 dark:text-gray-200">
178
+ <input type="email" id="signupEmail" placeholder="Email" class="w-full px-4 py-2 mb-4 border border-gray-300 rounded-button focus:outline-none focus:ring-2 focus:ring-primary/50 dark:bg-gray-700 dark:border-gray-600 dark:text-gray-200">
179
+ <input type="password" id="signupPassword" placeholder="Password" class="w-full px-4 py-2 mb-4 border border-gray-300 rounded-button focus:outline-none focus:ring-2 focus:ring-primary/50 dark:bg-gray-700 dark:border-gray-600 dark:text-gray-200">
180
+ <button id="signupButton" class="w-full bg-primary text-white px-4 py-2 rounded-button hover:bg-blue-700 transition-colors font-medium disabled:opacity-50">Sign Up</button>
181
+ <div class="mt-4 text-sm text-gray-600 dark:text-gray-400">
182
+ Already have an account? <a href="#" id="login-link" class="text-primary dark:text-blue-400 hover:underline">Login</a>
183
+ </div>
184
+ </div>
185
+ </div>
186
+
187
+ <!-- Main Application Container (Initially Hidden) -->
188
+ <div id="app-container" class="hidden flex-grow flex flex-col">
189
+
190
+ <!-- Header -->
191
+ <header class="bg-white dark:bg-gray-800 shadow-sm sticky top-0 z-50">
192
+ <div class="container mx-auto px-4 py-3 flex items-center justify-between">
193
+ <!-- Left Side: Logo & Nav -->
194
+ <div class="flex items-center">
195
+ <a href="#" class="text-xl font-bold text-primary dark:text-blue-400 mr-6">Vision & Discern</a>
196
+ <nav class="hidden md:flex space-x-6">
197
+ <a href="home.html" target="_blank" class="text-gray-600 dark:text-gray-300 hover:text-primary dark:hover:text-blue-400">Home</a>
198
+ <a href="features.html" target="_blank" class="text-gray-600 dark:text-gray-300 hover:text-primary dark:hover:text-blue-400">Key Features</a>
199
+ <a href="feedback.html" target="_blank" class="text-gray-600 dark:text-gray-300 hover:text-primary dark:hover:text-blue-400">Feedback</a>
200
+ <a href="contact.html" target="_blank" class="text-gray-600 dark:text-gray-300 hover:text-primary dark:hover:text-blue-400">Contact Us</a>
201
+ </nav>
202
+ </div>
203
+
204
+ <!-- Right Side: Switches & Logout -->
205
+ <div class="flex items-center space-x-4">
206
+ <!-- Language Switch (Placeholder Structure) -->
207
+ <div class="items-center space-x-2 hidden md:flex">
208
+ <span class="text-sm text-gray-600 dark:text-gray-400">EN</span>
209
+ <label class="custom-switch">
210
+ <input type="checkbox" id="languageToggle" class="custom-switch-input" disabled> <!-- Disabled for now -->
211
+ <span class="custom-switch-slider"></span>
212
+ </label>
213
+ <span class="text-sm text-gray-600 dark:text-gray-400 hindi-font">हिंदी</span>
214
+ </div>
215
+ <!-- Theme Switch -->
216
+ <div class="items-center space-x-2 hidden md:flex">
217
+ <span class="text-sm text-gray-600 dark:text-gray-400"><i class="ri-sun-line"></i></span>
218
+ <label class="custom-switch">
219
+ <input type="checkbox" id="themeToggle" class="custom-switch-input">
220
+ <span class="custom-switch-slider"></span>
221
+ </label>
222
+ <span class="text-sm text-gray-600 dark:text-gray-400"><i class="ri-moon-line"></i></span>
223
+ </div>
224
+ <!-- Logout Button -->
225
+ <button id="logoutButton" class="button-secondary bg-red-600 hover:bg-red-700 dark:bg-red-700 dark:hover:bg-red-800 text-white px-3 py-1.5 rounded-button text-sm inline-flex items-center">
226
+ <i class="ri-logout-box-r-line mr-1"></i>
227
+ Logout
228
+ </button>
229
+ <!-- Mobile Menu Button (Placeholder) -->
230
+ <button class="md:hidden w-10 h-10 flex items-center justify-center" aria-label="Toggle Menu">
231
+ <i class="ri-menu-line text-gray-600 dark:text-gray-300 text-xl"></i>
232
+ </button>
233
+ </div>
234
+ </div>
235
+ </header>
236
+
237
+ <!-- Main Content Area -->
238
+ <main class="flex-grow container mx-auto px-4 py-8">
239
+
240
+ <!-- App Header Section -->
241
+ <section class="mb-8 text-center">
242
+ <h1 class="text-3xl md:text-4xl font-bold text-gray-900 dark:text-white mb-3">Comprehensive AI-Powered Text Analysis</h1>
243
+ <p class="subtitle text-lg text-gray-700 dark:text-gray-300 max-w-3xl mx-auto">
244
+ Leverage advanced AI to extract Hindi text from images, translate it, and predict gender from names.
245
+ </p>
246
+ </section>
247
+
248
+ <!-- Tab Navigation - Styled with Tailwind -->
249
+ <div class="tab-container flex justify-center border-b border-gray-200 dark:border-gray-700 mb-6">
250
+ <!-- Added Icons to Tabs -->
251
+ <button class="tab-button flex items-center gap-2 px-6 py-3 text-gray-600 dark:text-gray-300 border-b-2 border-transparent hover:text-primary dark:hover:text-blue-400 transition-colors active" data-tab="ocr">
252
+ <i class="ri-image-line"></i> OCR
253
+ </button>
254
+ <button class="tab-button flex items-center gap-2 px-6 py-3 text-gray-600 dark:text-gray-300 border-b-2 border-transparent hover:text-primary dark:hover:text-blue-400 transition-colors" data-tab="translation">
255
+ <i class="ri-translate-2"></i> Translation
256
+ </button>
257
+ <button class="tab-button flex items-center gap-2 px-6 py-3 text-gray-600 dark:text-gray-300 border-b-2 border-transparent hover:text-primary dark:hover:text-blue-400 transition-colors" data-tab="gender">
258
+ <i class="ri-men-line"></i><i class="ri-women-line"></i> Gender
259
+ </button>
260
+ </div>
261
+
262
+ <!-- Tab Content Sections -->
263
+ <div>
264
+ <!-- OCR Tab Content -->
265
+ <div class="tab-content active" id="ocr">
266
+ <div class="grid grid-cols-1 md:grid-cols-2 gap-6">
267
+
268
+ <!-- Input Card -->
269
+ <div class="card bg-white dark:bg-gray-800 p-6 rounded-lg shadow-md border border-gray-100 dark:border-gray-700">
270
+ <h2 class="card-title text-xl font-semibold text-gray-800 dark:text-white mb-4 border-b border-gray-200 dark:border-gray-600 pb-2 flex items-center">
271
+ <i class="ri-upload-cloud-2-line mr-2 text-primary dark:text-blue-400"></i>
272
+ Upload Image
273
+ </h2>
274
+ <div id="uploadArea" class="upload-area border-2 border-dashed border-gray-300 dark:border-gray-600 rounded-lg p-6 text-center cursor-pointer hover:border-primary dark:hover:border-blue-400 mb-4 transition-colors">
275
+ <div class="upload-icon text-4xl text-gray-400 dark:text-gray-500 mb-2"><i class="ri-image-add-line"></i></div>
276
+ <p class="text-gray-700 dark:text-gray-300">Click to select or drag & drop</p>
277
+ <p class="info-text text-sm text-gray-500 dark:text-gray-400">PNG, JPG, JPEG</p>
278
+ </div>
279
+ <input type="file" id="fileInput" accept="image/png, image/jpeg, image/jpg" class="hidden"/>
280
+
281
+ <!-- Sample Images Section -->
282
+ <div class="sample-images-container mt-4 pt-4 border-t border-gray-200 dark:border-gray-600">
283
+ <h3 class="text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">Or try a sample image:</h3>
284
+ <div class="flex flex-wrap gap-2">
285
+ <img src="https://raw.githubusercontent.com/sameer-banchhor-git/data-sakshi/refs/heads/main/%E0%A4%B9%E0%A5%89%E0%A4%B8%E0%A5%8D%E0%A4%9F%E0%A4%B2.png"
286
+ data-src="https://raw.githubusercontent.com/sameer-banchhor-git/data-sakshi/refs/heads/main/%E0%A4%B9%E0%A5%89%E0%A4%B8%E0%A5%8D%E0%A4%9F%E0%A4%B2.png"
287
+ alt="Sample Hostel Image" class="sample-image h-16 w-auto border border-gray-300 dark:border-gray-600 rounded-sm cursor-pointer hover:scale-105 hover:shadow-md transition-transform" title="Load Hostel Sample">
288
+ <img src="https://raw.githubusercontent.com/sameer-banchhor-git/data-sakshi/refs/heads/main/%E0%A4%B8%E0%A5%8D%E0%A4%B5%E0%A4%BE%E0%A4%B8%E0%A5%8D%E0%A4%A5%E0%A5%8D%E0%A4%AF.png"
289
+ data-src="https://raw.githubusercontent.com/sameer-banchhor-git/data-sakshi/refs/heads/main/%E0%A4%B8%E0%A5%8D%E0%A4%B5%E0%A4%BE%E0%A4%B8%E0%A5%8D%E0%A4%A5%E0%A5%8D%E0%A4%AF.png"
290
+ alt="Sample Swasthya Image" class="sample-image h-16 w-auto border border-gray-300 dark:border-gray-600 rounded-sm cursor-pointer hover:scale-105 hover:shadow-md transition-transform" title="Load Swasthya Sample">
291
+ <img src="https://raw.githubusercontent.com/sameer-banchhor-git/data-sakshi/refs/heads/main/%E0%A4%B9%E0%A5%81%E0%A4%88.png"
292
+ data-src="https://raw.githubusercontent.com/sameer-banchhor-git/data-sakshi/refs/heads/main/%E0%A4%B9%E0%A5%81%E0%A4%88.png"
293
+ alt="Sample Hui Image" class="sample-image h-16 w-auto border border-gray-300 dark:border-gray-600 rounded-sm cursor-pointer hover:scale-105 hover:shadow-md transition-transform" title="Load Hui Sample">
294
+ </div>
295
+ </div>
296
+
297
+ <!-- Preview Area -->
298
+ <div class="mt-4 pt-4 border-t border-gray-200 dark:border-gray-600">
299
+ <h3 class="text-sm font-medium text-gray-700 dark:text-gray-300 mb-2 text-center">Preview</h3>
300
+ <div class="flex justify-center">
301
+ <img id="imagePreview" src="#" alt="Image Preview" class="hidden max-h-48 w-auto rounded border bg-gray-50 dark:bg-gray-700 dark:border-gray-600 p-1"/>
302
+ </div>
303
+ </div>
304
+
305
+
306
+ <!-- Action Buttons -->
307
+ <div class="mt-6 flex gap-3">
308
+ <button id="processButton" disabled class="flex-1 bg-green-600 text-white px-4 py-2 rounded-button font-medium hover:bg-green-700 transition-colors disabled:opacity-50 disabled:cursor-not-allowed inline-flex items-center justify-center gap-2">
309
+ <i class="ri-camera-lens-line"></i> Process
310
+ </button>
311
+ <button id="clearButton" class="button-secondary flex-1 bg-gray-300 text-gray-800 px-4 py-2 rounded-button hover:bg-gray-400 dark:bg-gray-600 dark:text-gray-200 dark:hover:bg-gray-500 transition-colors inline-flex items-center justify-center gap-2 hidden">
312
+ <i class="ri-delete-bin-line"></i> Clear
313
+ </button>
314
+ </div>
315
+ <div class="error-message bg-red-100 border border-red-300 text-red-800 dark:bg-red-900 dark:bg-opacity-50 dark:border-red-600 dark:text-red-200 px-4 py-2 rounded-md text-sm mt-4 hidden" id="ocrErrorMessage"></div>
316
+ </div>
317
+
318
+ <!-- Results Card -->
319
+ <div class="card bg-white dark:bg-gray-800 p-6 rounded-lg shadow-md border border-gray-100 dark:border-gray-700 flex flex-col items-center justify-center">
320
+ <h2 class="card-title text-xl font-semibold text-gray-800 dark:text-white mb-4 border-b border-gray-200 dark:border-gray-600 pb-2 w-full text-center flex items-center justify-center">
321
+ <i class="ri-file-text-line mr-2 text-primary dark:text-blue-400"></i>
322
+ Results
323
+ </h2>
324
+
325
+ <!-- Loading Indicator -->
326
+ <div id="loadingSpinner" class="flex flex-col items-center justify-center my-4 text-primary dark:text-blue-400 hidden">
327
+ <svg class="animate-spin h-8 w-8 text-primary dark:text-blue-400 mb-2" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
328
+ <circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
329
+ <path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
330
+ </svg>
331
+ <p class="text-sm">Processing your image...</p>
332
+ </div>
333
+
334
+ <!-- Results Display Area -->
335
+ <div id="resultsSection" class="w-full space-y-6 hidden">
336
+ <!-- Combined OCR Output Card -->
337
+ <div class="ocr-result bg-gray-50 dark:bg-gray-700 p-4 rounded-md border border-gray-200 dark:border-gray-600">
338
+ <div class="result-title text-md font-semibold text-gray-800 dark:text-white mb-2">OCR Text Output</div>
339
+ <div class="result-content bg-white dark:bg-gray-800 p-3 rounded text-lg font-medium min-h-[50px] border border-gray-200 dark:border-gray-600" id="ocrOutput">
340
+ <!-- Text will appear here -->
341
+ </div>
342
+ </div>
343
+
344
+ <!-- Word Detection Card -->
345
+ <div class="result-card border-t border-gray-200 dark:border-gray-600 pt-4">
346
+ <div class="result-title text-md font-semibold text-gray-800 dark:text-white mb-2">Word Detection</div>
347
+ <p class="text-sm text-gray-600 dark:text-gray-400 mb-2">Words Detected: <strong class="text-gray-800 dark:text-white"><span id="wordCount">0</span></strong></p>
348
+ <img id="wordDetectionImg" class="result-image w-full h-auto max-h-48 object-contain border border-gray-200 dark:border-gray-600 rounded bg-gray-100 dark:bg-gray-700 hidden" src="" alt="Word Detection">
349
+ </div>
350
+
351
+ <!-- Prediction Card -->
352
+ <div class="result-card border-t border-gray-200 dark:border-gray-600 pt-4">
353
+ <div class="result-title text-md font-semibold text-gray-800 dark:text-white mb-2">Prediction</div>
354
+ <p class="text-sm text-gray-600 dark:text-gray-400 mb-2">Predicted Text: <strong class="text-gray-800 dark:text-white"><span id="predictionLabel">N/A</span></strong></p>
355
+ <img id="predictionImg" class="result-image w-full h-auto max-h-48 object-contain border border-gray-200 dark:border-gray-600 rounded bg-gray-100 dark:bg-gray-700 hidden" src="" alt="Prediction">
356
+ </div>
357
+ </div>
358
+ <p id="initialMessage" class="text-center text-gray-500 dark:text-gray-400 text-sm mt-4">Upload or select a sample image and click 'Process' to see the results.</p>
359
+ </div>
360
+ </div>
361
+ </div>
362
+
363
+ <!-- Translation Tab Content -->
364
+ <div class="tab-content hidden" id="translation">
365
+ <div class="card bg-white dark:bg-gray-800 p-6 rounded-lg shadow-md border border-gray-100 dark:border-gray-700 max-w-2xl mx-auto">
366
+ <h2 class="card-title text-xl font-semibold text-gray-800 dark:text-white mb-4 border-b border-gray-200 dark:border-gray-600 pb-2 flex items-center">
367
+ <i class="ri-translate-2 mr-2 text-primary dark:text-blue-400"></i>
368
+ Text Translation
369
+ </h2>
370
+ <div class="space-y-4">
371
+ <div>
372
+ <label for="text-to-translate" class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">Text to Translate:</label>
373
+ <textarea id="text-to-translate" placeholder="Enter text here or it will be populated from OCR results..." rows="4" class="w-full px-3 py-2 border border-gray-300 rounded-button focus:outline-none focus:ring-2 focus:ring-primary/50 dark:bg-gray-700 dark:border-gray-600 dark:text-gray-200"></textarea>
374
+ </div>
375
+ <div class="grid grid-cols-1 sm:grid-cols-2 gap-4">
376
+ <div>
377
+ <label for="source-language" class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">Source Language (Optional):</label>
378
+ <input type="text" id="source-language" placeholder="Auto-detect if empty" class="w-full px-3 py-2 border border-gray-300 rounded-button focus:outline-none focus:ring-2 focus:ring-primary/50 dark:bg-gray-700 dark:border-gray-600 dark:text-gray-200">
379
+ </div>
380
+ <div>
381
+ <label for="target-language" class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">Target Language:</label>
382
+ <input type="text" id="target-language" placeholder="e.g., English, Hindi" required class="w-full px-3 py-2 border border-gray-300 rounded-button focus:outline-none focus:ring-2 focus:ring-primary/50 dark:bg-gray-700 dark:border-gray-600 dark:text-gray-200">
383
+ </div>
384
+ </div>
385
+ <div>
386
+ <button id="translate-button" class="w-full sm:w-auto bg-primary text-white px-5 py-2 rounded-button hover:bg-blue-700 transition-colors font-medium inline-flex items-center justify-center gap-2 disabled:opacity-50">
387
+ <i class="ri-translate"></i> Translate Text
388
+ </button>
389
+ </div>
390
+ </div>
391
+ <!-- Loading Indicator -->
392
+ <div id="translation-loading" class="flex items-center justify-center my-4 text-primary dark:text-blue-400 hidden">
393
+ <svg class="animate-spin -ml-1 mr-3 h-5 w-5" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
394
+ <circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
395
+ <path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
396
+ </svg>
397
+ <span>Translating...</span>
398
+ </div>
399
+ <div class="error-message bg-red-100 border border-red-300 text-red-800 dark:bg-red-900 dark:bg-opacity-50 dark:border-red-600 dark:text-red-200 px-4 py-2 rounded-md text-sm mt-4 hidden" id="translationErrorMessage"></div>
400
+ </div>
401
+
402
+ <!-- Translation Results -->
403
+ <div id="translation-result" class="results card bg-white dark:bg-gray-800 p-6 rounded-lg shadow-md border border-gray-100 dark:border-gray-700 max-w-2xl mx-auto mt-6 hidden">
404
+ <h3 class="result-header text-lg font-semibold text-gray-800 dark:text-white mb-4 border-b border-gray-200 dark:border-gray-600 pb-2">Translation Result</h3>
405
+ <div class="space-y-3 text-sm">
406
+ <div class="result-item flex justify-between">
407
+ <span class="result-label font-medium text-gray-600 dark:text-gray-400">Source Language:</span>
408
+ <span id="detected-source-language" class="text-gray-800 dark:text-white font-medium"></span>
409
+ </div>
410
+ <div class="result-item flex justify-between">
411
+ <span class="result-label font-medium text-gray-600 dark:text-gray-400">Target Language:</span>
412
+ <span id="translation-target-language" class="text-gray-800 dark:text-white font-medium"></span>
413
+ </div>
414
+ <div class="result-item pt-3 mt-3 border-t border-gray-200 dark:border-gray-600">
415
+ <div class="result-label font-medium text-gray-600 dark:text-gray-400 mb-1">Translated Text:</div>
416
+ <div id="translated-text" class="result-content bg-gray-50 dark:bg-gray-700 p-3 rounded border border-gray-200 dark:border-gray-600 text-gray-800 dark:text-white"></div>
417
+ </div>
418
+ </div>
419
+ </div>
420
+ </div>
421
+
422
+ <!-- Gender Prediction Tab Content -->
423
+ <div class="tab-content hidden" id="gender">
424
+ <div class="card bg-white dark:bg-gray-800 p-6 rounded-lg shadow-md border border-gray-100 dark:border-gray-700 max-w-2xl mx-auto">
425
+ <h2 class="card-title text-xl font-semibold text-gray-800 dark:text-white mb-4 border-b border-gray-200 dark:border-gray-600 pb-2 flex items-center">
426
+ <i class="ri-user-search-line mr-2 text-primary dark:text-blue-400"></i>
427
+ Gender Prediction
428
+ </h2>
429
+ <div class="space-y-4">
430
+ <div>
431
+ <label for="names-input" class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">Enter names (comma-separated):</label>
432
+ <textarea id="names-input" placeholder="e.g., Sakshi, Sameer, Priya" rows="3" class="w-full px-3 py-2 border border-gray-300 rounded-button focus:outline-none focus:ring-2 focus:ring-primary/50 dark:bg-gray-700 dark:border-gray-600 dark:text-gray-200"></textarea>
433
+ </div>
434
+ <div>
435
+ <button id="predict-gender-button" class="w-full sm:w-auto bg-primary text-white px-5 py-2 rounded-button hover:bg-blue-700 transition-colors font-medium inline-flex items-center justify-center gap-2 disabled:opacity-50">
436
+ <i class="ri-user-shared-line"></i> Predict Gender
437
+ </button>
438
+ </div>
439
+ </div>
440
+ <!-- Loading Indicator -->
441
+ <div id="gender-loading" class="flex items-center justify-center my-4 text-primary dark:text-blue-400 hidden">
442
+ <svg class="animate-spin -ml-1 mr-3 h-5 w-5" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
443
+ <circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
444
+ <path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
445
+ </svg>
446
+ <span>Analyzing names...</span>
447
+ </div>
448
+ <div class="error-message bg-red-100 border border-red-300 text-red-800 dark:bg-red-900 dark:bg-opacity-50 dark:border-red-600 dark:text-red-200 px-4 py-2 rounded-md text-sm mt-4 hidden" id="genderErrorMessage"></div>
449
+ </div>
450
+
451
+ <!-- Gender Prediction Results -->
452
+ <div id="gender-results" class="results card bg-white dark:bg-gray-800 p-6 rounded-lg shadow-md border border-gray-100 dark:border-gray-700 max-w-2xl mx-auto mt-6 hidden">
453
+ <h3 class="result-header text-lg font-semibold text-gray-800 dark:text-white mb-4 border-b border-gray-200 dark:border-gray-600 pb-2">Prediction Results</h3>
454
+ <!-- Results populated by JS -->
455
+ <div class="space-y-4 text-sm">
456
+ <!-- JS will insert result items here -->
457
+ </div>
458
+ </div>
459
+ </div>
460
+ </div>
461
+
462
+ </main>
463
+
464
+ <!-- Footer -->
465
+ <footer class="bg-gray-800 text-gray-400 py-8 mt-12"> <!-- Similar style to NIC project footer -->
466
+ <div class="container mx-auto px-4 text-center">
467
+ <div class="mb-4">
468
+ <a href="#" class="text-xl font-bold text-white hover:text-blue-300">Vision & Discern</a>
469
+ </div>
470
+ <div class="credits text-sm mb-4">
471
+ <p>Powered by <strong>D SAKSHI</strong> (MCA Final Year BIT Durg, Chhattisgarh) | © SlimShadow Org. All Rights Reserved.</p>
472
+ </div>
473
+ <div class="flex justify-center space-x-4">
474
+ <a href="#" class="hover:text-white" title="GitHub (Placeholder)"><i class="ri-github-fill"></i></a>
475
+ <a href="#" class="hover:text-white" title="LinkedIn (Placeholder)"><i class="ri-linkedin-box-fill"></i></a>
476
+ <a href="#" class="hover:text-white" title="Twitter (Placeholder)"><i class="ri-twitter-fill"></i></a>
477
+ </div>
478
+ </div>
479
+ </footer>
480
+
481
+ </div> <!-- End #app-container -->
482
+
483
+
484
+ <script>
485
+ // --- API Base URLs ---
486
+ const OCR_API_BASE_URL = 'https://sameernotes-ocr.hf.space';
487
+ const TRANSLATION_API_BASE_URL = 'https://sameernotes-translation-prediction-space.hf.space';
488
+ const GENDER_API_BASE_URL = "https://sidvilas-gender-prediction-space.hf.space";
489
+ const supportedLanguages = ["Afrikaans", "Arabic", "Armenian", "Azerbaijani", "Belarusian", "Bosnian", "Bulgarian", "Catalan", "Chinese", "Croatian", "Czech", "Danish", "Dutch", "English", "Estonian", "Finnish", "French", "Galician", "German", "Greek", "Hebrew", "Hindi", "Hungarian", "Icelandic", "Indonesian", "Italian", "Japanese", "Kannada", "Kazakh", "Korean", "Latvian", "Lithuanian", "Macedonian", "Malay", "Marathi", "Maori", "Nepali", "Norwegian", "Persian", "Polish", "Portuguese", "Romanian", "Russian", "Serbian", "Slovak", "Slovenian", "Spanish", "Swahili", "Swedish", "Tagalog", "Tamil", "Thai", "Turkish", "Ukrainian", "Urdu", "Vietnamese", "Welsh"];
490
+
491
+ let accessToken = null;
492
+ let selectedImageSource = null; // Holds the File object or Blob for processing
493
+
494
+ // --- DOM Element References (Consolidated) ---
495
+ const htmlElement = document.documentElement; // Target <html> for dark mode/lang
496
+
497
+ // Login/Signup
498
+ const loginContainer = document.getElementById('login-container');
499
+ const appContainer = document.getElementById('app-container');
500
+ const loginCard = document.getElementById('login-card');
501
+ const signupCard = document.getElementById('signup-card');
502
+ const loginLink = document.getElementById('login-link');
503
+ const signupLink = document.getElementById('signup-link');
504
+ const usernameInput = document.getElementById('username');
505
+ const passwordInput = document.getElementById('password');
506
+ const loginButton = document.getElementById('loginButton');
507
+ const loginErrorMessage = document.getElementById('loginErrorMessage');
508
+ const signupUsernameInput = document.getElementById('signupUsername');
509
+ const signupEmailInput = document.getElementById('signupEmail');
510
+ const signupPasswordInput = document.getElementById('signupPassword');
511
+ const signupButton = document.getElementById('signupButton');
512
+ const signupErrorMessage = document.getElementById('signupErrorMessage');
513
+ const logoutButton = document.getElementById('logoutButton');
514
+
515
+ // --- Theme/Lang Toggles ---
516
+ const themeToggle = document.getElementById('themeToggle');
517
+ const languageToggle = document.getElementById('languageToggle'); // Added for structure
518
+
519
+ // --- OCR Elements ---
520
+ const fileInput = document.getElementById('fileInput');
521
+ const uploadArea = document.getElementById('uploadArea');
522
+ const imagePreview = document.getElementById('imagePreview');
523
+ const processButton = document.getElementById('processButton');
524
+ const clearButton = document.getElementById('clearButton');
525
+ const sampleImages = document.querySelectorAll('.sample-image');
526
+ const loadingSpinner = document.getElementById('loadingSpinner');
527
+ const resultsSection = document.getElementById('resultsSection');
528
+ const initialMessage = document.getElementById('initialMessage');
529
+ const ocrErrorMessage = document.getElementById('ocrErrorMessage');
530
+ const ocrOutput = document.getElementById('ocrOutput');
531
+ const wordCount = document.getElementById('wordCount');
532
+ const wordDetectionImg = document.getElementById('wordDetectionImg');
533
+ const predictionLabel = document.getElementById('predictionLabel');
534
+ const predictionImg = document.getElementById('predictionImg');
535
+
536
+ // --- Translation Elements ---
537
+ const textToTranslateInput = document.getElementById('text-to-translate');
538
+ const sourceLanguageInput = document.getElementById('source-language');
539
+ const targetLanguageInput = document.getElementById('target-language');
540
+ const translateButton = document.getElementById('translate-button');
541
+ const translationLoading = document.getElementById('translation-loading');
542
+ const translationResultDiv = document.getElementById('translation-result');
543
+ const detectedSourceLanguage = document.getElementById('detected-source-language');
544
+ const translationTargetLanguage = document.getElementById('translation-target-language');
545
+ const translatedText = document.getElementById('translated-text');
546
+ const translationErrorMessage = document.getElementById('translationErrorMessage');
547
+
548
+ // --- Gender Prediction Elements ---
549
+ const namesInput = document.getElementById('names-input');
550
+ const predictGenderButton = document.getElementById('predict-gender-button');
551
+ const genderLoadingDiv = document.getElementById('gender-loading');
552
+ const genderResultsDiv = document.getElementById('gender-results');
553
+ const genderErrorMessageDiv = document.getElementById('genderErrorMessage');
554
+
555
+ // --- Tab Management ---
556
+ const tabs = document.querySelectorAll('.tab-button');
557
+ const tabContents = document.querySelectorAll('.tab-content');
558
+
559
+
560
+ // --- THEME TOGGLE LOGIC (from NIC Project) ---
561
+ function applyTheme(isDark) {
562
+ if (isDark) {
563
+ htmlElement.classList.add('dark');
564
+ if (themeToggle) themeToggle.checked = true;
565
+ console.log("Theme applied: dark");
566
+ } else {
567
+ htmlElement.classList.remove('dark');
568
+ if (themeToggle) themeToggle.checked = false;
569
+ console.log("Theme applied: light");
570
+ }
571
+ }
572
+ // Check localStorage on load
573
+ const prefersDark = localStorage.getItem('theme') === 'dark' ||
574
+ (localStorage.getItem('theme') === null && window.matchMedia('(prefers-color-scheme: dark)').matches);
575
+ applyTheme(prefersDark);
576
+
577
+ // Add listener to toggle button
578
+ if (themeToggle) {
579
+ themeToggle.addEventListener('change', (event) => {
580
+ const isDark = event.target.checked;
581
+ applyTheme(isDark);
582
+ localStorage.setItem('theme', isDark ? 'dark' : 'light');
583
+ });
584
+ } else {
585
+ console.warn("Theme toggle button not found.");
586
+ }
587
+
588
+ // --- LANGUAGE TOGGLE LOGIC (Placeholder from NIC Project) ---
589
+ // Note: This app doesn't use data-lang attributes yet, so this just sets the toggle state and html lang.
590
+ function applyLanguage(lang) { // lang should be 'en' or 'hi'
591
+ console.log("Applying language:", lang);
592
+ htmlElement.setAttribute('lang', lang); // Set overall page lang
593
+
594
+ // TODO: If you add data-lang attributes, iterate and update text here
595
+
596
+ // Update toggle state
597
+ if (languageToggle) languageToggle.checked = (lang === 'hi');
598
+
599
+ // Save preference
600
+ localStorage.setItem('language', lang);
601
+ }
602
+ // Check localStorage on load for language
603
+ const savedLang = localStorage.getItem('language') || 'en'; // Default to English
604
+ applyLanguage(savedLang);
605
+
606
+ // Add listener to language toggle button
607
+ if (languageToggle) {
608
+ languageToggle.disabled = false; // Enable if found
609
+ languageToggle.addEventListener('change', (event) => {
610
+ const newLang = event.target.checked ? 'hi' : 'en';
611
+ applyLanguage(newLang);
612
+ });
613
+ } else {
614
+ console.warn("Language toggle button not found.");
615
+ }
616
+
617
+
618
+ // --- TAB MANAGEMENT ---
619
+ tabs.forEach(tab => {
620
+ tab.addEventListener('click', () => {
621
+ // Deactivate all tabs and content
622
+ tabs.forEach(t => {
623
+ t.classList.remove('active', 'text-primary', 'dark:text-blue-400', 'border-primary', 'dark:border-blue-400', 'font-medium');
624
+ t.classList.add('text-gray-600', 'dark:text-gray-300', 'border-transparent');
625
+ });
626
+ tabContents.forEach(c => c.classList.add('hidden')); // Use hidden class
627
+
628
+ // Activate clicked tab and corresponding content
629
+ tab.classList.add('active', 'text-primary', 'dark:text-blue-400', 'border-primary', 'dark:border-blue-400', 'font-medium');
630
+ tab.classList.remove('text-gray-600', 'dark:text-gray-300', 'border-transparent');
631
+ const activeTabContent = document.getElementById(tab.dataset.tab);
632
+ if (activeTabContent) {
633
+ activeTabContent.classList.remove('hidden'); // Show active content
634
+ }
635
+ });
636
+ });
637
+
638
+ // --- LOGIN/SIGNUP LOGIC (Adapted for hidden class) ---
639
+ loginLink.addEventListener('click', (e) => {
640
+ e.preventDefault();
641
+ signupCard.classList.add('hidden');
642
+ loginCard.classList.remove('hidden');
643
+ });
644
+
645
+ signupLink.addEventListener('click', (e) => {
646
+ e.preventDefault();
647
+ loginCard.classList.add('hidden');
648
+ signupCard.classList.remove('hidden');
649
+ });
650
+
651
+ loginButton.addEventListener('click', async () => {
652
+ loginButton.disabled = true;
653
+ hideElement(loginErrorMessage); // Use helper
654
+ const username = usernameInput.value;
655
+ const password = passwordInput.value;
656
+
657
+ if (!username || !password) {
658
+ showLoginError("Please enter both username and password.");
659
+ loginButton.disabled = false;
660
+ return;
661
+ }
662
+
663
+ const formData = new URLSearchParams();
664
+ formData.append('username', username);
665
+ formData.append('password', password);
666
+
667
+ try {
668
+ const response = await fetch(`${OCR_API_BASE_URL}/token`, {
669
+ method: 'POST',
670
+ headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
671
+ body: formData.toString()
672
+ });
673
+
674
+ if (!response.ok) {
675
+ const errorData = await response.json().catch(() => ({ detail: "Login failed. Check credentials." }));
676
+ showLoginError(errorData.detail || "Login failed. Check credentials.");
677
+ return;
678
+ }
679
+
680
+ const data = await response.json();
681
+ accessToken = data.access_token;
682
+ hideElement(loginContainer); // Hide login section
683
+ showElement(appContainer, 'flex flex-col'); // Show app container using flex for layout
684
+
685
+ } catch (error) {
686
+ showLoginError("Network error during login. Please try again.");
687
+ console.error("Login error:", error);
688
+ } finally {
689
+ loginButton.disabled = false;
690
+ }
691
+ });
692
+
693
+ signupButton.addEventListener('click', async () => {
694
+ signupButton.disabled = true;
695
+ hideElement(signupErrorMessage); // Use helper
696
+ const username = signupUsernameInput.value;
697
+ const email = signupEmailInput.value;
698
+ const password = signupPasswordInput.value;
699
+
700
+ if (!username || !email || !password) {
701
+ showSignupError("Please fill in all fields.");
702
+ signupButton.disabled = false;
703
+ return;
704
+ }
705
+ if (!/\S+@\S+\.\S+/.test(email)) {
706
+ showSignupError("Please enter a valid email address.");
707
+ signupButton.disabled = false;
708
+ return;
709
+ }
710
+
711
+ try {
712
+ const response = await fetch(`${OCR_API_BASE_URL}/signup`, {
713
+ method: 'POST',
714
+ headers: { 'Content-Type': 'application/json' },
715
+ body: JSON.stringify({ username, email, password })
716
+ });
717
+
718
+ if (!response.ok) {
719
+ let errorMsg = "Signup failed.";
720
+ try {
721
+ const errorData = await response.json();
722
+ errorMsg = `Signup failed: ${errorData.detail || 'Unknown error'}`;
723
+ } catch (e) { errorMsg = `Signup failed with status ${response.status}.`; }
724
+ showSignupError(errorMsg);
725
+ return;
726
+ }
727
+
728
+ alert('Signup successful! Please login.');
729
+ hideElement(signupCard); // Use helper
730
+ showElement(loginCard); // Use helper
731
+ signupUsernameInput.value = '';
732
+ signupEmailInput.value = '';
733
+ signupPasswordInput.value = '';
734
+
735
+ } catch (error) {
736
+ showSignupError("Network error during signup. Please try again.");
737
+ console.error("Signup error:", error);
738
+ } finally {
739
+ signupButton.disabled = false;
740
+ }
741
+ });
742
+
743
+ logoutButton.addEventListener('click', () => {
744
+ accessToken = null;
745
+ selectedImageSource = null;
746
+ hideElement(appContainer); // Use helper
747
+ showElement(loginContainer, 'flex'); // Use helper for flex centering
748
+ usernameInput.value = '';
749
+ passwordInput.value = '';
750
+ hideElement(loginErrorMessage);
751
+ clearOCRResults();
752
+ clearTranslationResults();
753
+ clearGenderResults();
754
+ });
755
+
756
+ // --- OCR FUNCTIONALITY (Adapted for hidden class) ---
757
+ uploadArea.addEventListener('click', () => fileInput.click());
758
+ uploadArea.addEventListener('dragover', (e) => { e.preventDefault(); uploadArea.classList.add('dragover', 'border-primary', 'dark:border-blue-400', 'bg-secondary'); });
759
+ uploadArea.addEventListener('dragleave', () => uploadArea.classList.remove('dragover', 'border-primary', 'dark:border-blue-400', 'bg-secondary'));
760
+ uploadArea.addEventListener('drop', (e) => {
761
+ e.preventDefault();
762
+ uploadArea.classList.remove('dragover', 'border-primary', 'dark:border-blue-400', 'bg-secondary');
763
+ if (e.dataTransfer.files.length) {
764
+ fileInput.files = e.dataTransfer.files;
765
+ handleFileSelect();
766
+ }
767
+ });
768
+ fileInput.addEventListener('change', handleFileSelect);
769
+
770
+ sampleImages.forEach(img => {
771
+ img.addEventListener('click', () => {
772
+ const imageUrl = img.dataset.src;
773
+ if (imageUrl) { loadSampleImage(imageUrl); }
774
+ else { showOCRError("Sample image URL is missing."); }
775
+ });
776
+ });
777
+
778
+ async function loadSampleImage(imageUrl) {
779
+ clearOCRResults();
780
+ showLoading(loadingSpinner);
781
+ imagePreview.src = imageUrl;
782
+ showElement(imagePreview); // Show preview
783
+ processButton.disabled = true;
784
+ showElement(clearButton, 'inline-flex');
785
+ hideOCRError();
786
+
787
+ try {
788
+ const response = await fetch(imageUrl);
789
+ if (!response.ok) throw new Error(`Failed to fetch sample image: ${response.statusText}`);
790
+ const blob = await response.blob();
791
+ const filename = decodeURIComponent(imageUrl.substring(imageUrl.lastIndexOf('/') + 1) || 'sample.png');
792
+ selectedImageSource = new File([blob], filename, { type: blob.type });
793
+ processButton.disabled = false;
794
+ hideElement(initialMessage);
795
+
796
+ } catch (error) {
797
+ console.error('Error loading sample image:', error);
798
+ showOCRError(`Could not load sample image: ${error.message}`);
799
+ clearOCRResults();
800
+ } finally {
801
+ hideLoading(loadingSpinner);
802
+ }
803
+ }
804
+
805
+ function handleFileSelect() {
806
+ const file = fileInput.files[0];
807
+ if (file) {
808
+ if (!file.type.startsWith('image/')) {
809
+ showOCRError('Please select a valid image file (PNG, JPG, JPEG).');
810
+ fileInput.value = ''; return;
811
+ }
812
+ hideOCRError();
813
+ clearOCRResults();
814
+ selectedImageSource = file;
815
+
816
+ const reader = new FileReader();
817
+ reader.onload = (e) => {
818
+ imagePreview.src = e.target.result;
819
+ showElement(imagePreview);
820
+ processButton.disabled = false;
821
+ showElement(clearButton, 'inline-flex');
822
+ hideElement(initialMessage);
823
+ };
824
+ reader.onerror = () => { showOCRError('Error reading the selected file.'); };
825
+ reader.readAsDataURL(file);
826
+ }
827
+ }
828
+
829
+ processButton.addEventListener('click', processImage);
830
+ clearButton.addEventListener('click', clearOCRResults);
831
+
832
+ async function processImage() {
833
+ const imageSource = selectedImageSource;
834
+ if (!imageSource) { showOCRError('Please select or click a sample image first.'); return; }
835
+
836
+ showLoading(loadingSpinner);
837
+ hideElement(resultsSection);
838
+ hideElement(initialMessage);
839
+ processButton.disabled = true;
840
+ clearButton.disabled = true;
841
+ hideOCRError();
842
+
843
+ try {
844
+ const formData = new FormData();
845
+ formData.append('file', imageSource, imageSource.name || 'image.png');
846
+
847
+ const response = await fetch(`${OCR_API_BASE_URL}/process/`, {
848
+ method: 'POST',
849
+ headers: { 'Authorization': `Bearer ${accessToken}`, 'accept': 'application/json' },
850
+ body: formData
851
+ });
852
+
853
+ if (response.status === 401) {
854
+ showOCRError('Authentication failed. Please log out and log back in.');
855
+ logoutButton.click(); return;
856
+ }
857
+ if (!response.ok) {
858
+ let errorMsg = `Image processing failed (Status: ${response.status})`;
859
+ try { const errorData = await response.json(); errorMsg = `Image processing failed: ${errorData.detail || response.statusText}`; } catch (e) { }
860
+ throw new Error(errorMsg);
861
+ }
862
+
863
+ const data = await response.json();
864
+ ocrOutput.textContent = data.sakshi_output || 'No text detected.';
865
+ textToTranslateInput.value = data.sakshi_output || '';
866
+ wordCount.textContent = data.word_count ?? '0';
867
+ predictionLabel.textContent = data.prediction_label || 'N/A';
868
+
869
+ const fetchImage = async (url, imgElement) => {
870
+ if (!url) { imgElement.src = ''; hideElement(imgElement); return; }
871
+ try {
872
+ const imageResponse = await fetch(url, { headers: { 'Authorization': `Bearer ${accessToken}` } });
873
+ if (imageResponse.ok) {
874
+ const blob = await imageResponse.blob();
875
+ imgElement.src = URL.createObjectURL(blob);
876
+ showElement(imgElement); // Show the image
877
+ // Ensure parent card is visible if it contains the image
878
+ const parentCard = imgElement.closest('.result-card');
879
+ if(parentCard) showElement(parentCard);
880
+ } else { console.error('Failed to fetch result image:', url, imageResponse.status); imgElement.src = ''; hideElement(imgElement); }
881
+ } catch (fetchError) { console.error('Error fetching result image:', url, fetchError); imgElement.src = ''; hideElement(imgElement); }
882
+ };
883
+
884
+ // Use absolute URLs if provided by API, otherwise construct them
885
+ const wordDetectionUrl = data.word_detection_url ? (data.word_detection_url.startsWith('http') ? data.word_detection_url : `${OCR_API_BASE_URL}${data.word_detection_url}`) : null;
886
+ const predictionUrl = data.prediction_image_url ? (data.prediction_image_url.startsWith('http') ? data.prediction_image_url : `${OCR_API_BASE_URL}${data.prediction_image_url}`) : null;
887
+
888
+ await Promise.all([
889
+ fetchImage(wordDetectionUrl, wordDetectionImg),
890
+ fetchImage(predictionUrl, predictionImg)
891
+ ]);
892
+
893
+ showElement(resultsSection); // Show results section
894
+ hideElement(initialMessage);
895
+
896
+ } catch (error) {
897
+ console.error('Error processing image:', error);
898
+ showOCRError(`Error: ${error.message}`);
899
+ hideElement(resultsSection);
900
+ showElement(initialMessage);
901
+ initialMessage.textContent = 'An error occurred during processing. Please try again.';
902
+ } finally {
903
+ hideLoading(loadingSpinner);
904
+ processButton.disabled = false;
905
+ clearButton.disabled = false;
906
+ }
907
+ }
908
+
909
+ // --- TRANSLATION FUNCTIONALITY (Adapted for hidden class) ---
910
+ translateButton.addEventListener('click', async () => {
911
+ const text = textToTranslateInput.value.trim();
912
+ const sourceLanguage = sourceLanguageInput.value.trim();
913
+ const targetLanguage = targetLanguageInput.value.trim();
914
+
915
+ clearTranslationError();
916
+ hideElement(translationResultDiv);
917
+
918
+ if (!text) { showTranslationError('Please enter text to translate.'); return; }
919
+ if (!targetLanguage) { showTranslationError('Please enter the target language.'); return; }
920
+
921
+ showLoading(translationLoading);
922
+ translateButton.disabled = true;
923
+
924
+ try {
925
+ const requestBody = { text: text, target_language: targetLanguage };
926
+ if (sourceLanguage) { requestBody.source_language = sourceLanguage; }
927
+
928
+ const response = await fetch(`${TRANSLATION_API_BASE_URL}/translate`, {
929
+ method: 'POST',
930
+ headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${accessToken}`, 'accept': 'application/json' },
931
+ body: JSON.stringify(requestBody)
932
+ });
933
+
934
+ if (response.status === 401) { showTranslationError('Authentication failed...'); logoutButton.click(); return; }
935
+ if (!response.ok) {
936
+ let errorMsg = `Translation failed (Status: ${response.status})`;
937
+ try { const errorData = await response.json(); errorMsg = `Translation failed: ${errorData.detail || response.statusText}`; } catch (e) { }
938
+ throw new Error(errorMsg);
939
+ }
940
+
941
+ const data = await response.json();
942
+ detectedSourceLanguage.textContent = data.source_language || (sourceLanguage || "Auto-detected");
943
+ translationTargetLanguage.textContent = data.target_language || targetLanguage;
944
+ translatedText.textContent = data.translated_text || "No translation returned.";
945
+ showElement(translationResultDiv); // Show results
946
+
947
+ } catch (error) {
948
+ showTranslationError(`Error: ${error.message}`);
949
+ console.error('Error during translation:', error);
950
+ } finally {
951
+ hideLoading(translationLoading);
952
+ translateButton.disabled = false;
953
+ }
954
+ });
955
+
956
+ // --- GENDER PREDICTION FUNCTIONALITY (Adapted for hidden class) ---
957
+ predictGenderButton.addEventListener('click', async () => {
958
+ const namesString = namesInput.value.trim();
959
+ const names = namesString.split(',').map(name => name.trim()).filter(name => name !== "");
960
+
961
+ if (names.length === 0) { showGenderError("Please enter at least one name."); return; }
962
+
963
+ clearGenderError();
964
+ hideElement(genderResultsDiv);
965
+ showLoading(genderLoadingDiv);
966
+ predictGenderButton.disabled = true;
967
+
968
+ try {
969
+ const response = await fetch(`${GENDER_API_BASE_URL}/predict`, {
970
+ method: 'POST',
971
+ headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${accessToken}`, 'accept': 'application/json' },
972
+ body: JSON.stringify({ names: names, threshold: 0.5 }) // API expects { names: [...] }
973
+ });
974
+
975
+ if (response.status === 401) { showGenderError('Authentication failed...'); logoutButton.click(); return; }
976
+ if (!response.ok) {
977
+ let errorMsg = `Gender prediction failed (Status: ${response.status})`;
978
+ try { const errorData = await response.json(); errorMsg = `Prediction failed: ${errorData.detail || response.statusText}`; } catch (e) { }
979
+ throw new Error(errorMsg);
980
+ }
981
+
982
+ const data = await response.json();
983
+ if (data && Array.isArray(data.predictions)) {
984
+ displayGenderResults(data.predictions);
985
+ showElement(genderResultsDiv); // Show results container
986
+ } else {
987
+ console.error("Unexpected response format:", data);
988
+ showGenderError("Received an unexpected response format from the server.");
989
+ }
990
+
991
+ } catch (error) {
992
+ showGenderError(`Error: ${error.message}`);
993
+ console.error("Prediction error:", error);
994
+ } finally {
995
+ hideLoading(genderLoadingDiv);
996
+ predictGenderButton.disabled = false;
997
+ }
998
+ });
999
+
1000
+ // --- DISPLAY RESULTS (GENDER) ---
1001
+ function displayGenderResults(predictions) {
1002
+ const resultsContainer = genderResultsDiv.querySelector('.space-y-4'); // Target the inner container
1003
+ resultsContainer.innerHTML = ''; // Clear previous results first
1004
+
1005
+ if (predictions.length === 0) {
1006
+ resultsContainer.innerHTML = '<p class="text-gray-500 dark:text-gray-400">No predictions were returned.</p>';
1007
+ } else {
1008
+ predictions.forEach(prediction => {
1009
+ const name = prediction.name || 'N/A';
1010
+ const gender = prediction.predicted_gender || 'Unknown';
1011
+ const maleProb = typeof prediction.male_probability === 'number' ? (prediction.male_probability * 100).toFixed(1) + '%' : 'N/A';
1012
+ const confidence = typeof prediction.confidence === 'number' ? (prediction.confidence * 100).toFixed(1) + '%' : 'N/A';
1013
+
1014
+ // Create structure with Tailwind classes
1015
+ const resultItemHTML = `
1016
+ <div class="result-item border-b border-gray-200 dark:border-gray-600 pb-3 last:border-b-0 last:pb-0">
1017
+ <div class="flex justify-between mb-1">
1018
+ <span class="result-label font-medium text-gray-600 dark:text-gray-400">Name:</span>
1019
+ <span class="text-gray-800 dark:text-white font-semibold">${name}</span>
1020
+ </div>
1021
+ <div class="flex justify-between mb-1">
1022
+ <span class="result-label font-medium text-gray-600 dark:text-gray-400">Predicted Gender:</span>
1023
+ <span class="text-gray-800 dark:text-white">${gender}</span>
1024
+ </div>
1025
+ <div class="flex justify-between mb-1">
1026
+ <span class="result-label font-medium text-gray-600 dark:text-gray-400">Confidence:</span>
1027
+ <span class="text-gray-800 dark:text-white">${confidence}</span>
1028
+ </div>
1029
+ <div class="flex justify-between">
1030
+ <span class="result-label font-medium text-gray-600 dark:text-gray-400">Male Probability:</span>
1031
+ <span class="text-gray-800 dark:text-white">${maleProb}</span>
1032
+ </div>
1033
+ </div>
1034
+ `;
1035
+ resultsContainer.innerHTML += resultItemHTML;
1036
+ });
1037
+ }
1038
+ showElement(genderResultsDiv); // Ensure results container is visible
1039
+ }
1040
+
1041
+ // --- UTILITY FUNCTIONS (Error Handling, Loading, Clearing using hidden class) ---
1042
+
1043
+ function showElement(element, displayType = 'block') {
1044
+ if (element) {
1045
+ element.classList.remove('hidden');
1046
+ // If a specific display type like 'flex' is needed when visible
1047
+ if (displayType !== 'block') {
1048
+ // Ensure Tailwind classes for the display type are present
1049
+ // This might require removing 'block' if it was added by default
1050
+ element.classList.remove('block', 'inline-block', 'flex', 'grid'); // Remove common display types
1051
+ element.classList.add(...displayType.split(' ')); // Add the required classes
1052
+ }
1053
+ }
1054
+ }
1055
+
1056
+ function hideElement(element) {
1057
+ if (element) element.classList.add('hidden');
1058
+ }
1059
+
1060
+ function showLoginError(message) { loginErrorMessage.textContent = message; showElement(loginErrorMessage); }
1061
+ function showSignupError(message) { signupErrorMessage.textContent = message; showElement(signupErrorMessage); }
1062
+ function showOCRError(message) { ocrErrorMessage.textContent = message; showElement(ocrErrorMessage); }
1063
+ function hideOCRError() { hideElement(ocrErrorMessage); }
1064
+ function showTranslationError(message) { translationErrorMessage.textContent = message; showElement(translationErrorMessage); }
1065
+ function clearTranslationError() { translationErrorMessage.textContent = ''; hideElement(translationErrorMessage); }
1066
+ function showGenderError(message) { genderErrorMessageDiv.textContent = message; showElement(genderErrorMessageDiv); }
1067
+ function clearGenderError() { genderErrorMessageDiv.textContent = ''; hideElement(genderErrorMessageDiv); }
1068
+ function showLoading(loadingIndicator) { showElement(loadingIndicator, 'flex flex-col items-center justify-center'); } // Assuming flex layout for spinner
1069
+ function hideLoading(loadingIndicator) { hideElement(loadingIndicator); }
1070
+
1071
+ function clearOCRResults() {
1072
+ fileInput.value = '';
1073
+ selectedImageSource = null;
1074
+ hideElement(imagePreview); imagePreview.src = '';
1075
+ hideElement(wordDetectionImg); wordDetectionImg.src = '';
1076
+ hideElement(predictionImg); predictionImg.src = '';
1077
+ ocrOutput.textContent = '';
1078
+ wordCount.textContent = '0';
1079
+ predictionLabel.textContent = 'N/A';
1080
+ hideElement(resultsSection);
1081
+ showElement(initialMessage);
1082
+ initialMessage.textContent = "Upload or select a sample image and click 'Process' to see the results.";
1083
+ processButton.disabled = true;
1084
+ hideElement(clearButton);
1085
+ hideOCRError();
1086
+ hideLoading(loadingSpinner);
1087
+ }
1088
+
1089
+ function clearTranslationResults() {
1090
+ textToTranslateInput.value = ''; sourceLanguageInput.value = ''; targetLanguageInput.value = '';
1091
+ detectedSourceLanguage.textContent = ''; translationTargetLanguage.textContent = ''; translatedText.textContent = '';
1092
+ hideElement(translationResultDiv);
1093
+ clearTranslationError();
1094
+ hideLoading(translationLoading);
1095
+ }
1096
+
1097
+ function clearGenderResults() {
1098
+ namesInput.value = '';
1099
+ // Clear only the content inside the results div, keep the header
1100
+ const resultsContainer = genderResultsDiv.querySelector('.space-y-4');
1101
+ if (resultsContainer) resultsContainer.innerHTML = '';
1102
+ hideElement(genderResultsDiv);
1103
+ clearGenderError();
1104
+ hideLoading(genderLoadingDiv);
1105
+ }
1106
+
1107
+ // --- Initial state: show login screen ---
1108
+ showElement(loginContainer, 'flex'); // Use flex for centering
1109
+ hideElement(appContainer); // Hide app initially
1110
+
1111
+ </script>
1112
+
1113
+ </body>
1114
+ </html>
style.css CHANGED
@@ -1,28 +1,501 @@
1
- body {
2
- padding: 2rem;
3
- font-family: -apple-system, BlinkMacSystemFont, "Arial", sans-serif;
4
- }
5
-
6
- h1 {
7
- font-size: 16px;
8
- margin-top: 0;
9
- }
10
-
11
- p {
12
- color: rgb(107, 114, 128);
13
- font-size: 15px;
14
- margin-bottom: 10px;
15
- margin-top: 5px;
16
- }
17
-
18
- .card {
19
- max-width: 620px;
20
- margin: 0 auto;
21
- padding: 16px;
22
- border: 1px solid lightgray;
23
- border-radius: 16px;
24
- }
25
-
26
- .card p:last-child {
27
- margin-bottom: 0;
28
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ :root {
2
+ --primary: #4f46e5;
3
+ --primary-light: #818cf8;
4
+ --primary-dark: #3730a3;
5
+ --secondary: #f97316;
6
+ --background: #f8fafc;
7
+ --text: #1e293b;
8
+ --gray-50: #f8fafc;
9
+ --gray-100: #f1f5f9;
10
+ --gray-200: #e2e8f0;
11
+ --gray-300: #cbd5e1;
12
+ --gray-400: #94a3b8;
13
+ --gray-800: #1e293b;
14
+ --shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.05);
15
+ --shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
16
+ --shadow-lg: 0 10px 15px -3px rgba(0, 0, 0, 0.1);
17
+ --radius-sm: 4px;
18
+ --radius: 8px;
19
+ --radius-lg: 12px;
20
+ }
21
+
22
+ * {
23
+ margin: 0;
24
+ padding: 0;
25
+ box-sizing: border-box;
26
+ font-family: 'Inter', 'Segoe UI', system-ui, -apple-system, sans-serif;
27
+ }
28
+
29
+ body {
30
+ background: linear-gradient(135deg, var(--gray-50), var(--gray-100));
31
+ color: var(--text);
32
+ line-height: 1.6;
33
+ min-height: 100vh;
34
+ }
35
+
36
+ .container {
37
+ max-width: 1200px;
38
+ margin: 0 auto;
39
+ padding: 2rem;
40
+ }
41
+
42
+ header {
43
+ padding: 2.5rem 2rem;
44
+ text-align: center;
45
+ background: linear-gradient(135deg, var(--primary-dark), var(--primary));
46
+ color: white;
47
+ border-radius: var(--radius-lg);
48
+ margin-bottom: 3rem;
49
+ box-shadow: var(--shadow-lg);
50
+ position: relative;
51
+ overflow: hidden;
52
+ }
53
+
54
+ header::before {
55
+ content: '';
56
+ position: absolute;
57
+ top: 0;
58
+ left: 0;
59
+ right: 0;
60
+ bottom: 0;
61
+ background: linear-gradient(45deg, rgba(255,255,255,0.1) 25%, transparent 25%),
62
+ linear-gradient(-45deg, rgba(255,255,255,0.1) 25%, transparent 25%),
63
+ linear-gradient(45deg, transparent 75%, rgba(255,255,255,0.1) 75%),
64
+ linear-gradient(-45deg, transparent 75%, rgba(255,255,255,0.1) 75%);
65
+ background-size: 20px 20px;
66
+ opacity: 0.1;
67
+ }
68
+
69
+ h1 {
70
+ font-size: 3rem;
71
+ font-weight: 800;
72
+ margin-bottom: 1rem;
73
+ letter-spacing: -0.025em;
74
+ text-shadow: 2px 2px 4px rgba(0,0,0,0.1);
75
+ }
76
+
77
+ .subtitle {
78
+ font-size: 1.25rem;
79
+ opacity: 0.9;
80
+ margin-bottom: 0.5rem;
81
+ font-weight: 500;
82
+ }
83
+
84
+ .attribution {
85
+ margin-top: 1rem;
86
+ font-size: 0.875rem;
87
+ opacity: 0.8;
88
+ font-weight: 500;
89
+ }
90
+
91
+ nav {
92
+ display: flex;
93
+ justify-content: center;
94
+ gap: 2rem;
95
+ padding: 1rem 0;
96
+ border-bottom: 1px solid rgba(255, 255, 255, 0.2);
97
+ margin-bottom: 2rem;
98
+ }
99
+
100
+ nav a {
101
+ color: white;
102
+ text-decoration: none;
103
+ font-weight: 600;
104
+ opacity: 0.9;
105
+ transition: all 0.2s ease;
106
+ padding: 0.5rem 1rem;
107
+ border-radius: var(--radius);
108
+ position: relative;
109
+ }
110
+
111
+ nav a::after {
112
+ content: '';
113
+ position: absolute;
114
+ bottom: -2px;
115
+ left: 50%;
116
+ transform: translateX(-50%);
117
+ width: 0;
118
+ height: 2px;
119
+ background-color: white;
120
+ transition: width 0.2s ease;
121
+ }
122
+
123
+ nav a:hover::after {
124
+ width: 100%;
125
+ }
126
+
127
+ nav a:hover {
128
+ opacity: 1;
129
+ background-color: rgba(255, 255, 255, 0.1);
130
+ }
131
+
132
+ nav a.active {
133
+ background-color: rgba(255, 255, 255, 0.2);
134
+ opacity: 1;
135
+ }
136
+
137
+ .main-content {
138
+ display: grid;
139
+ grid-template-columns: 1fr;
140
+ gap: 2rem;
141
+ }
142
+
143
+ @media (min-width: 768px) {
144
+ .main-content {
145
+ grid-template-columns: repeat(2, 1fr);
146
+ }
147
+ }
148
+
149
+ .card {
150
+ background: white;
151
+ border-radius: var(--radius-lg);
152
+ padding: 2rem;
153
+ box-shadow: var(--shadow);
154
+ transition: all 0.3s ease;
155
+ border: 1px solid var(--gray-200);
156
+ }
157
+
158
+ .card:hover {
159
+ transform: translateY(-4px);
160
+ box-shadow: var(--shadow-lg);
161
+ }
162
+
163
+ .card-title {
164
+ font-size: 1.5rem;
165
+ color: var(--primary);
166
+ margin-bottom: 1.5rem;
167
+ display: flex;
168
+ align-items: center;
169
+ gap: 0.75rem;
170
+ font-weight: 700;
171
+ }
172
+
173
+ .card-title svg {
174
+ width: 1.5rem;
175
+ height: 1.5rem;
176
+ stroke-width: 2;
177
+ }
178
+
179
+ .upload-area {
180
+ border: 2px dashed var(--gray-300);
181
+ border-radius: var(--radius);
182
+ padding: 3rem 2rem;
183
+ text-align: center;
184
+ cursor: pointer;
185
+ transition: all 0.2s ease;
186
+ margin-bottom: 1.5rem;
187
+ background-color: var(--gray-50);
188
+ }
189
+
190
+ .upload-area:hover, .upload-area.dragover {
191
+ border-color: var(--primary);
192
+ background-color: rgba(79, 70, 229, 0.05);
193
+ }
194
+
195
+ .upload-icon {
196
+ font-size: 3rem;
197
+ color: var(--gray-400);
198
+ margin-bottom: 1rem;
199
+ }
200
+
201
+ #imagePreview {
202
+ max-width: 100%;
203
+ max-height: 300px;
204
+ margin: 1.5rem auto;
205
+ display: none;
206
+ border-radius: var(--radius);
207
+ box-shadow: var(--shadow);
208
+ }
209
+
210
+ button {
211
+ background-color: var(--primary);
212
+ color: white;
213
+ border: none;
214
+ padding: 0.75rem 1.5rem;
215
+ border-radius: var(--radius);
216
+ cursor: pointer;
217
+ font-size: 1rem;
218
+ font-weight: 600;
219
+ transition: all 0.2s ease;
220
+ display: inline-flex;
221
+ align-items: center;
222
+ gap: 0.5rem;
223
+ }
224
+
225
+ button:hover {
226
+ background-color: var(--primary-dark);
227
+ transform: translateY(-1px);
228
+ }
229
+
230
+ button:active {
231
+ transform: translateY(0);
232
+ }
233
+
234
+ button:disabled {
235
+ background-color: var(--gray-300);
236
+ cursor: not-allowed;
237
+ transform: none;
238
+ }
239
+
240
+ .button-secondary {
241
+ background-color: var(--secondary);
242
+ }
243
+
244
+ .button-secondary:hover {
245
+ background-color: #ea580c;
246
+ }
247
+
248
+ .results-section {
249
+ margin-top: 2rem;
250
+ }
251
+
252
+ .result-card {
253
+ background-color: var(--gray-50);
254
+ border-radius: var(--radius);
255
+ padding: 1.5rem;
256
+ margin-bottom: 1.5rem;
257
+ border: 1px solid var(--gray-200);
258
+ }
259
+
260
+ .result-title {
261
+ font-size: 1.25rem;
262
+ font-weight: 700;
263
+ margin-bottom: 1rem;
264
+ color: var(--primary-dark);
265
+ }
266
+
267
+ .result-content {
268
+ background-color: white;
269
+ padding: 1.25rem;
270
+ border-radius: var(--radius);
271
+ border: 1px solid var(--gray-200);
272
+ overflow-wrap: break-word;
273
+ }
274
+
275
+ .result-image {
276
+ max-width: 100%;
277
+ border-radius: var(--radius);
278
+ margin-top: 1rem;
279
+ box-shadow: var(--shadow);
280
+ }
281
+
282
+ .loading-spinner {
283
+ display: none;
284
+ text-align: center;
285
+ margin: 2rem 0;
286
+ }
287
+
288
+ .spinner {
289
+ width: 2.5rem;
290
+ height: 2.5rem;
291
+ border: 3px solid rgba(79, 70, 229, 0.1);
292
+ border-left-color: var(--primary);
293
+ border-radius: 50%;
294
+ animation: spin 0.8s linear infinite;
295
+ margin: 0 auto;
296
+ }
297
+
298
+ @keyframes spin {
299
+ to {
300
+ transform: rotate(360deg);
301
+ }
302
+ }
303
+
304
+ .error-message {
305
+ color: #dc2626;
306
+ background-color: #fee2e2;
307
+ padding: 1rem;
308
+ border-radius: var(--radius);
309
+ display: none;
310
+ margin: 1rem 0;
311
+ font-weight: 500;
312
+ border: 1px solid #fecaca;
313
+ }
314
+
315
+ .info-text {
316
+ color: var(--gray-400);
317
+ font-size: 0.875rem;
318
+ margin-top: 0.75rem;
319
+ }
320
+
321
+ .credits {
322
+ text-align: center;
323
+ margin-top: 3rem;
324
+ padding-top: 1.5rem;
325
+ border-top: 1px solid var(--gray-200);
326
+ color: var(--gray-400);
327
+ font-size: 0.875rem;
328
+ }
329
+
330
+ #login-container {
331
+ display: flex;
332
+ justify-content: center;
333
+ align-items: center;
334
+ min-height: 100vh;
335
+ background: linear-gradient(135deg, var(--gray-50), var(--gray-100));
336
+ padding: 2rem;
337
+ }
338
+
339
+ .auth-card {
340
+ background: white;
341
+ border-radius: var(--radius-lg);
342
+ padding: 2.5rem;
343
+ box-shadow: var(--shadow-lg);
344
+ width: 100%;
345
+ max-width: 400px;
346
+ text-align: center;
347
+ border: 1px solid var(--gray-200);
348
+ }
349
+
350
+ .auth-card h2 {
351
+ font-size: 2rem;
352
+ color: var(--primary);
353
+ margin-bottom: 1.5rem;
354
+ font-weight: 700;
355
+ }
356
+
357
+ .auth-card input[type="text"],
358
+ .auth-card input[type="password"],
359
+ .auth-card input[type="email"] {
360
+ width: 100%;
361
+ padding: 0.75rem 1rem;
362
+ margin-bottom: 1rem;
363
+ border: 1px solid var(--gray-300);
364
+ border-radius: var(--radius);
365
+ font-size: 1rem;
366
+ transition: all 0.2s ease;
367
+ }
368
+
369
+ .auth-card input:focus {
370
+ outline: none;
371
+ border-color: var(--primary);
372
+ box-shadow: 0 0 0 3px rgba(79, 70, 229, 0.1);
373
+ }
374
+
375
+ .auth-card button {
376
+ width: 100%;
377
+ padding: 0.875rem;
378
+ font-size: 1rem;
379
+ font-weight: 600;
380
+ margin-top: 0.5rem;
381
+ }
382
+
383
+ .auth-card .form-switch {
384
+ margin-top: 1.25rem;
385
+ font-size: 0.875rem;
386
+ color: var(--gray-400);
387
+ }
388
+
389
+ .auth-card .form-switch a {
390
+ color: var(--primary);
391
+ text-decoration: none;
392
+ font-weight: 600;
393
+ transition: color 0.2s ease;
394
+ }
395
+
396
+ .auth-card .form-switch a:hover {
397
+ color: var(--primary-dark);
398
+ text-decoration: underline;
399
+ }
400
+
401
+ .logout-button {
402
+ position: absolute;
403
+ top: 1.5rem;
404
+ right: 1.5rem;
405
+ background-color: var(--secondary);
406
+ padding: 0.625rem 1.25rem;
407
+ font-size: 0.875rem;
408
+ }
409
+
410
+ .features-list {
411
+ list-style-type: none;
412
+ padding: 0;
413
+ }
414
+
415
+ .features-list li {
416
+ margin-bottom: 1rem;
417
+ padding-left: 1.5rem;
418
+ position: relative;
419
+ }
420
+
421
+ .features-list li::before {
422
+ content: '•';
423
+ position: absolute;
424
+ left: 0;
425
+ color: var(--primary);
426
+ font-weight: bold;
427
+ }
428
+
429
+ .contact-info p,
430
+ .feedback-form p {
431
+ margin-bottom: 1rem;
432
+ }
433
+
434
+ .feedback-form textarea {
435
+ width: 100%;
436
+ padding: 1rem;
437
+ margin-bottom: 1rem;
438
+ border: 1px solid var(--gray-300);
439
+ border-radius: var(--radius);
440
+ font-size: 1rem;
441
+ min-height: 120px;
442
+ resize: vertical;
443
+ transition: all 0.2s ease;
444
+ }
445
+
446
+ .feedback-form textarea:focus {
447
+ outline: none;
448
+ border-color: var(--primary);
449
+ box-shadow: 0 0 0 3px rgba(79, 70, 229, 0.1);
450
+ }
451
+
452
+ .video-bg {
453
+ position: fixed;
454
+ top: 0;
455
+ left: 0;
456
+ width: 100%;
457
+ height: 100%;
458
+ overflow: hidden;
459
+ z-index: -1;
460
+ }
461
+
462
+ .video-bg video {
463
+ position: absolute;
464
+ top: 50%;
465
+ left: 50%;
466
+ min-width: 100%;
467
+ min-height: 100%;
468
+ width: auto;
469
+ height: auto;
470
+ transform: translate(-50%, -50%);
471
+ object-fit: cover;
472
+ filter: brightness(40%) saturate(120%);
473
+ }
474
+
475
+ @media (max-width: 640px) {
476
+ .container {
477
+ padding: 1rem;
478
+ }
479
+
480
+ header {
481
+ padding: 1.5rem 1rem;
482
+ }
483
+
484
+ h1 {
485
+ font-size: 2rem;
486
+ }
487
+
488
+ .card {
489
+ padding: 1.5rem;
490
+ }
491
+
492
+ nav {
493
+ gap: 1rem;
494
+ flex-wrap: wrap;
495
+ }
496
+
497
+ nav a {
498
+ padding: 0.375rem 0.75rem;
499
+ font-size: 0.875rem;
500
+ }
501
+ }
translation.html ADDED
@@ -0,0 +1,443 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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>Translation API</title>
7
+ <style>
8
+ :root {
9
+ --primary-color: #4a90e2;
10
+ --secondary-color: #f8f9fa;
11
+ --text-color: #2c3e50;
12
+ --border-radius: 12px;
13
+ --transition: all 0.3s ease;
14
+ }
15
+
16
+ * {
17
+ margin: 0;
18
+ padding: 0;
19
+ box-sizing: border-box;
20
+ }
21
+
22
+ body {
23
+ font-family: 'Segoe UI', system-ui, -apple-system, sans-serif;
24
+ line-height: 1.6;
25
+ color: var(--text-color);
26
+ background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%);
27
+ min-height: 100vh;
28
+ padding: 2rem;
29
+ }
30
+
31
+ .container {
32
+ max-width: 1000px;
33
+ margin: 0 auto;
34
+ background: white;
35
+ padding: 2rem;
36
+ border-radius: var(--border-radius);
37
+ box-shadow: 0 10px 30px rgba(0, 0, 0, 0.1);
38
+ }
39
+
40
+ h1 {
41
+ color: var(--primary-color);
42
+ text-align: center;
43
+ margin-bottom: 2rem;
44
+ font-size: 2.5rem;
45
+ font-weight: 700;
46
+ }
47
+
48
+ h2 {
49
+ color: var(--text-color);
50
+ margin-bottom: 1.5rem;
51
+ font-size: 1.8rem;
52
+ }
53
+
54
+ .tab {
55
+ display: flex;
56
+ gap: 1rem;
57
+ margin-bottom: 2rem;
58
+ border: none;
59
+ background: none;
60
+ }
61
+
62
+ .tab button {
63
+ flex: 1;
64
+ padding: 1rem;
65
+ font-size: 1.1rem;
66
+ border: 2px solid var(--primary-color);
67
+ background: transparent;
68
+ color: var(--primary-color);
69
+ border-radius: var(--border-radius);
70
+ cursor: pointer;
71
+ transition: var(--transition);
72
+ }
73
+
74
+ .tab button:hover {
75
+ background: rgba(74, 144, 226, 0.1);
76
+ }
77
+
78
+ .tab button.active {
79
+ background: var(--primary-color);
80
+ color: white;
81
+ }
82
+
83
+ .tabcontent {
84
+ display: none;
85
+ padding: 2rem;
86
+ background: var(--secondary-color);
87
+ border-radius: var(--border-radius);
88
+ margin-bottom: 2rem;
89
+ }
90
+
91
+ label {
92
+ display: block;
93
+ margin-bottom: 0.5rem;
94
+ font-weight: 600;
95
+ color: var(--text-color);
96
+ }
97
+
98
+ textarea, input[type="text"] {
99
+ width: 100%;
100
+ padding: 1rem;
101
+ margin-bottom: 1.5rem;
102
+ border: 2px solid #e1e8ed;
103
+ border-radius: var(--border-radius);
104
+ font-size: 1rem;
105
+ transition: var(--transition);
106
+ background: white;
107
+ }
108
+
109
+ textarea {
110
+ min-height: 150px;
111
+ resize: vertical;
112
+ }
113
+
114
+ textarea:focus, input[type="text"]:focus {
115
+ outline: none;
116
+ border-color: var(--primary-color);
117
+ box-shadow: 0 0 0 3px rgba(74, 144, 226, 0.1);
118
+ }
119
+
120
+ button {
121
+ background: var(--primary-color);
122
+ color: white;
123
+ padding: 1rem 2rem;
124
+ border: none;
125
+ border-radius: var(--border-radius);
126
+ font-size: 1.1rem;
127
+ cursor: pointer;
128
+ transition: var(--transition);
129
+ width: 100%;
130
+ margin-top: 1rem;
131
+ }
132
+
133
+ button:hover {
134
+ transform: translateY(-2px);
135
+ box-shadow: 0 5px 15px rgba(74, 144, 226, 0.3);
136
+ }
137
+
138
+ .loading-indicator {
139
+ display: none;
140
+ text-align: center;
141
+ color: var(--primary-color);
142
+ margin: 1rem 0;
143
+ font-weight: 600;
144
+ }
145
+
146
+ #translation-result, #detection-result {
147
+ background: white;
148
+ padding: 1.5rem;
149
+ border-radius: var(--border-radius);
150
+ margin-top: 2rem;
151
+ box-shadow: 0 5px 15px rgba(0, 0, 0, 0.05);
152
+ }
153
+
154
+ #error-message {
155
+ background: #fee2e2;
156
+ color: #dc2626;
157
+ padding: 1rem;
158
+ border-radius: var(--border-radius);
159
+ margin-top: 1rem;
160
+ display: none;
161
+ }
162
+
163
+ .result-header {
164
+ color: var(--primary-color);
165
+ margin-bottom: 1rem;
166
+ font-size: 1.3rem;
167
+ }
168
+
169
+ .result-item {
170
+ margin-bottom: 1rem;
171
+ padding-bottom: 1rem;
172
+ border-bottom: 1px solid #e1e8ed;
173
+ }
174
+
175
+ .result-item:last-child {
176
+ border-bottom: none;
177
+ margin-bottom: 0;
178
+ padding-bottom: 0;
179
+ }
180
+
181
+ .result-label {
182
+ font-weight: 600;
183
+ margin-bottom: 0.5rem;
184
+ }
185
+
186
+ #translation-options-list {
187
+ list-style: none;
188
+ margin-top: 1rem;
189
+ }
190
+
191
+ #translation-options-list li {
192
+ padding: 0.5rem 0;
193
+ border-bottom: 1px solid #e1e8ed;
194
+ }
195
+
196
+ #translation-options-list li:last-child {
197
+ border-bottom: none;
198
+ }
199
+
200
+ @media (max-width: 768px) {
201
+ body {
202
+ padding: 1rem;
203
+ }
204
+
205
+ .container {
206
+ padding: 1rem;
207
+ }
208
+
209
+ .tab button {
210
+ padding: 0.8rem;
211
+ font-size: 1rem;
212
+ }
213
+
214
+ .tabcontent {
215
+ padding: 1rem;
216
+ }
217
+ }
218
+ </style>
219
+ </head>
220
+ <body>
221
+ <div class="container">
222
+ <h1>Translation & Language Detection</h1>
223
+
224
+ <div class="tab">
225
+ <button class="tablinks active" onclick="openTab(event, 'translate')">Translate</button>
226
+ <button class="tablinks" onclick="openTab(event, 'detect')">Detect Language</button>
227
+ </div>
228
+
229
+ <div id="translate" class="tabcontent" style="display: block;">
230
+ <h2>Text Translation</h2>
231
+ <label for="text-to-translate">Text to Translate</label>
232
+ <textarea id="text-to-translate" placeholder="Enter your text here..."></textarea>
233
+
234
+ <label for="source-language">Source Language (optional)</label>
235
+ <input type="text" id="source-language" placeholder="Leave empty for auto-detection">
236
+
237
+ <label for="target-language">Target Language</label>
238
+ <input type="text" id="target-language" placeholder="e.g., Spanish, French, German">
239
+
240
+ <button id="translate-button">Translate Text</button>
241
+
242
+ <div class="loading-indicator" id="translation-loading">
243
+ Translating your text...
244
+ </div>
245
+
246
+ <div id="translation-result">
247
+ <h3 class="result-header">Translation Results</h3>
248
+ <div class="result-item">
249
+ <div class="result-label">Source Language:</div>
250
+ <div id="detected-source-language"></div>
251
+ </div>
252
+ <div class="result-item">
253
+ <div class="result-label">Target Language:</div>
254
+ <div id="translation-target-language"></div>
255
+ </div>
256
+ <div class="result-item">
257
+ <div class="result-label">Translated Text:</div>
258
+ <div id="translated-text"></div>
259
+ </div>
260
+ </div>
261
+ </div>
262
+
263
+ <div id="detect" class="tabcontent">
264
+ <h2>Language Detection</h2>
265
+ <label for="text-to-detect">Text to Analyze</label>
266
+ <textarea id="text-to-detect" placeholder="Enter text to detect its language..."></textarea>
267
+
268
+ <button id="detect-button">Detect Language</button>
269
+
270
+ <div class="loading-indicator" id="detection-loading">
271
+ Detecting language...
272
+ </div>
273
+
274
+ <div id="detection-result">
275
+ <h3 class="result-header">Detection Results</h3>
276
+ <div class="result-item">
277
+ <div class="result-label">Detected Language:</div>
278
+ <div id="detected-language"></div>
279
+ </div>
280
+ <div id="translation-options-container">
281
+ <h4 class="result-header">Translation Options</h4>
282
+ <ul id="translation-options-list"></ul>
283
+ </div>
284
+ </div>
285
+ </div>
286
+
287
+ <div id="error-message"></div>
288
+ </div>
289
+
290
+ <script>
291
+ const API_BASE_URL = 'https://sameernotes-translation-prediction-space.hf.space';
292
+ const supportedLanguages = ["Afrikaans","Arabic","Armenian","Azerbaijani","Belarusian","Bosnian","Bulgarian","Catalan","Chinese","Croatian","Czech","Danish","Dutch","English","Estonian","Finnish","French","Galician","German","Greek","Hebrew","Hindi","Hungarian","Icelandic","Indonesian","Italian","Japanese","Kannada","Kazakh","Korean","Latvian","Lithuanian","Macedonian","Malay","Marathi","Maori","Nepali","Norwegian","Persian","Polish","Portuguese","Romanian","Russian","Serbian","Slovak","Slovenian","Spanish","Swahili","Swedish","Tagalog","Tamil","Thai","Turkish","Ukrainian","Urdu","Vietnamese","Welsh"];
293
+
294
+ function showError(message) {
295
+ const errorElement = document.getElementById('error-message');
296
+ errorElement.textContent = message;
297
+ errorElement.style.display = 'block';
298
+ setTimeout(() => {
299
+ errorElement.style.display = 'none';
300
+ }, 5000);
301
+ }
302
+
303
+ function clearError() {
304
+ document.getElementById('error-message').style.display = 'none';
305
+ }
306
+
307
+ function openTab(evt, tabName) {
308
+ const tabcontent = document.getElementsByClassName("tabcontent");
309
+ for (let i = 0; i < tabcontent.length; i++) {
310
+ tabcontent[i].style.display = "none";
311
+ }
312
+
313
+ const tablinks = document.getElementsByClassName("tablinks");
314
+ for (let i = 0; i < tablinks.length; i++) {
315
+ tablinks[i].className = tablinks[i].className.replace(" active", "");
316
+ }
317
+
318
+ document.getElementById(tabName).style.display = "block";
319
+ evt.currentTarget.className += " active";
320
+
321
+ document.getElementById('translation-result').style.display = 'none';
322
+ document.getElementById('detection-result').style.display = 'none';
323
+ }
324
+
325
+ document.getElementById('translate-button').addEventListener('click', async () => {
326
+ const text = document.getElementById('text-to-translate').value;
327
+ const sourceLanguage = document.getElementById('source-language').value;
328
+ const targetLanguage = document.getElementById('target-language').value;
329
+
330
+ clearError();
331
+ document.getElementById('translation-loading').style.display = 'block';
332
+ document.getElementById('translation-result').style.display = 'none';
333
+
334
+ if (!text) {
335
+ showError('Please enter text to translate.');
336
+ document.getElementById('translation-loading').style.display = 'none';
337
+ return;
338
+ }
339
+ if (!targetLanguage) {
340
+ showError('Please enter the target language.');
341
+ document.getElementById('translation-loading').style.display = 'none';
342
+ return;
343
+ }
344
+
345
+ if (!supportedLanguages.includes(targetLanguage)) {
346
+ showError(`Target language '${targetLanguage}' is not supported. Supported languages are: ${supportedLanguages.join(', ')}`);
347
+ document.getElementById('translation-loading').style.display = 'none';
348
+ return;
349
+ }
350
+
351
+ try {
352
+ const response = await fetch(`${API_BASE_URL}/translate`, {
353
+ method: 'POST',
354
+ headers: {
355
+ 'Content-Type': 'application/json',
356
+ 'accept': 'application/json'
357
+ },
358
+ body: JSON.stringify({
359
+ text: text,
360
+ source_language: sourceLanguage || undefined,
361
+ target_language: targetLanguage
362
+ })
363
+ });
364
+
365
+ if (!response.ok) {
366
+ const errorData = await response.json();
367
+ showError(`Translation failed: ${errorData.detail}`);
368
+ return;
369
+ }
370
+
371
+ const htmlResponse = await response.text();
372
+
373
+ document.getElementById('detected-source-language').textContent = sourceLanguage || "Auto-detected";
374
+ document.getElementById('translation-target-language').textContent = targetLanguage;
375
+ document.getElementById('translated-text').innerHTML = htmlResponse;
376
+ document.getElementById('translation-result').style.display = 'block';
377
+
378
+ } catch (error) {
379
+ showError(`Error: ${error.message}`);
380
+ console.error('Error during translation:', error);
381
+ } finally {
382
+ document.getElementById('translation-loading').style.display = 'none';
383
+ }
384
+ });
385
+
386
+ document.getElementById('detect-button').addEventListener('click', async () => {
387
+ const text = document.getElementById('text-to-detect').value;
388
+
389
+ clearError();
390
+ document.getElementById('detection-loading').style.display = 'block';
391
+ document.getElementById('detection-result').style.display = 'none';
392
+
393
+ if (!text) {
394
+ showError('Please enter text to detect its language.');
395
+ document.getElementById('detection-loading').style.display = 'none';
396
+ return;
397
+ }
398
+
399
+ try {
400
+ const response = await fetch(`${API_BASE_URL}/detect_language`, {
401
+ method: 'POST',
402
+ headers: {
403
+ 'Content-Type': 'application/json',
404
+ 'accept': 'application/json',
405
+ },
406
+ body: JSON.stringify({ text: text }),
407
+ });
408
+
409
+ if (!response.ok) {
410
+ const errorData = await response.json();
411
+ showError(`Detection failed: ${errorData.detail}`);
412
+ return;
413
+ }
414
+
415
+ const data = await response.json();
416
+ document.getElementById('detected-language').textContent = data.source_language;
417
+
418
+ const optionsList = document.getElementById('translation-options-list');
419
+ optionsList.innerHTML = '';
420
+
421
+ if (data.translation_options && typeof data.translation_options === 'object') {
422
+ for (const [key, value] of Object.entries(data.translation_options)) {
423
+ const listItem = document.createElement('li');
424
+ listItem.textContent = `${key}: ${value}`;
425
+ optionsList.appendChild(listItem);
426
+ }
427
+ document.getElementById('translation-options-container').style.display = 'block';
428
+ } else {
429
+ document.getElementById('translation-options-container').style.display = 'none';
430
+ }
431
+
432
+ document.getElementById('detection-result').style.display = 'block';
433
+
434
+ } catch (error) {
435
+ showError(`Error: ${error.message}`);
436
+ console.error('Error during language detection:', error);
437
+ } finally {
438
+ document.getElementById('detection-loading').style.display = 'none';
439
+ }
440
+ });
441
+ </script>
442
+ </body>
443
+ </html>