hlsv 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/CHANGELOG.md +5 -0
- data/LICENSE +676 -0
- data/README.md +356 -0
- data/bin/hlsv +4 -0
- data/config.default.yaml +19 -0
- data/lib/hlsv/cli.rb +85 -0
- data/lib/hlsv/find_keys.rb +979 -0
- data/lib/hlsv/html2word.rb +602 -0
- data/lib/hlsv/mon_script.rb +169 -0
- data/lib/hlsv/version.rb +5 -0
- data/lib/hlsv/web_app.rb +569 -0
- data/lib/hlsv/xpt/dataset.rb +38 -0
- data/lib/hlsv/xpt/library.rb +28 -0
- data/lib/hlsv/xpt/reader.rb +367 -0
- data/lib/hlsv/xpt/variable.rb +130 -0
- data/lib/hlsv/xpt.rb +11 -0
- data/lib/hlsv.rb +49 -0
- data/public/Contact-LOGO.png +0 -0
- data/public/app.js +569 -0
- data/public/styles.css +586 -0
- data/public/styles_csv.css +448 -0
- data/views/csv_view.erb +85 -0
- data/views/index.erb +233 -0
- data/views/report_template.erb +1144 -0
- metadata +176 -0
|
@@ -0,0 +1,1144 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
Copyright (c) 2026 AdClin
|
|
3
|
+
|
|
4
|
+
This program is free software: you can redistribute it and/or modify
|
|
5
|
+
it under the terms of the GNU Affero General Public License as
|
|
6
|
+
published by the Free Software Foundation, either version 3 of the
|
|
7
|
+
License, or (at your option) any later version.
|
|
8
|
+
-->
|
|
9
|
+
|
|
10
|
+
<!DOCTYPE html>
|
|
11
|
+
<html lang="en">
|
|
12
|
+
<head>
|
|
13
|
+
<meta charset="UTF-8">
|
|
14
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
15
|
+
<style>
|
|
16
|
+
/* ===========================
|
|
17
|
+
BASE STYLES
|
|
18
|
+
=========================== */
|
|
19
|
+
body {
|
|
20
|
+
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
|
21
|
+
max-width: 1200px;
|
|
22
|
+
margin: 0 auto;
|
|
23
|
+
padding: 20px;
|
|
24
|
+
padding-left: 80px; /* Space for hamburger */
|
|
25
|
+
background-color: #f5f7fa;
|
|
26
|
+
line-height: 1.6;
|
|
27
|
+
color: #333;
|
|
28
|
+
position: relative;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/* ===========================
|
|
32
|
+
REPORT HEADER WITH BRANDING
|
|
33
|
+
=========================== */
|
|
34
|
+
.report-header {
|
|
35
|
+
display: flex;
|
|
36
|
+
justify-content: space-between;
|
|
37
|
+
align-items: center;
|
|
38
|
+
margin-bottom: 25px;
|
|
39
|
+
padding: 25px;
|
|
40
|
+
background: white;
|
|
41
|
+
border-radius: 8px;
|
|
42
|
+
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
.report-header h1 {
|
|
46
|
+
margin: 0;
|
|
47
|
+
padding: 0;
|
|
48
|
+
border: none;
|
|
49
|
+
color: #2c3e50;
|
|
50
|
+
font-size: 2em;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
.company-branding {
|
|
54
|
+
display: flex;
|
|
55
|
+
align-items: center;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
.company-link {
|
|
59
|
+
display: flex;
|
|
60
|
+
align-items: center;
|
|
61
|
+
gap: 12px;
|
|
62
|
+
text-decoration: none;
|
|
63
|
+
transition: all 0.3s ease;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
.company-link:hover {
|
|
67
|
+
opacity: 0.8;
|
|
68
|
+
transform: translateY(-2px);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
.company-logo {
|
|
72
|
+
height: 50px;
|
|
73
|
+
width: auto;
|
|
74
|
+
object-fit: contain;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
.company-name {
|
|
78
|
+
font-size: 1.2em;
|
|
79
|
+
font-weight: 600;
|
|
80
|
+
color: #2c3e50;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/* ===========================
|
|
84
|
+
REPORT FOOTER WITH COPYRIGHT AND LICENSE
|
|
85
|
+
=========================== */
|
|
86
|
+
.report-footer {
|
|
87
|
+
margin-top: 40px;
|
|
88
|
+
padding: 20px;
|
|
89
|
+
background: white;
|
|
90
|
+
border-radius: 8px;
|
|
91
|
+
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
|
92
|
+
text-align: center;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
.footer-content {
|
|
96
|
+
display: flex;
|
|
97
|
+
justify-content: center;
|
|
98
|
+
align-items: center;
|
|
99
|
+
flex-wrap: wrap;
|
|
100
|
+
gap: 15px;
|
|
101
|
+
color: #6c757d;
|
|
102
|
+
font-size: 0.9em;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
.footer-content .separator {
|
|
106
|
+
color: #dee2e6;
|
|
107
|
+
font-weight: bold;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
.footer-content a {
|
|
111
|
+
color: #3498db;
|
|
112
|
+
text-decoration: none;
|
|
113
|
+
transition: color 0.3s ease;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
.footer-content a:hover {
|
|
117
|
+
color: #2980b9;
|
|
118
|
+
text-decoration: underline;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/* ===========================
|
|
122
|
+
BUTTON
|
|
123
|
+
=========================== */
|
|
124
|
+
.hamburger {
|
|
125
|
+
position: fixed;
|
|
126
|
+
top: 20px;
|
|
127
|
+
left: 20px;
|
|
128
|
+
z-index: 1001;
|
|
129
|
+
background: linear-gradient(135deg, #3498db 0%, #2980b9 100%);
|
|
130
|
+
border: none;
|
|
131
|
+
border-radius: 8px;
|
|
132
|
+
padding: 12px;
|
|
133
|
+
cursor: pointer;
|
|
134
|
+
display: flex;
|
|
135
|
+
flex-direction: column;
|
|
136
|
+
gap: 5px;
|
|
137
|
+
box-shadow: 0 4px 12px rgba(52, 152, 219, 0.3);
|
|
138
|
+
transition: all 0.3s ease;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
.hamburger:hover {
|
|
142
|
+
background: linear-gradient(135deg, #2980b9 0%, #21618c 100%);
|
|
143
|
+
transform: scale(1.05);
|
|
144
|
+
box-shadow: 0 6px 16px rgba(52, 152, 219, 0.4);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
.hamburger:focus {
|
|
148
|
+
outline: 2px solid #3498db;
|
|
149
|
+
outline-offset: 2px;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
.hamburger span {
|
|
153
|
+
display: block;
|
|
154
|
+
width: 25px;
|
|
155
|
+
height: 3px;
|
|
156
|
+
background-color: white;
|
|
157
|
+
border-radius: 2px;
|
|
158
|
+
transition: all 0.3s ease;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
.btn-word {
|
|
162
|
+
background-color: #2B579A;
|
|
163
|
+
color: white;
|
|
164
|
+
border: none;
|
|
165
|
+
padding: 8px 16px;
|
|
166
|
+
border-radius: 4px;
|
|
167
|
+
cursor: pointer;
|
|
168
|
+
font-size: 14px;
|
|
169
|
+
display: inline-flex;
|
|
170
|
+
align-items: center;
|
|
171
|
+
gap: 6px;
|
|
172
|
+
white-space: nowrap;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/* ===========================
|
|
176
|
+
TABLE OF CONTENTS
|
|
177
|
+
=========================== */
|
|
178
|
+
.toc {
|
|
179
|
+
position: fixed;
|
|
180
|
+
top: 0;
|
|
181
|
+
left: -350px;
|
|
182
|
+
width: 320px;
|
|
183
|
+
height: 100vh;
|
|
184
|
+
background-color: white;
|
|
185
|
+
box-shadow: 2px 0 20px rgba(0,0,0,0.1);
|
|
186
|
+
z-index: 1002;
|
|
187
|
+
overflow-y: auto;
|
|
188
|
+
transition: left 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
|
189
|
+
padding: 20px;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
.toc.active {
|
|
193
|
+
left: 0;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
.toc-header {
|
|
197
|
+
display: flex;
|
|
198
|
+
justify-content: space-between;
|
|
199
|
+
align-items: center;
|
|
200
|
+
margin-bottom: 25px;
|
|
201
|
+
padding-bottom: 15px;
|
|
202
|
+
border-bottom: 2px solid #ecf0f1;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
.toc-header h2 {
|
|
206
|
+
margin: 0;
|
|
207
|
+
color: #2c3e50;
|
|
208
|
+
font-size: 1.4em;
|
|
209
|
+
border: none;
|
|
210
|
+
padding: 0;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
.close-btn {
|
|
214
|
+
background: none;
|
|
215
|
+
border: none;
|
|
216
|
+
font-size: 2em;
|
|
217
|
+
color: #95a5a6;
|
|
218
|
+
cursor: pointer;
|
|
219
|
+
padding: 0;
|
|
220
|
+
width: 35px;
|
|
221
|
+
height: 35px;
|
|
222
|
+
display: flex;
|
|
223
|
+
align-items: center;
|
|
224
|
+
justify-content: center;
|
|
225
|
+
transition: all 0.3s ease;
|
|
226
|
+
border-radius: 4px;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
.close-btn:hover {
|
|
230
|
+
color: #e74c3c;
|
|
231
|
+
background-color: #fef5f5;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
.close-btn:focus {
|
|
235
|
+
outline: 2px solid #3498db;
|
|
236
|
+
outline-offset: 2px;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
.toc-list {
|
|
240
|
+
list-style: none;
|
|
241
|
+
padding: 0;
|
|
242
|
+
margin: 0;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
.toc-list > li {
|
|
246
|
+
margin-bottom: 12px;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
.toc-list a {
|
|
250
|
+
display: block;
|
|
251
|
+
padding: 10px 14px;
|
|
252
|
+
color: #2c3e50;
|
|
253
|
+
text-decoration: none;
|
|
254
|
+
border-radius: 6px;
|
|
255
|
+
transition: all 0.3s ease;
|
|
256
|
+
font-weight: 500;
|
|
257
|
+
border-left: 3px solid transparent;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
.toc-list a:hover {
|
|
261
|
+
background-color: #e8f4f8;
|
|
262
|
+
color: #3498db;
|
|
263
|
+
border-left-color: #3498db;
|
|
264
|
+
transform: translateX(5px);
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
.toc-list a:focus {
|
|
268
|
+
outline: 2px solid #3498db;
|
|
269
|
+
outline-offset: 2px;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
.toc-list a.active {
|
|
273
|
+
background-color: #d6eef7;
|
|
274
|
+
color: #2980b9;
|
|
275
|
+
border-left-color: #2980b9;
|
|
276
|
+
font-weight: 600;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
.toc-sublist {
|
|
280
|
+
list-style: none;
|
|
281
|
+
padding-left: 20px;
|
|
282
|
+
margin-top: 8px;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
.toc-sublist li {
|
|
286
|
+
margin-bottom: 6px;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
.toc-sublist a {
|
|
290
|
+
font-size: 0.9em;
|
|
291
|
+
font-weight: normal;
|
|
292
|
+
padding: 8px 12px;
|
|
293
|
+
color: #7f8c8d;
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
.toc-sublist a:hover {
|
|
297
|
+
background-color: #f8f9fa;
|
|
298
|
+
color: #3498db;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
/* ===========================
|
|
302
|
+
OVERLAY
|
|
303
|
+
=========================== */
|
|
304
|
+
.overlay {
|
|
305
|
+
position: fixed;
|
|
306
|
+
top: 0;
|
|
307
|
+
left: 0;
|
|
308
|
+
width: 100%;
|
|
309
|
+
height: 100%;
|
|
310
|
+
background-color: rgba(0, 0, 0, 0.5);
|
|
311
|
+
z-index: 1000;
|
|
312
|
+
opacity: 0;
|
|
313
|
+
visibility: hidden;
|
|
314
|
+
transition: all 0.3s ease;
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
.overlay.active {
|
|
318
|
+
opacity: 1;
|
|
319
|
+
visibility: visible;
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
/* ===========================
|
|
323
|
+
MAIN CONTENT
|
|
324
|
+
=========================== */
|
|
325
|
+
.container {
|
|
326
|
+
background-color: white;
|
|
327
|
+
padding: 40px;
|
|
328
|
+
border-radius: 8px;
|
|
329
|
+
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
h1 {
|
|
333
|
+
color: #2c3e50;
|
|
334
|
+
border-bottom: 3px solid #3498db;
|
|
335
|
+
padding-bottom: 12px;
|
|
336
|
+
margin-bottom: 25px;
|
|
337
|
+
font-size: 2em;
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
h2 {
|
|
341
|
+
color: #34495e;
|
|
342
|
+
margin-top: 40px;
|
|
343
|
+
margin-bottom: 20px;
|
|
344
|
+
border-bottom: 2px solid #ecf0f1;
|
|
345
|
+
padding-bottom: 10px;
|
|
346
|
+
font-size: 1.6em;
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
h3 {
|
|
350
|
+
color: #546e7a;
|
|
351
|
+
margin-top: 25px;
|
|
352
|
+
margin-bottom: 12px;
|
|
353
|
+
font-size: 1.3em;
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
/* ===========================
|
|
357
|
+
STATUS BOXES
|
|
358
|
+
=========================== */
|
|
359
|
+
.info-box {
|
|
360
|
+
background: linear-gradient(135deg, #e8f4f8 0%, #d1ecf1 100%);
|
|
361
|
+
border-left: 4px solid #3498db;
|
|
362
|
+
padding: 18px;
|
|
363
|
+
margin: 25px 0;
|
|
364
|
+
border-radius: 6px;
|
|
365
|
+
box-shadow: 0 2px 4px rgba(52, 152, 219, 0.1);
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
.warning-box {
|
|
369
|
+
background: linear-gradient(135deg, #fff3cd 0%, #ffeaa7 100%);
|
|
370
|
+
border-left: 4px solid #f39c12;
|
|
371
|
+
padding: 18px;
|
|
372
|
+
margin: 25px 0;
|
|
373
|
+
border-radius: 6px;
|
|
374
|
+
box-shadow: 0 2px 4px rgba(243, 156, 18, 0.1);
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
.error-box {
|
|
378
|
+
background: linear-gradient(135deg, #f8d7da 0%, #f5c6cb 100%);
|
|
379
|
+
border-left: 4px solid #e74c3c;
|
|
380
|
+
padding: 18px;
|
|
381
|
+
margin: 25px 0;
|
|
382
|
+
border-radius: 6px;
|
|
383
|
+
box-shadow: 0 2px 4px rgba(231, 76, 60, 0.1);
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
.success-box {
|
|
387
|
+
background: linear-gradient(135deg, #d4edda 0%, #c3e6cb 100%);
|
|
388
|
+
border-left: 4px solid #27ae60;
|
|
389
|
+
padding: 18px;
|
|
390
|
+
margin: 25px 0;
|
|
391
|
+
border-radius: 6px;
|
|
392
|
+
box-shadow: 0 2px 4px rgba(39, 174, 96, 0.1);
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
/* ===========================
|
|
396
|
+
DATASET SECTIONS
|
|
397
|
+
=========================== */
|
|
398
|
+
.dataset-section {
|
|
399
|
+
margin: 35px 0;
|
|
400
|
+
padding: 25px;
|
|
401
|
+
background-color: #fafafa;
|
|
402
|
+
border-radius: 8px;
|
|
403
|
+
border: 1px solid #e0e0e0;
|
|
404
|
+
transition: all 0.3s ease;
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
.dataset-section:hover {
|
|
408
|
+
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08);
|
|
409
|
+
transform: translateY(-2px);
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
/* ===========================
|
|
413
|
+
LISTS
|
|
414
|
+
=========================== */
|
|
415
|
+
ul {
|
|
416
|
+
margin: 15px 0;
|
|
417
|
+
padding-left: 25px;
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
ul li {
|
|
421
|
+
margin: 8px 0;
|
|
422
|
+
line-height: 1.6;
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
.info-box ul,
|
|
426
|
+
.warning-box ul,
|
|
427
|
+
.error-box ul,
|
|
428
|
+
.success-box ul {
|
|
429
|
+
margin: 12px 0 0 0;
|
|
430
|
+
padding-left: 25px;
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
/* ===========================
|
|
434
|
+
KEY INFO STYLING
|
|
435
|
+
=========================== */
|
|
436
|
+
.key-info {
|
|
437
|
+
font-family: 'Courier New', monospace;
|
|
438
|
+
background-color: #f4f4f4;
|
|
439
|
+
padding: 4px 8px;
|
|
440
|
+
border-radius: 4px;
|
|
441
|
+
font-size: 0.95em;
|
|
442
|
+
color: #2c3e50;
|
|
443
|
+
border: 1px solid #dee2e6;
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
/* ===========================
|
|
447
|
+
STATUS TEXT COLORS
|
|
448
|
+
=========================== */
|
|
449
|
+
.success-text {
|
|
450
|
+
color: #27ae60;
|
|
451
|
+
font-weight: 600;
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
.warning-text {
|
|
455
|
+
color: #f39c12;
|
|
456
|
+
font-weight: 600;
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
.info-text {
|
|
460
|
+
color: #3498db;
|
|
461
|
+
font-weight: 600;
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
/* ===========================
|
|
465
|
+
LINKS
|
|
466
|
+
=========================== */
|
|
467
|
+
a {
|
|
468
|
+
color: #3498db;
|
|
469
|
+
text-decoration: none;
|
|
470
|
+
transition: all 0.2s ease;
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
a:hover {
|
|
474
|
+
color: #2980b9;
|
|
475
|
+
text-decoration: underline;
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
a:focus {
|
|
479
|
+
outline: 2px solid #3498db;
|
|
480
|
+
outline-offset: 2px;
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
/* ===========================
|
|
484
|
+
DETAILS/SUMMARY
|
|
485
|
+
=========================== */
|
|
486
|
+
details {
|
|
487
|
+
margin: 20px 0;
|
|
488
|
+
padding: 15px;
|
|
489
|
+
background-color: #f8f9fa;
|
|
490
|
+
border-radius: 6px;
|
|
491
|
+
border: 1px solid #dee2e6;
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
summary {
|
|
495
|
+
cursor: pointer;
|
|
496
|
+
font-weight: 600;
|
|
497
|
+
padding: 8px;
|
|
498
|
+
color: #495057;
|
|
499
|
+
transition: color 0.2s ease;
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
summary:hover {
|
|
503
|
+
color: #3498db;
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
summary:focus {
|
|
507
|
+
outline: 2px solid #3498db;
|
|
508
|
+
outline-offset: 2px;
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
details[open] summary {
|
|
512
|
+
margin-bottom: 12px;
|
|
513
|
+
padding-bottom: 12px;
|
|
514
|
+
border-bottom: 1px solid #dee2e6;
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
details ul {
|
|
518
|
+
margin-top: 12px;
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
/* ===========================
|
|
522
|
+
TEXT ELEMENTS
|
|
523
|
+
=========================== */
|
|
524
|
+
strong {
|
|
525
|
+
color: #2c3e50;
|
|
526
|
+
font-weight: 600;
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
em {
|
|
530
|
+
color: #6c757d;
|
|
531
|
+
font-style: italic;
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
code {
|
|
535
|
+
background-color: #f8f9fa;
|
|
536
|
+
padding: 3px 6px;
|
|
537
|
+
border-radius: 4px;
|
|
538
|
+
font-family: 'Courier New', monospace;
|
|
539
|
+
color: #e83e8c;
|
|
540
|
+
font-size: 0.9em;
|
|
541
|
+
border: 1px solid #e9ecef;
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
p {
|
|
545
|
+
margin: 12px 0;
|
|
546
|
+
line-height: 1.7;
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
/* ===========================
|
|
550
|
+
PRINT STYLES
|
|
551
|
+
=========================== */
|
|
552
|
+
@media print {
|
|
553
|
+
body {
|
|
554
|
+
padding-left: 20px;
|
|
555
|
+
background: white;
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
.hamburger,
|
|
559
|
+
.toc,
|
|
560
|
+
.overlay {
|
|
561
|
+
display: none !important;
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
.report-header,
|
|
565
|
+
.container,
|
|
566
|
+
.report-footer {
|
|
567
|
+
box-shadow: none;
|
|
568
|
+
padding: 20px;
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
.dataset-section {
|
|
572
|
+
page-break-inside: avoid;
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
.company-link {
|
|
576
|
+
pointer-events: none;
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
a {
|
|
580
|
+
color: #000;
|
|
581
|
+
text-decoration: underline;
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
.footer-content a {
|
|
585
|
+
color: #000;
|
|
586
|
+
}
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
/* ===========================
|
|
590
|
+
RESPONSIVE DESIGN
|
|
591
|
+
=========================== */
|
|
592
|
+
@media (max-width: 768px) {
|
|
593
|
+
body {
|
|
594
|
+
padding: 15px;
|
|
595
|
+
padding-left: 70px;
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
.report-header {
|
|
599
|
+
flex-direction: column;
|
|
600
|
+
gap: 15px;
|
|
601
|
+
text-align: center;
|
|
602
|
+
padding: 20px;
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
.report-header h1 {
|
|
606
|
+
font-size: 1.6em;
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
.company-logo {
|
|
610
|
+
height: 40px;
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
.company-name {
|
|
614
|
+
font-size: 1em;
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
.container {
|
|
618
|
+
padding: 25px;
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
.hamburger {
|
|
622
|
+
top: 15px;
|
|
623
|
+
left: 15px;
|
|
624
|
+
padding: 10px;
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
.toc {
|
|
628
|
+
width: 280px;
|
|
629
|
+
left: -300px;
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
h2 {
|
|
633
|
+
font-size: 1.4em;
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
h3 {
|
|
637
|
+
font-size: 1.2em;
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
.footer-content {
|
|
641
|
+
flex-direction: column;
|
|
642
|
+
gap: 8px;
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
.footer-content .separator {
|
|
646
|
+
display: none;
|
|
647
|
+
}
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
@media (max-width: 480px) {
|
|
651
|
+
body {
|
|
652
|
+
padding: 10px;
|
|
653
|
+
padding-left: 60px;
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
.container,
|
|
657
|
+
.report-header,
|
|
658
|
+
.report-footer {
|
|
659
|
+
padding: 20px;
|
|
660
|
+
}
|
|
661
|
+
|
|
662
|
+
.toc {
|
|
663
|
+
width: 100%;
|
|
664
|
+
left: -100%;
|
|
665
|
+
}
|
|
666
|
+
|
|
667
|
+
.report-header h1 {
|
|
668
|
+
font-size: 1.4em;
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
h2 {
|
|
672
|
+
font-size: 1.2em;
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
.company-logo {
|
|
676
|
+
height: 35px;
|
|
677
|
+
}
|
|
678
|
+
}
|
|
679
|
+
|
|
680
|
+
/* ===========================
|
|
681
|
+
ACCESSIBILITY
|
|
682
|
+
=========================== */
|
|
683
|
+
@media (prefers-reduced-motion: reduce) {
|
|
684
|
+
* {
|
|
685
|
+
animation: none !important;
|
|
686
|
+
transition: none !important;
|
|
687
|
+
}
|
|
688
|
+
}
|
|
689
|
+
|
|
690
|
+
@media (prefers-contrast: high) {
|
|
691
|
+
.container,
|
|
692
|
+
.report-header,
|
|
693
|
+
.report-footer {
|
|
694
|
+
border: 2px solid #000;
|
|
695
|
+
}
|
|
696
|
+
|
|
697
|
+
.info-box,
|
|
698
|
+
.warning-box,
|
|
699
|
+
.error-box,
|
|
700
|
+
.success-box {
|
|
701
|
+
border: 2px solid #000;
|
|
702
|
+
}
|
|
703
|
+
}
|
|
704
|
+
|
|
705
|
+
:focus-visible {
|
|
706
|
+
outline: 2px solid #3498db;
|
|
707
|
+
outline-offset: 2px;
|
|
708
|
+
}
|
|
709
|
+
</style>
|
|
710
|
+
<title><%= title %> - High Level Report</title>
|
|
711
|
+
</head>
|
|
712
|
+
<body>
|
|
713
|
+
<!-- Hamburger button -->
|
|
714
|
+
<button class="hamburger" id="hamburger" aria-label="Toggle table of contents" aria-expanded="false" aria-controls="toc">
|
|
715
|
+
<span></span>
|
|
716
|
+
<span></span>
|
|
717
|
+
<span></span>
|
|
718
|
+
</button>
|
|
719
|
+
|
|
720
|
+
<!-- Table of contents (sidebar) -->
|
|
721
|
+
<nav class="toc" id="toc" aria-label="Table of contents">
|
|
722
|
+
<div class="toc-header">
|
|
723
|
+
<h2>Table of Contents</h2>
|
|
724
|
+
<button class="close-btn" id="close-toc" aria-label="Close table of contents">×</button>
|
|
725
|
+
</div>
|
|
726
|
+
<ul class="toc-list">
|
|
727
|
+
<li><a href="#summary">Summary</a></li>
|
|
728
|
+
<li><a href="#configuration">Configuration</a></li>
|
|
729
|
+
<li><a href="#datasets-analysis">Datasets Analysis</a>
|
|
730
|
+
<ul class="toc-sublist">
|
|
731
|
+
<% dataset_reports.each do |ds_name, report| %>
|
|
732
|
+
<li><a href="#ds-<%= ds_name %>"><%= ds_name %></a></li>
|
|
733
|
+
<% end %>
|
|
734
|
+
</ul>
|
|
735
|
+
</li>
|
|
736
|
+
<% if externe_file_type != 'csv' %>
|
|
737
|
+
<li><a href="#output-file">Output File</a></li>
|
|
738
|
+
<% end %>
|
|
739
|
+
</ul>
|
|
740
|
+
</nav>
|
|
741
|
+
|
|
742
|
+
<!-- Overlay to close TOC by clicking outside -->
|
|
743
|
+
<div class="overlay" id="overlay" aria-hidden="true"></div>
|
|
744
|
+
|
|
745
|
+
<!-- Report Header with Branding -->
|
|
746
|
+
<header class="report-header">
|
|
747
|
+
<h1><%= title %> - High Level Report</h1>
|
|
748
|
+
<div class="company-branding">
|
|
749
|
+
<a href="https://adclin.com" target="_blank" rel="noopener noreferrer" class="company-link">
|
|
750
|
+
<% if @logo_base64 %>
|
|
751
|
+
<img src="<%= @logo_base64 %>" alt="AdClin Logo" class="company-logo">
|
|
752
|
+
<% else %>
|
|
753
|
+
<img src="/Contact-LOGO.png" alt="AdClin Logo" class="company-logo">
|
|
754
|
+
<% end %>
|
|
755
|
+
</a>
|
|
756
|
+
</div>
|
|
757
|
+
</header>
|
|
758
|
+
|
|
759
|
+
<main class="container">
|
|
760
|
+
<div class="info-box" id="summary">
|
|
761
|
+
<div style="display: flex; justify-content: space-between; align-items: center;">
|
|
762
|
+
<p style="margin: 0;">Report automatically generated on <%= Time.now.strftime("%Y-%m-%d at %H:%M") %></p>
|
|
763
|
+
<% if @web_mode %>
|
|
764
|
+
<button class="btn-word" id="convert-btn" onclick="convertToWord()"> 📄 Word </button>
|
|
765
|
+
<% end %>
|
|
766
|
+
</div>
|
|
767
|
+
</div>
|
|
768
|
+
|
|
769
|
+
<section id="configuration">
|
|
770
|
+
<h2>Configuration</h2>
|
|
771
|
+
<p>
|
|
772
|
+
This report is generated based on the information provided in the configuration form.
|
|
773
|
+
This configuration is saved in the file: <code><%= init_reports[:config_name] %></code>
|
|
774
|
+
</p>
|
|
775
|
+
|
|
776
|
+
<h3>Report Coverage:</h3>
|
|
777
|
+
<ul>
|
|
778
|
+
<li>Presence of non-ASCII characters in the data</li>
|
|
779
|
+
<li>Validity of keys declared in define.xml</li>
|
|
780
|
+
<li>Ad hoc search for a natural key in all datasets</li>
|
|
781
|
+
</ul>
|
|
782
|
+
|
|
783
|
+
<h3>Define.xml Information</h3>
|
|
784
|
+
<% define_info = init_reports[:define_information] %>
|
|
785
|
+
<ul>
|
|
786
|
+
<li><strong>Path:</strong> <%= define_info[:define_path] || 'Not specified' %></li>
|
|
787
|
+
<% if define_info[:load] %>
|
|
788
|
+
<li><strong>Status:</strong> <span class="success-text">Loaded successfully</span></li>
|
|
789
|
+
<% elsif define_info[:define_path] == '-' %>
|
|
790
|
+
<li><strong>Status:</strong> <span class="info-text">Not loaded (as expected)</span></li>
|
|
791
|
+
<% else %>
|
|
792
|
+
<li><strong>Status:</strong> <span class="warning-text">Not loaded</span></li>
|
|
793
|
+
<% end %>
|
|
794
|
+
</ul>
|
|
795
|
+
|
|
796
|
+
<h3>Datasets Information</h3>
|
|
797
|
+
<% data_info = init_reports[:data_information] %>
|
|
798
|
+
<ul>
|
|
799
|
+
<li><strong>Source directory:</strong> <%= data_info[:directory_name] %></li>
|
|
800
|
+
<li><strong>Number of files detected:</strong> <%= data_info[:file_number] %></li>
|
|
801
|
+
</ul>
|
|
802
|
+
</section>
|
|
803
|
+
|
|
804
|
+
<h2 id="datasets-analysis">Datasets Analysis</h2>
|
|
805
|
+
|
|
806
|
+
<% dataset_reports.each do |ds_name, report| %>
|
|
807
|
+
<article class="dataset-section" id="ds-<%= ds_name %>">
|
|
808
|
+
<h3>Dataset: <%= ds_name %></h3>
|
|
809
|
+
|
|
810
|
+
<% if report[:record_count] %>
|
|
811
|
+
<p><strong>Number of records:</strong> <%= report[:record_count] %></p>
|
|
812
|
+
<% end %>
|
|
813
|
+
|
|
814
|
+
<% if report[:candidates_type] %>
|
|
815
|
+
<p><strong>Dataset type:</strong> <%= report[:candidates_type] %></p>
|
|
816
|
+
<% end %>
|
|
817
|
+
|
|
818
|
+
<!-- ASCII Check -->
|
|
819
|
+
<% if report[:ascii_check] %>
|
|
820
|
+
<% if report[:ascii_check][:valid] %>
|
|
821
|
+
<div class="success-box" role="status">
|
|
822
|
+
<strong>✓ ASCII Verification</strong>
|
|
823
|
+
<p>No non-ASCII characters found</p>
|
|
824
|
+
</div>
|
|
825
|
+
<% else %>
|
|
826
|
+
<div class="error-box" role="alert">
|
|
827
|
+
<strong>✗ Non-ASCII Characters Detected</strong>
|
|
828
|
+
<ul>
|
|
829
|
+
<% report[:ascii_check][:issues].each do |issue| %>
|
|
830
|
+
<li><%= issue %></li>
|
|
831
|
+
<% end %>
|
|
832
|
+
</ul>
|
|
833
|
+
</div>
|
|
834
|
+
<% end %>
|
|
835
|
+
<% end %>
|
|
836
|
+
|
|
837
|
+
<!-- Define Key Check -->
|
|
838
|
+
<% if report[:define_key_check] %>
|
|
839
|
+
<% if report[:define_key_check][:valid]%>
|
|
840
|
+
<% if report[:define_key_check][:key] %>
|
|
841
|
+
<div class="success-box" role="status">
|
|
842
|
+
<strong>✓ Valid define.xml Key</strong>
|
|
843
|
+
<p>Key: <span class="key-info"><%= report[:define_key_check][:key].join(', ') %></span></p>
|
|
844
|
+
</div>
|
|
845
|
+
<% end %>
|
|
846
|
+
<% else %>
|
|
847
|
+
<div class="error-box" role="alert">
|
|
848
|
+
<strong>✗ Invalid define.xml Key</strong>
|
|
849
|
+
<% if report[:define_key_check][:absent] %>
|
|
850
|
+
<p>dataset not found in the define.xml</p>
|
|
851
|
+
<% end %>
|
|
852
|
+
<% if report[:define_key_check][:key] %>
|
|
853
|
+
<p>Tested key: <span class="key-info"><%= report[:define_key_check][:key].join(', ') %></span></p>
|
|
854
|
+
<% end %>
|
|
855
|
+
<% if report[:define_key_check][:duplicate_file] %>
|
|
856
|
+
<p>File containing duplicated records:
|
|
857
|
+
<a href="/csv_view?file=<%= URI.encode_www_form_component(report[:define_key_check][:duplicate_file]) %>"
|
|
858
|
+
target="_blank"
|
|
859
|
+
rel="noopener noreferrer">
|
|
860
|
+
<%= File.basename(report[:define_key_check][:duplicate_file]) %>
|
|
861
|
+
</a>
|
|
862
|
+
</p>
|
|
863
|
+
<% end %>
|
|
864
|
+
</div>
|
|
865
|
+
<% end %>
|
|
866
|
+
<% end %>
|
|
867
|
+
|
|
868
|
+
<!-- Data Key Check -->
|
|
869
|
+
<% if report[:data_key_check] %>
|
|
870
|
+
<%
|
|
871
|
+
# Handle case where data_key_check can be an array (SE, SV)
|
|
872
|
+
checks = report[:data_key_check].is_a?(Array) ? report[:data_key_check] : [report[:data_key_check]]
|
|
873
|
+
%>
|
|
874
|
+
|
|
875
|
+
<% checks.each_with_index do |check, idx| %>
|
|
876
|
+
<% if check[:valid] %>
|
|
877
|
+
<div class="success-box" role="status">
|
|
878
|
+
<strong>✓ Minimum Key Found <%= checks.size > 1 ? "(Option #{idx + 1})" : "" %></strong>
|
|
879
|
+
<p>Key: <span class="key-info"><%= check[:key].join(', ') %></span></p>
|
|
880
|
+
</div>
|
|
881
|
+
<% else %>
|
|
882
|
+
<div class="warning-box" role="status">
|
|
883
|
+
<strong>⚠ No Valid Key Found <%= checks.size > 1 ? "(Option #{idx + 1})" : "" %></strong>
|
|
884
|
+
<% if check[:candidates] %>
|
|
885
|
+
<p><em>Tested variables: <%= check[:candidates].join(', ') %></em></p>
|
|
886
|
+
<% end %>
|
|
887
|
+
<% if check[:last_valid_key] %>
|
|
888
|
+
<p>Last key tested: <span class="key-info"><%= check[:last_valid_key].join(', ') %></span></p>
|
|
889
|
+
<% end %>
|
|
890
|
+
<% if check[:duplicate_file] %>
|
|
891
|
+
<p>File containing duplicated records:
|
|
892
|
+
<a href="/csv_view?file=<%= URI.encode_www_form_component(check[:duplicate_file]) %>&last_valid_key=<%= URI.encode_www_form_component(check[:last_valid_key].join(',')) %>"
|
|
893
|
+
target="_blank"
|
|
894
|
+
rel="noopener noreferrer">
|
|
895
|
+
<%= File.basename(check[:duplicate_file]) %>
|
|
896
|
+
</a>
|
|
897
|
+
</p>
|
|
898
|
+
<% end %>
|
|
899
|
+
</div>
|
|
900
|
+
<% end %>
|
|
901
|
+
<% end %>
|
|
902
|
+
<% end %>
|
|
903
|
+
|
|
904
|
+
<!-- Verbose Details -->
|
|
905
|
+
<% if report[:verbose_details] && report[:verbose_details].any? %>
|
|
906
|
+
<details>
|
|
907
|
+
<summary>Processing Details</summary>
|
|
908
|
+
<ul>
|
|
909
|
+
<% report[:verbose_details].each do |detail| %>
|
|
910
|
+
<li><%= detail %></li>
|
|
911
|
+
<% end %>
|
|
912
|
+
</ul>
|
|
913
|
+
</details>
|
|
914
|
+
<% end %>
|
|
915
|
+
</article>
|
|
916
|
+
<% end %>
|
|
917
|
+
|
|
918
|
+
<!-- Output File Section -->
|
|
919
|
+
<% if output_file %>
|
|
920
|
+
<section id="output-file">
|
|
921
|
+
<h2>Output File</h2>
|
|
922
|
+
<p>File containing duplicated records: <strong><%= File.basename(output_file) %></strong></p>
|
|
923
|
+
<p>Full path: <code><%= output_file %></code></p>
|
|
924
|
+
</section>
|
|
925
|
+
<% end %>
|
|
926
|
+
</main>
|
|
927
|
+
|
|
928
|
+
<!-- Report Footer with Copyright and License -->
|
|
929
|
+
<footer class="report-footer">
|
|
930
|
+
<div class="footer-content">
|
|
931
|
+
<span class="copyright">© 1999-2026 AdClin. All rights reserved.</span>
|
|
932
|
+
<span class="separator">•</span>
|
|
933
|
+
<span class="license">Licensed under <a href="/LICENSE" target="_blank" rel="noopener noreferrer">AGPL v3</a></span>
|
|
934
|
+
</div>
|
|
935
|
+
</footer>
|
|
936
|
+
|
|
937
|
+
<script>
|
|
938
|
+
// Download HTML file as Word document
|
|
939
|
+
async function convertToWord() {
|
|
940
|
+
const chemin = window.location.pathname.replace(/^\/telecharger\//, '');
|
|
941
|
+
const response = await fetch('/telecharger_html_word/' + chemin);
|
|
942
|
+
const data = await response.json();
|
|
943
|
+
if (data.success) {
|
|
944
|
+
const cheminDocx = chemin.replace(/\.html$/, '.docx');
|
|
945
|
+
window.location.href = '/telecharger/' + cheminDocx;
|
|
946
|
+
} else {
|
|
947
|
+
alert('❌ ' + data.erreur);
|
|
948
|
+
}
|
|
949
|
+
}
|
|
950
|
+
// Enhanced TOC functionality with accessibility
|
|
951
|
+
|
|
952
|
+
// DOM Elements
|
|
953
|
+
const hamburger = document.getElementById('hamburger');
|
|
954
|
+
const toc = document.getElementById('toc');
|
|
955
|
+
const overlay = document.getElementById('overlay');
|
|
956
|
+
const closeBtn = document.getElementById('close-toc');
|
|
957
|
+
const tocLinks = document.querySelectorAll('.toc-list a');
|
|
958
|
+
|
|
959
|
+
// Open TOC
|
|
960
|
+
function openTOC() {
|
|
961
|
+
if (toc && overlay) {
|
|
962
|
+
toc.classList.add('active');
|
|
963
|
+
overlay.classList.add('active');
|
|
964
|
+
document.body.style.overflow = 'hidden';
|
|
965
|
+
|
|
966
|
+
if (hamburger) hamburger.setAttribute('aria-expanded', 'true');
|
|
967
|
+
if (overlay) overlay.setAttribute('aria-hidden', 'false');
|
|
968
|
+
|
|
969
|
+
if (closeBtn) {
|
|
970
|
+
setTimeout(() => closeBtn.focus(), 100);
|
|
971
|
+
}
|
|
972
|
+
}
|
|
973
|
+
}
|
|
974
|
+
|
|
975
|
+
// Close TOC
|
|
976
|
+
function closeTOC() {
|
|
977
|
+
if (toc && overlay) {
|
|
978
|
+
toc.classList.remove('active');
|
|
979
|
+
overlay.classList.remove('active');
|
|
980
|
+
document.body.style.overflow = '';
|
|
981
|
+
|
|
982
|
+
if (hamburger) hamburger.setAttribute('aria-expanded', 'false');
|
|
983
|
+
if (overlay) overlay.setAttribute('aria-hidden', 'true');
|
|
984
|
+
|
|
985
|
+
if (hamburger) {
|
|
986
|
+
setTimeout(() => hamburger.focus(), 100);
|
|
987
|
+
}
|
|
988
|
+
}
|
|
989
|
+
}
|
|
990
|
+
|
|
991
|
+
// Event Listeners
|
|
992
|
+
if (hamburger) {
|
|
993
|
+
hamburger.addEventListener('click', openTOC);
|
|
994
|
+
hamburger.addEventListener('keydown', (e) => {
|
|
995
|
+
if (e.key === 'Enter' || e.key === ' ') {
|
|
996
|
+
e.preventDefault();
|
|
997
|
+
openTOC();
|
|
998
|
+
}
|
|
999
|
+
});
|
|
1000
|
+
}
|
|
1001
|
+
|
|
1002
|
+
if (closeBtn) {
|
|
1003
|
+
closeBtn.addEventListener('click', closeTOC);
|
|
1004
|
+
closeBtn.addEventListener('keydown', (e) => {
|
|
1005
|
+
if (e.key === 'Enter' || e.key === ' ') {
|
|
1006
|
+
e.preventDefault();
|
|
1007
|
+
closeTOC();
|
|
1008
|
+
}
|
|
1009
|
+
});
|
|
1010
|
+
}
|
|
1011
|
+
|
|
1012
|
+
if (overlay) {
|
|
1013
|
+
overlay.addEventListener('click', closeTOC);
|
|
1014
|
+
}
|
|
1015
|
+
|
|
1016
|
+
tocLinks.forEach(link => {
|
|
1017
|
+
link.addEventListener('click', () => {
|
|
1018
|
+
closeTOC();
|
|
1019
|
+
});
|
|
1020
|
+
});
|
|
1021
|
+
|
|
1022
|
+
document.addEventListener('keydown', (e) => {
|
|
1023
|
+
if (e.key === 'Escape' && toc && toc.classList.contains('active')) {
|
|
1024
|
+
closeTOC();
|
|
1025
|
+
}
|
|
1026
|
+
});
|
|
1027
|
+
|
|
1028
|
+
if (toc) {
|
|
1029
|
+
toc.addEventListener('keydown', (e) => {
|
|
1030
|
+
if (e.key === 'Tab') {
|
|
1031
|
+
const focusableElements = toc.querySelectorAll(
|
|
1032
|
+
'button, a, input, select, textarea, [tabindex]:not([tabindex="-1"])'
|
|
1033
|
+
);
|
|
1034
|
+
|
|
1035
|
+
if (focusableElements.length === 0) return;
|
|
1036
|
+
|
|
1037
|
+
const firstElement = focusableElements[0];
|
|
1038
|
+
const lastElement = focusableElements[focusableElements.length - 1];
|
|
1039
|
+
|
|
1040
|
+
if (e.shiftKey && document.activeElement === firstElement) {
|
|
1041
|
+
e.preventDefault();
|
|
1042
|
+
lastElement.focus();
|
|
1043
|
+
} else if (!e.shiftKey && document.activeElement === lastElement) {
|
|
1044
|
+
e.preventDefault();
|
|
1045
|
+
firstElement.focus();
|
|
1046
|
+
}
|
|
1047
|
+
}
|
|
1048
|
+
});
|
|
1049
|
+
}
|
|
1050
|
+
|
|
1051
|
+
// Colour coding of datasets in the TOC according to their maximum error level
|
|
1052
|
+
function coloriserTOCDatasets() {
|
|
1053
|
+
document.querySelectorAll('.toc-sublist a').forEach(link => {
|
|
1054
|
+
const href = link.getAttribute('href');
|
|
1055
|
+
if (!href) return;
|
|
1056
|
+
|
|
1057
|
+
const section = document.querySelector(href);
|
|
1058
|
+
if (!section) return;
|
|
1059
|
+
|
|
1060
|
+
const aErreur = section.querySelector('.error-box');
|
|
1061
|
+
const aWarning = section.querySelector('.warning-box');
|
|
1062
|
+
|
|
1063
|
+
if (aErreur) {
|
|
1064
|
+
// Rouge - priorité maximale
|
|
1065
|
+
link.style.backgroundColor = '#f8d7da';
|
|
1066
|
+
link.style.borderLeftColor = '#e74c3c';
|
|
1067
|
+
link.style.color = '#c0392b';
|
|
1068
|
+
} else if (aWarning) {
|
|
1069
|
+
// Orange
|
|
1070
|
+
link.style.backgroundColor = '#fff3cd';
|
|
1071
|
+
link.style.borderLeftColor = '#f39c12';
|
|
1072
|
+
link.style.color = '#856404';
|
|
1073
|
+
}
|
|
1074
|
+
// Vert (success-box uniquement) → aucune couleur appliquée
|
|
1075
|
+
});
|
|
1076
|
+
}
|
|
1077
|
+
|
|
1078
|
+
// Attendre que le DOM soit prêt
|
|
1079
|
+
if (document.readyState === 'loading') {
|
|
1080
|
+
document.addEventListener('DOMContentLoaded', coloriserTOCDatasets);
|
|
1081
|
+
} else {
|
|
1082
|
+
coloriserTOCDatasets();
|
|
1083
|
+
}
|
|
1084
|
+
|
|
1085
|
+
// Smooth scroll for anchor links
|
|
1086
|
+
document.querySelectorAll('a[href^="#"]').forEach(anchor => {
|
|
1087
|
+
anchor.addEventListener('click', function (e) {
|
|
1088
|
+
e.preventDefault();
|
|
1089
|
+
const target = document.querySelector(this.getAttribute('href'));
|
|
1090
|
+
|
|
1091
|
+
if (target) {
|
|
1092
|
+
const offset = 20;
|
|
1093
|
+
const targetPosition = target.getBoundingClientRect().top + window.pageYOffset - offset;
|
|
1094
|
+
|
|
1095
|
+
window.scrollTo({
|
|
1096
|
+
top: targetPosition,
|
|
1097
|
+
behavior: 'smooth'
|
|
1098
|
+
});
|
|
1099
|
+
|
|
1100
|
+
target.setAttribute('tabindex', '-1');
|
|
1101
|
+
target.focus();
|
|
1102
|
+
target.addEventListener('blur', () => {
|
|
1103
|
+
target.removeAttribute('tabindex');
|
|
1104
|
+
}, { once: true });
|
|
1105
|
+
}
|
|
1106
|
+
});
|
|
1107
|
+
});
|
|
1108
|
+
|
|
1109
|
+
// Highlight current section in TOC
|
|
1110
|
+
let observerOptions = {
|
|
1111
|
+
root: null,
|
|
1112
|
+
rootMargin: '-20% 0px -35% 0px',
|
|
1113
|
+
threshold: 0
|
|
1114
|
+
};
|
|
1115
|
+
|
|
1116
|
+
let observer = new IntersectionObserver((entries) => {
|
|
1117
|
+
entries.forEach(entry => {
|
|
1118
|
+
if (entry.isIntersecting) {
|
|
1119
|
+
const id = entry.target.getAttribute('id');
|
|
1120
|
+
|
|
1121
|
+
tocLinks.forEach(link => {
|
|
1122
|
+
link.classList.remove('active');
|
|
1123
|
+
});
|
|
1124
|
+
|
|
1125
|
+
const activeLink = document.querySelector(`.toc-list a[href="#${id}"]`);
|
|
1126
|
+
if (activeLink) {
|
|
1127
|
+
activeLink.classList.add('active');
|
|
1128
|
+
}
|
|
1129
|
+
}
|
|
1130
|
+
});
|
|
1131
|
+
}, observerOptions);
|
|
1132
|
+
|
|
1133
|
+
document.querySelectorAll('[id]').forEach(section => {
|
|
1134
|
+
if (section.tagName.match(/^H[1-3]$/) || section.classList.contains('dataset-section')) {
|
|
1135
|
+
observer.observe(section);
|
|
1136
|
+
}
|
|
1137
|
+
});
|
|
1138
|
+
|
|
1139
|
+
document.addEventListener('DOMContentLoaded', () => {
|
|
1140
|
+
console.log('Report loaded successfully');
|
|
1141
|
+
});
|
|
1142
|
+
</script>
|
|
1143
|
+
</body>
|
|
1144
|
+
</html>
|