@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.js CHANGED
@@ -1,13 +1,13 @@
1
1
  import { setShopProvider, getShopProvider } from '@fayz-ai/shop/runtime';
2
2
  import { createMockShopProvider } from '@fayz-ai/shop/mock';
3
- import { createMockAuthAdapter } from '@fayz-ai/auth';
3
+ import { createAuthRuntime, createMockAuthAdapter } from '@fayz-ai/plugin-auth';
4
4
  import { create } from 'zustand';
5
5
  import { persist } from 'zustand/middleware';
6
- import React18, { createContext, useState, useRef, useEffect, useMemo, useContext, useCallback } from 'react';
6
+ import React6, { createContext, useState, useRef, useEffect, useMemo, useContext, useCallback } from 'react';
7
7
  import { hashRouterAdapter, registerScaffold, registerBlock, renderBlocks, defineApp } from '@fayz-ai/core';
8
8
  export { getSupabaseClientOptional, renderApp } from '@fayz-ai/core';
9
9
  import * as LucideIcons from 'lucide-react';
10
- import { ChevronLeft, ChevronRight, ShoppingBag, X, Trash2, Phone, Mail, Search, User, UserCircle, Package, CreditCard, LogOut, Truck, RefreshCcw, ShieldCheck, Lock, Minus, Plus, Info, AlertCircle, Check, MapPin, PackageCheck, QrCode, XCircle, RotateCcw, Clock } from 'lucide-react';
10
+ import { ChevronLeft, ChevronRight, ShoppingBag, X, Trash2, Phone, Mail, Search, User, UserCircle, Package, CreditCard, LogOut, Truck, RefreshCcw, ShieldCheck, Lock, Minus, Plus, Info, AlertCircle, Check, Send, MessageCircle, MapPin, PackageCheck, QrCode, XCircle, RotateCcw, Clock } from 'lucide-react';
11
11
  import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
12
12
  import { validateDiscount } from '@fayz-ai/shop';
13
13
 
