@syntrologie/adapt-product 2.49.1 → 2.51.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cdn.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  onActivate,
3
3
  runtime
4
- } from "./chunk-LFYTHTQS.js";
4
+ } from "./chunk-F6ZXHGAT.js";
5
5
  import "./chunk-D2W6UYLT.js";
6
6
 
7
7
  // src/cdn.ts
@@ -342,6 +342,110 @@ var NotifyZ = z.object({
342
342
  icon: z.string().optional().describe("Notification icon (emoji or URL)")
343
343
  }).describe("Optional toast notification shown when this action triggers.").nullable().optional();
344
344
 
345
+ // ../../sdk-contracts/dist/viewport.js
346
+ var VIEWPORT_CLASS_ATTR = "data-viewport";
347
+ var DESKTOP_MIN_WIDTH_PX = 720;
348
+ function isCoarsePointer() {
349
+ if (typeof window === "undefined" || typeof window.matchMedia !== "function")
350
+ return true;
351
+ return window.matchMedia("(pointer: coarse)").matches;
352
+ }
353
+ function classifyPointer() {
354
+ return isCoarsePointer() ? "coarse" : "fine";
355
+ }
356
+ function hasAnyFinePointer() {
357
+ if (typeof window === "undefined" || typeof window.matchMedia !== "function")
358
+ return true;
359
+ return window.matchMedia("(any-pointer: fine)").matches;
360
+ }
361
+ function observePointer(cb) {
362
+ const read = () => ({ primary: classifyPointer(), anyFine: hasAnyFinePointer() });
363
+ let current = read();
364
+ cb(current);
365
+ if (typeof window === "undefined" || typeof window.matchMedia !== "function")
366
+ return () => {
367
+ };
368
+ const coarse = window.matchMedia("(pointer: coarse)");
369
+ const anyFine = window.matchMedia("(any-pointer: fine)");
370
+ const onChange = () => {
371
+ const next = read();
372
+ if (next.primary !== current.primary || next.anyFine !== current.anyFine) {
373
+ current = next;
374
+ cb(next);
375
+ }
376
+ };
377
+ const sub = (mq) => {
378
+ if (mq.addEventListener)
379
+ mq.addEventListener("change", onChange);
380
+ else
381
+ mq.addListener?.(onChange);
382
+ };
383
+ const unsub = (mq) => {
384
+ if (mq.removeEventListener)
385
+ mq.removeEventListener("change", onChange);
386
+ else
387
+ mq.removeListener?.(onChange);
388
+ };
389
+ sub(coarse);
390
+ sub(anyFine);
391
+ return () => {
392
+ unsub(coarse);
393
+ unsub(anyFine);
394
+ };
395
+ }
396
+ var POINTER_CLASS_ATTR = "data-pointer";
397
+ function classifyViewport() {
398
+ if (typeof window === "undefined")
399
+ return "mobile";
400
+ return window.innerWidth >= DESKTOP_MIN_WIDTH_PX ? "desktop" : "mobile";
401
+ }
402
+ function observeViewportClass(cb) {
403
+ let current = classifyViewport();
404
+ cb(current);
405
+ if (typeof window === "undefined")
406
+ return () => {
407
+ };
408
+ const onResize = () => {
409
+ const next = classifyViewport();
410
+ if (next !== current) {
411
+ current = next;
412
+ cb(next);
413
+ }
414
+ };
415
+ window.addEventListener("resize", onResize, { passive: true });
416
+ return () => window.removeEventListener("resize", onResize);
417
+ }
418
+ var ViewportClassController = class {
419
+ constructor(host) {
420
+ this.value = classifyViewport();
421
+ this.pointer = classifyPointer();
422
+ this.anyFinePointer = hasAnyFinePointer();
423
+ this._unsubscribes = [];
424
+ this._host = host;
425
+ host.addController(this);
426
+ }
427
+ hostConnected() {
428
+ this._unsubscribes = [
429
+ observeViewportClass((c2) => {
430
+ this.value = c2;
431
+ this._host.setAttribute(VIEWPORT_CLASS_ATTR, c2);
432
+ this._host.requestUpdate();
433
+ }),
434
+ observePointer(({ primary, anyFine }) => {
435
+ this.pointer = primary;
436
+ this.anyFinePointer = anyFine;
437
+ this._host.setAttribute(POINTER_CLASS_ATTR, primary);
438
+ this._host.requestUpdate();
439
+ })
440
+ ];
441
+ }
442
+ hostDisconnected() {
443
+ for (const off of this._unsubscribes)
444
+ off();
445
+ this._unsubscribes = [];
446
+ }
447
+ };
448
+
345
449
  // src/presets-store.ts
