notify-me-wl 3.6.0 → 3.6.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,146 +1,83 @@
1
- ### Coming Soon/Notify Me Widget
1
+ # Notify Me / Coming Soon widget
2
2
 
3
- This JavaScript widget allows users to register their interest in a product that is either "coming soon" or "out of stock" (aka "notify me"). Depending on the `data-type` attribute provided, the widget will display appropriate messages and functionality.
3
+ A drop-in widget that lets shoppers register interest in an out-of-stock size ("Notify Me") or an upcoming product ("Coming Soon"). It opens as a modal or a slide-in panel, collects an email (and optionally more), and sends it to The Wishlist's Customer Interest API.
4
4
 
5
- ### Official docs
5
+ Full integration guide: [TWC docs — Notify Me & Coming Soon](https://the-wishlist-documentation.gitbook.io/the-wishlist-documentation-docs/notify-me-and-coming-soon/overview)
6
6
 
7
- The full implementation guide lives in the TWC documentation site — start here:
7
+ **Contents:** [Quick start](#quick-start) · [Product data](#product-data) · [Opening the widget](#opening-the-widget) · [Configuration](#configuration) · [Copy](#copy) · [Styling](#styling) · [Authentication](#authentication) · [Recommendations](#recommendations) · [What gets sent](#what-gets-sent) · [Development](#development) · [Upgrading](#upgrading)
8
8
 
9
- - [Notify Me & Coming Soon — Overview](https://the-wishlist-documentation.gitbook.io/the-wishlist-documentation-docs/notify-me-and-coming-soon/overview)
9
+ ---
10
10
 
11
- This README covers installation and the widget's configuration attributes; the docs
12
- site is the primary reference for integration, the Customer Interest API, and
13
- related guides.
11
+ ## Quick start
14
12
 
15
- #### Features:
16
- - Clean, centered modal that inherits the host store's font and stays out of the theme's way (all styles are namespaced under `#twc-nm-overlay`).
17
- - Dynamically display a labeled form to collect user information (email, mobile, first/last name, country, province).
18
- - Adjust form copy and button text based on the widget type (`notify-me` or `coming-soon`).
19
- - Inline success/error feedback (no browser `alert`s); the submit button shows a "Sending…" loading state.
20
- - Closes on the × button, clicking the backdrop, or pressing `Esc`; locks background scroll while open and respects `prefers-reduced-motion`.
21
- - Optional email/SMS marketing opt-ins, each shown only when its field is: "Email me store updates" with `email`, "Text me store updates" with `mobile`.
22
- - Configurable from one JSON block, with the widget's main strings overridable — see [JSON configuration](#json-configuration) and [Copy](#copy).
23
- - Optional product summary (image, name, size, now/was price) — see [Product summary](#product-summary).
24
- - Theme integration through CSS custom properties and per-element theme classes — see [Styling and theme classes](#styling-and-theme-classes).
25
- - Two display modes: a centred modal (default) or a right-hand slide-in panel — see [Display modes](#display-modes).
26
- - Opens from any element marked `data-twc-notify-me` — PLP size buttons, "Size out of stock?" links — for the product it belongs to, with the right size pre-selected. See [Opening from product listing pages](#opening-from-product-listing-pages).
27
- - Two auth modes: Shopify App Proxy (default) or a merchant-supplied token — see [Configuration](#configuration).
28
- - Optional "Shop similar styles" section under the form, showing personalised product recommendations for the customer.
13
+ Three pieces go into the theme. This example is a Shopify product page.
29
14
 
30
- ### Installation
15
+ **1. The product**, so the widget knows which sizes are out of stock:
31
16
 
32
- Add the following script to your HTML to include the widget:
33
-
34
- ```html
35
- <script src="https://cdn.jsdelivr.net/npm/notify-me-wl/build/notify-me-wl.min.js"></script>
36
- ```
37
-
38
- ### Development / Build
39
-
40
- Source lives in `src/` (TypeScript) and is bundled with Rollup into `build/`.
41
-
42
- ```bash
43
- npm install # install dev dependencies
44
- npm run build # emit build/notify-me-wl.min.js
45
- npm run dev # rebuild on change (watch mode)
46
- npm run typecheck # type-check without emitting
47
- npm run selftest # build, then run the harness pages in headless Chrome
48
- ```
49
-
50
- > **v2.0.0 path change:** the distributed bundle moved from `index.js` to
51
- > `build/notify-me-wl.min.js`. Update embeds to the new CDN URL above.
52
-
53
- ### Usage
54
-
55
- Add `<div id="notification-widget"></div>` once per page and put the configuration attributes below on it (or, as older installs do, on a `<button id="popup-open">` — its attributes win where both carry one). Anything marked `data-twc-notify-me` opens the widget; the legacy `#popup-open` button does too. `data-fields` is a JSON string of the fields to include, and `data-type` is `notify-me` or `coming-soon`.
56
-
57
- ```html
58
- <div id="notification-widget"></div>
59
- <button id="popup-open" data-fields='["mobile", "firstName", "lastName"]' data-type="notify-me">Open Popup</button>
17
+ ```liquid
18
+ <script>
19
+ window.currentProduct = {
20
+ id: {{ product.id | json }},
21
+ title: {{ product.title | json }},
22
+ variants: [
23
+ {%- for v in product.variants -%}
24
+ { id: {{ v.id | json }}, title: {{ v.title | json }}, available: {{ v.available | json }} }{% unless forloop.last %},{% endunless %}
25
+ {%- endfor -%}
26
+ ]
27
+ };
28
+ </script>
60
29
  ```
61
30
 
62
- `window.currentProduct` supplies the product on a product page. Its `variants` may
63
- include Shopify's `available` flag — the widget then lists only out-of-stock sizes
64
- itself — or be pre-filtered in Liquid as older installs do.
65
-
66
- ### Configuration
67
-
68
- Every attribute below can be set on `#notification-widget`, on `#popup-open`, or as a key in the JSON block (see [JSON configuration](#json-configuration)). Per setting, `#popup-open` wins, then `#notification-widget`, then the JSON block.
69
-
70
- - `data-fields`: A JSON array specifying the optional fields to include in the form. Possible values are `"email"`, `"mobile"`, `"firstName"`, `"lastName"`, `"countryCode"`, and `"provinceCode"`. Defaults to `["email"]`. (A required Size selector is always shown and populated from the product's variants.)
71
- - `data-type`: Specifies the type of the widget. Possible values are `"notify-me"` and `"coming-soon"`.
72
- - `data-display`: How the widget presents itself. `"modal"` (default) is a centred dialog; `"panel"` slides in from the right edge at full height. Any other value falls back to `"modal"`. See [Display modes](#display-modes).
73
- - `data-auth`: Auth mode. `"proxy"` (default) uses the Shopify App Proxy at `/apps/<proxy-app>/auth/token` to obtain a tenant-scoped token (requires the customer to be logged in to the storefront). `"token"` sends a token you supply via `data-access-token` or `window.TWC_ACCESS_TOKEN`. **The widget bundles no credential of its own** — see [Auth modes](#auth-modes).
74
- - `data-tenant`: The TWC tenant sent as the `X-Twc-Tenant` header. Used in both auth modes. Falls back to the bundled `TENANT_ID` if omitted.
75
- - `data-proxy-app`: The Shopify App Proxy subpath, forming the `/apps/<proxy-app>/...` URL prefix used by the `"proxy"` auth mode. Falls back to the bundled `PROXY_APP_NAME` (`twc-sdk`) if omitted.
76
- - `data-access-token`: Access token for the `"token"` auth mode, with or without the `Bearer ` prefix. Takes precedence over `window.TWC_ACCESS_TOKEN`. Ignored in `"proxy"` mode. Anything rendered here is visible to anyone viewing the page — use a short-lived, narrowly scoped token, never a staff or POS credential.
77
- - `data-market-id`: The Shopify market the shopper is browsing in. Accepts either the numeric id or the full GID — `gid://shopify/Market/12345` and `12345` both send `12345`. Falls back to `window.Shopify.markets.currentMarket.id` when omitted. See [Shopify markets](#shopify-markets).
78
- - `data-market-handle`: The market handle (e.g. `"au"`). Attribute only — there is no runtime fallback, so a theme that does not render it reports no handle.
79
- - `data-country-code`: Two-letter ISO country code for the market the shopper is browsing in. Takes priority over the automatic detection chain, but a country the shopper picks in the form still wins over both.
80
- - `data-recommendations`: Set to `"false"` to disable the "Shop similar styles" section. Enabled by default.
81
- - `data-recommendations-count`: How many products to show. Defaults to `8`. Non-numeric or non-positive values fall back to `8`.
82
- - `data-recommendations-profile`: Athos/Searchspring recommendation profile tag for this install (e.g. `"similar-snap"`). Only consulted when the tenant config carries no `profileTag`/`tags`; see [Choosing an engine](#choosing-an-engine). Ignored by the `TWC` engine.
83
- - `data-currency`: Fallback ISO currency code (e.g. `"AUD"`) for formatting recommendation prices, used only when the storefront does not expose `window.Shopify.currency.active`. Without either, prices render as bare numbers rather than risking the wrong symbol.
84
- - `data-customer-email`: Overrides the customer email used to request recommendations. Intended for testing — in production the email comes from the Shopify customer context, or from the email the shopper submits.
85
-
86
- ### JSON configuration
87
-
88
- Instead of (or as well as) attributes, put a JSON block inside `#notification-widget`:
31
+ **2. The widget and its configuration**, once per page:
89
32
 
90
33
  ```liquid
91
- <div id="notification-widget" data-market-id="{{ localization.market.id }}">
34
+ <div id="notification-widget"
35
+ data-market-id="{{ localization.market.id }}"
36
+ data-market-handle="{{ localization.market.handle }}"
37
+ data-country-code="{{ localization.country.iso_code }}">
92
38
  <script type="application/json" data-twc-config>
93
39
  {
94
40
  "tenant": "your-tenant",
95
41
  "type": "notify-me",
96
42
  "display": "panel",
97
- "fields": ["email"],
98
- "currency": {{ cart.currency.iso_code | json }},
99
- "productSummary": true,
100
- "copy": { "title": {{ 'Notify me when available' | json }} },
101
- "classes": { "submit": "Button Button--primary" }
43
+ "fields": ["email"]
102
44
  }
103
45
  </script>
104
46
  </div>
47
+ <script src="https://cdn.jsdelivr.net/npm/notify-me-wl@3.6.2/build/notify-me-wl.min.js" defer></script>
105
48
  ```
106
49
 
107
- - Each attribute's key is its name without `data-`, camelCased: `data-proxy-app` → `proxyApp`, `data-recommendations-count` → `recommendationsCount`. Values use real JSON types — `"fields": ["email"]`, `"recommendations": false`, `"recommendationsCount": 6`.
108
- - `productSummary`, `copy` and `classes` exist only in JSON.
109
- - Per setting, an attribute on `#popup-open` wins, then one on `#notification-widget`, then the JSON block, then the default. Liquid-rendered values such as `data-market-id` can stay as attributes next to the block.
110
- - Build values with Liquid's `| json` filter so quotes and apostrophes cannot break the block.
111
- - `null` counts as not set. A value of the wrong type is ignored with a console warning and the next layer applies. Invalid JSON is reported in the console and the widget runs as if there were no block.
112
- - The header row (`.twc-nm-header`, and so `--twc-nm-header-border`) is rendered only when a `data-twc-config` block is present.
113
- - String settings accept numbers too, so `"marketId": {{ localization.market.id | json }}` works.
50
+ **3. A button that opens it:**
114
51
 
115
- ### Copy
52
+ ```html
53
+ <button type="button" data-twc-notify-me>Notify me</button>
54
+ ```
116
55
 
117
- `copy` in the JSON block overrides the strings listed below. Everything is rendered as plain text. Status and validation messages ("Sending…", "Please select a size.", error and notice messages), the close button's aria-label, and the mobile, name, country and province labels are fixed for now.
56
+ That's a working install. Always pin the script to an exact version (`@3.6.2`), so a new release never reaches a live store until you choose to upgrade.
118
57
 
119
- | Key | Default |
120
- |---|---|
121
- | `title` | `"{product}"` — the product name |
122
- | `intro` | "Register to receive a notification as soon as this item is back in stock" (notify-me) / "Register your interest to hear more about this item" (coming-soon) |
123
- | `sizeLabel` | `"Size"` |
124
- | `sizePlaceholder` | `"Select a size"` |
125
- | `emailLabel` | `"Email"`; `false` removes the label (the input gets an `aria-label`) |
126
- | `emailPlaceholder` | `"you@example.com"` |
127
- | `emailOptIn` | `"Email me store updates"` (shown with the `email` field) |
128
- | `smsOptIn` | `"Text me store updates"` (shown with the `mobile` field) |
129
- | `submit` | `"Notify Me"` / `"Register Interest"` |
130
- | `success` | `"You're on the list. We'll be in touch."` |
131
- | `disclaimer` | none; when set, shown under the submit button |
58
+ ---
59
+
60
+ ## Product data
132
61
 
133
- `{product}` in `title` or `intro` is replaced by the product's name.
62
+ The widget reads the product from the clicked element's closest `[data-twc-product]`, or from `window.currentProduct` on a product page.
134
63
 
135
- ### Product summary
64
+ | Field | Required | Meaning |
65
+ |---|---|---|
66
+ | `id`, `title` | yes | Product id and name |
67
+ | `variants[].id`, `variants[].title` | yes | Each size. `title` is what the size dropdown shows |
68
+ | `variants[].available` | recommended | Only sizes that are not `true` are offered. Without it, every listed variant is offered (older installs pre-filter in Liquid) |
69
+ | `image` | for the summary | Product image URL, used when a variant has none |
70
+ | `variants[].price` | for the summary | Already-formatted price, e.g. `"$199.96 AUD"` |
71
+ | `variants[].compareAtPrice` | for the summary | Already-formatted "was" price. Only send it when it is a real discount |
72
+ | `variants[].image` | for the summary | Variant image URL |
136
73
 
137
- `"productSummary": true` shows the product at the top of the form — image, name, the selected size and its price, with the compare-at price struck through. It follows the size select, so a size changed in the widget updates the summary and is the size registered.
74
+ The last four only matter with `"productSummary": true` (see [Configuration](#configuration)). Prices are passed pre-formatted so they always match the theme's own price display. Image URLs must start with `https:`, `http:` or `//`.
138
75
 
139
- It reads optional fields on the product data (`window.currentProduct` or `data-twc-product`). Prices are passed already formatted, so they match the theme:
76
+ A full capture, including summary fields, that works for both product pages and cards:
140
77
 
141
78
  ```liquid
142
79
  {%- capture twc_product -%}
143
- {"id":{{ product.id | json }},"title":{{ product.title | json }},
80
+ {"id":{{ product.id | json }},"title":{{ product.title | json }}
144
81
  {%- if product.featured_image -%},"image":{{ product.featured_image | image_url: width: 300 | json }}{%- endif -%},"variants":[
145
82
  {%- for v in product.variants -%}
146
83
  {"id":{{ v.id | json }},"title":{{ v.title | json }},"available":{{ v.available | json }},
@@ -150,374 +87,253 @@ It reads optional fields on the product data (`window.currentProduct` or `data-t
150
87
  {%- unless forloop.last -%},{%- endunless -%}
151
88
  {%- endfor -%}]}
152
89
  {%- endcapture -%}
153
- ```
154
-
155
- Emit it on a product page as `<script>window.currentProduct = {{ twc_product }};</script>`, or on a product card as `<div data-twc-product="{{ twc_product | escape }}">` (`| escape` is required inside the attribute).
156
-
157
- A variant without an image falls back to the product image. Image URLs must be `https:`, `http:` or `//` URLs. With no size selected, the price shows only when every listed size has the same price.
158
-
159
- ### Styling and theme classes
160
90
 
161
- **CSS custom properties.** Set any of these (on `:root` or an ancestor, inside your own media queries if needed); unset ones keep the default look.
91
+ {%- comment -%} Product page {%- endcomment -%}
92
+ <script>window.currentProduct = {{ twc_product }};</script>
162
93
 
163
- | Variable | Default |
164
- |---|---|
165
- | `--twc-nm-panel-width` | `420px` |
166
- | `--twc-nm-panel-width-mobile` | `100%` (≤480px) |
167
- | `--twc-nm-padding` | `24px` |
168
- | `--twc-nm-overlay-bg` | `rgba(17, 17, 17, 0.55)` |
169
- | `--twc-nm-panel-shadow` | `-10px 0 40px rgba(0, 0, 0, 0.18)` |
170
- | `--twc-nm-transition` | `200ms ease` |
171
- | `--twc-nm-bg` | `#fff` |
172
- | `--twc-nm-text` | `#111827` |
173
- | `--twc-nm-muted` | `#6b7280` |
174
- | `--twc-nm-border` | `#e5e7eb` |
175
- | `--twc-nm-font` | `inherit` |
176
- | `--twc-nm-radius` | `8px` |
177
- | `--twc-nm-header-border` | `none` |
178
-
179
- **Theme classes.** `classes` in the JSON block adds your theme's classes to the widget's elements, so they pick up your existing button, input and heading styles:
180
-
181
- ```json
182
- "classes": {
183
- "title": "Heading u-h4",
184
- "input": "Form__Input",
185
- "select": "Form__Input",
186
- "submit": "Button Button--primary Button--full"
187
- }
94
+ {%- comment -%} Product card — | escape is required inside an attribute {%- endcomment -%}
95
+ <div class="product-card" data-twc-product="{{ twc_product | escape }}">…</div>
188
96
  ```
189
97
 
190
- Keys: `title`, `intro`, `label`, `input`, `select`, `checkbox`, `submit`, `close`, `disclaimer`, `summary`, `summaryImage`, `summaryName`, `summaryVariant`, `summaryPrice`, `summaryCompareAt`. `label`, `input` and `select` apply to every element of that kind.
191
-
192
- An element given classes drops the widget's own colours, fonts, borders, padding and hover styles, so your theme's apply cleanly. Its layout (width, position, truncation) stays the widget's. The close button keeps its border, background and box-shadow reset and its positioning, and a themed select keeps the widget's arrow image (give it enough right padding, about 36px). Class names may contain letters, digits, `-` and `_`; anything else is dropped with a console warning.
193
-
194
- ### Auth modes
98
+ Keep the `{%- if … -%}` guards: `image_url` on a missing image renders a Liquid error and breaks the JSON.
195
99
 
196
- Every TWC API call needs an `Authorization` header and an `X-Twc-Tenant` header.
197
- `data-auth` chooses where the token comes from. **The distributed bundle contains no
198
- access token** — a `"token"` install that supplies none will fail to submit.
100
+ ---
199
101
 
200
- #### `"proxy"` (default)
102
+ ## Opening the widget
201
103
 
202
- The widget lazily fetches a tenant-scoped token from the same-origin Shopify App Proxy
203
- endpoint `/apps/<proxy-app>/auth/token` on form submit (where `<proxy-app>` comes from
204
- `data-proxy-app`, defaulting to `twc-sdk`; Shopify signs and forwards the request). The
205
- token is cached for the page session and reused across submissions.
104
+ Any element with `data-twc-notify-me` opens the widget for its product. No theme JavaScript is needed, and cards added later (filters, infinite scroll) work too.
206
105
 
207
- Because the proxy only issues a token for a logged-in customer, if the token cannot be
208
- obtained the popup stays open and shows an inline "Please log in to your account to
209
- continue." message; submission is blocked until a token is available.
106
+ ```liquid
107
+ <div class="product-card" data-twc-product="{{ twc_product | escape }}">
108
+ {%- for v in product.variants -%}
109
+ <button type="button" data-variant-id="{{ v.id }}" {% unless v.available %}data-twc-notify-me{% endunless %}>{{ v.title }}</button>
110
+ {%- endfor -%}
111
+ <a href="#" data-twc-notify-me>Size out of stock?</a>
112
+ </div>
113
+ ```
210
114
 
211
- #### `"token"`
115
+ - Don't put `disabled` on a sold-out size button. Browsers don't fire clicks on disabled buttons, so use a class or `aria-disabled="true"` instead.
116
+ - The click's default action is prevented (`href="#"` won't jump), but the theme's own handlers still run.
212
117
 
213
- Sends a token you supply, from `data-access-token` on `#notification-widget` (or `#popup-open`) or
214
- `window.TWC_ACCESS_TOKEN`. The attribute wins if both are present, and the `Bearer `
215
- prefix is added if you omit it.
118
+ **Which size is pre-selected.** The first of these that matches an out-of-stock size wins:
216
119
 
217
- ```html
218
- <button id="popup-open" data-type="notify-me" data-auth="token"
219
- data-access-token="{{ twc_widget_token }}">Notify Me</button>
220
- ```
120
+ 1. `data-variant-id` on the clicked element.
121
+ 2. On a product page only: `?variant=` in the URL, then the product form's `select[name="id"]` (inside `form[action*="/cart/add"]`).
122
+ 3. The only out-of-stock size, if there is exactly one.
123
+ 4. Otherwise "Select a size".
221
124
 
222
- Anything you render here ships to the browser and is readable by anyone who views the
223
- page or the bundle. Mint a short-lived token scoped to customer-interest writes for the
224
- single tenant — never a staff, POS, or store-owner credential. If you cannot mint one
225
- that narrow, use `"proxy"` instead.
125
+ A product with nothing out of stock shows "All sizes are currently in stock." instead of the form.
226
126
 
227
- When `"token"` mode has no token, the widget logs a setup error to the console and the
228
- submission fails with the generic error message.
127
+ ---
229
128
 
230
- > **Breaking change in 3.0.0:** the default `data-auth` moved from `"token"` to
231
- > `"proxy"`, and the previously bundled access token was removed. Installs that relied
232
- > on the bundled token must either set up the App Proxy or supply their own token via
233
- > `data-access-token`.
129
+ ## Configuration
234
130
 
235
- ### Display modes
131
+ Put settings in the JSON block inside `#notification-widget`. Every setting except `productSummary`, `copy` and `classes` can also be an attribute: the attribute name is `data-` plus the key in kebab-case (`proxyApp` → `data-proxy-app`). Attributes are handy for values Liquid renders, like the market id.
236
132
 
237
- `data-display` selects the widget's geometry. Both modes are blocking and behave
238
- identically otherwise — same dimmed backdrop, same locked page scroll, and the same
239
- close paths (× button, backdrop click, `Esc`).
133
+ | Key | Attribute | Default | Description |
134
+ |---|---|---|---|
135
+ | `tenant` | `data-tenant` | bundled demo tenant | Your TWC tenant. **Always set it** |
136
+ | `type` | `data-type` | `"notify-me"` | `"notify-me"` or `"coming-soon"`. Changes the default copy and the payload flag |
137
+ | `display` | `data-display` | `"modal"` | `"modal"` (centred dialog) or `"panel"` (full-height, slides in from the right) |
138
+ | `fields` | `data-fields` | `["email"]` | Form fields, any of `"email"`, `"mobile"`, `"firstName"`, `"lastName"`, `"countryCode"`, `"provinceCode"`. The size dropdown is always shown |
139
+ | `auth` | `data-auth` | `"proxy"` | `"proxy"` or `"token"`; see [Authentication](#authentication) |
140
+ | `proxyApp` | `data-proxy-app` | `"twc-sdk"` | Shopify App Proxy subpath (`/apps/<proxyApp>/…`) |
141
+ | `accessToken` | `data-access-token` | — | Token for `"token"` auth. Falls back to `window.TWC_ACCESS_TOKEN` |
142
+ | `marketId` | `data-market-id` | `window.Shopify` market | Shopify market id; a full `gid://…` is fine |
143
+ | `marketHandle` | `data-market-handle` | — | Market handle, e.g. `"au"` |
144
+ | `countryCode` | `data-country-code` | detected | ISO country code. A country chosen in the form wins |
145
+ | `currency` | `data-currency` | `window.Shopify.currency.active` | Fallback currency for recommendation prices |
146
+ | `customerEmail` | `data-customer-email` | Shopify customer | Overrides the shopper's email (testing) |
147
+ | `recommendations` | `data-recommendations` | `true` | `false` hides the "Shop similar styles" row |
148
+ | `recommendationsCount` | `data-recommendations-count` | `8` | Products in that row |
149
+ | `recommendationsProfile` | `data-recommendations-profile` | — | Searchspring profile tag, if the tenant config has none |
150
+ | `productSummary` | — | `false` | Show image, name, size and price at the top of the form |
151
+ | `copy` | — | — | Text overrides; see [Copy](#copy) |
152
+ | `classes` | — | — | Theme classes per element; see [Styling](#styling) |
240
153
 
241
- | | `"modal"` (default) | `"panel"` |
242
- | --- | --- | --- |
243
- | Position | Centred | Docked to the right edge |
244
- | Size | 400px wide, fits the viewport | 420px wide, full height |
245
- | Corners | Rounded | Square |
246
- | Entry | Fades and lifts | Slides in from the right |
247
- | Below 480px | Unchanged | Spans the full viewport width |
154
+ **Rules**
248
155
 
249
- ```html
250
- <button id="popup-open" data-type="notify-me" data-display="panel"
251
- data-fields='["email"]'>Notify Me</button>
252
- ```
156
+ - **Precedence:** an attribute on the legacy `#popup-open` button wins, then an attribute on `#notification-widget`, then the JSON block, then the default. An empty attribute counts as not set.
157
+ - **Use Liquid's `| json` filter** for any value you render into the block, so quotes and apostrophes can't break it.
158
+ - **Bad values are skipped, not fatal.** A value of the wrong type is ignored with a console warning, and `null` counts as not set. Invalid JSON is logged and the widget runs with the remaining settings.
159
+ - **One `#notification-widget` per page.** If there are two, the first is used.
253
160
 
254
- Panel mode is a CSS-only variation — it adds a `twc-nm--panel` class to the overlay and
255
- changes nothing about the widget's behaviour, so anything documented elsewhere in this
256
- README applies to both modes. Under `prefers-reduced-motion` the panel appears without
257
- sliding.
161
+ ---
258
162
 
259
- ### Shopify markets
163
+ ## Copy
260
164
 
261
- Market and country are captured on every submission so interest can be reported per
262
- market. The values come from Liquid, which renders server-side, so the theme passes
263
- them to the widget as attributes on `#notification-widget` (or the legacy `#popup-open` button):
165
+ `copy` overrides the widget's main text. Everything is shown as plain text, never HTML.
264
166
 
265
- ```html
266
- <button id="popup-open"
267
- data-type="notify-me"
268
- data-market-id="{{ localization.market.id }}"
269
- data-market-handle="{{ localization.market.handle }}"
270
- data-country-code="{{ localization.country.iso_code }}">
271
- Notify Me
272
- </button>
167
+ ```json
168
+ "copy": {
169
+ "title": "Notify me when available",
170
+ "intro": "We'll email you as soon as {product} is back.",
171
+ "emailLabel": false,
172
+ "emailPlaceholder": "Email",
173
+ "submit": "NOTIFY ME",
174
+ "disclaimer": "You can unsubscribe at any time."
175
+ }
273
176
  ```
274
177
 
275
- **The market id is sent as the number only** — `12345`, never
276
- `gid://shopify/Market/12345`. The widget strips the GID prefix itself, so it does not
277
- matter which form the theme renders.
178
+ | Key | Default |
179
+ |---|---|
180
+ | `title` | `"{product}"` (the product name) |
181
+ | `intro` | "Register to receive a notification as soon as this item is back in stock" / "Register your interest to hear more about this item" |
182
+ | `sizeLabel` / `sizePlaceholder` | `"Size"` / `"Select a size"` |
183
+ | `emailLabel` | `"Email"`. `false` hides the label; the input keeps an accessible name |
184
+ | `emailPlaceholder` | `"you@example.com"` |
185
+ | `emailOptIn` | `"Email me store updates"` (shown when `fields` includes `email`) |
186
+ | `smsOptIn` | `"Text me store updates"` (shown when `fields` includes `mobile`) |
187
+ | `submit` | `"Notify Me"` / `"Register Interest"` |
188
+ | `success` | `"You're on the list. We'll be in touch."` |
189
+ | `disclaimer` | none. When set, it is shown under the submit button |
278
190
 
279
- Any value that does not look right — an empty string from a storefront with no market
280
- configured, or an unrendered Liquid tag — is dropped, and the field is left out of the
281
- payload rather than sent as `null`. All three attributes are optional: without them,
282
- `marketId` and `countryCode` still resolve from `window.Shopify` and the browser locale,
283
- and `marketHandle` is simply not reported.
191
+ `{product}` in `title` or `intro` becomes the product name. Not yet configurable: the status messages ("Sending…", "Please select a size.", errors), the close button label, and the mobile, name, country and province field labels.
284
192
 
285
- When `data-fields` includes `"countryCode"`, the country the shopper picks in the form
286
- takes precedence over `data-country-code`. The dropdown is pre-selected to the market's
287
- country, so leaving it untouched sends the market country either way.
193
+ ---
288
194
 
289
- ### Opening from product listing pages
195
+ ## Styling
290
196
 
291
- Three attributes connect a theme to the widget. No theme JavaScript is needed.
197
+ All widget styles are scoped under `#twc-nm-overlay`, and the widget uses the page's font. There are two ways to match a theme. They can be combined.
292
198
 
293
- | Attribute | Where | Meaning |
294
- | --- | --- | --- |
295
- | `data-twc-product` | A product container, e.g. the card | JSON `{ id, title, variants: [{ id, title, available }] }` |
296
- | `data-twc-notify-me` | Any clickable element | Clicking it opens the widget |
297
- | `data-variant-id` | Optional, on the same element | Variant to pre-select |
199
+ ### CSS variables
298
200
 
299
- The widget finds the product from the clicked element's closest `data-twc-product`,
300
- falling back to `window.currentProduct`. It listens at page level in the capture phase,
301
- so cards added later (filters, infinite scroll) work, and a theme handler that stops
302
- propagation does not block it. It calls `preventDefault()` on the click — `href="#"`
303
- does not jump — but lets the theme's own handlers run.
201
+ Set any of these on `:root` (or any ancestor, inside media queries if needed). Anything you don't set keeps the default look.
304
202
 
305
- Build the product JSON with a compact capture rather than `product | json`, which is
306
- too heavy to repeat on every card, and escape it into a double-quoted attribute so a
307
- title with a quote or apostrophe cannot break the markup:
203
+ | Variable | Default |
204
+ |---|---|
205
+ | `--twc-nm-panel-width` | `420px` |
206
+ | `--twc-nm-panel-width-mobile` | `100%` (at ≤480px) |
207
+ | `--twc-nm-padding` | `24px` |
208
+ | `--twc-nm-overlay-bg` | `rgba(17, 17, 17, 0.55)` |
209
+ | `--twc-nm-panel-shadow` | `-10px 0 40px rgba(0, 0, 0, 0.18)` |
210
+ | `--twc-nm-transition` | `200ms ease` |
211
+ | `--twc-nm-bg` / `--twc-nm-text` / `--twc-nm-muted` / `--twc-nm-border` | `#fff` / `#111827` / `#6b7280` / `#e5e7eb` |
212
+ | `--twc-nm-font` | `inherit` |
213
+ | `--twc-nm-radius` | `8px` |
214
+ | `--twc-nm-header-border` | `none` |
308
215
 
309
- ```liquid
310
- {%- capture twc_product -%}
311
- {"id":{{ product.id | json }},"title":{{ product.title | json }},"variants":[
312
- {%- for v in product.variants -%}
313
- {"id":{{ v.id | json }},"title":{{ v.title | json }},"available":{{ v.available | json }}}
314
- {%- unless forloop.last -%},{%- endunless -%}
315
- {%- endfor -%}]}
316
- {%- endcapture -%}
317
- <div class="product-card" data-twc-product="{{ twc_product | escape }}">
318
- {%- for variant in product.variants -%}
319
- <button type="button" data-variant-id="{{ variant.id }}"
320
- {% unless variant.available %}data-twc-notify-me{% endunless %}>{{ variant.title }}</button>
321
- {%- endfor -%}
322
- <a href="#" data-twc-notify-me>Size out of stock?</a>
323
- </div>
216
+ ```css
217
+ :root { --twc-nm-panel-width: 400px; --twc-nm-radius: 0; --twc-nm-overlay-bg: rgba(54, 54, 54, 0.5); }
324
218
  ```
325
219
 
326
- Out-of-stock size buttons must not carry the `disabled` attribute: browsers do not
327
- dispatch clicks on disabled buttons. Style them with a class or `aria-disabled="true"`
328
- instead.
220
+ ### Theme classes
329
221
 
330
- Render `#notification-widget` with the configuration once per page (e.g. in
331
- `theme.liquid`), and load the script with `defer` or no attribute:
222
+ `classes` adds your theme's own classes to the widget's elements, so buttons, inputs and headings look exactly like the rest of the store:
332
223
 
333
- ```liquid
334
- <div id="notification-widget"
335
- data-tenant="your-tenant"
336
- data-type="notify-me"
337
- data-fields='["email"]'
338
- data-currency="{{ cart.currency.iso_code }}"
339
- data-market-id="{{ localization.market.id }}"
340
- data-market-handle="{{ localization.market.handle }}"
341
- data-country-code="{{ localization.country.iso_code }}"></div>
342
- <script src="https://cdn.jsdelivr.net/npm/notify-me-wl@3.5.0/build/notify-me-wl.min.js" defer></script>
224
+ ```json
225
+ "classes": {
226
+ "title": "Heading u-h4",
227
+ "input": "Form__Input",
228
+ "select": "Form__Input",
229
+ "submit": "Button Button--primary Button--full"
230
+ }
343
231
  ```
344
232
 
345
- A page must have exactly one `#notification-widget`. A second, empty one earlier in
346
- the page would be the one the widget reads its configuration from.
233
+ **Keys:**
234
+ - Form elements: `title`, `intro`, `label`, `input`, `select`, `checkbox`, `submit`, `close`, `disclaimer`.
235
+ - Product summary: `summary`, `summaryImage`, `summaryName`, `summaryVariant`, `summaryPrice`, `summaryCompareAt`.
236
+ - `label`, `input` and `select` apply to every element of that kind.
347
237
 
348
- ### Size pre-selection
238
+ **How styling is split:**
239
+ - An element with classes drops the widget's own colours, fonts, borders, padding and hover styles, so the theme's apply cleanly.
240
+ - Layout (width, positioning, text truncation) always stays the widget's.
241
+ - A themed select keeps the widget's dropdown arrow, so give it about 36px of right padding.
242
+ - Class names may only contain letters, digits, `-` and `_`. Anything else is dropped, with a warning in the console.
349
243
 
350
- The size list shows only out-of-stock sizes. On open, the first rule that yields one of
351
- them wins:
244
+ **Header row.** When the JSON block is present, the title and close button sit in a `.twc-nm-header` row you can style. This is also what `--twc-nm-header-border` applies to. Without a JSON block, the markup is exactly as in earlier versions.
352
245
 
353
- 1. `data-variant-id` on the clicked element.
354
- 2. Product page only (the product came from `window.currentProduct`): `?variant=` in
355
- the URL, then the value of the first `select[name="id"]` inside a
356
- `form[action*="/cart/add"]`. Hidden `input[name="id"]` fields are ignored — some
357
- themes keep a stale one in an installments form. Never applied to a card, so page
358
- state cannot leak into a listing.
359
- 3. Exactly one out-of-stock size — that size.
360
- 4. Otherwise, "Select a size".
246
+ **Overriding widget rules.** The widget injects its stylesheet when it loads, after your theme's CSS. A theme rule must therefore be more specific than the widget's to win, e.g. `#twc-nm-overlay .twc-nm-card .twc-nm-title`.
361
247
 
362
- A product with no out-of-stock sizes shows "All sizes are currently in stock." instead
363
- of the form. A missing or malformed product shows "Product information is unavailable.
364
- Please refresh the page."
248
+ ---
365
249
 
366
- ### Shop similar styles
250
+ ## Authentication
367
251
 
368
- When enabled, the widget shows a horizontally scrolling row of recommended products
369
- beneath the form, fetched from:
252
+ Every request carries `Authorization: Bearer <token>` and `X-Twc-Tenant: <tenant>`. The bundle ships no credential.
370
253
 
371
- ```
372
- https://api.au-aws.thewishlist.io/services/recommendations/api/v1/recommendations/<tenant>/<customerEmail>
373
- ```
254
+ - **`"proxy"` (default):** on submit, the widget fetches a tenant-scoped token from the store's Shopify App Proxy at `/apps/<proxyApp>/auth/token` and reuses it for the page session. If the proxy returns no token, the widget shows "Please log in to your account to continue." and keeps the form open.
255
+ - **`"token"`:** the widget sends the token you supply in `accessToken` / `data-access-token` or `window.TWC_ACCESS_TOKEN`. Anything rendered into the page is public, so only use a short-lived token scoped to customer-interest writes for one tenant. Never use a staff or POS credential. If you can't mint one that narrow, use `"proxy"`.
374
256
 
375
- The request carries the same `Authorization` and `X-Twc-Tenant` headers as the
376
- customer-interest call, and the tenant doubles as the retailer id in the path.
257
+ ---
377
258
 
378
- The API needs a customer email, resolved in this order:
259
+ ## Recommendations
379
260
 
380
- 1. `data-customer-email` on `#notification-widget` (or `#popup-open`) (testing).
381
- 2. `window.customer.email` — a logged-in Shopify customer. The section then appears
382
- as soon as the modal opens.
383
- 3. `window.customerEmail` — the same address exposed as a bare global, which is how
384
- some themes already publish it. Also appears as soon as the modal opens.
385
- 4. The email the shopper submits through the form. The section appears after a
386
- successful submit, and the modal stays open instead of auto-closing so they can
387
- browse.
261
+ A "Shop similar styles" row can appear under the form. It shows only when **all** of these hold:
388
262
 
389
- Shopify does **not** expose either global by default. To get recommendations for
390
- logged-in customers, the theme must inject one, the same way it injects
391
- `window.currentProduct`:
263
+ - `recommendations` is not `false`.
264
+ - The tenant's config defines an engine (`websiteRecommendations`, see below).
265
+ - A shopper email is known. It comes from the first of these that is set:
266
+ - `customerEmail`.
267
+ - `window.customer.email`.
268
+ - `window.customerEmail`.
269
+ - The email just submitted. In this case the row appears after submit, and the widget stays open instead of auto-closing.
270
+
271
+ A known email also pre-fills the form's email field. Shopify doesn't expose the customer's email by default; add it in the theme:
392
272
 
393
273
  ```liquid
394
- <script>
395
- {% if customer %}
396
- window.customer = { email: {{ customer.email | json }} };
397
- {% endif %}
398
- </script>
274
+ <script>{% if customer %}window.customer = { email: {{ customer.email | json }} };{% endif %}</script>
399
275
  ```
400
276
 
401
- Themes often write the bare form as `{{ customer.email | json }} || false`, which makes
402
- it boolean `false` for guests. That is handled: the value is accepted only when it is a
403
- non-empty string, so `false` resolves to "no email" rather than the string `"false"`.
404
-
405
- When an email resolves from any of the first three sources, the form's Email field is
406
- **prefilled** with it. The field stays editable, and submit uses whatever it contains at
407
- that moment, so a shopper can send the notification to a different address.
408
-
409
- #### Choosing an engine
410
-
411
- Which engine powers the section is a per-tenant server-side setting, not a data
412
- attribute. On first open the widget fetches the tenant config from:
277
+ The row never blocks or changes the notify-me submission. If anything fails, it simply doesn't appear.
413
278
 
414
- ```
415
- GET /services/eventcollector/api/v1/custom/configs/public
416
- ```
279
+ <details>
280
+ <summary>Engine selection and internals</summary>
417
281
 
418
- with the same `Authorization` and `X-Twc-Tenant` headers as every other call — despite
419
- the path, this endpoint is not anonymous — and reads `websiteRecommendations`:
282
+ - **Engine:** read once per page from `GET /services/eventcollector/api/v1/custom/configs/public` (authenticated like every other call) → `websiteRecommendations`:
283
+ - `{ "engine": "TWC" }`: TWC's recommendations API, `/services/recommendations/api/v1/recommendations/<tenant>/<email>`.
284
+ - `{ "engine": "ATHOS", "siteIdentifier": "abc123" }`: Searchspring, `https://abc123.a.searchspring.io/boost/abc123/recommend`, called with **no** TWC credentials.
285
+ - When the field is missing, the engine is unknown, or ATHOS has no `siteIdentifier`, no row is shown. A failed config request is retried on the next open.
286
+ - **Searchspring profile tag:** `profileTag`, then `tags` from the tenant config, then `recommendationsProfile`, then `"similar"`. The tag must exist on the Searchspring site; an unknown tag returns no results. Check with:
287
+ `curl -s "https://{site}.a.searchspring.io/boost/{site}/recommend?tags={tag}&limits=3&products={productId}"`
288
+ - **TWC results** come per variant, so the widget over-fetches (up to the API's limit of `n=20`) and dedupes by product.
289
+ - **Prices** use `window.Shopify.currency.active`, then `currency`; with neither they render as bare numbers.
420
290
 
421
- ```json
422
- "websiteRecommendations": { "engine": "TWC" }
423
- ```
291
+ </details>
424
292
 
425
- ```json
426
- "websiteRecommendations": { "engine": "ATHOS", "siteIdentifier": "abc123" }
427
- ```
293
+ ---
428
294
 
429
- **If `websiteRecommendations` is absent, the section does not render at all** — no engine
430
- request is made and no placeholders appear. The older top-level `recommendationsEngine`
431
- field is ignored. The section is likewise skipped when the engine is unrecognised, or
432
- when `ATHOS` has no `siteIdentifier`. The config is fetched once per page session, and a
433
- failed config request is not cached, so a transient outage does not disable the section
434
- for the rest of the visit.
295
+ ## What gets sent
435
296
 
436
- For `ATHOS`, `siteIdentifier` is the Searchspring site ID and is used in both the
437
- subdomain and the path:
297
+ `POST https://api.au-aws.thewishlist.io/services/wsservice/api/wishlist/items/customerInterest`
438
298
 
439
- ```
440
- https://{siteIdentifier}.a.searchspring.io/boost/{siteIdentifier}/recommend
441
- ```
299
+ | Field | When |
300
+ |---|---|
301
+ | `variantRef` | Always: the size selected in the widget |
302
+ | `email` | Always (empty if the form has no email field) |
303
+ | `subscribe`, `subscribeSms` | Always: the opt-in checkboxes (`false` when not shown) |
304
+ | `notifyMe: true` or `comingSoon: true` | Depending on `type` |
305
+ | `mobile`, `firstName`, `lastName` | When those fields are in the form |
306
+ | `countryCode`, `provinceCode` | From the form if shown, otherwise detected |
307
+ | `marketId`, `marketHandle` | When known. `marketId` is always the bare number, never a `gid://` |
442
308
 
443
- The recommendation profile tag is resolved in this order, first match winning:
309
+ Marketing opt-ins are separate from the notify-me registration: shoppers can register without subscribing.
444
310
 
445
- 1. `profileTag` on `websiteRecommendations`.
446
- 2. `tags` on `websiteRecommendations`.
447
- 3. `data-recommendations-profile` on `#notification-widget` (or `#popup-open`).
448
- 4. The bundled default, `similar`.
311
+ ---
449
312
 
450
- The tenant config outranks the attribute deliberately — it is the intended home for this
451
- value, so a tenant that carries it must win. The attribute exists for merchants whose
452
- config schema cannot express `profileTag` yet, letting a theme supply the tag without a
453
- widget release.
313
+ ## Development
454
314
 
455
- **The tag must match a profile that exists on the Searchspring site.** An unknown tag
456
- returns `{"profile":{"type":"NotFound"},"results":[]}` with HTTP 200, which the widget
457
- treats as "nothing usable" and renders no section. Verify with:
315
+ TypeScript in `src/`, bundled by Rollup into `build/notify-me-wl.min.js`. The bundle is committed and is what jsDelivr serves from npm.
458
316
 
459
317
  ```bash
460
- curl -s "https://{siteIdentifier}.a.searchspring.io/boost/{siteIdentifier}/recommend?tags={tag}&limits=3&products={productId}"
318
+ npm install
319
+ npm run build # build/notify-me-wl.min.js
320
+ npm run dev # rebuild on change
321
+ npm run typecheck
322
+ npm run selftest # build, then run harness/*.html cases in headless Chrome
323
+ npm run visual # build, then pixel-compare default rendering against main
461
324
  ```
462
325
 
463
- Similar-items profiles are seeded by the product and return an empty `results` array when
464
- called without `products=`, so the section only populates where `window.currentProduct`
465
- is available at widget init.
326
+ - **`selftest`** runs each page listed in `harness/run-selftest.sh`. Each page defines `window.SELFTEST_CASES`, and `harness/selftest.js` stubs every network call. Add a page to the list when you add one.
327
+ - **`visual`** screenshots the open widget (modal and panel, desktop and mobile, with and without hostile host CSS) using `main`'s bundle and yours, and fails on any pixel difference. It protects every existing install from unintended visual change. Use `BASELINE_REF=<ref>` to compare against another ref.
328
+ - Both scripts need Chrome. Set `CHROME=/path/to/chrome` off macOS.
329
+ - `index.html` is a manual demo page. Add `?mockSubmit=1` to log the payload instead of sending it.
466
330
 
467
- Searchspring is called with **no** TWC credentials — it is a third-party host and must
468
- never receive a tenant-scoped token.
331
+ ---
469
332
 
470
- #### Deduping and result counts
471
-
472
- The TWC service returns one entry per product variant, so several entries can describe
473
- the same product in different colourways. The widget requests three times the display
474
- count and dedupes by `product_ref`, keeping the highest-scoring entry, so a row never
475
- repeats a product. **That API rejects `n` above 20 with a 422**, so the over-fetch is
476
- clamped to 20 — at the default count of 8 that means `n=20`. Athos returns products
477
- rather than variants, so it is asked for exactly `limits={count}` with no over-fetch.
478
-
479
- The row scrolls horizontally. Touch devices pan it natively; on desktop it can also be
480
- dragged with the mouse (click and drag, or use the scrollbar). A drag of more than a few
481
- pixels suppresses the click that would otherwise open the product it started on.
482
-
483
- Placeholder cards are shown while the request is in flight, so the section reserves its
484
- space instead of the modal jumping when the products land. If the request returns
485
- nothing usable, the placeholders are removed and the section disappears.
486
-
487
- Prices use `window.Shopify.currency.active` when the storefront exposes it, falling back
488
- to `data-currency`. With neither, prices render as bare numbers — showing `A$` on a GBP
489
- price would be worse than showing no symbol at all.
490
-
491
- If no email can be resolved, or the request fails or returns nothing, the section is
492
- simply not rendered. It never blocks or alters the notify-me submission.
493
-
494
- ### Example
495
-
496
- ```html
497
- <div id="notification-widget"></div>
498
- <button id="popup-open" data-fields='["mobile"]' data-type="notify-me">Open Popup</button>
499
-
500
- <script>
501
- // The script provided will be included here
502
- </script>
503
- ```
504
-
505
- ### How It Works
506
-
507
- 1. **Initialization**: The script initializes on `DOMContentLoaded` (or immediately, if the page has already loaded), reading configuration from `#notification-widget` and `#popup-open`.
508
- 2. **Styles Injection**: It injects the modal/form styles, all namespaced under `#twc-nm-overlay` so they don't collide with the host theme.
509
- 3. **Modal Creation**: Creates the modal structure and appends it to the `notification-widget` div.
510
- 4. **Dynamic Content**: Based on the `data-type`, it sets the appropriate copy and button text, and builds the fields listed in `data-fields` plus the always-present Size selector.
511
- 5. **Opening**: A click on any `data-twc-notify-me` element (or `#popup-open`) resolves the product, rebuilds the Size selector with that product's out-of-stock sizes, and pre-selects one per [Size pre-selection](#size-pre-selection).
512
- 6. **Form Submission**: On submit, the button enters a "Sending…" state, the configured auth token is resolved, and the form data is POSTed to the API. The result is shown inline (success message then auto-close, or an error message); the popup is never closed on failure.
513
-
514
- ### API Request Example
515
-
516
- ```
517
- https://api.au-aws.thewishlist.io/services/wsservice/api/wishlist/items/customerInterest
518
- ```
333
+ ## Upgrading
519
334
 
520
- Authenticate with an `Authorization: Bearer <token>` header and an
521
- `X-Twc-Tenant: <tenant>` header. The widget resolves the token per
522
- [Auth modes](#auth-modes) and the tenant from `data-tenant`; there is no
523
- `ACCESS_TOKEN` constant in the bundle to replace.
335
+ - **3.6.2:** on touch devices, opening the widget focuses the dialog rather than the size select, which iOS Safari otherwise would not open on the first tap. Desktop is unchanged.
336
+ - **3.6.0:** adds the JSON config block, `copy`, `productSummary`, CSS variables and `classes`. Existing installs need no changes: without the new settings the widget renders and behaves as 3.5.
337
+ - **3.0.0:** the default auth moved from `"token"` to `"proxy"`, and the bundled access token was removed. Set up the App Proxy or supply your own token.
338
+ - **2.0.0:** the bundle moved from `index.js` to `build/notify-me-wl.min.js`.
339
+ - **Legacy `#popup-open` button:** older installs put their attributes on a `<button id="popup-open">`. This still works, and its attributes win over everything else. New installs should use `data-twc-notify-me` triggers instead.
@@ -1 +1 @@
1
- !function(){"use strict";const e="data-twc-product";function n(e){if(!e||"object"!=typeof e)return!1;const n=e;return("number"==typeof n.id||"string"==typeof n.id)&&"string"==typeof n.title&&(void 0===n.variants||Array.isArray(n.variants))}function t(){const e=new URLSearchParams(window.location.search).get("variant"),n=document.querySelector('form[action*="/cart/add"] select[name="id"]');return[e,n?n.value:null]}function o(e){return e.replace(/^data-/,"").replace(/-([a-z])/g,(e,n)=>n.toUpperCase())}function a(e,n){console.warn(`notify-me-wl: ignoring data-twc-config "${e}": expected ${n}`)}function r(e){return Array.isArray(e)&&e.every(e=>"string"==typeof e)}const c={title:".twc-nm-title",intro:".twc-nm-text",label:".twc-nm-label",input:".twc-nm-input",select:".twc-nm-select",checkbox:".twc-nm-check input",submit:".twc-nm-submit",close:".twc-nm-close",disclaimer:".twc-nm-disclaimer",summary:".twc-nm-summary",summaryImage:".twc-nm-summary-image",summaryName:".twc-nm-summary-name",summaryVariant:".twc-nm-summary-variant",summaryPrice:".twc-nm-summary-price",summaryCompareAt:".twc-nm-summary-compare"},i=/^[A-Za-z_-][A-Za-z0-9_-]*$/;function m(e){const n={};if(null==e)return n;if("object"!=typeof e||Array.isArray(e))return a("classes","an object"),n;const t=e;return Object.keys(c).forEach(e=>{const o=t[e];if(null==o)return;if("string"!=typeof o)return void a(`classes.${e}`,"a string");const r=o.split(/\s+/).filter(Boolean),c=r.filter(e=>i.test(e));c.length<r.length&&a(`classes.${e}`,'class names of letters, digits, "-" and "_"'),c.length&&(n[e]=c)}),n}const s=[{code:"AF",name:"Afghanistan"},{code:"AX",name:"Åland Islands"},{code:"AL",name:"Albania"},{code:"DZ",name:"Algeria"},{code:"AS",name:"American Samoa"},{code:"AD",name:"Andorra"},{code:"AO",name:"Angola"},{code:"AI",name:"Anguilla"},{code:"AG",name:"Antigua and Barbuda"},{code:"AR",name:"Argentina"},{code:"AM",name:"Armenia"},{code:"AW",name:"Aruba"},{code:"AU",name:"Australia"},{code:"AT",name:"Austria"},{code:"AZ",name:"Azerbaijan"},{code:"BS",name:"Bahamas"},{code:"BH",name:"Bahrain"},{code:"BD",name:"Bangladesh"},{code:"BB",name:"Barbados"},{code:"BY",name:"Belarus"},{code:"BE",name:"Belgium"},{code:"BZ",name:"Belize"},{code:"BJ",name:"Benin"},{code:"BM",name:"Bermuda"},{code:"BT",name:"Bhutan"},{code:"BO",name:"Bolivia"},{code:"BA",name:"Bosnia and Herzegovina"},{code:"BW",name:"Botswana"},{code:"BR",name:"Brazil"},{code:"BN",name:"Brunei"},{code:"BG",name:"Bulgaria"},{code:"BF",name:"Burkina Faso"},{code:"BI",name:"Burundi"},{code:"CV",name:"Cabo Verde"},{code:"KH",name:"Cambodia"},{code:"CM",name:"Cameroon"},{code:"CA",name:"Canada"},{code:"KY",name:"Cayman Islands"},{code:"CF",name:"Central African Republic"},{code:"TD",name:"Chad"},{code:"CL",name:"Chile"},{code:"CN",name:"China"},{code:"CO",name:"Colombia"},{code:"KM",name:"Comoros"},{code:"CG",name:"Congo"},{code:"CD",name:"Congo, DR"},{code:"CK",name:"Cook Islands"},{code:"CR",name:"Costa Rica"},{code:"HR",name:"Croatia"},{code:"CU",name:"Cuba"},{code:"CW",name:"Curaçao"},{code:"CY",name:"Cyprus"},{code:"CZ",name:"Czech Republic"},{code:"DK",name:"Denmark"},{code:"DJ",name:"Djibouti"},{code:"DM",name:"Dominica"},{code:"DO",name:"Dominican Republic"},{code:"EC",name:"Ecuador"},{code:"EG",name:"Egypt"},{code:"SV",name:"El Salvador"},{code:"GQ",name:"Equatorial Guinea"},{code:"ER",name:"Eritrea"},{code:"EE",name:"Estonia"},{code:"SZ",name:"Eswatini"},{code:"ET",name:"Ethiopia"},{code:"FO",name:"Faroe Islands"},{code:"FJ",name:"Fiji"},{code:"FI",name:"Finland"},{code:"FR",name:"France"},{code:"GF",name:"French Guiana"},{code:"PF",name:"French Polynesia"},{code:"GA",name:"Gabon"},{code:"GM",name:"Gambia"},{code:"GE",name:"Georgia"},{code:"DE",name:"Germany"},{code:"GH",name:"Ghana"},{code:"GI",name:"Gibraltar"},{code:"GR",name:"Greece"},{code:"GL",name:"Greenland"},{code:"GD",name:"Grenada"},{code:"GP",name:"Guadeloupe"},{code:"GU",name:"Guam"},{code:"GT",name:"Guatemala"},{code:"GN",name:"Guinea"},{code:"GW",name:"Guinea-Bissau"},{code:"GY",name:"Guyana"},{code:"HT",name:"Haiti"},{code:"HN",name:"Honduras"},{code:"HK",name:"Hong Kong"},{code:"HU",name:"Hungary"},{code:"IS",name:"Iceland"},{code:"IN",name:"India"},{code:"ID",name:"Indonesia"},{code:"IR",name:"Iran"},{code:"IQ",name:"Iraq"},{code:"IE",name:"Ireland"},{code:"IM",name:"Isle of Man"},{code:"IL",name:"Israel"},{code:"IT",name:"Italy"},{code:"JM",name:"Jamaica"},{code:"JP",name:"Japan"},{code:"JE",name:"Jersey"},{code:"JO",name:"Jordan"},{code:"KZ",name:"Kazakhstan"},{code:"KE",name:"Kenya"},{code:"KI",name:"Kiribati"},{code:"KR",name:"Korea, South"},{code:"XK",name:"Kosovo"},{code:"KW",name:"Kuwait"},{code:"KG",name:"Kyrgyzstan"},{code:"LA",name:"Laos"},{code:"LV",name:"Latvia"},{code:"LB",name:"Lebanon"},{code:"LS",name:"Lesotho"},{code:"LR",name:"Liberia"},{code:"LY",name:"Libya"},{code:"LI",name:"Liechtenstein"},{code:"LT",name:"Lithuania"},{code:"LU",name:"Luxembourg"},{code:"MO",name:"Macao"},{code:"MG",name:"Madagascar"},{code:"MW",name:"Malawi"},{code:"MY",name:"Malaysia"},{code:"MV",name:"Maldives"},{code:"ML",name:"Mali"},{code:"MT",name:"Malta"},{code:"MH",name:"Marshall Islands"},{code:"MQ",name:"Martinique"},{code:"MR",name:"Mauritania"},{code:"MU",name:"Mauritius"},{code:"MX",name:"Mexico"},{code:"FM",name:"Micronesia"},{code:"MD",name:"Moldova"},{code:"MC",name:"Monaco"},{code:"MN",name:"Mongolia"},{code:"ME",name:"Montenegro"},{code:"MS",name:"Montserrat"},{code:"MA",name:"Morocco"},{code:"MZ",name:"Mozambique"},{code:"MM",name:"Myanmar"},{code:"NA",name:"Namibia"},{code:"NR",name:"Nauru"},{code:"NP",name:"Nepal"},{code:"NL",name:"Netherlands"},{code:"NC",name:"New Caledonia"},{code:"NZ",name:"New Zealand"},{code:"NI",name:"Nicaragua"},{code:"NE",name:"Niger"},{code:"NG",name:"Nigeria"},{code:"NU",name:"Niue"},{code:"MK",name:"North Macedonia"},{code:"NO",name:"Norway"},{code:"OM",name:"Oman"},{code:"PK",name:"Pakistan"},{code:"PW",name:"Palau"},{code:"PS",name:"Palestine"},{code:"PA",name:"Panama"},{code:"PG",name:"Papua New Guinea"},{code:"PY",name:"Paraguay"},{code:"PE",name:"Peru"},{code:"PH",name:"Philippines"},{code:"PL",name:"Poland"},{code:"PT",name:"Portugal"},{code:"PR",name:"Puerto Rico"},{code:"QA",name:"Qatar"},{code:"RE",name:"Réunion"},{code:"RO",name:"Romania"},{code:"RW",name:"Rwanda"},{code:"KN",name:"Saint Kitts and Nevis"},{code:"LC",name:"Saint Lucia"},{code:"VC",name:"Saint Vincent and the Grenadines"},{code:"WS",name:"Samoa"},{code:"SM",name:"San Marino"},{code:"SA",name:"Saudi Arabia"},{code:"SN",name:"Senegal"},{code:"RS",name:"Serbia"},{code:"SC",name:"Seychelles"},{code:"SL",name:"Sierra Leone"},{code:"SG",name:"Singapore"},{code:"SK",name:"Slovakia"},{code:"SI",name:"Slovenia"},{code:"SB",name:"Solomon Islands"},{code:"SO",name:"Somalia"},{code:"ZA",name:"South Africa"},{code:"SS",name:"South Sudan"},{code:"ES",name:"Spain"},{code:"LK",name:"Sri Lanka"},{code:"SD",name:"Sudan"},{code:"SR",name:"Suriname"},{code:"SE",name:"Sweden"},{code:"CH",name:"Switzerland"},{code:"SY",name:"Syria"},{code:"TW",name:"Taiwan"},{code:"TJ",name:"Tajikistan"},{code:"TZ",name:"Tanzania"},{code:"TH",name:"Thailand"},{code:"TL",name:"Timor-Leste"},{code:"TG",name:"Togo"},{code:"TO",name:"Tonga"},{code:"TT",name:"Trinidad and Tobago"},{code:"TN",name:"Tunisia"},{code:"TR",name:"Turkey"},{code:"TM",name:"Turkmenistan"},{code:"TC",name:"Turks and Caicos Islands"},{code:"TV",name:"Tuvalu"},{code:"UG",name:"Uganda"},{code:"UA",name:"Ukraine"},{code:"AE",name:"United Arab Emirates"},{code:"GB",name:"United Kingdom"},{code:"US",name:"United States"},{code:"UY",name:"Uruguay"},{code:"UZ",name:"Uzbekistan"},{code:"VU",name:"Vanuatu"},{code:"VE",name:"Venezuela"},{code:"VN",name:"Vietnam"},{code:"VG",name:"Virgin Islands, British"},{code:"VI",name:"Virgin Islands, U.S."},{code:"YE",name:"Yemen"},{code:"ZM",name:"Zambia"},{code:"ZW",name:"Zimbabwe"}];let l=null;async function d(e){if("proxy"===e.mode){const n=await async function(e){if(l)return l;try{const n=await fetch(`/apps/${e}/auth/token`,{method:"GET",headers:{Accept:"application/json"}}),t=await n.json().catch(()=>null);return n.ok&&t&&t.success&&t.data&&t.data.access_token?(l=t.data.access_token,l):null}catch(e){return console.error("Error fetching proxy access token:",e),null}}(e.proxyApp);return n?`Bearer ${n}`:null}return e.accessToken?/^Bearer /i.test(e.accessToken)?e.accessToken:`Bearer ${e.accessToken}`:(console.error('notify-me-wl: data-auth="token" requires an access token. Set data-access-token on the open button or window.TWC_ACCESS_TOKEN, or use data-auth="proxy" to obtain one via the Shopify App Proxy.'),null)}const u=new Map;function p(e){return"string"==typeof e?e.trim():""}async function w(e,n,t){const o=`${e}|${t||""}`,a=u.get(o);if(void 0!==a)return a;const r=await d(n);if(!r)return null;try{const n=await fetch("https://api.au-aws.thewishlist.io/services/eventcollector/api/v1/custom/configs/public",{method:"GET",headers:{Accept:"application/json",Authorization:r,"X-Twc-Tenant":e}});if(!n.ok)return console.error("Tenant config request failed:",n.status),null;const a=function(e,n=null){if(!e||"object"!=typeof e)return null;const t=e.websiteRecommendations;if(!t||"object"!=typeof t)return null;const o=p(t.engine).toUpperCase();if("TWC"===o)return{engine:"TWC"};if("ATHOS"===o){const e=p(t.siteIdentifier);return e?{engine:"ATHOS",siteIdentifier:e,profileTag:p(t.profileTag)||p(t.tags)||p(n)||"similar"}:null}return null}(await n.json().catch(()=>null),t);return u.set(o,a),a}catch(e){return console.error("Error fetching tenant config:",e),null}}function f(e){return"string"==typeof e?e.trim():""}function y(e){if("number"==typeof e&&Number.isFinite(e))return e;if("string"==typeof e){const n=parseFloat(e);if(Number.isFinite(n))return n}return null}function h(e){return Boolean(e.name&&e.imageUrl&&e.productUrl)}function g(e,n){const t=new Map;return e.forEach(e=>{const n=t.get(e.product.ref);n&&n.score>=e.score||t.set(e.product.ref,e)}),Array.from(t.values()).sort((e,n)=>n.score-e.score).slice(0,n).map(e=>e.product)}async function v(e){const{email:n,tenant:t,auth:o,count:a}=e,r=await d(o);if(!r)return null;const c=Math.min(3*a,20),i=`https://api.au-aws.thewishlist.io/services/recommendations/api/v1/recommendations/${encodeURIComponent(t)}/${encodeURIComponent(n)}?n=${c}`;try{const e=await fetch(i,{method:"GET",headers:{Accept:"application/json",Authorization:r,"X-Twc-Tenant":t}});if(!e.ok)return console.error("Recommendations request failed:",e.status),null;return function(e,n){const t=e&&e.recommendations;if(!Array.isArray(t))return[];const o=[];return t.forEach(e=>{var n;const t=e&&e.product;if(!t)return;const a=f(t.name),r=f(t.image_url),c=f(t.product_url);if(!h({name:a,imageUrl:r,productUrl:c}))return;const i=f(t.product_ref)||f(t.product_id)||c;o.push({score:"number"==typeof e.score?e.score:0,product:{id:f(t.product_id)||i,ref:i,name:a,imageUrl:r,productUrl:c,price:null!==(n=y(t.price))&&void 0!==n?n:0,originalPrice:y(t.original_price)}})}),g(o,n)}(await e.json().catch(()=>null),a)}catch(e){return console.error("Error fetching recommendations:",e),null}}async function b(e){const{engine:n,email:t,count:o,productId:a}=e,r=encodeURIComponent(n.siteIdentifier),c=[`tags=${encodeURIComponent(n.profileTag)}`,`limits=${o}`];a&&c.push(`products=${encodeURIComponent(a)}`),t&&c.push(`shopper=${encodeURIComponent(t)}`);const i=`https://${r}.a.searchspring.io/boost/${r}/recommend?`+c.join("&");try{const e=await fetch(i,{method:"GET",headers:{Accept:"application/json"}});if(!e.ok)return console.error("Athos recommendations request failed:",e.status),null;return function(e,n){const t=Array.isArray(e)?e[0]:e,o=t&&t.results;if(!Array.isArray(o))return[];const a=[];return o.forEach((e,n)=>{var t;const r=e&&e.mappings&&e.mappings.core;if(!r)return;const c=f(r.name),i=f(r.imageUrl)||f(r.thumbnailImageUrl),m=f(r.url);if(!h({name:c,imageUrl:i,productUrl:m}))return;const s=f(r.uid)||f(r.sku)||m,l=null!==(t=y(r.price))&&void 0!==t?t:0,d=y(r.msrp);a.push({score:o.length-n,product:{id:f(r.uid)||s,ref:s,name:c,imageUrl:i,productUrl:m,price:l,originalPrice:null!==d&&d>l?d:null}})}),g(a,n)}(await e.json().catch(()=>null),o)}catch(e){return console.error("Error fetching Athos recommendations:",e),null}}const x=new Map;const C="twc-nm-recs",k="twc-nm-recs-title";function S(e,n){if(n)try{return new Intl.NumberFormat(void 0,{style:"currency",currency:n}).format(e)}catch(e){}return new Intl.NumberFormat(void 0,{minimumFractionDigits:0,maximumFractionDigits:2}).format(e)}function E(){const e=document.createElement("section");e.id=C,e.className="twc-nm-recs",e.setAttribute("aria-labelledby",k);const n=document.createElement("h3");n.id=k,n.className="twc-nm-recs-title",n.textContent="Shop similar styles",e.appendChild(n);const t=document.createElement("div");return t.className="twc-nm-recs-row",function(e){let n=0,t=0,o=!1,a=!1,r=!1;e.addEventListener("pointerdown",c=>{"mouse"===c.pointerType&&0===c.button&&(o=!0,a=!1,r=!1,n=c.clientX,t=e.scrollLeft,e.classList.add("twc-nm-recs-row--dragging"))}),e.addEventListener("pointermove",r=>{if(!o)return;const c=r.clientX-n;!a&&Math.abs(c)<5||(a=!0,e.scrollLeft=t-c,r.preventDefault())});const c=()=>{o&&(o=!1,r=a,e.classList.remove("twc-nm-recs-row--dragging"))};e.addEventListener("pointerup",c),e.addEventListener("pointercancel",c),e.addEventListener("pointerleave",c),e.addEventListener("click",e=>{r&&(r=!1,e.preventDefault(),e.stopPropagation())},!0),e.addEventListener("dragstart",e=>{o&&a&&e.preventDefault()})}(t),e.appendChild(t),{section:e,row:t}}function A(){const e=document.createElement("div");e.className="twc-nm-rec twc-nm-rec--skeleton",e.setAttribute("aria-hidden","true");const n=document.createElement("div");n.className="twc-nm-rec-imgbox twc-nm-skel",e.appendChild(n);const t=document.createElement("span");t.className="twc-nm-skel twc-nm-skel-line",e.appendChild(t);const o=document.createElement("span");return o.className="twc-nm-skel twc-nm-skel-line twc-nm-skel-line--short",e.appendChild(o),e}function L(e,n,t){const o=e.querySelector(".twc-nm-recs-row");if(!o)return;const a=(r=t,window.Shopify&&window.Shopify.currency&&window.Shopify.currency.active||r||null);var r;const c=document.createDocumentFragment();n.forEach(e=>{c.appendChild(function(e,n){const t=document.createElement("a");t.className="twc-nm-rec",t.href=e.productUrl,t.target="_blank",t.rel="noopener noreferrer";const o=document.createElement("div");o.className="twc-nm-rec-imgbox";const a=document.createElement("img");a.className="twc-nm-rec-img",a.src=e.imageUrl,a.alt=e.name,a.loading="lazy",a.decoding="async",o.appendChild(a),t.appendChild(o);const r=document.createElement("span");r.className="twc-nm-rec-name",r.textContent=e.name,t.appendChild(r);const c=document.createElement("span");if(c.className="twc-nm-rec-price",null!==e.originalPrice&&e.originalPrice>e.price){const t=document.createElement("s");t.className="twc-nm-rec-was",t.textContent=S(e.originalPrice,n),c.appendChild(t),c.appendChild(document.createTextNode(" "))}return c.appendChild(document.createTextNode(S(e.price,n))),t.appendChild(c),t}(e,a))}),o.textContent="",o.appendChild(c),e.classList.remove("twc-nm-recs--loading"),e.removeAttribute("aria-busy")}function T(){const e=document.getElementById(C);e&&e.parentNode&&e.parentNode.removeChild(e)}const N={"notify-me":{intro:"Register to receive a notification as soon as this item is back in stock",submit:"Notify Me"},"coming-soon":{intro:"Register your interest to hear more about this item",submit:"Register Interest"}},M=["title","intro","sizeLabel","sizePlaceholder","emailPlaceholder","emailOptIn","smsOptIn","submit","success","disclaimer"];function I(e,n){const t={title:"{product}",intro:N[n].intro,sizeLabel:"Size",sizePlaceholder:"Select a size",emailLabel:"Email",emailPlaceholder:"you@example.com",emailOptIn:"Email me store updates",smsOptIn:"Text me store updates",submit:N[n].submit,success:"You're on the list. We'll be in touch.",disclaimer:null};if(void 0===e)return t;if(!e||"object"!=typeof e||Array.isArray(e))return a("copy","an object"),t;const o=e;M.forEach(e=>{const n=o[e];null!=n&&("string"==typeof n?t[e]=n:a(`copy.${e}`,"a string"))});const r=o.emailLabel;return!1===r||"string"==typeof r?t.emailLabel=r:null!=r&&a("copy.emailLabel","a string or false"),t}function B(e,n){return e.split("{product}").join(n)}function P(e){return e.replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;").replace(/'/g,"&#39;")}function $(e){if("string"!=typeof e)return null;const n=e.trim();return/^(https?:)?\/\//i.test(n)?n:null}function z(e){return"string"==typeof e?e.trim():""}function G(o){const{wrapper:a,config:r,localization:i}=o,{fields:m,type:l,display:u,auth:p,tenant:f,recommendationsEnabled:y,recommendationsCount:h,recommendationsProfile:g,currency:k,customerEmail:S,copy:N,productSummary:M,classes:I,jsonConfigured:G}=r,U='<button type="button" id="popup-close" class="twc-nm-close" aria-label="Close"><svg viewBox="0 0 16 16" width="16" height="16" aria-hidden="true" focusable="false"><path d="M3 3l10 10M13 3L3 13" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" fill="none"/></svg></button>',j='<h2 id="popup-title" class="twc-nm-title"></h2>',R=G?`<div class="twc-nm-header">${U}${j}</div>`:`${U}\n ${j}`,O=document.createElement("div");O.innerHTML=`\n <div id="twc-nm-overlay" role="dialog" aria-modal="true" aria-labelledby="popup-title">\n <div class="twc-nm-card">\n ${R}\n <p id="popup-text" class="twc-nm-text"></p>\n <div id="popup-notice" class="twc-nm-status" role="status" aria-live="polite" hidden></div>\n <form id="popup-form" class="twc-nm-form">\n <div class="twc-nm-field">\n <label class="twc-nm-label" for="twc-nm-size">${P(N.sizeLabel)}</label>\n <select class="twc-nm-select" id="twc-nm-size" name="select-size" required>\n <option value="" disabled selected>${P(N.sizePlaceholder)}</option>\n </select>\n </div>\n </form>\n </div>\n </div>\n `,a.appendChild(O);const H=document.getElementById("twc-nm-overlay"),F=document.getElementById("popup-close"),K=document.getElementById("popup-title"),D=document.getElementById("popup-text"),q=document.querySelector("select[name='select-size']"),_=document.getElementById("popup-form"),V=document.getElementById("popup-notice");if(!(H&&F&&K&&D&&q&&_&&V))return null;"panel"===u&&H.classList.add("twc-nm--panel");const J=M?function(){const e=document.createElement("div");return e.className="twc-nm-summary",e.innerHTML='<div class="twc-nm-summary-image"><img alt="" /></div><div class="twc-nm-summary-info"><p class="twc-nm-summary-name"></p><p class="twc-nm-summary-variant"></p><p class="twc-nm-summary-price"><span class="twc-nm-summary-now"></span> <s class="twc-nm-summary-compare"></s></p></div>',e}():null;J&&_.insertBefore(J,_.firstChild);const Z=s.map(e=>`<option value="${e.code}"${e.code===i.countryCode?" selected":""}>${e.name}</option>`).join(""),W=(e,n,t)=>`<div class="twc-nm-field"><label class="twc-nm-label" for="${e}">${n}</label>${t}</div>`,Y={email:!1===N.emailLabel?`<div class="twc-nm-field"><input class="twc-nm-input" id="twc-nm-email" name="email" type="email" placeholder="${P(N.emailPlaceholder)}" aria-label="${P(N.emailPlaceholder||"Email")}" required /></div>`:W("twc-nm-email",P(N.emailLabel),`<input class="twc-nm-input" id="twc-nm-email" name="email" type="email" placeholder="${P(N.emailPlaceholder)}" required />`),mobile:W("twc-nm-mobile","Mobile",'<input class="twc-nm-input" id="twc-nm-mobile" name="mobile" type="tel" placeholder="Mobile number" required />'),firstName:W("twc-nm-firstname","First name",'<input class="twc-nm-input" id="twc-nm-firstname" name="firstName" type="text" placeholder="First name" required />'),lastName:W("twc-nm-lastname","Last name",'<input class="twc-nm-input" id="twc-nm-lastname" name="lastName" type="text" placeholder="Last name" required />'),countryCode:W("twc-nm-country","Country",`<select class="twc-nm-select" id="twc-nm-country" name="countryCode"><option value="">Select country</option>${Z}</select>`),provinceCode:W("twc-nm-province","State / Province",`<input class="twc-nm-input" id="twc-nm-province" name="provinceCode" type="text" placeholder="State / province code" value="${i.provinceCode||""}" />`)};m.forEach(e=>{Y[e]&&_.insertAdjacentHTML("beforeend",Y[e])});const X=[m.includes("email")?`<label class="twc-nm-check"><input type="checkbox" name="mailList" /> ${P(N.emailOptIn)}</label>`:"",m.includes("mobile")?`<label class="twc-nm-check"><input type="checkbox" name="mailListSms" /> ${P(N.smsOptIn)}</label>`:""].join("");if(X&&_.insertAdjacentHTML("beforeend",`<div class="twc-nm-checks">${X}</div>`),_.insertAdjacentHTML("beforeend",'<div id="popup-status" class="twc-nm-status" role="status" aria-live="polite" hidden></div>'),_.insertAdjacentHTML("beforeend",`<button type="submit" class="twc-nm-submit">${P(N.submit)}</button>`),N.disclaimer&&_.insertAdjacentHTML("beforeend",`<p class="twc-nm-disclaimer">${P(N.disclaimer)}</p>`),function(e,n){Object.keys(n).forEach(t=>{const o=n[t];o&&e.querySelectorAll(c[t]).forEach(e=>{e.classList.add(...o,"twc-nm-themed")})})}(H,I),S){const e=_.querySelector("input[name='email']");e&&(e.value=S)}const Q=document.getElementById("popup-status"),ee=_.querySelector(".twc-nm-submit");if(!Q||!ee)return null;function ne(e,n){Q.textContent=n,Q.className=`twc-nm-status twc-nm-status--${e}`,Q.hidden=!1}function te(){Q.hidden=!0,Q.textContent=""}function oe(e,n){_.hidden=!0,V.textContent=n,V.className=`twc-nm-status twc-nm-status--${e}`,V.hidden=!1}const ae=e=>{const n=_.querySelector(e);return n?n.value:""},re=e=>{const n=_.querySelector(e);return!!n&&n.checked},ce=H.querySelector(".twc-nm-card");let ie=null,me=[],se=null;async function le(e){const n=await function(e,n,t){return w(e,n,t)}(f,p,g);if(!n)return!1;if(!H.classList.contains("is-open"))return!1;const t=function(e){const{section:n,row:t}=E();n.classList.add("twc-nm-recs--loading"),n.setAttribute("aria-busy","true");for(let n=0;n<e;n++)t.appendChild(A());return n}(h);ce.appendChild(t);const o=await async function(e,n){const{email:t,tenant:o,auth:a,count:r,productId:c}=n,i=`${e.engine}|${o}|${t}|${r}|${c||""}`,m=x.get(i);if(m)return m;let s=null;return s="ATHOS"===e.engine?await b({engine:e,email:t,count:r,productId:c}):await v({email:t,tenant:o,auth:a,count:r}),null===s?[]:(x.set(i,s),s)}(n,{email:e,tenant:f,auth:p,count:h,productId:ie?String(ie.id):null});return!!t.isConnected&&(o.length?(L(t,o,k),!0):(T(),!1))}function de(e){if(!y||!ce)return Promise.resolve(!1);if(se)return se;if(document.getElementById(C))return Promise.resolve(!0);const n=le(e);return se=n,n.then(e=>(se===n&&(se=null),e),e=>(se===n&&(se=null),console.error("Error rendering recommendations:",e),!1))}let ue=null,pe=0;function we(e){"Escape"===e.key&&fe()}function fe(){pe++,H.classList.remove("is-open"),document.body.style.overflow="",document.removeEventListener("keydown",we),te(),T(),se=null,ue&&"function"==typeof ue.focus&&ue.focus()}function ye(){if(!J||!ie)return;const e=me.find(e=>String(e.id)===q.value)||null;!function(e,n,t,o){const a=n=>e.querySelector(n),r=a(".twc-nm-summary-image"),c=a(".twc-nm-summary-image img"),i=t&&$(t.image)||$(n.image);i?(c.src=i,c.alt=n.title):c.removeAttribute("src"),r.hidden=!i,a(".twc-nm-summary-name").textContent=n.title;const m=a(".twc-nm-summary-variant");m.textContent=t?t.title:"",m.hidden=!t;let s="",l="";if(t)s=z(t.price),l=z(t.compareAtPrice);else{const e=o.map(e=>z(e.price));e.length&&e[0]&&e.every(n=>n===e[0])&&(s=e[0])}l===s&&(l=""),a(".twc-nm-summary-now").textContent=s;const d=a(".twc-nm-summary-compare");d.textContent=l,d.hidden=!l,a(".twc-nm-summary-price").hidden=!s}(J,ie,e,me)}return F.addEventListener("click",fe),q.addEventListener("change",ye),H.addEventListener("click",e=>{e.target===H&&fe()}),_.addEventListener("submit",async e=>{e.preventDefault();const n=me.find(e=>String(e.id)===q.value);if(!n)return void ne("error","Please select a size.");const t={variantRef:n.id,email:ae("input[name='email']"),subscribe:re("input[name='mailList']"),subscribeSms:re("input[name='mailListSms']")};if("coming-soon"===l?t.comingSoon=!0:t.notifyMe=!0,m.includes("firstName")&&(t.firstName=ae("input[name='firstName']")),m.includes("lastName")&&(t.lastName=ae("input[name='lastName']")),m.includes("mobile")&&(t.mobile=ae("input[name='mobile']")),m.includes("countryCode")){const e=ae("select[name='countryCode']");e&&(t.countryCode=e)}else i.countryCode&&(t.countryCode=i.countryCode);if(m.includes("provinceCode")){const e=ae("input[name='provinceCode']");e&&(t.provinceCode=e)}else i.provinceCode&&(t.provinceCode=i.provinceCode);i.marketId&&(t.marketId=i.marketId),i.marketHandle&&(t.marketHandle=i.marketHandle),te();const o=pe,a=ee.textContent||"";ee.disabled=!0,ee.textContent="Sending…";let r={ok:!1,reason:"error"};try{r=await async function(e,n,t){const o=await d(n);if(!o)return{ok:!1,reason:"auth"};try{return(await fetch("https://api.au-aws.thewishlist.io/services/wsservice/api/wishlist/items/customerInterest",{method:"POST",headers:{"Content-Type":"application/json",Authorization:o,"X-Twc-Tenant":t},body:JSON.stringify(e)})).ok?{ok:!0}:{ok:!1,reason:"error"}}catch(e){return console.error("Error submitting form:",e),{ok:!1,reason:"error"}}}(t,p,f)}finally{o===pe&&(ee.disabled=!1,ee.textContent=a)}if(o===pe)if(r.ok){ne("success",N.success);const e=S||ae("input[name='email']");!!e&&await de(e)||o!==pe||setTimeout(()=>{o===pe&&fe()},1500)}else"auth"===r.reason?ne("error","proxy"===p.mode?"Please log in to your account to continue.":"Something went wrong. Please try again."):ne("error","Something went wrong. Please try again.")}),{open:function(o){pe++,ee.disabled=!1,ee.textContent=N.submit,te(),_.hidden=!1,V.hidden=!0,V.textContent="",T(),se=null,H.classList.contains("is-open")||(ue=document.activeElement),H.classList.add("is-open"),document.body.style.overflow="hidden",document.addEventListener("keydown",we);const a=function(t){const o=t?t.closest(`[${e}]`):null;if(o){try{const t=JSON.parse(o.getAttribute(e)||"");if(n(t))return{ok:!0,product:t,source:"card"}}catch(e){}return console.error(`notify-me-wl: invalid ${e} JSON on`,o),{ok:!1}}const a=window.currentProduct;return n(a)?{ok:!0,product:a,source:"page"}:{ok:!1}}(o);if(!a.ok)return ie=null,me=[],K.textContent="",D.textContent=B(N.intro,""),void oe("error","Product information is unavailable. Please refresh the page.");if(ie=a.product,me=(ie.variants||[]).filter(e=>!!e&&("number"==typeof e.id||"string"==typeof e.id)&&"string"==typeof e.title&&!0!==e.available),K.textContent=B(N.title,ie.title),D.textContent=B(N.intro,ie.title),!me.length)return void oe("info","All sizes are currently in stock.");const r=o?o.getAttribute("data-variant-id"):null,c="page"===a.source?[r,...t()]:[r];var i,m;i=me,m=function(e,n){const t=e.map(e=>String(e.id));for(const e of n){const n=e?e.trim():"";if(n&&t.includes(n))return n}return 1===t.length?t[0]:null}(me,c),q.innerHTML=`<option value="" disabled selected>${P(N.sizePlaceholder)}</option>`,i.forEach(e=>{const n=document.createElement("option");n.value=String(e.id),n.textContent=e.title,q.appendChild(n)}),m&&(q.value=m),ye(),S&&de(S);const s=_.querySelector("select, input");s&&s.focus()}}}function U(e){var n;const t=(null!=e?e:"").trim();if(!t)return null;const o=null!==(n=t.split("?")[0].split("/").pop())&&void 0!==n?n:"";return/^\d+$/.test(o)?o:null}function j(e){const n=(null!=e?e:"").trim();return/^[a-z0-9_-]+$/i.test(n)?n:null}function R(e){const n=(null!=e?e:"").trim();return/^[a-z]{2}$/i.test(n)?n.toUpperCase():null}function O(e){var n,t,o;const a={countryCode:R(e("data-country-code")),provinceCode:null,marketId:U(e("data-market-id")),marketHandle:j(e("data-market-handle"))},r=window.Shopify;if(r){const e=null===(n=r.markets)||void 0===n?void 0:n.currentMarket;if(e&&(!a.countryCode&&e.countryCode&&(a.countryCode=e.countryCode),a.marketId||(a.marketId=U(String(null!==(t=e.id)&&void 0!==t?t:"")))),a.countryCode||"string"!=typeof r.country||(a.countryCode=r.country),!a.countryCode&&"string"==typeof r.locale){const e=r.locale.split("-");e.length>1&&(a.countryCode=e[e.length-1].toUpperCase())}}if(!a.countryCode||!a.provinceCode){const e=null===(o=window.customer)||void 0===o?void 0:o.default_address;e&&(!a.countryCode&&e.country_code&&(a.countryCode=e.country_code),!a.provinceCode&&e.province_code&&(a.provinceCode=e.province_code))}if(!a.countryCode){const e=(navigator.language||"").split("-");e.length>1&&(a.countryCode=e[e.length-1].toUpperCase())}return a}function H(e){const n=e("data-customer-email");if(n&&n.trim())return n.trim();const t=window.customer&&window.customer.email;if(t&&t.trim())return t.trim();const o=window.customerEmail;return"string"==typeof o&&o.trim()?o.trim():null}const F="[data-twc-notify-me]";function K(){const e=document.getElementById("notification-widget");if(!e)return;const n=document.getElementById("popup-open"),t=function(e){const n=e.querySelector('script[type="application/json"][data-twc-config]');if(!n)return null;try{const e=JSON.parse(n.textContent||"");if(e&&"object"==typeof e&&!Array.isArray(e))return e}catch(e){}return console.error("notify-me-wl: invalid data-twc-config JSON"),null}(e),c=function(e,n,t){const c=t=>(e?e.getAttribute(t):null)||n.getAttribute(t)||null,i=e=>null===t[e]?void 0:t[e],m=e=>{const n=i(e);return void 0===n?null:"boolean"==typeof n?n:(a(e,"true or false"),null)};return{string(e){const n=c(e);if(null!==n)return n;const t=o(e),r=i(t);return void 0===r?null:"string"==typeof r?r:"number"==typeof r&&Number.isFinite(r)?String(r):(a(t,"a string"),null)},boolean(e){const n=c(e);return null!==n?"false"!==n:m(o(e))},number(e){const n=c(e);if(null!==n){const e=parseInt(n,10);if(Number.isFinite(e))return e}const t=o(e),r=i(t);return void 0===r?null:"number"==typeof r&&Number.isFinite(r)?r:(a(t,"a number"),null)},stringArray(e){const n=c(e);if(null!==n){try{const e=JSON.parse(n);if(r(e))return e}catch(e){}console.warn(`notify-me-wl: ignoring invalid ${e} attribute`)}const t=o(e),m=i(t);return void 0===m?null:r(m)?m:(a(t,"an array of strings"),null)},json:i,jsonBoolean:m}}(n,e,t||{}),i=c.string;!function(){const e=document.createElement("style");e.type="text/css",e.innerText="\n .notification-btn {\n display: inline-block;\n width: 100%;\n padding: 12px 24px;\n background: #fff;\n border: 1px solid #111827;\n border-radius: 8px;\n color: #111827;\n font: inherit;\n text-align: center;\n text-transform: uppercase;\n cursor: pointer;\n }\n .notification-btn:hover {\n background: #111827;\n color: #fff;\n }\n #twc-nm-overlay {\n position: fixed;\n inset: 0;\n z-index: 99999;\n display: flex;\n align-items: center;\n justify-content: center;\n padding: 16px;\n background: var(--twc-nm-overlay-bg, rgba(17, 17, 17, 0.55));\n opacity: 0;\n visibility: hidden;\n transition: opacity var(--twc-nm-transition, 200ms ease);\n }\n #twc-nm-overlay.is-open {\n opacity: 1;\n visibility: visible;\n }\n #twc-nm-overlay * {\n box-sizing: border-box;\n }\n #twc-nm-overlay .twc-nm-card {\n position: relative;\n width: 100%;\n max-width: 400px;\n max-height: calc(100vh - 32px);\n overflow-y: auto;\n padding: var(--twc-nm-padding, 24px);\n background: var(--twc-nm-bg, #fff);\n color: var(--twc-nm-text, #111827);\n border-radius: 12px;\n box-shadow: 0 10px 40px rgba(0, 0, 0, 0.18);\n font: inherit;\n font-family: var(--twc-nm-font, inherit);\n transform: translateY(8px);\n transition: transform var(--twc-nm-transition, 200ms ease);\n }\n #twc-nm-overlay.is-open .twc-nm-card {\n transform: translateY(0);\n }\n /* Panel mode: same blocking backdrop, docked to the right edge and\n full height instead of centred. Declared after the modal rules so\n the shared properties are overridden. */\n #twc-nm-overlay.twc-nm--panel {\n align-items: stretch;\n justify-content: flex-end;\n padding: 0;\n }\n #twc-nm-overlay.twc-nm--panel .twc-nm-card {\n width: var(--twc-nm-panel-width, 420px);\n max-width: 100%;\n max-height: none;\n border-radius: 0;\n box-shadow: var(--twc-nm-panel-shadow, -10px 0 40px rgba(0, 0, 0, 0.18));\n transform: translateX(100%);\n }\n /* Needs the extra class to beat both the .is-open rule above and the\n panel's own resting transform. */\n #twc-nm-overlay.twc-nm--panel.is-open .twc-nm-card {\n transform: translateX(0);\n }\n @media (max-width: 480px) {\n #twc-nm-overlay.twc-nm--panel .twc-nm-card {\n width: var(--twc-nm-panel-width-mobile, 100%);\n }\n }\n /* Groups the title and close button so a theme can lay them out as a\n row. With no border it changes nothing: the close button stays\n positioned against the card. */\n #twc-nm-overlay .twc-nm-header {\n border-bottom: var(--twc-nm-header-border, none);\n }\n /* Layout below is never themed — it keeps the close button usable\n whatever classes a theme attaches. Cosmetics follow :not(themed). */\n #twc-nm-overlay .twc-nm-close {\n position: absolute;\n top: 12px;\n right: 12px;\n width: 32px;\n height: 32px;\n min-width: 0;\n min-height: 0;\n display: flex;\n align-items: center;\n justify-content: center;\n margin: 0;\n padding: 0;\n border: none;\n background: transparent;\n box-shadow: none;\n text-indent: 0;\n line-height: 1;\n opacity: 1;\n visibility: visible;\n cursor: pointer;\n }\n #twc-nm-overlay .twc-nm-close:where(:not(.twc-nm-themed)) {\n border-radius: 6px;\n color: var(--twc-nm-muted, #6b7280);\n }\n /* The icon is an inline SVG rather than a text glyph so host-theme\n button resets (font-size: 0, icon fonts, text-indent) can't hide it. */\n #twc-nm-overlay .twc-nm-close svg {\n display: block;\n width: 16px;\n height: 16px;\n flex: none;\n pointer-events: none;\n }\n #twc-nm-overlay .twc-nm-close::before,\n #twc-nm-overlay .twc-nm-close::after {\n content: none;\n }\n #twc-nm-overlay .twc-nm-close:where(:not(.twc-nm-themed)):hover {\n background: #f3f4f6;\n color: var(--twc-nm-text, #111827);\n }\n #twc-nm-overlay .twc-nm-title {\n margin: 0 32px 4px 0;\n }\n #twc-nm-overlay .twc-nm-title:where(:not(.twc-nm-themed)) {\n font-size: 18px;\n font-weight: 600;\n }\n #twc-nm-overlay .twc-nm-text {\n margin: 0 0 16px;\n }\n #twc-nm-overlay .twc-nm-text:where(:not(.twc-nm-themed)) {\n font-size: 14px;\n line-height: 1.5;\n color: var(--twc-nm-muted, #6b7280);\n }\n #twc-nm-overlay .twc-nm-form {\n display: flex;\n flex-direction: column;\n gap: 14px;\n }\n #twc-nm-overlay .twc-nm-field {\n display: flex;\n flex-direction: column;\n gap: 6px;\n }\n #twc-nm-overlay .twc-nm-label:where(:not(.twc-nm-themed)) {\n font-size: 13px;\n font-weight: 500;\n color: #374151;\n }\n #twc-nm-overlay .twc-nm-input,\n #twc-nm-overlay .twc-nm-select {\n width: 100%;\n }\n /* The shorthand resets every background sub-property, so host CSS\n can't leak into an unconfigured widget; the select's arrow is added\n by the rule after it. */\n #twc-nm-overlay .twc-nm-input:where(:not(.twc-nm-themed)),\n #twc-nm-overlay .twc-nm-select:where(:not(.twc-nm-themed)) {\n padding: 10px 12px;\n font-size: 14px;\n font-family: inherit;\n color: var(--twc-nm-text, #111827);\n background: #fff;\n border: 1px solid var(--twc-nm-border, #e5e7eb);\n border-radius: var(--twc-nm-radius, 8px);\n }\n #twc-nm-overlay .twc-nm-input:where(:not(.twc-nm-themed)):focus,\n #twc-nm-overlay .twc-nm-select:where(:not(.twc-nm-themed)):focus {\n outline: none;\n border-color: var(--twc-nm-text, #111827);\n box-shadow: 0 0 0 3px rgba(17, 24, 39, 0.08);\n }\n #twc-nm-overlay .twc-nm-select {\n appearance: none;\n -webkit-appearance: none;\n cursor: pointer;\n background-image: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 12 12'%3E%3Cpath d='M2 4l4 4 4-4' stroke='%236b7280' stroke-width='1.5' fill='none' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E\");\n background-repeat: no-repeat;\n background-position: right 12px center;\n }\n /* After the shared padding above, which it must override. */\n #twc-nm-overlay .twc-nm-select:where(:not(.twc-nm-themed)) {\n padding-right: 36px;\n }\n #twc-nm-overlay .twc-nm-checks {\n display: flex;\n flex-direction: column;\n gap: 10px;\n }\n #twc-nm-overlay .twc-nm-check {\n display: flex;\n align-items: center;\n gap: 8px;\n font-size: 13px;\n color: #374151;\n cursor: pointer;\n }\n #twc-nm-overlay .twc-nm-check input {\n width: 16px;\n height: 16px;\n margin: 0;\n }\n #twc-nm-overlay .twc-nm-check input:where(:not(.twc-nm-themed)) {\n accent-color: var(--twc-nm-text, #111827);\n }\n #twc-nm-overlay .twc-nm-status {\n font-size: 13px;\n line-height: 1.5;\n padding: 10px 12px;\n border-radius: var(--twc-nm-radius, 8px);\n }\n #twc-nm-overlay .twc-nm-status--error {\n color: #b91c1c;\n background: #fef2f2;\n }\n #twc-nm-overlay .twc-nm-status--success {\n color: #15803d;\n background: #f0fdf4;\n }\n #twc-nm-overlay .twc-nm-status--info {\n color: #374151;\n background: #f3f4f6;\n }\n #twc-nm-overlay .twc-nm-submit {\n width: 100%;\n cursor: pointer;\n }\n #twc-nm-overlay .twc-nm-submit:where(:not(.twc-nm-themed)) {\n padding: 12px 16px;\n font-size: 14px;\n font-weight: 600;\n font-family: inherit;\n color: #fff;\n background: #111827;\n border: none;\n border-radius: var(--twc-nm-radius, 8px);\n transition: background 150ms ease;\n }\n #twc-nm-overlay .twc-nm-submit:where(:not(.twc-nm-themed)):hover {\n background: #374151;\n }\n #twc-nm-overlay .twc-nm-submit:disabled {\n opacity: 0.6;\n cursor: default;\n }\n #twc-nm-overlay .twc-nm-disclaimer {\n margin: 0;\n }\n #twc-nm-overlay .twc-nm-disclaimer:where(:not(.twc-nm-themed)) {\n font-size: 12px;\n line-height: 1.5;\n color: var(--twc-nm-muted, #6b7280);\n }\n #twc-nm-overlay .twc-nm-summary {\n display: flex;\n align-items: flex-start;\n }\n #twc-nm-overlay .twc-nm-summary:where(:not(.twc-nm-themed)) {\n gap: 16px;\n }\n /* Theme classes often set display; hidden parts must stay hidden. */\n #twc-nm-overlay .twc-nm-summary [hidden] {\n display: none !important;\n }\n #twc-nm-overlay .twc-nm-summary-image {\n flex: none;\n }\n #twc-nm-overlay .twc-nm-summary-image:where(:not(.twc-nm-themed)) {\n width: 96px;\n }\n #twc-nm-overlay .twc-nm-summary-image img {\n display: block;\n width: 100%;\n height: auto;\n }\n #twc-nm-overlay .twc-nm-summary-info {\n flex: 1 1 auto;\n min-width: 0;\n display: flex;\n flex-direction: column;\n gap: 4px;\n }\n #twc-nm-overlay .twc-nm-summary-info p {\n margin: 0;\n }\n #twc-nm-overlay .twc-nm-summary-name {\n overflow: hidden;\n white-space: nowrap;\n text-overflow: ellipsis;\n }\n #twc-nm-overlay .twc-nm-summary-name:where(:not(.twc-nm-themed)) {\n font-size: 14px;\n font-weight: 600;\n }\n #twc-nm-overlay .twc-nm-summary-variant:where(:not(.twc-nm-themed)) {\n font-size: 13px;\n color: var(--twc-nm-muted, #6b7280);\n }\n #twc-nm-overlay .twc-nm-summary-price:where(:not(.twc-nm-themed)) {\n font-size: 14px;\n }\n #twc-nm-overlay .twc-nm-summary-compare:where(:not(.twc-nm-themed)) {\n margin-left: 6px;\n color: #9ca3af;\n }\n #twc-nm-overlay .twc-nm-recs {\n margin-top: 20px;\n padding-top: 20px;\n border-top: 1px solid var(--twc-nm-border, #e5e7eb);\n }\n #twc-nm-overlay .twc-nm-recs-title {\n margin: 0 0 12px;\n font-size: 14px;\n font-weight: 600;\n color: #111827;\n }\n #twc-nm-overlay .twc-nm-recs-row {\n display: flex;\n gap: 12px;\n overflow-x: auto;\n scroll-snap-type: x mandatory;\n -webkit-overflow-scrolling: touch;\n padding-bottom: 8px;\n scrollbar-width: thin;\n }\n /* Only where a real pointer exists — the affordance is meaningless\n on touch, which pans the row natively. */\n @media (hover: hover) {\n #twc-nm-overlay .twc-nm-recs-row {\n cursor: grab;\n }\n }\n #twc-nm-overlay .twc-nm-recs-row--dragging,\n #twc-nm-overlay .twc-nm-recs-row--dragging .twc-nm-rec {\n cursor: grabbing;\n }\n #twc-nm-overlay .twc-nm-recs-row--dragging {\n /* Snapping fights a manually driven scrollLeft and feels sticky. */\n scroll-snap-type: none;\n user-select: none;\n -webkit-user-select: none;\n }\n #twc-nm-overlay .twc-nm-rec {\n flex: 0 0 132px;\n scroll-snap-align: start;\n display: flex;\n flex-direction: column;\n gap: 6px;\n color: inherit;\n text-decoration: none;\n }\n #twc-nm-overlay .twc-nm-rec:focus-visible {\n outline: 2px solid #111827;\n outline-offset: 2px;\n border-radius: 8px;\n }\n #twc-nm-overlay .twc-nm-rec-imgbox {\n aspect-ratio: 3 / 4;\n overflow: hidden;\n border-radius: 8px;\n background: #f3f4f6;\n }\n #twc-nm-overlay .twc-nm-rec-img {\n display: block;\n width: 100%;\n height: 100%;\n object-fit: cover;\n transition: opacity 150ms ease;\n }\n #twc-nm-overlay .twc-nm-rec:hover .twc-nm-rec-img {\n opacity: 0.85;\n }\n #twc-nm-overlay .twc-nm-rec-name {\n display: -webkit-box;\n -webkit-line-clamp: 2;\n -webkit-box-orient: vertical;\n overflow: hidden;\n font-size: 13px;\n line-height: 1.35;\n color: #111827;\n }\n #twc-nm-overlay .twc-nm-rec-price {\n font-size: 13px;\n font-weight: 600;\n color: #111827;\n }\n #twc-nm-overlay .twc-nm-rec-was {\n margin-right: 4px;\n font-weight: 400;\n color: #9ca3af;\n }\n #twc-nm-overlay .twc-nm-rec--skeleton {\n pointer-events: none;\n }\n #twc-nm-overlay .twc-nm-skel {\n background-color: #f3f4f6;\n background-image: linear-gradient(\n 90deg,\n #f3f4f6 0%,\n #e8eaee 50%,\n #f3f4f6 100%\n );\n background-size: 200% 100%;\n border-radius: 4px;\n animation: twc-nm-shimmer 1.3s ease-in-out infinite;\n }\n #twc-nm-overlay .twc-nm-rec-imgbox.twc-nm-skel {\n border-radius: 8px;\n }\n #twc-nm-overlay .twc-nm-skel-line {\n height: 12px;\n width: 100%;\n }\n #twc-nm-overlay .twc-nm-skel-line--short {\n width: 45%;\n }\n @keyframes twc-nm-shimmer {\n from {\n background-position: 200% 0;\n }\n to {\n background-position: -200% 0;\n }\n }\n @media (prefers-reduced-motion: reduce) {\n #twc-nm-overlay,\n #twc-nm-overlay .twc-nm-card,\n #twc-nm-overlay .twc-nm-rec-img {\n transition: none;\n }\n #twc-nm-overlay .twc-nm-card,\n #twc-nm-overlay.twc-nm--panel .twc-nm-card {\n transform: none;\n }\n #twc-nm-overlay .twc-nm-skel {\n animation: none;\n }\n }\n ",document.head.appendChild(e)}();const s=c.stringArray("data-fields")||["email"],l=i("data-type")||"notify-me",d="panel"===i("data-display")?"panel":"modal",u=i("data-auth")||"proxy",p=i("data-tenant")||"victoria-woods",w=i("data-proxy-app")||"twc-sdk",f=i("data-access-token")||window.TWC_ACCESS_TOKEN||null,y=!1!==c.boolean("data-recommendations"),h=c.number("data-recommendations-count"),g=G({wrapper:e,config:{fields:s,type:l,display:d,auth:{mode:u,proxyApp:w,accessToken:f},tenant:p,recommendationsEnabled:y,recommendationsCount:null!==h&&h>0?h:8,recommendationsProfile:(i("data-recommendations-profile")||"").trim()||null,currency:i("data-currency")||null,customerEmail:H(i),copy:I(c.json("copy"),l),productSummary:!0===c.jsonBoolean("productSummary"),classes:m(c.json("classes")),jsonConfigured:null!==t},localization:O(i)});g&&(document.addEventListener("click",e=>{const n=e.target,t=n instanceof Element?n.closest(F):null;t&&(e.preventDefault(),g.open(t))},!0),n&&!n.matches(F)&&n.addEventListener("click",()=>g.open(n)))}"loading"===document.readyState?document.addEventListener("DOMContentLoaded",K):K()}();
1
+ !function(){"use strict";const e="data-twc-product";function n(e){if(!e||"object"!=typeof e)return!1;const n=e;return("number"==typeof n.id||"string"==typeof n.id)&&"string"==typeof n.title&&(void 0===n.variants||Array.isArray(n.variants))}function t(){const e=new URLSearchParams(window.location.search).get("variant"),n=document.querySelector('form[action*="/cart/add"] select[name="id"]');return[e,n?n.value:null]}function o(e){return e.replace(/^data-/,"").replace(/-([a-z])/g,(e,n)=>n.toUpperCase())}function a(e,n){console.warn(`notify-me-wl: ignoring data-twc-config "${e}": expected ${n}`)}function r(e){return Array.isArray(e)&&e.every(e=>"string"==typeof e)}const c={title:".twc-nm-title",intro:".twc-nm-text",label:".twc-nm-label",input:".twc-nm-input",select:".twc-nm-select",checkbox:".twc-nm-check input",submit:".twc-nm-submit",close:".twc-nm-close",disclaimer:".twc-nm-disclaimer",summary:".twc-nm-summary",summaryImage:".twc-nm-summary-image",summaryName:".twc-nm-summary-name",summaryVariant:".twc-nm-summary-variant",summaryPrice:".twc-nm-summary-price",summaryCompareAt:".twc-nm-summary-compare"},i=/^[A-Za-z_-][A-Za-z0-9_-]*$/;function s(e){const n={};if(null==e)return n;if("object"!=typeof e||Array.isArray(e))return a("classes","an object"),n;const t=e;return Object.keys(c).forEach(e=>{const o=t[e];if(null==o)return;if("string"!=typeof o)return void a(`classes.${e}`,"a string");const r=o.split(/\s+/).filter(Boolean),c=r.filter(e=>i.test(e));c.length<r.length&&a(`classes.${e}`,'class names of letters, digits, "-" and "_"'),c.length&&(n[e]=c)}),n}const m=[{code:"AF",name:"Afghanistan"},{code:"AX",name:"Åland Islands"},{code:"AL",name:"Albania"},{code:"DZ",name:"Algeria"},{code:"AS",name:"American Samoa"},{code:"AD",name:"Andorra"},{code:"AO",name:"Angola"},{code:"AI",name:"Anguilla"},{code:"AG",name:"Antigua and Barbuda"},{code:"AR",name:"Argentina"},{code:"AM",name:"Armenia"},{code:"AW",name:"Aruba"},{code:"AU",name:"Australia"},{code:"AT",name:"Austria"},{code:"AZ",name:"Azerbaijan"},{code:"BS",name:"Bahamas"},{code:"BH",name:"Bahrain"},{code:"BD",name:"Bangladesh"},{code:"BB",name:"Barbados"},{code:"BY",name:"Belarus"},{code:"BE",name:"Belgium"},{code:"BZ",name:"Belize"},{code:"BJ",name:"Benin"},{code:"BM",name:"Bermuda"},{code:"BT",name:"Bhutan"},{code:"BO",name:"Bolivia"},{code:"BA",name:"Bosnia and Herzegovina"},{code:"BW",name:"Botswana"},{code:"BR",name:"Brazil"},{code:"BN",name:"Brunei"},{code:"BG",name:"Bulgaria"},{code:"BF",name:"Burkina Faso"},{code:"BI",name:"Burundi"},{code:"CV",name:"Cabo Verde"},{code:"KH",name:"Cambodia"},{code:"CM",name:"Cameroon"},{code:"CA",name:"Canada"},{code:"KY",name:"Cayman Islands"},{code:"CF",name:"Central African Republic"},{code:"TD",name:"Chad"},{code:"CL",name:"Chile"},{code:"CN",name:"China"},{code:"CO",name:"Colombia"},{code:"KM",name:"Comoros"},{code:"CG",name:"Congo"},{code:"CD",name:"Congo, DR"},{code:"CK",name:"Cook Islands"},{code:"CR",name:"Costa Rica"},{code:"HR",name:"Croatia"},{code:"CU",name:"Cuba"},{code:"CW",name:"Curaçao"},{code:"CY",name:"Cyprus"},{code:"CZ",name:"Czech Republic"},{code:"DK",name:"Denmark"},{code:"DJ",name:"Djibouti"},{code:"DM",name:"Dominica"},{code:"DO",name:"Dominican Republic"},{code:"EC",name:"Ecuador"},{code:"EG",name:"Egypt"},{code:"SV",name:"El Salvador"},{code:"GQ",name:"Equatorial Guinea"},{code:"ER",name:"Eritrea"},{code:"EE",name:"Estonia"},{code:"SZ",name:"Eswatini"},{code:"ET",name:"Ethiopia"},{code:"FO",name:"Faroe Islands"},{code:"FJ",name:"Fiji"},{code:"FI",name:"Finland"},{code:"FR",name:"France"},{code:"GF",name:"French Guiana"},{code:"PF",name:"French Polynesia"},{code:"GA",name:"Gabon"},{code:"GM",name:"Gambia"},{code:"GE",name:"Georgia"},{code:"DE",name:"Germany"},{code:"GH",name:"Ghana"},{code:"GI",name:"Gibraltar"},{code:"GR",name:"Greece"},{code:"GL",name:"Greenland"},{code:"GD",name:"Grenada"},{code:"GP",name:"Guadeloupe"},{code:"GU",name:"Guam"},{code:"GT",name:"Guatemala"},{code:"GN",name:"Guinea"},{code:"GW",name:"Guinea-Bissau"},{code:"GY",name:"Guyana"},{code:"HT",name:"Haiti"},{code:"HN",name:"Honduras"},{code:"HK",name:"Hong Kong"},{code:"HU",name:"Hungary"},{code:"IS",name:"Iceland"},{code:"IN",name:"India"},{code:"ID",name:"Indonesia"},{code:"IR",name:"Iran"},{code:"IQ",name:"Iraq"},{code:"IE",name:"Ireland"},{code:"IM",name:"Isle of Man"},{code:"IL",name:"Israel"},{code:"IT",name:"Italy"},{code:"JM",name:"Jamaica"},{code:"JP",name:"Japan"},{code:"JE",name:"Jersey"},{code:"JO",name:"Jordan"},{code:"KZ",name:"Kazakhstan"},{code:"KE",name:"Kenya"},{code:"KI",name:"Kiribati"},{code:"KR",name:"Korea, South"},{code:"XK",name:"Kosovo"},{code:"KW",name:"Kuwait"},{code:"KG",name:"Kyrgyzstan"},{code:"LA",name:"Laos"},{code:"LV",name:"Latvia"},{code:"LB",name:"Lebanon"},{code:"LS",name:"Lesotho"},{code:"LR",name:"Liberia"},{code:"LY",name:"Libya"},{code:"LI",name:"Liechtenstein"},{code:"LT",name:"Lithuania"},{code:"LU",name:"Luxembourg"},{code:"MO",name:"Macao"},{code:"MG",name:"Madagascar"},{code:"MW",name:"Malawi"},{code:"MY",name:"Malaysia"},{code:"MV",name:"Maldives"},{code:"ML",name:"Mali"},{code:"MT",name:"Malta"},{code:"MH",name:"Marshall Islands"},{code:"MQ",name:"Martinique"},{code:"MR",name:"Mauritania"},{code:"MU",name:"Mauritius"},{code:"MX",name:"Mexico"},{code:"FM",name:"Micronesia"},{code:"MD",name:"Moldova"},{code:"MC",name:"Monaco"},{code:"MN",name:"Mongolia"},{code:"ME",name:"Montenegro"},{code:"MS",name:"Montserrat"},{code:"MA",name:"Morocco"},{code:"MZ",name:"Mozambique"},{code:"MM",name:"Myanmar"},{code:"NA",name:"Namibia"},{code:"NR",name:"Nauru"},{code:"NP",name:"Nepal"},{code:"NL",name:"Netherlands"},{code:"NC",name:"New Caledonia"},{code:"NZ",name:"New Zealand"},{code:"NI",name:"Nicaragua"},{code:"NE",name:"Niger"},{code:"NG",name:"Nigeria"},{code:"NU",name:"Niue"},{code:"MK",name:"North Macedonia"},{code:"NO",name:"Norway"},{code:"OM",name:"Oman"},{code:"PK",name:"Pakistan"},{code:"PW",name:"Palau"},{code:"PS",name:"Palestine"},{code:"PA",name:"Panama"},{code:"PG",name:"Papua New Guinea"},{code:"PY",name:"Paraguay"},{code:"PE",name:"Peru"},{code:"PH",name:"Philippines"},{code:"PL",name:"Poland"},{code:"PT",name:"Portugal"},{code:"PR",name:"Puerto Rico"},{code:"QA",name:"Qatar"},{code:"RE",name:"Réunion"},{code:"RO",name:"Romania"},{code:"RW",name:"Rwanda"},{code:"KN",name:"Saint Kitts and Nevis"},{code:"LC",name:"Saint Lucia"},{code:"VC",name:"Saint Vincent and the Grenadines"},{code:"WS",name:"Samoa"},{code:"SM",name:"San Marino"},{code:"SA",name:"Saudi Arabia"},{code:"SN",name:"Senegal"},{code:"RS",name:"Serbia"},{code:"SC",name:"Seychelles"},{code:"SL",name:"Sierra Leone"},{code:"SG",name:"Singapore"},{code:"SK",name:"Slovakia"},{code:"SI",name:"Slovenia"},{code:"SB",name:"Solomon Islands"},{code:"SO",name:"Somalia"},{code:"ZA",name:"South Africa"},{code:"SS",name:"South Sudan"},{code:"ES",name:"Spain"},{code:"LK",name:"Sri Lanka"},{code:"SD",name:"Sudan"},{code:"SR",name:"Suriname"},{code:"SE",name:"Sweden"},{code:"CH",name:"Switzerland"},{code:"SY",name:"Syria"},{code:"TW",name:"Taiwan"},{code:"TJ",name:"Tajikistan"},{code:"TZ",name:"Tanzania"},{code:"TH",name:"Thailand"},{code:"TL",name:"Timor-Leste"},{code:"TG",name:"Togo"},{code:"TO",name:"Tonga"},{code:"TT",name:"Trinidad and Tobago"},{code:"TN",name:"Tunisia"},{code:"TR",name:"Turkey"},{code:"TM",name:"Turkmenistan"},{code:"TC",name:"Turks and Caicos Islands"},{code:"TV",name:"Tuvalu"},{code:"UG",name:"Uganda"},{code:"UA",name:"Ukraine"},{code:"AE",name:"United Arab Emirates"},{code:"GB",name:"United Kingdom"},{code:"US",name:"United States"},{code:"UY",name:"Uruguay"},{code:"UZ",name:"Uzbekistan"},{code:"VU",name:"Vanuatu"},{code:"VE",name:"Venezuela"},{code:"VN",name:"Vietnam"},{code:"VG",name:"Virgin Islands, British"},{code:"VI",name:"Virgin Islands, U.S."},{code:"YE",name:"Yemen"},{code:"ZM",name:"Zambia"},{code:"ZW",name:"Zimbabwe"}];let l=null;async function d(e){if("proxy"===e.mode){const n=await async function(e){if(l)return l;try{const n=await fetch(`/apps/${e}/auth/token`,{method:"GET",headers:{Accept:"application/json"}}),t=await n.json().catch(()=>null);return n.ok&&t&&t.success&&t.data&&t.data.access_token?(l=t.data.access_token,l):null}catch(e){return console.error("Error fetching proxy access token:",e),null}}(e.proxyApp);return n?`Bearer ${n}`:null}return e.accessToken?/^Bearer /i.test(e.accessToken)?e.accessToken:`Bearer ${e.accessToken}`:(console.error('notify-me-wl: data-auth="token" requires an access token. Set data-access-token on the open button or window.TWC_ACCESS_TOKEN, or use data-auth="proxy" to obtain one via the Shopify App Proxy.'),null)}const u=new Map;function p(e){return"string"==typeof e?e.trim():""}async function w(e,n,t){const o=`${e}|${t||""}`,a=u.get(o);if(void 0!==a)return a;const r=await d(n);if(!r)return null;try{const n=await fetch("https://api.au-aws.thewishlist.io/services/eventcollector/api/v1/custom/configs/public",{method:"GET",headers:{Accept:"application/json",Authorization:r,"X-Twc-Tenant":e}});if(!n.ok)return console.error("Tenant config request failed:",n.status),null;const a=function(e,n=null){if(!e||"object"!=typeof e)return null;const t=e.websiteRecommendations;if(!t||"object"!=typeof t)return null;const o=p(t.engine).toUpperCase();if("TWC"===o)return{engine:"TWC"};if("ATHOS"===o){const e=p(t.siteIdentifier);return e?{engine:"ATHOS",siteIdentifier:e,profileTag:p(t.profileTag)||p(t.tags)||p(n)||"similar"}:null}return null}(await n.json().catch(()=>null),t);return u.set(o,a),a}catch(e){return console.error("Error fetching tenant config:",e),null}}function f(e){return"string"==typeof e?e.trim():""}function y(e){if("number"==typeof e&&Number.isFinite(e))return e;if("string"==typeof e){const n=parseFloat(e);if(Number.isFinite(n))return n}return null}function h(e){return Boolean(e.name&&e.imageUrl&&e.productUrl)}function g(e,n){const t=new Map;return e.forEach(e=>{const n=t.get(e.product.ref);n&&n.score>=e.score||t.set(e.product.ref,e)}),Array.from(t.values()).sort((e,n)=>n.score-e.score).slice(0,n).map(e=>e.product)}async function v(e){const{email:n,tenant:t,auth:o,count:a}=e,r=await d(o);if(!r)return null;const c=Math.min(3*a,20),i=`https://api.au-aws.thewishlist.io/services/recommendations/api/v1/recommendations/${encodeURIComponent(t)}/${encodeURIComponent(n)}?n=${c}`;try{const e=await fetch(i,{method:"GET",headers:{Accept:"application/json",Authorization:r,"X-Twc-Tenant":t}});if(!e.ok)return console.error("Recommendations request failed:",e.status),null;return function(e,n){const t=e&&e.recommendations;if(!Array.isArray(t))return[];const o=[];return t.forEach(e=>{var n;const t=e&&e.product;if(!t)return;const a=f(t.name),r=f(t.image_url),c=f(t.product_url);if(!h({name:a,imageUrl:r,productUrl:c}))return;const i=f(t.product_ref)||f(t.product_id)||c;o.push({score:"number"==typeof e.score?e.score:0,product:{id:f(t.product_id)||i,ref:i,name:a,imageUrl:r,productUrl:c,price:null!==(n=y(t.price))&&void 0!==n?n:0,originalPrice:y(t.original_price)}})}),g(o,n)}(await e.json().catch(()=>null),a)}catch(e){return console.error("Error fetching recommendations:",e),null}}async function b(e){const{engine:n,email:t,count:o,productId:a}=e,r=encodeURIComponent(n.siteIdentifier),c=[`tags=${encodeURIComponent(n.profileTag)}`,`limits=${o}`];a&&c.push(`products=${encodeURIComponent(a)}`),t&&c.push(`shopper=${encodeURIComponent(t)}`);const i=`https://${r}.a.searchspring.io/boost/${r}/recommend?`+c.join("&");try{const e=await fetch(i,{method:"GET",headers:{Accept:"application/json"}});if(!e.ok)return console.error("Athos recommendations request failed:",e.status),null;return function(e,n){const t=Array.isArray(e)?e[0]:e,o=t&&t.results;if(!Array.isArray(o))return[];const a=[];return o.forEach((e,n)=>{var t;const r=e&&e.mappings&&e.mappings.core;if(!r)return;const c=f(r.name),i=f(r.imageUrl)||f(r.thumbnailImageUrl),s=f(r.url);if(!h({name:c,imageUrl:i,productUrl:s}))return;const m=f(r.uid)||f(r.sku)||s,l=null!==(t=y(r.price))&&void 0!==t?t:0,d=y(r.msrp);a.push({score:o.length-n,product:{id:f(r.uid)||m,ref:m,name:c,imageUrl:i,productUrl:s,price:l,originalPrice:null!==d&&d>l?d:null}})}),g(a,n)}(await e.json().catch(()=>null),o)}catch(e){return console.error("Error fetching Athos recommendations:",e),null}}const x=new Map;const C="twc-nm-recs",k="twc-nm-recs-title";function S(e,n){if(n)try{return new Intl.NumberFormat(void 0,{style:"currency",currency:n}).format(e)}catch(e){}return new Intl.NumberFormat(void 0,{minimumFractionDigits:0,maximumFractionDigits:2}).format(e)}function E(){const e=document.createElement("section");e.id=C,e.className="twc-nm-recs",e.setAttribute("aria-labelledby",k);const n=document.createElement("h3");n.id=k,n.className="twc-nm-recs-title",n.textContent="Shop similar styles",e.appendChild(n);const t=document.createElement("div");return t.className="twc-nm-recs-row",function(e){let n=0,t=0,o=!1,a=!1,r=!1;e.addEventListener("pointerdown",c=>{"mouse"===c.pointerType&&0===c.button&&(o=!0,a=!1,r=!1,n=c.clientX,t=e.scrollLeft,e.classList.add("twc-nm-recs-row--dragging"))}),e.addEventListener("pointermove",r=>{if(!o)return;const c=r.clientX-n;!a&&Math.abs(c)<5||(a=!0,e.scrollLeft=t-c,r.preventDefault())});const c=()=>{o&&(o=!1,r=a,e.classList.remove("twc-nm-recs-row--dragging"))};e.addEventListener("pointerup",c),e.addEventListener("pointercancel",c),e.addEventListener("pointerleave",c),e.addEventListener("click",e=>{r&&(r=!1,e.preventDefault(),e.stopPropagation())},!0),e.addEventListener("dragstart",e=>{o&&a&&e.preventDefault()})}(t),e.appendChild(t),{section:e,row:t}}function A(){const e=document.createElement("div");e.className="twc-nm-rec twc-nm-rec--skeleton",e.setAttribute("aria-hidden","true");const n=document.createElement("div");n.className="twc-nm-rec-imgbox twc-nm-skel",e.appendChild(n);const t=document.createElement("span");t.className="twc-nm-skel twc-nm-skel-line",e.appendChild(t);const o=document.createElement("span");return o.className="twc-nm-skel twc-nm-skel-line twc-nm-skel-line--short",e.appendChild(o),e}function L(e,n,t){const o=e.querySelector(".twc-nm-recs-row");if(!o)return;const a=(r=t,window.Shopify&&window.Shopify.currency&&window.Shopify.currency.active||r||null);var r;const c=document.createDocumentFragment();n.forEach(e=>{c.appendChild(function(e,n){const t=document.createElement("a");t.className="twc-nm-rec",t.href=e.productUrl,t.target="_blank",t.rel="noopener noreferrer";const o=document.createElement("div");o.className="twc-nm-rec-imgbox";const a=document.createElement("img");a.className="twc-nm-rec-img",a.src=e.imageUrl,a.alt=e.name,a.loading="lazy",a.decoding="async",o.appendChild(a),t.appendChild(o);const r=document.createElement("span");r.className="twc-nm-rec-name",r.textContent=e.name,t.appendChild(r);const c=document.createElement("span");if(c.className="twc-nm-rec-price",null!==e.originalPrice&&e.originalPrice>e.price){const t=document.createElement("s");t.className="twc-nm-rec-was",t.textContent=S(e.originalPrice,n),c.appendChild(t),c.appendChild(document.createTextNode(" "))}return c.appendChild(document.createTextNode(S(e.price,n))),t.appendChild(c),t}(e,a))}),o.textContent="",o.appendChild(c),e.classList.remove("twc-nm-recs--loading"),e.removeAttribute("aria-busy")}function T(){const e=document.getElementById(C);e&&e.parentNode&&e.parentNode.removeChild(e)}const M={"notify-me":{intro:"Register to receive a notification as soon as this item is back in stock",submit:"Notify Me"},"coming-soon":{intro:"Register your interest to hear more about this item",submit:"Register Interest"}},N=["title","intro","sizeLabel","sizePlaceholder","emailPlaceholder","emailOptIn","smsOptIn","submit","success","disclaimer"];function I(e,n){const t={title:"{product}",intro:M[n].intro,sizeLabel:"Size",sizePlaceholder:"Select a size",emailLabel:"Email",emailPlaceholder:"you@example.com",emailOptIn:"Email me store updates",smsOptIn:"Text me store updates",submit:M[n].submit,success:"You're on the list. We'll be in touch.",disclaimer:null};if(void 0===e)return t;if(!e||"object"!=typeof e||Array.isArray(e))return a("copy","an object"),t;const o=e;N.forEach(e=>{const n=o[e];null!=n&&("string"==typeof n?t[e]=n:a(`copy.${e}`,"a string"))});const r=o.emailLabel;return!1===r||"string"==typeof r?t.emailLabel=r:null!=r&&a("copy.emailLabel","a string or false"),t}function B(e,n){return e.split("{product}").join(n)}function P(e){return e.replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;").replace(/'/g,"&#39;")}function $(e){if("string"!=typeof e)return null;const n=e.trim();return/^(https?:)?\/\//i.test(n)?n:null}function z(e){return"string"==typeof e?e.trim():""}function G(o){const{wrapper:a,config:r,localization:i}=o,{fields:s,type:l,display:u,auth:p,tenant:f,recommendationsEnabled:y,recommendationsCount:h,recommendationsProfile:g,currency:k,customerEmail:S,copy:M,productSummary:N,classes:I,jsonConfigured:G}=r,U='<button type="button" id="popup-close" class="twc-nm-close" aria-label="Close"><svg viewBox="0 0 16 16" width="16" height="16" aria-hidden="true" focusable="false"><path d="M3 3l10 10M13 3L3 13" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" fill="none"/></svg></button>',j='<h2 id="popup-title" class="twc-nm-title"></h2>',R=G?`<div class="twc-nm-header">${U}${j}</div>`:`${U}\n ${j}`,O=document.createElement("div");O.innerHTML=`\n <div id="twc-nm-overlay" role="dialog" aria-modal="true" aria-labelledby="popup-title">\n <div class="twc-nm-card" tabindex="-1">\n ${R}\n <p id="popup-text" class="twc-nm-text"></p>\n <div id="popup-notice" class="twc-nm-status" role="status" aria-live="polite" hidden></div>\n <form id="popup-form" class="twc-nm-form">\n <div class="twc-nm-field">\n <label class="twc-nm-label" for="twc-nm-size">${P(M.sizeLabel)}</label>\n <select class="twc-nm-select" id="twc-nm-size" name="select-size" required>\n <option value="" disabled selected>${P(M.sizePlaceholder)}</option>\n </select>\n </div>\n </form>\n </div>\n </div>\n `,a.appendChild(O);const H=document.getElementById("twc-nm-overlay"),F=document.getElementById("popup-close"),K=document.getElementById("popup-title"),D=document.getElementById("popup-text"),q=document.querySelector("select[name='select-size']"),_=document.getElementById("popup-form"),V=document.getElementById("popup-notice");if(!(H&&F&&K&&D&&q&&_&&V))return null;"panel"===u&&H.classList.add("twc-nm--panel");const J=N?function(){const e=document.createElement("div");return e.className="twc-nm-summary",e.innerHTML='<div class="twc-nm-summary-image"><img alt="" /></div><div class="twc-nm-summary-info"><p class="twc-nm-summary-name"></p><p class="twc-nm-summary-variant"></p><p class="twc-nm-summary-price"><span class="twc-nm-summary-now"></span> <s class="twc-nm-summary-compare"></s></p></div>',e}():null;J&&_.insertBefore(J,_.firstChild);const Z=m.map(e=>`<option value="${e.code}"${e.code===i.countryCode?" selected":""}>${e.name}</option>`).join(""),W=(e,n,t)=>`<div class="twc-nm-field"><label class="twc-nm-label" for="${e}">${n}</label>${t}</div>`,Y={email:!1===M.emailLabel?`<div class="twc-nm-field"><input class="twc-nm-input" id="twc-nm-email" name="email" type="email" placeholder="${P(M.emailPlaceholder)}" aria-label="${P(M.emailPlaceholder||"Email")}" required /></div>`:W("twc-nm-email",P(M.emailLabel),`<input class="twc-nm-input" id="twc-nm-email" name="email" type="email" placeholder="${P(M.emailPlaceholder)}" required />`),mobile:W("twc-nm-mobile","Mobile",'<input class="twc-nm-input" id="twc-nm-mobile" name="mobile" type="tel" placeholder="Mobile number" required />'),firstName:W("twc-nm-firstname","First name",'<input class="twc-nm-input" id="twc-nm-firstname" name="firstName" type="text" placeholder="First name" required />'),lastName:W("twc-nm-lastname","Last name",'<input class="twc-nm-input" id="twc-nm-lastname" name="lastName" type="text" placeholder="Last name" required />'),countryCode:W("twc-nm-country","Country",`<select class="twc-nm-select" id="twc-nm-country" name="countryCode"><option value="">Select country</option>${Z}</select>`),provinceCode:W("twc-nm-province","State / Province",`<input class="twc-nm-input" id="twc-nm-province" name="provinceCode" type="text" placeholder="State / province code" value="${i.provinceCode||""}" />`)};s.forEach(e=>{Y[e]&&_.insertAdjacentHTML("beforeend",Y[e])});const X=[s.includes("email")?`<label class="twc-nm-check"><input type="checkbox" name="mailList" /> ${P(M.emailOptIn)}</label>`:"",s.includes("mobile")?`<label class="twc-nm-check"><input type="checkbox" name="mailListSms" /> ${P(M.smsOptIn)}</label>`:""].join("");if(X&&_.insertAdjacentHTML("beforeend",`<div class="twc-nm-checks">${X}</div>`),_.insertAdjacentHTML("beforeend",'<div id="popup-status" class="twc-nm-status" role="status" aria-live="polite" hidden></div>'),_.insertAdjacentHTML("beforeend",`<button type="submit" class="twc-nm-submit">${P(M.submit)}</button>`),M.disclaimer&&_.insertAdjacentHTML("beforeend",`<p class="twc-nm-disclaimer">${P(M.disclaimer)}</p>`),function(e,n){Object.keys(n).forEach(t=>{const o=n[t];o&&e.querySelectorAll(c[t]).forEach(e=>{e.classList.add(...o,"twc-nm-themed")})})}(H,I),S){const e=_.querySelector("input[name='email']");e&&(e.value=S)}const Q=document.getElementById("popup-status"),ee=_.querySelector(".twc-nm-submit");if(!Q||!ee)return null;function ne(e,n){Q.textContent=n,Q.className=`twc-nm-status twc-nm-status--${e}`,Q.hidden=!1}function te(){Q.hidden=!0,Q.textContent=""}function oe(e,n){_.hidden=!0,V.textContent=n,V.className=`twc-nm-status twc-nm-status--${e}`,V.hidden=!1}const ae=e=>{const n=_.querySelector(e);return n?n.value:""},re=e=>{const n=_.querySelector(e);return!!n&&n.checked},ce=H.querySelector(".twc-nm-card");let ie=null,se=[],me=null;async function le(e){const n=await function(e,n,t){return w(e,n,t)}(f,p,g);if(!n)return!1;if(!H.classList.contains("is-open"))return!1;const t=function(e){const{section:n,row:t}=E();n.classList.add("twc-nm-recs--loading"),n.setAttribute("aria-busy","true");for(let n=0;n<e;n++)t.appendChild(A());return n}(h);ce.appendChild(t);const o=await async function(e,n){const{email:t,tenant:o,auth:a,count:r,productId:c}=n,i=`${e.engine}|${o}|${t}|${r}|${c||""}`,s=x.get(i);if(s)return s;let m=null;return m="ATHOS"===e.engine?await b({engine:e,email:t,count:r,productId:c}):await v({email:t,tenant:o,auth:a,count:r}),null===m?[]:(x.set(i,m),m)}(n,{email:e,tenant:f,auth:p,count:h,productId:ie?String(ie.id):null});return!!t.isConnected&&(o.length?(L(t,o,k),!0):(T(),!1))}function de(e){if(!y||!ce)return Promise.resolve(!1);if(me)return me;if(document.getElementById(C))return Promise.resolve(!0);const n=le(e);return me=n,n.then(e=>(me===n&&(me=null),e),e=>(me===n&&(me=null),console.error("Error rendering recommendations:",e),!1))}let ue=null,pe=0;function we(e){"Escape"===e.key&&fe()}function fe(){pe++,H.classList.remove("is-open"),document.body.style.overflow="",document.removeEventListener("keydown",we),te(),T(),me=null,ue&&"function"==typeof ue.focus&&ue.focus()}function ye(){if(!J||!ie)return;const e=se.find(e=>String(e.id)===q.value)||null;!function(e,n,t,o){const a=n=>e.querySelector(n),r=a(".twc-nm-summary-image"),c=a(".twc-nm-summary-image img"),i=t&&$(t.image)||$(n.image);i?(c.src=i,c.alt=n.title):c.removeAttribute("src"),r.hidden=!i,a(".twc-nm-summary-name").textContent=n.title;const s=a(".twc-nm-summary-variant");s.textContent=t?t.title:"",s.hidden=!t;let m="",l="";if(t)m=z(t.price),l=z(t.compareAtPrice);else{const e=o.map(e=>z(e.price));e.length&&e[0]&&e.every(n=>n===e[0])&&(m=e[0])}l===m&&(l=""),a(".twc-nm-summary-now").textContent=m;const d=a(".twc-nm-summary-compare");d.textContent=l,d.hidden=!l,a(".twc-nm-summary-price").hidden=!m}(J,ie,e,se)}return F.addEventListener("click",fe),q.addEventListener("change",ye),H.addEventListener("click",e=>{e.target===H&&fe()}),_.addEventListener("submit",async e=>{e.preventDefault();const n=se.find(e=>String(e.id)===q.value);if(!n)return void ne("error","Please select a size.");const t={variantRef:n.id,email:ae("input[name='email']"),subscribe:re("input[name='mailList']"),subscribeSms:re("input[name='mailListSms']")};if("coming-soon"===l?t.comingSoon=!0:t.notifyMe=!0,s.includes("firstName")&&(t.firstName=ae("input[name='firstName']")),s.includes("lastName")&&(t.lastName=ae("input[name='lastName']")),s.includes("mobile")&&(t.mobile=ae("input[name='mobile']")),s.includes("countryCode")){const e=ae("select[name='countryCode']");e&&(t.countryCode=e)}else i.countryCode&&(t.countryCode=i.countryCode);if(s.includes("provinceCode")){const e=ae("input[name='provinceCode']");e&&(t.provinceCode=e)}else i.provinceCode&&(t.provinceCode=i.provinceCode);i.marketId&&(t.marketId=i.marketId),i.marketHandle&&(t.marketHandle=i.marketHandle),te();const o=pe,a=ee.textContent||"";ee.disabled=!0,ee.textContent="Sending…";let r={ok:!1,reason:"error"};try{r=await async function(e,n,t){const o=await d(n);if(!o)return{ok:!1,reason:"auth"};try{return(await fetch("https://api.au-aws.thewishlist.io/services/wsservice/api/wishlist/items/customerInterest",{method:"POST",headers:{"Content-Type":"application/json",Authorization:o,"X-Twc-Tenant":t},body:JSON.stringify(e)})).ok?{ok:!0}:{ok:!1,reason:"error"}}catch(e){return console.error("Error submitting form:",e),{ok:!1,reason:"error"}}}(t,p,f)}finally{o===pe&&(ee.disabled=!1,ee.textContent=a)}if(o===pe)if(r.ok){ne("success",M.success);const e=S||ae("input[name='email']");!!e&&await de(e)||o!==pe||setTimeout(()=>{o===pe&&fe()},1500)}else"auth"===r.reason?ne("error","proxy"===p.mode?"Please log in to your account to continue.":"Something went wrong. Please try again."):ne("error","Something went wrong. Please try again.")}),{open:function(o){pe++,ee.disabled=!1,ee.textContent=M.submit,te(),_.hidden=!1,V.hidden=!0,V.textContent="",T(),me=null,H.classList.contains("is-open")||(ue=document.activeElement),H.classList.add("is-open"),document.body.style.overflow="hidden",document.addEventListener("keydown",we);const a=function(t){const o=t?t.closest(`[${e}]`):null;if(o){try{const t=JSON.parse(o.getAttribute(e)||"");if(n(t))return{ok:!0,product:t,source:"card"}}catch(e){}return console.error(`notify-me-wl: invalid ${e} JSON on`,o),{ok:!1}}const a=window.currentProduct;return n(a)?{ok:!0,product:a,source:"page"}:{ok:!1}}(o);if(!a.ok)return ie=null,se=[],K.textContent="",D.textContent=B(M.intro,""),void oe("error","Product information is unavailable. Please refresh the page.");if(ie=a.product,se=(ie.variants||[]).filter(e=>!!e&&("number"==typeof e.id||"string"==typeof e.id)&&"string"==typeof e.title&&!0!==e.available),K.textContent=B(M.title,ie.title),D.textContent=B(M.intro,ie.title),!se.length)return void oe("info","All sizes are currently in stock.");const r=o?o.getAttribute("data-variant-id"):null,c="page"===a.source?[r,...t()]:[r];var i,s;if(i=se,s=function(e,n){const t=e.map(e=>String(e.id));for(const e of n){const n=e?e.trim():"";if(n&&t.includes(n))return n}return 1===t.length?t[0]:null}(se,c),q.innerHTML=`<option value="" disabled selected>${P(M.sizePlaceholder)}</option>`,i.forEach(e=>{const n=document.createElement("option");n.value=String(e.id),n.textContent=e.title,q.appendChild(n)}),s&&(q.value=s),ye(),S&&de(S),"function"==typeof window.matchMedia&&window.matchMedia("(pointer: coarse)").matches)null==ce||ce.focus({preventScroll:!0});else{const e=_.querySelector("select, input");e&&e.focus()}}}}function U(e){var n;const t=(null!=e?e:"").trim();if(!t)return null;const o=null!==(n=t.split("?")[0].split("/").pop())&&void 0!==n?n:"";return/^\d+$/.test(o)?o:null}function j(e){const n=(null!=e?e:"").trim();return/^[a-z0-9_-]+$/i.test(n)?n:null}function R(e){const n=(null!=e?e:"").trim();return/^[a-z]{2}$/i.test(n)?n.toUpperCase():null}function O(e){var n,t,o;const a={countryCode:R(e("data-country-code")),provinceCode:null,marketId:U(e("data-market-id")),marketHandle:j(e("data-market-handle"))},r=window.Shopify;if(r){const e=null===(n=r.markets)||void 0===n?void 0:n.currentMarket;if(e&&(!a.countryCode&&e.countryCode&&(a.countryCode=e.countryCode),a.marketId||(a.marketId=U(String(null!==(t=e.id)&&void 0!==t?t:"")))),a.countryCode||"string"!=typeof r.country||(a.countryCode=r.country),!a.countryCode&&"string"==typeof r.locale){const e=r.locale.split("-");e.length>1&&(a.countryCode=e[e.length-1].toUpperCase())}}if(!a.countryCode||!a.provinceCode){const e=null===(o=window.customer)||void 0===o?void 0:o.default_address;e&&(!a.countryCode&&e.country_code&&(a.countryCode=e.country_code),!a.provinceCode&&e.province_code&&(a.provinceCode=e.province_code))}if(!a.countryCode){const e=(navigator.language||"").split("-");e.length>1&&(a.countryCode=e[e.length-1].toUpperCase())}return a}function H(e){const n=e("data-customer-email");if(n&&n.trim())return n.trim();const t=window.customer&&window.customer.email;if(t&&t.trim())return t.trim();const o=window.customerEmail;return"string"==typeof o&&o.trim()?o.trim():null}const F="[data-twc-notify-me]";function K(){const e=document.getElementById("notification-widget");if(!e)return;const n=document.getElementById("popup-open"),t=function(e){const n=e.querySelector('script[type="application/json"][data-twc-config]');if(!n)return null;try{const e=JSON.parse(n.textContent||"");if(e&&"object"==typeof e&&!Array.isArray(e))return e}catch(e){}return console.error("notify-me-wl: invalid data-twc-config JSON"),null}(e),c=function(e,n,t){const c=t=>(e?e.getAttribute(t):null)||n.getAttribute(t)||null,i=e=>null===t[e]?void 0:t[e],s=e=>{const n=i(e);return void 0===n?null:"boolean"==typeof n?n:(a(e,"true or false"),null)};return{string(e){const n=c(e);if(null!==n)return n;const t=o(e),r=i(t);return void 0===r?null:"string"==typeof r?r:"number"==typeof r&&Number.isFinite(r)?String(r):(a(t,"a string"),null)},boolean(e){const n=c(e);return null!==n?"false"!==n:s(o(e))},number(e){const n=c(e);if(null!==n){const e=parseInt(n,10);if(Number.isFinite(e))return e}const t=o(e),r=i(t);return void 0===r?null:"number"==typeof r&&Number.isFinite(r)?r:(a(t,"a number"),null)},stringArray(e){const n=c(e);if(null!==n){try{const e=JSON.parse(n);if(r(e))return e}catch(e){}console.warn(`notify-me-wl: ignoring invalid ${e} attribute`)}const t=o(e),s=i(t);return void 0===s?null:r(s)?s:(a(t,"an array of strings"),null)},json:i,jsonBoolean:s}}(n,e,t||{}),i=c.string;!function(){const e=document.createElement("style");e.type="text/css",e.innerText="\n .notification-btn {\n display: inline-block;\n width: 100%;\n padding: 12px 24px;\n background: #fff;\n border: 1px solid #111827;\n border-radius: 8px;\n color: #111827;\n font: inherit;\n text-align: center;\n text-transform: uppercase;\n cursor: pointer;\n }\n .notification-btn:hover {\n background: #111827;\n color: #fff;\n }\n #twc-nm-overlay {\n position: fixed;\n inset: 0;\n z-index: 99999;\n display: flex;\n align-items: center;\n justify-content: center;\n padding: 16px;\n background: var(--twc-nm-overlay-bg, rgba(17, 17, 17, 0.55));\n opacity: 0;\n visibility: hidden;\n transition: opacity var(--twc-nm-transition, 200ms ease);\n }\n #twc-nm-overlay.is-open {\n opacity: 1;\n visibility: visible;\n }\n #twc-nm-overlay * {\n box-sizing: border-box;\n }\n #twc-nm-overlay .twc-nm-card {\n position: relative;\n width: 100%;\n max-width: 400px;\n max-height: calc(100vh - 32px);\n overflow-y: auto;\n padding: var(--twc-nm-padding, 24px);\n background: var(--twc-nm-bg, #fff);\n color: var(--twc-nm-text, #111827);\n border-radius: 12px;\n box-shadow: 0 10px 40px rgba(0, 0, 0, 0.18);\n font: inherit;\n font-family: var(--twc-nm-font, inherit);\n transform: translateY(8px);\n transition: transform var(--twc-nm-transition, 200ms ease);\n }\n /* Focused on open on touch devices; it is a container, not a control. */\n #twc-nm-overlay .twc-nm-card:focus {\n outline: none;\n }\n #twc-nm-overlay.is-open .twc-nm-card {\n transform: translateY(0);\n }\n /* Panel mode: same blocking backdrop, docked to the right edge and\n full height instead of centred. Declared after the modal rules so\n the shared properties are overridden. */\n #twc-nm-overlay.twc-nm--panel {\n align-items: stretch;\n justify-content: flex-end;\n padding: 0;\n }\n #twc-nm-overlay.twc-nm--panel .twc-nm-card {\n width: var(--twc-nm-panel-width, 420px);\n max-width: 100%;\n max-height: none;\n border-radius: 0;\n box-shadow: var(--twc-nm-panel-shadow, -10px 0 40px rgba(0, 0, 0, 0.18));\n transform: translateX(100%);\n }\n /* Needs the extra class to beat both the .is-open rule above and the\n panel's own resting transform. */\n #twc-nm-overlay.twc-nm--panel.is-open .twc-nm-card {\n transform: translateX(0);\n }\n @media (max-width: 480px) {\n #twc-nm-overlay.twc-nm--panel .twc-nm-card {\n width: var(--twc-nm-panel-width-mobile, 100%);\n }\n }\n /* Groups the title and close button so a theme can lay them out as a\n row. With no border it changes nothing: the close button stays\n positioned against the card. */\n #twc-nm-overlay .twc-nm-header {\n border-bottom: var(--twc-nm-header-border, none);\n }\n /* Layout below is never themed — it keeps the close button usable\n whatever classes a theme attaches. Cosmetics follow :not(themed). */\n #twc-nm-overlay .twc-nm-close {\n position: absolute;\n top: 12px;\n right: 12px;\n width: 32px;\n height: 32px;\n min-width: 0;\n min-height: 0;\n display: flex;\n align-items: center;\n justify-content: center;\n margin: 0;\n padding: 0;\n border: none;\n background: transparent;\n box-shadow: none;\n text-indent: 0;\n line-height: 1;\n opacity: 1;\n visibility: visible;\n cursor: pointer;\n }\n #twc-nm-overlay .twc-nm-close:where(:not(.twc-nm-themed)) {\n border-radius: 6px;\n color: var(--twc-nm-muted, #6b7280);\n }\n /* The icon is an inline SVG rather than a text glyph so host-theme\n button resets (font-size: 0, icon fonts, text-indent) can't hide it. */\n #twc-nm-overlay .twc-nm-close svg {\n display: block;\n width: 16px;\n height: 16px;\n flex: none;\n pointer-events: none;\n }\n #twc-nm-overlay .twc-nm-close::before,\n #twc-nm-overlay .twc-nm-close::after {\n content: none;\n }\n #twc-nm-overlay .twc-nm-close:where(:not(.twc-nm-themed)):hover {\n background: #f3f4f6;\n color: var(--twc-nm-text, #111827);\n }\n #twc-nm-overlay .twc-nm-title {\n margin: 0 32px 4px 0;\n }\n #twc-nm-overlay .twc-nm-title:where(:not(.twc-nm-themed)) {\n font-size: 18px;\n font-weight: 600;\n }\n #twc-nm-overlay .twc-nm-text {\n margin: 0 0 16px;\n }\n #twc-nm-overlay .twc-nm-text:where(:not(.twc-nm-themed)) {\n font-size: 14px;\n line-height: 1.5;\n color: var(--twc-nm-muted, #6b7280);\n }\n #twc-nm-overlay .twc-nm-form {\n display: flex;\n flex-direction: column;\n gap: 14px;\n }\n #twc-nm-overlay .twc-nm-field {\n display: flex;\n flex-direction: column;\n gap: 6px;\n }\n #twc-nm-overlay .twc-nm-label:where(:not(.twc-nm-themed)) {\n font-size: 13px;\n font-weight: 500;\n color: #374151;\n }\n #twc-nm-overlay .twc-nm-input,\n #twc-nm-overlay .twc-nm-select {\n width: 100%;\n }\n /* The shorthand resets every background sub-property, so host CSS\n can't leak into an unconfigured widget; the select's arrow is added\n by the rule after it. */\n #twc-nm-overlay .twc-nm-input:where(:not(.twc-nm-themed)),\n #twc-nm-overlay .twc-nm-select:where(:not(.twc-nm-themed)) {\n padding: 10px 12px;\n font-size: 14px;\n font-family: inherit;\n color: var(--twc-nm-text, #111827);\n background: #fff;\n border: 1px solid var(--twc-nm-border, #e5e7eb);\n border-radius: var(--twc-nm-radius, 8px);\n }\n #twc-nm-overlay .twc-nm-input:where(:not(.twc-nm-themed)):focus,\n #twc-nm-overlay .twc-nm-select:where(:not(.twc-nm-themed)):focus {\n outline: none;\n border-color: var(--twc-nm-text, #111827);\n box-shadow: 0 0 0 3px rgba(17, 24, 39, 0.08);\n }\n #twc-nm-overlay .twc-nm-select {\n appearance: none;\n -webkit-appearance: none;\n cursor: pointer;\n background-image: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 12 12'%3E%3Cpath d='M2 4l4 4 4-4' stroke='%236b7280' stroke-width='1.5' fill='none' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E\");\n background-repeat: no-repeat;\n background-position: right 12px center;\n }\n /* After the shared padding above, which it must override. */\n #twc-nm-overlay .twc-nm-select:where(:not(.twc-nm-themed)) {\n padding-right: 36px;\n }\n #twc-nm-overlay .twc-nm-checks {\n display: flex;\n flex-direction: column;\n gap: 10px;\n }\n #twc-nm-overlay .twc-nm-check {\n display: flex;\n align-items: center;\n gap: 8px;\n font-size: 13px;\n color: #374151;\n cursor: pointer;\n }\n #twc-nm-overlay .twc-nm-check input {\n width: 16px;\n height: 16px;\n margin: 0;\n }\n #twc-nm-overlay .twc-nm-check input:where(:not(.twc-nm-themed)) {\n accent-color: var(--twc-nm-text, #111827);\n }\n #twc-nm-overlay .twc-nm-status {\n font-size: 13px;\n line-height: 1.5;\n padding: 10px 12px;\n border-radius: var(--twc-nm-radius, 8px);\n }\n #twc-nm-overlay .twc-nm-status--error {\n color: #b91c1c;\n background: #fef2f2;\n }\n #twc-nm-overlay .twc-nm-status--success {\n color: #15803d;\n background: #f0fdf4;\n }\n #twc-nm-overlay .twc-nm-status--info {\n color: #374151;\n background: #f3f4f6;\n }\n #twc-nm-overlay .twc-nm-submit {\n width: 100%;\n cursor: pointer;\n }\n #twc-nm-overlay .twc-nm-submit:where(:not(.twc-nm-themed)) {\n padding: 12px 16px;\n font-size: 14px;\n font-weight: 600;\n font-family: inherit;\n color: #fff;\n background: #111827;\n border: none;\n border-radius: var(--twc-nm-radius, 8px);\n transition: background 150ms ease;\n }\n #twc-nm-overlay .twc-nm-submit:where(:not(.twc-nm-themed)):hover {\n background: #374151;\n }\n #twc-nm-overlay .twc-nm-submit:disabled {\n opacity: 0.6;\n cursor: default;\n }\n #twc-nm-overlay .twc-nm-disclaimer {\n margin: 0;\n }\n #twc-nm-overlay .twc-nm-disclaimer:where(:not(.twc-nm-themed)) {\n font-size: 12px;\n line-height: 1.5;\n color: var(--twc-nm-muted, #6b7280);\n }\n #twc-nm-overlay .twc-nm-summary {\n display: flex;\n align-items: flex-start;\n }\n #twc-nm-overlay .twc-nm-summary:where(:not(.twc-nm-themed)) {\n gap: 16px;\n }\n /* Theme classes often set display; hidden parts must stay hidden. */\n #twc-nm-overlay .twc-nm-summary [hidden] {\n display: none !important;\n }\n #twc-nm-overlay .twc-nm-summary-image {\n flex: none;\n }\n #twc-nm-overlay .twc-nm-summary-image:where(:not(.twc-nm-themed)) {\n width: 96px;\n }\n #twc-nm-overlay .twc-nm-summary-image img {\n display: block;\n width: 100%;\n height: auto;\n }\n #twc-nm-overlay .twc-nm-summary-info {\n flex: 1 1 auto;\n min-width: 0;\n display: flex;\n flex-direction: column;\n gap: 4px;\n }\n #twc-nm-overlay .twc-nm-summary-info p {\n margin: 0;\n }\n #twc-nm-overlay .twc-nm-summary-name {\n overflow: hidden;\n white-space: nowrap;\n text-overflow: ellipsis;\n }\n #twc-nm-overlay .twc-nm-summary-name:where(:not(.twc-nm-themed)) {\n font-size: 14px;\n font-weight: 600;\n }\n #twc-nm-overlay .twc-nm-summary-variant:where(:not(.twc-nm-themed)) {\n font-size: 13px;\n color: var(--twc-nm-muted, #6b7280);\n }\n #twc-nm-overlay .twc-nm-summary-price:where(:not(.twc-nm-themed)) {\n font-size: 14px;\n }\n #twc-nm-overlay .twc-nm-summary-compare:where(:not(.twc-nm-themed)) {\n margin-left: 6px;\n color: #9ca3af;\n }\n #twc-nm-overlay .twc-nm-recs {\n margin-top: 20px;\n padding-top: 20px;\n border-top: 1px solid var(--twc-nm-border, #e5e7eb);\n }\n #twc-nm-overlay .twc-nm-recs-title {\n margin: 0 0 12px;\n font-size: 14px;\n font-weight: 600;\n color: #111827;\n }\n #twc-nm-overlay .twc-nm-recs-row {\n display: flex;\n gap: 12px;\n overflow-x: auto;\n scroll-snap-type: x mandatory;\n -webkit-overflow-scrolling: touch;\n padding-bottom: 8px;\n scrollbar-width: thin;\n }\n /* Only where a real pointer exists — the affordance is meaningless\n on touch, which pans the row natively. */\n @media (hover: hover) {\n #twc-nm-overlay .twc-nm-recs-row {\n cursor: grab;\n }\n }\n #twc-nm-overlay .twc-nm-recs-row--dragging,\n #twc-nm-overlay .twc-nm-recs-row--dragging .twc-nm-rec {\n cursor: grabbing;\n }\n #twc-nm-overlay .twc-nm-recs-row--dragging {\n /* Snapping fights a manually driven scrollLeft and feels sticky. */\n scroll-snap-type: none;\n user-select: none;\n -webkit-user-select: none;\n }\n #twc-nm-overlay .twc-nm-rec {\n flex: 0 0 132px;\n scroll-snap-align: start;\n display: flex;\n flex-direction: column;\n gap: 6px;\n color: inherit;\n text-decoration: none;\n }\n #twc-nm-overlay .twc-nm-rec:focus-visible {\n outline: 2px solid #111827;\n outline-offset: 2px;\n border-radius: 8px;\n }\n #twc-nm-overlay .twc-nm-rec-imgbox {\n aspect-ratio: 3 / 4;\n overflow: hidden;\n border-radius: 8px;\n background: #f3f4f6;\n }\n #twc-nm-overlay .twc-nm-rec-img {\n display: block;\n width: 100%;\n height: 100%;\n object-fit: cover;\n transition: opacity 150ms ease;\n }\n #twc-nm-overlay .twc-nm-rec:hover .twc-nm-rec-img {\n opacity: 0.85;\n }\n #twc-nm-overlay .twc-nm-rec-name {\n display: -webkit-box;\n -webkit-line-clamp: 2;\n -webkit-box-orient: vertical;\n overflow: hidden;\n font-size: 13px;\n line-height: 1.35;\n color: #111827;\n }\n #twc-nm-overlay .twc-nm-rec-price {\n font-size: 13px;\n font-weight: 600;\n color: #111827;\n }\n #twc-nm-overlay .twc-nm-rec-was {\n margin-right: 4px;\n font-weight: 400;\n color: #9ca3af;\n }\n #twc-nm-overlay .twc-nm-rec--skeleton {\n pointer-events: none;\n }\n #twc-nm-overlay .twc-nm-skel {\n background-color: #f3f4f6;\n background-image: linear-gradient(\n 90deg,\n #f3f4f6 0%,\n #e8eaee 50%,\n #f3f4f6 100%\n );\n background-size: 200% 100%;\n border-radius: 4px;\n animation: twc-nm-shimmer 1.3s ease-in-out infinite;\n }\n #twc-nm-overlay .twc-nm-rec-imgbox.twc-nm-skel {\n border-radius: 8px;\n }\n #twc-nm-overlay .twc-nm-skel-line {\n height: 12px;\n width: 100%;\n }\n #twc-nm-overlay .twc-nm-skel-line--short {\n width: 45%;\n }\n @keyframes twc-nm-shimmer {\n from {\n background-position: 200% 0;\n }\n to {\n background-position: -200% 0;\n }\n }\n @media (prefers-reduced-motion: reduce) {\n #twc-nm-overlay,\n #twc-nm-overlay .twc-nm-card,\n #twc-nm-overlay .twc-nm-rec-img {\n transition: none;\n }\n #twc-nm-overlay .twc-nm-card,\n #twc-nm-overlay.twc-nm--panel .twc-nm-card {\n transform: none;\n }\n #twc-nm-overlay .twc-nm-skel {\n animation: none;\n }\n }\n ",document.head.appendChild(e)}();const m=c.stringArray("data-fields")||["email"],l=i("data-type")||"notify-me",d="panel"===i("data-display")?"panel":"modal",u=i("data-auth")||"proxy",p=i("data-tenant")||"victoria-woods",w=i("data-proxy-app")||"twc-sdk",f=i("data-access-token")||window.TWC_ACCESS_TOKEN||null,y=!1!==c.boolean("data-recommendations"),h=c.number("data-recommendations-count"),g=G({wrapper:e,config:{fields:m,type:l,display:d,auth:{mode:u,proxyApp:w,accessToken:f},tenant:p,recommendationsEnabled:y,recommendationsCount:null!==h&&h>0?h:8,recommendationsProfile:(i("data-recommendations-profile")||"").trim()||null,currency:i("data-currency")||null,customerEmail:H(i),copy:I(c.json("copy"),l),productSummary:!0===c.jsonBoolean("productSummary"),classes:s(c.json("classes")),jsonConfigured:null!==t},localization:O(i)});g&&(document.addEventListener("click",e=>{const n=e.target,t=n instanceof Element?n.closest(F):null;t&&(e.preventDefault(),g.open(t))},!0),n&&!n.matches(F)&&n.addEventListener("click",()=>g.open(n)))}"loading"===document.readyState?document.addEventListener("DOMContentLoaded",K):K()}();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "notify-me-wl",
3
- "version": "3.6.0",
3
+ "version": "3.6.2",
4
4
  "description": "Coming Soon / Notify Me wishlist widget for retailer product pages.",
5
5
  "main": "build/notify-me-wl.min.js",
6
6
  "files": [