TheFrenchDemos commited on
Commit
781bbad
·
verified ·
1 Parent(s): 1704402

version samedi 3 mai 17:57

Browse files
Files changed (1) hide show
  1. templates/index.html +185 -286
templates/index.html CHANGED
@@ -21,116 +21,57 @@
21
  background: var(--apple-gray);
22
  color: #1c1c1e;
23
  }
24
- h1,
25
- h2,
26
- h5 {
27
- font-weight: 600;
28
- }
29
  /* thumbnails */
30
- .preset-image-container {
31
- overflow: hidden;
32
- margin-bottom: 12px;
33
- }
34
  .preset-image {
35
- width: 100%;
36
- border-radius: 12px;
37
- border: 2px solid transparent;
38
- box-shadow: var(--apple-shadow);
39
- transition: transform 0.3s ease, border-color 0.3s ease;
40
- cursor: pointer;
41
- }
42
- .preset-image:hover {
43
- transform: scale(1.05);
44
- }
45
- .preset-image.selected {
46
- border-color: var(--apple-blue);
47
  }
 
 
48
  /* dropzone */
49
  .dropzone {
50
- border: 2px dashed var(--apple-border);
51
- border-radius: 16px;
52
- background: #fff;
53
- padding: 20px;
54
- text-align: center;
55
- box-shadow: var(--apple-shadow);
56
  }
57
  /* preview */
58
  .preview-image {
59
- max-width: 220px;
60
- border-radius: 16px;
61
- box-shadow: var(--apple-shadow);
62
- opacity: 0;
63
- transition: opacity 0.4s ease;
64
- }
65
- .preview-image.visible {
66
- opacity: 1;
67
- }
68
- /* buttons */
69
- .btn-primary {
70
- background: var(--apple-blue);
71
- border: none;
72
- font-weight: 500;
73
- }
74
- .btn-primary:hover {
75
- background: #005ecb;
76
  }
 
 
 
 
 
 
 
77
  /* gauge */
78
  .jauge-container {
79
- display: none;
80
- align-items: center;
81
- gap: 24px;
82
- margin-top: 24px;
83
  }
84
  .jauge {
85
- width: 24px;
86
- height: 160px;
87
- border-radius: 12px;
88
- background: #e5e5ea;
89
- position: relative;
90
- overflow: hidden;
91
- border: 1px solid var(--apple-border);
92
  }
93
  .jauge-level {
94
- position: absolute;
95
- bottom: 0;
96
- width: 100%;
97
- border-radius: 12px;
98
- background: var(--apple-blue);
99
- transition: height 0.6s ease, background 0.3s ease;
100
  }
101
  .jauge-labels {
102
- font-size: 0.7rem;
103
- color: #636366;
104
- display: flex;
105
- flex-direction: column;
106
- justify-content: space-between;
107
- height: 160px;
108
  }
109
  /* icon column */
110
  .icon-buttons {
111
- display: none;
112
- flex-direction: column;
113
- gap: 16px;
114
- justify-content: center;
115
- align-items: center;
116
- margin-left: 12px;
117
- }
118
- .icon-buttons button {
119
- background: none;
120
- border: none;
121
- color: var(--apple-blue);
122
- font-size: 24px;
123
- cursor: pointer;
124
  }
 
125
  /* section hidden until first preview shown */
126
- #compareSection {
127
- margin-top: 40px;
128
- text-align: center;
129
- display: none;
130
- }
131
  /* modal */
132
- .modal-header{border-bottom:1px solid var(--apple-border)}
133
- .modal-footer{border-top:1px solid var(--apple-border)}
134
  </style>
135
  </head>
136
  <body>
@@ -149,12 +90,13 @@
149
  <div class="col-4"><div class="preset-image-container"><img id="preset-3-1" src="/static/muhammad ali_1.jpg" class="preset-image" onclick="selectPreset(3,1)" alt="Original 3" /></div></div>
150
  </div>
151
  <div class="dropzone mt-3" id="dropzone1">
152
- <p class="mb-2">Drag & Drop or Upload (.jpg/.png/.webp)</p>
153
  <input type="file" id="fileInput1" accept="image/*" hidden />
154
  <button class="btn btn-outline-secondary" onclick="document.getElementById('fileInput1').click()">Upload</button>
155
  </div>
156
  </div>
157
- <!-- AI -->
 
158
  <div class="col-md-6">
