@vio-live/web-sdk 0.35.0 → 0.37.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.
@@ -1,4 +1,4 @@
1
- import { Vio, __decorateClass, primaryImageUrl, displayPrice, resolveDetailLayout, toCampaignId, getGlobalCurrency, copyAppliedLayout, onGlobalDetailLayoutChange, layoutSignature, layoutOriginOf, ensureVippsButton, formatPrice, shippingDescriptionOf, buyerRefOf, nexiReturnPaymentId, readNexiPending, kustomReturnOutcome, KUSTOM_RETURN_QUERY_KEYS, storageGet, ADYEN_RETURN_METHOD, adyenReturnParams, ADYEN_RETURN_QUERY_KEYS, adyenReturnOutcome, getGlobalCountryCode, isValidPhone, gatewayHasBuyerRef, rememberedCheckoutSponsor, checkoutSponsorOf, AdyenOriginError, normalizeSlotId, autoSlotDisplayName, autoSlotId, sponsorIdentityFrom, Configuration, normalizeScope, campaignCandidates } from './chunk-QA5R4VYF.js';
1
+ import { Vio, __decorateClass, primaryImageUrl, displayPrice, resolveDetailLayout, toCampaignId, getGlobalCurrency, layoutOriginOf, copyAppliedLayout, onGlobalDetailLayoutChange, layoutSignature, ensureVippsButton, formatPrice, currentGlobalDescriptionLabel, shippingDescriptionOf, lineOrigins, buyerRefOf, nexiReturnPaymentId, readNexiPending, kustomReturnOutcome, KUSTOM_RETURN_QUERY_KEYS, storageGet, ADYEN_RETURN_METHOD, adyenReturnParams, ADYEN_RETURN_QUERY_KEYS, adyenReturnOutcome, getGlobalCountryCode, isValidPhone, gatewayHasBuyerRef, rememberedCheckoutSponsor, checkoutSponsorOf, AdyenOriginError, normalizeSlotId, autoSlotDisplayName, autoSlotId, sponsorIdentityFrom, Configuration, normalizeScope, campaignCandidates, ctaText } from './chunk-KVI56NGO.js';
2
2
  import { LitElement, css, html, nothing } from 'lit';
3
3
  import { property, state, query } from 'lit/decorators.js';
4
4
 
