@nok-integration/storefront-react 0.19.39 → 0.19.41

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -2089,6 +2089,10 @@ declare type CategoryPage = z.infer<typeof CategoryPage>;
2089
2089
  *
2090
2090
  * Fetching happens on mount, client-side, through the provider's client — so it inherits
2091
2091
  * the host's session and market without being told about either.
2092
+ *
2093
+ * The page is held under the CODE it belongs to and read back only while that code is
2094
+ * still the one being asked for, so a change of `code` never leaves the previous category
2095
+ * on screen while the next one loads.
2092
2096
  */
2093
2097
  export declare function CategoryView({ code, initialData, onNotFound }: CategoryViewProps): JSX.Element;
2094
2098
 
@@ -2100,6 +2104,10 @@ export declare interface CategoryViewProps {
2100
2104
  * synchronously on the first frame — no skeleton, no waterfall — and revalidated in the
2101
2105
  * background, because a server-rendered page can be arbitrarily old by the time it is
2102
2106
  * interactive and price and availability are the two things worst served by being stale.
2107
+ *
2108
+ * Matched against `code` on every render, not only at mount, so a host that swaps both
2109
+ * together has the new page painted immediately. A page for a different code is ignored
2110
+ * rather than shown.
2103
2111
  */
2104
2112
  initialData?: CategoryPage;
2105
2113
  /**
@@ -4803,6 +4811,19 @@ declare const Product: z.ZodObject<{
4803
4811
 
4804
4812
  declare type Product = z.infer<typeof Product>;
4805
4813
 
4814
+ declare class ProductCache {
4815
+ private readonly now;
4816
+ private readonly entries;
4817
+ /** Injectable so the age limit can be tested without leaning on timers. */
4818
+ constructor(now?: () => number);
4819
+ private static keyFor;
4820
+ get(sku: string, market?: Market): Product | undefined;
4821
+ set(product: Product, market?: Market): void;
4822
+ setMany(products: readonly Product[], market?: Market): void;
4823
+ /** Test seam. Nothing in the SDK calls this. */
4824
+ clear(): void;
4825
+ }
4826
+
4806
4827
  /**
4807
4828
  * 4.3 PDP interactive surface, aligned to the DAZN mobile PDP design. Sections render
4808
4829
  * from server-authoritative contract data: badges, title + price, promo nudge, description
@@ -4950,11 +4971,10 @@ declare type ProductVariant = z.infer<typeof ProductVariant>;
4950
4971
  /**
4951
4972
  * A whole product-detail screen, data fetching included — a **level 1 view**.
4952
4973
  *
4953
- * Fetches the product, then enriches it with inventory and recommendations. Those two are
4954
- * deliberately best-effort: the PDP renders perfectly well without either, so a failure in
4955
- * one must not fault the screen. `Promise.allSettled` rather than `all` is what makes that
4956
- * true with `all`, a recommendations outage would blank a page the fan could otherwise
4957
- * have bought from.
4974
+ * All of the fetching lives in {@link useProduct}, which is exported: a host composing its
4975
+ * own product screen from level-2 pieces gets the same behaviour rather than writing it
4976
+ * again. Switching between two items paints the new one immediately wherever the rail
4977
+ * already carried it, and never leaves the outgoing item up while the next one loads.
4958
4978
  *
4959
4979
  * `sku_not_found` is separated from the generic error path because it is not a fault: the
4960
4980
  * product is gone, and "this is no longer available" is the honest thing to say. An error
@@ -5538,6 +5558,8 @@ export declare function ShippingInfo(): JSX.Element;
5538
5558
  declare interface ShopContextValue {
5539
5559
  api: DaznClient;
5540
5560
  market?: Market;
5561
+ /** Products this provider has already been given — see {@link ProductCache}. */
5562
+ products: ProductCache;
5541
5563
  theme: 'light' | 'dark' | 'auto';
5542
5564
  notifications: 'internal' | 'host';
5543
5565
  locale?: string;
