Diamond Pendants by Avi & Co.
- APNDT-8016 Avi & Co. 8204
- APNDT-1560 Avi & Co. 6620
- PNDT-1546 Avi & Co. 6256
- ANKLC-5219 Avi & Co. 8202
<style>
/* Keep swap smooth + make sure Magento theme CSS doesn't fight you */
.products-grid .product-item .product-image-wrapper img,
.products-list .product-item .product-image-wrapper img {
transition: opacity 120ms ease-in-out !important;
}
.category-text {
display: none !important;
}
</style>
<script>
(function () {
const IMG_SELECTOR =
'.products-grid .product-item .product-image-wrapper img,' +
'.products-list .product-item .product-image-wrapper img';
// Replace _1.jpg -> _2.jpg (keeps querystring if present)
function swapSuffixTo2(url) {
return url.replace(/-1(\.jpg)(\?.*)?$/i, '-2$1$2');
}
// If the current image is a cached Magento URL like:
// /media/catalog/product/cache/<hash>/r/o/filename_1.jpg
// this returns the ORIGINAL:
// /media/catalog/product/r/o/filename_1.jpg
function toOriginalMediaUrl(url) {
try {
const u = new URL(url, location.origin);
const marker = '/media/catalog/product/cache/';
const idx = u.pathname.indexOf(marker);
if (idx === -1) return u.href; // already not cached
const after = u.pathname.slice(idx + marker.length); // "<hash>/r/o/filename_1.jpg"
const parts = after.split('/');
parts.shift(); // remove "<hash>"
u.pathname = '/media/catalog/product/' + parts.join('/');
return u.href;
} catch (e) {
return url;
}
}
function getCurrentSrc(img) {
return img.currentSrc || img.getAttribute('src') || img.getAttribute('data-src') || '';
}
function attachHoverSwap(img) {
if (!img || img.dataset.hoverSwapAttached === '1') return;
img.dataset.hoverSwapAttached = '1';
const originalShown = getCurrentSrc(img);
if (!originalShown) return;
// Candidate 1: try same cached path with _2 (may 404 due to different cache hash)
const cachedCandidate2 = swapSuffixTo2(originalShown);
// Candidate 2: guaranteed path by stripping cache hash and using original media + _2
const originalMedia1 = toOriginalMediaUrl(originalShown);
const originalMedia2 = swapSuffixTo2(originalMedia1);
// Pick a working hover URL by preloading cachedCandidate2 first; fallback to originalMedia2
let hoverSrc = originalMedia2;
const probe = new Image();
probe.onload = function () { hoverSrc = cachedCandidate2; };
probe.onerror = function () { hoverSrc = originalMedia2; };
probe.src = cachedCandidate2;
// Store the "default" listing src so we revert exactly
const defaultSrc = originalShown;
const host = img.closest('.product-item') || img;
function setImgSrc(newSrc) {
if (!newSrc) return;
// prevent srcset from overriding our swap
img.removeAttribute('srcset');
img.style.opacity = '0.01';
// small delay so opacity transition is visible
setTimeout(function () {
img.setAttribute('src', newSrc);
img.style.opacity = '1';
}, 30);
}
function onEnter() {
setImgSrc(hoverSrc);
}
function onLeave() {
setImgSrc(defaultSrc);
}
host.addEventListener('mouseenter', onEnter, { passive: true });
host.addEventListener('mouseleave', onLeave, { passive: true });
// Optional keyboard accessibility (tab focus)
host.addEventListener('focusin', onEnter, { passive: true });
host.addEventListener('focusout', onLeave, { passive: true });
}
function scanAndAttach() {
document.querySelectorAll(IMG_SELECTOR).forEach(attachHoverSwap);
}
// Initial attach (some themes load product grid after DOMContentLoaded)
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', scanAndAttach);
} else {
scanAndAttach();
}
// Catch products inserted later (layered nav, ajax, infinite scroll, etc.)
const observer = new MutationObserver(function () { scanAndAttach(); });
observer.observe(document.documentElement, { childList: true, subtree: true });
})();
</script>