@@ -171,6 +171,7 @@ var THEME_KEY_TO_CSS_VAR = {
171
171
  detailDescriptionPreviewLines: "--vio-detail-description-preview-lines",
172
172
  detailHighlightsDisplay: "--vio-detail-highlights-display",
173
173
  detailShippingReturnsDisplay: "--vio-detail-shipping-returns-display",
174
+ cartHighlightsDisplay: "--vio-cart-highlights-display",
174
175
  detailThumbSize: "--vio-detail-thumb-size",
175
176
  detailThumbGap: "--vio-detail-thumb-gap",
176
177
  detailThumbRadius: "--vio-detail-thumb-radius",
@@ -372,6 +373,61 @@ function closestAcrossShadow(start, selector) {
372
373
  return null;
373
374
  }
374
375
 
376
+ // src/ui/cta.ts
377
+ function labelOf(button) {
378
+ if (button.hasAttribute("data-vio-cta-label")) return ctaText(button.getAttribute("data-vio-cta-label"));
379
+ return ctaText(button.getAttribute("aria-label")) ?? ctaText(button.textContent);
380
+ }
381
+ function emitCta(host, location, cta, contextOf, extra = {}, button = null) {
382
+ try {
383
+ const context = contextOf(cta, button);
384
+ if (context === null) return;
385
+ const detail = { ...context, ...stripUndefined(extra), cta, location };
386
+ host.dispatchEvent(new CustomEvent("vio:cta-click", { bubbles: true, composed: true, detail }));
387
+ } catch {
388
+ }
389
+ }
390
+ function stripUndefined(obj) {
391
+ const out = {};
392
+ for (const [k, v] of Object.entries(obj)) if (v !== void 0) out[k] = v;
393
+ return out;
394
+ }
395
+ function trackCtaClicks(host, location, contextOf) {
396
+ const root = host.renderRoot ?? host.shadowRoot;
397
+ if (!root || typeof root.addEventListener !== "function") return () => {
398
+ };
399
+ const onClick = (ev) => {
400
+ try {
401
+ const button = markedButton(ev, root);
402
+ if (!button) return;
403
+ const cta = button.getAttribute("data-vio-cta") ?? "";
404
+ if (button.tagName === "SUMMARY" && button.parentElement?.open) return;
405
+ const option = button.getAttribute("data-vio-cta-option");
406
+ emitCta(
407
+ host,
408
+ location,
409
+ cta,
410
+ contextOf,
411
+ { label: labelOf(button), ...option !== null ? { optionValue: option } : {} },
412
+ button
413
+ );
414
+ } catch {
415
+ }
416
+ };
417
+ root.addEventListener("click", onClick, { capture: true });
418
+ return () => root.removeEventListener("click", onClick, { capture: true });
419
+ }
420
+ function markedButton(ev, root) {
421
+ const path = typeof ev.composedPath === "function" ? ev.composedPath() : [];
422
+ for (const node of path) {
423
+ if (node === root) break;
424
+ if (!(node instanceof Element)) continue;
425
+ if (node.getRootNode() !== root) continue;
426
+ if (node.hasAttribute("data-vio-cta")) return node;
427
+ }
428
+ return null;
429
+ }
430
+
375
431
  // src/ui/components/vio-product.ts
376
432
  var VioProduct = class extends LitElement {
377
433
  constructor() {
@@ -404,6 +460,21 @@ var VioProduct = class extends LitElement {
404
460
  this.cartQty = 0;
405
461
  this.justAdded = false;
406
462
  this.boundCartChange = () => this.updateInCart();
463
+ /** Stops reporting the taps on the buttons (`vio:cta-click`). */
464
+ this.stopCtaClicks = null;
465
+ /** What a tap on the card says (`vio:cta-click`): its product, sponsor and attribution. */
466
+ this.ctaContext = () => {
467
+ const a = this.attribution();
468
+ return {
469
+ productId: this.productId || void 0,
470
+ sponsorId: this.sponsorId || this.sponsorFromAttribution(),
471
+ campaignId: a.campaignId,
472
+ slot: a.slot,
473
+ contentId: a.contentId,
474
+ contentTitle: a.contentTitle,
475
+ componentTemplateId: a.componentTemplateId
476
+ };
477
+ };
407
478
  }
408
479
  /** The slot id in effect: `vio-slot`, else the auto id (`card-N`). Read-only. */
409
480
  get effectiveSlot() {
@@ -608,8 +679,12 @@ var VioProduct = class extends LitElement {
608
679
  this.updateInCart();
609
680
  this.slotRegistration.connect(this.slotAttributes());
610
681
  Vio.cart.addEventListener("change", this.boundCartChange);
682
+ this.stopCtaClicks?.();
683
+ this.stopCtaClicks = trackCtaClicks(this, "card", this.ctaContext);
611
684
  }
612
685
  disconnectedCallback() {
686
+ this.stopCtaClicks?.();
687
+ this.stopCtaClicks = null;
613
688
  Vio.cart.removeEventListener("change", this.boundCartChange);
614
689
  this.slotRegistration.clear();
615
690
  super.disconnectedCallback();
@@ -659,6 +734,7 @@ var VioProduct = class extends LitElement {
659
734
  onKeydown(e) {
660
735
  if (e.key === "Enter" || e.key === " ") {
661
736
  e.preventDefault();
737
+ if (e.target === e.currentTarget) emitCta(this, "card", "open", this.ctaContext);
662
738
  this.onClick();
663
739
  }
664
740
  }
@@ -709,6 +785,8 @@ var VioProduct = class extends LitElement {
709
785
  return html`
710
786
  <div
711
787
  class="card"
788
+ data-vio-cta="open"
789
+ data-vio-cta-label=""
712
790
  @click=${this.onClick}
713
791
  @keydown=${this.onKeydown}
714
792
  role="button"
@@ -719,6 +797,8 @@ var VioProduct = class extends LitElement {
719
797
  ${this.imageUrl ? html`<img src=${this.imageUrl} alt=${this.name || this.brand} loading="lazy" />` : ""}
720
798
  ${this.buttonStyle === "icon" ? html`<button
721
799
  class="add-btn ${this.inCart ? "in-cart" : ""} ${this.justAdded ? "pop" : ""}"
800
+ data-vio-cta="add"
801
+ data-vio-cta-label="Legg i kurv"
722
802
  @click=${this.onAdd}
723
803
  aria-label=${this.inCart ? "I handlekurven \u2013 legg til en til" : "Legg i kurv"}
724
804
  title=${this.inCart ? "I handlekurven" : "Legg i kurv"}
@@ -739,7 +819,7 @@ var VioProduct = class extends LitElement {
739
819
  ${this.showDescription && this.description ? html`<p class="description">${this.description}</p>` : ""}
740
820
  ${this.price ? html`<div class="price">${this.price}</div>` : ""}
741
821
  ${this.retailer ? html`<div class="retailer">${this.retailer}</div>` : ""}
742
- ${this.buttonStyle === "bar" ? html`<button class="bar-btn ${this.justAdded ? "pop" : ""}" @click=${this.onAdd}>
822
+ ${this.buttonStyle === "bar" ? html`<button class="bar-btn ${this.justAdded ? "pop" : ""}" data-vio-cta="add" data-vio-cta-label=${this.buttonLabel} @click=${this.onAdd}>
743
823
  ${this.inCart ? `${this.buttonLabel} \xB7 ${this.cartQty}` : this.buttonLabel}
744
824
  </button>` : ""}
745
825
  </div>`}
@@ -2051,6 +2131,67 @@ function unlockPageScroll() {
2051
2131
  window.scrollTo(0, scrollY);
2052
2132
  root.style.scrollBehavior = behavior;
2053
2133
  }
2134
+ var HIGHLIGHT_ICONS = {
2135
+ plane: "M21.5 2.5 2.5 10l7 3m12-10.5-12 10.5m12-10.5L17 21l-7.5-8m0 0V20l3-4M5 17l-2.5 2.5M7.5 18.5 6 20",
2136
+ heart: "M12 20.5s-8.5-5-8.5-11A4.75 4.75 0 0 1 12 6.75 4.75 4.75 0 0 1 20.5 9.5c0 6-8.5 11-8.5 11Z",
2137
+ shirt: "M8.5 3.5 3 6l1.75 4.25L7 9.5v11h10v-11l2.25.75L21 6l-5.5-2.5a3.5 3.5 0 0 1-7 0Z",
2138
+ truck: "M2.5 5.5h11v10h-11zM13.5 9.5h4l3 3.5v2.5h-7M6.5 18.5a2 2 0 1 0 0-.01M17 18.5a2 2 0 1 0 0-.01",
2139
+ return: "M4 9h11.5a4.5 4.5 0 0 1 0 9H8M4 9l4-4M4 9l4 4",
2140
+ leaf: "M5 19C5 10 10 4.5 20 4.5c0 10-5.5 15-14 15M5 19l7-7",
2141
+ star: "m12 3.5 2.6 5.4 5.9.8-4.3 4.1 1 5.9L12 17l-5.2 2.7 1-5.9-4.3-4.1 5.9-.8Z",
2142
+ shield: "M12 3 4.5 6v5.5c0 4.5 3.2 8.2 7.5 9.5 4.3-1.3 7.5-5 7.5-9.5V6Z",
2143
+ mailbox: "M3 19v-8a4 4 0 0 1 8 0v8m-8 0h18v-7a4 4 0 0 0-4-4H7m8 0V4h3M6 12h2",
2144
+ home: "M3 11 12 4l9 7M5.5 9.5V20h13V9.5M10 20v-5h4v5",
2145
+ pin: "M12 21s-6.5-6-6.5-11a6.5 6.5 0 0 1 13 0c0 5-6.5 11-6.5 11Zm0-8.5a2.5 2.5 0 1 0 0-5 2.5 2.5 0 0 0 0 5Z",
2146
+ clock: "M12 21a9 9 0 1 0 0-18 9 9 0 0 0 0 18Zm0-13.5V12l3 2"
2147
+ };
2148
+ function renderHighlights(items) {
2149
+ const list = (Array.isArray(items) ? items : []).filter((h) => h && h.title);
2150
+ if (list.length === 0) return nothing;
2151
+ return html`<ul class="highlights" part="highlights">
2152
+ ${list.map((h) => {
2153
+ const path = h.icon ? HIGHLIGHT_ICONS[h.icon] : void 0;
2154
+ const icon = path ? html`<svg class="hl-icon" viewBox="0 0 24 24" aria-hidden="true"><path d=${path}></path></svg>` : h.icon ? html`<img class="hl-icon" src=${h.icon} alt="" />` : nothing;
2155
+ return html`<li class="hl" part="highlight">
2156
+ ${icon}
2157
+ <span class="hl-title">${h.title}</span>
2158
+ ${h.text ? html`<span class="hl-text">${h.text}</span>` : nothing}
2159
+ </li>`;
2160
+ })}
2161
+ </ul>`;
2162
+ }
2163
+ function highlightsStyles(scope) {
2164
+ const display = scope === "detail" ? css`var(--vio-detail-highlights-display, grid)` : css`var(--vio-cart-highlights-display, grid)`;
2165
+ const padding = scope === "detail" ? css`var(--vio-detail-highlights-padding, 16px 0 2px)` : css`var(--vio-cart-highlights-padding, 14px 0 2px)`;
2166
+ return css`
2167
+ /* Like a shop's trust strip: a thin icon, the promise in regular weight,
2168
+ a smaller line under it — nothing bold. */
2169
+ .highlights {
2170
+ list-style: none;
2171
+ margin: 0;
2172
+ padding: ${padding};
2173
+ display: ${display};
2174
+ grid-auto-flow: column;
2175
+ grid-auto-columns: 1fr;
2176
+ gap: 10px;
2177
+ border-top: 1px solid var(--vio-color-border, #e5e5e5);
2178
+ }
2179
+ .hl { display: flex; flex-direction: column; align-items: center; text-align: center; gap: 2px; min-width: 0; }
2180
+ .hl-icon {
2181
+ width: var(--vio-detail-highlight-icon-size, 22px);
2182
+ height: var(--vio-detail-highlight-icon-size, 22px);
2183
+ margin-bottom: 8px;
2184
+ fill: none;
2185
+ stroke: currentColor;
2186
+ stroke-width: 1.2;
2187
+ stroke-linecap: round;
2188
+ stroke-linejoin: round;
2189
+ object-fit: contain;
2190
+ }
2191
+ .hl-title { font-size: 12px; font-weight: 400; line-height: 1.3; letter-spacing: 0.01em; color: var(--vio-color-text, #0a0a0a); }
2192
+ .hl-text { font-size: 10.5px; line-height: 1.35; letter-spacing: 0.01em; color: var(--vio-color-text-secondary, #666); }
2193
+ `;
2194
+ }
2054
2195
 
2055
2196
  // src/ui/components/vio-product-detail.ts
2056
2197
  var layoutAttribute = {
@@ -2070,20 +2211,6 @@ var ADDED_TOAST_MS = 8e3;
2070
2211
  var STRIPE_LOGO_SRC = "data:image/svg+xml;utf8," + encodeURIComponent(
2071
2212
  '<svg width="360" height="150" viewBox="0 0 360 150" fill="none" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" clip-rule="evenodd" d="M360 77.4001C360 51.8001 347.6 31.6001 323.9 31.6001C300.1 31.6001 285.7 51.8001 285.7 77.2001C285.7 107.3 302.7 122.5 327.1 122.5C339 122.5 348 119.8 354.8 116V96.0001C348 99.4001 340.2 101.5 330.3 101.5C320.6 101.5 312 98.1001 310.9 86.3001H359.8C359.8 85.0001 360 79.8001 360 77.4001ZM310.6 67.9001C310.6 56.6001 317.5 51.9001 323.8 51.9001C329.9 51.9001 336.4 56.6001 336.4 67.9001H310.6Z" fill="#533AFD"/><path fill-rule="evenodd" clip-rule="evenodd" d="M247.1 31.6001C237.3 31.6001 231 36.2001 227.5 39.4001L226.2 33.2001H204.2V149.8L229.2 144.5L229.3 116.2C232.9 118.8 238.2 122.5 247 122.5C264.9 122.5 281.2 108.1 281.2 76.4001C281.1 47.4001 264.6 31.6001 247.1 31.6001ZM241.1 100.5C235.2 100.5 231.7 98.4001 229.3 95.8001L229.2 58.7001C231.8 55.8001 235.4 53.8001 241.1 53.8001C250.2 53.8001 256.5 64.0001 256.5 77.1001C256.5 90.5001 250.3 100.5 241.1 100.5Z" fill="#533AFD"/><path fill-rule="evenodd" clip-rule="evenodd" d="M169.8 25.7L194.9 20.3V0L169.8 5.3V25.7Z" fill="#533AFD"/><path d="M194.9 33.3H169.8V120.8H194.9V33.3Z" fill="#533AFD"/><path fill-rule="evenodd" clip-rule="evenodd" d="M142.9 40.7L141.3 33.3H119.7V120.8H144.7V61.5C150.6 53.8 160.6 55.2 163.7 56.3V33.3C160.5 32.1 148.8 29.9 142.9 40.7Z" fill="#533AFD"/><path fill-rule="evenodd" clip-rule="evenodd" d="M92.8999 11.6001L68.4999 16.8001L68.3999 96.9001C68.3999 111.7 79.4999 122.6 94.2999 122.6C102.5 122.6 108.5 121.1 111.8 119.3V99.0001C108.6 100.3 92.7999 104.9 92.7999 90.1001V54.6001H111.8V33.3001H92.7999L92.8999 11.6001Z" fill="#533AFD"/><path fill-rule="evenodd" clip-rule="evenodd" d="M25.3 58.7001C25.3 54.8001 28.5 53.3001 33.8 53.3001C41.4 53.3001 51 55.6001 58.6 59.7001V36.2001C50.3 32.9001 42.1 31.6001 33.8 31.6001C13.5 31.6001 0 42.2001 0 59.9001C0 87.5001 38 83.1001 38 95.0001C38 99.6001 34 101.1 28.4 101.1C20.1 101.1 9.5 97.7001 1.1 93.1001V116.9C10.4 120.9 19.8 122.6 28.4 122.6C49.2 122.6 63.5 112.3 63.5 94.4001C63.4 64.6001 25.3 69.9001 25.3 58.7001Z" fill="#533AFD"/></svg>'
2072
2213
  );
2073
- var HIGHLIGHT_ICONS = {
2074
- plane: "M21.5 2.5 2.5 10l7 3m12-10.5-12 10.5m12-10.5L17 21l-7.5-8m0 0V20l3-4M5 17l-2.5 2.5M7.5 18.5 6 20",
2075
- heart: "M12 20.5s-8.5-5-8.5-11A4.75 4.75 0 0 1 12 6.75 4.75 4.75 0 0 1 20.5 9.5c0 6-8.5 11-8.5 11Z",
2076
- shirt: "M8.5 3.5 3 6l1.75 4.25L7 9.5v11h10v-11l2.25.75L21 6l-5.5-2.5a3.5 3.5 0 0 1-7 0Z",
2077
- truck: "M2.5 5.5h11v10h-11zM13.5 9.5h4l3 3.5v2.5h-7M6.5 18.5a2 2 0 1 0 0-.01M17 18.5a2 2 0 1 0 0-.01",
2078
- return: "M4 9h11.5a4.5 4.5 0 0 1 0 9H8M4 9l4-4M4 9l4 4",
2079
- leaf: "M5 19C5 10 10 4.5 20 4.5c0 10-5.5 15-14 15M5 19l7-7",
2080
- star: "m12 3.5 2.6 5.4 5.9.8-4.3 4.1 1 5.9L12 17l-5.2 2.7 1-5.9-4.3-4.1 5.9-.8Z",
2081
- shield: "M12 3 4.5 6v5.5c0 4.5 3.2 8.2 7.5 9.5 4.3-1.3 7.5-5 7.5-9.5V6Z",
2082
- mailbox: "M3 19v-8a4 4 0 0 1 8 0v8m-8 0h18v-7a4 4 0 0 0-4-4H7m8 0V4h3M6 12h2",
2083
- home: "M3 11 12 4l9 7M5.5 9.5V20h13V9.5M10 20v-5h4v5",
2084
- pin: "M12 21s-6.5-6-6.5-11a6.5 6.5 0 0 1 13 0c0 5-6.5 11-6.5 11Zm0-8.5a2.5 2.5 0 1 0 0-5 2.5 2.5 0 0 0 0 5Z",
2085
- clock: "M12 21a9 9 0 1 0 0-18 9 9 0 0 0 0 18Zm0-13.5V12l3 2"
2086
- };
2087
2214
  function rateIcon(method) {
2088
2215
  const m = method.toLowerCase();
2089
2216
  if (/postkasse|mailbox|brevkasse/.test(m)) return "mailbox";
@@ -2119,6 +2246,7 @@ var VioProductDetail = class extends LitElement {
2119
2246
  this.vippsCartPayment = "";
2120
2247
  this.gallery = "";
2121
2248
  this.descriptionMode = "";
2249
+ this.descriptionLabel = "";
2122
2250
  this.highlights = [];
2123
2251
  this.deliveryTime = "";
2124
2252
  this.carrierLogos = {};
@@ -2216,12 +2344,54 @@ var VioProductDetail = class extends LitElement {
2216
2344
  this.show();
2217
2345
  void this.fetchProduct();
2218
2346
  };
2347
+ /** Stops reporting the taps on the buttons (`vio:cta-click`). */
2348
+ this.stopCtaClicks = null;
2349
+ /** The product open when the shopper last changed photo: `gallery_swipe` once per open. */
2350
+ this.gallerySwipedFor = null;
2351
+ /** A finger, a pen or a wheel is on the carousel: its scroll is the shopper's swipe, not the SDK's. */
2352
+ this.galleryGesture = false;
2353
+ /**
2354
+ * What a tap on a button of the detail says (`vio:cta-click`, README
2355
+ * «Button tracking»): the open product and the variant chosen (when every
2356
+ * option is), its sponsor and attribution, and the layout shown (only a
2357
+ * non-default one). `gallery_swipe` only once per open product.
2358
+ */
2359
+ this.ctaContext = (cta) => {
2360
+ const product = this.product;
2361
+ if (cta === "gallery_swipe") {
2362
+ if (!product || this.gallerySwipedFor === product) return null;
2363
+ this.gallerySwipedFor = product;
2364
+ }
2365
+ const a = this.attribution();
2366
+ const variants = Array.isArray(product?.variants) ? product.variants : [];
2367
+ const variant = product && (variants.length === 1 || !this.missingOption()) ? this.selectedVariant() : null;
2368
+ const layout = layoutOriginOf(this.layoutNow);
2369
+ return {
2370
+ productId: product?.id ?? (this.productId || void 0),
2371
+ productVariantId: variant?.id,
2372
+ sponsorId: this.sponsorId > 0 ? this.sponsorId : void 0,
2373
+ campaignId: a.campaignId,
2374
+ slot: a.slot,
2375
+ contentId: a.contentId,
2376
+ contentTitle: a.contentTitle,
2377
+ componentTemplateId: a.componentTemplateId,
2378
+ layout: layout.layoutId ? layout : null
2379
+ };
2380
+ };
2381
+ /** The carousel scrolled under the shopper's finger: their first swipe of this product. */
2382
+ this.onGalleryGesture = () => {
2383
+ this.galleryGesture = true;
2384
+ };
2219
2385
  /** The cart changed (another tab, the drawer): the line under Vipps counts it again. */
2220
2386
  this.boundCartChange = () => {
2221
2387
  if (this.open) this.requestUpdate();
2222
2388
  };
2223
2389
  /** The photo in view is the active one (dots, Apple Pay sheet, events). */
2224
2390
  this.onCarouselScroll = () => {
2391
+ if (this.galleryGesture) {
2392
+ this.galleryGesture = false;
2393
+ emitCta(this, "detail", "gallery_swipe", this.ctaContext);
2394
+ }
2225
2395
  const track = this.carouselTrack();
2226
2396
  const step = this.carouselStep(track);
2227
2397
  if (!track || !step) return;
@@ -2307,7 +2477,8 @@ var VioProductDetail = class extends LitElement {
2307
2477
  };
2308
2478
  }
2309
2479
  static {
2310
- this.styles = css`
2480
+ this.styles = [
2481
+ css`
2311
2482
  :host { display: contents; }
2312
2483
 
2313
2484
  .backdrop {
@@ -2680,35 +2851,6 @@ var VioProductDetail = class extends LitElement {
2680
2851
  margin: 0;
2681
2852
  letter-spacing: -0.01em;
2682
2853
  }
2683
- /* The brand's promises: one row, an icon over a title and a line of text,
2684
- centred like a shop's own trust strip. */
2685
- .highlights {
2686
- list-style: none;
2687
- /* --vio-detail-highlights-display: none hides the strip (theme detailHighlightsDisplay). */
2688
- margin: 0;
2689
- padding: var(--vio-detail-highlights-padding, 16px 0 2px);
2690
- display: var(--vio-detail-highlights-display, grid);
2691
- grid-auto-flow: column;
2692
- grid-auto-columns: 1fr;
2693
- gap: 10px;
2694
- border-top: 1px solid var(--vio-color-border, #e5e5e5);
2695
- }
2696
- /* Like a shop's trust strip: a thin icon, the promise in regular weight,
2697
- a smaller line under it — nothing bold. */
2698
- .hl { display: flex; flex-direction: column; align-items: center; text-align: center; gap: 2px; min-width: 0; }
2699
- .hl-icon {
2700
- width: var(--vio-detail-highlight-icon-size, 22px);
2701
- height: var(--vio-detail-highlight-icon-size, 22px);
2702
- margin-bottom: 8px;
2703
- fill: none;
2704
- stroke: currentColor;
2705
- stroke-width: 1.2;
2706
- stroke-linecap: round;
2707
- stroke-linejoin: round;
2708
- object-fit: contain;
2709
- }
2710
- .hl-title { font-size: 12px; font-weight: 400; line-height: 1.3; letter-spacing: 0.01em; color: var(--vio-color-text, #0a0a0a); }
2711
- .hl-text { font-size: 10.5px; line-height: 1.35; letter-spacing: 0.01em; color: var(--vio-color-text-secondary, #666); }
2712
2854
  /* «Frakt & retur»: same fold as «Beskrivelse», with a small list of rates.
2713
2855
  --vio-detail-shipping-returns-display: none hides it (theme detailShippingReturnsDisplay). */
2714
2856
  .shipping-returns { display: var(--vio-detail-shipping-returns-display, block); }
@@ -3253,7 +3395,9 @@ var VioProductDetail = class extends LitElement {
3253
3395
  color: var(--vio-color-accent, #c14a3b);
3254
3396
  font-size: 14px;
3255
3397
  }
3256
- `;
3398
+ `,
3399
+ highlightsStyles("detail")
3400
+ ];
3257
3401
  }
3258
3402
  connectedCallback() {
3259
3403
  super.connectedCallback();
@@ -3263,12 +3407,16 @@ var VioProductDetail = class extends LitElement {
3263
3407
  this.slotRegistration.connect(this.slotAttributes());
3264
3408
  void this.loadVippsButton();
3265
3409
  document.addEventListener("vio:product-click", this.boundProductClick);
3410
+ this.stopCtaClicks?.();
3411
+ this.stopCtaClicks = trackCtaClicks(this, "detail", this.ctaContext);
3266
3412
  }
3267
3413
  disconnectedCallback() {
3268
3414
  if (this.addedToastTimer) clearTimeout(this.addedToastTimer);
3269
3415
  this.addedToastTimer = null;
3270
3416
  document.removeEventListener("vio:product-click", this.boundProductClick);
3271
3417
  Vio.cart.removeEventListener("change", this.boundCartChange);
3418
+ this.stopCtaClicks?.();
3419
+ this.stopCtaClicks = null;
3272
3420
  this.slotRegistration.clear();
3273
3421
  this.bottomSheet.disconnect();
3274
3422
  this.unsubscribeLayout?.();
@@ -3938,7 +4086,7 @@ var VioProductDetail = class extends LitElement {
3938
4086
  <div class="added-toast" part="added-toast" role="status" aria-live="polite">
3939
4087
  <div class="added-toast-text">Lagt i handlekurven</div>
3940
4088
  <div class="added-toast-actions">
3941
- <button class="added-toast-checkout" part="added-toast-checkout" @click=${this.toastToCheckout}>Til kassen</button>
4089
+ <button class="added-toast-checkout" part="added-toast-checkout" data-vio-cta="checkout" @click=${this.toastToCheckout}>Til kassen</button>
3942
4090
  <button class="added-toast-continue" part="added-toast-continue" @click=${this.hideAddedToast}>Fortsett å handle</button>
3943
4091
  </div>
3944
4092
  </div>
@@ -4201,7 +4349,14 @@ var VioProductDetail = class extends LitElement {
4201
4349
  const current2 = Math.min(this.activeImageIndex, Math.max(0, count - 1));
4202
4350
  return html`
4203
4351
  <div class="gallery carousel" part="gallery">
4204
- <div class="carousel-track" part="image" @scroll=${this.onCarouselScroll}>
4352
+ <div
4353
+ class="carousel-track"
4354
+ part="image"
4355
+ @scroll=${this.onCarouselScroll}
4356
+ @pointerdown=${this.onGalleryGesture}
4357
+ @touchstart=${this.onGalleryGesture}
4358
+ @wheel=${this.onGalleryGesture}
4359
+ >
4205
4360
  ${images.map(
4206
4361
  (img, idx) => html`
4207
4362
  <div class="carousel-slide" part="slide" aria-label=${`Bilde ${idx + 1} av ${count}`}>
@@ -4215,6 +4370,7 @@ var VioProductDetail = class extends LitElement {
4215
4370
  class="carousel-arrow prev"
4216
4371
  part="carousel-arrow"
4217
4372
  aria-label="Forrige bilde"
4373
+ data-vio-cta="gallery_swipe"
4218
4374
  ?disabled=${current2 === 0}
4219
4375
  @click=${() => this.stepCarousel(-1)}
4220
4376
  >‹</button>
@@ -4222,6 +4378,7 @@ var VioProductDetail = class extends LitElement {
4222
4378
  class="carousel-arrow next"
4223
4379
  part="carousel-arrow"
4224
4380
  aria-label="Neste bilde"
4381
+ data-vio-cta="gallery_swipe"
4225
4382
  ?disabled=${current2 === count - 1}
4226
4383
  @click=${() => this.stepCarousel(1)}
4227
4384
  >›</button>
@@ -4237,11 +4394,16 @@ var VioProductDetail = class extends LitElement {
4237
4394
  * the heading, the text or «Les mer» opens the rest (Angelo, 2026-10-28).
4238
4395
  * A description that fits in the preview has nothing to open.
4239
4396
  */
4397
+ /** The description fold's heading: this element's, else the page's, else «Beskrivelse». */
4398
+ descriptionHeading() {
4399
+ const own = this.descriptionLabel.trim();
4400
+ return (own && own.length <= 40 ? own : "") || currentGlobalDescriptionLabel() || "Beskrivelse";
4401
+ }
4240
4402
  renderDescription(text) {
4241
4403
  const mode = this.resolvedDescriptionMode;
4242
4404
  if (mode === "open") {
4243
4405
  return html`<div class="description-block description-preview open" part="description">
4244
- <div class="desc-head">Beskrivelse</div>
4406
+ <div class="desc-head">${this.descriptionHeading()}</div>
4245
4407
  <div class="description">${text}</div>
4246
4408
  </div>`;
4247
4409
  }
@@ -4250,10 +4412,16 @@ var VioProductDetail = class extends LitElement {
4250
4412
  const toggle = () => {
4251
4413
  if (foldable) this.descriptionOpen = !this.descriptionOpen;
4252
4414
  };
4415
+ const opens = foldable && !open ? "description_open" : nothing;
4253
4416
  return html`<div class="description-block description-preview ${open ? "open" : ""} ${foldable ? "clipped" : ""} ${mode}" part="description">
4254
- <button class="desc-head" type="button" aria-expanded=${open ? "true" : "false"} @click=${toggle}>Beskrivelse</button>
4255
- ${mode === "folded" && !open ? nothing : html`<div class="description ${open ? "" : "collapsed"}" @click=${() => !this.descriptionOpen && toggle()}>${text}</div>`}
4256
- ${mode === "preview" && this.descriptionClipped ? html`<button class="desc-more" type="button" @click=${toggle}>${this.descriptionOpen ? "Vis mindre" : "Les mer"}</button>` : nothing}
4417
+ <button class="desc-head" type="button" aria-expanded=${open ? "true" : "false"} data-vio-cta=${opens} @click=${toggle}>${this.descriptionHeading()}</button>
4418
+ ${mode === "folded" && !open ? nothing : html`<div
4419
+ class="description ${open ? "" : "collapsed"}"
4420
+ data-vio-cta=${opens}
4421
+ data-vio-cta-label=${opens === nothing ? nothing : this.descriptionHeading()}
4422
+ @click=${() => !this.descriptionOpen && toggle()}
4423
+ >${text}</div>`}
4424
+ ${mode === "preview" && this.descriptionClipped ? html`<button class="desc-more" type="button" data-vio-cta=${opens} @click=${toggle}>${this.descriptionOpen ? "Vis mindre" : "Les mer"}</button>` : nothing}
4257
4425
  </div>`;
4258
4426
  }
4259
4427
  /** Is the preview hiding part of the text? Measured with the preview applied. */
@@ -4268,19 +4436,7 @@ var VioProductDetail = class extends LitElement {
4268
4436
  }
4269
4437
  renderHighlights() {
4270
4438
  const own = (Array.isArray(this.highlights) ? this.highlights : []).filter((h) => h && h.title);
4271
- const items = own.length ? own : resolveSponsorIdentity(this.sponsorId)?.highlights ?? [];
4272
- if (items.length === 0) return nothing;
4273
- return html`<ul class="highlights" part="highlights">
4274
- ${items.map((h) => {
4275
- const path = h.icon ? HIGHLIGHT_ICONS[h.icon] : void 0;
4276
- const icon = path ? html`<svg class="hl-icon" viewBox="0 0 24 24" aria-hidden="true"><path d=${path}></path></svg>` : h.icon ? html`<img class="hl-icon" src=${h.icon} alt="" />` : nothing;
4277
- return html`<li class="hl" part="highlight">
4278
- ${icon}
4279
- <span class="hl-title">${h.title}</span>
4280
- ${h.text ? html`<span class="hl-text">${h.text}</span>` : nothing}
4281
- </li>`;
4282
- })}
4283
- </ul>`;
4439
+ return renderHighlights(own.length ? own : resolveSponsorIdentity(this.sponsorId)?.highlights ?? []);
4284
4440
  }
4285
4441
  /**
4286
4442
  * «Frakt & retur», folded like «Beskrivelse»: the delivery options for the
@@ -4299,11 +4455,11 @@ var VioProductDetail = class extends LitElement {
4299
4455
  const perRateTime = times.length > 1;
4300
4456
  const sharedTime = times.length === 1 ? times[0] : times.length === 0 && this.deliveryTime.trim() ? `Leveringstid: ${this.deliveryTime.trim()}` : "";
4301
4457
  const links = [
4302
- identity?.refundPolicyUrl ? html`<a href=${identity.refundPolicyUrl} target="_blank" rel="noopener noreferrer">Angrerett og retur</a>` : null,
4303
- identity?.termsUrl ? html`<a href=${identity.termsUrl} target="_blank" rel="noopener noreferrer">Kjøpsvilkår</a>` : null
4458
+ identity?.refundPolicyUrl ? html`<a href=${identity.refundPolicyUrl} target="_blank" rel="noopener noreferrer" data-vio-cta="refund_link">Angrerett og retur</a>` : null,
4459
+ identity?.termsUrl ? html`<a href=${identity.termsUrl} target="_blank" rel="noopener noreferrer" data-vio-cta="terms_link">Kjøpsvilkår</a>` : null
4304
4460
  ].filter(Boolean);
4305
4461
  return html`<details class="description-block shipping-returns" part="shipping-returns">
4306
- <summary>Frakt &amp; retur</summary>
4462
+ <summary data-vio-cta="shipping_returns_open">Frakt &amp; retur</summary>
4307
4463
  <div class="description shipping-returns-body">
4308
4464
  <div class="sr-title">Frakt</div>
4309
4465
  ${rates.length ? html`<ul class="sr-rates">
@@ -4386,8 +4542,8 @@ var VioProductDetail = class extends LitElement {
4386
4542
  const name = identity?.name || this.headerName();
4387
4543
  if (!name) return nothing;
4388
4544
  const links = [
4389
- identity?.termsUrl ? html`<a href=${identity.termsUrl} target="_blank" rel="noopener noreferrer" part="seller-terms">Kjøpsvilkår</a>` : null,
4390
- identity?.refundPolicyUrl ? html`<a href=${identity.refundPolicyUrl} target="_blank" rel="noopener noreferrer" part="seller-refund">Angrerett og retur</a>` : null
4545
+ identity?.termsUrl ? html`<a href=${identity.termsUrl} target="_blank" rel="noopener noreferrer" part="seller-terms" data-vio-cta="terms_link">Kjøpsvilkår</a>` : null,
4546
+ identity?.refundPolicyUrl ? html`<a href=${identity.refundPolicyUrl} target="_blank" rel="noopener noreferrer" part="seller-refund" data-vio-cta="refund_link">Angrerett og retur</a>` : null
4391
4547
  ].filter(Boolean);
4392
4548
  return html`<p class="seller-note" part="seller-note">
4393
4549
  Du handler direkte hos <strong>${name}</strong>.${links.length ? html`<span class="seller-links">${links}</span>` : nothing}
@@ -4411,12 +4567,12 @@ var VioProductDetail = class extends LitElement {
4411
4567
  }
4412
4568
  render() {
4413
4569
  return html`
4414
- <div class="backdrop" @click=${this.close}></div>
4570
+ <div class="backdrop" data-vio-cta="close" data-vio-cta-label="" @click=${this.close}></div>
4415
4571
  <div class="modal" role="dialog" aria-modal="true" aria-label="Produkt" tabindex="-1">
4416
- <div class="handle" @click=${this.close} aria-hidden="true"></div>
4572
+ <div class="handle" data-vio-cta="close" data-vio-cta-label="" @click=${this.close} aria-hidden="true"></div>
4417
4573
  <div class="topbar" part="topbar">
4418
4574
  <div class="topbar-sponsor ${this.logoSquare ? "is-square" : ""}" part="sponsor">${this.renderTopbarSponsor()}</div>
4419
- <button class="close" part="close" @click=${this.close} aria-label="Lukk">×</button>
4575
+ <button class="close" part="close" data-vio-cta="close" @click=${this.close} aria-label="Lukk">×</button>
4420
4576
  </div>
4421
4577
 
4422
4578
  ${this.renderBody()}
@@ -4466,6 +4622,7 @@ var VioProductDetail = class extends LitElement {
4466
4622
  class="gallery-thumb ${idx === this.activeImageIndex ? "active" : ""}"
4467
4623
  part=${idx === this.activeImageIndex ? "thumb thumb-active" : "thumb"}
4468
4624
  @click=${() => this.activeImageIndex = idx}
4625
+ data-vio-cta=${idx === this.activeImageIndex ? nothing : "gallery_swipe"}
4469
4626
  aria-label=${`Bilde ${idx + 1}`}
4470
4627
  aria-current=${idx === this.activeImageIndex ? "true" : "false"}
4471
4628
  >
@@ -4604,12 +4761,14 @@ var VioProductDetail = class extends LitElement {
4604
4761
  stretched="true"
4605
4762
  role="button"
4606
4763
  aria-label="Kjøp nå med Vipps"
4764
+ data-vio-cta="vipps"
4607
4765
  @click=${this.buyWithVipps}
4608
4766
  ></vipps-mobilepay-button>
4609
4767
  ` : html`
4610
4768
  <button
4611
4769
  class="buy-vipps"
4612
4770
  part="buy-vipps"
4771
+ data-vio-cta="vipps"
4613
4772
  @click=${this.buyWithVipps}
4614
4773
  ?disabled=${this.adding}
4615
4774
  aria-label="Kjøp nå med Vipps"
@@ -4624,6 +4783,7 @@ var VioProductDetail = class extends LitElement {
4624
4783
  <div class="qty-stepper">
4625
4784
  <button
4626
4785
  class="qty-btn"
4786
+ data-vio-cta="quantity_minus"
4627
4787
  @click=${() => this.quantity = Math.max(1, this.quantity - 1)}
4628
4788
  aria-label="Reduser antall"
4629
4789
  >
@@ -4632,6 +4792,7 @@ var VioProductDetail = class extends LitElement {
4632
4792
  <span class="qty-value">${this.quantity}</span>
4633
4793
  <button
4634
4794
  class="qty-btn"
4795
+ data-vio-cta="quantity_plus"
4635
4796
  @click=${() => this.quantity = Math.min(this.availableQuantity, this.quantity + 1)}
4636
4797
  aria-label="Øk antall"
4637
4798
  ?disabled=${this.quantity >= this.availableQuantity}
@@ -4642,6 +4803,7 @@ var VioProductDetail = class extends LitElement {
4642
4803
  <button
4643
4804
  class="add-to-cart"
4644
4805
  part="add-to-cart"
4806
+ data-vio-cta="add_to_cart"
4645
4807
  @click=${this.addToCart}
4646
4808
  ?disabled=${this.availableQuantity <= 0 || this.adding}
4647
4809
  >
@@ -4652,6 +4814,7 @@ var VioProductDetail = class extends LitElement {
4652
4814
  ${this.availableQuantity > 0 && this.methodEnabled(...NO_EXPRESS_BUTTON_METHODS) ? html`
4653
4815
  <button
4654
4816
  class="buy-now"
4817
+ data-vio-cta="buy_now"
4655
4818
  @click=${this.buyNow}
4656
4819
  ?disabled=${this.adding}
4657
4820
  aria-label="Kjøp nå"
@@ -4663,6 +4826,7 @@ var VioProductDetail = class extends LitElement {
4663
4826
  ${this.availableQuantity > 0 && this.hasPrice && this.applePayOk && this.methodEnabled("apple-pay", "applepay") ? html`
4664
4827
  <button
4665
4828
  class="buy-applepay"
4829
+ data-vio-cta="apple_pay"
4666
4830
  @click=${this.buyWithApplePay}
4667
4831
  ?disabled=${this.adding}
4668
4832
  aria-label="Kjøp nå med Apple Pay"
@@ -4675,6 +4839,7 @@ var VioProductDetail = class extends LitElement {
4675
4839
  ${this.availableQuantity > 0 && this.methodEnabled("stripe") ? html`
4676
4840
  <button
4677
4841
  class="buy-stripe"
4842
+ data-vio-cta="stripe"
4678
4843
  @click=${this.buyWithStripe}
4679
4844
  ?disabled=${this.adding}
4680
4845
  aria-label="Kjøp nå med Stripe"
@@ -4691,6 +4856,8 @@ var VioProductDetail = class extends LitElement {
4691
4856
  ${this.availableQuantity > 0 && this.methodEnabled("klarna") ? html`
4692
4857
  <button
4693
4858
  class="buy-klarna"
4859
+ data-vio-cta="klarna"
4860
+ data-vio-cta-label="Kjøp nå med Klarna"
4694
4861
  @click=${this.buyWithKlarna}
4695
4862
  ?disabled=${this.adding}
4696
4863
  >
@@ -4733,6 +4900,8 @@ var VioProductDetail = class extends LitElement {
4733
4900
  <button
4734
4901
  class="option-pill ${available ? "" : "unavailable"}"
4735
4902
  part=${isSelected ? "option option-selected" : "option"}
4903
+ data-vio-cta="size"
4904
+ data-vio-cta-option=${val}
4736
4905
  @click=${() => this.selectOption(opt.name, val)}
4737
4906
  aria-pressed=${isSelected}
4738
4907
  ?disabled=${!available}
@@ -4748,8 +4917,8 @@ var VioProductDetail = class extends LitElement {
4748
4917
  })}`;
4749
4918
  }
4750
4919
  /** Best-effort HTML strip for description. */
4751
- stripHtml(html7) {
4752
- return html7.replace(/<br\s*\/?>/gi, "\n").replace(/<\/p>/gi, "\n\n").replace(/<[^>]+>/g, "").replace(/&nbsp;/g, " ").replace(/&amp;/g, "&").replace(/&lt;/g, "<").replace(/&gt;/g, ">").trim();
4920
+ stripHtml(html8) {
4921
+ return html8.replace(/<br\s*\/?>/gi, "\n").replace(/<\/p>/gi, "\n\n").replace(/<[^>]+>/g, "").replace(/&nbsp;/g, " ").replace(/&amp;/g, "&").replace(/&lt;/g, "<").replace(/&gt;/g, ">").trim();
4753
4922
  }
4754
4923
  };
4755
4924
  __decorateClass([
@@ -4800,6 +4969,9 @@ __decorateClass([
4800
4969
  __decorateClass([
4801
4970
  property({ type: String, attribute: "description-mode" })
4802
4971
  ], VioProductDetail.prototype, "descriptionMode", 2);
4972
+ __decorateClass([
4973
+ property({ type: String, attribute: "description-label" })
4974
+ ], VioProductDetail.prototype, "descriptionLabel", 2);
4803
4975
  __decorateClass([
4804
4976
  property({ type: Array })
4805
4977
  ], VioProductDetail.prototype, "highlights", 2);
@@ -4931,6 +5103,12 @@ function cartShipping(items, currency) {
4931
5103
  }
4932
5104
  return null;
4933
5105
  }
5106
+ function cartShippingIsFrom(items, currency) {
5107
+ if (items.some((item) => ratePrice(item.shipping, currency) !== null)) return false;
5108
+ return items.some(
5109
+ (item) => (item.availableShippings ?? []).filter((r) => ratePrice(r, currency) !== null).length > 1
5110
+ );
5111
+ }
4934
5112
  var VioCart = class extends LitElement {
4935
5113
  constructor() {
4936
5114
  super(...arguments);
@@ -4980,9 +5158,39 @@ var VioCart = class extends LitElement {
4980
5158
  void this.loadAvailablePaymentMethods();
4981
5159
  }
4982
5160
  };
5161
+ /** Stops reporting the taps on the buttons (`vio:cta-click`). */
5162
+ this.stopCtaClicks = null;
5163
+ /**
5164
+ * What a tap on a button of the cart says (`vio:cta-click`): a line's
5165
+ * buttons name its product, variant and sponsor, and where its most recent
5166
+ * units came from (campaign, slot, article); the footer's buttons name the
5167
+ * sponsor they pay (Vipps: the one it pays; the others: the first).
5168
+ */
5169
+ this.ctaContext = (_cta, button) => {
5170
+ const lineId = button?.getAttribute("data-vio-cta-line");
5171
+ if (lineId) {
5172
+ for (const cart of this.carts.values()) {
5173
+ const item = cart.items.find((i) => i.id === lineId);
5174
+ if (!item) continue;
5175
+ const origin = [...lineOrigins(item)].reverse().find((o) => o.campaignId !== void 0 || o.slot || o.contentId);
5176
+ return {
5177
+ productId: item.productId,
5178
+ productVariantId: item.variantId,
5179
+ sponsorId: item.sponsorId,
5180
+ campaignId: origin?.campaignId,
5181
+ slot: origin?.slot,
5182
+ contentId: origin?.contentId
5183
+ };
5184
+ }
5185
+ return {};
5186
+ }
5187
+ const sponsorId = _cta === "vipps" ? this.vippsSponsorId() : [...this.carts.keys()][0];
5188
+ return { sponsorId };
5189
+ };
4983
5190
  }
4984
5191
  static {
4985
- this.styles = css`
5192
+ this.styles = [
5193
+ css`
4986
5194
  :host { display: contents; }
4987
5195
 
4988
5196
  .backdrop {
@@ -5201,6 +5409,12 @@ var VioCart = class extends LitElement {
5201
5409
  font-size: 14px;
5202
5410
  }
5203
5411
  .total-row { font-weight: 700; font-size: 16px; margin-top: 0; }
5412
+ /* Where the shipping method is chosen: in Vipps, or in the checkout. */
5413
+ .shipping-hint {
5414
+ margin: calc(var(--vio-space-md, 16px) * -0.5) 0 var(--vio-space-md, 16px);
5415
+ font-size: 12px;
5416
+ color: var(--vio-color-text-secondary, #666);
5417
+ }
5204
5418
  .subtotal-value { font-weight: 700; font-size: 16px; }
5205
5419
 
5206
5420
  /* Apple Pay express — pay the whole cart directly. */
@@ -5358,7 +5572,9 @@ var VioCart = class extends LitElement {
5358
5572
  font-family: var(--vio-font-sans, -apple-system, sans-serif);
5359
5573
  }
5360
5574
  .confirm-close:hover { background: #222; }
5361
- `;
5575
+ `,
5576
+ highlightsStyles("cart")
5577
+ ];
5362
5578
  }
5363
5579
  connectedCallback() {
5364
5580
  super.connectedCallback();
@@ -5367,8 +5583,12 @@ var VioCart = class extends LitElement {
5367
5583
  this.itemCount = Vio.cart.itemCount;
5368
5584
  Vio.cart.addEventListener("change", this.boundOnCartChange);
5369
5585
  void this.loadAvailablePaymentMethods();
5586
+ this.stopCtaClicks?.();
5587
+ this.stopCtaClicks = trackCtaClicks(this, "cart", this.ctaContext);
5370
5588
  }
5371
5589
  disconnectedCallback() {
5590
+ this.stopCtaClicks?.();
5591
+ this.stopCtaClicks = null;
5372
5592
  Vio.cart.removeEventListener("change", this.boundOnCartChange);
5373
5593
  this.bottomSheet.disconnect();
5374
5594
  super.disconnectedCallback();
@@ -5683,12 +5903,12 @@ var VioCart = class extends LitElement {
5683
5903
  const isEmpty = this.itemCount === 0;
5684
5904
  const sponsorCarts = [...this.carts.values()].filter((c) => c.items.length > 0);
5685
5905
  return html`
5686
- <div class="backdrop" @click=${this.close}></div>
5906
+ <div class="backdrop" data-vio-cta="close" data-vio-cta-label="" @click=${this.close}></div>
5687
5907
  <aside class="drawer" role="dialog" aria-modal="true" aria-label=${this.heading} tabindex="-1">
5688
- <div class="handle" @click=${this.close} aria-hidden="true"></div>
5908
+ <div class="handle" data-vio-cta="close" data-vio-cta-label="" @click=${this.close} aria-hidden="true"></div>
5689
5909
  <div class="header">
5690
5910
  <h2 class="heading">${this.confirmedOrder ? "Takk!" : this.heading}</h2>
5691
- <button class="close" @click=${this.close} aria-label="Lukk">×</button>
5911
+ <button class="close" data-vio-cta="close" @click=${this.close} aria-label="Lukk">×</button>
5692
5912
  </div>
5693
5913
 
5694
5914
  ${this.confirmedOrder ? this.renderConfirmation() : isEmpty ? html`<div class="empty">${this.emptyLabel}</div>` : html`
@@ -5710,6 +5930,8 @@ var VioCart = class extends LitElement {
5710
5930
  <span class="item-qty">
5711
5931
  <button
5712
5932
  class="qty-btn"
5933
+ data-vio-cta="quantity_minus"
5934
+ data-vio-cta-line=${item.id}
5713
5935
  @click=${() => this.onDecrement(item)}
5714
5936
  aria-label="Reduser"
5715
5937
  >
@@ -5718,6 +5940,8 @@ var VioCart = class extends LitElement {
5718
5940
  ${item.quantity}
5719
5941
  <button
5720
5942
  class="qty-btn"
5943
+ data-vio-cta="quantity_plus"
5944
+ data-vio-cta-line=${item.id}
5721
5945
  @click=${() => this.onIncrement(item)}
5722
5946
  aria-label="Øk"
5723
5947
  >
@@ -5725,6 +5949,8 @@ var VioCart = class extends LitElement {
5725
5949
  </button>
5726
5950
  <button
5727
5951
  class="qty-btn"
5952
+ data-vio-cta="remove"
5953
+ data-vio-cta-line=${item.id}
5728
5954
  @click=${() => this.onRemove(item)}
5729
5955
  aria-label="Fjern"
5730
5956
  style="margin-left: 8px;"
@@ -5757,6 +5983,7 @@ var VioCart = class extends LitElement {
5757
5983
  ${this.renderShippingRows()}
5758
5984
  <button
5759
5985
  class="checkout-btn"
5986
+ data-vio-cta="checkout"
5760
5987
  @click=${this.onCheckout}
5761
5988
  ?disabled=${!this.payable}
5762
5989
  aria-label="Til kassen"
@@ -5767,6 +5994,7 @@ var VioCart = class extends LitElement {
5767
5994
  ${this.methodEnabled("apple-pay") && this.applePayAvailable ? html`
5768
5995
  <button
5769
5996
  class="applepay-btn"
5997
+ data-vio-cta="apple_pay"
5770
5998
  @click=${this.onApplePay}
5771
5999
  aria-label="Betal med Apple Pay"
5772
6000
  >
@@ -5777,6 +6005,7 @@ var VioCart = class extends LitElement {
5777
6005
  ${this.methodEnabled("stripe") ? html`
5778
6006
  <button
5779
6007
  class="stripe-btn"
6008
+ data-vio-cta="stripe"
5780
6009
  @click=${this.onStripe}
5781
6010
  aria-label="Betal med Stripe"
5782
6011
  >
@@ -5786,6 +6015,7 @@ var VioCart = class extends LitElement {
5786
6015
  ${this.methodEnabled("klarna") ? html`
5787
6016
  <button
5788
6017
  class="klarna-btn"
6018
+ data-vio-cta="klarna"
5789
6019
  @click=${this.onKlarnaExpress}
5790
6020
  aria-label="Betal med Klarna"
5791
6021
  >
@@ -5808,34 +6038,52 @@ var VioCart = class extends LitElement {
5808
6038
  stretched="true"
5809
6039
  role="button"
5810
6040
  aria-label="Betal med Vipps"
6041
+ data-vio-cta="vipps"
5811
6042
  @click=${this.onVipps}
5812
6043
  ></vipps-mobilepay-button>
5813
6044
  ` : html`
5814
6045
  <button
5815
6046
  class="vipps-btn"
6047
+ data-vio-cta="vipps"
5816
6048
  @click=${this.onVipps}
5817
6049
  aria-label="Betal med Vipps"
5818
6050
  >
5819
6051
  <span class="vipps-logo">vipps</span>
5820
6052
  </button>
5821
6053
  ` : ""}
6054
+ ${this.renderBrandHighlights()}
5822
6055
  </div>
5823
6056
  `}
5824
6057
  </aside>
5825
6058
  `;
5826
6059
  }
6060
+ /**
6061
+ * The selling brand's promises under the ways to pay, as in the product
6062
+ * detail (one article = one brand: the cart's brand). Hidden with the theme's
6063
+ * `cartHighlightsDisplay: 'none'`; nothing when the brand has none.
6064
+ */
6065
+ renderBrandHighlights() {
6066
+ const sponsorId = [...this.carts.keys()].find((id) => (this.carts.get(id)?.items.length ?? 0) > 0);
6067
+ if (sponsorId === void 0) return "";
6068
+ return renderHighlights(resolveSponsorIdentity(sponsorId)?.highlights ?? []);
6069
+ }
5827
6070
  /** «Frakt» (the chosen or cheapest rate) and «Totalt» with it; «Beregnes i kassen» when unknown. */
5828
6071
  renderShippingRows() {
5829
6072
  const shipping = this.shippingAcrossSponsors;
6073
+ const from = shipping !== null && shipping > 0 && [...this.carts.values()].some(
6074
+ (cart) => cart.items.length > 0 && cartShippingIsFrom(cart.items, cart.currency)
6075
+ );
6076
+ const fra = from ? "fra " : "";
5830
6077
  return html`
5831
6078
  <div class="shipping-row" part="cart-shipping">
5832
6079
  <span class="subtotal-label">Frakt</span>
5833
- <span>${shipping === null ? "Beregnes i kassen" : shipping <= 0 ? "Gratis" : formatPrice(shipping, this.primaryCurrency)}</span>
6080
+ <span>${shipping === null ? "Beregnes i kassen" : shipping <= 0 ? "Gratis" : `${fra}${formatPrice(shipping, this.primaryCurrency)}`}</span>
5834
6081
  </div>
5835
6082
  ${shipping !== null ? html`<div class="total-row" part="cart-total">
5836
6083
  <span>Totalt</span>
5837
- <span>${formatPrice(this.totalAcrossSponsors + shipping, this.primaryCurrency)}</span>
6084
+ <span>${fra}${formatPrice(this.totalAcrossSponsors + shipping, this.primaryCurrency)}</span>
5838
6085
  </div>` : ""}
6086
+ <p class="shipping-hint" part="cart-shipping-hint">Frakt velger du i Vipps eller i neste steg.</p>
5839
6087
  `;
5840
6088
  }
5841
6089
  renderConfirmation() {
@@ -5860,7 +6108,7 @@ var VioCart = class extends LitElement {
5860
6108
  <span>${formatPrice(o.total, o.currency)}</span>
5861
6109
  </div>
5862
6110
  </div>
5863
- <button class="confirm-close" @click=${this.closeConfirmation}>Lukk</button>
6111
+ <button class="confirm-close" data-vio-cta="close" @click=${this.closeConfirmation}>Lukk</button>
5864
6112
  </div>
5865
6113
  `;
5866
6114
  }
@@ -6155,6 +6403,12 @@ var _VioCheckout = class _VioCheckout extends LitElement {
6155
6403
  this.paymentError = detail.method === "klarna" ? `Klarna-betalingen kunne ikke fullf\xF8res: ${detail.error}` : friendlyPaymentError(detail.error, detail.method);
6156
6404
  this.open = true;
6157
6405
  };
6406
+ /** Stops reporting the taps on the buttons (`vio:cta-click`). */
6407
+ this.stopCtaClicks = null;
6408
+ /** What a tap on a button of the checkout says (`vio:cta-click`): the sponsor whose cart it pays. */
6409
+ this.ctaContext = () => ({
6410
+ sponsorId: this.checkoutState?.sponsorId ?? this.orderSponsor?.sponsorId
6411
+ });
6158
6412
  /** Payment events still waiting for their buyer reference: settled early (without it) when the page hides or the element leaves. */
6159
6413
  this.pendingSuccessEmits = /* @__PURE__ */ new Set();
6160
6414
  }
@@ -6786,6 +7040,8 @@ var _VioCheckout = class _VioCheckout extends LitElement {
6786
7040
  void Vio.checkout.resumeKlarnaReturn();
6787
7041
  void this.checkReturnPaymentStatus();
6788
7042
  this.requestUpdate();
7043
+ this.stopCtaClicks?.();
7044
+ this.stopCtaClicks = trackCtaClicks(this, "checkout", this.ctaContext);
6789
7045
  }
6790
7046
  static {
6791
7047
  /**
@@ -7238,6 +7494,8 @@ var _VioCheckout = class _VioCheckout extends LitElement {
7238
7494
  }
7239
7495
  }
7240
7496
  disconnectedCallback() {
7497
+ this.stopCtaClicks?.();
7498
+ this.stopCtaClicks = null;
7241
7499
  Vio.checkout.removeEventListener("change", this.boundOnCheckoutChange);
7242
7500
  Vio.checkout.removeEventListener("payment-complete", this.boundOnPaymentComplete);
7243
7501
  Vio.checkout.removeEventListener("payment-error", this.boundOnPaymentError);
@@ -8058,7 +8316,7 @@ var _VioCheckout = class _VioCheckout extends LitElement {
8058
8316
  }
8059
8317
  render() {
8060
8318
  return html`
8061
- <div class="backdrop" @click=${this.close}></div>
8319
+ <div class="backdrop" data-vio-cta="close" data-vio-cta-label="" @click=${this.close}></div>
8062
8320
  <div
8063
8321
  class="modal ${this.express ? "as-side" : this.orderConfirmed ? "as-confirm" : ""}"
8064
8322
  role="dialog"
@@ -8066,13 +8324,13 @@ var _VioCheckout = class _VioCheckout extends LitElement {
8066
8324
  aria-label=${this.heading}
8067
8325
  tabindex="-1"
8068
8326
  >
8069
- <div class="handle" @click=${this.close} aria-hidden="true"></div>
8327
+ <div class="handle" data-vio-cta="close" data-vio-cta-label="" @click=${this.close} aria-hidden="true"></div>
8070
8328
  <div class="topbar">
8071
8329
  <div class="topbar-title">
8072
8330
  <h2 class="heading">${this.orderConfirmed ? "Takk!" : this.heading}</h2>
8073
8331
  ${this.renderTopbarSponsor()}
8074
8332
  </div>
8075
- <button class="close" @click=${this.close} aria-label="Lukk">×</button>
8333
+ <button class="close" data-vio-cta="close" @click=${this.close} aria-label="Lukk">×</button>
8076
8334
  </div>
8077
8335
 
8078
8336
  <div class="body">
@@ -8169,7 +8427,7 @@ var _VioCheckout = class _VioCheckout extends LitElement {
8169
8427
  return html`
8170
8428
  <section class="section provider-receipt">
8171
8429
  <slot name=${slot}></slot>
8172
- <button class="confirm-close" @click=${this.close}>Lukk</button>
8430
+ <button class="confirm-close" data-vio-cta="close" @click=${this.close}>Lukk</button>
8173
8431
  </section>
8174
8432
  `;
8175
8433
  }
@@ -8265,7 +8523,7 @@ var _VioCheckout = class _VioCheckout extends LitElement {
8265
8523
  </div>
8266
8524
  </div>
8267
8525
  ` : ""}
8268
- <button class="confirm-close" @click=${this.close}>Fortsett å handle</button>
8526
+ <button class="confirm-close" data-vio-cta="close" @click=${this.close}>Fortsett å handle</button>
8269
8527
  </section>
8270
8528
  `;
8271
8529
  }
@@ -8851,6 +9109,7 @@ var _VioCheckout = class _VioCheckout extends LitElement {
8851
9109
  class="payment-btn primary complete-cta"
8852
9110
  ?disabled=${!ready}
8853
9111
  title=${!this.isAddressValid ? "Vennligst fyll ut leveringsadresse" : !this.hasSelectedShipping ? "Vennligst velg en fraktmetode" : ""}
9112
+ data-vio-cta="pay_adyen"
8854
9113
  @click=${() => {
8855
9114
  this.adyenRequested = true;
8856
9115
  }}
@@ -9021,6 +9280,7 @@ var _VioCheckout = class _VioCheckout extends LitElement {
9021
9280
  class="payment-btn primary complete-cta"
9022
9281
  ?disabled=${!ready}
9023
9282
  title=${!this.isAddressValid ? "Vennligst fyll ut leveringsadresse" : !this.hasSelectedShipping ? "Vennligst velg en fraktmetode" : ""}
9283
+ data-vio-cta="pay_stripe"
9024
9284
  @click=${() => {
9025
9285
  this.stripeRequested = true;
9026
9286
  }}
@@ -9033,6 +9293,7 @@ var _VioCheckout = class _VioCheckout extends LitElement {
9033
9293
  <button
9034
9294
  class="payment-btn primary complete-cta"
9035
9295
  ?disabled=${this.stripePaying || !this.stripeReady}
9296
+ data-vio-cta="pay_stripe_confirm"
9036
9297
  @click=${() => void this.onStripePay()}
9037
9298
  >
9038
9299
  ${this.stripePaying ? "Betaler\u2026" : `Betal ${this.payTotalLabel()}`}
@@ -9099,6 +9360,7 @@ var _VioCheckout = class _VioCheckout extends LitElement {
9099
9360
  <div class="klarna-widget" id="vio-klarna-payments-container"></div>
9100
9361
  <button
9101
9362
  class="payment-btn primary complete-cta"
9363
+ data-vio-cta="pay_klarna"
9102
9364
  @click=${() => void this.onKlarnaAuthorize()}
9103
9365
  ?disabled=${this.klarnaAuthorizing || !this.klarnaHandle || this.klarnaMounting || !this.express && (!this.isAddressValid || !this.hasSelectedShipping)}
9104
9366
  >
@@ -9315,6 +9577,7 @@ var _VioCheckout = class _VioCheckout extends LitElement {
9315
9577
  ${method === "stripe" && this.stripeMode !== "native" ? html`
9316
9578
  <button
9317
9579
  class="payment-btn primary complete-cta"
9580
+ data-vio-cta="pay_stripe"
9318
9581
  @click=${() => void this.onPay("stripe")}
9319
9582
  ?disabled=${this.stripeLoading || !this.cartPayable || !this.isAddressValid || !this.hasSelectedShipping}
9320
9583
  title=${!this.isAddressValid ? "Vennligst fyll ut leveringsadresse" : !this.hasSelectedShipping ? "Vennligst velg en fraktmetode" : ""}
@@ -9325,6 +9588,7 @@ var _VioCheckout = class _VioCheckout extends LitElement {
9325
9588
  ${method === "vipps" ? html`
9326
9589
  <button
9327
9590
  class="payment-btn primary complete-cta"
9591
+ data-vio-cta="pay_vipps"
9328
9592
  @click=${() => void this.onPay("vipps")}
9329
9593
  ?disabled=${this.vippsLoading || !this.cartPayable || !this.isAddressValid || !this.hasSelectedShipping}
9330
9594
  title=${!this.isAddressValid ? "Vennligst fyll ut leveringsadresse" : !this.hasSelectedShipping ? "Vennligst velg en fraktmetode" : ""}
@@ -9335,6 +9599,7 @@ var _VioCheckout = class _VioCheckout extends LitElement {
9335
9599
  ${method === "apple-pay" ? html`
9336
9600
  <button
9337
9601
  class="payment-btn primary complete-cta"
9602
+ data-vio-cta="pay_apple_pay"
9338
9603
  @click=${() => void this.onPay("apple-pay")}
9339
9604
  ?disabled=${this.applePayInProgress || !this.cartPayable || !this.isAddressValid || !this.hasSelectedShipping}
9340
9605
  title=${!this.isAddressValid ? "Vennligst fyll ut leveringsadresse" : !this.hasSelectedShipping ? "Vennligst velg en fraktmetode" : ""}
@@ -9345,6 +9610,7 @@ var _VioCheckout = class _VioCheckout extends LitElement {
9345
9610
  ${showCompleteCta && method !== "stripe" && method !== "vipps" ? html`
9346
9611
  <button
9347
9612
  class="payment-btn primary complete-cta"
9613
+ data-vio-cta=${`pay_${ctaMethod(method)}`}
9348
9614
  @click=${this.onCompleteOrder}
9349
9615
  ?disabled=${!this.cartPayable || !this.isAddressValid || !this.hasSelectedShipping}
9350
9616
  title=${!this.isAddressValid ? "Vennligst fyll ut leveringsadresse" : !this.hasSelectedShipping ? "Vennligst velg en fraktmetode" : ""}
@@ -9373,6 +9639,8 @@ var _VioCheckout = class _VioCheckout extends LitElement {
9373
9639
  ${this.methodEnabled("apple-pay") && this.applePayAvailable && !this.stripeOwnsWallets() ? html`
9374
9640
  <button
9375
9641
  class="payment-btn primary"
9642
+ data-vio-cta="select_apple_pay"
9643
+ data-vio-cta-label="Apple Pay"
9376
9644
  @click=${() => this.onPay("apple-pay")}
9377
9645
  ?disabled=${this.applePayInProgress}
9378
9646
  >
@@ -9382,6 +9650,8 @@ var _VioCheckout = class _VioCheckout extends LitElement {
9382
9650
  ${this.methodEnabled("stripe") ? html`
9383
9651
  <button
9384
9652
  class="payment-btn"
9653
+ data-vio-cta="select_stripe"
9654
+ data-vio-cta-label="Stripe"
9385
9655
  @click=${() => this.onPay("stripe")}
9386
9656
  ?disabled=${this.stripeLoading}
9387
9657
  >
@@ -9389,37 +9659,37 @@ var _VioCheckout = class _VioCheckout extends LitElement {
9389
9659
  </button>
9390
9660
  ` : ""}
9391
9661
  ${this.methodEnabled("klarna") && this.klarnaAvailable ? html`
9392
- <button class="payment-btn" @click=${() => this.onPay("klarna")}>
9662
+ <button class="payment-btn" data-vio-cta="select_klarna" data-vio-cta-label="Klarna" @click=${() => this.onPay("klarna")}>
9393
9663
  Klarna
9394
9664
  </button>
9395
9665
  ` : ""}
9396
9666
  ${this.methodEnabled("kustom") ? html`
9397
- <button class="payment-btn" @click=${() => this.onPay("kustom")}>
9667
+ <button class="payment-btn" data-vio-cta="select_kustom" data-vio-cta-label="Kustom" @click=${() => this.onPay("kustom")}>
9398
9668
  <span style="font-weight:800;font-size:16px;letter-spacing:-0.01em">Kustom</span>
9399
9669
  </button>
9400
9670
  ` : ""}
9401
9671
  ${this.methodEnabled("qliro") ? html`
9402
- <button class="payment-btn" @click=${() => this.onPay("qliro")}>
9672
+ <button class="payment-btn" data-vio-cta="select_qliro" data-vio-cta-label="Qliro" @click=${() => this.onPay("qliro")}>
9403
9673
  <span style="font-weight:800;font-size:16px;letter-spacing:-0.01em">Qliro</span>
9404
9674
  </button>
9405
9675
  ` : ""}
9406
9676
  ${this.methodEnabled("walley") ? html`
9407
- <button class="payment-btn" @click=${() => this.onPay("walley")}>
9677
+ <button class="payment-btn" data-vio-cta="select_walley" data-vio-cta-label="Walley" @click=${() => this.onPay("walley")}>
9408
9678
  <span style="font-weight:800;font-size:16px;letter-spacing:-0.01em">Walley</span>
9409
9679
  </button>
9410
9680
  ` : ""}
9411
9681
  ${this.methodEnabled("nexi") ? html`
9412
- <button class="payment-btn" @click=${() => this.onPay("nexi")}>
9682
+ <button class="payment-btn" data-vio-cta="select_nexi" data-vio-cta-label="Nexi" @click=${() => this.onPay("nexi")}>
9413
9683
  <span style="font-weight:800;font-size:16px;letter-spacing:-0.01em">Nexi</span>
9414
9684
  </button>
9415
9685
  ` : ""}
9416
9686
  ${this.methodEnabled("adyen") ? html`
9417
- <button class="payment-btn" @click=${() => this.onPay("adyen")}>
9687
+ <button class="payment-btn" data-vio-cta="select_adyen" @click=${() => this.onPay("adyen")}>
9418
9688
  <span style="font-weight:700;font-size:15px;letter-spacing:-0.01em">${this.methodLabel("adyen")}</span>
9419
9689
  </button>
9420
9690
  ` : ""}
9421
9691
  ${this.methodEnabled("vipps") ? html`
9422
- <button class="payment-btn" @click=${() => this.onPay("vipps")} aria-label="Vipps">
9692
+ <button class="payment-btn" data-vio-cta="select_vipps" @click=${() => this.onPay("vipps")} aria-label="Vipps">
9423
9693
  ${this.vippsOfficial ? html`<vipps-mobilepay-button
9424
9694
  brand="vipps"
9425
9695
  variant="primary"
@@ -9637,6 +9907,9 @@ __decorateClass([
9637
9907
  state()
9638
9908
  ], _VioCheckout.prototype, "shippingsReadyFor", 2);
9639
9909
  var VioCheckout = _VioCheckout;
9910
+ function ctaMethod(method) {
9911
+ return String(method ?? "unknown").toLowerCase().replace(/[^a-z0-9]+/g, "_").replace(/^_+|_+$/g, "") || "unknown";
9912
+ }
9640
9913
 
9641
9914
  // src/ui/elements.ts
9642
9915
  function registerVioElements() {
@@ -9655,5 +9928,5 @@ function registerVioElements() {
9655
9928
  }
9656
9929
 
9657
9930
  export { VioCart, VioCheckout, VioProduct, VioProductCarousel, VioProductDetail, VioTokens, applyVioTheme, registerVioElements };
9658
- //# sourceMappingURL=chunk-F7LE5AZY.js.map
9659
- //# sourceMappingURL=chunk-F7LE5AZY.js.map
9931
+ //# sourceMappingURL=chunk-LDA7RI43.js.map
9932
+ //# sourceMappingURL=chunk-LDA7RI43.js.map