{"product_id":"designer-fragrances","title":"Designer Fragrances – YSL \u0026 Prada","description":"\u003cp data-pm-slice=\"1 3 []\"\u003e\u003cspan\u003eDiscover a curated selection of luxury designer fragrances from \u003c\/span\u003e\u003cstrong\u003e\u003cspan\u003eYves Saint Laurent and Prada\u003c\/span\u003e\u003c\/strong\u003e\u003cspan\u003e, featuring the captivating Black Opium collection and the elegant Prada Paradoxe Eau de Parfum.\u003c\/span\u003e\u003c\/p\u003e\n\u003cp\u003e\u003cspan\u003eEach fragrance offers its own distinctive scent profile, blending sophisticated floral notes with warm, sensual undertones. Choose your preferred fragrance and size from the options below to view the corresponding price.\u003c\/span\u003e\u003c\/p\u003e\n\u003cp\u003e\u003cspan\u003eWhether you're looking for an everyday signature scent, a luxurious fragrance for special occasions or the perfect gift, this collection offers an irresistible selection of iconic designer perfumes.\u003c\/span\u003e\u003c\/p\u003e\n\u003ch3\u003e\u003cspan\u003eKey Features\u003c\/span\u003e\u003c\/h3\u003e\n\u003cul data-spread=\"false\"\u003e\n\u003cli\u003e\n\u003cp\u003e\u003cstrong\u003e\u003cspan\u003eYves Saint Laurent Black Opium Neon Eau de Parfum\u003c\/span\u003e\u003c\/strong\u003e\u003cspan\u003e – 30ml\u003c\/span\u003e\u003c\/p\u003e\n\u003c\/li\u003e\n\u003cli\u003e\n\u003cp\u003e\u003cstrong\u003e\u003cspan\u003eYves Saint Laurent Black Opium Illicit Green Eau de Parfum\u003c\/span\u003e\u003c\/strong\u003e\u003cspan\u003e – 75ml\u003c\/span\u003e\u003c\/p\u003e\n\u003c\/li\u003e\n\u003cli\u003e\n\u003cp\u003e\u003cstrong\u003e\u003cspan\u003eYves Saint Laurent Black Opium Eau de Parfum\u003c\/span\u003e\u003c\/strong\u003e\u003cspan\u003e – 90ml\u003c\/span\u003e\u003c\/p\u003e\n\u003c\/li\u003e\n\u003cli\u003e\n\u003cp\u003e\u003cstrong\u003e\u003cspan\u003ePrada Paradoxe Eau de Parfum\u003c\/span\u003e\u003c\/strong\u003e\u003cspan\u003e – 90ml\u003c\/span\u003e\u003c\/p\u003e\n\u003c\/li\u003e\n\u003cli\u003e\n\u003cp\u003e\u003cspan\u003eDistinctive fragrance profiles featuring floral, sweet and sensual notes\u003c\/span\u003e\u003c\/p\u003e\n\u003c\/li\u003e\n\u003cli\u003e\n\u003cp\u003e\u003cspan\u003eLong-lasting Eau de Parfum formulations\u003c\/span\u003e\u003c\/p\u003e\n\u003c\/li\u003e\n\u003cli\u003e\n\u003cp\u003e\u003cspan\u003eOption-specific pricing that updates with your selection\u003c\/span\u003e\u003c\/p\u003e\n\u003c\/li\u003e\n\u003cli\u003e\n\u003cp\u003e\u003cspan\u003eIdeal for everyday elegance, special occasions and luxury gifting\u003c\/span\u003e\u003c\/p\u003e\n\u003c\/li\u003e\n\u003c\/ul\u003e\n\u003cstyle\u003e\n  \/* Price stays hidden until a bottle (variant) is chosen.\n     The container itself stays visible so the drop note shows. *\/\n  html.sb-pick-first [ref=\"priceContainer\"] .price__regular,\n  html.sb-pick-first [ref=\"priceContainer\"] .price__sale,\n  html.sb-pick-first [ref=\"priceContainer\"] .price-item__group,\n  html.sb-pick-first [ref=\"priceContainer\"] .product-on-sale-label {\n    display: none !important;\n  }\n\n  \/* Blur the price while the theme loads the new variant's price *\/\n  [ref=\"priceContainer\"] .price__regular,\n  [ref=\"priceContainer\"] .price__sale {\n    transition: filter .2s ease, opacity .2s ease;\n  }\n\n  html.sb-price-loading [ref=\"priceContainer\"] .price__regular,\n  html.sb-price-loading [ref=\"priceContainer\"] .price__sale {\n    filter: blur(6px);\n    opacity: .6;\n    pointer-events: none;\n  }\n\n  \/* Selected thumbnail *\/\n  .custom-product-gallery__thumb.is-active {\n    border: 2px solid #111 !important;\n  }\n\n  \/* ---------- Out of stock banner ---------- *\/\n  .custom-product-gallery__thumb,\n  .sb-main-image-wrap {\n    position: relative;\n  }\n\n  \/* Banner colours: change these to restyle it *\/\n  :root {\n    --sb-oos-bg: #0057d9;     \/* blue band *\/\n    --sb-oos-text: #ffffff;   \/* white text *\/\n    --sb-oos-border: #ffffff; \/* white borders *\/\n  }\n\n  \/* Anything with a banner clips it to its own edges *\/\n  .is-sold-out {\n    overflow: hidden !important;\n  }\n\n  \/* Band running corner to corner (bottom-left to top-right).\n     The script sets the exact angle and length for each image. *\/\n  .sb-oos-banner {\n    position: absolute !important;\n    left: 50% !important;\n    top: 50% !important;\n    width: var(--sb-oos-len, 150%) !important;\n    transform: translate(-50%, -50%) rotate(var(--sb-oos-angle, -45deg));\n    transform-origin: center;\n    z-index: 5 !important;\n    display: block !important;\n    box-sizing: border-box;\n    padding: 3px 0;\n    background: var(--sb-oos-bg);\n    color: var(--sb-oos-text);\n    border-top: 2px solid var(--sb-oos-border);\n    border-bottom: 2px solid var(--sb-oos-border);\n    box-shadow: 0 2px 6px rgba(0, 0, 0, .25);\n    font-size: 9px;\n    font-weight: 700;\n    letter-spacing: .08em;\n    line-height: 1.2;\n    text-align: center;\n    text-transform: uppercase;\n    white-space: nowrap;\n    pointer-events: none; \/* never blocks clicks, so it stays selectable *\/\n  }\n\n  .sb-main-image-wrap \u003e .sb-oos-banner {\n    padding: 12px 0;\n    border-top-width: 4px;\n    border-bottom-width: 4px;\n    font-size: 22px;\n    letter-spacing: .12em;\n  }\n\n  .sb-oos-wrap {\n    position: relative;\n    display: inline-block;\n    line-height: 0;\n  }\n\n  \/* Slightly fade out-of-stock images *\/\n  .sb-oos-wrap.is-sold-out img,\n  .custom-product-gallery__thumb.is-sold-out img,\n  .sb-main-image-wrap.is-sold-out img {\n    opacity: .6;\n  }\n\u003c\/style\u003e\n\u003cscript\u003e\n  \/\/ Switches the selected variant when a product thumbnail is clicked.\n\/\/ Set DEBUG to false once everything works.\n\nconst DEBUG = true;\n\nlet isSelecting = false;\n\nfunction log(...messages) {\n\n    if (DEBUG) {\n\n        console.log('[VariantThumbs]', ...messages);\n\n    }\n\n}\n\nfunction getProductHandle() {\n\n    const parts = window.location.pathname.split('\/products\/');\n\n    return parts[1] ? parts[1].split('\/')[0] : null;\n\n}\n\nasync function fetchProduct(handle) {\n\n    const response = await fetch('\/products\/' + handle + '.js');\n\n    return response.json();\n\n}\n\nfunction getFileName(url) {\n\n    const fileName = url.split('?')[0].split('\/').pop();\n\n    \/\/ Removes old Shopify size suffixes like _100x100 or _small\n    return fileName.replace(\/_(\\d+x\\d*|\\d*x\\d+|pico|icon|thumb|small|compact|medium|large|grande|master)(@\\dx)?(?=\\.)\/i, '');\n\n}\n\nfunction getClickedImage(target) {\n\n    if (target.matches('img')) {\n\n        return target;\n\n    }\n\n    const container = target.closest('button, a, li, [data-media-id]');\n\n    return container ? container.querySelector('img') : null;\n\n}\n\nfunction getImageUrl(image) {\n\n    return image.currentSrc || image.src || image.dataset.src || '';\n\n}\n\nfunction hasIdNearby(image, id) {\n\n    let element = image;\n\n    \/\/ Checks the image and a few parents for an attribute containing the id\n    for (let level = 0; level \u003c 5 \u0026\u0026 element; level++) {\n\n        const found = Array.from(element.attributes).some(function (attribute) {\n\n            return attribute.value.includes(String(id));\n\n        });\n\n        if (found) {\n\n            return true;\n\n        }\n\n        element = element.parentElement;\n\n    }\n\n    return false;\n\n}\n\nfunction matchesFileName(variant, image) {\n\n    if (!variant.featured_image) {\n\n        return false;\n\n    }\n\n    return getFileName(variant.featured_image.src) === getFileName(getImageUrl(image));\n\n}\n\nfunction matchesMediaId(variant, image) {\n\n    const mediaId = variant.featured_media ? variant.featured_media.id : null;\n    const imageId = variant.featured_image ? variant.featured_image.id : null;\n\n    return Boolean((mediaId \u0026\u0026 hasIdNearby(image, mediaId)) || (imageId \u0026\u0026 hasIdNearby(image, imageId)));\n\n}\n\nfunction findVariantByImage(variants, image) {\n\n    return variants.find(function (variant) {\n\n        return matchesFileName(variant, image) || matchesMediaId(variant, image);\n\n    });\n\n}\n\nfunction triggerChange(element) {\n\n    element.dispatchEvent(new Event('change', { bubbles: true }));\n\n}\n\nfunction clickInput(input) {\n\n    const label = input.id ? document.querySelector('label[for=\"' + CSS.escape(input.id) + '\"]') : null;\n\n    (label || input).click();\n\n}\n\nfunction selectRadio(scope, value) {\n\n    const radio = scope.querySelector('input[type=\"radio\"][value=\"' + CSS.escape(value) + '\"]');\n\n    if (!radio) {\n\n        return false;\n\n    }\n\n    if (radio.checked) {\n\n        return true;\n\n    }\n\n    if (radio.disabled) {\n\n        radio.checked = true;\n        triggerChange(radio);\n\n        return true;\n\n    }\n\n    clickInput(radio);\n\n    return true;\n\n}\n\nfunction selectSwatch(scope, value) {\n\n    const escaped = CSS.escape(value);\n    const swatch = scope.querySelector('[data-value=\"' + escaped + '\"]:not(input), [data-option-value=\"' + escaped + '\"]:not(input)');\n\n    if (!swatch) {\n\n        return false;\n\n    }\n\n    swatch.click();\n\n    return true;\n\n}\n\nfunction selectDropdown(scope, value) {\n\n    const selects = scope.querySelectorAll('select:not([name=\"id\"])');\n\n    for (const select of selects) {\n\n        const hasValue = Array.from(select.options).some(function (option) {\n\n            return option.value === value;\n\n        });\n\n        if (hasValue) {\n\n            select.value = value;\n            triggerChange(select);\n\n            return true;\n\n        }\n\n    }\n\n    return false;\n\n}\n\nfunction selectOption(scope, value) {\n\n    if (selectRadio(scope, value)) {\n\n        return 'radio';\n\n    }\n\n    if (selectSwatch(scope, value)) {\n\n        return 'swatch';\n\n    }\n\n    if (selectDropdown(scope, value)) {\n\n        return 'dropdown';\n\n    }\n\n    return null;\n\n}\n\nfunction selectOptions(scope, variant) {\n\n    variant.options.forEach(function (value) {\n\n        const method = selectOption(scope, value);\n\n        log(method ? 'Selected \"' + value + '\" using ' + method : 'No picker found for \"' + value + '\"');\n\n    });\n\n}\n\nfunction selectVariantId(scope, variantId) {\n\n    const idInput = scope.querySelector('form[action*=\"\/cart\/add\"] [name=\"id\"]');\n\n    if (!idInput) {\n\n        log('No hidden variant id field found');\n\n        return;\n\n    }\n\n    idInput.value = variantId;\n    triggerChange(idInput);\n\n}\n\nfunction selectVariant(variant) {\n\n    const scope = document.querySelector('main') || document;\n\n    isSelecting = true;\n\n    selectOptions(scope, variant);\n    selectVariantId(scope, variant.id);\n\n    isSelecting = false;\n\n}\n\nfunction handleThumbnailClick(event, variants) {\n\n    if (isSelecting) {\n\n        return;\n\n    }\n\n    const image = getClickedImage(event.target);\n\n    if (!image) {\n\n        return;\n\n    }\n\n    const variant = findVariantByImage(variants, image);\n\n    if (!variant) {\n\n        log('Clicked image matched no variant:', getFileName(getImageUrl(image)));\n\n        return;\n\n    }\n\n    log('Clicked image matched variant:', variant.title);\n    selectVariant(variant);\n\n}\n\nfunction logVariantImages(variants) {\n\n    variants.forEach(function (variant) {\n\n        const fileName = variant.featured_image ? getFileName(variant.featured_image.src) : 'NO IMAGE SET';\n\n        log('Variant \"' + variant.title + '\" image:', fileName);\n\n    });\n\n}\n\nasync function initVariantThumbnails() {\n\n    const handle = getProductHandle();\n\n    if (!handle) {\n\n        return;\n\n    }\n\n    const product = await fetchProduct(handle);\n\n    log('Loaded ' + product.variants.length + ' variants');\n    logVariantImages(product.variants);\n\n    \/\/ Capture phase so the theme's own thumbnail handler can't block it\n    document.addEventListener('click', function (event) {\n\n        handleThumbnailClick(event, product.variants);\n\n    }, true);\n\n}\n\ninitVariantThumbnails();\n\u003c\/script\u003e","brand":"Perfume","offers":[{"title":"Yves Saint Laurent Black Opium EDP - 90ml","offer_id":46358128984243,"sku":null,"price":1899.0,"currency_code":"ZAR","in_stock":true},{"title":"Yves Saint Laurent Black Opium Illicit Green Eau de Parfum - 75ml","offer_id":46358129017011,"sku":null,"price":1599.0,"currency_code":"ZAR","in_stock":true},{"title":"Yves Saint Laurent Black Opium Neon Eau de Parfum – 30ml","offer_id":46358129049779,"sku":null,"price":999.0,"currency_code":"ZAR","in_stock":true},{"title":"Prada Paradoxe Eau de purfume - 90ml","offer_id":46358158770355,"sku":null,"price":1999.0,"currency_code":"ZAR","in_stock":true}],"thumbnail_url":"\/\/cdn.shopify.com\/s\/files\/1\/0749\/5299\/9091\/files\/5EF8A3F1-3A8E-4A2B-B276-FFC2C5B0DD0C.png?v=1793693740","url":"https:\/\/www.specialbuyssa.co.za\/products\/designer-fragrances","provider":"Special Buys","version":"1.0","type":"link"}