Custom blocks let you add elements to your slide cart that aren't already built in — payment method logos, social proof badges, countdown timers, shipping notices, USP icons, and more. Write them in HTML and CSS, drop them into your slide cart, and position them exactly where you want.
What you can build
A custom block is a flexible container, so the use cases are broad. A few popular ideas:
Payment method logos — show accepted cards and wallets to build checkout confidence
Social proof — review snippets, star ratings, or "X customers bought this today"
Countdown timers — free shipping cutoff, end-of-sale urgency
Trust marks — secure checkout badges, phone support widget
Scratch card — let visitors scratch a card and reveal a random mystery discount
Shipping & returns notices — free shipping thresholds, easy returns guarantee
USP icons — quick visual reminders of your key selling points
Promotional banners — campaign messages or seasonal offers
Where custom blocks live
You get one custom block for each of two slide cart areas:
Body — appears alongside your cart items
Footer — sits near the checkout button
You can fine-tune the exact order within each area in Layout settings.
Add a custom block
Open your slide cart settings.
Find the Custom block component in either the body or footer section.
Paste your HTML and CSS into the code field.
Save your changes.
Drag the block to your preferred spot in Layout settings.
Using JavaScript
Custom blocks don't execute code inside <script> tags, but they do support JavaScript through inline event attributes like onclick, onmouseover, or onload. That's enough for many small interactions:
<button onclick="alert('Free shipping on orders over $50!')" style="background: #000; color: #fff; padding: 8px 16px; border: none; border-radius: 4px; cursor: pointer;">Shipping info</button>If your use case needs something heavier — fetching live data, complex state, third-party SDKs — let us know. We're tracking interest in fuller JavaScript support and may add a dedicated JS field based on what merchants ask for.
Preview before going live
You can save a custom block, keep the feature turned off, and still preview how it looks using a special preview link. The standard preview button in the navigation won't show the block while the feature is off — this dedicated link is the only way to see it without exposing it to shoppers.
This is useful for staging changes, getting approval from teammates, or testing on real devices before going live.
Good to know
HTML and CSS are fully supported
<script>tags are not parsed — use inline event attributes for JavaScriptOne custom block per zone (body + footer = two custom blocks total per slide cart)
CSS you write applies inside the slide cart — keep selectors specific to avoid affecting other slide cart elements
Examples you can use
Feel free to copy and paste these examples into your Custom block. If you need any modification, just paste the code to any LLM (we recommend Claude) and ask for changes.
1. Auto-scrolling reviews carousel
Here's an auto-scrolling widget showing reviews (hard-coded) pauses for ~3.5 seconds on each, smooth slide transitions between them. Pure CSS, no JS. You can add more views, adjust timing, etc.
Display full code
Display full code
<div class="reviews-widget">
<div class="reviews-summary">
<span class="reviews-stars">★★★★★</span>
<span class="reviews-meta">4.9 · 2,500+ reviews</span>
</div>
<div class="reviews-slider-wrapper">
<div class="reviews-track">
<div class="review-slide">
<p class="review-text">"Absolutely love this product! Best purchase I've made all year."</p>
<span class="review-author">Sarah M. · Verified buyer</span>
</div>
<div class="review-slide">
<p class="review-text">"Quality is amazing and shipping was fast. Highly recommend!"</p>
<span class="review-author">David K. · Verified buyer</span>
</div>
<div class="review-slide">
<p class="review-text">"Customer service was incredible. Will definitely buy again."</p>
<span class="review-author">Emma R. · Verified buyer</span>
</div>
</div>
</div>
</div>
<style>
.reviews-widget {
padding: 16px 14px;
background: #f7fafc;
margin: 0;
}
.reviews-summary {
display: flex;
align-items: center;
justify-content: center;
gap: 8px;
margin-bottom: 10px;
}
.reviews-stars {
color: #f6ad37;
font-size: 15px;
letter-spacing: 1px;
}
.reviews-meta {
font-size: 12px;
color: #4a5568;
font-weight: 600;
}
.reviews-slider-wrapper {
overflow: hidden;
position: relative;
}
.reviews-track {
display: flex;
width: 300%;
animation: review-slide 12s infinite ease-in-out;
}
.review-slide {
flex: 0 0 33.333%;
padding: 0 8px;
text-align: center;
box-sizing: border-box;
}
.review-text {
font-size: 13px;
color: #2d3748;
margin: 0 0 6px;
line-height: 1.4;
font-style: italic;
}
.review-author {
font-size: 11px;
color: #718096;
}
@keyframes review-slide {
0%, 28% { transform: translateX(0); }
33%, 61% { transform: translateX(-33.333%); }
66%, 94% { transform: translateX(-66.666%); }
100% { transform: translateX(0); }
}
</style>
2. Countdown timer
Countdown timers add urgency to limited-time offers, flash sales, and reserved-cart messaging. The example below is a 10-minute session timer: it starts at 10:00 each time a customer opens the slide cart, ticks down second by second, and shows an "Offer expired" message when it reaches zero. The duration, message, and colors are all easy to change in the code.
Display full code
Display full code
<div class="countdown-widget">
<div class="countdown-text">
<div class="countdown-title">
<svg width="14" height="14" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true">
<path fill-rule="evenodd" d="M12 2.25a9.75 9.75 0 1 0 0 19.5 9.75 9.75 0 0 0 0-19.5ZM12.75 6a.75.75 0 0 0-1.5 0v6c0 .2.08.39.22.53l4 4a.75.75 0 1 0 1.06-1.06l-3.78-3.78V6Z" clip-rule="evenodd"/>
</svg>
<span>Hurry — limited offer</span>
</div>
<div class="countdown-subtitle">Reserved in your cart for</div>
</div>
<div class="countdown-display">
<div class="cd-block"><span id="cd-m">10</span><small>min</small></div>
<div class="cd-sep">:</div>
<div class="cd-block"><span id="cd-s">00</span><small>sec</small></div>
</div>
<img src="x" style="display:none" onerror="this.remove();(function(){var endTime=Date.now()+(10*60*1000);function update(){var diff=endTime-Date.now();if(diff<=0){var w=document.querySelector('.countdown-widget');if(w){w.textContent='Offer expired';w.style.padding='14px';w.style.textAlign='center';w.style.color='#718096';w.style.display='block';}return;}var pad=function(n){return String(n).padStart(2,'0');};var m=Math.floor(diff/60000),s=Math.floor((diff%60000)/1000);var dm=document.getElementById('cd-m');var ds=document.getElementById('cd-s');if(dm)dm.textContent=pad(m);if(ds)ds.textContent=pad(s);}if(window.__cdInt)clearInterval(window.__cdInt);update();window.__cdInt=setInterval(update,1000);})();">
</div>
<style>
.countdown-widget {
display: flex;
align-items: center;
justify-content: space-between;
gap: 12px;
padding: 14px;
background: #fff5f5;
margin: 0;
}
.countdown-text {
flex: 1;
min-width: 0;
}
.countdown-title {
display: flex;
align-items: center;
gap: 6px;
font-size: 13px;
font-weight: 700;
color: #c53030;
margin-bottom: 2px;
}
.countdown-subtitle {
font-size: 11px;
color: #718096;
}
.countdown-display {
display: flex;
align-items: baseline;
gap: 4px;
flex-shrink: 0;
}
.cd-block {
display: flex;
flex-direction: column;
align-items: center;
min-width: 32px;
}
.cd-block span {
font-size: 22px;
font-weight: 700;
line-height: 1;
color: #2d3748;
font-variant-numeric: tabular-nums;
}
.cd-block small {
font-size: 9px;
color: #718096;
text-transform: uppercase;
letter-spacing: 0.5px;
margin-top: 2px;
}
.cd-sep {
color: #2d3748;
font-size: 22px;
font-weight: 700;
line-height: 1;
padding: 0 2px;
}
</style>
3. Trust signals
Here's a complete example you can paste in and customise. It shows three trust signals, which are typically displayed below the checkout button.
Display full code
Display full code
<div class="trust-footer">
<div class="trust-item">
<svg width="22" height="22" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true">
<path fill-rule="evenodd" d="M12 1.5a5.25 5.25 0 0 0-5.25 5.25v3a3 3 0 0 0-3 3v6.75a3 3 0 0 0 3 3h10.5a3 3 0 0 0 3-3v-6.75a3 3 0 0 0-3-3v-3c0-2.9-2.35-5.25-5.25-5.25Zm3.75 8.25v-3a3.75 3.75 0 1 0-7.5 0v3h7.5Z" clip-rule="evenodd"/>
</svg>
<span class="trust-title">Secure checkout</span>
<span class="trust-subtitle">256-bit SSL</span>
</div>
<div class="trust-item">
<svg width="22" height="22" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true">
<path d="M4.5 6.375a4.125 4.125 0 1 1 8.25 0 4.125 4.125 0 0 1-8.25 0ZM14.25 8.625a3.375 3.375 0 1 1 6.75 0 3.375 3.375 0 0 1-6.75 0ZM1.5 19.125a7.125 7.125 0 0 1 14.25 0v.003l-.001.119a.75.75 0 0 1-.363.63 13.067 13.067 0 0 1-6.761 1.873c-2.472 0-4.786-.684-6.76-1.873a.75.75 0 0 1-.364-.63l-.001-.122ZM17.25 19.128l-.001.144a2.25 2.25 0 0 1-.233.96 10.088 10.088 0 0 0 5.06-1.01.75.75 0 0 0 .42-.643 4.875 4.875 0 0 0-6.957-4.611 8.586 8.586 0 0 1 1.71 5.157v.003Z"/>
</svg>
<span class="trust-title">50,000+ customers</span>
<span class="trust-subtitle">Join the community</span>
</div>
<div class="trust-item">
<svg width="22" height="22" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true">
<path d="M1.5 4.5a3 3 0 0 1 3-3h1.372c.86 0 1.61.586 1.819 1.42l1.105 4.423a1.875 1.875 0 0 1-.694 1.955l-1.293.97c-.135.101-.164.249-.126.352a11.285 11.285 0 0 0 6.697 6.697c.103.038.25.009.352-.126l.97-1.293a1.875 1.875 0 0 1 1.955-.694l4.423 1.105c.834.209 1.42.959 1.42 1.82V19.5a3 3 0 0 1-3 3h-2.25C8.552 22.5 1.5 15.448 1.5 6.75V4.5Z"/>
</svg>
<span class="trust-title">Need help?</span>
<span class="trust-subtitle">(555) 123-4567</span>
</div>
</div>
<style>
.trust-footer {
display: flex;
justify-content: space-between;
align-items: flex-start;
gap: 8px;
padding: 14px;
background: #f7fafc;
border-radius: 8px;
margin: 8px 0;
}
.trust-item {
display: flex;
flex-direction: column;
align-items: center;
text-align: center;
flex: 1;
gap: 4px;
}
.trust-item svg {
color: #2d8659;
}
.trust-title {
font-size: 12px;
font-weight: 600;
color: #2d3748;
line-height: 1.2;
}
.trust-subtitle {
font-size: 11px;
color: #718096;
line-height: 1.2;
}
</style>
4. Scratch card
Scratch cards are a great way to share a "mystery" discount with your customers to give them an extra incentive to continue to the checkout. Using this card, you can share multiple discount codes and even control the probability of their reveal (i.e., you can show 10% discount code more often than the 20% one).
Display full code
Display full code
<div class="cr-scratch-wrap">
<div class="cr-scratch-card">
<div class="cr-scratch-prize">
<div class="cr-scratch-prize-tag">You won</div>
<div class="cr-scratch-prize-amount">—</div>
<button type="button" class="cr-scratch-prize-codepill cr-scratch-copy" aria-label="Copy discount code">
<span class="cr-scratch-prize-codetext">—</span>
<span class="cr-scratch-prize-copyicon"></span>
</button>
<div class="cr-scratch-prize-sub">Apply this code at checkout</div>
</div>
<canvas class="cr-scratch-canvas"></canvas>
</div>
</div>
<style>
.cr-scratch-wrap {
position: relative;
width: 100%;
margin: 0 auto;
padding: 18px 15%;
background: linear-gradient(180deg, #1a2030 0%, #0f1420 100%);
color: #f1f5f9;
border: 1px solid rgba(255, 255, 255, 0.06);
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", system-ui, sans-serif;
user-select: none;
-webkit-user-select: none;
box-sizing: border-box;
overflow: hidden;
}
.cr-scratch-wrap *,
.cr-scratch-wrap *::before,
.cr-scratch-wrap *::after {
box-sizing: border-box;
}
.cr-scratch-wrap .cr-scratch-card {
position: relative;
width: 100%;
height: 180px;
border-radius: 12px;
overflow: hidden;
}
.cr-scratch-wrap .cr-scratch-prize {
position: absolute;
inset: 0;
background:
radial-gradient(circle at 30% 20%, rgba(255, 255, 255, 0.5), transparent 50%),
linear-gradient(135deg, #fef3c7 0%, #fde68a 50%, #fbbf24 100%);
color: #422006;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 14px;
gap: 6px;
}
.cr-scratch-wrap .cr-scratch-prize-tag {
font-size: 10px;
font-weight: 700;
text-transform: uppercase;
letter-spacing: 2px;
color: rgba(66, 32, 6, 0.6);
}
.cr-scratch-wrap .cr-scratch-prize-amount {
font-size: 32px;
font-weight: 800;
letter-spacing: -0.5px;
line-height: 1;
background: linear-gradient(180deg, #422006 0%, #78350f 100%);
-webkit-background-clip: text;
background-clip: text;
color: transparent;
margin: 2px 0 4px;
}
.cr-scratch-wrap .cr-scratch-prize-codepill {
display: inline-flex;
align-items: center;
gap: 9px;
padding: 8px 14px;
background: #422006;
color: #fef3c7;
font-family: ui-monospace, "SF Mono", Menlo, monospace;
font-size: 14px;
font-weight: 700;
letter-spacing: 2px;
border: none;
border-radius: 6px;
cursor: pointer;
transition: background 0.15s, transform 0.1s, color 0.15s;
-webkit-tap-highlight-color: transparent;
}
.cr-scratch-wrap .cr-scratch-prize-codepill:hover {
background: #5b2d09;
}
.cr-scratch-wrap .cr-scratch-prize-codepill:active {
transform: translateY(1px);
}
.cr-scratch-wrap .cr-scratch-prize-codepill-success,
.cr-scratch-wrap .cr-scratch-prize-codepill-success:hover {
background: #10b981;
color: #fff;
}
.cr-scratch-wrap .cr-scratch-prize-copyicon {
display: flex;
opacity: 0.75;
}
.cr-scratch-wrap .cr-scratch-prize-sub {
font-size: 11px;
color: rgba(66, 32, 6, 0.65);
margin-top: 2px;
transition: color 0.15s;
}
.cr-scratch-wrap .cr-scratch-prize-sub-success {
color: #047857;
font-weight: 600;
}
.cr-scratch-wrap .cr-scratch-canvas {
position: absolute;
inset: 0;
width: 100%;
height: 100%;
touch-action: none;
cursor: grab;
transition: opacity 0.5s ease, transform 0.5s ease;
}
.cr-scratch-wrap .cr-scratch-canvas:active {
cursor: grabbing;
}
.cr-scratch-wrap .cr-scratch-canvas-revealed {
opacity: 0;
transform: scale(1.03);
pointer-events: none;
}
.cr-scratch-wrap .cr-scratch-confetti {
position: absolute;
top: 50%;
left: 50%;
width: 8px;
height: 8px;
border-radius: 2px;
pointer-events: none;
will-change: transform, opacity;
animation: cr-scratch-burst 1.3s cubic-bezier(0.2, 0.7, 0.4, 1) forwards;
}
@keyframes cr-scratch-burst {
0% {
transform: translate(-50%, -50%) rotate(0deg);
opacity: 1;
}
100% {
transform: translate(calc(-50% + var(--tx)), calc(-50% + var(--ty))) rotate(var(--rot));
opacity: 0;
}
}
</style>
<script type="text/plain" id="cr-scratch-code">
(function(){
function start() {
const root = document.querySelector('.cr-scratch-wrap');
if (!root || root.dataset.crScratchInit) return;
if (!root.querySelector('.cr-scratch-canvas')) return;
root.dataset.crScratchInit = '1';
run(root);
}
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', start);
} else {
start();
}
function run(root) {
// ---------- Prize pool ----------
// Adjust amounts, codes, and weights to taste. Higher weight = more common.
const PRIZES = [
{ amount: '10% OFF', code: 'SCRATCH10', weight: 50 },
{ amount: '15% OFF', code: 'SCRATCH15', weight: 28 },
{ amount: '20% OFF', code: 'SCRATCH20', weight: 15 },
{ amount: 'FREE SHIPPING', code: 'SHIPSCRATCH', weight: 5 },
{ amount: '25% OFF', code: 'SCRATCH25', weight: 2 }
];
function pickPrize() {
const total = PRIZES.reduce((s, p) => s + p.weight, 0);
let r = Math.random() * total;
for (const p of PRIZES) {
r -= p.weight;
if (r <= 0) return p;
}
return PRIZES[0];
}
// ---------- Setup ----------
const card = root.querySelector('.cr-scratch-card');
const canvas = root.querySelector('.cr-scratch-canvas');
const ctx = canvas.getContext('2d');
const amountEl = root.querySelector('.cr-scratch-prize-amount');
const codeEl = root.querySelector('.cr-scratch-prize-codetext');
const copyBtn = root.querySelector('.cr-scratch-copy');
const copyIconEl = root.querySelector('.cr-scratch-prize-copyicon');
const subEl = root.querySelector('.cr-scratch-prize-sub');
const SUB_DEFAULT = subEl.textContent;
const ICON_COPY = '<svg viewBox="0 0 24 24" width="14" height="14" fill="currentColor" aria-hidden="true"><path d="M16 1H4c-1.1 0-2 .9-2 2v14h2V3h12V1zm3 4H8c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h11c1.1 0 2-.9 2-2V7c0-1.1-.9-2-2-2zm0 16H8V7h11v14z"/></svg>';
const ICON_CHECK = '<svg viewBox="0 0 24 24" width="14" height="14" fill="currentColor" aria-hidden="true"><path d="M9 16.17L4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41L9 16.17z"/></svg>';
copyIconEl.innerHTML = ICON_COPY;
const prize = pickPrize();
amountEl.textContent = prize.amount;
codeEl.textContent = prize.code;
// Match canvas internal resolution to card display size, accounting for
// devicePixelRatio so text and edges stay crisp on retina / high-DPI screens.
// Display sizing is left to CSS (inset: 0 / width: 100%) so we never end up
// sub-pixel narrower than the card on mobile.
let cssW = 0, cssH = 0;
function sizeCanvas() {
const rect = card.getBoundingClientRect();
const dpr = window.devicePixelRatio || 1;
cssW = rect.width;
cssH = rect.height;
canvas.width = Math.max(1, Math.round(cssW * dpr));
canvas.height = Math.max(1, Math.round(cssH * dpr));
ctx.setTransform(1, 0, 0, 1, 0, 0);
ctx.scale(dpr, dpr);
}
function paintFoil() {
const w = cssW, h = cssH;
// Base silver-gold gradient
const grad = ctx.createLinearGradient(0, 0, w, h);
grad.addColorStop(0, '#9ca3af');
grad.addColorStop(0.25, '#cbd5e1');
grad.addColorStop(0.5, '#f1f5f9');
grad.addColorStop(0.75, '#cbd5e1');
grad.addColorStop(1, '#6b7280');
ctx.fillStyle = grad;
ctx.fillRect(0, 0, w, h);
// Soft warm highlight overlay for a "foil" sheen
const warm = ctx.createLinearGradient(0, 0, w, 0);
warm.addColorStop(0, 'rgba(252, 211, 77, 0)');
warm.addColorStop(0.5, 'rgba(252, 211, 77, 0.18)');
warm.addColorStop(1, 'rgba(252, 211, 77, 0)');
ctx.fillStyle = warm;
ctx.fillRect(0, 0, w, h);
// Sparkle dots for texture
ctx.fillStyle = 'rgba(255, 255, 255, 0.5)';
for (let i = 0; i < 90; i++) {
const x = Math.random() * w;
const y = Math.random() * h;
const r = Math.random() * 0.9 + 0.3;
ctx.beginPath();
ctx.arc(x, y, r, 0, Math.PI * 2);
ctx.fill();
}
// Few larger sparkles
ctx.fillStyle = 'rgba(255, 255, 255, 0.85)';
for (let i = 0; i < 8; i++) {
const x = Math.random() * w;
const y = Math.random() * h;
drawSparkle(x, y, 2 + Math.random() * 1.5);
}
// "Scratch to reveal" text
ctx.fillStyle = '#1f2937';
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
ctx.font = '700 17px -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif';
ctx.fillText('Reveal mystery discount', w / 2, h / 2 - 10);
ctx.fillStyle = 'rgba(31, 41, 55, 0.75)';
ctx.font = '500 12px -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif';
ctx.fillText('Drag with mouse or finger', w / 2, h / 2 + 18);
}
function drawSparkle(x, y, r) {
ctx.save();
ctx.translate(x, y);
ctx.beginPath();
ctx.moveTo(0, -r * 1.6);
ctx.lineTo(r * 0.4, -r * 0.4);
ctx.lineTo(r * 1.6, 0);
ctx.lineTo(r * 0.4, r * 0.4);
ctx.lineTo(0, r * 1.6);
ctx.lineTo(-r * 0.4, r * 0.4);
ctx.lineTo(-r * 1.6, 0);
ctx.lineTo(-r * 0.4, -r * 0.4);
ctx.closePath();
ctx.fill();
ctx.restore();
}
sizeCanvas();
paintFoil();
// ---------- Scratch interaction ----------
let scratching = false;
let last = null;
let revealed = false;
let lastCheck = 0;
function getPos(e) {
const rect = canvas.getBoundingClientRect();
const point = e.touches ? e.touches[0] : e;
return {
x: point.clientX - rect.left,
y: point.clientY - rect.top
};
}
function startScratch(e) {
if (revealed) return;
e.preventDefault();
scratching = true;
last = getPos(e);
scratchAt(last.x, last.y);
}
function moveScratch(e) {
if (!scratching || revealed) return;
e.preventDefault();
const p = getPos(e);
if (last) {
// Connect segments so fast drags don't leave gaps
const dist = Math.hypot(p.x - last.x, p.y - last.y);
const steps = Math.max(1, Math.floor(dist / 3));
for (let i = 1; i <= steps; i++) {
const t = i / steps;
scratchAt(last.x + (p.x - last.x) * t, last.y + (p.y - last.y) * t);
}
}
last = p;
maybeCheckProgress();
}
function endScratch() {
scratching = false;
last = null;
if (!revealed) checkProgress();
}
function scratchAt(x, y) {
ctx.save();
ctx.globalCompositeOperation = 'destination-out';
ctx.beginPath();
ctx.arc(x, y, 12, 0, Math.PI * 2);
ctx.fill();
// Soft feathered edge
ctx.globalAlpha = 0.6;
ctx.beginPath();
ctx.arc(x, y, 16, 0, Math.PI * 2);
ctx.fill();
ctx.restore();
}
function maybeCheckProgress() {
const now = (performance && performance.now) ? performance.now() : Date.now();
if (now - lastCheck < 180) return;
lastCheck = now;
checkProgress();
}
function checkProgress() {
if (revealed) return;
let cleared = 0, total = 0;
try {
const data = ctx.getImageData(0, 0, canvas.width, canvas.height).data;
// Sample every 6th pixel in both axes
for (let y = 0; y < canvas.height; y += 6) {
for (let x = 0; x < canvas.width; x += 6) {
const i = (y * canvas.width + x) * 4 + 3;
total++;
if (data[i] < 60) cleared++;
}
}
} catch (err) {
// getImageData can throw on tainted canvas — shouldn't happen here, but fail open
return;
}
const ratio = total ? cleared / total : 0;
if (ratio >= 0.7) reveal();
}
function reveal() {
if (revealed) return;
revealed = true;
canvas.classList.add('cr-scratch-canvas-revealed');
spawnConfetti();
// Optional: emit a custom event so the cart can react
try {
root.dispatchEvent(new CustomEvent('cr-scratch-reveal', {
detail: { code: prize.code, amount: prize.amount },
bubbles: true
}));
} catch (e) {}
}
// Mouse
canvas.addEventListener('mousedown', startScratch);
window.addEventListener('mousemove', moveScratch);
window.addEventListener('mouseup', endScratch);
// Touch
canvas.addEventListener('touchstart', startScratch, { passive: false });
canvas.addEventListener('touchmove', moveScratch, { passive: false });
canvas.addEventListener('touchend', endScratch);
canvas.addEventListener('touchcancel', endScratch);
// ---------- Confetti ----------
function spawnConfetti() {
const colors = ['#fbbf24', '#fde047', '#f59e0b', '#10b981', '#3b82f6', '#a78bfa', '#ef4444'];
for (let i = 0; i < 28; i++) {
const piece = document.createElement('div');
piece.className = 'cr-scratch-confetti';
piece.style.background = colors[i % colors.length];
const angle = Math.random() * Math.PI * 2;
const dist = 70 + Math.random() * 90;
piece.style.setProperty('--tx', (Math.cos(angle) * dist).toFixed(1) + 'px');
piece.style.setProperty('--ty', (Math.sin(angle) * dist - 20).toFixed(1) + 'px');
piece.style.setProperty('--rot', (Math.random() * 720 - 360).toFixed(0) + 'deg');
piece.style.animationDelay = (Math.random() * 0.12).toFixed(2) + 's';
piece.style.width = (5 + Math.random() * 6).toFixed(0) + 'px';
piece.style.height = (5 + Math.random() * 6).toFixed(0) + 'px';
card.appendChild(piece);
setTimeout(function(){ if (piece.parentNode) piece.parentNode.removeChild(piece); }, 1500);
}
}
// ---------- Copy code ----------
function copyCode(btn) {
const flashOk = function() {
copyIconEl.innerHTML = ICON_CHECK;
btn.classList.add('cr-scratch-prize-codepill-success');
subEl.textContent = 'Copied!';
subEl.classList.add('cr-scratch-prize-sub-success');
setTimeout(function() {
copyIconEl.innerHTML = ICON_COPY;
btn.classList.remove('cr-scratch-prize-codepill-success');
subEl.textContent = SUB_DEFAULT;
subEl.classList.remove('cr-scratch-prize-sub-success');
}, 1600);
};
if (navigator.clipboard && navigator.clipboard.writeText) {
navigator.clipboard.writeText(prize.code).then(flashOk).catch(fallback);
} else { fallback(); }
function fallback() {
const ta = document.createElement('textarea');
ta.value = prize.code;
ta.setAttribute('readonly', '');
ta.style.position = 'fixed';
ta.style.opacity = '0';
document.body.appendChild(ta);
ta.select();
try { document.execCommand('copy'); flashOk(); } catch (e) {}
document.body.removeChild(ta);
}
}
copyBtn.addEventListener('click', function(){ copyCode(copyBtn); });
}
})();
</script>
<img src="cr-scratch-bootstrap" alt="" style="display:none" onerror="(function(t){var c=document.getElementById('cr-scratch-code');if(!c)return;var s=document.createElement('script');s.textContent=c.textContent;document.head.appendChild(s);t.remove();})(this);">
Need help building a specific block? Reach out to support — we're happy to point
you in the right direction.









