{
  "version": 3,
  "sources": ["/cdn/shop/t/12/assets/bro-early-access.js?cb=0.36041084110501"],
  "sourcesContent": ["import { Component } from '@theme/component';\n\n/**\n * Klaviyo identifiers are merchant-entered free text that ends up in a script URL and in a\n * message handed to a third-party SDK. Both are checked against the shapes Klaviyo actually\n * issues before they are used, so a malformed (or hostile) value is dropped rather than\n * interpolated.\n */\nconst KLAVIYO_FORM_ID_PATTERN = /^[A-Za-z0-9_-]{1,64}$/;\nconst KLAVIYO_COMPANY_ID_PATTERN = /^[A-Za-z0-9]{1,32}$/;\nconst KLAVIYO_ONSITE_ORIGIN = 'https://static.klaviyo.com/onsite/js';\n\nconst MILLISECONDS_PER_SECOND = 1000;\nconst SECONDS_PER_MINUTE = 60;\nconst SECONDS_PER_HOUR = 60 * SECONDS_PER_MINUTE;\nconst SECONDS_PER_DAY = 24 * SECONDS_PER_HOUR;\n\n/**\n * @param {number} value\n * @returns {string}\n */\nfunction padTwo(value) {\n  return String(value).padStart(2, '0');\n}\n\n/**\n * @typedef {object} CountdownRefs\n * @property {HTMLElement} timer - Wrapper around the four units, hidden once the deadline passes.\n * @property {HTMLElement} days\n * @property {HTMLElement} hours\n * @property {HTMLElement} minutes\n * @property {HTMLElement} seconds\n * @property {HTMLElement} [expired] - Message shown in place of the timer after the deadline.\n *\n * @extends Component<CountdownRefs>\n */\nexport class BroEarlyAccessCountdown extends Component {\n  requiredRefs = ['timer', 'days', 'hours', 'minutes', 'seconds'];\n\n  /** @type {ReturnType<typeof setTimeout> | null} */\n  #timeoutId = null;\n\n  connectedCallback() {\n    super.connectedCallback();\n\n    // The section renders the current values server side, so this only corrects for the age\n    // of a cached response \u2014 there is no flash of empty digits to avoid.\n    this.#update();\n    this.#schedule();\n\n    document.addEventListener('visibilitychange', this.#handleVisibilityChange);\n  }\n\n  disconnectedCallback() {\n    super.disconnectedCallback();\n\n    this.#stop();\n    document.removeEventListener('visibilitychange', this.#handleVisibilityChange);\n  }\n\n  /**\n   * The deadline as a millisecond timestamp, or `null` when the section could not resolve one.\n   *\n   * @returns {number | null}\n   */\n  get deadline() {\n    const seconds = Number(this.dataset.deadline);\n\n    return Number.isFinite(seconds) && seconds > 0 ? seconds * MILLISECONDS_PER_SECOND : null;\n  }\n\n  /**\n   * Background tabs are throttled to roughly one timer per minute, which would leave stale\n   * digits on screen. Stopping while hidden and re-syncing on return is both cheaper and\n   * more accurate than letting a throttled interval limp along.\n   */\n  #handleVisibilityChange = () => {\n    if (document.hidden) {\n      this.#stop();\n    } else {\n      this.#update();\n      this.#schedule();\n    }\n  };\n\n  /**\n   * Ticks on the second boundary rather than every 1000ms from an arbitrary start, so the\n   * displayed value never skips a second under timer drift.\n   */\n  #schedule() {\n    if (this.#timeoutId != null) return;\n\n    const delay = MILLISECONDS_PER_SECOND - (Date.now() % MILLISECONDS_PER_SECOND);\n\n    this.#timeoutId = setTimeout(() => {\n      this.#timeoutId = null;\n      this.#update();\n      this.#schedule();\n    }, delay);\n  }\n\n  #stop() {\n    if (this.#timeoutId == null) return;\n\n    clearTimeout(this.#timeoutId);\n    this.#timeoutId = null;\n  }\n\n  #update() {\n    const { deadline } = this;\n\n    if (deadline == null) {\n      this.#stop();\n      return;\n    }\n\n    const remaining = Math.max(0, Math.floor((deadline - Date.now()) / MILLISECONDS_PER_SECOND));\n\n    if (remaining === 0) {\n      this.#expire();\n      return;\n    }\n\n    const { days, hours, minutes, seconds } = this.refs;\n\n    this.#setText(days, padTwo(Math.floor(remaining / SECONDS_PER_DAY)));\n    this.#setText(hours, padTwo(Math.floor((remaining % SECONDS_PER_DAY) / SECONDS_PER_HOUR)));\n    this.#setText(minutes, padTwo(Math.floor((remaining % SECONDS_PER_HOUR) / SECONDS_PER_MINUTE)));\n    this.#setText(seconds, padTwo(remaining % SECONDS_PER_MINUTE));\n  }\n\n  /**\n   * @param {HTMLElement | undefined} element\n   * @param {string} value\n   */\n  #setText(element, value) {\n    if (element && element.textContent !== value) element.textContent = value;\n  }\n\n  #expire() {\n    this.#stop();\n\n    const { timer, expired } = this.refs;\n\n    // Without an expiry message there is nothing to swap to, so a zeroed timer stays put\n    // rather than leaving a hole in the layout.\n    if (!expired) {\n      const { days, hours, minutes, seconds } = this.refs;\n      for (const element of [days, hours, minutes, seconds]) this.#setText(element, '00');\n      return;\n    }\n\n    timer.hidden = true;\n    expired.hidden = false;\n  }\n}\n\nif (!customElements.get('bro-early-access-countdown')) {\n  customElements.define('bro-early-access-countdown', BroEarlyAccessCountdown);\n}\n\n/**\n * Opens the merchant's Klaviyo signup popup for shoppers who do not have an Early Access\n * code yet.\n *\n * Klaviyo is only contacted on click. That keeps a third-party script off the critical path\n * of a page whose whole job is to render fast, and means nothing is requested from Klaviyo\n * for visitors who never ask to subscribe.\n *\n * @extends Component\n */\nexport class BroEarlyAccessSubscribe extends Component {\n  #scriptRequested = false;\n\n  /**\n   * @returns {string | null}\n   */\n  get formId() {\n    const value = (this.dataset.klaviyoFormId ?? '').trim();\n\n    return KLAVIYO_FORM_ID_PATTERN.test(value) ? value : null;\n  }\n\n  /**\n   * @returns {string | null}\n   */\n  get companyId() {\n    const value = (this.dataset.klaviyoCompanyId ?? '').trim();\n\n    return KLAVIYO_COMPANY_ID_PATTERN.test(value) ? value : null;\n  }\n\n  /**\n   * `popup` for Klaviyo popup/flyout forms, `embed` for forms Klaviyo renders into a\n   * `klaviyo-form-<id>` container. The two form types are opened differently, so the\n   * merchant's choice has to reach the client.\n   *\n   * @returns {'popup' | 'embed'}\n   */\n  get mode() {\n    return this.dataset.klaviyoMode === 'embed' ? 'embed' : 'popup';\n  }\n\n  /**\n   * The section's dialog holding the embedded form, when there is one.\n   *\n   * @returns {HTMLElement | null}\n   */\n  get dialogHost() {\n    const id = this.dataset.dialogId;\n\n    return id ? document.getElementById(id) : null;\n  }\n\n  /**\n   * @param {Event} [event]\n   */\n  openForm = (event) => {\n    event?.preventDefault();\n\n    const { formId } = this;\n\n    if (!formId) return;\n\n    if (this.mode === 'embed') {\n      this.#openEmbedDialog();\n    } else {\n      this.#openKlaviyoForm(formId);\n    }\n\n    this.#ensureOnsiteScript();\n  };\n\n  /**\n   * Opens the merchant's Klaviyo form.\n   *\n   * The id is resolved against the forms Klaviyo has actually rendered rather than trusted\n   * blindly, because the \"form ID\" field is easy to fill in with the wrong value \u2014 the\n   * company ID (public API key) sits right next to it in Klaviyo's UI and looks identical.\n   * A company ID in that field opens nothing at all, silently.\n   *\n   * @param {string} configuredId\n   */\n  #openKlaviyoForm(configuredId) {\n    const formId = this.#resolveFormId(configuredId);\n    const w = /** @type {any} */ (window);\n\n    // Current onsite API.\n    if (typeof w.klaviyo?.openForm === 'function') {\n      w.klaviyo.openForm(formId);\n      return;\n    }\n\n    // Legacy queue. Klaviyo's onsite script drains it on load and replaces `push` with a\n    // live handler afterwards, so this works whether the SDK is already present, still in\n    // flight, or about to be requested.\n    w._klOnsite = w._klOnsite || [];\n    w._klOnsite.push(['openForm', formId]);\n  }\n\n  /**\n   * Klaviyo tags each form it renders with `data-testid=\"klaviyo-form-<id>\"`. Those are the\n   * only ids that can actually be opened on this page.\n   *\n   * The configured id wins whenever it matches one of them. Otherwise the form Klaviyo did\n   * render is used, which is what makes the link open the same popup as Klaviyo's own\n   * floating teaser without asking the merchant to correct anything.\n   *\n   * @param {string} configuredId\n   * @returns {string}\n   */\n  #resolveFormId(configuredId) {\n    const rendered = [...document.querySelectorAll('[data-testid^=\"klaviyo-form-\"]')]\n      .map((el) => /** @type {HTMLElement} */ (el).dataset.testid?.slice('klaviyo-form-'.length))\n      .filter((id) => typeof id === 'string' && KLAVIYO_FORM_ID_PATTERN.test(id));\n\n    if (rendered.includes(configuredId)) return configuredId;\n\n    return rendered[0] ?? configuredId;\n  }\n\n  #openEmbedDialog() {\n    const host = this.dialogHost;\n\n    if (!host) return;\n\n    // The dialog element may be in the DOM before dialog.js has run. `upgrade` is\n    // synchronous and a no-op once the element is already upgraded.\n    customElements.upgrade(host);\n\n    /** @type {{ showDialog?: () => void }} */ (host).showDialog?.();\n\n    this.#nudgeEmbed(host);\n  }\n\n  /**\n   * A closed `<dialog>` is `display: none`, and Klaviyo can skip a container that was\n   * hidden when its script initialised. Re-inserting an empty container puts it back\n   * through Klaviyo's mutation observer so the form renders on first open.\n   *\n   * @param {HTMLElement} host\n   */\n  #nudgeEmbed(host) {\n    const container = host.querySelector('[data-bro-klaviyo-embed]');\n\n    if (!container || container.childElementCount > 0) return;\n\n    const { parentNode } = container;\n\n    if (!parentNode) return;\n\n    const anchor = container.nextSibling;\n    container.remove();\n    parentNode.insertBefore(container, anchor);\n  }\n\n  /**\n   * Loads the Klaviyo onsite script when the store's app embed has not already done it \u2014\n   * for example on the password page, where app embeds may not run.\n   */\n  #ensureOnsiteScript() {\n    if (this.#scriptRequested) return;\n\n    // Already initialised, or the tag is on the page but has not executed yet.\n    if (window.klaviyo) return;\n    if (document.querySelector(`script[src*=\"${KLAVIYO_ONSITE_ORIGIN}\"]`)) return;\n\n    const { companyId } = this;\n\n    if (!companyId) return;\n\n    this.#scriptRequested = true;\n\n    const script = document.createElement('script');\n    script.async = true;\n    script.src = `${KLAVIYO_ONSITE_ORIGIN}/${companyId}/klaviyo.js`;\n    document.head.append(script);\n  }\n}\n\nif (!customElements.get('bro-early-access-subscribe')) {\n  customElements.define('bro-early-access-subscribe', BroEarlyAccessSubscribe);\n}\n"],
  "mappings": "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",
  "names": []
}
