@occluze/tether 0.5.0 → 0.6.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (66) hide show
  1. package/README.md +9 -7
  2. package/dist/esm/astro.js +1 -1
  3. package/dist/esm/chunks/account-JBZJN7X7.js +1 -0
  4. package/dist/esm/chunks/analytics-22NMIVBV.js +1 -0
  5. package/dist/esm/chunks/chunk-4UR45XTC.js +1 -0
  6. package/dist/esm/chunks/chunk-6R752OYZ.js +1 -0
  7. package/dist/esm/chunks/chunk-BGIKQY6H.js +1 -0
  8. package/dist/esm/chunks/chunk-CISGFYOF.js +1 -0
  9. package/dist/esm/chunks/chunk-KARKXC6G.js +1 -0
  10. package/dist/esm/chunks/chunk-YXCXIPQV.js +4 -0
  11. package/dist/esm/chunks/{chunk-RBVRRQIU.js → chunk-YZMBXS6Y.js} +1 -1
  12. package/dist/esm/chunks/chunk-ZVUPVSLF.js +1 -0
  13. package/dist/esm/chunks/diagnose-6ZSK5DGE.js +1 -0
  14. package/dist/esm/chunks/extras-QDCSW6VU.js +1 -0
  15. package/dist/esm/chunks/markets-ui-HJCVLCKJ.js +1 -0
  16. package/dist/esm/chunks/search-LN2H6SXS.js +1 -0
  17. package/dist/esm/index.js +1 -1
  18. package/dist/esm/server.js +1 -1
  19. package/dist/tether.account.js +380 -0
  20. package/dist/tether.account.min.js +1 -0
  21. package/dist/tether.analytics.js +259 -0
  22. package/dist/tether.analytics.min.js +1 -0
  23. package/dist/tether.diagnose.js +11 -2
  24. package/dist/tether.diagnose.min.js +1 -1
  25. package/dist/tether.extras.js +87 -0
  26. package/dist/tether.extras.min.js +1 -0
  27. package/dist/tether.js +268 -75
  28. package/dist/tether.markets.js +3 -3
  29. package/dist/tether.markets.min.js +1 -1
  30. package/dist/tether.min.js +3 -3
  31. package/dist/tether.min.js.map +3 -3
  32. package/dist/tether.search.js +176 -0
  33. package/dist/tether.search.min.js +1 -0
  34. package/package.json +5 -2
  35. package/src/account.js +313 -0
  36. package/src/analytics.js +232 -0
  37. package/src/astro.js +4 -1
  38. package/src/cart.js +74 -11
  39. package/src/client.js +8 -3
  40. package/src/collection.js +15 -10
  41. package/src/config.js +3 -0
  42. package/src/diagnose.js +11 -2
  43. package/src/dom.js +7 -1
  44. package/src/extras.js +63 -0
  45. package/src/fragments.js +1 -1
  46. package/src/index.js +103 -41
  47. package/src/initial.js +13 -4
  48. package/src/lazy.js +9 -1
  49. package/src/loader.js +4 -1
  50. package/src/markets-ui.js +2 -1
  51. package/src/mounting.js +27 -2
  52. package/src/parts/account.js +3 -0
  53. package/src/parts/analytics.js +3 -0
  54. package/src/parts/extras.js +3 -0
  55. package/src/parts/search.js +3 -0
  56. package/src/product.js +19 -5
  57. package/src/search.js +153 -0
  58. package/src/server.js +78 -6
  59. package/src/status.js +2 -2
  60. package/types/index.d.ts +56 -3
  61. package/types/server.d.ts +10 -2
  62. package/dist/esm/chunks/chunk-HHMOPDZG.js +0 -1
  63. package/dist/esm/chunks/chunk-HXKV6QGB.js +0 -4
  64. package/dist/esm/chunks/chunk-RNZMTQTX.js +0 -1
  65. package/dist/esm/chunks/diagnose-J667GHGS.js +0 -1
  66. package/dist/esm/chunks/markets-ui-EGWIEUOZ.js +0 -1
package/src/server.js CHANGED
@@ -12,10 +12,10 @@ import { DEFAULTS } from './config.js';
12
12
  import { request, withContext, TetherError } from './client.js';
13
13
  import { productFragment, collectionFields } from './fragments.js';
14
14
  import * as plans from './selling-plans.js';
15
- import { imageUrl } from './dom.js';
15
+ import { imageUrl, formatPercent } from './dom.js';
16
16
  import { initialState } from './initial.js';
17
17
 
18
- export { imageUrl, initialState };
18
+ export { imageUrl, initialState, formatPercent };
19
19
 
20
20
  export { TetherError, plans, withContext };
21
21
 
@@ -59,7 +59,7 @@ const ENV = {
59
59
  };
60
60
 
