@fayz-ai/storefront 0.6.0 → 0.7.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 (94) hide show
  1. package/dist/auth.d.ts +12 -2
  2. package/dist/auth.d.ts.map +1 -1
  3. package/dist/blocks.d.ts +3 -3
  4. package/dist/blocks.d.ts.map +1 -1
  5. package/dist/component-contracts.d.ts +124 -0
  6. package/dist/component-contracts.d.ts.map +1 -0
  7. package/dist/component-selectors.d.ts +61 -0
  8. package/dist/component-selectors.d.ts.map +1 -0
  9. package/dist/components/CartDrawer.d.ts.map +1 -1
  10. package/dist/components/ProductCard.d.ts +2 -5
  11. package/dist/components/ProductCard.d.ts.map +1 -1
  12. package/dist/components/ProductEnquiryForm.d.ts +8 -0
  13. package/dist/components/ProductEnquiryForm.d.ts.map +1 -0
  14. package/dist/components/ProductGrid.d.ts.map +1 -1
  15. package/dist/components/RelatedProducts.d.ts +1 -1
  16. package/dist/components/RelatedProducts.d.ts.map +1 -1
  17. package/dist/components/SmoothImage.d.ts +7 -0
  18. package/dist/components/SmoothImage.d.ts.map +1 -0
  19. package/dist/components/StorefrontFooter.d.ts.map +1 -1
  20. package/dist/components/StorefrontHeader.d.ts.map +1 -1
  21. package/dist/components/sections/CategoryShowcase.d.ts.map +1 -1
  22. package/dist/components/sections/HeroSection.d.ts.map +1 -1
  23. package/dist/components/sections/MediaCarousel.d.ts +11 -0
  24. package/dist/components/sections/MediaCarousel.d.ts.map +1 -0
  25. package/dist/components/sections/MiscSections.d.ts.map +1 -1
  26. package/dist/components/sections/ProductRail.d.ts.map +1 -1
  27. package/dist/components/sections/ProductSlider.d.ts +15 -0
  28. package/dist/components/sections/ProductSlider.d.ts.map +1 -0
  29. package/dist/config.d.ts +38 -21
  30. package/dist/config.d.ts.map +1 -1
  31. package/dist/createStorefrontApp.d.ts +12 -0
  32. package/dist/createStorefrontApp.d.ts.map +1 -1
  33. package/dist/define.d.ts +17 -0
  34. package/dist/define.d.ts.map +1 -0
  35. package/dist/hooks/index.d.ts +1 -0
  36. package/dist/hooks/index.d.ts.map +1 -1
  37. package/dist/hooks/useStorefront.d.ts +35 -0
  38. package/dist/hooks/useStorefront.d.ts.map +1 -0
  39. package/dist/index.cjs +904 -185
  40. package/dist/index.cjs.map +1 -1
  41. package/dist/index.d.ts +20 -10
  42. package/dist/index.d.ts.map +1 -1
  43. package/dist/index.js +797 -95
  44. package/dist/index.js.map +1 -1
  45. package/dist/pages/CheckoutPage.d.ts.map +1 -1
  46. package/dist/pages/HomePage.d.ts +5 -2
  47. package/dist/pages/HomePage.d.ts.map +1 -1
  48. package/dist/pages/MyPurchasesPage.d.ts.map +1 -1
  49. package/dist/pages/OrderConfirmationPage.d.ts.map +1 -1
  50. package/dist/pages/ProductDetailPage.d.ts.map +1 -1
  51. package/dist/sections.d.ts +45 -2
  52. package/dist/sections.d.ts.map +1 -1
  53. package/dist/testids.d.ts +20 -0
  54. package/dist/testids.d.ts.map +1 -1
  55. package/dist/theme.d.ts +4 -0
  56. package/dist/theme.d.ts.map +1 -1
  57. package/package.json +7 -9
  58. package/src/auth.ts +47 -10
  59. package/src/blocks.tsx +7 -3
  60. package/src/component-contracts.ts +140 -0
  61. package/src/component-selectors.ts +31 -0
  62. package/src/components/CartDrawer.tsx +2 -1
  63. package/src/components/ProductCard.tsx +39 -23
  64. package/src/components/ProductEnquiryForm.tsx +175 -0
  65. package/src/components/ProductGrid.tsx +12 -3
  66. package/src/components/RelatedProducts.tsx +13 -4
  67. package/src/components/SmoothImage.tsx +59 -0
  68. package/src/components/StorefrontFooter.tsx +24 -14
  69. package/src/components/StorefrontHeader.tsx +43 -38
  70. package/src/components/sections/CategoryShowcase.tsx +3 -2
  71. package/src/components/sections/HeroSection.tsx +18 -2
  72. package/src/components/sections/MediaCarousel.tsx +141 -0
  73. package/src/components/sections/MiscSections.tsx +2 -1
  74. package/src/components/sections/ProductRail.tsx +11 -3
  75. package/src/components/sections/ProductSlider.tsx +119 -0
  76. package/src/config.ts +65 -24
  77. package/src/createStorefrontApp.tsx +62 -9
  78. package/src/define.ts +34 -0
  79. package/src/hooks/index.ts +1 -0
  80. package/src/hooks/useStorefront.ts +58 -0
  81. package/src/index.ts +61 -11
  82. package/src/pages/CheckoutPage.tsx +2 -1
  83. package/src/pages/HomePage.tsx +6 -2
  84. package/src/pages/MyPurchasesPage.tsx +2 -1
  85. package/src/pages/OrderConfirmationPage.tsx +2 -1
  86. package/src/pages/ProductDetailPage.tsx +138 -62
  87. package/src/scaffold.tsx +1 -1
  88. package/src/sections.ts +49 -2
  89. package/src/testids.ts +20 -0
  90. package/src/theme.ts +4 -0
  91. package/src/workflows/checkout.ts +1 -1
  92. package/dist/slot-contracts.d.ts +0 -34
  93. package/dist/slot-contracts.d.ts.map +0 -1
  94. 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,7 +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);
207
+ return React6.createElement("a", { href: `#${to}`, ...rest }, children);
166
208
  }
167
209
  var RADIUS_MAP = {
168
210
  none: { button: "0px", card: "0px", input: "0px" },
@@ -248,7 +290,7 @@ function StorefrontThemeStyle({ theme }) {
248
290
  }
249
291
  link.href = fontsHref;
250
292
  }, [fontsHref]);
251
- return React18.createElement("style", { "data-sf-theme": theme.name }, themeToCss(theme));
293
+ return React6.createElement("style", { "data-sf-theme": theme.name }, themeToCss(theme));
252
294
  }
253
295
 
254
296
  // src/format.ts
@@ -496,6 +538,13 @@ var TID = {
496
538
  hero: "home-hero",
497
539
  heroPrev: "hero-prev",
498
540
  heroNext: "hero-next",
541
+ mediaCarousel: "media-carousel",
542
+ mediaCarouselPrev: "media-carousel-prev",
543
+ mediaCarouselNext: "media-carousel-next",
544
+ productSlider: "product-slider",
545
+ productSliderViewport: "product-slider-viewport",
546
+ productSliderPrev: "product-slider-prev",
547
+ productSliderNext: "product-slider-next",
499
548
  categoryShowcase: "category-showcase",
500
549
  productRail: "product-rail",
501
550
  benefits: "benefits-row",
@@ -530,6 +579,19 @@ var TID = {
530
579
  productOptionValue: (id, value) => `product-option-${id.toLowerCase().replace(/\s+/g, "-")}-${value.toLowerCase().replace(/\s+/g, "-")}`,
531
580
  pdpQtyInput: "pdp-qty-input",
532
581
  pdpAddToCart: "pdp-add-to-cart",
582
+ pdpRoot: "pdp-root",
583
+ pdpGallery: "pdp-gallery",
584
+ pdpActions: "pdp-actions",
585
+ pdpEnquiryButton: "pdp-enquiry-button",
586
+ enquiryForm: "enquiry-form",
587
+ enquiryName: "enquiry-name",
588
+ enquiryEmail: "enquiry-email",
589
+ enquiryPhone: "enquiry-phone",
590
+ enquirySubject: "enquiry-subject",
591
+ enquiryMessage: "enquiry-message",
592
+ enquirySubmit: "enquiry-submit",
593
+ enquirySuccess: "enquiry-success",
594
+ enquiryError: "enquiry-error",
533
595
  // cart
534
596
  cartDrawer: "cart-drawer",
535
597
  cartLine: "cart-line",
@@ -757,7 +819,7 @@ function HeaderActions() {
757
819
  }
758
820
  )
759
821
  ] }),