159
  <h5 class="text-center mb-3">Select AI Generated Image</h5>
160
  <div class="row">
@@ -163,7 +105,7 @@
163
  <div class="col-4"><div class="preset-image-container"><img id="preset-3-2" src="/static/muhammad ali_2.png" class="preset-image" onclick="selectPreset(3,2)" alt="AI 3" /></div></div>
164
  </div>
165
  <div class="dropzone mt-3" id="dropzone2">
166
- <p class="mb-2">Drag & Drop or Upload (.jpg/.png/.webp)</p>
167
  <input type="file" id="fileInput2" accept="image/*" hidden />
168
  <button class="btn btn-outline-secondary" onclick="document.getElementById('fileInput2').click()">Upload</button>
169
  </div>
@@ -187,11 +129,9 @@
187
  <span>most certainly</span><span>very probably</span><span>probably</span><span>possibly</span><span>probably not</span><span>definitely not</span>
188
  </div>
189
  </div>
190
- <!-- boutons de PDF -->
191
  <div class="icon-buttons" id="iconButtons">
192
  <button title="Print" onclick="generatePDF1()"><i class="fas fa-print"></i></button>
193
  <button title="Certificate" data-bs-toggle="modal" data-bs-target="#certModal"><i class="fas fa-certificate"></i></button>
194
-
195
  </div>
196
  </div>
197
  </div>
@@ -202,6 +142,7 @@
202
  <div id="results" class="mt-4"></div>
203
  </div>
204
  </div>
 
205
  <!-- Certificate modal -->
206
  <div class="modal fade" id="certModal" tabindex="-1" aria-hidden="true">
207
  <div class="modal-dialog modal-dialog-centered">
@@ -214,15 +155,31 @@
214
  <div class="modal-body">
215
  <div class="row g-3">
216
  <div class="col-md-6">
217
- <label class="form-label">First Name</label>
218
  <input id="firstName" class="form-control" required>
219
  </div>
220
  <div class="col-md-6">
221
- <label class="form-label">Last Name</label>
222
  <input id="lastName" class="form-control" required>
223
  </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
224
  <div class="col-12">
225
- <label class="form-label">Generative AI</label>
226
  <select id="aiModel" class="form-select" required>
227
  <option value="" selected disabled>Select…</option>
228
  <option>Adobe Firefly Image Model 4</option>
@@ -237,7 +194,7 @@
237
  <option>NightCafe Creator</option>
238
  <option>Playground AI</option>
239
  <option>Runway Gen‑3 Alpha</option>
240
- <option>Stable Diffusion 3</option>
241
  <option>Other</option>
242
  </select>
243
  </div>
@@ -254,213 +211,154 @@
254
  </div>
255
  </div>
256
  </div>
 
257
  <div class="modal-footer">
258
  <button type="button" class="btn btn-outline-secondary" data-bs-dismiss="modal">Cancel</button>
259
- <button type="submit" class="btn btn-primary">Validate &amp; Generate PDF</button>
260
  </div>
261
  </form>
262
  </div>
263
  </div>
264
  </div>
 
 
265
  <script>