346
450
  function presetsWindow() {
347
451
  if (typeof window === "undefined") return void 0;
@@ -1903,16 +2007,22 @@ syntro-pdp-other-products-carousel {
1903
2007
  box-sizing: border-box;
1904
2008
  }
1905
2009
 
1906
- @media (max-width: 719px) {
1907
- .opc-item {
1908
- flex: 0 0 80%;
1909
- }
2010
+ /* viewport-scoped (was a width media query): classification is central
2011
+ (sdk-contracts classifyViewport()); the host tag carries data-viewport. */
2012
+ syntro-pdp-other-products-carousel[data-viewport="mobile"] .opc-item {
2013
+ flex: 0 0 80%;
1910
2014
  }
1911
2015
  `;
1912
2016
  var PdpOtherProductsCarouselLit = class extends LitElement4 {
1913
2017
  constructor() {
1914
2018
  super(...arguments);
1915
2019
  this.data = null;
2020
+ /** Stamps data-viewport on this tag (central classification,
2021
+ * sdk-contracts) — the viewport-scoped CSS keys off it. */
2022
+ // biome-ignore lint/correctness/noUnusedPrivateClassMembers: the
2023
+ // constructor registers the controller (stamps data-viewport); the
2024
+ // field exists to own its lifetime, not to be read.
2025
+ this._viewport = new ViewportClassController(this);
1916
2026
  }
1917
2027
  createRenderRoot() {
1918
2028
  return this;
@@ -1969,16 +2079,22 @@ syntro-pdp-other-products-grid {
1969
2079
  gap: 0.75rem;
1970
2080
  }
1971
2081
 
1972
- @media (max-width: 719px) {
1973
- .grid {
1974
- grid-template-columns: 1fr;
1975
- }
2082
+ /* viewport-scoped (was a width media query): classification is central
2083
+ (sdk-contracts classifyViewport()); the host tag carries data-viewport. */
2084
+ syntro-pdp-other-products-grid[data-viewport="mobile"] .grid {
2085
+ grid-template-columns: 1fr;
1976
2086
  }
1977
2087
  `;
1978
2088
  var PdpOtherProductsGridLit = class extends LitElement5 {
1979
2089
  constructor() {
1980
2090
  super(...arguments);
1981
2091
  this.data = null;
2092
+ /** Stamps data-viewport on this tag (central classification,
2093
+ * sdk-contracts) — the viewport-scoped CSS keys off it. */
2094
+ // biome-ignore lint/correctness/noUnusedPrivateClassMembers: the
2095
+ // constructor registers the controller (stamps data-viewport); the
2096
+ // field exists to own its lifetime, not to be read.
2097
+ this._viewport = new ViewportClassController(this);
1982
2098
  }
1983
2099
  createRenderRoot() {
1984
2100
  return this;
@@ -2443,8 +2559,10 @@ var SECTION_LAYOUT_CSS = `
2443
2559
  the full-bleed band (a grid can't be both full-width AND content-centered)
2444
2560
  for the side-by-side reading rhythm; the section reads as a deliberate
2445
2561
  magazine block on the base background. Mobile keeps the stacked layout. */
2446
- @media (min-width: 720px) {
2447
- [data-syntro-pdp-section][data-slot='pdp-slot-trending-news'] {
2562
+ /* viewport-scoped (was a width media query): classification is central
2563
+ (sdk-contracts classifyViewport()); the syntro-pdp tag carries
2564
+ data-viewport, stamped by ViewportClassController. */
2565
+ syntro-pdp[data-viewport="desktop"] [data-syntro-pdp-section][data-slot='pdp-slot-trending-news'] {
2448
2566
  background: transparent;
2449
2567
  display: grid;
2450
2568
  grid-template-columns: minmax(0, 0.8fr) minmax(0, 1.6fr);
@@ -2456,26 +2574,26 @@ var SECTION_LAYOUT_CSS = `
2456
2574
  }
2457
2575
  /* Title column: header left-aligned (not the full-band centered eyebrow),
2458
2576
  and gently sticky so it holds while the stories scroll past. */
2459
- [data-syntro-pdp-section][data-slot='pdp-slot-trending-news'] > syntro-pdp-section-header {
2577
+ syntro-pdp[data-viewport="desktop"] [data-syntro-pdp-section][data-slot='pdp-slot-trending-news'] > syntro-pdp-section-header {
2460
2578
  position: sticky;
2461
2579
  top: clamp(24px, 8vh, 72px);
2462
2580
  }
2463
- [data-syntro-pdp-section][data-slot='pdp-slot-trending-news'] .syntro-pdp-section-eyebrow {
2581
+ syntro-pdp[data-viewport="desktop"] [data-syntro-pdp-section][data-slot='pdp-slot-trending-news'] .syntro-pdp-section-eyebrow {
2464
2582
  justify-content: flex-start;
2465
2583
  }
2466
- [data-syntro-pdp-section][data-slot='pdp-slot-trending-news'] .syntro-pdp-section-headline {
2584
+ syntro-pdp[data-viewport="desktop"] [data-syntro-pdp-section][data-slot='pdp-slot-trending-news'] .syntro-pdp-section-headline {
2467
2585
  max-width: none;
2468
2586
  margin-inline: 0;
2469
2587
  padding-inline: 0;
2470
2588
  }
2471
2589
  /* Stories column: the body fills the wider right column (drop the centered
2472
2590
  inner constraint + the header-gap top margin \u2014 the grid gap spaces it). */
2473
- [data-syntro-pdp-section][data-slot='pdp-slot-trending-news'] > [data-syntro-pdp-section-inner] {
2591
+ syntro-pdp[data-viewport="desktop"] [data-syntro-pdp-section][data-slot='pdp-slot-trending-news'] > [data-syntro-pdp-section-inner] {
2474
2592
  max-width: none;
2475
2593
  margin: 0;
2476
2594
  padding: 0;
2477
2595
  }
2478
- }
2596
+
2479
2597
  `;
2480
2598
  var CHAT_EXCERPT_KEY = "syntro.chat.lastExcerpt";
2481
2599
  var CHAT_EXCERPT_MAX = 7500;
@@ -2662,6 +2780,12 @@ var _SyntroPdpLit = class _SyntroPdpLit extends LitElement6 {
2662
2780
  const open = e2.target.open;
2663
2781
  this._dispatchDecision({ type: "dossier-toggled", open });
2664
2782
  };
2783
+ /** Stamps data-viewport on this tag (central classification,
2784
+ * sdk-contracts) — the viewport-scoped CSS keys off it. */
2785
+ // biome-ignore lint/correctness/noUnusedPrivateClassMembers: the
2786
+ // constructor registers the controller (stamps data-viewport); the
2787
+ // field exists to own its lifetime, not to be read.
2788
+ this._viewport = new ViewportClassController(this);
2665
2789
  }
2666
2790
  /**
2667
2791
  * Widget props (the `adaptive-product:pdp` mountable sets `el.props` before
@@ -5855,6 +5979,26 @@ function _readDevice() {
5855
5979
  }
5856
5980
  var SPEC_CHIP_FADE_MASK = "linear-gradient(90deg, black 0, black calc(100% - 18px), transparent 100%)";
5857
5981
  var CARD_FLIP_CSS = `
5982
+ /* \u2500\u2500 Height-bounded container opt-in (velvet deck band) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
5983
+ The canvas that owns a DEFINITE-height mount opts the card into filling
5984
+ it by setting --sc-card-host-height: 100% (velvet: .deck-card tokens).
5985
+ The chain below then hands that definite height to the front-face grid,
5986
+ whose 1fr/auto rows resolve inside it and whose image box (max-height:
5987
+ 100% on .sc-product-card__media) crops to what remains. Everywhere the
5988
+ token is unset the host is auto-height and every percentage in the
5989
+ chain is inert \u2014 chat/PDP stacked cards are untouched.
5990
+ Regression this exists for: #3591 shaved the solo-deck band 24px and
5991
+ the fixed 132\xD7176 image box pushed the CTA/link row past the card
5992
+ bottom (live on iglowmedspa.com 2026-09-03; pinned by
5993
+ test-server/tests/velvet-deck-product-card-fit.spec.ts). */
5994
+ syntro-product-card {
5995
+ height: var(--sc-card-host-height, auto);
5996
+ min-height: 0;
5997
+ }
5998
+ .sc-product-card { height: 100%; min-height: 0; }
5999
+ .sc-product-card > [data-face] { height: 100%; min-height: 0; }
6000
+ .sc-product-card [data-product-card-grid] { height: 100%; min-height: 0; }
6001
+
5858
6002
  /* Hide the scrollbar chrome on the declared, scrollable spec-chip tracks
5859
6003
  (data-clip-ok scroll containers) while keeping them scrollable. The inline
5860
6004
  styles set scrollbar-width:none (Firefox); WebKit needs this pseudo. */
@@ -6365,6 +6509,16 @@ function renderProductCardInner(product, density, resolved, visibleFacts, ctas,
6365
6509
  height: "var(--sc-card-image-h, auto)",
6366
6510
  // 132 × (4/3) = 176 → the spec's 132×176 portrait. Velvet's token wins.
6367
6511
  aspectRatio: "var(--sc-card-image-aspect, 3 / 4)",
6512
+ // The aspect box is a PREFERENCE, never a floor: in a definite-height
6513
+ // container (velvet's deck band) the box caps at the area's height and
6514
+ // the photo object-fit:covers into whatever remains, so the card always
6515
+ // fits the band. In auto-height contexts (chat/PDP stacked layout)
6516
+ // 100% resolves against auto → no-op, the 3/4 box stands unchanged.
6517
+ // Regression this guards: #3591 shaved the solo-deck band 24px and the
6518
+ // fixed 176px box pushed the CTA/link row past the card bottom
6519
+ // (velvet-deck-product-card-fit.spec.ts).
6520
+ maxHeight: "100%",
6521
+ overflow: "hidden",
6368
6522
  alignSelf: "var(--sc-card-image-align, start)",
6369
6523
  minWidth: "0",
6370
6524
  minHeight: "0"
@@ -6534,7 +6688,12 @@ function renderProductCardInner(product, density, resolved, visibleFacts, ctas,
6534
6688
  justifyContent: "space-between",
6535
6689
  gap: "var(--sc-card-cta-gap, 0.5rem)",
6536
6690
  marginTop: "0",
6537
- minWidth: "0"
6691
+ minWidth: "0",
6692
+ // Lift the CTA pair off the card's bottom edge where a canvas overlays
6693
+ // chrome there (velvet's deck pager circles half-lap that edge — the
6694
+ // "Product Page" link sat right under them on crushed cards). The
6695
+ // canvas sets the token; default 0 keeps every other surface as-is.
6696
+ paddingBottom: "var(--sc-card-cta-padding-bottom, 0)"
6538
6697
  };
6539
6698
  const primaryCtaStyles = {
6540
6699
  display: "inline-flex",
@@ -11977,4 +12136,4 @@ export {
11977
12136
  * SPDX-License-Identifier: BSD-3-Clause
11978
12137
  *)
11979
12138
  */
11980
- //# sourceMappingURL=chunk-LFYTHTQS.js.map
12139
+ //# sourceMappingURL=chunk-F6ZXHGAT.js.map