760
- /* @__PURE__ */ jsxs(
822
+ config.features.cart && /* @__PURE__ */ jsxs(
761
823
  "button",
762
824
  {
763
825
  type: "button",
@@ -784,12 +846,13 @@ function NavLinks({ className }) {
784
846
  const config = useStorefrontConfig();
785
847
  const { categories } = useCategories();
786
848
  const setCategoryId = useCatalogStore((s) => s.setCategoryId);
849
+ const showCategories = config.theme?.header?.showCategories !== false;
787
850
  const goCategory = (categoryId) => {
788
851
  useCatalogStore.getState().reset();
789
852
  setCategoryId(categoryId);
790
853
  navigateTo(config.catalogPath);
791
854
  };
792
- return /* @__PURE__ */ jsxs("nav", { className: `flex items-center gap-6 ${className ?? ""}`, children: [
855
+ return /* @__PURE__ */ jsxs("nav", { className: `flex min-w-max items-center gap-6 ${className ?? ""}`, children: [
793
856
  config.nav.map((link) => /* @__PURE__ */ jsx(
794
857
  Link,
795
858
  {
@@ -800,7 +863,7 @@ function NavLinks({ className }) {
800
863
  },
801
864
  link.to
802
865
  )),
803
- categories.slice(0, 6).map((c) => /* @__PURE__ */ jsxs(
866
+ showCategories && categories.slice(0, 6).map((c) => /* @__PURE__ */ jsxs(
804
867
  "button",
805
868
  {
806
869
  type: "button",
@@ -820,6 +883,7 @@ function StorefrontHeader() {
820
883
  const config = useStorefrontConfig();
821
884
  const variant = config.theme?.header?.variant ?? "classic";
822
885
  const scrolled = useScrolled();
886
+ const showSearch = config.theme?.header?.showSearch !== false;
823
887
  const logo = /* @__PURE__ */ jsx(Link, { to: "/", className: "sf-heading shrink-0 text-xl font-bold tracking-tight", children: config.logo ?? config.name });
824
888
  return /* @__PURE__ */ jsxs(
825
889
  "header",
@@ -833,21 +897,21 @@ function StorefrontHeader() {
833
897
  // Rio/Flex pattern: centered logo row, nav row below
834
898
  /* @__PURE__ */ jsxs(Fragment, { children: [
835
899
  /* @__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" }),
900
+ showSearch ? /* @__PURE__ */ jsx(SearchInput, { className: "hidden w-full max-w-xs sm:block" }) : /* @__PURE__ */ jsx("div", {}),
837
901
  /* @__PURE__ */ jsx("div", { className: "text-center", children: logo }),
838
902
  /* @__PURE__ */ jsx("div", { className: "justify-self-end", children: /* @__PURE__ */ jsx(HeaderActions, {}) })
839
903
  ] }),
840
- /* @__PURE__ */ jsx("div", { className: "hidden justify-center border-t py-2.5 sm:flex", style: headerDivider, children: /* @__PURE__ */ jsx(NavLinks, {}) })
904
+ /* @__PURE__ */ jsx("div", { className: "flex overflow-x-auto border-t py-2.5 sm:justify-center", style: headerDivider, children: /* @__PURE__ */ jsx(NavLinks, {}) })
841
905
  ] })
842
906
  ) : variant === "search" ? (
843
907
  // Brasília pattern: prominent search left, logo center, actions right; nav row below
844
908
  /* @__PURE__ */ jsxs(Fragment, { children: [
845
909
  /* @__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" }),
910
+ showSearch ? /* @__PURE__ */ jsx(SearchInput, { className: "hidden w-full max-w-sm sm:block" }) : /* @__PURE__ */ jsx("div", {}),
847
911
  /* @__PURE__ */ jsx("div", { className: "text-center", children: logo }),
848
912
  /* @__PURE__ */ jsx("div", { className: "justify-self-end", children: /* @__PURE__ */ jsx(HeaderActions, {}) })
849
913
  ] }),
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, {}) }) })
914
+ /* @__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
915
  ] })
852
916
  ) : variant === "minimal" ? (
853
917
  // Uyuni pattern: nav left, centered logo, actions right — single compact row
@@ -855,7 +919,7 @@ function StorefrontHeader() {
855
919
  /* @__PURE__ */ jsx("div", { className: "flex items-center gap-5", children: /* @__PURE__ */ jsx(NavLinks, { className: "hidden sm:flex" }) }),
856
920
  /* @__PURE__ */ jsx("div", { className: "text-center", children: logo }),
857
921
  /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-end gap-3", children: [
858
- /* @__PURE__ */ jsx(SearchInput, { className: "hidden w-44 lg:block" }),
922
+ showSearch && /* @__PURE__ */ jsx(SearchInput, { className: "hidden w-44 lg:block" }),
859
923
  /* @__PURE__ */ jsx(HeaderActions, {})
860
924
  ] })
861
925
  ] })
@@ -864,7 +928,7 @@ function StorefrontHeader() {
864
928
  /* @__PURE__ */ jsxs("div", { className: "mx-auto flex h-16 max-w-7xl items-center gap-6 px-4 sm:px-6", children: [
865
929
  logo,
866
930
  /* @__PURE__ */ jsx(NavLinks, { className: "hidden md:flex" }),
867
- /* @__PURE__ */ jsx(SearchInput, { className: "ml-auto hidden w-full max-w-sm sm:block" }),
931
+ showSearch && /* @__PURE__ */ jsx(SearchInput, { className: "ml-auto hidden w-full max-w-sm sm:block" }),
868
932
  /* @__PURE__ */ jsx(HeaderActions, {})
869
933
  ] })
870
934
  )
@@ -877,6 +941,17 @@ function StorefrontFooter() {
877
941
  const { categories } = useCategories();
878
942
  const visibleCategories = categories.slice(0, 8);
879
943
  const hiddenCategoryCount = Math.max(categories.length - visibleCategories.length, 0);
944
+ const labels = {
945
+ categories: "Departamentos",
946
+ navigation: "Navega\xE7\xE3o",
947
+ contact: "Contato",
948
+ viewAll: "Ver todos",
949
+ purchases: "Minhas compras",
950
+ privacy: "Privacidade",
951
+ terms: "Termos",
952
+ returns: "Trocas e devolu\xE7\xF5es",
953
+ ...config.footer?.labels
954
+ };
880
955
  const goCategory = (categoryId) => {
881
956
  useCatalogStore.getState().reset();
882
957
  useCatalogStore.getState().setCategoryId(categoryId);
@@ -889,7 +964,7 @@ function StorefrontFooter() {
889
964
  config.footer?.about && /* @__PURE__ */ jsx("p", { className: "mt-3 text-sm leading-relaxed text-muted-foreground", children: config.footer.about })
890
965
  ] }),
891
966
  /* @__PURE__ */ jsxs("div", { children: [
892
- /* @__PURE__ */ jsx("h3", { className: "mb-3 text-sm font-semibold uppercase tracking-wide text-muted-foreground", children: "Departamentos" }),
967
+ /* @__PURE__ */ jsx("h3", { className: "mb-3 text-sm font-semibold uppercase tracking-wide text-muted-foreground", children: labels.categories }),
893
968
  /* @__PURE__ */ jsxs("ul", { className: "space-y-2 text-sm", children: [
894
969
  visibleCategories.map((c) => /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsx(
895
970
  "button",
@@ -901,21 +976,22 @@ function StorefrontFooter() {
901
976
  }
902
977
  ) }, c.id)),
903
978
  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 (",
979
+ labels.viewAll,
980
+ " (",
905
981
  categories.length,
906
982
  ")"
907
983
  ] }) })
908
984
  ] })
909
985
  ] }),
910
986
  /* @__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" }),
987
+ /* @__PURE__ */ jsx("h3", { className: "mb-3 text-sm font-semibold uppercase tracking-wide text-muted-foreground", children: labels.navigation }),
912
988
  /* @__PURE__ */ jsxs("ul", { className: "space-y-2 text-sm", children: [
913
989
  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" }) })
990
+ config.features.accounts && /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsx(Link, { to: "/account", className: "transition-opacity hover:opacity-70", children: labels.purchases }) })
915
991
  ] })
916
992
  ] }),
917
993
  /* @__PURE__ */ jsxs("div", { children: [
918
- /* @__PURE__ */ jsx("h3", { className: "mb-3 text-sm font-semibold uppercase tracking-wide text-muted-foreground", children: "Contato" }),
994
+ /* @__PURE__ */ jsx("h3", { className: "mb-3 text-sm font-semibold uppercase tracking-wide text-muted-foreground", children: labels.contact }),
919
995
  /* @__PURE__ */ jsxs("ul", { className: "space-y-2 text-sm text-muted-foreground", children: [
920
996
  config.footer?.contact?.phone && /* @__PURE__ */ jsx("li", { children: config.footer.contact.phone }),
921
997
  config.footer?.contact?.email && /* @__PURE__ */ jsx("li", { children: config.footer.contact.email }),
@@ -926,17 +1002,15 @@ function StorefrontFooter() {
926
1002
  ] }),
927
1003
  /* @__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
1004
  /* @__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" })
1005
+ /* @__PURE__ */ jsx(Link, { to: "/privacy", className: "transition-colors hover:text-foreground", children: labels.privacy }),
1006
+ /* @__PURE__ */ jsx(Link, { to: "/terms", className: "transition-colors hover:text-foreground", children: labels.terms }),
1007
+ /* @__PURE__ */ jsx(Link, { to: "/refunds", className: "transition-colors hover:text-foreground", children: labels.returns })
932
1008
  ] }),
933
- /* @__PURE__ */ jsxs("p", { className: "text-center", children: [
1009
+ /* @__PURE__ */ jsx("p", { className: "text-center", children: config.footer?.credit ?? /* @__PURE__ */ jsxs(Fragment, { children: [
934
1010
  "\xA9 ",
935
1011
  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
- ] })
1012
+ " \u2014 powered by Fayz"
1013
+ ] }) })
940
1014
  ] }) })
941
1015
  ] });
942
1016
  }
@@ -1029,6 +1103,53 @@ function QuantityInput({ value, onChange, min = 1, max, testId, incTestId, decTe
1029
1103
  )
1030
1104
  ] });
