keksi 0.1.3 → 0.1.5
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/README.md +13 -6
- package/dist/cli.js +27 -1
- package/dist/config.d.ts +3 -0
- package/dist/glob.d.ts +2 -0
- package/dist/i18n.d.ts +2 -1
- package/dist/index.js +100 -46
- package/dist/playwright.js +52 -14
- package/dist/render.d.ts +1 -1
- package/dist/runtime.d.ts +3 -0
- package/dist/scan.js +27 -1
- package/dist/sveltekit.js +100 -46
- package/dist/types.d.ts +18 -6
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -6,7 +6,7 @@ texts in English, Finnish and Swedish.
|
|
|
6
6
|
keksi loads the trackers itself, and only after consent. You don't add their script tags: you put their ids in the
|
|
7
7
|
config, so there's nothing to block.
|
|
8
8
|
|
|
9
|
-
- Google Analytics 4, Google Ads, Meta Pixel and YouTube. The short list is deliberate.
|
|
9
|
+
- Google Analytics 4, Google Ads, Meta Pixel, PostHog and YouTube. The short list is deliberate.
|
|
10
10
|
- About 5 KB gzipped, inline. No extra requests, no dependencies.
|
|
11
11
|
- Rendered on the server. Works on static pages, with no layout shift.
|
|
12
12
|
- The cookie policy table is generated from the same data.
|
|
@@ -25,6 +25,7 @@ export default defineConfig({
|
|
|
25
25
|
ga: 'G-XXXXXXX',
|
|
26
26
|
googleAds: 'AW-123456789',
|
|
27
27
|
meta: '1234567890',
|
|
28
|
+
posthog: { key: 'phc_XXXX' }, // EU cloud by default; set host for US or a proxy
|
|
28
29
|
youtube: true,
|
|
29
30
|
policyUrl: '/evasteet',
|
|
30
31
|
});
|
|
@@ -82,16 +83,18 @@ string.
|
|
|
82
83
|
|
|
83
84
|
## Tracking events
|
|
84
85
|
|
|
85
|
-
`gtag()
|
|
86
|
+
`gtag()`, `fbq()` and `posthog` are always defined, so you can call them anytime:
|
|
86
87
|
|
|
87
88
|
```ts
|
|
88
89
|
gtag('event', 'generate_lead');
|
|
89
90
|
fbq('track', 'Lead');
|
|
91
|
+
posthog.capture('signup');
|
|
90
92
|
```
|
|
91
93
|
|
|
92
94
|
- Calls made before consent are queued. They're sent only if the visitor consents on that page.
|
|
93
95
|
- Google gets Consent Mode v2 signals (basic mode).
|
|
94
|
-
- GA4
|
|
96
|
+
- GA4, Meta Pixel and PostHog track single-page navigation themselves.
|
|
97
|
+
- PostHog counts as analytics. With the US host, the settings say data is transferred to the US.
|
|
95
98
|
|
|
96
99
|
## YouTube
|
|
97
100
|
|
|
@@ -99,9 +102,13 @@ fbq('track', 'Lead');
|
|
|
99
102
|
<keksi-youtube video="aqz-KE-bpKQ" label="Esittelyvideo" poster="/img/poster.jpg"></keksi-youtube>
|
|
100
103
|
```
|
|
101
104
|
|
|
102
|
-
- Shows a placeholder
|
|
103
|
-
-
|
|
104
|
-
-
|
|
105
|
+
- Shows a YouTube-style placeholder. Clicking play is the consent: it's remembered, and all videos load from
|
|
106
|
+
`youtube-nocookie.com` from then on.
|
|
107
|
+
- Marketing consent in the banner covers YouTube too, and turning it off withdraws YouTube.
|
|
108
|
+
- With YouTube as the only provider, the banner doesn't show by itself. The `data-keksi-open` link still opens the
|
|
109
|
+
settings for withdrawing.
|
|
110
|
+
- `poster` is optional and should be self-hosted. YouTube's thumbnails come from `i.ytimg.com`, which would contact
|
|
111
|
+
Google before consent.
|
|
105
112
|
|
|
106
113
|
## API
|
|
107
114
|
|
package/dist/cli.js
CHANGED
|
@@ -4,6 +4,9 @@
|
|
|
4
4
|
import { existsSync, readFileSync, writeFileSync } from "node:fs";
|
|
5
5
|
import { parseArgs } from "node:util";
|
|
6
6
|
|
|
7
|
+
// src/glob.ts
|
|
8
|
+
var glob = (pattern, name) => new RegExp(`^${pattern.split("*").map((s) => s.replace(/[.+?^${}()|[\]\\]/g, "\\$&")).join(".*")}$`).test(name);
|
|
9
|
+
|
|
7
10
|
// src/providers.ts
|
|
8
11
|
var GOOGLE = { company: "Google Ireland Limited", privacyPolicy: "https://policies.google.com/privacy", transfer: "dpf" };
|
|
9
12
|
var META = { company: "Meta Platforms Ireland Limited", privacyPolicy: "https://www.facebook.com/privacy/policy/", transfer: "dpf" };
|
|
@@ -64,6 +67,29 @@ var providers = [
|
|
|
64
67
|
],
|
|
65
68
|
hosts: ["facebook.net", "facebook.com"]
|
|
66
69
|
},
|
|
70
|
+
{
|
|
71
|
+
id: "posthog",
|
|
72
|
+
name: "PostHog",
|
|
73
|
+
company: "PostHog Inc.",
|
|
74
|
+
privacyPolicy: "https://posthog.com/privacy",
|
|
75
|
+
category: "analytics",
|
|
76
|
+
purpose: {
|
|
77
|
+
fi: "Tuoteanalytiikka: sivujen katselut, klikkaukset ja k\xE4ytt\xF6tavat. Voi tallentaa istuntoja, jos istuntotallenteet ovat k\xE4yt\xF6ss\xE4.",
|
|
78
|
+
sv: "Produktanalys: sidvisningar, klick och anv\xE4ndningsm\xF6nster. Kan spela in sessioner om sessionsinspelning \xE4r aktiverad.",
|
|
79
|
+
en: "Product analytics: page views, clicks and usage patterns. Can record sessions if session replay is enabled."
|
|
80
|
+
},
|
|
81
|
+
cookies: [
|
|
82
|
+
{ name: "ph_*_posthog", duration: "1y" },
|
|
83
|
+
{ name: "ph_*_posthog_cpm", duration: "1y" },
|
|
84
|
+
{ name: "ph_*_posthog", duration: "persistent", storage: "localStorage" },
|
|
85
|
+
{ name: "ph_*_posthog__flags", duration: "persistent", storage: "localStorage" },
|
|
86
|
+
{ name: "ph_*_posthog", duration: "session", storage: "sessionStorage" },
|
|
87
|
+
{ name: "ph_*_primary_window_exists", duration: "session", storage: "sessionStorage" },
|
|
88
|
+
{ name: "ph_*_session_registered_properties", duration: "session", storage: "sessionStorage" },
|
|
89
|
+
{ name: "ph_*_window_id", duration: "session", storage: "sessionStorage" }
|
|
90
|
+
],
|
|
91
|
+
hosts: ["posthog.com"]
|
|
92
|
+
},
|
|
67
93
|
{
|
|
68
94
|
id: "youtube",
|
|
69
95
|
name: "YouTube",
|
|
@@ -119,7 +145,7 @@ var purposes = {
|
|
|
119
145
|
};
|
|
120
146
|
var trackerNames = providers.flatMap((p) => p.cookies.flatMap((c) => c.name.split(", ")));
|
|
121
147
|
var trackerHosts = providers.flatMap((p) => p.hosts);
|
|
122
|
-
var matches = (patterns, name) => patterns.some((p) =>
|
|
148
|
+
var matches = (patterns, name) => patterns.some((p) => glob(p, name));
|
|
123
149
|
function purposeOf(name) {
|
|
124
150
|
const k = Object.keys(purposes).find((p) => matches([p], name));
|
|
125
151
|
return k ? purposes[k] : void 0;
|
package/dist/config.d.ts
CHANGED
|
@@ -3,6 +3,9 @@ import type { Category, KeksiConfig, Locale, Provider, RuntimeData } from './typ
|
|
|
3
3
|
export declare function defineConfig(config: KeksiConfig): KeksiConfig;
|
|
4
4
|
export declare function activeProviders(config: KeksiConfig): Provider[];
|
|
5
5
|
export declare function categoriesOf(config: KeksiConfig): Category[];
|
|
6
|
+
/** False when YouTube is the only provider: a click on a video is the consent, so the banner doesn't show by itself. */
|
|
7
|
+
export declare function needsBanner(config: KeksiConfig): boolean;
|
|
8
|
+
export declare function policyOf(config: KeksiConfig, locale: Locale): string | undefined;
|
|
6
9
|
export declare function textsFor(config: KeksiConfig, locale: Locale): Texts;
|
|
7
10
|
/** Changes when a provider is added or removed, which asks everyone again. Ids and texts can change freely. */
|
|
8
11
|
export declare function version(config: KeksiConfig): string;
|
package/dist/glob.d.ts
ADDED
package/dist/i18n.d.ts
CHANGED
|
@@ -2,7 +2,7 @@ import type { Locale } from './types.js';
|
|
|
2
2
|
declare const fi: {
|
|
3
3
|
title: string;
|
|
4
4
|
intro: string;
|
|
5
|
-
|
|
5
|
+
change: string;
|
|
6
6
|
more: string;
|
|
7
7
|
accept: string;
|
|
8
8
|
reject: string;
|
|
@@ -22,6 +22,7 @@ declare const fi: {
|
|
|
22
22
|
consentPurpose: string;
|
|
23
23
|
privacyPolicy: string;
|
|
24
24
|
dpf: string;
|
|
25
|
+
us: string;
|
|
25
26
|
name: string;
|
|
26
27
|
duration: string;
|
|
27
28
|
purpose: string;
|
package/dist/index.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
var fi = {
|
|
3
3
|
title: "Ev\xE4steet",
|
|
4
4
|
intro: "K\xE4yt\xE4mme v\xE4ltt\xE4m\xE4tt\xF6mien ev\xE4steiden lis\xE4ksi seuraavia palveluita vain, jos annat siihen luvan:",
|
|
5
|
-
|
|
5
|
+
change: "Voit muuttaa valintaasi milloin tahansa.",
|
|
6
6
|
more: "Lue lis\xE4\xE4",
|
|
7
7
|
accept: "Hyv\xE4ksy kaikki",
|
|
8
8
|
reject: "Hylk\xE4\xE4 kaikki",
|
|
@@ -22,6 +22,7 @@ var fi = {
|
|
|
22
22
|
consentPurpose: "Tallentaa ev\xE4stevalintasi.",
|
|
23
23
|
privacyPolicy: "Tietosuojaseloste",
|
|
24
24
|
dpf: "Tietoja voidaan siirt\xE4\xE4 Yhdysvaltoihin EU:n ja Yhdysvaltojen v\xE4lisen tietosuojakehyksen (DPF) perusteella.",
|
|
25
|
+
us: "Tietoja siirret\xE4\xE4n Yhdysvaltoihin.",
|
|
25
26
|
name: "Nimi",
|
|
26
27
|
duration: "Kesto",
|
|
27
28
|
purpose: "Tarkoitus",
|
|
@@ -32,14 +33,14 @@ var fi = {
|
|
|
32
33
|
mo: "kuukausi|kuukautta",
|
|
33
34
|
d: "p\xE4iv\xE4|p\xE4iv\xE4\xE4",
|
|
34
35
|
min: "minuutti|minuuttia",
|
|
35
|
-
ytText: "
|
|
36
|
-
ytButton: "
|
|
37
|
-
ytOpen: "
|
|
36
|
+
ytText: "Toistamalla videon hyv\xE4ksyt YouTuben ev\xE4steet.",
|
|
37
|
+
ytButton: "Toista video",
|
|
38
|
+
ytOpen: "Katso YouTubessa"
|
|
38
39
|
};
|
|
39
40
|
var sv = {
|
|
40
41
|
title: "Kakor",
|
|
41
42
|
intro: "Ut\xF6ver n\xF6dv\xE4ndiga kakor anv\xE4nder vi f\xF6ljande tj\xE4nster endast om du ger ditt samtycke:",
|
|
42
|
-
|
|
43
|
+
change: "Du kan \xE4ndra ditt val n\xE4r som helst.",
|
|
43
44
|
more: "L\xE4s mer",
|
|
44
45
|
accept: "Godk\xE4nn alla",
|
|
45
46
|
reject: "Avvisa alla",
|
|
@@ -59,6 +60,7 @@ var sv = {
|
|
|
59
60
|
consentPurpose: "Sparar ditt val av kakor.",
|
|
60
61
|
privacyPolicy: "Dataskyddsbeskrivning",
|
|
61
62
|
dpf: "Uppgifter kan \xF6verf\xF6ras till USA med st\xF6d av ramverket f\xF6r dataskydd mellan EU och USA (DPF).",
|
|
63
|
+
us: "Uppgifter \xF6verf\xF6rs till USA.",
|
|
62
64
|
name: "Namn",
|
|
63
65
|
duration: "Varaktighet",
|
|
64
66
|
purpose: "Syfte",
|
|
@@ -69,14 +71,14 @@ var sv = {
|
|
|
69
71
|
mo: "m\xE5nad|m\xE5nader",
|
|
70
72
|
d: "dag|dagar",
|
|
71
73
|
min: "minut|minuter",
|
|
72
|
-
ytText: "
|
|
73
|
-
ytButton: "
|
|
74
|
-
ytOpen: "
|
|
74
|
+
ytText: "Genom att spela upp videon godk\xE4nner du kakor fr\xE5n YouTube.",
|
|
75
|
+
ytButton: "Spela upp video",
|
|
76
|
+
ytOpen: "Titta p\xE5 YouTube"
|
|
75
77
|
};
|
|
76
78
|
var en = {
|
|
77
79
|
title: "Cookies",
|
|
78
80
|
intro: "Besides necessary cookies, we use the following services only if you allow it:",
|
|
79
|
-
|
|
81
|
+
change: "You can change your choice at any time.",
|
|
80
82
|
more: "Read more",
|
|
81
83
|
accept: "Accept all",
|
|
82
84
|
reject: "Reject all",
|
|
@@ -96,6 +98,7 @@ var en = {
|
|
|
96
98
|
consentPurpose: "Stores your cookie choice.",
|
|
97
99
|
privacyPolicy: "Privacy policy",
|
|
98
100
|
dpf: "Data may be transferred to the United States under the EU\u2013US Data Privacy Framework (DPF).",
|
|
101
|
+
us: "Data is transferred to the United States.",
|
|
99
102
|
name: "Name",
|
|
100
103
|
duration: "Duration",
|
|
101
104
|
purpose: "Purpose",
|
|
@@ -106,9 +109,9 @@ var en = {
|
|
|
106
109
|
mo: "month|months",
|
|
107
110
|
d: "day|days",
|
|
108
111
|
min: "minute|minutes",
|
|
109
|
-
ytText: "
|
|
110
|
-
ytButton: "
|
|
111
|
-
ytOpen: "
|
|
112
|
+
ytText: "By playing the video, you accept cookies from YouTube.",
|
|
113
|
+
ytButton: "Play video",
|
|
114
|
+
ytOpen: "Watch on YouTube"
|
|
112
115
|
};
|
|
113
116
|
var texts = { fi, sv, en };
|
|
114
117
|
|
|
@@ -172,6 +175,29 @@ var providers = [
|
|
|
172
175
|
],
|
|
173
176
|
hosts: ["facebook.net", "facebook.com"]
|
|
174
177
|
},
|
|
178
|
+
{
|
|
179
|
+
id: "posthog",
|
|
180
|
+
name: "PostHog",
|
|
181
|
+
company: "PostHog Inc.",
|
|
182
|
+
privacyPolicy: "https://posthog.com/privacy",
|
|
183
|
+
category: "analytics",
|
|
184
|
+
purpose: {
|
|
185
|
+
fi: "Tuoteanalytiikka: sivujen katselut, klikkaukset ja k\xE4ytt\xF6tavat. Voi tallentaa istuntoja, jos istuntotallenteet ovat k\xE4yt\xF6ss\xE4.",
|
|
186
|
+
sv: "Produktanalys: sidvisningar, klick och anv\xE4ndningsm\xF6nster. Kan spela in sessioner om sessionsinspelning \xE4r aktiverad.",
|
|
187
|
+
en: "Product analytics: page views, clicks and usage patterns. Can record sessions if session replay is enabled."
|
|
188
|
+
},
|
|
189
|
+
cookies: [
|
|
190
|
+
{ name: "ph_*_posthog", duration: "1y" },
|
|
191
|
+
{ name: "ph_*_posthog_cpm", duration: "1y" },
|
|
192
|
+
{ name: "ph_*_posthog", duration: "persistent", storage: "localStorage" },
|
|
193
|
+
{ name: "ph_*_posthog__flags", duration: "persistent", storage: "localStorage" },
|
|
194
|
+
{ name: "ph_*_posthog", duration: "session", storage: "sessionStorage" },
|
|
195
|
+
{ name: "ph_*_primary_window_exists", duration: "session", storage: "sessionStorage" },
|
|
196
|
+
{ name: "ph_*_session_registered_properties", duration: "session", storage: "sessionStorage" },
|
|
197
|
+
{ name: "ph_*_window_id", duration: "session", storage: "sessionStorage" }
|
|
198
|
+
],
|
|
199
|
+
hosts: ["posthog.com"]
|
|
200
|
+
},
|
|
175
201
|
{
|
|
176
202
|
id: "youtube",
|
|
177
203
|
name: "YouTube",
|
|
@@ -200,12 +226,21 @@ function defineConfig(config) {
|
|
|
200
226
|
return config;
|
|
201
227
|
}
|
|
202
228
|
var list = (v) => v === void 0 ? [] : Array.isArray(v) ? v : [v];
|
|
229
|
+
var posthogHost = (config) => (config.posthog?.host ?? "https://eu.i.posthog.com").replace(/\/$/, "");
|
|
203
230
|
function activeProviders(config) {
|
|
204
|
-
return providers.filter(
|
|
231
|
+
return providers.filter(
|
|
232
|
+
(p) => p.id === "youtube" ? config.youtube : p.id === "posthog" ? config.posthog?.key : list(config[p.id]).length
|
|
233
|
+
).map((p) => p.id === "posthog" && /\/\/us\./.test(posthogHost(config)) ? { ...p, transfer: "us" } : p);
|
|
205
234
|
}
|
|
206
235
|
function categoriesOf(config) {
|
|
207
236
|
return ["analytics", "marketing"].filter((c) => activeProviders(config).some((p) => p.category === c));
|
|
208
237
|
}
|
|
238
|
+
function needsBanner(config) {
|
|
239
|
+
return activeProviders(config).some((p) => p.id !== "youtube");
|
|
240
|
+
}
|
|
241
|
+
function policyOf(config, locale) {
|
|
242
|
+
return typeof config.policyUrl === "string" ? config.policyUrl : config.policyUrl?.[locale];
|
|
243
|
+
}
|
|
209
244
|
function textsFor(config, locale) {
|
|
210
245
|
return { ...texts[locale], ...config.texts?.[locale] };
|
|
211
246
|
}
|
|
@@ -226,48 +261,67 @@ function runtimeData(config, locale) {
|
|
|
226
261
|
ga: list(config.ga),
|
|
227
262
|
ads: list(config.googleAds),
|
|
228
263
|
meta: list(config.meta),
|
|
264
|
+
ph: config.posthog?.key ? { key: config.posthog.key, host: posthogHost(config) } : void 0,
|
|
229
265
|
yt: !!config.youtube,
|
|
266
|
+
banner: needsBanner(config),
|
|
267
|
+
policy: policyOf(config, locale),
|
|
230
268
|
del: { analytics: del("analytics"), marketing: del("marketing") },
|
|
231
|
-
t: { ytText: t.ytText, ytButton: t.ytButton, ytOpen: t.ytOpen }
|
|
269
|
+
t: { ytText: t.ytText, ytButton: t.ytButton, ytOpen: t.ytOpen, more: t.more }
|
|
232
270
|
};
|
|
233
271
|
}
|
|
234
272
|
|
|
235
273
|
// src/styles.ts
|
|
236
274
|
var css = [
|
|
237
|
-
":where(.keksi,.keksi-
|
|
238
|
-
":where(.keksi
|
|
239
|
-
":where(.keksi){position:fixed;z-index:2147483000;inset:auto 1rem 1rem;max-width:34rem;max-height:calc(100vh - 2rem);
|
|
240
|
-
":where(.keksi,.keksi-
|
|
241
|
-
":where(.keksi)
|
|
242
|
-
":where(.keksi
|
|
243
|
-
":where(.keksi
|
|
244
|
-
":where(.keksi-
|
|
245
|
-
":where(.keksi-
|
|
275
|
+
":where(.keksi,.keksi-policy){--_bg:var(--keksi-bg,#fff);--_fg:var(--keksi-fg,#1b1b1b);--_line:var(--keksi-line,#e3e3e3);--_soft:color-mix(in srgb,var(--_fg) 4%,var(--_bg));--_r:var(--keksi-radius,8px)}",
|
|
276
|
+
":where(.keksi){box-sizing:border-box;background:var(--_bg);color:var(--_fg);font:inherit;font-size:14px;line-height:1.5;border:1px solid var(--_line);border-radius:var(--_r);box-shadow:0 8px 32px #0000002e}",
|
|
277
|
+
":where(.keksi){position:fixed;z-index:2147483000;inset:auto 1rem 1rem;display:flex;flex-direction:column;max-width:34rem;max-height:calc(100vh - 2rem);margin-inline:auto;overflow:hidden}",
|
|
278
|
+
":where(.keksi,.keksi-main,.keksi-settings)[hidden]{display:none}",
|
|
279
|
+
":where(.keksi-main,.keksi-settings){display:flex;flex-direction:column;min-height:0;margin:0}",
|
|
280
|
+
":where(.keksi:has(.keksi-settings:not([hidden]))){max-width:40rem}",
|
|
281
|
+
":where(.keksi-settings .keksi-body){padding-block:0}",
|
|
282
|
+
":where(.keksi-head){display:flex;align-items:center;justify-content:space-between;gap:1rem;padding:.875rem 1.25rem;border-bottom:1px solid var(--_line)}",
|
|
283
|
+
":where(.keksi-head) :where(h2){font-size:1rem;font-weight:600;line-height:1.3;margin:0}",
|
|
284
|
+
":where(.keksi-body){padding:1rem 1.25rem;overflow:auto}",
|
|
285
|
+
":where(.keksi) :where(.keksi-body>*){margin:0 0 .75rem}",
|
|
286
|
+
":where(.keksi) :where(.keksi-body>:last-child){margin-bottom:0}",
|
|
287
|
+
":where(.keksi) :where(ul){list-style:none;padding:0}",
|
|
288
|
+
":where(.keksi) :where(strong){font-weight:600}",
|
|
289
|
+
":where(.keksi) :where(a){color:inherit}",
|
|
290
|
+
".keksi a{text-decoration:underline}",
|
|
291
|
+
":where(.keksi-actions){display:flex;flex-wrap:wrap;align-items:center;justify-content:flex-end;gap:.5rem;padding:.75rem 1.25rem;background:var(--_soft)}",
|
|
292
|
+
":where(.keksi-btn){min-width:7.5rem;font:inherit;font-weight:600;padding:.5rem 1rem;border:1px solid var(--_fg);border-radius:calc(var(--_r) - 2px);background:var(--_bg);color:var(--_fg);cursor:pointer}",
|
|
246
293
|
":where(.keksi-btn:hover){background:var(--_fg);color:var(--_bg)}",
|
|
247
|
-
":where(.keksi
|
|
248
|
-
":where(.keksi-
|
|
249
|
-
":where(.keksi-
|
|
250
|
-
":where(.keksi-
|
|
251
|
-
":where(.keksi-
|
|
252
|
-
":where(.keksi-
|
|
253
|
-
":where(.keksi-cat){
|
|
254
|
-
":where(.keksi-cat-head) :where(h2,h3,h4,h5){font-size:1rem;margin:0}",
|
|
294
|
+
":where(.keksi-text){margin-right:auto;padding:.5rem .25rem;font:inherit;border:0;background:none;color:inherit;text-decoration:underline;text-underline-offset:2px;cursor:pointer}",
|
|
295
|
+
"@media (max-width:30rem){:where(.keksi-btn){flex:1 1 auto}}",
|
|
296
|
+
":where(.keksi,keksi-youtube) :where(button,a,input,summary):focus-visible{outline:3px solid var(--keksi-focus,#2563eb);outline-offset:2px}",
|
|
297
|
+
":where(.keksi-close){font:inherit;font-size:1.375rem;line-height:1;margin:-.25rem -.5rem;padding:.25rem .5rem;border:0;background:none;color:inherit;cursor:pointer}",
|
|
298
|
+
":where(.keksi-cat){padding:1rem 0;border-top:1px solid var(--_line)}",
|
|
299
|
+
":where(.keksi-body>.keksi-cat:first-child){border-top:0}",
|
|
300
|
+
":where(.keksi-cat-head){display:flex;align-items:center;justify-content:space-between;gap:1rem}",
|
|
301
|
+
":where(.keksi-cat-head) :where(h2,h3,h4,h5){font-size:1rem;font-weight:600;margin:0}",
|
|
302
|
+
":where(.keksi-cat-head) :where(span){font-size:.8125rem;opacity:.7}",
|
|
255
303
|
":where(.keksi-cat) :where(p){margin:.25rem 0}",
|
|
256
|
-
":where(.keksi-cat input[role=switch]){appearance:none;position:relative;flex:none;width:2.
|
|
257
|
-
':where(.keksi-cat input[role=switch])::before{content:"";position:absolute;top:.
|
|
304
|
+
":where(.keksi-cat input[role=switch]){appearance:none;position:relative;flex:none;width:2.5rem;height:1.375rem;margin:0;border-radius:1rem;background:color-mix(in srgb,var(--_fg) 22%,var(--_bg));cursor:pointer}",
|
|
305
|
+
':where(.keksi-cat input[role=switch])::before{content:"";position:absolute;top:.1875rem;left:.1875rem;width:1rem;height:1rem;border-radius:50%;background:#fff;transition:transform .15s}',
|
|
258
306
|
":where(.keksi-cat input[role=switch]:checked){background:var(--_fg)}",
|
|
259
|
-
":where(.keksi-cat input[role=switch]:checked)::before{transform:translateX(1.
|
|
307
|
+
":where(.keksi-cat input[role=switch]:checked)::before{transform:translateX(1.125rem)}",
|
|
260
308
|
":where(.keksi-cat summary){margin-top:.5rem;font-weight:600;cursor:pointer}",
|
|
261
|
-
":where(.keksi-provider){margin:.75rem 0}",
|
|
262
|
-
":where(.keksi-provider)
|
|
263
|
-
":where(.keksi-
|
|
264
|
-
":where(.keksi-cat
|
|
265
|
-
":where(keksi-
|
|
309
|
+
":where(.keksi-provider){margin:.75rem 0;padding:.75rem .875rem;border-radius:calc(var(--_r) - 2px);background:var(--_soft)}",
|
|
310
|
+
":where(.keksi-policy .keksi-provider){padding:0;background:none}",
|
|
311
|
+
":where(.keksi-provider) :where(h3,h4,h5,h6){font-size:.9375rem;font-weight:600;margin:0}",
|
|
312
|
+
":where(.keksi-cat table){width:100%;border-collapse:collapse;font-size:.8125rem;margin-top:.5rem}",
|
|
313
|
+
":where(.keksi-cat) :where(th,td){padding:.3rem .75rem .3rem 0;border-bottom:1px solid var(--_line);text-align:left;vertical-align:top}",
|
|
314
|
+
":where(.keksi-cat) :where(th){font-weight:600}",
|
|
315
|
+
":where(keksi-youtube){position:relative;display:block;aspect-ratio:16/9;overflow:hidden;border-radius:12px;background:#0f0f0f;color:#fff;font-family:Roboto,Arial,sans-serif}",
|
|
266
316
|
":where(keksi-youtube iframe){display:block;width:100%;height:100%;border:0}",
|
|
267
|
-
":where(.keksi-yt){
|
|
268
|
-
":where(.keksi-yt) :
|
|
269
|
-
":where(.keksi-yt) :where(
|
|
270
|
-
":where(.keksi-yt
|
|
317
|
+
":where(keksi-youtube[label]:has(.keksi-yt))::before{content:attr(label);position:absolute;inset:0 0 auto;z-index:1;padding:1rem 1.25rem 2.5rem;font-size:1.125rem;font-weight:500;background:linear-gradient(#000b,#0000);pointer-events:none}",
|
|
318
|
+
":where(.keksi-yt){position:relative;display:grid;grid-template-rows:1fr auto auto;gap:.25rem;height:100%;padding:0 1.25rem 1rem;box-sizing:border-box;font-size:.875rem;line-height:1.45;background:linear-gradient(#0000 55%,#000c),center/cover var(--keksi-poster,none)}",
|
|
319
|
+
":where(.keksi-yt) :where(p){margin:0}",
|
|
320
|
+
":where(.keksi-yt) :where(p:first-of-type){grid-row:2}",
|
|
321
|
+
":where(.keksi-yt) :where(p:last-of-type){grid-row:3;color:#fffc}",
|
|
322
|
+
":where(.keksi-yt) :where(a){color:inherit;text-decoration:underline;text-underline-offset:2px}",
|
|
323
|
+
`:where(.keksi-yt button){position:absolute;inset:0;width:68px;height:48px;margin:auto;padding:0;border:0;font-size:0;color:transparent;background:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 68 48'%3E%3Cpath d='M66.52 7.74c-.78-2.93-2.49-5.41-5.42-6.19C55.79.13 34 0 34 0S12.21.13 6.9 1.55c-2.93.78-4.63 3.26-5.42 6.19C.06 13.05 0 24 0 24s.06 10.95 1.48 16.26c.78 2.93 2.49 5.41 5.42 6.19C12.21 47.87 34 48 34 48s21.79-.13 27.1-1.55c2.93-.78 4.64-3.26 5.42-6.19C67.94 34.95 68 24 68 24s-.06-10.95-1.48-16.26z' fill='%23f00'/%3E%3Cpath d='M45 24 27 14v20' fill='%23fff'/%3E%3C/svg%3E") center/contain no-repeat;cursor:pointer;transition:transform .15s}`,
|
|
324
|
+
":where(.keksi-yt button:hover){transform:scale(1.08)}"
|
|
271
325
|
].join("");
|
|
272
326
|
|
|
273
327
|
// src/render.ts
|
|
@@ -277,12 +331,12 @@ function renderKeksi(config, { lang = "fi", nonce } = {}) {
|
|
|
277
331
|
if (!cats.length) return "";
|
|
278
332
|
const t = textsFor(config, lang);
|
|
279
333
|
const active = activeProviders(config);
|
|
280
|
-
const policy =
|
|
334
|
+
const policy = policyOf(config, lang);
|
|
281
335
|
const n = nonce ? ` nonce="${e(nonce)}"` : "";
|
|
282
336
|
const btn = (action, label, type = "button") => `<button type="${type}" class="keksi-btn" data-keksi="${action}">${e(label)}</button>`;
|
|
283
337
|
const uses = cats.map((c) => `<li><strong>${e(t[c])}:</strong> ${e(active.filter((p) => p.category === c).map((p) => p.name).join(", "))}</li>`).join("");
|
|
284
338
|
const json = JSON.stringify(runtimeData(config, lang)).replace(/</g, "\\u003c");
|
|
285
|
-
return `<style${n}>${css}</style><section id="keksi-banner" class="keksi" aria-labelledby="keksi-title" hidden><h2 id="keksi-title">${e(t.title)}</h2><p>${e(t.intro)}</p><ul>${uses}</ul><p>${e(t.
|
|
339
|
+
return `<style${n}>${css}</style><section id="keksi-banner" class="keksi" aria-labelledby="keksi-title" hidden><div class="keksi-main"><div class="keksi-head"><h2 id="keksi-title">${e(t.title)}</h2></div><div class="keksi-body"><p>${e(t.intro)}</p><ul>${uses}</ul><p>${e(t.change)}${policy ? ` <a href="${e(policy)}">${e(t.more)}</a>` : ""}</p></div><div class="keksi-actions"><button type="button" class="keksi-text" data-keksi-open>${e(t.settings)}</button>${btn("reject", t.reject)}${btn("accept", t.accept)}</div></div><form id="keksi-settings" class="keksi-settings" aria-labelledby="keksi-settings-title" hidden><div class="keksi-head"><h2 id="keksi-settings-title">${e(t.settingsTitle)}</h2><button type="button" class="keksi-close" data-keksi="close" aria-label="${e(t.close)}">\xD7</button></div><div class="keksi-body">${sections(config, lang, 3, true)}</div><div class="keksi-actions">${btn("save", t.save)}${btn("reject", t.reject)}${btn("accept", t.accept)}</div></form></section><script type="application/json" id="keksi-data">${json}</script><script${n}>${'"use strict";(()=>{var F=(n,s)=>new RegExp(`^${n.split("*").map(i=>i.replace(/[.+?^${}()|[\\]\\\\]/g,"\\\\$&")).join(".*")}$`).test(s);var _=["analytics","marketing"],v={analytics:!1,marketing:!1};function f(...n){return arguments}function H(n,s=window){if(typeof s.keksi?.sync=="function")return s.keksi.sync(),s.keksi;let i=s.document,g=e=>i.getElementById(e),c=I(),A=!1,w=!1,L=!1,E=new Set,x=f("consent","default",{ad_storage:"denied",ad_user_data:"denied",ad_personalization:"denied",analytics_storage:"denied"});if(n.ga.length||n.ads.length){let e=s.dataLayer??(s.dataLayer=[]);s.gtag=function(){e.push(arguments)},e.push(x)}if(n.meta.length){let e=function(){e.callMethod?e.callMethod.apply(e,[...arguments]):e.queue.push(arguments)};Object.assign(e,{push:e,loaded:!0,version:"2.0",queue:[]}),s.fbq=s._fbq=e}if(n.ph){let e=Object.assign([],{_i:[],people:[]});for(let o of"capture identify alias reset register register_once unregister group setPersonProperties getFeatureFlag isFeatureEnabled onFeatureFlags reloadFeatureFlags get_distinct_id startSessionRecording stopSessionRecording captureException on".split(" "))e[o]=(...t)=>e.push([o,...t]);s.posthog=e}function I(){let e=i.cookie.split("; ").find(l=>l.startsWith(`${n.name}=`))?.slice(n.name.length+1),[o,,t,a]=e?.split(".")??[];if(o!==n.v||!t||!a)return null;let r={analytics:a.includes("a"),marketing:a.includes("m")};return{id:t,c:r,yt:r.marketing||a.includes("y"),done:!a.includes("u")}}function T(e,o=e.marketing,t=!0){let a=c?.id??Array.from(s.crypto.getRandomValues(new Uint8Array(8)),l=>l.toString(16).padStart(2,"0")).join(""),r=(t?"":"u")+(e.analytics?"a":"")+(e.marketing?"m":"")+(o&&!e.marketing?"y":"")||"-";return i.cookie=`${n.name}=${n.v}.${Date.now().toString(36)}.${a}.${r}; Path=/; Max-Age=${n.days*86400}; SameSite=Lax`+(n.domain?`; Domain=${n.domain}`:"")+(s.location.protocol==="https:"?"; Secure":""),{id:a,c:e,yt:o,done:t}}function $(e){let o=i.createElement("script");return o.async=!0,o.src=e,i.head.append(o),o}function C(e){let o=[...e.analytics?n.ga:[],...e.marketing?n.ads:[]].filter(t=>!E.has(t));if(o.length){let t=e.marketing?"granted":"denied",a=[f("consent","update",{analytics_storage:e.analytics?"granted":"denied",ad_storage:t,ad_user_data:t,ad_personalization:t})];E.size||a.push(f("js",new Date));for(let r of o)a.push(f("config",r)),E.add(r);if(A)for(let r of a)s.dataLayer.push(r);else{let r=s.dataLayer;r.splice(r.indexOf(x)+1,0,...a),$(`https://www.googletagmanager.com/gtag/js?id=${encodeURIComponent(o[0])}`),A=!0}}if(e.marketing&&n.meta.length&&!w&&(w=!0,s.fbq.queue.unshift(...n.meta.map(t=>f("init",t)),f("track","PageView")),$("https://connect.facebook.net/en_US/fbevents.js")),e.analytics&&n.ph&&!L&&(L=!0,s.posthog._i.push([n.ph.key,{api_host:n.ph.host,defaults:"2026-08-30"},"posthog"]),$(`${n.ph.host.replace(".i.posthog.com","-assets.i.posthog.com")}/static/array.js`).crossOrigin="anonymous"),c?.yt)for(let t of i.querySelectorAll("keksi-youtube"))t.show(!0)}function O(){c?.yt||(c=T(c?.c??v,!0,c?.done??!1)),C(c.c)}function R(e){let o=e.flatMap(l=>n.del[l]),t=l=>o.some(u=>F(u,l)),a=s.location.hostname.split("."),r=["",...a.map((l,u)=>`; Domain=${a.slice(u).join(".")}`)];for(let l of i.cookie.split("; ")){let u=l.split("=")[0];if(t(u))for(let b of r)i.cookie=`${u}=; Max-Age=0; Path=/${b}`}try{for(let l of[s.localStorage,s.sessionStorage])for(let u of Object.keys(l))t(u)&&l.removeItem(u)}catch{}}function p(e){let o=c;c=T(e),h(),i.dispatchEvent(new s.CustomEvent("keksi:change",{detail:{...e}})),R(_.filter(t=>!e[t])),_.some(t=>o?.c[t]&&!e[t])||o?.yt&&!c.yt?s.location.reload():C(e)}function y(e=!1,o=n.banner&&!c?.done||e){let t=g("keksi-banner"),a=g("keksi-settings");!t||!a||(t.hidden=!o,a.hidden=!e,t.firstElementChild.hidden=e)}function h(){y()}function M(){let e=g("keksi-settings");for(let o of _){let t=e?.querySelector(`[name=${o}]`);t&&(t.checked=!!c?.c[o])}y(!0),e?.querySelector(".keksi-close")?.focus()}i.addEventListener("click",e=>{let o=e.target.closest?.("[data-keksi],[data-keksi-open]");if(!o)return;if(o.hasAttribute("data-keksi-open"))return e.preventDefault(),M();let t=o.getAttribute("data-keksi");if(t==="close")y();else if(t==="accept")p({analytics:!0,marketing:!0});else if(t==="reject")p(v);else if(t==="save"){let a=r=>!!g("keksi-settings")?.querySelector(`[name=${r}]`)?.checked;p({analytics:a("analytics"),marketing:a("marketing")})}}),i.addEventListener("keydown",e=>{e.key==="Escape"&&g("keksi-settings")?.hidden===!1&&y()}),i.addEventListener("astro:after-swap",h);class D extends s.HTMLElement{connectedCallback(){this.show(!!c?.yt)}show(o,t=!1){let a=encodeURIComponent(this.getAttribute("video")??""),r=this.getAttribute("label");if(o){if(this.querySelector("iframe"))return;let d=i.createElement("iframe");d.src=`https://www.youtube-nocookie.com/embed/${a}${t?"?autoplay=1":""}`,d.title=r??"YouTube",d.allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share",d.allowFullscreen=!0,d.referrerPolicy="strict-origin-when-cross-origin",this.replaceChildren(d),t&&d.focus();return}let l=i.createElement("div");l.className="keksi-yt";let u=this.getAttribute("poster");u&&l.style.setProperty("--keksi-poster",`url(${JSON.stringify(u)})`);let b=i.createElement("p");b.textContent=n.t.ytText;let m=i.createElement("button");m.type="button",m.textContent=n.t.ytButton,r&&m.setAttribute("aria-label",`${n.t.ytButton}: ${r}`),m.onclick=()=>{this.show(!0,!0),O()};let S=i.createElement("p"),q=(d,N,P=!1)=>{let k=i.createElement("a");k.href=d,k.textContent=N,P&&(k.target="_blank",k.rel="noopener"),S.append(S.childNodes.length?" \\xB7 ":"",k)};q(`https://www.youtube.com/watch?v=${a}`,n.t.ytOpen,!0),n.policy&&q(n.policy,n.t.more),l.append(b,m,S),this.replaceChildren(l)}}n.yt&&!s.customElements.get("keksi-youtube")&&s.customElements.define("keksi-youtube",D);let j={get consent(){return c?.done?{...c.c}:null},open:M,save:e=>p({...c?.c??v,...e}),acceptAll:()=>p({analytics:!0,marketing:!0}),rejectAll:()=>p(v),sync:h};return s.keksi=j,h(),c&&C(c.c),j}H(JSON.parse(document.getElementById("keksi-data").textContent));})();'}</script>`;
|
|
286
340
|
}
|
|
287
341
|
function cookieTable(config, { lang = "fi", headingLevel = 2 } = {}) {
|
|
288
342
|
return `<div class="keksi-policy">${sections(config, lang, headingLevel, false)}</div>`;
|
|
@@ -303,7 +357,7 @@ function sections(config, lang, h, dialog) {
|
|
|
303
357
|
const items = c === "necessary" ? [site] : active.filter((p) => p.category === c).map((p) => ({ ...p, purpose: p.purpose[lang] }));
|
|
304
358
|
const head = c === "necessary" ? `<h${h}>${e(t.necessary)}</h${h}><span>${e(t.alwaysOn)}</span>` : dialog ? `<h${h}><label for="keksi-c-${c}">${e(t[c])}</label></h${h}><input type="checkbox" role="switch" id="keksi-c-${c}" name="${c}" aria-describedby="keksi-i-${c}">` : `<h${h}>${e(t[c])}</h${h}>`;
|
|
305
359
|
const list2 = items.map(
|
|
306
|
-
(p) => `<div class="keksi-provider"><h${h + 1}>${e(p.name)}</h${h + 1}>${p.purpose ? `<p>${e(p.purpose)}</p>` : ""}` + (p.company && p.privacyPolicy ? `<p>${e(p.company)} \xB7 <a href="${e(p.privacyPolicy)}" target="_blank" rel="noopener">${e(t.privacyPolicy)}</a></p>` + (p.transfer ? `<p>${e(t.
|
|
360
|
+
(p) => `<div class="keksi-provider"><h${h + 1}>${e(p.name)}</h${h + 1}>${p.purpose ? `<p>${e(p.purpose)}</p>` : ""}` + (p.company && p.privacyPolicy ? `<p>${e(p.company)} \xB7 <a href="${e(p.privacyPolicy)}" target="_blank" rel="noopener">${e(t.privacyPolicy)}</a></p>` + (p.transfer ? `<p>${e(t[p.transfer])}</p>` : "") : "") + table(p.cookies, t) + `</div>`
|
|
307
361
|
).join("");
|
|
308
362
|
const body = dialog ? `<details><summary>${e(t.providers)} (${items.length})</summary>${list2}</details>` : list2;
|
|
309
363
|
return `<section class="keksi-cat"><div class="keksi-cat-head">${head}</div><p id="keksi-i-${c}">${e(t[`${c}Info`])}</p>${body}</section>`;
|
package/dist/playwright.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
var fi = {
|
|
3
3
|
title: "Ev\xE4steet",
|
|
4
4
|
intro: "K\xE4yt\xE4mme v\xE4ltt\xE4m\xE4tt\xF6mien ev\xE4steiden lis\xE4ksi seuraavia palveluita vain, jos annat siihen luvan:",
|
|
5
|
-
|
|
5
|
+
change: "Voit muuttaa valintaasi milloin tahansa.",
|
|
6
6
|
more: "Lue lis\xE4\xE4",
|
|
7
7
|
accept: "Hyv\xE4ksy kaikki",
|
|
8
8
|
reject: "Hylk\xE4\xE4 kaikki",
|
|
@@ -22,6 +22,7 @@ var fi = {
|
|
|
22
22
|
consentPurpose: "Tallentaa ev\xE4stevalintasi.",
|
|
23
23
|
privacyPolicy: "Tietosuojaseloste",
|
|
24
24
|
dpf: "Tietoja voidaan siirt\xE4\xE4 Yhdysvaltoihin EU:n ja Yhdysvaltojen v\xE4lisen tietosuojakehyksen (DPF) perusteella.",
|
|
25
|
+
us: "Tietoja siirret\xE4\xE4n Yhdysvaltoihin.",
|
|
25
26
|
name: "Nimi",
|
|
26
27
|
duration: "Kesto",
|
|
27
28
|
purpose: "Tarkoitus",
|
|
@@ -32,14 +33,14 @@ var fi = {
|
|
|
32
33
|
mo: "kuukausi|kuukautta",
|
|
33
34
|
d: "p\xE4iv\xE4|p\xE4iv\xE4\xE4",
|
|
34
35
|
min: "minuutti|minuuttia",
|
|
35
|
-
ytText: "
|
|
36
|
-
ytButton: "
|
|
37
|
-
ytOpen: "
|
|
36
|
+
ytText: "Toistamalla videon hyv\xE4ksyt YouTuben ev\xE4steet.",
|
|
37
|
+
ytButton: "Toista video",
|
|
38
|
+
ytOpen: "Katso YouTubessa"
|
|
38
39
|
};
|
|
39
40
|
var sv = {
|
|
40
41
|
title: "Kakor",
|
|
41
42
|
intro: "Ut\xF6ver n\xF6dv\xE4ndiga kakor anv\xE4nder vi f\xF6ljande tj\xE4nster endast om du ger ditt samtycke:",
|
|
42
|
-
|
|
43
|
+
change: "Du kan \xE4ndra ditt val n\xE4r som helst.",
|
|
43
44
|
more: "L\xE4s mer",
|
|
44
45
|
accept: "Godk\xE4nn alla",
|
|
45
46
|
reject: "Avvisa alla",
|
|
@@ -59,6 +60,7 @@ var sv = {
|
|
|
59
60
|
consentPurpose: "Sparar ditt val av kakor.",
|
|
60
61
|
privacyPolicy: "Dataskyddsbeskrivning",
|
|
61
62
|
dpf: "Uppgifter kan \xF6verf\xF6ras till USA med st\xF6d av ramverket f\xF6r dataskydd mellan EU och USA (DPF).",
|
|
63
|
+
us: "Uppgifter \xF6verf\xF6rs till USA.",
|
|
62
64
|
name: "Namn",
|
|
63
65
|
duration: "Varaktighet",
|
|
64
66
|
purpose: "Syfte",
|
|
@@ -69,14 +71,14 @@ var sv = {
|
|
|
69
71
|
mo: "m\xE5nad|m\xE5nader",
|
|
70
72
|
d: "dag|dagar",
|
|
71
73
|
min: "minut|minuter",
|
|
72
|
-
ytText: "
|
|
73
|
-
ytButton: "
|
|
74
|
-
ytOpen: "
|
|
74
|
+
ytText: "Genom att spela upp videon godk\xE4nner du kakor fr\xE5n YouTube.",
|
|
75
|
+
ytButton: "Spela upp video",
|
|
76
|
+
ytOpen: "Titta p\xE5 YouTube"
|
|
75
77
|
};
|
|
76
78
|
var en = {
|
|
77
79
|
title: "Cookies",
|
|
78
80
|
intro: "Besides necessary cookies, we use the following services only if you allow it:",
|
|
79
|
-
|
|
81
|
+
change: "You can change your choice at any time.",
|
|
80
82
|
more: "Read more",
|
|
81
83
|
accept: "Accept all",
|
|
82
84
|
reject: "Reject all",
|
|
@@ -96,6 +98,7 @@ var en = {
|
|
|
96
98
|
consentPurpose: "Stores your cookie choice.",
|
|
97
99
|
privacyPolicy: "Privacy policy",
|
|
98
100
|
dpf: "Data may be transferred to the United States under the EU\u2013US Data Privacy Framework (DPF).",
|
|
101
|
+
us: "Data is transferred to the United States.",
|
|
99
102
|
name: "Name",
|
|
100
103
|
duration: "Duration",
|
|
101
104
|
purpose: "Purpose",
|
|
@@ -106,9 +109,9 @@ var en = {
|
|
|
106
109
|
mo: "month|months",
|
|
107
110
|
d: "day|days",
|
|
108
111
|
min: "minute|minutes",
|
|
109
|
-
ytText: "
|
|
110
|
-
ytButton: "
|
|
111
|
-
ytOpen: "
|
|
112
|
+
ytText: "By playing the video, you accept cookies from YouTube.",
|
|
113
|
+
ytButton: "Play video",
|
|
114
|
+
ytOpen: "Watch on YouTube"
|
|
112
115
|
};
|
|
113
116
|
var texts = { fi, sv, en };
|
|
114
117
|
|
|
@@ -172,6 +175,29 @@ var providers = [
|
|
|
172
175
|
],
|
|
173
176
|
hosts: ["facebook.net", "facebook.com"]
|
|
174
177
|
},
|
|
178
|
+
{
|
|
179
|
+
id: "posthog",
|
|
180
|
+
name: "PostHog",
|
|
181
|
+
company: "PostHog Inc.",
|
|
182
|
+
privacyPolicy: "https://posthog.com/privacy",
|
|
183
|
+
category: "analytics",
|
|
184
|
+
purpose: {
|
|
185
|
+
fi: "Tuoteanalytiikka: sivujen katselut, klikkaukset ja k\xE4ytt\xF6tavat. Voi tallentaa istuntoja, jos istuntotallenteet ovat k\xE4yt\xF6ss\xE4.",
|
|
186
|
+
sv: "Produktanalys: sidvisningar, klick och anv\xE4ndningsm\xF6nster. Kan spela in sessioner om sessionsinspelning \xE4r aktiverad.",
|
|
187
|
+
en: "Product analytics: page views, clicks and usage patterns. Can record sessions if session replay is enabled."
|
|
188
|
+
},
|
|
189
|
+
cookies: [
|
|
190
|
+
{ name: "ph_*_posthog", duration: "1y" },
|
|
191
|
+
{ name: "ph_*_posthog_cpm", duration: "1y" },
|
|
192
|
+
{ name: "ph_*_posthog", duration: "persistent", storage: "localStorage" },
|
|
193
|
+
{ name: "ph_*_posthog__flags", duration: "persistent", storage: "localStorage" },
|
|
194
|
+
{ name: "ph_*_posthog", duration: "session", storage: "sessionStorage" },
|
|
195
|
+
{ name: "ph_*_primary_window_exists", duration: "session", storage: "sessionStorage" },
|
|
196
|
+
{ name: "ph_*_session_registered_properties", duration: "session", storage: "sessionStorage" },
|
|
197
|
+
{ name: "ph_*_window_id", duration: "session", storage: "sessionStorage" }
|
|
198
|
+
],
|
|
199
|
+
hosts: ["posthog.com"]
|
|
200
|
+
},
|
|
175
201
|
{
|
|
176
202
|
id: "youtube",
|
|
177
203
|
name: "YouTube",
|
|
@@ -197,8 +223,17 @@ var providers = [
|
|
|
197
223
|
|
|
198
224
|
// src/config.ts
|
|
199
225
|
var list = (v) => v === void 0 ? [] : Array.isArray(v) ? v : [v];
|
|
226
|
+
var posthogHost = (config) => (config.posthog?.host ?? "https://eu.i.posthog.com").replace(/\/$/, "");
|
|
200
227
|
function activeProviders(config) {
|
|
201
|
-
return providers.filter(
|
|
228
|
+
return providers.filter(
|
|
229
|
+
(p) => p.id === "youtube" ? config.youtube : p.id === "posthog" ? config.posthog?.key : list(config[p.id]).length
|
|
230
|
+
).map((p) => p.id === "posthog" && /\/\/us\./.test(posthogHost(config)) ? { ...p, transfer: "us" } : p);
|
|
231
|
+
}
|
|
232
|
+
function needsBanner(config) {
|
|
233
|
+
return activeProviders(config).some((p) => p.id !== "youtube");
|
|
234
|
+
}
|
|
235
|
+
function policyOf(config, locale) {
|
|
236
|
+
return typeof config.policyUrl === "string" ? config.policyUrl : config.policyUrl?.[locale];
|
|
202
237
|
}
|
|
203
238
|
function textsFor(config, locale) {
|
|
204
239
|
return { ...texts[locale], ...config.texts?.[locale] };
|
|
@@ -220,9 +255,12 @@ function runtimeData(config, locale) {
|
|
|
220
255
|
ga: list(config.ga),
|
|
221
256
|
ads: list(config.googleAds),
|
|
222
257
|
meta: list(config.meta),
|
|
258
|
+
ph: config.posthog?.key ? { key: config.posthog.key, host: posthogHost(config) } : void 0,
|
|
223
259
|
yt: !!config.youtube,
|
|
260
|
+
banner: needsBanner(config),
|
|
261
|
+
policy: policyOf(config, locale),
|
|
224
262
|
del: { analytics: del("analytics"), marketing: del("marketing") },
|
|
225
|
-
t: { ytText: t.ytText, ytButton: t.ytButton, ytOpen: t.ytOpen }
|
|
263
|
+
t: { ytText: t.ytText, ytButton: t.ytButton, ytOpen: t.ytOpen, more: t.more }
|
|
226
264
|
};
|
|
227
265
|
}
|
|
228
266
|
|
package/dist/render.d.ts
CHANGED
|
@@ -5,7 +5,7 @@ export interface RenderOptions {
|
|
|
5
5
|
/** For a CSP with nonces; applied to the inline `<style>` and `<script>`. */
|
|
6
6
|
nonce?: string;
|
|
7
7
|
}
|
|
8
|
-
/** Banner
|
|
8
|
+
/** Banner with its settings view, and the runtime. Place it right after `<body>`. Empty when no provider needs consent. */
|
|
9
9
|
export declare function renderKeksi(config: KeksiConfig, { lang, nonce }?: RenderOptions): string;
|
|
10
10
|
export interface CookieTableOptions {
|
|
11
11
|
lang?: Locale;
|
package/dist/runtime.d.ts
CHANGED
|
@@ -21,6 +21,9 @@ declare global {
|
|
|
21
21
|
gtag?: (...a: unknown[]) => void;
|
|
22
22
|
fbq?: Queue;
|
|
23
23
|
_fbq?: Queue;
|
|
24
|
+
posthog?: unknown[] & Record<string, unknown> & {
|
|
25
|
+
_i: unknown[];
|
|
26
|
+
};
|
|
24
27
|
}
|
|
25
28
|
}
|
|
26
29
|
export declare function boot(d: RuntimeData, w?: Window & typeof globalThis): KeksiApi;
|
package/dist/scan.js
CHANGED
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
// src/glob.ts
|
|
2
|
+
var glob = (pattern, name) => new RegExp(`^${pattern.split("*").map((s) => s.replace(/[.+?^${}()|[\]\\]/g, "\\$&")).join(".*")}$`).test(name);
|
|
3
|
+
|
|
1
4
|
// src/providers.ts
|
|
2
5
|
var GOOGLE = { company: "Google Ireland Limited", privacyPolicy: "https://policies.google.com/privacy", transfer: "dpf" };
|
|
3
6
|
var META = { company: "Meta Platforms Ireland Limited", privacyPolicy: "https://www.facebook.com/privacy/policy/", transfer: "dpf" };
|
|
@@ -58,6 +61,29 @@ var providers = [
|
|
|
58
61
|
],
|
|
59
62
|
hosts: ["facebook.net", "facebook.com"]
|
|
60
63
|
},
|
|
64
|
+
{
|
|
65
|
+
id: "posthog",
|
|
66
|
+
name: "PostHog",
|
|
67
|
+
company: "PostHog Inc.",
|
|
68
|
+
privacyPolicy: "https://posthog.com/privacy",
|
|
69
|
+
category: "analytics",
|
|
70
|
+
purpose: {
|
|
71
|
+
fi: "Tuoteanalytiikka: sivujen katselut, klikkaukset ja k\xE4ytt\xF6tavat. Voi tallentaa istuntoja, jos istuntotallenteet ovat k\xE4yt\xF6ss\xE4.",
|
|
72
|
+
sv: "Produktanalys: sidvisningar, klick och anv\xE4ndningsm\xF6nster. Kan spela in sessioner om sessionsinspelning \xE4r aktiverad.",
|
|
73
|
+
en: "Product analytics: page views, clicks and usage patterns. Can record sessions if session replay is enabled."
|
|
74
|
+
},
|
|
75
|
+
cookies: [
|
|
76
|
+
{ name: "ph_*_posthog", duration: "1y" },
|
|
77
|
+
{ name: "ph_*_posthog_cpm", duration: "1y" },
|
|
78
|
+
{ name: "ph_*_posthog", duration: "persistent", storage: "localStorage" },
|
|
79
|
+
{ name: "ph_*_posthog__flags", duration: "persistent", storage: "localStorage" },
|
|
80
|
+
{ name: "ph_*_posthog", duration: "session", storage: "sessionStorage" },
|
|
81
|
+
{ name: "ph_*_primary_window_exists", duration: "session", storage: "sessionStorage" },
|
|
82
|
+
{ name: "ph_*_session_registered_properties", duration: "session", storage: "sessionStorage" },
|
|
83
|
+
{ name: "ph_*_window_id", duration: "session", storage: "sessionStorage" }
|
|
84
|
+
],
|
|
85
|
+
hosts: ["posthog.com"]
|
|
86
|
+
},
|
|
61
87
|
{
|
|
62
88
|
id: "youtube",
|
|
63
89
|
name: "YouTube",
|
|
@@ -113,7 +139,7 @@ var purposes = {
|
|
|
113
139
|
};
|
|
114
140
|
var trackerNames = providers.flatMap((p) => p.cookies.flatMap((c) => c.name.split(", ")));
|
|
115
141
|
var trackerHosts = providers.flatMap((p) => p.hosts);
|
|
116
|
-
var matches = (patterns, name) => patterns.some((p) =>
|
|
142
|
+
var matches = (patterns, name) => patterns.some((p) => glob(p, name));
|
|
117
143
|
function purposeOf(name) {
|
|
118
144
|
const k = Object.keys(purposes).find((p) => matches([p], name));
|
|
119
145
|
return k ? purposes[k] : void 0;
|
package/dist/sveltekit.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
var fi = {
|
|
3
3
|
title: "Ev\xE4steet",
|
|
4
4
|
intro: "K\xE4yt\xE4mme v\xE4ltt\xE4m\xE4tt\xF6mien ev\xE4steiden lis\xE4ksi seuraavia palveluita vain, jos annat siihen luvan:",
|
|
5
|
-
|
|
5
|
+
change: "Voit muuttaa valintaasi milloin tahansa.",
|
|
6
6
|
more: "Lue lis\xE4\xE4",
|
|
7
7
|
accept: "Hyv\xE4ksy kaikki",
|
|
8
8
|
reject: "Hylk\xE4\xE4 kaikki",
|
|
@@ -22,6 +22,7 @@ var fi = {
|
|
|
22
22
|
consentPurpose: "Tallentaa ev\xE4stevalintasi.",
|
|
23
23
|
privacyPolicy: "Tietosuojaseloste",
|
|
24
24
|
dpf: "Tietoja voidaan siirt\xE4\xE4 Yhdysvaltoihin EU:n ja Yhdysvaltojen v\xE4lisen tietosuojakehyksen (DPF) perusteella.",
|
|
25
|
+
us: "Tietoja siirret\xE4\xE4n Yhdysvaltoihin.",
|
|
25
26
|
name: "Nimi",
|
|
26
27
|
duration: "Kesto",
|
|
27
28
|
purpose: "Tarkoitus",
|
|
@@ -32,14 +33,14 @@ var fi = {
|
|
|
32
33
|
mo: "kuukausi|kuukautta",
|
|
33
34
|
d: "p\xE4iv\xE4|p\xE4iv\xE4\xE4",
|
|
34
35
|
min: "minuutti|minuuttia",
|
|
35
|
-
ytText: "
|
|
36
|
-
ytButton: "
|
|
37
|
-
ytOpen: "
|
|
36
|
+
ytText: "Toistamalla videon hyv\xE4ksyt YouTuben ev\xE4steet.",
|
|
37
|
+
ytButton: "Toista video",
|
|
38
|
+
ytOpen: "Katso YouTubessa"
|
|
38
39
|
};
|
|
39
40
|
var sv = {
|
|
40
41
|
title: "Kakor",
|
|
41
42
|
intro: "Ut\xF6ver n\xF6dv\xE4ndiga kakor anv\xE4nder vi f\xF6ljande tj\xE4nster endast om du ger ditt samtycke:",
|
|
42
|
-
|
|
43
|
+
change: "Du kan \xE4ndra ditt val n\xE4r som helst.",
|
|
43
44
|
more: "L\xE4s mer",
|
|
44
45
|
accept: "Godk\xE4nn alla",
|
|
45
46
|
reject: "Avvisa alla",
|
|
@@ -59,6 +60,7 @@ var sv = {
|
|
|
59
60
|
consentPurpose: "Sparar ditt val av kakor.",
|
|
60
61
|
privacyPolicy: "Dataskyddsbeskrivning",
|
|
61
62
|
dpf: "Uppgifter kan \xF6verf\xF6ras till USA med st\xF6d av ramverket f\xF6r dataskydd mellan EU och USA (DPF).",
|
|
63
|
+
us: "Uppgifter \xF6verf\xF6rs till USA.",
|
|
62
64
|
name: "Namn",
|
|
63
65
|
duration: "Varaktighet",
|
|
64
66
|
purpose: "Syfte",
|
|
@@ -69,14 +71,14 @@ var sv = {
|
|
|
69
71
|
mo: "m\xE5nad|m\xE5nader",
|
|
70
72
|
d: "dag|dagar",
|
|
71
73
|
min: "minut|minuter",
|
|
72
|
-
ytText: "
|
|
73
|
-
ytButton: "
|
|
74
|
-
ytOpen: "
|
|
74
|
+
ytText: "Genom att spela upp videon godk\xE4nner du kakor fr\xE5n YouTube.",
|
|
75
|
+
ytButton: "Spela upp video",
|
|
76
|
+
ytOpen: "Titta p\xE5 YouTube"
|
|
75
77
|
};
|
|
76
78
|
var en = {
|
|
77
79
|
title: "Cookies",
|
|
78
80
|
intro: "Besides necessary cookies, we use the following services only if you allow it:",
|
|
79
|
-
|
|
81
|
+
change: "You can change your choice at any time.",
|
|
80
82
|
more: "Read more",
|
|
81
83
|
accept: "Accept all",
|
|
82
84
|
reject: "Reject all",
|
|
@@ -96,6 +98,7 @@ var en = {
|
|
|
96
98
|
consentPurpose: "Stores your cookie choice.",
|
|
97
99
|
privacyPolicy: "Privacy policy",
|
|
98
100
|
dpf: "Data may be transferred to the United States under the EU\u2013US Data Privacy Framework (DPF).",
|
|
101
|
+
us: "Data is transferred to the United States.",
|
|
99
102
|
name: "Name",
|
|
100
103
|
duration: "Duration",
|
|
101
104
|
purpose: "Purpose",
|
|
@@ -106,9 +109,9 @@ var en = {
|
|
|
106
109
|
mo: "month|months",
|
|
107
110
|
d: "day|days",
|
|
108
111
|
min: "minute|minutes",
|
|
109
|
-
ytText: "
|
|
110
|
-
ytButton: "
|
|
111
|
-
ytOpen: "
|
|
112
|
+
ytText: "By playing the video, you accept cookies from YouTube.",
|
|
113
|
+
ytButton: "Play video",
|
|
114
|
+
ytOpen: "Watch on YouTube"
|
|
112
115
|
};
|
|
113
116
|
var texts = { fi, sv, en };
|
|
114
117
|
|
|
@@ -172,6 +175,29 @@ var providers = [
|
|
|
172
175
|
],
|
|
173
176
|
hosts: ["facebook.net", "facebook.com"]
|
|
174
177
|
},
|
|
178
|
+
{
|
|
179
|
+
id: "posthog",
|
|
180
|
+
name: "PostHog",
|
|
181
|
+
company: "PostHog Inc.",
|
|
182
|
+
privacyPolicy: "https://posthog.com/privacy",
|
|
183
|
+
category: "analytics",
|
|
184
|
+
purpose: {
|
|
185
|
+
fi: "Tuoteanalytiikka: sivujen katselut, klikkaukset ja k\xE4ytt\xF6tavat. Voi tallentaa istuntoja, jos istuntotallenteet ovat k\xE4yt\xF6ss\xE4.",
|
|
186
|
+
sv: "Produktanalys: sidvisningar, klick och anv\xE4ndningsm\xF6nster. Kan spela in sessioner om sessionsinspelning \xE4r aktiverad.",
|
|
187
|
+
en: "Product analytics: page views, clicks and usage patterns. Can record sessions if session replay is enabled."
|
|
188
|
+
},
|
|
189
|
+
cookies: [
|
|
190
|
+
{ name: "ph_*_posthog", duration: "1y" },
|
|
191
|
+
{ name: "ph_*_posthog_cpm", duration: "1y" },
|
|
192
|
+
{ name: "ph_*_posthog", duration: "persistent", storage: "localStorage" },
|
|
193
|
+
{ name: "ph_*_posthog__flags", duration: "persistent", storage: "localStorage" },
|
|
194
|
+
{ name: "ph_*_posthog", duration: "session", storage: "sessionStorage" },
|
|
195
|
+
{ name: "ph_*_primary_window_exists", duration: "session", storage: "sessionStorage" },
|
|
196
|
+
{ name: "ph_*_session_registered_properties", duration: "session", storage: "sessionStorage" },
|
|
197
|
+
{ name: "ph_*_window_id", duration: "session", storage: "sessionStorage" }
|
|
198
|
+
],
|
|
199
|
+
hosts: ["posthog.com"]
|
|
200
|
+
},
|
|
175
201
|
{
|
|
176
202
|
id: "youtube",
|
|
177
203
|
name: "YouTube",
|
|
@@ -197,12 +223,21 @@ var providers = [
|
|
|
197
223
|
|
|
198
224
|
// src/config.ts
|
|
199
225
|
var list = (v) => v === void 0 ? [] : Array.isArray(v) ? v : [v];
|
|
226
|
+
var posthogHost = (config) => (config.posthog?.host ?? "https://eu.i.posthog.com").replace(/\/$/, "");
|
|
200
227
|
function activeProviders(config) {
|
|
201
|
-
return providers.filter(
|
|
228
|
+
return providers.filter(
|
|
229
|
+
(p) => p.id === "youtube" ? config.youtube : p.id === "posthog" ? config.posthog?.key : list(config[p.id]).length
|
|
230
|
+
).map((p) => p.id === "posthog" && /\/\/us\./.test(posthogHost(config)) ? { ...p, transfer: "us" } : p);
|
|
202
231
|
}
|
|
203
232
|
function categoriesOf(config) {
|
|
204
233
|
return ["analytics", "marketing"].filter((c) => activeProviders(config).some((p) => p.category === c));
|
|
205
234
|
}
|
|
235
|
+
function needsBanner(config) {
|
|
236
|
+
return activeProviders(config).some((p) => p.id !== "youtube");
|
|
237
|
+
}
|
|
238
|
+
function policyOf(config, locale) {
|
|
239
|
+
return typeof config.policyUrl === "string" ? config.policyUrl : config.policyUrl?.[locale];
|
|
240
|
+
}
|
|
206
241
|
function textsFor(config, locale) {
|
|
207
242
|
return { ...texts[locale], ...config.texts?.[locale] };
|
|
208
243
|
}
|
|
@@ -223,48 +258,67 @@ function runtimeData(config, locale) {
|
|
|
223
258
|
ga: list(config.ga),
|
|
224
259
|
ads: list(config.googleAds),
|
|
225
260
|
meta: list(config.meta),
|
|
261
|
+
ph: config.posthog?.key ? { key: config.posthog.key, host: posthogHost(config) } : void 0,
|
|
226
262
|
yt: !!config.youtube,
|
|
263
|
+
banner: needsBanner(config),
|
|
264
|
+
policy: policyOf(config, locale),
|
|
227
265
|
del: { analytics: del("analytics"), marketing: del("marketing") },
|
|
228
|
-
t: { ytText: t.ytText, ytButton: t.ytButton, ytOpen: t.ytOpen }
|
|
266
|
+
t: { ytText: t.ytText, ytButton: t.ytButton, ytOpen: t.ytOpen, more: t.more }
|
|
229
267
|
};
|
|
230
268
|
}
|
|
231
269
|
|
|
232
270
|
// src/styles.ts
|
|
233
271
|
var css = [
|
|
234
|
-
":where(.keksi,.keksi-
|
|
235
|
-
":where(.keksi
|
|
236
|
-
":where(.keksi){position:fixed;z-index:2147483000;inset:auto 1rem 1rem;max-width:34rem;max-height:calc(100vh - 2rem);
|
|
237
|
-
":where(.keksi,.keksi-
|
|
238
|
-
":where(.keksi)
|
|
239
|
-
":where(.keksi
|
|
240
|
-
":where(.keksi
|
|
241
|
-
":where(.keksi-
|
|
242
|
-
":where(.keksi-
|
|
272
|
+
":where(.keksi,.keksi-policy){--_bg:var(--keksi-bg,#fff);--_fg:var(--keksi-fg,#1b1b1b);--_line:var(--keksi-line,#e3e3e3);--_soft:color-mix(in srgb,var(--_fg) 4%,var(--_bg));--_r:var(--keksi-radius,8px)}",
|
|
273
|
+
":where(.keksi){box-sizing:border-box;background:var(--_bg);color:var(--_fg);font:inherit;font-size:14px;line-height:1.5;border:1px solid var(--_line);border-radius:var(--_r);box-shadow:0 8px 32px #0000002e}",
|
|
274
|
+
":where(.keksi){position:fixed;z-index:2147483000;inset:auto 1rem 1rem;display:flex;flex-direction:column;max-width:34rem;max-height:calc(100vh - 2rem);margin-inline:auto;overflow:hidden}",
|
|
275
|
+
":where(.keksi,.keksi-main,.keksi-settings)[hidden]{display:none}",
|
|
276
|
+
":where(.keksi-main,.keksi-settings){display:flex;flex-direction:column;min-height:0;margin:0}",
|
|
277
|
+
":where(.keksi:has(.keksi-settings:not([hidden]))){max-width:40rem}",
|
|
278
|
+
":where(.keksi-settings .keksi-body){padding-block:0}",
|
|
279
|
+
":where(.keksi-head){display:flex;align-items:center;justify-content:space-between;gap:1rem;padding:.875rem 1.25rem;border-bottom:1px solid var(--_line)}",
|
|
280
|
+
":where(.keksi-head) :where(h2){font-size:1rem;font-weight:600;line-height:1.3;margin:0}",
|
|
281
|
+
":where(.keksi-body){padding:1rem 1.25rem;overflow:auto}",
|
|
282
|
+
":where(.keksi) :where(.keksi-body>*){margin:0 0 .75rem}",
|
|
283
|
+
":where(.keksi) :where(.keksi-body>:last-child){margin-bottom:0}",
|
|
284
|
+
":where(.keksi) :where(ul){list-style:none;padding:0}",
|
|
285
|
+
":where(.keksi) :where(strong){font-weight:600}",
|
|
286
|
+
":where(.keksi) :where(a){color:inherit}",
|
|
287
|
+
".keksi a{text-decoration:underline}",
|
|
288
|
+
":where(.keksi-actions){display:flex;flex-wrap:wrap;align-items:center;justify-content:flex-end;gap:.5rem;padding:.75rem 1.25rem;background:var(--_soft)}",
|
|
289
|
+
":where(.keksi-btn){min-width:7.5rem;font:inherit;font-weight:600;padding:.5rem 1rem;border:1px solid var(--_fg);border-radius:calc(var(--_r) - 2px);background:var(--_bg);color:var(--_fg);cursor:pointer}",
|
|
243
290
|
":where(.keksi-btn:hover){background:var(--_fg);color:var(--_bg)}",
|
|
244
|
-
":where(.keksi
|
|
245
|
-
":where(.keksi-
|
|
246
|
-
":where(.keksi-
|
|
247
|
-
":where(.keksi-
|
|
248
|
-
":where(.keksi-
|
|
249
|
-
":where(.keksi-
|
|
250
|
-
":where(.keksi-cat){
|
|
251
|
-
":where(.keksi-cat-head) :where(h2,h3,h4,h5){font-size:1rem;margin:0}",
|
|
291
|
+
":where(.keksi-text){margin-right:auto;padding:.5rem .25rem;font:inherit;border:0;background:none;color:inherit;text-decoration:underline;text-underline-offset:2px;cursor:pointer}",
|
|
292
|
+
"@media (max-width:30rem){:where(.keksi-btn){flex:1 1 auto}}",
|
|
293
|
+
":where(.keksi,keksi-youtube) :where(button,a,input,summary):focus-visible{outline:3px solid var(--keksi-focus,#2563eb);outline-offset:2px}",
|
|
294
|
+
":where(.keksi-close){font:inherit;font-size:1.375rem;line-height:1;margin:-.25rem -.5rem;padding:.25rem .5rem;border:0;background:none;color:inherit;cursor:pointer}",
|
|
295
|
+
":where(.keksi-cat){padding:1rem 0;border-top:1px solid var(--_line)}",
|
|
296
|
+
":where(.keksi-body>.keksi-cat:first-child){border-top:0}",
|
|
297
|
+
":where(.keksi-cat-head){display:flex;align-items:center;justify-content:space-between;gap:1rem}",
|
|
298
|
+
":where(.keksi-cat-head) :where(h2,h3,h4,h5){font-size:1rem;font-weight:600;margin:0}",
|
|
299
|
+
":where(.keksi-cat-head) :where(span){font-size:.8125rem;opacity:.7}",
|
|
252
300
|
":where(.keksi-cat) :where(p){margin:.25rem 0}",
|
|
253
|
-
":where(.keksi-cat input[role=switch]){appearance:none;position:relative;flex:none;width:2.
|
|
254
|
-
':where(.keksi-cat input[role=switch])::before{content:"";position:absolute;top:.
|
|
301
|
+
":where(.keksi-cat input[role=switch]){appearance:none;position:relative;flex:none;width:2.5rem;height:1.375rem;margin:0;border-radius:1rem;background:color-mix(in srgb,var(--_fg) 22%,var(--_bg));cursor:pointer}",
|
|
302
|
+
':where(.keksi-cat input[role=switch])::before{content:"";position:absolute;top:.1875rem;left:.1875rem;width:1rem;height:1rem;border-radius:50%;background:#fff;transition:transform .15s}',
|
|
255
303
|
":where(.keksi-cat input[role=switch]:checked){background:var(--_fg)}",
|
|
256
|
-
":where(.keksi-cat input[role=switch]:checked)::before{transform:translateX(1.
|
|
304
|
+
":where(.keksi-cat input[role=switch]:checked)::before{transform:translateX(1.125rem)}",
|
|
257
305
|
":where(.keksi-cat summary){margin-top:.5rem;font-weight:600;cursor:pointer}",
|
|
258
|
-
":where(.keksi-provider){margin:.75rem 0}",
|
|
259
|
-
":where(.keksi-provider)
|
|
260
|
-
":where(.keksi-
|
|
261
|
-
":where(.keksi-cat
|
|
262
|
-
":where(keksi-
|
|
306
|
+
":where(.keksi-provider){margin:.75rem 0;padding:.75rem .875rem;border-radius:calc(var(--_r) - 2px);background:var(--_soft)}",
|
|
307
|
+
":where(.keksi-policy .keksi-provider){padding:0;background:none}",
|
|
308
|
+
":where(.keksi-provider) :where(h3,h4,h5,h6){font-size:.9375rem;font-weight:600;margin:0}",
|
|
309
|
+
":where(.keksi-cat table){width:100%;border-collapse:collapse;font-size:.8125rem;margin-top:.5rem}",
|
|
310
|
+
":where(.keksi-cat) :where(th,td){padding:.3rem .75rem .3rem 0;border-bottom:1px solid var(--_line);text-align:left;vertical-align:top}",
|
|
311
|
+
":where(.keksi-cat) :where(th){font-weight:600}",
|
|
312
|
+
":where(keksi-youtube){position:relative;display:block;aspect-ratio:16/9;overflow:hidden;border-radius:12px;background:#0f0f0f;color:#fff;font-family:Roboto,Arial,sans-serif}",
|
|
263
313
|
":where(keksi-youtube iframe){display:block;width:100%;height:100%;border:0}",
|
|
264
|
-
":where(.keksi-yt){
|
|
265
|
-
":where(.keksi-yt) :
|
|
266
|
-
":where(.keksi-yt) :where(
|
|
267
|
-
":where(.keksi-yt
|
|
314
|
+
":where(keksi-youtube[label]:has(.keksi-yt))::before{content:attr(label);position:absolute;inset:0 0 auto;z-index:1;padding:1rem 1.25rem 2.5rem;font-size:1.125rem;font-weight:500;background:linear-gradient(#000b,#0000);pointer-events:none}",
|
|
315
|
+
":where(.keksi-yt){position:relative;display:grid;grid-template-rows:1fr auto auto;gap:.25rem;height:100%;padding:0 1.25rem 1rem;box-sizing:border-box;font-size:.875rem;line-height:1.45;background:linear-gradient(#0000 55%,#000c),center/cover var(--keksi-poster,none)}",
|
|
316
|
+
":where(.keksi-yt) :where(p){margin:0}",
|
|
317
|
+
":where(.keksi-yt) :where(p:first-of-type){grid-row:2}",
|
|
318
|
+
":where(.keksi-yt) :where(p:last-of-type){grid-row:3;color:#fffc}",
|
|
319
|
+
":where(.keksi-yt) :where(a){color:inherit;text-decoration:underline;text-underline-offset:2px}",
|
|
320
|
+
`:where(.keksi-yt button){position:absolute;inset:0;width:68px;height:48px;margin:auto;padding:0;border:0;font-size:0;color:transparent;background:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 68 48'%3E%3Cpath d='M66.52 7.74c-.78-2.93-2.49-5.41-5.42-6.19C55.79.13 34 0 34 0S12.21.13 6.9 1.55c-2.93.78-4.63 3.26-5.42 6.19C.06 13.05 0 24 0 24s.06 10.95 1.48 16.26c.78 2.93 2.49 5.41 5.42 6.19C12.21 47.87 34 48 34 48s21.79-.13 27.1-1.55c2.93-.78 4.64-3.26 5.42-6.19C67.94 34.95 68 24 68 24s-.06-10.95-1.48-16.26z' fill='%23f00'/%3E%3Cpath d='M45 24 27 14v20' fill='%23fff'/%3E%3C/svg%3E") center/contain no-repeat;cursor:pointer;transition:transform .15s}`,
|
|
321
|
+
":where(.keksi-yt button:hover){transform:scale(1.08)}"
|
|
268
322
|
].join("");
|
|
269
323
|
|
|
270
324
|
// src/render.ts
|
|
@@ -274,12 +328,12 @@ function renderKeksi(config, { lang = "fi", nonce } = {}) {
|
|
|
274
328
|
if (!cats.length) return "";
|
|
275
329
|
const t = textsFor(config, lang);
|
|
276
330
|
const active = activeProviders(config);
|
|
277
|
-
const policy =
|
|
331
|
+
const policy = policyOf(config, lang);
|
|
278
332
|
const n = nonce ? ` nonce="${e(nonce)}"` : "";
|
|
279
333
|
const btn = (action, label, type = "button") => `<button type="${type}" class="keksi-btn" data-keksi="${action}">${e(label)}</button>`;
|
|
280
334
|
const uses = cats.map((c) => `<li><strong>${e(t[c])}:</strong> ${e(active.filter((p) => p.category === c).map((p) => p.name).join(", "))}</li>`).join("");
|
|
281
335
|
const json = JSON.stringify(runtimeData(config, lang)).replace(/</g, "\\u003c");
|
|
282
|
-
return `<style${n}>${css}</style><section id="keksi-banner" class="keksi" aria-labelledby="keksi-title" hidden><h2 id="keksi-title">${e(t.title)}</h2><p>${e(t.intro)}</p><ul>${uses}</ul><p>${e(t.
|
|
336
|
+
return `<style${n}>${css}</style><section id="keksi-banner" class="keksi" aria-labelledby="keksi-title" hidden><div class="keksi-main"><div class="keksi-head"><h2 id="keksi-title">${e(t.title)}</h2></div><div class="keksi-body"><p>${e(t.intro)}</p><ul>${uses}</ul><p>${e(t.change)}${policy ? ` <a href="${e(policy)}">${e(t.more)}</a>` : ""}</p></div><div class="keksi-actions"><button type="button" class="keksi-text" data-keksi-open>${e(t.settings)}</button>${btn("reject", t.reject)}${btn("accept", t.accept)}</div></div><form id="keksi-settings" class="keksi-settings" aria-labelledby="keksi-settings-title" hidden><div class="keksi-head"><h2 id="keksi-settings-title">${e(t.settingsTitle)}</h2><button type="button" class="keksi-close" data-keksi="close" aria-label="${e(t.close)}">\xD7</button></div><div class="keksi-body">${sections(config, lang, 3, true)}</div><div class="keksi-actions">${btn("save", t.save)}${btn("reject", t.reject)}${btn("accept", t.accept)}</div></form></section><script type="application/json" id="keksi-data">${json}</script><script${n}>${'"use strict";(()=>{var F=(n,s)=>new RegExp(`^${n.split("*").map(i=>i.replace(/[.+?^${}()|[\\]\\\\]/g,"\\\\$&")).join(".*")}$`).test(s);var _=["analytics","marketing"],v={analytics:!1,marketing:!1};function f(...n){return arguments}function H(n,s=window){if(typeof s.keksi?.sync=="function")return s.keksi.sync(),s.keksi;let i=s.document,g=e=>i.getElementById(e),c=I(),A=!1,w=!1,L=!1,E=new Set,x=f("consent","default",{ad_storage:"denied",ad_user_data:"denied",ad_personalization:"denied",analytics_storage:"denied"});if(n.ga.length||n.ads.length){let e=s.dataLayer??(s.dataLayer=[]);s.gtag=function(){e.push(arguments)},e.push(x)}if(n.meta.length){let e=function(){e.callMethod?e.callMethod.apply(e,[...arguments]):e.queue.push(arguments)};Object.assign(e,{push:e,loaded:!0,version:"2.0",queue:[]}),s.fbq=s._fbq=e}if(n.ph){let e=Object.assign([],{_i:[],people:[]});for(let o of"capture identify alias reset register register_once unregister group setPersonProperties getFeatureFlag isFeatureEnabled onFeatureFlags reloadFeatureFlags get_distinct_id startSessionRecording stopSessionRecording captureException on".split(" "))e[o]=(...t)=>e.push([o,...t]);s.posthog=e}function I(){let e=i.cookie.split("; ").find(l=>l.startsWith(`${n.name}=`))?.slice(n.name.length+1),[o,,t,a]=e?.split(".")??[];if(o!==n.v||!t||!a)return null;let r={analytics:a.includes("a"),marketing:a.includes("m")};return{id:t,c:r,yt:r.marketing||a.includes("y"),done:!a.includes("u")}}function T(e,o=e.marketing,t=!0){let a=c?.id??Array.from(s.crypto.getRandomValues(new Uint8Array(8)),l=>l.toString(16).padStart(2,"0")).join(""),r=(t?"":"u")+(e.analytics?"a":"")+(e.marketing?"m":"")+(o&&!e.marketing?"y":"")||"-";return i.cookie=`${n.name}=${n.v}.${Date.now().toString(36)}.${a}.${r}; Path=/; Max-Age=${n.days*86400}; SameSite=Lax`+(n.domain?`; Domain=${n.domain}`:"")+(s.location.protocol==="https:"?"; Secure":""),{id:a,c:e,yt:o,done:t}}function $(e){let o=i.createElement("script");return o.async=!0,o.src=e,i.head.append(o),o}function C(e){let o=[...e.analytics?n.ga:[],...e.marketing?n.ads:[]].filter(t=>!E.has(t));if(o.length){let t=e.marketing?"granted":"denied",a=[f("consent","update",{analytics_storage:e.analytics?"granted":"denied",ad_storage:t,ad_user_data:t,ad_personalization:t})];E.size||a.push(f("js",new Date));for(let r of o)a.push(f("config",r)),E.add(r);if(A)for(let r of a)s.dataLayer.push(r);else{let r=s.dataLayer;r.splice(r.indexOf(x)+1,0,...a),$(`https://www.googletagmanager.com/gtag/js?id=${encodeURIComponent(o[0])}`),A=!0}}if(e.marketing&&n.meta.length&&!w&&(w=!0,s.fbq.queue.unshift(...n.meta.map(t=>f("init",t)),f("track","PageView")),$("https://connect.facebook.net/en_US/fbevents.js")),e.analytics&&n.ph&&!L&&(L=!0,s.posthog._i.push([n.ph.key,{api_host:n.ph.host,defaults:"2026-08-30"},"posthog"]),$(`${n.ph.host.replace(".i.posthog.com","-assets.i.posthog.com")}/static/array.js`).crossOrigin="anonymous"),c?.yt)for(let t of i.querySelectorAll("keksi-youtube"))t.show(!0)}function O(){c?.yt||(c=T(c?.c??v,!0,c?.done??!1)),C(c.c)}function R(e){let o=e.flatMap(l=>n.del[l]),t=l=>o.some(u=>F(u,l)),a=s.location.hostname.split("."),r=["",...a.map((l,u)=>`; Domain=${a.slice(u).join(".")}`)];for(let l of i.cookie.split("; ")){let u=l.split("=")[0];if(t(u))for(let b of r)i.cookie=`${u}=; Max-Age=0; Path=/${b}`}try{for(let l of[s.localStorage,s.sessionStorage])for(let u of Object.keys(l))t(u)&&l.removeItem(u)}catch{}}function p(e){let o=c;c=T(e),h(),i.dispatchEvent(new s.CustomEvent("keksi:change",{detail:{...e}})),R(_.filter(t=>!e[t])),_.some(t=>o?.c[t]&&!e[t])||o?.yt&&!c.yt?s.location.reload():C(e)}function y(e=!1,o=n.banner&&!c?.done||e){let t=g("keksi-banner"),a=g("keksi-settings");!t||!a||(t.hidden=!o,a.hidden=!e,t.firstElementChild.hidden=e)}function h(){y()}function M(){let e=g("keksi-settings");for(let o of _){let t=e?.querySelector(`[name=${o}]`);t&&(t.checked=!!c?.c[o])}y(!0),e?.querySelector(".keksi-close")?.focus()}i.addEventListener("click",e=>{let o=e.target.closest?.("[data-keksi],[data-keksi-open]");if(!o)return;if(o.hasAttribute("data-keksi-open"))return e.preventDefault(),M();let t=o.getAttribute("data-keksi");if(t==="close")y();else if(t==="accept")p({analytics:!0,marketing:!0});else if(t==="reject")p(v);else if(t==="save"){let a=r=>!!g("keksi-settings")?.querySelector(`[name=${r}]`)?.checked;p({analytics:a("analytics"),marketing:a("marketing")})}}),i.addEventListener("keydown",e=>{e.key==="Escape"&&g("keksi-settings")?.hidden===!1&&y()}),i.addEventListener("astro:after-swap",h);class D extends s.HTMLElement{connectedCallback(){this.show(!!c?.yt)}show(o,t=!1){let a=encodeURIComponent(this.getAttribute("video")??""),r=this.getAttribute("label");if(o){if(this.querySelector("iframe"))return;let d=i.createElement("iframe");d.src=`https://www.youtube-nocookie.com/embed/${a}${t?"?autoplay=1":""}`,d.title=r??"YouTube",d.allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share",d.allowFullscreen=!0,d.referrerPolicy="strict-origin-when-cross-origin",this.replaceChildren(d),t&&d.focus();return}let l=i.createElement("div");l.className="keksi-yt";let u=this.getAttribute("poster");u&&l.style.setProperty("--keksi-poster",`url(${JSON.stringify(u)})`);let b=i.createElement("p");b.textContent=n.t.ytText;let m=i.createElement("button");m.type="button",m.textContent=n.t.ytButton,r&&m.setAttribute("aria-label",`${n.t.ytButton}: ${r}`),m.onclick=()=>{this.show(!0,!0),O()};let S=i.createElement("p"),q=(d,N,P=!1)=>{let k=i.createElement("a");k.href=d,k.textContent=N,P&&(k.target="_blank",k.rel="noopener"),S.append(S.childNodes.length?" \\xB7 ":"",k)};q(`https://www.youtube.com/watch?v=${a}`,n.t.ytOpen,!0),n.policy&&q(n.policy,n.t.more),l.append(b,m,S),this.replaceChildren(l)}}n.yt&&!s.customElements.get("keksi-youtube")&&s.customElements.define("keksi-youtube",D);let j={get consent(){return c?.done?{...c.c}:null},open:M,save:e=>p({...c?.c??v,...e}),acceptAll:()=>p({analytics:!0,marketing:!0}),rejectAll:()=>p(v),sync:h};return s.keksi=j,h(),c&&C(c.c),j}H(JSON.parse(document.getElementById("keksi-data").textContent));})();'}</script>`;
|
|
283
337
|
}
|
|
284
338
|
function sections(config, lang, h, dialog) {
|
|
285
339
|
const t = textsFor(config, lang);
|
|
@@ -297,7 +351,7 @@ function sections(config, lang, h, dialog) {
|
|
|
297
351
|
const items = c === "necessary" ? [site] : active.filter((p) => p.category === c).map((p) => ({ ...p, purpose: p.purpose[lang] }));
|
|
298
352
|
const head = c === "necessary" ? `<h${h}>${e(t.necessary)}</h${h}><span>${e(t.alwaysOn)}</span>` : dialog ? `<h${h}><label for="keksi-c-${c}">${e(t[c])}</label></h${h}><input type="checkbox" role="switch" id="keksi-c-${c}" name="${c}" aria-describedby="keksi-i-${c}">` : `<h${h}>${e(t[c])}</h${h}>`;
|
|
299
353
|
const list2 = items.map(
|
|
300
|
-
(p) => `<div class="keksi-provider"><h${h + 1}>${e(p.name)}</h${h + 1}>${p.purpose ? `<p>${e(p.purpose)}</p>` : ""}` + (p.company && p.privacyPolicy ? `<p>${e(p.company)} \xB7 <a href="${e(p.privacyPolicy)}" target="_blank" rel="noopener">${e(t.privacyPolicy)}</a></p>` + (p.transfer ? `<p>${e(t.
|
|
354
|
+
(p) => `<div class="keksi-provider"><h${h + 1}>${e(p.name)}</h${h + 1}>${p.purpose ? `<p>${e(p.purpose)}</p>` : ""}` + (p.company && p.privacyPolicy ? `<p>${e(p.company)} \xB7 <a href="${e(p.privacyPolicy)}" target="_blank" rel="noopener">${e(t.privacyPolicy)}</a></p>` + (p.transfer ? `<p>${e(t[p.transfer])}</p>` : "") : "") + table(p.cookies, t) + `</div>`
|
|
301
355
|
).join("");
|
|
302
356
|
const body = dialog ? `<details><summary>${e(t.providers)} (${items.length})</summary>${list2}</details>` : list2;
|
|
303
357
|
return `<section class="keksi-cat"><div class="keksi-cat-head">${head}</div><p id="keksi-i-${c}">${e(t[`${c}Info`])}</p>${body}</section>`;
|
package/dist/types.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { Texts } from './i18n.js';
|
|
2
2
|
export type Locale = 'fi' | 'sv' | 'en';
|
|
3
3
|
export type Category = 'analytics' | 'marketing';
|
|
4
|
-
export type ProviderId = 'ga' | 'googleAds' | 'meta' | 'youtube';
|
|
4
|
+
export type ProviderId = 'ga' | 'googleAds' | 'meta' | 'posthog' | 'youtube';
|
|
5
5
|
export type Consent = Record<Category, boolean>;
|
|
6
6
|
export interface KeksiConfig {
|
|
7
7
|
/** GA4 measurement ids, e.g. `G-XXXXXXX`. */
|
|
@@ -10,6 +10,11 @@ export interface KeksiConfig {
|
|
|
10
10
|
googleAds?: string | string[];
|
|
11
11
|
/** Meta Pixel ids. */
|
|
12
12
|
meta?: string | string[];
|
|
13
|
+
/** PostHog project token and ingestion host (default `https://eu.i.posthog.com`). */
|
|
14
|
+
posthog?: {
|
|
15
|
+
key: string;
|
|
16
|
+
host?: string;
|
|
17
|
+
};
|
|
13
18
|
/** Enables `<keksi-youtube>` embeds. */
|
|
14
19
|
youtube?: boolean;
|
|
15
20
|
/** Cookie policy page, linked from the banner. */
|
|
@@ -40,7 +45,7 @@ export interface CookieInfo {
|
|
|
40
45
|
duration: Duration;
|
|
41
46
|
/** Third-party host that sets it. Omitted = set on this site, deleted on withdrawal. */
|
|
42
47
|
host?: string;
|
|
43
|
-
storage?: 'localStorage';
|
|
48
|
+
storage?: 'localStorage' | 'sessionStorage';
|
|
44
49
|
}
|
|
45
50
|
export interface Provider {
|
|
46
51
|
id: ProviderId;
|
|
@@ -49,8 +54,8 @@ export interface Provider {
|
|
|
49
54
|
company: string;
|
|
50
55
|
privacyPolicy: string;
|
|
51
56
|
purpose: Record<Locale, string>;
|
|
52
|
-
/** Transfer outside the EU
|
|
53
|
-
transfer?: 'dpf';
|
|
57
|
+
/** Transfer outside the EU: under the DPF, or to the US with no basis stated. */
|
|
58
|
+
transfer?: 'dpf' | 'us';
|
|
54
59
|
cookies: CookieInfo[];
|
|
55
60
|
/** Hosts it contacts, for the Playwright check. */
|
|
56
61
|
hosts: string[];
|
|
@@ -64,8 +69,15 @@ export interface RuntimeData {
|
|
|
64
69
|
ga: string[];
|
|
65
70
|
ads: string[];
|
|
66
71
|
meta: string[];
|
|
72
|
+
ph?: {
|
|
73
|
+
key: string;
|
|
74
|
+
host: string;
|
|
75
|
+
};
|
|
67
76
|
yt: boolean;
|
|
68
|
-
/**
|
|
77
|
+
/** False with YouTube only: the settings exist but the banner doesn't show by itself. */
|
|
78
|
+
banner: boolean;
|
|
79
|
+
policy?: string;
|
|
80
|
+
/** First-party cookie and storage names per category, `*` = wildcard. */
|
|
69
81
|
del: Record<Category, string[]>;
|
|
70
|
-
t: Pick<Texts, 'ytText' | 'ytButton' | 'ytOpen'>;
|
|
82
|
+
t: Pick<Texts, 'ytText' | 'ytButton' | 'ytOpen' | 'more'>;
|
|
71
83
|
}
|