@fayz-ai/storefront 0.6.0 → 0.8.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.
Files changed (102) hide show
  1. package/README.md +3 -1
  2. package/dist/auth.d.ts +12 -2
  3. package/dist/auth.d.ts.map +1 -1
  4. package/dist/blocks.d.ts +3 -3
  5. package/dist/blocks.d.ts.map +1 -1
  6. package/dist/component-contracts.d.ts +124 -0
  7. package/dist/component-contracts.d.ts.map +1 -0
  8. package/dist/component-selectors.d.ts +61 -0
  9. package/dist/component-selectors.d.ts.map +1 -0
  10. package/dist/components/CartDrawer.d.ts.map +1 -1
  11. package/dist/components/ProductCard.d.ts +2 -5
  12. package/dist/components/ProductCard.d.ts.map +1 -1
  13. package/dist/components/ProductEnquiryForm.d.ts +8 -0
  14. package/dist/components/ProductEnquiryForm.d.ts.map +1 -0
  15. package/dist/components/ProductGrid.d.ts.map +1 -1
  16. package/dist/components/RelatedProducts.d.ts +1 -1
  17. package/dist/components/RelatedProducts.d.ts.map +1 -1
  18. package/dist/components/SmoothImage.d.ts +7 -0
  19. package/dist/components/SmoothImage.d.ts.map +1 -0
  20. package/dist/components/StorefrontFooter.d.ts.map +1 -1
  21. package/dist/components/StorefrontHeader.d.ts.map +1 -1
  22. package/dist/components/sections/CategoryShowcase.d.ts.map +1 -1
  23. package/dist/components/sections/HeroSection.d.ts.map +1 -1
  24. package/dist/components/sections/MediaCarousel.d.ts +11 -0
  25. package/dist/components/sections/MediaCarousel.d.ts.map +1 -0
  26. package/dist/components/sections/MiscSections.d.ts.map +1 -1
  27. package/dist/components/sections/ProductRail.d.ts.map +1 -1
  28. package/dist/components/sections/ProductSlider.d.ts +15 -0
  29. package/dist/components/sections/ProductSlider.d.ts.map +1 -0
  30. package/dist/config.d.ts +38 -21
  31. package/dist/config.d.ts.map +1 -1
  32. package/dist/createStorefrontApp.d.ts +12 -0
  33. package/dist/createStorefrontApp.d.ts.map +1 -1
  34. package/dist/define.d.ts +17 -0
  35. package/dist/define.d.ts.map +1 -0
  36. package/dist/hooks/index.d.ts +1 -0
  37. package/dist/hooks/index.d.ts.map +1 -1
  38. package/dist/hooks/useStorefront.d.ts +35 -0
  39. package/dist/hooks/useStorefront.d.ts.map +1 -0
  40. package/dist/index.cjs +1113 -277
  41. package/dist/index.cjs.map +1 -1
  42. package/dist/index.d.ts +21 -11
  43. package/dist/index.d.ts.map +1 -1
  44. package/dist/index.js +1006 -188
  45. package/dist/index.js.map +1 -1
  46. package/dist/pages/CheckoutPage.d.ts.map +1 -1
  47. package/dist/pages/HomePage.d.ts +5 -2
  48. package/dist/pages/HomePage.d.ts.map +1 -1
  49. package/dist/pages/MyPurchasesPage.d.ts.map +1 -1
  50. package/dist/pages/OrderConfirmationPage.d.ts.map +1 -1
  51. package/dist/pages/ProductDetailPage.d.ts.map +1 -1
  52. package/dist/presets.d.ts +5 -0
  53. package/dist/presets.d.ts.map +1 -1
  54. package/dist/sections.d.ts +45 -2
  55. package/dist/sections.d.ts.map +1 -1
  56. package/dist/stores/cart.store.d.ts +5 -0
  57. package/dist/stores/cart.store.d.ts.map +1 -1
  58. package/dist/testids.d.ts +21 -0
  59. package/dist/testids.d.ts.map +1 -1
  60. package/dist/theme.d.ts +21 -1
  61. package/dist/theme.d.ts.map +1 -1
  62. package/dist/workflows/checkout.d.ts.map +1 -1
  63. package/package.json +10 -6
  64. package/src/auth.ts +47 -10
  65. package/src/blocks.tsx +7 -3
  66. package/src/component-contracts.ts +140 -0
  67. package/src/component-selectors.ts +31 -0
  68. package/src/components/CartDrawer.tsx +2 -1
  69. package/src/components/ProductCard.tsx +39 -23
  70. package/src/components/ProductEnquiryForm.tsx +175 -0
  71. package/src/components/ProductGrid.tsx +12 -3
  72. package/src/components/RelatedProducts.tsx +13 -4
  73. package/src/components/SmoothImage.tsx +59 -0
  74. package/src/components/StorefrontFooter.tsx +24 -14
  75. package/src/components/StorefrontHeader.tsx +74 -42
  76. package/src/components/sections/CategoryShowcase.tsx +3 -2
  77. package/src/components/sections/HeroSection.tsx +18 -2
  78. package/src/components/sections/MediaCarousel.tsx +141 -0
  79. package/src/components/sections/MiscSections.tsx +2 -1
  80. package/src/components/sections/ProductRail.tsx +11 -3
  81. package/src/components/sections/ProductSlider.tsx +119 -0
  82. package/src/config.ts +65 -24
  83. package/src/createStorefrontApp.tsx +80 -10
  84. package/src/define.ts +34 -0
  85. package/src/hooks/index.ts +1 -0
  86. package/src/hooks/useStorefront.ts +58 -0
  87. package/src/index.ts +62 -11
  88. package/src/pages/CheckoutPage.tsx +2 -1
  89. package/src/pages/HomePage.tsx +6 -2
  90. package/src/pages/MyPurchasesPage.tsx +2 -1
  91. package/src/pages/OrderConfirmationPage.tsx +2 -1
  92. package/src/pages/ProductDetailPage.tsx +138 -62
  93. package/src/presets.ts +66 -0
  94. package/src/scaffold.tsx +1 -1
  95. package/src/sections.ts +49 -2
  96. package/src/stores/cart.store.ts +16 -1
  97. package/src/testids.ts +21 -0
  98. package/src/theme.ts +36 -2
  99. package/src/workflows/checkout.ts +5 -2
  100. package/dist/slot-contracts.d.ts +0 -34
  101. package/dist/slot-contracts.d.ts.map +0 -1
  102. package/src/slot-contracts.ts +0 -20
package/dist/index.cjs CHANGED
@@ -2,10 +2,10 @@
2
2
 
3
3
  var runtime = require('@fayz-ai/shop/runtime');
4
4
  var mock = require('@fayz-ai/shop/mock');
5
- var auth = require('@fayz-ai/auth');
5
+ var pluginAuth = require('@fayz-ai/plugin-auth');
6
6
  var zustand = require('zustand');
7
7
  var middleware = require('zustand/middleware');
8
- var React18 = require('react');
8
+ var React6 = require('react');
9
9
  var core = require('@fayz-ai/core');
10
10
  var LucideIcons = require('lucide-react');
11
11
  var jsxRuntime = require('react/jsx-runtime');
@@ -31,7 +31,7 @@ function _interopNamespace(e) {
31
31
  return Object.freeze(n);
32
32
  }
33
33
 
34
- var React18__default = /*#__PURE__*/_interopDefault(React18);
34
+ var React6__default = /*#__PURE__*/_interopDefault(React6);
35
35
  var LucideIcons__namespace = /*#__PURE__*/_interopNamespace(LucideIcons);
36
36
 
37
37
  // src/createStorefrontApp.tsx
