{% comment %}
Aftersell Carousel (Splide)
Type: Section — save to sections/aftersell-upsell-carousel.liquid
{% endcomment %}
{% if product %}
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@splidejs/splide@4.1.4/dist/css/splide-core.min.css">
<script src="https://cdn.jsdelivr.net/npm/@splidejs/splide@4.1.4/dist/js/splide.min.js" defer></script>
<div id="aftersell-upsell-{{ section.id }}" class="aftersell-upsell-carousel" style="display:none" {{ section.shopify_attributes }}>
<h2 class="aftersell-heading">{{ section.settings.heading | default: 'You might also like' }}</h2>
<div class="aftersell-carousel-wrapper">
<button class="aftersell-arrow aftersell-arrow--prev" aria-label="Previous" disabled>←</button>
<div class="aftersell-track-container">
<div class="splide" id="aftersell-splide-{{ section.id }}">
<div class="splide__track">
<ul class="splide__list">
<li class="splide__slide"><div class="aftersell-skeleton"></div></li>
<li class="splide__slide"><div class="aftersell-skeleton"></div></li>
<li class="splide__slide"><div class="aftersell-skeleton"></div></li>
<li class="splide__slide"><div class="aftersell-skeleton"></div></li>
</ul>
</div>
</div>
</div>
<button class="aftersell-arrow aftersell-arrow--next" aria-label="Next" disabled>→</button>
</div>
</div>
<style>
.aftersell-upsell-carousel { font-family: Modernist, sans-serif; max-width: 1300px; margin: 0 auto; padding: 24px 0 0; box-sizing: border-box; }
.aftersell-heading { font-family: Modernist, sans-serif; font-size: 30px; font-weight: 700; line-height: 45px; color: #0C0A09; margin: 0; }
.aftersell-carousel-wrapper { display: flex; align-items: center; gap: 8px; }
.aftersell-track-container { overflow: hidden; flex: 1; min-width: 0; }
.aftersell-card { display: flex; flex-direction: column; box-sizing: border-box; height: 100%; }
.aftersell-card-link { text-decoration: none; color: inherit; display: block; flex: 1; }
.aftersell-card-image { aspect-ratio: 1; overflow: hidden; background: #f5f5f5; border-radius: 8px 8px 0 0; position: relative; }
.aftersell-card-image img { width: 100%; height: 100%; object-fit: cover; display: block; transition: transform 0.3s ease; }
.aftersell-card-image:hover img { transform: scale(1.04); }
.aftersell-card-badge { position: absolute; top: 10px; left: 8px; background: #c60006; color: #fff; font-size: 11px; font-weight: 500; padding: 4px 8px; border-radius: 16px; z-index: 1; }
.aftersell-card-body { padding: 6px 0 0; display: flex; flex-direction: column; }
.aftersell-card-vendor { font-size: 13px; font-weight: 550; text-transform: uppercase; color: #1d4481; margin: 0; }
.aftersell-card-title { font-size: 13px; font-weight: 700; color: #0C0A09; margin: 0; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.aftersell-card-price { font-size: 13px; color: #0C0A09; margin: 0; display: flex; gap: 6px; align-items: center; flex-wrap: wrap; }
.aftersell-card-price--compare { text-decoration: line-through; color: #595959; }
.aftersell-card-price--sale { font-weight: 700; color: #c60006; }
.aftersell-card-actions { padding: 6px 0 0; display: flex; flex-direction: column; margin-top: auto; }
.aftersell-variant-select { width: 100%; font-size: 13px; padding: 0.35rem 0.5rem; border: 1px solid #DBDBDB; border-radius: 4px; background: #fff; color: #0C0A09; cursor: pointer; }
.aftersell-cta { padding: 16px 12px; background: #c50007; color: #FFFFFF; border: none; border-radius: 5px; font-size: 16px; font-weight: 700; cursor: pointer; width: 100%; display: flex; align-items: center; justify-content: center; transition: background 0.2s, opacity 0.2s; }
.aftersell-cta:hover:not(:disabled) { opacity: 0.85; }
.aftersell-cta:disabled { opacity: 0.5; cursor: default; }
.aftersell-cta--added { background: #1d4481; }
.aftersell-cta--unavailable { background: #999; cursor: default; }
.aftersell-arrow { width: 30px; height: 90px; background: rgba(255,255,255,0.6); border: 1px solid #DBDBDB; border-radius: 5px; cursor: pointer; display: flex; align-items: center; justify-content: center; flex-shrink: 0; padding: 0; }
.aftersell-arrow:disabled { opacity: 0.3; cursor: default; }
.aftersell-skeleton { border-radius: 8px; background: linear-gradient(90deg, #f0f0f0 25%, #e0e0e0 50%, #f0f0f0 75%); background-size: 200% 100%; animation: aftersell-shimmer 1.4s infinite; aspect-ratio: 0.75; width: 100%; }
@keyframes aftersell-shimmer { 0% { background-position: 200% 0; } 100% { background-position: -200% 0; } }
#aftersell-splide-{{ section.id }} .splide__arrows,
#aftersell-splide-{{ section.id }} .splide__pagination { display: none !important; }
@media (max-width: 768px) {
.aftersell-upsell-carousel { padding: 16px 16px 0; }
.aftersell-arrow { display: none; }
.aftersell-carousel-wrapper { gap: 0; }
}
</style>
<script>
(function() {
var BACKEND_URL = 'https://start.aftersell.app';
var API_KEY = 'YOUR_STRATEGY_API_KEY';
var STRATEGY_ID = {{ section.settings.strategy_id | json }};
var SHOP_DOMAIN = {{ shop.permanent_domain | json }};
var CTA_LABEL = '{{ section.settings.cta_label | default: "Add to cart" }}';
var MAX_PRODUCTS = {{ section.settings.max_products | default: 8 }};
var CURRENCY = {{ shop.currency | default: "USD" | json }};
var SECTION_ID = {{ section.id | json }};
if (!SHOP_DOMAIN || !STRATEGY_ID) return;
var fmt;
try {
fmt = new Intl.NumberFormat('en-US', { style: 'currency', currency: CURRENCY });
} catch(e) {
fmt = { format: function(n) { return '$' + parseFloat(n).toFixed(2); } };
}
function money(str) { return fmt.format(parseFloat(str) || 0); }
function gidToNumeric(gid) {
return gid ? String(gid).split('/').pop() : null;
}
function priceHtml(price, compareAt) {
var hasSale = compareAt && parseFloat(compareAt) > parseFloat(price);
return hasSale
? '<span class="aftersell-card-price--compare">' + money(compareAt) + '</span>'
+ '<span class="aftersell-card-price--sale">' + money(price) + '</span>'
: '<span class="aftersell-card-price--current">' + money(price) + '</span>';
}
var productContext = {
productId: 'gid://shopify/Product/{{ product.id }}',
variantId: 'gid://shopify/ProductVariant/{{ product.selected_or_first_available_variant.id }}',
quantity: 1,
price: {{ product.price | divided_by: 100.0 }},
handle: {{ product.handle | json }},
title: {{ product.title | json }},
vendor: {{ product.vendor | json }},
productType: {{ product.type | json }},
tags: {{ product.tags | json }},
collections: [{% for col in product.collections %}'gid://shopify/Collection/{{ col.id }}'{% unless forloop.last %},{% endunless %}{% endfor %}],
sellingPlan: {% if product.selected_selling_plan %}'subscription'{% else %}'one-time'{% endif %}
};
var cartContext = {
subtotal: {{ cart.total_price | divided_by: 100.0 }},
itemCount: {{ cart.item_count }},
lineCount: {{ cart.items.size }}
};
{% if customer %}
var customerContext = {
customerId: 'gid://shopify/Customer/{{ customer.id }}',
tags: {{ customer.tags | json }},
{% if customer.default_address.country_code %}countryCode: {{ customer.default_address.country_code | json }},{% endif %}
{% if customer.default_address.province_code %}provinceCode: {{ customer.default_address.province_code | json }},{% endif %}
locale: {{ request.locale.iso_code | json }},
orderCount: {{ customer.orders_count }},
totalSpent: {{ customer.total_spent | times: 1.0 }},
acceptsMarketing: {{ customer.accepts_marketing }}
};
{% endif %}
var blockEl = document.getElementById('aftersell-upsell-' + SECTION_ID);
var splideEl = document.getElementById('aftersell-splide-' + SECTION_ID);
var list = splideEl.querySelector('.splide__list');
var prevBtn = blockEl.querySelector('.aftersell-arrow--prev');
var nextBtn = blockEl.querySelector('.aftersell-arrow--next');
var splideInstance = null;
function getCartToken() {
var t = {{ cart.token | json }};
if (t) return Promise.resolve(t);
return fetch('/cart.js').then(function(r){ return r.json(); }).then(function(c){ return c.token || null; }).catch(function(){ return null; });
}
function evaluate() {
getCartToken().then(function(cartToken) {
if (!cartToken) return;
return fetch(BACKEND_URL + '/api/public/strategy/evaluate', {
method: 'POST',
headers: { 'Content-Type': 'application/json', 'X-Strategy-Api-Key': API_KEY },
body: JSON.stringify({
shopDomain: SHOP_DOMAIN,
strategyId: STRATEGY_ID,
context: {
products: [productContext],
cartToken: cartToken,
cart: cartContext.itemCount > 0 ? cartContext : undefined,
{% if customer %}customer: customerContext,{% endif %}
session: { currencyCode: CURRENCY }
}
})
});
}).then(function(res) { if (!res) return; return res.json(); })
.then(function(data) {
if (!data || !data.success || !data.products || !data.products.length) {
clearSkeletons(); return;
}
renderCards(data.products.slice(0, MAX_PRODUCTS));
}).catch(function(err) {
console.error('[AfterSell carousel] Error:', err);
clearSkeletons();
});
}
function renderCards(products) {
if (!products || !products.length) {
blockEl.style.display = 'none'; return;
}
blockEl.style.display = '';
list.innerHTML = products.map(function(p, i) {
var img = p.images && p.images[0] ? p.images[0].src : '';
var alt = (p.images && p.images[0] && p.images[0].altText) || p.title;
var variants = p.variants || [];
var availableVariants = variants.filter(function(v) {
return v.availableForSale !== false;
});
var isDefaultVariant = variants.length === 1 && variants[0].title === 'Default Title';
var showSelect = !isDefaultVariant && availableVariants.length >= 1;
var firstVariant = availableVariants.length ? availableVariants[0] : null;
var firstVariantId = firstVariant ? gidToNumeric(firstVariant.variantId) : null;
var anyAvailable = availableVariants.length > 0;
var onSale = p.compareAtPrice && parseFloat(p.compareAtPrice) > parseFloat(p.price);
var badge = '';
if (onSale) {
var pct = Math.round((1 - parseFloat(p.price) / parseFloat(p.compareAtPrice)) * 100);
badge = '<span class="aftersell-card-badge">Save ' + pct + '%</span>';
}
var vendor = p.vendor ? '<p class="aftersell-card-vendor">' + p.vendor + '</p>' : '';
var selectHtml = '';
if (showSelect) {
var options = availableVariants.map(function(v) {
return '<option value="' + gidToNumeric(v.variantId) + '"'
+ ' data-price="' + (v.price || p.price) + '"'
+ ' data-compare="' + (v.compareAtPrice || '') + '"'
+ '>' + v.title + '</option>';
}).join('');
selectHtml = '<select class="aftersell-variant-select" aria-label="Select variant">' + options + '</select>';
}
var atcLabel = anyAvailable ? CTA_LABEL : 'Sold Out';
var atcClass = anyAvailable ? 'aftersell-cta' : 'aftersell-cta aftersell-cta--unavailable';
var atcBtn = '<button class="' + atcClass + '"'
+ (firstVariantId ? ' data-variant-id="' + firstVariantId + '"' : '')
+ (anyAvailable ? '' : ' disabled')
+ '>' + atcLabel + '</button>';
var productUrl = p.url + (p.url.indexOf('?') > -1 ? '&' : '?') + 'ref=aftersell';
return '<li class="splide__slide">'
+ '<div class="aftersell-card" data-idx="' + i + '">'
+ '<a class="aftersell-card-link" href="' + productUrl + '">'
+ '<div class="aftersell-card-image">' + badge
+ (img ? '<img src="' + img + '" alt="' + alt + '" loading="lazy">' : '')
+ '</div>'
+ '<div class="aftersell-card-body">'
+ vendor
+ '<p class="aftersell-card-title">' + p.title + '</p>'
+ '<p class="aftersell-card-price" data-price-el>' + priceHtml(p.price, p.compareAtPrice) + '</p>'
+ '</div></a>'
+ '<div class="aftersell-card-actions">'
+ selectHtml
+ atcBtn
+ '</div></div></li>';
}).join('');
attachCardEvents();
initSplide();
}
function attachCardEvents() {
list.addEventListener('change', function(e) {
if (!e.target.classList.contains('aftersell-variant-select')) return;
var select = e.target;
var card = select.closest('.aftersell-card');
var opt = select.options[select.selectedIndex];
var priceEl = card.querySelector('[data-price-el]');
var btn = card.querySelector('.aftersell-cta');
priceEl.innerHTML = priceHtml(opt.getAttribute('data-price'), opt.getAttribute('data-compare'));
btn.dataset.variantId = opt.value;
btn.disabled = false;
btn.textContent = CTA_LABEL;
btn.className = 'aftersell-cta';
});
list.addEventListener('click', function(e) {
var btn = e.target.closest('.aftersell-cta');
if (!btn || btn.disabled) return;
var variantId = btn.dataset.variantId;
if (!variantId) return;
e.preventDefault();
btn.disabled = true;
btn.textContent = 'Adding…';
fetch('/cart/add.js', {
method: 'POST',
credentials: 'same-origin',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
id: parseInt(variantId, 10),
quantity: 1,
properties: {
"_source": "Aftersell",
"_attribution": "CTA"
}
})
})
.then(function(r) {
if (!r.ok) throw new Error('Cart add failed: ' + r.status);
return r.json();
})
.then(function() {
btn.textContent = 'Added!';
btn.classList.add('aftersell-cta--added');
if (typeof window.theme !== 'undefined' && window.theme.cart && window.theme.cart.open) {
window.theme.cart.open();
}
setTimeout(function() {
btn.textContent = CTA_LABEL;
btn.classList.remove('aftersell-cta--added');
btn.disabled = false;
}, 2500);
})
.catch(function(err) {
console.error('[AfterSell carousel] Add to cart error:', err);
btn.textContent = 'Try Again';
btn.disabled = false;
});
});
}
function initSplide() {
function mount() {
if (typeof window.Splide === 'undefined') {
setTimeout(mount, 50);
return;
}
splideInstance = new Splide('#aftersell-splide-' + SECTION_ID, {
type: 'slide',
perPage: 4,
perMove: 4,
gap: '10px',
pagination: false,
arrows: false,
speed: 350,
drag: false,
breakpoints: {
768: { perPage: 2, perMove: 2, drag: 'free', snap: true },
480: { perPage: 1, perMove: 1, drag: 'free', snap: true, padding: { right: '25%' } }
}
}).mount();
function updateArrows() {
var idx = splideInstance.index;
var end = splideInstance.length - splideInstance.options.perPage;
prevBtn.disabled = idx <= 0;
nextBtn.disabled = idx >= end;
}
prevBtn.addEventListener('click', function() { splideInstance.go('<'); });
nextBtn.addEventListener('click', function() { splideInstance.go('>'); });
splideInstance.on('moved', updateArrows);
updateArrows();
}
mount();
}
function clearSkeletons() { list.innerHTML = ''; }
evaluate();
})();
</script>
{% endif %}
{% schema %}
{
"name": "Aftersell Carousel",
"settings": [
{ "type": "text", "id": "strategy_id", "label": "AfterSell Strategy ID", "default": "ADD_ID_HERE" },
{ "type": "text", "id": "heading", "label": "Heading", "default": "You might also like" },
{ "type": "text", "id": "cta_label", "label": "CTA Button Label", "default": "Add to cart" },
{ "type": "range", "id": "max_products", "label": "Max Products to Show", "default": 8, "min": 1, "max": 20, "step": 1 }
],
"presets": [{ "name": "Aftersell Carousel" }]
}
{% endschema %}