GopalKrushnaMahapatra commited on
Commit
c4672cd
·
verified ·
1 Parent(s): 3e7ccee

Update grammar.html

Browse files
Files changed (1) hide show
  1. grammar.html +823 -823
grammar.html CHANGED
@@ -1,823 +1,823 @@
1
- <!DOCTYPE html>
2
- <html lang="en">
3
- <head>
4
- <meta charset="UTF-8" />
5
- <title>Grammar 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 SRI issues locally) -->
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-indigo-950 text-white min-h-screen flex flex-col">
12
- <!-- Glow background -->
13
- <div class="pointer-events-none fixed inset-0 opacity-40 blur-3xl"
14
- style="background: radial-gradient(circle at 0% 0%, rgba(59,130,246,0.35), transparent 55%), radial-gradient(circle at 80% 80%, rgba(16,185,129,0.28), transparent 55%);">
15
- </div>
16
-
17
- <main class="relative flex-1 max-w-6xl mx-auto px-4 py-8 z-10">
18
- <!-- Top bar -->
19
- <div class="flex items-center justify-between mb-6">
20
- <div class="flex items-center gap-3">
21
- <div>
22
- <img src="logo.png" alt="TrueWrite Scan Logo" class="w-10 h-10 rounded-xl shadow-lg shadow-indigo-500/40">
23
- </div>
24
- <div>
25
- <h1 class="text-2xl md:text-3xl font-extrabold tracking-tight">
26
- TrueWrite <span class="text-emerald-300">Scan</span>
27
- </h1>
28
- <p class="text-[11px] text-slate-300 uppercase tracking-[0.25em]">
29
- Grammar Rectifier
30
- </p>
31
- </div>
32
- </div>
33
- <a href="dashboard.html"
34
- 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">
35
- ← Back to dashboard
36
- </a>
37
- </div>
38
-
39
- <!-- Tool navigation -->
40
- <nav class="mb-6">
41
- <div class="inline-flex items-center gap-1 rounded-full bg-slate-900/80 border border-slate-800 p-1 text-xs">
42
- <a href="grammar.html"
43
- class="px-3 py-1.5 rounded-full bg-emerald-500 text-white font-medium shadow shadow-emerald-500/40"
44
- aria-current="page">
45
- Grammar
46
- </a>
47
- <a href="plagiarism.html"
48
- class="px-3 py-1.5 rounded-full text-slate-300 hover:bg-slate-800">
49
- Plagiarism
50
- </a>
51
- <a href="ai-check.html"
52
- class="px-3 py-1.5 rounded-full text-slate-300 hover:bg-slate-800">
53
- AI Content
54
- </a>
55
- </div>
56
- </nav>
57
-
58
- <div class="grid md:grid-cols-2 gap-6">
59
- <!-- LEFT: input -->
60
- <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">
61
- <header class="mb-4">
62
- <h2 class="text-lg md:text-xl font-semibold">Input text or upload file</h2>
63
- <p class="text-xs text-slate-300 mt-1">
64
- Large files are trimmed server-side for safety. Upload size limit is 15 MB.
65
- </p>
66
- </header>
67
-
68
- <!-- Drag & drop -->
69
- <div id="dropZone"
70
- 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-emerald-400/80 hover:bg-slate-900/70">
71
- <div class="flex items-center gap-3">
72
- <div class="w-8 h-8 rounded-full bg-slate-800/80 flex items-center justify-center">
73
- <span class="text-lg">📂</span>
74
- </div>
75
- <div>
76
- <p class="font-medium text-slate-100">Drag & drop your file here</p>
77
- <p class="text-[11px] text-slate-400">
78
- Supported: .txt, .pdf, .docx (max 15MB)
79
- </p>
80
- </div>
81
- </div>
82
- <div class="flex flex-col items-end gap-1">
83
- <label class="px-3 py-1 rounded-full border border-slate-500/80 text-[11px] cursor-pointer bg-slate-800/60 hover:bg-slate-700/80">
84
- Browse
85
- <input id="fileInput" type="file" accept=".txt,.pdf,.docx" class="hidden" />
86
- </label>
87
- <span id="fileName" class="text-[10px] text-slate-400 max-w-[150px] truncate"></span>
88
- </div>
89
- </div>
90
-
91
- <div id="statusTiny" class="text-[11px] text-slate-400 mb-2">
92
- Ready · No file selected
93
- </div>
94
-
95
- <!-- Paste / textarea -->
96
- <div class="flex gap-2 items-center mb-1">
97
- <button id="pasteBtn"
98
- class="text-[11px] px-3 py-1 rounded-full border border-slate-600/80 bg-slate-900/70 hover:bg-slate-800/90">
99
- Paste text from clipboard
100
- </button>
101
- <span class="text-[11px] text-slate-500">or type directly below</span>
102
- </div>
103
-
104
- <!-- Word count -->
105
- <div class="flex justify-end mb-2">
106
- <span id="wordCount" class="text-[11px] text-slate-400">0 / 1000 words</span>
107
- </div>
108
-
109
- <textarea id="inputText" rows="10"
110
- class="flex-1 w-full rounded-2xl bg-slate-950/70 border border-slate-700/80 px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-emerald-500/70 focus:border-emerald-500/60 placeholder:text-slate-500"
111
- placeholder="Paste your content here or use the drag-and-drop box above..."></textarea>
112
-
113
- <!-- Buttons -->
114
- <div class="mt-4 flex flex-wrap gap-2 items-center">
115
- <button id="checkBtn"
116
- class="px-5 py-2 rounded-xl bg-emerald-500 hover:bg-emerald-600 text-sm font-semibold shadow-lg shadow-emerald-500/40 flex items-center gap-2">
117
- <svg id="spinner"
118
- class="hidden animate-spin h-4 w-4 text-white"
119
- xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
120
- <circle class="opacity-25" cx="12" cy="12" r="10"
121
- stroke="currentColor" stroke-width="4"></circle>
122
- <path class="opacity-75" fill="currentColor"
123
- d="M4 12a8 8 0 018-8v4a4 4 0 00-4 4H4z"></path>
124
- </svg>
125
- <span id="checkLabel">Check Grammar</span>
126
- </button>
127
-
128
- <!-- Download report button: behavior from file 2 -->
129
- <button id="downloadBtn"
130
- class="px-5 py-2 rounded-xl bg-slate-900/70 border border-slate-600/80 text-sm font-medium hover:bg-slate-800/90">
131
- Download report as PDF
132
- </button>
133
- </div>
134
- </section>
135
-
136
- <!-- RIGHT: result -->
137
- <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 text-sm">
138
- <header class="mb-3">
139
- <h2 class="text-lg font-semibold">Result</h2>
140
- <p class="text-[11px] text-slate-400">
141
- Corrected text and statistics appear here after each scan.
142
- </p>
143
- </header>
144
-
145
- <!-- Progress bar -->
146
- <div class="mb-3">
147
- <div class="h-1.5 w-full rounded-full bg-slate-800/80 overflow-hidden">
148
- <div id="progressBar" class="h-full w-0 bg-gradient-to-r from-emerald-400 via-teal-400 to-sky-400 transition-[width] duration-200 ease-out"></div>
149
- </div>
150
- <div id="progressText" class="text-[11px] text-slate-400 mt-1">Idle</div>
151
- </div>
152
-
153
- <div class="grid gap-3 mb-2">
154
- <div class="flex flex-wrap gap-4 text-[12px] text-slate-200">
155
- <div>Words analysed: <span id="statWords" class="font-semibold text-emerald-300">0</span></div>
156
- <div>Corrections: <span id="statCorrections" class="font-semibold text-emerald-300">0</span></div>
157
- </div>
158
- <p id="summary" class="text-[13px] text-slate-200">
159
- Run a check to see corrected text and a brief summary of edits.
160
- </p>
161
- <!-- Extra corrections info area from file 1 -->
162
- <div id="corrections" class="text-[11px] text-slate-300"></div>
163
- </div>
164
-
165
- <div class="flex-1">
166
- <div class="bg-slate-950/60 border border-slate-800/80 rounded-2xl p-3 flex flex-col h-full">
167
- <p class="text-[11px] text-slate-400 mb-1">Corrected text</p>
168
- <textarea id="correctedText"
169
- class="flex-1 w-full bg-transparent text-xs md:text-[13px] resize-none focus:outline-none min-h-[260px]"
170
- placeholder="Corrected version will appear here..." readonly></textarea>
171
- </div>
172
-
173
- <!-- Hidden original preview holder so JS still works, but nothing is shown -->
174
- <div id="originalPreview" class="hidden"></div>
175
- </div>
176
- </section>
177
- </div>
178
-
179
- <!-- ========= Info sections from file 1 ========= -->
180
-
181
- <section class="mt-10 space-y-4">
182
- <h2 class="text-xl md:text-2xl font-semibold">Why choose this tool?</h2>
183
- <p class="text-sm text-slate-300">
184
- Inspired by tools like QuillBot, this page gives you a simple, focused space to check
185
- short pieces of writing quickly before you submit or share them.
186
- </p>
187
-
188
- <div class="grid md:grid-cols-3 gap-5 mt-3">
189
- <div class="bg-slate-900/70 border border-slate-800 rounded-2xl p-4">
190
- <div class="flex items-center gap-3 mb-2">
191
- <div class="w-9 h-9 rounded-full bg-indigo-500/20 flex items-center justify-center">
192
- <span class="text-lg">⚡</span>
193
- </div>
194
- <p class="font-semibold text-sm">Fast one-click review</p>
195
- </div>
196
- <p class="text-xs text-slate-300">
197
- Paste, click, and instantly get a cleaner version of your text without any sign-up
198
- or complicated options.
199
- </p>
200
- </div>
201
-
202
- <div class="bg-slate-900/70 border border-slate-800 rounded-2xl p-4">
203
- <div class="flex items-center gap-3 mb-2">
204
- <div class="w-9 h-9 rounded-full bg-emerald-500/20 flex items-center justify-center">
205
- <span class="text-lg">🎯</span>
206
- </div>
207
- <p class="font-semibold text-sm">Focus on basics that matter</p>
208
- </div>
209
- <p class="text-xs text-slate-300">
210
- Targets the most common issues students face: extra spaces, lowercase “i”,
211
- sentence starts, and missing punctuation.
212
- </p>
213
- </div>
214
-
215
- <div class="bg-slate-900/70 border border-slate-800 rounded-2xl p-4">
216
- <div class="flex items-center gap-3 mb-2">
217
- <div class="w-9 h-9 rounded-full bg-fuchsia-500/20 flex items-center justify-center">
218
- <span class="text-lg">📄</span>
219
- </div>
220
- <p class="font-semibold text-sm">Instant PDF summaries</p>
221
- </div>
222
- <p class="text-xs text-slate-300">
223
- Export a quick PDF report for your records or to attach with your assignment
224
- submissions.
225
- </p>
226
- </div>
227
- </div>
228
- </section>
229
-
230
- <section class="mt-10 space-y-4">
231
- <h2 class="text-xl md:text-2xl font-semibold">Who can use this tool?</h2>
232
- <p class="text-sm text-slate-300">
233
- This grammar checker is designed as a lightweight helper for anyone who wants a final
234
- polish before sending or submitting text.
235
- </p>
236
-
237
- <div class="grid md:grid-cols-3 gap-5 mt-3">
238
- <div class="bg-slate-900/70 border border-slate-800 rounded-2xl p-4">
239
- <div class="flex items-center gap-3 mb-2">
240
- <div class="w-9 h-9 rounded-full bg-sky-500/20 flex items-center justify-center">
241
- <span class="text-lg">👨‍🎓</span>
242
- </div>
243
- <p class="font-semibold text-sm">Students</p>
244
- </div>
245
- <p class="text-xs text-slate-300">
246
- Quickly check lab reports, assignments, mini-projects, and emails to faculty or
247
- supervisors.
248
- </p>
249
- </div>
250
-
251
- <div class="bg-slate-900/70 border border-slate-800 rounded-2xl p-4">
252
- <div class="flex items-center gap-3 mb-2">
253
- <div class="w-9 h-9 rounded-full bg-amber-500/20 flex items-center justify-center">
254
- <span class="text-lg">👩‍🏫</span>
255
- </div>
256
- <p class="font-semibold text-sm">Teachers & mentors</p>
257
- </div>
258
- <p class="text-xs text-slate-300">
259
- Use it as a quick demo in class to show students how common grammar issues can be
260
- auto-detected.
261
- </p>
262
- </div>
263
-
264
- <div class="bg-slate-900/70 border border-slate-800 rounded-2xl p-4">
265
- <div class="flex items-center gap-3 mb-2">
266
- <div class="w-9 h-9 rounded-full bg-rose-500/20 flex items-center justify-center">
267
- <span class="text-lg">💼</span>
268
- </div>
269
- <p class="font-semibold text-sm">Professionals & creators</p>
270
- </div>
271
- <p class="text-xs text-slate-300">
272
- Clean up short posts, descriptions, and emails before sharing them with clients,
273
- teams, or on social media.
274
- </p>
275
- </div>
276
- </div>
277
- </section>
278
-
279
- <!-- How it works (frontend-focused) from file 1 -->
280
- <section class="mt-10 space-y-4">
281
- <h2 class="text-xl md:text-2xl font-semibold">How does this work?</h2>
282
- <p class="text-sm text-slate-300">
283
- Behind the scenes, prefers a neural model (GECToR), then falls back to LanguageTool, and finally to a
284
- lightweight heuristic that do the necessary corrections.
285
- </p>
286
-
287
- <div class="grid md:grid-cols-3 gap-5 mt-3">
288
- <div class="bg-slate-900/70 border border-slate-800 rounded-2xl p-4">
289
- <div class="flex items-center gap-3 mb-2">
290
- <div class="w-9 h-9 rounded-full bg-indigo-500/20 flex items-center justify-center">
291
- <span class="text-lg">1️⃣</span>
292
- </div>
293
- <p class="font-semibold text-sm">Paste & analyse</p>
294
- </div>
295
- <p class="text-xs text-slate-300">
296
- You paste your text (up to ~1000 words). The tool counts words and prepares it for
297
- analysis.
298
- </p>
299
- </div>
300
-
301
- <div class="bg-slate-900/70 border border-slate-800 rounded-2xl p-4">
302
- <div class="flex items-center gap-3 mb-2">
303
- <div class="w-9 h-9 rounded-full bg-emerald-500/20 flex items-center justify-center">
304
- <span class="text-lg">2️⃣</span>
305
- </div>
306
- <p class="font-semibold text-sm">Apply smart rules and models</p>
307
- </div>
308
- <p class="text-xs text-slate-300">
309
- GECToR model rewrites sentences word-by-word, focusing on grammar, agreement, and spelling.
310
- JavaScript rules can fix double spaces, lowercase “i”, sentence capitalization, and
311
- end punctuation. Then the text is sent to a neural grammar model on the server for deeper fixes.
312
- </p>
313
- </div>
314
-
315
- <div class="bg-slate-900/70 border border-slate-800 rounded-2xl p-4">
316
- <div class="flex items-center gap-3 mb-2">
317
- <div class="w-9 h-9 rounded-full bg-fuchsia-500/20 flex items-center justify-center">
318
- <span class="text-lg">3️⃣</span>
319
- </div>
320
- <p class="font-semibold text-sm">Review & export</p>
321
- </div>
322
- <p class="text-xs text-slate-300">
323
- You compare the original and corrected text, then optionally export a PDF summary
324
- for future reference.
325
- </p>
326
- </div>
327
- </div>
328
- </section>
329
-
330
- <!-- Reviews slider from file 1 -->
331
- <section class="mt-12">
332
- <h2 class="text-xl md:text-2xl font-semibold mb-3">What users say about this grammar tool</h2>
333
- <div class="bg-slate-900/80 border border-slate-800 rounded-2xl p-5 md:p-6 relative overflow-hidden">
334
- <div id="reviewCard" class="transition-all duration-500">
335
- <!-- filled by JS -->
336
- </div>
337
-
338
- <div class="flex items-center justify-between mt-4">
339
- <div id="reviewDots" class="flex gap-1.5"></div>
340
- <div class="flex gap-2">
341
- <button id="prevReview"
342
- class="w-8 h-8 rounded-full border border-slate-600 flex items-center justify-center text-xs hover:bg-slate-800">
343
-
344
- </button>
345
- <button id="nextReview"
346
- class="w-8 h-8 rounded-full border border-slate-600 flex items-center justify-center text-xs hover:bg-slate-800">
347
-
348
- </button>
349
- </div>
350
- </div>
351
- </div>
352
- </section>
353
-
354
- </main>
355
-
356
- <!-- Footer -->
357
- <footer class="relative border-t border-slate-800/80 bg-slate-950/70 backdrop-blur">
358
- <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">
359
- <p>© 2025 TrueWrite Scan. All rights reserved.</p>
360
- <p>Designed & developed by <span class="text-emerald-300 font-medium">Gopal Krushna Mahapatra</span>.</p>
361
- </div>
362
- </footer>
363
-
364
- <script>
365
- const BACKEND_URL = "http://localhost:8000";
366
- const token = localStorage.getItem("truewriteToken");
367
- const user = localStorage.getItem("truewriteUser");
368
- if (!token || !user) {
369
- window.location.href = "login.html";
370
- }
371
-
372
- // jsPDF helper (from file 2)
373
- function getJsPDF() {
374
- if (window.jspdf && window.jspdf.jsPDF) return window.jspdf.jsPDF;
375
- if (window.jsPDF) return window.jsPDF;
376
- alert("PDF library (jsPDF) did not load. Check your internet connection or CDN access.");
377
- return null;
378
- }
379
-
380
- // DOM refs (superset)
381
- const dropZone = document.getElementById("dropZone");
382
- const fileInput = document.getElementById("fileInput");
383
- const fileNameSpan = document.getElementById("fileName");
384
- const statusTiny = document.getElementById("statusTiny");
385
- const pasteBtn = document.getElementById("pasteBtn");
386
- const textarea = document.getElementById("inputText");
387
- const checkBtn = document.getElementById("checkBtn");
388
- const checkLabel = document.getElementById("checkLabel");
389
- const spinner = document.getElementById("spinner");
390
- const progressBar = document.getElementById("progressBar");
391
- const progressText = document.getElementById("progressText");
392
- const statWords = document.getElementById("statWords");
393
- const statCorrections = document.getElementById("statCorrections");
394
- const summaryEl = document.getElementById("summary");
395
- const correctionsDiv = document.getElementById("corrections");
396
- const correctedTextEl = document.getElementById("correctedText");
397
- const originalPreview = document.getElementById("originalPreview");
398
- const downloadBtn = document.getElementById("downloadBtn");
399
- const wordCountSpan = document.getElementById("wordCount");
400
-
401
- let lastResult = null;
402
- let progressTimer = null;
403
-
404
- function setLoading(on) {
405
- if (on) {
406
- spinner.classList.remove("hidden");
407
- checkLabel.textContent = "Checking...";
408
- checkBtn.disabled = true;
409
- checkBtn.classList.add("opacity-60", "cursor-not-allowed");
410
-
411
- let pct = 0;
412
- progressBar.style.width = "0%";
413
- progressText.textContent = "Uploading / processing...";
414
- progressTimer = setInterval(() => {
415
- pct += Math.random() * 10;
416
- if (pct > 90) pct = 90;
417
- progressBar.style.width = pct + "%";
418
- }, 200);
419
- } else {
420
- spinner.classList.add("hidden");
421
- checkLabel.textContent = "Check Grammar";
422
- checkBtn.disabled = false;
423
- checkBtn.classList.remove("opacity-60", "cursor-not-allowed");
424
- if (progressTimer) {
425
- clearInterval(progressTimer);
426
- progressTimer = null;
427
- }
428
- progressBar.style.width = "100%";
429
- progressText.textContent = "Done";
430
- setTimeout(() => {
431
- progressBar.style.width = "0%";
432
- progressText.textContent = "Idle";
433
- }, 700);
434
- }
435
- }
436
-
437
- // Word counting & 1000-word indicator (from file 1)
438
- function countWords(text) {
439
- if (!text || !text.trim()) return 0;
440
- return text.trim().split(/\s+/).length;
441
- }
442
-
443
- function updateWordCount() {
444
- const words = countWords(textarea.value);
445
- if (wordCountSpan) {
446
- wordCountSpan.textContent = `${words} / 1000 words`;
447
- }
448
- if (words > 1000) {
449
- statusTiny.textContent = "Text longer than 1000 words — extra words will be ignored by the server.";
450
- }
451
- }
452
-
453
- textarea.addEventListener("input", updateWordCount);
454
-
455
- // Simple local heuristic rules (from file 1)
456
- function applyLocalGrammarRules(rawText) {
457
- let corrections = 0;
458
- let text = rawText || "";
459
-
460
- const beforeDoubleSpace = text;
461
- text = text.replace(/\s{2,}/g, " ");
462
- if (text !== beforeDoubleSpace) corrections++;
463
-
464
- const beforeI = text;
465
- text = text.replace(/\bi\b/g, "I");
466
- if (text !== beforeI) corrections++;
467
-
468
- const beforeSentence = text;
469
- text = text.replace(/(^\s*\w|[.!?]\s+\w)/g, c => c.toUpperCase());
470
- if (text !== beforeSentence) corrections++;
471
-
472
- if (text.trim() && !/[.!?]\s*$/.test(text.trim())) {
473
- text = text.trim() + ".";
474
- corrections++;
475
- }
476
-
477
- return { corrected: text, corrections };
478
- }
479
-
480
- // Drag & drop (from file 2)
481
- ["dragenter", "dragover"].forEach(evt => {
482
- dropZone.addEventListener(evt, e => {
483
- e.preventDefault();
484
- e.stopPropagation();
485
- dropZone.classList.add("border-emerald-400/80", "bg-slate-900/80");
486
- });
487
- });
488
-
489
- ["dragleave", "drop"].forEach(evt => {
490
- dropZone.addEventListener(evt, e => {
491
- e.preventDefault();
492
- e.stopPropagation();
493
- dropZone.classList.remove("border-emerald-400/80", "bg-slate-900/80");
494
- });
495
- });
496
-
497
- dropZone.addEventListener("drop", e => {
498
- const dt = e.dataTransfer;
499
- const files = dt.files;
500
- if (!files || !files.length) return;
501
- const file = files[0];
502
- fileInput.files = files;
503
- handleFileSelected(file);
504
- });
505
-
506
- fileInput.addEventListener("change", e => {
507
- const file = e.target.files[0];
508
- if (!file) {
509
- fileNameSpan.textContent = "";
510
- statusTiny.textContent = "Ready · No file selected";
511
- textarea.value = "";
512
- originalPreview.textContent = "";
513
- updateWordCount();
514
- return;
515
- }
516
- handleFileSelected(file);
517
- });
518
-
519
- function handleFileSelected(file) {
520
- fileNameSpan.textContent = file.name;
521
- const fname = file.name.toLowerCase();
522
- if (file.type === "text/plain" || fname.endsWith(".txt")) {
523
- const reader = new FileReader();
524
- reader.onload = () => {
525
- textarea.value = reader.result;
526
- statusTiny.textContent = `${file.name} loaded as text`;
527
- originalPreview.textContent = textarea.value.slice(0, 1500);
528
- updateWordCount();
529
- };
530
- reader.readAsText(file);
531
- } else {
532
- textarea.value = "";
533
- originalPreview.textContent = "";
534
- statusTiny.textContent = `${file.name} selected. Will be parsed on backend.`;
535
- updateWordCount();
536
- }
537
- }
538
-
539
- // Paste
540
- pasteBtn.onclick = async () => {
541
- try {
542
- const txt = await navigator.clipboard.readText();
543
- textarea.value = txt;
544
- originalPreview.textContent = txt.slice(0, 1500);
545
- statusTiny.textContent = "Text pasted from clipboard";
546
- updateWordCount();
547
- } catch {
548
- alert("Clipboard access blocked by browser.");
549
- }
550
- };
551
-
552
- // API calls (from file 2)
553
- async function callGrammarText(text) {
554
- const res = await fetch(`${BACKEND_URL}/api/grammar-check`, {
555
- method: "POST",
556
- headers: {
557
- "Content-Type": "application/json",
558
- "Authorization": `Bearer ${token}`
559
- },
560
- body: JSON.stringify({ text })
561
- });
562
- const data = await res.json();
563
- if (!res.ok) throw new Error(data.detail || data.error || "Server error");
564
- return data;
565
- }
566
-
567
- async function callGrammarFile(file) {
568
- const form = new FormData();
569
- form.append("file", file, file.name);
570
- const res = await fetch(`${BACKEND_URL}/api/grammar-check-file`, {
571
- method: "POST",
572
- headers: { "Authorization": `Bearer ${token}` },
573
- body: form
574
- });
575
- const data = await res.json();
576
- if (!res.ok) throw new Error(data.detail || data.error || "Server error");
577
- return data;
578
- }
579
-
580
- // Main check: file 2 logic + file 1 fallback
581
- checkBtn.onclick = async () => {
582
- const text = textarea.value.trim();
583
- const file = fileInput.files[0];
584
-
585
- if (!text && !file) {
586
- alert("Please paste text or upload a file first.");
587
- return;
588
- }
589
-
590
- setLoading(true);
591
- summaryEl.textContent = "Checking...";
592
- correctionsDiv.textContent = "";
593
- correctedTextEl.value = "";
594
- statWords.textContent = "0";
595
- statCorrections.textContent = "0";
596
- lastResult = null;
597
-
598
- // Local heuristic used only as fallback
599
- const localWords = countWords(text);
600
- const localTrimmedText = localWords > 1000
601
- ? text.split(/\s+/).slice(0, 1000).join(" ")
602
- : text;
603
- const localRes = applyLocalGrammarRules(localTrimmedText);
604
-
605
- try {
606
- let data;
607
- if (file && !(file.type === "text/plain" || file.name.toLowerCase().endsWith(".txt"))) {
608
- data = await callGrammarFile(file);
609
- } else {
610
- const payloadText = text || (file ? await file.text() : "");
611
- data = await callGrammarText(payloadText);
612
- }
613
-
614
- const words = data.original_words ?? localWords ?? 0;
615
- const corrections = data.corrections ?? 0;
616
- const corrected = data.corrected_text ?? localRes.corrected ?? "";
617
- const summary = data.summary ?? "";
618
-
619
- statWords.textContent = String(words);
620
- statCorrections.textContent = String(corrections);
621
- correctedTextEl.value = corrected;
622
- summaryEl.textContent = summary || "Grammar check completed.";
623
-
624
- correctionsDiv.innerHTML = `
625
- <div>Corrections made (server): <strong>${corrections}</strong></div>
626
- `;
627
-
628
- if (!originalPreview.textContent) {
629
- originalPreview.textContent = (textarea.value || "").slice(0, 1500);
630
- }
631
-
632
- lastResult = {
633
- original: textarea.value || (file ? "[uploaded file]" : ""),
634
- corrected,
635
- words,
636
- corrections,
637
- summary
638
- };
639
- statusTiny.textContent = "Done";
640
- } catch (err) {
641
- console.error(err);
642
- // Fallback to local heuristic result from file 1
643
- const words = localWords;
644
- const corrections = localRes.corrections;
645
- const corrected = localRes.corrected;
646
-
647
- statWords.textContent = String(words);
648
- statCorrections.textContent = String(corrections);
649
- correctedTextEl.value = corrected;
650
- summaryEl.textContent =
651
- "Server error — showing local heuristic corrections instead. " + (err.message || "");
652
-
653
- correctionsDiv.innerHTML = `
654
- <div class="text-xs text-amber-300">Server error: ${escapeHtml(err.message || String(err))}</div>
655
- <div class="mt-2">Local heuristic corrections applied: <strong>${corrections}</strong></div>
656
- `;
657
-
658
- if (!originalPreview.textContent) {
659
- originalPreview.textContent = (textarea.value || "").slice(0, 1500);
660
- }
661
-
662
- lastResult = {
663
- original: textarea.value || (file ? "[uploaded file]" : ""),
664
- corrected,
665
- words,
666
- corrections,
667
- summary: "Local heuristic corrections only"
668
- };
669
- statusTiny.textContent = "Error (using local corrections)";
670
- } finally {
671
- setLoading(false);
672
- }
673
- };
674
-
675
- // PDF download: logic from file 2
676
- downloadBtn.onclick = () => {
677
- if (!lastResult) {
678
- alert("Run at least one grammar check before downloading a report.");
679
- return;
680
- }
681
-
682
- const jsPDF = getJsPDF();
683
- if (!jsPDF) return;
684
-
685
- const doc = new jsPDF({ unit: "pt", format: "a4" });
686
- let y = 40;
687
-
688
- doc.setFontSize(16);
689
- doc.text("TrueWrite Scan — Grammar Report", 40, y); y += 22;
690
-
691
- doc.setFontSize(11);
692
- doc.text("Generated: " + new Date().toLocaleString(), 40, y); y += 18;
693
- doc.text("User: " + (user || "N/A"), 40, y); y += 18;
694
-
695
- doc.setFontSize(12);
696
- doc.text(`Words analysed: ${lastResult.words}`, 40, y); y += 16;
697
- doc.text(`Corrections: ${lastResult.corrections}`, 40, y); y += 20;
698
-
699
- if (lastResult.summary) {
700
- doc.text("Summary:", 40, y); y += 16;
701
- doc.setFontSize(10);
702
- let lines = doc.splitTextToSize(lastResult.summary, 520);
703
- doc.text(lines, 40, y);
704
- y += lines.length * 12 + 10;
705
- }
706
-
707
- doc.setFontSize(11);
708
- doc.text("--- Original text (truncated) ---", 40, y); y += 16;
709
- doc.setFontSize(9);
710
- const original = lastResult.original || "";
711
- const originalTrunc = original.length > 2500 ? original.slice(0, 2500) + "\n\n[TRUNCATED]" : original;
712
- let lines = doc.splitTextToSize(originalTrunc, 520);
713
- doc.text(lines, 40, y);
714
- y += lines.length * 10 + 12;
715
-
716
- doc.setFontSize(11);
717
- doc.text("--- Corrected text (truncated) ---", 40, y); y += 16;
718
- doc.setFontSize(9);
719
- const corrected = lastResult.corrected || "";
720
- const correctedTrunc = corrected.length > 2500 ? corrected.slice(0, 2500) + "\n\n[TRUNCATED]" : corrected;
721
- lines = doc.splitTextToSize(correctedTrunc, 520);
722
- doc.text(lines, 40, y);
723
-
724
- doc.save("truewrite-grammar-report.pdf");
725
- };
726
-
727
- function escapeHtml(str) {
728
- if (!str) return "";
729
- return String(str).replace(/[&<>"'`]/g, s => ({
730
- "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;", "'": "&#39;", "`": "&#96;"
731
- }[s]));
732
- }
733
-
734
- // ===== Reviews slider (from file 1) =====
735
- const reviews = [
736
- {
737
- name: "Aarav S.",
738
- role: "Final-year B.Tech student",
739
- text: "I use this grammar page before every assignment submission. It quickly cleans up the most obvious mistakes.",
740
- stars: 5
741
- },
742
- {
743
- name: "Priya K.",
744
- role: "M.Tech researcher",
745
- text: "The corrected text + PDF report help me keep a record of changes when I work on my thesis chapters.",
746
- stars: 5
747
- },
748
- {
749
- name: "Rahul M.",
750
- role: "Content creator",
751
- text: "Simple, fast, no distractions. Perfect when I just want to sanity-check captions and short posts.",
752
- stars: 4
753
- },
754
- {
755
- name: "Sneha R.",
756
- role: "English learner",
757
- text: "Seeing how my sentences are automatically fixed is helping me understand grammar rules better.",
758
- stars: 5
759
- },
760
- {
761
- name: "Vikram J.",
762
- role: "Developer",
763
- text: "Nice example of a purely front-end grammar tool. The UI feels inspired by popular tools like QuillBot.",
764
- stars: 4
765
- }
766
- ];
767
-
768
- let currentReview = 0;
769
- const reviewCard = document.getElementById("reviewCard");
770
- const reviewDots = document.getElementById("reviewDots");
771
-
772
- function createStarRow(stars) {
773
- let html = "";
774
- for (let i = 0; i < 5; i++) {
775
- html += `<span class="${i < stars ? "text-yellow-400" : "text-slate-600"} text-sm">★</span>`;
776
- }
777
- return html;
778
- }
779
-
780
- function renderReview() {
781
- const r = reviews[currentReview];
782
- reviewCard.innerHTML = `
783
- <div class="flex items-center gap-2 mb-2">
784
- ${createStarRow(r.stars)}
785
- </div>
786
- <p class="text-sm text-slate-200 mb-3">"${r.text}"</p>
787
- <p class="text-sm font-semibold">${r.name}</p>
788
- <p class="text-xs text-slate-400">${r.role}</p>
789
- `;
790
- reviewDots.innerHTML = reviews.map((_, i) =>
791
- `<span class="w-2 h-2 rounded-full ${i === currentReview ? "bg-emerald-400" : "bg-slate-600"}"></span>`
792
- ).join("");
793
- }
794
-
795
- function nextReview() {
796
- currentReview = (currentReview + 1) % reviews.length;
797
- renderReview();
798
- }
799
-
800
- function prevReview() {
801
- currentReview = (currentReview - 1 + reviews.length) % reviews.length;
802
- renderReview();
803
- }
804
-
805
- document.getElementById("nextReview").addEventListener("click", () => {
806
- clearInterval(reviewTimer);
807
- nextReview();
808
- reviewTimer = setInterval(nextReview, 6000);
809
- });
810
- document.getElementById("prevReview").addEventListener("click", () => {
811
- clearInterval(reviewTimer);
812
- prevReview();
813
- reviewTimer = setInterval(nextReview, 6000);
814
- });
815
-
816
- let reviewTimer = setInterval(nextReview, 6000);
817
- renderReview();
818
-
819
- // Initial word count
820
- updateWordCount();
821
- </script>
822
- </body>
823
- </html>
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <title>Grammar 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 SRI issues locally) -->
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-indigo-950 text-white min-h-screen flex flex-col">
12
+ <!-- Glow background -->
13
+ <div class="pointer-events-none fixed inset-0 opacity-40 blur-3xl"
14
+ style="background: radial-gradient(circle at 0% 0%, rgba(59,130,246,0.35), transparent 55%), radial-gradient(circle at 80% 80%, rgba(16,185,129,0.28), transparent 55%);">
15
+ </div>
16
+
17
+ <main class="relative flex-1 max-w-6xl mx-auto px-4 py-8 z-10">
18
+ <!-- Top bar -->
19
+ <div class="flex items-center justify-between mb-6">
20
+ <div class="flex items-center gap-3">
21
+ <div>
22
+ <img src="logo.png" alt="TrueWrite Scan Logo" class="w-10 h-10 rounded-xl shadow-lg shadow-indigo-500/40">
23
+ </div>
24
+ <div>
25
+ <h1 class="text-2xl md:text-3xl font-extrabold tracking-tight">
26
+ TrueWrite <span class="text-emerald-300">Scan</span>
27
+ </h1>
28
+ <p class="text-[11px] text-slate-300 uppercase tracking-[0.25em]">
29
+ Grammar Rectifier
30
+ </p>
31
+ </div>
32
+ </div>
33
+ <a href="dashboard.html"
34
+ 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">
35
+ ← Back to dashboard
36
+ </a>
37
+ </div>
38
+
39
+ <!-- Tool navigation -->
40
+ <nav class="mb-6">
41
+ <div class="inline-flex items-center gap-1 rounded-full bg-slate-900/80 border border-slate-800 p-1 text-xs">
42
+ <a href="grammar.html"
43
+ class="px-3 py-1.5 rounded-full bg-emerald-500 text-white font-medium shadow shadow-emerald-500/40"
44
+ aria-current="page">
45
+ Grammar
46
+ </a>
47
+ <a href="plagiarism.html"
48
+ class="px-3 py-1.5 rounded-full text-slate-300 hover:bg-slate-800">
49
+ Plagiarism
50
+ </a>
51
+ <a href="ai-check.html"
52
+ class="px-3 py-1.5 rounded-full text-slate-300 hover:bg-slate-800">
53
+ AI Content
54
+ </a>
55
+ </div>
56
+ </nav>
57
+
58
+ <div class="grid md:grid-cols-2 gap-6">
59
+ <!-- LEFT: input -->
60
+ <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">
61
+ <header class="mb-4">
62
+ <h2 class="text-lg md:text-xl font-semibold">Input text or upload file</h2>
63
+ <p class="text-xs text-slate-300 mt-1">
64
+ Large files are trimmed server-side for safety. Upload size limit is 15 MB.
65
+ </p>
66
+ </header>
67
+
68
+ <!-- Drag & drop -->
69
+ <div id="dropZone"
70
+ 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-emerald-400/80 hover:bg-slate-900/70">
71
+ <div class="flex items-center gap-3">
72
+ <div class="w-8 h-8 rounded-full bg-slate-800/80 flex items-center justify-center">
73
+ <span class="text-lg">📂</span>
74
+ </div>
75
+ <div>
76
+ <p class="font-medium text-slate-100">Drag & drop your file here</p>
77
+ <p class="text-[11px] text-slate-400">
78
+ Supported: .txt, .pdf, .docx (max 15MB)
79
+ </p>
80
+ </div>
81
+ </div>
82
+ <div class="flex flex-col items-end gap-1">
83
+ <label class="px-3 py-1 rounded-full border border-slate-500/80 text-[11px] cursor-pointer bg-slate-800/60 hover:bg-slate-700/80">
84
+ Browse
85
+ <input id="fileInput" type="file" accept=".txt,.pdf,.docx" class="hidden" />
86
+ </label>
87
+ <span id="fileName" class="text-[10px] text-slate-400 max-w-[150px] truncate"></span>
88
+ </div>
89
+ </div>
90
+
91
+ <div id="statusTiny" class="text-[11px] text-slate-400 mb-2">
92
+ Ready · No file selected
93
+ </div>
94
+
95
+ <!-- Paste / textarea -->
96
+ <div class="flex gap-2 items-center mb-1">
97
+ <button id="pasteBtn"
98
+ class="text-[11px] px-3 py-1 rounded-full border border-slate-600/80 bg-slate-900/70 hover:bg-slate-800/90">
99
+ Paste text from clipboard
100
+ </button>
101
+ <span class="text-[11px] text-slate-500">or type directly below</span>
102
+ </div>
103
+
104
+ <!-- Word count -->
105
+ <div class="flex justify-end mb-2">
106
+ <span id="wordCount" class="text-[11px] text-slate-400">0 / 1000 words</span>
107
+ </div>
108
+
109
+ <textarea id="inputText" rows="10"
110
+ class="flex-1 w-full rounded-2xl bg-slate-950/70 border border-slate-700/80 px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-emerald-500/70 focus:border-emerald-500/60 placeholder:text-slate-500"
111
+ placeholder="Paste your content here or use the drag-and-drop box above..."></textarea>
112
+
113
+ <!-- Buttons -->
114
+ <div class="mt-4 flex flex-wrap gap-2 items-center">
115
+ <button id="checkBtn"
116
+ class="px-5 py-2 rounded-xl bg-emerald-500 hover:bg-emerald-600 text-sm font-semibold shadow-lg shadow-emerald-500/40 flex items-center gap-2">
117
+ <svg id="spinner"
118
+ class="hidden animate-spin h-4 w-4 text-white"
119
+ xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
120
+ <circle class="opacity-25" cx="12" cy="12" r="10"
121
+ stroke="currentColor" stroke-width="4"></circle>
122
+ <path class="opacity-75" fill="currentColor"
123
+ d="M4 12a8 8 0 018-8v4a4 4 0 00-4 4H4z"></path>
124
+ </svg>
125
+ <span id="checkLabel">Check Grammar</span>
126
+ </button>
127
+
128
+ <!-- Download report button: behavior from file 2 -->
129
+ <button id="downloadBtn"
130
+ class="px-5 py-2 rounded-xl bg-slate-900/70 border border-slate-600/80 text-sm font-medium hover:bg-slate-800/90">
131
+ Download report as PDF
132
+ </button>
133
+ </div>
134
+ </section>
135
+
136
+ <!-- RIGHT: result -->
137
+ <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 text-sm">
138
+ <header class="mb-3">
139
+ <h2 class="text-lg font-semibold">Result</h2>
140
+ <p class="text-[11px] text-slate-400">
141
+ Corrected text and statistics appear here after each scan.
142
+ </p>
143
+ </header>
144
+
145
+ <!-- Progress bar -->
146
+ <div class="mb-3">
147
+ <div class="h-1.5 w-full rounded-full bg-slate-800/80 overflow-hidden">
148
+ <div id="progressBar" class="h-full w-0 bg-gradient-to-r from-emerald-400 via-teal-400 to-sky-400 transition-[width] duration-200 ease-out"></div>
149
+ </div>
150
+ <div id="progressText" class="text-[11px] text-slate-400 mt-1">Idle</div>
151
+ </div>
152
+
153
+ <div class="grid gap-3 mb-2">
154
+ <div class="flex flex-wrap gap-4 text-[12px] text-slate-200">
155
+ <div>Words analysed: <span id="statWords" class="font-semibold text-emerald-300">0</span></div>
156
+ <div>Corrections: <span id="statCorrections" class="font-semibold text-emerald-300">0</span></div>
157
+ </div>
158
+ <p id="summary" class="text-[13px] text-slate-200">
159
+ Run a check to see corrected text and a brief summary of edits.
160
+ </p>
161
+ <!-- Extra corrections info area from file 1 -->
162
+ <div id="corrections" class="text-[11px] text-slate-300"></div>
163
+ </div>
164
+
165
+ <div class="flex-1">
166
+ <div class="bg-slate-950/60 border border-slate-800/80 rounded-2xl p-3 flex flex-col h-full">
167
+ <p class="text-[11px] text-slate-400 mb-1">Corrected text</p>
168
+ <textarea id="correctedText"
169
+ class="flex-1 w-full bg-transparent text-xs md:text-[13px] resize-none focus:outline-none min-h-[260px]"
170
+ placeholder="Corrected version will appear here..." readonly></textarea>
171
+ </div>
172
+
173
+ <!-- Hidden original preview holder so JS still works, but nothing is shown -->
174
+ <div id="originalPreview" class="hidden"></div>
175
+ </div>
176
+ </section>
177
+ </div>
178
+
179
+ <!-- ========= Info sections from file 1 ========= -->
180
+
181
+ <section class="mt-10 space-y-4">
182
+ <h2 class="text-xl md:text-2xl font-semibold">Why choose this tool?</h2>
183
+ <p class="text-sm text-slate-300">
184
+ Inspired by tools like QuillBot, this page gives you a simple, focused space to check
185
+ short pieces of writing quickly before you submit or share them.
186
+ </p>
187
+
188
+ <div class="grid md:grid-cols-3 gap-5 mt-3">
189
+ <div class="bg-slate-900/70 border border-slate-800 rounded-2xl p-4">
190
+ <div class="flex items-center gap-3 mb-2">
191
+ <div class="w-9 h-9 rounded-full bg-indigo-500/20 flex items-center justify-center">
192
+ <span class="text-lg">⚡</span>
193
+ </div>
194
+ <p class="font-semibold text-sm">Fast one-click review</p>
195
+ </div>
196
+ <p class="text-xs text-slate-300">
197
+ Paste, click, and instantly get a cleaner version of your text without any sign-up
198
+ or complicated options.
199
+ </p>
200
+ </div>
201
+
202
+ <div class="bg-slate-900/70 border border-slate-800 rounded-2xl p-4">
203
+ <div class="flex items-center gap-3 mb-2">
204
+ <div class="w-9 h-9 rounded-full bg-emerald-500/20 flex items-center justify-center">
205
+ <span class="text-lg">🎯</span>
206
+ </div>
207
+ <p class="font-semibold text-sm">Focus on basics that matter</p>
208
+ </div>
209
+ <p class="text-xs text-slate-300">
210
+ Targets the most common issues students face: extra spaces, lowercase “i”,
211
+ sentence starts, and missing punctuation.
212
+ </p>
213
+ </div>
214
+
215
+ <div class="bg-slate-900/70 border border-slate-800 rounded-2xl p-4">
216
+ <div class="flex items-center gap-3 mb-2">
217
+ <div class="w-9 h-9 rounded-full bg-fuchsia-500/20 flex items-center justify-center">
218
+ <span class="text-lg">📄</span>
219
+ </div>
220
+ <p class="font-semibold text-sm">Instant PDF summaries</p>
221
+ </div>
222
+ <p class="text-xs text-slate-300">
223
+ Export a quick PDF report for your records or to attach with your assignment
224
+ submissions.
225
+ </p>
226
+ </div>
227
+ </div>
228
+ </section>
229
+
230
+ <section class="mt-10 space-y-4">
231
+ <h2 class="text-xl md:text-2xl font-semibold">Who can use this tool?</h2>
232
+ <p class="text-sm text-slate-300">
233
+ This grammar checker is designed as a lightweight helper for anyone who wants a final
234
+ polish before sending or submitting text.
235
+ </p>
236
+
237
+ <div class="grid md:grid-cols-3 gap-5 mt-3">
238
+ <div class="bg-slate-900/70 border border-slate-800 rounded-2xl p-4">
239
+ <div class="flex items-center gap-3 mb-2">
240
+ <div class="w-9 h-9 rounded-full bg-sky-500/20 flex items-center justify-center">
241
+ <span class="text-lg">👨‍🎓</span>
242
+ </div>
243
+ <p class="font-semibold text-sm">Students</p>
244
+ </div>
245
+ <p class="text-xs text-slate-300">
246
+ Quickly check lab reports, assignments, mini-projects, and emails to faculty or
247
+ supervisors.
248
+ </p>
249
+ </div>
250
+
251
+ <div class="bg-slate-900/70 border border-slate-800 rounded-2xl p-4">
252
+ <div class="flex items-center gap-3 mb-2">
253
+ <div class="w-9 h-9 rounded-full bg-amber-500/20 flex items-center justify-center">
254
+ <span class="text-lg">👩‍🏫</span>
255
+ </div>
256
+ <p class="font-semibold text-sm">Teachers & mentors</p>
257
+ </div>
258
+ <p class="text-xs text-slate-300">
259
+ Use it as a quick demo in class to show students how common grammar issues can be
260
+ auto-detected.
261
+ </p>
262
+ </div>
263
+
264
+ <div class="bg-slate-900/70 border border-slate-800 rounded-2xl p-4">
265
+ <div class="flex items-center gap-3 mb-2">
266
+ <div class="w-9 h-9 rounded-full bg-rose-500/20 flex items-center justify-center">
267
+ <span class="text-lg">💼</span>
268
+ </div>
269
+ <p class="font-semibold text-sm">Professionals & creators</p>
270
+ </div>
271
+ <p class="text-xs text-slate-300">
272
+ Clean up short posts, descriptions, and emails before sharing them with clients,
273
+ teams, or on social media.
274
+ </p>
275
+ </div>
276
+ </div>
277
+ </section>
278
+
279
+ <!-- How it works (frontend-focused) from file 1 -->
280
+ <section class="mt-10 space-y-4">
281
+ <h2 class="text-xl md:text-2xl font-semibold">How does this work?</h2>
282
+ <p class="text-sm text-slate-300">
283
+ Behind the scenes, prefers a neural model (GECToR), then falls back to LanguageTool, and finally to a
284
+ lightweight heuristic that do the necessary corrections.
285
+ </p>
286
+
287
+ <div class="grid md:grid-cols-3 gap-5 mt-3">
288
+ <div class="bg-slate-900/70 border border-slate-800 rounded-2xl p-4">
289
+ <div class="flex items-center gap-3 mb-2">
290
+ <div class="w-9 h-9 rounded-full bg-indigo-500/20 flex items-center justify-center">
291
+ <span class="text-lg">1️⃣</span>
292
+ </div>
293
+ <p class="font-semibold text-sm">Paste & analyse</p>
294
+ </div>
295
+ <p class="text-xs text-slate-300">
296
+ You paste your text (up to ~1000 words). The tool counts words and prepares it for
297
+ analysis.
298
+ </p>
299
+ </div>
300
+
301
+ <div class="bg-slate-900/70 border border-slate-800 rounded-2xl p-4">
302
+ <div class="flex items-center gap-3 mb-2">
303
+ <div class="w-9 h-9 rounded-full bg-emerald-500/20 flex items-center justify-center">
304
+ <span class="text-lg">2️⃣</span>
305
+ </div>
306
+ <p class="font-semibold text-sm">Apply smart rules and models</p>
307
+ </div>
308
+ <p class="text-xs text-slate-300">
309
+ GECToR model rewrites sentences word-by-word, focusing on grammar, agreement, and spelling.
310
+ JavaScript rules can fix double spaces, lowercase “i”, sentence capitalization, and
311
+ end punctuation. Then the text is sent to a neural grammar model on the server for deeper fixes.
312
+ </p>
313
+ </div>
314
+
315
+ <div class="bg-slate-900/70 border border-slate-800 rounded-2xl p-4">
316
+ <div class="flex items-center gap-3 mb-2">
317
+ <div class="w-9 h-9 rounded-full bg-fuchsia-500/20 flex items-center justify-center">
318
+ <span class="text-lg">3️⃣</span>
319
+ </div>
320
+ <p class="font-semibold text-sm">Review & export</p>
321
+ </div>
322
+ <p class="text-xs text-slate-300">
323
+ You compare the original and corrected text, then optionally export a PDF summary
324
+ for future reference.
325
+ </p>
326
+ </div>
327
+ </div>
328
+ </section>
329
+
330
+ <!-- Reviews slider from file 1 -->
331
+ <section class="mt-12">
332
+ <h2 class="text-xl md:text-2xl font-semibold mb-3">What users say about this grammar tool</h2>
333
+ <div class="bg-slate-900/80 border border-slate-800 rounded-2xl p-5 md:p-6 relative overflow-hidden">
334
+ <div id="reviewCard" class="transition-all duration-500">
335
+ <!-- filled by JS -->
336
+ </div>
337
+
338
+ <div class="flex items-center justify-between mt-4">
339
+ <div id="reviewDots" class="flex gap-1.5"></div>
340
+ <div class="flex gap-2">
341
+ <button id="prevReview"
342
+ class="w-8 h-8 rounded-full border border-slate-600 flex items-center justify-center text-xs hover:bg-slate-800">
343
+
344
+ </button>
345
+ <button id="nextReview"
346
+ class="w-8 h-8 rounded-full border border-slate-600 flex items-center justify-center text-xs hover:bg-slate-800">
347
+
348
+ </button>
349
+ </div>
350
+ </div>
351
+ </div>
352
+ </section>
353
+
354
+ </main>
355
+
356
+ <!-- Footer -->
357
+ <footer class="relative border-t border-slate-800/80 bg-slate-950/70 backdrop-blur">
358
+ <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">
359
+ <p>© 2025 TrueWrite Scan. All rights reserved.</p>
360
+ <p>Designed & developed by <span class="text-emerald-300 font-medium">Gopal Krushna Mahapatra</span>.</p>
361
+ </div>
362
+ </footer>
363
+
364
+ <script>
365
+ const BACKEND_URL = "https://gopalkrushnamahapatra-truewrite-scan.hf.space";
366
+ const token = localStorage.getItem("truewriteToken");
367
+ const user = localStorage.getItem("truewriteUser");
368
+ if (!token || !user) {
369
+ window.location.href = "login.html";
370
+ }
371
+
372
+ // jsPDF helper (from file 2)
373
+ function getJsPDF() {
374
+ if (window.jspdf && window.jspdf.jsPDF) return window.jspdf.jsPDF;
375
+ if (window.jsPDF) return window.jsPDF;
376
+ alert("PDF library (jsPDF) did not load. Check your internet connection or CDN access.");
377
+ return null;
378
+ }
379
+
380
+ // DOM refs (superset)
381
+ const dropZone = document.getElementById("dropZone");
382
+ const fileInput = document.getElementById("fileInput");
383
+ const fileNameSpan = document.getElementById("fileName");
384
+ const statusTiny = document.getElementById("statusTiny");
385
+ const pasteBtn = document.getElementById("pasteBtn");
386
+ const textarea = document.getElementById("inputText");
387
+ const checkBtn = document.getElementById("checkBtn");
388
+ const checkLabel = document.getElementById("checkLabel");
389
+ const spinner = document.getElementById("spinner");
390
+ const progressBar = document.getElementById("progressBar");
391
+ const progressText = document.getElementById("progressText");
392
+ const statWords = document.getElementById("statWords");
393
+ const statCorrections = document.getElementById("statCorrections");
394
+ const summaryEl = document.getElementById("summary");
395
+ const correctionsDiv = document.getElementById("corrections");
396
+ const correctedTextEl = document.getElementById("correctedText");
397
+ const originalPreview = document.getElementById("originalPreview");
398
+ const downloadBtn = document.getElementById("downloadBtn");
399
+ const wordCountSpan = document.getElementById("wordCount");
400
+
401
+ let lastResult = null;
402
+ let progressTimer = null;
403
+
404
+ function setLoading(on) {
405
+ if (on) {
406
+ spinner.classList.remove("hidden");
407
+ checkLabel.textContent = "Checking...";
408
+ checkBtn.disabled = true;
409
+ checkBtn.classList.add("opacity-60", "cursor-not-allowed");
410
+
411
+ let pct = 0;
412
+ progressBar.style.width = "0%";
413
+ progressText.textContent = "Uploading / processing...";
414
+ progressTimer = setInterval(() => {
415
+ pct += Math.random() * 10;
416
+ if (pct > 90) pct = 90;
417
+ progressBar.style.width = pct + "%";
418
+ }, 200);
419
+ } else {
420
+ spinner.classList.add("hidden");
421
+ checkLabel.textContent = "Check Grammar";
422
+ checkBtn.disabled = false;
423
+ checkBtn.classList.remove("opacity-60", "cursor-not-allowed");
424
+ if (progressTimer) {
425
+ clearInterval(progressTimer);
426
+ progressTimer = null;
427
+ }
428
+ progressBar.style.width = "100%";
429
+ progressText.textContent = "Done";
430
+ setTimeout(() => {
431
+ progressBar.style.width = "0%";
432
+ progressText.textContent = "Idle";
433
+ }, 700);
434
+ }
435
+ }
436
+
437
+ // Word counting & 1000-word indicator (from file 1)
438
+ function countWords(text) {
439
+ if (!text || !text.trim()) return 0;
440
+ return text.trim().split(/\s+/).length;
441
+ }
442
+
443
+ function updateWordCount() {
444
+ const words = countWords(textarea.value);
445
+ if (wordCountSpan) {
446
+ wordCountSpan.textContent = `${words} / 1000 words`;
447
+ }
448
+ if (words > 1000) {
449
+ statusTiny.textContent = "Text longer than 1000 words — extra words will be ignored by the server.";
450
+ }
451
+ }
452
+
453
+ textarea.addEventListener("input", updateWordCount);
454
+
455
+ // Simple local heuristic rules (from file 1)
456
+ function applyLocalGrammarRules(rawText) {
457
+ let corrections = 0;
458
+ let text = rawText || "";
459
+
460
+ const beforeDoubleSpace = text;
461
+ text = text.replace(/\s{2,}/g, " ");
462
+ if (text !== beforeDoubleSpace) corrections++;
463
+
464
+ const beforeI = text;
465
+ text = text.replace(/\bi\b/g, "I");
466
+ if (text !== beforeI) corrections++;
467
+
468
+ const beforeSentence = text;
469
+ text = text.replace(/(^\s*\w|[.!?]\s+\w)/g, c => c.toUpperCase());
470
+ if (text !== beforeSentence) corrections++;
471
+
472
+ if (text.trim() && !/[.!?]\s*$/.test(text.trim())) {
473
+ text = text.trim() + ".";
474
+ corrections++;
475
+ }
476
+
477
+ return { corrected: text, corrections };
478
+ }
479
+
480
+ // Drag & drop (from file 2)
481
+ ["dragenter", "dragover"].forEach(evt => {
482
+ dropZone.addEventListener(evt, e => {
483
+ e.preventDefault();
484
+ e.stopPropagation();
485
+ dropZone.classList.add("border-emerald-400/80", "bg-slate-900/80");
486
+ });
487
+ });
488
+
489
+ ["dragleave", "drop"].forEach(evt => {
490
+ dropZone.addEventListener(evt, e => {
491
+ e.preventDefault();
492
+ e.stopPropagation();
493
+ dropZone.classList.remove("border-emerald-400/80", "bg-slate-900/80");
494
+ });
495
+ });
496
+
497
+ dropZone.addEventListener("drop", e => {
498
+ const dt = e.dataTransfer;
499
+ const files = dt.files;
500
+ if (!files || !files.length) return;
501
+ const file = files[0];
502
+ fileInput.files = files;
503
+ handleFileSelected(file);
504
+ });
505
+
506
+ fileInput.addEventListener("change", e => {
507
+ const file = e.target.files[0];
508
+ if (!file) {
509
+ fileNameSpan.textContent = "";
510
+ statusTiny.textContent = "Ready · No file selected";
511
+ textarea.value = "";
512
+ originalPreview.textContent = "";
513
+ updateWordCount();
514
+ return;
515
+ }
516
+ handleFileSelected(file);
517
+ });
518
+
519
+ function handleFileSelected(file) {
520
+ fileNameSpan.textContent = file.name;
521
+ const fname = file.name.toLowerCase();
522
+ if (file.type === "text/plain" || fname.endsWith(".txt")) {
523
+ const reader = new FileReader();
524
+ reader.onload = () => {
525
+ textarea.value = reader.result;
526
+ statusTiny.textContent = `${file.name} loaded as text`;
527
+ originalPreview.textContent = textarea.value.slice(0, 1500);
528
+ updateWordCount();
529
+ };
530
+ reader.readAsText(file);
531
+ } else {
532
+ textarea.value = "";
533
+ originalPreview.textContent = "";
534
+ statusTiny.textContent = `${file.name} selected. Will be parsed on backend.`;
535
+ updateWordCount();
536
+ }
537
+ }
538
+
539
+ // Paste
540
+ pasteBtn.onclick = async () => {
541
+ try {
542
+ const txt = await navigator.clipboard.readText();
543
+ textarea.value = txt;
544
+ originalPreview.textContent = txt.slice(0, 1500);
545
+ statusTiny.textContent = "Text pasted from clipboard";
546
+ updateWordCount();
547
+ } catch {
548
+ alert("Clipboard access blocked by browser.");
549
+ }
550
+ };
551
+
552
+ // API calls (from file 2)
553
+ async function callGrammarText(text) {
554
+ const res = await fetch(`${BACKEND_URL}/api/grammar-check`, {
555
+ method: "POST",
556
+ headers: {
557
+ "Content-Type": "application/json",
558
+ "Authorization": `Bearer ${token}`
559
+ },
560
+ body: JSON.stringify({ text })
561
+ });
562
+ const data = await res.json();
563
+ if (!res.ok) throw new Error(data.detail || data.error || "Server error");
564
+ return data;
565
+ }
566
+
567
+ async function callGrammarFile(file) {
568
+ const form = new FormData();
569
+ form.append("file", file, file.name);
570
+ const res = await fetch(`${BACKEND_URL}/api/grammar-check-file`, {
571
+ method: "POST",
572
+ headers: { "Authorization": `Bearer ${token}` },
573
+ body: form
574
+ });
575
+ const data = await res.json();
576
+ if (!res.ok) throw new Error(data.detail || data.error || "Server error");
577
+ return data;
578
+ }
579
+
580
+ // Main check: file 2 logic + file 1 fallback
581
+ checkBtn.onclick = async () => {
582
+ const text = textarea.value.trim();
583
+ const file = fileInput.files[0];
584
+
585
+ if (!text && !file) {
586
+ alert("Please paste text or upload a file first.");
587
+ return;
588
+ }
589
+
590
+ setLoading(true);
591
+ summaryEl.textContent = "Checking...";
592
+ correctionsDiv.textContent = "";
593
+ correctedTextEl.value = "";
594
+ statWords.textContent = "0";
595
+ statCorrections.textContent = "0";
596
+ lastResult = null;
597
+
598
+ // Local heuristic used only as fallback
599
+ const localWords = countWords(text);
600
+ const localTrimmedText = localWords > 1000
601
+ ? text.split(/\s+/).slice(0, 1000).join(" ")
602
+ : text;
603
+ const localRes = applyLocalGrammarRules(localTrimmedText);
604
+
605
+ try {
606
+ let data;
607
+ if (file && !(file.type === "text/plain" || file.name.toLowerCase().endsWith(".txt"))) {
608
+ data = await callGrammarFile(file);
609
+ } else {
610
+ const payloadText = text || (file ? await file.text() : "");
611
+ data = await callGrammarText(payloadText);
612
+ }
613
+
614
+ const words = data.original_words ?? localWords ?? 0;
615
+ const corrections = data.corrections ?? 0;
616
+ const corrected = data.corrected_text ?? localRes.corrected ?? "";
617
+ const summary = data.summary ?? "";
618
+
619
+ statWords.textContent = String(words);
620
+ statCorrections.textContent = String(corrections);
621
+ correctedTextEl.value = corrected;
622
+ summaryEl.textContent = summary || "Grammar check completed.";
623
+
624
+ correctionsDiv.innerHTML = `
625
+ <div>Corrections made (server): <strong>${corrections}</strong></div>
626
+ `;
627
+
628
+ if (!originalPreview.textContent) {
629
+ originalPreview.textContent = (textarea.value || "").slice(0, 1500);
630
+ }
631
+
632
+ lastResult = {
633
+ original: textarea.value || (file ? "[uploaded file]" : ""),
634
+ corrected,
635
+ words,
636
+ corrections,
637
+ summary
638
+ };
639
+ statusTiny.textContent = "Done";
640
+ } catch (err) {
641
+ console.error(err);
642
+ // Fallback to local heuristic result from file 1
643
+ const words = localWords;
644
+ const corrections = localRes.corrections;
645
+ const corrected = localRes.corrected;
646
+
647
+ statWords.textContent = String(words);
648
+ statCorrections.textContent = String(corrections);
649
+ correctedTextEl.value = corrected;
650
+ summaryEl.textContent =
651
+ "Server error — showing local heuristic corrections instead. " + (err.message || "");
652
+
653
+ correctionsDiv.innerHTML = `
654
+ <div class="text-xs text-amber-300">Server error: ${escapeHtml(err.message || String(err))}</div>
655
+ <div class="mt-2">Local heuristic corrections applied: <strong>${corrections}</strong></div>
656
+ `;
657
+
658
+ if (!originalPreview.textContent) {
659
+ originalPreview.textContent = (textarea.value || "").slice(0, 1500);
660
+ }
661
+
662
+ lastResult = {
663
+ original: textarea.value || (file ? "[uploaded file]" : ""),
664
+ corrected,
665
+ words,
666
+ corrections,
667
+ summary: "Local heuristic corrections only"
668
+ };
669
+ statusTiny.textContent = "Error (using local corrections)";
670
+ } finally {
671
+ setLoading(false);
672
+ }
673
+ };
674
+
675
+ // PDF download: logic from file 2
676
+ downloadBtn.onclick = () => {
677
+ if (!lastResult) {
678
+ alert("Run at least one grammar check before downloading a report.");
679
+ return;
680
+ }
681
+
682
+ const jsPDF = getJsPDF();
683
+ if (!jsPDF) return;
684
+
685
+ const doc = new jsPDF({ unit: "pt", format: "a4" });
686
+ let y = 40;
687
+
688
+ doc.setFontSize(16);
689
+ doc.text("TrueWrite Scan — Grammar Report", 40, y); y += 22;
690
+
691
+ doc.setFontSize(11);
692
+ doc.text("Generated: " + new Date().toLocaleString(), 40, y); y += 18;
693
+ doc.text("User: " + (user || "N/A"), 40, y); y += 18;
694
+
695
+ doc.setFontSize(12);
696
+ doc.text(`Words analysed: ${lastResult.words}`, 40, y); y += 16;
697
+ doc.text(`Corrections: ${lastResult.corrections}`, 40, y); y += 20;
698
+
699
+ if (lastResult.summary) {
700
+ doc.text("Summary:", 40, y); y += 16;
701
+ doc.setFontSize(10);
702
+ let lines = doc.splitTextToSize(lastResult.summary, 520);
703
+ doc.text(lines, 40, y);
704
+ y += lines.length * 12 + 10;
705
+ }
706
+
707
+ doc.setFontSize(11);
708
+ doc.text("--- Original text (truncated) ---", 40, y); y += 16;
709
+ doc.setFontSize(9);
710
+ const original = lastResult.original || "";
711
+ const originalTrunc = original.length > 2500 ? original.slice(0, 2500) + "\n\n[TRUNCATED]" : original;
712
+ let lines = doc.splitTextToSize(originalTrunc, 520);
713
+ doc.text(lines, 40, y);
714
+ y += lines.length * 10 + 12;
715
+
716
+ doc.setFontSize(11);
717
+ doc.text("--- Corrected text (truncated) ---", 40, y); y += 16;
718
+ doc.setFontSize(9);
719
+ const corrected = lastResult.corrected || "";
720
+ const correctedTrunc = corrected.length > 2500 ? corrected.slice(0, 2500) + "\n\n[TRUNCATED]" : corrected;
721
+ lines = doc.splitTextToSize(correctedTrunc, 520);
722
+ doc.text(lines, 40, y);
723
+
724
+ doc.save("truewrite-grammar-report.pdf");
725
+ };
726
+
727
+ function escapeHtml(str) {
728
+ if (!str) return "";
729
+ return String(str).replace(/[&<>"'`]/g, s => ({
730
+ "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;", "'": "&#39;", "`": "&#96;"
731
+ }[s]));
732
+ }
733
+
734
+ // ===== Reviews slider (from file 1) =====
735
+ const reviews = [
736
+ {
737
+ name: "Aarav S.",
738
+ role: "Final-year B.Tech student",
739
+ text: "I use this grammar page before every assignment submission. It quickly cleans up the most obvious mistakes.",
740
+ stars: 5
741
+ },
742
+ {
743
+ name: "Priya K.",
744
+ role: "M.Tech researcher",
745
+ text: "The corrected text + PDF report help me keep a record of changes when I work on my thesis chapters.",
746
+ stars: 5
747
+ },
748
+ {
749
+ name: "Rahul M.",
750
+ role: "Content creator",
751
+ text: "Simple, fast, no distractions. Perfect when I just want to sanity-check captions and short posts.",
752
+ stars: 4
753
+ },
754
+ {
755
+ name: "Sneha R.",
756
+ role: "English learner",
757
+ text: "Seeing how my sentences are automatically fixed is helping me understand grammar rules better.",
758
+ stars: 5
759
+ },
760
+ {
761
+ name: "Vikram J.",
762
+ role: "Developer",
763
+ text: "Nice example of a purely front-end grammar tool. The UI feels inspired by popular tools like QuillBot.",
764
+ stars: 4
765
+ }
766
+ ];
767
+
768
+ let currentReview = 0;
769
+ const reviewCard = document.getElementById("reviewCard");
770
+ const reviewDots = document.getElementById("reviewDots");
771
+
772
+ function createStarRow(stars) {
773
+ let html = "";
774
+ for (let i = 0; i < 5; i++) {
775
+ html += `<span class="${i < stars ? "text-yellow-400" : "text-slate-600"} text-sm">★</span>`;
776
+ }
777
+ return html;
778
+ }
779
+
780
+ function renderReview() {
781
+ const r = reviews[currentReview];
782
+ reviewCard.innerHTML = `
783
+ <div class="flex items-center gap-2 mb-2">
784
+ ${createStarRow(r.stars)}
785
+ </div>
786
+ <p class="text-sm text-slate-200 mb-3">"${r.text}"</p>
787
+ <p class="text-sm font-semibold">${r.name}</p>
788
+ <p class="text-xs text-slate-400">${r.role}</p>
789
+ `;
790
+ reviewDots.innerHTML = reviews.map((_, i) =>
791
+ `<span class="w-2 h-2 rounded-full ${i === currentReview ? "bg-emerald-400" : "bg-slate-600"}"></span>`
792
+ ).join("");
793
+ }
794
+
795
+ function nextReview() {
796
+ currentReview = (currentReview + 1) % reviews.length;
797
+ renderReview();
798
+ }
799
+
800
+ function prevReview() {
801
+ currentReview = (currentReview - 1 + reviews.length) % reviews.length;
802
+ renderReview();
803
+ }
804
+
805
+ document.getElementById("nextReview").addEventListener("click", () => {
806
+ clearInterval(reviewTimer);
807
+ nextReview();
808
+ reviewTimer = setInterval(nextReview, 6000);
809
+ });
810
+ document.getElementById("prevReview").addEventListener("click", () => {
811
+ clearInterval(reviewTimer);
812
+ prevReview();
813
+ reviewTimer = setInterval(nextReview, 6000);
814
+ });
815
+
816
+ let reviewTimer = setInterval(nextReview, 6000);
817
+ renderReview();
818
+
819
+ // Initial word count
820
+ updateWordCount();
821
+ </script>
822
+ </body>
823
+ </html>