
This block reads live subscription data, so it only runs inside a real customer portal. The animation shows what subscribers see.
What it does
Shows only the relevant Smart Upsell picks and hides the rest, for a cleaner, focused offer.
Why it exists
For merchants on Smart Upsell who want a focused offer instead of the whole catalog. A few strong picks are easier to say yes to than a long row.
The code
Paste it as-is, or download tailored-upsells.html<!-- =========================================================================
SKIO CUSTOMER PORTAL (v3) - SMART UPSELL CAROUSEL FILTER (DYNAMIC)
=========================================================================
WHAT IT DOES
Shows ONLY the "priority" smart-upsell products in the customer portal
carousel and hides the extra one-time upsell products that create noise.
The remaining cards are re-packed so there is no empty space.
HOW IT KNOWS WHICH ARE PRIORITY
It keeps only the carousel cards that carry a promotion BADGE. So there
are NO product rules to maintain - it adapts automatically to each
customer and every product. It works as long as your Smart Upsell
"priority" products have a badge and the extra products do not.
---------------------------------------------------------------------
SETUP
1. In Skio Smart Upsell, add a badge to your PRIORITY products only.
(Do NOT badge the extra/one-time upsell products.)
2. In the customer portal editor:
Layout > Add Custom Extension > "HTML with embedded CSS and JavaScript"
3. Paste this ENTIRE file into that block and save.
4. Preview as a logged-in customer. In the browser Console you should see:
[SkioUpsellFilter] kept X of Y badged cards | carousels: 2
---------------------------------------------------------------------
CONFIG (only touch these if your badge changes)
- PRIORITY_BADGE_SELECTOR: how a badge is found inside a card.
- PRIORITY_BADGE_TEXT: leave "" to accept ANY badge, or set e.g.
"best offer" to require that specific badge label.
- NO_BADGE_BEHAVIOR: what to do for a customer whose carousel has no
badged (priority) cards - "show" the normal carousel, or "hide" it.
========================================================================= -->
<script type="text/plain" id="skio-upsell-code">
(function () {
"use strict";
console.log("[SkioUpsellFilter] booted");
/* ----------------------------- CONFIG ----------------------------- */
var PRIORITY_BADGE_SELECTOR = '[data-skio-name="promotion-group-badge"]';
var PRIORITY_BADGE_TEXT = ""; // "" = any badge counts; or "best offer"
var NO_BADGE_BEHAVIOR = "show"; // "show" | "hide"
/* ------------------------------------------------------------------ */
var S = {
carousel: '[data-skio-name="upsell-card-carousel"]',
slide: '[data-skio-name="upsell-card-carousel-item"]',
item: '[data-skio-name="upsell-product"]',
};
var STRIP = String.fromCharCode(8482, 174, 169); // (TM) (R) (C)
function nz(s) {
if (!s) return "";
var t = String(s), o = "", i;
for (i = 0; i < t.length; i++) { if (STRIP.indexOf(t.charAt(i)) === -1) o += t.charAt(i); }
return o.replace(/\s+/g, " ").trim().toLowerCase();
}
// Is this upsell card a "priority" product (i.e. does it carry the badge)?
function isPriority(card) {
var badges = card.querySelectorAll(PRIORITY_BADGE_SELECTOR);
if (!badges.length) return false;
if (!PRIORITY_BADGE_TEXT) return true;
var want = nz(PRIORITY_BADGE_TEXT);
return Array.prototype.some.call(badges, function (b) {
return nz(b.textContent).indexOf(want) !== -1;
});
}
function getMaxPerSlide(slide) {
var v = 0;
try { v = parseInt(getComputedStyle(slide).getPropertyValue("--skio-upsell-card-rows"), 10); } catch (e) {}
if (!v || v < 1) v = slide.querySelectorAll(S.item).length || 1;
return v;
}
// Re-pack kept cards into as few slides as possible for ONE carousel.
function reflow(carousel, keptCards) {
var slides = Array.prototype.slice.call(carousel.querySelectorAll(S.slide));
if (!slides.length) return;
var maxPerSlide = getMaxPerSlide(slides[0]);
var needed = Math.max(1, Math.ceil(keptCards.length / maxPerSlide));
var idx = 0;
slides.forEach(function (slide, i) {
if (i < needed) {
slide.style.display = "";
var placed = 0;
for (var k = 0; k < maxPerSlide && idx < keptCards.length; k++, idx++) {
slide.appendChild(keptCards[idx]); // MOVES the node
placed++;
}
slide.style.setProperty("--skio-upsell-card-rows", String(placed));
} else {
slide.style.display = "none";
}
});
var prev = carousel.querySelector('[data-skio-name="upsell-card-prev-button"]');
var nav = prev && prev.parentElement;
if (nav) nav.style.display = needed > 1 ? "" : "none";
try { window.dispatchEvent(new Event("resize")); } catch (e) {}
}
// Filter + repack a single carousel. Returns how many priority cards kept.
function filterCarousel(carousel) {
var items = carousel.querySelectorAll(S.item);
if (!items.length) return -1; // nothing rendered yet
var kept = [];
items.forEach(function (item) { if (isPriority(item)) kept.push(item); });
if (!kept.length) {
// No priority products for this customer.
if (NO_BADGE_BEHAVIOR === "hide") carousel.style.display = "none";
return 0; // "show" -> leave carousel untouched
}
items.forEach(function (item) {
item.style.display = kept.indexOf(item) !== -1 ? "" : "none";
});
carousel.style.display = "";
reflow(carousel, kept);
return kept.length;
}
var mutating = false;
function applyFilter() {
var carousels = document.querySelectorAll(S.carousel);
if (!carousels.length) return;
mutating = true;
try {
var total = 0, keptTotal = 0;
carousels.forEach(function (c) {
var n = filterCarousel(c);
if (n > 0) keptTotal += n;
total += c.querySelectorAll(S.item).length;
});
console.log("[SkioUpsellFilter] kept", keptTotal, "of", total,
"badged cards | carousels:", carousels.length);
} finally {
window.setTimeout(function () { mutating = false; }, 0);
}
}
function init() {
applyFilter();
var observer = new MutationObserver(function () {
if (mutating) return;
window.clearTimeout(init._t);
init._t = window.setTimeout(applyFilter, 100);
});
observer.observe(document.body, { childList: true, subtree: true });
}
if (document.readyState === "loading") {
document.addEventListener("DOMContentLoaded", init);
} else {
init();
}
})();
</script>
<!-- Bootstrap: promotes the inert code above into an executing script. -->
<img alt="" style="display:none"
src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7"
onload="(function(i){try{var c=document.getElementById('skio-upsell-code');if(c&&!window.__skioUpsellBooted){window.__skioUpsellBooted=true;var s=document.createElement('script');s.textContent=c.textContent;document.documentElement.appendChild(s);}}catch(e){console.error('[SkioUpsellFilter] boot failed',e);}i.remove();})(this)"
onerror="(function(i){try{var c=document.getElementById('skio-upsell-code');if(c&&!window.__skioUpsellBooted){window.__skioUpsellBooted=true;var s=document.createElement('script');s.textContent=c.textContent;document.documentElement.appendChild(s);}}catch(e){console.error('[SkioUpsellFilter] boot failed',e);}i.remove();})(this)">
How to install
- Copy the code above, or download the .html file.
- Go to
Storefront→Customer Portal→Branding→Layoutand clickAdd custom extension. - Pick
HTML with embedded CSS and JavaScriptand paste the whole file into the code editor. - Swap anything the file asks you to, like
[BRAND], product names or Quick Action IDs. The AI prompt below can walk you through it. - Save, click
+to add it to the layout, thenPublish. Check it on a test subscription’s magic link with?forcePreview=true, since some scripts don’t run in Preview.
In Smart Upsell, add a badge to your priority products only. The block keeps the badged cards and hides the rest, so leave the extras unbadged.
Customize it with AI
Paste this prompt into Claude Code or Codex. It asks for the code and your brand and product details, then writes a version for your store.
I want to add a custom extension from customizeyourportal.xyz to my subscription customer portal. Walk me through customizing it for my brand, then write the finished code. EXTENSION Name: Tailored Upsells (No. 03) Page: /blocks/tailored-upsells What it does: Shows only the relevant Smart Upsell picks and hides the rest, for a cleaner, focused offer. My portal app: Skio WHAT THE BUILDER SAYS TO CUSTOMIZE In Smart Upsell, add a badge to your priority products only. The block keeps the badged cards and hides the rest, so leave the extras unbadged. HOW TO WORK 1. Ask me to paste the full code (the "Copy code" button on the page) or to give you the path to the downloaded tailored-upsells.html. Wait until you have it. 2. Read the whole file. List every value that is specific to the original brand or needs my input: brand name and [PLACEHOLDERS], product and variant names or IDs, prices, discount codes and reward amounts, Quick Action IDs or tokens, store domains and API endpoints, colors, fonts, logos and images, and any copy that names the original brand, products or offers. Include everything in the builder's notes above. 3. Ask me for those values in one numbered list, with the current value next to each, so I can answer or say "keep". If you can open web pages, offer to pull my brand colors, fonts, logo and product names from my store's URL. 4. Don't invent stats, reviews, IDs, prices or claims. If I don't have a value, leave a clearly marked TODO in the code and tell me. 5. Write the customized version as tailored-upsells-<my-brand>.html. Change only what my brand needs. Keep the logic, behavior and structure the same, and keep it one self-contained file (HTML, <style> and <script>). 6. Finish with a short list of what you changed, any TODOs left, and these install steps for Skio: 1. Go to `Storefront` → `Customer Portal` → `Branding` → `Layout` and click `Add custom extension`. 2. Pick `HTML with embedded CSS and JavaScript` and paste the whole file into the code editor. 3. Save, click `+` to add it to the layout, then `Publish`. Check it on a test subscription’s magic link with `?forcePreview=true`, since some scripts don’t run in Preview.
Blocks are shared as-is by the people who built them. Read the code before you add it to your portal, and test it with a test subscriber first.