@@ -27,14 +27,31 @@ var useSessionStore = create()(
27
27
 
28
28
  // src/auth.ts
29
29
  var _adapter = null;
30
- function resolveAuthAdapter(configured) {
31
- if (configured && configured !== "mock" && configured !== "supabase") return configured;
32
- if (configured === "supabase") {
33
- console.warn(
34
- '@fayz-ai/shop: auth.adapter="supabase" is legacy. Pass an explicit AuthAdapter or use the Fayz SDK broker path.'
35
- );
36
- }
37
- return createMockAuthAdapter();
30
+ function isAuthAdapter(value) {
31
+ return Boolean(
32
+ value && typeof value === "object" && "getSession" in value && "signIn" in value && "signOut" in value
33
+ );
34
+ }
35
+ function normalizeAuthConfig(configured) {
36
+ if (!configured) return {};
37
+ if (typeof configured === "string" || isAuthAdapter(configured)) return { adapter: configured };
38
+ return configured;
39
+ }
40
+ function resolveAuthAdapter(configured, fallback) {
41
+ const config = normalizeAuthConfig(configured);
42
+ return createAuthRuntime({
43
+ provider: config.provider ?? config.adapter ?? (fallback?.supabaseUrl ? "supabase" : "mock"),
44
+ adapter: config.adapter,
45
+ routes: config.routes,
46
+ requireAuth: false,
47
+ supabase: {
48
+ url: config.supabase?.url ?? fallback?.supabaseUrl,
49
+ anonKey: config.supabase?.anonKey ?? fallback?.supabaseAnonKey,
50
+ client: config.supabase?.client,
51
+ callbackUrl: config.supabase?.callbackUrl,
52
+ resetPasswordUrl: config.supabase?.resetPasswordUrl
53
+ }
54
+ }).adapter;
38
55
  }
39
56
  function initCustomerAuth(adapter2) {
40
57
  _adapter = adapter2;
@@ -105,18 +122,37 @@ async function signOutCustomer() {
105
122
  }
106
123
  function resolveConfig(config) {
107
124
  const catalogPath = config.home ? "/catalog" : "/";
125
+ const commerceMode = config.commerceMode ?? "checkout";
126
+ const checkoutEnabled = commerceMode === "checkout";
108
127
  return {
109
128
  ...config,
110
129
  currency: config.currency ?? "BRL",
111
130
  locale: config.locale ?? "pt-BR",
112
131
  shipping: { flatRate: config.shipping?.flatRate ?? 0, freeAbove: config.shipping?.freeAbove },
113
132
  payments: { mode: config.payments?.mode ?? "mock" },
133
+ commerceMode,
134
+ enquiry: {
135
+ label: config.enquiry?.label ?? "Contact me",
136
+ successMessage: config.enquiry?.successMessage ?? "Thanks. We received your enquiry.",
137
+ subjectPrefix: config.enquiry?.subjectPrefix ?? "Product enquiry",
138
+ email: config.enquiry?.email,
139
+ whatsappUrl: config.enquiry?.whatsappUrl,
140
+ requirePhone: config.enquiry?.requirePhone
141
+ },
114
142
  features: {
115
143
  // Auto-enable the coupon UI when the store actually ships discount codes
116
144
  // (via config.discounts or a seeded catalog), unless explicitly overridden —
117
145
  // otherwise a store can advertise a coupon with no field to enter it.
118
146
  discounts: config.features?.discounts ?? Boolean(config.discounts?.length || config.catalog?.discounts?.length),
119
- accounts: config.features?.accounts ?? true
147
+ accounts: config.features?.accounts ?? checkoutEnabled,
148
+ cart: config.features?.cart ?? checkoutEnabled,
149
+ checkout: config.features?.checkout ?? checkoutEnabled
150
+ },
151
+ imageLoading: {
152
+ mode: config.imageLoading?.mode ?? "fade",
153
+ durationMs: config.imageLoading?.durationMs ?? 420,
154
+ easing: config.imageLoading?.easing ?? "cubic-bezier(0.22, 1, 0.36, 1)",
155
+ blur: config.imageLoading?.blur ?? true
120
156
  },
121
157
  catalogPath,
122
158
  nav: config.nav ?? (config.home ? [
@@ -132,6 +168,12 @@ function useStorefrontConfig() {
132
168
  if (!ctx) throw new Error("useStorefrontConfig must be used inside createStorefrontApp");
133
169
  return ctx;
134
170
  }
171
+ function useStorefrontConfigOptional() {
172
+ return useContext(StorefrontConfigContext);
173
+ }
174
+ function getStorefrontComponents(config) {
175
+ return config.components ?? {};
176
+ }
135
177
  var adapter = hashRouterAdapter();
136
178
  function navigateTo(to) {
137
179
  adapter.navigate(to);
@@ -162,93 +204,7 @@ function matchPath(pattern, path) {
162
204
  return params;
163
205
  }
164
206
  function Link({ to, children, ...rest }) {
165
- return React18.createElement("a", { href: `#${to}`, ...rest }, children);
166
- }
167
- var RADIUS_MAP = {
168
- none: { button: "0px", card: "0px", input: "0px" },
169
- soft: { button: "6px", card: "10px", input: "6px" },
170
- round: { button: "10px", card: "16px", input: "10px" },
171
- pill: { button: "999px", card: "20px", input: "999px" }
172
- };
173
- var COLOR_VAR_MAP = {
174
- background: "--background",
175
- foreground: "--foreground",
176
- primary: "--primary",
177
- primaryForeground: "--primary-foreground",
178
- card: "--card",
179
- cardForeground: "--card-foreground",
180
- muted: "--muted",
181
- mutedForeground: "--muted-foreground",
182
- border: "--border",
183
- headerBackground: "--sf-header-bg",
184
- headerForeground: "--sf-header-fg",
185
- announcementBackground: "--sf-announcement-bg",
186
- announcementForeground: "--sf-announcement-fg"
187
- };
188
- function themeToCss(theme) {
189
- const lines = [];
190
- for (const [key, cssVar] of Object.entries(COLOR_VAR_MAP)) {
191
- const value = theme.colors?.[key];
192
- if (value) lines.push(`${cssVar}: ${value};`);
193
- }
194
- if (!theme.colors?.headerBackground && theme.colors?.background) {
195
- lines.push(`--sf-header-bg: ${theme.colors.background};`);
196
- }
197
- if (!theme.colors?.headerForeground && theme.colors?.foreground) {
198
- lines.push(`--sf-header-fg: ${theme.colors.foreground};`);
199
- }
200
- if (theme.font) {
201
- const fallback = theme.font.fallback ?? "sans-serif";
202
- lines.push(`--font-family: '${theme.font.body}', ${fallback};`);
203
- lines.push(`--sf-font-heading: '${theme.font.heading}', ${fallback};`);
204
- }
205
- const radius = RADIUS_MAP[theme.radius ?? "soft"];
206
- lines.push(`--button-radius: ${radius.button};`);
207
- lines.push(`--card-radius: ${radius.card};`);
208
- lines.push(`--input-radius: ${radius.input};`);
209
- lines.push(`--sf-radius-button: ${radius.button};`);
210
- lines.push(`--sf-radius-card: ${radius.card};`);
211
- lines.push(`--sf-radius-input: ${radius.input};`);
212
- let css = `:root {
213
- ${lines.join("\n ")}
214
- }
215
- `;
216
- css += `body { font-family: var(--font-family); }
217
- `;
218
- css += `h1, h2, h3, h4, .sf-heading { font-family: var(--sf-font-heading, var(--font-family)); }
219
- `;
220
- if (theme.uppercaseButtons) {
221
- css += `.sf-cta { text-transform: uppercase; letter-spacing: 0.08em; }
222
- `;
223
- }
224
- if (theme.header?.uppercaseNav) {
225
- css += `.sf-nav-link { text-transform: uppercase; letter-spacing: 0.06em; font-size: 0.8rem; }
226
- `;
227
- }
228
- return css;
229
- }
230
- function googleFontsHref(theme) {
231
- if (!theme.font) return null;
232
- const families = theme.font.googleFonts ?? [theme.font.heading, theme.font.body];
233
- const unique = [...new Set(families)];
234
- const params = unique.map((f) => `family=${encodeURIComponent(f).replace(/%20/g, "+")}:wght@300;400;500;600;700`).join("&");
235
- return `https://fonts.googleapis.com/css2?${params}&display=swap`;
236
- }
237
- function StorefrontThemeStyle({ theme }) {
238
- const fontsHref = googleFontsHref(theme);
239
- useEffect(() => {
240
- if (!fontsHref) return;
241
- const id = "sf-theme-fonts";
242
- let link = document.getElementById(id);
243
- if (!link) {
244
- link = document.createElement("link");
245
- link.id = id;
246
- link.rel = "stylesheet";
247
- document.head.appendChild(link);
248
- }
249
- link.href = fontsHref;
250
- }, [fontsHref]);
251
- return React18.createElement("style", { "data-sf-theme": theme.name }, themeToCss(theme));
207
+ return React6.createElement("a", { href: `#${to}`, ...rest }, children);
252
208
  }
253
209
 
254
210
  // src/format.ts
@@ -302,7 +258,7 @@ function productOptionSelectionKey(selection) {
302
258
  // src/stores/cart.store.ts
303
259
  var useCartStore = create()(
304
260
  persist(
305
- (set) => ({
261
+ (set, get) => ({
306
262
  lines: [],
307
263
  discountCode: null,
308
264
  discountPercent: 0,
@@ -354,7 +310,16 @@ var useCartStore = create()(
354
310
  clearDiscount: () => set({ discountCode: null, discountPercent: 0 }),
355
311
  clear: () => set({ lines: [], discountCode: null, discountPercent: 0 }),
356
312
  openDrawer: () => set({ isOpen: true }),
357
- closeDrawer: () => set({ isOpen: false, justAddedLineId: null })
313
+ closeDrawer: () => set({ isOpen: false, justAddedLineId: null }),
314
+ reconcile: async (resolve) => {
315
+ const lines = get().lines;
316
+ if (lines.length === 0) return;
317
+ const keep = await Promise.all(
318
+ lines.map((line) => resolve(line).catch(() => true))
319
+ );
320
+ const kept = lines.filter((_, i) => keep[i]);
321
+ if (kept.length !== lines.length) set({ lines: kept });
322
+ }
358
323
  }),
359
324
  {
360
325
  name: "fayz.storefront.cart.v1",
@@ -376,6 +341,102 @@ var selectShipping = (s, cfg) => {
376
341
  return cfg.shipping.flatRate;
377
342
  };
378
343
  var selectTotal = (s, cfg) => roundCents(selectSubtotal(s) - selectDiscountTotal(s) + selectShipping(s, cfg));
344
+ var RADIUS_MAP = {
345
+ none: { button: "0px", card: "0px", input: "0px" },
346
+ soft: { button: "6px", card: "10px", input: "6px" },
347
+ round: { button: "10px", card: "16px", input: "10px" },
348
+ pill: { button: "999px", card: "20px", input: "999px" }
349
+ };
350
+ var COLOR_VAR_MAP = {
351
+ background: "--background",
352
+ foreground: "--foreground",
353
+ primary: "--primary",
354
+ primaryForeground: "--primary-foreground",
355
+ card: "--card",
356
+ cardForeground: "--card-foreground",
357
+ muted: "--muted",
358
+ mutedForeground: "--muted-foreground",
359
+ border: "--border",
360
+ headerBackground: "--sf-header-bg",
361
+ headerForeground: "--sf-header-fg",
362
+ announcementBackground: "--sf-announcement-bg",
363
+ announcementForeground: "--sf-announcement-fg",
364
+ accent: "--sf-accent",
365
+ accentForeground: "--sf-accent-foreground"
366
+ };
367
+ function themeToCss(theme) {
368
+ const lines = [];
369
+ for (const [key, cssVar] of Object.entries(COLOR_VAR_MAP)) {
370
+ const value = theme.colors?.[key];
371
+ if (value) lines.push(`${cssVar}: ${value};`);
372
+ }
373
+ if (!theme.colors?.headerBackground && theme.colors?.background) {
374
+ lines.push(`--sf-header-bg: ${theme.colors.background};`);
375
+ }
376
+ if (!theme.colors?.headerForeground && theme.colors?.foreground) {
377
+ lines.push(`--sf-header-fg: ${theme.colors.foreground};`);
378
+ }
379
+ if (theme.font) {
380
+ const fallback = theme.font.fallback ?? "sans-serif";
381
+ lines.push(`--font-family: '${theme.font.body}', ${fallback};`);
382
+ lines.push(`--sf-font-heading: '${theme.font.heading}', ${fallback};`);
383
+ if (theme.font.display) lines.push(`--sf-font-display: '${theme.font.display}', ${fallback};`);
384
+ if (theme.font.serif) lines.push(`--sf-font-serif: '${theme.font.serif}', serif;`);
385
+ }
386
+ for (const [key, value] of Object.entries(theme.tokens ?? {})) {
387
+ const name = key.startsWith("--") ? key : `--${key}`;
388
+ lines.push(`${name}: ${value};`);
389
+ }
390
+ const radius = RADIUS_MAP[theme.radius ?? "soft"];
391
+ lines.push(`--button-radius: ${radius.button};`);
392
+ lines.push(`--card-radius: ${radius.card};`);
393
+ lines.push(`--input-radius: ${radius.input};`);
394
+ lines.push(`--sf-radius-button: ${radius.button};`);
395
+ lines.push(`--sf-radius-card: ${radius.card};`);
396
+ lines.push(`--sf-radius-input: ${radius.input};`);
397
+ let css = `:root {
398
+ ${lines.join("\n ")}
399
+ }
400
+ `;
401
+ css += `body { font-family: var(--font-family); }
402
+ `;
403
+ css += `h1, h2, h3, h4, .sf-heading { font-family: var(--sf-font-heading, var(--font-family)); }
404
+ `;
405
+ if (theme.uppercaseButtons) {
406
+ css += `.sf-cta { text-transform: uppercase; letter-spacing: 0.08em; }
407
+ `;
408
+ }
409
+ if (theme.header?.uppercaseNav) {
410
+ css += `.sf-nav-link { text-transform: uppercase; letter-spacing: 0.06em; font-size: 0.8rem; }
411
+ `;
412
+ }
413
+ return css;
414
+ }
415
+ function googleFontsHref(theme) {
416
+ if (!theme.font) return null;
417
+ const families = theme.font.googleFonts ?? [theme.font.heading, theme.font.body, theme.font.display, theme.font.serif].filter(
418
+ (f) => Boolean(f)
419
+ );
420
+ const unique = [...new Set(families)];
421
+ const params = unique.map((f) => `family=${encodeURIComponent(f).replace(/%20/g, "+")}:wght@300;400;500;600;700`).join("&");
422
+ return `https://fonts.googleapis.com/css2?${params}&display=swap`;
423
+ }
424
+ function StorefrontThemeStyle({ theme }) {
425
+ const fontsHref = googleFontsHref(theme);
426
+ useEffect(() => {
427
+ if (!fontsHref) return;
428
+ const id = "sf-theme-fonts";
429
+ let link = document.getElementById(id);
430
+ if (!link) {
431
+ link = document.createElement("link");
432
+ link.id = id;
433
+ link.rel = "stylesheet";
434
+ document.head.appendChild(link);
435
+ }
436
+ link.href = fontsHref;
437
+ }, [fontsHref]);
438
+ return React6.createElement("style", { "data-sf-theme": theme.name }, themeToCss(theme));
439
+ }
379
440
  var initial = {
380
441
  search: "",
381
442
  categoryId: null,
@@ -484,6 +545,7 @@ function usePopOnChange(value) {
484
545
  var TID = {
485
546
  // header
486
547
  headerSearch: "header-search",
548
+ headerSearchToggle: "header-search-toggle",
487
549
  cartButton: "cart-button",
488
550
  cartCount: "cart-count",
489
551
  accountLink: "account-link",
@@ -496,6 +558,13 @@ var TID = {
496
558
  hero: "home-hero",
497
559
  heroPrev: "hero-prev",
498
560
  heroNext: "hero-next",
561
+ mediaCarousel: "media-carousel",
562
+ mediaCarouselPrev: "media-carousel-prev",
563
+ mediaCarouselNext: "media-carousel-next",
564
+ productSlider: "product-slider",
565
+ productSliderViewport: "product-slider-viewport",
566
+ productSliderPrev: "product-slider-prev",
567
+ productSliderNext: "product-slider-next",
499
568
  categoryShowcase: "category-showcase",
500
569
  productRail: "product-rail",
501
570
  benefits: "benefits-row",
@@ -530,6 +599,19 @@ var TID = {
530
599
  productOptionValue: (id, value) => `product-option-${id.toLowerCase().replace(/\s+/g, "-")}-${value.toLowerCase().replace(/\s+/g, "-")}`,
531
600
  pdpQtyInput: "pdp-qty-input",
532
601
  pdpAddToCart: "pdp-add-to-cart",
602
+ pdpRoot: "pdp-root",
603
+ pdpGallery: "pdp-gallery",
604
+ pdpActions: "pdp-actions",
605
+ pdpEnquiryButton: "pdp-enquiry-button",
606
+ enquiryForm: "enquiry-form",
607
+ enquiryName: "enquiry-name",
608
+ enquiryEmail: "enquiry-email",
609
+ enquiryPhone: "enquiry-phone",
610
+ enquirySubject: "enquiry-subject",
611
+ enquiryMessage: "enquiry-message",
612
+ enquirySubmit: "enquiry-submit",
613
+ enquirySuccess: "enquiry-success",
614
+ enquiryError: "enquiry-error",
533
615
  // cart
534
616
  cartDrawer: "cart-drawer",
535
617
  cartLine: "cart-line",
@@ -652,12 +734,26 @@ function UtilityBar() {
652
734
  /* @__PURE__ */ jsx("span", { className: "ml-auto flex items-center gap-4", children: social?.map((s) => /* @__PURE__ */ jsx("a", { href: s.href, target: "_blank", rel: "noreferrer", className: "hover:underline", children: s.label }, s.href)) })
653
735
  ] }) });
654
736
  }
655
- function SearchInput({ className }) {
737
+ function SearchInput({ className, iconOnly = false }) {
656
738
  const config = useStorefrontConfig();
657
739
  const search = useCatalogStore((s) => s.search);
658
740
  const setSearch = useCatalogStore((s) => s.setSearch);
659
741
  const path = useHashPath();
660
- return /* @__PURE__ */ jsxs("div", { className: `relative ${className ?? ""}`, children: [
742
+ const [open, setOpen] = useState(false);
743
+ if (iconOnly && !open) {
744
+ return /* @__PURE__ */ jsx(
745
+ "button",
746
+ {
747
+ type: "button",
748
+ "aria-label": "Buscar produtos",
749
+ "data-testid": TID.headerSearchToggle,
750
+ onClick: () => setOpen(true),
751
+ className: "rounded-full p-2.5 transition-opacity hover:opacity-70",
752
+ children: /* @__PURE__ */ jsx(Search, { className: "h-5 w-5" })
753
+ }
754
+ );
755
+ }
756
+ return /* @__PURE__ */ jsxs("div", { className: `relative ${iconOnly ? "w-full max-w-xs" : className ?? ""}`, children: [
661
757
  /* @__PURE__ */ jsx(Search, { className: "pointer-events-none absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 opacity-60" }),
662
758
  /* @__PURE__ */ jsx(
663
759
  "input",
@@ -666,10 +762,17 @@ function SearchInput({ className }) {
666
762
  type: "search",
667
763
  placeholder: "Buscar produtos...",
668
764
  value: search,
765
+ autoFocus: iconOnly,
669
766
  onChange: (e) => {
670
767
  setSearch(e.target.value);
671
768
  if (path !== config.catalogPath) navigateTo(config.catalogPath);
672
769
  },
770
+ onKeyDown: (e) => {
771
+ if (e.key === "Escape" && iconOnly) setOpen(false);
772
+ },
773
+ onBlur: () => {
774
+ if (iconOnly && !search) setOpen(false);
775
+ },
673
776
  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",
674
777
  style: {
675
778
  borderRadius: "var(--sf-radius-input)",
@@ -757,7 +860,7 @@ function HeaderActions() {
757
860
  }
758
861
  )
759
862
  ] }),
760
- /* @__PURE__ */ jsxs(
863
+ config.features.cart && /* @__PURE__ */ jsxs(
761
864
  "button",
762
865
  {
763
866
  type: "button",
@@ -784,12 +887,13 @@ function NavLinks({ className }) {
784
887
  const config = useStorefrontConfig();
785
888
  const { categories } = useCategories();
786
889
  const setCategoryId = useCatalogStore((s) => s.setCategoryId);
890
+ const showCategories = config.theme?.header?.showCategories !== false;
787
891
  const goCategory = (categoryId) => {
788
892
  useCatalogStore.getState().reset();
789
893
  setCategoryId(categoryId);
790
894
  navigateTo(config.catalogPath);
791
895
  };
792
- return /* @__PURE__ */ jsxs("nav", { className: `flex items-center gap-6 ${className ?? ""}`, children: [
896
+ return /* @__PURE__ */ jsxs("nav", { className: `flex min-w-max items-center gap-6 ${className ?? ""}`, children: [
793
897
  config.nav.map((link) => /* @__PURE__ */ jsx(
794
898
  Link,
795
899
  {
@@ -800,7 +904,7 @@ function NavLinks({ className }) {
800
904
  },
801
905
  link.to
802
906
  )),
803
- categories.slice(0, 6).map((c) => /* @__PURE__ */ jsxs(
907
+ showCategories && categories.slice(0, 6).map((c) => /* @__PURE__ */ jsxs(
804
908
  "button",
805
909
  {
806
910
  type: "button",
@@ -820,6 +924,8 @@ function StorefrontHeader() {
820
924
  const config = useStorefrontConfig();
821
925
  const variant = config.theme?.header?.variant ?? "classic";
822
926
  const scrolled = useScrolled();
927
+ const showSearch = config.theme?.header?.showSearch !== false;
928
+ const iconSearch = config.theme?.header?.searchStyle === "icon";
823
929
  const logo = /* @__PURE__ */ jsx(Link, { to: "/", className: "sf-heading shrink-0 text-xl font-bold tracking-tight", children: config.logo ?? config.name });
824
930
  return /* @__PURE__ */ jsxs(
825
931
  "header",
@@ -833,21 +939,21 @@ function StorefrontHeader() {
833
939
  // Rio/Flex pattern: centered logo row, nav row below
834
940
  /* @__PURE__ */ jsxs(Fragment, { children: [
835
941
  /* @__PURE__ */ jsxs("div", { className: "mx-auto grid h-16 max-w-7xl grid-cols-[1fr_auto_1fr] items-center px-4 sm:px-6", children: [
836
- /* @__PURE__ */ jsx(SearchInput, { className: "hidden w-full max-w-xs sm:block" }),
942
+ showSearch ? /* @__PURE__ */ jsx(SearchInput, { iconOnly: iconSearch, className: "hidden w-full max-w-xs sm:block" }) : /* @__PURE__ */ jsx("div", {}),
837
943
  /* @__PURE__ */ jsx("div", { className: "text-center", children: logo }),
838
944
  /* @__PURE__ */ jsx("div", { className: "justify-self-end", children: /* @__PURE__ */ jsx(HeaderActions, {}) })
839
945
  ] }),
840
- /* @__PURE__ */ jsx("div", { className: "hidden justify-center border-t py-2.5 sm:flex", style: headerDivider, children: /* @__PURE__ */ jsx(NavLinks, {}) })
946
+ /* @__PURE__ */ jsx("div", { className: "flex overflow-x-auto border-t py-2.5 sm:justify-center", style: headerDivider, children: /* @__PURE__ */ jsx(NavLinks, {}) })
841
947
  ] })
842
948
  ) : variant === "search" ? (
843
949
  // Brasília pattern: prominent search left, logo center, actions right; nav row below
844
950
  /* @__PURE__ */ jsxs(Fragment, { children: [
845
951
  /* @__PURE__ */ 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: [
846
- /* @__PURE__ */ jsx(SearchInput, { className: "hidden w-full max-w-sm sm:block" }),
952
+ showSearch ? /* @__PURE__ */ jsx(SearchInput, { iconOnly: iconSearch, className: "hidden w-full max-w-sm sm:block" }) : /* @__PURE__ */ jsx("div", {}),
847
953
  /* @__PURE__ */ jsx("div", { className: "text-center", children: logo }),
848
954
  /* @__PURE__ */ jsx("div", { className: "justify-self-end", children: /* @__PURE__ */ jsx(HeaderActions, {}) })
849
955
  ] }),
850
- /* @__PURE__ */ jsx("div", { className: "hidden border-t sm:block", style: headerDivider, children: /* @__PURE__ */ jsx("div", { className: "mx-auto max-w-7xl px-4 py-2.5 sm:px-6", children: /* @__PURE__ */ jsx(NavLinks, {}) }) })
956
+ /* @__PURE__ */ jsx("div", { className: "overflow-x-auto border-t", style: headerDivider, children: /* @__PURE__ */ jsx("div", { className: "mx-auto max-w-7xl px-4 py-2.5 sm:px-6", children: /* @__PURE__ */ jsx(NavLinks, {}) }) })
851
957
  ] })
852
958
  ) : variant === "minimal" ? (
853
959
  // Uyuni pattern: nav left, centered logo, actions right — single compact row
@@ -855,17 +961,19 @@ function StorefrontHeader() {
855
961
  /* @__PURE__ */ jsx("div", { className: "flex items-center gap-5", children: /* @__PURE__ */ jsx(NavLinks, { className: "hidden sm:flex" }) }),
856
962
  /* @__PURE__ */ jsx("div", { className: "text-center", children: logo }),
857
963
  /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-end gap-3", children: [
858
- /* @__PURE__ */ jsx(SearchInput, { className: "hidden w-44 lg:block" }),
964
+ showSearch && /* @__PURE__ */ jsx(SearchInput, { iconOnly: iconSearch, className: "hidden w-44 lg:block" }),
859
965
  /* @__PURE__ */ jsx(HeaderActions, {})
860
966
  ] })
861
967
  ] })
862
968
  ) : (
863
- // classic: logo left, search center-right, actions right (original layout)
969
+ // classic: logo left, nav, then a right-aligned search + actions cluster
864
970
  /* @__PURE__ */ jsxs("div", { className: "mx-auto flex h-16 max-w-7xl items-center gap-6 px-4 sm:px-6", children: [
865
971
  logo,
866
972
  /* @__PURE__ */ jsx(NavLinks, { className: "hidden md:flex" }),
867
- /* @__PURE__ */ jsx(SearchInput, { className: "ml-auto hidden w-full max-w-sm sm:block" }),
868
- /* @__PURE__ */ jsx(HeaderActions, {})
973
+ /* @__PURE__ */ jsxs("div", { className: "ml-auto flex items-center gap-2 sm:gap-3", children: [
974
+ showSearch && /* @__PURE__ */ jsx(SearchInput, { iconOnly: iconSearch, className: iconSearch ? "" : "hidden w-full max-w-sm sm:block" }),
975
+ /* @__PURE__ */ jsx(HeaderActions, {})
976
+ ] })
869
977
  ] })
870
978
  )
871
979
  ]
@@ -877,6 +985,17 @@ function StorefrontFooter() {
877
985
  const { categories } = useCategories();
878
986
  const visibleCategories = categories.slice(0, 8);
879
987
  const hiddenCategoryCount = Math.max(categories.length - visibleCategories.length, 0);
988
+ const labels = {
989
+ categories: "Departamentos",
990
+ navigation: "Navega\xE7\xE3o",
991
+ contact: "Contato",
992
+ viewAll: "Ver todos",
993
+ purchases: "Minhas compras",
994
+ privacy: "Privacidade",
995
+ terms: "Termos",
996
+ returns: "Trocas e devolu\xE7\xF5es",
997
+ ...config.footer?.labels
998
+ };
880
999
  const goCategory = (categoryId) => {
881
1000
  useCatalogStore.getState().reset();
882
1001
  useCatalogStore.getState().setCategoryId(categoryId);
@@ -889,7 +1008,7 @@ function StorefrontFooter() {
889
1008
  config.footer?.about && /* @__PURE__ */ jsx("p", { className: "mt-3 text-sm leading-relaxed text-muted-foreground", children: config.footer.about })
890
1009
  ] }),
891
1010
  /* @__PURE__ */ jsxs("div", { children: [
892
- /* @__PURE__ */ jsx("h3", { className: "mb-3 text-sm font-semibold uppercase tracking-wide text-muted-foreground", children: "Departamentos" }),
1011
+ /* @__PURE__ */ jsx("h3", { className: "mb-3 text-sm font-semibold uppercase tracking-wide text-muted-foreground", children: labels.categories }),
893
1012
  /* @__PURE__ */ jsxs("ul", { className: "space-y-2 text-sm", children: [
894
1013
  visibleCategories.map((c) => /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsx(
895
1014
  "button",
@@ -901,21 +1020,22 @@ function StorefrontFooter() {
901
1020
  }
902
1021
  ) }, c.id)),
903
1022
  hiddenCategoryCount > 0 && /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsxs(Link, { to: config.catalogPath, className: "font-medium underline-offset-2 transition-opacity hover:underline", children: [
904
- "Ver todos (",
1023
+ labels.viewAll,
1024
+ " (",
905
1025
  categories.length,
906
1026
  ")"
907
1027
  ] }) })
908
1028
  ] })
909
1029
  ] }),
910
1030
  /* @__PURE__ */ jsxs("div", { children: [
911
- /* @__PURE__ */ jsx("h3", { className: "mb-3 text-sm font-semibold uppercase tracking-wide text-muted-foreground", children: "Navega\xE7\xE3o" }),
1031
+ /* @__PURE__ */ jsx("h3", { className: "mb-3 text-sm font-semibold uppercase tracking-wide text-muted-foreground", children: labels.navigation }),
912
1032
  /* @__PURE__ */ jsxs("ul", { className: "space-y-2 text-sm", children: [
913
1033
  config.nav.map((link) => /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsx(Link, { to: link.to, className: "transition-opacity hover:opacity-70", children: link.label }) }, link.to)),
914
- /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsx(Link, { to: "/account", className: "transition-opacity hover:opacity-70", children: "Minhas compras" }) })
1034
+ config.features.accounts && /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsx(Link, { to: "/account", className: "transition-opacity hover:opacity-70", children: labels.purchases }) })
915
1035
  ] })
916
1036
  ] }),
917
1037
  /* @__PURE__ */ jsxs("div", { children: [
918
- /* @__PURE__ */ jsx("h3", { className: "mb-3 text-sm font-semibold uppercase tracking-wide text-muted-foreground", children: "Contato" }),
1038
+ /* @__PURE__ */ jsx("h3", { className: "mb-3 text-sm font-semibold uppercase tracking-wide text-muted-foreground", children: labels.contact }),
919
1039
  /* @__PURE__ */ jsxs("ul", { className: "space-y-2 text-sm text-muted-foreground", children: [
920
1040
  config.footer?.contact?.phone && /* @__PURE__ */ jsx("li", { children: config.footer.contact.phone }),
921
1041
  config.footer?.contact?.email && /* @__PURE__ */ jsx("li", { children: config.footer.contact.email }),
@@ -926,17 +1046,15 @@ function StorefrontFooter() {
926
1046
  ] }),
927
1047
  /* @__PURE__ */ jsx("div", { className: "border-t", children: /* @__PURE__ */ 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: [
928
1048
  /* @__PURE__ */ jsxs("nav", { className: "flex flex-wrap items-center justify-center gap-x-5 gap-y-2", children: [
929
- /* @__PURE__ */ jsx(Link, { to: "/privacy", className: "transition-colors hover:text-foreground", children: "Privacidade" }),
930
- /* @__PURE__ */ jsx(Link, { to: "/terms", className: "transition-colors hover:text-foreground", children: "Termos" }),
931
- /* @__PURE__ */ jsx(Link, { to: "/refunds", className: "transition-colors hover:text-foreground", children: "Trocas e devolu\xE7\xF5es" })
1049
+ /* @__PURE__ */ jsx(Link, { to: "/privacy", className: "transition-colors hover:text-foreground", children: labels.privacy }),
1050
+ /* @__PURE__ */ jsx(Link, { to: "/terms", className: "transition-colors hover:text-foreground", children: labels.terms }),
1051
+ /* @__PURE__ */ jsx(Link, { to: "/refunds", className: "transition-colors hover:text-foreground", children: labels.returns })
932
1052
  ] }),
933
- /* @__PURE__ */ jsxs("p", { className: "text-center", children: [
1053
+ /* @__PURE__ */ jsx("p", { className: "text-center", children: config.footer?.credit ?? /* @__PURE__ */ jsxs(Fragment, { children: [
934
1054
  "\xA9 ",
935
1055
  config.name,
936
- " \u2014 powered by Fayz \xB7 Fotos:",
937
- " ",
938
- /* @__PURE__ */ jsx("a", { href: "https://unsplash.com", target: "_blank", rel: "noreferrer", className: "underline-offset-2 hover:underline", children: "Unsplash" })
939
- ] })
1056
+ " \u2014 powered by Fayz"
1057
+ ] }) })
940
1058
  ] }) })
941
1059
  ] });
942
1060
  }
@@ -1029,6 +1147,53 @@ function QuantityInput({ value, onChange, min = 1, max, testId, incTestId, decTe
1029
1147
  )
1030
1148
  ] });
1031
1149
  }
1150
+ function SmoothImage({
1151
+ imageLoading,
1152
+ loading = "lazy",
1153
+ decoding = "async",
1154
+ onLoad,
1155
+ style,
1156
+ ...props
1157
+ }) {
1158
+ const config = useStorefrontConfigOptional();
1159
+ const resolved = {
1160
+ ...config?.imageLoading,
1161
+ ...imageLoading
1162
+ };
1163
+ const mode = resolved.mode ?? "fade";
1164
+ const durationMs = resolved.durationMs ?? 420;
1165
+ const easing = resolved.easing ?? "cubic-bezier(0.22, 1, 0.36, 1)";
1166
+ const blur = resolved.blur ?? true;
1167
+ const ref = React6.useRef(null);
1168
+ const [loaded, setLoaded] = React6.useState(mode === "none");
1169
+ React6.useEffect(() => {
1170
+ if (mode === "none") {
1171
+ setLoaded(true);
1172
+ return;
1173
+ }
1174
+ if (ref.current?.complete && ref.current.naturalWidth > 0) setLoaded(true);
1175
+ }, [mode, props.src]);
1176
+ const revealStyle = mode === "none" ? {} : {
1177
+ opacity: loaded ? 1 : 0,
1178
+ filter: blur && !loaded ? "blur(10px)" : "blur(0px)",
1179
+ transition: `opacity ${durationMs}ms ${easing}, filter ${durationMs}ms ${easing}, transform ${durationMs}ms ${easing}`
1180
+ };
1181
+ return /* @__PURE__ */ jsx(
1182
+ "img",
1183
+ {
1184
+ ...props,
1185
+ ref,
1186
+ loading,
1187
+ decoding,
1188
+ "data-sf-image-loaded": loaded ? "true" : "false",
1189
+ style: { ...style, ...revealStyle },
1190
+ onLoad: (event) => {
1191
+ setLoaded(true);
1192
+ onLoad?.(event);
1193
+ }
1194
+ }
1195
+ );
1196
+ }
1032
1197
  function CartDrawer() {
1033
1198
  const config = useStorefrontConfig();
1034
1199
  const cart = useCartStore();
@@ -1128,7 +1293,7 @@ function CartDrawer() {
1128
1293
  "data-line-id": line.lineId ?? line.productId,
1129
1294
  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" : ""}`,
1130
1295
  children: [
1131
- line.imageUrl && /* @__PURE__ */ jsx("img", { src: line.imageUrl, alt: line.name, className: "h-20 w-20 shrink-0 rounded-lg border object-cover" }),
1296
+ line.imageUrl && /* @__PURE__ */ jsx(SmoothImage, { src: line.imageUrl, alt: line.name, className: "h-20 w-20 shrink-0 rounded-lg border object-cover" }),
1132
1297
  /* @__PURE__ */ jsxs("div", { className: "flex flex-1 flex-col", children: [
1133
1298
  /* @__PURE__ */ jsxs("div", { className: "flex items-start justify-between gap-2", children: [
1134
1299
  /* @__PURE__ */ jsx("span", { className: "text-sm font-medium leading-snug", children: line.name }),
@@ -1298,36 +1463,91 @@ function Price({ value, compareAt, testId, compareTestId, className }) {
1298
1463
  ] });
1299
1464
  }
1300
1465
 
1301
- // src/slot-contracts.ts
1302
- var productCardSlotContract = {
1466
+ // src/component-selectors.ts
1467
+ var productCardComponentContract = {
1303
1468
  root: { "data-testid": TID.productCard },
1304
1469
  name: { "data-testid": TID.productCardName },
1305
1470
  priceTestId: TID.productCardPrice,
1306
1471
  addButton: { "data-testid": TID.productCardAdd }
1307
1472
  };
1308
- var storefrontSlotContracts = {
1309
- productCard: productCardSlotContract
1473
+ var storefrontComponentContracts = {
1474
+ productCard: productCardComponentContract,
1475
+ productDetail: {
1476
+ root: { "data-testid": TID.pdpRoot },
1477
+ gallery: { "data-testid": TID.pdpGallery },
1478
+ name: { "data-testid": TID.pdpName },
1479
+ description: { "data-testid": TID.pdpDescription },
1480
+ actions: { "data-testid": TID.pdpActions },
1481
+ addButton: { "data-testid": TID.pdpAddToCart },
1482
+ enquiryButton: { "data-testid": TID.pdpEnquiryButton },
1483
+ enquiryForm: { "data-testid": TID.enquiryForm }
1484
+ }
1310
1485
  };
1311
- function ProductCard({ product }) {
1486
+ function useStorefront() {
1487
+ return useStorefrontConfig();
1488
+ }
1489
+ function useCatalog() {
1312
1490
  const config = useStorefrontConfig();
1313
- const addItem = useCartStore((s) => s.addItem);
1491
+ const catalog = useCatalogStore();
1492
+ return {
1493
+ ...catalog,
1494
+ catalogPath: config.catalogPath,
1495
+ config
1496
+ };
1497
+ }
1498
+ function useCart() {
1499
+ return useCartStore();
1500
+ }
1501
+ function useEnquiry() {
1502
+ const config = useStorefrontConfig();
1503
+ const createProductEnquiry = async (input) => {
1504
+ const provider = getShopProvider();
1505
+ if (!provider.createProductEnquiry) {
1506
+ throw new Error("The active shop provider does not support product enquiries.");
1507
+ }
1508
+ return provider.createProductEnquiry(input);
1509
+ };
1510
+ return {
1511
+ enquiry: config.enquiry,
1512
+ createProductEnquiry
1513
+ };
1514
+ }
1515
+ function useStorefrontActions() {
1516
+ const addItem = useCartStore((state) => state.addItem);
1517
+ const openDrawer = useCartStore((state) => state.openDrawer);
1518
+ const closeDrawer = useCartStore((state) => state.closeDrawer);
1519
+ const { createProductEnquiry } = useEnquiry();
1520
+ return {
1521
+ navigateTo,
1522
+ addToCart: (product, quantity, options) => addItem(product, quantity, options),
1523
+ openCart: openDrawer,
1524
+ closeCart: closeDrawer,
1525
+ createProductEnquiry
1526
+ };
1527
+ }
1528
+ function ProductCard({ product, config: providedConfig, actions: providedActions }) {
1529
+ const runtimeConfig = useStorefrontConfig();
1530
+ const runtimeActions = useStorefrontActions();
1531
+ const config = providedConfig ?? runtimeConfig;
1532
+ const actions = providedActions ?? runtimeActions;
1314
1533
  const hasOptions = getProductOptionGroups(product).length > 0;
1315
1534
  const soldOut = product.inventoryCount <= 0;
1316
1535
  const onSale = product.compareAtPrice != null && product.compareAtPrice > product.price;
1317
1536
  const image = product.images.find((i) => i.isPrimary) ?? product.images[0];
1318
1537
  const cardStyle = config.theme?.productCard?.style ?? "card";
1319
1538
  const aspect = config.theme?.productCard?.imageAspect === "portrait" ? "aspect-[3/4]" : "aspect-square";
1539
+ const purchaseMode = config.commerceMode === "checkout";
1320
1540
  return /* @__PURE__ */ jsxs(
1321
1541
  "div",
1322
1542
  {
1323
- ...productCardSlotContract.root,
1543
+ ...productCardComponentContract.root,
1324
1544
  "data-slug": product.slug,
1325
1545
  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"}`,
1326
1546
  style: { borderRadius: "var(--sf-radius-card)" },
1327
1547
  children: [
1328
1548
  /* @__PURE__ */ 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: [
1329
1549
  image && /* @__PURE__ */ jsx(
1330
- "img",
1550
+ SmoothImage,
1331
1551
  {
1332
1552
  src: image.url,
1333
1553
  alt: image.altText ?? product.name,
@@ -1360,33 +1580,37 @@ function ProductCard({ product }) {
1360
1580
  Link,
1361
1581
  {
1362
1582
  to: `/product/${product.slug}`,
1363
- ...productCardSlotContract.name,
1583
+ ...productCardComponentContract.name,
1364
1584
  className: "font-medium leading-snug hover:underline",
1365
1585
  children: product.name
1366
1586
  }
1367
1587
  ),
1368
- /* @__PURE__ */ jsxs("div", { className: "mt-auto flex items-center justify-between pt-2", children: [
1369
- /* @__PURE__ */ jsx(
1588
+ /* @__PURE__ */ jsxs("div", { className: "mt-auto flex items-center justify-between gap-3 pt-2", children: [
1589
+ purchaseMode ? /* @__PURE__ */ jsx(
1370
1590
  Price,
1371
1591
  {
1372
1592
  value: product.price,
1373
1593
  compareAt: product.compareAtPrice,
1374
- testId: productCardSlotContract.priceTestId
1594
+ testId: productCardComponentContract.priceTestId
1375
1595
  }
1376
- ),
1596
+ ) : /* @__PURE__ */ jsx("span", { "data-testid": productCardComponentContract.priceTestId, className: "text-sm text-muted-foreground", children: config.commerceMode === "enquiry" ? config.enquiry.label : "Ver detalhes" }),
1377
1597
  /* @__PURE__ */ jsx(
1378
1598
  "button",
1379
1599
  {
1380
1600
  type: "button",
1381
- ...productCardSlotContract.addButton,
1382
- disabled: soldOut,
1383
- "aria-label": hasOptions ? `Escolher op\xE7\xF5es de ${product.name}` : `Adicionar ${product.name} ao carrinho`,
1601
+ ...productCardComponentContract.addButton,
1602
+ disabled: purchaseMode && soldOut,
1603
+ "aria-label": purchaseMode ? hasOptions ? `Escolher op\xE7\xF5es de ${product.name}` : `Adicionar ${product.name} ao carrinho` : `${config.enquiry.label} ${product.name}`,
1384
1604
  onClick: () => {
1605
+ if (!purchaseMode) {
1606
+ navigateTo(`/product/${product.slug}`);
1607
+ return;
1608
+ }
1385
1609
  if (hasOptions) {
1386
1610
  navigateTo(`/product/${product.slug}`);
1387
1611
  return;
1388
1612
  }
1389
- addItem(product);
1613
+ actions.addToCart(product);
1390
1614
  },
1391
1615
  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",
1392
1616
  children: /* @__PURE__ */ jsx(ShoppingBag, { className: "h-4 w-4" })
@@ -1400,14 +1624,25 @@ function ProductCard({ product }) {
1400
1624
  }
1401
1625
  function ProductGrid({ products, loading }) {
1402
1626
  const config = useStorefrontConfig();
1403
- const ProductCardComponent = config.slots?.ProductCard ?? ProductCard;
1627
+ const actions = useStorefrontActions();
1628
+ const components = getStorefrontComponents(config);
1629
+ const ProductCardComponent = components.ProductCard ?? ProductCard;
1404
1630
  if (loading) {
1405
1631
  return /* @__PURE__ */ 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__ */ jsx("div", { className: "aspect-[3/4] animate-pulse rounded-xl bg-muted" }, i)) });
1406
1632
  }
1407
1633
  if (products.length === 0) {
1408
1634
  return /* @__PURE__ */ jsx("div", { "data-testid": TID.productGrid, className: "py-20 text-center text-muted-foreground", children: "Nenhum produto encontrado." });
1409
1635
  }
1410
- return /* @__PURE__ */ 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__ */ jsx(ProductCardComponent, { product: p }, p.id)) });
1636
+ return /* @__PURE__ */ 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__ */ jsx(
1637
+ ProductCardComponent,
1638
+ {
1639
+ product: p,
1640
+ config,
1641
+ commerceMode: config.commerceMode,
1642
+ actions
1643
+ },
1644
+ p.id
1645
+ )) });
1411
1646
  }
1412
1647
  function FiltersPanel() {
1413
1648
  const { categories } = useCategories();
@@ -1645,8 +1880,15 @@ function HeroSection({
1645
1880
  height = "tall"
1646
1881
  }) {
1647
1882
  const [index, setIndex] = useState(0);
1883
+ const [loadedSlides, setLoadedSlides] = useState({});
1884
+ const config = useStorefrontConfig();
1648
1885
  const interacted = useRef(false);
1649
1886
  const hClass = height === "tall" ? "h-[72vh] min-h-[440px]" : "h-[48vh] min-h-[340px]";
1887
+ const revealImages = config.imageLoading.mode !== "none";
1888
+ const revealStyle = (loaded) => revealImages ? {
1889
+ filter: config.imageLoading.blur && !loaded ? "blur(10px)" : "blur(0px)",
1890
+ transition: `opacity 700ms ease, filter ${config.imageLoading.durationMs}ms ${config.imageLoading.easing}`
1891
+ } : void 0;
1650
1892
  useEffect(() => {
1651
1893
  if (variant !== "slider" || slides.length < 2 || prefersReducedMotion()) return;
1652
1894
  const timer = setInterval(() => {
@@ -1667,7 +1909,7 @@ function HeroSection({
1667
1909
  className: "group relative block h-[42vh] min-h-[320px] overflow-hidden",
1668
1910
  children: [
1669
1911
  /* @__PURE__ */ jsx(
1670
- "img",
1912
+ SmoothImage,
1671
1913
  {
1672
1914
  src: slideImage(slide, 900, 700),
1673
1915
  alt: slide.title,
@@ -1699,7 +1941,11 @@ function HeroSection({
1699
1941
  src: slideImage(slide),
1700
1942
  alt: slide.title,
1701
1943
  "aria-hidden": i !== index,
1702
- className: `absolute inset-0 h-full w-full object-cover transition-opacity duration-700 ${i === index ? "animate-kenburns opacity-100" : "opacity-0"}`
1944
+ loading: i === 0 ? "eager" : "lazy",
1945
+ decoding: "async",
1946
+ onLoad: () => setLoadedSlides((current) => ({ ...current, [i]: true })),
1947
+ style: revealStyle(Boolean(loadedSlides[i])),
1948
+ 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"}`
1703
1949
  },
1704
1950
  i
1705
1951
  )),
@@ -1761,7 +2007,7 @@ function CategoryShowcase({ style, title }) {
1761
2007
  title && /* @__PURE__ */ jsx(Reveal, { children: /* @__PURE__ */ jsx("h2", { className: "sf-heading mb-8 text-center text-3xl font-bold", children: title }) }),
1762
2008
  /* @__PURE__ */ jsx("div", { className: "flex flex-wrap items-start justify-center gap-8", children: categories.map((c, i) => /* @__PURE__ */ jsx(Reveal, { delay: i * 80, children: /* @__PURE__ */ jsxs("button", { type: "button", onClick: () => go(c.id), className: "group flex w-28 flex-col items-center gap-3", children: [
1763
2009
  /* @__PURE__ */ 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__ */ jsx(
1764
- "img",
2010
+ SmoothImage,
1765
2011
  {
1766
2012
  src: c.imageUrl ?? bannerPlaceholder(c.name, 30 + i * 70, 70 + i * 70, 200, 200),
1767
2013
  alt: c.name,
@@ -1776,7 +2022,7 @@ function CategoryShowcase({ style, title }) {
1776
2022
  title && /* @__PURE__ */ jsx(Reveal, { children: /* @__PURE__ */ jsx("h2", { className: "sf-heading mb-8 text-center text-3xl font-bold", children: title }) }),
1777
2023
  /* @__PURE__ */ jsx("div", { className: "grid grid-cols-2 gap-4 lg:grid-cols-4", children: categories.map((c, i) => /* @__PURE__ */ jsx(Reveal, { delay: i * 90, children: /* @__PURE__ */ 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: [
1778
2024
  /* @__PURE__ */ jsx(
1779
- "img",
2025
+ SmoothImage,
1780
2026
  {
1781
2027
  src: c.imageUrl ?? bannerPlaceholder(c.name, 30 + i * 70, 70 + i * 70, 480, 600),
1782
2028
  alt: c.name,
@@ -1798,7 +2044,9 @@ function ProductRail({
1798
2044
  limit = 4
1799
2045
  }) {
1800
2046
  const config = useStorefrontConfig();
1801
- const ProductCardComponent = config.slots?.ProductCard ?? ProductCard;
2047
+ const actions = useStorefrontActions();
2048
+ const components = getStorefrontComponents(config);
2049
+ const ProductCardComponent = components.ProductCard ?? ProductCard;
1802
2050
  const { products, loading } = useProducts(
1803
2051
  filter === "new" ? { status: "active", orderBy: "created_at", order: "desc" } : { status: "active" }
1804
2052
  );
@@ -1814,7 +2062,15 @@ function ProductRail({
1814
2062
  /* @__PURE__ */ jsx("h2", { className: "sf-heading text-3xl font-bold tracking-tight", children: title }),
1815
2063
  subtitle && /* @__PURE__ */ jsx("p", { className: "mt-2 text-muted-foreground", children: subtitle })
1816
2064
  ] }),
1817
- loading ? /* @__PURE__ */ jsx("div", { className: "grid grid-cols-2 gap-5 md:grid-cols-4", children: Array.from({ length: limit }, (_, i) => /* @__PURE__ */ jsx("div", { className: "aspect-[3/4] animate-pulse bg-muted", style: { borderRadius: "var(--sf-radius-card)" } }, i)) }) : /* @__PURE__ */ jsx("div", { className: "grid grid-cols-2 gap-5 md:grid-cols-4", children: visible.map((p, i) => /* @__PURE__ */ jsx(Reveal, { delay: i * 90, children: /* @__PURE__ */ jsx(ProductCardComponent, { product: p }) }, p.id)) }),
2065
+ loading ? /* @__PURE__ */ jsx("div", { className: "grid grid-cols-2 gap-5 md:grid-cols-4", children: Array.from({ length: limit }, (_, i) => /* @__PURE__ */ jsx("div", { className: "aspect-[3/4] animate-pulse bg-muted", style: { borderRadius: "var(--sf-radius-card)" } }, i)) }) : /* @__PURE__ */ jsx("div", { className: "grid grid-cols-2 gap-5 md:grid-cols-4", children: visible.map((p, i) => /* @__PURE__ */ jsx(Reveal, { delay: i * 90, children: /* @__PURE__ */ jsx(
2066
+ ProductCardComponent,
2067
+ {
2068
+ product: p,
2069
+ config,
2070
+ commerceMode: config.commerceMode,
2071
+ actions
2072
+ }
2073
+ ) }, p.id)) }),
1818
2074
  /* @__PURE__ */ jsx(Reveal, { className: "mt-10 text-center", delay: 200, children: /* @__PURE__ */ jsx(
1819
2075
  Link,
1820
2076
  {
@@ -1826,6 +2082,201 @@ function ProductRail({
1826
2082
  ) })
1827
2083
  ] });
1828
2084
  }
2085
+ var heightClass = {
2086
+ medium: "h-[58vh] min-h-[420px]",
2087
+ tall: "h-[74vh] min-h-[520px]",
2088
+ screen: "min-h-[calc(100vh-64px)]"
2089
+ };
2090
+ var alignClass = {
2091
+ start: "items-start text-left",
2092
+ center: "items-center text-center",
2093
+ end: "items-end text-right"
2094
+ };
2095
+ function MediaCarousel({
2096
+ items,
2097
+ height = "tall",
2098
+ autoplayMs = 7e3,
2099
+ overlay = "dark",
2100
+ className = ""
2101
+ }) {
2102
+ const [index, setIndex] = useState(0);
2103
+ const [loadedItems, setLoadedItems] = useState({});
2104
+ const config = useStorefrontConfig();
2105
+ const interacted = useRef(false);
2106
+ const revealImages = config.imageLoading.mode !== "none";
2107
+ const revealStyle = (loaded) => revealImages ? {
2108
+ filter: config.imageLoading.blur && !loaded ? "blur(10px)" : "blur(0px)",
2109
+ transition: `opacity 700ms ease, filter ${config.imageLoading.durationMs}ms ${config.imageLoading.easing}`
2110
+ } : void 0;
2111
+ useEffect(() => {
2112
+ if (items.length < 2 || autoplayMs <= 0 || prefersReducedMotion()) return;
2113
+ const timer = setInterval(() => {
2114
+ if (!interacted.current) setIndex((i) => (i + 1) % items.length);
2115
+ }, autoplayMs);
2116
+ return () => clearInterval(timer);
2117
+ }, [autoplayMs, items.length]);
2118
+ if (items.length === 0) return null;
2119
+ const active = items[Math.min(index, items.length - 1)];
2120
+ const goTo = (next) => {
2121
+ interacted.current = true;
2122
+ setIndex((next + items.length) % items.length);
2123
+ };
2124
+ 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" : "";
2125
+ return /* @__PURE__ */ jsxs("section", { "data-testid": TID.mediaCarousel, className: `relative isolate overflow-hidden ${heightClass[height]} ${className}`, children: [
2126
+ items.map((item, i) => /* @__PURE__ */ jsx(
2127
+ "img",
2128
+ {
2129
+ src: item.image,
2130
+ alt: item.imageAlt ?? item.title,
2131
+ "aria-hidden": i !== index,
2132
+ loading: i === 0 ? "eager" : "lazy",
2133
+ decoding: "async",
2134
+ onLoad: () => setLoadedItems((current) => ({ ...current, [i]: true })),
2135
+ style: revealStyle(Boolean(loadedItems[i])),
2136
+ 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"}`
2137
+ },
2138
+ `${item.title}-${i}`
2139
+ )),
2140
+ /* @__PURE__ */ jsx("div", { className: `absolute inset-0 -z-10 ${overlayClass}` }),
2141
+ /* @__PURE__ */ jsx("div", { className: `flex h-full px-5 py-16 sm:px-10 lg:px-16 ${alignClass[active.align ?? "start"]}`, children: /* @__PURE__ */ jsxs("div", { className: "my-auto max-w-2xl text-white drop-shadow-md", children: [
2142
+ active.eyebrow && /* @__PURE__ */ jsx("p", { className: "mb-4 text-xs font-semibold uppercase tracking-[0.24em] text-white/80", children: active.eyebrow }),
2143
+ /* @__PURE__ */ jsx("h1", { className: "sf-heading text-5xl font-normal leading-[0.95] sm:text-7xl lg:text-8xl", children: active.title }),
2144
+ active.subtitle && /* @__PURE__ */ jsx("p", { className: "mt-5 max-w-xl text-base leading-8 text-white/88 sm:text-lg", children: active.subtitle }),
2145
+ active.cta && /* @__PURE__ */ jsx(
2146
+ Link,
2147
+ {
2148
+ to: active.href ?? "/catalog",
2149
+ 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",
2150
+ children: active.cta
2151
+ }
2152
+ )
2153
+ ] }) }),
2154
+ items.length > 1 && /* @__PURE__ */ jsxs(Fragment, { children: [
2155
+ /* @__PURE__ */ jsx(
2156
+ "button",
2157
+ {
2158
+ type: "button",
2159
+ "aria-label": "Previous slide",
2160
+ "data-testid": TID.mediaCarouselPrev,
2161
+ onClick: () => goTo(index - 1),
2162
+ 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",
2163
+ children: /* @__PURE__ */ jsx(ChevronLeft, { className: "h-5 w-5" })
2164
+ }
2165
+ ),
2166
+ /* @__PURE__ */ jsx(
2167
+ "button",
2168
+ {
2169
+ type: "button",
2170
+ "aria-label": "Next slide",
2171
+ "data-testid": TID.mediaCarouselNext,
2172
+ onClick: () => goTo(index + 1),
2173
+ 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",
2174
+ children: /* @__PURE__ */ jsx(ChevronRight, { className: "h-5 w-5" })
2175
+ }
2176
+ ),
2177
+ /* @__PURE__ */ jsx("div", { className: "absolute bottom-5 left-1/2 flex -translate-x-1/2 gap-2", children: items.map((item, i) => /* @__PURE__ */ jsx(
2178
+ "button",
2179
+ {
2180
+ type: "button",
2181
+ "aria-label": `Go to slide ${i + 1}`,
2182
+ onClick: () => goTo(i),
2183
+ className: `h-2 rounded-full transition-all ${i === index ? "w-8 bg-white" : "w-2 bg-white/55 hover:bg-white"}`
2184
+ },
2185
+ `${item.title}-dot-${i}`
2186
+ )) })
2187
+ ] })
2188
+ ] });
2189
+ }
2190
+ function ProductSlider({
2191
+ title,
2192
+ subtitle,
2193
+ eyebrow,
2194
+ filter = "all",
2195
+ categoryName,
2196
+ collection,
2197
+ limit = 10,
2198
+ cta = "View all",
2199
+ href,
2200
+ className = ""
2201
+ }) {
2202
+ const config = useStorefrontConfig();
2203
+ const actions = useStorefrontActions();
2204
+ const viewportRef = useRef(null);
2205
+ const components = getStorefrontComponents(config);
2206
+ const ProductCardComponent = components.ProductCard ?? ProductCard;
2207
+ const { products, loading } = useProducts(
2208
+ filter === "new" ? { status: "active", orderBy: "created_at", order: "desc" } : { status: "active" }
2209
+ );
2210
+ const visible = useMemo(() => {
2211
+ let list = products;
2212
+ if (filter === "sale") list = list.filter((p) => p.compareAtPrice != null && p.compareAtPrice > p.price);
2213
+ if (categoryName) list = list.filter((p) => p.categoryName === categoryName);
2214
+ if (collection) list = list.filter((p) => p.metadata?.collection === collection);
2215
+ return list.slice(0, limit);
2216
+ }, [categoryName, collection, filter, limit, products]);
2217
+ const scroll = (direction) => {
2218
+ viewportRef.current?.scrollBy({ left: direction * Math.max(320, viewportRef.current.clientWidth * 0.75), behavior: "smooth" });
2219
+ };
2220
+ if (!loading && visible.length === 0) return null;
2221
+ return /* @__PURE__ */ jsxs("section", { "data-testid": TID.productSlider, className: `mx-auto max-w-7xl px-4 py-14 sm:px-6 ${className}`, children: [
2222
+ /* @__PURE__ */ jsxs("div", { className: "mb-8 flex flex-col gap-5 sm:flex-row sm:items-end sm:justify-between", children: [
2223
+ /* @__PURE__ */ jsxs("div", { className: "max-w-2xl", children: [
2224
+ eyebrow && /* @__PURE__ */ jsx("p", { className: "mb-2 text-xs font-semibold uppercase tracking-[0.2em] text-primary", children: eyebrow }),
2225
+ /* @__PURE__ */ jsx("h2", { className: "sf-heading text-3xl font-normal leading-tight tracking-normal sm:text-5xl", children: title }),
2226
+ subtitle && /* @__PURE__ */ jsx("p", { className: "mt-3 leading-7 text-muted-foreground", children: subtitle })
2227
+ ] }),
2228
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
2229
+ /* @__PURE__ */ jsx(
2230
+ "button",
2231
+ {
2232
+ type: "button",
2233
+ "aria-label": "Previous products",
2234
+ "data-testid": TID.productSliderPrev,
2235
+ onClick: () => scroll(-1),
2236
+ className: "inline-flex h-10 w-10 items-center justify-center border border-border bg-background transition hover:bg-foreground hover:text-background",
2237
+ children: /* @__PURE__ */ jsx(ChevronLeft, { className: "h-5 w-5" })
2238
+ }
2239
+ ),
2240
+ /* @__PURE__ */ jsx(
2241
+ "button",
2242
+ {
2243
+ type: "button",
2244
+ "aria-label": "Next products",
2245
+ "data-testid": TID.productSliderNext,
2246
+ onClick: () => scroll(1),
2247
+ className: "inline-flex h-10 w-10 items-center justify-center border border-border bg-background transition hover:bg-foreground hover:text-background",
2248
+ children: /* @__PURE__ */ jsx(ChevronRight, { className: "h-5 w-5" })
2249
+ }
2250
+ )
2251
+ ] })
2252
+ ] }),
2253
+ /* @__PURE__ */ jsx(
2254
+ "div",
2255
+ {
2256
+ ref: viewportRef,
2257
+ "data-testid": TID.productSliderViewport,
2258
+ className: "flex snap-x gap-5 overflow-x-auto pb-5 [scrollbar-width:thin]",
2259
+ children: loading ? Array.from({ length: Math.min(limit, 6) }, (_, i) => /* @__PURE__ */ jsx("div", { className: "h-[360px] min-w-[260px] snap-start animate-pulse bg-muted" }, i)) : visible.map((product) => /* @__PURE__ */ jsx("div", { className: "min-w-[260px] max-w-[300px] flex-[0_0_72vw] snap-start sm:flex-[0_0_300px]", children: /* @__PURE__ */ jsx(
2260
+ ProductCardComponent,
2261
+ {
2262
+ product,
2263
+ config,
2264
+ commerceMode: config.commerceMode,
2265
+ actions
2266
+ }
2267
+ ) }, product.id))
2268
+ }
2269
+ ),
2270
+ cta && /* @__PURE__ */ jsx(
2271
+ Link,
2272
+ {
2273
+ to: href ?? config.catalogPath,
2274
+ 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",
2275
+ children: cta
2276
+ }
2277
+ )
2278
+ ] });
2279
+ }
1829
2280
  function Icon({ name, className }) {
1830
2281
  const C = LucideIcons[name];
1831
2282
  return C ? /* @__PURE__ */ jsx(C, { className }) : null;
@@ -1850,7 +2301,7 @@ function PromoBanner({
1850
2301
  }) {
1851
2302
  return /* @__PURE__ */ jsxs("section", { "data-testid": TID.promoBanner, className: "group relative my-12 overflow-hidden", children: [
1852
2303
  /* @__PURE__ */ jsx(
1853
- "img",
2304
+ SmoothImage,
1854
2305
  {
1855
2306
  src: image ?? bannerPlaceholder(title, hue, hue + 50, 1600, 420),
1856
2307
  alt: title,
@@ -1953,8 +2404,10 @@ function registerStorefrontBlocks() {
1953
2404
  if (registered) return;
1954
2405
  registered = true;
1955
2406
  registerBlock("hero", HeroSection, { source: "sdk", label: "Hero" });
2407
+ registerBlock("mediaCarousel", MediaCarousel, { source: "sdk", label: "Media carousel" });
1956
2408
  registerBlock("categories", CategoryShowcase, { source: "sdk", label: "Categories" });
1957
2409
  registerBlock("products", ProductRail, { source: "sdk", label: "Product rail" });
2410
+ registerBlock("productSlider", ProductSlider, { source: "sdk", label: "Product slider" });
1958
2411
  registerBlock("benefits", BenefitsRow, { source: "sdk", label: "Benefits row" });
1959
2412
  registerBlock("banner", PromoBanner, { source: "sdk", label: "Promo banner" });
1960
2413
  registerBlock("manifesto", ManifestoBlock, { source: "sdk", label: "Manifesto" });
@@ -1968,9 +2421,12 @@ function sectionsToBlocks(sections) {
1968
2421
  return { type, id: `${type}-${i}`, props };
1969
2422
  });
1970
2423
  }
1971
- function HomePage({ sections }) {
2424
+ function StorefrontSections({ sections }) {
1972
2425
  return /* @__PURE__ */ jsx("main", { children: renderBlocks(sectionsToBlocks(sections)) });
1973
2426
  }
2427
+ function HomePage({ sections }) {
2428
+ return /* @__PURE__ */ jsx(StorefrontSections, { sections });
2429
+ }
1974
2430
  function useProduct(slug2) {
1975
2431
  const [product, setProduct] = useState(null);
1976
2432
  const [loading, setLoading] = useState(true);
@@ -2082,18 +2538,208 @@ function ProductSpecs({ product }) {
2082
2538
  }
2083
2539
  function RelatedProducts({ product }) {
2084
2540
  const config = useStorefrontConfig();
2085
- const Card = config.slots?.ProductCard ?? ProductCard;
2541
+ const actions = useStorefrontActions();
2542
+ const components = getStorefrontComponents(config);
2543
+ const Card = components.ProductCard ?? ProductCard;
2086
2544
  const opts = product.categoryId ? { categoryId: product.categoryId, status: "active", limit: 5 } : { status: "active", limit: 5 };
2087
2545
  const { products, loading } = useProducts(opts);
2088
2546
  const related = products.filter((p) => p.id !== product.id).slice(0, 4);
2089
2547
  if (loading || related.length === 0) return null;
2090
2548
  return /* @__PURE__ */ jsxs("section", { className: "mt-16", children: [
2091
2549
  /* @__PURE__ */ jsx("h2", { className: "sf-heading text-xl font-semibold tracking-tight", children: "Voc\xEA tamb\xE9m pode gostar" }),
2092
- /* @__PURE__ */ jsx("div", { className: "mt-6 grid grid-cols-2 gap-5 md:grid-cols-4", children: related.map((p) => /* @__PURE__ */ jsx(Card, { product: p }, p.id)) })
2550
+ /* @__PURE__ */ jsx("div", { className: "mt-6 grid grid-cols-2 gap-5 md:grid-cols-4", children: related.map((p) => /* @__PURE__ */ jsx(
2551
+ Card,
2552
+ {
2553
+ product: p,
2554
+ config,
2555
+ commerceMode: config.commerceMode,
2556
+ actions
2557
+ },
2558
+ p.id
2559
+ )) })
2093
2560
  ] });
2094
2561
  }
2562
+ function defaultSubject(prefix, product) {
2563
+ return `${prefix}: ${product.name}`;
2564
+ }
2565
+ function buildWhatsAppHref(url, product) {
2566
+ const text = encodeURIComponent(`Hello, I would like more information about ${product.name}.`);
2567
+ const sep = url.includes("?") ? "&" : "?";
2568
+ return `${url}${sep}text=${text}`;
2569
+ }
2570
+ function ProductEnquiryForm({ product, onSuccess }) {
2571
+ const config = useStorefrontConfig();
2572
+ const [customerName, setCustomerName] = useState("");
2573
+ const [customerEmail, setCustomerEmail] = useState("");
2574
+ const [customerPhone, setCustomerPhone] = useState("");
2575
+ const [subject, setSubject] = useState(defaultSubject(config.enquiry.subjectPrefix, product));
2576
+ const [message, setMessage] = useState(`I would like more information about ${product.name}.`);
2577
+ const [status, setStatus] = useState("idle");
2578
+ const [error, setError] = useState(null);
2579
+ const submit = async (event) => {
2580
+ event.preventDefault();
2581
+ setStatus("submitting");
2582
+ setError(null);
2583
+ try {
2584
+ const provider = getShopProvider();
2585
+ if (provider.createProductEnquiry) {
2586
+ await provider.createProductEnquiry({
2587
+ productId: product.id,
2588
+ productName: product.name,
2589
+ productSlug: product.slug,
2590
+ customerName,
2591
+ customerEmail,
2592
+ customerPhone: customerPhone || void 0,
2593
+ subject,
2594
+ message,
2595
+ sourceUrl: typeof window === "undefined" ? void 0 : window.location.href,
2596
+ metadata: { sku: product.sku, categoryName: product.categoryName }
2597
+ });
2598
+ } else if (config.enquiry.email) {
2599
+ window.location.href = `mailto:${config.enquiry.email}?subject=${encodeURIComponent(subject)}&body=${encodeURIComponent(message)}`;
2600
+ }
2601
+ setStatus("success");
2602
+ onSuccess?.();
2603
+ } catch (e) {
2604
+ setStatus("error");
2605
+ setError(e instanceof Error ? e.message : String(e));
2606
+ }
2607
+ };
2608
+ return /* @__PURE__ */ jsxs(
2609
+ "form",
2610
+ {
2611
+ ...storefrontComponentContracts.productDetail.enquiryForm,
2612
+ onSubmit: submit,
2613
+ className: "space-y-4",
2614
+ children: [
2615
+ /* @__PURE__ */ jsxs("div", { className: "grid gap-4 sm:grid-cols-2", children: [
2616
+ /* @__PURE__ */ jsxs("label", { className: "space-y-1.5 text-sm font-medium", children: [
2617
+ /* @__PURE__ */ jsx("span", { children: "Your name" }),
2618
+ /* @__PURE__ */ jsx(
2619
+ "input",
2620
+ {
2621
+ "data-testid": TID.enquiryName,
2622
+ required: true,
2623
+ value: customerName,
2624
+ onChange: (e) => setCustomerName(e.target.value),
2625
+ className: "w-full border bg-background px-3 py-2 outline-none focus:border-primary",
2626
+ style: { borderRadius: "var(--sf-radius-input)" }
2627
+ }
2628
+ )
2629
+ ] }),
2630
+ /* @__PURE__ */ jsxs("label", { className: "space-y-1.5 text-sm font-medium", children: [
2631
+ /* @__PURE__ */ jsx("span", { children: "Your email" }),
2632
+ /* @__PURE__ */ jsx(
2633
+ "input",
2634
+ {
2635
+ "data-testid": TID.enquiryEmail,
2636
+ required: true,
2637
+ type: "email",
2638
+ value: customerEmail,
2639
+ onChange: (e) => setCustomerEmail(e.target.value),
2640
+ className: "w-full border bg-background px-3 py-2 outline-none focus:border-primary",
2641
+ style: { borderRadius: "var(--sf-radius-input)" }
2642
+ }
2643
+ )
2644
+ ] })
2645
+ ] }),
2646
+ /* @__PURE__ */ jsxs("label", { className: "block space-y-1.5 text-sm font-medium", children: [
2647
+ /* @__PURE__ */ jsxs("span", { children: [
2648
+ "Phone",
2649
+ config.enquiry.requirePhone ? "" : " (optional)"
2650
+ ] }),
2651
+ /* @__PURE__ */ jsx(
2652
+ "input",
2653
+ {
2654
+ "data-testid": TID.enquiryPhone,
2655
+ required: config.enquiry.requirePhone,
2656
+ value: customerPhone,
2657
+ onChange: (e) => setCustomerPhone(e.target.value),
2658
+ className: "w-full border bg-background px-3 py-2 outline-none focus:border-primary",
2659
+ style: { borderRadius: "var(--sf-radius-input)" }
2660
+ }
2661
+ )
2662
+ ] }),
2663
+ /* @__PURE__ */ jsxs("label", { className: "block space-y-1.5 text-sm font-medium", children: [
2664
+ /* @__PURE__ */ jsx("span", { children: "Subject" }),
2665
+ /* @__PURE__ */ jsx(
2666
+ "input",
2667
+ {
2668
+ "data-testid": TID.enquirySubject,
2669
+ required: true,
2670
+ value: subject,
2671
+ onChange: (e) => setSubject(e.target.value),
2672
+ className: "w-full border bg-background px-3 py-2 outline-none focus:border-primary",
2673
+ style: { borderRadius: "var(--sf-radius-input)" }
2674
+ }
2675
+ )
2676
+ ] }),
2677
+ /* @__PURE__ */ jsxs("label", { className: "block space-y-1.5 text-sm font-medium", children: [
2678
+ /* @__PURE__ */ jsx("span", { children: "Message" }),
2679
+ /* @__PURE__ */ jsx(
2680
+ "textarea",
2681
+ {
2682
+ "data-testid": TID.enquiryMessage,
2683
+ required: true,
2684
+ rows: 5,
2685
+ value: message,
2686
+ onChange: (e) => setMessage(e.target.value),
2687
+ className: "w-full resize-none border bg-background px-3 py-2 outline-none focus:border-primary",
2688
+ style: { borderRadius: "var(--sf-radius-input)" }
2689
+ }
2690
+ )
2691
+ ] }),
2692
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center gap-3", children: [
2693
+ /* @__PURE__ */ jsxs(
2694
+ "button",
2695
+ {
2696
+ "data-testid": TID.enquirySubmit,
2697
+ type: "submit",
2698
+ disabled: status === "submitting",
2699
+ 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",
2700
+ style: { borderRadius: "var(--sf-radius-button)" },
2701
+ children: [
2702
+ /* @__PURE__ */ jsx(Send, { className: "h-4 w-4" }),
2703
+ status === "submitting" ? "Sending..." : "Send enquiry"
2704
+ ]
2705
+ }
2706
+ ),
2707
+ config.enquiry.whatsappUrl && /* @__PURE__ */ jsxs(
2708
+ "a",
2709
+ {
2710
+ href: buildWhatsAppHref(config.enquiry.whatsappUrl, product),
2711
+ target: "_blank",
2712
+ rel: "noreferrer",
2713
+ className: "inline-flex items-center gap-2 border px-5 py-3 text-sm font-semibold transition-colors hover:bg-muted",
2714
+ style: { borderRadius: "var(--sf-radius-button)" },
2715
+ children: [
2716
+ /* @__PURE__ */ jsx(MessageCircle, { className: "h-4 w-4" }),
2717
+ "WhatsApp"
2718
+ ]
2719
+ }
2720
+ ),
2721
+ config.enquiry.email && /* @__PURE__ */ jsxs(
2722
+ "a",
2723
+ {
2724
+ href: `mailto:${config.enquiry.email}?subject=${encodeURIComponent(subject)}&body=${encodeURIComponent(message)}`,
2725
+ className: "inline-flex items-center gap-2 border px-5 py-3 text-sm font-semibold transition-colors hover:bg-muted",
2726
+ style: { borderRadius: "var(--sf-radius-button)" },
2727
+ children: [
2728
+ /* @__PURE__ */ jsx(Mail, { className: "h-4 w-4" }),
2729
+ "Email"
2730
+ ]
2731
+ }
2732
+ )
2733
+ ] }),
2734
+ status === "success" && /* @__PURE__ */ jsx("p", { "data-testid": TID.enquirySuccess, className: "text-sm font-medium text-emerald-600", children: config.enquiry.successMessage }),
2735
+ error && /* @__PURE__ */ jsx("p", { "data-testid": TID.enquiryError, className: "text-sm font-medium text-red-600", children: error })
2736
+ ]
2737
+ }
2738
+ );
2739
+ }
2095
2740
  function ProductDetailPage({ slug: slug2 }) {
2096
2741
  const config = useStorefrontConfig();
2742
+ const actions = useStorefrontActions();
2097
2743
  const { product, loading } = useProduct(slug2);
2098
2744
  const addItem = useCartStore((s) => s.addItem);
2099
2745
  const [qty, setQty] = useState(1);
@@ -2129,21 +2775,56 @@ function ProductDetailPage({ slug: slug2 }) {
2129
2775
  const soldOut = product.inventoryCount <= 0;
2130
2776
  const lowStock = !soldOut && product.inventoryCount <= 5;
2131
2777
  const image = product.images.find((i) => i.isPrimary) ?? product.images[0];
2778
+ const components = getStorefrontComponents(config);
2779
+ const ProductDetailComponent = components.ProductDetail;
2780
+ const addToCart = () => addItem(product, qty, selectedOptions);
2781
+ const openEnquiry = () => {
2782
+ document.getElementById("storefront-product-enquiry")?.scrollIntoView({ behavior: "smooth", block: "start" });
2783
+ };
2132
2784
  const discountPct = product.compareAtPrice && product.compareAtPrice > product.price ? Math.round((1 - product.price / product.compareAtPrice) * 100) : 0;
2133
- return /* @__PURE__ */ jsxs("main", { className: "mx-auto max-w-5xl px-4 py-8 sm:px-6", children: [
2785
+ if (ProductDetailComponent) {
2786
+ return /* @__PURE__ */ jsx(
2787
+ ProductDetailComponent,
2788
+ {
2789
+ product,
2790
+ config,
2791
+ commerceMode: config.commerceMode,
2792
+ primaryImage: image,
2793
+ optionGroups,
2794
+ selectedOptions,
2795
+ setSelectedOptions,
2796
+ quantity: qty,
2797
+ setQuantity: setQty,
2798
+ soldOut,
2799
+ lowStock,
2800
+ actions,
2801
+ addToCart,
2802
+ openEnquiry
2803
+ }
2804
+ );
2805
+ }
2806
+ return /* @__PURE__ */ jsxs("main", { ...storefrontComponentContracts.productDetail.root, className: "mx-auto max-w-5xl px-4 py-8 sm:px-6", children: [
2134
2807
  /* @__PURE__ */ jsxs(Link, { to: "/", className: "mb-6 inline-flex items-center gap-1 text-sm text-muted-foreground hover:text-foreground", children: [
2135
2808
  /* @__PURE__ */ jsx(ChevronLeft, { className: "h-4 w-4" }),
2136
2809
  " Continuar comprando"
2137
2810
  ] }),
2138
2811
  /* @__PURE__ */ jsxs("div", { className: "grid animate-fade-up gap-10 md:grid-cols-2", children: [
2139
- /* @__PURE__ */ jsx("div", { className: "group overflow-hidden border bg-muted", style: { borderRadius: "var(--sf-radius-card)" }, children: image && /* @__PURE__ */ jsx(
2140
- "img",
2812
+ /* @__PURE__ */ jsx(
2813
+ "div",
2141
2814
  {
2142
- src: image.url,
2143
- alt: image.altText ?? product.name,
2144
- className: "aspect-square w-full object-cover transition-transform duration-700 ease-out group-hover:scale-105"
2815
+ ...storefrontComponentContracts.productDetail.gallery,
2816
+ className: "group overflow-hidden border bg-muted",
2817
+ style: { borderRadius: "var(--sf-radius-card)" },
2818
+ children: image && /* @__PURE__ */ jsx(
2819
+ SmoothImage,
2820
+ {
2821
+ src: image.url,
2822
+ alt: image.altText ?? product.name,
2823
+ className: "aspect-square w-full object-cover transition-transform duration-700 ease-out group-hover:scale-105"
2824
+ }
2825
+ )
2145
2826
  }
2146
- ) }),
2827
+ ),
2147
2828
  /* @__PURE__ */ jsxs("div", { className: "flex flex-col py-2 lg:sticky lg:top-24 lg:self-start", children: [
2148
2829
  /* @__PURE__ */ jsxs("nav", { className: "mb-3 flex flex-wrap items-center gap-1.5 text-xs text-muted-foreground", children: [
2149
2830
  /* @__PURE__ */ jsx(Link, { to: "/", className: "transition-colors hover:text-foreground", children: "In\xEDcio" }),
@@ -2155,7 +2836,7 @@ function ProductDetailPage({ slug: slug2 }) {
2155
2836
  /* @__PURE__ */ jsx("span", { className: "text-foreground", children: product.name })
2156
2837
  ] }),
2157
2838
  /* @__PURE__ */ jsx("h1", { "data-testid": TID.pdpName, className: "sf-heading text-3xl font-bold tracking-tight", children: product.name }),
2158
- /* @__PURE__ */ jsxs("div", { className: "mt-3 flex items-center gap-3", children: [
2839
+ config.commerceMode === "checkout" && /* @__PURE__ */ jsxs("div", { className: "mt-3 flex items-center gap-3", children: [
2159
2840
  /* @__PURE__ */ jsx("div", { className: "text-xl", children: /* @__PURE__ */ jsx(
2160
2841
  Price,
2161
2842
  {
@@ -2172,7 +2853,7 @@ function ProductDetailPage({ slug: slug2 }) {
2172
2853
  ] })
2173
2854
  ] }),
2174
2855
  /* @__PURE__ */ jsx("p", { "data-testid": TID.pdpDescription, className: "mt-5 leading-relaxed text-muted-foreground", children: product.description }),
2175
- /* @__PURE__ */ jsxs("div", { className: "mt-4 flex flex-wrap items-center gap-x-3 gap-y-1 text-sm", children: [
2856
+ config.commerceMode === "checkout" && /* @__PURE__ */ jsxs("div", { className: "mt-4 flex flex-wrap items-center gap-x-3 gap-y-1 text-sm", children: [
2176
2857
  soldOut ? /* @__PURE__ */ jsxs("span", { className: "inline-flex items-center gap-2 font-medium text-muted-foreground", children: [
2177
2858
  /* @__PURE__ */ jsx("span", { className: "h-2 w-2 rounded-full bg-muted-foreground" }),
2178
2859
  " Sem estoque"
@@ -2198,7 +2879,7 @@ function ProductDetailPage({ slug: slug2 }) {
2198
2879
  onChange: setSelectedOptions
2199
2880
  }
2200
2881
  ),
2201
- /* @__PURE__ */ jsxs("div", { className: "mt-8 flex items-center gap-4", children: [
2882
+ /* @__PURE__ */ jsx("div", { ...storefrontComponentContracts.productDetail.actions, className: "mt-8", children: config.commerceMode === "checkout" ? /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-4", children: [
2202
2883
  /* @__PURE__ */ jsx(
2203
2884
  QuantityInput,
2204
2885
  {
@@ -2214,14 +2895,32 @@ function ProductDetailPage({ slug: slug2 }) {
2214
2895
  type: "button",
2215
2896
  "data-testid": TID.pdpAddToCart,
2216
2897
  disabled: soldOut,
2217
- onClick: () => addItem(product, qty, selectedOptions),
2898
+ onClick: addToCart,
2218
2899
  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",
2219
2900
  style: { borderRadius: "var(--sf-radius-button)" },
2220
2901
  children: soldOut ? "Esgotado" : "Adicionar ao carrinho"
2221
2902
  }
2222
2903
  )
2223
- ] }),
2224
- /* @__PURE__ */ jsxs("div", { className: "mt-6 grid grid-cols-3 gap-3 border-t pt-6 text-center", children: [
2904
+ ] }) : config.commerceMode === "enquiry" ? /* @__PURE__ */ jsx(
2905
+ "button",
2906
+ {
2907
+ type: "button",
2908
+ ...storefrontComponentContracts.productDetail.enquiryButton,
2909
+ onClick: openEnquiry,
2910
+ 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",
2911
+ style: { borderRadius: "var(--sf-radius-button)" },
2912
+ children: config.enquiry.label
2913
+ }
2914
+ ) : /* @__PURE__ */ jsx(
2915
+ Link,
2916
+ {
2917
+ to: config.catalogPath,
2918
+ className: "inline-flex border px-5 py-3 text-sm font-semibold transition-colors hover:bg-muted",
2919
+ style: { borderRadius: "var(--sf-radius-button)" },
2920
+ children: "Voltar ao cat\xE1logo"
2921
+ }
2922
+ ) }),
2923
+ config.commerceMode === "checkout" && /* @__PURE__ */ jsxs("div", { className: "mt-6 grid grid-cols-3 gap-3 border-t pt-6 text-center", children: [
2225
2924
  /* @__PURE__ */ jsxs("div", { className: "flex flex-col items-center gap-1.5 text-[11px] font-medium text-muted-foreground", children: [
2226
2925
  /* @__PURE__ */ jsx(Truck, { className: "h-5 w-5 text-primary" }),
2227
2926
  " Envio r\xE1pido"
@@ -2237,6 +2936,11 @@ function ProductDetailPage({ slug: slug2 }) {
2237
2936
  ] })
2238
2937
  ] })
2239
2938
  ] }),
2939
+ config.commerceMode === "enquiry" && /* @__PURE__ */ jsx("section", { id: "storefront-product-enquiry", className: "mt-12 border-t pt-10", children: /* @__PURE__ */ jsxs("div", { className: "mx-auto max-w-3xl", children: [
2940
+ /* @__PURE__ */ jsx("h2", { className: "sf-heading text-2xl font-semibold", children: "Need more information?" }),
2941
+ /* @__PURE__ */ jsx("p", { className: "mt-2 text-muted-foreground", children: "Send an enquiry about this product." }),
2942
+ /* @__PURE__ */ jsx("div", { className: "mt-6", children: /* @__PURE__ */ jsx(ProductEnquiryForm, { product }) })
2943
+ ] }) }),
2240
2944
  /* @__PURE__ */ jsx(ProductSpecs, { product }),
2241
2945
  /* @__PURE__ */ jsx(RelatedProducts, { product })
2242
2946
  ] });
@@ -2277,7 +2981,8 @@ async function placeStorefrontOrder({
2277
2981
  }))
2278
2982
  });
2279
2983
  if (markPaid) {
2280
- await provider.updateOrder(order.id, { financialStatus: "paid" });
2984
+ if (provider.confirmPayment) await provider.confirmPayment(order.id);
2985
+ else await provider.updateOrder(order.id, { financialStatus: "paid" });
2281
2986
  }
2282
2987
  return { order, customerId: customerId ?? order.customerId ?? "" };
2283
2988
  }
@@ -2792,7 +3497,7 @@ function CheckoutPage() {
2792
3497
  /* @__PURE__ */ jsx("h2", { className: "sr-only", children: "Resumo do pedido" }),
2793
3498
  /* @__PURE__ */ jsx("ul", { className: "space-y-4", children: cart.lines.map((line) => /* @__PURE__ */ jsxs("li", { className: "flex gap-3 text-sm", children: [
2794
3499
  /* @__PURE__ */ jsxs("div", { className: "relative h-16 w-16 flex-none overflow-hidden rounded-lg border bg-background", children: [
2795
- line.imageUrl && /* @__PURE__ */ jsx("img", { src: line.imageUrl, alt: line.name, className: "h-full w-full object-cover" }),
3500
+ line.imageUrl && /* @__PURE__ */ jsx(SmoothImage, { src: line.imageUrl, alt: line.name, className: "h-full w-full object-cover" }),
2796
3501
  /* @__PURE__ */ 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 })
2797
3502
  ] }),
2798
3503
  /* @__PURE__ */ jsxs("div", { className: "min-w-0 flex-1", children: [
@@ -3214,7 +3919,7 @@ function OrderConfirmationPage({ orderId }) {
3214
3919
  /* @__PURE__ */ jsx(OrderTrackingTimeline, { order })
3215
3920
  ] }),
3216
3921
  /* @__PURE__ */ jsx("ul", { className: "mt-8 space-y-3 text-left", children: order.items.map((item) => /* @__PURE__ */ jsxs("li", { className: "flex items-center gap-3 text-sm", children: [
3217
- item.imageUrl && /* @__PURE__ */ jsx("img", { src: item.imageUrl, alt: item.name, className: "h-12 w-12 rounded-lg border object-cover" }),
3922
+ item.imageUrl && /* @__PURE__ */ jsx(SmoothImage, { src: item.imageUrl, alt: item.name, className: "h-12 w-12 rounded-lg border object-cover" }),
3218
3923
  /* @__PURE__ */ jsxs("span", { className: "flex-1", children: [
3219
3924
  item.name,
3220
3925
  " ",
@@ -3422,7 +4127,7 @@ function OrdersPanel() {
3422
4127
  /* @__PURE__ */ jsx("p", { className: "mt-0.5 text-xs text-muted-foreground", children: new Date(order.createdAt).toLocaleDateString(config.locale) }),
3423
4128
  /* @__PURE__ */ jsx("div", { className: "mt-4", children: /* @__PURE__ */ jsx(OrderTrackingTimeline, { order, compact: true }) }),
3424
4129
  /* @__PURE__ */ jsx("ul", { className: "mt-3 flex flex-wrap gap-2", children: order.items.map((item) => /* @__PURE__ */ jsxs("li", { className: "flex items-center gap-2 rounded-lg border px-2 py-1 text-xs", children: [
3425
- item.imageUrl && /* @__PURE__ */ jsx("img", { src: item.imageUrl, alt: item.name, className: "h-6 w-6 rounded object-cover" }),
4130
+ item.imageUrl && /* @__PURE__ */ jsx(SmoothImage, { src: item.imageUrl, alt: item.name, className: "h-6 w-6 rounded object-cover" }),
3426
4131
  item.name,
3427
4132
  " \xD7 ",
3428
4133
  item.quantity
@@ -3602,7 +4307,7 @@ function PolicyPage({ kind }) {
3602
4307
  ] }) })
3603
4308
  ] });
3604
4309
  }
3605
- var StorefrontErrorBoundary = class extends React18.Component {
4310
+ var StorefrontErrorBoundary = class extends React6.Component {
3606
4311
  constructor() {
3607
4312
  super(...arguments);
3608
4313
  this.state = { error: null };
@@ -3703,8 +4408,8 @@ function RouteSwitch({ path }) {
3703
4408
  if (product?.slug) return /* @__PURE__ */ jsx(ProductDetailPage, { slug: product.slug });
3704
4409
  const order = matchPath("/order/:id", path);
3705
4410
  if (order?.id) return /* @__PURE__ */ jsx(OrderConfirmationPage, { orderId: order.id });
3706
- if (matchPath("/checkout", path)) return /* @__PURE__ */ jsx(CheckoutPage, {});
3707
- if (matchPath("/account", path)) return /* @__PURE__ */ jsx(MyPurchasesPage, {});
4411
+ if (matchPath("/checkout", path) && config.features.checkout) return /* @__PURE__ */ jsx(CheckoutPage, {});
4412
+ if (matchPath("/account", path) && config.features.accounts) return /* @__PURE__ */ jsx(MyPurchasesPage, {});
3708
4413
  if (matchPath("/catalog", path)) return /* @__PURE__ */ jsx(CatalogPage, {});
3709
4414
  if (matchPath("/privacy", path)) return /* @__PURE__ */ jsx(PolicyPage, { kind: "privacy" });
3710
4415
  if (matchPath("/terms", path)) return /* @__PURE__ */ jsx(PolicyPage, { kind: "terms" });
@@ -3746,12 +4451,26 @@ function buildMockSeed(config) {
3746
4451
  };
3747
4452
  }
3748
4453
  function initStorefrontRuntime(config) {
3749
- initCustomerAuth(resolveAuthAdapter(config.auth?.adapter));
4454
+ if (resolveConfig(config).features.accounts) {
4455
+ initCustomerAuth(resolveAuthAdapter(config.auth, {
4456
+ supabaseUrl: config.supabaseUrl,
4457
+ supabaseAnonKey: config.supabaseAnonKey
4458
+ }));
4459
+ }
3750
4460
  if (config.provider) {
3751
4461
  setShopProvider(config.provider);
3752
4462
  } else {
3753
4463
  setShopProvider(createMockShopProvider(buildMockSeed(config)));
3754
4464
  }
4465
+ const reconcileCart = () => {
4466
+ const provider = getShopProvider();
4467
+ void useCartStore.getState().reconcile(async (line) => {
4468
+ const product = await provider.getProduct(line.productId);
4469
+ return product != null;
4470
+ });
4471
+ };
4472
+ if (useCartStore.persist.hasHydrated()) reconcileCart();
4473
+ else useCartStore.persist.onFinishHydration(reconcileCart);
3755
4474
  if (config.supabaseUrl || config.supabaseAnonKey) {
3756
4475
  console.warn(
3757
4476
  "@fayz-ai/shop: supabaseUrl/supabaseAnonKey are legacy fields. Pass an explicit provider/adapter or use the Fayz SDK broker path."
@@ -3762,14 +4481,20 @@ function StorefrontShell() {
3762
4481
  const config = useStorefrontConfig();
3763
4482
  const path = useHashPath();
3764
4483
  const focused = isFocusedRoute(config, path);
3765
- return /* @__PURE__ */ jsxs("div", { className: "min-h-screen bg-background text-foreground", children: [
4484
+ const components = getStorefrontComponents(config);
4485
+ const chromeProps = { config, commerceMode: config.commerceMode };
4486
+ const body = /* @__PURE__ */ jsxs(Fragment, { children: [
3766
4487
  config.theme && /* @__PURE__ */ jsx(StorefrontThemeStyle, { theme: config.theme }),
3767
- !focused && /* @__PURE__ */ jsx(StorefrontHeader, {}),
4488
+ !focused && (components.Header ? /* @__PURE__ */ jsx(components.Header, { ...chromeProps }) : /* @__PURE__ */ jsx(StorefrontHeader, {})),
3768
4489
  /* @__PURE__ */ jsx(StorefrontErrorBoundary, { children: /* @__PURE__ */ jsx(RouteSwitch, { path }) }, path),
3769
- !focused && /* @__PURE__ */ jsx(CartDrawer, {}),
3770
- !focused && /* @__PURE__ */ jsx(StorefrontFooter, {}),
4490
+ !focused && config.features.cart && /* @__PURE__ */ jsx(CartDrawer, {}),
4491
+ !focused && (components.Footer ? /* @__PURE__ */ jsx(components.Footer, { ...chromeProps }) : /* @__PURE__ */ jsx(StorefrontFooter, {})),
3771
4492
  /* @__PURE__ */ jsx(Toaster, {})
3772
4493
  ] });
4494
+ if (components.Shell) {
4495
+ return /* @__PURE__ */ jsx(components.Shell, { ...chromeProps, children: body });
4496
+ }
4497
+ return /* @__PURE__ */ jsx("div", { className: "min-h-screen bg-background text-foreground", children: body });
3773
4498
  }
3774
4499
  function createStorefrontApp(config) {
3775
4500
  const resolved = resolveConfig(config);
@@ -3780,6 +4505,37 @@ function createStorefrontApp(config) {
3780
4505
  StorefrontApp.displayName = "StorefrontApp";
3781
4506
  return StorefrontApp;
3782
4507
  }
4508
+ function createStorefront(options) {
4509
+ const routes = [...options.pages ?? [], ...options.routes ?? []];
4510
+ const config = {
4511
+ ...options.config,
4512
+ provider: options.provider ?? options.config.provider,
4513
+ components: {
4514
+ ...options.config.components,
4515
+ ...options.components
4516
+ },
4517
+ routes: routes.length > 0 ? routes : options.config.routes,
4518
+ home: options.sections ? { ...options.config.home ?? { sections: [] }, sections: options.sections } : options.config.home
4519
+ };
4520
+ return createStorefrontApp(config);
4521
+ }
4522
+
4523
+ // src/define.ts
4524
+ function defineStorefrontConfig(config) {
4525
+ return config;
4526
+ }
4527
+ function defineStorefrontComponents(components) {
4528
+ return components;
4529
+ }
4530
+ function defineStorefrontRoutes(routes) {
4531
+ return routes;
4532
+ }
4533
+ function defineStorefrontSections(sections) {
4534
+ return sections;
4535
+ }
4536
+ function defineStorefrontApp(options) {
4537
+ return options;
4538
+ }
3783
4539
  var runtimeConfigRegistry = /* @__PURE__ */ new Map();
3784
4540
  function slug(name) {
3785
4541
  return name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "") || "app";
@@ -3838,7 +4594,7 @@ function manifestToStorefrontConfig(manifest, surfaceName) {
3838
4594
  provider: manifest.backend?.provider,
3839
4595
  url: manifest.backend?.url
3840
4596
  },
3841
- slots: runtimeConfig?.slots,
4597
+ components: runtimeConfig?.components,
3842
4598
  routes: runtimeConfig?.routes,
3843
4599
  provider: runtimeConfig?.provider,
3844
4600
  auth: runtimeConfig?.auth,
@@ -3846,9 +4602,9 @@ function manifestToStorefrontConfig(manifest, surfaceName) {
3846
4602
  };
3847
4603
  }
3848
4604
  function StorefrontScaffold({ manifest, surface }) {
3849
- const config = React18.useMemo(() => manifestToStorefrontConfig(manifest, surface), [manifest, surface]);
3850
- const resolved = React18.useMemo(() => resolveConfig(config), [config]);
3851
- const inited = React18.useRef(false);
4605
+ const config = React6.useMemo(() => manifestToStorefrontConfig(manifest, surface), [manifest, surface]);
4606
+ const resolved = React6.useMemo(() => resolveConfig(config), [config]);
4607
+ const inited = React6.useRef(false);
3852
4608
  if (!inited.current) {
3853
4609
  initStorefrontRuntime(config);
3854
4610
  inited.current = true;
@@ -4074,13 +4830,75 @@ var atelierTemplate = {
4074
4830
  ]
4075
4831
  })
4076
4832
  };
4833
+ var foodTemplate = {
4834
+ id: "food",
4835
+ inspiration: "Delivery / restaurant storefront",
4836
+ announcement: "ENTREGA GR\xC1TIS ACIMA DE R$ 150 \xB7 PE\xC7A J\xC1",
4837
+ theme: {
4838
+ name: "food",
4839
+ colors: {
4840
+ background: "30 33% 97%",
4841
+ foreground: "20 14% 15%",
4842
+ primary: "14 84% 50%",
4843
+ primaryForeground: "0 0% 100%",
4844
+ card: "30 33% 99%",
4845
+ cardForeground: "20 14% 15%",
4846
+ muted: "28 20% 92%",
4847
+ mutedForeground: "22 10% 42%",
4848
+ border: "28 18% 85%",
4849
+ headerBackground: "20 14% 11%",
4850
+ headerForeground: "30 24% 96%",
4851
+ announcementBackground: "14 84% 50%",
4852
+ announcementForeground: "0 0% 100%"
4853
+ },
4854
+ font: { heading: "Poppins", body: "Inter" },
4855
+ radius: "round",
4856
+ header: { variant: "classic" },
4857
+ productCard: { style: "card", imageAspect: "square" }
4858
+ },
4859
+ home: (storeName, images) => ({
4860
+ sections: [
4861
+ {
4862
+ type: "hero",
4863
+ variant: "banner",
4864
+ height: "tall",
4865
+ slides: [
4866
+ { 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] }
4867
+ ]
4868
+ },
4869
+ {
4870
+ type: "benefits",
4871
+ items: [
4872
+ { icon: "Flame", title: "Feito na hora", text: "Preparado com capricho" },
4873
+ { icon: "Truck", title: "Entrega r\xE1pida", text: "Frete gr\xE1tis acima de R$ 150" },
4874
+ { icon: "CreditCard", title: "Pagamento f\xE1cil", text: "Pix, cart\xE3o e mais" }
4875
+ ]
4876
+ },
4877
+ { type: "categories", style: "tiles", title: "Nosso card\xE1pio" },
4878
+ { type: "products", title: "Mais pedidos", eyebrow: "favoritos da casa", filter: "all", limit: 8 },
4879
+ { type: "banner", title: "Combo do dia", eyebrow: "oferta", subtitle: "Sele\xE7\xE3o especial com desconto", cta: "Aproveitar", hue: 14, image: images?.banners?.[0] },
4880
+ { type: "products", title: "Novidades no card\xE1pio", filter: "new", limit: 4 },
4881
+ {
4882
+ type: "testimonials",
4883
+ title: "O que dizem por a\xED",
4884
+ items: [
4885
+ { quote: "Chegou quentinho e absurdo de bom. Virei cliente!", author: "Rafael M." },
4886
+ { quote: "Sabor de restaurante em casa, sem complica\xE7\xE3o.", author: "Beatriz L." },
4887
+ { quote: "Entrega r\xE1pida e por\xE7\xE3o generosa. Recomendo demais.", author: "Diego S." }
4888
+ ]
4889
+ },
4890
+ { type: "newsletter", title: "Receba nossas ofertas", subtitle: "Promo\xE7\xF5es e novidades do card\xE1pio, direto no seu e-mail." }
4891
+ ]
4892
+ })
4893
+ };
4077
4894
  var storefrontTemplates = {
4078
4895
  mare: mareTemplate,
4079
4896
  sertao: sertaoTemplate,
4080
4897
  volt: voltTemplate,
4081
- atelier: atelierTemplate
4898
+ atelier: atelierTemplate,
4899
+ food: foodTemplate
4082
4900
  };
4083
4901
 
4084
- export { BenefitsRow, CartDrawer, CatalogPage, CategoryShowcase, CheckoutPage, FiltersPanel, HeroSection, HomePage, Link, ManifestoBlock, MyPurchasesPage, NewsletterBand, OrderConfirmationPage, OrderTrackingTimeline, Price, ProductCard, ProductDetailPage, ProductGrid, ProductOptionSelector, ProductRail, PromoBanner, QuantityInput, Reveal, StorefrontFooter, StorefrontHeader, StorefrontScaffold, StorefrontShell, StorefrontThemeStyle, TID, Testimonials, atelierTemplate, bannerPlaceholder, createStorefrontApp, defineStorefront, establishCustomerSession, formatMoney, formatProductOptionSelection, getCustomerAuthAdapter, getProductOptionGroups, initStorefrontRuntime, mareTemplate, matchPath, navigateTo, normalizeProductOptionSelection, placeStorefrontOrder, prefersReducedMotion, productCardSlotContract, productOptionSelectionKey, registerStorefrontBlocks, resolveAuthAdapter, roundCents, sectionsToBlocks, selectCount, selectDiscountTotal, selectShipping, selectSubtotal, selectTotal, sertaoTemplate, signInByEmail, signOutCustomer, signUpCustomer, storefrontSlotContracts, storefrontTemplates, themeToCss, useCartStore, useCatalogStore, useCategories, useDiscountValidator, useHashPath, useInView, useMyOrders, usePopOnChange, useProduct, useProducts, useScrolled, useSessionStore, useStorefrontConfig, voltTemplate };
4902
+ export { BenefitsRow, CartDrawer, CatalogPage, CategoryShowcase, CheckoutPage, FiltersPanel, HeroSection, HomePage, Link, ManifestoBlock, MediaCarousel, MyPurchasesPage, NewsletterBand, OrderConfirmationPage, OrderTrackingTimeline, Price, ProductCard, ProductDetailPage, ProductEnquiryForm, ProductGrid, ProductOptionSelector, ProductRail, ProductSlider, PromoBanner, QuantityInput, Reveal, SmoothImage, StorefrontFooter, StorefrontHeader, StorefrontScaffold, StorefrontSections, StorefrontShell, StorefrontThemeStyle, TID, Testimonials, atelierTemplate, bannerPlaceholder, createStorefront, createStorefrontApp, defineStorefront, defineStorefrontApp, defineStorefrontComponents, defineStorefrontConfig, defineStorefrontRoutes, defineStorefrontSections, establishCustomerSession, foodTemplate, formatMoney, formatProductOptionSelection, getCustomerAuthAdapter, getProductOptionGroups, initStorefrontRuntime, mareTemplate, matchPath, navigateTo, normalizeProductOptionSelection, placeStorefrontOrder, prefersReducedMotion, productCardComponentContract, productOptionSelectionKey, registerStorefrontBlocks, resolveAuthAdapter, roundCents, sectionsToBlocks, selectCount, selectDiscountTotal, selectShipping, selectSubtotal, selectTotal, sertaoTemplate, signInByEmail, signOutCustomer, signUpCustomer, storefrontComponentContracts, storefrontTemplates, themeToCss, useCart, useCartStore, useCatalog, useCatalogStore, useCategories, useDiscountValidator, useEnquiry, useHashPath, useInView, useMyOrders, usePopOnChange, useProduct, useProducts, useScrolled, useSessionStore, useStorefront, useStorefrontActions, useStorefrontConfig, useStorefrontConfigOptional, voltTemplate };
4085
4903
  //# sourceMappingURL=index.js.map
4086
4904
  //# sourceMappingURL=index.js.map