TheFrenchDemos commited on
Commit
651b66f
·
verified ·
1 Parent(s): eb9597a

premier pdf

Browse files
Files changed (1) hide show
  1. templates/index.html +332 -149
templates/index.html CHANGED
@@ -1,174 +1,357 @@
1
  <!DOCTYPE html>
2
  <html lang="en">
3
  <head>
4
- <meta charset="UTF-8">
5
  <title>Fraud Score</title>
6
- <meta name="viewport" content="width=device-width, initial-scale=1">
7
- <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet">
8
- <link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css" rel="stylesheet">
 
 
 
9
  <style>
10
- :root{
11
- --apple-blue:#007aff;
12
- --apple-gray:#f2f2f7;
13
- --apple-border:#d1d1d6;
14
- --apple-shadow:0 4px 12px rgba(0,0,0,.08);
15
- }
16
- body{font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Helvetica,Arial,sans-serif;background:var(--apple-gray);color:#1c1c1e;}
17
- h1,h2,h5{font-weight:600;}
18
- .preset-image-container{overflow:hidden;margin-bottom:12px}
19
- .preset-image{width:100%;border-radius:12px;border:2px solid transparent;box-shadow:var(--apple-shadow);transition:transform .3s ease,border-color .3s ease;cursor:pointer}
20
- .preset-image:hover{transform:scale(1.05)}
21
- .preset-image.selected{border-color:var(--apple-blue)}
22
- .dropzone{border:2px dashed var(--apple-border);border-radius:16px;background:#fff;padding:20px;text-align:center;box-shadow:var(--apple-shadow)}
23
- .preview-image{max-width:220px;border-radius:16px;box-shadow:var(--apple-shadow);opacity:0;transition:opacity .4s ease}
24
- .preview-image.visible{opacity:1}
25
- .btn-primary{background:var(--apple-blue);border:none;font-weight:500}
26
- .btn-primary:hover{background:#005ecb}
27
- .jauge-container{display:none;align-items:center;gap:24px;margin-top:24px}
28
- .jauge{width:24px;height:160px;border-radius:12px;background:#e5e5ea;position:relative;overflow:hidden;border:1px solid var(--apple-border)}
29
- .jauge-level{position:absolute;bottom:0;width:100%;border-radius:12px;background:var(--apple-blue);transition:height .6s ease,background .3s ease}
30
- .jauge-labels{font-size:.7rem;color:#636366;display:flex;flex-direction:column;justify-content:space-between;height:160px}
31
- .icon-buttons { display: flex; flex-direction: column; gap: 16px; justify-content: center; align-items: center; margin-left: 12px; }
32
- .icon-buttons button { background: none; border: none; color: var(--apple-blue); font-size: 24px; cursor: pointer; }
33
- #compareSection{margin-top:40px;text-align:center;display:none}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
34
  </style>
35
  </head>
36
  <body>
37
- <div class="container py-5">
38
- <h1 class="text-center mb-1">Fraud Score</h1>
39
- <p class="text-center text-muted mb-5 fs-5">Is my work used by generative AI ?</p>
40
-
41
- <div class="row g-5">
42
- <div class="col-md-6">
43
- <h5 class="text-center mb-3">Select Original Image</h5>
44
- <div class="row">
45
- <div class="col-4"><div class="preset-image-container"><img id="preset-1-1" src="/static/marilyn_1.jpg" class="preset-image" onclick="selectPreset(1,1)" alt="Original 1"></div></div>
46
- <div class="col-4"><div class="preset-image-container"><img id="preset-2-1" src="/static/star wars_1b.jpg" class="preset-image" onclick="selectPreset(2,1)" alt="Original 2"></div></div>
47
- <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>
48
- </div>
49
- <div class="dropzone mt-3" id="dropzone1">
50
- <p class="mb-2">Drag & Drop or Upload (.jpg/.png/.webp)</p>
51
- <input type="file" id="fileInput1" accept="image/*" hidden>
52
- <button class="btn btn-outline-secondary" onclick="document.getElementById('fileInput1').click()">Upload</button>
53
- </div>
54
- </div>
55
 
56
- <div class="col-md-6">
57
- <h5 class="text-center mb-3">Select AI Generated Image</h5>
58
- <div class="row">
59
- <div class="col-4"><div class="preset-image-container"><img id="preset-1-2" src="/static/marilyn_2.png" class="preset-image" onclick="selectPreset(1,2)" alt="AI 1"></div></div>
60
- <div class="col-4"><div class="preset-image-container"><img id="preset-2-2" src="/static/star wars_2b.png" class="preset-image" onclick="selectPreset(2,2)" alt="AI 2"></div></div>
61
- <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>
 
 
 
 
 
 
 
 
 
62
  </div>
63
- <div class="dropzone mt-3" id="dropzone2">
64
- <p class="mb-2">Drag & Drop or Upload (.jpg/.png/.webp)</p>
65
- <input type="file" id="fileInput2" accept="image/*" hidden>
66
- <button class="btn btn-outline-secondary" onclick="document.getElementById('fileInput2').click()">Upload</button>
 
 
 
 
 
 
 
 
 
67
  </div>
68
  </div>
69
- </div>
70
 
71
- <div id="compareSection">
72
- <div class="d-flex justify-content-center align-items-center gap-5 mt-5">
73
- <img id="previewImage1" class="preview-image" alt="Original preview">
74
- <div class="d-flex flex-column align-items-center">
75
- <div id="compareButtonContainer" class="mb-3"></div>
76
- <div id="scoreBlock" style="display:none;">
77
- <h2 class="fw-semibold mb-3">Fraud Score <span id="euclidVal" class="text-muted"></span>%</h2>
78
- <div class="d-flex align-items-center gap-4">
79
- <div class="jauge-container mx-auto" id="gaugeWrapper">
80
- <div class="jauge"><div class="jauge-level" id="jaugeLevel"></div></div>
81
- <div class="jauge-labels text-start">
82
- <span>most certainly</span><span>very probably</span><span>probably</span><span>possibly</span><span>probably not</span><span>definitely not</span>
 
 
 
 
 
 
 
 
83
  </div>
84
- </div>
85
- <div class="icon-buttons">
86
- <button title="Print"><i class="fas fa-print"></i></button>
87
- <button title="Certificate"><i class="fas fa-certificate"></i></button>
88
  </div>
89
  </div>
90
  </div>
 
 
91
  </div>
92
- <img id="previewImage2" class="preview-image" alt="AI preview">
93
  </div>
94
- <div id="results" class="mt-4"></div>
95
  </div>
96
- </div>
97
- <script>
98
- let file1=null, file2=null;
99
- function resetSelection(target){
100
- for(let i=1;i<=3;i++){
101
- const thumb=document.getElementById(`preset-${i}-${target}`);
102
- if(thumb)thumb.classList.remove('selected');
103
- }
104
- }
105
- function selectPreset(n,target){
106
- const imgEl=document.getElementById(`preset-${n}-${target}`);
107
- const src=imgEl.src;
108
- fetch(src).then(r=>r.blob()).then(blob=>{
109
- const file=new File([blob],src.split('/').pop(),{type:blob.type});
110
- if(target===1){file1=file;} else {file2=file;}
111
- updatePreview(blob,target);
112
- resetSelection(target);
113
- imgEl.classList.add('selected');
114
- checkReady();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
115
  });
116
- }
117
- function updatePreview(fileOrBlob,target){
118
- const reader=new FileReader();
119
- reader.onload=e=>{
120
- const img=document.getElementById(`previewImage${target}`);
121
- img.src=e.target.result;
122
- img.classList.add('visible');
123
- document.getElementById('compareSection').style.display='block';
124
- };
125
- reader.readAsDataURL(fileOrBlob);
126
- }
127
- document.getElementById('fileInput1').addEventListener('change',e=>{
128
- if(e.target.files.length){file1=e.target.files[0];updatePreview(file1,1);resetSelection(1);checkReady();}
129
- });
130
- document.getElementById('fileInput2').addEventListener('change',e=>{
131
- if(e.target.files.length){file2=e.target.files[0];updatePreview(file2,2);resetSelection(2);checkReady();}
132
- });
133
- function checkReady(){
134
- const container=document.getElementById('compareButtonContainer');
135
- if(file1&&file2){
136
- container.innerHTML='<button class="btn btn-primary" onclick="processImages()">Calculate Fraud Score</button>';
137
- }else{
138
- container.innerHTML='';
139
- }
140
- }
141
- async function processImages(){
142
- const fd=new FormData();
143
- fd.append('image1',file1);
144
- fd.append('image2',file2);
145
- const resDiv=document.getElementById('results');
146
- resDiv.innerHTML='<div class="spinner-border text-primary"></div>';
147
- try{
148
- const res=await fetch('/process',{method:'POST',body:fd});
149
- const data=await res.json();
150
- const euclid=parseFloat(data.distance);
151
- const fraud=Math.min(100,Math.max(2,185-2.73*euclid));
152
- document.getElementById('euclidVal').textContent=fraud.toFixed(1);
153
- document.getElementById('scoreBlock').style.display='block';
154
- let color='#198754';
155
- if(fraud>90)color='#dc3545';
156
- else if(fraud>75)color='#fd7e14';
157
- else if(fraud>60)color='#ffc107';
158
- else if(fraud>45)color='#0dcaf0';
159
- else if(fraud>30)color='#0d6efd';
160
- const gaugeLevel=document.getElementById('jaugeLevel');
161
- gaugeLevel.style.height=`${Math.min(100,fraud)}%`;
162
- gaugeLevel.style.backgroundColor=color;
163
- document.getElementById('gaugeWrapper').style.display='flex';
164
- resDiv.innerHTML='';
165
- }catch(err){
166
- resDiv.innerHTML='<div class="text-danger">Error processing images.</div>';
167
- }
168
- }
169
- </script>
170
- <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js"></script>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
171
  </body>
172
  </html>
173
 
174
 
 
 
1
  <!DOCTYPE html>
2
  <html lang="en">
3
  <head>
4
+ <meta charset="UTF-8" />
5
  <title>Fraud Score</title>
6
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
7
+ <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet" />
8
+ <link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css" rel="stylesheet" />
9
+ <!-- jsPDF & html2canvas for PDF export -->
10
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/jspdf/2.5.1/jspdf.umd.min.js"></script>
11
+ <script src="https://html2canvas.hertzen.com/dist/html2canvas.min.js"></script>
12
  <style>
13
+ :root {
14
+ --apple-blue: #007aff;
15
+ --apple-gray: #f2f2f7;
16
+ --apple-border: #d1d1d6;
17
+ --apple-shadow: 0 4px 12px rgba(0, 0, 0, 0.08);
18
+ }
19
+
20
+ body {
21
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
22
+ background: var(--apple-gray);
23
+ color: #1c1c1e;
24
+ }
25
+
26
+ h1,
27
+ h2,
28
+ h5 {
29
+ font-weight: 600;
30
+ }
31
+
32
+ /* thumbnails */
33
+ .preset-image-container {
34
+ overflow: hidden;
35
+ margin-bottom: 12px;
36
+ }
37
+
38
+ .preset-image {
39
+ width: 100%;
40
+ border-radius: 12px;
41
+ border: 2px solid transparent;
42
+ box-shadow: var(--apple-shadow);
43
+ transition: transform 0.3s ease, border-color 0.3s ease;
44
+ cursor: pointer;
45
+ }
46
+
47
+ .preset-image:hover {
48
+ transform: scale(1.05);
49
+ }
50
+
51
+ .preset-image.selected {
52
+ border-color: var(--apple-blue);
53
+ }
54
+
55
+ /* dropzone */
56
+ .dropzone {
57
+ border: 2px dashed var(--apple-border);
58
+ border-radius: 16px;
59
+ background: #fff;
60
+ padding: 20px;
61
+ text-align: center;
62
+ box-shadow: var(--apple-shadow);
63
+ }
64
+
65
+ /* preview */
66
+ .preview-image {
67
+ max-width: 220px;
68
+ border-radius: 16px;
69
+ box-shadow: var(--apple-shadow);
70
+ opacity: 0;
71
+ transition: opacity 0.4s ease;
72
+ }
73
+
74
+ .preview-image.visible {
75
+ opacity: 1;
76
+ }
77
+
78
+ /* buttons */
79
+ .btn-primary {
80
+ background: var(--apple-blue);
81
+ border: none;
82
+ font-weight: 500;
83
+ }
84
+
85
+ .btn-primary:hover {
86
+ background: #005ecb;
87
+ }
88
+
89
+ /* gauge */
90
+ .jauge-container {
91
+ display: none;
92
+ align-items: center;
93
+ gap: 24px;
94
+ margin-top: 24px;
95
+ }
96
+
97
+ .jauge {
98
+ width: 24px;
99
+ height: 160px;
100
+ border-radius: 12px;
101
+ background: #e5e5ea;
102
+ position: relative;
103
+ overflow: hidden;
104
+ border: 1px solid var(--apple-border);
105
+ }
106
+
107
+ .jauge-level {
108
+ position: absolute;
109
+ bottom: 0;
110
+ width: 100%;
111
+ border-radius: 12px;
112
+ background: var(--apple-blue);
113
+ transition: height 0.6s ease, background 0.3s ease;
114
+ }
115
+
116
+ .jauge-labels {
117
+ font-size: 0.7rem;
118
+ color: #636366;
119
+ display: flex;
120
+ flex-direction: column;
121
+ justify-content: space-between;
122
+ height: 160px;
123
+ }
124
+
125
+ /* icon column */
126
+ .icon-buttons {
127
+ display: none;
128
+ flex-direction: column;
129
+ gap: 16px;
130
+ justify-content: center;
131
+ align-items: center;
132
+ margin-left: 12px;
133
+ }
134
+
135
+ .icon-buttons button {
136
+ background: none;
137
+ border: none;
138
+ color: var(--apple-blue);
139
+ font-size: 24px;
140
+ cursor: pointer;
141
+ }
142
+
143
+ /* section hidden until first preview shown */
144
+ #compareSection {
145
+ margin-top: 40px;
146
+ text-align: center;
147
+ display: none;
148
+ }
149
  </style>
150
  </head>
151
  <body>
152
+ <div class="container py-5">
153
+ <h1 class="text-center mb-1">Fraud Score</h1>
154
+ <p class="text-center text-muted mb-5 fs-5">Is my work used by generative AI ?</p>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
155
 
156
+ <!-- selection rows -->
157
+ <div class="row g-5">
158
+ <!-- original -->
159
+ <div class="col-md-6">
160
+ <h5 class="text-center mb-3">Select Original Image</h5>
161
+ <div class="row">
162
+ <div class="col-4"><div class="preset-image-container"><img id="preset-1-1" src="/static/marilyn_1.jpg" class="preset-image" onclick="selectPreset(1,1)" alt="Original 1" /></div></div>
163
+ <div class="col-4"><div class="preset-image-container"><img id="preset-2-1" src="/static/star wars_1b.jpg" class="preset-image" onclick="selectPreset(2,1)" alt="Original 2" /></div></div>
164
+ <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>
165
+ </div>
166
+ <div class="dropzone mt-3" id="dropzone1">
167
+ <p class="mb-2">Drag & Drop or Upload (.jpg/.png/.webp)</p>
168
+ <input type="file" id="fileInput1" accept="image/*" hidden />
169
+ <button class="btn btn-outline-secondary" onclick="document.getElementById('fileInput1').click()">Upload</button>
170
+ </div>
171
  </div>
172
+ <!-- AI -->
173
+ <div class="col-md-6">
174
+ <h5 class="text-center mb-3">Select AI Generated Image</h5>
175
+ <div class="row">
176
+ <div class="col-4"><div class="preset-image-container"><img id="preset-1-2" src="/static/marilyn_2.png" class="preset-image" onclick="selectPreset(1,2)" alt="AI 1" /></div></div>
177
+ <div class="col-4"><div class="preset-image-container"><img id="preset-2-2" src="/static/star wars_2b.png" class="preset-image" onclick="selectPreset(2,2)" alt="AI 2" /></div></div>
178
+ <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>
179
+ </div>
180
+ <div class="dropzone mt-3" id="dropzone2">
181
+ <p class="mb-2">Drag & Drop or Upload (.jpg/.png/.webp)</p>
182
+ <input type="file" id="fileInput2" accept="image/*" hidden />
183
+ <button class="btn btn-outline-secondary" onclick="document.getElementById('fileInput2').click()">Upload</button>
184
+ </div>
185
  </div>
186
  </div>
 
187
 
188
+ <!-- compare section -->
189
+ <div id="compareSection">
190
+ <div class="d-flex justify-content-center align-items-center gap-5 mt-5">
191
+ <img id="previewImage1" class="preview-image" alt="Original preview" />
192
+
193
+ <div class="d-flex flex-column align-items-center">
194
+ <div id="compareButtonContainer" class="mb-3"></div>
195
+
196
+ <div id="scoreBlock" style="display:none;">
197
+ <h2 class="fw-semibold mb-3">Fraud Score <span id="euclidVal" class="text-muted"></span>%</h2>
198
+ <div class="d-flex align-items-center gap-4">
199
+ <div class="jauge-container mx-auto" id="gaugeWrapper">
200
+ <div class="jauge"><div class="jauge-level" id="jaugeLevel"></div></div>
201
+ <div class="jauge-labels text-start">
202
+ <span>most certainly</span><span>very probably</span><span>probably</span><span>possibly</span><span>probably not</span><span>definitely not</span>
203
+ </div>
204
+ </div>
205
+ <div class="icon-buttons" id="iconButtons">
206
+ <button title="Print" onclick="generatePDF()"><i class="fas fa-print"></i></button>
207
+ <button title="Certificate"><i class="fa-solid fa-file-certificate"></i></button>
208
  </div>
 
 
 
 
209
  </div>
210
  </div>
211
  </div>
212
+
213
+ <img id="previewImage2" class="preview-image" alt="AI preview" />
214
  </div>
215
+ <div id="results" class="mt-4"></div>
216
  </div>
 
217
  </div>
218
+ <script>
219
+ let file1 = null,
220
+ file2 = null;
221
+
222
+ function resetSelection(target) {
223
+ for (let i = 1; i <= 3; i++) {
224
+ const thumb = document.getElementById(`preset-${i}-${target}`);
225
+ if (thumb) thumb.classList.remove("selected");
226
+ }
227
+ }
228
+
229
+ function selectPreset(n, target) {
230
+ const imgEl = document.getElementById(`preset-${n}-${target}`);
231
+ const src = imgEl.src;
232
+ fetch(src)
233
+ .then((r) => r.blob())
234
+ .then((blob) => {
235
+ const file = new File([blob], src.split("/").pop(), { type: blob.type });
236
+ if (target === 1) {
237
+ file1 = file;
238
+ } else {
239
+ file2 = file;
240
+ }
241
+ updatePreview(blob, target);
242
+ resetSelection(target);
243
+ imgEl.classList.add("selected");
244
+ checkReady();
245
+ });
246
+ }
247
+
248
+ function updatePreview(fileOrBlob, target) {
249
+ const reader = new FileReader();
250
+ reader.onload = (e) => {
251
+ const img = document.getElementById(`previewImage${target}`);
252
+ img.src = e.target.result;
253
+ img.classList.add("visible");
254
+ document.getElementById("compareSection").style.display = "block";
255
+ };
256
+ reader.readAsDataURL(fileOrBlob);
257
+ }
258
+
259
+ document.getElementById("fileInput1").addEventListener("change", (e) => {
260
+ if (e.target.files.length) {
261
+ file1 = e.target.files[0];
262
+ updatePreview(file1, 1);
263
+ resetSelection(1);
264
+ checkReady();
265
+ }
266
+ });
267
+ document.getElementById("fileInput2").addEventListener("change", (e) => {
268
+ if (e.target.files.length) {
269
+ file2 = e.target.files[0];
270
+ updatePreview(file2, 2);
271
+ resetSelection(2);
272
+ checkReady();
273
+ }
274
  });
275
+
276
+ function checkReady() {
277
+ const container = document.getElementById("compareButtonContainer");
278
+ if (file1 && file2) {
279
+ container.innerHTML =
280
+ '<button class="btn btn-primary" onclick="processImages()">Calculate Fraud Score</button>';
281
+ } else {
282
+ container.innerHTML = "";
283
+ }
284
+ }
285
+
286
+ async function processImages() {
287
+ const fd = new FormData();
288
+ fd.append("image1", file1);
289
+ fd.append("image2", file2);
290
+ const resDiv = document.getElementById("results");
291
+ resDiv.innerHTML = '<div class="spinner-border text-primary"></div>';
292
+ try {
293
+ const res = await fetch("/process", { method: "POST", body: fd });
294
+ const data = await res.json();
295
+ const euclid = parseFloat(data.distance);
296
+ const fraud = Math.min(100, Math.max(2, 185 - 2.73 * euclid));
297
+ document.getElementById("euclidVal").textContent = fraud.toFixed(1);
298
+ document.getElementById("scoreBlock").style.display = "block";
299
+ // gauge
300
+ let color = "#198754";
301
+ if (fraud > 90) color = "#dc3545";
302
+ else if (fraud > 75) color = "#fd7e14";
303
+ else if (fraud > 60) color = "#ffc107";
304
+ else if (fraud > 45) color = "#0dcaf0";
305
+ else if (fraud > 30) color = "#0d6efd";
306
+ const gaugeLevel = document.getElementById("jaugeLevel");
307
+ gaugeLevel.style.height = `${Math.min(100, fraud)}%`;
308
+ gaugeLevel.style.backgroundColor = color;
309
+ document.getElementById("gaugeWrapper").style.display = "flex";
310
+ document.getElementById("iconButtons").style.display = "flex";
311
+ resDiv.innerHTML = "";
312
+ } catch (err) {
313
+ resDiv.innerHTML = '<div class="text-danger">Error processing images.</div>';
314
+ }
315
+ }
316
+
317
+ async function generatePDF() {
318
+ const { jsPDF } = window.jspdf;
319
+ const pdf = new jsPDF({ orientation: "portrait", unit: "pt", format: "a4" });
320
+
321
+ // capture the score block including gauge & icons (icons will be hidden by CSS print media maybe, but fine)
322
+ const target = document.querySelector("#compareSection .d-flex");
323
+ const canvas = await html2canvas(target, { backgroundColor: null });
324
+ const imgData = canvas.toDataURL("image/png");
325
+ const pageWidth = pdf.internal.pageSize.getWidth();
326
+ const margin = 40;
327
+ const availableWidth = pageWidth - margin * 2;
328
+
329
+ // header
330
+ pdf.setFont("helvetica", "normal");
331
+ pdf.setFontSize(24);
332
+ pdf.text("Fraud Score", pageWidth / 2, 50, { align: "center" });
333
+ pdf.setFontSize(12);
334
+ pdf.text("Is my work used by generative AI ?", pageWidth / 2, 70, { align: "center" });
335
+
336
+ // image
337
+ const imgProps = pdf.getImageProperties(imgData);
338
+ const imgHeight = (imgProps.height * availableWidth) / imgProps.width;
339
+ pdf.addImage(imgData, "PNG", margin, 100, availableWidth, imgHeight);
340
+
341
+ // footer
342
+ const yFooterStart = 110 + imgHeight;
343
+ pdf.setFontSize(10);
344
+ const now = new Date();
345
+ pdf.text(`Generated on: ${now.toLocaleString()}`, margin, yFooterStart);
346
+ pdf.text(`Calculated by: ${window.location.href}`, margin, yFooterStart + 14);
347
+
348
+ const blobUrl = pdf.output("bloburl");
349
+ window.open(blobUrl, "_blank");
350
+ }
351
+ </script>
352
+ <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js"></script>
353
  </body>
354
  </html>
355
 
356
 
357
+