keksi 0.1.4 → 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 CHANGED
@@ -102,9 +102,13 @@ posthog.capture('signup');
102
102
  <keksi-youtube video="aqz-KE-bpKQ" label="Esittelyvideo" poster="/img/poster.jpg"></keksi-youtube>
103
103
  ```
104
104
 
105
- - Shows a placeholder until the visitor clicks it, then loads that one video from `youtube-nocookie.com`.
106
- - With marketing consent, videos load directly.
107
- - `poster` is optional and should be self-hosted. Thumbnails from YouTube would contact Google before consent.
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.
108
112
 
109
113
  ## API
110
114
 
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/index.js CHANGED
@@ -33,9 +33,9 @@ var fi = {
33
33
  mo: "kuukausi|kuukautta",
34
34
  d: "p\xE4iv\xE4|p\xE4iv\xE4\xE4",
35
35
  min: "minuutti|minuuttia",
36
- ytText: "Video ladataan YouTubesta. YouTube (Google) asettaa ev\xE4steit\xE4 ja voi siirt\xE4\xE4 tietoja Yhdysvaltoihin.",
37
- ytButton: "N\xE4yt\xE4 video",
38
- ytOpen: "Avaa YouTubessa"
36
+ ytText: "Toistamalla videon hyv\xE4ksyt YouTuben ev\xE4steet.",
37
+ ytButton: "Toista video",
38
+ ytOpen: "Katso YouTubessa"
39
39
  };
40
40
  var sv = {
41
41
  title: "Kakor",
@@ -71,9 +71,9 @@ var sv = {
71
71
  mo: "m\xE5nad|m\xE5nader",
72
72
  d: "dag|dagar",
73
73
  min: "minut|minuter",
74
- ytText: "Videon laddas fr\xE5n YouTube. YouTube (Google) s\xE4tter kakor och kan \xF6verf\xF6ra uppgifter till USA.",
75
- ytButton: "Visa video",
76
- ytOpen: "\xD6ppna p\xE5 YouTube"
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"
77
77
  };
78
78
  var en = {
79
79
  title: "Cookies",
@@ -109,9 +109,9 @@ var en = {
109
109
  mo: "month|months",
110
110
  d: "day|days",
111
111
  min: "minute|minutes",
112
- ytText: "The video loads from YouTube. YouTube (Google) sets cookies and may transfer data to the United States.",
113
- ytButton: "Show video",
114
- ytOpen: "Open on YouTube"
112
+ ytText: "By playing the video, you accept cookies from YouTube.",
113
+ ytButton: "Play video",
114
+ ytOpen: "Watch on YouTube"
115
115
  };
116
116
  var texts = { fi, sv, en };
117
117
 
@@ -235,6 +235,12 @@ function activeProviders(config) {
235
235
  function categoriesOf(config) {
236
236
  return ["analytics", "marketing"].filter((c) => activeProviders(config).some((p) => p.category === c));
237
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
+ }
238
244
  function textsFor(config, locale) {
239
245
  return { ...texts[locale], ...config.texts?.[locale] };
240
246
  }
@@ -257,8 +263,10 @@ function runtimeData(config, locale) {
257
263
  meta: list(config.meta),
258
264
  ph: config.posthog?.key ? { key: config.posthog.key, host: posthogHost(config) } : void 0,
259
265
  yt: !!config.youtube,
266
+ banner: needsBanner(config),
267
+ policy: policyOf(config, locale),
260
268
  del: { analytics: del("analytics"), marketing: del("marketing") },
261
- t: { ytText: t.ytText, ytButton: t.ytButton, ytOpen: t.ytOpen }
269
+ t: { ytText: t.ytText, ytButton: t.ytButton, ytOpen: t.ytOpen, more: t.more }
262
270
  };
263
271
  }
264
272
 
@@ -304,12 +312,16 @@ var css = [
304
312
  ":where(.keksi-cat table){width:100%;border-collapse:collapse;font-size:.8125rem;margin-top:.5rem}",
305
313
  ":where(.keksi-cat) :where(th,td){padding:.3rem .75rem .3rem 0;border-bottom:1px solid var(--_line);text-align:left;vertical-align:top}",
306
314
  ":where(.keksi-cat) :where(th){font-weight:600}",
307
- ":where(keksi-youtube){display:block;aspect-ratio:16/9;background:#111;color:#fff}",
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}",
308
316
  ":where(keksi-youtube iframe){display:block;width:100%;height:100%;border:0}",
309
- ":where(.keksi-yt){display:flex;flex-direction:column;align-items:center;justify-content:center;gap:.75rem;height:100%;padding:1rem;box-sizing:border-box;text-align:center}",
310
- ":where(.keksi-yt) :where(p){max-width:32rem;margin:0}",
311
- ":where(.keksi-yt) :where(a){color:inherit}",
312
- ":where(.keksi-yt button){font:inherit;font-weight:600;padding:.6rem 1.25rem;border:0;border-radius:6px;background:#fff;color:#111;cursor:pointer}"
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)}"
313
325
  ].join("");
314
326
 
315
327
  // src/render.ts
@@ -319,12 +331,12 @@ function renderKeksi(config, { lang = "fi", nonce } = {}) {
319
331
  if (!cats.length) return "";
320
332
  const t = textsFor(config, lang);
321
333
  const active = activeProviders(config);
322
- const policy = typeof config.policyUrl === "string" ? config.policyUrl : config.policyUrl?.[lang];
334
+ const policy = policyOf(config, lang);
323
335
  const n = nonce ? ` nonce="${e(nonce)}"` : "";
324
336
  const btn = (action, label, type = "button") => `<button type="${type}" class="keksi-btn" data-keksi="${action}">${e(label)}</button>`;
325
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("");
326
338
  const json = JSON.stringify(runtimeData(config, lang)).replace(/</g, "\\u003c");
327
- 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 M=(o,s)=>new RegExp(`^${o.split("*").map(i=>i.replace(/[.+?^${}()|[\\]\\\\]/g,"\\\\$&")).join(".*")}$`).test(s);var $=["analytics","marketing"],C={analytics:!1,marketing:!1};function p(...o){return arguments}function j(o,s=window){if(typeof s.keksi?.sync=="function")return s.keksi.sync(),s.keksi;let i=s.document,f=e=>i.getElementById(e),u=q(),_=!1,A=!1,S=!1,v=new Set,L=p("consent","default",{ad_storage:"denied",ad_user_data:"denied",ad_personalization:"denied",analytics_storage:"denied"});if(o.ga.length||o.ads.length){let e=s.dataLayer??(s.dataLayer=[]);s.gtag=function(){e.push(arguments)},e.push(L)}if(o.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(o.ph){let e=Object.assign([],{_i:[],people:[]});for(let n of"capture identify alias reset register register_once unregister group setPersonProperties getFeatureFlag isFeatureEnabled onFeatureFlags reloadFeatureFlags get_distinct_id startSessionRecording stopSessionRecording captureException on".split(" "))e[n]=(...t)=>e.push([n,...t]);s.posthog=e}function q(){let e=i.cookie.split("; ").find(r=>r.startsWith(`${o.name}=`))?.slice(o.name.length+1),[n,,t,a]=e?.split(".")??[];return n!==o.v||!t||!a?null:{id:t,c:{analytics:a.includes("a"),marketing:a.includes("m")}}}function F(e){let n=u?.id??Array.from(s.crypto.getRandomValues(new Uint8Array(8)),a=>a.toString(16).padStart(2,"0")).join(""),t=(e.analytics?"a":"")+(e.marketing?"m":"")||"-";return i.cookie=`${o.name}=${o.v}.${Date.now().toString(36)}.${n}.${t}; Path=/; Max-Age=${o.days*86400}; SameSite=Lax`+(o.domain?`; Domain=${o.domain}`:"")+(s.location.protocol==="https:"?"; Secure":""),{id:n,c:e}}function E(e){let n=i.createElement("script");return n.async=!0,n.src=e,i.head.append(n),n}function w(e){let n=[...e.analytics?o.ga:[],...e.marketing?o.ads:[]].filter(t=>!v.has(t));if(n.length){let t=e.marketing?"granted":"denied",a=[p("consent","update",{analytics_storage:e.analytics?"granted":"denied",ad_storage:t,ad_user_data:t,ad_personalization:t})];v.size||a.push(p("js",new Date));for(let r of n)a.push(p("config",r)),v.add(r);if(_)for(let r of a)s.dataLayer.push(r);else{let r=s.dataLayer;r.splice(r.indexOf(L)+1,0,...a),E(`https://www.googletagmanager.com/gtag/js?id=${encodeURIComponent(n[0])}`),_=!0}}if(e.marketing&&o.meta.length&&!A&&(A=!0,s.fbq.queue.unshift(...o.meta.map(t=>p("init",t)),p("track","PageView")),E("https://connect.facebook.net/en_US/fbevents.js")),e.analytics&&o.ph&&!S&&(S=!0,s.posthog._i.push([o.ph.key,{api_host:o.ph.host,defaults:"2026-08-30"},"posthog"]),E(`${o.ph.host.replace(".i.posthog.com","-assets.i.posthog.com")}/static/array.js`).crossOrigin="anonymous"),e.marketing)for(let t of i.querySelectorAll("keksi-youtube"))t.show(!0)}function H(e){let n=e.flatMap(c=>o.del[c]),t=c=>n.some(l=>M(l,c)),a=s.location.hostname.split("."),r=["",...a.map((c,l)=>`; Domain=${a.slice(l).join(".")}`)];for(let c of i.cookie.split("; ")){let l=c.split("=")[0];if(t(l))for(let b of r)i.cookie=`${l}=; Max-Age=0; Path=/${b}`}try{for(let c of[s.localStorage,s.sessionStorage])for(let l of Object.keys(c))t(l)&&c.removeItem(l)}catch{}}function g(e){let n=u?.c;u=F(e),y(),i.dispatchEvent(new s.CustomEvent("keksi:change",{detail:{...e}})),H($.filter(t=>!e[t])),$.some(t=>n?.[t]&&!e[t])?s.location.reload():w(e)}function h(e=!1,n=!u||e){let t=f("keksi-banner"),a=f("keksi-settings");!t||!a||(t.hidden=!n,a.hidden=!e,t.firstElementChild.hidden=e)}function y(){h()}function x(){let e=f("keksi-settings");for(let n of $){let t=e?.querySelector(`[name=${n}]`);t&&(t.checked=!!u?.c[n])}h(!0),e?.querySelector(".keksi-close")?.focus()}i.addEventListener("click",e=>{let n=e.target.closest?.("[data-keksi],[data-keksi-open]");if(!n)return;if(n.hasAttribute("data-keksi-open"))return e.preventDefault(),x();let t=n.getAttribute("data-keksi");if(t==="close")h();else if(t==="accept")g({analytics:!0,marketing:!0});else if(t==="reject")g(C);else if(t==="save"){let a=r=>!!f("keksi-settings")?.querySelector(`[name=${r}]`)?.checked;g({analytics:a("analytics"),marketing:a("marketing")})}}),i.addEventListener("keydown",e=>{e.key==="Escape"&&f("keksi-settings")?.hidden===!1&&h()}),i.addEventListener("astro:after-swap",y);class I extends s.HTMLElement{connectedCallback(){this.show(!!u?.c.marketing)}show(n,t=!1){let a=encodeURIComponent(this.getAttribute("video")??""),r=this.getAttribute("label");if(n){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 c=i.createElement("div");c.className="keksi-yt";let l=this.getAttribute("poster");l&&(c.style.background=`linear-gradient(#000a,#000a),center/cover url(${JSON.stringify(l)})`);let b=i.createElement("p");b.textContent=o.t.ytText;let m=i.createElement("button");m.type="button",m.textContent=o.t.ytButton,r&&m.setAttribute("aria-label",`${o.t.ytButton}: ${r}`),m.onclick=()=>this.show(!0,!0);let k=i.createElement("a");k.href=`https://www.youtube.com/watch?v=${a}`,k.target="_blank",k.rel="noopener",k.textContent=o.t.ytOpen,c.append(b,m,k),this.replaceChildren(c)}}o.yt&&!s.customElements.get("keksi-youtube")&&s.customElements.define("keksi-youtube",I);let T={get consent(){return u?{...u.c}:null},open:x,save:e=>g({...u?.c??C,...e}),acceptAll:()=>g({analytics:!0,marketing:!0}),rejectAll:()=>g(C),sync:y};return s.keksi=T,y(),u&&w(u.c),T}j(JSON.parse(document.getElementById("keksi-data").textContent));})();'}</script>`;
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>`;
328
340
  }
329
341
  function cookieTable(config, { lang = "fi", headingLevel = 2 } = {}) {
330
342
  return `<div class="keksi-policy">${sections(config, lang, headingLevel, false)}</div>`;
@@ -33,9 +33,9 @@ var fi = {
33
33
  mo: "kuukausi|kuukautta",
34
34
  d: "p\xE4iv\xE4|p\xE4iv\xE4\xE4",
35
35
  min: "minuutti|minuuttia",
36
- ytText: "Video ladataan YouTubesta. YouTube (Google) asettaa ev\xE4steit\xE4 ja voi siirt\xE4\xE4 tietoja Yhdysvaltoihin.",
37
- ytButton: "N\xE4yt\xE4 video",
38
- ytOpen: "Avaa YouTubessa"
36
+ ytText: "Toistamalla videon hyv\xE4ksyt YouTuben ev\xE4steet.",
37
+ ytButton: "Toista video",
38
+ ytOpen: "Katso YouTubessa"
39
39
  };
40
40
  var sv = {
41
41
  title: "Kakor",
@@ -71,9 +71,9 @@ var sv = {
71
71
  mo: "m\xE5nad|m\xE5nader",
72
72
  d: "dag|dagar",
73
73
  min: "minut|minuter",
74
- ytText: "Videon laddas fr\xE5n YouTube. YouTube (Google) s\xE4tter kakor och kan \xF6verf\xF6ra uppgifter till USA.",
75
- ytButton: "Visa video",
76
- ytOpen: "\xD6ppna p\xE5 YouTube"
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"
77
77
  };
78
78
  var en = {
79
79
  title: "Cookies",
@@ -109,9 +109,9 @@ var en = {
109
109
  mo: "month|months",
110
110
  d: "day|days",
111
111
  min: "minute|minutes",
112
- ytText: "The video loads from YouTube. YouTube (Google) sets cookies and may transfer data to the United States.",
113
- ytButton: "Show video",
114
- ytOpen: "Open on YouTube"
112
+ ytText: "By playing the video, you accept cookies from YouTube.",
113
+ ytButton: "Play video",
114
+ ytOpen: "Watch on YouTube"
115
115
  };
116
116
  var texts = { fi, sv, en };
117
117
 
@@ -229,6 +229,12 @@ function activeProviders(config) {
229
229
  (p) => p.id === "youtube" ? config.youtube : p.id === "posthog" ? config.posthog?.key : list(config[p.id]).length
230
230
  ).map((p) => p.id === "posthog" && /\/\/us\./.test(posthogHost(config)) ? { ...p, transfer: "us" } : p);
231
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];
237
+ }
232
238
  function textsFor(config, locale) {
233
239
  return { ...texts[locale], ...config.texts?.[locale] };
234
240
  }
@@ -251,8 +257,10 @@ function runtimeData(config, locale) {
251
257
  meta: list(config.meta),
252
258
  ph: config.posthog?.key ? { key: config.posthog.key, host: posthogHost(config) } : void 0,
253
259
  yt: !!config.youtube,
260
+ banner: needsBanner(config),
261
+ policy: policyOf(config, locale),
254
262
  del: { analytics: del("analytics"), marketing: del("marketing") },
255
- t: { ytText: t.ytText, ytButton: t.ytButton, ytOpen: t.ytOpen }
263
+ t: { ytText: t.ytText, ytButton: t.ytButton, ytOpen: t.ytOpen, more: t.more }
256
264
  };
257
265
  }
258
266
 
package/dist/sveltekit.js CHANGED
@@ -33,9 +33,9 @@ var fi = {
33
33
  mo: "kuukausi|kuukautta",
34
34
  d: "p\xE4iv\xE4|p\xE4iv\xE4\xE4",
35
35
  min: "minuutti|minuuttia",
36
- ytText: "Video ladataan YouTubesta. YouTube (Google) asettaa ev\xE4steit\xE4 ja voi siirt\xE4\xE4 tietoja Yhdysvaltoihin.",
37
- ytButton: "N\xE4yt\xE4 video",
38
- ytOpen: "Avaa YouTubessa"
36
+ ytText: "Toistamalla videon hyv\xE4ksyt YouTuben ev\xE4steet.",
37
+ ytButton: "Toista video",
38
+ ytOpen: "Katso YouTubessa"
39
39
  };
40
40
  var sv = {
41
41
  title: "Kakor",
@@ -71,9 +71,9 @@ var sv = {
71
71
  mo: "m\xE5nad|m\xE5nader",
72
72
  d: "dag|dagar",
73
73
  min: "minut|minuter",
74
- ytText: "Videon laddas fr\xE5n YouTube. YouTube (Google) s\xE4tter kakor och kan \xF6verf\xF6ra uppgifter till USA.",
75
- ytButton: "Visa video",
76
- ytOpen: "\xD6ppna p\xE5 YouTube"
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"
77
77
  };
78
78
  var en = {
79
79
  title: "Cookies",
@@ -109,9 +109,9 @@ var en = {
109
109
  mo: "month|months",
110
110
  d: "day|days",
111
111
  min: "minute|minutes",
112
- ytText: "The video loads from YouTube. YouTube (Google) sets cookies and may transfer data to the United States.",
113
- ytButton: "Show video",
114
- ytOpen: "Open on YouTube"
112
+ ytText: "By playing the video, you accept cookies from YouTube.",
113
+ ytButton: "Play video",
114
+ ytOpen: "Watch on YouTube"
115
115
  };
116
116
  var texts = { fi, sv, en };
117
117
 
@@ -232,6 +232,12 @@ function activeProviders(config) {
232
232
  function categoriesOf(config) {
233
233
  return ["analytics", "marketing"].filter((c) => activeProviders(config).some((p) => p.category === c));
234
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
+ }
235
241
  function textsFor(config, locale) {
236
242
  return { ...texts[locale], ...config.texts?.[locale] };
237
243
  }
@@ -254,8 +260,10 @@ function runtimeData(config, locale) {
254
260
  meta: list(config.meta),
255
261
  ph: config.posthog?.key ? { key: config.posthog.key, host: posthogHost(config) } : void 0,
256
262
  yt: !!config.youtube,
263
+ banner: needsBanner(config),
264
+ policy: policyOf(config, locale),
257
265
  del: { analytics: del("analytics"), marketing: del("marketing") },
258
- t: { ytText: t.ytText, ytButton: t.ytButton, ytOpen: t.ytOpen }
266
+ t: { ytText: t.ytText, ytButton: t.ytButton, ytOpen: t.ytOpen, more: t.more }
259
267
  };
260
268
  }
261
269
 
@@ -301,12 +309,16 @@ var css = [
301
309
  ":where(.keksi-cat table){width:100%;border-collapse:collapse;font-size:.8125rem;margin-top:.5rem}",
302
310
  ":where(.keksi-cat) :where(th,td){padding:.3rem .75rem .3rem 0;border-bottom:1px solid var(--_line);text-align:left;vertical-align:top}",
303
311
  ":where(.keksi-cat) :where(th){font-weight:600}",
304
- ":where(keksi-youtube){display:block;aspect-ratio:16/9;background:#111;color:#fff}",
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}",
305
313
  ":where(keksi-youtube iframe){display:block;width:100%;height:100%;border:0}",
306
- ":where(.keksi-yt){display:flex;flex-direction:column;align-items:center;justify-content:center;gap:.75rem;height:100%;padding:1rem;box-sizing:border-box;text-align:center}",
307
- ":where(.keksi-yt) :where(p){max-width:32rem;margin:0}",
308
- ":where(.keksi-yt) :where(a){color:inherit}",
309
- ":where(.keksi-yt button){font:inherit;font-weight:600;padding:.6rem 1.25rem;border:0;border-radius:6px;background:#fff;color:#111;cursor:pointer}"
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)}"
310
322
  ].join("");
311
323
 
312
324
  // src/render.ts
@@ -316,12 +328,12 @@ function renderKeksi(config, { lang = "fi", nonce } = {}) {
316
328
  if (!cats.length) return "";
317
329
  const t = textsFor(config, lang);
318
330
  const active = activeProviders(config);
319
- const policy = typeof config.policyUrl === "string" ? config.policyUrl : config.policyUrl?.[lang];
331
+ const policy = policyOf(config, lang);
320
332
  const n = nonce ? ` nonce="${e(nonce)}"` : "";
321
333
  const btn = (action, label, type = "button") => `<button type="${type}" class="keksi-btn" data-keksi="${action}">${e(label)}</button>`;
322
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("");
323
335
  const json = JSON.stringify(runtimeData(config, lang)).replace(/</g, "\\u003c");
324
- 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 M=(o,s)=>new RegExp(`^${o.split("*").map(i=>i.replace(/[.+?^${}()|[\\]\\\\]/g,"\\\\$&")).join(".*")}$`).test(s);var $=["analytics","marketing"],C={analytics:!1,marketing:!1};function p(...o){return arguments}function j(o,s=window){if(typeof s.keksi?.sync=="function")return s.keksi.sync(),s.keksi;let i=s.document,f=e=>i.getElementById(e),u=q(),_=!1,A=!1,S=!1,v=new Set,L=p("consent","default",{ad_storage:"denied",ad_user_data:"denied",ad_personalization:"denied",analytics_storage:"denied"});if(o.ga.length||o.ads.length){let e=s.dataLayer??(s.dataLayer=[]);s.gtag=function(){e.push(arguments)},e.push(L)}if(o.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(o.ph){let e=Object.assign([],{_i:[],people:[]});for(let n of"capture identify alias reset register register_once unregister group setPersonProperties getFeatureFlag isFeatureEnabled onFeatureFlags reloadFeatureFlags get_distinct_id startSessionRecording stopSessionRecording captureException on".split(" "))e[n]=(...t)=>e.push([n,...t]);s.posthog=e}function q(){let e=i.cookie.split("; ").find(r=>r.startsWith(`${o.name}=`))?.slice(o.name.length+1),[n,,t,a]=e?.split(".")??[];return n!==o.v||!t||!a?null:{id:t,c:{analytics:a.includes("a"),marketing:a.includes("m")}}}function F(e){let n=u?.id??Array.from(s.crypto.getRandomValues(new Uint8Array(8)),a=>a.toString(16).padStart(2,"0")).join(""),t=(e.analytics?"a":"")+(e.marketing?"m":"")||"-";return i.cookie=`${o.name}=${o.v}.${Date.now().toString(36)}.${n}.${t}; Path=/; Max-Age=${o.days*86400}; SameSite=Lax`+(o.domain?`; Domain=${o.domain}`:"")+(s.location.protocol==="https:"?"; Secure":""),{id:n,c:e}}function E(e){let n=i.createElement("script");return n.async=!0,n.src=e,i.head.append(n),n}function w(e){let n=[...e.analytics?o.ga:[],...e.marketing?o.ads:[]].filter(t=>!v.has(t));if(n.length){let t=e.marketing?"granted":"denied",a=[p("consent","update",{analytics_storage:e.analytics?"granted":"denied",ad_storage:t,ad_user_data:t,ad_personalization:t})];v.size||a.push(p("js",new Date));for(let r of n)a.push(p("config",r)),v.add(r);if(_)for(let r of a)s.dataLayer.push(r);else{let r=s.dataLayer;r.splice(r.indexOf(L)+1,0,...a),E(`https://www.googletagmanager.com/gtag/js?id=${encodeURIComponent(n[0])}`),_=!0}}if(e.marketing&&o.meta.length&&!A&&(A=!0,s.fbq.queue.unshift(...o.meta.map(t=>p("init",t)),p("track","PageView")),E("https://connect.facebook.net/en_US/fbevents.js")),e.analytics&&o.ph&&!S&&(S=!0,s.posthog._i.push([o.ph.key,{api_host:o.ph.host,defaults:"2026-08-30"},"posthog"]),E(`${o.ph.host.replace(".i.posthog.com","-assets.i.posthog.com")}/static/array.js`).crossOrigin="anonymous"),e.marketing)for(let t of i.querySelectorAll("keksi-youtube"))t.show(!0)}function H(e){let n=e.flatMap(c=>o.del[c]),t=c=>n.some(l=>M(l,c)),a=s.location.hostname.split("."),r=["",...a.map((c,l)=>`; Domain=${a.slice(l).join(".")}`)];for(let c of i.cookie.split("; ")){let l=c.split("=")[0];if(t(l))for(let b of r)i.cookie=`${l}=; Max-Age=0; Path=/${b}`}try{for(let c of[s.localStorage,s.sessionStorage])for(let l of Object.keys(c))t(l)&&c.removeItem(l)}catch{}}function g(e){let n=u?.c;u=F(e),y(),i.dispatchEvent(new s.CustomEvent("keksi:change",{detail:{...e}})),H($.filter(t=>!e[t])),$.some(t=>n?.[t]&&!e[t])?s.location.reload():w(e)}function h(e=!1,n=!u||e){let t=f("keksi-banner"),a=f("keksi-settings");!t||!a||(t.hidden=!n,a.hidden=!e,t.firstElementChild.hidden=e)}function y(){h()}function x(){let e=f("keksi-settings");for(let n of $){let t=e?.querySelector(`[name=${n}]`);t&&(t.checked=!!u?.c[n])}h(!0),e?.querySelector(".keksi-close")?.focus()}i.addEventListener("click",e=>{let n=e.target.closest?.("[data-keksi],[data-keksi-open]");if(!n)return;if(n.hasAttribute("data-keksi-open"))return e.preventDefault(),x();let t=n.getAttribute("data-keksi");if(t==="close")h();else if(t==="accept")g({analytics:!0,marketing:!0});else if(t==="reject")g(C);else if(t==="save"){let a=r=>!!f("keksi-settings")?.querySelector(`[name=${r}]`)?.checked;g({analytics:a("analytics"),marketing:a("marketing")})}}),i.addEventListener("keydown",e=>{e.key==="Escape"&&f("keksi-settings")?.hidden===!1&&h()}),i.addEventListener("astro:after-swap",y);class I extends s.HTMLElement{connectedCallback(){this.show(!!u?.c.marketing)}show(n,t=!1){let a=encodeURIComponent(this.getAttribute("video")??""),r=this.getAttribute("label");if(n){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 c=i.createElement("div");c.className="keksi-yt";let l=this.getAttribute("poster");l&&(c.style.background=`linear-gradient(#000a,#000a),center/cover url(${JSON.stringify(l)})`);let b=i.createElement("p");b.textContent=o.t.ytText;let m=i.createElement("button");m.type="button",m.textContent=o.t.ytButton,r&&m.setAttribute("aria-label",`${o.t.ytButton}: ${r}`),m.onclick=()=>this.show(!0,!0);let k=i.createElement("a");k.href=`https://www.youtube.com/watch?v=${a}`,k.target="_blank",k.rel="noopener",k.textContent=o.t.ytOpen,c.append(b,m,k),this.replaceChildren(c)}}o.yt&&!s.customElements.get("keksi-youtube")&&s.customElements.define("keksi-youtube",I);let T={get consent(){return u?{...u.c}:null},open:x,save:e=>g({...u?.c??C,...e}),acceptAll:()=>g({analytics:!0,marketing:!0}),rejectAll:()=>g(C),sync:y};return s.keksi=T,y(),u&&w(u.c),T}j(JSON.parse(document.getElementById("keksi-data").textContent));})();'}</script>`;
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>`;
325
337
  }
326
338
  function sections(config, lang, h, dialog) {
327
339
  const t = textsFor(config, lang);
package/dist/types.d.ts CHANGED
@@ -74,7 +74,10 @@ export interface RuntimeData {
74
74
  host: string;
75
75
  };
76
76
  yt: boolean;
77
+ /** False with YouTube only: the settings exist but the banner doesn't show by itself. */
78
+ banner: boolean;
79
+ policy?: string;
77
80
  /** First-party cookie and storage names per category, `*` = wildcard. */
78
81
  del: Record<Category, string[]>;
79
- t: Pick<Texts, 'ytText' | 'ytButton' | 'ytOpen'>;
82
+ t: Pick<Texts, 'ytText' | 'ytButton' | 'ytOpen' | 'more'>;
80
83
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "keksi",
3
- "version": "0.1.4",
3
+ "version": "0.1.5",
4
4
  "description": "Lean cookie consent for Google and Meta trackers",
5
5
  "license": "MIT",
6
6
  "type": "module",