@thefittingroom/shop-ui 5.0.23 → 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 +913 -353
  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$g = getLogger("fitting-room-storage");
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$g.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$g.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$g.logDebug("{{ts}} - Removing from fitting room", {
14109
- productId
14110
- });
14111
- state.removeFromFittingRoom(productId);
14112
- return;
14113
- }
14114
- logger$g.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$g.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$f = 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$f.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$f.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$f.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$f.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$f.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$f.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$f.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$f.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$f.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$e = 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$e.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$e.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$d = 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$d.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$d.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$c = 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$c.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$b = 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$b.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$b.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$b.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$b.logError("loadFittingRoomData failed", {
41669
+ logger$a.logError("loadFittingRoomData failed", {
41615
41670
  error
41616
41671
  });
41617
41672
  });
@@ -41662,12 +41717,14 @@ function buildVtoProductDataFromResolved(item) {
41662
41717
  merchantProduct,
41663
41718
  loadedProduct
41664
41719
  } = item;
41665
- if (!merchantProduct || !loadedProduct) return null;
41720
+ if (!merchantProduct || !loadedProduct) {
41721
+ return null;
41722
+ }
41666
41723
  const sizeRec = loadedProduct.sizeFitRecommendation;
41667
41724
  const recommendedSizeId = sizeRec.recommended_size.id || null;
41668
41725
  const recommendedSizeLabel = getSizeLabelFromSize(sizeRec.recommended_size);
