@freshjuice/zest 2.0.0 → 2.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (53) hide show
  1. package/dist/zest.d.ts +247 -0
  2. package/dist/zest.de.js +107 -3
  3. package/dist/zest.de.js.map +1 -1
  4. package/dist/zest.de.min.js +1 -1
  5. package/dist/zest.en.js +107 -3
  6. package/dist/zest.en.js.map +1 -1
  7. package/dist/zest.en.min.js +1 -1
  8. package/dist/zest.es.js +107 -3
  9. package/dist/zest.es.js.map +1 -1
  10. package/dist/zest.es.min.js +1 -1
  11. package/dist/zest.esm.js +107 -3
  12. package/dist/zest.esm.js.map +1 -1
  13. package/dist/zest.esm.min.js +1 -1
  14. package/dist/zest.fr.js +107 -3
  15. package/dist/zest.fr.js.map +1 -1
  16. package/dist/zest.fr.min.js +1 -1
  17. package/dist/zest.headless.d.ts +211 -0
  18. package/dist/zest.headless.esm.js +107 -3
  19. package/dist/zest.headless.esm.js.map +1 -1
  20. package/dist/zest.headless.esm.min.js +1 -1
  21. package/dist/zest.it.js +107 -3
  22. package/dist/zest.it.js.map +1 -1
  23. package/dist/zest.it.min.js +1 -1
  24. package/dist/zest.ja.js +107 -3
  25. package/dist/zest.ja.js.map +1 -1
  26. package/dist/zest.ja.min.js +1 -1
  27. package/dist/zest.js +107 -3
  28. package/dist/zest.js.map +1 -1
  29. package/dist/zest.min.js +1 -1
  30. package/dist/zest.nl.js +107 -3
  31. package/dist/zest.nl.js.map +1 -1
  32. package/dist/zest.nl.min.js +1 -1
  33. package/dist/zest.pl.js +107 -3
  34. package/dist/zest.pl.js.map +1 -1
  35. package/dist/zest.pl.min.js +1 -1
  36. package/dist/zest.pt.js +107 -3
  37. package/dist/zest.pt.js.map +1 -1
  38. package/dist/zest.pt.min.js +1 -1
  39. package/dist/zest.ru.js +107 -3
  40. package/dist/zest.ru.js.map +1 -1
  41. package/dist/zest.ru.min.js +1 -1
  42. package/dist/zest.uk.js +107 -3
  43. package/dist/zest.uk.js.map +1 -1
  44. package/dist/zest.uk.min.js +1 -1
  45. package/dist/zest.zh.js +107 -3
  46. package/dist/zest.zh.js.map +1 -1
  47. package/dist/zest.zh.min.js +1 -1
  48. package/package.json +9 -2
  49. package/src/config/defaults.js +48 -0
  50. package/src/core/pattern-matcher.js +37 -0
  51. package/src/core-lifecycle.js +23 -4
  52. package/src/types/zest.d.ts +247 -0
  53. package/src/types/zest.headless.d.ts +211 -0
