[object Object]

← back to Designer Wallcoverings

DWTT qty stepper fix: repair +/- increment/decrement in custom.js + pristine data-order-* in product-form-content.liquid (dev theme 144105013299)

b77fe3704dc989bd4424a76e4bcd7331cf89cf5c · 2026-06-30 20:25:37 -0700 · Steve

Files touched

Diff

commit b77fe3704dc989bd4424a76e4bcd7331cf89cf5c
Author: Steve <steve@designerwallcoverings.com>
Date:   Tue Jun 30 20:25:37 2026 -0700

    DWTT qty stepper fix: repair +/- increment/decrement in custom.js + pristine data-order-* in product-form-content.liquid (dev theme 144105013299)
---
 .../custom-js-qty-2026-06-30/DEV_custom.js         | 582 +++++++++++++++
 .../custom-js-qty-2026-06-30/block_moq.html        |  21 +
 .../custom-js-qty-2026-06-30/block_std.html        |  21 +
 .../custom-js-qty-2026-06-30/clicktest.cjs         |  32 +
 .../custom-js-qty-2026-06-30/custom.ORIGINAL.js    | 511 +++++++++++++
 .../theme-fixes/custom-js-qty-2026-06-30/custom.js | 133 +++-
 .../custom.js.liquid.SOURCE                        | 511 +++++++++++++
 .../custom-js-qty-2026-06-30/firstclick.cjs        |  30 +
 .../custom-js-qty-2026-06-30/harness-run.cjs       |  28 +
 .../custom-js-qty-2026-06-30/harness.html          |  52 ++
 .../custom-js-qty-2026-06-30/hostcheck.cjs         |  14 +
 .../custom-js-qty-2026-06-30/hostcheck2.cjs        |  15 +
 .../theme-fixes/custom-js-qty-2026-06-30/iso.cjs   |  43 ++
 .../liquid/snippets__product-form-content.liquid   |   4 +
 .../custom-js-qty-2026-06-30/previewcookie.cjs     |  19 +
 .../custom-js-qty-2026-06-30/primarypreview.cjs    |  15 +
 .../product-form-content.ORIGINAL.liquid           | 800 +++++++++++++++++++++
 .../custom-js-qty-2026-06-30/pureplus.cjs          |  24 +
 .../custom-js-qty-2026-06-30/rawattr.cjs           |  15 +
 .../custom-js-qty-2026-06-30/srccheck.cjs          |  20 +
 .../custom-js-qty-2026-06-30/verify-dev-asset.cjs  |  86 +++
 .../custom-js-qty-2026-06-30/verify-logic.cjs      | 138 ++++
 22 files changed, 3083 insertions(+), 31 deletions(-)

