@thefittingroom/shop-ui 5.0.22 → 5.0.24

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/index.js +1040 -484
  2. package/package.json +14 -3
package/dist/index.js CHANGED
@@ -14054,7 +14054,7 @@ function applyFrameBaseUrl(url, baseUrl2) {
14054
14054
  return `${cleanBase}${url.startsWith("/") ? "" : "/"}${url}`;
14055
14055
  }
14056
14056
  const STORAGE_KEY = "tfr:fitting-room:v1";
14057
- const logger$h = getLogger("fitting-room");
14057
+ const logger$f = getLogger("fitting-room-storage");
14058
14058
  function readAll() {
14059
14059
  try {
14060
14060
  const raw = window.localStorage.getItem(STORAGE_KEY);
@@ -14067,7 +14067,7 @@ function readAll() {
14067
14067
  }
14068
14068
  return parsed;
14069
14069
  } catch (error) {
14070
- logger$h.logWarn("Failed to read fitting room from localStorage", {
14070
+ logger$f.logWarn("Failed to read fitting room from localStorage", {
14071
14071
  error
14072
14072
  });
14073
14073
  return {};
@@ -14077,7 +14077,7 @@ function writeAll(all) {
14077
14077
  try {
14078
14078
  window.localStorage.setItem(STORAGE_KEY, JSON.stringify(all));
14079
14079
  } catch (error) {
14080
- logger$h.logWarn("Failed to write fitting room to localStorage", {
14080
+ logger$f.logWarn("Failed to write fitting room to localStorage", {
14081
14081
  error
14082
14082
  });
14083
14083
  }
@@ -14101,17 +14101,9 @@ function _init$7() {
14101
14101
  fittingRoom: items
14102
14102
  });
14103
14103
  }
14104
- async function toggleFittingRoomItem(productId, handle, isPdp) {
14104
+ async function addFittingRoomItem(productId, handle, isPdp) {
14105
14105
  const state = useMainStore.getState();
14106
- const isInFittingRoom = state.fittingRoom.some((item) => item.externalId === productId);
14107
- if (isInFittingRoom) {
14108
- logger$h.logDebug("{{ts}} - Removing from fitting room", {
14109
- productId
14110
- });
14111
- state.removeFromFittingRoom(productId);
14112
- return;
14113
- }
14114
- logger$h.logDebug("{{ts}} - Adding to fitting room", {
14106
+ logger$f.logDebug("{{ts}} - Adding to fitting room", {
14115
14107
  productId,
14116
14108
  handle,
14117
14109
  isPdp
@@ -14133,7 +14125,7 @@ async function toggleFittingRoomItem(productId, handle, isPdp) {
14133
14125
  size = selection.size || null;
14134
14126
  color = selection.color;
14135
14127
  } catch (error) {
14136
- logger$h.logWarn("Failed to read selected options from currentProduct", {
14128
+ logger$f.logWarn("Failed to read selected options from currentProduct", {
14137
14129
  error
14138
14130
  });
14139
14131
  }
@@ -14161,6 +14153,25 @@ async function toggleFittingRoomItem(productId, handle, isPdp) {
14161
14153
  addedAt: Date.now()
14162
14154
  });
14163
14155
  }
14156
+ async function toggleFittingRoomItem(productId, handle, isPdp) {
14157
+ const state = useMainStore.getState();
14158
+ const isInFittingRoom = state.fittingRoom.some((item) => item.externalId === productId);
14159
+ if (isInFittingRoom) {
14160
+ logger$f.logDebug("{{ts}} - Removing from fitting room", {
14161
+ productId
14162
+ });
14163
+ state.removeFromFittingRoom(productId);
14164
+ return;
14165
+ }
14166
+ await addFittingRoomItem(productId, handle, isPdp);
14167
+ }
14168
+ async function ensureFittingRoomItem(productId, handle, isPdp) {
14169
+ const state = useMainStore.getState();
14170
+ if (state.fittingRoom.some((item) => item.externalId === productId)) {
14171
+ return;
14172
+ }
14173
+ await addFittingRoomItem(productId, handle, isPdp);
14174
+ }
14164
14175
  const BROWSER_ALIASES_MAP = {
14165
14176
  AmazonBot: "amazonbot",
14166
14177
  "Amazon Silk": "amazon_silk",
@@ -19307,7 +19318,7 @@ const en$1 = {
19307
19318
  "get-app": { "create_avatar": "Create your avatar" },
19308
19319
  "sign-in": { "email": "Email", "password": "Password", "forgot_password": "Forgot password?", "sign_in": "Sign in", "no_account": "Don’t have an account?", "download_app": "Download the app.", "invalid_email": "Please enter a valid email address.", "missing_password": "Please enter your password.", "login_failed": "Incorrect email or password." },
19309
19320
  "forgot-password": { "title": "Forgot password", "description": "We’ll send you an email with a link to reset your password.", "send_link": "Send link", "link_sent": "Link sent! Please check your email.", "need_help": "Need help?", "contact_us": "Contact us." },
19310
- "vto-single": { "avatar_loading": "Finding your perfect fit...", "slide_to_rotate": "Slide to rotate", "sign_out": "Sign out", "color_label": "Color:", "add_to_cart": "Add to cart", "view_product_details": "View product details", "hide_product_details": "Hide product details", "no_recommendation": "There are currently no well fitting sizes available for this item." },
19321
+ "quick-view": { "title": "Quick View Try On", "avatar_loading": "Finding your perfect fit...", "slide_to_rotate": "Slide to rotate", "sign_out": "Sign out", "color_label": "Color:", "add_to_cart": "Add to cart", "view_product_details": "View product details", "hide_product_details": "Hide product details", "no_recommendation": "There are currently no well fitting sizes available for this item.", "vto_error": "Couldn't load the try-on. Please try again.", "zoom_in": "Zoom In" },
19311
19322
  "size-rec": { "recommended_size": "Recommended Size: {{size}}", "item_fit": "This item is {{fit}}", "select_size": "Select a size to see how it fits", "fitClassification": { "form_fitting": "Form Fitting", "slim_fit": "Slim Fit", "regular_fit": "Regular Fit", "relaxed_fit": "Relaxed Fit", "oversized_fit": "Oversized Fit" }, "measurementLocation": { "neck_base": "Neck", "across_shoulder": "Shoulders", "cb_neck_to_wrist": "Neck to Wrist", "sleeve_length_from_shoulder_point": "Sleeve", "bust": "Bust", "waist": "Waist", "low_waist": "Low Waist", "high_hip": "High Hip", "low_hip": "Low Hip", "thigh": "Thigh", "inseam": "Inseam", "hsp_to_low_hip": "HSP to Low Hip", "hsp_to_crotch": "HSP to Crotch", "low_hip_bottoms": "Low Hip (Bottoms)", "high_hip_bottoms": "High Hip (Bottoms)" }, "fit": { "too_tight": "Too Tight", "tight": "Tight", "slightly_tight": "Slightly Tight", "perfect_fit": "Perfect Fit", "slightly_loose": "Slightly Loose", "loose": "Loose", "oversized": "Oversized", "too_short": "Too Short", "short": "Short", "slightly_short": "Slightly Short", "slightly_long": "Slightly Long", "long": "Long", "too_long": "Too Long" } },
19312
19323
  "fit-chart": { "fit_scale": "Fit Scale", "fit": { "poor_fit": "Poor Fit", "acceptable_fit": "Acceptable Fit", "too_tight": "Too Tight", "tight": "Tight or More Fitted", "slightly_tight": "Slightly Tight or Fitted", "perfect_fit": "Perfect Fit", "slightly_loose": "Slightly Loose or Less Fitted", "loose": "Loose or Not Fitted", "oversized": "Oversized" }, "measurement_points": "Measurement Points", "point": { "bust": "Chest/Bust", "waist": "Natural Waist", "pant_waist": "Pant Waist", "high_hip": "High Hip", "low_hip": "Low Hip", "thigh": "Thigh" } }
19313
19324
  };
@@ -19331,11 +19342,11 @@ const fr = {
19331
19342
  "get-app": { "create_avatar": "Créez votre avatar" },
19332
19343
  "sign-in": { "email": "Email", "password": "Mot de passe", "forgot_password": "Mot de passe oublié ?", "sign_in": "Se connecter", "no_account": "Vous n'avez pas de compte ?", "download_app": "Téléchargez l'application.", "invalid_email": "Veuillez entrer une adresse e-mail valide.", "missing_password": "Veuillez entrer votre mot de passe.", "login_failed": "Email ou mot de passe incorrect." },
19333
19344
  "forgot-password": { "title": "Mot de passe oublié", "description": "Nous vous enverrons un e-mail avec un lien pour réinitialiser votre mot de passe.", "send_link": "Envoyer le lien", "link_sent": "Lien envoyé ! Veuillez vérifier votre e-mail.", "need_help": "Besoin d'aide ?", "contact_us": "Contactez-nous." },
19334
- "vto-single": { "avatar_loading": "Trouver votre ajustement parfait...", "slide_to_rotate": "Faites glisser pour faire pivoter", "sign_out": "Se déconnecter", "color_label": "Couleur :", "add_to_cart": "Ajouter au panier", "view_product_details": "Voir les détails du produit", "hide_product_details": "Masquer les détails du produit", "no_recommendation": "Il n'y a actuellement pas de tailles bien ajustées disponibles pour cet article." },
19345
+ "quick-view": { "title": "Essayage rapide", "avatar_loading": "Trouver votre ajustement parfait...", "slide_to_rotate": "Faites glisser pour faire pivoter", "sign_out": "Se déconnecter", "color_label": "Couleur :", "add_to_cart": "Ajouter au panier", "view_product_details": "Voir les détails du produit", "hide_product_details": "Masquer les détails du produit", "no_recommendation": "Il n'y a actuellement pas de tailles bien ajustées disponibles pour cet article.", "vto_error": "Impossible de charger l'essayage. Veuillez réessayer.", "zoom_in": "Agrandir" },
19335
19346
  "size-rec": { "recommended_size": "Taille recommandée : {{size}}", "item_fit": "Cet article est {{fit}}", "select_size": "Sélectionnez une taille pour voir comment elle s'adapte", "fitClassification": { "form_fitting": "Ajusté", "slim_fit": "Coupe slim", "regular_fit": "Coupe régulière", "relaxed_fit": "Coupe décontractée", "oversized_fit": "Coupe oversize" }, "measurementLocation": { "neck_base": "Cou", "across_shoulder": "Épaules", "cb_neck_to_wrist": "Cou jusqu'au poignet", "sleeve_length_from_shoulder_point": "Manche", "bust": "Buste", "waist": "Taille", "low_waist": "Bas de la taille", "high_hip": "Haut des hanches", "low_hip": "Bas des hanches", "thigh": "Cuisse", "inseam": "Entrejambe", "hsp_to_low_hip": "HSP au bas des hanches", "hsp_to_crotch": "HSP à l'entrejambe", "low_hip_bottoms": "Bas des hanches (bas)", "high_hip_bottoms": "Haut des hanches (bas)" }, "fit": { "too_tight": "Trop serré", "tight": "Serré", "slightly_tight": "Légèrement serré", "perfect_fit": "Parfait", "slightly_loose": "Légèrement ample", "loose": "Ample", "oversized": "Oversize", "too_short": "Trop court", "short": "Court", "slightly_short": "Légèrement court", "slightly_long": "Légèrement long", "long": "Long", "too_long": "Trop long" } },
19336
19347
  "fit-chart": { "fit_scale": "Échelle d'ajustement", "fit": { "poor_fit": "Mauvais ajustement", "acceptable_fit": "Ajustement acceptable", "too_tight": "Trop serré", "tight": "Serré ou plus ajusté", "slightly_tight": "Légèrement serré ou ajusté", "perfect_fit": "Parfait", "slightly_loose": "Légèrement ample ou moins ajusté", "loose": "Ample ou non ajusté", "oversized": "Oversize" }, "measurement_points": "Points de mesure", "point": { "bust": "Poitrine/Buste", "waist": "Taille naturelle", "pant_waist": "Taille du pantalon", "high_hip": "Haut des hanches", "low_hip": "Bas des hanches", "thigh": "Cuisse" } }
19337
19348
  };
19338
- instance.use(initReactI18next).init({
19349
+ void instance.use(initReactI18next).init({
19339
19350
  lng: "en",
19340
19351
  fallbackLng: "en",
19341
19352
  resources: {
@@ -19385,14 +19396,14 @@ function keyframes() {
19385
19396
  };
19386
19397
  }
19387
19398
  const themeData = {
19388
- brand_font_family: "TN web use only, Times New Roman, serif",
19399
+ brand_font_family: "'Inter', sans-serif",
19389
19400
  brand_link_text_decoration: "underline",
19390
19401
  brand_button_background_color: "#FFA273",
19391
19402
  brand_button_text_color: "#21201F",
19392
19403
  color_danger: "#900B09",
19393
19404
  color_fg_text: "#21201F",
19394
19405
  color_tfr_800: "#265A64",
19395
- font_family: "sans-serif"
19406
+ font_family: "'Inter', sans-serif"
19396
19407
  };
19397
19408
  function _init$6(initThemeData) {
19398
19409
  if (initThemeData) {
@@ -19487,7 +19498,7 @@ const Text = reactExports.forwardRef(({
19487
19498
  const variantCss = useVariantCss(variant, (theme) => ({
19488
19499
  base: {
19489
19500
  color: theme.color_fg_text,
19490
- fontFamily: "sans-serif",
19501
+ fontFamily: theme.font_family,
19491
19502
  fontSize: "14px"
19492
19503
  },
19493
19504
  brand: {
@@ -19497,6 +19508,7 @@ const Text = reactExports.forwardRef(({
19497
19508
  },
19498
19509
  error: {
19499
19510
  color: theme.color_danger,
19511
+ fontFamily: theme.font_family,
19500
19512
  fontSize: "14px"
19501
19513
  }
19502
19514
  }));
@@ -21244,6 +21256,45 @@ function SidecarModalFrame({
21244
21256
  };
21245
21257
  return /* @__PURE__ */ jsx$1(ModalFrame, { isOpen: true, onRequestClose, contentStyle: applyContentStyle, children });
21246
21258
  }
21259
+ function Snackbar({
21260
+ messageKey,
21261
+ onDismiss
21262
+ }) {
21263
+ const css2 = useCss((theme) => ({
21264
+ container: {
21265
+ position: "absolute",
21266
+ left: "50%",
21267
+ bottom: "24px",
21268
+ transform: "translateX(-50%)",
21269
+ padding: "12px 20px",
21270
+ borderRadius: "24px",
21271
+ backgroundColor: theme.color_fg_text,
21272
+ fontSize: "13px",
21273
+ display: "flex",
21274
+ alignItems: "center",
21275
+ gap: "12px",
21276
+ zIndex: 10,
21277
+ maxWidth: "calc(100% - 32px)"
21278
+ },
21279
+ // The Text 'base' variant carries a dark color; override it so the copy
21280
+ // is readable on the dark snackbar background.
21281
+ text: {
21282
+ color: "#FFFFFF"
21283
+ },
21284
+ dismiss: {
21285
+ background: "none",
21286
+ border: "none",
21287
+ color: "#FFFFFF",
21288
+ fontSize: "16px",
21289
+ lineHeight: 1,
21290
+ cursor: "pointer"
21291
+ }
21292
+ }));
21293
+ return /* @__PURE__ */ jsxs("div", { css: css2.container, children: [
21294
+ /* @__PURE__ */ jsx$1(TextT, { variant: "base", t: messageKey, css: css2.text }),
21295
+ /* @__PURE__ */ jsx$1("button", { css: css2.dismiss, onClick: onDismiss, "aria-label": "Dismiss", children: "×" })
21296
+ ] });
21297
+ }
21247
21298
  var dayjs_min$1 = { exports: {} };
21248
21299
  var dayjs_min = dayjs_min$1.exports;
21249
21300
  var hasRequiredDayjs_min;
@@ -23007,7 +23058,7 @@ function isVersionServiceProvider(provider) {
23007
23058
  }
23008
23059
  const name$q = "@firebase/app";
23009
23060
  const version$1$1 = "0.14.5";
23010
- const logger$g = new Logger3("@firebase/app");
23061
+ const logger$e = new Logger3("@firebase/app");
23011
23062
  const name$p = "@firebase/app-compat";
23012
23063
  const name$o = "@firebase/analytics-compat";
23013
23064
  const name$n = "@firebase/analytics";
@@ -23074,13 +23125,13 @@ function _addComponent(app, component) {
23074
23125
  try {
23075
23126
  app.container.addComponent(component);
23076
23127
  } catch (e) {
23077
- logger$g.debug(`Component ${component.name} failed to register with FirebaseApp ${app.name}`, e);
23128
+ logger$e.debug(`Component ${component.name} failed to register with FirebaseApp ${app.name}`, e);
23078
23129
  }
23079
23130
  }
23080
23131
  function _registerComponent(component) {
23081
23132
  const componentName = component.name;
23082
23133
  if (_components.has(componentName)) {
23083
- logger$g.debug(`There were multiple attempts to register component ${componentName}.`);
23134
+ logger$e.debug(`There were multiple attempts to register component ${componentName}.`);
23084
23135
  return false;
23085
23136
  }
23086
23137
  _components.set(componentName, component);
@@ -23289,7 +23340,7 @@ function registerVersion(libraryKeyOrName, version2, variant) {
23289
23340
  if (versionMismatch) {
23290
23341
  warning.push(`version name "${version2}" contains illegal characters (whitespace or "/")`);
23291
23342
  }
23292
- logger$g.warn(warning.join(" "));
23343
+ logger$e.warn(warning.join(" "));
23293
23344
  return;
23294
23345
  }
23295
23346
  _registerComponent(new Component(
@@ -23333,12 +23384,12 @@ async function readHeartbeatsFromIndexedDB(app) {
23333
23384
  return result;
23334
23385
  } catch (e) {
23335
23386
  if (e instanceof FirebaseError) {
23336
- logger$g.warn(e.message);
23387
+ logger$e.warn(e.message);
23337
23388
  } else {
23338
23389
  const idbGetError = ERROR_FACTORY.create("idb-get", {
23339
23390
  originalErrorMessage: e?.message
23340
23391
  });
23341
- logger$g.warn(idbGetError.message);
23392
+ logger$e.warn(idbGetError.message);
23342
23393
  }
23343
23394
  }
23344
23395
  }
@@ -23351,12 +23402,12 @@ async function writeHeartbeatsToIndexedDB(app, heartbeatObject) {
23351
23402
  await tx.done;
23352
23403
  } catch (e) {
23353
23404
  if (e instanceof FirebaseError) {
23354
- logger$g.warn(e.message);
23405
+ logger$e.warn(e.message);
23355
23406
  } else {
23356
23407
  const idbGetError = ERROR_FACTORY.create("idb-set", {
23357
23408
  originalErrorMessage: e?.message
23358
23409
  });
23359
- logger$g.warn(idbGetError.message);
23410
+ logger$e.warn(idbGetError.message);
23360
23411
  }
23361
23412
  }
23362
23413
  }
@@ -23405,7 +23456,7 @@ class HeartbeatServiceImpl {
23405
23456
  }
23406
23457
  return this._storage.overwrite(this._heartbeatsCache);
23407
23458
  } catch (e) {
23408
- logger$g.warn(e);
23459
+ logger$e.warn(e);
23409
23460
  }
23410
23461
  }
23411
23462
  /**
@@ -23436,7 +23487,7 @@ class HeartbeatServiceImpl {
23436
23487
  }
23437
23488
  return headerString;
23438
23489
  } catch (e) {
23439
- logger$g.warn(e);
23490
+ logger$e.warn(e);
23440
23491
  return "";
23441
23492
  }
23442
23493
  }
@@ -41004,7 +41055,7 @@ const firebaseDateToDayjs = (date) => {
41004
41055
  return dayjs(date.seconds * 1e3);
41005
41056
  };
41006
41057
  const LOGIN_TRACKING_PERIOD_SECONDS = 1800;
41007
- const logger$f = getLogger("firebase");
41058
+ const logger$d = getLogger("firebase");
41008
41059
  let firebaseApp = null;
41009
41060
  class FirestoreManager {
41010
41061
  constructor(firestore) {
@@ -41132,14 +41183,14 @@ class AuthManager {
41132
41183
  this.listenToUserProfileUnsub = null;
41133
41184
  }
41134
41185
  if (authUser) {
41135
- logger$f.logDebug("User logged in:", {
41186
+ logger$d.logDebug("User logged in:", {
41136
41187
  uid: authUser.uid
41137
41188
  });
41138
41189
  this.listenToUserProfileUnsub = getFirestoreManager().listenToDoc("users", authUser.uid, (doc2) => {
41139
41190
  this.userProfile = doc2;
41140
41191
  this.userProfileChangeListeners.forEach((callback) => callback(this.userProfile));
41141
41192
  });
41142
- (async () => {
41193
+ void (async () => {
41143
41194
  try {
41144
41195
  const firestore = getFirestoreManager();
41145
41196
  const userLoggingDocId = authUser.uid;
@@ -41166,7 +41217,7 @@ class AuthManager {
41166
41217
  await firestore.mergeDocData("user_logging", userLoggingDocId, userLoggingData);
41167
41218
  }
41168
41219
  } catch (error) {
41169
- logger$f.logError("Error logging user login activity:", {
41220
+ logger$d.logError("Error logging user login activity:", {
41170
41221
  error
41171
41222
  });
41172
41223
  }
@@ -41271,7 +41322,9 @@ async function execApiRequest(params) {
41271
41322
  try {
41272
41323
  response = await fetch(url, options);
41273
41324
  } finally {
41274
- if (timeoutHandle) clearTimeout(timeoutHandle);
41325
+ if (timeoutHandle) {
41326
+ clearTimeout(timeoutHandle);
41327
+ }
41275
41328
  }
41276
41329
  if (!response.ok) {
41277
41330
  let detail = "";
@@ -41364,7 +41417,7 @@ async function getStyleByExternalId(brandId, externalId) {
41364
41417
  recordCache[cacheKey] = record;
41365
41418
  return record;
41366
41419
  }
41367
- const logger$e = getLogger("product");
41420
+ const logger$c = getLogger("product");
41368
41421
  function _init$2() {
41369
41422
  useMainStore.subscribe((state, prevState) => {
41370
41423
  if (state.userHasAvatar && !prevState.userHasAvatar) {
@@ -41397,11 +41450,11 @@ function loadProductDataToStore(externalId) {
41397
41450
  try {
41398
41451
  const productData2 = await loadProductData(externalId);
41399
41452
  useMainStore.getState().setProductData(productData2.externalId, productData2);
41400
- logger$e.logDebug(`Loaded product data for externalId: ${externalId}`, {
41453
+ logger$c.logDebug(`Loaded product data for externalId: ${externalId}`, {
41401
41454
  productData: productData2
41402
41455
  });
41403
41456
  } catch (error) {
41404
- logger$e.logError(`Error loading product data for externalId: ${externalId}`, {
41457
+ logger$c.logError(`Error loading product data for externalId: ${externalId}`, {
41405
41458
  error
41406
41459
  });
41407
41460
  }
@@ -41414,9 +41467,9 @@ function loadProductDataToStore(externalId) {
41414
41467
  if (productData[externalId] || !userIsLoggedIn || userHasAvatar === false) {
41415
41468
  return;
41416
41469
  }
41417
- loadAndStore();
41470
+ void loadAndStore();
41418
41471
  }
41419
- const logger$d = getLogger("style-categories");
41472
+ const logger$b = getLogger("style-categories");
41420
41473
  let cached = null;
41421
41474
  let inflight = null;
41422
41475
  function buildIndex(categories, groups) {
@@ -41460,7 +41513,7 @@ async function loadStyleCategoryIndex() {
41460
41513
  }
41461
41514
  inflight = (async () => {
41462
41515
  try {
41463
- logger$d.logDebug("{{ts}} - Loading style-category index");
41516
+ logger$b.logDebug("{{ts}} - Loading style-category index");
41464
41517
  const [categories, groups] = await Promise.all([getStyleCategories(), getStyleCategoryGroups()]);
41465
41518
  cached = buildIndex(categories, groups);
41466
41519
  return cached;
@@ -41473,12 +41526,12 @@ async function loadStyleCategoryIndex() {
41473
41526
  function peekStyleCategoryIndex() {
41474
41527
  return cached;
41475
41528
  }
41476
- const logger$c = getLogger("fitting-room-data");
41529
+ const logger$a = getLogger("fitting-room-data");
41477
41530
  async function loadFittingRoomData() {
41478
41531
  const state = useMainStore.getState();
41479
41532
  const items = state.fittingRoom;
41480
41533
  loadStyleCategoryIndex().catch((error) => {
41481
- logger$c.logError("Failed to load style-category index", {
41534
+ logger$a.logError("Failed to load style-category index", {
41482
41535
  error
41483
41536
  });
41484
41537
  });
@@ -41490,7 +41543,9 @@ async function loadFittingRoomData() {
41490
41543
  } = getStaticData();
41491
41544
  if (!productLookup) {
41492
41545
  for (const item of items) {
41493
- if (state.merchantProductData[item.externalId]) continue;
41546
+ if (state.merchantProductData[item.externalId]) {
41547
+ continue;
41548
+ }
41494
41549
  state.setMerchantProductData(item.externalId, {
41495
41550
  error: new Error("No productLookup callback configured")
41496
41551
  });
@@ -41526,7 +41581,7 @@ async function loadFittingRoomData() {
41526
41581
  }
41527
41582
  }
41528
41583
  } catch (error) {
41529
- logger$c.logError("productLookup batch failed", {
41584
+ logger$a.logError("productLookup batch failed", {
41530
41585
  error,
41531
41586
  handles
41532
41587
  });
@@ -41561,7 +41616,7 @@ function resolveItem(item, merchantSlot, loadedSlot, index) {
41561
41616
  const found = loadedProduct.sizeFitRecommendation.available_sizes.some((sz) => sz.colorway_size_assets.some((csa) => csa.id === item.colorwaySizeAssetId));
41562
41617
  if (!found) {
41563
41618
  needsResize = true;
41564
- logger$c.logDebug("csa no longer in size rec, marking needsResize", {
41619
+ logger$a.logDebug("csa no longer in size rec, marking needsResize", {
41565
41620
  externalId: item.externalId,
41566
41621
  csa: item.colorwaySizeAssetId
41567
41622
  });
@@ -41611,7 +41666,7 @@ function useResolvedFittingRoom() {
41611
41666
  }, []);
41612
41667
  reactExports.useEffect(() => {
41613
41668
  loadFittingRoomData().catch((error) => {
41614
- logger$c.logError("loadFittingRoomData failed", {
41669
+ logger$a.logError("loadFittingRoomData failed", {
41615
41670
  error
41616
41671
  });
41617
41672
  });
@@ -41657,6 +41712,84 @@ function useResolvedFittingRoom() {
41657
41712
  };
41658
41713
  }, [fittingRoom, productData, merchantProductData, index, styleCategoryError]);
41659
41714
  }
41715
+ function buildVtoProductDataFromResolved(item) {
41716
+ const {
41717
+ merchantProduct,
41718
+ loadedProduct
41719
+ } = item;
41720
+ if (!merchantProduct || !loadedProduct) {
41721
+ return null;
41722
+ }
41723
+ const sizeRec = loadedProduct.sizeFitRecommendation;
41724
+ const recommendedSizeId = sizeRec.recommended_size.id || null;
41725
+ const recommendedSizeLabel = getSizeLabelFromSize(sizeRec.recommended_size);
41726
+ if (recommendedSizeId == null || !recommendedSizeLabel) {
41727
+ logger$a.logWarn("Missing recommended size for item", {
41728
+ externalId: item.externalId
41729
+ });
41730
+ return null;
41731
+ }
41732
+ const sizes = [];
41733
+ for (const sizeRecord of sizeRec.available_sizes) {
41734
+ const sizeLabel = getSizeLabelFromSize(sizeRecord);
41735
+ if (!sizeLabel) {
41736
+ continue;
41737
+ }
41738
+ const fit = sizeRec.fits.find((f) => f.size_id === sizeRecord.id);
41739
+ if (!fit) {
41740
+ continue;
41741
+ }
41742
+ const colors = [];
41743
+ for (const csa of sizeRecord.colorway_size_assets) {
41744
+ const variant = merchantProduct.variants.find((v) => v.sku === csa.sku);
41745
+ if (!variant) {
41746
+ continue;
41747
+ }
41748
+ colors.push({
41749
+ colorwaySizeAssetId: csa.id,
41750
+ colorLabel: variant.color || null,
41751
+ sku: csa.sku,
41752
+ priceFormatted: variant.priceFormatted
41753
+ });
41754
+ }
41755
+ if (colors.length === 0) {
41756
+ continue;
41757
+ }
41758
+ sizes.push({
41759
+ sizeId: sizeRecord.id,
41760
+ sizeLabel,
41761
+ isRecommended: sizeRecord.id === recommendedSizeId,
41762
+ fit,
41763
+ colors
41764
+ });
41765
+ }
41766
+ if (sizes.length === 0) {
41767
+ return null;
41768
+ }
41769
+ return {
41770
+ productName: merchantProduct.productName,
41771
+ productDescriptionHtml: merchantProduct.productDescriptionHtml,
41772
+ fitClassification: sizeRec.fit_classification,
41773
+ recommendedSizeId,
41774
+ recommendedSizeLabel,
41775
+ sizes,
41776
+ styleCategoryLabel: item.styleCategory?.label_singular ?? loadedProduct.style.style_category_label ?? null
41777
+ };
41778
+ }
41779
+ function findRecommendedColorSize(data, preferredColor) {
41780
+ const recommended = data.sizes.find((s) => s.isRecommended);
41781
+ if (!recommended || recommended.colors.length === 0) {
41782
+ return null;
41783
+ }
41784
+ return recommended.colors.find((c) => c.colorLabel === preferredColor) ?? recommended.colors[0];
41785
+ }
41786
+ function findCsaByLabel(data, sizeLabel, preferredColor) {
41787
+ const sizeRecord = data.sizes.find((s) => s.sizeLabel === sizeLabel);
41788
+ if (!sizeRecord || sizeRecord.colors.length === 0) {
41789
+ return null;
41790
+ }
41791
+ return sizeRecord.colors.find((c) => c.colorLabel === preferredColor) ?? sizeRecord.colors[0];
41792
+ }
41660
41793
  function useMobileSheetSnap(initial = "collapsed") {
41661
41794
  const [snap, setSnap] = reactExports.useState(initial);
41662
41795
  const handleTouchStart = reactExports.useCallback((e) => {
@@ -41664,7 +41797,9 @@ function useMobileSheetSnap(initial = "collapsed") {
41664
41797
  const initialSnap = snap;
41665
41798
  const onTouchMove = (moveEvent) => {
41666
41799
  const deltaY = moveEvent.touches[0].clientY - startY;
41667
- if (Math.abs(deltaY) < 30) return;
41800
+ if (Math.abs(deltaY) < 30) {
41801
+ return;
41802
+ }
41668
41803
  if (deltaY > 0) {
41669
41804
  if (initialSnap === "full" || initialSnap === "expanded") {
41670
41805
  setSnap("collapsed");
@@ -41693,11 +41828,16 @@ function useMobileSheetSnap(initial = "collapsed") {
41693
41828
  }
41694
41829
  const MAX_OUTFIT_ITEMS = 4;
41695
41830
  function asStringList(value) {
41696
- if (!Array.isArray(value)) return [];
41831
+ if (!Array.isArray(value)) {
41832
+ return [];
41833
+ }
41697
41834
  const out = [];
41698
41835
  for (const v of value) {
41699
- if (typeof v === "string") out.push(v);
41700
- else if (v != null) out.push(String(v));
41836
+ if (typeof v === "string") {
41837
+ out.push(v);
41838
+ } else if (v != null) {
41839
+ out.push(String(v));
41840
+ }
41701
41841
  }
41702
41842
  return out;
41703
41843
  }
@@ -41738,8 +41878,12 @@ function computeAvailability(item, selectedExternalIds, resolved) {
41738
41878
  }
41739
41879
  const itemCat = item.styleCategory;
41740
41880
  for (const sel of resolved.items) {
41741
- if (!selectedExternalIds.has(sel.externalId)) continue;
41742
- if (!sel.styleCategory) continue;
41881
+ if (!selectedExternalIds.has(sel.externalId)) {
41882
+ continue;
41883
+ }
41884
+ if (!sel.styleCategory) {
41885
+ continue;
41886
+ }
41743
41887
  if (!pairCompatible(sel.styleCategory, itemCat, sel.styleCategoryGroup)) {
41744
41888
  return "disabled";
41745
41889
  }
@@ -41747,8 +41891,12 @@ function computeAvailability(item, selectedExternalIds, resolved) {
41747
41891
  return "available";
41748
41892
  }
41749
41893
  function makeOutfitItem(r, forceUntuck) {
41750
- if (!r.styleCategory) return null;
41751
- if (r.storage.colorwaySizeAssetId == null) return null;
41894
+ if (!r.styleCategory) {
41895
+ return null;
41896
+ }
41897
+ if (r.storage.colorwaySizeAssetId == null) {
41898
+ return null;
41899
+ }
41752
41900
  const tuckable = !!r.styleCategory.tuckable;
41753
41901
  const untucked = forceUntuck && tuckable;
41754
41902
  const layerOrder = untucked ? r.styleCategory.layer_order_untucked : r.styleCategory.layer_order;
@@ -41768,10 +41916,16 @@ function buildOutfit(selectedExternalIds, resolved, forceUntuck, lastAddedExtern
41768
41916
  const entries = [];
41769
41917
  let lastAddedResolved = null;
41770
41918
  for (const r of resolved.items) {
41771
- if (!selectedExternalIds.has(r.externalId)) continue;
41772
- if (r.externalId === lastAddedExternalId) lastAddedResolved = r;
41919
+ if (!selectedExternalIds.has(r.externalId)) {
41920
+ continue;
41921
+ }
41922
+ if (r.externalId === lastAddedExternalId) {
41923
+ lastAddedResolved = r;
41924
+ }
41773
41925
  const entry = makeOutfitItem(r, forceUntuck);
41774
- if (entry) entries.push(entry);
41926
+ if (entry) {
41927
+ entries.push(entry);
41928
+ }
41775
41929
  }
41776
41930
  entries.sort((a, b) => a.layerOrder - b.layerOrder);
41777
41931
  const items = entries.slice(0, MAX_OUTFIT_ITEMS).map((e) => e.outfitItem);
@@ -41782,10 +41936,16 @@ function buildOutfit(selectedExternalIds, resolved, forceUntuck, lastAddedExtern
41782
41936
  };
41783
41937
  }
41784
41938
  function buildAlternateOutfits(primary, lastAddedResolved) {
41785
- if (!lastAddedResolved || !lastAddedResolved.loadedProduct) return [];
41939
+ if (!lastAddedResolved || !lastAddedResolved.loadedProduct) {
41940
+ return [];
41941
+ }
41786
41942
  const currentCsaId = lastAddedResolved.storage.colorwaySizeAssetId;
41787
- if (currentCsaId == null) return [];
41788
- if (!primary.some((i) => i.externalId === lastAddedResolved.externalId)) return [];
41943
+ if (currentCsaId == null) {
41944
+ return [];
41945
+ }
41946
+ if (!primary.some((i) => i.externalId === lastAddedResolved.externalId)) {
41947
+ return [];
41948
+ }
41789
41949
  const sizeRec = lastAddedResolved.loadedProduct.sizeFitRecommendation;
41790
41950
  let currentColorwayId = null;
41791
41951
  for (const sz of sizeRec.available_sizes) {
@@ -41798,7 +41958,9 @@ function buildAlternateOutfits(primary, lastAddedResolved) {
41798
41958
  const out = [];
41799
41959
  for (const sz of sizeRec.available_sizes) {
41800
41960
  const altCsa = sz.colorway_size_assets.find((c) => c.id !== currentCsaId && (currentColorwayId == null || c.colorway_id === currentColorwayId));
41801
- if (!altCsa) continue;
41961
+ if (!altCsa) {
41962
+ continue;
41963
+ }
41802
41964
  const alternate = primary.map((it) => it.externalId === lastAddedResolved.externalId ? {
41803
41965
  ...it,
41804
41966
  colorwaySizeAssetId: altCsa.id
@@ -41807,6 +41969,24 @@ function buildAlternateOutfits(primary, lastAddedResolved) {
41807
41969
  }
41808
41970
  return out;
41809
41971
  }
41972
+ function pillBaseStyle(theme) {
41973
+ return {
41974
+ display: "inline-flex",
41975
+ alignItems: "center",
41976
+ gap: "8px",
41977
+ padding: "8px 16px",
41978
+ borderRadius: "24px",
41979
+ backgroundColor: "rgba(255, 255, 255, 0.95)",
41980
+ border: `1px solid ${theme.color_fg_text}`,
41981
+ fontSize: "12px",
41982
+ fontWeight: "500",
41983
+ letterSpacing: "0.5px",
41984
+ textTransform: "uppercase",
41985
+ cursor: "pointer",
41986
+ userSelect: "none",
41987
+ WebkitUserSelect: "none"
41988
+ };
41989
+ }
41810
41990
  function AvatarControls({
41811
41991
  selectedItems,
41812
41992
  canTuck,
@@ -41823,7 +42003,9 @@ function AvatarControls({
41823
42003
  const [popoverOpen, setPopoverOpen] = reactExports.useState(false);
41824
42004
  const popoverWrapperRef = reactExports.useRef(null);
41825
42005
  reactExports.useEffect(() => {
41826
- if (!popoverOpen) return;
42006
+ if (!popoverOpen) {
42007
+ return;
42008
+ }
41827
42009
  const onDocClick = (e) => {
41828
42010
  if (popoverWrapperRef.current && !popoverWrapperRef.current.contains(e.target)) {
41829
42011
  setPopoverOpen(false);
@@ -41846,20 +42028,7 @@ function AvatarControls({
41846
42028
  alignItems: "flex-end"
41847
42029
  },
41848
42030
  pill: {
41849
- display: "inline-flex",
41850
- alignItems: "center",
41851
- gap: "8px",
41852
- padding: "8px 16px",
41853
- borderRadius: "24px",
41854
- backgroundColor: "rgba(255, 255, 255, 0.95)",
41855
- border: `1px solid ${theme.color_fg_text}`,
41856
- fontSize: "12px",
41857
- fontWeight: "500",
41858
- letterSpacing: "0.5px",
41859
- textTransform: "uppercase",
41860
- cursor: "pointer",
41861
- userSelect: "none",
41862
- WebkitUserSelect: "none",
42031
+ ...pillBaseStyle(theme),
41863
42032
  transition: "padding 500ms cubic-bezier(0.22, 1, 0.36, 1), gap 500ms cubic-bezier(0.22, 1, 0.36, 1)"
41864
42033
  },
41865
42034
  pillCollapsed: {
@@ -41961,13 +42130,114 @@ function AvatarControls({
41961
42130
  ] })
41962
42131
  ] });
41963
42132
  }
42133
+ function MobileTuckControl({
42134
+ canTuck,
42135
+ forceUntuck,
42136
+ onToggleUntuck
42137
+ }) {
42138
+ const {
42139
+ t
42140
+ } = useTranslation();
42141
+ const css2 = useCss((theme) => ({
42142
+ wrapper: {
42143
+ position: "absolute",
42144
+ bottom: "12px",
42145
+ right: "12px"
42146
+ // No z-index: the pill sits above the avatar image (later sibling than
42147
+ // the frame viewer) but below the product-details sheet, which is a
42148
+ // later sibling in the try-on view — so the sheet hides the pill when
42149
+ // it expands over the image.
42150
+ },
42151
+ pill: pillBaseStyle(theme),
42152
+ pillIcon: {
42153
+ width: "14px",
42154
+ height: "14px",
42155
+ flex: "none"
42156
+ }
42157
+ }));
42158
+ if (!canTuck) {
42159
+ return null;
42160
+ }
42161
+ return /* @__PURE__ */ jsx$1("div", { css: css2.wrapper, children: /* @__PURE__ */ jsxs(Button, { variant: "base", css: css2.pill, onClick: onToggleUntuck, children: [
42162
+ /* @__PURE__ */ jsx$1(SvgIconTuck, { css: css2.pillIcon }),
42163
+ /* @__PURE__ */ jsx$1(Text, { variant: "base", children: t(forceUntuck ? "fitting_room.tuck_in" : "fitting_room.untuck") })
42164
+ ] }) });
42165
+ }
42166
+ const DRAG_STEP_PX = 50;
42167
+ function useFrameRotation(frameUrls, setSelectedFrameIndex) {
42168
+ const frameCount = frameUrls?.length ?? 0;
42169
+ const rotateLeft = reactExports.useCallback(() => {
42170
+ setSelectedFrameIndex((prev2) => {
42171
+ if (prev2 == null || frameCount === 0) {
42172
+ return prev2;
42173
+ }
42174
+ return prev2 === 0 ? frameCount - 1 : prev2 - 1;
42175
+ });
42176
+ }, [frameCount, setSelectedFrameIndex]);
42177
+ const rotateRight = reactExports.useCallback(() => {
42178
+ setSelectedFrameIndex((prev2) => {
42179
+ if (prev2 == null || frameCount === 0) {
42180
+ return prev2;
42181
+ }
42182
+ return prev2 === frameCount - 1 ? 0 : prev2 + 1;
42183
+ });
42184
+ }, [frameCount, setSelectedFrameIndex]);
42185
+ const handleMouseDragStart = reactExports.useCallback((e) => {
42186
+ e.preventDefault();
42187
+ let startX = e.clientX;
42188
+ const onMove = (move) => {
42189
+ const deltaX = move.clientX - startX;
42190
+ if (Math.abs(deltaX) >= DRAG_STEP_PX) {
42191
+ if (deltaX > 0) {
42192
+ rotateRight();
42193
+ } else {
42194
+ rotateLeft();
42195
+ }
42196
+ startX = move.clientX;
42197
+ }
42198
+ };
42199
+ const onUp = () => {
42200
+ window.removeEventListener("mousemove", onMove);
42201
+ window.removeEventListener("mouseup", onUp);
42202
+ };
42203
+ window.addEventListener("mousemove", onMove);
42204
+ window.addEventListener("mouseup", onUp);
42205
+ }, [rotateLeft, rotateRight]);
42206
+ const handleTouchDragStart = reactExports.useCallback((e) => {
42207
+ e.preventDefault();
42208
+ let startX = e.touches[0].clientX;
42209
+ const onMove = (move) => {
42210
+ const deltaX = move.touches[0].clientX - startX;
42211
+ if (Math.abs(deltaX) >= DRAG_STEP_PX) {
42212
+ if (deltaX > 0) {
42213
+ rotateRight();
42214
+ } else {
42215
+ rotateLeft();
42216
+ }
42217
+ startX = move.touches[0].clientX;
42218
+ }
42219
+ };
42220
+ const onEnd = () => {
42221
+ window.removeEventListener("touchmove", onMove);
42222
+ window.removeEventListener("touchend", onEnd);
42223
+ };
42224
+ window.addEventListener("touchmove", onMove);
42225
+ window.addEventListener("touchend", onEnd);
42226
+ }, [rotateLeft, rotateRight]);
42227
+ return {
42228
+ rotateLeft,
42229
+ rotateRight,
42230
+ handleMouseDragStart,
42231
+ handleTouchDragStart
42232
+ };
42233
+ }
41964
42234
  function AvatarFrameViewer({
41965
42235
  frameUrls,
41966
42236
  selectedFrameIndex,
41967
42237
  setSelectedFrameIndex,
41968
42238
  imageContainerStyle,
41969
42239
  imageStyle,
41970
- loadingT = "vto-single.avatar_loading"
42240
+ loadingT = "quick-view.avatar_loading"
41971
42241
  }) {
41972
42242
  const css2 = useCss((_theme) => ({
41973
42243
  imageContainer: {
@@ -42011,59 +42281,17 @@ function AvatarFrameViewer({
42011
42281
  }, 500);
42012
42282
  return () => clearInterval(intervalId);
42013
42283
  }, [frameUrls, selectedFrameIndex, setSelectedFrameIndex]);
42014
- const rotateLeft = reactExports.useCallback(() => {
42015
- setSelectedFrameIndex((prevIndex) => {
42016
- if (prevIndex == null) return null;
42017
- return prevIndex === 0 ? frameUrls ? frameUrls.length - 1 : 0 : prevIndex - 1;
42018
- });
42019
- }, [frameUrls, setSelectedFrameIndex]);
42020
- const rotateRight = reactExports.useCallback(() => {
42021
- setSelectedFrameIndex((prevIndex) => {
42022
- if (prevIndex == null) return null;
42023
- return prevIndex === (frameUrls ? frameUrls.length - 1 : 0) ? 0 : prevIndex + 1;
42024
- });
42025
- }, [frameUrls, setSelectedFrameIndex]);
42026
- const handleImageMouseDrag = reactExports.useCallback((e) => {
42027
- e.preventDefault();
42028
- let startX = e.clientX;
42029
- const onMouseMove = (moveEvent) => {
42030
- const deltaX = moveEvent.clientX - startX;
42031
- if (Math.abs(deltaX) >= 50) {
42032
- if (deltaX > 0) rotateRight();
42033
- else rotateLeft();
42034
- startX = moveEvent.clientX;
42035
- }
42036
- };
42037
- const onMouseUp = () => {
42038
- window.removeEventListener("mousemove", onMouseMove);
42039
- window.removeEventListener("mouseup", onMouseUp);
42040
- };
42041
- window.addEventListener("mousemove", onMouseMove);
42042
- window.addEventListener("mouseup", onMouseUp);
42043
- }, [rotateLeft, rotateRight]);
42044
- const handleImageTouchDrag = reactExports.useCallback((e) => {
42045
- e.preventDefault();
42046
- let startX = e.touches[0].clientX;
42047
- const onTouchMove = (moveEvent) => {
42048
- const deltaX = moveEvent.touches[0].clientX - startX;
42049
- if (Math.abs(deltaX) >= 50) {
42050
- if (deltaX > 0) rotateRight();
42051
- else rotateLeft();
42052
- startX = moveEvent.touches[0].clientX;
42053
- }
42054
- };
42055
- const onTouchEnd = () => {
42056
- window.removeEventListener("touchmove", onTouchMove);
42057
- window.removeEventListener("touchend", onTouchEnd);
42058
- };
42059
- window.addEventListener("touchmove", onTouchMove);
42060
- window.addEventListener("touchend", onTouchEnd);
42061
- }, [rotateLeft, rotateRight]);
42284
+ const {
42285
+ rotateLeft,
42286
+ rotateRight,
42287
+ handleMouseDragStart,
42288
+ handleTouchDragStart
42289
+ } = useFrameRotation(frameUrls, setSelectedFrameIndex);
42062
42290
  if (!frameUrls || selectedFrameIndex == null) {
42063
42291
  return /* @__PURE__ */ jsx$1(Loading, { t: loadingT });
42064
42292
  }
42065
42293
  return /* @__PURE__ */ jsxs("div", { css: css2.imageContainer, style: imageContainerStyle, children: [
42066
- /* @__PURE__ */ jsx$1("img", { src: frameUrls[selectedFrameIndex], css: css2.image, style: imageStyle, onMouseDown: handleImageMouseDrag, onTouchStart: handleImageTouchDrag }),
42294
+ /* @__PURE__ */ jsx$1("img", { src: frameUrls[selectedFrameIndex], css: css2.image, style: imageStyle, onMouseDown: handleMouseDragStart, onTouchStart: handleTouchDragStart }),
42067
42295
  /* @__PURE__ */ jsx$1("div", { css: css2.chevronLeftContainer, onClick: rotateLeft, children: /* @__PURE__ */ jsx$1(SvgChevronLeft, { css: css2.chevronIcon }) }),
42068
42296
  /* @__PURE__ */ jsx$1("div", { css: css2.chevronRightContainer, onClick: rotateRight, children: /* @__PURE__ */ jsx$1(SvgChevronRight, { css: css2.chevronIcon }) })
42069
42297
  ] });
@@ -42072,9 +42300,13 @@ function AvatarPane({
42072
42300
  frameUrls,
42073
42301
  hasSelection,
42074
42302
  controls,
42075
- mobileFullscreen
42303
+ mobileFullscreen,
42304
+ selectedFrameIndex: indexProp,
42305
+ setSelectedFrameIndex: setIndexProp
42076
42306
  }) {
42077
- const [selectedFrameIndex, setSelectedFrameIndex] = reactExports.useState(null);
42307
+ const [localFrameIndex, setLocalFrameIndex] = reactExports.useState(null);
42308
+ const selectedFrameIndex = indexProp !== void 0 ? indexProp : localFrameIndex;
42309
+ const setSelectedFrameIndex = setIndexProp ?? setLocalFrameIndex;
42078
42310
  const css2 = useCss((theme) => ({
42079
42311
  container: {
42080
42312
  width: "100%",
@@ -42132,12 +42364,14 @@ function AvatarPane({
42132
42364
  }
42133
42365
  }));
42134
42366
  if (frameUrls && frameUrls.length > 0) {
42135
- const viewer = /* @__PURE__ */ jsx$1(AvatarFrameViewer, { frameUrls, selectedFrameIndex, setSelectedFrameIndex, imageContainerStyle: css2.frameContainer, imageStyle: css2.frameImage, loadingT: "vto-single.avatar_loading" });
42367
+ const viewer = /* @__PURE__ */ jsx$1(AvatarFrameViewer, { frameUrls, selectedFrameIndex, setSelectedFrameIndex, imageContainerStyle: css2.frameContainer, imageStyle: css2.frameImage, loadingT: "quick-view.avatar_loading" });
42136
42368
  if (mobileFullscreen) {
42137
42369
  return /* @__PURE__ */ jsxs("div", { css: css2.mobileContainer, children: [
42138
- /* @__PURE__ */ jsx$1("div", { css: css2.mobileFrameSlot, children: viewer }),
42139
- /* @__PURE__ */ jsx$1("div", { css: css2.bottomFiller, children: " " }),
42140
- controls
42370
+ /* @__PURE__ */ jsxs("div", { css: css2.mobileFrameSlot, children: [
42371
+ viewer,
42372
+ controls
42373
+ ] }),
42374
+ /* @__PURE__ */ jsx$1("div", { css: css2.bottomFiller, children: " " })
42141
42375
  ] });
42142
42376
  }
42143
42377
  return /* @__PURE__ */ jsxs("div", { css: css2.container, children: [
@@ -42146,7 +42380,7 @@ function AvatarPane({
42146
42380
  ] });
42147
42381
  }
42148
42382
  if (hasSelection) {
42149
- return /* @__PURE__ */ jsx$1("div", { css: css2.container, children: /* @__PURE__ */ jsx$1(Loading, { t: "vto-single.avatar_loading" }) });
42383
+ return /* @__PURE__ */ jsx$1("div", { css: css2.container, children: /* @__PURE__ */ jsx$1(Loading, { t: "quick-view.avatar_loading" }) });
42150
42384
  }
42151
42385
  return /* @__PURE__ */ jsxs("div", { css: css2.container, children: [
42152
42386
  /* @__PURE__ */ jsx$1("div", { css: css2.placeholder, children: /* @__PURE__ */ jsx$1(TextT, { variant: "base", t: "fitting_room.avatar_placeholder_empty" }) }),
@@ -42251,7 +42485,9 @@ function ProductCard({
42251
42485
  const disabled = availability === "disabled";
42252
42486
  const selected = availability === "selected";
42253
42487
  const handleClick = () => {
42254
- if (disabled) return;
42488
+ if (disabled) {
42489
+ return;
42490
+ }
42255
42491
  onClick();
42256
42492
  };
42257
42493
  const name2 = item.merchantProduct?.productName ?? item.externalId;
@@ -42297,9 +42533,13 @@ function CardRail({
42297
42533
  setCanScrollRight(el.scrollLeft + el.clientWidth < el.scrollWidth - 1);
42298
42534
  }, []);
42299
42535
  reactExports.useLayoutEffect(() => {
42300
- if (collapsed) return;
42536
+ if (collapsed) {
42537
+ return;
42538
+ }
42301
42539
  const el = scrollRef.current;
42302
- if (!el) return;
42540
+ if (!el) {
42541
+ return;
42542
+ }
42303
42543
  updateScrollState();
42304
42544
  const observer = new ResizeObserver(updateScrollState);
42305
42545
  observer.observe(el);
@@ -42307,7 +42547,9 @@ function CardRail({
42307
42547
  }, [collapsed, group.items, updateScrollState]);
42308
42548
  const scrollByPage = reactExports.useCallback((dir) => {
42309
42549
  const el = scrollRef.current;
42310
- if (!el) return;
42550
+ if (!el) {
42551
+ return;
42552
+ }
42311
42553
  el.scrollBy({
42312
42554
  left: dir * el.clientWidth * 0.8,
42313
42555
  behavior: "smooth"
@@ -42400,7 +42642,7 @@ function CardRail({
42400
42642
  function AddToCartButton({
42401
42643
  onClick
42402
42644
  }) {
42403
- return /* @__PURE__ */ jsx$1(ButtonT, { variant: "brand", t: "vto-single.add_to_cart", onClick });
42645
+ return /* @__PURE__ */ jsx$1(ButtonT, { variant: "brand", t: "quick-view.add_to_cart", onClick });
42404
42646
  }
42405
42647
  function ItemFitDetails({
42406
42648
  loadedProductData,
@@ -42501,69 +42743,6 @@ function SizeSelector({
42501
42743
  }), [loadedProductData.sizes, selectedSizeLabel, onChangeSize]);
42502
42744
  return /* @__PURE__ */ jsx$1("div", { css: css2.container, children: sizeSelectorNodeList });
42503
42745
  }
42504
- const logger$b = getLogger("overlays/fitting-room/product-data");
42505
- function buildVtoProductDataFromResolved(item) {
42506
- const {
42507
- merchantProduct,
42508
- loadedProduct
42509
- } = item;
42510
- if (!merchantProduct || !loadedProduct) return null;
42511
- const sizeRec = loadedProduct.sizeFitRecommendation;
42512
- const recommendedSizeId = sizeRec.recommended_size.id || null;
42513
- const recommendedSizeLabel = getSizeLabelFromSize(sizeRec.recommended_size);
42514
- if (recommendedSizeId == null || !recommendedSizeLabel) {
42515
- logger$b.logWarn("Missing recommended size for item", {
42516
- externalId: item.externalId
42517
- });
42518
- return null;
42519
- }
42520
- const sizes = [];
42521
- for (const sizeRecord of sizeRec.available_sizes) {
42522
- const sizeLabel = getSizeLabelFromSize(sizeRecord);
42523
- if (!sizeLabel) continue;
42524
- const fit = sizeRec.fits.find((f) => f.size_id === sizeRecord.id);
42525
- if (!fit) continue;
42526
- const colors = [];
42527
- for (const csa of sizeRecord.colorway_size_assets) {
42528
- const variant = merchantProduct.variants.find((v) => v.sku === csa.sku);
42529
- if (!variant) continue;
42530
- colors.push({
42531
- colorwaySizeAssetId: csa.id,
42532
- colorLabel: variant.color || null,
42533
- sku: csa.sku,
42534
- priceFormatted: variant.priceFormatted
42535
- });
42536
- }
42537
- if (colors.length === 0) continue;
42538
- sizes.push({
42539
- sizeId: sizeRecord.id,
42540
- sizeLabel,
42541
- isRecommended: sizeRecord.id === recommendedSizeId,
42542
- fit,
42543
- colors
42544
- });
42545
- }
42546
- if (sizes.length === 0) return null;
42547
- return {
42548
- productName: merchantProduct.productName,
42549
- productDescriptionHtml: merchantProduct.productDescriptionHtml,
42550
- fitClassification: sizeRec.fit_classification,
42551
- recommendedSizeId,
42552
- recommendedSizeLabel,
42553
- sizes,
42554
- styleCategoryLabel: item.styleCategory?.label_singular ?? loadedProduct.style.style_category_label ?? null
42555
- };
42556
- }
42557
- function findRecommendedColorSize(data, preferredColor) {
42558
- const recommended = data.sizes.find((s) => s.isRecommended);
42559
- if (!recommended || recommended.colors.length === 0) return null;
42560
- return recommended.colors.find((c) => c.colorLabel === preferredColor) ?? recommended.colors[0];
42561
- }
42562
- function findCsaByLabel(data, sizeLabel, preferredColor) {
42563
- const sizeRecord = data.sizes.find((s) => s.sizeLabel === sizeLabel);
42564
- if (!sizeRecord || sizeRecord.colors.length === 0) return null;
42565
- return sizeRecord.colors.find((c) => c.colorLabel === preferredColor) ?? sizeRecord.colors[0];
42566
- }
42567
42746
  function DetailAccordionItem({
42568
42747
  item,
42569
42748
  isOpen,
@@ -42580,9 +42759,13 @@ function DetailAccordionItem({
42580
42759
  }) {
42581
42760
  const productData = reactExports.useMemo(() => buildVtoProductDataFromResolved(item), [item]);
42582
42761
  const selectedSizeLabel = reactExports.useMemo(() => {
42583
- if (!productData) return null;
42762
+ if (!productData) {
42763
+ return null;
42764
+ }
42584
42765
  const csaId = item.storage.colorwaySizeAssetId;
42585
- if (csaId == null) return null;
42766
+ if (csaId == null) {
42767
+ return null;
42768
+ }
42586
42769
  for (const sizeRec of productData.sizes) {
42587
42770
  if (sizeRec.colors.some((c) => c.colorwaySizeAssetId === csaId)) {
42588
42771
  return sizeRec.sizeLabel;
@@ -42591,12 +42774,18 @@ function DetailAccordionItem({
42591
42774
  return null;
42592
42775
  }, [productData, item.storage.colorwaySizeAssetId]);
42593
42776
  const currentPrice = reactExports.useMemo(() => {
42594
- if (!productData) return null;
42777
+ if (!productData) {
42778
+ return null;
42779
+ }
42595
42780
  const csaId = item.storage.colorwaySizeAssetId;
42596
- if (csaId == null) return null;
42781
+ if (csaId == null) {
42782
+ return null;
42783
+ }
42597
42784
  for (const sizeRec of productData.sizes) {
42598
42785
  const c = sizeRec.colors.find((c2) => c2.colorwaySizeAssetId === csaId);
42599
- if (c) return c.priceFormatted;
42786
+ if (c) {
42787
+ return c.priceFormatted;
42788
+ }
42600
42789
  }
42601
42790
  return null;
42602
42791
  }, [productData, item.storage.colorwaySizeAssetId]);
@@ -42923,6 +43112,119 @@ function DetailAccordion({
42923
43112
  return /* @__PURE__ */ jsx$1(DetailAccordionItem, { item, isOpen, platform, detailMode, isMobileQuickRow, forceUntuck, canTuck, onToggleOpen: () => onOpenItem(isOpen ? null : item.externalId), onChangeDetailMode, onChangeSize: (label) => onChangeSize(item.externalId, label), onAddToCart: () => onAddToCart(item.externalId), onToggleUntuck }, item.externalId);
42924
43113
  }) });
42925
43114
  }
43115
+ function ZoomModal({
43116
+ frameUrls,
43117
+ selectedFrameIndex,
43118
+ setSelectedFrameIndex,
43119
+ onClose
43120
+ }) {
43121
+ reactExports.useEffect(() => {
43122
+ const onKeyDown = (e) => {
43123
+ if (e.key === "Escape") {
43124
+ e.stopPropagation();
43125
+ e.preventDefault();
43126
+ onClose();
43127
+ }
43128
+ };
43129
+ document.addEventListener("keydown", onKeyDown, true);
43130
+ return () => document.removeEventListener("keydown", onKeyDown, true);
43131
+ }, [onClose]);
43132
+ const {
43133
+ rotateLeft,
43134
+ rotateRight,
43135
+ handleMouseDragStart,
43136
+ handleTouchDragStart
43137
+ } = useFrameRotation(frameUrls, setSelectedFrameIndex);
43138
+ const css2 = useCss((_theme) => ({
43139
+ backdrop: {
43140
+ position: "fixed",
43141
+ inset: 0,
43142
+ backgroundColor: "#F4F4F4",
43143
+ zIndex: 100
43144
+ },
43145
+ scrollArea: {
43146
+ position: "absolute",
43147
+ // 40px padding on every side.
43148
+ inset: "40px",
43149
+ overflow: "auto"
43150
+ },
43151
+ // Centres the frame when it fits; grows past the scroll area when it
43152
+ // doesn't, so the scroll area scrolls instead of shrinking the image.
43153
+ imageWrap: {
43154
+ minWidth: "100%",
43155
+ minHeight: "100%",
43156
+ display: "flex",
43157
+ alignItems: "center",
43158
+ justifyContent: "center"
43159
+ },
43160
+ image: {
43161
+ // No sizing — the frame renders at its natural (full) resolution.
43162
+ display: "block",
43163
+ flex: "none",
43164
+ cursor: "grab"
43165
+ },
43166
+ // Rotation chevrons float at the modal's vertical centre, so they stay
43167
+ // put regardless of how the (possibly larger) frame is scrolled.
43168
+ chevron: {
43169
+ position: "absolute",
43170
+ top: "50%",
43171
+ transform: "translateY(-50%)",
43172
+ display: "flex",
43173
+ cursor: "pointer",
43174
+ zIndex: 1
43175
+ },
43176
+ chevronLeft: {
43177
+ left: "8px"
43178
+ },
43179
+ chevronRight: {
43180
+ right: "8px"
43181
+ },
43182
+ chevronIcon: {
43183
+ width: "48px",
43184
+ height: "48px"
43185
+ },
43186
+ close: {
43187
+ position: "absolute",
43188
+ top: "8px",
43189
+ right: "8px",
43190
+ width: "44px",
43191
+ height: "44px",
43192
+ display: "flex",
43193
+ alignItems: "center",
43194
+ justifyContent: "center",
43195
+ borderRadius: "50%",
43196
+ border: "none",
43197
+ // Reset native button chrome and default padding so the circle renders
43198
+ // exactly as styled.
43199
+ appearance: "none",
43200
+ WebkitAppearance: "none",
43201
+ padding: 0,
43202
+ backgroundColor: "rgba(0, 0, 0, 0.5)",
43203
+ color: "#FFFFFF",
43204
+ fontSize: "32px",
43205
+ lineHeight: 1,
43206
+ cursor: "pointer",
43207
+ // Keep the "×" glyph from being text-selected — a stray selection
43208
+ // paints a dark highlight rectangle behind it.
43209
+ userSelect: "none",
43210
+ WebkitUserSelect: "none",
43211
+ zIndex: 1
43212
+ }
43213
+ }));
43214
+ const imageUrl = frameUrls[selectedFrameIndex ?? 0];
43215
+ return /* @__PURE__ */ jsxs("div", { css: css2.backdrop, children: [
43216
+ /* @__PURE__ */ jsx$1("div", { css: css2.scrollArea, children: /* @__PURE__ */ jsx$1("div", { css: css2.imageWrap, children: /* @__PURE__ */ jsx$1("img", { src: imageUrl, css: css2.image, alt: "", onMouseDown: handleMouseDragStart, onTouchStart: handleTouchDragStart }) }) }),
43217
+ /* @__PURE__ */ jsx$1("div", { css: /* @__PURE__ */ css$1({
43218
+ ...css2.chevron,
43219
+ ...css2.chevronLeft
43220
+ }, "", ""), onClick: rotateLeft, children: /* @__PURE__ */ jsx$1(SvgChevronLeft, { css: css2.chevronIcon }) }),
43221
+ /* @__PURE__ */ jsx$1("div", { css: /* @__PURE__ */ css$1({
43222
+ ...css2.chevron,
43223
+ ...css2.chevronRight
43224
+ }, "", ""), onClick: rotateRight, children: /* @__PURE__ */ jsx$1(SvgChevronRight, { css: css2.chevronIcon }) }),
43225
+ /* @__PURE__ */ jsx$1("button", { css: css2.close, onClick: onClose, "aria-label": "Close zoom", children: "×" })
43226
+ ] });
43227
+ }
42926
43228
  const AVATAR_ASPECT_RATIO = 2 / 3;
42927
43229
  const EDGE_INSET_PX = 16;
42928
43230
  const AVATAR_MIN_WIDTH_PX = 240;
@@ -42938,7 +43240,6 @@ function DesktopLayout$1({
42938
43240
  detailMode,
42939
43241
  forceUntuck,
42940
43242
  canTuck,
42941
- zoomed,
42942
43243
  frameUrls,
42943
43244
  onSelectItem,
42944
43245
  onRemoveItem,
@@ -42947,26 +43248,31 @@ function DesktopLayout$1({
42947
43248
  onChangeSize,
42948
43249
  onAddToCart,
42949
43250
  onToggleUntuck,
42950
- onToggleZoom,
42951
43251
  onSignOut
42952
43252
  }) {
42953
43253
  const hasSelection = selectedItems.length > 0;
42954
43254
  const [avatarHovered, setAvatarHovered] = reactExports.useState(false);
43255
+ const [zoomOpen, setZoomOpen] = reactExports.useState(false);
43256
+ const [selectedFrameIndex, setSelectedFrameIndex] = reactExports.useState(null);
42955
43257
  const containerRef = reactExports.useRef(null);
42956
43258
  const [avatarWidth, setAvatarWidth] = reactExports.useState(AVATAR_MIN_WIDTH_PX);
42957
43259
  reactExports.useLayoutEffect(() => {
42958
43260
  const el = containerRef.current;
42959
- if (!el) return;
43261
+ if (!el) {
43262
+ return;
43263
+ }
42960
43264
  const observer = new ResizeObserver(() => {
42961
43265
  const availableHeightPx = el.clientHeight;
42962
- if (availableHeightPx <= 0) return;
43266
+ if (availableHeightPx <= 0) {
43267
+ return;
43268
+ }
42963
43269
  const target = Math.floor(availableHeightPx * AVATAR_ASPECT_RATIO);
42964
43270
  setAvatarWidth(Math.min(AVATAR_MAX_WIDTH_PX, Math.max(AVATAR_MIN_WIDTH_PX, target)));
42965
43271
  });
42966
43272
  observer.observe(el);
42967
43273
  return () => observer.disconnect();
42968
43274
  }, []);
42969
- const gridTemplateColumns = zoomed ? "1fr" : hasSelection ? `${avatarWidth}px minmax(${DETAILS_MIN_WIDTH_PX}px, ${DETAILS_FR}fr) ${CARDS_FR}fr` : `${avatarWidth}px 1fr`;
43275
+ const gridTemplateColumns = hasSelection ? `${avatarWidth}px minmax(${DETAILS_MIN_WIDTH_PX}px, ${DETAILS_FR}fr) ${CARDS_FR}fr` : `${avatarWidth}px 1fr`;
42970
43276
  const css2 = useCss((_theme) => ({
42971
43277
  container: {
42972
43278
  display: "grid",
@@ -43031,21 +43337,127 @@ function DesktopLayout$1({
43031
43337
  fontSize: "14px"
43032
43338
  }
43033
43339
  }));
43034
- const controls = hasSelection ? /* @__PURE__ */ jsx$1(AvatarControls, { selectedItems, canTuck, forceUntuck, zoomed, expanded: avatarHovered, onToggleUntuck, onToggleZoom, onRemoveItem }) : null;
43340
+ const controls = hasSelection ? /* @__PURE__ */ jsx$1(AvatarControls, { selectedItems, canTuck, forceUntuck, zoomed: zoomOpen, expanded: avatarHovered, onToggleUntuck, onToggleZoom: () => setZoomOpen(true), onRemoveItem }) : null;
43035
43341
  return /* @__PURE__ */ jsxs("div", { ref: containerRef, css: css2.container, style: {
43036
43342
  gridTemplateColumns
43037
43343
  }, children: [
43038
- /* @__PURE__ */ jsx$1("div", { css: css2.avatarColumn, onMouseEnter: () => setAvatarHovered(true), onMouseLeave: () => setAvatarHovered(false), children: /* @__PURE__ */ jsx$1(AvatarPane, { hasSelection, frameUrls, controls }) }),
43039
- !zoomed && hasSelection ? /* @__PURE__ */ jsx$1("div", { css: css2.detailColumn, children: /* @__PURE__ */ jsx$1(DetailAccordion, { items: selectedItems, openItemExternalId: openAccordionItemId, platform: "desktop", detailMode, isMobileQuickRow: false, forceUntuck, canTuck, onOpenItem: onOpenAccordionItem, onChangeDetailMode, onChangeSize, onAddToCart, onToggleUntuck }) }) : null,
43040
- !zoomed ? /* @__PURE__ */ jsxs("div", { css: css2.railsColumn, children: [
43344
+ /* @__PURE__ */ jsx$1("div", { css: css2.avatarColumn, onMouseEnter: () => setAvatarHovered(true), onMouseLeave: () => setAvatarHovered(false), children: /* @__PURE__ */ jsx$1(AvatarPane, { hasSelection, frameUrls, controls, selectedFrameIndex, setSelectedFrameIndex }) }),
43345
+ hasSelection ? /* @__PURE__ */ jsx$1("div", { css: css2.detailColumn, children: /* @__PURE__ */ jsx$1(DetailAccordion, { items: selectedItems, openItemExternalId: openAccordionItemId, platform: "desktop", detailMode, isMobileQuickRow: false, forceUntuck, canTuck, onOpenItem: onOpenAccordionItem, onChangeDetailMode, onChangeSize, onAddToCart, onToggleUntuck }) }) : null,
43346
+ /* @__PURE__ */ jsxs("div", { css: css2.railsColumn, children: [
43041
43347
  /* @__PURE__ */ jsxs("span", { css: css2.signOutWrapper, onClick: onSignOut, children: [
43042
43348
  /* @__PURE__ */ jsx$1(SvgTfrIcon, { css: css2.signOutIcon }),
43043
43349
  /* @__PURE__ */ jsx$1(LinkT, { variant: "underline", css: css2.signOut, t: "fitting_room.sign_out" })
43044
43350
  ] }),
43045
43351
  resolved.groups.map((group) => /* @__PURE__ */ jsx$1(CardRail, { group, availabilityByExternalId, onSelectItem, onRemoveItem }, group.group.name))
43046
- ] }) : null
43352
+ ] }),
43353
+ zoomOpen && frameUrls && frameUrls.length > 0 ? /* @__PURE__ */ jsx$1(ZoomModal, { frameUrls, selectedFrameIndex, setSelectedFrameIndex, onClose: () => setZoomOpen(false) }) : null
43047
43354
  ] });
43048
43355
  }
43356
+ function MenuIcon({
43357
+ size = 20
43358
+ }) {
43359
+ return /* @__PURE__ */ jsx$1("svg", { width: size, height: size, viewBox: "0 0 24 24", fill: "none", children: /* @__PURE__ */ jsx$1("path", { d: "M4 7H20M4 12H20M4 17H20", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round" }) });
43360
+ }
43361
+ function SectionNav({
43362
+ sections,
43363
+ activeName,
43364
+ onSelect
43365
+ }) {
43366
+ const [open, setOpen] = reactExports.useState(false);
43367
+ const wrapperRef = reactExports.useRef(null);
43368
+ reactExports.useEffect(() => {
43369
+ if (!open) {
43370
+ return;
43371
+ }
43372
+ const onDocClick = (e) => {
43373
+ if (wrapperRef.current && !wrapperRef.current.contains(e.target)) {
43374
+ setOpen(false);
43375
+ }
43376
+ };
43377
+ document.addEventListener("mousedown", onDocClick);
43378
+ return () => document.removeEventListener("mousedown", onDocClick);
43379
+ }, [open]);
43380
+ const handleSelect = reactExports.useCallback((name2) => {
43381
+ setOpen(false);
43382
+ onSelect(name2);
43383
+ }, [onSelect]);
43384
+ const css2 = useCss((theme) => ({
43385
+ wrapper: {
43386
+ // Floats over the card rails at the top-right instead of taking its own
43387
+ // row in the browse-view column (BrowseView's container is relative).
43388
+ position: "absolute",
43389
+ top: "12px",
43390
+ right: "16px",
43391
+ // Above the rails so the pill and its drop-down sit over the content.
43392
+ zIndex: 5
43393
+ },
43394
+ bar: {
43395
+ display: "inline-flex",
43396
+ alignItems: "center",
43397
+ gap: "8px",
43398
+ padding: "6px 16px",
43399
+ borderRadius: "999px",
43400
+ backgroundColor: theme.color_fg_text,
43401
+ color: "#FFFFFF",
43402
+ border: "none",
43403
+ cursor: "pointer",
43404
+ fontSize: "13px",
43405
+ fontWeight: "500",
43406
+ letterSpacing: "0.5px",
43407
+ textTransform: "uppercase",
43408
+ whiteSpace: "nowrap"
43409
+ },
43410
+ icon: {
43411
+ display: "inline-flex",
43412
+ alignItems: "center",
43413
+ flex: "none"
43414
+ },
43415
+ dropdown: {
43416
+ position: "absolute",
43417
+ top: "calc(100% + 6px)",
43418
+ right: 0,
43419
+ // At least as wide as the pill, growing to fit the longest label.
43420
+ minWidth: "100%",
43421
+ backgroundColor: theme.color_fg_text,
43422
+ borderRadius: "16px",
43423
+ maxHeight: "60vh",
43424
+ overflowY: "auto",
43425
+ boxShadow: "0 6px 16px rgba(0, 0, 0, 0.25)"
43426
+ },
43427
+ item: {
43428
+ display: "block",
43429
+ width: "100%",
43430
+ textAlign: "left",
43431
+ whiteSpace: "nowrap",
43432
+ padding: "8px 18px",
43433
+ backgroundColor: "transparent",
43434
+ color: "#FFFFFF",
43435
+ border: "none",
43436
+ cursor: "pointer",
43437
+ fontSize: "13px",
43438
+ letterSpacing: "0.5px",
43439
+ textTransform: "uppercase",
43440
+ "&:not(:first-of-type)": {
43441
+ borderTop: "1px solid rgba(255, 255, 255, 0.12)"
43442
+ }
43443
+ },
43444
+ itemActive: {
43445
+ backgroundColor: "rgba(255, 255, 255, 0.16)"
43446
+ }
43447
+ }));
43448
+ const activeLabel = sections.find((s) => s.name === activeName)?.label ?? sections[0]?.label ?? "";
43449
+ return /* @__PURE__ */ jsxs("div", { ref: wrapperRef, css: css2.wrapper, children: [
43450
+ /* @__PURE__ */ jsxs(Button, { variant: "base", css: css2.bar, onClick: () => setOpen((o) => !o), children: [
43451
+ /* @__PURE__ */ jsx$1("span", { children: activeLabel }),
43452
+ /* @__PURE__ */ jsx$1("span", { css: css2.icon, children: open ? /* @__PURE__ */ jsx$1(Chevron, { direction: "up", size: 20 }) : /* @__PURE__ */ jsx$1(MenuIcon, { size: 20 }) })
43453
+ ] }),
43454
+ open ? /* @__PURE__ */ jsx$1("div", { css: css2.dropdown, children: sections.map((s) => /* @__PURE__ */ jsx$1(Button, { variant: "base", css: s.name === activeName ? {
43455
+ ...css2.item,
43456
+ ...css2.itemActive
43457
+ } : css2.item, onClick: () => handleSelect(s.name), children: s.label }, s.name)) }) : null
43458
+ ] });
43459
+ }
43460
+ const SECTION_SCROLL_TOP_GAP_PX = 50;
43049
43461
  function MobileLayout$1({
43050
43462
  mode,
43051
43463
  resolved,
@@ -43081,12 +43493,55 @@ function BrowseView({
43081
43493
  onRemoveItem,
43082
43494
  onTryItOn
43083
43495
  }) {
43496
+ const railsAreaRef = reactExports.useRef(null);
43497
+ const sectionRefs = reactExports.useRef(/* @__PURE__ */ new Map());
43498
+ const [activeSectionName, setActiveSectionName] = reactExports.useState(null);
43499
+ const sections = resolved.groups.map((g) => ({
43500
+ name: g.group.name,
43501
+ label: g.group.label
43502
+ }));
43503
+ const recomputeActiveSection = reactExports.useCallback(() => {
43504
+ const container = railsAreaRef.current;
43505
+ if (!container) {
43506
+ return;
43507
+ }
43508
+ const containerTop = container.getBoundingClientRect().top;
43509
+ let active = null;
43510
+ for (const [name2, el] of sectionRefs.current) {
43511
+ if (el.getBoundingClientRect().top >= containerTop - 1) {
43512
+ active = name2;
43513
+ break;
43514
+ }
43515
+ }
43516
+ if (active == null) {
43517
+ const groups = resolved.groups;
43518
+ active = groups.length > 0 ? groups[groups.length - 1].group.name : null;
43519
+ }
43520
+ setActiveSectionName(active);
43521
+ }, [resolved.groups]);
43522
+ reactExports.useLayoutEffect(() => {
43523
+ recomputeActiveSection();
43524
+ }, [recomputeActiveSection, resolved.groups]);
43525
+ const scrollToSection = reactExports.useCallback((name2) => {
43526
+ const container = railsAreaRef.current;
43527
+ const el = sectionRefs.current.get(name2);
43528
+ if (!container || !el) {
43529
+ return;
43530
+ }
43531
+ const delta = el.getBoundingClientRect().top - container.getBoundingClientRect().top;
43532
+ container.scrollBy({
43533
+ top: delta - SECTION_SCROLL_TOP_GAP_PX,
43534
+ behavior: "smooth"
43535
+ });
43536
+ }, []);
43084
43537
  const css2 = useCss((_theme) => ({
43085
43538
  container: {
43086
43539
  display: "flex",
43087
43540
  flexDirection: "column",
43088
43541
  height: "100%",
43089
- width: "100%"
43542
+ width: "100%",
43543
+ // Positioning context for the floating SectionNav pill.
43544
+ position: "relative"
43090
43545
  },
43091
43546
  railsArea: {
43092
43547
  flex: 1,
@@ -43104,7 +43559,14 @@ function BrowseView({
43104
43559
  }
43105
43560
  }));
43106
43561
  return /* @__PURE__ */ jsxs("div", { css: css2.container, children: [
43107
- /* @__PURE__ */ jsx$1("div", { css: css2.railsArea, children: resolved.groups.map((group) => /* @__PURE__ */ jsx$1(CardRail, { group, availabilityByExternalId, onSelectItem, onRemoveItem }, group.group.name)) }),
43562
+ !resolved.isLoading && resolved.groups.length > 0 ? /* @__PURE__ */ jsx$1(SectionNav, { sections, activeName: activeSectionName, onSelect: scrollToSection }) : null,
43563
+ /* @__PURE__ */ jsx$1("div", { ref: railsAreaRef, css: css2.railsArea, onScroll: recomputeActiveSection, children: resolved.groups.map((group) => /* @__PURE__ */ jsx$1("div", { ref: (el) => {
43564
+ if (el) {
43565
+ sectionRefs.current.set(group.group.name, el);
43566
+ } else {
43567
+ sectionRefs.current.delete(group.group.name);
43568
+ }
43569
+ }, children: /* @__PURE__ */ jsx$1(CardRail, { group, availabilityByExternalId, onSelectItem, onRemoveItem }) }, group.group.name)) }),
43108
43570
  resolved.groups.length > 0 ? /* @__PURE__ */ jsx$1("div", { css: css2.bottomBar, children: /* @__PURE__ */ jsx$1(ButtonT, { variant: "brand", t: "fitting_room.try_it_on", onClick: onTryItOn, disabled: selectedCount === 0 }) }) : null
43109
43571
  ] });
43110
43572
  }
@@ -43130,8 +43592,14 @@ function TryOnView({
43130
43592
  reactExports.useEffect(() => {
43131
43593
  function refresh() {
43132
43594
  const el = innerRef.current;
43133
- if (!el) return;
43134
- const maxHeightPx = Number(window.getComputedStyle(el.parentElement).getPropertyValue("max-height").replace("px", ""));
43595
+ if (!el) {
43596
+ return;
43597
+ }
43598
+ const parentEl = el.parentElement;
43599
+ if (!parentEl) {
43600
+ return;
43601
+ }
43602
+ const maxHeightPx = Number(window.getComputedStyle(parentEl).getPropertyValue("max-height").replace("px", ""));
43135
43603
  const heightPx = Math.min(el.clientHeight, maxHeightPx || el.clientHeight);
43136
43604
  setSheetStyle({
43137
43605
  height: `${heightPx}px`
@@ -43167,7 +43635,7 @@ function TryOnView({
43167
43635
  },
43168
43636
  sheetOuter: {
43169
43637
  position: "absolute",
43170
- // 8px gap to either side, matching vto-single's mobile sheet.
43638
+ // 8px gap to either side, matching quick-view's mobile sheet.
43171
43639
  left: "8px",
43172
43640
  right: "8px",
43173
43641
  bottom: 0,
@@ -43208,7 +43676,7 @@ function TryOnView({
43208
43676
  }
43209
43677
  }));
43210
43678
  return /* @__PURE__ */ jsxs("div", { css: css2.container, children: [
43211
- /* @__PURE__ */ jsx$1(AvatarPane, { hasSelection: selectedItems.length > 0, frameUrls, mobileFullscreen: true }),
43679
+ /* @__PURE__ */ jsx$1(AvatarPane, { hasSelection: selectedItems.length > 0, frameUrls, mobileFullscreen: true, controls: /* @__PURE__ */ jsx$1(MobileTuckControl, { canTuck, forceUntuck, onToggleUntuck }) }),
43212
43680
  /* @__PURE__ */ jsx$1(Button, { variant: "base", css: css2.backButton, onClick: onBackToBrowse, "aria-label": "Back to browse", children: /* @__PURE__ */ jsx$1(SvgIconLeftArrow, { css: css2.backArrow }) }),
43213
43681
  /* @__PURE__ */ jsx$1("div", { css: css2.sheetOuter, style: sheetStyle, children: /* @__PURE__ */ jsxs("div", { ref: innerRef, css: css2.sheetInner, style: sheetStyle, children: [
43214
43682
  /* @__PURE__ */ jsxs("div", { css: css2.sheetHandleRow, onTouchStart: sheetTouchStart, children: [
@@ -43219,8 +43687,7 @@ function TryOnView({
43219
43687
  ] }) })
43220
43688
  ] });
43221
43689
  }
43222
- const logger$a = getLogger("overlays/fitting-room/use-vto-requests");
43223
- const NON_PRIORITY_REQUEST_DELAY_MS = 500;
43690
+ const logger$9 = getLogger("use-vto-requests");
43224
43691
  function outfitKey(items) {
43225
43692
  return items.map((i) => `${i.colorway_size_asset_id}:${i.untucked ? 1 : 0}`).sort().join("|");
43226
43693
  }
@@ -43228,21 +43695,26 @@ function useVtoRequests() {
43228
43695
  const [framesByKey, setFramesByKey] = reactExports.useState({});
43229
43696
  const requestedKeysRef = reactExports.useRef(/* @__PURE__ */ new Set());
43230
43697
  const lastPriorityTimeRef = reactExports.useRef(null);
43698
+ const pendingPrefetchTimersRef = reactExports.useRef(/* @__PURE__ */ new Set());
43231
43699
  const [lastError, setLastError] = reactExports.useState(null);
43232
43700
  const clearError = reactExports.useCallback(() => setLastError(null), []);
43233
43701
  const request = reactExports.useCallback((items, priority) => {
43234
- if (items.length === 0) return;
43702
+ if (items.length === 0) {
43703
+ return;
43704
+ }
43235
43705
  const key = outfitKey(items);
43236
- if (requestedKeysRef.current.has(key)) return;
43706
+ if (requestedKeysRef.current.has(key)) {
43707
+ return;
43708
+ }
43237
43709
  const exec = () => {
43238
43710
  requestedKeysRef.current.add(key);
43239
- logger$a.logDebug("Requesting VTO composition", {
43711
+ logger$9.logDebug("Requesting VTO composition", {
43240
43712
  key,
43241
43713
  items,
43242
43714
  priority
43243
43715
  });
43244
43716
  requestVto(items).then((resp) => {
43245
- logger$a.logDebug("VTO frames ready", {
43717
+ logger$9.logDebug("VTO frames ready", {
43246
43718
  key,
43247
43719
  count: resp.frames.length
43248
43720
  });
@@ -43251,7 +43723,7 @@ function useVtoRequests() {
43251
43723
  [key]: resp.frames
43252
43724
  }));
43253
43725
  }).catch((error) => {
43254
- logger$a.logError("VTO request failed", {
43726
+ logger$9.logError("VTO request failed", {
43255
43727
  error,
43256
43728
  items,
43257
43729
  key
@@ -43261,6 +43733,10 @@ function useVtoRequests() {
43261
43733
  });
43262
43734
  };
43263
43735
  if (priority) {
43736
+ for (const timer of pendingPrefetchTimersRef.current) {
43737
+ clearTimeout(timer);
43738
+ }
43739
+ pendingPrefetchTimersRef.current.clear();
43264
43740
  lastPriorityTimeRef.current = Date.now();
43265
43741
  exec();
43266
43742
  return;
@@ -43269,20 +43745,39 @@ function useVtoRequests() {
43269
43745
  let delay = 0;
43270
43746
  if (last) {
43271
43747
  const now = Date.now();
43272
- const minNext = last + NON_PRIORITY_REQUEST_DELAY_MS;
43273
- if (now < minNext) delay = minNext - now;
43748
+ const minNext = last + getStaticData().config.api.vtoPrefetchDelayMs;
43749
+ if (now < minNext) {
43750
+ delay = minNext - now;
43751
+ }
43274
43752
  }
43275
43753
  if (delay > 0) {
43276
- setTimeout(exec, delay);
43754
+ const timer = setTimeout(() => {
43755
+ pendingPrefetchTimersRef.current.delete(timer);
43756
+ exec();
43757
+ }, delay);
43758
+ pendingPrefetchTimersRef.current.add(timer);
43277
43759
  } else {
43278
43760
  exec();
43279
43761
  }
43280
43762
  }, []);
43763
+ reactExports.useEffect(() => {
43764
+ const timers = pendingPrefetchTimersRef.current;
43765
+ return () => {
43766
+ for (const timer of timers) {
43767
+ clearTimeout(timer);
43768
+ }
43769
+ timers.clear();
43770
+ };
43771
+ }, []);
43281
43772
  const framesForOutfit = reactExports.useCallback((items) => {
43282
- if (items.length === 0) return null;
43773
+ if (items.length === 0) {
43774
+ return null;
43775
+ }
43283
43776
  const key = outfitKey(items);
43284
43777
  const frames = framesByKey[key];
43285
- if (!frames || frames.length === 0) return null;
43778
+ if (!frames || frames.length === 0) {
43779
+ return null;
43780
+ }
43286
43781
  const baseUrl2 = getStaticData().config.frames.baseUrl;
43287
43782
  return frames.map((u) => applyFrameBaseUrl(u, baseUrl2));
43288
43783
  }, [framesByKey]);
@@ -43301,12 +43796,14 @@ function toWireItems(items) {
43301
43796
  } : {}
43302
43797
  }));
43303
43798
  }
43304
- const logger$9 = getLogger("overlays/fitting-room");
43799
+ const logger$8 = getLogger("overlays/fitting-room");
43305
43800
  function measureTopOffset() {
43306
43801
  const {
43307
43802
  getOverlayTopOffset
43308
43803
  } = getStaticData();
43309
- if (!getOverlayTopOffset) return 0;
43804
+ if (!getOverlayTopOffset) {
43805
+ return 0;
43806
+ }
43310
43807
  try {
43311
43808
  const offset = getOverlayTopOffset();
43312
43809
  return Number.isFinite(offset) && offset > 0 ? offset : 0;
@@ -43314,7 +43811,9 @@ function measureTopOffset() {
43314
43811
  return 0;
43315
43812
  }
43316
43813
  }
43317
- function FittingRoomOverlay() {
43814
+ function FittingRoomOverlay({
43815
+ preselectExternalId
43816
+ }) {
43318
43817
  const deviceLayout = useMainStore((state) => state.deviceLayout);
43319
43818
  const userIsLoggedIn = useMainStore((state) => state.userIsLoggedIn);
43320
43819
  const userHasAvatar = useMainStore((state) => state.userHasAvatar);
@@ -43330,7 +43829,6 @@ function FittingRoomOverlay() {
43330
43829
  const [forceUntuck, setForceUntuck] = reactExports.useState(false);
43331
43830
  const [lastAddedExternalId, setLastAddedExternalId] = reactExports.useState(null);
43332
43831
  const [mobileMode, setMobileMode] = reactExports.useState("browse");
43333
- const [zoomed, setZoomed] = reactExports.useState(false);
43334
43832
  const {
43335
43833
  snap: sheetSnap,
43336
43834
  setSnap: setSheetSnap,
@@ -43345,13 +43843,17 @@ function FittingRoomOverlay() {
43345
43843
  } = useVtoRequests();
43346
43844
  reactExports.useEffect(() => {
43347
43845
  const savedScrollY = window.scrollY;
43348
- if (savedScrollY > 0) window.scrollTo(0, 0);
43846
+ if (savedScrollY > 0) {
43847
+ window.scrollTo(0, 0);
43848
+ }
43349
43849
  setTopOffset(measureTopOffset());
43350
43850
  const onResize = () => setTopOffset(measureTopOffset());
43351
43851
  window.addEventListener("resize", onResize);
43352
43852
  return () => {
43353
43853
  window.removeEventListener("resize", onResize);
43354
- if (savedScrollY > 0) window.scrollTo(0, savedScrollY);
43854
+ if (savedScrollY > 0) {
43855
+ window.scrollTo(0, savedScrollY);
43856
+ }
43355
43857
  };
43356
43858
  }, []);
43357
43859
  const selectedItems = reactExports.useMemo(() => {
@@ -43364,14 +43866,22 @@ function FittingRoomOverlay() {
43364
43866
  indexed.sort((a, b) => {
43365
43867
  const aOrder = a.item.styleCategoryGroup?.display_order ?? Number.MAX_SAFE_INTEGER;
43366
43868
  const bOrder = b.item.styleCategoryGroup?.display_order ?? Number.MAX_SAFE_INTEGER;
43367
- if (aOrder !== bOrder) return aOrder - bOrder;
43869
+ if (aOrder !== bOrder) {
43870
+ return aOrder - bOrder;
43871
+ }
43368
43872
  return a.idx - b.idx;
43369
43873
  });
43370
43874
  return indexed.map(({
43371
43875
  item
43372
43876
  }) => item);
43373
43877
  }, [resolved.items, selectedExternalIds]);
43374
- const canTuck = reactExports.useMemo(() => selectedItems.some((top) => !!top.styleCategory?.tuckable && selectedItems.some((other) => !!other.styleCategory && !other.styleCategory.tuckable && other.styleCategory.layer_order > top.styleCategory.layer_order)), [selectedItems]);
43878
+ const canTuck = reactExports.useMemo(() => selectedItems.some((top) => {
43879
+ const topCategory = top.styleCategory;
43880
+ if (!topCategory?.tuckable) {
43881
+ return false;
43882
+ }
43883
+ return selectedItems.some((other) => !!other.styleCategory && !other.styleCategory.tuckable && other.styleCategory.layer_order > topCategory.layer_order);
43884
+ }), [selectedItems]);
43375
43885
  const availabilityByExternalId = reactExports.useMemo(() => {
43376
43886
  const out = {};
43377
43887
  for (const item of resolved.items) {
@@ -43380,11 +43890,17 @@ function FittingRoomOverlay() {
43380
43890
  return out;
43381
43891
  }, [resolved, selectedExternalIds]);
43382
43892
  const ensureSizeForItem = reactExports.useCallback((item) => {
43383
- if (item.storage.colorwaySizeAssetId != null) return;
43893
+ if (item.storage.colorwaySizeAssetId != null) {
43894
+ return;
43895
+ }
43384
43896
  const productData = buildVtoProductDataFromResolved(item);
43385
- if (!productData) return;
43897
+ if (!productData) {
43898
+ return;
43899
+ }
43386
43900
  const csa = findRecommendedColorSize(productData, item.storage.color);
43387
- if (!csa) return;
43901
+ if (!csa) {
43902
+ return;
43903
+ }
43388
43904
  const sizeRec = item.loadedProduct?.sizeFitRecommendation;
43389
43905
  const sizeLabel = sizeRec ? getSizeLabelFromSize(sizeRec.recommended_size) : productData.recommendedSizeLabel;
43390
43906
  updateFittingRoomItem(item.externalId, {
@@ -43392,7 +43908,7 @@ function FittingRoomOverlay() {
43392
43908
  size: sizeLabel,
43393
43909
  color: csa.colorLabel
43394
43910
  });
43395
- logger$9.logDebug("Auto-picked recommended size", {
43911
+ logger$8.logDebug("Auto-picked recommended size", {
43396
43912
  externalId: item.externalId,
43397
43913
  sizeLabel,
43398
43914
  csaId: csa.colorwaySizeAssetId
@@ -43400,12 +43916,16 @@ function FittingRoomOverlay() {
43400
43916
  }, [updateFittingRoomItem]);
43401
43917
  const handleSelectItem = reactExports.useCallback((externalId) => {
43402
43918
  const item = resolved.items.find((i) => i.externalId === externalId);
43403
- if (!item) return;
43919
+ if (!item) {
43920
+ return;
43921
+ }
43404
43922
  const isSelected = selectedExternalIds.has(externalId);
43405
43923
  const nextSelected = new Set(selectedExternalIds);
43406
43924
  if (isSelected) {
43407
43925
  nextSelected.delete(externalId);
43408
- if (openAccordionItemId === externalId) setOpenAccordionItemId(null);
43926
+ if (openAccordionItemId === externalId) {
43927
+ setOpenAccordionItemId(null);
43928
+ }
43409
43929
  } else {
43410
43930
  nextSelected.add(externalId);
43411
43931
  ensureSizeForItem(item);
@@ -43419,11 +43939,17 @@ function FittingRoomOverlay() {
43419
43939
  }, [resolved, selectedExternalIds, openAccordionItemId, isMobileLayout, ensureSizeForItem]);
43420
43940
  const handleChangeSize = reactExports.useCallback((externalId, sizeLabel) => {
43421
43941
  const item = resolved.items.find((i) => i.externalId === externalId);
43422
- if (!item) return;
43942
+ if (!item) {
43943
+ return;
43944
+ }
43423
43945
  const productData = buildVtoProductDataFromResolved(item);
43424
- if (!productData) return;
43946
+ if (!productData) {
43947
+ return;
43948
+ }
43425
43949
  const csa = findCsaByLabel(productData, sizeLabel, item.storage.color);
43426
- if (!csa) return;
43950
+ if (!csa) {
43951
+ return;
43952
+ }
43427
43953
  updateFittingRoomItem(externalId, {
43428
43954
  colorwaySizeAssetId: csa.colorwaySizeAssetId,
43429
43955
  size: sizeLabel,
@@ -43435,14 +43961,14 @@ function FittingRoomOverlay() {
43435
43961
  addToCart
43436
43962
  } = getStaticData();
43437
43963
  if (!addToCart) {
43438
- logger$9.logWarn("No addToCart callback configured; skipping", {
43964
+ logger$8.logWarn("No addToCart callback configured; skipping", {
43439
43965
  externalId
43440
43966
  });
43441
43967
  return;
43442
43968
  }
43443
43969
  const item = resolved.items.find((i) => i.externalId === externalId);
43444
43970
  if (!item || !item.storage.size || !item.storage.color) {
43445
- logger$9.logWarn("Cannot add to cart: missing size or color", {
43971
+ logger$8.logWarn("Cannot add to cart: missing size or color", {
43446
43972
  externalId,
43447
43973
  size: item?.storage.size,
43448
43974
  color: item?.storage.color
@@ -43456,7 +43982,7 @@ function FittingRoomOverlay() {
43456
43982
  });
43457
43983
  closeOverlay();
43458
43984
  } catch (error) {
43459
- logger$9.logError("addToCart failed", {
43985
+ logger$8.logError("addToCart failed", {
43460
43986
  error,
43461
43987
  externalId
43462
43988
  });
@@ -43465,17 +43991,18 @@ function FittingRoomOverlay() {
43465
43991
  const handleToggleUntuck = reactExports.useCallback(() => {
43466
43992
  setForceUntuck((prev2) => !prev2);
43467
43993
  }, []);
43468
- const handleToggleZoom = reactExports.useCallback(() => {
43469
- setZoomed((prev2) => !prev2);
43470
- }, []);
43471
43994
  const handleRemoveItem = reactExports.useCallback((externalId) => {
43472
43995
  setSelectedExternalIds((prev2) => {
43473
- if (!prev2.has(externalId)) return prev2;
43996
+ if (!prev2.has(externalId)) {
43997
+ return prev2;
43998
+ }
43474
43999
  const next2 = new Set(prev2);
43475
44000
  next2.delete(externalId);
43476
44001
  return next2;
43477
44002
  });
43478
- if (openAccordionItemId === externalId) setOpenAccordionItemId(null);
44003
+ if (openAccordionItemId === externalId) {
44004
+ setOpenAccordionItemId(null);
44005
+ }
43479
44006
  }, [openAccordionItemId]);
43480
44007
  const handleTryItOn = reactExports.useCallback(() => {
43481
44008
  setMobileMode("try-on");
@@ -43489,10 +44016,25 @@ function FittingRoomOverlay() {
43489
44016
  setOpenAccordionItemId(null);
43490
44017
  }
43491
44018
  }, [openAccordionItemId, selectedExternalIds]);
44019
+ const preselectAppliedRef = reactExports.useRef(false);
44020
+ reactExports.useEffect(() => {
44021
+ if (preselectAppliedRef.current || !preselectExternalId) {
44022
+ return;
44023
+ }
44024
+ if (!resolved.items.some((i) => i.externalId === preselectExternalId)) {
44025
+ return;
44026
+ }
44027
+ preselectAppliedRef.current = true;
44028
+ handleSelectItem(preselectExternalId);
44029
+ }, [preselectExternalId, resolved.items, handleSelectItem]);
43492
44030
  const outfit = reactExports.useMemo(() => buildOutfit(selectedExternalIds, resolved, forceUntuck, lastAddedExternalId), [selectedExternalIds, resolved, forceUntuck, lastAddedExternalId]);
43493
44031
  reactExports.useEffect(() => {
43494
- if (!userIsLoggedIn || !userHasAvatar) return;
43495
- if (outfit.items.length === 0) return;
44032
+ if (!userIsLoggedIn || !userHasAvatar) {
44033
+ return;
44034
+ }
44035
+ if (outfit.items.length === 0) {
44036
+ return;
44037
+ }
43496
44038
  requestVtoComposition(toWireItems(outfit.items), true);
43497
44039
  if (getStaticData().config.features.vtoPrefetch) {
43498
44040
  for (const alt of outfit.alternates) {
@@ -43503,7 +44045,9 @@ function FittingRoomOverlay() {
43503
44045
  const frameUrls = reactExports.useMemo(() => {
43504
44046
  if (outfit.items.length === 0) {
43505
44047
  const bareFrames = userProfile?.avatar_frames;
43506
- if (!bareFrames || bareFrames.length === 0) return null;
44048
+ if (!bareFrames || bareFrames.length === 0) {
44049
+ return null;
44050
+ }
43507
44051
  const baseUrl2 = getStaticData().config.frames.baseUrl;
43508
44052
  return bareFrames.map((u) => applyFrameBaseUrl(u, baseUrl2));
43509
44053
  }
@@ -43527,7 +44071,7 @@ function FittingRoomOverlay() {
43527
44071
  closeOverlay();
43528
44072
  const authManager2 = getAuthManager();
43529
44073
  authManager2.logout().catch((error) => {
43530
- logger$9.logError("Error during logout", {
44074
+ logger$8.logError("Error during logout", {
43531
44075
  error
43532
44076
  });
43533
44077
  });
@@ -43574,29 +44118,6 @@ function FittingRoomOverlay() {
43574
44118
  fontSize: "13px",
43575
44119
  textDecoration: "underline",
43576
44120
  marginTop: "8px"
43577
- },
43578
- snackbar: {
43579
- position: "absolute",
43580
- left: "50%",
43581
- bottom: "24px",
43582
- transform: "translateX(-50%)",
43583
- padding: "12px 20px",
43584
- borderRadius: "24px",
43585
- backgroundColor: theme.color_fg_text,
43586
- color: "#FFFFFF",
43587
- fontSize: "13px",
43588
- display: "flex",
43589
- alignItems: "center",
43590
- gap: "12px",
43591
- zIndex: 10,
43592
- maxWidth: "calc(100% - 32px)"
43593
- },
43594
- snackbarDismiss: {
43595
- background: "none",
43596
- border: "none",
43597
- color: "#FFFFFF",
43598
- fontSize: "16px",
43599
- cursor: "pointer"
43600
44121
  }
43601
44122
  }));
43602
44123
  const isMobileTryOn = isMobileLayout && mobileMode === "try-on";
@@ -43627,15 +44148,12 @@ function FittingRoomOverlay() {
43627
44148
  /* @__PURE__ */ jsx$1(TextT, { variant: "base", css: css2.emptyTagline, t: "landing.description" }),
43628
44149
  /* @__PURE__ */ jsx$1("div", { css: css2.emptyShopNow, children: /* @__PURE__ */ jsx$1(ButtonT, { variant: "primary", t: "fitting_room.shop_now", onClick: handleShopNow }) }),
43629
44150
  userIsLoggedIn ? /* @__PURE__ */ jsx$1(LinkT, { variant: "underline", css: css2.emptySignOut, t: "fitting_room.sign_out", onClick: handleSignOut }) : null
43630
- ] }) }) : isMobileLayout ? /* @__PURE__ */ jsx$1(MobileLayout$1, { mode: mobileMode, resolved, selectedItems, availabilityByExternalId, openAccordionItemId, detailMode, forceUntuck, canTuck, frameUrls, sheetSnap, sheetTouchStart, onSelectItem: handleSelectItem, onRemoveItem: handleRemoveItem, onTryItOn: handleTryItOn, onBackToBrowse: handleBackToBrowse, onOpenAccordionItem: setOpenAccordionItemId, onChangeDetailMode: setDetailMode, onChangeSize: handleChangeSize, onAddToCart: handleAddToCart, onToggleUntuck: handleToggleUntuck }) : /* @__PURE__ */ jsx$1(DesktopLayout$1, { resolved, selectedItems, availabilityByExternalId, openAccordionItemId, detailMode, forceUntuck, canTuck, zoomed, frameUrls, onSelectItem: handleSelectItem, onRemoveItem: handleRemoveItem, onOpenAccordionItem: setOpenAccordionItemId, onChangeDetailMode: setDetailMode, onChangeSize: handleChangeSize, onAddToCart: handleAddToCart, onToggleUntuck: handleToggleUntuck, onToggleZoom: handleToggleZoom, onSignOut: handleSignOut }),
43631
- vtoError ? /* @__PURE__ */ jsxs("div", { css: css2.snackbar, children: [
43632
- /* @__PURE__ */ jsx$1(TextT, { variant: "base", t: "fitting_room.vto_error" }),
43633
- /* @__PURE__ */ jsx$1("button", { css: css2.snackbarDismiss, onClick: clearVtoError, "aria-label": "Dismiss", children: "×" })
43634
- ] }) : null
44151
+ ] }) }) : isMobileLayout ? /* @__PURE__ */ jsx$1(MobileLayout$1, { mode: mobileMode, resolved, selectedItems, availabilityByExternalId, openAccordionItemId, detailMode, forceUntuck, canTuck, frameUrls, sheetSnap, sheetTouchStart, onSelectItem: handleSelectItem, onRemoveItem: handleRemoveItem, onTryItOn: handleTryItOn, onBackToBrowse: handleBackToBrowse, onOpenAccordionItem: setOpenAccordionItemId, onChangeDetailMode: setDetailMode, onChangeSize: handleChangeSize, onAddToCart: handleAddToCart, onToggleUntuck: handleToggleUntuck }) : /* @__PURE__ */ jsx$1(DesktopLayout$1, { resolved, selectedItems, availabilityByExternalId, openAccordionItemId, detailMode, forceUntuck, canTuck, frameUrls, onSelectItem: handleSelectItem, onRemoveItem: handleRemoveItem, onOpenAccordionItem: setOpenAccordionItemId, onChangeDetailMode: setDetailMode, onChangeSize: handleChangeSize, onAddToCart: handleAddToCart, onToggleUntuck: handleToggleUntuck, onSignOut: handleSignOut }),
44152
+ vtoError ? /* @__PURE__ */ jsx$1(Snackbar, { messageKey: "fitting_room.vto_error", onDismiss: clearVtoError }) : null
43635
44153
  ] }) });
43636
44154
  }
43637
44155
  const CONTACT_US_LINK = "mailto:info@thefittingroom.tech?subject=Forgot%20Password%20Assistance";
43638
- const logger$8 = getLogger("forgot-password");
44156
+ const logger$7 = getLogger("forgot-password");
43639
44157
  function ForgotPasswordOverlay({
43640
44158
  returnToOverlay
43641
44159
  }) {
@@ -43701,7 +44219,7 @@ function ForgotPasswordOverlay({
43701
44219
  await authManager2.sendPasswordResetEmail(email2);
43702
44220
  setLinkSent(true);
43703
44221
  } catch (error) {
43704
- logger$8.logError("Error sending password reset email:", {
44222
+ logger$7.logError("Error sending password reset email:", {
43705
44223
  error
43706
44224
  });
43707
44225
  }
@@ -43719,7 +44237,7 @@ function ForgotPasswordOverlay({
43719
44237
  return;
43720
44238
  }
43721
44239
  const email = emailEl.value;
43722
- resetUserPassword(email);
44240
+ void resetUserPassword(email);
43723
44241
  }, [t]);
43724
44242
  const handleBackToSignInClick = reactExports.useCallback(() => {
43725
44243
  openOverlay(OverlayName.SIGN_IN, {
@@ -43920,7 +44438,7 @@ function TfrTitle() {
43920
44438
  }));
43921
44439
  return /* @__PURE__ */ jsx$1("div", { css: css2.container, children: /* @__PURE__ */ jsx$1(SvgTfrName, { css: css2.nameIcon }) });
43922
44440
  }
43923
- const logger$7 = getLogger("sign-in");
44441
+ const logger$6 = getLogger("sign-in");
43924
44442
  function SignInOverlay({
43925
44443
  returnToOverlay
43926
44444
  }) {
@@ -43994,7 +44512,7 @@ function SignInOverlay({
43994
44512
  closeOverlay();
43995
44513
  }
43996
44514
  } catch (error) {
43997
- logger$7.logError("Login failed:", {
44515
+ logger$6.logError("Login failed:", {
43998
44516
  error
43999
44517
  });
44000
44518
  setEmailError(" ");
@@ -44026,7 +44544,7 @@ function SignInOverlay({
44026
44544
  }
44027
44545
  const email = emailEl.value;
44028
44546
  const password = passwordEl.value;
44029
- loginUser(email, password);
44547
+ void loginUser(email, password);
44030
44548
  }, [returnToOverlay, closeOverlay, openOverlay, t]);
44031
44549
  const handleForgotPasswordClick = reactExports.useCallback(() => {
44032
44550
  openOverlay(OverlayName.FORGOT_PASSWORD, {
@@ -44354,15 +44872,11 @@ function FitChart({
44354
44872
  ] })
44355
44873
  ] });
44356
44874
  }
44357
- const NON_PRIORITY_VTO_REQUEST_DELAY_MS = 500;
44358
44875
  const AVATAR_IMAGE_ASPECT_RATIO = 2 / 3;
44359
44876
  const AVATAR_GUTTER_HEIGHT_PX = 100;
44360
44877
  const CONTENT_AREA_WIDTH_PX = 550;
44361
- const logger$6 = getLogger("overlays/vto-single");
44362
- function compositionKey(csaId, untucked) {
44363
- return `${csaId}:${0}`;
44364
- }
44365
- function VtoSingleOverlay() {
44878
+ const logger$5 = getLogger("overlays/quick-view");
44879
+ function QuickViewOverlay() {
44366
44880
  const userIsLoggedIn = useMainStore((state) => state.userIsLoggedIn);
44367
44881
  const userHasAvatar = useMainStore((state) => state.userHasAvatar);
44368
44882
  const userProfile = useMainStore((state) => state.userProfile);
@@ -44374,19 +44888,22 @@ function VtoSingleOverlay() {
44374
44888
  const [selectedSizeLabel, setSelectedSizeLabel] = reactExports.useState(null);
44375
44889
  const [selectedColorLabel, setSelectedColorLabel] = reactExports.useState(null);
44376
44890
  const [modalStyle, setModalStyle] = reactExports.useState({});
44377
- const [framesByKey, setFramesByKey] = reactExports.useState({});
44378
- const requestedKeysRef = reactExports.useRef(/* @__PURE__ */ new Set());
44379
- const lastPriorityVtoRequestTimeRef = reactExports.useRef(null);
44891
+ const {
44892
+ request: vtoRequest,
44893
+ framesForOutfit,
44894
+ lastError: vtoError,
44895
+ clearError: clearVtoError
44896
+ } = useVtoRequests();
44380
44897
  reactExports.useEffect(() => {
44381
44898
  if (!userIsLoggedIn) {
44382
44899
  openOverlay(OverlayName.LANDING, {
44383
- returnToOverlay: OverlayName.VTO_SINGLE
44900
+ returnToOverlay: OverlayName.QUICK_VIEW
44384
44901
  });
44385
44902
  return;
44386
44903
  }
44387
44904
  if (userIsLoggedIn && userHasAvatar === false) {
44388
44905
  openOverlay(OverlayName.GET_APP, {
44389
- returnToOverlay: OverlayName.VTO_SINGLE,
44906
+ returnToOverlay: OverlayName.QUICK_VIEW,
44390
44907
  noAvatar: true
44391
44908
  });
44392
44909
  return;
@@ -44454,7 +44971,7 @@ function VtoSingleOverlay() {
44454
44971
  const sku = csaRec.sku;
44455
44972
  const variant = variants.find((v) => v.sku === sku);
44456
44973
  if (!variant) {
44457
- logger$6.logWarn(`Variant not found for externalId: ${currentProduct.externalId} sizeId: ${sizeId} sku: ${sku}`);
44974
+ logger$5.logWarn(`Variant not found for externalId: ${currentProduct.externalId} sizeId: ${sizeId} sku: ${sku}`);
44458
44975
  continue;
44459
44976
  }
44460
44977
  const colorLabel = variant.color || null;
@@ -44467,7 +44984,7 @@ function VtoSingleOverlay() {
44467
44984
  });
44468
44985
  }
44469
44986
  if (!colors.length) {
44470
- logger$6.logWarn(`No valid colorways found for externalId: ${currentProduct.externalId} sizeId: ${sizeId}`);
44987
+ logger$5.logWarn(`No valid colorways found for externalId: ${currentProduct.externalId} sizeId: ${sizeId}`);
44471
44988
  continue;
44472
44989
  }
44473
44990
  sizes.push({
@@ -44510,7 +45027,7 @@ function VtoSingleOverlay() {
44510
45027
  setSelectedColorLabel(recommendedColorLabel);
44511
45028
  }
44512
45029
  } catch (error) {
44513
- logger$6.logError("Error fetching initial data:", {
45030
+ logger$5.logError("Error fetching initial data:", {
44514
45031
  error
44515
45032
  });
44516
45033
  setVtoProductData(false);
@@ -44521,7 +45038,7 @@ function VtoSingleOverlay() {
44521
45038
  if (vtoProductData !== null) {
44522
45039
  return;
44523
45040
  }
44524
- setupInitialVtoData();
45041
+ void setupInitialVtoData();
44525
45042
  }, [storeProductData, vtoProductData, userProfile]);
44526
45043
  const {
44527
45044
  sizeColorRecord: selectedColorSizeRecord,
@@ -44547,63 +45064,17 @@ function VtoSingleOverlay() {
44547
45064
  availableColorLabels: availableColorLabels2
44548
45065
  };
44549
45066
  }, [vtoProductData, selectedSizeLabel, selectedColorLabel]);
44550
- const requestVto$1 = reactExports.useCallback((sizeColorRecord, priority) => {
44551
- const csaId = sizeColorRecord.colorwaySizeAssetId;
44552
- const key = compositionKey(csaId);
44553
- if (requestedKeysRef.current.has(key)) {
44554
- return;
44555
- }
44556
- function executeRequest() {
44557
- logger$6.timerStart(`requestVto_${sizeColorRecord.sku}`);
44558
- logger$6.logDebug(`{{ts}} - Requesting VTO for sku: ${sizeColorRecord.sku}`, {
44559
- priority,
44560
- sizeColorRecord
44561
- });
44562
- requestedKeysRef.current.add(key);
44563
- requestVto([{
44564
- colorway_size_asset_id: csaId,
44565
- untucked: false
44566
- }]).then((resp) => {
44567
- logger$6.timerEnd(`requestVto_${sizeColorRecord.sku}`);
44568
- logger$6.logTimer(`requestVto_${sizeColorRecord.sku}`, `{{ts}} - VTO data is loaded for sku: ${sizeColorRecord.sku}`);
44569
- setFramesByKey((prev2) => ({
44570
- ...prev2,
44571
- [key]: resp.frames
44572
- }));
44573
- }).catch((error) => {
44574
- logger$6.logError(`Error requesting VTO for sku: ${sizeColorRecord.sku}`, {
44575
- error,
44576
- sizeColorRecord
44577
- });
44578
- requestedKeysRef.current.delete(key);
44579
- });
44580
- }
44581
- {
44582
- let delay = 0;
44583
- if (priority) {
44584
- lastPriorityVtoRequestTimeRef.current = Date.now();
44585
- } else {
44586
- const lastPriorityTime = lastPriorityVtoRequestTimeRef.current;
44587
- if (lastPriorityTime) {
44588
- const now = Date.now();
44589
- const minNextRequestTime = lastPriorityTime + NON_PRIORITY_VTO_REQUEST_DELAY_MS;
44590
- if (now < minNextRequestTime) {
44591
- delay = minNextRequestTime - now;
44592
- }
44593
- }
44594
- }
44595
- if (delay) {
44596
- setTimeout(executeRequest, delay);
44597
- return;
44598
- }
44599
- }
44600
- executeRequest();
44601
- }, []);
45067
+ const requestVto2 = reactExports.useCallback((sizeColorRecord, priority) => {
45068
+ vtoRequest([{
45069
+ colorway_size_asset_id: sizeColorRecord.colorwaySizeAssetId,
45070
+ untucked: false
45071
+ }], priority);
45072
+ }, [vtoRequest]);
44602
45073
  reactExports.useEffect(() => {
44603
45074
  if (selectedColorSizeRecord) {
44604
- requestVto$1(selectedColorSizeRecord, true);
45075
+ requestVto2(selectedColorSizeRecord, true);
44605
45076
  }
44606
- }, [requestVto$1, selectedColorSizeRecord]);
45077
+ }, [requestVto2, selectedColorSizeRecord]);
44607
45078
  reactExports.useEffect(() => {
44608
45079
  if (!getStaticData().config.features.vtoPrefetch) {
44609
45080
  return;
@@ -44614,33 +45085,33 @@ function VtoSingleOverlay() {
44614
45085
  for (const sizeRecord of vtoProductData.sizes) {
44615
45086
  const sizeColorRecord = sizeRecord.colors.find((c) => c.colorLabel === selectedColorLabel) ?? sizeRecord.colors[0];
44616
45087
  if (sizeColorRecord) {
44617
- requestVto$1(sizeColorRecord, false);
45088
+ requestVto2(sizeColorRecord, false);
44618
45089
  }
44619
45090
  }
44620
- }, [requestVto$1, vtoProductData, selectedColorLabel]);
45091
+ }, [requestVto2, vtoProductData, selectedColorLabel]);
44621
45092
  const frameUrls = reactExports.useMemo(() => {
44622
45093
  if (!selectedColorSizeRecord) {
44623
45094
  return null;
44624
45095
  }
44625
- const key = compositionKey(selectedColorSizeRecord.colorwaySizeAssetId);
44626
- const frames = framesByKey[key];
44627
- if (!frames || frames.length === 0) {
45096
+ const rewritten = framesForOutfit([{
45097
+ colorway_size_asset_id: selectedColorSizeRecord.colorwaySizeAssetId,
45098
+ untucked: false
45099
+ }]);
45100
+ if (!rewritten) {
44628
45101
  return null;
44629
45102
  }
44630
- logger$6.logDebug(`{{ts}} - Displaying VTO for sku: ${selectedColorSizeRecord.sku}`);
44631
- const baseUrl2 = getStaticData().config.frames.baseUrl;
44632
- const rewritten = frames.map((u) => applyFrameBaseUrl(u, baseUrl2));
45103
+ logger$5.logDebug(`{{ts}} - Displaying VTO for sku: ${selectedColorSizeRecord.sku}`);
44633
45104
  rewritten.forEach((url) => {
44634
45105
  const img = new Image();
44635
45106
  img.src = url;
44636
45107
  });
44637
45108
  return rewritten;
44638
- }, [selectedColorSizeRecord, framesByKey]);
45109
+ }, [selectedColorSizeRecord, framesForOutfit]);
44639
45110
  const handleSignOutClick = reactExports.useCallback(() => {
44640
45111
  closeOverlay();
44641
45112
  const authManager2 = getAuthManager();
44642
45113
  authManager2.logout().catch((error) => {
44643
- logger$6.logError("Error during logout:", {
45114
+ logger$5.logError("Error during logout:", {
44644
45115
  error
44645
45116
  });
44646
45117
  });
@@ -44662,7 +45133,7 @@ function VtoSingleOverlay() {
44662
45133
  color: selectedColorLabel
44663
45134
  });
44664
45135
  } catch (error) {
44665
- logger$6.logError("Error adding to cart:", {
45136
+ logger$5.logError("Error adding to cart:", {
44666
45137
  error
44667
45138
  });
44668
45139
  }
@@ -44679,7 +45150,10 @@ function VtoSingleOverlay() {
44679
45150
  } else {
44680
45151
  Layout = DesktopLayout;
44681
45152
  }
44682
- return /* @__PURE__ */ jsx$1(SidecarModalFrame, { onRequestClose: closeOverlay, contentStyle: modalStyle, children: /* @__PURE__ */ jsx$1(Layout, { loadedProductData: vtoProductData, selectedColorSizeRecord, availableColorLabels, selectedColorLabel, selectedSizeLabel, frameUrls, setModalStyle, onClose: closeOverlay, onChangeColor: setSelectedColorLabel, onChangeSize: setSelectedSizeLabel, onAddToCart: handleAddToCartClick, onSignOut: handleSignOutClick }) });
45153
+ return /* @__PURE__ */ jsxs(SidecarModalFrame, { onRequestClose: closeOverlay, contentStyle: modalStyle, children: [
45154
+ /* @__PURE__ */ jsx$1(Layout, { loadedProductData: vtoProductData, selectedColorSizeRecord, availableColorLabels, selectedColorLabel, selectedSizeLabel, frameUrls, setModalStyle, onClose: closeOverlay, onChangeColor: setSelectedColorLabel, onChangeSize: setSelectedSizeLabel, onAddToCart: handleAddToCartClick, onSignOut: handleSignOutClick }),
45155
+ vtoError ? /* @__PURE__ */ jsx$1(Snackbar, { messageKey: "quick-view.vto_error", onDismiss: clearVtoError }) : null
45156
+ ] });
44683
45157
  }
44684
45158
  function NoFitLayout({
44685
45159
  onClose,
@@ -44711,14 +45185,59 @@ function NoFitLayout({
44711
45185
  }
44712
45186
  }));
44713
45187
  return /* @__PURE__ */ jsxs("div", { css: css2.mainContainer, children: [
44714
- /* @__PURE__ */ jsx$1("div", { css: css2.titlebarContainer, children: /* @__PURE__ */ jsx$1(ModalTitlebar, { title: t("try_it_on"), onCloseClick: onClose }) }),
45188
+ /* @__PURE__ */ jsx$1("div", { css: css2.titlebarContainer, children: /* @__PURE__ */ jsx$1(ModalTitlebar, { title: t("quick-view.title"), onCloseClick: onClose }) }),
44715
45189
  /* @__PURE__ */ jsxs("div", { css: css2.contentContainer, children: [
44716
45190
  /* @__PURE__ */ jsx$1("div", { children: " " }),
44717
- /* @__PURE__ */ jsx$1("div", { css: css2.messageContainer, children: /* @__PURE__ */ jsx$1(TextT, { variant: "base", t: "vto-single.no_recommendation" }) }),
45191
+ /* @__PURE__ */ jsx$1("div", { css: css2.messageContainer, children: /* @__PURE__ */ jsx$1(TextT, { variant: "base", t: "quick-view.no_recommendation" }) }),
44718
45192
  /* @__PURE__ */ jsx$1("div", { css: css2.footerContainer, children: /* @__PURE__ */ jsx$1(Footer, { onSignOutClick: onSignOut }) })
44719
45193
  ] })
44720
45194
  ] });
44721
45195
  }
45196
+ function FittingRoomToggleButton() {
45197
+ const {
45198
+ currentProduct
45199
+ } = getStaticData();
45200
+ const productId = currentProduct?.externalId ?? null;
45201
+ const isInFittingRoom = useMainStore((state) => productId == null ? false : state.fittingRoom.some((item) => item.externalId === productId));
45202
+ const css2 = useCss((theme) => ({
45203
+ button: {
45204
+ marginTop: "10px",
45205
+ width: "100%",
45206
+ display: "flex",
45207
+ alignItems: "center",
45208
+ justifyContent: "center",
45209
+ gap: "10px",
45210
+ padding: "13px",
45211
+ backgroundColor: "#FFFFFF",
45212
+ border: `1px solid ${theme.color_fg_text}`,
45213
+ borderRadius: "30px",
45214
+ cursor: "pointer"
45215
+ },
45216
+ icon: {
45217
+ color: theme.color_fg_text,
45218
+ width: "20px",
45219
+ height: "20px"
45220
+ },
45221
+ text: {
45222
+ fontSize: "14px",
45223
+ textTransform: "uppercase"
45224
+ }
45225
+ }));
45226
+ if (productId == null) {
45227
+ return null;
45228
+ }
45229
+ const handleClick = () => {
45230
+ toggleFittingRoomItem(productId, currentProduct?.handle ?? null, true).catch((error) => {
45231
+ logger$5.logError("toggleFittingRoomItem failed", {
45232
+ error
45233
+ });
45234
+ });
45235
+ };
45236
+ return /* @__PURE__ */ jsxs("button", { type: "button", onClick: handleClick, css: css2.button, children: [
45237
+ /* @__PURE__ */ jsx$1(SvgFittingRoomIcon, { css: css2.icon }),
45238
+ /* @__PURE__ */ jsx$1(TextT, { variant: "base", css: css2.text, t: isInFittingRoom ? "added_to_fitting_room" : "add_to_fitting_room" })
45239
+ ] });
45240
+ }
44722
45241
  function MobileLayout({
44723
45242
  loadedProductData,
44724
45243
  selectedColorSizeRecord,
@@ -44824,7 +45343,11 @@ function MobileLayout({
44824
45343
  if (!bottomFrameInnerEl) {
44825
45344
  return;
44826
45345
  }
44827
- const maxHeightPx = Number(window.getComputedStyle(bottomFrameInnerEl.parentElement).getPropertyValue("max-height").replace("px", ""));
45346
+ const parentEl = bottomFrameInnerEl.parentElement;
45347
+ if (!parentEl) {
45348
+ return;
45349
+ }
45350
+ const maxHeightPx = Number(window.getComputedStyle(parentEl).getPropertyValue("max-height").replace("px", ""));
44828
45351
  const heightPx = Math.min(bottomFrameInnerEl.clientHeight, maxHeightPx);
44829
45352
  const bottomFrameStyle = {
44830
45353
  height: `${heightPx}px`
@@ -44929,8 +45452,11 @@ function MobileContentExpanded({
44929
45452
  /* @__PURE__ */ jsx$1("div", { css: css2.sizeSelectorContainer, children: /* @__PURE__ */ jsx$1(SizeSelector, { loadedProductData, selectedSizeLabel, onChangeSize }) }),
44930
45453
  /* @__PURE__ */ jsx$1("div", { css: css2.itemFitTextContainer, children: /* @__PURE__ */ jsx$1(ItemFitText, { loadedProductData }) }),
44931
45454
  /* @__PURE__ */ jsx$1("div", { css: css2.itemFitDetailsContainer, children: /* @__PURE__ */ jsx$1(ItemFitDetails, { loadedProductData, selectedSizeLabel }) }),
44932
- /* @__PURE__ */ jsx$1("div", { css: css2.buttonContainer, children: /* @__PURE__ */ jsx$1(AddToCartButton, { onClick: onAddToCart }) }),
44933
- /* @__PURE__ */ jsx$1("div", { css: css2.productDetailsContainer, children: /* @__PURE__ */ jsx$1(LinkT, { variant: "base", css: css2.productDetailsText, t: "vto-single.view_product_details", onClick: handleProductDetailsClick }) })
45455
+ /* @__PURE__ */ jsxs("div", { css: css2.buttonContainer, children: [
45456
+ /* @__PURE__ */ jsx$1(AddToCartButton, { onClick: onAddToCart }),
45457
+ /* @__PURE__ */ jsx$1(FittingRoomToggleButton, {})
45458
+ ] }),
45459
+ /* @__PURE__ */ jsx$1("div", { css: css2.productDetailsContainer, children: /* @__PURE__ */ jsx$1(LinkT, { variant: "base", css: css2.productDetailsText, t: "quick-view.view_product_details", onClick: handleProductDetailsClick }) })
44934
45460
  ] });
44935
45461
  }
44936
45462
  function MobileContentFull({
@@ -45025,8 +45551,11 @@ function MobileContentFull({
45025
45551
  ] }),
45026
45552
  fitChartNode,
45027
45553
  /* @__PURE__ */ jsx$1("div", { css: css2.colorSelectorContainer, children: /* @__PURE__ */ jsx$1(ColorSelector, { availableColorLabels, selectedColorLabel, onChangeColor }) }),
45028
- /* @__PURE__ */ jsx$1("div", { css: css2.buttonContainer, children: /* @__PURE__ */ jsx$1(AddToCartButton, { onClick: onAddToCart }) }),
45029
- /* @__PURE__ */ jsx$1("div", { css: css2.productDetailsContainer, children: /* @__PURE__ */ jsx$1(LinkT, { variant: "base", css: css2.productDetailsText, t: "vto-single.hide_product_details", onClick: handleProductDetailsClick }) }),
45554
+ /* @__PURE__ */ jsxs("div", { css: css2.buttonContainer, children: [
45555
+ /* @__PURE__ */ jsx$1(AddToCartButton, { onClick: onAddToCart }),
45556
+ /* @__PURE__ */ jsx$1(FittingRoomToggleButton, {})
45557
+ ] }),
45558
+ /* @__PURE__ */ jsx$1("div", { css: css2.productDetailsContainer, children: /* @__PURE__ */ jsx$1(LinkT, { variant: "base", css: css2.productDetailsText, t: "quick-view.hide_product_details", onClick: handleProductDetailsClick }) }),
45030
45559
  /* @__PURE__ */ jsx$1("div", { css: css2.priceContainer, children: /* @__PURE__ */ jsx$1(Text, { variant: "base", css: css2.priceText, children: selectedColorSizeRecord.priceFormatted }) }),
45031
45560
  /* @__PURE__ */ jsx$1("div", { css: css2.productDescriptionContainer, children: /* @__PURE__ */ jsx$1(ProductDescriptionText, { loadedProductData }) }),
45032
45561
  /* @__PURE__ */ jsx$1("div", { css: css2.footerContainer, children: /* @__PURE__ */ jsx$1(Footer, { onSignOutClick: onSignOut }) })
@@ -45142,7 +45671,7 @@ function DesktopLayout({
45142
45671
  return /* @__PURE__ */ jsxs("div", { css: css2.mainContainer, children: [
45143
45672
  /* @__PURE__ */ jsx$1(Avatar, { frameUrls, setModalStyle }),
45144
45673
  /* @__PURE__ */ jsxs("div", { css: css2.rightContainer, children: [
45145
- /* @__PURE__ */ jsx$1(ModalTitlebar, { title: t("try_it_on"), onCloseClick: onClose }),
45674
+ /* @__PURE__ */ jsx$1(ModalTitlebar, { title: t("quick-view.title"), onCloseClick: onClose }),
45146
45675
  /* @__PURE__ */ jsxs("div", { css: css2.contentContainer, children: [
45147
45676
  /* @__PURE__ */ jsx$1("div", { css: css2.productNameContainer, children: /* @__PURE__ */ jsx$1(Text, { variant: "brand", css: css2.productNameText, children: loadedProductData.productName }) }),
45148
45677
  /* @__PURE__ */ jsx$1("div", { css: css2.priceContainer, children: /* @__PURE__ */ jsx$1(Text, { variant: "base", css: css2.priceText, children: selectedColorSizeRecord.priceFormatted }) }),
@@ -45158,7 +45687,10 @@ function DesktopLayout({
45158
45687
  /* @__PURE__ */ jsx$1("div", { css: css2.itemFitDetailsContainer, children: /* @__PURE__ */ jsx$1(ItemFitDetails, { loadedProductData, selectedSizeLabel }) })
45159
45688
  ] }),
45160
45689
  fitChartNode,
45161
- /* @__PURE__ */ jsx$1("div", { css: css2.buttonContainer, children: /* @__PURE__ */ jsx$1(AddToCartButton, { onClick: onAddToCart }) }),
45690
+ /* @__PURE__ */ jsxs("div", { css: css2.buttonContainer, children: [
45691
+ /* @__PURE__ */ jsx$1(AddToCartButton, { onClick: onAddToCart }),
45692
+ /* @__PURE__ */ jsx$1(FittingRoomToggleButton, {})
45693
+ ] }),
45162
45694
  /* @__PURE__ */ jsx$1("div", { css: css2.descriptionContainer, children: /* @__PURE__ */ jsx$1(ProductDescriptionText, { loadedProductData }) })
45163
45695
  ] }),
45164
45696
  /* @__PURE__ */ jsx$1(Footer, { onSignOutClick: onSignOut })
@@ -45178,10 +45710,37 @@ function Avatar({
45178
45710
  bottomContainerStyle: {}
45179
45711
  });
45180
45712
  const [selectedFrameIndex, setSelectedFrameIndex] = reactExports.useState(null);
45713
+ const [zoomOpen, setZoomOpen] = reactExports.useState(false);
45181
45714
  const css2 = useCss((theme) => ({
45182
45715
  topContainer: {
45183
45716
  flex: "none",
45184
- height: "100%"
45717
+ height: "100%",
45718
+ // Positioning context for the zoom pill overlaid on the avatar.
45719
+ position: "relative"
45720
+ },
45721
+ zoomPill: {
45722
+ position: "absolute",
45723
+ // Bottom-right of the avatar image, clear of the slider gutter below it.
45724
+ bottom: `${AVATAR_GUTTER_HEIGHT_PX + 16}px`,
45725
+ right: "16px",
45726
+ display: "inline-flex",
45727
+ alignItems: "center",
45728
+ gap: "8px",
45729
+ padding: "8px 16px",
45730
+ borderRadius: "24px",
45731
+ backgroundColor: "rgba(255, 255, 255, 0.95)",
45732
+ border: `1px solid ${theme.color_fg_text}`,
45733
+ fontSize: "12px",
45734
+ fontWeight: "500",
45735
+ letterSpacing: "0.5px",
45736
+ textTransform: "uppercase",
45737
+ cursor: "pointer",
45738
+ zIndex: 2
45739
+ },
45740
+ zoomPillIcon: {
45741
+ width: "14px",
45742
+ height: "14px",
45743
+ flex: "none"
45185
45744
  },
45186
45745
  bottomContainer: {
45187
45746
  position: "absolute",
@@ -45295,10 +45854,15 @@ function Avatar({
45295
45854
  }, [isMobileLayout]);
45296
45855
  const isReady = !!frameUrls && selectedFrameIndex != null;
45297
45856
  return /* @__PURE__ */ jsxs("div", { css: css2.topContainer, style: layoutData.topContainerStyle, children: [
45298
- /* @__PURE__ */ jsx$1(AvatarFrameViewer, { frameUrls, selectedFrameIndex, setSelectedFrameIndex, imageContainerStyle: layoutData.imageContainerStyle, imageStyle: layoutData.imageStyle, loadingT: "vto-single.avatar_loading" }),
45299
- isReady ? /* @__PURE__ */ jsx$1("div", { css: css2.bottomContainer, style: layoutData.bottomContainerStyle, children: isMobileLayout ? /* @__PURE__ */ jsx$1(Fragment, { children: " " }) : /* @__PURE__ */ jsxs(Fragment, { children: [
45857
+ /* @__PURE__ */ jsx$1(AvatarFrameViewer, { frameUrls, selectedFrameIndex, setSelectedFrameIndex, imageContainerStyle: layoutData.imageContainerStyle, imageStyle: layoutData.imageStyle, loadingT: "quick-view.avatar_loading" }),
45858
+ isReady && !isMobileLayout ? /* @__PURE__ */ jsxs(Button, { variant: "base", css: css2.zoomPill, onClick: () => setZoomOpen(true), children: [
45859
+ /* @__PURE__ */ jsx$1(SvgIconZoom, { css: css2.zoomPillIcon }),
45860
+ /* @__PURE__ */ jsx$1(TextT, { variant: "base", t: "quick-view.zoom_in" })
45861
+ ] }) : null,
45862
+ zoomOpen && frameUrls && frameUrls.length > 0 ? /* @__PURE__ */ jsx$1(ZoomModal, { frameUrls, selectedFrameIndex, setSelectedFrameIndex, onClose: () => setZoomOpen(false) }) : null,
45863
+ frameUrls && selectedFrameIndex != null ? /* @__PURE__ */ jsx$1("div", { css: css2.bottomContainer, style: layoutData.bottomContainerStyle, children: isMobileLayout ? /* @__PURE__ */ jsx$1(Fragment, { children: " " }) : /* @__PURE__ */ jsxs(Fragment, { children: [
45300
45864
  /* @__PURE__ */ jsx$1("input", { type: "range", min: 0, max: frameUrls.length - 1, step: 1, value: selectedFrameIndex, onChange: (e) => setSelectedFrameIndex(Number(e.target.value)), css: css2.sliderInput }),
45301
- /* @__PURE__ */ jsx$1(TextT, { variant: "base", t: "vto-single.slide_to_rotate", css: css2.sliderText })
45865
+ /* @__PURE__ */ jsx$1(TextT, { variant: "base", t: "quick-view.slide_to_rotate", css: css2.sliderText })
45302
45866
  ] }) }) : null
45303
45867
  ] });
45304
45868
  }
@@ -45353,7 +45917,7 @@ function ColorSelector({
45353
45917
  return null;
45354
45918
  }
45355
45919
  return /* @__PURE__ */ jsx$1("div", { css: css2.colorContainer, children: /* @__PURE__ */ jsxs("label", { children: [
45356
- /* @__PURE__ */ jsx$1(TextT, { variant: "base", css: css2.colorLabelText, t: "vto-single.color_label" }),
45920
+ /* @__PURE__ */ jsx$1(TextT, { variant: "base", css: css2.colorLabelText, t: "quick-view.color_label" }),
45357
45921
  /* @__PURE__ */ jsx$1("select", { value: selectedColorLabel ?? "", onChange: handleColorSelectChange, css: css2.colorSelect, children: availableColorLabels.map((colorLabel) => /* @__PURE__ */ jsx$1("option", { value: colorLabel, children: colorLabel }, colorLabel)) })
45358
45922
  ] }) });
45359
45923
  }
@@ -45400,11 +45964,11 @@ function Footer({
45400
45964
  }
45401
45965
  }));
45402
45966
  return /* @__PURE__ */ jsxs("div", { css: css2.container, children: [
45403
- /* @__PURE__ */ jsx$1(LinkT, { variant: "underline", css: css2.signOutLink, onClick: onSignOutClick, t: "vto-single.sign_out" }),
45967
+ /* @__PURE__ */ jsx$1(LinkT, { variant: "underline", css: css2.signOutLink, onClick: onSignOutClick, t: "quick-view.sign_out" }),
45404
45968
  /* @__PURE__ */ jsx$1(SvgTfrName, { css: css2.tfrIcon })
45405
45969
  ] });
45406
45970
  }
45407
- const logger$5 = getLogger("widgets/add-to-fitting-room-compact");
45971
+ const logger$4 = getLogger("widgets/add-to-fitting-room-compact");
45408
45972
  function AddToFittingRoomCompactWidget({
45409
45973
  attributes
45410
45974
  }) {
@@ -45450,7 +46014,7 @@ function AddToFittingRoomCompactWidget({
45450
46014
  }
45451
46015
  const handleClick = () => {
45452
46016
  toggleFittingRoomItem(productId, productHandle, isPdp).catch((error) => {
45453
- logger$5.logError("toggleFittingRoomItem failed", {
46017
+ logger$4.logError("toggleFittingRoomItem failed", {
45454
46018
  error
45455
46019
  });
45456
46020
  });
@@ -45458,64 +46022,8 @@ function AddToFittingRoomCompactWidget({
45458
46022
  const ariaLabel = t(isInFittingRoom ? "added_to_fitting_room" : "add_to_fitting_room");
45459
46023
  return /* @__PURE__ */ jsx$1("button", { type: "button", onClick: handleClick, css: [css2.button, isInFittingRoom && css2.buttonAdded, "", ""], "aria-label": ariaLabel, "aria-pressed": isInFittingRoom, children: /* @__PURE__ */ jsx$1(SvgFittingRoomIcon, { css: [css2.icon, isInFittingRoom && css2.iconAdded, "", ""] }) });
45460
46024
  }
45461
- const logger$4 = getLogger("widgets/add-to-fitting-room");
45462
- function AddToFittingRoomWidget({
45463
- attributes
45464
- }) {
45465
- const {
45466
- currentProduct
45467
- } = getStaticData();
45468
- const attrProductId = attributes["product-id"];
45469
- const attrProductHandle = attributes["product-handle"];
45470
- const productId = attrProductId || currentProduct?.externalId || null;
45471
- const isPdp = productId != null && productId === currentProduct?.externalId;
45472
- const productHandle = attrProductHandle || (isPdp ? currentProduct?.handle ?? null : null);
45473
- const isInFittingRoom = useMainStore((state) => productId == null ? false : state.fittingRoom.some((item) => item.externalId === productId));
45474
- const css2 = useCss((theme) => ({
45475
- button: {
45476
- marginTop: "10px",
45477
- marginBottom: "10px",
45478
- width: "100%",
45479
- maxWidth: "440px",
45480
- display: "flex",
45481
- alignItems: "center",
45482
- justifyContent: "center",
45483
- gap: "10px",
45484
- padding: "13px",
45485
- backgroundColor: "white",
45486
- borderWidth: "1px",
45487
- borderColor: theme.color_fg_text,
45488
- borderStyle: "solid",
45489
- borderRadius: "30px",
45490
- cursor: "pointer"
45491
- },
45492
- icon: {
45493
- color: theme.color_fg_text,
45494
- width: "20px",
45495
- height: "20px"
45496
- },
45497
- text: {
45498
- fontSize: "14px",
45499
- textTransform: "uppercase"
45500
- }
45501
- }));
45502
- if (productId == null) {
45503
- return null;
45504
- }
45505
- const handleClick = () => {
45506
- toggleFittingRoomItem(productId, productHandle, isPdp).catch((error) => {
45507
- logger$4.logError("toggleFittingRoomItem failed", {
45508
- error
45509
- });
45510
- });
45511
- };
45512
- return /* @__PURE__ */ jsxs("button", { type: "button", onClick: handleClick, css: css2.button, children: [
45513
- /* @__PURE__ */ jsx$1(SvgFittingRoomIcon, { css: css2.icon }),
45514
- /* @__PURE__ */ jsx$1(TextT, { variant: "base", css: css2.text, t: isInFittingRoom ? "added_to_fitting_room" : "add_to_fitting_room" })
45515
- ] });
45516
- }
45517
46025
  const logger$3 = getLogger("widgets/fitting-room-icon");
45518
- function FittingRoomIconWidget({}) {
46026
+ function FittingRoomIconWidget(_props) {
45519
46027
  const {
45520
46028
  t
45521
46029
  } = useTranslation();
@@ -45577,7 +46085,7 @@ function FittingRoomIconWidget({}) {
45577
46085
  ] });
45578
46086
  }
45579
46087
  const logger$2 = getLogger("widgets/fitting-room");
45580
- function FittingRoomWidget({}) {
46088
+ function FittingRoomWidget(_props) {
45581
46089
  const count = useMainStore((state) => state.fittingRoom.length);
45582
46090
  const openOverlay = useMainStore((state) => state.openOverlay);
45583
46091
  const css2 = useCss((theme) => ({
@@ -45634,11 +46142,11 @@ function FittingRoomWidget({}) {
45634
46142
  ] });
45635
46143
  }
45636
46144
  const logger$1 = getLogger("size-rec");
45637
- function SizeRecWidget({}) {
46145
+ function SizeRecWidget(_props) {
45638
46146
  const openOverlay = useMainStore((state) => state.openOverlay);
45639
46147
  const openedOverlays = useMainStore((state) => state.openedOverlays);
45640
46148
  const storeProductData = useMainStore((state) => state.productData);
45641
- const hasOpenedVtoSingleOverlay = openedOverlays.includes(OverlayName.VTO_SINGLE);
46149
+ const hasOpenedQuickViewOverlay = openedOverlays.includes(OverlayName.QUICK_VIEW);
45642
46150
  const sizeRecommendationRecord = reactExports.useMemo(() => {
45643
46151
  const {
45644
46152
  currentProduct
@@ -45662,9 +46170,9 @@ function SizeRecWidget({}) {
45662
46170
  return productData.sizeFitRecommendation || null;
45663
46171
  }, [storeProductData]);
45664
46172
  const handleLinkClick = reactExports.useCallback(() => {
45665
- openOverlay(OverlayName.VTO_SINGLE);
46173
+ openOverlay(OverlayName.QUICK_VIEW);
45666
46174
  }, [openOverlay]);
45667
- if (!sizeRecommendationRecord || !hasOpenedVtoSingleOverlay) {
46175
+ if (!sizeRecommendationRecord || !hasOpenedQuickViewOverlay) {
45668
46176
  return null;
45669
46177
  }
45670
46178
  const sizeLabel = getSizeLabelFromSize(sizeRecommendationRecord.recommended_size);
@@ -45676,8 +46184,11 @@ function SizeRecWidget({}) {
45676
46184
  } });
45677
46185
  }
45678
46186
  const logger = getLogger("widgets/vto-button");
45679
- function VtoButtonWidget({}) {
46187
+ function VtoButtonWidget(_props) {
45680
46188
  const openOverlay = useMainStore((state) => state.openOverlay);
46189
+ const currentProduct = getStaticData().currentProduct ?? null;
46190
+ const currentProductId = currentProduct?.externalId ?? null;
46191
+ const hasOtherFittingRoomItems = useMainStore((state) => state.fittingRoom.some((item) => item.externalId !== currentProductId));
45681
46192
  const css2 = useCss((theme) => ({
45682
46193
  button: {
45683
46194
  marginTop: "10px",
@@ -45704,13 +46215,29 @@ function VtoButtonWidget({}) {
45704
46215
  textTransform: "uppercase"
45705
46216
  }
45706
46217
  }));
45707
- const openVto = () => {
45708
- logger.logDebug("{{ts}} - Opening VTO overlay");
45709
- openOverlay(OverlayName.VTO_SINGLE);
46218
+ const handleClick = () => {
46219
+ if (!hasOtherFittingRoomItems) {
46220
+ logger.logDebug("{{ts}} - Opening quick-view overlay");
46221
+ openOverlay(OverlayName.QUICK_VIEW);
46222
+ return;
46223
+ }
46224
+ logger.logDebug("{{ts}} - Opening fitting-room overlay");
46225
+ if (!currentProductId) {
46226
+ openOverlay(OverlayName.FITTING_ROOM);
46227
+ return;
46228
+ }
46229
+ ensureFittingRoomItem(currentProductId, currentProduct?.handle ?? null, true).catch((error) => {
46230
+ logger.logError("Failed to add current product to fitting room", {
46231
+ error
46232
+ });
46233
+ });
46234
+ openOverlay(OverlayName.FITTING_ROOM, {
46235
+ preselectExternalId: currentProductId
46236
+ });
45710
46237
  };
45711
- return /* @__PURE__ */ jsxs("button", { type: "button", onClick: openVto, css: css2.button, children: [
46238
+ return /* @__PURE__ */ jsxs("button", { type: "button", onClick: handleClick, css: css2.button, children: [
45712
46239
  /* @__PURE__ */ jsx$1(SvgTfrIcon, { css: css2.icon }),
45713
- /* @__PURE__ */ jsx$1(TextT, { variant: "base", css: css2.text, t: "try_it_on" })
46240
+ /* @__PURE__ */ jsx$1(TextT, { variant: "base", css: css2.text, t: hasOtherFittingRoomItems ? "try_it_on" : "quick-view.title" })
45714
46241
  ] });
45715
46242
  }
45716
46243
  var DeviceLayout = /* @__PURE__ */ ((DeviceLayout2) => {
@@ -45775,10 +46302,6 @@ function _init$1() {
45775
46302
  });
45776
46303
  }
45777
46304
  const WIDGETS = {
45778
- [
45779
- "add-to-fitting-room"
45780
- /* ADD_TO_FITTING_ROOM */
45781
- ]: AddToFittingRoomWidget,
45782
46305
  [
45783
46306
  "add-to-fitting-room-compact"
45784
46307
  /* ADD_TO_FITTING_ROOM_COMPACT */
@@ -45806,7 +46329,7 @@ var OverlayName = /* @__PURE__ */ ((OverlayName2) => {
45806
46329
  OverlayName2["GET_APP"] = "get-app";
45807
46330
  OverlayName2["LANDING"] = "landing";
45808
46331
  OverlayName2["SIGN_IN"] = "sign-in";
45809
- OverlayName2["VTO_SINGLE"] = "vto-single";
46332
+ OverlayName2["QUICK_VIEW"] = "quick-view";
45810
46333
  return OverlayName2;
45811
46334
  })(OverlayName || {});
45812
46335
  const OVERLAYS = {
@@ -45831,9 +46354,9 @@ const OVERLAYS = {
45831
46354
  /* SIGN_IN */
45832
46355
  ]: SignInOverlay,
45833
46356
  [
45834
- "vto-single"
45835
- /* VTO_SINGLE */
45836
- ]: VtoSingleOverlay
46357
+ "quick-view"
46358
+ /* QUICK_VIEW */
46359
+ ]: QuickViewOverlay
45837
46360
  };
45838
46361
  let staticData = null;
45839
46362
  function _init(initStaticData) {
@@ -45968,6 +46491,7 @@ function Widget({
45968
46491
  var EnvName = /* @__PURE__ */ ((EnvName2) => {
45969
46492
  EnvName2["DEVELOPMENT"] = "development";
45970
46493
  EnvName2["PRODUCTION"] = "production";
46494
+ EnvName2["DEMO"] = "demo";
45971
46495
  EnvName2["LOCAL"] = "local";
45972
46496
  return EnvName2;
45973
46497
  })(EnvName || {});
@@ -45977,9 +46501,9 @@ const SHARED_CONFIG = {
45977
46501
  appGooglePlayUrl: "https://play.google.com/store/apps/details?id=com.thefittingroom.marketplace"
45978
46502
  },
45979
46503
  build: {
45980
- version: `${"5.0.22"}`,
45981
- commitHash: `${"8a06d35"}`,
45982
- date: `${"2026-05-17T15:31:13.958Z"}`
46504
+ version: `${"5.0.24"}`,
46505
+ commitHash: `${"931e0b2"}`,
46506
+ date: `${"2026-05-19T21:17:10.057Z"}`
45983
46507
  }
45984
46508
  };
45985
46509
  const CONFIGS = {
@@ -45998,7 +46522,8 @@ const CONFIGS = {
45998
46522
  },
45999
46523
  api: {
46000
46524
  baseUrl: "https://tfr.dev.thefittingroom.xyz",
46001
- vtoTimeoutMs: 12e4
46525
+ vtoTimeoutMs: 12e4,
46526
+ vtoPrefetchDelayMs: 3e3
46002
46527
  },
46003
46528
  asset: {
46004
46529
  baseUrl: "https://assets.dev.thefittingroom.xyz/shop-sdk/assets/v5"
@@ -46007,7 +46532,7 @@ const CONFIGS = {
46007
46532
  baseUrl: "https://assets.dev.thefittingroom.xyz"
46008
46533
  },
46009
46534
  features: {
46010
- vtoPrefetch: false
46535
+ vtoPrefetch: true
46011
46536
  },
46012
46537
  ...SHARED_CONFIG
46013
46538
  },
@@ -46026,7 +46551,8 @@ const CONFIGS = {
46026
46551
  },
46027
46552
  api: {
46028
46553
  baseUrl: "https://tfr.p.thefittingroom.xyz",
46029
- vtoTimeoutMs: 12e4
46554
+ vtoTimeoutMs: 12e4,
46555
+ vtoPrefetchDelayMs: 3e3
46030
46556
  },
46031
46557
  asset: {
46032
46558
  baseUrl: "https://assets.p.thefittingroom.xyz/shop-sdk/assets/v5"
@@ -46035,13 +46561,13 @@ const CONFIGS = {
46035
46561
  baseUrl: "https://assets.p.thefittingroom.xyz"
46036
46562
  },
46037
46563
  features: {
46038
- vtoPrefetch: false
46564
+ vtoPrefetch: true
46039
46565
  },
46040
46566
  ...SHARED_CONFIG
46041
46567
  },
46042
46568
  [
46043
- "local"
46044
- /* LOCAL */
46569
+ "demo"
46570
+ /* DEMO */
46045
46571
  ]: {
46046
46572
  firebase: {
46047
46573
  apiKey: "AIzaSyDfjBWzpmzb-mhGN8VSURxzLg6nkzmKUD8",
@@ -46054,7 +46580,8 @@ const CONFIGS = {
46054
46580
  },
46055
46581
  api: {
46056
46582
  baseUrl: "https://demo.thefittingroom.xyz/api",
46057
- vtoTimeoutMs: 12e4
46583
+ vtoTimeoutMs: 12e4,
46584
+ vtoPrefetchDelayMs: 3e3
46058
46585
  },
46059
46586
  asset: {
46060
46587
  baseUrl: "http://demo.thefittingroom.xyz/s3/tfr-assets-dev/shop-sdk/assets/v5"
@@ -46063,7 +46590,36 @@ const CONFIGS = {
46063
46590
  baseUrl: "http://demo.thefittingroom.xyz/s3/tfr-assets-dev"
46064
46591
  },
46065
46592
  features: {
46066
- vtoPrefetch: false
46593
+ vtoPrefetch: true
46594
+ },
46595
+ ...SHARED_CONFIG
46596
+ },
46597
+ [
46598
+ "local"
46599
+ /* LOCAL */
46600
+ ]: {
46601
+ firebase: {
46602
+ apiKey: "AIzaSyDfjBWzpmzb-mhGN8VSURxzLg6nkzmKUD8",
46603
+ authDomain: "fittingroom-dev-5d248.firebaseapp.com",
46604
+ projectId: "fittingroom-dev-5d248",
46605
+ storageBucket: "fittingroom-dev-5d248.appspot.com",
46606
+ messagingSenderId: "2298664147",
46607
+ appId: "1:2298664147:web:340bda75cd5d25f3997026",
46608
+ measurementId: "G-B7GDQ1Y9LL"
46609
+ },
46610
+ api: {
46611
+ baseUrl: "http://localhost:8080",
46612
+ vtoTimeoutMs: 12e4,
46613
+ vtoPrefetchDelayMs: 3e3
46614
+ },
46615
+ asset: {
46616
+ baseUrl: "http://localhost:9000/tfr-assets-dev/shop-sdk/assets/v5"
46617
+ },
46618
+ frames: {
46619
+ baseUrl: "http://localhost:9000/tfr-assets-dev"
46620
+ },
46621
+ features: {
46622
+ vtoPrefetch: true
46067
46623
  },
46068
46624
  ...SHARED_CONFIG
46069
46625
  }
@@ -46071,7 +46627,7 @@ const CONFIGS = {
46071
46627
  const getConfig = (envName) => {
46072
46628
  return CONFIGS[envName];
46073
46629
  };
46074
- const css = "body.tfr-modal-open {\n overflow: hidden;\n position: fixed;\n width: 100%;\n}\n";
46630
+ const css = "\n@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap');\n/* Inter — the SDK's own typeface, matching the Figma designs. Loaded here so\n overlay text renders in Inter regardless of the host page's fonts. */\nbody.tfr-modal-open {\n overflow: hidden;\n position: fixed;\n width: 100%;\n}\n";
46075
46631
  class TfrWidgetElement extends HTMLElement {
46076
46632
  connectedCallback() {
46077
46633
  const attributes = this.getAttributeNames().reduce((attrs, name2) => {