@thefittingroom/shop-ui 5.0.23 → 5.0.25

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 (3) hide show
  1. package/README.md +18 -1
  2. package/dist/index.js +1209 -383
  3. package/package.json +19 -3
package/dist/index.js CHANGED
@@ -14101,16 +14101,8 @@ 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
14106
  logger$g.logDebug("{{ts}} - Adding to fitting room", {
14115
14107
  productId,
14116
14108
  handle,
@@ -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$g.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;
@@ -41000,11 +41051,148 @@ registerAuth(
41000
41051
  "Browser"
41001
41052
  /* ClientPlatform.BROWSER */
41002
41053
  );
41054
+ const logger$e = getLogger("firebase-mock");
41055
+ class MockFirestoreManager {
41056
+ constructor(seedDocs = {}) {
41057
+ const cloned = {};
41058
+ for (const [coll, byId] of Object.entries(seedDocs)) {
41059
+ cloned[coll] = {
41060
+ ...byId
41061
+ };
41062
+ }
41063
+ this.docs = cloned;
41064
+ }
41065
+ async getDocData(collectionName, docId) {
41066
+ const doc2 = this.docs[collectionName]?.[docId];
41067
+ return doc2 ?? null;
41068
+ }
41069
+ async setDocData(collectionName, docId, data) {
41070
+ if (!this.docs[collectionName]) {
41071
+ this.docs[collectionName] = {};
41072
+ }
41073
+ this.docs[collectionName][docId] = data;
41074
+ }
41075
+ async mergeDocData(collectionName, docId, data) {
41076
+ if (!this.docs[collectionName]) {
41077
+ this.docs[collectionName] = {};
41078
+ }
41079
+ const existing = this.docs[collectionName][docId] ?? {};
41080
+ this.docs[collectionName][docId] = {
41081
+ ...existing,
41082
+ ...data
41083
+ };
41084
+ }
41085
+ listenToDoc(collectionName, docId, callback) {
41086
+ const doc2 = this.docs[collectionName]?.[docId];
41087
+ callback(doc2 ?? null);
41088
+ return () => {
41089
+ };
41090
+ }
41091
+ async queryDocs(collectionName, _constraints) {
41092
+ const entries = Object.values(this.docs[collectionName] ?? {});
41093
+ const docs = entries.map((data) => ({
41094
+ data: () => data
41095
+ }));
41096
+ const snapshot = {
41097
+ empty: docs.length === 0,
41098
+ docs,
41099
+ forEach: (cb) => docs.forEach(cb)
41100
+ };
41101
+ return snapshot;
41102
+ }
41103
+ }
41104
+ function makeMockAuthUser(seed) {
41105
+ const fake = {
41106
+ uid: seed.uid,
41107
+ email: seed.email,
41108
+ getIdToken: async (_forceRefresh) => seed.idToken
41109
+ };
41110
+ return fake;
41111
+ }
41112
+ class MockAuthManager {
41113
+ constructor(seed, firestore) {
41114
+ this.userProfile = null;
41115
+ this.authStateChangeListeners = /* @__PURE__ */ new Set();
41116
+ this.userProfileChangeListeners = /* @__PURE__ */ new Set();
41117
+ this.profileUnsub = null;
41118
+ this.seed = seed;
41119
+ this.currentUser = seed ? makeMockAuthUser(seed) : null;
41120
+ this.firestore = firestore;
41121
+ this.addAuthStateChangeListener((authUser) => this.handleAuthStateChanged(authUser));
41122
+ }
41123
+ addAuthStateChangeListener(callback) {
41124
+ this.authStateChangeListeners.add(callback);
41125
+ callback(this.currentUser);
41126
+ return () => {
41127
+ this.authStateChangeListeners.delete(callback);
41128
+ };
41129
+ }
41130
+ removeAuthStateChangeListener(callback) {
41131
+ this.authStateChangeListeners.delete(callback);
41132
+ }
41133
+ addUserProfileChangeListener(callback) {
41134
+ this.userProfileChangeListeners.add(callback);
41135
+ callback(this.userProfile);
41136
+ return () => {
41137
+ this.userProfileChangeListeners.delete(callback);
41138
+ };
41139
+ }
41140
+ removeUserProfileChangeListener(callback) {
41141
+ this.userProfileChangeListeners.delete(callback);
41142
+ }
41143
+ getAuthUser() {
41144
+ return this.currentUser;
41145
+ }
41146
+ async getAuthToken(_forceRefresh = false) {
41147
+ if (!this.currentUser || !this.seed) {
41148
+ throw new Error("No authenticated user");
41149
+ }
41150
+ return this.seed.idToken;
41151
+ }
41152
+ async getUserProfile(_forceRefresh = false) {
41153
+ return this.userProfile;
41154
+ }
41155
+ async login(_email, _password) {
41156
+ throw new Error("MockAuthManager.login is not supported — seed the user via InitParams.testHooks.auth");
41157
+ }
41158
+ async logout() {
41159
+ if (this.profileUnsub) {
41160
+ this.profileUnsub();
41161
+ this.profileUnsub = null;
41162
+ }
41163
+ this.currentUser = null;
41164
+ this.userProfile = null;
41165
+ this.authStateChangeListeners.forEach((cb) => cb(null));
41166
+ this.userProfileChangeListeners.forEach((cb) => cb(null));
41167
+ }
41168
+ async sendPasswordResetEmail(_email) {
41169
+ }
41170
+ async confirmPasswordReset(_code, _newPassword) {
41171
+ }
41172
+ handleAuthStateChanged(authUser) {
41173
+ if (this.profileUnsub) {
41174
+ this.profileUnsub();
41175
+ this.profileUnsub = null;
41176
+ }
41177
+ if (authUser) {
41178
+ logger$e.logDebug("Mock user logged in:", {
41179
+ uid: authUser.uid
41180
+ });
41181
+ this.profileUnsub = this.firestore.listenToDoc("users", authUser.uid, (profile) => {
41182
+ this.userProfile = profile;
41183
+ this.userProfileChangeListeners.forEach((cb) => cb(this.userProfile));
41184
+ });
41185
+ } else {
41186
+ this.userProfile = null;
41187
+ this.userProfileChangeListeners.forEach((cb) => cb(null));
41188
+ }
41189
+ }
41190
+ }
41003
41191
  const firebaseDateToDayjs = (date) => {
41004
41192
  return dayjs(date.seconds * 1e3);
41005
41193
  };
41006
41194
  const LOGIN_TRACKING_PERIOD_SECONDS = 1800;
41007
- const logger$e = getLogger("firebase");
41195
+ const logger$d = getLogger("firebase");
41008
41196
  let firebaseApp = null;
41009
41197
  class FirestoreManager {
41010
41198
  constructor(firestore) {
@@ -41132,14 +41320,14 @@ class AuthManager {
41132
41320
  this.listenToUserProfileUnsub = null;
41133
41321
  }
41134
41322
  if (authUser) {
41135
- logger$e.logDebug("User logged in:", {
41323
+ logger$d.logDebug("User logged in:", {
41136
41324
  uid: authUser.uid
41137
41325
  });
41138
41326
  this.listenToUserProfileUnsub = getFirestoreManager().listenToDoc("users", authUser.uid, (doc2) => {
41139
41327
  this.userProfile = doc2;
41140
41328
  this.userProfileChangeListeners.forEach((callback) => callback(this.userProfile));
41141
41329
  });
41142
- (async () => {
41330
+ void (async () => {
41143
41331
  try {
41144
41332
  const firestore = getFirestoreManager();
41145
41333
  const userLoggingDocId = authUser.uid;
@@ -41166,7 +41354,7 @@ class AuthManager {
41166
41354
  await firestore.mergeDocData("user_logging", userLoggingDocId, userLoggingData);
41167
41355
  }
41168
41356
  } catch (error) {
41169
- logger$e.logError("Error logging user login activity:", {
41357
+ logger$d.logError("Error logging user login activity:", {
41170
41358
  error
41171
41359
  });
41172
41360
  }
@@ -41187,8 +41375,24 @@ function getAuthManager() {
41187
41375
  async function _init$4() {
41188
41376
  const {
41189
41377
  brandId,
41190
- config
41378
+ config,
41379
+ testHooks
41191
41380
  } = getStaticData();
41381
+ if (testHooks !== void 0) {
41382
+ const seedDocs = {
41383
+ ...testHooks.firestore?.docs ?? {}
41384
+ };
41385
+ if (testHooks.auth?.profile) {
41386
+ seedDocs.users = {
41387
+ ...seedDocs.users ?? {},
41388
+ [testHooks.auth.uid]: testHooks.auth.profile
41389
+ };
41390
+ }
41391
+ firestoreManager = new MockFirestoreManager(seedDocs);
41392
+ authManager = new MockAuthManager(testHooks.auth ?? null, firestoreManager);
41393
+ logger$d.logDebug("Firebase initialized in MOCK mode (testHooks present)");
41394
+ return;
41395
+ }
41192
41396
  {
41193
41397
  const {
41194
41398
  firebase: sdkFirebaseConfig
@@ -41271,7 +41475,9 @@ async function execApiRequest(params) {
41271
41475
  try {
41272
41476
  response = await fetch(url, options);
41273
41477
  } finally {
41274
- if (timeoutHandle) clearTimeout(timeoutHandle);
41478
+ if (timeoutHandle) {
41479
+ clearTimeout(timeoutHandle);
41480
+ }
41275
41481
  }
41276
41482
  if (!response.ok) {
41277
41483
  let detail = "";
@@ -41364,7 +41570,7 @@ async function getStyleByExternalId(brandId, externalId) {
41364
41570
  recordCache[cacheKey] = record;
41365
41571
  return record;
41366
41572
  }
41367
- const logger$d = getLogger("product");
41573
+ const logger$c = getLogger("product");
41368
41574
  function _init$2() {
41369
41575
  useMainStore.subscribe((state, prevState) => {
41370
41576
  if (state.userHasAvatar && !prevState.userHasAvatar) {
@@ -41397,11 +41603,11 @@ function loadProductDataToStore(externalId) {
41397
41603
  try {
41398
41604
  const productData2 = await loadProductData(externalId);
41399
41605
  useMainStore.getState().setProductData(productData2.externalId, productData2);
41400
- logger$d.logDebug(`Loaded product data for externalId: ${externalId}`, {
41606
+ logger$c.logDebug(`Loaded product data for externalId: ${externalId}`, {
41401
41607
  productData: productData2
41402
41608
  });
41403
41609
  } catch (error) {
41404
- logger$d.logError(`Error loading product data for externalId: ${externalId}`, {
41610
+ logger$c.logError(`Error loading product data for externalId: ${externalId}`, {
41405
41611
  error
41406
41612
  });
41407
41613
  }
@@ -41414,9 +41620,9 @@ function loadProductDataToStore(externalId) {
41414
41620
  if (productData[externalId] || !userIsLoggedIn || userHasAvatar === false) {
41415
41621
  return;
41416
41622
  }
41417
- loadAndStore();
41623
+ void loadAndStore();
41418
41624
  }
41419
- const logger$c = getLogger("style-categories");
41625
+ const logger$b = getLogger("style-categories");
41420
41626
  let cached = null;
41421
41627
  let inflight = null;
41422
41628
  function buildIndex(categories, groups) {
@@ -41460,7 +41666,7 @@ async function loadStyleCategoryIndex() {
41460
41666
  }
41461
41667
  inflight = (async () => {
41462
41668
  try {
41463
- logger$c.logDebug("{{ts}} - Loading style-category index");
41669
+ logger$b.logDebug("{{ts}} - Loading style-category index");
41464
41670
  const [categories, groups] = await Promise.all([getStyleCategories(), getStyleCategoryGroups()]);
41465
41671
  cached = buildIndex(categories, groups);
41466
41672
  return cached;
@@ -41473,12 +41679,12 @@ async function loadStyleCategoryIndex() {
41473
41679
  function peekStyleCategoryIndex() {
41474
41680
  return cached;
41475
41681
  }
41476
- const logger$b = getLogger("fitting-room-data");
41682
+ const logger$a = getLogger("fitting-room-data");
41477
41683
  async function loadFittingRoomData() {
41478
41684
  const state = useMainStore.getState();
41479
41685
  const items = state.fittingRoom;
41480
41686
  loadStyleCategoryIndex().catch((error) => {
41481
- logger$b.logError("Failed to load style-category index", {
41687
+ logger$a.logError("Failed to load style-category index", {
41482
41688
  error
41483
41689
  });
41484
41690
  });
@@ -41490,7 +41696,9 @@ async function loadFittingRoomData() {
41490
41696
  } = getStaticData();
41491
41697
  if (!productLookup) {
41492
41698
  for (const item of items) {
41493
- if (state.merchantProductData[item.externalId]) continue;
41699
+ if (state.merchantProductData[item.externalId]) {
41700
+ continue;
41701
+ }
41494
41702
  state.setMerchantProductData(item.externalId, {
41495
41703
  error: new Error("No productLookup callback configured")
41496
41704
  });
@@ -41526,7 +41734,7 @@ async function loadFittingRoomData() {
41526
41734
  }
41527
41735
  }
41528
41736
  } catch (error) {
41529
- logger$b.logError("productLookup batch failed", {
41737
+ logger$a.logError("productLookup batch failed", {
41530
41738
  error,
41531
41739
  handles
41532
41740
  });
@@ -41561,7 +41769,7 @@ function resolveItem(item, merchantSlot, loadedSlot, index) {
41561
41769
  const found = loadedProduct.sizeFitRecommendation.available_sizes.some((sz) => sz.colorway_size_assets.some((csa) => csa.id === item.colorwaySizeAssetId));
41562
41770
  if (!found) {
41563
41771
  needsResize = true;
41564
- logger$b.logDebug("csa no longer in size rec, marking needsResize", {
41772
+ logger$a.logDebug("csa no longer in size rec, marking needsResize", {
41565
41773
  externalId: item.externalId,
41566
41774
  csa: item.colorwaySizeAssetId
41567
41775
  });
@@ -41611,7 +41819,7 @@ function useResolvedFittingRoom() {
41611
41819
  }, []);
41612
41820
  reactExports.useEffect(() => {
41613
41821
  loadFittingRoomData().catch((error) => {
41614
- logger$b.logError("loadFittingRoomData failed", {
41822
+ logger$a.logError("loadFittingRoomData failed", {
41615
41823
  error
41616
41824
  });
41617
41825
  });
@@ -41662,12 +41870,14 @@ function buildVtoProductDataFromResolved(item) {
41662
41870
  merchantProduct,
41663
41871
  loadedProduct
41664
41872
  } = item;
41665
- if (!merchantProduct || !loadedProduct) return null;
41873
+ if (!merchantProduct || !loadedProduct) {
41874
+ return null;
41875
+ }
41666
41876
  const sizeRec = loadedProduct.sizeFitRecommendation;
41667
41877
  const recommendedSizeId = sizeRec.recommended_size.id || null;
41668
41878
  const recommendedSizeLabel = getSizeLabelFromSize(sizeRec.recommended_size);
41669
41879
  if (recommendedSizeId == null || !recommendedSizeLabel) {
41670
- logger$b.logWarn("Missing recommended size for item", {
41880
+ logger$a.logWarn("Missing recommended size for item", {
41671
41881
  externalId: item.externalId
41672
41882
  });
41673
41883
  return null;
@@ -41675,13 +41885,19 @@ function buildVtoProductDataFromResolved(item) {
41675
41885
  const sizes = [];
41676
41886
  for (const sizeRecord of sizeRec.available_sizes) {
41677
41887
  const sizeLabel = getSizeLabelFromSize(sizeRecord);
41678
- if (!sizeLabel) continue;
41888
+ if (!sizeLabel) {
41889
+ continue;
41890
+ }
41679
41891
  const fit = sizeRec.fits.find((f) => f.size_id === sizeRecord.id);
41680
- if (!fit) continue;
41892
+ if (!fit) {
41893
+ continue;
41894
+ }
41681
41895
  const colors = [];
41682
41896
  for (const csa of sizeRecord.colorway_size_assets) {
41683
41897
  const variant = merchantProduct.variants.find((v) => v.sku === csa.sku);
41684
- if (!variant) continue;
41898
+ if (!variant) {
41899
+ continue;
41900
+ }
41685
41901
  colors.push({
41686
41902
  colorwaySizeAssetId: csa.id,
41687
41903
  colorLabel: variant.color || null,
@@ -41689,7 +41905,9 @@ function buildVtoProductDataFromResolved(item) {
41689
41905
  priceFormatted: variant.priceFormatted
41690
41906
  });
41691
41907
  }
41692
- if (colors.length === 0) continue;
41908
+ if (colors.length === 0) {
41909
+ continue;
41910
+ }
41693
41911
  sizes.push({
41694
41912
  sizeId: sizeRecord.id,
41695
41913
  sizeLabel,
@@ -41698,7 +41916,9 @@ function buildVtoProductDataFromResolved(item) {
41698
41916
  colors
41699
41917
  });
41700
41918
  }
41701
- if (sizes.length === 0) return null;
41919
+ if (sizes.length === 0) {
41920
+ return null;
41921
+ }
41702
41922
  return {
41703
41923
  productName: merchantProduct.productName,
41704
41924
  productDescriptionHtml: merchantProduct.productDescriptionHtml,
@@ -41711,12 +41931,16 @@ function buildVtoProductDataFromResolved(item) {
41711
41931
  }
41712
41932
  function findRecommendedColorSize(data, preferredColor) {
41713
41933
  const recommended = data.sizes.find((s) => s.isRecommended);
41714
- if (!recommended || recommended.colors.length === 0) return null;
41934
+ if (!recommended || recommended.colors.length === 0) {
41935
+ return null;
41936
+ }
41715
41937
  return recommended.colors.find((c) => c.colorLabel === preferredColor) ?? recommended.colors[0];
41716
41938
  }
41717
41939
  function findCsaByLabel(data, sizeLabel, preferredColor) {
41718
41940
  const sizeRecord = data.sizes.find((s) => s.sizeLabel === sizeLabel);
41719
- if (!sizeRecord || sizeRecord.colors.length === 0) return null;
41941
+ if (!sizeRecord || sizeRecord.colors.length === 0) {
41942
+ return null;
41943
+ }
41720
41944
  return sizeRecord.colors.find((c) => c.colorLabel === preferredColor) ?? sizeRecord.colors[0];
41721
41945
  }
41722
41946
  function useMobileSheetSnap(initial = "collapsed") {
@@ -41726,7 +41950,9 @@ function useMobileSheetSnap(initial = "collapsed") {
41726
41950
  const initialSnap = snap;
41727
41951
  const onTouchMove = (moveEvent) => {
41728
41952
  const deltaY = moveEvent.touches[0].clientY - startY;
41729
- if (Math.abs(deltaY) < 30) return;
41953
+ if (Math.abs(deltaY) < 30) {
41954
+ return;
41955
+ }
41730
41956
  if (deltaY > 0) {
41731
41957
  if (initialSnap === "full" || initialSnap === "expanded") {
41732
41958
  setSnap("collapsed");
@@ -41755,11 +41981,16 @@ function useMobileSheetSnap(initial = "collapsed") {
41755
41981
  }
41756
41982
  const MAX_OUTFIT_ITEMS = 4;
41757
41983
  function asStringList(value) {
41758
- if (!Array.isArray(value)) return [];
41984
+ if (!Array.isArray(value)) {
41985
+ return [];
41986
+ }
41759
41987
  const out = [];
41760
41988
  for (const v of value) {
41761
- if (typeof v === "string") out.push(v);
41762
- else if (v != null) out.push(String(v));
41989
+ if (typeof v === "string") {
41990
+ out.push(v);
41991
+ } else if (v != null) {
41992
+ out.push(String(v));
41993
+ }
41763
41994
  }
41764
41995
  return out;
41765
41996
  }
@@ -41800,8 +42031,12 @@ function computeAvailability(item, selectedExternalIds, resolved) {
41800
42031
  }
41801
42032
  const itemCat = item.styleCategory;
41802
42033
  for (const sel of resolved.items) {
41803
- if (!selectedExternalIds.has(sel.externalId)) continue;
41804
- if (!sel.styleCategory) continue;
42034
+ if (!selectedExternalIds.has(sel.externalId)) {
42035
+ continue;
42036
+ }
42037
+ if (!sel.styleCategory) {
42038
+ continue;
42039
+ }
41805
42040
  if (!pairCompatible(sel.styleCategory, itemCat, sel.styleCategoryGroup)) {
41806
42041
  return "disabled";
41807
42042
  }
@@ -41809,8 +42044,12 @@ function computeAvailability(item, selectedExternalIds, resolved) {
41809
42044
  return "available";
41810
42045
  }
41811
42046
  function makeOutfitItem(r, forceUntuck) {
41812
- if (!r.styleCategory) return null;
41813
- if (r.storage.colorwaySizeAssetId == null) return null;
42047
+ if (!r.styleCategory) {
42048
+ return null;
42049
+ }
42050
+ if (r.storage.colorwaySizeAssetId == null) {
42051
+ return null;
42052
+ }
41814
42053
  const tuckable = !!r.styleCategory.tuckable;
41815
42054
  const untucked = forceUntuck && tuckable;
41816
42055
  const layerOrder = untucked ? r.styleCategory.layer_order_untucked : r.styleCategory.layer_order;
@@ -41830,10 +42069,16 @@ function buildOutfit(selectedExternalIds, resolved, forceUntuck, lastAddedExtern
41830
42069
  const entries = [];
41831
42070
  let lastAddedResolved = null;
41832
42071
  for (const r of resolved.items) {
41833
- if (!selectedExternalIds.has(r.externalId)) continue;
41834
- if (r.externalId === lastAddedExternalId) lastAddedResolved = r;
42072
+ if (!selectedExternalIds.has(r.externalId)) {
42073
+ continue;
42074
+ }
42075
+ if (r.externalId === lastAddedExternalId) {
42076
+ lastAddedResolved = r;
42077
+ }
41835
42078
  const entry = makeOutfitItem(r, forceUntuck);
41836
- if (entry) entries.push(entry);
42079
+ if (entry) {
42080
+ entries.push(entry);
42081
+ }
41837
42082
  }
41838
42083
  entries.sort((a, b) => a.layerOrder - b.layerOrder);
41839
42084
  const items = entries.slice(0, MAX_OUTFIT_ITEMS).map((e) => e.outfitItem);
@@ -41844,10 +42089,16 @@ function buildOutfit(selectedExternalIds, resolved, forceUntuck, lastAddedExtern
41844
42089
  };
41845
42090
  }
41846
42091
  function buildAlternateOutfits(primary, lastAddedResolved) {
41847
- if (!lastAddedResolved || !lastAddedResolved.loadedProduct) return [];
42092
+ if (!lastAddedResolved || !lastAddedResolved.loadedProduct) {
42093
+ return [];
42094
+ }
41848
42095
  const currentCsaId = lastAddedResolved.storage.colorwaySizeAssetId;
41849
- if (currentCsaId == null) return [];
41850
- if (!primary.some((i) => i.externalId === lastAddedResolved.externalId)) return [];
42096
+ if (currentCsaId == null) {
42097
+ return [];
42098
+ }
42099
+ if (!primary.some((i) => i.externalId === lastAddedResolved.externalId)) {
42100
+ return [];
42101
+ }
41851
42102
  const sizeRec = lastAddedResolved.loadedProduct.sizeFitRecommendation;
41852
42103
  let currentColorwayId = null;
41853
42104
  for (const sz of sizeRec.available_sizes) {
@@ -41860,7 +42111,9 @@ function buildAlternateOutfits(primary, lastAddedResolved) {
41860
42111
  const out = [];
41861
42112
  for (const sz of sizeRec.available_sizes) {
41862
42113
  const altCsa = sz.colorway_size_assets.find((c) => c.id !== currentCsaId && (currentColorwayId == null || c.colorway_id === currentColorwayId));
41863
- if (!altCsa) continue;
42114
+ if (!altCsa) {
42115
+ continue;
42116
+ }
41864
42117
  const alternate = primary.map((it) => it.externalId === lastAddedResolved.externalId ? {
41865
42118
  ...it,
41866
42119
  colorwaySizeAssetId: altCsa.id
@@ -41869,6 +42122,24 @@ function buildAlternateOutfits(primary, lastAddedResolved) {
41869
42122
  }
41870
42123
  return out;
41871
42124
  }
42125
+ function pillBaseStyle(theme) {
42126
+ return {
42127
+ display: "inline-flex",
42128
+ alignItems: "center",
42129
+ gap: "8px",
42130
+ padding: "8px 16px",
42131
+ borderRadius: "24px",
42132
+ backgroundColor: "rgba(255, 255, 255, 0.95)",
42133
+ border: `1px solid ${theme.color_fg_text}`,
42134
+ fontSize: "12px",
42135
+ fontWeight: "500",
42136
+ letterSpacing: "0.5px",
42137
+ textTransform: "uppercase",
42138
+ cursor: "pointer",
42139
+ userSelect: "none",
42140
+ WebkitUserSelect: "none"
42141
+ };
42142
+ }
41872
42143
  function AvatarControls({
41873
42144
  selectedItems,
41874
42145
  canTuck,
@@ -41885,7 +42156,9 @@ function AvatarControls({
41885
42156
  const [popoverOpen, setPopoverOpen] = reactExports.useState(false);
41886
42157
  const popoverWrapperRef = reactExports.useRef(null);
41887
42158
  reactExports.useEffect(() => {
41888
- if (!popoverOpen) return;
42159
+ if (!popoverOpen) {
42160
+ return;
42161
+ }
41889
42162
  const onDocClick = (e) => {
41890
42163
  if (popoverWrapperRef.current && !popoverWrapperRef.current.contains(e.target)) {
41891
42164
  setPopoverOpen(false);
@@ -41908,20 +42181,7 @@ function AvatarControls({
41908
42181
  alignItems: "flex-end"
41909
42182
  },
41910
42183
  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",
42184
+ ...pillBaseStyle(theme),
41925
42185
  transition: "padding 500ms cubic-bezier(0.22, 1, 0.36, 1), gap 500ms cubic-bezier(0.22, 1, 0.36, 1)"
41926
42186
  },
41927
42187
  pillCollapsed: {
@@ -42023,13 +42283,114 @@ function AvatarControls({
42023
42283
  ] })
42024
42284
  ] });
42025
42285
  }
42286
+ function MobileTuckControl({
42287
+ canTuck,
42288
+ forceUntuck,
42289
+ onToggleUntuck
42290
+ }) {
42291
+ const {
42292
+ t
42293
+ } = useTranslation();
42294
+ const css2 = useCss((theme) => ({
42295
+ wrapper: {
42296
+ position: "absolute",
42297
+ bottom: "12px",
42298
+ right: "12px"
42299
+ // No z-index: the pill sits above the avatar image (later sibling than
42300
+ // the frame viewer) but below the product-details sheet, which is a
42301
+ // later sibling in the try-on view — so the sheet hides the pill when
42302
+ // it expands over the image.
42303
+ },
42304
+ pill: pillBaseStyle(theme),
42305
+ pillIcon: {
42306
+ width: "14px",
42307
+ height: "14px",
42308
+ flex: "none"
42309
+ }
42310
+ }));
42311
+ if (!canTuck) {
42312
+ return null;
42313
+ }
42314
+ return /* @__PURE__ */ jsx$1("div", { css: css2.wrapper, children: /* @__PURE__ */ jsxs(Button, { variant: "base", css: css2.pill, onClick: onToggleUntuck, children: [
42315
+ /* @__PURE__ */ jsx$1(SvgIconTuck, { css: css2.pillIcon }),
42316
+ /* @__PURE__ */ jsx$1(Text, { variant: "base", children: t(forceUntuck ? "fitting_room.tuck_in" : "fitting_room.untuck") })
42317
+ ] }) });
42318
+ }
42319
+ const DRAG_STEP_PX = 50;
42320
+ function useFrameRotation(frameUrls, setSelectedFrameIndex) {
42321
+ const frameCount = frameUrls?.length ?? 0;
42322
+ const rotateLeft = reactExports.useCallback(() => {
42323
+ setSelectedFrameIndex((prev2) => {
42324
+ if (prev2 == null || frameCount === 0) {
42325
+ return prev2;
42326
+ }
42327
+ return prev2 === 0 ? frameCount - 1 : prev2 - 1;
42328
+ });
42329
+ }, [frameCount, setSelectedFrameIndex]);
42330
+ const rotateRight = reactExports.useCallback(() => {
42331
+ setSelectedFrameIndex((prev2) => {
42332
+ if (prev2 == null || frameCount === 0) {
42333
+ return prev2;
42334
+ }
42335
+ return prev2 === frameCount - 1 ? 0 : prev2 + 1;
42336
+ });
42337
+ }, [frameCount, setSelectedFrameIndex]);
42338
+ const handleMouseDragStart = reactExports.useCallback((e) => {
42339
+ e.preventDefault();
42340
+ let startX = e.clientX;
42341
+ const onMove = (move) => {
42342
+ const deltaX = move.clientX - startX;
42343
+ if (Math.abs(deltaX) >= DRAG_STEP_PX) {
42344
+ if (deltaX > 0) {
42345
+ rotateRight();
42346
+ } else {
42347
+ rotateLeft();
42348
+ }
42349
+ startX = move.clientX;
42350
+ }
42351
+ };
42352
+ const onUp = () => {
42353
+ window.removeEventListener("mousemove", onMove);
42354
+ window.removeEventListener("mouseup", onUp);
42355
+ };
42356
+ window.addEventListener("mousemove", onMove);
42357
+ window.addEventListener("mouseup", onUp);
42358
+ }, [rotateLeft, rotateRight]);
42359
+ const handleTouchDragStart = reactExports.useCallback((e) => {
42360
+ e.preventDefault();
42361
+ let startX = e.touches[0].clientX;
42362
+ const onMove = (move) => {
42363
+ const deltaX = move.touches[0].clientX - startX;
42364
+ if (Math.abs(deltaX) >= DRAG_STEP_PX) {
42365
+ if (deltaX > 0) {
42366
+ rotateRight();
42367
+ } else {
42368
+ rotateLeft();
42369
+ }
42370
+ startX = move.touches[0].clientX;
42371
+ }
42372
+ };
42373
+ const onEnd = () => {
42374
+ window.removeEventListener("touchmove", onMove);
42375
+ window.removeEventListener("touchend", onEnd);
42376
+ };
42377
+ window.addEventListener("touchmove", onMove);
42378
+ window.addEventListener("touchend", onEnd);
42379
+ }, [rotateLeft, rotateRight]);
42380
+ return {
42381
+ rotateLeft,
42382
+ rotateRight,
42383
+ handleMouseDragStart,
42384
+ handleTouchDragStart
42385
+ };
42386
+ }
42026
42387
  function AvatarFrameViewer({
42027
42388
  frameUrls,
42028
42389
  selectedFrameIndex,
42029
42390
  setSelectedFrameIndex,
42030
42391
  imageContainerStyle,
42031
42392
  imageStyle,
42032
- loadingT = "vto-single.avatar_loading"
42393
+ loadingT = "quick-view.avatar_loading"
42033
42394
  }) {
42034
42395
  const css2 = useCss((_theme) => ({
42035
42396
  imageContainer: {
@@ -42073,59 +42434,17 @@ function AvatarFrameViewer({
42073
42434
  }, 500);
42074
42435
  return () => clearInterval(intervalId);
42075
42436
  }, [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]);
42437
+ const {
42438
+ rotateLeft,
42439
+ rotateRight,
42440
+ handleMouseDragStart,
42441
+ handleTouchDragStart
42442
+ } = useFrameRotation(frameUrls, setSelectedFrameIndex);
42124
42443
  if (!frameUrls || selectedFrameIndex == null) {
42125
42444
  return /* @__PURE__ */ jsx$1(Loading, { t: loadingT });
42126
42445
  }
42127
42446
  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 }),
42447
+ /* @__PURE__ */ jsx$1("img", { src: frameUrls[selectedFrameIndex], css: css2.image, style: imageStyle, onMouseDown: handleMouseDragStart, onTouchStart: handleTouchDragStart }),
42129
42448
  /* @__PURE__ */ jsx$1("div", { css: css2.chevronLeftContainer, onClick: rotateLeft, children: /* @__PURE__ */ jsx$1(SvgChevronLeft, { css: css2.chevronIcon }) }),
42130
42449
  /* @__PURE__ */ jsx$1("div", { css: css2.chevronRightContainer, onClick: rotateRight, children: /* @__PURE__ */ jsx$1(SvgChevronRight, { css: css2.chevronIcon }) })
42131
42450
  ] });
@@ -42134,9 +42453,13 @@ function AvatarPane({
42134
42453
  frameUrls,
42135
42454
  hasSelection,
42136
42455
  controls,
42137
- mobileFullscreen
42456
+ mobileFullscreen,
42457
+ selectedFrameIndex: indexProp,
42458
+ setSelectedFrameIndex: setIndexProp
42138
42459
  }) {
42139
- const [selectedFrameIndex, setSelectedFrameIndex] = reactExports.useState(null);
42460
+ const [localFrameIndex, setLocalFrameIndex] = reactExports.useState(null);
42461
+ const selectedFrameIndex = indexProp !== void 0 ? indexProp : localFrameIndex;
42462
+ const setSelectedFrameIndex = setIndexProp ?? setLocalFrameIndex;
42140
42463
  const css2 = useCss((theme) => ({
42141
42464
  container: {
42142
42465
  width: "100%",
@@ -42194,12 +42517,14 @@ function AvatarPane({
42194
42517
  }
42195
42518
  }));
42196
42519
  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" });
42520
+ const viewer = /* @__PURE__ */ jsx$1(AvatarFrameViewer, { frameUrls, selectedFrameIndex, setSelectedFrameIndex, imageContainerStyle: css2.frameContainer, imageStyle: css2.frameImage, loadingT: "quick-view.avatar_loading" });
42198
42521
  if (mobileFullscreen) {
42199
42522
  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
42523
+ /* @__PURE__ */ jsxs("div", { css: css2.mobileFrameSlot, children: [
42524
+ viewer,
42525
+ controls
42526
+ ] }),
42527
+ /* @__PURE__ */ jsx$1("div", { css: css2.bottomFiller, children: " " })
42203
42528
  ] });
42204
42529
  }
42205
42530
  return /* @__PURE__ */ jsxs("div", { css: css2.container, children: [
@@ -42208,7 +42533,7 @@ function AvatarPane({
42208
42533
  ] });
42209
42534
  }
42210
42535
  if (hasSelection) {
42211
- return /* @__PURE__ */ jsx$1("div", { css: css2.container, children: /* @__PURE__ */ jsx$1(Loading, { t: "vto-single.avatar_loading" }) });
42536
+ return /* @__PURE__ */ jsx$1("div", { css: css2.container, children: /* @__PURE__ */ jsx$1(Loading, { t: "quick-view.avatar_loading" }) });
42212
42537
  }
42213
42538
  return /* @__PURE__ */ jsxs("div", { css: css2.container, children: [
42214
42539
  /* @__PURE__ */ jsx$1("div", { css: css2.placeholder, children: /* @__PURE__ */ jsx$1(TextT, { variant: "base", t: "fitting_room.avatar_placeholder_empty" }) }),
@@ -42313,7 +42638,9 @@ function ProductCard({
42313
42638
  const disabled = availability === "disabled";
42314
42639
  const selected = availability === "selected";
42315
42640
  const handleClick = () => {
42316
- if (disabled) return;
42641
+ if (disabled) {
42642
+ return;
42643
+ }
42317
42644
  onClick();
42318
42645
  };
42319
42646
  const name2 = item.merchantProduct?.productName ?? item.externalId;
@@ -42359,9 +42686,13 @@ function CardRail({
42359
42686
  setCanScrollRight(el.scrollLeft + el.clientWidth < el.scrollWidth - 1);
42360
42687
  }, []);
42361
42688
  reactExports.useLayoutEffect(() => {
42362
- if (collapsed) return;
42689
+ if (collapsed) {
42690
+ return;
42691
+ }
42363
42692
  const el = scrollRef.current;
42364
- if (!el) return;
42693
+ if (!el) {
42694
+ return;
42695
+ }
42365
42696
  updateScrollState();
42366
42697
  const observer = new ResizeObserver(updateScrollState);
42367
42698
  observer.observe(el);
@@ -42369,7 +42700,9 @@ function CardRail({
42369
42700
  }, [collapsed, group.items, updateScrollState]);
42370
42701
  const scrollByPage = reactExports.useCallback((dir) => {
42371
42702
  const el = scrollRef.current;
42372
- if (!el) return;
42703
+ if (!el) {
42704
+ return;
42705
+ }
42373
42706
  el.scrollBy({
42374
42707
  left: dir * el.clientWidth * 0.8,
42375
42708
  behavior: "smooth"
@@ -42462,7 +42795,36 @@ function CardRail({
42462
42795
  function AddToCartButton({
42463
42796
  onClick
42464
42797
  }) {
42465
- return /* @__PURE__ */ jsx$1(ButtonT, { variant: "brand", t: "vto-single.add_to_cart", onClick });
42798
+ return /* @__PURE__ */ jsx$1(ButtonT, { variant: "brand", t: "quick-view.add_to_cart", onClick });
42799
+ }
42800
+ function ColorSelector({
42801
+ availableColorLabels,
42802
+ selectedColorLabel,
42803
+ onChangeColor
42804
+ }) {
42805
+ const css2 = useCss((theme) => ({
42806
+ colorContainer: {},
42807
+ colorLabelText: {
42808
+ fontSize: "12px"
42809
+ },
42810
+ colorSelect: {
42811
+ border: "none",
42812
+ color: theme.color_fg_text,
42813
+ fontFamily: theme.font_family,
42814
+ fontSize: "12px"
42815
+ }
42816
+ }));
42817
+ const handleColorSelectChange = reactExports.useCallback((e) => {
42818
+ const newColorLabel = e.target.value || null;
42819
+ onChangeColor(newColorLabel);
42820
+ }, [onChangeColor]);
42821
+ if (availableColorLabels.length < 2) {
42822
+ return null;
42823
+ }
42824
+ return /* @__PURE__ */ jsx$1("div", { css: css2.colorContainer, children: /* @__PURE__ */ jsxs("label", { children: [
42825
+ /* @__PURE__ */ jsx$1(TextT, { variant: "base", css: css2.colorLabelText, t: "quick-view.color_label" }),
42826
+ /* @__PURE__ */ jsx$1("select", { value: selectedColorLabel ?? "", onChange: handleColorSelectChange, css: css2.colorSelect, children: availableColorLabels.map((colorLabel) => /* @__PURE__ */ jsx$1("option", { value: colorLabel, children: colorLabel }, colorLabel)) })
42827
+ ] }) });
42466
42828
  }
42467
42829
  function ItemFitDetails({
42468
42830
  loadedProductData,
@@ -42574,14 +42936,19 @@ function DetailAccordionItem({
42574
42936
  onToggleOpen,
42575
42937
  onChangeDetailMode,
42576
42938
  onChangeSize,
42939
+ onChangeColor,
42577
42940
  onAddToCart,
42578
42941
  onToggleUntuck
42579
42942
  }) {
42580
42943
  const productData = reactExports.useMemo(() => buildVtoProductDataFromResolved(item), [item]);
42581
42944
  const selectedSizeLabel = reactExports.useMemo(() => {
42582
- if (!productData) return null;
42945
+ if (!productData) {
42946
+ return null;
42947
+ }
42583
42948
  const csaId = item.storage.colorwaySizeAssetId;
42584
- if (csaId == null) return null;
42949
+ if (csaId == null) {
42950
+ return null;
42951
+ }
42585
42952
  for (const sizeRec of productData.sizes) {
42586
42953
  if (sizeRec.colors.some((c) => c.colorwaySizeAssetId === csaId)) {
42587
42954
  return sizeRec.sizeLabel;
@@ -42590,22 +42957,38 @@ function DetailAccordionItem({
42590
42957
  return null;
42591
42958
  }, [productData, item.storage.colorwaySizeAssetId]);
42592
42959
  const currentPrice = reactExports.useMemo(() => {
42593
- if (!productData) return null;
42960
+ if (!productData) {
42961
+ return null;
42962
+ }
42594
42963
  const csaId = item.storage.colorwaySizeAssetId;
42595
- if (csaId == null) return null;
42964
+ if (csaId == null) {
42965
+ return null;
42966
+ }
42596
42967
  for (const sizeRec of productData.sizes) {
42597
42968
  const c = sizeRec.colors.find((c2) => c2.colorwaySizeAssetId === csaId);
42598
- if (c) return c.priceFormatted;
42969
+ if (c) {
42970
+ return c.priceFormatted;
42971
+ }
42599
42972
  }
42600
42973
  return null;
42601
42974
  }, [productData, item.storage.colorwaySizeAssetId]);
42975
+ const availableColorLabels = reactExports.useMemo(() => {
42976
+ if (!productData || !selectedSizeLabel) {
42977
+ return [];
42978
+ }
42979
+ const sizeRec = productData.sizes.find((s) => s.sizeLabel === selectedSizeLabel);
42980
+ if (!sizeRec) {
42981
+ return [];
42982
+ }
42983
+ return sizeRec.colors.map((c) => c.colorLabel).filter((label) => !!label);
42984
+ }, [productData, selectedSizeLabel]);
42602
42985
  const categoryLabel = item.styleCategory?.label_singular ?? item.styleCategory?.label ?? "";
42603
42986
  const productName = item.merchantProduct?.productName ?? item.externalId;
42604
42987
  const tuckable = !!item.styleCategory?.tuckable && canTuck;
42605
42988
  if (platform === "desktop") {
42606
- return /* @__PURE__ */ jsx$1(DesktopAccordionItem, { isOpen, categoryLabel, productData, currentPrice, selectedSizeLabel, onToggleOpen, onChangeSize, onAddToCart });
42989
+ return /* @__PURE__ */ jsx$1(DesktopAccordionItem, { isOpen, categoryLabel, productData, currentPrice, selectedSizeLabel, availableColorLabels, selectedColorLabel: item.storage.color, onToggleOpen, onChangeSize, onChangeColor, onAddToCart });
42607
42990
  }
42608
- return /* @__PURE__ */ jsx$1(MobileAccordionItem, { isOpen, categoryLabel, productName, productData, selectedSizeLabel, currentPrice, detailMode, isMobileQuickRow, tuckable, forceUntuck, onToggleOpen, onChangeDetailMode, onChangeSize, onAddToCart, onToggleUntuck });
42991
+ return /* @__PURE__ */ jsx$1(MobileAccordionItem, { isOpen, categoryLabel, productName, productData, selectedSizeLabel, availableColorLabels, selectedColorLabel: item.storage.color, currentPrice, detailMode, isMobileQuickRow, tuckable, forceUntuck, onToggleOpen, onChangeDetailMode, onChangeSize, onChangeColor, onAddToCart, onToggleUntuck });
42609
42992
  }
42610
42993
  function DesktopAccordionItem({
42611
42994
  isOpen,
@@ -42613,8 +42996,11 @@ function DesktopAccordionItem({
42613
42996
  productData,
42614
42997
  currentPrice,
42615
42998
  selectedSizeLabel,
42999
+ availableColorLabels,
43000
+ selectedColorLabel,
42616
43001
  onToggleOpen,
42617
43002
  onChangeSize,
43003
+ onChangeColor,
42618
43004
  onAddToCart
42619
43005
  }) {
42620
43006
  const ACCORDION_SHADE = "#F4F4F4";
@@ -42660,35 +43046,60 @@ function DesktopAccordionItem({
42660
43046
  price: {
42661
43047
  fontSize: "15px"
42662
43048
  },
43049
+ // Padding matches quick-view's sizeRecommendationFrame (32px / 56px) so
43050
+ // the "fit box" feels visually consistent between the two overlays.
43051
+ //
43052
+ // No flex `gap` — the three text lines (recommended size, fit text,
43053
+ // select-a-size prompt) sit tight against each other (matching
43054
+ // quick-view), with explicit marginTop on the size selector + fit
43055
+ // details below them to introduce the larger break.
42663
43056
  sizeBox: {
42664
43057
  width: "100%",
42665
43058
  border: `1px solid ${theme.color_fg_text}`,
42666
- padding: "20px 24px",
43059
+ padding: "32px 56px",
42667
43060
  display: "flex",
42668
43061
  flexDirection: "column",
42669
43062
  alignItems: "center",
42670
- gap: "12px",
42671
43063
  marginTop: "8px",
42672
43064
  textAlign: "center"
42673
43065
  },
43066
+ colorSelectorContainer: {
43067
+ width: "100%",
43068
+ marginTop: "8px"
43069
+ },
43070
+ // 14px / line-height 1.5 on these three text lines matches quick-view's
43071
+ // fit-box. Quick-view's first line wraps an InfoIcon button alongside
43072
+ // the recommended-size text, which stretches that line vertically; the
43073
+ // simpler line-height bump here matches the *visual* line spacing
43074
+ // without dragging the icon in. The two lines below it inherit
43075
+ // line-height from their containers in quick-view, which the host page's
43076
+ // body styles tend to set looser than Inter's intrinsic `normal` (~1.21).
42674
43077
  recommendedSize: {
42675
- fontSize: "13px",
42676
- fontWeight: "600"
43078
+ fontSize: "14px",
43079
+ fontWeight: "600",
43080
+ lineHeight: 1.5
42677
43081
  },
42678
43082
  selectPrompt: {
42679
- fontSize: "12px"
43083
+ fontSize: "14px",
43084
+ lineHeight: 1.5
42680
43085
  },
42681
43086
  fitText: {
42682
- fontSize: "12px"
43087
+ fontSize: "14px",
43088
+ lineHeight: 1.5,
43089
+ // Tight 8px lift to the recommended-size line above; matches
43090
+ // quick-view's `itemFitContainer` marginTop.
43091
+ marginTop: "8px"
42683
43092
  },
42684
43093
  fitDetails: {
42685
- width: "100%"
43094
+ width: "100%",
43095
+ marginTop: "24px"
42686
43096
  },
42687
43097
  sizeRow: {
42688
43098
  display: "flex",
42689
43099
  gap: "8px",
42690
43100
  alignItems: "center",
42691
- justifyContent: "center"
43101
+ justifyContent: "center",
43102
+ marginTop: "24px"
42692
43103
  },
42693
43104
  cartContainer: {
42694
43105
  width: "100%",
@@ -42709,6 +43120,7 @@ function DesktopAccordionItem({
42709
43120
  !isOpen ? null : /* @__PURE__ */ jsx$1("div", { css: css2.body, children: productData ? /* @__PURE__ */ jsxs(Fragment, { children: [
42710
43121
  /* @__PURE__ */ jsx$1(Text, { variant: "brand", css: css2.productName, children: productData.productName }),
42711
43122
  currentPrice ? /* @__PURE__ */ jsx$1(Text, { variant: "base", css: css2.price, children: currentPrice }) : null,
43123
+ /* @__PURE__ */ jsx$1("div", { css: css2.colorSelectorContainer, children: /* @__PURE__ */ jsx$1(ColorSelector, { availableColorLabels, selectedColorLabel, onChangeColor }) }),
42712
43124
  /* @__PURE__ */ jsxs("div", { css: css2.sizeBox, children: [
42713
43125
  /* @__PURE__ */ jsxs(Text, { variant: "base", css: css2.recommendedSize, children: [
42714
43126
  "Recommended Size: ",
@@ -42732,6 +43144,8 @@ function MobileAccordionItem({
42732
43144
  productName,
42733
43145
  productData,
42734
43146
  selectedSizeLabel,
43147
+ availableColorLabels,
43148
+ selectedColorLabel,
42735
43149
  currentPrice,
42736
43150
  detailMode,
42737
43151
  isMobileQuickRow,
@@ -42740,6 +43154,7 @@ function MobileAccordionItem({
42740
43154
  onToggleOpen,
42741
43155
  onChangeDetailMode,
42742
43156
  onChangeSize,
43157
+ onChangeColor,
42743
43158
  onAddToCart,
42744
43159
  onToggleUntuck
42745
43160
  }) {
@@ -42879,6 +43294,7 @@ function MobileAccordionItem({
42879
43294
  ] }),
42880
43295
  isMobileQuickRow ? /* @__PURE__ */ jsx$1("div", { css: css2.content, children: /* @__PURE__ */ jsx$1("div", { css: css2.quickRow, children: productData ? /* @__PURE__ */ jsx$1(SizeSelector, { loadedProductData: productData, selectedSizeLabel, onChangeSize }) : null }) }) : !isOpen ? null : /* @__PURE__ */ jsx$1("div", { css: css2.content, children: /* @__PURE__ */ jsx$1("div", { css: css2.body, children: productData ? /* @__PURE__ */ jsxs(Fragment, { children: [
42881
43296
  /* @__PURE__ */ jsx$1("div", { css: css2.sizeRow, children: /* @__PURE__ */ jsx$1(SizeSelector, { loadedProductData: productData, selectedSizeLabel, onChangeSize }) }),
43297
+ /* @__PURE__ */ jsx$1(ColorSelector, { availableColorLabels, selectedColorLabel, onChangeColor }),
42882
43298
  /* @__PURE__ */ jsx$1(ItemFitText, { loadedProductData: productData }),
42883
43299
  /* @__PURE__ */ jsx$1("div", { css: css2.fitDetailsContainer, children: /* @__PURE__ */ jsx$1(ItemFitDetails, { loadedProductData: productData, selectedSizeLabel }) }),
42884
43300
  /* @__PURE__ */ jsx$1("div", { css: css2.buttonContainer, children: /* @__PURE__ */ jsx$1(AddToCartButton, { onClick: onAddToCart }) }),
@@ -42905,6 +43321,7 @@ function DetailAccordion({
42905
43321
  onOpenItem,
42906
43322
  onChangeDetailMode,
42907
43323
  onChangeSize,
43324
+ onChangeColor,
42908
43325
  onAddToCart,
42909
43326
  onToggleUntuck
42910
43327
  }) {
@@ -42919,9 +43336,164 @@ function DetailAccordion({
42919
43336
  gap
42920
43337
  }, children: items.map((item) => {
42921
43338
  const isOpen = openItemExternalId === item.externalId;
42922
- 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);
43339
+ 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), onChangeColor: (label) => onChangeColor(item.externalId, label), onAddToCart: () => onAddToCart(item.externalId), onToggleUntuck }, item.externalId);
42923
43340
  }) });
42924
43341
  }
43342
+ const AXIS_LOCK_PX = 8;
43343
+ const ROTATE_STEP_PX = 50;
43344
+ function ZoomModal({
43345
+ frameUrls,
43346
+ selectedFrameIndex,
43347
+ setSelectedFrameIndex,
43348
+ onClose
43349
+ }) {
43350
+ reactExports.useEffect(() => {
43351
+ const onKeyDown = (e) => {
43352
+ if (e.key === "Escape") {
43353
+ e.stopPropagation();
43354
+ e.preventDefault();
43355
+ onClose();
43356
+ }
43357
+ };
43358
+ document.addEventListener("keydown", onKeyDown, true);
43359
+ return () => document.removeEventListener("keydown", onKeyDown, true);
43360
+ }, [onClose]);
43361
+ const {
43362
+ rotateLeft,
43363
+ rotateRight
43364
+ } = useFrameRotation(frameUrls, setSelectedFrameIndex);
43365
+ const scrollAreaRef = reactExports.useRef(null);
43366
+ const handleImageMouseDown = reactExports.useCallback((e) => {
43367
+ e.preventDefault();
43368
+ const startX = e.clientX;
43369
+ const startY = e.clientY;
43370
+ const scrollArea = scrollAreaRef.current;
43371
+ const startScrollTop = scrollArea?.scrollTop ?? 0;
43372
+ let mode = "unknown";
43373
+ let lastRotateX = startX;
43374
+ const onMove = (move) => {
43375
+ const deltaX = move.clientX - startX;
43376
+ const deltaY = move.clientY - startY;
43377
+ if (mode === "unknown") {
43378
+ const absX = Math.abs(deltaX);
43379
+ const absY = Math.abs(deltaY);
43380
+ if (absX < AXIS_LOCK_PX && absY < AXIS_LOCK_PX) {
43381
+ return;
43382
+ }
43383
+ mode = absY > absX ? "scroll" : "rotate";
43384
+ lastRotateX = move.clientX;
43385
+ }
43386
+ if (mode === "scroll" && scrollArea) {
43387
+ scrollArea.scrollTop = startScrollTop - deltaY;
43388
+ } else if (mode === "rotate") {
43389
+ const rotateDelta = move.clientX - lastRotateX;
43390
+ if (Math.abs(rotateDelta) >= ROTATE_STEP_PX) {
43391
+ if (rotateDelta > 0) {
43392
+ rotateRight();
43393
+ } else {
43394
+ rotateLeft();
43395
+ }
43396
+ lastRotateX = move.clientX;
43397
+ }
43398
+ }
43399
+ };
43400
+ const onUp = () => {
43401
+ window.removeEventListener("mousemove", onMove);
43402
+ window.removeEventListener("mouseup", onUp);
43403
+ };
43404
+ window.addEventListener("mousemove", onMove);
43405
+ window.addEventListener("mouseup", onUp);
43406
+ }, [rotateLeft, rotateRight]);
43407
+ const css2 = useCss((_theme) => ({
43408
+ backdrop: {
43409
+ position: "fixed",
43410
+ inset: 0,
43411
+ backgroundColor: "#F4F4F4",
43412
+ zIndex: 100
43413
+ },
43414
+ scrollArea: {
43415
+ position: "absolute",
43416
+ // 40px padding on every side.
43417
+ inset: "40px",
43418
+ overflow: "auto"
43419
+ },
43420
+ // Centres the frame when it fits; grows past the scroll area when it
43421
+ // doesn't, so the scroll area scrolls instead of shrinking the image.
43422
+ imageWrap: {
43423
+ minWidth: "100%",
43424
+ minHeight: "100%",
43425
+ display: "flex",
43426
+ alignItems: "center",
43427
+ justifyContent: "center"
43428
+ },
43429
+ image: {
43430
+ // No sizing — the frame renders at its natural (full) resolution.
43431
+ display: "block",
43432
+ flex: "none",
43433
+ cursor: "grab"
43434
+ },
43435
+ // Rotation chevrons float at the modal's vertical centre, so they stay
43436
+ // put regardless of how the (possibly larger) frame is scrolled.
43437
+ chevron: {
43438
+ position: "absolute",
43439
+ top: "50%",
43440
+ transform: "translateY(-50%)",
43441
+ display: "flex",
43442
+ cursor: "pointer",
43443
+ zIndex: 1
43444
+ },
43445
+ chevronLeft: {
43446
+ left: "8px"
43447
+ },
43448
+ chevronRight: {
43449
+ right: "8px"
43450
+ },
43451
+ chevronIcon: {
43452
+ width: "48px",
43453
+ height: "48px"
43454
+ },
43455
+ close: {
43456
+ position: "absolute",
43457
+ top: "8px",
43458
+ right: "8px",
43459
+ width: "44px",
43460
+ height: "44px",
43461
+ display: "flex",
43462
+ alignItems: "center",
43463
+ justifyContent: "center",
43464
+ borderRadius: "50%",
43465
+ border: "none",
43466
+ // Reset native button chrome and default padding so the circle renders
43467
+ // exactly as styled.
43468
+ appearance: "none",
43469
+ WebkitAppearance: "none",
43470
+ padding: 0,
43471
+ backgroundColor: "rgba(0, 0, 0, 0.5)",
43472
+ color: "#FFFFFF",
43473
+ fontSize: "32px",
43474
+ lineHeight: 1,
43475
+ cursor: "pointer",
43476
+ // Keep the "×" glyph from being text-selected — a stray selection
43477
+ // paints a dark highlight rectangle behind it.
43478
+ userSelect: "none",
43479
+ WebkitUserSelect: "none",
43480
+ zIndex: 1
43481
+ }
43482
+ }));
43483
+ const imageUrl = frameUrls[selectedFrameIndex ?? 0];
43484
+ return /* @__PURE__ */ jsxs("div", { css: css2.backdrop, children: [
43485
+ /* @__PURE__ */ jsx$1("div", { ref: scrollAreaRef, css: css2.scrollArea, children: /* @__PURE__ */ jsx$1("div", { css: css2.imageWrap, children: /* @__PURE__ */ jsx$1("img", { src: imageUrl, css: css2.image, alt: "", onMouseDown: handleImageMouseDown }) }) }),
43486
+ /* @__PURE__ */ jsx$1("div", { css: /* @__PURE__ */ css$1({
43487
+ ...css2.chevron,
43488
+ ...css2.chevronLeft
43489
+ }, "", ""), onClick: rotateLeft, children: /* @__PURE__ */ jsx$1(SvgChevronLeft, { css: css2.chevronIcon }) }),
43490
+ /* @__PURE__ */ jsx$1("div", { css: /* @__PURE__ */ css$1({
43491
+ ...css2.chevron,
43492
+ ...css2.chevronRight
43493
+ }, "", ""), onClick: rotateRight, children: /* @__PURE__ */ jsx$1(SvgChevronRight, { css: css2.chevronIcon }) }),
43494
+ /* @__PURE__ */ jsx$1("button", { css: css2.close, onClick: onClose, "aria-label": "Close zoom", children: "×" })
43495
+ ] });
43496
+ }
42925
43497
  const AVATAR_ASPECT_RATIO = 2 / 3;
42926
43498
  const EDGE_INSET_PX = 16;
42927
43499
  const AVATAR_MIN_WIDTH_PX = 240;
@@ -42937,35 +43509,40 @@ function DesktopLayout$1({
42937
43509
  detailMode,
42938
43510
  forceUntuck,
42939
43511
  canTuck,
42940
- zoomed,
42941
43512
  frameUrls,
42942
43513
  onSelectItem,
42943
43514
  onRemoveItem,
42944
43515
  onOpenAccordionItem,
42945
43516
  onChangeDetailMode,
42946
43517
  onChangeSize,
43518
+ onChangeColor,
42947
43519
  onAddToCart,
42948
43520
  onToggleUntuck,
42949
- onToggleZoom,
42950
43521
  onSignOut
42951
43522
  }) {
42952
43523
  const hasSelection = selectedItems.length > 0;
42953
43524
  const [avatarHovered, setAvatarHovered] = reactExports.useState(false);
43525
+ const [zoomOpen, setZoomOpen] = reactExports.useState(false);
43526
+ const [selectedFrameIndex, setSelectedFrameIndex] = reactExports.useState(null);
42954
43527
  const containerRef = reactExports.useRef(null);
42955
43528
  const [avatarWidth, setAvatarWidth] = reactExports.useState(AVATAR_MIN_WIDTH_PX);
42956
43529
  reactExports.useLayoutEffect(() => {
42957
43530
  const el = containerRef.current;
42958
- if (!el) return;
43531
+ if (!el) {
43532
+ return;
43533
+ }
42959
43534
  const observer = new ResizeObserver(() => {
42960
43535
  const availableHeightPx = el.clientHeight;
42961
- if (availableHeightPx <= 0) return;
43536
+ if (availableHeightPx <= 0) {
43537
+ return;
43538
+ }
42962
43539
  const target = Math.floor(availableHeightPx * AVATAR_ASPECT_RATIO);
42963
43540
  setAvatarWidth(Math.min(AVATAR_MAX_WIDTH_PX, Math.max(AVATAR_MIN_WIDTH_PX, target)));
42964
43541
  });
42965
43542
  observer.observe(el);
42966
43543
  return () => observer.disconnect();
42967
43544
  }, []);
42968
- const gridTemplateColumns = zoomed ? "1fr" : hasSelection ? `${avatarWidth}px minmax(${DETAILS_MIN_WIDTH_PX}px, ${DETAILS_FR}fr) ${CARDS_FR}fr` : `${avatarWidth}px 1fr`;
43545
+ const gridTemplateColumns = hasSelection ? `${avatarWidth}px minmax(${DETAILS_MIN_WIDTH_PX}px, ${DETAILS_FR}fr) ${CARDS_FR}fr` : `${avatarWidth}px 1fr`;
42969
43546
  const css2 = useCss((_theme) => ({
42970
43547
  container: {
42971
43548
  display: "grid",
@@ -43030,21 +43607,127 @@ function DesktopLayout$1({
43030
43607
  fontSize: "14px"
43031
43608
  }
43032
43609
  }));
43033
- const controls = hasSelection ? /* @__PURE__ */ jsx$1(AvatarControls, { selectedItems, canTuck, forceUntuck, zoomed, expanded: avatarHovered, onToggleUntuck, onToggleZoom, onRemoveItem }) : null;
43610
+ const controls = hasSelection ? /* @__PURE__ */ jsx$1(AvatarControls, { selectedItems, canTuck, forceUntuck, zoomed: zoomOpen, expanded: avatarHovered, onToggleUntuck, onToggleZoom: () => setZoomOpen(true), onRemoveItem }) : null;
43034
43611
  return /* @__PURE__ */ jsxs("div", { ref: containerRef, css: css2.container, style: {
43035
43612
  gridTemplateColumns
43036
43613
  }, 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: [
43614
+ /* @__PURE__ */ jsx$1("div", { css: css2.avatarColumn, onMouseEnter: () => setAvatarHovered(true), onMouseLeave: () => setAvatarHovered(false), children: /* @__PURE__ */ jsx$1(AvatarPane, { hasSelection, frameUrls, controls, selectedFrameIndex, setSelectedFrameIndex }) }),
43615
+ 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, onChangeColor, onAddToCart, onToggleUntuck }) }) : null,
43616
+ /* @__PURE__ */ jsxs("div", { css: css2.railsColumn, children: [
43040
43617
  /* @__PURE__ */ jsxs("span", { css: css2.signOutWrapper, onClick: onSignOut, children: [
43041
43618
  /* @__PURE__ */ jsx$1(SvgTfrIcon, { css: css2.signOutIcon }),
43042
43619
  /* @__PURE__ */ jsx$1(LinkT, { variant: "underline", css: css2.signOut, t: "fitting_room.sign_out" })
43043
43620
  ] }),
43044
43621
  resolved.groups.map((group) => /* @__PURE__ */ jsx$1(CardRail, { group, availabilityByExternalId, onSelectItem, onRemoveItem }, group.group.name))
43045
- ] }) : null
43622
+ ] }),
43623
+ zoomOpen && frameUrls && frameUrls.length > 0 ? /* @__PURE__ */ jsx$1(ZoomModal, { frameUrls, selectedFrameIndex, setSelectedFrameIndex, onClose: () => setZoomOpen(false) }) : null
43624
+ ] });
43625
+ }
43626
+ function MenuIcon({
43627
+ size = 20
43628
+ }) {
43629
+ 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" }) });
43630
+ }
43631
+ function SectionNav({
43632
+ sections,
43633
+ activeName,
43634
+ onSelect
43635
+ }) {
43636
+ const [open, setOpen] = reactExports.useState(false);
43637
+ const wrapperRef = reactExports.useRef(null);
43638
+ reactExports.useEffect(() => {
43639
+ if (!open) {
43640
+ return;
43641
+ }
43642
+ const onDocClick = (e) => {
43643
+ if (wrapperRef.current && !wrapperRef.current.contains(e.target)) {
43644
+ setOpen(false);
43645
+ }
43646
+ };
43647
+ document.addEventListener("mousedown", onDocClick);
43648
+ return () => document.removeEventListener("mousedown", onDocClick);
43649
+ }, [open]);
43650
+ const handleSelect = reactExports.useCallback((name2) => {
43651
+ setOpen(false);
43652
+ onSelect(name2);
43653
+ }, [onSelect]);
43654
+ const css2 = useCss((theme) => ({
43655
+ wrapper: {
43656
+ // Floats over the card rails at the top-right instead of taking its own
43657
+ // row in the browse-view column (BrowseView's container is relative).
43658
+ position: "absolute",
43659
+ top: "12px",
43660
+ right: "16px",
43661
+ // Above the rails so the pill and its drop-down sit over the content.
43662
+ zIndex: 5
43663
+ },
43664
+ bar: {
43665
+ display: "inline-flex",
43666
+ alignItems: "center",
43667
+ gap: "8px",
43668
+ padding: "6px 16px",
43669
+ borderRadius: "999px",
43670
+ backgroundColor: theme.color_fg_text,
43671
+ color: "#FFFFFF",
43672
+ border: "none",
43673
+ cursor: "pointer",
43674
+ fontSize: "13px",
43675
+ fontWeight: "500",
43676
+ letterSpacing: "0.5px",
43677
+ textTransform: "uppercase",
43678
+ whiteSpace: "nowrap"
43679
+ },
43680
+ icon: {
43681
+ display: "inline-flex",
43682
+ alignItems: "center",
43683
+ flex: "none"
43684
+ },
43685
+ dropdown: {
43686
+ position: "absolute",
43687
+ top: "calc(100% + 6px)",
43688
+ right: 0,
43689
+ // At least as wide as the pill, growing to fit the longest label.
43690
+ minWidth: "100%",
43691
+ backgroundColor: theme.color_fg_text,
43692
+ borderRadius: "16px",
43693
+ maxHeight: "60vh",
43694
+ overflowY: "auto",
43695
+ boxShadow: "0 6px 16px rgba(0, 0, 0, 0.25)"
43696
+ },
43697
+ item: {
43698
+ display: "block",
43699
+ width: "100%",
43700
+ textAlign: "left",
43701
+ whiteSpace: "nowrap",
43702
+ padding: "8px 18px",
43703
+ backgroundColor: "transparent",
43704
+ color: "#FFFFFF",
43705
+ border: "none",
43706
+ cursor: "pointer",
43707
+ fontSize: "13px",
43708
+ letterSpacing: "0.5px",
43709
+ textTransform: "uppercase",
43710
+ "&:not(:first-of-type)": {
43711
+ borderTop: "1px solid rgba(255, 255, 255, 0.12)"
43712
+ }
43713
+ },
43714
+ itemActive: {
43715
+ backgroundColor: "rgba(255, 255, 255, 0.16)"
43716
+ }
43717
+ }));
43718
+ const activeLabel = sections.find((s) => s.name === activeName)?.label ?? sections[0]?.label ?? "";
43719
+ return /* @__PURE__ */ jsxs("div", { ref: wrapperRef, css: css2.wrapper, children: [
43720
+ /* @__PURE__ */ jsxs(Button, { variant: "base", css: css2.bar, onClick: () => setOpen((o) => !o), children: [
43721
+ /* @__PURE__ */ jsx$1("span", { children: activeLabel }),
43722
+ /* @__PURE__ */ jsx$1("span", { css: css2.icon, children: open ? /* @__PURE__ */ jsx$1(Chevron, { direction: "up", size: 20 }) : /* @__PURE__ */ jsx$1(MenuIcon, { size: 20 }) })
43723
+ ] }),
43724
+ open ? /* @__PURE__ */ jsx$1("div", { css: css2.dropdown, children: sections.map((s) => /* @__PURE__ */ jsx$1(Button, { variant: "base", css: s.name === activeName ? {
43725
+ ...css2.item,
43726
+ ...css2.itemActive
43727
+ } : css2.item, onClick: () => handleSelect(s.name), children: s.label }, s.name)) }) : null
43046
43728
  ] });
43047
43729
  }
43730
+ const SECTION_SCROLL_TOP_GAP_PX = 50;
43048
43731
  function MobileLayout$1({
43049
43732
  mode,
43050
43733
  resolved,
@@ -43064,13 +43747,14 @@ function MobileLayout$1({
43064
43747
  onOpenAccordionItem,
43065
43748
  onChangeDetailMode,
43066
43749
  onChangeSize,
43750
+ onChangeColor,
43067
43751
  onAddToCart,
43068
43752
  onToggleUntuck
43069
43753
  }) {
43070
43754
  if (mode === "browse") {
43071
43755
  return /* @__PURE__ */ jsx$1(BrowseView, { resolved, availabilityByExternalId, selectedCount: selectedItems.length, onSelectItem, onRemoveItem, onTryItOn });
43072
43756
  }
43073
- return /* @__PURE__ */ jsx$1(TryOnView, { selectedItems, openAccordionItemId, detailMode, forceUntuck, canTuck, frameUrls, sheetSnap, sheetTouchStart, onBackToBrowse, onOpenAccordionItem, onChangeDetailMode, onChangeSize, onAddToCart, onToggleUntuck });
43757
+ return /* @__PURE__ */ jsx$1(TryOnView, { selectedItems, openAccordionItemId, detailMode, forceUntuck, canTuck, frameUrls, sheetSnap, sheetTouchStart, onBackToBrowse, onOpenAccordionItem, onChangeDetailMode, onChangeSize, onChangeColor, onAddToCart, onToggleUntuck });
43074
43758
  }
43075
43759
  function BrowseView({
43076
43760
  resolved,
@@ -43080,12 +43764,55 @@ function BrowseView({
43080
43764
  onRemoveItem,
43081
43765
  onTryItOn
43082
43766
  }) {
43767
+ const railsAreaRef = reactExports.useRef(null);
43768
+ const sectionRefs = reactExports.useRef(/* @__PURE__ */ new Map());
43769
+ const [activeSectionName, setActiveSectionName] = reactExports.useState(null);
43770
+ const sections = resolved.groups.map((g) => ({
43771
+ name: g.group.name,
43772
+ label: g.group.label
43773
+ }));
43774
+ const recomputeActiveSection = reactExports.useCallback(() => {
43775
+ const container = railsAreaRef.current;
43776
+ if (!container) {
43777
+ return;
43778
+ }
43779
+ const containerTop = container.getBoundingClientRect().top;
43780
+ let active = null;
43781
+ for (const [name2, el] of sectionRefs.current) {
43782
+ if (el.getBoundingClientRect().top >= containerTop - 1) {
43783
+ active = name2;
43784
+ break;
43785
+ }
43786
+ }
43787
+ if (active == null) {
43788
+ const groups = resolved.groups;
43789
+ active = groups.length > 0 ? groups[groups.length - 1].group.name : null;
43790
+ }
43791
+ setActiveSectionName(active);
43792
+ }, [resolved.groups]);
43793
+ reactExports.useLayoutEffect(() => {
43794
+ recomputeActiveSection();
43795
+ }, [recomputeActiveSection, resolved.groups]);
43796
+ const scrollToSection = reactExports.useCallback((name2) => {
43797
+ const container = railsAreaRef.current;
43798
+ const el = sectionRefs.current.get(name2);
43799
+ if (!container || !el) {
43800
+ return;
43801
+ }
43802
+ const delta = el.getBoundingClientRect().top - container.getBoundingClientRect().top;
43803
+ container.scrollBy({
43804
+ top: delta - SECTION_SCROLL_TOP_GAP_PX,
43805
+ behavior: "smooth"
43806
+ });
43807
+ }, []);
43083
43808
  const css2 = useCss((_theme) => ({
43084
43809
  container: {
43085
43810
  display: "flex",
43086
43811
  flexDirection: "column",
43087
43812
  height: "100%",
43088
- width: "100%"
43813
+ width: "100%",
43814
+ // Positioning context for the floating SectionNav pill.
43815
+ position: "relative"
43089
43816
  },
43090
43817
  railsArea: {
43091
43818
  flex: 1,
@@ -43103,7 +43830,14 @@ function BrowseView({
43103
43830
  }
43104
43831
  }));
43105
43832
  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)) }),
43833
+ !resolved.isLoading && resolved.groups.length > 0 ? /* @__PURE__ */ jsx$1(SectionNav, { sections, activeName: activeSectionName, onSelect: scrollToSection }) : null,
43834
+ /* @__PURE__ */ jsx$1("div", { ref: railsAreaRef, css: css2.railsArea, onScroll: recomputeActiveSection, children: resolved.groups.map((group) => /* @__PURE__ */ jsx$1("div", { ref: (el) => {
43835
+ if (el) {
43836
+ sectionRefs.current.set(group.group.name, el);
43837
+ } else {
43838
+ sectionRefs.current.delete(group.group.name);
43839
+ }
43840
+ }, children: /* @__PURE__ */ jsx$1(CardRail, { group, availabilityByExternalId, onSelectItem, onRemoveItem }) }, group.group.name)) }),
43107
43841
  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
43842
  ] });
43109
43843
  }
@@ -43120,6 +43854,7 @@ function TryOnView({
43120
43854
  onOpenAccordionItem,
43121
43855
  onChangeDetailMode,
43122
43856
  onChangeSize,
43857
+ onChangeColor,
43123
43858
  onAddToCart,
43124
43859
  onToggleUntuck
43125
43860
  }) {
@@ -43129,8 +43864,14 @@ function TryOnView({
43129
43864
  reactExports.useEffect(() => {
43130
43865
  function refresh() {
43131
43866
  const el = innerRef.current;
43132
- if (!el) return;
43133
- const maxHeightPx = Number(window.getComputedStyle(el.parentElement).getPropertyValue("max-height").replace("px", ""));
43867
+ if (!el) {
43868
+ return;
43869
+ }
43870
+ const parentEl = el.parentElement;
43871
+ if (!parentEl) {
43872
+ return;
43873
+ }
43874
+ const maxHeightPx = Number(window.getComputedStyle(parentEl).getPropertyValue("max-height").replace("px", ""));
43134
43875
  const heightPx = Math.min(el.clientHeight, maxHeightPx || el.clientHeight);
43135
43876
  setSheetStyle({
43136
43877
  height: `${heightPx}px`
@@ -43166,7 +43907,7 @@ function TryOnView({
43166
43907
  },
43167
43908
  sheetOuter: {
43168
43909
  position: "absolute",
43169
- // 8px gap to either side, matching vto-single's mobile sheet.
43910
+ // 8px gap to either side, matching quick-view's mobile sheet.
43170
43911
  left: "8px",
43171
43912
  right: "8px",
43172
43913
  bottom: 0,
@@ -43207,18 +43948,18 @@ function TryOnView({
43207
43948
  }
43208
43949
  }));
43209
43950
  return /* @__PURE__ */ jsxs("div", { css: css2.container, children: [
43210
- /* @__PURE__ */ jsx$1(AvatarPane, { hasSelection: selectedItems.length > 0, frameUrls, mobileFullscreen: true }),
43951
+ /* @__PURE__ */ jsx$1(AvatarPane, { hasSelection: selectedItems.length > 0, frameUrls, mobileFullscreen: true, controls: /* @__PURE__ */ jsx$1(MobileTuckControl, { canTuck, forceUntuck, onToggleUntuck }) }),
43211
43952
  /* @__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
43953
  /* @__PURE__ */ jsx$1("div", { css: css2.sheetOuter, style: sheetStyle, children: /* @__PURE__ */ jsxs("div", { ref: innerRef, css: css2.sheetInner, style: sheetStyle, children: [
43213
43954
  /* @__PURE__ */ jsxs("div", { css: css2.sheetHandleRow, onTouchStart: sheetTouchStart, children: [
43214
43955
  /* @__PURE__ */ jsx$1(SvgDragHandle, {}),
43215
43956
  /* @__PURE__ */ jsx$1(Text, { variant: "base", css: css2.sheetTitle, children: "RECOMMENDED SIZES" })
43216
43957
  ] }),
43217
- sheetSnap === "collapsed" ? null : /* @__PURE__ */ jsx$1("div", { css: css2.sheetContent, children: /* @__PURE__ */ jsx$1(DetailAccordion, { items: selectedItems, openItemExternalId: openAccordionItemId, platform: "mobile", detailMode, isMobileQuickRow, forceUntuck, canTuck, onOpenItem: onOpenAccordionItem, onChangeDetailMode, onChangeSize, onAddToCart, onToggleUntuck }) })
43958
+ sheetSnap === "collapsed" ? null : /* @__PURE__ */ jsx$1("div", { css: css2.sheetContent, children: /* @__PURE__ */ jsx$1(DetailAccordion, { items: selectedItems, openItemExternalId: openAccordionItemId, platform: "mobile", detailMode, isMobileQuickRow, forceUntuck, canTuck, onOpenItem: onOpenAccordionItem, onChangeDetailMode, onChangeSize, onChangeColor, onAddToCart, onToggleUntuck }) })
43218
43959
  ] }) })
43219
43960
  ] });
43220
43961
  }
43221
- const logger$a = getLogger("use-vto-requests");
43962
+ const logger$9 = getLogger("use-vto-requests");
43222
43963
  function outfitKey(items) {
43223
43964
  return items.map((i) => `${i.colorway_size_asset_id}:${i.untucked ? 1 : 0}`).sort().join("|");
43224
43965
  }
@@ -43230,18 +43971,22 @@ function useVtoRequests() {
43230
43971
  const [lastError, setLastError] = reactExports.useState(null);
43231
43972
  const clearError = reactExports.useCallback(() => setLastError(null), []);
43232
43973
  const request = reactExports.useCallback((items, priority) => {
43233
- if (items.length === 0) return;
43974
+ if (items.length === 0) {
43975
+ return;
43976
+ }
43234
43977
  const key = outfitKey(items);
43235
- if (requestedKeysRef.current.has(key)) return;
43978
+ if (requestedKeysRef.current.has(key)) {
43979
+ return;
43980
+ }
43236
43981
  const exec = () => {
43237
43982
  requestedKeysRef.current.add(key);
43238
- logger$a.logDebug("Requesting VTO composition", {
43983
+ logger$9.logDebug("Requesting VTO composition", {
43239
43984
  key,
43240
43985
  items,
43241
43986
  priority
43242
43987
  });
43243
43988
  requestVto(items).then((resp) => {
43244
- logger$a.logDebug("VTO frames ready", {
43989
+ logger$9.logDebug("VTO frames ready", {
43245
43990
  key,
43246
43991
  count: resp.frames.length
43247
43992
  });
@@ -43250,7 +43995,7 @@ function useVtoRequests() {
43250
43995
  [key]: resp.frames
43251
43996
  }));
43252
43997
  }).catch((error) => {
43253
- logger$a.logError("VTO request failed", {
43998
+ logger$9.logError("VTO request failed", {
43254
43999
  error,
43255
44000
  items,
43256
44001
  key
@@ -43260,7 +44005,9 @@ function useVtoRequests() {
43260
44005
  });
43261
44006
  };
43262
44007
  if (priority) {
43263
- for (const timer of pendingPrefetchTimersRef.current) clearTimeout(timer);
44008
+ for (const timer of pendingPrefetchTimersRef.current) {
44009
+ clearTimeout(timer);
44010
+ }
43264
44011
  pendingPrefetchTimersRef.current.clear();
43265
44012
  lastPriorityTimeRef.current = Date.now();
43266
44013
  exec();
@@ -43271,7 +44018,9 @@ function useVtoRequests() {
43271
44018
  if (last) {
43272
44019
  const now = Date.now();
43273
44020
  const minNext = last + getStaticData().config.api.vtoPrefetchDelayMs;
43274
- if (now < minNext) delay = minNext - now;
44021
+ if (now < minNext) {
44022
+ delay = minNext - now;
44023
+ }
43275
44024
  }
43276
44025
  if (delay > 0) {
43277
44026
  const timer = setTimeout(() => {
@@ -43286,15 +44035,21 @@ function useVtoRequests() {
43286
44035
  reactExports.useEffect(() => {
43287
44036
  const timers = pendingPrefetchTimersRef.current;
43288
44037
  return () => {
43289
- for (const timer of timers) clearTimeout(timer);
44038
+ for (const timer of timers) {
44039
+ clearTimeout(timer);
44040
+ }
43290
44041
  timers.clear();
43291
44042
  };
43292
44043
  }, []);
43293
44044
  const framesForOutfit = reactExports.useCallback((items) => {
43294
- if (items.length === 0) return null;
44045
+ if (items.length === 0) {
44046
+ return null;
44047
+ }
43295
44048
  const key = outfitKey(items);
43296
44049
  const frames = framesByKey[key];
43297
- if (!frames || frames.length === 0) return null;
44050
+ if (!frames || frames.length === 0) {
44051
+ return null;
44052
+ }
43298
44053
  const baseUrl2 = getStaticData().config.frames.baseUrl;
43299
44054
  return frames.map((u) => applyFrameBaseUrl(u, baseUrl2));
43300
44055
  }, [framesByKey]);
@@ -43313,12 +44068,14 @@ function toWireItems(items) {
43313
44068
  } : {}
43314
44069
  }));
43315
44070
  }
43316
- const logger$9 = getLogger("overlays/fitting-room");
44071
+ const logger$8 = getLogger("overlays/fitting-room");
43317
44072
  function measureTopOffset() {
43318
44073
  const {
43319
44074
  getOverlayTopOffset
43320
44075
  } = getStaticData();
43321
- if (!getOverlayTopOffset) return 0;
44076
+ if (!getOverlayTopOffset) {
44077
+ return 0;
44078
+ }
43322
44079
  try {
43323
44080
  const offset = getOverlayTopOffset();
43324
44081
  return Number.isFinite(offset) && offset > 0 ? offset : 0;
@@ -43326,7 +44083,9 @@ function measureTopOffset() {
43326
44083
  return 0;
43327
44084
  }
43328
44085
  }
43329
- function FittingRoomOverlay() {
44086
+ function FittingRoomOverlay({
44087
+ preselectExternalId
44088
+ }) {
43330
44089
  const deviceLayout = useMainStore((state) => state.deviceLayout);
43331
44090
  const userIsLoggedIn = useMainStore((state) => state.userIsLoggedIn);
43332
44091
  const userHasAvatar = useMainStore((state) => state.userHasAvatar);
@@ -43342,7 +44101,6 @@ function FittingRoomOverlay() {
43342
44101
  const [forceUntuck, setForceUntuck] = reactExports.useState(false);
43343
44102
  const [lastAddedExternalId, setLastAddedExternalId] = reactExports.useState(null);
43344
44103
  const [mobileMode, setMobileMode] = reactExports.useState("browse");
43345
- const [zoomed, setZoomed] = reactExports.useState(false);
43346
44104
  const {
43347
44105
  snap: sheetSnap,
43348
44106
  setSnap: setSheetSnap,
@@ -43357,13 +44115,17 @@ function FittingRoomOverlay() {
43357
44115
  } = useVtoRequests();
43358
44116
  reactExports.useEffect(() => {
43359
44117
  const savedScrollY = window.scrollY;
43360
- if (savedScrollY > 0) window.scrollTo(0, 0);
44118
+ if (savedScrollY > 0) {
44119
+ window.scrollTo(0, 0);
44120
+ }
43361
44121
  setTopOffset(measureTopOffset());
43362
44122
  const onResize = () => setTopOffset(measureTopOffset());
43363
44123
  window.addEventListener("resize", onResize);
43364
44124
  return () => {
43365
44125
  window.removeEventListener("resize", onResize);
43366
- if (savedScrollY > 0) window.scrollTo(0, savedScrollY);
44126
+ if (savedScrollY > 0) {
44127
+ window.scrollTo(0, savedScrollY);
44128
+ }
43367
44129
  };
43368
44130
  }, []);
43369
44131
  const selectedItems = reactExports.useMemo(() => {
@@ -43376,14 +44138,22 @@ function FittingRoomOverlay() {
43376
44138
  indexed.sort((a, b) => {
43377
44139
  const aOrder = a.item.styleCategoryGroup?.display_order ?? Number.MAX_SAFE_INTEGER;
43378
44140
  const bOrder = b.item.styleCategoryGroup?.display_order ?? Number.MAX_SAFE_INTEGER;
43379
- if (aOrder !== bOrder) return aOrder - bOrder;
44141
+ if (aOrder !== bOrder) {
44142
+ return aOrder - bOrder;
44143
+ }
43380
44144
  return a.idx - b.idx;
43381
44145
  });
43382
44146
  return indexed.map(({
43383
44147
  item
43384
44148
  }) => item);
43385
44149
  }, [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]);
44150
+ const canTuck = reactExports.useMemo(() => selectedItems.some((top) => {
44151
+ const topCategory = top.styleCategory;
44152
+ if (!topCategory?.tuckable) {
44153
+ return false;
44154
+ }
44155
+ return selectedItems.some((other) => !!other.styleCategory && !other.styleCategory.tuckable && other.styleCategory.layer_order > topCategory.layer_order);
44156
+ }), [selectedItems]);
43387
44157
  const availabilityByExternalId = reactExports.useMemo(() => {
43388
44158
  const out = {};
43389
44159
  for (const item of resolved.items) {
@@ -43392,11 +44162,17 @@ function FittingRoomOverlay() {
43392
44162
  return out;
43393
44163
  }, [resolved, selectedExternalIds]);
43394
44164
  const ensureSizeForItem = reactExports.useCallback((item) => {
43395
- if (item.storage.colorwaySizeAssetId != null) return;
44165
+ if (item.storage.colorwaySizeAssetId != null) {
44166
+ return;
44167
+ }
43396
44168
  const productData = buildVtoProductDataFromResolved(item);
43397
- if (!productData) return;
44169
+ if (!productData) {
44170
+ return;
44171
+ }
43398
44172
  const csa = findRecommendedColorSize(productData, item.storage.color);
43399
- if (!csa) return;
44173
+ if (!csa) {
44174
+ return;
44175
+ }
43400
44176
  const sizeRec = item.loadedProduct?.sizeFitRecommendation;
43401
44177
  const sizeLabel = sizeRec ? getSizeLabelFromSize(sizeRec.recommended_size) : productData.recommendedSizeLabel;
43402
44178
  updateFittingRoomItem(item.externalId, {
@@ -43404,7 +44180,7 @@ function FittingRoomOverlay() {
43404
44180
  size: sizeLabel,
43405
44181
  color: csa.colorLabel
43406
44182
  });
43407
- logger$9.logDebug("Auto-picked recommended size", {
44183
+ logger$8.logDebug("Auto-picked recommended size", {
43408
44184
  externalId: item.externalId,
43409
44185
  sizeLabel,
43410
44186
  csaId: csa.colorwaySizeAssetId
@@ -43412,12 +44188,16 @@ function FittingRoomOverlay() {
43412
44188
  }, [updateFittingRoomItem]);
43413
44189
  const handleSelectItem = reactExports.useCallback((externalId) => {
43414
44190
  const item = resolved.items.find((i) => i.externalId === externalId);
43415
- if (!item) return;
44191
+ if (!item) {
44192
+ return;
44193
+ }
43416
44194
  const isSelected = selectedExternalIds.has(externalId);
43417
44195
  const nextSelected = new Set(selectedExternalIds);
43418
44196
  if (isSelected) {
43419
44197
  nextSelected.delete(externalId);
43420
- if (openAccordionItemId === externalId) setOpenAccordionItemId(null);
44198
+ if (openAccordionItemId === externalId) {
44199
+ setOpenAccordionItemId(null);
44200
+ }
43421
44201
  } else {
43422
44202
  nextSelected.add(externalId);
43423
44203
  ensureSizeForItem(item);
@@ -43431,30 +44211,55 @@ function FittingRoomOverlay() {
43431
44211
  }, [resolved, selectedExternalIds, openAccordionItemId, isMobileLayout, ensureSizeForItem]);
43432
44212
  const handleChangeSize = reactExports.useCallback((externalId, sizeLabel) => {
43433
44213
  const item = resolved.items.find((i) => i.externalId === externalId);
43434
- if (!item) return;
44214
+ if (!item) {
44215
+ return;
44216
+ }
43435
44217
  const productData = buildVtoProductDataFromResolved(item);
43436
- if (!productData) return;
44218
+ if (!productData) {
44219
+ return;
44220
+ }
43437
44221
  const csa = findCsaByLabel(productData, sizeLabel, item.storage.color);
43438
- if (!csa) return;
44222
+ if (!csa) {
44223
+ return;
44224
+ }
43439
44225
  updateFittingRoomItem(externalId, {
43440
44226
  colorwaySizeAssetId: csa.colorwaySizeAssetId,
43441
44227
  size: sizeLabel,
43442
44228
  color: csa.colorLabel
43443
44229
  });
43444
44230
  }, [resolved.items, updateFittingRoomItem]);
44231
+ const handleChangeColor = reactExports.useCallback((externalId, colorLabel) => {
44232
+ const item = resolved.items.find((i) => i.externalId === externalId);
44233
+ if (!item || !item.storage.size) {
44234
+ return;
44235
+ }
44236
+ const productData = buildVtoProductDataFromResolved(item);
44237
+ if (!productData) {
44238
+ return;
44239
+ }
44240
+ const csa = findCsaByLabel(productData, item.storage.size, colorLabel);
44241
+ if (!csa) {
44242
+ return;
44243
+ }
44244
+ updateFittingRoomItem(externalId, {
44245
+ colorwaySizeAssetId: csa.colorwaySizeAssetId,
44246
+ size: item.storage.size,
44247
+ color: csa.colorLabel
44248
+ });
44249
+ }, [resolved.items, updateFittingRoomItem]);
43445
44250
  const handleAddToCart = reactExports.useCallback(async (externalId) => {
43446
44251
  const {
43447
44252
  addToCart
43448
44253
  } = getStaticData();
43449
44254
  if (!addToCart) {
43450
- logger$9.logWarn("No addToCart callback configured; skipping", {
44255
+ logger$8.logWarn("No addToCart callback configured; skipping", {
43451
44256
  externalId
43452
44257
  });
43453
44258
  return;
43454
44259
  }
43455
44260
  const item = resolved.items.find((i) => i.externalId === externalId);
43456
44261
  if (!item || !item.storage.size || !item.storage.color) {
43457
- logger$9.logWarn("Cannot add to cart: missing size or color", {
44262
+ logger$8.logWarn("Cannot add to cart: missing size or color", {
43458
44263
  externalId,
43459
44264
  size: item?.storage.size,
43460
44265
  color: item?.storage.color
@@ -43468,7 +44273,7 @@ function FittingRoomOverlay() {
43468
44273
  });
43469
44274
  closeOverlay();
43470
44275
  } catch (error) {
43471
- logger$9.logError("addToCart failed", {
44276
+ logger$8.logError("addToCart failed", {
43472
44277
  error,
43473
44278
  externalId
43474
44279
  });
@@ -43477,17 +44282,18 @@ function FittingRoomOverlay() {
43477
44282
  const handleToggleUntuck = reactExports.useCallback(() => {
43478
44283
  setForceUntuck((prev2) => !prev2);
43479
44284
  }, []);
43480
- const handleToggleZoom = reactExports.useCallback(() => {
43481
- setZoomed((prev2) => !prev2);
43482
- }, []);
43483
44285
  const handleRemoveItem = reactExports.useCallback((externalId) => {
43484
44286
  setSelectedExternalIds((prev2) => {
43485
- if (!prev2.has(externalId)) return prev2;
44287
+ if (!prev2.has(externalId)) {
44288
+ return prev2;
44289
+ }
43486
44290
  const next2 = new Set(prev2);
43487
44291
  next2.delete(externalId);
43488
44292
  return next2;
43489
44293
  });
43490
- if (openAccordionItemId === externalId) setOpenAccordionItemId(null);
44294
+ if (openAccordionItemId === externalId) {
44295
+ setOpenAccordionItemId(null);
44296
+ }
43491
44297
  }, [openAccordionItemId]);
43492
44298
  const handleTryItOn = reactExports.useCallback(() => {
43493
44299
  setMobileMode("try-on");
@@ -43501,10 +44307,25 @@ function FittingRoomOverlay() {
43501
44307
  setOpenAccordionItemId(null);
43502
44308
  }
43503
44309
  }, [openAccordionItemId, selectedExternalIds]);
44310
+ const preselectAppliedRef = reactExports.useRef(false);
44311
+ reactExports.useEffect(() => {
44312
+ if (preselectAppliedRef.current || !preselectExternalId) {
44313
+ return;
44314
+ }
44315
+ if (!resolved.items.some((i) => i.externalId === preselectExternalId)) {
44316
+ return;
44317
+ }
44318
+ preselectAppliedRef.current = true;
44319
+ handleSelectItem(preselectExternalId);
44320
+ }, [preselectExternalId, resolved.items, handleSelectItem]);
43504
44321
  const outfit = reactExports.useMemo(() => buildOutfit(selectedExternalIds, resolved, forceUntuck, lastAddedExternalId), [selectedExternalIds, resolved, forceUntuck, lastAddedExternalId]);
43505
44322
  reactExports.useEffect(() => {
43506
- if (!userIsLoggedIn || !userHasAvatar) return;
43507
- if (outfit.items.length === 0) return;
44323
+ if (!userIsLoggedIn || !userHasAvatar) {
44324
+ return;
44325
+ }
44326
+ if (outfit.items.length === 0) {
44327
+ return;
44328
+ }
43508
44329
  requestVtoComposition(toWireItems(outfit.items), true);
43509
44330
  if (getStaticData().config.features.vtoPrefetch) {
43510
44331
  for (const alt of outfit.alternates) {
@@ -43515,7 +44336,9 @@ function FittingRoomOverlay() {
43515
44336
  const frameUrls = reactExports.useMemo(() => {
43516
44337
  if (outfit.items.length === 0) {
43517
44338
  const bareFrames = userProfile?.avatar_frames;
43518
- if (!bareFrames || bareFrames.length === 0) return null;
44339
+ if (!bareFrames || bareFrames.length === 0) {
44340
+ return null;
44341
+ }
43519
44342
  const baseUrl2 = getStaticData().config.frames.baseUrl;
43520
44343
  return bareFrames.map((u) => applyFrameBaseUrl(u, baseUrl2));
43521
44344
  }
@@ -43539,7 +44362,7 @@ function FittingRoomOverlay() {
43539
44362
  closeOverlay();
43540
44363
  const authManager2 = getAuthManager();
43541
44364
  authManager2.logout().catch((error) => {
43542
- logger$9.logError("Error during logout", {
44365
+ logger$8.logError("Error during logout", {
43543
44366
  error
43544
44367
  });
43545
44368
  });
@@ -43586,29 +44409,6 @@ function FittingRoomOverlay() {
43586
44409
  fontSize: "13px",
43587
44410
  textDecoration: "underline",
43588
44411
  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
44412
  }
43613
44413
  }));
43614
44414
  const isMobileTryOn = isMobileLayout && mobileMode === "try-on";
@@ -43639,15 +44439,12 @@ function FittingRoomOverlay() {
43639
44439
  /* @__PURE__ */ jsx$1(TextT, { variant: "base", css: css2.emptyTagline, t: "landing.description" }),
43640
44440
  /* @__PURE__ */ jsx$1("div", { css: css2.emptyShopNow, children: /* @__PURE__ */ jsx$1(ButtonT, { variant: "primary", t: "fitting_room.shop_now", onClick: handleShopNow }) }),
43641
44441
  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
44442
+ ] }) }) : 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, onChangeColor: handleChangeColor, 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, onChangeColor: handleChangeColor, onAddToCart: handleAddToCart, onToggleUntuck: handleToggleUntuck, onSignOut: handleSignOut }),
44443
+ vtoError ? /* @__PURE__ */ jsx$1(Snackbar, { messageKey: "fitting_room.vto_error", onDismiss: clearVtoError }) : null
43647
44444
  ] }) });
43648
44445
  }
43649
44446
  const CONTACT_US_LINK = "mailto:info@thefittingroom.tech?subject=Forgot%20Password%20Assistance";
43650
- const logger$8 = getLogger("forgot-password");
44447
+ const logger$7 = getLogger("forgot-password");
43651
44448
  function ForgotPasswordOverlay({
43652
44449
  returnToOverlay
43653
44450
  }) {
@@ -43713,7 +44510,7 @@ function ForgotPasswordOverlay({
43713
44510
  await authManager2.sendPasswordResetEmail(email2);
43714
44511
  setLinkSent(true);
43715
44512
  } catch (error) {
43716
- logger$8.logError("Error sending password reset email:", {
44513
+ logger$7.logError("Error sending password reset email:", {
43717
44514
  error
43718
44515
  });
43719
44516
  }
@@ -43731,7 +44528,7 @@ function ForgotPasswordOverlay({
43731
44528
  return;
43732
44529
  }
43733
44530
  const email = emailEl.value;
43734
- resetUserPassword(email);
44531
+ void resetUserPassword(email);
43735
44532
  }, [t]);
43736
44533
  const handleBackToSignInClick = reactExports.useCallback(() => {
43737
44534
  openOverlay(OverlayName.SIGN_IN, {
@@ -43932,7 +44729,7 @@ function TfrTitle() {
43932
44729
  }));
43933
44730
  return /* @__PURE__ */ jsx$1("div", { css: css2.container, children: /* @__PURE__ */ jsx$1(SvgTfrName, { css: css2.nameIcon }) });
43934
44731
  }
43935
- const logger$7 = getLogger("sign-in");
44732
+ const logger$6 = getLogger("sign-in");
43936
44733
  function SignInOverlay({
43937
44734
  returnToOverlay
43938
44735
  }) {
@@ -44006,7 +44803,7 @@ function SignInOverlay({
44006
44803
  closeOverlay();
44007
44804
  }
44008
44805
  } catch (error) {
44009
- logger$7.logError("Login failed:", {
44806
+ logger$6.logError("Login failed:", {
44010
44807
  error
44011
44808
  });
44012
44809
  setEmailError(" ");
@@ -44038,7 +44835,7 @@ function SignInOverlay({
44038
44835
  }
44039
44836
  const email = emailEl.value;
44040
44837
  const password = passwordEl.value;
44041
- loginUser(email, password);
44838
+ void loginUser(email, password);
44042
44839
  }, [returnToOverlay, closeOverlay, openOverlay, t]);
44043
44840
  const handleForgotPasswordClick = reactExports.useCallback(() => {
44044
44841
  openOverlay(OverlayName.FORGOT_PASSWORD, {
@@ -44369,8 +45166,8 @@ function FitChart({
44369
45166
  const AVATAR_IMAGE_ASPECT_RATIO = 2 / 3;
44370
45167
  const AVATAR_GUTTER_HEIGHT_PX = 100;
44371
45168
  const CONTENT_AREA_WIDTH_PX = 550;
44372
- const logger$6 = getLogger("overlays/vto-single");
44373
- function VtoSingleOverlay() {
45169
+ const logger$5 = getLogger("overlays/quick-view");
45170
+ function QuickViewOverlay() {
44374
45171
  const userIsLoggedIn = useMainStore((state) => state.userIsLoggedIn);
44375
45172
  const userHasAvatar = useMainStore((state) => state.userHasAvatar);
44376
45173
  const userProfile = useMainStore((state) => state.userProfile);
@@ -44384,18 +45181,20 @@ function VtoSingleOverlay() {
44384
45181
  const [modalStyle, setModalStyle] = reactExports.useState({});
44385
45182
  const {
44386
45183
  request: vtoRequest,
44387
- framesForOutfit
45184
+ framesForOutfit,
45185
+ lastError: vtoError,
45186
+ clearError: clearVtoError
44388
45187
  } = useVtoRequests();
44389
45188
  reactExports.useEffect(() => {
44390
45189
  if (!userIsLoggedIn) {
44391
45190
  openOverlay(OverlayName.LANDING, {
44392
- returnToOverlay: OverlayName.VTO_SINGLE
45191
+ returnToOverlay: OverlayName.QUICK_VIEW
44393
45192
  });
44394
45193
  return;
44395
45194
  }
44396
45195
  if (userIsLoggedIn && userHasAvatar === false) {
44397
45196
  openOverlay(OverlayName.GET_APP, {
44398
- returnToOverlay: OverlayName.VTO_SINGLE,
45197
+ returnToOverlay: OverlayName.QUICK_VIEW,
44399
45198
  noAvatar: true
44400
45199
  });
44401
45200
  return;
@@ -44434,7 +45233,9 @@ function VtoSingleOverlay() {
44434
45233
  const {
44435
45234
  color: selectedColor
44436
45235
  } = await currentProduct.getSelectedOptions();
44437
- const styleCategoryLabel = storeProduct.style.style_category_label || null;
45236
+ const styleCategoryIndex = await loadStyleCategoryIndex();
45237
+ const styleCategoryGroup = styleCategoryIndex.groupForCategory(storeProduct.style.style_category_name);
45238
+ const styleCategoryLabel = styleCategoryGroup?.label ?? null;
44438
45239
  const sizeRecommendationRecord = storeProduct.sizeFitRecommendation;
44439
45240
  {
44440
45241
  const recommendedSizeId = sizeRecommendationRecord.recommended_size.id || null;
@@ -44463,7 +45264,7 @@ function VtoSingleOverlay() {
44463
45264
  const sku = csaRec.sku;
44464
45265
  const variant = variants.find((v) => v.sku === sku);
44465
45266
  if (!variant) {
44466
- logger$6.logWarn(`Variant not found for externalId: ${currentProduct.externalId} sizeId: ${sizeId} sku: ${sku}`);
45267
+ logger$5.logWarn(`Variant not found for externalId: ${currentProduct.externalId} sizeId: ${sizeId} sku: ${sku}`);
44467
45268
  continue;
44468
45269
  }
44469
45270
  const colorLabel = variant.color || null;
@@ -44476,7 +45277,7 @@ function VtoSingleOverlay() {
44476
45277
  });
44477
45278
  }
44478
45279
  if (!colors.length) {
44479
- logger$6.logWarn(`No valid colorways found for externalId: ${currentProduct.externalId} sizeId: ${sizeId}`);
45280
+ logger$5.logWarn(`No valid colorways found for externalId: ${currentProduct.externalId} sizeId: ${sizeId}`);
44480
45281
  continue;
44481
45282
  }
44482
45283
  sizes.push({
@@ -44519,7 +45320,7 @@ function VtoSingleOverlay() {
44519
45320
  setSelectedColorLabel(recommendedColorLabel);
44520
45321
  }
44521
45322
  } catch (error) {
44522
- logger$6.logError("Error fetching initial data:", {
45323
+ logger$5.logError("Error fetching initial data:", {
44523
45324
  error
44524
45325
  });
44525
45326
  setVtoProductData(false);
@@ -44530,7 +45331,7 @@ function VtoSingleOverlay() {
44530
45331
  if (vtoProductData !== null) {
44531
45332
  return;
44532
45333
  }
44533
- setupInitialVtoData();
45334
+ void setupInitialVtoData();
44534
45335
  }, [storeProductData, vtoProductData, userProfile]);
44535
45336
  const {
44536
45337
  sizeColorRecord: selectedColorSizeRecord,
@@ -44592,7 +45393,7 @@ function VtoSingleOverlay() {
44592
45393
  if (!rewritten) {
44593
45394
  return null;
44594
45395
  }
44595
- logger$6.logDebug(`{{ts}} - Displaying VTO for sku: ${selectedColorSizeRecord.sku}`);
45396
+ logger$5.logDebug(`{{ts}} - Displaying VTO for sku: ${selectedColorSizeRecord.sku}`);
44596
45397
  rewritten.forEach((url) => {
44597
45398
  const img = new Image();
44598
45399
  img.src = url;
@@ -44603,7 +45404,7 @@ function VtoSingleOverlay() {
44603
45404
  closeOverlay();
44604
45405
  const authManager2 = getAuthManager();
44605
45406
  authManager2.logout().catch((error) => {
44606
- logger$6.logError("Error during logout:", {
45407
+ logger$5.logError("Error during logout:", {
44607
45408
  error
44608
45409
  });
44609
45410
  });
@@ -44625,7 +45426,7 @@ function VtoSingleOverlay() {
44625
45426
  color: selectedColorLabel
44626
45427
  });
44627
45428
  } catch (error) {
44628
- logger$6.logError("Error adding to cart:", {
45429
+ logger$5.logError("Error adding to cart:", {
44629
45430
  error
44630
45431
  });
44631
45432
  }
@@ -44642,7 +45443,10 @@ function VtoSingleOverlay() {
44642
45443
  } else {
44643
45444
  Layout = DesktopLayout;
44644
45445
  }
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 }) });
45446
+ return /* @__PURE__ */ jsxs(SidecarModalFrame, { onRequestClose: closeOverlay, contentStyle: modalStyle, children: [
45447
+ /* @__PURE__ */ jsx$1(Layout, { loadedProductData: vtoProductData, selectedColorSizeRecord, availableColorLabels, selectedColorLabel, selectedSizeLabel, frameUrls, setModalStyle, onClose: closeOverlay, onChangeColor: setSelectedColorLabel, onChangeSize: setSelectedSizeLabel, onAddToCart: handleAddToCartClick, onSignOut: handleSignOutClick }),
45448
+ vtoError ? /* @__PURE__ */ jsx$1(Snackbar, { messageKey: "quick-view.vto_error", onDismiss: clearVtoError }) : null
45449
+ ] });
44646
45450
  }
44647
45451
  function NoFitLayout({
44648
45452
  onClose,
@@ -44674,14 +45478,59 @@ function NoFitLayout({
44674
45478
  }
44675
45479
  }));
44676
45480
  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 }) }),
45481
+ /* @__PURE__ */ jsx$1("div", { css: css2.titlebarContainer, children: /* @__PURE__ */ jsx$1(ModalTitlebar, { title: t("quick-view.title"), onCloseClick: onClose }) }),
44678
45482
  /* @__PURE__ */ jsxs("div", { css: css2.contentContainer, children: [
44679
45483
  /* @__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" }) }),
45484
+ /* @__PURE__ */ jsx$1("div", { css: css2.messageContainer, children: /* @__PURE__ */ jsx$1(TextT, { variant: "base", t: "quick-view.no_recommendation" }) }),
44681
45485
  /* @__PURE__ */ jsx$1("div", { css: css2.footerContainer, children: /* @__PURE__ */ jsx$1(Footer, { onSignOutClick: onSignOut }) })
44682
45486
  ] })
44683
45487
  ] });
44684
45488
  }
45489
+ function FittingRoomToggleButton() {
45490
+ const {
45491
+ currentProduct
45492
+ } = getStaticData();
45493
+ const productId = currentProduct?.externalId ?? null;
45494
+ const isInFittingRoom = useMainStore((state) => productId == null ? false : state.fittingRoom.some((item) => item.externalId === productId));
45495
+ const css2 = useCss((theme) => ({
45496
+ button: {
45497
+ marginTop: "10px",
45498
+ width: "100%",
45499
+ display: "flex",
45500
+ alignItems: "center",
45501
+ justifyContent: "center",
45502
+ gap: "10px",
45503
+ padding: "13px",
45504
+ backgroundColor: "#FFFFFF",
45505
+ border: `1px solid ${theme.color_fg_text}`,
45506
+ borderRadius: "30px",
45507
+ cursor: "pointer"
45508
+ },
45509
+ icon: {
45510
+ color: theme.color_fg_text,
45511
+ width: "20px",
45512
+ height: "20px"
45513
+ },
45514
+ text: {
45515
+ fontSize: "14px",
45516
+ textTransform: "uppercase"
45517
+ }
45518
+ }));
45519
+ if (productId == null) {
45520
+ return null;
45521
+ }
45522
+ const handleClick = () => {
45523
+ toggleFittingRoomItem(productId, currentProduct?.handle ?? null, true).catch((error) => {
45524
+ logger$5.logError("toggleFittingRoomItem failed", {
45525
+ error
45526
+ });
45527
+ });
45528
+ };
45529
+ return /* @__PURE__ */ jsxs("button", { type: "button", onClick: handleClick, css: css2.button, children: [
45530
+ /* @__PURE__ */ jsx$1(SvgFittingRoomIcon, { css: css2.icon }),
45531
+ /* @__PURE__ */ jsx$1(TextT, { variant: "base", css: css2.text, t: isInFittingRoom ? "added_to_fitting_room" : "add_to_fitting_room" })
45532
+ ] });
45533
+ }
44685
45534
  function MobileLayout({
44686
45535
  loadedProductData,
44687
45536
  selectedColorSizeRecord,
@@ -44787,7 +45636,11 @@ function MobileLayout({
44787
45636
  if (!bottomFrameInnerEl) {
44788
45637
  return;
44789
45638
  }
44790
- const maxHeightPx = Number(window.getComputedStyle(bottomFrameInnerEl.parentElement).getPropertyValue("max-height").replace("px", ""));
45639
+ const parentEl = bottomFrameInnerEl.parentElement;
45640
+ if (!parentEl) {
45641
+ return;
45642
+ }
45643
+ const maxHeightPx = Number(window.getComputedStyle(parentEl).getPropertyValue("max-height").replace("px", ""));
44791
45644
  const heightPx = Math.min(bottomFrameInnerEl.clientHeight, maxHeightPx);
44792
45645
  const bottomFrameStyle = {
44793
45646
  height: `${heightPx}px`
@@ -44892,8 +45745,11 @@ function MobileContentExpanded({
44892
45745
  /* @__PURE__ */ jsx$1("div", { css: css2.sizeSelectorContainer, children: /* @__PURE__ */ jsx$1(SizeSelector, { loadedProductData, selectedSizeLabel, onChangeSize }) }),
44893
45746
  /* @__PURE__ */ jsx$1("div", { css: css2.itemFitTextContainer, children: /* @__PURE__ */ jsx$1(ItemFitText, { loadedProductData }) }),
44894
45747
  /* @__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 }) })
45748
+ /* @__PURE__ */ jsxs("div", { css: css2.buttonContainer, children: [
45749
+ /* @__PURE__ */ jsx$1(AddToCartButton, { onClick: onAddToCart }),
45750
+ /* @__PURE__ */ jsx$1(FittingRoomToggleButton, {})
45751
+ ] }),
45752
+ /* @__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
45753
  ] });
44898
45754
  }
44899
45755
  function MobileContentFull({
@@ -44988,8 +45844,11 @@ function MobileContentFull({
44988
45844
  ] }),
44989
45845
  fitChartNode,
44990
45846
  /* @__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 }) }),
45847
+ /* @__PURE__ */ jsxs("div", { css: css2.buttonContainer, children: [
45848
+ /* @__PURE__ */ jsx$1(AddToCartButton, { onClick: onAddToCart }),
45849
+ /* @__PURE__ */ jsx$1(FittingRoomToggleButton, {})
45850
+ ] }),
45851
+ /* @__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
45852
  /* @__PURE__ */ jsx$1("div", { css: css2.priceContainer, children: /* @__PURE__ */ jsx$1(Text, { variant: "base", css: css2.priceText, children: selectedColorSizeRecord.priceFormatted }) }),
44994
45853
  /* @__PURE__ */ jsx$1("div", { css: css2.productDescriptionContainer, children: /* @__PURE__ */ jsx$1(ProductDescriptionText, { loadedProductData }) }),
44995
45854
  /* @__PURE__ */ jsx$1("div", { css: css2.footerContainer, children: /* @__PURE__ */ jsx$1(Footer, { onSignOutClick: onSignOut }) })
@@ -45105,7 +45964,7 @@ function DesktopLayout({
45105
45964
  return /* @__PURE__ */ jsxs("div", { css: css2.mainContainer, children: [
45106
45965
  /* @__PURE__ */ jsx$1(Avatar, { frameUrls, setModalStyle }),
45107
45966
  /* @__PURE__ */ jsxs("div", { css: css2.rightContainer, children: [
45108
- /* @__PURE__ */ jsx$1(ModalTitlebar, { title: t("try_it_on"), onCloseClick: onClose }),
45967
+ /* @__PURE__ */ jsx$1(ModalTitlebar, { title: t("quick-view.title"), onCloseClick: onClose }),
45109
45968
  /* @__PURE__ */ jsxs("div", { css: css2.contentContainer, children: [
45110
45969
  /* @__PURE__ */ jsx$1("div", { css: css2.productNameContainer, children: /* @__PURE__ */ jsx$1(Text, { variant: "brand", css: css2.productNameText, children: loadedProductData.productName }) }),
45111
45970
  /* @__PURE__ */ jsx$1("div", { css: css2.priceContainer, children: /* @__PURE__ */ jsx$1(Text, { variant: "base", css: css2.priceText, children: selectedColorSizeRecord.priceFormatted }) }),
@@ -45121,7 +45980,10 @@ function DesktopLayout({
45121
45980
  /* @__PURE__ */ jsx$1("div", { css: css2.itemFitDetailsContainer, children: /* @__PURE__ */ jsx$1(ItemFitDetails, { loadedProductData, selectedSizeLabel }) })
45122
45981
  ] }),
45123
45982
  fitChartNode,
45124
- /* @__PURE__ */ jsx$1("div", { css: css2.buttonContainer, children: /* @__PURE__ */ jsx$1(AddToCartButton, { onClick: onAddToCart }) }),
45983
+ /* @__PURE__ */ jsxs("div", { css: css2.buttonContainer, children: [
45984
+ /* @__PURE__ */ jsx$1(AddToCartButton, { onClick: onAddToCart }),
45985
+ /* @__PURE__ */ jsx$1(FittingRoomToggleButton, {})
45986
+ ] }),
45125
45987
  /* @__PURE__ */ jsx$1("div", { css: css2.descriptionContainer, children: /* @__PURE__ */ jsx$1(ProductDescriptionText, { loadedProductData }) })
45126
45988
  ] }),
45127
45989
  /* @__PURE__ */ jsx$1(Footer, { onSignOutClick: onSignOut })
@@ -45141,10 +46003,37 @@ function Avatar({
45141
46003
  bottomContainerStyle: {}
45142
46004
  });
45143
46005
  const [selectedFrameIndex, setSelectedFrameIndex] = reactExports.useState(null);
46006
+ const [zoomOpen, setZoomOpen] = reactExports.useState(false);
45144
46007
  const css2 = useCss((theme) => ({
45145
46008
  topContainer: {
45146
46009
  flex: "none",
45147
- height: "100%"
46010
+ height: "100%",
46011
+ // Positioning context for the zoom pill overlaid on the avatar.
46012
+ position: "relative"
46013
+ },
46014
+ zoomPill: {
46015
+ position: "absolute",
46016
+ // Bottom-right of the avatar image, clear of the slider gutter below it.
46017
+ bottom: `${AVATAR_GUTTER_HEIGHT_PX + 16}px`,
46018
+ right: "16px",
46019
+ display: "inline-flex",
46020
+ alignItems: "center",
46021
+ gap: "8px",
46022
+ padding: "8px 16px",
46023
+ borderRadius: "24px",
46024
+ backgroundColor: "rgba(255, 255, 255, 0.95)",
46025
+ border: `1px solid ${theme.color_fg_text}`,
46026
+ fontSize: "12px",
46027
+ fontWeight: "500",
46028
+ letterSpacing: "0.5px",
46029
+ textTransform: "uppercase",
46030
+ cursor: "pointer",
46031
+ zIndex: 2
46032
+ },
46033
+ zoomPillIcon: {
46034
+ width: "14px",
46035
+ height: "14px",
46036
+ flex: "none"
45148
46037
  },
45149
46038
  bottomContainer: {
45150
46039
  position: "absolute",
@@ -45258,10 +46147,15 @@ function Avatar({
45258
46147
  }, [isMobileLayout]);
45259
46148
  const isReady = !!frameUrls && selectedFrameIndex != null;
45260
46149
  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: [
46150
+ /* @__PURE__ */ jsx$1(AvatarFrameViewer, { frameUrls, selectedFrameIndex, setSelectedFrameIndex, imageContainerStyle: layoutData.imageContainerStyle, imageStyle: layoutData.imageStyle, loadingT: "quick-view.avatar_loading" }),
46151
+ isReady && !isMobileLayout ? /* @__PURE__ */ jsxs(Button, { variant: "base", css: css2.zoomPill, onClick: () => setZoomOpen(true), children: [
46152
+ /* @__PURE__ */ jsx$1(SvgIconZoom, { css: css2.zoomPillIcon }),
46153
+ /* @__PURE__ */ jsx$1(TextT, { variant: "base", t: "quick-view.zoom_in" })
46154
+ ] }) : null,
46155
+ zoomOpen && frameUrls && frameUrls.length > 0 ? /* @__PURE__ */ jsx$1(ZoomModal, { frameUrls, selectedFrameIndex, setSelectedFrameIndex, onClose: () => setZoomOpen(false) }) : null,
46156
+ 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
46157
  /* @__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 })
46158
+ /* @__PURE__ */ jsx$1(TextT, { variant: "base", t: "quick-view.slide_to_rotate", css: css2.sliderText })
45265
46159
  ] }) }) : null
45266
46160
  ] });
45267
46161
  }
@@ -45291,35 +46185,6 @@ function ProductSummaryRow({
45291
46185
  /* @__PURE__ */ jsx$1("div", { css: css2.nameContainer, children: /* @__PURE__ */ jsx$1(Text, { variant: "brand", css: css2.nameText, children: loadedProductData.productName }) })
45292
46186
  ] });
45293
46187
  }
45294
- function ColorSelector({
45295
- availableColorLabels,
45296
- selectedColorLabel,
45297
- onChangeColor
45298
- }) {
45299
- const css2 = useCss((theme) => ({
45300
- colorContainer: {},
45301
- colorLabelText: {
45302
- fontSize: "12px"
45303
- },
45304
- colorSelect: {
45305
- border: "none",
45306
- color: theme.color_fg_text,
45307
- fontFamily: theme.font_family,
45308
- fontSize: "12px"
45309
- }
45310
- }));
45311
- const handleColorSelectChange = reactExports.useCallback((e) => {
45312
- const newColorLabel = e.target.value || null;
45313
- onChangeColor(newColorLabel);
45314
- }, []);
45315
- if (availableColorLabels.length < 2) {
45316
- return null;
45317
- }
45318
- 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" }),
45320
- /* @__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
- ] }) });
45322
- }
45323
46188
  function RecommendedSizeText({
45324
46189
  loadedProductData,
45325
46190
  textCss
@@ -45363,11 +46228,11 @@ function Footer({
45363
46228
  }
45364
46229
  }));
45365
46230
  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" }),
46231
+ /* @__PURE__ */ jsx$1(LinkT, { variant: "underline", css: css2.signOutLink, onClick: onSignOutClick, t: "quick-view.sign_out" }),
45367
46232
  /* @__PURE__ */ jsx$1(SvgTfrName, { css: css2.tfrIcon })
45368
46233
  ] });
45369
46234
  }
45370
- const logger$5 = getLogger("widgets/add-to-fitting-room-compact");
46235
+ const logger$4 = getLogger("widgets/add-to-fitting-room-compact");
45371
46236
  function AddToFittingRoomCompactWidget({
45372
46237
  attributes
45373
46238
  }) {
@@ -45413,7 +46278,7 @@ function AddToFittingRoomCompactWidget({
45413
46278
  }
45414
46279
  const handleClick = () => {
45415
46280
  toggleFittingRoomItem(productId, productHandle, isPdp).catch((error) => {
45416
- logger$5.logError("toggleFittingRoomItem failed", {
46281
+ logger$4.logError("toggleFittingRoomItem failed", {
45417
46282
  error
45418
46283
  });
45419
46284
  });
@@ -45421,64 +46286,8 @@ function AddToFittingRoomCompactWidget({
45421
46286
  const ariaLabel = t(isInFittingRoom ? "added_to_fitting_room" : "add_to_fitting_room");
45422
46287
  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
46288
  }
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
46289
  const logger$3 = getLogger("widgets/fitting-room-icon");
45481
- function FittingRoomIconWidget({}) {
46290
+ function FittingRoomIconWidget(_props) {
45482
46291
  const {
45483
46292
  t
45484
46293
  } = useTranslation();
@@ -45540,7 +46349,7 @@ function FittingRoomIconWidget({}) {
45540
46349
  ] });
45541
46350
  }
45542
46351
  const logger$2 = getLogger("widgets/fitting-room");
45543
- function FittingRoomWidget({}) {
46352
+ function FittingRoomWidget(_props) {
45544
46353
  const count = useMainStore((state) => state.fittingRoom.length);
45545
46354
  const openOverlay = useMainStore((state) => state.openOverlay);
45546
46355
  const css2 = useCss((theme) => ({
@@ -45597,11 +46406,11 @@ function FittingRoomWidget({}) {
45597
46406
  ] });
45598
46407
  }
45599
46408
  const logger$1 = getLogger("size-rec");
45600
- function SizeRecWidget({}) {
46409
+ function SizeRecWidget(_props) {
45601
46410
  const openOverlay = useMainStore((state) => state.openOverlay);
45602
46411
  const openedOverlays = useMainStore((state) => state.openedOverlays);
45603
46412
  const storeProductData = useMainStore((state) => state.productData);
45604
- const hasOpenedVtoSingleOverlay = openedOverlays.includes(OverlayName.VTO_SINGLE);
46413
+ const hasOpenedQuickViewOverlay = openedOverlays.includes(OverlayName.QUICK_VIEW);
45605
46414
  const sizeRecommendationRecord = reactExports.useMemo(() => {
45606
46415
  const {
45607
46416
  currentProduct
@@ -45625,9 +46434,9 @@ function SizeRecWidget({}) {
45625
46434
  return productData.sizeFitRecommendation || null;
45626
46435
  }, [storeProductData]);
45627
46436
  const handleLinkClick = reactExports.useCallback(() => {
45628
- openOverlay(OverlayName.VTO_SINGLE);
46437
+ openOverlay(OverlayName.QUICK_VIEW);
45629
46438
  }, [openOverlay]);
45630
- if (!sizeRecommendationRecord || !hasOpenedVtoSingleOverlay) {
46439
+ if (!sizeRecommendationRecord || !hasOpenedQuickViewOverlay) {
45631
46440
  return null;
45632
46441
  }
45633
46442
  const sizeLabel = getSizeLabelFromSize(sizeRecommendationRecord.recommended_size);
@@ -45639,8 +46448,11 @@ function SizeRecWidget({}) {
45639
46448
  } });
45640
46449
  }
45641
46450
  const logger = getLogger("widgets/vto-button");
45642
- function VtoButtonWidget({}) {
46451
+ function VtoButtonWidget(_props) {
45643
46452
  const openOverlay = useMainStore((state) => state.openOverlay);
46453
+ const currentProduct = getStaticData().currentProduct ?? null;
46454
+ const currentProductId = currentProduct?.externalId ?? null;
46455
+ const hasOtherFittingRoomItems = useMainStore((state) => state.fittingRoom.some((item) => item.externalId !== currentProductId));
45644
46456
  const css2 = useCss((theme) => ({
45645
46457
  button: {
45646
46458
  marginTop: "10px",
@@ -45667,13 +46479,29 @@ function VtoButtonWidget({}) {
45667
46479
  textTransform: "uppercase"
45668
46480
  }
45669
46481
  }));
45670
- const openVto = () => {
45671
- logger.logDebug("{{ts}} - Opening VTO overlay");
45672
- openOverlay(OverlayName.VTO_SINGLE);
46482
+ const handleClick = () => {
46483
+ if (!hasOtherFittingRoomItems) {
46484
+ logger.logDebug("{{ts}} - Opening quick-view overlay");
46485
+ openOverlay(OverlayName.QUICK_VIEW);
46486
+ return;
46487
+ }
46488
+ logger.logDebug("{{ts}} - Opening fitting-room overlay");
46489
+ if (!currentProductId) {
46490
+ openOverlay(OverlayName.FITTING_ROOM);
46491
+ return;
46492
+ }
46493
+ ensureFittingRoomItem(currentProductId, currentProduct?.handle ?? null, true).catch((error) => {
46494
+ logger.logError("Failed to add current product to fitting room", {
46495
+ error
46496
+ });
46497
+ });
46498
+ openOverlay(OverlayName.FITTING_ROOM, {
46499
+ preselectExternalId: currentProductId
46500
+ });
45673
46501
  };
45674
- return /* @__PURE__ */ jsxs("button", { type: "button", onClick: openVto, css: css2.button, children: [
46502
+ return /* @__PURE__ */ jsxs("button", { type: "button", onClick: handleClick, css: css2.button, children: [
45675
46503
  /* @__PURE__ */ jsx$1(SvgTfrIcon, { css: css2.icon }),
45676
- /* @__PURE__ */ jsx$1(TextT, { variant: "base", css: css2.text, t: "try_it_on" })
46504
+ /* @__PURE__ */ jsx$1(TextT, { variant: "base", css: css2.text, t: hasOtherFittingRoomItems ? "try_it_on" : "quick-view.title" })
45677
46505
  ] });
45678
46506
  }
45679
46507
  var DeviceLayout = /* @__PURE__ */ ((DeviceLayout2) => {
@@ -45738,10 +46566,6 @@ function _init$1() {
45738
46566
  });
45739
46567
  }
45740
46568
  const WIDGETS = {
45741
- [
45742
- "add-to-fitting-room"
45743
- /* ADD_TO_FITTING_ROOM */
45744
- ]: AddToFittingRoomWidget,
45745
46569
  [
45746
46570
  "add-to-fitting-room-compact"
45747
46571
  /* ADD_TO_FITTING_ROOM_COMPACT */
@@ -45769,7 +46593,7 @@ var OverlayName = /* @__PURE__ */ ((OverlayName2) => {
45769
46593
  OverlayName2["GET_APP"] = "get-app";
45770
46594
  OverlayName2["LANDING"] = "landing";
45771
46595
  OverlayName2["SIGN_IN"] = "sign-in";
45772
- OverlayName2["VTO_SINGLE"] = "vto-single";
46596
+ OverlayName2["QUICK_VIEW"] = "quick-view";
45773
46597
  return OverlayName2;
45774
46598
  })(OverlayName || {});
45775
46599
  const OVERLAYS = {
@@ -45794,9 +46618,9 @@ const OVERLAYS = {
45794
46618
  /* SIGN_IN */
45795
46619
  ]: SignInOverlay,
45796
46620
  [
45797
- "vto-single"
45798
- /* VTO_SINGLE */
45799
- ]: VtoSingleOverlay
46621
+ "quick-view"
46622
+ /* QUICK_VIEW */
46623
+ ]: QuickViewOverlay
45800
46624
  };
45801
46625
  let staticData = null;
45802
46626
  function _init(initStaticData) {
@@ -45941,9 +46765,9 @@ const SHARED_CONFIG = {
45941
46765
  appGooglePlayUrl: "https://play.google.com/store/apps/details?id=com.thefittingroom.marketplace"
45942
46766
  },
45943
46767
  build: {
45944
- version: `${"5.0.23"}`,
45945
- commitHash: `${"ab793d5"}`,
45946
- date: `${"2026-05-19T13:40:12.965Z"}`
46768
+ version: `${"5.0.25"}`,
46769
+ commitHash: `${"101d833"}`,
46770
+ date: `${"2026-05-22T00:57:54.147Z"}`
45947
46771
  }
45948
46772
  };
45949
46773
  const CONFIGS = {
@@ -46067,7 +46891,7 @@ const CONFIGS = {
46067
46891
  const getConfig = (envName) => {
46068
46892
  return CONFIGS[envName];
46069
46893
  };
46070
- const css = "body.tfr-modal-open {\n overflow: hidden;\n position: fixed;\n width: 100%;\n}\n";
46894
+ 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
46895
  class TfrWidgetElement extends HTMLElement {
46072
46896
  connectedCallback() {
46073
46897
  const attributes = this.getAttributeNames().reduce((attrs, name2) => {
@@ -46090,7 +46914,8 @@ async function init(initParams) {
46090
46914
  debug,
46091
46915
  productLookup,
46092
46916
  getOverlayTopOffset,
46093
- addToCart
46917
+ addToCart,
46918
+ testHooks
46094
46919
  } = initParams;
46095
46920
  if (!brandId || typeof brandId !== "number" || isNaN(brandId) || brandId <= 0) {
46096
46921
  throw new Error(`Invalid brandId "${brandId}"`);
@@ -46116,7 +46941,8 @@ async function init(initParams) {
46116
46941
  config,
46117
46942
  productLookup: productLookup ?? null,
46118
46943
  getOverlayTopOffset: getOverlayTopOffset ?? null,
46119
- addToCart: addToCart ?? null
46944
+ addToCart: addToCart ?? null,
46945
+ testHooks
46120
46946
  });
46121
46947
  _init$7();
46122
46948
  _init$5();