@slashgear/gdpr-cookie-scanner 1.3.0 → 2.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.
- package/.dockerignore +13 -0
- package/.github/workflows/ci.yml +8 -2
- package/.github/workflows/docker.yml +49 -0
- package/.github/workflows/pages.yml +40 -0
- package/.github/workflows/release.yml +1 -1
- package/.nvmrc +1 -0
- package/CHANGELOG.md +87 -0
- package/Dockerfile +36 -0
- package/README.md +44 -63
- package/dist/cli.js +21 -3
- package/dist/cli.js.map +1 -1
- package/dist/report/generator.d.ts +1 -4
- package/dist/report/generator.d.ts.map +1 -1
- package/dist/report/generator.js +45 -23
- package/dist/report/generator.js.map +1 -1
- package/dist/report/html.d.ts +3 -0
- package/dist/report/html.d.ts.map +1 -0
- package/dist/report/html.js +766 -0
- package/dist/report/html.js.map +1 -0
- package/dist/types.d.ts +2 -0
- package/dist/types.d.ts.map +1 -1
- package/docs/index.html +314 -0
- package/docs/reports/github.com/after-accept.png +0 -0
- package/docs/reports/github.com/after-reject.png +0 -0
- package/docs/reports/github.com/gdpr-checklist-github.com-2026-02-22.md +44 -0
- package/docs/reports/github.com/gdpr-cookies-github.com-2026-02-22.md +29 -0
- package/docs/reports/github.com/gdpr-report-github.com-2026-02-22.md +102 -0
- package/docs/reports/github.com/gdpr-report-github.com-2026-02-22.pdf +0 -0
- package/docs/reports/gitlab.com/after-accept.png +0 -0
- package/docs/reports/gitlab.com/after-reject.png +0 -0
- package/docs/reports/gitlab.com/gdpr-checklist-gitlab.com-2026-02-22.md +44 -0
- package/docs/reports/gitlab.com/gdpr-cookies-gitlab.com-2026-02-22.md +55 -0
- package/docs/reports/gitlab.com/gdpr-report-gitlab.com-2026-02-22.md +200 -0
- package/docs/reports/gitlab.com/gdpr-report-gitlab.com-2026-02-22.pdf +0 -0
- package/docs/reports/gitlab.com/modal-initial.png +0 -0
- package/docs/reports/npmjs.com/after-accept.png +0 -0
- package/docs/reports/npmjs.com/after-reject.png +0 -0
- package/docs/reports/npmjs.com/gdpr-checklist-npmjs.com-2026-02-22.md +44 -0
- package/docs/reports/npmjs.com/gdpr-cookies-npmjs.com-2026-02-22.md +25 -0
- package/docs/reports/npmjs.com/gdpr-report-npmjs.com-2026-02-22.md +88 -0
- package/docs/reports/npmjs.com/gdpr-report-npmjs.com-2026-02-22.pdf +0 -0
- package/docs/reports/reddit.com/after-accept.png +0 -0
- package/docs/reports/reddit.com/after-reject.png +0 -0
- package/docs/reports/reddit.com/gdpr-checklist-reddit.com-2026-02-22.md +44 -0
- package/docs/reports/reddit.com/gdpr-cookies-reddit.com-2026-02-22.md +33 -0
- package/docs/reports/reddit.com/gdpr-report-reddit.com-2026-02-22.md +148 -0
- package/docs/reports/reddit.com/gdpr-report-reddit.com-2026-02-22.pdf +0 -0
- package/docs/reports/reddit.com/modal-initial.png +0 -0
- package/docs/reports/stackoverflow.com/after-accept.png +0 -0
- package/docs/reports/stackoverflow.com/after-reject.png +0 -0
- package/docs/reports/stackoverflow.com/gdpr-checklist-stackoverflow.com-2026-02-22.md +44 -0
- package/docs/reports/stackoverflow.com/gdpr-cookies-stackoverflow.com-2026-02-22.md +67 -0
- package/docs/reports/stackoverflow.com/gdpr-report-stackoverflow.com-2026-02-22.md +206 -0
- package/docs/reports/stackoverflow.com/gdpr-report-stackoverflow.com-2026-02-22.pdf +0 -0
- package/docs/reports/stackoverflow.com/modal-initial.png +0 -0
- package/docs/reports/www.afp.com/after-accept.png +0 -0
- package/docs/reports/www.afp.com/after-reject.png +0 -0
- package/docs/reports/www.afp.com/gdpr-checklist-afp.com-2026-02-22.md +44 -0
- package/docs/reports/www.afp.com/gdpr-cookies-afp.com-2026-02-22.md +42 -0
- package/docs/reports/www.afp.com/gdpr-report-afp.com-2026-02-22.md +202 -0
- package/docs/reports/www.afp.com/gdpr-report-afp.com-2026-02-22.pdf +0 -0
- package/docs/reports/www.afp.com/modal-initial.png +0 -0
- package/docs/style.css +439 -0
- package/package.json +10 -7
- package/src/cli.ts +28 -4
- package/src/report/generator.ts +54 -29
- package/src/report/html.ts +940 -0
- package/src/types.ts +3 -0
- package/tests/e2e/fixtures/compliant-site.html +80 -0
- package/tests/e2e/fixtures/no-modal-site.html +17 -0
- package/tests/e2e/fixtures/non-compliant-site.html +54 -0
- package/tests/e2e/scanner.test.ts +135 -0
- package/tests/helpers/test-server.ts +57 -0
- package/tests/unit/compliance.test.ts +460 -0
- package/tests/unit/cookie-classifier.test.ts +192 -0
- package/tests/unit/network-classifier.test.ts +91 -0
- package/tests/unit/wording.test.ts +162 -0
- package/vitest.config.ts +9 -0
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
# GDPR Compliance Report — www.afp.com
|
|
2
|
+
|
|
3
|
+
> **Scan date:** 22/02/2026, 19:36:58
|
|
4
|
+
> **Scanned URL:** https://www.afp.com/
|
|
5
|
+
> **Scan duration:** 14.0s
|
|
6
|
+
> **Tool:** gdpr-cookie-scanner v0.1.0
|
|
7
|
+
|
|
8
|
+
## Global Compliance Score
|
|
9
|
+
|
|
10
|
+
### 🔴 47/100 — Grade D
|
|
11
|
+
|
|
12
|
+
| Criterion | Score | Progress | Status |
|
|
13
|
+
| ---------------- | ---------- | ---------- | ------ |
|
|
14
|
+
| Consent validity | 20/25 | ████████░░ | ✅ |
|
|
15
|
+
| Easy refusal | 10/25 | ████░░░░░░ | ❌ |
|
|
16
|
+
| Transparency | 14/25 | ██████░░░░ | ⚠️ |
|
|
17
|
+
| Cookie behavior | 3/25 | █░░░░░░░░░ | ❌ |
|
|
18
|
+
| **TOTAL** | **47/100** | | **D** |
|
|
19
|
+
|
|
20
|
+
## Executive Summary
|
|
21
|
+
|
|
22
|
+
✅ Consent modal detected (`#onetrust-banner-sdk`).
|
|
23
|
+
❌ **3 non-essential cookie(s)** set before any interaction (RGPD violation).
|
|
24
|
+
❌ **3 non-essential cookie(s)** persisting after rejection (RGPD violation).
|
|
25
|
+
❌ **13 tracker request(s)** fired before consent.
|
|
26
|
+
|
|
27
|
+
**3 critical issue(s)** and **3 warning(s)** identified.
|
|
28
|
+
|
|
29
|
+
## 1. Consent Modal
|
|
30
|
+
|
|
31
|
+
**CSS selector:** `#onetrust-banner-sdk`
|
|
32
|
+
**Granular controls:** ✅ Yes
|
|
33
|
+
**Layer count:** 2
|
|
34
|
+
**Privacy policy link:** ⚠️ Not found in the modal
|
|
35
|
+
|
|
36
|
+
### Detected buttons
|
|
37
|
+
|
|
38
|
+
| Button | Text | Visible | Font size | Contrast ratio |
|
|
39
|
+
| -------------- | -------------------------- | ------- | --------- | -------------- |
|
|
40
|
+
| ⚙️ Preferences | Paramètres des cookies | ✅ | 13.008px | 5.2:1 |
|
|
41
|
+
| ❓ Unknown | Autoriser tous les cookies | ✅ | 13.008px | 5.2:1 |
|
|
42
|
+
| 🟢 Accept | Continuer sans accepter | ✅ | 11.04px | 3.83:1 |
|
|
43
|
+
|
|
44
|
+
### Screenshot
|
|
45
|
+
|
|
46
|
+