266
- let file1 = null,
267
- file2 = null;
268
- /* formulaire certificat */
269
- document.getElementById('certForm').addEventListener('submit',e=>{
270
- e.preventDefault();
271
- certData={
272
- first:document.getElementById('firstName').value.trim(),
273
- last:document.getElementById('lastName').value.trim(),
274
- ai:document.getElementById('aiModel').value,
275
- prompt:document.getElementById('promptText').value.trim()
276
- };
277
- bootstrap.Modal.getInstance(document.getElementById('certModal')).hide();
278
- generatePDF2();
279
- });
280
- /* PDF2 : certificat avec les infos du formulaire */
281
- async function generatePDF2 () {
282
- const { jsPDF } = window.jspdf;
283
- const pdf = new jsPDF({ unit : 'pt', format : 'a4' });
284
- /* ── page metrics ─────────────────────────────────── */
285
- const pageW = pdf.internal.pageSize.getWidth();
286
- const margin = 40;
287
- const usableW = pageW - margin * 2;
288
- let y = 80;
289
- /* ── header ───────────────────────────────────────── */
290
- pdf.setFont('helvetica', 'normal');
291
- pdf.setFontSize(26);
292
- pdf.text('Certification', pageW / 2, y, { align : 'center' });
293
- y += 22;
294
- pdf.setDrawColor('#d1d1d6');
295
- pdf.line(margin, y, pageW - margin, y); /* thin divider */
296
- y += 32;
297
- /* ── visual block (images + gauge + score) ────────── */
298
- const section = document.querySelector('#compareSection .d-flex');
299
- const clone = section.cloneNode(true);
300
- clone.querySelectorAll('button').forEach(b => (b.style.display = 'none'));
301
- document.body.appendChild(clone);
302
- clone.style.cssText = 'position:absolute;left:-9999px;top:0';
303
- const canvas = await html2canvas(clone, { backgroundColor : null });
304
- document.body.removeChild(clone);
305
- const imgData = canvas.toDataURL('image/png');
306
- const props = pdf.getImageProperties(imgData);
307
- const imgH = (props.height * usableW) / props.width;
308
- pdf.addImage(imgData, 'PNG', margin, y, usableW, imgH);
309
- y += imgH + 40;
310
- /* ── certificate text ─────────────────────────────── */
311
- pdf.setFontSize(12);
312
- pdf.text('I hereby certify that the original picture shown on the left is my own work and that', margin, y);
313
- y += 20;
314
- pdf.text('the AI‑generated picture shown on the right was produced by the generative AI', margin, y);
315
- y += 20;
316
- pdf.text('indicated below, using the prompt that follows.', margin, y);
317
- y += 50;
318
- const indentX = 150; // tabulation pour les valeurs saisies
319
-
320
- /* generative AI (label regular, value italic) */
321
- pdf.setFont('helvetica', 'normal');
322
- pdf.text('Generative AI:', margin + 50, y);
323
- pdf.setFont('helvetica', 'italic');
324
- pdf.text(certData.ai, margin + indentX, y);
325
- y += 30;
326
- /* prompt */
327
- const promptMaxWidth = usableW - indentX; // largeur réellement disponible
328
-
329
- pdf.setFont('helvetica', 'normal');
330
- pdf.text('Prompt:', margin + 50, y);
331
- pdf.setFont('helvetica', 'italic');
332
- const promptLines = pdf.splitTextToSize(certData.prompt, promptMaxWidth);
333
- pdf.text(promptLines, margin + indentX, y); // même retrait pour toutes les lignes
334
- y += promptLines.length * 14 + 20;
335
- /* signer’s name */
336
- pdf.setFont('helvetica', 'normal');
337
- pdf.text('Name:', margin + 50, y);
338
- pdf.setFont('helvetica', 'italic');
339
- pdf.text(` ${certData.first} ${certData.last}`,margin + indentX,y);
340
- y += 30;
341
- /* date */
342
- const now = new Date().toLocaleString();
343
- pdf.setFont('helvetica', 'normal');
344
- pdf.text('Date:', margin + 50, y);
345
- pdf.setFont('helvetica', 'italic');
346
- pdf.text(` ${now.toLocaleString()}`,margin + indentX,y);
347
- y += 50;
348
- /* URL */
349
- pdf.setFont('helvetica', 'normal');
350
- const footerLines = pdf.splitTextToSize(`Fraud Score calculated by: ${window.location.href}`, usableW);
351
- pdf.text(footerLines, margin, y);
352
- /* ── open PDF ─────────────────────────────────────── */
353
- window.open(pdf.output('bloburl'), '_blank');
354
- }
355
-
356
- function resetSelection(target) {
357
- for (let i = 1; i <= 3; i++) {
358
- const thumb = document.getElementById(`preset-${i}-${target}`);
359
- if (thumb) thumb.classList.remove("selected");
360
  }
361
  }
362
- function selectPreset(n, target) {
363
- const imgEl = document.getElementById(`preset-${n}-${target}`);
364
- const src = imgEl.src;
365
- fetch(src)
366
- .then((r) => r.blob())
367
- .then((blob) => {
368
- const file = new File([blob], src.split("/").pop(), { type: blob.type });
369
- if (target === 1) {
370
- file1 = file;
371
- } else {
372
- file2 = file;
373
- }
374
- updatePreview(blob, target);
375
- resetSelection(target);
376
- imgEl.classList.add("selected");
377
- checkReady();
378
- });
379
  }
380
- function updatePreview(fileOrBlob, target) {
381
- const reader = new FileReader();
382
- reader.onload = (e) => {
383
- const img = document.getElementById(`previewImage${target}`);
384
- img.src = e.target.result;
385
- img.classList.add("visible");
386
- document.getElementById("compareSection").style.display = "block";
387
  };
388
- reader.readAsDataURL(fileOrBlob);
389
  }