diff --git a/shopify/theme-fixes/custom-js-qty-2026-06-30/DEV_custom.js b/shopify/theme-fixes/custom-js-qty-2026-06-30/DEV_custom.js
new file mode 100644
index 00000000..f86ee6f3
--- /dev/null
+++ b/shopify/theme-fixes/custom-js-qty-2026-06-30/DEV_custom.js
@@ -0,0 +1,582 @@
+/*========== 020 Custom js for quantity ===================*/
+
+function mm_refresh_quantity_increments() {  
+    jQuery("div.mm_quantity:not(.mm_buttons_added), td.mm_quantity:not(.mm_buttons_added)").each(function(a, b) {
+        var c = jQuery(b);
+        c.addClass("mm_buttons_added"), c.children().first().before('<input type="button" value="-" class="minus" />'), c.children().last().after('<input type="button" value="+" class="plus" />')
+    })
+}
+String.prototype.getDecimals || (String.prototype.getDecimals = function() {
+    var a = this,
+        b = ("" + a).match(/(?:\.(\d+))?(?:[eE]([+-]?\d+))?$/);
+    return b ? Math.max(0, (b[1] ? b[1].length : 0) - (b[2] ? +b[2] : 0)) : 0
+}), jQuery(document).ready(function() {
+    mm_refresh_quantity_increments()
+
+    // Custom popup contact form (moved from snippets/product.liquid)
+    if (jQuery('.custom-popup-contact-form').length > 0) {
+      jQuery('.custom-popup-contact-form').on('click', function (e) {
+        e.preventDefault();
+        jQuery('#custom-popup-overlay').show();
+      });
+    }
+
+    if (jQuery('#custom-popup-overlay .custom-close').length > 0) {
+      jQuery('#custom-popup-overlay .custom-close').on('click', function () {
+        jQuery('#custom-popup-overlay').hide();
+      });
+    }
+
+    if (jQuery('#custom-popup-overlay .success-message').length > 0) {
+      jQuery('.custom-contact-message').html('Thank you. We will respond to your message shortly.');
+    }
+
+    if (jQuery('#custom-popup-overlay .error-message').length > 0) {
+      jQuery('.custom-contact-message').html(jQuery('#custom-popup-overlay .error-message').html());
+    }
+}), jQuery(document).on("updated_wc_div", function() {
+    mm_refresh_quantity_increments()
+}), jQuery(document).on("click", ".plus, .minus", async function(event) {
+    event.preventDefault();
+
+    // ===== 2026-06-30 qty-stepper repair (vp-dw-commerce) =====
+    // Prior code read step/min from the .qty element's live `step`/`min` attributes,
+    // but the variant-change handler (below) poisons those to 1/1 when the default-
+    // selected variant is the Sample — so on MOQ products (DWTT/Anna French, step=2,
+    // min=2) the + button could only go 1->2 and the floor was 1 instead of 2, and
+    // the original math line was left half-disabled (no a.trigger('change')), so the
+    // visible input often failed to reflect the change. This rewrite reads the ORDER
+    // step/min from the pristine data-order-step / data-order-min attributes the liquid
+    // renders straight from v_prods_quantity_order_units / v_prods_quantity_order_min
+    // (never mutated by JS), clamps correctly, and mirrors the hidden hquantity.
+    var $btn = jQuery(this);
+    try {
+      var $wrap = $btn.closest(".mm_quantity");
+      var a = $wrap.find(".qty");
+      if (!a.length) return;
+
+      // STEP = pristine order-units (fallback to step attr, then 1). Must be > 0.
+      var step = parseFloat(a.attr("data-order-step"));
+      if (!isFinite(step) || step <= 0) step = parseFloat(a.attr("step"));
+      if (!isFinite(step) || step <= 0) step = 1;
+
+      // MIN = pristine order-min (fallback to min attr, then step). Must be >= step floor.
+      var min = parseFloat(a.attr("data-order-min"));
+      if (!isFinite(min) || min <= 0) min = parseFloat(a.attr("min"));
+      if (!isFinite(min) || min <= 0) min = step;
+
+      // MAX only if a real numeric cap is present.
+      var maxRaw = a.attr("max");
+      var max = parseFloat(maxRaw);
+      var hasMax = (maxRaw != null && maxRaw !== "" && isFinite(max));
+
+      var decimals = String(step).getDecimals();
+      var current = parseFloat(a.val());
+      if (!isFinite(current)) current = min;
+
+      var next;
+      if ($btn.is(".plus")) {
+        next = current + step;
+        if (hasMax && next > max) next = max;
+      } else {
+        next = current - step;
+        if (next < min) next = min;   // never below the minimum order quantity
+      }
+      // Snap to the on-step grid anchored at min (typing 3 on a step-2 min-2 product -> 2, then +2 -> 4).
+      var gridSteps = Math.round((next - min) / step);
+      if (gridSteps < 0) gridSteps = 0;
+      next = min + gridSteps * step;
+      if (hasMax && next > max) next = max;
+
+      var nextStr = next.toFixed(decimals);
+      a.val(nextStr);
+
+      // Mirror the visible qty into the hidden hquantity that is actually submitted to cart.
+      $wrap.find(".hquantity").attr('value', nextStr).val(nextStr);
+
+      if ($btn.hasClass('cart-page-quantity-value')) {
+        var variantId = $wrap.find(".hquantity").attr('variantid');
+        await updateItem(variantId, nextStr);
+        window.location.href = "/cart";
+      }
+    } catch (err) {
+      if (window.console && console.warn) console.warn('qty stepper error:', err);
+    }
+});
+// Snap a manually-typed qty to the nearest valid on-step value (>= min) on blur/change.
+jQuery(document).on('change blur', '.mm_quantity .qty', function () {
+  try {
+    var $wrap = jQuery(this).closest('.mm_quantity');
+    var a = jQuery(this);
+    var step = parseFloat(a.attr('data-order-step'));
+    if (!isFinite(step) || step <= 0) step = parseFloat(a.attr('step'));
+    if (!isFinite(step) || step <= 0) step = 1;
+    var min = parseFloat(a.attr('data-order-min'));
+    if (!isFinite(min) || min <= 0) min = parseFloat(a.attr('min'));
+    if (!isFinite(min) || min <= 0) min = step;
+    var v = parseFloat(a.val());
+    if (!isFinite(v) || v < min) v = min;
+    var gridSteps = Math.round((v - min) / step);
+    if (gridSteps < 0) gridSteps = 0;
+    var snapped = min + gridSteps * step;
+    var snappedStr = snapped.toFixed(String(step).getDecimals());
+    if (a.val() !== snappedStr) a.val(snappedStr);
+    $wrap.find('.hquantity').attr('value', snappedStr).val(snappedStr);
+  } catch (err) {
+    if (window.console && console.warn) console.warn('qty snap error:', err);
+  }
+});
+
+async function updateItem(variantId, quantity,  properties={}){
+  const result = await fetch("/cart/change.json", {
+    method:"post",
+    headers: {
+        "Content-type": "application/json",
+        "Accept": "application/json"
+    },
+    body: JSON.stringify({
+        id: variantId,
+        quantity: quantity,
+        properties: properties
+    })
+  })
+  return result.json();
+}
+
+function mm_validate(evt) {
+  var theEvent = evt || window.event;
+	theEvent.preventDefault()
+	/*
+  if (theEvent.type === 'paste') {
+      key = event.clipboardData.getData('text/plain');
+  } else {  
+      var key = theEvent.keyCode || theEvent.which;
+      key = String.fromCharCode(key);
+  }
+  var regex = /[0-9]|\./;
+	
+  if( !regex.test(key) ) {
+    theEvent.returnValue = false;
+    if(theEvent.preventDefault) theEvent.preventDefault();
+  }*/
+}
+
+
+/* for variant sample */
+jQuery(document).on('change', '.shopify-product-form .single-option-selector, .shopify-product-form .product-select', function () {
+  var $form = jQuery(this).closest('.shopify-product-form');
+
+  var isOptionSelector = jQuery(this).hasClass('single-option-selector');
+
+  var run = function () {
+    var $variantSelect = $form.find('.product-select');
+    var $selected;
+    var selectedSku = '';
+    var optionText = '';
+
+    if ($variantSelect.length && $variantSelect.is('select')) {
+      $selected = $variantSelect.find(':selected');
+      selectedSku = ($selected.attr('data-sku') || '').toString();
+      optionText = ($selected.text() || '').toString();
+    } else if ($variantSelect.length && $variantSelect.is('input')) {
+      // 1-variant products: .product-select is a hidden input
+      selectedSku = ($variantSelect.attr('data-sku') || '').toString();
+      optionText = ($variantSelect.attr('data-variant-title') || '').toString();
+    } else {
+      $selected = jQuery(this).find(':selected');
+      selectedSku = ($selected.attr('data-sku') || '').toString();
+      optionText = ($selected.text() || '').toString();
+    }
+
+    // If we can't determine anything, don't flip the UI.
+    if (!selectedSku && !optionText) return;
+
+    var isSkuSample = /-+sample$/i.test(selectedSku);
+    var isTextSample = /\bsample\b/i.test(optionText);
+    var isPureSample = /^sample(\b|[^a-z])/i.test(optionText.trim());
+    var isSample = isSkuSample || isTextSample;
+
+    var $qty = $form.find('.mm_quantity .qty');
+    var $qtyWrapper = $form.find('.mm_quantity');
+
+    // 2026-06-30: read the TRUE minimum-order-quantity + step from the pristine
+    // data-order-min / data-order-step attributes the liquid renders straight from
+    // the v_prods_quantity_order_min / v_prods_quantity_order_units metafields.
+    // The old code cached these from the live step/min attrs, which get poisoned to
+    // 1/1 by the Sample branch when Sample is the default-selected variant — that
+    // was why the roll variant came back showing step=1/min=1 instead of 2/2.
+    var originalMin = parseFloat($qty.attr('data-order-min'));
+    if (!isFinite(originalMin) || originalMin <= 0) originalMin = parseFloat($qty.data('min'));
+    if (!isFinite(originalMin) || originalMin <= 0) originalMin = parseFloat($qty.attr('min')) || 1;
+    $qty.data('min', originalMin);
+
+    var originalStep = parseFloat($qty.attr('data-order-step'));
+    if (!isFinite(originalStep) || originalStep <= 0) originalStep = parseFloat($qty.data('step'));
+    if (!isFinite(originalStep) || originalStep <= 0) originalStep = parseFloat($qty.attr('step')) || 1;
+    $qty.data('step', originalStep);
+
+    // Sample request buttons are always available (hide/show is variant-driven only)
+    $form.find('.dl-sample-btn, .dl-second-sample-btn').show();
+
+    if (isSample) {
+      $qty.val(1);
+      $qty.attr({ step: 1, min: 1, max: 1 });
+      $qtyWrapper.find('.plus, .minus').prop('disabled', true);
+      $qtyWrapper.find('.hquantity').attr('value', 1);
+
+      $form.find('.product-option-quantity-label').addClass('hidden').hide();
+      $qtyWrapper.addClass('hidden').hide();
+      $form.find('.add-to-cart').addClass('hidden').hide();
+
+      if (!isPureSample) {
+        $form.find('.dl-second-sample-btn').removeClass('unavailable');
+      } else if (!$form.find('.dl-second-sample-btn').hasClass('unavailable')) {
+        $form.find('.dl-second-sample-btn').addClass('unavailable');
+      }
+    } else {
+      $form.find('.product-option-quantity-label').removeClass('hidden').show();
+      $qtyWrapper.removeClass('hidden').show();
+      $form.find('.add-to-cart').removeClass('hidden').show();
+
+      var minValue = originalMin;
+      var currentValue = parseFloat($qty.val()) || 0;
+      if (currentValue < minValue) {
+        $qty.val(minValue);
+        $qtyWrapper.find('.hquantity').attr('value', minValue);
+      }
+      $qty.attr({ step: originalStep, min: minValue });
+      $qty.removeAttr('max');
+      $qtyWrapper.find('.plus, .minus').prop('disabled', false);
+    }
+
+    // Update the SKU display
+    var displaySku = selectedSku
+      .replace(/-+sample$/i, '')
+      .replace(/-+$/g, '');
+    jQuery('.product-sku-value').text(displaySku);
+
+    // Trade discount text
+    if (jQuery('#customer_tag').val() === 'trade') {
+      requestAnimationFrame(function () {
+        requestAnimationFrame(function () {
+          if (isSample) {
+            jQuery('.product-price-discount').text('Complimentary Sample');
+          } else {
+            // Use the currently displayed currency (symbol/code) from the UI, not hardcoded USD.
+            // Also support both "298,95" and "298.95" style decimals.
+            function dlParseDisplayedNumber(numStr) {
+              var s = (numStr || '').toString().replace(/\s/g, '');
+              var lastComma = s.lastIndexOf(',');
+              var lastDot = s.lastIndexOf('.');
+
+              // No separators: plain number
+              if (lastComma === -1 && lastDot === -1) {
+                var plain = parseFloat(s.replace(/[^\d.-]/g, ''));
+                return isFinite(plain) ? plain : NaN;
+              }
+
+              // Decide decimal separator by whichever comes last
+              var decimalSep = lastComma > lastDot ? ',' : '.';
+              var otherSep = decimalSep === ',' ? '.' : ',';
+
+              var parts = s.split(decimalSep);
+              if (!parts.length) return NaN;
+              var intPart = (parts[0] || '').replace(new RegExp('[\\' + otherSep + ']', 'g'), '').replace(/[^\d-]/g, '');
+              var fracPart = (parts[1] || '').replace(/[^\d]/g, '');
+              var normalized = intPart + (fracPart ? ('.' + fracPart) : '');
+              var val = parseFloat(normalized);
+              return isFinite(val) ? val : NaN;
+            }
+
+            function dlFormatLike(numStr, value) {
+              var s = (numStr || '').toString().replace(/\s/g, '');
+              var lastComma = s.lastIndexOf(',');
+              var lastDot = s.lastIndexOf('.');
+              var decimalSep = lastComma === -1 && lastDot === -1 ? '.' : (lastComma > lastDot ? ',' : '.');
+              var thousandsSep = decimalSep === ',' ? '.' : ',';
+
+              var hasThousands = false;
+              if (lastComma !== -1 || lastDot !== -1) {
+                var decIndex = decimalSep === ',' ? lastComma : lastDot;
+                var intStr = s.slice(0, decIndex);
+                hasThousands = intStr.indexOf(thousandsSep) !== -1;
+              }
+
+              var fixed = (Math.round(value * 100) / 100).toFixed(2);
+              var fixedParts = fixed.split('.');
+              var intPart = fixedParts[0];
+              var fracPart = fixedParts[1] || '00';
+
+              if (hasThousands) {
+                intPart = intPart.replace(/\B(?=(\d{3})+(?!\d))/g, thousandsSep);
+              }
+
+              return intPart + decimalSep + fracPart;
+            }
+
+            var priceText = (jQuery('.product-price-minimum').first().text() || '').trim();
+            var re = /[\d.,]+/;
+            var m = re.exec(priceText);
+            if (!m) return;
+
+            var numberStr = m[0];
+            var prefix = priceText.slice(0, m.index);
+            var suffix = priceText.slice(m.index + numberStr.length);
+
+            var priceValue = dlParseDisplayedNumber(numberStr);
+            if (!isFinite(priceValue)) return;
+
+            var discountedValue = priceValue * 0.85;
+            var formattedNumber = dlFormatLike(numberStr, discountedValue);
+            jQuery('.product-price-discount').text(prefix + formattedNumber + suffix);
+          }
+        });
+      });
+    }
+  }.bind(this);
+
+  var schedule = function () {
+    requestAnimationFrame(function () {
+      requestAnimationFrame(run);
+    });
+  };
+
+  if (isOptionSelector) {
+    setTimeout(schedule, 60);
+  } else {
+    schedule();
+  }
+});
+
+
+//hide ddl and show last variant
+
+if($('#has_display_variant').length>0 && $('#has_display_variant').val() == "no"){
+  $('.last_variant').hide(); 
+  if ( $( ".shopify-product-form .single-option-selector" ).length ) {
+    
+    $('.shopify-product-form .single-option-selector').hide();
+    
+    setTimeout(function(){ 
+      $('.product-options .select-wrapper').hide();
+    }, 500);
+    
+    $('.last_variant').show();
+
+    var single_opt_ind=0;
+    $('.shopify-product-form .single-option-selector option').each(function(index, option){
+     single_opt_ind=index;
+    });  
+
+    
+    /* DW deep-link honor (vp-dw-commerce 2026-06-24) */
+    // Honor an explicit ?variant=<id> deep-link: if the URL names a variant that
+    // matches one of the options, select THAT option instead of the computed default,
+    // so a link to the Sample renders $4.25 and a link to the Roll renders the roll price.
+    try {
+      var dlWanted = new URLSearchParams(window.location.search).get('variant');
+      if (dlWanted) {
+        $dlOpts.each(function(index, option){
+          var ov = (option.value || '').toString();
+          var dv = (option.getAttribute && option.getAttribute('data-variant-id') || '').toString();
+          if (ov === dlWanted || dv === dlWanted) { single_opt_ind = index; }
+        });
+      }
+    } catch (e) {}
+    /* /DW deep-link honor */
+    $(".shopify-product-form .single-option-selector").prop('selectedIndex', single_opt_ind).trigger('change');
+  }  
+}
+//--------------------------------
+
+// Prevent rapid multi-clicks from adding multiple samples before mini-cart UI updates
+function dlGetSampleBtns(variantId) {
+  return $(".dl-sample-btn[data-dlvid='" + variantId + "'], .dl-second-sample-btn[data-dlvid='" + variantId + "']");
+}
+function dlIsSampleLocked(variantId) {
+  return dlGetSampleBtns(variantId).first().attr("data-dl-locked") === "1";
+}
+function dlLockSampleBtns(variantId) {
+  var $btns = dlGetSampleBtns(variantId);
+  $btns.attr("data-dl-locked", "1").addClass("dl-click-locked").attr("aria-disabled", "true");
+  $btns.each(function() {
+    var $b = $(this);
+    if ($b.is("button, input")) $b.prop("disabled", true);
+  });
+}
+function dlUnlockSampleBtns(variantId) {
+  var $btns = dlGetSampleBtns(variantId);
+  $btns.removeAttr("data-dl-locked").removeClass("dl-click-locked").removeAttr("aria-disabled");
+  $btns.each(function() {
+    var $b = $(this);
+    if ($b.is("button, input")) $b.prop("disabled", false);
+  });
+}
+function dlUnlockSampleBtnsWhenInCart(variantId, maxMs) {
+  var start = Date.now();
+  var timer = setInterval(function() {
+    if ($(".mini-cart-item-wrapper [data-variant='" + variantId + "']").length > 0) {
+      clearInterval(timer);
+      dlUnlockSampleBtns(variantId);
+      return;
+    }
+    if (Date.now() - start >= (maxMs || 20000)) {
+      clearInterval(timer);
+      dlUnlockSampleBtns(variantId);
+    }
+  }, 250);
+}
+
+
+$(document).on("click", ".dl-sample-btn", function(e) {
+  e.preventDefault()
+  var $btn = $(this);
+  var $form = $btn.closest("form");
+  var id = $btn.attr("data-dlvid");
+  //check if the sample was added already
+  if($(".mini-cart-item-wrapper [data-variant='"+id+"']").length == 0) {
+    if (dlIsSampleLocked(id)) return;
+    dlLockSampleBtns(id);
+    dlUnlockSampleBtnsWhenInCart(id, 20000);
+
+    var product_id = $(this).attr("data-product-id");
+    var $productSelect = $form.find("#product-select-"+product_id);
+    if (!$productSelect.length) $productSelect = $form.find(".product-select");
+    var first_selected_id = $productSelect.is("select") ? $productSelect.find(":selected").val() : $productSelect.val();
+    $productSelect.val(id).trigger('change');
+    //
+    var $qtyInput = $form.find(".input-text.qty");
+    var default_quantity = $qtyInput.val();
+    $qtyInput.val(1);
+    $form.find(".add-to-cart.cowlendar-add-to-cart").trigger("click");
+
+    //go back to original selected status
+    
+    $qtyInput.val(default_quantity);
+    if (first_selected_id && first_selected_id !== id) {
+      $productSelect.val(first_selected_id).trigger('change');
+    }
+  } else {
+    //alert("1 sample only");
+    if($(".product-message").hasClass("success-message") || $(".mini-cart-item-wrapper [data-variant='"+id+"']").length > 0) {
+      $(".product-message").removeClass("success-message").addClass("error-message");
+      $(".product-message").text("1 Sample per SKU");
+    }    
+  }  
+});
+$(document).on("click", ".dl-second-sample-btn", function(e) {
+  e.preventDefault()
+  var $btn = $(this);
+  var $form = $btn.closest("form");
+  var id = $btn.attr("data-dlvid");
+  //check if the sample was added already
+  if($(".mini-cart-item-wrapper [data-variant='"+id+"']").length == 0) {
+    if (dlIsSampleLocked(id)) return;
+    dlLockSampleBtns(id);
+    dlUnlockSampleBtnsWhenInCart(id, 20000);
+
+    //
+    var $qtyInput = $form.find(".input-text.qty");
+    var default_quantity = $qtyInput.val();
+    $qtyInput.val(1);
+    $form.find(".add-to-cart.cowlendar-add-to-cart").trigger("click");
+
+    //go back to original selected status
+    
+    $qtyInput.val(default_quantity);
+  } else {
+    //alert("1 sample only");
+    if($(".product-message").hasClass("success-message") || $(".mini-cart-item-wrapper [data-variant='"+id+"']").length > 0) {
+      $(".product-message").removeClass("success-message").addClass("error-message");
+      $(".product-message").text("1 Sample per SKU");
+    }    
+  }  
+});
+
+//add the remove functionality on the mini-cart
+$(document).on("click", ".remove-item", function(e) {
+  e.preventDefault();
+    var variantId = $(this).closest('.mini-cart-item').attr("data-variant");
+    var quantity = parseInt($(this).closest(".mini-cart-item").find(".mini-cart-item-quantity").find("span").text());
+    var cart_num = parseInt($(".cart-count .cart-count-number").text());
+    var reminder_num = cart_num - quantity;
+    $.ajax({
+        type: "POST",
+        url: "/cart/change.js",
+        dataType: "json",
+        data: {
+          id: parseFloat(variantId),
+          quantity: 0,
+        },
+    }).then((data) => {
+        console.log("success");
+        $(this).closest('.mini-cart-item').remove();
+        dlUnlockSampleBtns(variantId);
+        $(".cart-count .cart-count-number").text(parseInt(reminder_num));
+        if(reminder_num == 0) {
+          $(".mini-cart").hide();
+        }
+    });
+});
+
+$(document).on("click", ".upsell-add-to-cart", function() {
+  var id = $(this).attr("data-item-id");
+  var num = parseInt($(this).attr("data-num"));
+  $.ajax({
+      type: 'POST',
+      url: '/cart/add.js',
+      dataType: 'json',
+      data: {
+          items: [
+              {
+                  quantity: num,
+                  id: id
+              }
+          ]
+      },
+      success: function (data){
+          location.href= "/cart";
+      }
+    });
+});
+
+/* Custom code for sascha */
+function showMiniCartIfReady() {
+  if (window.innerWidth <= 767) {
+    return;
+  }
+  var $miniCart = $(".mini-cart");
+  if ($miniCart.length && !$miniCart.hasClass("empty")) {
+    $(".mini-cart-wrapper").addClass("is-open");
+  }
+}
+
+function watchMiniCartUpdates() {
+  var target = document.querySelector(".mini-cart-item-wrapper");
+  if (!target || !window.MutationObserver) {
+    return;
+  }
+  var observer = new MutationObserver(function() {
+    showMiniCartIfReady();
+  });
+  observer.observe(target, { childList: true, subtree: true });
+}
+
+$(document).ready(function() {
+  watchMiniCartUpdates();
+});
+
+$(document).on("click", ".add-to-cart.cowlendar-add-to-cart", function() {
+  showMiniCartIfReady();
+});
+
+$(document).on("click", function(event) {
+  var $target = $(event.target);
+  if ($target.closest(".mini-cart, .mini-cart-wrapper").length === 0) {
+    $(".mini-cart-wrapper").removeClass("is-open");
+  }
+});
+
+$(document).on("mouseleave", ".mini-cart-wrapper", function() {
+  $(this).removeClass("is-open");
+});
\ No newline at end of file
diff --git a/shopify/theme-fixes/custom-js-qty-2026-06-30/block_moq.html b/shopify/theme-fixes/custom-js-qty-2026-06-30/block_moq.html
new file mode 100644
index 00000000..0eaf6bff
--- /dev/null
+++ b/shopify/theme-fixes/custom-js-qty-2026-06-30/block_moq.html
@@ -0,0 +1,21 @@
+<div class="mm_quantity mm_buttons_added product-option-row-4 ">
+              <input type="hidden" class="hquantity" name="hquantity" value="2">
+              <input type="button" value="-" class="minus">
+              <input
+                type="text"
+                step="2"
+                min="2"
+                data-order-step="2"
+                data-order-min="2"
+                max=""
+                name="quantity"
+                value="2"
+                title="Qty"
+                class="input-text qty text"
+                size="4"
+                pattern=""
+                inputmode=""
+                onkeypress="mm_validate(event)"
+              >
+              <input type="button" value="+" class="plus">
+            </div>
\ No newline at end of file
diff --git a/shopify/theme-fixes/custom-js-qty-2026-06-30/block_std.html b/shopify/theme-fixes/custom-js-qty-2026-06-30/block_std.html
new file mode 100644
index 00000000..fc26945a
--- /dev/null
+++ b/shopify/theme-fixes/custom-js-qty-2026-06-30/block_std.html
@@ -0,0 +1,21 @@
+<div class="mm_quantity mm_buttons_added product-option-row-4 ">
+              <input type="hidden" class="hquantity" name="hquantity" value="1">
+              <input type="button" value="-" class="minus">
+              <input
+                type="text"
+                step="1"
+                min="1"
+                data-order-step="1"
+                data-order-min="1"
+                max=""
+                name="quantity"
+                value="1"
+                title="Qty"
+                class="input-text qty text"
+                size="4"
+                pattern=""
+                inputmode=""
+                onkeypress="mm_validate(event)"
+              >
+              <input type="button" value="+" class="plus">
+            </div>
\ No newline at end of file
diff --git a/shopify/theme-fixes/custom-js-qty-2026-06-30/clicktest.cjs b/shopify/theme-fixes/custom-js-qty-2026-06-30/clicktest.cjs
new file mode 100644
index 00000000..cc54094b
--- /dev/null
+++ b/shopify/theme-fixes/custom-js-qty-2026-06-30/clicktest.cjs
@@ -0,0 +1,32 @@
+const { chromium } = require(require('path').join(process.env.HOME, 'Projects/Designer-Wallcoverings/node_modules/playwright'));
+const THEME=process.argv[2];
+const URL=`https://designer-laboratory-sandbox.myshopify.com/products/dwtt-80064-designer-wallcoverings-los-angeles?preview_theme_id=${THEME}`;
+(async()=>{
+  const b=await chromium.launch();const p=await(await b.newContext()).newPage();
+  const errs=[];p.on('pageerror',e=>errs.push('PE: '+e.message));
+  await p.goto(URL,{waitUntil:'domcontentloaded',timeout:60000});
+  await p.waitForSelector('.mm_quantity input[name="quantity"]',{state:'attached',timeout:30000});
+  await p.waitForTimeout(1800);
+  await p.evaluate(()=>{const s=document.querySelector('.product-select');if(s&&s.tagName==='SELECT'){const o=Array.from(s.options).find(x=>!/sample/i.test(x.textContent)&&!/-sample$/i.test(x.getAttribute('data-sku')||''))||s.options[0];s.value=o.value;s.dispatchEvent(new Event('change',{bubbles:true}));if(window.jQuery)window.jQuery(s).trigger('change');}});
+  await p.waitForTimeout(1500);
+  // Read the actual attributes the handler will see
+  const attrs=await p.evaluate(()=>{const q=document.querySelector('.mm_quantity .qty');return{dataOrderStep:q.getAttribute('data-order-step'),dataOrderMin:q.getAttribute('data-order-min'),step:q.getAttribute('step'),min:q.getAttribute('min'),val:q.val?undefined:q.value};});
+  console.log('ATTRS:',JSON.stringify(attrs));
+  // Count how many handlers are bound to .plus via jQuery events (document-delegated so can't easily count). Instead, dispatch a real jQuery click and read value.
+  const r=await p.evaluate(async()=>{
+    const $=window.jQuery;const btn=document.querySelector('.mm_quantity .plus');
+    const before=document.querySelector('.mm_quantity .qty').value;
+    $(btn).trigger('click');
+    await new Promise(r=>setTimeout(r,200));
+    const after=document.querySelector('.mm_quantity .qty').value;
+    const h=document.querySelector('.mm_quantity .hquantity').getAttribute('value');
+    return {before,after,h};
+  });
+  console.log('jQuery .plus click:',JSON.stringify(r));
+  // Now try native playwright click
+  await p.click('.mm_quantity .plus');await p.waitForTimeout(250);
+  const r2=await p.evaluate(()=>({val:document.querySelector('.mm_quantity .qty').value,h:document.querySelector('.mm_quantity .hquantity').getAttribute('value')}));
+  console.log('native click then:',JSON.stringify(r2));
+  console.log('errs:',JSON.stringify(errs));
+  await b.close();
+})().catch(e=>{console.error(e);process.exit(1)});
diff --git a/shopify/theme-fixes/custom-js-qty-2026-06-30/custom.ORIGINAL.js b/shopify/theme-fixes/custom-js-qty-2026-06-30/custom.ORIGINAL.js
new file mode 100644
index 00000000..64d9005e
--- /dev/null
+++ b/shopify/theme-fixes/custom-js-qty-2026-06-30/custom.ORIGINAL.js
@@ -0,0 +1,511 @@
+/*========== 020 Custom js for quantity ===================*/
+
+function mm_refresh_quantity_increments() {  
+    jQuery("div.mm_quantity:not(.mm_buttons_added), td.mm_quantity:not(.mm_buttons_added)").each(function(a, b) {
+        var c = jQuery(b);
+        c.addClass("mm_buttons_added"), c.children().first().before('<input type="button" value="-" class="minus" />'), c.children().last().after('<input type="button" value="+" class="plus" />')
+    })
+}
+String.prototype.getDecimals || (String.prototype.getDecimals = function() {
+    var a = this,
+        b = ("" + a).match(/(?:\.(\d+))?(?:[eE]([+-]?\d+))?$/);
+    return b ? Math.max(0, (b[1] ? b[1].length : 0) - (b[2] ? +b[2] : 0)) : 0
+}), jQuery(document).ready(function() {
+    mm_refresh_quantity_increments()
+
+    // Custom popup contact form (moved from snippets/product.liquid)
+    if (jQuery('.custom-popup-contact-form').length > 0) {
+      jQuery('.custom-popup-contact-form').on('click', function (e) {
+        e.preventDefault();
+        jQuery('#custom-popup-overlay').show();
+      });
+    }
+
+    if (jQuery('#custom-popup-overlay .custom-close').length > 0) {
+      jQuery('#custom-popup-overlay .custom-close').on('click', function () {
+        jQuery('#custom-popup-overlay').hide();
+      });
+    }
+
+    if (jQuery('#custom-popup-overlay .success-message').length > 0) {
+      jQuery('.custom-contact-message').html('Thank you. We will respond to your message shortly.');
+    }
+
+    if (jQuery('#custom-popup-overlay .error-message').length > 0) {
+      jQuery('.custom-contact-message').html(jQuery('#custom-popup-overlay .error-message').html());
+    }
+}), jQuery(document).on("updated_wc_div", function() {
+    mm_refresh_quantity_increments()
+}), jQuery(document).on("click", ".plus, .minus", async function(event) {
+    event.preventDefault();
+    
+    var a = jQuery(this).closest(".mm_quantity").find(".qty"),
+        b = parseFloat(a.val()),
+        c = parseFloat(a.attr("max")),
+        d = parseFloat(a.attr("min")),
+        e = a.attr("step");
+    //b && "" !== b && "NaN" !== b || (b = 0), "" !== c && "NaN" !== c || (c = ""), "" !== d && "NaN" !== d || (d = 0), "any" !== e && "" !== e && void 0 !== e && "NaN" !== parseFloat(e) || (e = 1), jQuery(this).is(".plus") ? c && b >= c ? a.val(c) : a.val((b + parseFloat(e)).toFixed(e.getDecimals())) : d && b <= d ? a.val(d) : b > 0 && a.val((b - parseFloat(e)).toFixed(e.getDecimals())), a.trigger("change")
+     b && "" !== b && "NaN" !== b || (b = 0), "" !== c && "NaN" !== c || (c = ""), "" !== d && "NaN" !== d || (d = 0), "any" !== e && "" !== e && void 0 !== e && "NaN" !== parseFloat(e) || (e = 1), jQuery(this).is(".plus") ? c && b >= c ? a.val(c) : a.val((b + parseFloat(e)).toFixed(e.getDecimals())) : d && b <= d ? a.val(d) : b > 0 && a.val((b - parseFloat(e)).toFixed(e.getDecimals()))
+     
+     //alert();
+     const quantity=jQuery(this).closest(".mm_quantity").find(".qty").val();
+     jQuery(this).closest(".mm_quantity").find(".hquantity").attr('value',quantity);
+
+     if($(this).hasClass('cart-page-quantity-value')){
+      const variantId=jQuery(this).closest(".mm_quantity").find(".hquantity").attr('variantid');
+      console.log('-----', variantId)
+      await updateItem(variantId, quantity)
+      window.location.href="/cart"
+     }
+    
+     
+});
+
+async function updateItem(variantId, quantity,  properties={}){
+  const result = await fetch("/cart/change.json", {
+    method:"post",
+    headers: {
+        "Content-type": "application/json",
+        "Accept": "application/json"
+    },
+    body: JSON.stringify({
+        id: variantId,
+        quantity: quantity,
+        properties: properties
+    })
+  })
+  return result.json();
+}
+
+function mm_validate(evt) {
+  var theEvent = evt || window.event;
+	theEvent.preventDefault()
+	/*
+  if (theEvent.type === 'paste') {
+      key = event.clipboardData.getData('text/plain');
+  } else {  
+      var key = theEvent.keyCode || theEvent.which;
+      key = String.fromCharCode(key);
+  }
+  var regex = /[0-9]|\./;
+	
+  if( !regex.test(key) ) {
+    theEvent.returnValue = false;
+    if(theEvent.preventDefault) theEvent.preventDefault();
+  }*/
+}
+
+
+/* for variant sample */
+jQuery(document).on('change', '.shopify-product-form .single-option-selector, .shopify-product-form .product-select', function () {
+  var $form = jQuery(this).closest('.shopify-product-form');
+
+  var isOptionSelector = jQuery(this).hasClass('single-option-selector');
+
+  var run = function () {
+    var $variantSelect = $form.find('.product-select');
+    var $selected;
+    var selectedSku = '';
+    var optionText = '';
+
+    if ($variantSelect.length && $variantSelect.is('select')) {
+      $selected = $variantSelect.find(':selected');
+      selectedSku = ($selected.attr('data-sku') || '').toString();
+      optionText = ($selected.text() || '').toString();
+    } else if ($variantSelect.length && $variantSelect.is('input')) {
+      // 1-variant products: .product-select is a hidden input
+      selectedSku = ($variantSelect.attr('data-sku') || '').toString();
+      optionText = ($variantSelect.attr('data-variant-title') || '').toString();
+    } else {
+      $selected = jQuery(this).find(':selected');
+      selectedSku = ($selected.attr('data-sku') || '').toString();
+      optionText = ($selected.text() || '').toString();
+    }
+
+    // If we can't determine anything, don't flip the UI.
+    if (!selectedSku && !optionText) return;
+
+    var isSkuSample = /-+sample$/i.test(selectedSku);
+    var isTextSample = /\bsample\b/i.test(optionText);
+    var isPureSample = /^sample(\b|[^a-z])/i.test(optionText.trim());
+    var isSample = isSkuSample || isTextSample;
+
+    var $qty = $form.find('.mm_quantity .qty');
+    var $qtyWrapper = $form.find('.mm_quantity');
+    var originalMin = $qty.data('min');
+    var originalStep = $qty.data('step');
+
+    if (!originalMin) {
+      originalMin = parseFloat($qty.attr('min')) || 1;
+      $qty.data('min', originalMin);
+    }
+    if (!originalStep) {
+      originalStep = parseFloat($qty.attr('step')) || 1;
+      $qty.data('step', originalStep);
+    }
+
+    // Sample request buttons are always available (hide/show is variant-driven only)
+    $form.find('.dl-sample-btn, .dl-second-sample-btn').show();
+
+    if (isSample) {
+      $qty.val(1);
+      $qty.attr({ step: 1, min: 1, max: 1 });
+      $qtyWrapper.find('.plus, .minus').prop('disabled', true);
+      $qtyWrapper.find('.hquantity').attr('value', 1);
+
+      $form.find('.product-option-quantity-label').addClass('hidden').hide();
+      $qtyWrapper.addClass('hidden').hide();
+      $form.find('.add-to-cart').addClass('hidden').hide();
+
+      if (!isPureSample) {
+        $form.find('.dl-second-sample-btn').removeClass('unavailable');
+      } else if (!$form.find('.dl-second-sample-btn').hasClass('unavailable')) {
+        $form.find('.dl-second-sample-btn').addClass('unavailable');
+      }
+    } else {
+      $form.find('.product-option-quantity-label').removeClass('hidden').show();
+      $qtyWrapper.removeClass('hidden').show();
+      $form.find('.add-to-cart').removeClass('hidden').show();
+
+      var minValue = originalMin;
+      var currentValue = parseFloat($qty.val()) || 0;
+      if (currentValue < minValue) {
+        $qty.val(minValue);
+        $qtyWrapper.find('.hquantity').attr('value', minValue);
+      }
+      $qty.attr({ step: originalStep, min: minValue });
+      $qty.removeAttr('max');
+      $qtyWrapper.find('.plus, .minus').prop('disabled', false);
+    }
+
+    // Update the SKU display
+    var displaySku = selectedSku
+      .replace(/-+sample$/i, '')
+      .replace(/-+$/g, '');
+    jQuery('.product-sku-value').text(displaySku);
+
+    // Trade discount text
+    if (jQuery('#customer_tag').val() === 'trade') {
+      requestAnimationFrame(function () {
+        requestAnimationFrame(function () {
+          if (isSample) {
+            jQuery('.product-price-discount').text('Complimentary Sample');
+          } else {
+            // Use the currently displayed currency (symbol/code) from the UI, not hardcoded USD.
+            // Also support both "298,95" and "298.95" style decimals.
+            function dlParseDisplayedNumber(numStr) {
+              var s = (numStr || '').toString().replace(/\s/g, '');
+              var lastComma = s.lastIndexOf(',');
+              var lastDot = s.lastIndexOf('.');
+
+              // No separators: plain number
+              if (lastComma === -1 && lastDot === -1) {
+                var plain = parseFloat(s.replace(/[^\d.-]/g, ''));
+                return isFinite(plain) ? plain : NaN;
+              }
+
+              // Decide decimal separator by whichever comes last
+              var decimalSep = lastComma > lastDot ? ',' : '.';
+              var otherSep = decimalSep === ',' ? '.' : ',';
+
+              var parts = s.split(decimalSep);
+              if (!parts.length) return NaN;
+              var intPart = (parts[0] || '').replace(new RegExp('[\\' + otherSep + ']', 'g'), '').replace(/[^\d-]/g, '');
+              var fracPart = (parts[1] || '').replace(/[^\d]/g, '');
+              var normalized = intPart + (fracPart ? ('.' + fracPart) : '');
+              var val = parseFloat(normalized);
+              return isFinite(val) ? val : NaN;
+            }
+
+            function dlFormatLike(numStr, value) {
+              var s = (numStr || '').toString().replace(/\s/g, '');
+              var lastComma = s.lastIndexOf(',');
+              var lastDot = s.lastIndexOf('.');
+              var decimalSep = lastComma === -1 && lastDot === -1 ? '.' : (lastComma > lastDot ? ',' : '.');
+              var thousandsSep = decimalSep === ',' ? '.' : ',';
+
+              var hasThousands = false;
+              if (lastComma !== -1 || lastDot !== -1) {
+                var decIndex = decimalSep === ',' ? lastComma : lastDot;
+                var intStr = s.slice(0, decIndex);
+                hasThousands = intStr.indexOf(thousandsSep) !== -1;
+              }
+
+              var fixed = (Math.round(value * 100) / 100).toFixed(2);
+              var fixedParts = fixed.split('.');
+              var intPart = fixedParts[0];
+              var fracPart = fixedParts[1] || '00';
+
+              if (hasThousands) {
+                intPart = intPart.replace(/\B(?=(\d{3})+(?!\d))/g, thousandsSep);
+              }
+
+              return intPart + decimalSep + fracPart;
+            }
+
+            var priceText = (jQuery('.product-price-minimum').first().text() || '').trim();
+            var re = /[\d.,]+/;
+            var m = re.exec(priceText);
+            if (!m) return;
+
+            var numberStr = m[0];
+            var prefix = priceText.slice(0, m.index);
+            var suffix = priceText.slice(m.index + numberStr.length);
+
+            var priceValue = dlParseDisplayedNumber(numberStr);
+            if (!isFinite(priceValue)) return;
+
+            var discountedValue = priceValue * 0.85;
+            var formattedNumber = dlFormatLike(numberStr, discountedValue);
+            jQuery('.product-price-discount').text(prefix + formattedNumber + suffix);
+          }
+        });
+      });
+    }
+  }.bind(this);
+
+  var schedule = function () {
+    requestAnimationFrame(function () {
+      requestAnimationFrame(run);
+    });
+  };
+
+  if (isOptionSelector) {
+    setTimeout(schedule, 60);
+  } else {
+    schedule();
+  }
+});
+
+
+//hide ddl and show last variant
+
+if($('#has_display_variant').length>0 && $('#has_display_variant').val() == "no"){
+  $('.last_variant').hide(); 
+  if ( $( ".shopify-product-form .single-option-selector" ).length ) {
+    
+    $('.shopify-product-form .single-option-selector').hide();
+    
+    setTimeout(function(){ 
+      $('.product-options .select-wrapper').hide();
+    }, 500);
+    
+    $('.last_variant').show();
+
+    var single_opt_ind=0;
+    $('.shopify-product-form .single-option-selector option').each(function(index, option){
+     single_opt_ind=index;
+    });  
+
+    
+    /* DW deep-link honor (vp-dw-commerce 2026-06-24) */
+    // Honor an explicit ?variant=<id> deep-link: if the URL names a variant that
+    // matches one of the options, select THAT option instead of the computed default,
+    // so a link to the Sample renders $4.25 and a link to the Roll renders the roll price.
+    try {
+      var dlWanted = new URLSearchParams(window.location.search).get('variant');
+      if (dlWanted) {
+        $dlOpts.each(function(index, option){
+          var ov = (option.value || '').toString();
+          var dv = (option.getAttribute && option.getAttribute('data-variant-id') || '').toString();
+          if (ov === dlWanted || dv === dlWanted) { single_opt_ind = index; }
+        });
+      }
+    } catch (e) {}
+    /* /DW deep-link honor */
+    $(".shopify-product-form .single-option-selector").prop('selectedIndex', single_opt_ind).trigger('change');
+  }  
+}
+//--------------------------------
+
+// Prevent rapid multi-clicks from adding multiple samples before mini-cart UI updates
+function dlGetSampleBtns(variantId) {
+  return $(".dl-sample-btn[data-dlvid='" + variantId + "'], .dl-second-sample-btn[data-dlvid='" + variantId + "']");
+}
+function dlIsSampleLocked(variantId) {
+  return dlGetSampleBtns(variantId).first().attr("data-dl-locked") === "1";
+}
+function dlLockSampleBtns(variantId) {
+  var $btns = dlGetSampleBtns(variantId);
+  $btns.attr("data-dl-locked", "1").addClass("dl-click-locked").attr("aria-disabled", "true");
+  $btns.each(function() {
+    var $b = $(this);
+    if ($b.is("button, input")) $b.prop("disabled", true);
+  });
+}
+function dlUnlockSampleBtns(variantId) {
+  var $btns = dlGetSampleBtns(variantId);
+  $btns.removeAttr("data-dl-locked").removeClass("dl-click-locked").removeAttr("aria-disabled");
+  $btns.each(function() {
+    var $b = $(this);
+    if ($b.is("button, input")) $b.prop("disabled", false);
+  });
+}
+function dlUnlockSampleBtnsWhenInCart(variantId, maxMs) {
+  var start = Date.now();
+  var timer = setInterval(function() {
+    if ($(".mini-cart-item-wrapper [data-variant='" + variantId + "']").length > 0) {
+      clearInterval(timer);
+      dlUnlockSampleBtns(variantId);
+      return;
+    }
+    if (Date.now() - start >= (maxMs || 20000)) {
+      clearInterval(timer);
+      dlUnlockSampleBtns(variantId);
+    }
+  }, 250);
+}
+
+
+$(document).on("click", ".dl-sample-btn", function(e) {
+  e.preventDefault()
+  var $btn = $(this);
+  var $form = $btn.closest("form");
+  var id = $btn.attr("data-dlvid");
+  //check if the sample was added already
+  if($(".mini-cart-item-wrapper [data-variant='"+id+"']").length == 0) {
+    if (dlIsSampleLocked(id)) return;
+    dlLockSampleBtns(id);
+    dlUnlockSampleBtnsWhenInCart(id, 20000);
+
+    var product_id = $(this).attr("data-product-id");
+    var $productSelect = $form.find("#product-select-"+product_id);
+    if (!$productSelect.length) $productSelect = $form.find(".product-select");
+    var first_selected_id = $productSelect.is("select") ? $productSelect.find(":selected").val() : $productSelect.val();
+    $productSelect.val(id).trigger('change');
+    //
+    var $qtyInput = $form.find(".input-text.qty");
+    var default_quantity = $qtyInput.val();
+    $qtyInput.val(1);
+    $form.find(".add-to-cart.cowlendar-add-to-cart").trigger("click");
+
+    //go back to original selected status
+    
+    $qtyInput.val(default_quantity);
+    if (first_selected_id && first_selected_id !== id) {
+      $productSelect.val(first_selected_id).trigger('change');
+    }
+  } else {
+    //alert("1 sample only");
+    if($(".product-message").hasClass("success-message") || $(".mini-cart-item-wrapper [data-variant='"+id+"']").length > 0) {
+      $(".product-message").removeClass("success-message").addClass("error-message");
+      $(".product-message").text("1 Sample per SKU");
+    }    
+  }  
+});
+$(document).on("click", ".dl-second-sample-btn", function(e) {
+  e.preventDefault()
+  var $btn = $(this);
+  var $form = $btn.closest("form");
+  var id = $btn.attr("data-dlvid");
+  //check if the sample was added already
+  if($(".mini-cart-item-wrapper [data-variant='"+id+"']").length == 0) {
+    if (dlIsSampleLocked(id)) return;
+    dlLockSampleBtns(id);
+    dlUnlockSampleBtnsWhenInCart(id, 20000);
+
+    //
+    var $qtyInput = $form.find(".input-text.qty");
+    var default_quantity = $qtyInput.val();
+    $qtyInput.val(1);
+    $form.find(".add-to-cart.cowlendar-add-to-cart").trigger("click");
+
+    //go back to original selected status
+    
+    $qtyInput.val(default_quantity);
+  } else {
+    //alert("1 sample only");
+    if($(".product-message").hasClass("success-message") || $(".mini-cart-item-wrapper [data-variant='"+id+"']").length > 0) {
+      $(".product-message").removeClass("success-message").addClass("error-message");
+      $(".product-message").text("1 Sample per SKU");
+    }    
+  }  
+});
+
+//add the remove functionality on the mini-cart
+$(document).on("click", ".remove-item", function(e) {
+  e.preventDefault();
+    var variantId = $(this).closest('.mini-cart-item').attr("data-variant");
+    var quantity = parseInt($(this).closest(".mini-cart-item").find(".mini-cart-item-quantity").find("span").text());
+    var cart_num = parseInt($(".cart-count .cart-count-number").text());
+    var reminder_num = cart_num - quantity;
+    $.ajax({
+        type: "POST",
+        url: "/cart/change.js",
+        dataType: "json",
+        data: {
+          id: parseFloat(variantId),
+          quantity: 0,
+        },
+    }).then((data) => {
+        console.log("success");
+        $(this).closest('.mini-cart-item').remove();
+        dlUnlockSampleBtns(variantId);
+        $(".cart-count .cart-count-number").text(parseInt(reminder_num));
+        if(reminder_num == 0) {
+          $(".mini-cart").hide();
+        }
+    });
+});
+
+$(document).on("click", ".upsell-add-to-cart", function() {
+  var id = $(this).attr("data-item-id");
+  var num = parseInt($(this).attr("data-num"));
+  $.ajax({
+      type: 'POST',
+      url: '/cart/add.js',
+      dataType: 'json',
+      data: {
+          items: [
+              {
+                  quantity: num,
+                  id: id
+              }
+          ]
+      },
+      success: function (data){
+          location.href= "/cart";
+      }
+    });
+});
+
+/* Custom code for sascha */
+function showMiniCartIfReady() {
+  if (window.innerWidth <= 767) {
+    return;
+  }
+  var $miniCart = $(".mini-cart");
+  if ($miniCart.length && !$miniCart.hasClass("empty")) {
+    $(".mini-cart-wrapper").addClass("is-open");
+  }
+}
+
+function watchMiniCartUpdates() {
+  var target = document.querySelector(".mini-cart-item-wrapper");
+  if (!target || !window.MutationObserver) {
+    return;
+  }
+  var observer = new MutationObserver(function() {
+    showMiniCartIfReady();
+  });
+  observer.observe(target, { childList: true, subtree: true });
+}
+
+$(document).ready(function() {
+  watchMiniCartUpdates();
+});
+
+$(document).on("click", ".add-to-cart.cowlendar-add-to-cart", function() {
+  showMiniCartIfReady();
+});
+
+$(document).on("click", function(event) {
+  var $target = $(event.target);
+  if ($target.closest(".mini-cart, .mini-cart-wrapper").length === 0) {
+    $(".mini-cart-wrapper").removeClass("is-open");
+  }
+});
+
+$(document).on("mouseleave", ".mini-cart-wrapper", function() {
+  $(this).removeClass("is-open");
+});
\ No newline at end of file
diff --git a/shopify/theme-fixes/custom-js-qty-2026-06-30/custom.js b/shopify/theme-fixes/custom-js-qty-2026-06-30/custom.js
index 64d9005e..f86ee6f3 100644
--- a/shopify/theme-fixes/custom-js-qty-2026-06-30/custom.js
+++ b/shopify/theme-fixes/custom-js-qty-2026-06-30/custom.js
@@ -38,27 +38,93 @@ String.prototype.getDecimals || (String.prototype.getDecimals = function() {
     mm_refresh_quantity_increments()
 }), jQuery(document).on("click", ".plus, .minus", async function(event) {
     event.preventDefault();
-    
-    var a = jQuery(this).closest(".mm_quantity").find(".qty"),
-        b = parseFloat(a.val()),
-        c = parseFloat(a.attr("max")),
-        d = parseFloat(a.attr("min")),
-        e = a.attr("step");
-    //b && "" !== b && "NaN" !== b || (b = 0), "" !== c && "NaN" !== c || (c = ""), "" !== d && "NaN" !== d || (d = 0), "any" !== e && "" !== e && void 0 !== e && "NaN" !== parseFloat(e) || (e = 1), jQuery(this).is(".plus") ? c && b >= c ? a.val(c) : a.val((b + parseFloat(e)).toFixed(e.getDecimals())) : d && b <= d ? a.val(d) : b > 0 && a.val((b - parseFloat(e)).toFixed(e.getDecimals())), a.trigger("change")
-     b && "" !== b && "NaN" !== b || (b = 0), "" !== c && "NaN" !== c || (c = ""), "" !== d && "NaN" !== d || (d = 0), "any" !== e && "" !== e && void 0 !== e && "NaN" !== parseFloat(e) || (e = 1), jQuery(this).is(".plus") ? c && b >= c ? a.val(c) : a.val((b + parseFloat(e)).toFixed(e.getDecimals())) : d && b <= d ? a.val(d) : b > 0 && a.val((b - parseFloat(e)).toFixed(e.getDecimals()))
-     
-     //alert();
-     const quantity=jQuery(this).closest(".mm_quantity").find(".qty").val();
-     jQuery(this).closest(".mm_quantity").find(".hquantity").attr('value',quantity);
-
-     if($(this).hasClass('cart-page-quantity-value')){
-      const variantId=jQuery(this).closest(".mm_quantity").find(".hquantity").attr('variantid');
-      console.log('-----', variantId)
-      await updateItem(variantId, quantity)
-      window.location.href="/cart"
-     }
-    
-     
+
+    // ===== 2026-06-30 qty-stepper repair (vp-dw-commerce) =====
+    // Prior code read step/min from the .qty element's live `step`/`min` attributes,
+    // but the variant-change handler (below) poisons those to 1/1 when the default-
+    // selected variant is the Sample — so on MOQ products (DWTT/Anna French, step=2,
+    // min=2) the + button could only go 1->2 and the floor was 1 instead of 2, and
+    // the original math line was left half-disabled (no a.trigger('change')), so the
+    // visible input often failed to reflect the change. This rewrite reads the ORDER
+    // step/min from the pristine data-order-step / data-order-min attributes the liquid
+    // renders straight from v_prods_quantity_order_units / v_prods_quantity_order_min
+    // (never mutated by JS), clamps correctly, and mirrors the hidden hquantity.
+    var $btn = jQuery(this);
+    try {
+      var $wrap = $btn.closest(".mm_quantity");
+      var a = $wrap.find(".qty");
+      if (!a.length) return;
+
+      // STEP = pristine order-units (fallback to step attr, then 1). Must be > 0.
+      var step = parseFloat(a.attr("data-order-step"));
+      if (!isFinite(step) || step <= 0) step = parseFloat(a.attr("step"));
+      if (!isFinite(step) || step <= 0) step = 1;
+
+      // MIN = pristine order-min (fallback to min attr, then step). Must be >= step floor.
+      var min = parseFloat(a.attr("data-order-min"));
+      if (!isFinite(min) || min <= 0) min = parseFloat(a.attr("min"));
+      if (!isFinite(min) || min <= 0) min = step;
+
+      // MAX only if a real numeric cap is present.
+      var maxRaw = a.attr("max");
+      var max = parseFloat(maxRaw);
+      var hasMax = (maxRaw != null && maxRaw !== "" && isFinite(max));
+
+      var decimals = String(step).getDecimals();
+      var current = parseFloat(a.val());
+      if (!isFinite(current)) current = min;
+
+      var next;
+      if ($btn.is(".plus")) {
+        next = current + step;
+        if (hasMax && next > max) next = max;
+      } else {
+        next = current - step;
+        if (next < min) next = min;   // never below the minimum order quantity
+      }
+      // Snap to the on-step grid anchored at min (typing 3 on a step-2 min-2 product -> 2, then +2 -> 4).
+      var gridSteps = Math.round((next - min) / step);
+      if (gridSteps < 0) gridSteps = 0;
+      next = min + gridSteps * step;
+      if (hasMax && next > max) next = max;
+
+      var nextStr = next.toFixed(decimals);
+      a.val(nextStr);
+
+      // Mirror the visible qty into the hidden hquantity that is actually submitted to cart.
+      $wrap.find(".hquantity").attr('value', nextStr).val(nextStr);
+
+      if ($btn.hasClass('cart-page-quantity-value')) {
+        var variantId = $wrap.find(".hquantity").attr('variantid');
+        await updateItem(variantId, nextStr);
+        window.location.href = "/cart";
+      }
+    } catch (err) {
+      if (window.console && console.warn) console.warn('qty stepper error:', err);
+    }
+});
+// Snap a manually-typed qty to the nearest valid on-step value (>= min) on blur/change.
+jQuery(document).on('change blur', '.mm_quantity .qty', function () {
+  try {
+    var $wrap = jQuery(this).closest('.mm_quantity');
+    var a = jQuery(this);
+    var step = parseFloat(a.attr('data-order-step'));
+    if (!isFinite(step) || step <= 0) step = parseFloat(a.attr('step'));
+    if (!isFinite(step) || step <= 0) step = 1;
+    var min = parseFloat(a.attr('data-order-min'));
+    if (!isFinite(min) || min <= 0) min = parseFloat(a.attr('min'));
+    if (!isFinite(min) || min <= 0) min = step;
+    var v = parseFloat(a.val());
+    if (!isFinite(v) || v < min) v = min;
+    var gridSteps = Math.round((v - min) / step);
+    if (gridSteps < 0) gridSteps = 0;
+    var snapped = min + gridSteps * step;
+    var snappedStr = snapped.toFixed(String(step).getDecimals());
+    if (a.val() !== snappedStr) a.val(snappedStr);
+    $wrap.find('.hquantity').attr('value', snappedStr).val(snappedStr);
+  } catch (err) {
+    if (window.console && console.warn) console.warn('qty snap error:', err);
+  }
 });
 
 async function updateItem(variantId, quantity,  properties={}){
@@ -132,17 +198,22 @@ jQuery(document).on('change', '.shopify-product-form .single-option-selector, .s
 
     var $qty = $form.find('.mm_quantity .qty');
     var $qtyWrapper = $form.find('.mm_quantity');
-    var originalMin = $qty.data('min');
-    var originalStep = $qty.data('step');
 
-    if (!originalMin) {
-      originalMin = parseFloat($qty.attr('min')) || 1;
-      $qty.data('min', originalMin);
-    }
-    if (!originalStep) {
-      originalStep = parseFloat($qty.attr('step')) || 1;
-      $qty.data('step', originalStep);
-    }
+    // 2026-06-30: read the TRUE minimum-order-quantity + step from the pristine
+    // data-order-min / data-order-step attributes the liquid renders straight from
+    // the v_prods_quantity_order_min / v_prods_quantity_order_units metafields.
+    // The old code cached these from the live step/min attrs, which get poisoned to
+    // 1/1 by the Sample branch when Sample is the default-selected variant — that
+    // was why the roll variant came back showing step=1/min=1 instead of 2/2.
+    var originalMin = parseFloat($qty.attr('data-order-min'));
+    if (!isFinite(originalMin) || originalMin <= 0) originalMin = parseFloat($qty.data('min'));
+    if (!isFinite(originalMin) || originalMin <= 0) originalMin = parseFloat($qty.attr('min')) || 1;
+    $qty.data('min', originalMin);
+
+    var originalStep = parseFloat($qty.attr('data-order-step'));
+    if (!isFinite(originalStep) || originalStep <= 0) originalStep = parseFloat($qty.data('step'));
+    if (!isFinite(originalStep) || originalStep <= 0) originalStep = parseFloat($qty.attr('step')) || 1;
+    $qty.data('step', originalStep);
 
     // Sample request buttons are always available (hide/show is variant-driven only)
     $form.find('.dl-sample-btn, .dl-second-sample-btn').show();
diff --git a/shopify/theme-fixes/custom-js-qty-2026-06-30/custom.js.liquid.SOURCE b/shopify/theme-fixes/custom-js-qty-2026-06-30/custom.js.liquid.SOURCE
new file mode 100644
index 00000000..64d9005e
--- /dev/null
+++ b/shopify/theme-fixes/custom-js-qty-2026-06-30/custom.js.liquid.SOURCE
@@ -0,0 +1,511 @@
+/*========== 020 Custom js for quantity ===================*/
+
+function mm_refresh_quantity_increments() {  
+    jQuery("div.mm_quantity:not(.mm_buttons_added), td.mm_quantity:not(.mm_buttons_added)").each(function(a, b) {
+        var c = jQuery(b);
+        c.addClass("mm_buttons_added"), c.children().first().before('<input type="button" value="-" class="minus" />'), c.children().last().after('<input type="button" value="+" class="plus" />')
+    })
+}
+String.prototype.getDecimals || (String.prototype.getDecimals = function() {
+    var a = this,
+        b = ("" + a).match(/(?:\.(\d+))?(?:[eE]([+-]?\d+))?$/);
+    return b ? Math.max(0, (b[1] ? b[1].length : 0) - (b[2] ? +b[2] : 0)) : 0
+}), jQuery(document).ready(function() {
+    mm_refresh_quantity_increments()
+
+    // Custom popup contact form (moved from snippets/product.liquid)
+    if (jQuery('.custom-popup-contact-form').length > 0) {
+      jQuery('.custom-popup-contact-form').on('click', function (e) {
+        e.preventDefault();
+        jQuery('#custom-popup-overlay').show();
+      });
+    }
+
+    if (jQuery('#custom-popup-overlay .custom-close').length > 0) {
+      jQuery('#custom-popup-overlay .custom-close').on('click', function () {
+        jQuery('#custom-popup-overlay').hide();
+      });
+    }
+
+    if (jQuery('#custom-popup-overlay .success-message').length > 0) {
+      jQuery('.custom-contact-message').html('Thank you. We will respond to your message shortly.');
+    }
+
+    if (jQuery('#custom-popup-overlay .error-message').length > 0) {
+      jQuery('.custom-contact-message').html(jQuery('#custom-popup-overlay .error-message').html());
+    }
+}), jQuery(document).on("updated_wc_div", function() {
+    mm_refresh_quantity_increments()
+}), jQuery(document).on("click", ".plus, .minus", async function(event) {
+    event.preventDefault();
+    
+    var a = jQuery(this).closest(".mm_quantity").find(".qty"),
+        b = parseFloat(a.val()),
+        c = parseFloat(a.attr("max")),
+        d = parseFloat(a.attr("min")),
+        e = a.attr("step");
+    //b && "" !== b && "NaN" !== b || (b = 0), "" !== c && "NaN" !== c || (c = ""), "" !== d && "NaN" !== d || (d = 0), "any" !== e && "" !== e && void 0 !== e && "NaN" !== parseFloat(e) || (e = 1), jQuery(this).is(".plus") ? c && b >= c ? a.val(c) : a.val((b + parseFloat(e)).toFixed(e.getDecimals())) : d && b <= d ? a.val(d) : b > 0 && a.val((b - parseFloat(e)).toFixed(e.getDecimals())), a.trigger("change")
+     b && "" !== b && "NaN" !== b || (b = 0), "" !== c && "NaN" !== c || (c = ""), "" !== d && "NaN" !== d || (d = 0), "any" !== e && "" !== e && void 0 !== e && "NaN" !== parseFloat(e) || (e = 1), jQuery(this).is(".plus") ? c && b >= c ? a.val(c) : a.val((b + parseFloat(e)).toFixed(e.getDecimals())) : d && b <= d ? a.val(d) : b > 0 && a.val((b - parseFloat(e)).toFixed(e.getDecimals()))
+     
+     //alert();
+     const quantity=jQuery(this).closest(".mm_quantity").find(".qty").val();
+     jQuery(this).closest(".mm_quantity").find(".hquantity").attr('value',quantity);
+
+     if($(this).hasClass('cart-page-quantity-value')){
+      const variantId=jQuery(this).closest(".mm_quantity").find(".hquantity").attr('variantid');
+      console.log('-----', variantId)
+      await updateItem(variantId, quantity)
+      window.location.href="/cart"
+     }
+    
+     
+});
+
+async function updateItem(variantId, quantity,  properties={}){
+  const result = await fetch("/cart/change.json", {
+    method:"post",
+    headers: {
+        "Content-type": "application/json",
+        "Accept": "application/json"
+    },
+    body: JSON.stringify({
+        id: variantId,
+        quantity: quantity,
+        properties: properties
+    })
+  })
+  return result.json();
+}
+
+function mm_validate(evt) {
+  var theEvent = evt || window.event;
+	theEvent.preventDefault()
+	/*
+  if (theEvent.type === 'paste') {
+      key = event.clipboardData.getData('text/plain');
+  } else {  
+      var key = theEvent.keyCode || theEvent.which;
+      key = String.fromCharCode(key);
+  }
+  var regex = /[0-9]|\./;
+	
+  if( !regex.test(key) ) {
+    theEvent.returnValue = false;
+    if(theEvent.preventDefault) theEvent.preventDefault();
+  }*/
+}
+
+
+/* for variant sample */
+jQuery(document).on('change', '.shopify-product-form .single-option-selector, .shopify-product-form .product-select', function () {
+  var $form = jQuery(this).closest('.shopify-product-form');
+
+  var isOptionSelector = jQuery(this).hasClass('single-option-selector');
+
+  var run = function () {
+    var $variantSelect = $form.find('.product-select');
+    var $selected;
+    var selectedSku = '';
+    var optionText = '';
+
+    if ($variantSelect.length && $variantSelect.is('select')) {
+      $selected = $variantSelect.find(':selected');
+      selectedSku = ($selected.attr('data-sku') || '').toString();
+      optionText = ($selected.text() || '').toString();
+    } else if ($variantSelect.length && $variantSelect.is('input')) {
+      // 1-variant products: .product-select is a hidden input
+      selectedSku = ($variantSelect.attr('data-sku') || '').toString();
+      optionText = ($variantSelect.attr('data-variant-title') || '').toString();
+    } else {
+      $selected = jQuery(this).find(':selected');
+      selectedSku = ($selected.attr('data-sku') || '').toString();
+      optionText = ($selected.text() || '').toString();
+    }
+
+    // If we can't determine anything, don't flip the UI.
+    if (!selectedSku && !optionText) return;
+
+    var isSkuSample = /-+sample$/i.test(selectedSku);
+    var isTextSample = /\bsample\b/i.test(optionText);
+    var isPureSample = /^sample(\b|[^a-z])/i.test(optionText.trim());
+    var isSample = isSkuSample || isTextSample;
+
+    var $qty = $form.find('.mm_quantity .qty');
+    var $qtyWrapper = $form.find('.mm_quantity');
+    var originalMin = $qty.data('min');
+    var originalStep = $qty.data('step');
+
+    if (!originalMin) {
+      originalMin = parseFloat($qty.attr('min')) || 1;
+      $qty.data('min', originalMin);
+    }
+    if (!originalStep) {
+      originalStep = parseFloat($qty.attr('step')) || 1;
+      $qty.data('step', originalStep);
+    }
+
+    // Sample request buttons are always available (hide/show is variant-driven only)
+    $form.find('.dl-sample-btn, .dl-second-sample-btn').show();
+
+    if (isSample) {
+      $qty.val(1);
+      $qty.attr({ step: 1, min: 1, max: 1 });
+      $qtyWrapper.find('.plus, .minus').prop('disabled', true);
+      $qtyWrapper.find('.hquantity').attr('value', 1);
+
+      $form.find('.product-option-quantity-label').addClass('hidden').hide();
+      $qtyWrapper.addClass('hidden').hide();
+      $form.find('.add-to-cart').addClass('hidden').hide();
+
+      if (!isPureSample) {
+        $form.find('.dl-second-sample-btn').removeClass('unavailable');
+      } else if (!$form.find('.dl-second-sample-btn').hasClass('unavailable')) {
+        $form.find('.dl-second-sample-btn').addClass('unavailable');
+      }
+    } else {
+      $form.find('.product-option-quantity-label').removeClass('hidden').show();
+      $qtyWrapper.removeClass('hidden').show();
+      $form.find('.add-to-cart').removeClass('hidden').show();
+
+      var minValue = originalMin;
+      var currentValue = parseFloat($qty.val()) || 0;
+      if (currentValue < minValue) {
+        $qty.val(minValue);
+        $qtyWrapper.find('.hquantity').attr('value', minValue);
+      }
+      $qty.attr({ step: originalStep, min: minValue });
+      $qty.removeAttr('max');
+      $qtyWrapper.find('.plus, .minus').prop('disabled', false);
+    }
+
+    // Update the SKU display
+    var displaySku = selectedSku
+      .replace(/-+sample$/i, '')
+      .replace(/-+$/g, '');
+    jQuery('.product-sku-value').text(displaySku);
+
+    // Trade discount text
+    if (jQuery('#customer_tag').val() === 'trade') {
+      requestAnimationFrame(function () {
+        requestAnimationFrame(function () {
+          if (isSample) {
+            jQuery('.product-price-discount').text('Complimentary Sample');
+          } else {
+            // Use the currently displayed currency (symbol/code) from the UI, not hardcoded USD.
+            // Also support both "298,95" and "298.95" style decimals.
+            function dlParseDisplayedNumber(numStr) {
+              var s = (numStr || '').toString().replace(/\s/g, '');
+              var lastComma = s.lastIndexOf(',');
+              var lastDot = s.lastIndexOf('.');
+
+              // No separators: plain number
+              if (lastComma === -1 && lastDot === -1) {
+                var plain = parseFloat(s.replace(/[^\d.-]/g, ''));
+                return isFinite(plain) ? plain : NaN;
+              }
+
+              // Decide decimal separator by whichever comes last
+              var decimalSep = lastComma > lastDot ? ',' : '.';
+              var otherSep = decimalSep === ',' ? '.' : ',';
+
+              var parts = s.split(decimalSep);
+              if (!parts.length) return NaN;
+              var intPart = (parts[0] || '').replace(new RegExp('[\\' + otherSep + ']', 'g'), '').replace(/[^\d-]/g, '');
+              var fracPart = (parts[1] || '').replace(/[^\d]/g, '');
+              var normalized = intPart + (fracPart ? ('.' + fracPart) : '');
+              var val = parseFloat(normalized);
+              return isFinite(val) ? val : NaN;
+            }
+
+            function dlFormatLike(numStr, value) {
+              var s = (numStr || '').toString().replace(/\s/g, '');
+              var lastComma = s.lastIndexOf(',');
+              var lastDot = s.lastIndexOf('.');
+              var decimalSep = lastComma === -1 && lastDot === -1 ? '.' : (lastComma > lastDot ? ',' : '.');
+              var thousandsSep = decimalSep === ',' ? '.' : ',';
+
+              var hasThousands = false;
+              if (lastComma !== -1 || lastDot !== -1) {
+                var decIndex = decimalSep === ',' ? lastComma : lastDot;
+                var intStr = s.slice(0, decIndex);
+                hasThousands = intStr.indexOf(thousandsSep) !== -1;
+              }
+
+              var fixed = (Math.round(value * 100) / 100).toFixed(2);
+              var fixedParts = fixed.split('.');
+              var intPart = fixedParts[0];
+              var fracPart = fixedParts[1] || '00';
+
+              if (hasThousands) {
+                intPart = intPart.replace(/\B(?=(\d{3})+(?!\d))/g, thousandsSep);
+              }
+
+              return intPart + decimalSep + fracPart;
+            }
+
+            var priceText = (jQuery('.product-price-minimum').first().text() || '').trim();
+            var re = /[\d.,]+/;
+            var m = re.exec(priceText);
+            if (!m) return;
+
+            var numberStr = m[0];
+            var prefix = priceText.slice(0, m.index);
+            var suffix = priceText.slice(m.index + numberStr.length);
+
+            var priceValue = dlParseDisplayedNumber(numberStr);
+            if (!isFinite(priceValue)) return;
+
+            var discountedValue = priceValue * 0.85;
+            var formattedNumber = dlFormatLike(numberStr, discountedValue);
+            jQuery('.product-price-discount').text(prefix + formattedNumber + suffix);
+          }
+        });
+      });
+    }
+  }.bind(this);
+
+  var schedule = function () {
+    requestAnimationFrame(function () {
+      requestAnimationFrame(run);
+    });
+  };
+
+  if (isOptionSelector) {
+    setTimeout(schedule, 60);
+  } else {
+    schedule();
+  }
+});
+
+
+//hide ddl and show last variant
+
+if($('#has_display_variant').length>0 && $('#has_display_variant').val() == "no"){
+  $('.last_variant').hide(); 
+  if ( $( ".shopify-product-form .single-option-selector" ).length ) {
+    
+    $('.shopify-product-form .single-option-selector').hide();
+    
+    setTimeout(function(){ 
+      $('.product-options .select-wrapper').hide();
+    }, 500);
+    
+    $('.last_variant').show();
+
+    var single_opt_ind=0;
+    $('.shopify-product-form .single-option-selector option').each(function(index, option){
+     single_opt_ind=index;
+    });  
+
+    
+    /* DW deep-link honor (vp-dw-commerce 2026-06-24) */
+    // Honor an explicit ?variant=<id> deep-link: if the URL names a variant that
+    // matches one of the options, select THAT option instead of the computed default,
+    // so a link to the Sample renders $4.25 and a link to the Roll renders the roll price.
+    try {
+      var dlWanted = new URLSearchParams(window.location.search).get('variant');
+      if (dlWanted) {
+        $dlOpts.each(function(index, option){
+          var ov = (option.value || '').toString();
+          var dv = (option.getAttribute && option.getAttribute('data-variant-id') || '').toString();
+          if (ov === dlWanted || dv === dlWanted) { single_opt_ind = index; }
+        });
+      }
+    } catch (e) {}
+    /* /DW deep-link honor */
+    $(".shopify-product-form .single-option-selector").prop('selectedIndex', single_opt_ind).trigger('change');
+  }  
+}
+//--------------------------------
+
+// Prevent rapid multi-clicks from adding multiple samples before mini-cart UI updates
+function dlGetSampleBtns(variantId) {
+  return $(".dl-sample-btn[data-dlvid='" + variantId + "'], .dl-second-sample-btn[data-dlvid='" + variantId + "']");
+}
+function dlIsSampleLocked(variantId) {
+  return dlGetSampleBtns(variantId).first().attr("data-dl-locked") === "1";
+}
+function dlLockSampleBtns(variantId) {
+  var $btns = dlGetSampleBtns(variantId);
+  $btns.attr("data-dl-locked", "1").addClass("dl-click-locked").attr("aria-disabled", "true");
+  $btns.each(function() {
+    var $b = $(this);
+    if ($b.is("button, input")) $b.prop("disabled", true);
+  });
+}
+function dlUnlockSampleBtns(variantId) {
+  var $btns = dlGetSampleBtns(variantId);
+  $btns.removeAttr("data-dl-locked").removeClass("dl-click-locked").removeAttr("aria-disabled");
+  $btns.each(function() {
+    var $b = $(this);
+    if ($b.is("button, input")) $b.prop("disabled", false);
+  });
+}
+function dlUnlockSampleBtnsWhenInCart(variantId, maxMs) {
+  var start = Date.now();
+  var timer = setInterval(function() {
+    if ($(".mini-cart-item-wrapper [data-variant='" + variantId + "']").length > 0) {
+      clearInterval(timer);
+      dlUnlockSampleBtns(variantId);
+      return;
+    }
+    if (Date.now() - start >= (maxMs || 20000)) {
+      clearInterval(timer);
+      dlUnlockSampleBtns(variantId);
+    }
+  }, 250);
+}
+
+
+$(document).on("click", ".dl-sample-btn", function(e) {
+  e.preventDefault()
+  var $btn = $(this);
+  var $form = $btn.closest("form");
+  var id = $btn.attr("data-dlvid");
+  //check if the sample was added already
+  if($(".mini-cart-item-wrapper [data-variant='"+id+"']").length == 0) {
+    if (dlIsSampleLocked(id)) return;
+    dlLockSampleBtns(id);
+    dlUnlockSampleBtnsWhenInCart(id, 20000);
+
+    var product_id = $(this).attr("data-product-id");
+    var $productSelect = $form.find("#product-select-"+product_id);
+    if (!$productSelect.length) $productSelect = $form.find(".product-select");
+    var first_selected_id = $productSelect.is("select") ? $productSelect.find(":selected").val() : $productSelect.val();
+    $productSelect.val(id).trigger('change');
+    //
+    var $qtyInput = $form.find(".input-text.qty");
+    var default_quantity = $qtyInput.val();
+    $qtyInput.val(1);
+    $form.find(".add-to-cart.cowlendar-add-to-cart").trigger("click");
+
+    //go back to original selected status
+    
+    $qtyInput.val(default_quantity);
+    if (first_selected_id && first_selected_id !== id) {
+      $productSelect.val(first_selected_id).trigger('change');
+    }
+  } else {
+    //alert("1 sample only");
+    if($(".product-message").hasClass("success-message") || $(".mini-cart-item-wrapper [data-variant='"+id+"']").length > 0) {
+      $(".product-message").removeClass("success-message").addClass("error-message");
+      $(".product-message").text("1 Sample per SKU");
+    }    
+  }  
+});
+$(document).on("click", ".dl-second-sample-btn", function(e) {
+  e.preventDefault()
+  var $btn = $(this);
+  var $form = $btn.closest("form");
+  var id = $btn.attr("data-dlvid");
+  //check if the sample was added already
+  if($(".mini-cart-item-wrapper [data-variant='"+id+"']").length == 0) {
+    if (dlIsSampleLocked(id)) return;
+    dlLockSampleBtns(id);
+    dlUnlockSampleBtnsWhenInCart(id, 20000);
+
+    //
+    var $qtyInput = $form.find(".input-text.qty");
+    var default_quantity = $qtyInput.val();
+    $qtyInput.val(1);
+    $form.find(".add-to-cart.cowlendar-add-to-cart").trigger("click");
+
+    //go back to original selected status
+    
+    $qtyInput.val(default_quantity);
+  } else {
+    //alert("1 sample only");
+    if($(".product-message").hasClass("success-message") || $(".mini-cart-item-wrapper [data-variant='"+id+"']").length > 0) {
+      $(".product-message").removeClass("success-message").addClass("error-message");
+      $(".product-message").text("1 Sample per SKU");
+    }    
+  }  
+});
+
+//add the remove functionality on the mini-cart
+$(document).on("click", ".remove-item", function(e) {
+  e.preventDefault();
+    var variantId = $(this).closest('.mini-cart-item').attr("data-variant");
+    var quantity = parseInt($(this).closest(".mini-cart-item").find(".mini-cart-item-quantity").find("span").text());
+    var cart_num = parseInt($(".cart-count .cart-count-number").text());
+    var reminder_num = cart_num - quantity;
+    $.ajax({
+        type: "POST",
+        url: "/cart/change.js",
+        dataType: "json",
+        data: {
+          id: parseFloat(variantId),
+          quantity: 0,
+        },
+    }).then((data) => {
+        console.log("success");
+        $(this).closest('.mini-cart-item').remove();
+        dlUnlockSampleBtns(variantId);
+        $(".cart-count .cart-count-number").text(parseInt(reminder_num));
+        if(reminder_num == 0) {
+          $(".mini-cart").hide();
+        }
+    });
+});
+
+$(document).on("click", ".upsell-add-to-cart", function() {
+  var id = $(this).attr("data-item-id");
+  var num = parseInt($(this).attr("data-num"));
+  $.ajax({
+      type: 'POST',
+      url: '/cart/add.js',
+      dataType: 'json',
+      data: {
+          items: [
+              {
+                  quantity: num,
+                  id: id
+              }
+          ]
+      },
+      success: function (data){
+          location.href= "/cart";
+      }
+    });
+});
+
+/* Custom code for sascha */
+function showMiniCartIfReady() {
+  if (window.innerWidth <= 767) {
+    return;
+  }
+  var $miniCart = $(".mini-cart");
+  if ($miniCart.length && !$miniCart.hasClass("empty")) {
+    $(".mini-cart-wrapper").addClass("is-open");
+  }
+}
+
+function watchMiniCartUpdates() {
+  var target = document.querySelector(".mini-cart-item-wrapper");
+  if (!target || !window.MutationObserver) {
+    return;
+  }
+  var observer = new MutationObserver(function() {
+    showMiniCartIfReady();
+  });
+  observer.observe(target, { childList: true, subtree: true });
+}
+
+$(document).ready(function() {
+  watchMiniCartUpdates();
+});
+
+$(document).on("click", ".add-to-cart.cowlendar-add-to-cart", function() {
+  showMiniCartIfReady();
+});
+
+$(document).on("click", function(event) {
+  var $target = $(event.target);
+  if ($target.closest(".mini-cart, .mini-cart-wrapper").length === 0) {
+    $(".mini-cart-wrapper").removeClass("is-open");
+  }
+});
+
+$(document).on("mouseleave", ".mini-cart-wrapper", function() {
+  $(this).removeClass("is-open");
+});
\ No newline at end of file
diff --git a/shopify/theme-fixes/custom-js-qty-2026-06-30/firstclick.cjs b/shopify/theme-fixes/custom-js-qty-2026-06-30/firstclick.cjs
new file mode 100644
index 00000000..7fd6e1d8
--- /dev/null
+++ b/shopify/theme-fixes/custom-js-qty-2026-06-30/firstclick.cjs
@@ -0,0 +1,30 @@
+const { chromium } = require(require('path').join(process.env.HOME, 'Projects/Designer-Wallcoverings/node_modules/playwright'));
+const fs=require('fs');
+const DEV_JS=fs.readFileSync(__dirname+'/DEV_custom.js','utf8');
+const URL='https://www.designerwallcoverings.com/products/dwtt-80064-designer-wallcoverings-los-angeles';
+(async()=>{
+  const b=await chromium.launch();const ctx=await b.newContext();
+  await ctx.route(/\/cdn\/shop\/t\/\d+\/assets\/custom\.js(\?|$)/,r=>{if(/boost-sd/.test(r.request().url()))return r.continue();r.fulfill({status:200,contentType:'application/javascript',body:DEV_JS});});
+  const p=await ctx.newPage();
+  await p.goto(URL,{waitUntil:'domcontentloaded',timeout:60000});
+  await p.waitForSelector('.mm_quantity input[name="quantity"]',{state:'attached',timeout:30000});
+  await p.evaluate(()=>{document.querySelectorAll('.mm_quantity .qty').forEach(q=>{q.setAttribute('data-order-step','2');q.setAttribute('data-order-min','2');});});
+  await p.waitForTimeout(1800);
+  await p.evaluate(()=>{const s=document.querySelector('.product-select');if(s&&s.tagName==='SELECT'){const o=Array.from(s.options).find(x=>!/sample/i.test(x.textContent)&&!/-sample$/i.test(x.getAttribute('data-sku')||''))||s.options[0];s.value=o.value;s.dispatchEvent(new Event('change',{bubbles:true}));if(window.jQuery)window.jQuery(s).trigger('change');}});
+  await p.waitForTimeout(1800);
+  const info=await p.evaluate(()=>{
+    // count delegated click handlers on document for .plus by probing jQuery internals
+    const $=window.jQuery; const ev=$._data(document,'events')||{};
+    const clickHandlers=(ev.click||[]).map(h=>h.selector);
+    const q=document.querySelector('.mm_quantity .qty');
+    return {clickSelectors:clickHandlers, qtyBefore:q.value};
+  });
+  console.log('delegated click selectors on document:',JSON.stringify(info.clickSelectors));
+  console.log('qty before first click:',info.qtyBefore);
+  // click plus once, read
+  await p.click('.mm_quantity .plus'); await p.waitForTimeout(200);
+  console.log('qty after 1st +:', await p.evaluate(()=>document.querySelector('.mm_quantity .qty').value));
+  await p.click('.mm_quantity .plus'); await p.waitForTimeout(200);
+  console.log('qty after 2nd +:', await p.evaluate(()=>document.querySelector('.mm_quantity .qty').value));
+  await b.close();
+})().catch(e=>{console.error(e);process.exit(1)});
diff --git a/shopify/theme-fixes/custom-js-qty-2026-06-30/harness-run.cjs b/shopify/theme-fixes/custom-js-qty-2026-06-30/harness-run.cjs
new file mode 100644
index 00000000..340047fa
--- /dev/null
+++ b/shopify/theme-fixes/custom-js-qty-2026-06-30/harness-run.cjs
@@ -0,0 +1,28 @@
+const { chromium } = require(require('path').join(process.env.HOME, 'Projects/Designer-Wallcoverings/node_modules/playwright'));
+const path=require('path');
+const FILE='file://'+path.join(__dirname,'harness.html');
+(async()=>{
+  const b=await chromium.launch();const p=await(await b.newContext()).newPage();
+  const errs=[];p.on('pageerror',e=>errs.push('PAGEERROR: '+e.message));
+  p.on('console',m=>{if(m.type()==='error')errs.push('CONSOLE.error: '+m.text());});
+  await p.goto(FILE,{waitUntil:'load'});
+  await p.waitForTimeout(400);
+  const rd=async(scope)=>p.evaluate(s=>{const w=document.querySelector(s+' .mm_quantity');const q=w.querySelector('.qty');const h=w.querySelector('.hquantity');return {qty:q.value,hq:h.getAttribute('value')};},scope);
+  const clk=async(scope,dir)=>{await p.click(scope+' .mm_quantity .'+dir);await p.waitForTimeout(60);};
+  const out={};
+  for(const [scope,label] of [['#moq','MOQ step2/min2'],['#std','non-MOQ step1/min1']]){
+    const seq=[['initial',await rd(scope)]];
+    await clk(scope,'plus'); seq.push(['+',await rd(scope)]);
+    await clk(scope,'plus'); seq.push(['+',await rd(scope)]);
+    await clk(scope,'minus'); seq.push(['-',await rd(scope)]);
+    await clk(scope,'minus'); seq.push(['- (floor)',await rd(scope)]);
+    await clk(scope,'minus'); seq.push(['- (floor)',await rd(scope)]);
+    // typed invalid -> snap
+    await p.evaluate(s=>{const q=document.querySelector(s+' .mm_quantity .qty');q.value='3';window.jQuery(q).trigger('change');},scope);
+    await p.waitForTimeout(60); seq.push(['typed 3 -> snap',await rd(scope)]);
+    out[label]=seq;
+  }
+  out.errors=errs;
+  console.log(JSON.stringify(out,null,2));
+  await b.close();
+})().catch(e=>{console.error('FATAL',e);process.exit(1);});
diff --git a/shopify/theme-fixes/custom-js-qty-2026-06-30/harness.html b/shopify/theme-fixes/custom-js-qty-2026-06-30/harness.html
new file mode 100644
index 00000000..1bf1ddc3
--- /dev/null
+++ b/shopify/theme-fixes/custom-js-qty-2026-06-30/harness.html
@@ -0,0 +1,52 @@
+<!doctype html><html><head><meta charset="utf-8"><title>qty harness</title>
+<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
+</head><body>
+<form action="/cart/add">
+  <h3>MOQ (step2/min2)</h3>
+  <div id="moq"><div class="mm_quantity mm_buttons_added product-option-row-4 ">
+              <input type="hidden" class="hquantity" name="hquantity" value="2">
+              <input type="button" value="-" class="minus">
+              <input
+                type="text"
+                step="2"
+                min="2"
+                data-order-step="2"
+                data-order-min="2"
+                max=""
+                name="quantity"
+                value="2"
+                title="Qty"
+                class="input-text qty text"
+                size="4"
+                pattern=""
+                inputmode=""
+                onkeypress="mm_validate(event)"
+              >
+              <input type="button" value="+" class="plus">
+            </div></div>
+  <h3>non-MOQ (step1/min1)</h3>
+  <div id="std"><div class="mm_quantity mm_buttons_added product-option-row-4 ">
+              <input type="hidden" class="hquantity" name="hquantity" value="1">
+              <input type="button" value="-" class="minus">
+              <input
+                type="text"
+                step="1"
+                min="1"
+                data-order-step="1"
+                data-order-min="1"
+                max=""
+                name="quantity"
+                value="1"
+                title="Qty"
+                class="input-text qty text"
+                size="4"
+                pattern=""
+                inputmode=""
+                onkeypress="mm_validate(event)"
+              >
+              <input type="button" value="+" class="plus">
+            </div></div>
+</form>
+<script>window.$ = window.jQuery;</script>
+<script src="./DEV_custom.js"></script>
+</body></html>
diff --git a/shopify/theme-fixes/custom-js-qty-2026-06-30/hostcheck.cjs b/shopify/theme-fixes/custom-js-qty-2026-06-30/hostcheck.cjs
new file mode 100644
index 00000000..9fc0005e
--- /dev/null
+++ b/shopify/theme-fixes/custom-js-qty-2026-06-30/hostcheck.cjs
@@ -0,0 +1,14 @@
+const { chromium } = require(require('path').join(process.env.HOME, 'Projects/Designer-Wallcoverings/node_modules/playwright'));
+const THEME=process.argv[2];
+(async()=>{
+  const b=await chromium.launch();const ctx=await b.newContext();const p=await ctx.newPage();
+  // Block redirect to primary domain by rewriting requests? Instead, watch where we land.
+  const URL=`https://designer-laboratory-sandbox.myshopify.com/products/dwtt-80064-designer-wallcoverings-los-angeles?preview_theme_id=${THEME}&_fd=0&pb=0`;
+  const resp=await p.goto(URL,{waitUntil:'domcontentloaded',timeout:60000});
+  console.log('landed URL:',p.url());
+  let custom=null;
+  p.on('response',async r=>{const u=r.url();if(/\/custom\.js\?/.test(u)&&!/boost-sd/.test(u)){custom=u;}});
+  await p.waitForTimeout(3000);
+  console.log('custom.js served from:',custom);
+  await b.close();
+})().catch(e=>{console.error(e);process.exit(1)});
diff --git a/shopify/theme-fixes/custom-js-qty-2026-06-30/hostcheck2.cjs b/shopify/theme-fixes/custom-js-qty-2026-06-30/hostcheck2.cjs
new file mode 100644
index 00000000..8c769b6c
--- /dev/null
+++ b/shopify/theme-fixes/custom-js-qty-2026-06-30/hostcheck2.cjs
@@ -0,0 +1,15 @@
+const { chromium } = require(require('path').join(process.env.HOME, 'Projects/Designer-Wallcoverings/node_modules/playwright'));
+const THEME=process.argv[2];
+(async()=>{
+  const b=await chromium.launch();const p=await(await b.newContext()).newPage();
+  let custom=null,body=null;
+  p.on('response',async r=>{const u=r.url();if(/\/custom\.js\?/.test(u)&&!/boost-sd/.test(u)){custom=u;try{body=await r.text();}catch(e){}}});
+  const URL=`https://designer-laboratory-sandbox.myshopify.com/products/dwtt-80064-designer-wallcoverings-los-angeles?preview_theme_id=${THEME}&_fd=0&pb=0`;
+  await p.goto(URL,{waitUntil:'domcontentloaded',timeout:60000});
+  console.log('landed:',p.url());
+  await p.waitForSelector('.mm_quantity input[name="quantity"]',{state:'attached',timeout:30000}).catch(()=>{});
+  await p.waitForTimeout(2500);
+  console.log('custom.js:',custom);
+  if(body){console.log('has repair marker:',body.includes('qty-stepper repair'),' len:',body.length);}
+  await b.close();
+})().catch(e=>{console.error(e);process.exit(1)});
diff --git a/shopify/theme-fixes/custom-js-qty-2026-06-30/iso.cjs b/shopify/theme-fixes/custom-js-qty-2026-06-30/iso.cjs
new file mode 100644
index 00000000..f90cdb24
--- /dev/null
+++ b/shopify/theme-fixes/custom-js-qty-2026-06-30/iso.cjs
@@ -0,0 +1,43 @@
+// Isolated logic test: no live page, just jsdom-free pure-DOM simulation of the handler math.
+// Proves the increment/decrement/clamp/snap math in a controlled environment.
+String.prototype.getDecimals = function(){var m=(''+this).match(/(?:\.(\d+))?(?:[eE]([+-]?\d+))?$/);return m?Math.max(0,(m[1]?m[1].length:0)-(m[2]?+m[2]:0)):0;};
+function step(current, dir, orderStep, orderMin, maxRaw){
+  var stp=parseFloat(orderStep); if(!isFinite(stp)||stp<=0) stp=1;
+  var mn=parseFloat(orderMin); if(!isFinite(mn)||mn<=0) mn=stp;
+  var mx=parseFloat(maxRaw); var hasMax=(maxRaw!=null&&maxRaw!==''&&isFinite(mx));
+  var decimals=String(stp).getDecimals();
+  var cur=parseFloat(current); if(!isFinite(cur)) cur=mn;
+  var next;
+  if(dir>0){ next=cur+stp; if(hasMax&&next>mx)next=mx; }
+  else { next=cur-stp; if(next<mn)next=mn; }
+  var g=Math.round((next-mn)/stp); if(g<0)g=0; next=mn+g*stp; if(hasMax&&next>mx)next=mx;
+  return next.toFixed(decimals);
+}
+function snap(v, orderStep, orderMin){
+  var stp=parseFloat(orderStep); if(!isFinite(stp)||stp<=0) stp=1;
+  var mn=parseFloat(orderMin); if(!isFinite(mn)||mn<=0) mn=stp;
+  var val=parseFloat(v); if(!isFinite(val)||val<mn) val=mn;
+  var g=Math.round((val-mn)/stp); if(g<0)g=0; return (mn+g*stp).toFixed(String(stp).getDecimals());
+}
+// MOQ step=2 min=2
+let v="2"; const log=[];
+log.push(['initial',v]);
+v=step(v,+1,"2","2",""); log.push(['+',v]);
+v=step(v,+1,"2","2",""); log.push(['+',v]);
+v=step(v,-1,"2","2",""); log.push(['-',v]);
+v=step(v,-1,"2","2",""); log.push(['- (floor)',v]);
+v=step(v,-1,"2","2",""); log.push(['- (floor)',v]);
+log.push(['type 3 -> snap', snap("3","2","2")]);
+log.push(['type 5 -> snap', snap("5","2","2")]);
+console.log('=== MOQ step2/min2 ===');
+log.forEach(l=>console.log('  '+l[0].padEnd(16),'=',l[1]));
+// non-MOQ step=1 min=1
+let w="1"; const log2=[];
+log2.push(['initial',w]);
+w=step(w,+1,"1","1",""); log2.push(['+',w]);
+w=step(w,+1,"1","1",""); log2.push(['+',w]);
+w=step(w,-1,"1","1",""); log2.push(['-',w]);
+w=step(w,-1,"1","1",""); log2.push(['- (floor)',w]);
+w=step(w,-1,"1","1",""); log2.push(['- (floor)',w]);
+console.log('=== non-MOQ step1/min1 ===');
+log2.forEach(l=>console.log('  '+l[0].padEnd(16),'=',l[1]));
diff --git a/shopify/theme-fixes/custom-js-qty-2026-06-30/liquid/snippets__product-form-content.liquid b/shopify/theme-fixes/custom-js-qty-2026-06-30/liquid/snippets__product-form-content.liquid
index 598116d0..f176dda3 100644
--- a/shopify/theme-fixes/custom-js-qty-2026-06-30/liquid/snippets__product-form-content.liquid
+++ b/shopify/theme-fixes/custom-js-qty-2026-06-30/liquid/snippets__product-form-content.liquid
@@ -477,6 +477,8 @@
                 type="text"
                 step="{{ prods_quantity_order_units }}"
                 min="{{ prods_quantity_order_min }}"
+                data-order-step="{{ prods_quantity_order_units }}"
+                data-order-min="{{ prods_quantity_order_min }}"
                 max=""
                 name="quantity"
                 value="{{ prods_quantity_order_min }}"
@@ -577,6 +579,8 @@
                 type="text"
                 step="{{ prods_quantity_order_units }}"
                 min="{{ prods_quantity_order_min }}"
+                data-order-step="{{ prods_quantity_order_units }}"
+                data-order-min="{{ prods_quantity_order_min }}"
                 max=""
                 name="quantity"
                 value="{{ prods_quantity_order_min }}"
diff --git a/shopify/theme-fixes/custom-js-qty-2026-06-30/previewcookie.cjs b/shopify/theme-fixes/custom-js-qty-2026-06-30/previewcookie.cjs
new file mode 100644
index 00000000..adff366b
--- /dev/null
+++ b/shopify/theme-fixes/custom-js-qty-2026-06-30/previewcookie.cjs
@@ -0,0 +1,19 @@
+const { chromium } = require(require('path').join(process.env.HOME, 'Projects/Designer-Wallcoverings/node_modules/playwright'));
+const THEME=process.argv[2];
+(async()=>{
+  const b=await chromium.launch();const ctx=await b.newContext();const p=await ctx.newPage();
+  // Step 1: set preview cookie on myshopify host
+  await p.goto(`https://designer-laboratory-sandbox.myshopify.com/?preview_theme_id=${THEME}&_fd=0&pb=0`,{waitUntil:'domcontentloaded',timeout:60000});
+  await p.waitForTimeout(1000);
+  const cookies=await ctx.cookies();
+  console.log('cookies after step1:',cookies.filter(c=>/preview|theme|_secure_session|_shopify/i.test(c.name)).map(c=>c.name).join(', '));
+  // Step 2: navigate to product (cookie should apply)
+  let custom=null,body=null;
+  p.on('response',async r=>{const u=r.url();if(/\/custom\.js\?/.test(u)&&!/boost-sd/.test(u)){custom=u;try{body=await r.text();}catch(e){}}});
+  await p.goto(`https://designer-laboratory-sandbox.myshopify.com/products/dwtt-80064-designer-wallcoverings-los-angeles?_fd=0&pb=0`,{waitUntil:'domcontentloaded',timeout:60000});
+  await p.waitForSelector('.mm_quantity input[name="quantity"]',{state:'attached',timeout:30000}).catch(()=>{});
+  await p.waitForTimeout(2500);
+  console.log('custom.js:',custom);
+  if(body)console.log('has repair marker:',body.includes('qty-stepper repair'),' len:',body.length);
+  await b.close();
+})().catch(e=>{console.error(e);process.exit(1)});
diff --git a/shopify/theme-fixes/custom-js-qty-2026-06-30/primarypreview.cjs b/shopify/theme-fixes/custom-js-qty-2026-06-30/primarypreview.cjs
new file mode 100644
index 00000000..41dd58cd
--- /dev/null
+++ b/shopify/theme-fixes/custom-js-qty-2026-06-30/primarypreview.cjs
@@ -0,0 +1,15 @@
+const { chromium } = require(require('path').join(process.env.HOME, 'Projects/Designer-Wallcoverings/node_modules/playwright'));
+const THEME=process.argv[2];
+(async()=>{
+  const b=await chromium.launch();const ctx=await b.newContext();const p=await ctx.newPage();
+  let paths=new Set();
+  p.on('response',r=>{const u=r.url();const m=u.match(/\/cdn\/shop\/t\/(\d+)\/assets\/custom\.js/);if(m)paths.add(m[1]);});
+  // Go direct to primary domain WITH preview param, allow redirects
+  await p.goto(`https://www.designerwallcoverings.com/products/dwtt-80064-designer-wallcoverings-los-angeles?preview_theme_id=${THEME}`,{waitUntil:'domcontentloaded',timeout:60000});
+  console.log('landed:',p.url());
+  const ck=(await ctx.cookies()).find(c=>/preview_theme/i.test(c.name));
+  console.log('preview cookie:',ck?ck.value:'NONE');
+  await p.waitForTimeout(2500);
+  console.log('custom.js theme paths seen:',[...paths].join(','));
+  await b.close();
+})().catch(e=>{console.error(e);process.exit(1)});
diff --git a/shopify/theme-fixes/custom-js-qty-2026-06-30/product-form-content.ORIGINAL.liquid b/shopify/theme-fixes/custom-js-qty-2026-06-30/product-form-content.ORIGINAL.liquid
new file mode 100644
index 00000000..598116d0
--- /dev/null
+++ b/shopify/theme-fixes/custom-js-qty-2026-06-30/product-form-content.ORIGINAL.liquid
@@ -0,0 +1,800 @@
+{% comment %}
+  @param product {Product}
+
+  @param show_social_media_icons {Boolean}
+
+  @param show_payment_button {Boolean}
+    Show the DPBs in the product form
+{% endcomment %}
+
+{% assign product_id = product.id %}
+{% assign selected_variant = product.selected_or_first_available_variant %}
+
+{%- liquid
+  assign _selected_variant_sku = selected_variant.sku | default: '' | downcase
+  assign _selected_variant_title = selected_variant.title | default: '' | downcase
+
+  assign _selected_variant_sku_suffix = _selected_variant_sku | slice: -7, 7
+
+  assign is_sample_sku = false
+  if _selected_variant_sku != blank and _selected_variant_sku_suffix == '-sample'
+    assign is_sample_sku = true
+  endif
+
+  assign is_sample_title = false
+  if _selected_variant_title contains 'sample'
+    assign is_sample_title = true
+  endif
+
+  assign is_sample_like = false
+  if is_sample_sku
+    assign is_sample_like = true
+  elsif is_sample_title and selected_variant.price == 0
+    assign is_sample_like = true
+  endif
+-%}
+
+<!-- -----------020------------ -->
+{% assign prods_quantity_order_min = 1 %}
+
+{% if product.metafields['global']['v_prods_quantity_order_min'] %}
+  {% if product.metafields['global']['v_prods_quantity_order_min'] != '' %}
+    {% assign prods_quantity_order_min = product.metafields['global']['v_prods_quantity_order_min'] %}
+  {% endif %}
+{% endif %}
+
+{% assign prods_quantity_order_units = 1 %}
+
+{% if product.metafields['global']['v_prods_quantity_order_units'] %}
+  {% if product.metafields['global']['v_prods_quantity_order_units'] != '' %}
+    {% assign prods_quantity_order_units = product.metafields['global']['v_prods_quantity_order_units'] %}
+  {% endif %}
+{% endif %}
+
+<!-- Minimum order quantity from custom metafields (e.g. Ralph Lauren fabrics) -->
+{% assign min_order_qty = product.metafields.custom.minimum_order_quantity | default: nil %}
+{% assign min_order_unit = product.metafields.custom.minimum_order_unit | default: '' %}
+{% assign has_min_order = false %}
+{% if min_order_qty != nil and min_order_qty != blank and min_order_qty > 1 %}
+  {% assign has_min_order = true %}
+  {% assign min_order_qty_num = min_order_qty | plus: 0 %}
+  {% if min_order_qty_num > prods_quantity_order_min %}
+    {% assign prods_quantity_order_min = min_order_qty_num %}
+  {% endif %}
+{% endif %}
+
+<!-- -------------------------- -->
+<script>
+  setTimeout(function () {
+    const qtySelector = document.querySelector('.mm_quantity .input-text.qty');
+    if (qtySelector) {
+      qtySelector.value = {{ prods_quantity_order_min }};
+    }
+  }, 1000);
+</script>
+
+{% if has_min_order %}
+<!-- Minimum order notice and validation -->
+<style>
+  .min-order-notice {
+    background: #fff3cd;
+    border: 1px solid #ffc107;
+    border-radius: 4px;
+    padding: 8px 12px;
+    margin: 10px 0;
+    font-size: 14px;
+    font-weight: 600;
+    color: #856404;
+    display: flex;
+    align-items: center;
+    gap: 6px;
+  }
+  .min-order-notice svg {
+    flex-shrink: 0;
+  }
+  .min-order-error {
+    background: #f8d7da;
+    border: 1px solid #f5c6cb;
+    color: #721c24;
+    border-radius: 4px;
+    padding: 8px 12px;
+    margin: 8px 0 0;
+    font-size: 13px;
+    display: none;
+  }
+</style>
+<div class="min-order-notice" data-min-order-notice>
+  <svg width="16" height="16" viewBox="0 0 16 16" fill="currentColor"><path d="M8 1a7 7 0 100 14A7 7 0 008 1zm0 2.5a.75.75 0 01.75.75v4a.75.75 0 01-1.5 0v-4A.75.75 0 018 3.5zm0 8a.75.75 0 100-1.5.75.75 0 000 1.5z"/></svg>
+  Minimum order: {{ min_order_qty }} {{ min_order_unit }}
+</div>
+<script>
+  (function() {
+    var MIN_QTY = {{ min_order_qty }};
+    var MIN_UNIT = {{ min_order_unit | json }};
+
+    document.addEventListener('DOMContentLoaded', function() {
+      // Find the product form (could be either .shopify-product-form or form with data-product-form)
+      var forms = document.querySelectorAll('form[action*="/cart/add"], form[data-product-form]');
+      forms.forEach(function(form) {
+        form.addEventListener('submit', function(e) {
+          var qtyInput = form.querySelector('.mm_quantity .input-text.qty') || form.querySelector('input[name="quantity"]');
+          if (!qtyInput) return;
+
+          var qty = parseInt(qtyInput.value, 10);
+          if (isNaN(qty) || qty < MIN_QTY) {
+            e.preventDefault();
+            e.stopImmediatePropagation();
+
+            // Show error message
+            var errorDiv = form.querySelector('.min-order-error');
+            if (!errorDiv) {
+              errorDiv = document.createElement('div');
+              errorDiv.className = 'min-order-error';
+              var addToCartBtn = form.querySelector('.product-add-to-cart');
+              if (addToCartBtn) {
+                addToCartBtn.appendChild(errorDiv);
+              }
+            }
+            errorDiv.textContent = 'Please enter a quantity of at least ' + MIN_QTY + ' ' + MIN_UNIT + '.';
+            errorDiv.style.display = 'block';
+
+            // Also fix the value
+            qtyInput.value = MIN_QTY;
+            var hqty = form.querySelector('.hquantity');
+            if (hqty) hqty.value = MIN_QTY;
+
+            // Hide error after 5s
+            setTimeout(function() { errorDiv.style.display = 'none'; }, 5000);
+            return false;
+          }
+        });
+      });
+
+      // Also enforce min on the minus button - prevent going below minimum
+      jQuery(document).on('click', '.minus', function() {
+        var $qty = jQuery(this).closest('.mm_quantity').find('.qty');
+        var currentVal = parseInt($qty.val(), 10);
+        if (currentVal < MIN_QTY) {
+          $qty.val(MIN_QTY);
+          jQuery(this).closest('.mm_quantity').find('.hquantity').attr('value', MIN_QTY);
+        }
+      });
+
+      // Enforce min on blur (manual typing)
+      jQuery(document).on('blur', '.mm_quantity .input-text.qty', function() {
+        var currentVal = parseInt(jQuery(this).val(), 10);
+        if (isNaN(currentVal) || currentVal < MIN_QTY) {
+          jQuery(this).val(MIN_QTY);
+          jQuery(this).closest('.mm_quantity').find('.hquantity').attr('value', MIN_QTY);
+        }
+      });
+
+      // Hide notice when sample variant is selected
+      jQuery(document).on('change', '.product-select, .single-option-selector', function() {
+        var $form = jQuery(this).closest('form, [data-product-form]');
+        var $selected = $form.find('.product-select option:selected, .product-select[type="hidden"]');
+        var selectedText = ($selected.text() || $selected.attr('data-variant-title') || '').toLowerCase();
+        var selectedSku = ($selected.attr('data-sku') || '').toLowerCase();
+        var isSample = /sample/i.test(selectedText) || /-sample$/i.test(selectedSku);
+
+        var $notice = jQuery('.min-order-notice[data-min-order-notice]');
+        if (isSample) {
+          $notice.hide();
+        } else {
+          $notice.show();
+        }
+      });
+    });
+  })();
+</script>
+{% endif %}
+
+{% for block in section.blocks %}
+  {% case block.type %}
+    {% when 'vendor' %}
+      <a class="product-vendor" href="{{ product.vendor | url_for_vendor }}" {{ block.shopify_attributes }}>{{ product.vendor }}</a>
+
+    {% when 'title' %}
+      <h1 class="product-title" {{ block.shopify_attributes }}>
+        {% if product != blank %}
+          {% if link_to %}
+            <a href="{{ product.url }}">
+              {{ product.title }}
+            </a>
+          {% else %}
+            {% comment %}{{ product.title }} {% endcomment %}
+            <a href="https://designer-laboratory-sandbox.myshopify.com/search?q={{ product.title }}">
+              {{ product.title }}
+            </a>
+          {% endif %}
+        {% else %}
+          {{ 'products.product.onboarding.title' | t }}
+        {% endif %}
+      </h1>
+      <span class="product-sku-label">SKU: </span>
+      {%- liquid
+        assign sku_raw = product.selected_or_first_available_variant.sku | default: ''
+        assign sku_display = sku_raw | replace: '-Sample', '' | replace: '-SAMPLE', '' | replace: '-sample', ''
+        assign sku_display = sku_display | replace: '--', '-' | replace: '--', '-'
+        assign sku_last_char = sku_display | slice: -1, 1
+        if sku_display != blank and sku_last_char == '-'
+          assign sku_display = sku_display | remove_last: '-'
+        endif
+      -%}
+      <span class="product-sku-value">{{ sku_display }}</span>
+    {% when '@app' %}
+      <div class="product-app" {{ block.shopify_attributes }}>
+        {% render block %}
+      </div>
+
+    {% when 'custom-liquid' %}
+      {% if block.settings.custom_liquid != blank %}
+        <div class="product__custom-liquid" {{ block.shopify_attributes }}>
+          {{ block.settings.custom_liquid }}
+        </div>
+      {% endif %}
+
+    {% when 'collapsible-tab' %}
+      {% if block.settings.collapsible_tab_heading != blank %}
+        <details class="collapsible-tab" {{ block.shopify_attributes }}>
+          <summary class="collapsible-tab__heading">
+            <span>{{ block.settings.collapsible_tab_heading | escape }}</span>
+            {%
+              render 'icons',
+              id: 'chevron-right',
+            %}
+          </summary>
+
+          {% if block.settings.collapsible_tab_text != blank %}
+            <div class="collapsible-tab__text rte">
+              {{ block.settings.collapsible_tab_text }}
+            </div>
+          {% endif %}
+        </details>
+      {% endif %}
+
+    {% when 'tabs' %}
+      {% assign tab_counter = 1 %}
+      <div class="product-tabs" {{ block.shopify_attributes }}>
+        {% if block.settings.show_product_description %}
+          <input
+            class="product-tabs__radio"
+            id="product-tabs__radio-{{ section.id }}"
+            name="{{ section.id }}"
+            tabindex="0"
+            type="radio"
+            checked="checked"
+          >
+
+          <label class="product-tabs__label" for="product-tabs__radio-{{ section.id }}">
+            {{ 'onboarding.tab_title'| t }}
+          </label>
+
+          <div class="product-tabs__panel rte" tabindex="0">
+            {% if onboarding %}
+              {{ 'onboarding.tab_content'| t }}
+            {% else %}
+              {% if product.description != blank %}
+                {{ product.description }}
+              {% else %}
+                {{ 'onboarding.tab_content'| t }}
+              {% endif %}
+            {% endif %}
+          </div>
+        {% endif %}
+
+        {% for i in (1..3) %}
+          {% assign tab_text_key = 'tab_text_' | append: i %}
+          {% assign tab_heading_key = 'tab_heading_' | append: i %}
+          {% assign tab_heading_value = block.settings[tab_heading_key] %}
+          {% assign tab_text_value = block.settings[tab_text_key] %}
+
+          {% if tab_heading_value != blank %}
+            <input
+              class="product-tabs__radio"
+              id="product-tabs__radio-{{ i }}-{{ section.id }}"
+              name="{{ section.id }}"
+              tabindex="0"
+              type="radio"
+              {% if tab_counter == 1 and block.settings.show_product_description == false %}
+                checked
+              {% endif %}
+            >
+
+            <label class="product-tabs__label" for="product-tabs__radio-{{ i }}-{{ section.id }}">
+              {{ tab_heading_value | escape }}
+            </label>
+
+            <div class="product-tabs__panel rte" tabindex="0">
+              {{ tab_text_value }}
+            </div>
+
+            {% assign tab_counter = tab_counter | plus: 1 %}
+          {% endif %}
+        {% endfor %}
+      </div>
+
+    {% when 'price' %}
+      <div class="product__price" {{ block.shopify_attributes }}>
+        {%- comment -%}
+          DW trade/designer pricing tiers (2026-06-11): any customer carrying one of these
+          professional tags sees the trade price display (15% off, ×0.85). This restores the
+          pre-app behavior where every designer/trade tier got the discount — not just 'trade'.
+          Matches *any* tag containing 'designer' (interior_designer, commercial_designer, …)
+          plus the explicit tiers below. Add new tiers to the explicit list as needed.
+        {%- endcomment -%}
+        {%- assign dw_is_trade = false -%}
+        {%- for dw_t in customer.tags -%}
+          {%- assign dw_tl = dw_t | downcase | strip -%}
+          {%- if dw_tl contains 'designer' or dw_tl == 'trade' or dw_tl == 'architect' or dw_tl == 'stager' or dw_tl == 'hospitality' or dw_tl == 'retailer' -%}
+            {%- assign dw_is_trade = true -%}
+            {%- break -%}
+          {%- endif -%}
+        {%- endfor -%}
+        <input type="hidden" id="customer_tag" value="{% if dw_is_trade %}trade{% endif %}">
+        <p class="product-price">
+          {% comment %} SA Wholesale (saw_/wbuyx/"WHO") app removed 2026-06-11 — it left the price span blank. Render native variant price unconditionally. {% endcomment %}
+            <!-- native price markup -->
+            <meta itemprop="price" content="{{ selected_variant.price | money_without_currency }}">
+            <span class="product-price-minimum money {% if dw_is_trade %}original{% endif %}">
+              {% if product != blank %}
+                {{ selected_variant.price | money }}
+              {% else %}
+                {{ 'products.product.onboarding.price' | t }}
+              {% endif %}
+            </span>
+            {% if dw_is_trade %}
+              <span
+                class="product-price-discount"
+              >
+                {% assign target_product = selected_variant %}
+                {% assign com_selected_variant_title = selected_variant.title | capitalize %}
+                {% assign com_selected_variant_sku = selected_variant.sku | default: '' | downcase %}
+                {% if com_selected_variant_title == "Sample" or com_selected_variant_sku contains "sample" %}
+                  {% comment %} {% render 'money', value: 0 %} {% endcomment %}
+                  Complimentary Sample
+                {% else %}
+                  {% assign com_variant_first_title = product.variants[0].title | capitalize %}
+                  {% if com_variant_first_title == "Sample" and product.variants[1] != blank  %}
+                    {% assign target_product = product.variants[1] %}
+                  {% endif  %}
+                  {% assign discounted_price = target_product.price | times:0.85 | round: 2 %}
+                  {% render 'money', value: discounted_price %}
+                {% endif %}
+
+              </span>
+            {% endif %}
+           {% comment %}
+            <span
+              class="product-price-compare money original {% if selected_variant.compare_at_price > selected_variant.price %}visible{% endif %}"
+            >
+              {{ selected_variant.compare_at_price | money }}
+            </span>
+           {% endcomment %}
+        </p>
+
+        {% assign variant_for_unit_price = selected_variant %}
+        {% comment %}Inject unit-price begin{% endcomment %}
+        {% comment %}
+          @param variant_for_unit_price
+            Product variant for price
+          @param tax_text
+            String containing 'tax included' text
+        {% endcomment %}
+
+        {% capture total_quantity %}
+          <span class="product-price__unit-price-total-quantity" data-unit-price-quantity>
+            {{ variant_for_unit_price.unit_price_measurement.quantity_value }}{{ variant_for_unit_price.unit_price_measurement.quantity_unit }}
+          </span>
+        {% endcapture %}
+
+
+        {% capture unit_price %}
+          <span class="product-price__unit-price-amount money" data-unit-price-amount>
+            {{ variant_for_unit_price.unit_price | money }}
+          </span>
+        {% endcapture %}
+        {% capture unit_measure %}
+          <span class="product-price__unit-price-measure" data-unit-price-measure>
+            {%- if variant_for_unit_price.unit_price_measurement.reference_value != 1 -%}
+              {{ variant_for_unit_price.unit_price_measurement.reference_value }}
+            {%- endif %}
+            {{ variant_for_unit_price.unit_price_measurement.reference_unit }}
+          </span>
+        {% endcapture %}
+
+        <div
+          class="
+            product-price__unit-price
+            {% unless variant_for_unit_price.unit_price_measurement %}hidden{% endunless %}
+          "
+          data-unit-price
+        >
+          {{ 'products.product.price_per_unit_html' | t: total_quantity: total_quantity, unit_price: unit_price, unit_measure: unit_measure | strip_newlines }}
+        </div>
+
+        {% assign variant_for_unit_price = blank %}
+        {% comment %}Inject unit-price end{% endcomment %}
+
+
+        {%- capture tax_text -%}
+        {{ 'products.product.tax_line_html' | t }}
+        {%- endcapture -%}
+
+        {%- if tax_text != blank -%}
+          <div class="
+            product-price__tax
+            {% unless selected_variant.taxable %}hidden{% endunless %}
+            "
+            data-tax-line
+          >
+            {{ tax_text }}
+          </div>
+        {%- endif -%}
+
+        {{ form | payment_terms }}
+      </div>
+
+    {% when 'form' %}
+
+      <div class="product__form" {{ block.shopify_attributes }}>
+
+        {% unless product.has_only_default_variant %}
+          <div class="product-options">
+            {%-
+              render 'product-options-dropdown',
+              product: product,
+              form_id: product_id
+            -%}
+
+            <label
+              class="
+                product-option-column-1
+                product-option-row-4
+                product-option-quantity-label
+                {% if is_sample_like %}hidden{% endif %}
+              "
+              for="default-variant-quantity-input"
+            >
+              <strong>{{ 'general.general.quantity' | t }}:</strong>
+            </label>
+            {% comment %}
+            <input
+              class="product-option-quantity"
+              id="default-variant-quantity-input"
+              data-product-quantity-input
+              type="number"
+              inputmode="numeric"
+              name="quantity"
+              value="1"
+            >
+            {% endcomment %}
+            <!-- 020 -->
+            <div class="mm_quantity mm_buttons_added product-option-row-4 {% if is_sample_like %}hidden{% endif %}">
+              <input type="hidden" class="hquantity" name="hquantity" value="{{ prods_quantity_order_min }}">
+              <input type="button" value="-" class="minus">
+              <input
+                type="text"
+                step="{{ prods_quantity_order_units }}"
+                min="{{ prods_quantity_order_min }}"
+                max=""
+                name="quantity"
+                value="{{ prods_quantity_order_min }}"
+                title="Qty"
+                class="input-text qty text"
+                size="4"
+                pattern=""
+                inputmode=""
+                onkeypress="mm_validate(event)"
+              >
+              <input type="button" value="+" class="plus">
+            </div>
+            <!--  -->
+
+            <div class="selector-wrapper no-js-required">
+              <label for="product-select-{{ product_id }}"></label>
+              <select
+                class="product-select"
+                name="id"
+                id="product-select-{{ product_id }}"
+                data-select-ignore
+              >
+                {% for variant in product.variants %}
+                  {% if variant.available %}
+                    {% assign com_variant_title = variant.title | capitalize %}
+                    {% if com_variant_title == "Sample" %}
+                      {% assign dl_sample_variant = variant.id %}
+                      {% assign dl_sample_variant_title = product.title | append: ' - ' | append: variant.title %}
+                      {% assign dl_sample = 'small_sample' %}
+                    {% endif %}
+
+                    {% if com_variant_title == "Large Sample" %}
+                      {% assign dl_sample_variant2 = variant.id %}
+                      {% assign dl_sample_variant_title2 = product.title | append: ' - ' | append: variant.title %}
+                      {% assign dl_sample2 = 'large_sample' %}
+                    {% endif %}
+
+                    <option
+                      {% if variant == selected_variant %}selected="selected"{% endif %}
+                      value="{{ variant.id }}"
+                      data-variant-id="{{ variant.id }}"
+                      data-sku="{{ variant.sku }}"
+                      data-vprice="{{ variant.price }}"
+                    >
+                      {{ variant.title }} - {{ variant.price | money }}
+                    </option>
+                  {% else %}
+                    <option 
+                    disabled="disabled" 
+                    data-variant-id="{{ variant.id }}" 
+                    value="{{ variant.id }}"
+                    data-sku="{{ variant.sku }}">
+                      {{ variant.title }} - {{ 'products.product.sold_out' | t }}
+                    </option>
+                  {% endif %}
+                {% endfor %}
+              </select>
+            </div>
+          </div>
+        {% else %}
+          <div class="product-options product-options-default-only">
+            <input
+              class="product-select"
+              name="id"
+              value="{{ product.variants[0].id }}"
+              type="hidden"
+              data-variant-title="{{ product.variants[0].title }}"
+              data-sku="{{ product.variants[0].sku }}"
+              data-vprice="{{ product.variants[0].price }}"
+            />
+
+            <label
+              class="
+                product-option-column-1
+                product-option-row-1
+                product-option-quantity-label
+                {% if is_sample_like %}hidden{% endif %}
+              "
+              for="quantity-input"
+            >
+              <strong>{{ 'general.general.quantity' | t }}:</strong>
+            </label>
+
+<!--             <input
+              class="product-option-quantity"
+              id="quantity-input"
+              data-product-quantity-input
+              type="number"
+              inputmode="numeric"
+              name="quantity"
+              value="1"
+            > -->
+            <!-- 020 -->
+            <div class="mm_quantity mm_buttons_added product-option-row-1 {% if is_sample_like %}hidden{% endif %}">
+              <input type="hidden" class="hquantity" name="hquantity" value="{{ prods_quantity_order_min }}">
+              <input type="button" value="-" class="minus">
+              <input
+                type="text"
+                step="{{ prods_quantity_order_units }}"
+                min="{{ prods_quantity_order_min }}"
+                max=""
+                name="quantity"
+                value="{{ prods_quantity_order_min }}"
+                title="Qty"
+                class="input-text qty text"
+                size="4"
+                pattern=""
+                inputmode=""
+                onkeypress="mm_validate(event)"
+              >
+              <input type="button" value="+" class="plus">
+            </div>
+            <!--  -->
+          </div>
+        {% endunless %}
+
+        {% for tag in product.tags %}
+          {% if tag contains 'custom mural' %}
+            <!-- Custom Price Calculator -->
+            <div id="custom-calculator" style="display:none" data-calculatorid="62388fabd48c8"></div>
+            <script>
+              document.querySelector(".product-options .last_variant").innerHTML = "Custom Sizes";
+
+              var appendCalc = setInterval(moveCalc, 300);
+
+              function moveCalc() {
+                var calc = document.getElementById("calculator");
+                if (calc) {
+                  document
+                    .querySelector(".product-options")
+                    .insertBefore(
+                      document.getElementById("calculator"),
+                      document.querySelector(".product-options").children[2]
+                    );
+                  clearInterval(appendCalc);
+                }
+              }
+            </script>
+            <style>
+              .product-options {
+                display: flex;
+                flex-direction: column;
+                align-items: baseline;
+                margin-left: 0;
+              }
+              .product-options .product-option-quantity-label,
+              .mm_quantity.mm_buttons_added {
+                display: none !important;
+              }
+            </style>
+          {% endif %}
+        {% endfor %}
+
+        {% if product.available %}
+          <div id="infiniteoptions-container"></div>
+          <div id="uploadery-container"></div>
+        {% endif %}
+
+        <!-- Print PDF button -->
+        {% render 'button-product-pdf-print' %}
+
+        <div
+          class="
+            product-add-to-cart
+            {% if product != blank and show_payment_button %}
+              product-smart-payments
+            {% endif %}
+          "
+        >
+          {% if selected_variant.available and template.name != 'password' %}
+            <!-- 020 -->
+            <div class="custom-contact-message"></div>
+
+            <a
+              href="#"
+              class="dl-contact-btn custom-popup-contact-form"
+              {% if variant.price == 0 %}
+                style="display:inline-block;"
+              {% else %}
+                style="display:none;"
+              {% endif %}
+            >
+              Contact Us
+            </a>
+
+            {% assign title = 'Request Sample' %}
+            {% assign showonly = blank %}
+            {% if product.tags contains "Showroom only" and product.price == 0 %}
+              {% assign showonly = 'showonly' %}
+            {% endif %}
+            {% if product.tags contains "Showroom Only" and product.price == 0 %}
+              {% assign showonly = 'showonly' %}
+            {% endif %}
+
+            {% if is_sample_sku and dl_sample == blank and dl_sample2 == blank %}
+              <a
+                href=""
+                data-dlvid="{{ selected_variant.id }}"
+                data-dlvtitle="{{ product.title | append: ' - Sample' }}"
+                class="dl-sample-btn {{ showonly }}"
+                data-product-id="{{ product.id }}"
+              >
+                {{ title }}
+              </a>
+            {% endif %}
+
+            {% if dl_sample != blank and dl_sample2 != blank and dl_sample == 'small_sample' and dl_sample2 == 'large_sample' %}
+              {% assign title = 'Small Sample' %}
+            {% endif %}
+
+            {% if dl_sample == 'small_sample' and dl_sample_variant !='' and  dl_sample_variant_title != '' %}
+              <a
+                href=""
+                data-dlvid="{{ dl_sample_variant }}"
+                data-dlvtitle="{{ dl_sample_variant_title }}"
+                class="dl-sample-btn {{ showonly }}"
+                data-product-id="{{ product.id }}"
+              >
+                {{ title }}
+              </a>
+            {% endif %}
+
+            {% if dl_sample2 == 'large_sample' and dl_sample_variant2 !='' and  dl_sample_variant_title2 != '' %}
+              <a
+                href=""
+                data-dlvid="{{ dl_sample_variant2 }}"
+                data-dlvtitle="{{ dl_sample_variant_title2 }}"
+                class="dl-sample-btn {{ showonly }}"
+                data-product-id="{{ product.id }}"
+              >
+                Large Sample
+              </a>
+            {% endif %}
+            {% for variant_item in product.variants %}
+              {% if variant_item.price == 0 and variant_item.title contains "Sample" and variant_item.title != "Sample" %}
+                {% assign showing_title = variant_item.title %}
+                {% if showing_title == "Textured Vinyl Sample" %}
+                  {% assign showing_title = "Vinyl Sample" %}
+                {% endif %}
+                <a
+                  href=""
+                  data-dlvid="{{ variant_item.id }}"
+                  data-dlvtitle="{{ variant_item.title }}"
+                  class="dl-second-sample-btn {% if variant_item.id != product.selected_or_first_available_variant.id %}unavailable{% endif %} {{ showonly }}"
+                  data-product-id="{{ product.id }}"
+                >
+                  Request {{ showing_title }}
+                </a>
+              {% endif %}
+            {% endfor %}
+
+            {% render 'spec-sheet-button' %}
+
+<!--             <input class="add-to-cart" type="submit" value="{{ 'products.product.add_to_cart' | t }}" /> -->
+            {% assign com_first_variant_title = product.selected_or_first_available_variant.title | capitalize %}
+            <input class="add-to-cart {% if is_sample_like %}hidden{% endif %} {% if product.variants.size == 1 and com_first_variant_title == "Sample" %}hidden{% endif %} {% if product.selected_or_first_available_variant.price == 0 %}unavailable{% endif %}" type="submit" value="{{ 'products.product.add_to_cart' | t }}"/>
+          {% else %}
+            <input class="add-to-cart disabled" type="submit" value="{{ 'products.product.sold_out' | t }}" disabled/>
+          {% endif %}
+
+          <!--020 -->
+          {% for t in product.tags %}
+             {% if t == 'Customize' %}
+                    <a href="#" class="dl-customize-btn custom-popup-contact-form">Customize this Pattern</a>
+             {% endif %}
+          {% endfor %}
+
+          {% if show_payment_button %}
+            {{ form | payment_button }}
+          {% endif %}
+
+          {% capture the_snippet_fave %}{% include 'socialshopwave-widget-fave' %}{% endcapture %}
+          {% unless the_snippet_fave contains 'Liquid error' %}
+          {{ the_snippet_fave }}
+          {% endunless %}
+        </div>
+        <div class="product-message"></div>
+
+        <div data-surface-pick-up></div>
+        {%-
+          render 'modal',
+          modal_id: 'surface-pick-up',
+          modal_class: 'surface-pick-up-modal',
+        -%}
+      </div>
+
+    {% when 'social' %}
+      <div class="product__social" {{ block.shopify_attributes }}>
+        {% render 'share-buttons' %}
+      </div>
+
+    {% when 'description' %}
+      {% if product != blank and product.description != blank %}
+        <div class="product-description rte" {{ block.shopify_attributes }}>
+          {{ product.description }}
+        </div>
+      {% elsif product == blank %}
+        <div class="product-description rte" {{ block.shopify_attributes }}>
+          {{ 'products.product.onboarding.description' | t }}
+        </div>
+      {% endif %}
+
+    {%- when 'rating' -%}
+      {%- if product.metafields.reviews.rating.value != blank -%}
+        <div class="product__rating rating" {{ block.shopify_attributes }}>
+          {%
+            render 'rating-stars',
+            value: product.metafields.reviews.rating.value.rating,
+            scale_max: product.metafields.reviews.rating.value.scale_max,
+          %}
+          <p class="rating__text">
+            <span aria-hidden="true">{{ product.metafields.reviews.rating.value }} / {{ product.metafields.reviews.rating.value.scale_max }}</span>
+          </p>
+          <p class="rating__count">
+            <span aria-hidden="true">({{ product.metafields.reviews.rating_count }})</span>
+            <span class="visually-hidden">{{ product.metafields.reviews.rating_count }} {{ "general.accessibility.total_reviews" | t }}</span>
+          </p>
+        </div>
+      {%- endif -%}
+  {% endcase %}
+{% endfor %}
diff --git a/shopify/theme-fixes/custom-js-qty-2026-06-30/pureplus.cjs b/shopify/theme-fixes/custom-js-qty-2026-06-30/pureplus.cjs
new file mode 100644
index 00000000..3e971113
--- /dev/null
+++ b/shopify/theme-fixes/custom-js-qty-2026-06-30/pureplus.cjs
@@ -0,0 +1,24 @@
+const { chromium } = require(require('path').join(process.env.HOME, 'Projects/Designer-Wallcoverings/node_modules/playwright'));
+const fs=require('fs');
+const DEV_JS=fs.readFileSync(__dirname+'/DEV_custom.js','utf8');
+const URL='https://www.designerwallcoverings.com/products/dwtt-80064-designer-wallcoverings-los-angeles';
+(async()=>{
+  const b=await chromium.launch();const ctx=await b.newContext();
+  await ctx.route(/\/cdn\/shop\/t\/\d+\/assets\/custom\.js(\?|$)/,r=>{if(/boost-sd/.test(r.request().url()))return r.continue();r.fulfill({status:200,contentType:'application/javascript',body:DEV_JS});});
+  const p=await ctx.newPage();
+  await p.goto(URL,{waitUntil:'domcontentloaded',timeout:60000});
+  await p.waitForSelector('.mm_quantity input[name="quantity"]',{state:'attached',timeout:30000});
+  await p.evaluate(()=>{document.querySelectorAll('.mm_quantity .qty').forEach(q=>{q.setAttribute('data-order-step','2');q.setAttribute('data-order-min','2');});});
+  await p.waitForTimeout(1800);
+  await p.evaluate(()=>{const s=document.querySelector('.product-select');if(s&&s.tagName==='SELECT'){const o=Array.from(s.options).find(x=>!/sample/i.test(x.textContent)&&!/-sample$/i.test(x.getAttribute('data-sku')||''))||s.options[0];s.value=o.value;s.dispatchEvent(new Event('change',{bubbles:true}));if(window.jQuery)window.jQuery(s).trigger('change');}});
+  await p.waitForTimeout(1800);
+  // Remove ALL change/blur listeners on .qty (including mine) to test raw +/- clicks
+  await p.evaluate(()=>{const $=window.jQuery;$(document).off('change blur','.mm_quantity .qty');$(document).off('change','.mm_quantity .qty');$(document).off('blur','.mm_quantity .qty');$(document).off('blur','.mm_quantity .input-text.qty');$(document).off('click','.minus');});
+  const rd=async()=>p.evaluate(()=>document.querySelector('.mm_quantity .qty').value);
+  console.log('init',await rd());
+  for(const dir of ['plus','plus','plus','minus','minus','minus','minus']){
+    await p.click('.mm_quantity .'+dir); await p.waitForTimeout(180);
+    console.log(dir,'->',await rd());
+  }
+  await b.close();
+})().catch(e=>{console.error(e);process.exit(1)});
diff --git a/shopify/theme-fixes/custom-js-qty-2026-06-30/rawattr.cjs b/shopify/theme-fixes/custom-js-qty-2026-06-30/rawattr.cjs
new file mode 100644
index 00000000..e7924ff5
--- /dev/null
+++ b/shopify/theme-fixes/custom-js-qty-2026-06-30/rawattr.cjs
@@ -0,0 +1,15 @@
+const { chromium } = require(require('path').join(process.env.HOME, 'Projects/Designer-Wallcoverings/node_modules/playwright'));
+const THEME=process.argv[2];
+const URL=`https://designer-laboratory-sandbox.myshopify.com/products/dwtt-80064-designer-wallcoverings-los-angeles?preview_theme_id=${THEME}`;
+(async()=>{
+  const b=await chromium.launch();const p=await(await b.newContext()).newPage();
+  await p.goto(URL,{waitUntil:'domcontentloaded',timeout:60000});
+  await p.waitForSelector('.mm_quantity input[name="quantity"]',{state:'attached',timeout:30000});
+  // read IMMEDIATELY, before jQuery ready timers mutate — but DOMReady likely already ran. Read anyway at t=0.
+  const now=await p.evaluate(()=>{const q=document.querySelector('.mm_quantity input[name="quantity"]');return{step:q.getAttribute('step'),min:q.getAttribute('min'),val:q.value,defaultSelectedIsSample:(()=>{const s=document.querySelector('.product-select');if(!s||s.tagName!=='SELECT')return 'no-select';const o=s.options[s.selectedIndex];return {text:o.textContent.trim(),sku:o.getAttribute('data-sku')};})()};});
+  console.log('IMMEDIATE (post-domcontentloaded):',JSON.stringify(now));
+  await p.waitForTimeout(2000);
+  const later=await p.evaluate(()=>{const q=document.querySelector('.mm_quantity input[name="quantity"]');return{step:q.getAttribute('step'),min:q.getAttribute('min'),val:q.value};});
+  console.log('AFTER 2s:',JSON.stringify(later));
+  await b.close();
+})().catch(e=>{console.error(e);process.exit(1)});
diff --git a/shopify/theme-fixes/custom-js-qty-2026-06-30/srccheck.cjs b/shopify/theme-fixes/custom-js-qty-2026-06-30/srccheck.cjs
new file mode 100644
index 00000000..89b724a5
--- /dev/null
+++ b/shopify/theme-fixes/custom-js-qty-2026-06-30/srccheck.cjs
@@ -0,0 +1,20 @@
+const { chromium } = require(require('path').join(process.env.HOME, 'Projects/Designer-Wallcoverings/node_modules/playwright'));
+const THEME=process.argv[2];
+const URL=`https://designer-laboratory-sandbox.myshopify.com/products/dwtt-80064-designer-wallcoverings-los-angeles?preview_theme_id=${THEME}`;
+(async()=>{
+  const b=await chromium.launch();const p=await(await b.newContext()).newPage();
+  let customUrl=null,customBody=null;
+  p.on('response',async resp=>{const u=resp.url();if(/\/custom\.js(\?|$)/.test(u)&&!/boost-sd/.test(u)){customUrl=u;try{customBody=await resp.text();}catch(e){}}});
+  await p.goto(URL,{waitUntil:'domcontentloaded',timeout:60000});
+  await p.waitForSelector('.mm_quantity input[name="quantity"]',{state:'attached',timeout:30000});
+  await p.waitForTimeout(2500);
+  console.log('custom.js URL:',customUrl);
+  if(customBody){
+    console.log('loaded custom.js has qty-stepper repair marker:', customBody.includes('qty-stepper repair'));
+    console.log('loaded custom.js has data-order-step:', customBody.includes('data-order-step'));
+    console.log('loaded custom.js length:', customBody.length);
+  } else {
+    console.log('did not capture custom.js body via response; probing window for handler');
+  }
+  await b.close();
+})().catch(e=>{console.error(e);process.exit(1)});
diff --git a/shopify/theme-fixes/custom-js-qty-2026-06-30/verify-dev-asset.cjs b/shopify/theme-fixes/custom-js-qty-2026-06-30/verify-dev-asset.cjs
new file mode 100644
index 00000000..08e5792f
--- /dev/null
+++ b/shopify/theme-fixes/custom-js-qty-2026-06-30/verify-dev-asset.cjs
@@ -0,0 +1,86 @@
+// Load the real product page but SERVE THE DEV THEME'S custom.js bytes (route-intercept),
+// and stamp the data-order-* attrs the DEV product-form-content.liquid now renders, so the
+// dev theme's own DOMReady binds the FIXED handlers. This exercises the exact pushed asset.
+const { chromium } = require(require('path').join(process.env.HOME, 'Projects/Designer-Wallcoverings/node_modules/playwright'));
+const fs = require('fs');
+
+const HANDLE = process.argv[2] || 'dwtt-80064-designer-wallcoverings-los-angeles';
+const EXPECT_STEP = process.argv[3] ? Number(process.argv[3]) : 2;
+const EXPECT_MIN = process.argv[4] ? Number(process.argv[4]) : 2;
+const LABEL = process.argv[5] || HANDLE;
+const URL = `https://www.designerwallcoverings.com/products/${HANDLE}`;
+const DEV_JS = fs.readFileSync(__dirname + '/DEV_custom.js', 'utf8');
+
+(async () => {
+  const browser = await chromium.launch();
+  const ctx = await browser.newContext();
+  // Intercept the published custom.js and return the DEV theme bytes.
+  await ctx.route(/\/cdn\/shop\/t\/\d+\/assets\/custom\.js(\?|$)/, route => {
+    if (/boost-sd/.test(route.request().url())) return route.continue();
+    route.fulfill({ status: 200, contentType: 'application/javascript', body: DEV_JS });
+  });
+  const page = await ctx.newPage();
+  const errors = [];
+  page.on('pageerror', e => errors.push('PAGEERROR: ' + e.message));
+  page.on('console', m => { if (m.type() === 'error' && !/Content Security Policy|Failed to load resource/.test(m.text())) errors.push('CONSOLE.error: ' + m.text()); });
+
+  await page.goto(URL, { waitUntil: 'domcontentloaded', timeout: 60000 });
+  await page.waitForSelector('.mm_quantity input[name="quantity"]', { state: 'attached', timeout: 30000 });
+
+  // Stamp data-order-* onto qty inputs (what the DEV product-form-content.liquid renders).
+  await page.evaluate(({ step, min }) => {
+    document.querySelectorAll('.mm_quantity .qty').forEach(q => {
+      q.setAttribute('data-order-step', String(step));
+      q.setAttribute('data-order-min', String(min));
+    });
+  }, { step: EXPECT_STEP, min: EXPECT_MIN });
+
+  await page.waitForTimeout(1800);
+  // Select roll variant
+  await page.evaluate(() => {
+    const sel = document.querySelector('.product-select');
+    if (sel && sel.tagName === 'SELECT') {
+      const opts = Array.from(sel.options);
+      const roll = opts.find(o => !/sample/i.test(o.textContent) && !/-sample$/i.test(o.getAttribute('data-sku') || '')) || opts[0];
+      sel.value = roll.value; sel.dispatchEvent(new Event('change', { bubbles: true }));
+      if (window.jQuery) window.jQuery(sel).trigger('change');
+    }
+  });
+  await page.waitForTimeout(1600); // past the leftover 1000ms liquid reset
+
+  // The published page's INLINE product-form-content min-enforcement handlers (blur/change/
+  // setTimeout snap-to-min) are still bound and fight the stepper — on the DEV theme the fixed
+  // product-form-content.liquid replaces them. Remove those competing document-delegated
+  // handlers so this test reflects the dev-theme environment (custom.js stepper only).
+  await page.evaluate(() => {
+    const $ = window.jQuery;
+    // Rebind: keep ONLY the custom.js stepper handlers. The inline liquid ones were bound as
+    // jQuery(document).on('click','.minus',...) / on('blur','.mm_quantity .input-text.qty',...)
+    // — we can't selectively remove by function ref, but the dev liquid simply doesn't emit them,
+    // so neutralize by removing the specific selectors the inline block used.
+    $(document).off('click', '.minus');                       // inline min-enforcement (plus is untouched)
+    $(document).off('blur', '.mm_quantity .input-text.qty');  // inline blur snap
+  });
+
+  const getState = async () => page.evaluate(() => {
+    const q = document.querySelector('.mm_quantity .qty');
+    const h = document.querySelector('.mm_quantity .hquantity');
+    return { qty: q ? q.value : null, hquantity: h ? h.getAttribute('value') : null, step: q ? q.getAttribute('data-order-step') : null, min: q ? q.getAttribute('data-order-min') : null };
+  });
+  const clickPlus = async () => { await page.click('.mm_quantity .plus'); await page.waitForTimeout(160); };
+  const clickMinus = async () => { await page.click('.mm_quantity .minus'); await page.waitForTimeout(160); };
+
+  const out = { label: LABEL, url: URL, expectStep: EXPECT_STEP, expectMin: EXPECT_MIN, steps: [] };
+  out.steps.push(['initial', await getState()]);
+  await clickPlus(); out.steps.push(['after +', await getState()]);
+  await clickPlus(); out.steps.push(['after +', await getState()]);
+  await clickMinus(); out.steps.push(['after -', await getState()]);
+  await clickMinus(); out.steps.push(['after -', await getState()]);
+  await clickMinus(); out.steps.push(['after - (floor)', await getState()]);
+  await page.evaluate((m) => { const q = document.querySelector('.mm_quantity .qty'); q.value = String(m + 1); window.jQuery(q).trigger('change'); }, EXPECT_MIN);
+  await page.waitForTimeout(160);
+  out.steps.push(['typed min+1 -> snap', await getState()]);
+  out.errors = errors;
+  console.log(JSON.stringify(out, null, 2));
+  await browser.close();
+})().catch(e => { console.error('FATAL', e); process.exit(1); });
diff --git a/shopify/theme-fixes/custom-js-qty-2026-06-30/verify-logic.cjs b/shopify/theme-fixes/custom-js-qty-2026-06-30/verify-logic.cjs
new file mode 100644
index 00000000..60291cbd
--- /dev/null
+++ b/shopify/theme-fixes/custom-js-qty-2026-06-30/verify-logic.cjs
@@ -0,0 +1,138 @@
+// Verify the qty-stepper FIX LOGIC against the real product DOM.
+// Preview-asset routing serves the published theme's custom.js, so instead we
+// (1) load the real page, (2) apply the liquid change (add data-order-step / data-order-min
+// exactly as the fixed product-form-content.liquid now renders them), (3) tear down the OLD
+// document-delegated .plus/.minus handlers and bind the NEW fixed handler (byte-identical to
+// what was pushed to assets/custom.js.liquid on the dev theme), then (4) drive the buttons.
+const { chromium } = require(require('path').join(process.env.HOME, 'Projects/Designer-Wallcoverings/node_modules/playwright'));
+const fs = require('fs');
+const path = require('path');
+
+const HANDLE = process.argv[2] || 'dwtt-80064-designer-wallcoverings-los-angeles';
+const EXPECT_STEP = process.argv[3] ? Number(process.argv[3]) : 2;
+const EXPECT_MIN = process.argv[4] ? Number(process.argv[4]) : 2;
+const LABEL = process.argv[5] || HANDLE;
+const URL = `https://www.designerwallcoverings.com/products/${HANDLE}`;
+
+(async () => {
+  const browser = await chromium.launch();
+  const page = await (await browser.newContext()).newPage();
+  const errors = [];
+  page.on('pageerror', e => errors.push('PAGEERROR: ' + e.message));
+  page.on('console', m => { if (m.type() === 'error' && !/Content Security Policy|Failed to load resource/.test(m.text())) errors.push('CONSOLE.error: ' + m.text()); });
+
+  await page.goto(URL, { waitUntil: 'domcontentloaded', timeout: 60000 });
+  await page.waitForSelector('.mm_quantity input[name="quantity"]', { state: 'attached', timeout: 30000 });
+  await page.waitForTimeout(1800);
+
+  // Select the roll (non-sample) variant like a shopper would.
+  await page.evaluate(() => {
+    const sel = document.querySelector('.product-select');
+    if (sel && sel.tagName === 'SELECT') {
+      const opts = Array.from(sel.options);
+      const roll = opts.find(o => !/sample/i.test(o.textContent) && !/-sample$/i.test(o.getAttribute('data-sku') || '')) || opts[0];
+      sel.value = roll.value; sel.dispatchEvent(new Event('change', { bubbles: true }));
+      if (window.jQuery) window.jQuery(sel).trigger('change');
+    }
+  });
+  await page.waitForTimeout(1200);
+
+  // Apply the liquid fix + swap in the fixed handler.
+  await page.evaluate(({ step, min }) => {
+    const $ = window.jQuery;
+    // (a) liquid fix: stamp pristine data-order-* onto every qty input (what the fixed liquid emits)
+    document.querySelectorAll('.mm_quantity .qty').forEach(q => {
+      q.setAttribute('data-order-step', String(step));
+      q.setAttribute('data-order-min', String(min));
+    });
+    // (b) tear down the OLD delegated plus/minus handlers (published theme) so only ours runs
+    $(document).off('click', '.plus, .minus');
+    $(document).off('click', '.minus');   // liquid min-enforcement minus handler
+    $(document).off('change blur', '.mm_quantity .qty');
+    $(document).off('blur', '.mm_quantity .input-text.qty');
+
+    // (c) bind the NEW fixed handler (mirror of assets/custom.js.liquid, DWTT lines 39+)
+    $(document).on('click', '.plus, .minus', async function (event) {
+      event.preventDefault();
+      var $btn = $(this);
+      try {
+        var $wrap = $btn.closest('.mm_quantity');
+        var a = $wrap.find('.qty');
+        if (!a.length) return;
+        var stp = parseFloat(a.attr('data-order-step'));
+        if (!isFinite(stp) || stp <= 0) stp = parseFloat(a.attr('step'));
+        if (!isFinite(stp) || stp <= 0) stp = 1;
+        var mn = parseFloat(a.attr('data-order-min'));
+        if (!isFinite(mn) || mn <= 0) mn = parseFloat(a.attr('min'));
+        if (!isFinite(mn) || mn <= 0) mn = stp;
+        var maxRaw = a.attr('max');
+        var mx = parseFloat(maxRaw);
+        var hasMax = (maxRaw != null && maxRaw !== '' && isFinite(mx));
+        var decimals = String(stp).getDecimals();
+        var current = parseFloat(a.val());
+        if (!isFinite(current)) current = mn;
+        var next;
+        if ($btn.is('.plus')) { next = current + stp; if (hasMax && next > mx) next = mx; }
+        else { next = current - stp; if (next < mn) next = mn; }
+        var gridSteps = Math.round((next - mn) / stp);
+        if (gridSteps < 0) gridSteps = 0;
+        next = mn + gridSteps * stp;
+        if (hasMax && next > mx) next = mx;
+        var nextStr = next.toFixed(decimals);
+        a.val(nextStr);
+        $wrap.find('.hquantity').attr('value', nextStr).val(nextStr);
+      } catch (err) { if (window.console && console.warn) console.warn('qty stepper error:', err); }
+    });
+    $(document).on('change blur', '.mm_quantity .qty', function () {
+      try {
+        var $wrap = $(this).closest('.mm_quantity');
+        var a = $(this);
+        var stp = parseFloat(a.attr('data-order-step'));
+        if (!isFinite(stp) || stp <= 0) stp = parseFloat(a.attr('step'));
+        if (!isFinite(stp) || stp <= 0) stp = 1;
+        var mn = parseFloat(a.attr('data-order-min'));
+        if (!isFinite(mn) || mn <= 0) mn = parseFloat(a.attr('min'));
+        if (!isFinite(mn) || mn <= 0) mn = stp;
+        var v = parseFloat(a.val());
+        if (!isFinite(v) || v < mn) v = mn;
+        var gridSteps = Math.round((v - mn) / stp);
+        if (gridSteps < 0) gridSteps = 0;
+        var snapped = mn + gridSteps * stp;
+        var snappedStr = snapped.toFixed(String(stp).getDecimals());
+        if (a.val() !== snappedStr) a.val(snappedStr);
+        $wrap.find('.hquantity').attr('value', snappedStr).val(snappedStr);
+      } catch (err) {}
+    });
+
+    // Set the visible value to min like the fixed variant-handler / liquid would.
+    document.querySelectorAll('.mm_quantity .qty').forEach(q => {
+      if (parseFloat(q.value) < min) { q.value = String(min); }
+      const h = q.closest('.mm_quantity').querySelector('.hquantity');
+      if (h) { h.setAttribute('value', q.value); h.value = q.value; }
+    });
+  }, { step: EXPECT_STEP, min: EXPECT_MIN });
+
+  const getState = async () => page.evaluate(() => {
+    const q = document.querySelector('.mm_quantity .qty');
+    const h = document.querySelector('.mm_quantity .hquantity');
+    return { qty: q ? q.value : null, hquantity: h ? (h.getAttribute('value')) : null };
+  });
+  const clickPlus = async () => { await page.evaluate(() => window.jQuery('.mm_quantity .plus').first().trigger('click')); await page.waitForTimeout(120); };
+  const clickMinus = async () => { await page.evaluate(() => window.jQuery('.mm_quantity .minus').first().trigger('click')); await page.waitForTimeout(120); };
+
+  await page.waitForTimeout(1400); // let leftover liquid setTimeout(1000ms) reset fire BEFORE we start clicking
+  const out = { label: LABEL, url: URL, expectStep: EXPECT_STEP, expectMin: EXPECT_MIN, steps: [] };
+  out.steps.push(['initial', await getState()]);
+  await clickPlus(); out.steps.push(['after +', await getState()]);
+  await clickPlus(); out.steps.push(['after +', await getState()]);
+  await clickMinus(); out.steps.push(['after -', await getState()]);
+  await clickMinus(); out.steps.push(['after -', await getState()]);
+  await clickMinus(); out.steps.push(['after - (floor)', await getState()]);
+  // invalid-typed value snaps
+  await page.evaluate((m) => { const q=document.querySelector('.mm_quantity .qty'); q.value=String(m+1); window.jQuery(q).trigger('change'); }, EXPECT_MIN);
+  await page.waitForTimeout(120);
+  out.steps.push(['typed min+1 -> snap', await getState()]);
+  out.errors = errors;
+  console.log(JSON.stringify(out, null, 2));
+  await browser.close();
+})().catch(e => { console.error('FATAL', e); process.exit(1); });

← 67ffd784 auto-save: 2026-06-30T20:13:04 (3 files) — pending-approval/  ·  back to Designer Wallcoverings  ·  DWTT qty stepper: add combined diffs, clean harness proof + 08928754 →