@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.
- package/dist/zest.d.ts +247 -0
- package/dist/zest.de.js +107 -3
- package/dist/zest.de.js.map +1 -1
- package/dist/zest.de.min.js +1 -1
- package/dist/zest.en.js +107 -3
- package/dist/zest.en.js.map +1 -1
- package/dist/zest.en.min.js +1 -1
- package/dist/zest.es.js +107 -3
- package/dist/zest.es.js.map +1 -1
- package/dist/zest.es.min.js +1 -1
- package/dist/zest.esm.js +107 -3
- package/dist/zest.esm.js.map +1 -1
- package/dist/zest.esm.min.js +1 -1
- package/dist/zest.fr.js +107 -3
- package/dist/zest.fr.js.map +1 -1
- package/dist/zest.fr.min.js +1 -1
- package/dist/zest.headless.d.ts +211 -0
- package/dist/zest.headless.esm.js +107 -3
- package/dist/zest.headless.esm.js.map +1 -1
- package/dist/zest.headless.esm.min.js +1 -1
- package/dist/zest.it.js +107 -3
- package/dist/zest.it.js.map +1 -1
- package/dist/zest.it.min.js +1 -1
- package/dist/zest.ja.js +107 -3
- package/dist/zest.ja.js.map +1 -1
- package/dist/zest.ja.min.js +1 -1
- package/dist/zest.js +107 -3
- package/dist/zest.js.map +1 -1
- package/dist/zest.min.js +1 -1
- package/dist/zest.nl.js +107 -3
- package/dist/zest.nl.js.map +1 -1
- package/dist/zest.nl.min.js +1 -1
- package/dist/zest.pl.js +107 -3
- package/dist/zest.pl.js.map +1 -1
- package/dist/zest.pl.min.js +1 -1
- package/dist/zest.pt.js +107 -3
- package/dist/zest.pt.js.map +1 -1
- package/dist/zest.pt.min.js +1 -1
- package/dist/zest.ru.js +107 -3
- package/dist/zest.ru.js.map +1 -1
- package/dist/zest.ru.min.js +1 -1
- package/dist/zest.uk.js +107 -3
- package/dist/zest.uk.js.map +1 -1
- package/dist/zest.uk.min.js +1 -1
- package/dist/zest.zh.js +107 -3
- package/dist/zest.zh.js.map +1 -1
- package/dist/zest.zh.min.js +1 -1
- package/package.json +9 -2
- package/src/config/defaults.js +48 -0
- package/src/core/pattern-matcher.js +37 -0
- package/src/core-lifecycle.js +23 -4
- package/src/types/zest.d.ts +247 -0
- package/src/types/zest.headless.d.ts +211 -0
package/dist/zest.zh.min.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
var Zest=function(){"use strict";const t={"&":"&","<":"<",">":">",'"':""","'":"'","`":"`"};function n(n){if(null==n)return"";return("string"==typeof n?n:n+"").replace(/[&<>"'`]/g,n=>t[n])}const e=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 a(t,n){if(t instanceof RegExp)return t;if("string"!=typeof t)return null;if(t.length>500)return null;for(const n of o)if(n.test(t))return null;try{return RegExp(t,n)}catch(t){return null}}const s=new Set(["__proto__","constructor","prototype"]);function r(t,n){if(!t||"object"!=typeof t||Array.isArray(t))return null;const e={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 n)s.has(t)||Object.prototype.hasOwnProperty.call(o,t)&&(e.categories[t]=!0===o[t]);return n.includes("essential")&&(e.categories.essential=!0),e}function i(t,...n){if("function"==typeof t)try{return t(...n)}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[n,e]of Object.entries(l))if(e.some(n=>n.test(t)))return n;return"marketing"}let u=null;const p=[];let f=()=>!1;function g(){return u}let m=null,b=null;const h=[],y=[];let z=()=>!1;function w(t,n,e){return new Proxy(t,{get(t,e){if("setItem"===e)return(e,o)=>{const a=d(e);z(a)?t.setItem(e,o):200>n.length&&n.push({key:e,value:o,category:a,timestamp:Date.now()})};const o=t[e];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,n){let e;try{e=new URL(t)}catch(t){return!1}const o=e.hostname.toLowerCase(),a=e.pathname.toLowerCase();for(const t of n){if("string"!=typeof t)continue;const n=t.toLowerCase();if(n.endsWith(".")){const t=n.slice(0,-1);if(o.split(".").some(n=>n===t)||o.startsWith(n))return!0;continue}const e=n.indexOf("/");if(-1!==e){const t=n.slice(0,e),s=n.slice(e);if((o===t||o.endsWith("."+t))&&a.startsWith(s))return!0;continue}if(o===n||o.endsWith("."+n))return!0}return!1}function _(t,n="safe"){const e="strict"===n?v:x;for(const[n,o]of Object.entries(e))if(k(t,o))return n;return null}const A=new Set(["functional","analytics","marketing"]),C=[];let S=null,j="safe",E=[],$=()=>!1;function L(t){if(t.hasAttribute("data-zest-allow"))return null;const n=t.getAttribute("data-consent-category"),e=n&&A.has(n)?n:null,o=t.src;if(!o)return e;const a=function(t){if(!t||0===E.length)return null;try{const n=new URL(t).hostname.toLowerCase();for(const t of E){const e="string"==typeof t?t:t.domain,o="string"==typeof t?"marketing":t.category||"marketing";if(n===e||n.endsWith("."+e))return o}}catch(t){}return null}(o);let s=null;switch(j){case"manual":break;case"safe":case"strict":s=_(o,j);break;case"doomsday":(function(t){try{const n=new URL(t).hostname,e=window.location.hostname,o=t=>t.replace(/^www\./,"");return o(n)!==o(e)}catch(t){return!1}})(o)&&(s=_(o,"strict")||"marketing")}return s||a||e}function R(t){if(t.hasAttribute("data-zest-processed"))return!1;const n=L(t);if(!n)return t.setAttribute("data-zest-processed","allowed"),!1;if($(n))return t.setAttribute("data-zest-processed","allowed"),!1;const e={category:n,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",n),t.type="text/plain",t.src&&t.removeAttribute("src"),500>C.length&&C.push(e),!0}function M(t){for(const n of t)for(const t of n.addedNodes)if("SCRIPT"!==t.nodeName||t.hasAttribute("data-zest-processed")||R(t),t.querySelectorAll){t.querySelectorAll("script:not([data-zest-processed])").forEach(R)}}function N(t="safe",n=[]){return j=t,E=n,document.querySelectorAll("script:not([data-zest-processed])").forEach(R),S=new MutationObserver(M),S.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 O(){return Object.keys(D)}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 T(t,n,e){const o=e?"default":"update";n.consentModeGoogle&&function(t,n){if(window.dataLayer=window.dataLayer||[],"function"==typeof window.gtag)window.gtag("consent",t,n);else{function e(){window.dataLayer.push(arguments)}e("consent",t,n)}}(o,function(t){const n=t=>t?"granted":"denied";return{ad_storage:n(t.marketing),ad_user_data:n(t.marketing),ad_personalization:n(t.marketing),analytics_storage:n(t.analytics),functionality_storage:"granted",personalization_storage:n(t.functional)}}(t)),n.consentModeMicrosoft&&function(t,n){window.uetq=window.uetq||[],window.uetq.push("consent",t,n)}(o,function(t){return{ad_storage:t.marketing?"granted":"denied"}}(t))}const W={labels:{banner:{title:"我们重视您的隐私",description:'我们使用Cookie来改善您的浏览体验、提供个性化内容并分析我们的流量。点击"全部接受"即表示您同意我们使用Cookie。',acceptAll:"全部接受",rejectAll:"全部拒绝",settings:"设置"},modal:{title:"隐私设置",description:"管理您的Cookie偏好设置。您可以在下方启用或禁用不同类型的Cookie。",save:"保存设置",acceptAll:"全部接受",rejectAll:"全部拒绝"},widget:{label:"Cookie设置"}},categories:{essential:{label:"必要",description:"网站正常运行所必需的。无法禁用。"},functional:{label:"功能性",description:"启用个性化功能,如语言偏好和主题设置。"},analytics:{label:"分析",description:"帮助我们了解访问者如何与网站互动。"},marketing:{label:"营销",description:"用于展示相关广告并衡量营销活动效果。"}}};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",blockedDomains:[],policyUrl:null,imprintUrl:null,callbacks:{onAccept:null,onReject:null,onChange:null,onReady:null}};function Z(t){const n={...I};t||(t={});const e=["lang","position","theme","accentColor","autoInit","showWidget","expiration","policyUrl","imprintUrl","customStyles","mode","blockedDomains","respectDNT","dntBehavior","consentModeGoogle","consentModeMicrosoft"];for(const o of e)void 0!==t[o]&&(n[o]=t[o]);n.lang="zh";const o=W,a=o.labels||{},s=t.labels||{};n.labels={banner:{...I.labels.banner,...a.banner,...s.banner},modal:{...I.labels.modal,...a.modal,...s.modal},widget:{...I.labels.widget,...a.widget,...s.widget}};const r=o.categories||{},i=t.categories||{};n.categories={...I.categories};for(const t of Object.keys(I.categories))n.categories[t]={...I.categories[t],...r[t],...i[t]};return t.callbacks&&(n.callbacks={...I.callbacks,...t.callbacks}),t.patterns&&(n.patterns=t.patterns),n}function U(){const t="undefined"!=typeof window&&window.ZestConfig?window.ZestConfig:{},n=function(){const t=document.currentScript||document.querySelector("script[data-zest]")||document.querySelector('script[src*="zest"]');if(!t)return{};const n={},e=t.getAttribute("data-position");e&&(n.position=e);const o=t.getAttribute("data-theme");o&&(n.theme=o);const a=t.getAttribute("data-accent")||t.getAttribute("data-accent-color");a&&(n.accentColor=a);const s=t.getAttribute("data-policy-url")||t.getAttribute("data-privacy-url");s&&(n.policyUrl=s);const r=t.getAttribute("data-imprint-url");r&&(n.imprintUrl=r);const i=t.getAttribute("data-show-widget");null!==i&&(n.showWidget="false"!==i);const c=t.getAttribute("data-auto-init");null!==c&&(n.autoInit="false"!==c);const l=t.getAttribute("data-expiration");l&&(n.expiration=parseInt(l,10));const d=t.getAttribute("data-consent-mode-google");null!==d&&(n.consentModeGoogle="false"!==d);const u=t.getAttribute("data-consent-mode-microsoft");return null!==u&&(n.consentModeMicrosoft="false"!==u),n}();return Z({...t,...n})}let P=null;function Y(){return P||(P=U()),P}const H="zest_consent";function B(){try{return"undefined"!=typeof location&&"https:"===location.protocol?"; Secure":""}catch(t){return""}}let F=null;function G(t){const n=g();n?.set?n.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 n=r(JSON.parse(decodeURIComponent(t[1])),O());if(n&&n.categories)return F={essential:!0,functional:!1,analytics:!1,marketing:!1,...n.categories},{...F}}}catch(t){}return F={essential:!0,functional:!1,analytics:!1,marketing:!1},{...F}}function V(){return F||(F=X()),{...F}}function K(t,n=365){const e=F?{...F}:{essential:!0,functional:!1,analytics:!1,marketing:!1};return F={essential:!0,functional:!!t.functional,analytics:!!t.analytics,marketing:!!t.marketing},function(t=365){F||(F={essential:!0,functional:!1,analytics:!1,marketing:!1});const n={version:"1.0",timestamp:Date.now(),categories:F},e=new Date(Date.now()+24*t*60*60*1e3).toUTCString();G(`${H}=${encodeURIComponent(JSON.stringify(n))}; expires=${e}; path=/; SameSite=Lax${B()}`)}(n),{current:{...F},previous:e}}function Q(t){return F||(F=X()),!0===F[t]}function tt(t=365){return K({functional:!1,analytics:!1,marketing:!1},t)}function nt(){try{return J().includes(H)}catch(t){return!1}}const et={READY:"zest:ready",CONSENT:"zest:consent",REJECT:"zest:reject",CHANGE:"zest:change",SHOW:"zest:show",HIDE:"zest:hide"};function ot(t,n={}){const e=new CustomEvent(t,{detail:n,bubbles:!0,cancelable:!0});return document.dispatchEvent(e),e}function at(t,n){return ot(et.CONSENT,{consent:t,previous:n})}function st(t){return ot(et.REJECT,{consent:t})}function rt(t,n){return ot(et.CHANGE,{consent:t,previous:n})}function it(t="banner"){return ot(et.SHOW,{type:t})}function ct(t="banner"){return ot(et.HIDE,{type:t})}let lt=!1,dt=null;function ut(t){return Q(t)}function pt(t){!function(t){const n=[];for(const e of p)t.includes(e.category)?u?.set&&u.set.call(document,e.value):n.push(e);p.length=0,p.push(...n)}(t),function(t){const n=[];for(const e of h)t.includes(e.category)?m?.setItem(e.key,e.value):n.push(e);h.length=0,h.push(...n);const e=[];for(const n of y)t.includes(n.category)?b?.setItem(n.key,n.value):e.push(n);y.length=0,y.push(...e)}(t),function(t){const n=[];for(const e of C){if(!t.includes(e.category)){n.push(e);continue}const o=document.createElement("script");e.src?o.src=e.src:e.inline&&(o.textContent=e.inline),e.async&&(o.async=!0),e.defer&&(o.defer=!0),e.type&&"text/plain"!==e.type&&(o.type=e.type),o.setAttribute("data-zest-processed","executed"),o.setAttribute("data-consent-executed","true");const a=e.element;a&&a.isConnected&&a.parentNode?a.parentNode.replaceChild(o,a):document.head.appendChild(o)}C.length=0,C.push(...n)}(t)}function ft(t={}){if(lt)return{alreadyInitialized:!0,config:dt,consent:X(),hasDecision:nt(),dntApplied:!1};P=Z(t),dt=P,T({functional:!1,analytics:!1,marketing:!1},dt,!0),dt.patterns&&function(t){if(l={...c},t&&"object"==typeof t)for(const[n,e]of Object.entries(t)){if(!Array.isArray(e))continue;const t=[];for(const n of e){const e=a(n);if(e)t.push(e);else try{console.warn("[Zest] Rejected unsafe pattern:",n)}catch(t){}}l[n]=t}}(dt.patterns),f=ut,function(t){z=t}(ut),function(t){$=t}(ut),u=Object.getOwnPropertyDescriptor(Document.prototype,"cookie"),u?Object.defineProperty(document,"cookie",{get:()=>u.get.call(document),set(t){const n=function(t){const n=t.match(/^([^=]+)/);return n?n[1].trim():null}(t);if(!n)return;const e=d(n);f(e)?u.set.call(document,t):100>p.length&&p.push({value:t,name:n,category:e,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,h),configurable:!0,writable:!1}),Object.defineProperty(window,"sessionStorage",{value:w(b,y),configurable:!0,writable:!1}),!0}catch(t){return console.warn("[Zest] Could not intercept storage APIs:",t),!1}}(),N(dt.mode,dt.blockedDomains);const n=X();lt=!0,nt()&&T(n,dt,!1);let e=!1;if(q()&&dt.respectDNT&&"ignore"!==dt.dntBehavior&&"reject"===dt.dntBehavior&&!nt()){const t=tt(dt.expiration);e=!0,T(t.current,dt,!1),st(t.current),rt(t.current,t.previous),i(dt.callbacks?.onReject),i(dt.callbacks?.onChange,t.current)}return function(t){ot(et.READY,{consent:t})}(n),i(dt.callbacks?.onReady,n),{alreadyInitialized:!1,config:dt,consent:n,hasDecision:nt(),dntApplied:e}}function gt(){if(!lt)return null;const t=function(t=365){return K({functional:!0,analytics:!0,marketing:!0},t)}(dt.expiration);return T(t.current,dt,!1),pt(O()),at(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${B()}`),F=null}function bt(){return lt}function ht(){return dt}const yt="#4F46E5";function zt(t){const n=function(t){if("string"!=typeof t)return null;const n=t.trim();return/^#[0-9a-fA-F]{3,8}$/.test(n)||e.has(n.toLowerCase())||/^(rgb|rgba|hsl|hsla)\(\s*[\d.,%\s/]+\s*\)$/i.test(n)?n:null}(t.accentColor)||yt,o=function(t){if("string"!=typeof t||0===t.length)return"";if(t.length>2e4)return"";let n=t.replace(/\/\*[\s\S]*?\*\//g,"");return n=n.replace(/@import\s+[^;]+;?/gi,""),n=n.replace(/@charset\s+[^;]+;?/gi,""),n=n.replace(/url\(\s*(['"]?)([^)'"]+)\1\s*\)/gi,(t,n,e)=>{const o=e.trim().toLowerCase();return o.startsWith("https:")||o.startsWith("data:image/")||o.startsWith("/")||o.startsWith("#")?t:"url(#)"}),n=n.replace(/\.zest-btn--secondary\s*\{[^}]*\}/gi,""),n=n.replace(/\[data-action\s*=\s*["']reject-all["']\]\s*\{[^}]*\}/gi,""),n=n.replace(/\[data-action\s*=\s*["']accept-all["']\]\s*\{[^}]*\}/gi,""),n=n.replace(/expression\s*\([^)]*\)/gi,""),n=n.replace(/-moz-binding\s*:[^;}]*/gi,""),n}(t.customStyles);return`\n:host {\n --zest-accent: ${n};\n --zest-accent-hover: ${function(t,n){"string"==typeof t&&/^#[0-9a-fA-F]{3,8}$/.test(t.trim())||(t=yt);let e=t.trim().replace("#","");3===e.length&&(e=e.split("").map(t=>t+t).join(""));8===e.length&&(e=e.slice(0,6));6!==e.length&&(e="4F46E5");const o=parseInt(e,16),a=Math.round(2.55*n);return"#"+(16777216+65536*Math.min(255,Math.max(0,(o>>16)+a))+256*Math.min(255,Math.max(0,(o>>8&255)+a))+Math.min(255,Math.max(0,(255&o)+a))).toString(16).slice(1)}(n,-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 e=Y();wt=document.createElement("zest-banner"),wt.setAttribute("data-theme",e.theme||"light"),xt=wt.attachShadow({mode:"open"});const o=document.createElement("style");o.textContent=zt(e),xt.appendChild(o);const a=document.createElement("div");a.innerHTML=function(t){const e=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="${n(e.title)}">\n <h2 class="zest-banner__title">${n(e.title)}</h2>\n <p class="zest-banner__description">${n(e.description)}</p>\n <div class="zest-banner__buttons">\n <button type="button" class="zest-btn zest-btn--primary" data-action="accept-all">\n ${n(e.acceptAll)}\n </button>\n <button type="button" class="zest-btn zest-btn--secondary" data-action="reject-all">\n ${n(e.rejectAll)}\n </button>\n <button type="button" class="zest-btn zest-btn--ghost" data-action="settings">\n ${n(e.settings)}\n </button>\n </div>\n </div>\n `}(e),xt.appendChild(a.firstElementChild);const s=xt.querySelector(".zest-banner");return s.addEventListener("click",n=>{const e=n.target.dataset.action;if(e)switch(e){case"accept-all":t.onAcceptAll?.();break;case"reject-all":t.onRejectAll?.();break;case"settings":t.onSettings?.()}}),s.addEventListener("keydown",n=>{"Escape"===n.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,e){const o=t.labels.modal,a=Object.values(t.categories||D).map(t=>function(t,e,o){const a=o?"disabled":"",s=e?"checked":"",r=n(t.id),i=n(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">${n(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 ${s}\n ${a}\n aria-label="${i}"\n >\n <span class="zest-toggle__slider"></span>\n </label>\n </div>\n </div>\n `}(t,e[t.id]??t.default,t.required)).join(""),s=t.policyUrl?function(t){if("string"!=typeof t||0===t.length)return null;const n=t.trim();if(0===n.length)return null;if(/^[/?#]/.test(n))return n;const e=n.match(/^([a-z][a-z0-9+.-]*):/i);if(!e)return n;const o=e[1].toLowerCase();return"http"===o||"https"===o||"mailto"===o||"tel"===o?n:null}(t.policyUrl):null,r=s?`<a href="${n(s)}" 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="${n(o.title)}">\n <div class="zest-modal">\n <div class="zest-modal__header">\n <h2 class="zest-modal__title">${n(o.title)}</h2>\n <p class="zest-modal__description">${n(o.description)} ${r}</p>\n </div>\n <div class="zest-modal__body">\n ${a}\n </div>\n <div class="zest-modal__footer">\n <button type="button" class="zest-btn zest-btn--primary" data-action="save">\n ${n(o.save)}\n </button>\n <button type="button" class="zest-btn zest-btn--secondary" data-action="accept-all">\n ${n(o.acceptAll)}\n </button>\n <button type="button" class="zest-btn zest-btn--ghost" data-action="reject-all">\n ${n(o.rejectAll)}\n </button>\n </div>\n </div>\n </div>\n `}function $t(){if(!St)return jt;const t=St.querySelectorAll(".zest-toggle__input"),n={essential:!0};return t.forEach(t=>{const e=t.dataset.category;e&&"essential"!==e&&(n[e]=t.checked)}),n}function Lt(){Ct&&(Ct.remove(),Ct=null,St=null)}let Rt=null,Mt=null;function Nt(t={}){if(Rt)return Rt;const e=Y();Rt=document.createElement("zest-widget"),Rt.setAttribute("data-theme",e.theme||"light"),Mt=Rt.attachShadow({mode:"open"});const o=document.createElement("style");o.textContent=zt(e),Mt.appendChild(o);const a=document.createElement("div");a.innerHTML=function(t){const e=n(t.labels.widget.label);return`\n <div class="zest-widget">\n <button type="button" class="zest-widget__btn" aria-label="${e}" title="${e}">\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 `}(e),Mt.appendChild(a.firstElementChild);return Mt.querySelector(".zest-widget__btn").addEventListener("click",()=>{t.onClick?.()}),document.body.appendChild(Rt),Rt}function Dt(t={}){Rt?Rt.style.display="":Nt(t)}function Ot(){Rt&&(Rt.style.display="none")}function qt(){gt();const t=ht();At(),Lt(),t?.showWidget&&Dt({onClick:It})}function Tt(){!function(){if(!lt)return null;const t=tt(dt.expiration);T(t.current,dt,!1),st(t.current),rt(t.current,t.previous),i(dt.callbacks?.onReject),i(dt.callbacks?.onChange,t.current)}();const t=ht();At(),Lt(),t?.showWidget&&Dt({onClick:It})}function Wt(t){!function(t){if(!lt)return null;const n=K(t,dt.expiration);T(n.current,dt,!1);const e=Object.keys(n.current).filter(t=>n.current[t]&&!n.previous[t]);e.length>0&&pt(e),Object.entries(t||{}).some(([t,n])=>"essential"!==t&&n)?at(n.current,n.previous):st(n.current),rt(n.current,n.previous),i(dt.callbacks?.onChange,n.current)}(t);const n=ht();Lt(),n?.showWidget&&Dt({onClick:It})}function It(){At(),Ot(),function(t={},n={}){if(Ct)return Ct;const e=Y();jt={...t},Ct=document.createElement("zest-modal"),Ct.setAttribute("data-theme",e.theme||"light"),St=Ct.attachShadow({mode:"open"});const o=document.createElement("style");o.textContent=zt(e),St.appendChild(o);const a=document.createElement("div");a.innerHTML=Et(e,t),St.appendChild(a.firstElementChild);const s=St.querySelector(".zest-modal-overlay");s.addEventListener("click",t=>{const e=t.target.dataset.action;if(e)switch(e){case"save":n.onSave?.($t());break;case"accept-all":n.onAcceptAll?.();break;case"reject-all":n.onRejectAll?.()}else t.target===s&&n.onClose?.()}),s.addEventListener("keydown",t=>{"Escape"===t.key&&n.onClose?.()}),St.querySelectorAll(".zest-toggle__input").forEach(t=>{t.addEventListener("change",()=>{jt=$t()})}),document.body.appendChild(Ct),requestAnimationFrame(()=>{const t=St.querySelector("button");t?.focus()})}(V(),{onSave:Wt,onAcceptAll:qt,onRejectAll:Tt,onClose:Zt}),it("modal")}function Zt(){Lt(),ct("modal");const t=ht();nt()&&t?.showWidget?Dt({onClick:It}):_t({onAcceptAll:qt,onRejectAll:Tt,onSettings:It})}function Ut(t={}){const{alreadyInitialized:n,hasDecision:e,dntApplied:o}=ft(t);if(n)return console.warn("[Zest] Already initialized"),Pt;const a=ht();return e||o?a?.showWidget&&Dt({onClick:It}):(_t({onAcceptAll:qt,onRejectAll:Tt,onSettings:It}),it("banner")),Pt}const Pt={init:Ut,show(){bt()?(Lt(),Ot(),_t({onAcceptAll:qt,onRejectAll:Tt,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(){Lt(),ct("modal")},getConsent:V,hasConsent:Q,hasConsentDecision:nt,getConsentProof:function(){try{const t=J().match(RegExp(H+"=([^;]+)"));if(t){return r(JSON.parse(decodeURIComponent(t[1])),O())}}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()?Tt():console.warn("[Zest] Not initialized. Call Zest.init() first.")},reset(){mt(),Lt(),Rt&&(Rt.remove(),Rt=null,Mt=null),bt()&&(_t({onAcceptAll:qt,onRejectAll:Tt,onSettings:It}),it("banner"))},getConfig:Y,on:function(t,n){return document.addEventListener(t,n),()=>document.removeEventListener(t,n)},once:function(t,n){document.addEventListener(t,n,{once:!0})},EVENTS:et};if("undefined"!=typeof window){try{Object.defineProperty(window,"Zest",{value:Object.freeze(Pt),writable:!1,configurable:!1,enumerable:!0})}catch(t){window.Zest=Pt}const t=()=>{!1!==U().autoInit&&Ut(window.ZestConfig)};"loading"===document.readyState?document.addEventListener("DOMContentLoaded",t):t()}return Pt}();
|
|
1
|
+
var Zest=function(){"use strict";const t={"&":"&","<":"<",">":">",'"':""","'":"'","`":"`"};function n(n){if(null==n)return"";return("string"==typeof n?n:n+"").replace(/[&<>"'`]/g,n=>t[n])}const e=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,n){if(t instanceof RegExp)return t;if("string"!=typeof t)return null;if(t.length>500)return null;for(const n of o)if(n.test(t))return null;try{return RegExp(t,n)}catch(t){return null}}const a=new Set(["__proto__","constructor","prototype"]);function r(t,n){if(!t||"object"!=typeof t||Array.isArray(t))return null;const e={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 n)a.has(t)||Object.prototype.hasOwnProperty.call(o,t)&&(e.categories[t]=!0===o[t]);return n.includes("essential")&&(e.categories.essential=!0),e}function i(t,...n){if("function"==typeof t)try{return t(...n)}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[n,e]of Object.entries(l))if(e.some(n=>n.test(t)))return n;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,n,e){return new Proxy(t,{get(t,e){if("setItem"===e)return(e,o)=>{const s=u(e);w(s)?t.setItem(e,o):200>n.length&&n.push({key:e,value:o,category:s,timestamp:Date.now()})};const o=t[e];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,n){let e;try{e=new URL(t)}catch(t){return!1}const o=e.hostname.toLowerCase(),s=e.pathname.toLowerCase();for(const t of n){if("string"!=typeof t)continue;const n=t.toLowerCase();if(n.endsWith(".")){const t=n.slice(0,-1);if(o.split(".").some(n=>n===t)||o.startsWith(n))return!0;continue}const e=n.indexOf("/");if(-1!==e){const t=n.slice(0,e),a=n.slice(e);if((o===t||o.endsWith("."+t))&&s.startsWith(a))return!0;continue}if(o===n||o.endsWith("."+n))return!0}return!1}function A(t,n="safe"){const e="strict"===n?k:v;for(const[n,o]of Object.entries(e))if(_(t,o))return n;return null}const C=new Set(["functional","analytics","marketing"]),S=[];let j=null,E="safe",$=[],L=()=>!1;function R(t){if(t.hasAttribute("data-zest-allow"))return null;const n=t.getAttribute("data-consent-category"),e=n&&C.has(n)?n:null,o=t.src;if(!o)return e;const s=function(t){if(!t||0===$.length)return null;try{const n=new URL(t).hostname.toLowerCase();for(const t of $){const e="string"==typeof t?t:t.domain,o="string"==typeof t?"marketing":t.category||"marketing";if(n===e||n.endsWith("."+e))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 n=new URL(t).hostname,e=window.location.hostname,o=t=>t.replace(/^www\./,"");return o(n)!==o(e)}catch(t){return!1}})(o)&&(a=A(o,"strict")||"marketing")}return a||s||e}function M(t){if(t.hasAttribute("data-zest-processed"))return!1;const n=R(t);if(!n)return t.setAttribute("data-zest-processed","allowed"),!1;if(L(n))return t.setAttribute("data-zest-processed","allowed"),!1;const e={category:n,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",n),t.type="text/plain",t.src&&t.removeAttribute("src"),500>S.length&&S.push(e),!0}function N(t){for(const n of t)for(const t of n.addedNodes)if("SCRIPT"!==t.nodeName||t.hasAttribute("data-zest-processed")||M(t),t.querySelectorAll){t.querySelectorAll("script:not([data-zest-processed])").forEach(M)}}function D(t="safe",n=[]){return E=t,$=n,document.querySelectorAll("script:not([data-zest-processed])").forEach(M),j=new MutationObserver(N),j.observe(document.documentElement,{childList:!0,subtree:!0}),!0}const O={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(O)}function T(){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,n,e){const o=e?"default":"update";n.consentModeGoogle&&function(t,n){if(window.dataLayer=window.dataLayer||[],"function"==typeof window.gtag)window.gtag("consent",t,n);else{function e(){window.dataLayer.push(arguments)}e("consent",t,n)}}(o,function(t){const n=t=>t?"granted":"denied";return{ad_storage:n(t.marketing),ad_user_data:n(t.marketing),ad_personalization:n(t.marketing),analytics_storage:n(t.analytics),functionality_storage:"granted",personalization_storage:n(t.functional)}}(t)),n.consentModeMicrosoft&&function(t,n){window.uetq=window.uetq||[],window.uetq.push("consent",t,n)}(o,function(t){return{ad_storage:t.marketing?"granted":"denied"}}(t))}const W={labels:{banner:{title:"我们重视您的隐私",description:'我们使用Cookie来改善您的浏览体验、提供个性化内容并分析我们的流量。点击"全部接受"即表示您同意我们使用Cookie。',acceptAll:"全部接受",rejectAll:"全部拒绝",settings:"设置"},modal:{title:"隐私设置",description:"管理您的Cookie偏好设置。您可以在下方启用或禁用不同类型的Cookie。",save:"保存设置",acceptAll:"全部接受",rejectAll:"全部拒绝"},widget:{label:"Cookie设置"}},categories:{essential:{label:"必要",description:"网站正常运行所必需的。无法禁用。"},functional:{label:"功能性",description:"启用个性化功能,如语言偏好和主题设置。"},analytics:{label:"分析",description:"帮助我们了解访问者如何与网站互动。"},marketing:{label:"营销",description:"用于展示相关广告并衡量营销活动效果。"}}};const I={lang:"auto",position:"bottom",theme:"auto",accentColor:"#0071e3",categories:O,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 Z(t){const n={...I};t||(t={});const e=["lang","position","theme","accentColor","autoInit","showWidget","expiration","policyUrl","imprintUrl","customStyles","mode","blockedDomains","respectDNT","dntBehavior","consentModeGoogle","consentModeMicrosoft"];for(const o of e)void 0!==t[o]&&(n[o]=t[o]);n.lang="zh";const o=W,s=o.labels||{},a=t.labels||{};n.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||{};n.categories={...I.categories};for(const t of Object.keys(I.categories))n.categories[t]={...I.categories[t],...r[t],...i[t]};return t.callbacks&&(n.callbacks={...I.callbacks,...t.callbacks}),t.patterns&&(n.patterns=t.patterns),t.intercept&&"object"==typeof t.intercept&&(n.intercept={...I.intercept,...t.intercept}),Array.isArray(t.essentialKeys)&&(n.essentialKeys=t.essentialKeys.filter(t=>"string"==typeof t&&t.length>0&&200>=t.length)),Array.isArray(t.essentialPatterns)&&(n.essentialPatterns=t.essentialPatterns.filter(t=>"string"==typeof t&&t.length>0&&500>=t.length)),n}function U(){const t="undefined"!=typeof window&&window.ZestConfig?window.ZestConfig:{},n=function(){const t=document.currentScript||document.querySelector("script[data-zest]")||document.querySelector('script[src*="zest"]');if(!t)return{};const n={},e=t.getAttribute("data-position");e&&(n.position=e);const o=t.getAttribute("data-theme");o&&(n.theme=o);const s=t.getAttribute("data-accent")||t.getAttribute("data-accent-color");s&&(n.accentColor=s);const a=t.getAttribute("data-policy-url")||t.getAttribute("data-privacy-url");a&&(n.policyUrl=a);const r=t.getAttribute("data-imprint-url");r&&(n.imprintUrl=r);const i=t.getAttribute("data-show-widget");null!==i&&(n.showWidget="false"!==i);const c=t.getAttribute("data-auto-init");null!==c&&(n.autoInit="false"!==c);const l=t.getAttribute("data-expiration");l&&(n.expiration=parseInt(l,10));const d=t.getAttribute("data-consent-mode-google");null!==d&&(n.consentModeGoogle="false"!==d);const u=t.getAttribute("data-consent-mode-microsoft");return null!==u&&(n.consentModeMicrosoft="false"!==u),n}();return Z({...t,...n})}let Y=null;function H(){return Y||(Y=U()),Y}const B="zest_consent";function F(){try{return"undefined"!=typeof location&&"https:"===location.protocol?"; Secure":""}catch(t){return""}}let G=null;function K(t){const n=m();n?.set?n.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(B+"=([^;]+)"));if(t){const n=r(JSON.parse(decodeURIComponent(t[1])),q());if(n&&n.categories)return G={essential:!0,functional:!1,analytics:!1,marketing:!1,...n.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,n=365){const e=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 n={version:"1.0",timestamp:Date.now(),categories:G},e=new Date(Date.now()+24*t*60*60*1e3).toUTCString();K(`${B}=${encodeURIComponent(JSON.stringify(n))}; expires=${e}; path=/; SameSite=Lax${F()}`)}(n),{current:{...G},previous:e}}function tt(t){return G||(G=X()),!0===G[t]}function nt(t=365){return Q({functional:!1,analytics:!1,marketing:!1},t)}function et(){try{return J().includes(B)}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,n={}){const e=new CustomEvent(t,{detail:n,bubbles:!0,cancelable:!0});return document.dispatchEvent(e),e}function at(t,n){return st(ot.CONSENT,{consent:t,previous:n})}function rt(t){return st(ot.REJECT,{consent:t})}function it(t,n){return st(ot.CHANGE,{consent:t,previous:n})}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 n=[];for(const e of f)t.includes(e.category)?p?.set&&p.set.call(document,e.value):n.push(e);f.length=0,f.push(...n)}(t),function(t){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);const e=[];for(const n of z)t.includes(n.category)?y?.setItem(n.key,n.value):e.push(n);z.length=0,z.push(...e)}(t),function(t){const n=[];for(const e of S){if(!t.includes(e.category)){n.push(e);continue}const o=document.createElement("script");e.src?o.src=e.src:e.inline&&(o.textContent=e.inline),e.async&&(o.async=!0),e.defer&&(o.defer=!0),e.type&&"text/plain"!==e.type&&(o.type=e.type),o.setAttribute("data-zest-processed","executed"),o.setAttribute("data-consent-executed","true");const s=e.element;s&&s.isConnected&&s.parentNode?s.parentNode.replaceChild(o,s):document.head.appendChild(o)}S.length=0,S.push(...n)}(t)}function gt(t={}){if(dt)return{alreadyInitialized:!0,config:ut,consent:X(),hasDecision:et(),dntApplied:!1};Y=Z(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[n,e]of Object.entries(t)){if(!Array.isArray(e))continue;const t=[];for(const n of e){const e=s(n);if(e)t.push(e);else try{console.warn("[Zest] Rejected unsafe pattern:",n)}catch(t){}}l[n]=t}}(ut.patterns),(Array.isArray(ut.essentialKeys)&&ut.essentialKeys.length>0||Array.isArray(ut.essentialPatterns)&&ut.essentialPatterns.length>0)&&function(t,{keys:n=[],patternStrings:e=[]}={}){l[t]||(l[t]=[]);for(const e of n){if("string"!=typeof e||!e)continue;const n=s(`^${d(e)}$`);n&&l[t].push(n)}for(const n of e){if("string"!=typeof n||!n)continue;const e=s(n);e&&l[t].push(e)}}("essential",{keys:ut.essentialKeys,patternStrings:ut.essentialPatterns}),g=pt,function(t){w=t}(pt),function(t){L=t}(pt);const n=ut.intercept||{cookies:!0,storage:!0,scripts:!0};!1!==n.cookies&&(p=Object.getOwnPropertyDescriptor(Document.prototype,"cookie"),p?Object.defineProperty(document,"cookie",{get:()=>p.get.call(document),set(t){const n=function(t){const n=t.match(/^([^=]+)/);return n?n[1].trim():null}(t);if(!n)return;const e=u(n);g(e)?p.set.call(document,t):100>f.length&&f.push({value:t,name:n,category:e,timestamp:Date.now()})},configurable:!1}):console.warn("[Zest] Could not get cookie descriptor")),!1!==n.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!==n.scripts&&D(ut.mode,ut.blockedDomains);const e=X();dt=!0,et()&&P(e,ut,!1);let o=!1;if(T()&&ut.respectDNT&&"ignore"!==ut.dntBehavior&&"reject"===ut.dntBehavior&&!et()){const t=nt(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})}(e),i(ut.callbacks?.onReady,e),{alreadyInitialized:!1,config:ut,consent:e,hasDecision:et(),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(`${B}=; expires=Thu, 01 Jan 1970 00:00:00 GMT; path=/; SameSite=Lax${F()}`),G=null}function yt(){return dt}function ht(){return ut}const zt="#4F46E5";function wt(t){const n=function(t){if("string"!=typeof t)return null;const n=t.trim();return/^#[0-9a-fA-F]{3,8}$/.test(n)||e.has(n.toLowerCase())||/^(rgb|rgba|hsl|hsla)\(\s*[\d.,%\s/]+\s*\)$/i.test(n)?n:null}(t.accentColor)||zt,o=function(t){if("string"!=typeof t||0===t.length)return"";if(t.length>2e4)return"";let n=t.replace(/\/\*[\s\S]*?\*\//g,"");return n=n.replace(/@import\s+[^;]+;?/gi,""),n=n.replace(/@charset\s+[^;]+;?/gi,""),n=n.replace(/url\(\s*(['"]?)([^)'"]+)\1\s*\)/gi,(t,n,e)=>{const o=e.trim().toLowerCase();return o.startsWith("https:")||o.startsWith("data:image/")||o.startsWith("/")||o.startsWith("#")?t:"url(#)"}),n=n.replace(/\.zest-btn--secondary\s*\{[^}]*\}/gi,""),n=n.replace(/\[data-action\s*=\s*["']reject-all["']\]\s*\{[^}]*\}/gi,""),n=n.replace(/\[data-action\s*=\s*["']accept-all["']\]\s*\{[^}]*\}/gi,""),n=n.replace(/expression\s*\([^)]*\)/gi,""),n=n.replace(/-moz-binding\s*:[^;}]*/gi,""),n}(t.customStyles);return`\n:host {\n --zest-accent: ${n};\n --zest-accent-hover: ${function(t,n){"string"==typeof t&&/^#[0-9a-fA-F]{3,8}$/.test(t.trim())||(t=zt);let e=t.trim().replace("#","");3===e.length&&(e=e.split("").map(t=>t+t).join(""));8===e.length&&(e=e.slice(0,6));6!==e.length&&(e="4F46E5");const o=parseInt(e,16),s=Math.round(2.55*n);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)}(n,-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 e=H();xt=document.createElement("zest-banner"),xt.setAttribute("data-theme",e.theme||"light"),vt=xt.attachShadow({mode:"open"});const o=document.createElement("style");o.textContent=wt(e),vt.appendChild(o);const s=document.createElement("div");s.innerHTML=function(t){const e=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="${n(e.title)}">\n <h2 class="zest-banner__title">${n(e.title)}</h2>\n <p class="zest-banner__description">${n(e.description)}</p>\n <div class="zest-banner__buttons">\n <button type="button" class="zest-btn zest-btn--primary" data-action="accept-all">\n ${n(e.acceptAll)}\n </button>\n <button type="button" class="zest-btn zest-btn--secondary" data-action="reject-all">\n ${n(e.rejectAll)}\n </button>\n <button type="button" class="zest-btn zest-btn--ghost" data-action="settings">\n ${n(e.settings)}\n </button>\n </div>\n </div>\n `}(e),vt.appendChild(s.firstElementChild);const a=vt.querySelector(".zest-banner");return a.addEventListener("click",n=>{const e=n.target.dataset.action;if(e)switch(e){case"accept-all":t.onAcceptAll?.();break;case"reject-all":t.onRejectAll?.();break;case"settings":t.onSettings?.()}}),a.addEventListener("keydown",n=>{"Escape"===n.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,e){const o=t.labels.modal,s=Object.values(t.categories||O).map(t=>function(t,e,o){const s=o?"disabled":"",a=e?"checked":"",r=n(t.id),i=n(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">${n(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,e[t.id]??t.default,t.required)).join(""),a=t.policyUrl?function(t){if("string"!=typeof t||0===t.length)return null;const n=t.trim();if(0===n.length)return null;if(/^[/?#]/.test(n))return n;const e=n.match(/^([a-z][a-z0-9+.-]*):/i);if(!e)return n;const o=e[1].toLowerCase();return"http"===o||"https"===o||"mailto"===o||"tel"===o?n:null}(t.policyUrl):null,r=a?`<a href="${n(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="${n(o.title)}">\n <div class="zest-modal">\n <div class="zest-modal__header">\n <h2 class="zest-modal__title">${n(o.title)}</h2>\n <p class="zest-modal__description">${n(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 ${n(o.save)}\n </button>\n <button type="button" class="zest-btn zest-btn--secondary" data-action="accept-all">\n ${n(o.acceptAll)}\n </button>\n <button type="button" class="zest-btn zest-btn--ghost" data-action="reject-all">\n ${n(o.rejectAll)}\n </button>\n </div>\n </div>\n </div>\n `}function Lt(){if(!jt)return Et;const t=jt.querySelectorAll(".zest-toggle__input"),n={essential:!0};return t.forEach(t=>{const e=t.dataset.category;e&&"essential"!==e&&(n[e]=t.checked)}),n}function Rt(){St&&(St.remove(),St=null,jt=null)}let Mt=null,Nt=null;function Dt(t={}){if(Mt)return Mt;const e=H();Mt=document.createElement("zest-widget"),Mt.setAttribute("data-theme",e.theme||"light"),Nt=Mt.attachShadow({mode:"open"});const o=document.createElement("style");o.textContent=wt(e),Nt.appendChild(o);const s=document.createElement("div");s.innerHTML=function(t){const e=n(t.labels.widget.label);return`\n <div class="zest-widget">\n <button type="button" class="zest-widget__btn" aria-label="${e}" title="${e}">\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 `}(e),Nt.appendChild(s.firstElementChild);return Nt.querySelector(".zest-widget__btn").addEventListener("click",()=>{t.onClick?.()}),document.body.appendChild(Mt),Mt}function Ot(t={}){Mt?Mt.style.display="":Dt(t)}function qt(){Mt&&(Mt.style.display="none")}function Tt(){mt();const t=ht();Ct(),Rt(),t?.showWidget&&Ot({onClick:It})}function Pt(){!function(){if(!dt)return null;const t=nt(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(),Rt(),t?.showWidget&&Ot({onClick:It})}function Wt(t){!function(t){if(!dt)return null;const n=Q(t,ut.expiration);P(n.current,ut,!1);const e=Object.keys(n.current).filter(t=>n.current[t]&&!n.previous[t]);e.length>0&&ft(e),Object.entries(t||{}).some(([t,n])=>"essential"!==t&&n)?at(n.current,n.previous):rt(n.current),it(n.current,n.previous),i(ut.callbacks?.onChange,n.current)}(t);const n=ht();Rt(),n?.showWidget&&Ot({onClick:It})}function It(){Ct(),qt(),function(t={},n={}){if(St)return St;const e=H();Et={...t},St=document.createElement("zest-modal"),St.setAttribute("data-theme",e.theme||"light"),jt=St.attachShadow({mode:"open"});const o=document.createElement("style");o.textContent=wt(e),jt.appendChild(o);const s=document.createElement("div");s.innerHTML=$t(e,t),jt.appendChild(s.firstElementChild);const a=jt.querySelector(".zest-modal-overlay");a.addEventListener("click",t=>{const e=t.target.dataset.action;if(e)switch(e){case"save":n.onSave?.(Lt());break;case"accept-all":n.onAcceptAll?.();break;case"reject-all":n.onRejectAll?.()}else t.target===a&&n.onClose?.()}),a.addEventListener("keydown",t=>{"Escape"===t.key&&n.onClose?.()}),jt.querySelectorAll(".zest-toggle__input").forEach(t=>{t.addEventListener("change",()=>{Et=Lt()})}),document.body.appendChild(St),requestAnimationFrame(()=>{const t=jt.querySelector("button");t?.focus()})}(V(),{onSave:Wt,onAcceptAll:Tt,onRejectAll:Pt,onClose:Zt}),ct("modal")}function Zt(){Rt(),lt("modal");const t=ht();et()&&t?.showWidget?Ot({onClick:It}):At({onAcceptAll:Tt,onRejectAll:Pt,onSettings:It})}function Ut(t={}){const{alreadyInitialized:n,hasDecision:e,dntApplied:o}=gt(t);if(n)return console.warn("[Zest] Already initialized"),Yt;const s=ht();return e||o?s?.showWidget&&Ot({onClick:It}):(At({onAcceptAll:Tt,onRejectAll:Pt,onSettings:It}),ct("banner")),Yt}const Yt={init:Ut,show(){yt()?(Rt(),qt(),At({onAcceptAll:Tt,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(){Rt(),lt("modal")},getConsent:V,hasConsent:tt,hasConsentDecision:et,getConsentProof:function(){try{const t=J().match(RegExp(B+"=([^;]+)"));if(t){return r(JSON.parse(decodeURIComponent(t[1])),q())}}catch(t){}return null},isDoNotTrackEnabled:T,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()?Tt():console.warn("[Zest] Not initialized. Call Zest.init() first.")},rejectAll(){yt()?Pt():console.warn("[Zest] Not initialized. Call Zest.init() first.")},reset(){bt(),Rt(),Mt&&(Mt.remove(),Mt=null,Nt=null),yt()&&(At({onAcceptAll:Tt,onRejectAll:Pt,onSettings:It}),ct("banner"))},getConfig:H,on:function(t,n){return document.addEventListener(t,n),()=>document.removeEventListener(t,n)},once:function(t,n){document.addEventListener(t,n,{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!==U().autoInit&&Ut(window.ZestConfig)};"loading"===document.readyState?document.addEventListener("DOMContentLoaded",t):t()}return Yt}();
|
package/package.json
CHANGED
|
@@ -1,20 +1,26 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@freshjuice/zest",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.2.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Lightweight cookie consent toolkit for GDPR/CCPA compliance",
|
|
6
6
|
"main": "dist/zest.min.js",
|
|
7
7
|
"module": "dist/zest.esm.min.js",
|
|
8
|
+
"types": "dist/zest.d.ts",
|
|
8
9
|
"exports": {
|
|
9
10
|
".": {
|
|
11
|
+
"types": "./dist/zest.d.ts",
|
|
10
12
|
"import": "./dist/zest.esm.min.js",
|
|
11
13
|
"default": "./dist/zest.min.js"
|
|
12
14
|
},
|
|
13
15
|
"./headless": {
|
|
16
|
+
"types": "./dist/zest.headless.d.ts",
|
|
14
17
|
"import": "./dist/zest.headless.esm.min.js",
|
|
15
18
|
"default": "./dist/zest.headless.esm.min.js"
|
|
16
19
|
},
|
|
17
|
-
"./src/headless":
|
|
20
|
+
"./src/headless": {
|
|
21
|
+
"types": "./dist/zest.headless.d.ts",
|
|
22
|
+
"default": "./src/headless.js"
|
|
23
|
+
},
|
|
18
24
|
"./src/*": "./src/*.js"
|
|
19
25
|
},
|
|
20
26
|
"files": [
|
|
@@ -26,6 +32,7 @@
|
|
|
26
32
|
"scripts": {
|
|
27
33
|
"prebuild": "node scripts/build-langs.js",
|
|
28
34
|
"build": "rollup -c",
|
|
35
|
+
"postbuild": "node scripts/copy-types.js",
|
|
29
36
|
"build:watch": "rollup -c -w",
|
|
30
37
|
"dev": "rollup -c -w",
|
|
31
38
|
"test": "vitest",
|
package/src/config/defaults.js
CHANGED
|
@@ -64,6 +64,32 @@ export const DEFAULTS = {
|
|
|
64
64
|
// Blocking mode: 'manual' | 'safe' | 'strict' | 'doomsday'
|
|
65
65
|
mode: 'safe',
|
|
66
66
|
|
|
67
|
+
// Interceptor toggles. By default Zest installs cookie + storage
|
|
68
|
+
// interceptors that route writes through the consent layer. Consumers
|
|
69
|
+
// who manage gating themselves (typically headless mode with custom
|
|
70
|
+
// analytics integrations) can opt out per channel.
|
|
71
|
+
intercept: {
|
|
72
|
+
cookies: true,
|
|
73
|
+
storage: true,
|
|
74
|
+
scripts: true
|
|
75
|
+
},
|
|
76
|
+
|
|
77
|
+
// Strictly-necessary declarations. Both fields *append* to whatever
|
|
78
|
+
// the essential category already matches via the pattern matcher
|
|
79
|
+
// defaults — they do not replace.
|
|
80
|
+
//
|
|
81
|
+
// - essentialKeys: array of exact storage / cookie names to treat
|
|
82
|
+
// as strictly-necessary. Easiest case.
|
|
83
|
+
// - essentialPatterns: array of regex source strings, validated via
|
|
84
|
+
// safeRegExp. For prefix or family matches.
|
|
85
|
+
//
|
|
86
|
+
// Use these instead of `patterns.essential` when you only want to
|
|
87
|
+
// ADD entries to the essential category without replacing the
|
|
88
|
+
// built-in patterns (zest_*, csrf*, xsrf*, session*, __host-*,
|
|
89
|
+
// __secure-*).
|
|
90
|
+
essentialKeys: [],
|
|
91
|
+
essentialPatterns: [],
|
|
92
|
+
|
|
67
93
|
// Custom domains to block (in addition to mode-based blocking)
|
|
68
94
|
blockedDomains: [], // days
|
|
69
95
|
|
|
@@ -146,5 +172,27 @@ export function mergeConfig(userConfig) {
|
|
|
146
172
|
config.patterns = userConfig.patterns;
|
|
147
173
|
}
|
|
148
174
|
|
|
175
|
+
// Interceptor toggles — shallow-merge so consumers can pass partial
|
|
176
|
+
// overrides like `intercept: { storage: false }` without losing the
|
|
177
|
+
// other defaults.
|
|
178
|
+
if (userConfig.intercept && typeof userConfig.intercept === 'object') {
|
|
179
|
+
config.intercept = {
|
|
180
|
+
...DEFAULTS.intercept,
|
|
181
|
+
...userConfig.intercept
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// Strictly-necessary declarations
|
|
186
|
+
if (Array.isArray(userConfig.essentialKeys)) {
|
|
187
|
+
config.essentialKeys = userConfig.essentialKeys.filter(
|
|
188
|
+
(k) => typeof k === 'string' && k.length > 0 && k.length <= 200
|
|
189
|
+
);
|
|
190
|
+
}
|
|
191
|
+
if (Array.isArray(userConfig.essentialPatterns)) {
|
|
192
|
+
config.essentialPatterns = userConfig.essentialPatterns.filter(
|
|
193
|
+
(p) => typeof p === 'string' && p.length > 0 && p.length <= 500
|
|
194
|
+
);
|
|
195
|
+
}
|
|
196
|
+
|
|
149
197
|
return config;
|
|
150
198
|
}
|
|
@@ -51,10 +51,47 @@ export const DEFAULT_PATTERNS = {
|
|
|
51
51
|
|
|
52
52
|
let patterns = { ...DEFAULT_PATTERNS };
|
|
53
53
|
|
|
54
|
+
/** Escape a string so it can be embedded in a regex literal verbatim. */
|
|
55
|
+
function escapeRegex(value) {
|
|
56
|
+
return String(value).replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Append patterns to a single category without replacing what's already
|
|
61
|
+
* there. Used by `essentialKeys` and `essentialPatterns` config to extend
|
|
62
|
+
* the strictly-necessary category with consumer-specific entries while
|
|
63
|
+
* keeping the built-in defaults (zest_*, csrf*, xsrf*, etc.).
|
|
64
|
+
*
|
|
65
|
+
* `keys` is an array of exact storage/cookie names; each one is
|
|
66
|
+
* compiled as a fully-anchored regex via `escapeRegex`.
|
|
67
|
+
* `patternStrings` is an array of regex source strings, each validated
|
|
68
|
+
* via `safeRegExp`. Invalid entries are dropped silently.
|
|
69
|
+
*/
|
|
70
|
+
export function appendPatternsToCategory(category, { keys = [], patternStrings = [] } = {}) {
|
|
71
|
+
if (!patterns[category]) patterns[category] = [];
|
|
72
|
+
|
|
73
|
+
for (const key of keys) {
|
|
74
|
+
if (typeof key !== 'string' || !key) continue;
|
|
75
|
+
const re = safeRegExp(`^${escapeRegex(key)}$`);
|
|
76
|
+
if (re) patterns[category].push(re);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
for (const p of patternStrings) {
|
|
80
|
+
if (typeof p !== 'string' || !p) continue;
|
|
81
|
+
const re = safeRegExp(p);
|
|
82
|
+
if (re) patterns[category].push(re);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
54
86
|
/**
|
|
55
87
|
* Set custom patterns. User-supplied strings are validated with safeRegExp,
|
|
56
88
|
* which rejects catastrophic-backtracking shapes and syntax errors.
|
|
57
89
|
* Invalid patterns are silently dropped with a console warning.
|
|
90
|
+
*
|
|
91
|
+
* Note: this REPLACES the patterns for any category present in
|
|
92
|
+
* `customPatterns`. To extend the essential category without losing the
|
|
93
|
+
* built-in defaults, use `appendPatternsToCategory()` (or pass
|
|
94
|
+
* `essentialKeys` / `essentialPatterns` to `Zest.init()`).
|
|
58
95
|
*/
|
|
59
96
|
export function setPatterns(customPatterns) {
|
|
60
97
|
patterns = { ...DEFAULT_PATTERNS };
|
package/src/core-lifecycle.js
CHANGED
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
import { interceptCookies, setConsentChecker as setCookieChecker, replayCookies } from './core/cookie-interceptor.js';
|
|
12
12
|
import { interceptStorage, setConsentChecker as setStorageChecker, replayStorage } from './core/storage-interceptor.js';
|
|
13
13
|
import { startScriptBlocking, setConsentChecker as setScriptChecker, replayScripts } from './core/script-blocker.js';
|
|
14
|
-
import { setPatterns } from './core/pattern-matcher.js';
|
|
14
|
+
import { setPatterns, appendPatternsToCategory } from './core/pattern-matcher.js';
|
|
15
15
|
import { getCategoryIds } from './core/categories.js';
|
|
16
16
|
import { isDoNotTrackEnabled } from './core/dnt.js';
|
|
17
17
|
import { safeInvoke } from './core/security.js';
|
|
@@ -73,13 +73,32 @@ export function coreInit(userConfig = {}) {
|
|
|
73
73
|
setPatterns(currentConfig.patterns);
|
|
74
74
|
}
|
|
75
75
|
|
|
76
|
+
// Append consumer-declared strictly-necessary entries on top of
|
|
77
|
+
// whatever's already in the essential category. This is the friendly
|
|
78
|
+
// alternative to overriding via `patterns.essential` directly.
|
|
79
|
+
if (
|
|
80
|
+
(Array.isArray(currentConfig.essentialKeys) && currentConfig.essentialKeys.length > 0) ||
|
|
81
|
+
(Array.isArray(currentConfig.essentialPatterns) && currentConfig.essentialPatterns.length > 0)
|
|
82
|
+
) {
|
|
83
|
+
appendPatternsToCategory('essential', {
|
|
84
|
+
keys: currentConfig.essentialKeys,
|
|
85
|
+
patternStrings: currentConfig.essentialPatterns
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
|
|
76
89
|
setCookieChecker(checkConsent);
|
|
77
90
|
setStorageChecker(checkConsent);
|
|
78
91
|
setScriptChecker(checkConsent);
|
|
79
92
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
93
|
+
// Interceptor toggles. By default everything is intercepted (back-compat
|
|
94
|
+
// with v2.0 / v2.1). Consumers that gate scripts and storage themselves
|
|
95
|
+
// can opt out per channel via `intercept: { storage: false, … }`.
|
|
96
|
+
const intercept = currentConfig.intercept || { cookies: true, storage: true, scripts: true };
|
|
97
|
+
if (intercept.cookies !== false) interceptCookies();
|
|
98
|
+
if (intercept.storage !== false) interceptStorage();
|
|
99
|
+
if (intercept.scripts !== false) {
|
|
100
|
+
startScriptBlocking(currentConfig.mode, currentConfig.blockedDomains);
|
|
101
|
+
}
|
|
83
102
|
|
|
84
103
|
const consent = loadConsent();
|
|
85
104
|
initialized = true;
|
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Type definitions for `@freshjuice/zest` (full build with UI).
|
|
3
|
+
*
|
|
4
|
+
* The full build ships the consent engine plus a Shadow-DOM banner,
|
|
5
|
+
* settings modal, and floating widget. It auto-initialises on script
|
|
6
|
+
* load when included via `<script>`, or you can drive it manually via
|
|
7
|
+
* `Zest.init()`.
|
|
8
|
+
*
|
|
9
|
+
* For a logic-only build without UI, import from
|
|
10
|
+
* `@freshjuice/zest/headless` instead.
|
|
11
|
+
*
|
|
12
|
+
* @example
|
|
13
|
+
* ```ts
|
|
14
|
+
* import Zest from '@freshjuice/zest';
|
|
15
|
+
*
|
|
16
|
+
* Zest.init({
|
|
17
|
+
* position: 'bottom-right',
|
|
18
|
+
* theme: 'auto',
|
|
19
|
+
* accentColor: '#0071e3',
|
|
20
|
+
* policyUrl: '/privacy'
|
|
21
|
+
* });
|
|
22
|
+
* ```
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
/** Built-in consent categories. */
|
|
26
|
+
export type ConsentCategory =
|
|
27
|
+
| 'essential'
|
|
28
|
+
| 'functional'
|
|
29
|
+
| 'analytics'
|
|
30
|
+
| 'marketing';
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Per-category boolean consent state. `essential` is always `true` —
|
|
34
|
+
* consent for it cannot be revoked because it covers strictly-necessary
|
|
35
|
+
* processing.
|
|
36
|
+
*/
|
|
37
|
+
export type ConsentState =
|
|
38
|
+
& Partial<Record<ConsentCategory, boolean>>
|
|
39
|
+
& { essential: true };
|
|
40
|
+
|
|
41
|
+
/** Snapshot returned by `init()`. */
|
|
42
|
+
export interface InitSnapshot {
|
|
43
|
+
consent: ConsentState;
|
|
44
|
+
hasDecision: boolean;
|
|
45
|
+
dntApplied: boolean;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/** Tamper-evident proof of the user's last consent decision. */
|
|
49
|
+
export interface ConsentProof {
|
|
50
|
+
version: string;
|
|
51
|
+
timestamp: number;
|
|
52
|
+
categories: ConsentState;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/** Output of `getDNTDetails()`. */
|
|
56
|
+
export interface DNTDetails {
|
|
57
|
+
dnt: boolean;
|
|
58
|
+
gpc: boolean;
|
|
59
|
+
doNotTrack: string | null;
|
|
60
|
+
globalPrivacyControl: boolean;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/** Behaviour when DNT / GPC is detected at init time. */
|
|
64
|
+
export type DNTBehavior = 'reject' | 'preselect' | 'ignore';
|
|
65
|
+
|
|
66
|
+
/** Banner position on the page. */
|
|
67
|
+
export type BannerPosition = 'bottom' | 'bottom-left' | 'bottom-right' | 'top';
|
|
68
|
+
|
|
69
|
+
/** UI theme. `auto` follows `prefers-color-scheme`. */
|
|
70
|
+
export type ZestTheme = 'light' | 'dark' | 'auto';
|
|
71
|
+
|
|
72
|
+
/** Script-blocking strictness. */
|
|
73
|
+
export type ZestMode = 'manual' | 'safe' | 'strict' | 'doomsday';
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Optional consumer callbacks. Each is wrapped in a try/catch internally
|
|
77
|
+
* so a thrown error never breaks the consent pipeline.
|
|
78
|
+
*/
|
|
79
|
+
export interface ZestCallbacks {
|
|
80
|
+
onAccept?: (consent: ConsentState) => void;
|
|
81
|
+
onReject?: (consent: ConsentState) => void;
|
|
82
|
+
onChange?: (consent: ConsentState) => void;
|
|
83
|
+
onReady?: (consent: ConsentState) => void;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Granular toggles for Zest's interceptor layer. Default is `true` on
|
|
88
|
+
* every channel — back-compat with previous versions.
|
|
89
|
+
*
|
|
90
|
+
* Consumers that gate optional scripts and storage themselves can
|
|
91
|
+
* disable interception per channel and use Zest as a pure consent-state
|
|
92
|
+
* engine.
|
|
93
|
+
*/
|
|
94
|
+
export interface InterceptToggles {
|
|
95
|
+
cookies?: boolean;
|
|
96
|
+
storage?: boolean;
|
|
97
|
+
scripts?: boolean;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/** Configuration accepted by `init()` and `window.ZestConfig`. */
|
|
101
|
+
export interface InitOptions {
|
|
102
|
+
/** Display language. `'auto'` detects from `<html lang>` / browser. */
|
|
103
|
+
lang?:
|
|
104
|
+
| 'auto'
|
|
105
|
+
| 'en' | 'de' | 'es' | 'fr' | 'it' | 'pt'
|
|
106
|
+
| 'nl' | 'pl' | 'uk' | 'ru' | 'ja' | 'zh';
|
|
107
|
+
/** Banner position. Default `'bottom'`. */
|
|
108
|
+
position?: BannerPosition;
|
|
109
|
+
/** UI theme. Default `'auto'`. */
|
|
110
|
+
theme?: ZestTheme;
|
|
111
|
+
/** Hex accent color for buttons (e.g. `'#0071e3'`). */
|
|
112
|
+
accentColor?: string;
|
|
113
|
+
/** Link to the host site's privacy policy. */
|
|
114
|
+
policyUrl?: string;
|
|
115
|
+
/** Show floating "manage cookies" widget after a decision. Default `true`. */
|
|
116
|
+
showWidget?: boolean;
|
|
117
|
+
/** Cookie expiration in days. Default `365`. */
|
|
118
|
+
expiration?: number;
|
|
119
|
+
/** Script-blocking mode. Default `'safe'`. */
|
|
120
|
+
mode?: ZestMode;
|
|
121
|
+
/** Auto-initialise on script load. Default `true` for the UI build. */
|
|
122
|
+
autoInit?: boolean;
|
|
123
|
+
/** Respect Do Not Track / Global Privacy Control. Default `true`. */
|
|
124
|
+
respectDNT?: boolean;
|
|
125
|
+
/** What to do when DNT/GPC is on. Default `'reject'`. */
|
|
126
|
+
dntBehavior?: DNTBehavior;
|
|
127
|
+
/** Disable individual interceptors. Default: all on. */
|
|
128
|
+
intercept?: InterceptToggles;
|
|
129
|
+
/**
|
|
130
|
+
* Exact storage / cookie names to treat as strictly-necessary. Each
|
|
131
|
+
* is appended to the essential category as a fully-anchored regex,
|
|
132
|
+
* so the built-in essential patterns (zest_*, csrf*, …) stay intact.
|
|
133
|
+
*/
|
|
134
|
+
essentialKeys?: string[];
|
|
135
|
+
/**
|
|
136
|
+
* Regex source strings to treat as strictly-necessary. Validated via
|
|
137
|
+
* safeRegExp, appended (not replaced) to the essential category.
|
|
138
|
+
*/
|
|
139
|
+
essentialPatterns?: string[];
|
|
140
|
+
/**
|
|
141
|
+
* Override patterns per category. Note: this REPLACES the category's
|
|
142
|
+
* built-in patterns. Prefer `essentialKeys` / `essentialPatterns` if
|
|
143
|
+
* you only want to add to the essential category.
|
|
144
|
+
*/
|
|
145
|
+
patterns?: Partial<Record<ConsentCategory, string[]>>;
|
|
146
|
+
/** Consumer callbacks. */
|
|
147
|
+
callbacks?: ZestCallbacks;
|
|
148
|
+
/** Anything else — Zest tolerates unknown keys at runtime. */
|
|
149
|
+
[key: string]: unknown;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/** Event names emitted on `document.documentElement`. */
|
|
153
|
+
export interface ZestEvents {
|
|
154
|
+
READY: 'zest:ready';
|
|
155
|
+
CONSENT: 'zest:consent';
|
|
156
|
+
REJECT: 'zest:reject';
|
|
157
|
+
CHANGE: 'zest:change';
|
|
158
|
+
SHOW: 'zest:show';
|
|
159
|
+
HIDE: 'zest:hide';
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
export type ZestEventName = ZestEvents[keyof ZestEvents];
|
|
163
|
+
|
|
164
|
+
/** Detail payload of consent events. */
|
|
165
|
+
export interface ZestEventDetail {
|
|
166
|
+
consent: ConsentState;
|
|
167
|
+
previous?: ConsentState;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
declare const Zest: {
|
|
171
|
+
/** Initialise. Auto-called when the script loads unless `autoInit: false`. */
|
|
172
|
+
init(options?: InitOptions): InitSnapshot;
|
|
173
|
+
|
|
174
|
+
/** Show the consent banner. */
|
|
175
|
+
show(): void;
|
|
176
|
+
|
|
177
|
+
/** Hide the consent banner. */
|
|
178
|
+
hide(): void;
|
|
179
|
+
|
|
180
|
+
/** Open the per-category settings modal. */
|
|
181
|
+
showSettings(): void;
|
|
182
|
+
|
|
183
|
+
/** Close the settings modal. */
|
|
184
|
+
hideSettings(): void;
|
|
185
|
+
|
|
186
|
+
/** Show the persistent "manage cookies" widget. */
|
|
187
|
+
showWidget(): void;
|
|
188
|
+
|
|
189
|
+
/** Hide the widget without removing it. */
|
|
190
|
+
hideWidget(): void;
|
|
191
|
+
|
|
192
|
+
/** Current consent state (clone, safe to mutate). */
|
|
193
|
+
getConsent(): ConsentState;
|
|
194
|
+
|
|
195
|
+
/** Has the user granted consent for `category`? */
|
|
196
|
+
hasConsent(category: ConsentCategory): boolean;
|
|
197
|
+
|
|
198
|
+
/** Has the user made any consent decision yet? */
|
|
199
|
+
hasConsentDecision(): boolean;
|
|
200
|
+
|
|
201
|
+
/** Tamper-evident snapshot of the last consent decision. */
|
|
202
|
+
getConsentProof(): ConsentProof | null;
|
|
203
|
+
|
|
204
|
+
/** Grant consent for every category and run accept callbacks. */
|
|
205
|
+
acceptAll(): void;
|
|
206
|
+
|
|
207
|
+
/** Revoke consent for every non-essential category and run reject callbacks. */
|
|
208
|
+
rejectAll(): void;
|
|
209
|
+
|
|
210
|
+
/** Wipe all consent state and reshow the banner. */
|
|
211
|
+
reset(): void;
|
|
212
|
+
|
|
213
|
+
/** True if the browser is sending DNT or GPC. */
|
|
214
|
+
isDoNotTrackEnabled(): boolean;
|
|
215
|
+
|
|
216
|
+
/** Why `isDoNotTrackEnabled()` returned what it did. */
|
|
217
|
+
getDNTDetails(): DNTDetails;
|
|
218
|
+
|
|
219
|
+
/** Subscribe to a consent event. Returns an unsubscribe function. */
|
|
220
|
+
on(
|
|
221
|
+
eventName: ZestEventName,
|
|
222
|
+
handler: (event: CustomEvent<ZestEventDetail>) => void
|
|
223
|
+
): () => void;
|
|
224
|
+
|
|
225
|
+
/** Subscribe once; auto-unsubscribes after the first call. */
|
|
226
|
+
once(
|
|
227
|
+
eventName: ZestEventName,
|
|
228
|
+
handler: (event: CustomEvent<ZestEventDetail>) => void
|
|
229
|
+
): () => void;
|
|
230
|
+
|
|
231
|
+
/** Constants for `on()` / `once()`. */
|
|
232
|
+
EVENTS: ZestEvents;
|
|
233
|
+
|
|
234
|
+
/** Active configuration after `init()`. */
|
|
235
|
+
getConfig(): InitOptions | null;
|
|
236
|
+
};
|
|
237
|
+
|
|
238
|
+
export default Zest;
|
|
239
|
+
|
|
240
|
+
declare global {
|
|
241
|
+
interface Window {
|
|
242
|
+
/** Set before loading `zest.min.js` to configure auto-initialisation. */
|
|
243
|
+
ZestConfig?: InitOptions;
|
|
244
|
+
/** The Zest singleton, attached after auto-init. */
|
|
245
|
+
Zest?: typeof Zest;
|
|
246
|
+
}
|
|
247
|
+
}
|