@@ -50,14 +50,31 @@ var useSessionStore = zustand.create()(
50
50
 
51
51
  // src/auth.ts
52
52
  var _adapter = null;
53
- function resolveAuthAdapter(configured) {
54
- if (configured && configured !== "mock" && configured !== "supabase") return configured;
55
- if (configured === "supabase") {
56
- console.warn(
57
- '@fayz-ai/shop: auth.adapter="supabase" is legacy. Pass an explicit AuthAdapter or use the Fayz SDK broker path.'
58
- );
59
- }
60
- return auth.createMockAuthAdapter();
53
+ function isAuthAdapter(value) {
54
+ return Boolean(
55
+ value && typeof value === "object" && "getSession" in value && "signIn" in value && "signOut" in value
56
+ );
57
+ }
58
+ function normalizeAuthConfig(configured) {
59
+ if (!configured) return {};
60
+ if (typeof configured === "string" || isAuthAdapter(configured)) return { adapter: configured };
61
+ return configured;
62
+ }
63
+ function resolveAuthAdapter(configured, fallback) {
64
+ const config = normalizeAuthConfig(configured);
65
+ return pluginAuth.createAuthRuntime({
66
+ provider: config.provider ?? config.adapter ?? (fallback?.supabaseUrl ? "supabase" : "mock"),
67
+ adapter: config.adapter,
68
+ routes: config.routes,
69
+ requireAuth: false,
70
+ supabase: {
71
+ url: config.supabase?.url ?? fallback?.supabaseUrl,
72
+ anonKey: config.supabase?.anonKey ?? fallback?.supabaseAnonKey,
73
+ client: config.supabase?.client,
74
+ callbackUrl: config.supabase?.callbackUrl,
75
+ resetPasswordUrl: config.supabase?.resetPasswordUrl
76
+ }
77
+ }).adapter;
61
78
  }
62
79
  function initCustomerAuth(adapter2) {
63
80
  _adapter = adapter2;
@@ -74,7 +91,7 @@ function initCustomerAuth(adapter2) {
74
91
  });
75
92
  }
76
93
  function getCustomerAuthAdapter() {
77
- if (!_adapter) _adapter = auth.createMockAuthAdapter();
94
+ if (!_adapter) _adapter = pluginAuth.createMockAuthAdapter();
78
95
  return _adapter;
79
96
  }
80
97
  async function resolveShopCustomer(email, name) {
@@ -128,18 +145,37 @@ async function signOutCustomer() {
128
145
  }
129
146
  function resolveConfig(config) {
130
147
  const catalogPath = config.home ? "/catalog" : "/";
148
+ const commerceMode = config.commerceMode ?? "checkout";
149
+ const checkoutEnabled = commerceMode === "checkout";
131
150
  return {
132
151
  ...config,
133
152
  currency: config.currency ?? "BRL",
134
153
  locale: config.locale ?? "pt-BR",
135
154
  shipping: { flatRate: config.shipping?.flatRate ?? 0, freeAbove: config.shipping?.freeAbove },
136
155
  payments: { mode: config.payments?.mode ?? "mock" },
156
+ commerceMode,
157
+ enquiry: {
158
+ label: config.enquiry?.label ?? "Contact me",
159
+ successMessage: config.enquiry?.successMessage ?? "Thanks. We received your enquiry.",
160
+ subjectPrefix: config.enquiry?.subjectPrefix ?? "Product enquiry",
161
+ email: config.enquiry?.email,
162
+ whatsappUrl: config.enquiry?.whatsappUrl,
163
+ requirePhone: config.enquiry?.requirePhone
164
+ },
137
165
  features: {
138
166
  // Auto-enable the coupon UI when the store actually ships discount codes
139
167
  // (via config.discounts or a seeded catalog), unless explicitly overridden —
140
168
  // otherwise a store can advertise a coupon with no field to enter it.
141
169
  discounts: config.features?.discounts ?? Boolean(config.discounts?.length || config.catalog?.discounts?.length),
142
- accounts: config.features?.accounts ?? true
170
+ accounts: config.features?.accounts ?? checkoutEnabled,
171
+ cart: config.features?.cart ?? checkoutEnabled,
172
+ checkout: config.features?.checkout ?? checkoutEnabled
173
+ },
174
+ imageLoading: {
175
+ mode: config.imageLoading?.mode ?? "fade",
176
+ durationMs: config.imageLoading?.durationMs ?? 420,
177
+ easing: config.imageLoading?.easing ?? "cubic-bezier(0.22, 1, 0.36, 1)",
178
+ blur: config.imageLoading?.blur ?? true
143
179
  },
144
180
  catalogPath,
145
181
  nav: config.nav ?? (config.home ? [
@@ -148,20 +184,26 @@ function resolveConfig(config) {
148
184
  ] : [{ label: "Loja", to: "/" }])
149
185
  };
150
186
  }
151
- var StorefrontConfigContext = React18.createContext(null);
187
+ var StorefrontConfigContext = React6.createContext(null);
152
188
  var StorefrontConfigProvider = StorefrontConfigContext.Provider;
153
189
  function useStorefrontConfig() {
154
- const ctx = React18.useContext(StorefrontConfigContext);
190
+ const ctx = React6.useContext(StorefrontConfigContext);
155
191
  if (!ctx) throw new Error("useStorefrontConfig must be used inside createStorefrontApp");
156
192
  return ctx;
157
193
  }
194
+ function useStorefrontConfigOptional() {
195
+ return React6.useContext(StorefrontConfigContext);
196
+ }
197
+ function getStorefrontComponents(config) {
198
+ return config.components ?? {};
199
+ }
158
200
  var adapter = core.hashRouterAdapter();
159
201
  function navigateTo(to) {
160
202
  adapter.navigate(to);
161
203
  }
162
204
  function useHashPath() {
163
- const [path, setPath] = React18.useState(() => adapter.getCurrentPath() || "/");
164
- React18.useEffect(() => {
205
+ const [path, setPath] = React6.useState(() => adapter.getCurrentPath() || "/");
206
+ React6.useEffect(() => {
165
207
  const unsub = adapter.onPathChange((p) => {
166
208
  setPath(p || "/");
167
209
  window.scrollTo(0, 0);
@@ -185,93 +227,7 @@ function matchPath(pattern, path) {
185
227
  return params;
186
228
  }
187
229
  function Link({ to, children, ...rest }) {
188
- return React18__default.default.createElement("a", { href: `#${to}`, ...rest }, children);
189
- }
190
- var RADIUS_MAP = {
191
- none: { button: "0px", card: "0px", input: "0px" },
192
- soft: { button: "6px", card: "10px", input: "6px" },
193
- round: { button: "10px", card: "16px", input: "10px" },
194
- pill: { button: "999px", card: "20px", input: "999px" }
195
- };
196
- var COLOR_VAR_MAP = {
197
- background: "--background",
198
- foreground: "--foreground",
199
- primary: "--primary",
200
- primaryForeground: "--primary-foreground",
201
- card: "--card",
202
- cardForeground: "--card-foreground",
203
- muted: "--muted",
204
- mutedForeground: "--muted-foreground",
205
- border: "--border",
206
- headerBackground: "--sf-header-bg",
207
- headerForeground: "--sf-header-fg",
208
- announcementBackground: "--sf-announcement-bg",
209
- announcementForeground: "--sf-announcement-fg"
210
- };
211
- function themeToCss(theme) {
212
- const lines = [];
213
- for (const [key, cssVar] of Object.entries(COLOR_VAR_MAP)) {
214
- const value = theme.colors?.[key];
215
- if (value) lines.push(`${cssVar}: ${value};`);
216
- }
217
- if (!theme.colors?.headerBackground && theme.colors?.background) {
218
- lines.push(`--sf-header-bg: ${theme.colors.background};`);
219
- }
220
- if (!theme.colors?.headerForeground && theme.colors?.foreground) {
221
- lines.push(`--sf-header-fg: ${theme.colors.foreground};`);
222
- }
223
- if (theme.font) {
224
- const fallback = theme.font.fallback ?? "sans-serif";
225
- lines.push(`--font-family: '${theme.font.body}', ${fallback};`);
226
- lines.push(`--sf-font-heading: '${theme.font.heading}', ${fallback};`);
227
- }
228
- const radius = RADIUS_MAP[theme.radius ?? "soft"];
229
- lines.push(`--button-radius: ${radius.button};`);
230
- lines.push(`--card-radius: ${radius.card};`);
231
- lines.push(`--input-radius: ${radius.input};`);
232
- lines.push(`--sf-radius-button: ${radius.button};`);
233
- lines.push(`--sf-radius-card: ${radius.card};`);
234
- lines.push(`--sf-radius-input: ${radius.input};`);
235
- let css = `:root {
236
- ${lines.join("\n ")}
237
- }
238
- `;
239
- css += `body { font-family: var(--font-family); }
240
- `;
241
- css += `h1, h2, h3, h4, .sf-heading { font-family: var(--sf-font-heading, var(--font-family)); }
242
- `;
243
- if (theme.uppercaseButtons) {
244
- css += `.sf-cta { text-transform: uppercase; letter-spacing: 0.08em; }
245
- `;
246
- }
247
- if (theme.header?.uppercaseNav) {
248
- css += `.sf-nav-link { text-transform: uppercase; letter-spacing: 0.06em; font-size: 0.8rem; }
249
- `;
250
- }
251
- return css;
252
- }
253
- function googleFontsHref(theme) {
254
- if (!theme.font) return null;
255
- const families = theme.font.googleFonts ?? [theme.font.heading, theme.font.body];
256
- const unique = [...new Set(families)];
257
- const params = unique.map((f) => `family=${encodeURIComponent(f).replace(/%20/g, "+")}:wght@300;400;500;600;700`).join("&");
258
- return `https://fonts.googleapis.com/css2?${params}&display=swap`;
259
- }
260
- function StorefrontThemeStyle({ theme }) {
261
- const fontsHref = googleFontsHref(theme);
262
- React18.useEffect(() => {
263
- if (!fontsHref) return;
264
- const id = "sf-theme-fonts";
265
- let link = document.getElementById(id);
266
- if (!link) {
267
- link = document.createElement("link");
268
- link.id = id;
269
- link.rel = "stylesheet";
270
- document.head.appendChild(link);
271
- }
272
- link.href = fontsHref;
273
- }, [fontsHref]);
274
- return React18__default.default.createElement("style", { "data-sf-theme": theme.name }, themeToCss(theme));
230
+ return React6__default.default.createElement("a", { href: `#${to}`, ...rest }, children);
275
231
  }
276
232
 
277
233
  // src/format.ts
@@ -325,7 +281,7 @@ function productOptionSelectionKey(selection) {
325
281
  // src/stores/cart.store.ts
326
282
  var useCartStore = zustand.create()(
327
283
  middleware.persist(
328
- (set) => ({
284
+ (set, get) => ({
329
285
  lines: [],
330
286
  discountCode: null,
331
287
  discountPercent: 0,
@@ -377,7 +333,16 @@ var useCartStore = zustand.create()(
377
333
  clearDiscount: () => set({ discountCode: null, discountPercent: 0 }),
378
334
  clear: () => set({ lines: [], discountCode: null, discountPercent: 0 }),
379
335
  openDrawer: () => set({ isOpen: true }),
380
- closeDrawer: () => set({ isOpen: false, justAddedLineId: null })
336
+ closeDrawer: () => set({ isOpen: false, justAddedLineId: null }),
337
+ reconcile: async (resolve) => {
338
+ const lines = get().lines;
339
+ if (lines.length === 0) return;
340
+ const keep = await Promise.all(
341
+ lines.map((line) => resolve(line).catch(() => true))
342
+ );
343
+ const kept = lines.filter((_, i) => keep[i]);
344
+ if (kept.length !== lines.length) set({ lines: kept });
345
+ }
381
346
  }),
382
347
  {
383
348
  name: "fayz.storefront.cart.v1",
@@ -399,6 +364,102 @@ var selectShipping = (s, cfg) => {
399
364
  return cfg.shipping.flatRate;
400
365
  };
401
366
  var selectTotal = (s, cfg) => roundCents(selectSubtotal(s) - selectDiscountTotal(s) + selectShipping(s, cfg));
367
+ var RADIUS_MAP = {
368
+ none: { button: "0px", card: "0px", input: "0px" },
369
+ soft: { button: "6px", card: "10px", input: "6px" },
370
+ round: { button: "10px", card: "16px", input: "10px" },
371
+ pill: { button: "999px", card: "20px", input: "999px" }
372
+ };
373
+ var COLOR_VAR_MAP = {
374
+ background: "--background",
375
+ foreground: "--foreground",
376
+ primary: "--primary",
377
+ primaryForeground: "--primary-foreground",
378
+ card: "--card",
379
+ cardForeground: "--card-foreground",
380
+ muted: "--muted",
381
+ mutedForeground: "--muted-foreground",
382
+ border: "--border",
383
+ headerBackground: "--sf-header-bg",
384
+ headerForeground: "--sf-header-fg",
385
+ announcementBackground: "--sf-announcement-bg",
386
+ announcementForeground: "--sf-announcement-fg",
387
+ accent: "--sf-accent",
388
+ accentForeground: "--sf-accent-foreground"
389
+ };
390
+ function themeToCss(theme) {
391
+ const lines = [];
392
+ for (const [key, cssVar] of Object.entries(COLOR_VAR_MAP)) {
393
+ const value = theme.colors?.[key];
394
+ if (value) lines.push(`${cssVar}: ${value};`);
395
+ }
396
+ if (!theme.colors?.headerBackground && theme.colors?.background) {
397
+ lines.push(`--sf-header-bg: ${theme.colors.background};`);
398
+ }
399
+ if (!theme.colors?.headerForeground && theme.colors?.foreground) {
400
+ lines.push(`--sf-header-fg: ${theme.colors.foreground};`);
401
+ }
402
+ if (theme.font) {
403
+ const fallback = theme.font.fallback ?? "sans-serif";
404
+ lines.push(`--font-family: '${theme.font.body}', ${fallback};`);
405
+ lines.push(`--sf-font-heading: '${theme.font.heading}', ${fallback};`);
406
+ if (theme.font.display) lines.push(`--sf-font-display: '${theme.font.display}', ${fallback};`);
407
+ if (theme.font.serif) lines.push(`--sf-font-serif: '${theme.font.serif}', serif;`);
408
+ }
409
+ for (const [key, value] of Object.entries(theme.tokens ?? {})) {
410
+ const name = key.startsWith("--") ? key : `--${key}`;
411
+ lines.push(`${name}: ${value};`);
412
+ }
413
+ const radius = RADIUS_MAP[theme.radius ?? "soft"];
414
+ lines.push(`--button-radius: ${radius.button};`);
415
+ lines.push(`--card-radius: ${radius.card};`);
416
+ lines.push(`--input-radius: ${radius.input};`);
417
+ lines.push(`--sf-radius-button: ${radius.button};`);
418
+ lines.push(`--sf-radius-card: ${radius.card};`);
419
+ lines.push(`--sf-radius-input: ${radius.input};`);
420
+ let css = `:root {
421
+ ${lines.join("\n ")}
422
+ }
423
+ `;
424
+ css += `body { font-family: var(--font-family); }
425
+ `;
426
+ css += `h1, h2, h3, h4, .sf-heading { font-family: var(--sf-font-heading, var(--font-family)); }
427
+ `;
428
+ if (theme.uppercaseButtons) {
429
+ css += `.sf-cta { text-transform: uppercase; letter-spacing: 0.08em; }
430
+ `;
431
+ }
432
+ if (theme.header?.uppercaseNav) {
433
+ css += `.sf-nav-link { text-transform: uppercase; letter-spacing: 0.06em; font-size: 0.8rem; }
434
+ `;
435
+ }
436
+ return css;
437
+ }
438
+ function googleFontsHref(theme) {
439
+ if (!theme.font) return null;
440
+ const families = theme.font.googleFonts ?? [theme.font.heading, theme.font.body, theme.font.display, theme.font.serif].filter(
441
+ (f) => Boolean(f)
442
+ );
443
+ const unique = [...new Set(families)];
444
+ const params = unique.map((f) => `family=${encodeURIComponent(f).replace(/%20/g, "+")}:wght@300;400;500;600;700`).join("&");
445
+ return `https://fonts.googleapis.com/css2?${params}&display=swap`;
446
+ }
447
+ function StorefrontThemeStyle({ theme }) {
448
+ const fontsHref = googleFontsHref(theme);
449
+ React6.useEffect(() => {
450
+ if (!fontsHref) return;
451
+ const id = "sf-theme-fonts";
452
+ let link = document.getElementById(id);
453
+ if (!link) {
454
+ link = document.createElement("link");
455
+ link.id = id;
456
+ link.rel = "stylesheet";
457
+ document.head.appendChild(link);
458
+ }
459
+ link.href = fontsHref;
460
+ }, [fontsHref]);
461
+ return React6__default.default.createElement("style", { "data-sf-theme": theme.name }, themeToCss(theme));
462
+ }
402
463
  var initial = {
403
464
  search: "",
404
465
  categoryId: null,
@@ -418,9 +479,9 @@ var useCatalogStore = zustand.create()((set) => ({
418
479
  reset: () => set(initial)
419
480
  }));
420
481
  function useCategories() {
421
- const [categories, setCategories] = React18.useState([]);
422
- const [loading, setLoading] = React18.useState(true);
423
- React18.useEffect(() => {
482
+ const [categories, setCategories] = React6.useState([]);
483
+ const [loading, setLoading] = React6.useState(true);
484
+ React6.useEffect(() => {
424
485
  let cancelled = false;
425
486
  runtime.getShopProvider().listCategories().then((data) => {
426
487
  if (!cancelled) setCategories(data);
@@ -439,9 +500,9 @@ function prefersReducedMotion() {
439
500
  return window.matchMedia("(prefers-reduced-motion: reduce)").matches;
440
501
  }
441
502
  function useInView(margin = "0px 0px -8% 0px") {
442
- const ref = React18.useRef(null);
443
- const [inView, setInView] = React18.useState(() => prefersReducedMotion());
444
- React18.useEffect(() => {
503
+ const ref = React6.useRef(null);
504
+ const [inView, setInView] = React6.useState(() => prefersReducedMotion());
505
+ React6.useEffect(() => {
445
506
  if (inView) return;
446
507
  const el = ref.current;
447
508
  if (!el || typeof IntersectionObserver === "undefined") {
@@ -476,8 +537,8 @@ function Reveal({ delay = 0, children, className, style, ...rest }) {
476
537
  );
477
538
  }
478
539
  function useScrolled(threshold = 8) {
479
- const [scrolled, setScrolled] = React18.useState(false);
480
- React18.useEffect(() => {
540
+ const [scrolled, setScrolled] = React6.useState(false);
541
+ React6.useEffect(() => {
481
542
  const onScroll = () => setScrolled(window.scrollY > threshold);
482
543
  onScroll();
483
544
  window.addEventListener("scroll", onScroll, { passive: true });
@@ -486,9 +547,9 @@ function useScrolled(threshold = 8) {
486
547
  return scrolled;
487
548
  }
488
549
  function usePopOnChange(value) {
489
- const [pop, setPop] = React18.useState(false);
490
- const prev = React18.useRef(value);
491
- React18.useEffect(() => {
550
+ const [pop, setPop] = React6.useState(false);
551
+ const prev = React6.useRef(value);
552
+ React6.useEffect(() => {
492
553
  if (prev.current !== value && value > 0) {
493
554
  setPop(true);
494
555
  const t = setTimeout(() => setPop(false), 400);
@@ -497,7 +558,7 @@ function usePopOnChange(value) {
497
558
  prev.current = value;
498
559
  return void 0;
499
560
  }, [value]);
500
- React18.useEffect(() => {
561
+ React6.useEffect(() => {
501
562
  prev.current = value;
502
563
  });
503
564
  return pop;
@@ -507,6 +568,7 @@ function usePopOnChange(value) {
507
568
  var TID = {
508
569
  // header
509
570
  headerSearch: "header-search",
571
+ headerSearchToggle: "header-search-toggle",
510
572
  cartButton: "cart-button",
511
573
  cartCount: "cart-count",
512
574
  accountLink: "account-link",
@@ -519,6 +581,13 @@ var TID = {
519
581
  hero: "home-hero",
520
582
  heroPrev: "hero-prev",
521
583
  heroNext: "hero-next",
584
+ mediaCarousel: "media-carousel",
585
+ mediaCarouselPrev: "media-carousel-prev",
586
+ mediaCarouselNext: "media-carousel-next",
587
+ productSlider: "product-slider",
588
+ productSliderViewport: "product-slider-viewport",
589
+ productSliderPrev: "product-slider-prev",
590
+ productSliderNext: "product-slider-next",
522
591
  categoryShowcase: "category-showcase",
523
592
  productRail: "product-rail",
524
593
  benefits: "benefits-row",
@@ -553,6 +622,19 @@ var TID = {
553
622
  productOptionValue: (id, value) => `product-option-${id.toLowerCase().replace(/\s+/g, "-")}-${value.toLowerCase().replace(/\s+/g, "-")}`,
554
623
  pdpQtyInput: "pdp-qty-input",
555
624
  pdpAddToCart: "pdp-add-to-cart",
625
+ pdpRoot: "pdp-root",
626
+ pdpGallery: "pdp-gallery",
627
+ pdpActions: "pdp-actions",
628
+ pdpEnquiryButton: "pdp-enquiry-button",
629
+ enquiryForm: "enquiry-form",
630
+ enquiryName: "enquiry-name",
631
+ enquiryEmail: "enquiry-email",
632
+ enquiryPhone: "enquiry-phone",
633
+ enquirySubject: "enquiry-subject",
634
+ enquiryMessage: "enquiry-message",
635
+ enquirySubmit: "enquiry-submit",
636
+ enquirySuccess: "enquiry-success",
637
+ enquiryError: "enquiry-error",
556
638
  // cart
557
639
  cartDrawer: "cart-drawer",
558
640
  cartLine: "cart-line",
@@ -616,7 +698,7 @@ var headerDivider = {
616
698
  var ANNOUNCEMENT_KEY = "fayz.storefront.announcement.dismissed";
617
699
  function AnnouncementBar() {
618
700
  const config = useStorefrontConfig();
619
- const [dismissed, setDismissed] = React18.useState(() => {
701
+ const [dismissed, setDismissed] = React6.useState(() => {
620
702
  try {
621
703
  return localStorage.getItem(ANNOUNCEMENT_KEY) === config.announcement;
622
704
  } catch {
@@ -675,12 +757,26 @@ function UtilityBar() {
675
757
  /* @__PURE__ */ jsxRuntime.jsx("span", { className: "ml-auto flex items-center gap-4", children: social?.map((s) => /* @__PURE__ */ jsxRuntime.jsx("a", { href: s.href, target: "_blank", rel: "noreferrer", className: "hover:underline", children: s.label }, s.href)) })
676
758
  ] }) });
677
759
  }
678
- function SearchInput({ className }) {
760
+ function SearchInput({ className, iconOnly = false }) {
679
761
  const config = useStorefrontConfig();
680
762
  const search = useCatalogStore((s) => s.search);
681
763
  const setSearch = useCatalogStore((s) => s.setSearch);
682
764
  const path = useHashPath();
683
- return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: `relative ${className ?? ""}`, children: [
765
+ const [open, setOpen] = React6.useState(false);
766
+ if (iconOnly && !open) {
767
+ return /* @__PURE__ */ jsxRuntime.jsx(
768
+ "button",
769
+ {
770
+ type: "button",
771
+ "aria-label": "Buscar produtos",
772
+ "data-testid": TID.headerSearchToggle,
773
+ onClick: () => setOpen(true),
774
+ className: "rounded-full p-2.5 transition-opacity hover:opacity-70",
775
+ children: /* @__PURE__ */ jsxRuntime.jsx(LucideIcons.Search, { className: "h-5 w-5" })
776
+ }
777
+ );
778
+ }
779
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: `relative ${iconOnly ? "w-full max-w-xs" : className ?? ""}`, children: [
684
780
  /* @__PURE__ */ jsxRuntime.jsx(LucideIcons.Search, { className: "pointer-events-none absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 opacity-60" }),
685
781
  /* @__PURE__ */ jsxRuntime.jsx(
686
782
  "input",
@@ -689,10 +785,17 @@ function SearchInput({ className }) {
689
785
  type: "search",
690
786
  placeholder: "Buscar produtos...",
691
787
  value: search,
788
+ autoFocus: iconOnly,
692
789
  onChange: (e) => {
693
790
  setSearch(e.target.value);
694
791
  if (path !== config.catalogPath) navigateTo(config.catalogPath);
695
792
  },
793
+ onKeyDown: (e) => {
794
+ if (e.key === "Escape" && iconOnly) setOpen(false);
795
+ },
796
+ onBlur: () => {
797
+ if (iconOnly && !search) setOpen(false);
798
+ },
696
799
  className: "w-full border bg-white/10 py-2 pl-9 pr-4 text-sm outline-none transition-colors placeholder:opacity-60 focus:border-primary focus:bg-white focus:text-gray-900",
697
800
  style: {
698
801
  borderRadius: "var(--sf-radius-input)",
@@ -707,7 +810,7 @@ function HeaderActions() {
707
810
  const count = useCartStore(selectCount);
708
811
  const openDrawer = useCartStore((s) => s.openDrawer);
709
812
  const session = useSessionStore();
710
- const [accountOpen, setAccountOpen] = React18.useState(false);
813
+ const [accountOpen, setAccountOpen] = React6.useState(false);
711
814
  const pop = usePopOnChange(count);
712
815
  const firstName = session.name?.split(/\s+/)[0];
713
816
  const initial2 = (firstName ?? session.email ?? "C").slice(0, 1).toUpperCase();
@@ -780,7 +883,7 @@ function HeaderActions() {
780
883
  }
781
884
  )
782
885
  ] }),
783
- /* @__PURE__ */ jsxRuntime.jsxs(
886
+ config.features.cart && /* @__PURE__ */ jsxRuntime.jsxs(
784
887
  "button",
785
888
  {
786
889
  type: "button",
@@ -807,12 +910,13 @@ function NavLinks({ className }) {
807
910
  const config = useStorefrontConfig();
808
911
  const { categories } = useCategories();
809
912
  const setCategoryId = useCatalogStore((s) => s.setCategoryId);
913
+ const showCategories = config.theme?.header?.showCategories !== false;
810
914
  const goCategory = (categoryId) => {
811
915
  useCatalogStore.getState().reset();
812
916
  setCategoryId(categoryId);
813
917
  navigateTo(config.catalogPath);
814
918
  };
815
- return /* @__PURE__ */ jsxRuntime.jsxs("nav", { className: `flex items-center gap-6 ${className ?? ""}`, children: [
919
+ return /* @__PURE__ */ jsxRuntime.jsxs("nav", { className: `flex min-w-max items-center gap-6 ${className ?? ""}`, children: [
816
920
  config.nav.map((link) => /* @__PURE__ */ jsxRuntime.jsx(
817
921
  Link,
818
922
  {
@@ -823,7 +927,7 @@ function NavLinks({ className }) {
823
927
  },
824
928
  link.to
825
929
  )),
826
- categories.slice(0, 6).map((c) => /* @__PURE__ */ jsxRuntime.jsxs(
930
+ showCategories && categories.slice(0, 6).map((c) => /* @__PURE__ */ jsxRuntime.jsxs(
827
931
  "button",
828
932
  {
829
933
  type: "button",
@@ -843,6 +947,8 @@ function StorefrontHeader() {
843
947
  const config = useStorefrontConfig();
844
948
  const variant = config.theme?.header?.variant ?? "classic";
845
949
  const scrolled = useScrolled();
950
+ const showSearch = config.theme?.header?.showSearch !== false;
951
+ const iconSearch = config.theme?.header?.searchStyle === "icon";
846
952
  const logo = /* @__PURE__ */ jsxRuntime.jsx(Link, { to: "/", className: "sf-heading shrink-0 text-xl font-bold tracking-tight", children: config.logo ?? config.name });
847
953
  return /* @__PURE__ */ jsxRuntime.jsxs(
848
954
  "header",
@@ -856,21 +962,21 @@ function StorefrontHeader() {
856
962
  // Rio/Flex pattern: centered logo row, nav row below
857
963
  /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
858
964
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mx-auto grid h-16 max-w-7xl grid-cols-[1fr_auto_1fr] items-center px-4 sm:px-6", children: [
859
- /* @__PURE__ */ jsxRuntime.jsx(SearchInput, { className: "hidden w-full max-w-xs sm:block" }),
965
+ showSearch ? /* @__PURE__ */ jsxRuntime.jsx(SearchInput, { iconOnly: iconSearch, className: "hidden w-full max-w-xs sm:block" }) : /* @__PURE__ */ jsxRuntime.jsx("div", {}),
860
966
  /* @__PURE__ */ jsxRuntime.jsx("div", { className: "text-center", children: logo }),
861
967
  /* @__PURE__ */ jsxRuntime.jsx("div", { className: "justify-self-end", children: /* @__PURE__ */ jsxRuntime.jsx(HeaderActions, {}) })
862
968
  ] }),
863
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "hidden justify-center border-t py-2.5 sm:flex", style: headerDivider, children: /* @__PURE__ */ jsxRuntime.jsx(NavLinks, {}) })
969
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex overflow-x-auto border-t py-2.5 sm:justify-center", style: headerDivider, children: /* @__PURE__ */ jsxRuntime.jsx(NavLinks, {}) })
864
970
  ] })
865
971
  ) : variant === "search" ? (
866
972
  // Brasília pattern: prominent search left, logo center, actions right; nav row below
867
973
  /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
868
974
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mx-auto grid h-16 max-w-7xl grid-cols-[1fr_auto_1fr] items-center gap-6 px-4 sm:px-6", children: [
869
- /* @__PURE__ */ jsxRuntime.jsx(SearchInput, { className: "hidden w-full max-w-sm sm:block" }),
975
+ showSearch ? /* @__PURE__ */ jsxRuntime.jsx(SearchInput, { iconOnly: iconSearch, className: "hidden w-full max-w-sm sm:block" }) : /* @__PURE__ */ jsxRuntime.jsx("div", {}),
870
976
  /* @__PURE__ */ jsxRuntime.jsx("div", { className: "text-center", children: logo }),
871
977
  /* @__PURE__ */ jsxRuntime.jsx("div", { className: "justify-self-end", children: /* @__PURE__ */ jsxRuntime.jsx(HeaderActions, {}) })
872
978
  ] }),
873
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "hidden border-t sm:block", style: headerDivider, children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "mx-auto max-w-7xl px-4 py-2.5 sm:px-6", children: /* @__PURE__ */ jsxRuntime.jsx(NavLinks, {}) }) })
979
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "overflow-x-auto border-t", style: headerDivider, children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "mx-auto max-w-7xl px-4 py-2.5 sm:px-6", children: /* @__PURE__ */ jsxRuntime.jsx(NavLinks, {}) }) })
874
980
  ] })
875
981
  ) : variant === "minimal" ? (
876
982
  // Uyuni pattern: nav left, centered logo, actions right — single compact row
@@ -878,17 +984,19 @@ function StorefrontHeader() {
878
984
  /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-center gap-5", children: /* @__PURE__ */ jsxRuntime.jsx(NavLinks, { className: "hidden sm:flex" }) }),
879
985
  /* @__PURE__ */ jsxRuntime.jsx("div", { className: "text-center", children: logo }),
880
986
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-end gap-3", children: [
881
- /* @__PURE__ */ jsxRuntime.jsx(SearchInput, { className: "hidden w-44 lg:block" }),
987
+ showSearch && /* @__PURE__ */ jsxRuntime.jsx(SearchInput, { iconOnly: iconSearch, className: "hidden w-44 lg:block" }),
882
988
  /* @__PURE__ */ jsxRuntime.jsx(HeaderActions, {})
883
989
  ] })
884
990
  ] })
885
991
  ) : (
886
- // classic: logo left, search center-right, actions right (original layout)
992
+ // classic: logo left, nav, then a right-aligned search + actions cluster
887
993
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mx-auto flex h-16 max-w-7xl items-center gap-6 px-4 sm:px-6", children: [
888
994
  logo,
889
995
  /* @__PURE__ */ jsxRuntime.jsx(NavLinks, { className: "hidden md:flex" }),
890
- /* @__PURE__ */ jsxRuntime.jsx(SearchInput, { className: "ml-auto hidden w-full max-w-sm sm:block" }),
891
- /* @__PURE__ */ jsxRuntime.jsx(HeaderActions, {})
996
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "ml-auto flex items-center gap-2 sm:gap-3", children: [
997
+ showSearch && /* @__PURE__ */ jsxRuntime.jsx(SearchInput, { iconOnly: iconSearch, className: iconSearch ? "" : "hidden w-full max-w-sm sm:block" }),
998
+ /* @__PURE__ */ jsxRuntime.jsx(HeaderActions, {})
999
+ ] })
892
1000
  ] })
893
1001
  )
894
1002
  ]
@@ -900,6 +1008,17 @@ function StorefrontFooter() {
900
1008
  const { categories } = useCategories();
901
1009
  const visibleCategories = categories.slice(0, 8);
902
1010
  const hiddenCategoryCount = Math.max(categories.length - visibleCategories.length, 0);
1011
+ const labels = {
1012
+ categories: "Departamentos",
1013
+ navigation: "Navega\xE7\xE3o",
1014
+ contact: "Contato",
1015
+ viewAll: "Ver todos",
1016
+ purchases: "Minhas compras",
1017
+ privacy: "Privacidade",
1018
+ terms: "Termos",
1019
+ returns: "Trocas e devolu\xE7\xF5es",
1020
+ ...config.footer?.labels
1021
+ };
903
1022
  const goCategory = (categoryId) => {
904
1023
  useCatalogStore.getState().reset();
905
1024
  useCatalogStore.getState().setCategoryId(categoryId);
@@ -912,7 +1031,7 @@ function StorefrontFooter() {
912
1031
  config.footer?.about && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "mt-3 text-sm leading-relaxed text-muted-foreground", children: config.footer.about })
913
1032
  ] }),
914
1033
  /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
915
- /* @__PURE__ */ jsxRuntime.jsx("h3", { className: "mb-3 text-sm font-semibold uppercase tracking-wide text-muted-foreground", children: "Departamentos" }),
1034
+ /* @__PURE__ */ jsxRuntime.jsx("h3", { className: "mb-3 text-sm font-semibold uppercase tracking-wide text-muted-foreground", children: labels.categories }),
916
1035
  /* @__PURE__ */ jsxRuntime.jsxs("ul", { className: "space-y-2 text-sm", children: [
917
1036
  visibleCategories.map((c) => /* @__PURE__ */ jsxRuntime.jsx("li", { children: /* @__PURE__ */ jsxRuntime.jsx(
918
1037
  "button",
@@ -924,21 +1043,22 @@ function StorefrontFooter() {
924
1043
  }
925
1044
  ) }, c.id)),
926
1045
  hiddenCategoryCount > 0 && /* @__PURE__ */ jsxRuntime.jsx("li", { children: /* @__PURE__ */ jsxRuntime.jsxs(Link, { to: config.catalogPath, className: "font-medium underline-offset-2 transition-opacity hover:underline", children: [
927
- "Ver todos (",
1046
+ labels.viewAll,
1047
+ " (",
928
1048
  categories.length,
929
1049
  ")"
930
1050
  ] }) })
931
1051
  ] })
932
1052
  ] }),
933
1053
  /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
934
- /* @__PURE__ */ jsxRuntime.jsx("h3", { className: "mb-3 text-sm font-semibold uppercase tracking-wide text-muted-foreground", children: "Navega\xE7\xE3o" }),
1054
+ /* @__PURE__ */ jsxRuntime.jsx("h3", { className: "mb-3 text-sm font-semibold uppercase tracking-wide text-muted-foreground", children: labels.navigation }),
935
1055
  /* @__PURE__ */ jsxRuntime.jsxs("ul", { className: "space-y-2 text-sm", children: [
936
1056
  config.nav.map((link) => /* @__PURE__ */ jsxRuntime.jsx("li", { children: /* @__PURE__ */ jsxRuntime.jsx(Link, { to: link.to, className: "transition-opacity hover:opacity-70", children: link.label }) }, link.to)),
937
- /* @__PURE__ */ jsxRuntime.jsx("li", { children: /* @__PURE__ */ jsxRuntime.jsx(Link, { to: "/account", className: "transition-opacity hover:opacity-70", children: "Minhas compras" }) })
1057
+ config.features.accounts && /* @__PURE__ */ jsxRuntime.jsx("li", { children: /* @__PURE__ */ jsxRuntime.jsx(Link, { to: "/account", className: "transition-opacity hover:opacity-70", children: labels.purchases }) })
938
1058
  ] })
939
1059
  ] }),
940
1060
  /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
941
- /* @__PURE__ */ jsxRuntime.jsx("h3", { className: "mb-3 text-sm font-semibold uppercase tracking-wide text-muted-foreground", children: "Contato" }),
1061
+ /* @__PURE__ */ jsxRuntime.jsx("h3", { className: "mb-3 text-sm font-semibold uppercase tracking-wide text-muted-foreground", children: labels.contact }),
942
1062
  /* @__PURE__ */ jsxRuntime.jsxs("ul", { className: "space-y-2 text-sm text-muted-foreground", children: [
943
1063
  config.footer?.contact?.phone && /* @__PURE__ */ jsxRuntime.jsx("li", { children: config.footer.contact.phone }),
944
1064
  config.footer?.contact?.email && /* @__PURE__ */ jsxRuntime.jsx("li", { children: config.footer.contact.email }),
@@ -949,17 +1069,15 @@ function StorefrontFooter() {
949
1069
  ] }),
950
1070
  /* @__PURE__ */ jsxRuntime.jsx("div", { className: "border-t", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mx-auto flex max-w-7xl flex-col items-center gap-3 px-4 py-5 text-xs text-muted-foreground sm:flex-row sm:justify-between sm:px-6", children: [
951
1071
  /* @__PURE__ */ jsxRuntime.jsxs("nav", { className: "flex flex-wrap items-center justify-center gap-x-5 gap-y-2", children: [
952
- /* @__PURE__ */ jsxRuntime.jsx(Link, { to: "/privacy", className: "transition-colors hover:text-foreground", children: "Privacidade" }),
953
- /* @__PURE__ */ jsxRuntime.jsx(Link, { to: "/terms", className: "transition-colors hover:text-foreground", children: "Termos" }),
954
- /* @__PURE__ */ jsxRuntime.jsx(Link, { to: "/refunds", className: "transition-colors hover:text-foreground", children: "Trocas e devolu\xE7\xF5es" })
1072
+ /* @__PURE__ */ jsxRuntime.jsx(Link, { to: "/privacy", className: "transition-colors hover:text-foreground", children: labels.privacy }),
1073
+ /* @__PURE__ */ jsxRuntime.jsx(Link, { to: "/terms", className: "transition-colors hover:text-foreground", children: labels.terms }),
1074
+ /* @__PURE__ */ jsxRuntime.jsx(Link, { to: "/refunds", className: "transition-colors hover:text-foreground", children: labels.returns })
955
1075
  ] }),
956
- /* @__PURE__ */ jsxRuntime.jsxs("p", { className: "text-center", children: [
1076
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-center", children: config.footer?.credit ?? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
957
1077
  "\xA9 ",
958
1078
  config.name,
959
- " \u2014 powered by Fayz \xB7 Fotos:",
960
- " ",
961
- /* @__PURE__ */ jsxRuntime.jsx("a", { href: "https://unsplash.com", target: "_blank", rel: "noreferrer", className: "underline-offset-2 hover:underline", children: "Unsplash" })
962
- ] })
1079
+ " \u2014 powered by Fayz"
1080
+ ] }) })
963
1081
  ] }) })
964
1082
  ] });
965
1083
  }
@@ -971,7 +1089,7 @@ var REASON_MESSAGE = {
971
1089
  };
972
1090
  function useDiscountValidator() {
973
1091
  const config = useStorefrontConfig();
974
- return React18.useCallback(async (code) => {
1092
+ return React6.useCallback(async (code) => {
975
1093
  const normalized = code.trim().toUpperCase();
976
1094
  if (!normalized) return { valid: false, percent: 0, message: "Informe um cupom." };
977
1095
  const configDiscount = config.discounts?.find((d) => d.code.trim().toUpperCase() === normalized);
@@ -1052,14 +1170,61 @@ function QuantityInput({ value, onChange, min = 1, max, testId, incTestId, decTe
1052
1170
  )
1053
1171
  ] });
1054
1172
  }
1173
+ function SmoothImage({
1174
+ imageLoading,
1175
+ loading = "lazy",
1176
+ decoding = "async",
1177
+ onLoad,
1178
+ style,
1179
+ ...props
1180
+ }) {
1181
+ const config = useStorefrontConfigOptional();
1182
+ const resolved = {
1183
+ ...config?.imageLoading,
1184
+ ...imageLoading
1185
+ };
1186
+ const mode = resolved.mode ?? "fade";
1187
+ const durationMs = resolved.durationMs ?? 420;
1188
+ const easing = resolved.easing ?? "cubic-bezier(0.22, 1, 0.36, 1)";
1189
+ const blur = resolved.blur ?? true;
1190
+ const ref = React6__default.default.useRef(null);
1191
+ const [loaded, setLoaded] = React6__default.default.useState(mode === "none");
1192
+ React6__default.default.useEffect(() => {
1193
+ if (mode === "none") {
1194
+ setLoaded(true);
1195
+ return;
1196
+ }
1197
+ if (ref.current?.complete && ref.current.naturalWidth > 0) setLoaded(true);
1198
+ }, [mode, props.src]);
1199
+ const revealStyle = mode === "none" ? {} : {
1200
+ opacity: loaded ? 1 : 0,
1201
+ filter: blur && !loaded ? "blur(10px)" : "blur(0px)",
1202
+ transition: `opacity ${durationMs}ms ${easing}, filter ${durationMs}ms ${easing}, transform ${durationMs}ms ${easing}`
1203
+ };
1204
+ return /* @__PURE__ */ jsxRuntime.jsx(
1205
+ "img",
1206
+ {
1207
+ ...props,
1208
+ ref,
1209
+ loading,
1210
+ decoding,
1211
+ "data-sf-image-loaded": loaded ? "true" : "false",
1212
+ style: { ...style, ...revealStyle },
1213
+ onLoad: (event) => {
1214
+ setLoaded(true);
1215
+ onLoad?.(event);
1216
+ }
1217
+ }
1218
+ );
1219
+ }
1055
1220
  function CartDrawer() {
1056
1221
  const config = useStorefrontConfig();
1057
1222
  const cart = useCartStore();
1058
1223
  const validate = useDiscountValidator();
1059
- const [code, setCode] = React18.useState("");
1060
- const [discountError, setDiscountError] = React18.useState(null);
1061
- const [applying, setApplying] = React18.useState(false);
1062
- React18.useEffect(() => {
1224
+ const [code, setCode] = React6.useState("");
1225
+ const [discountError, setDiscountError] = React6.useState(null);
1226
+ const [applying, setApplying] = React6.useState(false);
1227
+ React6.useEffect(() => {
1063
1228
  if (!cart.isOpen) return;
1064
1229
  const onKey = (e) => {
1065
1230
  if (e.key === "Escape") cart.closeDrawer();
@@ -1151,7 +1316,7 @@ function CartDrawer() {
1151
1316
  "data-line-id": line.lineId ?? line.productId,
1152
1317
  className: `flex animate-fade-up gap-3 rounded-lg p-1.5 transition-all hover:bg-muted/40 ${(line.lineId ?? line.productId) === cart.justAddedLineId ? "bg-primary/5 ring-1 ring-primary/30" : ""}`,
1153
1318
  children: [
1154
- line.imageUrl && /* @__PURE__ */ jsxRuntime.jsx("img", { src: line.imageUrl, alt: line.name, className: "h-20 w-20 shrink-0 rounded-lg border object-cover" }),
1319
+ line.imageUrl && /* @__PURE__ */ jsxRuntime.jsx(SmoothImage, { src: line.imageUrl, alt: line.name, className: "h-20 w-20 shrink-0 rounded-lg border object-cover" }),
1155
1320
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-1 flex-col", children: [
1156
1321
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-start justify-between gap-2", children: [
1157
1322
  /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-sm font-medium leading-snug", children: line.name }),
@@ -1282,12 +1447,12 @@ function CartDrawer() {
1282
1447
  ] });
1283
1448
  }
1284
1449
  function useProducts(opts) {
1285
- const [products, setProducts] = React18.useState([]);
1286
- const [loading, setLoading] = React18.useState(true);
1287
- const [error, setError] = React18.useState(null);
1288
- const [nonce, setNonce] = React18.useState(0);
1450
+ const [products, setProducts] = React6.useState([]);
1451
+ const [loading, setLoading] = React6.useState(true);
1452
+ const [error, setError] = React6.useState(null);
1453
+ const [nonce, setNonce] = React6.useState(0);
1289
1454
  const key = JSON.stringify(opts);
1290
- React18.useEffect(() => {
1455
+ React6.useEffect(() => {
1291
1456
  let cancelled = false;
1292
1457
  setLoading(true);
1293
1458
  runtime.getShopProvider().listProducts(JSON.parse(key)).then((data) => {
@@ -1321,36 +1486,91 @@ function Price({ value, compareAt, testId, compareTestId, className }) {
1321
1486
  ] });
1322
1487
  }
1323
1488
 
1324
- // src/slot-contracts.ts
1325
- var productCardSlotContract = {
1489
+ // src/component-selectors.ts
1490
+ var productCardComponentContract = {
1326
1491
  root: { "data-testid": TID.productCard },
1327
1492
  name: { "data-testid": TID.productCardName },
1328
1493
  priceTestId: TID.productCardPrice,
1329
1494
  addButton: { "data-testid": TID.productCardAdd }
1330
1495
  };
1331
- var storefrontSlotContracts = {
1332
- productCard: productCardSlotContract
1496
+ var storefrontComponentContracts = {
1497
+ productCard: productCardComponentContract,
1498
+ productDetail: {
1499
+ root: { "data-testid": TID.pdpRoot },
1500
+ gallery: { "data-testid": TID.pdpGallery },
1501
+ name: { "data-testid": TID.pdpName },
1502
+ description: { "data-testid": TID.pdpDescription },
1503
+ actions: { "data-testid": TID.pdpActions },
1504
+ addButton: { "data-testid": TID.pdpAddToCart },
1505
+ enquiryButton: { "data-testid": TID.pdpEnquiryButton },
1506
+ enquiryForm: { "data-testid": TID.enquiryForm }
1507
+ }
1333
1508
  };
1334
- function ProductCard({ product }) {
1509
+ function useStorefront() {
1510
+ return useStorefrontConfig();
1511
+ }
1512
+ function useCatalog() {
1335
1513
  const config = useStorefrontConfig();
1336
- const addItem = useCartStore((s) => s.addItem);
1514
+ const catalog = useCatalogStore();
1515
+ return {
1516
+ ...catalog,
1517
+ catalogPath: config.catalogPath,
1518
+ config
1519
+ };
1520
+ }
1521
+ function useCart() {
1522
+ return useCartStore();
1523
+ }
1524
+ function useEnquiry() {
1525
+ const config = useStorefrontConfig();
1526
+ const createProductEnquiry = async (input) => {
1527
+ const provider = runtime.getShopProvider();
1528
+ if (!provider.createProductEnquiry) {
1529
+ throw new Error("The active shop provider does not support product enquiries.");
1530
+ }
1531
+ return provider.createProductEnquiry(input);
1532
+ };
1533
+ return {
1534
+ enquiry: config.enquiry,
1535
+ createProductEnquiry
1536
+ };
1537
+ }
1538
+ function useStorefrontActions() {
1539
+ const addItem = useCartStore((state) => state.addItem);
1540
+ const openDrawer = useCartStore((state) => state.openDrawer);
1541
+ const closeDrawer = useCartStore((state) => state.closeDrawer);
1542
+ const { createProductEnquiry } = useEnquiry();
1543
+ return {
1544
+ navigateTo,
1545
+ addToCart: (product, quantity, options) => addItem(product, quantity, options),
1546
+ openCart: openDrawer,
1547
+ closeCart: closeDrawer,
1548
+ createProductEnquiry
1549
+ };
1550
+ }
1551
+ function ProductCard({ product, config: providedConfig, actions: providedActions }) {
1552
+ const runtimeConfig = useStorefrontConfig();
1553
+ const runtimeActions = useStorefrontActions();
1554
+ const config = providedConfig ?? runtimeConfig;
1555
+ const actions = providedActions ?? runtimeActions;
1337
1556
  const hasOptions = getProductOptionGroups(product).length > 0;
1338
1557
  const soldOut = product.inventoryCount <= 0;
1339
1558
  const onSale = product.compareAtPrice != null && product.compareAtPrice > product.price;
1340
1559
  const image = product.images.find((i) => i.isPrimary) ?? product.images[0];
1341
1560
  const cardStyle = config.theme?.productCard?.style ?? "card";
1342
1561
  const aspect = config.theme?.productCard?.imageAspect === "portrait" ? "aspect-[3/4]" : "aspect-square";
1562
+ const purchaseMode = config.commerceMode === "checkout";
1343
1563
  return /* @__PURE__ */ jsxRuntime.jsxs(
1344
1564
  "div",
1345
1565
  {
1346
- ...productCardSlotContract.root,
1566
+ ...productCardComponentContract.root,
1347
1567
  "data-slug": product.slug,
1348
1568
  className: `group flex flex-col overflow-hidden transition-all duration-300 ${cardStyle === "editorial" ? "hover:-translate-y-1" : "border bg-card hover:-translate-y-1 hover:shadow-lg"}`,
1349
1569
  style: { borderRadius: "var(--sf-radius-card)" },
1350
1570
  children: [
1351
1571
  /* @__PURE__ */ jsxRuntime.jsxs(Link, { to: `/product/${product.slug}`, className: `relative block ${aspect} overflow-hidden bg-muted`, style: { borderRadius: cardStyle === "editorial" ? "var(--sf-radius-card)" : void 0 }, children: [
1352
1572
  image && /* @__PURE__ */ jsxRuntime.jsx(
1353
- "img",
1573
+ SmoothImage,
1354
1574
  {
1355
1575
  src: image.url,
1356
1576
  alt: image.altText ?? product.name,
@@ -1383,33 +1603,37 @@ function ProductCard({ product }) {
1383
1603
  Link,
1384
1604
  {
1385
1605
  to: `/product/${product.slug}`,
1386
- ...productCardSlotContract.name,
1606
+ ...productCardComponentContract.name,
1387
1607
  className: "font-medium leading-snug hover:underline",
1388
1608
  children: product.name
1389
1609
  }
1390
1610
  ),
1391
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mt-auto flex items-center justify-between pt-2", children: [
1392
- /* @__PURE__ */ jsxRuntime.jsx(
1611
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mt-auto flex items-center justify-between gap-3 pt-2", children: [
1612
+ purchaseMode ? /* @__PURE__ */ jsxRuntime.jsx(
1393
1613
  Price,
1394
1614
  {
1395
1615
  value: product.price,
1396
1616
  compareAt: product.compareAtPrice,
1397
- testId: productCardSlotContract.priceTestId
1617
+ testId: productCardComponentContract.priceTestId
1398
1618
  }
1399
- ),
1619
+ ) : /* @__PURE__ */ jsxRuntime.jsx("span", { "data-testid": productCardComponentContract.priceTestId, className: "text-sm text-muted-foreground", children: config.commerceMode === "enquiry" ? config.enquiry.label : "Ver detalhes" }),
1400
1620
  /* @__PURE__ */ jsxRuntime.jsx(
1401
1621
  "button",
1402
1622
  {
1403
1623
  type: "button",
1404
- ...productCardSlotContract.addButton,
1405
- disabled: soldOut,
1406
- "aria-label": hasOptions ? `Escolher op\xE7\xF5es de ${product.name}` : `Adicionar ${product.name} ao carrinho`,
1624
+ ...productCardComponentContract.addButton,
1625
+ disabled: purchaseMode && soldOut,
1626
+ "aria-label": purchaseMode ? hasOptions ? `Escolher op\xE7\xF5es de ${product.name}` : `Adicionar ${product.name} ao carrinho` : `${config.enquiry.label} ${product.name}`,
1407
1627
  onClick: () => {
1628
+ if (!purchaseMode) {
1629
+ navigateTo(`/product/${product.slug}`);
1630
+ return;
1631
+ }
1408
1632
  if (hasOptions) {
1409
1633
  navigateTo(`/product/${product.slug}`);
1410
1634
  return;
1411
1635
  }
1412
- addItem(product);
1636
+ actions.addToCart(product);
1413
1637
  },
1414
1638
  className: "rounded-full border bg-background/90 p-2.5 text-foreground shadow-sm transition-all duration-200 hover:scale-110 hover:bg-primary hover:text-primary-foreground hover:shadow disabled:cursor-not-allowed disabled:opacity-40 lg:translate-y-1 lg:opacity-0 lg:group-hover:translate-y-0 lg:group-hover:opacity-100 lg:disabled:opacity-0 lg:group-hover:disabled:opacity-40",
1415
1639
  children: /* @__PURE__ */ jsxRuntime.jsx(LucideIcons.ShoppingBag, { className: "h-4 w-4" })
@@ -1423,14 +1647,25 @@ function ProductCard({ product }) {
1423
1647
  }
1424
1648
  function ProductGrid({ products, loading }) {
1425
1649
  const config = useStorefrontConfig();
1426
- const ProductCardComponent = config.slots?.ProductCard ?? ProductCard;
1650
+ const actions = useStorefrontActions();
1651
+ const components = getStorefrontComponents(config);
1652
+ const ProductCardComponent = components.ProductCard ?? ProductCard;
1427
1653
  if (loading) {
1428
1654
  return /* @__PURE__ */ jsxRuntime.jsx("div", { "data-testid": TID.productGrid, className: "grid grid-cols-2 gap-5 md:grid-cols-3 xl:grid-cols-4", children: Array.from({ length: 8 }, (_, i) => /* @__PURE__ */ jsxRuntime.jsx("div", { className: "aspect-[3/4] animate-pulse rounded-xl bg-muted" }, i)) });
1429
1655
  }
1430
1656
  if (products.length === 0) {
1431
1657
  return /* @__PURE__ */ jsxRuntime.jsx("div", { "data-testid": TID.productGrid, className: "py-20 text-center text-muted-foreground", children: "Nenhum produto encontrado." });
1432
1658
  }
1433
- return /* @__PURE__ */ jsxRuntime.jsx("div", { "data-testid": TID.productGrid, className: "grid grid-cols-2 gap-5 md:grid-cols-3 xl:grid-cols-4", children: products.map((p) => /* @__PURE__ */ jsxRuntime.jsx(ProductCardComponent, { product: p }, p.id)) });
1659
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { "data-testid": TID.productGrid, className: "grid grid-cols-2 gap-5 md:grid-cols-3 xl:grid-cols-4", children: products.map((p) => /* @__PURE__ */ jsxRuntime.jsx(
1660
+ ProductCardComponent,
1661
+ {
1662
+ product: p,
1663
+ config,
1664
+ commerceMode: config.commerceMode,
1665
+ actions
1666
+ },
1667
+ p.id
1668
+ )) });
1434
1669
  }
1435
1670
  function FiltersPanel() {
1436
1671
  const { categories } = useCategories();
@@ -1535,10 +1770,10 @@ function FiltersPanel() {
1535
1770
  }
1536
1771
  function useStorefrontHead(input) {
1537
1772
  const { title, description } = input;
1538
- React18.useEffect(() => {
1773
+ React6.useEffect(() => {
1539
1774
  if (title) document.title = title;
1540
1775
  }, [title]);
1541
- React18.useEffect(() => {
1776
+ React6.useEffect(() => {
1542
1777
  if (!description) return;
1543
1778
  let tag = document.head.querySelector('meta[name="description"]');
1544
1779
  if (!tag) {
@@ -1557,9 +1792,9 @@ var SORT_MAP = {
1557
1792
  };
1558
1793
  var SORTS = ["newest", "price-asc", "price-desc", "name"];
1559
1794
  function useCatalogUrlSync() {
1560
- const hydrated = React18.useRef(false);
1795
+ const hydrated = React6.useRef(false);
1561
1796
  const catalog = useCatalogStore();
1562
- React18.useEffect(() => {
1797
+ React6.useEffect(() => {
1563
1798
  if (hydrated.current) return;
1564
1799
  hydrated.current = true;
1565
1800
  const q = window.location.hash.split("?")[1];
@@ -1574,7 +1809,7 @@ function useCatalogUrlSync() {
1574
1809
  if (p.has("min")) s.setPriceMin(Number(p.get("min")) || null);
1575
1810
  if (p.has("max")) s.setPriceMax(Number(p.get("max")) || null);
1576
1811
  }, []);
1577
- React18.useEffect(() => {
1812
+ React6.useEffect(() => {
1578
1813
  const p = new URLSearchParams();
1579
1814
  if (catalog.search) p.set("search", catalog.search);
1580
1815
  if (catalog.categoryId) p.set("category", catalog.categoryId);
@@ -1602,7 +1837,7 @@ function CatalogPage() {
1602
1837
  ...SORT_MAP[catalog.sort]
1603
1838
  };
1604
1839
  const { products, loading, error, reload } = useProducts(providerOpts);
1605
- const filtered = React18.useMemo(
1840
+ const filtered = React6.useMemo(
1606
1841
  () => products.filter((p) => {
1607
1842
  if (catalog.priceMin != null && p.price < catalog.priceMin) return false;
1608
1843
  if (catalog.priceMax != null && p.price > catalog.priceMax) return false;
@@ -1667,10 +1902,17 @@ function HeroSection({
1667
1902
  slides,
1668
1903
  height = "tall"
1669
1904
  }) {
1670
- const [index, setIndex] = React18.useState(0);
1671
- const interacted = React18.useRef(false);
1905
+ const [index, setIndex] = React6.useState(0);
1906
+ const [loadedSlides, setLoadedSlides] = React6.useState({});
1907
+ const config = useStorefrontConfig();
1908
+ const interacted = React6.useRef(false);
1672
1909
  const hClass = height === "tall" ? "h-[72vh] min-h-[440px]" : "h-[48vh] min-h-[340px]";
1673
- React18.useEffect(() => {
1910
+ const revealImages = config.imageLoading.mode !== "none";
1911
+ const revealStyle = (loaded) => revealImages ? {
1912
+ filter: config.imageLoading.blur && !loaded ? "blur(10px)" : "blur(0px)",
1913
+ transition: `opacity 700ms ease, filter ${config.imageLoading.durationMs}ms ${config.imageLoading.easing}`
1914
+ } : void 0;
1915
+ React6.useEffect(() => {
1674
1916
  if (variant !== "slider" || slides.length < 2 || prefersReducedMotion()) return;
1675
1917
  const timer = setInterval(() => {
1676
1918
  if (!interacted.current) setIndex((i) => (i + 1) % slides.length);
@@ -1690,7 +1932,7 @@ function HeroSection({
1690
1932
  className: "group relative block h-[42vh] min-h-[320px] overflow-hidden",
1691
1933
  children: [
1692
1934
  /* @__PURE__ */ jsxRuntime.jsx(
1693
- "img",
1935
+ SmoothImage,
1694
1936
  {
1695
1937
  src: slideImage(slide, 900, 700),
1696
1938
  alt: slide.title,
@@ -1722,7 +1964,11 @@ function HeroSection({
1722
1964
  src: slideImage(slide),
1723
1965
  alt: slide.title,
1724
1966
  "aria-hidden": i !== index,
1725
- className: `absolute inset-0 h-full w-full object-cover transition-opacity duration-700 ${i === index ? "animate-kenburns opacity-100" : "opacity-0"}`
1967
+ loading: i === 0 ? "eager" : "lazy",
1968
+ decoding: "async",
1969
+ onLoad: () => setLoadedSlides((current) => ({ ...current, [i]: true })),
1970
+ style: revealStyle(Boolean(loadedSlides[i])),
1971
+ className: `absolute inset-0 h-full w-full object-cover transition-opacity duration-700 ${i === index && (!revealImages || loadedSlides[i]) ? "animate-kenburns opacity-100" : "opacity-0"}`
1726
1972
  },
1727
1973
  i
1728
1974
  )),
@@ -1784,7 +2030,7 @@ function CategoryShowcase({ style, title }) {
1784
2030
  title && /* @__PURE__ */ jsxRuntime.jsx(Reveal, { children: /* @__PURE__ */ jsxRuntime.jsx("h2", { className: "sf-heading mb-8 text-center text-3xl font-bold", children: title }) }),
1785
2031
  /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex flex-wrap items-start justify-center gap-8", children: categories.map((c, i) => /* @__PURE__ */ jsxRuntime.jsx(Reveal, { delay: i * 80, children: /* @__PURE__ */ jsxRuntime.jsxs("button", { type: "button", onClick: () => go(c.id), className: "group flex w-28 flex-col items-center gap-3", children: [
1786
2032
  /* @__PURE__ */ jsxRuntime.jsx("span", { className: "block h-24 w-24 overflow-hidden rounded-full border-2 border-transparent shadow-sm transition-all duration-300 group-hover:scale-105 group-hover:border-primary group-hover:shadow-md", children: /* @__PURE__ */ jsxRuntime.jsx(
1787
- "img",
2033
+ SmoothImage,
1788
2034
  {
1789
2035
  src: c.imageUrl ?? bannerPlaceholder(c.name, 30 + i * 70, 70 + i * 70, 200, 200),
1790
2036
  alt: c.name,
@@ -1799,7 +2045,7 @@ function CategoryShowcase({ style, title }) {
1799
2045
  title && /* @__PURE__ */ jsxRuntime.jsx(Reveal, { children: /* @__PURE__ */ jsxRuntime.jsx("h2", { className: "sf-heading mb-8 text-center text-3xl font-bold", children: title }) }),
1800
2046
  /* @__PURE__ */ jsxRuntime.jsx("div", { className: "grid grid-cols-2 gap-4 lg:grid-cols-4", children: categories.map((c, i) => /* @__PURE__ */ jsxRuntime.jsx(Reveal, { delay: i * 90, children: /* @__PURE__ */ jsxRuntime.jsxs("button", { type: "button", onClick: () => go(c.id), className: "group relative block aspect-[4/5] w-full overflow-hidden", style: { borderRadius: "var(--sf-radius-card)" }, children: [
1801
2047
  /* @__PURE__ */ jsxRuntime.jsx(
1802
- "img",
2048
+ SmoothImage,
1803
2049
  {
1804
2050
  src: c.imageUrl ?? bannerPlaceholder(c.name, 30 + i * 70, 70 + i * 70, 480, 600),
1805
2051
  alt: c.name,
@@ -1821,11 +2067,13 @@ function ProductRail({
1821
2067
  limit = 4
1822
2068
  }) {
1823
2069
  const config = useStorefrontConfig();
1824
- const ProductCardComponent = config.slots?.ProductCard ?? ProductCard;
2070
+ const actions = useStorefrontActions();
2071
+ const components = getStorefrontComponents(config);
2072
+ const ProductCardComponent = components.ProductCard ?? ProductCard;
1825
2073
  const { products, loading } = useProducts(
1826
2074
  filter === "new" ? { status: "active", orderBy: "created_at", order: "desc" } : { status: "active" }
1827
2075
  );
1828
- const visible = React18.useMemo(() => {
2076
+ const visible = React6.useMemo(() => {
1829
2077
  let list = products;
1830
2078
  if (filter === "sale") list = list.filter((p) => p.compareAtPrice != null && p.compareAtPrice > p.price);
1831
2079
  return list.slice(0, limit);
@@ -1837,7 +2085,15 @@ function ProductRail({
1837
2085
  /* @__PURE__ */ jsxRuntime.jsx("h2", { className: "sf-heading text-3xl font-bold tracking-tight", children: title }),
1838
2086
  subtitle && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "mt-2 text-muted-foreground", children: subtitle })
1839
2087
  ] }),
1840
- loading ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "grid grid-cols-2 gap-5 md:grid-cols-4", children: Array.from({ length: limit }, (_, i) => /* @__PURE__ */ jsxRuntime.jsx("div", { className: "aspect-[3/4] animate-pulse bg-muted", style: { borderRadius: "var(--sf-radius-card)" } }, i)) }) : /* @__PURE__ */ jsxRuntime.jsx("div", { className: "grid grid-cols-2 gap-5 md:grid-cols-4", children: visible.map((p, i) => /* @__PURE__ */ jsxRuntime.jsx(Reveal, { delay: i * 90, children: /* @__PURE__ */ jsxRuntime.jsx(ProductCardComponent, { product: p }) }, p.id)) }),
2088
+ loading ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "grid grid-cols-2 gap-5 md:grid-cols-4", children: Array.from({ length: limit }, (_, i) => /* @__PURE__ */ jsxRuntime.jsx("div", { className: "aspect-[3/4] animate-pulse bg-muted", style: { borderRadius: "var(--sf-radius-card)" } }, i)) }) : /* @__PURE__ */ jsxRuntime.jsx("div", { className: "grid grid-cols-2 gap-5 md:grid-cols-4", children: visible.map((p, i) => /* @__PURE__ */ jsxRuntime.jsx(Reveal, { delay: i * 90, children: /* @__PURE__ */ jsxRuntime.jsx(
2089
+ ProductCardComponent,
2090
+ {
2091
+ product: p,
2092
+ config,
2093
+ commerceMode: config.commerceMode,
2094
+ actions
2095
+ }
2096
+ ) }, p.id)) }),
1841
2097
  /* @__PURE__ */ jsxRuntime.jsx(Reveal, { className: "mt-10 text-center", delay: 200, children: /* @__PURE__ */ jsxRuntime.jsx(
1842
2098
  Link,
1843
2099
  {
@@ -1849,6 +2105,201 @@ function ProductRail({
1849
2105
  ) })
1850
2106
  ] });
1851
2107
  }
2108
+ var heightClass = {
2109
+ medium: "h-[58vh] min-h-[420px]",
2110
+ tall: "h-[74vh] min-h-[520px]",
2111
+ screen: "min-h-[calc(100vh-64px)]"
2112
+ };
2113
+ var alignClass = {
2114
+ start: "items-start text-left",
2115
+ center: "items-center text-center",
2116
+ end: "items-end text-right"
2117
+ };
2118
+ function MediaCarousel({
2119
+ items,
2120
+ height = "tall",
2121
+ autoplayMs = 7e3,
2122
+ overlay = "dark",
2123
+ className = ""
2124
+ }) {
2125
+ const [index, setIndex] = React6.useState(0);
2126
+ const [loadedItems, setLoadedItems] = React6.useState({});
2127
+ const config = useStorefrontConfig();
2128
+ const interacted = React6.useRef(false);
2129
+ const revealImages = config.imageLoading.mode !== "none";
2130
+ const revealStyle = (loaded) => revealImages ? {
2131
+ filter: config.imageLoading.blur && !loaded ? "blur(10px)" : "blur(0px)",
2132
+ transition: `opacity 700ms ease, filter ${config.imageLoading.durationMs}ms ${config.imageLoading.easing}`
2133
+ } : void 0;
2134
+ React6.useEffect(() => {
2135
+ if (items.length < 2 || autoplayMs <= 0 || prefersReducedMotion()) return;
2136
+ const timer = setInterval(() => {
2137
+ if (!interacted.current) setIndex((i) => (i + 1) % items.length);
2138
+ }, autoplayMs);
2139
+ return () => clearInterval(timer);
2140
+ }, [autoplayMs, items.length]);
2141
+ if (items.length === 0) return null;
2142
+ const active = items[Math.min(index, items.length - 1)];
2143
+ const goTo = (next) => {
2144
+ interacted.current = true;
2145
+ setIndex((next + items.length) % items.length);
2146
+ };
2147
+ const overlayClass = overlay === "dark" ? "bg-gradient-to-r from-black/70 via-black/25 to-black/25" : overlay === "soft" ? "bg-gradient-to-t from-black/45 via-black/10 to-transparent" : "";
2148
+ return /* @__PURE__ */ jsxRuntime.jsxs("section", { "data-testid": TID.mediaCarousel, className: `relative isolate overflow-hidden ${heightClass[height]} ${className}`, children: [
2149
+ items.map((item, i) => /* @__PURE__ */ jsxRuntime.jsx(
2150
+ "img",
2151
+ {
2152
+ src: item.image,
2153
+ alt: item.imageAlt ?? item.title,
2154
+ "aria-hidden": i !== index,
2155
+ loading: i === 0 ? "eager" : "lazy",
2156
+ decoding: "async",
2157
+ onLoad: () => setLoadedItems((current) => ({ ...current, [i]: true })),
2158
+ style: revealStyle(Boolean(loadedItems[i])),
2159
+ className: `absolute inset-0 -z-10 h-full w-full object-cover transition-opacity duration-700 ${i === index && (!revealImages || loadedItems[i]) ? "opacity-100" : "opacity-0"}`
2160
+ },
2161
+ `${item.title}-${i}`
2162
+ )),
2163
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: `absolute inset-0 -z-10 ${overlayClass}` }),
2164
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: `flex h-full px-5 py-16 sm:px-10 lg:px-16 ${alignClass[active.align ?? "start"]}`, children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "my-auto max-w-2xl text-white drop-shadow-md", children: [
2165
+ active.eyebrow && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "mb-4 text-xs font-semibold uppercase tracking-[0.24em] text-white/80", children: active.eyebrow }),
2166
+ /* @__PURE__ */ jsxRuntime.jsx("h1", { className: "sf-heading text-5xl font-normal leading-[0.95] sm:text-7xl lg:text-8xl", children: active.title }),
2167
+ active.subtitle && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "mt-5 max-w-xl text-base leading-8 text-white/88 sm:text-lg", children: active.subtitle }),
2168
+ active.cta && /* @__PURE__ */ jsxRuntime.jsx(
2169
+ Link,
2170
+ {
2171
+ to: active.href ?? "/catalog",
2172
+ className: "mt-8 inline-flex border border-white/75 px-6 py-3 text-xs font-semibold uppercase tracking-[0.18em] text-white transition hover:bg-white hover:text-black",
2173
+ children: active.cta
2174
+ }
2175
+ )
2176
+ ] }) }),
2177
+ items.length > 1 && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
2178
+ /* @__PURE__ */ jsxRuntime.jsx(
2179
+ "button",
2180
+ {
2181
+ type: "button",
2182
+ "aria-label": "Previous slide",
2183
+ "data-testid": TID.mediaCarouselPrev,
2184
+ onClick: () => goTo(index - 1),
2185
+ className: "absolute left-4 top-1/2 inline-flex h-10 w-10 -translate-y-1/2 items-center justify-center border border-white/60 bg-black/20 text-white backdrop-blur transition hover:bg-white hover:text-black",
2186
+ children: /* @__PURE__ */ jsxRuntime.jsx(LucideIcons.ChevronLeft, { className: "h-5 w-5" })
2187
+ }
2188
+ ),
2189
+ /* @__PURE__ */ jsxRuntime.jsx(
2190
+ "button",
2191
+ {
2192
+ type: "button",
2193
+ "aria-label": "Next slide",
2194
+ "data-testid": TID.mediaCarouselNext,
2195
+ onClick: () => goTo(index + 1),
2196
+ className: "absolute right-4 top-1/2 inline-flex h-10 w-10 -translate-y-1/2 items-center justify-center border border-white/60 bg-black/20 text-white backdrop-blur transition hover:bg-white hover:text-black",
2197
+ children: /* @__PURE__ */ jsxRuntime.jsx(LucideIcons.ChevronRight, { className: "h-5 w-5" })
2198
+ }
2199
+ ),
2200
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "absolute bottom-5 left-1/2 flex -translate-x-1/2 gap-2", children: items.map((item, i) => /* @__PURE__ */ jsxRuntime.jsx(
2201
+ "button",
2202
+ {
2203
+ type: "button",
2204
+ "aria-label": `Go to slide ${i + 1}`,
2205
+ onClick: () => goTo(i),
2206
+ className: `h-2 rounded-full transition-all ${i === index ? "w-8 bg-white" : "w-2 bg-white/55 hover:bg-white"}`
2207
+ },
2208
+ `${item.title}-dot-${i}`
2209
+ )) })
2210
+ ] })
2211
+ ] });
2212
+ }
2213
+ function ProductSlider({
2214
+ title,
2215
+ subtitle,
2216
+ eyebrow,
2217
+ filter = "all",
2218
+ categoryName,
2219
+ collection,
2220
+ limit = 10,
2221
+ cta = "View all",
2222
+ href,
2223
+ className = ""
2224
+ }) {
2225
+ const config = useStorefrontConfig();
2226
+ const actions = useStorefrontActions();
2227
+ const viewportRef = React6.useRef(null);
2228
+ const components = getStorefrontComponents(config);
2229
+ const ProductCardComponent = components.ProductCard ?? ProductCard;
2230
+ const { products, loading } = useProducts(
2231
+ filter === "new" ? { status: "active", orderBy: "created_at", order: "desc" } : { status: "active" }
2232
+ );
2233
+ const visible = React6.useMemo(() => {
2234
+ let list = products;
2235
+ if (filter === "sale") list = list.filter((p) => p.compareAtPrice != null && p.compareAtPrice > p.price);
2236
+ if (categoryName) list = list.filter((p) => p.categoryName === categoryName);
2237
+ if (collection) list = list.filter((p) => p.metadata?.collection === collection);
2238
+ return list.slice(0, limit);
2239
+ }, [categoryName, collection, filter, limit, products]);
2240
+ const scroll = (direction) => {
2241
+ viewportRef.current?.scrollBy({ left: direction * Math.max(320, viewportRef.current.clientWidth * 0.75), behavior: "smooth" });
2242
+ };
2243
+ if (!loading && visible.length === 0) return null;
2244
+ return /* @__PURE__ */ jsxRuntime.jsxs("section", { "data-testid": TID.productSlider, className: `mx-auto max-w-7xl px-4 py-14 sm:px-6 ${className}`, children: [
2245
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mb-8 flex flex-col gap-5 sm:flex-row sm:items-end sm:justify-between", children: [
2246
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "max-w-2xl", children: [
2247
+ eyebrow && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "mb-2 text-xs font-semibold uppercase tracking-[0.2em] text-primary", children: eyebrow }),
2248
+ /* @__PURE__ */ jsxRuntime.jsx("h2", { className: "sf-heading text-3xl font-normal leading-tight tracking-normal sm:text-5xl", children: title }),
2249
+ subtitle && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "mt-3 leading-7 text-muted-foreground", children: subtitle })
2250
+ ] }),
2251
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2", children: [
2252
+ /* @__PURE__ */ jsxRuntime.jsx(
2253
+ "button",
2254
+ {
2255
+ type: "button",
2256
+ "aria-label": "Previous products",
2257
+ "data-testid": TID.productSliderPrev,
2258
+ onClick: () => scroll(-1),
2259
+ className: "inline-flex h-10 w-10 items-center justify-center border border-border bg-background transition hover:bg-foreground hover:text-background",
2260
+ children: /* @__PURE__ */ jsxRuntime.jsx(LucideIcons.ChevronLeft, { className: "h-5 w-5" })
2261
+ }
2262
+ ),
2263
+ /* @__PURE__ */ jsxRuntime.jsx(
2264
+ "button",
2265
+ {
2266
+ type: "button",
2267
+ "aria-label": "Next products",
2268
+ "data-testid": TID.productSliderNext,
2269
+ onClick: () => scroll(1),
2270
+ className: "inline-flex h-10 w-10 items-center justify-center border border-border bg-background transition hover:bg-foreground hover:text-background",
2271
+ children: /* @__PURE__ */ jsxRuntime.jsx(LucideIcons.ChevronRight, { className: "h-5 w-5" })
2272
+ }
2273
+ )
2274
+ ] })
2275
+ ] }),
2276
+ /* @__PURE__ */ jsxRuntime.jsx(
2277
+ "div",
2278
+ {
2279
+ ref: viewportRef,
2280
+ "data-testid": TID.productSliderViewport,
2281
+ className: "flex snap-x gap-5 overflow-x-auto pb-5 [scrollbar-width:thin]",
2282
+ children: loading ? Array.from({ length: Math.min(limit, 6) }, (_, i) => /* @__PURE__ */ jsxRuntime.jsx("div", { className: "h-[360px] min-w-[260px] snap-start animate-pulse bg-muted" }, i)) : visible.map((product) => /* @__PURE__ */ jsxRuntime.jsx("div", { className: "min-w-[260px] max-w-[300px] flex-[0_0_72vw] snap-start sm:flex-[0_0_300px]", children: /* @__PURE__ */ jsxRuntime.jsx(
2283
+ ProductCardComponent,
2284
+ {
2285
+ product,
2286
+ config,
2287
+ commerceMode: config.commerceMode,
2288
+ actions
2289
+ }
2290
+ ) }, product.id))
2291
+ }
2292
+ ),
2293
+ cta && /* @__PURE__ */ jsxRuntime.jsx(
2294
+ Link,
2295
+ {
2296
+ to: href ?? config.catalogPath,
2297
+ className: "mt-4 inline-flex border border-foreground/30 px-6 py-3 text-xs font-semibold uppercase tracking-[0.16em] transition hover:bg-foreground hover:text-background",
2298
+ children: cta
2299
+ }
2300
+ )
2301
+ ] });
2302
+ }
1852
2303
  function Icon({ name, className }) {
1853
2304
  const C = LucideIcons__namespace[name];
1854
2305
  return C ? /* @__PURE__ */ jsxRuntime.jsx(C, { className }) : null;
@@ -1873,7 +2324,7 @@ function PromoBanner({
1873
2324
  }) {
1874
2325
  return /* @__PURE__ */ jsxRuntime.jsxs("section", { "data-testid": TID.promoBanner, className: "group relative my-12 overflow-hidden", children: [
1875
2326
  /* @__PURE__ */ jsxRuntime.jsx(
1876
- "img",
2327
+ SmoothImage,
1877
2328
  {
1878
2329
  src: image ?? bannerPlaceholder(title, hue, hue + 50, 1600, 420),
1879
2330
  alt: title,
@@ -1927,8 +2378,8 @@ function Testimonials({ title, items }) {
1927
2378
  ] }) });
1928
2379
  }
1929
2380
  function NewsletterBand({ title, subtitle }) {
1930
- const [email, setEmail] = React18.useState("");
1931
- const [done, setDone] = React18.useState(false);
2381
+ const [email, setEmail] = React6.useState("");
2382
+ const [done, setDone] = React6.useState(false);
1932
2383
  return /* @__PURE__ */ jsxRuntime.jsx("section", { "data-testid": TID.newsletter, className: "bg-primary py-16 text-primary-foreground", children: /* @__PURE__ */ jsxRuntime.jsxs(Reveal, { className: "mx-auto max-w-xl px-4 text-center sm:px-6", children: [
1933
2384
  /* @__PURE__ */ jsxRuntime.jsx("h2", { className: "sf-heading text-3xl font-bold", children: title ?? "Receba nossas novidades" }),
1934
2385
  subtitle && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "mt-2 opacity-90", children: subtitle }),
@@ -1976,8 +2427,10 @@ function registerStorefrontBlocks() {
1976
2427
  if (registered) return;
1977
2428
  registered = true;
1978
2429
  core.registerBlock("hero", HeroSection, { source: "sdk", label: "Hero" });
2430
+ core.registerBlock("mediaCarousel", MediaCarousel, { source: "sdk", label: "Media carousel" });
1979
2431
  core.registerBlock("categories", CategoryShowcase, { source: "sdk", label: "Categories" });
1980
2432
  core.registerBlock("products", ProductRail, { source: "sdk", label: "Product rail" });
2433
+ core.registerBlock("productSlider", ProductSlider, { source: "sdk", label: "Product slider" });
1981
2434
  core.registerBlock("benefits", BenefitsRow, { source: "sdk", label: "Benefits row" });
1982
2435
  core.registerBlock("banner", PromoBanner, { source: "sdk", label: "Promo banner" });
1983
2436
  core.registerBlock("manifesto", ManifestoBlock, { source: "sdk", label: "Manifesto" });
@@ -1991,14 +2444,17 @@ function sectionsToBlocks(sections) {
1991
2444
  return { type, id: `${type}-${i}`, props };
1992
2445
  });
1993
2446
  }
1994
- function HomePage({ sections }) {
2447
+ function StorefrontSections({ sections }) {
1995
2448
  return /* @__PURE__ */ jsxRuntime.jsx("main", { children: core.renderBlocks(sectionsToBlocks(sections)) });
1996
2449
  }
2450
+ function HomePage({ sections }) {
2451
+ return /* @__PURE__ */ jsxRuntime.jsx(StorefrontSections, { sections });
2452
+ }
1997
2453
  function useProduct(slug2) {
1998
- const [product, setProduct] = React18.useState(null);
1999
- const [loading, setLoading] = React18.useState(true);
2000
- const [error, setError] = React18.useState(null);
2001
- React18.useEffect(() => {
2454
+ const [product, setProduct] = React6.useState(null);
2455
+ const [loading, setLoading] = React6.useState(true);
2456
+ const [error, setError] = React6.useState(null);
2457
+ React6.useEffect(() => {
2002
2458
  let cancelled = false;
2003
2459
  setLoading(true);
2004
2460
  runtime.getShopProvider().listProducts({ slug: slug2, status: "active", limit: 1 }).then((data) => {
@@ -2105,28 +2561,218 @@ function ProductSpecs({ product }) {
2105
2561
  }
2106
2562
  function RelatedProducts({ product }) {
2107
2563
  const config = useStorefrontConfig();
2108
- const Card = config.slots?.ProductCard ?? ProductCard;
2564
+ const actions = useStorefrontActions();
2565
+ const components = getStorefrontComponents(config);
2566
+ const Card = components.ProductCard ?? ProductCard;
2109
2567
  const opts = product.categoryId ? { categoryId: product.categoryId, status: "active", limit: 5 } : { status: "active", limit: 5 };
2110
2568
  const { products, loading } = useProducts(opts);
2111
2569
  const related = products.filter((p) => p.id !== product.id).slice(0, 4);
2112
2570
  if (loading || related.length === 0) return null;
2113
2571
  return /* @__PURE__ */ jsxRuntime.jsxs("section", { className: "mt-16", children: [
2114
2572
  /* @__PURE__ */ jsxRuntime.jsx("h2", { className: "sf-heading text-xl font-semibold tracking-tight", children: "Voc\xEA tamb\xE9m pode gostar" }),
2115
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "mt-6 grid grid-cols-2 gap-5 md:grid-cols-4", children: related.map((p) => /* @__PURE__ */ jsxRuntime.jsx(Card, { product: p }, p.id)) })
2573
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "mt-6 grid grid-cols-2 gap-5 md:grid-cols-4", children: related.map((p) => /* @__PURE__ */ jsxRuntime.jsx(
2574
+ Card,
2575
+ {
2576
+ product: p,
2577
+ config,
2578
+ commerceMode: config.commerceMode,
2579
+ actions
2580
+ },
2581
+ p.id
2582
+ )) })
2116
2583
  ] });
2117
2584
  }
2585
+ function defaultSubject(prefix, product) {
2586
+ return `${prefix}: ${product.name}`;
2587
+ }
2588
+ function buildWhatsAppHref(url, product) {
2589
+ const text = encodeURIComponent(`Hello, I would like more information about ${product.name}.`);
2590
+ const sep = url.includes("?") ? "&" : "?";
2591
+ return `${url}${sep}text=${text}`;
2592
+ }
2593
+ function ProductEnquiryForm({ product, onSuccess }) {
2594
+ const config = useStorefrontConfig();
2595
+ const [customerName, setCustomerName] = React6.useState("");
2596
+ const [customerEmail, setCustomerEmail] = React6.useState("");
2597
+ const [customerPhone, setCustomerPhone] = React6.useState("");
2598
+ const [subject, setSubject] = React6.useState(defaultSubject(config.enquiry.subjectPrefix, product));
2599
+ const [message, setMessage] = React6.useState(`I would like more information about ${product.name}.`);
2600
+ const [status, setStatus] = React6.useState("idle");
2601
+ const [error, setError] = React6.useState(null);
2602
+ const submit = async (event) => {
2603
+ event.preventDefault();
2604
+ setStatus("submitting");
2605
+ setError(null);
2606
+ try {
2607
+ const provider = runtime.getShopProvider();
2608
+ if (provider.createProductEnquiry) {
2609
+ await provider.createProductEnquiry({
2610
+ productId: product.id,
2611
+ productName: product.name,
2612
+ productSlug: product.slug,
2613
+ customerName,
2614
+ customerEmail,
2615
+ customerPhone: customerPhone || void 0,
2616
+ subject,
2617
+ message,
2618
+ sourceUrl: typeof window === "undefined" ? void 0 : window.location.href,
2619
+ metadata: { sku: product.sku, categoryName: product.categoryName }
2620
+ });
2621
+ } else if (config.enquiry.email) {
2622
+ window.location.href = `mailto:${config.enquiry.email}?subject=${encodeURIComponent(subject)}&body=${encodeURIComponent(message)}`;
2623
+ }
2624
+ setStatus("success");
2625
+ onSuccess?.();
2626
+ } catch (e) {
2627
+ setStatus("error");
2628
+ setError(e instanceof Error ? e.message : String(e));
2629
+ }
2630
+ };
2631
+ return /* @__PURE__ */ jsxRuntime.jsxs(
2632
+ "form",
2633
+ {
2634
+ ...storefrontComponentContracts.productDetail.enquiryForm,
2635
+ onSubmit: submit,
2636
+ className: "space-y-4",
2637
+ children: [
2638
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "grid gap-4 sm:grid-cols-2", children: [
2639
+ /* @__PURE__ */ jsxRuntime.jsxs("label", { className: "space-y-1.5 text-sm font-medium", children: [
2640
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: "Your name" }),
2641
+ /* @__PURE__ */ jsxRuntime.jsx(
2642
+ "input",
2643
+ {
2644
+ "data-testid": TID.enquiryName,
2645
+ required: true,
2646
+ value: customerName,
2647
+ onChange: (e) => setCustomerName(e.target.value),
2648
+ className: "w-full border bg-background px-3 py-2 outline-none focus:border-primary",
2649
+ style: { borderRadius: "var(--sf-radius-input)" }
2650
+ }
2651
+ )
2652
+ ] }),
2653
+ /* @__PURE__ */ jsxRuntime.jsxs("label", { className: "space-y-1.5 text-sm font-medium", children: [
2654
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: "Your email" }),
2655
+ /* @__PURE__ */ jsxRuntime.jsx(
2656
+ "input",
2657
+ {
2658
+ "data-testid": TID.enquiryEmail,
2659
+ required: true,
2660
+ type: "email",
2661
+ value: customerEmail,
2662
+ onChange: (e) => setCustomerEmail(e.target.value),
2663
+ className: "w-full border bg-background px-3 py-2 outline-none focus:border-primary",
2664
+ style: { borderRadius: "var(--sf-radius-input)" }
2665
+ }
2666
+ )
2667
+ ] })
2668
+ ] }),
2669
+ /* @__PURE__ */ jsxRuntime.jsxs("label", { className: "block space-y-1.5 text-sm font-medium", children: [
2670
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { children: [
2671
+ "Phone",
2672
+ config.enquiry.requirePhone ? "" : " (optional)"
2673
+ ] }),
2674
+ /* @__PURE__ */ jsxRuntime.jsx(
2675
+ "input",
2676
+ {
2677
+ "data-testid": TID.enquiryPhone,
2678
+ required: config.enquiry.requirePhone,
2679
+ value: customerPhone,
2680
+ onChange: (e) => setCustomerPhone(e.target.value),
2681
+ className: "w-full border bg-background px-3 py-2 outline-none focus:border-primary",
2682
+ style: { borderRadius: "var(--sf-radius-input)" }
2683
+ }
2684
+ )
2685
+ ] }),
2686
+ /* @__PURE__ */ jsxRuntime.jsxs("label", { className: "block space-y-1.5 text-sm font-medium", children: [
2687
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: "Subject" }),
2688
+ /* @__PURE__ */ jsxRuntime.jsx(
2689
+ "input",
2690
+ {
2691
+ "data-testid": TID.enquirySubject,
2692
+ required: true,
2693
+ value: subject,
2694
+ onChange: (e) => setSubject(e.target.value),
2695
+ className: "w-full border bg-background px-3 py-2 outline-none focus:border-primary",
2696
+ style: { borderRadius: "var(--sf-radius-input)" }
2697
+ }
2698
+ )
2699
+ ] }),
2700
+ /* @__PURE__ */ jsxRuntime.jsxs("label", { className: "block space-y-1.5 text-sm font-medium", children: [
2701
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: "Message" }),
2702
+ /* @__PURE__ */ jsxRuntime.jsx(
2703
+ "textarea",
2704
+ {
2705
+ "data-testid": TID.enquiryMessage,
2706
+ required: true,
2707
+ rows: 5,
2708
+ value: message,
2709
+ onChange: (e) => setMessage(e.target.value),
2710
+ className: "w-full resize-none border bg-background px-3 py-2 outline-none focus:border-primary",
2711
+ style: { borderRadius: "var(--sf-radius-input)" }
2712
+ }
2713
+ )
2714
+ ] }),
2715
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-wrap items-center gap-3", children: [
2716
+ /* @__PURE__ */ jsxRuntime.jsxs(
2717
+ "button",
2718
+ {
2719
+ "data-testid": TID.enquirySubmit,
2720
+ type: "submit",
2721
+ disabled: status === "submitting",
2722
+ className: "sf-cta inline-flex items-center gap-2 bg-primary px-5 py-3 font-semibold text-primary-foreground disabled:cursor-wait disabled:opacity-60",
2723
+ style: { borderRadius: "var(--sf-radius-button)" },
2724
+ children: [
2725
+ /* @__PURE__ */ jsxRuntime.jsx(LucideIcons.Send, { className: "h-4 w-4" }),
2726
+ status === "submitting" ? "Sending..." : "Send enquiry"
2727
+ ]
2728
+ }
2729
+ ),
2730
+ config.enquiry.whatsappUrl && /* @__PURE__ */ jsxRuntime.jsxs(
2731
+ "a",
2732
+ {
2733
+ href: buildWhatsAppHref(config.enquiry.whatsappUrl, product),
2734
+ target: "_blank",
2735
+ rel: "noreferrer",
2736
+ className: "inline-flex items-center gap-2 border px-5 py-3 text-sm font-semibold transition-colors hover:bg-muted",
2737
+ style: { borderRadius: "var(--sf-radius-button)" },
2738
+ children: [
2739
+ /* @__PURE__ */ jsxRuntime.jsx(LucideIcons.MessageCircle, { className: "h-4 w-4" }),
2740
+ "WhatsApp"
2741
+ ]
2742
+ }
2743
+ ),
2744
+ config.enquiry.email && /* @__PURE__ */ jsxRuntime.jsxs(
2745
+ "a",
2746
+ {
2747
+ href: `mailto:${config.enquiry.email}?subject=${encodeURIComponent(subject)}&body=${encodeURIComponent(message)}`,
2748
+ className: "inline-flex items-center gap-2 border px-5 py-3 text-sm font-semibold transition-colors hover:bg-muted",
2749
+ style: { borderRadius: "var(--sf-radius-button)" },
2750
+ children: [
2751
+ /* @__PURE__ */ jsxRuntime.jsx(LucideIcons.Mail, { className: "h-4 w-4" }),
2752
+ "Email"
2753
+ ]
2754
+ }
2755
+ )
2756
+ ] }),
2757
+ status === "success" && /* @__PURE__ */ jsxRuntime.jsx("p", { "data-testid": TID.enquirySuccess, className: "text-sm font-medium text-emerald-600", children: config.enquiry.successMessage }),
2758
+ error && /* @__PURE__ */ jsxRuntime.jsx("p", { "data-testid": TID.enquiryError, className: "text-sm font-medium text-red-600", children: error })
2759
+ ]
2760
+ }
2761
+ );
2762
+ }
2118
2763
  function ProductDetailPage({ slug: slug2 }) {
2119
2764
  const config = useStorefrontConfig();
2765
+ const actions = useStorefrontActions();
2120
2766
  const { product, loading } = useProduct(slug2);
2121
2767
  const addItem = useCartStore((s) => s.addItem);
2122
- const [qty, setQty] = React18.useState(1);
2123
- const [selectedOptions, setSelectedOptions] = React18.useState({});
2768
+ const [qty, setQty] = React6.useState(1);
2769
+ const [selectedOptions, setSelectedOptions] = React6.useState({});
2124
2770
  const optionGroups = product ? getProductOptionGroups(product) : [];
2125
2771
  useStorefrontHead({
2126
2772
  title: product ? `${product.name} \u2014 ${config.name}` : config.name,
2127
2773
  description: product?.description
2128
2774
  });
2129
- React18.useEffect(() => {
2775
+ React6.useEffect(() => {
2130
2776
  if (!product) return;
2131
2777
  const defaults = Object.fromEntries(
2132
2778
  getProductOptionGroups(product).map((group) => [group.label, group.values[0] ?? ""])
@@ -2152,21 +2798,56 @@ function ProductDetailPage({ slug: slug2 }) {
2152
2798
  const soldOut = product.inventoryCount <= 0;
2153
2799
  const lowStock = !soldOut && product.inventoryCount <= 5;
2154
2800
  const image = product.images.find((i) => i.isPrimary) ?? product.images[0];
2801
+ const components = getStorefrontComponents(config);
2802
+ const ProductDetailComponent = components.ProductDetail;
2803
+ const addToCart = () => addItem(product, qty, selectedOptions);
2804
+ const openEnquiry = () => {
2805
+ document.getElementById("storefront-product-enquiry")?.scrollIntoView({ behavior: "smooth", block: "start" });
2806
+ };
2155
2807
  const discountPct = product.compareAtPrice && product.compareAtPrice > product.price ? Math.round((1 - product.price / product.compareAtPrice) * 100) : 0;
2156
- return /* @__PURE__ */ jsxRuntime.jsxs("main", { className: "mx-auto max-w-5xl px-4 py-8 sm:px-6", children: [
2808
+ if (ProductDetailComponent) {
2809
+ return /* @__PURE__ */ jsxRuntime.jsx(
2810
+ ProductDetailComponent,
2811
+ {
2812
+ product,
2813
+ config,
2814
+ commerceMode: config.commerceMode,
2815
+ primaryImage: image,
2816
+ optionGroups,
2817
+ selectedOptions,
2818
+ setSelectedOptions,
2819
+ quantity: qty,
2820
+ setQuantity: setQty,
2821
+ soldOut,
2822
+ lowStock,
2823
+ actions,
2824
+ addToCart,
2825
+ openEnquiry
2826
+ }
2827
+ );
2828
+ }
2829
+ return /* @__PURE__ */ jsxRuntime.jsxs("main", { ...storefrontComponentContracts.productDetail.root, className: "mx-auto max-w-5xl px-4 py-8 sm:px-6", children: [
2157
2830
  /* @__PURE__ */ jsxRuntime.jsxs(Link, { to: "/", className: "mb-6 inline-flex items-center gap-1 text-sm text-muted-foreground hover:text-foreground", children: [
2158
2831
  /* @__PURE__ */ jsxRuntime.jsx(LucideIcons.ChevronLeft, { className: "h-4 w-4" }),
2159
2832
  " Continuar comprando"
2160
2833
  ] }),
2161
2834
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "grid animate-fade-up gap-10 md:grid-cols-2", children: [
2162
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "group overflow-hidden border bg-muted", style: { borderRadius: "var(--sf-radius-card)" }, children: image && /* @__PURE__ */ jsxRuntime.jsx(
2163
- "img",
2835
+ /* @__PURE__ */ jsxRuntime.jsx(
2836
+ "div",
2164
2837
  {
2165
- src: image.url,
2166
- alt: image.altText ?? product.name,
2167
- className: "aspect-square w-full object-cover transition-transform duration-700 ease-out group-hover:scale-105"
2838
+ ...storefrontComponentContracts.productDetail.gallery,
2839
+ className: "group overflow-hidden border bg-muted",
2840
+ style: { borderRadius: "var(--sf-radius-card)" },
2841
+ children: image && /* @__PURE__ */ jsxRuntime.jsx(
2842
+ SmoothImage,
2843
+ {
2844
+ src: image.url,
2845
+ alt: image.altText ?? product.name,
2846
+ className: "aspect-square w-full object-cover transition-transform duration-700 ease-out group-hover:scale-105"
2847
+ }
2848
+ )
2168
2849
  }
2169
- ) }),
2850
+ ),
2170
2851
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col py-2 lg:sticky lg:top-24 lg:self-start", children: [
2171
2852
  /* @__PURE__ */ jsxRuntime.jsxs("nav", { className: "mb-3 flex flex-wrap items-center gap-1.5 text-xs text-muted-foreground", children: [
2172
2853
  /* @__PURE__ */ jsxRuntime.jsx(Link, { to: "/", className: "transition-colors hover:text-foreground", children: "In\xEDcio" }),
@@ -2178,7 +2859,7 @@ function ProductDetailPage({ slug: slug2 }) {
2178
2859
  /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-foreground", children: product.name })
2179
2860
  ] }),
2180
2861
  /* @__PURE__ */ jsxRuntime.jsx("h1", { "data-testid": TID.pdpName, className: "sf-heading text-3xl font-bold tracking-tight", children: product.name }),
2181
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mt-3 flex items-center gap-3", children: [
2862
+ config.commerceMode === "checkout" && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mt-3 flex items-center gap-3", children: [
2182
2863
  /* @__PURE__ */ jsxRuntime.jsx("div", { className: "text-xl", children: /* @__PURE__ */ jsxRuntime.jsx(
2183
2864
  Price,
2184
2865
  {
@@ -2195,7 +2876,7 @@ function ProductDetailPage({ slug: slug2 }) {
2195
2876
  ] })
2196
2877
  ] }),
2197
2878
  /* @__PURE__ */ jsxRuntime.jsx("p", { "data-testid": TID.pdpDescription, className: "mt-5 leading-relaxed text-muted-foreground", children: product.description }),
2198
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mt-4 flex flex-wrap items-center gap-x-3 gap-y-1 text-sm", children: [
2879
+ config.commerceMode === "checkout" && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mt-4 flex flex-wrap items-center gap-x-3 gap-y-1 text-sm", children: [
2199
2880
  soldOut ? /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "inline-flex items-center gap-2 font-medium text-muted-foreground", children: [
2200
2881
  /* @__PURE__ */ jsxRuntime.jsx("span", { className: "h-2 w-2 rounded-full bg-muted-foreground" }),
2201
2882
  " Sem estoque"
@@ -2221,7 +2902,7 @@ function ProductDetailPage({ slug: slug2 }) {
2221
2902
  onChange: setSelectedOptions
2222
2903
  }
2223
2904
  ),
2224
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mt-8 flex items-center gap-4", children: [
2905
+ /* @__PURE__ */ jsxRuntime.jsx("div", { ...storefrontComponentContracts.productDetail.actions, className: "mt-8", children: config.commerceMode === "checkout" ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-4", children: [
2225
2906
  /* @__PURE__ */ jsxRuntime.jsx(
2226
2907
  QuantityInput,
2227
2908
  {
@@ -2237,14 +2918,32 @@ function ProductDetailPage({ slug: slug2 }) {
2237
2918
  type: "button",
2238
2919
  "data-testid": TID.pdpAddToCart,
2239
2920
  disabled: soldOut,
2240
- onClick: () => addItem(product, qty, selectedOptions),
2921
+ onClick: addToCart,
2241
2922
  className: "sf-cta flex-1 bg-primary py-3.5 font-semibold text-primary-foreground shadow-md transition-all hover:-translate-y-0.5 hover:shadow-lg disabled:cursor-not-allowed disabled:opacity-40 disabled:shadow-none disabled:hover:translate-y-0",
2242
2923
  style: { borderRadius: "var(--sf-radius-button)" },
2243
2924
  children: soldOut ? "Esgotado" : "Adicionar ao carrinho"
2244
2925
  }
2245
2926
  )
2246
- ] }),
2247
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mt-6 grid grid-cols-3 gap-3 border-t pt-6 text-center", children: [
2927
+ ] }) : config.commerceMode === "enquiry" ? /* @__PURE__ */ jsxRuntime.jsx(
2928
+ "button",
2929
+ {
2930
+ type: "button",
2931
+ ...storefrontComponentContracts.productDetail.enquiryButton,
2932
+ onClick: openEnquiry,
2933
+ className: "sf-cta w-full bg-primary py-3.5 font-semibold text-primary-foreground shadow-md transition-all hover:-translate-y-0.5 hover:shadow-lg",
2934
+ style: { borderRadius: "var(--sf-radius-button)" },
2935
+ children: config.enquiry.label
2936
+ }
2937
+ ) : /* @__PURE__ */ jsxRuntime.jsx(
2938
+ Link,
2939
+ {
2940
+ to: config.catalogPath,
2941
+ className: "inline-flex border px-5 py-3 text-sm font-semibold transition-colors hover:bg-muted",
2942
+ style: { borderRadius: "var(--sf-radius-button)" },
2943
+ children: "Voltar ao cat\xE1logo"
2944
+ }
2945
+ ) }),
2946
+ config.commerceMode === "checkout" && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mt-6 grid grid-cols-3 gap-3 border-t pt-6 text-center", children: [
2248
2947
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col items-center gap-1.5 text-[11px] font-medium text-muted-foreground", children: [
2249
2948
  /* @__PURE__ */ jsxRuntime.jsx(LucideIcons.Truck, { className: "h-5 w-5 text-primary" }),
2250
2949
  " Envio r\xE1pido"
@@ -2260,6 +2959,11 @@ function ProductDetailPage({ slug: slug2 }) {
2260
2959
  ] })
2261
2960
  ] })
2262
2961
  ] }),
2962
+ config.commerceMode === "enquiry" && /* @__PURE__ */ jsxRuntime.jsx("section", { id: "storefront-product-enquiry", className: "mt-12 border-t pt-10", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mx-auto max-w-3xl", children: [
2963
+ /* @__PURE__ */ jsxRuntime.jsx("h2", { className: "sf-heading text-2xl font-semibold", children: "Need more information?" }),
2964
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "mt-2 text-muted-foreground", children: "Send an enquiry about this product." }),
2965
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "mt-6", children: /* @__PURE__ */ jsxRuntime.jsx(ProductEnquiryForm, { product }) })
2966
+ ] }) }),
2263
2967
  /* @__PURE__ */ jsxRuntime.jsx(ProductSpecs, { product }),
2264
2968
  /* @__PURE__ */ jsxRuntime.jsx(RelatedProducts, { product })
2265
2969
  ] });
@@ -2300,16 +3004,17 @@ async function placeStorefrontOrder({
2300
3004
  }))
2301
3005
  });
2302
3006
  if (markPaid) {
2303
- await provider.updateOrder(order.id, { financialStatus: "paid" });
3007
+ if (provider.confirmPayment) await provider.confirmPayment(order.id);
3008
+ else await provider.updateOrder(order.id, { financialStatus: "paid" });
2304
3009
  }
2305
3010
  return { order, customerId: customerId ?? order.customerId ?? "" };
2306
3011
  }
2307
3012
  function SignInModal({ defaultEmail = "", onClose, onSignedIn }) {
2308
- const [email, setEmail] = React18.useState(defaultEmail);
2309
- const [password, setPassword] = React18.useState("");
2310
- const [busy, setBusy] = React18.useState(false);
2311
- const [error, setError] = React18.useState(null);
2312
- React18.useEffect(() => {
3013
+ const [email, setEmail] = React6.useState(defaultEmail);
3014
+ const [password, setPassword] = React6.useState("");
3015
+ const [busy, setBusy] = React6.useState(false);
3016
+ const [error, setError] = React6.useState(null);
3017
+ React6.useEffect(() => {
2313
3018
  const onKey = (event) => {
2314
3019
  if (event.key === "Escape") onClose();
2315
3020
  };
@@ -2471,13 +3176,13 @@ function CheckoutPage() {
2471
3176
  const session = useSessionStore();
2472
3177
  const validateDiscount2 = useDiscountValidator();
2473
3178
  useStorefrontHead({ title: `Checkout \u2014 ${config.name}` });
2474
- const [selectedAddressId, setSelectedAddressId] = React18.useState(DEFAULT_ADDRESS.id);
2475
- const [selectedPaymentId, setSelectedPaymentId] = React18.useState(DEFAULT_PAYMENT.id);
2476
- const [savedAddresses, setSavedAddresses] = React18.useState(SAVED_ADDRESSES);
2477
- const [savedPaymentMethods, setSavedPaymentMethods] = React18.useState(SAVED_PAYMENT_METHODS);
2478
- const [addressMode, setAddressMode] = React18.useState("saved");
2479
- const [paymentMode, setPaymentMode] = React18.useState("saved");
2480
- const [form, setForm] = React18.useState({
3179
+ const [selectedAddressId, setSelectedAddressId] = React6.useState(DEFAULT_ADDRESS.id);
3180
+ const [selectedPaymentId, setSelectedPaymentId] = React6.useState(DEFAULT_PAYMENT.id);
3181
+ const [savedAddresses, setSavedAddresses] = React6.useState(SAVED_ADDRESSES);
3182
+ const [savedPaymentMethods, setSavedPaymentMethods] = React6.useState(SAVED_PAYMENT_METHODS);
3183
+ const [addressMode, setAddressMode] = React6.useState("saved");
3184
+ const [paymentMode, setPaymentMode] = React6.useState("saved");
3185
+ const [form, setForm] = React6.useState({
2481
3186
  email: session.email ?? "",
2482
3187
  name: session.name ?? "",
2483
3188
  street: DEFAULT_ADDRESS.street,
@@ -2487,20 +3192,20 @@ function CheckoutPage() {
2487
3192
  expiry: DEFAULT_PAYMENT.expiry,
2488
3193
  cvc: DEFAULT_PAYMENT.cvc
2489
3194
  });
2490
- const [error, setError] = React18.useState(null);
2491
- const [discountCode, setDiscountCode] = React18.useState("");
2492
- const [discountError, setDiscountError] = React18.useState(null);
2493
- const [discountSuccess, setDiscountSuccess] = React18.useState(null);
2494
- const [applyingDiscount, setApplyingDiscount] = React18.useState(false);
2495
- const [placing, setPlacing] = React18.useState(false);
2496
- const [processingStep, setProcessingStep] = React18.useState(0);
2497
- const [showSignin, setShowSignin] = React18.useState(false);
3195
+ const [error, setError] = React6.useState(null);
3196
+ const [discountCode, setDiscountCode] = React6.useState("");
3197
+ const [discountError, setDiscountError] = React6.useState(null);
3198
+ const [discountSuccess, setDiscountSuccess] = React6.useState(null);
3199
+ const [applyingDiscount, setApplyingDiscount] = React6.useState(false);
3200
+ const [placing, setPlacing] = React6.useState(false);
3201
+ const [processingStep, setProcessingStep] = React6.useState(0);
3202
+ const [showSignin, setShowSignin] = React6.useState(false);
2498
3203
  const subtotal = selectSubtotal(cart);
2499
3204
  const discountTotal = selectDiscountTotal(cart);
2500
3205
  const shipping = selectShipping(cart, config);
2501
3206
  const total = selectTotal(cart, config);
2502
3207
  const money = (value) => formatMoney(value, config.currency, config.locale);
2503
- React18.useEffect(() => {
3208
+ React6.useEffect(() => {
2504
3209
  if (cart.lines.length === 0 && !placing) navigateTo(config.catalogPath);
2505
3210
  }, [cart.lines.length, config.catalogPath, placing]);
2506
3211
  const set = (key) => (value) => setForm((current) => ({ ...current, [key]: value }));
@@ -2815,7 +3520,7 @@ function CheckoutPage() {
2815
3520
  /* @__PURE__ */ jsxRuntime.jsx("h2", { className: "sr-only", children: "Resumo do pedido" }),
2816
3521
  /* @__PURE__ */ jsxRuntime.jsx("ul", { className: "space-y-4", children: cart.lines.map((line) => /* @__PURE__ */ jsxRuntime.jsxs("li", { className: "flex gap-3 text-sm", children: [
2817
3522
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative h-16 w-16 flex-none overflow-hidden rounded-lg border bg-background", children: [
2818
- line.imageUrl && /* @__PURE__ */ jsxRuntime.jsx("img", { src: line.imageUrl, alt: line.name, className: "h-full w-full object-cover" }),
3523
+ line.imageUrl && /* @__PURE__ */ jsxRuntime.jsx(SmoothImage, { src: line.imageUrl, alt: line.name, className: "h-full w-full object-cover" }),
2819
3524
  /* @__PURE__ */ jsxRuntime.jsx("span", { className: "absolute -right-1 -top-1 flex h-5 min-w-5 items-center justify-center rounded-full bg-muted-foreground px-1.5 text-[10px] font-bold text-background", children: line.quantity })
2820
3525
  ] }),
2821
3526
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "min-w-0 flex-1", children: [
@@ -3155,11 +3860,11 @@ function SuccessMark() {
3155
3860
  }
3156
3861
  function OrderConfirmationPage({ orderId }) {
3157
3862
  const config = useStorefrontConfig();
3158
- const [order, setOrder] = React18.useState(null);
3159
- const [loading, setLoading] = React18.useState(true);
3863
+ const [order, setOrder] = React6.useState(null);
3864
+ const [loading, setLoading] = React6.useState(true);
3160
3865
  const money = (v) => formatMoney(v, config.currency, config.locale);
3161
3866
  useStorefrontHead({ title: order ? `Pedido #${order.orderNumber} \u2014 ${config.name}` : config.name });
3162
- React18.useEffect(() => {
3867
+ React6.useEffect(() => {
3163
3868
  let cancelled = false;
3164
3869
  runtime.getShopProvider().getOrder(orderId).then((o) => {
3165
3870
  if (!cancelled) setOrder(o);
@@ -3237,7 +3942,7 @@ function OrderConfirmationPage({ orderId }) {
3237
3942
  /* @__PURE__ */ jsxRuntime.jsx(OrderTrackingTimeline, { order })
3238
3943
  ] }),
3239
3944
  /* @__PURE__ */ jsxRuntime.jsx("ul", { className: "mt-8 space-y-3 text-left", children: order.items.map((item) => /* @__PURE__ */ jsxRuntime.jsxs("li", { className: "flex items-center gap-3 text-sm", children: [
3240
- item.imageUrl && /* @__PURE__ */ jsxRuntime.jsx("img", { src: item.imageUrl, alt: item.name, className: "h-12 w-12 rounded-lg border object-cover" }),
3945
+ item.imageUrl && /* @__PURE__ */ jsxRuntime.jsx(SmoothImage, { src: item.imageUrl, alt: item.name, className: "h-12 w-12 rounded-lg border object-cover" }),
3241
3946
  /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "flex-1", children: [
3242
3947
  item.name,
3243
3948
  " ",
@@ -3291,11 +3996,11 @@ function OrderConfirmationPage({ orderId }) {
3291
3996
  function useMyOrders() {
3292
3997
  const customerId = useSessionStore((s) => s.customerId);
3293
3998
  const email = useSessionStore((s) => s.email);
3294
- const [orders, setOrders] = React18.useState([]);
3295
- const [loading, setLoading] = React18.useState(true);
3296
- const [tick, setTick] = React18.useState(0);
3297
- const refresh = React18.useCallback(() => setTick((t) => t + 1), []);
3298
- React18.useEffect(() => {
3999
+ const [orders, setOrders] = React6.useState([]);
4000
+ const [loading, setLoading] = React6.useState(true);
4001
+ const [tick, setTick] = React6.useState(0);
4002
+ const refresh = React6.useCallback(() => setTick((t) => t + 1), []);
4003
+ React6.useEffect(() => {
3299
4004
  let cancelled = false;
3300
4005
  if (!customerId && !email) {
3301
4006
  setOrders([]);
@@ -3317,12 +4022,12 @@ function useMyOrders() {
3317
4022
  return { orders, loading, refresh };
3318
4023
  }
3319
4024
  function AuthForm() {
3320
- const [mode, setMode] = React18.useState("signin");
3321
- const [email, setEmail] = React18.useState("");
3322
- const [password, setPassword] = React18.useState("");
3323
- const [name, setName] = React18.useState("");
3324
- const [busy, setBusy] = React18.useState(false);
3325
- const [error, setError] = React18.useState(null);
4025
+ const [mode, setMode] = React6.useState("signin");
4026
+ const [email, setEmail] = React6.useState("");
4027
+ const [password, setPassword] = React6.useState("");
4028
+ const [name, setName] = React6.useState("");
4029
+ const [busy, setBusy] = React6.useState(false);
4030
+ const [error, setError] = React6.useState(null);
3326
4031
  async function submit(e) {
3327
4032
  e.preventDefault();
3328
4033
  setError(null);
@@ -3445,7 +4150,7 @@ function OrdersPanel() {
3445
4150
  /* @__PURE__ */ jsxRuntime.jsx("p", { className: "mt-0.5 text-xs text-muted-foreground", children: new Date(order.createdAt).toLocaleDateString(config.locale) }),
3446
4151
  /* @__PURE__ */ jsxRuntime.jsx("div", { className: "mt-4", children: /* @__PURE__ */ jsxRuntime.jsx(OrderTrackingTimeline, { order, compact: true }) }),
3447
4152
  /* @__PURE__ */ jsxRuntime.jsx("ul", { className: "mt-3 flex flex-wrap gap-2", children: order.items.map((item) => /* @__PURE__ */ jsxRuntime.jsxs("li", { className: "flex items-center gap-2 rounded-lg border px-2 py-1 text-xs", children: [
3448
- item.imageUrl && /* @__PURE__ */ jsxRuntime.jsx("img", { src: item.imageUrl, alt: item.name, className: "h-6 w-6 rounded object-cover" }),
4153
+ item.imageUrl && /* @__PURE__ */ jsxRuntime.jsx(SmoothImage, { src: item.imageUrl, alt: item.name, className: "h-6 w-6 rounded object-cover" }),
3449
4154
  item.name,
3450
4155
  " \xD7 ",
3451
4156
  item.quantity
@@ -3464,9 +4169,9 @@ function OrdersPanel() {
3464
4169
  }
3465
4170
  function ProfilePanel() {
3466
4171
  const session = useSessionStore();
3467
- const [name, setName] = React18.useState(session.name ?? "");
3468
- const [saving, setSaving] = React18.useState(false);
3469
- const [saved, setSaved] = React18.useState(false);
4172
+ const [name, setName] = React6.useState(session.name ?? "");
4173
+ const [saving, setSaving] = React6.useState(false);
4174
+ const [saved, setSaved] = React6.useState(false);
3470
4175
  async function save(e) {
3471
4176
  e.preventDefault();
3472
4177
  const trimmed = name.trim();
@@ -3562,7 +4267,7 @@ var SECTION_SUBTITLE = {
3562
4267
  function MyPurchasesPage() {
3563
4268
  const config = useStorefrontConfig();
3564
4269
  const session = useSessionStore();
3565
- const [section, setSection] = React18.useState("orders");
4270
+ const [section, setSection] = React6.useState("orders");
3566
4271
  useStorefrontHead({ title: `Minha conta \u2014 ${config.name}` });
3567
4272
  if (!session.email) {
3568
4273
  return /* @__PURE__ */ jsxRuntime.jsx("main", { className: "mx-auto max-w-3xl px-4 py-12 sm:px-6", children: /* @__PURE__ */ jsxRuntime.jsx(AuthForm, {}) });
@@ -3625,7 +4330,7 @@ function PolicyPage({ kind }) {
3625
4330
  ] }) })
3626
4331
  ] });
3627
4332
  }
3628
- var StorefrontErrorBoundary = class extends React18__default.default.Component {
4333
+ var StorefrontErrorBoundary = class extends React6__default.default.Component {
3629
4334
  constructor() {
3630
4335
  super(...arguments);
3631
4336
  this.state = { error: null };
@@ -3669,7 +4374,7 @@ var ACCENT = {
3669
4374
  function ToastCard({ toast: toast2 }) {
3670
4375
  const dismiss = useToastStore((s) => s.dismiss);
3671
4376
  const Icon2 = ICON[toast2.variant];
3672
- React18.useEffect(() => {
4377
+ React6.useEffect(() => {
3673
4378
  const timer = window.setTimeout(() => dismiss(toast2.id), toast2.durationMs);
3674
4379
  return () => window.clearTimeout(timer);
3675
4380
  }, [toast2.id, toast2.durationMs, dismiss]);
@@ -3726,8 +4431,8 @@ function RouteSwitch({ path }) {
3726
4431
  if (product?.slug) return /* @__PURE__ */ jsxRuntime.jsx(ProductDetailPage, { slug: product.slug });
3727
4432
  const order = matchPath("/order/:id", path);
3728
4433
  if (order?.id) return /* @__PURE__ */ jsxRuntime.jsx(OrderConfirmationPage, { orderId: order.id });
3729
- if (matchPath("/checkout", path)) return /* @__PURE__ */ jsxRuntime.jsx(CheckoutPage, {});
3730
- if (matchPath("/account", path)) return /* @__PURE__ */ jsxRuntime.jsx(MyPurchasesPage, {});
4434
+ if (matchPath("/checkout", path) && config.features.checkout) return /* @__PURE__ */ jsxRuntime.jsx(CheckoutPage, {});
4435
+ if (matchPath("/account", path) && config.features.accounts) return /* @__PURE__ */ jsxRuntime.jsx(MyPurchasesPage, {});
3731
4436
  if (matchPath("/catalog", path)) return /* @__PURE__ */ jsxRuntime.jsx(CatalogPage, {});
3732
4437
  if (matchPath("/privacy", path)) return /* @__PURE__ */ jsxRuntime.jsx(PolicyPage, { kind: "privacy" });
3733
4438
  if (matchPath("/terms", path)) return /* @__PURE__ */ jsxRuntime.jsx(PolicyPage, { kind: "terms" });
@@ -3769,12 +4474,26 @@ function buildMockSeed(config) {
3769
4474
  };
3770
4475
  }
3771
4476
  function initStorefrontRuntime(config) {
3772
- initCustomerAuth(resolveAuthAdapter(config.auth?.adapter));
4477
+ if (resolveConfig(config).features.accounts) {
4478
+ initCustomerAuth(resolveAuthAdapter(config.auth, {
4479
+ supabaseUrl: config.supabaseUrl,
4480
+ supabaseAnonKey: config.supabaseAnonKey
4481
+ }));
4482
+ }
3773
4483
  if (config.provider) {
3774
4484
  runtime.setShopProvider(config.provider);
3775
4485
  } else {
3776
4486
  runtime.setShopProvider(mock.createMockShopProvider(buildMockSeed(config)));
3777
4487
  }
4488
+ const reconcileCart = () => {
4489
+ const provider = runtime.getShopProvider();
4490
+ void useCartStore.getState().reconcile(async (line) => {
4491
+ const product = await provider.getProduct(line.productId);
4492
+ return product != null;
4493
+ });
4494
+ };
4495
+ if (useCartStore.persist.hasHydrated()) reconcileCart();
4496
+ else useCartStore.persist.onFinishHydration(reconcileCart);
3778
4497
  if (config.supabaseUrl || config.supabaseAnonKey) {
3779
4498
  console.warn(
3780
4499
  "@fayz-ai/shop: supabaseUrl/supabaseAnonKey are legacy fields. Pass an explicit provider/adapter or use the Fayz SDK broker path."
@@ -3785,14 +4504,20 @@ function StorefrontShell() {
3785
4504
  const config = useStorefrontConfig();
3786
4505
  const path = useHashPath();
3787
4506
  const focused = isFocusedRoute(config, path);
3788
- return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "min-h-screen bg-background text-foreground", children: [
4507
+ const components = getStorefrontComponents(config);
4508
+ const chromeProps = { config, commerceMode: config.commerceMode };
4509
+ const body = /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
3789
4510
  config.theme && /* @__PURE__ */ jsxRuntime.jsx(StorefrontThemeStyle, { theme: config.theme }),
3790
- !focused && /* @__PURE__ */ jsxRuntime.jsx(StorefrontHeader, {}),
4511
+ !focused && (components.Header ? /* @__PURE__ */ jsxRuntime.jsx(components.Header, { ...chromeProps }) : /* @__PURE__ */ jsxRuntime.jsx(StorefrontHeader, {})),
3791
4512
  /* @__PURE__ */ jsxRuntime.jsx(StorefrontErrorBoundary, { children: /* @__PURE__ */ jsxRuntime.jsx(RouteSwitch, { path }) }, path),
3792
- !focused && /* @__PURE__ */ jsxRuntime.jsx(CartDrawer, {}),
3793
- !focused && /* @__PURE__ */ jsxRuntime.jsx(StorefrontFooter, {}),
4513
+ !focused && config.features.cart && /* @__PURE__ */ jsxRuntime.jsx(CartDrawer, {}),
4514
+ !focused && (components.Footer ? /* @__PURE__ */ jsxRuntime.jsx(components.Footer, { ...chromeProps }) : /* @__PURE__ */ jsxRuntime.jsx(StorefrontFooter, {})),
3794
4515
  /* @__PURE__ */ jsxRuntime.jsx(Toaster, {})
3795
4516
  ] });
4517
+ if (components.Shell) {
4518
+ return /* @__PURE__ */ jsxRuntime.jsx(components.Shell, { ...chromeProps, children: body });
4519
+ }
4520
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "min-h-screen bg-background text-foreground", children: body });
3796
4521
  }
3797
4522
  function createStorefrontApp(config) {
3798
4523
  const resolved = resolveConfig(config);
@@ -3803,6 +4528,37 @@ function createStorefrontApp(config) {
3803
4528
  StorefrontApp.displayName = "StorefrontApp";
3804
4529
  return StorefrontApp;
3805
4530
  }
4531
+ function createStorefront(options) {
4532
+ const routes = [...options.pages ?? [], ...options.routes ?? []];
4533
+ const config = {
4534
+ ...options.config,
4535
+ provider: options.provider ?? options.config.provider,
4536
+ components: {
4537
+ ...options.config.components,
4538
+ ...options.components
4539
+ },
4540
+ routes: routes.length > 0 ? routes : options.config.routes,
4541
+ home: options.sections ? { ...options.config.home ?? { sections: [] }, sections: options.sections } : options.config.home
4542
+ };
4543
+ return createStorefrontApp(config);
4544
+ }
4545
+
4546
+ // src/define.ts
4547
+ function defineStorefrontConfig(config) {
4548
+ return config;
4549
+ }
4550
+ function defineStorefrontComponents(components) {
4551
+ return components;
4552
+ }
4553
+ function defineStorefrontRoutes(routes) {
4554
+ return routes;
4555
+ }
4556
+ function defineStorefrontSections(sections) {
4557
+ return sections;
4558
+ }
4559
+ function defineStorefrontApp(options) {
4560
+ return options;
4561
+ }
3806
4562
  var runtimeConfigRegistry = /* @__PURE__ */ new Map();
3807
4563
  function slug(name) {
3808
4564
  return name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "") || "app";
@@ -3861,7 +4617,7 @@ function manifestToStorefrontConfig(manifest, surfaceName) {
3861
4617
  provider: manifest.backend?.provider,
3862
4618
  url: manifest.backend?.url
3863
4619
  },
3864
- slots: runtimeConfig?.slots,
4620
+ components: runtimeConfig?.components,
3865
4621
  routes: runtimeConfig?.routes,
3866
4622
  provider: runtimeConfig?.provider,
3867
4623
  auth: runtimeConfig?.auth,
@@ -3869,9 +4625,9 @@ function manifestToStorefrontConfig(manifest, surfaceName) {
3869
4625
  };
3870
4626
  }
3871
4627
  function StorefrontScaffold({ manifest, surface }) {
3872
- const config = React18__default.default.useMemo(() => manifestToStorefrontConfig(manifest, surface), [manifest, surface]);
3873
- const resolved = React18__default.default.useMemo(() => resolveConfig(config), [config]);
3874
- const inited = React18__default.default.useRef(false);
4628
+ const config = React6__default.default.useMemo(() => manifestToStorefrontConfig(manifest, surface), [manifest, surface]);
4629
+ const resolved = React6__default.default.useMemo(() => resolveConfig(config), [config]);
4630
+ const inited = React6__default.default.useRef(false);
3875
4631
  if (!inited.current) {
3876
4632
  initStorefrontRuntime(config);
3877
4633
  inited.current = true;
@@ -4097,11 +4853,73 @@ var atelierTemplate = {
4097
4853
  ]
4098
4854
  })
4099
4855
  };
4856
+ var foodTemplate = {
4857
+ id: "food",
4858
+ inspiration: "Delivery / restaurant storefront",
4859
+ announcement: "ENTREGA GR\xC1TIS ACIMA DE R$ 150 \xB7 PE\xC7A J\xC1",
4860
+ theme: {
4861
+ name: "food",
4862
+ colors: {
4863
+ background: "30 33% 97%",
4864
+ foreground: "20 14% 15%",
4865
+ primary: "14 84% 50%",
4866
+ primaryForeground: "0 0% 100%",
4867
+ card: "30 33% 99%",
4868
+ cardForeground: "20 14% 15%",
4869
+ muted: "28 20% 92%",
4870
+ mutedForeground: "22 10% 42%",
4871
+ border: "28 18% 85%",
4872
+ headerBackground: "20 14% 11%",
4873
+ headerForeground: "30 24% 96%",
4874
+ announcementBackground: "14 84% 50%",
4875
+ announcementForeground: "0 0% 100%"
4876
+ },
4877
+ font: { heading: "Poppins", body: "Inter" },
4878
+ radius: "round",
4879
+ header: { variant: "classic" },
4880
+ productCard: { style: "card", imageAspect: "square" }
4881
+ },
4882
+ home: (storeName, images) => ({
4883
+ sections: [
4884
+ {
4885
+ type: "hero",
4886
+ variant: "banner",
4887
+ height: "tall",
4888
+ slides: [
4889
+ { title: "Sabor de verdade, na sua porta", subtitle: `${storeName} \u2014 feito na hora, do jeito que voc\xEA ama.`, cta: "Ver card\xE1pio", href: "/catalog", hue: 20, image: images?.hero?.[0] }
4890
+ ]
4891
+ },
4892
+ {
4893
+ type: "benefits",
4894
+ items: [
4895
+ { icon: "Flame", title: "Feito na hora", text: "Preparado com capricho" },
4896
+ { icon: "Truck", title: "Entrega r\xE1pida", text: "Frete gr\xE1tis acima de R$ 150" },
4897
+ { icon: "CreditCard", title: "Pagamento f\xE1cil", text: "Pix, cart\xE3o e mais" }
4898
+ ]
4899
+ },
4900
+ { type: "categories", style: "tiles", title: "Nosso card\xE1pio" },
4901
+ { type: "products", title: "Mais pedidos", eyebrow: "favoritos da casa", filter: "all", limit: 8 },
4902
+ { type: "banner", title: "Combo do dia", eyebrow: "oferta", subtitle: "Sele\xE7\xE3o especial com desconto", cta: "Aproveitar", hue: 14, image: images?.banners?.[0] },
4903
+ { type: "products", title: "Novidades no card\xE1pio", filter: "new", limit: 4 },
4904
+ {
4905
+ type: "testimonials",
4906
+ title: "O que dizem por a\xED",
4907
+ items: [
4908
+ { quote: "Chegou quentinho e absurdo de bom. Virei cliente!", author: "Rafael M." },
4909
+ { quote: "Sabor de restaurante em casa, sem complica\xE7\xE3o.", author: "Beatriz L." },
4910
+ { quote: "Entrega r\xE1pida e por\xE7\xE3o generosa. Recomendo demais.", author: "Diego S." }
4911
+ ]
4912
+ },
4913
+ { type: "newsletter", title: "Receba nossas ofertas", subtitle: "Promo\xE7\xF5es e novidades do card\xE1pio, direto no seu e-mail." }
4914
+ ]
4915
+ })
4916
+ };
4100
4917
  var storefrontTemplates = {
4101
4918
  mare: mareTemplate,
4102
4919
  sertao: sertaoTemplate,
4103
4920
  volt: voltTemplate,
4104
- atelier: atelierTemplate
4921
+ atelier: atelierTemplate,
4922
+ food: foodTemplate
4105
4923
  };
4106
4924
 
4107
4925
  Object.defineProperty(exports, "getSupabaseClientOptional", {
@@ -4122,6 +4940,7 @@ exports.HeroSection = HeroSection;
4122
4940
  exports.HomePage = HomePage;
4123
4941
  exports.Link = Link;
4124
4942
  exports.ManifestoBlock = ManifestoBlock;
4943
+ exports.MediaCarousel = MediaCarousel;
4125
4944
  exports.MyPurchasesPage = MyPurchasesPage;
4126
4945
  exports.NewsletterBand = NewsletterBand;
4127
4946
  exports.OrderConfirmationPage = OrderConfirmationPage;
@@ -4129,24 +4948,35 @@ exports.OrderTrackingTimeline = OrderTrackingTimeline;
4129
4948
  exports.Price = Price;
4130
4949
  exports.ProductCard = ProductCard;
4131
4950
  exports.ProductDetailPage = ProductDetailPage;
4951
+ exports.ProductEnquiryForm = ProductEnquiryForm;
4132
4952
  exports.ProductGrid = ProductGrid;
4133
4953
  exports.ProductOptionSelector = ProductOptionSelector;
4134
4954
  exports.ProductRail = ProductRail;
4955
+ exports.ProductSlider = ProductSlider;
4135
4956
  exports.PromoBanner = PromoBanner;
4136
4957
  exports.QuantityInput = QuantityInput;
4137
4958
  exports.Reveal = Reveal;
4959
+ exports.SmoothImage = SmoothImage;
4138
4960
  exports.StorefrontFooter = StorefrontFooter;
4139
4961
  exports.StorefrontHeader = StorefrontHeader;
4140
4962
  exports.StorefrontScaffold = StorefrontScaffold;
4963
+ exports.StorefrontSections = StorefrontSections;
4141
4964
  exports.StorefrontShell = StorefrontShell;
4142
4965
  exports.StorefrontThemeStyle = StorefrontThemeStyle;
4143
4966
  exports.TID = TID;
4144
4967
  exports.Testimonials = Testimonials;
4145
4968
  exports.atelierTemplate = atelierTemplate;
4146
4969
  exports.bannerPlaceholder = bannerPlaceholder;
4970
+ exports.createStorefront = createStorefront;
4147
4971
  exports.createStorefrontApp = createStorefrontApp;
4148
4972
  exports.defineStorefront = defineStorefront;
4973
+ exports.defineStorefrontApp = defineStorefrontApp;
4974
+ exports.defineStorefrontComponents = defineStorefrontComponents;
4975
+ exports.defineStorefrontConfig = defineStorefrontConfig;
4976
+ exports.defineStorefrontRoutes = defineStorefrontRoutes;
4977
+ exports.defineStorefrontSections = defineStorefrontSections;
4149
4978
  exports.establishCustomerSession = establishCustomerSession;
4979
+ exports.foodTemplate = foodTemplate;
4150
4980
  exports.formatMoney = formatMoney;
4151
4981
  exports.formatProductOptionSelection = formatProductOptionSelection;
4152
4982
  exports.getCustomerAuthAdapter = getCustomerAuthAdapter;
@@ -4158,7 +4988,7 @@ exports.navigateTo = navigateTo;
4158
4988
  exports.normalizeProductOptionSelection = normalizeProductOptionSelection;
4159
4989
  exports.placeStorefrontOrder = placeStorefrontOrder;
4160
4990
  exports.prefersReducedMotion = prefersReducedMotion;
4161
- exports.productCardSlotContract = productCardSlotContract;
4991
+ exports.productCardComponentContract = productCardComponentContract;
4162
4992
  exports.productOptionSelectionKey = productOptionSelectionKey;
4163
4993
  exports.registerStorefrontBlocks = registerStorefrontBlocks;
4164
4994
  exports.resolveAuthAdapter = resolveAuthAdapter;
@@ -4173,13 +5003,16 @@ exports.sertaoTemplate = sertaoTemplate;
4173
5003
  exports.signInByEmail = signInByEmail;
4174
5004
  exports.signOutCustomer = signOutCustomer;
4175
5005
  exports.signUpCustomer = signUpCustomer;
4176
- exports.storefrontSlotContracts = storefrontSlotContracts;
5006
+ exports.storefrontComponentContracts = storefrontComponentContracts;
4177
5007
  exports.storefrontTemplates = storefrontTemplates;
4178
5008
  exports.themeToCss = themeToCss;
5009
+ exports.useCart = useCart;
4179
5010
  exports.useCartStore = useCartStore;
5011
+ exports.useCatalog = useCatalog;
4180
5012
  exports.useCatalogStore = useCatalogStore;
4181
5013
  exports.useCategories = useCategories;
4182
5014
  exports.useDiscountValidator = useDiscountValidator;
5015
+ exports.useEnquiry = useEnquiry;
4183
5016
  exports.useHashPath = useHashPath;
4184
5017
  exports.useInView = useInView;
4185
5018
  exports.useMyOrders = useMyOrders;
@@ -4188,7 +5021,10 @@ exports.useProduct = useProduct;
4188
5021
  exports.useProducts = useProducts;
4189
5022
  exports.useScrolled = useScrolled;
4190
5023
  exports.useSessionStore = useSessionStore;
5024
+ exports.useStorefront = useStorefront;
5025
+ exports.useStorefrontActions = useStorefrontActions;
4191
5026
  exports.useStorefrontConfig = useStorefrontConfig;
5027
+ exports.useStorefrontConfigOptional = useStorefrontConfigOptional;
4192
5028
  exports.voltTemplate = voltTemplate;
4193
5029
  //# sourceMappingURL=index.cjs.map
4194
5030
  //# sourceMappingURL=index.cjs.map