61
61
  export function createStorefront(options = {}) {
62
- const known = Object.fromEntries(Object.entries(options).filter(([k]) => k in DEFAULTS || k === 'fetch' || k === 'pageSize'));
62
+ const known = Object.fromEntries(Object.entries(options).filter(([k]) => k in DEFAULTS || k === 'fetch' || k === 'pageSize' || k === 'siteUrl'));
63
63
  const conf = { ...DEFAULTS, ...known };
64
64
  for (const [key, names] of Object.entries(ENV)) if (!conf[key]) conf[key] = names.map((n) => options[n]).find(Boolean) || '';
65
65
  conf.domain = String(conf.domain || '').replace(/^https?:\/\//, '').replace(/\/.*$/, '');
@@ -154,12 +154,81 @@ ${fragment()}`, byId ? { id: key, intent } : { handle: key, intent });
154
154
  * without its own request, then refreshes prices and stock in the background.
155
155
  * Put it anywhere in the page (Astro: <Fragment set:html={shop.hydrate(products)} />).
156
156
  */
157
- function hydrate(products) {
158
- const list = (Array.isArray(products) ? products : [products]).filter(Boolean);
157
+ /**
158
+ * { card: true } for lists (home, collections, recommendations): about 40% smaller, without
159
+ * descriptions, extra images and variant images (cards show the main image). Price, stock and
160
+ * plans per variant stay. The full product comes with the background refresh.
161
+ */
162
+ function hydrate(products, { card = false } = {}) {
163
+ let list = (Array.isArray(products) ? products : [products]).filter(Boolean);
164
+ if (card) list = list.map(slim);
159
165
  const payload = { v: 1, at: new Date().toISOString(), country: conf.country || null, language: conf.language || null, products: list };
160
166
  return `<script type="application/json" data-tether-hydrate>${safeJson(payload)}</script>`;
161
167
  }
162
168
 
169
+ /**
170
+ * schema.org data for search engines (Google's product rich results): a Product, or a ProductGroup
171
+ * with one Product per variant (price, currency, stock, sku, image, link to ?variant=).
172
+ * jsonLd(product, { url: 'https://shop.example/products/x' }) -> the <script> tag; jsonLdData() -> the object.
173
+ */
174
+ function jsonLdData(p, { url, brand } = {}) {
175
+ if (!p) return null;
176
+ const pageUrl = url || (conf.siteUrl ? new URL(productUrl(p.handle, { productPath: conf.productPath, id: p.id }), conf.siteUrl).toString() : undefined);
177
+ const vid = (v) => String(v.id).split('/').pop();
178
+ const withVariant = (v) => (pageUrl ? `${pageUrl}${pageUrl.includes('?') ? '&' : '?'}variant=${vid(v)}` : undefined);
179
+ const text = (p.description || '').trim() || undefined;
180
+ const images = (p.images?.nodes || []).map((i) => i.url).filter(Boolean);
181
+ const brandName = brand || p.vendor;
182
+ // Only buyable on a subscription: the plan price is the price (the first plan the variant has).
183
+ const priceOf = (v) => {
184
+ if (!p.requiresSellingPlan) return v.price;
185
+ const plan = plans.allPlans(p).find((pl) => plans.allocationFor(v, pl.id));
186
+ return plan ? plans.planPrice(v, plan.id) : v.price;
187
+ };
188
+ const offer = (v) => ({
189
+ '@type': 'Offer',
190
+ price: priceOf(v)?.amount,
191
+ priceCurrency: priceOf(v)?.currencyCode,
192
+ availability: `https://schema.org/${v.availableForSale ? (v.currentlyNotInStock ? 'BackOrder' : 'InStock') : 'OutOfStock'}`,
193
+ url: withVariant(v),
194
+ });
195
+ const vs = p.variants?.nodes || [];
196
+ const clean = (o) => JSON.parse(JSON.stringify(o)); // drops undefined
197
+ if (vs.length <= 1) {
198
+ const v = vs[0];
199
+ return clean({
200
+ '@context': 'https://schema.org', '@type': 'Product', name: p.title, description: text, url: pageUrl,
201
+ image: images, sku: v?.sku || undefined, brand: brandName ? { '@type': 'Brand', name: brandName } : undefined,
202
+ offers: v ? offer(v) : undefined,
203
+ });
204
+ }
205
+ const varies = { size: 'https://schema.org/size', color: 'https://schema.org/color', colour: 'https://schema.org/color', material: 'https://schema.org/material' };
206
+ return clean({
207
+ '@context': 'https://schema.org', '@type': 'ProductGroup', name: p.title, description: text, url: pageUrl,
208
+ productGroupID: String(p.id).split('/').pop(), image: images,
209
+ brand: brandName ? { '@type': 'Brand', name: brandName } : undefined,
210
+ variesBy: [...new Set((p.options || []).map((o) => varies[o.name.toLowerCase()]).filter(Boolean))],
211
+ hasVariant: vs.map((v) => ({
212
+ '@type': 'Product',
213
+ name: `${p.title} - ${v.title}`,
214
+ sku: v.sku || undefined,
215
+ image: v.image?.url || images[0],
216
+ ...Object.fromEntries((v.selectedOptions || []).filter((o) => varies[o.name.toLowerCase()]).map((o) => [varies[o.name.toLowerCase()].split('/').pop(), o.value])),
217
+ offers: offer(v),
218
+ })),
219
+ });
220
+ }
221
+ const jsonLd = (p, opts) => (p ? `<script type="application/ld+json">${safeJson(jsonLdData(p, opts))}</script>` : '');
222
+
223
+ function slim(p) {
224
+ return {
225
+ ...p,
226
+ description: '', descriptionHtml: '',
227
+ images: { nodes: (p.images?.nodes || []).slice(0, 1) },
228
+ variants: { nodes: (p.variants?.nodes || []).map((v) => ({ ...v, image: null })) },
229
+ };
230
+ }
231
+
163
232
  return {
164
233
  config: conf,
165
234
  query: q,
@@ -171,8 +240,11 @@ ${fragment()}`, byId ? { id: key, intent } : { handle: key, intent });
171
240
  getRecommendations,
172
241
  hydrate,
173
242
  formatMoney: (money, locale = conf.locale) => formatMoney(money, locale),
243
+ formatPercent: (n, locale = conf.locale) => formatPercent(n, locale),
174
244
  imageUrl,
175
- initialState: (product, opts = {}) => initialState(product, { defaultPurchase: conf.defaultPurchase, ...opts }),
245
+ jsonLd,
246
+ jsonLdData,
247
+ initialState: (product, opts = {}) => initialState(product, { defaultPurchase: conf.defaultPurchase, lowStock: conf.lowStock, ...opts }),
176
248
  productUrl: (handle, opts = {}) => productUrl(handle, { productPath: conf.productPath, ...opts }),
177
249
  plans,
178
250
  };
package/src/status.js CHANGED
@@ -11,8 +11,8 @@ import { attr, sel, get, scoped, show } from './dom.js';
11
11
  * "url:path" the last segment of the page path (/shop/coffee-box -> coffee-box)
12
12
  * Returns { key, fromUrl, raw }. key is null when the URL has no value.
13
13
  */
14
- export function resolveProductKey(el, location = window.location) {
15
- const raw = (get(el, 'product') || '').trim();
14
+ export function resolveProductKey(el, location = window.location, name = 'product') {
15
+ const raw = (get(el, name) || '').trim();
16
16
  if (!raw.toLowerCase().startsWith('url:')) return { key: raw || null, fromUrl: false, raw };
17
17
  const what = raw.slice(4).trim();
18
18
  let key = null;
package/types/index.d.ts CHANGED
@@ -51,6 +51,8 @@ export interface ShopifyProduct {
51
51
  variants: { nodes: ShopifyVariant[] };
52
52
  sellingPlanGroups?: { nodes: unknown[] };
53
53
  metafields?: ({ namespace: string; key: string; type: string; value: string; reference?: unknown } | null)[];
54
+ seo?: { title?: string | null; description?: string | null };
55
+ onlineStoreUrl?: string | null;
54
56
  [k: string]: unknown;
55
57
  }
56
58
 
@@ -119,6 +121,35 @@ export interface TetherConfig {
119
121
  retries?: number;
120
122
  /** Products handed over by the server (tether/server hydrate()): refresh once in the background (default true). */
121
123
  revalidate?: boolean;
124
+ /** Seconds a Shopify answer may be cached (default 60). Shopify caches identical queries, prices included, for hours otherwise; 0 = Shopify's caching. */
125
+ maxAge?: number;
126
+ /** Analytics (optional part): GA4/GTM dataLayer events, Shopify's analytics (experimental), or your own handler. */
127
+ analytics?: {
128
+ dataLayer?: boolean | string;
129
+ shopify?: boolean | { consent?: 'no-banner' | 'default-banner' | 'custom-banner'; proxy?: string };
130
+ onEvent?: (name: string, data: Record<string, unknown>) => void;
131
+ } | null;
132
+ /** Customer accounts (optional part): the Headless channel's Customer Account API client id, and options. */
133
+ account?: {
134
+ clientId: string;
135
+ /** The page Shopify sends the visitor back to after login (registered as callback URI). Default: the current page. */
136
+ callbackUrl?: string;
137
+ /** Where Shopify sends the visitor after logout (registered as logout URI). Default: the home page. */
138
+ logoutUrl?: string;
139
+ /** Keep the login across tabs and browser restarts (localStorage instead of sessionStorage). */
140
+ remember?: boolean;
141
+ /** Your own token endpoint, if the browser can't call Shopify's (it forwards the form body). */
142
+ tokenUrl?: string;
143
+ /** The *.myshopify.com domain, if domain is something else. */
144
+ shopDomain?: string;
145
+ locale?: string;
146
+ } | null;
147
+ /** tether-show="low-stock" at this many or fewer (default 5). */
148
+ lowStock?: number;
149
+ /** true: ?variant= (and selling_plan=) follow the visitor's choice on the page's main product. */
150
+ variantUrl?: boolean;
151
+ /** Your text for Shopify's cart warnings (stock, codes), e.g. translated by code. Return nothing to keep Shopify's. */
152
+ warningText?: ((warning: CartWarning) => string | null | undefined) | null;
122
153
  /** Mount on Astro View Transitions (astro:page-load) and close the cart on navigation (default true). */
123
154
  astro?: boolean;
124
155
  storageKey?: string | null;
@@ -167,7 +198,8 @@ export interface LineInput {
167
198
  attributes?: Attributes;
168
199
  }
169
200
 
170
- export interface CartWarning { code: string; message: string }
201
+ /** Shopify's note on the last cart change; target is the cart line id it's about. */
202
+ export interface CartWarning { code: string; message: string; target?: string }
171
203
 
172
204
  export interface Cart {
173
205
  readonly state: ShopifyCart | null;
@@ -185,10 +217,15 @@ export interface Cart {
185
217
  /** updateLine(id, 3) · updateLine(id, { sellingPlanId: null }) switches to one-time. */
186
218
  updateLine(lineId: string, patch: number | (LineInput & { quantity?: number })): Promise<ShopifyCart>;
187
219
  removeLine(lineIds: string | string[]): Promise<ShopifyCart>;
220
+ /** +1 / -1 for a line; quick clicks add up and show at once (use it for your own +/- buttons). */
221
+ changeQuantity(lineId: string, delta: number): Promise<ShopifyCart | null>;
222
+ /** The quantity to show for a line: what the visitor asked for while an update is on its way. */
223
+ quantityOf(line: ShopifyCartLine): number;
188
224
  clear(): Promise<ShopifyCart | null>;
189
225
  setNote(note: string): Promise<ShopifyCart>;
190
226
  setAttributes(attributes: Attributes): Promise<ShopifyCart>;
191
227
  setDiscountCodes(codes: string[]): Promise<ShopifyCart>;
228
+ /** Rejects with code INVALID_DISCOUNT (and doesn't keep the code) when Shopify doesn't accept it. */
192
229
  applyDiscount(code: string): Promise<ShopifyCart>;
193
230
  removeDiscount(code: string): Promise<ShopifyCart>;
194
231
  setBuyerIdentity(identity: { countryCode?: string; email?: string; phone?: string }, opts?: { create?: boolean }): Promise<ShopifyCart | null>;
@@ -306,6 +343,15 @@ export interface Tether {
306
343
  getRecommendations(product: string | Pick<ShopifyProduct, 'id'>, intent?: 'RELATED' | 'COMPLEMENTARY'): Promise<ShopifyProduct[]>;
307
344
  getScope(el: Element | null | undefined): ProductScope | null;
308
345
  addGroup(button: Element): Promise<ShopifyCart | null>;
346
+ /** Customer accounts (loads the account part): login(), logout(), customer(), orders(), query(gql, vars), getToken(). */
347
+ account: {
348
+ login(opts?: { returnTo?: string; locale?: string }): Promise<void>;
349
+ logout(opts?: { returnTo?: string }): Promise<void>;
350
+ customer(): Promise<{ id: string; firstName?: string | null; lastName?: string | null; displayName?: string; emailAddress?: { emailAddress: string } | null } | null>;
351
+ orders(opts?: { first?: number }): Promise<Array<{ id: string; name: string; processedAt: string; financialStatus?: string; fulfillmentStatus?: string; statusPageUrl: string; totalPrice: Money }>>;
352
+ query<T = any>(query: string, variables?: Record<string, unknown>): Promise<T>;
353
+ getToken(): Promise<string | null>;
354
+ };
309
355
  primeProduct(product: ShopifyProduct): void;
310
356
  normalizeProductKey(raw: string | number): string | null;
311
357
  clearProductCache(): void;
@@ -316,7 +362,7 @@ export interface Tether {
316
362
  TetherError: typeof TetherError;
317
363
  resolveProductKey(el: Element, location?: Location | URL): { key: string | null; fromUrl: boolean; raw: string };
318
364
  /** Switch market: prices, currency, cart and every product on the page follow. */
319
- setCountry(code: string, opts?: { language?: string; remember?: boolean }): Promise<ShopifyCart | null>;
365
+ setCountry(code: string, opts?: SetCountryOptions): Promise<ShopifyCart | null>;
320
366
  detectMarket(markets?: MarketsConfig): Promise<{ country?: string; language?: string; locale?: string; sources: Record<string, string> }>;
321
367
  getLocalization(): Promise<Localization>;
322
368
  /** Logs and returns what's wrong on this page (config, token, API version, plans, markup). */
@@ -332,7 +378,14 @@ export declare function mount(root?: ParentNode): number;
332
378
  export declare function unmount(): void;
333
379
  export declare function getScope(el: Element | null | undefined): ProductScope | null;
334
380
  export declare function addGroup(button: Element): Promise<ShopifyCart | null>;
335
- export declare function setCountry(code: string, opts?: { language?: string; remember?: boolean }): Promise<ShopifyCart | null>;
381
+ export interface SetCountryOptions {
382
+ /** Shopify language (translations); null clears it. Default: the markets.locales entry for that country, else unchanged. */
383
+ language?: string | null;
384
+ /** Money format, e.g. 'de-DE'. Default: the markets.locales entry for that country, else unchanged. */
385
+ locale?: string;
386
+ remember?: boolean;
387
+ }
388
+ export declare function setCountry(code: string, opts?: SetCountryOptions): Promise<ShopifyCart | null>;
336
389
  export declare const api: Tether;
337
390
  declare const tether: Tether;
338
391
  export default tether;
package/types/server.d.ts CHANGED
@@ -2,11 +2,13 @@ import type { ShopifyProduct, ShopifyVariant, Money, TetherError, SellingPlanHel
2
2
  export type { ShopifyProduct, ShopifyVariant, ShopifyCart, ShopifyCartLine, Money } from './index.js';
3
3
 
4
4
  export interface StorefrontOptions extends Partial<Pick<TetherConfig,
5
- 'apiVersion' | 'country' | 'language' | 'locale' | 'productPath' | 'metafields' | 'imagesFirst' | 'variantsFirst' | 'sellingPlanGroupsFirst' | 'sellingPlansFirst' | 'retries'>> {
5
+ 'apiVersion' | 'country' | 'language' | 'locale' | 'productPath' | 'maxAge' | 'lowStock' | 'defaultPurchase' | 'metafields' | 'imagesFirst' | 'variantsFirst' | 'sellingPlanGroupsFirst' | 'sellingPlansFirst' | 'retries'>> {
6
6
  /** your-store.myshopify.com (or pass import.meta.env with PUBLIC_SHOPIFY_DOMAIN). */
7
7
  domain?: string;
8
8
  /** Public Storefront API token (or PUBLIC_SHOPIFY_STOREFRONT_TOKEN in import.meta.env). */
9
9
  token?: string;
10
+ /** Your site's address, e.g. https://shop.example: product URLs in jsonLd() without passing url. */
11
+ siteUrl?: string;
10
12
  /** Products per request for allProducts() (default 25). */
11
13
  pageSize?: number;
12
14
  /** Custom fetch (tests, caching). */
@@ -49,8 +51,11 @@ export interface Storefront {
49
51
  * products without its own request, then refreshes prices and stock once in the background.
50
52
  * Astro: <Fragment set:html={shop.hydrate(product)} />
51
53
  */
52
- hydrate(products: ShopifyProduct | ShopifyProduct[]): string;
54
+ /** { card: true } for lists: much smaller (no descriptions, one image), the rest comes with the refresh. */
55
+ hydrate(products: ShopifyProduct | ShopifyProduct[], opts?: { card?: boolean }): string;
53
56
  formatMoney(money: Money | null | undefined, locale?: string): string;
57
+ /** 30 -> '30%' (nl, en) / '30 %' (de, fr), the way Tether shows savings. '' for 0. */
58
+ formatPercent(n: number, locale?: string): string;
54
59
  productUrl(handle: string, opts?: { productPath?: string; id?: string }): string;
55
60
  /** Shopify CDN image at a size. Use the element's max-width value, so the browser keeps the image. */
56
61
  imageUrl(url: string | null | undefined, size?: ImageSize): string;
@@ -59,6 +64,9 @@ export interface Storefront {
59
64
  * so the server renders the same and nothing flashes. Pass Astro.url.searchParams.get('variant') in SSR.
60
65
  */
61
66
  initialState(product: ShopifyProduct, opts?: InitialOptions): InitialState | null;
67
+ /** schema.org Product / ProductGroup data for search engines, as a <script type="application/ld+json"> tag. */
68
+ jsonLd(product: ShopifyProduct | null, opts?: { url?: string; brand?: string }): string;
69
+ jsonLdData(product: ShopifyProduct | null, opts?: { url?: string; brand?: string }): Record<string, unknown> | null;
62
70
  plans: SellingPlanHelpers;
63
71
  }
64
72
 
@@ -1 +0,0 @@
1
- import{c as U,e as yt,f as vt,g as wt,h as It,i as $t,j as ct,k as Lt,l as Y,n as Tt,o as _}from"./chunk-RNZMTQTX.js";import{A as o,B as u,C as N,D as K,E as f,F as W,G as m,H as A,I as St,K as Ct,L as E,M as g,N as D,O as T,P as J,Q as Pt,R as x,S as xt,T as F,U as kt,V as G,W as qt,X as V,b as h,e as M,f as nt,g as $,h as st,l as B,m as bt,o as at,q as L,r as j,s as O,t as rt,u as Et,v as ot,w as At,x as H,z as b}from"./chunk-HXKV6QGB.js";function Bt(n,e=1,t={}){let i=typeof n=="object"&&n!==null?n:{variantId:n,quantity:e,...t},s=L.variant(i.merchandiseId||i.variantId||i.id);if(!s)throw new st("add(): variantId is required");let a={merchandiseId:s,quantity:Math.max(1,parseInt(i.quantity??1,10)||1)},r=i.sellingPlanId||i.sellingPlan;r&&(a.sellingPlanId=L.sellingPlan(r));let c=V(i.attributes);return c.length&&(a.attributes=c),a}function _t(n,e,t){return`mutation Tether_${n}(${e}){${n}(${t}){cart{...TetherCart}userErrors{field message}warnings{code message}}}${at()}`}var jt='a[href],button:not([disabled]),input:not([disabled]),select:not([disabled]),textarea:not([disabled]),[tabindex]:not([tabindex="-1"])',z=null,Qt=n=>f(n,jt).filter(e=>e.offsetParent!==null||e===document.activeElement);function lt(n){f(document,o("cart-open")).forEach(e=>e.setAttribute("aria-expanded",String(n))),f(document,o("cart-drawer")).forEach(e=>{e.hasAttribute("role")||e.setAttribute("role","dialog"),!e.hasAttribute("aria-label")&&!e.hasAttribute("aria-labelledby")&&e.setAttribute("aria-label",u(e,"cart-label")||"Cart"),e.setAttribute("aria-modal",String(n)),e.classList.contains(h.cartDrawerOpenClass)||e.setAttribute("aria-hidden","true")})}var ut=n=>/does not exist|not found|invalid id/i.test(n?.message||""),Ot={state:null,_chain:Promise.resolve(),id(){try{return localStorage.getItem(h.storageKey)}catch{return null}},_setId(n){try{n?localStorage.setItem(h.storageKey,n):localStorage.removeItem(h.storageKey)}catch{}},get lines(){return this.state?.lines?.nodes||[]},get count(){return this.state?.totalQuantity||0},lastError:null,warnings:[],loaded:!1,_take(n,e){let t=bt(n,e);return this.warnings=t.warnings||[],this.warnings.length&&m("cart-warning",{warnings:this.warnings}),t.cart},_queue(n){let e=this._chain.then(n,n).then(t=>(this.lastError&&(this.lastError=null,this.render()),t),t=>{throw this.lastError=t,this.render(),m("cart-error",{error:t}),t});return this._chain=e.catch(()=>{}),e},_commit(n,e){return this.state=n,n?.id&&this._setId(n.id),this.render(),m("cart-updated",{cart:n,reason:e}),n},async init(){let n=this.id();if(!n)return this.state=null,this.loaded=!0,this.render(),m("cart-updated",{cart:null,reason:"load"}),null;try{let e=await B(`query TetherCart($id:ID!){cart(id:$id){...TetherCart}}${at()}`,{id:n});e.cart?this.state=e.cart:(this._setId(null),this.state=null)}catch(e){$(e,"cart load failed"),this.lastError=e,ut(e)&&this._setId(null),this.state=null}return this.loaded=!0,this.render(),m("cart-updated",{cart:this.state,reason:"load"}),this.state},async _create(n={}){let e=h.country?{countryCode:String(h.country).toUpperCase()}:void 0,t=await B(_t("cartCreate","$input: CartInput!","input: $input"),{input:{buyerIdentity:e,...n}});return this._take(t.cartCreate,"cartCreate")},async _mutate(n,e,t,i,s){let a=this.id();if(!a)return s?this._create(s):null;try{let r=await B(_t(n,`$cartId: ID!, ${e}`,`cartId: $cartId, ${t}`),{...i,cartId:a});return this._take(r[n],n)}catch(r){if(s&&ut(r))return M("cart expired, creating a new one"),this._setId(null),this._create(s);throw r}},add(n,e=1,t={}){let i=(Array.isArray(n)?n:[n]).map(s=>Bt(s,e,t));return this._queue(async()=>{let s=await this._mutate("cartLinesAdd","$lines: [CartLineInput!]!","lines: $lines",{lines:i},{lines:i});return this._commit(s,"add"),m("added",{cart:s,lines:i}),h.openCartOnAdd&&this.open(),s})},updateLine(n,e){let t=typeof e=="number"?{quantity:e}:{...e};if(t.quantity!==void 0&&t.quantity<=0)return this.removeLine(n);let i={id:n};return t.quantity!==void 0&&(i.quantity=t.quantity),"sellingPlanId"in t&&(i.sellingPlanId=t.sellingPlanId?L.sellingPlan(t.sellingPlanId):null),t.attributes&&(i.attributes=V(t.attributes)),(t.merchandiseId||t.variantId)&&(i.merchandiseId=L.variant(t.merchandiseId||t.variantId)),this._queue(async()=>this._commit(await this._mutate("cartLinesUpdate","$lines: [CartLineUpdateInput!]!","lines: $lines",{lines:[i]}),"update"))},removeLine(n){let e=Array.isArray(n)?n:[n];return this._queue(async()=>this._commit(await this._mutate("cartLinesRemove","$lineIds: [ID!]!","lineIds: $lineIds",{lineIds:e}),"remove"))},clear(){let n=this.lines.map(e=>e.id);return n.length?this.removeLine(n):Promise.resolve(this.state)},setNote(n){return this._queue(async()=>this._commit(await this._mutate("cartNoteUpdate","$note: String!","note: $note",{note:n??""},{note:n??""}),"note"))},setAttributes(n){let e=new Map((this.state?.attributes||[]).map(i=>[i.key,i.value]));V(n).forEach(i=>e.set(i.key,i.value));let t=[...e].filter(([,i])=>i!=="").map(([i,s])=>({key:i,value:s}));return this._queue(async()=>this._commit(await this._mutate("cartAttributesUpdate","$attributes: [AttributeInput!]!","attributes: $attributes",{attributes:t},{attributes:t}),"attributes"))},setDiscountCodes(n){let e=(Array.isArray(n)?n:[n]).map(t=>String(t).trim()).filter(Boolean);return this._queue(async()=>this._commit(await this._mutate("cartDiscountCodesUpdate","$discountCodes: [String!]!","discountCodes: $discountCodes",{discountCodes:e},{discountCodes:e}),"discount"))},applyDiscount(n){let e=(this.state?.discountCodes||[]).map(t=>t.code);return this.setDiscountCodes([...new Set([...e,n])])},removeDiscount(n){let e=String(n).toLowerCase();return this.setDiscountCodes((this.state?.discountCodes||[]).map(t=>t.code).filter(t=>t.toLowerCase()!==e))},setBuyerIdentity(n,{create:e=!0}={}){return this._queue(async()=>{let t=await this._mutate("cartBuyerIdentityUpdate","$buyerIdentity: CartBuyerIdentityInput!","buyerIdentity: $buyerIdentity",{buyerIdentity:n},e?{buyerIdentity:n}:null).catch(i=>{if(e||!ut(i))throw i;return this._setId(null),null});return this._commit(t,"buyer-identity")})},async checkout(n=h.checkoutTarget){let e=this.id();if(!e||this.state&&!this.count)return null;let t=n==="_blank"?window.open("","_blank"):null;if(t)try{t.opener=null}catch{}try{let s=(await B("query TetherCheckout($id: ID!) { cart(id: $id) { checkoutUrl } }",{id:e})).cart?.checkoutUrl;if(!s)throw new st("No checkoutUrl (cart empty or expired)");return m("checkout",{url:s,cart:this.state}),t&&!t.closed?t.location.href=s:window.location.href=s,s}catch(i){return t&&t.close(),$(i,"checkout failed"),null}},drawers(){return f(document,o("cart-drawer"))},isOpen(){return this.drawers().some(n=>n.classList.contains(h.cartDrawerOpenClass))},open(){if(this.isOpen())return;let n=this.drawers().filter(e=>u(e,"cart-drawer")!=="manual");n.forEach(e=>{e.classList.add(h.cartDrawerOpenClass),e.setAttribute("aria-hidden","false")}),lt(!0),h.cartFocus!==!1&&n[0]&&(z=document.activeElement,setTimeout(()=>{let e=Qt(n[0])[0]||n[0];e===n[0]&&!n[0].hasAttribute("tabindex")&&(n[0].tabIndex=-1),e.focus?.({preventScroll:!0})},60)),typeof h.onCartOpen=="function"&&h.onCartOpen(n),m("cart-toggled",{open:!0})},close(){if(!this.isOpen())return;let n=this.drawers(),e=n.some(t=>t.contains(document.activeElement));n.forEach(t=>{t.classList.remove(h.cartDrawerOpenClass),t.setAttribute("aria-hidden","true")}),lt(!1),e&&z?.isConnected&&z.focus({preventScroll:!0}),z=null,typeof h.onCartClose=="function"&&h.onCartClose(n),m("cart-toggled",{open:!1})},toggle(){this.isOpen()?this.close():this.open()},subscribe(n){let e=()=>n(this.state,{open:this.isOpen(),count:this.count,loaded:this.loaded,warnings:this.warnings}),t=()=>e();return["cart-updated","cart-toggled"].forEach(i=>document.addEventListener(`${h.prefix}:${i}`,t)),e(),()=>["cart-updated","cart-toggled"].forEach(i=>document.removeEventListener(`${h.prefix}:${i}`,t))},render(){let n=this.state,e=n?n.totalQuantity:0,t=a=>n?A(n.cost?.[a]):"";lt(this.isOpen()),f(document,o("cart-count")).forEach(a=>D(a,e)),f(document,o("cart-subtotal")).forEach(a=>D(a,t("subtotalAmount"))),f(document,o("cart-total")).forEach(a=>D(a,t("totalAmount"))),f(document,o("cart-checkout-charge")).forEach(a=>D(a,t("checkoutChargeAmount")));let i=this.lines.some(a=>a.sellingPlanAllocation),s={"cart-empty":e===0,"cart-filled":e>0,"cart-has-subscription":i,"cart-no-subscription":!i,"cart-has-discount":(n?.discountCodes||[]).some(a=>a.applicable),"cart-error":!!this.lastError,"cart-warning":this.warnings.length>0};f(document,o("cart-warning")).forEach(a=>D(a,[...new Set(this.warnings.map(r=>r.message))].join(" "))),f(document,o("show")).forEach(a=>{let r=u(a,"show");r in s&&!a.closest(o("product"))&&T(a,s[r])}),f(document,o("checkout")).forEach(a=>{a.classList.toggle("is-disabled",!e),a.setAttribute("aria-disabled",String(!e))}),f(document,`textarea${o("cart-note")},input${o("cart-note")}`).forEach(a=>{document.activeElement!==a&&(a.value=n?.note||"")}),f(document,o("cart-discounts")).forEach(a=>{x(a,n?.discountCodes||[],(r,c)=>{r.setAttribute(b("discount-code"),c.code);let d=r.querySelector(o("discount","code"))||(r.firstElementChild?null:r);d&&g(d,c.code),r.classList.toggle("is-inapplicable",!c.applicable)})}),f(document,o("cart-lines")).forEach(a=>{xt(a,this.lines,r=>r.id,(r,c)=>Ht(r,c),{onEnter:h.onLineEnter,onLeave:h.onLineLeave})}),n&&F("cart")}};function Rt(n,e){let t=n.merchandise||{},i=n.sellingPlanAllocation?.sellingPlan;switch(e){case"title":return t.product?.title;case"product-title":return t.product?.title;case"variant-title":return t.title==="Default Title"?"":t.title;case"options":return(t.selectedOptions||[]).filter(s=>s.value!=="Default Title").map(s=>`${s.name}: ${s.value}`).join(", ");case"vendor":return t.product?.vendor;case"sku":return t.sku;case"price":return A(n.cost?.amountPerQuantity||t.price);case"compare-at-price":return A(n.cost?.compareAtAmountPerQuantity||t.compareAtPrice);case"line-total":return A(n.cost?.totalAmount);case"line-subtotal":return A(n.cost?.subtotalAmount);case"quantity":return n.quantity;case"plan":return i?.name||"";case"plan-description":return i?.description||"";case"components":return(n.lineComponents||[]).map(s=>`${s.quantity} \xD7 ${s.merchandise?.product?.title||""}`).join(", ");default:if(e.startsWith("attribute:")){let s=e.slice(10);return(n.attributes||[]).find(a=>a.key===s)?.value||""}return}}function Ht(n,e){let t=e.merchandise||{},i=!!e.sellingPlanAllocation;n.setAttribute(b("line-id"),e.id),n.classList.toggle("is-subscription",i),f(n,o("line")).forEach(a=>{let r=u(a,"line");if(r==="image")return E(a,t.image?.url||t.product?.featuredImage?.url,t.product?.title);if(r==="url"){let d=K(t.product?.handle,t.product?.id),w=new URLSearchParams;t.id&&w.set("variant",String(t.id).split("/").pop());let S=e.sellingPlanAllocation?.sellingPlan?.id;S&&w.set("selling_plan",S.split("/").pop());let C=w.toString();return a.setAttribute("href",C?`${d}${d.includes("?")?"&":"?"}${C}`:d)}let c=Rt(e,r);c!==void 0&&D(a,c)});let s={subscription:i,"one-time":!i,"on-sale":parseFloat(e.cost?.compareAtAmountPerQuantity?.amount)>parseFloat(e.cost?.amountPerQuantity?.amount),"has-components":(e.lineComponents||[]).length>0};f(n,o("line-show")).forEach(a=>{let r=u(a,"line-show");r in s&&T(a,s[r])}),f(n,`select${o("line-plan")}`).forEach(a=>{let r=t.product?.handle;r&&U(r).then(c=>{let d=j(c),S=[...!c.requiresSellingPlan&&u(a,"line-plan")!=="subscription-only"?[{value:"",label:a.getAttribute(b("one-time-label"))||"One-time purchase"}]:[],...d.map(P=>({value:P.id,label:P.name}))],C=S.map(P=>P.value).join("|");a.dataset.tetherSig!==C&&(a.innerHTML="",S.forEach(P=>{let y=document.createElement("option");y.value=P.value,y.textContent=P.label,a.appendChild(y)}),a.dataset.tetherSig=C),a.value=e.sellingPlanAllocation?.sellingPlan?.id||"",T(a,d.length>0)}).catch(c=>$(c,"line plan options"))})}var Q=new WeakMap,Nt=()=>o("product");function Kt(n,e){let t=e.indexOf("."),i=e.slice(0,t),s=e.slice(t+1);return(n.metafields||[]).find(r=>r&&r.namespace===i&&r.key===s)||null}var X=new Map;function dt(n){(n||[]).forEach(e=>{e?.handle&&!X.has(e.handle)&&X.set(e.handle,(e.options||[]).map(t=>t.name))})}var Z=class{constructor(e,t){this.root=e,this.product=t,this.selected={},this.variant=null,this.quantity=Math.max(0,parseInt(u(e,"quantity")??"1",10)||(N(e,"quantity")?0:1)),this.plans=j(t),this.purchase="one-time",this.planId=null,Q.set(e,this),e.setAttribute(b("state"),"ready"),e.removeAttribute(b("error")),this.initSelection(),this.initPurchase(),this.renderProduct(),this.bind(),this.renderVariant(),kt(e,"product"),F("product"),m("product-ready",{product:t,scope:this},e)}update(e){let t=this.variant?.id;this.product=e,this.plans=j(e),this.planId&&!this.plans.some(s=>s.id===this.planId)&&(this.planId=this.plans[0]?.id||null),!this.plans.length&&this.purchase==="subscription"&&(this.purchase="one-time");let i=e.variants.nodes.find(s=>s.id===t);i&&(this.selected={},i.selectedOptions.forEach(s=>{this.selected[s.name]=s.value})),this.root.setAttribute(b("state"),"ready"),this.root.removeAttribute(b("error")),this.renderProduct(),this.renderVariant(),F("product"),m("product-updated",{product:e,scope:this},this.root)}$(e){let t=W(this.root,e,Nt());return this.root.matches(e)?[this.root,...t]:t}optName(e){let t=this.product?.options||[];if(!e||t.some(s=>s.name===e))return e;let i=(X.get(this.product.handle)||[]).indexOf(e);return i>=0&&t[i]?t[i].name:e}initSelection(){let e=this.product.variants.nodes,t=e.find(s=>s.availableForSale)||e[0];t&&t.selectedOptions.forEach(s=>{this.selected[s.name]=s.value});let i=new URLSearchParams(window.location.search).get("variant")||u(this.root,"variant");if(i){let s=L.variant(i),a=e.find(r=>r.id===s);a&&(this.selected={},a.selectedOptions.forEach(r=>{this.selected[r.name]=r.value}))}}initPurchase(){if(!this.plans.length)return;let e=new URLSearchParams(window.location.search),t=H(this.product,e.get("selling_plan")),i=H(this.product,u(this.root,"default-plan"));this.planId=(t||i||this.plans[0]).id;let s=t?"subscription":u(this.root,"default-purchase")||h.defaultPurchase;this.purchase=this.product.requiresSellingPlan||s==="subscription"?"subscription":"one-time"}findVariant(){return this.product.variants.nodes.find(e=>e.selectedOptions.every(t=>this.selected[t.name]===t.value))||null}isValueAvailable(e,t){return this.product.variants.nodes.some(i=>i.availableForSale&&i.selectedOptions.every(s=>s.name===e?s.value===t:this.selected[s.name]===s.value))}get plan(){return this.plans.find(e=>e.id===this.planId)||null}get subscriptionAvailable(){return!!(this.variant&&this.plans.some(e=>O(this.variant,e.id)))}get isSubscription(){return this.purchase==="subscription"&&!!O(this.variant,this.planId)}getLine(){if(!this.variant)return null;let e={merchandiseId:this.variant.id,quantity:this.quantity};this.isSubscription&&(e.sellingPlanId=this.planId);let t=this.collectAttributes();return t.length&&(e.attributes=t),e}collectAttributes(){let e=V(qt(this.root,"line-attributes")),t=this.$(o("line-attribute")).map(i=>{let s=u(i,"line-attribute"),a;return i.type==="checkbox"?a=i.checked?i.value&&i.value!=="on"?i.value:"Yes":"":i.type==="radio"?a=i.checked?i.value:null:a=i.value,a==null||a===""?null:{key:s,value:String(a)}}).filter(Boolean);return[...e,...t]}select(e,t){this.selected[e]!==t&&(this.selected[e]=t,this.renderVariant())}selectVariant(e){let t=this.product.variants.nodes.find(i=>i.id===L.variant(e));t&&(this.selected={},t.selectedOptions.forEach(i=>{this.selected[i.name]=i.value}),this.renderVariant())}setPurchase(e){let t=e==="subscription"?"subscription":"one-time";t==="one-time"&&this.product.requiresSellingPlan||this.purchase!==t&&(this.purchase=t,this.renderVariant())}selectPlan(e){let t=H(this.product,e);t&&(this.planId=t.id,this.purchase="subscription",this.renderVariant())}setQuantity(e){let t=N(this.root,"quantity")?0:1;this.quantity=Math.max(t,parseInt(e,10)||0),this.$(o("qty")).forEach(i=>g(i,this.quantity)),this.renderVariant()}async addToCart(){if(!this.variant?.availableForSale)return null;let e=this.getLine();if(!e||e.quantity<1)return null;let t=await Ot.add(e);return m("product-added",{product:this.product,variant:this.variant,line:e},this.root),t}bind(){let e=t=>t&&t.closest(Nt())===this.root;this.root.addEventListener("click",t=>{let i=t.target;if(!i?.closest)return;let s=i.closest(o("option-value"));if(e(s)&&s.closest(o("option-list"))){t.preventDefault(),this.select(this.optName(u(s.closest(o("option-list")),"option-list")),u(s,"option-value"));return}let a=i.closest(o("plan-value"));if(e(a)){t.preventDefault(),this.selectPlan(u(a,"plan-value"));return}let r=i.closest(`${o("purchase")}:not(select)`);if(e(r)){r.tagName!=="INPUT"&&t.preventDefault(),this.setPurchase(u(r,"purchase"));return}let c=i.closest(o("qty-plus"));if(e(c)){t.preventDefault(),this.setQuantity(this.quantity+1);return}let d=i.closest(o("qty-minus"));if(e(d)){t.preventDefault(),this.setQuantity(this.quantity-1);return}let w=i.closest(o("add-to-cart"));e(w)&&(t.preventDefault(),this.onAddClick(w))}),this.root.addEventListener("change",t=>{let i=t.target;e(i)&&(i.matches(`select${o("option")}`)?this.select(this.optName(u(i,"option")),i.value):i.matches(`select${o("variant-select")}`)?this.selectVariant(i.value):i.matches(`select${o("plan")}`)?i.value?this.selectPlan(i.value):this.setPurchase("one-time"):i.matches(`select${o("purchase")}`)?this.setPurchase(i.value):i.matches(o("qty"))&&this.setQuantity(i.value))})}onAddClick(e){let t=e.querySelector(o("add-label"))||e,i=u(e,"adding-text"),s=a=>g(t,a);t.dataset.tetherLabel===void 0&&(t.dataset.tetherLabel=t.tagName==="INPUT"?t.value:t.textContent),G(e,async()=>{i&&s(i);try{await this.addToCart()}catch(a){$(a,"add to cart failed"),m("error",{error:a,context:"add-to-cart"},this.root)}finally{i&&s(t.dataset.tetherLabel)}}).then(()=>{this.renderVariant()})}renderProduct(){let e=this.product;X.has(e.handle)||this.$(`${o("option-list")},select${o("option")}`).map(s=>u(s,"option-list")||u(s,"option")).some(s=>e.options.some(a=>a.name===s))&&dt([e]);let t=this.product;this.$(o("bind")).forEach(i=>{let s=u(i,"bind")||"",a={title:t.title,"description-text":t.description,vendor:t.vendor,type:t.productType,handle:t.handle}[s];if(a!==void 0)return g(i,a);switch(s){case"description":i.innerHTML=t.descriptionHtml||"";return;case"published-at":return g(i,St(t.publishedAt));case"min-price":return g(i,A(t.priceRange.minVariantPrice));case"max-price":return g(i,A(t.priceRange.maxVariantPrice));case"featured-image":return E(i,t.featuredImage?.url,t.featuredImage?.altText||t.title);case"url":case"link":i.setAttribute("href",K(t.handle,t.id)),s==="url"&&!i.firstElementChild&&!i.textContent.trim()&&g(i,t.title);return;case"images":{let r=[...i.children].map(c=>c.tagName==="IMG"?c:c.querySelector("img"));if(!Pt(i)&&r.length===t.images.nodes.length&&r.every((c,d)=>Ct(c?.getAttribute("src"),t.images.nodes[d].url)))return}return x(i,t.images.nodes,(r,c)=>{let d=r.matches(`img,${o("bind","image")}`)?r:r.querySelector("img")||r;E(d,c.url,c.altText||t.title)});case"tags":{let r=(i.getAttribute("exclude")||"").split(",").map(c=>c.trim()).filter(Boolean);return x(i,t.tags.filter(c=>!r.includes(c)),(c,d)=>g(c,d))}default:if(/^image:\d+$/.test(s)){let r=t.images.nodes[s.slice(6)-1]||t.featuredImage;return E(i,r?.url,r?.altText||t.title)}if(s.startsWith("metafield:")){let r=Kt(t,s.slice(10));return r?r.reference?.image?E(i,r.reference.image.url,r.reference.image.altText):(/^(rich_text|multi_line_text)_field$/.test(r.type)&&(i.style.whiteSpace="pre-line"),g(i,r.value)):g(i,"")}return}}),this.$(o("option-list")).forEach(i=>{let s=this.optName(u(i,"option-list")),a=t.options.find(r=>r.name===s);return!a||a.optionValues.length<=1&&N(i,"hide-single")?T(i,!1):x(i,a.optionValues,(r,c)=>{r.setAttribute(b("option-value"),c.name);let d=r.querySelector(o("bind","option-value"));d?g(d,c.name):r.firstElementChild||g(r,c.name);let w=r.querySelector(o("bind","swatch"));w&&c.swatch?.color&&(w.style.backgroundColor=c.swatch.color),w&&c.swatch?.image?.previewImage&&E(w,c.swatch.image.previewImage.url,c.name)})}),this.$(`select${o("option")}`).forEach(i=>{let s=t.options.find(a=>a.name===this.optName(u(i,"option")));if(!s)return T(i,!1);i.innerHTML="",s.optionValues.forEach(a=>i.appendChild(new Option(a.name,a.name)))}),this.$(`select${o("variant-select")}`).forEach(i=>{i.innerHTML="",t.variants.nodes.forEach(s=>i.appendChild(new Option(s.title,s.id)))}),this.$(o("plan-list")).forEach(i=>{let s=(u(i,"plan-list")||"").trim(),a=["","true","all","*"].includes(s.toLowerCase())?null:s,r=this.plans.filter(c=>!a||c.group.name===a);x(i,r,(c,d)=>{c.setAttribute(b("plan-value"),d.id),!c.firstElementChild&&!c.querySelector(o("plan-bind"))&&g(c,d.name)})}),this.$(`select${o("plan")}`).forEach(i=>{i.innerHTML="",!t.requiresSellingPlan&&u(i,"plan")!=="subscription-only"&&i.appendChild(new Option(i.getAttribute(b("one-time-label"))||"One-time purchase","")),this.plans.forEach(s=>i.appendChild(new Option(s.name,s.id)))})}renderVariant(){let e=this.product,t=this.findVariant();if(t||([t]=e.variants.nodes,t&&(this.selected={},t.selectedOptions.forEach(l=>{this.selected[l.name]=l.value}))),this.variant=t,!t)return;if(this.plans.length&&!O(t,this.planId)){let l=this.plans.find(p=>O(t,p.id));l&&(this.planId=l.id)}let i=this.isSubscription,s=t.price,a=this.planId?rt(t,this.planId):null,r=i?a:s,c=i&&O(t,this.planId)?.priceAdjustments?.[0]?.compareAtPrice||t.compareAtPrice,d=c&&parseFloat(c.amount)>parseFloat(r.amount),w=d?Math.round((1-parseFloat(r.amount)/parseFloat(c.amount))*100):0,S=this.planId?ot(t,this.planId):{amount:null,percentage:0},C=this.plan,P={amount:(parseFloat(r.amount)*Math.max(1,this.quantity)).toFixed(2),currencyCode:r.currencyCode},y=A,ht=l=>l?`${l}%`:"",pt={price:()=>y(r),"total-price":()=>y(P),"one-time-price":()=>y(s),"subscription-price":()=>y(a),"subscription-savings":()=>ht(S.percentage),"subscription-savings-amount":()=>y(S.amount),"checkout-charge":()=>y(i?Et(t,this.planId):r),"plan-name":()=>C?.name,"plan-description":()=>C?.description,"plan-group":()=>C?.group?.name,"compare-at-price":()=>d?y(c):"","unit-price":()=>y(t.unitPrice),"discount-percentage":()=>`${w}%`,"discount-amount":()=>d?y({amount:String(parseFloat(c.amount)-parseFloat(r.amount)),currencyCode:r.currencyCode}):"","variant-title":()=>t.title,"variant-id":()=>t.id,sku:()=>t.sku,barcode:()=>t.barcode,stock:()=>t.quantityAvailable,quantity:()=>this.quantity};this.$(o("bind")).forEach(l=>{let p=u(l,"bind");p==="variant-image"?E(l,t.image?.url||e.featuredImage?.url,t.image?.altText||e.title):pt[p]&&g(l,pt[p]())}),this.$(o("option-selected")).forEach(l=>g(l,this.selected[this.optName(u(l,"option-selected"))]||"")),this.$(o("option-list")).forEach(l=>{let p=this.optName(u(l,"option-list"));f(l,o("option-value")).forEach(I=>{let k=u(I,"option-value");J(I,this.selected[p]===k);let v=!this.isValueAvailable(p,k);I.classList.toggle("is-unavailable",v),I.setAttribute("data-unavailable",v?"true":"false")})}),this.$(`select${o("option")}`).forEach(l=>{l.value=this.selected[this.optName(u(l,"option"))]||""}),this.$(`select${o("variant-select")}`).forEach(l=>{l.value=t.id}),this.$(o("purchase")).forEach(l=>{if(l.tagName==="SELECT"){l.value=this.purchase;return}let p=u(l,"purchase");J(l,p===this.purchase);let I=p==="subscription"?!this.subscriptionAvailable:e.requiresSellingPlan;l.classList.toggle("is-unavailable",I)}),this.$(o("plan-value")).forEach(l=>{let p=u(l,"plan-value"),I=this.plans.find(v=>v.id===p),k=!!O(t,p);J(l,i&&p===this.planId),l.classList.toggle("is-unavailable",!k),f(l,o("plan-bind")).forEach(v=>{let q=At(I),mt=ot(t,p),Mt=u(v,"plan-bind"),gt={name:()=>I?.name,description:()=>I?.description,price:()=>y(rt(t,p)),savings:()=>ht(mt.percentage),"savings-amount":()=>y(mt.amount),adjustment:()=>q?q.type==="percentage"?`${q.value}%`:y(q.value):""}[Mt];gt&&g(v,gt())})}),this.$(`select${o("plan")}`).forEach(l=>{l.value=i?this.planId:""});let ft={"in-stock":t.availableForSale,"out-of-stock":!t.availableForSale,backorder:t.availableForSale&&t.currentlyNotInStock,"on-sale":!!d,"not-on-sale":!d,"has-variants":e.variants.nodes.length>1,"single-variant":e.variants.nodes.length===1,"low-stock":t.quantityAvailable!=null&&t.quantityAvailable>0&&t.quantityAvailable<=5,"subscription-available":this.subscriptionAvailable,"subscription-unavailable":!this.subscriptionAvailable,"subscription-only":!!e.requiresSellingPlan,"one-time-available":!e.requiresSellingPlan,"subscription-selected":i,"one-time-selected":!i,"has-savings":i?S.percentage>0:!!d,loading:!1,ready:!0,error:!1,"not-found":!1};this.$(o("show")).forEach(l=>{let p=u(l,"show");p in ft&&(Lt.includes(p)&&!Tt(l,this.root)||T(l,ft[p]))}),this.root.classList.toggle("is-subscription",i),this.root.setAttribute(b("purchase-mode"),i?"subscription":"one-time"),this.$(o("add-to-cart")).forEach(l=>{let p=!t.availableForSale;l.classList.toggle("is-disabled",p),l.setAttribute("aria-disabled",String(p)),"disabled"in l&&(l.disabled=p);let I=u(l,"soldout-text"),k=u(l,"subscribe-text");if(I||k){let v=l.querySelector(o("add-label"))||l;v.dataset.tetherLabel===void 0&&(v.dataset.tetherLabel=v.tagName==="INPUT"?v.value:v.textContent);let q=v.dataset.tetherLabel;i&&k&&(q=k),p&&I&&(q=I),l.getAttribute("aria-busy")!=="true"&&g(v,q)}}),m("variant-changed",{product:e,variant:t,selected:{...this.selected},purchase:this.purchase,sellingPlanId:i?this.planId:null},this.root)}};var tt=()=>`${o("collection")},${o("products")},${o("recommendations")}`,Dt=class{constructor(e){this.root=e,this.products=[],this.pageInfo=null,this.collection=null,_(e,"loading"),e.addEventListener("click",t=>{let i=t.target.closest?.(o("load-more"));i&&i.closest(tt())===e&&(t.preventDefault(),G(i,()=>this.load(!0)))})}$(e){return W(this.root,e,tt())}options(){let e=this.root,t={first:parseInt(u(e,"limit")||"",10)||void 0,sortKey:u(e,"sort")||void 0,reverse:u(e,"reverse")==="true"};return N(e,"collection")?t.collection=u(e,"collection"):t.query=u(e,"products")||"",t}async load(e=!1){let t=this.loadToken=(this.loadToken||0)+1,i=()=>t!==this.loadToken;try{if(N(this.root,"recommendations")){let s=u(this.root,"recommendations"),a=await U(s),r=await $t(a.id,u(this.root,"intent")||"RELATED");if(i())return;let c=parseInt(u(this.root,"limit")||"",10);this.products=c?r.slice(0,c):r}else{let s=await It({...this.options(),after:e?this.pageInfo?.endCursor:null});if(i())return;this.collection=s.collection,this.pageInfo=s.pageInfo,this.products=e?[...this.products,...s.products]:s.products}this.render(),_(this.root,"ready"),m("list-ready",{products:this.products,collection:this.collection},this.root)}catch(s){if(i())return;e||_(this.root,Y(s)?"not-found":"error",s),$(s,"list load failed"),m("error",{error:s,context:"list"},this.root)}}render(){let e=this.collection;this.$(o("collection-bind")).forEach(i=>{switch(u(i,"collection-bind")){case"title":return g(i,e?.title||"");case"description":i.innerHTML=e?.descriptionHtml||"";return;case"image":return E(i,e?.image?.url,e?.image?.altText||e?.title);case"count":return g(i,this.products.length);default:return}}),this.$(o("list")).forEach(i=>{x(i,this.products,(s,a)=>{s.setAttribute(b("product"),a.handle),s.removeAttribute(b("state"))}),m("list-rendered",{container:i},this.root)});let t=this.products.length===0;this.$(o("show")).forEach(i=>{let s=u(i,"show");s==="list-empty"&&(i.style.display=t?"":"none"),s==="list-filled"&&(i.style.display=t?"none":"")}),this.$(o("load-more")).forEach(i=>{i.style.display=this.pageInfo?.hasNextPage?"":"none"}),F("list")}};var et=new WeakSet,$e=new WeakSet,R=new Set,Ft=new Set,Ut=new WeakMap;function Wt(){R.forEach(n=>{n.isConnected||R.delete(n)}),Ft.forEach(n=>{n.root.isConnected||Ft.delete(n)})}function Ee(n,e){return[...n.matches?.(e)?[n]:[],...f(n,e)]}function Jt(n){if(et.has(n))return!1;let{key:e,fromUrl:t,raw:i}=ct(n);if(!e)return t?(et.add(n),_(n,"not-found"),m("product-not-found",{key:null,source:i},n)):n.closest(tt())||nt(`[${b("product")}] without a value. Bind it to the Shopify handle field, or use "url:handle".`,n),!1;et.add(n),R.add(n);let s={};Ut.set(n,s);let a=()=>Ut.get(n)===s;return Q.get(n)?n.setAttribute(b("state"),"updating"):_(n,"loading"),U(e).then(r=>{if(!a())return;let c=Q.get(n);c?c.update(r):new Z(n,r),Gt(n,r),h.revalidate!==!1&&vt(e)&&Yt(e)}).catch(r=>{if(!a())return;let c=Y(r);_(n,c?"not-found":"error",r),c?(m("product-not-found",{key:e,source:i},n),t?M(`product not found: ${e}`):$(r,`product ${e}`)):($(r,`product ${e}`),m("error",{error:r,context:"product",key:e},n))}),!0}function Gt(n,e){let t=u(n,"page-title");!t||typeof document>"u"||(document.title=t.replace(/\{title\}/g,e.title).replace(/\{vendor\}/g,e.vendor||"").replace(/\{type\}/g,e.productType||""))}function Ae(){Wt(),R.forEach(n=>{n.getAttribute(b("state"))!=="error"||n.getAttribute(b("error"))==="not-found"||(et.delete(n),Jt(n))})}var it=new Set;function Yt(n){it.size||setTimeout(zt,0),it.add(n)}function zt(){let n=[...it];it=new Set,wt(n).forEach((e,t)=>e.then(i=>{R.forEach(s=>{let a=Q.get(s);if(!a||!s.isConnected)return;let r=ct(s).key;r!==n[t]&&r!==i.handle&&r!==i.id||JSON.stringify(a.product)!==JSON.stringify(i)&&a.update(i)})}).catch(i=>$(i,`refresh ${n[t]}`)))}var Vt=new WeakSet;function Se(n=document){f(n.ownerDocument||n,'script[type="application/json"][data-tether-hydrate]').forEach(e=>{if(!Vt.has(e)){Vt.add(e);try{let t=JSON.parse(e.textContent);dt(t.products);let i=(s,a)=>String(s||"").toUpperCase()===String(a||"").toUpperCase();i(t.country,h.country)&&i(t.language,h.language)?yt(t.products):M("hydration skipped: other market")}catch(t){nt("bad data-tether-hydrate",t)}}})}export{Qt as a,Ot as b,Q as c,tt as d,Dt as e,et as f,$e as g,R as h,Ft as i,Wt as j,Ee as k,Jt as l,Ae as m,Se as n};
@@ -1,4 +0,0 @@
1
- import{a as U}from"./chunk-VC46IEJQ.js";var j={domain:"",token:"",apiVersion:"2026-07",country:void 0,language:void 0,locale:void 0,markets:null,prefix:"tether",productPath:"/product",collectionPath:"/collection",metafields:[],imagesFirst:25,variantsFirst:100,sellingPlanGroupsFirst:10,sellingPlansFirst:25,collectionFirst:24,retries:2,revalidate:!0,storageKey:null,cartDrawerOpenClass:"is-open",openCartOnAdd:!0,cartFocus:!0,checkoutTarget:"_self",defaultPurchase:"one-time",autoMount:!0,observe:!1,astro:!0,partsUrl:null,formatMoney:null,onLayoutChange:null,onCartOpen:null,onCartClose:null,onLineEnter:null,onLineLeave:null,onValueChange:null,onError:null,debug:!1},i={...j};function ue(e={}){return Object.keys(i).forEach(t=>delete i[t]),Object.assign(i,j,e),i.domain&&(i.domain=String(i.domain).replace(/^https?:\/\//,"").replace(/\/.*$/,"")),i.storageKey||(i.storageKey=`${i.prefix}:cart:${i.domain}`),i.apiVersion==="unstable"&&y('apiVersion "unstable" changes without notice. Pin a dated version (e.g. 2026-07).'),i}var ce=()=>!!(i.domain&&i.token);function T(...e){i.debug&&console.log(`[${i.prefix}]`,...e)}function y(...e){console.warn(`[${i.prefix}]`,...e)}function le(e,t){if(y(t,e&&e.message?e.message:e),typeof i.onError=="function")try{i.onError(e,t)}catch{}}var f=class extends Error{constructor(t,{status:r,errors:n,userErrors:u,code:s}={}){super(t),this.name="TetherError",this.code=s||(r?`HTTP_${r}`:"ERROR"),this.status=r,this.errors=n,this.userErrors=u}};function R(e=i){return`https://${e.domain}/api/${e.apiVersion}/graphql.json`}function G(e,t=i){let r=[];if(t.country&&r.push(`country: ${String(t.country).toUpperCase()}`),t.language&&r.push(`language: ${String(t.language).toUpperCase().replace("-","_")}`),!r.length||/@inContext/.test(e))return e;let n=`@inContext(${r.join(", ")})`;return/^\s*\{/.test(e)?`query ${n} ${e.trimStart()}`:e.replace(/^(\s*(?:query|mutation)(?:\s+\w+)?\s*(?:\([^)]*\))?)/,`$1 ${n}`)}var E=e=>new Promise(t=>setTimeout(t,e));async function N(e,t,r={},{context:n=!0}={}){if(!e.domain||!e.token)throw new f("Tether is not configured: domain and token are required",{code:"NOT_CONFIGURED"});let u=JSON.stringify({query:n?G(t,e):t,variables:r}),s=0;for(;;){let c;try{c=await(e.fetch||fetch)(R(e),{method:"POST",headers:{"Content-Type":"application/json","X-Shopify-Storefront-Access-Token":e.token},body:u})}catch($){if(s++<e.retries){await E(300*2**s);continue}throw new f(`Network error: ${$.message}`,{code:"NETWORK"})}if(c.status===430)throw new f("Shopify security rejection (430)",{status:430});if((c.status===429||c.status>=500)&&s++<e.retries){T("retry",c.status,s),await E(300*2**s);continue}let p;try{p=await c.json()}catch{throw new f(`Invalid response (${c.status})`,{status:c.status})}if(p.errors?.length){if(p.errors.some(l=>/quantityAvailable/.test(l.message||""))&&/\bquantityAvailable\b/.test(u))return T("no inventory permission, retrying without quantityAvailable"),N(e,t.replace(/\bquantityAvailable\b/g,""),r,{context:n});if(p.errors.some(l=>/'warnings'/.test(l.message||""))&&/warnings \{ code message \}/.test(t))return N(e,t.replace(/ warnings \{ code message \}/g,""),r,{context:n});if(p.errors.some(l=>l.extensions?.code==="THROTTLED")&&s++<e.retries){await E(500*2**s);continue}throw new f(p.errors.map(l=>l.message).join(" | "),{status:c.status,errors:p.errors})}if(!c.ok)throw new f(`HTTP ${c.status}`,{status:c.status});return p.data}}var fe=(e,t,r)=>N(i,e,t,r);function me(e,t){if(!e)throw new f(`${t}: empty response`);let r=e.userErrors||[];if(r.length)throw new f(r.map(n=>n.message).join(" | "),{userErrors:r});return e}var a="amount currencyCode",m="url altText width height";function q(e=i){return(e.metafields||[]).map(t=>{if(typeof t=="string"){let r=t.indexOf(".");return{namespace:t.slice(0,r),key:t.slice(r+1)}}return t}).filter(t=>t.namespace&&t.key)}function _(e=i){let t=q(e);return t.length?`metafields(identifiers:[${t.map(n=>`{namespace: ${JSON.stringify(n.namespace)}, key: ${JSON.stringify(n.key)}}`).join(", ")}]){namespace key type value reference{... on MediaImage{image{${m}}}}}`:""}var H=` id name description recurringDeliveries options{name value}priceAdjustments{orderCount adjustmentValue{__typename ... on SellingPlanPercentagePriceAdjustment{adjustmentPercentage}... on SellingPlanFixedAmountPriceAdjustment{adjustmentAmount{${a}}}... on SellingPlanFixedPriceAdjustment{price{${a}}}}}`,K=` sellingPlan{id}checkoutChargeAmount{${a}}remainingBalanceChargeAmount{${a}}priceAdjustments{price{${a}}compareAtPrice{${a}}perDeliveryPrice{${a}}unitPrice{${a}}}`;function ye(e=i){return` fragment TetherProduct on Product{id handle title description descriptionHtml productType vendor tags availableForSale publishedAt onlineStoreUrl requiresSellingPlan seo{title description}featuredImage{${m}}images(first:${e.imagesFirst}){nodes{${m}}}options{name optionValues{name swatch{color image{previewImage{${m}}}}}}priceRange{minVariantPrice{${a}}maxVariantPrice{${a}}}compareAtPriceRange{minVariantPrice{${a}}maxVariantPrice{${a}}}${_(e)} sellingPlanGroups(first:${e.sellingPlanGroupsFirst}){nodes{name appName options{name values}sellingPlans(first:${e.sellingPlansFirst}){nodes{${H}}}}}variants(first:${e.variantsFirst}){nodes{id title availableForSale currentlyNotInStock quantityAvailable sku barcode selectedOptions{name value}image{${m}}price{${a}}compareAtPrice{${a}}unitPrice{${a}}sellingPlanAllocations(first:${e.sellingPlansFirst}){nodes{${K}}}}}}`}function xe(){return` fragment TetherCart on Cart{id checkoutUrl totalQuantity note createdAt updatedAt attributes{key value}buyerIdentity{countryCode email}discountCodes{code applicable}cost{subtotalAmount{${a}}totalAmount{${a}}checkoutChargeAmount{${a}}}lines(first:100){nodes{id quantity attributes{key value}cost{totalAmount{${a}}subtotalAmount{${a}}amountPerQuantity{${a}}compareAtAmountPerQuantity{${a}}}discountAllocations{discountedAmount{${a}}}sellingPlanAllocation{sellingPlan{id name description recurringDeliveries options{name value}}checkoutChargeAmount{${a}}priceAdjustments{price{${a}}compareAtPrice{${a}}perDeliveryPrice{${a}}}}merchandise{... on ProductVariant{id title sku availableForSale requiresShipping image{${m}}price{${a}}compareAtPrice{${a}}selectedOptions{name value}product{id title handle vendor featuredImage{${m}}}}}... on ComponentizableCartLine{lineComponents{id quantity merchandise{... on ProductVariant{id title product{title handle}}}}}}}}`}function $e(){return`
2
- id handle title description descriptionHtml
3
- image { ${m} }
4
- seo { title description }`}var Y={};U(Y,{GID:()=>I,allPlans:()=>S,allocationFor:()=>v,findPlan:()=>X,hasPlans:()=>J,planAdjustment:()=>W,planCheckoutCharge:()=>z,planPrice:()=>L,planSavings:()=>Q,plansForVariant:()=>B,toGid:()=>w});var I={product:e=>w(e,"Product"),variant:e=>w(e,"ProductVariant"),sellingPlan:e=>w(e,"SellingPlan")};function w(e,t){let r=String(e??"").trim();return r?r.startsWith("gid://")?r:/^\d+$/.test(r)?`gid://shopify/${t}/${r}`:r:null}function S(e){return(e?.sellingPlanGroups?.nodes||[]).flatMap(r=>(r.sellingPlans?.nodes||[]).map(n=>({...n,group:r})))}function J(e){return S(e).length>0}function v(e,t){return!e||!t?null:(e.sellingPlanAllocations?.nodes||[]).find(r=>r.sellingPlan?.id===t)||null}function B(e,t){return S(e).filter(r=>v(t,r.id))}function L(e,t){let n=v(e,t)?.priceAdjustments?.[0];return n?.perDeliveryPrice||n?.price||e?.price||null}function z(e,t){let r=v(e,t);return r?.checkoutChargeAmount||r?.priceAdjustments?.[0]?.price||e?.price||null}function Q(e,t){let r=parseFloat(e?.price?.amount),n=parseFloat(L(e,t)?.amount);return!Number.isFinite(r)||!Number.isFinite(n)||r<=0||n>=r?{amount:null,percentage:0}:{amount:{amount:(r-n).toFixed(2),currencyCode:e.price.currencyCode},percentage:Math.round((1-n/r)*100)}}function W(e){let t=e?.priceAdjustments?.[0]?.adjustmentValue;if(!t)return null;switch(t.__typename){case"SellingPlanPercentagePriceAdjustment":return{type:"percentage",value:t.adjustmentPercentage};case"SellingPlanFixedAmountPriceAdjustment":return{type:"amount",value:t.adjustmentAmount};case"SellingPlanFixedPriceAdjustment":return{type:"price",value:t.price};default:return null}}function X(e,t){if(!t)return null;let r=S(e),n=I.sellingPlan(t),u=String(t).trim().toLowerCase();return r.find(s=>s.id===n)||r.find(s=>s.name.toLowerCase()===u)||r.find(s=>(s.options||[]).some(c=>String(c.value).toLowerCase()===u))||null}var P=e=>`${i.prefix}-${e}`,be=(e,t)=>t===void 0?`[${P(e)}]`:`[${P(e)}="${t}"]`,Z=(e,t)=>e.getAttribute(P(t)),we=(e,t)=>e.hasAttribute(P(t)),ee=()=>`data-${i.prefix}-key`;function Se(e,t){let r=i.productPath||"/product";if(!/\{(handle|id)\}/.test(r))return`${r.replace(/\/$/,"")}/${e}`;let n=t?String(t).split("/").pop():"";return r.replace(/\{handle\}/g,encodeURIComponent(e||"")).replace(/\{id\}/g,encodeURIComponent(n))}var D=(e,t)=>e?[...e.querySelectorAll(t)]:[],ve=(e,t,r)=>D(e,t).filter(n=>n.closest(r)===e);function te(e,t,r=document){r.dispatchEvent(new CustomEvent(`${i.prefix}:${e}`,{detail:t,bubbles:!0}))}function Ce(e){if(!e)return"";if(typeof i.formatMoney=="function")return i.formatMoney(e,i.locale);let t=parseFloat(e.amount);if(Number.isNaN(t))return"";try{return new Intl.NumberFormat(i.locale||void 0,{style:"currency",currency:e.currencyCode}).format(t)}catch{return`${e.currencyCode} ${t.toFixed(2)}`}}function ke(e){if(!e)return"";try{return new Date(e).toLocaleDateString(i.locale||void 0)}catch{return""}}function re(e,{width:t,height:r,crop:n}={}){if(!e)return e;try{let u=new URL(e);return t&&u.searchParams.set("width",String(t)),r&&u.searchParams.set("height",String(r)),n&&u.searchParams.set("crop",n),u.toString()}catch{return e}}function ne(e,t){if(!e||!t)return e;let r=n=>t.getAttribute(n);return re(e,{width:r("max-width"),height:r("max-height"),crop:r("max-width")||r("max-height")?r("crop"):null})}var ie=(e,t)=>!!(e&&t)&&e.split("?")[0]===t.split("?")[0];function Te(e,t,r){if(!t)return;let n=ne(t,e);if(e.tagName==="IMG"&&ie(e.getAttribute("src"),n)){r!=null&&!e.getAttribute("alt")&&e.setAttribute("alt",r);return}e.tagName==="IMG"?(e.removeAttribute("srcset"),e.removeAttribute("sizes"),e.setAttribute("src",n),r!=null&&e.setAttribute("alt",r),e.getAttribute("loading")||e.setAttribute("loading","lazy")):e.style.backgroundImage=`url("${n}")`}function oe(e,t){let r=t==null?"":String(t);e.tagName==="INPUT"||e.tagName==="TEXTAREA"?e.value=r:e.textContent=r}var O=e=>e.tagName==="INPUT"||e.tagName==="TEXTAREA"?e.value:e.textContent;function Ee(e,t){let r=O(e);oe(e,t);let n=O(e);if(r!==n&&e.dataset.tetherSeen&&typeof i.onValueChange=="function")try{i.onValueChange(e,r)}catch{}e.dataset.tetherSeen="1"}function Ne(e,t){let r="tetherPrevDisplay";if(t){if(e.dataset.tetherShown!==void 0)return;e.dataset.tetherShown="",e.removeAttribute("hidden"),e.dataset[r]!==void 0?(e.style.display=e.dataset[r],delete e.dataset[r]):e.style.display==="none"&&(e.style.display=""),typeof getComputedStyle=="function"&&getComputedStyle(e).display==="none"&&(e.style.display=e.getAttribute(`${i.prefix}-display`)||"block")}else delete e.dataset.tetherShown,e.dataset[r]===void 0&&e.style.display!=="none"&&(e.dataset[r]=e.style.display||""),e.style.display="none"}function Fe(e,t){e.classList.toggle("is-selected",t),e.setAttribute("aria-pressed",t?"true":"false"),e.tagName==="INPUT"&&(e.type==="radio"||e.type==="checkbox")&&(e.checked=t);let r=e.querySelector?.('input[type="radio"],input[type="checkbox"]');r&&(r.checked=t)}var C=new WeakMap,je=e=>!!C.get(e);function M(e){if(!C.has(e)){let t=e.firstElementChild?.cloneNode(!0)??null;C.set(e,t),e.innerHTML=""}return C.get(e)}function Ie(e,t,r){let n=M(e);if(!n){y("repeater without a template child",e);return}e.innerHTML="",t.forEach((u,s)=>{let c=n.cloneNode(!0);r(c,u,s),e.appendChild(c)})}function Le(e,t,r,n,{onEnter:u,onLeave:s}={}){let c=M(e);if(!c){y("repeater without a template child",e);return}let x=ee(),p=`data-${i.prefix}-leaving`,$=()=>[...e.children].filter(o=>o.hasAttribute(x)&&!o.hasAttribute(p)),A=new Map($().map(o=>[o.getAttribute(x),o])),l=[],b=new Set;t.forEach((o,d)=>{let g=String(r(o,d)),h=A.get(g);h?A.delete(g):(h=c.cloneNode(!0),h.setAttribute(x,g),b.add(h)),n(h,o,d,b.has(h)),l.push(h)});let k=l.filter(o=>!b.has(o)),F=$().filter(o=>k.includes(o));if(!(F.length===k.length&&F.every((o,d)=>o===k[d]))){A.forEach(o=>o.remove()),l.forEach(o=>e.appendChild(o));return}l.forEach((o,d)=>{if(!b.has(o))return;let g=l[d-1];if(g&&g.parentNode===e?g.after(o):e.prepend(o),u)try{u(o)}catch{}}),A.forEach(o=>{o.setAttribute(p,""),o.removeAttribute(x),o.style.pointerEvents="none",o.inert=!0;let d=()=>o.remove();if(s)try{s(o,d)}catch{d()}else d()})}var V=null;function ae(e){clearTimeout(V),V=setTimeout(()=>{typeof i.onLayoutChange=="function"?i.onLayoutChange(e):window.ScrollTrigger?.refresh&&window.ScrollTrigger.refresh(),te("layout-changed",{reason:e})},120)}function Oe(e,t){let r=D(e,"img").filter(s=>!s.complete);if(!r.length)return;let n=r.length,u=()=>{--n===0&&ae(`${t}:images`)};r.forEach(s=>{s.addEventListener("load",u,{once:!0}),s.addEventListener("error",u,{once:!0})})}function Ve(e,t){return e.getAttribute("aria-busy")==="true"?Promise.resolve():(e.setAttribute("aria-busy","true"),Promise.resolve().then(t).finally(()=>e.removeAttribute("aria-busy")))}function De(e,t){let r=e&&Z(e,t);if(!r)return null;try{return JSON.parse(r)}catch{return y(`invalid JSON in ${P(t)}`,e),null}}function Me(e){return e?Array.isArray(e)?e.map(t=>({key:String(t.key),value:String(t.value??"")})):Object.entries(e).map(([t,r])=>({key:t,value:String(r??"")})):[]}export{j as a,i as b,ue as c,ce as d,T as e,y as f,le as g,f as h,R as i,G as j,N as k,fe as l,me as m,ye as n,xe as o,$e as p,I as q,S as r,v as s,L as t,z as u,Q as v,W as w,X as x,Y as y,P as z,be as A,Z as B,we as C,Se as D,D as E,ve as F,te as G,Ce as H,ke as I,re as J,ie as K,Te as L,oe as M,Ee as N,Ne as O,Fe as P,je as Q,Ie as R,Le as S,ae as T,Oe as U,Ve as V,De as W,Me as X};
@@ -1 +0,0 @@
1
- import{A as E,B as I,F as K,O as R,b,e as q,h as w,l as p,n as P,p as C,z as S}from"./chunk-HXKV6QGB.js";var a=new Map,U=[],v=new Map,D=!1,$=0,F=t=>typeof t=="string"&&t.startsWith("gid://");function x(t){let e=String(t??"").trim();if(!e)return null;if(F(e))return e;if(/^\d+$/.test(e))return`gid://shopify/Product/${e}`;let r=e.match(/\/products\/([^/?#]+)/);return r?r[1]:e}function g(t){if(!t)return;let e=Promise.resolve(t);a.set(t.handle,e),a.set(t.id,e)}function O(t){let e=x(t);if(!e)return Promise.reject(new w("empty product key",{code:"NOT_FOUND"}));if(a.has(e))return a.get(e);let r=new Promise((n,o)=>{v.set(e,{resolve:n,reject:o}),U.push(e),D||(D=!0,setTimeout(j,0))});return a.set(e,r),r}async function j(){D=!1;let t=$,e=U.splice(0);if(!e.length)return;let r=e.filter(F),n=e.filter(i=>!F(i)),o=[],d=[],f={};r.length&&(o.push("$ids: [ID!]!"),f.ids=r,d.push("byId: nodes(ids: $ids) { ... on Product { ...TetherProduct } }")),n.forEach((i,s)=>{o.push(`$h${s}: String!`),f[`h${s}`]=i,d.push(`h${s}: product(handle: $h${s}) { ...TetherProduct }`)});let m=`query TetherProducts(${o.join(",")}){${d.join(" ")}}${P()}`;q("fetch products",e);let l=(i,s)=>{let c=v.get(i);v.delete(i),c&&s(c)};try{let i=await p(m,f),s=new Map;(i.byId||[]).forEach(c=>{c&&s.set(c.id,c)}),n.forEach((c,h)=>{i[`h${h}`]&&s.set(c,i[`h${h}`])}),e.forEach(c=>l(c,h=>{if(s.has(c)){let y=s.get(c);t===$&&(g(y),T.set(y.handle,Date.now()),T.set(y.id,Date.now())),h.resolve(y)}else t===$&&a.delete(c),h.reject(new w(`Product not found: ${c}`,{code:"NOT_FOUND"}))}))}catch(i){e.forEach(s=>l(s,c=>{t===$&&a.delete(s),c.reject(i)}))}}function k(){a.clear(),$++,u.clear(),T.clear()}var u=new Set,T=new Map,A=6e4;function H(t){(t||[]).forEach(e=>{!e?.id||!e.handle||Date.now()-(T.get(e.handle)||0)<A&&a.has(e.handle)||(g(e),u.add(e.handle),u.add(e.id))})}function z(t){let e=x(t);if(!u.has(e))return!1;let r=a.get(e);return u.delete(e),r?.then?.(n=>{u.delete(n.handle),u.delete(n.id)}),!0}function G(t){return t.forEach(e=>a.delete(x(e))),t.map(e=>O(e))}async function Y({collection:t,query:e,first:r=b.collectionFirst,after:n=null,sortKey:o,reverse:d=!1,filters:f}={}){let m=P();if(t){let i=await p(`query TetherCollection($handle:String!,$first:Int!,$after:String,$sortKey:ProductCollectionSortKeys,$reverse:Boolean,$filters:[ProductFilter!]){collection(handle:$handle){${C()} products(first:$first,after:$after,sortKey:$sortKey,reverse:$reverse,filters:$filters){nodes{...TetherProduct}pageInfo{hasNextPage endCursor}}}}${m}`,{handle:t,first:r,after:n,sortKey:o||void 0,reverse:d,filters:f||void 0});if(!i.collection)throw new w(`Collection not found: ${t}`,{code:"NOT_FOUND"});let{products:s,...c}=i.collection;return s.nodes.forEach(g),{collection:c,products:s.nodes,pageInfo:s.pageInfo}}let l=await p(`query TetherSearch($first:Int!,$after:String,$query:String,$sortKey:ProductSortKeys,$reverse:Boolean){products(first:$first,after:$after,query:$query,sortKey:$sortKey,reverse:$reverse){nodes{...TetherProduct}pageInfo{hasNextPage endCursor}}}${m}`,{first:r,after:n,query:e||void 0,sortKey:o||void 0,reverse:d});return l.products.nodes.forEach(g),{collection:null,products:l.products.nodes,pageInfo:l.products.pageInfo}}async function J(t,e="RELATED"){let r=x(typeof t=="object"?t?.id:t),n=r?.startsWith("gid://"),d=(await p(`query TetherRecs(${n?"$id: ID!":"$handle: String!"},$intent:ProductRecommendationIntent){productRecommendations(${n?"productId: $id":"productHandle: $handle"},intent:$intent){...TetherProduct}}${P()}`,n?{id:r,intent:e}:{handle:r,intent:e})).productRecommendations||[];return d.forEach(g),d}function X(t,e=window.location){let r=(I(t,"product")||"").trim();if(!r.toLowerCase().startsWith("url:"))return{key:r||null,fromUrl:!1,raw:r};let n=r.slice(4).trim(),o=null;if(n.toLowerCase()==="path"){let d=e.pathname.split("/").filter(Boolean);o=d.length?_(d[d.length-1]):null}else o=new URLSearchParams(e.search).get(n||"handle");return o=o&&o.trim()?o.trim():null,{key:o,fromUrl:!0,raw:r}}function _(t){try{return decodeURIComponent(t)}catch{return t}}var L=["loading","ready","error","not-found"],Z=t=>t?.code==="NOT_FOUND",N=()=>["product","collection","products","recommendations"].map(t=>E(t)).join(","),ee=(t,e)=>t===e||t.parentElement?.closest(N())===e;function te(t,e,r){let n=e==="not-found";t.setAttribute(S("state"),n?"error":e),e==="error"||n?t.setAttribute(S("error"),n?"not-found":r?.code||"error"):t.removeAttribute(S("error")),K(t,E("show"),N()).forEach(o=>{let d=I(o,"show");L.includes(d)&&R(o,d===e)}),(e==="error"||n)&&K(t,E("add-to-cart"),N()).forEach(o=>{o.classList.add("is-disabled"),o.setAttribute("aria-disabled","true"),"disabled"in o&&(o.disabled=!0)})}export{x as a,g as b,O as c,k as d,H as e,z as f,G as g,Y as h,J as i,X as j,L as k,Z as l,N as m,ee as n,te as o};
@@ -1 +0,0 @@
1
- import{c as V}from"./chunk-RBVRRQIU.js";import{c as S,j as y,m as P}from"./chunk-RNZMTQTX.js";import{A as a,B as g,E as c,Q as v,b as l,d as b,i as k,l as w,r as E,z as u}from"./chunk-HXKV6QGB.js";import"./chunk-VC46IEJQ.js";var D=e=>{let o=/^(\d{4})-(01|04|07|10)$/.exec(String(e||""));return o?{y:+o[1],m:+o[2]}:null},A=(e,o)=>`${e}-${String(o).padStart(2,"0")}`;function x(e,o=1){let n=D(e);if(!n)throw new Error(`not a quarterly Shopify API version: ${e}`);let p=n.y*4+(n.m-1)/3+o;return A(Math.floor(p/4),p%4*3+1)}function M(e,o=new Date){if(e==="unstable")return{version:e,ok:!1,note:'"unstable" changes without notice: pin a dated version such as 2026-07'};let n=D(e);if(!n)return{version:e,ok:!1,note:"not a quarterly version (YYYY-01, -04, -07 or -10)"};let p=new Date(Date.UTC(n.y,n.m-1,1)),r=new Date(Date.UTC(n.y+1,n.m-1,1)),h=(r-o)/(30.44*24*3600*1e3),i=`supported until about ${r.toISOString().slice(0,7)}`;return p>o?i="not released yet (release candidate at best)":h<=0?i="no longer supported: Shopify falls back to the oldest supported version. Upgrade (see README)":h<3&&(i=`${i}: plan the upgrade to ${x(e)} or later (see README)`),{version:e,ok:p<=o&&h>0,monthsLeft:Math.round(h),next:x(e),note:i}}var q=e=>e?`${String(e).slice(0,4)}\u2026${String(e).slice(-4)} (${String(e).length} chars)`:"(missing)";function C(e){let o=e.id?`#${e.id}`:"",n=e.className&&typeof e.className=="string"?`.${e.className.trim().split(/\s+/).slice(0,2).join(".")}`:"";return`<${e.tagName.toLowerCase()}${o}${n}>`}function I(e=document){let o=[],n=(t,s,d)=>o.push({level:t,message:s,element:d?C(d):void 0,el:d}),p=t=>!!t.closest(a("product")),r=`${a("collection")},${a("products")},${a("recommendations")}`;c(e,a("product")).forEach(t=>{if(t.closest(r)&&t.closest(a("list")))return;let{key:s,fromUrl:d,raw:f}=y(t);f?d&&!s&&n("warn",`${u("product")}="${f}" but this URL has no value for it (e.g. ?handle=my-product).`,t):n("error",`${u("product")} has no value. Bind it to the Shopify handle (CMS field), or use "url:handle".`,t)}),["bind","option-list","option","variant-select","add-to-cart","purchase","plan-list","plan","qty","line-attribute"].forEach(t=>{c(e,a(t)).forEach(s=>{p(s)||t==="line-attribute"&&s.closest(a("group"))||n("warn",`${u(t)} outside any ${u("product")}: it does nothing here.`,s)})}),c(e,a("group-add")).forEach(t=>{t.closest(a("group"))||n("warn",`${u("group-add")} outside a ${u("group")}.`,t)}),[...c(e,a("option-list")),...c(e,a("plan-list")),...c(e,a("cart-lines")),...c(e,a("list"))].forEach(t=>{!t.firstElementChild&&!v(t)&&n("warn","Repeater without a template child: put one example element inside it.",t)}),c(e,r).forEach(t=>{c(t,a("list")).length||n("error",`List without a ${u("list")} child: products have nowhere to render.`,t)});let i=new Set(["in-stock","out-of-stock","backorder","low-stock","on-sale","not-on-sale","has-variants","single-variant","subscription-available","subscription-unavailable","subscription-only","one-time-available","subscription-selected","one-time-selected","has-savings","loading","ready","error","not-found","cart-empty","cart-filled","cart-has-subscription","cart-no-subscription","cart-has-discount","cart-error","cart-warning","list-empty","list-filled"]);return c(e,a("show")).forEach(t=>{let s=g(t,"show");i.has(s)||n("warn",`Unknown ${u("show")}="${s}".`,t)}),c(e,a("bind")).forEach(t=>{let s=g(t,"bind")||"";if(!s.startsWith("metafield:"))return;let d=s.slice(10);(l.metafields||[]).some(m=>(typeof m=="string"?m:`${m.namespace}.${m.key}`)===d)||n("error",`${u("bind")}="${s}" but "${d}" is not in config.metafields, so it stays empty.`,t)}),c(e,a("cart-open")).length&&!c(e,a("cart-drawer")).length&&n("warn",`${u("cart-open")} exists but there is no ${u("cart-drawer")} on this page.`),c(e,a("cart-drawer")).length>1&&n("warn",`More than one ${u("cart-drawer")}.`),c(e,`[${u("state")}="error"]`).forEach(t=>{t.matches(P())&&n("error",`Scope failed to load (${g(t,"error")||"error"}).`,t)}),o}async function F({root:e=document,silent:o=!1,version:n,now:p=new Date}={}){let r={version:n,config:{domain:l.domain||"(missing)",token:q(l.token),apiVersion:l.apiVersion,endpoint:l.domain?k():null,country:l.country,language:l.language,locale:l.locale,markets:l.markets||null,prefix:l.prefix,productPath:l.productPath,metafields:l.metafields,autoMount:l.autoMount,observe:l.observe,onError:typeof l.onError=="function"},apiVersion:M(l.apiVersion,p),market:l.markets?.detect?V(l.markets):null,reachable:!1,shop:null,latencyMs:null,products:[],sellingPlans:[],problems:[]};if(!b())r.problems.push({level:"error",message:"TETHER_CONFIG needs domain and token."});else{let h=Date.now();try{let i=await w("query TetherDiagnose { shop { name primaryDomain { url } paymentSettings { currencyCode } } }",{},{context:!1});r.reachable=!0,r.shop={name:i.shop?.name,url:i.shop?.primaryDomain?.url,currency:i.shop?.paymentSettings?.currencyCode}}catch(i){let t=i.status===401||i.status===403?"Token rejected: use the public Storefront token from the Headless channel, not an Admin token.":i.status===404?"Store or API version not found: check domain (x.myshopify.com) and apiVersion.":"";r.problems.push({level:"error",message:`Storefront API not reachable: ${i.message}${t?`. ${t}`:""}`})}if(r.latencyMs=Date.now()-h,r.reachable){let i=[...new Set(c(e,a("product")).map(s=>y(s).key).filter(Boolean))].slice(0,20);(await Promise.allSettled(i.map(s=>S(s)))).forEach((s,d)=>{if(s.status==="fulfilled"){let f=s.value,m=E(f);r.products.push({key:i[d],handle:f.handle,title:f.title,plans:m.length,requiresSellingPlan:f.requiresSellingPlan}),m.forEach($=>r.sellingPlans.push({product:f.handle,group:$.group?.name,plan:$.name,id:$.id}))}else r.products.push({key:i[d],error:s.reason?.message}),r.problems.push({level:"error",message:`Product "${i[d]}": ${s.reason?.message}. Is it published to the Headless channel?`})})}}return(!r.apiVersion.ok||(r.apiVersion.monthsLeft??99)<3)&&r.problems.push({level:r.apiVersion.ok?"warn":"error",message:`apiVersion ${l.apiVersion}: ${r.apiVersion.note}`}),typeof document<"u"&&r.problems.push(...I(e)),o||T(r),r}function T(e){let o=console,n=e.problems.filter(p=>p.level==="error").length;o.group?.(`[${l.prefix}] diagnose: ${n?`${n} error(s)`:"no errors"}, ${e.problems.length-n} warning(s)`),o.log("version",e.version,"| API",e.apiVersion.version,`(${e.apiVersion.note})`),o.log("config",e.config),e.market&&o.log("market detected",e.market,"(in use:",e.config.country,e.config.language,")"),o.log(e.reachable?`Storefront API reachable: ${e.shop?.name} (${e.latencyMs} ms)`:"Storefront API NOT reachable"),e.products.length&&(o.table||o.log)(e.products),e.sellingPlans.length?(o.table||o.log)(e.sellingPlans):e.reachable&&o.log("No selling plans on the products of this page."),e.problems.forEach(p=>(p.level==="error"?o.error:o.warn)(p.message,p.el||"")),o.groupEnd?.()}export{M as apiVersionStatus,I as checkMarkup,F as diagnose};
@@ -1 +0,0 @@
1
- import{b as d,d as x,f as U,h as z,i as B,j as M,k as v,l as N}from"./chunk-HHMOPDZG.js";import{a as C,b as k,c as T,d as A,e as I,f as b}from"./chunk-RBVRRQIU.js";import{d as w}from"./chunk-RNZMTQTX.js";import{A as h,B as E,G as P,b as t,g,h as L,z as p}from"./chunk-HXKV6QGB.js";import"./chunk-VC46IEJQ.js";function O(s=document){let l=v(s,`select${h("country-select")}`),m=v(s,h("country-bind"));return!l.length&&!m.length?Promise.resolve():b().then(a=>{let n=(t.country||a.country?.isoCode||"").toUpperCase(),u=[...a.availableCountries||[]].sort((e,o)=>e.name.localeCompare(o.name,t.locale||void 0)),i=u.find(e=>e.isoCode===n)||a.country,f=(e,o)=>e.replace(/\{name\}/g,o.name).replace(/\{code\}/g,o.isoCode).replace(/\{currency\}/g,o.currency?.isoCode||"").replace(/\{symbol\}/g,o.currency?.symbol||"");l.forEach(e=>{let o=e.getAttribute(p("country-label"))||"{name} ({currency})",c=(e.getAttribute(p("countries"))||"").toUpperCase().split(/[\s,]+/).filter(Boolean),r=c.length?c.map(y=>u.find(S=>S.isoCode===y)).filter(Boolean):u;e.innerHTML="",r.forEach(y=>e.appendChild(new Option(f(o,y),y.isoCode))),e.value=n,e.value!==n&&(e.selectedIndex=-1),e.disabled=r.length<2&&!e.hasAttribute(p("always-enabled"))}),m.forEach(e=>{let o=E(e,"country-bind")||"name",c={name:i?.name,code:i?.isoCode,currency:i?.currency?.isoCode,symbol:i?.currency?.symbol}[o];e.textContent=c||""})}).catch(a=>g(a,"country select"))}async function q(s,{language:l,locale:m,remember:a=!0}={}){let n=C(s);if(!n)throw new L(`Not a country code: ${s}`,{code:"INVALID_COUNTRY"});let u=Object.values(t.markets?.locales||{}).find(r=>C(r?.country)===n),i=m||u?.locale,f=i||t.locale,e=l===void 0?t.language:k(l),o=n!==String(t.country||"").toUpperCase()||e!==t.language||f!==t.locale;if(t.country=n,t.language=e||void 0,t.locale=f,a&&A({country:n,language:e,locale:i}),!o)return O(),d.state;w();let c=[];return l!==void 0&&I(),d.id()&&c.push(d.setBuyerIdentity({countryCode:n},{create:!1}).catch(r=>g(r,"cart country"))),M(),B.forEach(r=>c.push(r.load())),z.forEach(r=>{r.closest(x())||(U.delete(r),N(r))}),O(),P("country-changed",{country:n,language:t.language}),await Promise.all(c),d.state}export{T as detectMarket,b as getLocalization,O as renderCountrySelects,q as setCountry};