// Receipt comparison, features, how it works, pricing, FAQ, final CTA, footer.
function ChipSvg({ type }) {
const s = 28, r = 6;
const chips = {
netflix: (
<>
>
),
viaplay: (
<>
via
>
),
disney: (
<>
D+
>
),
max: (
<>
MAX
>
),
tv4: (
<>
TV4
>
),
};
const key = /netflix/i.test(type) ? 'netflix'
: /viaplay/i.test(type) ? 'viaplay'
: /disney/i.test(type) ? 'disney'
: /hbo|max/i.test(type) ? 'max'
: /tv4/i.test(type) ? 'tv4'
: 'netflix';
return (
);
}
function BarcodeSvg() {
const pat = [2,1,2,1,3,2,1,2,2,1,3,1,2,2,1,3,2,1,1,2,3,1,2,1,1,3,2,1,3,1,1,2,2,3,1,2,1,1,3,2,1,2,3,1,2,1,1,2,3,2];
let x = 8;
const bars = [];
pat.forEach((w, i) => {
const bw = w * 2.6;
if (i % 2 === 0) bars.push();
x += bw;
});
return (
);
}
function ReceiptBad({ mini = false }) {
const C = COPY[window.locale];
const fmtPrice = p => p.replace(/\s*kr$/, ',00');
return (
{/* Handwritten annotations (full version only) */}
{!mini && (
SERIÖST?
5 appar.
5 lösen.
för
alltid
∞
)}
Kund 8842
15.05.26 14:32
{C.receiptItems.map(([name, price]) => (
))}
ATT BETALA
· · · · · · ·
VARJE MÅNAD
eller
8 580
kr
per år
{!mini && (
<>
Varav moms 25 %
143,00
Kort • • • • 4471
GODKÄND
TACK FÖR DITT KÖP
>
)}
);
}
function ReceiptGood() {
const C = COPY[window.locale];
return (
TVMOMENTO
58 {C.perMnd}
vid 1-årsplan
{C.goodFeatures.map(f => (
-
{f}
))}
{C.cta}
);
}
function ReceiptCompare() {
return null;
}
function FeatureSavings() {
const C = COPY[window.locale];
return (
Pris
Du sparar
7 884 kr per år.
Det är vad du betalar extra med fem separata abonnemang. Inte hypotetiskt. Per år.
| Nuläget | 715 kr/mån | 8 580 kr/år |
| Tvmomento | 58 kr/mån | 696 kr/år |
| Du sparar | 657 kr/mån | 7 884 kr/år |
);
}
function HowItWorks() {
React.useEffect(() => {
const io = new IntersectionObserver(entries => {
entries.forEach(e => {
if (e.isIntersecting) { e.target.classList.add('visible'); io.unobserve(e.target); }
});
}, { threshold: 0.12, rootMargin: '0px 0px -40px 0px' });
document.querySelectorAll('.how-step').forEach(s => io.observe(s));
return () => io.disconnect();
}, []);
return (
Så fungerar det
Tre steg.
Femton minuter.
{/* Step 01 — plan comparison */}
01
Välj din plan.
Tre nivåer, samma innehåll. Inga dolda avgifter, ingen bindningstid.
3 år + 6 mån gratis
1 299
kr
{/* Step 02 — payment sheet */}
02
Betala säkert.
Visa, Mastercard, Swish eller Apple Pay. Shopify Payments-skyddad betalning, kvitto direkt.
Tvmomento1 år
Plan699 kr
Att betala699 kr
{/* Step 03 — cinematic image */}
03
Tryck play.
Inloggning till din e-post inom 15 minuter. Funkar på TV, mobil, surfplatta och dator.
);
}
const DEVICE_IMAGES = ['assets/devices/tv.webp', 'assets/devices/phone.webp', 'assets/devices/tablet.webp', 'assets/devices/laptop.webp'];
const DEVICE_ALT = ['Smart TV', 'Phone', 'Tablet', 'Laptop'];
const DEVICE_LOGOS = [
/* TV */
,
/* Mobile */
,
/* Tablet */
,
/* Laptop */
,
];
function DeviceCompat() {
const C = COPY[window.locale];
React.useEffect(() => {
const grid = document.querySelector('.dvc-grid');
if (grid && window.innerWidth <= 560) {
const origCards = Array.from(grid.querySelectorAll('.dvc-card'));
origCards.forEach(c => { c.classList.add('visible'); });
origCards.forEach(c => grid.appendChild(c.cloneNode(true)));
return;
}
const io = new IntersectionObserver(entries => {
entries.forEach(e => {
if (e.isIntersecting) { e.target.classList.add('visible'); io.unobserve(e.target); }
});
}, { threshold: 0.08, rootMargin: '0px 0px -40px 0px' });
document.querySelectorAll('.dvc-card').forEach(el => io.observe(el));
return () => io.disconnect();
}, []);
return (
Kompatibilitet
{C.deviceHeadline}
{C.deviceSub}
{C.deviceIntro}
{C.devices.map((d, i) => (
{C.devicesLogosLabel[i]}
{DEVICE_LOGOS[i]}
))}
);
}
function Pricing() {
const ref = React.useRef(null);
React.useEffect(() => {
const root = ref.current;
if (!root) return;
const CO_COPY = {
SE: {
planNames: { '3m': '3 Månader', '1y': '1 År', '3y': '3 År + 6 mån gratis' },
planSub: { '3m': 'Engångsbelopp', '1y': 'Spara vs månadsvis', '3y': 'Bästa värdet — 3,5 år totalt' },
chipUnit1: 'skärm', chipUnitN: 'skärmar', chipIncluded: 'Ingår',
sumExtra: n => '+' + n + ' skärm' + (n > 1 ? 'ar' : ''),
numLocale: 'sv-SE',
},
NO: {
planNames: { '3m': '3 Måneder', '1y': '1 År', '3y': '3 År + 6 mnd gratis' },
planSub: { '3m': 'Engangsbetaling', '1y': 'Spar vs. månedlig', '3y': 'Beste verdi — 3,5 år totalt' },
chipUnit1: 'skjerm', chipUnitN: 'skjermer', chipIncluded: 'Inkludert',
sumExtra: n => '+' + n + ' skjerm' + (n > 1 ? 'er' : ''),
numLocale: 'nb-NO',
},
};
const C = CO_COPY[window.locale || 'SE'];
const PLANS = {
'SE': {
'3m': { base: 349, extra: 175 },
'1y': { base: 699, extra: 350 },
'3y': { base: 1299, extra: 650 },
},
'NO': {
'3m': { base: 379, extra: 189 },
'1y': { base: 749, extra: 379 },
'3y': { base: 1399, extra: 699 },
}
}[window.locale || 'SE'];
let selPlan = '1y', selExtra = 0, planTapped = false, step2Active = false;
function fmt(n) { return n.toLocaleString(C.numLocale); }
function isMob() { return window.matchMedia('(max-width: 860px)').matches; }
function q(id) { return root.querySelector('#' + id); }
function updateChipPrices() {
const ppu = PLANS[selPlan].extra;
for (let i = 1; i <= 5; i++) {
const el = q('chip-price-' + i);
if (el) el.textContent = '+' + fmt(i * ppu) + ' kr';
}
}
function updateSummary() {
const plan = PLANS[selPlan], extra = selExtra * plan.extra, total = plan.base + extra;
if (q('sum-plan-name')) q('sum-plan-name').textContent = C.planNames[selPlan];
if (q('sum-base-price')) q('sum-base-price').textContent = fmt(plan.base) + ' kr';
if (q('sum-total')) q('sum-total').textContent = fmt(total);
if (q('cta-price')) q('cta-price').textContent = fmt(total);
const extraRow = q('sum-extra-row');
if (extraRow) {
if (selExtra > 0) {
extraRow.classList.add('visible');
q('sum-extra-label').textContent = C.sumExtra(selExtra);
q('sum-extra-price').textContent = '+' + fmt(extra) + ' kr';
} else { extraRow.classList.remove('visible'); }
}
if (q('ps-name')) q('ps-name').textContent = C.planNames[selPlan];
if (q('ps-price')) q('ps-price').textContent = fmt(plan.base) + ' kr';
const mcbTotal = root.querySelector('.m-cta-total');
const mcbAmt = root.querySelector('.m-cta-amount');
if (mcbTotal && mcbAmt) {
mcbTotal.classList.add('updating');
requestAnimationFrame(() => {
mcbAmt.textContent = fmt(total);
requestAnimationFrame(() => mcbTotal.classList.remove('updating'));
});
}
}
const mCtaEl = root.querySelector('.m-cta');
const mCtaBtn = root.querySelector('.m-cta-btn');
const checkoutPanel = root.querySelector('.checkout-grid .co-panel');
function syncCheckoutCtaState() {
const active = !!(mCtaEl && isMob() && mCtaEl.classList.contains('m-cta-up'));
document.body.classList.toggle('checkout-cta-active', active);
document.documentElement.classList.toggle('checkout-cta-active', active);
}
function keepCheckoutInView() {
if (!isMob() || !checkoutPanel) return;
const rect = checkoutPanel.getBoundingClientRect();
const ctaHeight = mCtaEl && mCtaEl.classList.contains('m-cta-up') ? mCtaEl.getBoundingClientRect().height : 0;
const viewportSpace = Math.max(320, window.innerHeight - ctaHeight - 20);
const target = window.scrollY + rect.top - Math.max(14, (viewportSpace - rect.height) / 2);
window.scrollTo({ top: Math.max(0, target), behavior: 'smooth' });
}
function revealDevices() {
root.querySelector('#plan-grid').classList.add('collapsed');
root.querySelector('#plan-summary').classList.add('visible');
root.querySelector('#xdev-block').classList.add('xdev-revealed');
root.querySelector('#mstep-1').className = 'm-step done';
root.querySelector('#mstep-2').className = 'm-step active';
if (mCtaEl) mCtaEl.classList.add('m-cta-up');
syncCheckoutCtaState();
step2Active = true;
refreshMobileCta();
keepCheckoutInView();
}
function changePlan() {
root.querySelector('#plan-grid').classList.remove('collapsed');
root.querySelector('#plan-summary').classList.remove('visible');
root.querySelector('#xdev-block').classList.remove('xdev-revealed');
if (mCtaEl) mCtaEl.classList.remove('m-cta-up');
syncCheckoutCtaState();
step2Active = false; planTapped = false;
root.querySelector('#mstep-1').className = 'm-step active';
root.querySelector('#mstep-2').className = 'm-step';
refreshMobileCta();
keepCheckoutInView();
}
function selectPlan(tile) {
root.querySelectorAll('.plan-tile').forEach(t => t.classList.remove('selected'));
tile.classList.add('selected');
selPlan = tile.dataset.plan;
updateChipPrices();
updateSummary();
if (isMob()) {
if (!planTapped) { planTapped = true; revealDevices(); }
else if (mCtaEl) {
mCtaEl.classList.add('m-cta-up');
syncCheckoutCtaState();
}
}
}
function selectExtra(chip) {
root.querySelectorAll('.chip').forEach(c => c.classList.remove('selected'));
chip.classList.add('selected');
selExtra = parseInt(chip.dataset.extra, 10);
updateSummary();
}
function goToCheckout() {
const links = window.CHECKOUT_LINKS;
const url = links && links[selPlan] && links[selPlan][selExtra];
if (url) window.location.href = url;
}
function refreshMobileCta() {
if (!mCtaEl || !mCtaBtn) return;
if (!isMob()) {
mCtaEl.classList.remove('m-cta-up');
syncCheckoutCtaState();
return;
}
const rect = (checkoutPanel || root).getBoundingClientRect();
const inView = rect.top < window.innerHeight - 80 && rect.bottom > 80;
if (!inView) {
mCtaEl.classList.remove('m-cta-up');
syncCheckoutCtaState();
return;
}
if (!step2Active) {
mCtaBtn.textContent = 'Välj antal skärmar →';
mCtaBtn.onclick = revealDevices;
} else {
mCtaEl.classList.add('m-cta-up');
mCtaBtn.textContent = 'Gå till kassa →';
mCtaBtn.onclick = goToCheckout;
}
syncCheckoutCtaState();
}
root.querySelectorAll('.plan-tile').forEach(t => t.addEventListener('click', () => selectPlan(t)));
root.querySelectorAll('.chip').forEach(c => c.addEventListener('click', () => selectExtra(c)));
root.querySelector('.ps-change')?.addEventListener('click', changePlan);
root.querySelector('#cta-btn')?.addEventListener('click', goToCheckout);
updateChipPrices();
updateSummary();
let snapCooled = false;
let snapTimer = null;
let lastClickInSection = 0;
function noteCheckoutClick() {
lastClickInSection = Date.now();
snapCooled = true;
setTimeout(() => { snapCooled = false; }, 1800);
}
root.addEventListener('pointerdown', noteCheckoutClick, { passive: true });
root.addEventListener('click', noteCheckoutClick);
function snapToSection() {
if (snapCooled || !isMob()) return;
if (Date.now() - lastClickInSection < 2000) return;
const rect = root.getBoundingClientRect();
const partial = rect.top < window.innerHeight * 0.7 && rect.top > -rect.height * 0.3;
if (partial && Math.abs(rect.top) > 4) {
snapCooled = true;
keepCheckoutInView();
setTimeout(() => { snapCooled = false; }, 1200);
}
}
function onScroll() {
refreshMobileCta();
clearTimeout(snapTimer);
snapTimer = setTimeout(snapToSection, 120);
}
window.addEventListener('scroll', onScroll, { passive: true });
window.addEventListener('resize', refreshMobileCta);
refreshMobileCta();
return () => {
window.removeEventListener('scroll', onScroll);
window.removeEventListener('resize', refreshMobileCta);
root.removeEventListener('pointerdown', noteCheckoutClick);
root.removeEventListener('click', noteCheckoutClick);
clearTimeout(snapTimer);
document.body.classList.remove('checkout-cta-active');
document.documentElement.classList.remove('checkout-cta-active');
};
}, []);
return (
Priser
Välj ditt paket.
Börja streama direkt.
Välj ditt paket
Abonnemang
1 299krBästa värdet — 3,5 år totalt
Antal skärmar
Varje skärm kan titta på olika kanaler samtidigt.
Inloggning inom 15 minuter
Krypterad anslutning (TLS 1.3)
Din beställning
Totalt att betala
699kr
Engångsbetalning — ingen prenumeration
Inloggning inom 15 minuter
Krypterad anslutning (TLS 1.3)
);
}
function FAQ() {
const listRef = React.useRef(null);
const C = COPY[window.locale];
React.useEffect(() => {
const list = listRef.current;
if (!list) return;
const cards = Array.from(list.querySelectorAll('.faq-item'));
const faqSection = list.closest('.faq');
let openCount = 0;
let ctaUpgraded = false;
function upgradeWhatsappCta() {
if (ctaUpgraded || !faqSection) return;
const el = faqSection.querySelector('.faq-wa-link');
if (!el) return;
ctaUpgraded = true;
el.classList.add('faq-wa-primary');
el.setAttribute('aria-label', 'Kontakta oss på WhatsApp');
if (!el.querySelector('svg')) {
const icon = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
icon.setAttribute('width', '18');
icon.setAttribute('height', '18');
icon.setAttribute('viewBox', '0 0 24 24');
icon.setAttribute('fill', 'white');
icon.setAttribute('aria-hidden', 'true');
icon.setAttribute('style', 'flex-shrink:0');
icon.innerHTML = '';
el.insertBefore(icon, el.firstChild);
}
}
cards.forEach(card => {
const trigger = card.querySelector('.faq-q');
function toggle(e) {
e.stopPropagation();
const isOpen = card.classList.contains('open');
cards.forEach(c => {
c.classList.remove('open');
c.querySelector('.faq-q').setAttribute('aria-expanded', 'false');
});
if (!isOpen) {
card.classList.add('open');
trigger.setAttribute('aria-expanded', 'true');
openCount += 1;
if (openCount >= 3) upgradeWhatsappCta();
}
}
trigger.addEventListener('click', toggle);
trigger.addEventListener('keydown', e => {
if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); toggle(e); }
});
});
}, []);
return (
{C.faqTitle}
{C.faqItems.map(([q, a], i) => (
))}
);
}
function FinalCta() {
const C = COPY[window.locale];
return (
{C.finalTitle1} {C.finalTitle2}
{C.finalSub}
{C.finalQuestions} {C.faqAsk}
);
}
function Footer() {
const C = COPY[window.locale];
return (
);
}
function RecentlyBought() {
const purchases = [
{ name:"Erik J.", plan:"1-årsplan", where:"Stockholm", ago:"just nu", color:"#6e6e73" },
{ name:"Amina K.", plan:"3-årsplan", where:"Göteborg", ago:"1 min", color:"#8e8e93" },
{ name:"Lars P.", plan:"3-månadersplan", where:"Oslo", ago:"3 min", color:"#636366" },
{ name:"Sara H.", plan:"1-årsplan", where:"Malmö", ago:"5 min", color:"#8e8e93" },
{ name:"Mohammed A.", plan:"3-årsplan", where:"Linköping", ago:"7 min", color:"#6e6e73" },
{ name:"Ingrid N.", plan:"1-årsplan", where:"Bergen", ago:"9 min", color:"#8e8e93" },
];
const CYCLE = 4500;
const STEP_MS = 80;
const [idx, setIdx] = React.useState(0);
const [out, setOut] = React.useState(false);
const [barPct, setBarPct] = React.useState(0);
const barTimer = React.useRef(null);
const startBar = React.useCallback(() => {
clearInterval(barTimer.current);
setBarPct(0);
let pct = 0;
barTimer.current = setInterval(() => {
pct = Math.min(100, pct + (STEP_MS / CYCLE) * 100);
setBarPct(pct);
if (pct >= 100) clearInterval(barTimer.current);
}, STEP_MS);
}, []);
React.useEffect(() => {
startBar();
const cycle = setInterval(() => {
setOut(true);
setTimeout(() => {
setIdx(i => (i + 1) % purchases.length);
setOut(false);
startBar();
}, 400);
}, CYCLE);
return () => { clearInterval(cycle); clearInterval(barTimer.current); };
}, [startBar]);
const p = purchases[idx];
return (
{p.name[0]}
Nytt köp
{p.name}
{p.plan} · {p.where}
);
}
function Testimonials() {
React.useEffect(() => {
const DIR = 'assets/ugc/';
const SPEED = 0.45;
const GAP = 14;
const VID_RE = /\.(webm|mp4|mov)$/i;
const IMG_RE = /\.(webp|jpg|jpeg|png)$/i;
const REVIEWS = [
{ name: 'Maria S.', text: 'Fantastisk tjänst! Kanalkvaliteten är överlägsen och priset är helt obeatable.' },
{ name: 'Johan K.', text: 'Fungerar perfekt på min Fire TV. Inga avbrott, skarp bild i 4K.' },
{ name: 'Anna L.', text: 'Har haft Tvmomento i 8 månader. Aldrig haft problem. Rekommenderar varmt.' },
{ name: 'Erik M.', text: 'Priset är löjligt lågt jämfört med vad man får. 10/10 utan tvekan.' },
{ name: 'Sara B.', text: 'Support svarade på 5 minuter och löste mitt problem direkt. Imponerande.' },
{ name: 'Thomas N.', text: 'Bästa köpet jag gjort. Hela familjen är nöjd med kanalutbudet.' },
{ name: 'Karin P.', text: '10 000+ kanaler är inte ett skämt. Det är bokstavligen allt man önskar sig.' },
{ name: 'Mikael R.', text: 'Installationen tog 10 minuter. Nu ser jag all sport live utan avbrott.' },
{ name: 'Lena A.', text: 'Bytte från Viaplay och sparar 400 kr i månaden. Bättre utbud dessutom!' },
{ name: 'Peter H.', text: 'Extremt stabil anslutning. Inga buffringsproblem ens under rusningstid.' },
{ name: 'Cecilia M.', text: 'Superenkel installation och supporten är otroligt hjälpsam och snabb.' },
{ name: 'Anders L.', text: 'Har provat allt — inget slår Tvmomento på pris och kanalutbud.' },
];
const track1 = document.getElementById('ugc-track-1');
const track2 = document.getElementById('ugc-track-2');
const wrap1 = document.getElementById('ugc-wrap-1');
const wrap2 = document.getElementById('ugc-wrap-2');
if (!track1 || !track2) return;
let offset1 = 0, setWidth1 = 0;
let offset2 = 0, setWidth2 = 0;
let rafId = null, isPaused = false;
function tick() {
if (!isPaused) {
offset1 -= SPEED;
if (offset1 <= -setWidth1) offset1 += setWidth1;
track1.style.transform = `translateX(${offset1}px)`;
offset2 += SPEED;
if (offset2 >= 0) offset2 -= setWidth2;
track2.style.transform = `translateX(${offset2}px)`;
}
rafId = requestAnimationFrame(tick);
}
function wireVideo(vid, bg, btn, cardEl) {
vid.muted = true; vid.loop = true;
['muted','playsinline','webkit-playsinline','x5-playsinline'].forEach(a => vid.setAttribute(a, ''));
vid.setAttribute('preload', 'none');
const show = () => { if (bg) bg.style.opacity='0'; if (btn) btn.classList.add('ugc-play-hidden'); };
const hide = () => { if (btn) btn.classList.remove('ugc-play-hidden'); };
vid.addEventListener('playing', show); vid.addEventListener('pause', hide);
vid.addEventListener('waiting', hide); vid.addEventListener('stalled', hide);
vid.addEventListener('error', () => { vid.style.display='none'; if(bg) bg.style.opacity='1'; if(btn) btn.style.display='none'; });
const io = new IntersectionObserver(entries => {
entries.forEach(e => {
if (e.isIntersecting) {
if (!vid.querySelector('source')) {
if (vid.dataset.mp4) { const s=document.createElement('source'); s.src=vid.dataset.mp4; s.type='video/mp4'; vid.appendChild(s); }
if (vid.dataset.webm) { const s=document.createElement('source'); s.src=vid.dataset.webm; s.type='video/webm; codecs="vp9,opus"'; vid.appendChild(s); }
vid.load();
}
vid.play().catch(() => {});
} else { vid.pause(); }
});
}, { threshold: 0.1, rootMargin: '0px 80px' });
io.observe(cardEl);
cardEl.addEventListener('click', () => { vid.paused ? vid.play().catch(()=>{}) : vid.pause(); });
}
function makeVideo(src) {
const card = document.createElement('div'); card.className = 'ugc-card ugc-video';
const bg = document.createElement('div'); bg.className = 'ugc-vid-bg'; card.appendChild(bg);
const vid = document.createElement('video');
if (/\.(mp4|mov)$/i.test(src)) { vid.dataset.mp4 = DIR + src; }
else if (/\.webm$/i.test(src)) { vid.dataset.mp4 = DIR + src.replace(/\.webm$/i, '.mp4'); vid.dataset.webm = DIR + src; }
card.appendChild(vid);
const btn = document.createElement('div'); btn.className = 'ugc-play';
btn.innerHTML = '';
card.appendChild(btn);
wireVideo(vid, bg, btn, card);
return card;
}
function makeImage(src) {
const card = document.createElement('div'); card.className = 'ugc-card ugc-img';
const img = document.createElement('img'); img.src = DIR + src; img.alt = 'Kundomdöme'; img.loading = 'lazy'; img.decoding = 'async';
card.appendChild(img); return card;
}
const STAR = '';
function makeTpCard(r) {
const card = document.createElement('div'); card.className = 'ugc-card ugc-tp';
const head = document.createElement('div'); head.className = 'ugc-tp-head';
const stars = document.createElement('div'); stars.className = 'ugc-tp-stars'; stars.innerHTML = STAR.repeat(5);
const logo = document.createElement('span'); logo.className = 'ugc-tp-logo'; logo.textContent = 'Trustpilot';
head.appendChild(stars); head.appendChild(logo); card.appendChild(head);
const txt = document.createElement('p'); txt.className = 'ugc-tp-text';
txt.textContent = '“' + r.text + '”'; card.appendChild(txt);
const foot = document.createElement('div'); foot.className = 'ugc-tp-foot';
const name = document.createElement('span'); name.className = 'ugc-tp-name'; name.textContent = r.name;
const badge = document.createElement('span'); badge.className = 'ugc-tp-badge'; badge.textContent = 'Verifierad köp';
foot.appendChild(name); foot.appendChild(badge); card.appendChild(foot);
return card;
}
function cloneCard(orig) {
const clone = orig.cloneNode(true);
const ov = orig.querySelector('video'), cv = clone.querySelector('video');
if (ov && cv) {
cv.dataset.mp4 = ov.dataset.mp4 || ''; cv.dataset.webm = ov.dataset.webm || '';
Array.from(cv.querySelectorAll('source')).forEach(s => s.remove());
cv.setAttribute('preload', 'none');
wireVideo(cv, clone.querySelector('.ugc-vid-bg'), clone.querySelector('.ugc-play'), clone);
}
return clone;
}
async function init() {
let files = [
'téléchargement (1).webp',
'téléchargement (2).webp',
'téléchargement (3).webp',
'téléchargement (4).webp',
'téléchargement.webp'
];
if (!files.length) return;
const vids = files.filter(f => VID_RE.test(f)), imgs = files.filter(f => IMG_RE.test(f));
const mediaList = []; let vi = 0, ii = 0;
while (vi < vids.length || ii < imgs.length) {
if (vi < vids.length) mediaList.push({ v: true, s: vids[vi++] });
if (ii < imgs.length) mediaList.push({ v: false, s: imgs[ii++] });
}
// pattern: [media, tp, tp] per group; phaseOffset shifts which slot starts
function buildCards(phaseOffset) {
const cards = []; const groupCount = Math.max(mediaList.length, 4);
let mi = 0, ri = 0;
for (let g = 0; g < groupCount; g++) {
for (let p = 0; p < 3; p++) {
const slot = (p + phaseOffset) % 3;
if (slot === 0) { const m = mediaList[mi % mediaList.length]; mi++; cards.push(m.v ? makeVideo(m.s) : makeImage(m.s)); }
else { cards.push(makeTpCard(REVIEWS[ri % REVIEWS.length])); ri++; }
}
}
return cards;
}
// row 1: media → tp → tp → … row 2: tp → tp → media → …
const cards1 = buildCards(0); cards1.forEach(c => track1.appendChild(c));
const cards2 = buildCards(1); cards2.forEach(c => track2.appendChild(c));
requestAnimationFrame(() => requestAnimationFrame(() => {
setWidth1 = cards1.reduce((sum, c) => sum + c.offsetWidth + GAP, 0);
setWidth2 = cards2.reduce((sum, c) => sum + c.offsetWidth + GAP, 0);
const minW = (window.innerWidth || 800) * 3;
let n1 = 0; while (n1 * setWidth1 < minW) { cards1.forEach(c => track1.appendChild(cloneCard(c))); n1++; }
let n2 = 0; while (n2 * setWidth2 < minW) { cards2.forEach(c => track2.appendChild(cloneCard(c))); n2++; }
offset2 = -setWidth2; // row 2 starts right, scrolls left-to-right
tick();
}));
}
document.addEventListener('visibilitychange', () => { isPaused = document.hidden; });
init();
return () => { if (rafId) cancelAnimationFrame(rafId); };
}, []);
return (
Omdömen
2 400+ nöjda kunder.
De pratar för oss.
);
}
function WhatsAppFloat() {
return (
{ e.currentTarget.style.transform='scale(1.1)'; e.currentTarget.style.boxShadow='0 8px 32px rgba(37,211,102,0.5), 0 4px 12px rgba(0,0,0,0.18)'; }}
onMouseLeave={e => { e.currentTarget.style.transform='scale(1)'; e.currentTarget.style.boxShadow='0 4px 20px rgba(37,211,102,0.4), 0 2px 8px rgba(0,0,0,0.15)'; }}
>
);
}
Object.assign(window, {
ReceiptBad, ReceiptGood, ReceiptCompare,
FeatureSavings, HowItWorks, DeviceCompat, Testimonials,
Pricing, FAQ, FinalCta, Footer, RecentlyBought, WhatsAppFloat,
});