@wewear/virtual-try-on 1.4.9 → 1.4.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/index.d.ts +1 -1
- package/dist/components/loading-overlay.d.ts +0 -9
- package/dist/components/status-badge.d.ts +3 -0
- package/dist/constants.d.ts +5 -8
- package/dist/index.d.ts +0 -9
- package/dist/index.esm.js +383 -508
- package/dist/index.esm.js.map +1 -1
- package/dist/index.js +383 -508
- package/dist/index.js.map +1 -1
- package/dist/widget.d.ts +2 -41
- package/package.json +1 -1
package/dist/index.esm.js
CHANGED
|
@@ -1,59 +1,13 @@
|
|
|
1
|
-
/** CSS class names for consistent styling */
|
|
2
1
|
const CSS_CLASSES = {
|
|
3
2
|
BUTTON_CONTAINER: "wewear-vto-button-container",
|
|
4
3
|
BUTTON: "wewear-vto-button",
|
|
5
4
|
MODAL: "wewear-vto-modal",
|
|
6
|
-
PREVIEW_BADGE: "ww-vto-preview-badge",
|
|
7
5
|
};
|
|
8
|
-
/** Z-index values for proper layering */
|
|
9
6
|
const Z_INDEX = {
|
|
10
7
|
BUTTON: 10,
|
|
11
8
|
MODAL: 99999,
|
|
12
9
|
};
|
|
13
10
|
|
|
14
|
-
function createPreviewBadge() {
|
|
15
|
-
const badge = document.createElement("div");
|
|
16
|
-
badge.className = CSS_CLASSES.PREVIEW_BADGE;
|
|
17
|
-
badge.innerText = "Enhancing result...";
|
|
18
|
-
const styleId = "ww-vto-badge-styles";
|
|
19
|
-
if (!document.getElementById(styleId)) {
|
|
20
|
-
const styles = document.createElement("style");
|
|
21
|
-
styles.id = styleId;
|
|
22
|
-
styles.innerHTML = `
|
|
23
|
-
.${CSS_CLASSES.PREVIEW_BADGE} {
|
|
24
|
-
position: absolute;
|
|
25
|
-
top: 20px;
|
|
26
|
-
right: 20px;
|
|
27
|
-
background: rgba(31, 41, 55, 0.9);
|
|
28
|
-
backdrop-filter: blur(4px);
|
|
29
|
-
-webkit-backdrop-filter: blur(4px);
|
|
30
|
-
color: white;
|
|
31
|
-
padding: 6px 14px;
|
|
32
|
-
border-radius: 6px;
|
|
33
|
-
font-size: 12px;
|
|
34
|
-
font-weight: 600;
|
|
35
|
-
letter-spacing: 0.5px;
|
|
36
|
-
z-index: 10;
|
|
37
|
-
pointer-events: none;
|
|
38
|
-
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
|
|
39
|
-
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
|
|
40
|
-
animation: ww-vto-pulse 2s ease-in-out infinite;
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
@keyframes ww-vto-pulse {
|
|
44
|
-
0%, 100% {
|
|
45
|
-
opacity: 0.9;
|
|
46
|
-
}
|
|
47
|
-
50% {
|
|
48
|
-
opacity: 1;
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
`;
|
|
52
|
-
document.head.appendChild(styles);
|
|
53
|
-
}
|
|
54
|
-
return badge;
|
|
55
|
-
}
|
|
56
|
-
|
|
57
11
|
function getPositionStyles(position) {
|
|
58
12
|
switch (position) {
|
|
59
13
|
case "bottom-left":
|
|
@@ -77,140 +31,135 @@ function createButtonContainer(buttonPosition, hasVirtualTryOn = false, onCamera
|
|
|
77
31
|
const container = document.createElement("div");
|
|
78
32
|
container.className = `${CSS_CLASSES.BUTTON_CONTAINER} ww-button-group`;
|
|
79
33
|
const positionStyles = getPositionStyles(buttonPosition);
|
|
80
|
-
container.style.cssText = `
|
|
81
|
-
position: absolute;
|
|
82
|
-
${positionStyles}
|
|
83
|
-
display: flex;
|
|
84
|
-
gap: 4px;
|
|
85
|
-
border-radius: 100px;
|
|
86
|
-
background: white;
|
|
87
|
-
padding: 4px;
|
|
88
|
-
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
|
|
89
|
-
z-index: ${Z_INDEX.BUTTON};
|
|
90
|
-
transition: all 0.2s ease;
|
|
34
|
+
container.style.cssText = `
|
|
35
|
+
position: absolute;
|
|
36
|
+
${positionStyles}
|
|
37
|
+
display: flex;
|
|
38
|
+
gap: 4px;
|
|
39
|
+
border-radius: 100px;
|
|
40
|
+
background: white;
|
|
41
|
+
padding: 4px;
|
|
42
|
+
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
|
|
43
|
+
z-index: ${Z_INDEX.BUTTON};
|
|
44
|
+
transition: all 0.2s ease;
|
|
91
45
|
`;
|
|
92
|
-
// Camera button
|
|
93
46
|
const cameraButton = document.createElement("button");
|
|
94
47
|
cameraButton.type = "button";
|
|
95
48
|
cameraButton.className = `${CSS_CLASSES.BUTTON} ww-camera-btn`;
|
|
96
49
|
cameraButton.setAttribute("aria-label", "Virtual Try-On");
|
|
97
|
-
// Show full text with sparkles when alone, just camera icon when with other buttons
|
|
98
50
|
if (hasVirtualTryOn) {
|
|
99
|
-
cameraButton.style.cssText = `
|
|
100
|
-
display: flex;
|
|
101
|
-
width: 40px;
|
|
102
|
-
height: 40px;
|
|
103
|
-
cursor: pointer;
|
|
104
|
-
align-items: center;
|
|
105
|
-
justify-content: center;
|
|
106
|
-
border-radius: 100px;
|
|
107
|
-
padding: 0;
|
|
108
|
-
border: none;
|
|
109
|
-
background: white;
|
|
110
|
-
color: #000000;
|
|
111
|
-
transition: all 0.2s ease;
|
|
112
|
-
flex-shrink: 0;
|
|
51
|
+
cameraButton.style.cssText = `
|
|
52
|
+
display: flex;
|
|
53
|
+
width: 40px;
|
|
54
|
+
height: 40px;
|
|
55
|
+
cursor: pointer;
|
|
56
|
+
align-items: center;
|
|
57
|
+
justify-content: center;
|
|
58
|
+
border-radius: 100px;
|
|
59
|
+
padding: 0;
|
|
60
|
+
border: none;
|
|
61
|
+
background: white;
|
|
62
|
+
color: #000000;
|
|
63
|
+
transition: all 0.2s ease;
|
|
64
|
+
flex-shrink: 0;
|
|
113
65
|
`;
|
|
114
|
-
cameraButton.innerHTML = `
|
|
115
|
-
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="display: block;">
|
|
116
|
-
<path d="M14.5 4h-5L7 7H4a2 2 0 0 0-2 2v9a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2V9a2 2 0 0 0-2-2h-3l-2.5-3z"></path>
|
|
117
|
-
<circle cx="12" cy="13" r="3"></circle>
|
|
118
|
-
</svg>
|
|
66
|
+
cameraButton.innerHTML = `
|
|
67
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="display: block;">
|
|
68
|
+
<path d="M14.5 4h-5L7 7H4a2 2 0 0 0-2 2v9a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2V9a2 2 0 0 0-2-2h-3l-2.5-3z"></path>
|
|
69
|
+
<circle cx="12" cy="13" r="3"></circle>
|
|
70
|
+
</svg>
|
|
119
71
|
`;
|
|
120
72
|
}
|
|
121
73
|
else {
|
|
122
|
-
cameraButton.style.cssText = `
|
|
123
|
-
display: flex;
|
|
124
|
-
cursor: pointer;
|
|
125
|
-
align-items: center;
|
|
126
|
-
justify-content: center;
|
|
127
|
-
border-radius: 100px;
|
|
128
|
-
padding: 10px 16px;
|
|
129
|
-
border: none;
|
|
130
|
-
background: white;
|
|
131
|
-
color: #000000;
|
|
132
|
-
transition: all 0.2s ease;
|
|
133
|
-
flex-shrink: 0;
|
|
134
|
-
gap: 8px;
|
|
135
|
-
font-size: 14px;
|
|
136
|
-
font-weight: 600;
|
|
137
|
-
white-space: nowrap;
|
|
74
|
+
cameraButton.style.cssText = `
|
|
75
|
+
display: flex;
|
|
76
|
+
cursor: pointer;
|
|
77
|
+
align-items: center;
|
|
78
|
+
justify-content: center;
|
|
79
|
+
border-radius: 100px;
|
|
80
|
+
padding: 10px 16px;
|
|
81
|
+
border: none;
|
|
82
|
+
background: white;
|
|
83
|
+
color: #000000;
|
|
84
|
+
transition: all 0.2s ease;
|
|
85
|
+
flex-shrink: 0;
|
|
86
|
+
gap: 8px;
|
|
87
|
+
font-size: 14px;
|
|
88
|
+
font-weight: 600;
|
|
89
|
+
white-space: nowrap;
|
|
138
90
|
`;
|
|
139
|
-
cameraButton.innerHTML = `
|
|
140
|
-
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="display: block; flex-shrink: 0;">
|
|
141
|
-
<path d="M11.017 2.814a1 1 0 0 1 1.966 0l1.051 5.558a2 2 0 0 0 1.594 1.594l5.558 1.051a1 1 0 0 1 0 1.966l-5.558 1.051a2 2 0 0 0-1.594 1.594l-1.051 5.558a1 1 0 0 1-1.966 0l-1.051-5.558a2 2 0 0 0-1.594-1.594l-5.558-1.051a1 1 0 0 1 0-1.966l5.558-1.051a2 2 0 0 0 1.594-1.594z"/>
|
|
142
|
-
<path d="M20 2v4"/>
|
|
143
|
-
<path d="M22 4h-4"/>
|
|
144
|
-
<circle cx="4" cy="20" r="2"/>
|
|
145
|
-
</svg>
|
|
146
|
-
<span style="display: block;">TRY THIS LOOK</span>
|
|
91
|
+
cameraButton.innerHTML = `
|
|
92
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="display: block; flex-shrink: 0;">
|
|
93
|
+
<path d="M11.017 2.814a1 1 0 0 1 1.966 0l1.051 5.558a2 2 0 0 0 1.594 1.594l5.558 1.051a1 1 0 0 1 0 1.966l-5.558 1.051a2 2 0 0 0-1.594 1.594l-1.051 5.558a1 1 0 0 1-1.966 0l-1.051-5.558a2 2 0 0 0-1.594-1.594l-5.558-1.051a1 1 0 0 1 0-1.966l5.558-1.051a2 2 0 0 0 1.594-1.594z"/>
|
|
94
|
+
<path d="M20 2v4"/>
|
|
95
|
+
<path d="M22 4h-4"/>
|
|
96
|
+
<circle cx="4" cy="20" r="2"/>
|
|
97
|
+
</svg>
|
|
98
|
+
<span style="display: block;">TRY THIS LOOK</span>
|
|
147
99
|
`;
|
|
148
100
|
}
|
|
149
101
|
cameraButton.onclick = onCameraClick;
|
|
150
102
|
container.appendChild(cameraButton);
|
|
151
|
-
// Add refresh and toggle buttons if we have a virtual try-on
|
|
152
103
|
if (hasVirtualTryOn) {
|
|
153
|
-
// Refresh button - calls API again
|
|
154
104
|
if (onRefreshClick) {
|
|
155
105
|
const refreshButton = document.createElement("button");
|
|
156
106
|
refreshButton.type = "button";
|
|
157
107
|
refreshButton.className = "ww-refresh-btn";
|
|
158
108
|
refreshButton.setAttribute("aria-label", "Refresh virtual try-on");
|
|
159
|
-
refreshButton.style.cssText = `
|
|
160
|
-
display: flex;
|
|
161
|
-
width: 40px;
|
|
162
|
-
height: 40px;
|
|
163
|
-
cursor: pointer;
|
|
164
|
-
align-items: center;
|
|
165
|
-
justify-content: center;
|
|
166
|
-
border-radius: 100px;
|
|
167
|
-
padding: 0;
|
|
168
|
-
border: none;
|
|
169
|
-
background: white;
|
|
170
|
-
color: #333333;
|
|
171
|
-
transition: all 0.2s ease;
|
|
172
|
-
flex-shrink: 0;
|
|
109
|
+
refreshButton.style.cssText = `
|
|
110
|
+
display: flex;
|
|
111
|
+
width: 40px;
|
|
112
|
+
height: 40px;
|
|
113
|
+
cursor: pointer;
|
|
114
|
+
align-items: center;
|
|
115
|
+
justify-content: center;
|
|
116
|
+
border-radius: 100px;
|
|
117
|
+
padding: 0;
|
|
118
|
+
border: none;
|
|
119
|
+
background: white;
|
|
120
|
+
color: #333333;
|
|
121
|
+
transition: all 0.2s ease;
|
|
122
|
+
flex-shrink: 0;
|
|
173
123
|
`;
|
|
174
|
-
refreshButton.innerHTML = `
|
|
175
|
-
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="display: block;">
|
|
176
|
-
<path d="M21 12a9 9 0 0 0-9-9 9.75 9.75 0 0 0-6.74 2.74L3 8"></path>
|
|
177
|
-
<path d="M3 3v5h5"></path>
|
|
178
|
-
<path d="M3 12a9 9 0 0 0 9 9 9.75 9.75 0 0 0 6.74-2.74L21 16"></path>
|
|
179
|
-
<path d="M16 16h5v5"></path>
|
|
180
|
-
</svg>
|
|
124
|
+
refreshButton.innerHTML = `
|
|
125
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="display: block;">
|
|
126
|
+
<path d="M21 12a9 9 0 0 0-9-9 9.75 9.75 0 0 0-6.74 2.74L3 8"></path>
|
|
127
|
+
<path d="M3 3v5h5"></path>
|
|
128
|
+
<path d="M3 12a9 9 0 0 0 9 9 9.75 9.75 0 0 0 6.74-2.74L21 16"></path>
|
|
129
|
+
<path d="M16 16h5v5"></path>
|
|
130
|
+
</svg>
|
|
181
131
|
`;
|
|
182
132
|
refreshButton.onclick = onRefreshClick;
|
|
183
133
|
refreshButton.onclick = onRefreshClick;
|
|
184
134
|
container.appendChild(refreshButton);
|
|
185
135
|
}
|
|
186
|
-
// Toggle button (scan-face) - switches between original and virtual try-on
|
|
187
136
|
if (onToggleClick) {
|
|
188
137
|
const toggleButton = document.createElement("button");
|
|
189
138
|
toggleButton.type = "button";
|
|
190
139
|
toggleButton.className = "ww-toggle-btn";
|
|
191
140
|
toggleButton.setAttribute("aria-label", isShowingVirtualTryOn ? "Show Original Image" : "Show Virtual Try-On");
|
|
192
|
-
toggleButton.style.cssText = `
|
|
193
|
-
display: flex;
|
|
194
|
-
width: 40px;
|
|
195
|
-
height: 40px;
|
|
196
|
-
cursor: pointer;
|
|
197
|
-
align-items: center;
|
|
198
|
-
justify-content: center;
|
|
199
|
-
border-radius: 100px;
|
|
200
|
-
padding: 0;
|
|
201
|
-
border: none;
|
|
202
|
-
background: ${isShowingVirtualTryOn ? "#333333" : "#e5e7eb"};
|
|
203
|
-
color: ${isShowingVirtualTryOn ? "white" : "#333333"};
|
|
204
|
-
transition: all 0.2s ease;
|
|
205
|
-
flex-shrink: 0;
|
|
141
|
+
toggleButton.style.cssText = `
|
|
142
|
+
display: flex;
|
|
143
|
+
width: 40px;
|
|
144
|
+
height: 40px;
|
|
145
|
+
cursor: pointer;
|
|
146
|
+
align-items: center;
|
|
147
|
+
justify-content: center;
|
|
148
|
+
border-radius: 100px;
|
|
149
|
+
padding: 0;
|
|
150
|
+
border: none;
|
|
151
|
+
background: ${isShowingVirtualTryOn ? "#333333" : "#e5e7eb"};
|
|
152
|
+
color: ${isShowingVirtualTryOn ? "white" : "#333333"};
|
|
153
|
+
transition: all 0.2s ease;
|
|
154
|
+
flex-shrink: 0;
|
|
206
155
|
`;
|
|
207
|
-
toggleButton.innerHTML = `
|
|
208
|
-
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="display: block;">
|
|
209
|
-
<path d="M11.017 2.814a1 1 0 0 1 1.966 0l1.051 5.558a2 2 0 0 0 1.594 1.594l5.558 1.051a1 1 0 0 1 0 1.966l-5.558 1.051a2 2 0 0 0-1.594 1.594l-1.051 5.558a1 1 0 0 1-1.966 0l-1.051-5.558a2 2 0 0 0-1.594-1.594l-5.558-1.051a1 1 0 0 1 0-1.966l5.558-1.051a2 2 0 0 0 1.594-1.594z"/>
|
|
210
|
-
<path d="M20 2v4"/>
|
|
211
|
-
<path d="M22 4h-4"/>
|
|
212
|
-
<circle cx="4" cy="20" r="2"/>
|
|
213
|
-
</svg>
|
|
156
|
+
toggleButton.innerHTML = `
|
|
157
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="display: block;">
|
|
158
|
+
<path d="M11.017 2.814a1 1 0 0 1 1.966 0l1.051 5.558a2 2 0 0 0 1.594 1.594l5.558 1.051a1 1 0 0 1 0 1.966l-5.558 1.051a2 2 0 0 0-1.594 1.594l-1.051 5.558a1 1 0 0 1-1.966 0l-1.051-5.558a2 2 0 0 0-1.594-1.594l-5.558-1.051a1 1 0 0 1 0-1.966l5.558-1.051a2 2 0 0 0 1.594-1.594z"/>
|
|
159
|
+
<path d="M20 2v4"/>
|
|
160
|
+
<path d="M22 4h-4"/>
|
|
161
|
+
<circle cx="4" cy="20" r="2"/>
|
|
162
|
+
</svg>
|
|
214
163
|
`;
|
|
215
164
|
toggleButton.onmouseover = () => {
|
|
216
165
|
if (isShowingVirtualTryOn) {
|
|
@@ -248,114 +197,112 @@ function createButton(buttonPosition, onClick, disabled = false) {
|
|
|
248
197
|
return container;
|
|
249
198
|
}
|
|
250
199
|
|
|
251
|
-
/**
|
|
252
|
-
* Creates a loading overlay that can be shown in different containers
|
|
253
|
-
*/
|
|
254
200
|
function createLoadingOverlay(text = "Processing...") {
|
|
255
201
|
const overlay = document.createElement("div");
|
|
256
202
|
overlay.className = "ww-loading-overlay";
|
|
257
|
-
overlay.style.cssText = `
|
|
258
|
-
position: absolute;
|
|
259
|
-
top: 0;
|
|
260
|
-
left: 0;
|
|
261
|
-
width: 100%;
|
|
262
|
-
height: 100%;
|
|
263
|
-
background: linear-gradient(135deg, rgba(0, 0, 0, 0.92) 0%, rgba(0, 0, 0, 0.88) 100%);
|
|
264
|
-
backdrop-filter: blur(8px);
|
|
265
|
-
-webkit-backdrop-filter: blur(8px);
|
|
266
|
-
display: flex;
|
|
267
|
-
flex-direction: column;
|
|
268
|
-
align-items: center;
|
|
269
|
-
justify-content: center;
|
|
270
|
-
z-index: ${Z_INDEX.MODAL + 1};
|
|
271
|
-
border-radius: inherit;
|
|
272
|
-
animation: ww-fade-in 0.3s ease;
|
|
203
|
+
overlay.style.cssText = `
|
|
204
|
+
position: absolute;
|
|
205
|
+
top: 0;
|
|
206
|
+
left: 0;
|
|
207
|
+
width: 100%;
|
|
208
|
+
height: 100%;
|
|
209
|
+
background: linear-gradient(135deg, rgba(0, 0, 0, 0.92) 0%, rgba(0, 0, 0, 0.88) 100%);
|
|
210
|
+
backdrop-filter: blur(8px);
|
|
211
|
+
-webkit-backdrop-filter: blur(8px);
|
|
212
|
+
display: flex;
|
|
213
|
+
flex-direction: column;
|
|
214
|
+
align-items: center;
|
|
215
|
+
justify-content: center;
|
|
216
|
+
z-index: ${Z_INDEX.MODAL + 1};
|
|
217
|
+
border-radius: inherit;
|
|
218
|
+
animation: ww-fade-in 0.3s ease;
|
|
273
219
|
`;
|
|
274
|
-
// Add CSS animation to the document if not already added
|
|
275
220
|
if (!document.getElementById("ww-loading-animation")) {
|
|
276
221
|
const style = document.createElement("style");
|
|
277
222
|
style.id = "ww-loading-animation";
|
|
278
|
-
style.textContent = `
|
|
279
|
-
@keyframes ww-fade-in {
|
|
280
|
-
from { opacity: 0; }
|
|
281
|
-
to { opacity: 1; }
|
|
282
|
-
}
|
|
283
|
-
|
|
284
|
-
@keyframes ww-pulse-logo {
|
|
285
|
-
0%, 100% {
|
|
286
|
-
opacity: 0.8;
|
|
287
|
-
transform: scale(1);
|
|
288
|
-
}
|
|
289
|
-
50% {
|
|
290
|
-
opacity: 1;
|
|
291
|
-
transform: scale(1.05);
|
|
292
|
-
}
|
|
293
|
-
}
|
|
294
|
-
|
|
295
|
-
@keyframes ww-spinner-rotate {
|
|
296
|
-
0% { transform: rotate(0deg); }
|
|
297
|
-
100% { transform: rotate(360deg); }
|
|
298
|
-
}
|
|
299
|
-
|
|
300
|
-
@keyframes ww-dot-bounce {
|
|
301
|
-
0%, 80%, 100% {
|
|
302
|
-
transform: scale(0);
|
|
303
|
-
opacity: 0.5;
|
|
304
|
-
}
|
|
305
|
-
40% {
|
|
306
|
-
transform: scale(1);
|
|
307
|
-
opacity: 1;
|
|
308
|
-
}
|
|
309
|
-
}
|
|
310
|
-
|
|
311
|
-
.ww-loading-logo {
|
|
312
|
-
animation: ww-pulse-logo 2s ease-in-out infinite;
|
|
313
|
-
}
|
|
314
|
-
|
|
315
|
-
.ww-loading-spinner {
|
|
316
|
-
animation: ww-spinner-rotate 1s linear infinite;
|
|
317
|
-
}
|
|
223
|
+
style.textContent = `
|
|
224
|
+
@keyframes ww-fade-in {
|
|
225
|
+
from { opacity: 0; }
|
|
226
|
+
to { opacity: 1; }
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
@keyframes ww-pulse-logo {
|
|
230
|
+
0%, 100% {
|
|
231
|
+
opacity: 0.8;
|
|
232
|
+
transform: scale(1);
|
|
233
|
+
}
|
|
234
|
+
50% {
|
|
235
|
+
opacity: 1;
|
|
236
|
+
transform: scale(1.05);
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
@keyframes ww-spinner-rotate {
|
|
241
|
+
0% { transform: rotate(0deg); }
|
|
242
|
+
100% { transform: rotate(360deg); }
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
@keyframes ww-dot-bounce {
|
|
246
|
+
0%, 80%, 100% {
|
|
247
|
+
transform: scale(0);
|
|
248
|
+
opacity: 0.5;
|
|
249
|
+
}
|
|
250
|
+
40% {
|
|
251
|
+
transform: scale(1);
|
|
252
|
+
opacity: 1;
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
.ww-loading-logo {
|
|
257
|
+
animation: ww-pulse-logo 2s ease-in-out infinite;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
.ww-loading-spinner {
|
|
261
|
+
animation: ww-spinner-rotate 1s linear infinite;
|
|
262
|
+
}
|
|
318
263
|
`;
|
|
319
264
|
document.head.appendChild(style);
|
|
320
265
|
}
|
|
321
|
-
overlay.innerHTML = `
|
|
322
|
-
<div style="display: flex; flex-direction: column; align-items: center; gap: 32px;">
|
|
323
|
-
<div style="position: relative; width: 120px; height: 120px; display: flex; align-items: center; justify-content: center;">
|
|
324
|
-
<!-- Outer spinning circle -->
|
|
325
|
-
<div class="ww-loading-spinner" style="
|
|
326
|
-
position: absolute;
|
|
327
|
-
width: 120px;
|
|
328
|
-
height: 120px;
|
|
329
|
-
border: 3px solid transparent;
|
|
330
|
-
border-top: 3px solid #333333;
|
|
331
|
-
border-right: 3px solid #333333;
|
|
332
|
-
border-radius: 50%;
|
|
333
|
-
"></div>
|
|
334
|
-
|
|
335
|
-
<!-- Logo -->
|
|
336
|
-
<svg class="ww-loading-logo" width="80" height="50" viewBox="0 0 214 135" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
337
|
-
<path d="M102.906 74.8679C102.906 77.9717 101.574 80.7453 98.9104 83.1887C96.6871 85.1918 93.9025 86.6997 90.5566 87.7123C87.695 88.5708 84.6462 89 81.4104 89C73.8821 89 68.0047 87.0189 63.7783 83.0566C59.5519 87.0189 53.6855 89 46.1792 89C42.9434 89 39.9057 88.5708 37.066 87.7123C33.7201 86.6997 30.9245 85.1918 28.6792 83.1887C26.0157 80.7453 24.684 77.9717 24.684 74.8679V41.6509H32.3774V74.8679C32.3774 76.2547 33.489 77.5645 35.7123 78.7972C37.3632 79.7217 39.0692 80.3711 40.8302 80.7453C42.5252 81.1195 44.3082 81.3066 46.1792 81.3066C48.0063 81.3066 49.7673 81.1195 51.4623 80.7453C53.2453 80.3711 54.9623 79.7217 56.6132 78.7972C58.8585 77.5645 59.9811 76.2547 59.9811 74.8679V41.6509H67.6085V74.8679C67.6085 76.2547 68.7311 77.5645 70.9764 78.7972C72.6274 79.7217 74.3443 80.3711 76.1274 80.7453C77.8223 81.1195 79.5833 81.3066 81.4104 81.3066C83.2814 81.3066 85.0755 81.1195 86.7925 80.7453C88.5314 80.3711 90.2264 79.7217 91.8774 78.7972C94.1006 77.5645 95.2123 76.2547 95.2123 74.8679V41.6509H102.906V74.8679ZM189.283 74.8679C189.283 77.9717 187.951 80.7453 185.288 83.1887C183.064 85.1918 180.28 86.6997 176.934 87.7123C174.072 88.5708 171.024 89 167.788 89C160.259 89 154.382 87.0189 150.156 83.0566C145.929 87.0189 140.063 89 132.557 89C129.321 89 126.283 88.5708 123.443 87.7123C120.097 86.6997 117.302 85.1918 115.057 83.1887C112.393 80.7453 111.061 77.9717 111.061 74.8679V41.6509H118.755V74.8679C118.755 76.2547 119.866 77.5645 122.09 78.7972C123.741 79.7217 125.447 80.3711 127.208 80.7453C128.903 81.1195 130.686 81.3066 132.557 81.3066C134.384 81.3066 136.145 81.1195 137.84 80.7453C139.623 80.3711 141.34 79.7217 142.991 78.7972C145.236 77.5645 146.358 76.2547 146.358 74.8679V41.6509H153.986V74.8679C153.986 76.2547 155.108 77.5645 157.354 78.7972C159.005 79.7217 160.722 80.3711 162.505 80.7453C164.2 81.1195 165.961 81.3066 167.788 81.3066C169.659 81.3066 171.453 81.1195 173.17 80.7453C174.909 80.3711 176.604 79.7217 178.255 78.7972C180.478 77.5645 181.59 76.2547 181.59 74.8679V41.6509H189.283V74.8679Z" fill="white"/>
|
|
338
|
-
</svg>
|
|
339
|
-
</div>
|
|
340
|
-
|
|
341
|
-
<div style="font-size: 17px; font-weight: 500; text-align: center; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; color: white; letter-spacing: 0.5px; max-width: 280px;">${text}</div>
|
|
342
|
-
|
|
343
|
-
<div style="display: flex; gap: 10px; margin-top: 4px;">
|
|
344
|
-
<div style="width: 10px; height: 10px; border-radius: 50%; background: #333333; animation: ww-dot-bounce 1.4s ease-in-out infinite;"></div>
|
|
345
|
-
<div style="width: 10px; height: 10px; border-radius: 50%; background: #333333; animation: ww-dot-bounce 1.4s ease-in-out 0.2s infinite;"></div>
|
|
346
|
-
<div style="width: 10px; height: 10px; border-radius: 50%; background: #333333; animation: ww-dot-bounce 1.4s ease-in-out 0.4s infinite;"></div>
|
|
347
|
-
</div>
|
|
348
|
-
</div>
|
|
266
|
+
overlay.innerHTML = `
|
|
267
|
+
<div style="display: flex; flex-direction: column; align-items: center; gap: 32px;">
|
|
268
|
+
<div style="position: relative; width: 120px; height: 120px; display: flex; align-items: center; justify-content: center;">
|
|
269
|
+
<!-- Outer spinning circle -->
|
|
270
|
+
<div class="ww-loading-spinner" style="
|
|
271
|
+
position: absolute;
|
|
272
|
+
width: 120px;
|
|
273
|
+
height: 120px;
|
|
274
|
+
border: 3px solid transparent;
|
|
275
|
+
border-top: 3px solid #333333;
|
|
276
|
+
border-right: 3px solid #333333;
|
|
277
|
+
border-radius: 50%;
|
|
278
|
+
"></div>
|
|
279
|
+
|
|
280
|
+
<!-- Logo -->
|
|
281
|
+
<svg class="ww-loading-logo" width="80" height="50" viewBox="0 0 214 135" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
282
|
+
<path d="M102.906 74.8679C102.906 77.9717 101.574 80.7453 98.9104 83.1887C96.6871 85.1918 93.9025 86.6997 90.5566 87.7123C87.695 88.5708 84.6462 89 81.4104 89C73.8821 89 68.0047 87.0189 63.7783 83.0566C59.5519 87.0189 53.6855 89 46.1792 89C42.9434 89 39.9057 88.5708 37.066 87.7123C33.7201 86.6997 30.9245 85.1918 28.6792 83.1887C26.0157 80.7453 24.684 77.9717 24.684 74.8679V41.6509H32.3774V74.8679C32.3774 76.2547 33.489 77.5645 35.7123 78.7972C37.3632 79.7217 39.0692 80.3711 40.8302 80.7453C42.5252 81.1195 44.3082 81.3066 46.1792 81.3066C48.0063 81.3066 49.7673 81.1195 51.4623 80.7453C53.2453 80.3711 54.9623 79.7217 56.6132 78.7972C58.8585 77.5645 59.9811 76.2547 59.9811 74.8679V41.6509H67.6085V74.8679C67.6085 76.2547 68.7311 77.5645 70.9764 78.7972C72.6274 79.7217 74.3443 80.3711 76.1274 80.7453C77.8223 81.1195 79.5833 81.3066 81.4104 81.3066C83.2814 81.3066 85.0755 81.1195 86.7925 80.7453C88.5314 80.3711 90.2264 79.7217 91.8774 78.7972C94.1006 77.5645 95.2123 76.2547 95.2123 74.8679V41.6509H102.906V74.8679ZM189.283 74.8679C189.283 77.9717 187.951 80.7453 185.288 83.1887C183.064 85.1918 180.28 86.6997 176.934 87.7123C174.072 88.5708 171.024 89 167.788 89C160.259 89 154.382 87.0189 150.156 83.0566C145.929 87.0189 140.063 89 132.557 89C129.321 89 126.283 88.5708 123.443 87.7123C120.097 86.6997 117.302 85.1918 115.057 83.1887C112.393 80.7453 111.061 77.9717 111.061 74.8679V41.6509H118.755V74.8679C118.755 76.2547 119.866 77.5645 122.09 78.7972C123.741 79.7217 125.447 80.3711 127.208 80.7453C128.903 81.1195 130.686 81.3066 132.557 81.3066C134.384 81.3066 136.145 81.1195 137.84 80.7453C139.623 80.3711 141.34 79.7217 142.991 78.7972C145.236 77.5645 146.358 76.2547 146.358 74.8679V41.6509H153.986V74.8679C153.986 76.2547 155.108 77.5645 157.354 78.7972C159.005 79.7217 160.722 80.3711 162.505 80.7453C164.2 81.1195 165.961 81.3066 167.788 81.3066C169.659 81.3066 171.453 81.1195 173.17 80.7453C174.909 80.3711 176.604 79.7217 178.255 78.7972C180.478 77.5645 181.59 76.2547 181.59 74.8679V41.6509H189.283V74.8679Z" fill="white"/>
|
|
283
|
+
</svg>
|
|
284
|
+
</div>
|
|
285
|
+
|
|
286
|
+
<div style="font-size: 17px; font-weight: 500; text-align: center; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; color: white; letter-spacing: 0.5px; max-width: 280px;">${text}</div>
|
|
287
|
+
|
|
288
|
+
<div style="display: flex; gap: 10px; margin-top: 4px;">
|
|
289
|
+
<div style="width: 10px; height: 10px; border-radius: 50%; background: #333333; animation: ww-dot-bounce 1.4s ease-in-out infinite;"></div>
|
|
290
|
+
<div style="width: 10px; height: 10px; border-radius: 50%; background: #333333; animation: ww-dot-bounce 1.4s ease-in-out 0.2s infinite;"></div>
|
|
291
|
+
<div style="width: 10px; height: 10px; border-radius: 50%; background: #333333; animation: ww-dot-bounce 1.4s ease-in-out 0.4s infinite;"></div>
|
|
292
|
+
</div>
|
|
293
|
+
</div>
|
|
349
294
|
`;
|
|
350
295
|
return overlay;
|
|
351
296
|
}
|
|
352
|
-
/**
|
|
353
|
-
* Shows a loading overlay in the product gallery container
|
|
354
|
-
*/
|
|
355
297
|
function showProductLoading(container, text = "Generating virtual try-on...") {
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
298
|
+
const existingOverlay = container.querySelector(".ww-loading-overlay");
|
|
299
|
+
if (existingOverlay) {
|
|
300
|
+
const textElement = existingOverlay.querySelector('div[style*="font-size: 17px"]');
|
|
301
|
+
if (textElement) {
|
|
302
|
+
textElement.textContent = text;
|
|
303
|
+
}
|
|
304
|
+
return;
|
|
305
|
+
}
|
|
359
306
|
const computedStyle = window.getComputedStyle(container);
|
|
360
307
|
if (computedStyle.position === "static") {
|
|
361
308
|
container.style.position = "relative";
|
|
@@ -363,9 +310,6 @@ function showProductLoading(container, text = "Generating virtual try-on...") {
|
|
|
363
310
|
const loadingOverlay = createLoadingOverlay(text);
|
|
364
311
|
container.appendChild(loadingOverlay);
|
|
365
312
|
}
|
|
366
|
-
/**
|
|
367
|
-
* Removes loading overlay from product container
|
|
368
|
-
*/
|
|
369
313
|
function removeProductLoading(container) {
|
|
370
314
|
const existingOverlay = container.querySelector(".ww-loading-overlay");
|
|
371
315
|
if (existingOverlay) {
|
|
@@ -373,99 +317,74 @@ function removeProductLoading(container) {
|
|
|
373
317
|
}
|
|
374
318
|
}
|
|
375
319
|
|
|
376
|
-
function
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
;
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
padding: 0;
|
|
427
|
-
line-height: 1;
|
|
428
|
-
opacity: 0.7;
|
|
429
|
-
transition: opacity 0.2s ease;
|
|
430
|
-
width: 24px;
|
|
431
|
-
height: 24px;
|
|
432
|
-
display: flex;
|
|
433
|
-
align-items: center;
|
|
434
|
-
justify-content: center;
|
|
435
|
-
"
|
|
436
|
-
onmouseover="this.style.opacity='1'"
|
|
437
|
-
onmouseout="this.style.opacity='0.7'">
|
|
438
|
-
×
|
|
439
|
-
</button>
|
|
440
|
-
`;
|
|
441
|
-
// Close on button click
|
|
442
|
-
(_a = alertDiv.querySelector("button")) === null || _a === void 0 ? void 0 : _a.addEventListener("click", () => {
|
|
443
|
-
fadeOutAndRemove(alertDiv);
|
|
444
|
-
});
|
|
445
|
-
container.appendChild(alertDiv);
|
|
446
|
-
// Animate in
|
|
447
|
-
requestAnimationFrame(() => {
|
|
448
|
-
alertDiv.style.opacity = "1";
|
|
449
|
-
alertDiv.style.transform = "translateX(-50%) translateY(0)";
|
|
450
|
-
});
|
|
451
|
-
// Auto-dismiss after 8 seconds
|
|
452
|
-
setTimeout(() => {
|
|
453
|
-
if (alertDiv.parentElement) {
|
|
454
|
-
fadeOutAndRemove(alertDiv);
|
|
455
|
-
}
|
|
456
|
-
}, 8000);
|
|
320
|
+
function createStatusBadge(message) {
|
|
321
|
+
const badge = document.createElement("div");
|
|
322
|
+
badge.className = "ww-status-badge";
|
|
323
|
+
badge.style.cssText = `
|
|
324
|
+
position: absolute;
|
|
325
|
+
bottom: 16px;
|
|
326
|
+
left: 50%;
|
|
327
|
+
transform: translateX(-50%);
|
|
328
|
+
background: linear-gradient(135deg, rgba(0, 0, 0, 0.92) 0%, rgba(0, 0, 0, 0.88) 100%);
|
|
329
|
+
backdrop-filter: blur(12px);
|
|
330
|
+
-webkit-backdrop-filter: blur(12px);
|
|
331
|
+
color: white;
|
|
332
|
+
padding: 12px 24px;
|
|
333
|
+
border-radius: 24px;
|
|
334
|
+
font-size: 14px;
|
|
335
|
+
font-weight: 500;
|
|
336
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
|
337
|
+
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.3), 0 2px 8px rgba(0, 0, 0, 0.2);
|
|
338
|
+
z-index: 1000;
|
|
339
|
+
display: flex;
|
|
340
|
+
align-items: center;
|
|
341
|
+
gap: 10px;
|
|
342
|
+
animation: ww-badge-fade-in 0.3s ease;
|
|
343
|
+
letter-spacing: 0.3px;
|
|
344
|
+
`;
|
|
345
|
+
if (!document.getElementById("ww-status-badge-animation")) {
|
|
346
|
+
const style = document.createElement("style");
|
|
347
|
+
style.id = "ww-status-badge-animation";
|
|
348
|
+
style.textContent = `
|
|
349
|
+
@keyframes ww-badge-fade-in {
|
|
350
|
+
from { opacity: 0; transform: translateX(-50%) translateY(-10px); }
|
|
351
|
+
to { opacity: 1; transform: translateX(-50%) translateY(0); }
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
@keyframes ww-badge-pulse {
|
|
355
|
+
0%, 100% { opacity: 0.6; }
|
|
356
|
+
50% { opacity: 1; }
|
|
357
|
+
}
|
|
358
|
+
`;
|
|
359
|
+
document.head.appendChild(style);
|
|
360
|
+
}
|
|
361
|
+
badge.innerHTML = `
|
|
362
|
+
<div style="display: flex; gap: 4px;">
|
|
363
|
+
<div style="width: 6px; height: 6px; border-radius: 50%; background: white; animation: ww-badge-pulse 1.4s ease-in-out infinite;"></div>
|
|
364
|
+
<div style="width: 6px; height: 6px; border-radius: 50%; background: white; animation: ww-badge-pulse 1.4s ease-in-out 0.2s infinite;"></div>
|
|
365
|
+
<div style="width: 6px; height: 6px; border-radius: 50%; background: white; animation: ww-badge-pulse 1.4s ease-in-out 0.4s infinite;"></div>
|
|
366
|
+
</div>
|
|
367
|
+
<span>${message}</span>
|
|
368
|
+
`;
|
|
369
|
+
return badge;
|
|
457
370
|
}
|
|
458
|
-
function
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
371
|
+
function showStatusBadge(container, message) {
|
|
372
|
+
const existingBadge = container.querySelector(".ww-status-badge");
|
|
373
|
+
if (existingBadge) {
|
|
374
|
+
const textElement = existingBadge.querySelector("span");
|
|
375
|
+
if (textElement) {
|
|
376
|
+
textElement.textContent = message;
|
|
377
|
+
}
|
|
378
|
+
return;
|
|
379
|
+
}
|
|
380
|
+
const badge = createStatusBadge(message);
|
|
381
|
+
container.appendChild(badge);
|
|
464
382
|
}
|
|
465
|
-
function
|
|
466
|
-
const
|
|
467
|
-
if (
|
|
468
|
-
|
|
383
|
+
function removeStatusBadge(container) {
|
|
384
|
+
const badge = container.querySelector(".ww-status-badge");
|
|
385
|
+
if (badge) {
|
|
386
|
+
badge.remove();
|
|
387
|
+
}
|
|
469
388
|
}
|
|
470
389
|
|
|
471
390
|
class VirtualTryOnWidget {
|
|
@@ -477,7 +396,6 @@ class VirtualTryOnWidget {
|
|
|
477
396
|
this.lastModelImage = null;
|
|
478
397
|
this.cameraButton = null;
|
|
479
398
|
this.iframeMessageListener = null;
|
|
480
|
-
this.previewBadge = null;
|
|
481
399
|
this.config = {
|
|
482
400
|
baseUrl: config.baseUrl,
|
|
483
401
|
productPageSelector: config.productPageSelector,
|
|
@@ -486,51 +404,36 @@ class VirtualTryOnWidget {
|
|
|
486
404
|
buttonPosition: config.buttonPosition,
|
|
487
405
|
};
|
|
488
406
|
}
|
|
489
|
-
/**
|
|
490
|
-
* Initializes the virtual try-on widget on the current page
|
|
491
|
-
* @returns Promise that resolves when initialization is complete
|
|
492
|
-
*/
|
|
493
407
|
async init() {
|
|
494
408
|
try {
|
|
495
|
-
// Check if we're on a product page
|
|
496
409
|
if (!window.location.pathname.includes(this.config.productPageSelector)) {
|
|
497
410
|
return;
|
|
498
411
|
}
|
|
499
|
-
// Find the gallery container
|
|
500
412
|
const container = document.querySelector(this.config.gallerySelector);
|
|
501
413
|
if (!container || !(container instanceof HTMLElement)) {
|
|
502
414
|
console.warn("[WeWear VTO] Gallery container not found:", this.config.gallerySelector);
|
|
503
415
|
return;
|
|
504
416
|
}
|
|
505
|
-
// Ensure container has relative positioning for button placement
|
|
506
417
|
if (getComputedStyle(container).position === "static") {
|
|
507
418
|
container.style.position = "relative";
|
|
508
419
|
}
|
|
509
|
-
// Create and add the virtual try-on button
|
|
510
420
|
const button = createButton(this.config.buttonPosition, async () => {
|
|
511
421
|
if (this.cameraButton && !this.cameraButton.disabled) {
|
|
512
422
|
await this.handleTryOnClick();
|
|
513
423
|
}
|
|
514
424
|
});
|
|
515
|
-
// Store reference to camera button for dynamic enable/disable
|
|
516
425
|
this.cameraButton = button.querySelector(".ww-camera-btn");
|
|
517
426
|
container.appendChild(button);
|
|
518
427
|
console.log("[WeWear VTO] Widget initialized successfully");
|
|
519
|
-
// Listen for messages from the photo upload page
|
|
520
428
|
this.setupIframeListener();
|
|
521
429
|
}
|
|
522
430
|
catch (error) {
|
|
523
431
|
console.error("[WeWear VTO] Initialization failed:", error);
|
|
524
432
|
}
|
|
525
433
|
}
|
|
526
|
-
/**
|
|
527
|
-
* Handles virtual try-on button click
|
|
528
|
-
* @private
|
|
529
|
-
*/
|
|
530
434
|
async handleTryOnClick() {
|
|
531
435
|
console.log("[WeWear VTO] Button clicked, starting try-on process...");
|
|
532
436
|
try {
|
|
533
|
-
// Store original images on first click if not already stored
|
|
534
437
|
if (this.originalProductImages.length === 0) {
|
|
535
438
|
this.originalProductImages = this.getAllProductImages();
|
|
536
439
|
console.log("[WeWear VTO] Stored original product images:", this.originalProductImages);
|
|
@@ -542,13 +445,10 @@ class VirtualTryOnWidget {
|
|
|
542
445
|
console.warn("[WeWear VTO] Product images not found:", this.config.productImageSelector);
|
|
543
446
|
return;
|
|
544
447
|
}
|
|
545
|
-
// Store the first image as the original
|
|
546
448
|
if (!this.originalProductImageUrl) {
|
|
547
449
|
this.originalProductImageUrl = this.originalProductImages[0];
|
|
548
450
|
}
|
|
549
|
-
// Open the photo upload page in a modal
|
|
550
451
|
const photoUploadUrl = new URL(`${this.config.baseUrl}`);
|
|
551
|
-
// Pass all product images as a comma-separated string
|
|
552
452
|
photoUploadUrl.searchParams.append("ww_product_images", this.originalProductImages.join(","));
|
|
553
453
|
this.showPhotoUploadModal(photoUploadUrl.toString());
|
|
554
454
|
}
|
|
@@ -556,11 +456,6 @@ class VirtualTryOnWidget {
|
|
|
556
456
|
console.error("[WeWear VTO] Try-on request failed:", error);
|
|
557
457
|
}
|
|
558
458
|
}
|
|
559
|
-
/**
|
|
560
|
-
* Gets all product images from the gallery
|
|
561
|
-
* @private
|
|
562
|
-
* @returns Array of product image URLs
|
|
563
|
-
*/
|
|
564
459
|
getAllProductImages() {
|
|
565
460
|
const productImageElements = document.querySelectorAll(this.config.productImageSelector);
|
|
566
461
|
const images = [];
|
|
@@ -569,7 +464,6 @@ class VirtualTryOnWidget {
|
|
|
569
464
|
img.getAttribute("data-src") ||
|
|
570
465
|
img.getAttribute("data-large_image") ||
|
|
571
466
|
"";
|
|
572
|
-
// Only include http/https URLs, exclude blob URLs and data URLs
|
|
573
467
|
if (imageUrl &&
|
|
574
468
|
(imageUrl.startsWith("http://") || imageUrl.startsWith("https://")) &&
|
|
575
469
|
!images.includes(imageUrl)) {
|
|
@@ -578,92 +472,86 @@ class VirtualTryOnWidget {
|
|
|
578
472
|
});
|
|
579
473
|
return images;
|
|
580
474
|
}
|
|
581
|
-
/**
|
|
582
|
-
* Shows a modal with an iframe for the photo upload page
|
|
583
|
-
* @private
|
|
584
|
-
*/
|
|
585
475
|
showPhotoUploadModal(url) {
|
|
586
|
-
// Remove existing modals
|
|
587
476
|
removeElements(`.${CSS_CLASSES.MODAL}`);
|
|
588
477
|
const modal = document.createElement("div");
|
|
589
478
|
modal.className = CSS_CLASSES.MODAL;
|
|
590
|
-
modal.style.cssText = `
|
|
591
|
-
position: fixed;
|
|
592
|
-
top: 0;
|
|
593
|
-
left: 0;
|
|
594
|
-
right: 0;
|
|
595
|
-
bottom: 0;
|
|
596
|
-
width: 100%;
|
|
597
|
-
height: 100%;
|
|
598
|
-
background-color: rgba(0, 0, 0, 0.8);
|
|
599
|
-
display: flex;
|
|
600
|
-
justify-content: center;
|
|
601
|
-
align-items: center;
|
|
602
|
-
z-index: 999999999;
|
|
603
|
-
animation: ww-modal-fade-in 0.3s ease;
|
|
479
|
+
modal.style.cssText = `
|
|
480
|
+
position: fixed;
|
|
481
|
+
top: 0;
|
|
482
|
+
left: 0;
|
|
483
|
+
right: 0;
|
|
484
|
+
bottom: 0;
|
|
485
|
+
width: 100%;
|
|
486
|
+
height: 100%;
|
|
487
|
+
background-color: rgba(0, 0, 0, 0.8);
|
|
488
|
+
display: flex;
|
|
489
|
+
justify-content: center;
|
|
490
|
+
align-items: center;
|
|
491
|
+
z-index: 999999999;
|
|
492
|
+
animation: ww-modal-fade-in 0.3s ease;
|
|
604
493
|
`;
|
|
605
|
-
// Add fade-in animation
|
|
606
494
|
if (!document.getElementById("ww-modal-animation")) {
|
|
607
495
|
const style = document.createElement("style");
|
|
608
496
|
style.id = "ww-modal-animation";
|
|
609
|
-
style.textContent = `
|
|
610
|
-
@keyframes ww-modal-fade-in {
|
|
611
|
-
from { opacity: 0; }
|
|
612
|
-
to { opacity: 1; }
|
|
613
|
-
}
|
|
614
|
-
@keyframes ww-modal-scale-in {
|
|
615
|
-
from { transform: scale(0.95); opacity: 0; }
|
|
616
|
-
to { transform: scale(1); opacity: 1; }
|
|
617
|
-
}
|
|
497
|
+
style.textContent = `
|
|
498
|
+
@keyframes ww-modal-fade-in {
|
|
499
|
+
from { opacity: 0; }
|
|
500
|
+
to { opacity: 1; }
|
|
501
|
+
}
|
|
502
|
+
@keyframes ww-modal-scale-in {
|
|
503
|
+
from { transform: scale(0.95); opacity: 0; }
|
|
504
|
+
to { transform: scale(1); opacity: 1; }
|
|
505
|
+
}
|
|
618
506
|
`;
|
|
619
507
|
document.head.appendChild(style);
|
|
620
508
|
}
|
|
621
509
|
const iframeContainer = document.createElement("div");
|
|
622
|
-
iframeContainer.style.cssText = `
|
|
623
|
-
position: relative;
|
|
624
|
-
width: 90%;
|
|
625
|
-
height: 90%;
|
|
626
|
-
max-width: 480px;
|
|
627
|
-
max-height: 720px;
|
|
628
|
-
border-radius: 20px;
|
|
629
|
-
overflow: hidden;
|
|
630
|
-
box-shadow: 0 24px 48px rgba(0, 0, 0, 0.3), 0 12px 24px rgba(0, 0, 0, 0.2);
|
|
631
|
-
animation: ww-modal-scale-in 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
|
632
|
-
background: white;
|
|
510
|
+
iframeContainer.style.cssText = `
|
|
511
|
+
position: relative;
|
|
512
|
+
width: 90%;
|
|
513
|
+
height: 90%;
|
|
514
|
+
max-width: 480px;
|
|
515
|
+
max-height: 720px;
|
|
516
|
+
border-radius: 20px;
|
|
517
|
+
overflow: hidden;
|
|
518
|
+
box-shadow: 0 24px 48px rgba(0, 0, 0, 0.3), 0 12px 24px rgba(0, 0, 0, 0.2);
|
|
519
|
+
animation: ww-modal-scale-in 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
|
520
|
+
background: white;
|
|
633
521
|
`;
|
|
634
522
|
const iframe = document.createElement("iframe");
|
|
635
523
|
iframe.src = url;
|
|
636
|
-
iframe.style.cssText = `
|
|
637
|
-
width: 100%;
|
|
638
|
-
height: 100%;
|
|
639
|
-
border: none;
|
|
524
|
+
iframe.style.cssText = `
|
|
525
|
+
width: 100%;
|
|
526
|
+
height: 100%;
|
|
527
|
+
border: none;
|
|
640
528
|
`;
|
|
641
529
|
const closeButton = document.createElement("button");
|
|
642
|
-
closeButton.innerHTML = `
|
|
643
|
-
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
644
|
-
<line x1="18" y1="6" x2="6" y2="18"></line>
|
|
645
|
-
<line x1="6" y1="6" x2="18" y2="18"></line>
|
|
646
|
-
</svg>
|
|
530
|
+
closeButton.innerHTML = `
|
|
531
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
532
|
+
<line x1="18" y1="6" x2="6" y2="18"></line>
|
|
533
|
+
<line x1="6" y1="6" x2="18" y2="18"></line>
|
|
534
|
+
</svg>
|
|
647
535
|
`;
|
|
648
536
|
closeButton.setAttribute("aria-label", "Close modal");
|
|
649
|
-
closeButton.style.cssText = `
|
|
650
|
-
position: absolute;
|
|
651
|
-
top: -48px;
|
|
652
|
-
right: 0;
|
|
653
|
-
background: rgba(255, 255, 255, 0.95);
|
|
654
|
-
backdrop-filter: blur(8px);
|
|
655
|
-
-webkit-backdrop-filter: blur(8px);
|
|
656
|
-
border: none;
|
|
657
|
-
border-radius: 50%;
|
|
658
|
-
width: 40px;
|
|
659
|
-
height: 40px;
|
|
660
|
-
cursor: pointer;
|
|
661
|
-
display: flex;
|
|
662
|
-
align-items: center;
|
|
663
|
-
justify-content: center;
|
|
664
|
-
color: #1f2937;
|
|
665
|
-
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
|
666
|
-
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2);
|
|
537
|
+
closeButton.style.cssText = `
|
|
538
|
+
position: absolute;
|
|
539
|
+
top: -48px;
|
|
540
|
+
right: 0;
|
|
541
|
+
background: rgba(255, 255, 255, 0.95);
|
|
542
|
+
backdrop-filter: blur(8px);
|
|
543
|
+
-webkit-backdrop-filter: blur(8px);
|
|
544
|
+
border: none;
|
|
545
|
+
border-radius: 50%;
|
|
546
|
+
width: 40px;
|
|
547
|
+
height: 40px;
|
|
548
|
+
cursor: pointer;
|
|
549
|
+
display: flex;
|
|
550
|
+
align-items: center;
|
|
551
|
+
justify-content: center;
|
|
552
|
+
color: #1f2937;
|
|
553
|
+
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
|
554
|
+
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2);
|
|
667
555
|
`;
|
|
668
556
|
closeButton.onmouseover = () => {
|
|
669
557
|
closeButton.style.transform = "scale(1.1) rotate(90deg)";
|
|
@@ -685,19 +573,13 @@ class VirtualTryOnWidget {
|
|
|
685
573
|
iframeContainer.appendChild(closeButton);
|
|
686
574
|
modal.appendChild(iframeContainer);
|
|
687
575
|
document.body.appendChild(modal);
|
|
688
|
-
// Close on backdrop click
|
|
689
576
|
modal.onclick = (e) => {
|
|
690
577
|
if (e.target === modal) {
|
|
691
578
|
closeButton.click();
|
|
692
579
|
}
|
|
693
580
|
};
|
|
694
581
|
}
|
|
695
|
-
/**
|
|
696
|
-
* Sets up listener for messages from the iframe
|
|
697
|
-
* @private
|
|
698
|
-
*/
|
|
699
582
|
setupIframeListener() {
|
|
700
|
-
// Remove existing listener if any
|
|
701
583
|
if (this.iframeMessageListener) {
|
|
702
584
|
window.removeEventListener("message", this.iframeMessageListener);
|
|
703
585
|
}
|
|
@@ -727,7 +609,6 @@ class VirtualTryOnWidget {
|
|
|
727
609
|
console.warn("[WeWear VTO] Missing required data to start virtual try-on.");
|
|
728
610
|
return;
|
|
729
611
|
}
|
|
730
|
-
// Use stored original images, or get them if not stored yet
|
|
731
612
|
if (this.originalProductImages.length === 0) {
|
|
732
613
|
this.originalProductImages = this.getAllProductImages();
|
|
733
614
|
}
|
|
@@ -735,37 +616,56 @@ class VirtualTryOnWidget {
|
|
|
735
616
|
console.warn("[WeWear VTO] No product images found.");
|
|
736
617
|
return;
|
|
737
618
|
}
|
|
738
|
-
// Hide the modal and show loading indicator
|
|
739
619
|
const modal = document.querySelector(`.${CSS_CLASSES.MODAL}`);
|
|
740
620
|
if (modal && modal instanceof HTMLElement) {
|
|
741
621
|
modal.style.display = "none";
|
|
742
622
|
}
|
|
743
623
|
const container = document.querySelector(this.config.gallerySelector);
|
|
744
624
|
if (container instanceof HTMLElement) {
|
|
745
|
-
showProductLoading(container, "
|
|
625
|
+
showProductLoading(container, "Preparing your personalized look");
|
|
746
626
|
}
|
|
627
|
+
let hasPreview = false;
|
|
747
628
|
try {
|
|
748
629
|
const submitResponse = await this.callVtoApi(this.lastModelImage, this.originalProductImages);
|
|
749
630
|
let status = await this.fetchJobStatus(submitResponse.job_id);
|
|
750
631
|
let lastPreviewCount = 0;
|
|
751
632
|
while (status.status !== "COMPLETED" && status.status !== "FAILED") {
|
|
752
|
-
|
|
633
|
+
if (!hasPreview && container instanceof HTMLElement) {
|
|
634
|
+
let statusMessage = "Preparing your personalized look";
|
|
635
|
+
if (status.current_iteration === 1) {
|
|
636
|
+
statusMessage = "Refining your look";
|
|
637
|
+
}
|
|
638
|
+
else if (status.current_iteration && status.current_iteration > 1) {
|
|
639
|
+
statusMessage = "Finalizing your look";
|
|
640
|
+
}
|
|
641
|
+
showProductLoading(container, statusMessage);
|
|
642
|
+
}
|
|
643
|
+
else if (hasPreview && container instanceof HTMLElement) {
|
|
644
|
+
let statusMessage = "Refining your look";
|
|
645
|
+
if (status.current_iteration && status.current_iteration > 1) {
|
|
646
|
+
statusMessage = "Finalizing your look";
|
|
647
|
+
}
|
|
648
|
+
showStatusBadge(container, statusMessage);
|
|
649
|
+
this.hideButtonContainer(container);
|
|
650
|
+
}
|
|
753
651
|
if (status.preview_count > lastPreviewCount) {
|
|
754
652
|
try {
|
|
755
653
|
const previewUrl = await this.fetchJobImage(submitResponse.job_id);
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
this.previewBadge = createPreviewBadge();
|
|
762
|
-
container.appendChild(this.previewBadge);
|
|
763
|
-
}
|
|
764
|
-
else if (!container.contains(this.previewBadge)) {
|
|
765
|
-
// Re-add badge if it was removed
|
|
766
|
-
container.appendChild(this.previewBadge);
|
|
654
|
+
if (!hasPreview && container instanceof HTMLElement) {
|
|
655
|
+
this.replaceProductImage(previewUrl);
|
|
656
|
+
let statusMessage = "Refining your look";
|
|
657
|
+
if (status.current_iteration && status.current_iteration > 1) {
|
|
658
|
+
statusMessage = "Finalizing your look";
|
|
767
659
|
}
|
|
660
|
+
showStatusBadge(container, statusMessage);
|
|
661
|
+
this.hideButtonContainer(container);
|
|
662
|
+
removeProductLoading(container);
|
|
663
|
+
hasPreview = true;
|
|
664
|
+
}
|
|
665
|
+
else if (hasPreview) {
|
|
666
|
+
this.replaceProductImage(previewUrl);
|
|
768
667
|
}
|
|
668
|
+
lastPreviewCount = status.preview_count;
|
|
769
669
|
}
|
|
770
670
|
catch (e) {
|
|
771
671
|
if (!(e instanceof Error && e.message === "202_PROCESSING"))
|
|
@@ -780,27 +680,41 @@ class VirtualTryOnWidget {
|
|
|
780
680
|
if (finalImage) {
|
|
781
681
|
this.replaceProductImage(finalImage);
|
|
782
682
|
}
|
|
783
|
-
if (
|
|
784
|
-
|
|
785
|
-
this.
|
|
683
|
+
if (container instanceof HTMLElement) {
|
|
684
|
+
removeStatusBadge(container);
|
|
685
|
+
this.showButtonContainer(container);
|
|
786
686
|
}
|
|
787
687
|
}
|
|
788
688
|
if (status.status === "FAILED") {
|
|
789
689
|
console.error("[WeWear VTO] VTO process failed:", status.message);
|
|
790
690
|
if (container instanceof HTMLElement) {
|
|
791
|
-
|
|
691
|
+
if (!hasPreview) {
|
|
692
|
+
showProductLoading(container, "We are experiencing some technical issues and apologise for any inconvenience caused. Please come back later again");
|
|
693
|
+
}
|
|
694
|
+
else {
|
|
695
|
+
showStatusBadge(container, "We are experiencing some technical issues and apologise for any inconvenience caused. Please come back later again");
|
|
696
|
+
}
|
|
792
697
|
}
|
|
698
|
+
await new Promise((r) => setTimeout(r, 5000));
|
|
793
699
|
}
|
|
794
700
|
}
|
|
795
701
|
catch (error) {
|
|
796
702
|
console.error("[WeWear VTO] Error during virtual try-on process:", error);
|
|
797
703
|
if (container instanceof HTMLElement) {
|
|
798
|
-
|
|
704
|
+
if (!hasPreview) {
|
|
705
|
+
showProductLoading(container, "We are experiencing technical issues and apologise for any inconvenience caused. Please come back later again");
|
|
706
|
+
}
|
|
707
|
+
else {
|
|
708
|
+
showStatusBadge(container, "We are experiencing technical issues. Please try again later.");
|
|
709
|
+
}
|
|
799
710
|
}
|
|
711
|
+
await new Promise((r) => setTimeout(r, 5000));
|
|
800
712
|
}
|
|
801
713
|
finally {
|
|
802
714
|
if (container instanceof HTMLElement) {
|
|
803
715
|
removeProductLoading(container);
|
|
716
|
+
removeStatusBadge(container);
|
|
717
|
+
this.showButtonContainer(container);
|
|
804
718
|
}
|
|
805
719
|
removeElements(`.${CSS_CLASSES.MODAL}`);
|
|
806
720
|
}
|
|
@@ -808,7 +722,6 @@ class VirtualTryOnWidget {
|
|
|
808
722
|
async callVtoApi(modelImage, productImages) {
|
|
809
723
|
const formData = new FormData();
|
|
810
724
|
formData.append("model_image", modelImage, "model_image.png");
|
|
811
|
-
// Send all product images as a JSON array
|
|
812
725
|
formData.append("product_image_urls", JSON.stringify(productImages));
|
|
813
726
|
const res = await fetch(`${this.config.baseUrl}/api/vto`, {
|
|
814
727
|
method: "POST",
|
|
@@ -840,38 +753,27 @@ class VirtualTryOnWidget {
|
|
|
840
753
|
async refreshVirtualTryOn() {
|
|
841
754
|
await this.startVirtualTryOn(true);
|
|
842
755
|
}
|
|
843
|
-
/**
|
|
844
|
-
* Replaces the product image in the gallery with the virtual try-on result
|
|
845
|
-
* @private
|
|
846
|
-
*/
|
|
847
756
|
replaceProductImage(imageUrl) {
|
|
848
757
|
try {
|
|
849
|
-
// Store the virtual try-on image URL
|
|
850
758
|
this.virtualTryOnImageUrl = imageUrl;
|
|
851
|
-
// Find the gallery container
|
|
852
759
|
const container = document.querySelector(this.config.gallerySelector);
|
|
853
760
|
if (!container || !(container instanceof HTMLElement)) {
|
|
854
761
|
console.warn("[WeWear VTO] Gallery container not found for image replacement:", this.config.gallerySelector);
|
|
855
762
|
return;
|
|
856
763
|
}
|
|
857
|
-
// Store the original content if not already stored
|
|
858
764
|
if (!container.hasAttribute("data-ww-original-content")) {
|
|
859
765
|
container.setAttribute("data-ww-original-content", container.innerHTML);
|
|
860
766
|
}
|
|
861
|
-
// Capture and lock container dimensions before replacement to prevent layout shift
|
|
862
767
|
if (!container.hasAttribute("data-ww-container-locked")) {
|
|
863
768
|
const containerRect = container.getBoundingClientRect();
|
|
864
|
-
// Store original dimensions
|
|
865
769
|
container.setAttribute("data-ww-container-width", containerRect.width.toString());
|
|
866
770
|
container.setAttribute("data-ww-container-height", containerRect.height.toString());
|
|
867
|
-
// Lock the container dimensions
|
|
868
771
|
container.style.width = `${containerRect.width}px`;
|
|
869
772
|
container.style.height = `${containerRect.height}px`;
|
|
870
773
|
container.style.minWidth = `${containerRect.width}px`;
|
|
871
774
|
container.style.minHeight = `${containerRect.height}px`;
|
|
872
775
|
container.setAttribute("data-ww-container-locked", "true");
|
|
873
776
|
}
|
|
874
|
-
// Capture original image dimensions before replacement to prevent layout shift
|
|
875
777
|
const originalImg = container.querySelector("img");
|
|
876
778
|
if (originalImg &&
|
|
877
779
|
!container.hasAttribute("data-ww-original-dimensions")) {
|
|
@@ -883,9 +785,7 @@ class VirtualTryOnWidget {
|
|
|
883
785
|
container.setAttribute("data-ww-original-rect-height", rect.height.toString());
|
|
884
786
|
container.setAttribute("data-ww-original-dimensions", "true");
|
|
885
787
|
}
|
|
886
|
-
// Show the virtual try-on image initially
|
|
887
788
|
this.showVirtualTryOnImage(container);
|
|
888
|
-
// Replace the button container with the new multi-button version
|
|
889
789
|
this.updateButtonContainer(container);
|
|
890
790
|
console.log("[WeWear VTO] Product image replaced with virtual try-on result");
|
|
891
791
|
}
|
|
@@ -893,17 +793,23 @@ class VirtualTryOnWidget {
|
|
|
893
793
|
console.error("[WeWear VTO] Error replacing product image:", error);
|
|
894
794
|
}
|
|
895
795
|
}
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
796
|
+
hideButtonContainer(container) {
|
|
797
|
+
const buttonContainer = container.querySelector(`.${CSS_CLASSES.BUTTON_CONTAINER}`);
|
|
798
|
+
if (buttonContainer) {
|
|
799
|
+
buttonContainer.style.display = "none";
|
|
800
|
+
}
|
|
801
|
+
}
|
|
802
|
+
showButtonContainer(container) {
|
|
803
|
+
const buttonContainer = container.querySelector(`.${CSS_CLASSES.BUTTON_CONTAINER}`);
|
|
804
|
+
if (buttonContainer) {
|
|
805
|
+
buttonContainer.style.display = "flex";
|
|
806
|
+
}
|
|
807
|
+
}
|
|
900
808
|
updateButtonContainer(container) {
|
|
901
|
-
// Remove existing button containers
|
|
902
809
|
const existingButtons = container.querySelectorAll(`.${CSS_CLASSES.BUTTON_CONTAINER}`);
|
|
903
810
|
existingButtons.forEach((btn) => {
|
|
904
811
|
btn.remove();
|
|
905
812
|
});
|
|
906
|
-
// Create new button container with all buttons
|
|
907
813
|
const buttonContainer = createButtonContainer(this.config.buttonPosition, this.virtualTryOnImageUrl !== null, async () => {
|
|
908
814
|
await this.handleTryOnClick();
|
|
909
815
|
}, async () => {
|
|
@@ -920,22 +826,16 @@ class VirtualTryOnWidget {
|
|
|
920
826
|
}, this.isShowingVirtualTryOn);
|
|
921
827
|
container.appendChild(buttonContainer);
|
|
922
828
|
}
|
|
923
|
-
/**
|
|
924
|
-
* Shows the virtual try-on image in the container
|
|
925
|
-
* @private
|
|
926
|
-
*/
|
|
927
829
|
showVirtualTryOnImage(container) {
|
|
928
830
|
if (!this.virtualTryOnImageUrl) {
|
|
929
831
|
console.warn("[WeWear VTO] No virtual try-on image URL available");
|
|
930
832
|
return;
|
|
931
833
|
}
|
|
932
|
-
// Remove all direct children except for the button container
|
|
933
834
|
Array.from(container.children).forEach((child) => {
|
|
934
835
|
if (!child.classList.contains(CSS_CLASSES.BUTTON_CONTAINER)) {
|
|
935
836
|
child.remove();
|
|
936
837
|
}
|
|
937
838
|
});
|
|
938
|
-
// Get stored original dimensions to prevent layout shift
|
|
939
839
|
const originalWidth = container.getAttribute("data-ww-original-width") || "";
|
|
940
840
|
const originalHeight = container.getAttribute("data-ww-original-height") || "";
|
|
941
841
|
const originalRectWidth = container.getAttribute("data-ww-original-rect-width") || "";
|
|
@@ -943,10 +843,8 @@ class VirtualTryOnWidget {
|
|
|
943
843
|
const image = document.createElement("img");
|
|
944
844
|
image.src = this.virtualTryOnImageUrl;
|
|
945
845
|
image.alt = "Virtual Try-On Result";
|
|
946
|
-
// Use original dimensions to prevent layout shift
|
|
947
846
|
let widthStyle = "100%";
|
|
948
847
|
let heightStyle = "100%";
|
|
949
|
-
// Prefer computed style dimensions, fallback to bounding rect, then container fill
|
|
950
848
|
if (originalWidth && originalWidth !== "auto" && originalWidth !== "0px") {
|
|
951
849
|
widthStyle = originalWidth;
|
|
952
850
|
}
|
|
@@ -961,61 +859,42 @@ class VirtualTryOnWidget {
|
|
|
961
859
|
else if (originalRectHeight && originalRectHeight !== "0") {
|
|
962
860
|
heightStyle = `${originalRectHeight}px`;
|
|
963
861
|
}
|
|
964
|
-
image.style.cssText = `
|
|
965
|
-
width: ${widthStyle};
|
|
966
|
-
height: ${heightStyle};
|
|
967
|
-
object-fit: contain;
|
|
862
|
+
image.style.cssText = `
|
|
863
|
+
width: ${widthStyle};
|
|
864
|
+
height: ${heightStyle};
|
|
865
|
+
object-fit: contain;
|
|
968
866
|
`;
|
|
969
|
-
// Prepend the image to ensure buttons are rendered on top
|
|
970
867
|
container.prepend(image);
|
|
971
868
|
this.isShowingVirtualTryOn = true;
|
|
972
869
|
}
|
|
973
|
-
/**
|
|
974
|
-
* Shows the original product image in the container
|
|
975
|
-
* @private
|
|
976
|
-
*/
|
|
977
870
|
showOriginalImage(container) {
|
|
978
871
|
const originalContentHTML = container.getAttribute("data-ww-original-content");
|
|
979
872
|
if (originalContentHTML) {
|
|
980
|
-
// Remove all direct children except for the button container
|
|
981
873
|
Array.from(container.children).forEach((child) => {
|
|
982
874
|
if (!child.classList.contains(CSS_CLASSES.BUTTON_CONTAINER)) {
|
|
983
875
|
child.remove();
|
|
984
876
|
}
|
|
985
877
|
});
|
|
986
|
-
// Parse the original content and prepend it
|
|
987
878
|
const tempDiv = document.createElement("div");
|
|
988
879
|
tempDiv.innerHTML = originalContentHTML;
|
|
989
880
|
container.prepend(...Array.from(tempDiv.children));
|
|
990
881
|
}
|
|
991
882
|
this.isShowingVirtualTryOn = false;
|
|
992
883
|
}
|
|
993
|
-
/**
|
|
994
|
-
* Destroys the widget and cleans up resources
|
|
995
|
-
*/
|
|
996
884
|
destroy() {
|
|
997
885
|
try {
|
|
998
|
-
// Remove all buttons
|
|
999
886
|
removeElements(`.${CSS_CLASSES.BUTTON_CONTAINER}`);
|
|
1000
|
-
// Remove all modals
|
|
1001
887
|
removeElements(`.${CSS_CLASSES.MODAL}`);
|
|
1002
|
-
// Clear references
|
|
1003
888
|
this.cameraButton = null;
|
|
1004
|
-
// Remove message listener
|
|
1005
889
|
if (this.iframeMessageListener) {
|
|
1006
890
|
window.removeEventListener("message", this.iframeMessageListener);
|
|
1007
891
|
this.iframeMessageListener = null;
|
|
1008
892
|
}
|
|
1009
|
-
// Reset state
|
|
1010
893
|
this.virtualTryOnImageUrl = null;
|
|
1011
894
|
this.isShowingVirtualTryOn = false;
|
|
1012
895
|
this.lastModelImage = null;
|
|
1013
896
|
this.originalProductImageUrl = null;
|
|
1014
897
|
this.originalProductImages = [];
|
|
1015
|
-
if (this.previewBadge) {
|
|
1016
|
-
this.previewBadge.remove();
|
|
1017
|
-
this.previewBadge = null;
|
|
1018
|
-
}
|
|
1019
898
|
console.log("[WeWear VTO] Widget destroyed successfully");
|
|
1020
899
|
}
|
|
1021
900
|
catch (error) {
|
|
@@ -1027,19 +906,15 @@ class VirtualTryOnWidget {
|
|
|
1027
906
|
let widgetInstance = null;
|
|
1028
907
|
function initVirtualTryOn(config) {
|
|
1029
908
|
try {
|
|
1030
|
-
// Clean up any existing instance
|
|
1031
909
|
if (widgetInstance) {
|
|
1032
910
|
widgetInstance.destroy();
|
|
1033
911
|
widgetInstance = null;
|
|
1034
912
|
}
|
|
1035
|
-
// Early return if config is missing
|
|
1036
913
|
if (!config) {
|
|
1037
914
|
console.log("[WeWear VTO] Missing configuration. Widget not initialized.");
|
|
1038
915
|
return;
|
|
1039
916
|
}
|
|
1040
|
-
// Create and initialize new instance
|
|
1041
917
|
widgetInstance = new VirtualTryOnWidget(config);
|
|
1042
|
-
// Initialize immediately if DOM is ready, otherwise wait for it
|
|
1043
918
|
if (document.readyState === "loading") {
|
|
1044
919
|
document.addEventListener("DOMContentLoaded", () => {
|
|
1045
920
|
widgetInstance === null || widgetInstance === void 0 ? void 0 : widgetInstance.init().catch((error) => {
|