1031
1105
  }
1106
+ function SmoothImage({
1107
+ imageLoading,
1108
+ loading = "lazy",
1109
+ decoding = "async",
1110
+ onLoad,
1111
+ style,
1112
+ ...props
1113
+ }) {
1114
+ const config = useStorefrontConfigOptional();
1115
+ const resolved = {
1116
+ ...config?.imageLoading,
1117
+ ...imageLoading
1118
+ };
1119
+ const mode = resolved.mode ?? "fade";
1120
+ const durationMs = resolved.durationMs ?? 420;
1121
+ const easing = resolved.easing ?? "cubic-bezier(0.22, 1, 0.36, 1)";
1122
+ const blur = resolved.blur ?? true;
1123
+ const ref = React6.useRef(null);
1124
+ const [loaded, setLoaded] = React6.useState(mode === "none");
1125
+ React6.useEffect(() => {
1126
+ if (mode === "none") {
1127
+ setLoaded(true);
1128
+ return;
1129
+ }
1130
+ if (ref.current?.complete && ref.current.naturalWidth > 0) setLoaded(true);
1131
+ }, [mode, props.src]);
1132
+ const revealStyle = mode === "none" ? {} : {
1133
+ opacity: loaded ? 1 : 0,
1134
+ filter: blur && !loaded ? "blur(10px)" : "blur(0px)",
1135
+ transition: `opacity ${durationMs}ms ${easing}, filter ${durationMs}ms ${easing}, transform ${durationMs}ms ${easing}`
1136
+ };
1137
+ return /* @__PURE__ */ jsx(
1138
+ "img",
1139
+ {
1140
+ ...props,
1141
+ ref,
1142
+ loading,
1143
+ decoding,
1144
+ "data-sf-image-loaded": loaded ? "true" : "false",
1145
+ style: { ...style, ...revealStyle },
1146
+ onLoad: (event) => {
1147
+ setLoaded(true);
1148
+ onLoad?.(event);
1149
+ }
1150
+ }
1151
+ );
1152
+ }
1032
1153
  function CartDrawer() {
1033
1154
  const config = useStorefrontConfig();
1034
1155
  const cart = useCartStore();
@@ -1128,7 +1249,7 @@ function CartDrawer() {
1128
1249
  "data-line-id": line.lineId ?? line.productId,
1129
1250
  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
1251
  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" }),
1252
+ line.imageUrl && /* @__PURE__ */ jsx(SmoothImage, { src: line.imageUrl, alt: line.name, className: "h-20 w-20 shrink-0 rounded-lg border object-cover" }),
1132
1253
  /* @__PURE__ */ jsxs("div", { className: "flex flex-1 flex-col", children: [
1133
1254
  /* @__PURE__ */ jsxs("div", { className: "flex items-start justify-between gap-2", children: [
1134
1255
  /* @__PURE__ */ jsx("span", { className: "text-sm font-medium leading-snug", children: line.name }),
@@ -1298,36 +1419,91 @@ function Price({ value, compareAt, testId, compareTestId, className }) {
1298
1419
  ] });
1299
1420
  }
1300
1421
 
1301
- // src/slot-contracts.ts
1302
- var productCardSlotContract = {
1422
+ // src/component-selectors.ts
1423
+ var productCardComponentContract = {
1303
1424
  root: { "data-testid": TID.productCard },
1304
1425
  name: { "data-testid": TID.productCardName },
1305
1426
  priceTestId: TID.productCardPrice,
1306
1427
  addButton: { "data-testid": TID.productCardAdd }
1307
1428
  };
1308
- var storefrontSlotContracts = {
1309
- productCard: productCardSlotContract
1429
+ var storefrontComponentContracts = {
1430
+ productCard: productCardComponentContract,
1431
+ productDetail: {
1432
+ root: { "data-testid": TID.pdpRoot },
1433
+ gallery: { "data-testid": TID.pdpGallery },
1434
+ name: { "data-testid": TID.pdpName },
1435
+ description: { "data-testid": TID.pdpDescription },
1436
+ actions: { "data-testid": TID.pdpActions },
1437
+ addButton: { "data-testid": TID.pdpAddToCart },
1438
+ enquiryButton: { "data-testid": TID.pdpEnquiryButton },
1439
+ enquiryForm: { "data-testid": TID.enquiryForm }
1440
+ }
1310
1441
  };
1311
- function ProductCard({ product }) {
1442
+ function useStorefront() {
1443
+ return useStorefrontConfig();
1444
+ }
1445
+ function useCatalog() {
1312
1446
  const config = useStorefrontConfig();
1313
- const addItem = useCartStore((s) => s.addItem);
1447
+ const catalog = useCatalogStore();
1448
+ return {
1449
+ ...catalog,
1450
+ catalogPath: config.catalogPath,
1451
+ config
1452
+ };
1453
+ }
1454
+ function useCart() {
1455
+ return useCartStore();
1456
+ }
1457
+ function useEnquiry() {
1458
+ const config = useStorefrontConfig();
1459
+ const createProductEnquiry = async (input) => {
1460
+ const provider = getShopProvider();
1461
+ if (!provider.createProductEnquiry) {
1462
+ throw new Error("The active shop provider does not support product enquiries.");
1463
+ }
1464
+ return provider.createProductEnquiry(input);
1465
+ };
1466
+ return {
1467
+ enquiry: config.enquiry,
1468
+ createProductEnquiry
1469
+ };
1470
+ }
1471
+ function useStorefrontActions() {
1472
+ const addItem = useCartStore((state) => state.addItem);
1473
+ const openDrawer = useCartStore((state) => state.openDrawer);
1474
+ const closeDrawer = useCartStore((state) => state.closeDrawer);
1475
+ const { createProductEnquiry } = useEnquiry();
1476
+ return {
1477
+ navigateTo,
1478
+ addToCart: (product, quantity, options) => addItem(product, quantity, options),
1479
+ openCart: openDrawer,
1480
+ closeCart: closeDrawer,
1481
+ createProductEnquiry
1482
+ };
1483
+ }
1484
+ function ProductCard({ product, config: providedConfig, actions: providedActions }) {
1485
+ const runtimeConfig = useStorefrontConfig();
1486
+ const runtimeActions = useStorefrontActions();
1487
+ const config = providedConfig ?? runtimeConfig;
1488
+ const actions = providedActions ?? runtimeActions;
1314
1489
  const hasOptions = getProductOptionGroups(product).length > 0;
1315
1490
  const soldOut = product.inventoryCount <= 0;
1316
1491
  const onSale = product.compareAtPrice != null && product.compareAtPrice > product.price;
1317
1492
  const image = product.images.find((i) => i.isPrimary) ?? product.images[0];
1318
1493
  const cardStyle = config.theme?.productCard?.style ?? "card";
1319
1494
  const aspect = config.theme?.productCard?.imageAspect === "portrait" ? "aspect-[3/4]" : "aspect-square";
1495
+ const purchaseMode = config.commerceMode === "checkout";
1320
1496
  return /* @__PURE__ */ jsxs(
1321
1497
  "div",
1322
1498
  {
1323
- ...productCardSlotContract.root,
1499
+ ...productCardComponentContract.root,
1324
1500
  "data-slug": product.slug,
1325
1501
  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
1502
  style: { borderRadius: "var(--sf-radius-card)" },
1327
1503
  children: [
1328
1504
  /* @__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
1505
  image && /* @__PURE__ */ jsx(
1330
- "img",
1506
+ SmoothImage,
1331
1507
  {
1332
1508
  src: image.url,
1333
1509
  alt: image.altText ?? product.name,
@@ -1360,33 +1536,37 @@ function ProductCard({ product }) {
1360
1536
  Link,
1361
1537
  {
1362
1538
  to: `/product/${product.slug}`,
1363
- ...productCardSlotContract.name,
1539
+ ...productCardComponentContract.name,
1364
1540
  className: "font-medium leading-snug hover:underline",
1365
1541
  children: product.name
1366
1542
  }
1367
1543
  ),
1368
- /* @__PURE__ */ jsxs("div", { className: "mt-auto flex items-center justify-between pt-2", children: [
1369
- /* @__PURE__ */ jsx(
1544
+ /* @__PURE__ */ jsxs("div", { className: "mt-auto flex items-center justify-between gap-3 pt-2", children: [
1545
+ purchaseMode ? /* @__PURE__ */ jsx(
1370
1546
  Price,
1371
1547
  {
1372
1548
  value: product.price,
1373
1549
  compareAt: product.compareAtPrice,
1374
- testId: productCardSlotContract.priceTestId
1550
+ testId: productCardComponentContract.priceTestId
1375
1551
  }
1376
- ),
1552
+ ) : /* @__PURE__ */ jsx("span", { "data-testid": productCardComponentContract.priceTestId, className: "text-sm text-muted-foreground", children: config.commerceMode === "enquiry" ? config.enquiry.label : "Ver detalhes" }),
1377
1553
  /* @__PURE__ */ jsx(
1378
1554
  "button",
1379
1555
  {
1380
1556
  type: "button",
1381
- ...productCardSlotContract.addButton,
1382
- disabled: soldOut,
1383
- "aria-label": hasOptions ? `Escolher op\xE7\xF5es de ${product.name}` : `Adicionar ${product.name} ao carrinho`,
1557
+ ...productCardComponentContract.addButton,
1558
+ disabled: purchaseMode && soldOut,
1559
+ "aria-label": purchaseMode ? hasOptions ? `Escolher op\xE7\xF5es de ${product.name}` : `Adicionar ${product.name} ao carrinho` : `${config.enquiry.label} ${product.name}`,
1384
1560
  onClick: () => {
1561
+ if (!purchaseMode) {
1562
+ navigateTo(`/product/${product.slug}`);
1563
+ return;
1564
+ }
1385
1565
  if (hasOptions) {
1386
1566
  navigateTo(`/product/${product.slug}`);
1387
1567
  return;
1388
1568
  }
1389
- addItem(product);
1569
+ actions.addToCart(product);
1390
1570
  },
1391
1571
  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
1572
  children: /* @__PURE__ */ jsx(ShoppingBag, { className: "h-4 w-4" })
@@ -1400,14 +1580,25 @@ function ProductCard({ product }) {
1400
1580
  }
1401
1581
  function ProductGrid({ products, loading }) {
1402
1582
  const config = useStorefrontConfig();
1403
- const ProductCardComponent = config.slots?.ProductCard ?? ProductCard;
1583
+ const actions = useStorefrontActions();
1584
+ const components = getStorefrontComponents(config);
1585
+ const ProductCardComponent = components.ProductCard ?? ProductCard;
1404
1586
  if (loading) {
1405
1587
  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
1588
  }
1407
1589
  if (products.length === 0) {
1408
1590
  return /* @__PURE__ */ jsx("div", { "data-testid": TID.productGrid, className: "py-20 text-center text-muted-foreground", children: "Nenhum produto encontrado." });
1409
1591
  }
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)) });
1592
+ 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(
1593
+ ProductCardComponent,
1594
+ {
1595
+ product: p,
1596
+ config,
1597
+ commerceMode: config.commerceMode,
1598
+ actions
1599
+ },
1600
+ p.id
1601
+ )) });
1411
1602
  }
1412
1603
  function FiltersPanel() {
1413
1604
  const { categories } = useCategories();
@@ -1645,8 +1836,15 @@ function HeroSection({
1645
1836
  height = "tall"
1646
1837
  }) {
1647
1838
  const [index, setIndex] = useState(0);
1839
+ const [loadedSlides, setLoadedSlides] = useState({});
1840
+ const config = useStorefrontConfig();
1648
1841
  const interacted = useRef(false);
1649
1842
  const hClass = height === "tall" ? "h-[72vh] min-h-[440px]" : "h-[48vh] min-h-[340px]";
1843
+ const revealImages = config.imageLoading.mode !== "none";
1844
+ const revealStyle = (loaded) => revealImages ? {
1845
+ filter: config.imageLoading.blur && !loaded ? "blur(10px)" : "blur(0px)",
1846
+ transition: `opacity 700ms ease, filter ${config.imageLoading.durationMs}ms ${config.imageLoading.easing}`
1847
+ } : void 0;
1650
1848
  useEffect(() => {
1651
1849
  if (variant !== "slider" || slides.length < 2 || prefersReducedMotion()) return;
1652
1850
  const timer = setInterval(() => {
@@ -1667,7 +1865,7 @@ function HeroSection({
1667
1865
  className: "group relative block h-[42vh] min-h-[320px] overflow-hidden",
1668
1866
  children: [
1669
1867
  /* @__PURE__ */ jsx(
1670
- "img",
1868
+ SmoothImage,
1671
1869
  {
1672
1870
  src: slideImage(slide, 900, 700),
1673
1871
  alt: slide.title,
@@ -1699,7 +1897,11 @@ function HeroSection({
1699
1897
  src: slideImage(slide),
1700
1898
  alt: slide.title,
1701
1899
  "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"}`
1900
+ loading: i === 0 ? "eager" : "lazy",
1901
+ decoding: "async",
1902
+ onLoad: () => setLoadedSlides((current) => ({ ...current, [i]: true })),
1903
+ style: revealStyle(Boolean(loadedSlides[i])),
1904
+ 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
1905
  },
1704
1906
  i
1705
1907
  )),
@@ -1761,7 +1963,7 @@ function CategoryShowcase({ style, title }) {
1761
1963
  title && /* @__PURE__ */ jsx(Reveal, { children: /* @__PURE__ */ jsx("h2", { className: "sf-heading mb-8 text-center text-3xl font-bold", children: title }) }),
1762
1964
  /* @__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
1965
  /* @__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",
1966
+ SmoothImage,
1765
1967
  {
1766
1968
  src: c.imageUrl ?? bannerPlaceholder(c.name, 30 + i * 70, 70 + i * 70, 200, 200),
1767
1969
  alt: c.name,
@@ -1776,7 +1978,7 @@ function CategoryShowcase({ style, title }) {
1776
1978
  title && /* @__PURE__ */ jsx(Reveal, { children: /* @__PURE__ */ jsx("h2", { className: "sf-heading mb-8 text-center text-3xl font-bold", children: title }) }),
1777
1979
  /* @__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
1980
  /* @__PURE__ */ jsx(
1779
- "img",
1981
+ SmoothImage,
1780
1982
  {
1781
1983
  src: c.imageUrl ?? bannerPlaceholder(c.name, 30 + i * 70, 70 + i * 70, 480, 600),
1782
1984
  alt: c.name,
@@ -1798,7 +2000,9 @@ function ProductRail({
1798
2000
  limit = 4
1799
2001
  }) {
1800
2002
  const config = useStorefrontConfig();
1801
- const ProductCardComponent = config.slots?.ProductCard ?? ProductCard;
2003
+ const actions = useStorefrontActions();
2004
+ const components = getStorefrontComponents(config);
2005
+ const ProductCardComponent = components.ProductCard ?? ProductCard;
1802
2006
  const { products, loading } = useProducts(
1803
2007
  filter === "new" ? { status: "active", orderBy: "created_at", order: "desc" } : { status: "active" }
1804
2008
  );
@@ -1814,7 +2018,15 @@ function ProductRail({
1814
2018
  /* @__PURE__ */ jsx("h2", { className: "sf-heading text-3xl font-bold tracking-tight", children: title }),
1815
2019
  subtitle && /* @__PURE__ */ jsx("p", { className: "mt-2 text-muted-foreground", children: subtitle })
1816
2020
  ] }),
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)) }),
2021
+ 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(
2022
+ ProductCardComponent,
2023
+ {
2024
+ product: p,
2025
+ config,
2026
+ commerceMode: config.commerceMode,
2027
+ actions
2028
+ }
2029
+ ) }, p.id)) }),
1818
2030
  /* @__PURE__ */ jsx(Reveal, { className: "mt-10 text-center", delay: 200, children: /* @__PURE__ */ jsx(
1819
2031
  Link,
1820
2032
  {
@@ -1826,6 +2038,201 @@ function ProductRail({
1826
2038
  ) })
1827
2039
  ] });
1828
2040
  }
2041
+ var heightClass = {
2042
+ medium: "h-[58vh] min-h-[420px]",
2043
+ tall: "h-[74vh] min-h-[520px]",
2044
+ screen: "min-h-[calc(100vh-64px)]"
2045
+ };
2046
+ var alignClass = {
2047
+ start: "items-start text-left",
2048
+ center: "items-center text-center",
2049
+ end: "items-end text-right"
2050
+ };
2051
+ function MediaCarousel({
2052
+ items,
2053
+ height = "tall",
2054
+ autoplayMs = 7e3,
2055
+ overlay = "dark",
2056
+ className = ""
2057
+ }) {
2058
+ const [index, setIndex] = useState(0);
2059
+ const [loadedItems, setLoadedItems] = useState({});
2060
+ const config = useStorefrontConfig();
2061
+ const interacted = useRef(false);
2062
+ const revealImages = config.imageLoading.mode !== "none";
2063
+ const revealStyle = (loaded) => revealImages ? {
2064
+ filter: config.imageLoading.blur && !loaded ? "blur(10px)" : "blur(0px)",
2065
+ transition: `opacity 700ms ease, filter ${config.imageLoading.durationMs}ms ${config.imageLoading.easing}`
2066
+ } : void 0;
2067
+ useEffect(() => {
2068
+ if (items.length < 2 || autoplayMs <= 0 || prefersReducedMotion()) return;
2069
+ const timer = setInterval(() => {
2070
+ if (!interacted.current) setIndex((i) => (i + 1) % items.length);
2071
+ }, autoplayMs);
2072
+ return () => clearInterval(timer);
2073
+ }, [autoplayMs, items.length]);
2074
+ if (items.length === 0) return null;
2075
+ const active = items[Math.min(index, items.length - 1)];
2076
+ const goTo = (next) => {
2077
+ interacted.current = true;
2078
+ setIndex((next + items.length) % items.length);
2079
+ };
2080
+ 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" : "";
2081
+ return /* @__PURE__ */ jsxs("section", { "data-testid": TID.mediaCarousel, className: `relative isolate overflow-hidden ${heightClass[height]} ${className}`, children: [
2082
+ items.map((item, i) => /* @__PURE__ */ jsx(
2083
+ "img",
2084
+ {
2085
+ src: item.image,
2086
+ alt: item.imageAlt ?? item.title,
2087
+ "aria-hidden": i !== index,
2088
+ loading: i === 0 ? "eager" : "lazy",
2089
+ decoding: "async",
2090
+ onLoad: () => setLoadedItems((current) => ({ ...current, [i]: true })),
2091
+ style: revealStyle(Boolean(loadedItems[i])),
2092
+ 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"}`
2093
+ },
2094
+ `${item.title}-${i}`
2095
+ )),
2096
+ /* @__PURE__ */ jsx("div", { className: `absolute inset-0 -z-10 ${overlayClass}` }),
2097
+ /* @__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: [
2098
+ active.eyebrow && /* @__PURE__ */ jsx("p", { className: "mb-4 text-xs font-semibold uppercase tracking-[0.24em] text-white/80", children: active.eyebrow }),
2099
+ /* @__PURE__ */ jsx("h1", { className: "sf-heading text-5xl font-normal leading-[0.95] sm:text-7xl lg:text-8xl", children: active.title }),
2100
+ active.subtitle && /* @__PURE__ */ jsx("p", { className: "mt-5 max-w-xl text-base leading-8 text-white/88 sm:text-lg", children: active.subtitle }),
2101
+ active.cta && /* @__PURE__ */ jsx(
2102
+ Link,
2103
+ {
2104
+ to: active.href ?? "/catalog",
2105
+ 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",
2106
+ children: active.cta
2107
+ }
2108
+ )
2109
+ ] }) }),
2110
+ items.length > 1 && /* @__PURE__ */ jsxs(Fragment, { children: [
2111
+ /* @__PURE__ */ jsx(
2112
+ "button",
2113
+ {
2114
+ type: "button",
2115
+ "aria-label": "Previous slide",
2116
+ "data-testid": TID.mediaCarouselPrev,
2117
+ onClick: () => goTo(index - 1),
2118
+ 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",
2119
+ children: /* @__PURE__ */ jsx(ChevronLeft, { className: "h-5 w-5" })
2120
+ }
2121
+ ),
2122
+ /* @__PURE__ */ jsx(
2123
+ "button",
2124
+ {
2125
+ type: "button",
2126
+ "aria-label": "Next slide",
2127
+ "data-testid": TID.mediaCarouselNext,
2128
+ onClick: () => goTo(index + 1),
2129
+ 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",
2130
+ children: /* @__PURE__ */ jsx(ChevronRight, { className: "h-5 w-5" })
2131
+ }
2132
+ ),
2133
+ /* @__PURE__ */ jsx("div", { className: "absolute bottom-5 left-1/2 flex -translate-x-1/2 gap-2", children: items.map((item, i) => /* @__PURE__ */ jsx(
2134
+ "button",
2135
+ {
2136
+ type: "button",
2137
+ "aria-label": `Go to slide ${i + 1}`,
2138
+ onClick: () => goTo(i),
2139
+ className: `h-2 rounded-full transition-all ${i === index ? "w-8 bg-white" : "w-2 bg-white/55 hover:bg-white"}`
2140
+ },
2141
+ `${item.title}-dot-${i}`
2142
+ )) })
2143
+ ] })
2144
+ ] });
2145
+ }
2146
+ function ProductSlider({
2147
+ title,
2148
+ subtitle,
2149
+ eyebrow,
2150
+ filter = "all",
2151
+ categoryName,
2152
+ collection,
2153
+ limit = 10,
2154
+ cta = "View all",
2155
+ href,
2156
+ className = ""
2157
+ }) {
2158
+ const config = useStorefrontConfig();
2159
+ const actions = useStorefrontActions();
2160
+ const viewportRef = useRef(null);
2161
+ const components = getStorefrontComponents(config);
2162
+ const ProductCardComponent = components.ProductCard ?? ProductCard;
2163
+ const { products, loading } = useProducts(
2164
+ filter === "new" ? { status: "active", orderBy: "created_at", order: "desc" } : { status: "active" }
2165
+ );
2166
+ const visible = useMemo(() => {
2167
+ let list = products;
2168
+ if (filter === "sale") list = list.filter((p) => p.compareAtPrice != null && p.compareAtPrice > p.price);
2169
+ if (categoryName) list = list.filter((p) => p.categoryName === categoryName);
2170
+ if (collection) list = list.filter((p) => p.metadata?.collection === collection);
2171
+ return list.slice(0, limit);
2172
+ }, [categoryName, collection, filter, limit, products]);
2173
+ const scroll = (direction) => {
2174
+ viewportRef.current?.scrollBy({ left: direction * Math.max(320, viewportRef.current.clientWidth * 0.75), behavior: "smooth" });
2175
+ };
2176
+ if (!loading && visible.length === 0) return null;
2177
+ return /* @__PURE__ */ jsxs("section", { "data-testid": TID.productSlider, className: `mx-auto max-w-7xl px-4 py-14 sm:px-6 ${className}`, children: [
2178
+ /* @__PURE__ */ jsxs("div", { className: "mb-8 flex flex-col gap-5 sm:flex-row sm:items-end sm:justify-between", children: [
2179
+ /* @__PURE__ */ jsxs("div", { className: "max-w-2xl", children: [
2180
+ eyebrow && /* @__PURE__ */ jsx("p", { className: "mb-2 text-xs font-semibold uppercase tracking-[0.2em] text-primary", children: eyebrow }),
2181
+ /* @__PURE__ */ jsx("h2", { className: "sf-heading text-3xl font-normal leading-tight tracking-normal sm:text-5xl", children: title }),
2182
+ subtitle && /* @__PURE__ */ jsx("p", { className: "mt-3 leading-7 text-muted-foreground", children: subtitle })
2183
+ ] }),
2184
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
2185
+ /* @__PURE__ */ jsx(
2186
+ "button",
2187
+ {
2188
+ type: "button",
2189
+ "aria-label": "Previous products",
2190
+ "data-testid": TID.productSliderPrev,
2191
+ onClick: () => scroll(-1),
2192
+ className: "inline-flex h-10 w-10 items-center justify-center border border-border bg-background transition hover:bg-foreground hover:text-background",
2193
+ children: /* @__PURE__ */ jsx(ChevronLeft, { className: "h-5 w-5" })
2194
+ }
2195
+ ),
2196
+ /* @__PURE__ */ jsx(
2197
+ "button",
2198
+ {
2199
+ type: "button",
2200
+ "aria-label": "Next products",
2201
+ "data-testid": TID.productSliderNext,
2202
+ onClick: () => scroll(1),
2203
+ className: "inline-flex h-10 w-10 items-center justify-center border border-border bg-background transition hover:bg-foreground hover:text-background",
2204
+ children: /* @__PURE__ */ jsx(ChevronRight, { className: "h-5 w-5" })
2205
+ }
2206
+ )
2207
+ ] })
2208
+ ] }),
2209
+ /* @__PURE__ */ jsx(
2210
+ "div",
2211
+ {
2212
+ ref: viewportRef,
2213
+ "data-testid": TID.productSliderViewport,
2214
+ className: "flex snap-x gap-5 overflow-x-auto pb-5 [scrollbar-width:thin]",
2215
+ 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(
2216
+ ProductCardComponent,
2217
+ {
2218
+ product,
2219
+ config,
2220
+ commerceMode: config.commerceMode,
2221
+ actions
2222
+ }
2223
+ ) }, product.id))
2224
+ }
2225
+ ),
2226
+ cta && /* @__PURE__ */ jsx(
2227
+ Link,
2228
+ {
2229
+ to: href ?? config.catalogPath,
2230
+ 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",
2231
+ children: cta
2232
+ }
2233
+ )
2234
+ ] });
2235
+ }
1829
2236
  function Icon({ name, className }) {
1830
2237
  const C = LucideIcons[name];
1831
2238
  return C ? /* @__PURE__ */ jsx(C, { className }) : null;
@@ -1850,7 +2257,7 @@ function PromoBanner({
1850
2257
  }) {
1851
2258
  return /* @__PURE__ */ jsxs("section", { "data-testid": TID.promoBanner, className: "group relative my-12 overflow-hidden", children: [
1852
2259
  /* @__PURE__ */ jsx(
1853
- "img",
2260
+ SmoothImage,
1854
2261
  {
1855
2262
  src: image ?? bannerPlaceholder(title, hue, hue + 50, 1600, 420),
1856
2263
  alt: title,
@@ -1953,8 +2360,10 @@ function registerStorefrontBlocks() {
1953
2360
  if (registered) return;
1954
2361
  registered = true;
1955
2362
  registerBlock("hero", HeroSection, { source: "sdk", label: "Hero" });
2363
+ registerBlock("mediaCarousel", MediaCarousel, { source: "sdk", label: "Media carousel" });
1956
2364
  registerBlock("categories", CategoryShowcase, { source: "sdk", label: "Categories" });
1957
2365
  registerBlock("products", ProductRail, { source: "sdk", label: "Product rail" });
2366
+ registerBlock("productSlider", ProductSlider, { source: "sdk", label: "Product slider" });
1958
2367
  registerBlock("benefits", BenefitsRow, { source: "sdk", label: "Benefits row" });
1959
2368
  registerBlock("banner", PromoBanner, { source: "sdk", label: "Promo banner" });
1960
2369
  registerBlock("manifesto", ManifestoBlock, { source: "sdk", label: "Manifesto" });
@@ -1968,9 +2377,12 @@ function sectionsToBlocks(sections) {
1968
2377
  return { type, id: `${type}-${i}`, props };
1969
2378
  });
1970
2379
  }
1971
- function HomePage({ sections }) {
2380
+ function StorefrontSections({ sections }) {
1972
2381
  return /* @__PURE__ */ jsx("main", { children: renderBlocks(sectionsToBlocks(sections)) });
1973
2382
  }
2383
+ function HomePage({ sections }) {
2384
+ return /* @__PURE__ */ jsx(StorefrontSections, { sections });
2385
+ }
1974
2386
  function useProduct(slug2) {
1975
2387
  const [product, setProduct] = useState(null);
1976
2388
  const [loading, setLoading] = useState(true);
@@ -2082,18 +2494,208 @@ function ProductSpecs({ product }) {
2082
2494
  }
2083
2495
  function RelatedProducts({ product }) {
2084
2496
  const config = useStorefrontConfig();
2085
- const Card = config.slots?.ProductCard ?? ProductCard;
2497
+ const actions = useStorefrontActions();
2498
+ const components = getStorefrontComponents(config);
2499
+ const Card = components.ProductCard ?? ProductCard;
2086
2500
  const opts = product.categoryId ? { categoryId: product.categoryId, status: "active", limit: 5 } : { status: "active", limit: 5 };
2087
2501
  const { products, loading } = useProducts(opts);
2088
2502
  const related = products.filter((p) => p.id !== product.id).slice(0, 4);
2089
2503
  if (loading || related.length === 0) return null;
2090
2504
  return /* @__PURE__ */ jsxs("section", { className: "mt-16", children: [
2091
2505
  /* @__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)) })
2506
+ /* @__PURE__ */ jsx("div", { className: "mt-6 grid grid-cols-2 gap-5 md:grid-cols-4", children: related.map((p) => /* @__PURE__ */ jsx(
2507
+ Card,
2508
+ {
2509
+ product: p,
2510
+ config,
2511
+ commerceMode: config.commerceMode,
2512
+ actions
2513
+ },
2514
+ p.id
2515
+ )) })
2093
2516
  ] });
2094
2517
  }
2518
+ function defaultSubject(prefix, product) {
2519
+ return `${prefix}: ${product.name}`;
2520
+ }
2521
+ function buildWhatsAppHref(url, product) {
2522
+ const text = encodeURIComponent(`Hello, I would like more information about ${product.name}.`);
2523
+ const sep = url.includes("?") ? "&" : "?";
2524
+ return `${url}${sep}text=${text}`;
2525
+ }
2526
+ function ProductEnquiryForm({ product, onSuccess }) {
2527
+ const config = useStorefrontConfig();
2528
+ const [customerName, setCustomerName] = useState("");
2529
+ const [customerEmail, setCustomerEmail] = useState("");
2530
+ const [customerPhone, setCustomerPhone] = useState("");
2531
+ const [subject, setSubject] = useState(defaultSubject(config.enquiry.subjectPrefix, product));
2532
+ const [message, setMessage] = useState(`I would like more information about ${product.name}.`);
2533
+ const [status, setStatus] = useState("idle");
2534
+ const [error, setError] = useState(null);
2535
+ const submit = async (event) => {
2536
+ event.preventDefault();
2537
+ setStatus("submitting");
2538
+ setError(null);
2539
+ try {
2540
+ const provider = getShopProvider();
2541
+ if (provider.createProductEnquiry) {
2542
+ await provider.createProductEnquiry({
2543
+ productId: product.id,
2544
+ productName: product.name,
2545
+ productSlug: product.slug,
2546
+ customerName,
2547
+ customerEmail,
2548
+ customerPhone: customerPhone || void 0,
2549
+ subject,
2550
+ message,
2551
+ sourceUrl: typeof window === "undefined" ? void 0 : window.location.href,
2552
+ metadata: { sku: product.sku, categoryName: product.categoryName }
2553
+ });
2554
+ } else if (config.enquiry.email) {
2555
+ window.location.href = `mailto:${config.enquiry.email}?subject=${encodeURIComponent(subject)}&body=${encodeURIComponent(message)}`;
2556
+ }
2557
+ setStatus("success");
2558
+ onSuccess?.();
2559
+ } catch (e) {
2560
+ setStatus("error");
2561
+ setError(e instanceof Error ? e.message : String(e));
2562
+ }
2563
+ };
2564
+ return /* @__PURE__ */ jsxs(
2565
+ "form",
2566
+ {
2567
+ ...storefrontComponentContracts.productDetail.enquiryForm,
2568
+ onSubmit: submit,
2569
+ className: "space-y-4",
2570
+ children: [
2571
+ /* @__PURE__ */ jsxs("div", { className: "grid gap-4 sm:grid-cols-2", children: [
2572
+ /* @__PURE__ */ jsxs("label", { className: "space-y-1.5 text-sm font-medium", children: [
2573
+ /* @__PURE__ */ jsx("span", { children: "Your name" }),
2574
+ /* @__PURE__ */ jsx(
2575
+ "input",
2576
+ {
2577
+ "data-testid": TID.enquiryName,
2578
+ required: true,
2579
+ value: customerName,
2580
+ onChange: (e) => setCustomerName(e.target.value),
2581
+ className: "w-full border bg-background px-3 py-2 outline-none focus:border-primary",
2582
+ style: { borderRadius: "var(--sf-radius-input)" }
2583
+ }
2584
+ )
2585
+ ] }),
2586
+ /* @__PURE__ */ jsxs("label", { className: "space-y-1.5 text-sm font-medium", children: [
2587
+ /* @__PURE__ */ jsx("span", { children: "Your email" }),
2588
+ /* @__PURE__ */ jsx(
2589
+ "input",
2590
+ {
2591
+ "data-testid": TID.enquiryEmail,
2592
+ required: true,
2593
+ type: "email",
2594
+ value: customerEmail,
2595
+ onChange: (e) => setCustomerEmail(e.target.value),
2596
+ className: "w-full border bg-background px-3 py-2 outline-none focus:border-primary",
2597
+ style: { borderRadius: "var(--sf-radius-input)" }
2598
+ }
2599
+ )
2600
+ ] })
2601
+ ] }),
2602
+ /* @__PURE__ */ jsxs("label", { className: "block space-y-1.5 text-sm font-medium", children: [
2603
+ /* @__PURE__ */ jsxs("span", { children: [
2604
+ "Phone",
2605
+ config.enquiry.requirePhone ? "" : " (optional)"
2606
+ ] }),
2607
+ /* @__PURE__ */ jsx(
2608
+ "input",
2609
+ {
2610
+ "data-testid": TID.enquiryPhone,
2611
+ required: config.enquiry.requirePhone,
2612
+ value: customerPhone,
2613
+ onChange: (e) => setCustomerPhone(e.target.value),
2614
+ className: "w-full border bg-background px-3 py-2 outline-none focus:border-primary",
2615
+ style: { borderRadius: "var(--sf-radius-input)" }
2616
+ }
2617
+ )
2618
+ ] }),
2619
+ /* @__PURE__ */ jsxs("label", { className: "block space-y-1.5 text-sm font-medium", children: [
2620
+ /* @__PURE__ */ jsx("span", { children: "Subject" }),
2621
+ /* @__PURE__ */ jsx(
2622
+ "input",
2623
+ {
2624
+ "data-testid": TID.enquirySubject,
2625
+ required: true,
2626
+ value: subject,
2627
+ onChange: (e) => setSubject(e.target.value),
2628
+ className: "w-full border bg-background px-3 py-2 outline-none focus:border-primary",
2629
+ style: { borderRadius: "var(--sf-radius-input)" }
2630
+ }
2631
+ )
2632
+ ] }),
2633
+ /* @__PURE__ */ jsxs("label", { className: "block space-y-1.5 text-sm font-medium", children: [
2634
+ /* @__PURE__ */ jsx("span", { children: "Message" }),
2635
+ /* @__PURE__ */ jsx(
2636
+ "textarea",
2637
+ {
2638
+ "data-testid": TID.enquiryMessage,
2639
+ required: true,
2640
+ rows: 5,
2641
+ value: message,
2642
+ onChange: (e) => setMessage(e.target.value),
2643
+ className: "w-full resize-none border bg-background px-3 py-2 outline-none focus:border-primary",
2644
+ style: { borderRadius: "var(--sf-radius-input)" }
2645
+ }
2646
+ )
2647
+ ] }),
2648
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center gap-3", children: [
2649
+ /* @__PURE__ */ jsxs(
2650
+ "button",
2651
+ {
2652
+ "data-testid": TID.enquirySubmit,
2653
+ type: "submit",
2654
+ disabled: status === "submitting",
2655
+ 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",
2656
+ style: { borderRadius: "var(--sf-radius-button)" },
2657
+ children: [
2658
+ /* @__PURE__ */ jsx(Send, { className: "h-4 w-4" }),
2659
+ status === "submitting" ? "Sending..." : "Send enquiry"
2660
+ ]
2661
+ }
2662
+ ),
2663
+ config.enquiry.whatsappUrl && /* @__PURE__ */ jsxs(
2664
+ "a",
2665
+ {
2666
+ href: buildWhatsAppHref(config.enquiry.whatsappUrl, product),
2667
+ target: "_blank",
2668
+ rel: "noreferrer",
2669
+ className: "inline-flex items-center gap-2 border px-5 py-3 text-sm font-semibold transition-colors hover:bg-muted",
2670
+ style: { borderRadius: "var(--sf-radius-button)" },
2671
+ children: [
2672
+ /* @__PURE__ */ jsx(MessageCircle, { className: "h-4 w-4" }),
2673
+ "WhatsApp"
2674
+ ]
2675
+ }
2676
+ ),
2677
+ config.enquiry.email && /* @__PURE__ */ jsxs(
2678
+ "a",
2679
+ {
2680
+ href: `mailto:${config.enquiry.email}?subject=${encodeURIComponent(subject)}&body=${encodeURIComponent(message)}`,
2681
+ className: "inline-flex items-center gap-2 border px-5 py-3 text-sm font-semibold transition-colors hover:bg-muted",
2682
+ style: { borderRadius: "var(--sf-radius-button)" },
2683
+ children: [
2684
+ /* @__PURE__ */ jsx(Mail, { className: "h-4 w-4" }),
2685
+ "Email"
2686
+ ]
2687
+ }
2688
+ )
2689
+ ] }),
2690
+ status === "success" && /* @__PURE__ */ jsx("p", { "data-testid": TID.enquirySuccess, className: "text-sm font-medium text-emerald-600", children: config.enquiry.successMessage }),
2691
+ error && /* @__PURE__ */ jsx("p", { "data-testid": TID.enquiryError, className: "text-sm font-medium text-red-600", children: error })
2692
+ ]
2693
+ }
2694
+ );
2695
+ }
2095
2696
  function ProductDetailPage({ slug: slug2 }) {
2096
2697
  const config = useStorefrontConfig();
2698
+ const actions = useStorefrontActions();
2097
2699
  const { product, loading } = useProduct(slug2);
2098
2700
  const addItem = useCartStore((s) => s.addItem);
2099
2701
  const [qty, setQty] = useState(1);
@@ -2129,21 +2731,56 @@ function ProductDetailPage({ slug: slug2 }) {
2129
2731
  const soldOut = product.inventoryCount <= 0;
2130
2732
  const lowStock = !soldOut && product.inventoryCount <= 5;
2131
2733
  const image = product.images.find((i) => i.isPrimary) ?? product.images[0];
2734
+ const components = getStorefrontComponents(config);
2735
+ const ProductDetailComponent = components.ProductDetail;
2736
+ const addToCart = () => addItem(product, qty, selectedOptions);
2737
+ const openEnquiry = () => {
2738
+ document.getElementById("storefront-product-enquiry")?.scrollIntoView({ behavior: "smooth", block: "start" });
2739
+ };
2132
2740
  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: [
2741
+ if (ProductDetailComponent) {
2742
+ return /* @__PURE__ */ jsx(
2743
+ ProductDetailComponent,
2744
+ {
2745
+ product,
2746
+ config,
2747
+ commerceMode: config.commerceMode,
2748
+ primaryImage: image,
2749
+ optionGroups,
2750
+ selectedOptions,
2751
+ setSelectedOptions,
2752
+ quantity: qty,
2753
+ setQuantity: setQty,
2754
+ soldOut,
2755
+ lowStock,
2756
+ actions,
2757
+ addToCart,
2758
+ openEnquiry
2759
+ }
2760
+ );
2761
+ }
2762
+ return /* @__PURE__ */ jsxs("main", { ...storefrontComponentContracts.productDetail.root, className: "mx-auto max-w-5xl px-4 py-8 sm:px-6", children: [
2134
2763
  /* @__PURE__ */ jsxs(Link, { to: "/", className: "mb-6 inline-flex items-center gap-1 text-sm text-muted-foreground hover:text-foreground", children: [
2135
2764
  /* @__PURE__ */ jsx(ChevronLeft, { className: "h-4 w-4" }),
2136
2765
  " Continuar comprando"
2137
2766
  ] }),
2138
2767
  /* @__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",
2768
+ /* @__PURE__ */ jsx(
2769
+ "div",
2141
2770
  {
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"
2771
+ ...storefrontComponentContracts.productDetail.gallery,
2772
+ className: "group overflow-hidden border bg-muted",
2773
+ style: { borderRadius: "var(--sf-radius-card)" },
2774
+ children: image && /* @__PURE__ */ jsx(
2775
+ SmoothImage,
2776
+ {
2777
+ src: image.url,
2778
+ alt: image.altText ?? product.name,
2779
+ className: "aspect-square w-full object-cover transition-transform duration-700 ease-out group-hover:scale-105"
2780
+ }
2781
+ )
2145
2782
  }
2146
- ) }),
2783
+ ),
2147
2784
  /* @__PURE__ */ jsxs("div", { className: "flex flex-col py-2 lg:sticky lg:top-24 lg:self-start", children: [
2148
2785
  /* @__PURE__ */ jsxs("nav", { className: "mb-3 flex flex-wrap items-center gap-1.5 text-xs text-muted-foreground", children: [
2149
2786
  /* @__PURE__ */ jsx(Link, { to: "/", className: "transition-colors hover:text-foreground", children: "In\xEDcio" }),
@@ -2155,7 +2792,7 @@ function ProductDetailPage({ slug: slug2 }) {
2155
2792
  /* @__PURE__ */ jsx("span", { className: "text-foreground", children: product.name })
2156
2793
  ] }),
2157
2794
  /* @__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: [
2795
+ config.commerceMode === "checkout" && /* @__PURE__ */ jsxs("div", { className: "mt-3 flex items-center gap-3", children: [
2159
2796
  /* @__PURE__ */ jsx("div", { className: "text-xl", children: /* @__PURE__ */ jsx(
2160
2797
  Price,
2161
2798
  {
@@ -2172,7 +2809,7 @@ function ProductDetailPage({ slug: slug2 }) {
2172
2809
  ] })
2173
2810
  ] }),
2174
2811
  /* @__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: [
2812
+ config.commerceMode === "checkout" && /* @__PURE__ */ jsxs("div", { className: "mt-4 flex flex-wrap items-center gap-x-3 gap-y-1 text-sm", children: [
2176
2813
  soldOut ? /* @__PURE__ */ jsxs("span", { className: "inline-flex items-center gap-2 font-medium text-muted-foreground", children: [
2177
2814
  /* @__PURE__ */ jsx("span", { className: "h-2 w-2 rounded-full bg-muted-foreground" }),
2178
2815
  " Sem estoque"
@@ -2198,7 +2835,7 @@ function ProductDetailPage({ slug: slug2 }) {
2198
2835
  onChange: setSelectedOptions
2199
2836
  }
2200
2837
  ),
2201
- /* @__PURE__ */ jsxs("div", { className: "mt-8 flex items-center gap-4", children: [
2838
+ /* @__PURE__ */ jsx("div", { ...storefrontComponentContracts.productDetail.actions, className: "mt-8", children: config.commerceMode === "checkout" ? /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-4", children: [
2202
2839
  /* @__PURE__ */ jsx(
2203
2840
  QuantityInput,
2204
2841
  {
@@ -2214,14 +2851,32 @@ function ProductDetailPage({ slug: slug2 }) {
2214
2851
  type: "button",
2215
2852
  "data-testid": TID.pdpAddToCart,
2216
2853
  disabled: soldOut,
2217
- onClick: () => addItem(product, qty, selectedOptions),
2854
+ onClick: addToCart,
2218
2855
  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
2856
  style: { borderRadius: "var(--sf-radius-button)" },
2220
2857
  children: soldOut ? "Esgotado" : "Adicionar ao carrinho"
2221
2858
  }
2222
2859
  )
2223
- ] }),
2224
- /* @__PURE__ */ jsxs("div", { className: "mt-6 grid grid-cols-3 gap-3 border-t pt-6 text-center", children: [
2860
+ ] }) : config.commerceMode === "enquiry" ? /* @__PURE__ */ jsx(
2861
+ "button",
2862
+ {
2863
+ type: "button",
2864
+ ...storefrontComponentContracts.productDetail.enquiryButton,
2865
+ onClick: openEnquiry,
2866
+ 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",
2867
+ style: { borderRadius: "var(--sf-radius-button)" },
2868
+ children: config.enquiry.label
2869
+ }
2870
+ ) : /* @__PURE__ */ jsx(
2871
+ Link,
2872
+ {
2873
+ to: config.catalogPath,
2874
+ className: "inline-flex border px-5 py-3 text-sm font-semibold transition-colors hover:bg-muted",
2875
+ style: { borderRadius: "var(--sf-radius-button)" },
2876
+ children: "Voltar ao cat\xE1logo"
2877
+ }
2878
+ ) }),
2879
+ config.commerceMode === "checkout" && /* @__PURE__ */ jsxs("div", { className: "mt-6 grid grid-cols-3 gap-3 border-t pt-6 text-center", children: [
2225
2880
  /* @__PURE__ */ jsxs("div", { className: "flex flex-col items-center gap-1.5 text-[11px] font-medium text-muted-foreground", children: [
2226
2881
  /* @__PURE__ */ jsx(Truck, { className: "h-5 w-5 text-primary" }),
2227
2882
  " Envio r\xE1pido"
@@ -2237,6 +2892,11 @@ function ProductDetailPage({ slug: slug2 }) {
2237
2892
  ] })
2238
2893
  ] })
2239
2894
  ] }),
2895
+ 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: [
2896
+ /* @__PURE__ */ jsx("h2", { className: "sf-heading text-2xl font-semibold", children: "Need more information?" }),
2897
+ /* @__PURE__ */ jsx("p", { className: "mt-2 text-muted-foreground", children: "Send an enquiry about this product." }),
2898
+ /* @__PURE__ */ jsx("div", { className: "mt-6", children: /* @__PURE__ */ jsx(ProductEnquiryForm, { product }) })
2899
+ ] }) }),
2240
2900
  /* @__PURE__ */ jsx(ProductSpecs, { product }),
2241
2901
  /* @__PURE__ */ jsx(RelatedProducts, { product })
2242
2902
  ] });
@@ -2792,7 +3452,7 @@ function CheckoutPage() {
2792
3452
  /* @__PURE__ */ jsx("h2", { className: "sr-only", children: "Resumo do pedido" }),
2793
3453
  /* @__PURE__ */ jsx("ul", { className: "space-y-4", children: cart.lines.map((line) => /* @__PURE__ */ jsxs("li", { className: "flex gap-3 text-sm", children: [
2794
3454
  /* @__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" }),
3455
+ line.imageUrl && /* @__PURE__ */ jsx(SmoothImage, { src: line.imageUrl, alt: line.name, className: "h-full w-full object-cover" }),
2796
3456
  /* @__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
3457
  ] }),
2798
3458
  /* @__PURE__ */ jsxs("div", { className: "min-w-0 flex-1", children: [
@@ -3214,7 +3874,7 @@ function OrderConfirmationPage({ orderId }) {
3214
3874
  /* @__PURE__ */ jsx(OrderTrackingTimeline, { order })
3215
3875
  ] }),
3216
3876
  /* @__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" }),
3877
+ item.imageUrl && /* @__PURE__ */ jsx(SmoothImage, { src: item.imageUrl, alt: item.name, className: "h-12 w-12 rounded-lg border object-cover" }),
3218
3878
  /* @__PURE__ */ jsxs("span", { className: "flex-1", children: [
3219
3879
  item.name,
3220
3880
  " ",
@@ -3422,7 +4082,7 @@ function OrdersPanel() {
3422
4082
  /* @__PURE__ */ jsx("p", { className: "mt-0.5 text-xs text-muted-foreground", children: new Date(order.createdAt).toLocaleDateString(config.locale) }),
3423
4083
  /* @__PURE__ */ jsx("div", { className: "mt-4", children: /* @__PURE__ */ jsx(OrderTrackingTimeline, { order, compact: true }) }),
3424
4084
  /* @__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" }),
4085
+ item.imageUrl && /* @__PURE__ */ jsx(SmoothImage, { src: item.imageUrl, alt: item.name, className: "h-6 w-6 rounded object-cover" }),
3426
4086
  item.name,
3427
4087
  " \xD7 ",
3428
4088
  item.quantity
@@ -3602,7 +4262,7 @@ function PolicyPage({ kind }) {
3602
4262
  ] }) })
3603
4263
  ] });
3604
4264
  }
3605
- var StorefrontErrorBoundary = class extends React18.Component {
4265
+ var StorefrontErrorBoundary = class extends React6.Component {
3606
4266
  constructor() {
3607
4267
  super(...arguments);
3608
4268
  this.state = { error: null };
@@ -3703,8 +4363,8 @@ function RouteSwitch({ path }) {
3703
4363
  if (product?.slug) return /* @__PURE__ */ jsx(ProductDetailPage, { slug: product.slug });
3704
4364
  const order = matchPath("/order/:id", path);
3705
4365
  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, {});
4366
+ if (matchPath("/checkout", path) && config.features.checkout) return /* @__PURE__ */ jsx(CheckoutPage, {});
4367
+ if (matchPath("/account", path) && config.features.accounts) return /* @__PURE__ */ jsx(MyPurchasesPage, {});
3708
4368
  if (matchPath("/catalog", path)) return /* @__PURE__ */ jsx(CatalogPage, {});
3709
4369
  if (matchPath("/privacy", path)) return /* @__PURE__ */ jsx(PolicyPage, { kind: "privacy" });
3710
4370
  if (matchPath("/terms", path)) return /* @__PURE__ */ jsx(PolicyPage, { kind: "terms" });
@@ -3746,7 +4406,12 @@ function buildMockSeed(config) {
3746
4406
  };
3747
4407
  }
3748
4408
  function initStorefrontRuntime(config) {
3749
- initCustomerAuth(resolveAuthAdapter(config.auth?.adapter));
4409
+ if (resolveConfig(config).features.accounts) {
4410
+ initCustomerAuth(resolveAuthAdapter(config.auth, {
4411
+ supabaseUrl: config.supabaseUrl,
4412
+ supabaseAnonKey: config.supabaseAnonKey
4413
+ }));
4414
+ }
3750
4415
  if (config.provider) {
3751
4416
  setShopProvider(config.provider);
3752
4417
  } else {
@@ -3762,14 +4427,20 @@ function StorefrontShell() {
3762
4427
  const config = useStorefrontConfig();
3763
4428
  const path = useHashPath();
3764
4429
  const focused = isFocusedRoute(config, path);
3765
- return /* @__PURE__ */ jsxs("div", { className: "min-h-screen bg-background text-foreground", children: [
4430
+ const components = getStorefrontComponents(config);
4431
+ const chromeProps = { config, commerceMode: config.commerceMode };
4432
+ const body = /* @__PURE__ */ jsxs(Fragment, { children: [
3766
4433
  config.theme && /* @__PURE__ */ jsx(StorefrontThemeStyle, { theme: config.theme }),
3767
- !focused && /* @__PURE__ */ jsx(StorefrontHeader, {}),
4434
+ !focused && (components.Header ? /* @__PURE__ */ jsx(components.Header, { ...chromeProps }) : /* @__PURE__ */ jsx(StorefrontHeader, {})),
3768
4435
  /* @__PURE__ */ jsx(StorefrontErrorBoundary, { children: /* @__PURE__ */ jsx(RouteSwitch, { path }) }, path),
3769
- !focused && /* @__PURE__ */ jsx(CartDrawer, {}),
3770
- !focused && /* @__PURE__ */ jsx(StorefrontFooter, {}),
4436
+ !focused && config.features.cart && /* @__PURE__ */ jsx(CartDrawer, {}),
4437
+ !focused && (components.Footer ? /* @__PURE__ */ jsx(components.Footer, { ...chromeProps }) : /* @__PURE__ */ jsx(StorefrontFooter, {})),
3771
4438
  /* @__PURE__ */ jsx(Toaster, {})
3772
4439
  ] });
4440
+ if (components.Shell) {
4441
+ return /* @__PURE__ */ jsx(components.Shell, { ...chromeProps, children: body });
4442
+ }
4443
+ return /* @__PURE__ */ jsx("div", { className: "min-h-screen bg-background text-foreground", children: body });
3773
4444
  }
3774
4445
  function createStorefrontApp(config) {
3775
4446
  const resolved = resolveConfig(config);
@@ -3780,6 +4451,37 @@ function createStorefrontApp(config) {
3780
4451
  StorefrontApp.displayName = "StorefrontApp";
3781
4452
  return StorefrontApp;
3782
4453
  }
4454
+ function createStorefront(options) {
4455
+ const routes = [...options.pages ?? [], ...options.routes ?? []];
4456
+ const config = {
4457
+ ...options.config,
4458
+ provider: options.provider ?? options.config.provider,
4459
+ components: {
4460
+ ...options.config.components,
4461
+ ...options.components
4462
+ },
4463
+ routes: routes.length > 0 ? routes : options.config.routes,
4464
+ home: options.sections ? { ...options.config.home ?? { sections: [] }, sections: options.sections } : options.config.home
4465
+ };
4466
+ return createStorefrontApp(config);
4467
+ }
4468
+
4469
+ // src/define.ts
4470
+ function defineStorefrontConfig(config) {
4471
+ return config;
4472
+ }
4473
+ function defineStorefrontComponents(components) {
4474
+ return components;
4475
+ }
4476
+ function defineStorefrontRoutes(routes) {
4477
+ return routes;
4478
+ }
4479
+ function defineStorefrontSections(sections) {
4480
+ return sections;
4481
+ }
4482
+ function defineStorefrontApp(options) {
4483
+ return options;
4484
+ }
3783
4485
  var runtimeConfigRegistry = /* @__PURE__ */ new Map();
3784
4486
  function slug(name) {
3785
4487
  return name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "") || "app";
@@ -3838,7 +4540,7 @@ function manifestToStorefrontConfig(manifest, surfaceName) {
3838
4540
  provider: manifest.backend?.provider,
3839
4541
  url: manifest.backend?.url
3840
4542
  },
3841
- slots: runtimeConfig?.slots,
4543
+ components: runtimeConfig?.components,
3842
4544
  routes: runtimeConfig?.routes,
3843
4545
  provider: runtimeConfig?.provider,
3844
4546
  auth: runtimeConfig?.auth,
@@ -3846,9 +4548,9 @@ function manifestToStorefrontConfig(manifest, surfaceName) {
3846
4548
  };
3847
4549
  }
3848
4550
  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);
4551
+ const config = React6.useMemo(() => manifestToStorefrontConfig(manifest, surface), [manifest, surface]);
4552
+ const resolved = React6.useMemo(() => resolveConfig(config), [config]);
4553
+ const inited = React6.useRef(false);
3852
4554
  if (!inited.current) {
3853
4555
  initStorefrontRuntime(config);
3854
4556
  inited.current = true;
@@ -4081,6 +4783,6 @@ var storefrontTemplates = {
4081
4783
  atelier: atelierTemplate
4082
4784
  };
4083
4785
 
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 };
4786
+ 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, 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
4787
  //# sourceMappingURL=index.js.map
4086
4788
  //# sourceMappingURL=index.js.map