41669
41726
  if (recommendedSizeId == null || !recommendedSizeLabel) {
41670
- logger$b.logWarn("Missing recommended size for item", {
41727
+ logger$a.logWarn("Missing recommended size for item", {
41671
41728
  externalId: item.externalId
41672
41729
  });
41673
41730
  return null;
@@ -41675,13 +41732,19 @@ function buildVtoProductDataFromResolved(item) {
41675
41732
  const sizes = [];
41676
41733
  for (const sizeRecord of sizeRec.available_sizes) {
41677
41734
  const sizeLabel = getSizeLabelFromSize(sizeRecord);
41678
- if (!sizeLabel) continue;
41735
+ if (!sizeLabel) {
41736
+ continue;
41737
+ }
41679
41738
  const fit = sizeRec.fits.find((f) => f.size_id === sizeRecord.id);
41680
- if (!fit) continue;
41739
+ if (!fit) {
41740
+ continue;
41741
+ }
41681
41742
  const colors = [];
41682
41743
  for (const csa of sizeRecord.colorway_size_assets) {
41683
41744
  const variant = merchantProduct.variants.find((v) => v.sku === csa.sku);
41684
- if (!variant) continue;
41745
+ if (!variant) {
41746
+ continue;
41747
+ }
41685
41748
  colors.push({
41686
41749
  colorwaySizeAssetId: csa.id,
41687
41750
  colorLabel: variant.color || null,
@@ -41689,7 +41752,9 @@ function buildVtoProductDataFromResolved(item) {
41689
41752
  priceFormatted: variant.priceFormatted
41690
41753
  });
41691
41754
  }
41692
- if (colors.length === 0) continue;
41755
+ if (colors.length === 0) {
41756
+ continue;
41757
+ }
41693
41758
  sizes.push({
41694
41759
  sizeId: sizeRecord.id,
41695
41760
  sizeLabel,
@@ -41698,7 +41763,9 @@ function buildVtoProductDataFromResolved(item) {
41698
41763
  colors
41699
41764
  });
41700
41765
  }
41701
- if (sizes.length === 0) return null;
41766
+ if (sizes.length === 0) {
41767
+ return null;
41768
+ }
41702
41769
  return {
41703
41770
  productName: merchantProduct.productName,
41704
41771
  productDescriptionHtml: merchantProduct.productDescriptionHtml,
@@ -41711,12 +41778,16 @@ function buildVtoProductDataFromResolved(item) {
41711
41778
  }
41712
41779
  function findRecommendedColorSize(data, preferredColor) {
41713
41780
  const recommended = data.sizes.find((s) => s.isRecommended);
41714
- if (!recommended || recommended.colors.length === 0) return null;
41781
+ if (!recommended || recommended.colors.length === 0) {
41782
+ return null;
41783
+ }
41715
41784
  return recommended.colors.find((c) => c.colorLabel === preferredColor) ?? recommended.colors[0];
41716
41785
  }
41717
41786
  function findCsaByLabel(data, sizeLabel, preferredColor) {
41718
41787
  const sizeRecord = data.sizes.find((s) => s.sizeLabel === sizeLabel);
41719
- if (!sizeRecord || sizeRecord.colors.length === 0) return null;
41788
+ if (!sizeRecord || sizeRecord.colors.length === 0) {
41789
+ return null;
41790
+ }
41720
41791
  return sizeRecord.colors.find((c) => c.colorLabel === preferredColor) ?? sizeRecord.colors[0];
41721
41792
  }
41722
41793
  function useMobileSheetSnap(initial = "collapsed") {
@@ -41726,7 +41797,9 @@ function useMobileSheetSnap(initial = "collapsed") {
41726
41797
  const initialSnap = snap;
41727
41798
  const onTouchMove = (moveEvent) => {
41728
41799
  const deltaY = moveEvent.touches[0].clientY - startY;
41729
- if (Math.abs(deltaY) < 30) return;
41800
+ if (Math.abs(deltaY) < 30) {
41801
+ return;
41802
+ }
41730
41803
  if (deltaY > 0) {
41731
41804
  if (initialSnap === "full" || initialSnap === "expanded") {
41732
41805
  setSnap("collapsed");
@@ -41755,11 +41828,16 @@ function useMobileSheetSnap(initial = "collapsed") {
41755
41828
  }
41756
41829
  const MAX_OUTFIT_ITEMS = 4;
41757
41830
  function asStringList(value) {
41758
- if (!Array.isArray(value)) return [];
41831
+ if (!Array.isArray(value)) {
41832
+ return [];
41833
+ }
41759
41834
  const out = [];
41760
41835
  for (const v of value) {
41761
- if (typeof v === "string") out.push(v);
41762
- 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
+ }
41763
41841
  }
41764
41842
  return out;
41765
41843
  }
@@ -41800,8 +41878,12 @@ function computeAvailability(item, selectedExternalIds, resolved) {
41800
41878
  }
41801
41879
  const itemCat = item.styleCategory;
41802
41880
  for (const sel of resolved.items) {
41803
- if (!selectedExternalIds.has(sel.externalId)) continue;
41804
- if (!sel.styleCategory) continue;
41881
+ if (!selectedExternalIds.has(sel.externalId)) {
41882
+ continue;
41883
+ }
41884
+ if (!sel.styleCategory) {
41885
+ continue;
41886
+ }
41805
41887
  if (!pairCompatible(sel.styleCategory, itemCat, sel.styleCategoryGroup)) {
41806
41888
  return "disabled";
41807
41889
  }
@@ -41809,8 +41891,12 @@ function computeAvailability(item, selectedExternalIds, resolved) {
41809
41891
  return "available";
41810
41892
  }
41811
41893
  function makeOutfitItem(r, forceUntuck) {
41812
- if (!r.styleCategory) return null;
41813
- 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
+ }
41814
41900
  const tuckable = !!r.styleCategory.tuckable;
41815
41901
  const untucked = forceUntuck && tuckable;
41816
41902
  const layerOrder = untucked ? r.styleCategory.layer_order_untucked : r.styleCategory.layer_order;
@@ -41830,10 +41916,16 @@ function buildOutfit(selectedExternalIds, resolved, forceUntuck, lastAddedExtern
41830
41916
  const entries = [];
41831
41917
  let lastAddedResolved = null;
41832
41918
  for (const r of resolved.items) {
41833
- if (!selectedExternalIds.has(r.externalId)) continue;
41834
- if (r.externalId === lastAddedExternalId) lastAddedResolved = r;
41919
+ if (!selectedExternalIds.has(r.externalId)) {
41920
+ continue;
41921
+ }
41922
+ if (r.externalId === lastAddedExternalId) {
41923
+ lastAddedResolved = r;
41924
+ }
41835
41925
  const entry = makeOutfitItem(r, forceUntuck);
41836
- if (entry) entries.push(entry);
41926
+ if (entry) {
41927
+ entries.push(entry);
41928
+ }
41837
41929
  }
41838
41930
  entries.sort((a, b) => a.layerOrder - b.layerOrder);
41839
41931
  const items = entries.slice(0, MAX_OUTFIT_ITEMS).map((e) => e.outfitItem);
@@ -41844,10 +41936,16 @@ function buildOutfit(selectedExternalIds, resolved, forceUntuck, lastAddedExtern
41844
41936
  };
41845
41937
  }
41846
41938
  function buildAlternateOutfits(primary, lastAddedResolved) {
41847
- if (!lastAddedResolved || !lastAddedResolved.loadedProduct) return [];
41939
+ if (!lastAddedResolved || !lastAddedResolved.loadedProduct) {
41940
+ return [];
41941
+ }
41848
41942
  const currentCsaId = lastAddedResolved.storage.colorwaySizeAssetId;
41849
- if (currentCsaId == null) return [];
41850
- 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
+ }
41851
41949
  const sizeRec = lastAddedResolved.loadedProduct.sizeFitRecommendation;
41852
41950
  let currentColorwayId = null;
41853
41951
  for (const sz of sizeRec.available_sizes) {
@@ -41860,7 +41958,9 @@ function buildAlternateOutfits(primary, lastAddedResolved) {
41860
41958
  const out = [];
41861
41959
  for (const sz of sizeRec.available_sizes) {
41862
41960
  const altCsa = sz.colorway_size_assets.find((c) => c.id !== currentCsaId && (currentColorwayId == null || c.colorway_id === currentColorwayId));
41863
- if (!altCsa) continue;
41961
+ if (!altCsa) {
41962
+ continue;
41963
+ }
41864
41964
  const alternate = primary.map((it) => it.externalId === lastAddedResolved.externalId ? {
41865
41965
  ...it,
41866
41966
  colorwaySizeAssetId: altCsa.id
@@ -41869,6 +41969,24 @@ function buildAlternateOutfits(primary, lastAddedResolved) {
41869
41969
  }
41870
41970
  return out;
41871
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
+ }
41872
41990
  function AvatarControls({
41873
41991
  selectedItems,
41874
41992
  canTuck,
@@ -41885,7 +42003,9 @@ function AvatarControls({
41885
42003
  const [popoverOpen, setPopoverOpen] = reactExports.useState(false);
41886
42004
  const popoverWrapperRef = reactExports.useRef(null);
41887
42005
  reactExports.useEffect(() => {
41888
- if (!popoverOpen) return;
42006
+ if (!popoverOpen) {
42007
+ return;
42008
+ }
41889
42009
  const onDocClick = (e) => {
41890
42010
  if (popoverWrapperRef.current && !popoverWrapperRef.current.contains(e.target)) {
41891
42011
  setPopoverOpen(false);
@@ -41908,20 +42028,7 @@ function AvatarControls({
41908
42028
  alignItems: "flex-end"
41909
42029
  },
41910
42030
  pill: {
41911
- display: "inline-flex",
41912
- alignItems: "center",
41913
- gap: "8px",
41914
- padding: "8px 16px",
41915
- borderRadius: "24px",
41916
- backgroundColor: "rgba(255, 255, 255, 0.95)",
41917
- border: `1px solid ${theme.color_fg_text}`,
41918
- fontSize: "12px",
41919
- fontWeight: "500",
41920
- letterSpacing: "0.5px",
41921
- textTransform: "uppercase",
41922
- cursor: "pointer",
41923
- userSelect: "none",
41924
- WebkitUserSelect: "none",
42031
+ ...pillBaseStyle(theme),
41925
42032
  transition: "padding 500ms cubic-bezier(0.22, 1, 0.36, 1), gap 500ms cubic-bezier(0.22, 1, 0.36, 1)"
41926
42033
  },
41927
42034
  pillCollapsed: {
@@ -42023,13 +42130,114 @@ function AvatarControls({
42023
42130
  ] })
42024
42131
  ] });
42025
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
+ }
42026
42234
  function AvatarFrameViewer({
42027
42235
  frameUrls,
42028
42236
  selectedFrameIndex,
42029
42237
  setSelectedFrameIndex,
42030
42238
  imageContainerStyle,
42031
42239
  imageStyle,
42032
- loadingT = "vto-single.avatar_loading"
42240
+ loadingT = "quick-view.avatar_loading"
42033
42241
  }) {
42034
42242
  const css2 = useCss((_theme) => ({
42035
42243
  imageContainer: {
@@ -42073,59 +42281,17 @@ function AvatarFrameViewer({
42073
42281
  }, 500);
42074
42282
  return () => clearInterval(intervalId);
42075
42283
  }, [frameUrls, selectedFrameIndex, setSelectedFrameIndex]);
42076
- const rotateLeft = reactExports.useCallback(() => {
42077
- setSelectedFrameIndex((prevIndex) => {
42078
- if (prevIndex == null) return null;
42079
- return prevIndex === 0 ? frameUrls ? frameUrls.length - 1 : 0 : prevIndex - 1;
42080
- });
42081
- }, [frameUrls, setSelectedFrameIndex]);
42082
- const rotateRight = reactExports.useCallback(() => {
42083
- setSelectedFrameIndex((prevIndex) => {
42084
- if (prevIndex == null) return null;
42085
- return prevIndex === (frameUrls ? frameUrls.length - 1 : 0) ? 0 : prevIndex + 1;
42086
- });
42087
- }, [frameUrls, setSelectedFrameIndex]);
42088
- const handleImageMouseDrag = reactExports.useCallback((e) => {
42089
- e.preventDefault();
42090
- let startX = e.clientX;
42091
- const onMouseMove = (moveEvent) => {
42092
- const deltaX = moveEvent.clientX - startX;
42093
- if (Math.abs(deltaX) >= 50) {
42094
- if (deltaX > 0) rotateRight();
42095
- else rotateLeft();
42096
- startX = moveEvent.clientX;
42097
- }
42098
- };
42099
- const onMouseUp = () => {
42100
- window.removeEventListener("mousemove", onMouseMove);
42101
- window.removeEventListener("mouseup", onMouseUp);
42102
- };
42103
- window.addEventListener("mousemove", onMouseMove);
42104
- window.addEventListener("mouseup", onMouseUp);
42105
- }, [rotateLeft, rotateRight]);
42106
- const handleImageTouchDrag = reactExports.useCallback((e) => {
42107
- e.preventDefault();
42108
- let startX = e.touches[0].clientX;
42109
- const onTouchMove = (moveEvent) => {
42110
- const deltaX = moveEvent.touches[0].clientX - startX;
42111
- if (Math.abs(deltaX) >= 50) {
42112
- if (deltaX > 0) rotateRight();
42113
- else rotateLeft();
42114
- startX = moveEvent.touches[0].clientX;
42115
- }
42116
- };
42117
- const onTouchEnd = () => {
42118
- window.removeEventListener("touchmove", onTouchMove);
42119
- window.removeEventListener("touchend", onTouchEnd);
42120
- };
42121
- window.addEventListener("touchmove", onTouchMove);
42122
- window.addEventListener("touchend", onTouchEnd);
42123
- }, [rotateLeft, rotateRight]);
42284
+ const {
42285
+ rotateLeft,
42286
+ rotateRight,
42287
+ handleMouseDragStart,
42288
+ handleTouchDragStart
42289
+ } = useFrameRotation(frameUrls, setSelectedFrameIndex);
42124
42290
  if (!frameUrls || selectedFrameIndex == null) {
42125
42291
  return /* @__PURE__ */ jsx$1(Loading, { t: loadingT });
42126
42292
  }
42127
42293
  return /* @__PURE__ */ jsxs("div", { css: css2.imageContainer, style: imageContainerStyle, children: [
42128
- /* @__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 }),
42129
42295
  /* @__PURE__ */ jsx$1("div", { css: css2.chevronLeftContainer, onClick: rotateLeft, children: /* @__PURE__ */ jsx$1(SvgChevronLeft, { css: css2.chevronIcon }) }),
42130
42296
  /* @__PURE__ */ jsx$1("div", { css: css2.chevronRightContainer, onClick: rotateRight, children: /* @__PURE__ */ jsx$1(SvgChevronRight, { css: css2.chevronIcon }) })
42131
42297
  ] });
@@ -42134,9 +42300,13 @@ function AvatarPane({
42134
42300
  frameUrls,
42135
42301
  hasSelection,
42136
42302
  controls,
42137
- mobileFullscreen
42303
+ mobileFullscreen,
42304
+ selectedFrameIndex: indexProp,
42305
+ setSelectedFrameIndex: setIndexProp
42138
42306
  }) {
42139
- 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;
42140
42310
  const css2 = useCss((theme) => ({
42141
42311
  container: {
42142
42312
  width: "100%",
@@ -42194,12 +42364,14 @@ function AvatarPane({
42194
42364
  }
42195
42365
  }));
42196
42366
  if (frameUrls && frameUrls.length > 0) {
42197
- 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" });
42198
42368
  if (mobileFullscreen) {
42199
42369
  return /* @__PURE__ */ jsxs("div", { css: css2.mobileContainer, children: [
42200
- /* @__PURE__ */ jsx$1("div", { css: css2.mobileFrameSlot, children: viewer }),
42201
- /* @__PURE__ */ jsx$1("div", { css: css2.bottomFiller, children: " " }),
42202
- controls
42370
+ /* @__PURE__ */ jsxs("div", { css: css2.mobileFrameSlot, children: [
42371
+ viewer,
42372
+ controls
42373
+ ] }),
42374
+ /* @__PURE__ */ jsx$1("div", { css: css2.bottomFiller, children: " " })
42203
42375
  ] });
42204
42376
  }
42205
42377
  return /* @__PURE__ */ jsxs("div", { css: css2.container, children: [
@@ -42208,7 +42380,7 @@ function AvatarPane({
42208
42380
  ] });
42209
42381
  }
42210
42382
  if (hasSelection) {
42211
- 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" }) });
42212
42384
  }
42213
42385
  return /* @__PURE__ */ jsxs("div", { css: css2.container, children: [
42214
42386
  /* @__PURE__ */ jsx$1("div", { css: css2.placeholder, children: /* @__PURE__ */ jsx$1(TextT, { variant: "base", t: "fitting_room.avatar_placeholder_empty" }) }),
@@ -42313,7 +42485,9 @@ function ProductCard({
42313
42485
  const disabled = availability === "disabled";
42314
42486
  const selected = availability === "selected";
42315
42487
  const handleClick = () => {
42316
- if (disabled) return;
42488
+ if (disabled) {
42489
+ return;
42490
+ }
42317
42491
  onClick();
42318
42492
  };
42319
42493
  const name2 = item.merchantProduct?.productName ?? item.externalId;
@@ -42359,9 +42533,13 @@ function CardRail({
42359
42533
  setCanScrollRight(el.scrollLeft + el.clientWidth < el.scrollWidth - 1);
42360
42534
  }, []);
42361
42535
  reactExports.useLayoutEffect(() => {
42362
- if (collapsed) return;
42536
+ if (collapsed) {
42537
+ return;
42538
+ }
42363
42539
  const el = scrollRef.current;
42364
- if (!el) return;
42540
+ if (!el) {
42541
+ return;
42542
+ }
42365
42543
  updateScrollState();
42366
42544
  const observer = new ResizeObserver(updateScrollState);
42367
42545
  observer.observe(el);
@@ -42369,7 +42547,9 @@ function CardRail({
42369
42547
  }, [collapsed, group.items, updateScrollState]);
42370
42548
  const scrollByPage = reactExports.useCallback((dir) => {
42371
42549
  const el = scrollRef.current;
42372
- if (!el) return;
42550
+ if (!el) {
42551
+ return;
42552
+ }
42373
42553
  el.scrollBy({
42374
42554
  left: dir * el.clientWidth * 0.8,
42375
42555
  behavior: "smooth"
@@ -42462,7 +42642,7 @@ function CardRail({
42462
42642
  function AddToCartButton({
42463
42643
  onClick
42464
42644
  }) {
42465
- 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 });
42466
42646
  }
42467
42647
  function ItemFitDetails({
42468
42648
  loadedProductData,
@@ -42579,9 +42759,13 @@ function DetailAccordionItem({
42579
42759
  }) {
42580
42760
  const productData = reactExports.useMemo(() => buildVtoProductDataFromResolved(item), [item]);
42581
42761
  const selectedSizeLabel = reactExports.useMemo(() => {
42582
- if (!productData) return null;
42762
+ if (!productData) {
42763
+ return null;
42764
+ }
42583
42765
  const csaId = item.storage.colorwaySizeAssetId;
42584
- if (csaId == null) return null;
42766
+ if (csaId == null) {
42767
+ return null;
42768
+ }
42585
42769
  for (const sizeRec of productData.sizes) {
42586
42770
  if (sizeRec.colors.some((c) => c.colorwaySizeAssetId === csaId)) {
42587
42771
  return sizeRec.sizeLabel;
@@ -42590,12 +42774,18 @@ function DetailAccordionItem({
42590
42774
  return null;
42591
42775
  }, [productData, item.storage.colorwaySizeAssetId]);
42592
42776
  const currentPrice = reactExports.useMemo(() => {
42593
- if (!productData) return null;
42777
+ if (!productData) {
42778
+ return null;
42779
+ }
42594
42780
  const csaId = item.storage.colorwaySizeAssetId;
42595
- if (csaId == null) return null;
42781
+ if (csaId == null) {
42782
+ return null;
42783
+ }
42596
42784
  for (const sizeRec of productData.sizes) {
42597
42785
  const c = sizeRec.colors.find((c2) => c2.colorwaySizeAssetId === csaId);
42598
- if (c) return c.priceFormatted;
42786
+ if (c) {
42787
+ return c.priceFormatted;
42788
+ }
42599
42789
  }
42600
42790
  return null;
42601
42791
  }, [productData, item.storage.colorwaySizeAssetId]);
@@ -42922,6 +43112,119 @@ function DetailAccordion({
42922
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);
42923
43113
  }) });
42924
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
+ }
42925
43228
  const AVATAR_ASPECT_RATIO = 2 / 3;
42926
43229
  const EDGE_INSET_PX = 16;
42927
43230
  const AVATAR_MIN_WIDTH_PX = 240;
@@ -42937,7 +43240,6 @@ function DesktopLayout$1({
42937
43240
  detailMode,
42938
43241
  forceUntuck,
42939
43242
  canTuck,
42940
- zoomed,
42941
43243
  frameUrls,
42942
43244
  onSelectItem,
42943
43245
  onRemoveItem,
@@ -42946,26 +43248,31 @@ function DesktopLayout$1({
42946
43248
  onChangeSize,
42947
43249
  onAddToCart,
42948
43250
  onToggleUntuck,
42949
- onToggleZoom,
42950
43251
  onSignOut
42951
43252
  }) {
42952
43253
  const hasSelection = selectedItems.length > 0;
42953
43254
  const [avatarHovered, setAvatarHovered] = reactExports.useState(false);
43255
+ const [zoomOpen, setZoomOpen] = reactExports.useState(false);
43256
+ const [selectedFrameIndex, setSelectedFrameIndex] = reactExports.useState(null);
42954
43257
  const containerRef = reactExports.useRef(null);
42955
43258
  const [avatarWidth, setAvatarWidth] = reactExports.useState(AVATAR_MIN_WIDTH_PX);
42956
43259
  reactExports.useLayoutEffect(() => {
42957
43260
  const el = containerRef.current;
42958
- if (!el) return;
43261
+ if (!el) {
43262
+ return;
43263
+ }
42959
43264
  const observer = new ResizeObserver(() => {
42960
43265
  const availableHeightPx = el.clientHeight;
42961
- if (availableHeightPx <= 0) return;
43266
+ if (availableHeightPx <= 0) {
43267
+ return;
43268
+ }
42962
43269
  const target = Math.floor(availableHeightPx * AVATAR_ASPECT_RATIO);
42963
43270
  setAvatarWidth(Math.min(AVATAR_MAX_WIDTH_PX, Math.max(AVATAR_MIN_WIDTH_PX, target)));
42964
43271
  });
42965
43272
  observer.observe(el);
42966
43273
  return () => observer.disconnect();
42967
43274
  }, []);
42968
- 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`;
42969
43276
  const css2 = useCss((_theme) => ({
42970
43277
  container: {
42971
43278
  display: "grid",
@@ -43030,21 +43337,127 @@ function DesktopLayout$1({
43030
43337
  fontSize: "14px"
43031
43338
  }
43032
43339
  }));
43033
- 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;
43034
43341
  return /* @__PURE__ */ jsxs("div", { ref: containerRef, css: css2.container, style: {
43035
43342
  gridTemplateColumns
43036
43343
  }, children: [
43037
- /* @__PURE__ */ jsx$1("div", { css: css2.avatarColumn, onMouseEnter: () => setAvatarHovered(true), onMouseLeave: () => setAvatarHovered(false), children: /* @__PURE__ */ jsx$1(AvatarPane, { hasSelection, frameUrls, controls }) }),
43038
- !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,
43039
- !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: [
43040
43347
  /* @__PURE__ */ jsxs("span", { css: css2.signOutWrapper, onClick: onSignOut, children: [
43041
43348
  /* @__PURE__ */ jsx$1(SvgTfrIcon, { css: css2.signOutIcon }),
43042
43349
  /* @__PURE__ */ jsx$1(LinkT, { variant: "underline", css: css2.signOut, t: "fitting_room.sign_out" })
43043
43350
  ] }),
43044
43351
  resolved.groups.map((group) => /* @__PURE__ */ jsx$1(CardRail, { group, availabilityByExternalId, onSelectItem, onRemoveItem }, group.group.name))
43045
- ] }) : null
43352
+ ] }),
43353
+ zoomOpen && frameUrls && frameUrls.length > 0 ? /* @__PURE__ */ jsx$1(ZoomModal, { frameUrls, selectedFrameIndex, setSelectedFrameIndex, onClose: () => setZoomOpen(false) }) : null
43354
+ ] });
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
43046
43458
  ] });
43047
43459
  }
43460
+ const SECTION_SCROLL_TOP_GAP_PX = 50;
43048
43461
  function MobileLayout$1({
43049
43462
  mode,
43050
43463
  resolved,
@@ -43080,12 +43493,55 @@ function BrowseView({
43080
43493
  onRemoveItem,
43081
43494
  onTryItOn
43082
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
+ }, []);
43083
43537
  const css2 = useCss((_theme) => ({
43084
43538
  container: {
43085
43539
  display: "flex",
43086
43540
  flexDirection: "column",
43087
43541
  height: "100%",
43088
- width: "100%"
43542
+ width: "100%",
43543
+ // Positioning context for the floating SectionNav pill.
43544
+ position: "relative"
43089
43545
  },
43090
43546
  railsArea: {
43091
43547
  flex: 1,
@@ -43103,7 +43559,14 @@ function BrowseView({
43103
43559
  }
43104
43560
  }));
43105
43561
  return /* @__PURE__ */ jsxs("div", { css: css2.container, children: [
43106
- /* @__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)) }),
43107
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
43108
43571
  ] });
43109
43572
  }
@@ -43129,8 +43592,14 @@ function TryOnView({
43129
43592
  reactExports.useEffect(() => {
43130
43593
  function refresh() {
43131
43594
  const el = innerRef.current;
43132
- if (!el) return;
43133
- 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", ""));
43134
43603
  const heightPx = Math.min(el.clientHeight, maxHeightPx || el.clientHeight);
43135
43604
  setSheetStyle({
43136
43605
  height: `${heightPx}px`
@@ -43166,7 +43635,7 @@ function TryOnView({
43166
43635
  },
43167
43636
  sheetOuter: {
43168
43637
  position: "absolute",
43169
- // 8px gap to either side, matching vto-single's mobile sheet.
43638
+ // 8px gap to either side, matching quick-view's mobile sheet.
43170
43639
  left: "8px",
43171
43640
  right: "8px",
43172
43641
  bottom: 0,
@@ -43207,7 +43676,7 @@ function TryOnView({
43207
43676
  }
43208
43677
  }));
43209
43678
  return /* @__PURE__ */ jsxs("div", { css: css2.container, children: [
43210
- /* @__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 }) }),
43211
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 }) }),
43212
43681
  /* @__PURE__ */ jsx$1("div", { css: css2.sheetOuter, style: sheetStyle, children: /* @__PURE__ */ jsxs("div", { ref: innerRef, css: css2.sheetInner, style: sheetStyle, children: [
43213
43682
  /* @__PURE__ */ jsxs("div", { css: css2.sheetHandleRow, onTouchStart: sheetTouchStart, children: [
@@ -43218,7 +43687,7 @@ function TryOnView({
43218
43687
  ] }) })
43219
43688
  ] });
43220
43689
  }
43221
- const logger$a = getLogger("use-vto-requests");
43690
+ const logger$9 = getLogger("use-vto-requests");
43222
43691
  function outfitKey(items) {
43223
43692
  return items.map((i) => `${i.colorway_size_asset_id}:${i.untucked ? 1 : 0}`).sort().join("|");
43224
43693
  }
@@ -43230,18 +43699,22 @@ function useVtoRequests() {
43230
43699
  const [lastError, setLastError] = reactExports.useState(null);
43231
43700
  const clearError = reactExports.useCallback(() => setLastError(null), []);
43232
43701
  const request = reactExports.useCallback((items, priority) => {
43233
- if (items.length === 0) return;
43702
+ if (items.length === 0) {
43703
+ return;
43704
+ }
43234
43705
  const key = outfitKey(items);
43235
- if (requestedKeysRef.current.has(key)) return;
43706
+ if (requestedKeysRef.current.has(key)) {
43707
+ return;
43708
+ }
43236
43709
  const exec = () => {
43237
43710
  requestedKeysRef.current.add(key);
43238
- logger$a.logDebug("Requesting VTO composition", {
43711
+ logger$9.logDebug("Requesting VTO composition", {
43239
43712
  key,
43240
43713
  items,
43241
43714
  priority
43242
43715
  });
43243
43716
  requestVto(items).then((resp) => {
43244
- logger$a.logDebug("VTO frames ready", {
43717
+ logger$9.logDebug("VTO frames ready", {
43245
43718
  key,
43246
43719
  count: resp.frames.length
43247
43720
  });
@@ -43250,7 +43723,7 @@ function useVtoRequests() {
43250
43723
  [key]: resp.frames
43251
43724
  }));
43252
43725
  }).catch((error) => {
43253
- logger$a.logError("VTO request failed", {
43726
+ logger$9.logError("VTO request failed", {
43254
43727
  error,
43255
43728
  items,
43256
43729
  key
@@ -43260,7 +43733,9 @@ function useVtoRequests() {
43260
43733
  });
43261
43734
  };
43262
43735
  if (priority) {
43263
- for (const timer of pendingPrefetchTimersRef.current) clearTimeout(timer);
43736
+ for (const timer of pendingPrefetchTimersRef.current) {
43737
+ clearTimeout(timer);
43738
+ }
43264
43739
  pendingPrefetchTimersRef.current.clear();
43265
43740
  lastPriorityTimeRef.current = Date.now();
43266
43741
  exec();
@@ -43271,7 +43746,9 @@ function useVtoRequests() {
43271
43746
  if (last) {
43272
43747
  const now = Date.now();
43273
43748
  const minNext = last + getStaticData().config.api.vtoPrefetchDelayMs;
43274
- if (now < minNext) delay = minNext - now;
43749
+ if (now < minNext) {
43750
+ delay = minNext - now;
43751
+ }
43275
43752
  }
43276
43753
  if (delay > 0) {
43277
43754
  const timer = setTimeout(() => {
@@ -43286,15 +43763,21 @@ function useVtoRequests() {
43286
43763
  reactExports.useEffect(() => {
43287
43764
  const timers = pendingPrefetchTimersRef.current;
43288
43765
  return () => {
43289
- for (const timer of timers) clearTimeout(timer);
43766
+ for (const timer of timers) {
43767
+ clearTimeout(timer);
43768
+ }
43290
43769
  timers.clear();
43291
43770
  };
43292
43771
  }, []);
43293
43772
  const framesForOutfit = reactExports.useCallback((items) => {
43294
- if (items.length === 0) return null;
43773
+ if (items.length === 0) {
43774
+ return null;
43775
+ }
43295
43776
  const key = outfitKey(items);
43296
43777
  const frames = framesByKey[key];
43297
- if (!frames || frames.length === 0) return null;
43778
+ if (!frames || frames.length === 0) {
43779
+ return null;
43780
+ }
43298
43781
  const baseUrl2 = getStaticData().config.frames.baseUrl;
43299
43782
  return frames.map((u) => applyFrameBaseUrl(u, baseUrl2));
43300
43783
  }, [framesByKey]);
@@ -43313,12 +43796,14 @@ function toWireItems(items) {
43313
43796
  } : {}
43314
43797
  }));
43315
43798
  }
43316
- const logger$9 = getLogger("overlays/fitting-room");
43799
+ const logger$8 = getLogger("overlays/fitting-room");
43317
43800
  function measureTopOffset() {
43318
43801
  const {
43319
43802
  getOverlayTopOffset
43320
43803
  } = getStaticData();
43321
- if (!getOverlayTopOffset) return 0;
43804
+ if (!getOverlayTopOffset) {
43805
+ return 0;
43806
+ }
43322
43807
  try {
43323
43808
  const offset = getOverlayTopOffset();
43324
43809
  return Number.isFinite(offset) && offset > 0 ? offset : 0;
@@ -43326,7 +43811,9 @@ function measureTopOffset() {
43326
43811
  return 0;
43327
43812
  }
43328
43813
  }
43329
- function FittingRoomOverlay() {
43814
+ function FittingRoomOverlay({
43815
+ preselectExternalId
43816
+ }) {
43330
43817
  const deviceLayout = useMainStore((state) => state.deviceLayout);
43331
43818
  const userIsLoggedIn = useMainStore((state) => state.userIsLoggedIn);
43332
43819
  const userHasAvatar = useMainStore((state) => state.userHasAvatar);
@@ -43342,7 +43829,6 @@ function FittingRoomOverlay() {
43342
43829
  const [forceUntuck, setForceUntuck] = reactExports.useState(false);
43343
43830
  const [lastAddedExternalId, setLastAddedExternalId] = reactExports.useState(null);
43344
43831
  const [mobileMode, setMobileMode] = reactExports.useState("browse");
43345
- const [zoomed, setZoomed] = reactExports.useState(false);
43346
43832
  const {
43347
43833
  snap: sheetSnap,
43348
43834
  setSnap: setSheetSnap,
@@ -43357,13 +43843,17 @@ function FittingRoomOverlay() {
43357
43843
  } = useVtoRequests();
43358
43844
  reactExports.useEffect(() => {
43359
43845
  const savedScrollY = window.scrollY;
43360
- if (savedScrollY > 0) window.scrollTo(0, 0);
43846
+ if (savedScrollY > 0) {
43847
+ window.scrollTo(0, 0);
43848
+ }
43361
43849
  setTopOffset(measureTopOffset());
43362
43850
  const onResize = () => setTopOffset(measureTopOffset());
43363
43851
  window.addEventListener("resize", onResize);
43364
43852
  return () => {
43365
43853
  window.removeEventListener("resize", onResize);
43366
- if (savedScrollY > 0) window.scrollTo(0, savedScrollY);
43854
+ if (savedScrollY > 0) {
43855
+ window.scrollTo(0, savedScrollY);
43856
+ }
43367
43857
  };
43368
43858
  }, []);
43369
43859
  const selectedItems = reactExports.useMemo(() => {
@@ -43376,14 +43866,22 @@ function FittingRoomOverlay() {
43376
43866
  indexed.sort((a, b) => {
43377
43867
  const aOrder = a.item.styleCategoryGroup?.display_order ?? Number.MAX_SAFE_INTEGER;
43378
43868
  const bOrder = b.item.styleCategoryGroup?.display_order ?? Number.MAX_SAFE_INTEGER;
43379
- if (aOrder !== bOrder) return aOrder - bOrder;
43869
+ if (aOrder !== bOrder) {
43870
+ return aOrder - bOrder;
43871
+ }
43380
43872
  return a.idx - b.idx;
43381
43873
  });
43382
43874
  return indexed.map(({
43383
43875
  item
43384
43876
  }) => item);
43385
43877
  }, [resolved.items, selectedExternalIds]);
43386
- 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]);
43387
43885
  const availabilityByExternalId = reactExports.useMemo(() => {
43388
43886
  const out = {};
43389
43887
  for (const item of resolved.items) {
@@ -43392,11 +43890,17 @@ function FittingRoomOverlay() {
43392
43890
  return out;
43393
43891
  }, [resolved, selectedExternalIds]);
43394
43892
  const ensureSizeForItem = reactExports.useCallback((item) => {
43395
- if (item.storage.colorwaySizeAssetId != null) return;
43893
+ if (item.storage.colorwaySizeAssetId != null) {
43894
+ return;
43895
+ }
43396
43896
  const productData = buildVtoProductDataFromResolved(item);
43397
- if (!productData) return;
43897
+ if (!productData) {
43898
+ return;
43899
+ }
43398
43900
  const csa = findRecommendedColorSize(productData, item.storage.color);
43399
- if (!csa) return;
43901
+ if (!csa) {
43902
+ return;
43903
+ }
43400
43904
  const sizeRec = item.loadedProduct?.sizeFitRecommendation;
43401
43905
  const sizeLabel = sizeRec ? getSizeLabelFromSize(sizeRec.recommended_size) : productData.recommendedSizeLabel;
43402
43906
  updateFittingRoomItem(item.externalId, {
@@ -43404,7 +43908,7 @@ function FittingRoomOverlay() {
43404
43908
  size: sizeLabel,
43405
43909
  color: csa.colorLabel
43406
43910
  });
43407
- logger$9.logDebug("Auto-picked recommended size", {
43911
+ logger$8.logDebug("Auto-picked recommended size", {
43408
43912
  externalId: item.externalId,
43409
43913
  sizeLabel,
43410
43914
  csaId: csa.colorwaySizeAssetId
@@ -43412,12 +43916,16 @@ function FittingRoomOverlay() {
43412
43916
  }, [updateFittingRoomItem]);
43413
43917
  const handleSelectItem = reactExports.useCallback((externalId) => {
43414
43918
  const item = resolved.items.find((i) => i.externalId === externalId);
43415
- if (!item) return;
43919
+ if (!item) {
43920
+ return;
43921
+ }
43416
43922
  const isSelected = selectedExternalIds.has(externalId);
43417
43923
  const nextSelected = new Set(selectedExternalIds);
43418
43924
  if (isSelected) {
43419
43925
  nextSelected.delete(externalId);
43420
- if (openAccordionItemId === externalId) setOpenAccordionItemId(null);
43926
+ if (openAccordionItemId === externalId) {
43927
+ setOpenAccordionItemId(null);
43928
+ }
43421
43929
  } else {
43422
43930
  nextSelected.add(externalId);
43423
43931
  ensureSizeForItem(item);
@@ -43431,11 +43939,17 @@ function FittingRoomOverlay() {
43431
43939
  }, [resolved, selectedExternalIds, openAccordionItemId, isMobileLayout, ensureSizeForItem]);
43432
43940
  const handleChangeSize = reactExports.useCallback((externalId, sizeLabel) => {
43433
43941
  const item = resolved.items.find((i) => i.externalId === externalId);
43434
- if (!item) return;
43942
+ if (!item) {
43943
+ return;
43944
+ }
43435
43945
  const productData = buildVtoProductDataFromResolved(item);
43436
- if (!productData) return;
43946
+ if (!productData) {
43947
+ return;
43948
+ }
43437
43949
  const csa = findCsaByLabel(productData, sizeLabel, item.storage.color);
43438
- if (!csa) return;
43950
+ if (!csa) {
43951
+ return;
43952
+ }
43439
43953
  updateFittingRoomItem(externalId, {
43440
43954
  colorwaySizeAssetId: csa.colorwaySizeAssetId,
43441
43955
  size: sizeLabel,
@@ -43447,14 +43961,14 @@ function FittingRoomOverlay() {
43447
43961
  addToCart
43448
43962
  } = getStaticData();
43449
43963
  if (!addToCart) {
43450
- logger$9.logWarn("No addToCart callback configured; skipping", {
43964
+ logger$8.logWarn("No addToCart callback configured; skipping", {
43451
43965
  externalId
43452
43966
  });
43453
43967
  return;
43454
43968
  }
43455
43969
  const item = resolved.items.find((i) => i.externalId === externalId);
43456
43970
  if (!item || !item.storage.size || !item.storage.color) {
43457
- logger$9.logWarn("Cannot add to cart: missing size or color", {
43971
+ logger$8.logWarn("Cannot add to cart: missing size or color", {
43458
43972
  externalId,
43459
43973
  size: item?.storage.size,
43460
43974
  color: item?.storage.color
@@ -43468,7 +43982,7 @@ function FittingRoomOverlay() {
43468
43982
  });
43469
43983
  closeOverlay();
43470
43984
  } catch (error) {
43471
- logger$9.logError("addToCart failed", {
43985
+ logger$8.logError("addToCart failed", {
43472
43986
  error,
43473
43987
  externalId
43474
43988
  });
@@ -43477,17 +43991,18 @@ function FittingRoomOverlay() {
43477
43991
  const handleToggleUntuck = reactExports.useCallback(() => {
43478
43992
  setForceUntuck((prev2) => !prev2);
43479
43993
  }, []);
43480
- const handleToggleZoom = reactExports.useCallback(() => {
43481
- setZoomed((prev2) => !prev2);
43482
- }, []);
43483
43994
  const handleRemoveItem = reactExports.useCallback((externalId) => {
43484
43995
  setSelectedExternalIds((prev2) => {
43485
- if (!prev2.has(externalId)) return prev2;
43996
+ if (!prev2.has(externalId)) {
43997
+ return prev2;
43998
+ }
43486
43999
  const next2 = new Set(prev2);
43487
44000
  next2.delete(externalId);
43488
44001
  return next2;
43489
44002
  });
43490
- if (openAccordionItemId === externalId) setOpenAccordionItemId(null);
44003
+ if (openAccordionItemId === externalId) {
44004
+ setOpenAccordionItemId(null);
44005
+ }
43491
44006
  }, [openAccordionItemId]);
43492
44007
  const handleTryItOn = reactExports.useCallback(() => {
43493
44008
  setMobileMode("try-on");
@@ -43501,10 +44016,25 @@ function FittingRoomOverlay() {
43501
44016
  setOpenAccordionItemId(null);
43502
44017
  }
43503
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]);
43504
44030
  const outfit = reactExports.useMemo(() => buildOutfit(selectedExternalIds, resolved, forceUntuck, lastAddedExternalId), [selectedExternalIds, resolved, forceUntuck, lastAddedExternalId]);
43505
44031
  reactExports.useEffect(() => {
43506
- if (!userIsLoggedIn || !userHasAvatar) return;
43507
- if (outfit.items.length === 0) return;
44032
+ if (!userIsLoggedIn || !userHasAvatar) {
44033
+ return;
44034
+ }
44035
+ if (outfit.items.length === 0) {
44036
+ return;
44037
+ }
43508
44038
  requestVtoComposition(toWireItems(outfit.items), true);
43509
44039
  if (getStaticData().config.features.vtoPrefetch) {
43510
44040
  for (const alt of outfit.alternates) {
@@ -43515,7 +44045,9 @@ function FittingRoomOverlay() {
43515
44045
  const frameUrls = reactExports.useMemo(() => {
43516
44046
  if (outfit.items.length === 0) {
43517
44047
  const bareFrames = userProfile?.avatar_frames;
43518
- if (!bareFrames || bareFrames.length === 0) return null;
44048
+ if (!bareFrames || bareFrames.length === 0) {
44049
+ return null;
44050
+ }
43519
44051
  const baseUrl2 = getStaticData().config.frames.baseUrl;
43520
44052
  return bareFrames.map((u) => applyFrameBaseUrl(u, baseUrl2));
43521
44053
  }
@@ -43539,7 +44071,7 @@ function FittingRoomOverlay() {
43539
44071
  closeOverlay();
43540
44072
  const authManager2 = getAuthManager();
43541
44073
  authManager2.logout().catch((error) => {
43542
- logger$9.logError("Error during logout", {
44074
+ logger$8.logError("Error during logout", {
43543
44075
  error
43544
44076
  });
43545
44077
  });
@@ -43586,29 +44118,6 @@ function FittingRoomOverlay() {
43586
44118
  fontSize: "13px",
43587
44119
  textDecoration: "underline",
43588
44120
  marginTop: "8px"
43589
- },
43590
- snackbar: {
43591
- position: "absolute",
43592
- left: "50%",
43593
- bottom: "24px",
43594
- transform: "translateX(-50%)",
43595
- padding: "12px 20px",
43596
- borderRadius: "24px",
43597
- backgroundColor: theme.color_fg_text,
43598
- color: "#FFFFFF",
43599
- fontSize: "13px",
43600
- display: "flex",
43601
- alignItems: "center",
43602
- gap: "12px",
43603
- zIndex: 10,
43604
- maxWidth: "calc(100% - 32px)"
43605
- },
43606
- snackbarDismiss: {
43607
- background: "none",
43608
- border: "none",
43609
- color: "#FFFFFF",
43610
- fontSize: "16px",
43611
- cursor: "pointer"
43612
44121
  }
43613
44122
  }));
43614
44123
  const isMobileTryOn = isMobileLayout && mobileMode === "try-on";
@@ -43639,15 +44148,12 @@ function FittingRoomOverlay() {
43639
44148
  /* @__PURE__ */ jsx$1(TextT, { variant: "base", css: css2.emptyTagline, t: "landing.description" }),
43640
44149
  /* @__PURE__ */ jsx$1("div", { css: css2.emptyShopNow, children: /* @__PURE__ */ jsx$1(ButtonT, { variant: "primary", t: "fitting_room.shop_now", onClick: handleShopNow }) }),
43641
44150
  userIsLoggedIn ? /* @__PURE__ */ jsx$1(LinkT, { variant: "underline", css: css2.emptySignOut, t: "fitting_room.sign_out", onClick: handleSignOut }) : null
43642
- ] }) }) : 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 }),
43643
- vtoError ? /* @__PURE__ */ jsxs("div", { css: css2.snackbar, children: [
43644
- /* @__PURE__ */ jsx$1(TextT, { variant: "base", t: "fitting_room.vto_error" }),
43645
- /* @__PURE__ */ jsx$1("button", { css: css2.snackbarDismiss, onClick: clearVtoError, "aria-label": "Dismiss", children: "×" })
43646
- ] }) : 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
43647
44153
  ] }) });
43648
44154
  }
43649
44155
  const CONTACT_US_LINK = "mailto:info@thefittingroom.tech?subject=Forgot%20Password%20Assistance";
43650
- const logger$8 = getLogger("forgot-password");
44156
+ const logger$7 = getLogger("forgot-password");
43651
44157
  function ForgotPasswordOverlay({
43652
44158
  returnToOverlay
43653
44159
  }) {
@@ -43713,7 +44219,7 @@ function ForgotPasswordOverlay({
43713
44219
  await authManager2.sendPasswordResetEmail(email2);
43714
44220
  setLinkSent(true);
43715
44221
  } catch (error) {
43716
- logger$8.logError("Error sending password reset email:", {
44222
+ logger$7.logError("Error sending password reset email:", {
43717
44223
  error
43718
44224
  });
43719
44225
  }
@@ -43731,7 +44237,7 @@ function ForgotPasswordOverlay({
43731
44237
  return;
43732
44238
  }
43733
44239
  const email = emailEl.value;
43734
- resetUserPassword(email);
44240
+ void resetUserPassword(email);
43735
44241
  }, [t]);
43736
44242
  const handleBackToSignInClick = reactExports.useCallback(() => {
43737
44243
  openOverlay(OverlayName.SIGN_IN, {
@@ -43932,7 +44438,7 @@ function TfrTitle() {
43932
44438
  }));
43933
44439
  return /* @__PURE__ */ jsx$1("div", { css: css2.container, children: /* @__PURE__ */ jsx$1(SvgTfrName, { css: css2.nameIcon }) });
43934
44440
  }
43935
- const logger$7 = getLogger("sign-in");
44441
+ const logger$6 = getLogger("sign-in");
43936
44442
  function SignInOverlay({
43937
44443
  returnToOverlay
43938
44444
  }) {
@@ -44006,7 +44512,7 @@ function SignInOverlay({
44006
44512
  closeOverlay();
44007
44513
  }
44008
44514
  } catch (error) {
44009
- logger$7.logError("Login failed:", {
44515
+ logger$6.logError("Login failed:", {
44010
44516
  error
44011
44517
  });
44012
44518
  setEmailError(" ");
@@ -44038,7 +44544,7 @@ function SignInOverlay({
44038
44544
  }
44039
44545
  const email = emailEl.value;
44040
44546
  const password = passwordEl.value;
44041
- loginUser(email, password);
44547
+ void loginUser(email, password);
44042
44548
  }, [returnToOverlay, closeOverlay, openOverlay, t]);
44043
44549
  const handleForgotPasswordClick = reactExports.useCallback(() => {
44044
44550
  openOverlay(OverlayName.FORGOT_PASSWORD, {
@@ -44369,8 +44875,8 @@ function FitChart({
44369
44875
  const AVATAR_IMAGE_ASPECT_RATIO = 2 / 3;
44370
44876
  const AVATAR_GUTTER_HEIGHT_PX = 100;
44371
44877
  const CONTENT_AREA_WIDTH_PX = 550;
44372
- const logger$6 = getLogger("overlays/vto-single");
44373
- function VtoSingleOverlay() {
44878
+ const logger$5 = getLogger("overlays/quick-view");
44879
+ function QuickViewOverlay() {
44374
44880
  const userIsLoggedIn = useMainStore((state) => state.userIsLoggedIn);
44375
44881
  const userHasAvatar = useMainStore((state) => state.userHasAvatar);
44376
44882
  const userProfile = useMainStore((state) => state.userProfile);
@@ -44384,18 +44890,20 @@ function VtoSingleOverlay() {
44384
44890
  const [modalStyle, setModalStyle] = reactExports.useState({});
44385
44891
  const {
44386
44892
  request: vtoRequest,
44387
- framesForOutfit
44893
+ framesForOutfit,
44894
+ lastError: vtoError,
44895
+ clearError: clearVtoError
44388
44896
  } = useVtoRequests();
44389
44897
  reactExports.useEffect(() => {
44390
44898
  if (!userIsLoggedIn) {
44391
44899
  openOverlay(OverlayName.LANDING, {
44392
- returnToOverlay: OverlayName.VTO_SINGLE
44900
+ returnToOverlay: OverlayName.QUICK_VIEW
44393
44901
  });
44394
44902
  return;
44395
44903
  }
44396
44904
  if (userIsLoggedIn && userHasAvatar === false) {
44397
44905
  openOverlay(OverlayName.GET_APP, {
44398
- returnToOverlay: OverlayName.VTO_SINGLE,
44906
+ returnToOverlay: OverlayName.QUICK_VIEW,
44399
44907
  noAvatar: true
44400
44908
  });
44401
44909
  return;
@@ -44463,7 +44971,7 @@ function VtoSingleOverlay() {
44463
44971
  const sku = csaRec.sku;
44464
44972
  const variant = variants.find((v) => v.sku === sku);
44465
44973
  if (!variant) {
44466
- 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}`);
44467
44975
  continue;
44468
44976
  }
44469
44977
  const colorLabel = variant.color || null;
@@ -44476,7 +44984,7 @@ function VtoSingleOverlay() {
44476
44984
  });
44477
44985
  }
44478
44986
  if (!colors.length) {
44479
- 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}`);
44480
44988
  continue;
44481
44989
  }
44482
44990
  sizes.push({
@@ -44519,7 +45027,7 @@ function VtoSingleOverlay() {
44519
45027
  setSelectedColorLabel(recommendedColorLabel);
44520
45028
  }
44521
45029
  } catch (error) {
44522
- logger$6.logError("Error fetching initial data:", {
45030
+ logger$5.logError("Error fetching initial data:", {
44523
45031
  error
44524
45032
  });
44525
45033
  setVtoProductData(false);
@@ -44530,7 +45038,7 @@ function VtoSingleOverlay() {
44530
45038
  if (vtoProductData !== null) {
44531
45039
  return;
44532
45040
  }
44533
- setupInitialVtoData();
45041
+ void setupInitialVtoData();
44534
45042
  }, [storeProductData, vtoProductData, userProfile]);
44535
45043
  const {
44536
45044
  sizeColorRecord: selectedColorSizeRecord,
@@ -44592,7 +45100,7 @@ function VtoSingleOverlay() {
44592
45100
  if (!rewritten) {
44593
45101
  return null;
44594
45102
  }
44595
- logger$6.logDebug(`{{ts}} - Displaying VTO for sku: ${selectedColorSizeRecord.sku}`);
45103
+ logger$5.logDebug(`{{ts}} - Displaying VTO for sku: ${selectedColorSizeRecord.sku}`);
44596
45104
  rewritten.forEach((url) => {
44597
45105
  const img = new Image();
44598
45106
  img.src = url;
@@ -44603,7 +45111,7 @@ function VtoSingleOverlay() {
44603
45111
  closeOverlay();
44604
45112
  const authManager2 = getAuthManager();
44605
45113
  authManager2.logout().catch((error) => {
44606
- logger$6.logError("Error during logout:", {
45114
+ logger$5.logError("Error during logout:", {
44607
45115
  error
44608
45116
  });
44609
45117
  });
@@ -44625,7 +45133,7 @@ function VtoSingleOverlay() {
44625
45133
  color: selectedColorLabel
44626
45134
  });
44627
45135
  } catch (error) {
44628
- logger$6.logError("Error adding to cart:", {
45136
+ logger$5.logError("Error adding to cart:", {
44629
45137
  error
44630
45138
  });
44631
45139
  }
@@ -44642,7 +45150,10 @@ function VtoSingleOverlay() {
44642
45150
  } else {
44643
45151
  Layout = DesktopLayout;
44644
45152
  }
44645
- 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
+ ] });
44646
45157
  }
44647
45158
  function NoFitLayout({
44648
45159
  onClose,
@@ -44674,14 +45185,59 @@ function NoFitLayout({
44674
45185
  }
44675
45186
  }));
44676
45187
  return /* @__PURE__ */ jsxs("div", { css: css2.mainContainer, children: [
44677
- /* @__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 }) }),
44678
45189
  /* @__PURE__ */ jsxs("div", { css: css2.contentContainer, children: [
44679
45190
  /* @__PURE__ */ jsx$1("div", { children: " " }),
44680
- /* @__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" }) }),
44681
45192
  /* @__PURE__ */ jsx$1("div", { css: css2.footerContainer, children: /* @__PURE__ */ jsx$1(Footer, { onSignOutClick: onSignOut }) })
44682
45193
  ] })
44683
45194
  ] });
44684
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
+ }
44685
45241
  function MobileLayout({
44686
45242
  loadedProductData,
44687
45243
  selectedColorSizeRecord,
@@ -44787,7 +45343,11 @@ function MobileLayout({
44787
45343
  if (!bottomFrameInnerEl) {
44788
45344
  return;
44789
45345
  }
44790
- 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", ""));
44791
45351
  const heightPx = Math.min(bottomFrameInnerEl.clientHeight, maxHeightPx);
44792
45352
  const bottomFrameStyle = {
44793
45353
  height: `${heightPx}px`
@@ -44892,8 +45452,11 @@ function MobileContentExpanded({
44892
45452
  /* @__PURE__ */ jsx$1("div", { css: css2.sizeSelectorContainer, children: /* @__PURE__ */ jsx$1(SizeSelector, { loadedProductData, selectedSizeLabel, onChangeSize }) }),
44893
45453
  /* @__PURE__ */ jsx$1("div", { css: css2.itemFitTextContainer, children: /* @__PURE__ */ jsx$1(ItemFitText, { loadedProductData }) }),
44894
45454
  /* @__PURE__ */ jsx$1("div", { css: css2.itemFitDetailsContainer, children: /* @__PURE__ */ jsx$1(ItemFitDetails, { loadedProductData, selectedSizeLabel }) }),
44895
- /* @__PURE__ */ jsx$1("div", { css: css2.buttonContainer, children: /* @__PURE__ */ jsx$1(AddToCartButton, { onClick: onAddToCart }) }),
44896
- /* @__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 }) })
44897
45460
  ] });
44898
45461
  }
44899
45462
  function MobileContentFull({
@@ -44988,8 +45551,11 @@ function MobileContentFull({
44988
45551
  ] }),
44989
45552
  fitChartNode,
44990
45553
  /* @__PURE__ */ jsx$1("div", { css: css2.colorSelectorContainer, children: /* @__PURE__ */ jsx$1(ColorSelector, { availableColorLabels, selectedColorLabel, onChangeColor }) }),
44991
- /* @__PURE__ */ jsx$1("div", { css: css2.buttonContainer, children: /* @__PURE__ */ jsx$1(AddToCartButton, { onClick: onAddToCart }) }),
44992
- /* @__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 }) }),
44993
45559
  /* @__PURE__ */ jsx$1("div", { css: css2.priceContainer, children: /* @__PURE__ */ jsx$1(Text, { variant: "base", css: css2.priceText, children: selectedColorSizeRecord.priceFormatted }) }),
44994
45560
  /* @__PURE__ */ jsx$1("div", { css: css2.productDescriptionContainer, children: /* @__PURE__ */ jsx$1(ProductDescriptionText, { loadedProductData }) }),
44995
45561
  /* @__PURE__ */ jsx$1("div", { css: css2.footerContainer, children: /* @__PURE__ */ jsx$1(Footer, { onSignOutClick: onSignOut }) })
@@ -45105,7 +45671,7 @@ function DesktopLayout({
45105
45671
  return /* @__PURE__ */ jsxs("div", { css: css2.mainContainer, children: [
45106
45672
  /* @__PURE__ */ jsx$1(Avatar, { frameUrls, setModalStyle }),
45107
45673
  /* @__PURE__ */ jsxs("div", { css: css2.rightContainer, children: [
45108
- /* @__PURE__ */ jsx$1(ModalTitlebar, { title: t("try_it_on"), onCloseClick: onClose }),
45674
+ /* @__PURE__ */ jsx$1(ModalTitlebar, { title: t("quick-view.title"), onCloseClick: onClose }),
45109
45675
  /* @__PURE__ */ jsxs("div", { css: css2.contentContainer, children: [
45110
45676
  /* @__PURE__ */ jsx$1("div", { css: css2.productNameContainer, children: /* @__PURE__ */ jsx$1(Text, { variant: "brand", css: css2.productNameText, children: loadedProductData.productName }) }),
45111
45677
  /* @__PURE__ */ jsx$1("div", { css: css2.priceContainer, children: /* @__PURE__ */ jsx$1(Text, { variant: "base", css: css2.priceText, children: selectedColorSizeRecord.priceFormatted }) }),
@@ -45121,7 +45687,10 @@ function DesktopLayout({
45121
45687
  /* @__PURE__ */ jsx$1("div", { css: css2.itemFitDetailsContainer, children: /* @__PURE__ */ jsx$1(ItemFitDetails, { loadedProductData, selectedSizeLabel }) })
45122
45688
  ] }),
45123
45689
  fitChartNode,
45124
- /* @__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
+ ] }),
45125
45694
  /* @__PURE__ */ jsx$1("div", { css: css2.descriptionContainer, children: /* @__PURE__ */ jsx$1(ProductDescriptionText, { loadedProductData }) })
45126
45695
  ] }),
45127
45696
  /* @__PURE__ */ jsx$1(Footer, { onSignOutClick: onSignOut })
@@ -45141,10 +45710,37 @@ function Avatar({
45141
45710
  bottomContainerStyle: {}
45142
45711
  });
45143
45712
  const [selectedFrameIndex, setSelectedFrameIndex] = reactExports.useState(null);
45713
+ const [zoomOpen, setZoomOpen] = reactExports.useState(false);
45144
45714
  const css2 = useCss((theme) => ({
45145
45715
  topContainer: {
45146
45716
  flex: "none",
45147
- 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"
45148
45744
  },
45149
45745
  bottomContainer: {
45150
45746
  position: "absolute",
@@ -45258,10 +45854,15 @@ function Avatar({
45258
45854
  }, [isMobileLayout]);
45259
45855
  const isReady = !!frameUrls && selectedFrameIndex != null;
45260
45856
  return /* @__PURE__ */ jsxs("div", { css: css2.topContainer, style: layoutData.topContainerStyle, children: [
45261
- /* @__PURE__ */ jsx$1(AvatarFrameViewer, { frameUrls, selectedFrameIndex, setSelectedFrameIndex, imageContainerStyle: layoutData.imageContainerStyle, imageStyle: layoutData.imageStyle, loadingT: "vto-single.avatar_loading" }),
45262
- 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: [
45263
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 }),
45264
- /* @__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 })
45265
45866
  ] }) }) : null
45266
45867
  ] });
45267
45868
  }
@@ -45316,7 +45917,7 @@ function ColorSelector({
45316
45917
  return null;
45317
45918
  }
45318
45919
  return /* @__PURE__ */ jsx$1("div", { css: css2.colorContainer, children: /* @__PURE__ */ jsxs("label", { children: [
45319
- /* @__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" }),
45320
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)) })
45321
45922
  ] }) });
45322
45923
  }
@@ -45363,11 +45964,11 @@ function Footer({
45363
45964
  }
45364
45965
  }));
45365
45966
  return /* @__PURE__ */ jsxs("div", { css: css2.container, children: [
45366
- /* @__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" }),
45367
45968
  /* @__PURE__ */ jsx$1(SvgTfrName, { css: css2.tfrIcon })
45368
45969
  ] });
45369
45970
  }
45370
- const logger$5 = getLogger("widgets/add-to-fitting-room-compact");
45971
+ const logger$4 = getLogger("widgets/add-to-fitting-room-compact");
45371
45972
  function AddToFittingRoomCompactWidget({
45372
45973
  attributes
45373
45974
  }) {
@@ -45413,7 +46014,7 @@ function AddToFittingRoomCompactWidget({
45413
46014
  }
45414
46015
  const handleClick = () => {
45415
46016
  toggleFittingRoomItem(productId, productHandle, isPdp).catch((error) => {
45416
- logger$5.logError("toggleFittingRoomItem failed", {
46017
+ logger$4.logError("toggleFittingRoomItem failed", {
45417
46018
  error
45418
46019
  });
45419
46020
  });
@@ -45421,64 +46022,8 @@ function AddToFittingRoomCompactWidget({
45421
46022
  const ariaLabel = t(isInFittingRoom ? "added_to_fitting_room" : "add_to_fitting_room");
45422
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, "", ""] }) });
45423
46024
  }
45424
- const logger$4 = getLogger("widgets/add-to-fitting-room");
45425
- function AddToFittingRoomWidget({
45426
- attributes
45427
- }) {
45428
- const {
45429
- currentProduct
45430
- } = getStaticData();
45431
- const attrProductId = attributes["product-id"];
45432
- const attrProductHandle = attributes["product-handle"];
45433
- const productId = attrProductId || currentProduct?.externalId || null;
45434
- const isPdp = productId != null && productId === currentProduct?.externalId;
45435
- const productHandle = attrProductHandle || (isPdp ? currentProduct?.handle ?? null : null);
45436
- const isInFittingRoom = useMainStore((state) => productId == null ? false : state.fittingRoom.some((item) => item.externalId === productId));
45437
- const css2 = useCss((theme) => ({
45438
- button: {
45439
- marginTop: "10px",
45440
- marginBottom: "10px",
45441
- width: "100%",
45442
- maxWidth: "440px",
45443
- display: "flex",
45444
- alignItems: "center",
45445
- justifyContent: "center",
45446
- gap: "10px",
45447
- padding: "13px",
45448
- backgroundColor: "white",
45449
- borderWidth: "1px",
45450
- borderColor: theme.color_fg_text,
45451
- borderStyle: "solid",
45452
- borderRadius: "30px",
45453
- cursor: "pointer"
45454
- },
45455
- icon: {
45456
- color: theme.color_fg_text,
45457
- width: "20px",
45458
- height: "20px"
45459
- },
45460
- text: {
45461
- fontSize: "14px",
45462
- textTransform: "uppercase"
45463
- }
45464
- }));
45465
- if (productId == null) {
45466
- return null;
45467
- }
45468
- const handleClick = () => {
45469
- toggleFittingRoomItem(productId, productHandle, isPdp).catch((error) => {
45470
- logger$4.logError("toggleFittingRoomItem failed", {
45471
- error
45472
- });
45473
- });
45474
- };
45475
- return /* @__PURE__ */ jsxs("button", { type: "button", onClick: handleClick, css: css2.button, children: [
45476
- /* @__PURE__ */ jsx$1(SvgFittingRoomIcon, { css: css2.icon }),
45477
- /* @__PURE__ */ jsx$1(TextT, { variant: "base", css: css2.text, t: isInFittingRoom ? "added_to_fitting_room" : "add_to_fitting_room" })
45478
- ] });
45479
- }
45480
46025
  const logger$3 = getLogger("widgets/fitting-room-icon");
45481
- function FittingRoomIconWidget({}) {
46026
+ function FittingRoomIconWidget(_props) {
45482
46027
  const {
45483
46028
  t
45484
46029
  } = useTranslation();
@@ -45540,7 +46085,7 @@ function FittingRoomIconWidget({}) {
45540
46085
  ] });
45541
46086
  }
45542
46087
  const logger$2 = getLogger("widgets/fitting-room");
45543
- function FittingRoomWidget({}) {
46088
+ function FittingRoomWidget(_props) {
45544
46089
  const count = useMainStore((state) => state.fittingRoom.length);
45545
46090
  const openOverlay = useMainStore((state) => state.openOverlay);
45546
46091
  const css2 = useCss((theme) => ({
@@ -45597,11 +46142,11 @@ function FittingRoomWidget({}) {
45597
46142
  ] });
45598
46143
  }
45599
46144
  const logger$1 = getLogger("size-rec");
45600
- function SizeRecWidget({}) {
46145
+ function SizeRecWidget(_props) {
45601
46146
  const openOverlay = useMainStore((state) => state.openOverlay);
45602
46147
  const openedOverlays = useMainStore((state) => state.openedOverlays);
45603
46148
  const storeProductData = useMainStore((state) => state.productData);
45604
- const hasOpenedVtoSingleOverlay = openedOverlays.includes(OverlayName.VTO_SINGLE);
46149
+ const hasOpenedQuickViewOverlay = openedOverlays.includes(OverlayName.QUICK_VIEW);
45605
46150
  const sizeRecommendationRecord = reactExports.useMemo(() => {
45606
46151
  const {
45607
46152
  currentProduct
@@ -45625,9 +46170,9 @@ function SizeRecWidget({}) {
45625
46170
  return productData.sizeFitRecommendation || null;
45626
46171
  }, [storeProductData]);
45627
46172
  const handleLinkClick = reactExports.useCallback(() => {
45628
- openOverlay(OverlayName.VTO_SINGLE);
46173
+ openOverlay(OverlayName.QUICK_VIEW);
45629
46174
  }, [openOverlay]);
45630
- if (!sizeRecommendationRecord || !hasOpenedVtoSingleOverlay) {
46175
+ if (!sizeRecommendationRecord || !hasOpenedQuickViewOverlay) {
45631
46176
  return null;
45632
46177
  }
45633
46178
  const sizeLabel = getSizeLabelFromSize(sizeRecommendationRecord.recommended_size);
@@ -45639,8 +46184,11 @@ function SizeRecWidget({}) {
45639
46184
  } });
45640
46185
  }
45641
46186
  const logger = getLogger("widgets/vto-button");
45642
- function VtoButtonWidget({}) {
46187
+ function VtoButtonWidget(_props) {
45643
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));
45644
46192
  const css2 = useCss((theme) => ({
45645
46193
  button: {
45646
46194
  marginTop: "10px",
@@ -45667,13 +46215,29 @@ function VtoButtonWidget({}) {
45667
46215
  textTransform: "uppercase"
45668
46216
  }
45669
46217
  }));
45670
- const openVto = () => {
45671
- logger.logDebug("{{ts}} - Opening VTO overlay");
45672
- 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
+ });
45673
46237
  };
45674
- 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: [
45675
46239
  /* @__PURE__ */ jsx$1(SvgTfrIcon, { css: css2.icon }),
45676
- /* @__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" })
45677
46241
  ] });
45678
46242
  }
45679
46243
  var DeviceLayout = /* @__PURE__ */ ((DeviceLayout2) => {
@@ -45738,10 +46302,6 @@ function _init$1() {
45738
46302
  });
45739
46303
  }
45740
46304
  const WIDGETS = {
45741
- [
45742
- "add-to-fitting-room"
45743
- /* ADD_TO_FITTING_ROOM */
45744
- ]: AddToFittingRoomWidget,
45745
46305
  [
45746
46306
  "add-to-fitting-room-compact"
45747
46307
  /* ADD_TO_FITTING_ROOM_COMPACT */
@@ -45769,7 +46329,7 @@ var OverlayName = /* @__PURE__ */ ((OverlayName2) => {
45769
46329
  OverlayName2["GET_APP"] = "get-app";
45770
46330
  OverlayName2["LANDING"] = "landing";
45771
46331
  OverlayName2["SIGN_IN"] = "sign-in";
45772
- OverlayName2["VTO_SINGLE"] = "vto-single";
46332
+ OverlayName2["QUICK_VIEW"] = "quick-view";
45773
46333
  return OverlayName2;
45774
46334
  })(OverlayName || {});
45775
46335
  const OVERLAYS = {
@@ -45794,9 +46354,9 @@ const OVERLAYS = {
45794
46354
  /* SIGN_IN */
45795
46355
  ]: SignInOverlay,
45796
46356
  [
45797
- "vto-single"
45798
- /* VTO_SINGLE */
45799
- ]: VtoSingleOverlay
46357
+ "quick-view"
46358
+ /* QUICK_VIEW */
46359
+ ]: QuickViewOverlay
45800
46360
  };
45801
46361
  let staticData = null;
45802
46362
  function _init(initStaticData) {
@@ -45941,9 +46501,9 @@ const SHARED_CONFIG = {
45941
46501
  appGooglePlayUrl: "https://play.google.com/store/apps/details?id=com.thefittingroom.marketplace"
45942
46502
  },
45943
46503
  build: {
45944
- version: `${"5.0.23"}`,
45945
- commitHash: `${"ab793d5"}`,
45946
- date: `${"2026-05-19T13:40:12.965Z"}`
46504
+ version: `${"5.0.24"}`,
46505
+ commitHash: `${"931e0b2"}`,
46506
+ date: `${"2026-05-19T21:17:10.057Z"}`
45947
46507
  }
45948
46508
  };
45949
46509
  const CONFIGS = {
@@ -46067,7 +46627,7 @@ const CONFIGS = {
46067
46627
  const getConfig = (envName) => {
46068
46628
  return CONFIGS[envName];
46069
46629
  };
46070
- 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";
46071
46631
  class TfrWidgetElement extends HTMLElement {
46072
46632
  connectedCallback() {
46073
46633
  const attributes = this.getAttributeNames().reduce((attrs, name2) => {