390
- document.getElementById("fileInput1").addEventListener("change", (e) => {
391
- if (e.target.files.length) {
392
- file1 = e.target.files[0];
393
- updatePreview(file1, 1);
394
- resetSelection(1);
395
- checkReady();
396
- }
397
  });
398
- document.getElementById("fileInput2").addEventListener("change", (e) => {
399
- if (e.target.files.length) {
400
- file2 = e.target.files[0];
401
- updatePreview(file2, 2);
402
- resetSelection(2);
403
- checkReady();
404
- }
405
  });
406
- function checkReady() {
407
- const container = document.getElementById("compareButtonContainer");
408
- if (file1 && file2) {
409
- container.innerHTML =
410
- '<button class="btn btn-primary" onclick="processImages()">Calculate Fraud Score</button>';
411
- } else {
412
- container.innerHTML = "";
413
- }
414
  }
415
- async function processImages() {
416
- const fd = new FormData();
417
- fd.append("image1", file1);
418
- fd.append("image2", file2);
419
- const resDiv = document.getElementById("results");
420
- resDiv.innerHTML = '<div class="spinner-border text-primary"></div>';
421
- try {
422
- const res = await fetch("/process", { method: "POST", body: fd });
423
- const data = await res.json();
424
- const euclid = parseFloat(data.distance);
425
- const fraud = Math.min(100, Math.max(2, 185 - 2.73 * euclid));
426
- document.getElementById("euclidVal").textContent = fraud.toFixed(1);
427
- document.getElementById("scoreBlock").style.display = "block";
428
- // gauge
429
- let color = "#198754";
430
- if (fraud > 90) color = "#dc3545";
431
- else if (fraud > 75) color = "#fd7e14";
432
- else if (fraud > 60) color = "#ffc107";
433
- else if (fraud > 45) color = "#0dcaf0";
434
- else if (fraud > 30) color = "#0d6efd";
435
- const gaugeLevel = document.getElementById("jaugeLevel");
436
- gaugeLevel.style.height = `${Math.min(100, fraud)}%`;
437
- gaugeLevel.style.backgroundColor = color;
438
- document.getElementById("gaugeWrapper").style.display = "flex";
439
- document.getElementById("iconButtons").style.display = "flex";
440
- resDiv.innerHTML = "";
441
- } catch (err) {
442
- resDiv.innerHTML = '<div class="text-danger">Error processing images.</div>';
443
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
444
  }
445
- async function generatePDF1(){const {jsPDF}=window.jspdf;const pdf=new jsPDF({orientation:'portrait',unit:'pt',format:'a4'});
446
- const pageW=pdf.internal.pageSize.getWidth();const margin=40;const usableW=pageW-margin*2;let y=100;
447
- pdf.setFont('helvetica','normal');pdf.setFontSize(24);pdf.text('Fraud Score',pageW/2,y,{align:'center'});
448
- y+=20;pdf.setFontSize(12);pdf.text('Is my work used by generative AI ?',pageW/2,y,{align:'center'});
449
- y+=100; // six blank lines
450
- const section=document.querySelector('#compareSection .d-flex');const clone=section.cloneNode(true);
451
- clone.querySelectorAll('button').forEach(b=>b.style.display='none'); // hide calculate/print
452
- document.body.appendChild(clone);clone.style.position='absolute';clone.style.left='-9999px';
453
- const canvas=await html2canvas(clone,{backgroundColor:null});document.body.removeChild(clone);
454
- const imgData=canvas.toDataURL('image/png');const props=pdf.getImageProperties(imgData);const imgH=(props.height*usableW)/props.width;
455
- pdf.addImage(imgData,'PNG',margin,y,usableW,imgH);
456
- y+=imgH+100; // six blank lines before footer
457
- pdf.setFontSize(10);
458
- const now=new Date();pdf.text(`Calculated on: ${now.toLocaleString()}`,margin,y);
459
- const urlText=`by: ${window.location.href}`;
460
- const wrapped=pdf.splitTextToSize(urlText,usableW);
461
- pdf.text(wrapped,margin,y+28);
462
- window.open(pdf.output('bloburl'),'_blank');}
463
-
464
  </script>
465
  <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js"></script>
466
  </body>
@@ -468,3 +366,4 @@ async function generatePDF2 () {
468
 
469
 
470
 
 
 
21
  background: var(--apple-gray);
22
  color: #1c1c1e;
23
  }
24
+ h1, h2, h5 { font-weight: 600; }
 
 
 
 
25
  /* thumbnails */
26
+ .preset-image-container { overflow: hidden; margin-bottom: 12px; }
 
 
 
27
  .preset-image {
28
+ width: 100%; border-radius: 12px; border: 2px solid transparent;
29
+ box-shadow: var(--apple-shadow); transition: transform .3s ease, border-color .3s ease; cursor: pointer;
 
 
 
 
 
 
 
 
 
 
30
  }
31
+ .preset-image:hover { transform: scale(1.05); }
32
+ .preset-image.selected { border-color: var(--apple-blue); }
33
  /* dropzone */
34
  .dropzone {
35
+ border: 2px dashed var(--apple-border); border-radius: 16px; background: #fff;
36
+ padding: 20px; text-align: center; box-shadow: var(--apple-shadow);
 
 
 
 
37
  }
38
  /* preview */
39
  .preview-image {
40
+ max-width: 220px; border-radius: 16px; box-shadow: var(--apple-shadow);
41
+ opacity: 0; transition: opacity .4s ease;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
42
  }
43
+ .preview-image.visible { opacity: 1; }
44
+ /* generic primary button (blue) */
45
+ .btn-primary { background: var(--apple-blue); border: none; font-weight: 500; }
46
+ .btn-primary:hover { background: #005ecb; }
47
+ /* validate button (light blue) */
48
+ .btn-validate { background: #8ec3ff; border: none; font-weight: 500; }
49
+ .btn-validate:disabled { background: #d0e5ff; cursor: not-allowed; }
50
  /* gauge */
51
  .jauge-container {
52
+ display: none; align-items: center; gap: 24px; margin-top: 24px;
 
 
 
53
  }
54
  .jauge {
55
+ width: 24px; height: 160px; border-radius: 12px; background: #e5e5ea;
56
+ position: relative; overflow: hidden; border: 1px solid var(--apple-border);
 
 
 
 
 
57
  }
58
  .jauge-level {
59
+ position: absolute; bottom: 0; width: 100%; border-radius: 12px;
60
+ background: var(--apple-blue); transition: height .6s ease, background .3s ease;
 
 
 
 
61
  }
62
  .jauge-labels {
63
+ font-size: 0.7rem; color: #636366; display: flex; flex-direction: column; justify-content: space-between; height: 160px;
 
 
 
 
 
64
  }
65
  /* icon column */
66
  .icon-buttons {
67
+ display: none; flex-direction: column; gap: 16px; justify-content: center; align-items: center; margin-left: 12px;
 
 
 
 
 
 
 
 
 
 
 
 
68
  }
69
+ .icon-buttons button { background: none; border: none; color: var(--apple-blue); font-size: 24px; cursor: pointer; }
70
  /* section hidden until first preview shown */
71
+ #compareSection { margin-top: 40px; text-align: center; display: none; }
 
 
 
 
72
  /* modal */
73
+ .modal-header { border-bottom: 1px solid var(--apple-border); }
74
+ .modal-footer { border-top: 1px solid var(--apple-border); }
75
  </style>
76
  </head>
77
  <body>
 
90
  <div class="col-4"><div class="preset-image-container"><img id="preset-3-1" src="/static/muhammad ali_1.jpg" class="preset-image" onclick="selectPreset(3,1)" alt="Original 3" /></div></div>
91
  </div>
92
  <div class="dropzone mt-3" id="dropzone1">
93
+ <p class="mb-2">Drag &amp; Drop or Upload (.jpg/.png/.webp)</p>
94
  <input type="file" id="fileInput1" accept="image/*" hidden />
95
  <button class="btn btn-outline-secondary" onclick="document.getElementById('fileInput1').click()">Upload</button>
96
  </div>
97
  </div>
98
+
99
+ <!-- AI generated -->
100
  <div class="col-md-6">
101
  <h5 class="text-center mb-3">Select AI Generated Image</h5>
102
  <div class="row">
 
105
  <div class="col-4"><div class="preset-image-container"><img id="preset-3-2" src="/static/muhammad ali_2.png" class="preset-image" onclick="selectPreset(3,2)" alt="AI 3" /></div></div>
106
  </div>
107
  <div class="dropzone mt-3" id="dropzone2">
108
+ <p class="mb-2">Drag &amp; Drop or Upload (.jpg/.png/.webp)</p>
109
  <input type="file" id="fileInput2" accept="image/*" hidden />
110
  <button class="btn btn-outline-secondary" onclick="document.getElementById('fileInput2').click()">Upload</button>
111
  </div>
 
129
  <span>most certainly</span><span>very probably</span><span>probably</span><span>possibly</span><span>probably not</span><span>definitely not</span>
130
  </div>
131
  </div>
 
132
  <div class="icon-buttons" id="iconButtons">
133
  <button title="Print" onclick="generatePDF1()"><i class="fas fa-print"></i></button>
134
  <button title="Certificate" data-bs-toggle="modal" data-bs-target="#certModal"><i class="fas fa-certificate"></i></button>
 
135
  </div>
136
  </div>
137
  </div>
 
142
  <div id="results" class="mt-4"></div>
143
  </div>
144
  </div>
145
+
146
  <!-- Certificate modal -->
147
  <div class="modal fade" id="certModal" tabindex="-1" aria-hidden="true">
148
  <div class="modal-dialog modal-dialog-centered">
 
155
  <div class="modal-body">
156
  <div class="row g-3">
157
  <div class="col-md-6">
158
+ <label for="firstName" class="form-label">First Name</label>
159
  <input id="firstName" class="form-control" required>
160
  </div>
161
  <div class="col-md-6">
162
+ <label for="lastName" class="form-label">Last Name</label>
163
  <input id="lastName" class="form-control" required>
164
  </div>
165
+ <div class="col-md-6">
166
+ <label for="occupation" class="form-label">Occupation</label>
167
+ <input id="occupation" class="form-control" required>
168
+ </div>
169
+ <div class="col-md-6">
170
+ <label for="city" class="form-label">City</label>
171
+ <input id="city" class="form-control" required>
172
+ </div>
173
+ <div class="col-md-6">
174
+ <label for="state" class="form-label">State</label>
175
+ <input id="state" class="form-control" required>
176
+ </div>
177
+ <div class="col-md-6">
178
+ <label for="linkedin" class="form-label">LinkedIn URL</label>
179
+ <input id="linkedin" type="url" class="form-control" placeholder="https://www.linkedin.com/in/…" required>
180
+ </div>
181
  <div class="col-12">
182
+ <label for="aiModel" class="form-label">Generative AI</label>
183
  <select id="aiModel" class="form-select" required>
184
  <option value="" selected disabled>Select…</option>
185
  <option>Adobe Firefly Image Model 4</option>
 
194
  <option>NightCafe Creator</option>
195
  <option>Playground AI</option>
196
  <option>Runway Gen‑3 Alpha</option>
197
+ <option>Stable Diffusion 3</option>
198
  <option>Other</option>
199
  </select>
200
  </div>
 
211
  </div>
212
  </div>
213
  </div>
214
+ <!-- modal footer -->
215
  <div class="modal-footer">
216
  <button type="button" class="btn btn-outline-secondary" data-bs-dismiss="modal">Cancel</button>
217
+ <button id="validateBtn" type="submit" class="btn btn-validate" disabled>Validate & Generate PDF</button>
218
  </div>
219
  </form>
220
  </div>
221
  </div>
222
  </div>
223
+
224
+ <!-- scripts -->
225
  <script>
226
+ let file1 = null, file2 = null;
227
+ /* helper functions for selection */
228
+ function resetSelection(target){
229
+ for(let i=1;i<=3;i++){
230
+ const t=document.getElementById(`preset-${i}-${target}`);
231
+ if(t) t.classList.remove('selected');
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
232
  }
233
  }
234
+ function selectPreset(n,target){
235
+ const imgEl=document.getElementById(`preset-${n}-${target}`);
236
+ const src=imgEl.src;
237
+ fetch(src).then(r=>r.blob()).then(blob=>{
238
+ const f=new File([blob],src.split('/').pop(),{type:blob.type});
239
+ if(target===1){file1=f;}else{file2=f;}
240
+ updatePreview(blob,target);
241
+ resetSelection(target);
242
+ imgEl.classList.add('selected');
243
+ checkReady();
244
+ });
 
 
 
 
 
 
245
  }
246
+ function updatePreview(blob,target){
247
+ const reader=new FileReader();
248
+ reader.onload=e=>{
249
+ const img=document.getElementById(`previewImage${target}`);
250
+ img.src=e.target.result;
251
+ img.classList.add('visible');
252
+ document.getElementById('compareSection').style.display='block';
253
  };
254
+ reader.readAsDataURL(blob);
255
  }
256
+ /* upload inputs */
257
+ document.getElementById('fileInput1').addEventListener('change',e=>{
258
+ if(e.target.files.length){file1=e.target.files[0];updatePreview(file1,1);resetSelection(1);checkReady();}
 
 
 
 
259
  });
260
+ document.getElementById('fileInput2').addEventListener('change',e=>{
261
+ if(e.target.files.length){file2=e.target.files[0];updatePreview(file2,2);resetSelection(2);checkReady();}
 
 
 
 
 
262
  });
263
+ /* enable calculate button */
264
+ function checkReady(){
265
+ const c=document.getElementById('compareButtonContainer');
266
+ c.innerHTML = (file1&&file2) ? '<button class="btn btn-primary" onclick="processImages()">Calculate Fraud Score</button>' : '';
 
 
 
 
267
  }
268
+ /* fraud score API */
269
+ async function processImages(){
270
+ const fd=new FormData();
271
+ fd.append('image1',file1);
272
+ fd.append('image2',file2);
273
+ const resDiv=document.getElementById('results');
274
+ resDiv.innerHTML='<div class="spinner-border text-primary"></div>';
275
+ try{
276
+ const res=await fetch('/process',{method:'POST',body:fd});
277
+ const data=await res.json();
278
+ const euclid=parseFloat(data.distance);
279
+ const fraud=Math.min(100,Math.max(2,185-2.73*euclid));
280
+ document.getElementById('euclidVal').textContent=fraud.toFixed(1);
281
+ document.getElementById('scoreBlock').style.display='block';
282
+ /* gauge colour */
283
+ const g=document.getElementById('jaugeLevel');
284
+ g.style.height=`${Math.min(100,fraud)}%`;
285
+ g.style.backgroundColor = fraud>90?"#dc3545":fraud>75?"#fd7e14":fraud>60?"#ffc107":fraud>45?"#0dcaf0":fraud>30?"#0d6efd":"#198754";
286
+ document.getElementById('gaugeWrapper').style.display='flex';
287
+ document.getElementById('iconButtons').style.display='flex';
288
+ resDiv.innerHTML='';
289
+ }catch{resDiv.innerHTML='<div class="text-danger">Error processing images.</div>';}
290
+ }
291
+ /* validate button enable logic */
292
+ const validateBtn=document.getElementById('validateBtn');
293
+ const certCheck =document.getElementById('certCheck');
294
+ certCheck.addEventListener('change',()=>{ validateBtn.disabled = !certCheck.checked; });
295
+ /* certificate form submission */
296
+ document.getElementById('certForm').addEventListener('submit',e=>{
297
+ if(!certCheck.checked){ e.preventDefault(); certCheck.focus(); return; }
298
+ e.preventDefault();
299
+ const certData={
300
+ first : firstName.value.trim(),
301
+ last : lastName.value.trim(),
302
+ occupation : occupation.value.trim(),
303
+ city : city.value.trim(),
304
+ state : state.value.trim(),
305
+ linkedin : linkedin.value.trim(),
306
+ ai : aiModel.value,
307
+ prompt : promptText.value.trim()
308
+ };
309
+ bootstrap.Modal.getInstance(certModal).hide();
310
+ generatePDF2(certData);
311
+ });
312
+ /* PDF – simple fraud score */
313
+ async function generatePDF1(){
314
+ const {jsPDF}=window.jspdf;
315
+ const pdf=new jsPDF({unit:'pt',format:'a4'});
316
+ const pageW=pdf.internal.pageSize.getWidth(),margin=40,usableW=pageW-margin*2; let y=100;
317
+ pdf.setFont('helvetica','normal'); pdf.setFontSize(24); pdf.text('Fraud Score',pageW/2,y,{align:'center'});
318
+ y+=20; pdf.setFontSize(12); pdf.text('Is my work used by generative AI ?',pageW/2,y,{align:'center'});
319
+ y+=100;
320
+ const section=document.querySelector('#compareSection .d-flex'); const clone=section.cloneNode(true);
321
+ clone.querySelectorAll('button').forEach(b=>b.style.display='none'); document.body.appendChild(clone);
322
+ clone.style.cssText='position:absolute;left:-9999px;top:0;';
323
+ const canvas=await html2canvas(clone,{backgroundColor:null}); document.body.removeChild(clone);
324
+ const img=canvas.toDataURL('image/png'); const prop=pdf.getImageProperties(img);
325
+ const imgH=(prop.height*usableW)/prop.width; pdf.addImage(img,'PNG',margin,y,usableW,imgH);
326
+ y+=imgH+60; pdf.setFontSize(10);
327
+ const urlLines=pdf.splitTextToSize(`Calculated by: ${window.location.href}`,usableW);
328
+ pdf.text(urlLines,margin,y);
329
+ window.open(pdf.output('bloburl'),'_blank');
330
+ }
331
+ /* PDF – certificate */
332
+ async function generatePDF2(certData){
333
+ const {jsPDF}=window.jspdf;
334
+ const pdf=new jsPDF({unit:'pt',format:'a4'});
335
+ const pageW=pdf.internal.pageSize.getWidth(),margin=40,usableW=pageW-margin*2; let y=80;
336
+ pdf.setFont('helvetica','normal'); pdf.setFontSize(26); pdf.text('Certification',pageW/2,y,{align:'center'});
337
+ y+=22; pdf.setDrawColor('#d1d1d6'); pdf.line(margin,y,pageW-margin,y); y+=32;
338
+ const section=document.querySelector('#compareSection .d-flex'); const clone=section.cloneNode(true);
339
+ clone.querySelectorAll('button').forEach(b=>b.style.display='none'); document.body.appendChild(clone);
340
+ clone.style.cssText='position:absolute;left:-9999px;top:0;';
341
+ const canvas=await html2canvas(clone,{backgroundColor:null}); document.body.removeChild(clone);
342
+ const img=canvas.toDataURL('image/png'); const prop=pdf.getImageProperties(img);
343
+ const imgH=(prop.height*usableW)/prop.width; pdf.addImage(img,'PNG',margin,y,usableW,imgH); y+=imgH+40;
344
+ pdf.setFontSize(12);
345
+ pdf.text('I hereby certify that the original picture shown on the left is my own work and that',margin,y); y+=18;
346
+ pdf.text('the AI‑generated picture shown on the right was produced by the generative AI', margin,y); y+=18;
347
+ pdf.text('indicated below, using the prompt that follows.', margin,y); y+=40;
348
+ const labelX = margin + 50, valueX = margin + 150;
349
+ pdf.setFont('helvetica','normal'); pdf.text('Generative AI:',labelX,y); pdf.setFont('helvetica','italic'); pdf.text(certData.ai,valueX,y); y+=28;
350
+ pdf.setFont('helvetica','normal'); pdf.text('Prompt:',labelX,y); pdf.setFont('helvetica','italic');
351
+ const promptLines=pdf.splitTextToSize(certData.prompt,usableW-valueX);
352
+ pdf.text(promptLines,valueX,y); y+=promptLines.length*14+28;
353
+ pdf.setFont('helvetica','normal'); pdf.text('Name:',labelX,y); pdf.setFont('helvetica','italic'); pdf.text(`${certData.first} ${certData.last}`,valueX,y); y+=28;
354
+ pdf.setFont('helvetica','normal'); pdf.text('Occupation:',labelX,y); pdf.setFont('helvetica','italic'); pdf.text(certData.occupation,valueX,y); y+=28;
355
+ pdf.setFont('helvetica','normal'); pdf.text('City / State:',labelX,y); pdf.setFont('helvetica','italic'); pdf.text(`${certData.city}, ${certData.state}`,valueX,y); y+=28;
356
+ pdf.setFont('helvetica','normal'); pdf.text('LinkedIn:',labelX,y); pdf.setFont('helvetica','italic'); pdf.text(certData.linkedin,valueX,y); y+=40;
357
+ pdf.setFont('helvetica','normal'); pdf.text('Date:',labelX,y); pdf.setFont('helvetica','italic'); pdf.text(new Date().toLocaleString(),valueX,y); y+=40;
358
+ const footer=pdf.splitTextToSize(`Fraud Score calculated by: ${window.location.href}`,usableW);
359
+ pdf.text(footer,margin,y);
360
+ window.open(pdf.output('bloburl'),'_blank');
361
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
362
  </script>
363
  <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js"></script>
364
  </body>
 
366
 
367
 
368
 
369
+