@kotao/storefront 0.2.1 → 0.2.2

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/react.js CHANGED
@@ -437,6 +437,12 @@ function useGlobalThemeTokens() {
437
437
 
438
438
  // ../smart-cart/src/format.ts
439
439
  var formatMoney2 = (m) => {
440
+ if (m.currency === "EUR" && Number.isFinite(m.amount)) {
441
+ const negative = m.amount < 0;
442
+ const abs = Math.round(Math.abs(m.amount));
443
+ const units = Math.floor(abs / 100).toString().replace(/\B(?=(\d{3})+(?!\d))/g, ".");
444
+ return `${negative ? "-" : ""}\u20AC${units},${String(abs % 100).padStart(2, "0")}`;
445
+ }
440
446
  if (m.currency && /^[A-Za-z]{3}$/.test(m.currency)) {
441
447
  try {
442
448
  return new Intl.NumberFormat(void 0, {
@@ -448,6 +454,10 @@ var formatMoney2 = (m) => {
448
454
  }
449
455
  return new Intl.NumberFormat(void 0, { minimumFractionDigits: 2 }).format(m.amount / 100);
450
456
  };
457
+ var formatMoneyCompact = (m) => {
458
+ const formatted = formatMoney2(m);
459
+ return Number.isFinite(m.amount) && Math.round(Math.abs(m.amount)) % 100 === 0 ? formatted.replace(/[.,]00(?=\D*$)/, "") : formatted;
460
+ };
451
461
  var escapeHtml = (s) => String(s ?? "").replace(
452
462
  /[&<>"']/g,
453
463
  (c) => ({ "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;", "'": "&#39;" })[c]
@@ -456,7 +466,7 @@ var formatCountdown = (ms) => {
456
466
  const total = Number.isFinite(ms) && ms > 0 ? Math.floor(ms / 1e3) : 0;
457
467
  const minutes = Math.floor(total / 60);
458
468
  const seconds = total % 60;
459
- return `${minutes}:${String(seconds).padStart(2, "0")}`;
469
+ return `${String(minutes).padStart(2, "0")}:${String(seconds).padStart(2, "0")}`;
460
470
  };
461
471
 
462
472
  // ../smart-cart/src/widgets/add-on.ts
@@ -478,10 +488,10 @@ function findAddOnLine(state, variantId) {
478
488
  var css = `
479
489
  .ksc-addon { display: grid; grid-template-columns: auto minmax(0, 1fr) auto auto; align-items: center;
480
490
  gap: 2px 12px; padding: 14px 18px; border-top: 1px solid var(--ksc-border); font: inherit; color: var(--ksc-text); }
481
- .ksc-addon__icon { grid-row: 1 / span 2; width: 34px; height: 34px; display: flex; align-items: center;
491
+ .ksc-addon__icon { grid-row: 1 / span 2; width: 44px; height: 44px; display: flex; align-items: center;
482
492
  justify-content: center; color: var(--ksc-accent); }
483
- .ksc-addon__icon svg { width: 28px; height: 28px; }
484
- .ksc-addon__img { grid-row: 1 / span 2; width: 34px; height: 34px; object-fit: cover; border-radius: 8px;
493
+ .ksc-addon__icon svg { width: 34px; height: 34px; }
494
+ .ksc-addon__img { grid-row: 1 / span 2; width: 44px; height: 44px; object-fit: cover; border-radius: 8px;
485
495
  background: var(--ksc-border); }
486
496
  .ksc-addon__text { grid-column: 2; display: flex; flex-direction: column; gap: 1px; min-width: 0; }
487
497
  .ksc-addon__title { font-size: .875rem; font-weight: 600; }
@@ -549,12 +559,11 @@ function readMessage(config) {
549
559
  return typeof raw === "string" && raw.trim() !== "" ? raw : null;
550
560
  }
551
561
  var css2 = `
552
- .ksc-rt { display: flex; align-items: center; justify-content: center; gap: 6px;
553
- padding: 10px 16px; font: inherit; font-size: 0.85rem; font-weight: 500;
554
- color: color-mix(in oklab, var(--ksc-positive, #0e9f6e) 72%, var(--ksc-text, #111));
555
- background: color-mix(in oklab, var(--ksc-positive, #0e9f6e) 13%, var(--ksc-surface, #fff)); }
556
- .ksc-rt__count { font-variant-numeric: tabular-nums; font-weight: 700;
557
- color: color-mix(in oklab, var(--ksc-positive, #0e9f6e) 85%, var(--ksc-text, #111)); }
562
+ .ksc-rt { display: flex; align-items: center; justify-content: center; gap: 4px;
563
+ padding: 9px 16px; font: inherit; font-size: 0.8125rem; font-weight: 400;
564
+ color: var(--ksc-text, #111);
565
+ background: rgb(215, 254, 218); }
566
+ .ksc-rt__count { font-variant-numeric: tabular-nums; font-weight: 700; }
558
567
  .ksc-rt__icon { font-size: 0.95em; line-height: 1; }
559
568
  @media (prefers-reduced-motion: reduce) { .ksc-rt { transition: none; } }
560
569
  `;
@@ -567,10 +576,10 @@ var reservationTimer = {
567
576
  if (minutes === null) return "";
568
577
  void readMode(config);
569
578
  const seed = formatCountdown(minutes * MS_PER_MINUTE);
570
- const message = readMessage(config) ?? "Your cart is reserved for";
579
+ const message = readMessage(config) ?? "Your cart will expire in";
571
580
  return `<div class="ksc-rt">
572
581
  <span>${escapeHtml(message)}</span>
573
- <span class="ksc-rt__count" data-ksc-countdown data-ksc-end-text="0:00">${seed}</span>
582
+ <span class="ksc-rt__count" data-ksc-countdown data-ksc-end-text="00:00">${seed}</span>
574
583
  <span class="ksc-rt__icon" aria-hidden="true">\u23F0</span>
575
584
  </div>`;
576
585
  }
@@ -591,78 +600,121 @@ function readVariantIds(config) {
591
600
  }
592
601
  return [...new Set(ids)];
593
602
  }
603
+ function readTitle(config) {
604
+ const raw = config.title;
605
+ return typeof raw === "string" && raw.trim() !== "" ? raw : "You might also like";
606
+ }
594
607
  function cartVariantIds(state) {
595
608
  if (state === null) return /* @__PURE__ */ new Set();
596
609
  const ids = /* @__PURE__ */ new Set();
597
610
  for (const line of state.lines) {
598
611
  const tag = line.attributes?.[UPSELL_VARIANT_ATTR];
599
612
  if (typeof tag === "string") ids.add(tag);
613
+ if (line.variantId !== null && line.variantId !== void 0) ids.add(String(line.variantId));
600
614
  }
601
615
  return ids;
602
616
  }
617
+ var ARROW_LEFT_SVG = `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M19 12H5M11 6l-6 6 6 6"/></svg>`;
618
+ var ARROW_RIGHT_SVG = `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M5 12h14M13 6l6 6-6 6"/></svg>`;
603
619
  var css3 = `
604
- .ksc-up { padding: 12px 16px; font: inherit; color: var(--ksc-text); }
605
- .ksc-up__title { margin: 0 0 8px; font-size: 0.8125rem; font-weight: 600;
606
- text-transform: uppercase; letter-spacing: 0.04em; color: color-mix(in oklab, var(--ksc-text) 65%, transparent); }
620
+ .ksc-up { padding: 14px 6px 16px; font: inherit; color: var(--ksc-text); }
621
+ .ksc-up__title { margin: 0 0 10px; font-size: 0.9375rem; font-weight: 700; text-align: center; }
622
+ .ksc-up__row { display: grid; grid-template-columns: 28px minmax(0, 1fr) 28px; align-items: center; gap: 2px; }
623
+ .ksc-up__arrow { display: flex; align-items: center; justify-content: center; width: 28px; height: 28px;
624
+ padding: 0; border: none; background: none; cursor: pointer; color: rgb(52, 64, 84); border-radius: 8px; }
625
+ .ksc-up__arrow:hover { background: var(--ksc-border); }
626
+ .ksc-up__arrow svg { width: 18px; height: 18px; }
607
627
  .ksc-up__track { display: flex; gap: 10px; overflow-x: auto; scroll-snap-type: x mandatory;
608
- -webkit-overflow-scrolling: touch; padding-bottom: 4px; scrollbar-width: thin; }
609
- .ksc-up__card { scroll-snap-align: start; flex: 0 0 132px; display: flex; flex-direction: column;
610
- gap: 6px; border: 1px solid color-mix(in oklab, var(--ksc-text) 12%, transparent);
611
- border-radius: var(--ksc-radius); padding: 8px; background: var(--ksc-surface); }
612
- .ksc-up__img { width: 100%; aspect-ratio: 1 / 1; object-fit: cover; border-radius: calc(var(--ksc-radius) - 4px);
613
- background: color-mix(in oklab, var(--ksc-text) 6%, transparent); }
614
- .ksc-up__name { font-size: 0.8125rem; line-height: 1.2; display: -webkit-box; -webkit-line-clamp: 2;
615
- -webkit-box-orient: vertical; overflow: hidden; }
616
- .ksc-up__variant { font-size: 0.75rem; color: color-mix(in oklab, var(--ksc-text) 55%, transparent); }
617
- .ksc-up__price { font-size: 0.8125rem; color: color-mix(in oklab, var(--ksc-text) 70%, transparent); }
618
- .ksc-up__compare { color: color-mix(in oklab, var(--ksc-text) 45%, transparent); margin-right: 4px; }
619
- .ksc-up__add { margin-top: auto; padding: 6px 8px; font: inherit; font-size: 0.8125rem; cursor: pointer;
620
- border: none; border-radius: calc(var(--ksc-radius) - 4px); background: var(--ksc-accent);
621
- color: var(--ksc-accent-text); transition: opacity .15s ease; }
628
+ scroll-behavior: smooth; -webkit-overflow-scrolling: touch; scrollbar-width: none; }
629
+ .ksc-up__track::-webkit-scrollbar { display: none; }
630
+ .ksc-up__card { scroll-snap-align: start; flex: 0 0 100%; display: grid;
631
+ grid-template-columns: 64px minmax(0, 1fr); gap: 4px 12px; align-items: start;
632
+ border-radius: 12px; padding: 12px; background: rgb(242, 244, 247); box-sizing: border-box; }
633
+ .ksc-up__img { grid-row: 1 / span 4; width: 64px; height: 64px; object-fit: cover; border-radius: 8px;
634
+ background: var(--ksc-surface); }
635
+ .ksc-up__name { font-size: 0.9375rem; font-weight: 700; line-height: 1.25; display: -webkit-box;
636
+ -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; }
637
+ .ksc-up__prices { display: flex; align-items: center; flex-wrap: wrap; gap: 6px; font-size: 0.875rem; }
638
+ .ksc-up__price { font-weight: 700; }
639
+ .ksc-up__compare { color: rgb(152, 162, 179); text-decoration: line-through; }
640
+ .ksc-up__save { font-size: 0.75rem; font-weight: 500; color: rgb(7, 148, 85);
641
+ background: rgb(220, 250, 230); padding: 2px 8px; border-radius: 6px; white-space: nowrap; }
642
+ .ksc-up__variant { display: flex; flex-direction: column; gap: 3px; align-items: flex-start;
643
+ font-size: 0.78rem; color: rgb(152, 162, 179); }
644
+ .ksc-up__select { font: inherit; font-size: 0.8125rem; color: var(--ksc-text); padding: 4px 24px 4px 10px;
645
+ border: 1px solid var(--ksc-border); border-radius: 8px; background-color: var(--ksc-surface); cursor: pointer;
646
+ appearance: none; -webkit-appearance: none;
647
+ background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='6' viewBox='0 0 10 6' fill='none' stroke='%23999' stroke-width='1.5'%3E%3Cpath d='M1 1l4 4 4-4'/%3E%3C/svg%3E");
648
+ background-repeat: no-repeat; background-position: right 8px center; }
649
+ .ksc-up__add { justify-self: start; margin-top: 6px; padding: 8px 14px; font: inherit; font-size: 0.8125rem;
650
+ font-weight: 600; cursor: pointer; border: none; border-radius: 8px; background: rgb(7, 148, 85);
651
+ color: #fff; transition: opacity .15s ease; }
622
652
  .ksc-up__add:hover { opacity: 0.9; }
623
- .ksc-up__add:focus-visible { outline: 2px solid var(--ksc-accent); outline-offset: 2px; }
653
+ .ksc-up__add:focus-visible { outline: 2px solid rgb(7, 148, 85); outline-offset: 2px; }
624
654
  @media (prefers-reduced-motion: reduce) { .ksc-up__track { scroll-behavior: auto; } .ksc-up__add { transition: none; } }
625
655
  `;
626
- function card(variant, currency) {
656
+ function card(variant, currency, siblings) {
627
657
  const name = escapeHtml(variant.productTitle);
628
658
  const img = variant.imageUrl !== null ? `<img class="ksc-up__img" src="${escapeHtml(variant.imageUrl)}" alt="${escapeHtml(variant.imageAltText ?? "")}" loading="lazy" />` : `<div class="ksc-up__img" role="presentation"></div>`;
629
- const variantTitle = variant.title !== null && variant.title.trim() !== "" && variant.title !== DEFAULT_VARIANT_TITLE ? `<span class="ksc-up__variant">${escapeHtml(variant.title)}</span>` : "";
630
659
  const price = escapeHtml(formatMoney2({ amount: variant.price.amount, currency }));
631
660
  const showCompare = variant.compareAtPrice !== null && variant.compareAtPrice.amount > variant.price.amount;
632
- const priceHtml = showCompare ? `<span class="ksc-up__price"><s class="ksc-up__compare">${escapeHtml(formatMoney2({ amount: variant.compareAtPrice.amount, currency }))}</s>${price}</span>` : `<span class="ksc-up__price">${price}</span>`;
661
+ const compare = showCompare ? `<s class="ksc-up__compare">${escapeHtml(formatMoney2({ amount: variant.compareAtPrice.amount, currency }))}</s>` : "";
662
+ const savePct = showCompare ? Math.round(
663
+ (variant.compareAtPrice.amount - variant.price.amount) / variant.compareAtPrice.amount * 100
664
+ ) : 0;
665
+ const save = savePct > 0 ? `<span class="ksc-up__save">You save ${savePct}%</span>` : "";
666
+ const hasMeaningfulTitle = variant.title !== null && variant.title.trim() !== "" && variant.title !== DEFAULT_VARIANT_TITLE;
667
+ const variantHtml = siblings !== void 0 && siblings.length > 1 ? `<span class="ksc-up__variant"><span>Size:</span><select class="ksc-up__select" data-smart-cart-upsell-variant aria-label="Size">${siblings.map(
668
+ (v) => `<option value="${escapeHtml(v.id)}"${v.id === variant.id ? " selected" : ""}>${escapeHtml(v.title ?? "")}</option>`
669
+ ).join("")}</select></span>` : hasMeaningfulTitle ? `<span class="ksc-up__variant"><span>Size:</span><span>${escapeHtml(variant.title)}</span></span>` : "";
633
670
  return `<div class="ksc-up__card" role="listitem">
634
671
  ${img}
635
672
  <span class="ksc-up__name">${name}</span>
636
- ${variantTitle}
637
- ${priceHtml}
638
- <button type="button" class="ksc-up__add" data-smart-cart-upsell-add data-variant-id="${escapeHtml(variant.id)}" aria-label="Add ${name} to cart">Add</button>
673
+ <span class="ksc-up__prices"><span class="ksc-up__price">${price}</span>${compare}${save}</span>
674
+ ${variantHtml}
675
+ <button type="button" class="ksc-up__add" data-smart-cart-upsell-add data-variant-id="${escapeHtml(variant.id)}" aria-label="Add ${name} to cart">Add to cart</button>
639
676
  </div>`;
640
677
  }
641
678
  var upsellCarousel = {
642
679
  key: UPSELL_KEY,
643
680
  zone: "after-items",
644
681
  css: css3,
645
- render({ config, state, variants }) {
682
+ render({ config, state, variants, productVariants }) {
646
683
  if (variants === void 0 || variants.size === 0) return "";
647
684
  const inCart = cartVariantIds(state);
648
- const cards = readVariantIds(config).filter((id) => !inCart.has(id)).map((id) => variants.get(id)).filter((variant) => variant !== void 0).map((variant) => card(variant, state?.currency ?? variant.price.currency)).join("");
685
+ const cards = readVariantIds(config).filter((id) => !inCart.has(id)).map((id) => variants.get(id)).filter((variant) => variant !== void 0).map(
686
+ (variant) => card(
687
+ variant,
688
+ state?.currency ?? variant.price.currency,
689
+ variant.productHandle !== null ? productVariants?.get(variant.productHandle) : void 0
690
+ )
691
+ ).join("");
649
692
  if (cards === "") return "";
650
693
  return `<section class="ksc-up" aria-label="Recommended add-ons">
651
- <p class="ksc-up__title">You might also like</p>
652
- <div class="ksc-up__track" role="list">${cards}</div>
694
+ <p class="ksc-up__title">${escapeHtml(readTitle(config))}</p>
695
+ <div class="ksc-up__row">
696
+ <button type="button" class="ksc-up__arrow" data-smart-cart-upsell-prev aria-label="Previous item">${ARROW_LEFT_SVG}</button>
697
+ <div class="ksc-up__track" role="list">${cards}</div>
698
+ <button type="button" class="ksc-up__arrow" data-smart-cart-upsell-next aria-label="Next item">${ARROW_RIGHT_SVG}</button>
699
+ </div>
653
700
  </section>`;
654
701
  }
655
702
  };
656
703
 
657
704
  // ../smart-cart/src/widgets/discount-code.ts
658
705
  var css4 = `
659
- .ksc-discount { padding: 12px 16px; display: flex; flex-direction: column; gap: 8px; }
660
- .ksc-discount__row { display: flex; gap: 8px; }
661
- .ksc-discount__input { flex: 1; padding: 8px 10px; border: 1.5px solid color-mix(in oklab, var(--ksc-text) 25%, transparent); border-radius: calc(var(--ksc-radius) / 2); background: none; color: var(--ksc-text); font: inherit; font-size: 0.875rem; outline: none; }
662
- .ksc-discount__input:focus { border-color: var(--ksc-accent); }
663
- .ksc-discount__apply { padding: 8px 14px; border: none; border-radius: calc(var(--ksc-radius) / 2); background: var(--ksc-accent); color: var(--ksc-accent-text); font: inherit; font-size: 0.875rem; cursor: pointer; white-space: nowrap; }
706
+ .ksc-discount { padding: 10px 20px; display: flex; flex-direction: column; gap: 8px; }
707
+ .ksc-discount__row { display: flex; align-items: center; gap: 6px; padding: 5px 5px 5px 14px;
708
+ border: 1px solid rgb(190, 190, 190); border-radius: 10px; background: var(--ksc-surface); }
709
+ .ksc-discount__row:focus-within { border-color: var(--ksc-text); }
710
+ .ksc-discount__input { flex: 1; min-width: 0; padding: 6px 0; border: none; background: none;
711
+ color: var(--ksc-text); font: inherit; font-size: 0.875rem; outline: none; }
712
+ .ksc-discount__apply { padding: 8px 16px; border: none; border-radius: 8px;
713
+ background: rgb(220, 250, 230); color: var(--ksc-text); font: inherit; font-size: 0.875rem;
714
+ font-weight: 500; cursor: pointer; white-space: nowrap; }
715
+ .ksc-discount__apply:hover { filter: brightness(0.97); }
664
716
  .ksc-discount__chips { display: flex; flex-wrap: wrap; gap: 6px; }
665
- .ksc-discount__chip { display: inline-flex; align-items: center; gap: 5px; padding: 3px 8px 3px 10px; background: color-mix(in oklab, var(--ksc-accent) 12%, transparent); border-radius: 999px; font-size: 0.8125rem; color: var(--ksc-text); }
717
+ .ksc-discount__chip { display: inline-flex; align-items: center; gap: 5px; padding: 3px 8px 3px 10px; background: rgb(242, 244, 247); border-radius: 999px; font-size: 0.8125rem; color: var(--ksc-text); }
666
718
  .ksc-discount__chip-remove { display: inline-flex; align-items: center; justify-content: center; background: none; border: none; cursor: pointer; padding: 2px; color: color-mix(in oklab, var(--ksc-text) 60%, transparent); line-height: 1; font-size: 1rem; }
667
719
  .ksc-discount__chip-remove:hover { color: var(--ksc-text); }
668
720
  `;
@@ -695,11 +747,26 @@ function readMessage2(config, key) {
695
747
  const raw = config[key];
696
748
  return typeof raw === "string" && raw.trim() !== "" ? raw : null;
697
749
  }
750
+ function renderTemplate(template, amount) {
751
+ return template.split(/\{amount\}/g).map((part) => escapeHtml(part)).join(`<b>${escapeHtml(amount)}</b>`);
752
+ }
753
+ var TRUCK_SVG = `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M1 5h14v11H1zM15 9h4l3 3v4h-7z"/><circle cx="6" cy="18.5" r="1.8"/><circle cx="18" cy="18.5" r="1.8"/></svg>`;
698
754
  var css5 = `
699
- .ksc-fsb { padding: 12px 16px; font: inherit; color: var(--ksc-text); }
700
- .ksc-fsb__msg { margin: 0 0 8px; font-size: 0.875rem; }
701
- .ksc-fsb__track { height: 6px; border-radius: var(--ksc-radius); background: color-mix(in oklab, var(--ksc-text) 12%, transparent); overflow: hidden; }
702
- .ksc-fsb__fill { height: 100%; background: var(--ksc-accent); transition: width .3s ease; }
755
+ .ksc-fsb { padding: 14px 20px 10px; font: inherit; color: var(--ksc-text); }
756
+ .ksc-fsb__msg { margin: 0 0 10px; font-size: 0.8125rem; text-align: center; }
757
+ .ksc-fsb__msg b { font-weight: 700; }
758
+ .ksc-fsb__track { position: relative; height: 8px; border-radius: 999px;
759
+ background: rgb(234, 236, 240); }
760
+ .ksc-fsb__fill { height: 100%; border-radius: 999px; background: rgb(29, 41, 57);
761
+ transition: width .3s ease; }
762
+ .ksc-fsb__step { position: absolute; right: -2px; top: 50%; transform: translateY(-50%);
763
+ width: 14px; height: 14px; border-radius: 4px; border: 2px solid rgb(29, 41, 57);
764
+ background: var(--ksc-surface); box-sizing: border-box; }
765
+ .ksc-fsb--unlocked .ksc-fsb__step { background: rgb(29, 41, 57); }
766
+ .ksc-fsb__tier { display: flex; flex-direction: column; align-items: flex-end; gap: 1px;
767
+ margin-top: 6px; font-size: 0.78rem; color: rgb(152, 162, 179); }
768
+ .ksc-fsb__tier svg { width: 20px; height: 20px; }
769
+ .ksc-fsb--unlocked .ksc-fsb__tier { color: rgb(29, 41, 57); }
703
770
  @media (prefers-reduced-motion: reduce) { .ksc-fsb__fill { transition: none; } }
704
771
  `;
705
772
  var freeShippingBar = {
@@ -709,20 +776,22 @@ var freeShippingBar = {
709
776
  render({ config, state }) {
710
777
  const threshold = readThreshold(config);
711
778
  if (threshold === null || state === null) return "";
712
- const subtotal = state.subtotal.amount;
779
+ const discountTotal = state.discountTotal?.amount ?? 0;
780
+ const subtotal = Math.max(0, state.subtotal.amount - discountTotal);
713
781
  const remaining = Math.max(0, threshold - subtotal);
714
782
  const unlocked = remaining === 0;
715
783
  const pct = Math.min(100, Math.round(subtotal / threshold * 100));
716
- const remainingMoney = formatMoney2({ amount: remaining, currency: state.subtotal.currency });
784
+ const remainingMoney = formatMoneyCompact({
785
+ amount: remaining,
786
+ currency: state.subtotal.currency
787
+ });
717
788
  const beforeTemplate = readMessage2(config, "messageBefore");
718
789
  const afterTemplate = readMessage2(config, "messageAfter");
719
- const message = unlocked ? afterTemplate ?? "You've unlocked free shipping!" : (beforeTemplate ?? "You're {amount} away from free shipping").replace(
720
- /\{amount\}/g,
721
- remainingMoney
722
- );
723
- return `<div class="ksc-fsb">
724
- <p class="ksc-fsb__msg">${escapeHtml(message)}</p>
725
- <div class="ksc-fsb__track"><div class="ksc-fsb__fill" style="width: ${pct}%"></div></div>
790
+ const message = unlocked ? afterTemplate !== null ? renderTemplate(afterTemplate, remainingMoney) : `<b>Free shipping</b> unlocked!` : beforeTemplate !== null ? renderTemplate(beforeTemplate, remainingMoney) : `Add <b>${escapeHtml(remainingMoney)}</b> to unlock <b>Free shipping</b>!`;
791
+ return `<div class="ksc-fsb${unlocked ? " ksc-fsb--unlocked" : ""}">
792
+ <p class="ksc-fsb__msg">${message}</p>
793
+ <div class="ksc-fsb__track"><div class="ksc-fsb__fill" style="width: ${pct}%"></div><span class="ksc-fsb__step" aria-hidden="true"></span></div>
794
+ <div class="ksc-fsb__tier">${TRUCK_SVG}<span>${unlocked ? "" : "\u{1F512} "}Free shipping</span></div>
726
795
  </div>`;
727
796
  }
728
797
  };
@@ -889,6 +958,23 @@ var PAYMENT_ICON_SVGS = {
889
958
  <path d="M228.897012,82.7490197 C216.569147,146.069792 174.366398,167.970272 120.46841,167.970272 L93.0241367,167.970272 C86.4398419,167.970272 80.8794007,172.764903 79.8555471,179.265958 L61.8174095,293.621258 C61.1431644,297.883153 64.4394738,301.745495 68.7513129,301.745495 L117.421821,301.745495 C123.182038,301.745495 128.084882,297.550192 128.983876,291.864891 L129.458344,289.384335 L138.631407,231.249423 L139.222412,228.036354 C140.121406,222.351053 145.02425,218.15575 150.784467,218.15575 L158.067979,218.15575 C205.215193,218.15575 242.132193,199.002194 252.920115,143.605884 C257.423406,120.456802 255.092683,101.128442 243.181019,87.5519756 C239.568397,83.4399129 235.081754,80.0437153 229.854273,77.2718188 C229.571257,79.0614817 229.263268,80.8761167 228.897012,82.7490197 L228.897012,82.7490197 Z" fill="#2790C3"></path>
890
959
  <path d="M216.952052,72.1275783 C215.070825,71.5781934 213.13133,71.0787526 211.133566,70.6375799 C209.135803,70.1964071 207.071448,69.8051785 204.957148,69.4638939 C197.548776,68.265236 189.457835,67.699203 180.767565,67.699203 L107.457976,67.699203 C105.651665,67.699203 103.936919,68.1070797 102.4053,68.8479169 C99.0174265,70.4710996 96.5118984,73.6675208 95.8959214,77.5881313 L80.3133678,176.385849 L79.8638711,179.265958 C80.8877248,172.764903 86.4481659,167.970272 93.0324607,167.970272 L120.476734,167.970272 C174.374722,167.970272 216.577471,146.078116 228.905336,82.7490197 C229.271592,80.8761167 229.579581,79.0614817 229.862597,77.2718188 C226.741092,75.623664 223.361542,74.2002577 219.723948,72.9932757 C218.816631,72.6936112 217.892665,72.4022708 216.952052,72.1275783" fill="#1F264F"></path>
891
960
  </g>
961
+ </svg>`,
962
+ "apple-pay": `<svg width="512px" height="211px" viewBox="0 0 512 211" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" preserveAspectRatio="xMidYMid">
963
+ <g>
964
+ <path d="M93.5520633,27.1031049 C87.5513758,34.2039183 77.9502759,39.8045599 68.349176,39.0044683 C67.1490386,29.4033684 71.849577,19.2021998 77.3502072,12.901478 C83.3508946,5.6006416 93.8520976,0.400045829 102.353071,0 C103.353186,10.0011457 99.4527392,19.8022685 93.5520633,27.1031049 Z M102.25306,40.904686 C88.3514675,40.1045943 76.4501041,48.8055911 69.8493479,48.8055911 C63.1485803,48.8055911 53.0474231,41.3047318 42.0461628,41.5047547 C27.7445244,41.7047776 14.4430006,49.8057057 7.14216427,62.7071836 C-7.8595543,88.5101396 3.24171744,126.714516 17.7433787,147.716922 C24.8441922,158.118114 33.345166,169.51942 44.5464492,169.119374 C55.1476637,168.719328 59.3481449,162.218584 72.1496114,162.218584 C85.0510894,162.218584 88.7515133,169.119374 99.9527965,168.919351 C111.554126,168.719328 118.854962,158.51816 125.955775,148.116968 C134.056703,136.315616 137.357081,124.814299 137.557104,124.21423 C137.357081,124.014207 115.154538,115.513233 114.954515,89.9103 C114.754492,68.5078482 132.45652,58.3066795 133.256612,57.7066108 C123.255466,42.9049151 107.653679,41.3047318 102.25306,40.904686 Z M182.56226,11.9013634 L182.56226,167.819225 L206.765033,167.819225 L206.765033,114.513118 L240.268871,114.513118 C270.872377,114.513118 292.37484,93.5107124 292.37484,63.1072295 C292.37484,32.7037465 271.272423,11.9013634 241.068963,11.9013634 L182.56226,11.9013634 Z M206.765033,32.3037007 L234.668229,32.3037007 C255.670635,32.3037007 267.67201,43.5049839 267.67201,63.2072409 C267.67201,82.909498 255.670635,94.2107926 234.568218,94.2107926 L206.765033,94.2107926 L206.765033,32.3037007 Z M336.579904,169.019363 C351.781646,169.019363 365.883261,161.31848 372.283994,149.117083 L372.784052,149.117083 L372.784052,167.819225 L395.186618,167.819225 L395.186618,90.2103344 C395.186618,67.7077565 377.184556,53.2060952 349.481382,53.2060952 C323.778438,53.2060952 304.776261,67.9077794 304.076181,88.1100938 L325.878678,88.1100938 C327.678884,78.5089939 336.579904,72.2082721 348.781302,72.2082721 C363.582998,72.2082721 371.883949,79.1090626 371.883949,91.8105177 L371.883949,100.411503 L341.680488,102.211709 C313.577269,103.911904 298.375528,115.413222 298.375528,135.415513 C298.375528,155.617827 314.077326,169.019363 336.579904,169.019363 Z M343.080649,150.517243 C330.179171,150.517243 321.978231,144.316533 321.978231,134.815444 C321.978231,125.014321 329.879137,119.313668 344.980867,118.413565 L371.883949,116.713371 L371.883949,125.514379 C371.883949,140.116051 359.482528,150.517243 343.080649,150.517243 Z M425.090044,210.224083 C448.692748,210.224083 459.794019,201.223052 469.495131,173.919924 L512,54.7062671 L487.397182,54.7062671 L458.893916,146.816819 L458.393859,146.816819 L429.890594,54.7062671 L404.587695,54.7062671 L445.592392,168.219271 L443.39214,175.120061 C439.691716,186.821402 433.691029,191.321918 422.989803,191.321918 C421.089585,191.321918 417.389162,191.121895 415.88899,190.921872 L415.88899,209.624014 C417.28915,210.02406 423.289838,210.224083 425.090044,210.224083 Z" fill="#000000"></path>
965
+ </g>
966
+ </svg>`,
967
+ "google-pay": `<svg width="512px" height="204px" viewBox="0 0 512 204" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" preserveAspectRatio="xMidYMid">
968
+ <title>Google Pay</title>
969
+ <g>
970
+ <path d="M362.926585,55.0572275 C377.002187,55.0572275 387.878788,58.8960279 396.196189,66.5736286 C404.51359,74.2512294 408.35239,84.4880304 408.35239,97.2840316 L408.35239,158.704838 L390.437988,158.704838 L390.437988,144.629236 L389.798188,144.629236 C382.120587,156.145637 371.243986,161.903838 358.447985,161.903838 C347.571384,161.903838 337.974383,158.704838 330.296782,152.306837 C323.258982,145.908836 318.780381,136.951636 318.780381,127.354635 C318.780381,117.117834 322.619182,108.800433 330.296782,102.402432 C337.974383,96.0044315 348.850984,93.4452313 361.646985,93.4452313 C373.163386,93.4452313 382.120587,95.3646315 389.158388,99.8432319 L389.158388,95.3646315 C389.158388,89.3931642 386.92904,83.421697 382.470345,79.5309543 L381.480787,78.7298298 C376.362387,74.2512294 369.964386,71.6920291 362.926585,71.6920291 C352.049984,71.6920291 343.732584,76.1706296 337.974383,85.1278305 L321.339581,74.8910295 C331.576382,61.4552281 345.012184,55.0572275 362.926585,55.0572275 Z M272.714777,11.5508233 C284.195633,11.5508233 295.105804,15.5456243 303.828346,22.9962452 L305.34458,24.3468245 C314.301781,32.0244253 318.780381,43.5408264 318.780381,55.6970276 C318.780381,67.8532288 314.301781,78.7298298 305.34458,87.0472306 C296.387379,95.3646315 285.510778,99.8432319 272.714777,99.8432319 L242.004374,99.2034318 L242.004374,158.704838 L222.810372,158.704838 L222.810372,11.5508233 L272.714777,11.5508233 Z M365.485786,108.800433 C357.808185,108.800433 351.410184,110.719833 346.291784,114.558633 C341.173383,117.757634 338.614183,122.236234 338.614183,127.994435 C338.614183,133.112835 341.173383,137.591436 345.012184,140.150636 C349.490784,143.349636 354.609185,145.269036 359.727585,145.269036 C366.893346,145.269036 374.059107,142.482352 379.664324,137.429163 L380.840987,136.311835 C387.238988,130.553635 390.437988,123.515834 390.437988,115.838233 C384.679788,111.359633 376.362387,108.800433 365.485786,108.800433 Z M273.994377,29.465225 L242.004374,29.465225 L242.004374,80.64923 L273.994377,80.64923 C281.032177,80.64923 288.069978,78.0900298 292.548579,72.9716293 C302.78538,63.3746283 302.78538,47.3796268 293.188379,37.7826258 L292.548579,37.1428258 C287.430178,32.0244253 281.032177,28.825425 273.994377,29.465225 Z M512,58.2562278 L448.659794,203.490842 L429.465792,203.490842 L453.138394,152.946637 L411.55139,58.8960279 L432.024992,58.8960279 L462.095595,131.193435 L462.735395,131.193435 L492.166198,58.8960279 L512,58.8960279 L512,58.2562278 Z" fill="#5F6368"></path>
971
+ <g>
972
+ <path d="M165.868166,86.4074306 C165.868166,80.64923 165.228366,74.8910295 164.588566,69.1328289 L84.6135583,69.1328289 L84.6135583,101.762632 L130.039363,101.762632 C128.119963,111.999433 122.361762,121.596434 113.404561,127.354635 L113.404561,148.468037 L140.915964,148.468037 C156.910965,133.752635 165.868166,111.999433 165.868166,86.4074306 Z" fill="#4285F4"></path>
973
+ <path d="M84.6135583,168.941639 C107.646361,168.941639 126.840362,161.264038 140.915964,148.468037 L113.404561,127.354635 C105.72696,132.473035 96.1299594,135.672035 84.6135583,135.672035 C62.8603561,135.672035 43.6663543,120.956634 37.2683536,100.483032 L9.11715089,100.483032 L9.11715089,122.236234 C23.8325523,151.027237 52.6235551,168.941639 84.6135583,168.941639 Z" fill="#34A853"></path>
974
+ <path d="M37.2683536,100.483032 C33.4295533,90.246231 33.4295533,78.7298298 37.2683536,67.8532288 L37.2683536,46.1000266 L9.11715089,46.1000266 C-3.0390503,69.772629 -3.0390503,97.9238317 9.11715089,122.236234 L37.2683536,100.483032 Z" fill="#FBBC04"></path>
975
+ <path d="M84.6135583,33.3040254 C96.7697595,33.3040254 108.286161,37.7826258 117.243361,46.1000266 L117.243361,46.1000266 L141.555764,21.7876243 C126.200562,7.71202289 105.72696,-0.60537792 85.2533583,0.0344221425 C53.2633552,0.0344221425 23.8325523,17.9488239 9.75695095,46.7398267 L37.9081537,68.4930288 C43.6663543,48.0194268 62.8603561,33.3040254 84.6135583,33.3040254 Z" fill="#EA4335"></path>
976
+ </g>
977
+ </g>
892
978
  </svg>`
893
979
  };
894
980
 
@@ -909,10 +995,11 @@ function readBadges(config) {
909
995
  return valid.length > 0 ? valid : null;
910
996
  }
911
997
  var css7 = `
912
- .ksc-tb { display: flex; flex-wrap: wrap; gap: 8px; align-items: center; justify-content: center;
998
+ .ksc-tb { display: flex; flex-wrap: wrap; gap: 6px; align-items: center; justify-content: center;
913
999
  padding: 10px 16px 4px; }
914
- .ksc-tb__icon { display: flex; align-items: center; height: 22px; width: auto; }
915
- .ksc-tb__icon svg { height: 22px; width: auto; display: block; }
1000
+ .ksc-tb__icon { display: flex; align-items: center; justify-content: center; width: 38px; height: 26px;
1001
+ border: 1px solid rgb(234, 236, 240); border-radius: 5px; background: #fff; box-sizing: border-box; }
1002
+ .ksc-tb__icon svg { max-height: 15px; max-width: 28px; width: auto; height: auto; display: block; }
916
1003
  `;
917
1004
  var trustBadges = {
918
1005
  key: "trustBadges",
@@ -960,7 +1047,7 @@ var STYLES2 = `
960
1047
  --ksc-border: color-mix(in oklab, var(--ksc-text) 12%, transparent); }
961
1048
  .overlay { position: absolute; inset: 0; background: rgba(0,0,0,.45); opacity: 0;
962
1049
  transition: opacity .25s ease; }
963
- .panel { position: absolute; top: 0; right: 0; height: 100%; width: min(480px, 100vw);
1050
+ .panel { position: absolute; top: 0; right: 0; height: 100%; width: min(520px, 100vw);
964
1051
  background: var(--ksc-surface); color: var(--ksc-text); font-family: var(--ksc-font);
965
1052
  transform: translateX(100%); transition: transform .25s ease; display: flex; flex-direction: column;
966
1053
  box-shadow: -8px 0 40px rgba(0,0,0,.14); }
@@ -969,12 +1056,13 @@ var STYLES2 = `
969
1056
  :host(:not([open])) { pointer-events: none; }
970
1057
  :host([open]) { pointer-events: auto; }
971
1058
 
972
- /* Header */
973
- .ksc-head { display: flex; align-items: center; gap: 8px; flex: 0 0 auto;
974
- padding: 16px 18px; border-bottom: 1px solid var(--ksc-border); }
975
- .ksc-head__title { font-size: 1rem; font-weight: 600; letter-spacing: .01em; }
976
- .ksc-head__count { color: var(--ksc-muted); font-weight: 500; font-size: .95rem; }
977
- .ksc-head__close { margin-left: auto; display: flex; align-items: center; justify-content: center;
1059
+ /* Header \u2014 centered "My cart \xB7 N" with the close control pinned right (original cart layout). */
1060
+ .ksc-head { position: relative; display: flex; align-items: center; justify-content: center;
1061
+ gap: 6px; flex: 0 0 auto; padding: 16px 52px; }
1062
+ .ksc-head__title { font-size: 1.125rem; font-weight: 600; letter-spacing: .01em; }
1063
+ .ksc-head__count { font-weight: 600; font-size: 1.125rem; }
1064
+ .ksc-head__close { position: absolute; right: 14px; top: 50%; transform: translateY(-50%);
1065
+ display: flex; align-items: center; justify-content: center;
978
1066
  width: 32px; height: 32px; padding: 0; border: none; background: none; cursor: pointer;
979
1067
  color: var(--ksc-text); border-radius: 8px; }
980
1068
  .ksc-head__close:hover { background: var(--ksc-border); }
@@ -986,63 +1074,66 @@ var STYLES2 = `
986
1074
  gap: 14px; padding: 56px 24px; text-align: center; color: var(--ksc-muted); }
987
1075
  .ksc-empty svg { width: 44px; height: 44px; opacity: .5; }
988
1076
  .ksc-empty p { margin: 0; font-size: .95rem; }
989
- .ksc-line { display: grid; grid-template-columns: 64px 1fr; gap: 14px;
990
- padding: 16px 18px; border-bottom: 1px solid var(--ksc-border); }
991
- .ksc-line__img { width: 64px; height: 64px; object-fit: cover; border-radius: 10px;
1077
+ .ksc-line { display: grid; grid-template-columns: 80px 1fr; gap: 14px;
1078
+ padding: 14px 20px; }
1079
+ .ksc-line + .ksc-line { border-top: 1px solid var(--ksc-border); }
1080
+ .ksc-line__img { width: 80px; height: 80px; object-fit: cover; border-radius: 8px;
992
1081
  background: var(--ksc-border); }
993
1082
  .ksc-line__img--ph { display: block; }
994
- .ksc-line__body { display: flex; flex-direction: column; gap: 5px; min-width: 0; }
995
- .ksc-line__top { display: flex; align-items: flex-start; justify-content: space-between; gap: 10px; }
996
- .ksc-line__title { font-size: .875rem; font-weight: 600; line-height: 1.3; }
997
- .ksc-line__compare { color: var(--ksc-muted); text-decoration: line-through;
998
- font-size: .8125rem; white-space: nowrap; padding-top: 1px; }
999
- .ksc-line__meta { display: flex; flex-direction: column; gap: 1px; }
1000
- .ksc-line__opt { font-size: .78rem; color: var(--ksc-muted); line-height: 1.35;
1083
+ .ksc-line__body { display: flex; flex-direction: column; gap: 6px; min-width: 0; }
1084
+ .ksc-line__top { display: flex; align-items: flex-start; justify-content: space-between; gap: 12px; }
1085
+ .ksc-line__main { display: flex; flex-direction: column; gap: 5px; min-width: 0; }
1086
+ .ksc-line__title { font-size: .9375rem; font-weight: 700; line-height: 1.3; }
1087
+ .ksc-line__compare { color: rgb(152, 162, 179); text-decoration: line-through;
1088
+ font-size: .875rem; white-space: nowrap; }
1089
+ .ksc-line__meta { display: flex; flex-direction: column; gap: 3px; }
1090
+ .ksc-line__opt { font-size: .8125rem; color: rgb(152, 162, 179); line-height: 1.35;
1001
1091
  overflow: hidden; text-overflow: ellipsis; }
1002
- .ksc-line__opt b { font-weight: 500; color: color-mix(in oklab, var(--ksc-text) 78%, transparent); }
1003
- .ksc-line__variant { display: flex; align-items: center; gap: 6px; font-size: .78rem; color: var(--ksc-muted); }
1004
- .ksc-line__discount { font-size: .78rem; font-weight: 500; color: var(--ksc-positive); line-height: 1.35; }
1005
- .ksc-line__discount s { color: var(--ksc-muted); text-decoration: line-through; }
1006
- .ksc-line__select { font: inherit; font-size: .78rem; color: var(--ksc-text); padding: 3px 22px 3px 8px;
1092
+ .ksc-line__opt b { font-weight: 700; color: var(--ksc-text); }
1093
+ .ksc-line__variant { display: flex; flex-direction: column; gap: 3px; align-items: flex-start;
1094
+ font-size: .8125rem; color: rgb(152, 162, 179); }
1095
+ .ksc-line__discount { font-size: .8125rem; font-weight: 500; color: var(--ksc-positive); line-height: 1.35; }
1096
+ .ksc-line__discount s { color: rgb(152, 162, 179); text-decoration: line-through; }
1097
+ .ksc-line__select { font: inherit; font-size: .8125rem; color: var(--ksc-text); padding: 4px 24px 4px 10px;
1007
1098
  border: 1px solid var(--ksc-border); border-radius: 8px; background: var(--ksc-surface); cursor: pointer;
1008
1099
  appearance: none; -webkit-appearance: none;
1009
1100
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='6' viewBox='0 0 10 6' fill='none' stroke='%23999' stroke-width='1.5'%3E%3Cpath d='M1 1l4 4 4-4'/%3E%3C/svg%3E");
1010
1101
  background-repeat: no-repeat; background-position: right 8px center; }
1011
1102
  .ksc-line__select:focus { outline: none; border-color: var(--ksc-accent); }
1012
1103
  .ksc-line__bottom { display: flex; align-items: center; justify-content: space-between;
1013
- gap: 10px; margin-top: 5px; }
1104
+ gap: 10px; margin-top: 4px; }
1014
1105
  .ksc-qty { display: inline-flex; align-items: center; border: 1px solid var(--ksc-border);
1015
- border-radius: 999px; }
1016
- .ksc-qty button { width: 28px; height: 28px; display: flex; align-items: center; justify-content: center;
1106
+ border-radius: 8px; }
1107
+ .ksc-qty button { width: 32px; height: 32px; display: flex; align-items: center; justify-content: center;
1017
1108
  padding: 0; border: none; background: none; cursor: pointer; color: var(--ksc-text);
1018
- font-size: 16px; line-height: 1; border-radius: 999px; }
1109
+ font-size: 16px; line-height: 1; border-radius: 8px; }
1019
1110
  .ksc-qty button:hover { background: var(--ksc-border); }
1020
- .ksc-qty__n { min-width: 22px; text-align: center; font-size: .8125rem; font-variant-numeric: tabular-nums; }
1021
- .ksc-line__end { display: flex; align-items: center; gap: 8px; }
1022
- .ksc-line__prices { display: flex; flex-direction: column; align-items: flex-end; gap: 3px; }
1023
- .ksc-line__price { font-size: .875rem; font-weight: 600; }
1024
- .ksc-line__save { font-size: .68rem; font-weight: 600; color: var(--ksc-positive); white-space: nowrap;
1025
- background: color-mix(in oklab, var(--ksc-positive) 14%, transparent); padding: 1px 6px; border-radius: 999px; }
1026
- .ksc-line__remove { display: flex; align-items: center; justify-content: center; width: 28px; height: 28px;
1111
+ .ksc-qty__n { min-width: 26px; text-align: center; font-size: .875rem; font-variant-numeric: tabular-nums; }
1112
+ .ksc-line__prices { display: flex; flex-direction: column; align-items: flex-end; gap: 3px; flex: 0 0 auto; }
1113
+ .ksc-line__price { font-size: .9375rem; font-weight: 700; }
1114
+ .ksc-line__save { display: inline-flex; align-items: center; gap: 4px; font-size: .75rem;
1115
+ font-weight: 500; color: rgb(7, 148, 85); white-space: nowrap;
1116
+ background: rgb(220, 250, 230); padding: 3px 8px; border-radius: 6px; }
1117
+ .ksc-line__save svg { width: 12px; height: 12px; }
1118
+ .ksc-line__remove { display: flex; align-items: center; justify-content: center; width: 32px; height: 32px;
1027
1119
  padding: 0; border: none; background: none; cursor: pointer; color: var(--ksc-muted); border-radius: 8px; }
1028
1120
  .ksc-line__remove:hover { color: var(--ksc-text); background: var(--ksc-border); }
1029
- .ksc-line__remove svg { width: 15px; height: 15px; }
1121
+ .ksc-line__remove svg { width: 17px; height: 17px; }
1030
1122
 
1031
1123
  /* Footer / summary / checkout */
1032
1124
  .footer { flex: 0 0 auto; border-top: 1px solid var(--ksc-border); }
1033
- .ksc-summary { display: flex; flex-direction: column; gap: 3px; padding: 12px 18px 2px; }
1125
+ .ksc-summary { display: flex; flex-direction: column; gap: 6px; padding: 12px 20px 2px; }
1034
1126
  .ksc-summary__row { display: flex; align-items: center; justify-content: space-between;
1035
- font-size: .8125rem; color: var(--ksc-muted); }
1036
- .ksc-summary__row--discount { color: var(--ksc-positive); font-weight: 500; }
1037
- .ksc-summary__row--total { font-size: .95rem; font-weight: 600; color: var(--ksc-text); padding-top: 3px; }
1038
- .checkout { display: block; text-align: center; margin: 8px 18px 0; padding: 15px;
1127
+ font-size: .875rem; color: rgb(71, 84, 103); }
1128
+ .ksc-summary__row--total { font-size: 1rem; font-weight: 700; color: var(--ksc-text); }
1129
+ .checkout { display: block; text-align: center; margin: 10px 20px 0; padding: 16px;
1039
1130
  background: var(--ksc-accent); color: var(--ksc-accent-text); text-decoration: none;
1040
- border-radius: var(--ksc-radius); font-weight: 600; font-size: .95rem; }
1131
+ border-radius: 10px; font-weight: 700; font-size: 1rem; }
1041
1132
  .checkout:hover { opacity: .92; }
1042
1133
  .ksc-continue { display: block; width: 100%; padding: 8px 18px 16px; text-align: center;
1043
- border: none; background: none; cursor: pointer; color: var(--ksc-muted); font: inherit;
1044
- font-size: .8125rem; text-decoration: underline; text-underline-offset: 2px; }
1045
- .ksc-continue:hover { color: var(--ksc-text); }
1134
+ border: none; background: none; cursor: pointer; color: var(--ksc-text); font: inherit;
1135
+ font-size: .875rem; font-weight: 700; }
1136
+ .ksc-continue:hover { opacity: .7; }
1046
1137
 
1047
1138
  @media (prefers-reduced-motion: reduce) { .overlay, .panel { transition: none; } }
1048
1139
  .chrome { position: absolute; inset: 0; pointer-events: none; }
@@ -1052,6 +1143,7 @@ var STYLES2 = `
1052
1143
  var CLOSE_SVG = `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" aria-hidden="true"><path d="M6 6l12 12M18 6L6 18"/></svg>`;
1053
1144
  var TRASH_SVG = `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M4 7h16M9 7V4h6v3M7 7l1 13h8l1-13"/></svg>`;
1054
1145
  var BAG_SVG = `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M6 8h12l1 12H5L6 8zM9 8a3 3 0 0 1 6 0"/></svg>`;
1146
+ var TAG_SVG = `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M20 13l-7 7-9-9V4h7l9 9z"/><circle cx="7.5" cy="7.5" r="1" fill="currentColor"/></svg>`;
1055
1147
  var Base = typeof HTMLElement !== "undefined" ? HTMLElement : class {
1056
1148
  };
1057
1149
  var SmartCartElement = class _SmartCartElement extends Base {
@@ -1145,6 +1237,13 @@ var SmartCartElement = class _SmartCartElement extends Base {
1145
1237
  }
1146
1238
  const upsellBtn = target?.closest("[data-smart-cart-upsell-add]");
1147
1239
  if (upsellBtn) void this.#onUpsellAdd(upsellBtn);
1240
+ const upsellNav = target?.closest(
1241
+ "[data-smart-cart-upsell-prev], [data-smart-cart-upsell-next]"
1242
+ );
1243
+ if (upsellNav) {
1244
+ this.#onUpsellNav(upsellNav);
1245
+ return;
1246
+ }
1148
1247
  const discountApply = target?.closest("[data-smart-cart-discount-apply]");
1149
1248
  if (discountApply) {
1150
1249
  void this.#onDiscountApply(discountApply);
@@ -1155,7 +1254,12 @@ var SmartCartElement = class _SmartCartElement extends Base {
1155
1254
  });
1156
1255
  this.#root.querySelector(".panel")?.addEventListener("change", (e) => {
1157
1256
  const select = e.target?.closest("[data-smart-cart-variant]");
1158
- if (select) void this.#onVariantSelect(select);
1257
+ if (select) {
1258
+ void this.#onVariantSelect(select);
1259
+ return;
1260
+ }
1261
+ const upsellSelect = e.target?.closest("[data-smart-cart-upsell-variant]");
1262
+ if (upsellSelect) this.#onUpsellVariantSelect(upsellSelect);
1159
1263
  });
1160
1264
  }
1161
1265
  connectedCallback() {
@@ -1200,8 +1304,7 @@ var SmartCartElement = class _SmartCartElement extends Base {
1200
1304
  this.#renderCart(state);
1201
1305
  this.#renderChrome(state);
1202
1306
  }
1203
- void this.#resolveUpsellVariants();
1204
- void this.#resolveProductVariants();
1307
+ void this.#resolveUpsellVariants().then(() => this.#resolveProductVariants());
1205
1308
  }
1206
1309
  /** Add a line (from a theme add-to-cart submit intercepted by mountSmartCart) and open the drawer on
1207
1310
  * the result. Opens first for instant feedback, then adds and reconciles against the server cart.
@@ -1223,8 +1326,9 @@ var SmartCartElement = class _SmartCartElement extends Base {
1223
1326
  }
1224
1327
  }
1225
1328
  /** Lazily fetch sibling variants for every cart line that has a product handle and a real variant
1226
- * title (single-variant lines have none → no switcher). Caches per handle, never re-fetches, and
1227
- * re-renders the cart once new variants land so the `<select>`s appear. Fail-closed per the lookup. */
1329
+ * title (single-variant lines have none → no switcher), plus every resolved upsell/add-on
1330
+ * product (the upsell card's size select). Caches per handle, never re-fetches, and re-renders
1331
+ * the cart once new variants land so the `<select>`s appear. Fail-closed per the lookup. */
1228
1332
  async #resolveProductVariants() {
1229
1333
  const lookup = this.#variantLookup;
1230
1334
  if (!lookup?.fetchProductVariants || this.#lastState === null) return;
@@ -1235,6 +1339,12 @@ var SmartCartElement = class _SmartCartElement extends Base {
1235
1339
  handles.add(handle);
1236
1340
  }
1237
1341
  }
1342
+ for (const variant of this.#upsellVariants?.values() ?? []) {
1343
+ const handle = variant.productHandle;
1344
+ if (handle !== null && handle.length > 0 && !this.#productVariants.has(handle) && !this.#productVariantsFetching.has(handle)) {
1345
+ handles.add(handle);
1346
+ }
1347
+ }
1238
1348
  if (handles.size === 0) return;
1239
1349
  await Promise.all(
1240
1350
  [...handles].map(async (handle) => {
@@ -1379,6 +1489,20 @@ var SmartCartElement = class _SmartCartElement extends Base {
1379
1489
  this.#addOnPending = false;
1380
1490
  }
1381
1491
  }
1492
+ /** Scroll the upsell track one card left/right (the arrows sit outside the track). */
1493
+ #onUpsellNav(btn) {
1494
+ const track = btn.closest(".ksc-up")?.querySelector(".ksc-up__track");
1495
+ if (!track) return;
1496
+ const direction = btn.hasAttribute("data-smart-cart-upsell-prev") ? -1 : 1;
1497
+ track.scrollBy({ left: direction * track.clientWidth, behavior: "smooth" });
1498
+ }
1499
+ /** Retarget an upsell card's add button when the shopper picks a different size — the select
1500
+ * carries no other intent (no re-render, no network). */
1501
+ #onUpsellVariantSelect(select) {
1502
+ if (!(select instanceof HTMLSelectElement)) return;
1503
+ const addBtn = select.closest(".ksc-up__card")?.querySelector("[data-smart-cart-upsell-add]");
1504
+ if (addBtn && select.value) addBtn.setAttribute("data-variant-id", select.value);
1505
+ }
1382
1506
  async #onUpsellAdd(btn) {
1383
1507
  if (!this.#adapter || this.#upsellPending) return;
1384
1508
  const variantId = btn.getAttribute("data-variant-id");
@@ -1554,7 +1678,8 @@ ${css8}`);
1554
1678
  config: cfg,
1555
1679
  state,
1556
1680
  formatMoney: formatMoney2,
1557
- ...this.#upsellVariants !== null && { variants: this.#upsellVariants }
1681
+ ...this.#upsellVariants !== null && { variants: this.#upsellVariants },
1682
+ ...this.#productVariants.size > 0 && { productVariants: this.#productVariants }
1558
1683
  });
1559
1684
  } catch (err) {
1560
1685
  console.error(`[kotao-smart-cart] widget render failed`, widget.key, err);
@@ -1612,33 +1737,34 @@ ${css8}`);
1612
1737
  const hasSwitcher = variants !== void 0 && variants.length > 1;
1613
1738
  const optName = l.options?.[0]?.name ?? "";
1614
1739
  const propLines = (l.properties ?? []).map(
1615
- (p) => `<span class="ksc-line__opt"><b>${escapeHtml(p.name)}:</b> ${escapeHtml(p.value)}</span>`
1740
+ (p) => `<span class="ksc-line__opt">${escapeHtml(p.name)}: <b>${escapeHtml(p.value)}</b></span>`
1616
1741
  );
1617
1742
  const optLines = hasSwitcher ? [] : opts.map(
1618
- (o) => o.name ? `<span class="ksc-line__opt"><b>${escapeHtml(o.name)}:</b> ${escapeHtml(o.value)}</span>` : `<span class="ksc-line__opt">${escapeHtml(o.value)}</span>`
1743
+ (o) => o.name ? `<span class="ksc-line__opt">${escapeHtml(o.name)}: <b>${escapeHtml(o.value)}</b></span>` : `<span class="ksc-line__opt">${escapeHtml(o.value)}</span>`
1619
1744
  );
1620
- const switcher = hasSwitcher ? `<div class="ksc-line__variant">${optName ? `<span><b>${escapeHtml(optName)}:</b></span>` : ""}<select class="ksc-line__select" data-smart-cart-variant data-line-id="${id}" data-qty="${l.quantity}" aria-label="${escapeHtml(optName || "Variant")}">${variants.map(
1745
+ const switcher = hasSwitcher ? `<div class="ksc-line__variant">${optName ? `<span>${escapeHtml(optName)}:</span>` : ""}<select class="ksc-line__select" data-smart-cart-variant data-line-id="${id}" data-qty="${l.quantity}" aria-label="${escapeHtml(optName || "Variant")}">${variants.map(
1621
1746
  (v) => `<option value="${escapeHtml(v.id)}"${v.title === l.variantTitle ? " selected" : ""}>${escapeHtml(v.title ?? "")}</option>`
1622
1747
  ).join("")}</select></div>` : "";
1623
1748
  const metaParts = [...propLines, ...optLines, ...switcher ? [switcher] : []];
1624
1749
  const meta = metaParts.length ? `<div class="ksc-line__meta">${metaParts.join("")}</div>` : "";
1625
- const compare = l.compareAtLinePrice && l.compareAtLinePrice.amount > l.linePrice.amount ? `<span class="ksc-line__compare">${escapeHtml(formatMoney2(l.compareAtLinePrice))}</span>` : "";
1626
- const save = l.compareAtLinePrice && l.compareAtLinePrice.amount > l.linePrice.amount ? `<span class="ksc-line__save">You save ${escapeHtml(
1627
- formatMoney2({
1628
- amount: l.compareAtLinePrice.amount - l.linePrice.amount,
1750
+ const hasLineDiscount = (l.discountAllocations?.length ?? 0) > 0;
1751
+ const struck = l.compareAtLinePrice && l.compareAtLinePrice.amount > l.linePrice.amount ? l.compareAtLinePrice : hasLineDiscount && l.originalLinePrice && l.originalLinePrice.amount > l.linePrice.amount ? l.originalLinePrice : void 0;
1752
+ const compare = struck ? `<span class="ksc-line__compare">${escapeHtml(formatMoney2(struck))}</span>` : "";
1753
+ const save = struck && struck.amount - l.linePrice.amount > 0 ? `<span class="ksc-line__save">${TAG_SVG}You save ${escapeHtml(
1754
+ formatMoneyCompact({
1755
+ amount: struck.amount - l.linePrice.amount,
1629
1756
  currency: l.linePrice.currency
1630
1757
  })
1631
1758
  )}</span>` : "";
1632
- const hasLineDiscount = (l.discountAllocations?.length ?? 0) > 0;
1633
- const displayPrice = hasLineDiscount ? l.originalLinePrice ?? l.linePrice : l.linePrice;
1759
+ const displayPrice = l.linePrice;
1634
1760
  const discountRows = hasLineDiscount ? l.discountAllocations.map((a) => {
1635
1761
  const code = escapeHtml(a.code);
1636
1762
  if (typeof a.quantity === "number" && a.quantity > 0) {
1637
1763
  const unit = Math.round((l.originalLinePrice ?? l.linePrice).amount / l.quantity);
1638
- const struck = escapeHtml(
1764
+ const struck2 = escapeHtml(
1639
1765
  formatMoney2({ amount: unit * a.quantity, currency: l.linePrice.currency })
1640
1766
  );
1641
- return `<div class="ksc-line__discount"><span>\u{1F381} ${a.quantity}\xD7 ${code} \u2014 <s>${struck}</s> FREE \xB7 You save 100%</span></div>`;
1767
+ return `<div class="ksc-line__discount"><span>\u{1F381} ${a.quantity}\xD7 ${code} \u2014 <s>${struck2}</s> FREE \xB7 You save 100%</span></div>`;
1642
1768
  }
1643
1769
  return `<div class="ksc-line__discount"><span>\u{1F3F7} ${code} \u2212${escapeHtml(formatMoney2(a.amount))}</span></div>`;
1644
1770
  }).join("") : "";
@@ -1646,24 +1772,24 @@ ${css8}`);
1646
1772
  ${image}
1647
1773
  <div class="ksc-line__body">
1648
1774
  <div class="ksc-line__top">
1649
- <span class="ksc-line__title">${escapeHtml(l.title)}</span>
1650
- ${compare}
1775
+ <div class="ksc-line__main">
1776
+ <span class="ksc-line__title">${escapeHtml(l.title)}</span>
1777
+ ${meta}
1778
+ ${discountRows}
1779
+ </div>
1780
+ <div class="ksc-line__prices">
1781
+ ${compare}
1782
+ <span class="ksc-line__price">${escapeHtml(formatMoney2(displayPrice))}</span>
1783
+ ${save}
1784
+ </div>
1651
1785
  </div>
1652
- ${meta}
1653
- ${discountRows}
1654
1786
  <div class="ksc-line__bottom">
1655
1787
  <div class="ksc-qty">
1656
1788
  <button type="button" data-smart-cart-qty-dec data-qty="${l.quantity}" aria-label="Decrease quantity">\u2212</button>
1657
1789
  <span class="ksc-qty__n">${l.quantity}</span>
1658
1790
  <button type="button" data-smart-cart-qty-inc data-qty="${l.quantity}" aria-label="Increase quantity">+</button>
1659
1791
  </div>
1660
- <div class="ksc-line__end">
1661
- <div class="ksc-line__prices">
1662
- <span class="ksc-line__price">${escapeHtml(formatMoney2(displayPrice))}</span>
1663
- ${save}
1664
- </div>
1665
- <button type="button" class="ksc-line__remove" data-smart-cart-line-remove aria-label="Remove item">${TRASH_SVG}</button>
1666
- </div>
1792
+ <button type="button" class="ksc-line__remove" data-smart-cart-line-remove aria-label="Remove item">${TRASH_SVG}</button>
1667
1793
  </div>
1668
1794
  </div>
1669
1795
  </div>`;
@@ -1683,7 +1809,7 @@ ${css8}`);
1683
1809
  }
1684
1810
  try {
1685
1811
  if (displayLines.length === 0) {
1686
- items.innerHTML = `<div class="ksc-empty">${BAG_SVG}<p data-empty>Your cart is empty.</p></div>`;
1812
+ items.innerHTML = `<div class="ksc-empty">${BAG_SVG}<p data-empty>Your cart is empty</p></div>`;
1687
1813
  checkoutWrap.innerHTML = "";
1688
1814
  if (continueWrap)
1689
1815
  continueWrap.innerHTML = `<button type="button" class="ksc-continue" data-smart-cart-close>Continue shopping</button>`;
@@ -1693,30 +1819,21 @@ ${css8}`);
1693
1819
  return;
1694
1820
  }
1695
1821
  items.innerHTML = displayLines.map((l) => this.#renderLine(l)).join("");
1696
- const namedAllocations = state.discountAllocations ?? [];
1697
- const cartDiscount = namedAllocations.length > 0 ? namedAllocations.reduce((sum, a) => sum + a.amount.amount, 0) : state.discountTotal?.amount ?? 0;
1698
- const displayTotal = {
1699
- amount: Math.max(0, state.subtotal.amount - cartDiscount),
1822
+ const finalSubtotal = {
1823
+ amount: state.lines.reduce((sum, l) => sum + l.linePrice.amount, 0),
1700
1824
  currency: state.subtotal.currency
1701
1825
  };
1702
- const rows = [];
1703
- if (namedAllocations.length > 0) {
1704
- for (const a of namedAllocations) {
1705
- rows.push(
1706
- `<div class="ksc-summary__row ksc-summary__row--discount" data-savings><span>${escapeHtml(a.code)}</span><span>\u2212${escapeHtml(formatMoney2(a.amount))}</span></div>`
1707
- );
1708
- }
1709
- } else if ((state.discountTotal?.amount ?? 0) > 0) {
1710
- rows.push(
1711
- `<div class="ksc-summary__row ksc-summary__row--discount" data-savings><span>Discounts</span><span>\u2212${escapeHtml(formatMoney2(state.discountTotal))}</span></div>`
1712
- );
1713
- }
1714
- rows.push(
1715
- `<div class="ksc-summary__row ksc-summary__row--total"><span>Subtotal</span><span>${escapeHtml(formatMoney2(state.subtotal))}</span></div>`
1716
- );
1826
+ const discountsTotal = state.discountTotal?.amount ?? state.discountAllocations?.reduce((sum, a) => sum + a.amount.amount, 0) ?? 0;
1827
+ const displayTotal = finalSubtotal;
1828
+ const rows = [
1829
+ ...discountsTotal > 0 ? [
1830
+ `<div class="ksc-summary__row ksc-summary__row--discount"><span>Discounts</span><span>-${escapeHtml(formatMoney2({ amount: discountsTotal, currency: state.subtotal.currency }))}</span></div>`
1831
+ ] : [],
1832
+ `<div class="ksc-summary__row ksc-summary__row--total"><span>Subtotal</span><span>${escapeHtml(formatMoney2(finalSubtotal))}</span></div>`
1833
+ ];
1717
1834
  const totalLabel = escapeHtml(formatMoney2(displayTotal));
1718
1835
  checkoutWrap.innerHTML = `<div class="ksc-summary">${rows.join("")}</div>
1719
- <a class="checkout" data-checkout href="${escapeHtml(state.checkoutUrl)}">Checkout${totalLabel ? ` \xB7 ${totalLabel}` : ""}</a>`;
1836
+ <a class="checkout" data-checkout href="${escapeHtml(state.checkoutUrl)}">Checkout${totalLabel ? ` \u2022 ${totalLabel}` : ""}</a>`;
1720
1837
  if (continueWrap)
1721
1838
  continueWrap.innerHTML = `<button type="button" class="ksc-continue" data-smart-cart-close>or continue shopping</button>`;
1722
1839
  } catch (err) {