@primestyleai/tryon 1.0.0 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,332 @@
1
+ <p align="center">
2
+ <img src="https://primestyleai.com/logo-gold.svg" alt="PrimeStyle AI" width="200" />
3
+ </p>
4
+
5
+ <h1 align="center">@primestyleai/tryon</h1>
6
+
7
+ <p align="center">
8
+ <strong>AI-Powered Virtual Try-On for React & Next.js</strong>
9
+ </p>
10
+
11
+ <p align="center">
12
+ Add a virtual try-on button to your product pages in 3 steps. Customers upload a photo and see how clothes look on them — powered by PrimeStyle AI.
13
+ </p>
14
+
15
+ <p align="center">
16
+ <a href="https://www.npmjs.com/package/@primestyleai/tryon"><img src="https://img.shields.io/npm/v/@primestyleai/tryon?color=D6BA7D&label=npm" alt="npm version" /></a>
17
+ <a href="https://www.npmjs.com/package/@primestyleai/tryon"><img src="https://img.shields.io/npm/dm/@primestyleai/tryon?color=D6BA7D" alt="npm downloads" /></a>
18
+ <a href="https://github.com/primestyleai/tryon-sdk/blob/main/LICENSE"><img src="https://img.shields.io/npm/l/@primestyleai/tryon?color=D6BA7D" alt="license" /></a>
19
+ <img src="https://img.shields.io/badge/gzip-5.5kB-green" alt="bundle size" />
20
+ </p>
21
+
22
+ <p align="center">
23
+ <a href="https://primestyleai.com/developer/demo">Live Demo</a> &bull;
24
+ <a href="https://primestyleai.com/docs">Documentation</a> &bull;
25
+ <a href="https://primestyleai.com/dashboard/developer/keys">Get API Key</a> &bull;
26
+ <a href="https://primestyleai.com/pricing">Pricing</a>
27
+ </p>
28
+
29
+ ---
30
+
31
+ ## How It Works
32
+
33
+ 1. Customer clicks the **"Virtual Try-On"** button on your product page
34
+ 2. They upload a photo of themselves
35
+ 3. PrimeStyle AI generates a realistic try-on image in ~15 seconds
36
+ 4. Customer sees how the garment looks on them before buying
37
+
38
+ The entire flow happens inside a beautiful modal — no redirects, no iframes.
39
+
40
+ ---
41
+
42
+ ## Quick Start
43
+
44
+ ### 1. Install
45
+
46
+ ```bash
47
+ npm install @primestyleai/tryon
48
+ ```
49
+
50
+ ### 2. Set Your API Key
51
+
52
+ Get your key from the [Developer Dashboard](https://primestyleai.com/dashboard/developer/keys) and add it to your `.env.local`:
53
+
54
+ ```env
55
+ NEXT_PUBLIC_PRIMESTYLE_API_KEY=ps_live_your_key_here
56
+ ```
57
+
58
+ ### 3. Use the Component
59
+
60
+ ```jsx
61
+ import { PrimeStyleTryon } from '@primestyleai/tryon/react';
62
+
63
+ function ProductPage({ product }) {
64
+ return (
65
+ <div>
66
+ <h1>{product.name}</h1>
67
+ <img src={product.image} alt={product.name} />
68
+
69
+ <PrimeStyleTryon
70
+ productImage={product.image}
71
+ buttonText="Try It On"
72
+ />
73
+ </div>
74
+ );
75
+ }
76
+ ```
77
+
78
+ That's it. No API key prop needed — the component reads it from your environment automatically.
79
+
80
+ ---
81
+
82
+ ## Props
83
+
84
+ | Prop | Type | Default | Description |
85
+ |------|------|---------|-------------|
86
+ | `productImage` | `string` | **required** | URL of the garment image |
87
+ | `buttonText` | `string` | `"Virtual Try-On"` | Text on the trigger button |
88
+ | `apiUrl` | `string` | `NEXT_PUBLIC_PRIMESTYLE_API_URL` or production | API endpoint override |
89
+ | `showPoweredBy` | `boolean` | `true` | Show "Powered by PrimeStyle AI" in modal |
90
+ | `buttonStyles` | `ButtonStyles` | `{}` | Customize button appearance |
91
+ | `modalStyles` | `ModalStyles` | `{}` | Customize modal appearance |
92
+ | `className` | `string` | — | Additional CSS class on wrapper |
93
+ | `style` | `CSSProperties` | — | Inline styles on wrapper |
94
+ | `onOpen` | `() => void` | — | Modal opened |
95
+ | `onClose` | `() => void` | — | Modal closed |
96
+ | `onUpload` | `(file: File) => void` | — | User uploaded a photo |
97
+ | `onProcessing` | `(jobId: string) => void` | — | Try-on generation started |
98
+ | `onComplete` | `(result) => void` | — | Result ready: `{ jobId, imageUrl }` |
99
+ | `onError` | `(error) => void` | — | Error occurred: `{ message, code? }` |
100
+
101
+ ---
102
+
103
+ ## Customization
104
+
105
+ ### Button Styles
106
+
107
+ ```jsx
108
+ <PrimeStyleTryon
109
+ productImage={product.image}
110
+ buttonStyles={{
111
+ backgroundColor: '#000000',
112
+ textColor: '#ffffff',
113
+ borderRadius: '50px',
114
+ padding: '16px 32px',
115
+ fontSize: '16px',
116
+ fontWeight: '700',
117
+ width: '100%',
118
+ border: '2px solid #333',
119
+ hoverBackgroundColor: '#222',
120
+ iconSize: '20px',
121
+ boxShadow: '0 4px 12px rgba(0,0,0,0.3)',
122
+ }}
123
+ />
124
+ ```
125
+
126
+ **All ButtonStyles properties:**
127
+
128
+ | Property | Description |
129
+ |----------|-------------|
130
+ | `backgroundColor` | Button background color |
131
+ | `textColor` | Button text color |
132
+ | `borderRadius` | Corner rounding |
133
+ | `fontSize` | Text size |
134
+ | `fontFamily` | Font stack |
135
+ | `fontWeight` | Font weight |
136
+ | `padding` | Inner spacing |
137
+ | `border` | Border style |
138
+ | `width` | Button width (e.g. `"100%"`) |
139
+ | `height` | Button height |
140
+ | `hoverBackgroundColor` | Background on hover |
141
+ | `hoverTextColor` | Text color on hover |
142
+ | `iconSize` | Camera icon size |
143
+ | `iconColor` | Camera icon color |
144
+ | `boxShadow` | Shadow effect |
145
+
146
+ ### Modal Styles
147
+
148
+ ```jsx
149
+ <PrimeStyleTryon
150
+ productImage={product.image}
151
+ modalStyles={{
152
+ backgroundColor: '#ffffff',
153
+ textColor: '#111111',
154
+ overlayColor: 'rgba(0,0,0,0.7)',
155
+ borderRadius: '16px',
156
+ maxWidth: '520px',
157
+ headerBackgroundColor: '#f5f5f5',
158
+ headerTextColor: '#111111',
159
+ primaryButtonBackgroundColor: '#000000',
160
+ primaryButtonTextColor: '#ffffff',
161
+ loaderColor: '#000000',
162
+ }}
163
+ />
164
+ ```
165
+
166
+ **All ModalStyles properties:**
167
+
168
+ | Property | Description |
169
+ |----------|-------------|
170
+ | `overlayColor` | Background overlay color |
171
+ | `backgroundColor` | Modal background |
172
+ | `textColor` | Modal text color |
173
+ | `borderRadius` | Modal corner rounding |
174
+ | `width` / `maxWidth` | Modal dimensions |
175
+ | `fontFamily` | Modal font stack |
176
+ | `headerBackgroundColor` | Header section background |
177
+ | `headerTextColor` | Header title color |
178
+ | `closeButtonColor` | Close (X) button color |
179
+ | `uploadBorderColor` | Upload drop zone border |
180
+ | `uploadBackgroundColor` | Upload zone background |
181
+ | `uploadTextColor` | Upload zone text |
182
+ | `uploadIconColor` | Upload icon color |
183
+ | `primaryButtonBackgroundColor` | Submit / download button background |
184
+ | `primaryButtonTextColor` | Submit / download button text |
185
+ | `primaryButtonBorderRadius` | Submit button rounding |
186
+ | `loaderColor` | Loading spinner color |
187
+ | `resultBorderRadius` | Result image corner rounding |
188
+
189
+ ---
190
+
191
+ ## Callbacks
192
+
193
+ ```jsx
194
+ <PrimeStyleTryon
195
+ productImage={product.image}
196
+ onOpen={() => {
197
+ analytics.track('tryon_opened');
198
+ }}
199
+ onUpload={(file) => {
200
+ console.log('Photo uploaded:', file.name);
201
+ }}
202
+ onProcessing={(jobId) => {
203
+ console.log('Processing:', jobId);
204
+ }}
205
+ onComplete={(result) => {
206
+ console.log('Result ready:', result.imageUrl);
207
+ analytics.track('tryon_completed', { jobId: result.jobId });
208
+ }}
209
+ onError={(error) => {
210
+ console.error('Try-on failed:', error.message);
211
+ // error.code: 'INSUFFICIENT_TOKENS' | 'API_ERROR' | etc.
212
+ }}
213
+ onClose={() => {
214
+ console.log('Modal closed');
215
+ }}
216
+ />
217
+ ```
218
+
219
+ ---
220
+
221
+ ## Environment Variables
222
+
223
+ | Variable | Required | Default | Description |
224
+ |----------|----------|---------|-------------|
225
+ | `NEXT_PUBLIC_PRIMESTYLE_API_KEY` | Yes | — | Your PrimeStyle API key |
226
+ | `NEXT_PUBLIC_PRIMESTYLE_API_URL` | No | `https://api.primestyleai.com` | Custom API endpoint |
227
+
228
+ The component reads these automatically — no need to pass them as props.
229
+
230
+ ---
231
+
232
+ ## Tokens & Pricing
233
+
234
+ Each virtual try-on consumes **tokens** from your account balance.
235
+
236
+ | | |
237
+ |---|---|
238
+ | **Free trial** | Tokens included on signup |
239
+ | **Token packs** | Pay-as-you-go, never expire |
240
+ | **Monthly plans** | Volume discounts for high-traffic stores |
241
+
242
+ Manage your balance and purchase tokens at [primestyleai.com/dashboard/billing](https://primestyleai.com/dashboard/billing).
243
+
244
+ If a try-on fails, tokens are **automatically refunded** to your account.
245
+
246
+ The `onComplete` callback includes token info:
247
+
248
+ ```jsx
249
+ onComplete={(result) => {
250
+ // result.jobId — the job ID
251
+ // result.imageUrl — the result image URL
252
+ }}
253
+ ```
254
+
255
+ ---
256
+
257
+ ## API Key Management
258
+
259
+ Create and manage your API keys at the [Developer Dashboard](https://primestyleai.com/dashboard/developer/keys).
260
+
261
+ - Keys start with `ps_live_` and are shown **once** at creation
262
+ - Set **allowed domains** to restrict where your key can be used
263
+ - **Revoke** keys instantly if compromised
264
+ - Maximum 10 active keys per account
265
+
266
+ ---
267
+
268
+ ## Full Example
269
+
270
+ ```jsx
271
+ // app/product/[id]/page.tsx (Next.js App Router)
272
+
273
+ import { PrimeStyleTryon } from '@primestyleai/tryon/react';
274
+
275
+ export default function ProductPage({ product }) {
276
+ return (
277
+ <div className="product-page">
278
+ <div className="product-gallery">
279
+ <img src={product.image} alt={product.name} />
280
+ </div>
281
+
282
+ <div className="product-info">
283
+ <h1>{product.name}</h1>
284
+ <p className="price">${product.price}</p>
285
+
286
+ {/* Virtual Try-On — reads API key from env */}
287
+ <PrimeStyleTryon
288
+ productImage={product.image}
289
+ buttonText="Try It On"
290
+ buttonStyles={{
291
+ width: '100%',
292
+ padding: '14px 24px',
293
+ borderRadius: '10px',
294
+ fontSize: '15px',
295
+ }}
296
+ onComplete={(result) => {
297
+ // Show success toast, track analytics, etc.
298
+ toast.success('Your try-on is ready!');
299
+ }}
300
+ onError={(error) => {
301
+ toast.error(error.message);
302
+ }}
303
+ />
304
+
305
+ <button className="add-to-cart">Add to Cart</button>
306
+ </div>
307
+ </div>
308
+ );
309
+ }
310
+ ```
311
+
312
+ ---
313
+
314
+ ## Requirements
315
+
316
+ - React 18+
317
+ - Next.js 13+ (App Router or Pages Router)
318
+
319
+ ---
320
+
321
+ ## Need Help?
322
+
323
+ - [Live Demo](https://primestyleai.com/developer/demo) — See it in action
324
+ - [Documentation](https://primestyleai.com/docs) — Full API reference
325
+ - [Dashboard](https://primestyleai.com/dashboard/developer/keys) — Manage keys & tokens
326
+ - [Contact](mailto:support@primestyleai.com) — We're here to help
327
+
328
+ ---
329
+
330
+ <p align="center">
331
+ Built with care by <a href="https://primestyleai.com">PrimeStyle AI</a>
332
+ </p>
@@ -0,0 +1,186 @@
1
+ const DEFAULT_API_URL = "https://api.primestyleai.com";
2
+ class ApiClient {
3
+ constructor(apiKey, apiUrl) {
4
+ this.apiKey = apiKey;
5
+ this.baseUrl = (apiUrl || DEFAULT_API_URL).replace(/\/+$/, "");
6
+ }
7
+ get headers() {
8
+ return {
9
+ "Content-Type": "application/json",
10
+ Authorization: `Bearer ${this.apiKey}`
11
+ };
12
+ }
13
+ async submitTryOn(modelImage, garmentImage) {
14
+ const res = await fetch(`${this.baseUrl}/api/v1/tryon`, {
15
+ method: "POST",
16
+ headers: this.headers,
17
+ body: JSON.stringify({ modelImage, garmentImage })
18
+ });
19
+ if (!res.ok) {
20
+ const data = await res.json().catch(() => ({}));
21
+ if (res.status === 402) {
22
+ throw new PrimeStyleError(
23
+ data.message || "Insufficient tokens",
24
+ "INSUFFICIENT_TOKENS"
25
+ );
26
+ }
27
+ throw new PrimeStyleError(
28
+ data.message || "Failed to submit try-on",
29
+ "API_ERROR"
30
+ );
31
+ }
32
+ return res.json();
33
+ }
34
+ async getStatus(jobId) {
35
+ const res = await fetch(`${this.baseUrl}/api/v1/tryon/status/${jobId}`, {
36
+ headers: this.headers
37
+ });
38
+ if (!res.ok) {
39
+ const data = await res.json().catch(() => ({}));
40
+ throw new PrimeStyleError(
41
+ data.message || "Failed to get status",
42
+ "API_ERROR"
43
+ );
44
+ }
45
+ return res.json();
46
+ }
47
+ getStreamUrl() {
48
+ return `${this.baseUrl}/api/v1/tryon/stream?key=${encodeURIComponent(this.apiKey)}`;
49
+ }
50
+ }
51
+ class PrimeStyleError extends Error {
52
+ constructor(message, code) {
53
+ super(message);
54
+ this.name = "PrimeStyleError";
55
+ this.code = code;
56
+ }
57
+ }
58
+ class SseClient {
59
+ constructor(streamUrl) {
60
+ this.eventSource = null;
61
+ this.listeners = /* @__PURE__ */ new Map();
62
+ this.reconnectTimer = null;
63
+ this.reconnectAttempts = 0;
64
+ this.maxReconnectAttempts = 5;
65
+ this.streamUrl = streamUrl;
66
+ }
67
+ connect() {
68
+ if (this.eventSource) return;
69
+ this.eventSource = new EventSource(this.streamUrl);
70
+ this.eventSource.addEventListener("vto-update", (event) => {
71
+ try {
72
+ const data = JSON.parse(event.data);
73
+ this.emit(data.galleryId, data);
74
+ } catch {
75
+ }
76
+ });
77
+ this.eventSource.onopen = () => {
78
+ this.reconnectAttempts = 0;
79
+ };
80
+ this.eventSource.onerror = () => {
81
+ this.eventSource?.close();
82
+ this.eventSource = null;
83
+ this.scheduleReconnect();
84
+ };
85
+ }
86
+ scheduleReconnect() {
87
+ if (this.reconnectAttempts >= this.maxReconnectAttempts) return;
88
+ if (this.listeners.size === 0) return;
89
+ const delay = Math.min(1e3 * 2 ** this.reconnectAttempts, 3e4);
90
+ this.reconnectAttempts++;
91
+ this.reconnectTimer = setTimeout(() => {
92
+ this.connect();
93
+ }, delay);
94
+ }
95
+ onJob(jobId, callback) {
96
+ if (!this.listeners.has(jobId)) {
97
+ this.listeners.set(jobId, /* @__PURE__ */ new Set());
98
+ }
99
+ this.listeners.get(jobId).add(callback);
100
+ if (!this.eventSource) {
101
+ this.connect();
102
+ }
103
+ return () => {
104
+ const jobListeners = this.listeners.get(jobId);
105
+ if (jobListeners) {
106
+ jobListeners.delete(callback);
107
+ if (jobListeners.size === 0) {
108
+ this.listeners.delete(jobId);
109
+ }
110
+ }
111
+ if (this.listeners.size === 0) {
112
+ this.disconnect();
113
+ }
114
+ };
115
+ }
116
+ emit(jobId, update) {
117
+ const callbacks = this.listeners.get(jobId);
118
+ if (callbacks) {
119
+ callbacks.forEach((cb) => cb(update));
120
+ }
121
+ }
122
+ disconnect() {
123
+ if (this.reconnectTimer) {
124
+ clearTimeout(this.reconnectTimer);
125
+ this.reconnectTimer = null;
126
+ }
127
+ if (this.eventSource) {
128
+ this.eventSource.close();
129
+ this.eventSource = null;
130
+ }
131
+ this.listeners.clear();
132
+ this.reconnectAttempts = 0;
133
+ }
134
+ }
135
+ const MAX_DIMENSION = 1024;
136
+ const JPEG_QUALITY = 0.85;
137
+ function compressImage(file) {
138
+ return new Promise((resolve, reject) => {
139
+ const reader = new FileReader();
140
+ reader.onload = () => {
141
+ const img = new Image();
142
+ img.onload = () => {
143
+ try {
144
+ const canvas = document.createElement("canvas");
145
+ let { width, height } = img;
146
+ if (width > MAX_DIMENSION || height > MAX_DIMENSION) {
147
+ if (width > height) {
148
+ height = Math.round(height * MAX_DIMENSION / width);
149
+ width = MAX_DIMENSION;
150
+ } else {
151
+ width = Math.round(width * MAX_DIMENSION / height);
152
+ height = MAX_DIMENSION;
153
+ }
154
+ }
155
+ canvas.width = width;
156
+ canvas.height = height;
157
+ const ctx = canvas.getContext("2d");
158
+ if (!ctx) {
159
+ reject(new Error("Canvas context not available"));
160
+ return;
161
+ }
162
+ ctx.drawImage(img, 0, 0, width, height);
163
+ const dataUrl = canvas.toDataURL("image/jpeg", JPEG_QUALITY);
164
+ resolve(dataUrl);
165
+ } catch (err) {
166
+ reject(err);
167
+ }
168
+ };
169
+ img.onerror = () => reject(new Error("Failed to load image"));
170
+ img.src = reader.result;
171
+ };
172
+ reader.onerror = () => reject(new Error("Failed to read file"));
173
+ reader.readAsDataURL(file);
174
+ });
175
+ }
176
+ function isValidImageFile(file) {
177
+ const accepted = ["image/jpeg", "image/png", "image/webp"];
178
+ return accepted.includes(file.type);
179
+ }
180
+ export {
181
+ ApiClient as A,
182
+ PrimeStyleError as P,
183
+ SseClient as S,
184
+ compressImage as c,
185
+ isValidImageFile as i
186
+ };
@@ -1,137 +1,5 @@
1
- const DEFAULT_API_URL = "https://api.primestyleai.com";
2
- class ApiClient {
3
- constructor(apiKey, apiUrl) {
4
- this.apiKey = apiKey;
5
- this.baseUrl = (apiUrl || DEFAULT_API_URL).replace(/\/+$/, "");
6
- }
7
- get headers() {
8
- return {
9
- "Content-Type": "application/json",
10
- Authorization: `Bearer ${this.apiKey}`
11
- };
12
- }
13
- async submitTryOn(modelImage, garmentImage) {
14
- const res = await fetch(`${this.baseUrl}/api/v1/tryon`, {
15
- method: "POST",
16
- headers: this.headers,
17
- body: JSON.stringify({ modelImage, garmentImage })
18
- });
19
- if (!res.ok) {
20
- const data = await res.json().catch(() => ({}));
21
- if (res.status === 402) {
22
- throw new PrimeStyleError(
23
- data.message || "Insufficient tokens",
24
- "INSUFFICIENT_TOKENS"
25
- );
26
- }
27
- throw new PrimeStyleError(
28
- data.message || "Failed to submit try-on",
29
- "API_ERROR"
30
- );
31
- }
32
- return res.json();
33
- }
34
- async getStatus(jobId) {
35
- const res = await fetch(`${this.baseUrl}/api/v1/tryon/status/${jobId}`, {
36
- headers: this.headers
37
- });
38
- if (!res.ok) {
39
- const data = await res.json().catch(() => ({}));
40
- throw new PrimeStyleError(
41
- data.message || "Failed to get status",
42
- "API_ERROR"
43
- );
44
- }
45
- return res.json();
46
- }
47
- getStreamUrl() {
48
- return `${this.baseUrl}/api/v1/tryon/stream?key=${encodeURIComponent(this.apiKey)}`;
49
- }
50
- }
51
- class PrimeStyleError extends Error {
52
- constructor(message, code) {
53
- super(message);
54
- this.name = "PrimeStyleError";
55
- this.code = code;
56
- }
57
- }
58
- class SseClient {
59
- constructor(streamUrl) {
60
- this.eventSource = null;
61
- this.listeners = /* @__PURE__ */ new Map();
62
- this.reconnectTimer = null;
63
- this.reconnectAttempts = 0;
64
- this.maxReconnectAttempts = 5;
65
- this.streamUrl = streamUrl;
66
- }
67
- connect() {
68
- if (this.eventSource) return;
69
- this.eventSource = new EventSource(this.streamUrl);
70
- this.eventSource.addEventListener("vto-update", (event) => {
71
- try {
72
- const data = JSON.parse(event.data);
73
- this.emit(data.galleryId, data);
74
- } catch {
75
- }
76
- });
77
- this.eventSource.onopen = () => {
78
- this.reconnectAttempts = 0;
79
- };
80
- this.eventSource.onerror = () => {
81
- this.eventSource?.close();
82
- this.eventSource = null;
83
- this.scheduleReconnect();
84
- };
85
- }
86
- scheduleReconnect() {
87
- if (this.reconnectAttempts >= this.maxReconnectAttempts) return;
88
- if (this.listeners.size === 0) return;
89
- const delay = Math.min(1e3 * 2 ** this.reconnectAttempts, 3e4);
90
- this.reconnectAttempts++;
91
- this.reconnectTimer = setTimeout(() => {
92
- this.connect();
93
- }, delay);
94
- }
95
- onJob(jobId, callback) {
96
- if (!this.listeners.has(jobId)) {
97
- this.listeners.set(jobId, /* @__PURE__ */ new Set());
98
- }
99
- this.listeners.get(jobId).add(callback);
100
- if (!this.eventSource) {
101
- this.connect();
102
- }
103
- return () => {
104
- const jobListeners = this.listeners.get(jobId);
105
- if (jobListeners) {
106
- jobListeners.delete(callback);
107
- if (jobListeners.size === 0) {
108
- this.listeners.delete(jobId);
109
- }
110
- }
111
- if (this.listeners.size === 0) {
112
- this.disconnect();
113
- }
114
- };
115
- }
116
- emit(jobId, update) {
117
- const callbacks = this.listeners.get(jobId);
118
- if (callbacks) {
119
- callbacks.forEach((cb) => cb(update));
120
- }
121
- }
122
- disconnect() {
123
- if (this.reconnectTimer) {
124
- clearTimeout(this.reconnectTimer);
125
- this.reconnectTimer = null;
126
- }
127
- if (this.eventSource) {
128
- this.eventSource.close();
129
- this.eventSource = null;
130
- }
131
- this.listeners.clear();
132
- this.reconnectAttempts = 0;
133
- }
134
- }
1
+ import { A as ApiClient, S as SseClient, i as isValidImageFile, c as compressImage } from "./image-utils-usff6Qu8.js";
2
+ import { P } from "./image-utils-usff6Qu8.js";
135
3
  function detectProductImage() {
136
4
  const ogImage = document.querySelector(
137
5
  'meta[property="og:image"]'
@@ -193,51 +61,6 @@ function extractSchemaImage(data) {
193
61
  }
194
62
  return null;
195
63
  }
196
- const MAX_DIMENSION = 1024;
197
- const JPEG_QUALITY = 0.85;
198
- function compressImage(file) {
199
- return new Promise((resolve, reject) => {
200
- const reader = new FileReader();
201
- reader.onload = () => {
202
- const img = new Image();
203
- img.onload = () => {
204
- try {
205
- const canvas = document.createElement("canvas");
206
- let { width, height } = img;
207
- if (width > MAX_DIMENSION || height > MAX_DIMENSION) {
208
- if (width > height) {
209
- height = Math.round(height * MAX_DIMENSION / width);
210
- width = MAX_DIMENSION;
211
- } else {
212
- width = Math.round(width * MAX_DIMENSION / height);
213
- height = MAX_DIMENSION;
214
- }
215
- }
216
- canvas.width = width;
217
- canvas.height = height;
218
- const ctx = canvas.getContext("2d");
219
- if (!ctx) {
220
- reject(new Error("Canvas context not available"));
221
- return;
222
- }
223
- ctx.drawImage(img, 0, 0, width, height);
224
- const dataUrl = canvas.toDataURL("image/jpeg", JPEG_QUALITY);
225
- resolve(dataUrl);
226
- } catch (err) {
227
- reject(err);
228
- }
229
- };
230
- img.onerror = () => reject(new Error("Failed to load image"));
231
- img.src = reader.result;
232
- };
233
- reader.onerror = () => reject(new Error("Failed to read file"));
234
- reader.readAsDataURL(file);
235
- });
236
- }
237
- function isValidImageFile(file) {
238
- const accepted = ["image/jpeg", "image/png", "image/webp"];
239
- return accepted.includes(file.type);
240
- }
241
64
  function getStyles() {
242
65
  return `
243
66
  :host {
@@ -1160,7 +983,7 @@ if (typeof window !== "undefined" && !customElements.get("primestyle-tryon")) {
1160
983
  }
1161
984
  export {
1162
985
  ApiClient,
1163
- PrimeStyleError,
986
+ P as PrimeStyleError,
1164
987
  PrimeStyleTryon,
1165
988
  SseClient,
1166
989
  compressImage,