astro-consent 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.
- package/LICENSE.md +36 -0
- package/README.md +128 -0
- package/dist/cli.cjs +311 -0
- package/dist/config/defaults.js +32 -0
- package/dist/config/loadConfig.js +74 -0
- package/dist/index.js +333 -0
- package/dist/templates/cssTemplate.js +117 -0
- package/dist/types/config.js +1 -0
- package/package.json +55 -0
package/LICENSE.md
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
BSD 4-Clause License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026, Velohost
|
|
4
|
+
All rights reserved.
|
|
5
|
+
|
|
6
|
+
Redistribution and use in source and binary forms, with or without
|
|
7
|
+
modification, are permitted provided that the following conditions are met:
|
|
8
|
+
|
|
9
|
+
1. Redistributions of source code must retain the above copyright notice,
|
|
10
|
+
this list of conditions and the following disclaimer.
|
|
11
|
+
|
|
12
|
+
2. Redistributions in binary form must reproduce the above copyright notice,
|
|
13
|
+
this list of conditions and the following disclaimer in the documentation
|
|
14
|
+
and/or other materials provided with the distribution.
|
|
15
|
+
|
|
16
|
+
3. All advertising materials mentioning features or use of this software
|
|
17
|
+
must display the following acknowledgement:
|
|
18
|
+
|
|
19
|
+
This product includes software developed by Velohost
|
|
20
|
+
(https://velohost.co.uk)
|
|
21
|
+
|
|
22
|
+
4. Neither the name of Velohost nor the names of its contributors may be used
|
|
23
|
+
to endorse or promote products derived from this software without
|
|
24
|
+
specific prior written permission.
|
|
25
|
+
|
|
26
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
27
|
+
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
28
|
+
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
|
29
|
+
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
|
|
30
|
+
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
|
31
|
+
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
|
32
|
+
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
|
33
|
+
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
|
34
|
+
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
|
35
|
+
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
|
36
|
+
POSSIBILITY OF SUCH DAMAGE.
|
package/README.md
ADDED
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
# astro-cookiebanner
|
|
2
|
+
|
|
3
|
+
A **privacy‑first, zero‑dependency cookie consent banner** for Astro projects — built for speed, compliance, and full visual control.
|
|
4
|
+
|
|
5
|
+
Designed and maintained by **Velohost**.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## ✨ Features
|
|
10
|
+
|
|
11
|
+
- ✅ GDPR / UK GDPR friendly
|
|
12
|
+
- 🍪 Essential, Analytics & Marketing categories
|
|
13
|
+
- 🎛️ Manage preferences modal with toggle switches
|
|
14
|
+
- ⚡ No external dependencies
|
|
15
|
+
- 🎨 Fully themeable via CSS variables
|
|
16
|
+
- 🧠 Frontend‑controlled script loading
|
|
17
|
+
- 🧩 Astro Integration + CLI installer
|
|
18
|
+
- 🔁 Easy uninstall via CLI
|
|
19
|
+
- 🌍 Framework‑agnostic frontend API
|
|
20
|
+
|
|
21
|
+
---
|
|
22
|
+
|
|
23
|
+
## 📦 Installation
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
npm install astro-cookiebanner
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
Then run the installer inside your Astro project:
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
npx astro-cookiebanner
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
To remove everything:
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
npx astro-cookiebanner remove
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
---
|
|
42
|
+
|
|
43
|
+
## 🔧 Usage
|
|
44
|
+
|
|
45
|
+
```ts
|
|
46
|
+
import astroCookieBanner from "astro-cookiebanner";
|
|
47
|
+
|
|
48
|
+
export default {
|
|
49
|
+
integrations: [
|
|
50
|
+
astroCookieBanner({
|
|
51
|
+
siteName: "My Website",
|
|
52
|
+
policyUrl: "/privacy",
|
|
53
|
+
consent: {
|
|
54
|
+
days: 30,
|
|
55
|
+
storageKey: "astro-cookie-consent"
|
|
56
|
+
},
|
|
57
|
+
categories: {
|
|
58
|
+
analytics: false,
|
|
59
|
+
marketing: false
|
|
60
|
+
}
|
|
61
|
+
})
|
|
62
|
+
]
|
|
63
|
+
};
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
---
|
|
67
|
+
|
|
68
|
+
## 🧠 Frontend API
|
|
69
|
+
|
|
70
|
+
```js
|
|
71
|
+
window.cookieConsent.get();
|
|
72
|
+
window.cookieConsent.set({ essential: true, analytics: true });
|
|
73
|
+
window.cookieConsent.reset();
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
Example conditional loading:
|
|
77
|
+
|
|
78
|
+
```js
|
|
79
|
+
const consent = window.cookieConsent.get();
|
|
80
|
+
|
|
81
|
+
if (consent?.categories?.analytics) {
|
|
82
|
+
// Load analytics script
|
|
83
|
+
}
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
---
|
|
87
|
+
|
|
88
|
+
## 🎨 Theming
|
|
89
|
+
|
|
90
|
+
All visuals are controlled via:
|
|
91
|
+
|
|
92
|
+
```
|
|
93
|
+
src/cookiebanner.css
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
This file is never overwritten.
|
|
97
|
+
|
|
98
|
+
---
|
|
99
|
+
|
|
100
|
+
## 🔐 Privacy
|
|
101
|
+
|
|
102
|
+
- No cookies before consent
|
|
103
|
+
- No tracking without permission
|
|
104
|
+
- No external calls
|
|
105
|
+
- Stored locally with TTL
|
|
106
|
+
|
|
107
|
+
---
|
|
108
|
+
|
|
109
|
+
## 🏷️ License & Attribution
|
|
110
|
+
|
|
111
|
+
Open‑source with **mandatory attribution**.
|
|
112
|
+
|
|
113
|
+
Any public use, fork, or redistribution **must credit Velohost**.
|
|
114
|
+
|
|
115
|
+
See `LICENSE.md` for full terms.
|
|
116
|
+
|
|
117
|
+
---
|
|
118
|
+
|
|
119
|
+
## 🏢 Velohost
|
|
120
|
+
|
|
121
|
+
Built by **Velohost**
|
|
122
|
+
https://velohost.co.uk
|
|
123
|
+
|
|
124
|
+
---
|
|
125
|
+
|
|
126
|
+
## 🤝 Contributions
|
|
127
|
+
|
|
128
|
+
PRs welcome — attribution must be preserved.
|
package/dist/cli.cjs
ADDED
|
@@ -0,0 +1,311 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
4
|
+
if (k2 === undefined) k2 = k;
|
|
5
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
6
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
7
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
8
|
+
}
|
|
9
|
+
Object.defineProperty(o, k2, desc);
|
|
10
|
+
}) : (function(o, m, k, k2) {
|
|
11
|
+
if (k2 === undefined) k2 = k;
|
|
12
|
+
o[k2] = m[k];
|
|
13
|
+
}));
|
|
14
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
15
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
16
|
+
}) : function(o, v) {
|
|
17
|
+
o["default"] = v;
|
|
18
|
+
});
|
|
19
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
20
|
+
var ownKeys = function(o) {
|
|
21
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
22
|
+
var ar = [];
|
|
23
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
24
|
+
return ar;
|
|
25
|
+
};
|
|
26
|
+
return ownKeys(o);
|
|
27
|
+
};
|
|
28
|
+
return function (mod) {
|
|
29
|
+
if (mod && mod.__esModule) return mod;
|
|
30
|
+
var result = {};
|
|
31
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
32
|
+
__setModuleDefault(result, mod);
|
|
33
|
+
return result;
|
|
34
|
+
};
|
|
35
|
+
})();
|
|
36
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
37
|
+
const fs = __importStar(require("node:fs"));
|
|
38
|
+
const path = __importStar(require("node:path"));
|
|
39
|
+
const process = __importStar(require("node:process"));
|
|
40
|
+
const CWD = process.cwd();
|
|
41
|
+
const args = process.argv.slice(2);
|
|
42
|
+
const command = args[0] ?? "install";
|
|
43
|
+
const ASTRO_CONFIG_FILES = [
|
|
44
|
+
"astro.config.mjs",
|
|
45
|
+
"astro.config.ts",
|
|
46
|
+
"astro.config.js"
|
|
47
|
+
];
|
|
48
|
+
function findAstroConfig() {
|
|
49
|
+
for (const file of ASTRO_CONFIG_FILES) {
|
|
50
|
+
const fullPath = path.join(CWD, file);
|
|
51
|
+
if (fs.existsSync(fullPath))
|
|
52
|
+
return fullPath;
|
|
53
|
+
}
|
|
54
|
+
return null;
|
|
55
|
+
}
|
|
56
|
+
function exitWith(message, code = 1) {
|
|
57
|
+
console.error(`\n❌ ${message}\n`);
|
|
58
|
+
process.exit(code);
|
|
59
|
+
}
|
|
60
|
+
/* ─────────────────────────────────────
|
|
61
|
+
Locate astro.config
|
|
62
|
+
───────────────────────────────────── */
|
|
63
|
+
const configPath = findAstroConfig();
|
|
64
|
+
if (!configPath) {
|
|
65
|
+
exitWith("No astro.config.(mjs|ts|js) found. Run this in the root of an Astro project.");
|
|
66
|
+
}
|
|
67
|
+
let source = fs.readFileSync(configPath, "utf8");
|
|
68
|
+
/* ─────────────────────────────────────
|
|
69
|
+
REMOVE MODE
|
|
70
|
+
───────────────────────────────────── */
|
|
71
|
+
if (command === "remove") {
|
|
72
|
+
source = source.replace(/\s*astroCookieBanner\s*\(\s*\{[\s\S]*?\}\s*\),?/gm, "");
|
|
73
|
+
source = source.replace(/import\s+astroCookieBanner\s+from\s+["']astro-cookiebanner["'];?\n?/, "");
|
|
74
|
+
fs.writeFileSync(configPath, source.trim() + "\n", "utf8");
|
|
75
|
+
const cssFile = path.join(CWD, "src", "cookiebanner.css");
|
|
76
|
+
if (fs.existsSync(cssFile))
|
|
77
|
+
fs.unlinkSync(cssFile);
|
|
78
|
+
console.log("\n🧹 astro-cookiebanner fully removed\n");
|
|
79
|
+
process.exit(0);
|
|
80
|
+
}
|
|
81
|
+
/* ─────────────────────────────────────
|
|
82
|
+
INSTALL MODE
|
|
83
|
+
───────────────────────────────────── */
|
|
84
|
+
const cssDir = path.join(CWD, "src");
|
|
85
|
+
const cssFile = path.join(cssDir, "cookiebanner.css");
|
|
86
|
+
if (!fs.existsSync(cssDir)) {
|
|
87
|
+
fs.mkdirSync(cssDir, { recursive: true });
|
|
88
|
+
}
|
|
89
|
+
/* ─────────────────────────────────────
|
|
90
|
+
Create FULL CSS VARIABLE OVERRIDE
|
|
91
|
+
───────────────────────────────────── */
|
|
92
|
+
if (!fs.existsSync(cssFile)) {
|
|
93
|
+
fs.writeFileSync(cssFile, `/* =========================================================
|
|
94
|
+
astro-cookiebanner — FULL THEME VARIABLES
|
|
95
|
+
All visuals are controlled from here.
|
|
96
|
+
This file is NEVER overwritten.
|
|
97
|
+
========================================================= */
|
|
98
|
+
|
|
99
|
+
:root {
|
|
100
|
+
/* ───── Core ───── */
|
|
101
|
+
--cb-font: system-ui, -apple-system, BlinkMacSystemFont, sans-serif;
|
|
102
|
+
|
|
103
|
+
/* ───── Banner ───── */
|
|
104
|
+
--cb-bg: rgba(12,18,32,0.88);
|
|
105
|
+
--cb-border: rgba(255,255,255,0.08);
|
|
106
|
+
--cb-text: #e5e7eb;
|
|
107
|
+
--cb-muted: #9ca3af;
|
|
108
|
+
--cb-link: #60a5fa;
|
|
109
|
+
--cb-radius: 16px;
|
|
110
|
+
--cb-shadow: 0 20px 40px rgba(0,0,0,0.35);
|
|
111
|
+
|
|
112
|
+
/* ───── Buttons ───── */
|
|
113
|
+
--cb-btn-radius: 999px;
|
|
114
|
+
--cb-btn-padding: 10px 18px;
|
|
115
|
+
|
|
116
|
+
--cb-accept-bg: #22c55e;
|
|
117
|
+
--cb-accept-text: #052e16;
|
|
118
|
+
|
|
119
|
+
--cb-reject-bg: #374151;
|
|
120
|
+
--cb-reject-text: #e5e7eb;
|
|
121
|
+
|
|
122
|
+
--cb-manage-bg: transparent;
|
|
123
|
+
--cb-manage-text: #e5e7eb;
|
|
124
|
+
--cb-manage-border: rgba(255,255,255,0.15);
|
|
125
|
+
|
|
126
|
+
/* ───── Modal ───── */
|
|
127
|
+
--cb-modal-backdrop: rgba(0,0,0,0.55);
|
|
128
|
+
--cb-modal-bg: #0c1220;
|
|
129
|
+
--cb-modal-radius: 18px;
|
|
130
|
+
--cb-modal-width: 480px;
|
|
131
|
+
|
|
132
|
+
/* ───── Toggles ───── */
|
|
133
|
+
--cb-toggle-off-bg: #374151;
|
|
134
|
+
--cb-toggle-on-bg: #22c55e;
|
|
135
|
+
--cb-toggle-knob: #ffffff;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/* =========================================================
|
|
139
|
+
Banner
|
|
140
|
+
========================================================= */
|
|
141
|
+
|
|
142
|
+
#astro-cookie-banner {
|
|
143
|
+
position: fixed;
|
|
144
|
+
left: 16px;
|
|
145
|
+
right: 16px;
|
|
146
|
+
bottom: 16px;
|
|
147
|
+
z-index: 9999;
|
|
148
|
+
font-family: var(--cb-font);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
.cb-container {
|
|
152
|
+
max-width: 1200px;
|
|
153
|
+
margin: 0 auto;
|
|
154
|
+
padding: 20px 24px;
|
|
155
|
+
display: flex;
|
|
156
|
+
gap: 24px;
|
|
157
|
+
justify-content: space-between;
|
|
158
|
+
align-items: center;
|
|
159
|
+
|
|
160
|
+
background: var(--cb-bg);
|
|
161
|
+
backdrop-filter: blur(14px);
|
|
162
|
+
border-radius: var(--cb-radius);
|
|
163
|
+
border: 1px solid var(--cb-border);
|
|
164
|
+
box-shadow: var(--cb-shadow);
|
|
165
|
+
color: var(--cb-text);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
.cb-title {
|
|
169
|
+
font-size: 16px;
|
|
170
|
+
font-weight: 600;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
.cb-desc {
|
|
174
|
+
font-size: 14px;
|
|
175
|
+
color: var(--cb-muted);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
.cb-desc a {
|
|
179
|
+
color: var(--cb-link);
|
|
180
|
+
text-decoration: none;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
.cb-actions {
|
|
184
|
+
display: flex;
|
|
185
|
+
gap: 10px;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/* =========================================================
|
|
189
|
+
Buttons
|
|
190
|
+
========================================================= */
|
|
191
|
+
|
|
192
|
+
.cb-actions button {
|
|
193
|
+
padding: var(--cb-btn-padding);
|
|
194
|
+
border-radius: var(--cb-btn-radius);
|
|
195
|
+
border: 0;
|
|
196
|
+
font-size: 14px;
|
|
197
|
+
font-weight: 600;
|
|
198
|
+
cursor: pointer;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
.cb-accept {
|
|
202
|
+
background: var(--cb-accept-bg);
|
|
203
|
+
color: var(--cb-accept-text);
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
.cb-reject {
|
|
207
|
+
background: var(--cb-reject-bg);
|
|
208
|
+
color: var(--cb-reject-text);
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
.cb-manage {
|
|
212
|
+
background: var(--cb-manage-bg);
|
|
213
|
+
color: var(--cb-manage-text);
|
|
214
|
+
border: 1px solid var(--cb-manage-border);
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/* =========================================================
|
|
218
|
+
Modal
|
|
219
|
+
========================================================= */
|
|
220
|
+
|
|
221
|
+
#astro-cookie-modal {
|
|
222
|
+
position: fixed;
|
|
223
|
+
inset: 0;
|
|
224
|
+
background: var(--cb-modal-backdrop);
|
|
225
|
+
display: flex;
|
|
226
|
+
align-items: center;
|
|
227
|
+
justify-content: center;
|
|
228
|
+
z-index: 10000;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
.cb-modal {
|
|
232
|
+
width: 100%;
|
|
233
|
+
max-width: var(--cb-modal-width);
|
|
234
|
+
background: var(--cb-modal-bg);
|
|
235
|
+
border-radius: var(--cb-modal-radius);
|
|
236
|
+
padding: 24px;
|
|
237
|
+
color: var(--cb-text);
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
/* =========================================================
|
|
241
|
+
Toggles
|
|
242
|
+
========================================================= */
|
|
243
|
+
|
|
244
|
+
.cb-toggle {
|
|
245
|
+
width: 44px;
|
|
246
|
+
height: 24px;
|
|
247
|
+
background: var(--cb-toggle-off-bg);
|
|
248
|
+
border-radius: 999px;
|
|
249
|
+
position: relative;
|
|
250
|
+
cursor: pointer;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
.cb-toggle span {
|
|
254
|
+
position: absolute;
|
|
255
|
+
width: 18px;
|
|
256
|
+
height: 18px;
|
|
257
|
+
background: var(--cb-toggle-knob);
|
|
258
|
+
border-radius: 50%;
|
|
259
|
+
top: 3px;
|
|
260
|
+
left: 3px;
|
|
261
|
+
transition: transform 0.2s ease;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
.cb-toggle.active {
|
|
265
|
+
background: var(--cb-toggle-on-bg);
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
.cb-toggle.active span {
|
|
269
|
+
transform: translateX(20px);
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
/* =========================================================
|
|
273
|
+
Mobile
|
|
274
|
+
========================================================= */
|
|
275
|
+
|
|
276
|
+
@media (max-width: 640px) {
|
|
277
|
+
.cb-container {
|
|
278
|
+
flex-direction: column;
|
|
279
|
+
align-items: stretch;
|
|
280
|
+
gap: 16px;
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
`, "utf8");
|
|
284
|
+
console.log("🎨 Created src/cookiebanner.css (CSS variables enabled)");
|
|
285
|
+
}
|
|
286
|
+
/* ─────────────────────────────────────
|
|
287
|
+
Inject Astro integration
|
|
288
|
+
───────────────────────────────────── */
|
|
289
|
+
if (!source.includes(`from "astro-cookiebanner"`)) {
|
|
290
|
+
source = `import astroCookieBanner from "astro-cookiebanner";\n${source}`;
|
|
291
|
+
}
|
|
292
|
+
if (!source.includes("astroCookieBanner(")) {
|
|
293
|
+
const injection = ` astroCookieBanner({
|
|
294
|
+
siteName: "My Website",
|
|
295
|
+
policyUrl: "/privacy",
|
|
296
|
+
consent: {
|
|
297
|
+
days: 30,
|
|
298
|
+
storageKey: "astro-cookie-consent"
|
|
299
|
+
},
|
|
300
|
+
categories: {
|
|
301
|
+
analytics: false,
|
|
302
|
+
marketing: false
|
|
303
|
+
}
|
|
304
|
+
}),
|
|
305
|
+
`;
|
|
306
|
+
source = source.replace(/integrations\s*:\s*\[/, match => `${match}\n${injection}`);
|
|
307
|
+
}
|
|
308
|
+
fs.writeFileSync(configPath, source, "utf8");
|
|
309
|
+
console.log("\n🎉 astro-cookiebanner installed successfully");
|
|
310
|
+
console.log("👉 Edit src/cookiebanner.css to theme everything");
|
|
311
|
+
console.log("👉 Run `astro-cookiebanner remove` to uninstall\n");
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Default configuration values.
|
|
3
|
+
* These are ONLY used as fallbacks if the user omits something.
|
|
4
|
+
* User config always takes priority.
|
|
5
|
+
*/
|
|
6
|
+
export const DEFAULT_CONFIG = {
|
|
7
|
+
siteName: "This website",
|
|
8
|
+
policyUrl: "/privacy",
|
|
9
|
+
consent: {
|
|
10
|
+
enabled: true,
|
|
11
|
+
days: 30,
|
|
12
|
+
storageKey: "astro-cookie-consent"
|
|
13
|
+
},
|
|
14
|
+
categories: {
|
|
15
|
+
essential: {
|
|
16
|
+
label: "Essential",
|
|
17
|
+
description: "Required for the website to function correctly",
|
|
18
|
+
enabled: true,
|
|
19
|
+
readonly: true
|
|
20
|
+
},
|
|
21
|
+
analytics: {
|
|
22
|
+
label: "Analytics",
|
|
23
|
+
description: "Helps us understand how visitors use the site",
|
|
24
|
+
enabled: false
|
|
25
|
+
},
|
|
26
|
+
marketing: {
|
|
27
|
+
label: "Marketing",
|
|
28
|
+
description: "Used to deliver personalised ads",
|
|
29
|
+
enabled: false
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
};
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import * as fs from "node:fs";
|
|
2
|
+
import * as path from "node:path";
|
|
3
|
+
import { pathToFileURL } from "node:url";
|
|
4
|
+
import { DEFAULT_CONFIG } from "./defaults.js";
|
|
5
|
+
/**
|
|
6
|
+
* Safely loads the user config file and merges it with defaults.
|
|
7
|
+
* User values always override defaults.
|
|
8
|
+
* Cache-busted to ensure updates are picked up during dev.
|
|
9
|
+
*/
|
|
10
|
+
export async function loadUserConfig(projectRoot) {
|
|
11
|
+
const configPath = path.join(projectRoot, "src", "cookiebanner", "config.ts");
|
|
12
|
+
let userConfig = {};
|
|
13
|
+
try {
|
|
14
|
+
// 🔑 IMPORTANT:
|
|
15
|
+
// Bust Node ESM import cache using file modified time
|
|
16
|
+
const stat = fs.statSync(configPath);
|
|
17
|
+
const cacheBuster = `?v=${stat.mtimeMs}`;
|
|
18
|
+
const imported = await import(
|
|
19
|
+
/* @vite-ignore */
|
|
20
|
+
pathToFileURL(configPath).href + cacheBuster);
|
|
21
|
+
userConfig = imported?.default ?? {};
|
|
22
|
+
}
|
|
23
|
+
catch (err) {
|
|
24
|
+
console.warn("[cookiebanner] Failed to load user config, falling back to defaults:", err);
|
|
25
|
+
}
|
|
26
|
+
return {
|
|
27
|
+
/* ─────────────────────────────
|
|
28
|
+
Site name
|
|
29
|
+
───────────────────────────── */
|
|
30
|
+
siteName: userConfig.siteName ?? DEFAULT_CONFIG.siteName,
|
|
31
|
+
/* ─────────────────────────────
|
|
32
|
+
Policy URL
|
|
33
|
+
───────────────────────────── */
|
|
34
|
+
policyUrl: userConfig.policyUrl ?? DEFAULT_CONFIG.policyUrl,
|
|
35
|
+
/* ─────────────────────────────
|
|
36
|
+
Consent settings
|
|
37
|
+
───────────────────────────── */
|
|
38
|
+
consent: {
|
|
39
|
+
enabled: userConfig.consent?.enabled ??
|
|
40
|
+
DEFAULT_CONFIG.consent.enabled,
|
|
41
|
+
days: userConfig.consent?.days ??
|
|
42
|
+
DEFAULT_CONFIG.consent.days,
|
|
43
|
+
storageKey: userConfig.consent?.storageKey ??
|
|
44
|
+
DEFAULT_CONFIG.consent.storageKey
|
|
45
|
+
},
|
|
46
|
+
/* ─────────────────────────────
|
|
47
|
+
Categories
|
|
48
|
+
───────────────────────────── */
|
|
49
|
+
categories: mergeCategories(userConfig.categories, DEFAULT_CONFIG.categories)
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Merge category config safely.
|
|
54
|
+
* Defaults are preserved, user overrides where provided.
|
|
55
|
+
*/
|
|
56
|
+
function mergeCategories(userCategories, defaultCategories) {
|
|
57
|
+
const merged = {};
|
|
58
|
+
// Start with defaults
|
|
59
|
+
for (const key of Object.keys(defaultCategories)) {
|
|
60
|
+
merged[key] = {
|
|
61
|
+
...defaultCategories[key],
|
|
62
|
+
...(userCategories?.[key] ?? {})
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
// Include any custom categories the user added
|
|
66
|
+
if (userCategories) {
|
|
67
|
+
for (const key of Object.keys(userCategories)) {
|
|
68
|
+
if (!merged[key]) {
|
|
69
|
+
merged[key] = userCategories[key];
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
return merged;
|
|
74
|
+
}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,333 @@
|
|
|
1
|
+
export default function astroCookieBanner(options = {}) {
|
|
2
|
+
const siteName = options.siteName ?? "This website";
|
|
3
|
+
const policyUrl = options.policyUrl ?? "/privacy";
|
|
4
|
+
const consentDays = options.consent?.days ?? 30;
|
|
5
|
+
const storageKey = options.consent?.storageKey ?? "astro-cookie-consent";
|
|
6
|
+
const defaultCategories = {
|
|
7
|
+
essential: true,
|
|
8
|
+
analytics: false,
|
|
9
|
+
marketing: false,
|
|
10
|
+
...options.categories
|
|
11
|
+
};
|
|
12
|
+
const ttl = consentDays * 24 * 60 * 60 * 1000;
|
|
13
|
+
return {
|
|
14
|
+
name: "astro-cookiebanner",
|
|
15
|
+
hooks: {
|
|
16
|
+
"astro:config:setup": ({ injectScript }) => {
|
|
17
|
+
/* ─────────────────────────────────────
|
|
18
|
+
ALL STYLES (banner + modal)
|
|
19
|
+
───────────────────────────────────── */
|
|
20
|
+
injectScript("head-inline", `
|
|
21
|
+
const style = document.createElement("style");
|
|
22
|
+
style.innerHTML = \`
|
|
23
|
+
:root {
|
|
24
|
+
--cb-bg: rgba(12,18,32,.88);
|
|
25
|
+
--cb-border: rgba(255,255,255,.08);
|
|
26
|
+
--cb-text: #e5e7eb;
|
|
27
|
+
--cb-muted: #9ca3af;
|
|
28
|
+
--cb-link: #60a5fa;
|
|
29
|
+
--cb-accept: #22c55e;
|
|
30
|
+
--cb-reject: #374151;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/* ───── Banner ───── */
|
|
34
|
+
|
|
35
|
+
#astro-cookie-banner {
|
|
36
|
+
position: fixed;
|
|
37
|
+
left: 16px;
|
|
38
|
+
right: 16px;
|
|
39
|
+
bottom: 16px;
|
|
40
|
+
z-index: 9999;
|
|
41
|
+
font-family: system-ui,-apple-system,BlinkMacSystemFont,sans-serif;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
.cb-container {
|
|
45
|
+
max-width: 1200px;
|
|
46
|
+
margin: 0 auto;
|
|
47
|
+
padding: 20px 24px;
|
|
48
|
+
display: flex;
|
|
49
|
+
gap: 24px;
|
|
50
|
+
justify-content: space-between;
|
|
51
|
+
align-items: center;
|
|
52
|
+
|
|
53
|
+
background: var(--cb-bg);
|
|
54
|
+
backdrop-filter: blur(14px);
|
|
55
|
+
border-radius: 16px;
|
|
56
|
+
border: 1px solid var(--cb-border);
|
|
57
|
+
box-shadow: 0 20px 40px rgba(0,0,0,.35);
|
|
58
|
+
|
|
59
|
+
color: var(--cb-text);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
.cb-text {
|
|
63
|
+
max-width: 760px;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
.cb-title {
|
|
67
|
+
font-size: 16px;
|
|
68
|
+
font-weight: 600;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
.cb-desc {
|
|
72
|
+
font-size: 14px;
|
|
73
|
+
color: var(--cb-muted);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
.cb-desc a {
|
|
77
|
+
color: var(--cb-link);
|
|
78
|
+
text-decoration: none;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
.cb-desc a:hover {
|
|
82
|
+
text-decoration: underline;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
.cb-actions {
|
|
86
|
+
display: flex;
|
|
87
|
+
gap: 10px;
|
|
88
|
+
flex-shrink: 0;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
.cb-actions button {
|
|
92
|
+
padding: 10px 18px;
|
|
93
|
+
border-radius: 999px;
|
|
94
|
+
border: 0;
|
|
95
|
+
font-size: 14px;
|
|
96
|
+
font-weight: 600;
|
|
97
|
+
cursor: pointer;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
.cb-accept {
|
|
101
|
+
background: var(--cb-accept);
|
|
102
|
+
color: #052e16;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
.cb-reject {
|
|
106
|
+
background: var(--cb-reject);
|
|
107
|
+
color: #e5e7eb;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
.cb-manage {
|
|
111
|
+
background: transparent;
|
|
112
|
+
color: var(--cb-text);
|
|
113
|
+
border: 1px solid var(--cb-border);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/* ───── Modal ───── */
|
|
117
|
+
|
|
118
|
+
#astro-cookie-modal {
|
|
119
|
+
position: fixed;
|
|
120
|
+
inset: 0;
|
|
121
|
+
background: rgba(0,0,0,.55);
|
|
122
|
+
display: flex;
|
|
123
|
+
align-items: center;
|
|
124
|
+
justify-content: center;
|
|
125
|
+
z-index: 10000;
|
|
126
|
+
font-family: system-ui,-apple-system,BlinkMacSystemFont,sans-serif;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
.cb-modal {
|
|
130
|
+
width: 100%;
|
|
131
|
+
max-width: 480px;
|
|
132
|
+
background: #0c1220;
|
|
133
|
+
border-radius: 18px;
|
|
134
|
+
padding: 24px;
|
|
135
|
+
border: 1px solid var(--cb-border);
|
|
136
|
+
color: var(--cb-text);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
.cb-modal h3 {
|
|
140
|
+
margin: 0 0 16px;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
.cb-row {
|
|
144
|
+
display: flex;
|
|
145
|
+
justify-content: space-between;
|
|
146
|
+
align-items: center;
|
|
147
|
+
padding: 12px 0;
|
|
148
|
+
border-bottom: 1px solid var(--cb-border);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
.cb-row:last-child {
|
|
152
|
+
border-bottom: 0;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
.cb-toggle {
|
|
156
|
+
width: 44px;
|
|
157
|
+
height: 24px;
|
|
158
|
+
background: #374151;
|
|
159
|
+
border-radius: 999px;
|
|
160
|
+
position: relative;
|
|
161
|
+
cursor: pointer;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
.cb-toggle span {
|
|
165
|
+
position: absolute;
|
|
166
|
+
width: 18px;
|
|
167
|
+
height: 18px;
|
|
168
|
+
background: #fff;
|
|
169
|
+
border-radius: 50%;
|
|
170
|
+
top: 3px;
|
|
171
|
+
left: 3px;
|
|
172
|
+
transition: transform .2s;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
.cb-toggle.active {
|
|
176
|
+
background: var(--cb-accept);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
.cb-toggle.active span {
|
|
180
|
+
transform: translateX(20px);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
@media (max-width: 640px) {
|
|
184
|
+
.cb-container {
|
|
185
|
+
flex-direction: column;
|
|
186
|
+
align-items: stretch;
|
|
187
|
+
gap: 16px;
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
\`;
|
|
191
|
+
document.head.appendChild(style);
|
|
192
|
+
`);
|
|
193
|
+
/* ─────────────────────────────────────
|
|
194
|
+
Consent runtime
|
|
195
|
+
───────────────────────────────────── */
|
|
196
|
+
injectScript("page", `
|
|
197
|
+
(() => {
|
|
198
|
+
const KEY = "${storageKey}";
|
|
199
|
+
const TTL = ${ttl};
|
|
200
|
+
|
|
201
|
+
function now(){ return Date.now(); }
|
|
202
|
+
|
|
203
|
+
function read(){
|
|
204
|
+
try{
|
|
205
|
+
const raw = localStorage.getItem(KEY);
|
|
206
|
+
if(!raw) return null;
|
|
207
|
+
const data = JSON.parse(raw);
|
|
208
|
+
if(data.expiresAt < now()){
|
|
209
|
+
localStorage.removeItem(KEY);
|
|
210
|
+
return null;
|
|
211
|
+
}
|
|
212
|
+
return data;
|
|
213
|
+
}catch{ return null; }
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
function write(categories){
|
|
217
|
+
localStorage.setItem(KEY, JSON.stringify({
|
|
218
|
+
updatedAt: now(),
|
|
219
|
+
expiresAt: now() + TTL,
|
|
220
|
+
categories
|
|
221
|
+
}));
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
window.cookieConsent = {
|
|
225
|
+
get: read,
|
|
226
|
+
set: write,
|
|
227
|
+
reset(){
|
|
228
|
+
localStorage.removeItem(KEY);
|
|
229
|
+
location.reload();
|
|
230
|
+
}
|
|
231
|
+
};
|
|
232
|
+
})();
|
|
233
|
+
`);
|
|
234
|
+
/* ─────────────────────────────────────
|
|
235
|
+
Banner + modal UI
|
|
236
|
+
───────────────────────────────────── */
|
|
237
|
+
injectScript("page", `
|
|
238
|
+
(() => {
|
|
239
|
+
if (window.cookieConsent.get()) return;
|
|
240
|
+
|
|
241
|
+
const state = { ...${JSON.stringify(defaultCategories)} };
|
|
242
|
+
|
|
243
|
+
const banner = document.createElement("div");
|
|
244
|
+
banner.id = "astro-cookie-banner";
|
|
245
|
+
|
|
246
|
+
banner.innerHTML = \`
|
|
247
|
+
<div class="cb-container">
|
|
248
|
+
<div class="cb-text">
|
|
249
|
+
<div class="cb-title">${siteName} uses cookies</div>
|
|
250
|
+
<div class="cb-desc">
|
|
251
|
+
Choose how your data is used.
|
|
252
|
+
<a href="${policyUrl}">Learn more</a>
|
|
253
|
+
</div>
|
|
254
|
+
</div>
|
|
255
|
+
<div class="cb-actions">
|
|
256
|
+
<button class="cb-manage">Manage</button>
|
|
257
|
+
<button class="cb-reject">Reject</button>
|
|
258
|
+
<button class="cb-accept">Accept all</button>
|
|
259
|
+
</div>
|
|
260
|
+
</div>
|
|
261
|
+
\`;
|
|
262
|
+
|
|
263
|
+
document.body.appendChild(banner);
|
|
264
|
+
|
|
265
|
+
banner.querySelector(".cb-accept").onclick = () => {
|
|
266
|
+
window.cookieConsent.set({ essential:true, analytics:true, marketing:true });
|
|
267
|
+
banner.remove();
|
|
268
|
+
};
|
|
269
|
+
|
|
270
|
+
banner.querySelector(".cb-reject").onclick = () => {
|
|
271
|
+
window.cookieConsent.set({ essential:true });
|
|
272
|
+
banner.remove();
|
|
273
|
+
};
|
|
274
|
+
|
|
275
|
+
banner.querySelector(".cb-manage").onclick = openModal;
|
|
276
|
+
|
|
277
|
+
function openModal(){
|
|
278
|
+
const modal = document.createElement("div");
|
|
279
|
+
modal.id = "astro-cookie-modal";
|
|
280
|
+
|
|
281
|
+
modal.innerHTML = \`
|
|
282
|
+
<div class="cb-modal">
|
|
283
|
+
<h3>Cookie preferences</h3>
|
|
284
|
+
|
|
285
|
+
<div class="cb-row">
|
|
286
|
+
<span>Essential</span>
|
|
287
|
+
<strong>Always on</strong>
|
|
288
|
+
</div>
|
|
289
|
+
|
|
290
|
+
<div class="cb-row">
|
|
291
|
+
<span>Analytics</span>
|
|
292
|
+
<div class="cb-toggle" data-key="analytics"><span></span></div>
|
|
293
|
+
</div>
|
|
294
|
+
|
|
295
|
+
<div class="cb-row">
|
|
296
|
+
<span>Marketing</span>
|
|
297
|
+
<div class="cb-toggle" data-key="marketing"><span></span></div>
|
|
298
|
+
</div>
|
|
299
|
+
|
|
300
|
+
<div class="cb-actions" style="margin-top:16px;justify-content:flex-end">
|
|
301
|
+
<button class="cb-accept">Save preferences</button>
|
|
302
|
+
</div>
|
|
303
|
+
</div>
|
|
304
|
+
\`;
|
|
305
|
+
|
|
306
|
+
document.body.appendChild(modal);
|
|
307
|
+
|
|
308
|
+
modal.querySelectorAll(".cb-toggle").forEach(toggle => {
|
|
309
|
+
const key = toggle.getAttribute("data-key");
|
|
310
|
+
if(state[key]) toggle.classList.add("active");
|
|
311
|
+
|
|
312
|
+
toggle.onclick = () => {
|
|
313
|
+
state[key] = !state[key];
|
|
314
|
+
toggle.classList.toggle("active");
|
|
315
|
+
};
|
|
316
|
+
});
|
|
317
|
+
|
|
318
|
+
modal.querySelector(".cb-accept").onclick = () => {
|
|
319
|
+
window.cookieConsent.set({ essential:true, ...state });
|
|
320
|
+
modal.remove();
|
|
321
|
+
banner.remove();
|
|
322
|
+
};
|
|
323
|
+
|
|
324
|
+
modal.onclick = e => {
|
|
325
|
+
if(e.target === modal) modal.remove();
|
|
326
|
+
};
|
|
327
|
+
}
|
|
328
|
+
})();
|
|
329
|
+
`);
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
};
|
|
333
|
+
}
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
export const DEFAULT_CSS = `
|
|
2
|
+
:root {
|
|
3
|
+
/* Layout */
|
|
4
|
+
--cb-z-index: 9999;
|
|
5
|
+
--cb-max-width: 960px;
|
|
6
|
+
--cb-padding: 16px;
|
|
7
|
+
--cb-gap: 12px;
|
|
8
|
+
--cb-radius: 10px;
|
|
9
|
+
|
|
10
|
+
/* Colours */
|
|
11
|
+
--cb-bg: #111827;
|
|
12
|
+
--cb-surface: #1f2933;
|
|
13
|
+
--cb-text: #ffffff;
|
|
14
|
+
--cb-muted: #9ca3af;
|
|
15
|
+
--cb-border: #374151;
|
|
16
|
+
--cb-accent: #6366f1;
|
|
17
|
+
|
|
18
|
+
/* Buttons */
|
|
19
|
+
--cb-btn-bg: var(--cb-accent);
|
|
20
|
+
--cb-btn-text: #ffffff;
|
|
21
|
+
--cb-btn-secondary-bg: transparent;
|
|
22
|
+
--cb-btn-secondary-text: var(--cb-text);
|
|
23
|
+
--cb-btn-border: var(--cb-border);
|
|
24
|
+
|
|
25
|
+
/* Typography */
|
|
26
|
+
--cb-font-family: system-ui, -apple-system, BlinkMacSystemFont, sans-serif;
|
|
27
|
+
--cb-title-size: 1rem;
|
|
28
|
+
--cb-text-size: 0.875rem;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
#astro-cookie-banner {
|
|
32
|
+
position: fixed;
|
|
33
|
+
bottom: 0;
|
|
34
|
+
left: 0;
|
|
35
|
+
right: 0;
|
|
36
|
+
z-index: var(--cb-z-index);
|
|
37
|
+
background: var(--cb-bg);
|
|
38
|
+
color: var(--cb-text);
|
|
39
|
+
font-family: var(--cb-font-family);
|
|
40
|
+
border-top: 1px solid var(--cb-border);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
#astro-cookie-banner > * {
|
|
44
|
+
max-width: var(--cb-max-width);
|
|
45
|
+
margin: 0 auto;
|
|
46
|
+
padding: var(--cb-padding);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
#astro-cookie-banner h2 {
|
|
50
|
+
margin: 0 0 4px;
|
|
51
|
+
font-size: var(--cb-title-size);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
#astro-cookie-banner p {
|
|
55
|
+
margin: 0;
|
|
56
|
+
font-size: var(--cb-text-size);
|
|
57
|
+
color: var(--cb-muted);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
#astro-cookie-banner a {
|
|
61
|
+
color: var(--cb-accent);
|
|
62
|
+
text-decoration: underline;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
.astro-cookie-categories {
|
|
66
|
+
display: grid;
|
|
67
|
+
gap: var(--cb-gap);
|
|
68
|
+
margin-top: var(--cb-gap);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
.astro-cookie-category {
|
|
72
|
+
background: var(--cb-surface);
|
|
73
|
+
padding: 12px;
|
|
74
|
+
border-radius: var(--cb-radius);
|
|
75
|
+
border: 1px solid var(--cb-border);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
.astro-cookie-label {
|
|
79
|
+
display: flex;
|
|
80
|
+
align-items: center;
|
|
81
|
+
gap: 8px;
|
|
82
|
+
font-weight: 600;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
.astro-cookie-description {
|
|
86
|
+
margin: 4px 0 0 26px;
|
|
87
|
+
font-size: 0.8rem;
|
|
88
|
+
color: var(--cb-muted);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
.astro-cookie-actions {
|
|
92
|
+
display: flex;
|
|
93
|
+
gap: 8px;
|
|
94
|
+
justify-content: flex-end;
|
|
95
|
+
margin-top: var(--cb-gap);
|
|
96
|
+
flex-wrap: wrap;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
.astro-cookie-actions button {
|
|
100
|
+
padding: 8px 14px;
|
|
101
|
+
border-radius: var(--cb-radius);
|
|
102
|
+
font-size: 0.875rem;
|
|
103
|
+
cursor: pointer;
|
|
104
|
+
border: 1px solid var(--cb-btn-border);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
.astro-cookie-actions button:first-child {
|
|
108
|
+
background: var(--cb-btn-secondary-bg);
|
|
109
|
+
color: var(--cb-btn-secondary-text);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
.astro-cookie-actions button:last-child {
|
|
113
|
+
background: var(--cb-btn-bg);
|
|
114
|
+
color: var(--cb-btn-text);
|
|
115
|
+
border-color: var(--cb-btn-bg);
|
|
116
|
+
}
|
|
117
|
+
`;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/package.json
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "astro-consent",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "A privacy-first, GDPR-compliant cookie consent banner for Astro with a built-in preferences modal, zero dependencies, and full theme control.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"author": {
|
|
7
|
+
"name": "Velohost",
|
|
8
|
+
"url": "https://velohost.co.uk"
|
|
9
|
+
},
|
|
10
|
+
"license": "BSD-4-Clause",
|
|
11
|
+
"homepage": "https://velohost.co.uk",
|
|
12
|
+
"repository": {
|
|
13
|
+
"type": "git",
|
|
14
|
+
"url": "git+https://github.com/velohost/astro-cookiebanner.git"
|
|
15
|
+
},
|
|
16
|
+
"bugs": {
|
|
17
|
+
"url": "https://github.com/velohost/astro-cookiebanner/issues"
|
|
18
|
+
},
|
|
19
|
+
"main": "./dist/index.js",
|
|
20
|
+
"exports": {
|
|
21
|
+
".": "./dist/index.js"
|
|
22
|
+
},
|
|
23
|
+
"bin": {
|
|
24
|
+
"astro-cookiebanner": "dist/cli.cjs"
|
|
25
|
+
},
|
|
26
|
+
"files": [
|
|
27
|
+
"dist",
|
|
28
|
+
"README.md",
|
|
29
|
+
"LICENSE.md"
|
|
30
|
+
],
|
|
31
|
+
"scripts": {
|
|
32
|
+
"build": "tsc -p tsconfig.json && tsc -p tsconfig.cli.json"
|
|
33
|
+
},
|
|
34
|
+
"keywords": [
|
|
35
|
+
"astro",
|
|
36
|
+
"astro-integration",
|
|
37
|
+
"cookie-banner",
|
|
38
|
+
"cookie-consent",
|
|
39
|
+
"gdpr",
|
|
40
|
+
"privacy",
|
|
41
|
+
"analytics-consent",
|
|
42
|
+
"marketing-consent",
|
|
43
|
+
"withastro",
|
|
44
|
+
"velohost"
|
|
45
|
+
],
|
|
46
|
+
"peerDependencies": {
|
|
47
|
+
"astro": "^4.0.0 || ^5.0.0"
|
|
48
|
+
},
|
|
49
|
+
"devDependencies": {
|
|
50
|
+
"astro": "^5.16.6"
|
|
51
|
+
},
|
|
52
|
+
"engines": {
|
|
53
|
+
"node": ">=18.0.0"
|
|
54
|
+
}
|
|
55
|
+
}
|