card-product.liquid
{% comment %} add option h {% endcomment %}
{% comment %} {%- if has_size_option -%} {% endcomment %}
<product-form data-section-id="{{ section.id }}" data-product-id="{{ card_product.id }}">
{% form 'product', card_product, class: 'js-card-add-to-cart' %}
<div class="card-variant-sizes">
{%- for option in card_product.options_with_values -%}
{% if option.name == 'Size' or option.name == 'Größe' or option.name == 'size' or option.name == 'Contents' or option.name == 'Inhalt' %}
{%- for value in option.values -%}
{%- assign variant_available = false -%}
{%- for v in card_product.variants -%}
{%- if v.options contains value and v.available -%}
{%- assign variant_available = true -%}
{%- endif -%}
{%- endfor -%}
<label class="size-label {% unless variant_available %}soldout{% endunless %}">
<input
type="radio" data-option-index="{{ forloop.parentloop.index }}"
name="variant-{{ card_product.id }}"
value="{{ value }}"
{% if option.selected_value == value %}checked{% endif %}
{% unless variant_available %}disabled{% endunless %}
>
<span>{{ value }}</span>
{%- assign img_url = blank -%}
{%- for variant in card_product.variants -%}
{%- if variant.options contains value -%}
{%- if variant.image -%}
{%- assign img_url = variant.image | image_url: width: 600 -%}
{%- endif -%}
{%- break -%}
{%- endif -%}
{%- endfor -%}
{%- if img_url == blank and product.featured_image -%}
{%- assign img_url = card_product.featured_image | image_url: width: 600 -%}
{%- endif -%}
<span
data-image="{{ img_url }}"
data-product-url="{{ product.url }}"
data-title="{{ value }}"
class="color-custom color-option {% if is_white %}border-white-css{% endif %}"
style="{{ bg_style }}">
</span>
</label>
{%- endfor -%}
{% endif %}
{%- endfor -%}
</div>
<!-- hidden variant id input (updated by JS) -->
<input type="hidden" name="id" value="{{ card_product.selected_or_first_available_variant.id }}">
<!-- Add to cart button -->
<button type="submit" name="add" class=" add-to-cart-btn">
{{ 'shopping-bag-custom.svg' | inline_asset_content }}
</button>
{% endform %}
</product-form>
{% comment %} {%- endif -%} {% endcomment %}
<script type="application/json" class="product-variants-json">
{{ card_product.variants | json }}
</script>
{% comment %} add option h {% endcomment %}
<div class="card-price" data-price-wrapper>
{% render 'price', prod
js
<script>
{% comment %} function formatMoney(cents) {
if (cents === null || cents === undefined) return '';
return (cents / 100).toLocaleString("en-US", {
style: "currency",
currency: {{ cart.currency.iso_code | json }}
});
} {% endcomment %}
function formatMoney(cents) {
if (cents === null || cents === undefined) return '';
return (cents / 100).toLocaleString("de-DE", {
style: "currency",
currency: {{ cart.currency.iso_code | json }}
});
}
function updateCardVariant(inputEl) {
if (!inputEl) return;
const form = inputEl.closest("product-form");
if (!form) return;
const root = form.parentElement;
const label = inputEl.closest('.size-label');
// ACTIVE label (only inside this product card)
root.querySelectorAll('.size-label.active').forEach(l => l.classList.remove('active'));
if (label) label.classList.add('active');
// Find variants JSON (same parentElement in your markup)
const jsonEl = root.querySelector(".product-variants-json");
if (!jsonEl) return;
const variants = JSON.parse(jsonEl.textContent || '[]');
if (!variants.length) return;
const selectedValue = (inputEl.value || '').trim();
if (!selectedValue) return;
//from your input
const optIndex = parseInt(inputEl.getAttribute('data-option-index') || '1', 10);
const matched = variants.find(v => {
const o1 = (v.option1 || '').trim();
const o2 = (v.option2 || '').trim();
const o3 = (v.option3 || '').trim();
if (optIndex === 1) return o1 === selectedValue;
if (optIndex === 2) return o2 === selectedValue;
if (optIndex === 3) return o3 === selectedValue;
return false;
});
if (!matched) return;
// Update hidden variant id (Add to cart uses this!)
const idInput = form.querySelector('input[name="id"]');
if (idInput) idInput.value = matched.id;
// Update price
const wrapper = root.querySelector("[data-price-wrapper]");
if (wrapper) {
const cmp = matched.compare_at_price;
const pr = matched.price;
wrapper.innerHTML = (cmp && cmp > pr)
? `<div class="price price--on-sale">
<div class="price__sale">
<s class="price-item price-item--regular">${formatMoney(cmp)}</s>
<span class="price-item price-item--sale">${formatMoney(pr)}</span>
</div>
</div>`
: `<div class="price"><div class="price__regular">
<span class="price-item price-item--regular">${formatMoney(pr)}</span>
</div></div>`;
}
}
// 1) Normal change (works when selecting a different option)
document.addEventListener("change", function(e) {
if (!e.target.matches('[name^="variant-"]')) return;
updateCardVariant(e.target);
});
// clicking on an already-checked radio should still update price/id
document.addEventListener("click", function(e) {
const label = e.target.closest('.card-variant-sizes .size-label');
if (!label) return;
const input = label.querySelector('input[type="radio"][name^="variant-"]');
if (!input) return;
// If it's already checked, "change" won't fire → update manually
if (input.checked) {
updateCardVariant(input);
} else {
// Ensure check + then update
input.checked = true;
updateCardVariant(input);
}
});
Optional: auto-initialize on page load so default (50ml) shows correct price immediately
document.addEventListener("DOMContentLoaded", function() {
document.querySelectorAll('product-form').forEach(function(pf){
const input = pf.querySelector('input[type="radio"][name^="variant-"]:checked');
if (input) updateCardVariant(input);
});
});
</script>
main.js
// cusom variant //
// Custom variant image preview/persist per product card
// - Hover: preview only, revert to current "base" image on mouseleave
// - Click: persist, and update "base" image to the clicked option
function setCardImages($card, url) {
var $mediaImgs = $card.find('.media--hover-effect img');
$mediaImgs.each(function () {
$(this).attr('src', url).attr('srcset', url);
});
}
function cacheBaseImages($card) {
var $mediaImgs = $card.find('.media--hover-effect img');
// Cache base src/srcset once
$mediaImgs.each(function () {
var $img = $(this);
if (!$img.attr('data-base-src')) $img.attr('data-base-src', $img.attr('src') || '');
if (!$img.attr('data-base-srcset')) $img.attr('data-base-srcset', $img.attr('srcset') || '');
});
}
function updateBaseImagesTo($card, url) {
var $mediaImgs = $card.find('.media--hover-effect img');
$mediaImgs.each(function () {
var $img = $(this);
$img.attr('data-base-src', url);
$img.attr('data-base-srcset', url);
});
}
function revertToBase($card) {
var $mediaImgs = $card.find('.media--hover-effect img');
$mediaImgs.each(function () {
var $img = $(this);
var baseSrc = $img.attr('data-base-src');
var baseSrcset = $img.attr('data-base-srcset');
if (typeof baseSrc !== 'undefined') $img.attr('src', baseSrc);
if (typeof baseSrcset !== 'undefined') $img.attr('srcset', baseSrcset);
});
}
// Hover preview
$(document).on('mouseenter', '.card-variant-sizes .size-label', function () {
var $label = $(this);
var $swatch = $label.find('.color-custom.color-option');
var newImage = $swatch.attr('data-image');
if (!newImage) return;
var $card = $label.closest('.card');
cacheBaseImages($card);
setCardImages($card, newImage);
$card.find('.color-custom.color-option').removeClass('selected');
$swatch.addClass('selected');
}).on('mouseleave', '.card-variant-sizes .size-label', function () {
var $label = $(this);
var $card = $label.closest('.card');
// Always revert to the current base image (which may be updated by click)
revertToBase($card);
// Restore "selected" state based on active/checked option (optional)
var $active = $card.find('.card-variant-sizes .size-label.active .color-custom.color-option');
$card.find('.color-custom.color-option').removeClass('selected');
if ($active.length) $active.addClass('selected');
});
// Click persist + update base
$(document).on('click', '.card-variant-sizes .size-label', function () {
var $label = $(this);
var $swatch = $label.find('.color-custom.color-option');
var newImage = $swatch.attr('data-image');
if (!newImage) return;
var $card = $label.closest('.card');
cacheBaseImages($card);
// Update UI active state (optional)
$card.find('.card-variant-sizes .size-label').removeClass('active');
$label.addClass('active');
// Persist image
setCardImages($card, newImage);
// IMPORTANT: update base image to the clicked image
updateBaseImagesTo($card, newImage);
$card.find('.color-custom.color-option').removeClass('selected');
$swatch.addClass('selected');
});
// cusom variant //
csss
/*** CARD PRODUCT ***/
.size-label {
border: none;
padding: 0;
font-size: 0.875rem;
position: relative;
cursor: pointer;
color: #151515b3;
background: #F7F7F7;
margin-right: 0.5rem;
height: 1.5rem;
line-height: 1.5rem;
text-align: center;
border-radius: 1.25rem;
padding-left: 1rem;
padding-right: 1rem;
letter-spacing: 0.28px !important;
}
.size-label input {
display: none;
}
.size-label.soldout {
opacity: .2;
cursor: not-allowed;
}
body .predictive-search--search-template {
background: #fff;
}
.card-price {
margin-top: 0 !important;
}
.size-label input:checked+span {
/* color:#151515; */
}
.card__information {
position: relative;
}
button.add-to-cart-btn {
border: 0;
width: 2.1875rem;
height: 2.1875rem;
line-height: 2.1875rem;
text-align: center;
color: #fff;
font-size: 2.25rem;
font-weight: 400;
font-family: var(--font-body-family);
cursor: pointer;
border-radius: 50%;
padding: 0;
position: absolute;
right: 1rem;
opacity: 0;
z-index: 9;
display: flex;
align-items: center;
justify-content: center;
background: transparent linear-gradient(180deg, #161738, #111472) 0% 0% no-repeat padding-box;
bottom: 1rem;
}
.card-variant-sizes {
position: absolute;
top: 2.2rem;
background: transparent;
width: 100%;
display: flex;
justify-content: flex-start;
height: 2.81rem;
line-height: 2.81rem !important;
padding-left: 0;
z-index: 9;
opacity: 0;
}
.grid__item:hover .card-variant-sizes ,.grid__item:hover button.add-to-cart-btn {
opacity: 1;
}
.grid__item .has-size:hover .card-information-sub-title {
opacity: 0;
}
label.size-label.active {
background: #E6E6E6;
}
label.size-label.active,
.size-label:hover {
background: #ccc;
color: #000;
}
.quick-add.no-js-hidden {
display: none !important;
}
/*** CARD PRODUCT ***/