@@ -6395,6 +6417,56 @@ export declare function useMoney(defaultLocale?: string): {
6395
6417
  */
6396
6418
  export declare function useNavigate(): ((to: ShopRoute) => void) | null;
6397
6419
 
6420
+ /**
6421
+ * The data behind a product screen: the product, then inventory and recommendations.
6422
+ *
6423
+ * This is what {@link ProductView} is built from, exported so a host composing its own
6424
+ * product screen from the level-2 pieces does not have to write the fetching again. Two
6425
+ * properties are worth knowing about, because a hand-rolled version has to reproduce both.
6426
+ *
6427
+ * **Everything is held under the SKU it belongs to**, and read back only while that SKU is
6428
+ * still the one being asked for. A change of `sku` invalidates the whole set in the same
6429
+ * render that changes it, so the outgoing product is never left on screen while the next
6430
+ * one loads, and there is no frame in which one item's title sits above another item's
6431
+ * price.
6432
+ *
6433
+ * **A product already in memory is painted at once.** The recommendation rail carries whole
6434
+ * products, so opening one of them is usually drawn on the first frame and corrected a
6435
+ * moment later, rather than spending a round trip behind a skeleton. Inventory and
6436
+ * recommendations always come from the request — they are the two things worst served by
6437
+ * being remembered, and neither is needed to draw the top of the screen.
6438
+ *
6439
+ * Inventory and recommendations are best-effort: the screen renders perfectly well without
6440
+ * either, so a failure in one does not fault it.
6441
+ */ export declare function useProduct(sku: string, options?: UseProductOptions): UseProductResult;
6442
+
6443
+ export declare interface UseProductOptions {
6444
+ /**
6445
+ * An already-fetched product, from a host that server-rendered this screen. Painted on
6446
+ * the first frame and revalidated in the background.
6447
+ *
6448
+ * Read on every render, not only at mount, so a host that swaps `sku` and `initialData`
6449
+ * together has the new product painted immediately. It is matched against `sku` before
6450
+ * use: a product for a different SKU is ignored rather than shown.
6451
+ */
6452
+ initialData?: {
6453
+ product: Product;
6454
+ inventory?: Inventory | null;
6455
+ };
6456
+ /** Fetch the "you may also like" rail. Default `true`. */
6457
+ showRecommendations?: boolean;
6458
+ }
6459
+
6460
+ export declare interface UseProductResult {
6461
+ /** Null until there is something to draw for this SKU. */
6462
+ product: Product | null;
6463
+ inventory: Inventory | null;
6464
+ recommendations: Product[];
6465
+ /** Nothing to draw yet and nothing has gone wrong — it is still on its way. */
6466
+ loading: boolean;
6467
+ error: ReturnType<typeof codeOf> | undefined;
6468
+ }
6469
+
6398
6470
  /** The typed, schema-validating commerce client. Requires a provider. */
6399
6471
  export declare function useShopApi(): DaznClient;
6400
6472
 
package/dist/index.mjs CHANGED
@@ -5112,6 +5112,49 @@ function resolveAssetUrl(src, base) {
5112
5112
  if (!src.startsWith("/") || src.startsWith("//")) return src;
5113
5113
  return `${base.replace(/\/+$/, "")}${src}`;
5114
5114
  }
5115
+ const MAX_AGE_MS = 5 * 6e4;
5116
+ const CAPACITY = 60;
5117
+ class ProductCache {
5118
+ constructor(now = () => Date.now()) {
5119
+ this.now = now;
5120
+ }
5121
+ now;
5122
+ entries = /* @__PURE__ */ new Map();
5123
+ static keyFor(sku, market) {
5124
+ return `${market?.country ?? ""}\0${market?.lang ?? ""}\0${sku}`;
5125
+ }
5126
+ get(sku, market) {
5127
+ const key = ProductCache.keyFor(sku, market);
5128
+ const entry = this.entries.get(key);
5129
+ if (!entry) return void 0;
5130
+ if (this.now() - entry.at > MAX_AGE_MS) {
5131
+ this.entries.delete(key);
5132
+ return void 0;
5133
+ }
5134
+ this.entries.delete(key);
5135
+ this.entries.set(key, entry);
5136
+ return entry.product;
5137
+ }
5138
+ set(product, market) {
5139
+ const key = ProductCache.keyFor(product.sku, market);
5140
+ this.entries.delete(key);
5141
+ this.entries.set(key, {
5142
+ product,
5143
+ at: this.now()
5144
+ });
5145
+ while (this.entries.size > CAPACITY) {
5146
+ const oldest = this.entries.keys().next();
5147
+ if (oldest.done) break;
5148
+ this.entries.delete(oldest.value);
5149
+ }
5150
+ }
5151
+ setMany(products, market) {
5152
+ for (const product of products) this.set(product, market);
5153
+ }
5154
+ clear() {
5155
+ this.entries.clear();
5156
+ }
5157
+ }
5115
5158
  function createShopFetch(opts) {
5116
5159
  const baseFetch = opts.fetchImpl ?? globalThis.fetch;
5117
5160
  if (!baseFetch) throw new Error("No fetch implementation available");
@@ -5226,6 +5269,9 @@ function DaznShopProvider({ children, ...config }) {
5226
5269
  const getTokenRef = useRef(getToken);
5227
5270
  getTokenRef.current = getToken;
5228
5271
  const stableGetToken = useCallback(() => getTokenRef.current(), []);
5272
+ const productsRef = useRef(null);
5273
+ productsRef.current ??= new ProductCache();
5274
+ const products = productsRef.current;
5229
5275
  const emit = useCallback((event) => {
5230
5276
  for (const listener of listeners.current) {
5231
5277
  try {
@@ -5266,6 +5312,7 @@ function DaznShopProvider({ children, ...config }) {
5266
5312
  const value2 = useMemo(() => ({
5267
5313
  api,
5268
5314
  market,
5315
+ products,
5269
5316
  theme,
5270
5317
  notifications,
5271
5318
  locale,
@@ -5273,7 +5320,7 @@ function DaznShopProvider({ children, ...config }) {
5273
5320
  emit,
5274
5321
  subscribe,
5275
5322
  navigate
5276
- }), [api, market, theme, notifications, locale, assetBaseUrl, emit, subscribe, navigate]);
5323
+ }), [api, market, products, theme, notifications, locale, assetBaseUrl, emit, subscribe, navigate]);
5277
5324
  return jsx(ShopContext.Provider, {
5278
5325
  value: value2,
5279
5326
  children: jsx(ScreenTitleProvider, {
@@ -5482,7 +5529,7 @@ const styles$O = {
5482
5529
  surface: surface$1,
5483
5530
  fill
5484
5531
  };
5485
- const SETTLE_MS$1 = 150;
5532
+ const SETTLE_MS = 150;
5486
5533
  function scrolledOffOrigin(el) {
5487
5534
  for (let node = el.parentElement; node; node = node.parentElement) {
5488
5535
  if (node.scrollTop > 0) return true;
@@ -5506,11 +5553,11 @@ function useTopOffset(ref, property, enabled = true) {
5506
5553
  };
5507
5554
  const settle = () => {
5508
5555
  if (timer2) clearTimeout(timer2);
5509
- timer2 = setTimeout(measure, SETTLE_MS$1);
5556
+ timer2 = setTimeout(measure, SETTLE_MS);
5510
5557
  };
5511
5558
  measure();
5512
5559
  const frame2 = typeof requestAnimationFrame === "undefined" ? void 0 : requestAnimationFrame(measure);
5513
- const settleAfterMount = setTimeout(measure, SETTLE_MS$1);
5560
+ const settleAfterMount = setTimeout(measure, SETTLE_MS);
5514
5561
  window.addEventListener("resize", measure);
5515
5562
  window.addEventListener("orientationchange", measure);
5516
5563
  document.addEventListener("scroll", settle, {
@@ -10710,32 +10757,51 @@ function CategoryContent({ page: page2 }) {
10710
10757
  })]
10711
10758
  });
10712
10759
  }
10760
+ function productsIn(page2) {
10761
+ const found = [];
10762
+ for (const block of page2.blocks) {
10763
+ if (block.kind === "rail" || block.kind === "grid") found.push(...block.products);
10764
+ else if (block.kind === "featured" || block.kind === "product_gallery") found.push(block.product);
10765
+ }
10766
+ return found;
10767
+ }
10713
10768
  function CategoryView({ code, initialData, onNotFound }) {
10714
10769
  const t = useT();
10715
- const { api, market, emit } = useShop();
10716
- const [page2, setPage] = useState(initialData ?? null);
10717
- const [error2, setError] = useState();
10718
- const [loading, setLoading] = useState(!initialData);
10770
+ const { api, market, emit, products } = useShop();
10771
+ const [loaded, setLoaded] = useState(null);
10772
+ const [failure, setFailure] = useState(null);
10773
+ const settled = loaded?.code === code;
10774
+ const seeded = initialData && initialData.code === code ? initialData : void 0;
10775
+ const page2 = (settled ? loaded?.page : void 0) ?? seeded ?? null;
10776
+ const error2 = failure?.code === code ? failure.error : void 0;
10777
+ const loading = !page2 && !error2 && !settled;
10719
10778
  useEffect(() => {
10720
10779
  let live = true;
10721
- setError(void 0);
10722
10780
  api.catalog.getCategoryPage(code, market).then((res) => {
10781
+ const fetched = res;
10782
+ products.setMany(productsIn(fetched), market);
10723
10783
  if (!live) return;
10724
- setPage(res);
10784
+ setLoaded({
10785
+ code,
10786
+ page: fetched
10787
+ });
10725
10788
  }).catch((err) => {
10726
10789
  if (!live) return;
10727
10790
  const c = codeOf(err);
10728
- setError(c);
10791
+ setFailure({
10792
+ code,
10793
+ error: c
10794
+ });
10729
10795
  emit({
10730
10796
  type: "error",
10731
10797
  code: c,
10732
10798
  scope: "category"
10733
10799
  });
10734
- }).finally(() => live && setLoading(false));
10800
+ });
10735
10801
  return () => {
10736
10802
  live = false;
10737
10803
  };
10738
- }, [api, code, market, emit]);
10804
+ }, [api, code, market, emit, products]);
10739
10805
  if (error2 === "category_not_found" && onNotFound) return jsx(Fragment, {
10740
10806
  children: onNotFound()
10741
10807
  });
@@ -10756,23 +10822,22 @@ function CategoryView({ code, initialData, onNotFound }) {
10756
10822
  })
10757
10823
  });
10758
10824
  }
10759
- function ProductView({ sku, initialData, showRecommendations = true, onNotFound }) {
10760
- const t = useT();
10761
- const { api, market, emit } = useShop();
10762
- const [loaded, setLoaded] = useState(initialData ? {
10763
- sku,
10764
- product: initialData.product,
10765
- inventory: initialData.inventory ?? null,
10766
- recommendations: []
10767
- } : null);
10825
+ function useProduct(sku, options2 = {}) {
10826
+ const { initialData, showRecommendations = true } = options2;
10827
+ const { api, market, emit, products } = useShop();
10828
+ const [loaded, setLoaded] = useState(null);
10768
10829
  const [failure, setFailure] = useState(null);
10769
10830
  const current2 = loaded?.sku === sku ? loaded : null;
10770
10831
  const error2 = failure?.sku === sku ? failure.code : void 0;
10832
+ const seeded = initialData && initialData.product.sku === sku ? initialData.product : products.get(sku, market);
10833
+ const product = current2?.product ?? seeded ?? null;
10834
+ const inventory2 = current2 ? current2.inventory : initialData && initialData.product.sku === sku ? initialData.inventory ?? null : null;
10771
10835
  useEffect(() => {
10772
10836
  let live = true;
10773
10837
  (async () => {
10774
10838
  try {
10775
10839
  const p = await api.catalog.getProduct(sku, market);
10840
+ products.set(p, market);
10776
10841
  if (!live) return;
10777
10842
  setLoaded({
10778
10843
  sku,
@@ -10784,12 +10849,14 @@ function ProductView({ sku, initialData, showRecommendations = true, onNotFound
10784
10849
  const [inv, recs] = await Promise.allSettled([hasVariants ? Promise.resolve(null) : api.inventory.get(sku), showRecommendations ? api.catalog.getRecommendations(sku, market) : Promise.resolve({
10785
10850
  data: []
10786
10851
  })]);
10852
+ const recommendations = recs.status === "fulfilled" ? recs.value.data : [];
10853
+ products.setMany(recommendations, market);
10787
10854
  if (!live) return;
10788
10855
  setLoaded({
10789
10856
  sku,
10790
10857
  product: p,
10791
10858
  inventory: inv.status === "fulfilled" ? inv.value : null,
10792
- recommendations: recs.status === "fulfilled" ? recs.value.data : []
10859
+ recommendations
10793
10860
  });
10794
10861
  } catch (err) {
10795
10862
  if (!live) return;
@@ -10808,7 +10875,23 @@ function ProductView({ sku, initialData, showRecommendations = true, onNotFound
10808
10875
  return () => {
10809
10876
  live = false;
10810
10877
  };
10811
- }, [api, sku, market, emit, showRecommendations]);
10878
+ }, [api, sku, market, emit, showRecommendations, products]);
10879
+ return {
10880
+ product,
10881
+ inventory: inventory2,
10882
+ recommendations: current2?.recommendations ?? [],
10883
+ loading: !product && !error2,
10884
+ error: error2
10885
+ };
10886
+ }
10887
+ function ProductView({ sku, initialData, showRecommendations = true, onNotFound }) {
10888
+ const t = useT();
10889
+ const { product, inventory: inventory2, recommendations, loading, error: error2 } = useProduct(sku, {
10890
+ ...initialData ? {
10891
+ initialData
10892
+ } : {},
10893
+ showRecommendations
10894
+ });
10812
10895
  const unavailable = jsx(ShopSurface, {
10813
10896
  tone: "light",
10814
10897
  fill: true,
@@ -10823,14 +10906,14 @@ function ProductView({ sku, initialData, showRecommendations = true, onNotFound
10823
10906
  });
10824
10907
  return unavailable;
10825
10908
  }
10826
- if (current2) {
10909
+ if (product) {
10827
10910
  return jsx(ProductDetail, {
10828
- product: current2.product,
10829
- inventory: current2.inventory,
10830
- recommendations: current2.recommendations
10831
- });
10911
+ product,
10912
+ inventory: inventory2,
10913
+ recommendations
10914
+ }, product.sku);
10832
10915
  }
10833
- if (!error2) return jsx(ScreenSkeleton, {
10916
+ if (loading) return jsx(ScreenSkeleton, {
10834
10917
  tone: "light"
10835
10918
  });
10836
10919
  return jsx(ShopSurface, {
@@ -10841,24 +10924,24 @@ function ProductView({ sku, initialData, showRecommendations = true, onNotFound
10841
10924
  })
10842
10925
  });
10843
10926
  }
10844
- const showcase = "_showcase_1qnt2_44";
10845
- const panel = "_panel_1qnt2_48";
10846
- const image = "_image_1qnt2_76";
10847
- const hero = "_hero_1qnt2_116";
10848
- const heroContent = "_heroContent_1qnt2_197";
10849
- const heroText = "_heroText_1qnt2_221";
10850
- const logo = "_logo_1qnt2_230";
10851
- const heroTitle = "_heroTitle_1qnt2_250";
10852
- const heroSub = "_heroSub_1qnt2_274";
10853
- const swipe = "_swipe_1qnt2_293";
10854
- const swipeLabel = "_swipeLabel_1qnt2_300";
10855
- const chevron$1 = "_chevron_1qnt2_306";
10856
- const overlay$1 = "_overlay_1qnt2_11";
10857
- const copy = "_copy_1qnt2_341";
10858
- const title$2 = "_title_1qnt2_346";
10859
- const subtitle$1 = "_subtitle_1qnt2_353";
10860
- const cta = "_cta_1qnt2_359";
10861
- const mosaic = "_mosaic_1qnt2_403";
10927
+ const showcase = "_showcase_c2hqd_59";
10928
+ const panel = "_panel_c2hqd_63";
10929
+ const image = "_image_c2hqd_91";
10930
+ const hero = "_hero_c2hqd_131";
10931
+ const heroContent = "_heroContent_c2hqd_212";
10932
+ const heroText = "_heroText_c2hqd_236";
10933
+ const logo = "_logo_c2hqd_245";
10934
+ const heroTitle = "_heroTitle_c2hqd_265";
10935
+ const heroSub = "_heroSub_c2hqd_289";
10936
+ const swipe = "_swipe_c2hqd_308";
10937
+ const swipeLabel = "_swipeLabel_c2hqd_315";
10938
+ const chevron$1 = "_chevron_c2hqd_321";
10939
+ const overlay$1 = "_overlay_c2hqd_11";
10940
+ const copy = "_copy_c2hqd_356";
10941
+ const title$2 = "_title_c2hqd_361";
10942
+ const subtitle$1 = "_subtitle_c2hqd_368";
10943
+ const cta = "_cta_c2hqd_374";
10944
+ const mosaic = "_mosaic_c2hqd_418";
10862
10945
  const styles$2 = {
10863
10946
  showcase,
10864
10947
  panel,
@@ -10879,7 +10962,6 @@ const styles$2 = {
10879
10962
  cta,
10880
10963
  mosaic
10881
10964
  };
10882
- const SETTLE_MS = 150;
10883
10965
  function useDocumentSnap(ref, enabled = true) {
10884
10966
  useEffect(() => {
10885
10967
  const el = ref.current;
@@ -10887,23 +10969,9 @@ function useDocumentSnap(ref, enabled = true) {
10887
10969
  const root2 = document.documentElement;
10888
10970
  const previousType = root2.style.scrollSnapType;
10889
10971
  const previousPadding = root2.style.scrollPaddingTop;
10890
- let timer2;
10891
- const syncPadding = () => {
10892
- const top2 = getComputedStyle(el).getPropertyValue("--showcase-top").trim();
10893
- root2.style.scrollPaddingTop = top2 === "" ? "0px" : top2;
10894
- };
10895
- const schedule = () => {
10896
- if (timer2) clearTimeout(timer2);
10897
- timer2 = setTimeout(syncPadding, SETTLE_MS);
10898
- };
10899
10972
  root2.style.scrollSnapType = "y mandatory";
10900
- syncPadding();
10901
- window.addEventListener("resize", schedule);
10902
- window.addEventListener("orientationchange", schedule);
10973
+ root2.style.scrollPaddingTop = "0px";
10903
10974
  return () => {
10904
- if (timer2) clearTimeout(timer2);
10905
- window.removeEventListener("resize", schedule);
10906
- window.removeEventListener("orientationchange", schedule);
10907
10975
  root2.style.scrollSnapType = previousType;
10908
10976
  root2.style.scrollPaddingTop = previousPadding;
10909
10977
  };
@@ -11030,7 +11098,6 @@ function HomeShowcase() {
11030
11098
  const assetUrl = useAssetUrl();
11031
11099
  const panels = useSlides();
11032
11100
  const showcaseRef = useRef(null);
11033
- useTopOffset(showcaseRef, "--showcase-top");
11034
11101
  useDocumentSnap(showcaseRef);
11035
11102
  return jsx(ShopSurface, {
11036
11103
  tone: "dark",
@@ -11634,6 +11701,7 @@ export {
11634
11701
  useCartSheet,
11635
11702
  useMoney,
11636
11703
  useNavigate,
11704
+ useProduct,
11637
11705
  useShopApi,
11638
11706
  useShopEvent,
11639
11707
  useShopOptional,
@@ -5112,6 +5112,49 @@ function resolveAssetUrl(src, base) {
5112
5112
  if (!src.startsWith("/") || src.startsWith("//")) return src;
5113
5113
  return `${base.replace(/\/+$/, "")}${src}`;
5114
5114
  }
5115
+ const MAX_AGE_MS = 5 * 6e4;
5116
+ const CAPACITY = 60;
5117
+ class ProductCache {
5118
+ constructor(now = () => Date.now()) {
5119
+ this.now = now;
5120
+ }
5121
+ now;
5122
+ entries = /* @__PURE__ */ new Map();
5123
+ static keyFor(sku, market) {
5124
+ return `${market?.country ?? ""}\0${market?.lang ?? ""}\0${sku}`;
5125
+ }
5126
+ get(sku, market) {
5127
+ const key = ProductCache.keyFor(sku, market);
5128
+ const entry = this.entries.get(key);
5129
+ if (!entry) return void 0;
5130
+ if (this.now() - entry.at > MAX_AGE_MS) {
5131
+ this.entries.delete(key);
5132
+ return void 0;
5133
+ }
5134
+ this.entries.delete(key);
5135
+ this.entries.set(key, entry);
5136
+ return entry.product;
5137
+ }
5138
+ set(product, market) {
5139
+ const key = ProductCache.keyFor(product.sku, market);
5140
+ this.entries.delete(key);
5141
+ this.entries.set(key, {
5142
+ product,
5143
+ at: this.now()
5144
+ });
5145
+ while (this.entries.size > CAPACITY) {
5146
+ const oldest = this.entries.keys().next();
5147
+ if (oldest.done) break;
5148
+ this.entries.delete(oldest.value);
5149
+ }
5150
+ }
5151
+ setMany(products, market) {
5152
+ for (const product of products) this.set(product, market);
5153
+ }
5154
+ clear() {
5155
+ this.entries.clear();
5156
+ }
5157
+ }
5115
5158
  function createShopFetch(opts) {
5116
5159
  const baseFetch = opts.fetchImpl ?? globalThis.fetch;
5117
5160
  if (!baseFetch) throw new Error("No fetch implementation available");
@@ -5226,6 +5269,9 @@ function DaznShopProvider({ children, ...config }) {
5226
5269
  const getTokenRef = useRef(getToken);
5227
5270
  getTokenRef.current = getToken;
5228
5271
  const stableGetToken = useCallback(() => getTokenRef.current(), []);
5272
+ const productsRef = useRef(null);
5273
+ productsRef.current ??= new ProductCache();
5274
+ const products = productsRef.current;
5229
5275
  const emit = useCallback((event) => {
5230
5276
  for (const listener of listeners.current) {
5231
5277
  try {
@@ -5266,6 +5312,7 @@ function DaznShopProvider({ children, ...config }) {
5266
5312
  const value2 = useMemo(() => ({
5267
5313
  api,
5268
5314
  market,
5315
+ products,
5269
5316
  theme,
5270
5317
  notifications,
5271
5318
  locale,
@@ -5273,7 +5320,7 @@ function DaznShopProvider({ children, ...config }) {
5273
5320
  emit,
5274
5321
  subscribe,
5275
5322
  navigate
5276
- }), [api, market, theme, notifications, locale, assetBaseUrl, emit, subscribe, navigate]);
5323
+ }), [api, market, products, theme, notifications, locale, assetBaseUrl, emit, subscribe, navigate]);
5277
5324
  return jsx(ShopContext.Provider, {
5278
5325
  value: value2,
5279
5326
  children: jsx(ScreenTitleProvider, {
@@ -5482,7 +5529,7 @@ const styles$O = {
5482
5529
  surface: surface$1,
5483
5530
  fill
5484
5531
  };
5485
- const SETTLE_MS$1 = 150;
5532
+ const SETTLE_MS = 150;
5486
5533
  function scrolledOffOrigin(el) {
5487
5534
  for (let node = el.parentElement; node; node = node.parentElement) {
5488
5535
  if (node.scrollTop > 0) return true;
@@ -5506,11 +5553,11 @@ function useTopOffset(ref, property, enabled = true) {
5506
5553
  };
5507
5554
  const settle = () => {
5508
5555
  if (timer2) clearTimeout(timer2);
5509
- timer2 = setTimeout(measure, SETTLE_MS$1);
5556
+ timer2 = setTimeout(measure, SETTLE_MS);
5510
5557
  };
5511
5558
  measure();
5512
5559
  const frame2 = typeof requestAnimationFrame === "undefined" ? void 0 : requestAnimationFrame(measure);
5513
- const settleAfterMount = setTimeout(measure, SETTLE_MS$1);
5560
+ const settleAfterMount = setTimeout(measure, SETTLE_MS);
5514
5561
  window.addEventListener("resize", measure);
5515
5562
  window.addEventListener("orientationchange", measure);
5516
5563
  document.addEventListener("scroll", settle, {
@@ -10710,32 +10757,51 @@ function CategoryContent({ page: page2 }) {
10710
10757
  })]
10711
10758
  });
10712
10759
  }
10760
+ function productsIn(page2) {
10761
+ const found = [];
10762
+ for (const block of page2.blocks) {
10763
+ if (block.kind === "rail" || block.kind === "grid") found.push(...block.products);
10764
+ else if (block.kind === "featured" || block.kind === "product_gallery") found.push(block.product);
10765
+ }
10766
+ return found;
10767
+ }
10713
10768
  function CategoryView({ code, initialData, onNotFound }) {
10714
10769
  const t = useT();
10715
- const { api, market, emit } = useShop();
10716
- const [page2, setPage] = useState(initialData ?? null);
10717
- const [error2, setError] = useState();
10718
- const [loading, setLoading] = useState(!initialData);
10770
+ const { api, market, emit, products } = useShop();
10771
+ const [loaded, setLoaded] = useState(null);
10772
+ const [failure, setFailure] = useState(null);
10773
+ const settled = loaded?.code === code;
10774
+ const seeded = initialData && initialData.code === code ? initialData : void 0;
10775
+ const page2 = (settled ? loaded?.page : void 0) ?? seeded ?? null;
10776
+ const error2 = failure?.code === code ? failure.error : void 0;
10777
+ const loading = !page2 && !error2 && !settled;
10719
10778
  useEffect(() => {
10720
10779
  let live = true;
10721
- setError(void 0);
10722
10780
  api.catalog.getCategoryPage(code, market).then((res) => {
10781
+ const fetched = res;
10782
+ products.setMany(productsIn(fetched), market);
10723
10783
  if (!live) return;
10724
- setPage(res);
10784
+ setLoaded({
10785
+ code,
10786
+ page: fetched
10787
+ });
10725
10788
  }).catch((err) => {
10726
10789
  if (!live) return;
10727
10790
  const c = codeOf(err);
10728
- setError(c);
10791
+ setFailure({
10792
+ code,
10793
+ error: c
10794
+ });
10729
10795
  emit({
10730
10796
  type: "error",
10731
10797
  code: c,
10732
10798
  scope: "category"
10733
10799
  });
10734
- }).finally(() => live && setLoading(false));
10800
+ });
10735
10801
  return () => {
10736
10802
  live = false;
10737
10803
  };
10738
- }, [api, code, market, emit]);
10804
+ }, [api, code, market, emit, products]);
10739
10805
  if (error2 === "category_not_found" && onNotFound) return jsx(Fragment, {
10740
10806
  children: onNotFound()
10741
10807
  });
@@ -10756,23 +10822,22 @@ function CategoryView({ code, initialData, onNotFound }) {
10756
10822
  })
10757
10823
  });
10758
10824
  }
10759
- function ProductView({ sku, initialData, showRecommendations = true, onNotFound }) {
10760
- const t = useT();
10761
- const { api, market, emit } = useShop();
10762
- const [loaded, setLoaded] = useState(initialData ? {
10763
- sku,
10764
- product: initialData.product,
10765
- inventory: initialData.inventory ?? null,
10766
- recommendations: []
10767
- } : null);
10825
+ function useProduct(sku, options2 = {}) {
10826
+ const { initialData, showRecommendations = true } = options2;
10827
+ const { api, market, emit, products } = useShop();
10828
+ const [loaded, setLoaded] = useState(null);
10768
10829
  const [failure, setFailure] = useState(null);
10769
10830
  const current2 = loaded?.sku === sku ? loaded : null;
10770
10831
  const error2 = failure?.sku === sku ? failure.code : void 0;
10832
+ const seeded = initialData && initialData.product.sku === sku ? initialData.product : products.get(sku, market);
10833
+ const product = current2?.product ?? seeded ?? null;
10834
+ const inventory2 = current2 ? current2.inventory : initialData && initialData.product.sku === sku ? initialData.inventory ?? null : null;
10771
10835
  useEffect(() => {
10772
10836
  let live = true;
10773
10837
  (async () => {
10774
10838
  try {
10775
10839
  const p = await api.catalog.getProduct(sku, market);
10840
+ products.set(p, market);
10776
10841
  if (!live) return;
10777
10842
  setLoaded({
10778
10843
  sku,
@@ -10784,12 +10849,14 @@ function ProductView({ sku, initialData, showRecommendations = true, onNotFound
10784
10849
  const [inv, recs] = await Promise.allSettled([hasVariants ? Promise.resolve(null) : api.inventory.get(sku), showRecommendations ? api.catalog.getRecommendations(sku, market) : Promise.resolve({
10785
10850
  data: []
10786
10851
  })]);
10852
+ const recommendations = recs.status === "fulfilled" ? recs.value.data : [];
10853
+ products.setMany(recommendations, market);
10787
10854
  if (!live) return;
10788
10855
  setLoaded({
10789
10856
  sku,
10790
10857
  product: p,
10791
10858
  inventory: inv.status === "fulfilled" ? inv.value : null,
10792
- recommendations: recs.status === "fulfilled" ? recs.value.data : []
10859
+ recommendations
10793
10860
  });
10794
10861
  } catch (err) {
10795
10862
  if (!live) return;
@@ -10808,7 +10875,23 @@ function ProductView({ sku, initialData, showRecommendations = true, onNotFound
10808
10875
  return () => {
10809
10876
  live = false;
10810
10877
  };
10811
- }, [api, sku, market, emit, showRecommendations]);
10878
+ }, [api, sku, market, emit, showRecommendations, products]);
10879
+ return {
10880
+ product,
10881
+ inventory: inventory2,
10882
+ recommendations: current2?.recommendations ?? [],
10883
+ loading: !product && !error2,
10884
+ error: error2
10885
+ };
10886
+ }
10887
+ function ProductView({ sku, initialData, showRecommendations = true, onNotFound }) {
10888
+ const t = useT();
10889
+ const { product, inventory: inventory2, recommendations, loading, error: error2 } = useProduct(sku, {
10890
+ ...initialData ? {
10891
+ initialData
10892
+ } : {},
10893
+ showRecommendations
10894
+ });
10812
10895
  const unavailable = jsx(ShopSurface, {
10813
10896
  tone: "light",
10814
10897
  fill: true,
@@ -10823,14 +10906,14 @@ function ProductView({ sku, initialData, showRecommendations = true, onNotFound
10823
10906
  });
10824
10907
  return unavailable;
10825
10908
  }
10826
- if (current2) {
10909
+ if (product) {
10827
10910
  return jsx(ProductDetail, {
10828
- product: current2.product,
10829
- inventory: current2.inventory,
10830
- recommendations: current2.recommendations
10831
- });
10911
+ product,
10912
+ inventory: inventory2,
10913
+ recommendations
10914
+ }, product.sku);
10832
10915
  }
10833
- if (!error2) return jsx(ScreenSkeleton, {
10916
+ if (loading) return jsx(ScreenSkeleton, {
10834
10917
  tone: "light"
10835
10918
  });
10836
10919
  return jsx(ShopSurface, {
@@ -10841,24 +10924,24 @@ function ProductView({ sku, initialData, showRecommendations = true, onNotFound
10841
10924
  })
10842
10925
  });
10843
10926
  }
10844
- const showcase = "_showcase_1qnt2_44";
10845
- const panel = "_panel_1qnt2_48";
10846
- const image = "_image_1qnt2_76";
10847
- const hero = "_hero_1qnt2_116";
10848
- const heroContent = "_heroContent_1qnt2_197";
10849
- const heroText = "_heroText_1qnt2_221";
10850
- const logo = "_logo_1qnt2_230";
10851
- const heroTitle = "_heroTitle_1qnt2_250";
10852
- const heroSub = "_heroSub_1qnt2_274";
10853
- const swipe = "_swipe_1qnt2_293";
10854
- const swipeLabel = "_swipeLabel_1qnt2_300";
10855
- const chevron$1 = "_chevron_1qnt2_306";
10856
- const overlay$1 = "_overlay_1qnt2_11";
10857
- const copy = "_copy_1qnt2_341";
10858
- const title$2 = "_title_1qnt2_346";
10859
- const subtitle$1 = "_subtitle_1qnt2_353";
10860
- const cta = "_cta_1qnt2_359";
10861
- const mosaic = "_mosaic_1qnt2_403";
10927
+ const showcase = "_showcase_c2hqd_59";
10928
+ const panel = "_panel_c2hqd_63";
10929
+ const image = "_image_c2hqd_91";
10930
+ const hero = "_hero_c2hqd_131";
10931
+ const heroContent = "_heroContent_c2hqd_212";
10932
+ const heroText = "_heroText_c2hqd_236";
10933
+ const logo = "_logo_c2hqd_245";
10934
+ const heroTitle = "_heroTitle_c2hqd_265";
10935
+ const heroSub = "_heroSub_c2hqd_289";
10936
+ const swipe = "_swipe_c2hqd_308";
10937
+ const swipeLabel = "_swipeLabel_c2hqd_315";
10938
+ const chevron$1 = "_chevron_c2hqd_321";
10939
+ const overlay$1 = "_overlay_c2hqd_11";
10940
+ const copy = "_copy_c2hqd_356";
10941
+ const title$2 = "_title_c2hqd_361";
10942
+ const subtitle$1 = "_subtitle_c2hqd_368";
10943
+ const cta = "_cta_c2hqd_374";
10944
+ const mosaic = "_mosaic_c2hqd_418";
10862
10945
  const styles$2 = {
10863
10946
  showcase,
10864
10947
  panel,
@@ -10879,7 +10962,6 @@ const styles$2 = {
10879
10962
  cta,
10880
10963
  mosaic
10881
10964
  };
10882
- const SETTLE_MS = 150;
10883
10965
  function useDocumentSnap(ref, enabled = true) {
10884
10966
  useEffect(() => {
10885
10967
  const el = ref.current;
@@ -10887,23 +10969,9 @@ function useDocumentSnap(ref, enabled = true) {
10887
10969
  const root2 = document.documentElement;
10888
10970
  const previousType = root2.style.scrollSnapType;
10889
10971
  const previousPadding = root2.style.scrollPaddingTop;
10890
- let timer2;
10891
- const syncPadding = () => {
10892
- const top2 = getComputedStyle(el).getPropertyValue("--showcase-top").trim();
10893
- root2.style.scrollPaddingTop = top2 === "" ? "0px" : top2;
10894
- };
10895
- const schedule = () => {
10896
- if (timer2) clearTimeout(timer2);
10897
- timer2 = setTimeout(syncPadding, SETTLE_MS);
10898
- };
10899
10972
  root2.style.scrollSnapType = "y mandatory";
10900
- syncPadding();
10901
- window.addEventListener("resize", schedule);
10902
- window.addEventListener("orientationchange", schedule);
10973
+ root2.style.scrollPaddingTop = "0px";
10903
10974
  return () => {
10904
- if (timer2) clearTimeout(timer2);
10905
- window.removeEventListener("resize", schedule);
10906
- window.removeEventListener("orientationchange", schedule);
10907
10975
  root2.style.scrollSnapType = previousType;
10908
10976
  root2.style.scrollPaddingTop = previousPadding;
10909
10977
  };
@@ -11030,7 +11098,6 @@ function HomeShowcase() {
11030
11098
  const assetUrl = useAssetUrl();
11031
11099
  const panels = useSlides();
11032
11100
  const showcaseRef = useRef(null);
11033
- useTopOffset(showcaseRef, "--showcase-top");
11034
11101
  useDocumentSnap(showcaseRef);
11035
11102
  return jsx(ShopSurface, {
11036
11103
  tone: "dark",
@@ -11634,6 +11701,7 @@ export {
11634
11701
  useCartSheet,
11635
11702
  useMoney,
11636
11703
  useNavigate,
11704
+ useProduct,
11637
11705
  useShopApi,
11638
11706
  useShopEvent,
11639
11707
  useShopOptional,
@@ -3487,22 +3487,22 @@
3487
3487
  column-gap: var(--space-12);
3488
3488
  row-gap: var(--space-12);
3489
3489
  }
3490
- ._showcase_1qnt2_44 {
3490
+ ._showcase_c2hqd_59 {
3491
3491
  background: #080e12;
3492
3492
  }
3493
- ._panel_1qnt2_48 {
3493
+ ._panel_c2hqd_63 {
3494
3494
  position: relative;
3495
- height: calc(100lvh - var(--showcase-top, 0px));
3495
+ height: 100lvh;
3496
3496
  scroll-snap-align: start;
3497
3497
  scroll-snap-stop: always;
3498
3498
  overflow: hidden;
3499
3499
  background: #080e12;
3500
3500
  }
3501
- ._image_1qnt2_76 {
3501
+ ._image_c2hqd_91 {
3502
3502
  object-fit: cover;
3503
3503
  object-position: 50% var(--slide-focal-y, 50%);
3504
3504
  }
3505
- ._hero_1qnt2_116::after {
3505
+ ._hero_c2hqd_131::after {
3506
3506
  content: "";
3507
3507
  position: absolute;
3508
3508
  inset: 0;
@@ -3517,7 +3517,7 @@
3517
3517
  rgba(8, 14, 18, 0) 520px);
3518
3518
  pointer-events: none;
3519
3519
  }
3520
- ._hero_1qnt2_116 ._heroContent_1qnt2_197 {
3520
+ ._hero_c2hqd_131 ._heroContent_c2hqd_212 {
3521
3521
  position: absolute;
3522
3522
  bottom: 32px;
3523
3523
  bottom: calc(32px + 100lvh - 100dvh);
@@ -3529,7 +3529,7 @@
3529
3529
  gap: 24px;
3530
3530
  align-items: stretch;
3531
3531
  }
3532
- ._heroText_1qnt2_221 {
3532
+ ._heroText_c2hqd_236 {
3533
3533
  display: flex;
3534
3534
  flex-direction: column;
3535
3535
  gap: 8px;
@@ -3537,11 +3537,11 @@
3537
3537
  padding: 0 24px;
3538
3538
  text-align: center;
3539
3539
  }
3540
- ._heroText_1qnt2_221 ._logo_1qnt2_230 {
3540
+ ._heroText_c2hqd_236 ._logo_c2hqd_245 {
3541
3541
  width: 48px;
3542
3542
  height: 48px;
3543
3543
  }
3544
- ._heroTitle_1qnt2_250 {
3544
+ ._heroTitle_c2hqd_265 {
3545
3545
  width: 100%;
3546
3546
  margin: 0;
3547
3547
  font-family:
@@ -3556,33 +3556,33 @@
3556
3556
  letter-spacing: -1.12px;
3557
3557
  color: #f9fafa;
3558
3558
  }
3559
- ._heroSub_1qnt2_274 {
3559
+ ._heroSub_c2hqd_289 {
3560
3560
  margin: 0;
3561
3561
  font-size: 16px;
3562
3562
  line-height: 1.55;
3563
3563
  color: #f9fafa;
3564
3564
  max-width: 327px;
3565
3565
  }
3566
- ._swipe_1qnt2_293 {
3566
+ ._swipe_c2hqd_308 {
3567
3567
  display: flex;
3568
3568
  flex-direction: column;
3569
3569
  align-items: center;
3570
3570
  gap: 6px;
3571
3571
  padding: 8px 0;
3572
3572
  }
3573
- ._swipeLabel_1qnt2_300 {
3573
+ ._swipeLabel_c2hqd_315 {
3574
3574
  margin: 0;
3575
3575
  font-size: 14px;
3576
3576
  line-height: 1;
3577
3577
  color: rgba(255, 255, 255, 0.54);
3578
3578
  }
3579
- ._chevron_1qnt2_306 {
3579
+ ._chevron_c2hqd_321 {
3580
3580
  width: 24px;
3581
3581
  height: 24px;
3582
3582
  color: rgba(255, 255, 255, 0.54);
3583
- animation: _bob_1qnt2_1 1.8s ease-in-out infinite;
3583
+ animation: _bob_c2hqd_1 1.8s ease-in-out infinite;
3584
3584
  }
3585
- @keyframes _bob_1qnt2_1 {
3585
+ @keyframes _bob_c2hqd_1 {
3586
3586
  0%, 100% {
3587
3587
  transform: translateY(0);
3588
3588
  }
@@ -3590,7 +3590,7 @@
3590
3590
  transform: translateY(4px);
3591
3591
  }
3592
3592
  }
3593
- ._overlay_1qnt2_11 {
3593
+ ._overlay_c2hqd_11 {
3594
3594
  position: absolute;
3595
3595
  inset: 0;
3596
3596
  z-index: 1;
@@ -3607,25 +3607,25 @@
3607
3607
  padding: 0 24px 24px;
3608
3608
  padding-bottom: calc(24px + 100lvh - 100dvh);
3609
3609
  }
3610
- ._copy_1qnt2_341 {
3610
+ ._copy_c2hqd_356 {
3611
3611
  display: flex;
3612
3612
  flex-direction: column;
3613
3613
  gap: 8px;
3614
3614
  }
3615
- ._title_1qnt2_346 {
3615
+ ._title_c2hqd_361 {
3616
3616
  margin: 0;
3617
3617
  font-size: 32px;
3618
3618
  line-height: 1.2;
3619
3619
  font-weight: 700;
3620
3620
  color: #f9fafa;
3621
3621
  }
3622
- ._subtitle_1qnt2_353 {
3622
+ ._subtitle_c2hqd_368 {
3623
3623
  margin: 0;
3624
3624
  font-size: 16px;
3625
3625
  line-height: 1.55;
3626
3626
  color: #f9fafa;
3627
3627
  }
3628
- ._cta_1qnt2_359 {
3628
+ ._cta_c2hqd_374 {
3629
3629
  display: inline-flex;
3630
3630
  align-items: center;
3631
3631
  justify-content: center;
@@ -3639,19 +3639,19 @@
3639
3639
  font-weight: 700;
3640
3640
  text-decoration: none;
3641
3641
  }
3642
- ._cta_1qnt2_359::after {
3642
+ ._cta_c2hqd_374::after {
3643
3643
  content: "";
3644
3644
  position: absolute;
3645
3645
  inset: 0;
3646
3646
  }
3647
- ._cta_1qnt2_359:active {
3647
+ ._cta_c2hqd_374:active {
3648
3648
  background: rgba(249, 250, 250, 0.12);
3649
3649
  }
3650
- ._cta_1qnt2_359:focus-visible {
3650
+ ._cta_c2hqd_374:focus-visible {
3651
3651
  outline: none;
3652
3652
  box-shadow: var(--focus-ring);
3653
3653
  }
3654
- ._mosaic_1qnt2_403 {
3654
+ ._mosaic_c2hqd_418 {
3655
3655
  background: #080e12;
3656
3656
  }
3657
3657
  ._header_8o3d7_25 {
package/dist/styles.css CHANGED
@@ -3487,22 +3487,22 @@
3487
3487
  column-gap: var(--space-12);
3488
3488
  row-gap: var(--space-12);
3489
3489
  }
3490
- ._showcase_1qnt2_44 {
3490
+ ._showcase_c2hqd_59 {
3491
3491
  background: #080e12;
3492
3492
  }
3493
- ._panel_1qnt2_48 {
3493
+ ._panel_c2hqd_63 {
3494
3494
  position: relative;
3495
- height: calc(100lvh - var(--showcase-top, 0px));
3495
+ height: 100lvh;
3496
3496
  scroll-snap-align: start;
3497
3497
  scroll-snap-stop: always;
3498
3498
  overflow: hidden;
3499
3499
  background: #080e12;
3500
3500
  }
3501
- ._image_1qnt2_76 {
3501
+ ._image_c2hqd_91 {
3502
3502
  object-fit: cover;
3503
3503
  object-position: 50% var(--slide-focal-y, 50%);
3504
3504
  }
3505
- ._hero_1qnt2_116::after {
3505
+ ._hero_c2hqd_131::after {
3506
3506
  content: "";
3507
3507
  position: absolute;
3508
3508
  inset: 0;
@@ -3517,7 +3517,7 @@
3517
3517
  rgba(8, 14, 18, 0) 520px);
3518
3518
  pointer-events: none;
3519
3519
  }
3520
- ._hero_1qnt2_116 ._heroContent_1qnt2_197 {
3520
+ ._hero_c2hqd_131 ._heroContent_c2hqd_212 {
3521
3521
  position: absolute;
3522
3522
  bottom: 32px;
3523
3523
  bottom: calc(32px + 100lvh - 100dvh);
@@ -3529,7 +3529,7 @@
3529
3529
  gap: 24px;
3530
3530
  align-items: stretch;
3531
3531
  }
3532
- ._heroText_1qnt2_221 {
3532
+ ._heroText_c2hqd_236 {
3533
3533
  display: flex;
3534
3534
  flex-direction: column;
3535
3535
  gap: 8px;
@@ -3537,11 +3537,11 @@
3537
3537
  padding: 0 24px;
3538
3538
  text-align: center;
3539
3539
  }
3540
- ._heroText_1qnt2_221 ._logo_1qnt2_230 {
3540
+ ._heroText_c2hqd_236 ._logo_c2hqd_245 {
3541
3541
  width: 48px;
3542
3542
  height: 48px;
3543
3543
  }
3544
- ._heroTitle_1qnt2_250 {
3544
+ ._heroTitle_c2hqd_265 {
3545
3545
  width: 100%;
3546
3546
  margin: 0;
3547
3547
  font-family:
@@ -3556,33 +3556,33 @@
3556
3556
  letter-spacing: -1.12px;
3557
3557
  color: #f9fafa;
3558
3558
  }
3559
- ._heroSub_1qnt2_274 {
3559
+ ._heroSub_c2hqd_289 {
3560
3560
  margin: 0;
3561
3561
  font-size: 16px;
3562
3562
  line-height: 1.55;
3563
3563
  color: #f9fafa;
3564
3564
  max-width: 327px;
3565
3565
  }
3566
- ._swipe_1qnt2_293 {
3566
+ ._swipe_c2hqd_308 {
3567
3567
  display: flex;
3568
3568
  flex-direction: column;
3569
3569
  align-items: center;
3570
3570
  gap: 6px;
3571
3571
  padding: 8px 0;
3572
3572
  }
3573
- ._swipeLabel_1qnt2_300 {
3573
+ ._swipeLabel_c2hqd_315 {
3574
3574
  margin: 0;
3575
3575
  font-size: 14px;
3576
3576
  line-height: 1;
3577
3577
  color: rgba(255, 255, 255, 0.54);
3578
3578
  }
3579
- ._chevron_1qnt2_306 {
3579
+ ._chevron_c2hqd_321 {
3580
3580
  width: 24px;
3581
3581
  height: 24px;
3582
3582
  color: rgba(255, 255, 255, 0.54);
3583
- animation: _bob_1qnt2_1 1.8s ease-in-out infinite;
3583
+ animation: _bob_c2hqd_1 1.8s ease-in-out infinite;
3584
3584
  }
3585
- @keyframes _bob_1qnt2_1 {
3585
+ @keyframes _bob_c2hqd_1 {
3586
3586
  0%, 100% {
3587
3587
  transform: translateY(0);
3588
3588
  }
@@ -3590,7 +3590,7 @@
3590
3590
  transform: translateY(4px);
3591
3591
  }
3592
3592
  }
3593
- ._overlay_1qnt2_11 {
3593
+ ._overlay_c2hqd_11 {
3594
3594
  position: absolute;
3595
3595
  inset: 0;
3596
3596
  z-index: 1;
@@ -3607,25 +3607,25 @@
3607
3607
  padding: 0 24px 24px;
3608
3608
  padding-bottom: calc(24px + 100lvh - 100dvh);
3609
3609
  }
3610
- ._copy_1qnt2_341 {
3610
+ ._copy_c2hqd_356 {
3611
3611
  display: flex;
3612
3612
  flex-direction: column;
3613
3613
  gap: 8px;
3614
3614
  }
3615
- ._title_1qnt2_346 {
3615
+ ._title_c2hqd_361 {
3616
3616
  margin: 0;
3617
3617
  font-size: 32px;
3618
3618
  line-height: 1.2;
3619
3619
  font-weight: 700;
3620
3620
  color: #f9fafa;
3621
3621
  }
3622
- ._subtitle_1qnt2_353 {
3622
+ ._subtitle_c2hqd_368 {
3623
3623
  margin: 0;
3624
3624
  font-size: 16px;
3625
3625
  line-height: 1.55;
3626
3626
  color: #f9fafa;
3627
3627
  }
3628
- ._cta_1qnt2_359 {
3628
+ ._cta_c2hqd_374 {
3629
3629
  display: inline-flex;
3630
3630
  align-items: center;
3631
3631
  justify-content: center;
@@ -3639,19 +3639,19 @@
3639
3639
  font-weight: 700;
3640
3640
  text-decoration: none;
3641
3641
  }
3642
- ._cta_1qnt2_359::after {
3642
+ ._cta_c2hqd_374::after {
3643
3643
  content: "";
3644
3644
  position: absolute;
3645
3645
  inset: 0;
3646
3646
  }
3647
- ._cta_1qnt2_359:active {
3647
+ ._cta_c2hqd_374:active {
3648
3648
  background: rgba(249, 250, 250, 0.12);
3649
3649
  }
3650
- ._cta_1qnt2_359:focus-visible {
3650
+ ._cta_c2hqd_374:focus-visible {
3651
3651
  outline: none;
3652
3652
  box-shadow: var(--focus-ring);
3653
3653
  }
3654
- ._mosaic_1qnt2_403 {
3654
+ ._mosaic_c2hqd_418 {
3655
3655
  background: #080e12;
3656
3656
  }
3657
3657
  ._header_8o3d7_25 {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nok-integration/storefront-react",
3
- "version": "0.19.39",
3
+ "version": "0.19.41",
4
4
  "description": "Mountable React storefront components: catalogue, product detail and cart, mounted directly into a host DOM tree. No iframe.",
5
5
  "license": "UNLICENSED",
6
6
  "publishConfig": {