{"title":"Home page","description":null,"products":[{"product_id":"retatrutide-20mg","title":"Retatrutide 20mg","description":"Retatrutide is a synthetic peptide, supplied at 20mg per vial as a lyophilised powder. Manufactured and HPLC verified by our supplier to a 99%+ purity standard.\n\u003cbr\u003e\u003cbr\u003e\n\u003cdiv class=\"as-desc-reconstitution\"\u003e\n  Requires bacteriostatic water for reconstitution —\n  \u003cbutton type=\"button\" class=\"as-desc-add-btn\" data-variant-id=\"58724358750592\"\u003e\n    \u003cspan class=\"as-desc-add-btn__label\"\u003eAdd to basket for £4.99\u003c\/span\u003e\n    \u003cspan class=\"as-desc-add-btn__check\" aria-hidden=\"true\"\u003e✓\u003c\/span\u003e\n  \u003c\/button\u003e\n  \u003cspan class=\"as-desc-add-status\" aria-live=\"polite\"\u003e\u003c\/span\u003e\n\u003c\/div\u003e\n\n\u003cstyle\u003e\n  .as-desc-reconstitution { font-size: 0.95em; }\n\n  .as-desc-add-btn {\n    position: relative;\n    display: inline-flex;\n    align-items: center;\n    justify-content: center;\n    overflow: hidden;\n    background: #16233d;\n    color: #ffffff;\n    font-weight: 600;\n    font-size: 0.92em;\n    padding: 7px 16px;\n    border-radius: 6px;\n    border: none;\n    cursor: pointer;\n    line-height: 1.3;\n    transition: background 0.18s ease, transform 0.12s ease, box-shadow 0.18s ease;\n  }\n  .as-desc-add-btn:hover {\n    background: #0e1830;\n    transform: translateY(-1px);\n    box-shadow: 0 4px 12px rgba(20, 33, 61, 0.25);\n  }\n  .as-desc-add-btn:active { transform: translateY(0); }\n\n  .as-desc-add-btn__check {\n    display: inline-block;\n    margin-left: 0;\n    max-width: 0;\n    opacity: 0;\n    transform: scale(0.5);\n    transition: max-width 0.25s ease, opacity 0.2s ease, transform 0.25s ease, margin-left 0.25s ease;\n  }\n\n  .as-desc-add-btn.is-loading { pointer-events: none; }\n  .as-desc-add-btn.is-loading .as-desc-add-btn__label { opacity: 0.55; }\n  .as-desc-add-btn.is-loading::after {\n    content: \"\";\n    position: absolute;\n    right: 10px;\n    width: 12px;\n    height: 12px;\n    border-radius: 50%;\n    border: 2px solid rgba(255, 255, 255, 0.35);\n    border-top-color: #ffffff;\n    animation: as-desc-spin 0.6s linear infinite;\n  }\n\n  .as-desc-add-btn.is-added { background: #1f7a4d; }\n  .as-desc-add-btn.is-added .as-desc-add-btn__check {\n    max-width: 20px;\n    opacity: 1;\n    transform: scale(1);\n    margin-left: 6px;\n  }\n\n  .as-desc-add-btn.is-error {\n    background: #b3261e;\n    animation: as-desc-shake 0.4s ease;\n  }\n\n  @keyframes as-desc-spin { to { transform: rotate(360deg); } }\n  @keyframes as-desc-shake {\n    10%, 90% { transform: translateX(-1px); }\n    20%, 80% { transform: translateX(2px); }\n    30%, 50%, 70% { transform: translateX(-3px); }\n    40%, 60% { transform: translateX(3px); }\n  }\n  @media (prefers-reduced-motion: reduce) {\n    .as-desc-add-btn, .as-desc-add-btn__check, .as-desc-add-btn.is-loading::after {\n      animation: none !important;\n      transition: none !important;\n    }\n  }\n\n  .as-desc-add-status {\n    margin-left: 8px;\n    font-size: 0.85em;\n    font-weight: 600;\n    color: #16233d;\n  }\n\u003c\/style\u003e\n\n\u003cscript type=\"module\"\u003e\n  if (!window.__asDescAddInit) {\n    window.__asDescAddInit = true;\n\n    \/\/ Import the theme's real cart event classes so our button plugs into\n    \/\/ the same event contract product-form-component uses. This is what\n    \/\/ makes the cart drawer content update (and animate) immediately,\n    \/\/ instead of only being told to open.\n    const eventsModulePromise = import('@shopify\/events');\n\n    document.addEventListener('click', async function (e) {\n      const btn = e.target.closest \u0026\u0026 e.target.closest('.as-desc-add-btn');\n      if (!btn) return;\n      e.preventDefault();\n      if (btn.classList.contains('is-loading')) return;\n\n      const variantId = btn.getAttribute('data-variant-id');\n      const statusEl = btn.parentElement ? btn.parentElement.querySelector('.as-desc-add-status') : null;\n\n      btn.classList.remove('is-added', 'is-error');\n      btn.classList.add('is-loading');\n\n      const root = (window.Shopify \u0026\u0026 Shopify.routes \u0026\u0026 Shopify.routes.root) ? Shopify.routes.root : '\/';\n      const addUrl = (window.Theme \u0026\u0026 Theme.routes \u0026\u0026 Theme.routes.cart_add_url) ? Theme.routes.cart_add_url : (root + 'cart\/add.js');\n\n      \/\/ Collect every cart-items-component's section id, exactly like\n      \/\/ product-form-component does, so the add-to-cart response includes\n      \/\/ fresh rendered HTML for the drawer (and full cart page, if present).\n      const cartItemsComponents = document.querySelectorAll('cart-items-component');\n      const sectionIds = [];\n      cartItemsComponents.forEach(function (el) {\n        if (el \u0026\u0026 el.dataset \u0026\u0026 el.dataset.sectionId) sectionIds.push(el.dataset.sectionId);\n      });\n\n      const formData = new FormData();\n      formData.append('id', variantId);\n      formData.append('quantity', '1');\n      if (sectionIds.length) formData.append('sections', sectionIds.join(','));\n\n      let CartLinesUpdateEvent = null;\n      let deferred = null;\n      try {\n        const mod = await eventsModulePromise;\n        CartLinesUpdateEvent = mod.CartLinesUpdateEvent;\n        deferred = CartLinesUpdateEvent.createPromise();\n        \/\/ Dispatch on document, same target the cart drawer\/cart-items\n        \/\/ components listen on, carrying our deferred promise.\n        document.dispatchEvent(\n          new CartLinesUpdateEvent({\n            action: 'add',\n            context: 'product',\n            lines: [{ merchandiseId: variantId, quantity: 1 }],\n            promise: deferred.promise,\n          })\n        );\n      } catch (err) {\n        CartLinesUpdateEvent = null;\n      }\n\n      fetch(addUrl, {\n        method: 'POST',\n        headers: { Accept: 'application\/json' },\n        body: formData,\n      })\n        .then(function (res) {\n          return res.json().then(function (data) {\n            if (!res.ok) throw new Error(data.description || data.message || 'Could not add to cart');\n            return data;\n          });\n        })\n        .then(async function (response) {\n          btn.classList.remove('is-loading');\n          btn.classList.add('is-added');\n          if (statusEl) statusEl.textContent = 'Added';\n\n          if (CartLinesUpdateEvent \u0026\u0026 deferred) {\n            \/\/ Resolve with the same shape product-form-component resolves\n            \/\/ with: cart summary + a detail payload including any rendered\n            \/\/ section HTML, so cart-items-component morphs the drawer in\n            \/\/ (with its built-in row\/scale animation) instead of us having\n            \/\/ to fake it.\n            let cartJson = null;\n            try {\n              const cartRes = await fetch(root + 'cart.js', { headers: { Accept: 'application\/json' } });\n              cartJson = await cartRes.json();\n            } catch (err) {\n              cartJson = null;\n            }\n\n            deferred.resolve({\n              cart: cartJson ? CartLinesUpdateEvent.createCartFromAjaxResponse(cartJson) : undefined,\n              detail: {\n                items: cartJson ? cartJson.items : undefined,\n                source: 'as-desc-add-btn',\n                sourceId: 'as-desc-add-btn',\n                itemCount: 1,\n                sections: response.sections,\n                didError: false,\n              },\n            });\n          }\n\n          setTimeout(function () {\n            btn.classList.remove('is-added');\n            if (statusEl) statusEl.textContent = '';\n          }, 2500);\n        })\n        .catch(function (err) {\n          btn.classList.remove('is-loading');\n          btn.classList.add('is-error');\n          if (statusEl) statusEl.textContent = err.message || 'Error adding to cart';\n          if (CartLinesUpdateEvent \u0026\u0026 deferred) deferred.reject(err);\n          setTimeout(function () {\n            btn.classList.remove('is-error');\n            if (statusEl) statusEl.textContent = '';\n          }, 3000);\n        });\n    });\n  }\n\u003c\/script\u003e","brand":"ASPeptides","offers":[{"title":"20mg","offer_id":58724358193536,"sku":"RETA-20MG","price":67.99,"currency_code":"GBP","in_stock":true}],"thumbnail_url":"\/\/cdn.shopify.com\/s\/files\/1\/1012\/4538\/7136\/files\/retatrutide-3ml-vial_c537971c-9564-4a19-b01d-59a47c901b33.png?v=1787230897"}],"url":"https:\/\/aspeptides.co.uk\/collections\/frontpage.oembed","provider":"ASPeptides","version":"1.0","type":"link"}