|
|
47
|
+
|
|
48
|
+
### Modal text excerpt
|
|
49
|
+
|
|
50
|
+
> En cliquant sur « Autoriser tous les cookies », vous acceptez le stockage de cookies sur votre appareil pour améliorer la navigation sur le site, analyser son utilisation et contribuer à nos efforts de marketing. Paramètres des cookies Autoriser tous les cookiesContinuer sans accepter
|
|
51
|
+
|
|
52
|
+
## 2. Dark Patterns and Detected Issues
|
|
53
|
+
|
|
54
|
+
### ❌ Critical issues
|
|
55
|
+
|
|
56
|
+
**No reject button on first layer**
|
|
57
|
+
|
|
58
|
+
> CNIL (2022) requires reject to require no more clicks than accept
|
|
59
|
+
|
|
60
|
+
**3 non-essential cookie(s) deposited before any interaction**
|
|
61
|
+
|
|
62
|
+
> YSC (social), bcookie (advertising), li_gc (advertising)
|
|
63
|
+
|
|
64
|
+
**13 tracker request(s) fired before any consent**
|
|
65
|
+
|
|
66
|
+
> Google Tag Manager, LinkedIn Insight Tag, Microsoft Clarity, Tracking Pixel
|
|
67
|
+
|
|
68
|
+
### ⚠️ Warnings
|
|
69
|
+
|
|
70
|
+
**Missing required information: "third-parties"**
|
|
71
|
+
|
|
72
|
+
> The consent text does not mention third-parties
|
|
73
|
+
|
|
74
|
+
**Missing required information: "withdrawal"**
|
|
75
|
+
|
|
76
|
+
> The consent text does not mention withdrawal
|
|
77
|
+
|
|
78
|
+
**No privacy policy link found in the consent modal**
|
|
79
|
+
|
|
80
|
+
> GDPR Art. 13 requires the privacy policy to be accessible from the consent interface
|
|
81
|
+
|
|
82
|
+
## 3. Cookies Set Before Any Interaction
|
|
83
|
+
|
|
84
|
+
| Name | Domain | Category | Expiry | Consent required |
|
|
85
|
+
| --------------------------------------------------- | ------------- | ----------- | --------- | ---------------- |
|
|
86
|
+
| `__Secure-YNID` | .youtube.com | unknown | 6 months | ✅ No |
|
|
87
|
+
| `YSC` | .youtube.com | social | Session | ⚠️ Yes |
|
|
88
|
+
| `__Secure-ROLLOUT_TOKEN` | .youtube.com | unknown | 6 months | ✅ No |
|
|
89
|
+
| `VISITOR_INFO1_LIVE` | .youtube.com | unknown | 6 months | ✅ No |
|
|
90
|
+
| `VISITOR_PRIVACY_METADATA` | .youtube.com | unknown | 6 months | ✅ No |
|
|
91
|
+
| `_mkto_trk` | .afp.com | unknown | 13 months | ✅ No |
|
|
92
|
+
| `_gcl_au` | .afp.com | unknown | 3 months | ✅ No |
|
|
93
|
+
| `gtmreferers` | www.afp.com | unknown | 3 months | ✅ No |
|
|
94
|
+
| `_pcid` | .afp.com | unknown | 13 months | ✅ No |
|
|
95
|
+
| `_pctx` | .afp.com | unknown | 13 months | ✅ No |
|
|
96
|
+
| `_pprv` | .afp.com | unknown | 13 months | ✅ No |
|
|
97
|
+
| `OptanonConsent` | .www.afp.com | unknown | 12 months | ✅ No |
|
|
98
|
+
| `__cf_bm` | .page.afp.com | unknown | < 1 day | ✅ No |
|
|
99
|
+
| `bcookie` | .linkedin.com | advertising | 12 months | ⚠️ Yes |
|
|
100
|
+
| `li_gc` | .linkedin.com | advertising | 6 months | ⚠️ Yes |
|
|
101
|
+
| `lidc` | .linkedin.com | unknown | 1 days | ✅ No |
|
|
102
|
+
| `BIGipServer~VS65000-N117~lon08web-nginx-app_https` | page.afp.com | unknown | Session | ✅ No |
|
|
103
|
+
|
|
104
|
+
## 4. Cookies After Consent Rejection
|
|
105
|
+
|
|
106
|
+
✅ No non-essential cookie detected after rejection.
|
|
107
|
+
|
|
108
|
+
_No cookies detected._
|
|
109
|
+
|
|
110
|
+
## 5. Cookies After Consent Acceptance
|
|
111
|
+
|
|
112
|
+
| Name | Domain | Category | Expiry | Consent required |
|
|
113
|
+
| --------------------------------------------------- | ------------- | ----------- | --------- | ---------------- |
|
|
114
|
+
| `AKA_A2` | .afp.com | unknown | < 1 day | ✅ No |
|
|
115
|
+
| `_mkto_trk` | .afp.com | unknown | 13 months | ✅ No |
|
|
116
|
+
| `__Secure-YNID` | .youtube.com | unknown | 6 months | ✅ No |
|
|
117
|
+
| `YSC` | .youtube.com | social | Session | ⚠️ Yes |
|
|
118
|
+
| `VISITOR_INFO1_LIVE` | .youtube.com | unknown | 6 months | ✅ No |
|
|
119
|
+
| `VISITOR_PRIVACY_METADATA` | .youtube.com | unknown | 6 months | ✅ No |
|
|
120
|
+
| `__Secure-ROLLOUT_TOKEN` | .youtube.com | unknown | 6 months | ✅ No |
|
|
121
|
+
| `_gcl_au` | .afp.com | unknown | 3 months | ✅ No |
|
|
122
|
+
| `gtmreferers` | www.afp.com | unknown | 3 months | ✅ No |
|
|
123
|
+
| `__cf_bm` | .page.afp.com | unknown | < 1 day | ✅ No |
|
|
124
|
+
| `_pcid` | .afp.com | unknown | 13 months | ✅ No |
|
|
125
|
+
| `_pctx` | .afp.com | unknown | 13 months | ✅ No |
|
|
126
|
+
| `bcookie` | .linkedin.com | advertising | 12 months | ⚠️ Yes |
|
|
127
|
+
| `li_gc` | .linkedin.com | advertising | 6 months | ⚠️ Yes |
|
|
128
|
+
| `lidc` | .linkedin.com | unknown | 1 days | ✅ No |
|
|
129
|
+
| `BIGipServer~VS65000-N117~lon08web-nginx-app_https` | page.afp.com | unknown | Session | ✅ No |
|
|
130
|
+
| `OptanonAlertBoxClosed` | .www.afp.com | unknown | 12 months | ✅ No |
|
|
131
|
+
| `OptanonConsent` | .www.afp.com | unknown | 12 months | ✅ No |
|
|
132
|
+
| `_pprv` | .afp.com | unknown | 13 months | ✅ No |
|
|
133
|
+
|
|
134
|
+
## 6. Network Requests — Detected Trackers
|
|
135
|
+
|
|
136
|
+
### Before interaction (13 tracker(s))
|
|
137
|
+
|
|
138
|
+
| Tracker | Category | URL | Type |
|
|
139
|
+
| -------------------- | ----------- | -------------------------------------------------------------- | -------- |
|
|
140
|
+
| Google Tag Manager | analytics | `https://www.googletagmanager.com/gtm.js?id=GTM-T4C5DKK` | script |
|
|
141
|
+
| Google Tag Manager | analytics | `https://www.googletagmanager.com/gtag/js?id=AW-1090164081...` | script |
|
|
142
|
+
| LinkedIn Insight Tag | advertising | `https://snap.licdn.com/li.lms-analytics/insight.min.js` | script |
|
|
143
|
+
| Microsoft Clarity | analytics | `https://www.clarity.ms/tag/t3we3mu2lf?ref=gtm` | script |
|
|
144
|
+
| Tracking Pixel | pixel | `https://www.google.com/ccm/collect?frm=0&en=page_view&dl=...` | fetch |
|
|
145
|
+
| Google Tag Manager | analytics | `https://www.googletagmanager.com/static/service_worker/62...` | document |
|
|
146
|
+
| Tracking Pixel | pixel | `https://www.google.com/ccm/collect?frm=0&en=page_view&dl=...` | fetch |
|
|
147
|
+
| Tracking Pixel | pixel | `https://kxgqdwd.pa-cd.com/event?s=628508&idclient=mly3a95...` | ping |
|
|
148
|
+
| Tracking Pixel | pixel | `https://px.ads.linkedin.com/collect?v=2&fmt=js&pid=347975...` | image |
|
|
149
|
+
| Microsoft Clarity | analytics | `https://scripts.clarity.ms/0.8.54/clarity.js` | script |
|
|
150
|
+
| Microsoft Clarity | analytics | `https://y.clarity.ms/collect` | xhr |
|
|
151
|
+
| Tracking Pixel | pixel | `https://px4.ads.linkedin.com/collect?v=2&fmt=js&pid=34797...` | image |
|
|
152
|
+
| Microsoft Clarity | analytics | `https://y.clarity.ms/collect` | xhr |
|
|
153
|
+
|
|
154
|
+
### After acceptance (26 tracker(s))
|
|
155
|
+
|
|
156
|
+
| Tracker | Category | URL | Type |
|
|
157
|
+
| -------------------- | ----------- | -------------------------------------------------------------- | -------- |
|
|
158
|
+
| Google Tag Manager | analytics | `https://www.googletagmanager.com/gtm.js?id=GTM-T4C5DKK` | script |
|
|
159
|
+
| Google Tag Manager | analytics | `https://www.googletagmanager.com/gtag/js?id=AW-1090164081...` | script |
|
|
160
|
+
| LinkedIn Insight Tag | advertising | `https://snap.licdn.com/li.lms-analytics/insight.min.js` | script |
|
|
161
|
+
| Microsoft Clarity | analytics | `https://www.clarity.ms/tag/t3we3mu2lf?ref=gtm` | script |
|
|
162
|
+
| Google Tag Manager | analytics | `https://www.googletagmanager.com/a?id=GTM-T4C5DKK&v=3&t=t...` | image |
|
|
163
|
+
| Google Tag Manager | analytics | `https://www.googletagmanager.com/a?id=GTM-T4C5DKK&v=3&t=t...` | image |
|
|
164
|
+
| Tracking Pixel | pixel | `https://www.google.com/ccm/collect?frm=0&en=page_view&dl=...` | fetch |
|
|
165
|
+
| Google Tag Manager | analytics | `https://www.googletagmanager.com/a?id=GTM-T4C5DKK&v=3&t=t...` | image |
|
|
166
|
+
| Google Tag Manager | analytics | `https://www.googletagmanager.com/a?id=GTM-T4C5DKK&v=3&t=t...` | image |
|
|
167
|
+
| Google Tag Manager | analytics | `https://www.googletagmanager.com/a?id=GTM-T4C5DKK&v=3&t=t...` | image |
|
|
168
|
+
| Google Tag Manager | analytics | `https://www.googletagmanager.com/static/service_worker/62...` | document |
|
|
169
|
+
| Tracking Pixel | pixel | `https://www.google.com/ccm/collect?frm=0&en=page_view&dl=...` | fetch |
|
|
170
|
+
| Google Tag Manager | analytics | `https://www.googletagmanager.com/a?ctid=GTM-T4C5DKK&t=s&s...` | image |
|
|
171
|
+
| Tracking Pixel | pixel | `https://kxgqdwd.pa-cd.com/event?s=628508&idclient=mly3ad9...` | ping |
|
|
172
|
+
| Tracking Pixel | pixel | `https://px.ads.linkedin.com/collect?v=2&fmt=js&pid=347975...` | image |
|
|
173
|
+
| Microsoft Clarity | analytics | `https://scripts.clarity.ms/0.8.54/clarity.js` | script |
|
|
174
|
+
| Microsoft Clarity | analytics | `https://y.clarity.ms/collect` | xhr |
|
|
175
|
+
| Tracking Pixel | pixel | `https://px4.ads.linkedin.com/collect?v=2&fmt=js&pid=34797...` | image |
|
|
176
|
+
| Google Tag Manager | analytics | `https://www.googletagmanager.com/a?id=GTM-T4C5DKK&v=3&t=t...` | image |
|
|
177
|
+
| Microsoft Clarity | analytics | `https://y.clarity.ms/collect` | xhr |
|
|
178
|
+
|
|
179
|
+
_... and 6 additional request(s)._
|
|
180
|
+
|
|
181
|
+
## 7. Recommendations
|
|
182
|
+
|
|
183
|
+
1. **Add a "Reject all" button** at the first layer of the modal, requiring no more clicks than "Accept all" (CNIL 2022).
|
|
184
|
+
|
|
185
|
+
1. **Do not set any non-essential cookie before consent.** Gate the initialisation of third-party scripts on acceptance.
|
|
186
|
+
|
|
187
|
+
1. **Complete the modal information**: purposes, identity of sub-processors, retention period, right to withdraw.
|
|
188
|
+
|
|
189
|
+
1. **Remove or block non-essential cookies** after rejection, and verify consent handling server-side.
|
|
190
|
+
|
|
191
|
+
## Scan Errors and Warnings
|
|
192
|
+
|
|
193
|
+
- ⚠️ No reject button found — could not test rejection flow
|
|
194
|
+
|
|
195
|
+
## Legal References
|
|
196
|
+
|
|
197
|
+
- **RGPD Art. 7** — Conditions for consent
|
|
198
|
+
- **RGPD Recital 32** — Consent must result from an unambiguous positive action
|
|
199
|
+
- **ePrivacy Directive 2002/58/EC** — Consent requirement for non-essential cookies
|
|
200
|
+
- **CEPD Guidelines 05/2020** — Consent under the RGPD
|
|
201
|
+
- **CEPD Guidelines 03/2022** — Dark patterns on platforms
|
|
202
|
+
- **CNIL Recommendation 2022** — Rejection must be as easy as acceptance (same number of clicks)
|
|
Binary file
|
|
Binary file
|
package/docs/style.css
ADDED
|
@@ -0,0 +1,439 @@
|
|
|
1
|
+
/* gdpr-cookie-scanner — landing page styles */
|
|
2
|
+
|
|
3
|
+
:root {
|
|
4
|
+
--color-bg: #ffffff;
|
|
5
|
+
--color-surface: #f8fafc;
|
|
6
|
+
--color-border: #e2e8f0;
|
|
7
|
+
--color-text: #0f172a;
|
|
8
|
+
--color-text-muted: #64748b;
|
|
9
|
+
--color-primary: #3b82f6;
|
|
10
|
+
--color-primary-dark: #2563eb;
|
|
11
|
+
--color-code-bg: #1e293b;
|
|
12
|
+
--color-code-text: #e2e8f0;
|
|
13
|
+
--radius: 0.75rem;
|
|
14
|
+
--shadow: 0 1px 3px rgba(0, 0, 0, 0.08), 0 1px 2px rgba(0, 0, 0, 0.06);
|
|
15
|
+
--shadow-md: 0 4px 6px rgba(0, 0, 0, 0.07), 0 2px 4px rgba(0, 0, 0, 0.05);
|
|
16
|
+
--font: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
|
|
17
|
+
--font-mono: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
@media (prefers-color-scheme: dark) {
|
|
21
|
+
:root {
|
|
22
|
+
--color-bg: #0f172a;
|
|
23
|
+
--color-surface: #1e293b;
|
|
24
|
+
--color-border: #334155;
|
|
25
|
+
--color-text: #f1f5f9;
|
|
26
|
+
--color-text-muted: #94a3b8;
|
|
27
|
+
--color-primary: #60a5fa;
|
|
28
|
+
--color-primary-dark: #3b82f6;
|
|
29
|
+
--color-code-bg: #0f172a;
|
|
30
|
+
--color-code-text: #e2e8f0;
|
|
31
|
+
--shadow: 0 1px 3px rgba(0, 0, 0, 0.3);
|
|
32
|
+
--shadow-md: 0 4px 6px rgba(0, 0, 0, 0.25);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
*,
|
|
37
|
+
*::before,
|
|
38
|
+
*::after {
|
|
39
|
+
box-sizing: border-box;
|
|
40
|
+
margin: 0;
|
|
41
|
+
padding: 0;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
html {
|
|
45
|
+
scroll-behavior: smooth;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
body {
|
|
49
|
+
font-family: var(--font);
|
|
50
|
+
background: var(--color-bg);
|
|
51
|
+
color: var(--color-text);
|
|
52
|
+
line-height: 1.6;
|
|
53
|
+
font-size: 1rem;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
a {
|
|
57
|
+
color: var(--color-primary);
|
|
58
|
+
text-decoration: none;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
a:hover {
|
|
62
|
+
text-decoration: underline;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/* ── Layout ── */
|
|
66
|
+
|
|
67
|
+
.container {
|
|
68
|
+
max-width: 1100px;
|
|
69
|
+
margin: 0 auto;
|
|
70
|
+
padding: 0 1.5rem;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
section {
|
|
74
|
+
padding: 5rem 0;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
section:nth-child(even) {
|
|
78
|
+
background: var(--color-surface);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/* ── Header ── */
|
|
82
|
+
|
|
83
|
+
header {
|
|
84
|
+
position: sticky;
|
|
85
|
+
top: 0;
|
|
86
|
+
z-index: 100;
|
|
87
|
+
background: var(--color-bg);
|
|
88
|
+
border-bottom: 1px solid var(--color-border);
|
|
89
|
+
padding: 1rem 0;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
header .container {
|
|
93
|
+
display: flex;
|
|
94
|
+
align-items: center;
|
|
95
|
+
justify-content: space-between;
|
|
96
|
+
gap: 1rem;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
.header-logo {
|
|
100
|
+
display: flex;
|
|
101
|
+
align-items: center;
|
|
102
|
+
gap: 0.5rem;
|
|
103
|
+
font-weight: 700;
|
|
104
|
+
font-size: 1.1rem;
|
|
105
|
+
color: var(--color-text);
|
|
106
|
+
text-decoration: none;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
.header-logo .logo-icon {
|
|
110
|
+
font-size: 1.4rem;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
.header-nav {
|
|
114
|
+
display: flex;
|
|
115
|
+
align-items: center;
|
|
116
|
+
gap: 1.5rem;
|
|
117
|
+
flex-wrap: wrap;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
.header-nav a {
|
|
121
|
+
color: var(--color-text-muted);
|
|
122
|
+
font-size: 0.9rem;
|
|
123
|
+
font-weight: 500;
|
|
124
|
+
transition: color 0.15s;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
.header-nav a:hover {
|
|
128
|
+
color: var(--color-primary);
|
|
129
|
+
text-decoration: none;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/* ── Hero ── */
|
|
133
|
+
|
|
134
|
+
.hero {
|
|
135
|
+
padding: 6rem 0 5rem;
|
|
136
|
+
text-align: center;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
.hero h1 {
|
|
140
|
+
font-size: clamp(2rem, 5vw, 3.25rem);
|
|
141
|
+
font-weight: 800;
|
|
142
|
+
line-height: 1.2;
|
|
143
|
+
letter-spacing: -0.02em;
|
|
144
|
+
margin-bottom: 1.25rem;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
.hero h1 .accent {
|
|
148
|
+
color: var(--color-primary);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
.hero-tagline {
|
|
152
|
+
font-size: clamp(1rem, 2.5vw, 1.25rem);
|
|
153
|
+
color: var(--color-text-muted);
|
|
154
|
+
max-width: 640px;
|
|
155
|
+
margin: 0 auto 2.5rem;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
.install-block {
|
|
159
|
+
display: flex;
|
|
160
|
+
flex-direction: column;
|
|
161
|
+
gap: 0.75rem;
|
|
162
|
+
align-items: center;
|
|
163
|
+
margin-bottom: 2rem;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
.code-line {
|
|
167
|
+
display: inline-flex;
|
|
168
|
+
align-items: center;
|
|
169
|
+
gap: 0.75rem;
|
|
170
|
+
background: var(--color-code-bg);
|
|
171
|
+
color: var(--color-code-text);
|
|
172
|
+
font-family: var(--font-mono);
|
|
173
|
+
font-size: 0.9rem;
|
|
174
|
+
padding: 0.6rem 1.2rem;
|
|
175
|
+
border-radius: 0.5rem;
|
|
176
|
+
white-space: nowrap;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
.code-line .prompt {
|
|
180
|
+
color: #64748b;
|
|
181
|
+
user-select: none;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
.hero-links {
|
|
185
|
+
display: flex;
|
|
186
|
+
gap: 1rem;
|
|
187
|
+
justify-content: center;
|
|
188
|
+
flex-wrap: wrap;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
.btn {
|
|
192
|
+
display: inline-flex;
|
|
193
|
+
align-items: center;
|
|
194
|
+
gap: 0.4rem;
|
|
195
|
+
padding: 0.6rem 1.4rem;
|
|
196
|
+
border-radius: 0.5rem;
|
|
197
|
+
font-weight: 600;
|
|
198
|
+
font-size: 0.95rem;
|
|
199
|
+
transition:
|
|
200
|
+
opacity 0.15s,
|
|
201
|
+
transform 0.1s;
|
|
202
|
+
text-decoration: none;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
.btn:hover {
|
|
206
|
+
opacity: 0.9;
|
|
207
|
+
transform: translateY(-1px);
|
|
208
|
+
text-decoration: none;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
.btn-primary {
|
|
212
|
+
background: var(--color-primary);
|
|
213
|
+
color: #fff;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
.btn-outline {
|
|
217
|
+
border: 1.5px solid var(--color-border);
|
|
218
|
+
color: var(--color-text);
|
|
219
|
+
background: transparent;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
/* ── Features ── */
|
|
223
|
+
|
|
224
|
+
.section-title {
|
|
225
|
+
font-size: clamp(1.5rem, 3vw, 2.25rem);
|
|
226
|
+
font-weight: 700;
|
|
227
|
+
text-align: center;
|
|
228
|
+
margin-bottom: 0.75rem;
|
|
229
|
+
letter-spacing: -0.01em;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
.section-subtitle {
|
|
233
|
+
text-align: center;
|
|
234
|
+
color: var(--color-text-muted);
|
|
235
|
+
margin-bottom: 3rem;
|
|
236
|
+
font-size: 1.05rem;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
.features-grid {
|
|
240
|
+
display: grid;
|
|
241
|
+
grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
|
|
242
|
+
gap: 1.5rem;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
.feature-card {
|
|
246
|
+
background: var(--color-bg);
|
|
247
|
+
border: 1px solid var(--color-border);
|
|
248
|
+
border-radius: var(--radius);
|
|
249
|
+
padding: 1.75rem;
|
|
250
|
+
box-shadow: var(--shadow);
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
.feature-icon {
|
|
254
|
+
font-size: 2rem;
|
|
255
|
+
margin-bottom: 1rem;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
.feature-card h3 {
|
|
259
|
+
font-size: 1.05rem;
|
|
260
|
+
font-weight: 700;
|
|
261
|
+
margin-bottom: 0.5rem;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
.feature-card p {
|
|
265
|
+
color: var(--color-text-muted);
|
|
266
|
+
font-size: 0.9rem;
|
|
267
|
+
line-height: 1.6;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
/* ── Live Reports ── */
|
|
271
|
+
|
|
272
|
+
.reports-grid {
|
|
273
|
+
display: grid;
|
|
274
|
+
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
|
|
275
|
+
gap: 1.5rem;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
.report-card {
|
|
279
|
+
background: var(--color-bg);
|
|
280
|
+
border: 1px solid var(--color-border);
|
|
281
|
+
border-radius: var(--radius);
|
|
282
|
+
padding: 1.5rem;
|
|
283
|
+
box-shadow: var(--shadow);
|
|
284
|
+
display: flex;
|
|
285
|
+
flex-direction: column;
|
|
286
|
+
gap: 1rem;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
.report-header {
|
|
290
|
+
display: flex;
|
|
291
|
+
align-items: center;
|
|
292
|
+
gap: 1rem;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
.grade-badge {
|
|
296
|
+
width: 3rem;
|
|
297
|
+
height: 3rem;
|
|
298
|
+
border-radius: 0.5rem;
|
|
299
|
+
display: flex;
|
|
300
|
+
align-items: center;
|
|
301
|
+
justify-content: center;
|
|
302
|
+
font-size: 1.5rem;
|
|
303
|
+
font-weight: 800;
|
|
304
|
+
color: #fff;
|
|
305
|
+
flex-shrink: 0;
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
.grade-A {
|
|
309
|
+
background: #22c55e;
|
|
310
|
+
}
|
|
311
|
+
.grade-B {
|
|
312
|
+
background: #84cc16;
|
|
313
|
+
}
|
|
314
|
+
.grade-C {
|
|
315
|
+
background: #eab308;
|
|
316
|
+
}
|
|
317
|
+
.grade-D {
|
|
318
|
+
background: #f97316;
|
|
319
|
+
}
|
|
320
|
+
.grade-F {
|
|
321
|
+
background: #ef4444;
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
.report-meta h3 {
|
|
325
|
+
font-size: 1rem;
|
|
326
|
+
font-weight: 700;
|
|
327
|
+
margin-bottom: 0.15rem;
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
.report-meta .score {
|
|
331
|
+
font-size: 0.85rem;
|
|
332
|
+
color: var(--color-text-muted);
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
.report-date {
|
|
336
|
+
font-size: 0.8rem;
|
|
337
|
+
color: var(--color-text-muted);
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
.report-card .btn {
|
|
341
|
+
align-self: flex-start;
|
|
342
|
+
font-size: 0.85rem;
|
|
343
|
+
padding: 0.45rem 1rem;
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
/* ── How it works ── */
|
|
347
|
+
|
|
348
|
+
.steps {
|
|
349
|
+
display: grid;
|
|
350
|
+
grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
|
|
351
|
+
gap: 1.5rem;
|
|
352
|
+
counter-reset: steps;
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
.step {
|
|
356
|
+
background: var(--color-bg);
|
|
357
|
+
border: 1px solid var(--color-border);
|
|
358
|
+
border-radius: var(--radius);
|
|
359
|
+
padding: 1.75rem;
|
|
360
|
+
box-shadow: var(--shadow);
|
|
361
|
+
position: relative;
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
.step-number {
|
|
365
|
+
width: 2.25rem;
|
|
366
|
+
height: 2.25rem;
|
|
367
|
+
background: var(--color-primary);
|
|
368
|
+
color: #fff;
|
|
369
|
+
border-radius: 50%;
|
|
370
|
+
display: flex;
|
|
371
|
+
align-items: center;
|
|
372
|
+
justify-content: center;
|
|
373
|
+
font-weight: 700;
|
|
374
|
+
font-size: 0.9rem;
|
|
375
|
+
margin-bottom: 1rem;
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
.step h3 {
|
|
379
|
+
font-size: 1rem;
|
|
380
|
+
font-weight: 700;
|
|
381
|
+
margin-bottom: 0.5rem;
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
.step p {
|
|
385
|
+
color: var(--color-text-muted);
|
|
386
|
+
font-size: 0.9rem;
|
|
387
|
+
line-height: 1.6;
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
/* ── Footer ── */
|
|
391
|
+
|
|
392
|
+
footer {
|
|
393
|
+
border-top: 1px solid var(--color-border);
|
|
394
|
+
padding: 2.5rem 0;
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
footer .container {
|
|
398
|
+
display: flex;
|
|
399
|
+
align-items: center;
|
|
400
|
+
justify-content: space-between;
|
|
401
|
+
flex-wrap: wrap;
|
|
402
|
+
gap: 1rem;
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
.footer-copy {
|
|
406
|
+
font-size: 0.875rem;
|
|
407
|
+
color: var(--color-text-muted);
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
.footer-links {
|
|
411
|
+
display: flex;
|
|
412
|
+
gap: 1.25rem;
|
|
413
|
+
flex-wrap: wrap;
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
.footer-links a {
|
|
417
|
+
font-size: 0.875rem;
|
|
418
|
+
color: var(--color-text-muted);
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
.footer-links a:hover {
|
|
422
|
+
color: var(--color-primary);
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
/* ── Responsive tweaks ── */
|
|
426
|
+
|
|
427
|
+
@media (max-width: 640px) {
|
|
428
|
+
.header-nav {
|
|
429
|
+
gap: 1rem;
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
section {
|
|
433
|
+
padding: 3.5rem 0;
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
.hero {
|
|
437
|
+
padding: 4rem 0 3rem;
|
|
438
|
+
}
|
|
439
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@slashgear/gdpr-cookie-scanner",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.0.0",
|
|
4
4
|
"description": "CLI tool to scan websites for GDPR cookie consent compliance",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"compliance",
|
|
@@ -12,15 +12,15 @@
|
|
|
12
12
|
"rgpd",
|
|
13
13
|
"scanner"
|
|
14
14
|
],
|
|
15
|
-
"homepage": "https://github.com/Slashgear/gdpr-
|
|
15
|
+
"homepage": "https://github.com/Slashgear/gdpr-cookie-scanner#readme",
|
|
16
16
|
"bugs": {
|
|
17
|
-
"url": "https://github.com/Slashgear/gdpr-
|
|
17
|
+
"url": "https://github.com/Slashgear/gdpr-cookie-scanner/issues"
|
|
18
18
|
},
|
|
19
19
|
"license": "MIT",
|
|
20
20
|
"author": "Antoine Caron (https://slashgear.github.io)",
|
|
21
21
|
"repository": {
|
|
22
22
|
"type": "git",
|
|
23
|
-
"url": "git+https://github.com/Slashgear/gdpr-
|
|
23
|
+
"url": "git+https://github.com/Slashgear/gdpr-cookie-scanner.git"
|
|
24
24
|
},
|
|
25
25
|
"bin": {
|
|
26
26
|
"gdpr-scan": "dist/cli.js"
|
|
@@ -41,12 +41,13 @@
|
|
|
41
41
|
"devDependencies": {
|
|
42
42
|
"@changesets/cli": "^2.29.8",
|
|
43
43
|
"@types/node": "^22.0.0",
|
|
44
|
-
"oxfmt": "^0.
|
|
44
|
+
"oxfmt": "^0.34.0",
|
|
45
45
|
"oxlint": "^1.48.0",
|
|
46
|
-
"typescript": "^5.5.0"
|
|
46
|
+
"typescript": "^5.5.0",
|
|
47
|
+
"vitest": "^4.0.18"
|
|
47
48
|
},
|
|
48
49
|
"engines": {
|
|
49
|
-
"node": ">=
|
|
50
|
+
"node": ">=24.0.0"
|
|
50
51
|
},
|
|
51
52
|
"scripts": {
|
|
52
53
|
"build": "tsc",
|
|
@@ -57,6 +58,8 @@
|
|
|
57
58
|
"format": "oxfmt .",
|
|
58
59
|
"format:check": "oxfmt --check .",
|
|
59
60
|
"typecheck": "tsc --noEmit",
|
|
61
|
+
"test": "vitest run",
|
|
62
|
+
"test:watch": "vitest",
|
|
60
63
|
"changeset": "changeset"
|
|
61
64
|
}
|
|
62
65
|
}
|