{"version":3,"file":"clarity-pixel-snippet.min.js","mappings":"wZAsFA,SAASA,EAAWC,GAClB,IACE,OAAOC,mBAAmBD,EAC5B,CAAE,MAAOE,GACP,OAAOF,CACT,CACF,CA6DM,SAAUG,EAAKC,GACnB,OAGI,SAAkBA,GACtB,OAjDOC,SAASC,OAAOC,MAAM,KAAKC,IAAoCC,IAAQ,IAAAC,EAC5E,MAAMC,EAAQF,EAAMG,QAAQ,KAGtBC,EAAqC,OAAzBH,EAAGD,EAAMK,MAAMH,EAAQ,SAAE,EAAtBD,EAAwBK,OAC7C,MAAO,CACLX,KAAML,EAAWU,EAAMK,MAAM,EAAGH,GAAOI,QACvCf,MAA+B,iBAAjBa,EAA4Bd,EAAWc,QAAgBG,KA0C1DC,OAAOX,GAAUA,EAAOF,OAASA,GAAMI,IAAIF,GAAUA,EAAON,MAC7E,CALSkB,CAAQd,GAAMe,OACvB,CC2DM,SAAUC,IAId,MAAMd,EAlFF,WACJ,IAAIA,EACJ,IACEA,EAASH,EAAK,oBAChB,CAAE,MAAOD,GAAQ,CAEjB,OAAKI,GACI,IAIX,CAuEiBe,GACTC,EAjEF,WACJ,IAAIA,EACJ,IACEA,EAAUC,eAAeC,QAAQ,gBACnC,CAAE,MAAOtB,GAAQ,CAEjB,OAAKoB,GACI,IAIX,CAsDkBG,GACVC,EA/CF,WACJ,IACE,MACMC,EADUC,YAAYC,iBAAiB,cACW,GACxD,IAAKF,EACH,OAAO,KAGT,MAAMG,EAAUH,EAAWD,aAC3B,IAAKI,EACH,OAAO,KAGT,IAAIC,EACJ,IAAK,MAAMC,KAAUF,EACnB,GAAoB,SAAhBE,EAAO5B,KAAiB,CAC1B2B,EAASC,EAAOC,YAChB,KACF,CAGF,IAAKF,EACH,OAAO,KAIT,IACEA,EAAS9B,mBAAmB8B,EAC9B,CAAE,MAAO7B,GAAQ,CAEjB,OAAO6B,CACT,CAAE,MAAO7B,GACP,OAAO,IACT,CACF,CAauBgC,GAErB,IAAIC,EAAU7B,EASd,OARK6B,IACHA,EAAUb,GAGPa,IACHA,EAAUT,GAGPS,EArLD,SAA2BnC,GAuB/B,MAAMoC,EAAU,8CAA8CC,KAAKrC,GAEnE,IAAW,MAAPoC,OAAO,EAAPA,EAASE,QAAS,EAAG,CACvB,MAAMC,EAAQH,EAAQ,GAEhBI,EAAsB,CAC1BC,SAAU,GACVC,OAHaN,EAAQ,IA+BvB,OAzBIG,EAAMI,SAAS,KACjBH,EAAMC,SAASG,GAAI,EACVL,EAAMI,SAAS,OACxBH,EAAMC,SAASG,GAAI,GAGjBL,EAAMI,SAAS,KACjBH,EAAMC,SAASI,GAAI,EACVN,EAAMI,SAAS,OACxBH,EAAMC,SAASI,GAAI,GAGjBN,EAAMI,SAAS,KACjBH,EAAMC,SAASK,GAAI,EACVP,EAAMI,SAAS,OACxBH,EAAMC,SAASK,GAAI,GAIjBP,EAAMI,SAAS,KACjBH,EAAMC,SAASM,GAAI,EACVR,EAAMI,SAAS,OACxBH,EAAMC,SAASM,GAAI,GAGdP,CACT,CAQA,GAAqB,iBAAVxC,GAAsBA,IAAUA,EAAMgD,WAAW,KAE1D,OADAC,QAAQC,KAAK,kDAAmDlD,GACzD,KAKT,IAAImC,EACJ,IACEA,EAAUgB,KAAKC,MAAMpD,EACvB,CAAE,MAAOE,GAAQ,CAEjB,OA2JI,SAAyBF,GAC7B,OAAOA,GAAS,aAAcA,CAChC,CA7JOqD,CAAelB,GAIbA,EAHE,IAIX,CAsGSmB,CAAiBnB,IAJtBc,QAAQM,MAAM,8BAA+BjD,EAAQgB,EAASI,GACvD,KAIX,CAWM,SAAU8B,IAAmB,IAAAC,EACjC,MAAMnD,EAASc,IACf,MAAmC,kBAAlB,MAANd,GAAgB,OAAVmD,EAANnD,EAAQmC,eAAQ,EAAhBgB,EAAkBZ,GACpBvC,EAAOmC,SAASI,EAGlBa,EAAwB,MAANpD,OAAM,EAANA,EAAQoC,OACnC,CAWM,SAAUiB,IAAmB,IAAAC,EACjC,MAAMtD,EAASc,IACf,MAAmC,kBAAlB,MAANd,GAAgB,OAAVsD,EAANtD,EAAQmC,eAAQ,EAAhBmB,EAAkBhB,GACpBtC,EAAOmC,SAASG,EAGlBc,EAAwB,MAANpD,OAAM,EAANA,EAAQoC,OACnC,CAoGM,SAAUgB,EAAkBhB,GAChC,MAAMmB,EAhFF,SAA8BnB,GAYlC,GAAsB,iBAAXA,GAAuBA,EAAOJ,OAAS,EAChD,OAGF,MAAMwB,EAAapB,EAAO5B,MAAM,EAAG,GAQ7BiD,EAAgBrB,EAAOJ,OAAS,EAAII,EAAO5B,MAAM,QAAKE,EAqC5D,MAlCsB,CACpB,KACA,KACA,KACA,KACA,KACA,KACA,KACA,KACA,KACA,KACA,KACA,KACA,KACA,KACA,KACA,KACA,KACA,KACA,KACA,KACA,KACA,KACA,KACA,KACA,KACA,KACA,KACA,KACA,KACA,KACA,MAGgB2B,SAASmB,GAClB,OAMU,OAAfA,GAFmB,CAAC,KAAM,MAEYnB,SAASoB,GAC1C,YADT,CAGF,CASqBC,CAAoBtB,GAGvC,MAAmB,SAAfmB,CAMN,CC3WA,SAASI,EAAyBjE,GAChC,OAAOA,aAAiBkE,OAAwB,uBAAflE,EAAMmE,IACzC,CAEA,SAASC,EAAoBC,GAQ3B,MAAMrE,EAAQqE,EAAMC,OAAOC,YACN,iBAAVvE,GAAsBA,EAAMsC,OAAS,IAC9CkC,QAAQ,QAAS,sBACjBA,QAAQ,MAAOH,EAAMC,OAAOG,cAAgB,WAAYzE,GAE5D,CAyEA,SAAS0E,EAA0BL,GAC7BA,EAAMC,QACRE,QAAQ,YAAa,CACnBG,WAAYN,EAAMC,OAAOM,iBAAmB,UAAY,SACxDC,kBAAmBR,EAAMC,OAAOQ,iBAAmB,UAAY,UAGrE,EAtEA,WAME,GAAuB,mBAAZN,QAAX,CAOA,GAAIO,UAAUC,UAQZ,OAPA/B,QAAQM,MAAM,qEAEdiB,QAAQ,YAAa,CACnBG,WAAY,SACZE,kBAAmB,WAMvBL,QAAQ,YAAa,CACnBG,WAAYnB,IAAwB,UAAY,SAChDqB,kBAAmBlB,IAAwB,UAAY,WAKzDsB,oBAAoB,qBAAsBC,aAAaC,UAGvD,IAAK,MAAMd,KAASa,aAAaE,MAC/B,GAAInB,EAAyBI,GAC3B,IACED,EAAoBC,EACtB,CAAE,MAAOnE,GACP+C,QAAQC,KAAKhD,EACf,CAKJgF,aAAaE,MAAQ,GAErBC,iBAAiB,qBAAsBjB,GAGvC/D,SAASgF,iBAAiB,0BAA2BX,EA1CrD,CA2CF,CAqBAY","sources":["webpack://lange/../node_modules/@integrabeauty/cookie/src/index.ts","webpack://lange/../node_modules/@integrabeauty/shopify-consent/src/index.ts","webpack://lange/./snippets/clarity-pixel/index.ts"],"sourcesContent":["/**\n * Represents the properties of a cookie.\n */\nexport interface Cookie {\n  /**\n   * Include a leading \".\" to include subdomains.\n   *\n   * @example \".langehair.com\"\n   */\n  domain: string;\n\n  /**\n   * Indicates the maximum lifetime of the cookie as an HTTP-date timestamp. See Date for the\n   * required formatting.\n   *\n   * If unspecified, the cookie becomes a session cookie. A session finishes when the client shuts\n   * down, after which the session cookie is removed.\n   *\n   * Warning: Many web browsers have a session restore feature that will save all tabs and restore\n   * them the next time the browser is used. Session cookies will also be restored, as if the\n   * browser was never closed.\n   *\n   * When an Expires date is set, the deadline is relative to the client the cookie is being set on,\n   * not the server.\n   */\n  expires: Date;\n\n  /**\n   * A <cookie-name> can contain any US-ASCII characters except for: control characters (ASCII\n   * characters `0` up to `31` and ASCII character `127`) or separator characters (space, tab and\n   * the characters: `( ) < > @ , ; : \\ \" / [ ] ? = { })`.\n   */\n  name: string;\n\n  /**\n   * Path is generally \"/\" which is for all pages for a domain. Otherwise the cookie is specific to\n   * the given path (e.g. particular page).\n   */\n  path: string;\n\n  /**\n   * Controls whether or not a cookie is sent with cross-site requests, providing some protection\n   * against cross-site request forgery attacks (CSRF).\n   *\n   * * Strict Means that the browser sends the cookie only for same-site requests, that is, requests\n   *   originating from the same site that set the cookie. If a request originates from a different\n   *   domain or scheme (even with the same domain), no cookies with the SameSite=Strict attribute\n   *   are sent.\n   * * Lax Means that the cookie is not sent on cross-site requests, such as on requests to load\n   *   images or frames, but is sent when a user is navigating to the origin site from an external\n   *   site (for example, when following a link). This is the default behavior if the SameSite\n   *   attribute is not specified.\n   * * None means that the browser sends the cookie with both cross-site and same-site requests. The\n   *   Secure attribute must also be set when setting this value, like so SameSite=None; Secure. If\n   *   Secure is missing an error will be logged\n   *\n   * @see https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie\n   */\n  same_site: 'Lax' | 'None' | 'Strict';\n\n  /**\n   * Indicates that the cookie is sent to the server only when a request is made with the https:\n   * scheme (except on localhost), and therefore, is more resistant to man-in-the-middle attacks.\n   */\n  secure: boolean;\n\n  /**\n   * A <cookie-value> can optionally be wrapped in double quotes and include any US-ASCII character\n   * excluding control characters (ASCII characters 0 up to 31 and ASCII character 127), Whitespace,\n   * double quotes, commas, semicolons, and backslashes.\n   */\n  value: string;\n}\n\n/**\n * Decodes a percent-encoded cookie name or value, falling back to the raw input when decoding is\n * not possible.\n *\n * Cookie storage is shared by every script on a domain, including third party libraries that write\n * raw, improperly-encoded values. A single cookie whose value contains a malformed percent-\n * sequence, such as a literal percent sign that is not followed by two hexadecimal digits, makes\n * decodeURIComponent throw a URIError. Because the whole cookie string is parsed at once, one such\n * cookie would otherwise poison every read on the page rather than just its own entry. Returning\n * the undecoded text keeps the remaining cookies readable, and a value that was never percent-\n * encoded is returned unchanged anyway.\n */\nfunction safeDecode(value: string) {\n  try {\n    return decodeURIComponent(value);\n  } catch (error) {\n    return value;\n  }\n}\n\n/**\n * Parses the value of document.cookie into an an array of cookie objects.\n *\n * **This decodes the cookie name and value. You should not decode the cookie name or value\n * yourself**.\n *\n * Cookies can be set with separate flags. When there are two cookies with the same name but with\n * different flags, the name will appear twice in document.cookie. Therefore, this returns an array\n * of Cookie objects instead of a single object where each name is a property.\n *\n * @throws {Error} if document is undefined, if document.cookie cannot be accessed for some reason\n * such as security restrictions on iframes or browser restrictions, or if document.cookie does not\n * produce a string\n */\nexport function parse() {\n  return document.cookie.split(';').map<Pick<Cookie, 'name' | 'value'>>(parts => {\n    const index = parts.indexOf('=');\n    // decodeURIComponent(undefined) produces strange results in some browsers, such as the string\n    // undefined, so we have to avoid that\n    const encodedValue = parts.slice(index + 1)?.trim();\n    return {\n      name: safeDecode(parts.slice(0, index).trim()),\n      value: typeof encodedValue === 'string' ? safeDecode(encodedValue) : undefined\n    };\n  });\n}\n\n/**\n * Retrieves the value of a particular cookie by name.\n *\n * If a cookie with the given name is not found, then undefined is returned.\n *\n * Internally this calls a private function to parse the cookie and that function accesses\n * document.cookie and for that reason this function can throw. This will almost never happen except\n * when attempting to access cookies cross-context, e.g. accessing parent window cookies from within\n * an iframe where the iframe has certain attributes. Some third party libraries do this naively and\n * without concern for the security constraints imposed by browsers. As a result, you should always\n * consider handling errors when calling this function.\n *\n * Because this can throw, **please implement proper error handling** when calling this function.\n *\n * **This decodes the cookie name and value. You should not decode the cookie name or value\n * yourself**.\n *\n * Cookie values are always strings. If you happen to be storing something like a JSON-like object\n * in the cookie value, then the raw JSON string is returned and is not parsed on your behalf. If\n * you are enclosing your value in double quotes, the leading and trailing double quote characters\n * are included in the returned value.\n *\n * **WARNING**: This is a naive read. When examining the list of cookies, this returns the first one\n * in the list. More than one cookie can have the same name when two or more cookies have the same\n * name but different flags. This is entirely dependent on the order of the names in\n * document.cookie, which is completely unknown and can change at any time and probably varies by\n * browser. Therefore, when there is more than one cookie with the same name, it is possible this\n * returns an unexpected cookie value.\n *\n * @throws {SecurityError}\n * @throws {Error}\n */\nexport function read(name: string) {\n  return readAll(name).shift();\n}\n\nexport function readAll(name: string) {\n  return parse().filter(cookie => cookie.name === name).map(cookie => cookie.value);\n}\n\n/**\n * Given a cookie, generate the string that can be supplied to document.cookie\n *\n * **This encodes the cookie name and cookie. Do not encode these yourself**.\n */\nexport function serialize(cookie: Partial<Cookie>) {\n  if (!cookie.name) {\n    throw new Error('Cookie name is required');\n  }\n\n  const parts: string[] = [];\n\n  parts.push(`${encodeURIComponent(cookie.name)}=${encodeURIComponent(cookie.value || '')}`);\n\n  if (typeof cookie.path === 'string') {\n    parts.push(`Path=${cookie.path}`);\n  }\n\n  if (typeof cookie.domain === 'string') {\n    parts.push(`Domain=${cookie.domain}`);\n  }\n\n  if (cookie.expires && typeof cookie.expires.getTime === 'function') {\n    parts.push(`Expires=${cookie.expires.toUTCString()}`);\n  }\n\n  if (typeof cookie.same_site === 'string') {\n    parts.push(`SameSite=${cookie.same_site}`);\n  }\n\n  if (cookie.secure) {\n    parts.push('Secure');\n  }\n\n  // The delimiter must include a single space after semicolon\n\n  return parts.join('; ');\n}\n\n/**\n * This function can throw because setting document.cookie sometimes throws an error.\n *\n * This function can throw on invalid input, such as when not specifying a required property such as\n * cookie name.\n *\n * **This encodes the cookie name and cookie. Do not encode these yourself**.\n */\nexport function write(cookie: Partial<Cookie>) {\n  document.cookie = serialize(cookie);\n}\n\n/**\n * Deletes a cookie.\n *\n * Of the cookie properties, only the name is required. However, some browsers are extremely strict\n * about how a cookie is deleted. To delete a cookie the exact same other properties must also be\n * provided, other than the cookie's value and expiration date. For example, if the cookie to be\n * deleted is secure, then you must specify the secure property to delete the cookie.\n *\n * If the properties do not match, this completes without error and the cookie that was to be\n * deleted still remains.\n *\n * Deleting a cookie is done by setting its value to an empty string and then assigning the full\n * cookie value to document.cookie.\n *\n * This function may throw an error because deleting a cookie is done by assigning a value to\n * document.cookie which can sometimes throw.\n *\n * The expires property is ignored. This sets expiration date to 1970 to force cookie to become\n * expired.\n *\n * Insecure cookies cannot be removed. These are cookies belonging to the http protocol.\n */\nexport function remove(cookie: Partial<Cookie>) {\n  write({ ...cookie, value: '', expires: new Date(0) });\n}\n","import { read } from '@integrabeauty/cookie';\n\ntype Limitation = 'CCPA_BLOCK_ALL' | 'GDPR';\n\n/**\n * Represents the state of consent.\n *\n * Sometimes this is serialized into a cookie value. Sometimes it is constructed from a esoteric\n * serialization format.\n */\nexport interface ConsentState {\n  con: {\n    CMP?: {\n      a: '0' | '1';\n      m: '0' | '1';\n      p: '0' | '1';\n      s: '0' | '1';\n    };\n    GDPR?: '' | '0' | '1' | 'no_interaction';\n  };\n  display_banner?: boolean;\n  lim: Limitation[];\n  purposes: {\n    /**\n     * analytics\n     */\n    a: boolean;\n\n    /**\n     * marketing\n     */\n    m: boolean;\n\n    /**\n     * personalization\n     */\n    p: boolean;\n\n    t: boolean;\n  };\n\n  reg: '' | 'CCPA' | 'GDPR' | undefined;\n\n  region?: string;\n\n  sale_of_data_region?: boolean;\n\n  v: string;\n}\n\nexport function parseConsentHash(value: string) {\n  // Check for v3 cookie or session hash. AMPS can be before or after period, all preceding\n  // underscore. lowercase is denied. uppercase is granted. it seems like initially AMPS defaults\n  // are after the period. After interacting with checkboxes, characters move to before the period.\n  //\n  // Format: 3[flags]_[region]_[other data] Where region is a 2 to 6 character code like USCA (US\n  // California) or USIL (US Illinois) I do not know about 6. I do see 5, e.g. GBNIR (Great Britain\n  // Northern Ireland).\n  //\n  // The region can also just be the country. I suppose Shopify is sometimes unable to determine the\n  // subregion.\n  //\n  // The subregion can contain digits.\n  //\n  // The region can be empty.\n  //\n  // The hash can carry trailing underscore-delimited segments after the region that we do not need,\n  // most notably a consent id and, when a metafield is written through setTrackingConsent, an\n  // appended metafield blob in raw or URL encoded JSON. This regex is anchored only at the start\n  // and reads just the flags and region, so any such trailing data is tolerated and ignored. Do not\n  // anchor it at the end or switch to a positional split, which would make those trailing segments\n  // break parsing.\n\n  const matches = /^3([amps]{0,4}\\.?[amps]{0,4})_([A-Z0-9]*)_/i.exec(value);\n\n  if (matches?.length > 2) {\n    const flags = matches[1];\n    const region = matches[2];\n    const state = <ConsentState>{\n      purposes: {},\n      region\n    };\n\n    if (flags.includes('A')) {\n      state.purposes.a = true;\n    } else if (flags.includes('a')) {\n      state.purposes.a = false;\n    }\n\n    if (flags.includes('M')) {\n      state.purposes.m = true;\n    } else if (flags.includes('m')) {\n      state.purposes.m = false;\n    }\n\n    if (flags.includes('P')) {\n      state.purposes.p = true;\n    } else if (flags.includes('p')) {\n      state.purposes.p = false;\n    }\n\n    // note that s appears as t in the object\n    if (flags.includes('S')) {\n      state.purposes.t = true;\n    } else if (flags.includes('s')) {\n      state.purposes.t = false;\n    }\n\n    return state;\n  }\n\n  // Shopify can update the cookie format at any time without coordination and the docs even mention\n  // that parsing the cookie is not advised. Log a warning message so that we get spammed with\n  // warnings about when this happens. We could eventually decide to throw here, but downstream\n  // callers are all written to expect this not to throw. So we leak the error by logging so that\n  // error diagnostics platforms that have hooked console can still observe the error. Beware this\n  // could result in false positives (e.g. genuinely bad input).\n  if (typeof value === 'string' && value && !value.startsWith('{')) {\n    console.warn('Failed to parse _tracking_consent cookie value:', value);\n    return null;\n  }\n\n  // Otherwise, assume it is a v1 or v2 cookie that is a json stringified object.\n\n  let consent: unknown;\n  try {\n    consent = JSON.parse(value);\n  } catch (error) {}\n\n  if (!isConsentState(consent)) {\n    return null;\n  }\n\n  return consent;\n}\n\nexport function readCookie() {\n  let cookie;\n  try {\n    cookie = read('_tracking_consent');\n  } catch (error) {}\n\n  if (!cookie) {\n    return null;\n  }\n\n  return cookie;\n}\n\n/**\n * Reads the consent hash from session storage. Shopify's consent library reads the consent value\n * from the navigation Server-Timing entry and stores it under this key, so the value here is the\n * same one the Server-Timing source provides, just read from Shopify's cache of it.\n */\nexport function readSession() {\n  let session;\n  try {\n    session = sessionStorage.getItem('consentHeader');\n  } catch (error) {}\n\n  if (!session) {\n    return null;\n  }\n\n  return session;\n}\n\n/**\n * Reads the consent hash from the navigation response's Server-Timing entry. Shopify's consent\n * library reads the current consent state from this entry, so this reads the same entry. Shopify\n * also copies this value into session storage, so the session storage source is a cache of this\n * one. The Performance API is the only way for script to observe this header.\n */\nexport function readServerTiming() {\n  try {\n    const entries = performance.getEntriesByType('navigation');\n    const navigation = <PerformanceNavigationTiming>entries[0];\n    if (!navigation) {\n      return null;\n    }\n\n    const timings = navigation.serverTiming;\n    if (!timings) {\n      return null;\n    }\n\n    let header: string | undefined;\n    for (const timing of timings) {\n      if (timing.name === '_cmp') {\n        header = timing.description;\n        break;\n      }\n    }\n\n    if (!header) {\n      return null;\n    }\n\n    // Decoding is best effort. A malformed value is left as is rather than discarded.\n    try {\n      header = decodeURIComponent(header);\n    } catch (error) {}\n\n    return header;\n  } catch (error) {\n    return null;\n  }\n}\n\n/**\n * Resolves the visitor's consent hash. All three sources are read, then the first available value\n * is used in priority order: cookie, then session storage, then the navigation Server-Timing entry.\n * Each source carries the same hash format. Shopify's consent library reads these same sources.\n */\nexport function readCustomerConsent() {\n  // All three are read up front so that every value is available for the diagnostic log below when\n  // none of them resolve. The cookie can be missing when it is not accessible due to same-site\n  // restrictions, in which case session storage or the Server-Timing entry can still carry a value.\n  const cookie = readCookie();\n  const session = readSession();\n  const serverTiming = readServerTiming();\n\n  let consent = cookie;\n  if (!consent) {\n    consent = session;\n  }\n\n  if (!consent) {\n    consent = serverTiming;\n  }\n\n  if (!consent) {\n    console.debug('Unable to determine consent', cookie, session, serverTiming);\n    return null;\n  }\n\n  return parseConsentHash(consent);\n}\n\n/**\n * We use the Shopify consent API to determine if a user has given consent to marketing tracking.\n * Although Shopify does provide a client-side mechanism to load the customer privacy API and\n * supporting methods/functionality, we have made the decision to use the Shopify set\n * \"_tracking_consent\" cookie to prevent pixels from having to wait for an asynchronous call to load\n * the API module.\n *\n * @see https://developer.mozilla.org/en-US/docs/Web/API/Navigator/globalPrivacyControl\n */\nexport function hasMarketingConsent() {\n  const cookie = readCustomerConsent();\n  if (typeof cookie?.purposes?.m === 'boolean') {\n    return cookie.purposes.m;\n  }\n\n  return getDefaultConsent(cookie?.region);\n}\n\n/**\n * We use the Shopify consent API to determine if a user has given consent to analytics tracking.\n * Although Shopify does provide a client-side mechanism to load the customer privacy API and\n * supporting methods/functionality, we have made the decision to use the Shopify set\n * \"_tracking_consent\" cookie to prevent pixels from having to wait for an asynchronous call to load\n * the API module.\n *\n * @see https://developer.mozilla.org/en-US/docs/Web/API/Navigator/globalPrivacyControl\n */\nexport function hasAnalyticsConsent() {\n  const cookie = readCustomerConsent();\n  if (typeof cookie?.purposes?.a === 'boolean') {\n    return cookie.purposes.a;\n  }\n\n  return getDefaultConsent(cookie?.region);\n}\n\nexport function hasSaleOfDataConsent() {\n  const cookie = readCustomerConsent();\n  if (typeof cookie?.purposes?.t === 'boolean') {\n    return cookie.purposes.t;\n  }\n\n  return getDefaultConsent(cookie?.region);\n}\n\nexport function isConsentState(value: any): value is ConsentState {\n  return value && 'purposes' in value;\n}\n\n/**\n * Determines the regulation type based on the region code.\n *\n * @param region country and province e.g. \"USCA\", \"DEDE\", \"GBGB\"\n * @returns The applicable regulation type\n */\nexport function determineRegulation(region: string) {\n  // Most regions are 4, but in some cases they can be longer such as FRIDF or GBNIR, and can\n  // sometimes be shorter, such as ROB. A country code is always 2, and there has to be at least one\n  // character for the subregion, so we use 3 as the cutoff for minimal validity. For example, for\n  // ROB, RO is Romania, and B is some subregion.\n  //\n  // The region can also just consist of the country. I am not sure why but I definitely see it in\n  // the wild, where the region value is just \"US\". Therefore, < 2 is the correct condition.\n  //\n  // The region can be the empty string. When the region is completely unknown, we cannot derive the\n  // regulation, so we still want to exit early here.\n\n  if (typeof region !== 'string' || region.length < 2) {\n    return;\n  }\n\n  const regionCode = region.slice(0, 2);\n\n  // All country codes are 2 digits. The province/state are all remaining characters, which is\n  // typically 2 characters, but might be 3, e.g. GBNIR for Great Britain Northern Ireland. This\n  // might not matter that much in practice as we only consider the subregion for CCPA, which only\n  // applies to US, where all subregions are 2 characters in length, but this is more correct.\n  //\n  // There may not be a subregion.\n  const subregionCode = region.length > 2 ? region.slice(2) : undefined;\n\n  // EU countries require GDPR compliance\n  const gdprCountries = [\n    'AT',\n    'BE',\n    'BG',\n    'HR',\n    'CY',\n    'CZ',\n    'DK',\n    'EE',\n    'FI',\n    'FR',\n    'DE',\n    'GR',\n    'HU',\n    'IS',\n    'IE',\n    'IT',\n    'LV',\n    'LI',\n    'LT',\n    'LU',\n    'MT',\n    'NL',\n    'NO',\n    'PL',\n    'PT',\n    'RO',\n    'SI',\n    'SK',\n    'ES',\n    'SE',\n    'GB'\n  ];\n\n  if (gdprCountries.includes(regionCode)) {\n    return 'GDPR';\n  }\n\n  // US states with CCPA-like regulations\n  const ccpaSubregions = ['CA', 'VA'];\n\n  if (regionCode === 'US' && ccpaSubregions.includes(subregionCode)) {\n    return 'CCPA';\n  }\n}\n\n/**\n * Gets the default consent value for a region when no explicit consent is given.\n *\n * @param region 4-character region code\n * @returns Default consent value based on regional privacy laws\n */\nexport function getDefaultConsent(region: string) {\n  const regulation = determineRegulation(region);\n\n  // GDPR requires opt-in consent (default = false)\n  if (regulation === 'GDPR') {\n    return false;\n  }\n\n  // CCPA allows opt-out (default = true, user can opt out). Other regions are generally permissive\n  return true;\n}\n","import { hasAnalyticsConsent, hasMarketingConsent } from '@integrabeauty/shopify-consent';\n\ndeclare global {\n  /**\n   * Seeded inline in liquid before this bundle parses. Holds the parse-time queue of buffered\n   * events and the listener that fills it, which this bundle drains and then replaces with its own\n   * listeners once it loads.\n   */\n  var clarity_init: {\n    listener: (event: Event) => void;\n    queue: Event[];\n  };\n}\n\ntype ExperimentExposedEvent = WindowEventMap['experiment-exposed'];\n\n/**\n * Whether the value is an experiment exposed event. Replaying the loader's queue narrows each\n * buffered event with this before handling it, since the queue hands them back untyped.\n */\nfunction isExperimentExposedEvent(value: any): value is ExperimentExposedEvent {\n  return value instanceof Event && value.type === 'experiment-exposed';\n}\n\nfunction onExperimentExposed(event: ExperimentExposedEvent) {\n  // Upstream code was changed to correctly reflect how GrowthBook works, in that it can work with\n  // values of different types. We had to widen the type of the properties in the event, and we had\n  // to remove some incorrect checks. Previously the event was not emitted upstream unless the value\n  // was a string that was not empty. Now the event is emitted without any checks. To preserve the\n  // original behavior, we check for those conditions here now instead. Since we only work with\n  // strings, no change in behavior is expected.\n\n  const value = event.detail.param_value;\n  if (typeof value === 'string' && value.length > 0) {\n    clarity('event', 'experiment_exposed');\n    clarity('set', event.detail.experiment_id + '_variant', value);\n  }\n}\n\n/**\n * Shallow Microsoft Clarity integration that listens for experiment exposure events to relay and\n * tag the active Clarity session. This is not a full fidelity integration and is only intended to\n * relay a single custom event and set a tag per active experiment, for better filtering and\n * analysis of Clarity sessions in the context of active experiments.\n *\n * @see https://learn.microsoft.com/en-us/clarity/setup-and-installation/clarity-api\n */\nfunction main() {\n  // This module does not actually load Microsoft Clarity. Instead, that code is loaded separately\n  // through the Microsoft Clarity Shopify app. This piggybacks on that existing integration to set\n  // tags and relay events as needed. This does not guarantee Clarity loaded, it is just a best\n  // effort integration.\n\n  if (typeof clarity !== 'function') {\n    return;\n  }\n\n  // We do not control whether or not Clarity is loaded when the user agent nakedly announces that\n  // it is automated. However, we can disable consent so as to suppress events.\n\n  if (navigator.webdriver) {\n    console.debug('Partially disabling Clarity pixel because of automated agent');\n\n    clarity('consentv2', {\n      ad_Storage: 'denied',\n      analytics_Storage: 'denied'\n    });\n\n    return;\n  }\n\n  clarity('consentv2', {\n    ad_Storage: hasMarketingConsent() ? 'granted' : 'denied',\n    analytics_Storage: hasAnalyticsConsent() ? 'granted' : 'denied'\n  });\n\n  // Take over from the liquid loader, which buffers only experiment exposures. Stop it buffering,\n  // replay what it caught, then listen live. Both sides must agree on that one event name.\n  removeEventListener('experiment-exposed', clarity_init.listener);\n\n  // Replay under a per-event try/catch so one malformed event does not drop the rest of the queue.\n  for (const event of clarity_init.queue) {\n    if (isExperimentExposedEvent(event)) {\n      try {\n        onExperimentExposed(event);\n      } catch (error) {\n        console.warn(error);\n      }\n    }\n  }\n\n  // Reset the queue (for idempotency and memory)\n  clarity_init.queue = [];\n\n  addEventListener('experiment-exposed', onExperimentExposed);\n\n  // This event is dispatched on document without bubbling through Shopify managed code.\n  document.addEventListener('visitorConsentCollected', onVisitorConsentCollected);\n}\n\n/**\n * Shopify emitted event that indicates the user has changed their consent settings. This handler\n * should be responsible for updating the clarity consent state based on the passed in values.\n *\n * On page load, the settings are read in using their defaults. If the user happens to be presented\n * with a cookie accept popup, or changes their consent settings after the page load, Shopify then\n * fires this event.\n *\n * @see https://shopify.dev/docs/api/customer-privacy#use-an-event-listener\n */\nfunction onVisitorConsentCollected(event: DocumentEventMap['visitorConsentCollected']) {\n  if (event.detail) {\n    clarity('consentv2', {\n      ad_Storage: event.detail.marketingAllowed ? 'granted' : 'denied',\n      analytics_Storage: event.detail.analyticsAllowed ? 'granted' : 'denied'\n    });\n  }\n}\n\nmain();\n"],"names":["safeDecode","value","decodeURIComponent","error","read","name","document","cookie","split","map","parts","_parts$slice","index","indexOf","encodedValue","slice","trim","undefined","filter","readAll","shift","readCustomerConsent","readCookie","session","sessionStorage","getItem","readSession","serverTiming","navigation","performance","getEntriesByType","timings","header","timing","description","readServerTiming","consent","matches","exec","length","flags","state","purposes","region","includes","a","m","p","t","startsWith","console","warn","JSON","parse","isConsentState","parseConsentHash","debug","hasMarketingConsent","_cookie$purposes","getDefaultConsent","hasAnalyticsConsent","_cookie$purposes2","regulation","regionCode","subregionCode","determineRegulation","isExperimentExposedEvent","Event","type","onExperimentExposed","event","detail","param_value","clarity","experiment_id","onVisitorConsentCollected","ad_Storage","marketingAllowed","analytics_Storage","analyticsAllowed","navigator","webdriver","removeEventListener","clarity_init","listener","queue","addEventListener","main"],"sourceRoot":""}