@hifilabs/pixel 0.3.0 → 0.6.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/browser.js CHANGED
@@ -1,4 +1,11 @@
1
1
  var BalancePixel = (() => {
2
+ var __defProp = Object.defineProperty;
3
+ var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
4
+ var __publicField = (obj, key, value) => {
5
+ __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
6
+ return value;
7
+ };
8
+
2
9
  // src/browser.ts
3
10
  (function() {
4
11
  function parseUserAgent(ua) {
@@ -46,13 +53,26 @@ var BalancePixel = (() => {
46
53
  }
47
54
  const currentScript = document.currentScript;
48
55
  const artistId = currentScript?.dataset.artistId;
49
- const projectId = currentScript?.dataset.projectId;
56
+ const rawProjectId = currentScript?.dataset.projectId;
57
+ const projectId = rawProjectId ? normalizeProjectId(rawProjectId) : void 0;
58
+ function normalizeProjectId(id) {
59
+ if (!id)
60
+ return id;
61
+ if (id.startsWith("release_") || id.startsWith("merch_") || id.startsWith("link_") || id.startsWith("custom_")) {
62
+ return id;
63
+ }
64
+ return `custom_${id}`;
65
+ }
50
66
  const useEmulator = currentScript?.dataset.emulator === "true";
51
67
  const debug = currentScript?.dataset.debug === "true";
52
68
  const heartbeatInterval = parseInt(currentScript?.dataset.heartbeatInterval || "30000", 10);
53
69
  const heartbeatEnabled = currentScript?.dataset.heartbeat !== "false";
54
70
  const explicitSource = currentScript?.dataset.source;
55
71
  const customEndpoint = currentScript?.dataset.endpoint;
72
+ const consentUIEnabled = currentScript?.dataset.consentUi === "true";
73
+ const consentStyle = currentScript?.dataset.consentStyle || "brutalist";
74
+ const consentPrimaryColor = currentScript?.dataset.primaryColor;
75
+ const consentBannerPosition = currentScript?.dataset.bannerPosition || "bottom";
56
76
  function detectTrackingSource() {
57
77
  if (explicitSource)
58
78
  return explicitSource;
@@ -72,11 +92,13 @@ var BalancePixel = (() => {
72
92
  const SESSION_TIMESTAMP_KEY = "session_timestamp";
73
93
  const ATTRIBUTION_KEY = "attribution";
74
94
  const FAN_ID_KEY = "fan_id_hash";
95
+ const VISITOR_ID_KEY = "visitor_id";
75
96
  const CONSENT_STORAGE_KEY = "balance_consent";
76
97
  const SESSION_DURATION = 60 * 60 * 1e3;
77
98
  const STORAGE_PREFIX = "balance_";
78
99
  const API_ENDPOINT = customEndpoint ? customEndpoint : useEmulator ? `http://localhost:5001/artist-os-distro/us-central1/pixelEndpoint` : `https://us-central1-artist-os-distro.cloudfunctions.net/pixelEndpoint`;
79
100
  let sessionId = null;
101
+ let visitorId = null;
80
102
  let fanIdHash = null;
81
103
  let consent = null;
82
104
  let attribution = {};
@@ -95,6 +117,169 @@ var BalancePixel = (() => {
95
117
  if (debug)
96
118
  console.log("[BALANCE Pixel]", ...args);
97
119
  };
120
+ const CONSENT_STYLES = {
121
+ base: `
122
+ :host {
123
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
124
+ position: fixed;
125
+ left: 0;
126
+ right: 0;
127
+ z-index: 2147483647;
128
+ }
129
+ .banner {
130
+ padding: 16px 24px;
131
+ display: flex;
132
+ gap: 24px;
133
+ align-items: center;
134
+ justify-content: space-between;
135
+ flex-wrap: wrap;
136
+ }
137
+ .text {
138
+ font-size: 14px;
139
+ line-height: 1.5;
140
+ flex: 1;
141
+ min-width: 280px;
142
+ }
143
+ .text strong {
144
+ text-transform: uppercase;
145
+ letter-spacing: 0.5px;
146
+ }
147
+ .actions {
148
+ display: flex;
149
+ gap: 12px;
150
+ flex-shrink: 0;
151
+ }
152
+ .btn {
153
+ padding: 10px 20px;
154
+ font-size: 14px;
155
+ font-weight: 600;
156
+ cursor: pointer;
157
+ transition: all 0.15s ease;
158
+ }
159
+ @media (max-width: 600px) {
160
+ .banner { flex-direction: column; text-align: center; }
161
+ .actions { width: 100%; justify-content: center; }
162
+ }
163
+ `,
164
+ brutalist: `
165
+ .banner {
166
+ background: #000;
167
+ color: #00ff00;
168
+ border-top: 4px solid #00ff00;
169
+ font-family: 'Courier New', monospace;
170
+ text-transform: uppercase;
171
+ }
172
+ .btn {
173
+ border: 2px solid #00ff00;
174
+ background: transparent;
175
+ color: #00ff00;
176
+ text-transform: uppercase;
177
+ letter-spacing: 1px;
178
+ }
179
+ .btn:hover { background: #00ff00; color: #000; }
180
+ .btn.decline { border-color: #ff0000; color: #ff0000; }
181
+ .btn.decline:hover { background: #ff0000; color: #000; }
182
+ `,
183
+ default: `
184
+ .banner {
185
+ background: #fff;
186
+ color: #000;
187
+ box-shadow: 0 -2px 10px rgba(0,0,0,0.1);
188
+ border-top: 1px solid #e0e0e0;
189
+ }
190
+ .btn {
191
+ border: 1px solid #000;
192
+ background: transparent;
193
+ color: #000;
194
+ border-radius: 4px;
195
+ }
196
+ .btn.accept { background: #000; color: #fff; }
197
+ .btn:hover { opacity: 0.8; }
198
+ `,
199
+ minimal: `
200
+ .banner { background: #1a1a1a; color: #e0e0e0; }
201
+ .btn {
202
+ border: 1px solid #e0e0e0;
203
+ background: transparent;
204
+ color: #e0e0e0;
205
+ border-radius: 4px;
206
+ }
207
+ .btn.accept { background: #fff; color: #000; border-color: #fff; }
208
+ .btn:hover { opacity: 0.8; }
209
+ `
210
+ };
211
+ class ConsentManager {
212
+ constructor(config) {
213
+ __publicField(this, "container", null);
214
+ __publicField(this, "shadow", null);
215
+ __publicField(this, "config");
216
+ this.config = config;
217
+ if (this.hasStoredConsent()) {
218
+ log("ConsentManager: Consent already exists, not showing banner");
219
+ return;
220
+ }
221
+ this.container = document.createElement("div");
222
+ this.container.id = "balance-consent-manager";
223
+ this.shadow = this.container.attachShadow({ mode: "closed" });
224
+ this.render();
225
+ document.body.appendChild(this.container);
226
+ log("ConsentManager: Banner rendered");
227
+ }
228
+ hasStoredConsent() {
229
+ try {
230
+ return localStorage.getItem(CONSENT_STORAGE_KEY) !== null;
231
+ } catch {
232
+ return false;
233
+ }
234
+ }
235
+ render() {
236
+ if (!this.shadow)
237
+ return;
238
+ const styleKey = this.config.style || "brutalist";
239
+ const baseStyles = CONSENT_STYLES.base;
240
+ const themeStyles = CONSENT_STYLES[styleKey] || CONSENT_STYLES.brutalist;
241
+ const positionStyle = this.config.position === "top" ? "top: 0;" : "bottom: 0;";
242
+ const customColor = this.config.primaryColor ? `.btn.accept { background: ${this.config.primaryColor} !important; border-color: ${this.config.primaryColor} !important; color: #fff !important; }` : "";
243
+ this.shadow.innerHTML = `
244
+ <style>
245
+ ${baseStyles}
246
+ :host { ${positionStyle} }
247
+ ${themeStyles}
248
+ ${customColor}
249
+ </style>
250
+ <div class="banner" role="dialog" aria-label="Cookie consent" aria-modal="false">
251
+ <p class="text">
252
+ <strong>Privacy Notice:</strong> We use data to support the artist and improve your experience.
253
+ </p>
254
+ <div class="actions">
255
+ <button id="decline" class="btn decline">Decline</button>
256
+ <button id="accept" class="btn accept">Accept</button>
257
+ </div>
258
+ </div>
259
+ `;
260
+ this.shadow.getElementById("accept")?.addEventListener("click", () => this.handleConsent(true));
261
+ this.shadow.getElementById("decline")?.addEventListener("click", () => this.handleConsent(false));
262
+ }
263
+ handleConsent(accepted) {
264
+ if (window.balance?.setConsent) {
265
+ window.balance.setConsent({
266
+ analytics: accepted,
267
+ marketing: accepted,
268
+ personalization: accepted,
269
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
270
+ });
271
+ }
272
+ this.remove();
273
+ }
274
+ remove() {
275
+ if (this.container) {
276
+ this.container.remove();
277
+ this.container = null;
278
+ this.shadow = null;
279
+ log("ConsentManager: Banner removed");
280
+ }
281
+ }
282
+ }
98
283
  function getStorage() {
99
284
  try {
100
285
  return currentStorageTier === "local" ? localStorage : sessionStorage;
@@ -218,6 +403,36 @@ var BalancePixel = (() => {
218
403
  } catch (e) {
219
404
  }
220
405
  }
406
+ function getOrCreateVisitorId() {
407
+ if (!consent?.analytics) {
408
+ return null;
409
+ }
410
+ try {
411
+ const stored = localStorage.getItem(STORAGE_PREFIX + VISITOR_ID_KEY);
412
+ if (stored) {
413
+ return stored;
414
+ }
415
+ const newId = generateUUID();
416
+ localStorage.setItem(STORAGE_PREFIX + VISITOR_ID_KEY, newId);
417
+ log("Created persistent visitor ID:", newId.substring(0, 8) + "...");
418
+ return newId;
419
+ } catch (e) {
420
+ return null;
421
+ }
422
+ }
423
+ function loadVisitorId() {
424
+ if (!consent?.analytics) {
425
+ visitorId = null;
426
+ return;
427
+ }
428
+ try {
429
+ visitorId = localStorage.getItem(STORAGE_PREFIX + VISITOR_ID_KEY);
430
+ if (visitorId) {
431
+ log("Loaded visitor ID:", visitorId.substring(0, 8) + "...");
432
+ }
433
+ } catch (e) {
434
+ }
435
+ }
221
436
  function loadConsent() {
222
437
  try {
223
438
  const stored = localStorage.getItem(CONSENT_STORAGE_KEY);
@@ -245,6 +460,9 @@ var BalancePixel = (() => {
245
460
  }
246
461
  if (preferences.analytics === true) {
247
462
  upgradeStorageTier();
463
+ if (!visitorId) {
464
+ visitorId = getOrCreateVisitorId();
465
+ }
248
466
  }
249
467
  const event = buildEvent({
250
468
  event_name: "consent_updated",
@@ -255,6 +473,13 @@ var BalancePixel = (() => {
255
473
  }
256
474
  });
257
475
  enqueueEvent(event);
476
+ try {
477
+ window.dispatchEvent(new CustomEvent("balance:consent:updated", {
478
+ detail: preferences
479
+ }));
480
+ log("DOM event balance:consent:updated dispatched");
481
+ } catch (e) {
482
+ }
258
483
  }
259
484
  function getConsent() {
260
485
  return consent;
@@ -275,6 +500,8 @@ var BalancePixel = (() => {
275
500
  const base = {
276
501
  artist_id: artistId,
277
502
  fan_session_id: sessionId,
503
+ visitor_id: visitorId || void 0,
504
+ // Persistent visitor ID for deduplication
278
505
  fan_id_hash: fanIdHash || void 0,
279
506
  timestamp: (/* @__PURE__ */ new Date()).toISOString(),
280
507
  source_url: window.location.href,
@@ -456,6 +683,8 @@ var BalancePixel = (() => {
456
683
  event_name: "identify",
457
684
  fan_id_hash: fanIdHash,
458
685
  metadata: {
686
+ email,
687
+ // Pass email for display_name fallback (extracted prefix only stored)
459
688
  email_sha256: fanIdHash,
460
689
  traits,
461
690
  consent_preferences: consent || void 0,
@@ -491,22 +720,32 @@ var BalancePixel = (() => {
491
720
  }
492
721
  sessionId = getOrCreateSession();
493
722
  loadFanId();
723
+ loadVisitorId();
494
724
  if (!consent) {
495
- consent = {
496
- analytics: true,
497
- marketing: true,
498
- personalization: true,
499
- timestamp: (/* @__PURE__ */ new Date()).toISOString()
500
- };
501
- log("Default consent enabled (all tracking):", consent);
502
- upgradeStorageTier();
725
+ if (consentUIEnabled) {
726
+ log("Consent UI enabled, waiting for explicit user consent (Tier 0 mode)");
727
+ } else {
728
+ consent = {
729
+ analytics: true,
730
+ marketing: true,
731
+ personalization: true,
732
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
733
+ };
734
+ log("Default consent enabled (all tracking):", consent);
735
+ upgradeStorageTier();
736
+ }
737
+ }
738
+ if (consent?.analytics && !visitorId) {
739
+ visitorId = getOrCreateVisitorId();
503
740
  }
504
741
  captureAttribution();
505
742
  startFlushTimer();
506
743
  log("Initialized", {
507
744
  artistId,
508
745
  projectId: projectId || "(none - will track to all projects)",
746
+ rawProjectId: rawProjectId || "(none)",
509
747
  sessionId,
748
+ visitorId: visitorId ? visitorId.substring(0, 8) + "..." : null,
510
749
  fanIdHash,
511
750
  consent,
512
751
  storageTier: currentStorageTier,
@@ -567,20 +806,32 @@ var BalancePixel = (() => {
567
806
  page: trackPageView,
568
807
  purchase,
569
808
  getSessionId: () => sessionId,
809
+ getVisitorId: () => visitorId,
570
810
  getFanIdHash: () => fanIdHash,
571
811
  getAttribution: () => attribution,
572
812
  setConsent,
573
813
  getConsent,
574
814
  hasConsent
575
815
  };
816
+ function initConsentManager() {
817
+ if (consentUIEnabled && !consent) {
818
+ new ConsentManager({
819
+ style: consentStyle,
820
+ primaryColor: consentPrimaryColor,
821
+ position: consentBannerPosition
822
+ });
823
+ }
824
+ }
576
825
  if (document.readyState === "loading") {
577
826
  document.addEventListener("DOMContentLoaded", () => {
578
827
  init();
579
828
  processQueue();
829
+ initConsentManager();
580
830
  });
581
831
  } else {
582
832
  init();
583
833
  processQueue();
834
+ initConsentManager();
584
835
  }
585
836
  log("Pixel script loaded");
586
837
  })();
@@ -1 +1,102 @@
1
- var BalancePixel=(()=>{(function(){function te(e){let t="desktop";/ipad|tablet|android(?!.*mobile)/i.test(e)?t="tablet":/mobile|iphone|android.*mobile|blackberry|iemobile/i.test(e)&&(t="mobile");let n="Unknown";/edg/i.test(e)?n="Edge":/opr|opera/i.test(e)?n="Opera":/firefox/i.test(e)?n="Firefox":/chrome/i.test(e)?n="Chrome":/safari/i.test(e)&&(n="Safari");let i="Unknown";return/iphone|ipad/i.test(e)?i="iOS":/android/i.test(e)?i="Android":/windows/i.test(e)?i="Windows":/mac os/i.test(e)?i="macOS":/linux/i.test(e)?i="Linux":/cros/i.test(e)&&(i="ChromeOS"),{device_type:t,browser:n,os:i}}let v=null;function ne(){if(!v)try{v=te(navigator.userAgent)}catch{v={device_type:"desktop",browser:"Unknown",os:"Unknown"}}return v}let l=document.currentScript,I=l?.dataset.artistId,x=l?.dataset.projectId,R=l?.dataset.emulator==="true",re=l?.dataset.debug==="true",k=parseInt(l?.dataset.heartbeatInterval||"30000",10),H=l?.dataset.heartbeat!=="false",F=l?.dataset.source,B=l?.dataset.endpoint;function ie(){if(F)return F;let e=typeof window.dataLayer<"u"&&Array.isArray(window.dataLayer),t=typeof window.gtag=="function";return e&&t?"gtm":"pixel"}let b="pixel";if(!I){console.error("[BALANCE Pixel] Error: data-artist-id attribute is required");return}let j="session_id",E="session_timestamp",M="attribution",q="fan_id_hash",z="balance_consent",ae=60*60*1e3,A="balance_",T=B||(R?"http://localhost:5001/artist-os-distro/us-central1/pixelEndpoint":"https://us-central1-artist-os-distro.cloudfunctions.net/pixelEndpoint"),S=null,s=null,a=null,d={},o=[],P=null,c="session",u=null,se=0,C=0,g=0,p=!0,oe=2*60*1e3,K=Date.now(),h=!1,r=(...e)=>{re&&console.log("[BALANCE Pixel]",...e)};function J(){try{return c==="local"?localStorage:sessionStorage}catch{return null}}function w(e){let t=J();if(!t)return null;try{let n=A+e,i=t.getItem(n);if(!i&&c==="session")try{i=localStorage.getItem(n)}catch{}return i}catch{return null}}function y(e,t){let n=J();if(n)try{n.setItem(A+e,t)}catch{}}function D(){if(c!=="local"){r("Upgrading storage tier: session -> local");try{let e=[];for(let t=0;t<sessionStorage.length;t++){let n=sessionStorage.key(t);n?.startsWith(A)&&e.push(n)}for(let t of e){let n=sessionStorage.getItem(t);n&&localStorage.setItem(t,n)}for(let t of e)sessionStorage.removeItem(t);c="local",r(`Storage tier upgraded, migrated ${e.length} items`)}catch(e){console.error("[BALANCE Pixel] Storage migration failed:",e)}}}function Y(){return crypto&&crypto.randomUUID?crypto.randomUUID():"xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g,e=>{let t=Math.random()*16|0;return(e==="x"?t:t&3|8).toString(16)})}function ce(){try{let e=w(j),t=w(E);if(e&&t&&Date.now()-parseInt(t,10)<ae)return y(E,Date.now().toString()),e;let n=Y();return y(j,n),y(E,Date.now().toString()),n}catch{return Y()}}function le(){let e=new URLSearchParams(window.location.search),t={};return["source","medium","campaign","content","term"].forEach(n=>{let i=e.get(`utm_${n}`);i&&(t[`utm_${n}`]=i)}),t}function de(){try{let e=w(M);if(e){d=JSON.parse(e),r("Loaded attribution:",d);return}let t=le();Object.keys(t).length>0&&(d=t,y(M,JSON.stringify(t)),r("Captured attribution:",d))}catch{}}function ue(){try{s=w(q)}catch{}}function ge(){try{let e=localStorage.getItem(z);e&&(a=JSON.parse(e).preferences||null,r("Loaded consent:",a))}catch{}}function G(e){let t=a;a=e;try{let i={preferences:e,method:"explicit",version:1};localStorage.setItem(z,JSON.stringify(i)),r("Consent saved:",e)}catch(i){console.error("[BALANCE Pixel] Could not save consent:",i)}e.analytics===!0&&D();let n=f({event_name:"consent_updated",metadata:{consent_preferences:e,consent_method:"explicit",previous_consent:t||void 0}});m(n)}function fe(){return a}function me(e){return a?.[e]===!0}async function pe(e){let t=e.toLowerCase().trim(),i=new TextEncoder().encode(t),L=await crypto.subtle.digest("SHA-256",i);return Array.from(new Uint8Array(L)).map(we=>we.toString(16).padStart(2,"0")).join("")}function f(e){let t=ne(),n={artist_id:I,fan_session_id:S,fan_id_hash:s||void 0,timestamp:new Date().toISOString(),source_url:window.location.href,referrer_url:document.referrer||void 0,user_agent:navigator.userAgent,device_type:t.device_type,browser:t.browser,os:t.os,tracking_source:b,...e,...d};return x&&!e.projectId&&(n.projectId=x),n}function m(e){o.push(e),r("Event queued:",e.event_name,"(queue:",o.length,")"),o.length>=10&&_()}async function _(){if(o.length===0)return;let e=[...o];o=[],r("Flushing",e.length,"events to",T);try{let t=await fetch(T,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({events:e}),keepalive:!0});if(!t.ok)throw new Error(`HTTP ${t.status}`);r("Events sent successfully")}catch(t){console.error("[BALANCE Pixel] Failed to send events:",t),o.length<50&&o.push(...e)}}function he(){P&&clearInterval(P),P=window.setInterval(()=>{o.length>0&&_()},5e3)}function O(){g||(se=Date.now()),g=Date.now(),p=!0,r("Active time tracking started/resumed")}function $(){g&&p&&(C+=Date.now()-g),p=!1,r("Active time tracking paused, accumulated:",C,"ms")}function ye(){let e=C;return p&&g&&(e+=Date.now()-g),e}function ve(){K=Date.now(),h&&(h=!1,O(),r("User returned from idle"))}function Q(){if(document.visibilityState==="hidden"){r("Skipping heartbeat - tab hidden");return}if(Date.now()-K>oe){h||(h=!0,$(),r("User idle - pausing heartbeat"));return}let e=ye(),t=Math.round(e/1e3),n=f({event_name:"engagement_heartbeat",metadata:{time_on_page_seconds:t,time_on_page_ms:e,heartbeat_interval_ms:k,is_active:p&&!h}});m(n),r("Heartbeat sent:",t,"seconds active")}function be(){if(!H){r('Heartbeat disabled via data-heartbeat="false"');return}u&&clearInterval(u),O(),u=window.setInterval(()=>{Q()},k),r("Heartbeat started with interval:",k,"ms")}function Se(){u&&(clearInterval(u),u=null),H&&(Q(),r("Heartbeat stopped, final time sent"))}function N(e={}){let t=f({event_name:"pageview",page_title:e.title||document.title,source_url:e.url||window.location.href});m(t)}function W(e,t={}){let n=f({event_name:"custom",metadata:{event_type:e,...t}});m(n)}async function V(e,t={}){try{if(a&&a.analytics===!1){r("Identify skipped - user declined analytics consent");return}s=await pe(e),a?.analytics===!0&&D(),y(q,s);let n=e.split("@"),i=n[0].charAt(0)+"***@"+(n[1]||"");r("Fan identified:",{name:t.name||"(no name)",email:i,hash:s.substring(0,16)+"...",traits:t,storageTier:c});let L=f({event_name:"identify",fan_id_hash:s,metadata:{email_sha256:s,traits:t,consent_preferences:a||void 0,storage_tier:c}});m(L)}catch(n){console.error("[BALANCE Pixel] Failed to identify:",n)}}function X(e,t="USD",n={}){let i=f({event_name:"purchase",metadata:{revenue:e,currency:t,...n}});m(i)}function Z(){b=ie(),r("Tracking source detected:",b),ge(),a?.analytics===!0?(c="local",r("Storage tier: local (analytics consent granted)")):(c="session",r("Storage tier: session (privacy by default)")),S=ce(),ue(),a||(a={analytics:!0,marketing:!0,personalization:!0,timestamp:new Date().toISOString()},r("Default consent enabled (all tracking):",a),D()),de(),he(),r("Initialized",{artistId:I,projectId:x||"(none - will track to all projects)",sessionId:S,fanIdHash:s,consent:a,storageTier:c,trackingSource:b,useEmulator:R,endpoint:T}),window._balanceInitialPageviewFired=!0,N(),be(),["mousemove","keydown","scroll","touchstart"].forEach(e=>{document.addEventListener(e,ve,{passive:!0})}),window.addEventListener("beforeunload",()=>{Se(),_()}),document.addEventListener("visibilitychange",()=>{document.visibilityState==="hidden"?($(),_()):O()})}let U=window.balance?.q||[];function ee(){if(U.length>0){r("Processing",U.length,"queued commands");for(let e of U){let[t,...n]=e;switch(t){case"track":W(n[0],n[1]);break;case"identify":V(n[0],n[1]);break;case"page":N(n[0]);break;case"purchase":X(n[0],n[1],n[2]);break;case"setConsent":G(n[0]);break;default:r("Unknown queued command:",t)}}}}window.balance={track:W,identify:V,page:N,purchase:X,getSessionId:()=>S,getFanIdHash:()=>s,getAttribution:()=>d,setConsent:G,getConsent:fe,hasConsent:me},document.readyState==="loading"?document.addEventListener("DOMContentLoaded",()=>{Z(),ee()}):(Z(),ee()),r("Pixel script loaded")})();})();
1
+ var BalancePixel=(()=>{var Me=Object.defineProperty;var Be=(f,a,g)=>a in f?Me(f,a,{enumerable:!0,configurable:!0,writable:!0,value:g}):f[a]=g;var T=(f,a,g)=>(Be(f,typeof a!="symbol"?a+"":a,g),g);(function(){function f(e){let t="desktop";/ipad|tablet|android(?!.*mobile)/i.test(e)?t="tablet":/mobile|iphone|android.*mobile|blackberry|iemobile/i.test(e)&&(t="mobile");let n="Unknown";/edg/i.test(e)?n="Edge":/opr|opera/i.test(e)?n="Opera":/firefox/i.test(e)?n="Firefox":/chrome/i.test(e)?n="Chrome":/safari/i.test(e)&&(n="Safari");let o="Unknown";return/iphone|ipad/i.test(e)?o="iOS":/android/i.test(e)?o="Android":/windows/i.test(e)?o="Windows":/mac os/i.test(e)?o="macOS":/linux/i.test(e)?o="Linux":/cros/i.test(e)&&(o="ChromeOS"),{device_type:t,browser:n,os:o}}let a=null;function g(){if(!a)try{a=f(navigator.userAgent)}catch{a={device_type:"desktop",browser:"Unknown",os:"Unknown"}}return a}let s=document.currentScript,A=s?.dataset.artistId,P=s?.dataset.projectId,D=P?fe(P):void 0;function fe(e){return!e||e.startsWith("release_")||e.startsWith("merch_")||e.startsWith("link_")||e.startsWith("custom_")?e:`custom_${e}`}let W=s?.dataset.emulator==="true",ge=s?.dataset.debug==="true",O=parseInt(s?.dataset.heartbeatInterval||"30000",10),q=s?.dataset.heartbeat!=="false",V=s?.dataset.source,Y=s?.dataset.endpoint,J=s?.dataset.consentUi==="true",pe=s?.dataset.consentStyle||"brutalist",me=s?.dataset.primaryColor,he=s?.dataset.bannerPosition||"bottom";function be(){if(V)return V;let e=typeof window.dataLayer<"u"&&Array.isArray(window.dataLayer),t=typeof window.gtag=="function";return e&&t?"gtm":"pixel"}let _="pixel";if(!A){console.error("[BALANCE Pixel] Error: data-artist-id attribute is required");return}let G="session_id",L="session_timestamp",Q="attribution",X="fan_id_hash",N="visitor_id",U="balance_consent",ye=60*60*1e3,p="balance_",R=Y||(W?"http://localhost:5001/artist-os-distro/us-central1/pixelEndpoint":"https://us-central1-artist-os-distro.cloudfunctions.net/pixelEndpoint"),k=null,c=null,l=null,i=null,m={},d=[],M=null,u="session",h=null,ve=0,B=0,b=0,w=!0,we=2*60*1e3,Z=Date.now(),x=!1,r=(...e)=>{ge&&console.log("[BALANCE Pixel]",...e)},H={base:`
2
+ :host {
3
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
4
+ position: fixed;
5
+ left: 0;
6
+ right: 0;
7
+ z-index: 2147483647;
8
+ }
9
+ .banner {
10
+ padding: 16px 24px;
11
+ display: flex;
12
+ gap: 24px;
13
+ align-items: center;
14
+ justify-content: space-between;
15
+ flex-wrap: wrap;
16
+ }
17
+ .text {
18
+ font-size: 14px;
19
+ line-height: 1.5;
20
+ flex: 1;
21
+ min-width: 280px;
22
+ }
23
+ .text strong {
24
+ text-transform: uppercase;
25
+ letter-spacing: 0.5px;
26
+ }
27
+ .actions {
28
+ display: flex;
29
+ gap: 12px;
30
+ flex-shrink: 0;
31
+ }
32
+ .btn {
33
+ padding: 10px 20px;
34
+ font-size: 14px;
35
+ font-weight: 600;
36
+ cursor: pointer;
37
+ transition: all 0.15s ease;
38
+ }
39
+ @media (max-width: 600px) {
40
+ .banner { flex-direction: column; text-align: center; }
41
+ .actions { width: 100%; justify-content: center; }
42
+ }
43
+ `,brutalist:`
44
+ .banner {
45
+ background: #000;
46
+ color: #00ff00;
47
+ border-top: 4px solid #00ff00;
48
+ font-family: 'Courier New', monospace;
49
+ text-transform: uppercase;
50
+ }
51
+ .btn {
52
+ border: 2px solid #00ff00;
53
+ background: transparent;
54
+ color: #00ff00;
55
+ text-transform: uppercase;
56
+ letter-spacing: 1px;
57
+ }
58
+ .btn:hover { background: #00ff00; color: #000; }
59
+ .btn.decline { border-color: #ff0000; color: #ff0000; }
60
+ .btn.decline:hover { background: #ff0000; color: #000; }
61
+ `,default:`
62
+ .banner {
63
+ background: #fff;
64
+ color: #000;
65
+ box-shadow: 0 -2px 10px rgba(0,0,0,0.1);
66
+ border-top: 1px solid #e0e0e0;
67
+ }
68
+ .btn {
69
+ border: 1px solid #000;
70
+ background: transparent;
71
+ color: #000;
72
+ border-radius: 4px;
73
+ }
74
+ .btn.accept { background: #000; color: #fff; }
75
+ .btn:hover { opacity: 0.8; }
76
+ `,minimal:`
77
+ .banner { background: #1a1a1a; color: #e0e0e0; }
78
+ .btn {
79
+ border: 1px solid #e0e0e0;
80
+ background: transparent;
81
+ color: #e0e0e0;
82
+ border-radius: 4px;
83
+ }
84
+ .btn.accept { background: #fff; color: #000; border-color: #fff; }
85
+ .btn:hover { opacity: 0.8; }
86
+ `};class xe{constructor(t){T(this,"container",null);T(this,"shadow",null);T(this,"config");if(this.config=t,this.hasStoredConsent()){r("ConsentManager: Consent already exists, not showing banner");return}this.container=document.createElement("div"),this.container.id="balance-consent-manager",this.shadow=this.container.attachShadow({mode:"closed"}),this.render(),document.body.appendChild(this.container),r("ConsentManager: Banner rendered")}hasStoredConsent(){try{return localStorage.getItem(U)!==null}catch{return!1}}render(){if(!this.shadow)return;let t=this.config.style||"brutalist",n=H.base,o=H[t]||H.brutalist,S=this.config.position==="top"?"top: 0;":"bottom: 0;",ue=this.config.primaryColor?`.btn.accept { background: ${this.config.primaryColor} !important; border-color: ${this.config.primaryColor} !important; color: #fff !important; }`:"";this.shadow.innerHTML=`
87
+ <style>
88
+ ${n}
89
+ :host { ${S} }
90
+ ${o}
91
+ ${ue}
92
+ </style>
93
+ <div class="banner" role="dialog" aria-label="Cookie consent" aria-modal="false">
94
+ <p class="text">
95
+ <strong>Privacy Notice:</strong> We use data to support the artist and improve your experience.
96
+ </p>
97
+ <div class="actions">
98
+ <button id="decline" class="btn decline">Decline</button>
99
+ <button id="accept" class="btn accept">Accept</button>
100
+ </div>
101
+ </div>
102
+ `,this.shadow.getElementById("accept")?.addEventListener("click",()=>this.handleConsent(!0)),this.shadow.getElementById("decline")?.addEventListener("click",()=>this.handleConsent(!1))}handleConsent(t){window.balance?.setConsent&&window.balance.setConsent({analytics:t,marketing:t,personalization:t,timestamp:new Date().toISOString()}),this.remove()}remove(){this.container&&(this.container.remove(),this.container=null,this.shadow=null,r("ConsentManager: Banner removed"))}}function ee(){try{return u==="local"?localStorage:sessionStorage}catch{return null}}function C(e){let t=ee();if(!t)return null;try{let n=p+e,o=t.getItem(n);if(!o&&u==="session")try{o=localStorage.getItem(n)}catch{}return o}catch{return null}}function I(e,t){let n=ee();if(n)try{n.setItem(p+e,t)}catch{}}function j(){if(u!=="local"){r("Upgrading storage tier: session -> local");try{let e=[];for(let t=0;t<sessionStorage.length;t++){let n=sessionStorage.key(t);n?.startsWith(p)&&e.push(n)}for(let t of e){let n=sessionStorage.getItem(t);n&&localStorage.setItem(t,n)}for(let t of e)sessionStorage.removeItem(t);u="local",r(`Storage tier upgraded, migrated ${e.length} items`)}catch(e){console.error("[BALANCE Pixel] Storage migration failed:",e)}}}function F(){return crypto&&crypto.randomUUID?crypto.randomUUID():"xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g,e=>{let t=Math.random()*16|0;return(e==="x"?t:t&3|8).toString(16)})}function Ie(){try{let e=C(G),t=C(L);if(e&&t&&Date.now()-parseInt(t,10)<ye)return I(L,Date.now().toString()),e;let n=F();return I(G,n),I(L,Date.now().toString()),n}catch{return F()}}function Se(){let e=new URLSearchParams(window.location.search),t={};return["source","medium","campaign","content","term"].forEach(n=>{let o=e.get(`utm_${n}`);o&&(t[`utm_${n}`]=o)}),t}function _e(){try{let e=C(Q);if(e){m=JSON.parse(e),r("Loaded attribution:",m);return}let t=Se();Object.keys(t).length>0&&(m=t,I(Q,JSON.stringify(t)),r("Captured attribution:",m))}catch{}}function ke(){try{l=C(X)}catch{}}function te(){if(!i?.analytics)return null;try{let e=localStorage.getItem(p+N);if(e)return e;let t=F();return localStorage.setItem(p+N,t),r("Created persistent visitor ID:",t.substring(0,8)+"..."),t}catch{return null}}function Ce(){if(!i?.analytics){c=null;return}try{c=localStorage.getItem(p+N),c&&r("Loaded visitor ID:",c.substring(0,8)+"...")}catch{}}function Ee(){try{let e=localStorage.getItem(U);e&&(i=JSON.parse(e).preferences||null,r("Loaded consent:",i))}catch{}}function ne(e){let t=i;i=e;try{let o={preferences:e,method:"explicit",version:1};localStorage.setItem(U,JSON.stringify(o)),r("Consent saved:",e)}catch(o){console.error("[BALANCE Pixel] Could not save consent:",o)}e.analytics===!0&&(j(),c||(c=te()));let n=y({event_name:"consent_updated",metadata:{consent_preferences:e,consent_method:"explicit",previous_consent:t||void 0}});v(n);try{window.dispatchEvent(new CustomEvent("balance:consent:updated",{detail:e})),r("DOM event balance:consent:updated dispatched")}catch{}}function Te(){return i}function Ae(e){return i?.[e]===!0}async function Pe(e){let t=e.toLowerCase().trim(),o=new TextEncoder().encode(t),S=await crypto.subtle.digest("SHA-256",o);return Array.from(new Uint8Array(S)).map(Re=>Re.toString(16).padStart(2,"0")).join("")}function y(e){let t=g(),n={artist_id:A,fan_session_id:k,visitor_id:c||void 0,fan_id_hash:l||void 0,timestamp:new Date().toISOString(),source_url:window.location.href,referrer_url:document.referrer||void 0,user_agent:navigator.userAgent,device_type:t.device_type,browser:t.browser,os:t.os,tracking_source:_,...e,...m};return D&&!e.projectId&&(n.projectId=D),n}function v(e){d.push(e),r("Event queued:",e.event_name,"(queue:",d.length,")"),d.length>=10&&E()}async function E(){if(d.length===0)return;let e=[...d];d=[],r("Flushing",e.length,"events to",R);try{let t=await fetch(R,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({events:e}),keepalive:!0});if(!t.ok)throw new Error(`HTTP ${t.status}`);r("Events sent successfully")}catch(t){console.error("[BALANCE Pixel] Failed to send events:",t),d.length<50&&d.push(...e)}}function De(){M&&clearInterval(M),M=window.setInterval(()=>{d.length>0&&E()},5e3)}function z(){b||(ve=Date.now()),b=Date.now(),w=!0,r("Active time tracking started/resumed")}function re(){b&&w&&(B+=Date.now()-b),w=!1,r("Active time tracking paused, accumulated:",B,"ms")}function Oe(){let e=B;return w&&b&&(e+=Date.now()-b),e}function Le(){Z=Date.now(),x&&(x=!1,z(),r("User returned from idle"))}function oe(){if(document.visibilityState==="hidden"){r("Skipping heartbeat - tab hidden");return}if(Date.now()-Z>we){x||(x=!0,re(),r("User idle - pausing heartbeat"));return}let e=Oe(),t=Math.round(e/1e3),n=y({event_name:"engagement_heartbeat",metadata:{time_on_page_seconds:t,time_on_page_ms:e,heartbeat_interval_ms:O,is_active:w&&!x}});v(n),r("Heartbeat sent:",t,"seconds active")}function Ne(){if(!q){r('Heartbeat disabled via data-heartbeat="false"');return}h&&clearInterval(h),z(),h=window.setInterval(()=>{oe()},O),r("Heartbeat started with interval:",O,"ms")}function Ue(){h&&(clearInterval(h),h=null),q&&(oe(),r("Heartbeat stopped, final time sent"))}function $(e={}){let t=y({event_name:"pageview",page_title:e.title||document.title,source_url:e.url||window.location.href});v(t)}function ie(e,t={}){let n=y({event_name:"custom",metadata:{event_type:e,...t}});v(n)}async function ae(e,t={}){try{if(i&&i.analytics===!1){r("Identify skipped - user declined analytics consent");return}l=await Pe(e),i?.analytics===!0&&j(),I(X,l);let n=e.split("@"),o=n[0].charAt(0)+"***@"+(n[1]||"");r("Fan identified:",{name:t.name||"(no name)",email:o,hash:l.substring(0,16)+"...",traits:t,storageTier:u});let S=y({event_name:"identify",fan_id_hash:l,metadata:{email:e,email_sha256:l,traits:t,consent_preferences:i||void 0,storage_tier:u}});v(S)}catch(n){console.error("[BALANCE Pixel] Failed to identify:",n)}}function se(e,t="USD",n={}){let o=y({event_name:"purchase",metadata:{revenue:e,currency:t,...n}});v(o)}function ce(){_=be(),r("Tracking source detected:",_),Ee(),i?.analytics===!0?(u="local",r("Storage tier: local (analytics consent granted)")):(u="session",r("Storage tier: session (privacy by default)")),k=Ie(),ke(),Ce(),i||(J?r("Consent UI enabled, waiting for explicit user consent (Tier 0 mode)"):(i={analytics:!0,marketing:!0,personalization:!0,timestamp:new Date().toISOString()},r("Default consent enabled (all tracking):",i),j())),i?.analytics&&!c&&(c=te()),_e(),De(),r("Initialized",{artistId:A,projectId:D||"(none - will track to all projects)",rawProjectId:P||"(none)",sessionId:k,visitorId:c?c.substring(0,8)+"...":null,fanIdHash:l,consent:i,storageTier:u,trackingSource:_,useEmulator:W,endpoint:R}),window._balanceInitialPageviewFired=!0,$(),Ne(),["mousemove","keydown","scroll","touchstart"].forEach(e=>{document.addEventListener(e,Le,{passive:!0})}),window.addEventListener("beforeunload",()=>{Ue(),E()}),document.addEventListener("visibilitychange",()=>{document.visibilityState==="hidden"?(re(),E()):z()})}let K=window.balance?.q||[];function le(){if(K.length>0){r("Processing",K.length,"queued commands");for(let e of K){let[t,...n]=e;switch(t){case"track":ie(n[0],n[1]);break;case"identify":ae(n[0],n[1]);break;case"page":$(n[0]);break;case"purchase":se(n[0],n[1],n[2]);break;case"setConsent":ne(n[0]);break;default:r("Unknown queued command:",t)}}}}window.balance={track:ie,identify:ae,page:$,purchase:se,getSessionId:()=>k,getVisitorId:()=>c,getFanIdHash:()=>l,getAttribution:()=>m,setConsent:ne,getConsent:Te,hasConsent:Ae};function de(){J&&!i&&new xe({style:pe,primaryColor:me,position:he})}document.readyState==="loading"?document.addEventListener("DOMContentLoaded",()=>{ce(),le(),de()}):(ce(),le(),de()),r("Pixel script loaded")})();})();