@@ -1 +1 @@
1
- var Zest=function(){"use strict";const t={"&":"&amp;","<":"&lt;",">":"&gt;",'"':"&quot;","'":"&#39;","`":"&#96;"};function e(e){if(null==e)return"";return("string"==typeof e?e:e+"").replace(/[&<>"'`]/g,e=>t[e])}const n=new Set(["transparent","black","white","red","green","blue","yellow","orange","purple","pink","gray","grey","brown","cyan","magenta","silver","gold","navy","teal","maroon","olive","lime","aqua","fuchsia","indigo","violet","crimson","coral","salmon","tomato"]);const o=[/(\([^)]*[+*][^)]*\)|\[[^\]]*\]|\\w|\\d|\\s)\s*[+*]/,/\(\?[=!][^)]*[+*][^)]*\)[+*]/];function s(t,e){if(t instanceof RegExp)return t;if("string"!=typeof t)return null;if(t.length>500)return null;for(const e of o)if(e.test(t))return null;try{return RegExp(t,e)}catch(t){return null}}const a=new Set(["__proto__","constructor","prototype"]);function r(t,e){if(!t||"object"!=typeof t||Array.isArray(t))return null;const n={version:"string"==typeof t.version?t.version:null,timestamp:"number"==typeof t.timestamp&&Number.isFinite(t.timestamp)?t.timestamp:null,categories:{}},o=t.categories;if(!o||"object"!=typeof o||Array.isArray(o))return null;for(const t of e)a.has(t)||Object.prototype.hasOwnProperty.call(o,t)&&(n.categories[t]=!0===o[t]);return e.includes("essential")&&(n.categories.essential=!0),n}function i(t,...e){if("function"==typeof t)try{return t(...e)}catch(t){try{console.error("[Zest] User callback threw:",t)}catch(t){}return}}const c={essential:[/^zest_/,/^csrf/i,/^xsrf/i,/^session/i,/^__host-/i,/^__secure-/i],functional:[/^lang/i,/^locale/i,/^theme/i,/^preferences/i,/^ui_/i],analytics:[/^_ga/,/^_gid/,/^_gat/,/^_utm/,/^__utm/,/^plausible/i,/^_pk_/,/^matomo/i,/^_hj/,/^ajs_/],marketing:[/^_fbp/,/^_fbc/,/^_gcl/,/^_ttp/,/^ads/i,/^doubleclick/i,/^__gads/,/^__gpi/,/^_pin_/,/^li_/]};let l={...c};function d(t){for(const[e,n]of Object.entries(l))if(n.some(e=>e.test(t)))return e;return"marketing"}let u=null;const p=[];let f=()=>!1;function g(){return u}let m=null,b=null;const y=[],h=[];let z=()=>!1;function w(t,e,n){return new Proxy(t,{get(t,n){if("setItem"===n)return(n,o)=>{const s=d(n);z(s)?t.setItem(n,o):200>e.length&&e.push({key:n,value:o,category:s,timestamp:Date.now()})};const o=t[n];return"function"==typeof o?o.bind(t):o}})}const x={analytics:["google-analytics.com","www.google-analytics.com","analytics.google.com","googletagmanager.com","www.googletagmanager.com","plausible.io","cloudflareinsights.com","static.cloudflareinsights.com"],marketing:["connect.facebook.net","www.facebook.com/tr","ads.google.com","www.googleadservices.com","googleads.g.doubleclick.net","pagead2.googlesyndication.com"]},v={analytics:[...x.analytics,"analytics.tiktok.com","matomo.","hotjar.com","static.hotjar.com","script.hotjar.com","clarity.ms","www.clarity.ms","heapanalytics.com","cdn.heapanalytics.com","mixpanel.com","cdn.mxpnl.com","segment.com","cdn.segment.com","api.segment.io","fullstory.com","rs.fullstory.com","amplitude.com","cdn.amplitude.com","mouseflow.com","cdn.mouseflow.com","luckyorange.com","cdn.luckyorange.net","crazyegg.com","script.crazyegg.com"],marketing:[...x.marketing,"snap.licdn.com","px.ads.linkedin.com","ads.linkedin.com","analytics.twitter.com","static.ads-twitter.com","t.co","analytics.tiktok.com","ads.tiktok.com","sc-static.net","tr.snapchat.com","ct.pinterest.com","pintrk.com","s.pinimg.com","widgets.pinterest.com","bat.bing.com","ads.yahoo.com","sp.analytics.yahoo.com","amazon-adsystem.com","z-na.amazon-adsystem.com","criteo.com","static.criteo.net","dis.criteo.com","taboola.com","cdn.taboola.com","trc.taboola.com","outbrain.com","widgets.outbrain.com","adroll.com","s.adroll.com"],functional:["cdn.onesignal.com","onesignal.com","pusher.com","js.pusher.com","intercom.io","widget.intercom.io","js.intercomcdn.com","crisp.chat","client.crisp.chat","cdn.livechatinc.com","livechatinc.com","tawk.to","embed.tawk.to","zendesk.com","static.zdassets.com"]};function k(t,e){let n;try{n=new URL(t)}catch(t){return!1}const o=n.hostname.toLowerCase(),s=n.pathname.toLowerCase();for(const t of e){if("string"!=typeof t)continue;const e=t.toLowerCase();if(e.endsWith(".")){const t=e.slice(0,-1);if(o.split(".").some(e=>e===t)||o.startsWith(e))return!0;continue}const n=e.indexOf("/");if(-1!==n){const t=e.slice(0,n),a=e.slice(n);if((o===t||o.endsWith("."+t))&&s.startsWith(a))return!0;continue}if(o===e||o.endsWith("."+e))return!0}return!1}function _(t,e="safe"){const n="strict"===e?v:x;for(const[e,o]of Object.entries(n))if(k(t,o))return e;return null}const A=new Set(["functional","analytics","marketing"]),C=[];let S=null,j="safe",E=[],N=()=>!1;function $(t){if(t.hasAttribute("data-zest-allow"))return null;const e=t.getAttribute("data-consent-category"),n=e&&A.has(e)?e:null,o=t.src;if(!o)return n;const s=function(t){if(!t||0===E.length)return null;try{const e=new URL(t).hostname.toLowerCase();for(const t of E){const n="string"==typeof t?t:t.domain,o="string"==typeof t?"marketing":t.category||"marketing";if(e===n||e.endsWith("."+n))return o}}catch(t){}return null}(o);let a=null;switch(j){case"manual":break;case"safe":case"strict":a=_(o,j);break;case"doomsday":(function(t){try{const e=new URL(t).hostname,n=window.location.hostname,o=t=>t.replace(/^www\./,"");return o(e)!==o(n)}catch(t){return!1}})(o)&&(a=_(o,"strict")||"marketing")}return a||s||n}function L(t){if(t.hasAttribute("data-zest-processed"))return!1;const e=$(t);if(!e)return t.setAttribute("data-zest-processed","allowed"),!1;if(N(e))return t.setAttribute("data-zest-processed","allowed"),!1;const n={category:e,src:t.src||"",inline:t.textContent,type:t.type,async:t.async,defer:t.defer,element:t,timestamp:Date.now()};return t.setAttribute("data-zest-processed","blocked"),t.setAttribute("data-consent-category",e),t.type="text/plain",t.src&&t.removeAttribute("src"),500>C.length&&C.push(n),!0}function M(t){for(const e of t)for(const t of e.addedNodes)if("SCRIPT"!==t.nodeName||t.hasAttribute("data-zest-processed")||L(t),t.querySelectorAll){t.querySelectorAll("script:not([data-zest-processed])").forEach(L)}}function R(t="safe",e=[]){return j=t,E=e,document.querySelectorAll("script:not([data-zest-processed])").forEach(L),S=new MutationObserver(M),S.observe(document.documentElement,{childList:!0,subtree:!0}),!0}const T={essential:{id:"essential",label:"Essential",description:"Required for the website to function properly. Cannot be disabled.",required:!0,default:!0},functional:{id:"functional",label:"Functional",description:"Enable personalized features like language preferences and themes.",required:!1,default:!1},analytics:{id:"analytics",label:"Analytics",description:"Help us understand how visitors interact with our website.",required:!1,default:!1},marketing:{id:"marketing",label:"Marketing",description:"Used to deliver relevant advertisements and track campaign performance.",required:!1,default:!1}};function D(){return Object.keys(T)}function q(){if("undefined"==typeof navigator)return!1;const t=navigator.doNotTrack||window.doNotTrack||navigator.msDoNotTrack;return"1"===t||"yes"===t||!0===t||!0===navigator.globalPrivacyControl}function O(t,e,n){const o=n?"default":"update";e.consentModeGoogle&&function(t,e){if(window.dataLayer=window.dataLayer||[],"function"==typeof window.gtag)window.gtag("consent",t,e);else{function n(){window.dataLayer.push(arguments)}n("consent",t,e)}}(o,function(t){const e=t=>t?"granted":"denied";return{ad_storage:e(t.marketing),ad_user_data:e(t.marketing),ad_personalization:e(t.marketing),analytics_storage:e(t.analytics),functionality_storage:"granted",personalization_storage:e(t.functional)}}(t)),e.consentModeMicrosoft&&function(t,e){window.uetq=window.uetq||[],window.uetq.push("consent",t,e)}(o,function(t){return{ad_storage:t.marketing?"granted":"denied"}}(t))}const W={labels:{banner:{title:"Nous respectons votre vie privée",description:"Nous utilisons des cookies pour améliorer votre expérience de navigation, proposer du contenu personnalisé et analyser notre trafic. En cliquant sur « Tout accepter », vous consentez à l'utilisation de cookies.",acceptAll:"Tout accepter",rejectAll:"Tout refuser",settings:"Paramètres"},modal:{title:"Paramètres de confidentialité",description:"Gérez vos préférences en matière de cookies. Vous pouvez activer ou désactiver différents types de cookies ci-dessous.",save:"Enregistrer les préférences",acceptAll:"Tout accepter",rejectAll:"Tout refuser"},widget:{label:"Paramètres des cookies"}},categories:{essential:{label:"Essentiels",description:"Nécessaires au bon fonctionnement du site. Ne peuvent pas être désactivés."},functional:{label:"Fonctionnels",description:"Permettent des fonctionnalités personnalisées comme les préférences de langue et de thème."},analytics:{label:"Analytiques",description:"Nous aident à comprendre comment les visiteurs interagissent avec notre site."},marketing:{label:"Marketing",description:"Utilisés pour afficher des publicités pertinentes et mesurer les performances des campagnes."}}};const I={lang:"auto",position:"bottom",theme:"auto",accentColor:"#0071e3",categories:T,labels:{banner:{title:"We value your privacy",description:'We use cookies to enhance your browsing experience, serve personalized content, and analyze our traffic. By clicking "Accept All", you consent to our use of cookies.',acceptAll:"Accept All",rejectAll:"Reject All",settings:"Settings"},modal:{title:"Privacy Settings",description:"Manage your cookie preferences. You can enable or disable different types of cookies below.",save:"Save Preferences",acceptAll:"Accept All",rejectAll:"Reject All"},widget:{label:"Cookie Settings"}},autoInit:!0,showWidget:!0,expiration:365,respectDNT:!0,dntBehavior:"reject",customStyles:"",consentModeGoogle:!1,consentModeMicrosoft:!1,mode:"safe",blockedDomains:[],policyUrl:null,imprintUrl:null,callbacks:{onAccept:null,onReject:null,onChange:null,onReady:null}};function P(t){const e={...I};t||(t={});const n=["lang","position","theme","accentColor","autoInit","showWidget","expiration","policyUrl","imprintUrl","customStyles","mode","blockedDomains","respectDNT","dntBehavior","consentModeGoogle","consentModeMicrosoft"];for(const o of n)void 0!==t[o]&&(e[o]=t[o]);e.lang="fr";const o=W,s=o.labels||{},a=t.labels||{};e.labels={banner:{...I.labels.banner,...s.banner,...a.banner},modal:{...I.labels.modal,...s.modal,...a.modal},widget:{...I.labels.widget,...s.widget,...a.widget}};const r=o.categories||{},i=t.categories||{};e.categories={...I.categories};for(const t of Object.keys(I.categories))e.categories[t]={...I.categories[t],...r[t],...i[t]};return t.callbacks&&(e.callbacks={...I.callbacks,...t.callbacks}),t.patterns&&(e.patterns=t.patterns),e}function U(){const t="undefined"!=typeof window&&window.ZestConfig?window.ZestConfig:{},e=function(){const t=document.currentScript||document.querySelector("script[data-zest]")||document.querySelector('script[src*="zest"]');if(!t)return{};const e={},n=t.getAttribute("data-position");n&&(e.position=n);const o=t.getAttribute("data-theme");o&&(e.theme=o);const s=t.getAttribute("data-accent")||t.getAttribute("data-accent-color");s&&(e.accentColor=s);const a=t.getAttribute("data-policy-url")||t.getAttribute("data-privacy-url");a&&(e.policyUrl=a);const r=t.getAttribute("data-imprint-url");r&&(e.imprintUrl=r);const i=t.getAttribute("data-show-widget");null!==i&&(e.showWidget="false"!==i);const c=t.getAttribute("data-auto-init");null!==c&&(e.autoInit="false"!==c);const l=t.getAttribute("data-expiration");l&&(e.expiration=parseInt(l,10));const d=t.getAttribute("data-consent-mode-google");null!==d&&(e.consentModeGoogle="false"!==d);const u=t.getAttribute("data-consent-mode-microsoft");return null!==u&&(e.consentModeMicrosoft="false"!==u),e}();return P({...t,...e})}let Z=null;function Y(){return Z||(Z=U()),Z}const H="zest_consent";function F(){try{return"undefined"!=typeof location&&"https:"===location.protocol?"; Secure":""}catch(t){return""}}let B=null;function G(t){const e=g();e?.set?e.set.call(document,t):document.cookie=t}function J(){const t=g();return t?.get?t.get.call(document):document.cookie}function X(){try{const t=J().match(RegExp(H+"=([^;]+)"));if(t){const e=r(JSON.parse(decodeURIComponent(t[1])),D());if(e&&e.categories)return B={essential:!0,functional:!1,analytics:!1,marketing:!1,...e.categories},{...B}}}catch(t){}return B={essential:!0,functional:!1,analytics:!1,marketing:!1},{...B}}function V(){return B||(B=X()),{...B}}function K(t,e=365){const n=B?{...B}:{essential:!0,functional:!1,analytics:!1,marketing:!1};return B={essential:!0,functional:!!t.functional,analytics:!!t.analytics,marketing:!!t.marketing},function(t=365){B||(B={essential:!0,functional:!1,analytics:!1,marketing:!1});const e={version:"1.0",timestamp:Date.now(),categories:B},n=new Date(Date.now()+24*t*60*60*1e3).toUTCString();G(`${H}=${encodeURIComponent(JSON.stringify(e))}; expires=${n}; path=/; SameSite=Lax${F()}`)}(e),{current:{...B},previous:n}}function Q(t){return B||(B=X()),!0===B[t]}function tt(t=365){return K({functional:!1,analytics:!1,marketing:!1},t)}function et(){try{return J().includes(H)}catch(t){return!1}}const nt={READY:"zest:ready",CONSENT:"zest:consent",REJECT:"zest:reject",CHANGE:"zest:change",SHOW:"zest:show",HIDE:"zest:hide"};function ot(t,e={}){const n=new CustomEvent(t,{detail:e,bubbles:!0,cancelable:!0});return document.dispatchEvent(n),n}function st(t,e){return ot(nt.CONSENT,{consent:t,previous:e})}function at(t){return ot(nt.REJECT,{consent:t})}function rt(t,e){return ot(nt.CHANGE,{consent:t,previous:e})}function it(t="banner"){return ot(nt.SHOW,{type:t})}function ct(t="banner"){return ot(nt.HIDE,{type:t})}let lt=!1,dt=null;function ut(t){return Q(t)}function pt(t){!function(t){const e=[];for(const n of p)t.includes(n.category)?u?.set&&u.set.call(document,n.value):e.push(n);p.length=0,p.push(...e)}(t),function(t){const e=[];for(const n of y)t.includes(n.category)?m?.setItem(n.key,n.value):e.push(n);y.length=0,y.push(...e);const n=[];for(const e of h)t.includes(e.category)?b?.setItem(e.key,e.value):n.push(e);h.length=0,h.push(...n)}(t),function(t){const e=[];for(const n of C){if(!t.includes(n.category)){e.push(n);continue}const o=document.createElement("script");n.src?o.src=n.src:n.inline&&(o.textContent=n.inline),n.async&&(o.async=!0),n.defer&&(o.defer=!0),n.type&&"text/plain"!==n.type&&(o.type=n.type),o.setAttribute("data-zest-processed","executed"),o.setAttribute("data-consent-executed","true");const s=n.element;s&&s.isConnected&&s.parentNode?s.parentNode.replaceChild(o,s):document.head.appendChild(o)}C.length=0,C.push(...e)}(t)}function ft(t={}){if(lt)return{alreadyInitialized:!0,config:dt,consent:X(),hasDecision:et(),dntApplied:!1};Z=P(t),dt=Z,O({functional:!1,analytics:!1,marketing:!1},dt,!0),dt.patterns&&function(t){if(l={...c},t&&"object"==typeof t)for(const[e,n]of Object.entries(t)){if(!Array.isArray(n))continue;const t=[];for(const e of n){const n=s(e);if(n)t.push(n);else try{console.warn("[Zest] Rejected unsafe pattern:",e)}catch(t){}}l[e]=t}}(dt.patterns),f=ut,function(t){z=t}(ut),function(t){N=t}(ut),u=Object.getOwnPropertyDescriptor(Document.prototype,"cookie"),u?Object.defineProperty(document,"cookie",{get:()=>u.get.call(document),set(t){const e=function(t){const e=t.match(/^([^=]+)/);return e?e[1].trim():null}(t);if(!e)return;const n=d(e);f(n)?u.set.call(document,t):100>p.length&&p.push({value:t,name:e,category:n,timestamp:Date.now()})},configurable:!1}):console.warn("[Zest] Could not get cookie descriptor"),function(){try{return m=window.localStorage,b=window.sessionStorage,Object.defineProperty(window,"localStorage",{value:w(m,y),configurable:!0,writable:!1}),Object.defineProperty(window,"sessionStorage",{value:w(b,h),configurable:!0,writable:!1}),!0}catch(t){return console.warn("[Zest] Could not intercept storage APIs:",t),!1}}(),R(dt.mode,dt.blockedDomains);const e=X();lt=!0,et()&&O(e,dt,!1);let n=!1;if(q()&&dt.respectDNT&&"ignore"!==dt.dntBehavior&&"reject"===dt.dntBehavior&&!et()){const t=tt(dt.expiration);n=!0,O(t.current,dt,!1),at(t.current),rt(t.current,t.previous),i(dt.callbacks?.onReject),i(dt.callbacks?.onChange,t.current)}return function(t){ot(nt.READY,{consent:t})}(e),i(dt.callbacks?.onReady,e),{alreadyInitialized:!1,config:dt,consent:e,hasDecision:et(),dntApplied:n}}function gt(){if(!lt)return null;const t=function(t=365){return K({functional:!0,analytics:!0,marketing:!0},t)}(dt.expiration);return O(t.current,dt,!1),pt(D()),st(t.current,t.previous),rt(t.current,t.previous),i(dt.callbacks?.onAccept,t.current),i(dt.callbacks?.onChange,t.current),t}function mt(){G(`${H}=; expires=Thu, 01 Jan 1970 00:00:00 GMT; path=/; SameSite=Lax${F()}`),B=null}function bt(){return lt}function yt(){return dt}const ht="#4F46E5";function zt(t){const e=function(t){if("string"!=typeof t)return null;const e=t.trim();return/^#[0-9a-fA-F]{3,8}$/.test(e)||n.has(e.toLowerCase())||/^(rgb|rgba|hsl|hsla)\(\s*[\d.,%\s/]+\s*\)$/i.test(e)?e:null}(t.accentColor)||ht,o=function(t){if("string"!=typeof t||0===t.length)return"";if(t.length>2e4)return"";let e=t.replace(/\/\*[\s\S]*?\*\//g,"");return e=e.replace(/@import\s+[^;]+;?/gi,""),e=e.replace(/@charset\s+[^;]+;?/gi,""),e=e.replace(/url\(\s*(['"]?)([^)'"]+)\1\s*\)/gi,(t,e,n)=>{const o=n.trim().toLowerCase();return o.startsWith("https:")||o.startsWith("data:image/")||o.startsWith("/")||o.startsWith("#")?t:"url(#)"}),e=e.replace(/\.zest-btn--secondary\s*\{[^}]*\}/gi,""),e=e.replace(/\[data-action\s*=\s*["']reject-all["']\]\s*\{[^}]*\}/gi,""),e=e.replace(/\[data-action\s*=\s*["']accept-all["']\]\s*\{[^}]*\}/gi,""),e=e.replace(/expression\s*\([^)]*\)/gi,""),e=e.replace(/-moz-binding\s*:[^;}]*/gi,""),e}(t.customStyles);return`\n:host {\n --zest-accent: ${e};\n --zest-accent-hover: ${function(t,e){"string"==typeof t&&/^#[0-9a-fA-F]{3,8}$/.test(t.trim())||(t=ht);let n=t.trim().replace("#","");3===n.length&&(n=n.split("").map(t=>t+t).join(""));8===n.length&&(n=n.slice(0,6));6!==n.length&&(n="4F46E5");const o=parseInt(n,16),s=Math.round(2.55*e);return"#"+(16777216+65536*Math.min(255,Math.max(0,(o>>16)+s))+256*Math.min(255,Math.max(0,(o>>8&255)+s))+Math.min(255,Math.max(0,(255&o)+s))).toString(16).slice(1)}(e,-15)};\n --zest-bg: #ffffff;\n --zest-bg-secondary: #f3f4f6;\n --zest-text: #1f2937;\n --zest-text-secondary: #6b7280;\n --zest-border: #e5e7eb;\n --zest-shadow: 0 10px 25px -5px rgba(0, 0, 0, 0.1), 0 8px 10px -6px rgba(0, 0, 0, 0.1);\n --zest-radius: 12px;\n --zest-radius-sm: 8px;\n --zest-font: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;\n\n font-family: var(--zest-font);\n font-size: 14px;\n line-height: 1.5;\n color: var(--zest-text);\n box-sizing: border-box;\n}\n\n:host([data-theme="dark"]) {\n --zest-bg: #1f2937;\n --zest-bg-secondary: #374151;\n --zest-text: #f9fafb;\n --zest-text-secondary: #9ca3af;\n --zest-border: #4b5563;\n --zest-shadow: 0 10px 25px -5px rgba(0, 0, 0, 0.4), 0 8px 10px -6px rgba(0, 0, 0, 0.3);\n}\n\n@media (prefers-color-scheme: dark) {\n :host([data-theme="auto"]) {\n --zest-bg: #1f2937;\n --zest-bg-secondary: #374151;\n --zest-text: #f9fafb;\n --zest-text-secondary: #9ca3af;\n --zest-border: #4b5563;\n --zest-shadow: 0 10px 25px -5px rgba(0, 0, 0, 0.4), 0 8px 10px -6px rgba(0, 0, 0, 0.3);\n }\n}\n\n*, *::before, *::after {\n box-sizing: border-box;\n}\n\n/* Banner */\n.zest-banner {\n position: fixed;\n z-index: 999999;\n max-width: 480px;\n padding: 20px;\n background: var(--zest-bg);\n border-radius: var(--zest-radius);\n box-shadow: var(--zest-shadow);\n animation: zest-slide-in 0.3s ease-out;\n}\n\n.zest-banner--bottom {\n bottom: 20px;\n left: 50%;\n transform: translateX(-50%);\n}\n\n.zest-banner--bottom-left {\n bottom: 20px;\n left: 20px;\n}\n\n.zest-banner--bottom-right {\n bottom: 20px;\n right: 20px;\n}\n\n.zest-banner--top {\n top: 20px;\n left: 50%;\n transform: translateX(-50%);\n}\n\n@keyframes zest-slide-in {\n from {\n opacity: 0;\n transform: translateX(-50%) translateY(20px);\n }\n to {\n opacity: 1;\n transform: translateX(-50%) translateY(0);\n }\n}\n\n.zest-banner--bottom-left {\n animation-name: zest-slide-in-left;\n}\n\n@keyframes zest-slide-in-left {\n from {\n opacity: 0;\n transform: translateY(20px);\n }\n to {\n opacity: 1;\n transform: translateY(0);\n }\n}\n\n.zest-banner--bottom-right {\n animation-name: zest-slide-in-right;\n}\n\n@keyframes zest-slide-in-right {\n from {\n opacity: 0;\n transform: translateY(20px);\n }\n to {\n opacity: 1;\n transform: translateY(0);\n }\n}\n\n@media (prefers-reduced-motion: reduce) {\n .zest-banner,\n .zest-modal {\n animation: none;\n }\n}\n\n.zest-banner__title {\n margin: 0 0 8px 0;\n font-size: 16px;\n font-weight: 600;\n color: var(--zest-text);\n}\n\n.zest-banner__description {\n margin: 0 0 16px 0;\n font-size: 14px;\n color: var(--zest-text-secondary);\n}\n\n.zest-banner__buttons {\n display: flex;\n flex-wrap: wrap;\n gap: 8px;\n}\n\n/* Buttons */\n.zest-btn {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n padding: 10px 16px;\n font-size: 14px;\n font-weight: 500;\n font-family: inherit;\n border: none;\n border-radius: var(--zest-radius-sm);\n cursor: pointer;\n transition: background-color 0.15s ease, transform 0.1s ease;\n}\n\n.zest-btn:hover {\n transform: translateY(-1px);\n}\n\n.zest-btn:active {\n transform: translateY(0);\n}\n\n.zest-btn:focus-visible {\n outline: 2px solid var(--zest-accent);\n outline-offset: 2px;\n}\n\n.zest-btn--primary {\n background: var(--zest-accent);\n color: #ffffff;\n}\n\n.zest-btn--primary:hover {\n background: var(--zest-accent-hover);\n}\n\n.zest-btn--secondary {\n background: var(--zest-bg-secondary);\n color: var(--zest-text);\n}\n\n.zest-btn--secondary:hover {\n background: var(--zest-border);\n}\n\n.zest-btn--ghost {\n background: transparent;\n color: var(--zest-text-secondary);\n}\n\n.zest-btn--ghost:hover {\n background: var(--zest-bg-secondary);\n color: var(--zest-text);\n}\n\n/* Modal */\n.zest-modal-overlay {\n position: fixed;\n inset: 0;\n z-index: 999998;\n display: flex;\n align-items: center;\n justify-content: center;\n padding: 20px;\n background: rgba(0, 0, 0, 0.5);\n animation: zest-fade-in 0.2s ease-out;\n}\n\n@keyframes zest-fade-in {\n from { opacity: 0; }\n to { opacity: 1; }\n}\n\n.zest-modal {\n width: 100%;\n max-width: 500px;\n max-height: 90vh;\n overflow-y: auto;\n background: var(--zest-bg);\n border-radius: var(--zest-radius);\n box-shadow: var(--zest-shadow);\n animation: zest-modal-in 0.3s ease-out;\n}\n\n@keyframes zest-modal-in {\n from {\n opacity: 0;\n transform: scale(0.95);\n }\n to {\n opacity: 1;\n transform: scale(1);\n }\n}\n\n.zest-modal__header {\n padding: 20px 20px 0;\n}\n\n.zest-modal__title {\n margin: 0 0 8px 0;\n font-size: 18px;\n font-weight: 600;\n color: var(--zest-text);\n}\n\n.zest-modal__description {\n margin: 0;\n font-size: 14px;\n color: var(--zest-text-secondary);\n}\n\n.zest-modal__body {\n padding: 20px;\n}\n\n.zest-modal__footer {\n display: flex;\n flex-wrap: wrap;\n gap: 8px;\n padding: 0 20px 20px;\n}\n\n/* Categories */\n.zest-category {\n padding: 16px;\n margin-bottom: 12px;\n background: var(--zest-bg-secondary);\n border-radius: var(--zest-radius-sm);\n}\n\n.zest-category:last-child {\n margin-bottom: 0;\n}\n\n.zest-category__header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 12px;\n}\n\n.zest-category__info {\n flex: 1;\n}\n\n.zest-category__label {\n display: block;\n font-size: 14px;\n font-weight: 600;\n color: var(--zest-text);\n}\n\n.zest-category__description {\n margin: 4px 0 0;\n font-size: 13px;\n color: var(--zest-text-secondary);\n}\n\n/* Toggle Switch */\n.zest-toggle {\n position: relative;\n width: 44px;\n height: 24px;\n flex-shrink: 0;\n}\n\n.zest-toggle__input {\n position: absolute;\n opacity: 0;\n width: 100%;\n height: 100%;\n cursor: pointer;\n margin: 0;\n}\n\n.zest-toggle__input:disabled {\n cursor: not-allowed;\n}\n\n.zest-toggle__slider {\n position: absolute;\n inset: 0;\n background: var(--zest-border);\n border-radius: 12px;\n transition: background-color 0.2s ease;\n pointer-events: none;\n}\n\n.zest-toggle__slider::before {\n content: '';\n position: absolute;\n top: 2px;\n left: 2px;\n width: 20px;\n height: 20px;\n background: #ffffff;\n border-radius: 50%;\n transition: transform 0.2s ease;\n box-shadow: 0 1px 3px rgba(0, 0, 0, 0.2);\n}\n\n.zest-toggle__input:checked + .zest-toggle__slider {\n background: var(--zest-accent);\n}\n\n.zest-toggle__input:checked + .zest-toggle__slider::before {\n transform: translateX(20px);\n}\n\n.zest-toggle__input:focus-visible + .zest-toggle__slider {\n outline: 2px solid var(--zest-accent);\n outline-offset: 2px;\n}\n\n.zest-toggle__input:disabled + .zest-toggle__slider {\n opacity: 0.6;\n}\n\n/* Widget */\n.zest-widget {\n position: fixed;\n z-index: 999997;\n bottom: 20px;\n left: 20px;\n}\n\n.zest-widget__btn {\n display: flex;\n align-items: center;\n justify-content: center;\n width: 48px;\n height: 48px;\n padding: 0;\n background: var(--zest-bg);\n border: 1px solid var(--zest-border);\n border-radius: 50%;\n box-shadow: var(--zest-shadow);\n cursor: pointer;\n transition: transform 0.2s ease, box-shadow 0.2s ease;\n}\n\n.zest-widget__btn:hover {\n transform: scale(1.05);\n box-shadow: 0 12px 28px -5px rgba(0, 0, 0, 0.15);\n}\n\n.zest-widget__btn:focus-visible {\n outline: 2px solid var(--zest-accent);\n outline-offset: 2px;\n}\n\n.zest-widget__icon {\n width: 24px;\n height: 24px;\n fill: var(--zest-text);\n}\n\n/* Link */\n.zest-link {\n color: var(--zest-accent);\n text-decoration: none;\n}\n\n.zest-link:hover {\n text-decoration: underline;\n}\n\n/* Mobile */\n@media (max-width: 480px) {\n .zest-banner {\n left: 10px;\n right: 10px;\n max-width: none;\n transform: none;\n }\n\n .zest-banner--bottom,\n .zest-banner--bottom-left,\n .zest-banner--bottom-right {\n bottom: 10px;\n }\n\n .zest-banner--top {\n top: 10px;\n transform: none;\n }\n\n @keyframes zest-slide-in {\n from {\n opacity: 0;\n transform: translateY(20px);\n }\n to {\n opacity: 1;\n transform: translateY(0);\n }\n }\n\n .zest-banner__buttons {\n flex-direction: column;\n }\n\n .zest-btn {\n width: 100%;\n }\n\n .zest-modal-overlay {\n padding: 10px;\n }\n\n .zest-widget {\n bottom: 10px;\n left: 10px;\n }\n}\n\n/* Hidden utility */\n.zest-hidden {\n display: none !important;\n}\n${o}\n`}let wt=null,xt=null;const vt=new Set(["bottom","bottom-left","bottom-right","top"]);function kt(t={}){if(wt)return wt;const n=Y();wt=document.createElement("zest-banner"),wt.setAttribute("data-theme",n.theme||"light"),xt=wt.attachShadow({mode:"open"});const o=document.createElement("style");o.textContent=zt(n),xt.appendChild(o);const s=document.createElement("div");s.innerHTML=function(t){const n=t.labels.banner,o=t.position||"bottom";return`\n <div class="zest-banner zest-banner--${vt.has(o)?o:"bottom"}" role="dialog" aria-modal="false" aria-label="${e(n.title)}">\n <h2 class="zest-banner__title">${e(n.title)}</h2>\n <p class="zest-banner__description">${e(n.description)}</p>\n <div class="zest-banner__buttons">\n <button type="button" class="zest-btn zest-btn--primary" data-action="accept-all">\n ${e(n.acceptAll)}\n </button>\n <button type="button" class="zest-btn zest-btn--secondary" data-action="reject-all">\n ${e(n.rejectAll)}\n </button>\n <button type="button" class="zest-btn zest-btn--ghost" data-action="settings">\n ${e(n.settings)}\n </button>\n </div>\n </div>\n `}(n),xt.appendChild(s.firstElementChild);const a=xt.querySelector(".zest-banner");return a.addEventListener("click",e=>{const n=e.target.dataset.action;if(n)switch(n){case"accept-all":t.onAcceptAll?.();break;case"reject-all":t.onRejectAll?.();break;case"settings":t.onSettings?.()}}),a.addEventListener("keydown",e=>{"Escape"===e.key&&t.onSettings?.()}),document.body.appendChild(wt),requestAnimationFrame(()=>{const t=xt.querySelector("button");t?.focus()}),wt}function _t(t={}){wt?wt.classList.remove("zest-hidden"):kt(t)}function At(){wt&&(wt.remove(),wt=null,xt=null)}let Ct=null,St=null,jt={};function Et(t,n){const o=t.labels.modal,s=Object.values(t.categories||T).map(t=>function(t,n,o){const s=o?"disabled":"",a=n?"checked":"",r=e(t.id),i=e(t.label);return`\n <div class="zest-category">\n <div class="zest-category__header">\n <div class="zest-category__info">\n <span class="zest-category__label">${i}</span>\n <p class="zest-category__description">${e(t.description)}</p>\n </div>\n <label class="zest-toggle">\n <input\n type="checkbox"\n class="zest-toggle__input"\n data-category="${r}"\n ${a}\n ${s}\n aria-label="${i}"\n >\n <span class="zest-toggle__slider"></span>\n </label>\n </div>\n </div>\n `}(t,n[t.id]??t.default,t.required)).join(""),a=t.policyUrl?function(t){if("string"!=typeof t||0===t.length)return null;const e=t.trim();if(0===e.length)return null;if(/^[/?#]/.test(e))return e;const n=e.match(/^([a-z][a-z0-9+.-]*):/i);if(!n)return e;const o=n[1].toLowerCase();return"http"===o||"https"===o||"mailto"===o||"tel"===o?e:null}(t.policyUrl):null,r=a?`<a href="${e(a)}" class="zest-link" target="_blank" rel="noopener noreferrer">Privacy Policy</a>`:"";return`\n <div class="zest-modal-overlay" role="dialog" aria-modal="true" aria-label="${e(o.title)}">\n <div class="zest-modal">\n <div class="zest-modal__header">\n <h2 class="zest-modal__title">${e(o.title)}</h2>\n <p class="zest-modal__description">${e(o.description)} ${r}</p>\n </div>\n <div class="zest-modal__body">\n ${s}\n </div>\n <div class="zest-modal__footer">\n <button type="button" class="zest-btn zest-btn--primary" data-action="save">\n ${e(o.save)}\n </button>\n <button type="button" class="zest-btn zest-btn--secondary" data-action="accept-all">\n ${e(o.acceptAll)}\n </button>\n <button type="button" class="zest-btn zest-btn--ghost" data-action="reject-all">\n ${e(o.rejectAll)}\n </button>\n </div>\n </div>\n </div>\n `}function Nt(){if(!St)return jt;const t=St.querySelectorAll(".zest-toggle__input"),e={essential:!0};return t.forEach(t=>{const n=t.dataset.category;n&&"essential"!==n&&(e[n]=t.checked)}),e}function $t(){Ct&&(Ct.remove(),Ct=null,St=null)}let Lt=null,Mt=null;function Rt(t={}){if(Lt)return Lt;const n=Y();Lt=document.createElement("zest-widget"),Lt.setAttribute("data-theme",n.theme||"light"),Mt=Lt.attachShadow({mode:"open"});const o=document.createElement("style");o.textContent=zt(n),Mt.appendChild(o);const s=document.createElement("div");s.innerHTML=function(t){const n=e(t.labels.widget.label);return`\n <div class="zest-widget">\n <button type="button" class="zest-widget__btn" aria-label="${n}" title="${n}">\n <span class="zest-widget__icon"><svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M12 2C6.477 2 2 6.477 2 12s4.477 10 10 10 10-4.477 10-10c0-.728-.078-1.437-.225-2.12a1 1 0 0 0-1.482-.63 3 3 0 0 1-4.086-3.72 1 1 0 0 0-.793-1.263A10.05 10.05 0 0 0 12 2zm0 2c.178 0 .354.006.528.017a5 5 0 0 0 5.955 5.955c.011.174.017.35.017.528 0 4.418-3.582 8-8 8s-8-3.582-8-8 3.582-8 8-8zm-4 6a1.5 1.5 0 1 0 0 3 1.5 1.5 0 0 0 0-3zm5 0a1 1 0 1 0 0 2 1 1 0 0 0 0-2zm-2 4a1.5 1.5 0 1 0 0 3 1.5 1.5 0 0 0 0-3z"/></svg></span>\n </button>\n </div>\n `}(n),Mt.appendChild(s.firstElementChild);return Mt.querySelector(".zest-widget__btn").addEventListener("click",()=>{t.onClick?.()}),document.body.appendChild(Lt),Lt}function Tt(t={}){Lt?Lt.style.display="":Rt(t)}function Dt(){Lt&&(Lt.style.display="none")}function qt(){gt();const t=yt();At(),$t(),t?.showWidget&&Tt({onClick:It})}function Ot(){!function(){if(!lt)return null;const t=tt(dt.expiration);O(t.current,dt,!1),at(t.current),rt(t.current,t.previous),i(dt.callbacks?.onReject),i(dt.callbacks?.onChange,t.current)}();const t=yt();At(),$t(),t?.showWidget&&Tt({onClick:It})}function Wt(t){!function(t){if(!lt)return null;const e=K(t,dt.expiration);O(e.current,dt,!1);const n=Object.keys(e.current).filter(t=>e.current[t]&&!e.previous[t]);n.length>0&&pt(n),Object.entries(t||{}).some(([t,e])=>"essential"!==t&&e)?st(e.current,e.previous):at(e.current),rt(e.current,e.previous),i(dt.callbacks?.onChange,e.current)}(t);const e=yt();$t(),e?.showWidget&&Tt({onClick:It})}function It(){At(),Dt(),function(t={},e={}){if(Ct)return Ct;const n=Y();jt={...t},Ct=document.createElement("zest-modal"),Ct.setAttribute("data-theme",n.theme||"light"),St=Ct.attachShadow({mode:"open"});const o=document.createElement("style");o.textContent=zt(n),St.appendChild(o);const s=document.createElement("div");s.innerHTML=Et(n,t),St.appendChild(s.firstElementChild);const a=St.querySelector(".zest-modal-overlay");a.addEventListener("click",t=>{const n=t.target.dataset.action;if(n)switch(n){case"save":e.onSave?.(Nt());break;case"accept-all":e.onAcceptAll?.();break;case"reject-all":e.onRejectAll?.()}else t.target===a&&e.onClose?.()}),a.addEventListener("keydown",t=>{"Escape"===t.key&&e.onClose?.()}),St.querySelectorAll(".zest-toggle__input").forEach(t=>{t.addEventListener("change",()=>{jt=Nt()})}),document.body.appendChild(Ct),requestAnimationFrame(()=>{const t=St.querySelector("button");t?.focus()})}(V(),{onSave:Wt,onAcceptAll:qt,onRejectAll:Ot,onClose:Pt}),it("modal")}function Pt(){$t(),ct("modal");const t=yt();et()&&t?.showWidget?Tt({onClick:It}):_t({onAcceptAll:qt,onRejectAll:Ot,onSettings:It})}function Ut(t={}){const{alreadyInitialized:e,hasDecision:n,dntApplied:o}=ft(t);if(e)return console.warn("[Zest] Already initialized"),Zt;const s=yt();return n||o?s?.showWidget&&Tt({onClick:It}):(_t({onAcceptAll:qt,onRejectAll:Ot,onSettings:It}),it("banner")),Zt}const Zt={init:Ut,show(){bt()?($t(),Dt(),_t({onAcceptAll:qt,onRejectAll:Ot,onSettings:It}),it("banner")):console.warn("[Zest] Not initialized. Call Zest.init() first.")},hide(){At(),ct("banner")},showSettings(){bt()?It():console.warn("[Zest] Not initialized. Call Zest.init() first.")},hideSettings(){$t(),ct("modal")},getConsent:V,hasConsent:Q,hasConsentDecision:et,getConsentProof:function(){try{const t=J().match(RegExp(H+"=([^;]+)"));if(t){return r(JSON.parse(decodeURIComponent(t[1])),D())}}catch(t){}return null},isDoNotTrackEnabled:q,getDNTDetails:function(){if("undefined"==typeof navigator)return{enabled:!1,source:null};const t=navigator.doNotTrack||window.doNotTrack||navigator.msDoNotTrack;return"1"===t||"yes"===t||!0===t?{enabled:!0,source:"dnt"}:!0===navigator.globalPrivacyControl?{enabled:!0,source:"gpc"}:{enabled:!1,source:null}},acceptAll(){bt()?qt():console.warn("[Zest] Not initialized. Call Zest.init() first.")},rejectAll(){bt()?Ot():console.warn("[Zest] Not initialized. Call Zest.init() first.")},reset(){mt(),$t(),Lt&&(Lt.remove(),Lt=null,Mt=null),bt()&&(_t({onAcceptAll:qt,onRejectAll:Ot,onSettings:It}),it("banner"))},getConfig:Y,on:function(t,e){return document.addEventListener(t,e),()=>document.removeEventListener(t,e)},once:function(t,e){document.addEventListener(t,e,{once:!0})},EVENTS:nt};if("undefined"!=typeof window){try{Object.defineProperty(window,"Zest",{value:Object.freeze(Zt),writable:!1,configurable:!1,enumerable:!0})}catch(t){window.Zest=Zt}const t=()=>{!1!==U().autoInit&&Ut(window.ZestConfig)};"loading"===document.readyState?document.addEventListener("DOMContentLoaded",t):t()}return Zt}();
1
+ var Zest=function(){"use strict";const t={"&":"&amp;","<":"&lt;",">":"&gt;",'"':"&quot;","'":"&#39;","`":"&#96;"};function e(e){if(null==e)return"";return("string"==typeof e?e:e+"").replace(/[&<>"'`]/g,e=>t[e])}const n=new Set(["transparent","black","white","red","green","blue","yellow","orange","purple","pink","gray","grey","brown","cyan","magenta","silver","gold","navy","teal","maroon","olive","lime","aqua","fuchsia","indigo","violet","crimson","coral","salmon","tomato"]);const o=[/(\([^)]*[+*][^)]*\)|\[[^\]]*\]|\\w|\\d|\\s)\s*[+*]/,/\(\?[=!][^)]*[+*][^)]*\)[+*]/];function s(t,e){if(t instanceof RegExp)return t;if("string"!=typeof t)return null;if(t.length>500)return null;for(const e of o)if(e.test(t))return null;try{return RegExp(t,e)}catch(t){return null}}const a=new Set(["__proto__","constructor","prototype"]);function r(t,e){if(!t||"object"!=typeof t||Array.isArray(t))return null;const n={version:"string"==typeof t.version?t.version:null,timestamp:"number"==typeof t.timestamp&&Number.isFinite(t.timestamp)?t.timestamp:null,categories:{}},o=t.categories;if(!o||"object"!=typeof o||Array.isArray(o))return null;for(const t of e)a.has(t)||Object.prototype.hasOwnProperty.call(o,t)&&(n.categories[t]=!0===o[t]);return e.includes("essential")&&(n.categories.essential=!0),n}function i(t,...e){if("function"==typeof t)try{return t(...e)}catch(t){try{console.error("[Zest] User callback threw:",t)}catch(t){}return}}const c={essential:[/^zest_/,/^csrf/i,/^xsrf/i,/^session/i,/^__host-/i,/^__secure-/i],functional:[/^lang/i,/^locale/i,/^theme/i,/^preferences/i,/^ui_/i],analytics:[/^_ga/,/^_gid/,/^_gat/,/^_utm/,/^__utm/,/^plausible/i,/^_pk_/,/^matomo/i,/^_hj/,/^ajs_/],marketing:[/^_fbp/,/^_fbc/,/^_gcl/,/^_ttp/,/^ads/i,/^doubleclick/i,/^__gads/,/^__gpi/,/^_pin_/,/^li_/]};let l={...c};function d(t){return(t+"").replace(/[.*+?^${}()|[\]\\]/g,"\\$&")}function u(t){for(const[e,n]of Object.entries(l))if(n.some(e=>e.test(t)))return e;return"marketing"}let p=null;const f=[];let g=()=>!1;function m(){return p}let b=null,y=null;const h=[],z=[];let w=()=>!1;function x(t,e,n){return new Proxy(t,{get(t,n){if("setItem"===n)return(n,o)=>{const s=u(n);w(s)?t.setItem(n,o):200>e.length&&e.push({key:n,value:o,category:s,timestamp:Date.now()})};const o=t[n];return"function"==typeof o?o.bind(t):o}})}const v={analytics:["google-analytics.com","www.google-analytics.com","analytics.google.com","googletagmanager.com","www.googletagmanager.com","plausible.io","cloudflareinsights.com","static.cloudflareinsights.com"],marketing:["connect.facebook.net","www.facebook.com/tr","ads.google.com","www.googleadservices.com","googleads.g.doubleclick.net","pagead2.googlesyndication.com"]},k={analytics:[...v.analytics,"analytics.tiktok.com","matomo.","hotjar.com","static.hotjar.com","script.hotjar.com","clarity.ms","www.clarity.ms","heapanalytics.com","cdn.heapanalytics.com","mixpanel.com","cdn.mxpnl.com","segment.com","cdn.segment.com","api.segment.io","fullstory.com","rs.fullstory.com","amplitude.com","cdn.amplitude.com","mouseflow.com","cdn.mouseflow.com","luckyorange.com","cdn.luckyorange.net","crazyegg.com","script.crazyegg.com"],marketing:[...v.marketing,"snap.licdn.com","px.ads.linkedin.com","ads.linkedin.com","analytics.twitter.com","static.ads-twitter.com","t.co","analytics.tiktok.com","ads.tiktok.com","sc-static.net","tr.snapchat.com","ct.pinterest.com","pintrk.com","s.pinimg.com","widgets.pinterest.com","bat.bing.com","ads.yahoo.com","sp.analytics.yahoo.com","amazon-adsystem.com","z-na.amazon-adsystem.com","criteo.com","static.criteo.net","dis.criteo.com","taboola.com","cdn.taboola.com","trc.taboola.com","outbrain.com","widgets.outbrain.com","adroll.com","s.adroll.com"],functional:["cdn.onesignal.com","onesignal.com","pusher.com","js.pusher.com","intercom.io","widget.intercom.io","js.intercomcdn.com","crisp.chat","client.crisp.chat","cdn.livechatinc.com","livechatinc.com","tawk.to","embed.tawk.to","zendesk.com","static.zdassets.com"]};function _(t,e){let n;try{n=new URL(t)}catch(t){return!1}const o=n.hostname.toLowerCase(),s=n.pathname.toLowerCase();for(const t of e){if("string"!=typeof t)continue;const e=t.toLowerCase();if(e.endsWith(".")){const t=e.slice(0,-1);if(o.split(".").some(e=>e===t)||o.startsWith(e))return!0;continue}const n=e.indexOf("/");if(-1!==n){const t=e.slice(0,n),a=e.slice(n);if((o===t||o.endsWith("."+t))&&s.startsWith(a))return!0;continue}if(o===e||o.endsWith("."+e))return!0}return!1}function A(t,e="safe"){const n="strict"===e?k:v;for(const[e,o]of Object.entries(n))if(_(t,o))return e;return null}const C=new Set(["functional","analytics","marketing"]),S=[];let j=null,E="safe",$=[],N=()=>!1;function L(t){if(t.hasAttribute("data-zest-allow"))return null;const e=t.getAttribute("data-consent-category"),n=e&&C.has(e)?e:null,o=t.src;if(!o)return n;const s=function(t){if(!t||0===$.length)return null;try{const e=new URL(t).hostname.toLowerCase();for(const t of $){const n="string"==typeof t?t:t.domain,o="string"==typeof t?"marketing":t.category||"marketing";if(e===n||e.endsWith("."+n))return o}}catch(t){}return null}(o);let a=null;switch(E){case"manual":break;case"safe":case"strict":a=A(o,E);break;case"doomsday":(function(t){try{const e=new URL(t).hostname,n=window.location.hostname,o=t=>t.replace(/^www\./,"");return o(e)!==o(n)}catch(t){return!1}})(o)&&(a=A(o,"strict")||"marketing")}return a||s||n}function M(t){if(t.hasAttribute("data-zest-processed"))return!1;const e=L(t);if(!e)return t.setAttribute("data-zest-processed","allowed"),!1;if(N(e))return t.setAttribute("data-zest-processed","allowed"),!1;const n={category:e,src:t.src||"",inline:t.textContent,type:t.type,async:t.async,defer:t.defer,element:t,timestamp:Date.now()};return t.setAttribute("data-zest-processed","blocked"),t.setAttribute("data-consent-category",e),t.type="text/plain",t.src&&t.removeAttribute("src"),500>S.length&&S.push(n),!0}function R(t){for(const e of t)for(const t of e.addedNodes)if("SCRIPT"!==t.nodeName||t.hasAttribute("data-zest-processed")||M(t),t.querySelectorAll){t.querySelectorAll("script:not([data-zest-processed])").forEach(M)}}function T(t="safe",e=[]){return E=t,$=e,document.querySelectorAll("script:not([data-zest-processed])").forEach(M),j=new MutationObserver(R),j.observe(document.documentElement,{childList:!0,subtree:!0}),!0}const D={essential:{id:"essential",label:"Essential",description:"Required for the website to function properly. Cannot be disabled.",required:!0,default:!0},functional:{id:"functional",label:"Functional",description:"Enable personalized features like language preferences and themes.",required:!1,default:!1},analytics:{id:"analytics",label:"Analytics",description:"Help us understand how visitors interact with our website.",required:!1,default:!1},marketing:{id:"marketing",label:"Marketing",description:"Used to deliver relevant advertisements and track campaign performance.",required:!1,default:!1}};function q(){return Object.keys(D)}function O(){if("undefined"==typeof navigator)return!1;const t=navigator.doNotTrack||window.doNotTrack||navigator.msDoNotTrack;return"1"===t||"yes"===t||!0===t||!0===navigator.globalPrivacyControl}function P(t,e,n){const o=n?"default":"update";e.consentModeGoogle&&function(t,e){if(window.dataLayer=window.dataLayer||[],"function"==typeof window.gtag)window.gtag("consent",t,e);else{function n(){window.dataLayer.push(arguments)}n("consent",t,e)}}(o,function(t){const e=t=>t?"granted":"denied";return{ad_storage:e(t.marketing),ad_user_data:e(t.marketing),ad_personalization:e(t.marketing),analytics_storage:e(t.analytics),functionality_storage:"granted",personalization_storage:e(t.functional)}}(t)),e.consentModeMicrosoft&&function(t,e){window.uetq=window.uetq||[],window.uetq.push("consent",t,e)}(o,function(t){return{ad_storage:t.marketing?"granted":"denied"}}(t))}const W={labels:{banner:{title:"Nous respectons votre vie privée",description:"Nous utilisons des cookies pour améliorer votre expérience de navigation, proposer du contenu personnalisé et analyser notre trafic. En cliquant sur « Tout accepter », vous consentez à l'utilisation de cookies.",acceptAll:"Tout accepter",rejectAll:"Tout refuser",settings:"Paramètres"},modal:{title:"Paramètres de confidentialité",description:"Gérez vos préférences en matière de cookies. Vous pouvez activer ou désactiver différents types de cookies ci-dessous.",save:"Enregistrer les préférences",acceptAll:"Tout accepter",rejectAll:"Tout refuser"},widget:{label:"Paramètres des cookies"}},categories:{essential:{label:"Essentiels",description:"Nécessaires au bon fonctionnement du site. Ne peuvent pas être désactivés."},functional:{label:"Fonctionnels",description:"Permettent des fonctionnalités personnalisées comme les préférences de langue et de thème."},analytics:{label:"Analytiques",description:"Nous aident à comprendre comment les visiteurs interagissent avec notre site."},marketing:{label:"Marketing",description:"Utilisés pour afficher des publicités pertinentes et mesurer les performances des campagnes."}}};const I={lang:"auto",position:"bottom",theme:"auto",accentColor:"#0071e3",categories:D,labels:{banner:{title:"We value your privacy",description:'We use cookies to enhance your browsing experience, serve personalized content, and analyze our traffic. By clicking "Accept All", you consent to our use of cookies.',acceptAll:"Accept All",rejectAll:"Reject All",settings:"Settings"},modal:{title:"Privacy Settings",description:"Manage your cookie preferences. You can enable or disable different types of cookies below.",save:"Save Preferences",acceptAll:"Accept All",rejectAll:"Reject All"},widget:{label:"Cookie Settings"}},autoInit:!0,showWidget:!0,expiration:365,respectDNT:!0,dntBehavior:"reject",customStyles:"",consentModeGoogle:!1,consentModeMicrosoft:!1,mode:"safe",intercept:{cookies:!0,storage:!0,scripts:!0},essentialKeys:[],essentialPatterns:[],blockedDomains:[],policyUrl:null,imprintUrl:null,callbacks:{onAccept:null,onReject:null,onChange:null,onReady:null}};function U(t){const e={...I};t||(t={});const n=["lang","position","theme","accentColor","autoInit","showWidget","expiration","policyUrl","imprintUrl","customStyles","mode","blockedDomains","respectDNT","dntBehavior","consentModeGoogle","consentModeMicrosoft"];for(const o of n)void 0!==t[o]&&(e[o]=t[o]);e.lang="fr";const o=W,s=o.labels||{},a=t.labels||{};e.labels={banner:{...I.labels.banner,...s.banner,...a.banner},modal:{...I.labels.modal,...s.modal,...a.modal},widget:{...I.labels.widget,...s.widget,...a.widget}};const r=o.categories||{},i=t.categories||{};e.categories={...I.categories};for(const t of Object.keys(I.categories))e.categories[t]={...I.categories[t],...r[t],...i[t]};return t.callbacks&&(e.callbacks={...I.callbacks,...t.callbacks}),t.patterns&&(e.patterns=t.patterns),t.intercept&&"object"==typeof t.intercept&&(e.intercept={...I.intercept,...t.intercept}),Array.isArray(t.essentialKeys)&&(e.essentialKeys=t.essentialKeys.filter(t=>"string"==typeof t&&t.length>0&&200>=t.length)),Array.isArray(t.essentialPatterns)&&(e.essentialPatterns=t.essentialPatterns.filter(t=>"string"==typeof t&&t.length>0&&500>=t.length)),e}function Z(){const t="undefined"!=typeof window&&window.ZestConfig?window.ZestConfig:{},e=function(){const t=document.currentScript||document.querySelector("script[data-zest]")||document.querySelector('script[src*="zest"]');if(!t)return{};const e={},n=t.getAttribute("data-position");n&&(e.position=n);const o=t.getAttribute("data-theme");o&&(e.theme=o);const s=t.getAttribute("data-accent")||t.getAttribute("data-accent-color");s&&(e.accentColor=s);const a=t.getAttribute("data-policy-url")||t.getAttribute("data-privacy-url");a&&(e.policyUrl=a);const r=t.getAttribute("data-imprint-url");r&&(e.imprintUrl=r);const i=t.getAttribute("data-show-widget");null!==i&&(e.showWidget="false"!==i);const c=t.getAttribute("data-auto-init");null!==c&&(e.autoInit="false"!==c);const l=t.getAttribute("data-expiration");l&&(e.expiration=parseInt(l,10));const d=t.getAttribute("data-consent-mode-google");null!==d&&(e.consentModeGoogle="false"!==d);const u=t.getAttribute("data-consent-mode-microsoft");return null!==u&&(e.consentModeMicrosoft="false"!==u),e}();return U({...t,...e})}let Y=null;function H(){return Y||(Y=Z()),Y}const F="zest_consent";function B(){try{return"undefined"!=typeof location&&"https:"===location.protocol?"; Secure":""}catch(t){return""}}let G=null;function K(t){const e=m();e?.set?e.set.call(document,t):document.cookie=t}function J(){const t=m();return t?.get?t.get.call(document):document.cookie}function X(){try{const t=J().match(RegExp(F+"=([^;]+)"));if(t){const e=r(JSON.parse(decodeURIComponent(t[1])),q());if(e&&e.categories)return G={essential:!0,functional:!1,analytics:!1,marketing:!1,...e.categories},{...G}}}catch(t){}return G={essential:!0,functional:!1,analytics:!1,marketing:!1},{...G}}function V(){return G||(G=X()),{...G}}function Q(t,e=365){const n=G?{...G}:{essential:!0,functional:!1,analytics:!1,marketing:!1};return G={essential:!0,functional:!!t.functional,analytics:!!t.analytics,marketing:!!t.marketing},function(t=365){G||(G={essential:!0,functional:!1,analytics:!1,marketing:!1});const e={version:"1.0",timestamp:Date.now(),categories:G},n=new Date(Date.now()+24*t*60*60*1e3).toUTCString();K(`${F}=${encodeURIComponent(JSON.stringify(e))}; expires=${n}; path=/; SameSite=Lax${B()}`)}(e),{current:{...G},previous:n}}function tt(t){return G||(G=X()),!0===G[t]}function et(t=365){return Q({functional:!1,analytics:!1,marketing:!1},t)}function nt(){try{return J().includes(F)}catch(t){return!1}}const ot={READY:"zest:ready",CONSENT:"zest:consent",REJECT:"zest:reject",CHANGE:"zest:change",SHOW:"zest:show",HIDE:"zest:hide"};function st(t,e={}){const n=new CustomEvent(t,{detail:e,bubbles:!0,cancelable:!0});return document.dispatchEvent(n),n}function at(t,e){return st(ot.CONSENT,{consent:t,previous:e})}function rt(t){return st(ot.REJECT,{consent:t})}function it(t,e){return st(ot.CHANGE,{consent:t,previous:e})}function ct(t="banner"){return st(ot.SHOW,{type:t})}function lt(t="banner"){return st(ot.HIDE,{type:t})}let dt=!1,ut=null;function pt(t){return tt(t)}function ft(t){!function(t){const e=[];for(const n of f)t.includes(n.category)?p?.set&&p.set.call(document,n.value):e.push(n);f.length=0,f.push(...e)}(t),function(t){const e=[];for(const n of h)t.includes(n.category)?b?.setItem(n.key,n.value):e.push(n);h.length=0,h.push(...e);const n=[];for(const e of z)t.includes(e.category)?y?.setItem(e.key,e.value):n.push(e);z.length=0,z.push(...n)}(t),function(t){const e=[];for(const n of S){if(!t.includes(n.category)){e.push(n);continue}const o=document.createElement("script");n.src?o.src=n.src:n.inline&&(o.textContent=n.inline),n.async&&(o.async=!0),n.defer&&(o.defer=!0),n.type&&"text/plain"!==n.type&&(o.type=n.type),o.setAttribute("data-zest-processed","executed"),o.setAttribute("data-consent-executed","true");const s=n.element;s&&s.isConnected&&s.parentNode?s.parentNode.replaceChild(o,s):document.head.appendChild(o)}S.length=0,S.push(...e)}(t)}function gt(t={}){if(dt)return{alreadyInitialized:!0,config:ut,consent:X(),hasDecision:nt(),dntApplied:!1};Y=U(t),ut=Y,P({functional:!1,analytics:!1,marketing:!1},ut,!0),ut.patterns&&function(t){if(l={...c},t&&"object"==typeof t)for(const[e,n]of Object.entries(t)){if(!Array.isArray(n))continue;const t=[];for(const e of n){const n=s(e);if(n)t.push(n);else try{console.warn("[Zest] Rejected unsafe pattern:",e)}catch(t){}}l[e]=t}}(ut.patterns),(Array.isArray(ut.essentialKeys)&&ut.essentialKeys.length>0||Array.isArray(ut.essentialPatterns)&&ut.essentialPatterns.length>0)&&function(t,{keys:e=[],patternStrings:n=[]}={}){l[t]||(l[t]=[]);for(const n of e){if("string"!=typeof n||!n)continue;const e=s(`^${d(n)}$`);e&&l[t].push(e)}for(const e of n){if("string"!=typeof e||!e)continue;const n=s(e);n&&l[t].push(n)}}("essential",{keys:ut.essentialKeys,patternStrings:ut.essentialPatterns}),g=pt,function(t){w=t}(pt),function(t){N=t}(pt);const e=ut.intercept||{cookies:!0,storage:!0,scripts:!0};!1!==e.cookies&&(p=Object.getOwnPropertyDescriptor(Document.prototype,"cookie"),p?Object.defineProperty(document,"cookie",{get:()=>p.get.call(document),set(t){const e=function(t){const e=t.match(/^([^=]+)/);return e?e[1].trim():null}(t);if(!e)return;const n=u(e);g(n)?p.set.call(document,t):100>f.length&&f.push({value:t,name:e,category:n,timestamp:Date.now()})},configurable:!1}):console.warn("[Zest] Could not get cookie descriptor")),!1!==e.storage&&function(){try{return b=window.localStorage,y=window.sessionStorage,Object.defineProperty(window,"localStorage",{value:x(b,h),configurable:!0,writable:!1}),Object.defineProperty(window,"sessionStorage",{value:x(y,z),configurable:!0,writable:!1}),!0}catch(t){return console.warn("[Zest] Could not intercept storage APIs:",t),!1}}(),!1!==e.scripts&&T(ut.mode,ut.blockedDomains);const n=X();dt=!0,nt()&&P(n,ut,!1);let o=!1;if(O()&&ut.respectDNT&&"ignore"!==ut.dntBehavior&&"reject"===ut.dntBehavior&&!nt()){const t=et(ut.expiration);o=!0,P(t.current,ut,!1),rt(t.current),it(t.current,t.previous),i(ut.callbacks?.onReject),i(ut.callbacks?.onChange,t.current)}return function(t){st(ot.READY,{consent:t})}(n),i(ut.callbacks?.onReady,n),{alreadyInitialized:!1,config:ut,consent:n,hasDecision:nt(),dntApplied:o}}function mt(){if(!dt)return null;const t=function(t=365){return Q({functional:!0,analytics:!0,marketing:!0},t)}(ut.expiration);return P(t.current,ut,!1),ft(q()),at(t.current,t.previous),it(t.current,t.previous),i(ut.callbacks?.onAccept,t.current),i(ut.callbacks?.onChange,t.current),t}function bt(){K(`${F}=; expires=Thu, 01 Jan 1970 00:00:00 GMT; path=/; SameSite=Lax${B()}`),G=null}function yt(){return dt}function ht(){return ut}const zt="#4F46E5";function wt(t){const e=function(t){if("string"!=typeof t)return null;const e=t.trim();return/^#[0-9a-fA-F]{3,8}$/.test(e)||n.has(e.toLowerCase())||/^(rgb|rgba|hsl|hsla)\(\s*[\d.,%\s/]+\s*\)$/i.test(e)?e:null}(t.accentColor)||zt,o=function(t){if("string"!=typeof t||0===t.length)return"";if(t.length>2e4)return"";let e=t.replace(/\/\*[\s\S]*?\*\//g,"");return e=e.replace(/@import\s+[^;]+;?/gi,""),e=e.replace(/@charset\s+[^;]+;?/gi,""),e=e.replace(/url\(\s*(['"]?)([^)'"]+)\1\s*\)/gi,(t,e,n)=>{const o=n.trim().toLowerCase();return o.startsWith("https:")||o.startsWith("data:image/")||o.startsWith("/")||o.startsWith("#")?t:"url(#)"}),e=e.replace(/\.zest-btn--secondary\s*\{[^}]*\}/gi,""),e=e.replace(/\[data-action\s*=\s*["']reject-all["']\]\s*\{[^}]*\}/gi,""),e=e.replace(/\[data-action\s*=\s*["']accept-all["']\]\s*\{[^}]*\}/gi,""),e=e.replace(/expression\s*\([^)]*\)/gi,""),e=e.replace(/-moz-binding\s*:[^;}]*/gi,""),e}(t.customStyles);return`\n:host {\n --zest-accent: ${e};\n --zest-accent-hover: ${function(t,e){"string"==typeof t&&/^#[0-9a-fA-F]{3,8}$/.test(t.trim())||(t=zt);let n=t.trim().replace("#","");3===n.length&&(n=n.split("").map(t=>t+t).join(""));8===n.length&&(n=n.slice(0,6));6!==n.length&&(n="4F46E5");const o=parseInt(n,16),s=Math.round(2.55*e);return"#"+(16777216+65536*Math.min(255,Math.max(0,(o>>16)+s))+256*Math.min(255,Math.max(0,(o>>8&255)+s))+Math.min(255,Math.max(0,(255&o)+s))).toString(16).slice(1)}(e,-15)};\n --zest-bg: #ffffff;\n --zest-bg-secondary: #f3f4f6;\n --zest-text: #1f2937;\n --zest-text-secondary: #6b7280;\n --zest-border: #e5e7eb;\n --zest-shadow: 0 10px 25px -5px rgba(0, 0, 0, 0.1), 0 8px 10px -6px rgba(0, 0, 0, 0.1);\n --zest-radius: 12px;\n --zest-radius-sm: 8px;\n --zest-font: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;\n\n font-family: var(--zest-font);\n font-size: 14px;\n line-height: 1.5;\n color: var(--zest-text);\n box-sizing: border-box;\n}\n\n:host([data-theme="dark"]) {\n --zest-bg: #1f2937;\n --zest-bg-secondary: #374151;\n --zest-text: #f9fafb;\n --zest-text-secondary: #9ca3af;\n --zest-border: #4b5563;\n --zest-shadow: 0 10px 25px -5px rgba(0, 0, 0, 0.4), 0 8px 10px -6px rgba(0, 0, 0, 0.3);\n}\n\n@media (prefers-color-scheme: dark) {\n :host([data-theme="auto"]) {\n --zest-bg: #1f2937;\n --zest-bg-secondary: #374151;\n --zest-text: #f9fafb;\n --zest-text-secondary: #9ca3af;\n --zest-border: #4b5563;\n --zest-shadow: 0 10px 25px -5px rgba(0, 0, 0, 0.4), 0 8px 10px -6px rgba(0, 0, 0, 0.3);\n }\n}\n\n*, *::before, *::after {\n box-sizing: border-box;\n}\n\n/* Banner */\n.zest-banner {\n position: fixed;\n z-index: 999999;\n max-width: 480px;\n padding: 20px;\n background: var(--zest-bg);\n border-radius: var(--zest-radius);\n box-shadow: var(--zest-shadow);\n animation: zest-slide-in 0.3s ease-out;\n}\n\n.zest-banner--bottom {\n bottom: 20px;\n left: 50%;\n transform: translateX(-50%);\n}\n\n.zest-banner--bottom-left {\n bottom: 20px;\n left: 20px;\n}\n\n.zest-banner--bottom-right {\n bottom: 20px;\n right: 20px;\n}\n\n.zest-banner--top {\n top: 20px;\n left: 50%;\n transform: translateX(-50%);\n}\n\n@keyframes zest-slide-in {\n from {\n opacity: 0;\n transform: translateX(-50%) translateY(20px);\n }\n to {\n opacity: 1;\n transform: translateX(-50%) translateY(0);\n }\n}\n\n.zest-banner--bottom-left {\n animation-name: zest-slide-in-left;\n}\n\n@keyframes zest-slide-in-left {\n from {\n opacity: 0;\n transform: translateY(20px);\n }\n to {\n opacity: 1;\n transform: translateY(0);\n }\n}\n\n.zest-banner--bottom-right {\n animation-name: zest-slide-in-right;\n}\n\n@keyframes zest-slide-in-right {\n from {\n opacity: 0;\n transform: translateY(20px);\n }\n to {\n opacity: 1;\n transform: translateY(0);\n }\n}\n\n@media (prefers-reduced-motion: reduce) {\n .zest-banner,\n .zest-modal {\n animation: none;\n }\n}\n\n.zest-banner__title {\n margin: 0 0 8px 0;\n font-size: 16px;\n font-weight: 600;\n color: var(--zest-text);\n}\n\n.zest-banner__description {\n margin: 0 0 16px 0;\n font-size: 14px;\n color: var(--zest-text-secondary);\n}\n\n.zest-banner__buttons {\n display: flex;\n flex-wrap: wrap;\n gap: 8px;\n}\n\n/* Buttons */\n.zest-btn {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n padding: 10px 16px;\n font-size: 14px;\n font-weight: 500;\n font-family: inherit;\n border: none;\n border-radius: var(--zest-radius-sm);\n cursor: pointer;\n transition: background-color 0.15s ease, transform 0.1s ease;\n}\n\n.zest-btn:hover {\n transform: translateY(-1px);\n}\n\n.zest-btn:active {\n transform: translateY(0);\n}\n\n.zest-btn:focus-visible {\n outline: 2px solid var(--zest-accent);\n outline-offset: 2px;\n}\n\n.zest-btn--primary {\n background: var(--zest-accent);\n color: #ffffff;\n}\n\n.zest-btn--primary:hover {\n background: var(--zest-accent-hover);\n}\n\n.zest-btn--secondary {\n background: var(--zest-bg-secondary);\n color: var(--zest-text);\n}\n\n.zest-btn--secondary:hover {\n background: var(--zest-border);\n}\n\n.zest-btn--ghost {\n background: transparent;\n color: var(--zest-text-secondary);\n}\n\n.zest-btn--ghost:hover {\n background: var(--zest-bg-secondary);\n color: var(--zest-text);\n}\n\n/* Modal */\n.zest-modal-overlay {\n position: fixed;\n inset: 0;\n z-index: 999998;\n display: flex;\n align-items: center;\n justify-content: center;\n padding: 20px;\n background: rgba(0, 0, 0, 0.5);\n animation: zest-fade-in 0.2s ease-out;\n}\n\n@keyframes zest-fade-in {\n from { opacity: 0; }\n to { opacity: 1; }\n}\n\n.zest-modal {\n width: 100%;\n max-width: 500px;\n max-height: 90vh;\n overflow-y: auto;\n background: var(--zest-bg);\n border-radius: var(--zest-radius);\n box-shadow: var(--zest-shadow);\n animation: zest-modal-in 0.3s ease-out;\n}\n\n@keyframes zest-modal-in {\n from {\n opacity: 0;\n transform: scale(0.95);\n }\n to {\n opacity: 1;\n transform: scale(1);\n }\n}\n\n.zest-modal__header {\n padding: 20px 20px 0;\n}\n\n.zest-modal__title {\n margin: 0 0 8px 0;\n font-size: 18px;\n font-weight: 600;\n color: var(--zest-text);\n}\n\n.zest-modal__description {\n margin: 0;\n font-size: 14px;\n color: var(--zest-text-secondary);\n}\n\n.zest-modal__body {\n padding: 20px;\n}\n\n.zest-modal__footer {\n display: flex;\n flex-wrap: wrap;\n gap: 8px;\n padding: 0 20px 20px;\n}\n\n/* Categories */\n.zest-category {\n padding: 16px;\n margin-bottom: 12px;\n background: var(--zest-bg-secondary);\n border-radius: var(--zest-radius-sm);\n}\n\n.zest-category:last-child {\n margin-bottom: 0;\n}\n\n.zest-category__header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 12px;\n}\n\n.zest-category__info {\n flex: 1;\n}\n\n.zest-category__label {\n display: block;\n font-size: 14px;\n font-weight: 600;\n color: var(--zest-text);\n}\n\n.zest-category__description {\n margin: 4px 0 0;\n font-size: 13px;\n color: var(--zest-text-secondary);\n}\n\n/* Toggle Switch */\n.zest-toggle {\n position: relative;\n width: 44px;\n height: 24px;\n flex-shrink: 0;\n}\n\n.zest-toggle__input {\n position: absolute;\n opacity: 0;\n width: 100%;\n height: 100%;\n cursor: pointer;\n margin: 0;\n}\n\n.zest-toggle__input:disabled {\n cursor: not-allowed;\n}\n\n.zest-toggle__slider {\n position: absolute;\n inset: 0;\n background: var(--zest-border);\n border-radius: 12px;\n transition: background-color 0.2s ease;\n pointer-events: none;\n}\n\n.zest-toggle__slider::before {\n content: '';\n position: absolute;\n top: 2px;\n left: 2px;\n width: 20px;\n height: 20px;\n background: #ffffff;\n border-radius: 50%;\n transition: transform 0.2s ease;\n box-shadow: 0 1px 3px rgba(0, 0, 0, 0.2);\n}\n\n.zest-toggle__input:checked + .zest-toggle__slider {\n background: var(--zest-accent);\n}\n\n.zest-toggle__input:checked + .zest-toggle__slider::before {\n transform: translateX(20px);\n}\n\n.zest-toggle__input:focus-visible + .zest-toggle__slider {\n outline: 2px solid var(--zest-accent);\n outline-offset: 2px;\n}\n\n.zest-toggle__input:disabled + .zest-toggle__slider {\n opacity: 0.6;\n}\n\n/* Widget */\n.zest-widget {\n position: fixed;\n z-index: 999997;\n bottom: 20px;\n left: 20px;\n}\n\n.zest-widget__btn {\n display: flex;\n align-items: center;\n justify-content: center;\n width: 48px;\n height: 48px;\n padding: 0;\n background: var(--zest-bg);\n border: 1px solid var(--zest-border);\n border-radius: 50%;\n box-shadow: var(--zest-shadow);\n cursor: pointer;\n transition: transform 0.2s ease, box-shadow 0.2s ease;\n}\n\n.zest-widget__btn:hover {\n transform: scale(1.05);\n box-shadow: 0 12px 28px -5px rgba(0, 0, 0, 0.15);\n}\n\n.zest-widget__btn:focus-visible {\n outline: 2px solid var(--zest-accent);\n outline-offset: 2px;\n}\n\n.zest-widget__icon {\n width: 24px;\n height: 24px;\n fill: var(--zest-text);\n}\n\n/* Link */\n.zest-link {\n color: var(--zest-accent);\n text-decoration: none;\n}\n\n.zest-link:hover {\n text-decoration: underline;\n}\n\n/* Mobile */\n@media (max-width: 480px) {\n .zest-banner {\n left: 10px;\n right: 10px;\n max-width: none;\n transform: none;\n }\n\n .zest-banner--bottom,\n .zest-banner--bottom-left,\n .zest-banner--bottom-right {\n bottom: 10px;\n }\n\n .zest-banner--top {\n top: 10px;\n transform: none;\n }\n\n @keyframes zest-slide-in {\n from {\n opacity: 0;\n transform: translateY(20px);\n }\n to {\n opacity: 1;\n transform: translateY(0);\n }\n }\n\n .zest-banner__buttons {\n flex-direction: column;\n }\n\n .zest-btn {\n width: 100%;\n }\n\n .zest-modal-overlay {\n padding: 10px;\n }\n\n .zest-widget {\n bottom: 10px;\n left: 10px;\n }\n}\n\n/* Hidden utility */\n.zest-hidden {\n display: none !important;\n}\n${o}\n`}let xt=null,vt=null;const kt=new Set(["bottom","bottom-left","bottom-right","top"]);function _t(t={}){if(xt)return xt;const n=H();xt=document.createElement("zest-banner"),xt.setAttribute("data-theme",n.theme||"light"),vt=xt.attachShadow({mode:"open"});const o=document.createElement("style");o.textContent=wt(n),vt.appendChild(o);const s=document.createElement("div");s.innerHTML=function(t){const n=t.labels.banner,o=t.position||"bottom";return`\n <div class="zest-banner zest-banner--${kt.has(o)?o:"bottom"}" role="dialog" aria-modal="false" aria-label="${e(n.title)}">\n <h2 class="zest-banner__title">${e(n.title)}</h2>\n <p class="zest-banner__description">${e(n.description)}</p>\n <div class="zest-banner__buttons">\n <button type="button" class="zest-btn zest-btn--primary" data-action="accept-all">\n ${e(n.acceptAll)}\n </button>\n <button type="button" class="zest-btn zest-btn--secondary" data-action="reject-all">\n ${e(n.rejectAll)}\n </button>\n <button type="button" class="zest-btn zest-btn--ghost" data-action="settings">\n ${e(n.settings)}\n </button>\n </div>\n </div>\n `}(n),vt.appendChild(s.firstElementChild);const a=vt.querySelector(".zest-banner");return a.addEventListener("click",e=>{const n=e.target.dataset.action;if(n)switch(n){case"accept-all":t.onAcceptAll?.();break;case"reject-all":t.onRejectAll?.();break;case"settings":t.onSettings?.()}}),a.addEventListener("keydown",e=>{"Escape"===e.key&&t.onSettings?.()}),document.body.appendChild(xt),requestAnimationFrame(()=>{const t=vt.querySelector("button");t?.focus()}),xt}function At(t={}){xt?xt.classList.remove("zest-hidden"):_t(t)}function Ct(){xt&&(xt.remove(),xt=null,vt=null)}let St=null,jt=null,Et={};function $t(t,n){const o=t.labels.modal,s=Object.values(t.categories||D).map(t=>function(t,n,o){const s=o?"disabled":"",a=n?"checked":"",r=e(t.id),i=e(t.label);return`\n <div class="zest-category">\n <div class="zest-category__header">\n <div class="zest-category__info">\n <span class="zest-category__label">${i}</span>\n <p class="zest-category__description">${e(t.description)}</p>\n </div>\n <label class="zest-toggle">\n <input\n type="checkbox"\n class="zest-toggle__input"\n data-category="${r}"\n ${a}\n ${s}\n aria-label="${i}"\n >\n <span class="zest-toggle__slider"></span>\n </label>\n </div>\n </div>\n `}(t,n[t.id]??t.default,t.required)).join(""),a=t.policyUrl?function(t){if("string"!=typeof t||0===t.length)return null;const e=t.trim();if(0===e.length)return null;if(/^[/?#]/.test(e))return e;const n=e.match(/^([a-z][a-z0-9+.-]*):/i);if(!n)return e;const o=n[1].toLowerCase();return"http"===o||"https"===o||"mailto"===o||"tel"===o?e:null}(t.policyUrl):null,r=a?`<a href="${e(a)}" class="zest-link" target="_blank" rel="noopener noreferrer">Privacy Policy</a>`:"";return`\n <div class="zest-modal-overlay" role="dialog" aria-modal="true" aria-label="${e(o.title)}">\n <div class="zest-modal">\n <div class="zest-modal__header">\n <h2 class="zest-modal__title">${e(o.title)}</h2>\n <p class="zest-modal__description">${e(o.description)} ${r}</p>\n </div>\n <div class="zest-modal__body">\n ${s}\n </div>\n <div class="zest-modal__footer">\n <button type="button" class="zest-btn zest-btn--primary" data-action="save">\n ${e(o.save)}\n </button>\n <button type="button" class="zest-btn zest-btn--secondary" data-action="accept-all">\n ${e(o.acceptAll)}\n </button>\n <button type="button" class="zest-btn zest-btn--ghost" data-action="reject-all">\n ${e(o.rejectAll)}\n </button>\n </div>\n </div>\n </div>\n `}function Nt(){if(!jt)return Et;const t=jt.querySelectorAll(".zest-toggle__input"),e={essential:!0};return t.forEach(t=>{const n=t.dataset.category;n&&"essential"!==n&&(e[n]=t.checked)}),e}function Lt(){St&&(St.remove(),St=null,jt=null)}let Mt=null,Rt=null;function Tt(t={}){if(Mt)return Mt;const n=H();Mt=document.createElement("zest-widget"),Mt.setAttribute("data-theme",n.theme||"light"),Rt=Mt.attachShadow({mode:"open"});const o=document.createElement("style");o.textContent=wt(n),Rt.appendChild(o);const s=document.createElement("div");s.innerHTML=function(t){const n=e(t.labels.widget.label);return`\n <div class="zest-widget">\n <button type="button" class="zest-widget__btn" aria-label="${n}" title="${n}">\n <span class="zest-widget__icon"><svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M12 2C6.477 2 2 6.477 2 12s4.477 10 10 10 10-4.477 10-10c0-.728-.078-1.437-.225-2.12a1 1 0 0 0-1.482-.63 3 3 0 0 1-4.086-3.72 1 1 0 0 0-.793-1.263A10.05 10.05 0 0 0 12 2zm0 2c.178 0 .354.006.528.017a5 5 0 0 0 5.955 5.955c.011.174.017.35.017.528 0 4.418-3.582 8-8 8s-8-3.582-8-8 3.582-8 8-8zm-4 6a1.5 1.5 0 1 0 0 3 1.5 1.5 0 0 0 0-3zm5 0a1 1 0 1 0 0 2 1 1 0 0 0 0-2zm-2 4a1.5 1.5 0 1 0 0 3 1.5 1.5 0 0 0 0-3z"/></svg></span>\n </button>\n </div>\n `}(n),Rt.appendChild(s.firstElementChild);return Rt.querySelector(".zest-widget__btn").addEventListener("click",()=>{t.onClick?.()}),document.body.appendChild(Mt),Mt}function Dt(t={}){Mt?Mt.style.display="":Tt(t)}function qt(){Mt&&(Mt.style.display="none")}function Ot(){mt();const t=ht();Ct(),Lt(),t?.showWidget&&Dt({onClick:It})}function Pt(){!function(){if(!dt)return null;const t=et(ut.expiration);P(t.current,ut,!1),rt(t.current),it(t.current,t.previous),i(ut.callbacks?.onReject),i(ut.callbacks?.onChange,t.current)}();const t=ht();Ct(),Lt(),t?.showWidget&&Dt({onClick:It})}function Wt(t){!function(t){if(!dt)return null;const e=Q(t,ut.expiration);P(e.current,ut,!1);const n=Object.keys(e.current).filter(t=>e.current[t]&&!e.previous[t]);n.length>0&&ft(n),Object.entries(t||{}).some(([t,e])=>"essential"!==t&&e)?at(e.current,e.previous):rt(e.current),it(e.current,e.previous),i(ut.callbacks?.onChange,e.current)}(t);const e=ht();Lt(),e?.showWidget&&Dt({onClick:It})}function It(){Ct(),qt(),function(t={},e={}){if(St)return St;const n=H();Et={...t},St=document.createElement("zest-modal"),St.setAttribute("data-theme",n.theme||"light"),jt=St.attachShadow({mode:"open"});const o=document.createElement("style");o.textContent=wt(n),jt.appendChild(o);const s=document.createElement("div");s.innerHTML=$t(n,t),jt.appendChild(s.firstElementChild);const a=jt.querySelector(".zest-modal-overlay");a.addEventListener("click",t=>{const n=t.target.dataset.action;if(n)switch(n){case"save":e.onSave?.(Nt());break;case"accept-all":e.onAcceptAll?.();break;case"reject-all":e.onRejectAll?.()}else t.target===a&&e.onClose?.()}),a.addEventListener("keydown",t=>{"Escape"===t.key&&e.onClose?.()}),jt.querySelectorAll(".zest-toggle__input").forEach(t=>{t.addEventListener("change",()=>{Et=Nt()})}),document.body.appendChild(St),requestAnimationFrame(()=>{const t=jt.querySelector("button");t?.focus()})}(V(),{onSave:Wt,onAcceptAll:Ot,onRejectAll:Pt,onClose:Ut}),ct("modal")}function Ut(){Lt(),lt("modal");const t=ht();nt()&&t?.showWidget?Dt({onClick:It}):At({onAcceptAll:Ot,onRejectAll:Pt,onSettings:It})}function Zt(t={}){const{alreadyInitialized:e,hasDecision:n,dntApplied:o}=gt(t);if(e)return console.warn("[Zest] Already initialized"),Yt;const s=ht();return n||o?s?.showWidget&&Dt({onClick:It}):(At({onAcceptAll:Ot,onRejectAll:Pt,onSettings:It}),ct("banner")),Yt}const Yt={init:Zt,show(){yt()?(Lt(),qt(),At({onAcceptAll:Ot,onRejectAll:Pt,onSettings:It}),ct("banner")):console.warn("[Zest] Not initialized. Call Zest.init() first.")},hide(){Ct(),lt("banner")},showSettings(){yt()?It():console.warn("[Zest] Not initialized. Call Zest.init() first.")},hideSettings(){Lt(),lt("modal")},getConsent:V,hasConsent:tt,hasConsentDecision:nt,getConsentProof:function(){try{const t=J().match(RegExp(F+"=([^;]+)"));if(t){return r(JSON.parse(decodeURIComponent(t[1])),q())}}catch(t){}return null},isDoNotTrackEnabled:O,getDNTDetails:function(){if("undefined"==typeof navigator)return{enabled:!1,source:null};const t=navigator.doNotTrack||window.doNotTrack||navigator.msDoNotTrack;return"1"===t||"yes"===t||!0===t?{enabled:!0,source:"dnt"}:!0===navigator.globalPrivacyControl?{enabled:!0,source:"gpc"}:{enabled:!1,source:null}},acceptAll(){yt()?Ot():console.warn("[Zest] Not initialized. Call Zest.init() first.")},rejectAll(){yt()?Pt():console.warn("[Zest] Not initialized. Call Zest.init() first.")},reset(){bt(),Lt(),Mt&&(Mt.remove(),Mt=null,Rt=null),yt()&&(At({onAcceptAll:Ot,onRejectAll:Pt,onSettings:It}),ct("banner"))},getConfig:H,on:function(t,e){return document.addEventListener(t,e),()=>document.removeEventListener(t,e)},once:function(t,e){document.addEventListener(t,e,{once:!0})},EVENTS:ot};if("undefined"!=typeof window){try{Object.defineProperty(window,"Zest",{value:Object.freeze(Yt),writable:!1,configurable:!1,enumerable:!0})}catch(t){window.Zest=Yt}const t=()=>{!1!==Z().autoInit&&Zt(window.ZestConfig)};"loading"===document.readyState?document.addEventListener("DOMContentLoaded",t):t()}return Yt}();
@@ -0,0 +1,211 @@
1
+ /**
2
+ * Type definitions for `@freshjuice/zest/headless`.
3
+ *
4
+ * The headless build ships the consent engine without any UI: no Shadow
5
+ * DOM, no styles, no DOM mounting. You bring your own banner / modal /
6
+ * settings markup and call into Zest for the consent state.
7
+ *
8
+ * @example
9
+ * ```ts
10
+ * import Zest from '@freshjuice/zest/headless';
11
+ *
12
+ * Zest.init({ respectDNT: true, expiration: 365 });
13
+ *
14
+ * if (!Zest.hasConsentDecision()) myBanner.show();
15
+ *
16
+ * acceptBtn.addEventListener('click', () => Zest.acceptAll());
17
+ * rejectBtn.addEventListener('click', () => Zest.rejectAll());
18
+ * ```
19
+ */
20
+
21
+ /** Built-in consent categories. */
22
+ export type ConsentCategory =
23
+ | 'essential'
24
+ | 'functional'
25
+ | 'analytics'
26
+ | 'marketing';
27
+
28
+ /**
29
+ * Per-category boolean consent state. `essential` is always `true` —
30
+ * consent for it cannot be revoked because it covers strictly-necessary
31
+ * processing.
32
+ */
33
+ export type ConsentState =
34
+ & Partial<Record<ConsentCategory, boolean>>
35
+ & { essential: true };
36
+
37
+ /** Snapshot returned by `init()`. */
38
+ export interface InitSnapshot {
39
+ consent: ConsentState;
40
+ hasDecision: boolean;
41
+ dntApplied: boolean;
42
+ }
43
+
44
+ /** Tamper-evident proof of the user's last consent decision. */
45
+ export interface ConsentProof {
46
+ version: string;
47
+ timestamp: number;
48
+ categories: ConsentState;
49
+ }
50
+
51
+ /** Output of `getDNTDetails()`. */
52
+ export interface DNTDetails {
53
+ dnt: boolean;
54
+ gpc: boolean;
55
+ doNotTrack: string | null;
56
+ globalPrivacyControl: boolean;
57
+ }
58
+
59
+ /** Behaviour when DNT / GPC is detected at init time. */
60
+ export type DNTBehavior = 'reject' | 'preselect' | 'ignore';
61
+
62
+ /**
63
+ * Optional consumer callbacks. Each is wrapped in a try/catch internally
64
+ * so a thrown error never breaks the consent pipeline.
65
+ */
66
+ export interface ZestCallbacks {
67
+ onAccept?: (consent: ConsentState) => void;
68
+ onReject?: (consent: ConsentState) => void;
69
+ onChange?: (consent: ConsentState) => void;
70
+ onReady?: (consent: ConsentState) => void;
71
+ }
72
+
73
+ /**
74
+ * Granular toggles for Zest's interceptor layer. Default is `true` on
75
+ * every channel — back-compat with previous versions.
76
+ *
77
+ * Consumers that gate optional scripts and storage themselves (typical
78
+ * for headless integrations) can disable interception per channel and
79
+ * use Zest as a pure consent-state engine.
80
+ */
81
+ export interface InterceptToggles {
82
+ cookies?: boolean;
83
+ storage?: boolean;
84
+ scripts?: boolean;
85
+ }
86
+
87
+ /** Configuration accepted by `init()`. */
88
+ export interface InitOptions {
89
+ /** Respect Do Not Track / Global Privacy Control. Default `true`. */
90
+ respectDNT?: boolean;
91
+ /** What to do when DNT/GPC is on. Default `'reject'`. */
92
+ dntBehavior?: DNTBehavior;
93
+ /** Cookie expiration in days. Default `365`. */
94
+ expiration?: number;
95
+ /** Disable individual interceptors. Default: all on. */
96
+ intercept?: InterceptToggles;
97
+ /**
98
+ * Exact storage / cookie names to treat as strictly-necessary. Each
99
+ * is appended to the essential category as a fully-anchored regex,
100
+ * so the built-in essential patterns (zest_*, csrf*, …) stay intact.
101
+ */
102
+ essentialKeys?: string[];
103
+ /**
104
+ * Regex source strings to treat as strictly-necessary. Validated via
105
+ * safeRegExp, appended (not replaced) to the essential category.
106
+ */
107
+ essentialPatterns?: string[];
108
+ /**
109
+ * Override patterns per category. Note: this REPLACES the category's
110
+ * built-in patterns. Prefer `essentialKeys` / `essentialPatterns` if
111
+ * you only want to add to the essential category.
112
+ */
113
+ patterns?: Partial<Record<ConsentCategory, string[]>>;
114
+ /** Consumer callbacks. */
115
+ callbacks?: ZestCallbacks;
116
+ /** Anything else — Zest tolerates unknown keys at runtime. */
117
+ [key: string]: unknown;
118
+ }
119
+
120
+ /** Event names emitted on the `window` `document.documentElement`. */
121
+ export interface ZestEvents {
122
+ READY: 'zest:ready';
123
+ CONSENT: 'zest:consent';
124
+ REJECT: 'zest:reject';
125
+ CHANGE: 'zest:change';
126
+ SHOW: 'zest:show';
127
+ HIDE: 'zest:hide';
128
+ }
129
+
130
+ export type ZestEventName = ZestEvents[keyof ZestEvents];
131
+
132
+ /** Detail payload of the consent-change event. */
133
+ export interface ZestEventDetail {
134
+ consent: ConsentState;
135
+ previous?: ConsentState;
136
+ }
137
+
138
+ declare const Zest: {
139
+ /** Initialise the consent engine. Must be called before any other API. */
140
+ init(options?: InitOptions): InitSnapshot;
141
+
142
+ /** Current consent state (clone, safe to mutate). */
143
+ getConsent(): ConsentState;
144
+
145
+ /** Has the user granted consent for `category`? */
146
+ hasConsent(category: ConsentCategory): boolean;
147
+
148
+ /** Has the user made any consent decision yet (accept, reject, or
149
+ * partial)? */
150
+ hasConsentDecision(): boolean;
151
+
152
+ /** Tamper-evident snapshot of the last consent decision. */
153
+ getConsentProof(): ConsentProof | null;
154
+
155
+ /** Grant consent for every category. */
156
+ acceptAll(): ConsentState | null;
157
+
158
+ /** Revoke consent for every non-essential category. */
159
+ rejectAll(): ConsentState | null;
160
+
161
+ /** Set per-category consent. Missing keys are left untouched. */
162
+ updateConsent(
163
+ selections: Partial<Record<ConsentCategory, boolean>>
164
+ ): ConsentState | null;
165
+
166
+ /** Wipe all consent state. Useful for "I changed my mind" flows. */
167
+ reset(): void;
168
+
169
+ /** True if the browser is sending DNT or GPC. */
170
+ isDoNotTrackEnabled(): boolean;
171
+
172
+ /** Why `isDoNotTrackEnabled()` returned what it did. */
173
+ getDNTDetails(): DNTDetails;
174
+
175
+ /** Subscribe to a consent event. Returns an unsubscribe function. */
176
+ on(
177
+ eventName: ZestEventName,
178
+ handler: (event: CustomEvent<ZestEventDetail>) => void
179
+ ): () => void;
180
+
181
+ /** Subscribe once; auto-unsubscribes after the first call. */
182
+ once(
183
+ eventName: ZestEventName,
184
+ handler: (event: CustomEvent<ZestEventDetail>) => void
185
+ ): () => void;
186
+
187
+ /** Constants for `on()` / `once()`. */
188
+ EVENTS: ZestEvents;
189
+
190
+ /** Active configuration after `init()`. */
191
+ getConfig(): InitOptions | null;
192
+ };
193
+
194
+ export default Zest;
195
+
196
+ // Named tree-shake-friendly exports.
197
+ export const init: typeof Zest.init;
198
+ export const acceptAll: typeof Zest.acceptAll;
199
+ export const rejectAll: typeof Zest.rejectAll;
200
+ export const updateConsent: typeof Zest.updateConsent;
201
+ export const reset: typeof Zest.reset;
202
+ export const getConsent: typeof Zest.getConsent;
203
+ export const hasConsent: typeof Zest.hasConsent;
204
+ export const hasConsentDecision: typeof Zest.hasConsentDecision;
205
+ export const getConsentProof: typeof Zest.getConsentProof;
206
+ export const isDoNotTrackEnabled: typeof Zest.isDoNotTrackEnabled;
207
+ export const getDNTDetails: typeof Zest.getDNTDetails;
208
+ export const on: typeof Zest.on;
209
+ export const once: typeof Zest.once;
210
+ export const EVENTS: ZestEvents;
211
+ export const getConfig: typeof Zest.getConfig;
@@ -141,10 +141,47 @@ const DEFAULT_PATTERNS = {
141
141
 
142
142
  let patterns = { ...DEFAULT_PATTERNS };
143
143
 
144
+ /** Escape a string so it can be embedded in a regex literal verbatim. */
145
+ function escapeRegex(value) {
146
+ return String(value).replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
147
+ }
148
+
149
+ /**
150
+ * Append patterns to a single category without replacing what's already
151
+ * there. Used by `essentialKeys` and `essentialPatterns` config to extend
152
+ * the strictly-necessary category with consumer-specific entries while
153
+ * keeping the built-in defaults (zest_*, csrf*, xsrf*, etc.).
154
+ *
155
+ * `keys` is an array of exact storage/cookie names; each one is
156
+ * compiled as a fully-anchored regex via `escapeRegex`.
157
+ * `patternStrings` is an array of regex source strings, each validated
158
+ * via `safeRegExp`. Invalid entries are dropped silently.
159
+ */
160
+ function appendPatternsToCategory(category, { keys = [], patternStrings = [] } = {}) {
161
+ if (!patterns[category]) patterns[category] = [];
162
+
163
+ for (const key of keys) {
164
+ if (typeof key !== 'string' || !key) continue;
165
+ const re = safeRegExp(`^${escapeRegex(key)}$`);
166
+ if (re) patterns[category].push(re);
167
+ }
168
+
169
+ for (const p of patternStrings) {
170
+ if (typeof p !== 'string' || !p) continue;
171
+ const re = safeRegExp(p);
172
+ if (re) patterns[category].push(re);
173
+ }
174
+ }
175
+
144
176
  /**
145
177
  * Set custom patterns. User-supplied strings are validated with safeRegExp,
146
178
  * which rejects catastrophic-backtracking shapes and syntax errors.
147
179
  * Invalid patterns are silently dropped with a console warning.
180
+ *
181
+ * Note: this REPLACES the patterns for any category present in
182
+ * `customPatterns`. To extend the essential category without losing the
183
+ * built-in defaults, use `appendPatternsToCategory()` (or pass
184
+ * `essentialKeys` / `essentialPatterns` to `Zest.init()`).
148
185
  */
149
186
  function setPatterns(customPatterns) {
150
187
  patterns = { ...DEFAULT_PATTERNS };
@@ -1663,6 +1700,32 @@ const DEFAULTS = {
1663
1700
  // Blocking mode: 'manual' | 'safe' | 'strict' | 'doomsday'
1664
1701
  mode: 'safe',
1665
1702
 
1703
+ // Interceptor toggles. By default Zest installs cookie + storage
1704
+ // interceptors that route writes through the consent layer. Consumers
1705
+ // who manage gating themselves (typically headless mode with custom
1706
+ // analytics integrations) can opt out per channel.
1707
+ intercept: {
1708
+ cookies: true,
1709
+ storage: true,
1710
+ scripts: true
1711
+ },
1712
+
1713
+ // Strictly-necessary declarations. Both fields *append* to whatever
1714
+ // the essential category already matches via the pattern matcher
1715
+ // defaults — they do not replace.
1716
+ //
1717
+ // - essentialKeys: array of exact storage / cookie names to treat
1718
+ // as strictly-necessary. Easiest case.
1719
+ // - essentialPatterns: array of regex source strings, validated via
1720
+ // safeRegExp. For prefix or family matches.
1721
+ //
1722
+ // Use these instead of `patterns.essential` when you only want to
1723
+ // ADD entries to the essential category without replacing the
1724
+ // built-in patterns (zest_*, csrf*, xsrf*, session*, __host-*,
1725
+ // __secure-*).
1726
+ essentialKeys: [],
1727
+ essentialPatterns: [],
1728
+
1666
1729
  // Custom domains to block (in addition to mode-based blocking)
1667
1730
  blockedDomains: [], // days
1668
1731
 
@@ -1745,6 +1808,28 @@ function mergeConfig(userConfig) {
1745
1808
  config.patterns = userConfig.patterns;
1746
1809
  }
1747
1810
 
1811
+ // Interceptor toggles — shallow-merge so consumers can pass partial
1812
+ // overrides like `intercept: { storage: false }` without losing the
1813
+ // other defaults.
1814
+ if (userConfig.intercept && typeof userConfig.intercept === 'object') {
1815
+ config.intercept = {
1816
+ ...DEFAULTS.intercept,
1817
+ ...userConfig.intercept
1818
+ };
1819
+ }
1820
+
1821
+ // Strictly-necessary declarations
1822
+ if (Array.isArray(userConfig.essentialKeys)) {
1823
+ config.essentialKeys = userConfig.essentialKeys.filter(
1824
+ (k) => typeof k === 'string' && k.length > 0 && k.length <= 200
1825
+ );
1826
+ }
1827
+ if (Array.isArray(userConfig.essentialPatterns)) {
1828
+ config.essentialPatterns = userConfig.essentialPatterns.filter(
1829
+ (p) => typeof p === 'string' && p.length > 0 && p.length <= 500
1830
+ );
1831
+ }
1832
+
1748
1833
  return config;
1749
1834
  }
1750
1835
 
@@ -2084,13 +2169,32 @@ function coreInit(userConfig = {}) {
2084
2169
  setPatterns(currentConfig.patterns);
2085
2170
  }
2086
2171
 
2172
+ // Append consumer-declared strictly-necessary entries on top of
2173
+ // whatever's already in the essential category. This is the friendly
2174
+ // alternative to overriding via `patterns.essential` directly.
2175
+ if (
2176
+ (Array.isArray(currentConfig.essentialKeys) && currentConfig.essentialKeys.length > 0) ||
2177
+ (Array.isArray(currentConfig.essentialPatterns) && currentConfig.essentialPatterns.length > 0)
2178
+ ) {
2179
+ appendPatternsToCategory('essential', {
2180
+ keys: currentConfig.essentialKeys,
2181
+ patternStrings: currentConfig.essentialPatterns
2182
+ });
2183
+ }
2184
+
2087
2185
  setConsentChecker$2(checkConsent);
2088
2186
  setConsentChecker$1(checkConsent);
2089
2187
  setConsentChecker(checkConsent);
2090
2188
 
2091
- interceptCookies();
2092
- interceptStorage();
2093
- startScriptBlocking(currentConfig.mode, currentConfig.blockedDomains);
2189
+ // Interceptor toggles. By default everything is intercepted (back-compat
2190
+ // with v2.0 / v2.1). Consumers that gate scripts and storage themselves
2191
+ // can opt out per channel via `intercept: { storage: false, … }`.
2192
+ const intercept = currentConfig.intercept || { cookies: true, storage: true, scripts: true };
2193
+ if (intercept.cookies !== false) interceptCookies();
2194
+ if (intercept.storage !== false) interceptStorage();
2195
+ if (intercept.scripts !== false) {
2196
+ startScriptBlocking(currentConfig.mode, currentConfig.blockedDomains);
2197
+ }
2094
2198
 
2095
2199
  const consent = loadConsent();
2096
2200
  initialized = true;