GopalKrushnaMahapatra commited on
Commit
4d43163
·
verified ·
1 Parent(s): 2684d16

Update plagiarism.html

Browse files
Files changed (1) hide show
  1. plagiarism.html +740 -740
plagiarism.html CHANGED
@@ -1,740 +1,740 @@
1
- <!DOCTYPE html>
2
- <html lang="en">
3
- <head>
4
- <meta charset="utf-8" />
5
- <title>Plagiarism Check – TrueWrite Scan</title>
6
- <meta name="viewport" content="width=device-width,initial-scale=1" />
7
- <script src="https://cdn.tailwindcss.com"></script>
8
- <!-- jsPDF (no integrity to avoid local SRI issues) -->
9
- <script src="https://cdnjs.cloudflare.com/ajax/libs/jspdf/2.5.1/jspdf.umd.min.js"></script>
10
- </head>
11
- <body class="bg-gradient-to-br from-slate-950 via-slate-900 to-violet-950 text-white min-h-screen flex flex-col">
12
- <main class="flex-1 max-w-6xl mx-auto px-4 py-8">
13
- <!-- Top bar with logo -->
14
- <div class="flex items-center justify-between mb-4">
15
- <div class="flex items-center gap-3">
16
- <div>
17
- <img src="logo.png" alt="TrueWrite Scan Logo" class="w-10 h-10 rounded-xl shadow-lg shadow-indigo-500/40">
18
- </div>
19
- <div>
20
- <h1 class="text-2xl md:text-3xl font-extrabold">TrueWrite <span class="text-blue-400">Scan</span></h1>
21
- <p class="text-[11px] text-slate-300 uppercase tracking-[0.25em]">
22
- Plagiarism Checker
23
- </p>
24
- </div>
25
- </div>
26
- <a href="dashboard.html"
27
- class="text-xs text-slate-200 px-3 py-1.5 rounded-full border border-slate-600/70 bg-slate-900/40 backdrop-blur hover:bg-slate-800/70 transition">
28
- ← Back to dashboard
29
- </a>
30
- </div>
31
-
32
- <!-- Tool navigation -->
33
- <nav class="mb-6">
34
- <div class="inline-flex items-center gap-1 rounded-full bg-slate-900/80 border border-slate-800 p-1 text-xs">
35
- <a href="grammar.html"
36
- class="px-3 py-1.5 rounded-full text-slate-300 hover:bg-slate-800">
37
- Grammar
38
- </a>
39
- <a href="plagiarism.html"
40
- class="px-3 py-1.5 rounded-full bg-blue-500 text-white font-medium shadow shadow-blue-500/40"
41
- aria-current="page">
42
- Plagiarism
43
- </a>
44
- <a href="ai-check.html"
45
- class="px-3 py-1.5 rounded-full text-slate-300 hover:bg-slate-800">
46
- AI Content
47
- </a>
48
- </div>
49
- </nav>
50
-
51
- <!-- Main checker grid -->
52
- <div class="grid md:grid-cols-2 gap-6">
53
- <!-- LEFT PANEL -->
54
- <section class="rounded-3xl border border-white/10 bg-slate-900/40 backdrop-blur-xl shadow-2xl shadow-black/50 p-5 md:p-6 flex flex-col">
55
- <header class="mb-4">
56
- <h2 class="text-lg md:text-xl font-semibold">Analyze your content</h2>
57
- <p class="text-xs text-slate-300 mt-1">
58
- Paste text or drop a <span class="font-semibold">.txt / .pdf / .docx</span> file. This gives an idea of ​​how much Plagiarized content there is.
59
- </p>
60
- </header>
61
- <!-- Drag & drop -->
62
- <div id="dropZone"
63
- class="mb-3 border-2 border-dashed border-slate-600/80 rounded-2xl bg-slate-900/50 backdrop-blur-md px-4 py-3 text-xs flex items-center justify-between transition hover:border-blue-400/80 hover:bg-slate-900/70">
64
- <div class="flex items-center gap-3">
65
- <div class="w-8 h-8 rounded-full bg-slate-800/80 flex items-center justify-center">
66
- <span class="text-lg">📂</span>
67
- </div>
68
- <div>
69
- <div class="font-semibold text-sm">Drag & drop file</div>
70
- <div class="text-xs text-slate-400">Supported: .txt, .pdf, .docx (max 15MB)</div>
71
- </div>
72
- </div>
73
- <div class="text-right">
74
- <label class="px-3 py-1 rounded-full border border-slate-600 cursor-pointer bg-slate-800/60 text-xs">
75
- Browse <input id="fileInput" type="file" accept=".txt,.pdf,.docx" class="hidden" />
76
- </label>
77
- <div id="fileName" class="text-xs text-slate-400 mt-1 max-w-[160px] truncate"></div>
78
- </div>
79
- </div>
80
-
81
- <div class="mt-3 text-xs text-slate-400">
82
- Tip: .txt files preview locally; .pdf/.docx will be uploaded and parsed by the server.
83
- </div>
84
-
85
- <div class="mt-3 flex items-center justify-between gap-3">
86
- <button id="pasteBtn"
87
- class="text-xs px-3 py-1 rounded-full border border-slate-600 hover:bg-slate-800">
88
- Paste text
89
- </button>
90
- <span id="statusTiny" class="text-xs text-slate-400">Ready</span>
91
- </div>
92
-
93
- <textarea id="inputText" rows="12"
94
- class="w-full mt-3 p-3 bg-slate-950/60 border border-slate-800 rounded-xl text-sm focus:outline-none focus:ring-2 focus:ring-[#0487D9]
95
- placeholder="Paste text here or load a .txt file (preview)."></textarea>
96
-
97
- <div class="mt-4 flex flex-wrap gap-2 items-center">
98
- <button id="checkBtn"
99
- class="px-5 py-2 rounded-xl bg-[#0487D9] hover:bg-[#0487D9] text-sm font-medium flex items-center gap-2">
100
- <svg id="spinner" class="hidden animate-spin h-4 w-4" viewBox="0 0 24 24" fill="none">
101
- <circle cx="12" cy="12" r="10" stroke="white" stroke-opacity="0.25" stroke-width="4"></circle>
102
- <path d="M4 12a8 8 0 018-8v4a4 4 0 00-4 4H4z" fill="white" fill-opacity="0.8"></path>
103
- </svg>
104
- <span id="checkLabel">Check Plagiarism</span>
105
- </button>
106
-
107
- <button id="downloadBtn"
108
- class="px-5 py-2 rounded-xl bg-slate-800 border border-slate-700 text-sm font-medium">
109
- Download report as PDF
110
- </button>
111
- </div>
112
- </section>
113
-
114
- <!-- RIGHT: Result & Progress -->
115
- <section class="p-5 rounded-3xl bg-slate-900/40 border border-white/5 backdrop-blur text-sm">
116
- <h2 class="font-semibold text-lg mb-2">Result</h2>
117
-
118
- <!-- Progress bar -->
119
- <div class="h-2.5 w-full rounded-full bg-slate-800 overflow-hidden mb-2">
120
- <div id="progressBar" class="h-full w-0 bg-[#0487D9] transition-all"></div>
121
- </div>
122
- <div id="progressText" class="text-xs text-slate-400 mb-3">Idle</div>
123
-
124
- <!-- Big score line -->
125
- <p id="score" class="text-lg font-bold mb-1 text-[#0487D9]">No scan yet.</p>
126
-
127
- <!-- Gauge + risk badge -->
128
- <div class="flex items-center gap-4 mb-4">
129
- <div class="relative w-24 h-24">
130
- <svg viewBox="0 0 100 100" class="w-24 h-24 transform -rotate-90">
131
- <!-- background circle -->
132
- <circle cx="50" cy="50" r="40"
133
- stroke="rgba(148, 163, 184, 0.35)"
134
- stroke-width="8"
135
- fill="none" />
136
- <!-- progress circle -->
137
- <circle id="gaugeCircle" cx="50" cy="50" r="40"
138
- stroke="rgb(59, 130, 246)"
139
- stroke-width="8"
140
- fill="none"
141
- stroke-linecap="round"
142
- stroke-dasharray="251.2"
143
- stroke-dashoffset="251.2" />
144
- </svg>
145
- <div class="absolute inset-0 flex items-center justify-center">
146
- <span id="gaugeLabel" class="text-xs font-semibold text-slate-100">0%</span>
147
- </div>
148
- </div>
149
-
150
- <div>
151
- <span id="riskBadge"
152
- class="inline-flex items-center px-3 py-1 rounded-full text-xs font-semibold bg-slate-800 text-slate-200 border border-slate-600">
153
- No risk yet
154
- </span>
155
- <p class="mt-1 text-[11px] text-slate-400">
156
- Risk level updates after each scan based on plagiarism percentage.
157
- </p>
158
- </div>
159
- </div>
160
-
161
- <p id="summary" class="text-sm text-slate-200 mb-3">
162
- Run a scan to see estimated plagiarism percentage based on the demo corpus.
163
- </p>
164
-
165
- <div class="text-xs text-slate-400 mb-3">
166
- <ul class="list-disc list-inside space-y-1">
167
- <li>Educational demo – not connected to real academic databases.</li>
168
- <li>Backend uses TF-IDF and set-based similarity against local corpus files.</li>
169
- </ul>
170
- </div>
171
-
172
- <h3 class="font-semibold text-sm mt-2 mb-1">Top matches in corpus</h3>
173
- <div id="matches" class="mt-1 text-xs text-slate-300 space-y-2">
174
- <!-- filled by JS -->
175
- </div>
176
- </section>
177
- </div>
178
-
179
- <!-- Extra sections (unchanged UI text) -->
180
- <section class="mt-10 space-y-4">
181
- <h2 class="text-xl md:text-2xl font-semibold">Why choose this plagiarism checker?</h2>
182
- <p class="text-sm text-slate-300">
183
- TrueWrite Scan’s plagiarism page is built to help you understand how similarity checks work,
184
- from input text to percentage score and PDF report.
185
- </p>
186
-
187
- <div class="grid md:grid-cols-3 gap-5 mt-3">
188
- <div class="bg-slate-900/70 border border-slate-800 rounded-2xl p-4">
189
- <div class="flex items-center gap-3 mb-2">
190
- <div class="w-9 h-9 rounded-full bg-emerald-500/20 flex items-center justify-center">
191
- <span class="text-lg">📊</span>
192
- </div>
193
- <p class="font-semibold text-sm">Clear percentage score</p>
194
- </div>
195
- <p class="text-xs text-slate-300">
196
- See an easy-to-understand plagiarism percentage instead of raw technical metrics.
197
- </p>
198
- </div>
199
-
200
- <div class="bg-slate-900/70 border border-slate-800 rounded-2xl p-4">
201
- <div class="flex items-center gap-3 mb-2">
202
- <div class="w-9 h-9 rounded-full bg-indigo-500/20 flex items-center justify-center">
203
- <span class="text-lg">🌐</span>
204
- </div>
205
- <p class="font-semibold text-sm">Backend similarity engine</p>
206
- </div>
207
- <p class="text-xs text-slate-300">
208
- The backend uses TF-IDF and set-based similarity to compare your text with a demo corpus.
209
- </p>
210
- </div>
211
-
212
- <div class="bg-slate-900/70 border border-slate-800 rounded-2xl p-4">
213
- <div class="flex items-center gap-3 mb-2">
214
- <div class="w-9 h-9 rounded-full bg-fuchsia-500/20 flex items-center justify-center">
215
- <span class="text-lg">📄</span>
216
- </div>
217
- <p class="font-semibold text-sm">Instant PDF reports</p>
218
- </div>
219
- <p class="text-xs text-slate-300">
220
- Export a mini plagiarism report so you can attach it with your draft or keep it for records.
221
- </p>
222
- </div>
223
- </div>
224
- </section>
225
-
226
- <section class="mt-10 space-y-4">
227
- <h2 class="text-xl md:text-2xl font-semibold">Who can use this tool?</h2>
228
- <p class="text-sm text-slate-300">
229
- Anyone who wants a first originality check before using heavy enterprise tools.
230
- </p>
231
-
232
- <div class="grid md:grid-cols-3 gap-5 mt-3">
233
- <div class="bg-slate-900/70 border border-slate-800 rounded-2xl p-4">
234
- <div class="flex items-center gap-3 mb-2">
235
- <div class="w-9 h-9 rounded-full bg-sky-500/20 flex items-center justify-center">
236
- <span class="text-lg">🎓</span>
237
- </div>
238
- <p class="font-semibold text-sm">Students & project teams</p>
239
- </div>
240
- <p class="text-xs text-slate-300">
241
- Check reports, abstracts, and essays for overlap with sample academic-style text.
242
- </p>
243
- </div>
244
-
245
- <div class="bg-slate-900/70 border border-slate-800 rounded-2xl p-4">
246
- <div class="flex items-center gap-3 mb-2">
247
- <div class="w-9 h-9 rounded-full bg-amber-500/20 flex items-center justify-center">
248
- <span class="text-lg">👩‍🏫</span>
249
- </div>
250
- <p class="font-semibold text-sm">Educators</p>
251
- </div>
252
- <p class="text-xs text-slate-300">
253
- Demonstrate the concept of similarity checking in class with a clear, visual UI.
254
- </p>
255
- </div>
256
-
257
- <div class="bg-slate-900/70 border border-slate-800 rounded-2xl p-4">
258
- <div class="flex items-center gap-3 mb-2">
259
- <div class="w-9 h-9 rounded-full bg-rose-500/20 flex items-center justify-center">
260
- <span class="text-lg">✍️</span>
261
- </div>
262
- <p class="font-semibold text-sm">Bloggers & writers</p>
263
- </div>
264
- <p class="text-xs text-slate-300">
265
- Quickly see if short articles look too similar to built-in reference styles.
266
- </p>
267
- </div>
268
- </div>
269
- </section>
270
-
271
- <section class="mt-10 space-y-4">
272
- <h2 class="text-xl md:text-2xl font-semibold">How does this plagiarism checker work?</h2>
273
- <p class="text-sm text-slate-300">
274
- The logic is intentionally simple and transparent so you can follow and later extend it with
275
- more advanced databases or APIs.
276
- </p>
277
-
278
- <div class="grid md:grid-cols-3 gap-5 mt-3">
279
- <div class="bg-slate-900/70 border border-slate-800 rounded-2xl p-4">
280
- <div class="flex items-center gap-3 mb-2">
281
- <div class="w-9 h-9 rounded-full bg-emerald-500/20 flex items-center justify-center">
282
- <span class="text-lg">1️⃣</span>
283
- </div>
284
- <p class="font-semibold text-sm">Clean and vectorise</p>
285
- </div>
286
- <p class="text-xs text-slate-300">
287
- Your text is cleaned and transformed into TF-IDF vectors on the server.
288
- </p>
289
- </div>
290
-
291
- <div class="bg-slate-900/70 border border-slate-800 rounded-2xl p-4">
292
- <div class="flex items-center gap-3 mb-2">
293
- <div class="w-9 h-9 rounded-full bg-indigo-500/20 flex items-center justify-center">
294
- <span class="text-lg">2️⃣</span>
295
- </div>
296
- <p class="font-semibold text-sm">Compare with corpus</p>
297
- </div>
298
- <p class="text-xs text-slate-300">
299
- The backend compares your text with stored documents using cosine and set-based similarity.
300
- </p>
301
- </div>
302
-
303
- <div class="bg-slate-900/70 border border-slate-800 rounded-2xl p-4">
304
- <div class="flex items-center gap-3 mb-2">
305
- <div class="w-9 h-9 rounded-full bg-fuchsia-500/20 flex items-center justify-center">
306
- <span class="text-lg">3️⃣</span>
307
- </div>
308
- <p class="font-semibold text-sm">Return a percentage</p>
309
- </div>
310
- <p class="text-xs text-slate-300">
311
- The highest overlap is converted into a simple percentage and returned to this page with top matches.
312
- </p>
313
- </div>
314
- </div>
315
- </section>
316
-
317
- <section class="mt-12">
318
- <h2 class="text-xl md:text-2xl font-semibold mb-3">Top reviews for this plagiarism tool</h2>
319
- <div class="bg-slate-900/80 border border-slate-800 rounded-2xl p-5 md:p-6 relative overflow-hidden">
320
- <div id="reviewCard" class="transition-all duration-500"></div>
321
-
322
- <div class="flex items-center justify-between mt-4">
323
- <div id="reviewDots" class="flex gap-1.5"></div>
324
- <div class="flex gap-2">
325
- <button id="prevReview"
326
- class="w-8 h-8 rounded-full border border-slate-600 flex items-center justify-center text-xs hover:bg-slate-800">‹</button>
327
- <button id="nextReview"
328
- class="w-8 h-8 rounded-full border border-slate-600 flex items-center justify-center text-xs hover:bg-slate-800">›</button>
329
- </div>
330
- </div>
331
- </div>
332
- </section>
333
- </main>
334
-
335
- <!-- Footer -->
336
- <footer class="border-t border-slate-800">
337
- <div class="max-w-6xl mx-auto px-4 py-4 text-[11px] text-slate-400 flex flex-col md:flex-row items-center justify-between gap-2">
338
- <p>© 2025 TrueWrite Scan. All rights reserved.</p>
339
- <p>Designed & developed by <span class="text-blue-300 font-medium">Gopal Krushna Mahapatra</span>.</p>
340
- </div>
341
- </footer>
342
-
343
- <!-- ===================== SCRIPTS ===================== -->
344
- <script>
345
- const BACKEND_URL = "http://localhost:8000";
346
- const token = localStorage.getItem("truewriteToken");
347
- const user = localStorage.getItem("truewriteUser");
348
- if (!token || !user) {
349
- window.location.href = "login.html";
350
- }
351
-
352
- // jsPDF helper
353
- function getJsPDF() {
354
- if (window.jspdf && window.jspdf.jsPDF) return window.jspdf.jsPDF;
355
- if (window.jsPDF) return window.jsPDF;
356
- alert("PDF library (jsPDF) did not load. Check your internet connection or CDN access.");
357
- return null;
358
- }
359
-
360
- // Elements
361
- const dropZone = document.getElementById("dropZone");
362
- const fileInput = document.getElementById("fileInput");
363
- const fileName = document.getElementById("fileName");
364
- const textarea = document.getElementById("inputText");
365
- const checkBtn = document.getElementById("checkBtn");
366
- const spinner = document.getElementById("spinner");
367
- const checkLabel = document.getElementById("checkLabel");
368
- const statusTiny = document.getElementById("statusTiny");
369
- const progressBar = document.getElementById("progressBar");
370
- const progressText = document.getElementById("progressText");
371
- const summaryP = document.getElementById("summary");
372
- const matchesDiv = document.getElementById("matches");
373
- const downloadBtn = document.getElementById("downloadBtn");
374
- const scoreP = document.getElementById("score");
375
- const pasteBtn = document.getElementById("pasteBtn");
376
-
377
- const gaugeCircle = document.getElementById("gaugeCircle");
378
- const gaugeLabel = document.getElementById("gaugeLabel");
379
- const riskBadge = document.getElementById("riskBadge");
380
- const GAUGE_CIRCUMFERENCE = 251.2; // 2πr for r=40
381
-
382
- let lastResult = null;
383
- let progressTimer = null;
384
-
385
- // Gauge & risk helpers
386
- function updateGauge(percent) {
387
- const p = Math.max(0, Math.min(100, Number(percent) || 0));
388
- const offset = GAUGE_CIRCUMFERENCE * (1 - p / 100);
389
- gaugeCircle.style.strokeDashoffset = offset;
390
- gaugeLabel.textContent = p.toFixed(0) + "%";
391
- }
392
-
393
- function updateRiskBadge(percent) {
394
- const p = Math.max(0, Math.min(100, Number(percent) || 0));
395
- let label, extraClasses;
396
-
397
- if (p <= 20) {
398
- label = "Safe";
399
- extraClasses = "bg-blue-500/20 text-blue-300 border border-blue-500/60";
400
- } else if (p <= 50) {
401
- label = "Medium risk";
402
- extraClasses = "bg-amber-500/20 text-amber-300 border border-amber-500/60";
403
- } else {
404
- label = "High risk";
405
- extraClasses = "bg-rose-500/20 text-rose-300 border border-rose-500/60";
406
- }
407
-
408
- riskBadge.className =
409
- "inline-flex items-center px-3 py-1 rounded-full text-xs font-semibold " + extraClasses;
410
- riskBadge.textContent = label;
411
- }
412
-
413
- // Loading & progress
414
- function setLoading(on) {
415
- if (on) {
416
- spinner.classList.remove("hidden");
417
- checkLabel.textContent = "Checking...";
418
- checkBtn.disabled = true;
419
- checkBtn.classList.add("opacity-80");
420
- statusTiny.textContent = "Working...";
421
- progressBar.style.width = "5%";
422
- progressText.textContent = "Uploading/processing...";
423
- progressTimer = setInterval(() => {
424
- let cur = parseFloat(progressBar.style.width) || 5;
425
- cur = Math.min(90, cur + Math.random() * 8);
426
- progressBar.style.width = cur + "%";
427
- }, 250);
428
- } else {
429
- spinner.classList.add("hidden");
430
- checkLabel.textContent = "Check Plagiarism";
431
- checkBtn.disabled = false;
432
- checkBtn.classList.remove("opacity-80");
433
- if (progressTimer) clearInterval(progressTimer);
434
- progressBar.style.width = "100%";
435
- progressText.textContent = "Done";
436
- setTimeout(() => {
437
- progressBar.style.width = "0%";
438
- progressText.textContent = "Idle";
439
- }, 700);
440
- }
441
- }
442
-
443
- // Drag & drop + file handling
444
- ["dragenter", "dragover"].forEach(e =>
445
- dropZone.addEventListener(e, ev => {
446
- ev.preventDefault();
447
- dropZone.classList.add("ring-2", "ring-[#0487D9]");
448
- })
449
- );
450
- ["dragleave", "drop"].forEach(e =>
451
- dropZone.addEventListener(e, ev => {
452
- ev.preventDefault();
453
- dropZone.classList.remove("ring-2", "ring-[#0487D9]");
454
- })
455
- );
456
-
457
- dropZone.addEventListener("drop", ev => {
458
- ev.preventDefault();
459
- const f = ev.dataTransfer.files[0];
460
- if (!f) return;
461
- fileInput.files = ev.dataTransfer.files;
462
- fileName.textContent = f.name;
463
- handleSelectedFile(f);
464
- });
465
-
466
- fileInput.addEventListener("change", ev => {
467
- const f = ev.target.files[0];
468
- if (!f) return;
469
- fileName.textContent = f.name;
470
- handleSelectedFile(f);
471
- });
472
-
473
- function handleSelectedFile(file) {
474
- const fname = file.name || "file";
475
- if (file.type === "text/plain" || fname.toLowerCase().endsWith(".txt")) {
476
- const reader = new FileReader();
477
- reader.onload = () => {
478
- textarea.value = reader.result;
479
- statusTiny.textContent = `${fname} loaded for preview`;
480
- };
481
- reader.readAsText(file);
482
- } else {
483
- textarea.value = "";
484
- statusTiny.textContent = `${fname} selected — will be uploaded and parsed on server when you click Check`;
485
- }
486
- }
487
-
488
- // Paste button
489
- pasteBtn.addEventListener("click", async () => {
490
- try {
491
- const text = await navigator.clipboard.readText();
492
- textarea.value = text;
493
- statusTiny.textContent = "Pasted from clipboard.";
494
- } catch (err) {
495
- alert("Clipboard access blocked by browser.");
496
- }
497
- });
498
-
499
- // Backend API helpers
500
- async function callPlagiarismText(text) {
501
- const res = await fetch(`${BACKEND_URL}/api/plagiarism-check`, {
502
- method: "POST",
503
- headers: {
504
- "Content-Type": "application/json",
505
- "Authorization": `Bearer ${token}`
506
- },
507
- body: JSON.stringify({ text })
508
- });
509
- if (!res.ok) {
510
- const txt = await res.text();
511
- throw new Error(txt || `Server responded ${res.status}`);
512
- }
513
- return res.json();
514
- }
515
-
516
- async function callPlagiarismFile(file) {
517
- const form = new FormData();
518
- form.append("file", file, file.name);
519
- const res = await fetch(`${BACKEND_URL}/api/plagiarism-check-file`, {
520
- method: "POST",
521
- headers: { "Authorization": `Bearer ${token}` },
522
- body: form
523
- });
524
- if (!res.ok) {
525
- const txt = await res.text();
526
- throw new Error(txt || `Server responded ${res.status}`);
527
- }
528
- return res.json();
529
- }
530
-
531
- // Main plagiarism action
532
- checkBtn.addEventListener("click", async () => {
533
- const file = fileInput.files[0];
534
- const text = (textarea.value || "").trim();
535
-
536
- if (!file && !text) {
537
- alert("Please paste text or select a file.");
538
- return;
539
- }
540
-
541
- setLoading(true);
542
- summaryP.textContent = "Checking...";
543
- scoreP.textContent = "Scanning...";
544
- matchesDiv.innerHTML = "";
545
- lastResult = null;
546
-
547
- try {
548
- let data;
549
- if (file && !(file.type === "text/plain" || file.name.toLowerCase().endsWith(".txt"))) {
550
- data = await callPlagiarismFile(file);
551
- } else {
552
- const payloadText = text || (file ? await file.text() : "");
553
- data = await callPlagiarismText(payloadText);
554
- }
555
-
556
- if (!data) throw new Error("Empty response from server");
557
- if (data.plagiarism_percent === undefined) {
558
- throw new Error(data.detail || data.error || "Unexpected response");
559
- }
560
-
561
- const percent = data.plagiarism_percent;
562
- scoreP.textContent = `Estimated Plagiarism: ${percent}%`;
563
- summaryP.textContent = data.summary || `Plagiarism estimate: ${percent}%`;
564
-
565
- updateGauge(percent);
566
- updateRiskBadge(percent);
567
-
568
- if (data.matches && data.matches.length) {
569
- matchesDiv.innerHTML = data.matches.map(m => `
570
- <div class="p-2 rounded bg-slate-800/40 flex items-center justify-between gap-2">
571
- <div>
572
- <div class="font-semibold">${m.title}</div>
573
- <div class="text-[11px] text-slate-400">Similarity score: ${m.score}%</div>
574
- </div>
575
- </div>
576
- `).join("");
577
- } else {
578
- matchesDiv.innerHTML =
579
- "<div class='text-xs text-slate-400'>No close matches found in the current demo corpus.</div>";
580
- }
581
-
582
- lastResult = {
583
- input: text || (file ? "[uploaded file]" : ""),
584
- result: data
585
- };
586
- statusTiny.textContent = "Done";
587
- } catch (err) {
588
- console.error(err);
589
- summaryP.textContent = "Error: " + (err.message || err);
590
- scoreP.textContent = "Scan failed.";
591
- statusTiny.textContent = "Error";
592
- } finally {
593
- setLoading(false);
594
- }
595
- });
596
-
597
- // Download PDF report
598
- downloadBtn.addEventListener("click", () => {
599
- if (!lastResult) {
600
- alert("Run a check first.");
601
- return;
602
- }
603
-
604
- const jsPDF = getJsPDF();
605
- if (!jsPDF) return;
606
-
607
- const doc = new jsPDF({ unit: "pt", format: "a4" });
608
- let y = 40;
609
-
610
- doc.setFontSize(16);
611
- doc.text("TrueWrite Scan — Plagiarism Report", 40, y); y += 22;
612
-
613
- doc.setFontSize(11);
614
- doc.text("Generated: " + new Date().toLocaleString(), 40, y); y += 18;
615
- doc.text("User: " + (user || "N/A"), 40, y); y += 18;
616
-
617
- const pr = lastResult.result.plagiarism_percent ?? "N/A";
618
- doc.setFontSize(12);
619
- doc.text(`Plagiarism: ${pr}%`, 40, y); y += 16;
620
-
621
- if (lastResult.result.summary) {
622
- doc.setFontSize(10);
623
- const summaryLines = doc.splitTextToSize("Summary: " + lastResult.result.summary, 520);
624
- doc.text(summaryLines, 40, y);
625
- y += summaryLines.length * 12 + 10;
626
- }
627
-
628
- if (lastResult.result.matches && lastResult.result.matches.length) {
629
- doc.setFontSize(11);
630
- doc.text("Top matches:", 40, y); y += 14;
631
- lastResult.result.matches.forEach(m => {
632
- const line = `• ${m.title} — ${m.score}%`;
633
- doc.text(line, 48, y);
634
- y += 12;
635
- });
636
- }
637
-
638
- y += 10;
639
- doc.setFontSize(11);
640
- doc.text("--- Original text (truncated) ---", 40, y); y += 16;
641
- doc.setFontSize(9);
642
- const raw = lastResult.input || "";
643
- const truncated = raw.length > 3000 ? raw.slice(0, 3000) + "\n\n[TRUNCATED]" : raw;
644
- const textLines = doc.splitTextToSize(truncated, 520);
645
- doc.text(textLines, 40, y);
646
-
647
- doc.save("plagiarism-report.pdf");
648
- });
649
-
650
- // Reviews slider
651
- const reviews = [
652
- {
653
- name: "Aarav S.",
654
- role: "B.Tech Student",
655
- text: "Great for a quick originality check before using heavy tools like Turnitin.",
656
- stars: 5
657
- },
658
- {
659
- name: "Priya K.",
660
- role: "Research Scholar",
661
- text: "Helps me understand how similarity scores work in a simple, visible way.",
662
- stars: 5
663
- },
664
- {
665
- name: "Rahul M.",
666
- role: "Content Writer",
667
- text: "Nice to get an approximate plagiarism percentage while drafting blog posts.",
668
- stars: 4
669
- },
670
- {
671
- name: "Sneha R.",
672
- role: "M.Sc. Student",
673
- text: "Perfect educational example to show classmates how plagiarism detection is implemented.",
674
- stars: 5
675
- },
676
- {
677
- name: "Vikram J.",
678
- role: "Developer",
679
- text: "Clean UI and easy to tweak the logic for my own experiments.",
680
- stars: 4
681
- }
682
- ];
683
-
684
- let currentReview = 0;
685
- const reviewCard = document.getElementById("reviewCard");
686
- const reviewDots = document.getElementById("reviewDots");
687
-
688
- function starRow(stars) {
689
- let html = "";
690
- for (let i = 0; i < 5; i++) {
691
- html += `<span class="${i < stars ? "text-yellow-400" : "text-slate-600"} text-sm">★</span>`;
692
- }
693
- return html;
694
- }
695
-
696
- function renderReview() {
697
- const r = reviews[currentReview];
698
- reviewCard.innerHTML = `
699
- <div class="flex items-center gap-2 mb-2">
700
- ${starRow(r.stars)}
701
- </div>
702
- <p class="text-sm text-slate-200 mb-3">"${r.text}"</p>
703
- <p class="text-sm font-semibold">${r.name}</p>
704
- <p class="text-xs text-slate-400">${r.role}</p>
705
- `;
706
- reviewDots.innerHTML = reviews.map((_, i) =>
707
- `<span class="w-2 h-2 rounded-full ${i === currentReview ? "bg-[#0487D9]" : "bg-slate-600"}"></span>`
708
- ).join("");
709
- }
710
-
711
- function nextReview() {
712
- currentReview = (currentReview + 1) % reviews.length;
713
- renderReview();
714
- }
715
-
716
- function prevReview() {
717
- currentReview = (currentReview - 1 + reviews.length) % reviews.length;
718
- renderReview();
719
- }
720
-
721
- document.getElementById("nextReview").onclick = () => {
722
- clearInterval(reviewTimer);
723
- nextReview();
724
- reviewTimer = setInterval(nextReview, 6000);
725
- };
726
- document.getElementById("prevReview").onclick = () => {
727
- clearInterval(reviewTimer);
728
- prevReview();
729
- reviewTimer = setInterval(nextReview, 6000);
730
- };
731
-
732
- let reviewTimer = setInterval(nextReview, 6000);
733
- renderReview();
734
-
735
- // Initialise gauge/risk with 0
736
- updateGauge(0);
737
- updateRiskBadge(0);
738
- </script>
739
- </body>
740
- </html>
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="utf-8" />
5
+ <title>Plagiarism Check – TrueWrite Scan</title>
6
+ <meta name="viewport" content="width=device-width,initial-scale=1" />
7
+ <script src="https://cdn.tailwindcss.com"></script>
8
+ <!-- jsPDF (no integrity to avoid local SRI issues) -->
9
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/jspdf/2.5.1/jspdf.umd.min.js"></script>
10
+ </head>
11
+ <body class="bg-gradient-to-br from-slate-950 via-slate-900 to-violet-950 text-white min-h-screen flex flex-col">
12
+ <main class="flex-1 max-w-6xl mx-auto px-4 py-8">
13
+ <!-- Top bar with logo -->
14
+ <div class="flex items-center justify-between mb-4">
15
+ <div class="flex items-center gap-3">
16
+ <div>
17
+ <img src="logo.png" alt="TrueWrite Scan Logo" class="w-10 h-10 rounded-xl shadow-lg shadow-indigo-500/40">
18
+ </div>
19
+ <div>
20
+ <h1 class="text-2xl md:text-3xl font-extrabold">TrueWrite <span class="text-blue-400">Scan</span></h1>
21
+ <p class="text-[11px] text-slate-300 uppercase tracking-[0.25em]">
22
+ Plagiarism Checker
23
+ </p>
24
+ </div>
25
+ </div>
26
+ <a href="dashboard.html"
27
+ class="text-xs text-slate-200 px-3 py-1.5 rounded-full border border-slate-600/70 bg-slate-900/40 backdrop-blur hover:bg-slate-800/70 transition">
28
+ ← Back to dashboard
29
+ </a>
30
+ </div>
31
+
32
+ <!-- Tool navigation -->
33
+ <nav class="mb-6">
34
+ <div class="inline-flex items-center gap-1 rounded-full bg-slate-900/80 border border-slate-800 p-1 text-xs">
35
+ <a href="grammar.html"
36
+ class="px-3 py-1.5 rounded-full text-slate-300 hover:bg-slate-800">
37
+ Grammar
38
+ </a>
39
+ <a href="plagiarism.html"
40
+ class="px-3 py-1.5 rounded-full bg-blue-500 text-white font-medium shadow shadow-blue-500/40"
41
+ aria-current="page">
42
+ Plagiarism
43
+ </a>
44
+ <a href="ai-check.html"
45
+ class="px-3 py-1.5 rounded-full text-slate-300 hover:bg-slate-800">
46
+ AI Content
47
+ </a>
48
+ </div>
49
+ </nav>
50
+
51
+ <!-- Main checker grid -->
52
+ <div class="grid md:grid-cols-2 gap-6">
53
+ <!-- LEFT PANEL -->
54
+ <section class="rounded-3xl border border-white/10 bg-slate-900/40 backdrop-blur-xl shadow-2xl shadow-black/50 p-5 md:p-6 flex flex-col">
55
+ <header class="mb-4">
56
+ <h2 class="text-lg md:text-xl font-semibold">Analyze your content</h2>
57
+ <p class="text-xs text-slate-300 mt-1">
58
+ Paste text or drop a <span class="font-semibold">.txt / .pdf / .docx</span> file. This gives an idea of ​​how much Plagiarized content there is.
59
+ </p>
60
+ </header>
61
+ <!-- Drag & drop -->
62
+ <div id="dropZone"
63
+ class="mb-3 border-2 border-dashed border-slate-600/80 rounded-2xl bg-slate-900/50 backdrop-blur-md px-4 py-3 text-xs flex items-center justify-between transition hover:border-blue-400/80 hover:bg-slate-900/70">
64
+ <div class="flex items-center gap-3">
65
+ <div class="w-8 h-8 rounded-full bg-slate-800/80 flex items-center justify-center">
66
+ <span class="text-lg">📂</span>
67
+ </div>
68
+ <div>
69
+ <div class="font-semibold text-sm">Drag & drop file</div>
70
+ <div class="text-xs text-slate-400">Supported: .txt, .pdf, .docx (max 15MB)</div>
71
+ </div>
72
+ </div>
73
+ <div class="text-right">
74
+ <label class="px-3 py-1 rounded-full border border-slate-600 cursor-pointer bg-slate-800/60 text-xs">
75
+ Browse <input id="fileInput" type="file" accept=".txt,.pdf,.docx" class="hidden" />
76
+ </label>
77
+ <div id="fileName" class="text-xs text-slate-400 mt-1 max-w-[160px] truncate"></div>
78
+ </div>
79
+ </div>
80
+
81
+ <div class="mt-3 text-xs text-slate-400">
82
+ Tip: .txt files preview locally; .pdf/.docx will be uploaded and parsed by the server.
83
+ </div>
84
+
85
+ <div class="mt-3 flex items-center justify-between gap-3">
86
+ <button id="pasteBtn"
87
+ class="text-xs px-3 py-1 rounded-full border border-slate-600 hover:bg-slate-800">
88
+ Paste text
89
+ </button>
90
+ <span id="statusTiny" class="text-xs text-slate-400">Ready</span>
91
+ </div>
92
+
93
+ <textarea id="inputText" rows="12"
94
+ class="w-full mt-3 p-3 bg-slate-950/60 border border-slate-800 rounded-xl text-sm focus:outline-none focus:ring-2 focus:ring-[#0487D9]
95
+ placeholder="Paste text here or load a .txt file (preview)."></textarea>
96
+
97
+ <div class="mt-4 flex flex-wrap gap-2 items-center">
98
+ <button id="checkBtn"
99
+ class="px-5 py-2 rounded-xl bg-[#0487D9] hover:bg-[#0487D9] text-sm font-medium flex items-center gap-2">
100
+ <svg id="spinner" class="hidden animate-spin h-4 w-4" viewBox="0 0 24 24" fill="none">
101
+ <circle cx="12" cy="12" r="10" stroke="white" stroke-opacity="0.25" stroke-width="4"></circle>
102
+ <path d="M4 12a8 8 0 018-8v4a4 4 0 00-4 4H4z" fill="white" fill-opacity="0.8"></path>
103
+ </svg>
104
+ <span id="checkLabel">Check Plagiarism</span>
105
+ </button>
106
+
107
+ <button id="downloadBtn"
108
+ class="px-5 py-2 rounded-xl bg-slate-800 border border-slate-700 text-sm font-medium">
109
+ Download report as PDF
110
+ </button>
111
+ </div>
112
+ </section>
113
+
114
+ <!-- RIGHT: Result & Progress -->
115
+ <section class="p-5 rounded-3xl bg-slate-900/40 border border-white/5 backdrop-blur text-sm">
116
+ <h2 class="font-semibold text-lg mb-2">Result</h2>
117
+
118
+ <!-- Progress bar -->
119
+ <div class="h-2.5 w-full rounded-full bg-slate-800 overflow-hidden mb-2">
120
+ <div id="progressBar" class="h-full w-0 bg-[#0487D9] transition-all"></div>
121
+ </div>
122
+ <div id="progressText" class="text-xs text-slate-400 mb-3">Idle</div>
123
+
124
+ <!-- Big score line -->
125
+ <p id="score" class="text-lg font-bold mb-1 text-[#0487D9]">No scan yet.</p>
126
+
127
+ <!-- Gauge + risk badge -->
128
+ <div class="flex items-center gap-4 mb-4">
129
+ <div class="relative w-24 h-24">
130
+ <svg viewBox="0 0 100 100" class="w-24 h-24 transform -rotate-90">
131
+ <!-- background circle -->
132
+ <circle cx="50" cy="50" r="40"
133
+ stroke="rgba(148, 163, 184, 0.35)"
134
+ stroke-width="8"
135
+ fill="none" />
136
+ <!-- progress circle -->
137
+ <circle id="gaugeCircle" cx="50" cy="50" r="40"
138
+ stroke="rgb(59, 130, 246)"
139
+ stroke-width="8"
140
+ fill="none"
141
+ stroke-linecap="round"
142
+ stroke-dasharray="251.2"
143
+ stroke-dashoffset="251.2" />
144
+ </svg>
145
+ <div class="absolute inset-0 flex items-center justify-center">
146
+ <span id="gaugeLabel" class="text-xs font-semibold text-slate-100">0%</span>
147
+ </div>
148
+ </div>
149
+
150
+ <div>
151
+ <span id="riskBadge"
152
+ class="inline-flex items-center px-3 py-1 rounded-full text-xs font-semibold bg-slate-800 text-slate-200 border border-slate-600">
153
+ No risk yet
154
+ </span>
155
+ <p class="mt-1 text-[11px] text-slate-400">
156
+ Risk level updates after each scan based on plagiarism percentage.
157
+ </p>
158
+ </div>
159
+ </div>
160
+
161
+ <p id="summary" class="text-sm text-slate-200 mb-3">
162
+ Run a scan to see estimated plagiarism percentage based on the demo corpus.
163
+ </p>
164
+
165
+ <div class="text-xs text-slate-400 mb-3">
166
+ <ul class="list-disc list-inside space-y-1">
167
+ <li>Educational demo – not connected to real academic databases.</li>
168
+ <li>Backend uses TF-IDF and set-based similarity against local corpus files.</li>
169
+ </ul>
170
+ </div>
171
+
172
+ <h3 class="font-semibold text-sm mt-2 mb-1">Top matches in corpus</h3>
173
+ <div id="matches" class="mt-1 text-xs text-slate-300 space-y-2">
174
+ <!-- filled by JS -->
175
+ </div>
176
+ </section>
177
+ </div>
178
+
179
+ <!-- Extra sections (unchanged UI text) -->
180
+ <section class="mt-10 space-y-4">
181
+ <h2 class="text-xl md:text-2xl font-semibold">Why choose this plagiarism checker?</h2>
182
+ <p class="text-sm text-slate-300">
183
+ TrueWrite Scan’s plagiarism page is built to help you understand how similarity checks work,
184
+ from input text to percentage score and PDF report.
185
+ </p>
186
+
187
+ <div class="grid md:grid-cols-3 gap-5 mt-3">
188
+ <div class="bg-slate-900/70 border border-slate-800 rounded-2xl p-4">
189
+ <div class="flex items-center gap-3 mb-2">
190
+ <div class="w-9 h-9 rounded-full bg-emerald-500/20 flex items-center justify-center">
191
+ <span class="text-lg">📊</span>
192
+ </div>
193
+ <p class="font-semibold text-sm">Clear percentage score</p>
194
+ </div>
195
+ <p class="text-xs text-slate-300">
196
+ See an easy-to-understand plagiarism percentage instead of raw technical metrics.
197
+ </p>
198
+ </div>
199
+
200
+ <div class="bg-slate-900/70 border border-slate-800 rounded-2xl p-4">
201
+ <div class="flex items-center gap-3 mb-2">
202
+ <div class="w-9 h-9 rounded-full bg-indigo-500/20 flex items-center justify-center">
203
+ <span class="text-lg">🌐</span>
204
+ </div>
205
+ <p class="font-semibold text-sm">Backend similarity engine</p>
206
+ </div>
207
+ <p class="text-xs text-slate-300">
208
+ The backend uses TF-IDF and set-based similarity to compare your text with a demo corpus.
209
+ </p>
210
+ </div>
211
+
212
+ <div class="bg-slate-900/70 border border-slate-800 rounded-2xl p-4">
213
+ <div class="flex items-center gap-3 mb-2">
214
+ <div class="w-9 h-9 rounded-full bg-fuchsia-500/20 flex items-center justify-center">
215
+ <span class="text-lg">📄</span>
216
+ </div>
217
+ <p class="font-semibold text-sm">Instant PDF reports</p>
218
+ </div>
219
+ <p class="text-xs text-slate-300">
220
+ Export a mini plagiarism report so you can attach it with your draft or keep it for records.
221
+ </p>
222
+ </div>
223
+ </div>
224
+ </section>
225
+
226
+ <section class="mt-10 space-y-4">
227
+ <h2 class="text-xl md:text-2xl font-semibold">Who can use this tool?</h2>
228
+ <p class="text-sm text-slate-300">
229
+ Anyone who wants a first originality check before using heavy enterprise tools.
230
+ </p>
231
+
232
+ <div class="grid md:grid-cols-3 gap-5 mt-3">
233
+ <div class="bg-slate-900/70 border border-slate-800 rounded-2xl p-4">
234
+ <div class="flex items-center gap-3 mb-2">
235
+ <div class="w-9 h-9 rounded-full bg-sky-500/20 flex items-center justify-center">
236
+ <span class="text-lg">🎓</span>
237
+ </div>
238
+ <p class="font-semibold text-sm">Students & project teams</p>
239
+ </div>
240
+ <p class="text-xs text-slate-300">
241
+ Check reports, abstracts, and essays for overlap with sample academic-style text.
242
+ </p>
243
+ </div>
244
+
245
+ <div class="bg-slate-900/70 border border-slate-800 rounded-2xl p-4">
246
+ <div class="flex items-center gap-3 mb-2">
247
+ <div class="w-9 h-9 rounded-full bg-amber-500/20 flex items-center justify-center">
248
+ <span class="text-lg">👩‍🏫</span>
249
+ </div>
250
+ <p class="font-semibold text-sm">Educators</p>
251
+ </div>
252
+ <p class="text-xs text-slate-300">
253
+ Demonstrate the concept of similarity checking in class with a clear, visual UI.
254
+ </p>
255
+ </div>
256
+
257
+ <div class="bg-slate-900/70 border border-slate-800 rounded-2xl p-4">
258
+ <div class="flex items-center gap-3 mb-2">
259
+ <div class="w-9 h-9 rounded-full bg-rose-500/20 flex items-center justify-center">
260
+ <span class="text-lg">✍️</span>
261
+ </div>
262
+ <p class="font-semibold text-sm">Bloggers & writers</p>
263
+ </div>
264
+ <p class="text-xs text-slate-300">
265
+ Quickly see if short articles look too similar to built-in reference styles.
266
+ </p>
267
+ </div>
268
+ </div>
269
+ </section>
270
+
271
+ <section class="mt-10 space-y-4">
272
+ <h2 class="text-xl md:text-2xl font-semibold">How does this plagiarism checker work?</h2>
273
+ <p class="text-sm text-slate-300">
274
+ The logic is intentionally simple and transparent so you can follow and later extend it with
275
+ more advanced databases or APIs.
276
+ </p>
277
+
278
+ <div class="grid md:grid-cols-3 gap-5 mt-3">
279
+ <div class="bg-slate-900/70 border border-slate-800 rounded-2xl p-4">
280
+ <div class="flex items-center gap-3 mb-2">
281
+ <div class="w-9 h-9 rounded-full bg-emerald-500/20 flex items-center justify-center">
282
+ <span class="text-lg">1️⃣</span>
283
+ </div>
284
+ <p class="font-semibold text-sm">Clean and vectorise</p>
285
+ </div>
286
+ <p class="text-xs text-slate-300">
287
+ Your text is cleaned and transformed into TF-IDF vectors on the server.
288
+ </p>
289
+ </div>
290
+
291
+ <div class="bg-slate-900/70 border border-slate-800 rounded-2xl p-4">
292
+ <div class="flex items-center gap-3 mb-2">
293
+ <div class="w-9 h-9 rounded-full bg-indigo-500/20 flex items-center justify-center">
294
+ <span class="text-lg">2️⃣</span>
295
+ </div>
296
+ <p class="font-semibold text-sm">Compare with corpus</p>
297
+ </div>
298
+ <p class="text-xs text-slate-300">
299
+ The backend compares your text with stored documents using cosine and set-based similarity.
300
+ </p>
301
+ </div>
302
+
303
+ <div class="bg-slate-900/70 border border-slate-800 rounded-2xl p-4">
304
+ <div class="flex items-center gap-3 mb-2">
305
+ <div class="w-9 h-9 rounded-full bg-fuchsia-500/20 flex items-center justify-center">
306
+ <span class="text-lg">3️⃣</span>
307
+ </div>
308
+ <p class="font-semibold text-sm">Return a percentage</p>
309
+ </div>
310
+ <p class="text-xs text-slate-300">
311
+ The highest overlap is converted into a simple percentage and returned to this page with top matches.
312
+ </p>
313
+ </div>
314
+ </div>
315
+ </section>
316
+
317
+ <section class="mt-12">
318
+ <h2 class="text-xl md:text-2xl font-semibold mb-3">Top reviews for this plagiarism tool</h2>
319
+ <div class="bg-slate-900/80 border border-slate-800 rounded-2xl p-5 md:p-6 relative overflow-hidden">
320
+ <div id="reviewCard" class="transition-all duration-500"></div>
321
+
322
+ <div class="flex items-center justify-between mt-4">
323
+ <div id="reviewDots" class="flex gap-1.5"></div>
324
+ <div class="flex gap-2">
325
+ <button id="prevReview"
326
+ class="w-8 h-8 rounded-full border border-slate-600 flex items-center justify-center text-xs hover:bg-slate-800">‹</button>
327
+ <button id="nextReview"
328
+ class="w-8 h-8 rounded-full border border-slate-600 flex items-center justify-center text-xs hover:bg-slate-800">›</button>
329
+ </div>
330
+ </div>
331
+ </div>
332
+ </section>
333
+ </main>
334
+
335
+ <!-- Footer -->
336
+ <footer class="border-t border-slate-800">
337
+ <div class="max-w-6xl mx-auto px-4 py-4 text-[11px] text-slate-400 flex flex-col md:flex-row items-center justify-between gap-2">
338
+ <p>© 2025 TrueWrite Scan. All rights reserved.</p>
339
+ <p>Designed & developed by <span class="text-blue-300 font-medium">Gopal Krushna Mahapatra</span>.</p>
340
+ </div>
341
+ </footer>
342
+
343
+ <!-- ===================== SCRIPTS ===================== -->
344
+ <script>
345
+ const BACKEND_URL = "https://gopalkrushnamahapatra-truewrite-scan.hf.space";
346
+ const token = localStorage.getItem("truewriteToken");
347
+ const user = localStorage.getItem("truewriteUser");
348
+ if (!token || !user) {
349
+ window.location.href = "login.html";
350
+ }
351
+
352
+ // jsPDF helper
353
+ function getJsPDF() {
354
+ if (window.jspdf && window.jspdf.jsPDF) return window.jspdf.jsPDF;
355
+ if (window.jsPDF) return window.jsPDF;
356
+ alert("PDF library (jsPDF) did not load. Check your internet connection or CDN access.");
357
+ return null;
358
+ }
359
+
360
+ // Elements
361
+ const dropZone = document.getElementById("dropZone");
362
+ const fileInput = document.getElementById("fileInput");
363
+ const fileName = document.getElementById("fileName");
364
+ const textarea = document.getElementById("inputText");
365
+ const checkBtn = document.getElementById("checkBtn");
366
+ const spinner = document.getElementById("spinner");
367
+ const checkLabel = document.getElementById("checkLabel");
368
+ const statusTiny = document.getElementById("statusTiny");
369
+ const progressBar = document.getElementById("progressBar");
370
+ const progressText = document.getElementById("progressText");
371
+ const summaryP = document.getElementById("summary");
372
+ const matchesDiv = document.getElementById("matches");
373
+ const downloadBtn = document.getElementById("downloadBtn");
374
+ const scoreP = document.getElementById("score");
375
+ const pasteBtn = document.getElementById("pasteBtn");
376
+
377
+ const gaugeCircle = document.getElementById("gaugeCircle");
378
+ const gaugeLabel = document.getElementById("gaugeLabel");
379
+ const riskBadge = document.getElementById("riskBadge");
380
+ const GAUGE_CIRCUMFERENCE = 251.2; // 2πr for r=40
381
+
382
+ let lastResult = null;
383
+ let progressTimer = null;
384
+
385
+ // Gauge & risk helpers
386
+ function updateGauge(percent) {
387
+ const p = Math.max(0, Math.min(100, Number(percent) || 0));
388
+ const offset = GAUGE_CIRCUMFERENCE * (1 - p / 100);
389
+ gaugeCircle.style.strokeDashoffset = offset;
390
+ gaugeLabel.textContent = p.toFixed(0) + "%";
391
+ }
392
+
393
+ function updateRiskBadge(percent) {
394
+ const p = Math.max(0, Math.min(100, Number(percent) || 0));
395
+ let label, extraClasses;
396
+
397
+ if (p <= 20) {
398
+ label = "Safe";
399
+ extraClasses = "bg-blue-500/20 text-blue-300 border border-blue-500/60";
400
+ } else if (p <= 50) {
401
+ label = "Medium risk";
402
+ extraClasses = "bg-amber-500/20 text-amber-300 border border-amber-500/60";
403
+ } else {
404
+ label = "High risk";
405
+ extraClasses = "bg-rose-500/20 text-rose-300 border border-rose-500/60";
406
+ }
407
+
408
+ riskBadge.className =
409
+ "inline-flex items-center px-3 py-1 rounded-full text-xs font-semibold " + extraClasses;
410
+ riskBadge.textContent = label;
411
+ }
412
+
413
+ // Loading & progress
414
+ function setLoading(on) {
415
+ if (on) {
416
+ spinner.classList.remove("hidden");
417
+ checkLabel.textContent = "Checking...";
418
+ checkBtn.disabled = true;
419
+ checkBtn.classList.add("opacity-80");
420
+ statusTiny.textContent = "Working...";
421
+ progressBar.style.width = "5%";
422
+ progressText.textContent = "Uploading/processing...";
423
+ progressTimer = setInterval(() => {
424
+ let cur = parseFloat(progressBar.style.width) || 5;
425
+ cur = Math.min(90, cur + Math.random() * 8);
426
+ progressBar.style.width = cur + "%";
427
+ }, 250);
428
+ } else {
429
+ spinner.classList.add("hidden");
430
+ checkLabel.textContent = "Check Plagiarism";
431
+ checkBtn.disabled = false;
432
+ checkBtn.classList.remove("opacity-80");
433
+ if (progressTimer) clearInterval(progressTimer);
434
+ progressBar.style.width = "100%";
435
+ progressText.textContent = "Done";
436
+ setTimeout(() => {
437
+ progressBar.style.width = "0%";
438
+ progressText.textContent = "Idle";
439
+ }, 700);
440
+ }
441
+ }
442
+
443
+ // Drag & drop + file handling
444
+ ["dragenter", "dragover"].forEach(e =>
445
+ dropZone.addEventListener(e, ev => {
446
+ ev.preventDefault();
447
+ dropZone.classList.add("ring-2", "ring-[#0487D9]");
448
+ })
449
+ );
450
+ ["dragleave", "drop"].forEach(e =>
451
+ dropZone.addEventListener(e, ev => {
452
+ ev.preventDefault();
453
+ dropZone.classList.remove("ring-2", "ring-[#0487D9]");
454
+ })
455
+ );
456
+
457
+ dropZone.addEventListener("drop", ev => {
458
+ ev.preventDefault();
459
+ const f = ev.dataTransfer.files[0];
460
+ if (!f) return;
461
+ fileInput.files = ev.dataTransfer.files;
462
+ fileName.textContent = f.name;
463
+ handleSelectedFile(f);
464
+ });
465
+
466
+ fileInput.addEventListener("change", ev => {
467
+ const f = ev.target.files[0];
468
+ if (!f) return;
469
+ fileName.textContent = f.name;
470
+ handleSelectedFile(f);
471
+ });
472
+
473
+ function handleSelectedFile(file) {
474
+ const fname = file.name || "file";
475
+ if (file.type === "text/plain" || fname.toLowerCase().endsWith(".txt")) {
476
+ const reader = new FileReader();
477
+ reader.onload = () => {
478
+ textarea.value = reader.result;
479
+ statusTiny.textContent = `${fname} loaded for preview`;
480
+ };
481
+ reader.readAsText(file);
482
+ } else {
483
+ textarea.value = "";
484
+ statusTiny.textContent = `${fname} selected — will be uploaded and parsed on server when you click Check`;
485
+ }
486
+ }
487
+
488
+ // Paste button
489
+ pasteBtn.addEventListener("click", async () => {
490
+ try {
491
+ const text = await navigator.clipboard.readText();
492
+ textarea.value = text;
493
+ statusTiny.textContent = "Pasted from clipboard.";
494
+ } catch (err) {
495
+ alert("Clipboard access blocked by browser.");
496
+ }
497
+ });
498
+
499
+ // Backend API helpers
500
+ async function callPlagiarismText(text) {
501
+ const res = await fetch(`${BACKEND_URL}/api/plagiarism-check`, {
502
+ method: "POST",
503
+ headers: {
504
+ "Content-Type": "application/json",
505
+ "Authorization": `Bearer ${token}`
506
+ },
507
+ body: JSON.stringify({ text })
508
+ });
509
+ if (!res.ok) {
510
+ const txt = await res.text();
511
+ throw new Error(txt || `Server responded ${res.status}`);
512
+ }
513
+ return res.json();
514
+ }
515
+
516
+ async function callPlagiarismFile(file) {
517
+ const form = new FormData();
518
+ form.append("file", file, file.name);
519
+ const res = await fetch(`${BACKEND_URL}/api/plagiarism-check-file`, {
520
+ method: "POST",
521
+ headers: { "Authorization": `Bearer ${token}` },
522
+ body: form
523
+ });
524
+ if (!res.ok) {
525
+ const txt = await res.text();
526
+ throw new Error(txt || `Server responded ${res.status}`);
527
+ }
528
+ return res.json();
529
+ }
530
+
531
+ // Main plagiarism action
532
+ checkBtn.addEventListener("click", async () => {
533
+ const file = fileInput.files[0];
534
+ const text = (textarea.value || "").trim();
535
+
536
+ if (!file && !text) {
537
+ alert("Please paste text or select a file.");
538
+ return;
539
+ }
540
+
541
+ setLoading(true);
542
+ summaryP.textContent = "Checking...";
543
+ scoreP.textContent = "Scanning...";
544
+ matchesDiv.innerHTML = "";
545
+ lastResult = null;
546
+
547
+ try {
548
+ let data;
549
+ if (file && !(file.type === "text/plain" || file.name.toLowerCase().endsWith(".txt"))) {
550
+ data = await callPlagiarismFile(file);
551
+ } else {
552
+ const payloadText = text || (file ? await file.text() : "");
553
+ data = await callPlagiarismText(payloadText);
554
+ }
555
+
556
+ if (!data) throw new Error("Empty response from server");
557
+ if (data.plagiarism_percent === undefined) {
558
+ throw new Error(data.detail || data.error || "Unexpected response");
559
+ }
560
+
561
+ const percent = data.plagiarism_percent;
562
+ scoreP.textContent = `Estimated Plagiarism: ${percent}%`;
563
+ summaryP.textContent = data.summary || `Plagiarism estimate: ${percent}%`;
564
+
565
+ updateGauge(percent);
566
+ updateRiskBadge(percent);
567
+
568
+ if (data.matches && data.matches.length) {
569
+ matchesDiv.innerHTML = data.matches.map(m => `
570
+ <div class="p-2 rounded bg-slate-800/40 flex items-center justify-between gap-2">
571
+ <div>
572
+ <div class="font-semibold">${m.title}</div>
573
+ <div class="text-[11px] text-slate-400">Similarity score: ${m.score}%</div>
574
+ </div>
575
+ </div>
576
+ `).join("");
577
+ } else {
578
+ matchesDiv.innerHTML =
579
+ "<div class='text-xs text-slate-400'>No close matches found in the current demo corpus.</div>";
580
+ }
581
+
582
+ lastResult = {
583
+ input: text || (file ? "[uploaded file]" : ""),
584
+ result: data
585
+ };
586
+ statusTiny.textContent = "Done";
587
+ } catch (err) {
588
+ console.error(err);
589
+ summaryP.textContent = "Error: " + (err.message || err);
590
+ scoreP.textContent = "Scan failed.";
591
+ statusTiny.textContent = "Error";
592
+ } finally {
593
+ setLoading(false);
594
+ }
595
+ });
596
+
597
+ // Download PDF report
598
+ downloadBtn.addEventListener("click", () => {
599
+ if (!lastResult) {
600
+ alert("Run a check first.");
601
+ return;
602
+ }
603
+
604
+ const jsPDF = getJsPDF();
605
+ if (!jsPDF) return;
606
+
607
+ const doc = new jsPDF({ unit: "pt", format: "a4" });
608
+ let y = 40;
609
+
610
+ doc.setFontSize(16);
611
+ doc.text("TrueWrite Scan — Plagiarism Report", 40, y); y += 22;
612
+
613
+ doc.setFontSize(11);
614
+ doc.text("Generated: " + new Date().toLocaleString(), 40, y); y += 18;
615
+ doc.text("User: " + (user || "N/A"), 40, y); y += 18;
616
+
617
+ const pr = lastResult.result.plagiarism_percent ?? "N/A";
618
+ doc.setFontSize(12);
619
+ doc.text(`Plagiarism: ${pr}%`, 40, y); y += 16;
620
+
621
+ if (lastResult.result.summary) {
622
+ doc.setFontSize(10);
623
+ const summaryLines = doc.splitTextToSize("Summary: " + lastResult.result.summary, 520);
624
+ doc.text(summaryLines, 40, y);
625
+ y += summaryLines.length * 12 + 10;
626
+ }
627
+
628
+ if (lastResult.result.matches && lastResult.result.matches.length) {
629
+ doc.setFontSize(11);
630
+ doc.text("Top matches:", 40, y); y += 14;
631
+ lastResult.result.matches.forEach(m => {
632
+ const line = `• ${m.title} — ${m.score}%`;
633
+ doc.text(line, 48, y);
634
+ y += 12;
635
+ });
636
+ }
637
+
638
+ y += 10;
639
+ doc.setFontSize(11);
640
+ doc.text("--- Original text (truncated) ---", 40, y); y += 16;
641
+ doc.setFontSize(9);
642
+ const raw = lastResult.input || "";
643
+ const truncated = raw.length > 3000 ? raw.slice(0, 3000) + "\n\n[TRUNCATED]" : raw;
644
+ const textLines = doc.splitTextToSize(truncated, 520);
645
+ doc.text(textLines, 40, y);
646
+
647
+ doc.save("plagiarism-report.pdf");
648
+ });
649
+
650
+ // Reviews slider
651
+ const reviews = [
652
+ {
653
+ name: "Aarav S.",
654
+ role: "B.Tech Student",
655
+ text: "Great for a quick originality check before using heavy tools like Turnitin.",
656
+ stars: 5
657
+ },
658
+ {
659
+ name: "Priya K.",
660
+ role: "Research Scholar",
661
+ text: "Helps me understand how similarity scores work in a simple, visible way.",
662
+ stars: 5
663
+ },
664
+ {
665
+ name: "Rahul M.",
666
+ role: "Content Writer",
667
+ text: "Nice to get an approximate plagiarism percentage while drafting blog posts.",
668
+ stars: 4
669
+ },
670
+ {
671
+ name: "Sneha R.",
672
+ role: "M.Sc. Student",
673
+ text: "Perfect educational example to show classmates how plagiarism detection is implemented.",
674
+ stars: 5
675
+ },
676
+ {
677
+ name: "Vikram J.",
678
+ role: "Developer",
679
+ text: "Clean UI and easy to tweak the logic for my own experiments.",
680
+ stars: 4
681
+ }
682
+ ];
683
+
684
+ let currentReview = 0;
685
+ const reviewCard = document.getElementById("reviewCard");
686
+ const reviewDots = document.getElementById("reviewDots");
687
+
688
+ function starRow(stars) {
689
+ let html = "";
690
+ for (let i = 0; i < 5; i++) {
691
+ html += `<span class="${i < stars ? "text-yellow-400" : "text-slate-600"} text-sm">★</span>`;
692
+ }
693
+ return html;
694
+ }
695
+
696
+ function renderReview() {
697
+ const r = reviews[currentReview];
698
+ reviewCard.innerHTML = `
699
+ <div class="flex items-center gap-2 mb-2">
700
+ ${starRow(r.stars)}
701
+ </div>
702
+ <p class="text-sm text-slate-200 mb-3">"${r.text}"</p>
703
+ <p class="text-sm font-semibold">${r.name}</p>
704
+ <p class="text-xs text-slate-400">${r.role}</p>
705
+ `;
706
+ reviewDots.innerHTML = reviews.map((_, i) =>
707
+ `<span class="w-2 h-2 rounded-full ${i === currentReview ? "bg-[#0487D9]" : "bg-slate-600"}"></span>`
708
+ ).join("");
709
+ }
710
+
711
+ function nextReview() {
712
+ currentReview = (currentReview + 1) % reviews.length;
713
+ renderReview();
714
+ }
715
+
716
+ function prevReview() {
717
+ currentReview = (currentReview - 1 + reviews.length) % reviews.length;
718
+ renderReview();
719
+ }
720
+
721
+ document.getElementById("nextReview").onclick = () => {
722
+ clearInterval(reviewTimer);
723
+ nextReview();
724
+ reviewTimer = setInterval(nextReview, 6000);
725
+ };
726
+ document.getElementById("prevReview").onclick = () => {
727
+ clearInterval(reviewTimer);
728
+ prevReview();
729
+ reviewTimer = setInterval(nextReview, 6000);
730
+ };
731
+
732
+ let reviewTimer = setInterval(nextReview, 6000);
733
+ renderReview();
734
+
735
+ // Initialise gauge/risk with 0
736
+ updateGauge(0);
737
+ updateRiskBadge(0);
738
+ </script>
739
+ </body>
740
+ </html>