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.
@@ -0,0 +1,448 @@
1
+ /*
2
+ Copyright (c) 2026 AdClin
3
+ Licensed under the GNU Affero General Public License v3.0 or later.
4
+ See the LICENSE file for details.
5
+ */
6
+
7
+ /* styles_csv.css - Enhanced CSV view styling with branding */
8
+
9
+ /* ===========================
10
+ BASE STYLES
11
+ =========================== */
12
+ body {
13
+ font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
14
+ margin: 0;
15
+ padding: 20px;
16
+ background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%);
17
+ min-height: 100vh;
18
+ }
19
+
20
+ /* ===========================
21
+ CSV HEADER WITH BRANDING
22
+ =========================== */
23
+ .csv-header {
24
+ display: flex;
25
+ justify-content: space-between;
26
+ align-items: center;
27
+ background: white;
28
+ padding: 25px;
29
+ border-radius: 8px;
30
+ margin-bottom: 20px;
31
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
32
+ }
33
+
34
+ .csv-header h1 {
35
+ margin: 0;
36
+ padding: 0;
37
+ border: none;
38
+ color: #2c3e50;
39
+ font-size: 1.8em;
40
+ }
41
+
42
+ .company-branding {
43
+ display: flex;
44
+ align-items: center;
45
+ }
46
+
47
+ .company-link {
48
+ display: flex;
49
+ align-items: center;
50
+ gap: 12px;
51
+ text-decoration: none;
52
+ transition: all 0.3s ease;
53
+ }
54
+
55
+ .company-link:hover {
56
+ opacity: 0.8;
57
+ transform: translateY(-2px);
58
+ }
59
+
60
+ .company-link:focus {
61
+ outline: 2px solid #007bff;
62
+ outline-offset: 2px;
63
+ }
64
+
65
+ .company-logo {
66
+ height: 50px;
67
+ width: auto;
68
+ object-fit: contain;
69
+ }
70
+
71
+ .company-name {
72
+ font-size: 1.2em;
73
+ font-weight: 600;
74
+ color: #2c3e50;
75
+ }
76
+
77
+ /* ===========================
78
+ CSV FOOTER WITH COPYRIGHT AND LICENSE
79
+ =========================== */
80
+ .csv-footer {
81
+ margin-top: 30px;
82
+ padding: 20px;
83
+ background: white;
84
+ border-radius: 8px;
85
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
86
+ text-align: center;
87
+ }
88
+
89
+ .footer-content {
90
+ display: flex;
91
+ justify-content: center;
92
+ align-items: center;
93
+ flex-wrap: wrap;
94
+ gap: 15px;
95
+ color: #6c757d;
96
+ font-size: 0.9em;
97
+ margin-bottom: 10px;
98
+ }
99
+
100
+ .footer-content .separator {
101
+ color: #dee2e6;
102
+ font-weight: bold;
103
+ }
104
+
105
+ .footer-content a {
106
+ color: #007bff;
107
+ text-decoration: none;
108
+ transition: color 0.3s ease;
109
+ }
110
+
111
+ .footer-content a:hover {
112
+ color: #0056b3;
113
+ text-decoration: underline;
114
+ }
115
+
116
+ .generated-date {
117
+ color: #6c757d;
118
+ font-size: 0.85em;
119
+ margin: 5px 0 0 0;
120
+ }
121
+
122
+ /* ===========================
123
+ INFO SECTION
124
+ =========================== */
125
+ .info-section {
126
+ background: linear-gradient(135deg, #e8f4f8 0%, #d1ecf1 100%);
127
+ border-left: 4px solid #3498db;
128
+ padding: 20px;
129
+ border-radius: 6px;
130
+ margin-bottom: 20px;
131
+ box-shadow: 0 2px 4px rgba(52, 152, 219, 0.1);
132
+ }
133
+
134
+ .info-section p {
135
+ margin: 10px 0;
136
+ line-height: 1.6;
137
+ color: #2c3e50;
138
+ }
139
+
140
+ .info-section strong {
141
+ color: #2980b9;
142
+ }
143
+
144
+ /* ===========================
145
+ ACTIONS SECTION
146
+ =========================== */
147
+ .actions {
148
+ margin-bottom: 20px;
149
+ display: flex;
150
+ gap: 10px;
151
+ flex-wrap: wrap;
152
+ }
153
+
154
+ .btn-export {
155
+ display: inline-flex;
156
+ align-items: center;
157
+ gap: 8px;
158
+ padding: 12px 24px;
159
+ border: none;
160
+ border-radius: 6px;
161
+ background: linear-gradient(135deg, #007bff 0%, #0056b3 100%);
162
+ color: white;
163
+ font-size: 14px;
164
+ font-weight: 600;
165
+ text-decoration: none;
166
+ box-shadow: 0 4px 8px rgba(0, 123, 255, 0.2);
167
+ transition: all 0.3s ease;
168
+ cursor: pointer;
169
+ }
170
+
171
+ .btn-export:hover {
172
+ background: linear-gradient(135deg, #0056b3 0%, #004085 100%);
173
+ transform: translateY(-2px);
174
+ box-shadow: 0 6px 12px rgba(0, 123, 255, 0.3);
175
+ }
176
+
177
+ .btn-export:active {
178
+ transform: translateY(0);
179
+ }
180
+
181
+ .btn-export:focus {
182
+ outline: 2px solid #007bff;
183
+ outline-offset: 2px;
184
+ }
185
+
186
+ /* ===========================
187
+ TABLE CONTAINER
188
+ =========================== */
189
+ .table-container {
190
+ background: white;
191
+ border-radius: 8px;
192
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
193
+ overflow: auto;
194
+ max-height: calc(100vh - 450px);
195
+ }
196
+
197
+ /* ===========================
198
+ TABLE STYLES
199
+ =========================== */
200
+ table {
201
+ border-collapse: collapse;
202
+ width: max-content;
203
+ min-width: 100%;
204
+ font-size: 13px;
205
+ }
206
+
207
+ th, td {
208
+ border: 1px solid #dee2e6;
209
+ padding: 10px 12px;
210
+ white-space: nowrap;
211
+ text-align: left;
212
+ }
213
+
214
+ /* ===========================
215
+ TABLE HEADER
216
+ =========================== */
217
+ thead th {
218
+ background: linear-gradient(135deg, #e9ecef 0%, #dee2e6 100%);
219
+ color: #2c3e50;
220
+ font-weight: 600;
221
+ position: sticky;
222
+ top: 0;
223
+ z-index: 10;
224
+ box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
225
+ }
226
+
227
+ thead th:first-child {
228
+ border-top-left-radius: 8px;
229
+ }
230
+
231
+ thead th:last-child {
232
+ border-top-right-radius: 8px;
233
+ }
234
+
235
+ /* ===========================
236
+ TABLE BODY - ALTERNATING COLORS
237
+ =========================== */
238
+ tbody tr {
239
+ transition: all 0.2s ease;
240
+ }
241
+
242
+ tbody tr:hover {
243
+ background-color: #f8f9fa !important;
244
+ transform: scale(1.01);
245
+ box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
246
+ }
247
+
248
+ tr.even-group {
249
+ background-color: #ffffff;
250
+ }
251
+
252
+ tr.odd-group {
253
+ background-color: #e3f2fd;
254
+ }
255
+
256
+ tbody td {
257
+ color: #495057;
258
+ }
259
+
260
+ /* First column (key number) styling */
261
+ tbody td:first-child {
262
+ font-weight: 600;
263
+ color: #2c3e50;
264
+ background-color: rgba(52, 152, 219, 0.05);
265
+ }
266
+
267
+ /* ===========================
268
+ RESPONSIVE DESIGN
269
+ =========================== */
270
+ @media (max-width: 768px) {
271
+ body {
272
+ padding: 10px;
273
+ }
274
+
275
+ .csv-header {
276
+ flex-direction: column;
277
+ gap: 15px;
278
+ text-align: center;
279
+ padding: 20px;
280
+ }
281
+
282
+ .csv-header h1 {
283
+ font-size: 1.4em;
284
+ }
285
+
286
+ .company-logo {
287
+ height: 40px;
288
+ }
289
+
290
+ .company-name {
291
+ font-size: 1em;
292
+ }
293
+
294
+ .info-section {
295
+ padding: 15px;
296
+ }
297
+
298
+ table {
299
+ font-size: 11px;
300
+ }
301
+
302
+ th, td {
303
+ padding: 8px 10px;
304
+ }
305
+
306
+ .btn-export {
307
+ width: 100%;
308
+ justify-content: center;
309
+ }
310
+
311
+ .table-container {
312
+ max-height: calc(100vh - 500px);
313
+ }
314
+
315
+ .footer-content {
316
+ flex-direction: column;
317
+ gap: 8px;
318
+ }
319
+
320
+ .footer-content .separator {
321
+ display: none;
322
+ }
323
+ }
324
+
325
+ @media (max-width: 480px) {
326
+ .csv-header h1 {
327
+ font-size: 1.2em;
328
+ }
329
+
330
+ table {
331
+ font-size: 10px;
332
+ }
333
+
334
+ th, td {
335
+ padding: 6px 8px;
336
+ }
337
+
338
+ .company-logo {
339
+ height: 35px;
340
+ }
341
+ }
342
+
343
+ /* ===========================
344
+ PRINT STYLES
345
+ =========================== */
346
+ @media print {
347
+ body {
348
+ background: white;
349
+ padding: 0;
350
+ }
351
+
352
+ .actions {
353
+ display: none;
354
+ }
355
+
356
+ .table-container {
357
+ max-height: none;
358
+ box-shadow: none;
359
+ overflow: visible;
360
+ }
361
+
362
+ thead th {
363
+ position: static;
364
+ box-shadow: none;
365
+ }
366
+
367
+ tbody tr:hover {
368
+ transform: none;
369
+ box-shadow: none;
370
+ }
371
+
372
+ .info-section {
373
+ page-break-inside: avoid;
374
+ }
375
+
376
+ .csv-header,
377
+ .csv-footer {
378
+ box-shadow: none;
379
+ }
380
+
381
+ .company-link {
382
+ pointer-events: none;
383
+ }
384
+
385
+ a {
386
+ color: #000;
387
+ text-decoration: underline;
388
+ }
389
+ }
390
+
391
+ /* ===========================
392
+ ACCESSIBILITY
393
+ =========================== */
394
+ @media (prefers-reduced-motion: reduce) {
395
+ * {
396
+ animation: none !important;
397
+ transition: none !important;
398
+ }
399
+ }
400
+
401
+ /* Focus visible for keyboard navigation */
402
+ .table-container:focus,
403
+ .btn-export:focus,
404
+ .company-link:focus {
405
+ outline: 2px solid #007bff;
406
+ outline-offset: 2px;
407
+ }
408
+
409
+ /* High contrast mode support */
410
+ @media (prefers-contrast: high) {
411
+ table {
412
+ border: 2px solid #000;
413
+ }
414
+
415
+ th, td {
416
+ border: 1px solid #000;
417
+ }
418
+
419
+ .btn-export {
420
+ border: 2px solid #000;
421
+ }
422
+
423
+ .csv-header,
424
+ .csv-footer {
425
+ border: 2px solid #000;
426
+ }
427
+ }
428
+
429
+ /* ===========================
430
+ LOADING STATE (if needed)
431
+ =========================== */
432
+ .loading {
433
+ display: flex;
434
+ justify-content: center;
435
+ align-items: center;
436
+ padding: 40px;
437
+ color: #6c757d;
438
+ }
439
+
440
+ .loading::after {
441
+ content: "Loading...";
442
+ animation: loading 1.5s infinite;
443
+ }
444
+
445
+ @keyframes loading {
446
+ 0%, 100% { opacity: 0.5; }
447
+ 50% { opacity: 1; }
448
+ }
@@ -0,0 +1,85 @@
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
+ <title><%= @ds_name %>: Duplicate Analysis</title>
16
+ <link rel="stylesheet" href="/styles_csv.css">
17
+ </head>
18
+ <body>
19
+
20
+ <!-- Header with title and logo -->
21
+ <header class="csv-header">
22
+ <h1>Duplicates in <%= @ds_name %> for <%= @type %></h1>
23
+ <div class="company-branding">
24
+ <a href="https://adclin.com" target="_blank" rel="noopener noreferrer" class="company-link">
25
+ <img src="/Contact-LOGO.png" alt="AdClin Logo" class="company-logo">
26
+ </a>
27
+ </div>
28
+ </header>
29
+
30
+ <nav class="actions" role="navigation" aria-label="File actions">
31
+ <a class="btn-export"
32
+ href="/excel_export?file=<%= URI.encode_www_form_component(params[:file]) %>&last_valid_key=<%= URI.encode_www_form_component(@last_valid_key.join(',')) %>"
33
+ aria-label="Export to Excel">
34
+ ⬇️ Export to Excel
35
+ </a>
36
+ </nav>
37
+
38
+ <section class="info-section" role="region" aria-label="Information">
39
+ <p>This table displays the detected duplicates, grouped according to the last key tested.
40
+ The duplicate groups are represented by a number in the "No" column.
41
+ Alternating colors are used to distinguish them visually.</p>
42
+ <p><strong>Note:</strong> All variables present in the dataset are displayed to help you identify
43
+ the variable(s) to add to the configuration to find a valid key.</p>
44
+ <% unless @last_valid_key.empty? %>
45
+ <p>Last key tested: <strong><%= @last_valid_key.join(', ') %></strong></p>
46
+ <% end %>
47
+ </section>
48
+
49
+ <div class="table-container" role="region" aria-label="Duplicates table" tabindex="0">
50
+ <table role="table">
51
+ <thead>
52
+ <tr role="row">
53
+ <% @rows.headers.each do |header| %>
54
+ <th role="columnheader" scope="col"><%= header %></th>
55
+ <% end %>
56
+ </tr>
57
+ </thead>
58
+
59
+ <tbody>
60
+ <% @rows.each do |row| %>
61
+ <% key = row[0].to_i %>
62
+ <% css_class = key.even? ? "even-group" : "odd-group" %>
63
+
64
+ <tr class="<%= css_class %>" role="row">
65
+ <% row.each do |_, value| %>
66
+ <td role="cell"><%= value %></td>
67
+ <% end %>
68
+ </tr>
69
+ <% end %>
70
+ </tbody>
71
+ </table>
72
+ </div>
73
+
74
+ <!-- Footer with copyright and license on same line -->
75
+ <footer class="csv-footer">
76
+ <div class="footer-content">
77
+ <span class="copyright">© 1999-2026 AdClin. All rights reserved.</span>
78
+ <span class="separator">•</span>
79
+ <span class="license">Licensed under <a href="/LICENSE" target="_blank" rel="noopener noreferrer">AGPL v3</a></span>
80
+ </div>
81
+ <p class="generated-date">Generated on <%= Time.now.strftime('%Y-%m-%d at %H:%M') %></p>
82
+ </footer>
83
+
84
+ </body>
85
+ </html>